@adhdev/daemon-core 0.8.51 → 0.8.53
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/commands/cli-manager.d.ts +8 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +896 -299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +887 -300
- package/dist/index.mjs.map +1 -1
- package/dist/providers/acp-provider-instance.d.ts +2 -1
- package/dist/providers/cli-provider-instance.d.ts +3 -1
- package/dist/providers/cli-script-results.d.ts +8 -0
- package/dist/providers/contracts.d.ts +58 -9
- package/dist/providers/control-effects.d.ts +4 -1
- package/dist/providers/io-contracts.d.ts +91 -0
- package/dist/providers/provider-schema.d.ts +5 -0
- package/dist/session-host/runtime-surface.d.ts +16 -0
- package/dist/session-host/startup-restore-policy.d.ts +1 -0
- package/dist/shared-types.d.ts +6 -0
- package/dist/types.d.ts +3 -3
- package/node_modules/@adhdev/session-host-core/dist/index.d.mts +16 -1
- package/node_modules/@adhdev/session-host-core/dist/index.d.ts +16 -1
- package/node_modules/@adhdev/session-host-core/dist/index.js +59 -0
- package/node_modules/@adhdev/session-host-core/dist/index.js.map +1 -1
- package/node_modules/@adhdev/session-host-core/dist/index.mjs +54 -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/commands/cdp-commands.ts +27 -0
- package/src/commands/chat-commands.ts +7 -2
- package/src/commands/cli-manager.ts +9 -5
- package/src/commands/handler.ts +1 -9
- package/src/commands/stream-commands.ts +26 -36
- package/src/daemon/dev-server.ts +4 -18
- package/src/index.d.ts +3 -0
- package/src/index.ts +12 -1
- package/src/providers/acp-provider-instance.ts +156 -14
- package/src/providers/cli-provider-instance.ts +54 -5
- package/src/providers/cli-script-results.ts +39 -0
- package/src/providers/contracts.ts +72 -19
- package/src/providers/control-effects.ts +86 -1
- package/src/providers/io-contracts.ts +340 -0
- package/src/providers/provider-loader.ts +18 -13
- package/src/providers/provider-schema.ts +154 -0
- package/src/session-host/runtime-surface.ts +80 -0
- package/src/session-host/startup-restore-policy.d.ts +1 -0
- package/src/session-host/startup-restore-policy.js +7 -0
- package/src/session-host/startup-restore-policy.ts +7 -0
- package/src/shared-types.ts +6 -0
- package/src/status/builders.ts +5 -81
- package/src/types.ts +3 -3
package/src/commands/handler.ts
CHANGED
|
@@ -416,10 +416,6 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
416
416
|
'pty_input',
|
|
417
417
|
'pty_resize',
|
|
418
418
|
'invoke_provider_script',
|
|
419
|
-
'list_extension_models',
|
|
420
|
-
'set_extension_model',
|
|
421
|
-
'list_extension_modes',
|
|
422
|
-
'set_extension_mode',
|
|
423
419
|
]);
|
|
424
420
|
|
|
425
421
|
if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd)) {
|
|
@@ -511,12 +507,8 @@ export class DaemonCommandHandler implements CommandHelpers {
|
|
|
511
507
|
case 'get_ide_extensions': return Stream.handleGetIdeExtensions(this, args);
|
|
512
508
|
case 'set_ide_extension': return Stream.handleSetIdeExtension(this, args);
|
|
513
509
|
|
|
514
|
-
// ───
|
|
510
|
+
// ─── Provider control execution (stream-commands.ts) ──────────
|
|
515
511
|
case 'invoke_provider_script': return Stream.handleProviderScript(this, args);
|
|
516
|
-
case 'list_extension_models': return Stream.handleExtensionScript(this, args, 'listModels');
|
|
517
|
-
case 'set_extension_model': return Stream.handleExtensionScript(this, args, 'setModel');
|
|
518
|
-
case 'list_extension_modes': return Stream.handleExtensionScript(this, args, 'listModes');
|
|
519
|
-
case 'set_extension_mode': return Stream.handleExtensionScript(this, args, 'setMode');
|
|
520
512
|
|
|
521
513
|
// ─── Provider Auto-Fix / Clone (DevServer proxy) ──────────
|
|
522
514
|
case 'provider_auto_fix': return this.proxyDevServerPost(args, 'auto-implement');
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
import type { CommandResult, CommandHelpers } from './handler.js';
|
|
7
7
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
8
8
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
9
|
+
import { getCliScriptCommand, parseCliScriptResult } from '../providers/cli-script-results.js';
|
|
10
|
+
import {
|
|
11
|
+
normalizeControlInvokeResult,
|
|
12
|
+
normalizeControlListResult,
|
|
13
|
+
normalizeControlSetResult,
|
|
14
|
+
} from '../providers/control-effects.js';
|
|
9
15
|
import { LOG } from '../logging/logger.js';
|
|
10
16
|
|
|
11
17
|
interface CliPresentationInstance extends ProviderInstance {
|
|
@@ -113,42 +119,22 @@ function normalizeProviderScriptArgs(args: any): Record<string, any> {
|
|
|
113
119
|
return normalizedArgs;
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
function
|
|
117
|
-
if (typeof
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (parsed && typeof parsed === 'object' && parsed.success === false) {
|
|
121
|
-
return { success: false, payload: parsed };
|
|
122
|
-
}
|
|
123
|
-
return { success: true, payload: parsed };
|
|
124
|
-
} catch {
|
|
125
|
-
return { success: true, payload: { result } };
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if (result && typeof result === 'object' && 'success' in result && result.success === false) {
|
|
129
|
-
return { success: false, payload: result };
|
|
122
|
+
function buildControlScriptResult(scriptName: string, payload: any): Record<string, unknown> {
|
|
123
|
+
if (!payload || typeof payload !== 'object') return {};
|
|
124
|
+
if (Array.isArray(payload.options) || Array.isArray(payload.models) || Array.isArray(payload.modes)) {
|
|
125
|
+
return { controlResult: normalizeControlListResult(payload) };
|
|
130
126
|
}
|
|
131
|
-
return { success: true, payload: result };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function getCliScriptCommand(payload: any): { type: string; text?: string } | null {
|
|
135
|
-
if (!payload || typeof payload !== 'object') return null;
|
|
136
127
|
|
|
137
|
-
|
|
138
|
-
|
|
128
|
+
const looksLikeValueMutation = /^set|^change/i.test(scriptName)
|
|
129
|
+
|| payload.currentValue !== undefined
|
|
130
|
+
|| payload.value !== undefined;
|
|
131
|
+
if (looksLikeValueMutation) {
|
|
132
|
+
return { controlResult: normalizeControlSetResult(payload) };
|
|
139
133
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const text = typeof command.text === 'string'
|
|
146
|
-
? command.text.trim()
|
|
147
|
-
: typeof command.message === 'string'
|
|
148
|
-
? command.message.trim()
|
|
149
|
-
: '';
|
|
150
|
-
if (!text) return null;
|
|
151
|
-
return { type: command.type, text };
|
|
134
|
+
if (payload.ok !== undefined || payload.success !== undefined || Array.isArray(payload.effects)) {
|
|
135
|
+
return { controlResult: normalizeControlInvokeResult(payload) };
|
|
136
|
+
}
|
|
137
|
+
return {};
|
|
152
138
|
}
|
|
153
139
|
|
|
154
140
|
function applyProviderPatch(h: CommandHelpers, args: any, payload: any): void {
|
|
@@ -195,7 +181,7 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
195
181
|
}
|
|
196
182
|
try {
|
|
197
183
|
const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
|
|
198
|
-
const parsed =
|
|
184
|
+
const parsed = parseCliScriptResult(raw);
|
|
199
185
|
if (!parsed.success) {
|
|
200
186
|
return { success: false, ...(parsed.payload || {}) };
|
|
201
187
|
}
|
|
@@ -206,7 +192,11 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
206
192
|
adapter.writeRaw(cliCommand.text + '\r');
|
|
207
193
|
}
|
|
208
194
|
applyProviderPatch(h, args, parsed.payload);
|
|
209
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
...(parsed.payload && typeof parsed.payload === 'object' ? parsed.payload : { result: parsed.payload }),
|
|
198
|
+
...buildControlScriptResult(scriptName, parsed.payload),
|
|
199
|
+
};
|
|
210
200
|
} catch (e: any) {
|
|
211
201
|
return { success: false, error: `Script execution failed: ${e.message}` };
|
|
212
202
|
}
|
|
@@ -282,7 +272,7 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
282
272
|
if (parsed && typeof parsed === 'object' && parsed.success === false) {
|
|
283
273
|
return { success: false, ...parsed };
|
|
284
274
|
}
|
|
285
|
-
return { success: true, ...parsed };
|
|
275
|
+
return { success: true, ...parsed, ...buildControlScriptResult(scriptName, parsed) };
|
|
286
276
|
} catch {
|
|
287
277
|
return { success: true, result };
|
|
288
278
|
}
|
package/src/daemon/dev-server.ts
CHANGED
|
@@ -21,6 +21,7 @@ import * as path from 'path';
|
|
|
21
21
|
import * as os from 'os';
|
|
22
22
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
23
23
|
import type { ProviderCategory, ProviderModule, ProviderScripts, ProviderSettingDef } from '../providers/contracts.js';
|
|
24
|
+
import { validateProviderDefinition } from '../providers/provider-schema.js';
|
|
24
25
|
import type { ChildProcess } from 'child_process';
|
|
25
26
|
import type { DaemonCdpManager } from '../cdp/manager.js';
|
|
26
27
|
import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
|
|
@@ -773,24 +774,9 @@ export class DevServer implements DevServerContext {
|
|
|
773
774
|
const warnings: string[] = [];
|
|
774
775
|
try {
|
|
775
776
|
const config = typeof content === 'string' ? JSON.parse(content) : content;
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
if (!config.category) errors.push('Missing required field: category');
|
|
780
|
-
else if (!['ide', 'extension', 'cli', 'acp'].includes(config.category)) errors.push(`Invalid category: ${config.category}`);
|
|
781
|
-
// Category-specific
|
|
782
|
-
if (config.category === 'ide' || config.category === 'extension') {
|
|
783
|
-
if (!config.cdpPorts || !Array.isArray(config.cdpPorts) || config.cdpPorts.length === 0)
|
|
784
|
-
warnings.push('IDE/Extension providers should have cdpPorts');
|
|
785
|
-
if (config.category === 'extension' && !config.extensionId)
|
|
786
|
-
warnings.push('Extension providers should have extensionId');
|
|
787
|
-
}
|
|
788
|
-
if (config.category === 'acp' || config.category === 'cli') {
|
|
789
|
-
if (!config.spawn) errors.push('ACP/CLI providers must have spawn config');
|
|
790
|
-
else {
|
|
791
|
-
if (!config.spawn.command) errors.push('spawn.command is required');
|
|
792
|
-
}
|
|
793
|
-
}
|
|
777
|
+
const validation = validateProviderDefinition(config);
|
|
778
|
+
errors.push(...validation.errors);
|
|
779
|
+
warnings.push(...validation.warnings);
|
|
794
780
|
// Settings validation
|
|
795
781
|
if (config.settings) {
|
|
796
782
|
for (const [key, val] of Object.entries(config.settings)) {
|
package/src/index.d.ts
CHANGED
|
@@ -76,6 +76,9 @@ export type { PtyRuntimeTransport, PtyTransportFactory, PtySpawnOptions } from '
|
|
|
76
76
|
export { SessionHostPtyTransportFactory } from './cli-adapters/session-host-transport.js';
|
|
77
77
|
export type { HostedCliRuntimeDescriptor, CliTransportFactoryParams } from './commands/cli-manager.js';
|
|
78
78
|
export { ensureSessionHostReady, listHostedCliRuntimes } from './session-host/runtime-support.js';
|
|
79
|
+
export { getSessionHostRecoveryLabel, getSessionHostSurfaceKind, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, } from './session-host/runtime-surface.js';
|
|
80
|
+
export type { SessionHostSurfaceKind, SessionHostSurfaceRecordLike } from './session-host/runtime-surface.js';
|
|
81
|
+
export { shouldAutoRestoreHostedSessionsOnStartup } from './session-host/startup-restore-policy.js';
|
|
79
82
|
export type { SessionHostEndpoint } from '@adhdev/session-host-core';
|
|
80
83
|
export { getAIExtensions, installExtensions, launchIDE, isExtensionInstalled } from './installer.js';
|
|
81
84
|
export type { ExtensionInfo as InstallerExtensionInfo } from './installer.js';
|
package/src/index.ts
CHANGED
|
@@ -194,7 +194,8 @@ export { ProviderInstanceManager } from './providers/provider-instance-manager.j
|
|
|
194
194
|
export { IdeProviderInstance } from './providers/ide-provider-instance.js';
|
|
195
195
|
export { CliProviderInstance } from './providers/cli-provider-instance.js';
|
|
196
196
|
export { AcpProviderInstance } from './providers/acp-provider-instance.js';
|
|
197
|
-
export type { ProviderModule, CdpTargetFilter, ProviderResumeCapability } from './providers/contracts.js';
|
|
197
|
+
export type { ProviderModule, CdpTargetFilter, ProviderResumeCapability, InputEnvelope, InputPart, MessagePart, ControlListResult, ControlSetResult, ControlInvokeResult } from './providers/contracts.js';
|
|
198
|
+
export { normalizeInputEnvelope, normalizeMessageParts, flattenMessageParts } from './providers/io-contracts.js';
|
|
198
199
|
export { VersionArchive, detectAllVersions } from './providers/version-archive.js';
|
|
199
200
|
export type { ProviderVersionInfo, VersionHistory } from './providers/version-archive.js';
|
|
200
201
|
|
|
@@ -214,6 +215,16 @@ export {
|
|
|
214
215
|
resolveSessionHostAppName,
|
|
215
216
|
} from './session-host/app-name.js';
|
|
216
217
|
export { ensureSessionHostReady, listHostedCliRuntimes } from './session-host/runtime-support.js';
|
|
218
|
+
export {
|
|
219
|
+
getSessionHostRecoveryLabel,
|
|
220
|
+
getSessionHostSurfaceKind,
|
|
221
|
+
isSessionHostLiveRuntime,
|
|
222
|
+
isSessionHostRecoverySnapshot,
|
|
223
|
+
partitionSessionHostDiagnosticsSessions,
|
|
224
|
+
partitionSessionHostRecords,
|
|
225
|
+
} from './session-host/runtime-surface.js';
|
|
226
|
+
export type { SessionHostSurfaceKind, SessionHostSurfaceRecordLike } from './session-host/runtime-surface.js';
|
|
227
|
+
export { shouldAutoRestoreHostedSessionsOnStartup } from './session-host/startup-restore-policy.js';
|
|
217
228
|
export type { SessionHostEndpoint } from '@adhdev/session-host-core';
|
|
218
229
|
|
|
219
230
|
// ── Installer ──
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 5. dispose() → kill process
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import * as path from 'path';
|
|
18
19
|
import { Readable, Writable } from 'stream';
|
|
19
20
|
import { spawn, type ChildProcess } from 'child_process';
|
|
20
21
|
import {
|
|
@@ -45,8 +46,8 @@ import {
|
|
|
45
46
|
type ToolCallStatus,
|
|
46
47
|
type SessionConfigOption,
|
|
47
48
|
} from '@agentclientprotocol/sdk';
|
|
48
|
-
import type { ProviderModule, ContentBlock, ToolCallInfo, ToolCallContent as TCC, ToolKind, ToolCallStatus as TCS } from './contracts.js';
|
|
49
|
-
import { normalizeContent, flattenContent } from './contracts.js';
|
|
49
|
+
import type { ProviderModule, ContentBlock, InputEnvelope, InputPart, ToolCallInfo, ToolCallContent as TCC, ToolKind, ToolCallStatus as TCS } from './contracts.js';
|
|
50
|
+
import { normalizeContent, flattenContent, normalizeInputEnvelope } from './contracts.js';
|
|
50
51
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
51
52
|
import { StatusMonitor } from './status-monitor.js';
|
|
52
53
|
import { LOG } from '../logging/logger.js';
|
|
@@ -83,6 +84,121 @@ interface AcpMode {
|
|
|
83
84
|
description?: string;
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
interface PromptCapabilityFlags {
|
|
88
|
+
image: boolean;
|
|
89
|
+
audio: boolean;
|
|
90
|
+
embeddedContext: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getPromptCapabilityFlags(agentCapabilities?: Record<string, any>): PromptCapabilityFlags {
|
|
94
|
+
const prompt = agentCapabilities?.promptCapabilities || {};
|
|
95
|
+
return {
|
|
96
|
+
image: prompt.image === true,
|
|
97
|
+
audio: prompt.audio === true,
|
|
98
|
+
embeddedContext: prompt.embeddedContext === true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getResourceNameFromUri(uri: string, fallback: string): string {
|
|
103
|
+
try {
|
|
104
|
+
if (uri.startsWith('file://')) {
|
|
105
|
+
return path.basename(new URL(uri).pathname) || fallback;
|
|
106
|
+
}
|
|
107
|
+
return path.basename(uri) || fallback;
|
|
108
|
+
} catch {
|
|
109
|
+
return fallback;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function inputPartToResourceLink(part: Extract<InputPart, { type: 'image' | 'audio' | 'video' | 'resource' }>, fallbackName: string): ContentBlock | null {
|
|
114
|
+
if (!part.uri) return null;
|
|
115
|
+
return {
|
|
116
|
+
type: 'resource_link',
|
|
117
|
+
uri: part.uri,
|
|
118
|
+
name: getResourceNameFromUri(part.uri, fallbackName),
|
|
119
|
+
...(part.mimeType ? { mimeType: part.mimeType } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function appendPromptText(promptParts: ContentBlock[], text: string | undefined): void {
|
|
124
|
+
const normalized = typeof text === 'string' ? text.trim() : '';
|
|
125
|
+
if (!normalized) return;
|
|
126
|
+
const last = promptParts[promptParts.length - 1];
|
|
127
|
+
if (last?.type === 'text' && last.text === normalized) return;
|
|
128
|
+
promptParts.push({ type: 'text', text: normalized });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Record<string, any>): ContentBlock[] {
|
|
132
|
+
const caps = getPromptCapabilityFlags(agentCapabilities);
|
|
133
|
+
const promptParts: ContentBlock[] = [];
|
|
134
|
+
|
|
135
|
+
for (const part of input.parts) {
|
|
136
|
+
if (part.type === 'text') {
|
|
137
|
+
promptParts.push({ type: 'text', text: part.text });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (part.type === 'image') {
|
|
142
|
+
if (caps.image && part.data) {
|
|
143
|
+
promptParts.push({
|
|
144
|
+
type: 'image',
|
|
145
|
+
data: part.data,
|
|
146
|
+
mimeType: part.mimeType,
|
|
147
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const fallback = inputPartToResourceLink(part, 'image');
|
|
152
|
+
if (fallback) promptParts.push(fallback);
|
|
153
|
+
appendPromptText(promptParts, part.alt || (!part.uri ? `Attached image (${part.mimeType})` : undefined));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (part.type === 'audio') {
|
|
158
|
+
if (caps.audio && part.data) {
|
|
159
|
+
promptParts.push({
|
|
160
|
+
type: 'audio',
|
|
161
|
+
data: part.data,
|
|
162
|
+
mimeType: part.mimeType,
|
|
163
|
+
});
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const fallback = inputPartToResourceLink(part, 'audio');
|
|
167
|
+
if (fallback) promptParts.push(fallback);
|
|
168
|
+
appendPromptText(promptParts, part.transcript || (!part.uri ? `Attached audio (${part.mimeType})` : undefined));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (part.type === 'resource') {
|
|
173
|
+
if (caps.embeddedContext && (part.text || part.data)) {
|
|
174
|
+
promptParts.push({
|
|
175
|
+
type: 'resource',
|
|
176
|
+
resource: part.text
|
|
177
|
+
? { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null }
|
|
178
|
+
: { uri: part.uri, blob: part.data || '', mimeType: part.mimeType ?? null },
|
|
179
|
+
});
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const fallback = inputPartToResourceLink(part, part.name || 'resource');
|
|
183
|
+
if (fallback) promptParts.push(fallback);
|
|
184
|
+
appendPromptText(promptParts, part.text || (!part.uri && part.name ? part.name : undefined));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (part.type === 'video') {
|
|
189
|
+
const fallback = inputPartToResourceLink(part, 'video');
|
|
190
|
+
if (fallback) promptParts.push(fallback);
|
|
191
|
+
appendPromptText(promptParts, !part.uri ? `Attached video (${part.mimeType})` : undefined);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!promptParts.some((part) => part.type === 'text') && input.textFallback) {
|
|
196
|
+
promptParts.unshift({ type: 'text', text: input.textFallback });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return promptParts;
|
|
200
|
+
}
|
|
201
|
+
|
|
86
202
|
// ─── AcpProviderInstance ───────────────────────────
|
|
87
203
|
|
|
88
204
|
export class AcpProviderInstance implements ProviderInstance {
|
|
@@ -237,8 +353,10 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
237
353
|
}
|
|
238
354
|
|
|
239
355
|
onEvent(event: string, data?: any): void {
|
|
240
|
-
if (event === 'send_message'
|
|
241
|
-
|
|
356
|
+
if (event === 'send_message') {
|
|
357
|
+
const input = normalizeInputEnvelope(data)
|
|
358
|
+
const promptParts = buildAcpPromptParts(input, this.agentCapabilities)
|
|
359
|
+
this.sendPrompt(input.textFallback, promptParts.length > 0 ? promptParts : undefined).catch(e =>
|
|
242
360
|
this.log.warn(`[${this.type}] sendPrompt error: ${e?.message}`)
|
|
243
361
|
);
|
|
244
362
|
} else if (event === 'resolve_action') {
|
|
@@ -768,19 +886,36 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
768
886
|
}
|
|
769
887
|
|
|
770
888
|
// Build prompt content
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
// Rich content — forward ContentBlock[] as ACP prompt parts
|
|
774
|
-
promptParts = contentBlocks.map(b => {
|
|
889
|
+
const promptParts: any[] = contentBlocks && contentBlocks.length > 0
|
|
890
|
+
? contentBlocks.map((b) => {
|
|
775
891
|
if (b.type === 'text') return { type: 'text', text: b.text };
|
|
776
|
-
if (b.type === 'image')
|
|
777
|
-
|
|
892
|
+
if (b.type === 'image') {
|
|
893
|
+
return {
|
|
894
|
+
type: 'image',
|
|
895
|
+
data: b.data,
|
|
896
|
+
mimeType: b.mimeType,
|
|
897
|
+
...(b.uri ? { uri: b.uri } : {}),
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (b.type === 'audio') {
|
|
901
|
+
return {
|
|
902
|
+
type: 'audio',
|
|
903
|
+
data: b.data,
|
|
904
|
+
mimeType: b.mimeType,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
if (b.type === 'resource_link') {
|
|
908
|
+
return {
|
|
909
|
+
type: 'resource_link',
|
|
910
|
+
uri: b.uri,
|
|
911
|
+
name: b.name,
|
|
912
|
+
...(b.mimeType ? { mimeType: b.mimeType } : {}),
|
|
913
|
+
};
|
|
914
|
+
}
|
|
778
915
|
if (b.type === 'resource') return { type: 'resource', resource: b.resource };
|
|
779
916
|
return { type: 'text', text: flattenContent([b]) };
|
|
780
|
-
})
|
|
781
|
-
|
|
782
|
-
promptParts = [{ type: 'text', text }];
|
|
783
|
-
}
|
|
917
|
+
})
|
|
918
|
+
: [{ type: 'text', text }];
|
|
784
919
|
|
|
785
920
|
// Add user message locally (store as ContentBlock[])
|
|
786
921
|
this.messages.push({
|
|
@@ -862,6 +997,13 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
862
997
|
type: 'image',
|
|
863
998
|
data: content.data,
|
|
864
999
|
mimeType: content.mimeType,
|
|
1000
|
+
...(content.uri ? { uri: content.uri } : {}),
|
|
1001
|
+
});
|
|
1002
|
+
} else if (content.type === 'audio') {
|
|
1003
|
+
this.partialBlocks.push({
|
|
1004
|
+
type: 'audio',
|
|
1005
|
+
data: content.data,
|
|
1006
|
+
mimeType: content.mimeType,
|
|
865
1007
|
});
|
|
866
1008
|
} else if (content.type === 'resource_link') {
|
|
867
1009
|
this.partialBlocks.push({
|
|
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
-
import type
|
|
13
|
+
import { normalizeInputEnvelope, type ProviderModule } from './contracts.js';
|
|
14
14
|
import type { ProviderInstance, ProviderState, ProviderEvent, InstanceContext } from './provider-instance.js';
|
|
15
15
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
16
16
|
import type { CliProviderModule } from '../cli-adapters/provider-cli-adapter.js';
|
|
@@ -21,6 +21,7 @@ import { LOG } from '../logging/logger.js';
|
|
|
21
21
|
import type { ChatMessage } from '../types.js';
|
|
22
22
|
import { extractProviderControlValues, normalizeProviderEffects } from './control-effects.js';
|
|
23
23
|
import { formatAutoApprovalMessage, pickApprovalButton } from './approval-utils.js';
|
|
24
|
+
import { getCliScriptCommand, parseCliScriptResult } from './cli-script-results.js';
|
|
24
25
|
|
|
25
26
|
let CachedDatabaseSync: (new (path: string, options?: { readOnly?: boolean }) => {
|
|
26
27
|
prepare(sql: string): { get(...params: Array<string | number>): unknown };
|
|
@@ -42,6 +43,29 @@ function getDatabaseSync() {
|
|
|
42
43
|
return CachedDatabaseSync;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export function getForcedNewSessionScriptName(
|
|
47
|
+
provider: ProviderModule | undefined,
|
|
48
|
+
launchMode: 'new' | 'resume' | 'manual',
|
|
49
|
+
): string | null {
|
|
50
|
+
if (!provider || launchMode !== 'new') return null;
|
|
51
|
+
const resume = provider.resume;
|
|
52
|
+
if (!resume?.supported) return null;
|
|
53
|
+
if (Array.isArray(resume.newSessionArgs) && resume.newSessionArgs.length > 0) return null;
|
|
54
|
+
|
|
55
|
+
const controls = Array.isArray((provider as any).controls) ? (provider as any).controls : [];
|
|
56
|
+
for (const control of controls) {
|
|
57
|
+
if (control?.type !== 'action') continue;
|
|
58
|
+
const invokeScript = typeof control?.invokeScript === 'string' ? control.invokeScript.trim() : '';
|
|
59
|
+
if (!invokeScript) continue;
|
|
60
|
+
const controlId = typeof control?.id === 'string' ? control.id.trim() : '';
|
|
61
|
+
if (controlId === 'new_session' || /^new.?session$/i.test(invokeScript)) {
|
|
62
|
+
return invokeScript;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
export class CliProviderInstance implements ProviderInstance {
|
|
46
70
|
readonly type: string;
|
|
47
71
|
readonly category = 'cli' as const;
|
|
@@ -135,6 +159,7 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
135
159
|
|
|
136
160
|
// PTY spawn
|
|
137
161
|
await this.adapter.spawn();
|
|
162
|
+
await this.enforceFreshSessionLaunchIfNeeded();
|
|
138
163
|
this.maybeAppendRuntimeRecoveryMessage(this.adapter.getRuntimeMetadata());
|
|
139
164
|
if (this.providerSessionId) {
|
|
140
165
|
this.historyWriter.compactHistorySession(this.type, this.providerSessionId);
|
|
@@ -370,10 +395,13 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
370
395
|
}
|
|
371
396
|
|
|
372
397
|
onEvent(event: string, data?: any): void {
|
|
373
|
-
if (event === 'send_message'
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
398
|
+
if (event === 'send_message') {
|
|
399
|
+
const input = normalizeInputEnvelope(data);
|
|
400
|
+
if (input.textFallback) {
|
|
401
|
+
void this.adapter.sendMessage(input.textFallback).catch((e: any) => {
|
|
402
|
+
LOG.warn('CLI', `[${this.type}] send_message failed: ${e?.message || e}`);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
377
405
|
} else if (event === 'server_connected' && data?.serverConn) {
|
|
378
406
|
this.adapter.setServerConn(data.serverConn);
|
|
379
407
|
} else if (event === 'resolve_action' && data) {
|
|
@@ -394,6 +422,27 @@ export class CliProviderInstance implements ProviderInstance {
|
|
|
394
422
|
private completedDebounceTimer: NodeJS.Timeout | null = null;
|
|
395
423
|
private completedDebouncePending: { chatTitle: string; duration: number; timestamp: number } | null = null;
|
|
396
424
|
|
|
425
|
+
private async enforceFreshSessionLaunchIfNeeded(): Promise<void> {
|
|
426
|
+
const scriptName = getForcedNewSessionScriptName(this.provider, this.launchMode);
|
|
427
|
+
if (!scriptName) return;
|
|
428
|
+
|
|
429
|
+
LOG.info('CLI', `[${this.type}] forcing fresh session launch via script: ${scriptName}`);
|
|
430
|
+
const raw = await this.adapter.invokeScript(scriptName, {});
|
|
431
|
+
const parsed = parseCliScriptResult(raw);
|
|
432
|
+
if (!parsed.success) {
|
|
433
|
+
throw new Error(parsed.payload?.error || `Failed to invoke fresh-session script '${scriptName}'`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const cliCommand = getCliScriptCommand(parsed.payload);
|
|
437
|
+
if (cliCommand?.type === 'send_message' && cliCommand.text) {
|
|
438
|
+
await this.adapter.sendMessage(cliCommand.text);
|
|
439
|
+
} else if (cliCommand?.type === 'pty_write' && cliCommand.text) {
|
|
440
|
+
this.adapter.writeRaw(cliCommand.text + '\r');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
this.applyProviderResponse(parsed.payload, { phase: 'immediate' });
|
|
444
|
+
}
|
|
445
|
+
|
|
397
446
|
private detectStatusTransition(): void {
|
|
398
447
|
const now = Date.now();
|
|
399
448
|
const adapterStatus = this.adapter.getStatus();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function parseCliScriptResult(result: unknown): { success: boolean; payload: any } {
|
|
2
|
+
if (typeof result === 'string') {
|
|
3
|
+
try {
|
|
4
|
+
const parsed = JSON.parse(result)
|
|
5
|
+
if (parsed && typeof parsed === 'object' && parsed.success === false) {
|
|
6
|
+
return { success: false, payload: parsed }
|
|
7
|
+
}
|
|
8
|
+
return { success: true, payload: parsed }
|
|
9
|
+
} catch {
|
|
10
|
+
return { success: true, payload: { result } }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (result && typeof result === 'object' && 'success' in result && result.success === false) {
|
|
15
|
+
return { success: false, payload: result }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { success: true, payload: result }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getCliScriptCommand(payload: any): { type: string; text?: string } | null {
|
|
22
|
+
if (!payload || typeof payload !== 'object') return null
|
|
23
|
+
|
|
24
|
+
if (typeof payload.sendMessage === 'string' && payload.sendMessage.trim()) {
|
|
25
|
+
return { type: 'send_message', text: payload.sendMessage.trim() }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const command = payload.command
|
|
29
|
+
if (!command || typeof command !== 'object') return null
|
|
30
|
+
if (command.type !== 'send_message' && command.type !== 'pty_write') return null
|
|
31
|
+
|
|
32
|
+
const text = typeof command.text === 'string'
|
|
33
|
+
? command.text.trim()
|
|
34
|
+
: typeof command.message === 'string'
|
|
35
|
+
? command.message.trim()
|
|
36
|
+
: ''
|
|
37
|
+
if (!text) return null
|
|
38
|
+
return { type: command.type, text }
|
|
39
|
+
}
|