@harness-fe/mcp-server 4.0.0-next.2 → 4.0.0-next.4

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 (100) hide show
  1. package/dist/bin.d.ts +2 -0
  2. package/dist/bin.js +15 -0
  3. package/dist/daemon.d.ts +3 -3
  4. package/dist/daemon.js +1 -1
  5. package/dist/index.d.ts +4 -4
  6. package/dist/index.js +3 -3
  7. package/dist/mcp.d.ts +2 -2
  8. package/dist/mcp.js +65 -19
  9. package/dist/mcpHttp.d.ts +2 -2
  10. package/dist/mcpHttp.js +88 -18
  11. package/package.json +5 -7
  12. package/src/bin.ts +19 -0
  13. package/src/daemon.ts +3 -3
  14. package/src/experimental.test.ts +2 -2
  15. package/src/index.ts +4 -4
  16. package/src/mcp.ts +67 -23
  17. package/src/mcpHttp.test.ts +52 -3
  18. package/src/mcpHttp.ts +102 -23
  19. package/src/mcpLayer.e2e.test.ts +2 -2
  20. package/src/newCapabilities.e2e.test.ts +3 -3
  21. package/dist/auth.d.ts +0 -53
  22. package/dist/auth.js +0 -212
  23. package/dist/bridge.d.ts +0 -323
  24. package/dist/bridge.js +0 -1618
  25. package/dist/cli.d.ts +0 -18
  26. package/dist/cli.js +0 -293
  27. package/dist/dashboardApi.d.ts +0 -40
  28. package/dist/dashboardApi.js +0 -142
  29. package/dist/dashboardSpa.d.ts +0 -18
  30. package/dist/dashboardSpa.js +0 -180
  31. package/dist/dashboardUrl.d.ts +0 -13
  32. package/dist/dashboardUrl.js +0 -18
  33. package/dist/eventsHandler.d.ts +0 -24
  34. package/dist/eventsHandler.js +0 -114
  35. package/dist/identity.d.ts +0 -90
  36. package/dist/identity.js +0 -123
  37. package/dist/openBrowser.d.ts +0 -33
  38. package/dist/openBrowser.js +0 -63
  39. package/dist/remoteBridge.d.ts +0 -61
  40. package/dist/remoteBridge.js +0 -307
  41. package/dist/replayCreate.d.ts +0 -36
  42. package/dist/replayCreate.js +0 -156
  43. package/dist/replayViewer.d.ts +0 -20
  44. package/dist/replayViewer.js +0 -168
  45. package/dist/sessionRouter.d.ts +0 -45
  46. package/dist/sessionRouter.js +0 -88
  47. package/dist/store/JsonMemoryStore.d.ts +0 -52
  48. package/dist/store/JsonMemoryStore.js +0 -119
  49. package/dist/store/JsonTaskStore.d.ts +0 -21
  50. package/dist/store/JsonTaskStore.js +0 -53
  51. package/dist/store/JsonlStore.d.ts +0 -128
  52. package/dist/store/JsonlStore.js +0 -1172
  53. package/dist/store/MemoryEventStore.d.ts +0 -47
  54. package/dist/store/MemoryEventStore.js +0 -111
  55. package/dist/store/WriteQueue.d.ts +0 -51
  56. package/dist/store/WriteQueue.js +0 -142
  57. package/dist/store/index.d.ts +0 -6
  58. package/dist/store/index.js +0 -5
  59. package/dist/store/types.d.ts +0 -427
  60. package/dist/store/types.js +0 -19
  61. package/dist/visitorTimeline.d.ts +0 -24
  62. package/dist/visitorTimeline.js +0 -68
  63. package/src/auth.test.ts +0 -90
  64. package/src/auth.ts +0 -248
  65. package/src/bridge-auth.test.ts +0 -196
  66. package/src/bridge.test.ts +0 -1708
  67. package/src/bridge.ts +0 -1854
  68. package/src/cli.ts +0 -338
  69. package/src/dashboardApi.test.ts +0 -235
  70. package/src/dashboardApi.ts +0 -184
  71. package/src/dashboardSpa.test.ts +0 -239
  72. package/src/dashboardSpa.ts +0 -195
  73. package/src/dashboardUrl.test.ts +0 -46
  74. package/src/dashboardUrl.ts +0 -28
  75. package/src/eventsHandler.test.ts +0 -247
  76. package/src/eventsHandler.ts +0 -136
  77. package/src/identity.test.ts +0 -109
  78. package/src/identity.ts +0 -137
  79. package/src/openBrowser.test.ts +0 -103
  80. package/src/openBrowser.ts +0 -81
  81. package/src/remoteBridge.test.ts +0 -119
  82. package/src/remoteBridge.ts +0 -404
  83. package/src/replay.test.ts +0 -271
  84. package/src/replayCreate.ts +0 -194
  85. package/src/replayViewer.ts +0 -173
  86. package/src/sessionRouter.ts +0 -119
  87. package/src/store/JsonMemoryStore.test.ts +0 -175
  88. package/src/store/JsonMemoryStore.ts +0 -128
  89. package/src/store/JsonTaskStore.test.ts +0 -212
  90. package/src/store/JsonTaskStore.ts +0 -59
  91. package/src/store/JsonlStore.test.ts +0 -1538
  92. package/src/store/JsonlStore.ts +0 -1325
  93. package/src/store/MemoryEventStore.test.ts +0 -119
  94. package/src/store/MemoryEventStore.ts +0 -151
  95. package/src/store/WriteQueue.ts +0 -165
  96. package/src/store/identityTagging.test.ts +0 -67
  97. package/src/store/index.ts +0 -29
  98. package/src/store/types.ts +0 -532
  99. package/src/visitorTimeline.test.ts +0 -197
  100. package/src/visitorTimeline.ts +0 -89
