maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923

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 (131) hide show
  1. package/DISCLAIMER-WIP +6 -1
  2. package/README.md +6 -1
  3. package/dist/acp/maka-acp-agent.js +1 -1
  4. package/dist/acp/session-mcp.js +212 -0
  5. package/dist/acp/session-registry.js +52 -8
  6. package/dist/acp/stdio-server.js +3 -0
  7. package/dist/cli-core.js +1 -1
  8. package/dist/mcp-capability-provider.js +6 -1
  9. package/dist/mcp-capability-publication.js +139 -0
  10. package/dist/pi-tui-layout.js +46 -17
  11. package/dist/pi-tui-pickers.js +25 -14
  12. package/dist/pi-tui-runner.js +24 -15
  13. package/dist/runtime-host-capability-provider-command.js +25 -6
  14. package/dist/runtime-host-service-manager.js +26 -2
  15. package/dist/tui-context-refresh.js +15 -26
  16. package/dist/tui-mcp-control.js +66 -112
  17. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  18. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  19. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  20. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  21. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  22. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  23. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  24. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  25. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  26. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  27. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  28. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  29. package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
  30. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  31. package/node_modules/@maka/core/dist/mcp.js +4 -1
  32. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  33. package/node_modules/@maka/core/dist/model-thinking.js +36 -1
  34. package/node_modules/@maka/core/dist/provider-registry.js +0 -1
  35. package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
  36. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  37. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  38. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  40. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  41. package/node_modules/@maka/core/dist/session.js +19 -1
  42. package/node_modules/@maka/core/dist/settings.js +3 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  45. package/node_modules/@maka/core/package.json +3 -0
  46. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  47. package/node_modules/@maka/mcp/dist/index.js +31 -5
  48. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  49. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  50. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
  51. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
  52. package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
  53. package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
  54. package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
  55. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  56. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  57. package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
  58. package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
  59. package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
  60. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
  61. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  62. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  63. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  64. package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
  65. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  66. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  67. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  68. package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
  69. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
  70. package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
  71. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  72. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
  74. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  75. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  76. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
  77. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
  78. package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  80. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
  81. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
  82. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  83. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  84. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
  85. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
  86. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  87. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  89. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
  90. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
  91. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  92. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
  93. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  94. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  95. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  96. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  97. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
  98. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  99. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  100. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  101. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  102. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  103. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  104. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  105. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  106. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  107. package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
  109. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  110. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
  111. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  112. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  114. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  115. package/node_modules/@maka/runtime-host/package.json +2 -0
  116. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  117. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  118. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  119. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  120. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  121. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  122. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  123. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  124. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  125. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  126. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  127. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  128. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  129. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  130. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  131. package/package.json +1 -1
@@ -36,7 +36,7 @@ import { readLogicalRuntimeExecutionForRun } from '@maka/core/runtime-logical-ex
36
36
  import { buildInvocationOpenedEvent, isSessionInlineInvocation, runtimeInvocationOutcome, } from '@maka/core/runtime-invocation';
37
37
  import { isArtifactChildResultOutput } from '@maka/core/artifacts';
38
38
  import { invocationMatchesClaimTarget } from '@maka/core/runtime-boundary';
39
- import { readRunInvocation, } from '@maka/core/runtime-event-store';
39
+ import { readRunInvocation, runtimeInvocationRecoveryInventoryFromInvocations, } from '@maka/core/runtime-event-store';
40
40
  import { AGENT_GRAPH_OPERATOR_PROVISION_SCHEMA_VERSION } from '@maka/core/agent-graph-topology';
41
41
  import { runtimeInvocationFailureClass, } from './runtime-event-read-model.js';
42
42
  import { RuntimeReadModel, RuntimeReadModelError, } from './runtime-read-model.js';
