@borgee/agents-host 0.2.74 → 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 +11 -24
- package/dist/agents-host.d.ts +6 -4
- package/dist/agents-host.js +127 -16
- package/dist/background-runs.d.ts +24 -0
- package/dist/background-runs.js +184 -0
- package/dist/chat/chat-control-plane.d.ts +7 -1
- package/dist/chat/sdk-chat-control-plane.d.ts +7 -2
- package/dist/chat/sdk-chat-control-plane.js +22 -0
- package/dist/context/claude-file-brief.js +3 -3
- package/dist/context/injection.d.ts +18 -12
- package/dist/context/injection.js +53 -53
- package/dist/context/prompt.js +10 -10
- package/dist/context/resolved-working-folder.d.ts +3 -0
- package/dist/context/resolved-working-folder.js +106 -0
- package/dist/context/turn-preparation.js +1 -1
- package/dist/managed-daemon.js +30 -30
- package/dist/plugin-sdk.js +315 -29
- package/dist/plugin-sdk.js.map +3 -3
- package/dist/policy/authorization-audit.d.ts +1 -1
- package/dist/policy/copilot-permission.d.ts +9 -0
- package/dist/policy/copilot-permission.js +120 -1
- package/dist/progress-to-activity.d.ts +1 -1
- package/dist/progress-to-activity.js +1 -0
- package/dist/providers/claude/adapter.d.ts +2 -1
- package/dist/providers/claude/adapter.js +36 -1
- 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 +453 -72
- 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/codex/adapter.d.ts +1 -1
- package/dist/providers/codex/adapter.js +7 -0
- package/dist/providers/codex/cli-client.js +8 -7
- package/dist/providers/codex/project-doc.js +6 -6
- package/dist/providers/copilot/activity-metadata.d.ts +3 -0
- package/dist/providers/copilot/activity-metadata.js +19 -0
- package/dist/providers/copilot/adapter.d.ts +6 -2
- package/dist/providers/copilot/adapter.js +27 -1
- package/dist/providers/copilot/cli-client.d.ts +37 -10
- package/dist/providers/copilot/cli-client.js +756 -122
- package/dist/providers/copilot/sdk-session.d.ts +149 -0
- package/dist/providers/copilot/sdk-session.js +981 -0
- package/dist/providers/create-provider.js +33 -0
- package/dist/providers/provider-adapter.d.ts +24 -1
- package/dist/providers/provider-adapter.js +17 -0
- package/dist/types.d.ts +40 -11
- 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 +15 -10
- package/skills/borgee-agent/references/task-properties.md +3 -3
- package/skills/borgee-agent/scripts/borgee-agent.mjs +1 -1
- package/skills/borgee-agent/scripts/borgee-agent.py +1 -1
- package/dist/context/resolved-workspace.d.ts +0 -3
- package/dist/context/resolved-workspace.js +0 -106
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
import { accessSync, constants as fsConstants, existsSync, statSync } from 'node:fs';
|
|
2
|
+
import { delimiter, extname, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { CopilotClient, RuntimeConnection, } from '@github/copilot-sdk';
|
|
4
|
+
function isExecutableFile(path) {
|
|
5
|
+
if (!existsSync(path) || !statSync(path).isFile()) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (process.platform === 'win32') {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
accessSync(path, fsConstants.X_OK);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function executableCandidates(command) {
|
|
20
|
+
if (process.platform !== 'win32' || extname(command)) {
|
|
21
|
+
return [command];
|
|
22
|
+
}
|
|
23
|
+
const pathExt = process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD';
|
|
24
|
+
return pathExt
|
|
25
|
+
.split(';')
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.map((extension) => `${command}${extension.toLowerCase()}`);
|
|
28
|
+
}
|
|
29
|
+
export function resolveCopilotCommandPath(command, cwd = process.cwd(), env = process.env) {
|
|
30
|
+
const trimmed = command.trim();
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
throw new Error('Copilot SDK command must not be empty');
|
|
33
|
+
}
|
|
34
|
+
if (isAbsolute(trimmed) || trimmed.includes('/') || trimmed.includes('\\')) {
|
|
35
|
+
const commandPath = isAbsolute(trimmed) ? trimmed : resolve(cwd, trimmed);
|
|
36
|
+
for (const candidate of executableCandidates(commandPath)) {
|
|
37
|
+
if (isExecutableFile(candidate)) {
|
|
38
|
+
return candidate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Copilot SDK command was not found: ${commandPath}`);
|
|
42
|
+
}
|
|
43
|
+
for (const rawDirectory of (env.PATH ?? '').split(delimiter)) {
|
|
44
|
+
const directory = rawDirectory.replace(/^"(.*)"$/u, '$1').trim();
|
|
45
|
+
if (!directory) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const candidate of executableCandidates(resolve(directory, trimmed))) {
|
|
49
|
+
if (isExecutableFile(candidate)) {
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Copilot SDK command was not found on PATH: ${trimmed}`);
|
|
55
|
+
}
|
|
56
|
+
export function createCopilotSdkRuntimeConnection(commandPath) {
|
|
57
|
+
if (process.platform === 'win32' && /\.(?:cmd|bat)$/iu.test(commandPath)) {
|
|
58
|
+
const commandProcessor = process.env.ComSpec ?? process.env.COMSPEC;
|
|
59
|
+
if (!commandProcessor) {
|
|
60
|
+
throw new Error('Copilot SDK requires ComSpec to launch a Windows command shim');
|
|
61
|
+
}
|
|
62
|
+
return RuntimeConnection.forStdio({
|
|
63
|
+
path: commandProcessor,
|
|
64
|
+
args: ['/d', '/s', '/c', 'call', commandPath],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return RuntimeConnection.forStdio({ path: commandPath });
|
|
68
|
+
}
|
|
69
|
+
export const createCopilotSdkClient = (command, cwd, debug) => new CopilotClient({
|
|
70
|
+
connection: createCopilotSdkRuntimeConnection(resolveCopilotCommandPath(command, cwd)),
|
|
71
|
+
workingDirectory: cwd,
|
|
72
|
+
logLevel: debug ? 'debug' : 'none',
|
|
73
|
+
});
|
|
74
|
+
export function createCopilotSdkSessionConfig(cwd, onPermissionRequest) {
|
|
75
|
+
return {
|
|
76
|
+
workingDirectory: cwd,
|
|
77
|
+
streaming: true,
|
|
78
|
+
includeSubAgentStreamingEvents: false,
|
|
79
|
+
onPermissionRequest,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const BACKGROUND_COMPLETION_SIGNAL_TTL_MS = 30_000;
|
|
83
|
+
const MAX_BACKGROUND_COMPLETION_SIGNALS = 32;
|
|
84
|
+
function normalizeError(error) {
|
|
85
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
86
|
+
}
|
|
87
|
+
function toolKindForName(toolName) {
|
|
88
|
+
const normalized = toolName.toLowerCase();
|
|
89
|
+
if (normalized.includes('read') || normalized.includes('view')) {
|
|
90
|
+
return 'read';
|
|
91
|
+
}
|
|
92
|
+
if (normalized.includes('edit') ||
|
|
93
|
+
normalized.includes('write') ||
|
|
94
|
+
normalized.includes('create') ||
|
|
95
|
+
normalized.includes('patch')) {
|
|
96
|
+
return 'edit';
|
|
97
|
+
}
|
|
98
|
+
if (normalized.includes('search') || normalized.includes('grep') || normalized.includes('glob')) {
|
|
99
|
+
return 'search';
|
|
100
|
+
}
|
|
101
|
+
if (normalized.includes('shell') ||
|
|
102
|
+
normalized.includes('bash') ||
|
|
103
|
+
normalized.includes('powershell') ||
|
|
104
|
+
normalized.includes('terminal') ||
|
|
105
|
+
normalized.includes('execute') ||
|
|
106
|
+
normalized.includes('run')) {
|
|
107
|
+
return 'execute';
|
|
108
|
+
}
|
|
109
|
+
if (normalized.includes('fetch') || normalized.includes('http') || normalized.includes('web')) {
|
|
110
|
+
return 'fetch';
|
|
111
|
+
}
|
|
112
|
+
return 'other';
|
|
113
|
+
}
|
|
114
|
+
function toMessageOptions(input) {
|
|
115
|
+
if (typeof input === 'string') {
|
|
116
|
+
return { prompt: input };
|
|
117
|
+
}
|
|
118
|
+
const text = input
|
|
119
|
+
.filter((block) => block.type === 'text')
|
|
120
|
+
.map((block) => block.text)
|
|
121
|
+
.join('\n');
|
|
122
|
+
const attachments = input.flatMap((block) => {
|
|
123
|
+
if (block.type !== 'image') {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
return [
|
|
127
|
+
{
|
|
128
|
+
type: 'blob',
|
|
129
|
+
data: block.data,
|
|
130
|
+
mimeType: block.mimeType,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
prompt: text,
|
|
136
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function sessionUpdate(update) {
|
|
140
|
+
return {
|
|
141
|
+
kind: 'session_update',
|
|
142
|
+
update,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function stopUpdate(stopReason) {
|
|
146
|
+
return {
|
|
147
|
+
kind: 'stop',
|
|
148
|
+
stopReason,
|
|
149
|
+
response: { stopReason },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function parseTimestamp(value) {
|
|
153
|
+
if (!value) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
const timestamp = Date.parse(value);
|
|
157
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
158
|
+
}
|
|
159
|
+
function isCopilotExecutionTerminal(status, waiting) {
|
|
160
|
+
return (!waiting &&
|
|
161
|
+
(status === 'idle' || status === 'completed' || status === 'failed' || status === 'cancelled'));
|
|
162
|
+
}
|
|
163
|
+
export function toCopilotBackgroundRunProgress(task, explicitWait, executionStartedAt) {
|
|
164
|
+
if (task.type !== 'agent' || task.executionMode !== 'background') {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
const waiting = explicitWait && (task.status === 'running' || task.status === 'idle');
|
|
168
|
+
const state = waiting ? 'waiting' : task.status === 'idle' ? 'completed' : task.status;
|
|
169
|
+
const startedAt = executionStartedAt ?? parseTimestamp(task.activeStartedAt) ?? parseTimestamp(task.startedAt);
|
|
170
|
+
const finishedAt = state === 'running' || state === 'waiting'
|
|
171
|
+
? undefined
|
|
172
|
+
: state === 'completed' && task.status === 'idle'
|
|
173
|
+
? parseTimestamp(task.idleSince)
|
|
174
|
+
: parseTimestamp(task.completedAt);
|
|
175
|
+
const reason = (waiting ? explicitWait.reason : undefined) ||
|
|
176
|
+
task.error?.trim() ||
|
|
177
|
+
(task.status === 'cancelled' ? 'Cancelled' : undefined);
|
|
178
|
+
return {
|
|
179
|
+
providerRunId: task.id,
|
|
180
|
+
label: task.description?.trim() || 'Background agent',
|
|
181
|
+
state,
|
|
182
|
+
...(startedAt !== undefined ? { startedAt } : {}),
|
|
183
|
+
...(finishedAt !== undefined ? { finishedAt } : {}),
|
|
184
|
+
...(reason ? { reason } : {}),
|
|
185
|
+
stopSupported: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export class CopilotSdkSessionAdapter {
|
|
189
|
+
session;
|
|
190
|
+
onBackgroundRun;
|
|
191
|
+
onBackgroundAgentPresenceChanged;
|
|
192
|
+
onBackgroundRunError;
|
|
193
|
+
onAutonomousReply;
|
|
194
|
+
onAutonomousTurnPresenceChanged;
|
|
195
|
+
onBackgroundTaskExecutionsChanged;
|
|
196
|
+
transport = 'sdk';
|
|
197
|
+
pendingUpdates = [];
|
|
198
|
+
updateWaiters = [];
|
|
199
|
+
unsubscribe;
|
|
200
|
+
activeTurn;
|
|
201
|
+
currentRootTurnId;
|
|
202
|
+
lastRootTurnId;
|
|
203
|
+
backgroundAgentsActive = false;
|
|
204
|
+
backgroundTaskRefresh = Promise.resolve();
|
|
205
|
+
backgroundTaskRefreshFailures = 0;
|
|
206
|
+
backgroundTaskRefreshRetry;
|
|
207
|
+
reportedBackgroundTasks = new Map();
|
|
208
|
+
autonomousTurns = new Map();
|
|
209
|
+
autonomousTurnIdByRuntimeTurnId = new Map();
|
|
210
|
+
autonomousTurnIdByInteractionId = new Map();
|
|
211
|
+
backgroundCompletionSignals = [];
|
|
212
|
+
backgroundCompletionSignalExpiry;
|
|
213
|
+
autonomousTurnPending = false;
|
|
214
|
+
backgroundTaskExecutions;
|
|
215
|
+
backgroundTaskExecutionsDirty = false;
|
|
216
|
+
backgroundWaits = new Map();
|
|
217
|
+
lastSyntheticExecutionStartedAt = 0;
|
|
218
|
+
closePromise;
|
|
219
|
+
constructor(session, onBackgroundRun, onBackgroundAgentPresenceChanged, onBackgroundRunError, onAutonomousReply, onAutonomousTurnPresenceChanged, initialBackgroundTaskExecutions = {}, onBackgroundTaskExecutionsChanged = async () => { }) {
|
|
220
|
+
this.session = session;
|
|
221
|
+
this.onBackgroundRun = onBackgroundRun;
|
|
222
|
+
this.onBackgroundAgentPresenceChanged = onBackgroundAgentPresenceChanged;
|
|
223
|
+
this.onBackgroundRunError = onBackgroundRunError;
|
|
224
|
+
this.onAutonomousReply = onAutonomousReply;
|
|
225
|
+
this.onAutonomousTurnPresenceChanged = onAutonomousTurnPresenceChanged;
|
|
226
|
+
this.onBackgroundTaskExecutionsChanged = onBackgroundTaskExecutionsChanged;
|
|
227
|
+
this.backgroundTaskExecutions = new Map(Object.entries(initialBackgroundTaskExecutions).map(([taskId, execution]) => [
|
|
228
|
+
taskId,
|
|
229
|
+
{ ...execution },
|
|
230
|
+
]));
|
|
231
|
+
this.unsubscribe = session.on((event) => this.handleEvent(event));
|
|
232
|
+
}
|
|
233
|
+
get sessionId() {
|
|
234
|
+
return this.session.sessionId;
|
|
235
|
+
}
|
|
236
|
+
async prompt(input) {
|
|
237
|
+
if (this.closePromise) {
|
|
238
|
+
throw new Error('Copilot SDK session is closed');
|
|
239
|
+
}
|
|
240
|
+
if (this.activeTurn) {
|
|
241
|
+
throw new Error('Copilot SDK session already has an active foreground turn');
|
|
242
|
+
}
|
|
243
|
+
let resolveTurn;
|
|
244
|
+
let rejectTurn;
|
|
245
|
+
const completion = new Promise((resolvePromise, rejectPromise) => {
|
|
246
|
+
resolveTurn = resolvePromise;
|
|
247
|
+
rejectTurn = rejectPromise;
|
|
248
|
+
});
|
|
249
|
+
const turn = {
|
|
250
|
+
messageIdsWithDeltas: new Set(),
|
|
251
|
+
ownedTurnIds: new Set(),
|
|
252
|
+
userMessageObserved: false,
|
|
253
|
+
aborted: false,
|
|
254
|
+
cancelRequested: false,
|
|
255
|
+
resolve: resolveTurn,
|
|
256
|
+
reject: rejectTurn,
|
|
257
|
+
};
|
|
258
|
+
this.activeTurn = turn;
|
|
259
|
+
try {
|
|
260
|
+
await this.session.send(toMessageOptions(input));
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (this.activeTurn === turn) {
|
|
264
|
+
this.activeTurn = undefined;
|
|
265
|
+
}
|
|
266
|
+
turn.reject(normalizeError(error));
|
|
267
|
+
}
|
|
268
|
+
return completion;
|
|
269
|
+
}
|
|
270
|
+
nextUpdate() {
|
|
271
|
+
const queued = this.pendingUpdates.shift();
|
|
272
|
+
if (queued) {
|
|
273
|
+
return Promise.resolve(queued);
|
|
274
|
+
}
|
|
275
|
+
if (this.closePromise) {
|
|
276
|
+
return Promise.reject(new Error('Copilot SDK session is closed'));
|
|
277
|
+
}
|
|
278
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
279
|
+
this.updateWaiters.push({
|
|
280
|
+
resolve: resolvePromise,
|
|
281
|
+
reject: rejectPromise,
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async interruptMainTurn() {
|
|
286
|
+
const turn = this.activeTurn;
|
|
287
|
+
if (turn) {
|
|
288
|
+
turn.cancelRequested = true;
|
|
289
|
+
}
|
|
290
|
+
const result = await this.session.rpc.interruptMainTurn({ flushQueued: false });
|
|
291
|
+
return result.interrupted || (turn !== undefined && this.activeTurn === turn);
|
|
292
|
+
}
|
|
293
|
+
async cancelAllBackgroundWork() {
|
|
294
|
+
await this.backgroundTaskRefresh;
|
|
295
|
+
const hadBackgroundSignal = this.backgroundAgentsActive;
|
|
296
|
+
const taskList = await this.session.rpc.tasks.list();
|
|
297
|
+
const activeShells = taskList.tasks.filter((task) => task.type === 'shell' && (task.status === 'running' || task.status === 'idle'));
|
|
298
|
+
const [cancelledAgents, ...cancelledShells] = await Promise.all([
|
|
299
|
+
this.session.rpc.cancelAllBackgroundAgents(),
|
|
300
|
+
...activeShells.map((task) => this.session.rpc.tasks.cancel({ id: task.id })),
|
|
301
|
+
]);
|
|
302
|
+
return (hadBackgroundSignal ||
|
|
303
|
+
cancelledAgents > 0 ||
|
|
304
|
+
activeShells.length > 0 ||
|
|
305
|
+
cancelledShells.some((result) => result.cancelled));
|
|
306
|
+
}
|
|
307
|
+
async cancelBackgroundRun(providerRunId) {
|
|
308
|
+
const result = await this.session.rpc.tasks.cancel({ id: providerRunId });
|
|
309
|
+
return result.cancelled;
|
|
310
|
+
}
|
|
311
|
+
async hasBackgroundAgents() {
|
|
312
|
+
await this.backgroundTaskRefresh;
|
|
313
|
+
return this.backgroundAgentsActive;
|
|
314
|
+
}
|
|
315
|
+
backgroundTaskExecutionState() {
|
|
316
|
+
return Object.fromEntries([...this.backgroundTaskExecutions].map(([taskId, execution]) => [
|
|
317
|
+
taskId,
|
|
318
|
+
{ ...execution },
|
|
319
|
+
]));
|
|
320
|
+
}
|
|
321
|
+
async reconcileBackgroundTasks() {
|
|
322
|
+
this.backgroundTaskExecutionsDirty = true;
|
|
323
|
+
this.queueBackgroundTaskRefresh();
|
|
324
|
+
await this.backgroundTaskRefresh;
|
|
325
|
+
}
|
|
326
|
+
close(reason) {
|
|
327
|
+
if (!this.closePromise) {
|
|
328
|
+
if (this.backgroundTaskRefreshRetry) {
|
|
329
|
+
clearTimeout(this.backgroundTaskRefreshRetry);
|
|
330
|
+
this.backgroundTaskRefreshRetry = undefined;
|
|
331
|
+
}
|
|
332
|
+
if (this.backgroundCompletionSignalExpiry) {
|
|
333
|
+
clearTimeout(this.backgroundCompletionSignalExpiry);
|
|
334
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
335
|
+
}
|
|
336
|
+
this.backgroundCompletionSignals.length = 0;
|
|
337
|
+
this.autonomousTurns.clear();
|
|
338
|
+
this.autonomousTurnIdByRuntimeTurnId.clear();
|
|
339
|
+
this.autonomousTurnIdByInteractionId.clear();
|
|
340
|
+
this.refreshAutonomousTurnPresence();
|
|
341
|
+
this.closePromise = (async () => {
|
|
342
|
+
this.unsubscribe();
|
|
343
|
+
this.activeTurn?.reject(reason ?? new Error('Copilot SDK session closed'));
|
|
344
|
+
this.activeTurn = undefined;
|
|
345
|
+
for (const waiter of this.updateWaiters.splice(0)) {
|
|
346
|
+
waiter.reject(reason ?? new Error('Copilot SDK session closed'));
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await this.session.rpc.shutdown({
|
|
350
|
+
type: reason ? 'error' : 'routine',
|
|
351
|
+
...(reason ? { reason: reason.message } : {}),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
finally {
|
|
355
|
+
await this.session.disconnect();
|
|
356
|
+
}
|
|
357
|
+
})();
|
|
358
|
+
}
|
|
359
|
+
return this.closePromise;
|
|
360
|
+
}
|
|
361
|
+
emitUpdate(update) {
|
|
362
|
+
const waiter = this.updateWaiters.shift();
|
|
363
|
+
if (waiter) {
|
|
364
|
+
waiter.resolve(update);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.pendingUpdates.push(update);
|
|
368
|
+
}
|
|
369
|
+
queueBackgroundTaskRefresh() {
|
|
370
|
+
if (this.backgroundTaskRefreshRetry) {
|
|
371
|
+
clearTimeout(this.backgroundTaskRefreshRetry);
|
|
372
|
+
this.backgroundTaskRefreshRetry = undefined;
|
|
373
|
+
}
|
|
374
|
+
this.backgroundTaskRefresh = this.backgroundTaskRefresh
|
|
375
|
+
.then(async () => {
|
|
376
|
+
if (this.closePromise) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const taskList = await this.session.rpc.tasks.list();
|
|
380
|
+
if (this.closePromise) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
this.backgroundTaskRefreshFailures = 0;
|
|
384
|
+
const reportedTaskIds = new Set();
|
|
385
|
+
let hasReusableBackgroundAgents = false;
|
|
386
|
+
for (const task of taskList.tasks) {
|
|
387
|
+
const explicitWait = this.backgroundWaitFor(task.id);
|
|
388
|
+
const executionStartedAt = task.type === 'agent' && task.executionMode === 'background'
|
|
389
|
+
? this.resolveBackgroundExecutionStartedAt(task, explicitWait !== undefined)
|
|
390
|
+
: undefined;
|
|
391
|
+
const run = toCopilotBackgroundRunProgress(task, explicitWait, executionStartedAt);
|
|
392
|
+
if (!run) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
reportedTaskIds.add(run.providerRunId);
|
|
396
|
+
if (task.status === 'running' || task.status === 'idle') {
|
|
397
|
+
hasReusableBackgroundAgents = true;
|
|
398
|
+
}
|
|
399
|
+
const fingerprint = JSON.stringify(run);
|
|
400
|
+
const previous = this.reportedBackgroundTasks.get(run.providerRunId);
|
|
401
|
+
if (previous && JSON.stringify(previous) === fingerprint) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
this.reportedBackgroundTasks.set(run.providerRunId, run);
|
|
405
|
+
this.onBackgroundRun(run);
|
|
406
|
+
}
|
|
407
|
+
for (const [providerRunId, previous] of this.reportedBackgroundTasks) {
|
|
408
|
+
if (!reportedTaskIds.has(providerRunId)) {
|
|
409
|
+
this.reportedBackgroundTasks.delete(providerRunId);
|
|
410
|
+
if (this.backgroundTaskExecutions.delete(providerRunId)) {
|
|
411
|
+
this.backgroundTaskExecutionsDirty = true;
|
|
412
|
+
}
|
|
413
|
+
this.backgroundWaits.delete(providerRunId);
|
|
414
|
+
if (previous.state === 'running' || previous.state === 'waiting') {
|
|
415
|
+
this.onBackgroundRun({
|
|
416
|
+
...previous,
|
|
417
|
+
state: 'unknown',
|
|
418
|
+
finishedAt: Date.now(),
|
|
419
|
+
reason: 'Provider registry stopped reporting this background task before its final state was observed',
|
|
420
|
+
stopSupported: false,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (this.backgroundTaskExecutionsDirty) {
|
|
426
|
+
await this.onBackgroundTaskExecutionsChanged(this.backgroundTaskExecutionState());
|
|
427
|
+
this.backgroundTaskExecutionsDirty = false;
|
|
428
|
+
}
|
|
429
|
+
if (this.backgroundAgentsActive !== hasReusableBackgroundAgents) {
|
|
430
|
+
this.backgroundAgentsActive = hasReusableBackgroundAgents;
|
|
431
|
+
this.onBackgroundAgentPresenceChanged(hasReusableBackgroundAgents);
|
|
432
|
+
}
|
|
433
|
+
})
|
|
434
|
+
.catch((error) => {
|
|
435
|
+
if (this.closePromise) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
this.backgroundTaskRefreshFailures += 1;
|
|
439
|
+
if (!this.backgroundAgentsActive) {
|
|
440
|
+
this.backgroundAgentsActive = true;
|
|
441
|
+
this.onBackgroundAgentPresenceChanged(true);
|
|
442
|
+
}
|
|
443
|
+
this.onBackgroundRunError(normalizeError(error));
|
|
444
|
+
const retryDelayMs = Math.min(250 * 2 ** Math.min(this.backgroundTaskRefreshFailures - 1, 4), 5_000);
|
|
445
|
+
if (this.backgroundTaskRefreshRetry) {
|
|
446
|
+
clearTimeout(this.backgroundTaskRefreshRetry);
|
|
447
|
+
}
|
|
448
|
+
const retry = setTimeout(() => {
|
|
449
|
+
if (this.backgroundTaskRefreshRetry !== retry) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.backgroundTaskRefreshRetry = undefined;
|
|
453
|
+
this.queueBackgroundTaskRefresh();
|
|
454
|
+
}, retryDelayMs);
|
|
455
|
+
this.backgroundTaskRefreshRetry = retry;
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Completion signals and their autonomous root turns are separate SDK event
|
|
460
|
+
* streams and may arrive in either order. A short-lived signal confirms an
|
|
461
|
+
* otherwise unowned root turn; only that root turn's assistant messages are
|
|
462
|
+
* eligible for delivery outside the foreground prompt.
|
|
463
|
+
*/
|
|
464
|
+
noteBackgroundCompletion(event) {
|
|
465
|
+
const kind = event.data.kind;
|
|
466
|
+
if (kind.type !== 'agent_completed' && kind.type !== 'agent_idle') {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
this.pruneBackgroundCompletionSignals();
|
|
470
|
+
this.backgroundCompletionSignals.push({
|
|
471
|
+
eventId: event.id,
|
|
472
|
+
providerRunId: kind.agentId,
|
|
473
|
+
expiresAt: Date.now() + BACKGROUND_COMPLETION_SIGNAL_TTL_MS,
|
|
474
|
+
});
|
|
475
|
+
if (this.backgroundCompletionSignals.length > MAX_BACKGROUND_COMPLETION_SIGNALS) {
|
|
476
|
+
this.backgroundCompletionSignals.splice(0, this.backgroundCompletionSignals.length - MAX_BACKGROUND_COMPLETION_SIGNALS);
|
|
477
|
+
}
|
|
478
|
+
if (this.currentRootTurnId
|
|
479
|
+
&& !this.activeTurn?.ownedTurnIds.has(this.currentRootTurnId)) {
|
|
480
|
+
const existing = this.findAutonomousTurn(this.currentRootTurnId);
|
|
481
|
+
if (existing) {
|
|
482
|
+
existing.confirmed = true;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
const turn = this.createAutonomousTurn(event.id, true, this.currentRootTurnId);
|
|
486
|
+
this.bindAutonomousTurn(turn, this.currentRootTurnId);
|
|
487
|
+
}
|
|
488
|
+
this.consumeBackgroundCompletionSignal(kind.agentId);
|
|
489
|
+
}
|
|
490
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
491
|
+
this.refreshAutonomousTurnPresence();
|
|
492
|
+
}
|
|
493
|
+
isAutonomousCompletionMessage(event) {
|
|
494
|
+
const source = event.data.source?.trim().toLowerCase();
|
|
495
|
+
const parentTaskId = event.data.parentAgentTaskId?.trim();
|
|
496
|
+
// Foreground SDK prompts can also carry external parent-task metadata.
|
|
497
|
+
const knownBackgroundParent = parentTaskId !== undefined &&
|
|
498
|
+
(this.reportedBackgroundTasks.has(parentTaskId) ||
|
|
499
|
+
this.backgroundTaskExecutions.has(parentTaskId));
|
|
500
|
+
if (source === 'background-task' || knownBackgroundParent) {
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
this.pruneBackgroundCompletionSignals();
|
|
504
|
+
return event.data.isAutopilotContinuation === true && this.backgroundCompletionSignals.length > 0;
|
|
505
|
+
}
|
|
506
|
+
beginAutonomousTurn(event) {
|
|
507
|
+
const turnId = this.readUserMessageTurnId(event);
|
|
508
|
+
const interactionId = event.data.interactionId?.trim() || undefined;
|
|
509
|
+
const existing = this.findAutonomousTurn(turnId, interactionId);
|
|
510
|
+
if (existing) {
|
|
511
|
+
return existing;
|
|
512
|
+
}
|
|
513
|
+
const turn = this.createAutonomousTurn(event.id, true, turnId, interactionId);
|
|
514
|
+
this.bindAutonomousTurn(turn, turnId, interactionId);
|
|
515
|
+
this.consumeBackgroundCompletionSignal(event.data.parentAgentTaskId);
|
|
516
|
+
this.refreshAutonomousTurnPresence();
|
|
517
|
+
return turn;
|
|
518
|
+
}
|
|
519
|
+
createAutonomousTurn(eventId, confirmed, turnId, interactionId) {
|
|
520
|
+
const turn = {
|
|
521
|
+
eventId,
|
|
522
|
+
confirmed,
|
|
523
|
+
turnId,
|
|
524
|
+
interactionId,
|
|
525
|
+
messageOrder: [],
|
|
526
|
+
messageTextById: new Map(),
|
|
527
|
+
seenEventIds: new Set(),
|
|
528
|
+
};
|
|
529
|
+
this.autonomousTurns.set(eventId, turn);
|
|
530
|
+
return turn;
|
|
531
|
+
}
|
|
532
|
+
bindAutonomousTurn(turn, turnId, interactionId) {
|
|
533
|
+
if (turnId) {
|
|
534
|
+
turn.turnId = turnId;
|
|
535
|
+
this.autonomousTurnIdByRuntimeTurnId.set(turnId, turn.eventId);
|
|
536
|
+
}
|
|
537
|
+
if (interactionId) {
|
|
538
|
+
turn.interactionId = interactionId;
|
|
539
|
+
this.autonomousTurnIdByInteractionId.set(interactionId, turn.eventId);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
findAutonomousTurn(turnId, interactionId) {
|
|
543
|
+
const eventId = ((turnId ? this.autonomousTurnIdByRuntimeTurnId.get(turnId) : undefined)
|
|
544
|
+
?? (interactionId ? this.autonomousTurnIdByInteractionId.get(interactionId) : undefined));
|
|
545
|
+
return eventId ? this.autonomousTurns.get(eventId) : undefined;
|
|
546
|
+
}
|
|
547
|
+
consumeBackgroundCompletionSignal(providerRunId) {
|
|
548
|
+
this.pruneBackgroundCompletionSignals();
|
|
549
|
+
const index = providerRunId
|
|
550
|
+
? this.backgroundCompletionSignals.findIndex((signal) => signal.providerRunId === providerRunId)
|
|
551
|
+
: 0;
|
|
552
|
+
if (index < 0 || this.backgroundCompletionSignals.length === 0) {
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
const [signal] = this.backgroundCompletionSignals.splice(index, 1);
|
|
556
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
557
|
+
return signal;
|
|
558
|
+
}
|
|
559
|
+
pruneBackgroundCompletionSignals() {
|
|
560
|
+
const now = Date.now();
|
|
561
|
+
while (this.backgroundCompletionSignals.length > 0
|
|
562
|
+
&& this.backgroundCompletionSignals[0].expiresAt <= now) {
|
|
563
|
+
this.backgroundCompletionSignals.shift();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
scheduleBackgroundCompletionSignalExpiry() {
|
|
567
|
+
if (this.backgroundCompletionSignalExpiry) {
|
|
568
|
+
clearTimeout(this.backgroundCompletionSignalExpiry);
|
|
569
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
570
|
+
}
|
|
571
|
+
this.pruneBackgroundCompletionSignals();
|
|
572
|
+
const next = this.backgroundCompletionSignals[0];
|
|
573
|
+
if (!next) {
|
|
574
|
+
this.refreshAutonomousTurnPresence();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
this.backgroundCompletionSignalExpiry = setTimeout(() => {
|
|
578
|
+
this.backgroundCompletionSignalExpiry = undefined;
|
|
579
|
+
this.pruneBackgroundCompletionSignals();
|
|
580
|
+
this.scheduleBackgroundCompletionSignalExpiry();
|
|
581
|
+
}, Math.max(0, next.expiresAt - Date.now()));
|
|
582
|
+
this.backgroundCompletionSignalExpiry.unref?.();
|
|
583
|
+
}
|
|
584
|
+
refreshAutonomousTurnPresence() {
|
|
585
|
+
const pending = this.autonomousTurns.size > 0 || this.backgroundCompletionSignals.length > 0;
|
|
586
|
+
if (pending === this.autonomousTurnPending) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
this.autonomousTurnPending = pending;
|
|
590
|
+
this.onAutonomousTurnPresenceChanged(pending);
|
|
591
|
+
}
|
|
592
|
+
resolveBackgroundExecutionStartedAt(task, waiting) {
|
|
593
|
+
// task.startedAt belongs to the reusable task, so only per-execution fields or
|
|
594
|
+
// an adapter-owned generation may identify a later invocation.
|
|
595
|
+
const previous = this.backgroundTaskExecutions.get(task.id);
|
|
596
|
+
const activeStartedAt = parseTimestamp(task.activeStartedAt);
|
|
597
|
+
const previousTerminal = previous?.terminal ?? false;
|
|
598
|
+
const currentTerminal = isCopilotExecutionTerminal(task.status, waiting);
|
|
599
|
+
const terminalAt = currentTerminal
|
|
600
|
+
? task.status === 'idle'
|
|
601
|
+
? parseTimestamp(task.idleSince)
|
|
602
|
+
: parseTimestamp(task.completedAt)
|
|
603
|
+
: undefined;
|
|
604
|
+
const activePeriodChanged = previous?.activeStartedAt !== undefined &&
|
|
605
|
+
activeStartedAt !== undefined &&
|
|
606
|
+
previous.activeStartedAt !== activeStartedAt &&
|
|
607
|
+
!previous.waiting;
|
|
608
|
+
const terminalBoundaryChanged = previousTerminal &&
|
|
609
|
+
currentTerminal &&
|
|
610
|
+
terminalAt !== undefined &&
|
|
611
|
+
previous?.terminalAt !== undefined &&
|
|
612
|
+
previous.terminalAt !== terminalAt;
|
|
613
|
+
const startsNewExecution = !previous ||
|
|
614
|
+
(previousTerminal && !currentTerminal) ||
|
|
615
|
+
activePeriodChanged ||
|
|
616
|
+
terminalBoundaryChanged;
|
|
617
|
+
let startedAt = previous?.startedAt;
|
|
618
|
+
if (startsNewExecution) {
|
|
619
|
+
const observedStartedAt = currentTerminal && (!previous || terminalBoundaryChanged)
|
|
620
|
+
? (terminalAt ?? activeStartedAt)
|
|
621
|
+
: (activeStartedAt ?? terminalAt);
|
|
622
|
+
if (observedStartedAt !== undefined && observedStartedAt !== previous?.startedAt) {
|
|
623
|
+
startedAt = observedStartedAt;
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
const now = Date.now();
|
|
627
|
+
startedAt = Math.max(now, this.lastSyntheticExecutionStartedAt + 1, (previous?.startedAt ?? 0) + 1);
|
|
628
|
+
this.lastSyntheticExecutionStartedAt = startedAt;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (startedAt === undefined) {
|
|
632
|
+
startedAt = Math.max(Date.now(), this.lastSyntheticExecutionStartedAt + 1);
|
|
633
|
+
this.lastSyntheticExecutionStartedAt = startedAt;
|
|
634
|
+
}
|
|
635
|
+
const execution = {
|
|
636
|
+
startedAt,
|
|
637
|
+
terminal: currentTerminal,
|
|
638
|
+
waiting,
|
|
639
|
+
...(activeStartedAt !== undefined
|
|
640
|
+
? { activeStartedAt }
|
|
641
|
+
: !startsNewExecution && previous?.activeStartedAt !== undefined
|
|
642
|
+
? { activeStartedAt: previous.activeStartedAt }
|
|
643
|
+
: {}),
|
|
644
|
+
...(terminalAt !== undefined
|
|
645
|
+
? { terminalAt }
|
|
646
|
+
: !startsNewExecution && previous?.terminalAt !== undefined
|
|
647
|
+
? { terminalAt: previous.terminalAt }
|
|
648
|
+
: {}),
|
|
649
|
+
};
|
|
650
|
+
if (!previous ||
|
|
651
|
+
previous.startedAt !== execution.startedAt ||
|
|
652
|
+
previous.terminal !== execution.terminal ||
|
|
653
|
+
previous.waiting !== execution.waiting ||
|
|
654
|
+
previous.activeStartedAt !== execution.activeStartedAt ||
|
|
655
|
+
previous.terminalAt !== execution.terminalAt) {
|
|
656
|
+
this.backgroundTaskExecutionsDirty = true;
|
|
657
|
+
}
|
|
658
|
+
this.backgroundTaskExecutions.set(task.id, execution);
|
|
659
|
+
return execution.startedAt;
|
|
660
|
+
}
|
|
661
|
+
handleEvent(event) {
|
|
662
|
+
if (event.type === 'session.background_tasks_changed') {
|
|
663
|
+
this.queueBackgroundTaskRefresh();
|
|
664
|
+
}
|
|
665
|
+
if (event.type === 'system.notification') {
|
|
666
|
+
this.noteBackgroundCompletion(event);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (event.agentId) {
|
|
670
|
+
this.handleBackgroundAgentEvent(event, event.agentId);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (event.type === 'user.message') {
|
|
674
|
+
if (this.isAutonomousCompletionMessage(event)) {
|
|
675
|
+
this.beginAutonomousTurn(event);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (this.activeTurn &&
|
|
679
|
+
!this.activeTurn.userMessageObserved &&
|
|
680
|
+
this.isPromptUserMessage(event)) {
|
|
681
|
+
this.activeTurn.userMessageObserved = true;
|
|
682
|
+
this.activeTurn.interactionId = event.data.interactionId;
|
|
683
|
+
const wireTurnId = this.readUserMessageTurnId(event);
|
|
684
|
+
if (wireTurnId) {
|
|
685
|
+
this.activeTurn.ownedTurnIds.add(wireTurnId);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (event.type === 'assistant.turn_start') {
|
|
691
|
+
this.currentRootTurnId = event.data.turnId;
|
|
692
|
+
this.lastRootTurnId = event.data.turnId;
|
|
693
|
+
const activeTurn = this.activeTurn;
|
|
694
|
+
const foregroundOwnsTurn = (activeTurn &&
|
|
695
|
+
activeTurn.userMessageObserved &&
|
|
696
|
+
(activeTurn.ownedTurnIds.has(event.data.turnId) ||
|
|
697
|
+
(activeTurn.ownedTurnIds.size === 0 &&
|
|
698
|
+
(!activeTurn.interactionId || activeTurn.interactionId === event.data.interactionId)) ||
|
|
699
|
+
(activeTurn.interactionId !== undefined &&
|
|
700
|
+
activeTurn.interactionId === event.data.interactionId)));
|
|
701
|
+
if (foregroundOwnsTurn) {
|
|
702
|
+
activeTurn.ownedTurnIds.add(event.data.turnId);
|
|
703
|
+
if (activeTurn.cancelRequested) {
|
|
704
|
+
void this.interruptAttachedTurn(activeTurn);
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
let autonomousTurn = this.findAutonomousTurn(event.data.turnId, event.data.interactionId);
|
|
709
|
+
if (!autonomousTurn) {
|
|
710
|
+
const signal = this.consumeBackgroundCompletionSignal();
|
|
711
|
+
autonomousTurn = this.createAutonomousTurn(signal?.eventId ?? `${this.sessionId}:${event.data.turnId}`, signal !== undefined, event.data.turnId, event.data.interactionId);
|
|
712
|
+
}
|
|
713
|
+
if (autonomousTurn) {
|
|
714
|
+
this.bindAutonomousTurn(autonomousTurn, event.data.turnId, event.data.interactionId);
|
|
715
|
+
this.refreshAutonomousTurnPresence();
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const eventTurnId = this.resolveEventTurnId(event);
|
|
720
|
+
const autonomousTurn = eventTurnId
|
|
721
|
+
? this.findAutonomousTurn(eventTurnId)
|
|
722
|
+
: undefined;
|
|
723
|
+
if (autonomousTurn) {
|
|
724
|
+
this.handleAutonomousTurnEvent(autonomousTurn, event);
|
|
725
|
+
this.noteTurnBoundary(event, eventTurnId);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const activeTurn = this.activeTurn;
|
|
729
|
+
if (!activeTurn || !eventTurnId) {
|
|
730
|
+
this.noteTurnBoundary(event, eventTurnId);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (!activeTurn.ownedTurnIds.has(eventTurnId)) {
|
|
734
|
+
this.noteTurnBoundary(event, eventTurnId);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.handleOwnedTurnEvent(event);
|
|
738
|
+
this.noteTurnBoundary(event, eventTurnId);
|
|
739
|
+
}
|
|
740
|
+
handleAutonomousTurnEvent(turn, event) {
|
|
741
|
+
if (event.type === 'assistant.message_delta') {
|
|
742
|
+
if (turn.seenEventIds.has(event.id)) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
turn.seenEventIds.add(event.id);
|
|
746
|
+
if (!turn.messageTextById.has(event.data.messageId)) {
|
|
747
|
+
turn.messageOrder.push(event.data.messageId);
|
|
748
|
+
}
|
|
749
|
+
const current = turn.messageTextById.get(event.data.messageId) ?? '';
|
|
750
|
+
turn.messageTextById.set(event.data.messageId, current + event.data.deltaContent);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (event.type === 'assistant.message') {
|
|
754
|
+
if (turn.seenEventIds.has(event.id)) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
turn.seenEventIds.add(event.id);
|
|
758
|
+
if (!turn.messageTextById.has(event.data.messageId)) {
|
|
759
|
+
turn.messageOrder.push(event.data.messageId);
|
|
760
|
+
}
|
|
761
|
+
turn.messageTextById.set(event.data.messageId, event.data.content);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (event.type === 'session.error') {
|
|
765
|
+
this.discardAutonomousTurn(turn);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (event.type === 'assistant.idle') {
|
|
769
|
+
this.finishAutonomousTurn(turn);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
finishAutonomousTurn(turn) {
|
|
773
|
+
const text = turn.messageOrder
|
|
774
|
+
.map((messageId) => turn.messageTextById.get(messageId) ?? '')
|
|
775
|
+
.join('\n\n')
|
|
776
|
+
.trim();
|
|
777
|
+
try {
|
|
778
|
+
if (turn.confirmed && text) {
|
|
779
|
+
this.onAutonomousReply({ eventId: turn.eventId, text });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
finally {
|
|
783
|
+
this.discardAutonomousTurn(turn);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
discardAutonomousTurn(turn) {
|
|
787
|
+
this.autonomousTurns.delete(turn.eventId);
|
|
788
|
+
if (turn.turnId) {
|
|
789
|
+
this.autonomousTurnIdByRuntimeTurnId.delete(turn.turnId);
|
|
790
|
+
}
|
|
791
|
+
if (turn.interactionId) {
|
|
792
|
+
this.autonomousTurnIdByInteractionId.delete(turn.interactionId);
|
|
793
|
+
}
|
|
794
|
+
this.refreshAutonomousTurnPresence();
|
|
795
|
+
}
|
|
796
|
+
handleBackgroundAgentEvent(event, agentId) {
|
|
797
|
+
switch (event.type) {
|
|
798
|
+
case 'permission.requested':
|
|
799
|
+
if (event.data.resolvedByHook !== true) {
|
|
800
|
+
this.recordBackgroundWait(agentId, event.data.requestId, 'Waiting for permission approval');
|
|
801
|
+
}
|
|
802
|
+
return;
|
|
803
|
+
case 'user_input.requested':
|
|
804
|
+
this.recordBackgroundWait(agentId, event.data.requestId, event.data.question.trim() || 'Waiting for user input');
|
|
805
|
+
return;
|
|
806
|
+
case 'elicitation.requested':
|
|
807
|
+
this.recordBackgroundWait(agentId, event.data.requestId, event.data.message.trim() || 'Waiting for external input');
|
|
808
|
+
return;
|
|
809
|
+
case 'exit_plan_mode.requested':
|
|
810
|
+
this.recordBackgroundWait(agentId, event.data.requestId, event.data.summary.trim() || 'Waiting for plan approval');
|
|
811
|
+
return;
|
|
812
|
+
case 'permission.completed':
|
|
813
|
+
case 'user_input.completed':
|
|
814
|
+
case 'elicitation.completed':
|
|
815
|
+
case 'exit_plan_mode.completed':
|
|
816
|
+
this.resolveBackgroundWait(agentId, event.data.requestId);
|
|
817
|
+
return;
|
|
818
|
+
case 'assistant.turn_start':
|
|
819
|
+
case 'subagent.completed':
|
|
820
|
+
case 'subagent.failed':
|
|
821
|
+
if (this.backgroundWaits.delete(agentId)) {
|
|
822
|
+
this.queueBackgroundTaskRefresh();
|
|
823
|
+
}
|
|
824
|
+
return;
|
|
825
|
+
default:
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
recordBackgroundWait(agentId, requestId, reason) {
|
|
830
|
+
const waits = this.backgroundWaits.get(agentId) ?? new Map();
|
|
831
|
+
waits.set(requestId, reason);
|
|
832
|
+
this.backgroundWaits.set(agentId, waits);
|
|
833
|
+
this.queueBackgroundTaskRefresh();
|
|
834
|
+
}
|
|
835
|
+
resolveBackgroundWait(agentId, requestId) {
|
|
836
|
+
const waits = this.backgroundWaits.get(agentId);
|
|
837
|
+
if (!waits?.delete(requestId)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (waits.size === 0) {
|
|
841
|
+
this.backgroundWaits.delete(agentId);
|
|
842
|
+
}
|
|
843
|
+
this.queueBackgroundTaskRefresh();
|
|
844
|
+
}
|
|
845
|
+
backgroundWaitFor(agentId) {
|
|
846
|
+
const waits = this.backgroundWaits.get(agentId);
|
|
847
|
+
if (!waits || waits.size === 0) {
|
|
848
|
+
return undefined;
|
|
849
|
+
}
|
|
850
|
+
const reason = [...waits.values()].at(-1);
|
|
851
|
+
return reason ? { reason } : undefined;
|
|
852
|
+
}
|
|
853
|
+
handleOwnedTurnEvent(event) {
|
|
854
|
+
if (!this.activeTurn) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
switch (event.type) {
|
|
858
|
+
case 'assistant.message_delta':
|
|
859
|
+
this.activeTurn.messageIdsWithDeltas.add(event.data.messageId);
|
|
860
|
+
this.emitUpdate(sessionUpdate({
|
|
861
|
+
sessionUpdate: 'agent_message_chunk',
|
|
862
|
+
content: { type: 'text', text: event.data.deltaContent },
|
|
863
|
+
messageId: event.data.messageId,
|
|
864
|
+
}));
|
|
865
|
+
return;
|
|
866
|
+
case 'assistant.reasoning_delta':
|
|
867
|
+
this.emitUpdate(sessionUpdate({
|
|
868
|
+
sessionUpdate: 'agent_thought_chunk',
|
|
869
|
+
content: { type: 'text', text: event.data.deltaContent },
|
|
870
|
+
messageId: event.data.reasoningId,
|
|
871
|
+
}));
|
|
872
|
+
return;
|
|
873
|
+
case 'assistant.message':
|
|
874
|
+
if (!this.activeTurn.messageIdsWithDeltas.has(event.data.messageId)) {
|
|
875
|
+
this.emitUpdate(sessionUpdate({
|
|
876
|
+
sessionUpdate: 'agent_message_chunk',
|
|
877
|
+
content: { type: 'text', text: event.data.content },
|
|
878
|
+
messageId: event.data.messageId,
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
881
|
+
return;
|
|
882
|
+
case 'tool.execution_start':
|
|
883
|
+
this.emitUpdate(sessionUpdate({
|
|
884
|
+
sessionUpdate: 'tool_call',
|
|
885
|
+
toolCallId: event.data.toolCallId,
|
|
886
|
+
title: event.data.toolDescription?.name ?? event.data.toolName,
|
|
887
|
+
kind: toolKindForName(event.data.toolName),
|
|
888
|
+
status: 'in_progress',
|
|
889
|
+
rawInput: event.data.arguments,
|
|
890
|
+
}));
|
|
891
|
+
return;
|
|
892
|
+
case 'tool.execution_progress':
|
|
893
|
+
this.emitUpdate(sessionUpdate({
|
|
894
|
+
sessionUpdate: 'tool_call_update',
|
|
895
|
+
toolCallId: event.data.toolCallId,
|
|
896
|
+
status: 'in_progress',
|
|
897
|
+
rawOutput: { content: event.data.progressMessage },
|
|
898
|
+
}));
|
|
899
|
+
return;
|
|
900
|
+
case 'tool.execution_complete':
|
|
901
|
+
this.emitUpdate(sessionUpdate({
|
|
902
|
+
sessionUpdate: 'tool_call_update',
|
|
903
|
+
toolCallId: event.data.toolCallId,
|
|
904
|
+
title: event.data.toolDescription?.name,
|
|
905
|
+
status: event.data.success ? 'completed' : 'failed',
|
|
906
|
+
rawOutput: event.data.result ?? event.data.error,
|
|
907
|
+
}));
|
|
908
|
+
return;
|
|
909
|
+
case 'abort':
|
|
910
|
+
this.activeTurn.aborted = true;
|
|
911
|
+
return;
|
|
912
|
+
case 'session.error': {
|
|
913
|
+
const turn = this.activeTurn;
|
|
914
|
+
this.activeTurn = undefined;
|
|
915
|
+
turn.reject(new Error(event.data.message));
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
case 'assistant.idle': {
|
|
919
|
+
this.finishActiveTurn(event.data.aborted === true);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
default:
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async interruptAttachedTurn(turn) {
|
|
927
|
+
try {
|
|
928
|
+
const result = await this.session.rpc.interruptMainTurn({ flushQueued: false });
|
|
929
|
+
if (!result.interrupted && this.activeTurn === turn) {
|
|
930
|
+
throw new Error('Copilot SDK did not accept the queued foreground interrupt');
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
if (this.activeTurn !== turn) {
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
this.activeTurn = undefined;
|
|
938
|
+
turn.reject(normalizeError(error));
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
finishActiveTurn(aborted) {
|
|
942
|
+
const turn = this.activeTurn;
|
|
943
|
+
if (!turn) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
this.activeTurn = undefined;
|
|
947
|
+
const stopReason = aborted || turn.aborted ? 'cancelled' : 'end_turn';
|
|
948
|
+
this.emitUpdate(stopUpdate(stopReason));
|
|
949
|
+
turn.resolve({ stopReason });
|
|
950
|
+
}
|
|
951
|
+
resolveEventTurnId(event) {
|
|
952
|
+
switch (event.type) {
|
|
953
|
+
case 'assistant.turn_end':
|
|
954
|
+
return event.data.turnId;
|
|
955
|
+
case 'assistant.message':
|
|
956
|
+
case 'tool.execution_start':
|
|
957
|
+
case 'tool.execution_complete':
|
|
958
|
+
return event.data.turnId ?? this.currentRootTurnId;
|
|
959
|
+
case 'assistant.idle':
|
|
960
|
+
return this.currentRootTurnId ?? this.lastRootTurnId;
|
|
961
|
+
default:
|
|
962
|
+
return this.currentRootTurnId;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
noteTurnBoundary(event, turnId) {
|
|
966
|
+
if (event.type !== 'assistant.turn_end' || !turnId) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
this.lastRootTurnId = turnId;
|
|
970
|
+
if (this.currentRootTurnId === turnId) {
|
|
971
|
+
this.currentRootTurnId = undefined;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
isPromptUserMessage(event) {
|
|
975
|
+
return event.data.isAutopilotContinuation !== true && event.data.source === undefined;
|
|
976
|
+
}
|
|
977
|
+
readUserMessageTurnId(event) {
|
|
978
|
+
const turnId = event.data.turnId;
|
|
979
|
+
return typeof turnId === 'string' ? turnId : undefined;
|
|
980
|
+
}
|
|
981
|
+
}
|