maka-agent 0.2.0-dev.12.20260901 → 0.2.0-dev.14.20260902

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 (105) hide show
  1. package/THIRD_PARTY_NOTICES.txt +2 -2
  2. package/dist/acp/maka-acp-agent.js +6 -3
  3. package/dist/acp/session-registry.js +307 -0
  4. package/dist/acp/stdio-server.js +20 -1
  5. package/dist/cli-core.js +15 -2
  6. package/dist/pi-transcript.js +1 -1
  7. package/dist/pi-tui-layout.js +1 -1
  8. package/dist/pi-tui-mcp-status.js +1 -1
  9. package/dist/pi-tui-runner.js +1 -1
  10. package/dist/runtime-host-access-command.js +37 -4
  11. package/dist/runtime-host-capability-provider-command.js +16 -2
  12. package/dist/runtime-host-cli-context.js +73 -3
  13. package/dist/runtime-host-cli.js +37 -6
  14. package/dist/runtime-host-run-command.js +3 -1
  15. package/dist/runtime-host-update-command.js +59 -31
  16. package/dist/tui-copy-catalog.js +4 -2
  17. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  18. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  19. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  20. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  21. package/node_modules/@maka/core/dist/attachments.js +12 -0
  22. package/node_modules/@maka/core/dist/connection-error-copy.js +17 -34
  23. package/node_modules/@maka/core/dist/events.js +0 -13
  24. package/node_modules/@maka/core/dist/maka-wordmark.js +23 -0
  25. package/node_modules/@maka/core/dist/model-catalog.js +22 -1
  26. package/node_modules/@maka/core/dist/runtime-event.js +1 -0
  27. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +50 -1
  28. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +9 -1
  29. package/node_modules/@maka/core/dist/runtime-policy/model-catalog-entry-codec.js +10 -1
  30. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +75 -0
  31. package/node_modules/@maka/core/dist/runtime-policy.js +4 -2
  32. package/node_modules/@maka/core/dist/session.js +1 -0
  33. package/node_modules/@maka/core/dist/settings.js +10 -2
  34. package/node_modules/@maka/core/dist/usage-record-schema.js +11 -1
  35. package/node_modules/@maka/core/package.json +2 -1
  36. package/node_modules/@maka/mcp/dist/index.js +1 -0
  37. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +52 -80
  38. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +178 -140
  39. package/node_modules/@maka/runtime/dist/archive-read-tool.js +26 -5
  40. package/node_modules/@maka/runtime/dist/builtin-tools.js +27 -2
  41. package/node_modules/@maka/runtime/dist/context-budget-policy.js +0 -12
  42. package/node_modules/@maka/runtime/dist/conversation-copy.js +66 -43
  43. package/node_modules/@maka/runtime/dist/durable-tool-result-projection.js +31 -0
  44. package/node_modules/@maka/runtime/dist/history-compact-error.js +10 -2
  45. package/node_modules/@maka/runtime/dist/history-compact-input-fit.js +7 -1
  46. package/node_modules/@maka/runtime/dist/history-compaction.js +13 -22
  47. package/node_modules/@maka/runtime/dist/model-history.js +41 -16
  48. package/node_modules/@maka/runtime/dist/provider-image-overflow-recovery.js +12 -31
  49. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +3 -0
  50. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +13 -2
  51. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -6
  52. package/node_modules/@maka/runtime/dist/shell-tools.js +77 -1
  53. package/node_modules/@maka/runtime/dist/tool-availability.js +74 -5
  54. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +333 -52
  55. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +34 -3
  56. package/node_modules/@maka/runtime/dist/tool-runtime.js +12 -0
  57. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +0 -3
  58. package/node_modules/@maka/runtime-host/dist/client/catalog-reader.js +40 -13
  59. package/node_modules/@maka/runtime-host/dist/client/connection.js +50 -31
  60. package/node_modules/@maka/runtime-host/dist/client/host-profile.js +48 -4
  61. package/node_modules/@maka/runtime-host/dist/client/index.js +2 -2
  62. package/node_modules/@maka/runtime-host/dist/client/owner-connection-code.js +32 -1
  63. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +22 -4
  64. package/node_modules/@maka/runtime-host/dist/operator/access-management-frame.js +10 -1
  65. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +56 -12
  66. package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +3 -0
  67. package/node_modules/@maka/runtime-host/dist/protocol/configuration.js +55 -9
  68. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +37 -0
  69. package/node_modules/@maka/runtime-host/dist/protocol/index.js +17 -1
  70. package/node_modules/@maka/runtime-host/dist/protocol/network-proxy.js +1 -7
  71. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +2 -0
  72. package/node_modules/@maka/runtime-host/dist/protocol/runtime-policy.js +48 -1
  73. package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +4 -2
  74. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +2 -12
  75. package/node_modules/@maka/runtime-host/dist/server/access-authority.js +2 -1
  76. package/node_modules/@maka/runtime-host/dist/server/agent-graph-execution-coordinator.js +1 -2
  77. package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +0 -9
  78. package/node_modules/@maka/runtime-host/dist/server/configuration-coordinator.js +20 -1
  79. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +46 -24
  80. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +4 -1
  81. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +15 -0
  82. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +8 -4
  83. package/node_modules/@maka/runtime-host/dist/server/network-proxy-coordinator.js +0 -1
  84. package/node_modules/@maka/runtime-host/dist/server/peer-listener.js +3 -0
  85. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +28 -1
  86. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +3 -3
  87. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +35 -3
  88. package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +16 -0
  89. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +189 -6
  90. package/node_modules/@maka/storage/dist/runtime-policy-stores.js +4 -1
  91. package/node_modules/@maka/storage/dist/settings-store.js +16 -25
  92. package/node_modules/fast-uri/index.js +215 -47
  93. package/node_modules/fast-uri/lib/schemes.js +9 -4
  94. package/node_modules/fast-uri/lib/utils.js +396 -92
  95. package/node_modules/fast-uri/package.json +1 -1
  96. package/node_modules/qs/.github/THREAT_MODEL.md +3 -3
  97. package/node_modules/qs/CHANGELOG.md +15 -0
  98. package/node_modules/qs/README.md +19 -1
  99. package/node_modules/qs/dist/qs.js +21 -21
  100. package/node_modules/qs/eslint.config.mjs +1 -1
  101. package/node_modules/qs/lib/parse.js +3 -4
  102. package/node_modules/qs/lib/stringify.js +23 -8
  103. package/node_modules/qs/lib/utils.js +10 -3
  104. package/node_modules/qs/package.json +4 -4
  105. package/package.json +1 -1
