@ericsanchezok/synergy-workbench 0.0.0-stage → 3.1.0

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 (153) hide show
  1. package/AGENTS.md +30 -0
  2. package/README.md +16 -2
  3. package/dist/agents.js +11 -0
  4. package/dist/cli-adapter.js +8 -0
  5. package/dist/component.js +65 -0
  6. package/dist/config-schema.js +265 -0
  7. package/dist/execution/activity.js +78 -0
  8. package/dist/execution/content.js +184 -0
  9. package/dist/execution/json-sections.js +231 -0
  10. package/dist/execution/presentation.js +79 -0
  11. package/dist/execution/preview.js +13 -0
  12. package/dist/execution/process.js +125 -0
  13. package/dist/execution/projection.js +327 -0
  14. package/dist/execution/query.js +18 -0
  15. package/dist/execution/routes.js +189 -0
  16. package/dist/execution/schema.js +322 -0
  17. package/dist/execution/service.js +1993 -0
  18. package/dist/features.js +22 -0
  19. package/dist/http.js +65 -0
  20. package/dist/performance/analysis.js +330 -0
  21. package/dist/performance/catalog.js +391 -0
  22. package/dist/performance/dashboard.js +483 -0
  23. package/dist/performance/error.js +15 -0
  24. package/dist/performance/index.js +6 -0
  25. package/dist/performance/inflight.js +43 -0
  26. package/dist/performance/projection.js +138 -0
  27. package/dist/performance/prompt/base.txt +38 -0
  28. package/dist/performance/prompt/builder.js +21 -0
  29. package/dist/performance/routes/performance-route.js +439 -0
  30. package/dist/performance/schema.js +628 -0
  31. package/dist/performance/timeline.js +98 -0
  32. package/dist/performance/trace-detail.js +91 -0
  33. package/dist/presentation/service.js +270 -0
  34. package/dist/presentation/tools/workbench-view.js +65 -0
  35. package/dist/presentation/tools.js +10 -0
  36. package/dist/project/directories.js +363 -0
  37. package/dist/project/git-health.js +498 -0
  38. package/dist/project/ide.js +66 -0
  39. package/dist/project/migration.js +22 -0
  40. package/dist/project/routes/directories.js +89 -0
  41. package/dist/project/routes/git.js +30 -0
  42. package/dist/project/routes/task-defaults.js +42 -0
  43. package/dist/project/session-health.js +9 -0
  44. package/dist/project/startup.js +28 -0
  45. package/dist/project/task-defaults.js +68 -0
  46. package/dist/project/tools/session-control.js +664 -0
  47. package/dist/project/tools/session-control.txt +60 -0
  48. package/dist/project/tools/worktree-archive.js +76 -0
  49. package/dist/project/tools/worktree-enter.js +175 -0
  50. package/dist/project/tools/worktree-leave.js +119 -0
  51. package/dist/project/tools/worktree-list.js +52 -0
  52. package/dist/project/tools.js +26 -0
  53. package/dist/project/vcs-branch-watcher.js +76 -0
  54. package/dist/project/vcs.js +114 -0
  55. package/dist/project/worktree-janitor.js +140 -0
  56. package/dist/project/worktrees.js +98 -0
  57. package/dist/push/bridge.js +171 -0
  58. package/dist/push/index.js +4 -0
  59. package/dist/push/routes/push.js +224 -0
  60. package/dist/push/service.js +93 -0
  61. package/dist/push/store.js +121 -0
  62. package/dist/push/types.js +58 -0
  63. package/dist/review/git.js +226 -0
  64. package/dist/review/routes.js +45 -0
  65. package/dist/review/schema.js +32 -0
  66. package/dist/review/state.js +79 -0
  67. package/dist/stats/aggregator.js +252 -0
  68. package/dist/stats/canonical.js +89 -0
  69. package/dist/stats/cli/stats.js +302 -0
  70. package/dist/stats/engine.js +176 -0
  71. package/dist/stats/index.js +5 -0
  72. package/dist/stats/rollup.js +460 -0
  73. package/dist/stats/routes/stats.js +94 -0
  74. package/dist/stats/routes/usage.js +83 -0
  75. package/dist/stats/storage.js +86 -0
  76. package/dist/stats/types.js +267 -0
  77. package/dist/worker.js +6 -0
  78. package/package.json +232 -4
  79. package/src/agents.ts +15 -0
  80. package/src/cli-adapter.ts +11 -0
  81. package/src/component.ts +73 -0
  82. package/src/config-schema.ts +289 -0
  83. package/src/execution/activity.ts +83 -0
  84. package/src/execution/content.ts +189 -0
  85. package/src/execution/json-sections.ts +230 -0
  86. package/src/execution/presentation.ts +91 -0
  87. package/src/execution/preview.ts +12 -0
  88. package/src/execution/process.ts +118 -0
  89. package/src/execution/projection.ts +330 -0
  90. package/src/execution/query.ts +19 -0
  91. package/src/execution/routes.ts +324 -0
  92. package/src/execution/schema.ts +336 -0
  93. package/src/execution/service.ts +2149 -0
  94. package/src/features.ts +22 -0
  95. package/src/http.ts +75 -0
  96. package/src/performance/analysis.ts +356 -0
  97. package/src/performance/catalog.ts +902 -0
  98. package/src/performance/dashboard.ts +521 -0
  99. package/src/performance/error.ts +32 -0
  100. package/src/performance/index.ts +6 -0
  101. package/src/performance/inflight.ts +46 -0
  102. package/src/performance/projection.ts +143 -0
  103. package/src/performance/prompt/base.txt +38 -0
  104. package/src/performance/prompt/builder.ts +24 -0
  105. package/src/performance/routes/performance-route.ts +541 -0
  106. package/src/performance/schema.ts +695 -0
  107. package/src/performance/timeline.ts +100 -0
  108. package/src/performance/trace-detail.ts +97 -0
  109. package/src/presentation/service.ts +303 -0
  110. package/src/presentation/tools/workbench-view.ts +75 -0
  111. package/src/presentation/tools.ts +11 -0
  112. package/src/project/directories.ts +391 -0
  113. package/src/project/git-health.ts +577 -0
  114. package/src/project/ide.ts +80 -0
  115. package/src/project/migration.ts +24 -0
  116. package/src/project/routes/directories.ts +124 -0
  117. package/src/project/routes/git.ts +42 -0
  118. package/src/project/routes/task-defaults.ts +50 -0
  119. package/src/project/session-health.ts +10 -0
  120. package/src/project/startup.ts +28 -0
  121. package/src/project/task-defaults.ts +70 -0
  122. package/src/project/tools/session-control.ts +725 -0
  123. package/src/project/tools/session-control.txt +60 -0
  124. package/src/project/tools/worktree-archive.ts +92 -0
  125. package/src/project/tools/worktree-enter.ts +227 -0
  126. package/src/project/tools/worktree-leave.ts +140 -0
  127. package/src/project/tools/worktree-list.ts +68 -0
  128. package/src/project/tools.ts +29 -0
  129. package/src/project/vcs-branch-watcher.ts +81 -0
  130. package/src/project/vcs.ts +124 -0
  131. package/src/project/worktree-janitor.ts +153 -0
  132. package/src/project/worktrees.ts +110 -0
  133. package/src/push/bridge.ts +182 -0
  134. package/src/push/index.ts +4 -0
  135. package/src/push/routes/push.ts +267 -0
  136. package/src/push/service.ts +108 -0
  137. package/src/push/store.ts +120 -0
  138. package/src/push/types.ts +67 -0
  139. package/src/review/git.ts +221 -0
  140. package/src/review/routes.ts +65 -0
  141. package/src/review/schema.ts +34 -0
  142. package/src/review/state.ts +77 -0
  143. package/src/stats/aggregator.ts +277 -0
  144. package/src/stats/canonical.ts +93 -0
  145. package/src/stats/cli/stats.ts +348 -0
  146. package/src/stats/engine.ts +197 -0
  147. package/src/stats/index.ts +5 -0
  148. package/src/stats/rollup.ts +522 -0
  149. package/src/stats/routes/stats.ts +107 -0
  150. package/src/stats/routes/usage.ts +103 -0
  151. package/src/stats/storage.ts +78 -0
  152. package/src/stats/types.ts +351 -0
  153. package/src/worker.ts +7 -0
