@cotal-ai/connector-core 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/agent.d.ts.map +1 -1
  2. package/dist/agent.js +12 -2
  3. package/dist/agent.js.map +1 -1
  4. package/dist/agui-holder.d.ts +89 -0
  5. package/dist/agui-holder.d.ts.map +1 -0
  6. package/dist/agui-holder.js +135 -0
  7. package/dist/agui-holder.js.map +1 -0
  8. package/dist/agui-render.d.ts +100 -0
  9. package/dist/agui-render.d.ts.map +1 -0
  10. package/dist/agui-render.js +283 -0
  11. package/dist/agui-render.js.map +1 -0
  12. package/dist/agui-wal-path.d.ts +103 -0
  13. package/dist/agui-wal-path.d.ts.map +1 -0
  14. package/dist/agui-wal-path.js +324 -0
  15. package/dist/agui-wal-path.js.map +1 -0
  16. package/dist/agui.d.ts +727 -0
  17. package/dist/agui.d.ts.map +1 -0
  18. package/dist/agui.js +1114 -0
  19. package/dist/agui.js.map +1 -0
  20. package/dist/docs-bundle.generated.d.ts.map +1 -1
  21. package/dist/docs-bundle.generated.js +16 -9
  22. package/dist/docs-bundle.generated.js.map +1 -1
  23. package/dist/durable-source.d.ts +127 -0
  24. package/dist/durable-source.d.ts.map +1 -0
  25. package/dist/durable-source.js +219 -0
  26. package/dist/durable-source.js.map +1 -0
  27. package/dist/event-wal.d.ts +265 -0
  28. package/dist/event-wal.d.ts.map +1 -0
  29. package/dist/event-wal.js +698 -0
  30. package/dist/event-wal.js.map +1 -0
  31. package/dist/index.d.ts +5 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/launch.d.ts +78 -2
  36. package/dist/launch.d.ts.map +1 -1
  37. package/dist/launch.js +120 -0
  38. package/dist/launch.js.map +1 -1
  39. package/dist/tool-specs.d.ts +40 -2
  40. package/dist/tool-specs.d.ts.map +1 -1
  41. package/dist/tool-specs.js +51 -1
  42. package/dist/tool-specs.js.map +1 -1
  43. package/dist/tools.d.ts.map +1 -1
  44. package/dist/tools.js +5 -6
  45. package/dist/tools.js.map +1 -1
  46. package/package.json +8 -2
