@nanobpm/agentic 0.1.0 → 0.4.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/README.md +1 -0
- package/dist/demand/model.d.ts +7 -4
- package/dist/demand/model.js +22 -4
- package/dist/demand/taskdef.d.ts +13 -1
- package/dist/demand/taskdef.js +20 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/protocol/conformance/frames.js +32 -4
- package/dist/protocol/index.d.ts +1 -1
- package/dist/protocol/payloads.d.ts +44 -0
- package/dist/protocol/payloads.js +61 -7
- package/dist/session/acp/client.d.ts +109 -0
- package/dist/session/acp/client.js +254 -0
- package/dist/session/acp/index.d.ts +27 -0
- package/dist/session/acp/index.js +27 -0
- package/dist/session/acp/jsonrpc.d.ts +25 -0
- package/dist/session/acp/jsonrpc.js +148 -0
- package/dist/session/acp/normalize.d.ts +48 -0
- package/dist/session/acp/normalize.js +162 -0
- package/dist/session/acp/protocol.d.ts +94 -0
- package/dist/session/acp/protocol.js +136 -0
- package/dist/session/acp/spawn.d.ts +36 -0
- package/dist/session/acp/spawn.js +68 -0
- package/dist/session/acp/transport.d.ts +62 -0
- package/dist/session/acp/transport.js +126 -0
- package/dist/session/adapter.d.ts +135 -0
- package/dist/session/adapter.js +24 -0
- package/dist/session/backend.d.ts +43 -0
- package/dist/session/backend.js +95 -0
- package/dist/session/events.d.ts +152 -0
- package/dist/session/events.js +192 -0
- package/dist/session/index.d.ts +31 -0
- package/dist/session/index.js +5 -0
- package/dist/session/log.d.ts +107 -0
- package/dist/session/log.js +351 -0
- package/dist/session/normalizer/claude.d.ts +23 -0
- package/dist/session/normalizer/claude.js +138 -0
- package/dist/session/normalizer/copilot.d.ts +27 -0
- package/dist/session/normalizer/copilot.js +105 -0
- package/dist/session/normalizer/deepseek.d.ts +11 -0
- package/dist/session/normalizer/deepseek.js +68 -0
- package/dist/session/normalizer/index.d.ts +36 -0
- package/dist/session/normalizer/index.js +29 -0
- package/dist/session/normalizer/kimi.d.ts +10 -0
- package/dist/session/normalizer/kimi.js +80 -0
- package/dist/session/normalizer/link.d.ts +36 -0
- package/dist/session/normalizer/link.js +56 -0
- package/dist/session/normalizer/pi.d.ts +13 -0
- package/dist/session/normalizer/pi.js +61 -0
- package/dist/session/normalizer/qwen.d.ts +11 -0
- package/dist/session/normalizer/qwen.js +65 -0
- package/dist/session/normalizer/record.d.ts +21 -0
- package/dist/session/normalizer/record.js +87 -0
- package/dist/session/normalizer/types.d.ts +139 -0
- package/dist/session/normalizer/types.js +31 -0
- package/dist/session/schema.d.ts +38 -0
- package/dist/session/schema.js +74 -0
- package/package.json +17 -1
- package/src/demand/model.test.ts +82 -4
- package/src/demand/model.ts +30 -9
- package/src/demand/taskdef.test.ts +51 -6
- package/src/demand/taskdef.ts +31 -2
- package/src/index.ts +1 -0
- package/src/protocol/conformance/frames.ts +32 -4
- package/src/protocol/index.ts +4 -0
- package/src/protocol/payloads.test.ts +31 -1
- package/src/protocol/payloads.ts +110 -7
- package/src/session/acp/client.test.ts +222 -0
- package/src/session/acp/client.ts +356 -0
- package/src/session/acp/fake-agent.ts +71 -0
- package/src/session/acp/index.ts +68 -0
- package/src/session/acp/integration.test.ts +37 -0
- package/src/session/acp/jsonrpc.test.ts +75 -0
- package/src/session/acp/jsonrpc.ts +171 -0
- package/src/session/acp/normalize.test.ts +150 -0
- package/src/session/acp/normalize.ts +204 -0
- package/src/session/acp/protocol.ts +178 -0
- package/src/session/acp/spawn.test.ts +45 -0
- package/src/session/acp/spawn.ts +91 -0
- package/src/session/acp/transport.test.ts +82 -0
- package/src/session/acp/transport.ts +155 -0
- package/src/session/adapter.ts +159 -0
- package/src/session/backend.test.ts +198 -0
- package/src/session/backend.ts +128 -0
- package/src/session/events.test.ts +168 -0
- package/src/session/events.ts +347 -0
- package/src/session/index.ts +67 -0
- package/src/session/log.test.ts +215 -0
- package/src/session/log.ts +525 -0
- package/src/session/normalizer/backend-integration.test.ts +103 -0
- package/src/session/normalizer/claude.test.ts +68 -0
- package/src/session/normalizer/claude.ts +136 -0
- package/src/session/normalizer/copilot.test.ts +59 -0
- package/src/session/normalizer/copilot.ts +133 -0
- package/src/session/normalizer/deepseek.ts +80 -0
- package/src/session/normalizer/index.ts +61 -0
- package/src/session/normalizer/kimi.ts +82 -0
- package/src/session/normalizer/link.test.ts +24 -0
- package/src/session/normalizer/link.ts +81 -0
- package/src/session/normalizer/pi.ts +75 -0
- package/src/session/normalizer/probe.test.ts +49 -0
- package/src/session/normalizer/qwen.test.ts +20 -0
- package/src/session/normalizer/qwen.ts +77 -0
- package/src/session/normalizer/record.test.ts +68 -0
- package/src/session/normalizer/record.ts +88 -0
- package/src/session/normalizer/resume.test.ts +25 -0
- package/src/session/normalizer/types.ts +152 -0
- package/src/session/normalizer/vectors.test.ts +180 -0
- package/src/session/schema.test.ts +84 -0
- package/src/session/schema.ts +78 -0
- package/src/session/test-db.ts +56 -0
- package/dist/blackboard/test-db.d.ts +0 -5
- package/dist/blackboard/test-db.js +0 -42
- package/dist/presence/test-db.d.ts +0 -5
- package/dist/presence/test-db.js +0 -42
- package/dist/transcript/test-db.d.ts +0 -5
- package/dist/transcript/test-db.js +0 -41
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical `SessionEvent` model — ADR 0062, slice 1 (the shared contract).
|
|
3
|
+
*
|
|
4
|
+
* This is **Nano's own** agent-session event model: the single schema every
|
|
5
|
+
* harness dialect (ACP, stream-json, a native normalizer, …) normalizes *into*.
|
|
6
|
+
* We never adopt an external harness schema as ours — those are ingestion
|
|
7
|
+
* details owned by the later slices; this union is the stable interface they all
|
|
8
|
+
* target.
|
|
9
|
+
*
|
|
10
|
+
* ## The causal chain
|
|
11
|
+
*
|
|
12
|
+
* A session is an append-only log of events. Two orthogonal orderings make the
|
|
13
|
+
* log both replayable and mergeable:
|
|
14
|
+
*
|
|
15
|
+
* - a **monotonic, gap-free `offset`** assigned by the authoritative log on
|
|
16
|
+
* append (see {@link AppendedSessionEvent}); it is the resume coordinate —
|
|
17
|
+
* `restore` hands back everything up to a checkpoint offset.
|
|
18
|
+
* - a **causal `parentId`** the producer stamps: the id of the event this one
|
|
19
|
+
* logically follows (`null` for the first event of a session). Offset gives a
|
|
20
|
+
* total order for replay; `parentId` records the *causal* edge, which survives
|
|
21
|
+
* a compaction that rewrites offsets.
|
|
22
|
+
*
|
|
23
|
+
* The producer owns identity (`id`) and causality (`parentId`); the log owns
|
|
24
|
+
* ordering (`offset`) and fencing (`incarnation`). Keeping those responsibilities
|
|
25
|
+
* split is what lets a resumed incarnation continue the same causal chain at a
|
|
26
|
+
* fresh offset without the producer knowing the log's internal cursor.
|
|
27
|
+
*/
|
|
28
|
+
/** The set of valid event types, for a runtime membership check at the DB boundary. */
|
|
29
|
+
export const SESSION_EVENT_TYPES = [
|
|
30
|
+
"system",
|
|
31
|
+
"user",
|
|
32
|
+
"assistant",
|
|
33
|
+
"reasoning",
|
|
34
|
+
"tool-call",
|
|
35
|
+
"tool-result",
|
|
36
|
+
"compaction",
|
|
37
|
+
"usage",
|
|
38
|
+
"turn-start",
|
|
39
|
+
"turn-end",
|
|
40
|
+
];
|
|
41
|
+
/** Raised when a value read back from storage is not a well-formed session event. */
|
|
42
|
+
export class SessionEventShapeError extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "SessionEventShapeError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function isRecord(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
function reqString(obj, field) {
|
|
52
|
+
const v = obj[field];
|
|
53
|
+
if (typeof v !== "string") {
|
|
54
|
+
throw new SessionEventShapeError(`session event field "${field}" must be a string, got ${typeof v}`);
|
|
55
|
+
}
|
|
56
|
+
return v;
|
|
57
|
+
}
|
|
58
|
+
function optString(obj, field) {
|
|
59
|
+
const v = obj[field];
|
|
60
|
+
if (v === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (typeof v !== "string") {
|
|
63
|
+
throw new SessionEventShapeError(`session event field "${field}" must be a string when present, got ${typeof v}`);
|
|
64
|
+
}
|
|
65
|
+
return v;
|
|
66
|
+
}
|
|
67
|
+
function reqNonNegInt(obj, field) {
|
|
68
|
+
const v = obj[field];
|
|
69
|
+
if (typeof v !== "number" || !Number.isSafeInteger(v) || v < 0) {
|
|
70
|
+
throw new SessionEventShapeError(`session event field "${field}" must be a non-negative safe integer, got ${String(v)}`);
|
|
71
|
+
}
|
|
72
|
+
return v;
|
|
73
|
+
}
|
|
74
|
+
function reqBool(obj, field) {
|
|
75
|
+
const v = obj[field];
|
|
76
|
+
if (typeof v !== "boolean") {
|
|
77
|
+
throw new SessionEventShapeError(`session event field "${field}" must be a boolean, got ${typeof v}`);
|
|
78
|
+
}
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
function parentId(obj) {
|
|
82
|
+
const v = obj.parentId;
|
|
83
|
+
if (v === null)
|
|
84
|
+
return null;
|
|
85
|
+
if (typeof v === "string")
|
|
86
|
+
return v;
|
|
87
|
+
throw new SessionEventShapeError(`session event "parentId" must be a string or null, got ${typeof v}`);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Coerce a required opaque payload (a tool-call `args` / tool-result `result`) to
|
|
91
|
+
* a JSON-serialisable value. These fields are documented as opaque *JSON-
|
|
92
|
+
* serialisable* values, but a dialect can legitimately omit them (e.g. a tool
|
|
93
|
+
* call with no arguments surfaces as `obj.arguments ?? obj.args === undefined`).
|
|
94
|
+
* `undefined` is not JSON-serialisable — `JSON.stringify` drops the key — so an
|
|
95
|
+
* un-normalised `undefined` would persist an event that no longer round-trips to
|
|
96
|
+
* the same shape on replay. We normalise the absence to the canonical JSON "no
|
|
97
|
+
* value" (`null`) here, at the single boundary every dialect flows through, so
|
|
98
|
+
* they all get the same replay-stable guarantee (derivation over duplication).
|
|
99
|
+
*/
|
|
100
|
+
function opaquePayload(value) {
|
|
101
|
+
return value === undefined ? null : value;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Parse and validate an untyped value (e.g. `JSON.parse` of a stored row) into a
|
|
105
|
+
* {@link SessionEvent}, reconstructing the exact union member for its `type`.
|
|
106
|
+
* Throws {@link SessionEventShapeError} on any malformed field. This is the
|
|
107
|
+
* single trusted boundary between untyped storage and the typed union — it never
|
|
108
|
+
* uses an `as`-cast to fabricate a shape (see AGENTS.md), it *builds* one field
|
|
109
|
+
* by field, so a corrupt row fails loudly instead of masquerading as valid.
|
|
110
|
+
*/
|
|
111
|
+
export function parseSessionEvent(value) {
|
|
112
|
+
if (!isRecord(value)) {
|
|
113
|
+
throw new SessionEventShapeError(`session event must be an object, got ${typeof value}`);
|
|
114
|
+
}
|
|
115
|
+
const id = reqString(value, "id");
|
|
116
|
+
const parent = parentId(value);
|
|
117
|
+
const type = value.type;
|
|
118
|
+
switch (type) {
|
|
119
|
+
case "system":
|
|
120
|
+
return { type, id, parentId: parent, text: reqString(value, "text") };
|
|
121
|
+
case "user":
|
|
122
|
+
return { type, id, parentId: parent, text: reqString(value, "text") };
|
|
123
|
+
case "assistant":
|
|
124
|
+
return { type, id, parentId: parent, text: reqString(value, "text") };
|
|
125
|
+
case "reasoning": {
|
|
126
|
+
const event = { type, id, parentId: parent };
|
|
127
|
+
const text = optString(value, "text");
|
|
128
|
+
const cont = optString(value, "providerContinuation");
|
|
129
|
+
return {
|
|
130
|
+
...event,
|
|
131
|
+
...(text !== undefined ? { text } : {}),
|
|
132
|
+
...(cont !== undefined ? { providerContinuation: cont } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
case "tool-call":
|
|
136
|
+
return {
|
|
137
|
+
type,
|
|
138
|
+
id,
|
|
139
|
+
parentId: parent,
|
|
140
|
+
callId: reqString(value, "callId"),
|
|
141
|
+
name: reqString(value, "name"),
|
|
142
|
+
args: opaquePayload(value.args),
|
|
143
|
+
};
|
|
144
|
+
case "tool-result":
|
|
145
|
+
return {
|
|
146
|
+
type,
|
|
147
|
+
id,
|
|
148
|
+
parentId: parent,
|
|
149
|
+
callId: reqString(value, "callId"),
|
|
150
|
+
ok: reqBool(value, "ok"),
|
|
151
|
+
result: opaquePayload(value.result),
|
|
152
|
+
};
|
|
153
|
+
case "compaction": {
|
|
154
|
+
const reason = value.reason;
|
|
155
|
+
if (reason !== "compaction" && reason !== "truncation") {
|
|
156
|
+
throw new SessionEventShapeError(`compaction "reason" must be "compaction" or "truncation", got ${String(reason)}`);
|
|
157
|
+
}
|
|
158
|
+
const summary = optString(value, "summary");
|
|
159
|
+
const replacesFrom = reqNonNegInt(value, "replacesFrom");
|
|
160
|
+
const replacesTo = reqNonNegInt(value, "replacesTo");
|
|
161
|
+
if (replacesTo < replacesFrom) {
|
|
162
|
+
throw new SessionEventShapeError(`compaction "replacesTo" (${replacesTo}) must be >= "replacesFrom" (${replacesFrom})`);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
type,
|
|
166
|
+
id,
|
|
167
|
+
parentId: parent,
|
|
168
|
+
reason,
|
|
169
|
+
replacesFrom,
|
|
170
|
+
replacesTo,
|
|
171
|
+
...(summary !== undefined ? { summary } : {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
case "usage": {
|
|
175
|
+
const model = optString(value, "model");
|
|
176
|
+
return {
|
|
177
|
+
type,
|
|
178
|
+
id,
|
|
179
|
+
parentId: parent,
|
|
180
|
+
inputTokens: reqNonNegInt(value, "inputTokens"),
|
|
181
|
+
outputTokens: reqNonNegInt(value, "outputTokens"),
|
|
182
|
+
...(model !== undefined ? { model } : {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
case "turn-start":
|
|
186
|
+
return { type, id, parentId: parent, turn: reqNonNegInt(value, "turn") };
|
|
187
|
+
case "turn-end":
|
|
188
|
+
return { type, id, parentId: parent, turn: reqNonNegInt(value, "turn") };
|
|
189
|
+
default:
|
|
190
|
+
throw new SessionEventShapeError(`unknown session event type: ${JSON.stringify(type)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nanobpm/agentic/session` — the canonical agent-session contract (ADR 0062,
|
|
3
|
+
* slice 1). The foundation the two ingestion backends (ACP, normalisers) and the
|
|
4
|
+
* nano-workforce world-restore all code against in parallel.
|
|
5
|
+
*
|
|
6
|
+
* Exports, in the order a consumer meets them:
|
|
7
|
+
* - the canonical {@link SessionEvent} union every harness dialect normalises
|
|
8
|
+
* into, plus {@link parseSessionEvent} (the untyped-storage boundary);
|
|
9
|
+
* - the three-method {@link SessionAdapter} interface (emit/checkpoint/restore)
|
|
10
|
+
* with its {@link ActivationKey}, {@link SessionCheckpoint} and
|
|
11
|
+
* {@link SessionSeed} types;
|
|
12
|
+
* - the {@link SessionLog} port and its two backends — the in-memory reference
|
|
13
|
+
* ({@link InMemorySessionLog}, the stub) and the durable, authoritative
|
|
14
|
+
* {@link SqliteSessionLog} that promotes the ADR 0056 §12 relay ring + fence;
|
|
15
|
+
* - {@link SessionBackend} (the one adapter implementation) and the
|
|
16
|
+
* {@link openInMemorySession} / {@link openSqliteSession} factories.
|
|
17
|
+
*
|
|
18
|
+
* The new DB schema ships as the forward-only migration
|
|
19
|
+
* `db/migrations/005_agentic_session.sql`, mirrored by {@link SESSION_SCHEMA_SQL}
|
|
20
|
+
* and kept in lockstep by a drift-guard test. Nothing here rides the Camunda-8
|
|
21
|
+
* engine — the log is app-tier (ADR 0056 boundary preserved).
|
|
22
|
+
*/
|
|
23
|
+
export type { AppendedSessionEvent, AssistantMessageEvent, CompactionEvent, ReasoningEvent, SessionEvent, SessionEventEnvelope, SessionEventType, SystemMessageEvent, ToolCallEvent, ToolResultEvent, TurnEndEvent, TurnStartEvent, UsageEvent, UserMessageEvent, } from "./events.ts";
|
|
24
|
+
export { parseSessionEvent, SESSION_EVENT_TYPES, SessionEventShapeError } from "./events.ts";
|
|
25
|
+
export type { ActivationKey, EffectEntry, EffectLedger, SessionAdapter, SessionCheckpoint, SessionSeed, } from "./adapter.ts";
|
|
26
|
+
export { activationKeyString, StaleIncarnationError } from "./adapter.ts";
|
|
27
|
+
export type { Clock, SessionLog, SqliteDb } from "./log.ts";
|
|
28
|
+
export { InMemorySessionLog, SessionLogCorruptionError, SqliteSessionLog, systemClock, } from "./log.ts";
|
|
29
|
+
export type { SessionBackendOptions } from "./backend.ts";
|
|
30
|
+
export { openInMemorySession, openSqliteSession, SessionBackend } from "./backend.ts";
|
|
31
|
+
export { SESSION_CHECKPOINT_TABLE, SESSION_EVENT_TABLE, SESSION_LOG_TABLE, SESSION_SCHEMA_SQL, } from "./schema.ts";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { parseSessionEvent, SESSION_EVENT_TYPES, SessionEventShapeError } from "./events.js";
|
|
2
|
+
export { activationKeyString, StaleIncarnationError } from "./adapter.js";
|
|
3
|
+
export { InMemorySessionLog, SessionLogCorruptionError, SqliteSessionLog, systemClock, } from "./log.js";
|
|
4
|
+
export { openInMemorySession, openSqliteSession, SessionBackend } from "./backend.js";
|
|
5
|
+
export { SESSION_CHECKPOINT_TABLE, SESSION_EVENT_TABLE, SESSION_LOG_TABLE, SESSION_SCHEMA_SQL, } from "./schema.js";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type ActivationKey, type SessionCheckpoint } from "./adapter.ts";
|
|
2
|
+
import { type AppendedSessionEvent, type SessionEvent } from "./events.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The authoritative-log port the {@link SessionBackend} adapter writes through.
|
|
5
|
+
* Two backends implement it: {@link InMemorySessionLog} (the reference/stub) and
|
|
6
|
+
* {@link SqliteSessionLog} (durable, over the app DataLayer). All writes are
|
|
7
|
+
* fenced by `incarnation`; a stale writer throws {@link StaleIncarnationError}.
|
|
8
|
+
*/
|
|
9
|
+
export interface SessionLog {
|
|
10
|
+
/**
|
|
11
|
+
* Take (or renew) the lease for `key` at `incarnation`, advancing the fence
|
|
12
|
+
* high-water mark. Throws {@link StaleIncarnationError} if a newer incarnation
|
|
13
|
+
* already owns the activation. Called once when an adapter is constructed so a
|
|
14
|
+
* re-lease fences prior incarnations immediately, before any write.
|
|
15
|
+
*/
|
|
16
|
+
lease(key: ActivationKey, incarnation: number): void;
|
|
17
|
+
/** The current (highest leased) incarnation for `key`, or `undefined`. */
|
|
18
|
+
currentIncarnation(key: ActivationKey): number | undefined;
|
|
19
|
+
/** The offset the next appended event will occupy (also the event count). */
|
|
20
|
+
nextOffset(key: ActivationKey): number;
|
|
21
|
+
/**
|
|
22
|
+
* Append `event` at `offset` under `incarnation`, returning it stamped as an
|
|
23
|
+
* {@link AppendedSessionEvent}. Fenced. `offset` must be `<= nextOffset`: at
|
|
24
|
+
* `nextOffset` it extends the log; below it (a resume writing back into the log
|
|
25
|
+
* after `restore`) it first drops the now-superseded uncommitted tail
|
|
26
|
+
* `[offset, nextOffset)` — **and every checkpoint pinned above `offset`**, which
|
|
27
|
+
* would otherwise dangle past the rewritten head and mis-seed a later `restore`
|
|
28
|
+
* (a gap `RangeError` on the next `emit`) — and then writes: an idempotent
|
|
29
|
+
* re-key that keeps the authoritative log gap-free. An `offset > nextOffset` is
|
|
30
|
+
* a gap and throws.
|
|
31
|
+
*/
|
|
32
|
+
append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent;
|
|
33
|
+
/** Persist a checkpoint (fenced by `incarnation`). Returns it unchanged. */
|
|
34
|
+
putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint;
|
|
35
|
+
/** The checkpoint with the highest offset (newest), or `undefined`. */
|
|
36
|
+
latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined;
|
|
37
|
+
/** A specific checkpoint by id, or `undefined`. */
|
|
38
|
+
getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined;
|
|
39
|
+
/** The events with `from <= offset < to`, in offset order. `to` defaults to the head. */
|
|
40
|
+
replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The in-memory reference backend (the stub slices 2–5 code against and the tests
|
|
44
|
+
* exercise). Reuses the relay {@link IncarnationFence} verbatim and keeps each
|
|
45
|
+
* activation's full event array — the authoritative, non-evicting analogue of the
|
|
46
|
+
* relay ring's resume window.
|
|
47
|
+
*/
|
|
48
|
+
export declare class InMemorySessionLog implements SessionLog {
|
|
49
|
+
#private;
|
|
50
|
+
lease(key: ActivationKey, incarnation: number): void;
|
|
51
|
+
currentIncarnation(key: ActivationKey): number | undefined;
|
|
52
|
+
nextOffset(key: ActivationKey): number;
|
|
53
|
+
append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent;
|
|
54
|
+
putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint;
|
|
55
|
+
latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined;
|
|
56
|
+
getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined;
|
|
57
|
+
replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The minimal synchronous SQLite handle the durable log needs — structurally the
|
|
61
|
+
* same surface the Urban runtime's DataLayer exposes (`host.openSqlite`), and
|
|
62
|
+
* identical to the presence/transcript stores' `SqliteDb`. Kept local so the log
|
|
63
|
+
* depends on a shape, not on the runtime package.
|
|
64
|
+
*/
|
|
65
|
+
export interface SqliteDb {
|
|
66
|
+
exec(sql: string): void;
|
|
67
|
+
run(sql: string, params?: unknown[]): {
|
|
68
|
+
changes: number;
|
|
69
|
+
lastInsertRowid: number | bigint;
|
|
70
|
+
};
|
|
71
|
+
all<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
|
|
72
|
+
}
|
|
73
|
+
/** A monotonic wall clock, injectable for deterministic tests. */
|
|
74
|
+
export interface Clock {
|
|
75
|
+
now(): number;
|
|
76
|
+
}
|
|
77
|
+
/** The default clock: `Date.now()`. */
|
|
78
|
+
export declare const systemClock: Clock;
|
|
79
|
+
/**
|
|
80
|
+
* The durable authoritative log over the app DataLayer/SQLite. The fence
|
|
81
|
+
* high-water lives in the activation row's `incarnation` column, so fencing
|
|
82
|
+
* survives a process restart — the durable counterpart of the in-memory
|
|
83
|
+
* {@link IncarnationFence}.
|
|
84
|
+
*/
|
|
85
|
+
export declare class SqliteSessionLog implements SessionLog {
|
|
86
|
+
#private;
|
|
87
|
+
constructor(db: SqliteDb, options?: {
|
|
88
|
+
clock?: Clock;
|
|
89
|
+
});
|
|
90
|
+
/** Apply the canonical DDL (idempotent). Identical to the boot migration (drift-guarded). */
|
|
91
|
+
ensureSchema(): void;
|
|
92
|
+
lease(key: ActivationKey, incarnation: number): void;
|
|
93
|
+
currentIncarnation(key: ActivationKey): number | undefined;
|
|
94
|
+
nextOffset(key: ActivationKey): number;
|
|
95
|
+
append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent;
|
|
96
|
+
putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint;
|
|
97
|
+
latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined;
|
|
98
|
+
getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined;
|
|
99
|
+
replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Raised when a row read back from the durable session log holds a value outside
|
|
103
|
+
* its domain (e.g. a corrupt effect-ledger JSON). Fail fast rather than coercing.
|
|
104
|
+
*/
|
|
105
|
+
export declare class SessionLogCorruptionError extends Error {
|
|
106
|
+
constructor(message: string);
|
|
107
|
+
}
|