maka-agent 0.2.0-dev.47.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 (92) hide show
  1. package/dist/runtime-host-capability-provider-command.js +25 -6
  2. package/dist/tui-mcp-control.js +37 -24
  3. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  4. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  5. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  6. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  7. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  8. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  9. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  10. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  11. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  12. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  13. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  14. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  15. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  16. package/node_modules/@maka/core/dist/mcp.js +4 -1
  17. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  18. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  19. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  20. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  21. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  22. package/node_modules/@maka/core/dist/session.js +2 -0
  23. package/node_modules/@maka/core/dist/settings.js +1 -0
  24. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  25. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  26. package/node_modules/@maka/core/package.json +2 -0
  27. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  28. package/node_modules/@maka/mcp/dist/index.js +31 -5
  29. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  30. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  31. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  32. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  33. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +116 -3
  34. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  35. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  36. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  37. package/node_modules/@maka/runtime/dist/session-manager.js +170 -125
  38. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  39. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  40. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  41. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  42. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  43. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  44. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  45. package/node_modules/@maka/runtime-host/dist/protocol/index.js +10 -2
  46. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  47. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +40 -2
  48. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  49. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  50. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  51. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  52. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  53. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +159 -19
  54. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +3 -0
  55. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  56. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  57. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  58. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  59. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  60. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +4 -0
  61. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  62. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  63. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  64. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  65. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  66. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  67. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  68. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  69. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  70. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +99 -12
  71. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  72. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  73. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  74. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  75. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  76. package/node_modules/@maka/runtime-host/package.json +2 -0
  77. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  78. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  79. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  80. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  81. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  82. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  83. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  84. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  85. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  86. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  87. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  88. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  89. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  90. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  91. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  92. 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
  }
@@ -567,8 +569,43 @@ export class SessionManager {
567
569
  return this.recoverInterruptedSessionsWithPolicy({ kind: 'strict', stores });
568
570
  }
569
571
  async recoverInterruptedSessionsWithPolicy(policy, scope) {
570
- const interrupted = (await listSessionsForRecovery(this.deps.store, policy)).filter((session) => !session.isArchived && (scope === undefined || scope.has(session.id)));
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;
571
607
  const recovered = new Set();
608
+ const runRecoveryQueue = [];
572
609
  for (const session of interrupted) {
573
610
  if (this.runtimeKernel.hasActiveRuns(session.id))
574
611
  continue;
@@ -589,8 +626,9 @@ export class SessionManager {
589
626
  }), undefined);
590
627
  }
591
628
  }
592
- if (this.deps.shellRuns) {
593
- 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));
594
632
  if (recoveredShellRuns > 0)
595
633
  recovered.add(session.id);
596
634
  }
