@cotal-ai/connector-core 0.22.0 → 0.23.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.
@@ -0,0 +1,416 @@
1
+ /**
2
+ * The SUBJECT frontier, scoped to the PRINCIPAL rather than to one thread.
3
+ *
4
+ * **WHY THIS FILE EXISTS.** The write-ahead log is keyed per thread, so a new native session opens
5
+ * `virgin` with `lastSubjectSeq: 0` and publishes that value as its expectation. The subject is
6
+ * keyed per PRINCIPAL: `eventChannelForSession` returns `eventChannel(ep.principal)`, so every
7
+ * thread of one agent shares `events.<owner>.<actor>`. A virgin frontier therefore expected an
8
+ * empty subject that the agent's own previous session had already filled, the broker refused the
9
+ * publish, and the emitter halted permanently. Measured before this file was written: thread 1
10
+ * published, threads 2 and 3 halted identically. Two correct components with a false assumption
11
+ * between them.
12
+ *
13
+ * **WHAT IT IS NOT.** It is not a cache of something readable. Agent credentials hold neither read
14
+ * shape for the stream, so the tip cannot be looked up, and guessing it is the failure this
15
+ * replaces. It is writable without any read capability for one reason: the writer learns the
16
+ * assigned sequence from its OWN ack, so it only ever records what it was told about a publish it
17
+ * made.
18
+ *
19
+ * **MONOTONE, AND A DECREASE IS REFUSED RATHER THAN ACCEPTED.** The tip only advances. A recorded
20
+ * value lower than the one on disk means either a stale writer or a corrupted file, and writing it
21
+ * would produce an expectation the broker rejects forever with no indication of why. The one thing
22
+ * that legitimately moves the tip backwards is a filtered channel purge, which is an abandonment
23
+ * for every thread on the channel and is therefore an explicit {@link reset}, never an implicit
24
+ * decrease.
25
+ *
26
+ * Durability discipline is the write-ahead log's, deliberately: atomic temp-and-rename, fsync of
27
+ * the file and its directory, fatal UTF-8 decode, 0600, and corruption that refuses rather than
28
+ * guesses.
29
+ */
30
+ import { randomUUID } from "node:crypto";
31
+ import { constants } from "node:fs";
32
+ import { lstat, open, readdir, readFile, rename, unlink } from "node:fs/promises";
33
+ import { dirname, join } from "node:path";
34
+ import { fsyncDir } from "./agui-wal-path.js";
35
+ /** The document on disk. Versioned, because a shape change must be refused rather than misread. */
36
+ const SUBJECT_FRONTIER_VERSION = 1;
37
+ /** Every refusal is one of these, so a caller never mistakes it for an I/O blip. */
38
+ export class SubjectFrontierCorruptError extends Error {
39
+ path;
40
+ invariant;
41
+ constructor(path, invariant, detail) {
42
+ super(`subject frontier ${path}: expected ${invariant} — ${detail}`);
43
+ this.path = path;
44
+ this.invariant = invariant;
45
+ this.name = "SubjectFrontierCorruptError";
46
+ }
47
+ }
48
+ /**
49
+ * The record on disk is not the one this view was opened from, so this writer's number is not the
50
+ * one to write.
51
+ *
52
+ * DISTINCT FROM CORRUPTION: the file is perfectly well formed, it just belongs to a later state
53
+ * than this object remembers. Both are refusals rather than repairs, and telling them apart is what
54
+ * lets an operator know whether to look at the filesystem or at a second writer.
55
+ */
56
+ export class SubjectFrontierMovedError extends Error {
57
+ path;
58
+ viewTip;
59
+ diskTip;
60
+ constructor(path, viewTip, diskTip) {
61
+ super(`subject frontier ${path}: the record moved under this writer (this view holds ${viewTip}, ` +
62
+ `the file holds ${diskTip === undefined ? "no record at all" : diskTip}). The tip is shared by ` +
63
+ `every thread of the principal, so writing this view's number would take the record backwards ` +
64
+ `to a sequence the broker has already passed, and every later publish would expect a tip the ` +
65
+ `subject no longer has.`);
66
+ this.path = path;
67
+ this.viewTip = viewTip;
68
+ this.diskTip = diskTip;
69
+ this.name = "SubjectFrontierMovedError";
70
+ }
71
+ }
72
+ const isSafeNonNegInt = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
73
+ /** The durable implementation, one file per principal beside that principal's thread directories. */
74
+ export class FileSubjectFrontier {
75
+ path;
76
+ doc;
77
+ constructor(path, doc) {
78
+ this.path = path;
79
+ this.doc = doc;
80
+ }
81
+ get tip() {
82
+ return this.doc.tip;
83
+ }
84
+ /**
85
+ * Open, or create a virgin record.
86
+ *
87
+ * A MISSING file is virgin and legal: this principal has never published, which is the ordinary
88
+ * state on a first run and after a fresh install. A ZERO-BYTE file is NOT, for the same reason
89
+ * the write-ahead log refuses one: an atomic temp-and-rename never produces it, so it is a
90
+ * filesystem that lost the tail, and reading it as "never published" is the guess this whole
91
+ * mechanism exists to remove.
92
+ */
93
+ static async open(path, opts) {
94
+ let bytes;
95
+ try {
96
+ bytes = await readFile(path);
97
+ }
98
+ catch (e) {
99
+ if (e.code !== "ENOENT")
100
+ throw e;
101
+ }
102
+ if (bytes === undefined) {
103
+ // ABSENT, so this record has never existed. Before calling the principal virgin, look at the
104
+ // thread logs that already sit beside it. See {@link recoverTipFromThreadLogs}.
105
+ const recovered = await FileSubjectFrontier.recoverTipFromThreadLogs(dirname(path), opts.principal);
106
+ const fresh = new FileSubjectFrontier(path, { v: SUBJECT_FRONTIER_VERSION, space: opts.space, principal: opts.principal, tip: 0 });
107
+ if (recovered > 0)
108
+ await fresh.write({ ...fresh.doc, tip: recovered });
109
+ return fresh;
110
+ }
111
+ return new FileSubjectFrontier(path, FileSubjectFrontier.parse(path, bytes, opts));
112
+ }
113
+ /**
114
+ * Bytes to a validated document, or a refusal.
115
+ *
116
+ * SHARED BY `open` AND BY THE RE-READ IN {@link advance} on purpose. A record that went corrupt
117
+ * underneath a live writer has to meet the same wall as one that was corrupt at boot; validating
118
+ * only on the way in would let a writer that opened a good file overwrite a bad one, which
119
+ * destroys the evidence of whatever produced it.
120
+ */
121
+ static parse(path, bytes, opts) {
122
+ let raw;
123
+ try {
124
+ // FATAL decode, never `readFile(path, "utf8")`: Node's default substitutes U+FFFD for invalid
125
+ // bytes, so a corrupted file arrives as a changed-but-parseable document. For a record whose
126
+ // whole posture is that an unreadable state fails loud, "quietly altered and accepted" is the
127
+ // one outcome it must not produce.
128
+ raw = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
129
+ }
130
+ catch {
131
+ throw new SubjectFrontierCorruptError(path, "valid UTF-8", "invalid UTF-8 bytes; refusing rather than substituting U+FFFD");
132
+ }
133
+ if (raw.length === 0)
134
+ throw new SubjectFrontierCorruptError(path, "a non-empty file", "the file is zero bytes — distinct from missing, and never treated as virgin");
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(raw);
138
+ }
139
+ catch (e) {
140
+ throw new SubjectFrontierCorruptError(path, "parseable JSON", e.message);
141
+ }
142
+ const d = parsed;
143
+ if (d?.v !== SUBJECT_FRONTIER_VERSION)
144
+ throw new SubjectFrontierCorruptError(path, `v === ${SUBJECT_FRONTIER_VERSION}`, String(d?.v));
145
+ if (d.space !== opts.space)
146
+ throw new SubjectFrontierCorruptError(path, "space matches", `file=${String(d.space)} caller=${opts.space}`);
147
+ // The principal is the whole key of this record: a file belonging to another identity would
148
+ // hand this writer another principal's tip, which is a fabricated frontier of exactly the kind
149
+ // the write-ahead log refuses for the same reason.
150
+ if (d.principal !== opts.principal)
151
+ throw new SubjectFrontierCorruptError(path, "principal matches", `file=${String(d.principal)} caller=${opts.principal}`);
152
+ if (!isSafeNonNegInt(d.tip))
153
+ throw new SubjectFrontierCorruptError(path, "tip is a safe non-negative integer", String(d.tip));
154
+ return { v: d.v, space: d.space, principal: d.principal, tip: d.tip };
155
+ }
156
+ async advance(seq) {
157
+ return this.serialize(async () => {
158
+ if (!isSafeNonNegInt(seq))
159
+ throw new Error(`subject frontier ${this.path}: seq must be a safe non-negative integer, got ${String(seq)}`);
160
+ // VALIDATE BEFORE THE DURABLE WRITE. A bad value written first bricks the record permanently
161
+ // while the call that wrote it reports success, and the next open refuses a file nothing can
162
+ // repair. Fail-closed has to happen before the write, not on the boot after it.
163
+ if (seq <= this.doc.tip)
164
+ throw new Error(`subject frontier ${this.path}: seq=${seq} does not advance the tip ${this.doc.tip}`);
165
+ // THE FILE IS THE FRONTIER; THIS OBJECT IS ONLY A VIEW OF IT, AND THE CHECK ABOVE GRADES THE
166
+ // VIEW. The header of this file says a decrease is refused because it is lower than the one
167
+ // ON DISK, and until this line the comparison was against memory, so two views of one record
168
+ // took it backwards with no error at all: `A.advance(10)` then `B.advance(6)` left 6 on disk.
169
+ //
170
+ // NOT REACHABLE THROUGH A PUBLISH TODAY, AND THAT IS EXACTLY WHY IT IS GUARDED. A view that
171
+ // has gone stale publishes a stale `E`, and the broker's compare-and-set refuses it before
172
+ // any ack exists to record, so JetStream is what holds this file monotone right now.
173
+ // Measured, not assumed: two emitters on one principal, the second opened early, halted on
174
+ // `wrong last sequence` with the record still holding the first one's number. That is the
175
+ // same shape the released defect shipped on, two correct components with an assumption
176
+ // standing where a guard belongs, so the assumption becomes a guard here too.
177
+ const disk = await this.readDiskTip();
178
+ // ABSENT IS NOT ZERO, and this is the third place in this plane where conflating them is the
179
+ // bug. A missing record is legal only for a view that has not written one either; a view
180
+ // holding a tip whose file has gone is a record something removed underneath a live writer,
181
+ // and re-creating it would resurrect a frontier that was deliberately or accidentally cleared.
182
+ if (disk === undefined ? this.doc.tip !== 0 : disk !== this.doc.tip)
183
+ throw new SubjectFrontierMovedError(this.path, this.doc.tip, disk);
184
+ await this.write({ ...this.doc, tip: seq });
185
+ });
186
+ }
187
+ /**
188
+ * The tip the FILE holds, or `undefined` when no record exists yet.
189
+ *
190
+ * Fully validated, not a bare `JSON.parse().tip`: the disagreement this feeds is decided on a
191
+ * number, and a number taken from a document that failed its own shape checks is not evidence.
192
+ */
193
+ async readDiskTip() {
194
+ let bytes;
195
+ try {
196
+ bytes = await readFile(this.path);
197
+ }
198
+ catch (e) {
199
+ if (e.code === "ENOENT")
200
+ return undefined;
201
+ throw e;
202
+ }
203
+ return FileSubjectFrontier.parse(this.path, bytes, { space: this.doc.space, principal: this.doc.principal }).tip;
204
+ }
205
+ /**
206
+ * One mutation at a time on THIS instance.
207
+ *
208
+ * The re-read above is a read-modify-write, so two callers that interleave between the read and
209
+ * the rename would both pass a check neither still satisfies. One frontier is legitimately bound
210
+ * to SEVERAL logs (the pinning runs the other way: a log may not change which record it
211
+ * publishes onto), so concurrent callers on one instance are an ordinary state, not a misuse.
212
+ *
213
+ * It serializes this instance and nothing else. Two instances have two chains, which is the case
214
+ * the re-read exists for.
215
+ */
216
+ chain = Promise.resolve();
217
+ serialize(op) {
218
+ const next = this.chain.then(op, op);
219
+ // Keep the chain alive after a rejection so one refused write cannot wedge every later one.
220
+ this.chain = next.catch(() => undefined);
221
+ return next;
222
+ }
223
+ /**
224
+ * Recover the tip from the THREAD LOGS beside this record, for an installation upgrading from a
225
+ * release where this record did not exist.
226
+ *
227
+ * **THIS IS THE WHOLE UPGRADE PATH AND LEAVING IT OUT MAKES THE FIX APPLY TO NOBODY WHO ALREADY
228
+ * RAN THE BROKEN VERSION.** My first attempt seeded from the log of the thread being opened, which
229
+ * is empty in the case that matters: upgrading restarts the seat, so the first session after the
230
+ * upgrade is a NEW thread with a virgin log, while the sequence it needs sits in the PREVIOUS
231
+ * thread's log. A cell in `smoke:agui-multi-session` failed on exactly that and is the reason this
232
+ * function exists rather than the reasoning that produced the first version.
233
+ *
234
+ * **ONLY WHEN THE RECORD IS ABSENT, NEVER WHEN IT READS ZERO.** A record holding zero is what
235
+ * abandonment writes after a filtered purge, and re-seeding it from a thread log would silently
236
+ * undo the abandonment and restore an expectation the subject no longer has. Missing and zero are
237
+ * different states and this is the second place in this plane where conflating them is the bug.
238
+ *
239
+ * A sibling that cannot be read or does not parse is FATAL rather than skipped. Skipping it
240
+ * under-counts the tip, which produces a permanent halt later with a message about a moved tip,
241
+ * pointing at everything except the file that was quietly ignored here.
242
+ */
243
+ static async recoverTipFromThreadLogs(principalDir, principal) {
244
+ let entries;
245
+ try {
246
+ entries = await readdir(principalDir, { withFileTypes: true });
247
+ }
248
+ catch (e) {
249
+ if (e.code === "ENOENT")
250
+ return 0;
251
+ throw e;
252
+ }
253
+ let best = 0;
254
+ for (const ent of entries) {
255
+ // THE SCAN MAY NOT BE WALKED OUTSIDE THE PRINCIPAL DIRECTORY. The writer that creates these
256
+ // directories refuses a symlinked component (`ensureDirNoSymlink`), so a symlink here is a
257
+ // state it cannot produce, and following one would take a tip from a log belonging to some
258
+ // other tree. The create path and the recovery path have to agree about that or the guard is
259
+ // only on the half nobody attacks.
260
+ if (ent.isSymbolicLink())
261
+ throw new SubjectFrontierCorruptError(join(principalDir, ent.name), "a real directory beside the record, never a symlink", "following it would carry this scan outside the principal directory, and the writer that creates these directories refuses a symlinked component for the same reason");
262
+ if (!ent.isDirectory())
263
+ continue; // the record itself, the lock, anything else that is not a thread
264
+ const walPath = join(principalDir, ent.name, "wal.json");
265
+ // The entry check above clears the DIRECTORY and stops there, so a real thread directory
266
+ // holding a symlinked `wal.json` reaches the same foreign log by one more hop. Two layers,
267
+ // and they are not redundant:
268
+ //
269
+ // - `lstat` is the GRADED guard and the portable one. It decides the refusal on every
270
+ // platform, which matters because `O_NOFOLLOW` does not exist on Windows and a guard that
271
+ // silently evaporates there is worse than one that was never claimed.
272
+ // - `O_NOFOLLOW` narrows the window between the `lstat` and the `open`, where the file could
273
+ // be replaced by a link. It applies to the FINAL COMPONENT ONLY, so a thread directory
274
+ // swapped for a link in that same window is still followed; closing that would take an
275
+ // `openat` walk per component, which this scan does not do. NO CELL CAN GRADE EITHER
276
+ // WINDOW, and the mutation config says so rather than registering a mutant that would
277
+ // survive and be explained away.
278
+ let st;
279
+ try {
280
+ st = await lstat(walPath);
281
+ }
282
+ catch (e) {
283
+ const code = e.code;
284
+ if (code === "ENOENT" || code === "ENOTDIR")
285
+ continue; // not a thread directory
286
+ throw e;
287
+ }
288
+ if (st.isSymbolicLink())
289
+ throw new SubjectFrontierCorruptError(walPath, "a real thread log, never a symlink", "following it would read a log this principal's writer never wrote");
290
+ // A HARD link is not a symlink and neither check above sees one, so a log hardlinked into
291
+ // this directory from elsewhere reads as an ordinary file and hands over its tip. The writer
292
+ // creates each log fresh, so more than one name for it is a state it cannot produce, which
293
+ // is the same reason the symlink is refused rather than resolved.
294
+ if (st.nlink > 1)
295
+ throw new SubjectFrontierCorruptError(walPath, "a thread log with exactly one name", `it has ${st.nlink}, so the same file is reachable from outside this principal's directory and its tip is not this principal's to read`);
296
+ let raw;
297
+ try {
298
+ const fh = await open(walPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
299
+ try {
300
+ raw = await fh.readFile();
301
+ }
302
+ finally {
303
+ await fh.close();
304
+ }
305
+ }
306
+ catch (e) {
307
+ const code = e.code;
308
+ if (code === "ENOENT" || code === "ENOTDIR")
309
+ continue;
310
+ if (code === "ELOOP")
311
+ throw new SubjectFrontierCorruptError(walPath, "a real thread log, never a symlink", "the file became a symlink between the check and the open");
312
+ throw e;
313
+ }
314
+ let doc;
315
+ try {
316
+ doc = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(raw));
317
+ }
318
+ catch (e) {
319
+ throw new SubjectFrontierCorruptError(walPath, "a readable thread log while recovering the subject tip", e.message);
320
+ }
321
+ // A log under another principal's directory is not ours to read a tip from. It cannot happen
322
+ // in the shipped layout, which is why it is checked: a recovery component must refuse the
323
+ // states its own writer cannot produce.
324
+ if (doc.principal !== principal)
325
+ throw new SubjectFrontierCorruptError(walPath, `a thread log for principal ${principal}`, `found ${String(doc.principal)}`);
326
+ const seq = doc.frontier?.lastSubjectSeq;
327
+ if (!isSafeNonNegInt(seq))
328
+ throw new SubjectFrontierCorruptError(walPath, "frontier.lastSubjectSeq is a safe non-negative integer", String(seq));
329
+ if (seq > best)
330
+ best = seq;
331
+ // AN ACKED PENDING HOLDS A SEQUENCE THE BROKER ASSIGNED AND THE FRONTIER DOES NOT.
332
+ //
333
+ // A log that took an ack and died before folding keeps the assigned sequence in
334
+ // `pending.ackSeq`, which `EventWal` requires to be strictly AHEAD of
335
+ // `frontier.lastSubjectSeq` (that is the crash window the acked state exists to survive).
336
+ // Reading the frontier alone recovers the older number, persists it, and hands the next
337
+ // publish an expectation the subject passed some time ago. The session that could fold it
338
+ // will never run again, because upgrading forks the session id, so this log is the only
339
+ // place that sequence survives. Under-counting here is the same defect this file fixes,
340
+ // reintroduced at the one boundary the file exists for.
341
+ const pending = doc.pending;
342
+ if (pending && pending.state === "acked") {
343
+ const acked = pending.ackSeq;
344
+ if (!isSafeNonNegInt(acked))
345
+ throw new SubjectFrontierCorruptError(walPath, "an acked pending carries a safe non-negative ackSeq", String(acked));
346
+ if (!(acked > seq))
347
+ throw new SubjectFrontierCorruptError(walPath, "an acked pending is ahead of the frontier it will fold into", `ackSeq=${acked} frontier.lastSubjectSeq=${seq}`);
348
+ if (acked > best)
349
+ best = acked;
350
+ }
351
+ // A `sent_unacked` PENDING IS PASSED OVER DELIBERATELY, AND NOT BECAUSE NOTHING WAS ASSIGNED.
352
+ //
353
+ // An earlier description of this scan said the broker assigned nothing to such a frame. That
354
+ // is the one thing the state does not know: the frame went out and the acknowledgement was
355
+ // never observed, so the subject may or may not have taken it. What is certain is structural
356
+ // and about the log rather than the broker: `sent_unacked` carries no `ackSeq` at all, and a
357
+ // document that pairs the two is refused as contradicting its own tag. There is therefore no
358
+ // sequence in it to fold, and inventing one, `frontier.lastSubjectSeq + 1` for instance, would
359
+ // assert an assignment nobody saw.
360
+ //
361
+ // The residue is real and is left standing on purpose. If the broker did assign a sequence to
362
+ // that frame, this scan recovers a number one short of the tip, and the next publish halts on
363
+ // a moved tip rather than publishing into a gap or overwriting anything. That is the safe
364
+ // direction of the two, it is the halt this file's message now explains, and its remedy is the
365
+ // one the message names. The owning session would have republished the frozen id and let the
366
+ // broker deduplicate, but that session is exactly what an upgrade forks away from, which is
367
+ // why the case reaches here at all.
368
+ }
369
+ return best;
370
+ }
371
+ // `seedFromThread` used to live here, and it is GONE rather than kept for a caller that might
372
+ // want it. Recovery moved into `open`, which is the only place that can see every sibling log,
373
+ // and what was left behind was a public method that writes a tip into a record whose only
374
+ // precondition is that the record reads 0. A record reading 0 is exactly what abandonment writes
375
+ // after a channel purge, so the leftover was a supported route back into the state this file
376
+ // exists to prevent, with no shipped caller to justify it.
377
+ async reset() {
378
+ // UNCONDITIONAL, and deliberately not re-read. Abandonment is the one thing that legitimately
379
+ // takes the tip backwards, so a record that moved under this writer is not an obstacle to it:
380
+ // clearing is correct whatever the file currently holds.
381
+ return this.serialize(async () => {
382
+ await this.write({ ...this.doc, tip: 0 });
383
+ });
384
+ }
385
+ /** Atomic replace: sibling temp, fsync, rename, fsync the directory. */
386
+ async write(next) {
387
+ const tmp = `${this.path}.${randomUUID()}.tmp`;
388
+ const fh = await open(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
389
+ try {
390
+ await fh.writeFile(JSON.stringify(next));
391
+ await fh.sync();
392
+ }
393
+ finally {
394
+ await fh.close();
395
+ }
396
+ try {
397
+ await rename(tmp, this.path);
398
+ }
399
+ catch (e) {
400
+ await unlink(tmp).catch(() => { });
401
+ throw e;
402
+ }
403
+ // The rename itself must be durable, or a crash can lose the new name and leave the old file:
404
+ // the record would silently go backwards, which is the one direction `advance` refuses.
405
+ //
406
+ // THE SHARED HELPER, NOT A SECOND STRICTER COPY. This was an inline open/sync that let every
407
+ // error propagate, and it sits on the ACK path: on a filesystem that refuses to fsync a
408
+ // directory handle, or under a permission that refuses the open, the same conditions the
409
+ // directory-creating path already tolerates would throw HERE, after the broker has acknowledged
410
+ // the frame, leaving an ack with no durable record and a halt on the next start. Two paths over
411
+ // the same operation disagreeing about which errors are fatal is a divergence, not a policy.
412
+ await fsyncDir(dirname(this.path));
413
+ this.doc = next;
414
+ }
415
+ }
416
+ //# sourceMappingURL=subject-frontier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject-frontier.js","sourceRoot":"","sources":["../src/subject-frontier.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/connector-core",
3
3
  "description": "Shared MCP-bridge runtime for Cotal connectors: the mesh agent, cotal_* tools, and hook relay.",
4
- "version": "0.22.0",
4
+ "version": "0.23.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "devDependencies": {
32
32
  "@ag-ui/core": "0.0.57",
33
33
  "@cotal-ai/smoke-kit": "0.0.0",
34
- "@cotal-ai/core": "0.22.0"
34
+ "@cotal-ai/core": "0.23.0"
35
35
  },
36
36
  "files": [
37
37
  "dist"