@meterbility/collector 0.3.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.
- package/LICENSE +33 -0
- package/README.md +11 -0
- package/dist/index.d.ts +497 -0
- package/dist/index.js +1475 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Meterbility authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
----------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
Note: This MIT license covers everything in this repository EXCEPT the
|
|
26
|
+
contents of the /ee directory (when present), which are licensed under the
|
|
27
|
+
Elastic License 2.0 (ELv2) — see /ee/LICENSE.
|
|
28
|
+
|
|
29
|
+
The /ee directory is reserved for Enterprise Edition modules: multi-tenant
|
|
30
|
+
fleet orchestration, SSO/RBAC, audit logs, and long-retention features.
|
|
31
|
+
|
|
32
|
+
Nothing under /ee today; the directory is created as a forward-compatible
|
|
33
|
+
marker so the licensing boundary is visible before any /ee code lands.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @meterbility/collector
|
|
2
|
+
|
|
3
|
+
Part of [Meterbility](https://github.com/HoneycombHairDevelopers/Meterbility) — the debugger for AI agents. Capture every run, inspect every decision, pause and inject live, fork from any step.
|
|
4
|
+
|
|
5
|
+
The SQLite data plane: schema, queries, content-addressed blob store, baseline trees, and replay primitives.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @meterbility/collector
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See the [Meterbility documentation](https://github.com/HoneycombHairDevelopers/Meterbility#readme) for the full guide. MIT licensed.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { StepStatus, Step, TokenUsage, FileChange, BaselineTree, Run, AnnotationKind, AnnotationVerdict, Annotation, ForkEdit, ProbeState, Project, WorkingTree, ManifestEntry, IgnoreMatcher } from '@meterbility/shared';
|
|
3
|
+
export { BaselineTree } from '@meterbility/shared';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Two-stage heuristic for "is this buffer safe to send through the
|
|
7
|
+
* redaction pipeline?". Used to gate `redactBuffer` so PNG / .woff2 /
|
|
8
|
+
* lockfile / `.pyc` bytes don't get round-tripped through
|
|
9
|
+
* `String#replace` and shredded by U+FFFD substitution.
|
|
10
|
+
*
|
|
11
|
+
* Stage 1 — Full-buffer NUL scan. UTF-8 source code — even with CJK,
|
|
12
|
+
* emoji, every weird code point — never contains 0x00. A NUL byte
|
|
13
|
+
* anywhere is strong evidence of binary content. For files whose
|
|
14
|
+
* binary signature is at the start (PNG, ELF, .pyc, .woff2) this
|
|
15
|
+
* bails at byte 7-12 and is effectively O(1). Removing the previous
|
|
16
|
+
* 8KB cap closes the rare-but-real "binary file with all-non-NUL
|
|
17
|
+
* first 8KB" gap (gzip headers, some compressed formats can hit it).
|
|
18
|
+
*
|
|
19
|
+
* Stage 2 — Round-trip-length check. Even with no NUL, a buffer can
|
|
20
|
+
* still be invalid UTF-8 (stray continuation bytes, truncated multi-
|
|
21
|
+
* byte sequences). The redaction pipeline calls
|
|
22
|
+
* `Buffer.toString('utf-8')` which silently replaces invalid bytes
|
|
23
|
+
* with U+FFFD (3 encoded bytes per replacement). If the re-encoded
|
|
24
|
+
* length differs from the input, the round trip is lossy and would
|
|
25
|
+
* corrupt the caller's bytes. Classify as binary so the bytes survive.
|
|
26
|
+
*
|
|
27
|
+
* Both stages are O(n). Cost is dominated by the redaction pipeline
|
|
28
|
+
* itself (which also runs `toString` on the buffer), so this adds at
|
|
29
|
+
* most one extra full pass to the redact path. Callers who already
|
|
30
|
+
* know their bytes are binary can still pass `skipRedact: true` to
|
|
31
|
+
* skip both stages entirely.
|
|
32
|
+
*/
|
|
33
|
+
declare function isProbablyText(buf: Buffer): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Content-addressed blob store.
|
|
36
|
+
*
|
|
37
|
+
* Layout: $METERBILITY_HOME/blobs/<aa>/<bb>/<sha256>
|
|
38
|
+
*
|
|
39
|
+
* Two-level sharding (256 × 256) keeps any single directory under a few
|
|
40
|
+
* thousand files even on heavy users. Writes are write-once: if the file
|
|
41
|
+
* exists, we trust it (SHA collision is the universe's problem, not ours).
|
|
42
|
+
*
|
|
43
|
+
* Every text write passes through the redaction pass and emits a
|
|
44
|
+
* `redaction_log` row when a rule fires so the user can audit what was
|
|
45
|
+
* scrubbed. **Binary writes skip redaction entirely** — `redactBuffer`
|
|
46
|
+
* goes via `String#replace`, which would shred any non-UTF-8 bytes. The
|
|
47
|
+
* `isProbablyText` heuristic gates this automatically; callers who
|
|
48
|
+
* already know the bytes are binary (e.g. v0.3 file capture) can also
|
|
49
|
+
* pass `skipRedact: true` to be explicit.
|
|
50
|
+
*/
|
|
51
|
+
declare class BlobStore {
|
|
52
|
+
private db;
|
|
53
|
+
constructor(db: Database.Database);
|
|
54
|
+
putString(content: string, opts?: {
|
|
55
|
+
skipRedact?: boolean;
|
|
56
|
+
}): Promise<string>;
|
|
57
|
+
putJson(value: unknown, opts?: {
|
|
58
|
+
skipRedact?: boolean;
|
|
59
|
+
}): Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Hash without persisting — useful when you want a snapshot id before
|
|
62
|
+
* deciding whether to write.
|
|
63
|
+
*/
|
|
64
|
+
hashJson(value: unknown): string;
|
|
65
|
+
putBuffer(buf: Buffer, opts?: {
|
|
66
|
+
skipRedact?: boolean;
|
|
67
|
+
}): Promise<string>;
|
|
68
|
+
getString(hash: string): Promise<string>;
|
|
69
|
+
getJson<T = unknown>(hash: string): Promise<T>;
|
|
70
|
+
getBuffer(hash: string): Promise<Buffer>;
|
|
71
|
+
tryGetString(hash: string): Promise<string | undefined>;
|
|
72
|
+
/**
|
|
73
|
+
* Binary-safe variant of `tryGetString`. Used by /api/blob/:hash/render
|
|
74
|
+
* which has to look at raw bytes for MIME sniffing before deciding
|
|
75
|
+
* whether to render text or serve as image/octet-stream.
|
|
76
|
+
*/
|
|
77
|
+
tryGetBuffer(hash: string): Promise<Buffer | undefined>;
|
|
78
|
+
/**
|
|
79
|
+
* Write pre-computed HTML (or other text) under a caller-chosen
|
|
80
|
+
* synthetic key, bypassing the content-addressed hash. Used by the
|
|
81
|
+
* blob_render cache to store rendered HTML under a key derived
|
|
82
|
+
* from `sha(blob_hash + lang + RENDER_VERSION)` rather than the
|
|
83
|
+
* content's own hash — this way two different renders of the same
|
|
84
|
+
* source code (e.g. lang=auto vs lang=typescript) cache separately
|
|
85
|
+
* but predictably.
|
|
86
|
+
*
|
|
87
|
+
* Skips redaction (renders are derived from already-stored blobs
|
|
88
|
+
* that went through redaction at put time).
|
|
89
|
+
*/
|
|
90
|
+
putWithKey(content: string, key: string): Promise<void>;
|
|
91
|
+
rootDir(): string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* One-stop entry into the local data plane. `open()` creates the
|
|
96
|
+
* `$METERBILITY_HOME` tree if needed, applies the schema, and exposes both the
|
|
97
|
+
* raw SQLite handle (for queries.ts) and the blob store.
|
|
98
|
+
*/
|
|
99
|
+
declare class Store {
|
|
100
|
+
readonly db: Database.Database;
|
|
101
|
+
readonly blobs: BlobStore;
|
|
102
|
+
private constructor();
|
|
103
|
+
static open(opts?: {
|
|
104
|
+
path?: string;
|
|
105
|
+
}): Store;
|
|
106
|
+
static openAsync(opts?: {
|
|
107
|
+
path?: string;
|
|
108
|
+
}): Promise<Store>;
|
|
109
|
+
close(): void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Schema — flat tables that map directly onto SPEC §6 entities. JSON
|
|
114
|
+
* columns hold the structured-but-not-indexed parts (action, outcome,
|
|
115
|
+
* tags). Foreign keys are enforced; large content lives in the blob store
|
|
116
|
+
* referenced by SHA256.
|
|
117
|
+
*
|
|
118
|
+
* Migration policy (locked in by SPEC v0.2 §17 and re-affirmed by v0.3
|
|
119
|
+
* §3.3): **additive only**. No renames, no drops, no CHECK-constraint
|
|
120
|
+
* tightening — SQLite can't ALTER a CHECK without a table rebuild, so we
|
|
121
|
+
* declare full enum coverage up front even for values future milestones
|
|
122
|
+
* write. v0.3 only writes `derived_from='tool_call'` and three of the
|
|
123
|
+
* five ops, but the constraint allows all of them so v0.4's file watcher
|
|
124
|
+
* and v0.5's normalizer don't need a migration.
|
|
125
|
+
*
|
|
126
|
+
* Version history:
|
|
127
|
+
* v1 → v2 — split 5m vs 1h cache write tokens on `steps`
|
|
128
|
+
* v2 → v3 — settings table for the web UI Settings page
|
|
129
|
+
* v3 → v4 — file_change, baseline_tree, runs.baseline_tree_id,
|
|
130
|
+
* runs.probe_state (Track A file capture + Track B Live Probe)
|
|
131
|
+
*/
|
|
132
|
+
declare const SCHEMA_VERSION = 5;
|
|
133
|
+
declare function ensureSchema(db: Database.Database): void;
|
|
134
|
+
|
|
135
|
+
declare function upsertProjectByCwd(store: Store, cwd: string, name?: string): Project;
|
|
136
|
+
declare function upsertAgent(store: Store, projectId: string, name: string): {
|
|
137
|
+
agent_id: string;
|
|
138
|
+
project_id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
created_at: string;
|
|
141
|
+
};
|
|
142
|
+
declare function insertRun(store: Store, run: Run): void;
|
|
143
|
+
declare function updateRunTotals(store: Store, runId: string): void;
|
|
144
|
+
declare function setRunStatus(store: Store, runId: string, status: StepStatus, endedAt?: string): void;
|
|
145
|
+
declare function insertStep(store: Store, step: Step): void;
|
|
146
|
+
declare function recordContextSnapshot(store: Store, snapshotId: string, blobRef: string, componentCount: number): void;
|
|
147
|
+
/** Translate a logical snapshot_id (hash of components) into the blob_ref
|
|
148
|
+
* under which the serialized snapshot is stored. Falls back to using the
|
|
149
|
+
* snapshot_id itself if no mapping is recorded — handy for round-tripped
|
|
150
|
+
* traces where the two hashes happen to coincide. */
|
|
151
|
+
declare function resolveSnapshotBlobRef(store: Store, snapshotId: string): string;
|
|
152
|
+
declare function getRun(store: Store, runId: string): Run | undefined;
|
|
153
|
+
interface ListRunsOpts {
|
|
154
|
+
limit?: number;
|
|
155
|
+
projectId?: string;
|
|
156
|
+
agentId?: string;
|
|
157
|
+
status?: StepStatus;
|
|
158
|
+
containsTool?: string;
|
|
159
|
+
}
|
|
160
|
+
declare function listRuns(store: Store, opts?: ListRunsOpts): Run[];
|
|
161
|
+
declare function listSteps(store: Store, runId: string): Step[];
|
|
162
|
+
declare function getStep(store: Store, stepId: string): Step | undefined;
|
|
163
|
+
declare function getStepBySequence(store: Store, runId: string, sequence: number): Step | undefined;
|
|
164
|
+
declare function insertFork(store: Store, args: {
|
|
165
|
+
originRunId: string;
|
|
166
|
+
originStepId: string;
|
|
167
|
+
forkRunId: string;
|
|
168
|
+
edit: ForkEdit;
|
|
169
|
+
}): string;
|
|
170
|
+
declare function listForks(store: Store, originRunId: string): Array<{
|
|
171
|
+
fork_id: string;
|
|
172
|
+
origin_run_id: string;
|
|
173
|
+
origin_step_id: string;
|
|
174
|
+
fork_run_id: string;
|
|
175
|
+
edit_type: string;
|
|
176
|
+
created_at: string;
|
|
177
|
+
}>;
|
|
178
|
+
declare function insertAnnotation(store: Store, args: {
|
|
179
|
+
targetKind: "step" | "run";
|
|
180
|
+
targetId: string;
|
|
181
|
+
author: string;
|
|
182
|
+
/**
|
|
183
|
+
* Annotation discriminator (v0.3 schema v5). Defaults to 'comment'
|
|
184
|
+
* — human-authored notes via the Annotate CLI / web UI keep the
|
|
185
|
+
* back-compat shape. System-emitted events pass their own kind
|
|
186
|
+
* (`probe_pause`, `probe_edit`, `capture_skipped`).
|
|
187
|
+
*/
|
|
188
|
+
kind?: AnnotationKind;
|
|
189
|
+
verdict?: AnnotationVerdict;
|
|
190
|
+
note?: string;
|
|
191
|
+
}): Annotation;
|
|
192
|
+
declare function listAnnotations(store: Store, targetKind: "step" | "run", targetId: string): Annotation[];
|
|
193
|
+
declare function getIngestOffset(store: Store, source_runtime: string, source_path: string): number;
|
|
194
|
+
declare function setIngestOffset(store: Store, source_runtime: string, source_path: string, offset: number): void;
|
|
195
|
+
declare function getRunBySessionId(store: Store, sourceSessionId: string): Run | undefined;
|
|
196
|
+
declare function aggregateTokens(steps: Step[]): TokenUsage;
|
|
197
|
+
/**
|
|
198
|
+
* Thrown by `insertFileChange` when a candidate row violates the
|
|
199
|
+
* biconditional contract documented on `FileChange` in
|
|
200
|
+
* `packages/shared/src/types.ts:242-244`:
|
|
201
|
+
*
|
|
202
|
+
* - `before_blob_ref` is null iff op === "create" OR partial_diff
|
|
203
|
+
* - `after_blob_ref` is null iff op === "delete" OR partial_diff
|
|
204
|
+
* - op === "rename" requires `old_path`
|
|
205
|
+
*
|
|
206
|
+
* `chmod` is the documented exception — it's a mode-only operation
|
|
207
|
+
* and is exempt from both blob_ref biconditionals. The replay layer
|
|
208
|
+
* (`applyFileChange`) treats chmod blob refs as a no-op regardless,
|
|
209
|
+
* so accepting either shape is safe.
|
|
210
|
+
*
|
|
211
|
+
* Hard-fail by design: an adapter that produces an invariant-violating
|
|
212
|
+
* row would otherwise ship the malformation silently into the DB and
|
|
213
|
+
* surface as a downstream rendering or replay bug. Better to throw at
|
|
214
|
+
* the boundary.
|
|
215
|
+
*/
|
|
216
|
+
declare class FileChangeInvariantError extends Error {
|
|
217
|
+
constructor(message: string);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Verify a candidate FileChange satisfies the contract on `FileChange`
|
|
221
|
+
* in `packages/shared/src/types.ts`. Throws `FileChangeInvariantError`
|
|
222
|
+
* with a clear message on the first violation. Pure; no side effects.
|
|
223
|
+
*
|
|
224
|
+
* Exported for direct testing.
|
|
225
|
+
*/
|
|
226
|
+
declare function assertFileChangeInvariants(fc: {
|
|
227
|
+
op: FileChange["op"];
|
|
228
|
+
partial_diff: boolean;
|
|
229
|
+
before_blob_ref?: string;
|
|
230
|
+
after_blob_ref?: string;
|
|
231
|
+
old_path?: string;
|
|
232
|
+
}): void;
|
|
233
|
+
/**
|
|
234
|
+
* Insert a FileChange row. ID auto-generated if not provided.
|
|
235
|
+
*
|
|
236
|
+
* Idempotency: the schema's `UNIQUE(step_id, sequence)` constraint
|
|
237
|
+
* means writing the same logical FileChange twice will fail loudly.
|
|
238
|
+
* Adapters should compute deterministic `(step_id, sequence)` so a
|
|
239
|
+
* re-ingest of the same JSONL session doesn't duplicate rows.
|
|
240
|
+
*
|
|
241
|
+
* `created_at` defaults to "now" if omitted — adapters usually want
|
|
242
|
+
* this since they don't have a per-FileChange wall-clock from the
|
|
243
|
+
* source.
|
|
244
|
+
*
|
|
245
|
+
* Validation: every row passes through `assertFileChangeInvariants`
|
|
246
|
+
* before the INSERT. Adapter bugs that would otherwise ship malformed
|
|
247
|
+
* rows surface here as a thrown `FileChangeInvariantError` rather
|
|
248
|
+
* than as a silent downstream replay bug.
|
|
249
|
+
*/
|
|
250
|
+
declare function insertFileChange(store: Store, fc: Omit<FileChange, "file_change_id" | "created_at"> & {
|
|
251
|
+
file_change_id?: string;
|
|
252
|
+
created_at?: string;
|
|
253
|
+
}): FileChange;
|
|
254
|
+
interface ListFileChangesOpts {
|
|
255
|
+
runId?: string;
|
|
256
|
+
stepId?: string;
|
|
257
|
+
path?: string;
|
|
258
|
+
/**
|
|
259
|
+
* Used by the replay algorithm: include only FileChanges for steps
|
|
260
|
+
* with sequence < this value. Implemented via a join on `steps`. If
|
|
261
|
+
* `runId` is also given, the join is scoped to that run.
|
|
262
|
+
*/
|
|
263
|
+
maxStepSeqExclusive?: number;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sort order is `(step.sequence ASC, file_change.sequence ASC)` which
|
|
267
|
+
* matches the replay algorithm's contract (v0.3 §3.6): later steps win
|
|
268
|
+
* over earlier ones, and within a step, the intra-step `sequence` field
|
|
269
|
+
* preserves atomic-batch ordering (MultiEdit fans out N rows in order).
|
|
270
|
+
*/
|
|
271
|
+
declare function listFileChanges(store: Store, opts?: ListFileChangesOpts): FileChange[];
|
|
272
|
+
declare function getFileChange(store: Store, fileChangeId: string): FileChange | undefined;
|
|
273
|
+
declare function insertBaselineTree(store: Store, bt: Omit<BaselineTree, "baseline_tree_id" | "captured_at"> & {
|
|
274
|
+
baseline_tree_id?: string;
|
|
275
|
+
captured_at?: string;
|
|
276
|
+
}): BaselineTree;
|
|
277
|
+
declare function getBaselineTree(store: Store, baselineTreeId: string): BaselineTree | undefined;
|
|
278
|
+
/**
|
|
279
|
+
* Find an existing baseline_tree row by its manifest blob ref — useful
|
|
280
|
+
* during baseline capture so two runs against the same git HEAD don't
|
|
281
|
+
* create two rows pointing at the same content. v0.3 §3.5 calls this
|
|
282
|
+
* out as the dominant dedup win.
|
|
283
|
+
*/
|
|
284
|
+
declare function findBaselineByManifest(store: Store, projectId: string, manifestBlobRef: string): BaselineTree | undefined;
|
|
285
|
+
/**
|
|
286
|
+
* Attach a baseline tree to a run. Idempotent: passing the same id
|
|
287
|
+
* twice is a no-op. Passing a different id overwrites (the assumption
|
|
288
|
+
* being that the adapter re-walked the cwd and produced a new
|
|
289
|
+
* manifest — rare but legal).
|
|
290
|
+
*/
|
|
291
|
+
declare function setRunBaselineTree(store: Store, runId: string, baselineTreeId: string): void;
|
|
292
|
+
/**
|
|
293
|
+
* Set the Live Probe state on a run. Pass `null` to clear. v0.3 §4 —
|
|
294
|
+
* only meaningful for source_runtime in (sdk-ts, sdk-py); callers are
|
|
295
|
+
* responsible for that guard.
|
|
296
|
+
*/
|
|
297
|
+
declare function setRunProbeState(store: Store, runId: string, state: ProbeState | null): void;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Tiny key-value store on top of the `settings` SQLite table. Used by
|
|
301
|
+
* the web UI's Settings page so users can configure Slack webhooks,
|
|
302
|
+
* watched tools, default fork model, etc. without re-typing per
|
|
303
|
+
* session.
|
|
304
|
+
*
|
|
305
|
+
* Secrets warning: values are stored in plaintext in `~/.meterbility/meterbility.db`.
|
|
306
|
+
* The Settings UI surfaces this clearly. For Keychain-backed storage,
|
|
307
|
+
* see SPEC-DESKTOP.md (the desktop app's job, not the web UI's).
|
|
308
|
+
*/
|
|
309
|
+
type SettingKey = "slack.webhook" | "slack.default_events" | "live.watch_tools" | "live.stall_seconds" | "fork.default_model" | "fork.default_max_iterations" | "anthropic.api_key" | "postgres.url" | "export.include_file_blobs" | "web.bind_token" | "capture.files.enabled" | "capture.files.max_partial_bytes" | "capture.files.max_skip_bytes";
|
|
310
|
+
declare function getSetting(store: Store, key: SettingKey): string | undefined;
|
|
311
|
+
declare function setSetting(store: Store, key: SettingKey, value: string): void;
|
|
312
|
+
declare function deleteSetting(store: Store, key: SettingKey): void;
|
|
313
|
+
declare function listSettings(store: Store): Array<{
|
|
314
|
+
key: string;
|
|
315
|
+
value: string;
|
|
316
|
+
updated_at: string;
|
|
317
|
+
}>;
|
|
318
|
+
/**
|
|
319
|
+
* Resolve a setting that may also live in an environment variable.
|
|
320
|
+
* Env var wins when both are present — matches CLI semantics where
|
|
321
|
+
* env vars are the authoritative source.
|
|
322
|
+
*/
|
|
323
|
+
declare function resolveSetting(store: Store, key: SettingKey, envVar: string): string | undefined;
|
|
324
|
+
declare function isSecret(key: string): boolean;
|
|
325
|
+
/** Mask a secret value for display: `sk-ant-xxx…last4`. */
|
|
326
|
+
declare function maskSecret(value: string): string;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Serialize a list of manifest entries into the on-disk format. Entries
|
|
330
|
+
* are sorted by path (bytewise on the UTF-8 representation, matching
|
|
331
|
+
* git's convention — deterministic across locales).
|
|
332
|
+
*
|
|
333
|
+
* Throws on illegal paths: NUL (0x00) and newline (0x0A) bytes would
|
|
334
|
+
* break the record separators. POSIX disallows both in filenames; if
|
|
335
|
+
* the agent somehow produced one we refuse rather than silently
|
|
336
|
+
* corrupt the manifest.
|
|
337
|
+
*/
|
|
338
|
+
declare function serializeManifest(entries: ManifestEntry[]): Buffer;
|
|
339
|
+
/**
|
|
340
|
+
* Inverse of `serializeManifest`. Tolerates an absent trailing newline
|
|
341
|
+
* (empty manifests serialize to an empty buffer; we return [] for both
|
|
342
|
+
* `Buffer.alloc(0)` and `Buffer.from([])`).
|
|
343
|
+
*/
|
|
344
|
+
declare function parseManifest(buf: Buffer): ManifestEntry[];
|
|
345
|
+
/**
|
|
346
|
+
* Load a baseline tree's manifest from the blob store and parse it
|
|
347
|
+
* into a WorkingTree (the seed state the replay layers FileChanges on
|
|
348
|
+
* top of). Returns an empty tree if the manifest blob can't be read
|
|
349
|
+
* — better than throwing because a run with a missing baseline can
|
|
350
|
+
* still be partially reconstructed from FileChanges, and the UI can
|
|
351
|
+
* surface a warning instead of a hard failure.
|
|
352
|
+
*/
|
|
353
|
+
declare function loadBaselineTree(store: Store, baseline: BaselineTree): Promise<WorkingTree>;
|
|
354
|
+
interface WorkingTreeAtOpts {
|
|
355
|
+
/**
|
|
356
|
+
* Apply only FileChanges from steps with `sequence < this value`.
|
|
357
|
+
* Default: undefined → include every step in the run (final state).
|
|
358
|
+
* Use 0 to get the baseline (pre-step-0) state.
|
|
359
|
+
*/
|
|
360
|
+
stepSeq?: number;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Compute the working tree at a chosen point in a run.
|
|
364
|
+
*
|
|
365
|
+
* Implements the algorithm in v0.3 §3.6:
|
|
366
|
+
* 1. Load the run's baseline tree (the seed).
|
|
367
|
+
* 2. Apply every FileChange with `step.sequence < stepSeq` in order
|
|
368
|
+
* (step.sequence ASC, file_change.sequence ASC).
|
|
369
|
+
* 3. Return the resulting (path → {blob_ref, mode}) map.
|
|
370
|
+
*
|
|
371
|
+
* Complexity: O(touched_paths). The `idx_fc_run_path` and the
|
|
372
|
+
* `listFileChanges` join keep this bounded by the number of files
|
|
373
|
+
* modified in the run, never by the total files in the repo.
|
|
374
|
+
*
|
|
375
|
+
* Returns an empty tree if:
|
|
376
|
+
* - The run doesn't exist.
|
|
377
|
+
* - The run has no `baseline_tree_id` AND no FileChanges (typical for
|
|
378
|
+
* non-coding runs).
|
|
379
|
+
*
|
|
380
|
+
* Returns a baseline-only tree if the run has a baseline but no
|
|
381
|
+
* FileChanges before `stepSeq` — useful for "what did the agent see
|
|
382
|
+
* walking in?" queries.
|
|
383
|
+
*/
|
|
384
|
+
declare function workingTreeAt(store: Store, runId: string, opts?: WorkingTreeAtOpts): Promise<WorkingTree>;
|
|
385
|
+
/**
|
|
386
|
+
* Apply a single FileChange to a WorkingTree in place. Pulled out so
|
|
387
|
+
* the v0.5 working-tree-scrubber UI can re-use it for incremental
|
|
388
|
+
* updates without re-running the whole replay each frame.
|
|
389
|
+
*
|
|
390
|
+
* `chmod` updates mode bits only — the spec's algorithm sketch passes
|
|
391
|
+
* for blob-ref purposes (content unchanged), but we DO update `mode`
|
|
392
|
+
* when present because mode is also part of the working-tree contract.
|
|
393
|
+
* If `mode_after` is unset, the operation is a true no-op.
|
|
394
|
+
*
|
|
395
|
+
* `partial_diff` FileChanges (typically Bash steps in v0.3) carry no
|
|
396
|
+
* blob refs. They're applied as a no-op for tree reconstruction — the
|
|
397
|
+
* Files UI surfaces the existence of the change separately so the user
|
|
398
|
+
* knows to enable the v0.4 watcher for full fidelity.
|
|
399
|
+
*/
|
|
400
|
+
declare function applyFileChange(tree: WorkingTree, fc: {
|
|
401
|
+
op: string;
|
|
402
|
+
path: string;
|
|
403
|
+
old_path?: string;
|
|
404
|
+
after_blob_ref?: string;
|
|
405
|
+
mode_after?: number;
|
|
406
|
+
partial_diff?: boolean;
|
|
407
|
+
}): void;
|
|
408
|
+
|
|
409
|
+
interface CaptureBaselineResult {
|
|
410
|
+
baseline_tree_id: string;
|
|
411
|
+
/** Manifest blob ref, exposed for diagnostics + tests. */
|
|
412
|
+
manifest_blob_ref: string;
|
|
413
|
+
/** Number of files that made it into the manifest. */
|
|
414
|
+
file_count: number;
|
|
415
|
+
/** Number of files skipped (oversize, unreadable, ignored). */
|
|
416
|
+
skipped: number;
|
|
417
|
+
/** True when this run dedup'd onto an existing baseline_tree row. */
|
|
418
|
+
reused_existing: boolean;
|
|
419
|
+
git_head?: string;
|
|
420
|
+
git_dirty: boolean;
|
|
421
|
+
}
|
|
422
|
+
interface CaptureBaselineOptions {
|
|
423
|
+
/**
|
|
424
|
+
* Pre-built ignore matcher. If omitted, the function loads
|
|
425
|
+
* `<cwd>/.meterbilityignore` + `<cwd>/.gitignore` on top of the
|
|
426
|
+
* shipped defaults.
|
|
427
|
+
*/
|
|
428
|
+
matcher?: IgnoreMatcher;
|
|
429
|
+
/** Override the size cap (mostly for tests). */
|
|
430
|
+
maxFileBytes?: number;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Capture the baseline tree for a project's cwd. Returns `undefined` if
|
|
434
|
+
* the cwd doesn't exist or is empty — the adapter treats that as
|
|
435
|
+
* "non-coding run, no baseline needed" and continues without one.
|
|
436
|
+
*/
|
|
437
|
+
declare function captureBaseline(store: Store, projectId: string, cwd: string, opts?: CaptureBaselineOptions): Promise<CaptureBaselineResult | undefined>;
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* File-size capture policy. Single source of truth for the 5MB / 50MB
|
|
441
|
+
* thresholds per SPEC-V0_3 §11.1. Every adapter that captures files
|
|
442
|
+
* MUST funnel through this helper before calling `insertFileChange`
|
|
443
|
+
* so the policy is uniform — a future adapter (Codex, Cursor,
|
|
444
|
+
* file-watcher) gets the gate for free.
|
|
445
|
+
*
|
|
446
|
+
* Policy (per SPEC-V0_3 §11.1):
|
|
447
|
+
*
|
|
448
|
+
* ≤ max_partial_bytes (default 5 MB): full capture, both blobs kept,
|
|
449
|
+
* partial_diff = false.
|
|
450
|
+
* > max_partial_bytes AND ≤ max_skip_bytes: drop the LARGER side's
|
|
451
|
+
* buffer (keep the smaller for delta context), partial_diff = true.
|
|
452
|
+
* > max_skip_bytes: drop both buffers, redacted = true, emit a stub
|
|
453
|
+
* FileChange row that records the path + op + original sizes so
|
|
454
|
+
* the user knows something happened.
|
|
455
|
+
*
|
|
456
|
+
* Behaviour is pure (no I/O, no settings read inside the helper). The
|
|
457
|
+
* adapter resolves settings once via `getSetting()` and passes the
|
|
458
|
+
* resolved numbers in — per A7 "live-read per FileChange," settings
|
|
459
|
+
* are read at the call site so a mid-run toggle takes effect on the
|
|
460
|
+
* very next FileChange.
|
|
461
|
+
*/
|
|
462
|
+
declare const DEFAULT_MAX_PARTIAL_BYTES = 5000000;
|
|
463
|
+
declare const DEFAULT_MAX_SKIP_BYTES = 50000000;
|
|
464
|
+
interface FileSizePolicySettings {
|
|
465
|
+
/** Files at or under this size are captured in full. */
|
|
466
|
+
max_partial_bytes?: number;
|
|
467
|
+
/** Files at or under this size are partial-captured (larger blob dropped). */
|
|
468
|
+
max_skip_bytes?: number;
|
|
469
|
+
}
|
|
470
|
+
interface FileSizePolicyInput {
|
|
471
|
+
beforeBuf?: Buffer;
|
|
472
|
+
afterBuf?: Buffer;
|
|
473
|
+
settings?: FileSizePolicySettings;
|
|
474
|
+
}
|
|
475
|
+
interface FileSizePolicyResult {
|
|
476
|
+
/** The (possibly nulled) buffer to persist for the before-blob. */
|
|
477
|
+
beforeBuf?: Buffer;
|
|
478
|
+
/** The (possibly nulled) buffer to persist for the after-blob. */
|
|
479
|
+
afterBuf?: Buffer;
|
|
480
|
+
/** True when at least one buffer was dropped due to max_partial_bytes. */
|
|
481
|
+
partial_diff: boolean;
|
|
482
|
+
/** True when both buffers were dropped due to max_skip_bytes. */
|
|
483
|
+
redacted: boolean;
|
|
484
|
+
/** Original (pre-policy) size of before, regardless of whether kept. */
|
|
485
|
+
size_before?: number;
|
|
486
|
+
/** Original (pre-policy) size of after, regardless of whether kept. */
|
|
487
|
+
size_after?: number;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Apply the size-cap policy to the given buffers. Pure function — no
|
|
491
|
+
* I/O, no SQLite, no side effects. Throws on misconfiguration
|
|
492
|
+
* (max_skip ≤ max_partial) so a bad settings value surfaces loudly
|
|
493
|
+
* at ingest rather than silently producing skewed captures.
|
|
494
|
+
*/
|
|
495
|
+
declare function enforceFileSizePolicy(input: FileSizePolicyInput): FileSizePolicyResult;
|
|
496
|
+
|
|
497
|
+
export { BlobStore, type CaptureBaselineOptions, type CaptureBaselineResult, DEFAULT_MAX_PARTIAL_BYTES, DEFAULT_MAX_SKIP_BYTES, FileChangeInvariantError, type FileSizePolicyInput, type FileSizePolicyResult, type FileSizePolicySettings, type ListFileChangesOpts, type ListRunsOpts, SCHEMA_VERSION, type SettingKey, Store, type WorkingTreeAtOpts, aggregateTokens, applyFileChange, assertFileChangeInvariants, captureBaseline, deleteSetting, enforceFileSizePolicy, ensureSchema, findBaselineByManifest, getBaselineTree, getFileChange, getIngestOffset, getRun, getRunBySessionId, getSetting, getStep, getStepBySequence, insertAnnotation, insertBaselineTree, insertFileChange, insertFork, insertRun, insertStep, isProbablyText, isSecret, listAnnotations, listFileChanges, listForks, listRuns, listSettings, listSteps, loadBaselineTree, maskSecret, parseManifest, recordContextSnapshot, resolveSetting, resolveSnapshotBlobRef, serializeManifest, setIngestOffset, setRunBaselineTree, setRunProbeState, setRunStatus, setSetting, updateRunTotals, upsertAgent, upsertProjectByCwd, workingTreeAt };
|