@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.
@@ -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
+ };
@@ -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 };