@@ -381,6 +381,8 @@ export class SessionManager {
381
381
  if (current.revision !== input.expectedRevision) {
382
382
  throw new SessionConfigurationRevisionConflictError(input.expectedRevision, current.revision);
383
383
  }
384
+ if (current.header.executorId)
385
+ throw new SessionConfigurationTransitionError('operation_unavailable', 'External executor workspace is fixed. Start a new task.');
384
386
  if (current.header.isArchived) {
385
387
  throw new SessionConfigurationTransitionError('operation_conflict', 'Archived Session workspace cannot be relocated');
386
388
  }
@@ -547,15 +549,63 @@ export class SessionManager {
547
549
  async recoverInterruptedSessions() {
548
550
  return this.recoverInterruptedSessionsWithPolicy({ kind: 'best_effort' });
549
551
  }
552
+ /**
553
+ * Recover only the Sessions named by an already-held quiescent operation.
554
+ *
555
+ * Bundle export uses this after fencing a Session subtree. A persisted open
556
+ * invocation can survive a Host restart even though no live execution claim
557
+ * remains; settling that invocation is safe under the fence and lets export
558
+ * distinguish an interrupted run from a live one without scanning or
559
+ * mutating unrelated Sessions.
560
+ */
561
+ async recoverInterruptedSessionsForSessions(sessionIds) {
562
+ const scope = new Set(sessionIds);
563
+ return this.recoverInterruptedSessionsWithPolicy({ kind: 'best_effort' }, scope);
564
+ }
550
565
  async recoverInterruptedSessionsStrict(stores) {
551
566
  if (stores.sessionStore !== this.deps.store || stores.agentRunStore !== this.deps.runStore) {
552
567
  throw new Error('Strict recovery stores must match the SessionManager composition');
553
568
  }
554
569
  return this.recoverInterruptedSessionsWithPolicy({ kind: 'strict', stores });
555
570
  }
556
- async recoverInterruptedSessionsWithPolicy(policy) {
557
- const interrupted = (await listSessionsForRecovery(this.deps.store, policy)).filter((session) => !session.isArchived);
571
+ async recoverInterruptedSessionsWithPolicy(policy, scope) {
572
+ const profileEnabled = process.env.MAKA_STARTUP_PROFILE === '1';
573
+ const profileTotals = new Map();
574
+ const profileCalls = new Map();
575
+ const measure = async (label, operation) => {
576
+ if (!profileEnabled)
577
+ return operation();
578
+ const started = performance.now();
579
+ try {
580
+ return await operation();
581
+ }
582
+ finally {
583
+ profileTotals.set(label, (profileTotals.get(label) ?? 0) + performance.now() - started);
584
+ profileCalls.set(label, (profileCalls.get(label) ?? 0) + 1);
585
+ }
586
+ };
587
+ const interrupted = (await measure('list-sessions', () => listSessionsForRecovery(this.deps.store, policy))).filter((session) => !session.isArchived && (scope === undefined || scope.has(session.id)));
588
+ const recoveryCandidateSet = async (label, operation) => {
589
+ if (!operation)
590
+ return undefined;
591
+ const sessionIds = await measure(label, () => recoverOr(policy, operation, undefined));
592
+ return sessionIds ? new Set(sessionIds) : undefined;
593
+ };
594
+ const continuationAuthority = runtimeContinuationAuthority(this.deps.runtimeEventStore);
595
+ const [shellRecoverySessions, unsettledContinuationClaims, planRecoverySessions] = await Promise.all([
596
+ recoveryCandidateSet('shell-inventory', this.deps.shellRuns ? () => this.deps.shellRuns.listRecoverySessionIds() : undefined),
597
+ continuationAuthority?.listUnsettledContinuationClaimsForRecovery
598
+ ? measure('continuation-inventory', () => recoverOr(policy, () => continuationAuthority.listUnsettledContinuationClaimsForRecovery(interrupted.map((session) => session.id)), undefined))
599
+ : Promise.resolve(undefined),
600
+ recoveryCandidateSet('plan-inventory', this.deps.planStore?.listPlanRecoverySessionIds
601
+ ? () => this.deps.planStore.listPlanRecoverySessionIds()
602
+ : undefined),
603
+ ]);
604
+ const continuationClaimsBySession = unsettledContinuationClaims
605
+ ? groupContinuationClaimsBySession(unsettledContinuationClaims)
606
+ : undefined;
558
607
  const recovered = new Set();
608
+ const runRecoveryQueue = [];
559
609
  for (const session of interrupted) {
560
610
  if (this.runtimeKernel.hasActiveRuns(session.id))
561
611
  continue;
@@ -576,8 +626,9 @@ export class SessionManager {
576
626
  }), undefined);
577
627
  }
578
628
  }
579
- if (this.deps.shellRuns) {
580
- const recoveredShellRuns = await recoverOr(policy, () => this.deps.shellRuns.recoverOrphanedSession(session.id), 0);
629
+ if (this.deps.shellRuns &&
630
+ (!shellRecoverySessions || shellRecoverySessions.has(session.id))) {
631
+ const recoveredShellRuns = await measure('shell', () => recoverOr(policy, () => this.deps.shellRuns.recoverOrphanedSession(session.id), 0));
581
632
  if (recoveredShellRuns > 0)
582
633
  recovered.add(session.id);
583
634
  }
@@ -586,10 +637,13 @@ export class SessionManager {
586
637
  // recovery. Deciding it a second time here — off a transcript scan, and
587
638
  // ending in `remove()` — could only ever contradict it.
588
639
  let continuationClaimRecovered = false;
589
- const continuationAuthority = runtimeContinuationAuthority(this.deps.runtimeEventStore);
590
- if (this.deps.runStore && continuationAuthority) {
640
+ const unsettledClaims = continuationClaimsBySession?.get(session.id);
641
+ if (this.deps.runStore &&
642
+ continuationAuthority &&
643
+ (!continuationClaimsBySession ||
644
+ unsettledClaims?.some((state) => state.startEventId === undefined || state.startKind === 'claim_repair'))) {
591
645
  try {
592
- continuationClaimRecovered = await this.recoverContinuationClaimsBeforeProvider(session.id, continuationAuthority, policy);
646
+ continuationClaimRecovered = await measure('continuation', () => this.recoverContinuationClaimsBeforeProvider(session.id, continuationAuthority, policy, unsettledClaims));
593
647
  }
594
648
  catch (error) {
595
649
  if (policy.kind === 'strict')
@@ -603,26 +657,62 @@ export class SessionManager {
603
657
  if (continuationClaimRecovered)
604
658
  recovered.add(session.id);
605
659
  }
606
- if (this.deps.planStore) {
607
- const preservesHandoff = await recoverOr(policy, async () => {
608
- if (!continuationAuthority)
609
- return false;
610
- const latest = latestInvocation((await this.listInvocations(session.id)).filter((run) => isSessionInlineInvocation(run.opening)));
611
- if (!latest)
612
- return false;
613
- // Only the current logical execution may preserve a session-owned Plan.
614
- // Read after claim repair: an abandoned successor now has a real failure.
615
- return Boolean((await readLogicalRuntimeExecutionForRun(continuationAuthority, latest))
616
- ?.pendingHandoff);
617
- }, false);
618
- if (!preservesHandoff) {
619
- const planRecovery = await recoverOr(policy, () => this.deps.planStore.interruptActiveExecution(session.id, 'runtime_recovery'), null);
620
- if (planRecovery)
621
- recovered.add(session.id);
660
+ if (this.deps.planStore && (!planRecoverySessions || planRecoverySessions.has(session.id))) {
661
+ // Plan events are the domain authority for whether there is a Plan
662
+ // obligation at all. Runtime evidence is needed only to decide whether
663
+ // an existing active Plan belongs to a pending handoff and must survive.
664
+ const planState = await measure('plan-state', () => recoverOr(policy, () => this.deps.planStore.readState(session.id), undefined));
665
+ if (planState?.activeExecutionId) {
666
+ const preservesHandoff = await measure('plan-handoff', () => recoverOr(policy, async () => {
667
+ if (!continuationAuthority)
668
+ return false;
669
+ const latest = latestInvocation((await this.listInvocations(session.id)).filter((run) => isSessionInlineInvocation(run.opening)));
670
+ if (!latest)
671
+ return false;
672
+ // Only the current logical execution may preserve a session-owned Plan.
673
+ // Read after claim repair: an abandoned successor now has a real failure.
674
+ return Boolean((await readLogicalRuntimeExecutionForRun(continuationAuthority, latest))
675
+ ?.pendingHandoff);
676
+ }, false));
677
+ if (!preservesHandoff) {
678
+ const planRecovery = await measure('plan-interrupt', () => recoverOr(policy, () => this.deps.planStore.interruptActiveExecution(session.id, 'runtime_recovery'), null));
679
+ if (planRecovery)
680
+ recovered.add(session.id);
681
+ }
682
+ }
683
+ }
684
+ runRecoveryQueue.push({
685
+ session,
686
+ continuationClaimRecovered,
687
+ ...(continuationClaimsBySession
688
+ ? {
689
+ claimOwnedUnsettledRunIds: new Set((unsettledClaims ?? []).map((state) => state.claim.target.runId)),
690
+ }
691
+ : {}),
692
+ });
693
+ }
694
+ let inventoryBySession;
695
+ if (this.deps.runStore &&
696
+ this.deps.runtimeEventStore?.listInvocationRecoveryInventory &&
697
+ runRecoveryQueue.length > 0) {
698
+ try {
699
+ const inventory = await measure('run-inventory', () => this.deps.runtimeEventStore.listInvocationRecoveryInventory(runRecoveryQueue.map(({ session }) => session.id)));
700
+ const mutable = new Map();
701
+ for (const entry of inventory) {
702
+ const entries = mutable.get(entry.sessionId) ?? [];
703
+ entries.push(entry);
704
+ mutable.set(entry.sessionId, entries);
622
705
  }
706
+ inventoryBySession = mutable;
707
+ }
708
+ catch (error) {
709
+ if (policy.kind === 'strict')
710
+ throw error;
623
711
  }
712
+ }
713
+ for (const { session, continuationClaimRecovered, claimOwnedUnsettledRunIds, } of runRecoveryQueue) {
624
714
  if (this.deps.runStore) {
625
- const runRecovery = await recoverOr(policy, () => this.recoverAgentRunsFromLedger(session.id, policy), undefined);
715
+ const runRecovery = await measure('runs', () => recoverOr(policy, () => this.recoverAgentRunsFromLedger(session.id, policy, inventoryBySession ? (inventoryBySession.get(session.id) ?? []) : undefined, claimOwnedUnsettledRunIds), undefined));
626
716
  if (runRecovery?.hasLedger) {
627
717
  if (runRecovery.recovered || continuationClaimRecovered) {
628
718
  await recoverOr(policy, () => this.updateStatus(session.id, 'active'), undefined);
@@ -646,6 +736,15 @@ export class SessionManager {
646
736
  recovered.add(session.id);
647
737
  }
648
738
  }
739
+ if (profileEnabled) {
740
+ console.error(`[startup-profile] session-recovery ${JSON.stringify({
741
+ sessions: interrupted.length,
742
+ totals: Object.fromEntries([...profileTotals].map(([label, ms]) => [
743
+ label,
744
+ { calls: profileCalls.get(label) ?? 0, ms },
745
+ ])),
746
+ })}`);
747
+ }
649
748
  return [...recovered];
650
749
  }
651
750
  async setSessionStatus(sessionId, status, blockedReason) {
@@ -680,66 +779,6 @@ export class SessionManager {
680
779
  await this.deps.store.readHeader(sessionId);
681
780
  return this.runtimeKernel.listActiveInteractions?.(sessionId) ?? [];
682
781
  }
683
- async setPermissionMode(sessionId, mode) {
684
- const readHeaderRecordSnapshot = this.deps.store.readHeaderRecordSnapshot?.bind(this.deps.store);
685
- if (!readHeaderRecordSnapshot || !this.deps.store.updateSessionConfiguration) {
686
- // Temporary compatibility bridge for SessionStore embeddings that predate
687
- // versioned configuration authority. A follow-up PR will shortly remove
688
- // setPermissionMode and this redundant fallback after callers migrate.
689
- return this.setPermissionModeWithLegacyStore(sessionId, mode);
690
- }
691
- const current = await readHeaderRecordSnapshot(sessionId);
692
- const next = await this.transitionSessionConfiguration(sessionId, {
693
- expectedRevision: current.revision,
694
- clearConnectionBlock: false,
695
- permissionModeOnly: true,
696
- configuration: sessionConfigurationWithPermissionMode(current.header, mode),
697
- });
698
- return headerToSummary(next.header);
699
- }
700
- async setPermissionModeWithLegacyStore(sessionId, mode) {
701
- const previous = await this.deps.store.readHeader(sessionId);
702
- const boundary = await this.deps.store.readExecutionBoundary(sessionId);
703
- if (previous.permissionMode === mode &&
704
- executionBoundaryMatchesPermissionMode(boundary, mode)) {
705
- return headerToSummary(previous);
706
- }
707
- const labels = previous.labels;
708
- const kind = mode === 'bypass' ? 'bypass' : 'managed';
709
- await this.commitExecutionBoundaryTransition(sessionId, boundary, mode, async () => {
710
- const current = await this.deps.store.readHeader(sessionId);
711
- if (current.status === 'waiting_for_user') {
712
- throw new SessionConfigurationTransitionError('session_busy', 'Session has a pending Interaction');
713
- }
714
- return () => this.deps.store.setExecutionBoundaryKind(sessionId, kind, {
715
- permissionMode: mode,
716
- labels,
717
- });
718
- });
719
- const next = await this.deps.store.readHeader(sessionId);
720
- this.runtimeKernel.updateCachedHeader(sessionId, next);
721
- return headerToSummary(next);
722
- }
723
- async setExecutionBoundaryKind(sessionId, kind) {
724
- const current = await this.deps.store.readExecutionBoundary(sessionId);
725
- const header = await this.deps.store.readHeader(sessionId);
726
- // Managed includes Explore. Match Storage's default projection, then pass
727
- // it explicitly so classification and commit describe the same transition.
728
- const permissionMode = kind === 'bypass'
729
- ? 'bypass'
730
- : header.permissionMode === 'bypass'
731
- ? 'ask'
732
- : header.permissionMode;
733
- const narrows = narrowsExecutionAuthority(current, permissionMode);
734
- if (narrows && this.runtimeKernel.hasActiveRuns(sessionId)) {
735
- throw new SessionConfigurationTransitionError('session_busy', 'Execution boundary cannot change while a Turn is running');
736
- }
737
- if (header.status === 'waiting_for_user') {
738
- throw new SessionConfigurationTransitionError('session_busy', 'Execution boundary cannot change while an Interaction is pending');
739
- }
740
- const boundary = await this.commitExecutionBoundaryTransition(sessionId, current, permissionMode, async () => () => this.deps.store.setExecutionBoundaryKind(sessionId, kind, { permissionMode }));
741
- return boundary;
742
- }
743
782
  async commitExecutionBoundaryTransition(sessionId, current, nextPermissionMode, prepareCommit) {
744
783
  const prepareBoundaryCommit = async () => {
745
784
  const latest = await this.deps.store.readExecutionBoundary(sessionId);
@@ -3010,10 +3049,10 @@ export class SessionManager {
3010
3049
  * dead, so that state remains `continuation_started_indeterminate` and is
3011
3050
  * deliberately left non-terminal.
3012
3051
  */
3013
- async recoverContinuationClaimsBeforeProvider(sessionId, authority, _policy) {
3052
+ async recoverContinuationClaimsBeforeProvider(sessionId, authority, _policy, recoveryStates) {
3014
3053
  if (!this.deps.runStore)
3015
3054
  return false;
3016
- const states = await authority.listContinuationClaimsForRecovery(sessionId);
3055
+ const states = recoveryStates ?? (await authority.listContinuationClaimsForRecovery(sessionId));
3017
3056
  let recovered = false;
3018
3057
  for (const initialState of states) {
3019
3058
  const { claim } = initialState;
@@ -3084,24 +3123,41 @@ export class SessionManager {
3084
3123
  }
3085
3124
  return recovered;
3086
3125
  }
3087
- async recoverAgentRunsFromLedger(sessionId, policy = { kind: 'best_effort' }) {
3126
+ async recoverAgentRunsFromLedger(sessionId, policy = { kind: 'best_effort' }, recoveryInventory, recoveryClaimRunIds) {
3088
3127
  if (!this.deps.runStore || !this.deps.runtimeEventStore)
3089
3128
  return { hasLedger: false, recovered: false };
3090
3129
  // The importer may have committed only a prefix before a restart. Sealing
3091
3130
  // that prefix here would make the next read skip the unconverted history.
3092
- const runs = (await this.listInvocations(sessionId)).filter((run) => !isTranscriptLedgerInvocation(run));
3093
- if (runs.length === 0)
3131
+ const inventory = recoveryInventory
3132
+ ? recoveryInventory
3133
+ : this.deps.runtimeEventStore.listInvocationRecoveryInventory
3134
+ ? await this.deps.runtimeEventStore.listInvocationRecoveryInventory([sessionId])
3135
+ : runtimeInvocationRecoveryInventoryFromInvocations(await this.listInvocations(sessionId));
3136
+ const ownedInventory = inventory.filter((entry) => {
3137
+ const identity = entry.candidate ?? entry.identity;
3138
+ return !identity || !isTranscriptLedgerInvocation(identity);
3139
+ });
3140
+ if (ownedInventory.length === 0)
3094
3141
  return { hasLedger: false, recovered: false };
3142
+ let claimOwnedUnsettledRunIds = recoveryClaimRunIds;
3095
3143
  const continuationAuthority = runtimeContinuationAuthority(this.deps.runtimeEventStore);
3096
- const claimOwnedUnsettledRunIds = new Set();
3097
- if (continuationAuthority) {
3144
+ if (!claimOwnedUnsettledRunIds && continuationAuthority) {
3145
+ const discovered = new Set();
3098
3146
  for (const state of await continuationAuthority.listContinuationClaimsForRecovery(sessionId)) {
3099
3147
  const events = await continuationAuthority.readImmutableRuntimeEvents(state.claim.target.sessionId, state.claim.target.runId);
3100
- if (!events.some(isTerminalRuntimeEvent)) {
3101
- claimOwnedUnsettledRunIds.add(state.claim.target.runId);
3102
- }
3148
+ if (!events.some(isTerminalRuntimeEvent))
3149
+ discovered.add(state.claim.target.runId);
3103
3150
  }
3151
+ claimOwnedUnsettledRunIds = discovered;
3104
3152
  }
3153
+ const runsNeedingRecovery = ownedInventory.flatMap((entry) => entry.candidate ? [entry.candidate] : []);
3154
+ if (runsNeedingRecovery.length === 0)
3155
+ return { hasLedger: true, recovered: false };
3156
+ const recoveryRunStore = policy.kind === 'strict'
3157
+ ? {
3158
+ readEvents: (candidateSessionId, runId) => policy.stores.agentRunStore.readEventsForRecovery(candidateSessionId, runId),
3159
+ }
3160
+ : this.deps.runStore;
3105
3161
  // Read once per recovered session, and only when a failure actually needs
3106
3162
  // attributing, so healthy sessions pay nothing for the query.
3107
3163
  let boundaryClosures;
@@ -3114,11 +3170,14 @@ export class SessionManager {
3114
3170
  return boundaryClosures;
3115
3171
  };
3116
3172
  let recovered = false;
3117
- for (const run of runs) {
3118
- if (policy.kind === 'strict') {
3119
- await policy.stores.agentRunStore.readEventsForRecovery(sessionId, run.runId);
3120
- }
3121
- let inspected = await inspectAgentRunReadModel(this.deps.runStore, this.deps.runtimeEventStore, { sessionId, runId: run.runId, invocation: run });
3173
+ for (const run of runsNeedingRecovery) {
3174
+ let inspected = await inspectAgentRunReadModel(recoveryRunStore, this.deps.runtimeEventStore, {
3175
+ sessionId,
3176
+ runId: run.runId,
3177
+ invocation: run,
3178
+ includeModelReplay: false,
3179
+ includeProjection: false,
3180
+ });
3122
3181
  if (inspected.sourceHealth.runtimeLedger === 'read_failed') {
3123
3182
  if (policy.kind === 'strict') {
3124
3183
  throw new Error(`RuntimeEvent ledger is unreadable for run ${run.runId}`);
@@ -3138,7 +3197,7 @@ export class SessionManager {
3138
3197
  // restart repair must neither replay its tools nor manufacture failure.
3139
3198
  continue;
3140
3199
  }
3141
- if (claimOwnedUnsettledRunIds.has(run.runId) &&
3200
+ if (claimOwnedUnsettledRunIds?.has(run.runId) &&
3142
3201
  !inspected.runtimeEvents.some(isTerminalRuntimeEvent)) {
3143
3202
  // Every unresolved claim target belongs to the claim saga. This
3144
3203
  // includes claim-only, deterministic repair-start, and live provider
@@ -3163,7 +3222,13 @@ export class SessionManager {
3163
3222
  continue;
3164
3223
  }
3165
3224
  if (interruptedOutcomes.length > 0) {
3166
- inspected = await inspectAgentRunReadModel(this.deps.runStore, this.deps.runtimeEventStore, { sessionId, runId: run.runId, invocation: run });
3225
+ inspected = await inspectAgentRunReadModel(recoveryRunStore, this.deps.runtimeEventStore, {
3226
+ sessionId,
3227
+ runId: run.runId,
3228
+ invocation: run,
3229
+ includeModelReplay: false,
3230
+ includeProjection: false,
3231
+ });
3167
3232
  }
3168
3233
  }
3169
3234
  const terminalLedger = classifyTerminalRuntimeLedger(run, inspected.runtimeEvents);
@@ -3260,6 +3325,16 @@ function continuationExecutionErrorClass(error) {
3260
3325
  }
3261
3326
  return error instanceof Error ? error.name : 'unknown';
3262
3327
  }
3328
+ function groupContinuationClaimsBySession(states) {
3329
+ const grouped = new Map();
3330
+ for (const state of states) {
3331
+ const sessionId = state.claim.target.sessionId;
3332
+ const sessionStates = grouped.get(sessionId) ?? [];
3333
+ sessionStates.push(state);
3334
+ grouped.set(sessionId, sessionStates);
3335
+ }
3336
+ return grouped;
3337
+ }
3263
3338
  const MAX_BEST_EFFORT_OUTCOME_COMMIT_ATTEMPTS = 2;
3264
3339
  function listSessionsForRecovery(store, policy) {
3265
3340
  return policy.kind === 'strict' ? policy.stores.sessionStore.listForRecovery() : store.list();
@@ -3391,7 +3466,12 @@ export function headerToSummary(h) {
3391
3466
  ...(h.revisionIndex !== undefined ? { revisionIndex: h.revisionIndex } : {}),
3392
3467
  ...(h.revisionState ? { revisionState: h.revisionState } : {}),
3393
3468
  backend: h.backend,
3394
- ...(h.executorId ? { executorId: h.executorId } : {}),
3469
+ ...(h.executorId
3470
+ ? {
3471
+ executorId: h.executorId,
3472
+ ...(h.executorConfig ? { executorConfig: h.executorConfig } : {}),
3473
+ }
3474
+ : {}),
3395
3475
  ...(h.llmConnectionId === undefined ? {} : { llmConnectionId: h.llmConnectionId }),
3396
3476
  llmConnectionSlug: h.llmConnectionSlug,
3397
3477
  connectionLocked: h.connectionLocked,
@@ -3527,23 +3607,10 @@ function claimedAgentGraphIntentResult(claim, result) {
3527
3607
  ...result,
3528
3608
  };
3529
3609
  }
3530
- function sessionConfigurationWithPermissionMode(header, permissionMode) {
3531
- return {
3532
- backend: header.backend,
3533
- executorId: header.executorId,
3534
- llmConnectionId: header.llmConnectionId,
3535
- llmConnectionSlug: header.llmConnectionSlug,
3536
- connectionLocked: header.connectionLocked,
3537
- model: header.model,
3538
- thinkingLevel: header.thinkingLevel,
3539
- permissionMode,
3540
- collaborationMode: header.collaborationMode ?? 'agent',
3541
- orchestrationMode: header.orchestrationMode ?? 'default',
3542
- };
3543
- }
3544
3610
  function sessionConfigurationMatchesExceptPermissionMode(header, configuration) {
3545
3611
  return (header.backend === configuration.backend &&
3546
3612
  header.executorId === configuration.executorId &&
3613
+ header.executorConfig?.model === configuration.executorConfig?.model &&
3547
3614
  header.llmConnectionId === configuration.llmConnectionId &&
3548
3615
  header.llmConnectionSlug === configuration.llmConnectionSlug &&
3549
3616
  header.connectionLocked === configuration.connectionLocked &&
@@ -3556,15 +3623,6 @@ function sessionConfigurationMatches(header, configuration) {
3556
3623
  return (header.permissionMode === configuration.permissionMode &&
3557
3624
  sessionConfigurationMatchesExceptPermissionMode(header, configuration));
3558
3625
  }
3559
- function executionBoundaryMatchesPermissionMode(boundary, mode) {
3560
- if (mode === 'bypass')
3561
- return boundary.kind === 'bypass';
3562
- if (boundary.kind !== 'managed')
3563
- return false;
3564
- return mode === 'explore'
3565
- ? boundary.profile.name === 'read-only'
3566
- : boundary.profile.name !== 'read-only';
3567
- }
3568
3626
  function narrowsExecutionAuthority(boundary, nextPermissionMode) {
3569
3627
  if (nextPermissionMode === 'bypass')
3570
3628
  return false;
@@ -424,6 +424,9 @@ export class ShellRunProcessManager {
424
424
  const records = await this.input.store.listSessionShellRuns(sessionId);
425
425
  return records.map(shellRunUpdate);
426
426
  }
427
+ listRecoverySessionIds() {
428
+ return this.input.store.listShellRunRecoverySessionIds?.() ?? Promise.resolve(undefined);
429
+ }
427
430
  async getSessionUpdate(sessionId, ref) {
428
431
  const target = parseShellRunResourceRef(ref);
429
432
  if (!target)
@@ -324,16 +324,41 @@ export class AgentGraphCoordinator {
324
324
  */
325
325
  async recover() {
326
326
  const recovered = [];
327
+ const listedRecoveryGraphIds = this.#input.controlStore.listAgentGraphScheduleRecoveryGraphIds
328
+ ? await this.#input.controlStore.listAgentGraphScheduleRecoveryGraphIds()
329
+ : undefined;
330
+ const recoveryGraphIds = listedRecoveryGraphIds ? new Set(listedRecoveryGraphIds) : undefined;
331
+ if (recoveryGraphIds?.size === 0)
332
+ return recovered;
333
+ const recoveryRootSessionIds = new Set();
334
+ if (recoveryGraphIds && this.#input.epochStore) {
335
+ for (const graphId of recoveryGraphIds) {
336
+ const binding = await this.#input.epochStore.readAgentGraphEpochByGraphId(graphId);
337
+ if (binding)
338
+ recoveryRootSessionIds.add(binding.rootSessionId);
339
+ }
340
+ }
327
341
  for (const header of await this.#input.sessionStore.listForRecovery()) {
328
342
  if (this.#input.rootSessionId && header.id !== this.#input.rootSessionId)
329
343
  continue;
330
344
  if (header.subagentParent || header.isArchived)
331
345
  continue;
332
- const graphId = await this.currentGraphId(header.id);
333
- const updates = await this.#input.controlStore.listAgentGraphScheduleUpdates(graphId);
334
- if (updates.length === 0)
346
+ if (recoveryGraphIds &&
347
+ !recoveryGraphIds.has(agentGraphIdForRootSession(header.id)) &&
348
+ !recoveryRootSessionIds.has(header.id)) {
335
349
  continue;
336
- updates.forEach((update) => this.#assertScheduleOwnedByRoot(update, header.id, graphId));
350
+ }
351
+ const graphId = await this.currentGraphId(header.id);
352
+ if (recoveryGraphIds) {
353
+ if (!recoveryGraphIds.has(graphId))
354
+ continue;
355
+ }
356
+ else {
357
+ const updates = await this.#input.controlStore.listAgentGraphScheduleUpdates(graphId);
358
+ if (updates.length === 0)
359
+ continue;
360
+ updates.forEach((update) => this.#assertScheduleOwnedByRoot(update, header.id, graphId));
361
+ }
337
362
  await this.reconcile(header.id);
338
363
  recovered.push(header.id);
339
364
  }
@@ -21,7 +21,6 @@ import { fileURLToPath } from 'node:url';
21
21
  import { candidateStartupFailureExitCode, classifyCandidateStartupFailure, } from './candidate-startup-failure.js';
22
22
  import { createRuntimeHostLaunchOwnerGuard } from './candidate-launch-owner-guard.js';
23
23
  import { parseInteractiveRuntimeHostCandidateArguments } from './candidate-cli.js';
24
- import { writeCandidateStartupDiagnostic } from './control/startup-diagnostic.js';
25
24
  import { installRuntimeHostLogCapture, runtimeHostLogBuffer } from './process-diagnostics.js';
26
25
  import { startExecutionRuntimeHostCandidate, } from './server/execution-candidate.js';
27
26
  import { runRuntimeHostProcessLifecycle } from './server/process-lifecycle.js';
@@ -60,13 +59,17 @@ export async function runExecutionCandidateEntry(argv, entrypointUrl, hooks = {}
60
59
  const logs = runtimeHostLogBuffer.snapshot();
61
60
  console.error('[runtime-host] startup failed:', error);
62
61
  if (rootId && startupAttemptId) {
63
- await writeCandidateStartupDiagnostic({
64
- rootId,
65
- startupAttemptId,
62
+ const diagnosticRootId = rootId;
63
+ const diagnosticStartupAttemptId = startupAttemptId;
64
+ await import('./control/startup-diagnostic.js')
65
+ .then(({ writeCandidateStartupDiagnostic }) => writeCandidateStartupDiagnostic({
66
+ rootId: diagnosticRootId,
67
+ startupAttemptId: diagnosticStartupAttemptId,
66
68
  failure,
67
69
  error,
68
70
  logs,
69
- }).catch(() => undefined);
71
+ }))
72
+ .catch(() => undefined);
70
73
  }
71
74
  process.exit(candidateStartupFailureExitCode(failure));
72
75
  }
@@ -140,12 +140,14 @@ function snapshotProvider(provider) {
140
140
  const call = provider.call?.bind(provider);
141
141
  const callService = provider.callService?.bind(provider);
142
142
  const close = provider.close?.bind(provider);
143
+ const currentRegistrationRetired = provider.currentRegistrationRetired?.bind(provider);
143
144
  return {
144
145
  offers: () => canonical.offers,
145
146
  ...(canonical.services === undefined ? {} : { services: () => canonical.services ?? [] }),
146
147
  ...(call ? { call } : {}),
147
148
  ...(callService ? { callService } : {}),
148
149
  ...(close ? { close } : {}),
150
+ ...(currentRegistrationRetired ? { currentRegistrationRetired } : {}),
149
151
  };
150
152
  }
151
153
  async function closeProvider(provider) {