@@ -599,10 +637,13 @@ export class SessionManager {
599
637
  // recovery. Deciding it a second time here — off a transcript scan, and
600
638
  // ending in `remove()` — could only ever contradict it.
601
639
  let continuationClaimRecovered = false;
602
- const continuationAuthority = runtimeContinuationAuthority(this.deps.runtimeEventStore);
603
- 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'))) {
604
645
  try {
605
- continuationClaimRecovered = await this.recoverContinuationClaimsBeforeProvider(session.id, continuationAuthority, policy);
646
+ continuationClaimRecovered = await measure('continuation', () => this.recoverContinuationClaimsBeforeProvider(session.id, continuationAuthority, policy, unsettledClaims));
606
647
  }
607
648
  catch (error) {
608
649
  if (policy.kind === 'strict')
@@ -616,26 +657,62 @@ export class SessionManager {
616
657
  if (continuationClaimRecovered)
617
658
  recovered.add(session.id);
618
659
  }
619
- if (this.deps.planStore) {
620
- const preservesHandoff = await recoverOr(policy, async () => {
621
- if (!continuationAuthority)
622
- return false;
623
- const latest = latestInvocation((await this.listInvocations(session.id)).filter((run) => isSessionInlineInvocation(run.opening)));
624
- if (!latest)
625
- return false;
626
- // Only the current logical execution may preserve a session-owned Plan.
627
- // Read after claim repair: an abandoned successor now has a real failure.
628
- return Boolean((await readLogicalRuntimeExecutionForRun(continuationAuthority, latest))
629
- ?.pendingHandoff);
630
- }, false);
631
- if (!preservesHandoff) {
632
- const planRecovery = await recoverOr(policy, () => this.deps.planStore.interruptActiveExecution(session.id, 'runtime_recovery'), null);
633
- if (planRecovery)
634
- 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);
635
705
  }
706
+ inventoryBySession = mutable;
636
707
  }
708
+ catch (error) {
709
+ if (policy.kind === 'strict')
710
+ throw error;
711
+ }
712
+ }
713
+ for (const { session, continuationClaimRecovered, claimOwnedUnsettledRunIds, } of runRecoveryQueue) {
637
714
  if (this.deps.runStore) {
638
- 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));
639
716
  if (runRecovery?.hasLedger) {
640
717
  if (runRecovery.recovered || continuationClaimRecovered) {
641
718
  await recoverOr(policy, () => this.updateStatus(session.id, 'active'), undefined);
@@ -659,6 +736,15 @@ export class SessionManager {
659
736
  recovered.add(session.id);
660
737
  }
661
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
+ }
662
748
  return [...recovered];
663
749
  }
