maka-agent 0.2.0-dev.23.20260906 → 0.2.0-dev.25.20260908

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 (215) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/acp/maka-acp-agent.js +2 -1
  4. package/dist/acp/session-configuration.js +143 -0
  5. package/dist/acp/session-registry.js +90 -4
  6. package/dist/cli-core.js +2 -0
  7. package/dist/pi-transcript-tools.js +6 -0
  8. package/dist/pi-transcript.js +5 -12
  9. package/dist/pi-tui-layout.js +56 -10
  10. package/dist/pi-tui-pickers.js +102 -1
  11. package/dist/pi-tui-runner.js +230 -46
  12. package/dist/pi-tui-todo.js +221 -0
  13. package/dist/pi-tui-transcript-viewer.js +134 -7
  14. package/dist/runtime-host-cli-context.js +224 -78
  15. package/dist/runtime-host-cli-installation.js +8 -2
  16. package/dist/runtime-host-cli.js +31 -1
  17. package/dist/runtime-host-handoff-surface.js +106 -0
  18. package/dist/runtime-host-installed-update-bootstrap.js +13 -0
  19. package/dist/runtime-host-installed-update-coordinator.js +33 -0
  20. package/dist/runtime-host-lifecycle-transaction.js +9 -1
  21. package/dist/runtime-host-local-handoff.js +48 -5
  22. package/dist/runtime-host-local-managed-activation.js +84 -0
  23. package/dist/runtime-host-local-source-retirement.js +18 -2
  24. package/dist/runtime-host-managed-lifecycle-manager.js +1 -0
  25. package/dist/runtime-host-operator-retirement.js +50 -0
  26. package/dist/runtime-host-service-manager.js +3 -3
  27. package/dist/runtime-host-session-channel.js +13 -0
  28. package/dist/runtime-host-session-driver.js +46 -26
  29. package/dist/runtime-host-session-update.js +62 -0
  30. package/dist/runtime-host-tui-command.js +38 -73
  31. package/dist/runtime-host-tui-context.js +1 -0
  32. package/dist/runtime-host-tui-owner.js +226 -0
  33. package/dist/tui-copy-catalog.js +219 -6
  34. package/dist/tui-host-owner-copy.js +24 -0
  35. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  36. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  37. package/node_modules/@maka/core/dist/attachments.js +3 -0
  38. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  39. package/node_modules/@maka/core/dist/canonical-runtime-event.js +3 -1
  40. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  41. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  42. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  43. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  44. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  45. package/node_modules/@maka/core/dist/model-failure.js +40 -0
  46. package/node_modules/@maka/core/dist/model-metadata.generated.js +4 -4
  47. package/node_modules/@maka/core/dist/model-metadata.js +7 -3
  48. package/node_modules/@maka/core/dist/model-web-search.js +5 -4
  49. package/node_modules/@maka/core/dist/provider-registry.js +45 -4
  50. package/node_modules/@maka/core/dist/record-schema.js +9 -2
  51. package/node_modules/@maka/core/dist/redaction.js +35 -0
  52. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  53. package/node_modules/@maka/core/dist/runtime-boundary.js +31 -11
  54. package/node_modules/@maka/core/dist/runtime-event.js +37 -6
  55. package/node_modules/@maka/core/dist/runtime-handoff.js +98 -0
  56. package/node_modules/@maka/core/dist/runtime-invocation.js +1 -1
  57. package/node_modules/@maka/core/dist/runtime-logical-execution.js +112 -0
  58. package/node_modules/@maka/core/dist/session.js +84 -56
  59. package/node_modules/@maka/core/dist/slash-command-catalog.js +3 -0
  60. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +20 -0
  61. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  62. package/node_modules/@maka/core/dist/usage-record-schema.js +26 -35
  63. package/node_modules/@maka/core/package.json +5 -0
  64. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  65. package/node_modules/@maka/mcp/dist/index.js +131 -1
  66. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  67. package/node_modules/@maka/runtime/dist/agent-run.js +351 -105
  68. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +29 -13
  69. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +109 -34
  70. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +184 -183
  71. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  72. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  73. package/node_modules/@maka/runtime/dist/context-budget.js +14 -9
  74. package/node_modules/@maka/runtime/dist/conversation-copy.js +158 -15
  75. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  76. package/node_modules/@maka/runtime/dist/goal-continuation.js +36 -4
  77. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +18 -9
  78. package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +30 -22
  79. package/node_modules/@maka/runtime/dist/history-compaction.js +29 -4
  80. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +174 -0
  81. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  82. package/node_modules/@maka/runtime/dist/message-authority.js +9 -0
  83. package/node_modules/@maka/runtime/dist/model-adapter.js +19 -98
  84. package/node_modules/@maka/runtime/dist/model-factory.js +19 -81
  85. package/node_modules/@maka/runtime/dist/model-fetcher.js +49 -2
  86. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  87. package/node_modules/@maka/runtime/dist/model-runtime.js +85 -111
  88. package/node_modules/@maka/runtime/dist/native-web-search-tool.js +2 -1
  89. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +2 -2
  90. package/node_modules/@maka/runtime/dist/opencode-session-header.js +4 -2
  91. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  92. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  93. package/node_modules/@maka/runtime/dist/provider-error-classification.js +84 -123
  94. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +76 -109
  95. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  96. package/node_modules/@maka/runtime/dist/run-handoff-gate.js +97 -0
  97. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +83 -55
  98. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +121 -159
  99. package/node_modules/@maka/runtime/dist/runtime-kernel.js +157 -112
  100. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +160 -79
  101. package/node_modules/@maka/runtime/dist/runtime-read-model.js +30 -89
  102. package/node_modules/@maka/runtime/dist/runtime-resume.js +80 -4
  103. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +5 -0
  104. package/node_modules/@maka/runtime/dist/session-manager.js +171 -288
  105. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +0 -27
  106. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  107. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  108. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +3 -4
  109. package/node_modules/@maka/runtime/dist/test-connection.js +16 -17
  110. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  111. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  112. package/node_modules/@maka/runtime/dist/tool-result-archive-encoding.js +38 -0
  113. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  114. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  115. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  116. package/node_modules/@maka/runtime/dist/tool-runtime.js +3 -61
  117. package/node_modules/@maka/runtime/package.json +2 -0
  118. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +35 -0
  119. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +9 -1
  120. package/node_modules/@maka/runtime-host/dist/client/connection.js +3 -0
  121. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +179 -0
  122. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +275 -0
  123. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +26 -5
  124. package/node_modules/@maka/runtime-host/dist/client/index.js +2 -0
  125. package/node_modules/@maka/runtime-host/dist/client/launcher.js +3 -0
  126. package/node_modules/@maka/runtime-host/dist/client/remote-compatibility-error.js +2 -0
  127. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +46 -5
  128. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  129. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  130. package/node_modules/@maka/runtime-host/dist/operator/index.js +2 -1
  131. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  132. package/node_modules/@maka/runtime-host/dist/operator/retirement-cancellation.js +20 -0
  133. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  134. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  135. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  136. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +21 -12
  137. package/node_modules/@maka/runtime-host/dist/protocol/index.js +29 -1
  138. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  139. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  140. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  141. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  142. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +40 -20
  143. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +0 -5
  144. package/node_modules/@maka/runtime-host/dist/protocol/session-turns.js +29 -58
  145. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +11 -1
  146. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +31 -2
  147. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
  148. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  149. package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +19 -4
  150. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +7 -6
  151. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  152. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +144 -33
  153. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +5 -5
  154. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +45 -16
  155. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +40 -1
  156. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +96 -17
  157. package/node_modules/@maka/runtime-host/dist/server/host-residency-registry.js +13 -7
  158. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-admission-registry.js +3 -1
  159. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +124 -62
  160. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +47 -22
  161. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +76 -6
  162. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  163. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  164. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +325 -73
  165. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +37 -2
  166. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +45 -3
  167. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +50 -7
  168. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +87 -25
  169. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  170. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +12 -55
  171. package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +1 -16
  172. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +0 -1
  173. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +460 -70
  174. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +1 -1
  175. package/node_modules/@maka/runtime-host/dist/server/storage-maintenance.js +109 -0
  176. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +20 -2
  177. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +13 -4
  178. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  179. package/node_modules/@maka/storage/dist/artifact-store.js +21 -10
  180. package/node_modules/@maka/storage/dist/artifact-stores.js +1 -1
  181. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -5
  182. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -198
  183. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +325 -0
  184. package/node_modules/@maka/storage/dist/execution-stores.js +9 -7
  185. package/node_modules/@maka/storage/dist/file-session-repository.js +1012 -0
  186. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  187. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  188. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  189. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  190. package/node_modules/@maka/storage/dist/opencode-session-adapter.js +0 -4
  191. package/node_modules/@maka/storage/dist/operational-state-backup.js +9 -5
  192. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  193. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  194. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  195. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  196. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +308 -0
  197. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  198. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  199. package/node_modules/@maka/storage/dist/session-bundle-policy.js +26 -5
  200. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  201. package/node_modules/@maka/storage/dist/session-message-projection.js +15 -0
  202. package/node_modules/@maka/storage/dist/session-repository.js +767 -0
  203. package/node_modules/@maka/storage/dist/session-store.js +25 -99
  204. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +10 -3
  205. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +24 -8
  206. package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +7 -1
  207. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +44 -1
  208. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +130 -27
  209. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +109 -892
  210. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  211. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +138 -0
  212. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  213. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  214. package/node_modules/@maka/storage/package.json +2 -0
  215. package/package.json +1 -1