@@ -0,0 +1,327 @@
1
+ import { RuntimeContext } from "@ericsanchezok/synergy-harness/lifecycle/context";
2
+ import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context";
3
+ import { Scope } from "@ericsanchezok/synergy-harness/scope";
4
+ import { Session } from "@ericsanchezok/synergy-harness/session";
5
+ import { Storage } from "@ericsanchezok/synergy-harness/storage/storage";
6
+ import { applyProjectionPatch, projectionPatch } from "@ericsanchezok/synergy-util/projection-patch";
7
+ import { ExecutionService } from "./service.js";
8
+ import { ExecutionSchema } from "./schema.js";
9
+ import { z } from "zod";
10
+ export var ExecutionProjection;
11
+ (function (ExecutionProjection) {
12
+ const checkpoints = RuntimeContext.state(() => new Map());
13
+ function checkpoint(sessionID, token) {
14
+ const value = checkpoints().get(token);
15
+ if (!value ||
16
+ !("summary" in value) ||
17
+ value.sessionID !== sessionID ||
18
+ value.scopeID !== ScopeContext.current.scope.id)
19
+ throw new Storage.NotFoundError({ message: "Execution checkpoint expired" });
20
+ return value.summary;
21
+ }
22
+ ExecutionProjection.checkpoint = checkpoint;
23
+ function projectionCheckpoint(sessionID, token) {
24
+ const value = checkpoints().get(token);
25
+ if (!value ||
26
+ !("projection" in value) ||
27
+ value.sessionID !== sessionID ||
28
+ value.scopeID !== ScopeContext.current.scope.id)
29
+ throw new Storage.NotFoundError({ message: "Execution checkpoint expired" });
30
+ return structuredClone(value.projection);
31
+ }
32
+ ExecutionProjection.projectionCheckpoint = projectionCheckpoint;
33
+ const Demand = z.enum(["root", "summary", "trajectory"]);
34
+ async function openDemand(...[interest, send, signal]) {
35
+ const demands = z.array(Demand).min(1).parse(interest.demands);
36
+ const scope = await Scope.resolve({ scopeID: interest.scopeID });
37
+ const session = await Session.get(interest.sessionID);
38
+ if (session.scope.id !== scope.id)
39
+ throw new Storage.NotFoundError({ message: "Execution belongs to another Scope" });
40
+ const controller = new AbortController();
41
+ const abort = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
42
+ abort.throwIfAborted();
43
+ const trajectory = demands.includes("trajectory");
44
+ const summary = trajectory || demands.includes("summary");
45
+ const releases = [];
46
+ if (summary)
47
+ releases.push(ExecutionService.subscribe(session.id, { trajectory }));
48
+ let disposed = false;
49
+ let frameRevision = 0;
50
+ let token;
51
+ let previous;
52
+ let current = {
53
+ state: "root",
54
+ sessionID: session.id,
55
+ revision: 0,
56
+ previousRevision: 0,
57
+ contexts: [],
58
+ contextTotal: 0,
59
+ };
60
+ const store = checkpoints();
61
+ const dispose = () => {
62
+ if (disposed)
63
+ return;
64
+ disposed = true;
65
+ abort.removeEventListener("abort", dispose);
66
+ controller.abort();
67
+ for (const release of releases.splice(0))
68
+ release();
69
+ if (token)
70
+ store.delete(token);
71
+ previous = undefined;
72
+ };
73
+ abort.addEventListener("abort", dispose, { once: true });
74
+ const publish = (value) => {
75
+ if (disposed)
76
+ return;
77
+ value = ExecutionSchema.Projection.parse(value);
78
+ current = value;
79
+ const patches = projectionPatch(previous, value);
80
+ if (!patches.length)
81
+ return;
82
+ const properties = {
83
+ kind: "execution",
84
+ sessionID: session.id,
85
+ generation: interest.generation,
86
+ revision: frameRevision + 1,
87
+ previousRevision: frameRevision,
88
+ patches,
89
+ snapshot: undefined,
90
+ };
91
+ const frame = { scopeID: scope.id, payload: { type: "server.view.patch", properties } };
92
+ if (Buffer.byteLength(JSON.stringify(frame)) > 64 * 1024) {
93
+ if (token)
94
+ store.delete(token);
95
+ token = crypto.randomUUID();
96
+ store.set(token, { scopeID: scope.id, sessionID: session.id, projection: structuredClone(value) });
97
+ properties.patches = [];
98
+ properties.snapshot = token;
99
+ }
100
+ previous = structuredClone(value);
101
+ frameRevision++;
102
+ send(frame);
103
+ };
104
+ const scoped = (fn) => ScopeContext.provide({ scope, fn });
105
+ try {
106
+ const release = await scoped(() => ExecutionService.observeRoot(session.id, (root) => {
107
+ const selected = root.contexts.filter((context) => !interest.runID || context.runID === interest.runID);
108
+ const visible = new Set(interest.nodeIDs);
109
+ const contexts = selected.filter((context, index) => index < 30 || visible.has(context.nodeID));
110
+ publish({
111
+ ...current,
112
+ revision: root.revision,
113
+ previousRevision: current.revision,
114
+ contexts,
115
+ contextTotal: selected.length,
116
+ });
117
+ }, abort));
118
+ if (disposed)
119
+ release();
120
+ else
121
+ releases.push(release);
122
+ if (summary && !disposed) {
123
+ const releaseProgress = await scoped(() => ExecutionService.observePreparation(session.id, (progress) => {
124
+ if (current.state === "preparing")
125
+ publish({ ...current, progress });
126
+ }));
127
+ if (disposed) {
128
+ releaseProgress();
129
+ return { project() { }, dispose };
130
+ }
131
+ releases.push(releaseProgress);
132
+ publish({ ...current, state: "preparing", progress: { stage: "metadata", prepared: 0 } });
133
+ void scoped(async () => {
134
+ const value = await ExecutionService.summary(session.id, undefined, abort);
135
+ const rounds = interest.runID ? [await ExecutionService.summary(session.id, interest.runID, abort)] : [];
136
+ if (current.state === "ready" && current.summary.revision > value.revision)
137
+ return;
138
+ publish({
139
+ ...current,
140
+ state: "ready",
141
+ summary: value,
142
+ detailPreviousRevision: value.revision,
143
+ roundSummaries: rounds,
144
+ upserts: [],
145
+ processUpserts: [],
146
+ processRemoved: [],
147
+ removed: [],
148
+ });
149
+ await ExecutionService.verifyJournal(session.id, abort);
150
+ abort.throwIfAborted();
151
+ const verified = await ExecutionService.summary(session.id, undefined, abort);
152
+ const verifiedRounds = interest.runID
153
+ ? [await ExecutionService.summary(session.id, interest.runID, abort)]
154
+ : [];
155
+ if (current.state === "ready" && current.summary.revision > verified.revision)
156
+ return;
157
+ publish({
158
+ ...current,
159
+ state: "ready",
160
+ summary: verified,
161
+ roundSummaries: verifiedRounds,
162
+ detailPreviousRevision: current.state === "ready" ? current.summary.revision : verified.revision,
163
+ upserts: [],
164
+ processUpserts: [],
165
+ processRemoved: [],
166
+ removed: [],
167
+ });
168
+ }).catch((error) => {
169
+ if (!disposed)
170
+ publish({
171
+ ...current,
172
+ state: "failed",
173
+ error: {
174
+ kind: error instanceof Error ? error.name : "Error",
175
+ message: "Unable to prepare execution summary",
176
+ retryable: true,
177
+ },
178
+ });
179
+ });
180
+ }
181
+ }
182
+ catch (error) {
183
+ dispose();
184
+ throw error;
185
+ }
186
+ return {
187
+ project(event) {
188
+ if (event.scopeID !== scope.id)
189
+ return;
190
+ if (event.payload.type === "session.deleted" &&
191
+ event.payload.properties.info?.id === session.id) {
192
+ dispose();
193
+ return;
194
+ }
195
+ if (!summary || event.payload.type !== "execution.updated" || event.payload.properties.sessionID !== session.id)
196
+ return;
197
+ const value = ExecutionSchema.Updated.properties.parse(event.payload.properties);
198
+ if (current.state === "ready" && value.summary.revision <= current.summary.revision)
199
+ return;
200
+ const visible = interest.nodeIDs && new Set(interest.nodeIDs);
201
+ publish({
202
+ ...current,
203
+ state: "ready",
204
+ summary: value.summary,
205
+ detailPreviousRevision: value.previousRevision ?? value.summary.revision,
206
+ roundSummaries: value.roundSummaries.filter((round) => !interest.runID || round.runID === interest.runID),
207
+ upserts: trajectory ? value.upserts.filter((node) => !visible || visible.has(node.id)) : [],
208
+ processUpserts: trajectory ? (value.processUpserts ?? []) : [],
209
+ processRemoved: trajectory ? (value.processRemoved ?? []) : [],
210
+ removed: trajectory ? value.removed : [],
211
+ });
212
+ },
213
+ dispose,
214
+ };
215
+ }
216
+ function baseline(summary) {
217
+ return {
218
+ sessionID: summary.sessionID,
219
+ revision: summary.revision,
220
+ summary,
221
+ roundSummaries: [],
222
+ contextUpserts: [],
223
+ upserts: [],
224
+ processUpserts: [],
225
+ processRemoved: [],
226
+ removed: [],
227
+ previousRevision: summary.revision,
228
+ };
229
+ }
230
+ ExecutionProjection.baseline = baseline;
231
+ function contribution() {
232
+ return {
233
+ kind: "execution",
234
+ eventType: "execution.updated",
235
+ legacy: ExecutionService.legacyClient,
236
+ async open(interest, send, signal) {
237
+ if (interest.demands)
238
+ return openDemand(interest, send, signal);
239
+ signal?.throwIfAborted();
240
+ const scope = await Scope.resolve({ scopeID: interest.scopeID });
241
+ const session = await Session.get(interest.sessionID);
242
+ if (session.scope.id !== scope.id)
243
+ throw new Storage.NotFoundError({ message: "Execution belongs to another Scope" });
244
+ signal?.throwIfAborted();
245
+ const release = ExecutionService.subscribe(session.id);
246
+ let previous, revision = 0, token, disposed = false;
247
+ const store = checkpoints();
248
+ const dispose = () => {
249
+ if (disposed)
250
+ return;
251
+ disposed = true;
252
+ signal?.removeEventListener("abort", dispose);
253
+ release();
254
+ if (token)
255
+ store.delete(token);
256
+ previous = undefined;
257
+ };
258
+ signal?.addEventListener("abort", dispose, { once: true });
259
+ const publish = (value) => {
260
+ if (disposed)
261
+ return;
262
+ const patches = projectionPatch(previous, value);
263
+ if (!patches.length)
264
+ return;
265
+ const properties = {
266
+ kind: "execution",
267
+ sessionID: session.id,
268
+ generation: interest.generation,
269
+ revision: revision + 1,
270
+ previousRevision: revision,
271
+ patches,
272
+ snapshot: undefined,
273
+ };
274
+ const frame = { scopeID: scope.id, payload: { type: "server.view.patch", properties } };
275
+ if (Buffer.byteLength(JSON.stringify(frame)) > 64 * 1024) {
276
+ const summary = value.summary;
277
+ if (token)
278
+ store.delete(token);
279
+ token = crypto.randomUUID();
280
+ const snapshot = structuredClone(summary);
281
+ store.set(token, { scopeID: scope.id, sessionID: session.id, summary: snapshot });
282
+ previous = baseline(snapshot);
283
+ properties.patches = [];
284
+ properties.snapshot = token;
285
+ }
286
+ else
287
+ previous = applyProjectionPatch(previous, patches);
288
+ revision++;
289
+ send(frame);
290
+ };
291
+ try {
292
+ const summary = await ScopeContext.provide({
293
+ scope,
294
+ fn: () => ExecutionService.summary(session.id, undefined, signal),
295
+ });
296
+ publish(baseline(summary));
297
+ }
298
+ catch (error) {
299
+ dispose();
300
+ throw error;
301
+ }
302
+ return {
303
+ project(event) {
304
+ if (event.scopeID !== scope.id ||
305
+ event.payload.type !== "execution.updated" ||
306
+ event.payload.properties.sessionID !== session.id)
307
+ return;
308
+ const value = event.payload.properties;
309
+ const priorRevision = previous?.revision ?? -1;
310
+ if (Number(value.revision) <= priorRevision)
311
+ return;
312
+ const visible = interest.nodeIDs && new Set(interest.nodeIDs);
313
+ const selected = {
314
+ ...value,
315
+ roundSummaries: value.roundSummaries.filter((summary) => !interest.runID || summary.runID === interest.runID),
316
+ contextUpserts: (value.contextUpserts ?? []).filter((context) => !visible || visible.has(context.nodeID)),
317
+ upserts: value.upserts.filter((node) => !visible || visible.has(node.id)),
318
+ };
319
+ publish(selected);
320
+ },
321
+ dispose,
322
+ };
323
+ },
324
+ };
325
+ }
326
+ ExecutionProjection.contribution = contribution;
327
+ })(ExecutionProjection || (ExecutionProjection = {}));
@@ -0,0 +1,18 @@
1
+ export class ExecutionQueryError extends RangeError {
2
+ }
3
+ export function queryValue(schema, value) {
4
+ const parsed = schema.safeParse(value);
5
+ if (!parsed.success)
6
+ throw new ExecutionQueryError("Invalid execution query");
7
+ return parsed.data;
8
+ }
9
+ export function queryCursor(schema, cursor) {
10
+ let value;
11
+ try {
12
+ value = JSON.parse(Buffer.from(cursor, "base64url").toString());
13
+ }
14
+ catch {
15
+ throw new ExecutionQueryError("Invalid execution cursor");
16
+ }
17
+ return queryValue(schema, value);
18
+ }
@@ -0,0 +1,189 @@
1
+ import { RolloutEvidence } from "@ericsanchezok/synergy-harness/rollout";
2
+ import { Log } from "@ericsanchezok/synergy-harness/util/log";
3
+ import { ExecutionQueryError } from "./query.js";
4
+ import { Hono } from "hono";
5
+ import { describeRoute, resolver, validator } from "hono-openapi";
6
+ import { z } from "zod";
7
+ import { Identifier } from "@ericsanchezok/synergy-harness/id/id";
8
+ import { Storage } from "@ericsanchezok/synergy-harness/storage/storage";
9
+ import { ExecutionSchema } from "./schema.js";
10
+ import { ExecutionService } from "./service.js";
11
+ import { ExecutionProjection } from "./projection.js";
12
+ import { ExecutionContent } from "./content.js";
13
+ import { RuntimeContext } from "@ericsanchezok/synergy-harness/lifecycle/context";
14
+ import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context";
15
+ import { Scope } from "@ericsanchezok/synergy-harness/scope";
16
+ const Params = z.object({ sessionID: Identifier.schema("session") });
17
+ const ContextParams = Params.extend({ callID: z.string().min(1).max(4096) });
18
+ const NodeParams = Params.extend({ nodeID: z.string().min(1).max(4096) });
19
+ const ContentQuery = ExecutionContent.Query.extend({
20
+ offset: z.coerce.number().int().nonnegative().safe().default(0),
21
+ limit: z.coerce.number().int().min(1).max(65_536).default(65_536),
22
+ });
23
+ async function download(source, field, signal) {
24
+ const runtime = RuntimeContext.current();
25
+ const scope = ScopeContext.current.scope;
26
+ const workspace = ScopeContext.current.workspace;
27
+ const iterator = source.stream()[Symbol.asyncIterator]();
28
+ const release = Scope.registerArchiveGuard((id) => {
29
+ if (id === scope.id)
30
+ throw new Error("Execution content is being read");
31
+ });
32
+ const bind = (fn) => runtime.bind(() => ScopeContext.provide({ scope, workspace, fn }));
33
+ let closed = false;
34
+ let controller;
35
+ const finish = () => {
36
+ if (closed)
37
+ return;
38
+ closed = true;
39
+ release();
40
+ signal.removeEventListener("abort", abort);
41
+ };
42
+ const abort = () => {
43
+ if (closed)
44
+ return;
45
+ controller?.error(signal.reason);
46
+ finish();
47
+ void bind(() => iterator.return?.())().catch(() => { });
48
+ };
49
+ signal.addEventListener("abort", abort, { once: true });
50
+ const body = new ReadableStream({
51
+ start(value) {
52
+ controller = value;
53
+ if (signal.aborted)
54
+ abort();
55
+ },
56
+ pull: bind(async () => {
57
+ if (closed)
58
+ return;
59
+ try {
60
+ const item = await iterator.next();
61
+ if (closed)
62
+ return;
63
+ if (item.done) {
64
+ finish();
65
+ controller.close();
66
+ return;
67
+ }
68
+ controller.enqueue(item.value);
69
+ }
70
+ catch (error) {
71
+ finish();
72
+ controller.error(error);
73
+ }
74
+ }),
75
+ cancel: bind(async () => {
76
+ finish();
77
+ await iterator.return?.();
78
+ }),
79
+ });
80
+ return new Response(body, {
81
+ headers: {
82
+ "Content-Type": "application/octet-stream",
83
+ "Content-Disposition": `attachment; filename="execution-${field.replace(/[^a-zA-Z0-9_-]/g, "_")}.${source.mediaType.includes("json") ? "json" : "txt"}"`,
84
+ "Content-Length": String(source.bytes),
85
+ "Cache-Control": "no-store",
86
+ "X-Execution-Content-Version": source.contentVersion,
87
+ "X-Execution-Content-Status": source.status,
88
+ ...(source.sha256 ? { "X-Execution-Content-Sha256": source.sha256 } : {}),
89
+ },
90
+ });
91
+ }
92
+ const metadata = (operationId, summary, schema) => describeRoute({
93
+ operationId,
94
+ summary,
95
+ responses: {
96
+ 200: { description: summary, content: { "application/json": { schema: resolver(schema) } } },
97
+ 400: { description: "Invalid execution query" },
98
+ 500: { description: "Execution computation failed" },
99
+ 404: { description: "Execution evidence was not found in the selected Scope and session" },
100
+ },
101
+ });
102
+ async function result(body) {
103
+ try {
104
+ return { status: 200, value: await body() };
105
+ }
106
+ catch (error) {
107
+ if (error instanceof Storage.NotFoundError)
108
+ return { status: 404, value: { message: error.message } };
109
+ if (error instanceof ExecutionQueryError || error instanceof RolloutEvidence.QueryError)
110
+ return { status: 400, value: { message: "Invalid execution query" } };
111
+ if (!(error instanceof Error && error.name === "AbortError"))
112
+ Log.create({ service: "execution" }).error("execution read failed", {
113
+ kind: error instanceof Error ? error.name : "unknown",
114
+ issues: error instanceof z.ZodError
115
+ ? error.issues.map((issue) => ({ code: issue.code, path: issue.path }))
116
+ : undefined,
117
+ });
118
+ throw error;
119
+ }
120
+ }
121
+ export const ExecutionRoute = () => new Hono()
122
+ .get("/:sessionID/execution/projection", metadata("session.executionProjection", "Read a captured execution projection", ExecutionSchema.Projection), validator("param", Params), validator("query", z.object({ token: z.string().uuid() })), async (c) => {
123
+ const response = await result(() => Promise.resolve(ExecutionProjection.projectionCheckpoint(c.req.valid("param").sessionID, c.req.valid("query").token)));
124
+ return c.json(response.value, response.status);
125
+ })
126
+ .get("/:sessionID/execution/summary", metadata("session.executionSummary", "Read a compact session execution summary", ExecutionSchema.Summary), validator("param", Params), validator("query", ExecutionSchema.Query.pick({ runID: true }).extend({ projection: z.string().uuid().optional() })), async (c) => {
127
+ const response = await result(() => c.req.valid("query").projection
128
+ ? Promise.resolve(ExecutionProjection.checkpoint(c.req.valid("param").sessionID, c.req.valid("query").projection))
129
+ : ExecutionService.summary(c.req.valid("param").sessionID, c.req.valid("query").runID, c.req.raw.signal));
130
+ return c.json(response.value, response.status);
131
+ })
132
+ .get("/:sessionID/execution/context/history", metadata("session.executionContextHistory", "Read context request history", ExecutionSchema.ContextHistory), validator("param", Params), validator("query", ExecutionSchema.ContextQuery), async (c) => {
133
+ const response = await result(() => ExecutionService.contextHistory(c.req.valid("param").sessionID, c.req.valid("query"), c.req.raw.signal));
134
+ return c.json(response.value, response.status);
135
+ })
136
+ .get("/:sessionID/execution/context/snapshots/:callID", metadata("session.executionContextSnapshot", "Read one context request snapshot", ExecutionSchema.ContextSnapshot), validator("param", ContextParams), validator("query", ExecutionSchema.ContextQuery.pick({ runID: true })), async (c) => {
137
+ const { sessionID, callID } = c.req.valid("param");
138
+ const response = await result(() => ExecutionService.contextSnapshot(sessionID, callID, c.req.valid("query").runID, c.req.raw.signal));
139
+ return c.json(response.value, response.status);
140
+ })
141
+ .get("/:sessionID/execution/context/snapshots/:callID/items", metadata("session.executionContextItems", "Read a bounded context source index", ExecutionSchema.ContextItems), validator("param", ContextParams), validator("query", ExecutionSchema.ContextItemsQuery), async (c) => {
142
+ const { sessionID, callID } = c.req.valid("param");
143
+ const response = await result(() => ExecutionService.contextItems(sessionID, callID, c.req.valid("query"), c.req.raw.signal));
144
+ return c.json(response.value, response.status);
145
+ })
146
+ .get("/:sessionID/execution/trajectory", metadata("session.executionTrajectory", "Page through session execution records", ExecutionSchema.Page), validator("param", Params), validator("query", ExecutionSchema.Query), async (c) => {
147
+ const response = await result(() => ExecutionService.trajectory(c.req.valid("param").sessionID, c.req.valid("query"), c.req.raw.signal));
148
+ return c.json(response.value, response.status);
149
+ })
150
+ .get("/:sessionID/execution/nodes/:nodeID", metadata("session.executionNode", "Inspect one session execution record", ExecutionSchema.Detail), validator("param", NodeParams), validator("query", ExecutionSchema.Query.pick({ runID: true })), async (c) => {
151
+ const { sessionID, nodeID } = c.req.valid("param");
152
+ const response = await result(() => ExecutionService.node(sessionID, nodeID, c.req.valid("query").runID, c.req.raw.signal));
153
+ return c.json(response.value, response.status);
154
+ })
155
+ .get("/:sessionID/execution/nodes/:nodeID/content", metadata("session.executionContent", "Read a bounded execution content page", ExecutionSchema.Content), validator("param", NodeParams), validator("query", ContentQuery), async (c) => {
156
+ const { sessionID, nodeID } = c.req.valid("param");
157
+ const { field, offset, limit, runID, version } = c.req.valid("query");
158
+ const response = await result(() => ExecutionService.content(sessionID, nodeID, field, offset, limit, runID, version, c.req.raw.signal));
159
+ return c.json(response.value, response.status);
160
+ })
161
+ .get("/:sessionID/execution/nodes/:nodeID/content/sections", metadata("session.executionContentSections", "Read structured execution content sections", ExecutionContent.Sections), validator("param", NodeParams), validator("query", ExecutionContent.PageQuery), async (c) => {
162
+ const { sessionID, nodeID } = c.req.valid("param");
163
+ const input = c.req.valid("query");
164
+ const response = await result(async () => ExecutionContent.sections(await ExecutionService.contentSource(sessionID, nodeID, input.field, input.runID, input.version, c.req.raw.signal), input, c.req.raw.signal));
165
+ return c.json(response.value, response.status);
166
+ })
167
+ .get("/:sessionID/execution/nodes/:nodeID/content/search", metadata("session.executionContentSearch", "Search an entire version of execution content", ExecutionContent.Search), validator("param", NodeParams), validator("query", ExecutionContent.SearchQuery), async (c) => {
168
+ const { sessionID, nodeID } = c.req.valid("param");
169
+ const input = c.req.valid("query");
170
+ const response = await result(async () => ExecutionContent.search(await ExecutionService.contentSource(sessionID, nodeID, input.field, input.runID, input.version, c.req.raw.signal), input, c.req.raw.signal));
171
+ return c.json(response.value, response.status);
172
+ })
173
+ .get("/:sessionID/execution/nodes/:nodeID/content/download", describeRoute({
174
+ operationId: "session.executionContentDownload",
175
+ summary: "Download complete execution content at a fixed version",
176
+ responses: {
177
+ 200: {
178
+ description: "Verified execution content",
179
+ content: { "application/octet-stream": { schema: { type: "string", format: "binary" } } },
180
+ },
181
+ 400: { description: "Invalid execution content query or version" },
182
+ 404: { description: "Execution content was not found" },
183
+ },
184
+ }), validator("param", NodeParams), validator("query", ExecutionContent.Query), async (c) => {
185
+ const { sessionID, nodeID } = c.req.valid("param");
186
+ const input = c.req.valid("query");
187
+ const response = await result(async () => download(await ExecutionService.contentSource(sessionID, nodeID, input.field, input.runID, input.version, c.req.raw.signal), input.field, c.req.raw.signal));
188
+ return response.status === 200 ? response.value : c.json(response.value, response.status);
189
+ });