@openclaw/copilot 2026.6.9-beta.1 → 2026.6.10-beta.1
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/{attempt-BMzeV7AP.js → attempt-BQoxgpM3.js} +425 -66
- package/dist/{harness-BFRPethX.js → harness-I9ahzRYI.js} +106 -11
- package/dist/harness.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{runtime-BUOrFPqD.js → runtime-Bo_ajDXI.js} +0 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as resolveCopilotAuth } from "./harness-
|
|
2
|
-
import { acquireSessionWriteLock, appendSessionTranscriptMessage, applyEmbeddedAttemptToolsAllow, buildEmbeddedAttemptToolRunContext, detectAndLoadAgentHarnessPromptImages, emitSessionTranscriptUpdate, getPluginToolMeta, isSubagentSessionKey, isToolResultError, resolveAttemptFsWorkspaceOnly, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveEmbeddedAttemptToolConstructionPlan, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentHarnessBeforeMessageWriteHook, sanitizeToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
1
|
+
import { n as resolveCopilotAuth } from "./harness-I9ahzRYI.js";
|
|
2
|
+
import { acquireSessionWriteLock, appendSessionTranscriptMessage, applyEmbeddedAttemptToolsAllow, awaitAgentEndSideEffects, buildAgentHookContextChannelFields, buildEmbeddedAttemptToolRunContext, detectAndLoadAgentHarnessPromptImages, emitSessionTranscriptUpdate, extractToolErrorMessage, getPluginToolMeta, isSubagentSessionKey, isToolResultError, resolveAgentHarnessBeforePromptBuildResult, resolveAttemptFsWorkspaceOnly, resolveAttemptSpawnWorkspaceDir, resolveBootstrapContextForRun, resolveCompactionTimeoutMs, resolveEmbeddedAttemptToolConstructionPlan, resolveModelAuthMode, resolveSandboxContext, resolveSessionAgentIds, resolveSessionWriteLockAcquireTimeoutMs, resolveUserPath, runAgentEndSideEffects, runAgentHarnessAfterCompactionHook, runAgentHarnessAfterToolCallHook, runAgentHarnessBeforeCompactionHook, runAgentHarnessBeforeMessageWriteHook, runAgentHarnessLlmInputHook, runAgentHarnessLlmOutputHook, sanitizeToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import fsp from "node:fs/promises";
|
|
@@ -225,8 +225,18 @@ function attachEventBridge(session, options) {
|
|
|
225
225
|
const toolNamesByCallId = /* @__PURE__ */ new Map();
|
|
226
226
|
let startedCount = 0;
|
|
227
227
|
let completedCount = 0;
|
|
228
|
+
let activeCompactionCount = 0;
|
|
229
|
+
let observedCompaction = false;
|
|
228
230
|
let deltaQueue = Promise.resolve();
|
|
229
231
|
let deltaChain = Promise.resolve();
|
|
232
|
+
let compactionChain = Promise.resolve();
|
|
233
|
+
let compactionIdle = Promise.resolve();
|
|
234
|
+
let resolveCompactionIdle;
|
|
235
|
+
let observedSessionIdle = false;
|
|
236
|
+
let resolveSessionIdle;
|
|
237
|
+
const sessionIdle = new Promise((resolve) => {
|
|
238
|
+
resolveSessionIdle = resolve;
|
|
239
|
+
});
|
|
230
240
|
let firstDeltaError;
|
|
231
241
|
let detached = false;
|
|
232
242
|
const unsubscribeFns = [];
|
|
@@ -284,6 +294,33 @@ function attachEventBridge(session, options) {
|
|
|
284
294
|
toolName
|
|
285
295
|
});
|
|
286
296
|
});
|
|
297
|
+
registerListener(session, unsubscribeFns, "session.compaction_start", (event) => {
|
|
298
|
+
if (!isRootCompactionEvent(event)) return;
|
|
299
|
+
observedCompaction = true;
|
|
300
|
+
if (activeCompactionCount === 0) compactionIdle = new Promise((resolve) => {
|
|
301
|
+
resolveCompactionIdle = resolve;
|
|
302
|
+
});
|
|
303
|
+
activeCompactionCount += 1;
|
|
304
|
+
enqueueCompactionCallback(options.onCompactionStart);
|
|
305
|
+
});
|
|
306
|
+
registerListener(session, unsubscribeFns, "session.compaction_complete", (event) => {
|
|
307
|
+
if (!isRootCompactionEvent(event)) return;
|
|
308
|
+
activeCompactionCount = Math.max(0, activeCompactionCount - 1);
|
|
309
|
+
enqueueCompactionCallback(() => options.onCompactionComplete?.({
|
|
310
|
+
...event.data.messagesRemoved !== void 0 ? { messagesRemoved: event.data.messagesRemoved } : {},
|
|
311
|
+
success: event.data.success
|
|
312
|
+
}));
|
|
313
|
+
if (activeCompactionCount === 0) {
|
|
314
|
+
resolveCompactionIdle?.();
|
|
315
|
+
resolveCompactionIdle = void 0;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
registerListener(session, unsubscribeFns, "session.idle", (event) => {
|
|
319
|
+
if (!isRootCompactionEvent(event)) return;
|
|
320
|
+
observedSessionIdle = true;
|
|
321
|
+
resolveSessionIdle?.();
|
|
322
|
+
resolveSessionIdle = void 0;
|
|
323
|
+
});
|
|
287
324
|
registerListener(session, unsubscribeFns, "session.error", (event) => {
|
|
288
325
|
if (!options.isAborted()) streamError = createPromptError$1(event.data.errorCode ?? event.data.errorType, event.data.message);
|
|
289
326
|
});
|
|
@@ -296,9 +333,32 @@ function attachEventBridge(session, options) {
|
|
|
296
333
|
lastAssistantEvent = result;
|
|
297
334
|
return true;
|
|
298
335
|
},
|
|
336
|
+
awaitCompactionChain() {
|
|
337
|
+
return compactionChain;
|
|
338
|
+
},
|
|
339
|
+
async awaitCompactionCompletion() {
|
|
340
|
+
await awaitStableCompaction();
|
|
341
|
+
},
|
|
342
|
+
awaitSessionIdle() {
|
|
343
|
+
return observedSessionIdle ? Promise.resolve() : sessionIdle;
|
|
344
|
+
},
|
|
345
|
+
settleCompactionWait() {
|
|
346
|
+
activeCompactionCount = 0;
|
|
347
|
+
resolveCompactionIdle?.();
|
|
348
|
+
resolveCompactionIdle = void 0;
|
|
349
|
+
},
|
|
299
350
|
awaitDeltaChain() {
|
|
300
351
|
return deltaChain;
|
|
301
352
|
},
|
|
353
|
+
hasObservedCompaction() {
|
|
354
|
+
return observedCompaction;
|
|
355
|
+
},
|
|
356
|
+
hasObservedSessionIdle() {
|
|
357
|
+
return observedSessionIdle;
|
|
358
|
+
},
|
|
359
|
+
isCompacting() {
|
|
360
|
+
return activeCompactionCount > 0;
|
|
361
|
+
},
|
|
302
362
|
snapshot() {
|
|
303
363
|
return {
|
|
304
364
|
assistantTexts: finalizeAssistantTexts(messageOrder, messagesById, lastAssistantEvent),
|
|
@@ -333,6 +393,17 @@ function attachEventBridge(session, options) {
|
|
|
333
393
|
unsubscribeFns.length = 0;
|
|
334
394
|
}
|
|
335
395
|
};
|
|
396
|
+
function enqueueCompactionCallback(callback) {
|
|
397
|
+
if (!callback) return;
|
|
398
|
+
compactionChain = compactionChain.then(callback, callback).catch(() => void 0);
|
|
399
|
+
}
|
|
400
|
+
async function awaitStableCompaction() {
|
|
401
|
+
const idle = activeCompactionCount > 0 ? compactionIdle : void 0;
|
|
402
|
+
if (idle) await idle;
|
|
403
|
+
const callbacks = compactionChain;
|
|
404
|
+
await callbacks;
|
|
405
|
+
if (activeCompactionCount > 0 || compactionChain !== callbacks) await awaitStableCompaction();
|
|
406
|
+
}
|
|
336
407
|
}
|
|
337
408
|
function buildAssistantMessage(params) {
|
|
338
409
|
const event = params.event;
|
|
@@ -396,6 +467,9 @@ function finalizeAssistantTexts(messageOrder, messagesById, event) {
|
|
|
396
467
|
function isAssistantMessageEvent(event) {
|
|
397
468
|
return event?.type === "assistant.message";
|
|
398
469
|
}
|
|
470
|
+
function isRootCompactionEvent(event) {
|
|
471
|
+
return event.agentId === void 0;
|
|
472
|
+
}
|
|
399
473
|
function joinReasoning(order, reasoningById) {
|
|
400
474
|
return order.map((reasoningId) => reasoningById.get(reasoningId) ?? "").join("");
|
|
401
475
|
}
|
|
@@ -425,10 +499,8 @@ const DEFAULT_HOOK_ERROR_HANDLER = ({ hookName, error }) => {
|
|
|
425
499
|
console.warn(`[copilot hooks-bridge] ${hookName} handler threw:`, error);
|
|
426
500
|
};
|
|
427
501
|
/**
|
|
428
|
-
* Wrap a
|
|
429
|
-
*
|
|
430
|
-
* host handler throws, so the SDK falls back to its default behaviour
|
|
431
|
-
* for that hook.
|
|
502
|
+
* Wrap a native handler so it cannot throw into the SDK. Returning undefined
|
|
503
|
+
* leaves the SDK's default decision in place.
|
|
432
504
|
*/
|
|
433
505
|
function isolate(hookName, handler, onError) {
|
|
434
506
|
if (!handler) return;
|
|
@@ -447,28 +519,46 @@ function isolate(hookName, handler, onError) {
|
|
|
447
519
|
};
|
|
448
520
|
}
|
|
449
521
|
/**
|
|
450
|
-
* Build an SDK-shaped
|
|
451
|
-
*
|
|
452
|
-
* supplied so the SDK skips the hook subsystem entirely.
|
|
522
|
+
* Build an SDK-shaped hook object from native per-attempt configuration.
|
|
523
|
+
* Omit the SDK hook subsystem when no handlers were configured.
|
|
453
524
|
*/
|
|
454
|
-
function createHooksBridge(config) {
|
|
525
|
+
function createHooksBridge(config, options) {
|
|
455
526
|
if (!config) return;
|
|
456
527
|
const onError = config.onHookError ?? DEFAULT_HOOK_ERROR_HANDLER;
|
|
457
528
|
const hooks = {};
|
|
458
529
|
const pre = isolate("onPreToolUse", config.onPreToolUse, onError);
|
|
530
|
+
const preMcp = isolate("onPreMcpToolCall", config.onPreMcpToolCall, onError);
|
|
459
531
|
const post = isolate("onPostToolUse", config.onPostToolUse, onError);
|
|
532
|
+
const postFailure = isolate("onPostToolUseFailure", config.onPostToolUseFailure, onError);
|
|
460
533
|
const userPrompt = isolate("onUserPromptSubmitted", config.onUserPromptSubmitted, onError);
|
|
461
534
|
const sessionStart = isolate("onSessionStart", config.onSessionStart, onError);
|
|
462
535
|
const sessionEnd = isolate("onSessionEnd", config.onSessionEnd, onError);
|
|
463
536
|
const errorOccurred = isolate("onErrorOccurred", config.onErrorOccurred, onError);
|
|
464
537
|
if (pre) hooks.onPreToolUse = pre;
|
|
538
|
+
if (preMcp) hooks.onPreMcpToolCall = preMcp;
|
|
465
539
|
if (post) hooks.onPostToolUse = post;
|
|
466
|
-
if (
|
|
540
|
+
if (postFailure) hooks.onPostToolUseFailure = postFailure;
|
|
541
|
+
if (userPrompt) hooks.onUserPromptSubmitted = async (input, invocation) => {
|
|
542
|
+
const output = await userPrompt(input, invocation);
|
|
543
|
+
try {
|
|
544
|
+
options?.onUserPromptSubmitted?.({
|
|
545
|
+
prompt: output?.modifiedPrompt ?? input.prompt,
|
|
546
|
+
...output?.additionalContext ? { additionalContext: output.additionalContext } : {}
|
|
547
|
+
});
|
|
548
|
+
} catch (error) {
|
|
549
|
+
try {
|
|
550
|
+
onError({
|
|
551
|
+
hookName: "onUserPromptSubmitted",
|
|
552
|
+
error
|
|
553
|
+
});
|
|
554
|
+
} catch {}
|
|
555
|
+
}
|
|
556
|
+
return output;
|
|
557
|
+
};
|
|
467
558
|
if (sessionStart) hooks.onSessionStart = sessionStart;
|
|
468
559
|
if (sessionEnd) hooks.onSessionEnd = sessionEnd;
|
|
469
560
|
if (errorOccurred) hooks.onErrorOccurred = errorOccurred;
|
|
470
|
-
|
|
471
|
-
return hooks;
|
|
561
|
+
return Object.keys(hooks).length > 0 ? hooks : void 0;
|
|
472
562
|
}
|
|
473
563
|
//#endregion
|
|
474
564
|
//#region extensions/copilot/src/permission-bridge.ts
|
|
@@ -737,7 +827,8 @@ async function createCopilotToolBridge(input) {
|
|
|
737
827
|
sdkTools: filteredTools.map((sourceTool) => convertOpenClawToolToSdkTool(sourceTool, {
|
|
738
828
|
abortSignal: input.abortSignal,
|
|
739
829
|
beforeExecute: input.beforeExecute,
|
|
740
|
-
onAgentToolResult: input.attemptParams?.onAgentToolResult
|
|
830
|
+
onAgentToolResult: input.attemptParams?.onAgentToolResult,
|
|
831
|
+
onToolCompleted: input.onToolCompleted
|
|
741
832
|
})),
|
|
742
833
|
sourceTools: filteredTools
|
|
743
834
|
};
|
|
@@ -867,7 +958,17 @@ function convertOpenClawToolToSdkTool(sourceTool, ctx) {
|
|
|
867
958
|
console.warn("[copilot-tool-bridge] onAgentToolResult handler threw; continuing", error);
|
|
868
959
|
}
|
|
869
960
|
};
|
|
870
|
-
const
|
|
961
|
+
const notifyToolCompleted = (completion) => {
|
|
962
|
+
try {
|
|
963
|
+
Promise.resolve(ctx.onToolCompleted?.(completion)).catch((error) => {
|
|
964
|
+
console.warn("[copilot-tool-bridge] onToolCompleted handler threw; continuing", error);
|
|
965
|
+
});
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.warn("[copilot-tool-bridge] onToolCompleted handler threw; continuing", error);
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
const failureResult = (executedArgs, invocation, startedAt, message, error) => {
|
|
971
|
+
const errorMessage = toError$1(error).message;
|
|
871
972
|
notifyToolResult(sanitizeToolResult({
|
|
872
973
|
content: [{
|
|
873
974
|
type: "text",
|
|
@@ -875,15 +976,23 @@ function convertOpenClawToolToSdkTool(sourceTool, ctx) {
|
|
|
875
976
|
}],
|
|
876
977
|
details: {
|
|
877
978
|
status: "failed",
|
|
878
|
-
error:
|
|
979
|
+
error: errorMessage
|
|
879
980
|
}
|
|
880
981
|
}), true);
|
|
982
|
+
notifyToolCompleted({
|
|
983
|
+
toolName: sourceTool.name,
|
|
984
|
+
toolCallId: invocation.toolCallId,
|
|
985
|
+
args: toToolStartArgs(executedArgs),
|
|
986
|
+
error: errorMessage,
|
|
987
|
+
startedAt
|
|
988
|
+
});
|
|
881
989
|
return createFailureResult(message, error);
|
|
882
990
|
};
|
|
883
991
|
const executeOnce = async (args, invocation) => {
|
|
992
|
+
const startedAt = Date.now();
|
|
884
993
|
if (ctx.abortSignal?.aborted) {
|
|
885
994
|
const error = /* @__PURE__ */ new Error("[copilot-tool-bridge] aborted before execution");
|
|
886
|
-
return failureResult(error.message, error);
|
|
995
|
+
return failureResult(args, invocation, startedAt, error.message, error);
|
|
887
996
|
}
|
|
888
997
|
try {
|
|
889
998
|
await ctx.beforeExecute?.({
|
|
@@ -894,23 +1003,33 @@ function convertOpenClawToolToSdkTool(sourceTool, ctx) {
|
|
|
894
1003
|
toolName: sourceTool.name
|
|
895
1004
|
});
|
|
896
1005
|
} catch (error) {
|
|
897
|
-
return failureResult(`[copilot-tool-bridge] beforeExecute failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
|
|
1006
|
+
return failureResult(args, invocation, startedAt, `[copilot-tool-bridge] beforeExecute failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
|
|
898
1007
|
}
|
|
899
1008
|
let preparedArgs;
|
|
900
1009
|
try {
|
|
901
1010
|
preparedArgs = sourceTool.prepareArguments ? sourceTool.prepareArguments(args) : args;
|
|
902
1011
|
} catch (error) {
|
|
903
|
-
return failureResult(`[copilot-tool-bridge] prepareArguments failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
|
|
1012
|
+
return failureResult(args, invocation, startedAt, `[copilot-tool-bridge] prepareArguments failed for tool '${sourceTool.name}': ${toError$1(error).message}`, error);
|
|
904
1013
|
}
|
|
905
1014
|
let result;
|
|
906
1015
|
try {
|
|
907
1016
|
result = await sourceTool.execute(invocation.toolCallId, preparedArgs, ctx.abortSignal, void 0);
|
|
908
1017
|
} catch (error) {
|
|
909
|
-
return failureResult(`[copilot-tool-bridge] tool '${sourceTool.name}' failed: ${toError$1(error).message}`, error);
|
|
1018
|
+
return failureResult(preparedArgs, invocation, startedAt, `[copilot-tool-bridge] tool '${sourceTool.name}' failed: ${toError$1(error).message}`, error);
|
|
910
1019
|
}
|
|
911
1020
|
const sdkResult = agentToolResultToSdk(result);
|
|
912
1021
|
const sanitizedResult = sanitizeToolResult(result);
|
|
913
|
-
|
|
1022
|
+
const resultIsError = sdkResult.resultType === "failure" || isToolResultError(sanitizedResult);
|
|
1023
|
+
const resultError = resultIsError ? extractToolErrorMessage(sanitizedResult) : void 0;
|
|
1024
|
+
notifyToolResult(sanitizedResult, resultIsError);
|
|
1025
|
+
notifyToolCompleted({
|
|
1026
|
+
toolName: sourceTool.name,
|
|
1027
|
+
toolCallId: invocation.toolCallId,
|
|
1028
|
+
args: toToolStartArgs(preparedArgs),
|
|
1029
|
+
result: sanitizedResult,
|
|
1030
|
+
...resultError ? { error: resultError } : {},
|
|
1031
|
+
startedAt
|
|
1032
|
+
});
|
|
914
1033
|
return sdkResult;
|
|
915
1034
|
};
|
|
916
1035
|
const handler = sourceTool.executionMode === "sequential" ? (args, invocation) => {
|
|
@@ -927,6 +1046,9 @@ function convertOpenClawToolToSdkTool(sourceTool, ctx) {
|
|
|
927
1046
|
skipPermission: true
|
|
928
1047
|
};
|
|
929
1048
|
}
|
|
1049
|
+
function toToolStartArgs(args) {
|
|
1050
|
+
return args && typeof args === "object" && !Array.isArray(args) ? args : { value: args };
|
|
1051
|
+
}
|
|
930
1052
|
function agentToolResultToSdk(result) {
|
|
931
1053
|
const content = result?.content;
|
|
932
1054
|
if (content == null) return createSuccessResult("");
|
|
@@ -1202,12 +1324,135 @@ function readResolvedWorkspacePath(value) {
|
|
|
1202
1324
|
//#endregion
|
|
1203
1325
|
//#region extensions/copilot/src/attempt.ts
|
|
1204
1326
|
const SUPPORTED_PROVIDERS = new Set(["github-copilot"]);
|
|
1327
|
+
const BACKGROUND_COMPACTION_CANCEL_TIMEOUT_MS = 5e3;
|
|
1328
|
+
async function runCopilotAgentEndHook(params, hookParams) {
|
|
1329
|
+
if (!params.messageChannel && !params.messageProvider) {
|
|
1330
|
+
await awaitAgentEndSideEffects(hookParams);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
runAgentEndSideEffects(hookParams);
|
|
1334
|
+
}
|
|
1335
|
+
async function finalizeCopilotAttempt(params, result, ctx, attemptStartedAt, now) {
|
|
1336
|
+
await runCopilotAgentEndHook(params, {
|
|
1337
|
+
event: {
|
|
1338
|
+
messages: result.messagesSnapshot,
|
|
1339
|
+
success: !result.aborted && !result.promptError && !result.timedOut,
|
|
1340
|
+
...result.promptError ? { error: toError(result.promptError).message } : result.timedOut ? { error: "Copilot SDK turn timed out." } : {},
|
|
1341
|
+
durationMs: now() - attemptStartedAt
|
|
1342
|
+
},
|
|
1343
|
+
ctx
|
|
1344
|
+
});
|
|
1345
|
+
return result;
|
|
1346
|
+
}
|
|
1347
|
+
async function awaitDeferredCleanupCompletionOrAbort(params) {
|
|
1348
|
+
const awaitCompletion = async () => {
|
|
1349
|
+
if (params.awaitSessionIdle) await params.bridge.awaitSessionIdle();
|
|
1350
|
+
await params.bridge.awaitCompactionCompletion();
|
|
1351
|
+
};
|
|
1352
|
+
if (!params.abortSignal) {
|
|
1353
|
+
await awaitCompletion();
|
|
1354
|
+
return "completed";
|
|
1355
|
+
}
|
|
1356
|
+
if (params.abortSignal.aborted) return "aborted";
|
|
1357
|
+
let resolveAbort = () => void 0;
|
|
1358
|
+
const aborted = new Promise((resolve) => {
|
|
1359
|
+
resolveAbort = () => resolve("aborted");
|
|
1360
|
+
});
|
|
1361
|
+
params.abortSignal.addEventListener("abort", resolveAbort, { once: true });
|
|
1362
|
+
try {
|
|
1363
|
+
return await Promise.race([awaitCompletion().then(() => "completed"), aborted]);
|
|
1364
|
+
} finally {
|
|
1365
|
+
params.abortSignal.removeEventListener("abort", resolveAbort);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function deferBackgroundCompactionCleanup(params) {
|
|
1369
|
+
return (async () => {
|
|
1370
|
+
let outcome = "deadline";
|
|
1371
|
+
try {
|
|
1372
|
+
outcome = await awaitDeferredCleanupBeforeDeadline({
|
|
1373
|
+
abortSignal: params.abortSignal,
|
|
1374
|
+
awaitSessionIdle: params.awaitSessionIdle,
|
|
1375
|
+
bridge: params.bridge,
|
|
1376
|
+
timeoutMs: params.timeoutMs
|
|
1377
|
+
});
|
|
1378
|
+
} catch {} finally {
|
|
1379
|
+
if (outcome !== "completed") {
|
|
1380
|
+
await cancelBackgroundCompactionBeforeTeardown(params.session);
|
|
1381
|
+
params.bridge.settleCompactionWait();
|
|
1382
|
+
}
|
|
1383
|
+
params.bridge.detach();
|
|
1384
|
+
try {
|
|
1385
|
+
await params.session.disconnect();
|
|
1386
|
+
} catch {}
|
|
1387
|
+
if (outcome !== "completed" && params.sdkSessionId) try {
|
|
1388
|
+
await params.handle.client.deleteSession(params.sdkSessionId);
|
|
1389
|
+
} catch {}
|
|
1390
|
+
try {
|
|
1391
|
+
await params.pool.release(params.handle);
|
|
1392
|
+
} catch {}
|
|
1393
|
+
}
|
|
1394
|
+
return outcome;
|
|
1395
|
+
})();
|
|
1396
|
+
}
|
|
1397
|
+
async function cancelBackgroundCompactionBeforeTeardown(session) {
|
|
1398
|
+
const cancelBackgroundCompaction = session.rpc?.history?.cancelBackgroundCompaction;
|
|
1399
|
+
if (!cancelBackgroundCompaction) return;
|
|
1400
|
+
let timeoutId;
|
|
1401
|
+
const deadline = new Promise((resolve) => {
|
|
1402
|
+
timeoutId = setTimeout(resolve, BACKGROUND_COMPACTION_CANCEL_TIMEOUT_MS);
|
|
1403
|
+
});
|
|
1404
|
+
try {
|
|
1405
|
+
await Promise.race([Promise.resolve().then(() => cancelBackgroundCompaction()).catch(() => void 0), deadline]);
|
|
1406
|
+
} finally {
|
|
1407
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
async function awaitDeferredCleanupBeforeDeadline(params) {
|
|
1411
|
+
let timeoutId;
|
|
1412
|
+
const deadline = new Promise((resolve) => {
|
|
1413
|
+
timeoutId = setTimeout(() => resolve("deadline"), params.timeoutMs);
|
|
1414
|
+
});
|
|
1415
|
+
try {
|
|
1416
|
+
return await Promise.race([awaitDeferredCleanupCompletionOrAbort(params), deadline]);
|
|
1417
|
+
} finally {
|
|
1418
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1205
1421
|
async function runCopilotAttempt(params, deps) {
|
|
1206
1422
|
const now = deps.now ?? Date.now;
|
|
1423
|
+
const attemptStartedAt = now();
|
|
1207
1424
|
const input = params;
|
|
1208
1425
|
const createToolBridge = deps.createToolBridge ?? createCopilotToolBridge;
|
|
1209
1426
|
const messages = getMessagesSnapshotInput(input);
|
|
1210
|
-
|
|
1427
|
+
const modelRef = resolveModelRef(input);
|
|
1428
|
+
const resolvedWorkspaceForSandbox = readResolvedAttemptPath(input.workspaceDir) ?? readResolvedAttemptPath(input.cwd);
|
|
1429
|
+
const sandboxSessionKey = readString(input.sandboxSessionKey) ?? readString(input.sessionKey) ?? readString(input.sessionId);
|
|
1430
|
+
const { sessionAgentId } = resolveSessionAgentIds({
|
|
1431
|
+
sessionKey: readString(input.sessionKey),
|
|
1432
|
+
config: input.config,
|
|
1433
|
+
agentId: readString(params.agentId)
|
|
1434
|
+
});
|
|
1435
|
+
const hookContextWindowFields = {
|
|
1436
|
+
...input.contextWindowInfo?.tokens ? { contextTokenBudget: input.contextWindowInfo.tokens } : input.contextTokenBudget ? { contextTokenBudget: input.contextTokenBudget } : {},
|
|
1437
|
+
...input.contextWindowInfo?.source ? { contextWindowSource: input.contextWindowInfo.source } : {},
|
|
1438
|
+
...input.contextWindowInfo?.referenceTokens ? { contextWindowReferenceTokens: input.contextWindowInfo.referenceTokens } : {}
|
|
1439
|
+
};
|
|
1440
|
+
const hookContext = {
|
|
1441
|
+
runId: input.runId,
|
|
1442
|
+
jobId: input.jobId,
|
|
1443
|
+
agentId: sessionAgentId,
|
|
1444
|
+
sessionKey: sandboxSessionKey,
|
|
1445
|
+
sessionId: input.sessionId,
|
|
1446
|
+
workspaceDir: resolvedWorkspaceForSandbox,
|
|
1447
|
+
modelProviderId: modelRef.provider,
|
|
1448
|
+
modelId: modelRef.id,
|
|
1449
|
+
trigger: input.trigger,
|
|
1450
|
+
...input.config ? { config: input.config } : {},
|
|
1451
|
+
...hookContextWindowFields,
|
|
1452
|
+
...buildAgentHookContextChannelFields(input)
|
|
1453
|
+
};
|
|
1454
|
+
const finishAttempt = (result) => finalizeCopilotAttempt(input, result, hookContext, attemptStartedAt, now);
|
|
1455
|
+
if (params.abortSignal?.aborted) return finishAttempt(createResult(input, {
|
|
1211
1456
|
aborted: true,
|
|
1212
1457
|
externalAbort: true,
|
|
1213
1458
|
messagesSnapshot: messages,
|
|
@@ -1215,20 +1460,20 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1215
1460
|
promptError: void 0,
|
|
1216
1461
|
sdkSessionId: void 0,
|
|
1217
1462
|
sessionIdUsed: input.sessionId
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
if (!SUPPORTED_PROVIDERS.has(modelRef.provider)) return createResult(input, {
|
|
1463
|
+
}));
|
|
1464
|
+
if (!SUPPORTED_PROVIDERS.has(modelRef.provider)) return finishAttempt(createResult(input, {
|
|
1221
1465
|
messagesSnapshot: messages,
|
|
1222
1466
|
now,
|
|
1223
1467
|
promptError: createPromptError("model_not_supported", `[copilot-attempt] provider ${modelRef.provider} is not supported at MVP (subscription Copilot models only; BYOK arrives via byok-mapping-skeleton)`),
|
|
1224
1468
|
sdkSessionId: void 0,
|
|
1225
1469
|
sessionIdUsed: input.sessionId
|
|
1226
|
-
});
|
|
1470
|
+
}));
|
|
1227
1471
|
let abortRequested = false;
|
|
1228
1472
|
let aborted = false;
|
|
1229
1473
|
let externalAbort = false;
|
|
1230
1474
|
let settled = false;
|
|
1231
1475
|
let sentTurnStarted = false;
|
|
1476
|
+
let timedOutDuringCompaction = false;
|
|
1232
1477
|
let timedOut = false;
|
|
1233
1478
|
let promptError;
|
|
1234
1479
|
let sdkSessionId;
|
|
@@ -1249,8 +1494,6 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1249
1494
|
session.abort().catch(() => void 0);
|
|
1250
1495
|
};
|
|
1251
1496
|
params.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1252
|
-
const resolvedWorkspaceForSandbox = readResolvedAttemptPath(input.workspaceDir) ?? readResolvedAttemptPath(input.cwd);
|
|
1253
|
-
const sandboxSessionKey = readString(input.sandboxSessionKey) ?? readString(input.sessionKey) ?? readString(input.sessionId);
|
|
1254
1497
|
const resolveSandbox = deps.resolveSandboxContextOverride ?? resolveSandboxContext;
|
|
1255
1498
|
let sandbox = null;
|
|
1256
1499
|
let effectiveWorkspaceDir = resolvedWorkspaceForSandbox;
|
|
@@ -1265,7 +1508,7 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1265
1508
|
} catch (error) {
|
|
1266
1509
|
settled = true;
|
|
1267
1510
|
params.abortSignal?.removeEventListener("abort", onAbort);
|
|
1268
|
-
if (abortRequested || params.abortSignal?.aborted) return createResult(input, {
|
|
1511
|
+
if (abortRequested || params.abortSignal?.aborted) return finishAttempt(createResult(input, {
|
|
1269
1512
|
aborted: true,
|
|
1270
1513
|
externalAbort: true,
|
|
1271
1514
|
messagesSnapshot: messages,
|
|
@@ -1273,33 +1516,29 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1273
1516
|
promptError: void 0,
|
|
1274
1517
|
sdkSessionId: void 0,
|
|
1275
1518
|
sessionIdUsed: input.sessionId
|
|
1276
|
-
});
|
|
1277
|
-
return createResult(input, {
|
|
1519
|
+
}));
|
|
1520
|
+
return finishAttempt(createResult(input, {
|
|
1278
1521
|
messagesSnapshot: messages,
|
|
1279
1522
|
now,
|
|
1280
1523
|
promptError: createPromptError("sandbox_resolution_failure", `[copilot-attempt] sandbox resolution failed: ${toError(error).message}`, error),
|
|
1281
1524
|
sdkSessionId: void 0,
|
|
1282
1525
|
sessionIdUsed: input.sessionId
|
|
1283
|
-
});
|
|
1526
|
+
}));
|
|
1284
1527
|
}
|
|
1528
|
+
hookContext.workspaceDir = effectiveWorkspaceDir;
|
|
1285
1529
|
const requestedCwd = readResolvedAttemptPath(input.cwd);
|
|
1286
1530
|
if (sandbox?.enabled && requestedCwd && requestedCwd !== resolvedWorkspaceForSandbox) {
|
|
1287
1531
|
settled = true;
|
|
1288
1532
|
params.abortSignal?.removeEventListener("abort", onAbort);
|
|
1289
|
-
return createResult(input, {
|
|
1533
|
+
return finishAttempt(createResult(input, {
|
|
1290
1534
|
messagesSnapshot: messages,
|
|
1291
1535
|
now,
|
|
1292
1536
|
promptError: createPromptError("sandbox_cwd_override_unsupported", "[copilot-attempt] cwd override is not supported for sandboxed Copilot runs; omit cwd or use the agent workspace as cwd"),
|
|
1293
1537
|
sdkSessionId: void 0,
|
|
1294
1538
|
sessionIdUsed: input.sessionId
|
|
1295
|
-
});
|
|
1539
|
+
}));
|
|
1296
1540
|
}
|
|
1297
1541
|
const effectiveCwd = sandbox?.enabled ? effectiveWorkspaceDir : requestedCwd ?? effectiveWorkspaceDir;
|
|
1298
|
-
const { sessionAgentId } = resolveSessionAgentIds({
|
|
1299
|
-
sessionKey: readString(input.sessionKey),
|
|
1300
|
-
config: input.config,
|
|
1301
|
-
agentId: readString(params.agentId)
|
|
1302
|
-
});
|
|
1303
1542
|
const effectiveFsWorkspaceOnly = resolveAttemptFsWorkspaceOnly({
|
|
1304
1543
|
config: input.config,
|
|
1305
1544
|
sessionAgentId
|
|
@@ -1329,25 +1568,70 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1329
1568
|
sessionRef,
|
|
1330
1569
|
onYieldDetected: () => {
|
|
1331
1570
|
yieldDetected = true;
|
|
1332
|
-
}
|
|
1571
|
+
},
|
|
1572
|
+
onToolCompleted: ({ args, error, result, startedAt, toolCallId, toolName }) => runAgentHarnessAfterToolCallHook({
|
|
1573
|
+
toolName,
|
|
1574
|
+
toolCallId,
|
|
1575
|
+
runId: input.runId,
|
|
1576
|
+
agentId: sessionAgentId,
|
|
1577
|
+
sessionId: input.sessionId,
|
|
1578
|
+
sessionKey: sandboxSessionKey,
|
|
1579
|
+
channelId: hookContext.channelId,
|
|
1580
|
+
startArgs: args,
|
|
1581
|
+
...result !== void 0 ? { result } : {},
|
|
1582
|
+
...error ? { error } : {},
|
|
1583
|
+
startedAt
|
|
1584
|
+
})
|
|
1333
1585
|
})).sdkTools;
|
|
1334
1586
|
} catch (error) {
|
|
1335
|
-
return createResult(input, {
|
|
1587
|
+
return finishAttempt(createResult(input, {
|
|
1336
1588
|
messagesSnapshot: messages,
|
|
1337
1589
|
now,
|
|
1338
1590
|
promptError: createPromptError("tool_bridge_failure", `[copilot-attempt] tool-bridge construction failed: ${toError(error).message}`, error),
|
|
1339
1591
|
sdkSessionId: void 0,
|
|
1340
1592
|
sessionIdUsed: input.sessionId
|
|
1341
|
-
});
|
|
1593
|
+
}));
|
|
1342
1594
|
}
|
|
1343
1595
|
handle = await deps.pool.acquire(poolAcquire.key, poolAcquire.options);
|
|
1344
1596
|
const client = handle.client;
|
|
1345
|
-
const
|
|
1597
|
+
const originalDeveloperInstructions = createSystemMessageContent(input, (await resolveCopilotWorkspaceBootstrapContext({
|
|
1346
1598
|
attempt: input,
|
|
1347
1599
|
effectiveWorkspaceDir,
|
|
1348
1600
|
warn: (message) => console.warn(message)
|
|
1601
|
+
})).instructions) ?? "";
|
|
1602
|
+
const promptBuild = isRawCopilotModelRun(input) ? {
|
|
1603
|
+
prompt: input.prompt,
|
|
1604
|
+
developerInstructions: originalDeveloperInstructions
|
|
1605
|
+
} : await resolveAgentHarnessBeforePromptBuildResult({
|
|
1606
|
+
prompt: input.prompt,
|
|
1607
|
+
developerInstructions: originalDeveloperInstructions,
|
|
1608
|
+
messages,
|
|
1609
|
+
ctx: hookContext,
|
|
1610
|
+
..."beforeAgentStartResult" in input ? { beforeAgentStartResult: input.beforeAgentStartResult } : {}
|
|
1349
1611
|
});
|
|
1350
|
-
const
|
|
1612
|
+
const attemptInput = promptBuild.prompt === input.prompt ? input : {
|
|
1613
|
+
...input,
|
|
1614
|
+
prompt: promptBuild.prompt
|
|
1615
|
+
};
|
|
1616
|
+
let promptImagesCount = 0;
|
|
1617
|
+
const emitLlmInput = (prompt, additionalContext) => {
|
|
1618
|
+
runAgentHarnessLlmInputHook({
|
|
1619
|
+
event: {
|
|
1620
|
+
runId: input.runId,
|
|
1621
|
+
sessionId: input.sessionId,
|
|
1622
|
+
provider: modelRef.provider,
|
|
1623
|
+
model: modelRef.id,
|
|
1624
|
+
...promptBuild.developerInstructions ? { systemPrompt: promptBuild.developerInstructions } : {},
|
|
1625
|
+
prompt: additionalContext ? `${prompt}\n\n${additionalContext}` : prompt,
|
|
1626
|
+
historyMessages: [],
|
|
1627
|
+
imagesCount: promptImagesCount,
|
|
1628
|
+
tools: sdkTools
|
|
1629
|
+
},
|
|
1630
|
+
ctx: hookContext
|
|
1631
|
+
});
|
|
1632
|
+
};
|
|
1633
|
+
const hasNativePromptHook = Boolean(attemptInput.hooksConfig?.onUserPromptSubmitted);
|
|
1634
|
+
const sessionConfig = createSessionConfig(attemptInput, modelRef.id, sdkTools, poolAcquire.auth, promptBuild.developerInstructions || void 0, effectiveWorkspaceDir, effectiveCwd, hasNativePromptHook ? { onUserPromptSubmitted: ({ additionalContext, prompt }) => emitLlmInput(prompt, additionalContext) } : void 0);
|
|
1351
1635
|
const replayDecision = decideReplayAction({
|
|
1352
1636
|
sdkSessionId: input.initialReplayState?.sdkSessionId,
|
|
1353
1637
|
replayInvalid: input.initialReplayState?.replayInvalid
|
|
@@ -1377,52 +1661,103 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1377
1661
|
} catch {}
|
|
1378
1662
|
bridge = attachEventBridge(session, {
|
|
1379
1663
|
onAssistantDelta: input.onAssistantDelta,
|
|
1664
|
+
onCompactionStart: async () => {
|
|
1665
|
+
const sessionFile = readString(input.sessionFile);
|
|
1666
|
+
if (!sessionFile) return;
|
|
1667
|
+
await runAgentHarnessBeforeCompactionHook({
|
|
1668
|
+
sessionFile,
|
|
1669
|
+
ctx: hookContext
|
|
1670
|
+
});
|
|
1671
|
+
},
|
|
1672
|
+
onCompactionComplete: async ({ messagesRemoved, success }) => {
|
|
1673
|
+
const sessionFile = readString(input.sessionFile);
|
|
1674
|
+
if (!success || !sessionFile) return;
|
|
1675
|
+
await runAgentHarnessAfterCompactionHook({
|
|
1676
|
+
sessionFile,
|
|
1677
|
+
compactedCount: messagesRemoved ?? -1,
|
|
1678
|
+
ctx: hookContext
|
|
1679
|
+
});
|
|
1680
|
+
},
|
|
1380
1681
|
getSdkSessionId: () => sdkSessionId,
|
|
1381
1682
|
isAborted: () => aborted
|
|
1382
1683
|
});
|
|
1383
|
-
const messageOptions = await createMessageOptions(
|
|
1684
|
+
const messageOptions = await createMessageOptions(attemptInput, {
|
|
1384
1685
|
effectiveCwd,
|
|
1385
1686
|
effectiveWorkspaceDir,
|
|
1386
1687
|
sandbox,
|
|
1387
1688
|
workspaceOnly: effectiveFsWorkspaceOnly
|
|
1388
1689
|
});
|
|
1690
|
+
promptImagesCount = messageOptions.attachments?.length ?? 0;
|
|
1389
1691
|
if (abortRequested || params.abortSignal?.aborted) {
|
|
1390
1692
|
aborted = true;
|
|
1391
1693
|
externalAbort = true;
|
|
1392
1694
|
} else {
|
|
1393
1695
|
sentTurnStarted = true;
|
|
1696
|
+
if (!hasNativePromptHook) emitLlmInput(attemptInput.prompt);
|
|
1394
1697
|
const result = await session.sendAndWait(messageOptions, input.timeoutMs);
|
|
1395
1698
|
await bridge.awaitDeltaChain();
|
|
1396
|
-
if (!bridge.recordSendResult(result) && !aborted)
|
|
1699
|
+
if (!bridge.recordSendResult(result) && !aborted) {
|
|
1700
|
+
timedOut = true;
|
|
1701
|
+
timedOutDuringCompaction = bridge.isCompacting();
|
|
1702
|
+
}
|
|
1397
1703
|
const snap = bridge.snapshot();
|
|
1398
1704
|
if (!promptError && !timedOut && !aborted && snap.streamError) promptError = snap.streamError;
|
|
1399
1705
|
}
|
|
1400
1706
|
} catch (error) {
|
|
1401
1707
|
if (!aborted) if (isSdkSendAndWaitTimeoutError(error)) {
|
|
1402
1708
|
timedOut = true;
|
|
1709
|
+
timedOutDuringCompaction = bridge?.isCompacting() === true;
|
|
1403
1710
|
try {
|
|
1404
1711
|
await bridge?.awaitDeltaChain();
|
|
1405
1712
|
} catch {}
|
|
1406
1713
|
} else promptError = toError(error);
|
|
1407
1714
|
} finally {
|
|
1408
1715
|
settled = true;
|
|
1409
|
-
bridge?.
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1716
|
+
if ((bridge?.hasObservedCompaction() || timedOut && bridge?.hasObservedSessionIdle() === false) && bridge && session && handle) {
|
|
1717
|
+
const cleanupAbort = new AbortController();
|
|
1718
|
+
const abortCleanup = () => cleanupAbort.abort();
|
|
1719
|
+
if (params.abortSignal?.aborted) abortCleanup();
|
|
1720
|
+
else params.abortSignal?.addEventListener("abort", abortCleanup, { once: true });
|
|
1721
|
+
const cleanup = deferBackgroundCompactionCleanup({
|
|
1722
|
+
abortSignal: cleanupAbort.signal,
|
|
1723
|
+
awaitSessionIdle: !bridge.hasObservedSessionIdle(),
|
|
1724
|
+
bridge,
|
|
1725
|
+
handle,
|
|
1726
|
+
pool: deps.pool,
|
|
1727
|
+
sdkSessionId,
|
|
1728
|
+
session,
|
|
1729
|
+
timeoutMs: resolveCompactionTimeoutMs(input.config)
|
|
1730
|
+
});
|
|
1731
|
+
cleanup.finally(() => {
|
|
1732
|
+
params.abortSignal?.removeEventListener("abort", abortCleanup);
|
|
1733
|
+
}).catch(() => void 0);
|
|
1734
|
+
if (sdkSessionId) try {
|
|
1735
|
+
deps.onDeferredCompaction?.({
|
|
1736
|
+
abort: () => cleanupAbort.abort(),
|
|
1737
|
+
cleanup,
|
|
1738
|
+
sdkSessionId
|
|
1739
|
+
});
|
|
1740
|
+
} catch {}
|
|
1741
|
+
params.abortSignal?.removeEventListener("abort", onAbort);
|
|
1742
|
+
} else {
|
|
1743
|
+
await bridge?.awaitCompactionChain();
|
|
1744
|
+
bridge?.detach();
|
|
1745
|
+
params.abortSignal?.removeEventListener("abort", onAbort);
|
|
1746
|
+
if (session) try {
|
|
1747
|
+
await session.disconnect();
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
disconnectError = toError(error);
|
|
1750
|
+
if (!promptError && !timedOut) promptError = disconnectError;
|
|
1751
|
+
}
|
|
1752
|
+
if (handle) try {
|
|
1753
|
+
await deps.pool.release(handle);
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
const releaseFailure = toError(error);
|
|
1756
|
+
if (promptError) console.warn("[copilot-attempt] pool.release failed after primary error", releaseFailure);
|
|
1757
|
+
else releaseError = releaseFailure;
|
|
1758
|
+
}
|
|
1423
1759
|
}
|
|
1424
1760
|
}
|
|
1425
|
-
if (releaseError) throw releaseError;
|
|
1426
1761
|
const snap = bridge?.snapshot();
|
|
1427
1762
|
const assistantTexts = bridge?.finalizeAssistantTexts() ?? [];
|
|
1428
1763
|
const lastAssistant = bridge?.buildAssistantMessage({
|
|
@@ -1461,7 +1796,7 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1461
1796
|
console.warn("[copilot-attempt] dual-write transcript wrapper rejected unexpectedly", mirrorError);
|
|
1462
1797
|
});
|
|
1463
1798
|
}
|
|
1464
|
-
|
|
1799
|
+
const result = createResult(input, {
|
|
1465
1800
|
aborted,
|
|
1466
1801
|
assistantTexts,
|
|
1467
1802
|
currentAttemptAssistant: lastAssistant,
|
|
@@ -1480,10 +1815,35 @@ async function runCopilotAttempt(params, deps) {
|
|
|
1480
1815
|
sdkSessionId,
|
|
1481
1816
|
sessionIdUsed,
|
|
1482
1817
|
timedOut,
|
|
1818
|
+
timedOutDuringCompaction,
|
|
1483
1819
|
toolMetas: snap ? [...snap.toolMetas] : [],
|
|
1484
1820
|
usage: snap?.usage,
|
|
1485
1821
|
yieldDetected
|
|
1486
1822
|
});
|
|
1823
|
+
if (sentTurnStarted) runAgentHarnessLlmOutputHook({
|
|
1824
|
+
event: {
|
|
1825
|
+
runId: input.runId,
|
|
1826
|
+
sessionId: input.sessionId,
|
|
1827
|
+
provider: modelRef.provider,
|
|
1828
|
+
model: modelRef.id,
|
|
1829
|
+
...hookContextWindowFields,
|
|
1830
|
+
resolvedRef: input.runtimePlan?.observability.resolvedRef ?? `${modelRef.provider}/${modelRef.id}`,
|
|
1831
|
+
...input.runtimePlan?.observability.harnessId ? { harnessId: input.runtimePlan.observability.harnessId } : {},
|
|
1832
|
+
assistantTexts: result.assistantTexts,
|
|
1833
|
+
...result.lastAssistant ? { lastAssistant: result.lastAssistant } : {},
|
|
1834
|
+
...result.attemptUsage ? { usage: result.attemptUsage } : {},
|
|
1835
|
+
...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}
|
|
1836
|
+
},
|
|
1837
|
+
ctx: hookContext
|
|
1838
|
+
});
|
|
1839
|
+
if (releaseError) {
|
|
1840
|
+
await finalizeCopilotAttempt(input, {
|
|
1841
|
+
...result,
|
|
1842
|
+
promptError: releaseError
|
|
1843
|
+
}, hookContext, attemptStartedAt, now);
|
|
1844
|
+
throw releaseError;
|
|
1845
|
+
}
|
|
1846
|
+
return finishAttempt(result);
|
|
1487
1847
|
}
|
|
1488
1848
|
function createResult(params, state) {
|
|
1489
1849
|
const promptError = state.promptError;
|
|
@@ -1523,7 +1883,7 @@ function createResult(params, state) {
|
|
|
1523
1883
|
sessionFileUsed: readString(params.sessionFile),
|
|
1524
1884
|
sessionIdUsed: state.sessionIdUsed ?? readString(params.sessionId) ?? "copilot-session",
|
|
1525
1885
|
timedOut,
|
|
1526
|
-
timedOutDuringCompaction:
|
|
1886
|
+
timedOutDuringCompaction: state.timedOutDuringCompaction === true,
|
|
1527
1887
|
toolMetas,
|
|
1528
1888
|
yieldDetected: state.yieldDetected === true
|
|
1529
1889
|
};
|
|
@@ -1534,11 +1894,10 @@ function createPromptError(code, message, cause) {
|
|
|
1534
1894
|
if (cause !== void 0) error.cause = cause;
|
|
1535
1895
|
return error;
|
|
1536
1896
|
}
|
|
1537
|
-
function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth,
|
|
1897
|
+
function createSessionConfig(params, sdkModelId, sdkTools, resolvedAuth, systemMessageContent, effectiveWorkspaceDir, effectiveCwd, hooksBridgeOptions) {
|
|
1538
1898
|
const permissionPolicy = params.permissionPolicy ?? rejectAllPolicy;
|
|
1539
|
-
const hooks = createHooksBridge(params.hooksConfig);
|
|
1899
|
+
const hooks = createHooksBridge(params.hooksConfig, hooksBridgeOptions);
|
|
1540
1900
|
const infiniteSessions = createInfiniteSessionConfig(params.infiniteSessionConfig);
|
|
1541
|
-
const systemMessageContent = createSystemMessageContent(params, workspaceBootstrapInstructions);
|
|
1542
1901
|
return {
|
|
1543
1902
|
model: sdkModelId,
|
|
1544
1903
|
onPermissionRequest: createPermissionBridge(permissionPolicy),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { compactWithSafetyTimeout, resolveCompactionTimeoutMs } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
1
|
+
import { buildAgentHookContextChannelFields, compactWithSafetyTimeout, resolveCompactionTimeoutMs, runAgentHarnessAfterCompactionHook, runAgentHarnessBeforeCompactionHook } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
@@ -341,23 +341,65 @@ function computeSessionCompactKey(params) {
|
|
|
341
341
|
includeAuth: false
|
|
342
342
|
});
|
|
343
343
|
}
|
|
344
|
+
function buildCopilotCompactionHookContext(params) {
|
|
345
|
+
return {
|
|
346
|
+
...params.runId ? { runId: params.runId } : {},
|
|
347
|
+
agentId: params.agentId,
|
|
348
|
+
sessionKey: params.sessionKey,
|
|
349
|
+
sessionId: params.sessionId,
|
|
350
|
+
workspaceDir: params.workspaceDir,
|
|
351
|
+
modelProviderId: params.provider,
|
|
352
|
+
modelId: params.model,
|
|
353
|
+
trigger: params.trigger,
|
|
354
|
+
...buildAgentHookContextChannelFields(params)
|
|
355
|
+
};
|
|
356
|
+
}
|
|
344
357
|
function createCopilotAgentHarness(options) {
|
|
345
358
|
let poolPromise;
|
|
346
359
|
let createdPool;
|
|
347
360
|
let disposed = false;
|
|
348
361
|
let disposePromise;
|
|
349
362
|
const inFlight = /* @__PURE__ */ new Set();
|
|
363
|
+
const deferredCompactionCleanups = /* @__PURE__ */ new Map();
|
|
350
364
|
const trackedSessions = /* @__PURE__ */ new Map();
|
|
351
365
|
const resetBlockedStoredSessions = /* @__PURE__ */ new Set();
|
|
352
366
|
async function getPool() {
|
|
353
367
|
if (options?.pool) return options.pool;
|
|
354
368
|
if (!poolPromise) poolPromise = (async () => {
|
|
355
|
-
const { createCopilotClientPool } = await import("./runtime-
|
|
369
|
+
const { createCopilotClientPool } = await import("./runtime-Bo_ajDXI.js");
|
|
356
370
|
createdPool = createCopilotClientPool(options?.poolOptions);
|
|
357
371
|
return createdPool;
|
|
358
372
|
})();
|
|
359
373
|
return poolPromise;
|
|
360
374
|
}
|
|
375
|
+
function trackDeferredCompactionCleanup(params) {
|
|
376
|
+
const cleanups = deferredCompactionCleanups.get(params.sessionId) ?? /* @__PURE__ */ new Map();
|
|
377
|
+
cleanups.set(params.cleanup, {
|
|
378
|
+
abort: params.abort,
|
|
379
|
+
sdkSessionId: params.sdkSessionId
|
|
380
|
+
});
|
|
381
|
+
deferredCompactionCleanups.set(params.sessionId, cleanups);
|
|
382
|
+
params.cleanup.then(() => removeDeferredCompactionCleanup(params.sessionId, params.cleanup), () => removeDeferredCompactionCleanup(params.sessionId, params.cleanup));
|
|
383
|
+
}
|
|
384
|
+
function removeDeferredCompactionCleanup(sessionId, cleanup) {
|
|
385
|
+
const cleanups = deferredCompactionCleanups.get(sessionId);
|
|
386
|
+
if (!cleanups) return;
|
|
387
|
+
cleanups.delete(cleanup);
|
|
388
|
+
if (cleanups.size === 0) deferredCompactionCleanups.delete(sessionId);
|
|
389
|
+
}
|
|
390
|
+
function hasPendingDeferredCompactionCleanup(sessionId) {
|
|
391
|
+
const cleanups = deferredCompactionCleanups.get(sessionId);
|
|
392
|
+
if (!cleanups) return false;
|
|
393
|
+
const currentSdkSessionId = trackedSessions.get(sessionId)?.sdkSessionId ?? lookupStoredBinding(options?.sessionStore, sessionId)?.sdkSessionId;
|
|
394
|
+
return currentSdkSessionId !== void 0 && [...cleanups.values()].some((cleanup) => cleanup.sdkSessionId === currentSdkSessionId);
|
|
395
|
+
}
|
|
396
|
+
async function abortDeferredCompactionCleanups(sessionId) {
|
|
397
|
+
const cleanups = deferredCompactionCleanups.get(sessionId);
|
|
398
|
+
if (!cleanups) return;
|
|
399
|
+
const pending = [...cleanups.entries()];
|
|
400
|
+
for (const [, cleanup] of pending) cleanup.abort();
|
|
401
|
+
await Promise.allSettled(pending.map(([cleanup]) => cleanup));
|
|
402
|
+
}
|
|
361
403
|
return {
|
|
362
404
|
id: options?.id ?? "copilot",
|
|
363
405
|
label: options?.label ?? "GitHub Copilot agent runtime",
|
|
@@ -379,7 +421,7 @@ function createCopilotAgentHarness(options) {
|
|
|
379
421
|
async runAttempt(params) {
|
|
380
422
|
const attemptPromise = (async () => {
|
|
381
423
|
if (disposed) throw new Error("[copilot] harness has been disposed; cannot start new attempts");
|
|
382
|
-
const { resolvePoolAcquire, runCopilotAttempt } = await import("./attempt-
|
|
424
|
+
const { resolvePoolAcquire, runCopilotAttempt } = await import("./attempt-BQoxgpM3.js");
|
|
383
425
|
if (disposed) throw new Error("[copilot] harness was disposed while starting an attempt");
|
|
384
426
|
const poolAcquire = resolvePoolAcquire(params);
|
|
385
427
|
const pool = await getPool();
|
|
@@ -387,8 +429,10 @@ function createCopilotAgentHarness(options) {
|
|
|
387
429
|
const openclawSessionId = typeof params.sessionId === "string" ? params.sessionId : void 0;
|
|
388
430
|
const currentCompatKey = computeSessionCompatKey(params);
|
|
389
431
|
const currentCompactKey = computeSessionCompactKey(params);
|
|
390
|
-
const
|
|
391
|
-
const
|
|
432
|
+
const compactionCleanupPending = openclawSessionId !== void 0 && hasPendingDeferredCompactionCleanup(openclawSessionId);
|
|
433
|
+
const replayBlocked = openclawSessionId !== void 0 && (compactionCleanupPending || resetBlockedStoredSessions.has(openclawSessionId));
|
|
434
|
+
const tracked = openclawSessionId && !replayBlocked ? trackedSessions.get(openclawSessionId) : void 0;
|
|
435
|
+
const stored = openclawSessionId ? replayBlocked ? void 0 : lookupStoredBinding(options?.sessionStore, openclawSessionId) : void 0;
|
|
392
436
|
const resumableSessionId = tracked && tracked.compatKey === currentCompatKey ? tracked.sdkSessionId : !tracked && stored && stored.compatKey === currentCompatKey ? stored.sdkSessionId : void 0;
|
|
393
437
|
return runCopilotAttempt(resumableSessionId ? {
|
|
394
438
|
...params,
|
|
@@ -409,14 +453,42 @@ function createCopilotAgentHarness(options) {
|
|
|
409
453
|
sessionConfig,
|
|
410
454
|
...sessionAuthFields(poolAcquire.auth)
|
|
411
455
|
});
|
|
412
|
-
|
|
456
|
+
registerStoredBinding(options?.sessionStore, openclawSessionId, {
|
|
413
457
|
schemaVersion: 2,
|
|
414
458
|
sdkSessionId,
|
|
415
459
|
compatKey: currentCompatKey,
|
|
416
460
|
compactKey: currentCompactKey,
|
|
417
461
|
...sessionAuthFields(poolAcquire.auth),
|
|
418
462
|
updatedAt: Date.now()
|
|
419
|
-
})
|
|
463
|
+
});
|
|
464
|
+
resetBlockedStoredSessions.delete(openclawSessionId);
|
|
465
|
+
} : void 0,
|
|
466
|
+
onDeferredCompaction: openclawSessionId ? ({ abort, cleanup, sdkSessionId }) => {
|
|
467
|
+
const trackedBinding = trackedSessions.get(openclawSessionId);
|
|
468
|
+
const storedBinding = lookupStoredBinding(options?.sessionStore, openclawSessionId);
|
|
469
|
+
const ownsTrackedSession = trackedBinding?.sdkSessionId === sdkSessionId;
|
|
470
|
+
const ownsStoredSession = storedBinding?.sdkSessionId === sdkSessionId;
|
|
471
|
+
if (!ownsTrackedSession && !ownsStoredSession) return;
|
|
472
|
+
trackDeferredCompactionCleanup({
|
|
473
|
+
abort,
|
|
474
|
+
cleanup,
|
|
475
|
+
sessionId: openclawSessionId,
|
|
476
|
+
sdkSessionId
|
|
477
|
+
});
|
|
478
|
+
resetBlockedStoredSessions.add(openclawSessionId);
|
|
479
|
+
cleanup.then((outcome) => {
|
|
480
|
+
const currentTracked = trackedSessions.get(openclawSessionId);
|
|
481
|
+
const currentStored = lookupStoredBinding(options?.sessionStore, openclawSessionId);
|
|
482
|
+
const stillOwnsTrackedSession = currentTracked?.sdkSessionId === sdkSessionId;
|
|
483
|
+
const stillOwnsStoredSession = currentStored?.sdkSessionId === sdkSessionId;
|
|
484
|
+
if (outcome === "completed") {
|
|
485
|
+
if (stillOwnsTrackedSession || stillOwnsStoredSession) resetBlockedStoredSessions.delete(openclawSessionId);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (stillOwnsTrackedSession) trackedSessions.delete(openclawSessionId);
|
|
489
|
+
if (stillOwnsStoredSession) deleteStoredBinding(options?.sessionStore, openclawSessionId);
|
|
490
|
+
if (stillOwnsTrackedSession || stillOwnsStoredSession) resetBlockedStoredSessions.add(openclawSessionId);
|
|
491
|
+
});
|
|
420
492
|
} : void 0
|
|
421
493
|
});
|
|
422
494
|
})();
|
|
@@ -431,10 +503,15 @@ function createCopilotAgentHarness(options) {
|
|
|
431
503
|
const openclawSessionId = typeof params.sessionId === "string" ? params.sessionId : void 0;
|
|
432
504
|
if (!openclawSessionId) return;
|
|
433
505
|
const tracked = trackedSessions.get(openclawSessionId);
|
|
434
|
-
|
|
435
|
-
|
|
506
|
+
const stored = lookupStoredBinding(options?.sessionStore, openclawSessionId);
|
|
507
|
+
resetBlockedStoredSessions.add(openclawSessionId);
|
|
508
|
+
await abortDeferredCompactionCleanups(openclawSessionId);
|
|
509
|
+
const currentStored = lookupStoredBinding(options?.sessionStore, openclawSessionId);
|
|
510
|
+
if (stored !== void 0 && currentStored?.sdkSessionId === stored.sdkSessionId) {
|
|
511
|
+
if (deleteStoredBinding(options?.sessionStore, openclawSessionId)) resetBlockedStoredSessions.delete(openclawSessionId);
|
|
512
|
+
} else resetBlockedStoredSessions.delete(openclawSessionId);
|
|
436
513
|
if (!tracked) return;
|
|
437
|
-
trackedSessions.delete(openclawSessionId);
|
|
514
|
+
if (trackedSessions.get(openclawSessionId)?.sdkSessionId === tracked.sdkSessionId) trackedSessions.delete(openclawSessionId);
|
|
438
515
|
try {
|
|
439
516
|
await tracked.client.deleteSession(tracked.sdkSessionId);
|
|
440
517
|
} catch {}
|
|
@@ -446,9 +523,15 @@ function createCopilotAgentHarness(options) {
|
|
|
446
523
|
compacted: false,
|
|
447
524
|
reason: "missing-required-params"
|
|
448
525
|
};
|
|
526
|
+
if (hasPendingDeferredCompactionCleanup(openclawSessionId)) return {
|
|
527
|
+
ok: false,
|
|
528
|
+
compacted: false,
|
|
529
|
+
reason: "background-compaction-pending",
|
|
530
|
+
failure: { reason: "background-compaction-pending" }
|
|
531
|
+
};
|
|
449
532
|
const tracked = trackedSessions.get(openclawSessionId);
|
|
450
533
|
const currentCompactKey = computeSessionCompactKey(params);
|
|
451
|
-
const { resolvePoolAcquire } = await import("./attempt-
|
|
534
|
+
const { resolvePoolAcquire } = await import("./attempt-BQoxgpM3.js");
|
|
452
535
|
const resolvedPoolAcquire = resolvePoolAcquire(params);
|
|
453
536
|
const currentAuth = sessionAuthFields(resolvedPoolAcquire.auth);
|
|
454
537
|
const compatibleTracked = tracked?.compactKey === currentCompactKey && sessionAuthMatches(tracked, currentAuth) ? tracked : void 0;
|
|
@@ -466,11 +549,16 @@ function createCopilotAgentHarness(options) {
|
|
|
466
549
|
let handle;
|
|
467
550
|
let pool;
|
|
468
551
|
let activeSdkSession;
|
|
552
|
+
const hookContext = buildCopilotCompactionHookContext(params);
|
|
469
553
|
try {
|
|
470
554
|
throwIfAborted(params.abortSignal);
|
|
471
555
|
pool = await getPool();
|
|
472
556
|
handle = await pool.acquire(poolAcquire.key, poolAcquire.options);
|
|
473
557
|
const client = handle.client;
|
|
558
|
+
await runAgentHarnessBeforeCompactionHook({
|
|
559
|
+
sessionFile: params.sessionFile,
|
|
560
|
+
ctx: hookContext
|
|
561
|
+
});
|
|
474
562
|
compactResult = await compactWithSafetyTimeout((abortSignal) => compactTrackedSdkSession({
|
|
475
563
|
abortSignal,
|
|
476
564
|
client,
|
|
@@ -521,6 +609,11 @@ function createCopilotAgentHarness(options) {
|
|
|
521
609
|
failure: { reason: "copilot-sdk-history-compact-failed" }
|
|
522
610
|
};
|
|
523
611
|
const compacted = compactResult.tokensRemoved > 0 || compactResult.messagesRemoved > 0;
|
|
612
|
+
if (compacted) await runAgentHarnessAfterCompactionHook({
|
|
613
|
+
sessionFile: params.sessionFile,
|
|
614
|
+
compactedCount: compactResult.messagesRemoved,
|
|
615
|
+
ctx: hookContext
|
|
616
|
+
});
|
|
524
617
|
return {
|
|
525
618
|
ok: true,
|
|
526
619
|
compacted,
|
|
@@ -532,6 +625,8 @@ function createCopilotAgentHarness(options) {
|
|
|
532
625
|
disposed = true;
|
|
533
626
|
disposePromise = (async () => {
|
|
534
627
|
if (inFlight.size > 0) await Promise.allSettled(inFlight);
|
|
628
|
+
const cleanupSessionIds = [...deferredCompactionCleanups.keys()];
|
|
629
|
+
for (const sessionId of cleanupSessionIds) await abortDeferredCompactionCleanups(sessionId);
|
|
535
630
|
trackedSessions.clear();
|
|
536
631
|
resetBlockedStoredSessions.clear();
|
|
537
632
|
if (createdPool) {
|
package/dist/harness.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createCopilotAgentHarness } from "./harness-
|
|
1
|
+
import { t as createCopilotAgentHarness } from "./harness-I9ahzRYI.js";
|
|
2
2
|
export { createCopilotAgentHarness };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createCopilotAgentHarness } from "./harness-
|
|
1
|
+
import { t as createCopilotAgentHarness } from "./harness-I9ahzRYI.js";
|
|
2
2
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
3
|
//#region extensions/copilot/index.ts
|
|
4
4
|
function isRecord(value) {
|
|
@@ -7,7 +7,6 @@ import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
|
7
7
|
function resolveCopilotSdkFallbackDir(env = process.env) {
|
|
8
8
|
return path.join(resolveStateDir(env), "npm-runtime", "copilot");
|
|
9
9
|
}
|
|
10
|
-
resolveCopilotSdkFallbackDir();
|
|
11
10
|
const COPILOT_SDK_SPEC = "@github/copilot-sdk@1.0.0-beta.9";
|
|
12
11
|
let cached;
|
|
13
12
|
async function loadCopilotSdk(options = {}) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/copilot",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.10-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/copilot",
|
|
9
|
-
"version": "2026.6.
|
|
9
|
+
"version": "2026.6.10-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@github/copilot-sdk": "1.0.0-beta.9"
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/copilot",
|
|
3
|
-
"version": "2026.6.
|
|
3
|
+
"version": "2026.6.10-beta.1",
|
|
4
4
|
"description": "OpenClaw GitHub Copilot agent runtime plugin (registers a `github-copilot` AgentHarness backed by @github/copilot-sdk over JSON-RPC to the GitHub Copilot CLI)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"minHostVersion": ">=2026.5.28"
|
|
26
26
|
},
|
|
27
27
|
"compat": {
|
|
28
|
-
"pluginApi": ">=2026.6.
|
|
28
|
+
"pluginApi": ">=2026.6.10-beta.1"
|
|
29
29
|
},
|
|
30
30
|
"build": {
|
|
31
|
-
"openclawVersion": "2026.6.
|
|
31
|
+
"openclawVersion": "2026.6.10-beta.1",
|
|
32
32
|
"bundledDist": false
|
|
33
33
|
},
|
|
34
34
|
"release": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"README.md"
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"openclaw": ">=2026.6.
|
|
50
|
+
"openclaw": ">=2026.6.10-beta.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"openclaw": {
|