@adhdev/daemon-core 0.8.28 → 0.8.30
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/agent-stream/manager.d.ts +1 -1
- package/dist/agent-stream/provider-adapter.d.ts +5 -0
- package/dist/commands/router.d.ts +5 -0
- package/dist/config/chat-history.d.ts +12 -0
- package/dist/index.js +552 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +552 -52
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +1 -0
- package/dist/providers/approval-utils.d.ts +7 -0
- package/dist/providers/cli-provider-instance.d.ts +3 -0
- package/dist/providers/contracts.d.ts +2 -0
- package/dist/providers/ide-provider-instance.d.ts +1 -0
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +24 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +24 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +6 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +6 -1
- 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/agent-stream/manager.ts +2 -2
- package/src/agent-stream/poller.ts +38 -1
- package/src/agent-stream/provider-adapter.ts +97 -3
- package/src/cli-adapters/provider-cli-adapter.ts +3 -0
- package/src/commands/chat-commands.ts +53 -3
- package/src/commands/cli-manager.ts +14 -0
- package/src/commands/router.ts +11 -0
- package/src/config/chat-history.ts +269 -18
- package/src/providers/acp-provider-instance.ts +17 -2
- package/src/providers/approval-utils.ts +66 -0
- package/src/providers/cli-provider-instance.ts +47 -6
- package/src/providers/contracts.d.ts +1 -0
- package/src/providers/contracts.ts +3 -1
- package/src/providers/ide-provider-instance.ts +28 -23
- package/src/providers/provider-loader.ts +26 -2
|
@@ -244,7 +244,7 @@ export class DaemonAgentStreamManager {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
async resolveSessionAction(cdp: DaemonCdpManager, sessionId: string, action: 'approve' | 'reject'): Promise<boolean> {
|
|
247
|
+
async resolveSessionAction(cdp: DaemonCdpManager, sessionId: string, action: 'approve' | 'reject', button?: string): Promise<boolean> {
|
|
248
248
|
await this.ensureSessionPanelOpen(sessionId);
|
|
249
249
|
const target = this.getSessionTarget(sessionId);
|
|
250
250
|
if (!target?.parentSessionId) return false;
|
|
@@ -255,7 +255,7 @@ export class DaemonAgentStreamManager {
|
|
|
255
255
|
try {
|
|
256
256
|
const evaluate: AgentEvaluateFn = (expr, timeout) =>
|
|
257
257
|
cdp.evaluateInSessionFrame(agent.cdpSessionId, expr, timeout);
|
|
258
|
-
return await agent.adapter.resolveAction(evaluate, action);
|
|
258
|
+
return await agent.adapter.resolveAction(evaluate, action, button);
|
|
259
259
|
} catch (e) {
|
|
260
260
|
this.logFn(`[AgentStream] resolveAction(${sessionId}) error: ${(e as Error).message}`);
|
|
261
261
|
return false;
|
|
@@ -19,6 +19,7 @@ import type { SessionRegistry } from '../sessions/registry.js';
|
|
|
19
19
|
import { reconcileIdeRuntimeSessions } from '../sessions/reconcile.js';
|
|
20
20
|
import { LOG } from '../logging/logger.js';
|
|
21
21
|
import type { AgentStreamState } from './types.js';
|
|
22
|
+
import { formatAutoApprovalMessage, pickApprovalButton } from '../providers/approval-utils.js';
|
|
22
23
|
|
|
23
24
|
// ─── Types ───
|
|
24
25
|
|
|
@@ -189,7 +190,43 @@ export class AgentStreamPoller {
|
|
|
189
190
|
|
|
190
191
|
try {
|
|
191
192
|
await agentStreamManager.syncActiveSession(cdp, parentSessionId);
|
|
192
|
-
|
|
193
|
+
let stream = await agentStreamManager.collectActiveSession(cdp, parentSessionId);
|
|
194
|
+
if (stream?.status === 'waiting_approval') {
|
|
195
|
+
const autoApprove = providerLoader.getSettings(stream.agentType).autoApprove !== false;
|
|
196
|
+
if (autoApprove && resolvedActiveSessionId) {
|
|
197
|
+
const provider = providerLoader.getMeta(stream.agentType);
|
|
198
|
+
const { label: buttonLabel } = pickApprovalButton(stream.activeModal?.buttons, provider);
|
|
199
|
+
const approved = await agentStreamManager.resolveSessionAction(cdp, resolvedActiveSessionId, 'approve', buttonLabel);
|
|
200
|
+
if (approved) {
|
|
201
|
+
const effectId = [
|
|
202
|
+
'auto_approval',
|
|
203
|
+
resolvedActiveSessionId,
|
|
204
|
+
String(stream.messages?.length || 0),
|
|
205
|
+
buttonLabel,
|
|
206
|
+
String(stream.activeModal?.message || '').trim(),
|
|
207
|
+
].join(':');
|
|
208
|
+
stream = {
|
|
209
|
+
...stream,
|
|
210
|
+
status: 'streaming',
|
|
211
|
+
activeModal: undefined,
|
|
212
|
+
effects: [
|
|
213
|
+
...(stream.effects || []),
|
|
214
|
+
{
|
|
215
|
+
type: 'message',
|
|
216
|
+
id: effectId,
|
|
217
|
+
persist: true,
|
|
218
|
+
message: {
|
|
219
|
+
role: 'system',
|
|
220
|
+
senderName: 'System',
|
|
221
|
+
kind: 'system',
|
|
222
|
+
content: formatAutoApprovalMessage(stream.activeModal?.message, buttonLabel),
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
193
230
|
this.deps.onStreamsUpdated?.(ideType, stream ? [stream] : []);
|
|
194
231
|
} catch { }
|
|
195
232
|
}
|
|
@@ -70,6 +70,64 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
70
70
|
|| /Cannot find context with specified id/i.test(reason);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
private titlesMatch(actual: string, expected: string): boolean {
|
|
74
|
+
const lhs = actual.trim().toLowerCase();
|
|
75
|
+
const rhs = expected.trim().toLowerCase();
|
|
76
|
+
if (!lhs || !rhs) return false;
|
|
77
|
+
return lhs === rhs || lhs.includes(rhs) || rhs.includes(lhs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private messageCount(state: AgentStreamState | null | undefined): number {
|
|
81
|
+
return Array.isArray(state?.messages) ? state!.messages.length : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private lastMessageSignature(state: AgentStreamState | null | undefined): string {
|
|
85
|
+
const messages = Array.isArray(state?.messages) ? state!.messages : [];
|
|
86
|
+
const last = messages[messages.length - 1] as any;
|
|
87
|
+
if (!last) return '';
|
|
88
|
+
return `${last.role || ''}:${String(last.content || '').replace(/\s+/g, ' ').trim()}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private async verifySendOutcome(
|
|
92
|
+
evaluate: AgentEvaluateFn,
|
|
93
|
+
before: AgentStreamState | null,
|
|
94
|
+
): Promise<boolean> {
|
|
95
|
+
const beforeCount = this.messageCount(before);
|
|
96
|
+
const beforeSignature = this.lastMessageSignature(before);
|
|
97
|
+
|
|
98
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
100
|
+
let state: AgentStreamState;
|
|
101
|
+
try {
|
|
102
|
+
state = await this.readChat(evaluate);
|
|
103
|
+
} catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (state.status === 'waiting_approval') {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const afterCount = this.messageCount(state);
|
|
112
|
+
const afterSignature = this.lastMessageSignature(state);
|
|
113
|
+
if (afterCount > beforeCount) return true;
|
|
114
|
+
if (afterSignature && afterSignature !== beforeSignature) return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private async readStableBaselineState(evaluate: AgentEvaluateFn): Promise<AgentStreamState | null> {
|
|
121
|
+
const first = await this.readChat(evaluate);
|
|
122
|
+
if (this.messageCount(first) > 0 || this.lastMessageSignature(first)) {
|
|
123
|
+
return first;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
127
|
+
const second = await this.readChat(evaluate);
|
|
128
|
+
return this.messageCount(second) >= this.messageCount(first) ? second : first;
|
|
129
|
+
}
|
|
130
|
+
|
|
73
131
|
async readChat(evaluate: AgentEvaluateFn): Promise<AgentStreamState> {
|
|
74
132
|
const script = this.callScript('readChat');
|
|
75
133
|
if (!script) return this.errorState('readChat script not available');
|
|
@@ -96,6 +154,9 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
96
154
|
mode: data.mode,
|
|
97
155
|
activeModal: data.activeModal,
|
|
98
156
|
};
|
|
157
|
+
if (typeof data.title === 'string' && data.title.trim()) {
|
|
158
|
+
(state as any).title = data.title.trim();
|
|
159
|
+
}
|
|
99
160
|
const controlValues = extractProviderControlValues(this.provider.controls, data);
|
|
100
161
|
if (controlValues) state.controlValues = controlValues;
|
|
101
162
|
const effects = normalizeProviderEffects(data);
|
|
@@ -120,6 +181,13 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
120
181
|
}
|
|
121
182
|
|
|
122
183
|
async sendMessage(evaluate: AgentEvaluateFn, text: string): Promise<void> {
|
|
184
|
+
let beforeState: AgentStreamState | null = null;
|
|
185
|
+
try {
|
|
186
|
+
beforeState = await this.readStableBaselineState(evaluate);
|
|
187
|
+
} catch {
|
|
188
|
+
beforeState = null;
|
|
189
|
+
}
|
|
190
|
+
|
|
123
191
|
const params = { message: text };
|
|
124
192
|
const script = this.callScript('sendMessage', params) || this.callScript('sendMessage', text);
|
|
125
193
|
if (!script) throw new Error(`[${this.agentName}] sendMessage script not available`);
|
|
@@ -138,7 +206,9 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
138
206
|
}
|
|
139
207
|
if (parsed && typeof parsed === 'object') {
|
|
140
208
|
if (parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true) {
|
|
141
|
-
|
|
209
|
+
const verified = await this.verifySendOutcome(evaluate, beforeState);
|
|
210
|
+
if (verified) return;
|
|
211
|
+
throw new Error(`[${this.agentName}] sendMessage was not observed in chat state`);
|
|
142
212
|
}
|
|
143
213
|
if (typeof parsed.error === 'string' && parsed.error.trim()) {
|
|
144
214
|
throw new Error(`[${this.agentName}] sendMessage failed: ${parsed.error}`);
|
|
@@ -151,7 +221,23 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
151
221
|
async resolveAction(evaluate: AgentEvaluateFn, action: string, button?: string): Promise<boolean> {
|
|
152
222
|
const script = this.callScript('resolveAction', { action, button });
|
|
153
223
|
if (!script) return false; // Not supported if provider has no resolveAction
|
|
154
|
-
|
|
224
|
+
const result = await evaluate(script);
|
|
225
|
+
const parsed = this.parseMaybeJson(result);
|
|
226
|
+
if (parsed === true) return true;
|
|
227
|
+
if (typeof parsed === 'string') {
|
|
228
|
+
const normalized = parsed.trim().toLowerCase();
|
|
229
|
+
return normalized === 'ok'
|
|
230
|
+
|| normalized === 'success'
|
|
231
|
+
|| normalized === 'true'
|
|
232
|
+
|| normalized === 'resolved'
|
|
233
|
+
|| normalized === 'approved'
|
|
234
|
+
|| normalized === 'rejected';
|
|
235
|
+
}
|
|
236
|
+
if (!parsed || typeof parsed !== 'object') return false;
|
|
237
|
+
return parsed.resolved === true
|
|
238
|
+
|| parsed.success === true
|
|
239
|
+
|| parsed.ok === true
|
|
240
|
+
|| parsed.found === true;
|
|
155
241
|
}
|
|
156
242
|
|
|
157
243
|
async newSession(evaluate: AgentEvaluateFn): Promise<void> {
|
|
@@ -189,7 +275,15 @@ export class ProviderStreamAdapter implements IAgentStreamAdapter {
|
|
|
189
275
|
return normalized === 'true' || normalized === 'ok' || normalized === 'switched' || normalized === 'success';
|
|
190
276
|
}
|
|
191
277
|
if (data && typeof data === 'object') {
|
|
192
|
-
|
|
278
|
+
if (data.switched === true || data.success === true || data.ok === true) return true;
|
|
279
|
+
if (typeof data.error === 'string' && data.error.trim()) return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
284
|
+
const state = await this.readChat(evaluate);
|
|
285
|
+
const title = typeof (state as any).title === 'string' ? (state as any).title : '';
|
|
286
|
+
if (this.titlesMatch(title, sessionId)) return true;
|
|
193
287
|
}
|
|
194
288
|
return false;
|
|
195
289
|
}
|
|
@@ -1241,6 +1241,9 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
1241
1241
|
private looksLikeVisibleIdlePrompt(screenText: string): boolean {
|
|
1242
1242
|
const text = String(screenText || '');
|
|
1243
1243
|
if (!text.trim()) return false;
|
|
1244
|
+
if (this.cliType === 'codex-cli' && /(^|\n)\s*[❯›>]\s+(?:Find and fix a bug in @filename|Improve documentation in @filename|Use \/skills|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Run \/review on my current changes)(?:\n|$)/im.test(text)) {
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1244
1247
|
return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text)
|
|
1245
1248
|
|| /⏎\s+send/i.test(text)
|
|
1246
1249
|
|| /\?\s*for\s*shortcuts/i.test(text)
|
|
@@ -115,6 +115,51 @@ function didProviderConfirmSend(result: any): boolean {
|
|
|
115
115
|
|| parsed.dispatched === true;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
async function readExtensionChatState(h: CommandHelpers): Promise<any | null> {
|
|
119
|
+
try {
|
|
120
|
+
const evalResult = await h.evaluateProviderScript('readChat', undefined, 50000);
|
|
121
|
+
if (!evalResult?.result) return null;
|
|
122
|
+
const parsed = parseMaybeJson(evalResult.result);
|
|
123
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getStateMessageCount(state: any): number {
|
|
130
|
+
return Array.isArray(state?.messages) ? state.messages.length : 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getStateLastSignature(state: any): string {
|
|
134
|
+
const messages = Array.isArray(state?.messages) ? state.messages : [];
|
|
135
|
+
const last = messages[messages.length - 1];
|
|
136
|
+
if (!last) return '';
|
|
137
|
+
return `${last.role || ''}:${String(last.content || '').replace(/\s+/g, ' ').trim()}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function getStableExtensionBaseline(h: CommandHelpers): Promise<any | null> {
|
|
141
|
+
const first = await readExtensionChatState(h);
|
|
142
|
+
if (getStateMessageCount(first) > 0 || getStateLastSignature(first)) return first;
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
144
|
+
const second = await readExtensionChatState(h);
|
|
145
|
+
return getStateMessageCount(second) >= getStateMessageCount(first) ? second : first;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function verifyExtensionSendObserved(h: CommandHelpers, before: any): Promise<boolean> {
|
|
149
|
+
const beforeCount = getStateMessageCount(before);
|
|
150
|
+
const beforeSignature = getStateLastSignature(before);
|
|
151
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
153
|
+
const state = await readExtensionChatState(h);
|
|
154
|
+
if (state?.status === 'waiting_approval') return true;
|
|
155
|
+
const afterCount = getStateMessageCount(state);
|
|
156
|
+
const afterSignature = getStateLastSignature(state);
|
|
157
|
+
if (afterCount > beforeCount) return true;
|
|
158
|
+
if (afterSignature && afterSignature !== beforeSignature) return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
118
163
|
export async function handleChatHistory(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
119
164
|
const { agentType, offset, limit } = args;
|
|
120
165
|
const historySessionId = getHistorySessionId(h, args);
|
|
@@ -303,12 +348,17 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
303
348
|
_log(`Extension: ${provider?.type || 'unknown_extension'}`);
|
|
304
349
|
// Method 1: provider sendMessage script via evaluateInSession
|
|
305
350
|
try {
|
|
351
|
+
const beforeState = await getStableExtensionBaseline(h);
|
|
306
352
|
const evalResult = await h.evaluateProviderScript('sendMessage', { message: text }, 30000);
|
|
307
353
|
if (evalResult?.result) {
|
|
308
354
|
const parsed = parseMaybeJson(evalResult.result);
|
|
309
355
|
if (didProviderConfirmSend(parsed)) {
|
|
310
|
-
|
|
311
|
-
|
|
356
|
+
const observed = await verifyExtensionSendObserved(h, beforeState);
|
|
357
|
+
if (observed) {
|
|
358
|
+
_log(`Extension script sent OK`);
|
|
359
|
+
return _logSendSuccess('extension-script');
|
|
360
|
+
}
|
|
361
|
+
_log(`Extension script reported send but no chat-state change was observed`);
|
|
312
362
|
}
|
|
313
363
|
if (parsed?.needsTypeAndSend) {
|
|
314
364
|
_log(`Extension needsTypeAndSend → AgentStreamManager`);
|
|
@@ -829,7 +879,7 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
829
879
|
|
|
830
880
|
// 1. Extension transport: via AgentStreamManager
|
|
831
881
|
if (isExtensionTransport(transport) && h.agentStream && h.getCdp() && h.currentSession?.sessionId) {
|
|
832
|
-
const ok = await h.agentStream.resolveSessionAction(h.getCdp()!, h.currentSession.sessionId, action);
|
|
882
|
+
const ok = await h.agentStream.resolveSessionAction(h.getCdp()!, h.currentSession.sessionId, action, button);
|
|
833
883
|
return { success: ok };
|
|
834
884
|
}
|
|
835
885
|
|
|
@@ -687,6 +687,7 @@ export class DaemonCliManager {
|
|
|
687
687
|
if (!instanceManager) return 0;
|
|
688
688
|
const sessions = records || await this.deps.listHostedCliRuntimes?.() || [];
|
|
689
689
|
let restored = 0;
|
|
690
|
+
const restoredBindings = new Set<string>();
|
|
690
691
|
|
|
691
692
|
for (const record of sessions) {
|
|
692
693
|
if (!record?.runtimeId || !record?.cliType || !record?.workspace) continue;
|
|
@@ -702,6 +703,18 @@ export class DaemonCliManager {
|
|
|
702
703
|
record.cliArgs,
|
|
703
704
|
record.providerSessionId,
|
|
704
705
|
);
|
|
706
|
+
const bindingKey = [
|
|
707
|
+
normalizedType,
|
|
708
|
+
record.workspace,
|
|
709
|
+
sessionBinding.providerSessionId || record.runtimeId,
|
|
710
|
+
].join('::');
|
|
711
|
+
if (restoredBindings.has(bindingKey)) {
|
|
712
|
+
LOG.info(
|
|
713
|
+
'CLI',
|
|
714
|
+
`↷ Skipping duplicate hosted runtime restore: ${record.runtimeKey || record.runtimeId} (${normalizedType} @ ${record.workspace}) binding=${sessionBinding.providerSessionId || 'runtime'}`
|
|
715
|
+
);
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
705
718
|
try {
|
|
706
719
|
await this.registerCliInstance(
|
|
707
720
|
record.runtimeId,
|
|
@@ -717,6 +730,7 @@ export class DaemonCliManager {
|
|
|
717
730
|
launchMode: 'manual',
|
|
718
731
|
},
|
|
719
732
|
);
|
|
733
|
+
restoredBindings.add(bindingKey);
|
|
720
734
|
restored += 1;
|
|
721
735
|
LOG.info('CLI', `♻ Restored hosted runtime: ${record.runtimeKey || record.runtimeId} (${record.displayName || record.workspace})`);
|
|
722
736
|
} catch (error: any) {
|
package/src/commands/router.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface SessionHostControlPlane {
|
|
|
44
44
|
restartSession(sessionId: string): Promise<any>;
|
|
45
45
|
sendSignal(sessionId: string, signal: string): Promise<any>;
|
|
46
46
|
forceDetachClient(sessionId: string, clientId: string): Promise<any>;
|
|
47
|
+
pruneDuplicateSessions(payload?: { providerType?: string; workspace?: string; dryRun?: boolean }): Promise<any>;
|
|
47
48
|
acquireWrite(payload: { sessionId: string; clientId: string; ownerType: 'agent' | 'user'; force?: boolean }): Promise<any>;
|
|
48
49
|
releaseWrite(payload: { sessionId: string; clientId: string }): Promise<any>;
|
|
49
50
|
}
|
|
@@ -265,6 +266,16 @@ export class DaemonCommandRouter {
|
|
|
265
266
|
return { success: true, record };
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
case 'session_host_prune_duplicate_sessions': {
|
|
270
|
+
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
271
|
+
const result = await this.deps.sessionHostControl.pruneDuplicateSessions({
|
|
272
|
+
providerType: typeof args?.providerType === 'string' ? args.providerType : undefined,
|
|
273
|
+
workspace: typeof args?.workspace === 'string' ? args.workspace : undefined,
|
|
274
|
+
dryRun: args?.dryRun === true,
|
|
275
|
+
});
|
|
276
|
+
return { success: true, result };
|
|
277
|
+
}
|
|
278
|
+
|
|
268
279
|
case 'session_host_acquire_write': {
|
|
269
280
|
if (!this.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
|
|
270
281
|
const sessionId = typeof args?.sessionId === 'string' ? args.sessionId : '';
|