@adhdev/daemon-core 0.7.44 → 0.7.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapters/provider-cli-adapter.d.ts +1 -0
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +11 -2
- package/dist/config/chat-history.d.ts +29 -2
- package/dist/config/config.d.ts +4 -0
- package/dist/config/recent-activity.d.ts +3 -1
- package/dist/config/saved-sessions.d.ts +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4623 -3973
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4617 -3969
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +24 -1
- package/dist/providers/contracts.d.ts +3 -0
- package/dist/providers/provider-instance.d.ts +1 -0
- package/dist/shared-types.d.ts +2 -0
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +12 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +11 -2
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +11 -2
- package/node_modules/@adhdev/session-host-core/dist/index.mjs.map +1 -1
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/boot/daemon-lifecycle.ts +19 -15
- package/src/cli-adapters/provider-cli-adapter.ts +11 -5
- package/src/cli-adapters/pty-transport.ts +1 -0
- package/src/cli-adapters/session-host-transport.ts +19 -0
- package/src/commands/chat-commands.ts +28 -8
- package/src/commands/cli-manager.ts +259 -22
- package/src/commands/router.ts +52 -1
- package/src/config/chat-history.ts +193 -10
- package/src/config/config.d.ts +4 -0
- package/src/config/config.ts +6 -0
- package/src/config/recent-activity.ts +13 -2
- package/src/config/saved-sessions.ts +73 -0
- package/src/daemon/dev-auto-implement.ts +23 -5
- package/src/daemon/dev-server.ts +22 -4
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +205 -4
- package/src/providers/contracts.ts +3 -0
- package/src/providers/provider-instance.d.ts +1 -0
- package/src/providers/provider-instance.ts +1 -0
- package/src/session-host/runtime-support.ts +1 -0
- package/src/shared-types.d.ts +2 -0
- package/src/shared-types.ts +2 -0
- package/src/status/builders.ts +1 -0
- package/src/status/snapshot.ts +1 -0
|
@@ -276,7 +276,7 @@ function shSingleQuote(arg: string): string {
|
|
|
276
276
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
function estimatePromptDisplayLines(text: string, cols =
|
|
279
|
+
function estimatePromptDisplayLines(text: string, cols = 80): number {
|
|
280
280
|
const normalized = String(text || '').replace(/\r/g, '');
|
|
281
281
|
if (!normalized) return 1;
|
|
282
282
|
return normalized
|
|
@@ -433,7 +433,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
433
433
|
/** Full accumulated raw PTY output (with ANSI) */
|
|
434
434
|
private accumulatedRawBuffer: string = '';
|
|
435
435
|
/** Current visible terminal screen snapshot */
|
|
436
|
-
private terminalScreen = new TerminalScreen(
|
|
436
|
+
private terminalScreen = new TerminalScreen(24, 80);
|
|
437
437
|
/** Max accumulated buffer size (last 50KB) */
|
|
438
438
|
private static readonly MAX_ACCUMULATED_BUFFER = 50000;
|
|
439
439
|
private currentTurnScope: TurnParseScope | null = null;
|
|
@@ -620,8 +620,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
620
620
|
}
|
|
621
621
|
|
|
622
622
|
const ptyOpts = {
|
|
623
|
-
cols:
|
|
624
|
-
rows:
|
|
623
|
+
cols: 80,
|
|
624
|
+
rows: 24,
|
|
625
625
|
cwd: this.workingDir,
|
|
626
626
|
env: buildCliSpawnEnv(process.env, spawnConfig.env),
|
|
627
627
|
};
|
|
@@ -684,7 +684,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
684
684
|
this.spawnAt = Date.now();
|
|
685
685
|
this.startupParseGate = true;
|
|
686
686
|
this.startupBuffer = '';
|
|
687
|
-
this.terminalScreen.reset(
|
|
687
|
+
this.terminalScreen.reset(24, 80);
|
|
688
688
|
this.pendingTerminalQueryTail = '';
|
|
689
689
|
this.currentTurnScope = null;
|
|
690
690
|
this.ready = false;
|
|
@@ -1025,6 +1025,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1025
1025
|
title: parsed.title || this.cliName,
|
|
1026
1026
|
messages: parsed.messages,
|
|
1027
1027
|
activeModal: parsed.activeModal ?? this.activeModal,
|
|
1028
|
+
providerSessionId: typeof parsed.providerSessionId === 'string' ? parsed.providerSessionId : undefined,
|
|
1028
1029
|
};
|
|
1029
1030
|
}
|
|
1030
1031
|
|
|
@@ -1222,6 +1223,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1222
1223
|
return this.ptyProcess.getMetadata();
|
|
1223
1224
|
}
|
|
1224
1225
|
|
|
1226
|
+
updateRuntimeMeta(meta: Record<string, unknown>, replace = false): void {
|
|
1227
|
+
if (!this.ptyProcess || typeof this.ptyProcess.updateMeta !== 'function') return;
|
|
1228
|
+
this.ptyProcess.updateMeta(meta, replace);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1225
1231
|
cancel(): void { this.shutdown(); }
|
|
1226
1232
|
|
|
1227
1233
|
async saveAndStop(): Promise<void> {
|
|
@@ -43,6 +43,7 @@ export interface PtyRuntimeTransport {
|
|
|
43
43
|
kill(): void;
|
|
44
44
|
clearBuffer?(): void;
|
|
45
45
|
detach?(): void;
|
|
46
|
+
updateMeta?(meta: Record<string, unknown>, replace?: boolean): void;
|
|
46
47
|
getMetadata?(): PtyRuntimeMetadata | null;
|
|
47
48
|
onData(callback: (data: string) => void): void;
|
|
48
49
|
onExit(callback: (info: { exitCode: number }) => void): void;
|
|
@@ -171,6 +171,25 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
updateMeta(meta: Record<string, unknown>, replace = false): void {
|
|
175
|
+
this.enqueue(async () => {
|
|
176
|
+
const response = await this.client.request<SessionHostRecord>({
|
|
177
|
+
type: 'update_session_meta',
|
|
178
|
+
payload: {
|
|
179
|
+
sessionId: this.options.runtimeId,
|
|
180
|
+
meta,
|
|
181
|
+
replace,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
if (!response?.success) {
|
|
185
|
+
throw new Error(response.error || `Failed to update runtime meta ${this.options.runtimeId}`);
|
|
186
|
+
}
|
|
187
|
+
if (response.result) {
|
|
188
|
+
this.updateMetadata(response.result);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
174
193
|
private async boot(): Promise<void> {
|
|
175
194
|
await this.client.connect();
|
|
176
195
|
this.unsubscribe = this.client.onEvent((event: SessionHostEvent) => this.handleEvent(event));
|
|
@@ -59,6 +59,19 @@ function buildRecentSendKey(h: CommandHelpers, args: any, provider: any, text: s
|
|
|
59
59
|
return `${transport}:${target}:${text.trim()}`;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
63
|
+
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
64
|
+
if (explicit) return explicit;
|
|
65
|
+
|
|
66
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
67
|
+
if (!targetSessionId) return undefined;
|
|
68
|
+
|
|
69
|
+
const instance = h.ctx.instanceManager?.getInstance(targetSessionId) as any;
|
|
70
|
+
const state = instance?.getState?.();
|
|
71
|
+
const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
|
|
72
|
+
return providerSessionId || targetSessionId;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
function isRecentDuplicateSend(key: string): boolean {
|
|
63
76
|
const now = Date.now();
|
|
64
77
|
for (const [candidate, ts] of recentSendByTarget.entries()) {
|
|
@@ -72,11 +85,11 @@ function isRecentDuplicateSend(key: string): boolean {
|
|
|
72
85
|
|
|
73
86
|
export async function handleChatHistory(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
74
87
|
const { agentType, offset, limit } = args;
|
|
75
|
-
const
|
|
88
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
76
89
|
try {
|
|
77
90
|
const provider = h.getProvider(agentType);
|
|
78
91
|
const agentStr = provider?.type || agentType || getCurrentProviderType(h);
|
|
79
|
-
const result = readChatHistory(agentStr, offset || 0, limit || 30,
|
|
92
|
+
const result = readChatHistory(agentStr, offset || 0, limit || 30, historySessionId);
|
|
80
93
|
return { success: true, ...result, agent: agentStr };
|
|
81
94
|
} catch (e: any) {
|
|
82
95
|
return { success: false, error: e.message };
|
|
@@ -86,6 +99,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
86
99
|
export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
87
100
|
const provider = h.getProvider(args?.agentType);
|
|
88
101
|
const transport = getTargetTransport(h, provider);
|
|
102
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
89
103
|
|
|
90
104
|
const _log = (msg: string) => LOG.debug('Command', `[read_chat] ${msg}`);
|
|
91
105
|
|
|
@@ -120,7 +134,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
120
134
|
provider?.type || 'unknown_extension',
|
|
121
135
|
parsed.messages || [],
|
|
122
136
|
parsed.title,
|
|
123
|
-
args?.targetSessionId
|
|
137
|
+
args?.targetSessionId,
|
|
138
|
+
historySessionId,
|
|
124
139
|
);
|
|
125
140
|
return { success: true, ...parsed };
|
|
126
141
|
}
|
|
@@ -142,7 +157,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
142
157
|
stream.agentType,
|
|
143
158
|
stream.messages || [],
|
|
144
159
|
undefined,
|
|
145
|
-
args?.targetSessionId
|
|
160
|
+
args?.targetSessionId,
|
|
161
|
+
historySessionId,
|
|
146
162
|
);
|
|
147
163
|
return { success: true, messages: stream.messages || [], status: stream.status, agentType: stream.agentType };
|
|
148
164
|
}
|
|
@@ -169,11 +185,12 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
169
185
|
if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
|
|
170
186
|
if (parsed && typeof parsed === 'object') {
|
|
171
187
|
_log(`Webview OK: ${parsed.messages?.length || 0} msgs`);
|
|
172
|
-
|
|
188
|
+
h.historyWriter.appendNewMessages(
|
|
173
189
|
provider?.type || getCurrentProviderType(h, 'unknown_webview'),
|
|
174
190
|
parsed.messages || [],
|
|
175
191
|
parsed.title,
|
|
176
|
-
args?.targetSessionId
|
|
192
|
+
args?.targetSessionId,
|
|
193
|
+
historySessionId,
|
|
177
194
|
);
|
|
178
195
|
return { success: true, ...parsed };
|
|
179
196
|
}
|
|
@@ -197,7 +214,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
197
214
|
provider?.type || getCurrentProviderType(h, 'unknown_ide'),
|
|
198
215
|
parsed.messages || [],
|
|
199
216
|
parsed.title,
|
|
200
|
-
args?.targetSessionId
|
|
217
|
+
args?.targetSessionId,
|
|
218
|
+
historySessionId,
|
|
201
219
|
);
|
|
202
220
|
return { success: true, ...parsed };
|
|
203
221
|
}
|
|
@@ -216,13 +234,15 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
216
234
|
const provider = h.getProvider(args?.agentType);
|
|
217
235
|
const transport = getTargetTransport(h, provider);
|
|
218
236
|
const dedupeKey = buildRecentSendKey(h, args, provider, text);
|
|
237
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
219
238
|
|
|
220
239
|
const _logSendSuccess = (method: string, targetAgent?: string) => {
|
|
221
240
|
h.historyWriter.appendNewMessages(
|
|
222
241
|
targetAgent || provider?.type || getCurrentProviderType(h, 'unknown_agent'),
|
|
223
242
|
[{ role: 'user', content: text, receivedAt: Date.now() }],
|
|
224
243
|
undefined, // title
|
|
225
|
-
args?.targetSessionId
|
|
244
|
+
args?.targetSessionId,
|
|
245
|
+
historySessionId,
|
|
226
246
|
);
|
|
227
247
|
return { success: true, sent: true, method, targetAgent };
|
|
228
248
|
};
|
|
@@ -14,10 +14,12 @@ import { detectCLI } from '../detection/cli-detector.js';
|
|
|
14
14
|
import { loadConfig, saveConfig } from '../config/config.js';
|
|
15
15
|
import { getWorkspaceState, resolveLaunchDirectory } from '../config/workspaces.js';
|
|
16
16
|
import { appendRecentActivity } from '../config/recent-activity.js';
|
|
17
|
+
import { upsertSavedProviderSession } from '../config/saved-sessions.js';
|
|
17
18
|
import { CliProviderInstance } from '../providers/cli-provider-instance.js';
|
|
18
19
|
import { AcpProviderInstance } from '../providers/acp-provider-instance.js';
|
|
19
20
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
20
21
|
import { ProviderLoader } from '../providers/provider-loader.js';
|
|
22
|
+
import type { ProviderModule, ProviderResumeCapability } from '../providers/contracts.js';
|
|
21
23
|
import type { CliAdapter } from '../cli-adapter-types.js';
|
|
22
24
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
23
25
|
import type { SessionRegistry } from '../sessions/registry.js';
|
|
@@ -29,7 +31,7 @@ export interface CliManagerDeps {
|
|
|
29
31
|
/** Server connection — injected into adapter */
|
|
30
32
|
getServerConn(): any | null;
|
|
31
33
|
/** P2P — PTY output transmit */
|
|
32
|
-
getP2p(): {
|
|
34
|
+
getP2p(): { broadcastSessionOutput(key: string, data: string): void } | null;
|
|
33
35
|
/** StatusReporter callback */
|
|
34
36
|
onStatusChange(): void;
|
|
35
37
|
removeAgentTracking(key: string): void;
|
|
@@ -47,6 +49,7 @@ export interface CliTransportFactoryParams {
|
|
|
47
49
|
providerType: string;
|
|
48
50
|
workspace: string;
|
|
49
51
|
cliArgs?: string[];
|
|
52
|
+
providerSessionId?: string;
|
|
50
53
|
attachExisting?: boolean;
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -60,6 +63,7 @@ export interface HostedCliRuntimeDescriptor {
|
|
|
60
63
|
cliType: string;
|
|
61
64
|
workspace: string;
|
|
62
65
|
cliArgs?: string[];
|
|
66
|
+
providerSessionId?: string;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
const chalkApi: any = (chalk as any)?.yellow
|
|
@@ -71,6 +75,148 @@ function colorize(color: 'red' | 'green' | 'yellow' | 'cyan', text: string): str
|
|
|
71
75
|
return typeof fn === 'function' ? fn(text) : text;
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
type CliLaunchMode = 'new' | 'resume' | 'manual';
|
|
79
|
+
|
|
80
|
+
type CliSessionBinding = {
|
|
81
|
+
cliArgs?: string[];
|
|
82
|
+
providerSessionId?: string;
|
|
83
|
+
launchMode: CliLaunchMode;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function isUuid(value: string): boolean {
|
|
87
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function readArgValue(args: string[], flags: string[]): string | undefined {
|
|
91
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
92
|
+
const arg = args[index];
|
|
93
|
+
for (const flag of flags) {
|
|
94
|
+
if (arg === flag) {
|
|
95
|
+
const next = args[index + 1];
|
|
96
|
+
if (next && !next.startsWith('-')) return next;
|
|
97
|
+
}
|
|
98
|
+
const prefix = `${flag}=`;
|
|
99
|
+
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function hasArg(args: string[], flags: string[]): boolean {
|
|
106
|
+
return args.some((arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function expandResumeArgs(template: string[] | undefined, sessionId: string): string[] | undefined {
|
|
110
|
+
if (!Array.isArray(template) || template.length === 0) return undefined;
|
|
111
|
+
return template.map((part) => part === '{{id}}' ? sessionId : part);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readCodexResumeSessionId(args: string[]): string | undefined {
|
|
115
|
+
const resumeIndex = args.findIndex((arg) => arg === 'resume' || arg === 'fork');
|
|
116
|
+
if (resumeIndex < 0) return undefined;
|
|
117
|
+
const candidate = args[resumeIndex + 1];
|
|
118
|
+
if (!candidate || candidate.startsWith('-')) return undefined;
|
|
119
|
+
return candidate;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function detectExplicitProviderSessionId(
|
|
123
|
+
normalizedType: string,
|
|
124
|
+
args: string[],
|
|
125
|
+
): { providerSessionId?: string; launchMode: CliLaunchMode } {
|
|
126
|
+
const explicitResumeId = readArgValue(args, ['--resume', '-r']);
|
|
127
|
+
if (explicitResumeId) {
|
|
128
|
+
return { providerSessionId: explicitResumeId, launchMode: 'resume' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const explicitSessionFlagId = readArgValue(args, ['--session']);
|
|
132
|
+
if (explicitSessionFlagId) {
|
|
133
|
+
return {
|
|
134
|
+
providerSessionId: explicitSessionFlagId,
|
|
135
|
+
launchMode: 'resume',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const explicitSessionId = readArgValue(args, ['--session-id']);
|
|
140
|
+
if (explicitSessionId) {
|
|
141
|
+
if (normalizedType === 'goose-cli' && !hasArg(args, ['--resume', '-r'])) {
|
|
142
|
+
return { launchMode: 'manual' };
|
|
143
|
+
}
|
|
144
|
+
const isResume = normalizedType === 'goose-cli'
|
|
145
|
+
? hasArg(args, ['--resume', '-r'])
|
|
146
|
+
: (hasArg(args, ['--continue']) || hasArg(args, ['--resume', '-r']));
|
|
147
|
+
return {
|
|
148
|
+
providerSessionId: explicitSessionId,
|
|
149
|
+
launchMode: isResume ? 'resume' : 'new',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (normalizedType === 'codex-cli') {
|
|
154
|
+
const codexSessionId = readCodexResumeSessionId(args);
|
|
155
|
+
if (codexSessionId) {
|
|
156
|
+
return { providerSessionId: codexSessionId, launchMode: 'resume' };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { launchMode: 'manual' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function supportsExplicitSessionResume(resume?: ProviderResumeCapability): boolean {
|
|
164
|
+
return !!(resume?.supported && Array.isArray(resume.resumeSessionArgs) && resume.resumeSessionArgs.length > 0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function supportsExplicitSessionStart(resume?: ProviderResumeCapability): boolean {
|
|
168
|
+
return !!(resume?.supported && Array.isArray(resume.newSessionArgs) && resume.newSessionArgs.length > 0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolveCliSessionBinding(
|
|
172
|
+
provider: ProviderModule | undefined,
|
|
173
|
+
normalizedType: string,
|
|
174
|
+
cliArgs?: string[],
|
|
175
|
+
requestedResumeSessionId?: string,
|
|
176
|
+
): CliSessionBinding {
|
|
177
|
+
const baseArgs = Array.isArray(cliArgs) ? [...cliArgs] : undefined;
|
|
178
|
+
const resume = provider?.resume;
|
|
179
|
+
if (!resume?.supported) {
|
|
180
|
+
return { cliArgs: baseArgs, launchMode: 'manual' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const explicit = detectExplicitProviderSessionId(normalizedType, baseArgs || []);
|
|
184
|
+
if (explicit.providerSessionId) {
|
|
185
|
+
return {
|
|
186
|
+
cliArgs: baseArgs,
|
|
187
|
+
providerSessionId: explicit.providerSessionId,
|
|
188
|
+
launchMode: explicit.launchMode,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (requestedResumeSessionId) {
|
|
193
|
+
if (resume.sessionIdFormat === 'uuid' && !isUuid(requestedResumeSessionId)) {
|
|
194
|
+
throw new Error(`Invalid ${provider?.displayName || provider?.name || normalizedType} session ID: ${requestedResumeSessionId}`);
|
|
195
|
+
}
|
|
196
|
+
const resumeSessionArgs = expandResumeArgs(resume.resumeSessionArgs, requestedResumeSessionId);
|
|
197
|
+
if (!resumeSessionArgs) {
|
|
198
|
+
return { cliArgs: baseArgs, launchMode: 'manual' };
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
cliArgs: [...(baseArgs || []), ...resumeSessionArgs],
|
|
202
|
+
providerSessionId: requestedResumeSessionId,
|
|
203
|
+
launchMode: 'resume',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!supportsExplicitSessionStart(resume)) {
|
|
208
|
+
return { cliArgs: baseArgs, launchMode: 'manual' };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const providerSessionId = crypto.randomUUID();
|
|
212
|
+
const newSessionArgs = expandResumeArgs(resume.newSessionArgs, providerSessionId);
|
|
213
|
+
return {
|
|
214
|
+
cliArgs: [...(baseArgs || []), ...(newSessionArgs || [])],
|
|
215
|
+
providerSessionId,
|
|
216
|
+
launchMode: 'new',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
74
220
|
// ─── DaemonCliManager ────────────────────────────
|
|
75
221
|
|
|
76
222
|
export class DaemonCliManager {
|
|
@@ -107,13 +253,26 @@ export class DaemonCliManager {
|
|
|
107
253
|
kind: 'ide' | 'cli' | 'acp';
|
|
108
254
|
providerType: string;
|
|
109
255
|
providerName: string;
|
|
256
|
+
providerSessionId?: string;
|
|
110
257
|
workspace?: string;
|
|
111
258
|
currentModel?: string;
|
|
112
259
|
sessionId?: string;
|
|
113
260
|
title?: string;
|
|
114
261
|
}): void {
|
|
115
262
|
try {
|
|
116
|
-
|
|
263
|
+
let nextConfig = appendRecentActivity(loadConfig(), entry);
|
|
264
|
+
if (entry.providerSessionId && (entry.kind === 'cli' || entry.kind === 'acp')) {
|
|
265
|
+
nextConfig = upsertSavedProviderSession(nextConfig, {
|
|
266
|
+
kind: entry.kind,
|
|
267
|
+
providerType: entry.providerType,
|
|
268
|
+
providerName: entry.providerName,
|
|
269
|
+
providerSessionId: entry.providerSessionId,
|
|
270
|
+
workspace: entry.workspace,
|
|
271
|
+
currentModel: entry.currentModel,
|
|
272
|
+
title: entry.title,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
saveConfig(nextConfig);
|
|
117
276
|
} catch (e) {
|
|
118
277
|
console.error(colorize('red', ` ✗ Failed to save recent activity: ${e}`));
|
|
119
278
|
}
|
|
@@ -124,6 +283,7 @@ export class DaemonCliManager {
|
|
|
124
283
|
providerType: string,
|
|
125
284
|
workspace: string,
|
|
126
285
|
cliArgs?: string[],
|
|
286
|
+
providerSessionId?: string,
|
|
127
287
|
attachExisting = false,
|
|
128
288
|
): PtyTransportFactory | undefined {
|
|
129
289
|
return this.deps.createPtyTransportFactory?.({
|
|
@@ -131,6 +291,7 @@ export class DaemonCliManager {
|
|
|
131
291
|
providerType,
|
|
132
292
|
workspace,
|
|
133
293
|
cliArgs,
|
|
294
|
+
providerSessionId,
|
|
134
295
|
attachExisting,
|
|
135
296
|
}) || undefined;
|
|
136
297
|
}
|
|
@@ -140,6 +301,7 @@ export class DaemonCliManager {
|
|
|
140
301
|
workingDir: string,
|
|
141
302
|
cliArgs: string[] | undefined,
|
|
142
303
|
runtimeId: string,
|
|
304
|
+
providerSessionId?: string,
|
|
143
305
|
attachExisting = false,
|
|
144
306
|
): CliAdapter {
|
|
145
307
|
// cliType normalize (Resolve alias)
|
|
@@ -150,7 +312,14 @@ export class DaemonCliManager {
|
|
|
150
312
|
if (provider && provider.category === 'cli' && provider.patterns && provider.spawn) {
|
|
151
313
|
console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
|
|
152
314
|
const resolvedProvider = this.providerLoader.resolve(normalizedType) || provider;
|
|
153
|
-
const transportFactory = this.getTransportFactory(
|
|
315
|
+
const transportFactory = this.getTransportFactory(
|
|
316
|
+
runtimeId,
|
|
317
|
+
normalizedType,
|
|
318
|
+
workingDir,
|
|
319
|
+
cliArgs,
|
|
320
|
+
providerSessionId,
|
|
321
|
+
attachExisting,
|
|
322
|
+
);
|
|
154
323
|
return new ProviderCliAdapter(resolvedProvider as any, workingDir, cliArgs, transportFactory);
|
|
155
324
|
}
|
|
156
325
|
|
|
@@ -191,18 +360,37 @@ export class DaemonCliManager {
|
|
|
191
360
|
provider: any,
|
|
192
361
|
settings: Record<string, any>,
|
|
193
362
|
attachExisting = false,
|
|
363
|
+
options?: {
|
|
364
|
+
providerSessionId?: string;
|
|
365
|
+
launchMode?: CliLaunchMode;
|
|
366
|
+
onProviderSessionResolved?: (info: {
|
|
367
|
+
instanceId: string;
|
|
368
|
+
providerType: string;
|
|
369
|
+
providerName: string;
|
|
370
|
+
workspace: string;
|
|
371
|
+
providerSessionId: string;
|
|
372
|
+
previousProviderSessionId?: string;
|
|
373
|
+
}) => void;
|
|
374
|
+
},
|
|
194
375
|
): Promise<void> {
|
|
195
376
|
const instanceManager = this.deps.getInstanceManager();
|
|
196
377
|
const sessionRegistry = this.deps.getSessionRegistry?.() || null;
|
|
197
378
|
if (!instanceManager) throw new Error('InstanceManager not available');
|
|
198
|
-
const transportFactory = this.getTransportFactory(
|
|
199
|
-
|
|
379
|
+
const transportFactory = this.getTransportFactory(
|
|
380
|
+
key,
|
|
381
|
+
normalizedType,
|
|
382
|
+
resolvedDir,
|
|
383
|
+
cliArgs,
|
|
384
|
+
options?.providerSessionId,
|
|
385
|
+
attachExisting,
|
|
386
|
+
);
|
|
387
|
+
const cliInstance = new CliProviderInstance(provider, resolvedDir, cliArgs, key, transportFactory, options);
|
|
200
388
|
try {
|
|
201
389
|
await instanceManager.addInstance(key, cliInstance, {
|
|
202
390
|
serverConn: this.deps.getServerConn(),
|
|
203
391
|
settings,
|
|
204
392
|
onPtyData: (data: string) => {
|
|
205
|
-
this.deps.getP2p()?.
|
|
393
|
+
this.deps.getP2p()?.broadcastSessionOutput(cliInstance.instanceId, data);
|
|
206
394
|
},
|
|
207
395
|
});
|
|
208
396
|
sessionRegistry?.register({
|
|
@@ -225,7 +413,13 @@ export class DaemonCliManager {
|
|
|
225
413
|
|
|
226
414
|
// ─── Session start/management ──────────────────────────────
|
|
227
415
|
|
|
228
|
-
async startSession(
|
|
416
|
+
async startSession(
|
|
417
|
+
cliType: string,
|
|
418
|
+
workingDir: string,
|
|
419
|
+
cliArgs?: string[],
|
|
420
|
+
initialModel?: string,
|
|
421
|
+
options?: { resumeSessionId?: string },
|
|
422
|
+
): Promise<{ runtimeSessionId: string; providerSessionId?: string }> {
|
|
229
423
|
const trimmed = (workingDir || '').trim();
|
|
230
424
|
if (!trimmed) throw new Error('working directory required');
|
|
231
425
|
const resolvedDir = trimmed.startsWith('~')
|
|
@@ -319,7 +513,7 @@ export class DaemonCliManager {
|
|
|
319
513
|
title: provider.displayName || provider.name || normalizedType,
|
|
320
514
|
});
|
|
321
515
|
this.deps.onStatusChange();
|
|
322
|
-
return;
|
|
516
|
+
return { runtimeSessionId: sessionId };
|
|
323
517
|
}
|
|
324
518
|
|
|
325
519
|
// ─── CLI category handling (existing) ───
|
|
@@ -331,8 +525,9 @@ export class DaemonCliManager {
|
|
|
331
525
|
console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
|
|
332
526
|
}
|
|
333
527
|
|
|
334
|
-
// ─── Resolve launch options →
|
|
335
|
-
const
|
|
528
|
+
// ─── Resolve launch options → provider session binding ───
|
|
529
|
+
const sessionBinding = resolveCliSessionBinding(provider, normalizedType, cliArgs, options?.resumeSessionId);
|
|
530
|
+
const resolvedCliArgs = sessionBinding.cliArgs;
|
|
336
531
|
|
|
337
532
|
// If InstanceManager exists, manage as CliProviderInstance unified
|
|
338
533
|
const instanceManager = this.deps.getInstanceManager();
|
|
@@ -347,11 +542,32 @@ export class DaemonCliManager {
|
|
|
347
542
|
resolvedProvider,
|
|
348
543
|
{},
|
|
349
544
|
false,
|
|
545
|
+
{
|
|
546
|
+
providerSessionId: sessionBinding.providerSessionId,
|
|
547
|
+
launchMode: sessionBinding.launchMode,
|
|
548
|
+
onProviderSessionResolved: ({ providerSessionId, providerName, providerType, workspace }) => {
|
|
549
|
+
this.persistRecentActivity({
|
|
550
|
+
kind: 'cli',
|
|
551
|
+
providerType,
|
|
552
|
+
providerName,
|
|
553
|
+
providerSessionId,
|
|
554
|
+
workspace,
|
|
555
|
+
title: providerName,
|
|
556
|
+
});
|
|
557
|
+
},
|
|
558
|
+
},
|
|
350
559
|
);
|
|
351
560
|
console.log(colorize('green', ` ✓ CLI started: ${cliInfo.displayName} v${cliInfo.version || 'unknown'} in ${resolvedDir}`));
|
|
352
561
|
} else {
|
|
353
562
|
// Fallback: InstanceManager without directly adapter manage
|
|
354
|
-
const adapter = this.createAdapter(
|
|
563
|
+
const adapter = this.createAdapter(
|
|
564
|
+
cliType,
|
|
565
|
+
resolvedDir,
|
|
566
|
+
resolvedCliArgs,
|
|
567
|
+
key,
|
|
568
|
+
sessionBinding.providerSessionId,
|
|
569
|
+
false,
|
|
570
|
+
);
|
|
355
571
|
try {
|
|
356
572
|
await adapter.spawn();
|
|
357
573
|
} catch (spawnErr: any) {
|
|
@@ -380,7 +596,7 @@ export class DaemonCliManager {
|
|
|
380
596
|
|
|
381
597
|
if (typeof adapter.setOnPtyData === 'function') {
|
|
382
598
|
adapter.setOnPtyData((data: string) => {
|
|
383
|
-
this.deps.getP2p()?.
|
|
599
|
+
this.deps.getP2p()?.broadcastSessionOutput(key, data);
|
|
384
600
|
});
|
|
385
601
|
}
|
|
386
602
|
|
|
@@ -392,6 +608,7 @@ export class DaemonCliManager {
|
|
|
392
608
|
kind: 'cli',
|
|
393
609
|
providerType: normalizedType,
|
|
394
610
|
providerName: provider?.displayName || provider?.name || normalizedType,
|
|
611
|
+
providerSessionId: sessionBinding.providerSessionId,
|
|
395
612
|
workspace: resolvedDir,
|
|
396
613
|
currentModel: initialModel,
|
|
397
614
|
sessionId: key,
|
|
@@ -399,6 +616,10 @@ export class DaemonCliManager {
|
|
|
399
616
|
});
|
|
400
617
|
|
|
401
618
|
this.deps.onStatusChange();
|
|
619
|
+
return {
|
|
620
|
+
runtimeSessionId: key,
|
|
621
|
+
providerSessionId: sessionBinding.providerSessionId,
|
|
622
|
+
};
|
|
402
623
|
}
|
|
403
624
|
|
|
404
625
|
async stopSession(key: string): Promise<void> {
|
|
@@ -464,6 +685,12 @@ export class DaemonCliManager {
|
|
|
464
685
|
if (!providerMeta || providerMeta.category !== 'cli') continue;
|
|
465
686
|
|
|
466
687
|
const resolvedProvider = this.providerLoader.resolve(normalizedType) || providerMeta;
|
|
688
|
+
const sessionBinding = resolveCliSessionBinding(
|
|
689
|
+
resolvedProvider,
|
|
690
|
+
normalizedType,
|
|
691
|
+
record.cliArgs,
|
|
692
|
+
record.providerSessionId,
|
|
693
|
+
);
|
|
467
694
|
try {
|
|
468
695
|
await this.registerCliInstance(
|
|
469
696
|
record.runtimeId,
|
|
@@ -474,6 +701,10 @@ export class DaemonCliManager {
|
|
|
474
701
|
resolvedProvider,
|
|
475
702
|
{},
|
|
476
703
|
true,
|
|
704
|
+
{
|
|
705
|
+
providerSessionId: sessionBinding.providerSessionId,
|
|
706
|
+
launchMode: 'manual',
|
|
707
|
+
},
|
|
477
708
|
);
|
|
478
709
|
restored += 1;
|
|
479
710
|
LOG.info('CLI', `♻ Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
|
|
@@ -562,17 +793,23 @@ export class DaemonCliManager {
|
|
|
562
793
|
const launchSource = resolved.source;
|
|
563
794
|
if (!cliType) throw new Error('cliType required');
|
|
564
795
|
|
|
565
|
-
await this.startSession(
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
}
|
|
796
|
+
const started = await this.startSession(
|
|
797
|
+
cliType,
|
|
798
|
+
dir,
|
|
799
|
+
args?.cliArgs,
|
|
800
|
+
args?.initialModel,
|
|
801
|
+
{ resumeSessionId: args?.resumeSessionId },
|
|
802
|
+
);
|
|
574
803
|
|
|
575
|
-
return {
|
|
804
|
+
return {
|
|
805
|
+
success: true,
|
|
806
|
+
cliType,
|
|
807
|
+
dir,
|
|
808
|
+
id: started.runtimeSessionId,
|
|
809
|
+
sessionId: started.runtimeSessionId,
|
|
810
|
+
providerSessionId: started.providerSessionId,
|
|
811
|
+
launchSource,
|
|
812
|
+
};
|
|
576
813
|
}
|
|
577
814
|
case 'stop_cli': {
|
|
578
815
|
const cliType = args?.cliType;
|