maka-agent 0.2.0-dev.24.20260907 → 0.2.0-dev.26.20260909

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.
Files changed (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -71,7 +71,8 @@ export class AiSdkBackend {
71
71
  messageProjection;
72
72
  providerTelemetry;
73
73
  resolvedProviderOptions;
74
- toolAvailabilityRuntime;
74
+ memoryTools;
75
+ applyPatchProfile;
75
76
  /** Bounds outstanding Code Mode cells on this backend. */
76
77
  codeCellAdmission = new AdmissionLimiter(MAX_ACTIVE_CODE_MODE_CELLS);
77
78
  /**
@@ -147,6 +148,7 @@ export class AiSdkBackend {
147
148
  beforeRunProviderDispatch: input.beforeRunProviderDispatch,
148
149
  });
149
150
  const applyPatchProfile = runtime.applyPatchProfile;
151
+ this.applyPatchProfile = applyPatchProfile;
150
152
  this.messageProjection = new AiSdkMessageProjection({
151
153
  modelAdapter: this.modelAdapter,
152
154
  applyPatchProfile,
@@ -168,7 +170,7 @@ export class AiSdkBackend {
168
170
  if (input.tools.some((tool) => tool.name === MEMORY_REMEMBER_TOOL_NAME || tool.name === MEMORY_EXTRACT_TOOL_NAME)) {
169
171
  throw new Error('Long-term Memory trigger tool names are reserved by Runtime');
170
172
  }
171
- const memoryTools = input.memoryExtraction
173
+ this.memoryTools = input.memoryExtraction
172
174
  ? buildMemoryExtractionTriggerTools({
173
175
  capabilities: input.memoryExtraction,
174
176
  snapshot: (trigger, context) => this.memorySourceSnapshot(trigger, context),
@@ -182,11 +184,17 @@ export class AiSdkBackend {
182
184
  : {}),
183
185
  })
184
186
  : [];
185
- const modelTools = routeApplyPatchTools(input.tools, applyPatchProfile);
186
- this.toolAvailabilityRuntime = new ToolAvailabilityRuntime(
187
- // The archive decoder is a runtime protocol tool, not a host binding:
188
- // this session's placeholders name it, so this session advertises it.
189
- bindToolResultArchiveDecoder([...modelTools, ...memoryTools], input.toolResultArchive), input.toolAvailability, buildInvalidMakaTool());
187
+ }
188
+ snapshotToolAvailability() {
189
+ const hostTools = Object.freeze([...(this.input.resolveTools?.() ?? this.input.tools)]);
190
+ if (hostTools.some((tool) => tool.name === MEMORY_REMEMBER_TOOL_NAME || tool.name === MEMORY_EXTRACT_TOOL_NAME)) {
191
+ throw new Error('Long-term Memory trigger tool names are reserved by Runtime');
192
+ }
193
+ const modelTools = routeApplyPatchTools(hostTools, this.applyPatchProfile);
194
+ return {
195
+ hostTools,
196
+ runtime: new ToolAvailabilityRuntime(bindToolResultArchiveDecoder([...modelTools, ...this.memoryTools], this.input.toolResultArchive), this.input.toolAvailability, buildInvalidMakaTool()),
197
+ };
190
198
  }
191
199
  memorySourceSnapshot(trigger, context) {
192
200
  if (trigger !== 'remember')
@@ -255,7 +263,7 @@ export class AiSdkBackend {
255
263
  messageProjection: this.messageProjection,
256
264
  providerTelemetry: this.providerTelemetry,
257
265
  compaction: this.compaction,
258
- toolAvailabilityRuntime: this.toolAvailabilityRuntime,
266
+ snapshotToolAvailability: () => this.snapshotToolAvailability(),
259
267
  codeCellAdmission: this.codeCellAdmission,
260
268
  resolvedProviderOptions: this.resolvedProviderOptions,
261
269
  session: this.turnSessionState,
@@ -37,7 +37,8 @@ import { hasActiveToolResultPruneDiagnosticPatch, hasBlockingReplayDiagnostics,
37
37
  import { RunTrace } from './run-trace.js';
38
38
  import { REQUEST_SANDBOX_BOUNDARY_TOOL_NAME, SANDBOX_BOUNDARY_DENIED_FOR_TURN, SANDBOX_BOUNDARY_FINALIZATION_PROMPT, } from './sandbox-boundary-tool.js';
39
39
  import { buildRuntimeEventModelReplayPlan, buildSteeringEnvelope, collectToolActivityTurnIds, compatibleProviderReasoningReplayEventIds, formatTextWithInlineRefs, steeringMessagesMissingFromBase, steeringModelMessage, } from './model-history.js';
40
- import { toolSchemaCharsForDiagnostics } from './request-shape.js';
40
+ import { toolSchemaCharsForDiagnostics, requestCompositionToolSchemas, stableHash, toolCatalogHash, } from './request-shape.js';
41
+ import { toolAvailabilityHash } from './tool-availability.js';
41
42
  import { renderSwarmModePrompt } from './swarm-mode.js';
42
43
  import { renderGraphModePrompt } from './graph-mode.js';
43
44
  import { modelUsesNativeOpenAiResponses } from './model-runtime.js';
@@ -800,8 +801,8 @@ export class AiSdkTurn {
800
801
  return;
801
802
  }
802
803
  // --- Build the provider-visible schema set. Tool execution stays in Runtime. ---
803
- // One immutable runtime owns the bound search catalog and cached index.
804
- // Mutable activation belongs to this turn.
804
+ // Each logical step freezes its own scoped catalog and search projection.
805
+ // Mutable activation belongs to this turn and follows contribution identity.
805
806
  const requiredOrchestrationTools = this.orchestration.mode === 'swarm'
806
807
  ? new Set([
807
808
  'agent_list',
@@ -825,11 +826,22 @@ export class AiSdkTurn {
825
826
  throw new Error(`Invalid tool mode: ${String(requestedToolMode)}`);
826
827
  }
827
828
  const toolMode = requestedToolMode;
828
- if (toolMode === 'code_mode' && this.deps.backend.tools.some((tool) => tool.name === 'exec')) {
829
- throw new Error('Tool name "exec" is reserved for Code Mode.');
830
- }
831
- const plan = projectToolModePlan(this.deps.toolAvailabilityRuntime.prepare(this.activeTools, requiredOrchestrationTools), toolMode, codeModeExecTool);
832
- const providerTools = plan.providerTools;
829
+ const snapshotStepTools = () => {
830
+ const snapshot = this.deps.snapshotToolAvailability();
831
+ if (toolMode === 'code_mode' && snapshot.hostTools.some((tool) => tool.name === 'exec')) {
832
+ throw new Error('Tool name "exec" is reserved for Code Mode.');
833
+ }
834
+ const plan = projectToolModePlan(snapshot.runtime.prepare(this.activeTools, requiredOrchestrationTools), toolMode, codeModeExecTool);
835
+ const modelTools = {};
836
+ for (const tool of plan.providerTools) {
837
+ modelTools[tool.name] = tool.providerTool
838
+ ? { kind: 'provider', providerTool: tool.providerTool }
839
+ : { kind: 'function', description: tool.description, inputSchema: tool.parameters };
840
+ }
841
+ toolRuntime.setGating(plan.gating);
842
+ return { plan, providerTools: plan.providerTools, modelTools };
843
+ };
844
+ let { plan, providerTools, modelTools } = snapshotStepTools();
833
845
  let activeToolResultPruneDiagnosticPatch = {};
834
846
  let midTurnCompactDiagnosticPatch;
835
847
  // Tool names the repair path matches a mis-cased call against — follows the
@@ -843,44 +855,8 @@ export class AiSdkTurn {
843
855
  : [...names];
844
856
  };
845
857
  const currentRepairToolNames = () => boundaryAwareToolNames(plan.currentRepairToolNames());
846
- if (plan.gating) {
847
- toolRuntime.setGating(plan.gating);
848
- }
849
- const modelTools = {};
850
- for (const t of providerTools) {
851
- modelTools[t.name] = t.providerTool
852
- ? { kind: 'provider', providerTool: t.providerTool }
853
- : {
854
- kind: 'function',
855
- description: t.description,
856
- inputSchema: t.parameters,
857
- };
858
- }
859
- // Resolve the stable Provider envelope before automatic Compaction freezes
860
- // its source. The same value is reused by the primary request; Memory does
861
- // not resolve or mutate Agent configuration after the checkpoint commits.
858
+ let resolvedSystemPrompt = { sourceRevisions: [] };
862
859
  let systemPrompt;
863
- try {
864
- systemPrompt = joinPromptFragments([
865
- await this.resolveSystemPrompt(),
866
- this.orchestration?.mode === 'swarm' ? renderSwarmModePrompt() : undefined,
867
- this.orchestration?.mode === 'graph' ? renderGraphModePrompt() : undefined,
868
- ]);
869
- }
870
- catch (err) {
871
- trace.modelStreamFailed(this.deps.modelAdapter.classifyError(err), err);
872
- queue.push(this.makeErrorEvent(turnId, err));
873
- queue.push({
874
- type: 'complete',
875
- id: this.deps.newId(),
876
- turnId,
877
- ts: this.deps.now(),
878
- stopReason: 'error',
879
- });
880
- queue.close();
881
- yield* this.drain(queue);
882
- return;
883
- }
884
860
  // --- Build messages from RuntimeEvent history and its compatibility projection. ---
885
861
  const priorReplayResult = await this.buildPriorMessages(input);
886
862
  if (this.aborted) {
@@ -1083,7 +1059,8 @@ export class AiSdkTurn {
1083
1059
  const onMidTurnDiagnosticPatch = (patch) => {
1084
1060
  midTurnCompactDiagnosticPatch = mergeContextBudgetDiagnosticPatches(midTurnCompactDiagnosticPatch, patch);
1085
1061
  };
1086
- const midTurnCapacityHook = this.deps.compaction.buildMidTurnCapacityCompactProjection(turnId, midTurnState, queue, providerTools, onMidTurnDiagnosticPatch, this, this.automaticMemoryCompactionSupported()
1062
+ const capacityProviderTools = [...providerTools];
1063
+ const midTurnCapacityHook = this.deps.compaction.buildMidTurnCapacityCompactProjection(turnId, midTurnState, queue, capacityProviderTools, onMidTurnDiagnosticPatch, this, this.automaticMemoryCompactionSupported()
1087
1064
  ? () => this.automaticMemoryCompactionDecision()
1088
1065
  : undefined, this.automaticMemoryCompactionSupported()
1089
1066
  ? (dispatch) => this.dispatchAutomaticMemoryCompaction(dispatch)
@@ -1095,7 +1072,8 @@ export class AiSdkTurn {
1095
1072
  pruneAppliedAtStep = runtimeSteps;
1096
1073
  activeToolResultPruneDiagnosticPatch = mergeActiveToolResultPruneDiagnosticPatches(activeToolResultPruneDiagnosticPatch, patch);
1097
1074
  });
1098
- const shapedProjection = composeRequestProjection(plan.projectActiveTools, midTurnCapacityHook, activeToolResultPruneHook);
1075
+ const projectCurrentToolAvailability = (options) => plan.projectActiveTools?.(options);
1076
+ const shapedProjection = composeRequestProjection(projectCurrentToolAvailability, midTurnCapacityHook, activeToolResultPruneHook);
1099
1077
  // Hooks shape; nothing measures the final payload. Whether it fits is
1100
1078
  // the provider's answer (#4559).
1101
1079
  const requestProjection = shapedProjection;
@@ -1111,6 +1089,14 @@ export class AiSdkTurn {
1111
1089
  let finishReason = 'stop';
1112
1090
  let terminalProviderError;
1113
1091
  agentLoop: for (;;) {
1092
+ ({ plan, providerTools, modelTools } = snapshotStepTools());
1093
+ resolvedSystemPrompt = await this.resolveSystemPrompt();
1094
+ systemPrompt = joinPromptFragments([
1095
+ resolvedSystemPrompt.text,
1096
+ this.orchestration.mode === 'swarm' ? renderSwarmModePrompt() : undefined,
1097
+ this.orchestration.mode === 'graph' ? renderGraphModePrompt() : undefined,
1098
+ ]);
1099
+ capacityProviderTools.splice(0, capacityProviderTools.length, ...providerTools);
1114
1100
  await this.drainSteeringInto(input, queue);
1115
1101
  if (this.deps.backend.loadTurnRuntimeEvents) {
1116
1102
  requestMessages = await loadDurableTurnProjection();
@@ -1159,7 +1145,20 @@ export class AiSdkTurn {
1159
1145
  : undefined;
1160
1146
  const projectedMessages = shaped?.messages ?? requestMessages;
1161
1147
  const activeToolsForRequest = resolveDispatch(shaped?.activeTools).activeTools;
1162
- providerRequestTracker?.setStep(runtimeSteps);
1148
+ const requestCompositionId = this.runId && this.deps.backend.recordRequestComposition
1149
+ ? await this.deps.backend.recordRequestComposition(this.runId, {
1150
+ compositionId: this.deps.newId(),
1151
+ step: runtimeSteps,
1152
+ sourceRevisions: resolvedSystemPrompt.sourceRevisions,
1153
+ systemPromptHash: stableHash(requestSystemPrompt ?? ''),
1154
+ toolCatalogHash: toolCatalogHash(providerTools),
1155
+ toolAvailabilityHash: toolAvailabilityHash(this.deps.backend.toolAvailability),
1156
+ providerOptionsHash: stableHash(this.deps.resolvedProviderOptions),
1157
+ toolNames: activeToolsForRequest,
1158
+ toolSchemas: requestCompositionToolSchemas(providerTools, activeToolsForRequest),
1159
+ })
1160
+ : undefined;
1161
+ providerRequestTracker?.setStep(runtimeSteps, requestCompositionId);
1163
1162
  let attemptMessages = projectedMessages;
1164
1163
  let providerAttempt = 1;
1165
1164
  let idleWatchdogRetryCount = 0;
@@ -2488,14 +2487,22 @@ export class AiSdkTurn {
2488
2487
  async resolveSystemPrompt() {
2489
2488
  const turnId = this.turnId;
2490
2489
  if (typeof this.deps.backend.systemPrompt === 'function') {
2491
- return await this.deps.backend.systemPrompt({
2490
+ const resolved = await this.deps.backend.systemPrompt({
2492
2491
  sessionId: this.deps.backend.sessionId,
2493
2492
  turnId,
2494
2493
  cwd: this.deps.backend.header.cwd,
2495
2494
  emitSkillCatalogTrace: (message, data) => this.runTrace?.emit('skill', 'skill_catalog_built', message, data),
2496
2495
  });
2496
+ return typeof resolved === 'string' || resolved === undefined
2497
+ ? { ...(resolved === undefined ? {} : { text: resolved }), sourceRevisions: [] }
2498
+ : resolved;
2497
2499
  }
2498
- return this.deps.backend.systemPrompt;
2500
+ return {
2501
+ ...(this.deps.backend.systemPrompt === undefined
2502
+ ? {}
2503
+ : { text: this.deps.backend.systemPrompt }),
2504
+ sourceRevisions: [],
2505
+ };
2499
2506
  }
2500
2507
  async *drain(queue) {
2501
2508
  try {
@@ -25,7 +25,7 @@ export function buildArchiveReadTool(reader) {
25
25
  .object({
26
26
  ref: z
27
27
  .string()
28
- .describe('A maka://archive/... ref returned in an archived tool-result placeholder'),
28
+ .describe('The exact resourceRef returned in an archived tool-result placeholder (ledger or legacy archive)'),
29
29
  operation: z
30
30
  .enum(['inspect', 'read', 'query', 'search'])
31
31
  .default('inspect')
@@ -82,7 +82,7 @@ export function buildArchiveReadTool(reader) {
82
82
  name: ARCHIVE_READ_TOOL_NAME,
83
83
  displayName: 'Read archived result',
84
84
  activityKind: 'read',
85
- description: 'Inspect, search, or page through a tool-result archive returned as a maka://archive/... ref. Start with inspect for a preview and the char/line coordinate space. Use operation "search" with a pattern to locate text, operation "read" with unit "line" for line-oriented terminal output, or operation "query" with an itemId for one agent_swarm item. Results are strictly bounded so reading an archive cannot immediately trigger another archive.',
85
+ description: 'Inspect, search, or page through a tool-result archive using its exact resourceRef. Both ledger and legacy archive references are supported. Start with inspect for a preview and the char/line coordinate space. Use operation "search" with a pattern to locate text, operation "read" with unit "line" for line-oriented terminal output, or operation "query" with an itemId for one agent_swarm item. Results are strictly bounded so reading an archive cannot immediately trigger another archive.',
86
86
  parameters: jsonSchema(async () => await providerSchema.jsonSchema, {
87
87
  validate: async (value) => {
88
88
  const result = await parameters.safeParseAsync(value);
@@ -46,22 +46,9 @@ export function stableJsonLength(value) {
46
46
  export function turnKey(event) {
47
47
  return event.turnId || '<unknown-turn>';
48
48
  }
49
- export function uniqueSorted(values) {
50
- return [...new Set(values.filter((value) => value.length > 0))].sort();
51
- }
52
49
  export function sha256(text) {
53
50
  return createHash('sha256').update(text).digest('hex');
54
51
  }
55
- export function stableStringify(value) {
56
- if (value === undefined)
57
- return '';
58
- try {
59
- return JSON.stringify(value) ?? '';
60
- }
61
- catch {
62
- return String(value);
63
- }
64
- }
65
52
  export function finitePositive(value) {
66
53
  return typeof value === 'number' && Number.isFinite(value) && value > 0
67
54
  ? Math.floor(value)
@@ -70,6 +57,3 @@ export function finitePositive(value) {
70
57
  export function utf8ByteLength(text) {
71
58
  return Buffer.byteLength(text, 'utf8');
72
59
  }
73
- export function optionalNonNegativeFiniteNumber(value) {
74
- return value === undefined || (typeof value === 'number' && Number.isFinite(value) && value >= 0);
75
- }
@@ -31,11 +31,13 @@ import { buildToolOperationId } from './runtime-commit-sink.js';
31
31
  import { isContinuationStartRuntimeEvent } from './runtime-event-read-model.js';
32
32
  import { runtimeHandoffPause } from '@maka/core/runtime-handoff';
33
33
  import { buildToolResultArchiveResourceRef, parseToolResultArchiveResourceRef, } from './tool-result-archive-resource.js';
34
- import { deserializeToolResultArchive, isArchivedToolResultPlaceholder, } from './tool-result-archive.js';
34
+ import { deserializeToolResultArchive, isArchivedToolResultPlaceholder, buildLedgerArchivedToolResultPlaceholder, } from './tool-result-archive.js';
35
35
  import { rewriteDurableToolResultProjectionArtifactRefs } from './durable-tool-result-projection.js';
36
- import { buildModelProjectionTransition, decodeModelProjectionTransition, MODEL_PROJECTION_TRANSITION_EVENT_TYPE, } from '@maka/core/model-projection-transition';
36
+ import { buildModelProjectionTransition, durableToolResultProjectionDigest, decodeModelProjectionTransition, MODEL_PROJECTION_TRANSITION_EVENT_TYPE, } from '@maka/core/model-projection-transition';
37
37
  import { baseToolResultProjection, decodeLedgerTransition, reduceEffectiveModelProjections, } from './model-projection-transition-ledger.js';
38
38
  import { archivedToolResultProjection } from './tool-result-archive-transition.js';
39
+ import { serializeToolResultProjectionV1 } from './tool-result-archive-encoding.js';
40
+ import { createHash } from 'node:crypto';
39
41
  /** Walks every typed StorageRef site reached by conversation-copy rewriting. */
40
42
  function collectConversationCopyStorageRefs(input) {
41
43
  const refs = [];
@@ -308,6 +310,7 @@ export async function cloneConversationRuntimeLedger(input) {
308
310
  runtimeEventIds,
309
311
  providerTraceIds,
310
312
  agentRunEventIds: operationalEventIds,
313
+ ledgerArchives: new Map(),
311
314
  };
312
315
  const clonedEventBySourceId = new Map();
313
316
  for (const plan of flattenedPlans) {
@@ -328,6 +331,64 @@ export async function cloneConversationRuntimeLedger(input) {
328
331
  // successor is rebuilt against the projection it actually replaces.
329
332
  const transitionIds = new Map();
330
333
  const transitionState = new Map();
334
+ const clonedTransitions = new Map();
335
+ const visiting = new Set();
336
+ const finished = new Set();
337
+ const transitionsByTarget = new Map();
338
+ for (const plan of flattenedPlans) {
339
+ for (const record of plan.operationalEvents) {
340
+ if (record.type !== MODEL_PROJECTION_TRANSITION_EVENT_TYPE)
341
+ continue;
342
+ const transition = decodeModelProjectionTransition(record.data?.transition, record.sessionId);
343
+ const group = transitionsByTarget.get(transition.target.runtimeEventId) ?? [];
344
+ group.push(record);
345
+ transitionsByTarget.set(transition.target.runtimeEventId, group);
346
+ }
347
+ }
348
+ // ArchiveRead can itself be archived. Resolve its referenced target first,
349
+ // then rebuild this result's transitions against its final copied projection.
350
+ const finishTarget = (sourceId) => {
351
+ if (finished.has(sourceId))
352
+ return;
353
+ if (visiting.has(sourceId))
354
+ throw new Error('Cyclic copied archive reference');
355
+ visiting.add(sourceId);
356
+ const target = clonedEventBySourceId.get(sourceId);
357
+ if (target?.content?.kind === 'function_response' && target.content.name === 'ArchiveRead') {
358
+ const resolveResult = (value) => rewriteArchiveReadResult(value, references, (ref) => {
359
+ const identity = parseToolResultArchiveResourceRef(ref);
360
+ if (identity?.storage === 'ledger' && clonedEventBySourceId.has(identity.runtimeEventId))
361
+ finishTarget(identity.runtimeEventId);
362
+ });
363
+ target.content.result = resolveResult(target.content.result);
364
+ const projection = target.content.modelProjection;
365
+ if (projection?.kind === 'json')
366
+ target.content.modelProjection = {
367
+ ...projection,
368
+ value: resolveResult(projection.value),
369
+ };
370
+ }
371
+ for (const record of transitionsByTarget.get(sourceId) ?? []) {
372
+ clonedTransitions.set(record, cloneModelProjectionTransition(record, references, clonedEventBySourceId, transitionIds, transitionState));
373
+ }
374
+ visiting.delete(sourceId);
375
+ finished.add(sourceId);
376
+ };
377
+ for (const sourceId of clonedEventBySourceId.keys())
378
+ finishTarget(sourceId);
379
+ for (const event of clonedEventBySourceId.values()) {
380
+ if (event.content?.kind === 'text')
381
+ event.content.text = rewriteLedgerArchiveText(event.content.text, references);
382
+ if (event.content?.kind === 'function_call' && event.content.name === 'ArchiveRead') {
383
+ const args = event.content.args;
384
+ if (args && typeof args === 'object' && 'ref' in args && typeof args.ref === 'string') {
385
+ event.content = {
386
+ ...event.content,
387
+ args: { ...args, ref: rewriteLedgerArchiveText(args.ref, references) },
388
+ };
389
+ }
390
+ }
391
+ }
331
392
  const preparedPlans = flattenedPlans.map((plan) => {
332
393
  const runId = runIds.get(plan.run.runId);
333
394
  const clonedOperationalEvents = plan.operationalEvents.flatMap((event) => {
@@ -335,7 +396,7 @@ export async function cloneConversationRuntimeLedger(input) {
335
396
  sessionId: input.referenceMap.targetSessionId,
336
397
  runId,
337
398
  eventId: operationalEventIds.get(event.id),
338
- }, references, sourceCompactableEvents.get(plan.run.runId) ?? [], clonedEventBySourceId, checkpointIds, transitionIds, transitionState, providerTraceIds, logicalCallIds);
399
+ }, references, sourceCompactableEvents.get(plan.run.runId) ?? [], clonedEventBySourceId, checkpointIds, providerTraceIds, logicalCallIds, clonedTransitions);
339
400
  return clonedEvent ? [clonedEvent] : [];
340
401
  });
341
402
  const terminalEvent = plan.terminal.kind === 'fact'
@@ -351,7 +412,22 @@ export async function cloneConversationRuntimeLedger(input) {
351
412
  terminalEvent,
352
413
  };
353
414
  });
354
- const copiedMessages = input.copiedMessages.map((message) => rewriteConversationCopyMessage(message, references));
415
+ const archiveReadCalls = new Set(flattenedPlans.flatMap((plan) => plan.events.flatMap((event) => event.content?.kind === 'function_response' && event.content.name === 'ArchiveRead'
416
+ ? [`${event.turnId}:${event.content.id}`]
417
+ : [])));
418
+ const copiedMessages = input.copiedMessages.map((message) => {
419
+ const copied = rewriteConversationCopyMessage(message, references);
420
+ if (copied.type === 'user' || copied.type === 'assistant')
421
+ return { ...copied, text: rewriteLedgerArchiveText(copied.text, references) };
422
+ if (copied.type === 'tool_result' &&
423
+ archiveReadCalls.has(`${copied.turnId}:${copied.toolUseId}`)) {
424
+ return {
425
+ ...copied,
426
+ content: rewriteArchiveReadResult(copied.content, references),
427
+ };
428
+ }
429
+ return copied;
430
+ });
355
431
  const importedSourceEventIds = new Set();
356
432
  const orderedBatches = input.plan.inlineRuntimeEvents.flatMap((event) => {
357
433
  const cloned = clonedEventBySourceId.get(event.id);
@@ -400,6 +476,33 @@ export async function cloneConversationRuntimeLedger(input) {
400
476
  })),
401
477
  };
402
478
  }