@@ -19,7 +19,7 @@
19
19
  import { createHash, randomUUID } from 'node:crypto';
20
20
  import { MAX_READ_IMAGE_BYTES } from '@maka/core/attachments';
21
21
  import { messageContentDigest, normalizeMessageContent } from '@maka/core/events';
22
- import { describeChatConfigurationReason, NO_REAL_CONNECTION_CODE, } from '@maka/core/connection-error-copy';
22
+ import { NO_REAL_CONNECTION_CODE } from '@maka/core/connection-error-copy';
23
23
  import { generalizedErrorMessage } from '@maka/core/redaction';
24
24
  import { emptyPlanSessionState } from '@maka/core/plan';
25
25
  import { isDeepResearchSession, WORKHUB_COORDINATION_SESSION_ID, WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION, } from '@maka/core/session';
@@ -42,8 +42,9 @@ import { SessionActivityRegistry } from '@maka/runtime/goal-turn-lifecycle';
42
42
  import { ShellRunProcessManager } from '@maka/runtime/shell-run-manager';
43
43
  import { resolveShellPlan, resolveTurnShellPlan, validateShellPreference, } from '@maka/runtime/shell-detect';
44
44
  import { createAgentGraphControlStore } from '@maka/storage/agent-graph-control-store';
45
- import { createArtifactAttachmentResourceReader, createReadImageSnapshotter, } from '@maka/storage/artifact-stores';
46
- import { isSessionNotFoundError, SessionMetadataConflictError, } from '@maka/storage/execution-stores';
45
+ import { createArtifactAttachmentResourceReader } from '@maka/storage/artifact-stores';
46
+ import { createReadImageSnapshotStore } from '@maka/storage/read-image-snapshot-store';
47
+ import { isSessionNotFoundError } from '@maka/storage/execution-stores';
47
48
  import { createExternalSessionAdapterRegistry } from '@maka/storage/external-sessions';
