@adhdev/daemon-core 0.9.76-rc.61 → 0.9.76-rc.62
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/chat-commands.d.ts +2 -0
- package/dist/index.js +634 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +639 -296
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +4 -1
- package/dist/providers/contracts.d.ts +20 -1
- package/dist/providers/io-contracts.d.ts +17 -1
- package/dist/providers/provider-input-support.d.ts +18 -2
- package/dist/providers/provider-instance.d.ts +2 -0
- package/dist/shared-types.d.ts +4 -0
- package/package.json +1 -1
- package/src/commands/chat-commands.ts +40 -5
- package/src/commands/mesh-coordinator.ts +8 -1
- package/src/commands/router.ts +4 -0
- package/src/mesh/coordinator-prompt.ts +7 -0
- package/src/providers/acp-provider-instance.ts +118 -30
- package/src/providers/cli-provider-instance.ts +96 -5
- package/src/providers/contracts.ts +25 -1
- package/src/providers/io-contracts.ts +63 -5
- package/src/providers/provider-input-support.ts +125 -1
- package/src/providers/provider-instance.ts +2 -0
- package/src/providers/provider-schema.ts +38 -8
- package/src/shared-types.ts +4 -0
- package/src/status/builders.ts +5 -3
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Lifecycle layer on top of ProviderCliAdapter.
|
|
5
5
|
* collectCliData() + status transition logic from daemon-status.ts moved here.
|
|
6
6
|
*/
|
|
7
|
-
import { type ProviderModule } from './contracts.js';
|
|
7
|
+
import { type ProviderModule, type InputEnvelope } from './contracts.js';
|
|
8
8
|
import type { ProviderInstance, ProviderState, InstanceContext, HotChatSessionState, SessionModalState } from './provider-instance.js';
|
|
9
9
|
import { ProviderCliAdapter } from '../cli-adapters/provider-cli-adapter.js';
|
|
10
10
|
import type { PtyTransportFactory } from '../cli-adapters/pty-transport.js';
|
|
@@ -16,6 +16,9 @@ type PersistableCliHistoryMessage = {
|
|
|
16
16
|
senderName?: string;
|
|
17
17
|
receivedAt?: number;
|
|
18
18
|
};
|
|
19
|
+
export declare function buildCliStructuredInputPrompt(input: InputEnvelope, options?: {
|
|
20
|
+
materializeDir?: string;
|
|
21
|
+
}): string;
|
|
19
22
|
export declare function buildIncrementalHistoryAppendMessages(previousMessages: PersistableCliHistoryMessage[], currentMessages: PersistableCliHistoryMessage[]): PersistableCliHistoryMessage[];
|
|
20
23
|
export declare function getForcedNewSessionScriptName(provider: ProviderModule | undefined, launchMode: 'new' | 'resume' | 'manual'): string | null;
|
|
21
24
|
export declare function waitForCliAdapterReady(adapter: {
|
|
@@ -87,7 +87,7 @@ export interface ProviderEffect {
|
|
|
87
87
|
* ContentBlock — ACP ContentBlock union type
|
|
88
88
|
* Represents displayable content in messages, tool call results, etc.
|
|
89
89
|
*/
|
|
90
|
-
export type ContentBlock = TextBlock | ImageBlock | AudioBlock | ResourceLinkBlock | ResourceBlock;
|
|
90
|
+
export type ContentBlock = TextBlock | ImageBlock | AudioBlock | VideoBlock | ResourceLinkBlock | ResourceBlock;
|
|
91
91
|
/** Text content — ACP TextContent */
|
|
92
92
|
export interface TextBlock {
|
|
93
93
|
type: 'text';
|
|
@@ -100,6 +100,7 @@ export interface ImageBlock {
|
|
|
100
100
|
data: string;
|
|
101
101
|
mimeType: string;
|
|
102
102
|
uri?: string;
|
|
103
|
+
alt?: string;
|
|
103
104
|
annotations?: ContentAnnotations;
|
|
104
105
|
}
|
|
105
106
|
/** Audio content — ACP AudioContent */
|
|
@@ -107,6 +108,18 @@ export interface AudioBlock {
|
|
|
107
108
|
type: 'audio';
|
|
108
109
|
data: string;
|
|
109
110
|
mimeType: string;
|
|
111
|
+
uri?: string;
|
|
112
|
+
transcript?: string;
|
|
113
|
+
annotations?: ContentAnnotations;
|
|
114
|
+
}
|
|
115
|
+
/** Video content — ADHDev canonical display block. ACP prompt input degrades video to resource_link/text. */
|
|
116
|
+
export interface VideoBlock {
|
|
117
|
+
type: 'video';
|
|
118
|
+
data?: string;
|
|
119
|
+
mimeType: string;
|
|
120
|
+
uri?: string;
|
|
121
|
+
transcript?: string;
|
|
122
|
+
posterUri?: string;
|
|
110
123
|
annotations?: ContentAnnotations;
|
|
111
124
|
}
|
|
112
125
|
/** Resource link (file reference) — ACP ResourceLink */
|
|
@@ -487,6 +500,12 @@ export interface ProviderModule {
|
|
|
487
500
|
input?: {
|
|
488
501
|
multipart?: boolean;
|
|
489
502
|
mediaTypes?: Array<'text' | 'image' | 'audio' | 'video' | 'resource'>;
|
|
503
|
+
strategies?: Array<{
|
|
504
|
+
mediaType: 'text' | 'image' | 'audio' | 'video' | 'resource';
|
|
505
|
+
strategies?: Array<'native' | 'native_acp' | 'resource_link' | 'text_fallback' | 'paste' | 'upload'>;
|
|
506
|
+
native?: boolean;
|
|
507
|
+
degradation?: Array<'native' | 'native_acp' | 'resource_link' | 'text_fallback' | 'paste' | 'upload'>;
|
|
508
|
+
}>;
|
|
490
509
|
};
|
|
491
510
|
output?: {
|
|
492
511
|
richContent?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContentAnnotations } from './contracts.js';
|
|
2
|
-
export type InputPart = TextInputPart | ImageInputPart | AudioInputPart | VideoInputPart | ResourceInputPart;
|
|
2
|
+
export type InputPart = TextInputPart | ImageInputPart | AudioInputPart | VideoInputPart | ResourceLinkInputPart | ResourceInputPart;
|
|
3
3
|
export interface TextInputPart {
|
|
4
4
|
type: 'text';
|
|
5
5
|
text: string;
|
|
@@ -23,6 +23,7 @@ export interface VideoInputPart {
|
|
|
23
23
|
mimeType: string;
|
|
24
24
|
uri?: string;
|
|
25
25
|
data?: string;
|
|
26
|
+
transcript?: string;
|
|
26
27
|
posterUri?: string;
|
|
27
28
|
}
|
|
28
29
|
export interface ResourceInputPart {
|
|
@@ -33,6 +34,16 @@ export interface ResourceInputPart {
|
|
|
33
34
|
text?: string;
|
|
34
35
|
data?: string;
|
|
35
36
|
}
|
|
37
|
+
export interface ResourceLinkInputPart {
|
|
38
|
+
type: 'resource_link';
|
|
39
|
+
uri: string;
|
|
40
|
+
name: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
mimeType?: string;
|
|
44
|
+
size?: number;
|
|
45
|
+
annotations?: ContentAnnotations;
|
|
46
|
+
}
|
|
36
47
|
export interface InputEnvelope {
|
|
37
48
|
parts: InputPart[];
|
|
38
49
|
textFallback: string;
|
|
@@ -52,6 +63,7 @@ export interface ImageMessagePart {
|
|
|
52
63
|
mimeType: string;
|
|
53
64
|
uri?: string;
|
|
54
65
|
data?: string;
|
|
66
|
+
alt?: string;
|
|
55
67
|
annotations?: ContentAnnotations;
|
|
56
68
|
}
|
|
57
69
|
export interface AudioMessagePart {
|
|
@@ -67,6 +79,7 @@ export interface VideoMessagePart {
|
|
|
67
79
|
mimeType: string;
|
|
68
80
|
uri?: string;
|
|
69
81
|
data?: string;
|
|
82
|
+
transcript?: string;
|
|
70
83
|
posterUri?: string;
|
|
71
84
|
annotations?: ContentAnnotations;
|
|
72
85
|
}
|
|
@@ -74,8 +87,11 @@ export interface ResourceLinkMessagePart {
|
|
|
74
87
|
type: 'resource_link';
|
|
75
88
|
uri: string;
|
|
76
89
|
name: string;
|
|
90
|
+
title?: string;
|
|
91
|
+
description?: string;
|
|
77
92
|
mimeType?: string;
|
|
78
93
|
size?: number;
|
|
94
|
+
annotations?: ContentAnnotations;
|
|
79
95
|
}
|
|
80
96
|
export interface ResourceMessagePart {
|
|
81
97
|
type: 'resource';
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import type { InputEnvelope, ProviderModule } from './contracts.js';
|
|
2
|
-
type InputMediaType = 'text' | 'image' | 'audio' | 'video' | 'resource';
|
|
2
|
+
export type InputMediaType = 'text' | 'image' | 'audio' | 'video' | 'resource';
|
|
3
|
+
export type InputAttachmentStrategy = 'native' | 'native_acp' | 'resource_link' | 'text_fallback' | 'paste' | 'upload';
|
|
4
|
+
export interface InputMediaStrategyDescriptor {
|
|
5
|
+
mediaType: InputMediaType;
|
|
6
|
+
strategies: InputAttachmentStrategy[];
|
|
7
|
+
native?: boolean;
|
|
8
|
+
degradation?: InputAttachmentStrategy[];
|
|
9
|
+
}
|
|
10
|
+
export interface MessageInputSupport {
|
|
11
|
+
text: boolean;
|
|
12
|
+
multipart: boolean;
|
|
13
|
+
mediaTypes: InputMediaType[];
|
|
14
|
+
strategies: InputMediaStrategyDescriptor[];
|
|
15
|
+
}
|
|
16
|
+
export declare const TEXT_ONLY_MESSAGE_INPUT_SUPPORT: MessageInputSupport;
|
|
3
17
|
export declare function assertTextOnlyInput(provider: Pick<ProviderModule, 'name' | 'type'> | null | undefined, input: InputEnvelope): void;
|
|
4
18
|
export declare function getDeclaredProviderInputSupport(provider?: Pick<ProviderModule, 'capabilities'> | null): {
|
|
5
19
|
multipart: boolean;
|
|
6
20
|
mediaTypes: Set<InputMediaType>;
|
|
21
|
+
strategies: InputMediaStrategyDescriptor[];
|
|
7
22
|
};
|
|
23
|
+
export declare function normalizeInputStrategyDescriptors(raw: unknown): InputMediaStrategyDescriptor[];
|
|
24
|
+
export declare function getEffectiveMessageInputSupport(provider?: Pick<ProviderModule, 'category' | 'capabilities'> | null, runtimeCapabilities?: Record<string, any> | null): MessageInputSupport;
|
|
8
25
|
export declare function assertProviderSupportsDeclaredInput(provider: Pick<ProviderModule, 'name' | 'type' | 'capabilities'> | null | undefined, input: InputEnvelope): void;
|
|
9
|
-
export {};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ProviderModule, ProviderResumeCapability } from './contracts.js';
|
|
11
11
|
import type { AcpConfigOption, AcpMode, ProviderControlSchema, ProviderSummaryMetadata, SessionCapability } from '../shared-types.js';
|
|
12
|
+
import type { MessageInputSupport } from './provider-input-support.js';
|
|
12
13
|
import type { ChatMessage } from '../types.js';
|
|
13
14
|
export type ProviderStatus = 'idle' | 'generating' | 'waiting_approval' | 'error' | 'stopped' | 'starting';
|
|
14
15
|
export interface ProviderRuntimeWriteOwner {
|
|
@@ -71,6 +72,7 @@ interface ProviderStateBase {
|
|
|
71
72
|
runtime?: ProviderRuntimeInfo;
|
|
72
73
|
resume?: ProviderResumeCapability;
|
|
73
74
|
sessionCapabilities?: SessionCapability[];
|
|
75
|
+
messageInput?: MessageInputSupport;
|
|
74
76
|
/** Dynamic control current values */
|
|
75
77
|
controlValues?: Record<string, string | number | boolean>;
|
|
76
78
|
/** Provider-declared controls schema (from provider.controls) */
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -240,7 +240,9 @@ export type SessionTransport = 'cdp-page' | 'cdp-webview' | 'pty' | 'acp';
|
|
|
240
240
|
export type SessionKind = 'workspace' | 'agent';
|
|
241
241
|
export type SessionCapability = 'read_chat' | 'send_message' | 'new_session' | 'list_sessions' | 'switch_session' | 'resolve_action' | 'open_panel' | 'terminal_io' | 'resize_terminal' | 'change_model' | 'set_mode' | 'set_thought_level' | 'delete_notification' | 'mark_notification_unread';
|
|
242
242
|
import type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
|
|
243
|
+
import type { MessageInputSupport } from './providers/provider-input-support.js';
|
|
243
244
|
export type { RuntimeWriteOwner, RuntimeAttachedClient, SessionStatus } from './shared-types-extra.js';
|
|
245
|
+
export type { MessageInputSupport, InputMediaStrategyDescriptor, InputAttachmentStrategy, InputMediaType } from './providers/provider-input-support.js';
|
|
244
246
|
export interface SessionEntry {
|
|
245
247
|
id: string;
|
|
246
248
|
parentId: string | null;
|
|
@@ -267,6 +269,8 @@ export interface SessionEntry {
|
|
|
267
269
|
resume?: ProviderResumeCapability;
|
|
268
270
|
activeChat: SessionActiveChatData | null;
|
|
269
271
|
capabilities?: SessionCapability[];
|
|
272
|
+
/** Effective message input/media support for this session. Defaults fail-closed to text-only. */
|
|
273
|
+
messageInput?: MessageInputSupport;
|
|
270
274
|
cdpConnected?: boolean;
|
|
271
275
|
/** Dynamic control current values (generic key-value) */
|
|
272
276
|
controlValues?: Record<string, string | number | boolean>;
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
|
16
16
|
import { readProviderChatHistory } from '../config/chat-history.js';
|
|
17
17
|
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
18
18
|
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
19
|
-
import { buildChatMessageSignature } from '../chat/chat-signatures.js';
|
|
19
|
+
import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
|
|
20
20
|
import type { ChatMessage } from '../types.js';
|
|
21
21
|
import type { SessionTransport } from '../shared-types.js';
|
|
22
22
|
import { filterUserFacingChatMessages, normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
@@ -93,10 +93,32 @@ function buildRecentSendKey(h: CommandHelpers, args: any, provider: ProviderModu
|
|
|
93
93
|
return `${transport}:${target}:${signature.trim()}`;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
function
|
|
96
|
+
function summarizeSendInputPart(part: any): string {
|
|
97
|
+
if (!part || typeof part !== 'object') return String(part ?? '');
|
|
98
|
+
if (part.type === 'text') return `text:${String(part.text || '').trim()}`;
|
|
99
|
+
const fields = [
|
|
100
|
+
`type=${String(part.type || '')}`,
|
|
101
|
+
`mime=${String(part.mimeType || '')}`,
|
|
102
|
+
`uri=${String(part.uri || '')}`,
|
|
103
|
+
`name=${String(part.name || '')}`,
|
|
104
|
+
];
|
|
105
|
+
const data = typeof part.data === 'string'
|
|
106
|
+
? part.data
|
|
107
|
+
: typeof part.resource?.blob === 'string'
|
|
108
|
+
? part.resource.blob
|
|
109
|
+
: '';
|
|
110
|
+
if (data) fields.push(`dataLen=${data.length}`, `dataHash=${hashSignatureParts([data]).slice(0, 12)}`);
|
|
111
|
+
const textish = [part.alt, part.transcript, part.description, part.title, part.resource?.uri]
|
|
112
|
+
.filter((value) => typeof value === 'string' && value.trim())
|
|
113
|
+
.join('\u001f');
|
|
114
|
+
if (textish) fields.push(`meta=${hashSignatureParts([textish]).slice(0, 12)}`);
|
|
115
|
+
return fields.join(';');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildSendInputSignature(input: InputEnvelope): string {
|
|
97
119
|
const text = typeof input.textFallback === 'string' ? input.textFallback.trim() : '';
|
|
98
|
-
|
|
99
|
-
return
|
|
120
|
+
const partSummaries = (input.parts || []).map(summarizeSendInputPart);
|
|
121
|
+
return hashSignatureParts([text, ...partSummaries]);
|
|
100
122
|
}
|
|
101
123
|
|
|
102
124
|
function getSendChatInputEnvelope(args: any): InputEnvelope {
|
|
@@ -1140,12 +1162,25 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1140
1162
|
}
|
|
1141
1163
|
}
|
|
1142
1164
|
|
|
1143
|
-
// PTY transport:
|
|
1165
|
+
// PTY transport: route structured input through the provider instance so
|
|
1166
|
+
// provider-specific CLI attachment strategies (for example Hermes file-path
|
|
1167
|
+
// image prompts) are applied instead of collapsing everything to text.
|
|
1144
1168
|
if (transport === 'pty') {
|
|
1145
1169
|
const adapter = getTargetedCliAdapter(h, args, provider?.type);
|
|
1146
1170
|
if (adapter) {
|
|
1147
1171
|
_log(`${transport} adapter: ${adapter.cliType}`);
|
|
1148
1172
|
try {
|
|
1173
|
+
const hasStructuredParts = input.parts.some((part) => part.type !== 'text');
|
|
1174
|
+
if (hasStructuredParts) {
|
|
1175
|
+
const target = getTargetInstance(h, args);
|
|
1176
|
+
if (!target || target.category !== 'cli') {
|
|
1177
|
+
return { success: false, error: `CLI instance not found for ${provider?.type || args?.agentType || 'unknown'}` };
|
|
1178
|
+
}
|
|
1179
|
+
assertProviderSupportsDeclaredInput(provider, input);
|
|
1180
|
+
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1181
|
+
target.onEvent('send_message', { input });
|
|
1182
|
+
return _logSendSuccess(`${transport}-instance`, target.type);
|
|
1183
|
+
}
|
|
1149
1184
|
assertTextOnlyInput(provider, input);
|
|
1150
1185
|
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
1151
1186
|
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
2
3
|
import { existsSync, readdirSync, realpathSync } from 'node:fs'
|
|
3
4
|
import { createRequire } from 'node:module'
|
|
4
5
|
import * as os from 'node:os'
|
|
@@ -64,7 +65,7 @@ function resolveHermesMeshCoordinatorSetup(options: ResolveMeshCoordinatorSetupO
|
|
|
64
65
|
reason: 'Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode',
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
const configPath =
|
|
68
|
+
const configPath = join(resolveHermesCoordinatorHome(options.meshId, options.workspace), 'config.yaml')
|
|
68
69
|
if (!configPath.trim()) {
|
|
69
70
|
return createHermesManualMeshCoordinatorSetup(options.meshId, options.workspace)
|
|
70
71
|
}
|
|
@@ -177,6 +178,12 @@ function renderMeshCoordinatorTemplate(template: string, values: Record<string,
|
|
|
177
178
|
return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand)\s*\}\}/g, (_, key: string) => values[key] || '')
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
function resolveHermesCoordinatorHome(meshId: string, workspace: string): string {
|
|
182
|
+
const key = `${meshId || 'mesh'}\n${resolve(workspace || os.tmpdir())}`
|
|
183
|
+
const hash = createHash('sha256').update(key).digest('hex').slice(0, 16)
|
|
184
|
+
return join(os.tmpdir(), `adhdev-hermes-mesh-coordinator-${hash}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
180
187
|
function resolveMcpConfigPath(configPath: string, workspace: string): string {
|
|
181
188
|
const trimmed = configPath.trim()
|
|
182
189
|
if (trimmed === '~') return os.homedir()
|
package/src/commands/router.ts
CHANGED
|
@@ -1672,6 +1672,10 @@ export class DaemonCommandRouter {
|
|
|
1672
1672
|
|
|
1673
1673
|
const cliArgs: string[] = [];
|
|
1674
1674
|
const launchEnv: Record<string, string> = {};
|
|
1675
|
+
if (configFormat === 'hermes_config_yaml') {
|
|
1676
|
+
launchEnv.HERMES_HOME = dirname(mcpConfigPath);
|
|
1677
|
+
launchEnv.HERMES_IGNORE_USER_CONFIG = '';
|
|
1678
|
+
}
|
|
1675
1679
|
if (systemPrompt) {
|
|
1676
1680
|
if (configFormat === 'hermes_config_yaml') {
|
|
1677
1681
|
launchEnv.HERMES_EPHEMERAL_SYSTEM_PROMPT = systemPrompt;
|
|
@@ -52,6 +52,9 @@ Repository: \`${mesh.repoIdentity}\`${mesh.defaultBranch ? `\nDefault branch: \`
|
|
|
52
52
|
// ── Tools ──
|
|
53
53
|
sections.push(TOOLS_SECTION);
|
|
54
54
|
|
|
55
|
+
// ── Tool Exposure Preflight ──
|
|
56
|
+
sections.push(TOOL_EXPOSURE_PREFLIGHT_SECTION);
|
|
57
|
+
|
|
55
58
|
// ── Workflow ──
|
|
56
59
|
sections.push(WORKFLOW_SECTION);
|
|
57
60
|
|
|
@@ -136,6 +139,10 @@ const TOOLS_SECTION = `## Available Tools
|
|
|
136
139
|
| \`mesh_clone_node\` | Create a worktree node for isolated parallel branch work |
|
|
137
140
|
| \`mesh_remove_node\` | Remove a node (cleans up worktree if applicable) |`;
|
|
138
141
|
|
|
142
|
+
const TOOL_EXPOSURE_PREFLIGHT_SECTION = `## Tool Exposure Preflight
|
|
143
|
+
|
|
144
|
+
Before doing any coordinator work, confirm that the actual callable tool list includes \`mesh_status\` and the other \`mesh_*\` tools from the table above. If this Repo Mesh coordinator prompt is present but the callable \`mesh_*\` tools are missing, the MCP server/tool manifest is stale or not injected yet. Do not substitute terminal/file/git tools, do not inspect or edit the repository directly, and do not continue as a non-mesh local coding agent. Stop immediately and tell the user to run \`/reload-mcp\` or start a fresh coordinator session so ADHDev can reconnect \`adhdev-mesh\`.`;
|
|
145
|
+
|
|
139
146
|
const WORKFLOW_SECTION = `## Orchestration Workflow
|
|
140
147
|
|
|
141
148
|
1. **Assess** — Call \`mesh_status\` to see which nodes are healthy and available.
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
} from '@agentclientprotocol/sdk';
|
|
49
49
|
import type { ProviderModule, ContentBlock, InputEnvelope, ToolCallInfo, ToolCallContent as TCC, ToolKind, ToolCallStatus as TCS } from './contracts.js';
|
|
50
50
|
import { normalizeContent, flattenContent, normalizeInputEnvelope } from './contracts.js';
|
|
51
|
-
import { assertProviderSupportsDeclaredInput } from './provider-input-support.js';
|
|
51
|
+
import { assertProviderSupportsDeclaredInput, getEffectiveMessageInputSupport } from './provider-input-support.js';
|
|
52
52
|
import type { ProviderInstance, ProviderState, AcpProviderState, ProviderErrorReason, ProviderEvent, InstanceContext, SessionModalState } from './provider-instance.js';
|
|
53
53
|
import { StatusMonitor } from './status-monitor.js';
|
|
54
54
|
import { buildLegacyModelModeSummaryMetadata } from './summary-metadata.js';
|
|
@@ -121,6 +121,41 @@ function appendPromptText(promptParts: ContentBlock[], text: string | undefined)
|
|
|
121
121
|
promptParts.push({ type: 'text', text: normalized });
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function getUriDisplayName(uri: string | undefined, fallback: string): string {
|
|
125
|
+
if (!uri) return fallback;
|
|
126
|
+
try {
|
|
127
|
+
const pathname = uri.startsWith('file://') ? new URL(uri).pathname : uri;
|
|
128
|
+
return pathname.split(/[\\/]/).filter(Boolean).pop() || fallback;
|
|
129
|
+
} catch {
|
|
130
|
+
return uri.split(/[\\/]/).filter(Boolean).pop() || fallback;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function appendResourceLink(
|
|
135
|
+
promptParts: ContentBlock[],
|
|
136
|
+
uri: string,
|
|
137
|
+
fallbackName: string,
|
|
138
|
+
mimeType?: string,
|
|
139
|
+
description?: string,
|
|
140
|
+
metadata?: Pick<Extract<ContentBlock, { type: 'resource_link' }>, 'title' | 'size' | 'annotations'> & { name?: string },
|
|
141
|
+
): void {
|
|
142
|
+
promptParts.push({
|
|
143
|
+
type: 'resource_link',
|
|
144
|
+
uri,
|
|
145
|
+
name: metadata?.name || getUriDisplayName(uri, fallbackName),
|
|
146
|
+
...(metadata?.title ? { title: metadata.title } : {}),
|
|
147
|
+
...(mimeType ? { mimeType } : {}),
|
|
148
|
+
...(description ? { description } : {}),
|
|
149
|
+
...(typeof metadata?.size === 'number' ? { size: metadata.size } : {}),
|
|
150
|
+
...(metadata?.annotations ? { annotations: metadata.annotations } : {}),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function appendMediaFallbackText(promptParts: ContentBlock[], label: string, details: Array<string | undefined>): void {
|
|
155
|
+
const normalizedDetails = details.map((value) => typeof value === 'string' ? value.trim() : '').filter(Boolean);
|
|
156
|
+
appendPromptText(promptParts, `[${[label, ...normalizedDetails].join(': ')}]`);
|
|
157
|
+
}
|
|
158
|
+
|
|
124
159
|
export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Record<string, any>): ContentBlock[] {
|
|
125
160
|
const caps = getPromptCapabilityFlags(agentCapabilities);
|
|
126
161
|
const promptParts: ContentBlock[] = [];
|
|
@@ -132,59 +167,82 @@ export function buildAcpPromptParts(input: InputEnvelope, agentCapabilities?: Re
|
|
|
132
167
|
}
|
|
133
168
|
|
|
134
169
|
if (part.type === 'image') {
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
if (caps.image && part.data) {
|
|
171
|
+
promptParts.push({
|
|
172
|
+
type: 'image',
|
|
173
|
+
data: part.data,
|
|
174
|
+
mimeType: part.mimeType,
|
|
175
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
176
|
+
...(part.alt ? { alt: part.alt } : {}),
|
|
177
|
+
});
|
|
178
|
+
if (part.alt) appendPromptText(promptParts, part.alt);
|
|
179
|
+
} else if (part.uri) {
|
|
180
|
+
appendResourceLink(promptParts, part.uri, 'image', part.mimeType, part.alt);
|
|
181
|
+
if (part.alt) appendPromptText(promptParts, part.alt);
|
|
182
|
+
} else {
|
|
183
|
+
appendMediaFallbackText(promptParts, 'Image attachment', [part.alt, part.mimeType]);
|
|
140
184
|
}
|
|
141
|
-
promptParts.push({
|
|
142
|
-
type: 'image',
|
|
143
|
-
data: part.data,
|
|
144
|
-
mimeType: part.mimeType,
|
|
145
|
-
...(part.uri ? { uri: part.uri } : {}),
|
|
146
|
-
});
|
|
147
185
|
continue;
|
|
148
186
|
}
|
|
149
187
|
|
|
150
188
|
if (part.type === 'audio') {
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
189
|
+
if (caps.audio && part.data) {
|
|
190
|
+
promptParts.push({
|
|
191
|
+
type: 'audio',
|
|
192
|
+
data: part.data,
|
|
193
|
+
mimeType: part.mimeType,
|
|
194
|
+
...(part.uri ? { uri: part.uri } : {}),
|
|
195
|
+
...(part.transcript ? { transcript: part.transcript } : {}),
|
|
196
|
+
});
|
|
197
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
198
|
+
} else if (part.uri) {
|
|
199
|
+
appendResourceLink(promptParts, part.uri, 'audio', part.mimeType, part.transcript);
|
|
200
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
201
|
+
} else {
|
|
202
|
+
appendMediaFallbackText(promptParts, 'Audio attachment', [part.transcript, part.mimeType]);
|
|
156
203
|
}
|
|
157
|
-
promptParts.push({
|
|
158
|
-
type: 'audio',
|
|
159
|
-
data: part.data,
|
|
160
|
-
mimeType: part.mimeType,
|
|
161
|
-
});
|
|
162
204
|
continue;
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
if (part.type === 'resource') {
|
|
166
|
-
if (
|
|
167
|
-
throw new Error('ACP agent does not support input type: resource');
|
|
168
|
-
}
|
|
169
|
-
if (part.text) {
|
|
208
|
+
if (caps.embeddedContext && part.text) {
|
|
170
209
|
promptParts.push({
|
|
171
210
|
type: 'resource',
|
|
172
211
|
resource: { uri: part.uri, text: part.text, mimeType: part.mimeType ?? null },
|
|
173
212
|
});
|
|
174
213
|
continue;
|
|
175
214
|
}
|
|
176
|
-
if (part.data) {
|
|
215
|
+
if (caps.embeddedContext && part.data) {
|
|
177
216
|
promptParts.push({
|
|
178
217
|
type: 'resource',
|
|
179
218
|
resource: { uri: part.uri, blob: part.data, mimeType: part.mimeType ?? null },
|
|
180
219
|
});
|
|
181
220
|
continue;
|
|
182
221
|
}
|
|
183
|
-
|
|
222
|
+
appendResourceLink(promptParts, part.uri, part.name || 'resource', part.mimeType, part.text);
|
|
223
|
+
if (part.text) appendPromptText(promptParts, part.text);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (part.type === 'resource_link') {
|
|
228
|
+
appendResourceLink(promptParts, part.uri, part.name, part.mimeType, part.description, {
|
|
229
|
+
name: part.name,
|
|
230
|
+
...(part.title ? { title: part.title } : {}),
|
|
231
|
+
...(typeof part.size === 'number' ? { size: part.size } : {}),
|
|
232
|
+
...(part.annotations ? { annotations: part.annotations } : {}),
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
184
235
|
}
|
|
185
236
|
|
|
186
237
|
if (part.type === 'video') {
|
|
187
|
-
|
|
238
|
+
// ACP v0.16 prompt capabilities do not advertise native video input. Preserve meaning by
|
|
239
|
+
// sending a linked resource when possible, plus transcript/descriptive text when present.
|
|
240
|
+
if (part.uri) {
|
|
241
|
+
appendResourceLink(promptParts, part.uri, 'video', part.mimeType, part.transcript);
|
|
242
|
+
if (part.transcript) appendPromptText(promptParts, part.transcript);
|
|
243
|
+
} else {
|
|
244
|
+
appendMediaFallbackText(promptParts, 'Video attachment', [part.transcript, part.mimeType]);
|
|
245
|
+
}
|
|
188
246
|
}
|
|
189
247
|
}
|
|
190
248
|
|
|
@@ -346,6 +404,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
346
404
|
lastUpdated: Date.now(),
|
|
347
405
|
settings: this.settings,
|
|
348
406
|
pendingEvents: this.flushEvents(),
|
|
407
|
+
messageInput: getEffectiveMessageInputSupport(this.provider, this.agentCapabilities),
|
|
349
408
|
// ACP-specific: expose available models/modes for dashboard
|
|
350
409
|
acpConfigOptions: this.configOptions,
|
|
351
410
|
acpModes: this.availableModes,
|
|
@@ -962,6 +1021,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
962
1021
|
data: b.data,
|
|
963
1022
|
mimeType: b.mimeType,
|
|
964
1023
|
...(b.uri ? { uri: b.uri } : {}),
|
|
1024
|
+
...(b.alt ? { alt: b.alt } : {}),
|
|
965
1025
|
};
|
|
966
1026
|
}
|
|
967
1027
|
if (b.type === 'audio') {
|
|
@@ -969,14 +1029,31 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
969
1029
|
type: 'audio',
|
|
970
1030
|
data: b.data,
|
|
971
1031
|
mimeType: b.mimeType,
|
|
1032
|
+
...(b.uri ? { uri: b.uri } : {}),
|
|
1033
|
+
...(b.transcript ? { transcript: b.transcript } : {}),
|
|
972
1034
|
};
|
|
973
1035
|
}
|
|
1036
|
+
if (b.type === 'video') {
|
|
1037
|
+
return b.uri
|
|
1038
|
+
? {
|
|
1039
|
+
type: 'resource_link',
|
|
1040
|
+
uri: b.uri,
|
|
1041
|
+
name: path.basename(b.uri),
|
|
1042
|
+
mimeType: b.mimeType,
|
|
1043
|
+
...(b.transcript ? { description: b.transcript } : {}),
|
|
1044
|
+
}
|
|
1045
|
+
: { type: 'text', text: b.transcript || `[Video attachment: ${b.mimeType}]` };
|
|
1046
|
+
}
|
|
974
1047
|
if (b.type === 'resource_link') {
|
|
975
1048
|
return {
|
|
976
1049
|
type: 'resource_link',
|
|
977
1050
|
uri: b.uri,
|
|
978
1051
|
name: b.name,
|
|
1052
|
+
...(b.title ? { title: b.title } : {}),
|
|
1053
|
+
...(b.description ? { description: b.description } : {}),
|
|
979
1054
|
...(b.mimeType ? { mimeType: b.mimeType } : {}),
|
|
1055
|
+
...(typeof b.size === 'number' ? { size: b.size } : {}),
|
|
1056
|
+
...(b.annotations ? { annotations: b.annotations } : {}),
|
|
980
1057
|
};
|
|
981
1058
|
}
|
|
982
1059
|
if (b.type === 'resource') return { type: 'resource', resource: b.resource };
|
|
@@ -1056,7 +1133,7 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1056
1133
|
|
|
1057
1134
|
switch (update.sessionUpdate) {
|
|
1058
1135
|
case 'agent_message_chunk': {
|
|
1059
|
-
const content = update.content;
|
|
1136
|
+
const content: any = update.content;
|
|
1060
1137
|
if (content.type === 'text') {
|
|
1061
1138
|
this.partialContent += content.text;
|
|
1062
1139
|
} else if (content.type === 'image') {
|
|
@@ -1071,6 +1148,17 @@ export class AcpProviderInstance implements ProviderInstance {
|
|
|
1071
1148
|
type: 'audio',
|
|
1072
1149
|
data: content.data,
|
|
1073
1150
|
mimeType: content.mimeType,
|
|
1151
|
+
...(content.uri ? { uri: content.uri } : {}),
|
|
1152
|
+
...(content.transcript ? { transcript: content.transcript } : {}),
|
|
1153
|
+
});
|
|
1154
|
+
} else if (content.type === 'video') {
|
|
1155
|
+
this.partialBlocks.push({
|
|
1156
|
+
type: 'video',
|
|
1157
|
+
data: content.data,
|
|
1158
|
+
mimeType: content.mimeType,
|
|
1159
|
+
...(content.uri ? { uri: content.uri } : {}),
|
|
1160
|
+
...(content.transcript ? { transcript: content.transcript } : {}),
|
|
1161
|
+
...(content.posterUri ? { posterUri: content.posterUri } : {}),
|
|
1074
1162
|
});
|
|
1075
1163
|
} else if (content.type === 'resource_link') {
|
|
1076
1164
|
this.partialBlocks.push({
|