@meterbility/server 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/chunk-VULWJFYO.js +3595 -0
- package/dist/html-QXNPWM2W.js +24 -0
- package/dist/index.d.ts +652 -0
- package/dist/index.js +3886 -0
- package/package.json +59 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderContext,
|
|
3
|
+
renderDiff,
|
|
4
|
+
renderFleet,
|
|
5
|
+
renderProbePanel,
|
|
6
|
+
renderRun,
|
|
7
|
+
renderRunList,
|
|
8
|
+
renderSettings,
|
|
9
|
+
renderShell,
|
|
10
|
+
renderStepCardFragment,
|
|
11
|
+
renderTests
|
|
12
|
+
} from "./chunk-VULWJFYO.js";
|
|
13
|
+
export {
|
|
14
|
+
renderContext,
|
|
15
|
+
renderDiff,
|
|
16
|
+
renderFleet,
|
|
17
|
+
renderProbePanel,
|
|
18
|
+
renderRun,
|
|
19
|
+
renderRunList,
|
|
20
|
+
renderSettings,
|
|
21
|
+
renderShell,
|
|
22
|
+
renderStepCardFragment,
|
|
23
|
+
renderTests
|
|
24
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import * as _meterbility_shared from '@meterbility/shared';
|
|
2
|
+
import { Action, Outcome, TokenUsage, ContextSnapshot, Step, ForkEdit, Run, ConversationMessage } from '@meterbility/shared';
|
|
3
|
+
import { Store } from '@meterbility/collector';
|
|
4
|
+
import { EventEmitter } from 'node:events';
|
|
5
|
+
import * as hono_types from 'hono/types';
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Replay engine. v0 implements two modes:
|
|
10
|
+
*
|
|
11
|
+
* deterministic_prefix: walk steps 0..fork_point, recompute and persist
|
|
12
|
+
* context snapshots and decision refs from cached blobs. No external
|
|
13
|
+
* calls. The new run is a "shadow" of the original until the fork
|
|
14
|
+
* point, then live execution takes over (handled separately by the
|
|
15
|
+
* fork command).
|
|
16
|
+
*
|
|
17
|
+
* fully_live: re-run from start with edit applied. Requires an
|
|
18
|
+
* Anthropic API key in ANTHROPIC_API_KEY. Not implemented in v0;
|
|
19
|
+
* reserved for v0.1.
|
|
20
|
+
*/
|
|
21
|
+
type ReplayMode = "deterministic_prefix" | "fully_live";
|
|
22
|
+
interface ReplayResult {
|
|
23
|
+
run_id: string;
|
|
24
|
+
prefix_steps: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Materialize a deterministic-prefix copy of `originRunId` up to (and
|
|
28
|
+
* including) `originStepId`. The resulting run shares all blob content
|
|
29
|
+
* with the origin (content-addressed dedup) but has its own row in the
|
|
30
|
+
* `runs` table and its own step rows. The Action and Outcome of the
|
|
31
|
+
* fork-point step may be rewritten by the caller before calling
|
|
32
|
+
* {@link liveSuffix}.
|
|
33
|
+
*/
|
|
34
|
+
declare function materializePrefix(store: Store, originRunId: string, forkStepId: string, edit: ForkEdit): Promise<ReplayResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Read the origin step's context snapshot, apply the edit, return a new
|
|
37
|
+
* snapshot. The new id is content-addressed off the rewritten components.
|
|
38
|
+
*/
|
|
39
|
+
declare function rewriteSnapshot(store: Store, snapshotId: string, edit: ForkEdit): Promise<ContextSnapshot>;
|
|
40
|
+
/**
|
|
41
|
+
* Append one synthetic Step to a fork run carrying the live-suffix
|
|
42
|
+
* result. Used after fork_point edit has been applied and the operator
|
|
43
|
+
* has produced (or simulated) a new model response.
|
|
44
|
+
*/
|
|
45
|
+
declare function appendLiveStep(store: Store, runId: string, args: {
|
|
46
|
+
model: string;
|
|
47
|
+
action: Action;
|
|
48
|
+
outcome: Outcome;
|
|
49
|
+
tokens: TokenUsage;
|
|
50
|
+
latency_ms: number;
|
|
51
|
+
parent_step_id?: string;
|
|
52
|
+
snapshot: ContextSnapshot;
|
|
53
|
+
decisionContent: unknown;
|
|
54
|
+
}): Promise<Step>;
|
|
55
|
+
|
|
56
|
+
interface ForkArgs {
|
|
57
|
+
origin_run_id: string;
|
|
58
|
+
/** Either a full step id (stp_…) or a step sequence number. */
|
|
59
|
+
at: string | number;
|
|
60
|
+
edit: ForkEdit;
|
|
61
|
+
}
|
|
62
|
+
interface ForkOutcome {
|
|
63
|
+
fork_id: string;
|
|
64
|
+
fork_run_id: string;
|
|
65
|
+
prefix_steps: number;
|
|
66
|
+
/** True if a live-suffix step was also materialized. False if the
|
|
67
|
+
* fork was created without a live model call (the operator can run
|
|
68
|
+
* one later via the API/CLI). */
|
|
69
|
+
live: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* High-level fork entry point. Resolves the target step, materializes
|
|
73
|
+
* the deterministic prefix, records the fork relationship, and (if
|
|
74
|
+
* `liveSuffix` is provided) generates one live step using the operator-
|
|
75
|
+
* supplied responder. We deliberately avoid hard-coding an Anthropic
|
|
76
|
+
* call here so the engine is testable without a network round-trip and
|
|
77
|
+
* so non-Anthropic providers can plug in later.
|
|
78
|
+
*/
|
|
79
|
+
declare function forkRun(store: Store, args: ForkArgs, liveSuffix?: LiveResponder): Promise<ForkOutcome>;
|
|
80
|
+
interface LiveResponderArgs {
|
|
81
|
+
origin_step: _meterbility_shared.Step;
|
|
82
|
+
context_snapshot_id: string;
|
|
83
|
+
edit: ForkEdit;
|
|
84
|
+
}
|
|
85
|
+
interface LiveResponderResult {
|
|
86
|
+
model: string;
|
|
87
|
+
action: Action;
|
|
88
|
+
outcome?: Outcome;
|
|
89
|
+
tokens: TokenUsage;
|
|
90
|
+
latency_ms?: number;
|
|
91
|
+
decision_content: unknown;
|
|
92
|
+
}
|
|
93
|
+
type LiveResponder = (args: LiveResponderArgs) => Promise<LiveResponderResult>;
|
|
94
|
+
/**
|
|
95
|
+
* A fake responder useful for tests and demos — produces a trivial
|
|
96
|
+
* "message" step with zero tokens. Real implementations call an LLM.
|
|
97
|
+
*/
|
|
98
|
+
declare function fakeResponder(text: string): LiveResponder;
|
|
99
|
+
interface AnthropicResponderOptions {
|
|
100
|
+
apiKey: string;
|
|
101
|
+
model: string;
|
|
102
|
+
systemPrompt?: string;
|
|
103
|
+
maxTokens?: number;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Anthropic-backed live responder. Pulls the conversation history out
|
|
107
|
+
* of the snapshot's blobs, sends it to the API, and packages the
|
|
108
|
+
* response back into a Step. Kept here (not in adapters/) because it is
|
|
109
|
+
* runtime-agnostic — it works for any captured history regardless of
|
|
110
|
+
* which agent runtime produced it.
|
|
111
|
+
*/
|
|
112
|
+
declare function anthropicResponder(store: Store, opts: AnthropicResponderOptions): LiveResponder;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Structural diff. Walks both runs in sequence order, aligning steps by
|
|
116
|
+
* sequence number. v0 only supports this "parallel" mode — semantic
|
|
117
|
+
* (embedding-based) alignment is on the v1 roadmap.
|
|
118
|
+
*/
|
|
119
|
+
type DiffRowKind = "shared" | "context_diff" | "decision_diff" | "action_diff" | "outcome_diff" | "only_a" | "only_b" | "diverged";
|
|
120
|
+
interface DiffRow {
|
|
121
|
+
sequence: number;
|
|
122
|
+
kind: DiffRowKind;
|
|
123
|
+
a?: StepDigest;
|
|
124
|
+
b?: StepDigest;
|
|
125
|
+
}
|
|
126
|
+
interface StepDigest {
|
|
127
|
+
step_id: string;
|
|
128
|
+
model: string;
|
|
129
|
+
context_snapshot_id: string;
|
|
130
|
+
decision_ref: string;
|
|
131
|
+
action_kind: string;
|
|
132
|
+
tool_name?: string;
|
|
133
|
+
outcome_status: string;
|
|
134
|
+
outcome_summary?: string;
|
|
135
|
+
cost_cents: number;
|
|
136
|
+
tokens_total: number;
|
|
137
|
+
status: string;
|
|
138
|
+
}
|
|
139
|
+
interface DiffResult {
|
|
140
|
+
run_a_id: string;
|
|
141
|
+
run_b_id: string;
|
|
142
|
+
total_steps_a: number;
|
|
143
|
+
total_steps_b: number;
|
|
144
|
+
shared_prefix_length: number;
|
|
145
|
+
first_divergence_sequence?: number;
|
|
146
|
+
rows: DiffRow[];
|
|
147
|
+
}
|
|
148
|
+
declare function diffRuns(store: Store, runA: string, runB: string): DiffResult;
|
|
149
|
+
|
|
150
|
+
type LiveStatus = "progressing" | "stalled" | "looping" | "awaiting_input" | "errored" | "completed";
|
|
151
|
+
/**
|
|
152
|
+
* Heuristic context-window utilization derived from the last step's
|
|
153
|
+
* token usage. Anthropic's Opus 4.x models use a 200k window in stable
|
|
154
|
+
* mode and a 1M window in extended mode; we read both totals from the
|
|
155
|
+
* step's tokens and assume the larger of (input + cached_read) vs.
|
|
156
|
+
* (input alone) as the effective load. Returns 0–100.
|
|
157
|
+
*/
|
|
158
|
+
declare function contextUtilization(step?: Step): number;
|
|
159
|
+
/**
|
|
160
|
+
* Classify a run's live status from its step stream. The order matters
|
|
161
|
+
* — a stalled run with an outstanding tool call is "awaiting_input"
|
|
162
|
+
* rather than "stalled," and "errored" beats everything else.
|
|
163
|
+
*/
|
|
164
|
+
declare function classifyRunStatus(run: Run, steps: Step[], stallSeconds: number): LiveStatus;
|
|
165
|
+
/**
|
|
166
|
+
* Loop detection: the last `window` steps share the same tool and the
|
|
167
|
+
* same canonical input. Returns the loop signature or undefined if no
|
|
168
|
+
* loop is detected. window=4 by default; rationale per SPEC §10.x —
|
|
169
|
+
* three repeats can be coincidence, four is a pattern worth surfacing.
|
|
170
|
+
*/
|
|
171
|
+
declare function detectLoop(steps: Step[], window?: number): {
|
|
172
|
+
tool: string;
|
|
173
|
+
signature: string;
|
|
174
|
+
repeats: number;
|
|
175
|
+
} | undefined;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Live inspector — watches the Claude Code projects directory for new
|
|
179
|
+
* sessions and growing session files, runs incremental ingest, and
|
|
180
|
+
* emits structured events that the web UI subscribes to via SSE.
|
|
181
|
+
*
|
|
182
|
+
* v0.1 only watches Claude Code; the abstraction is generic enough that
|
|
183
|
+
* Codex (and others) will plug in by passing alternative discovery /
|
|
184
|
+
* ingest functions.
|
|
185
|
+
*/
|
|
186
|
+
type LiveEvent = {
|
|
187
|
+
type: "run:created";
|
|
188
|
+
run: Run;
|
|
189
|
+
} | {
|
|
190
|
+
type: "run:updated";
|
|
191
|
+
run: Run;
|
|
192
|
+
new_steps: Step[];
|
|
193
|
+
} | {
|
|
194
|
+
type: "run:completed";
|
|
195
|
+
run: Run;
|
|
196
|
+
} | {
|
|
197
|
+
type: "fleet:snapshot";
|
|
198
|
+
entries: FleetEntry[];
|
|
199
|
+
} | {
|
|
200
|
+
type: "alert";
|
|
201
|
+
run_id: string;
|
|
202
|
+
kind: "loop" | "stall" | "context_threshold" | "tool_called";
|
|
203
|
+
message: string;
|
|
204
|
+
meta?: Record<string, unknown>;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* v0.3 §8.5 — fired once per step whose ingest produced one or more
|
|
208
|
+
* file_change rows. `paths` is the unique path set (deduped across
|
|
209
|
+
* old/new for renames); `partial` is true iff any row in the batch
|
|
210
|
+
* has `partial_diff = true` (e.g. Bash stubs).
|
|
211
|
+
*/
|
|
212
|
+
| {
|
|
213
|
+
type: "files:changed";
|
|
214
|
+
run_id: string;
|
|
215
|
+
step_id: string;
|
|
216
|
+
paths: string[];
|
|
217
|
+
partial: boolean;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* v0.3 §4.9 — fired on the `pause_requested → paused` transition,
|
|
221
|
+
* i.e. the moment the SDK actually blocked. `step_id` is the last
|
|
222
|
+
* step the inspector observed before the pause acknowledgment, the
|
|
223
|
+
* closest signal we have for "the step that would have started next."
|
|
224
|
+
*/
|
|
225
|
+
| {
|
|
226
|
+
type: "run:paused";
|
|
227
|
+
run_id: string;
|
|
228
|
+
step_id: string | null;
|
|
229
|
+
paused_at: string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* v0.3 §4.9 — fired on the `paused | pause_requested → running`
|
|
233
|
+
* transition. `edits` counts distinct non-null inject values the
|
|
234
|
+
* inspector observed during the paused window. Best-effort: an
|
|
235
|
+
* inject queued and consumed entirely between two ticks won't be
|
|
236
|
+
* counted. Document, don't gold-plate.
|
|
237
|
+
*/
|
|
238
|
+
| {
|
|
239
|
+
type: "run:resumed";
|
|
240
|
+
run_id: string;
|
|
241
|
+
edits: number;
|
|
242
|
+
resumed_at: string;
|
|
243
|
+
};
|
|
244
|
+
interface FleetEntry {
|
|
245
|
+
run: Run;
|
|
246
|
+
status: LiveStatus;
|
|
247
|
+
context_pct: number;
|
|
248
|
+
recent_tools: string[];
|
|
249
|
+
last_step_at?: string;
|
|
250
|
+
alerts: Array<{
|
|
251
|
+
kind: string;
|
|
252
|
+
message: string;
|
|
253
|
+
}>;
|
|
254
|
+
}
|
|
255
|
+
interface LiveOptions {
|
|
256
|
+
projectsRoot?: string;
|
|
257
|
+
/** Polling interval (ms) for the projects-root scan. Defaults to 1500. */
|
|
258
|
+
scanIntervalMs?: number;
|
|
259
|
+
/** Stall threshold in seconds. Step inactivity beyond this triggers an alert. */
|
|
260
|
+
stallSeconds?: number;
|
|
261
|
+
/** Context-threshold percentages that fire alerts (in %). */
|
|
262
|
+
contextThresholds?: number[];
|
|
263
|
+
/** Tools that, when called, fire a `tool_called` alert. */
|
|
264
|
+
watchTools?: string[];
|
|
265
|
+
/** Loop detection — N repeated identical tool calls. */
|
|
266
|
+
loopWindow?: number;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Build the fleet snapshot from the store. Pulled out of `LiveInspector`
|
|
270
|
+
* so the web UI can render the same view as a one-shot, even when
|
|
271
|
+
* `meter web` was launched without `--live`. Live mode passes its
|
|
272
|
+
* `firedAlerts` map; static mode passes nothing and just gets empty
|
|
273
|
+
* alert lists.
|
|
274
|
+
*/
|
|
275
|
+
declare function buildFleetEntries(store: Store, opts?: {
|
|
276
|
+
limit?: number;
|
|
277
|
+
stallSeconds?: number;
|
|
278
|
+
firedAlerts?: Map<string, Map<string, {
|
|
279
|
+
kind: string;
|
|
280
|
+
message: string;
|
|
281
|
+
}>>;
|
|
282
|
+
}): FleetEntry[];
|
|
283
|
+
declare class LiveInspector extends EventEmitter {
|
|
284
|
+
private store;
|
|
285
|
+
private opts;
|
|
286
|
+
private knownPaths;
|
|
287
|
+
private firedAlerts;
|
|
288
|
+
private lastSizes;
|
|
289
|
+
private lastStepCounts;
|
|
290
|
+
private lastStatus;
|
|
291
|
+
private probeTracks;
|
|
292
|
+
private timer?;
|
|
293
|
+
private stopped;
|
|
294
|
+
/** Set on the first scan so we can backfill state silently — historical
|
|
295
|
+
* sessions on disk are not "new runs" the user just kicked off. */
|
|
296
|
+
private booted;
|
|
297
|
+
constructor(store: Store, opts?: LiveOptions);
|
|
298
|
+
start(): Promise<void>;
|
|
299
|
+
/**
|
|
300
|
+
* Sample the newest few sessions and validate their records against
|
|
301
|
+
* the shapes `types.ts` claims. One warning per unique drift hash
|
|
302
|
+
* (so a single schema change doesn't spam thousands of lines).
|
|
303
|
+
* Disabled when `METERBILITY_DISABLE_SHAPE_PROBE` is set — useful for
|
|
304
|
+
* tests with intentionally minimal fixtures.
|
|
305
|
+
*/
|
|
306
|
+
private runShapeProbe;
|
|
307
|
+
stop(): void;
|
|
308
|
+
/**
|
|
309
|
+
* One scan: discover sessions, re-ingest any whose file has grown,
|
|
310
|
+
* compute the current fleet snapshot, fire alerts where appropriate.
|
|
311
|
+
*
|
|
312
|
+
* `silent: true` runs the same ingest pipeline but suppresses event
|
|
313
|
+
* emission — used for the first-tick backfill at startup so historical
|
|
314
|
+
* runs don't masquerade as freshly-created ones.
|
|
315
|
+
*/
|
|
316
|
+
tick(opts?: {
|
|
317
|
+
silent?: boolean;
|
|
318
|
+
}): Promise<void>;
|
|
319
|
+
/**
|
|
320
|
+
* Emit `files:changed` if the given step has any file_change rows.
|
|
321
|
+
* Pulled into its own method so tests can exercise the dedup-paths
|
|
322
|
+
* shape without going through a full tick.
|
|
323
|
+
*/
|
|
324
|
+
private emitFilesChangedIfAny;
|
|
325
|
+
/**
|
|
326
|
+
* Poll probe state for every run with an active probe file. Two-axis
|
|
327
|
+
* detection: the FSM `state` drives inject-window accounting; the
|
|
328
|
+
* `paused_at_ms` / `resumed_at_ms` timestamps drive `run:paused` /
|
|
329
|
+
* `run:resumed` emission. The timestamp axis matters because a full
|
|
330
|
+
* pause→ack→resume cycle can complete inside one 1500ms tick — a
|
|
331
|
+
* pure `state → state` comparison would silently swallow those
|
|
332
|
+
* events (the bug Codex flagged at this site).
|
|
333
|
+
*
|
|
334
|
+
* Runs without a probe file are skipped entirely: probe files only
|
|
335
|
+
* exist while a run is under operator control (created lazily by
|
|
336
|
+
* `requestPause` / `setInject`, removed by `clearProbe` on terminal
|
|
337
|
+
* cleanup), so the file's presence is the cleanest natural gate.
|
|
338
|
+
* Per-run reads are guarded — a single EACCES or corrupt file on
|
|
339
|
+
* one probe shouldn't degrade `/api/live` for every other run.
|
|
340
|
+
*/
|
|
341
|
+
private pollProbeStates;
|
|
342
|
+
/** Compute the current fleet view (active + recently-completed runs). */
|
|
343
|
+
fleetEntries(): FleetEntry[];
|
|
344
|
+
private maybeAlert;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Runtime-toggleable owner of a LiveInspector. Existed implicitly in
|
|
348
|
+
* v0.2 (each `meter web --live` invocation built an inspector at
|
|
349
|
+
* startup and discarded it on close). v0.3 needs runtime toggling
|
|
350
|
+
* because the web UI's "Live" button starts/stops without restarting
|
|
351
|
+
* the server.
|
|
352
|
+
*
|
|
353
|
+
* The class is a thin shell — it forwards `on`/`off`/`fleetEntries`
|
|
354
|
+
* to whichever inspector instance is currently active, and a
|
|
355
|
+
* stopped-state stub for the off case so the routes always have
|
|
356
|
+
* something safe to call.
|
|
357
|
+
*
|
|
358
|
+
* Listener routing: the controller stores subscribers itself and
|
|
359
|
+
* re-binds them to any new inspector it spawns. That means an SSE
|
|
360
|
+
* client opened *before* live mode is enabled will start receiving
|
|
361
|
+
* events as soon as the operator hits the toggle, with no reconnect.
|
|
362
|
+
*/
|
|
363
|
+
declare class LiveController {
|
|
364
|
+
private store;
|
|
365
|
+
private inspector?;
|
|
366
|
+
private subscribers;
|
|
367
|
+
private storeOpts;
|
|
368
|
+
constructor(store: Store);
|
|
369
|
+
/** Spin up an inspector if one isn't already running. Idempotent. */
|
|
370
|
+
start(opts?: LiveOptions): Promise<void>;
|
|
371
|
+
/** Stop the running inspector if any. Idempotent. */
|
|
372
|
+
stop(): void;
|
|
373
|
+
isLive(): boolean;
|
|
374
|
+
on(event: "data", fn: (e: LiveEvent) => void): void;
|
|
375
|
+
off(event: "data", fn: (e: LiveEvent) => void): void;
|
|
376
|
+
/** Latest fleet snapshot — empty when not running. */
|
|
377
|
+
fleetEntries(): FleetEntry[];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Hono app over the local Store. Two surfaces share the same routes:
|
|
382
|
+
* - JSON API (`/api/...`) — for future web/UI clients.
|
|
383
|
+
* - Server-rendered HTML (`/`, `/runs/:id`, `/diff?a=&b=`) — what
|
|
384
|
+
* `meter web` opens by default.
|
|
385
|
+
*/
|
|
386
|
+
interface BuildAppOptions {
|
|
387
|
+
/**
|
|
388
|
+
* v0.3 — runtime-toggleable live controller. Always present; the
|
|
389
|
+
* caller (serveApp) is responsible for starting it if --live was
|
|
390
|
+
* passed at boot. Routes dispatch through the controller so the
|
|
391
|
+
* web UI's Live button can start/stop without rebuilding the app.
|
|
392
|
+
*/
|
|
393
|
+
controller?: LiveController;
|
|
394
|
+
/**
|
|
395
|
+
* v0.2 back-compat: tests that built an inspector manually can pass
|
|
396
|
+
* it via `live`. `buildApp` wraps it into a fresh controller and
|
|
397
|
+
* adopts the inspector's events. Prefer `controller` for new code.
|
|
398
|
+
*/
|
|
399
|
+
live?: LiveInspector;
|
|
400
|
+
}
|
|
401
|
+
declare function buildApp(store: Store, opts?: BuildAppOptions): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
402
|
+
interface ServeOptions {
|
|
403
|
+
port?: number;
|
|
404
|
+
host?: string;
|
|
405
|
+
live?: boolean;
|
|
406
|
+
liveOptions?: LiveOptions;
|
|
407
|
+
}
|
|
408
|
+
declare function serveApp(store: Store, opts?: ServeOptions): {
|
|
409
|
+
url: string;
|
|
410
|
+
close: () => void;
|
|
411
|
+
controller: LiveController;
|
|
412
|
+
/** Back-compat shim — returns the controller's current inspector
|
|
413
|
+
* (undefined when not live). Existing callers that captured
|
|
414
|
+
* `result.live` continue to compile. */
|
|
415
|
+
live?: LiveInspector;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Regression suite (v0.1).
|
|
420
|
+
*
|
|
421
|
+
* A test is a named bundle of assertions. Assertions are pure: they take
|
|
422
|
+
* a (run, steps) pair and return pass/fail. v0.1 supports the structural
|
|
423
|
+
* checks called out in SPEC §7.3 — tool-call presence, output-text
|
|
424
|
+
* matching, step counts, final status, cost ceiling. LLM-judge
|
|
425
|
+
* assertions are deferred to v1.
|
|
426
|
+
*
|
|
427
|
+
* Tests are stored in the local SQLite store as JSON-encoded assertion
|
|
428
|
+
* arrays so they round-trip cleanly via export/import.
|
|
429
|
+
*/
|
|
430
|
+
type AssertionKind = "includes_tool_call" | "excludes_tool_call" | "tool_call_count" | "output_contains" | "output_does_not_contain" | "min_steps" | "max_steps" | "final_status" | "max_cost_cents" | "no_error_step" | "step_status_at";
|
|
431
|
+
interface Assertion {
|
|
432
|
+
kind: AssertionKind;
|
|
433
|
+
/** A flexible payload — string for text assertions, number for counts, etc. */
|
|
434
|
+
value: string | number;
|
|
435
|
+
/** Optional sequence index for `step_status_at`. */
|
|
436
|
+
at?: number;
|
|
437
|
+
/** Friendly label for reports. */
|
|
438
|
+
label?: string;
|
|
439
|
+
}
|
|
440
|
+
interface RegressionTest {
|
|
441
|
+
test_id: string;
|
|
442
|
+
name: string;
|
|
443
|
+
description?: string;
|
|
444
|
+
assertions: Assertion[];
|
|
445
|
+
canonical_run_id?: string;
|
|
446
|
+
created_at: string;
|
|
447
|
+
}
|
|
448
|
+
interface AssertionResult {
|
|
449
|
+
assertion: Assertion;
|
|
450
|
+
passed: boolean;
|
|
451
|
+
reason: string;
|
|
452
|
+
}
|
|
453
|
+
interface RegressionResult {
|
|
454
|
+
result_id: string;
|
|
455
|
+
test_id: string;
|
|
456
|
+
test_name: string;
|
|
457
|
+
run_id: string;
|
|
458
|
+
passed: boolean;
|
|
459
|
+
assertions: AssertionResult[];
|
|
460
|
+
created_at: string;
|
|
461
|
+
}
|
|
462
|
+
declare function listTests(store: Store): RegressionTest[];
|
|
463
|
+
declare function getTestByName(store: Store, name: string): RegressionTest | undefined;
|
|
464
|
+
declare function createTest(store: Store, args: {
|
|
465
|
+
name: string;
|
|
466
|
+
description?: string;
|
|
467
|
+
assertions: Assertion[];
|
|
468
|
+
canonical_run_id?: string;
|
|
469
|
+
}): RegressionTest;
|
|
470
|
+
declare function deleteTest(store: Store, name: string): boolean;
|
|
471
|
+
declare function addAssertion(store: Store, testName: string, assertion: Assertion): RegressionTest;
|
|
472
|
+
/**
|
|
473
|
+
* Build a starter test from a canonical run. Each tool used in the run
|
|
474
|
+
* becomes an `includes_tool_call`; final status becomes `final_status`;
|
|
475
|
+
* total step count becomes `max_steps × 1.5` and `min_steps × 0.5` — a
|
|
476
|
+
* loose envelope the operator can tighten by hand later.
|
|
477
|
+
*/
|
|
478
|
+
declare function deriveAssertionsFromRun(run: Run, steps: Step[]): Assertion[];
|
|
479
|
+
declare function runTest(store: Store, test: RegressionTest, runId: string): RegressionResult;
|
|
480
|
+
declare function listResults(store: Store, testId?: string, limit?: number): RegressionResult[];
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Multi-step continuation engine. After a fork has materialized its
|
|
484
|
+
* deterministic prefix, this drives the agent loop until either:
|
|
485
|
+
* - the model produces a message (no tool call), or
|
|
486
|
+
* - max iterations are exceeded, or
|
|
487
|
+
* - a tool is requested that we cannot resolve in `simulate` mode.
|
|
488
|
+
*
|
|
489
|
+
* Two modes are offered, mirroring SPEC §7.2:
|
|
490
|
+
*
|
|
491
|
+
* simulate — run the model live, but for each tool the model picks,
|
|
492
|
+
* look up the *original* run's tool result and feed that back. Only
|
|
493
|
+
* works when the model picks a tool whose call signature appears in
|
|
494
|
+
* the origin run; otherwise the loop stops with a `simulate_miss`
|
|
495
|
+
* marker tag on the last step.
|
|
496
|
+
*
|
|
497
|
+
* live — run the model live AND execute tools via the caller-supplied
|
|
498
|
+
* executor. Side-effecting; the caller takes responsibility for
|
|
499
|
+
* sandboxing.
|
|
500
|
+
*/
|
|
501
|
+
type ContinuationMode = "simulate" | "live";
|
|
502
|
+
interface ContinuationCallArgs {
|
|
503
|
+
/** History assembled so far (user/assistant turns, oldest → newest). */
|
|
504
|
+
history: ConversationMessage[];
|
|
505
|
+
/** The most recent step in the fork run. */
|
|
506
|
+
prior_step: Step;
|
|
507
|
+
/** Resolved system prompt content (if available). */
|
|
508
|
+
system_prompt?: string;
|
|
509
|
+
/** Iteration number, 0-indexed. */
|
|
510
|
+
iteration: number;
|
|
511
|
+
}
|
|
512
|
+
interface ContinuationCallResult {
|
|
513
|
+
model: string;
|
|
514
|
+
/** Raw model output to persist as decision blob. */
|
|
515
|
+
decision_content: unknown;
|
|
516
|
+
action: Action;
|
|
517
|
+
tokens: TokenUsage;
|
|
518
|
+
latency_ms: number;
|
|
519
|
+
}
|
|
520
|
+
type ContinuationModelCaller = (args: ContinuationCallArgs) => Promise<ContinuationCallResult>;
|
|
521
|
+
interface ToolCall {
|
|
522
|
+
tool_name: string;
|
|
523
|
+
tool_use_id?: string;
|
|
524
|
+
tool_input: unknown;
|
|
525
|
+
}
|
|
526
|
+
interface ToolExecutionResult {
|
|
527
|
+
/** Anything serializable. Stored as a tool_result blob. */
|
|
528
|
+
output: unknown;
|
|
529
|
+
is_error?: boolean;
|
|
530
|
+
/** Human-readable one-liner. */
|
|
531
|
+
summary?: string;
|
|
532
|
+
}
|
|
533
|
+
type ToolExecutor = (call: ToolCall) => Promise<ToolExecutionResult>;
|
|
534
|
+
interface ContinuationOptions {
|
|
535
|
+
mode: ContinuationMode;
|
|
536
|
+
modelCaller: ContinuationModelCaller;
|
|
537
|
+
/** Required in `live` mode; ignored in `simulate`. */
|
|
538
|
+
toolExecutor?: ToolExecutor;
|
|
539
|
+
/** Cap iterations to bound runaway loops. Defaults to 25. */
|
|
540
|
+
maxIterations?: number;
|
|
541
|
+
/** Origin run id — used by simulate mode to look up cached tool results. */
|
|
542
|
+
originRunId?: string;
|
|
543
|
+
}
|
|
544
|
+
interface ContinuationResult {
|
|
545
|
+
iterations_run: number;
|
|
546
|
+
steps_added: number;
|
|
547
|
+
terminal_reason: "model_completed" | "max_iterations" | "simulate_miss" | "tool_error" | "model_error";
|
|
548
|
+
final_step_id: string;
|
|
549
|
+
}
|
|
550
|
+
declare function continueFork(store: Store, forkRunId: string, opts: ContinuationOptions): Promise<ContinuationResult>;
|
|
551
|
+
/**
|
|
552
|
+
* Helper: simulate-mode tool results store a `__ref` pointer rather than
|
|
553
|
+
* the inline bytes (so we don't double-store). Resolve it back to a real
|
|
554
|
+
* value when downstream code needs the actual result.
|
|
555
|
+
*/
|
|
556
|
+
declare function resolveSimulatedResult(store: Store, result: unknown): Promise<unknown>;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Slack notifier — subscribes to a LiveInspector and posts shaped Block
|
|
560
|
+
* Kit messages to an incoming webhook. Stays out of the hot path: posts
|
|
561
|
+
* are fire-and-forget, errors are logged but don't crash the watcher.
|
|
562
|
+
*
|
|
563
|
+
* Anchor URLs use the server's externally-reachable origin (passed via
|
|
564
|
+
* `serverUrl`) so operators can jump from Slack into Meterbility's web UI.
|
|
565
|
+
*/
|
|
566
|
+
type SlackEventKind = "alert" | "run:created" | "run:completed";
|
|
567
|
+
interface SlackNotifierOptions {
|
|
568
|
+
/** Incoming-webhook URL (https://hooks.slack.com/services/...). */
|
|
569
|
+
webhookUrl: string;
|
|
570
|
+
/** External origin of the local Meterbility web UI for clickable links. */
|
|
571
|
+
serverUrl?: string;
|
|
572
|
+
/** Which event types to post. Defaults to alerts only. */
|
|
573
|
+
events?: SlackEventKind[];
|
|
574
|
+
/** Optional channel override (only honored by some workspaces). */
|
|
575
|
+
channel?: string;
|
|
576
|
+
/** Cap posts per minute to keep things tame. Default 30. */
|
|
577
|
+
rateLimitPerMinute?: number;
|
|
578
|
+
}
|
|
579
|
+
declare class SlackNotifier {
|
|
580
|
+
private opts;
|
|
581
|
+
private bucket;
|
|
582
|
+
private detach?;
|
|
583
|
+
constructor(opts: SlackNotifierOptions);
|
|
584
|
+
attach(live: LiveInspector): void;
|
|
585
|
+
detachFrom(): void;
|
|
586
|
+
handleEvent(e: LiveEvent): Promise<void>;
|
|
587
|
+
/**
|
|
588
|
+
* Send a one-off test message — used by `meter slack test`.
|
|
589
|
+
*/
|
|
590
|
+
sendTest(): Promise<void>;
|
|
591
|
+
private acquireRateLimitToken;
|
|
592
|
+
private formatPayload;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* pretty.ts — schema-aware pretty-print for the four step-detail tabs
|
|
597
|
+
* (action, outcome, decision, cost) shown by `meter inspect` and the
|
|
598
|
+
* web step cards.
|
|
599
|
+
*
|
|
600
|
+
* Raw JSON stays the default everywhere; pretty mode is opt-in via the
|
|
601
|
+
* CLI `--pretty-print` flag or the per-step `Pretty (all tabs)` button
|
|
602
|
+
* on the web. This module is the single source of truth for both.
|
|
603
|
+
*
|
|
604
|
+
* Pure: no I/O. Callers pre-resolve blob refs and pass text in via
|
|
605
|
+
* `opts.toolResultText`.
|
|
606
|
+
*/
|
|
607
|
+
type PrettyMode = "ansi" | "plain" | "html";
|
|
608
|
+
interface PrettyOptions {
|
|
609
|
+
mode: PrettyMode;
|
|
610
|
+
/** Cap for individual string values. Anything longer truncates with "… (N more chars)". */
|
|
611
|
+
maxStringLen?: number;
|
|
612
|
+
/** Spaces per indent level. */
|
|
613
|
+
indent?: number;
|
|
614
|
+
/** Caller pre-resolves outcome.tool_result_ref and passes the text here. */
|
|
615
|
+
toolResultText?: string;
|
|
616
|
+
/** Caller signal: the source decision string hit a slice cap upstream. */
|
|
617
|
+
truncated?: boolean;
|
|
618
|
+
/** Web-only: link to the raw blob endpoint (e.g. "/api/blob/abc..."). */
|
|
619
|
+
rawBlobHref?: string;
|
|
620
|
+
}
|
|
621
|
+
type TabKind = "action" | "outcome" | "decision" | "cost";
|
|
622
|
+
/**
|
|
623
|
+
* Server-side slice limit for the decision preview blob (see
|
|
624
|
+
* `loadDecisionPreviews` in `web.ts`). When a decision string lands at
|
|
625
|
+
* the renderer with `length >= this`, the caller treats it as
|
|
626
|
+
* truncated and pretty mode swaps the `(not JSON)` fallback for
|
|
627
|
+
* `(truncated · view raw)`.
|
|
628
|
+
*/
|
|
629
|
+
declare const DECISION_PREVIEW_LIMIT = 32000;
|
|
630
|
+
/**
|
|
631
|
+
* Render a multi-line string as `┃ ` prefixed lines. Handles both \n
|
|
632
|
+
* and \r\n. Truncates the rendered string at maxStringLen.
|
|
633
|
+
*/
|
|
634
|
+
declare function prettyMultilineString(s: string, opts: PrettyOptions): string;
|
|
635
|
+
/**
|
|
636
|
+
* Render any JSON-ish value at the given depth. `depth` is the nesting
|
|
637
|
+
* level (object/array containers); strings and primitives don't
|
|
638
|
+
* increment it. Hits MAX_DEPTH at 16 and emits a marker.
|
|
639
|
+
*/
|
|
640
|
+
declare function prettyValue(v: unknown, opts: PrettyOptions, depth: number): string;
|
|
641
|
+
declare function prettyTab(kind: TabKind, value: unknown, opts: PrettyOptions): string;
|
|
642
|
+
/**
|
|
643
|
+
* Parse a JSON string and re-stringify with 2-space indent. If the
|
|
644
|
+
* input is not valid JSON, return it unchanged. Pure helper; no
|
|
645
|
+
* coloring, no schema awareness. Replaces:
|
|
646
|
+
* - packages/cli/src/commands/inspect.ts:456 prettyJson
|
|
647
|
+
* - packages/server/src/html.ts:3068 prettyJson
|
|
648
|
+
* - packages/server/src/html.ts:3393 prettyJsonMaybe
|
|
649
|
+
*/
|
|
650
|
+
declare function reformatJsonString(s: string): string;
|
|
651
|
+
|
|
652
|
+
export { type AnthropicResponderOptions, type Assertion, type AssertionKind, type AssertionResult, type BuildAppOptions, type ContinuationCallArgs, type ContinuationCallResult, type ContinuationMode, type ContinuationModelCaller, type ContinuationOptions, type ContinuationResult, DECISION_PREVIEW_LIMIT, type DiffResult, type DiffRow, type DiffRowKind, type FleetEntry, type ForkArgs, type ForkOutcome, LiveController, type LiveEvent, LiveInspector, type LiveOptions, type LiveResponder, type LiveResponderArgs, type LiveResponderResult, type LiveStatus, type PrettyMode, type PrettyOptions, type RegressionResult, type RegressionTest, type ReplayMode, type ReplayResult, type ServeOptions, type SlackEventKind, SlackNotifier, type SlackNotifierOptions, type StepDigest, type TabKind, type ToolCall, type ToolExecutionResult, type ToolExecutor, addAssertion, anthropicResponder, appendLiveStep, buildApp, buildFleetEntries, classifyRunStatus, contextUtilization, continueFork, createTest, deleteTest, deriveAssertionsFromRun, detectLoop, diffRuns, fakeResponder, forkRun, getTestByName, listResults, listTests, materializePrefix, prettyMultilineString, prettyTab, prettyValue, reformatJsonString, resolveSimulatedResult, rewriteSnapshot, runTest, serveApp };
|