@graphorin/core 0.6.1 → 0.7.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.
- package/CHANGELOG.md +82 -0
- package/README.md +8 -5
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2 -2
- package/dist/channels/pause.d.ts +47 -2
- package/dist/channels/pause.d.ts.map +1 -1
- package/dist/channels/pause.js +62 -2
- package/dist/channels/pause.js.map +1 -1
- package/dist/contracts/checkpoint-store.d.ts +97 -1
- package/dist/contracts/checkpoint-store.d.ts.map +1 -1
- package/dist/contracts/checkpoint-store.js.map +1 -1
- package/dist/contracts/index.d.ts +5 -5
- package/dist/contracts/index.js +2 -1
- package/dist/contracts/memory-store.d.ts +59 -2
- package/dist/contracts/memory-store.d.ts.map +1 -1
- package/dist/contracts/provider.d.ts +20 -6
- package/dist/contracts/provider.d.ts.map +1 -1
- package/dist/contracts/session-store.d.ts +10 -2
- package/dist/contracts/session-store.d.ts.map +1 -1
- package/dist/contracts/tool.d.ts +75 -1
- package/dist/contracts/tool.d.ts.map +1 -1
- package/dist/contracts/tool.js +57 -0
- package/dist/contracts/tool.js.map +1 -0
- package/dist/contracts/tracer.d.ts +53 -5
- package/dist/contracts/tracer.d.ts.map +1 -1
- package/dist/contracts/tracer.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/types/agent-event-wire.d.ts +74 -0
- package/dist/types/agent-event-wire.d.ts.map +1 -0
- package/dist/types/agent-event-wire.js +130 -0
- package/dist/types/agent-event-wire.js.map +1 -0
- package/dist/types/agent-event.d.ts +48 -6
- package/dist/types/agent-event.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +2 -1
- package/dist/types/memory.d.ts +11 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/run.d.ts +86 -4
- package/dist/types/run.d.ts.map +1 -1
- package/dist/types/run.js.map +1 -1
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/usage.d.ts +11 -1
- package/dist/types/usage.d.ts.map +1 -1
- package/dist/types/usage.js.map +1 -1
- package/dist/utils/binary-json.d.ts +165 -0
- package/dist/utils/binary-json.d.ts.map +1 -0
- package/dist/utils/binary-json.js +240 -0
- package/dist/utils/binary-json.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/validation.d.ts +10 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/package.json +9 -7
- package/src/channels/channels.ts +206 -0
- package/src/channels/directive.ts +41 -0
- package/src/channels/dispatch.ts +37 -0
- package/src/channels/durable.ts +151 -0
- package/src/channels/index.ts +62 -0
- package/src/channels/pause.ts +216 -0
- package/src/contracts/auth-token-store.ts +42 -0
- package/src/contracts/checkpoint-store.ts +256 -0
- package/src/contracts/embedder.ts +42 -0
- package/src/contracts/eval-scorer.ts +44 -0
- package/src/contracts/index.ts +112 -0
- package/src/contracts/local-provider-trust.ts +33 -0
- package/src/contracts/logger.ts +61 -0
- package/src/contracts/memory-store.ts +187 -0
- package/src/contracts/oauth-server-store.ts +78 -0
- package/src/contracts/preferred-model.ts +56 -0
- package/src/contracts/provider.ts +316 -0
- package/src/contracts/reasoning-retention.ts +52 -0
- package/src/contracts/redaction-validator.ts +56 -0
- package/src/contracts/sandbox.ts +70 -0
- package/src/contracts/secret-ref.ts +22 -0
- package/src/contracts/secret-value.ts +117 -0
- package/src/contracts/secrets-store.ts +90 -0
- package/src/contracts/session-store.ts +163 -0
- package/src/contracts/token-counter.ts +23 -0
- package/src/contracts/tool.ts +397 -0
- package/src/contracts/tracer.ts +219 -0
- package/src/contracts/trigger-store.ts +40 -0
- package/src/index.ts +23 -0
- package/src/types/agent-event-wire.ts +193 -0
- package/src/types/agent-event.ts +579 -0
- package/src/types/handoff.ts +111 -0
- package/src/types/index.ts +148 -0
- package/src/types/memory.ts +427 -0
- package/src/types/message.ts +174 -0
- package/src/types/run.ts +312 -0
- package/src/types/sensitivity.ts +35 -0
- package/src/types/session-scope.ts +18 -0
- package/src/types/stop-condition.ts +108 -0
- package/src/types/tool-call.ts +24 -0
- package/src/types/tool.ts +324 -0
- package/src/types/usage.ts +120 -0
- package/src/types/workflow-event.ts +132 -0
- package/src/utils/assert-never.ts +24 -0
- package/src/utils/async-context.ts +55 -0
- package/src/utils/binary-json.ts +425 -0
- package/src/utils/hash.ts +122 -0
- package/src/utils/index.ts +57 -0
- package/src/utils/streams.ts +233 -0
- package/src/utils/validation.ts +82 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# @graphorin/core
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-100: `AnyTool` existential type; `createAgent({ tools })` accepts concretely-typed tools without casts.
|
|
8
|
+
|
|
9
|
+
`Tool` is invariant in `TInput` (the `needsApproval`/`idempotencyKey` predicate properties are contravariant), so a typed `Tool<{q: string}, number, D>` was never assignable to `Tool<unknown, unknown, D>` - forcing `as unknown as Tool<...>` at every collection seam. `@graphorin/core` now exports `AnyTool<TDeps> = Tool<any, any, TDeps>` (existential input/output, following the `HandoffEntry` precedent), and both `AgentConfig.tools` and `PrepareStepOverrides.tools` in `@graphorin/agent` accept `ReadonlyArray<AnyTool<TDeps>>`. Widening only - existing code keeps compiling. `ToolRegistry.register` was already per-call generic and needed no change.
|
|
10
|
+
|
|
11
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-005: HITL/workflow checkpoints are linked to their session and erased by the session hard-delete cascade.
|
|
12
|
+
|
|
13
|
+
HITL suspends persist the FULL serialized conversation (`RunState`) into `workflow_checkpoints`; previously nothing connected those rows to a session, so `DELETE /v1/sessions/:id` left the entire transcript recoverable forever. Now: `CheckpointMetadata` gains an optional `sessionId` (additive); migration 029 adds a `session_id` column + index to `workflow_checkpoints` and backfills historical `namespace='agent'` rows from the state blob; the agent runtime stamps `sessionId` on all three suspend write sites (step suspend, resume write-ahead intent, post-dispatch journal); and `deleteSession`/`pruneSessions` collect thread ids from BOTH `session_workflow_runs` and the new column, erasing `workflow_checkpoints` + `workflow_pending_writes` before dropping the mapping. Deleting a session now removes its suspended-run snapshots - time-travel/forensics for a deleted session is intentionally gone (that is what hard-delete means).
|
|
14
|
+
|
|
15
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-009: checkpoint GC primitives - `CheckpointStoreExt` with `pruneThreads` and `compactThread`.
|
|
16
|
+
|
|
17
|
+
The engine writes a full state snapshot per execution step and nothing ever deleted them (`deleteThread` had zero production callers). `@graphorin/core` adds the additive `CheckpointStoreExt` contract: `pruneThreads({beforeEpochMs, onlyTerminal})` - a namespace-SCOPED retention sweep whose policy reads each pair's LATEST checkpoint (suspended threads with live HITL approvals/awakeables survive by default) - and `compactThread(threadId, namespace, keepLast)` for in-place history compaction (resume reads the latest tuple, so `keepLast >= 1` never breaks resumability). Implemented by `SqliteCheckpointStore` (per-pair transactions, never via the namespace-blind `deleteThread` - a reused threadId across workflows must not lose another workflow's suspended state) and by `InMemoryCheckpointStore`. `GraphorinSqliteStore.checkpoints` is now typed `CheckpointStoreExt`. `documentation/guide/workflow-engine.md` gains a "Retention and cleanup" section with the growth arithmetic and the deleteThread caveat.
|
|
18
|
+
|
|
19
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.
|
|
20
|
+
|
|
21
|
+
CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).
|
|
22
|
+
|
|
23
|
+
- [#153](https://github.com/o-stepper/graphorin/pull/153) [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534) Thanks [@o-stepper](https://github.com/o-stepper)! - Reachable retention lever for `memory_history` (W-066):
|
|
24
|
+
|
|
25
|
+
- `@graphorin/core`: new `MemoryStoreExt` contract (`extends MemoryStore` with `pruneHistory(olderThanMs)`), mirroring the `SessionStoreExt` precedent - strictly additive, custom `MemoryStore` implementations keep compiling. The TSDoc pins the unit semantics: the argument is an AGE in milliseconds, never an epoch cutoff.
|
|
26
|
+
- `@graphorin/store-sqlite`: `SqliteMemoryStore` declares `implements MemoryStoreExt` and `GraphorinSqliteStore.memory` is now typed `MemoryStoreExt`, so `pruneHistory` is reachable without casts.
|
|
27
|
+
- `@graphorin/cli`: new `graphorin memory prune-history --older-than <duration|date>` command. `--older-than` is mandatory (destructive by design, no default), takes a duration (`30d`, `12h`) or a PAST ISO date (converted to `now - date`; future dates are refused - they would prune the whole table). Documented in the CLI guide and the memory-system guide: history grows by design, `purge()` already scrubs sensitive text, pruning is storage-cost hygiene.
|
|
28
|
+
|
|
29
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-047: `RunContext.state` is now typed as the new `ReadonlyRunState` projection.
|
|
30
|
+
|
|
31
|
+
Tools and hooks observe the run; they do not mutate its bookkeeping - assignments to `status`/`finishedAt` and `push`/`splice` on `steps`/`messages`/`pendingApprovals` through `ctx.state` are now compile errors. `ReadonlyRunState` is a hand-written structural mirror of `RunState` (keyof parity pinned by type tests); `RunState` remains assignable to it, so runtime call sites needed no changes. This is a compile-time contract only (no runtime freeze). BREAKING at the type level for tools that wrote to `ctx.runContext.state` - that was never supported. Companion cleanup in `@graphorin/agent`: `finishRunBase`/`finalize` take `MutableRunState & RunState`, removing the last `as unknown as RunState` cast in the runtime.
|
|
32
|
+
|
|
33
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-024: thinking-block signatures now actually round-trip - the whole retention pipeline was dead because nothing captured them.
|
|
34
|
+
|
|
35
|
+
`ProviderEvent` gains a `{type: 'reasoning-end', meta?: ReasoningContentMeta}` terminator (per-block, matching both AI SDK generations). The vercel adapter maps v4 `reasoning-signature`/`redacted-reasoning` chunks and v7 `reasoning-end` (`providerMetadata.anthropic.signature`/`.redactedData`) onto it; `reasoning-start` stays a no-op. The agent runtime flushes buffered deltas into per-block `ReasoningContent` parts carrying the meta (redacted blocks become meta-only parts), and the step assembles those parts instead of one meta-less collapse - adapters without block structure keep the collapsed fallback. Downstream, the already-shipped chain finally engages: `applyReasoningPolicy('pass-through-claude')` retains the signed parts and `toAssistantPart` emits `providerOptions.anthropic.signature`, so multi-step tool use with Anthropic extended thinking replays each block byte-equal (pinned end-to-end: the step-2 request carries both signatures of a two-block step-1). Known scope limit: the one-shot `generate()` path still returns no reasoning (`ProviderResponse` has no field for it). MIGRATION: external exhaustive switches over `ProviderEvent` need a case for `'reasoning-end'`; transcripts may now carry several reasoning parts per step instead of one.
|
|
36
|
+
|
|
37
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-126: `SpanType` opens up via a namespaced escape: `SpanType = KnownSpanType | CustomSpanType` where `CustomSpanType` is any `` `x.${string}` `` (convention `x.<vendor>.<operation>`). Custom tracers can start spans for operations the framework has no literal for (rerankers, eval steps, ...) without a core release, while typos of known literals stay compile errors (they do not start with `x.`). MIGRATION: external exhaustive switches over `SpanType` need a default branch for the custom domain; span-type analytics must tolerate unknown strings (the policy events already follow). The previous closed union remains exported as `KnownSpanType`.
|
|
38
|
+
|
|
39
|
+
- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable HITL composes across the sub-agent boundary (W-001). A handoff target or `toTool` child that suspends on an approval-gated tool now PARKS on the parent (`RunState.pendingSubRuns`, new core type `PendingSubRun`) instead of surfacing a terminal `execution_failed`/thrown error: the child's pending approvals mirror onto the parent's `pendingApprovals` with the new `ToolApproval.subRunToolCallId` routing field, and the parent suspends once per step. Operators echo (`toolCallId`, `subRunToolCallId`) back in `ApprovalDecision` - decisions match on the composite key, so colliding child-local ids never cross-apply; nested parks route recursively via a `/`-separated path. On grant the child's side effect executes exactly once, its shaped output becomes the parent's tool message, and its usage folds into the parent. `toTool` tools are executed INLINE by the tool-call walk (marked with the well-known `SUBAGENT_TOOL` symbol) rather than through the executor; foreign harnesses mounting them outside the graphorin loop keep the plain throw. `serializeRunState`/`deserializeRunState` recurse into parked children (each nested snapshot is version-stamped and secret-redacted; the core wire codec projects them recursively). Consumers relying on the old terminal-error behavior for suspending children will observe a suspension instead; failed/aborted children still surface as tool errors.
|
|
40
|
+
|
|
41
|
+
- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Durable timers now fire without user polling code (W-032). The engine stamps `CheckpointMetadata.wakeAt` (earliest due frontier timer) on suspended checkpoints; `CheckpointStore` gains the optional `listSuspended(namespace, { dueBefore, limit })` enumeration (implemented by the SQLite adapter - migration 032 adds `workflow_checkpoints.wake_at` with a partial index - and by `InMemoryCheckpointStore`); the new `createTimerDriver({ workflows: [{ workflow, checkpointStore }] })` polls due threads and calls `workflow.tick`, re-arming at `min(pollIntervalMs, earliest nextWakeAt)`, with per-thread error isolation and benign handling of cross-process `checkpoint-version-conflict` races. On the server, `createServer({ workflowTimers: { driver } })` binds a lifecycle daemon and reports `checks.workflowTimers` on `/v1/health`. A custom store without `listSuspended` fails fast with `TimerDriverStoreUnsupportedError`. Threads suspended before migration 032 carry no `wake_at` and stay invisible to the driver until one manual `tick` or resume re-persists them.
|
|
42
|
+
|
|
43
|
+
- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Sub-agent transparency (W-036). New additive `AgentEvent` member `subagent.event` wraps a child's event (with the parent-side toolCallId and the child agent name) and forwards it into the parent stream per the `forwardEvents` policy on handoff entries and `AgentToToolOptions`: `'lifecycle'` (default) forwards tool execution/approval, guardrail, lateral-leak, compaction and error events - never text deltas; `'all'` forwards everything; `'none'` keeps the child a black box. Multi-agent runs now form ONE trace tree: `AgentCallOptions.parentSpan` (not persisted in RunState) parents the run span, and the runtime supplies it automatically for handoffs and `toTool` children from the live step span. The wire codec projects the wrapped event recursively. TypeScript consumers with exhaustive switches over `AgentEvent` must add the new case.
|
|
44
|
+
|
|
45
|
+
- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-049: `tool.execute.start` / `tool.execute.end` / `tool.execute.error` events now carry an optional `toolName` (the agent runtime always fills it, on every emit path: batch dispatch, handoff, inline sub-agent, approval pre-screen and resumed dispatch). Direct stream subscribers can render the tool name from any lifecycle event without a stateful join back to `tool.call.start`. The union TSDoc now states the correlation policy explicitly: cross-run attribution belongs to the server envelope (`subject`), in-lifecycle correlation is by `toolCallId`, and `runId` is deliberately NOT retrofitted onto every variant. Additive and wire-compatible (the wire projection spreads unknown fields through); old consumers keep working.
|
|
46
|
+
|
|
47
|
+
- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-094: span EVENT attributes can finally carry a sensitivity tier. `AISpan.addEvent` gains an optional third parameter (`{ sensitivity, sensitivityByAttribute }`, additive - existing implementations keep compiling), the tracer records it onto `SpanRecordEvent.sensitivityByAttribute`, and the validation exporter honours it instead of passing an empty map (which dropped EVERY event attribute under the default `'public'` floor). Out of the box, `recordException` now exports a `exception` event with a non-empty `exception.type` (the class name - safe and load-bearing for error dashboards) while `exception.message`/`exception.stacktrace` stay `'internal'`; `emitGenAIMessageEvents` marks role / `gen_ai.system` / message name / tool-call id `'public'` and keeps content `'internal'`. Untagged event attributes keep the default-deny behaviour, and `onViolation` now distinguishes event drops from span-attribute drops via `origin: 'event:<name>'`.
|
|
48
|
+
|
|
49
|
+
- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - The ToolReturn envelope gets a symbol brand (W-115). New core exports: `TOOL_RETURN_BRAND` (`Symbol.for`, duplicate-copy safe), the `toolReturn()` factory, and the ONE shared guard `isToolReturnEnvelope` consumed by both the executor's unwrap and the registry's example-normalizer (the duplicated sniff is gone). The structural fallback for unbranded objects is deliberately narrow - own keys within `{output, contentParts, taint}` - so a tool legitimately returning `{output, exitCode, stderr}` now reaches the model whole instead of being silently stripped to `.output`; canonical unbranded literals keep unwrapping and increment `tool.result.envelope.unbranded-toolreturn.total` toward the sniff's future deprecation. First-party producers (MCP adaptCallResult, memory recall tools, toTool taint envelopes) now brand via `toolReturn()`. Downstream consumers relying on extra fields being dropped will now see them; plain data of exactly `{output: X}` remains ambiguous by contract - brand it or rename the field.
|
|
50
|
+
|
|
51
|
+
- [#158](https://github.com/o-stepper/graphorin/pull/158) [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab) Thanks [@o-stepper](https://github.com/o-stepper)! - Positional pause replay now detects divergence (W-120). Every satisfied resume value is journaled together with the identity of the pause it answered (`PendingPauseRecord.satisfiedMeta`: durable-primitive kind + awakeable/approval name), and `pause()` verifies the identity at each cursor during replay - a node whose pause ORDER depends on time/state/model output fails loudly with the new typed `pause-replay-divergence` WorkflowError (naming the node and the expected vs actual pause) instead of silently delivering a value to the wrong wait. Conservative by design: two plain `pause()` calls carry no identity and are never flagged (false positives impossible), and checkpoints written before the field existed replay their old values unchecked. Consumers with exhaustive switches over `WorkflowErrorCode` must add the new member; workflows that previously "worked by accident" with crossed values will now fail - that is the finding.
|
|
52
|
+
|
|
53
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Store mutation paths gain scope-guarded variants, symmetric with the read-side isolation (W-154): every read binds `scope_user_id`, but `forget`, `setStatus` (facts/episodes/rules/insights), `archive`, `archiveFact`, `purge`, and `markAccessed` operated on the bare id - code holding a leaked or cross-user id could quarantine, archive, or hard-purge another user's memory. All mutators now accept an optional trailing `scope?: SessionScope` (additive; existing adapter implementations stay structurally compatible): when supplied, a non-owned row is a deterministic silent no-op - a scoped `purge` of a foreign id writes nothing at all, not even the PURGE audit row. The `@graphorin/memory` tiers pass their scope through on `validate`/`forget`/`purge`/`archive` and the recall `markAccessed` path; the consolidator and erasure cascades deliberately keep calling unscoped.
|
|
54
|
+
|
|
55
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-046: JSON-safe `WireAgentEvent` projection for all binary-bearing event variants, applied on the server WS path.
|
|
56
|
+
|
|
57
|
+
`@graphorin/core` gains `WireFileGeneratedEvent`, `WireToolExecutePartialEvent`, `WireAgentEndEvent` (whose `result.state` is the `WireRunState` projection) plus the `WireAgentEvent` union and pure `toWireAgentEvent`/`fromWireAgentEvent` codecs. The `AgentEvent` TSDoc now documents the real two-layer wire contract (envelope `{eventId, subject, type, payload}` with `payload = WireAgentEvent`) instead of the false claim that `@graphorin/protocol` re-exports the union; protocol stays zod-only with a doc pointer. The server's `backgroundStreamAgent` projects every streamed event before emitting, so `file.generated`, binary `tool.execute.partial` chunks and a multimodal `agent.end` state arrive at WS clients decodable instead of as numeric-key mush. An exhaustive `Record<AgentEvent['type'], ...>` fixture gate in core forces a wire decision for every future event variant.
|
|
58
|
+
|
|
59
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-004: JSON-safe binary codec for message content and a `WireRunState` projection; run-state schema 1.2.
|
|
60
|
+
|
|
61
|
+
`@graphorin/core` gains `EncodedBytes`/`EncodedUrl` envelopes, `WireMessage`/`WireMessageContent`/`WireRunState` wire twins and pure `toJsonSafeMessage`/`fromJsonSafeMessage`, `toJsonSafeContentParts`/`fromJsonSafeContentParts`, `toJsonSafeRunState`/`fromJsonSafeRunState` codecs (plus `bytesToBase64`/`base64ToBytes`, Buffer-free). `serializeRunState` in `@graphorin/agent` now projects binary payloads (`Uint8Array | URL` in `messages` and tool-outcome `contentParts`) through the codec before its detach stringify, so a run with an image checkpointed at `awaiting_approval` and resumed no longer hands the provider corrupted bytes. `RUN_STATE_SCHEMA_VERSION` is now `graphorin-run-state/1.2`; 1.0/1.1 payloads remain readable and their stringify-corrupted numeric-key byte objects are repaired best-effort on rehydration. `SerializedRunState.messages`/`.steps` are now typed as `WireMessage[]`/`WireRunStep[]` - the on-disk truth.
|
|
62
|
+
|
|
63
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-013: the declared `zod ^3.23 || ^4` peer range now actually typechecks under zod@4.
|
|
64
|
+
|
|
65
|
+
Two classes of breakage fixed: (a) `ZodLikeError.issues[].path` is now `ReadonlyArray<PropertyKey>` - zod 4 bases `$ZodIssue.path` on `PropertyKey`, and the shim must be a superset of both peer majors or the canonical `tool({ inputSchema: z.object({...}) })` failed to compile for every zod@4 consumer even with `skipLibCheck` (type-level breaking for downstream code assigning path elements to `string | number`; `validateOrThrow` maps elements through `String` first since `join` throws on symbols); (b) the published d.ts of `@graphorin/memory` (fact/block/recall/runbook tools) and `@graphorin/tools` (read_result, tool_search, code-mode meta-tools) baked concrete v3 `z.ZodObject<...>` generics via `z.infer<typeof schema>` aliases - replaced with explicit exported interfaces whose schema parity is pinned by in-source compile-time gates. The pack gate gains a `dts-no-concrete-zod-generics` leg and CI no longer allow-fails the zod4 leg - both zod majors are enforced at `skipLibCheck: false` from here on.
|
|
66
|
+
|
|
67
|
+
### Patch Changes
|
|
68
|
+
|
|
69
|
+
- [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-029/W-060: schema-driven session-content erasure via the exported `SESSION_SCOPED_PURGES` registry + a completeness gate test.
|
|
70
|
+
|
|
71
|
+
`deleteSession`/`pruneSessions` previously purged only `session_messages` and `episodes`; consolidator-distilled facts (with their FTS/vec rows and entity links), insights, rules, working blocks, spans, consolidator state/runs and `memory_history` values all survived a hard-delete and stayed findable through semantic search. The purge is now a loop over a declarative registry (`SESSION_SCOPED_PURGES`, each entry naming the session column, FTS shadow, vec0 sidecar family, FK-referencing tables and memory-history scrub policy), with `SESSION_TABLE_EXEMPTIONS` documenting the tables the cascade handles directly. A schema-introspection gate test diffs the registry against every live table carrying `scope_session_id`/`session_id` - adding a new session-scoped table without an erasure decision fails the suite. Only rows scoped to the deleted session are removed; user-level rows (`scope_session_id IS NULL`) are untouched. BEHAVIOR CHANGE: session-scoped facts and insights no longer survive session deletion.
|
|
72
|
+
|
|
73
|
+
- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-045: the `Cost.amount` units contract is now consistent across the ecosystem - and it is WHOLE currency units (for USD: dollars, fractional values expected), never "smallest fractional unit" / cents as the core TSDoc previously claimed. The canonical producer `calculateCost` (@graphorin/pricing), `CostTracker` snapshots (@graphorin/observability) and the memory consolidator's `costUsd` all already operated in dollars; a consumer that followed the old doc and divided by 100 was off by 100x. Docs-only for the code paths, with a numeric pin test (1M input tokens at $5/Mtok = exactly `5`) freezing the convention. If you implemented a minor-units conversion against the old wording, remove it.
|
|
74
|
+
|
|
75
|
+
- [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-048: the MemoryStore baseline-vs-full-adapter story is now explicit and gated. `GraphMemoryStoreExt` and `ProceduralMemoryStoreExt` are exported from the root of `@graphorin/memory` (they were package-internal); a type test pins `MemoryStore extends MemoryStoreAdapter`, so any future REQUIRED member on an `*MemoryStoreExt` breaks CI instead of silently breaking third-party core-only adapters; and the TSDoc on core `MemoryStore`, `Insight` and `GraphEntity` (plus the persistence guide) now states where the full-parity surfaces live and that graceful degradation is the contract.
|
|
76
|
+
|
|
77
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Core API stability is now enforced, not just claimed (W-127): the public surface is committed as an api-extractor report (`packages/core/etc/core.api.md`) and the new `check-api-report` CI gate fails any PR that changes the surface without updating it. Point fixes ride along: `ProviderResponse.toolCalls` reuses the canonical `ToolCall` type (the inline shape was structurally identical - not a breaking change), `Tool.defer_loading` documents its deliberate snake_case (one-to-one with the Anthropic wire flag), `SessionMemoryStore.search` documents that the positional `query` is authoritative over `opts.query` (pinned by a store-sqlite test), and the README's stale "loose @experimental corners" line now states the truth: the whole core surface ships `@stable`; `@experimental` lives in mcp/tools/security/skills.
|
|
78
|
+
|
|
79
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - TSDoc `{@link}` hygiene sweep (W-130): all 55 broken links found by TypeDoc's now-enabled `validation.invalidLink` are fixed - two resolved to their real targets (`GraphorinMCPError` was misnamed `MCPError`), the rest (cross-package, `import()`-form, unexported-constant, and DOM-type references that have never rendered as hrefs) converted to plain inline code. The docs build now fails on any new broken `{@link}` via a scoped gate.
|
|
80
|
+
|
|
81
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.
|
|
82
|
+
|
|
83
|
+
- [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Every published package now declares its tree-shaking contract via `sideEffects` (W-137): 18 packages audited to a pure module scope get `false`, the CLI declares its bin entry (`["./dist/bin/*"]`), and `@graphorin/security` gets an explicit `true` - its secrets subsystem registers built-in resolvers and the SecretValue caller-context provider at import time, so marking it pure would let bundlers drop those registrations. `mvp-readiness` now fails any publishable manifest without a declared `sideEffects`, closing the drift for future packages.
|
|
84
|
+
|
|
3
85
|
## 0.6.1
|
|
4
86
|
|
|
5
87
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ implementation (sandbox, secrets store, memory store, provider adapters,
|
|
|
8
8
|
agent runtime, workflow engine, server, …) lives in a sibling package and
|
|
9
9
|
depends on the interfaces declared here.
|
|
10
10
|
|
|
11
|
-
- **Status:** v0.
|
|
11
|
+
- **Status:** v0.7.0 - type and contract surface for the v0.1 release line.
|
|
12
12
|
- **License:** [MIT](./LICENSE) - © 2026 Oleksiy Stepurenko.
|
|
13
13
|
- **Engines:** Node.js 22+ (ESM only).
|
|
14
14
|
- **Runtime dependencies:** none.
|
|
@@ -59,9 +59,12 @@ Every exported type is annotated with one of two TSDoc tags:
|
|
|
59
59
|
- `@experimental` - may change between minor versions; a deprecation note
|
|
60
60
|
in the `CHANGELOG.md` will accompany every removal.
|
|
61
61
|
|
|
62
|
-
The
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
The whole `@graphorin/core` surface ships as `@stable` today - the
|
|
63
|
+
`@experimental` tag is used in the packages where genuinely unsettled
|
|
64
|
+
corners live (`mcp`, `tools`, `security`, `skills`), not here. The
|
|
65
|
+
committed API report in `etc/core.api.md` is diffed in CI
|
|
66
|
+
(`check-api-report`), so every change to this public surface is
|
|
67
|
+
explicit in review.
|
|
65
68
|
|
|
66
69
|
## Versioning
|
|
67
70
|
|
|
@@ -70,4 +73,4 @@ records are still being refined for v0.2.
|
|
|
70
73
|
|
|
71
74
|
---
|
|
72
75
|
|
|
73
|
-
**Graphorin** · v0.
|
|
76
|
+
**Graphorin** · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
|
package/dist/channels/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { AnyValue, Barrier, Channel, ChannelKind, Ephemeral, LatestValue, ListAg
|
|
|
2
2
|
import { Directive, DirectiveOptions } from "./directive.js";
|
|
3
3
|
import { Dispatch, dispatch } from "./dispatch.js";
|
|
4
4
|
import { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, ApprovalPauseValue, AwakeablePauseValue, TIMER_PAUSE_KIND, TimerPauseValue, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil } from "./durable.js";
|
|
5
|
-
import { PAUSE_SIGNAL_BRAND, PauseResumeScope, PauseSignal, isPauseSignal, pause, runWithPauseResume } from "./pause.js";
|
|
6
|
-
export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, type AnyValue, type ApprovalPauseValue, type AwakeablePauseValue, type Barrier, type Channel, type ChannelKind, Directive, type DirectiveOptions, Dispatch, type Ephemeral, type LatestValue, type ListAggregate, PAUSE_SIGNAL_BRAND, type PauseResumeScope, PauseSignal, type Reducer, type Stream, TIMER_PAUSE_KIND, type TimerPauseValue, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };
|
|
5
|
+
import { PAUSE_SIGNAL_BRAND, PauseIdentity, PauseResumeScope, PauseSignal, REPLAY_DIVERGENCE_BRAND, ReplayDivergenceSignal, isPauseSignal, isReplayDivergenceSignal, pause, runWithPauseResume } from "./pause.js";
|
|
6
|
+
export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, type AnyValue, type ApprovalPauseValue, type AwakeablePauseValue, type Barrier, type Channel, type ChannelKind, Directive, type DirectiveOptions, Dispatch, type Ephemeral, type LatestValue, type ListAggregate, PAUSE_SIGNAL_BRAND, type PauseIdentity, type PauseResumeScope, PauseSignal, REPLAY_DIVERGENCE_BRAND, type Reducer, ReplayDivergenceSignal, type Stream, TIMER_PAUSE_KIND, type TimerPauseValue, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isReplayDivergenceSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };
|
package/dist/channels/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { anyValue, barrier, ephemeral, latestValue, listAggregate, reducer, stream } from "./channels.js";
|
|
2
2
|
import { Directive } from "./directive.js";
|
|
3
3
|
import { Dispatch, dispatch } from "./dispatch.js";
|
|
4
|
-
import { PAUSE_SIGNAL_BRAND, PauseSignal, isPauseSignal, pause, runWithPauseResume } from "./pause.js";
|
|
4
|
+
import { PAUSE_SIGNAL_BRAND, PauseSignal, REPLAY_DIVERGENCE_BRAND, ReplayDivergenceSignal, isPauseSignal, isReplayDivergenceSignal, pause, runWithPauseResume } from "./pause.js";
|
|
5
5
|
import { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, TIMER_PAUSE_KIND, awaitExternal, isApprovalPauseValue, isAwakeablePauseValue, isTimerPauseValue, requestApproval, sleepFor, sleepUntil } from "./durable.js";
|
|
6
6
|
|
|
7
|
-
export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, Directive, Dispatch, PAUSE_SIGNAL_BRAND, PauseSignal, TIMER_PAUSE_KIND, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };
|
|
7
|
+
export { APPROVAL_PAUSE_KIND, AWAKEABLE_PAUSE_KIND, Directive, Dispatch, PAUSE_SIGNAL_BRAND, PauseSignal, REPLAY_DIVERGENCE_BRAND, ReplayDivergenceSignal, TIMER_PAUSE_KIND, anyValue, awaitExternal, barrier, dispatch, ephemeral, isApprovalPauseValue, isAwakeablePauseValue, isPauseSignal, isReplayDivergenceSignal, isTimerPauseValue, latestValue, listAggregate, pause, reducer, requestApproval, runWithPauseResume, sleepFor, sleepUntil, stream };
|
package/dist/channels/pause.d.ts
CHANGED
|
@@ -22,6 +22,46 @@ declare class PauseSignal<TValue = unknown> extends Error {
|
|
|
22
22
|
readonly value: TValue;
|
|
23
23
|
constructor(value: TValue);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Brand attached to the signal thrown by `pause(value)` when the
|
|
27
|
+
* positional replay diverges from the journaled pause identity
|
|
28
|
+
* (W-120). Cross-realm safe like {@link PAUSE_SIGNAL_BRAND}.
|
|
29
|
+
*
|
|
30
|
+
* @stable
|
|
31
|
+
*/
|
|
32
|
+
declare const REPLAY_DIVERGENCE_BRAND: unique symbol;
|
|
33
|
+
/**
|
|
34
|
+
* Identity of one pause as recorded next to its satisfied resume value
|
|
35
|
+
* (W-120): the durable-primitive `kind` (`timer` / `awakeable` /
|
|
36
|
+
* `approval`) and the awakeable/approval `name`. A plain `pause()` has
|
|
37
|
+
* neither - two plain pauses are indistinguishable BY DESIGN (no
|
|
38
|
+
* false positives; the check is deliberately conservative).
|
|
39
|
+
*
|
|
40
|
+
* @stable
|
|
41
|
+
*/
|
|
42
|
+
interface PauseIdentity {
|
|
43
|
+
readonly kind?: string;
|
|
44
|
+
readonly name?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Thrown by `pause(value)` during replay when the CURRENT pause's
|
|
48
|
+
* identity does not match what the journal recorded for this cursor
|
|
49
|
+
* position (W-120): the node body's pause order depends on
|
|
50
|
+
* time/state/LLM output, so a positional replay would silently hand a
|
|
51
|
+
* resume value to the wrong pause. The workflow engine converts this
|
|
52
|
+
* into a typed `pause-replay-divergence` WorkflowError.
|
|
53
|
+
*
|
|
54
|
+
* @stable
|
|
55
|
+
*/
|
|
56
|
+
declare class ReplayDivergenceSignal extends Error {
|
|
57
|
+
readonly [REPLAY_DIVERGENCE_BRAND]: true;
|
|
58
|
+
readonly expected: PauseIdentity;
|
|
59
|
+
readonly actual: PauseIdentity;
|
|
60
|
+
readonly cursor: number;
|
|
61
|
+
constructor(expected: PauseIdentity, actual: PauseIdentity, cursor: number);
|
|
62
|
+
}
|
|
63
|
+
/** Cross-realm safe type guard for {@link ReplayDivergenceSignal}. @stable */
|
|
64
|
+
declare function isReplayDivergenceSignal(err: unknown): err is ReplayDivergenceSignal;
|
|
25
65
|
/**
|
|
26
66
|
* Resume-injection scope set by the workflow runtime around the second
|
|
27
67
|
* (and later) invocations of a paused node body. When the scope is
|
|
@@ -38,6 +78,11 @@ declare class PauseSignal<TValue = unknown> extends Error {
|
|
|
38
78
|
interface PauseResumeScope {
|
|
39
79
|
/** Ordered resume values replayed to successive `pause()` calls (WF-2). */
|
|
40
80
|
readonly values: ReadonlyArray<unknown>;
|
|
81
|
+
/**
|
|
82
|
+
* W-120: per-value identity of the pause each value answered.
|
|
83
|
+
* Absent (legacy checkpoints) or `null`/empty entries skip the check.
|
|
84
|
+
*/
|
|
85
|
+
readonly meta?: ReadonlyArray<PauseIdentity | null | undefined>;
|
|
41
86
|
cursor: number;
|
|
42
87
|
}
|
|
43
88
|
/**
|
|
@@ -56,7 +101,7 @@ interface PauseResumeScope {
|
|
|
56
101
|
*
|
|
57
102
|
* @internal
|
|
58
103
|
*/
|
|
59
|
-
declare function runWithPauseResume<R>(values: ReadonlyArray<unknown>, fn: () => R | Promise<R>): Promise<R>;
|
|
104
|
+
declare function runWithPauseResume<R>(values: ReadonlyArray<unknown>, fn: () => R | Promise<R>, meta?: ReadonlyArray<PauseIdentity | null | undefined>): Promise<R>;
|
|
60
105
|
/**
|
|
61
106
|
* Programmatically suspend the current workflow node. The `value` is
|
|
62
107
|
* surfaced to callers via the `WorkflowSuspendedEvent.value` field; the
|
|
@@ -80,5 +125,5 @@ declare function pause<TValue, TResume = unknown>(value: TValue): TResume;
|
|
|
80
125
|
*/
|
|
81
126
|
declare function isPauseSignal(err: unknown): err is PauseSignal;
|
|
82
127
|
//#endregion
|
|
83
|
-
export { PAUSE_SIGNAL_BRAND, PauseResumeScope, PauseSignal, isPauseSignal, pause, runWithPauseResume };
|
|
128
|
+
export { PAUSE_SIGNAL_BRAND, PauseIdentity, PauseResumeScope, PauseSignal, REPLAY_DIVERGENCE_BRAND, ReplayDivergenceSignal, isPauseSignal, isReplayDivergenceSignal, pause, runWithPauseResume };
|
|
84
129
|
//# sourceMappingURL=pause.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pause.d.ts","names":[],"sources":["../../src/channels/pause.ts"],"sourcesContent":[],"mappings":";;AASA;AAYA;;;;;AAAwD,cAZ3C,kBAY2C,EAAA,OAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"pause.d.ts","names":[],"sources":["../../src/channels/pause.ts"],"sourcesContent":[],"mappings":";;AASA;AAYA;;;;;AAAwD,cAZ3C,kBAY2C,EAAA,OAAA,MAAA;AAkBxD;AAaA;AAeA;;;;;;;;AAkBgB,cAhEH,WAgE2B,CAAA,SAAA,OAAuB,CAAA,SAhEZ,KAAA,CAgEY;EA0C9C,UAzGL,kBAAA,CAyGqB,EAAA,IAAA;EAEd,SAAA,KAAA,EA1GD,MA0GC;EAKa,WAAA,CAAA,KAAA,EA7GX,MA6GW;;;AAsBhC;;;;;;AAGS,cAxHI,uBAwHJ,EAAA,OAAA,MAAA;;;;AAqBT;AA6BA;;;;;UA7JiB,aAAA;;;;;;;;;;;;;;cAeJ,sBAAA,SAA+B,KAAA;YAChC,uBAAA;qBACS;mBACF;;wBAGK,uBAAuB;;;iBAY/B,wBAAA,uBAA+C;;;;;;;;;;;;;;UA0C9C,gBAAA;;mBAEE;;;;;kBAKD,cAAc;;;;;;;;;;;;;;;;;;;iBAsBhB,8BACN,kCACE,IAAI,QAAQ,WACf,cAAc,oCACpB,QAAQ;;;;;;;;;;;;;;;;iBAoBK,wCAAwC,SAAS;;;;;;iBA6BjD,aAAA,uBAAoC"}
|
package/dist/channels/pause.js
CHANGED
|
@@ -28,6 +28,60 @@ var PauseSignal = class extends Error {
|
|
|
28
28
|
this.value = value;
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Brand attached to the signal thrown by `pause(value)` when the
|
|
33
|
+
* positional replay diverges from the journaled pause identity
|
|
34
|
+
* (W-120). Cross-realm safe like {@link PAUSE_SIGNAL_BRAND}.
|
|
35
|
+
*
|
|
36
|
+
* @stable
|
|
37
|
+
*/
|
|
38
|
+
const REPLAY_DIVERGENCE_BRAND = Symbol.for("graphorin.ReplayDivergenceSignal");
|
|
39
|
+
/**
|
|
40
|
+
* Thrown by `pause(value)` during replay when the CURRENT pause's
|
|
41
|
+
* identity does not match what the journal recorded for this cursor
|
|
42
|
+
* position (W-120): the node body's pause order depends on
|
|
43
|
+
* time/state/LLM output, so a positional replay would silently hand a
|
|
44
|
+
* resume value to the wrong pause. The workflow engine converts this
|
|
45
|
+
* into a typed `pause-replay-divergence` WorkflowError.
|
|
46
|
+
*
|
|
47
|
+
* @stable
|
|
48
|
+
*/
|
|
49
|
+
var ReplayDivergenceSignal = class extends Error {
|
|
50
|
+
[REPLAY_DIVERGENCE_BRAND] = true;
|
|
51
|
+
expected;
|
|
52
|
+
actual;
|
|
53
|
+
cursor;
|
|
54
|
+
constructor(expected, actual, cursor) {
|
|
55
|
+
super(`graphorin: pause replay divergence at cursor ${cursor}: paused as ${describeIdentity(actual)} where the journal recorded ${describeIdentity(expected)}`);
|
|
56
|
+
this.name = "ReplayDivergenceSignal";
|
|
57
|
+
this.expected = expected;
|
|
58
|
+
this.actual = actual;
|
|
59
|
+
this.cursor = cursor;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Cross-realm safe type guard for {@link ReplayDivergenceSignal}. @stable */
|
|
63
|
+
function isReplayDivergenceSignal(err) {
|
|
64
|
+
return typeof err === "object" && err !== null && err[REPLAY_DIVERGENCE_BRAND] === true;
|
|
65
|
+
}
|
|
66
|
+
function describeIdentity(id) {
|
|
67
|
+
const parts = [];
|
|
68
|
+
if (id.kind !== void 0) parts.push(`kind '${id.kind}'`);
|
|
69
|
+
if (id.name !== void 0) parts.push(`name '${id.name}'`);
|
|
70
|
+
return parts.length > 0 ? parts.join(" / ") : "a plain pause";
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract the {@link PauseIdentity} of a pause payload: generic field
|
|
74
|
+
* access only (no import of the durable-primitive types - that would
|
|
75
|
+
* cycle).
|
|
76
|
+
*/
|
|
77
|
+
function identityOfPauseValue(value) {
|
|
78
|
+
if (typeof value !== "object" || value === null) return {};
|
|
79
|
+
const v = value;
|
|
80
|
+
return {
|
|
81
|
+
...typeof v.kind === "string" ? { kind: v.kind } : {},
|
|
82
|
+
...typeof v.name === "string" ? { name: v.name } : {}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
31
85
|
const pauseResumeStorage = new AsyncLocalStorage();
|
|
32
86
|
/**
|
|
33
87
|
* Run `fn` inside a scope where successive `pause(...)` calls return the
|
|
@@ -45,9 +99,10 @@ const pauseResumeStorage = new AsyncLocalStorage();
|
|
|
45
99
|
*
|
|
46
100
|
* @internal
|
|
47
101
|
*/
|
|
48
|
-
function runWithPauseResume(values, fn) {
|
|
102
|
+
function runWithPauseResume(values, fn, meta) {
|
|
49
103
|
const scope = {
|
|
50
104
|
values,
|
|
105
|
+
...meta !== void 0 ? { meta } : {},
|
|
51
106
|
cursor: 0
|
|
52
107
|
};
|
|
53
108
|
return pauseResumeStorage.run(scope, async () => fn());
|
|
@@ -70,6 +125,11 @@ function runWithPauseResume(values, fn) {
|
|
|
70
125
|
function pause(value) {
|
|
71
126
|
const scope = pauseResumeStorage.getStore();
|
|
72
127
|
if (scope !== void 0 && scope.cursor < scope.values.length) {
|
|
128
|
+
const expected = scope.meta?.[scope.cursor];
|
|
129
|
+
if (expected != null && (expected.kind !== void 0 || expected.name !== void 0)) {
|
|
130
|
+
const actual = identityOfPauseValue(value);
|
|
131
|
+
if (expected.kind !== void 0 && expected.kind !== actual.kind || expected.name !== void 0 && expected.name !== actual.name) throw new ReplayDivergenceSignal(expected, actual, scope.cursor);
|
|
132
|
+
}
|
|
73
133
|
const next = scope.values[scope.cursor];
|
|
74
134
|
scope.cursor += 1;
|
|
75
135
|
return next;
|
|
@@ -86,5 +146,5 @@ function isPauseSignal(err) {
|
|
|
86
146
|
}
|
|
87
147
|
|
|
88
148
|
//#endregion
|
|
89
|
-
export { PAUSE_SIGNAL_BRAND, PauseSignal, isPauseSignal, pause, runWithPauseResume };
|
|
149
|
+
export { PAUSE_SIGNAL_BRAND, PauseSignal, REPLAY_DIVERGENCE_BRAND, ReplayDivergenceSignal, isPauseSignal, isReplayDivergenceSignal, pause, runWithPauseResume };
|
|
90
150
|
//# sourceMappingURL=pause.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pause.js","names":["PAUSE_SIGNAL_BRAND: unique symbol","scope: PauseResumeScope"],"sources":["../../src/channels/pause.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Brand attached to the signal thrown by `pause(value)` so that the\n * workflow runtime can recognise it across realms (Worker threads,\n * sandboxes, …) without `instanceof`.\n *\n * @stable\n */\nexport const PAUSE_SIGNAL_BRAND: unique symbol = Symbol.for('graphorin.PauseSignal');\n\n/**\n * Thrown by `pause(value)` from inside a workflow node. The runtime\n * catches it, persists state with a pending pause, and suspends the\n * thread until `Workflow.resume(threadId, directive)` is called.\n *\n * Application code should never construct or catch this directly -\n * always go through `pause(...)`.\n *\n * @stable\n */\nexport class PauseSignal<TValue = unknown> extends Error {\n readonly [PAUSE_SIGNAL_BRAND]: true = true;\n readonly value: TValue;\n\n constructor(value: TValue) {\n super('graphorin: workflow paused');\n this.name = 'PauseSignal';\n this.value = value;\n }\n}\n\n/**\n * Resume-injection scope set by the workflow runtime around the second\n * (and later) invocations of a paused node body. When the scope is\n * present, `pause(...)` consults it to decide whether to throw a fresh\n * {@link PauseSignal} or return the injected value the runtime supplied\n * via `Workflow.resume(threadId, new Directive({ resume }))`.\n *\n * This is the storage mechanism that gives `pause()` its symmetric\n * pair semantics (`pause` ↔ `resume`) without forcing every node body\n * to be re-architected as a state machine.\n *\n * @internal\n */\nexport interface PauseResumeScope {\n /** Ordered resume values replayed to successive `pause()` calls (WF-2). */\n readonly values: ReadonlyArray<unknown>;\n cursor: number;\n}\n\nconst pauseResumeStorage = new AsyncLocalStorage<PauseResumeScope>();\n\n/**\n * Run `fn` inside a scope where successive `pause(...)` calls return the\n * supplied `values` in order instead of throwing a fresh\n * {@link PauseSignal} (WF-2: a node body re-executes from the top on\n * every resume, so earlier pauses must replay their already-delivered\n * values and only the FIRST unsatisfied `pause()` suspends again). An\n * empty `values` array behaves exactly like no scope - every `pause()`\n * suspends - which is what a static-gate resume needs so a programmatic\n * `pause()` inside the node is never silently satisfied.\n *\n * This helper is the contract between the runtime and `pause(...)`.\n * Consumers of `pause(...)` never call it directly - only the workflow\n * engine wires it up around the resumed node body.\n *\n * @internal\n */\nexport function runWithPauseResume<R>(\n values: ReadonlyArray<unknown>,\n fn: () => R | Promise<R>,\n): Promise<R> {\n const scope: PauseResumeScope = { values, cursor: 0 };\n return pauseResumeStorage.run(scope, async () => fn());\n}\n\n/**\n * Programmatically suspend the current workflow node. The `value` is\n * surfaced to callers via the `WorkflowSuspendedEvent.value` field; the\n * eventual `Directive({ resume })` is delivered as the return value of\n * this call once the runtime resumes the thread.\n *\n * Implementation note: when the call is made outside a runtime-managed\n * resume scope, `pause(...)` throws a fresh {@link PauseSignal} so the\n * engine can catch it, persist state, and suspend. When the runtime\n * later resumes the node body, it wraps the second invocation in\n * {@link runWithPauseResume}, which causes the same `pause(...)` call to\n * return the operator-supplied resume value instead of throwing.\n *\n * @stable\n */\nexport function pause<TValue, TResume = unknown>(value: TValue): TResume {\n const scope = pauseResumeStorage.getStore();\n if (scope !== undefined && scope.cursor < scope.values.length) {\n const next = scope.values[scope.cursor];\n scope.cursor += 1;\n return next as TResume;\n }\n throw new PauseSignal<TValue>(value);\n}\n\n/**\n * Cross-realm safe type guard for `PauseSignal`.\n *\n * @stable\n */\nexport function isPauseSignal(err: unknown): err is PauseSignal {\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as Record<symbol, unknown>)[PAUSE_SIGNAL_BRAND] === true\n );\n}\n"],"mappings":";;;;;;;;;;AASA,MAAaA,qBAAoC,OAAO,IAAI,wBAAwB;;;;;;;;;;;AAYpF,IAAa,cAAb,cAAmD,MAAM;CACvD,CAAU,sBAA4B;CACtC,AAAS;CAET,YAAY,OAAe;AACzB,QAAM,6BAA6B;AACnC,OAAK,OAAO;AACZ,OAAK,QAAQ;;;AAuBjB,MAAM,qBAAqB,IAAI,mBAAqC;;;;;;;;;;;;;;;;;AAkBpE,SAAgB,mBACd,QACA,IACY;CACZ,MAAMC,QAA0B;EAAE;EAAQ,QAAQ;EAAG;AACrD,QAAO,mBAAmB,IAAI,OAAO,YAAY,IAAI,CAAC;;;;;;;;;;;;;;;;;AAkBxD,SAAgB,MAAiC,OAAwB;CACvE,MAAM,QAAQ,mBAAmB,UAAU;AAC3C,KAAI,UAAU,UAAa,MAAM,SAAS,MAAM,OAAO,QAAQ;EAC7D,MAAM,OAAO,MAAM,OAAO,MAAM;AAChC,QAAM,UAAU;AAChB,SAAO;;AAET,OAAM,IAAI,YAAoB,MAAM;;;;;;;AAQtC,SAAgB,cAAc,KAAkC;AAC9D,QACE,OAAO,QAAQ,YACf,QAAQ,QACP,IAAgC,wBAAwB"}
|
|
1
|
+
{"version":3,"file":"pause.js","names":["PAUSE_SIGNAL_BRAND: unique symbol","REPLAY_DIVERGENCE_BRAND: unique symbol","parts: string[]","scope: PauseResumeScope"],"sources":["../../src/channels/pause.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Brand attached to the signal thrown by `pause(value)` so that the\n * workflow runtime can recognise it across realms (Worker threads,\n * sandboxes, …) without `instanceof`.\n *\n * @stable\n */\nexport const PAUSE_SIGNAL_BRAND: unique symbol = Symbol.for('graphorin.PauseSignal');\n\n/**\n * Thrown by `pause(value)` from inside a workflow node. The runtime\n * catches it, persists state with a pending pause, and suspends the\n * thread until `Workflow.resume(threadId, directive)` is called.\n *\n * Application code should never construct or catch this directly -\n * always go through `pause(...)`.\n *\n * @stable\n */\nexport class PauseSignal<TValue = unknown> extends Error {\n readonly [PAUSE_SIGNAL_BRAND]: true = true;\n readonly value: TValue;\n\n constructor(value: TValue) {\n super('graphorin: workflow paused');\n this.name = 'PauseSignal';\n this.value = value;\n }\n}\n\n/**\n * Brand attached to the signal thrown by `pause(value)` when the\n * positional replay diverges from the journaled pause identity\n * (W-120). Cross-realm safe like {@link PAUSE_SIGNAL_BRAND}.\n *\n * @stable\n */\nexport const REPLAY_DIVERGENCE_BRAND: unique symbol = Symbol.for(\n 'graphorin.ReplayDivergenceSignal',\n);\n\n/**\n * Identity of one pause as recorded next to its satisfied resume value\n * (W-120): the durable-primitive `kind` (`timer` / `awakeable` /\n * `approval`) and the awakeable/approval `name`. A plain `pause()` has\n * neither - two plain pauses are indistinguishable BY DESIGN (no\n * false positives; the check is deliberately conservative).\n *\n * @stable\n */\nexport interface PauseIdentity {\n readonly kind?: string;\n readonly name?: string;\n}\n\n/**\n * Thrown by `pause(value)` during replay when the CURRENT pause's\n * identity does not match what the journal recorded for this cursor\n * position (W-120): the node body's pause order depends on\n * time/state/LLM output, so a positional replay would silently hand a\n * resume value to the wrong pause. The workflow engine converts this\n * into a typed `pause-replay-divergence` WorkflowError.\n *\n * @stable\n */\nexport class ReplayDivergenceSignal extends Error {\n readonly [REPLAY_DIVERGENCE_BRAND]: true = true;\n readonly expected: PauseIdentity;\n readonly actual: PauseIdentity;\n readonly cursor: number;\n\n constructor(expected: PauseIdentity, actual: PauseIdentity, cursor: number) {\n super(\n `graphorin: pause replay divergence at cursor ${cursor}: paused as ${describeIdentity(actual)} where the journal recorded ${describeIdentity(expected)}`,\n );\n this.name = 'ReplayDivergenceSignal';\n this.expected = expected;\n this.actual = actual;\n this.cursor = cursor;\n }\n}\n\n/** Cross-realm safe type guard for {@link ReplayDivergenceSignal}. @stable */\nexport function isReplayDivergenceSignal(err: unknown): err is ReplayDivergenceSignal {\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as Record<symbol, unknown>)[REPLAY_DIVERGENCE_BRAND] === true\n );\n}\n\nfunction describeIdentity(id: PauseIdentity): string {\n const parts: string[] = [];\n if (id.kind !== undefined) parts.push(`kind '${id.kind}'`);\n if (id.name !== undefined) parts.push(`name '${id.name}'`);\n return parts.length > 0 ? parts.join(' / ') : 'a plain pause';\n}\n\n/**\n * Extract the {@link PauseIdentity} of a pause payload: generic field\n * access only (no import of the durable-primitive types - that would\n * cycle).\n */\nfunction identityOfPauseValue(value: unknown): PauseIdentity {\n if (typeof value !== 'object' || value === null) return {};\n const v = value as { readonly kind?: unknown; readonly name?: unknown };\n return {\n ...(typeof v.kind === 'string' ? { kind: v.kind } : {}),\n ...(typeof v.name === 'string' ? { name: v.name } : {}),\n };\n}\n\n/**\n * Resume-injection scope set by the workflow runtime around the second\n * (and later) invocations of a paused node body. When the scope is\n * present, `pause(...)` consults it to decide whether to throw a fresh\n * {@link PauseSignal} or return the injected value the runtime supplied\n * via `Workflow.resume(threadId, new Directive({ resume }))`.\n *\n * This is the storage mechanism that gives `pause()` its symmetric\n * pair semantics (`pause` ↔ `resume`) without forcing every node body\n * to be re-architected as a state machine.\n *\n * @internal\n */\nexport interface PauseResumeScope {\n /** Ordered resume values replayed to successive `pause()` calls (WF-2). */\n readonly values: ReadonlyArray<unknown>;\n /**\n * W-120: per-value identity of the pause each value answered.\n * Absent (legacy checkpoints) or `null`/empty entries skip the check.\n */\n readonly meta?: ReadonlyArray<PauseIdentity | null | undefined>;\n cursor: number;\n}\n\nconst pauseResumeStorage = new AsyncLocalStorage<PauseResumeScope>();\n\n/**\n * Run `fn` inside a scope where successive `pause(...)` calls return the\n * supplied `values` in order instead of throwing a fresh\n * {@link PauseSignal} (WF-2: a node body re-executes from the top on\n * every resume, so earlier pauses must replay their already-delivered\n * values and only the FIRST unsatisfied `pause()` suspends again). An\n * empty `values` array behaves exactly like no scope - every `pause()`\n * suspends - which is what a static-gate resume needs so a programmatic\n * `pause()` inside the node is never silently satisfied.\n *\n * This helper is the contract between the runtime and `pause(...)`.\n * Consumers of `pause(...)` never call it directly - only the workflow\n * engine wires it up around the resumed node body.\n *\n * @internal\n */\nexport function runWithPauseResume<R>(\n values: ReadonlyArray<unknown>,\n fn: () => R | Promise<R>,\n meta?: ReadonlyArray<PauseIdentity | null | undefined>,\n): Promise<R> {\n const scope: PauseResumeScope = { values, ...(meta !== undefined ? { meta } : {}), cursor: 0 };\n return pauseResumeStorage.run(scope, async () => fn());\n}\n\n/**\n * Programmatically suspend the current workflow node. The `value` is\n * surfaced to callers via the `WorkflowSuspendedEvent.value` field; the\n * eventual `Directive({ resume })` is delivered as the return value of\n * this call once the runtime resumes the thread.\n *\n * Implementation note: when the call is made outside a runtime-managed\n * resume scope, `pause(...)` throws a fresh {@link PauseSignal} so the\n * engine can catch it, persist state, and suspend. When the runtime\n * later resumes the node body, it wraps the second invocation in\n * {@link runWithPauseResume}, which causes the same `pause(...)` call to\n * return the operator-supplied resume value instead of throwing.\n *\n * @stable\n */\nexport function pause<TValue, TResume = unknown>(value: TValue): TResume {\n const scope = pauseResumeStorage.getStore();\n if (scope !== undefined && scope.cursor < scope.values.length) {\n // W-120: verify the replayed value is answering the SAME pause the\n // journal recorded at this cursor. Legacy checkpoints (no meta) and\n // plain pauses (empty identity) replay unchecked - conservative by\n // design, false positives are impossible.\n const expected = scope.meta?.[scope.cursor];\n if (expected != null && (expected.kind !== undefined || expected.name !== undefined)) {\n const actual = identityOfPauseValue(value);\n if (\n (expected.kind !== undefined && expected.kind !== actual.kind) ||\n (expected.name !== undefined && expected.name !== actual.name)\n ) {\n throw new ReplayDivergenceSignal(expected, actual, scope.cursor);\n }\n }\n const next = scope.values[scope.cursor];\n scope.cursor += 1;\n return next as TResume;\n }\n throw new PauseSignal<TValue>(value);\n}\n\n/**\n * Cross-realm safe type guard for `PauseSignal`.\n *\n * @stable\n */\nexport function isPauseSignal(err: unknown): err is PauseSignal {\n return (\n typeof err === 'object' &&\n err !== null &&\n (err as Record<symbol, unknown>)[PAUSE_SIGNAL_BRAND] === true\n );\n}\n"],"mappings":";;;;;;;;;;AASA,MAAaA,qBAAoC,OAAO,IAAI,wBAAwB;;;;;;;;;;;AAYpF,IAAa,cAAb,cAAmD,MAAM;CACvD,CAAU,sBAA4B;CACtC,AAAS;CAET,YAAY,OAAe;AACzB,QAAM,6BAA6B;AACnC,OAAK,OAAO;AACZ,OAAK,QAAQ;;;;;;;;;;AAWjB,MAAaC,0BAAyC,OAAO,IAC3D,mCACD;;;;;;;;;;;AA0BD,IAAa,yBAAb,cAA4C,MAAM;CAChD,CAAU,2BAAiC;CAC3C,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,UAAyB,QAAuB,QAAgB;AAC1E,QACE,gDAAgD,OAAO,cAAc,iBAAiB,OAAO,CAAC,8BAA8B,iBAAiB,SAAS,GACvJ;AACD,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,OAAK,SAAS;AACd,OAAK,SAAS;;;;AAKlB,SAAgB,yBAAyB,KAA6C;AACpF,QACE,OAAO,QAAQ,YACf,QAAQ,QACP,IAAgC,6BAA6B;;AAIlE,SAAS,iBAAiB,IAA2B;CACnD,MAAMC,QAAkB,EAAE;AAC1B,KAAI,GAAG,SAAS,OAAW,OAAM,KAAK,SAAS,GAAG,KAAK,GAAG;AAC1D,KAAI,GAAG,SAAS,OAAW,OAAM,KAAK,SAAS,GAAG,KAAK,GAAG;AAC1D,QAAO,MAAM,SAAS,IAAI,MAAM,KAAK,MAAM,GAAG;;;;;;;AAQhD,SAAS,qBAAqB,OAA+B;AAC3D,KAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO,EAAE;CAC1D,MAAM,IAAI;AACV,QAAO;EACL,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;EACtD,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;EACvD;;AA2BH,MAAM,qBAAqB,IAAI,mBAAqC;;;;;;;;;;;;;;;;;AAkBpE,SAAgB,mBACd,QACA,IACA,MACY;CACZ,MAAMC,QAA0B;EAAE;EAAQ,GAAI,SAAS,SAAY,EAAE,MAAM,GAAG,EAAE;EAAG,QAAQ;EAAG;AAC9F,QAAO,mBAAmB,IAAI,OAAO,YAAY,IAAI,CAAC;;;;;;;;;;;;;;;;;AAkBxD,SAAgB,MAAiC,OAAwB;CACvE,MAAM,QAAQ,mBAAmB,UAAU;AAC3C,KAAI,UAAU,UAAa,MAAM,SAAS,MAAM,OAAO,QAAQ;EAK7D,MAAM,WAAW,MAAM,OAAO,MAAM;AACpC,MAAI,YAAY,SAAS,SAAS,SAAS,UAAa,SAAS,SAAS,SAAY;GACpF,MAAM,SAAS,qBAAqB,MAAM;AAC1C,OACG,SAAS,SAAS,UAAa,SAAS,SAAS,OAAO,QACxD,SAAS,SAAS,UAAa,SAAS,SAAS,OAAO,KAEzD,OAAM,IAAI,uBAAuB,UAAU,QAAQ,MAAM,OAAO;;EAGpE,MAAM,OAAO,MAAM,OAAO,MAAM;AAChC,QAAM,UAAU;AAChB,SAAO;;AAET,OAAM,IAAI,YAAoB,MAAM;;;;;;;AAQtC,SAAgB,cAAc,KAAkC;AAC9D,QACE,OAAO,QAAQ,YACf,QAAQ,QACP,IAAgC,wBAAwB"}
|
|
@@ -42,6 +42,26 @@ interface CheckpointMetadata {
|
|
|
42
42
|
readonly status: 'running' | 'suspended' | 'completed' | 'failed' | 'aborted';
|
|
43
43
|
readonly nodeName?: string;
|
|
44
44
|
readonly tags?: ReadonlyArray<string>;
|
|
45
|
+
/**
|
|
46
|
+
* Session this checkpoint's state belongs to, when known (W-005).
|
|
47
|
+
* The agent runtime stamps it on every HITL-suspend write so a
|
|
48
|
+
* session hard-delete can cascade into `workflow_checkpoints` /
|
|
49
|
+
* `workflow_pending_writes` without parsing the opaque state blob.
|
|
50
|
+
* Optional and additive: third-party stores may ignore it, but any
|
|
51
|
+
* store that also implements `SessionStoreExt.deleteSession` should
|
|
52
|
+
* use it to honour the full erasure contract.
|
|
53
|
+
*/
|
|
54
|
+
readonly sessionId?: string;
|
|
55
|
+
/**
|
|
56
|
+
* W-032: earliest due durable timer among this checkpoint's frontier
|
|
57
|
+
* pauses (epoch ms). The workflow engine stamps it on every
|
|
58
|
+
* `suspended` write whose frontier holds a timer, so a store's
|
|
59
|
+
* {@link CheckpointStore.listSuspended} can enumerate due threads
|
|
60
|
+
* without parsing the opaque tags. Absent on non-timer suspends and
|
|
61
|
+
* on checkpoints written before the field existed - such threads
|
|
62
|
+
* need one manual `tick` (or a resume) to become driver-visible.
|
|
63
|
+
*/
|
|
64
|
+
readonly wakeAt?: number;
|
|
45
65
|
}
|
|
46
66
|
/**
|
|
47
67
|
* A checkpoint paired with its sidecar metadata. Returned by
|
|
@@ -119,8 +139,84 @@ interface CheckpointStore {
|
|
|
119
139
|
putWrites(threadId: string, namespace: string, checkpointId: CheckpointId, writes: ReadonlyArray<PendingWrite>, taskId: string): Promise<void>;
|
|
120
140
|
getTuple(threadId: string, namespace: string, checkpointId?: CheckpointId): Promise<CheckpointTuple | null>;
|
|
121
141
|
list(threadId: string, namespace: string, opts?: ListOptions): AsyncIterable<CheckpointTuple>;
|
|
142
|
+
/**
|
|
143
|
+
* Full erasure primitive: delete every checkpoint and pending write of
|
|
144
|
+
* this thread across ALL namespaces. Namespace-blind by contract -
|
|
145
|
+
* retention sweeps must use {@link CheckpointStoreExt.pruneThreads}
|
|
146
|
+
* instead, which is namespace-scoped and protects suspended threads.
|
|
147
|
+
*/
|
|
122
148
|
deleteThread(threadId: string): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* W-032: enumerate threads whose LATEST checkpoint in `namespace` is
|
|
151
|
+
* `suspended` with a due `wakeAt` (`<= opts.dueBefore`, default: any
|
|
152
|
+
* stamped wakeAt). This is what a durable-timer driver polls -
|
|
153
|
+
* without it an operator would have to keep an external registry of
|
|
154
|
+
* sleeping threadIds. OPTIONAL so third-party stores compile
|
|
155
|
+
* unchanged; `createTimerDriver` throws a typed error when the store
|
|
156
|
+
* lacks it (deterministic policy, no silent no-op).
|
|
157
|
+
*/
|
|
158
|
+
listSuspended?(namespace: string, opts?: {
|
|
159
|
+
readonly dueBefore?: number;
|
|
160
|
+
readonly limit?: number;
|
|
161
|
+
}): Promise<ReadonlyArray<{
|
|
162
|
+
readonly threadId: string;
|
|
163
|
+
readonly wakeAt: number;
|
|
164
|
+
}>>;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Options for {@link CheckpointStoreExt.pruneThreads}.
|
|
168
|
+
*
|
|
169
|
+
* @stable
|
|
170
|
+
*/
|
|
171
|
+
interface PruneThreadsOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Cutoff: a `(threadId, namespace)` pair qualifies when its LATEST
|
|
174
|
+
* checkpoint (by `stepNumber`) was created before this epoch-ms
|
|
175
|
+
* instant.
|
|
176
|
+
*/
|
|
177
|
+
readonly beforeEpochMs: number;
|
|
178
|
+
/**
|
|
179
|
+
* When `true` (the default), only pairs whose latest checkpoint has a
|
|
180
|
+
* terminal status (`completed` / `failed` / `aborted`) are pruned -
|
|
181
|
+
* suspended threads hold live HITL approvals / awakeables and must
|
|
182
|
+
* survive a retention sweep. Set to `false` for a hard age-based
|
|
183
|
+
* sweep that also removes suspended threads.
|
|
184
|
+
*/
|
|
185
|
+
readonly onlyTerminal?: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Retention extension over {@link CheckpointStore} (W-009). The engine
|
|
189
|
+
* intentionally never deletes finished threads itself - a completed
|
|
190
|
+
* thread is still needed for inspection and duplicate-resume refusal;
|
|
191
|
+
* how long to keep it is an operator decision. These primitives are
|
|
192
|
+
* what an operator (or a host scheduler) drives.
|
|
193
|
+
*
|
|
194
|
+
* Additive: third-party `CheckpointStore` implementations compile
|
|
195
|
+
* unchanged; hosts feature-detect with `'pruneThreads' in store`.
|
|
196
|
+
*
|
|
197
|
+
* @stable
|
|
198
|
+
*/
|
|
199
|
+
interface CheckpointStoreExt extends CheckpointStore {
|
|
200
|
+
/**
|
|
201
|
+
* Namespace-scoped retention sweep: for every `(threadId, namespace)`
|
|
202
|
+
* pair matching the policy, delete that pair's checkpoints and pending
|
|
203
|
+
* writes - and ONLY that pair's. Never implemented via
|
|
204
|
+
* {@link CheckpointStore.deleteThread} (namespace-blind): with a
|
|
205
|
+
* reused threadId, pruning a terminal thread of workflow A must not
|
|
206
|
+
* erase the suspended checkpoints of workflow B. Returns the number
|
|
207
|
+
* of pairs pruned.
|
|
208
|
+
*/
|
|
209
|
+
pruneThreads(opts: PruneThreadsOptions): Promise<number>;
|
|
210
|
+
/**
|
|
211
|
+
* Keep only the `keepLast` most recent checkpoints (by `stepNumber`)
|
|
212
|
+
* of one `(threadId, namespace)` pair, deleting older ones together
|
|
213
|
+
* with their pending writes. `keepLast >= 1`; resume works from the
|
|
214
|
+
* latest tuple, so compaction never breaks resumability - it does
|
|
215
|
+
* remove time-travel/fork targets. Returns the number of checkpoints
|
|
216
|
+
* deleted.
|
|
217
|
+
*/
|
|
218
|
+
compactThread(threadId: string, namespace: string, keepLast: number): Promise<number>;
|
|
123
219
|
}
|
|
124
220
|
//#endregion
|
|
125
|
-
export { Checkpoint, CheckpointConflictError, CheckpointId, CheckpointMetadata, CheckpointPutOptions, CheckpointStore, CheckpointTuple, ListOptions, PendingWrite };
|
|
221
|
+
export { Checkpoint, CheckpointConflictError, CheckpointId, CheckpointMetadata, CheckpointPutOptions, CheckpointStore, CheckpointStoreExt, CheckpointTuple, ListOptions, PendingWrite, PruneThreadsOptions };
|
|
126
222
|
//# sourceMappingURL=checkpoint-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint-store.d.ts","names":[],"sources":["../../src/contracts/checkpoint-store.ts"],"sourcesContent":[],"mappings":";;AAOA;AAQA;;;;;AAQoC,KAhBxB,YAAA,GAgBwB,MAAA;AAWpC;
|
|
1
|
+
{"version":3,"file":"checkpoint-store.d.ts","names":[],"sources":["../../src/contracts/checkpoint-store.ts"],"sourcesContent":[],"mappings":";;AAOA;AAQA;;;;;AAQoC,KAhBxB,YAAA,GAgBwB,MAAA;AAWpC;AAuCA;;;;;AAGwC,UA7DvB,UAAA,CA6DuB;EAUvB,SAAA,EAAA,EAtEF,YAsEc;EAaZ,SAAA,QAAW,EAAA,MAAA;EAmBX,SAAA,SAAA,EAAA,MAAoB;EAYxB,SAAA,QAAA,CAAA,EA/GS,YA+Ge;EAER;EACF,SAAA,KAAA,EAAA,OAAA;EAEe;EAA6B,SAAA,eAAA,EAhH3C,QAgH2C,CAhHlC,MAgHkC,CAAA,MAAA,EAAA,MAAA,CAAA,CAAA;EAL1B,SAAA,UAAA,EAAA,MAAA;EAAK,SAAA,SAAA,EAAA,MAAA;AAsBlD;;;;;;;AAa0B,UAnIT,kBAAA,CAmIS;EAAd;;;;;;EAUmE,SAAA,MAAA,EAAA,MAAA,GAAA,MAAA;EAAd,SAAA,MAAA,EAAA,SAAA,GAAA,WAAA,GAAA,WAAA,GAAA,QAAA,GAAA,SAAA;EAQ/B,SAAA,QAAA,CAAA,EAAA,MAAA;EAcrB,SAAA,IAAA,CAAA,EAzJK,aAyJL,CAAA,MAAA,CAAA;EAAR;;AAQL;AA6BA;;;;;;;;;;;;;;;;;;;;;;;;UAjKiB,eAAA;uBACM;qBACF;2BACM,cAAc;;;;;;;;;UAUxB,YAAA;;;;;;;;;;;;UAaA,WAAA;;oBAEG;oBACA;;;;;;;;;;;;;;;UAgBH,oBAAA;8BACa;;;;;;;;;;cAWjB,uBAAA,SAAgC,KAAA;;6BAEhB;2BACF;0CAEe,6BAA6B;;;;;;;;UAiBtD,eAAA;uDAID,sBACF,2BACH,uBACN,QAAQ;+DAKK,sBACN,cAAc,gCAErB;+DAKc,eACd,QAAQ;mDAEsC,cAAc,cAAc;;;;;;;kCAQ7C;;;;;;;;;;;;;MAc7B,QAAQ;;;;;;;;;;UAQI,mBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6BA,kBAAA,SAA2B;;;;;;;;;;qBAUvB,sBAAsB;;;;;;;;;wEAU6B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint-store.js","names":[],"sources":["../../src/contracts/checkpoint-store.ts"],"sourcesContent":["/**\n * Opaque identifier for a single workflow checkpoint. Treated as a string\n * by every consumer so adapters can pick whatever encoding they prefer\n * (ULID, UUID, snowflake-like, …).\n *\n * @stable\n */\nexport type CheckpointId = string;\n\n/**\n * Serialized snapshot of workflow state, written after every execution\n * step.\n *\n * @stable\n */\nexport interface Checkpoint {\n readonly id: CheckpointId;\n readonly threadId: string;\n readonly namespace: string;\n readonly parentId?: CheckpointId;\n /** Serialized state blob - adapter-specific encoding (JSON / superjson / …). */\n readonly state: unknown;\n /** Per-channel monotonic versions used by the workflow scheduler. */\n readonly channelVersions: Readonly<Record<string, number>>;\n readonly stepNumber: number;\n readonly createdAt: string;\n}\n\n/**\n * Metadata associated with a checkpoint write. Adapters store this in a\n * sidecar table for efficient listing.\n *\n * @stable\n */\nexport interface CheckpointMetadata {\n /**\n * Durability mode that produced this write. The legacy `'async'`\n * value was removed (workflow-14 / WF-7 - it was byte-identical to\n * `'sync'`); adapters normalize legacy persisted rows to `'sync'` at\n * read time.\n */\n readonly source: 'sync' | 'exit';\n readonly status: 'running' | 'suspended' | 'completed' | 'failed' | 'aborted';\n readonly nodeName?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * A checkpoint paired with its sidecar metadata. Returned by\n * `CheckpointStore.getTuple(...)` and the `list(...)` iterator.\n *\n * @stable\n */\nexport interface CheckpointTuple {\n readonly checkpoint: Checkpoint;\n readonly metadata: CheckpointMetadata;\n readonly pendingWrites?: ReadonlyArray<PendingWrite>;\n}\n\n/**\n * Per-task pending write. Captured when a task in an execution step\n * succeeds while a sibling task fails: the next resume attempt skips the\n * already-completed work.\n *\n * @stable\n */\nexport interface PendingWrite {\n readonly taskId: string;\n readonly index: number;\n readonly channel: string;\n /** Serialized value blob - adapter-specific encoding. */\n readonly value: unknown;\n}\n\n/**\n * Optional listing range for `CheckpointStore.list(...)`.\n *\n * @stable\n */\nexport interface ListOptions {\n readonly limit?: number;\n readonly before?: CheckpointId;\n readonly status?: CheckpointMetadata['status'];\n}\n\n/**\n * Optional atomicity contract for {@link CheckpointStore.put} (D1 /\n * workflow-01). When `expectedLatestId` is supplied, the store MUST\n * perform the latest-checkpoint comparison and the insert atomically\n * (single transaction / synchronous critical section) and throw\n * {@link CheckpointConflictError} on mismatch - closing the TOCTOU\n * window an engine-level read-then-write cannot. `null` means \"expect\n * no checkpoint for this thread yet\"; `undefined` (or a store that\n * ignores the argument) preserves the unguarded legacy behaviour, which\n * the engine backstops with its own pre-check.\n *\n * @stable\n */\nexport interface CheckpointPutOptions {\n readonly expectedLatestId?: CheckpointId | null;\n}\n\n/**\n * Thrown by a {@link CheckpointStore.put} honouring\n * {@link CheckpointPutOptions.expectedLatestId} when another writer\n * advanced the thread in between. The workflow engine maps it to its\n * `checkpoint-version-conflict` error.\n *\n * @stable\n */\nexport class CheckpointConflictError extends Error {\n readonly threadId: string;\n readonly expectedLatestId: CheckpointId | null;\n readonly actualLatestId: CheckpointId | null;\n\n constructor(threadId: string, expected: CheckpointId | null, actual: CheckpointId | null) {\n super(\n `checkpoint conflict on thread \"${threadId}\": expected latest ${expected ?? '<none>'}, found ${actual ?? '<none>'}`,\n );\n this.name = 'CheckpointConflictError';\n this.threadId = threadId;\n this.expectedLatestId = expected;\n this.actualLatestId = actual;\n }\n}\n\n/**\n * Pluggable checkpoint storage interface. The default implementation\n * lives in `@graphorin/store-sqlite`.\n *\n * @stable\n */\nexport interface CheckpointStore {\n put(\n threadId: string,\n namespace: string,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n opts?: CheckpointPutOptions,\n ): Promise<CheckpointId>;\n\n putWrites(\n threadId: string,\n namespace: string,\n checkpointId: CheckpointId,\n writes: ReadonlyArray<PendingWrite>,\n taskId: string,\n ): Promise<void>;\n\n getTuple(\n threadId: string,\n namespace: string,\n checkpointId?: CheckpointId,\n ): Promise<CheckpointTuple | null>;\n\n list(threadId: string, namespace: string, opts?: ListOptions): AsyncIterable<CheckpointTuple>;\n\n deleteThread(threadId: string): Promise<void>;\n}\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"checkpoint-store.js","names":[],"sources":["../../src/contracts/checkpoint-store.ts"],"sourcesContent":["/**\n * Opaque identifier for a single workflow checkpoint. Treated as a string\n * by every consumer so adapters can pick whatever encoding they prefer\n * (ULID, UUID, snowflake-like, …).\n *\n * @stable\n */\nexport type CheckpointId = string;\n\n/**\n * Serialized snapshot of workflow state, written after every execution\n * step.\n *\n * @stable\n */\nexport interface Checkpoint {\n readonly id: CheckpointId;\n readonly threadId: string;\n readonly namespace: string;\n readonly parentId?: CheckpointId;\n /** Serialized state blob - adapter-specific encoding (JSON / superjson / …). */\n readonly state: unknown;\n /** Per-channel monotonic versions used by the workflow scheduler. */\n readonly channelVersions: Readonly<Record<string, number>>;\n readonly stepNumber: number;\n readonly createdAt: string;\n}\n\n/**\n * Metadata associated with a checkpoint write. Adapters store this in a\n * sidecar table for efficient listing.\n *\n * @stable\n */\nexport interface CheckpointMetadata {\n /**\n * Durability mode that produced this write. The legacy `'async'`\n * value was removed (workflow-14 / WF-7 - it was byte-identical to\n * `'sync'`); adapters normalize legacy persisted rows to `'sync'` at\n * read time.\n */\n readonly source: 'sync' | 'exit';\n readonly status: 'running' | 'suspended' | 'completed' | 'failed' | 'aborted';\n readonly nodeName?: string;\n readonly tags?: ReadonlyArray<string>;\n /**\n * Session this checkpoint's state belongs to, when known (W-005).\n * The agent runtime stamps it on every HITL-suspend write so a\n * session hard-delete can cascade into `workflow_checkpoints` /\n * `workflow_pending_writes` without parsing the opaque state blob.\n * Optional and additive: third-party stores may ignore it, but any\n * store that also implements `SessionStoreExt.deleteSession` should\n * use it to honour the full erasure contract.\n */\n readonly sessionId?: string;\n /**\n * W-032: earliest due durable timer among this checkpoint's frontier\n * pauses (epoch ms). The workflow engine stamps it on every\n * `suspended` write whose frontier holds a timer, so a store's\n * {@link CheckpointStore.listSuspended} can enumerate due threads\n * without parsing the opaque tags. Absent on non-timer suspends and\n * on checkpoints written before the field existed - such threads\n * need one manual `tick` (or a resume) to become driver-visible.\n */\n readonly wakeAt?: number;\n}\n\n/**\n * A checkpoint paired with its sidecar metadata. Returned by\n * `CheckpointStore.getTuple(...)` and the `list(...)` iterator.\n *\n * @stable\n */\nexport interface CheckpointTuple {\n readonly checkpoint: Checkpoint;\n readonly metadata: CheckpointMetadata;\n readonly pendingWrites?: ReadonlyArray<PendingWrite>;\n}\n\n/**\n * Per-task pending write. Captured when a task in an execution step\n * succeeds while a sibling task fails: the next resume attempt skips the\n * already-completed work.\n *\n * @stable\n */\nexport interface PendingWrite {\n readonly taskId: string;\n readonly index: number;\n readonly channel: string;\n /** Serialized value blob - adapter-specific encoding. */\n readonly value: unknown;\n}\n\n/**\n * Optional listing range for `CheckpointStore.list(...)`.\n *\n * @stable\n */\nexport interface ListOptions {\n readonly limit?: number;\n readonly before?: CheckpointId;\n readonly status?: CheckpointMetadata['status'];\n}\n\n/**\n * Optional atomicity contract for {@link CheckpointStore.put} (D1 /\n * workflow-01). When `expectedLatestId` is supplied, the store MUST\n * perform the latest-checkpoint comparison and the insert atomically\n * (single transaction / synchronous critical section) and throw\n * {@link CheckpointConflictError} on mismatch - closing the TOCTOU\n * window an engine-level read-then-write cannot. `null` means \"expect\n * no checkpoint for this thread yet\"; `undefined` (or a store that\n * ignores the argument) preserves the unguarded legacy behaviour, which\n * the engine backstops with its own pre-check.\n *\n * @stable\n */\nexport interface CheckpointPutOptions {\n readonly expectedLatestId?: CheckpointId | null;\n}\n\n/**\n * Thrown by a {@link CheckpointStore.put} honouring\n * {@link CheckpointPutOptions.expectedLatestId} when another writer\n * advanced the thread in between. The workflow engine maps it to its\n * `checkpoint-version-conflict` error.\n *\n * @stable\n */\nexport class CheckpointConflictError extends Error {\n readonly threadId: string;\n readonly expectedLatestId: CheckpointId | null;\n readonly actualLatestId: CheckpointId | null;\n\n constructor(threadId: string, expected: CheckpointId | null, actual: CheckpointId | null) {\n super(\n `checkpoint conflict on thread \"${threadId}\": expected latest ${expected ?? '<none>'}, found ${actual ?? '<none>'}`,\n );\n this.name = 'CheckpointConflictError';\n this.threadId = threadId;\n this.expectedLatestId = expected;\n this.actualLatestId = actual;\n }\n}\n\n/**\n * Pluggable checkpoint storage interface. The default implementation\n * lives in `@graphorin/store-sqlite`.\n *\n * @stable\n */\nexport interface CheckpointStore {\n put(\n threadId: string,\n namespace: string,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata,\n opts?: CheckpointPutOptions,\n ): Promise<CheckpointId>;\n\n putWrites(\n threadId: string,\n namespace: string,\n checkpointId: CheckpointId,\n writes: ReadonlyArray<PendingWrite>,\n taskId: string,\n ): Promise<void>;\n\n getTuple(\n threadId: string,\n namespace: string,\n checkpointId?: CheckpointId,\n ): Promise<CheckpointTuple | null>;\n\n list(threadId: string, namespace: string, opts?: ListOptions): AsyncIterable<CheckpointTuple>;\n\n /**\n * Full erasure primitive: delete every checkpoint and pending write of\n * this thread across ALL namespaces. Namespace-blind by contract -\n * retention sweeps must use {@link CheckpointStoreExt.pruneThreads}\n * instead, which is namespace-scoped and protects suspended threads.\n */\n deleteThread(threadId: string): Promise<void>;\n\n /**\n * W-032: enumerate threads whose LATEST checkpoint in `namespace` is\n * `suspended` with a due `wakeAt` (`<= opts.dueBefore`, default: any\n * stamped wakeAt). This is what a durable-timer driver polls -\n * without it an operator would have to keep an external registry of\n * sleeping threadIds. OPTIONAL so third-party stores compile\n * unchanged; `createTimerDriver` throws a typed error when the store\n * lacks it (deterministic policy, no silent no-op).\n */\n listSuspended?(\n namespace: string,\n opts?: { readonly dueBefore?: number; readonly limit?: number },\n ): Promise<ReadonlyArray<{ readonly threadId: string; readonly wakeAt: number }>>;\n}\n\n/**\n * Options for {@link CheckpointStoreExt.pruneThreads}.\n *\n * @stable\n */\nexport interface PruneThreadsOptions {\n /**\n * Cutoff: a `(threadId, namespace)` pair qualifies when its LATEST\n * checkpoint (by `stepNumber`) was created before this epoch-ms\n * instant.\n */\n readonly beforeEpochMs: number;\n /**\n * When `true` (the default), only pairs whose latest checkpoint has a\n * terminal status (`completed` / `failed` / `aborted`) are pruned -\n * suspended threads hold live HITL approvals / awakeables and must\n * survive a retention sweep. Set to `false` for a hard age-based\n * sweep that also removes suspended threads.\n */\n readonly onlyTerminal?: boolean;\n}\n\n/**\n * Retention extension over {@link CheckpointStore} (W-009). The engine\n * intentionally never deletes finished threads itself - a completed\n * thread is still needed for inspection and duplicate-resume refusal;\n * how long to keep it is an operator decision. These primitives are\n * what an operator (or a host scheduler) drives.\n *\n * Additive: third-party `CheckpointStore` implementations compile\n * unchanged; hosts feature-detect with `'pruneThreads' in store`.\n *\n * @stable\n */\nexport interface CheckpointStoreExt extends CheckpointStore {\n /**\n * Namespace-scoped retention sweep: for every `(threadId, namespace)`\n * pair matching the policy, delete that pair's checkpoints and pending\n * writes - and ONLY that pair's. Never implemented via\n * {@link CheckpointStore.deleteThread} (namespace-blind): with a\n * reused threadId, pruning a terminal thread of workflow A must not\n * erase the suspended checkpoints of workflow B. Returns the number\n * of pairs pruned.\n */\n pruneThreads(opts: PruneThreadsOptions): Promise<number>;\n\n /**\n * Keep only the `keepLast` most recent checkpoints (by `stepNumber`)\n * of one `(threadId, namespace)` pair, deleting older ones together\n * with their pending writes. `keepLast >= 1`; resume works from the\n * latest tuple, so compaction never breaks resumability - it does\n * remove time-travel/fork targets. Returns the number of checkpoints\n * deleted.\n */\n compactThread(threadId: string, namespace: string, keepLast: number): Promise<number>;\n}\n"],"mappings":";;;;;;;;;AAkIA,IAAa,0BAAb,cAA6C,MAAM;CACjD,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,UAAkB,UAA+B,QAA6B;AACxF,QACE,kCAAkC,SAAS,qBAAqB,YAAY,SAAS,UAAU,UAAU,WAC1G;AACD,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,OAAK,mBAAmB;AACxB,OAAK,iBAAiB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { AuthTokenRecord, AuthTokenStore } from "./auth-token-store.js";
|
|
2
|
-
import { Checkpoint, CheckpointConflictError, CheckpointId, CheckpointMetadata, CheckpointPutOptions, CheckpointStore, CheckpointTuple, ListOptions, PendingWrite } from "./checkpoint-store.js";
|
|
2
|
+
import { Checkpoint, CheckpointConflictError, CheckpointId, CheckpointMetadata, CheckpointPutOptions, CheckpointStore, CheckpointStoreExt, CheckpointTuple, ListOptions, PendingWrite, PruneThreadsOptions } from "./checkpoint-store.js";
|
|
3
3
|
import { EmbedOptions, EmbedderProvider } from "./embedder.js";
|
|
4
4
|
import { EvalSample, EvalScore, EvalScorer } from "./eval-scorer.js";
|
|
5
5
|
import { LocalProviderTrust, OllamaTrust } from "./local-provider-trust.js";
|
|
6
6
|
import { LogFields, LogLevel, Logger, NOOP_LOGGER } from "./logger.js";
|
|
7
|
-
import { EpisodicMemoryStore, MemoryStore, MessageRef, ProceduralMemoryStore, SemanticMemoryStore, SessionListOptions, SessionMemoryStore, SessionMessageWithMetadata, SharedMemoryStore, WorkingMemoryStore } from "./memory-store.js";
|
|
7
|
+
import { EpisodicMemoryStore, MemoryStore, MemoryStoreExt, MessageRef, ProceduralMemoryStore, SemanticMemoryStore, SessionListOptions, SessionMemoryStore, SessionMessageWithMetadata, SharedMemoryStore, WorkingMemoryStore } from "./memory-store.js";
|
|
8
8
|
import { OAuthServerRecord, OAuthServerStore } from "./oauth-server-store.js";
|
|
9
9
|
import { MODEL_HINTS, ModelHint, ModelSpec, ProviderLike } from "./preferred-model.js";
|
|
10
|
-
import { AISpan, NOOP_TRACER, SpanAttributeValue, SpanAttributes, SpanStatus, SpanType, StartSpanOptions, Tracer } from "./tracer.js";
|
|
10
|
+
import { AISpan, AddEventOptions, CustomSpanType, KnownSpanType, NOOP_TRACER, SpanAttributeValue, SpanAttributes, SpanStatus, SpanType, StartSpanOptions, Tracer } from "./tracer.js";
|
|
11
11
|
import { ReasoningContract, ReasoningRetention } from "./reasoning-retention.js";
|
|
12
12
|
import { ComposeProviderMiddleware, FinishReason, OutputSpec, Provider, ProviderCachePolicy, ProviderCapabilities, ProviderError, ProviderErrorKind, ProviderEvent, ProviderMiddleware, ProviderRequest, ProviderRequestMetadata, ProviderResponse, ResponseMetadata, ToolChoice, ToolDefinition, ToolDefinitionExample } from "./provider.js";
|
|
13
13
|
import { RedactionInput, RedactionOutput, RedactionValidator } from "./redaction-validator.js";
|
|
@@ -17,6 +17,6 @@ import { NODEJS_INSPECT_CUSTOM, SECRET_VALUE_BRAND, SecretValue, SecretValueOpti
|
|
|
17
17
|
import { SecretMetadata, SecretResolver, SecretResolverContext, SecretsSetOptions, SecretsStore } from "./secrets-store.js";
|
|
18
18
|
import { AgentRegistryEntry, SessionAuditEntry, SessionMetadata, SessionStore, SessionStoreExt, SessionWorkflowRun } from "./session-store.js";
|
|
19
19
|
import { TokenCounter } from "./token-counter.js";
|
|
20
|
-
import { ResolvedTool, Tool, ToolExample, ToolExecutionContext, ToolReturn, ToolSecretsAccessor } from "./tool.js";
|
|
20
|
+
import { AnyTool, ResolvedTool, TOOL_RETURN_BRAND, Tool, ToolExample, ToolExecutionContext, ToolReturn, ToolSecretsAccessor, isToolReturnEnvelope, isUnbrandedToolReturn, toolReturn } from "./tool.js";
|
|
21
21
|
import { TriggerState, TriggerStore } from "./trigger-store.js";
|
|
22
|
-
export { type AISpan, type AgentRegistryEntry, type AuthTokenRecord, type AuthTokenStore, type Checkpoint, CheckpointConflictError, type CheckpointId, type CheckpointMetadata, type CheckpointPutOptions, type CheckpointStore, type CheckpointTuple, type ComposeProviderMiddleware, type EmbedOptions, type EmbedderProvider, type EpisodicMemoryStore, type EvalSample, type EvalScore, type EvalScorer, type FinishReason, type ListOptions, type LocalProviderTrust, type LogFields, type LogLevel, type Logger, MODEL_HINTS, type MemoryStore, type MessageRef, type ModelHint, type ModelSpec, NODEJS_INSPECT_CUSTOM, NOOP_LOGGER, NOOP_TRACER, type OAuthServerRecord, type OAuthServerStore, type OllamaTrust, type OutputSpec, type PendingWrite, type ProceduralMemoryStore, type Provider, type ProviderCachePolicy, type ProviderCapabilities, type ProviderError, type ProviderErrorKind, type ProviderEvent, type ProviderLike, type ProviderMiddleware, type ProviderRequest, type ProviderRequestMetadata, type ProviderResponse, type ReasoningContract, type ReasoningRetention, type RedactionInput, type RedactionOutput, type RedactionValidator, type ResolvedTool, type ResponseMetadata, SECRET_VALUE_BRAND, type Sandbox, type SandboxCode, type SandboxResult, type SandboxRunOptions, type SecretMetadata, type SecretRef, type SecretResolver, type SecretResolverContext, type SecretValue, type SecretValueOptions, type SecretValueStatic, type SecretsSetOptions, type SecretsStore, type SemanticMemoryStore, type SessionAuditEntry, type SessionListOptions, type SessionMemoryStore, type SessionMessageWithMetadata, type SessionMetadata, type SessionStore, type SessionStoreExt, type SessionWorkflowRun, type SharedMemoryStore, type SpanAttributeValue, type SpanAttributes, type SpanStatus, type SpanType, type StartSpanOptions, type TokenCounter, type Tool, type ToolChoice, type ToolDefinition, type ToolDefinitionExample, type ToolExample, type ToolExecutionContext, type ToolReturn, type ToolSecretsAccessor, type Tracer, type TriggerState, type TriggerStore, type WorkingMemoryStore };
|
|
22
|
+
export { type AISpan, type AddEventOptions, type AgentRegistryEntry, type AnyTool, type AuthTokenRecord, type AuthTokenStore, type Checkpoint, CheckpointConflictError, type CheckpointId, type CheckpointMetadata, type CheckpointPutOptions, type CheckpointStore, type CheckpointStoreExt, type CheckpointTuple, type ComposeProviderMiddleware, type CustomSpanType, type EmbedOptions, type EmbedderProvider, type EpisodicMemoryStore, type EvalSample, type EvalScore, type EvalScorer, type FinishReason, type KnownSpanType, type ListOptions, type LocalProviderTrust, type LogFields, type LogLevel, type Logger, MODEL_HINTS, type MemoryStore, type MemoryStoreExt, type MessageRef, type ModelHint, type ModelSpec, NODEJS_INSPECT_CUSTOM, NOOP_LOGGER, NOOP_TRACER, type OAuthServerRecord, type OAuthServerStore, type OllamaTrust, type OutputSpec, type PendingWrite, type ProceduralMemoryStore, type Provider, type ProviderCachePolicy, type ProviderCapabilities, type ProviderError, type ProviderErrorKind, type ProviderEvent, type ProviderLike, type ProviderMiddleware, type ProviderRequest, type ProviderRequestMetadata, type ProviderResponse, type PruneThreadsOptions, type ReasoningContract, type ReasoningRetention, type RedactionInput, type RedactionOutput, type RedactionValidator, type ResolvedTool, type ResponseMetadata, SECRET_VALUE_BRAND, type Sandbox, type SandboxCode, type SandboxResult, type SandboxRunOptions, type SecretMetadata, type SecretRef, type SecretResolver, type SecretResolverContext, type SecretValue, type SecretValueOptions, type SecretValueStatic, type SecretsSetOptions, type SecretsStore, type SemanticMemoryStore, type SessionAuditEntry, type SessionListOptions, type SessionMemoryStore, type SessionMessageWithMetadata, type SessionMetadata, type SessionStore, type SessionStoreExt, type SessionWorkflowRun, type SharedMemoryStore, type SpanAttributeValue, type SpanAttributes, type SpanStatus, type SpanType, type StartSpanOptions, TOOL_RETURN_BRAND, type TokenCounter, type Tool, type ToolChoice, type ToolDefinition, type ToolDefinitionExample, type ToolExample, type ToolExecutionContext, type ToolReturn, type ToolSecretsAccessor, type Tracer, type TriggerState, type TriggerStore, type WorkingMemoryStore, isToolReturnEnvelope, isUnbrandedToolReturn, toolReturn };
|
package/dist/contracts/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CheckpointConflictError } from "./checkpoint-store.js";
|
|
|
2
2
|
import { NOOP_LOGGER } from "./logger.js";
|
|
3
3
|
import { MODEL_HINTS } from "./preferred-model.js";
|
|
4
4
|
import { NODEJS_INSPECT_CUSTOM, SECRET_VALUE_BRAND } from "./secret-value.js";
|
|
5
|
+
import { TOOL_RETURN_BRAND, isToolReturnEnvelope, isUnbrandedToolReturn, toolReturn } from "./tool.js";
|
|
5
6
|
import { NOOP_TRACER } from "./tracer.js";
|
|
6
7
|
|
|
7
|
-
export { CheckpointConflictError, MODEL_HINTS, NODEJS_INSPECT_CUSTOM, NOOP_LOGGER, NOOP_TRACER, SECRET_VALUE_BRAND };
|
|
8
|
+
export { CheckpointConflictError, MODEL_HINTS, NODEJS_INSPECT_CUSTOM, NOOP_LOGGER, NOOP_TRACER, SECRET_VALUE_BRAND, TOOL_RETURN_BRAND, isToolReturnEnvelope, isUnbrandedToolReturn, toolReturn };
|