mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(omp): resume by mode, not silently as claude, and support --continue
resumeHistorySession() never sent mode when recreating a session from a history/session-manager row, so the server default silently opened a plain Claude session for every non-claude row -- reproduced live: OMP rows spawned Claude sessions on click. Thread the row's mode through every call site (welcome list, session manager, mobile overview) and only send the Claude-specific resumeSessionId for claude rows. Codeman has no live PTY-reattach outside server boot, and it's moot for OMP anyway (exiting it kills the pane's only process), so route the non-claude relaunch through each CLI's own continue-most-recent flag instead of a context-free fresh start. OMP never got one: buildOmpCommand only implemented --model/--resume despite omp --help documenting -c/--continue. Added continueSession to OmpConfig end-to-end (type, schema, builder) mirroring the existing opencode/pi/grok/deepseek fields, and wired resumeHistorySession to use it. Verified live: told a real omp session a secret, exited it, closed the tab without killing tmux, relaunched with --continue in the same directory, and had it recall the secret.
This commit is contained in:
+8
-3
@@ -914,9 +914,14 @@ function buildOmpCommand(config?: OmpConfig): string {
|
||||
if (safeModel) parts.push('--model', safeModel);
|
||||
}
|
||||
|
||||
if (config?.resumeSessionId) {
|
||||
const safeId = /^[a-zA-Z0-9._-]+$/.test(config.resumeSessionId) ? config.resumeSessionId : undefined;
|
||||
if (safeId) parts.push('--resume', safeId);
|
||||
// --resume and --continue conflict; a valid explicit session id wins,
|
||||
// mirroring the sibling builders (grok/pi/opencode).
|
||||
const safeId =
|
||||
config?.resumeSessionId && /^[a-zA-Z0-9._-]+$/.test(config.resumeSessionId) ? config.resumeSessionId : undefined;
|
||||
if (safeId) {
|
||||
parts.push('--resume', safeId);
|
||||
} else if (config?.continueSession) {
|
||||
parts.push('--continue');
|
||||
}
|
||||
|
||||
return parts.join(' ');
|
||||
|
||||
@@ -350,6 +350,8 @@ export interface OmpConfig {
|
||||
model?: string;
|
||||
/** Resume a previous conversation (passed via --resume). */
|
||||
resumeSessionId?: string;
|
||||
/** Continue the most recent session in this directory (passed via --continue). */
|
||||
continueSession?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -389,7 +389,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
async resumeMobileOverviewSession(sessionId) {
|
||||
const row = (this._mobileOverviewPastRows || []).find((r) => r.id === sessionId);
|
||||
if (!row || !row.workingDir) return;
|
||||
await this.resumeHistorySession(row.claudeSessionId || row.id, row.workingDir, row.name || undefined);
|
||||
await this.resumeHistorySession(row.claudeSessionId || row.id, row.workingDir, row.name || undefined, row.mode);
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -670,7 +670,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
} else if (record.workingDir) {
|
||||
// History rows are keyed by the Claude conversation UUID; resumed
|
||||
// sessions carry theirs separately as claudeSessionId.
|
||||
void this.resumeHistorySession(s.claudeSessionId || s.sessionId, record.workingDir);
|
||||
void this.resumeHistorySession(s.claudeSessionId || s.sessionId, record.workingDir, undefined, s.mode);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -692,7 +692,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
btn.append(...parts);
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.resumeHistorySession(s.sessionId, s.workingDir, s.name);
|
||||
this.resumeHistorySession(s.sessionId, s.workingDir, s.name, s.mode);
|
||||
});
|
||||
container.appendChild(btn);
|
||||
}
|
||||
|
||||
@@ -2193,7 +2193,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (isLive && this.sessions.has(s.sessionId)) {
|
||||
this.selectSession(s.sessionId);
|
||||
} else {
|
||||
this.resumeHistorySession(s.claudeSessionId || s.sessionId, s.workingDir || '', s.name);
|
||||
this.resumeHistorySession(s.claudeSessionId || s.sessionId, s.workingDir || '', s.name, s.mode);
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -2436,7 +2436,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
} else {
|
||||
// Resume by the Claude conversation UUID when present (resumed sessions
|
||||
// carry theirs separately from their Codeman id).
|
||||
this.resumeHistorySession(s.claudeSessionId || s.sessionId, s.workingDir || '', s.name);
|
||||
this.resumeHistorySession(s.claudeSessionId || s.sessionId, s.workingDir || '', s.name, s.mode);
|
||||
}
|
||||
this.closeSessionManager?.();
|
||||
closeMenu();
|
||||
@@ -2904,7 +2904,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
return `w${startNumber}-${dirName}`;
|
||||
},
|
||||
|
||||
async resumeHistorySession(sessionId, workingDir, existingName) {
|
||||
async resumeHistorySession(sessionId, workingDir, existingName, mode) {
|
||||
// Close the run mode menu if open
|
||||
document.getElementById('runModeMenu')?.classList.remove('active');
|
||||
// Close folder history modal if open
|
||||
@@ -2925,13 +2925,38 @@ Object.assign(CodemanApp.prototype, {
|
||||
const globalSettings = this.loadAppSettingsFromStorage();
|
||||
const envOverrides = this.buildEnvOverrides(this.getCaseSettings(caseName), globalSettings);
|
||||
const effort = this.getEffortSetting(globalSettings);
|
||||
// `resumeSessionId` is a Claude conversation UUID (server reads it from
|
||||
// ~/.claude/projects); an external-CLI row has no such thing, so sending
|
||||
// it there gets silently ignored while the OMITTED `mode` field defaults
|
||||
// the create to plain claude — reproducing whatever conversation THAT
|
||||
// uuid happens to collide with instead of the row's own backend. Row mode
|
||||
// wins here. Codeman has no cross-restart PTY-reattach outside server
|
||||
// boot, so "resume" for a non-claude row means relaunching the CLI's own
|
||||
// continue-most-recent flag (opencode/pi/grok/omp --continue, deepseek
|
||||
// resumeSession) in the same directory — real conversation continuity,
|
||||
// just not the literal old process.
|
||||
const effectiveMode = mode || 'claude';
|
||||
const modeConfigKey = {
|
||||
opencode: 'openCodeConfig',
|
||||
pi: 'piConfig',
|
||||
grok: 'grokConfig',
|
||||
omp: 'ompConfig',
|
||||
}[effectiveMode];
|
||||
const modeConfig =
|
||||
modeConfigKey
|
||||
? { [modeConfigKey]: { continueSession: true } }
|
||||
: effectiveMode === 'deepseek'
|
||||
? { deepSeekConfig: { resumeSession: true } }
|
||||
: {};
|
||||
const createRes = await fetch('/api/sessions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workingDir,
|
||||
name,
|
||||
resumeSessionId: sessionId,
|
||||
mode: effectiveMode,
|
||||
...(effectiveMode === 'claude' ? { resumeSessionId: sessionId } : {}),
|
||||
...modeConfig,
|
||||
...(Object.keys(envOverrides).length > 0 ? { envOverrides } : {}),
|
||||
...(effort ? { effort } : {}),
|
||||
}),
|
||||
|
||||
@@ -361,6 +361,7 @@ const OmpConfigSchema = z
|
||||
.max(100)
|
||||
.regex(/^[a-zA-Z0-9._-]+$/)
|
||||
.optional(),
|
||||
continueSession: z.boolean().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ describe('Session Manager unified list', () => {
|
||||
const [historyRecord, , historyOptions] = app._buildHistoryItem.mock.calls[1];
|
||||
expect(historyRecord).toMatchObject({ sessionId: 'conv-uuid-1', sizeBytes: 2048, firstPrompt: 'old prompt' });
|
||||
historyOptions.onActivate();
|
||||
expect(app.resumeHistorySession).toHaveBeenCalledWith('conv-uuid-1', '/repo/old');
|
||||
expect(app.resumeHistorySession).toHaveBeenCalledWith('conv-uuid-1', '/repo/old', undefined, undefined);
|
||||
});
|
||||
|
||||
it('surfaces an error message instead of an empty list when the endpoint fails', async () => {
|
||||
|
||||
@@ -85,6 +85,26 @@ describe('OMP spawn command', () => {
|
||||
).toBe('omp');
|
||||
});
|
||||
|
||||
it('continues the most recent session when no explicit resume id is given', () => {
|
||||
expect(
|
||||
buildSpawnCommand({
|
||||
mode: 'omp',
|
||||
sessionId: 'abc12345',
|
||||
ompConfig: { continueSession: true },
|
||||
})
|
||||
).toBe('omp --continue');
|
||||
});
|
||||
|
||||
it('prefers an explicit --resume id over --continue', () => {
|
||||
expect(
|
||||
buildSpawnCommand({
|
||||
mode: 'omp',
|
||||
sessionId: 'abc12345',
|
||||
ompConfig: { resumeSessionId: 'session-99', continueSession: true },
|
||||
})
|
||||
).toBe('omp --resume session-99');
|
||||
});
|
||||
|
||||
it('drops unsafe model strings from the spawn command', () => {
|
||||
expect(
|
||||
buildSpawnCommand({
|
||||
|
||||
Reference in New Issue
Block a user