479
+ function rewriteLedgerArchiveText(text, references) {
480
+ for (const [source, target] of references.ledgerArchives ?? [])
481
+ text = text.split(source).join(target.resourceRef);
482
+ return text;
483
+ }
484
+ /** Only ArchiveRead's defined envelope is rewritten, never its opaque content/items. */
485
+ function rewriteArchiveReadResult(value, references, before) {
486
+ if (!value || typeof value !== 'object' || Array.isArray(value))
487
+ return value;
488
+ const record = value;
489
+ if (record.kind === 'json')
490
+ return { ...record, value: rewriteArchiveReadResult(record.value, references, before) };
491
+ if (record.kind !== 'tool_result_archive' || typeof record.ref !== 'string')
492
+ return value;
493
+ before?.(record.ref);
494
+ const mapped = references.ledgerArchives?.get(record.ref);
495
+ if (!mapped)
496
+ return value;
497
+ return {
498
+ ...record,
499
+ ref: mapped.resourceRef,
500
+ ...(record.storage === 'ledger' && typeof record.runtimeEventId === 'string'
501
+ ? { runtimeEventId: mapped.runtimeEventId }
502
+ : {}),
503
+ ...(typeof record.originalBytes === 'number' ? { originalBytes: mapped.originalBytes } : {}),
504
+ };
505
+ }
403
506
  async function loadConversationCopyRunEvents(sourceRuns, sourceEvents, copyTurnIds, runtimeEventStore) {
404
507
  const copiedTurnIds = new Set(copyTurnIds);
405
508
  return Promise.all(sourceRuns.flatMap((run) => {
@@ -546,7 +649,7 @@ export function collectConversationCopySessionFileRefs(input) {
546
649
  }
547
650
  return refs;
548
651
  }
549
- function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clonedRuntimeEvents, checkpointIds, transitionIds, transitionState, providerTraceIds, logicalCallIds) {
652
+ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clonedRuntimeEvents, checkpointIds, providerTraceIds, logicalCallIds, clonedTransitions) {
550
653
  if (event.type === 'event_corrupt') {
551
654
  throw new Error(`Cannot copy corrupt AgentRun event ${event.id}`);
552
655
  }
@@ -601,7 +704,7 @@ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clo
601
704
  const checkpoint = buildHistoryCompactCheckpoint({
602
705
  sessionId: references.targetSessionId,
603
706
  coveredRuntimeEvents,
604
- summary: sourceCheckpoint.summary,
707
+ summary: rewriteLedgerArchiveText(sourceCheckpoint.summary, references),
605
708
  highWaterName: sourceCheckpoint.highWaterName,
606
709
  highWaterSeq: sourceCheckpoint.highWaterSeq,
607
710
  now: sourceCheckpoint.createdAt,
@@ -622,7 +725,7 @@ function cloneAgentRunEvent(event, ids, references, sourceCompactableEvents, clo
622
725
  };
623
726
  }
624
727
  else if (event.type === MODEL_PROJECTION_TRANSITION_EVENT_TYPE) {
625
- const cloned = cloneModelProjectionTransition(event, references, clonedRuntimeEvents, transitionIds, transitionState);
728
+ const cloned = clonedTransitions.get(event);
626
729
  // Every transition whose target is in the copied slice was gathered into
627
730
  // this run's ledger, wherever it was recorded. So a transition that finds no
628
731
  // cloned target has genuinely lost its target as well, and dropping it
@@ -660,16 +763,37 @@ function cloneModelProjectionTransition(event, references, clonedRuntimeEvents,
660
763
  const clonedTarget = clonedRuntimeEvents.get(source.target.runtimeEventId);
661
764
  if (!clonedTarget)
662
765
  return null;
663
- const placeholder = source.replacement.kind === 'json' ? source.replacement.value : undefined;
664
- if (!isArchivedToolResultPlaceholder(placeholder)) {
766
+ const rawPlaceholder = source.replacement.kind === 'json' ? source.replacement.value : undefined;
767
+ if (!isArchivedToolResultPlaceholder(rawPlaceholder)) {
665
768
  throw new Error(`Cannot copy unsupported model projection transition ${event.id}`);
666
769
  }
770
+ const placeholder = rawPlaceholder;
667
771
  const existing = transitionState.get(clonedTarget.id);
668
772
  const sourceProjection = existing?.projection ?? baseToolResultProjection(clonedTarget);
669
773
  if (!sourceProjection) {
670
774
  throw new Error(`Cannot copy model projection transition ${event.id} onto its target`);
671
775
  }
672
- const rewritten = rewriteArchivedToolResult(placeholder, references);
776
+ let rewritten;
777
+ if (placeholder.rewriteVersion === 2) {
778
+ const { previousTransitionId: _previous, ...rest } = placeholder;
779
+ const serialized = serializeToolResultProjectionV1(sourceProjection);
780
+ rewritten = buildLedgerArchivedToolResultPlaceholder({
781
+ ...rest,
782
+ runtimeEventId: clonedTarget.id,
783
+ sourceProjectionDigest: durableToolResultProjectionDigest(sourceProjection),
784
+ bodySha256: createHash('sha256').update(serialized).digest('hex'),
785
+ originalBytes: Buffer.byteLength(serialized),
786
+ ...(source.previousTransitionId
787
+ ? {
788
+ previousTransitionId: requiredMappedId(transitionIds, source.previousTransitionId, 'model projection transition'),
789
+ }
790
+ : {}),
791
+ });
792
+ references.ledgerArchives?.set(buildToolResultArchiveResourceRef(placeholder), rewritten);
793
+ }
794
+ else {
795
+ rewritten = rewriteArchivedToolResult(placeholder, references);
796
+ }
673
797
  const transition = buildModelProjectionTransition({
674
798
  sessionId: references.targetSessionId,
675
799
  target: {
@@ -1030,6 +1154,18 @@ function rewriteToolResultContent(content, references) {
1030
1154
  return { ...content, ref: rewriteStorageRef(content.ref, references) };
1031
1155
  }
1032
1156
  if (content.kind === 'archived_tool_result') {
1157
+ if (content.rewriteVersion === 2 && content.resourceRef) {
1158
+ const rewritten = references.ledgerArchives?.get(content.resourceRef);
1159
+ if (!rewritten)
1160
+ throw new Error('Cannot copy ledger archive without its committed transition');
1161
+ return {
1162
+ ...content,
1163
+ runtimeEventId: rewritten.runtimeEventId,
1164
+ resourceRef: rewritten.resourceRef,
1165
+ bodySha256: rewritten.bodySha256,
1166
+ originalBytes: rewritten.originalBytes,
1167
+ };
1168
+ }
1033
1169
  const snapshot = rewriteArchivedSnapshot(content, references);
1034
1170
  if (snapshot)
1035
1171
  return snapshot;
@@ -1257,6 +1393,12 @@ function rewriteProjectionArtifactRef(ref, references) {
1257
1393
  return rewritten;
1258
1394
  }
1259
1395
  function rewriteArchivedToolResult(value, references) {
1396
+ if (value.rewriteVersion === 2) {
1397
+ const rewritten = references.ledgerArchives?.get(buildToolResultArchiveResourceRef(value));
1398
+ if (!rewritten)
1399
+ throw new Error('Cannot copy ledger archive without its committed transition');
1400
+ return rewritten;
1401
+ }
1260
1402
  const artifactId = rewriteOwnedArtifactId(value.artifactId, references);
1261
1403
  const resource = value.resourceRef
1262
1404
  ? parseToolResultArchiveResourceRef(value.resourceRef)
@@ -1265,7 +1407,7 @@ function rewriteArchivedToolResult(value, references) {
1265
1407
  ...value,
1266
1408
  runtimeEventId: rewriteOwnedId(value.runtimeEventId, references.runtimeEventIds, 'RuntimeEvent'),
1267
1409
  artifactId,
1268
- ...(resource && artifactId !== value.artifactId
1410
+ ...(resource && resource.storage !== 'ledger' && artifactId !== value.artifactId
1269
1411
  ? {
1270
1412
  resourceRef: buildToolResultArchiveResourceRef({
1271
1413
  ...resource,
@@ -17,7 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { createGitHubCopilotAccountTokens, GITHUB_COPILOT_DEFAULT_API_ENDPOINT, isSupportedGitHubCopilotAccountToken, } from './subscription-credentials.js';
20
- import { fetchGitHubCopilotModels } from './model-fetcher.js';
20
+ import { fetchGitHubCopilotModels, GitHubCopilotModelPolicyError } from './model-fetcher.js';
21
21
  import { ConnectionEffectHttpError } from './connection-effect-outcome.js';
22
22
  import { OAUTH_LOGIN_MAX_TOKEN_CHARS, OAuthTokenEndpointError, requestOAuthEndpointJson, } from './oauth-login.js';
23
23
  import { OAUTH_PROVIDER_CONTRACTS, OAuthDeviceAuthorizationExpiredError, oauthExpiresAt, requireOAuthBoundedString, requireOAuthDataRecord, requireOAuthPositiveInteger, } from './oauth-provider-contracts.js';
@@ -185,8 +185,8 @@ export async function verifyGitHubCopilotModelEntitlement(input) {
185
185
  // body tell us nothing about the subscription, and reporting them as
186
186
  // ineligibility would send a paying user back through a device login that
187
187
  // was never the problem.
188
- if (error instanceof ConnectionEffectHttpError &&
189
- (error.status === 401 || error.status === 403)) {
188
+ if (error instanceof GitHubCopilotModelPolicyError ||
189
+ (error instanceof ConnectionEffectHttpError && (error.status === 401 || error.status === 403))) {
190
190
  throw new GitHubCopilotEntitlementError({ cause: error });
191
191
  }
192
192
  throw new GitHubCopilotEntitlementUnavailableError(error instanceof ConnectionEffectHttpError ? error.status : undefined, { cause: error });
@@ -418,13 +418,17 @@ export class GoalContinuationCoordinator {
418
418
  return;
419
419
  if (this.handoffHeld && lane.queue.length === 0)
420
420
  return;
421
+ const activity = this.deps.acquireActivity?.();
421
422
  const task = this.drainLane(lane).catch((error) => {
422
423
  if (!this.isCurrent(lane))
423
424
  return;
424
425
  this.pauseCurrentGoal(lane, `Goal continuation coordinator failed: ${errorMessage(error)}`);
425
426
  });
426
427
  this.activeDrains.add(task);
427
- void task.finally(() => this.activeDrains.delete(task));
428
+ void task.finally(() => {
429
+ this.activeDrains.delete(task);
430
+ activity?.release();
431
+ });
428
432
  }
429
433
  async drainLane(lane) {
430
434
  if (!this.isCurrent(lane) || lane.draining)