@jobshimo/browser-link 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/activity/db.d.ts +27 -0
  2. package/dist/activity/db.js +98 -0
  3. package/dist/activity/db.js.map +1 -0
  4. package/dist/activity/extract.d.ts +32 -0
  5. package/dist/activity/extract.js +126 -0
  6. package/dist/activity/extract.js.map +1 -0
  7. package/dist/activity/paths.d.ts +16 -0
  8. package/dist/activity/paths.js +24 -0
  9. package/dist/activity/paths.js.map +1 -0
  10. package/dist/activity/queries.d.ts +100 -0
  11. package/dist/activity/queries.js +286 -0
  12. package/dist/activity/queries.js.map +1 -0
  13. package/dist/activity/types.d.ts +119 -0
  14. package/dist/activity/types.js +41 -0
  15. package/dist/activity/types.js.map +1 -0
  16. package/dist/bridge/ws-bridge.d.ts +18 -1
  17. package/dist/bridge/ws-bridge.js +107 -0
  18. package/dist/bridge/ws-bridge.js.map +1 -1
  19. package/dist/cdp/detached-flows.d.ts +13 -0
  20. package/dist/cdp/detached-flows.js +33 -3
  21. package/dist/cdp/detached-flows.js.map +1 -1
  22. package/dist/cdp/inpage/deep-query.d.ts +1 -1
  23. package/dist/cdp/inpage/deep-query.js +1 -1
  24. package/dist/cdp/inpage/dom-helpers.d.ts +1 -1
  25. package/dist/cdp/inpage/dom-helpers.js +1 -1
  26. package/dist/cdp/transport.js +16 -5
  27. package/dist/cdp/transport.js.map +1 -1
  28. package/dist/cli.js +24 -0
  29. package/dist/cli.js.map +1 -1
  30. package/dist/commands/activity.d.ts +11 -0
  31. package/dist/commands/activity.js +347 -0
  32. package/dist/commands/activity.js.map +1 -0
  33. package/dist/config.d.ts +26 -0
  34. package/dist/config.js.map +1 -1
  35. package/dist/extension/activity.d.ts +201 -0
  36. package/dist/extension/activity.html +662 -0
  37. package/dist/extension/activity.js +555 -0
  38. package/dist/extension/activity.js.map +1 -0
  39. package/dist/extension/background.js +174 -3
  40. package/dist/extension/background.js.map +1 -1
  41. package/dist/extension/flow-registry.d.ts +14 -3
  42. package/dist/extension/flow-registry.js +37 -9
  43. package/dist/extension/flow-registry.js.map +1 -1
  44. package/dist/extension/inpage/deep-query.d.ts +1 -1
  45. package/dist/extension/inpage/deep-query.js +1 -1
  46. package/dist/extension/inpage/dom-helpers.d.ts +1 -1
  47. package/dist/extension/inpage/dom-helpers.js +1 -1
  48. package/dist/extension/manifest.json +3 -2
  49. package/dist/extension/popup.html +58 -0
  50. package/dist/extension/popup.js +11 -0
  51. package/dist/extension/popup.js.map +1 -1
  52. package/dist/messages.d.ts +80 -2
  53. package/dist/server.js +30 -0
  54. package/dist/server.js.map +1 -1
  55. package/dist/tools/browser-definitions.js +50 -2
  56. package/dist/tools/browser-definitions.js.map +1 -1
  57. package/dist/tools/browser-dispatch.d.ts +34 -7
  58. package/dist/tools/browser-dispatch.js +107 -1
  59. package/dist/tools/browser-dispatch.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1,27 @@
