@adhdev/daemon-core 0.9.76-rc.8 → 0.9.76
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 +5 -2
- package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +24 -0
- package/dist/commands/chat-commands.d.ts +2 -0
- package/dist/commands/cli-manager.d.ts +17 -4
- package/dist/commands/mesh-coordinator.d.ts +2 -0
- package/dist/commands/router.d.ts +11 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/git/git-types.d.ts +1 -1
- package/dist/git/git-worktree.d.ts +64 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2427 -561
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2432 -584
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +18 -0
- package/dist/providers/chat-message-normalization.d.ts +40 -0
- package/dist/providers/cli-provider-instance.d.ts +7 -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-manager.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +4 -0
- package/dist/repo-mesh-types.d.ts +34 -0
- package/dist/session-host/runtime-support.d.ts +2 -1
- package/dist/shared-types.d.ts +8 -0
- package/dist/types.d.ts +9 -0
- package/package.json +4 -5
- package/src/chat/subscription-updates.ts +3 -1
- package/src/cli-adapters/provider-cli-adapter.ts +44 -11
- package/src/cli-adapters/provider-cli-runtime.ts +3 -2
- package/src/cli-adapters/provider-cli-shared.ts +201 -15
- package/src/commands/chat-commands.ts +166 -16
- package/src/commands/cli-manager.ts +78 -5
- package/src/commands/handler.ts +13 -4
- package/src/commands/mesh-coordinator.ts +155 -5
- package/src/commands/router.d.ts +1 -0
- package/src/commands/router.ts +606 -32
- package/src/config/mesh-config.ts +27 -2
- package/src/git/git-commands.ts +5 -1
- package/src/git/git-types.ts +1 -0
- package/src/git/git-worktree.ts +214 -0
- package/src/git/index.ts +14 -0
- package/src/index.ts +20 -1
- package/src/mesh/coordinator-prompt.ts +36 -14
- package/src/mesh/mesh-events.ts +173 -42
- package/src/providers/acp-provider-instance.ts +118 -30
- package/src/providers/chat-message-normalization.ts +241 -0
- package/src/providers/cli-provider-instance.d.ts +2 -0
- package/src/providers/cli-provider-instance.ts +219 -13
- 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-manager.ts +20 -1
- package/src/providers/provider-instance.ts +4 -0
- package/src/providers/provider-schema.ts +38 -8
- package/src/providers/read-chat-contract.ts +8 -0
- package/src/repo-mesh-types.ts +38 -0
- package/src/session-host/runtime-support.ts +55 -7
- package/src/shared-types.ts +8 -0
- package/src/status/builders.ts +5 -3
- package/src/status/reporter.ts +6 -0
- package/src/types.ts +9 -0
package/src/mesh/mesh-events.ts
CHANGED
|
@@ -1,61 +1,192 @@
|
|
|
1
1
|
import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
|
|
2
|
-
import { getMeshByRepo } from '../config/mesh-config.js';
|
|
2
|
+
import { getMesh, getMeshByRepo } from '../config/mesh-config.js';
|
|
3
3
|
import { LOG } from '../logging/logger.js';
|
|
4
4
|
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// MCP coordinator pending-event queue
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// When a mesh event fires but no CLI coordinator session is registered (e.g.
|
|
9
|
+
// the coordinator is Claude Code running via MCP), we buffer the event here.
|
|
10
|
+
// The MCP server drains this queue on every mesh_status / mesh_send_task poll.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export interface PendingMeshCoordinatorEvent {
|
|
14
|
+
event: string;
|
|
15
|
+
meshId: string;
|
|
16
|
+
nodeLabel: string;
|
|
17
|
+
metadataEvent: Record<string, unknown>;
|
|
18
|
+
queuedAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MAX_PENDING_EVENTS = 50;
|
|
22
|
+
const pendingMeshCoordinatorEvents: PendingMeshCoordinatorEvent[] = [];
|
|
23
|
+
|
|
24
|
+
/** Drain and return all pending coordinator events, clearing the queue. */
|
|
25
|
+
export function drainPendingMeshCoordinatorEvents(): PendingMeshCoordinatorEvent[] {
|
|
26
|
+
return pendingMeshCoordinatorEvents.splice(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readNonEmptyString(value: unknown): string {
|
|
30
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const MESH_COORDINATOR_EVENTS = new Set([
|
|
34
|
+
'agent:generating_completed',
|
|
35
|
+
'agent:waiting_approval',
|
|
36
|
+
'agent:stopped',
|
|
37
|
+
'monitor:long_generating',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function isMeshCoordinatorEvent(eventName: unknown): eventName is string {
|
|
41
|
+
return typeof eventName === 'string' && MESH_COORDINATOR_EVENTS.has(eventName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formatCompletionMetadata(event: Record<string, unknown>): string {
|
|
45
|
+
const parts = [
|
|
46
|
+
readNonEmptyString(event.targetSessionId) ? `session_id=${readNonEmptyString(event.targetSessionId)}` : '',
|
|
47
|
+
readNonEmptyString(event.providerType) ? `provider=${readNonEmptyString(event.providerType)}` : '',
|
|
48
|
+
readNonEmptyString(event.providerSessionId) ? `provider_session_id=${readNonEmptyString(event.providerSessionId)}` : '',
|
|
49
|
+
].filter(Boolean);
|
|
50
|
+
return parts.length > 0 ? ` (${parts.join('; ')})` : '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildMeshSystemMessage(args: {
|
|
54
|
+
event: string;
|
|
55
|
+
nodeLabel: string;
|
|
56
|
+
metadataEvent: Record<string, unknown>;
|
|
57
|
+
}): string {
|
|
58
|
+
const metadata = formatCompletionMetadata(args.metadataEvent);
|
|
59
|
+
if (args.event === 'agent:generating_completed') {
|
|
60
|
+
return `[System] ${args.nodeLabel} has completed its task and is now idle${metadata}. This completion came from the agent status event path; use mesh_read_chat once to review its final progress, but do not poll repeatedly.`;
|
|
61
|
+
}
|
|
62
|
+
if (args.event === 'agent:waiting_approval') {
|
|
63
|
+
return `[System] ${args.nodeLabel} is waiting for approval to proceed${metadata}. You may use mesh_read_chat and mesh_approve to handle it.`;
|
|
64
|
+
}
|
|
65
|
+
if (args.event === 'agent:stopped') {
|
|
66
|
+
return `[System] ${args.nodeLabel} has stopped${metadata}. Use mesh_read_chat once if you need to inspect its last output.`;
|
|
67
|
+
}
|
|
68
|
+
if (args.event === 'monitor:long_generating') {
|
|
69
|
+
return `[System] ${args.nodeLabel} has been generating for a long time${metadata}. Use mesh_read_chat once for a status check, but do not poll repeatedly.`;
|
|
70
|
+
}
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
75
|
+
meshId: string;
|
|
76
|
+
sourceInstanceId?: string;
|
|
77
|
+
nodeLabel: string;
|
|
78
|
+
event: string;
|
|
79
|
+
metadataEvent: Record<string, unknown>;
|
|
80
|
+
}) {
|
|
81
|
+
const coordinatorInstances = components.instanceManager.getByCategory('cli').filter((inst) => {
|
|
82
|
+
const instState = inst.getState();
|
|
83
|
+
if (instState.settings?.meshCoordinatorFor !== args.meshId) return false;
|
|
84
|
+
if (args.sourceInstanceId && instState.instanceId === args.sourceInstanceId) return false;
|
|
85
|
+
return true;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (coordinatorInstances.length === 0) {
|
|
89
|
+
// No CLI coordinator session found — buffer for MCP-based coordinators.
|
|
90
|
+
if (pendingMeshCoordinatorEvents.length < MAX_PENDING_EVENTS) {
|
|
91
|
+
pendingMeshCoordinatorEvents.push({
|
|
92
|
+
event: args.event,
|
|
93
|
+
meshId: args.meshId,
|
|
94
|
+
nodeLabel: args.nodeLabel,
|
|
95
|
+
metadataEvent: args.metadataEvent,
|
|
96
|
+
queuedAt: Date.now(),
|
|
97
|
+
});
|
|
98
|
+
LOG.info('MeshEvents', `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
|
|
99
|
+
}
|
|
100
|
+
return { success: true, forwarded: 0 };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const messageText = buildMeshSystemMessage({
|
|
104
|
+
event: args.event,
|
|
105
|
+
nodeLabel: args.nodeLabel,
|
|
106
|
+
metadataEvent: args.metadataEvent,
|
|
107
|
+
});
|
|
108
|
+
if (!messageText) return { success: false, error: 'unsupported mesh event' };
|
|
109
|
+
|
|
110
|
+
for (const coord of coordinatorInstances) {
|
|
111
|
+
const coordState = coord.getState();
|
|
112
|
+
LOG.info('MeshEvents', `Forwarding mesh event to coordinator ${coordState.instanceId}`);
|
|
113
|
+
coord.onEvent('send_message', { input: { text: messageText, textFallback: messageText } });
|
|
114
|
+
}
|
|
115
|
+
return { success: true, forwarded: coordinatorInstances.length };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function handleMeshForwardEvent(components: DaemonComponents, payload: Record<string, unknown>) {
|
|
119
|
+
const eventName = readNonEmptyString(payload.event);
|
|
120
|
+
if (!isMeshCoordinatorEvent(eventName)) {
|
|
121
|
+
return { success: false, error: 'unsupported mesh event' };
|
|
122
|
+
}
|
|
123
|
+
const meshId = readNonEmptyString(payload.meshId);
|
|
124
|
+
if (!meshId) return { success: false, error: 'meshId required' };
|
|
125
|
+
|
|
126
|
+
const nodeId = readNonEmptyString(payload.nodeId);
|
|
127
|
+
const workspace = readNonEmptyString(payload.workspace);
|
|
128
|
+
const nodeLabel = nodeId ? `Node '${nodeId}'` : workspace ? `Agent at ${workspace}` : 'Remote agent';
|
|
129
|
+
return injectMeshSystemMessage(components, {
|
|
130
|
+
meshId,
|
|
131
|
+
nodeLabel,
|
|
132
|
+
event: eventName,
|
|
133
|
+
metadataEvent: {
|
|
134
|
+
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId),
|
|
135
|
+
providerType: readNonEmptyString(payload.providerType),
|
|
136
|
+
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
5
141
|
export function setupMeshEventForwarding(components: DaemonComponents) {
|
|
6
142
|
components.instanceManager.onEvent((event) => {
|
|
7
|
-
// We only care about
|
|
8
|
-
if (event.event
|
|
9
|
-
|
|
10
|
-
const instanceId = event.instanceId
|
|
143
|
+
// We only care about lightweight Repo Mesh coordinator control/status hints.
|
|
144
|
+
if (!isMeshCoordinatorEvent(event.event)) return;
|
|
145
|
+
|
|
146
|
+
const instanceId = readNonEmptyString(event.instanceId);
|
|
11
147
|
if (!instanceId) return;
|
|
12
148
|
|
|
13
|
-
// Try to find the workspace of the sub-agent
|
|
149
|
+
// Try to find the workspace and mesh metadata of the sub-agent.
|
|
14
150
|
const sourceInstance = components.instanceManager.getInstance(instanceId);
|
|
15
151
|
if (!sourceInstance || sourceInstance.category !== 'cli') return;
|
|
16
152
|
const state = sourceInstance.getState();
|
|
17
|
-
const workspace = state.workspace;
|
|
153
|
+
const workspace = readNonEmptyString(state.workspace);
|
|
18
154
|
if (!workspace) return;
|
|
155
|
+
const settings = state.settings && typeof state.settings === 'object' ? state.settings as Record<string, unknown> : {};
|
|
19
156
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
// Find the coordinator session(s)
|
|
25
|
-
const allInstances = components.instanceManager.getByCategory('cli');
|
|
26
|
-
const coordinatorInstances = allInstances.filter((inst) => {
|
|
27
|
-
const instState = inst.getState();
|
|
28
|
-
|
|
29
|
-
// The coordinator session was launched with meshCoordinatorFor setting
|
|
30
|
-
if (instState.settings?.meshCoordinatorFor !== mesh.id) return false;
|
|
31
|
-
|
|
32
|
-
// Exclude the source instance itself (just in case)
|
|
33
|
-
if (instState.instanceId === instanceId) return false;
|
|
34
|
-
|
|
35
|
-
return true;
|
|
36
|
-
});
|
|
157
|
+
// Coordinator sessions must never inject events into themselves.
|
|
158
|
+
// A coordinator instance carries meshCoordinatorFor but not meshNodeFor/launchedByCoordinator.
|
|
159
|
+
if (readNonEmptyString(settings.meshCoordinatorFor)) return;
|
|
37
160
|
|
|
38
|
-
|
|
161
|
+
const meshIdFromRuntime = readNonEmptyString(settings.meshNodeFor);
|
|
39
162
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
163
|
+
// Only forward events for sessions that were explicitly launched as mesh-node delegates
|
|
164
|
+
// (meshNodeFor set by mesh_launch_session) or that carry the launchedByCoordinator flag.
|
|
165
|
+
// Do NOT fall back to workspace-based mesh lookup: that would pick up coordinator sessions
|
|
166
|
+
// and any other CLI session that happens to share the same workspace, causing spurious
|
|
167
|
+
// system-message injection into the coordinator's own conversation.
|
|
168
|
+
const isMeshDelegate = Boolean(meshIdFromRuntime || settings.launchedByCoordinator);
|
|
169
|
+
if (!isMeshDelegate) return;
|
|
43
170
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
messageText = `[System] ${nodeLabel} has completed its task and is now idle. You may use mesh_read_chat to review its progress.`;
|
|
48
|
-
} else if (event.event === 'agent:waiting_approval') {
|
|
49
|
-
messageText = `[System] ${nodeLabel} is waiting for approval to proceed. You may use mesh_read_chat and mesh_approve to handle it.`;
|
|
50
|
-
}
|
|
171
|
+
const mesh = meshIdFromRuntime ? getMesh(meshIdFromRuntime) : getMeshByRepo(workspace);
|
|
172
|
+
const meshId = meshIdFromRuntime || readNonEmptyString(mesh?.id);
|
|
173
|
+
if (!meshId) return;
|
|
51
174
|
|
|
52
|
-
|
|
175
|
+
// Determine node label. Inline/cloud meshes may be unavailable here, so preserve runtime node id.
|
|
176
|
+
const targetNode = mesh?.nodes?.find((n: any) => n.workspace === workspace);
|
|
177
|
+
const runtimeNodeId = readNonEmptyString(settings.meshNodeId);
|
|
178
|
+
const nodeLabel = targetNode
|
|
179
|
+
? `Node '${targetNode.id}'`
|
|
180
|
+
: runtimeNodeId
|
|
181
|
+
? `Node '${runtimeNodeId}'`
|
|
182
|
+
: `Agent at ${workspace}`;
|
|
53
183
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
184
|
+
injectMeshSystemMessage(components, {
|
|
185
|
+
meshId,
|
|
186
|
+
sourceInstanceId: instanceId,
|
|
187
|
+
nodeLabel,
|
|
188
|
+
event: event.event,
|
|
189
|
+
metadataEvent: event,
|
|
190
|
+
});
|
|
60
191
|
});
|
|
61
192
|
}
|
|
@@ -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({
|