@@ -16,27 +16,104 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { affectsRuntimeEventStoredMessageProjection, isHardRuntimeEventReadModelDiagnostic, projectRuntimeEventsToStoredMessages, } from '@maka/runtime/runtime-event-read-model';
19
+ import { readRunInvocation } from '@maka/core/runtime-event-store';
20
+ import { runtimeHandoffPause } from '@maka/core/runtime-handoff';
21
+ import { readLogicalRuntimeExecution } from '@maka/core/runtime-logical-execution';
22
+ import { WORKHUB_COORDINATION_SESSION_ID } from '@maka/core/session';
23
+ import { activePresentationRuntimeEvents, affectsRuntimeEventStoredMessageProjection, isHardRuntimeEventReadModelDiagnostic, projectRuntimeEventsToStoredMessages, projectRuntimeEventUserMessage, } from '@maka/runtime/runtime-event-read-model';
24
+ import { foldTurnContribution } from '@maka/storage/session-message-projection';
20
25
  import { SESSION_TRANSCRIPT_OVERLAY_MAX_MESSAGES } from '../protocol/index.js';
21
26
  const PERMISSION_OUTCOME_READ_CONCURRENCY = 8;
27
+ /** One event can emit content, a permission, usage, and terminal/notice rows. */
28
+ const EVENT_SEQUENCE_STRIDE = 8;
22
29
  export const ACTIVE_TRANSCRIPT_OVERLAY_MAX_MESSAGES = SESSION_TRANSCRIPT_OVERLAY_MAX_MESSAGES;
