@harness-fe/mcp-server 4.0.0-next.1 → 4.0.0-next.3
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/dist/bin.d.ts +2 -0
- package/dist/bin.js +15 -0
- package/dist/daemon.d.ts +3 -3
- package/dist/daemon.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +49 -15
- package/dist/mcpHttp.d.ts +2 -2
- package/dist/mcpHttp.js +8 -2
- package/package.json +5 -7
- package/src/bin.ts +19 -0
- package/src/daemon.ts +3 -3
- package/src/experimental.test.ts +2 -2
- package/src/index.ts +4 -4
- package/src/mcp.ts +51 -19
- package/src/mcpHttp.test.ts +3 -3
- package/src/mcpHttp.ts +10 -4
- package/src/mcpLayer.e2e.test.ts +2 -2
- package/src/newCapabilities.e2e.test.ts +3 -3
- package/dist/auth.d.ts +0 -53
- package/dist/auth.js +0 -212
- package/dist/bridge.d.ts +0 -323
- package/dist/bridge.js +0 -1618
- package/dist/cli.d.ts +0 -18
- package/dist/cli.js +0 -293
- package/dist/dashboardApi.d.ts +0 -40
- package/dist/dashboardApi.js +0 -142
- package/dist/dashboardSpa.d.ts +0 -18
- package/dist/dashboardSpa.js +0 -180
- package/dist/dashboardUrl.d.ts +0 -13
- package/dist/dashboardUrl.js +0 -18
- package/dist/eventsHandler.d.ts +0 -24
- package/dist/eventsHandler.js +0 -114
- package/dist/identity.d.ts +0 -74
- package/dist/identity.js +0 -101
- package/dist/openBrowser.d.ts +0 -33
- package/dist/openBrowser.js +0 -63
- package/dist/remoteBridge.d.ts +0 -61
- package/dist/remoteBridge.js +0 -307
- package/dist/replayCreate.d.ts +0 -36
- package/dist/replayCreate.js +0 -156
- package/dist/replayViewer.d.ts +0 -20
- package/dist/replayViewer.js +0 -168
- package/dist/sessionRouter.d.ts +0 -45
- package/dist/sessionRouter.js +0 -88
- package/dist/store/JsonMemoryStore.d.ts +0 -52
- package/dist/store/JsonMemoryStore.js +0 -119
- package/dist/store/JsonTaskStore.d.ts +0 -21
- package/dist/store/JsonTaskStore.js +0 -53
- package/dist/store/JsonlStore.d.ts +0 -128
- package/dist/store/JsonlStore.js +0 -1172
- package/dist/store/MemoryEventStore.d.ts +0 -47
- package/dist/store/MemoryEventStore.js +0 -111
- package/dist/store/WriteQueue.d.ts +0 -51
- package/dist/store/WriteQueue.js +0 -142
- package/dist/store/index.d.ts +0 -6
- package/dist/store/index.js +0 -5
- package/dist/store/types.d.ts +0 -427
- package/dist/store/types.js +0 -19
- package/dist/visitorTimeline.d.ts +0 -24
- package/dist/visitorTimeline.js +0 -68
- package/src/auth.test.ts +0 -90
- package/src/auth.ts +0 -248
- package/src/bridge-auth.test.ts +0 -196
- package/src/bridge.test.ts +0 -1708
- package/src/bridge.ts +0 -1854
- package/src/cli.ts +0 -338
- package/src/dashboardApi.test.ts +0 -235
- package/src/dashboardApi.ts +0 -184
- package/src/dashboardSpa.test.ts +0 -239
- package/src/dashboardSpa.ts +0 -195
- package/src/dashboardUrl.test.ts +0 -46
- package/src/dashboardUrl.ts +0 -28
- package/src/eventsHandler.test.ts +0 -247
- package/src/eventsHandler.ts +0 -136
- package/src/identity.test.ts +0 -86
- package/src/identity.ts +0 -116
- package/src/openBrowser.test.ts +0 -103
- package/src/openBrowser.ts +0 -81
- package/src/remoteBridge.test.ts +0 -119
- package/src/remoteBridge.ts +0 -404
- package/src/replay.test.ts +0 -271
- package/src/replayCreate.ts +0 -194
- package/src/replayViewer.ts +0 -173
- package/src/sessionRouter.ts +0 -119
- package/src/store/JsonMemoryStore.test.ts +0 -175
- package/src/store/JsonMemoryStore.ts +0 -128
- package/src/store/JsonTaskStore.test.ts +0 -212
- package/src/store/JsonTaskStore.ts +0 -59
- package/src/store/JsonlStore.test.ts +0 -1538
- package/src/store/JsonlStore.ts +0 -1325
- package/src/store/MemoryEventStore.test.ts +0 -119
- package/src/store/MemoryEventStore.ts +0 -151
- package/src/store/WriteQueue.ts +0 -165
- package/src/store/identityTagging.test.ts +0 -67
- package/src/store/index.ts +0 -29
- package/src/store/types.ts +0 -532
- package/src/visitorTimeline.test.ts +0 -197
- package/src/visitorTimeline.ts +0 -89
package/dist/store/types.d.ts
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Store types — the public interface for the JSONL-based persistence layer.
|
|
3
|
-
*
|
|
4
|
-
* v0.4.0 layout (new, flat):
|
|
5
|
-
* {dataDir}/projects/{projectId}/meta.json
|
|
6
|
-
* {dataDir}/projects/{projectId}/tasks.json
|
|
7
|
-
* {dataDir}/projects/{projectId}/memory.json
|
|
8
|
-
* {dataDir}/projects/{projectId}/notes.jsonl
|
|
9
|
-
* {dataDir}/projects/{projectId}/builds/{buildId}/meta.json
|
|
10
|
-
* {dataDir}/tabs/{tabId}/meta.json
|
|
11
|
-
* {dataDir}/sessions/{sessionId}/meta.json ← one per pageload
|
|
12
|
-
* {dataDir}/sessions/{sessionId}/timeline.jsonl ← mixed parent+child events
|
|
13
|
-
* {dataDir}/sessions/{sessionId}/recording.jsonl ← rrweb chunks
|
|
14
|
-
* {dataDir}/visitors/{visitorId}/meta.json ← per-browser identity (0.5+)
|
|
15
|
-
*
|
|
16
|
-
* Legacy layout (v0.3.x, read-only fallback — daemon warns on startup):
|
|
17
|
-
* {dataDir}/{projectId}/sessions/{buildId}/tabs/{tabId}/...
|
|
18
|
-
*/
|
|
19
|
-
import type { Task, VisitorEnv } from '@harness-fe/protocol';
|
|
20
|
-
export type { EventId, EventStore, StreamId, } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
21
|
-
/** Short type codes used in JSONL lines to keep files compact. */
|
|
22
|
-
export type EventType = 'log' | 'err' | 'req' | 'res' | 'cmd' | 'resp' | 'hmr' | 'task' | 'task:claim' | 'task:resolve' | 'rrweb' | 'node:log' | 'node:err' | 'note' | 'load' | 'storage' | 'ws' | 'navigation' | 'globals' | 'indexeddb' | 'server-log' | 'server-err' | 'server-action' | 'app-log' | string;
|
|
23
|
-
/** A single event line in a JSONL file. Carries row-level projectId/buildId tags. */
|
|
24
|
-
export interface StoreEvent {
|
|
25
|
-
/**
|
|
26
|
-
* Server-assigned monotonic integer per session (assigned at enqueue time).
|
|
27
|
-
* Optional on input — the store layer assigns this.
|
|
28
|
-
* Always present on events returned by `tail` and `search`.
|
|
29
|
-
*/
|
|
30
|
-
seq?: number;
|
|
31
|
-
/** Unix timestamp in milliseconds. */
|
|
32
|
-
ts: number;
|
|
33
|
-
/** Short event type code. */
|
|
34
|
-
t: EventType;
|
|
35
|
-
/** Tab ID — present for tab-scoped events. */
|
|
36
|
-
tab?: string;
|
|
37
|
-
/**
|
|
38
|
-
* Load/session ID on tab-scoped events. Kept for backward compat with
|
|
39
|
-
* v0.3.x event lines and bridge code that still stamps event.load.
|
|
40
|
-
*/
|
|
41
|
-
load?: string;
|
|
42
|
-
/**
|
|
43
|
-
* Row-level project ID. Stamped by the bridge before calling appendEvent().
|
|
44
|
-
*/
|
|
45
|
-
projectId?: string;
|
|
46
|
-
/**
|
|
47
|
-
* Row-level build ID. Stamped by the bridge.
|
|
48
|
-
*/
|
|
49
|
-
buildId?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Row-level visitor ID. Stamped by the bridge from the registered peer
|
|
52
|
-
* or the frame's own `visitorId`. Lets agents filter timeline by
|
|
53
|
-
* "everything from this user" without join lookups.
|
|
54
|
-
*/
|
|
55
|
-
visitorId?: string;
|
|
56
|
-
/** Event payload — structure depends on `t`. */
|
|
57
|
-
d?: unknown;
|
|
58
|
-
}
|
|
59
|
-
export interface ProjectMeta {
|
|
60
|
-
id: string;
|
|
61
|
-
createdAt: number;
|
|
62
|
-
lastActiveAt: number;
|
|
63
|
-
parentProjectId?: string;
|
|
64
|
-
displayName?: string;
|
|
65
|
-
tags?: string[];
|
|
66
|
-
/**
|
|
67
|
-
* Caller-identity tag (4.0 · P1): principal id that first created this
|
|
68
|
-
* project. Write-once (locked on creation like `createdAt`); informational
|
|
69
|
-
* in P1, the basis for `project → agent` routing/isolation in P3.
|
|
70
|
-
*/
|
|
71
|
-
createdBy?: string;
|
|
72
|
-
metadata?: Record<string, unknown>;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Per-build metadata. Lives at projects/{projectId}/builds/{buildId}/meta.json.
|
|
76
|
-
*/
|
|
77
|
-
export interface BuildMeta {
|
|
78
|
-
id: string;
|
|
79
|
-
projectId: string;
|
|
80
|
-
builtAt: number;
|
|
81
|
-
gitSha?: string;
|
|
82
|
-
gitDirty?: boolean;
|
|
83
|
-
sourceDigest?: string;
|
|
84
|
-
nodeVersion?: string;
|
|
85
|
-
/** 'vite' | 'webpack' | 'esbuild' | 'rspack' | … */
|
|
86
|
-
bundler?: string;
|
|
87
|
-
bundlerVersion?: string;
|
|
88
|
-
/** Timestamp when this build's dev server was closed. */
|
|
89
|
-
endedAt?: number;
|
|
90
|
-
metadata?: Record<string, unknown>;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Node in a project tree returned by `getProjectTree`.
|
|
94
|
-
*/
|
|
95
|
-
export interface ProjectTreeNode {
|
|
96
|
-
id: string;
|
|
97
|
-
displayName?: string;
|
|
98
|
-
tags?: string[];
|
|
99
|
-
children: ProjectTreeNode[];
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Per-tab metadata. Lives at tabs/{tabId}/meta.json.
|
|
103
|
-
* A tab spans multiple sessions and may host multiple projects.
|
|
104
|
-
*/
|
|
105
|
-
export interface TabMeta {
|
|
106
|
-
id: string;
|
|
107
|
-
userAgent?: string;
|
|
108
|
-
connectedAt: number;
|
|
109
|
-
disconnectedAt?: number;
|
|
110
|
-
metadata?: Record<string, unknown>;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Per-session (pageload) metadata. Lives at sessions/{sessionId}/meta.json.
|
|
114
|
-
* A session = one pageload. Multiple projects/iframes may participate
|
|
115
|
-
* (they share sessionId via tryInheritFromParent).
|
|
116
|
-
*/
|
|
117
|
-
export interface SessionMeta {
|
|
118
|
-
/** sessionId generated by the runtime (shared across same-origin iframes). */
|
|
119
|
-
id: string;
|
|
120
|
-
tabId: string;
|
|
121
|
-
startedAt: number;
|
|
122
|
-
endedAt?: number;
|
|
123
|
-
url?: string;
|
|
124
|
-
title?: string;
|
|
125
|
-
referrer?: string;
|
|
126
|
-
userAgent?: string;
|
|
127
|
-
/**
|
|
128
|
-
* Every (projectId, buildId) pair that participated in this pageload.
|
|
129
|
-
* Merge semantics: new participants are appended on each upsertSession call.
|
|
130
|
-
*/
|
|
131
|
-
participants: Array<{
|
|
132
|
-
projectId: string;
|
|
133
|
-
buildId?: string;
|
|
134
|
-
joinedAt: number;
|
|
135
|
-
}>;
|
|
136
|
-
initial?: {
|
|
137
|
-
viewport?: {
|
|
138
|
-
w: number;
|
|
139
|
-
h: number;
|
|
140
|
-
dpr: number;
|
|
141
|
-
};
|
|
142
|
-
storageKeys?: {
|
|
143
|
-
local?: number;
|
|
144
|
-
session?: number;
|
|
145
|
-
cookie?: number;
|
|
146
|
-
};
|
|
147
|
-
storageTruncated?: boolean;
|
|
148
|
-
};
|
|
149
|
-
/**
|
|
150
|
-
* Caller-identity tag (4.0 · P1): principal id of the connection that
|
|
151
|
-
* opened this session. Write-once. Informational in P1.
|
|
152
|
-
*/
|
|
153
|
-
createdBy?: string;
|
|
154
|
-
metadata?: Record<string, unknown>;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Per-visitor identity. Lives at visitors/{visitorId}/meta.json. Stitches a
|
|
158
|
-
* user's activity across pageloads / refreshes / tabs.
|
|
159
|
-
*/
|
|
160
|
-
export interface VisitorMeta {
|
|
161
|
-
/** visitorId — anonymous UUID persisted in browser localStorage. */
|
|
162
|
-
id: string;
|
|
163
|
-
/** App-supplied identifier (latest non-empty value wins). */
|
|
164
|
-
userId?: string;
|
|
165
|
-
firstSeenAt: number;
|
|
166
|
-
lastSeenAt: number;
|
|
167
|
-
/** Distinct sessions (pageloads) attributed to this visitor. */
|
|
168
|
-
sessionCount: number;
|
|
169
|
-
/** LRU-capped list of distinct tabIds seen (max 50). */
|
|
170
|
-
tabIds: string[];
|
|
171
|
-
/** Distinct projects this visitor has touched (max 50). */
|
|
172
|
-
projectIds: string[];
|
|
173
|
-
/** Last-seen environment snapshot. */
|
|
174
|
-
lastEnv?: VisitorEnv;
|
|
175
|
-
}
|
|
176
|
-
export interface TailOptions {
|
|
177
|
-
/** Number of lines to return from the end. Default 50. */
|
|
178
|
-
n?: number;
|
|
179
|
-
/** Filter by event type(s). */
|
|
180
|
-
type?: EventType | EventType[];
|
|
181
|
-
/** Only return events after this timestamp. */
|
|
182
|
-
since?: number;
|
|
183
|
-
/** Only return events before this timestamp. */
|
|
184
|
-
until?: number;
|
|
185
|
-
/** Filter by projectId (useful for multi-project session timelines). */
|
|
186
|
-
projectId?: string;
|
|
187
|
-
}
|
|
188
|
-
export interface SearchOptions {
|
|
189
|
-
/** Filter by event type(s). */
|
|
190
|
-
type?: EventType | EventType[];
|
|
191
|
-
/** Max results. Default 50. */
|
|
192
|
-
limit?: number;
|
|
193
|
-
}
|
|
194
|
-
export interface RecordingChunkSummary {
|
|
195
|
-
chunkId: string;
|
|
196
|
-
tabId: string;
|
|
197
|
-
startTs: number;
|
|
198
|
-
endTs: number;
|
|
199
|
-
eventCount: number;
|
|
200
|
-
}
|
|
201
|
-
export interface RecordingChunk extends RecordingChunkSummary {
|
|
202
|
-
events: unknown[];
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Metadata for a saved replay export.
|
|
206
|
-
*/
|
|
207
|
-
export interface ReplayExportMeta {
|
|
208
|
-
exportId: string;
|
|
209
|
-
projectId: string;
|
|
210
|
-
sessionId: string;
|
|
211
|
-
tabId?: string;
|
|
212
|
-
label?: string;
|
|
213
|
-
since: number;
|
|
214
|
-
until: number;
|
|
215
|
-
startTs: number;
|
|
216
|
-
endTs: number;
|
|
217
|
-
chunkCount: number;
|
|
218
|
-
eventCount: number;
|
|
219
|
-
bytes: number;
|
|
220
|
-
createdAt: number;
|
|
221
|
-
}
|
|
222
|
-
export interface SessionSummary {
|
|
223
|
-
session: SessionMeta;
|
|
224
|
-
counts: Partial<Record<EventType, number>>;
|
|
225
|
-
lastError?: StoreEvent;
|
|
226
|
-
lastActivity?: number;
|
|
227
|
-
tabs: string[];
|
|
228
|
-
}
|
|
229
|
-
export interface RetentionPolicy {
|
|
230
|
-
/** Delete sessions older than this many days. Default 7. */
|
|
231
|
-
maxAgeDays?: number;
|
|
232
|
-
/** Keep at most this many sessions globally. Default 200. */
|
|
233
|
-
maxSessions?: number;
|
|
234
|
-
/** Delete recording.jsonl files older than this many days. Default 3. */
|
|
235
|
-
recordingRetentionDays?: number;
|
|
236
|
-
/** Keep at most this many recording chunks per session. */
|
|
237
|
-
maxRecordingChunksPerSession?: number;
|
|
238
|
-
/** Keep at most this many bytes of recording data per session. */
|
|
239
|
-
maxRecordingBytesPerSession?: number;
|
|
240
|
-
/** Prefer keeping chunks that overlap rrweb markers when trimming. */
|
|
241
|
-
preserveMarkedChunks?: boolean;
|
|
242
|
-
/** Keep at most this many replay exports per project. Default 50. */
|
|
243
|
-
maxExportsPerProject?: number;
|
|
244
|
-
/** Keep at most this many bytes of replay exports per project. Default 200MB. */
|
|
245
|
-
maxExportBytesPerProject?: number;
|
|
246
|
-
/** Keep at most this many BuildMeta records per project. Default 100. */
|
|
247
|
-
maxBuildsPerProject?: number;
|
|
248
|
-
/** @deprecated Use maxSessions. */
|
|
249
|
-
maxSessionsPerProject?: number;
|
|
250
|
-
/** @deprecated Use maxRecordingChunksPerSession. */
|
|
251
|
-
maxRecordingChunksPerTab?: number;
|
|
252
|
-
/** @deprecated Use maxRecordingBytesPerSession. */
|
|
253
|
-
maxRecordingBytesPerTab?: number;
|
|
254
|
-
}
|
|
255
|
-
export interface PurgeResult {
|
|
256
|
-
sessionsDeleted: number;
|
|
257
|
-
recordingsDeleted: number;
|
|
258
|
-
exportsDeleted: number;
|
|
259
|
-
buildsDeleted?: number;
|
|
260
|
-
bytesFreed: number;
|
|
261
|
-
}
|
|
262
|
-
export interface ITaskStore {
|
|
263
|
-
loadTasks(projectId: string): Task[];
|
|
264
|
-
saveTasks(projectId: string, tasks: Task[]): void;
|
|
265
|
-
}
|
|
266
|
-
export interface MemoryEntry {
|
|
267
|
-
key: string;
|
|
268
|
-
value: string;
|
|
269
|
-
updatedAt: number;
|
|
270
|
-
}
|
|
271
|
-
export interface IMemoryStore {
|
|
272
|
-
get(projectId: string, key: string): MemoryEntry | undefined;
|
|
273
|
-
set(projectId: string, key: string, value: string): MemoryEntry;
|
|
274
|
-
delete(projectId: string, key: string): boolean;
|
|
275
|
-
list(projectId: string): MemoryEntry[];
|
|
276
|
-
}
|
|
277
|
-
export interface IStore {
|
|
278
|
-
/**
|
|
279
|
-
* Open a new build (dev server start / prod build). Returns buildId.
|
|
280
|
-
* Writes projects/{projectId}/builds/{buildId}/meta.json.
|
|
281
|
-
* (Replaces openSession() from v0.3.x)
|
|
282
|
-
*/
|
|
283
|
-
openBuild(projectId: string, patch?: Partial<Omit<BuildMeta, 'id' | 'projectId' | 'builtAt'>>): string;
|
|
284
|
-
/**
|
|
285
|
-
* Mark a build as ended.
|
|
286
|
-
* (Replaces closeSession() for build-plugin connections from v0.3.x)
|
|
287
|
-
*/
|
|
288
|
-
closeBuild(buildId: string, closedAt?: number): void;
|
|
289
|
-
/**
|
|
290
|
-
* Write or update tab metadata at tabs/{tabId}/meta.json.
|
|
291
|
-
* Merge semantics: caller-provided fields overwrite, others preserved.
|
|
292
|
-
*/
|
|
293
|
-
upsertTab(tabId: string, patch: Partial<Omit<TabMeta, 'id'>>): TabMeta;
|
|
294
|
-
/** Get tab metadata. */
|
|
295
|
-
getTab(tabId: string): TabMeta | undefined;
|
|
296
|
-
/**
|
|
297
|
-
* Mark a tab as disconnected.
|
|
298
|
-
* New signature: (tabId, disconnectedAt?) — no sessionId param.
|
|
299
|
-
*/
|
|
300
|
-
closeTab(tabId: string, disconnectedAt?: number): void;
|
|
301
|
-
/**
|
|
302
|
-
* Open or update a session (one pageload). Writes sessions/{sessionId}/meta.json.
|
|
303
|
-
* participants list is extended (not replaced) on each call.
|
|
304
|
-
* (Replaces openLoad() from v0.3.x)
|
|
305
|
-
*/
|
|
306
|
-
upsertSession(sessionId: string, meta: Partial<Omit<SessionMeta, 'id'>> & {
|
|
307
|
-
tabId: string;
|
|
308
|
-
startedAt: number;
|
|
309
|
-
}): SessionMeta;
|
|
310
|
-
/**
|
|
311
|
-
* Mark a session as ended.
|
|
312
|
-
* (Replaces closeLatestLoad() from v0.3.x)
|
|
313
|
-
*/
|
|
314
|
-
closeSession(sessionId: string, endedAt?: number): void;
|
|
315
|
-
/** Get session metadata. */
|
|
316
|
-
getSession(sessionId: string): SessionMeta | undefined;
|
|
317
|
-
/**
|
|
318
|
-
* List sessions by recency.
|
|
319
|
-
* New signature: opts object with optional tabId / projectId / buildId / limit.
|
|
320
|
-
* (Replaces listSessions(projectId, limit?) from v0.3.x)
|
|
321
|
-
*/
|
|
322
|
-
listSessions(opts?: {
|
|
323
|
-
tabId?: string;
|
|
324
|
-
projectId?: string;
|
|
325
|
-
buildId?: string;
|
|
326
|
-
limit?: number;
|
|
327
|
-
}): SessionMeta[];
|
|
328
|
-
/**
|
|
329
|
-
* Append a single event to sessions/{sessionId}/timeline.jsonl.
|
|
330
|
-
* event.projectId and event.buildId should be pre-stamped by the bridge.
|
|
331
|
-
* (Replaces append(sessionId=buildId, event, tabId?) from v0.3.x)
|
|
332
|
-
*/
|
|
333
|
-
appendEvent(sessionId: string, event: StoreEvent): void;
|
|
334
|
-
/**
|
|
335
|
-
* Append a batch of events.
|
|
336
|
-
* (Replaces appendBatch() from v0.3.x)
|
|
337
|
-
*/
|
|
338
|
-
appendEventBatch(sessionId: string, events: StoreEvent[]): void;
|
|
339
|
-
/**
|
|
340
|
-
* Append an rrweb recording chunk to sessions/{sessionId}/recording.jsonl.
|
|
341
|
-
* (Replaces appendRecording(sessionId, tabId, chunk, loadId?) from v0.3.x)
|
|
342
|
-
*/
|
|
343
|
-
appendRecording(sessionId: string, chunk: unknown): void;
|
|
344
|
-
/** Write a project-level note. */
|
|
345
|
-
writeNote(projectId: string, key: string, value: string): void;
|
|
346
|
-
/**
|
|
347
|
-
* Upsert project metadata. `id` and `createdAt` are never overwritten.
|
|
348
|
-
* Throws if `patch.parentProjectId` would create a cycle.
|
|
349
|
-
*/
|
|
350
|
-
upsertProject(projectId: string, patch: Partial<Omit<ProjectMeta, 'id' | 'createdAt'>>): ProjectMeta;
|
|
351
|
-
/** Read a single project's metadata. */
|
|
352
|
-
getProject(projectId: string): ProjectMeta | undefined;
|
|
353
|
-
/** List all known projects. */
|
|
354
|
-
listProjects(): ProjectMeta[];
|
|
355
|
-
/** Upsert build metadata. Creates the project dir if missing. */
|
|
356
|
-
upsertBuild(projectId: string, buildId: string, patch: Partial<Omit<BuildMeta, 'id' | 'projectId'>>): BuildMeta;
|
|
357
|
-
/** Read a single build's metadata. */
|
|
358
|
-
getBuild(projectId: string, buildId: string): BuildMeta | undefined;
|
|
359
|
-
/** List builds for a project, newest first. */
|
|
360
|
-
listBuilds(projectId: string, limit?: number): BuildMeta[];
|
|
361
|
-
/** Get a forest (or sub-tree from `rootId`) from parentProjectId links. */
|
|
362
|
-
getProjectTree(rootId?: string): ProjectTreeNode[];
|
|
363
|
-
/**
|
|
364
|
-
* Upsert visitor metadata. Merges with existing meta:
|
|
365
|
-
* - `firstSeenAt` preserved; `lastSeenAt` advances
|
|
366
|
-
* - `sessionCount` increments when caller passes `incrementSession: true`
|
|
367
|
-
* - `tabIds` / `projectIds` deduped and LRU-capped at 50
|
|
368
|
-
* - `userId` overwritten if patch carries a non-empty value
|
|
369
|
-
* - `lastEnv` overwritten if patch carries one
|
|
370
|
-
*/
|
|
371
|
-
upsertVisitor(visitorId: string, patch: {
|
|
372
|
-
userId?: string;
|
|
373
|
-
seenAt?: number;
|
|
374
|
-
incrementSession?: boolean;
|
|
375
|
-
addTabId?: string;
|
|
376
|
-
addProjectId?: string;
|
|
377
|
-
lastEnv?: VisitorEnv;
|
|
378
|
-
}): VisitorMeta;
|
|
379
|
-
/** Read a single visitor's metadata. */
|
|
380
|
-
getVisitor(visitorId: string): VisitorMeta | undefined;
|
|
381
|
-
/** List known visitors, newest lastSeenAt first. */
|
|
382
|
-
listVisitors(opts?: {
|
|
383
|
-
projectId?: string;
|
|
384
|
-
limit?: number;
|
|
385
|
-
}): VisitorMeta[];
|
|
386
|
-
/**
|
|
387
|
-
* Read the last N events from a session timeline.
|
|
388
|
-
* New signature: no tabId param (tab is implicit per session).
|
|
389
|
-
*/
|
|
390
|
-
tail(sessionId: string, opts?: TailOptions): StoreEvent[];
|
|
391
|
-
/** Search events in a session timeline by substring match. */
|
|
392
|
-
search(sessionId: string, query: string, opts?: SearchOptions): StoreEvent[];
|
|
393
|
-
/** List recording chunks for a session. */
|
|
394
|
-
listRecordings(sessionId: string): RecordingChunkSummary[];
|
|
395
|
-
/** Return recording chunks overlapping the requested time window. */
|
|
396
|
-
sliceRecordings(sessionId: string, since: number, until: number): RecordingChunk[];
|
|
397
|
-
/** Persist a replay export. */
|
|
398
|
-
writeExport(input: {
|
|
399
|
-
sessionId: string;
|
|
400
|
-
tabId?: string;
|
|
401
|
-
since: number;
|
|
402
|
-
until: number;
|
|
403
|
-
label?: string;
|
|
404
|
-
events: unknown[];
|
|
405
|
-
startTs: number;
|
|
406
|
-
endTs: number;
|
|
407
|
-
chunkCount: number;
|
|
408
|
-
}): ReplayExportMeta;
|
|
409
|
-
/** Read export metadata by id. */
|
|
410
|
-
getExport(exportId: string): ReplayExportMeta | undefined;
|
|
411
|
-
/** Read the raw events array for an export. */
|
|
412
|
-
readExportEvents(exportId: string): unknown[] | undefined;
|
|
413
|
-
/** List exports for a project, newest first. */
|
|
414
|
-
listExports(projectId: string, limit?: number): ReplayExportMeta[];
|
|
415
|
-
/** Get a summary of a session (counts, last error, etc.). */
|
|
416
|
-
summary(sessionId: string): SessionSummary;
|
|
417
|
-
/** Read project notes. */
|
|
418
|
-
listNotes(projectId: string): Array<{
|
|
419
|
-
key: string;
|
|
420
|
-
value: string;
|
|
421
|
-
ts: number;
|
|
422
|
-
}>;
|
|
423
|
-
/** Delete old sessions and recordings according to retention policy. */
|
|
424
|
-
purge(policy?: RetentionPolicy): PurgeResult;
|
|
425
|
-
/** Close any open file handles. */
|
|
426
|
-
close(): void | Promise<void>;
|
|
427
|
-
}
|
package/dist/store/types.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Store types — the public interface for the JSONL-based persistence layer.
|
|
3
|
-
*
|
|
4
|
-
* v0.4.0 layout (new, flat):
|
|
5
|
-
* {dataDir}/projects/{projectId}/meta.json
|
|
6
|
-
* {dataDir}/projects/{projectId}/tasks.json
|
|
7
|
-
* {dataDir}/projects/{projectId}/memory.json
|
|
8
|
-
* {dataDir}/projects/{projectId}/notes.jsonl
|
|
9
|
-
* {dataDir}/projects/{projectId}/builds/{buildId}/meta.json
|
|
10
|
-
* {dataDir}/tabs/{tabId}/meta.json
|
|
11
|
-
* {dataDir}/sessions/{sessionId}/meta.json ← one per pageload
|
|
12
|
-
* {dataDir}/sessions/{sessionId}/timeline.jsonl ← mixed parent+child events
|
|
13
|
-
* {dataDir}/sessions/{sessionId}/recording.jsonl ← rrweb chunks
|
|
14
|
-
* {dataDir}/visitors/{visitorId}/meta.json ← per-browser identity (0.5+)
|
|
15
|
-
*
|
|
16
|
-
* Legacy layout (v0.3.x, read-only fallback — daemon warns on startup):
|
|
17
|
-
* {dataDir}/{projectId}/sessions/{buildId}/tabs/{tabId}/...
|
|
18
|
-
*/
|
|
19
|
-
export {};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* visitor.timeline — merge event timelines across all sessions belonging to
|
|
3
|
-
* one visitor. Pulled out of mcp.ts so the merge / filter logic is unit
|
|
4
|
-
* testable without spinning up an McpServer.
|
|
5
|
-
*/
|
|
6
|
-
import type { IStore, StoreEvent } from './store/index.js';
|
|
7
|
-
export interface VisitorTimelineOptions {
|
|
8
|
-
since?: number;
|
|
9
|
-
until?: number;
|
|
10
|
-
types?: string | string[];
|
|
11
|
-
tabIds?: string[];
|
|
12
|
-
sessionIds?: string[];
|
|
13
|
-
limit?: number;
|
|
14
|
-
}
|
|
15
|
-
export interface VisitorTimelineResult {
|
|
16
|
-
visitorId: string;
|
|
17
|
-
sessionCount: number;
|
|
18
|
-
eventCount: number;
|
|
19
|
-
truncated: boolean;
|
|
20
|
-
events: StoreEvent[];
|
|
21
|
-
}
|
|
22
|
-
export declare function buildVisitorTimeline(store: IStore, visitorId: string, opts?: VisitorTimelineOptions): VisitorTimelineResult | {
|
|
23
|
-
error: string;
|
|
24
|
-
};
|
package/dist/visitorTimeline.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* visitor.timeline — merge event timelines across all sessions belonging to
|
|
3
|
-
* one visitor. Pulled out of mcp.ts so the merge / filter logic is unit
|
|
4
|
-
* testable without spinning up an McpServer.
|
|
5
|
-
*/
|
|
6
|
-
const DEFAULT_LIMIT = 200;
|
|
7
|
-
const SESSION_DISCOVERY_PAGE = 200;
|
|
8
|
-
export function buildVisitorTimeline(store, visitorId, opts = {}) {
|
|
9
|
-
const visitor = store.getVisitor(visitorId);
|
|
10
|
-
if (!visitor)
|
|
11
|
-
return { error: `visitor not found: ${visitorId}` };
|
|
12
|
-
const cap = opts.limit ?? DEFAULT_LIMIT;
|
|
13
|
-
const tabFilter = opts.tabIds && opts.tabIds.length > 0 ? new Set(opts.tabIds) : undefined;
|
|
14
|
-
// 1. Discover candidate sessions (or honor the explicit list).
|
|
15
|
-
const candidateIds = new Set();
|
|
16
|
-
if (opts.sessionIds && opts.sessionIds.length > 0) {
|
|
17
|
-
for (const id of opts.sessionIds)
|
|
18
|
-
candidateIds.add(id);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
const visitorTabs = new Set(visitor.tabIds);
|
|
22
|
-
for (const pid of visitor.projectIds) {
|
|
23
|
-
for (const sess of store.listSessions({ projectId: pid, limit: SESSION_DISCOVERY_PAGE })) {
|
|
24
|
-
if (candidateIds.has(sess.id))
|
|
25
|
-
continue;
|
|
26
|
-
if (sess.tabId && !visitorTabs.has(sess.tabId))
|
|
27
|
-
continue;
|
|
28
|
-
if (tabFilter && sess.tabId && !tabFilter.has(sess.tabId))
|
|
29
|
-
continue;
|
|
30
|
-
candidateIds.add(sess.id);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// 2. Pull tail() from each session and merge. Over-fetch by 1 per session
|
|
35
|
-
// so we can detect single-session truncation (tail returns exactly `cap`
|
|
36
|
-
// when there are more, indistinguishable from "the session had exactly
|
|
37
|
-
// `cap` events" otherwise).
|
|
38
|
-
const merged = [];
|
|
39
|
-
let perSessionTruncated = false;
|
|
40
|
-
for (const sid of candidateIds) {
|
|
41
|
-
const events = store.tail(sid, {
|
|
42
|
-
n: cap + 1,
|
|
43
|
-
type: opts.types,
|
|
44
|
-
since: opts.since,
|
|
45
|
-
until: opts.until,
|
|
46
|
-
});
|
|
47
|
-
if (events.length > cap)
|
|
48
|
-
perSessionTruncated = true;
|
|
49
|
-
for (const ev of events) {
|
|
50
|
-
if (ev.visitorId && ev.visitorId !== visitorId)
|
|
51
|
-
continue;
|
|
52
|
-
if (tabFilter && ev.tab && !tabFilter.has(ev.tab))
|
|
53
|
-
continue;
|
|
54
|
-
merged.push(ev);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
// 3. Ascending sort, then trim to the newest `cap` events.
|
|
58
|
-
merged.sort((a, b) => a.ts - b.ts);
|
|
59
|
-
const truncated = perSessionTruncated || merged.length > cap;
|
|
60
|
-
const slice = merged.length > cap ? merged.slice(merged.length - cap) : merged;
|
|
61
|
-
return {
|
|
62
|
-
visitorId,
|
|
63
|
-
sessionCount: candidateIds.size,
|
|
64
|
-
eventCount: slice.length,
|
|
65
|
-
truncated,
|
|
66
|
-
events: slice,
|
|
67
|
-
};
|
|
68
|
-
}
|
package/src/auth.test.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import type { IncomingMessage } from 'node:http';
|
|
3
|
-
import {
|
|
4
|
-
extractToken,
|
|
5
|
-
isAuthEnabled,
|
|
6
|
-
isAuthorized,
|
|
7
|
-
verifyToken,
|
|
8
|
-
} from './auth.js';
|
|
9
|
-
|
|
10
|
-
function fakeReq(init: { headers?: Record<string, string>; url?: string }): IncomingMessage {
|
|
11
|
-
return {
|
|
12
|
-
headers: init.headers ?? {},
|
|
13
|
-
url: init.url ?? '/',
|
|
14
|
-
} as unknown as IncomingMessage;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('auth: isAuthEnabled', () => {
|
|
18
|
-
it('disabled when no token', () => {
|
|
19
|
-
expect(isAuthEnabled({})).toBe(false);
|
|
20
|
-
expect(isAuthEnabled({ token: '' })).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
it('enabled with token', () => {
|
|
23
|
-
expect(isAuthEnabled({ token: 'x' })).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('auth: extractToken', () => {
|
|
28
|
-
it('reads Authorization: Bearer …', () => {
|
|
29
|
-
const req = fakeReq({ headers: { authorization: 'Bearer my-token' } });
|
|
30
|
-
expect(extractToken(req)).toBe('my-token');
|
|
31
|
-
});
|
|
32
|
-
it('reads cookie harness_fe_token=…', () => {
|
|
33
|
-
const req = fakeReq({ headers: { cookie: 'other=1; harness_fe_token=cookie-tok; bar=2' } });
|
|
34
|
-
expect(extractToken(req)).toBe('cookie-tok');
|
|
35
|
-
});
|
|
36
|
-
it('reads ?token=… query string', () => {
|
|
37
|
-
const req = fakeReq({ url: '/dashboard?foo=1&token=qs-tok&bar=2' });
|
|
38
|
-
expect(extractToken(req)).toBe('qs-tok');
|
|
39
|
-
});
|
|
40
|
-
it('reads WS subprotocol harness-fe.token.…', () => {
|
|
41
|
-
const req = fakeReq({
|
|
42
|
-
headers: { 'sec-websocket-protocol': 'json, harness-fe.token.ws-tok' },
|
|
43
|
-
});
|
|
44
|
-
expect(extractToken(req)).toBe('ws-tok');
|
|
45
|
-
});
|
|
46
|
-
it('header beats cookie beats query', () => {
|
|
47
|
-
const req = fakeReq({
|
|
48
|
-
headers: { authorization: 'Bearer hdr', cookie: 'harness_fe_token=ck' },
|
|
49
|
-
url: '/?token=qs',
|
|
50
|
-
});
|
|
51
|
-
expect(extractToken(req)).toBe('hdr');
|
|
52
|
-
});
|
|
53
|
-
it('returns undefined when none provided', () => {
|
|
54
|
-
expect(extractToken(fakeReq({}))).toBeUndefined();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('auth: verifyToken (timing-safe)', () => {
|
|
59
|
-
it('matches identical tokens', () => {
|
|
60
|
-
expect(verifyToken('abc', 'abc')).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
it('rejects mismatched tokens', () => {
|
|
63
|
-
expect(verifyToken('abc', 'abd')).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
it('rejects empty/undefined', () => {
|
|
66
|
-
expect(verifyToken(undefined, 'abc')).toBe(false);
|
|
67
|
-
expect(verifyToken('', 'abc')).toBe(false);
|
|
68
|
-
expect(verifyToken('abc', '')).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
it('handles different length safely (no throw)', () => {
|
|
71
|
-
expect(verifyToken('a', 'abcdefg')).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('auth: isAuthorized', () => {
|
|
76
|
-
it('passes everything when auth disabled', () => {
|
|
77
|
-
expect(isAuthorized(fakeReq({}), {})).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
it('passes valid token via header', () => {
|
|
80
|
-
const req = fakeReq({ headers: { authorization: 'Bearer s3cret' } });
|
|
81
|
-
expect(isAuthorized(req, { token: 's3cret' })).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
it('rejects missing token when auth enabled', () => {
|
|
84
|
-
expect(isAuthorized(fakeReq({}), { token: 's3cret' })).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
it('rejects wrong token', () => {
|
|
87
|
-
const req = fakeReq({ headers: { authorization: 'Bearer nope' } });
|
|
88
|
-
expect(isAuthorized(req, { token: 's3cret' })).toBe(false);
|
|
89
|
-
});
|
|
90
|
-
});
|