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.
- package/README.md +38 -19
- package/README.zh-CN.md +34 -19
- package/dist/acp/maka-acp-agent.js +2 -1
- package/dist/acp/session-configuration.js +143 -0
- package/dist/acp/session-registry.js +90 -4
- package/dist/cli-core.js +2 -0
- package/dist/pi-transcript-tools.js +6 -0
- package/dist/pi-transcript.js +5 -12
- package/dist/pi-tui-layout.js +56 -10
- package/dist/pi-tui-pickers.js +102 -1
- package/dist/pi-tui-runner.js +230 -46
- package/dist/pi-tui-todo.js +221 -0
- package/dist/pi-tui-transcript-viewer.js +134 -7
- package/dist/runtime-host-cli-context.js +224 -78
- package/dist/runtime-host-cli-installation.js +8 -2
- package/dist/runtime-host-cli.js +31 -1
- package/dist/runtime-host-handoff-surface.js +106 -0
- package/dist/runtime-host-installed-update-bootstrap.js +13 -0
- package/dist/runtime-host-installed-update-coordinator.js +33 -0
- package/dist/runtime-host-lifecycle-transaction.js +9 -1
- package/dist/runtime-host-local-handoff.js +48 -5
- package/dist/runtime-host-local-managed-activation.js +84 -0
- package/dist/runtime-host-local-source-retirement.js +18 -2
- package/dist/runtime-host-managed-lifecycle-manager.js +1 -0
- package/dist/runtime-host-operator-retirement.js +50 -0
- package/dist/runtime-host-service-manager.js +3 -3
- package/dist/runtime-host-session-channel.js +13 -0
- package/dist/runtime-host-session-driver.js +46 -26
- package/dist/runtime-host-session-update.js +62 -0
- package/dist/runtime-host-tui-command.js +38 -73
- package/dist/runtime-host-tui-context.js +1 -0
- package/dist/runtime-host-tui-owner.js +226 -0
- package/dist/tui-copy-catalog.js +219 -6
- package/dist/tui-host-owner-copy.js +24 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/agent-run.js +1 -0
- package/node_modules/@maka/core/dist/attachments.js +3 -0
- package/node_modules/@maka/core/dist/canonical-json.js +27 -0
- package/node_modules/@maka/core/dist/canonical-runtime-event.js +3 -1
- package/node_modules/@maka/core/dist/capabilities.js +35 -0
- package/node_modules/@maka/core/dist/computer-use.js +0 -1
- package/node_modules/@maka/core/dist/display-redaction.js +18 -0
- package/node_modules/@maka/core/dist/llm-connections.js +13 -0
- package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
- package/node_modules/@maka/core/dist/model-failure.js +40 -0
- package/node_modules/@maka/core/dist/model-metadata.generated.js +4 -4
- package/node_modules/@maka/core/dist/model-metadata.js +7 -3
- package/node_modules/@maka/core/dist/model-web-search.js +5 -4
- package/node_modules/@maka/core/dist/provider-registry.js +45 -4
- package/node_modules/@maka/core/dist/record-schema.js +9 -2
- package/node_modules/@maka/core/dist/redaction.js +35 -0
- package/node_modules/@maka/core/dist/run-composition.js +77 -2
- package/node_modules/@maka/core/dist/runtime-boundary.js +31 -11
- package/node_modules/@maka/core/dist/runtime-event.js +37 -6
- package/node_modules/@maka/core/dist/runtime-handoff.js +98 -0
- package/node_modules/@maka/core/dist/runtime-invocation.js +1 -1
- package/node_modules/@maka/core/dist/runtime-logical-execution.js +112 -0
- package/node_modules/@maka/core/dist/session.js +84 -56
- package/node_modules/@maka/core/dist/slash-command-catalog.js +3 -0
- package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +20 -0
- package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
- package/node_modules/@maka/core/dist/usage-record-schema.js +26 -35
- package/node_modules/@maka/core/package.json +5 -0
- package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
- package/node_modules/@maka/mcp/dist/index.js +131 -1
- package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
- package/node_modules/@maka/runtime/dist/agent-run.js +351 -105
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +29 -13
- package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +109 -34
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +184 -183
- package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
- package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
- package/node_modules/@maka/runtime/dist/context-budget.js +14 -9
- package/node_modules/@maka/runtime/dist/conversation-copy.js +158 -15
- package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
- package/node_modules/@maka/runtime/dist/goal-continuation.js +36 -4
- package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +18 -9
- package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +30 -22
- package/node_modules/@maka/runtime/dist/history-compaction.js +29 -4
- package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +174 -0
- package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
- package/node_modules/@maka/runtime/dist/message-authority.js +9 -0
- package/node_modules/@maka/runtime/dist/model-adapter.js +19 -98
- package/node_modules/@maka/runtime/dist/model-factory.js +19 -81
- package/node_modules/@maka/runtime/dist/model-fetcher.js +49 -2
- package/node_modules/@maka/runtime/dist/model-history.js +0 -112
- package/node_modules/@maka/runtime/dist/model-runtime.js +85 -111
- package/node_modules/@maka/runtime/dist/native-web-search-tool.js +2 -1
- package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +2 -2
- package/node_modules/@maka/runtime/dist/opencode-session-header.js +4 -2
- package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
- package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
- package/node_modules/@maka/runtime/dist/provider-error-classification.js +84 -123
- package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +76 -109
- package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
- package/node_modules/@maka/runtime/dist/run-handoff-gate.js +97 -0
- package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +83 -55
- package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +121 -159
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +157 -112
- package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +160 -79
- package/node_modules/@maka/runtime/dist/runtime-read-model.js +30 -89
- package/node_modules/@maka/runtime/dist/runtime-resume.js +80 -4
- package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +5 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +171 -288
- package/node_modules/@maka/runtime/dist/session-projection-helpers.js +0 -27
- package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
- package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
- package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +3 -4
- package/node_modules/@maka/runtime/dist/test-connection.js +16 -17
- package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
- package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
- package/node_modules/@maka/runtime/dist/tool-result-archive-encoding.js +38 -0
- package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
- package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
- package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
- package/node_modules/@maka/runtime/dist/tool-runtime.js +3 -61
- package/node_modules/@maka/runtime/package.json +2 -0
- package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +35 -0
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +9 -1
- package/node_modules/@maka/runtime-host/dist/client/connection.js +3 -0
- package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +179 -0
- package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +275 -0
- package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +26 -5
- package/node_modules/@maka/runtime-host/dist/client/index.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/launcher.js +3 -0
- package/node_modules/@maka/runtime-host/dist/client/remote-compatibility-error.js +2 -0
- package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +46 -5
- package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
- package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
- package/node_modules/@maka/runtime-host/dist/operator/index.js +2 -1
- package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
- package/node_modules/@maka/runtime-host/dist/operator/retirement-cancellation.js +20 -0
- package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
- package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
- package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
- package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +21 -12
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +29 -1
- package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
- package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
- package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
- package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +40 -20
- package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +0 -5
- package/node_modules/@maka/runtime-host/dist/protocol/session-turns.js +29 -58
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +11 -1
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +31 -2
- package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
- package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
- package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +19 -4
- package/node_modules/@maka/runtime-host/dist/server/connection-session.js +7 -6
- package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +144 -33
- package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +5 -5
- package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +45 -16
- package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +40 -1
- package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +96 -17
- package/node_modules/@maka/runtime-host/dist/server/host-residency-registry.js +13 -7
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-admission-registry.js +3 -1
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +124 -62
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +47 -22
- package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +76 -6
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +325 -73
- package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +37 -2
- package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +45 -3
- package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +50 -7
- package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +87 -25
- package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
- package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +12 -55
- package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +1 -16
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +0 -1
- package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +460 -70
- package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/storage-maintenance.js +109 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +20 -2
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +13 -4
- package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
- package/node_modules/@maka/storage/dist/artifact-store.js +21 -10
- package/node_modules/@maka/storage/dist/artifact-stores.js +1 -1
- package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -5
- package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -198
- package/node_modules/@maka/storage/dist/context-offload-snapshot.js +325 -0
- package/node_modules/@maka/storage/dist/execution-stores.js +9 -7
- package/node_modules/@maka/storage/dist/file-session-repository.js +1012 -0
- package/node_modules/@maka/storage/dist/git-exec.js +61 -0
- package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
- package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
- package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
- package/node_modules/@maka/storage/dist/opencode-session-adapter.js +0 -4
- package/node_modules/@maka/storage/dist/operational-state-backup.js +9 -5
- package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
- package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
- package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
- package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
- package/node_modules/@maka/storage/dist/runtime-transcript-query.js +308 -0
- package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
- package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
- package/node_modules/@maka/storage/dist/session-bundle-policy.js +26 -5
- package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
- package/node_modules/@maka/storage/dist/session-message-projection.js +15 -0
- package/node_modules/@maka/storage/dist/session-repository.js +767 -0
- package/node_modules/@maka/storage/dist/session-store.js +25 -99
- package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +10 -3
- package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +24 -8
- package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +7 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +44 -1
- package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +130 -27
- package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +109 -892
- package/node_modules/@maka/storage/dist/storage-id.js +28 -0
- package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +138 -0
- package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
- package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
- package/node_modules/@maka/storage/package.json +2 -0
- 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 {
|
|
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) =>
|
|
29
|
-
readDurablePage: (sessionId, request) =>
|
|
30
|
-
readDurableRecords: (sessionId, request) =>
|
|
31
|
-
readDurableMessagesById: (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
|
|
36
|
-
const
|
|
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(
|
|
39
|
-
invocations:
|
|
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
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 (
|
|
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(...
|
|
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
|
-
|
|
122
|
+
...(message.retry === undefined ? {} : { retry: message.retry }),
|
|
123
123
|
};
|
|
124
124
|
case 'token_usage':
|
|
125
125
|
return {
|