@openclaw/copilot 2026.6.9 → 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.
@@ -1,5 +1,5 @@
1
- import { n as resolveCopilotAuth } from "./harness-wpPHFcMD.js";
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 host handler in an error-isolating envelope so it cannot
429
- * throw out into the SDK. Returns `undefined` (no opinion) when the
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 `SessionHooks` object from a host-supplied
451
- * `CopilotHooksConfig`. Returns `undefined` when no handlers were
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 (userPrompt) hooks.onUserPromptSubmitted = userPrompt;
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
- if (Object.keys(hooks).length === 0) return;
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 failureResult = (message, error) => {
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: toError$1(error).message
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
- notifyToolResult(sanitizedResult, sdkResult.resultType === "failure" || isToolResultError(sanitizedResult));
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
- if (params.abortSignal?.aborted) return createResult(input, {
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
- const modelRef = resolveModelRef(input);
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 workspaceBootstrap = await resolveCopilotWorkspaceBootstrapContext({
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 sessionConfig = createSessionConfig(input, modelRef.id, sdkTools, poolAcquire.auth, workspaceBootstrap.instructions, effectiveWorkspaceDir, effectiveCwd);
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(input, {
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) timedOut = true;
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?.detach();
1410
- params.abortSignal?.removeEventListener("abort", onAbort);
1411
- if (session) try {
1412
- await session.disconnect();
1413
- } catch (error) {
1414
- disconnectError = toError(error);
1415
- if (!promptError && !timedOut) promptError = disconnectError;
1416
- }
1417
- if (handle) try {
1418
- await deps.pool.release(handle);
1419
- } catch (error) {
1420
- const releaseFailure = toError(error);
1421
- if (promptError) console.warn("[copilot-attempt] pool.release failed after primary error", releaseFailure);
1422
- else releaseError = releaseFailure;
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
- return createResult(input, {
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: false,
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, workspaceBootstrapInstructions, effectiveWorkspaceDir, effectiveCwd) {
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,12 +341,26 @@ 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() {
@@ -358,6 +372,34 @@ function createCopilotAgentHarness(options) {
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-DMjwK4f0.js");
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 tracked = openclawSessionId ? trackedSessions.get(openclawSessionId) : void 0;
391
- const stored = openclawSessionId ? resetBlockedStoredSessions.has(openclawSessionId) ? void 0 : lookupStoredBinding(options?.sessionStore, openclawSessionId) : void 0;
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
- if (registerStoredBinding(options?.sessionStore, openclawSessionId, {
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
- })) resetBlockedStoredSessions.delete(openclawSessionId);
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
- if (deleteStoredBinding(options?.sessionStore, openclawSessionId)) resetBlockedStoredSessions.delete(openclawSessionId);
435
- else resetBlockedStoredSessions.add(openclawSessionId);
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-DMjwK4f0.js");
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-wpPHFcMD.js";
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-wpPHFcMD.js";
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) {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/copilot",
3
- "version": "2026.6.9",
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",
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.9",
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.9"
28
+ "pluginApi": ">=2026.6.10-beta.1"
29
29
  },
30
30
  "build": {
31
- "openclawVersion": "2026.6.9",
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.9"
50
+ "openclaw": ">=2026.6.10-beta.1"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "openclaw": {