1
+ import Database from 'better-sqlite3';
2
+ /**
3
+ * Rows kept before the oldest are pruned.
4
+ *
5
+ * Sized so a heavy day of agent work never evicts the morning: a `repeat`
6
+ * flow can dispatch a few hundred actions in one call, so a 200-row ceiling
7
+ * like `BridgeEventLog`'s would make the trail useless for exactly the runs
8
+ * worth auditing. 50k rows is a few tens of MB at this row width — cheap for
9
+ * a file on disk, and unlike the in-memory event log nothing here competes
10
+ * with the process's working set.
11
+ */
12
+ export declare const MAX_ACTIVITY_ROWS = 50000;
13
+ export declare function getActivityDb(): Database.Database;
14
+ /**
15
+ * AUTOINCREMENT on the primary key is deliberate, and it is the reason the
16
+ * `sinceId` cursor is safe.
17
+ *
18
+ * Without it SQLite reuses the rowids of deleted rows, so pruning the oldest
19
+ * rows could hand a NEW row an id a tailing panel had already seen and
20
+ * skipped past — the panel would silently never render it. AUTOINCREMENT
21
+ * costs one extra table (`sqlite_sequence`) and buys a monotonic cursor that
22
+ * survives pruning.
23
+ */
24
+ export declare function pruneIfDue(db: Database.Database, force?: boolean): number;
25
+ /** Close and forget the handle. Tests use this between temp data dirs; the
26
+ * CLI uses it so a `clear --vacuum` is not fighting its own open WAL. */
27
+ export declare function closeActivityDb(): void;
@@ -0,0 +1,98 @@
1
+ import Database from 'better-sqlite3';
2
+ import { mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import { getActivityDbPath } from './paths.js';
5
+ let dbInstance = null;
6
+ /**
7
+ * Rows kept before the oldest are pruned.
8
+ *
9
+ * Sized so a heavy day of agent work never evicts the morning: a `repeat`
10
+ * flow can dispatch a few hundred actions in one call, so a 200-row ceiling
11
+ * like `BridgeEventLog`'s would make the trail useless for exactly the runs
12
+ * worth auditing. 50k rows is a few tens of MB at this row width — cheap for
13
+ * a file on disk, and unlike the in-memory event log nothing here competes
14
+ * with the process's working set.
15
+ */
16
+ export const MAX_ACTIVITY_ROWS = 50_000;
17
+ /**
18
+ * How many inserts between prune sweeps.
19
+ *
20
+ * Pruning on every insert would run a DELETE with a subquery on the hot path
21
+ * of every single agent action. Pruning on a counter means the table can
22
+ * briefly exceed `MAX_ACTIVITY_ROWS` by at most this much, which costs
23
+ * nothing and keeps the write path a single INSERT.
24
+ */
25
+ const PRUNE_INTERVAL = 500;
26
+ let insertsSincePrune = 0;
27
+ export function getActivityDb() {
28
+ if (dbInstance)
29
+ return dbInstance;
30
+ const path = getActivityDbPath();
31
+ mkdirSync(dirname(path), { recursive: true });
32
+ const db = new Database(path);
33
+ db.pragma('journal_mode = WAL');
34
+ runMigrations(db);
35
+ dbInstance = db;
36
+ return db;
37
+ }
38
+ function runMigrations(db) {
39
+ // Same idempotent CREATE-IF-NOT-EXISTS pattern as map/db.ts: an existing
40
+ // file from an earlier version picks up new tables on next open without
41
+ // touching a single stored row.
42
+ db.exec(`
43
+ CREATE TABLE IF NOT EXISTS activity (
44
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ at TEXT NOT NULL,
46
+ tool TEXT NOT NULL,
47
+ tab_id TEXT,
48
+ transport TEXT CHECK (transport IN ('extension', 'cdp')),
49
+ url TEXT,
50
+ title TEXT,
51
+ agent TEXT,
52
+ agent_pid INTEGER,
53
+ selector TEXT,
54
+ payload TEXT,
55
+ outcome TEXT NOT NULL CHECK (outcome IN ('ok', 'error')),
56
+ error TEXT,
57
+ duration_ms INTEGER NOT NULL,
58
+ flow_id TEXT
59
+ );
60
+
61
+ CREATE INDEX IF NOT EXISTS idx_activity_at ON activity(at);
62
+ CREATE INDEX IF NOT EXISTS idx_activity_tab ON activity(tab_id);
63
+ CREATE INDEX IF NOT EXISTS idx_activity_flow ON activity(flow_id);
64
+ CREATE INDEX IF NOT EXISTS idx_activity_agent ON activity(agent);
65
+ `);
66
+ }
67
+ /**
68
+ * AUTOINCREMENT on the primary key is deliberate, and it is the reason the
69
+ * `sinceId` cursor is safe.
70
+ *
71
+ * Without it SQLite reuses the rowids of deleted rows, so pruning the oldest
72
+ * rows could hand a NEW row an id a tailing panel had already seen and
73
+ * skipped past — the panel would silently never render it. AUTOINCREMENT
74
+ * costs one extra table (`sqlite_sequence`) and buys a monotonic cursor that
75
+ * survives pruning.
76
+ */
77
+ export function pruneIfDue(db, force = false) {
78
+ insertsSincePrune += 1;
79
+ if (!force && insertsSincePrune < PRUNE_INTERVAL)
80
+ return 0;
81
+ insertsSincePrune = 0;
82
+ const result = db
83
+ .prepare(`DELETE FROM activity WHERE id <= (
84
+ SELECT MAX(id) FROM activity
85
+ ) - ?`)
86
+ .run(MAX_ACTIVITY_ROWS);
87
+ return result.changes;
88
+ }
89
+ /** Close and forget the handle. Tests use this between temp data dirs; the
90
+ * CLI uses it so a `clear --vacuum` is not fighting its own open WAL. */
91
+ export function closeActivityDb() {
92
+ if (!dbInstance)
93
+ return;
94
+ dbInstance.close();
95
+ dbInstance = null;
96
+ insertsSincePrune = 0;
97
+ }
98
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/activity/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,IAAI,UAAU,GAA6B,IAAI,CAAC;AAEhD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B,IAAI,iBAAiB,GAAG,CAAC,CAAC;AAE1B,MAAM,UAAU,aAAa;IAC3B,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAClC,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,CAAC,CAAC;IAClB,UAAU,GAAG,EAAE,CAAC;IAChB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,aAAa,CAAC,EAAqB;IAC1C,yEAAyE;IACzE,wEAAwE;IACxE,gCAAgC;IAChC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;GAuBP,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,EAAqB,EAAE,KAAK,GAAG,KAAK;IAC7D,iBAAiB,IAAI,CAAC,CAAC;IACvB,IAAI,CAAC,KAAK,IAAI,iBAAiB,GAAG,cAAc;QAAE,OAAO,CAAC,CAAC;IAC3D,iBAAiB,GAAG,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,EAAE;SACd,OAAO,CACN;;aAEO,CACR;SACA,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;yEACyE;AACzE,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,UAAU,CAAC,KAAK,EAAE,CAAC;IACnB,UAAU,GAAG,IAAI,CAAC;IAClB,iBAAiB,GAAG,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { ActivityInput, ActivityTransport } from './types.js';
2
+ import type { AgentCaller } from '../tools/tab-claims.js';
3
+ export declare function extractSelector(tool: string, args: unknown): string | null;
4
+ export declare function extractPayload(tool: string, args: unknown): string | null;
5
+ /** The browser-link tab id the call targeted, when it takes one. */
6
+ export declare function extractTabId(args: unknown): string | null;
7
+ /** Flow id, for the two tools that address an existing flow by id. Steps
8
+ * dispatched INSIDE a flow are stamped by the flow runner, not here. */
9
+ export declare function extractFlowId(args: unknown): string | null;
10
+ /** `cdp:`-prefixed ids are the cdp-direct transport; everything else reached
11
+ * the page through the extension. Mirrors `isCdpTabId` without importing it,
12
+ * so the trail keeps working if that predicate ever gains transport-selection
13
+ * side effects. */
14
+ export declare function transportFor(tabId: string | null): ActivityTransport | null;
15
+ /** Self-declared label first, binary as the fallback. `agent_id` alone is a
16
+ * uuid nobody can read; the point of this column is that a human scanning an
17
+ * exported trail can tell Claude Code from OpenCode at a glance. */
18
+ export declare function agentNameFor(caller: AgentCaller | undefined): string | null;
19
+ export interface BuildRowInput {
20
+ tool: string;
21
+ args: unknown;
22
+ caller?: AgentCaller;
23
+ durationMs: number;
24
+ error?: unknown;
25
+ /** False when `activity.record-payloads` is off: the row is still written,
26
+ * with its shape intact, minus the free text. */
27
+ recordPayloads: boolean;
28
+ }
29
+ /** Assemble the row. Deliberately total — every branch returns something
30
+ * storable, because a tool call that produced a row we could not describe is
31
+ * still a tool call that happened. */
32
+ export declare function buildRow({ tool, args, caller, durationMs, error, recordPayloads, }: BuildRowInput): ActivityInput;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Turn one tool dispatch into a trail row.
3
+ *
4
+ * This module exists so the dispatcher wrapper stays four lines. Knowing that
5
+ * `type`'s interesting text is `args.text` while `evaluate`'s is
6
+ * `args.expression` is knowledge about the TOOL SURFACE, and it belongs next
7
+ * to the trail that consumes it — not smeared through a 30-arm switch whose
8
+ * job is routing.
9
+ */
10
+ /** Which argument carries the CSS selector, per tool. Tools absent from the
11
+ * map simply record no selector. */
12
+ const SELECTOR_ARG = {
13
+ 'browser.click': 'selector',
14
+ 'browser.type': 'selector',
15
+ 'browser.press': 'selector',
16
+ 'browser.wait_for': 'selector',
17
+ 'browser.snapshot': 'within_selector',
18
+ 'browser.canvas_screenshot': 'selector',
19
+ };
20
+ /**
21
+ * Which argument carries the free text worth keeping, per tool.
22
+ *
23
+ * Only tools whose payload actually reconstructs the action appear here. A
24
+ * `browser.snapshot`'s `max_interactive` is a knob, not a payload; recording
25
+ * knobs would bury the three fields that matter (what was typed, what was
26
+ * evaluated, where it navigated) under noise.
27
+ */
28
+ const PAYLOAD_ARG = {
29
+ 'browser.navigate': 'url',
30
+ 'browser.type': 'text',
31
+ 'browser.find': 'text',
32
+ 'browser.evaluate': 'expression',
33
+ 'browser.press': 'key',
34
+ };
35
+ function readString(args, key) {
36
+ if (typeof args !== 'object' || args === null)
37
+ return null;
38
+ const value = args[key];
39
+ return typeof value === 'string' && value.length > 0 ? value : null;
40
+ }
41
+ /** `browser.flow` has no single payload argument — its substance is the step
42
+ * list. Summarising it as `"5 steps: find → click → type"` keeps one trail row
43
+ * readable where inlining the whole JSON would not, and the per-step detail is
44
+ * already recorded by the steps' own rows. */
45
+ function summariseFlow(args) {
46
+ if (typeof args !== 'object' || args === null)
47
+ return null;
48
+ const steps = args.steps;
49
+ if (!Array.isArray(steps))
50
+ return null;
51
+ const kinds = steps
52
+ .map((step) => typeof step === 'object' && step !== null ? Object.keys(step)[0] : null)
53
+ .filter((k) => typeof k === 'string');
54
+ if (kinds.length === 0)
55
+ return `${steps.length} steps`;
56
+ return `${steps.length} steps: ${kinds.join(' → ')}`;
57
+ }
58
+ export function extractSelector(tool, args) {
59
+ const key = SELECTOR_ARG[tool];
60
+ return key === undefined ? null : readString(args, key);
61
+ }
62
+ export function extractPayload(tool, args) {
63
+ if (tool === 'browser.flow')
64
+ return summariseFlow(args);
65
+ const key = PAYLOAD_ARG[tool];
66
+ return key === undefined ? null : readString(args, key);
67
+ }
68
+ /** The browser-link tab id the call targeted, when it takes one. */
69
+ export function extractTabId(args) {
70
+ return readString(args, 'tab_id');
71
+ }
72
+ /** Flow id, for the two tools that address an existing flow by id. Steps
73
+ * dispatched INSIDE a flow are stamped by the flow runner, not here. */
74
+ export function extractFlowId(args) {
75
+ return readString(args, 'flow_id');
76
+ }
77
+ /** `cdp:`-prefixed ids are the cdp-direct transport; everything else reached
78
+ * the page through the extension. Mirrors `isCdpTabId` without importing it,
79
+ * so the trail keeps working if that predicate ever gains transport-selection
80
+ * side effects. */
81
+ export function transportFor(tabId) {
82
+ if (tabId === null)
83
+ return null;
84
+ return tabId.startsWith('cdp:') ? 'cdp' : 'extension';
85
+ }
86
+ /** Self-declared label first, binary as the fallback. `agent_id` alone is a
87
+ * uuid nobody can read; the point of this column is that a human scanning an
88
+ * exported trail can tell Claude Code from OpenCode at a glance. */
89
+ export function agentNameFor(caller) {
90
+ if (!caller)
91
+ return null;
92
+ if (caller.label && caller.label.length > 0)
93
+ return caller.label;
94
+ return caller.binary && caller.binary.length > 0 ? caller.binary : null;
95
+ }
96
+ /** Assemble the row. Deliberately total — every branch returns something
97
+ * storable, because a tool call that produced a row we could not describe is
98
+ * still a tool call that happened. */
99
+ export function buildRow({ tool, args, caller, durationMs, error, recordPayloads, }) {
100
+ const tabId = extractTabId(args);
101
+ return {
102
+ tool,
103
+ tabId,
104
+ transport: transportFor(tabId),
105
+ // Filled in by the dispatcher wrapper, which is the only caller that can
106
+ // see the tab table. Null here keeps this module free of bridge deps.
107
+ url: null,
108
+ title: null,
109
+ agent: agentNameFor(caller),
110
+ agentPid: caller?.pid ?? null,
111
+ selector: extractSelector(tool, args),
112
+ payload: recordPayloads ? extractPayload(tool, args) : null,
113
+ outcome: error === undefined ? 'ok' : 'error',
114
+ error: error === undefined ? null : errorMessage(error),
115
+ durationMs,
116
+ flowId: extractFlowId(args),
117
+ };
118
+ }
119
+ function errorMessage(error) {
120
+ if (error instanceof Error)
121
+ return error.message;
122
+ if (typeof error === 'string')
123
+ return error;
124
+ return String(error);
125
+ }
126
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../../src/activity/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The activity trail gets its OWN SQLite file, next to `map.db` rather than
3
+ * inside it.
4
+ *
5
+ * The two have opposite lifecycles. `map.db` is small, slow-growing and
6
+ * precious — it holds what the agent learned about the user's apps, and losing
7
+ * it means relearning every selector. The trail is append-only, high-churn and
8
+ * disposable: thousands of rows a session, cleared whenever the user feels like
9
+ * it.
10
+ *
11
+ * Putting them in one file would mean the trail's WAL growth and its
12
+ * `activity clear` VACUUM both operate on the file holding the map, and it
13
+ * would make "back up my map" ambiguous. Two files, two retention policies,
14
+ * no shared blast radius.
15
+ */
16
+ export declare function getActivityDbPath(): string;
@@ -0,0 +1,24 @@
1
+ import { join } from 'node:path';
2
+ // The data dir resolver is shared, not map-specific — it predates this module
3
+ // and simply happens to live under map/. Imported rather than duplicated so
4
+ // $BROWSER_LINK_DATA_DIR keeps meaning one thing for the whole process.
5
+ import { getDataDir } from '../map/paths.js';
6
+ /**
7
+ * The activity trail gets its OWN SQLite file, next to `map.db` rather than
8
+ * inside it.
9
+ *
10
+ * The two have opposite lifecycles. `map.db` is small, slow-growing and
11
+ * precious — it holds what the agent learned about the user's apps, and losing
12
+ * it means relearning every selector. The trail is append-only, high-churn and
13
+ * disposable: thousands of rows a session, cleared whenever the user feels like
14
+ * it.
15
+ *
16
+ * Putting them in one file would mean the trail's WAL growth and its
17
+ * `activity clear` VACUUM both operate on the file holding the map, and it
18
+ * would make "back up my map" ambiguous. Two files, two retention policies,
19
+ * no shared blast radius.
20
+ */
21
+ export function getActivityDbPath() {
22
+ return join(getDataDir(), 'activity.db');
23
+ }
24
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/activity/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,8EAA8E;AAC9E,4EAA4E;AAC5E,wEAAwE;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,UAAU,EAAE,EAAE,aAAa,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,100 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { ActivityInput, ActivityPage, ActivityQuery, ActivityRecord } from './types.js';
3
+ /** Hard ceiling on rows a single read can return, whatever `limit` asks for.
4
+ * The panel pages; nothing needs ten thousand rows in one response, and an
5
+ * unbounded read is how a renderer freezes a browser. */
6
+ export declare const MAX_QUERY_LIMIT = 500;
7
+ /** Longest stored `payload` / `error`. A `browser.evaluate` expression can be
8
+ * arbitrarily long and an error can carry a page's worth of text; neither is
9
+ * more legible at 40 KB than at 4 KB, and the trail is meant to stay
10
+ * greppable. Truncation is marked so a reader never mistakes a cut string for
11
+ * the whole thing. */
12
+ export declare const MAX_TEXT_LEN = 4000;
13
+ export declare function truncate(value: string | null, max?: number): string | null;
14
+ /**
15
+ * Append one dispatched call to the trail.
16
+ *
17
+ * NEVER THROWS. Recording is observation, not the work: an agent's click must
18
+ * not fail because the disk is full or the DB is locked. The caller
19
+ * (`browser-dispatch.ts`) treats this as fire-and-forget, and a lost row is
20
+ * strictly better than a lost action.
21
+ */
22
+ export declare function record(input: ActivityInput, db?: Database.Database): ActivityRecord | null;
23
+ /**
24
+ * Read a page of the trail, NEWEST FIRST.
25
+ *
26
+ * Newest-first is what both readers want and it is the only ordering that
27
+ * makes `limit` honest: with oldest-first, "the last 200" would require
28
+ * counting the whole table before choosing an offset. The window reverses the
29
+ * page for display; the CLI prints it as-is.
30
+ */
31
+ export declare function query(q?: ActivityQuery, db?: Database.Database): ActivityPage;
32
+ /** Every distinct agent that appears in the trail, most recently seen first.
33
+ * Feeds the window's filter chips — a hardcoded list would go stale the
34
+ * moment someone wires up a new MCP client. */
35
+ export declare function listAgents(db?: Database.Database): {
36
+ agent: string;
37
+ count: number;
38
+ }[];
39
+ /** Delete the trail, or the part of it older than `before`. Returns rows
40
+ * removed. VACUUM is the caller's call (the CLI offers it) — it rewrites the
41
+ * whole file and has no business running inside a tool dispatch. */
42
+ export declare function clear(before?: string, db?: Database.Database): number;
43
+ /** An inclusive date window. Both ends optional: `{ from }` alone means
44
+ * "everything since", `{ to }` alone means "everything up to", `{}` means the
45
+ * whole trail. ISO-8601 strings, compared lexically — which is exactly right
46
+ * for the UTC ISO timestamps `record` writes. */
47
+ export interface ActivityRange {
48
+ from?: string;
49
+ to?: string;
50
+ }
51
+ /**
52
+ * How many rows a purge of this range WOULD remove.
53
+ *
54
+ * Exists so the UI can tell the user the number before they confirm, not
55
+ * after. "Delete 4,812 of 12,483 actions" is a decision; "Delete?" is a
56
+ * gamble, and this delete is permanent.
57
+ */
58
+ export declare function countInRange(range: ActivityRange, db?: Database.Database): number;
59
+ /**
60
+ * Delete every row inside an inclusive date range. Returns rows removed.
61
+ *
62
+ * `vacuum` defaults ON here, unlike `clear`. The whole point of purging from
63
+ * the UI is that the file got big, and a DELETE alone leaves the pages
64
+ * allocated — the user would purge half the trail, watch the reported size not
65
+ * move, and reasonably conclude it did nothing. VACUUM rewrites the file so
66
+ * the number they are watching actually drops.
67
+ */
68
+ export declare function purgeRange(range: ActivityRange, options?: {
69
+ vacuum?: boolean;
70
+ }, db?: Database.Database): number;
71
+ /** What the trail currently costs, for the panel that must always show it. */
72
+ export interface ActivityStats {
73
+ rows: number;
74
+ /** Bytes on disk: the DB plus its WAL and shared-memory sidecars. Reporting
75
+ * only the .db file would understate a busy trail badly — WAL mode parks
76
+ * recent writes in `activity.db-wal` until a checkpoint folds them in. */
77
+ bytes: number;
78
+ /** ISO timestamps of the oldest and newest row, or null on an empty trail.
79
+ * Lets the UI say "since Aug 3" instead of a bare row count. */
80
+ oldestAt: string | null;
81
+ newestAt: string | null;
82
+ /** The ceiling from `db.ts`, so the UI can show how close the trail is to
83
+ * pruning itself rather than hardcoding a number that could drift. */
84
+ maxRows: number;
85
+ }
86
+ export declare function stats(db?: Database.Database): ActivityStats;
87
+ /**
88
+ * Stream the whole trail for export, oldest first, in batches.
89
+ *
90
+ * A generator rather than an array on purpose: an export of 50k rows must not
91
+ * materialise 50k objects to hand to a JSON serializer. Oldest-first because
92
+ * an exported file is read top-to-bottom as a story, which is the opposite of
93
+ * how a live panel is read.
94
+ */
95
+ export declare function exportAll(filter?: ActivityQuery, db?: Database.Database): Generator<ActivityRecord>;
96
+ /** Strip the free-text columns from a record on its way out of the machine.
97
+ * Applied at EXPORT time, never at record time — see the PRIVACY CONTRACT in
98
+ * `types.ts`. The row keeps its shape so a redacted export is still a valid,
99
+ * diffable trail; only what was typed or evaluated goes. */
100
+ export declare function redact(record: ActivityRecord): ActivityRecord;