@@ -1,168 +0,0 @@
1
- /**
2
- * Replay viewer — HTTP routes for serving exported rrweb recordings.
3
- *
4
- * Routes (all served on the same port as the WS bridge):
5
- * GET /replay/:id HTML viewer page (rrweb-player UI)
6
- * GET /replay/:id.json Raw events array for the player to fetch
7
- * GET /replay/static/player.js Bundled rrweb-player (UMD)
8
- * GET /replay/static/player.css Bundled rrweb-player styles
9
- *
10
- * The HTML page loads the static assets relatively and then calls
11
- * /replay/:id.json to hydrate the player. This keeps the viewer self-contained
12
- * and offline-capable — no CDN dependencies.
13
- */
14
- import { readFileSync, existsSync } from 'node:fs';
15
- import { createRequire } from 'node:module';
16
- import { dirname, join } from 'node:path';
17
- const require = createRequire(import.meta.url);
18
- /** Locate the bundled rrweb-player dist directory. */
19
- function resolvePlayerDist() {
20
- // rrweb-player exposes package.json; use it to find the dist folder.
21
- const pkgPath = require.resolve('rrweb-player/package.json');
22
- return join(dirname(pkgPath), 'dist');
23
- }
24
- const PLAYER_DIST = (() => {
25
- try {
26
- return resolvePlayerDist();
27
- }
28
- catch {
29
- return '';
30
- }
31
- })();
32
- const VIEWER_HTML = (exportId, meta) => `<!doctype html>
33
- <html lang="en">
34
- <head>
35
- <meta charset="utf-8" />
36
- <title>Harness replay · ${escapeHtml(exportId)}</title>
37
- <link rel="stylesheet" href="/replay/static/player.css" />
38
- <style>
39
- html, body { margin: 0; padding: 0; background: #1a1a1a; color: #eee; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
40
- .meta { padding: 12px 16px; font-size: 13px; color: #aaa; border-bottom: 1px solid #333; }
41
- .meta code { color: #6cf; }
42
- .stage { padding: 16px; display: flex; justify-content: center; }
43
- .error { padding: 24px; color: #f66; }
44
- </style>
45
- </head>
46
- <body>
47
- <div class="meta">
48
- Replay <code>${escapeHtml(exportId)}</code> · session <code>${escapeHtml(meta.sessionId)}</code>${meta.tabId ? ` · tab <code>${escapeHtml(meta.tabId)}</code>` : ''} · ${meta.eventCount} events · ${Math.round(meta.durationMs / 100) / 10}s
49
- </div>
50
- <div class="stage" id="stage"></div>
51
- <script src="/replay/static/player.js"></script>
52
- <script>
53
- (async function() {
54
- const stage = document.getElementById('stage');
55
- try {
56
- const resp = await fetch(${JSON.stringify(`/replay/${exportId}.json`)});
57
- if (!resp.ok) throw new Error('failed to fetch events: ' + resp.status);
58
- const events = await resp.json();
59
- if (!Array.isArray(events) || events.length < 2) {
60
- throw new Error('export has fewer than 2 events — cannot play back');
61
- }
62
- // rrwebPlayer is the UMD global.
63
- new rrwebPlayer({
64
- target: stage,
65
- props: {
66
- events,
67
- autoPlay: true,
68
- showController: true,
69
- width: Math.min(1280, window.innerWidth - 64),
70
- height: Math.min(720, window.innerHeight - 120),
71
- },
72
- });
73
- } catch (err) {
74
- stage.innerHTML = '<div class="error">Replay failed: ' + (err && err.message ? String(err.message).replace(/[<>&]/g, '') : 'unknown error') + '</div>';
75
- console.error('[harness replay]', err);
76
- }
77
- })();
78
- </script>
79
- </body>
80
- </html>`;
81
- function escapeHtml(s) {
82
- return s.replace(/[&<>"']/g, (ch) => {
83
- switch (ch) {
84
- case '&': return '&amp;';
85
- case '<': return '&lt;';
86
- case '>': return '&gt;';
87
- case '"': return '&quot;';
88
- default: return '&#39;';
89
- }
90
- });
91
- }
92
- function send(res, status, contentType, body) {
93
- res.statusCode = status;
94
- res.setHeader('content-type', contentType);
95
- res.setHeader('cache-control', 'no-store');
96
- res.end(body);
97
- }
98
- /**
99
- * Build a handler that dispatches /replay/* requests. Returns undefined for
100
- * non-replay paths so the caller can chain or 404.
101
- */
102
- export function createReplayHandler(store) {
103
- return (req, res) => {
104
- if (!req.url)
105
- return false;
106
- const url = new URL(req.url, 'http://localhost');
107
- const path = url.pathname;
108
- if (!path.startsWith('/replay/'))
109
- return false;
110
- // Static assets
111
- if (path === '/replay/static/player.js') {
112
- if (!PLAYER_DIST)
113
- return reply500(res, 'rrweb-player not installed');
114
- const file = join(PLAYER_DIST, 'index.js');
115
- if (!existsSync(file))
116
- return reply500(res, 'rrweb-player bundle missing');
117
- send(res, 200, 'application/javascript; charset=utf-8', readFileSync(file));
118
- return true;
119
- }
120
- if (path === '/replay/static/player.css') {
121
- if (!PLAYER_DIST)
122
- return reply500(res, 'rrweb-player not installed');
123
- const file = join(PLAYER_DIST, 'style.css');
124
- if (!existsSync(file))
125
- return reply500(res, 'rrweb-player styles missing');
126
- send(res, 200, 'text/css; charset=utf-8', readFileSync(file));
127
- return true;
128
- }
129
- // /replay/:id or /replay/:id.json
130
- const tail = path.slice('/replay/'.length);
131
- if (!tail || tail.includes('/')) {
132
- send(res, 404, 'text/plain; charset=utf-8', 'Not Found');
133
- return true;
134
- }
135
- const isJson = tail.endsWith('.json');
136
- const exportId = isJson ? tail.slice(0, -'.json'.length) : tail;
137
- if (!/^[A-Za-z0-9_-]+$/.test(exportId)) {
138
- send(res, 400, 'text/plain; charset=utf-8', 'Invalid export id');
139
- return true;
140
- }
141
- const meta = store.getExport(exportId);
142
- if (!meta) {
143
- send(res, 404, 'text/plain; charset=utf-8', `Unknown export: ${exportId}`);
144
- return true;
145
- }
146
- if (isJson) {
147
- const events = store.readExportEvents(exportId);
148
- if (!events) {
149
- send(res, 404, 'application/json; charset=utf-8', '{"error":"export events missing"}');
150
- return true;
151
- }
152
- send(res, 200, 'application/json; charset=utf-8', JSON.stringify(events));
153
- return true;
154
- }
155
- const html = VIEWER_HTML(exportId, {
156
- sessionId: meta.sessionId,
157
- tabId: meta.tabId,
158
- durationMs: Math.max(0, meta.endTs - meta.startTs),
159
- eventCount: meta.eventCount,
160
- });
161
- send(res, 200, 'text/html; charset=utf-8', html);
162
- return true;
163
- };
164
- function reply500(res, msg) {
165
- send(res, 500, 'text/plain; charset=utf-8', msg);
166
- return true;
167
- }
168
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * SessionRouter — registry of connected peers (vite-plugin + runtime-client).
3
- *
4
- * - Project: 1 vite-plugin per project (uniqued by projectId)
5
- * - Tab : N runtime-clients per project (each browser tab is one)
6
- *
7
- * Active tab heuristic: most recently active (last command or last event).
8
- * Caller can override via explicit tabId on every command.
9
- */
10
- import type { PeerRole, TabInfo } from '@harness-fe/protocol';
11
- import type { Principal } from './identity.js';
12
- export interface PeerSession {
13
- role: PeerRole;
14
- projectId: string;
15
- tabId?: string;
16
- /** Caller identity behind this connection (4.0 · P1). Defaults to `local`. */
17
- principal?: Principal;
18
- /** Runtime-client only: identifies the page load (sessionId) this connection belongs to. */
19
- sessionId?: string;
20
- /** Runtime-client only: stable per-browser visitor identifier. */
21
- visitorId?: string;
22
- /** App-supplied user identifier propagated from HarnessScript userId prop. */
23
- userId?: string;
24
- /** Opaque identifier for the underlying connection. */
25
- connectionId: string;
26
- lastActive: number;
27
- page?: {
28
- url?: string;
29
- title?: string;
30
- userAgent?: string;
31
- };
32
- }
33
- export declare class SessionRouter {
34
- private peers;
35
- private mostRecentTabId?;
36
- register(session: Omit<PeerSession, 'lastActive'>): PeerSession;
37
- unregister(connectionId: string): void;
38
- touch(connectionId: string): void;
39
- getByConnectionId(connectionId: string): PeerSession | undefined;
40
- findVitePlugin(projectId?: string): PeerSession | undefined;
41
- findTab(tabId?: string): PeerSession | undefined;
42
- private findFallbackTab;
43
- listTabs(): TabInfo[];
44
- listProjects(): string[];
45
- }
@@ -1,88 +0,0 @@
1
- /**
2
- * SessionRouter — registry of connected peers (vite-plugin + runtime-client).
3
- *
4
- * - Project: 1 vite-plugin per project (uniqued by projectId)
5
- * - Tab : N runtime-clients per project (each browser tab is one)
6
- *
7
- * Active tab heuristic: most recently active (last command or last event).
8
- * Caller can override via explicit tabId on every command.
9
- */
10
- export class SessionRouter {
11
- peers = new Map(); // key = connectionId
12
- mostRecentTabId;
13
- register(session) {
14
- const stored = { ...session, lastActive: Date.now() };
15
- this.peers.set(session.connectionId, stored);
16
- if (session.role === 'runtime-client' && session.tabId) {
17
- this.mostRecentTabId = session.tabId;
18
- }
19
- return stored;
20
- }
21
- unregister(connectionId) {
22
- const peer = this.peers.get(connectionId);
23
- this.peers.delete(connectionId);
24
- if (peer?.tabId && this.mostRecentTabId === peer.tabId) {
25
- this.mostRecentTabId = this.findFallbackTab()?.tabId;
26
- }
27
- }
28
- touch(connectionId) {
29
- const peer = this.peers.get(connectionId);
30
- if (!peer)
31
- return;
32
- peer.lastActive = Date.now();
33
- if (peer.role === 'runtime-client' && peer.tabId) {
34
- this.mostRecentTabId = peer.tabId;
35
- }
36
- }
37
- getByConnectionId(connectionId) {
38
- return this.peers.get(connectionId);
39
- }
40
- findVitePlugin(projectId) {
41
- const candidates = [...this.peers.values()].filter((p) => p.role === 'vite-plugin' || p.role === 'webpack-plugin');
42
- if (!candidates.length)
43
- return undefined;
44
- if (projectId)
45
- return candidates.find((c) => c.projectId === projectId);
46
- // No projectId filter — return the most recent.
47
- return candidates.sort((a, b) => b.lastActive - a.lastActive)[0];
48
- }
49
- findTab(tabId) {
50
- if (tabId) {
51
- for (const p of this.peers.values()) {
52
- if (p.role === 'runtime-client' && p.tabId === tabId)
53
- return p;
54
- }
55
- return undefined;
56
- }
57
- if (this.mostRecentTabId) {
58
- return this.findTab(this.mostRecentTabId);
59
- }
60
- return this.findFallbackTab();
61
- }
62
- findFallbackTab() {
63
- const tabs = [...this.peers.values()]
64
- .filter((p) => p.role === 'runtime-client' && p.tabId)
65
- .sort((a, b) => b.lastActive - a.lastActive);
66
- return tabs[0];
67
- }
68
- listTabs() {
69
- return [...this.peers.values()]
70
- .filter((p) => p.role === 'runtime-client' && !!p.tabId)
71
- .map((p) => ({
72
- tabId: p.tabId,
73
- projectId: p.projectId,
74
- url: p.page?.url,
75
- title: p.page?.title,
76
- userAgent: p.page?.userAgent,
77
- connectedAt: p.lastActive,
78
- }));
79
- }
80
- listProjects() {
81
- const ids = new Set();
82
- for (const p of this.peers.values()) {
83
- if (p.role === 'vite-plugin' || p.role === 'webpack-plugin')
84
- ids.add(p.projectId);
85
- }
86
- return [...ids];
87
- }
88
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * JsonMemoryStore — JSON-based persistence for agent memory (key-value store per project).
3
- *
4
- * File layout:
5
- * {dataDir}/{projectId}/memory.json
6
- *
7
- * File format:
8
- * {
9
- * "key1": { "key": "key1", "value": "...", "updatedAt": 1700000000000 },
10
- * "key2": { "key": "key2", "value": "...", "updatedAt": 1700000001000 }
11
- * }
12
- *
13
- * All mutations use atomic write-then-rename for durability.
14
- */
15
- import type { IMemoryStore, MemoryEntry } from './types.js';
16
- export declare class JsonMemoryStore implements IMemoryStore {
17
- private readonly dataDir;
18
- constructor(dataDir: string);
19
- private memoryPath;
20
- /**
21
- * Read and parse memory.json for a project.
22
- * Returns a null-prototype object on missing or corrupt file (never throws).
23
- * Using Object.create(null) prevents prototype pollution from keys like __proto__.
24
- */
25
- private load;
26
- /**
27
- * Atomically write memory data to disk using tmp + rename strategy.
28
- * Logs error on failure without throwing.
29
- */
30
- private save;
31
- /**
32
- * Get a memory entry by key.
33
- * Returns undefined if the key does not exist or memory.json is missing.
34
- */
35
- get(projectId: string, key: string): MemoryEntry | undefined;
36
- /**
37
- * Write or update a memory entry.
38
- * Sets updatedAt to the current Unix ms timestamp.
39
- * Returns the new/updated MemoryEntry.
40
- */
41
- set(projectId: string, key: string, value: string): MemoryEntry;
42
- /**
43
- * Delete a memory entry by key.
44
- * Returns true if the key existed and was removed, false otherwise.
45
- */
46
- delete(projectId: string, key: string): boolean;
47
- /**
48
- * List all memory entries for a project, sorted by updatedAt descending.
49
- * Returns an empty array if memory.json does not exist.
50
- */
51
- list(projectId: string): MemoryEntry[];
52
- }
@@ -1,119 +0,0 @@
1
- /**
2
- * JsonMemoryStore — JSON-based persistence for agent memory (key-value store per project).
3
- *
4
- * File layout:
5
- * {dataDir}/{projectId}/memory.json
6
- *
7
- * File format:
8
- * {
9
- * "key1": { "key": "key1", "value": "...", "updatedAt": 1700000000000 },
10
- * "key2": { "key": "key2", "value": "...", "updatedAt": 1700000001000 }
11
- * }
12
- *
13
- * All mutations use atomic write-then-rename for durability.
14
- */
15
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
16
- import { join } from 'node:path';
17
- import { sanitizeId } from './JsonlStore.js';
18
- export class JsonMemoryStore {
19
- dataDir;
20
- constructor(dataDir) {
21
- this.dataDir = dataDir;
22
- }
23
- // ── Path helpers ──────────────────────────────────────────────────────
24
- memoryPath(projectId) {
25
- return join(this.dataDir, sanitizeId(projectId), 'memory.json');
26
- }
27
- // ── Private I/O ───────────────────────────────────────────────────────
28
- /**
29
- * Read and parse memory.json for a project.
30
- * Returns a null-prototype object on missing or corrupt file (never throws).
31
- * Using Object.create(null) prevents prototype pollution from keys like __proto__.
32
- */
33
- load(projectId) {
34
- const path = this.memoryPath(projectId);
35
- try {
36
- const raw = readFileSync(path, 'utf-8');
37
- const parsed = JSON.parse(raw);
38
- // Copy into a null-prototype object to prevent prototype pollution
39
- const safe = Object.create(null);
40
- for (const key of Object.keys(parsed)) {
41
- safe[key] = parsed[key];
42
- }
43
- return safe;
44
- }
45
- catch {
46
- return Object.create(null);
47
- }
48
- }
49
- /**
50
- * Atomically write memory data to disk using tmp + rename strategy.
51
- * Logs error on failure without throwing.
52
- */
53
- save(projectId, data) {
54
- const path = this.memoryPath(projectId);
55
- const tmpPath = `${path}.tmp`;
56
- // Ensure the project directory exists
57
- const dir = join(this.dataDir, sanitizeId(projectId));
58
- try {
59
- mkdirSync(dir, { recursive: true });
60
- }
61
- catch (err) {
62
- console.error(`[JsonMemoryStore] failed to create directory ${dir}:`, err);
63
- return;
64
- }
65
- try {
66
- writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
67
- renameSync(tmpPath, path);
68
- }
69
- catch (err) {
70
- console.error(`[JsonMemoryStore] failed to write ${path}:`, err);
71
- }
72
- }
73
- // ── IMemoryStore implementation ───────────────────────────────────────
74
- /**
75
- * Get a memory entry by key.
76
- * Returns undefined if the key does not exist or memory.json is missing.
77
- */
78
- get(projectId, key) {
79
- const data = this.load(projectId);
80
- return data[key];
81
- }
82
- /**
83
- * Write or update a memory entry.
84
- * Sets updatedAt to the current Unix ms timestamp.
85
- * Returns the new/updated MemoryEntry.
86
- */
87
- set(projectId, key, value) {
88
- const data = this.load(projectId);
89
- const entry = {
90
- key,
91
- value,
92
- updatedAt: Date.now(),
93
- };
94
- data[key] = entry;
95
- this.save(projectId, data);
96
- return entry;
97
- }
98
- /**
99
- * Delete a memory entry by key.
100
- * Returns true if the key existed and was removed, false otherwise.
101
- */
102
- delete(projectId, key) {
103
- const data = this.load(projectId);
104
- if (!(key in data)) {
105
- return false;
106
- }
107
- delete data[key];
108
- this.save(projectId, data);
109
- return true;
110
- }
111
- /**
112
- * List all memory entries for a project, sorted by updatedAt descending.
113
- * Returns an empty array if memory.json does not exist.
114
- */
115
- list(projectId) {
116
- const data = this.load(projectId);
117
- return Object.values(data).sort((a, b) => b.updatedAt - a.updatedAt);
118
- }
119
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * JsonTaskStore — JSON-based persistence for annotation tasks.
3
- *
4
- * File format: {dataDir}/{sanitizeId(projectId)}/tasks.json
5
- * ```json
6
- * { "version": 1, "tasks": Task[] }
7
- * ```
8
- *
9
- * Writes are atomic: write to a .tmp file then rename to the final path.
10
- * On read failure (missing or corrupt file), returns an empty array.
11
- * On write failure, logs the error without throwing.
12
- */
13
- import type { Task } from '@harness-fe/protocol';
14
- import type { ITaskStore } from './types.js';
15
- export declare class JsonTaskStore implements ITaskStore {
16
- private readonly dataDir;
17
- constructor(dataDir: string);
18
- private tasksPath;
19
- loadTasks(projectId: string): Task[];
20
- saveTasks(projectId: string, tasks: Task[]): void;
21
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * JsonTaskStore — JSON-based persistence for annotation tasks.
3
- *
4
- * File format: {dataDir}/{sanitizeId(projectId)}/tasks.json
5
- * ```json
6
- * { "version": 1, "tasks": Task[] }
7
- * ```
8
- *
9
- * Writes are atomic: write to a .tmp file then rename to the final path.
10
- * On read failure (missing or corrupt file), returns an empty array.
11
- * On write failure, logs the error without throwing.
12
- */
13
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
14
- import { join } from 'node:path';
15
- import { sanitizeId } from './JsonlStore.js';
16
- export class JsonTaskStore {
17
- dataDir;
18
- constructor(dataDir) {
19
- this.dataDir = dataDir;
20
- }
21
- tasksPath(projectId) {
22
- return join(this.dataDir, sanitizeId(projectId), 'tasks.json');
23
- }
24
- loadTasks(projectId) {
25
- const path = this.tasksPath(projectId);
26
- if (!existsSync(path))
27
- return [];
28
- try {
29
- const raw = readFileSync(path, 'utf-8');
30
- const parsed = JSON.parse(raw);
31
- if (!Array.isArray(parsed?.tasks))
32
- return [];
33
- return parsed.tasks;
34
- }
35
- catch {
36
- return [];
37
- }
38
- }
39
- saveTasks(projectId, tasks) {
40
- const path = this.tasksPath(projectId);
41
- const dir = join(this.dataDir, sanitizeId(projectId));
42
- try {
43
- mkdirSync(dir, { recursive: true });
44
- const tmp = `${path}.tmp`;
45
- const data = { version: 1, tasks };
46
- writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
47
- renameSync(tmp, path);
48
- }
49
- catch (err) {
50
- console.error(`[JsonTaskStore] saveTasks failed for project "${projectId}":`, err);
51
- }
52
- }
53
- }
@@ -1,128 +0,0 @@
1
- /**
2
- * JsonlStore — JSONL-based persistence layer (v0.4.0 layout).
3
- *
4
- * New layout (v0.4.0):
5
- * {dataDir}/projects/{projectId}/meta.json
6
- * {dataDir}/projects/{projectId}/notes.jsonl
7
- * {dataDir}/projects/{projectId}/builds/{buildId}/meta.json
8
- * {dataDir}/tabs/{tabId}/meta.json
9
- * {dataDir}/sessions/{sessionId}/meta.json
10
- * {dataDir}/sessions/{sessionId}/timeline.jsonl
11
- * {dataDir}/sessions/{sessionId}/recording.jsonl
12
- * {dataDir}/exports/index.jsonl
13
- * {dataDir}/exports/{exportId}.rrweb.json
14
- *
15
- * Legacy layout (v0.3.x, read-only fallback):
16
- * {dataDir}/{projectId}/sessions/{buildId}/tabs/{tabId}/...
17
- * On startup, if legacy dirs are detected a warning is emitted pointing
18
- * users to `rm -rf ~/.harness/data`.
19
- */
20
- import type { BuildMeta, IStore, ProjectMeta, ProjectTreeNode, PurgeResult, RecordingChunk, RecordingChunkSummary, ReplayExportMeta, RetentionPolicy, SearchOptions, SessionMeta, SessionSummary, StoreEvent, TabMeta, TailOptions, VisitorMeta } from './types.js';
21
- import type { VisitorEnv } from '@harness-fe/protocol';
22
- export declare class JsonlStore implements IStore {
23
- private readonly dataDir;
24
- private readonly writeQueue;
25
- /**
26
- * In-memory index: sessionId → SessionMeta (rebuilt on startup, kept in sync).
27
- * Enables O(1) session lookup without disk reads.
28
- */
29
- private sessionIndex;
30
- /**
31
- * In-memory index: buildId → projectId (from openBuild / upsertBuild).
32
- * Enables resolving project from buildId for legacy bridge compat.
33
- */
34
- private buildIndex;
35
- constructor(dataDir?: string);
36
- /** Scan disk to rebuild in-memory indexes. Mark orphaned sessions (no endedAt). */
37
- private _rebuildIndexes;
38
- private projectsDir;
39
- private projectDir;
40
- private buildDir;
41
- private visitorsDir;
42
- private visitorDir;
43
- private tabsDir;
44
- private tabDir;
45
- private sessionsDir;
46
- private sessionDir;
47
- private sessionTimeline;
48
- private sessionRecording;
49
- private exportsDir;
50
- private exportIndex;
51
- private exportEventsPath;
52
- openBuild(projectId: string, patch?: Partial<Omit<BuildMeta, 'id' | 'projectId' | 'builtAt'>>): string;
53
- closeBuild(buildId: string, closedAt?: number): void;
54
- upsertTab(tabId: string, patch: Partial<Omit<TabMeta, 'id'>>): TabMeta;
55
- getTab(tabId: string): TabMeta | undefined;
56
- closeTab(tabId: string, disconnectedAt?: number): void;
57
- upsertSession(sessionId: string, meta: Partial<Omit<SessionMeta, 'id'>> & {
58
- tabId: string;
59
- startedAt: number;
60
- }): SessionMeta;
61
- closeSession(sessionId: string, endedAt?: number): void;
62
- getSession(sessionId: string): SessionMeta | undefined;
63
- listSessions(opts?: {
64
- tabId?: string;
65
- projectId?: string;
66
- buildId?: string;
67
- limit?: number;
68
- }): SessionMeta[];
69
- appendEvent(sessionId: string, event: StoreEvent): void;
70
- appendEventBatch(sessionId: string, events: StoreEvent[]): void;
71
- appendRecording(sessionId: string, chunk: unknown): void;
72
- writeNote(projectId: string, key: string, value: string): void;
73
- upsertProject(projectId: string, patch: Partial<Omit<ProjectMeta, 'id' | 'createdAt'>>): ProjectMeta;
74
- getProject(projectId: string): ProjectMeta | undefined;
75
- listProjects(): ProjectMeta[];
76
- upsertVisitor(visitorId: string, patch: {
77
- userId?: string;
78
- seenAt?: number;
79
- incrementSession?: boolean;
80
- addTabId?: string;
81
- addProjectId?: string;
82
- lastEnv?: VisitorEnv;
83
- }): VisitorMeta;
84
- getVisitor(visitorId: string): VisitorMeta | undefined;
85
- listVisitors(opts?: {
86
- projectId?: string;
87
- limit?: number;
88
- }): VisitorMeta[];
89
- upsertBuild(projectId: string, buildId: string, patch: Partial<Omit<BuildMeta, 'id' | 'projectId'>>): BuildMeta;
90
- getBuild(projectId: string, buildId: string): BuildMeta | undefined;
91
- listBuilds(projectId: string, limit?: number): BuildMeta[];
92
- getProjectTree(rootId?: string): ProjectTreeNode[];
93
- tail(sessionId: string, opts?: TailOptions): StoreEvent[];
94
- search(sessionId: string, query: string, opts?: SearchOptions): StoreEvent[];
95
- listRecordings(sessionId: string): RecordingChunkSummary[];
96
- sliceRecordings(sessionId: string, since: number, until: number): RecordingChunk[];
97
- writeExport(input: {
98
- sessionId: string;
99
- tabId?: string;
100
- since: number;
101
- until: number;
102
- label?: string;
103
- events: unknown[];
104
- startTs: number;
105
- endTs: number;
106
- chunkCount: number;
107
- }): ReplayExportMeta;
108
- getExport(exportId: string): ReplayExportMeta | undefined;
109
- readExportEvents(exportId: string): unknown[] | undefined;
110
- listExports(projectId: string, limit?: number): ReplayExportMeta[];
111
- summary(sessionId: string): SessionSummary;
112
- listNotes(projectId: string): Array<{
113
- key: string;
114
- value: string;
115
- ts: number;
116
- }>;
117
- purge(policy?: RetentionPolicy): PurgeResult;
118
- private pruneExports;
119
- /**
120
- * Flush all pending WriteQueue entries to disk. Used in tests.
121
- */
122
- flush(): Promise<void>;
123
- close(): Promise<void>;
124
- private pruneRecordingFile;
125
- private readMarkerTimestamps;
126
- }
127
- /** Sanitize a string for use as a directory name. */
128
- export declare function sanitizeId(id: string): string;