@borgee/agents-host 0.2.84 → 0.2.94
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/README.md +1 -1
- package/dist/agents-host.js +15 -5
- package/dist/background-runs.d.ts +2 -1
- package/dist/background-runs.js +48 -17
- package/dist/chat/chat-control-plane.d.ts +1 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +3 -0
- package/dist/chat/sdk-chat-control-plane.js +15 -0
- package/dist/plugin-sdk.js +136 -20
- package/dist/plugin-sdk.js.map +3 -3
- package/dist/providers/claude/adapter.d.ts +1 -0
- package/dist/providers/claude/adapter.js +31 -3
- package/dist/providers/claude/background-run-observer.d.ts +32 -0
- package/dist/providers/claude/background-run-observer.js +381 -0
- package/dist/providers/claude/cli-client.d.ts +12 -0
- package/dist/providers/claude/cli-client.js +449 -69
- package/dist/providers/claude/foreground-handoff.d.ts +4 -0
- package/dist/providers/claude/foreground-handoff.js +45 -0
- package/dist/providers/claude/task-cancellation-protocol.d.ts +14 -0
- package/dist/providers/claude/task-cancellation-protocol.js +21 -0
- package/dist/providers/copilot/activity-metadata.d.ts +3 -0
- package/dist/providers/copilot/activity-metadata.js +19 -0
- package/dist/providers/copilot/cli-client.js +5 -0
- package/dist/providers/provider-adapter.d.ts +8 -0
- package/dist/providers/provider-adapter.js +4 -0
- package/dist/types.d.ts +8 -0
- package/dist/vendor/claude-agent-acp/LICENSE +191 -0
- package/dist/vendor/claude-agent-acp/NOTICE +8 -0
- package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts +1017 -0
- package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/acp-agent.js +6305 -0
- package/dist/vendor/claude-agent-acp/dist/borgee-foreground-handoff-bridge.js +322 -0
- package/dist/vendor/claude-agent-acp/dist/borgee-task-cancellation-bridge.js +101 -0
- package/dist/vendor/claude-agent-acp/dist/borgee-task-lifecycle-bridge.js +58 -0
- package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts +130 -0
- package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/elicitation.js +304 -0
- package/dist/vendor/claude-agent-acp/dist/index.d.ts +3 -0
- package/dist/vendor/claude-agent-acp/dist/index.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/index.js +75 -0
- package/dist/vendor/claude-agent-acp/dist/lib.d.ts +6 -0
- package/dist/vendor/claude-agent-acp/dist/lib.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/lib.js +5 -0
- package/dist/vendor/claude-agent-acp/dist/settings.d.ts +68 -0
- package/dist/vendor/claude-agent-acp/dist/settings.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/settings.js +185 -0
- package/dist/vendor/claude-agent-acp/dist/tools.d.ts +102 -0
- package/dist/vendor/claude-agent-acp/dist/tools.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/tools.js +1000 -0
- package/dist/vendor/claude-agent-acp/dist/utils.d.ts +16 -0
- package/dist/vendor/claude-agent-acp/dist/utils.d.ts.map +1 -0
- package/dist/vendor/claude-agent-acp/dist/utils.js +81 -0
- package/dist/vendor/claude-agent-acp/package.json +85 -0
- package/package.json +14 -10
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
export const CLAUDE_FOREGROUND_HANDOFF_METHOD = '_borgee/session/foreground_handoff';
|
|
2
|
+
export const CLAUDE_FOREGROUND_LIFECYCLE_EVENT = 'settled_with_background';
|
|
3
|
+
|
|
4
|
+
const AUTONOMOUS_ORIGIN_KINDS = new Set([
|
|
5
|
+
'task-notification',
|
|
6
|
+
'peer',
|
|
7
|
+
'coordinator',
|
|
8
|
+
'observer',
|
|
9
|
+
'observer-activity',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
function isPositiveSafeInteger(value) {
|
|
13
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function activeForegroundTurn(session) {
|
|
17
|
+
const owner = session?.activeTurn?.borgeeForegroundTurn;
|
|
18
|
+
return isPositiveSafeInteger(owner) ? owner : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureOwnershipMaps(session) {
|
|
22
|
+
session.borgeeForegroundTurns ??= new Map();
|
|
23
|
+
session.borgeeTaskOwners ??= new Map();
|
|
24
|
+
session.borgeeToolOwners ??= new Map();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ownerForPromptUuid(session, uuid) {
|
|
28
|
+
if (typeof uuid !== 'string') {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return session.borgeeForegroundTurns?.get(uuid);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ownerForTool(session, toolUseId) {
|
|
35
|
+
if (typeof toolUseId !== 'string') {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return session.borgeeToolOwners?.get(toolUseId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ownerForTask(session, taskId) {
|
|
42
|
+
if (typeof taskId !== 'string') {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return session.borgeeTaskOwners?.get(taskId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerClaudeForegroundTurn(session, turn) {
|
|
49
|
+
ensureOwnershipMaps(session);
|
|
50
|
+
const next = isPositiveSafeInteger(session.borgeeNextForegroundTurn)
|
|
51
|
+
? session.borgeeNextForegroundTurn + 1
|
|
52
|
+
: 1;
|
|
53
|
+
session.borgeeNextForegroundTurn = next;
|
|
54
|
+
turn.borgeeForegroundTurn = next;
|
|
55
|
+
session.borgeeForegroundTurns.set(turn.promptUuid, next);
|
|
56
|
+
return next;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseClaudeForegroundHandoffRequest(value) {
|
|
60
|
+
if (
|
|
61
|
+
typeof value !== 'object'
|
|
62
|
+
|| value === null
|
|
63
|
+
|| Array.isArray(value)
|
|
64
|
+
|| typeof value.sessionId !== 'string'
|
|
65
|
+
|| value.sessionId.length === 0
|
|
66
|
+
) {
|
|
67
|
+
throw new Error('foreground handoff requires a non-empty sessionId');
|
|
68
|
+
}
|
|
69
|
+
return { sessionId: value.sessionId };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function admitClaudeForegroundHandoff(session) {
|
|
73
|
+
if (!session || session.queryClosed) {
|
|
74
|
+
return { outcome: 'not_found' };
|
|
75
|
+
}
|
|
76
|
+
const turn = session.activeTurn
|
|
77
|
+
?? session.turnQueue?.find((candidate) => !candidate.settled);
|
|
78
|
+
if (!turn || turn.settled) {
|
|
79
|
+
return { outcome: 'not_found' };
|
|
80
|
+
}
|
|
81
|
+
const foregroundTurn = isPositiveSafeInteger(turn.borgeeForegroundTurn)
|
|
82
|
+
? turn.borgeeForegroundTurn
|
|
83
|
+
: registerClaudeForegroundTurn(session, turn);
|
|
84
|
+
turn.borgeeForegroundHandoffRequested = true;
|
|
85
|
+
const released = turn.borgeeReleaseDeferredForeground?.() === true;
|
|
86
|
+
return {
|
|
87
|
+
outcome: released ? 'released' : 'admitted',
|
|
88
|
+
foregroundTurn,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function shouldDeferClaudeForegroundTurn(turn) {
|
|
93
|
+
return turn?.borgeeForegroundHandoffRequested !== true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function bindClaudeDeferredForegroundRelease(turn, release) {
|
|
97
|
+
turn.borgeeReleaseDeferredForeground = release;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function emitClaudeForegroundLifecycle(client, sessionId, turn, event = CLAUDE_FOREGROUND_LIFECYCLE_EVENT) {
|
|
101
|
+
const foregroundTurn = isPositiveSafeInteger(turn?.borgeeForegroundTurn)
|
|
102
|
+
? turn.borgeeForegroundTurn
|
|
103
|
+
: undefined;
|
|
104
|
+
return client.sessionUpdate({
|
|
105
|
+
sessionId,
|
|
106
|
+
update: {
|
|
107
|
+
sessionUpdate: 'session_info_update',
|
|
108
|
+
_meta: {
|
|
109
|
+
claudeCode: {
|
|
110
|
+
foregroundLifecycle: {
|
|
111
|
+
event,
|
|
112
|
+
...(foregroundTurn ? { foregroundTurn } : {}),
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function noteClaudeUpdateOwnership(session, message) {
|
|
121
|
+
ensureOwnershipMaps(session);
|
|
122
|
+
const messageUuids = [
|
|
123
|
+
...(message?.type === 'user' && typeof message.uuid === 'string'
|
|
124
|
+
? [message.uuid]
|
|
125
|
+
: []),
|
|
126
|
+
...(typeof message?.user_message_uuid === 'string'
|
|
127
|
+
? [message.user_message_uuid]
|
|
128
|
+
: []),
|
|
129
|
+
...(Array.isArray(message?.user_message_uuids)
|
|
130
|
+
? message.user_message_uuids.filter((value) => typeof value === 'string')
|
|
131
|
+
: []),
|
|
132
|
+
];
|
|
133
|
+
let owner = messageUuids
|
|
134
|
+
.map((uuid) => ownerForPromptUuid(session, uuid))
|
|
135
|
+
.find(isPositiveSafeInteger);
|
|
136
|
+
owner ??= ownerForTool(session, message?.parent_tool_use_id);
|
|
137
|
+
|
|
138
|
+
const subtype = message?.type === 'system' ? message.subtype : undefined;
|
|
139
|
+
if (subtype === 'task_started') {
|
|
140
|
+
owner ??= ownerForTool(session, message.tool_use_id);
|
|
141
|
+
owner ??= activeForegroundTurn(session);
|
|
142
|
+
owner ??= session.borgeeUpdateOwner;
|
|
143
|
+
if (isPositiveSafeInteger(owner)) {
|
|
144
|
+
session.borgeeTaskOwners.set(message.task_id, owner);
|
|
145
|
+
if (typeof message.tool_use_id === 'string') {
|
|
146
|
+
session.borgeeToolOwners.set(message.tool_use_id, owner);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else if (subtype === 'task_notification' || subtype === 'task_updated') {
|
|
150
|
+
owner ??= ownerForTask(session, message.task_id);
|
|
151
|
+
if (subtype === 'task_notification' && isPositiveSafeInteger(owner)) {
|
|
152
|
+
session.borgeeAutonomousOwner = owner;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const originKind = message?.origin?.kind;
|
|
157
|
+
owner ??= ownerForTask(session, message?.origin?.senderTaskId);
|
|
158
|
+
if (originKind === 'task-notification') {
|
|
159
|
+
owner ??= session.borgeeAutonomousOwner;
|
|
160
|
+
}
|
|
161
|
+
if (messageUuids.length > 0 || AUTONOMOUS_ORIGIN_KINDS.has(originKind)) {
|
|
162
|
+
owner ??= session.borgeeAutonomousOwner;
|
|
163
|
+
owner ??= session.borgeeUpdateOwner;
|
|
164
|
+
owner ??= activeForegroundTurn(session);
|
|
165
|
+
} else {
|
|
166
|
+
owner ??= session.borgeeUpdateOwner;
|
|
167
|
+
owner ??= activeForegroundTurn(session);
|
|
168
|
+
}
|
|
169
|
+
if (isPositiveSafeInteger(owner)) {
|
|
170
|
+
session.borgeeUpdateOwner = owner;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const activeOwner = activeForegroundTurn(session);
|
|
174
|
+
if (messageUuids.length > 0) {
|
|
175
|
+
session.borgeeForegroundLane = owner === activeOwner ? 'foreground' : 'background';
|
|
176
|
+
if (owner === activeOwner) {
|
|
177
|
+
session.borgeeAutonomousOwner = undefined;
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (typeof originKind === 'string' && AUTONOMOUS_ORIGIN_KINDS.has(originKind)) {
|
|
182
|
+
session.borgeeForegroundLane = 'background';
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (
|
|
186
|
+
typeof message?.parent_tool_use_id === 'string'
|
|
187
|
+
|| (isPositiveSafeInteger(owner) && isPositiveSafeInteger(activeOwner) && owner !== activeOwner)
|
|
188
|
+
) {
|
|
189
|
+
session.borgeeForegroundLane = 'background';
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (message?.type === 'user') {
|
|
193
|
+
session.borgeeForegroundLane =
|
|
194
|
+
originKind === undefined || originKind === 'human' || originKind === 'channel'
|
|
195
|
+
? 'foreground'
|
|
196
|
+
: 'background';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function ownerForNotification(session, notification, ownerHint) {
|
|
201
|
+
if (isPositiveSafeInteger(ownerHint)) {
|
|
202
|
+
return ownerHint;
|
|
203
|
+
}
|
|
204
|
+
const update = notification?.update;
|
|
205
|
+
const provider = update?._meta?.claudeCode;
|
|
206
|
+
const explicitOwner = provider?.foregroundTurn;
|
|
207
|
+
if (isPositiveSafeInteger(explicitOwner)) {
|
|
208
|
+
return explicitOwner;
|
|
209
|
+
}
|
|
210
|
+
const taskId = provider?.taskLifecycle?.taskId;
|
|
211
|
+
return ownerForTool(session, update?.toolCallId)
|
|
212
|
+
?? ownerForTool(session, provider?.parentToolUseId)
|
|
213
|
+
?? ownerForTask(session, taskId)
|
|
214
|
+
?? session?.borgeeUpdateOwner
|
|
215
|
+
?? activeForegroundTurn(session);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function stampClaudeUpdateOwnership(session, notification, ownerHint) {
|
|
219
|
+
if (!session) {
|
|
220
|
+
return notification;
|
|
221
|
+
}
|
|
222
|
+
ensureOwnershipMaps(session);
|
|
223
|
+
const owner = ownerForNotification(session, notification, ownerHint);
|
|
224
|
+
const lane = session.borgeeForegroundLane;
|
|
225
|
+
if (isPositiveSafeInteger(owner) && typeof notification?.update?.toolCallId === 'string') {
|
|
226
|
+
session.borgeeToolOwners.set(notification.update.toolCallId, owner);
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
!isPositiveSafeInteger(owner)
|
|
230
|
+
&& lane !== 'foreground'
|
|
231
|
+
&& lane !== 'background'
|
|
232
|
+
) {
|
|
233
|
+
return notification;
|
|
234
|
+
}
|
|
235
|
+
const activeOwner = activeForegroundTurn(session);
|
|
236
|
+
const resolvedLane =
|
|
237
|
+
isPositiveSafeInteger(owner)
|
|
238
|
+
&& isPositiveSafeInteger(activeOwner)
|
|
239
|
+
&& owner !== activeOwner
|
|
240
|
+
? 'background'
|
|
241
|
+
: lane;
|
|
242
|
+
return {
|
|
243
|
+
...notification,
|
|
244
|
+
update: {
|
|
245
|
+
...notification.update,
|
|
246
|
+
_meta: {
|
|
247
|
+
...notification.update._meta,
|
|
248
|
+
claudeCode: {
|
|
249
|
+
...notification.update._meta?.claudeCode,
|
|
250
|
+
...(resolvedLane === 'foreground' || resolvedLane === 'background'
|
|
251
|
+
? { foregroundLane: resolvedLane }
|
|
252
|
+
: {}),
|
|
253
|
+
...(isPositiveSafeInteger(owner) ? { foregroundTurn: owner } : {}),
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function noteClaudePermissionOwnership(session, toolUseId, agentId) {
|
|
261
|
+
if (!session || typeof toolUseId !== 'string') {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
ensureOwnershipMaps(session);
|
|
265
|
+
const owner = ownerForTool(session, toolUseId)
|
|
266
|
+
?? ownerForTask(session, agentId)
|
|
267
|
+
?? activeForegroundTurn(session)
|
|
268
|
+
?? session.borgeeUpdateOwner;
|
|
269
|
+
if (isPositiveSafeInteger(owner)) {
|
|
270
|
+
session.borgeeToolOwners.set(toolUseId, owner);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function stampClaudePermissionOwnership(session, params) {
|
|
275
|
+
if (!session || typeof params?.toolCall?.toolCallId !== 'string') {
|
|
276
|
+
return params;
|
|
277
|
+
}
|
|
278
|
+
ensureOwnershipMaps(session);
|
|
279
|
+
const provider = params.toolCall._meta?.claudeCode;
|
|
280
|
+
const owner = ownerForTool(session, params.toolCall.toolCallId)
|
|
281
|
+
?? ownerForTool(session, provider?.parentToolUseId)
|
|
282
|
+
?? activeForegroundTurn(session)
|
|
283
|
+
?? session.borgeeUpdateOwner;
|
|
284
|
+
if (!isPositiveSafeInteger(owner)) {
|
|
285
|
+
return params;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
...params,
|
|
289
|
+
toolCall: {
|
|
290
|
+
...params.toolCall,
|
|
291
|
+
_meta: {
|
|
292
|
+
...params.toolCall._meta,
|
|
293
|
+
claudeCode: {
|
|
294
|
+
...provider,
|
|
295
|
+
foregroundTurn: owner,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function sendClaudeHookUpdate(client, session, hookInput, notification) {
|
|
303
|
+
const owner = ownerForTask(session, hookInput?.agent_id)
|
|
304
|
+
?? ownerForPromptUuid(session, hookInput?.prompt_id)
|
|
305
|
+
?? session?.borgeeUpdateOwner
|
|
306
|
+
?? activeForegroundTurn(session);
|
|
307
|
+
return client.sessionUpdate(stampClaudeUpdateOwnership(session, notification, owner));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const wrappedClients = new WeakSet();
|
|
311
|
+
|
|
312
|
+
export function bindClaudeSessionUpdateOwnership(client, resolveSession) {
|
|
313
|
+
if (wrappedClients.has(client)) {
|
|
314
|
+
return client;
|
|
315
|
+
}
|
|
316
|
+
const sessionUpdate = client.sessionUpdate.bind(client);
|
|
317
|
+
client.sessionUpdate = (notification) => sessionUpdate(
|
|
318
|
+
stampClaudeUpdateOwnership(resolveSession(notification?.sessionId), notification),
|
|
319
|
+
);
|
|
320
|
+
wrappedClients.add(client);
|
|
321
|
+
return client;
|
|
322
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export const CLAUDE_TASK_CANCELLATION_METHOD = '_borgee/claude/task/cancel/v1';
|
|
2
|
+
export const CLAUDE_TASK_CANCELLATION_VERSION = 1;
|
|
3
|
+
export const CLAUDE_TASK_CANCELLATION_CAPABILITY = Object.freeze({
|
|
4
|
+
method: CLAUDE_TASK_CANCELLATION_METHOD,
|
|
5
|
+
version: CLAUDE_TASK_CANCELLATION_VERSION,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const taskStates = new WeakMap();
|
|
9
|
+
|
|
10
|
+
function stateFor(session) {
|
|
11
|
+
let state = taskStates.get(session);
|
|
12
|
+
if (!state) {
|
|
13
|
+
state = {
|
|
14
|
+
backgroundTaskIds: new Set(),
|
|
15
|
+
stopRequests: new Map(),
|
|
16
|
+
};
|
|
17
|
+
taskStates.set(session, state);
|
|
18
|
+
}
|
|
19
|
+
return state;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function removeTask(state, taskId) {
|
|
23
|
+
state.backgroundTaskIds.delete(taskId);
|
|
24
|
+
state.stopRequests.delete(taskId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function trackClaudeTaskLifecycle(session, message) {
|
|
28
|
+
const state = stateFor(session);
|
|
29
|
+
switch (message.subtype) {
|
|
30
|
+
case 'background_tasks_changed': {
|
|
31
|
+
const taskIds = new Set(message.tasks.map((task) => task.task_id));
|
|
32
|
+
state.backgroundTaskIds = taskIds;
|
|
33
|
+
for (const taskId of state.stopRequests.keys()) {
|
|
34
|
+
if (!taskIds.has(taskId)) {
|
|
35
|
+
state.stopRequests.delete(taskId);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case 'task_updated':
|
|
41
|
+
if (
|
|
42
|
+
message.patch.status === 'completed'
|
|
43
|
+
|| message.patch.status === 'failed'
|
|
44
|
+
|| message.patch.status === 'killed'
|
|
45
|
+
|| message.patch.is_backgrounded === false
|
|
46
|
+
) {
|
|
47
|
+
removeTask(state, message.task_id);
|
|
48
|
+
} else if (message.patch.is_backgrounded === true) {
|
|
49
|
+
state.backgroundTaskIds.add(message.task_id);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case 'task_notification':
|
|
53
|
+
removeTask(state, message.task_id);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseClaudeTaskCancellationRequest(params) {
|
|
59
|
+
if (!params || typeof params !== 'object') {
|
|
60
|
+
throw new TypeError('Claude task cancellation params must be an object');
|
|
61
|
+
}
|
|
62
|
+
const { sessionId, taskId } = params;
|
|
63
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) {
|
|
64
|
+
throw new TypeError('Claude task cancellation requires a non-empty sessionId');
|
|
65
|
+
}
|
|
66
|
+
if (typeof taskId !== 'string' || taskId.length === 0) {
|
|
67
|
+
throw new TypeError('Claude task cancellation requires a non-empty taskId');
|
|
68
|
+
}
|
|
69
|
+
return { sessionId, taskId };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function stopClaudeBackgroundTask(session, taskId) {
|
|
73
|
+
if (!session || session.queryClosed) {
|
|
74
|
+
return { outcome: 'not_found' };
|
|
75
|
+
}
|
|
76
|
+
const state = stateFor(session);
|
|
77
|
+
if (!state.backgroundTaskIds.has(taskId)) {
|
|
78
|
+
return { outcome: 'not_found' };
|
|
79
|
+
}
|
|
80
|
+
if (typeof session.query?.stopTask !== 'function') {
|
|
81
|
+
return { outcome: 'unsupported' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const existing = state.stopRequests.get(taskId);
|
|
85
|
+
if (existing) {
|
|
86
|
+
return existing;
|
|
87
|
+
}
|
|
88
|
+
const request = (async () => {
|
|
89
|
+
await session.query.stopTask(taskId);
|
|
90
|
+
return { outcome: 'accepted' };
|
|
91
|
+
})();
|
|
92
|
+
state.stopRequests.set(taskId, request);
|
|
93
|
+
try {
|
|
94
|
+
return await request;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (state.stopRequests.get(taskId) === request) {
|
|
97
|
+
state.stopRequests.delete(taskId);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
function lifecycleForMessage(message) {
|
|
2
|
+
switch (message.subtype) {
|
|
3
|
+
case 'task_started':
|
|
4
|
+
return {
|
|
5
|
+
event: 'started',
|
|
6
|
+
taskId: message.task_id,
|
|
7
|
+
...(message.tool_use_id ? { toolUseId: message.tool_use_id } : {}),
|
|
8
|
+
description: message.description,
|
|
9
|
+
...(message.subagent_type ? { subagentType: message.subagent_type } : {}),
|
|
10
|
+
...(message.task_type ? { taskType: message.task_type } : {}),
|
|
11
|
+
};
|
|
12
|
+
case 'task_notification':
|
|
13
|
+
return {
|
|
14
|
+
event: 'notification',
|
|
15
|
+
taskId: message.task_id,
|
|
16
|
+
...(message.tool_use_id ? { toolUseId: message.tool_use_id } : {}),
|
|
17
|
+
status: message.status,
|
|
18
|
+
summary: message.summary,
|
|
19
|
+
...(message.usage ? { usage: message.usage } : {}),
|
|
20
|
+
};
|
|
21
|
+
case 'task_updated':
|
|
22
|
+
return {
|
|
23
|
+
event: 'updated',
|
|
24
|
+
taskId: message.task_id,
|
|
25
|
+
patch: message.patch,
|
|
26
|
+
};
|
|
27
|
+
case 'background_tasks_changed':
|
|
28
|
+
return {
|
|
29
|
+
event: 'background_tasks_changed',
|
|
30
|
+
tasks: message.tasks.map((task) => ({
|
|
31
|
+
taskId: task.task_id,
|
|
32
|
+
taskType: task.task_type,
|
|
33
|
+
description: task.description,
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
default:
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function emitClaudeTaskLifecycle(sendUpdate, sessionId, message) {
|
|
42
|
+
const taskLifecycle = lifecycleForMessage(message);
|
|
43
|
+
if (!taskLifecycle) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
await sendUpdate({
|
|
47
|
+
sessionId,
|
|
48
|
+
update: {
|
|
49
|
+
sessionUpdate: 'session_info_update',
|
|
50
|
+
_meta: {
|
|
51
|
+
claudeCode: {
|
|
52
|
+
taskLifecycle,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
|
|
2
|
+
import type { CreateElicitationRequest } from "@agentclientprotocol/sdk";
|
|
3
|
+
import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
|
|
4
|
+
import type { AskUserQuestionInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
|
|
5
|
+
/**
|
|
6
|
+
* Bridges between the Claude Agent SDK's elicitation/dialog callbacks and ACP's
|
|
7
|
+
* (unstable) elicitation protocol.
|
|
8
|
+
*
|
|
9
|
+
* Two distinct SDK surfaces flow through here:
|
|
10
|
+
*
|
|
11
|
+
* 1. `onElicitation` — fired when an MCP server requests user input. These map
|
|
12
|
+
* directly onto ACP `session/create_elicitation` (form or url mode).
|
|
13
|
+
* 2. The built-in AskUserQuestion tool — when a `canUseTool` callback is
|
|
14
|
+
* registered the SDK routes its permission check through `canUseTool`
|
|
15
|
+
* (not the interactive `permission_ask_user_question` dialog). We render
|
|
16
|
+
* its questions as an ACP form elicitation and feed the user's selections
|
|
17
|
+
* back as the tool's `updatedInput`, which the tool's own `call()` reads.
|
|
18
|
+
*/
|
|
19
|
+
/** Modes the connected client advertised support for. */
|
|
20
|
+
export type ElicitationSupport = {
|
|
21
|
+
form: boolean;
|
|
22
|
+
url: boolean;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Convert an MCP elicitation request (from the SDK's `onElicitation` callback)
|
|
26
|
+
* into an ACP `CreateElicitationRequest`. Returns `null` when the request can't
|
|
27
|
+
* be represented (e.g. a url-mode request with no url).
|
|
28
|
+
*/
|
|
29
|
+
export declare function mcpElicitationToCreateRequest(request: ElicitationRequest, sessionId: string): CreateElicitationRequest | null;
|
|
30
|
+
/**
|
|
31
|
+
* Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
|
|
32
|
+
* to hand back to the requesting server.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createElicitationResponseToElicitResult(response: CreateElicitationResponse): ElicitationResult;
|
|
35
|
+
/**
|
|
36
|
+
* A single question as supplied by the AskUserQuestion tool. Derived from the
|
|
37
|
+
* SDK's input type so the shape stays in sync; the SDK validates the model's
|
|
38
|
+
* tool call against this schema before it reaches us.
|
|
39
|
+
*/
|
|
40
|
+
export type AskUserQuestion = AskUserQuestionInput["questions"][number];
|
|
41
|
+
/**
|
|
42
|
+
* Pull the well-formed questions out of an AskUserQuestion tool input. Returns
|
|
43
|
+
* `null` when there are no usable questions — including the case where every
|
|
44
|
+
* entry is malformed and filtering leaves an empty list — so callers can treat
|
|
45
|
+
* "nothing to ask" uniformly.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractAskUserQuestions(input: Record<string, unknown>): AskUserQuestion[] | null;
|
|
48
|
+
/**
|
|
49
|
+
* Render the AskUserQuestion tool's questions as an ACP form elicitation.
|
|
50
|
+
*
|
|
51
|
+
* Fields are keyed by a short stable id (`question_<n>`) rather than the full
|
|
52
|
+
* question text, so the question text appears in exactly one place per field.
|
|
53
|
+
* Single-select questions use a titled `oneOf` enum; multi-select questions use
|
|
54
|
+
* an array with a titled `anyOf` item enum. The enum `const` is always the
|
|
55
|
+
* option label, since that is what the tool records as the answer; an option's
|
|
56
|
+
* secondary text travels in the enum option's own `description` field.
|
|
57
|
+
*
|
|
58
|
+
* Each question is followed by its own optional free-text "custom answer" field
|
|
59
|
+
* (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
|
|
60
|
+
* user can type their own answer instead of picking an option, scoped to that
|
|
61
|
+
* specific question. Nothing is marked required, so the user can also just skip
|
|
62
|
+
* — matching the built-in tool, which always offers Skip + a free-text box.
|
|
63
|
+
*/
|
|
64
|
+
export declare function askUserQuestionsToCreateRequest(questions: AskUserQuestion[], sessionId: string, toolCallId: string | undefined): CreateElicitationRequest;
|
|
65
|
+
/** Outcome of an AskUserQuestion elicitation, decoupled from any transport. */
|
|
66
|
+
export type AskUserQuestionOutcome = {
|
|
67
|
+
action: "answered";
|
|
68
|
+
updatedInput: Record<string, unknown>;
|
|
69
|
+
} | {
|
|
70
|
+
action: "cancel";
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Fold an ACP elicitation response into the AskUserQuestion tool's input.
|
|
74
|
+
*
|
|
75
|
+
* Selected labels are read back from the indexed form fields and written into
|
|
76
|
+
* `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
|
|
77
|
+
* — the key shape the tool's own `call()` reads. A non-empty per-question
|
|
78
|
+
* custom-answer field (`question_<n>_custom`) takes precedence over that
|
|
79
|
+
* question's selection, since the user typed their own answer instead of
|
|
80
|
+
* picking one. Decline yields empty answers (the model is told the user skipped
|
|
81
|
+
* rather than the turn aborting); cancel — and any custom/future action we
|
|
82
|
+
* don't understand — aborts the tool call.
|
|
83
|
+
*/
|
|
84
|
+
export declare function applyAskElicitationResponse(response: CreateElicitationResponse, toolInput: Record<string, unknown>, questions: AskUserQuestion[]): AskUserQuestionOutcome;
|
|
85
|
+
/**
|
|
86
|
+
* The `request_user_dialog` kind the CLI emits when a model refusal has a
|
|
87
|
+
* fallback available but needs user consent before retrying (e.g. Claude Fable
|
|
88
|
+
* declining a request with Opus available as the fallback). Declaring this
|
|
89
|
+
* kind in `supportedDialogKinds` is the opt-in: the CLI fails closed and never
|
|
90
|
+
* emits an undeclared kind — the flow degrades to the classic refusal error
|
|
91
|
+
* ending the turn.
|
|
92
|
+
*/
|
|
93
|
+
export declare const REFUSAL_FALLBACK_DIALOG_KIND = "refusal_fallback_prompt";
|
|
94
|
+
/**
|
|
95
|
+
* Payload of the `refusal_fallback_prompt` dialog. The dialog protocol
|
|
96
|
+
* transports payloads opaquely, so this shape is recovered from the CLI's own
|
|
97
|
+
* schema (v2.1.177): `originalModel`/`fallbackModel` are required strings;
|
|
98
|
+
* `apiRefusalCategory` (nullable), `guidanceText`, and
|
|
99
|
+
* `retractedMessageUuids` are optional. We ignore `retractedMessageUuids` —
|
|
100
|
+
* ACP has no way to retract already-streamed chunks.
|
|
101
|
+
*/
|
|
102
|
+
export type RefusalFallbackPrompt = {
|
|
103
|
+
originalModel: string;
|
|
104
|
+
fallbackModel: string;
|
|
105
|
+
apiRefusalCategory: string | null;
|
|
106
|
+
guidanceText?: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Validate the opaque dialog payload into a {@link RefusalFallbackPrompt}.
|
|
110
|
+
* Returns `null` when the required fields are missing or mistyped (a newer CLI
|
|
111
|
+
* may reshape the payload), so the caller can cancel the dialog and let the
|
|
112
|
+
* CLI apply its default behavior instead of rendering something misleading.
|
|
113
|
+
*/
|
|
114
|
+
export declare function extractRefusalFallbackPrompt(payload: Record<string, unknown>): RefusalFallbackPrompt | null;
|
|
115
|
+
/**
|
|
116
|
+
* Render the refusal-fallback consent prompt as an ACP form elicitation: a
|
|
117
|
+
* single-select between retrying on the fallback model and keeping the
|
|
118
|
+
* refusal. The enum `const`s are the dialog's wire result values, so the
|
|
119
|
+
* response maps back without a translation table.
|
|
120
|
+
*/
|
|
121
|
+
export declare function refusalFallbackToCreateRequest(prompt: RefusalFallbackPrompt, sessionId: string): CreateElicitationRequest;
|
|
122
|
+
/**
|
|
123
|
+
* Map the elicitation response back to the dialog's result enum. Only an
|
|
124
|
+
* explicit accept-with-retry resolves to `retry_fallback`; decline, cancel, a
|
|
125
|
+
* skipped field, or an unrecognized value all keep the refusal — the dialog's
|
|
126
|
+
* own default — so a dismissed or half-filled form can never trigger a model
|
|
127
|
+
* switch the user didn't ask for.
|
|
128
|
+
*/
|
|
129
|
+
export declare function refusalFallbackResultFromResponse(response: CreateElicitationResponse): string;
|
|
130
|
+
//# sourceMappingURL=elicitation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"elicitation.d.ts","sourceRoot":"","sources":["../src/elicitation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EACV,wBAAwB,EAKzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,6CAA6C,CAAC;AAErD;;;;;;;;;;;;;GAaG;AAEH,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAyBjC;AAgBD;;;GAGG;AACH,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,yBAAyB,GAClC,iBAAiB,CAUnB;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAUhG;AAgCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,eAAe,EAAE,EAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CA2D1B;AAED,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAChC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvF;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,eAAe,EAAE,GAC3B,sBAAsB,CAkCxB;AAgBD;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,4BAA4B,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,qBAAqB,GAAG,IAAI,CAW9B;AAYD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,qBAAqB,EAC7B,SAAS,EAAE,MAAM,GAChB,wBAAwB,CA+B1B;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,CAG7F"}
|