agent-hitch 0.1.0 → 0.2.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 (92) hide show
  1. package/README.md +240 -126
  2. package/README.zh-CN.md +330 -0
  3. package/dist/bin/hitch.js +9 -0
  4. package/dist/bin/hitch.js.map +1 -0
  5. package/dist/scripts/check-release.js +25 -0
  6. package/dist/scripts/check-release.js.map +1 -0
  7. package/dist/scripts/check-syntax.js +32 -0
  8. package/dist/scripts/check-syntax.js.map +1 -0
  9. package/dist/src/adapters.js +461 -0
  10. package/dist/src/adapters.js.map +1 -0
  11. package/dist/src/artifacts.js +1081 -0
  12. package/dist/src/artifacts.js.map +1 -0
  13. package/dist/src/cli.js +699 -0
  14. package/dist/src/cli.js.map +1 -0
  15. package/dist/src/config.js +53 -0
  16. package/dist/src/config.js.map +1 -0
  17. package/dist/src/controller-runtime/hash.js +353 -0
  18. package/dist/src/controller-runtime/hash.js.map +1 -0
  19. package/dist/src/controller-runtime/store.js +323 -0
  20. package/dist/src/controller-runtime/store.js.map +1 -0
  21. package/dist/src/daemon.js +461 -0
  22. package/dist/src/daemon.js.map +1 -0
  23. package/dist/src/domain/types.js +10 -0
  24. package/dist/src/domain/types.js.map +1 -0
  25. package/dist/src/domain/validate.js +240 -0
  26. package/dist/src/domain/validate.js.map +1 -0
  27. package/dist/src/engine.js +465 -0
  28. package/dist/src/engine.js.map +1 -0
  29. package/dist/src/errors.js +14 -0
  30. package/dist/src/errors.js.map +1 -0
  31. package/dist/src/eval-tools.js +318 -0
  32. package/dist/src/eval-tools.js.map +1 -0
  33. package/dist/src/evals.js +313 -0
  34. package/dist/src/evals.js.map +1 -0
  35. package/dist/src/events.js +80 -0
  36. package/dist/src/events.js.map +1 -0
  37. package/dist/src/feedback/service.js +315 -0
  38. package/dist/src/feedback/service.js.map +1 -0
  39. package/dist/src/fs.js +45 -0
  40. package/dist/src/fs.js.map +1 -0
  41. package/dist/src/harbor-backend.js +280 -0
  42. package/dist/src/harbor-backend.js.map +1 -0
  43. package/dist/src/harness-reference.js +83 -0
  44. package/dist/src/harness-reference.js.map +1 -0
  45. package/dist/src/line-stream.js +20 -0
  46. package/dist/src/line-stream.js.map +1 -0
  47. package/dist/src/locks.js +38 -0
  48. package/dist/src/locks.js.map +1 -0
  49. package/dist/src/package-root.js +40 -0
  50. package/dist/src/package-root.js.map +1 -0
  51. package/dist/src/process.js +50 -0
  52. package/dist/src/process.js.map +1 -0
  53. package/dist/src/registry.js +81 -0
  54. package/dist/src/registry.js.map +1 -0
  55. package/dist/src/scheduler.js +162 -0
  56. package/dist/src/scheduler.js.map +1 -0
  57. package/dist/src/trajectories/contract.js +17 -0
  58. package/dist/src/trajectories/contract.js.map +1 -0
  59. package/dist/src/trajectories/format.js +127 -0
  60. package/dist/src/trajectories/format.js.map +1 -0
  61. package/dist/src/trajectories/projector.js +385 -0
  62. package/dist/src/trajectories/projector.js.map +1 -0
  63. package/dist/src/trajectories/store.js +281 -0
  64. package/dist/src/trajectories/store.js.map +1 -0
  65. package/dist/src/workspaces.js +858 -0
  66. package/dist/src/workspaces.js.map +1 -0
  67. package/docs/schemas/controller-runtime-manifest.schema.json +57 -0
  68. package/docs/schemas/controller-runtime-ref.schema.json +16 -0
  69. package/docs/schemas/message-feedback.schema.json +37 -0
  70. package/docs/schemas/trajectory-ref.schema.json +30 -0
  71. package/integrations/harbor/hitch_harbor_agent.py +176 -4
  72. package/package.json +19 -11
  73. package/bin/hitch.js +0 -9
  74. package/src/adapters.js +0 -361
  75. package/src/artifacts.js +0 -948
  76. package/src/cli.js +0 -505
  77. package/src/config.js +0 -51
  78. package/src/daemon.js +0 -416
  79. package/src/engine.js +0 -386
  80. package/src/errors.js +0 -12
  81. package/src/eval-tools.js +0 -331
  82. package/src/evals.js +0 -276
  83. package/src/events.js +0 -69
  84. package/src/fs.js +0 -43
  85. package/src/harbor-backend.js +0 -285
  86. package/src/harness-reference.js +0 -78
  87. package/src/line-stream.js +0 -17
  88. package/src/locks.js +0 -33
  89. package/src/process.js +0 -49
  90. package/src/registry.js +0 -84
  91. package/src/scheduler.js +0 -156
  92. package/src/workspaces.js +0 -893
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Projector that turns normalized Hitch adapter events (and plain-text output
3
+ * for `minimal` fidelity) into a canonical DSH-compatible trajectory
4
+ * (spec §5.4, §5.5). It owns the turn/step bracket state machine and the
5
+ * tool-call pairing invariant.
6
+ */
7
+ import { randomUUID } from "node:crypto";
8
+ const EVENT_TYPE_KEYS = new Set([
9
+ "turn/start", "turn/end", "step/start", "step/end",
10
+ "user/message", "assistant/chunk", "assistant/message",
11
+ "tool/call", "tool/result",
12
+ ]);
13
+ export class TrajectoryProjector {
14
+ header;
15
+ events = [];
16
+ fidelity;
17
+ prompt;
18
+ modelSource;
19
+ seq = 0;
20
+ // DSH's current persistence format numbers turns from one. A zero turn is
21
+ // reserved for an older pre-react-loop envelope and is rejected on load.
22
+ turn = 1;
23
+ step = 1;
24
+ stepOpen = false;
25
+ promptRecorded = false;
26
+ requestHeaderRecorded = false;
27
+ turnOpen = false;
28
+ assistantOpen = false;
29
+ assistantText = "";
30
+ assistantUsage;
31
+ lastAssistantEvent;
32
+ openCalls = new Map();
33
+ finalOutput = "";
34
+ providerSessionId;
35
+ finalized = false;
36
+ constructor(options) {
37
+ this.fidelity = options.fidelity;
38
+ this.prompt = options.prompt;
39
+ this.modelSource = parseModelSource(options.model);
40
+ this.header = {
41
+ type: "session",
42
+ version: 0,
43
+ id: options.runId,
44
+ createdAt: Date.now(),
45
+ cwd: options.cwd,
46
+ delegationDepth: 0,
47
+ ...(options.agentPreset ? { agentPreset: options.agentPreset } : {}),
48
+ };
49
+ }
50
+ get sessionId() {
51
+ return this.header.id;
52
+ }
53
+ get sessionHeader() {
54
+ return this.header;
55
+ }
56
+ get eventCount() {
57
+ return this.events.length;
58
+ }
59
+ /** Feed one normalized Hitch event into the projector. */
60
+ feed(event) {
61
+ if (this.finalized)
62
+ throw new Error("cannot feed a finalized trajectory");
63
+ switch (event.type) {
64
+ case "session.created":
65
+ this.providerSessionId ||= event.session_id;
66
+ this.ensureSessionOpen();
67
+ break;
68
+ case "message.delta":
69
+ this.advanceAfterCompletedStep();
70
+ this.ensureStepOpen();
71
+ this.assistantOpen = true;
72
+ this.assistantText += event.text;
73
+ break;
74
+ case "message.completed":
75
+ this.advanceAfterCompletedStep();
76
+ this.ensureStepOpen();
77
+ this.assistantOpen = true;
78
+ // A completed message is the authoritative final text for the step:
79
+ // it replaces any deltas accumulated so far (the original engine
80
+ // overwrote `finalMessage` on `message.completed`).
81
+ this.assistantText = event.text;
82
+ this.closeAssistantMessage();
83
+ break;
84
+ case "tool.started":
85
+ this.ensureStepOpen();
86
+ this.openCalls.set(event.call_id, { callId: event.call_id, name: event.name });
87
+ this.append({
88
+ type: "tool/call",
89
+ data: {
90
+ turn: this.turn,
91
+ step: this.step,
92
+ callId: event.call_id,
93
+ name: event.name,
94
+ arguments: event.input === undefined ? "{}" : JSON.stringify(event.input),
95
+ },
96
+ });
97
+ break;
98
+ case "tool.completed": {
99
+ const open = this.openCalls.get(event.call_id);
100
+ if (!open) {
101
+ // A result without a recorded call cannot be paired; record it as a
102
+ // namespaced informational event rather than fabricating a call.
103
+ this.append({
104
+ type: "hitch/unpaired-tool-result",
105
+ data: { call_id: event.call_id, status: event.status, output: event.output ?? null },
106
+ ignorable: true,
107
+ });
108
+ break;
109
+ }
110
+ this.openCalls.delete(event.call_id);
111
+ const isError = event.status !== "succeeded";
112
+ this.append({
113
+ type: "tool/result",
114
+ data: {
115
+ turn: this.turn,
116
+ step: this.step,
117
+ message: {
118
+ id: randomUUID(),
119
+ role: "user",
120
+ content: [{
121
+ type: "tool-result",
122
+ toolCallId: event.call_id,
123
+ content: [{ type: "text", text: toolResultText(event.output) }],
124
+ isError,
125
+ }],
126
+ source: { kind: "tool", callId: event.call_id },
127
+ },
128
+ ...(isError ? { error: { name: open.name, code: "tool_failed" } } : {}),
129
+ },
130
+ surfaceOp: "append",
131
+ });
132
+ break;
133
+ }
134
+ case "usage.updated":
135
+ const usage = normalizeUsage(event.usage);
136
+ if (!usage)
137
+ break;
138
+ if (this.assistantOpen) {
139
+ this.assistantUsage = usage;
140
+ }
141
+ else if (this.lastAssistantEvent && this.lastAssistantEvent.type === "assistant/message") {
142
+ // Some adapters (Pi, OpenCode) emit usage after `message.completed`
143
+ // closed the assistant message. Attach it to the just-closed
144
+ // assistant/message so the canonical record carries the accounting.
145
+ const data = this.lastAssistantEvent.data;
146
+ if (!("usage" in data))
147
+ data.usage = usage;
148
+ }
149
+ break;
150
+ case "diagnostic":
151
+ this.append({
152
+ type: "hitch/diagnostic",
153
+ data: { level: event.level, message: event.message },
154
+ ignorable: true,
155
+ });
156
+ break;
157
+ case "provider.event":
158
+ this.append({
159
+ type: "hitch/provider-event",
160
+ data: { provider_type: event.provider_type, native: event.native },
161
+ ignorable: true,
162
+ });
163
+ break;
164
+ default:
165
+ this.append({
166
+ type: "hitch/unknown-event",
167
+ data: { native: event },
168
+ ignorable: true,
169
+ });
170
+ }
171
+ }
172
+ /** Feed raw plain-text output (minimal fidelity). */
173
+ feedText(text) {
174
+ this.ensureStepOpen();
175
+ this.assistantOpen = true;
176
+ this.assistantText += text;
177
+ }
178
+ /**
179
+ * Close the trajectory. `status` selects the terminal turn reason.
180
+ * Returns the finalized projection.
181
+ */
182
+ finalize(status) {
183
+ if (this.finalized)
184
+ return this.project();
185
+ this.finalized = true;
186
+ // A timeout/cancellation/crash with no recorded work still gets a valid
187
+ // terminal boundary (spec §5.4): open and close the session so the log is
188
+ // structurally complete even when nothing was emitted.
189
+ if (this.events.length === 0 && status !== "succeeded")
190
+ this.ensureSessionOpen();
191
+ if (this.assistantOpen && this.assistantText)
192
+ this.closeAssistantMessage(true);
193
+ if (this.stepOpen) {
194
+ // A successful run may not end with open tool calls: the harness claimed
195
+ // to finish cleanly, so an unpaired call is evidence of a broken
196
+ // trajectory, not an interruption. Only failed/timed_out/cancelled runs
197
+ // may synthesize unknown-outcome results for interrupted calls (spec
198
+ // §5.4: a completed run has no open call).
199
+ if (status === "succeeded" && this.openCalls.size > 0) {
200
+ const open = [...this.openCalls.values()].map((call) => call.callId).join(", ");
201
+ throw new Error(`trajectory finalization failed: run succeeded with open tool calls: ${open}`);
202
+ }
203
+ for (const open of [...this.openCalls.values()]) {
204
+ this.openCalls.delete(open.callId);
205
+ this.append({
206
+ type: "tool/result",
207
+ data: {
208
+ turn: this.turn,
209
+ step: this.step,
210
+ message: {
211
+ id: randomUUID(),
212
+ role: "user",
213
+ content: [{
214
+ type: "tool-result",
215
+ toolCallId: open.callId,
216
+ content: [{ type: "text", text: `tool call interrupted: ${open.name} outcome unknown` }],
217
+ isError: true,
218
+ }],
219
+ source: { kind: "tool", callId: open.callId },
220
+ },
221
+ error: { name: open.name, code: "TOOL_OUTCOME_UNKNOWN" },
222
+ },
223
+ surfaceOp: "append",
224
+ });
225
+ }
226
+ this.append({ type: "step/end", data: { turn: this.turn, step: this.step } });
227
+ this.stepOpen = false;
228
+ }
229
+ if (this.turnOpen) {
230
+ this.append({
231
+ type: "turn/end",
232
+ data: { turn: this.turn, reason: terminalReason(status) },
233
+ });
234
+ this.turnOpen = false;
235
+ }
236
+ return this.project();
237
+ }
238
+ ensureSessionOpen() {
239
+ if (this.turnOpen)
240
+ return;
241
+ this.turnOpen = true;
242
+ this.append({
243
+ type: "turn/start",
244
+ data: { turn: this.turn },
245
+ });
246
+ }
247
+ appendPrompt() {
248
+ if (this.promptRecorded)
249
+ return;
250
+ this.append({
251
+ type: "user/message",
252
+ data: {
253
+ id: randomUUID(),
254
+ role: "user",
255
+ content: [{ type: "text", text: this.prompt }],
256
+ source: { kind: "user" },
257
+ },
258
+ surfaceOp: "append",
259
+ });
260
+ this.promptRecorded = true;
261
+ }
262
+ ensureStepOpen() {
263
+ this.ensureSessionOpen();
264
+ if (this.stepOpen)
265
+ return;
266
+ this.stepOpen = true;
267
+ this.append({ type: "step/start", data: { turn: this.turn, step: this.step } });
268
+ this.appendPrompt();
269
+ if (!this.requestHeaderRecorded) {
270
+ this.append({
271
+ type: "request/header",
272
+ data: {
273
+ header: { config: { ...this.modelSource } },
274
+ reason: "initial",
275
+ },
276
+ });
277
+ this.requestHeaderRecorded = true;
278
+ }
279
+ }
280
+ /** A new model reply after a completed reply starts the next DSH react step. */
281
+ advanceAfterCompletedStep() {
282
+ if (!this.stepOpen || this.assistantOpen || !this.lastAssistantEvent || this.openCalls.size > 0)
283
+ return;
284
+ this.append({ type: "step/end", data: { turn: this.turn, step: this.step } });
285
+ this.step += 1;
286
+ this.stepOpen = false;
287
+ this.lastAssistantEvent = undefined;
288
+ }
289
+ closeAssistantMessage(interrupted = false) {
290
+ if (!this.assistantOpen)
291
+ return;
292
+ const text = this.assistantText;
293
+ if (text)
294
+ this.finalOutput = text;
295
+ this.lastAssistantEvent = this.append({
296
+ type: "assistant/message",
297
+ data: {
298
+ turn: this.turn,
299
+ step: this.step,
300
+ message: {
301
+ id: randomUUID(),
302
+ role: "assistant",
303
+ content: [{ type: "text", text }],
304
+ source: { kind: "model", ...this.modelSource },
305
+ },
306
+ ...(this.assistantUsage ? { usage: this.assistantUsage } : {}),
307
+ ...(interrupted ? { interrupted: true } : {}),
308
+ },
309
+ sourceEventSeqs: [],
310
+ surfaceOp: "append",
311
+ });
312
+ this.assistantOpen = false;
313
+ this.assistantText = "";
314
+ this.assistantUsage = undefined;
315
+ }
316
+ append(event) {
317
+ const framed = {
318
+ ...event,
319
+ seq: this.seq,
320
+ time: Date.now(),
321
+ };
322
+ this.seq += 1;
323
+ this.events.push(framed);
324
+ return framed;
325
+ }
326
+ project() {
327
+ const result = {
328
+ header: { ...this.header },
329
+ events: [...this.events],
330
+ finalOutput: this.finalOutput,
331
+ fidelity: this.fidelity,
332
+ };
333
+ if (this.providerSessionId)
334
+ result.providerSessionId = this.providerSessionId;
335
+ return result;
336
+ }
337
+ }
338
+ function parseModelSource(model) {
339
+ const separator = model.indexOf("/");
340
+ if (separator > 0 && separator < model.length - 1) {
341
+ return { provider: model.slice(0, separator), model: model.slice(separator + 1) };
342
+ }
343
+ return { provider: "hitch", model: model || "unknown" };
344
+ }
345
+ /** Convert native harness accounting into the canonical DSH usage envelope. */
346
+ function normalizeUsage(usage) {
347
+ const cache = usage.cache && typeof usage.cache === "object" ? usage.cache : {};
348
+ const number = (...values) => {
349
+ for (const value of values)
350
+ if (typeof value === "number" && Number.isFinite(value))
351
+ return value;
352
+ return 0;
353
+ };
354
+ const input = number(usage.inputTokens, usage.uncachedInputTokens, usage.input_tokens, usage.input);
355
+ const output = number(usage.outputTokens, usage.output_tokens, usage.output);
356
+ const cacheRead = number(usage.cacheReadTokens, usage.cached_input_tokens, usage.cacheRead, cache.read);
357
+ const cacheWrite = number(usage.cacheWriteTokens, usage.cache_write_input_tokens, usage.cacheWrite, cache.write);
358
+ return { inputTokens: input, outputTokens: output, cacheReadTokens: cacheRead, cacheWriteTokens: cacheWrite };
359
+ }
360
+ function toolResultText(output) {
361
+ if (typeof output === "string")
362
+ return output;
363
+ if (output === undefined || output === null)
364
+ return "";
365
+ try {
366
+ return JSON.stringify(output);
367
+ }
368
+ catch {
369
+ return String(output);
370
+ }
371
+ }
372
+ function terminalReason(status) {
373
+ switch (status) {
374
+ case "succeeded":
375
+ return { kind: "completed" };
376
+ case "cancelled":
377
+ return { kind: "aborted", reason: { kind: "user" } };
378
+ case "timed_out":
379
+ return { kind: "aborted", reason: { kind: "hook", reason: "timeout" } };
380
+ case "failed":
381
+ return { kind: "error", error: { message: "agent process failed", code: "UNKNOWN" } };
382
+ }
383
+ }
384
+ export { EVENT_TYPE_KEYS };
385
+ //# sourceMappingURL=projector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projector.js","sourceRoot":"","sources":["../../../src/trajectories/projector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA8BzC,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU;IAClD,cAAc,EAAE,iBAAiB,EAAE,mBAAmB;IACtD,WAAW,EAAE,aAAa;CAC3B,CAAC,CAAC;AAEH,MAAM,OAAO,mBAAmB;IACb,MAAM,CAAoB;IAC1B,MAAM,GAAmB,EAAE,CAAC;IAC5B,QAAQ,CAAqB;IAC7B,MAAM,CAAS;IACf,WAAW,CAAsC;IAC1D,GAAG,GAAG,CAAC,CAAC;IAChB,0EAA0E;IAC1E,yEAAyE;IACjE,IAAI,GAAG,CAAC,CAAC;IACT,IAAI,GAAG,CAAC,CAAC;IACT,QAAQ,GAAG,KAAK,CAAC;IACjB,cAAc,GAAG,KAAK,CAAC;IACvB,qBAAqB,GAAG,KAAK,CAAC;IAC9B,QAAQ,GAAG,KAAK,CAAC;IACjB,aAAa,GAAG,KAAK,CAAC;IACtB,aAAa,GAAG,EAAE,CAAC;IACnB,cAAc,CAAsC;IACpD,kBAAkB,CAA2B;IACpC,SAAS,GAAG,IAAI,GAAG,EAAwB,CAAC;IACrD,WAAW,GAAG,EAAE,CAAC;IACjB,iBAAiB,CAAqB;IACtC,SAAS,GAAG,KAAK,CAAC;IAE1B,YAAY,OAA0B;QACpC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,GAAG;YACZ,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,CAAC;YACV,EAAE,EAAE,OAAO,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,eAAe,EAAE,CAAC;YAClB,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC;IACJ,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED,0DAA0D;IAC1D,IAAI,CAAC,KAAsB;QACzB,IAAI,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC1E,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,iBAAiB;gBACpB,IAAI,CAAC,iBAAiB,KAAK,KAAK,CAAC,UAAU,CAAC;gBAC5C,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzB,MAAM;YACR,KAAK,eAAe;gBAClB,IAAI,CAAC,yBAAyB,EAAE,CAAC;gBACjC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC;gBACjC,MAAM;YACR,KAAK,mBAAmB;gBACtB,IAAI,CAAC,yBAAyB,EAAE,CAAC;gBACjC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,oEAAoE;gBACpE,iEAAiE;gBACjE,oDAAoD;gBACpD,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC;gBAChC,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBAC7B,MAAM;YACR,KAAK,cAAc;gBACjB,IAAI,CAAC,cAAc,EAAE,CAAC;gBACtB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC/E,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,WAAW;oBACjB,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,MAAM,EAAE,KAAK,CAAC,OAAO;wBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,SAAS,EAAE,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC;qBAC1E;iBACF,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,oEAAoE;oBACpE,iEAAiE;oBACjE,IAAI,CAAC,MAAM,CAAC;wBACV,IAAI,EAAE,4BAA4B;wBAClC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI,EAAE;wBACpF,SAAS,EAAE,IAAI;qBAChB,CAAC,CAAC;oBACH,MAAM;gBACR,CAAC;gBACD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACrC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC;gBAC7C,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,aAAa;oBACnB,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,OAAO,EAAE;4BACP,EAAE,EAAE,UAAU,EAAE;4BAChB,IAAI,EAAE,MAAM;4BACZ,OAAO,EAAE,CAAC;oCACR,IAAI,EAAE,aAAa;oCACnB,UAAU,EAAE,KAAK,CAAC,OAAO;oCACzB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;oCAC/D,OAAO;iCACR,CAAC;4BACF,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE;yBAChD;wBACD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBACxE;oBACD,SAAS,EAAE,QAAQ;iBACpB,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YACD,KAAK,eAAe;gBAClB,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC1C,IAAI,CAAC,KAAK;oBAAE,MAAM;gBAClB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACvB,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;gBAC9B,CAAC;qBAAM,IAAI,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,kBAAkB,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;oBAC3F,oEAAoE;oBACpE,6DAA6D;oBAC7D,oEAAoE;oBACpE,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAA+B,CAAC;oBACrE,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC;wBAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;gBAC7C,CAAC;gBACD,MAAM;YACR,KAAK,YAAY;gBACf,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,kBAAkB;oBACxB,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;oBACpD,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;gBACH,MAAM;YACR,KAAK,gBAAgB;gBACnB,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,sBAAsB;oBAC5B,IAAI,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;oBAClE,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;gBACH,MAAM;YACR;gBACE,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,qBAAqB;oBAC3B,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;oBACvB,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;QACP,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,QAAQ,CAAC,IAAY;QACnB,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,MAA0D;QACjE,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,wEAAwE;QACxE,0EAA0E;QAC1E,uDAAuD;QACvD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,KAAK,WAAW;YAAE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACjF,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC/E,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,yEAAyE;YACzE,iEAAiE;YACjE,wEAAwE;YACxE,qEAAqE;YACrE,2CAA2C;YAC3C,IAAI,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACtD,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChF,MAAM,IAAI,KAAK,CAAC,uEAAuE,IAAI,EAAE,CAAC,CAAC;YACjG,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;gBAChD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACnC,IAAI,CAAC,MAAM,CAAC;oBACV,IAAI,EAAE,aAAa;oBACnB,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,OAAO,EAAE;4BACP,EAAE,EAAE,UAAU,EAAE;4BAChB,IAAI,EAAE,MAAM;4BACZ,OAAO,EAAE,CAAC;oCACR,IAAI,EAAE,aAAa;oCACnB,UAAU,EAAE,IAAI,CAAC,MAAM;oCACvB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,IAAI,CAAC,IAAI,kBAAkB,EAAE,CAAC;oCACxF,OAAO,EAAE,IAAI;iCACd,CAAC;4BACF,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;yBAC9C;wBACD,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,sBAAsB,EAAE;qBACzD;oBACD,SAAS,EAAE,QAAQ;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC9E,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC;gBACV,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE;aAC1D,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC;YACV,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IAEO,YAAY;QAClB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO;QAChC,IAAI,CAAC,MAAM,CAAC;YACV,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE;gBACJ,EAAE,EAAE,UAAU,EAAE;gBAChB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC9C,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;aACzB;YACD,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QACH,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC;gBACV,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE;oBACJ,MAAM,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,EAAE;oBAC3C,MAAM,EAAE,SAAS;iBAClB;aACF,CAAC,CAAC;YACH,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;QACpC,CAAC;IACH,CAAC;IAED,gFAAgF;IACxE,yBAAyB;QAC/B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC;YAAE,OAAO;QACxG,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;IACtC,CAAC;IAEO,qBAAqB,CAAC,WAAW,GAAG,KAAK;QAC/C,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC;QAChC,IAAI,IAAI;YAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAClC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC;YACpC,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE;gBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE;oBACP,EAAE,EAAE,UAAU,EAAE;oBAChB,IAAI,EAAE,WAAW;oBACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;oBACjC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE;iBAC/C;gBACD,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9C;YACD,eAAe,EAAE,EAAE;YACnB,SAAS,EAAE,QAAQ;SACpB,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;IAClC,CAAC;IAEO,MAAM,CAAC,KAAyC;QACtD,MAAM,MAAM,GAAiB;YAC3B,GAAG,KAAK;YACR,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;SACjB,CAAC;QACF,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACd,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,OAAO;QACb,MAAM,MAAM,GAAqB;YAC/B,MAAM,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE;YAC1B,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;YACxB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;QACF,IAAI,IAAI,CAAC,iBAAiB;YAAE,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAC9E,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;AAC1D,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,KAA8B;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3G,MAAM,MAAM,GAAG,CAAC,GAAG,MAAiB,EAAU,EAAE;QAC9C,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;QAClG,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpG,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACxG,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAE,CAAC;AAChH,CAAC;AAED,SAAS,cAAc,CAAC,MAAe;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACvD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAA0D;IAChF,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAC/B,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;QACvD,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;QAC1E,KAAK,QAAQ;YACX,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IAC1F,CAAC;AACH,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Canonical trajectory store: per-run DSH-compatible persistence root
3
+ * (spec §5.2). One persistence root per run prevents collisions when two
4
+ * providers reuse a native session id while retaining the DSH
5
+ * project/session directory shape.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { createWriteStream } from "node:fs";
9
+ import { readFile, readdir, rm, stat } from "node:fs/promises";
10
+ import path from "node:path";
11
+ import { ensureDir, readJSON } from "../fs.js";
12
+ import { eventLine, headerLine, logPath, parseEventLine, parseHeaderLine } from "./format.js";
13
+ import { TRAJECTORY_FORMAT } from "./contract.js";
14
+ /**
15
+ * Append-only writer for one canonical trajectory. The first logical line is
16
+ * the immutable session header; every following line is one `SessionEvent`
17
+ * JSON object. `seq` starts at 0 and remains contiguous. Writes are
18
+ * serialized through a promise chain so failures surface on `close()`.
19
+ */
20
+ export class TrajectoryWriter {
21
+ target;
22
+ stream;
23
+ pending = Promise.resolve();
24
+ nextSeq = 0;
25
+ closed = false;
26
+ streamError;
27
+ constructor(target, stream) {
28
+ this.target = target;
29
+ this.stream = stream;
30
+ stream.on("error", (error) => { this.streamError ||= error; });
31
+ }
32
+ static async open(options) {
33
+ const target = trajectoryLogPath(options.runDirectory, options.cwd, options.sessionId);
34
+ await ensureDir(path.dirname(target));
35
+ const writer = new TrajectoryWriter(target, createWriteStream(target, { flags: "ax", mode: 0o600 }));
36
+ await writer.enqueue(headerLine(options.header));
37
+ return writer;
38
+ }
39
+ get path() {
40
+ return this.target;
41
+ }
42
+ /** Append one event; `seq` must continue the log. */
43
+ append(event) {
44
+ if (this.closed)
45
+ throw new Error("trajectory writer is closed");
46
+ if (event.seq !== this.nextSeq) {
47
+ throw new Error(`trajectory seq must be contiguous: expected ${this.nextSeq}, got ${event.seq}`);
48
+ }
49
+ this.nextSeq += 1;
50
+ this.enqueue(eventLine(event));
51
+ }
52
+ enqueue(line) {
53
+ const operation = this.pending.then(() => new Promise((resolve, reject) => {
54
+ this.stream.write(line, (error) => error ? reject(error) : resolve());
55
+ }));
56
+ this.pending = operation.catch(() => { });
57
+ return operation;
58
+ }
59
+ async flush() {
60
+ await this.pending;
61
+ }
62
+ /** Close the writer; resolves with the canonical trajectory path. */
63
+ async close() {
64
+ if (this.closed) {
65
+ await this.pending;
66
+ return this.target;
67
+ }
68
+ this.closed = true;
69
+ let failure;
70
+ try {
71
+ await this.pending;
72
+ }
73
+ catch (error) {
74
+ failure = error;
75
+ }
76
+ try {
77
+ await closeStream(this.stream);
78
+ }
79
+ catch (error) {
80
+ failure ||= error;
81
+ }
82
+ failure ||= this.streamError;
83
+ if (failure)
84
+ throw failure;
85
+ return this.target;
86
+ }
87
+ }
88
+ function closeStream(stream) {
89
+ return new Promise((resolve, reject) => {
90
+ stream.once("error", reject);
91
+ stream.end(resolve);
92
+ });
93
+ }
94
+ /** The `trajectory/--<normalized-cwd>--/<encoded-session-id>/session.jsonl` path under a run directory. */
95
+ export function trajectoryLogPath(runDirectory, cwd, sessionId) {
96
+ return logPath(path.join(runDirectory, "trajectory"), cwd, sessionId);
97
+ }
98
+ export function trajectoryRefPath(runDirectory) {
99
+ return path.join(runDirectory, "trajectory.ref.json");
100
+ }
101
+ export function trajectoryRef(runId, sessionId, fidelity, trajectoryPath, sha256, providerSessionId) {
102
+ const ref = {
103
+ schema_version: "1",
104
+ run_id: runId,
105
+ session_id: sessionId,
106
+ format: TRAJECTORY_FORMAT,
107
+ fidelity,
108
+ path: trajectoryPath,
109
+ sha256: sha256,
110
+ };
111
+ if (providerSessionId !== undefined)
112
+ ref.provider_session_id = providerSessionId;
113
+ return ref;
114
+ }
115
+ /**
116
+ * Read and validate a canonical trajectory. Validates header identity, the
117
+ * declared file set, contiguous sequence numbers, and relational invariants.
118
+ */
119
+ export async function readTrajectory(file) {
120
+ const content = await readFile(file, "utf8");
121
+ const sha256 = `sha256:${createHash("sha256").update(content).digest("hex")}`;
122
+ const lines = content.split(/\r?\n/);
123
+ const nonEmpty = lines.filter((line) => line.length > 0);
124
+ if (nonEmpty.length === 0)
125
+ throw new Error(`trajectory is empty: ${file}`);
126
+ const header = parseHeaderLine(JSON.parse(nonEmpty[0]));
127
+ const events = [];
128
+ for (let i = 1; i < nonEmpty.length; i += 1) {
129
+ const parsed = parseEventLine(JSON.parse(nonEmpty[i]));
130
+ if (parsed.seq !== i - 1) {
131
+ throw new Error(`trajectory seq must be contiguous: expected ${i - 1}, got ${parsed.seq}`);
132
+ }
133
+ events.push(parsed);
134
+ }
135
+ validateTrajectoryInvariants(header, events);
136
+ return { header, events, sha256 };
137
+ }
138
+ /** Validate the required relational invariants of a canonical trajectory (spec §5.4). */
139
+ export function validateTrajectoryInvariants(header, events) {
140
+ const knownTypes = new Set([
141
+ "turn/start", "turn/end", "step/start", "step/end",
142
+ "request/header", "user/message", "assistant/chunk", "assistant/message",
143
+ "tool/call", "tool/result",
144
+ ]);
145
+ let turnOpen = false;
146
+ let stepOpen = false;
147
+ let openTurn = null;
148
+ let openStep = null;
149
+ const openCalls = new Set();
150
+ let seq = 0;
151
+ for (const event of events) {
152
+ if (event.seq !== seq)
153
+ throw new Error(`trajectory seq must be contiguous: expected ${seq}, got ${event.seq}`);
154
+ seq += 1;
155
+ const data = (event.data || {});
156
+ switch (event.type) {
157
+ case "turn/start": {
158
+ if (turnOpen)
159
+ throw new Error(`nested turn/start at seq ${event.seq}`);
160
+ turnOpen = true;
161
+ openTurn = data.turn;
162
+ break;
163
+ }
164
+ case "turn/end": {
165
+ if (!turnOpen)
166
+ throw new Error(`turn/end without turn/start at seq ${event.seq}`);
167
+ if (stepOpen)
168
+ throw new Error(`turn/end with open step at seq ${event.seq}`);
169
+ turnOpen = false;
170
+ openTurn = null;
171
+ break;
172
+ }
173
+ case "step/start": {
174
+ if (!turnOpen)
175
+ throw new Error(`step/start outside a turn at seq ${event.seq}`);
176
+ if (stepOpen)
177
+ throw new Error(`nested step/start at seq ${event.seq}`);
178
+ stepOpen = true;
179
+ openStep = { turn: data.turn, step: data.step };
180
+ break;
181
+ }
182
+ case "step/end": {
183
+ if (!stepOpen)
184
+ throw new Error(`step/end without step/start at seq ${event.seq}`);
185
+ if (openCalls.size > 0)
186
+ throw new Error(`step/end with open tool calls at seq ${event.seq}`);
187
+ stepOpen = false;
188
+ openStep = null;
189
+ break;
190
+ }
191
+ case "tool/call": {
192
+ if (!stepOpen)
193
+ throw new Error(`tool/call outside a step at seq ${event.seq}`);
194
+ const callId = data.callId;
195
+ if (openCalls.has(callId))
196
+ throw new Error(`duplicate tool call ${callId} at seq ${event.seq}`);
197
+ openCalls.add(callId);
198
+ break;
199
+ }
200
+ case "tool/result": {
201
+ const message = (data.message || {});
202
+ const source = (message.source || {});
203
+ const content = Array.isArray(message.content) ? message.content : [];
204
+ const callId = (source.callId ?? content[0]?.toolCallId);
205
+ if (!callId || !openCalls.has(callId)) {
206
+ throw new Error(`tool/result without a matching open tool call at seq ${event.seq}`);
207
+ }
208
+ openCalls.delete(callId);
209
+ break;
210
+ }
211
+ default:
212
+ if (!knownTypes.has(event.type) && !event.ignorable) {
213
+ throw new Error(`unknown required event type ${event.type} at seq ${event.seq}`);
214
+ }
215
+ }
216
+ }
217
+ if (turnOpen)
218
+ throw new Error("trajectory ends with an open turn");
219
+ if (stepOpen)
220
+ throw new Error("trajectory ends with an open step");
221
+ if (openCalls.size > 0)
222
+ throw new Error("trajectory ends with open tool calls");
223
+ if (openTurn !== null)
224
+ throw new Error("trajectory ends with an open turn bracket");
225
+ if (openStep !== null)
226
+ throw new Error("trajectory ends with an open step bracket");
227
+ }
228
+ /** Locate the canonical trajectory for a run from its `trajectory.ref.json`. */
229
+ export async function loadTrajectoryRef(runDirectory) {
230
+ const ref = await readJSON(trajectoryRefPath(runDirectory), null);
231
+ if (!ref || ref.schema_version !== "1")
232
+ return null;
233
+ return ref;
234
+ }
235
+ export async function removeTrajectory(runDirectory) {
236
+ await rm(path.join(runDirectory, "trajectory"), { recursive: true, force: true });
237
+ }
238
+ /** Compute the SHA-256 digest of an existing trajectory file. */
239
+ export async function trajectoryFileSha256(file) {
240
+ const content = await readFile(file, "utf8");
241
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
242
+ }
243
+ export async function listTrajectorySessions(runDirectory) {
244
+ const root = path.join(runDirectory, "trajectory");
245
+ let projects;
246
+ try {
247
+ projects = await readdir(root, { withFileTypes: true });
248
+ }
249
+ catch (error) {
250
+ if (error?.code === "ENOENT")
251
+ return [];
252
+ throw error;
253
+ }
254
+ const sessions = [];
255
+ for (const project of projects) {
256
+ if (!project.isDirectory())
257
+ continue;
258
+ let entries;
259
+ try {
260
+ entries = await readdir(path.join(root, project.name), { withFileTypes: true });
261
+ }
262
+ catch {
263
+ continue;
264
+ }
265
+ for (const entry of entries) {
266
+ if (!entry.isDirectory())
267
+ continue;
268
+ const candidate = path.join(root, project.name, entry.name, "session.jsonl");
269
+ try {
270
+ await stat(candidate);
271
+ sessions.push(candidate);
272
+ }
273
+ catch {
274
+ // Not a session directory.
275
+ }
276
+ }
277
+ }
278
+ return sessions.sort();
279
+ }
280
+ export { TRAJECTORY_FORMAT };
281
+ //# sourceMappingURL=store.js.map