@namzu/sdk 20.3.0 → 21.0.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 (111) hide show
  1. package/CHANGELOG.md +321 -0
  2. package/dist/bridge/sse/mapper.d.ts +19 -0
  3. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  4. package/dist/bridge/sse/mapper.js +9 -1
  5. package/dist/bridge/sse/mapper.js.map +1 -1
  6. package/dist/contracts/schemas.d.ts +10 -10
  7. package/dist/manager/run/persistence.d.ts +26 -0
  8. package/dist/manager/run/persistence.d.ts.map +1 -1
  9. package/dist/manager/run/persistence.js +40 -0
  10. package/dist/manager/run/persistence.js.map +1 -1
  11. package/dist/public-runtime.d.ts +5 -0
  12. package/dist/public-runtime.d.ts.map +1 -1
  13. package/dist/public-runtime.js +21 -0
  14. package/dist/public-runtime.js.map +1 -1
  15. package/dist/run/drain.d.ts +194 -0
  16. package/dist/run/drain.d.ts.map +1 -0
  17. package/dist/run/drain.js +228 -0
  18. package/dist/run/drain.js.map +1 -0
  19. package/dist/run/index.d.ts +2 -0
  20. package/dist/run/index.d.ts.map +1 -1
  21. package/dist/run/index.js +1 -0
  22. package/dist/run/index.js.map +1 -1
  23. package/dist/runtime/query/checkpoint.d.ts +27 -1
  24. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  25. package/dist/runtime/query/checkpoint.js +34 -4
  26. package/dist/runtime/query/checkpoint.js.map +1 -1
  27. package/dist/runtime/query/events.d.ts +12 -0
  28. package/dist/runtime/query/events.d.ts.map +1 -1
  29. package/dist/runtime/query/events.js +61 -3
  30. package/dist/runtime/query/events.js.map +1 -1
  31. package/dist/runtime/query/index.d.ts +57 -1
  32. package/dist/runtime/query/index.d.ts.map +1 -1
  33. package/dist/runtime/query/index.js +36 -0
  34. package/dist/runtime/query/index.js.map +1 -1
  35. package/dist/runtime/query/resume-run.d.ts +34 -1
  36. package/dist/runtime/query/resume-run.d.ts.map +1 -1
  37. package/dist/runtime/query/resume-run.js +16 -3
  38. package/dist/runtime/query/resume-run.js.map +1 -1
  39. package/dist/store/index.d.ts +2 -2
  40. package/dist/store/index.d.ts.map +1 -1
  41. package/dist/store/index.js +2 -2
  42. package/dist/store/index.js.map +1 -1
  43. package/dist/store/run/checkpoint-disk.d.ts +18 -2
  44. package/dist/store/run/checkpoint-disk.d.ts.map +1 -1
  45. package/dist/store/run/checkpoint-disk.js +50 -5
  46. package/dist/store/run/checkpoint-disk.js.map +1 -1
  47. package/dist/store/run/checkpoint-memory.d.ts +22 -2
  48. package/dist/store/run/checkpoint-memory.d.ts.map +1 -1
  49. package/dist/store/run/checkpoint-memory.js +99 -4
  50. package/dist/store/run/checkpoint-memory.js.map +1 -1
  51. package/dist/store/run/claim-disk.d.ts +130 -0
  52. package/dist/store/run/claim-disk.d.ts.map +1 -0
  53. package/dist/store/run/claim-disk.js +550 -0
  54. package/dist/store/run/claim-disk.js.map +1 -0
  55. package/dist/store/run/conformance.d.ts +159 -0
  56. package/dist/store/run/conformance.d.ts.map +1 -0
  57. package/dist/store/run/conformance.js +451 -0
  58. package/dist/store/run/conformance.js.map +1 -0
  59. package/dist/store/run/disk.d.ts +30 -2
  60. package/dist/store/run/disk.d.ts.map +1 -1
  61. package/dist/store/run/disk.js +101 -0
  62. package/dist/store/run/disk.js.map +1 -1
  63. package/dist/store/run/listing.d.ts +44 -1
  64. package/dist/store/run/listing.d.ts.map +1 -1
  65. package/dist/store/run/listing.js +92 -1
  66. package/dist/store/run/listing.js.map +1 -1
  67. package/dist/store/run/memory.d.ts +5 -4
  68. package/dist/store/run/memory.d.ts.map +1 -1
  69. package/dist/store/run/memory.js +30 -1
  70. package/dist/store/run/memory.js.map +1 -1
  71. package/dist/types/doctor/check.d.ts +28 -2
  72. package/dist/types/doctor/check.d.ts.map +1 -1
  73. package/dist/types/run/checkpoint-store.d.ts +178 -2
  74. package/dist/types/run/checkpoint-store.d.ts.map +1 -1
  75. package/dist/types/run/event-cursor.d.ts +90 -0
  76. package/dist/types/run/event-cursor.d.ts.map +1 -0
  77. package/dist/types/run/event-cursor.js +47 -0
  78. package/dist/types/run/event-cursor.js.map +1 -0
  79. package/dist/types/run/events.d.ts +71 -1
  80. package/dist/types/run/events.d.ts.map +1 -1
  81. package/dist/types/run/events.js.map +1 -1
  82. package/dist/types/run/index.d.ts +1 -0
  83. package/dist/types/run/index.d.ts.map +1 -1
  84. package/dist/types/run/index.js +1 -0
  85. package/dist/types/run/index.js.map +1 -1
  86. package/dist/types/run/store.d.ts +41 -1
  87. package/dist/types/run/store.d.ts.map +1 -1
  88. package/package.json +6 -1
  89. package/src/bridge/sse/mapper.ts +28 -1
  90. package/src/manager/run/persistence.ts +43 -0
  91. package/src/public-runtime.ts +27 -0
  92. package/src/run/drain.ts +393 -0
  93. package/src/run/index.ts +3 -0
  94. package/src/runtime/query/checkpoint.ts +42 -5
  95. package/src/runtime/query/events.ts +68 -4
  96. package/src/runtime/query/index.ts +113 -1
  97. package/src/runtime/query/resume-run.ts +77 -12
  98. package/src/store/index.ts +5 -1
  99. package/src/store/run/checkpoint-disk.ts +70 -5
  100. package/src/store/run/checkpoint-memory.ts +118 -3
  101. package/src/store/run/claim-disk.ts +593 -0
  102. package/src/store/run/conformance.ts +705 -0
  103. package/src/store/run/disk.ts +106 -2
  104. package/src/store/run/listing.ts +116 -1
  105. package/src/store/run/memory.ts +35 -5
  106. package/src/types/doctor/check.ts +28 -2
  107. package/src/types/run/checkpoint-store.ts +189 -2
  108. package/src/types/run/event-cursor.ts +118 -0
  109. package/src/types/run/events.ts +80 -3
  110. package/src/types/run/index.ts +1 -0
  111. package/src/types/run/store.ts +43 -1
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Where a reconnecting consumer left off, and whether the kernel can honour it.
3
+ *
4
+ * The shortfall is a VALUE here, not a sentence in a document. A consumer that
5
+ * asks for everything after sequence 400 and silently receives a splice from a
6
+ * different generation of the run is wrong and has no way to find out; a
7
+ * consumer handed `{ status: 'unavailable', reason: 'cursor_ahead' }` re-derives
8
+ * from the transcript and is right. That is refuse-do-not-degrade applied to a
9
+ * subscription rather than to a capability.
10
+ */
11
+
12
+ import type { ClaimFence } from './checkpoint-store.js'
13
+ import type { PersistedRunEvent } from './events.js'
14
+
15
+ /** What a consumer holds when it comes back. */
16
+ export interface RunEventCursor {
17
+ /**
18
+ * The last `seq` the consumer actually received, from the envelope. Zero —
19
+ * or the field absent — means "from the beginning".
20
+ */
21
+ readonly sinceSeq: number
22
+ /**
23
+ * The `generation` carried on the events the consumer received, when they
24
+ * carried one.
25
+ *
26
+ * Compared for equality only. Absent on both sides is not a match and not a
27
+ * mismatch: it is an unfenced run, where the sequence is the only evidence
28
+ * available and the log persisting is the assumption.
29
+ */
30
+ readonly generation?: ClaimFence
31
+ }
32
+
33
+ /** Why a cursor could not be honoured. */
34
+ export type RunEventReplayRefusal =
35
+ /**
36
+ * The consumer claims to have seen more than the log holds.
37
+ *
38
+ * The ordinary cause is not a lying client: it is a log that did not
39
+ * survive. An in-memory run store on a restarted process seeds at zero
40
+ * while the consumer still holds 400, and this is the verdict that says so
41
+ * instead of reporting a serene "you are up to date".
42
+ */
43
+ | 'cursor_ahead'
44
+ /**
45
+ * The run has been taken over since. The consumer's sequence numbers were
46
+ * minted under an older claim and address a different sequence space, so
47
+ * they are not comparable — not merely stale.
48
+ */
49
+ | 'generation_changed'
50
+ /**
51
+ * The store answered, and its oldest available event is above the one after
52
+ * the cursor. A pruning or windowed backend, caught at the boundary rather
53
+ * than delivered as a continuous stream with a hole in it.
54
+ */
55
+ | 'gap'
56
+
57
+ /** What came of a cursor. */
58
+ export type RunEventReplay =
59
+ /** The cursor is already at the log's head. Nothing was missed. */
60
+ | { readonly status: 'complete' }
61
+ /** Contiguous from `sinceSeq + 1`, oldest first. */
62
+ | { readonly status: 'replayed'; readonly events: readonly PersistedRunEvent[] }
63
+ /** Nothing is delivered. The consumer re-derives from the transcript. */
64
+ | { readonly status: 'unavailable'; readonly reason: RunEventReplayRefusal }
65
+
66
+ /** The log's present state, as the store reports it. */
67
+ export interface RunEventLogHead {
68
+ /** Highest sequence in the log; zero when it is empty. */
69
+ readonly lastSeq: number
70
+ /** The fence the run is being written under, when it holds a claim. */
71
+ readonly generation?: ClaimFence
72
+ }
73
+
74
+ /**
75
+ * Decide what a cursor is owed, given what the log holds.
76
+ *
77
+ * Pure, and ordered deliberately: a takeover invalidates the sequence space, so
78
+ * comparing sequences across generations would be arithmetic on two different
79
+ * scales. The generation check therefore runs FIRST, before the numbers are
80
+ * allowed to mean anything.
81
+ *
82
+ * `events` is what the store returned for `{ sinceSeq: cursor.sinceSeq }`. It is
83
+ * passed in rather than fetched here so this stays a function of its arguments
84
+ * — the property "no gap, no duplicate" is decidable from the three inputs, and
85
+ * a test can drive every branch without a store.
86
+ */
87
+ export function resolveRunEventReplay(
88
+ cursor: RunEventCursor,
89
+ head: RunEventLogHead,
90
+ events: readonly PersistedRunEvent[],
91
+ ): RunEventReplay {
92
+ // Both sides must carry one for a mismatch to mean anything. An unfenced run
93
+ // has no generation to disagree with, and treating "absent" as a distinct
94
+ // value would refuse every run that never took a claim — which is most of
95
+ // them.
96
+ if (
97
+ cursor.generation !== undefined &&
98
+ head.generation !== undefined &&
99
+ cursor.generation !== head.generation
100
+ ) {
101
+ return { status: 'unavailable', reason: 'generation_changed' }
102
+ }
103
+
104
+ if (cursor.sinceSeq > head.lastSeq) {
105
+ return { status: 'unavailable', reason: 'cursor_ahead' }
106
+ }
107
+
108
+ if (cursor.sinceSeq === head.lastSeq) return { status: 'complete' }
109
+
110
+ // The log says there is something above the cursor, so an empty answer is
111
+ // the store contradicting its own head rather than "nothing to send".
112
+ const first = events[0]
113
+ if (!first || first.seq !== cursor.sinceSeq + 1) {
114
+ return { status: 'unavailable', reason: 'gap' }
115
+ }
116
+
117
+ return { status: 'replayed', events }
118
+ }
@@ -15,6 +15,7 @@ import type {
15
15
  import type { PlanStep } from '../plan/index.js'
16
16
  import type { PluginHookEvent, PluginHookResult } from '../plugin/index.js'
17
17
  import type { TaskStatus } from '../task/index.js'
18
+ import type { ClaimFence } from './checkpoint-store.js'
18
19
  import type { Lineage } from './lineage.js'
19
20
  import type { MessageStopReason, StopReason } from './stop-reason.js'
20
21
  import type {
@@ -43,6 +44,57 @@ interface RunEventEnvelope {
43
44
  */
44
45
  schemaVersion?: 3
45
46
  lineage?: Lineage
47
+ /**
48
+ * This event's position in its OWN run's durable event log, from 1.
49
+ *
50
+ * **Present means recorded.** The emitter takes a candidate number, appends
51
+ * the event stamped with it, and only then advances the counter and hands
52
+ * the event to the live stream — so a `seq` a consumer can see is a `seq`
53
+ * the log contains. That is what makes it usable as a cursor: reconnect,
54
+ * ask for everything above it, and nothing is missed and nothing arrives
55
+ * twice.
56
+ *
57
+ * **Absent means not recoverable**, and three different things arrive that
58
+ * way:
59
+ *
60
+ * - the high-frequency events {@link isEphemeralEvent} names, which are
61
+ * deliberately never persisted;
62
+ * - an event whose durable write FAILED — it still reaches the live
63
+ * stream, unstamped, because losing the news of a failure is worse than
64
+ * delivering it without a cursor;
65
+ * - the delegation lifecycle events (`agent_pending`, `agent_completed`,
66
+ * `agent_failed`, `agent_canceled` and the three sub-session variants),
67
+ * which the agent manager hands straight to a host's listener without
68
+ * passing through the run's event translator at all. They are not in any
69
+ * run's log, and the missing `seq` is how a consumer learns that rather
70
+ * than discovering it after a reconnect.
71
+ *
72
+ * **Per run, not per stream.** A parent's listener also receives its
73
+ * children's events, each numbered in its own run's log, so a consumer
74
+ * keeps one cursor per `runId`. The SSE mapper carries the pair as
75
+ * `<runId>:<seq>` for exactly this reason.
76
+ *
77
+ * Monotonic under a single writer. The run store takes no claim fence
78
+ * today (see `QueryParams.claimFence`), so two workers that both took one
79
+ * run can both append — `generation` is what makes that detectable.
80
+ */
81
+ seq?: number
82
+ /**
83
+ * The claim fence the run was being written under, when it holds a claim.
84
+ *
85
+ * A sequence alone lies across a takeover: a client at seq 400 reconnects
86
+ * to a run whose store lost its log, the new writer starts at 1, and the
87
+ * client's cursor silently addresses a different sequence space. The fence
88
+ * is already the arbiter of who may write and it only increases, so
89
+ * carrying it here makes a takeover ORDERED rather than merely
90
+ * distinguishable.
91
+ *
92
+ * Absent when the run was written unfenced, which is every run that took no
93
+ * claim. A cursor then relies on the log persisting — the disk store's
94
+ * does, an in-memory store's does not, and `cursor_ahead` is the verdict
95
+ * that catches the difference.
96
+ */
97
+ generation?: ClaimFence
46
98
  }
47
99
 
48
100
  type CoreRunEvent =
@@ -679,12 +731,37 @@ type CoreRunEvent =
679
731
  */
680
732
  export type RunEvent =
681
733
  | (CoreRunEvent & RunEventEnvelope)
682
- | SubsessionSpawnedEvent
683
- | SubsessionMessagedEvent
684
- | SubsessionIdledEvent
734
+ // Intersected so `seq` is READABLE on every member of the union — TypeScript
735
+ // refuses a property access on a union where one member lacks the field, and
736
+ // a consumer holding a cursor has to be able to ask any event for its
737
+ // number. These three never carry one, and that is the answer rather than an
738
+ // omission: they are emitted straight to a host's listener and reach no run's
739
+ // durable log.
740
+ | (SubsessionSpawnedEvent & RunEventEnvelope)
741
+ | (SubsessionMessagedEvent & RunEventEnvelope)
742
+ | (SubsessionIdledEvent & RunEventEnvelope)
685
743
 
686
744
  export type RunEventListener = (event: RunEvent) => void | Promise<void>
687
745
 
746
+ /**
747
+ * A run event as the durable log gives it back.
748
+ *
749
+ * `seq` and `timestamp` are REQUIRED here and optional on the live envelope,
750
+ * which is the whole difference between the two types: a recorded event has a
751
+ * position and a moment, and a live one may be neither recorded nor
752
+ * recoverable.
753
+ *
754
+ * `timestamp` is not new — both store implementations have always stamped it
755
+ * on write. It was simply never declared anywhere, so the field was persisted
756
+ * by two writers, typed by neither, and read by nobody. Declaring it is what
757
+ * lets the read-back stop casting.
758
+ */
759
+ export type PersistedRunEvent = RunEvent & {
760
+ readonly seq: number
761
+ /** Epoch ms at which the store recorded the event. */
762
+ readonly timestamp: number
763
+ }
764
+
688
765
  /**
689
766
  * Event types whose volume makes durable persistence wasteful.
690
767
  *
@@ -4,6 +4,7 @@ export * from './stop-reason.js'
4
4
  export * from './config.js'
5
5
  export * from './checkpoint-store.js'
6
6
  export * from './store.js'
7
+ export * from './event-cursor.js'
7
8
  export * from './entity.js'
8
9
  export * from './replay.js'
9
10
  // Domain `RunStatus` (session-hierarchy.md §4.6 state machine). Safe to
@@ -28,7 +28,19 @@
28
28
  */
29
29
 
30
30
  import type { Run } from './entity.js'
31
- import type { RunEvent } from './events.js'
31
+ import type { PersistedRunEvent, RunEvent } from './events.js'
32
+
33
+ /** What a caller asks the log for. See {@link RunStore.readEvents}. */
34
+ export interface ReadRunEventsOptions {
35
+ /**
36
+ * Return only events ABOVE this sequence — strictly greater, never equal.
37
+ *
38
+ * The exclusive boundary is what makes a cursor round-trip: a consumer that
39
+ * last saw `seq: 12` passes 12 and receives 13 onward, so nothing is
40
+ * delivered twice. Absent means the whole log.
41
+ */
42
+ readonly sinceSeq?: number
43
+ }
32
44
 
33
45
  /**
34
46
  * One finished tool call, recovered from the run's own transcript.
@@ -71,6 +83,36 @@ export interface RunStore {
71
83
  */
72
84
  appendEvent(event: RunEvent): Promise<void>
73
85
 
86
+ /**
87
+ * Read the run's durable event log back, oldest first.
88
+ *
89
+ * Required, unlike {@link RunStore.addToIndex}, and the asymmetry is the
90
+ * point: a store that records a transcript it cannot read back is
91
+ * write-only evidence, which is the defect the whole contract exists to
92
+ * fix one level up. It is also what a reconnecting consumer catches up
93
+ * through — "refresh the page and keep watching the answer arrive" is this
94
+ * method plus a cursor and nothing else.
95
+ *
96
+ * Three obligations, each of which a consumer relies on:
97
+ *
98
+ * 1. **Ascending by `seq`, in the order the events were appended.** Do not
99
+ * sort a log back into order — a log that needs sorting was written by
100
+ * two processes, and hiding that produces a plausible transcript of a
101
+ * run that never happened.
102
+ * 2. **`sinceSeq` is exclusive.** See {@link ReadRunEventsOptions}.
103
+ * 3. **Contiguous, or honestly short.** A backend that prunes may return a
104
+ * first event above `sinceSeq + 1`; that is a gap, the caller detects
105
+ * it, and the reconnect is refused rather than spliced. Do NOT
106
+ * manufacture placeholders to close it.
107
+ *
108
+ * High-frequency events never enter the log (see
109
+ * {@link RunStore.appendEvent}), so what a late subscriber recovers is
110
+ * message-granular, not keystroke-granular. Aggregated assistant text,
111
+ * every tool result and the full message list are all intact; the deltas
112
+ * that composed them are not, and are not meant to be.
113
+ */
114
+ readEvents(options?: ReadRunEventsOptions): Promise<readonly PersistedRunEvent[]>
115
+
74
116
  /**
75
117
  * Persist the run's final report. Returns a location, or `null` when the
76
118
  * backend has none. See {@link RunStore.initRun}.