664
750
  async setSessionStatus(sessionId, status, blockedReason) {
@@ -693,66 +779,6 @@ export class SessionManager {
693
779
  await this.deps.store.readHeader(sessionId);
694
780
  return this.runtimeKernel.listActiveInteractions?.(sessionId) ?? [];
695
781
  }
696
- async setPermissionMode(sessionId, mode) {
697
- const readHeaderRecordSnapshot = this.deps.store.readHeaderRecordSnapshot?.bind(this.deps.store);
698
- if (!readHeaderRecordSnapshot || !this.deps.store.updateSessionConfiguration) {
699
- // Temporary compatibility bridge for SessionStore embeddings that predate
700
- // versioned configuration authority. A follow-up PR will shortly remove
701
- // setPermissionMode and this redundant fallback after callers migrate.
702
- return this.setPermissionModeWithLegacyStore(sessionId, mode);
703
- }
704
- const current = await readHeaderRecordSnapshot(sessionId);
705
- const next = await this.transitionSessionConfiguration(sessionId, {
706
- expectedRevision: current.revision,
707
- clearConnectionBlock: false,
708
- permissionModeOnly: true,
709
- configuration: sessionConfigurationWithPermissionMode(current.header, mode),
710
- });
711
- return headerToSummary(next.header);
712
- }
713
- async setPermissionModeWithLegacyStore(sessionId, mode) {
714
- const previous = await this.deps.store.readHeader(sessionId);
715
- const boundary = await this.deps.store.readExecutionBoundary(sessionId);
716
- if (previous.permissionMode === mode &&
717
- executionBoundaryMatchesPermissionMode(boundary, mode)) {
718
- return headerToSummary(previous);
719
- }
720
- const labels = previous.labels;
721
- const kind = mode === 'bypass' ? 'bypass' : 'managed';
722
- await this.commitExecutionBoundaryTransition(sessionId, boundary, mode, async () => {
723
- const current = await this.deps.store.readHeader(sessionId);
724
- if (current.status === 'waiting_for_user') {
725
- throw new SessionConfigurationTransitionError('session_busy', 'Session has a pending Interaction');
726
- }
727
- return () => this.deps.store.setExecutionBoundaryKind(sessionId, kind, {
728
- permissionMode: mode,
729
- labels,
730
- });
731
- });
732
- const next = await this.deps.store.readHeader(sessionId);
733
- this.runtimeKernel.updateCachedHeader(sessionId, next);
734
- return headerToSummary(next);
735
- }
736
- async setExecutionBoundaryKind(sessionId, kind) {
737
- const current = await this.deps.store.readExecutionBoundary(sessionId);
738
- const header = await this.deps.store.readHeader(sessionId);
739
- // Managed includes Explore. Match Storage's default projection, then pass
740
- // it explicitly so classification and commit describe the same transition.
741
- const permissionMode = kind === 'bypass'
742
- ? 'bypass'
743
- : header.permissionMode === 'bypass'
744
- ? 'ask'
745
- : header.permissionMode;
746
- const narrows = narrowsExecutionAuthority(current, permissionMode);
747
- if (narrows && this.runtimeKernel.hasActiveRuns(sessionId)) {
748
- throw new SessionConfigurationTransitionError('session_busy', 'Execution boundary cannot change while a Turn is running');
749
- }
750
- if (header.status === 'waiting_for_user') {
751
- throw new SessionConfigurationTransitionError('session_busy', 'Execution boundary cannot change while an Interaction is pending');
752
- }
753
- const boundary = await this.commitExecutionBoundaryTransition(sessionId, current, permissionMode, async () => () => this.deps.store.setExecutionBoundaryKind(sessionId, kind, { permissionMode }));
754
- return boundary;
755
- }
756
782
  async commitExecutionBoundaryTransition(sessionId, current, nextPermissionMode, prepareCommit) {
757
783
  const prepareBoundaryCommit = async () => {
758
784
  const latest = await this.deps.store.readExecutionBoundary(sessionId);
@@ -3023,10 +3049,10 @@ export class SessionManager {
3023
3049
  * dead, so that state remains `continuation_started_indeterminate` and is
3024
3050
  * deliberately left non-terminal.
3025
3051
  */
3026
- async recoverContinuationClaimsBeforeProvider(sessionId, authority, _policy) {
3052
+ async recoverContinuationClaimsBeforeProvider(sessionId, authority, _policy, recoveryStates) {
3027
3053
  if (!this.deps.runStore)
3028
3054
  return false;
3029
- const states = await authority.listContinuationClaimsForRecovery(sessionId);
3055
+ const states = recoveryStates ?? (await authority.listContinuationClaimsForRecovery(sessionId));
3030
3056
  let recovered = false;
3031
3057
  for (const initialState of states) {
3032
3058
  const { claim } = initialState;
@@ -3097,24 +3123,41 @@ export class SessionManager {
3097
3123
  }
3098
3124
  return recovered;
3099
3125
  }
3100
- async recoverAgentRunsFromLedger(sessionId, policy = { kind: 'best_effort' }) {
3126
+ async recoverAgentRunsFromLedger(sessionId, policy = { kind: 'best_effort' }, recoveryInventory, recoveryClaimRunIds) {
3101
3127
  if (!this.deps.runStore || !this.deps.runtimeEventStore)
3102
3128
  return { hasLedger: false, recovered: false };
3103
3129
  // The importer may have committed only a prefix before a restart. Sealing
3104
3130
  // that prefix here would make the next read skip the unconverted history.
3105
- const runs = (await this.listInvocations(sessionId)).filter((run) => !isTranscriptLedgerInvocation(run));
3106
- 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)
3107
3141
  return { hasLedger: false, recovered: false };
3142
+ let claimOwnedUnsettledRunIds = recoveryClaimRunIds;
3108
3143
  const continuationAuthority = runtimeContinuationAuthority(this.deps.runtimeEventStore);
3109
- const claimOwnedUnsettledRunIds = new Set();
3110
- if (continuationAuthority) {
3144
+ if (!claimOwnedUnsettledRunIds && continuationAuthority) {
3145
+ const discovered = new Set();
3111
3146
  for (const state of await continuationAuthority.listContinuationClaimsForRecovery(sessionId)) {
3112
3147
  const events = await continuationAuthority.readImmutableRuntimeEvents(state.claim.target.sessionId, state.claim.target.runId);
3113
- if (!events.some(isTerminalRuntimeEvent)) {
3114
- claimOwnedUnsettledRunIds.add(state.claim.target.runId);
3115
- }
3148
+ if (!events.some(isTerminalRuntimeEvent))
3149
+ discovered.add(state.claim.target.runId);
3116
3150
  }
3151
+ claimOwnedUnsettledRunIds = discovered;
3117
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;
3118
3161
  // Read once per recovered session, and only when a failure actually needs
3119
3162
  // attributing, so healthy sessions pay nothing for the query.
3120
3163
  let boundaryClosures;
@@ -3127,11 +3170,14 @@ export class SessionManager {
3127
3170
  return boundaryClosures;
3128
3171
  };
3129
3172
  let recovered = false;
3130
- for (const run of runs) {
3131
- if (policy.kind === 'strict') {
3132
- await policy.stores.agentRunStore.readEventsForRecovery(sessionId, run.runId);
3133
- }
3134
- 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
+ });
3135
3181
  if (inspected.sourceHealth.runtimeLedger === 'read_failed') {
3136
3182
  if (policy.kind === 'strict') {
3137
3183
  throw new Error(`RuntimeEvent ledger is unreadable for run ${run.runId}`);
@@ -3151,7 +3197,7 @@ export class SessionManager {
3151
3197
  // restart repair must neither replay its tools nor manufacture failure.
3152
3198
  continue;
3153
3199
  }
3154
- if (claimOwnedUnsettledRunIds.has(run.runId) &&
3200
+ if (claimOwnedUnsettledRunIds?.has(run.runId) &&
3155
3201
  !inspected.runtimeEvents.some(isTerminalRuntimeEvent)) {
3156
3202
  // Every unresolved claim target belongs to the claim saga. This
3157
3203
  // includes claim-only, deterministic repair-start, and live provider
@@ -3176,7 +3222,13 @@ export class SessionManager {
3176
3222
  continue;
3177
3223
  }
3178
3224
  if (interruptedOutcomes.length > 0) {
3179
- 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
+ });
3180
3232
  }
3181
3233
  }
3182
3234
  const terminalLedger = classifyTerminalRuntimeLedger(run, inspected.runtimeEvents);
@@ -3273,6 +3325,16 @@ function continuationExecutionErrorClass(error) {
3273
3325
  }
3274
3326
  return error instanceof Error ? error.name : 'unknown';
3275
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
+ }
3276
3338
  const MAX_BEST_EFFORT_OUTCOME_COMMIT_ATTEMPTS = 2;
3277
3339
  function listSessionsForRecovery(store, policy) {
3278
3340
  return policy.kind === 'strict' ? policy.stores.sessionStore.listForRecovery() : store.list();
@@ -3404,7 +3466,12 @@ export function headerToSummary(h) {
3404
3466
  ...(h.revisionIndex !== undefined ? { revisionIndex: h.revisionIndex } : {}),
3405
3467
  ...(h.revisionState ? { revisionState: h.revisionState } : {}),
3406
3468
  backend: h.backend,
3407
- ...(h.executorId ? { executorId: h.executorId } : {}),
3469
+ ...(h.executorId
3470
+ ? {
3471
+ executorId: h.executorId,
3472
+ ...(h.executorConfig ? { executorConfig: h.executorConfig } : {}),
3473
+ }
3474
+ : {}),
3408
3475
  ...(h.llmConnectionId === undefined ? {} : { llmConnectionId: h.llmConnectionId }),
3409
3476
  llmConnectionSlug: h.llmConnectionSlug,
3410
3477
  connectionLocked: h.connectionLocked,
@@ -3540,23 +3607,10 @@ function claimedAgentGraphIntentResult(claim, result) {
3540
3607
  ...result,
3541
3608
  };
3542
3609
  }
3543
- function sessionConfigurationWithPermissionMode(header, permissionMode) {
3544
- return {
3545
- backend: header.backend,
3546
- executorId: header.executorId,
3547
- llmConnectionId: header.llmConnectionId,
3548
- llmConnectionSlug: header.llmConnectionSlug,
3549
- connectionLocked: header.connectionLocked,
3550
- model: header.model,
3551
- thinkingLevel: header.thinkingLevel,
3552
- permissionMode,
3553
- collaborationMode: header.collaborationMode ?? 'agent',
3554
- orchestrationMode: header.orchestrationMode ?? 'default',
3555
- };
3556
- }
3557
3610
  function sessionConfigurationMatchesExceptPermissionMode(header, configuration) {
3558
3611
  return (header.backend === configuration.backend &&
3559
3612
  header.executorId === configuration.executorId &&
3613
+ header.executorConfig?.model === configuration.executorConfig?.model &&
3560
3614
  header.llmConnectionId === configuration.llmConnectionId &&
3561
3615
  header.llmConnectionSlug === configuration.llmConnectionSlug &&
3562
3616
  header.connectionLocked === configuration.connectionLocked &&
@@ -3569,15 +3623,6 @@ function sessionConfigurationMatches(header, configuration) {
3569
3623
  return (header.permissionMode === configuration.permissionMode &&
3570
3624
  sessionConfigurationMatchesExceptPermissionMode(header, configuration));
3571
3625
  }
3572
- function executionBoundaryMatchesPermissionMode(boundary, mode) {
3573
- if (mode === 'bypass')
3574
- return boundary.kind === 'bypass';
3575
- if (boundary.kind !== 'managed')
3576
- return false;
3577
- return mode === 'explore'
3578
- ? boundary.profile.name === 'read-only'
3579
- : boundary.profile.name !== 'read-only';
3580
- }
3581
3626
  function narrowsExecutionAuthority(boundary, nextPermissionMode) {
3582
3627
  if (nextPermissionMode === 'bypass')
3583
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
  }
@@ -28,7 +28,7 @@ export { LOCAL_RUNTIME_HOST_PROFILE, RUNTIME_HOST_ACCESS_CREDENTIAL_MAX_BYTES, c
28
28
  export { createRuntimeHostReconnectingConnection, isRuntimeHostReconnectingConnection, } from './reconnecting-connection.js';
29
29
  export { RuntimeHostSshOperatorActivationError, activateRuntimeHostSshOperator, runtimeHostSshOperatorRemoteCommand, } from './ssh-operator-activation.js';
30
30
  export { normalizeRuntimeHostSshDestination, openRuntimeHostSshTunnel, } from './ssh-tunnel.js';
31
- export { RuntimeHostPermanentReconnectError, startRuntimeHostReconnectLifecycle, } from './reconnect-lifecycle.js';
31
+ export { RuntimeHostPermanentReconnectError, createRuntimeHostReconnectLifecycle, startRuntimeHostReconnectLifecycle, } from './reconnect-lifecycle.js';
32
32
  export { RUNTIME_HOST_REMOTE_INCOMPATIBLE_CODE, RuntimeHostRemoteCompatibilityError, } from './remote-compatibility-error.js';
33
33
  export { RuntimeHostSubscriptionError, SessionRemovedSubscriptionError, subscriptionClosedError, } from './session-subscription.js';
34
34
  export { RuntimeHostStartupError, runtimeHostStartupError, } from './startup-error.js';
@@ -26,8 +26,15 @@ export class RuntimeHostPermanentReconnectError extends Error {
26
26
  this.name = 'RuntimeHostPermanentReconnectError';
27
27
  }
28
28
  }
29
+ /**
30
+ * A lifecycle that exists before its first connection. `waitForCurrent` parks
31
+ * until `start()` installs one and rejects if the start fails permanently.
32
+ */
33
+ export function createRuntimeHostReconnectLifecycle(input) {
34
+ return new RuntimeHostReconnectLifecycleImpl(input);
35
+ }
29
36
  export async function startRuntimeHostReconnectLifecycle(input) {
30
- const lifecycle = new RuntimeHostReconnectLifecycleImpl(input);
37
+ const lifecycle = createRuntimeHostReconnectLifecycle(input);
31
38
  await lifecycle.start();
32
39
  return lifecycle;
33
40
  }
@@ -54,7 +54,12 @@ export function projectSessionCatalogSummary(session) {
54
54
  ...(session.revisionIndex === undefined ? {} : { revisionIndex: session.revisionIndex }),
55
55
  ...(session.revisionState === undefined ? {} : { revisionState: session.revisionState }),
56
56
  backend: session.backend,
57
- ...(session.executorId ? { executorId: session.executorId } : {}),
57
+ ...(session.executorId
58
+ ? {
59
+ executorId: session.executorId,
60
+ ...(session.executorConfig ? { executorConfig: session.executorConfig } : {}),
61
+ }
62
+ : {}),
58
63
  ...(session.llmConnectionId === null ? {} : { llmConnectionId: session.llmConnectionId }),
59
64
  llmConnectionSlug: session.llmConnectionSlug,
60
65
  connectionLocked: session.connectionLocked,
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ export const INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID = 'maka.interactive';
@@ -28,6 +28,7 @@ import { decodeProjectCatalogChangedFrame, } from './project-catalog-change.js';
28
28
  import { decodeConnectionCatalogChangedFrame, } from './connection-catalog-change.js';
29
29
  import { decodeRequestFrame, decodeResponseFrame, } from './operations.js';
30
30
  import { isCanonicalRuntimeHostWebSocketPath } from './websocket-path.js';
31
+ import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID } from '../composition-identity.js';
31
32
  export * from './access-authority.js';
32
33
  export * from './agent-graph.js';
33
34
  export * from './interaction.js';
@@ -58,11 +59,19 @@ export * from './session-todo.js';
58
59
  export * from './workspace.js';
59
60
  export * from './workhub-coordination.js';
60
61
  export * from './websocket-path.js';
62
+ export { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID } from '../composition-identity.js';
61
63
  export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
62
64
  export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
63
65
  // Increment when the same protocol version no longer guarantees safe Client-Host
64
66
  // interoperability. Mismatches are rejected before domain commands are admitted.
65
- export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 177;
67
+ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 183;
68
+ // 183: Jev policy snapshots, set_jev mutation and credential locator require matching peers.
69
+ // 182: Executor catalogs expose structured model families and thinking variant IDs.
70
+ // 181: Canonical executor models and retained provider stop reasons after cancellation.
71
+ // 180: Reject contradictory executor configuration and legacy model targets.
72
+ // 179: Generic executor discovery and confirmed per-Session configuration.
73
+ // 178: WorkHub result turns carry a Host-owned workhub_result origin. Older
74
+ // Clients reject that origin and cannot render the result notification.
66
75
  // 177: External Session import input may carry an optional Host-resolved
67
76
  // workspace target. Epoch-176 peers reject the unknown `workspace` key.
68
77
  // 176: Session-scoped capability publication and MCP admission require compatible
@@ -415,7 +424,6 @@ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 177;
415
424
  // one transport message; narrower domains retain their own encoded limits.
416
425
  export const RUNTIME_HOST_MAX_MESSAGE_BYTES = 768 * 1024;
417
426
  export const RUNTIME_HOST_MAX_IN_FLIGHT_DOMAIN_REQUESTS = 64;
418
- export const INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID = 'maka.interactive';
419
427
  export function negotiateProtocol(client, host) {
420
428
  validateProtocolRange(client);
421
429
  validateProtocolRange(host);