@adhdev/daemon-core 0.7.45 → 0.8.0
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 +34 -0
- package/dist/cli-adapters/pty-transport.d.ts +1 -0
- package/dist/cli-adapters/session-host-transport.d.ts +1 -0
- package/dist/commands/cli-manager.d.ts +11 -2
- package/dist/config/chat-history.d.ts +32 -2
- package/dist/config/config.d.ts +5 -1
- package/dist/config/recent-activity.d.ts +3 -1
- package/dist/config/saved-sessions.d.ts +22 -0
- package/dist/daemon/dev-auto-implement.d.ts +18 -2
- package/dist/daemon/dev-cli-debug.d.ts +82 -0
- package/dist/daemon/dev-server.d.ts +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6122 -4038
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6114 -4032
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +29 -1
- package/dist/providers/contracts.d.ts +11 -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 +9 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +9 -0
- 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 +424 -7
- package/src/cli-adapters/pty-transport.ts +1 -0
- package/src/cli-adapters/session-host-transport.ts +32 -1
- package/src/commands/chat-commands.ts +36 -8
- package/src/commands/cli-manager.ts +259 -22
- package/src/commands/router.ts +52 -1
- package/src/config/chat-history.ts +197 -10
- package/src/config/config.d.ts +4 -0
- package/src/config/config.ts +8 -2
- package/src/config/recent-activity.ts +13 -2
- package/src/config/saved-sessions.ts +73 -0
- package/src/daemon/dev-auto-implement.ts +394 -43
- package/src/daemon/dev-cli-debug.ts +839 -0
- package/src/daemon/dev-server.ts +51 -5
- package/src/index.ts +2 -0
- package/src/providers/cli-provider-instance.ts +283 -4
- package/src/providers/contracts.ts +11 -0
- package/src/providers/provider-instance.d.ts +1 -0
- package/src/providers/provider-instance.ts +1 -0
- package/src/providers/provider-loader.ts +39 -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
|
@@ -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;
|
|
@@ -11,6 +11,7 @@ import type { PtyRuntimeMetadata, PtyRuntimeTransport, PtySpawnOptions, PtyTrans
|
|
|
11
11
|
interface SessionHostPtyTransportFactoryOptions {
|
|
12
12
|
endpoint?: SessionHostEndpoint;
|
|
13
13
|
appName?: string;
|
|
14
|
+
ensureReady?: () => Promise<void>;
|
|
14
15
|
clientId: string;
|
|
15
16
|
runtimeId: string;
|
|
16
17
|
providerType: string;
|
|
@@ -171,8 +172,38 @@ class SessionHostRuntimeTransport implements PtyRuntimeTransport {
|
|
|
171
172
|
});
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
updateMeta(meta: Record<string, unknown>, replace = false): void {
|
|
176
|
+
this.enqueue(async () => {
|
|
177
|
+
const response = await this.client.request<SessionHostRecord>({
|
|
178
|
+
type: 'update_session_meta',
|
|
179
|
+
payload: {
|
|
180
|
+
sessionId: this.options.runtimeId,
|
|
181
|
+
meta,
|
|
182
|
+
replace,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (!response?.success) {
|
|
186
|
+
throw new Error(response.error || `Failed to update runtime meta ${this.options.runtimeId}`);
|
|
187
|
+
}
|
|
188
|
+
if (response.result) {
|
|
189
|
+
this.updateMetadata(response.result);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
174
194
|
private async boot(): Promise<void> {
|
|
175
|
-
|
|
195
|
+
if (typeof this.options.ensureReady === 'function') {
|
|
196
|
+
await this.options.ensureReady();
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
await this.client.connect();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (typeof this.options.ensureReady !== 'function') {
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
await this.options.ensureReady();
|
|
205
|
+
await this.client.connect();
|
|
206
|
+
}
|
|
176
207
|
this.unsubscribe = this.client.onEvent((event: SessionHostEvent) => this.handleEvent(event));
|
|
177
208
|
|
|
178
209
|
let record: SessionHostRecord | null = null;
|
|
@@ -23,6 +23,13 @@ function getTargetedCliAdapter(h: CommandHelpers, args: any, providerType?: stri
|
|
|
23
23
|
return h.getCliAdapter(args?.targetSessionId || providerType || h.currentSession?.providerType || h.currentManagerKey);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function getTargetInstance(h: CommandHelpers, args: any) {
|
|
27
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
28
|
+
const sessionId = targetSessionId || h.currentSession?.sessionId || '';
|
|
29
|
+
if (!sessionId) return null;
|
|
30
|
+
return h.ctx.instanceManager?.getInstance(sessionId) as any;
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
function getTargetTransport(h: CommandHelpers, provider?: any): SessionTransport | null {
|
|
27
34
|
if (h.currentSession?.transport) return h.currentSession.transport;
|
|
28
35
|
switch (provider?.category) {
|
|
@@ -59,6 +66,19 @@ function buildRecentSendKey(h: CommandHelpers, args: any, provider: any, text: s
|
|
|
59
66
|
return `${transport}:${target}:${text.trim()}`;
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
70
|
+
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
71
|
+
if (explicit) return explicit;
|
|
72
|
+
|
|
73
|
+
const targetSessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '';
|
|
74
|
+
if (!targetSessionId) return undefined;
|
|
75
|
+
|
|
76
|
+
const instance = h.ctx.instanceManager?.getInstance(targetSessionId) as any;
|
|
77
|
+
const state = instance?.getState?.();
|
|
78
|
+
const providerSessionId = typeof state?.providerSessionId === 'string' ? state.providerSessionId.trim() : '';
|
|
79
|
+
return providerSessionId || targetSessionId;
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
function isRecentDuplicateSend(key: string): boolean {
|
|
63
83
|
const now = Date.now();
|
|
64
84
|
for (const [candidate, ts] of recentSendByTarget.entries()) {
|
|
@@ -72,11 +92,11 @@ function isRecentDuplicateSend(key: string): boolean {
|
|
|
72
92
|
|
|
73
93
|
export async function handleChatHistory(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
74
94
|
const { agentType, offset, limit } = args;
|
|
75
|
-
const
|
|
95
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
76
96
|
try {
|
|
77
97
|
const provider = h.getProvider(agentType);
|
|
78
98
|
const agentStr = provider?.type || agentType || getCurrentProviderType(h);
|
|
79
|
-
const result = readChatHistory(agentStr, offset || 0, limit || 30,
|
|
99
|
+
const result = readChatHistory(agentStr, offset || 0, limit || 30, historySessionId);
|
|
80
100
|
return { success: true, ...result, agent: agentStr };
|
|
81
101
|
} catch (e: any) {
|
|
82
102
|
return { success: false, error: e.message };
|
|
@@ -86,6 +106,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
86
106
|
export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
87
107
|
const provider = h.getProvider(args?.agentType);
|
|
88
108
|
const transport = getTargetTransport(h, provider);
|
|
109
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
89
110
|
|
|
90
111
|
const _log = (msg: string) => LOG.debug('Command', `[read_chat] ${msg}`);
|
|
91
112
|
|
|
@@ -120,7 +141,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
120
141
|
provider?.type || 'unknown_extension',
|
|
121
142
|
parsed.messages || [],
|
|
122
143
|
parsed.title,
|
|
123
|
-
args?.targetSessionId
|
|
144
|
+
args?.targetSessionId,
|
|
145
|
+
historySessionId,
|
|
124
146
|
);
|
|
125
147
|
return { success: true, ...parsed };
|
|
126
148
|
}
|
|
@@ -142,7 +164,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
142
164
|
stream.agentType,
|
|
143
165
|
stream.messages || [],
|
|
144
166
|
undefined,
|
|
145
|
-
args?.targetSessionId
|
|
167
|
+
args?.targetSessionId,
|
|
168
|
+
historySessionId,
|
|
146
169
|
);
|
|
147
170
|
return { success: true, messages: stream.messages || [], status: stream.status, agentType: stream.agentType };
|
|
148
171
|
}
|
|
@@ -169,11 +192,12 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
169
192
|
if (typeof parsed === 'string') { try { parsed = JSON.parse(parsed); } catch { } }
|
|
170
193
|
if (parsed && typeof parsed === 'object') {
|
|
171
194
|
_log(`Webview OK: ${parsed.messages?.length || 0} msgs`);
|
|
172
|
-
|
|
195
|
+
h.historyWriter.appendNewMessages(
|
|
173
196
|
provider?.type || getCurrentProviderType(h, 'unknown_webview'),
|
|
174
197
|
parsed.messages || [],
|
|
175
198
|
parsed.title,
|
|
176
|
-
args?.targetSessionId
|
|
199
|
+
args?.targetSessionId,
|
|
200
|
+
historySessionId,
|
|
177
201
|
);
|
|
178
202
|
return { success: true, ...parsed };
|
|
179
203
|
}
|
|
@@ -197,7 +221,8 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
197
221
|
provider?.type || getCurrentProviderType(h, 'unknown_ide'),
|
|
198
222
|
parsed.messages || [],
|
|
199
223
|
parsed.title,
|
|
200
|
-
args?.targetSessionId
|
|
224
|
+
args?.targetSessionId,
|
|
225
|
+
historySessionId,
|
|
201
226
|
);
|
|
202
227
|
return { success: true, ...parsed };
|
|
203
228
|
}
|
|
@@ -216,13 +241,15 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
216
241
|
const provider = h.getProvider(args?.agentType);
|
|
217
242
|
const transport = getTargetTransport(h, provider);
|
|
218
243
|
const dedupeKey = buildRecentSendKey(h, args, provider, text);
|
|
244
|
+
const historySessionId = getHistorySessionId(h, args);
|
|
219
245
|
|
|
220
246
|
const _logSendSuccess = (method: string, targetAgent?: string) => {
|
|
221
247
|
h.historyWriter.appendNewMessages(
|
|
222
248
|
targetAgent || provider?.type || getCurrentProviderType(h, 'unknown_agent'),
|
|
223
249
|
[{ role: 'user', content: text, receivedAt: Date.now() }],
|
|
224
250
|
undefined, // title
|
|
225
|
-
args?.targetSessionId
|
|
251
|
+
args?.targetSessionId,
|
|
252
|
+
historySessionId,
|
|
226
253
|
);
|
|
227
254
|
return { success: true, sent: true, method, targetAgent };
|
|
228
255
|
};
|
|
@@ -761,6 +788,7 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
761
788
|
(adapter as any).writeRaw?.(keys);
|
|
762
789
|
}
|
|
763
790
|
LOG.info('Command', `[resolveAction] CLI PTY → buttonIndex=${buttonIndex} "${buttons[buttonIndex] ?? '?'}"`);
|
|
791
|
+
getTargetInstance(h, args)?.recordApprovalSelection?.(buttons[buttonIndex] ?? button);
|
|
764
792
|
return { success: true, buttonIndex, button: buttons[buttonIndex] ?? button };
|
|
765
793
|
}
|
|
766
794
|
|
|
@@ -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;
|