23
30
  export const ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES = 16 * 1024 * 1024;
24
31
  const ACTIVE_TRANSCRIPT_SOURCE_MAX_EVENTS = ACTIVE_TRANSCRIPT_OVERLAY_MAX_MESSAGES * 2;
25
32
  const ACTIVE_TRANSCRIPT_SCAN_BATCH_MAX_BYTES = 256 * 1024;
33
+ /**
34
+ * What one durable page may read of a Turn, matching the bound the live
35
+ * overlay already holds for a run. A Turn past it is refused rather than
36
+ * half-projected: a prefix of a Turn is not a smaller transcript of it.
37
+ */
38
+ const DURABLE_TRANSCRIPT_TURN_MAX_EVENTS = ACTIVE_TRANSCRIPT_SOURCE_MAX_EVENTS;
39
+ const DURABLE_TRANSCRIPT_TURN_MAX_BYTES = ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES;
40
+ /** Turns per storage round trip: one, so a page loads no Turn it cannot use. */
41
+ const TRANSCRIPT_TURN_SCAN_LIMIT = 1;
42
+ /**
43
+ * How far back the live-to-durable handoff looks for a message id. The ids come
44
+ * from assistant streams the subscriber is still watching, so they are in the
45
+ * newest Turn or the one it continued from; an id that is in neither is treated
46
+ * as absent rather than searched for down the Session.
47
+ */
48
+ const TRANSCRIPT_LOOKUP_MAX_TURNS = 2;
49
+ /** One storage round trip of Coordination rows, sized like one ledger Turn. */
50
+ const COORDINATION_TRANSCRIPT_SCAN_LIMIT = 64;
51
+ const COORDINATION_TRANSCRIPT_SCAN_MAX_BYTES = DURABLE_TRANSCRIPT_TURN_MAX_BYTES;
26
52
  export function createSessionTranscriptReader(input) {
53
+ const ledger = createDurableLedgerTranscriptReader(input);
54
+ const coordination = createCoordinationTranscriptReader(input.stores);
55
+ const isCoordination = (sessionId) => sessionId === WORKHUB_COORDINATION_SESSION_ID;
56
+ // Only a ledger-backed Session has a conversion; the Coordination Session's
57
+ // rows are the transcript, not something a run left behind.
58
+ const prepared = async (sessionId) => {
59
+ if (isCoordination(sessionId))
60
+ return coordination;
61
+ await input.ensureTranscriptLedger?.(sessionId);
62
+ return ledger;
63
+ };
27
64
  return {
28
- readDurableHighWater: (sessionId) => input.stores.sessionStore.readTranscriptHighWaterSnapshot(sessionId),
29
- readDurablePage: (sessionId, request) => input.stores.sessionStore.readTranscriptPageSnapshot(sessionId, request),
30
- readDurableRecords: (sessionId, request) => input.stores.sessionStore.readTranscriptRecordsSnapshot(sessionId, request),
31
- readDurableMessagesById: (sessionId, request) => input.stores.sessionStore.readTranscriptMessagesSnapshot(sessionId, request),
65
+ readDurableHighWater: async (sessionId) => (await prepared(sessionId)).readHighWater(sessionId),
66
+ readDurablePage: async (sessionId, request) => (await prepared(sessionId)).readPage(sessionId, request),
67
+ readDurableRecords: async (sessionId, request) => (await prepared(sessionId)).readRecords(sessionId, request),
68
+ readDurableMessagesById: async (sessionId, request) => (await prepared(sessionId)).readMessagesById(sessionId, request),
69
+ readDurableTurnContributions: async (sessionId, throughSequence, position, maxContributions) => (await prepared(sessionId)).readTurnContributions(sessionId, throughSequence, position, maxContributions),
70
+ readDurableTurnLandmarks: async (sessionId, maxLandmarks) => (await prepared(sessionId)).readTurnLandmarks(sessionId, maxLandmarks),
32
71
  readActiveOverlay: async (sessionId, rootTurn) => {
33
72
  if (!rootTurn || isTerminalTurn(rootTurn))
34
73
  return [];
35
- const invocations = await input.stores.runtimeEventStore.listSessionInvocations(sessionId);
36
- const events = await readActiveProjectionEvents(input.stores, sessionId, rootTurn.runId);
74
+ const store = input.stores.runtimeEventStore;
75
+ const root = await readRunInvocation(store, sessionId, rootTurn.runId);
76
+ if (!root)
77
+ return [];
78
+ const budget = { events: 0, bytes: 0 };
79
+ const scanned = new Map();
80
+ const invocations = new Map([[root.runId, root]]);
81
+ const readEvents = async (runId) => {
82
+ const cached = scanned.get(runId);
83
+ if (cached)
84
+ return cached;
85
+ const events = await readActiveRuntimeEvents(input.stores, sessionId, runId, budget);
86
+ scanned.set(runId, events);
87
+ return events;
88
+ };
89
+ let runIds = [root.runId];
90
+ if (root.terminalEvent && runtimeHandoffPause(root.terminalEvent)) {
91
+ const logical = await readLogicalRuntimeExecution({
92
+ ...store,
93
+ readRunInvocation: async (id, runId) => {
94
+ const run = await readRunInvocation(store, id, runId);
95
+ if (run)
96
+ invocations.set(runId, run);
97
+ return run;
98
+ },
99
+ // Authenticate only after the same bounded scan used for presentation.
100
+ // Sealed prefixes cannot grow between this check and digest verification.
101
+ readImmutableRuntimeEvents: async (_id, runId) => (await readEvents(runId)).filter((event) => !event.partial),
102
+ readImmutableRuntimePrefix: async (prefix) => {
103
+ await readEvents(prefix.runId);
104
+ return store.readImmutableRuntimePrefix(prefix);
105
+ },
106
+ }, { sessionId, turnId: rootTurn.turnId, runId: rootTurn.runId }, root);
107
+ if (!logical)
108
+ return [];
109
+ runIds = logical.runIds;
110
+ }
111
+ const events = [];
112
+ for (const runId of runIds)
113
+ events.push(...(await readEvents(runId)).filter(affectsRuntimeEventStoredMessageProjection));
37
114
  const canonicalPermissionOutcomes = await readCanonicalPermissionOutcomes(events, input.canonicalPermissionOutcomes);
38
- const projected = projectRuntimeEventsToStoredMessages(activePresentationEvents(events), {
39
- invocations: invocations.filter((invocation) => invocation.runId === rootTurn.runId),
115
+ const projected = projectRuntimeEventsToStoredMessages(activePresentationRuntimeEvents(events), {
116
+ invocations: runIds.map((runId) => invocations.get(runId)),
40
117
  canonicalPermissionOutcomes,
41
118
  });
42
119
  if (projected.diagnostics.some(isHardRuntimeEventReadModelDiagnostic)) {
@@ -47,6 +124,373 @@ export function createSessionTranscriptReader(input) {
47
124
  },
48
125
  };
49
126
  }
127
+ /**
128
+ * Pages seek immutable Session event ordinals before decoding payloads. One
129
+ * Turn is projected at a time, so a page costs one Turn rather than the
130
+ * Session. The low sequence bits distinguish the few rows one event emits.
131
+ *
132
+ * What an event becomes is asked only of the read model. Storage selects Turns
133
+ * by ordinal and hands over their events; it never classifies one.
134
+ */
135
+ function createDurableLedgerTranscriptReader(input) {
136
+ const store = input.stores.runtimeEventStore;
137
+ const highWater = async (sessionId) => {
138
+ const ordinal = await store.readTranscriptHighWater(sessionId);
139
+ return ordinal === null ? null : ordinal * EVENT_SEQUENCE_STRIDE + EVENT_SEQUENCE_STRIDE - 1;
140
+ };
141
+ /** One Turn's rows, each at the sequence its own event sits at. */
142
+ const projectTurn = async (turn) => {
143
+ const events = turn.events.map((entry) => entry.event);
144
+ const projected = projectRuntimeEventsToStoredMessages(events, {
145
+ invocations: [turn.invocation],
146
+ canonicalPermissionOutcomes: await readCanonicalPermissionOutcomes(events, input.canonicalPermissionOutcomes),
147
+ });
148
+ if (projected.diagnostics.some(isHardRuntimeEventReadModelDiagnostic)) {
149
+ throw new Error('Durable RuntimeEvent transcript projection is incomplete');
150
+ }
151
+ const ordinals = new Map(turn.events.map((entry) => [entry.event.id, entry.ordinal]));
152
+ const emitted = new Map();
153
+ return projected.messages.map((message, index) => {
154
+ const ordinal = ordinals.get(projected.sourceEventIds[index]);
155
+ if (ordinal === undefined) {
156
+ throw new Error('Durable transcript message has no source RuntimeEvent');
157
+ }
158
+ const offset = emitted.get(ordinal) ?? 0;
159
+ if (offset >= EVENT_SEQUENCE_STRIDE) {
160
+ throw new Error('RuntimeEvent exceeds its transcript sequence stride');
161
+ }
162
+ emitted.set(ordinal, offset + 1);
163
+ return { sequence: ordinal * EVENT_SEQUENCE_STRIDE + offset, message };
164
+ });
165
+ };
166
+ const readTurns = async (sessionId, request) => store.readTranscriptInvocations(sessionId, {
167
+ ...request,
168
+ limit: TRANSCRIPT_TURN_SCAN_LIMIT,
169
+ maxEvents: DURABLE_TRANSCRIPT_TURN_MAX_EVENTS,
170
+ maxBytes: DURABLE_TRANSCRIPT_TURN_MAX_BYTES,
171
+ });
172
+ const scan = async function* (sessionId, request) {
173
+ const throughSequence = request.throughSequence === undefined ? await highWater(sessionId) : request.throughSequence;
174
+ if (throughSequence === null)
175
+ return;
176
+ const position = request.position ?? (request.direction === 'older' ? throughSequence : 0);
177
+ const throughOrdinal = ordinalOf(throughSequence);
178
+ const older = request.direction === 'older';
179
+ const readTurnAt = async (at) => at < 0 || at > throughOrdinal
180
+ ? undefined
181
+ : (await readTurns(sessionId, {
182
+ direction: request.direction,
183
+ throughOrdinal,
184
+ position: at,
185
+ }))[0];
186
+ let ordinal = ordinalOf(position);
187
+ let walked = 0;
188
+ let carried;
189
+ while (ordinal >= 0 && ordinal <= throughOrdinal) {
190
+ const first = carried ?? (await readTurnAt(ordinal));
191
+ carried = undefined;
192
+ if (first === undefined)
193
+ return;
194
+ if (request.maxTurns !== undefined && walked >= request.maxTurns)
195
+ return;
196
+ // A page resumes from one record's sequence and drops everything the other
197
+ // side of it, so what this yields has to be monotone in sequence. Turns
198
+ // whose ordinal ranges overlap — a nested run inside its parent — are
199
+ // therefore drained together instead of one after the other.
200
+ const cluster = [first];
201
+ let low = first.firstOrdinal;
202
+ let high = first.lastOrdinal;
203
+ for (;;) {
204
+ const next = await readTurnAt(older ? low - 1 : high + 1);
205
+ if (next === undefined)
206
+ break;
207
+ if (older ? next.lastOrdinal < low : next.firstOrdinal > high) {
208
+ carried = next;
209
+ break;
210
+ }
211
+ cluster.push(next);
212
+ low = Math.min(low, next.firstOrdinal);
213
+ high = Math.max(high, next.lastOrdinal);
214
+ }
215
+ walked += cluster.length;
216
+ const records = (await Promise.all(cluster.map(projectTurn)))
217
+ .flat()
218
+ .filter(({ sequence }) => sequence <= throughSequence && (older ? sequence <= position : sequence >= position))
219
+ .sort((a, b) => (older ? b.sequence - a.sequence : a.sequence - b.sequence));
220
+ yield* records;
221
+ ordinal = older ? low - 1 : high + 1;
222
+ }
223
+ };
224
+ return {
225
+ readHighWater: highWater,
226
+ ...pagedTranscriptReads({ readHighWater: highWater, scan }),
227
+ /** One row per Turn, folded from the Turn's own projected messages. */
228
+ async readTurnContributions(sessionId, throughSequence, position, maxContributions) {
229
+ const watermark = throughSequence ?? (await highWater(sessionId));
230
+ if (watermark === null) {
231
+ return { throughSequence: null, contributions: [], nextPosition: null };
232
+ }
233
+ const turns = await store.readTranscriptInvocations(sessionId, {
234
+ direction: 'newer',
235
+ throughOrdinal: ordinalOf(watermark),
236
+ position: ordinalOf(position),
237
+ limit: maxContributions + 1,
238
+ maxEvents: DURABLE_TRANSCRIPT_TURN_MAX_EVENTS,
239
+ maxBytes: DURABLE_TRANSCRIPT_TURN_MAX_BYTES,
240
+ });
241
+ const contributions = [];
242
+ for (const turn of turns.slice(0, maxContributions)) {
243
+ // Folded from the Turn's own rows, so `firstSequence` lands on its first
244
+ // row rather than on the opening fact, which has no row at all.
245
+ let contribution;
246
+ for (const { sequence, message } of await projectTurn(turn)) {
247
+ if (sequence < position || sequence > watermark)
248
+ continue;
249
+ contribution = foldTurnContribution(contribution, turn.invocation.turnId, sequence, message);
250
+ }
251
+ if (contribution)
252
+ contributions.push(contribution);
253
+ }
254
+ const next = turns[maxContributions];
255
+ return {
256
+ throughSequence: watermark,
257
+ contributions,
258
+ nextPosition: next ? next.firstOrdinal * EVENT_SEQUENCE_STRIDE : null,
259
+ };
260
+ },
261
+ /** Evenly spaced Turn starts, selected in SQL before loading their prompts. */
262
+ async readTurnLandmarks(sessionId, maxLandmarks) {
263
+ const throughSequence = await highWater(sessionId);
264
+ if (throughSequence === null)
265
+ return { throughSequence: null, landmarks: [] };
266
+ const turns = await store.readTranscriptLandmarks(sessionId, ordinalOf(throughSequence), maxLandmarks);
267
+ const landmarks = [];
268
+ for (const turn of turns) {
269
+ if (!turn.prompt)
270
+ continue;
271
+ const message = projectRuntimeEventUserMessage(turn.prompt.event, turn.prompt.event.id);
272
+ const label = (message?.displayText ?? message?.text ?? '').trim();
273
+ if (!label)
274
+ continue;
275
+ landmarks.push({
276
+ turnId: turn.invocation.turnId,
277
+ sequence: turn.prompt.ordinal * EVENT_SEQUENCE_STRIDE,
278
+ label,
279
+ });
280
+ }
281
+ return { throughSequence, landmarks };
282
+ },
283
+ };
284
+ }
285
+ /**
286
+ * The reads that are the same whatever produces the records: a byte-bounded
287
+ * page, a record scan, and a lookup by message id. Each walks one source's
288
+ * ordered records and never asks where they came from.
289
+ */
290
+ function pagedTranscriptReads(source) {
291
+ return {
292
+ async readPage(sessionId, request) {
293
+ const throughSequence = request.throughSequence === undefined
294
+ ? await source.readHighWater(sessionId)
295
+ : request.throughSequence;
296
+ if (throughSequence === null) {
297
+ return { throughSequence: null, fragments: [], rawBytes: 0, next: null };
298
+ }
299
+ const fragments = [];
300
+ let rawBytes = 0;
301
+ let next = null;
302
+ let truncated = false;
303
+ for await (const record of source.scan(sessionId, { ...request, throughSequence })) {
304
+ if (fragments.length >= request.maxMessages || rawBytes >= request.maxBytes) {
305
+ truncated = true;
306
+ next = { position: record.sequence, byteOffset: null };
307
+ break;
308
+ }
309
+ const data = Buffer.from(JSON.stringify(record.message), 'utf8');
310
+ // A message larger than the remaining budget is served in byte slices,
311
+ // from the edge the traversal is moving away from, so the next page
312
+ // resumes inside the same record instead of skipping it.
313
+ const continued = record.sequence === request.position && request.byteOffset !== undefined;
314
+ const edge = continued
315
+ ? request.byteOffset
316
+ : request.direction === 'older'
317
+ ? data.byteLength
318
+ : 0;
319
+ const available = request.maxBytes - rawBytes;
320
+ const byteOffset = request.direction === 'older' ? Math.max(0, edge - available) : edge;
321
+ const end = request.direction === 'older' ? edge : Math.min(data.byteLength, edge + available);
322
+ fragments.push({
323
+ sequence: record.sequence,
324
+ byteOffset,
325
+ totalBytes: data.byteLength,
326
+ payloadDigest: null,
327
+ data: data.subarray(byteOffset, end),
328
+ });
329
+ rawBytes += end - byteOffset;
330
+ const complete = request.direction === 'older' ? byteOffset === 0 : end === data.byteLength;
331
+ if (!complete) {
332
+ truncated = true;
333
+ next = {
334
+ position: record.sequence,
335
+ byteOffset: request.direction === 'older' ? byteOffset : end,
336
+ };
337
+ break;
338
+ }
339
+ }
340
+ if (!truncated)
341
+ next = null;
342
+ return { throughSequence, fragments, rawBytes, next };
343
+ },
344
+ async readRecords(sessionId, request) {
345
+ const throughSequence = request.throughSequence === undefined
346
+ ? await source.readHighWater(sessionId)
347
+ : request.throughSequence;
348
+ if (throughSequence === null) {
349
+ return { throughSequence: null, records: [], nextPosition: null };
350
+ }
351
+ const records = [];
352
+ let storedBytes = 0;
353
+ let nextPosition = null;
354
+ for await (const record of source.scan(sessionId, { ...request, throughSequence })) {
355
+ if (records.length >= request.maxMessages || storedBytes >= request.maxStoredBytes) {
356
+ nextPosition = record.sequence;
357
+ break;
358
+ }
359
+ records.push(record);
360
+ storedBytes += Buffer.byteLength(JSON.stringify(record.message), 'utf8');
361
+ }
362
+ return { throughSequence, records, nextPosition };
363
+ },
364
+ /**
365
+ * The durable rows behind a set of message ids.
366
+ *
367
+ * The ids come from the assistant streams a subscriber is still watching,
368
+ * so they belong to the Session's newest Turns. The scan walks back from
369
+ * the watermark a Turn at a time and stops as soon as every id is found,
370
+ * rather than keeping an index from message id to event. An id that is not
371
+ * there stops the walk after the newest Turns instead of reading the
372
+ * Session: the handoff shows what the tail holds, not everything it could.
373
+ */
374
+ async readMessagesById(sessionId, request) {
375
+ if (request.throughSequence === null || request.messageIds.length === 0)
376
+ return [];
377
+ const wanted = new Set(request.messageIds);
378
+ const found = [];
379
+ let bytes = 0;
380
+ for await (const record of source.scan(sessionId, {
381
+ direction: 'older',
382
+ throughSequence: request.throughSequence,
383
+ maxTurns: TRANSCRIPT_LOOKUP_MAX_TURNS,
384
+ })) {
385
+ if (!wanted.delete(record.message.id))
386
+ continue;
387
+ bytes += Buffer.byteLength(JSON.stringify(record.message), 'utf8');
388
+ if (found.length >= request.maxMessages || bytes > request.maxBytes)
389
+ break;
390
+ found.push(record);
391
+ if (wanted.size === 0)
392
+ break;
393
+ }
394
+ return found.sort((a, b) => a.sequence - b.sequence).map((record) => record.message);
395
+ },
396
+ };
397
+ }
398
+ /**
399
+ * The WorkHub Coordination Session's transcript, read from the rows the WorkHub
400
+ * writes.
401
+ *
402
+ * Every other Session's transcript is what its runs did, so the ledger holds
403
+ * all of it. The Coordination Session's is not: a delegation, a stop and a
404
+ * routing summary are appended under a Turn id that no root Turn admission ever
405
+ * minted, so there is no invocation for the ledger to hang them on and no
406
+ * conversion that could lift them. `workhub.coordination.answer` is the one
407
+ * path that would admit a real Turn and nothing in the renderer calls it.
408
+ *
409
+ * Delete this source once the WorkHub admits a Coordination Turn for every
410
+ * action, which its own ADR already requires (#3492,
411
+ * `docs/architecture/workhub-coordination-session-adr.md`): the Session then
412
+ * reads like any other and this reader has nothing left to do.
413
+ */
414
+ function createCoordinationTranscriptReader(stores) {
415
+ const store = stores.sessionStore;
416
+ const highWater = (sessionId) => store.readTranscriptHighWaterSnapshot(sessionId);
417
+ const scan = async function* (sessionId, request) {
418
+ const throughSequence = request.throughSequence === undefined ? await highWater(sessionId) : request.throughSequence;
419
+ if (throughSequence === null)
420
+ return;
421
+ const older = request.direction === 'older';
422
+ const position = request.position ?? (older ? throughSequence : 0);
423
+ let cursor = older ? Math.min(position, throughSequence) + 1 : position - 1;
424
+ for (;;) {
425
+ const page = await store.readMessagesAfter(sessionId, {
426
+ ...(older ? { beforeSequence: cursor } : { afterSequence: cursor }),
427
+ maxMessages: COORDINATION_TRANSCRIPT_SCAN_LIMIT,
428
+ maxStoredBytes: COORDINATION_TRANSCRIPT_SCAN_MAX_BYTES,
429
+ });
430
+ if (page.records.length === 0)
431
+ return;
432
+ for (const record of page.records) {
433
+ if (!older && record.sequence > throughSequence)
434
+ return;
435
+ yield record;
436
+ }
437
+ cursor = page.records.at(-1).sequence;
438
+ }
439
+ };
440
+ const source = { readHighWater: highWater, scan };
441
+ return {
442
+ readHighWater: highWater,
443
+ ...pagedTranscriptReads(source),
444
+ /** Folded from the rows themselves; these Turns have nothing else. */
445
+ async readTurnContributions(sessionId, throughSequence, position, maxContributions) {
446
+ const watermark = throughSequence ?? (await highWater(sessionId));
447
+ if (watermark === null) {
448
+ return { throughSequence: null, contributions: [], nextPosition: null };
449
+ }
450
+ const byTurn = new Map();
451
+ let nextPosition = null;
452
+ for await (const { sequence, message } of scan(sessionId, {
453
+ direction: 'newer',
454
+ throughSequence: watermark,
455
+ position,
456
+ })) {
457
+ const turnId = message.turnId;
458
+ if (turnId === undefined)
459
+ continue;
460
+ if (!byTurn.has(turnId) && byTurn.size === maxContributions) {
461
+ nextPosition = sequence;
462
+ break;
463
+ }
464
+ byTurn.set(turnId, foldTurnContribution(byTurn.get(turnId), turnId, sequence, message));
465
+ }
466
+ return { throughSequence: watermark, contributions: [...byTurn.values()], nextPosition };
467
+ },
468
+ /** Every prompt, in order: this transcript has no index to sample from. */
469
+ async readTurnLandmarks(sessionId, maxLandmarks) {
470
+ const throughSequence = await highWater(sessionId);
471
+ if (throughSequence === null)
472
+ return { throughSequence: null, landmarks: [] };
473
+ const landmarks = [];
474
+ for await (const { sequence, message } of scan(sessionId, {
475
+ direction: 'newer',
476
+ throughSequence,
477
+ })) {
478
+ if (message.type !== 'user' || message.turnId === undefined)
479
+ continue;
480
+ const label = (message.displayText ?? message.text ?? '').trim();
481
+ if (!label)
482
+ continue;
483
+ landmarks.push({ turnId: message.turnId, sequence, label });
484
+ if (landmarks.length === maxLandmarks)
485
+ break;
486
+ }
487
+ return { throughSequence, landmarks };
488
+ },
489
+ };
490
+ }
491
+ function ordinalOf(sequence) {
492
+ return Math.floor(sequence / EVENT_SEQUENCE_STRIDE);
493
+ }
50
494
  function assertActiveOverlayBounded(messages) {
51
495
  if (messages.length > ACTIVE_TRANSCRIPT_OVERLAY_MAX_MESSAGES) {
52
496
  throw new Error('Active Session transcript overlay exceeds its message limit');
@@ -84,41 +528,8 @@ async function readCanonicalPermissionOutcomes(events, reader) {
84
528
  }
85
529
  return outcomes;
86
530
  }
87
- function activePresentationEvents(events) {
88
- const textMessages = new Set();
89
- const lastThinkingByMessage = new Map();
90
- for (const event of events) {
91
- const content = event.content;
92
- if (event.role !== 'model' || (content?.kind !== 'text' && content?.kind !== 'thinking')) {
93
- continue;
94
- }
95
- const messageKey = activeMessageKey(event);
96
- if (content.kind === 'text')
97
- textMessages.add(messageKey);
98
- else
99
- lastThinkingByMessage.set(messageKey, event);
100
- }
101
- const syntheticAfter = new Map();
102
- for (const [messageKey, thinking] of lastThinkingByMessage) {
103
- if (textMessages.has(messageKey))
104
- continue;
105
- const existing = syntheticAfter.get(thinking) ?? [];
106
- existing.push(emptyAssistantText(thinking));
107
- syntheticAfter.set(thinking, existing);
108
- }
109
- const presented = [];
110
- for (const event of events) {
111
- presented.push(presentationEvent(event));
112
- const synthetic = syntheticAfter.get(event);
113
- if (synthetic)
114
- presented.push(...synthetic);
115
- }
116
- return presented;
117
- }
118
- async function readActiveProjectionEvents(stores, sessionId, runId) {
531
+ async function readActiveRuntimeEvents(stores, sessionId, runId, budget) {
119
532
  const events = [];
120
- let retainedEvents = 0;
121
- let retainedBytes = 0;
122
533
  const result = await stores.runtimeEventStore.scanRuntimeEvents(sessionId, runId, {
123
534
  maxBatchBytes: ACTIVE_TRANSCRIPT_SCAN_BATCH_MAX_BYTES,
124
535
  maxRecordBytes: ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES,
@@ -127,44 +538,23 @@ async function readActiveProjectionEvents(stores, sessionId, runId) {
127
538
  maxPartialRecords: ACTIVE_TRANSCRIPT_SOURCE_MAX_EVENTS,
128
539
  maxPartialBytes: ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES,
129
540
  }, (batch) => {
130
- const relevant = batch.filter(affectsRuntimeEventStoredMessageProjection);
131
- for (const event of relevant) {
132
- retainedEvents += 1;
133
- retainedBytes += Buffer.byteLength(JSON.stringify(event), 'utf8');
134
- if (retainedEvents > ACTIVE_TRANSCRIPT_SOURCE_MAX_EVENTS) {
541
+ for (const event of batch) {
542
+ budget.events += 1;
543
+ budget.bytes += Buffer.byteLength(JSON.stringify(event), 'utf8');
544
+ if (budget.events > ACTIVE_TRANSCRIPT_SOURCE_MAX_EVENTS) {
135
545
  throw new Error('Active RuntimeEvent transcript exceeds its event limit');
136
546
  }
137
- if (retainedBytes > ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES) {
547
+ if (budget.bytes > ACTIVE_TRANSCRIPT_OVERLAY_MAX_BYTES) {
138
548
  throw new Error('Active RuntimeEvent transcript exceeds its byte limit');
139
549
  }
140
550
  }
141
- events.push(...relevant);
551
+ events.push(...batch);
142
552
  });
143
553
  if (result.status === 'limit_exceeded') {
144
554
  throw new Error('Active RuntimeEvent transcript exceeds its storage scan limit');
145
555
  }
146
556
  return events;
147
557
  }
148
- function activeMessageKey(event) {
149
- const messageId = event.refs?.providerEventId ?? event.refs?.storedMessageId ?? event.id;
150
- return `${event.runId}\0${messageId}`;
151
- }
152
- function presentationEvent(event) {
153
- const content = event.content;
154
- return event.partial &&
155
- event.role === 'model' &&
156
- (content?.kind === 'text' || content?.kind === 'thinking')
157
- ? { ...event, partial: false }
158
- : event;
159
- }
160
- function emptyAssistantText(thinking) {
161
- return {
162
- ...thinking,
163
- id: `${thinking.id}:active-transcript-empty-text`,
164
- partial: false,
165
- content: { kind: 'text', text: '' },
166
- };
167
- }
168
558
  function isTerminalTurn(turn) {
169
559
  return turn.status === 'completed' || turn.status === 'failed' || turn.status === 'cancelled';
170
560
  }
@@ -119,7 +119,7 @@ export function projectSharedSessionTranscriptMessage(message, sessionId) {
119
119
  ...(message.abortedAt === undefined ? {} : { abortedAt: message.abortedAt }),
120
120
  ...(message.abortSource === undefined ? {} : { abortSource: message.abortSource }),
121
121
  ...(message.errorClass === undefined ? {} : { errorClass: message.errorClass }),
122
- partialOutputRetained: message.partialOutputRetained,
122
+ ...(message.retry === undefined ? {} : { retry: message.retry }),
123
123
  };
124
124
  case 'token_usage':
125
125
  return {