@@ -0,0 +1,283 @@
1
+ /**
2
+ * THE FIRST RENDERER THAT CAN DISPLAY AN AG-UI FRAME.
3
+ *
4
+ * A frame carries **no text part by design** — its content is a list of AG-UI events — so every
5
+ * surface that renders a message as flat text draws one as `[unrenderable part kind "ag-ui.frame"]`.
6
+ * This is the provider that fixes that for the console family: a core {@link PartRenderer} of kind
7
+ * `part-renderer`, named for the part kind it draws, self-registering on import exactly as a
8
+ * terminal-layout provider does.
9
+ *
10
+ * **IT LANDS BEFORE ANY PRODUCER, AND THAT ORDERING IS THE POINT.** At this tip nothing in
11
+ * production publishes a frame: no connector constructs an emitter and every connector still writes
12
+ * its `tr-<name>` text mirror. So this renderer has no live traffic to draw yet, and saying that
13
+ * plainly matters more than it sounds, because the alternative ordering is the one that fails. A
14
+ * cutover shipped first would replace a mirror a human can read with a part every surface shows as a
15
+ * marker, and the regression would be invisible to the change that caused it. Display first, then
16
+ * the producer.
17
+ *
18
+ * **CORE NEVER LEARNS AG-UI.** `partsToText` resolves a renderer by the part's own kind and calls
19
+ * it. The knowledge of what a `TOOL_CALL_ARGS` means lives here, in the package that defined the
20
+ * vocabulary, which is where `AGENTS.md` requires an adapter's concepts to stay. What moved into
21
+ * core is only the frame's IDENTITY (`agui-kind.ts`) and the channel's NAME (`event-channel.ts`),
22
+ * both of which a reader needs to recognise a frame without knowing who produced it.
23
+ *
24
+ * ── WHAT IT DELIBERATELY DOES NOT DO ────────────────────────────────────────────────────────────
25
+ *
26
+ * **It does not print raw event-type tokens.** A reader wants `bash({"cmd":"ls"}) -> ok`, not
27
+ * `TOOL_CALL_START TOOL_CALL_ARGS TOOL_CALL_RESULT`. So the suite must not assert the token either:
28
+ * gating on `TOOL_CALL_ARGS` appearing in the output would fail a correct renderer, which is why the
29
+ * cells assert the FOLD (name, arguments, result) rather than the vocabulary.
30
+ *
31
+ * **It does not validate the frame as a schema.** `parseAguiFrame` checks the envelope and that each
32
+ * event's `type` is in the vocabulary, and nothing else, which is measured rather than assumed. So
33
+ * this renderer treats event fields as untrusted: every field access is guarded and a malformed
34
+ * event degrades to a named marker rather than throwing. A renderer is the wrong place to discover a
35
+ * producer's bug, and a very good place to make one visible.
36
+ *
37
+ * ── WHY IT ROUTES ON THE KIND AND NEVER ON SHAPE ────────────────────────────────────────────────
38
+ *
39
+ * Two formats will share one channel for exactly as long as the cutover takes: `events.<owner>.<actor>`
40
+ * is designed to carry frames, while the connectors that have not cut over still publish plain text
41
+ * parts to their own mirror. Registration is for `ag-ui.frame` ONLY, so a text part renders as the
42
+ * text it is and never as a degenerate frame. The two cannot be confused because the routing is on
43
+ * the part kind and not on a guess about shape.
44
+ */
45
+ import { AGUI_EVENT_TYPE, AGUI_FRAME_KIND, isAguiFramePart, registry } from "@cotal-ai/core";
46
+ const str = (v) => (typeof v === "string" ? v : undefined);
47
+ /**
48
+ * Line prefixes. **Every one of these starts with a non-space glyph within the first three columns,
49
+ * and that is a requirement rather than a style choice.** Leading spaces do not establish a line
50
+ * start, they indent one, so a prefix of spaces alone leaves the payload's first character still the
51
+ * first character of the line. `CONT` therefore carries `·`, and no prefix here may be widened past
52
+ * three leading spaces without re-deriving {@link LINE_START_SAFE}.
53
+ */
54
+ const CONT = " · ";
55
+ const TEXT_PREFIX = "» ";
56
+ const THINK_PREFIX = "(thinking) ";
57
+ const TOOL_PREFIX = "⚙ ";
58
+ const RESULT_PREFIX = " ↳ ";
59
+ /**
60
+ * Every character markdown can read as BEGINNING a block, so {@link LINE_START_SAFE} never has to
61
+ * know which block: `#` heading, `>` quote, `|` table, `` ` `` and `~` fence, `=` and `-` setext
62
+ * underline, `-` `*` `+` bullet, `*` `_` `-` thematic break, a digit for an ordered list, `[` a link
63
+ * reference definition (which renders as NOTHING, so it deletes the line it is on), and a tab, which
64
+ * counts as up to four columns of indent and so opens code.
65
+ */
66
+ const BLOCK_START_CHARS = new Set([..."#>|`~=_*+-[<0123456789", "\t"]);
67
+ /**
68
+ * A line this renderer is allowed to emit: one that cannot open a block construct.
69
+ *
70
+ * **Exported so the suite asserts the invariant against the SAME expression the renderer is
71
+ * documented by**, rather than a second copy of it that can drift. It is a property of the emitted
72
+ * lines and nothing more, deliberately not a claim about what any consumer does downstream, which
73
+ * would be an assertion outside this module that no test here could hold up.
74
+ *
75
+ * **IT TESTS THE FIRST CHARACTER, NOT A LIST OF CONSTRUCTS, AND THAT IS THE WHOLE POINT.** The
76
+ * previous version enumerated the openers it knew (ATX heading, bullet, ordered list, blockquote,
77
+ * table row, indented code) and called them "the block-level openers". An enumeration of a syntax
78
+ * someone else defines is incomplete the moment it is written, and this one was: a bare `---`, `***`
79
+ * and a fence line all PASSED it while being a thematic break, a setext underline and a code fence.
80
+ * The suite stayed green because the PREFIXES stop the renderer from emitting such a line at all,
81
+ * which means the predicate was passing for a reason unrelated to what it claimed to check, and a
82
+ * weakened prefix would not have been caught by the thing whose job is to catch exactly that.
83
+ *
84
+ * So the rule is a property of one character: a line is safe when its first non-space character
85
+ * arrives within three columns and is not one markdown can read as beginning a block. That is
86
+ * stricter than the constructs strictly require (`1x` and `-x` open nothing and are refused anyway),
87
+ * and strictness is the correct direction here, because this constrains what THIS renderer emits and
88
+ * every line it emits carries a glyph prefix that is not punctuation at all.
89
+ */
90
+ export const LINE_START_SAFE = (line) => {
91
+ if (line === "")
92
+ return true; // a blank line opens nothing and separates rather than nests
93
+ const indent = /^ */.exec(line)[0].length;
94
+ if (indent > 3)
95
+ return false; // four columns is an indented code block
96
+ const first = line[indent];
97
+ if (first === undefined)
98
+ return true; // spaces only, same as blank
99
+ return !BLOCK_START_CHARS.has(first);
100
+ };
101
+ /**
102
+ * Fold a frame's events into display lines.
103
+ *
104
+ * Deltas are ACCUMULATED rather than printed one per line: `TEXT_MESSAGE_CONTENT` arrives as a
105
+ * stream of fragments, and a renderer that printed each one would turn a sentence into a column.
106
+ * They are keyed by `messageId`/`toolCallId` so two interleaved streams do not braid into each
107
+ * other; the ids exist precisely because interleaving is legal.
108
+ */
109
+ function renderEvents(events) {
110
+ const lines = [];
111
+ const text = new Map();
112
+ const reasoning = new Map();
113
+ const toolName = new Map();
114
+ const toolArgs = new Map();
115
+ /**
116
+ * Emit a payload as lines, prefixing EVERY line, not just the first.
117
+ *
118
+ * **THE STRUCTURAL INVARIANT THIS EXISTS FOR: no emitted line begins with a payload character.**
119
+ * Payload values are multi-line (a tool result is the common case, but pretty-printed args, a
120
+ * multi-paragraph message and an error body all are), and pushing one of them as a single string
121
+ * put its second and later lines at column 0 with nothing of the renderer's in front of them.
122
+ *
123
+ * At that point the payload is no longer inside the scaffolding, it IS scaffolding, and a consumer
124
+ * that reads line starts cannot tell which lines the renderer wrote. **Measured: a tool result
125
+ * whose second line began `- ` captured the frame's own `◂ run … finished` terminator into a list
126
+ * the payload had opened.** The invariant is asserted over this function's output in the suite.
127
+ *
128
+ * `cont` carries a non-space glyph for the same reason `first` does: leading spaces alone do not
129
+ * establish a line start, they only indent one.
130
+ */
131
+ const emit = (first, cont, body) => {
132
+ const [head, ...rest] = body.split("\n");
133
+ lines.push(first + head);
134
+ for (const l of rest)
135
+ lines.push(cont + l);
136
+ };
137
+ /** Flush an accumulator, if it has anything in it. */
138
+ const flush = (map, id, first, cont, suffix = "") => {
139
+ const acc = map.get(id);
140
+ if (acc !== undefined && acc.length > 0)
141
+ emit(first, cont, acc + suffix);
142
+ map.delete(id);
143
+ };
144
+ for (const e of events) {
145
+ // THE ELEMENT ITSELF IS UNTRUSTED, not just its fields. `events` is an array off the wire and
146
+ // `parseAguiFrame` does not check that each element is an object, so `null` reaches here and a
147
+ // bare `e.type` throws on it. The throw was not fatal (core's dispatcher turns it into a named
148
+ // marker) and that is exactly what made it worth guarding: the whole frame degraded to one
149
+ // failure line, so a single null element deleted every other event in the frame from the
150
+ // reader's view. Named per element instead, so one malformed event costs one line.
151
+ const type = typeof e === "object" && e !== null ? str(e.type) : undefined;
152
+ switch (type) {
153
+ case AGUI_EVENT_TYPE.RUN_STARTED:
154
+ emit("▸ ", CONT, `run ${str(e.runId) ?? "?"} started`);
155
+ break;
156
+ case AGUI_EVENT_TYPE.RUN_FINISHED: {
157
+ // `outcome` is optional by the real schema — a turn that merely ended says nothing more, and
158
+ // manufacturing "success" would assert something the source never said.
159
+ const outcome = str(e.outcome?.type);
160
+ emit("◂ ", CONT, `run ${str(e.runId) ?? "?"} finished${outcome ? ` (${outcome})` : ""}`);
161
+ break;
162
+ }
163
+ case AGUI_EVENT_TYPE.RUN_ERROR: {
164
+ const code = str(e.code);
165
+ emit("✗ ", CONT, `run error${code ? ` [${code}]` : ""}: ${str(e.message) ?? "(no message)"}`);
166
+ break;
167
+ }
168
+ case AGUI_EVENT_TYPE.TEXT_MESSAGE_START:
169
+ text.set(str(e.messageId) ?? "", "");
170
+ break;
171
+ case AGUI_EVENT_TYPE.TEXT_MESSAGE_CONTENT: {
172
+ const id = str(e.messageId) ?? "";
173
+ text.set(id, (text.get(id) ?? "") + (str(e.delta) ?? ""));
174
+ break;
175
+ }
176
+ case AGUI_EVENT_TYPE.TEXT_MESSAGE_END:
177
+ flush(text, str(e.messageId) ?? "", TEXT_PREFIX, TEXT_PREFIX);
178
+ break;
179
+ case AGUI_EVENT_TYPE.REASONING_MESSAGE_START:
180
+ reasoning.set(str(e.messageId) ?? "", "");
181
+ break;
182
+ case AGUI_EVENT_TYPE.REASONING_MESSAGE_CONTENT: {
183
+ const id = str(e.messageId) ?? "";
184
+ reasoning.set(id, (reasoning.get(id) ?? "") + (str(e.delta) ?? ""));
185
+ break;
186
+ }
187
+ case AGUI_EVENT_TYPE.REASONING_MESSAGE_END:
188
+ flush(reasoning, str(e.messageId) ?? "", THINK_PREFIX, CONT);
189
+ break;
190
+ case AGUI_EVENT_TYPE.TOOL_CALL_START: {
191
+ const id = str(e.toolCallId) ?? "";
192
+ toolName.set(id, str(e.toolCallName) ?? "?");
193
+ toolArgs.set(id, "");
194
+ break;
195
+ }
196
+ case AGUI_EVENT_TYPE.TOOL_CALL_ARGS: {
197
+ const id = str(e.toolCallId) ?? "";
198
+ toolArgs.set(id, (toolArgs.get(id) ?? "") + (str(e.delta) ?? ""));
199
+ break;
200
+ }
201
+ case AGUI_EVENT_TYPE.TOOL_CALL_END: {
202
+ const id = str(e.toolCallId) ?? "";
203
+ emit(TOOL_PREFIX, CONT, `${toolName.get(id) ?? "?"}(${toolArgs.get(id) ?? ""})`);
204
+ toolName.delete(id);
205
+ toolArgs.delete(id);
206
+ break;
207
+ }
208
+ case AGUI_EVENT_TYPE.TOOL_CALL_RESULT:
209
+ emit(RESULT_PREFIX, CONT, str(e.content) ?? "(no content)");
210
+ break;
211
+ case AGUI_EVENT_TYPE.CUSTOM:
212
+ emit("• ", CONT, `custom ${str(e.name) ?? "(unnamed)"}`);
213
+ break;
214
+ // An event whose `type` this build does not know. NAMED, never skipped — a skipped event is a
215
+ // hole in a transcript that still looks complete, which is `parseAguiFrame`'s own stated
216
+ // reason for refusing one. Here the surface is a reader rather than a parser, so it is shown.
217
+ default:
218
+ emit("• ", CONT, `unrecognised event ${JSON.stringify(type ?? null)}`);
219
+ }
220
+ }
221
+ // A stream that ended without its END event still has content a reader needs. Dropping it would
222
+ // make a truncated turn indistinguishable from a silent one, which is the exact failure the mirror
223
+ // had.
224
+ for (const id of [...text.keys()])
225
+ flush(text, id, TEXT_PREFIX, TEXT_PREFIX, " …");
226
+ for (const id of [...reasoning.keys()])
227
+ flush(reasoning, id, THINK_PREFIX, CONT, " …");
228
+ for (const id of [...toolName.keys()])
229
+ emit(TOOL_PREFIX, CONT, `${toolName.get(id) ?? "?"}(${toolArgs.get(id) ?? ""}) …`);
230
+ return lines;
231
+ }
232
+ /**
233
+ * The registered provider. `name` IS the part kind, which is how core resolves it without ever
234
+ * learning what the kind means.
235
+ */
236
+ export const aguiFramePartRenderer = Object.freeze({
237
+ kind: "part-renderer",
238
+ name: AGUI_FRAME_KIND,
239
+ render(part) {
240
+ // Defensive rather than trusting: core resolved us by kind, but a caller can hand us anything.
241
+ if (!isAguiFramePart(part))
242
+ return `[not an AG-UI frame]`;
243
+ const events = part.events;
244
+ if (!Array.isArray(events) || events.length === 0)
245
+ return `[AG-UI frame carrying no events]`;
246
+ const lines = renderEvents(events);
247
+ // An events array that produced no lines is impossible today (every branch pushes), but if a
248
+ // future branch returns nothing this must not become the silent empty string that the whole
249
+ // exercise exists to remove.
250
+ return lines.length > 0 ? lines.join("\n") : `[AG-UI frame with ${events.length} event(s) and nothing to show]`;
251
+ },
252
+ });
253
+ /**
254
+ * Register the provider, once, however many copies of this module get loaded.
255
+ *
256
+ * **THIS PACKAGE LEGITIMATELY EXISTS TWICE IN ONE PROCESS, AND A BARE `register` CRASHES THERE.**
257
+ * Measured, not anticipated: `cotal ext add <connector>` failed with
258
+ * `extension already registered: part-renderer:ag-ui.frame`, and the mechanism is the extension
259
+ * prefix's layout. The CLI imports this module from its OWN copy of `connector-core`. Materializing
260
+ * an installed connector imports `connector-core` again, from the extension prefix's `node_modules`,
261
+ * which is a different physical file and therefore a second module instance with its own top-level
262
+ * evaluation. `@cotal-ai/core` is LINKED to the CLI's single copy (`ext add` writes that link on
263
+ * purpose), so both instances see ONE registry, and `registry.register` throws on the duplicate
264
+ * `kind:name`. That throw took down `ext add` for every connector.
265
+ *
266
+ * **WHY THIS IS NOT THE SILENT DEGRADE THE PROJECT REFUSES.** `register`'s refusal exists to catch
267
+ * TWO EXTENSIONS CLAIMING ONE NAME, which is a genuine conflict nobody can adjudicate. This is one
268
+ * extension arriving twice, which is not a conflict: `ag-ui.frame` is defined by this package, no
269
+ * other package may claim it, and every copy of this file registers a provider that draws it the
270
+ * same way. First one wins, deterministically, and the second is a no-op rather than a fatal error
271
+ * on a path a customer runs. Skipping a duplicate self-registration is a different act from
272
+ * swallowing a failure.
273
+ *
274
+ * It is a named function rather than a bare guard so the property is executable: a cell calls it
275
+ * twice and asserts the second call neither throws nor displaces the first.
276
+ */
277
+ export function registerAguiFramePartRenderer() {
278
+ if (registry.has("part-renderer", AGUI_FRAME_KIND))
279
+ return;
280
+ registry.register(aguiFramePartRenderer);
281
+ }
282
+ registerAguiFramePartRenderer();
283
+ //# sourceMappingURL=agui-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agui-render.js","sourceRoot":"","sources":["../src/agui-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Thrown when a session that is going to publish events cannot say where its WAL belongs.
3
+ *
4
+ * A distinct type rather than a bare `Error` so a caller can tell "misconfigured launch" from
5
+ * "filesystem said no" without matching on message text.
6
+ */
7
+ export declare class EventsStateRootMissing extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * The state root for an events-enabled session, or a loud failure.
12
+ *
13
+ * **THE FAILURE THIS PREVENTS IS SILENT, WHICH IS WHY IT IS A THROW AND NOT A DEFAULT.** The
14
+ * tempting fallback is `process.cwd()`. A WAL written there is not an error anyone sees: the emitter
15
+ * starts, frames publish, and the durable state that makes a restart safe sits in whatever directory
16
+ * the launch happened to begin in — a per-agent working directory that "can point at any repo". The
17
+ * next start resolves the real root, finds no WAL, and reads an already-published thread as VIRGIN:
18
+ * `E := 0`, a fresh epoch, a second frame claiming a sequence the stream has already seen. Nothing
19
+ * reports a problem at any point, and an absence is what a clean board looks like.
20
+ *
21
+ * `env` is a PARAMETER and the root resolves PER CALL. A module-level read would freeze whatever the
22
+ * environment was at first import, and none of what this path is keyed on — space, principal,
23
+ * thread — is process-wide.
24
+ */
25
+ export declare function resolveEventsStateRoot(env: {
26
+ COTAL_WORKSPACE_ROOT?: string | undefined;
27
+ }): string;
28
+ /** The resolved locations for one `(space, principal, threadId)`. Pure: computes, touches nothing. */
29
+ export interface EventWalLocation {
30
+ /** `<workspaceRoot>/.cotal/events/<h(space)>/<h(principal)>` — the lock's directory. */
31
+ principalDir: string;
32
+ /** The single-emitter-per-principal lock. One emitter per PRINCIPAL, not per thread. */
33
+ lockPath: string;
34
+ /** `<principalDir>/<h(threadId)>` — the directory holding exactly one `wal.json`. */
35
+ threadDir: string;
36
+ /** The WAL document itself. */
37
+ walPath: string;
38
+ }
39
+ /** What {@link ensureEventWalDir} returns: the location, plus the lock it actually took for it. */
40
+ export interface HeldEventWalLocation extends EventWalLocation {
41
+ lock: PrincipalLock;
42
+ }
43
+ /**
44
+ * Resolve where this principal's WAL for this thread lives. Pure — no IO, no side effects.
45
+ *
46
+ * `workspaceRoot` is taken as given rather than defaulted. A default here would be a silent fallback
47
+ * onto whatever directory the process happened to start in, which is precisely the scattering the
48
+ * root exists to prevent; the caller that cannot resolve one must fail loud instead.
49
+ */
50
+ export declare function eventWalLocation(opts: {
51
+ workspaceRoot: string;
52
+ space: string;
53
+ principal: string;
54
+ threadId: string;
55
+ }): EventWalLocation;
56
+ /** Every refusal on the lock path is one of these, so a caller never mistakes it for an I/O blip. */
57
+ export declare class PrincipalLockError extends Error {
58
+ readonly path: string;
59
+ readonly invariant: string;
60
+ constructor(path: string, invariant: string, detail: string);
61
+ }
62
+ /** A HELD lock. It exists as an object only while this process owns the file. */
63
+ export interface PrincipalLock {
64
+ readonly path: string;
65
+ /** Close the handle and remove the file. Idempotent: releasing twice is not an error. */
66
+ release(): Promise<void>;
67
+ }
68
+ /**
69
+ * Take this principal's lock and HOLD IT for the life of the process.
70
+ *
71
+ * The handle stays open deliberately. A lock released at the end of the acquiring function is a
72
+ * lock that was never held, and the layout comment above has claimed single-emitter exclusion since
73
+ * this module was written while `lockPath` was only ever COMPUTED — a path in a struct standing in
74
+ * for a guarantee. This is that claim made real.
75
+ */
76
+ export declare function acquirePrincipalLock(lockPath: string): Promise<PrincipalLock>;
77
+ /**
78
+ * Create the directory chain for a thread's WAL and make it durable: **once, before the
79
+ * first transition.**
80
+ *
81
+ * The WAL's own replace protocol fsyncs `wal.json` and the thread directory that holds it. That is
82
+ * not sufficient on its own: a newly created `<h(threadId)>/` directory's NAME is an entry in its
83
+ * PARENT, and `ensureDirNoSymlink` only `mkdir`s each missing component with no fsync anywhere. On a
84
+ * filesystem that honours the distinction, a crash after the first transition and after the publish
85
+ * can come back with the directory link itself lost — no WAL, no pending record — and boot then
86
+ * reads a thread that has already published as VIRGIN: `E := 0`, a fresh epoch, and a second frame
87
+ * claiming a sequence the stream has already seen.
88
+ *
89
+ * So every component from the workspace root down is fsynced, parents included, rather than the leaf
90
+ * alone. It runs once at emitter start; the cost is a handful of directory syncs against a session
91
+ * that is about to do real work.
92
+ *
93
+ * Returns the WAL path, so a caller cannot resolve the location by one route and create it by
94
+ * another — and the HELD principal lock with it, for the same reason. Handing back a location whose
95
+ * lock the caller then has to remember to take is how the lock came to be a path and nothing else.
96
+ */
97
+ export declare function ensureEventWalDir(opts: {
98
+ workspaceRoot: string;
99
+ space: string;
100
+ principal: string;
101
+ threadId: string;
102
+ }): Promise<HeldEventWalLocation>;
103
+ //# sourceMappingURL=agui-wal-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agui-wal-path.d.ts","sourceRoot":"","sources":["../src/agui-wal-path.ts"],"names":[],"mappings":"AAwCA;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAAG,MAAM,CAWjG;AAaD,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,YAAY,EAAE,MAAM,CAAC;IACrB,wFAAwF;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mGAAmG;AACnG,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,gBAAgB,CASnB;AAED,qGAAqG;AACrG,qBAAa,kBAAmB,SAAQ,KAAK;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM;gBAAxC,IAAI,EAAE,MAAM,EAAW,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI9E;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AA4FD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCnF;AA8BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqBhC"}