48
49
  import { createGitWorktreeChildExecutor } from '@maka/storage/git-worktree-child-executor';
49
50
  import { runWithStorageRootLease } from '@maka/storage/root-authority';
@@ -121,22 +122,23 @@ import { createHostWebSearchService, createHostWebSearchToolFromService, resolve
121
122
  import { createHostWebFetchService, createHostWebFetchToolFromService } from './web-fetch-tool.js';
122
123
  import { createHostExecutionArtifactServices } from './execution-artifacts.js';
123
124
  import { createRuntimeHostWorkspaceExecutionComposition, RuntimeHostWorkspaceExecutionError, } from './workspace-execution-composition.js';
124
- const CONTEXT_OFFLOAD_READER_LIMITS = Object.freeze({
125
+ const GIBIBYTE = 1024 * 1024 * 1024;
126
+ const CONTEXT_OFFLOAD_LIMITS = Object.freeze({
125
127
  ownerMaxBytes: Object.freeze({
126
128
  read_image_snapshot: MAX_READ_IMAGE_BYTES,
127
129
  tool_result_archive: 0,
128
130
  }),
129
- // This expand slice opens only the reader path. Zero quotas make accidental
130
- // non-empty puts fail closed until the writer/lifecycle cutover lands.
131
- sessionLogicalBytes: 0,
132
- workspacePhysicalBytes: 0,
131
+ // Read images are bounded individually and logically per Session. Physical
132
+ // bytes are content-addressed across Sessions and bounded per workspace.
133
+ sessionLogicalBytes: GIBIBYTE,
134
+ workspacePhysicalBytes: 20 * GIBIBYTE,
133
135
  });
134
136
  export function runtimeHostFilesystemWorkerRuntime(versions) {
135
137
  return versions.electron ? 'electron' : 'node';
136
138
  }
137
139
  export async function createExecutionRuntimeHostComposition(context, options = {}, dependencies = {}) {
138
140
  const storage = await openStorageWriterComposition(context.owner.lease, {
139
- contextOffloadLimits: CONTEXT_OFFLOAD_READER_LIMITS,
141
+ contextOffloadLimits: CONTEXT_OFFLOAD_LIMITS,
140
142
  afterRuntimePolicyOpened: async (stores) => {
141
143
  if (options.bootstrapRuntimePolicy !== false) {
142
144
  await ensureBootstrapRuntimePolicy({
@@ -148,7 +150,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
148
150
  },
149
151
  });
150
152
  if (storage.contextOffloadUnavailable) {
151
- console.error(`[runtime-host] optional context-offload reader could not be opened: ${generalizedErrorMessage(storage.contextOffloadUnavailable.cause)}`);
153
+ console.error(`[runtime-host] optional context-offload Store could not be opened: ${generalizedErrorMessage(storage.contextOffloadUnavailable.cause)}`);
152
154
  }
153
155
  const stores = storage.execution;
154
156
  let graphControlStore;
@@ -180,6 +182,25 @@ export async function createExecutionRuntimeHostComposition(context, options = {
180
182
  const openedContextOffloadReader = openedContextOffloadStore
181
183
  ? createInteractiveContextOffloadReader(openedContextOffloadStore)
182
184
  : undefined;
185
+ const contextOffloadAuthority = openedContextOffloadStore
186
+ ? openedContextOffloadStore
187
+ : storage.contextOffloadUnavailable
188
+ ? {
189
+ copyReferences: async () => {
190
+ throw new Error('Context-offload Store is unavailable during Session copy', {
191
+ cause: storage.contextOffloadUnavailable?.cause,
192
+ });
193
+ },
194
+ retireSession: async (_sessionId) => {
195
+ throw new Error('Context-offload Store is unavailable during Session retirement', {
196
+ cause: storage.contextOffloadUnavailable?.cause,
197
+ });
198
+ },
199
+ collectGarbage: async () => {
200
+ throw new Error('Context-offload Store is unavailable during context garbage collection', { cause: storage.contextOffloadUnavailable?.cause });
201
+ },
202
+ }
203
+ : undefined;
183
204
  const openedUsageStores = storage.usage;
184
205
  const openedShellRunStore = storage.shellRuns;
185
206
  const worktreeChildExecutor = createGitWorktreeChildExecutor({
@@ -194,7 +215,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
194
215
  // product's existing answer for these rows: this task came from the retired
195
216
  // local simulation, configure a real model and start a new one.
196
217
  backends.register('fake', () => {
197
- throw new Error(`${NO_REAL_CONNECTION_CODE}:fake_backend: ${describeChatConfigurationReason('fake_backend')}`);
218
+ throw new Error(`${NO_REAL_CONNECTION_CODE}:fake_backend: Runtime Host cannot send on a retired local-simulation connection; configure a real model and start a new task`);
198
219
  });
199
220
  const runtimePolicyActivation = new RuntimePolicyActivationGate();
200
221
  const runtimePolicy = new HostRuntimePolicyCoordinator(runtimePolicyStores, runtimePolicyActivation, applyRuntimePolicyMutationEffects, async (input) => {
@@ -270,7 +291,18 @@ export async function createExecutionRuntimeHostComposition(context, options = {
270
291
  }),
271
292
  backgroundTasks: runtimeResources,
272
293
  ptyControls: runtimeResources,
273
- snapshotImage: createReadImageSnapshotter(openedArtifactStore),
294
+ ...(openedContextOffloadStore
295
+ ? {
296
+ snapshotImage: async (input) => createReadImageSnapshotStore(openedContextOffloadStore, input.sessionId).snapshot({
297
+ ownerId: input.ownerId,
298
+ bytes: input.bytes,
299
+ mimeType: input.mimeType,
300
+ }),
301
+ releaseImageSnapshot: async (input) => {
302
+ await openedContextOffloadStore.releaseReference(input);
303
+ },
304
+ }
305
+ : {}),
274
306
  ...(sandboxManager ? { sandboxManager } : {}),
275
307
  ...(filesystemWorker ? { filesystemWorker } : {}),
276
308
  };
@@ -1215,6 +1247,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
1215
1247
  stores,
1216
1248
  artifacts: openedArtifactStore,
1217
1249
  sessionTodo: sessionTodoStore,
1250
+ ...(contextOffloadAuthority ? { contextOffload: contextOffloadAuthority } : {}),
1218
1251
  manager,
1219
1252
  admission: sessionAdmission,
1220
1253
  continuity: continuityCoordinator,
@@ -1239,18 +1272,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
1239
1272
  continuity: continuityCoordinator,
1240
1273
  artifacts: openedArtifactStore,
1241
1274
  sessionTodo: sessionTodoStore,
1242
- assertNoContextOffloadReferences: async (sessionIds) => {
1243
- if (!openedContextOffloadStore) {
1244
- throw new Error('Context-offload reader is unavailable during Session removal', {
1245
- cause: storage.contextOffloadUnavailable?.cause,
1246
- });
1247
- }
1248
- for (const sessionId of sessionIds) {
1249
- if ((await openedContextOffloadStore.usage(sessionId)).references > 0) {
1250
- throw new SessionMetadataConflictError('Session removal does not support Session context references yet');
1251
- }
1252
- }
1253
- },
1275
+ ...(contextOffloadAuthority ? { contextOffload: contextOffloadAuthority } : {}),
1254
1276
  purgeOperationalState: async (sessionId) => {
1255
1277
  await stores.purgeConversationOperationalState(sessionId);
1256
1278
  await openedPlanStore.purgeSessionState(sessionId);
@@ -55,6 +55,9 @@ export async function startExecutionRuntimeHostService(options, dependencies = {
55
55
  ...options.peer,
56
56
  dataRoot: options.peer.meshDataRoot,
57
57
  endpointKind: 'host',
58
+ onBackgroundReconcileError: (error) => {
59
+ console.error('[runtime-host] Peer Mesh background synchronization failed:', error);
60
+ },
58
61
  });
59
62
  }
60
63
  catch (error) {
@@ -109,7 +112,7 @@ export async function startExecutionRuntimeHostService(options, dependencies = {
109
112
  throw error;
110
113
  }
111
114
  }
112
- function attachPeerOwnerCleanup(listeners, owner) {
115
+ export function attachPeerOwnerCleanup(listeners, owner) {
113
116
  return {
114
117
  ...listeners,
115
118
  cleanup: async () => {
@@ -569,6 +569,7 @@ export class RuntimeHostKernel {
569
569
  return outcome;
570
570
  }
571
571
  #statusSnapshot() {
572
+ const peer = this.peerListeners[0];
572
573
  return {
573
574
  hostEpoch: this.hostEpoch,
574
575
  compositionId: this.compositionDescriptor.id,
@@ -577,6 +578,15 @@ export class RuntimeHostKernel {
577
578
  connections: this.#acceptedTransports.size,
578
579
  activeOperations: this.#activeOperations,
579
580
  activeResidencies: this.#residencies.activeCount,
581
+ ...(peer
582
+ ? {
583
+ peerEndpoint: {
584
+ peerId: peer.peerId,
585
+ routeHints: peer.listenAddresses,
586
+ coordinationRelays: peer.coordinationRelays,
587
+ },
588
+ }
589
+ : {}),
580
590
  };
581
591
  }
582
592
  #activitySnapshot() {
@@ -588,6 +598,11 @@ export class RuntimeHostKernel {
588
598
  };
589
599
  }
590
600
  #hasUpgradeBlockingActivity() {
601
+ // The request's own accepted transport is expected. Any other live
602
+ // connection arrived after discovery or remained attached and therefore
603
+ // requires explicit interruption authority before retirement.
604
+ if (this.#acceptedTransports.size > 1)
605
+ return true;
591
606
  if (this.#activeCommandOperations > 1)
592
607
  return true;
593
608
  return this.#residencies.snapshot().some(({ label }) => label !== 'process-retention');
@@ -51,16 +51,20 @@ export async function startRuntimeHostAuthenticatedListenerSet(input, options) {
51
51
  }
52
52
  export function createRuntimeHostListenerSet(local, additional = []) {
53
53
  const listeners = Object.freeze([local, ...additional]);
54
+ const peerListeners = Object.freeze(additional.filter(isRuntimeHostPeerListener).map((listener) => Object.freeze({
55
+ peerId: listener.peerId,
56
+ listenAddresses: Object.freeze([...listener.listenAddresses]),
57
+ get coordinationRelays() {
58
+ return Object.freeze([...listener.coordinationRelays]);
59
+ },
60
+ })));
54
61
  return {
55
62
  listeners,
56
63
  localEndpoint: local.endpoint,
57
64
  websocketEndpoints: Object.freeze(additional
58
65
  .filter((listener) => listener.kind === 'websocket')
59
66
  .map((listener) => listener.endpoint)),
60
- peerListeners: Object.freeze(additional.filter(isRuntimeHostPeerListener).map((listener) => Object.freeze({
61
- peerId: listener.peerId,
62
- listenAddresses: Object.freeze([...listener.listenAddresses]),
63
- }))),
67
+ peerListeners,
64
68
  closeAdmission: () => settleListeners(listeners, (listener) => listener.closeAdmission()),
65
69
  cleanup: () => settleListeners([...listeners].reverse(), (listener) => listener.cleanup()),
66
70
  };
@@ -29,7 +29,6 @@ export class HostNetworkProxyCoordinator {
29
29
  try {
30
30
  const resolved = await this.policy.resolveNetworkProxyExecution({
31
31
  ...(input.networkProxy ? { networkProxy: input.networkProxy } : {}),
32
- ...(input.password ? { secretOverride: input.password } : {}),
33
32
  });
34
33
  if (resolved.kind === 'credential_not_configured') {
35
34
  return {
@@ -66,6 +66,9 @@ class RuntimeHostPeerListener {
66
66
  .serveApplication((stream) => this.#acceptStream(stream), this.#serveLifetime.signal)
67
67
  .catch(captureFailure);
68
68
  }
69
+ get coordinationRelays() {
70
+ return this.#client.identity().coordinationRelays;
71
+ }
69
72
  closeAdmission() {
70
73
  this.#closeAdmissionTask ??= (async () => {
71
74
  this.#admitting = false;
@@ -29,6 +29,7 @@ export class HostRuntimePolicyCoordinator {
29
29
  handlers = {
30
30
  'runtime.policy.query': () => this.#queryPolicy(),
31
31
  'runtime.policy.mutate': (input) => this.#mutatePolicy(input),
32
+ 'runtime.policy.network-proxy.update': (input) => this.#updateNetworkProxy(input),
32
33
  'connection.catalog.query': (input) => this.#queryCatalog(input),
33
34
  'connection.catalog.create': (input) => this.#createConnection(input),
34
35
  'connection.catalog.update': (input) => this.#updateConnection(input),
@@ -65,6 +66,27 @@ export class HostRuntimePolicyCoordinator {
65
66
  return projectPolicyMutation(await this.#stores.runtimePolicy.mutate(input));
66
67
  });
67
68
  }
69
+ async #updateNetworkProxy(input) {
70
+ return this.#storeMutation(async () => {
71
+ try {
72
+ await this.validateMutation({
73
+ expectedRevision: input.expectedPolicyRevision,
74
+ operation: { kind: 'set_network_proxy', value: input.networkProxy },
75
+ });
76
+ }
77
+ catch (error) {
78
+ throw new RuntimePolicyStoreError('invalid_policy_input', 'Network proxy update failed Host validation', { cause: error });
79
+ }
80
+ const result = await this.#stores.operations.updateNetworkProxy(input);
81
+ return result.kind === 'committed'
82
+ ? {
83
+ kind: 'committed',
84
+ revision: result.snapshot.revision,
85
+ credentialStatus: result.credentialStatus,
86
+ }
87
+ : result;
88
+ });
89
+ }
68
90
  async #queryCatalog(input) {
69
91
  const stored = await this.#storeQuery(() => this.#stores.connectionCatalog.getSnapshot());
70
92
  if (!stored.ok)
@@ -143,7 +165,9 @@ export class HostRuntimePolicyCoordinator {
143
165
  async #setCredential(input) {
144
166
  return this.#storeMutation(async () => {
145
167
  const result = await this.#stores.credentialVault.set(input);
146
- if (result.kind === 'connection_not_found' || result.kind === 'credential_stale') {
168
+ if (result.kind === 'connection_not_found' ||
169
+ result.kind === 'connection_stale' ||
170
+ result.kind === 'credential_stale') {
147
171
  return result;
148
172
  }
149
173
  const status = result.snapshot.entries.find((entry) => sameLocator(entry.locator, input.locator));
@@ -156,6 +180,9 @@ export class HostRuntimePolicyCoordinator {
156
180
  async #deleteCredential(input) {
157
181
  return this.#storeMutation(async () => {
158
182
  const result = await this.#stores.credentialVault.delete(input);
183
+ if (result.kind === 'connection_stale') {
184
+ throw invariantFailure('Credential deletion returned an impossible connection conflict');
185
+ }
159
186
  if (result.kind === 'connection_not_found' || result.kind === 'credential_stale') {
160
187
  return result;
161
188
  }
@@ -68,7 +68,7 @@ export class HostSessionRetirementCoordinator {
68
68
  #continuity;
69
69
  #artifacts;
70
70
  #sessionTodo;
71
- #assertNoContextOffloadReferences;
71
+ #contextOffload;
72
72
  #purgeOperationalState;
73
73
  #purgeAgentGraphState;
74
74
  #worktrees;
@@ -95,7 +95,7 @@ export class HostSessionRetirementCoordinator {
95
95
  this.#continuity = options.continuity;
96
96
  this.#artifacts = options.artifacts;
97
97
  this.#sessionTodo = options.sessionTodo;
98
- this.#assertNoContextOffloadReferences = options.assertNoContextOffloadReferences;
98
+ this.#contextOffload = options.contextOffload;
99
99
  this.#purgeOperationalState = options.purgeOperationalState;
100
100
  this.#purgeAgentGraphState = options.purgeAgentGraphState;
101
101
  this.#worktrees = options.worktrees;
@@ -201,7 +201,6 @@ export class HostSessionRetirementCoordinator {
201
201
  if (plan.archive.sessionIds.length > 0) {
202
202
  archiveHandles = await this.#prepareRetirement(plan.archive, 'archive');
203
203
  }
204
- await this.#assertNoContextOffloadReferences?.(plan.remove.sessionIds);
205
204
  const allSessionIds = [...plan.remove.sessionIds, ...plan.archive.sessionIds];
206
205
  await this.#finalizeWorkspacePatches(allSessionIds);
207
206
  await this.#disposeBackends(allSessionIds);
@@ -494,6 +493,7 @@ export class HostSessionRetirementCoordinator {
494
493
  purgeSessionSidecars({
495
494
  artifacts: this.#artifacts,
496
495
  sessionTodo: this.#sessionTodo,
496
+ ...(this.#contextOffload ? { contextOffload: this.#contextOffload } : {}),
497
497
  purgeOperationalState: this.#purgeOperationalState,
498
498
  }, sessionId),
499
499
  this.#purgeAgentGraphState(sessionId),
@@ -20,7 +20,7 @@ import { createHash, randomUUID } from 'node:crypto';
20
20
  import { isDeepResearchSession } from '@maka/core/deep-research';
21
21
  import { SIDE_CONVERSATION_SESSION_LABEL } from '@maka/core/side-conversation';
22
22
  import { isWorkHubCoordinationSessionId, isWorkHubCoordinationSessionTarget, sessionRevisionFamilyId, } from '@maka/core/session';
23
- import { archivedToolResultContainsLinkedChildReferences, archivedToolResultContainsConversationOwnedReferences, cloneConversationRuntimeLedger, collectConversationCopyLinkedChildReferences, collectConversationCopySessionFileRefs, createConversationCopySlice, prepareConversationRuntimeLedgerCopy, } from '@maka/runtime/conversation-copy';
23
+ import { archivedToolResultContainsLinkedChildReferences, archivedToolResultContainsConversationOwnedReferences, cloneConversationRuntimeLedger, collectConversationCopyLinkedChildReferences, collectConversationCopySessionContextRefIds, collectConversationCopySessionFileRefs, createConversationCopySlice, prepareConversationRuntimeLedgerCopy, } from '@maka/runtime/conversation-copy';
24
24
  import { isArchivedToolResultPlaceholder } from '@maka/runtime/context-budget';
25
25
  import { decodeModelProjectionTransition, MODEL_PROJECTION_TRANSITION_EVENT_TYPE, } from '@maka/core/model-projection-transition';
26
26
  import { authenticateInteractiveArtifactStoreWriter, } from '@maka/storage/artifact-stores';
@@ -52,7 +52,7 @@ export class HostSessionRevisionCoordinator {
52
52
  const copies = (await this.#stores.sessionStore.listHeaders()).filter((header) => header.conversationCopy !== undefined);
53
53
  for (const header of copies) {
54
54
  if (header.conversationCopy.state === 'preparing')
55
- await this.#discard(header);
55
+ await this.#discardDuringRecovery(header);
56
56
  }
57
57
  const committed = copies.filter((header) => header.conversationCopy.state === 'committed');
58
58
  const retained = new Set(committed
@@ -85,10 +85,18 @@ export class HostSessionRevisionCoordinator {
85
85
  await this.options.manager.commitRevisionVersion(header.id);
86
86
  }
87
87
  else {
88
- await this.#discard(header);
88
+ await this.#discardDuringRecovery(header);
89
89
  }
90
90
  }
91
91
  }
92
+ async #discardDuringRecovery(header) {
93
+ try {
94
+ await this.#discard(header);
95
+ }
96
+ catch (error) {
97
+ console.error(`[runtime-host] conversation copy cleanup deferred during recovery (${header.id}): ${conversationCopyCommitFailureDiagnostic(error)}`);
98
+ }
99
+ }
92
100
  async #copy(kind, input) {
93
101
  if (isWorkHubCoordinationSessionId(input.targetSessionId)) {
94
102
  return copyFailure('operation_conflict', 'Target Session identity is reserved for WorkHub coordination');
@@ -315,6 +323,28 @@ export class HostSessionRevisionCoordinator {
315
323
  .filter(({ serializedResult }) => archivedToolResultContainsLinkedChildReferences(serializedResult) ||
316
324
  archivedToolResultContainsConversationOwnedReferences(serializedResult, input.sourceSessionId, linkedReferences.references))
317
325
  .map(({ descriptor, serializedResult }) => [descriptor.artifactId, serializedResult]));
326
+ const sourceContextRefIds = collectConversationCopySessionContextRefIds({
327
+ sourceSessionId: input.sourceSessionId,
328
+ messages: slice.messages,
329
+ runtimeEvents: plan.runs.flatMap(({ runtimeEvents }) => runtimeEvents),
330
+ archivedResults: archivePreflight.serializedResults,
331
+ });
332
+ if (sourceContextRefIds.length > 0 && !this.options.contextOffload) {
333
+ throw new Error('Session context copy authority is unavailable');
334
+ }
335
+ const contextCopy = sourceContextRefIds.length === 0
336
+ ? { ok: true, copied: [] }
337
+ : await this.options.contextOffload.copyReferences({
338
+ sourceSessionId: input.sourceSessionId,
339
+ targetSessionId: input.targetSessionId,
340
+ references: sourceContextRefIds.map((sourceRefId) => ({
341
+ sourceRefId,
342
+ targetOwner: { kind: 'read_image_snapshot', ownerId: sourceRefId },
343
+ })),
344
+ });
345
+ if (!contextCopy.ok) {
346
+ throw new Error(`Session context references could not be copied: ${contextCopy.reason}`);
347
+ }
318
348
  const artifactCopy = await this.#artifacts.copyConversationArtifacts({
319
349
  sourceSessionId: input.sourceSessionId,
320
350
  targetSessionId: input.targetSessionId,
@@ -340,6 +370,7 @@ export class HostSessionRevisionCoordinator {
340
370
  targetSessionId: input.targetSessionId,
341
371
  artifactIds: artifactCopy.artifactIds,
342
372
  relativePaths: artifactCopy.relativePaths,
373
+ contextRefs: new Map(contextCopy.copied.map(({ sourceRefId, targetRefId }) => [sourceRefId, targetRefId])),
343
374
  linkedChildren: kind === 'side_conversation'
344
375
  ? {
345
376
  mode: 'snapshot',
@@ -544,6 +575,7 @@ export class HostSessionRevisionCoordinator {
544
575
  await purgeSessionSidecars({
545
576
  artifacts: this.#artifacts,
546
577
  sessionTodo: this.#sessionTodo,
578
+ ...(this.options.contextOffload ? { contextOffload: this.options.contextOffload } : {}),
547
579
  purgeOperationalState: (sessionId) => this.#stores.purgeConversationOperationalState(sessionId),
548
580
  }, header.id);
549
581
  await this.#stores.sessionStore.discardStableConversationCopy(header.id, copy.requestFingerprint);
@@ -16,10 +16,26 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ const CONTEXT_GARBAGE_BATCH_BLOBS = 64;
20
+ async function retireContextSession(contextOffload, sessionId) {
21
+ await contextOffload.retireSession(sessionId);
22
+ for (;;) {
23
+ const collected = await contextOffload.collectGarbage({
24
+ olderThan: Number.MAX_SAFE_INTEGER,
25
+ maxBlobs: CONTEXT_GARBAGE_BATCH_BLOBS,
26
+ maxBytes: Number.MAX_SAFE_INTEGER,
27
+ });
28
+ if (!collected.hasMore)
29
+ return;
30
+ }
31
+ }
19
32
  export async function purgeSessionSidecars(authority, sessionId) {
20
33
  const outcomes = await Promise.allSettled([
21
34
  authority.artifacts.purgeSessionArtifacts(sessionId),
22
35
  authority.sessionTodo.purgeSessionState(sessionId),
36
+ ...(authority.contextOffload
37
+ ? [retireContextSession(authority.contextOffload, sessionId)]
38
+ : []),
23
39
  authority.purgeOperationalState(sessionId),
24
40
  ]);
25
41
  const failures = outcomes.flatMap((outcome) => outcome.status === 'rejected' ? [outcome.reason] : []);