@cat-factory/local-server 0.92.1 → 0.93.1

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,49 @@
1
+ import type { Clock, Logger } from '@cat-factory/kernel';
2
+ import { type MachineTelemetryClient } from '@cat-factory/server';
3
+ import type { LocalTelemetryIngestReader } from './sqlite/telemetryIngestReader.js';
4
+ /** A row no batch can ever carry, named so the sweep can REPORT the drop rather than hide it. */
5
+ export interface OversizedRow {
6
+ id: string;
7
+ chars: number;
8
+ }
9
+ /** What one sweep moved, for logging. */
10
+ export interface TelemetryIngestSweepResult {
11
+ runs: number;
12
+ metrics: number;
13
+ snapshots: number;
14
+ searchQueries: number;
15
+ /** Runs left un-marked because their upload failed — retried on the next pass. */
16
+ failed: number;
17
+ /**
18
+ * Rows skipped because one row alone exceeds the mothership's whole-body cap. Reported, never
19
+ * silent: the alternative is a run that can never finish draining, and a cap that drops
20
+ * something has to say so.
21
+ */
22
+ skipped: number;
23
+ }
24
+ export interface TelemetryIngestDeps {
25
+ reader: LocalTelemetryIngestReader;
26
+ client: MachineTelemetryClient;
27
+ clock: Clock;
28
+ log: Logger;
29
+ }
30
+ /**
31
+ * Upload every quiesced run's telemetry once. Pure over its dependencies (no timer), so it is
32
+ * unit-testable directly — the same shape as `sweepLocalTelemetryRetention`.
33
+ *
34
+ * A run whose upload fails is left UNMARKED and counted in `failed`: the next pass retries it from
35
+ * the beginning, which is safe precisely because the mothership's append is idempotent by row id.
36
+ * One run's failure never stops the pass — a single unreachable moment must not park the backlog.
37
+ */
38
+ export declare function sweepTelemetryIngest(deps: TelemetryIngestDeps, now: number): Promise<TelemetryIngestSweepResult>;
39
+ /**
40
+ * Start the periodic telemetry ingest sweep. Best-effort throughout — losing observability must
41
+ * never take the node down, and an unreachable mothership only means the rows stay local until it
42
+ * comes back.
43
+ *
44
+ * Like the retention sweep, the returned stop function is ASYNC and must be AWAITED before the
45
+ * telemetry store is closed: a pass in flight when shutdown closes the SQLite handle would die on
46
+ * "database is not open" and put a spurious error line on every clean exit.
47
+ */
48
+ export declare function startTelemetryIngest(deps: TelemetryIngestDeps): () => Promise<void>;
49
+ //# sourceMappingURL=telemetryIngest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetryIngest.d.ts","sourceRoot":"","sources":["../src/telemetryIngest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAgB,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAyDjG,iGAAiG;AACjG,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;CACd;AAED,yCAAyC;AACzC,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,0BAA0B,CAAA;IAClC,MAAM,EAAE,sBAAsB,CAAA;IAC9B,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAoFD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,mBAAmB,EACzB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,0BAA0B,CAAC,CAqErC;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CA6BnF"}
@@ -0,0 +1,226 @@
1
+ import { startSweeper } from '@cat-factory/node-server';
2
+ import { MAX_TELEMETRY_INGEST_CHARS, MachineTokenUnavailableError, TELEMETRY_INGEST_LIMITS, } from '@cat-factory/server';
3
+ // The UPSTREAM half of the mothership-mode telemetry bucket (docs/initiatives/mothership-mode.md,
4
+ // PR 5). The capture half writes every LLM call, dispatch context and performed search to the
5
+ // laptop's `node:sqlite` store, because putting a network round trip on the hot path of every
6
+ // model call is exactly what the local-first bucket exists to avoid. This is the background sweep
7
+ // that carries a FINISHED run's rows up to the mothership afterwards, so hosted teammates can read
8
+ // the observability of a run a developer drove, and so those rows outlive the node's short local
9
+ // retention window.
10
+ //
11
+ // Two design points worth keeping:
12
+ //
13
+ // - "Finished" is QUIESCENCE, not a run-status read. The node holds no execution index of its
14
+ // own (runs live on the mothership), so asking "which runs ended" would mean a remote query
15
+ // per candidate — the N+1 this bucket was created to avoid. A run that has produced no
16
+ // telemetry for the grace period is done as far as its telemetry is concerned, and a RESUMED
17
+ // run simply becomes a candidate again on its next quiet period.
18
+ // - Progress is a per-run high-water mark, advanced only after the whole run drained. A node
19
+ // that dies mid-drain re-offers the rows it already sent, which costs bandwidth and nothing
20
+ // else: the mothership's append is idempotent by row id.
21
+ /** How often the sweep runs. Telemetry is read after the fact, so a slow cadence is right. */
22
+ const INGEST_SWEEP_INTERVAL_MS = 5 * 60 * 1000;
23
+ /**
24
+ * How long a run must produce no telemetry before it is uploaded. Long enough that an agent step
25
+ * thinking between model calls is not mistaken for a finished run (which would upload the run
26
+ * repeatedly as it continued), short enough that a developer's teammates see a run's telemetry
27
+ * while it is still the thing being discussed.
28
+ */
29
+ const QUIESCENCE_MS = 10 * 60 * 1000;
30
+ /** Runs uploaded per pass — a bound on how much a single sweep can spend on a backlog. */
31
+ const RUNS_PER_SWEEP = 20;
32
+ /**
33
+ * Rows read per page, per sink. Deliberately the mothership's own per-request ROW caps: a page the
34
+ * node built larger would be refused whole, and the drain would never advance.
35
+ */
36
+ const PAGE_SIZES = TELEMETRY_INGEST_LIMITS;
37
+ /**
38
+ * Headroom left under {@link MAX_TELEMETRY_INGEST_CHARS} for everything in the request body that
39
+ * is not the rows themselves — the envelope's two ids, the sink key, the brackets and separators.
40
+ * Generous, because being a few hundred bytes over the mothership's cap costs a whole refused
41
+ * batch.
42
+ */
43
+ const ENVELOPE_OVERHEAD_CHARS = 4_096;
44
+ /**
45
+ * The byte budget one posted batch may fill. The mothership enforces TWO caps — a row count AND
46
+ * {@link MAX_TELEMETRY_INGEST_CHARS} — and mirroring only the first is what let a page of 20
47
+ * snapshot rows (routinely megabytes each) exceed the byte cap, be refused 413 whole, and leave
48
+ * the run retrying the same doomed page every sweep until the local prune deleted it.
49
+ */
50
+ const BATCH_BUDGET_CHARS = MAX_TELEMETRY_INGEST_CHARS - ENVELOPE_OVERHEAD_CHARS;
51
+ /** The last row of a page, as the cursor the next page resumes from. */
52
+ function cursorOf(rows) {
53
+ const last = rows[rows.length - 1];
54
+ return last ? { createdAt: last.createdAt, id: last.id } : undefined;
55
+ }
56
+ /**
57
+ * Split one page into batches that each fit {@link BATCH_BUDGET_CHARS}, separating out any row
58
+ * that cannot fit ALONE.
59
+ *
60
+ * The row caps bound COUNT; this bounds BYTES, which is the axis a snapshot row moves — the whole
61
+ * composed prompt plus every injected context file's body. Sizing is by the row's own serialized
62
+ * length, the same measure the mothership applies to the body it receives.
63
+ *
64
+ * A row too big even by itself is returned in `oversized` rather than posted: sending it means a
65
+ * guaranteed 413, which fails the run's whole drain and re-offers the identical page on the next
66
+ * sweep, forever. Skipping it lets the rest of the run through, and the caller reports the drop.
67
+ */
68
+ function budgetBatches(rows) {
69
+ const batches = [];
70
+ const oversized = [];
71
+ let batch = [];
72
+ let used = 0;
73
+ for (const row of rows) {
74
+ const chars = JSON.stringify(row).length;
75
+ if (chars > BATCH_BUDGET_CHARS) {
76
+ oversized.push({ id: row.id, chars });
77
+ continue;
78
+ }
79
+ if (batch.length > 0 && used + chars > BATCH_BUDGET_CHARS) {
80
+ batches.push(batch);
81
+ batch = [];
82
+ used = 0;
83
+ }
84
+ batch.push(row);
85
+ used += chars;
86
+ }
87
+ if (batch.length > 0)
88
+ batches.push(batch);
89
+ return { batches, oversized };
90
+ }
91
+ /**
92
+ * Drain ONE sink of one run, posting each page as one or more byte-budgeted batches. Pages are
93
+ * read forwards on the `(createdAt, id)` keyset, so a run's telemetry lands on the mothership in
94
+ * the order it was captured — which is what keeps a metric's prompt-delta chain readable there.
95
+ *
96
+ * The cursor advances past a skipped oversized row deliberately: it can never be uploaded, so
97
+ * holding the drain on it would strand every row behind it too. The caller names what was dropped.
98
+ */
99
+ async function drainSink(read, limit, post) {
100
+ let cursor;
101
+ const drain = { uploaded: 0, oversized: [] };
102
+ for (;;) {
103
+ const rows = read(cursor, limit);
104
+ if (rows.length === 0)
105
+ return drain;
106
+ const budgeted = budgetBatches(rows);
107
+ for (const batch of budgeted.batches) {
108
+ await post(batch);
109
+ drain.uploaded += batch.length;
110
+ }
111
+ drain.oversized.push(...budgeted.oversized);
112
+ cursor = cursorOf(rows);
113
+ // A short page is the last one; asking again would cost a query to learn nothing.
114
+ if (rows.length < limit)
115
+ return drain;
116
+ }
117
+ }
118
+ /**
119
+ * Upload every quiesced run's telemetry once. Pure over its dependencies (no timer), so it is
120
+ * unit-testable directly — the same shape as `sweepLocalTelemetryRetention`.
121
+ *
122
+ * A run whose upload fails is left UNMARKED and counted in `failed`: the next pass retries it from
123
+ * the beginning, which is safe precisely because the mothership's append is idempotent by row id.
124
+ * One run's failure never stops the pass — a single unreachable moment must not park the backlog.
125
+ */
126
+ export async function sweepTelemetryIngest(deps, now) {
127
+ const pending = deps.reader.listPendingRuns(now - QUIESCENCE_MS, RUNS_PER_SWEEP);
128
+ const result = {
129
+ runs: 0,
130
+ metrics: 0,
131
+ snapshots: 0,
132
+ searchQueries: 0,
133
+ failed: 0,
134
+ skipped: 0,
135
+ };
136
+ for (const run of pending) {
137
+ const { workspaceId, executionId } = run;
138
+ const send = (batch) => deps.client.ingest({ workspaceId, executionId, ...batch }).then(() => undefined);
139
+ try {
140
+ const metrics = await drainSink((cursor, limit) => deps.reader.listMetrics(workspaceId, executionId, cursor, limit), PAGE_SIZES.metrics, (rows) => send({ metrics: rows }));
141
+ const snapshots = await drainSink((cursor, limit) => deps.reader.listSnapshots(workspaceId, executionId, cursor, limit), PAGE_SIZES.snapshots, (rows) => send({ snapshots: rows }));
142
+ const searchQueries = await drainSink((cursor, limit) => deps.reader.listSearchQueries(workspaceId, executionId, cursor, limit), PAGE_SIZES.searchQueries, (rows) => send({ searchQueries: rows }));
143
+ result.metrics += metrics.uploaded;
144
+ result.snapshots += snapshots.uploaded;
145
+ result.searchQueries += searchQueries.uploaded;
146
+ // A row too large to ever post is dropped rather than retried forever — but a cap that drops
147
+ // something says what it dropped, naming the rows so the gap in the mothership's view of
148
+ // this run is explicable rather than an unexplained hole.
149
+ const oversized = [...metrics.oversized, ...snapshots.oversized, ...searchQueries.oversized];
150
+ if (oversized.length > 0) {
151
+ result.skipped += oversized.length;
152
+ deps.log.warn('mothership telemetry ingest skipped rows over the body cap', {
153
+ workspaceId,
154
+ executionId,
155
+ skipped: oversized.length,
156
+ capChars: BATCH_BUDGET_CHARS,
157
+ rows: oversized.map((row) => `${row.id}:${row.chars}`).join(','),
158
+ });
159
+ }
160
+ // Marked only once every sink drained — the mark is what stops the run being re-offered, so
161
+ // writing it after a partial upload would strand whatever had not been sent yet.
162
+ deps.reader.markIngested(workspaceId, executionId, run.lastWriteAt, now);
163
+ result.runs += 1;
164
+ }
165
+ catch (error) {
166
+ result.failed += 1;
167
+ // No token yet: EVERY remaining run would fail the same way, so the pass stops here with one
168
+ // line instead of twenty identical ones. The runs stay candidates — nothing was marked.
169
+ if (error instanceof MachineTokenUnavailableError) {
170
+ deps.log.info('mothership telemetry ingest deferred: node has not logged in yet', {
171
+ pending: pending.length,
172
+ });
173
+ return result;
174
+ }
175
+ deps.log.warn('mothership telemetry ingest failed for a run', {
176
+ workspaceId,
177
+ executionId,
178
+ err: error instanceof Error ? error.message : String(error),
179
+ });
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ /**
185
+ * Start the periodic telemetry ingest sweep. Best-effort throughout — losing observability must
186
+ * never take the node down, and an unreachable mothership only means the rows stay local until it
187
+ * comes back.
188
+ *
189
+ * Like the retention sweep, the returned stop function is ASYNC and must be AWAITED before the
190
+ * telemetry store is closed: a pass in flight when shutdown closes the SQLite handle would die on
191
+ * "database is not open" and put a spurious error line on every clean exit.
192
+ */
193
+ export function startTelemetryIngest(deps) {
194
+ let stopped = false;
195
+ let inFlight;
196
+ const stopSweeper = startSweeper({
197
+ name: 'mothership-telemetry-ingest',
198
+ intervalMs: INGEST_SWEEP_INTERVAL_MS,
199
+ log: deps.log,
200
+ failureMessage: 'mothership telemetry ingest sweep failed',
201
+ tick: async () => {
202
+ if (stopped)
203
+ return;
204
+ const pass = sweepTelemetryIngest(deps, deps.clock.now());
205
+ inFlight = pass;
206
+ try {
207
+ const moved = await pass;
208
+ if (moved.runs > 0 || moved.failed > 0 || moved.skipped > 0) {
209
+ deps.log.info('mothership telemetry ingest uploaded runs', { ...moved });
210
+ }
211
+ }
212
+ finally {
213
+ if (inFlight === pass)
214
+ inFlight = undefined;
215
+ }
216
+ },
217
+ });
218
+ return async () => {
219
+ stopped = true;
220
+ stopSweeper();
221
+ // silent-catch-ok: the sweeper's own error handler already logged this pass's failure; here we
222
+ // only need it to have FINISHED touching the store before the caller closes it.
223
+ await inFlight?.catch(() => undefined);
224
+ };
225
+ }
226
+ //# sourceMappingURL=telemetryIngest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetryIngest.js","sourceRoot":"","sources":["../src/telemetryIngest.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,8 @@ export interface LocalTelemetryRetentionResult {
8
8
  agentSearchQueries: number;
9
9
  provisioningLog: number;
10
10
  subscriptionQuotaCycles: number;
11
+ /** Upstream-ingest high-water marks (PR 5's sync UP) whose runs' rows are long gone. */
12
+ ingestState: number;
11
13
  }
12
14
  /**
13
15
  * Prune each local telemetry table to its retention window. Pure over the store, so it is
@@ -1 +1 @@
1
- {"version":3,"file":"telemetryRetention.d.ts","sourceRoot":"","sources":["../src/telemetryRetention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4BrE,mEAAmE;AACnE,MAAM,WAAW,6BAA6B;IAC5C,cAAc,EAAE,MAAM,CAAA;IACtB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,eAAe,EAAE,MAAM,CAAA;IACvB,uBAAuB,EAAE,MAAM,CAAA;CAChC;AAYD;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,6BAA6B,CAAC,CAkBxC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,OAAO,CAAC,IAAI,CAAC,CA8BrB"}
1
+ {"version":3,"file":"telemetryRetention.d.ts","sourceRoot":"","sources":["../src/telemetryRetention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AA4BrE,mEAAmE;AACnE,MAAM,WAAW,6BAA6B;IAC5C,cAAc,EAAE,MAAM,CAAA;IACtB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,eAAe,EAAE,MAAM,CAAA;IACvB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAA;CACpB;AAYD;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,6BAA6B,CAAC,CAyBxC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,OAAO,CAAC,IAAI,CAAC,CA8BrB"}
@@ -41,6 +41,11 @@ export async function sweepLocalTelemetryRetention(store, retention, now) {
41
41
  agentSearchQueries: await prune(retention.llmCallMetricsMs, now, (c) => store.agentSearchQueryRepository.deleteOlderThan(c)),
42
42
  provisioningLog: await prune(retention.provisioningLogMs, now, (c) => store.provisioningLogRepository.deleteOlderThan(c)),
43
43
  subscriptionQuotaCycles: await prune(SUBSCRIPTION_QUOTA_CYCLE_RETENTION_MS, now, (c) => store.subscriptionQuotaCycleRepository.deleteOlderThan(c)),
44
+ // The ingest bookkeeping rides the LLM-call window too, and the ordering is what makes that
45
+ // safe: a mark is stamped at ingest time, which is always at or after the newest row it covers,
46
+ // so the mark outlives the rows it describes and can never be dropped while a still-stored run
47
+ // would be re-uploaded because of it.
48
+ ingestState: await prune(retention.llmCallMetricsMs, now, async (c) => store.ingestReader.deleteIngestStateOlderThan(c)),
44
49
  };
45
50
  }
46
51
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"telemetryRetention.js","sourceRoot":"","sources":["../src/telemetryRetention.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"telemetryRetention.js","sourceRoot":"","sources":["../src/telemetryRetention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Clock, Logger } from '@cat-factory/kernel';
2
+ import type { MachineTelemetryClient, RetentionConfig } from '@cat-factory/server';
3
+ import type { LocalTelemetryStore } from './sqlite/telemetryStore.js';
4
+ export interface MothershipTelemetrySweepDeps {
5
+ store: LocalTelemetryStore;
6
+ client: MachineTelemetryClient;
7
+ retention: RetentionConfig;
8
+ clock: Clock;
9
+ log: Logger;
10
+ }
11
+ /**
12
+ * Start the local telemetry PRUNE and the UPSTREAM ingest, returning one stop function that awaits
13
+ * both.
14
+ *
15
+ * - The prune is the only thing bounding the store: the mothership's cron owns ITS tables, and the
16
+ * Node facade's retention sweeper runs from `start()`, which a mothership-mode boot never calls.
17
+ * Left unpruned, `llm_call_metrics` — full per-call prompt + response bodies — grows without
18
+ * bound on the developer's disk.
19
+ * - The ingest is what stops the prune being a DATA LOSS: it carries a quiesced run's rows up to
20
+ * the mothership, so a run this laptop drove stays readable (by hosted teammates, and by anyone
21
+ * at all) after the local window passes.
22
+ *
23
+ * The returned stop is ASYNC and must be AWAITED before the store closes — each sweep's first pass
24
+ * is asynchronous, so an un-awaited one dies on "database is not open" and puts a spurious error
25
+ * line on every clean exit.
26
+ */
27
+ export declare function startMothershipTelemetrySweeps(deps: MothershipTelemetrySweepDeps): () => Promise<void>;
28
+ //# sourceMappingURL=telemetrySweeps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetrySweeps.d.ts","sourceRoot":"","sources":["../src/telemetrySweeps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,KAAK,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAWrE,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,mBAAmB,CAAA;IAC1B,MAAM,EAAE,sBAAsB,CAAA;IAC9B,SAAS,EAAE,eAAe,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,4BAA4B,GACjC,MAAM,OAAO,CAAC,IAAI,CAAC,CAiBrB"}
@@ -0,0 +1,32 @@
1
+ import { startTelemetryIngest } from './telemetryIngest.js';
2
+ import { startLocalTelemetryRetention } from './telemetryRetention.js';
3
+ /**
4
+ * Start the local telemetry PRUNE and the UPSTREAM ingest, returning one stop function that awaits
5
+ * both.
6
+ *
7
+ * - The prune is the only thing bounding the store: the mothership's cron owns ITS tables, and the
8
+ * Node facade's retention sweeper runs from `start()`, which a mothership-mode boot never calls.
9
+ * Left unpruned, `llm_call_metrics` — full per-call prompt + response bodies — grows without
10
+ * bound on the developer's disk.
11
+ * - The ingest is what stops the prune being a DATA LOSS: it carries a quiesced run's rows up to
12
+ * the mothership, so a run this laptop drove stays readable (by hosted teammates, and by anyone
13
+ * at all) after the local window passes.
14
+ *
15
+ * The returned stop is ASYNC and must be AWAITED before the store closes — each sweep's first pass
16
+ * is asynchronous, so an un-awaited one dies on "database is not open" and puts a spurious error
17
+ * line on every clean exit.
18
+ */
19
+ export function startMothershipTelemetrySweeps(deps) {
20
+ const stopRetention = startLocalTelemetryRetention(deps.store, deps.retention, deps.clock, deps.log);
21
+ const stopIngest = startTelemetryIngest({
22
+ reader: deps.store.ingestReader,
23
+ client: deps.client,
24
+ clock: deps.clock,
25
+ log: deps.log,
26
+ });
27
+ return async () => {
28
+ await stopRetention();
29
+ await stopIngest();
30
+ };
31
+ }
32
+ //# sourceMappingURL=telemetrySweeps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetrySweeps.js","sourceRoot":"","sources":["../src/telemetrySweeps.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AAiBtE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,8BAA8B,CAC5C,IAAkC;IAElC,MAAM,aAAa,GAAG,4BAA4B,CAChD,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,GAAG,CACT,CAAA;IACD,MAAM,UAAU,GAAG,oBAAoB,CAAC;QACtC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAA;IACF,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,aAAa,EAAE,CAAA;QACrB,MAAM,UAAU,EAAE,CAAA;IACpB,CAAC,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/local-server",
3
- "version": "0.92.1",
3
+ "version": "0.93.1",
4
4
  "description": "Local-mode runtime facade for the Agent Architecture Board: the @cat-factory/node-server stack with agent jobs run as local Docker/Podman containers and GitHub accessed via a personal access token — a developer can run the whole product on their own machine.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,23 +27,23 @@
27
27
  "@hono/node-server": "^2.0.12",
28
28
  "drizzle-orm": "1.0.0-rc.4",
29
29
  "ws": "^8.21.1",
30
- "@cat-factory/agents": "0.94.0",
31
- "@cat-factory/contracts": "0.202.0",
32
- "@cat-factory/executor-harness": "1.82.0",
33
- "@cat-factory/gitlab": "0.14.13",
34
- "@cat-factory/integrations": "0.113.5",
35
- "@cat-factory/kernel": "0.201.0",
36
- "@cat-factory/node-server": "0.144.1",
37
- "@cat-factory/orchestration": "0.177.1",
38
- "@cat-factory/server": "0.185.2"
30
+ "@cat-factory/agents": "0.95.1",
31
+ "@cat-factory/contracts": "0.203.0",
32
+ "@cat-factory/executor-harness": "1.84.0",
33
+ "@cat-factory/gitlab": "0.14.15",
34
+ "@cat-factory/integrations": "0.113.7",
35
+ "@cat-factory/kernel": "0.202.0",
36
+ "@cat-factory/node-server": "0.145.0",
37
+ "@cat-factory/orchestration": "0.178.1",
38
+ "@cat-factory/server": "0.187.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "^26.1.1",
42
42
  "@types/ws": "^8.18.1",
43
43
  "typescript": "7.0.2",
44
44
  "vitest": "^4.1.10",
45
- "@cat-factory/conformance": "0.16.11",
46
- "@cat-factory/gates": "0.8.30"
45
+ "@cat-factory/conformance": "0.16.13",
46
+ "@cat-factory/gates": "0.8.32"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsc -b tsconfig.build.json",