@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.
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 +49 -15
  9. package/dist/mcpHttp.d.ts +2 -2
  10. package/dist/mcpHttp.js +8 -2
  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 +51 -19
  17. package/src/mcpHttp.test.ts +3 -3
  18. package/src/mcpHttp.ts +10 -4
  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 -74
  36. package/dist/identity.js +0 -101
  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 -86
  78. package/src/identity.ts +0 -116
  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,197 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdtempSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { randomUUID } from 'node:crypto';
6
- import { JsonlStore } from './store/index.js';
7
- import { buildVisitorTimeline } from './visitorTimeline.js';
8
-
9
- function openSession(
10
- store: JsonlStore,
11
- projectId: string,
12
- tabId: string,
13
- sessionId = randomUUID(),
14
- startedAt = Date.now(),
15
- ): string {
16
- store.upsertTab(tabId, { connectedAt: startedAt });
17
- store.upsertSession(sessionId, {
18
- tabId,
19
- startedAt,
20
- participants: [{ projectId, joinedAt: startedAt }],
21
- });
22
- return sessionId;
23
- }
24
-
25
- describe('buildVisitorTimeline', () => {
26
- let dir: string;
27
- let store: JsonlStore;
28
-
29
- beforeEach(() => {
30
- dir = mkdtempSync(join(tmpdir(), 'visitor-timeline-'));
31
- store = new JsonlStore(dir);
32
- });
33
-
34
- afterEach(() => {
35
- rmSync(dir, { recursive: true, force: true });
36
- });
37
-
38
- it('returns error when visitor not found', async () => {
39
- await store.flush();
40
- const result = buildVisitorTimeline(store, 'no-such-visitor');
41
- expect(result).toEqual({ error: 'visitor not found: no-such-visitor' });
42
- });
43
-
44
- it('merges events from multiple sessions ascending by ts', async () => {
45
- const visitorId = 'visitor-1';
46
- const projectId = 'proj-a';
47
- const tabA = 'tab-a';
48
- const tabB = 'tab-b';
49
-
50
- const sessA = openSession(store, projectId, tabA);
51
- const sessB = openSession(store, projectId, tabB);
52
- store.upsertVisitor(visitorId, { seenAt: Date.now(), addTabId: tabA, addProjectId: projectId });
53
- store.upsertVisitor(visitorId, { addTabId: tabB });
54
-
55
- // Interleave events across the two tabs.
56
- store.appendEvent(sessA, { ts: 1000, t: 'req', tab: tabA, visitorId, d: { url: '/a1' } });
57
- store.appendEvent(sessB, { ts: 1500, t: 'req', tab: tabB, visitorId, d: { url: '/b1' } });
58
- store.appendEvent(sessA, { ts: 2000, t: 'res', tab: tabA, visitorId, d: { status: 200 } });
59
- store.appendEvent(sessB, { ts: 2500, t: 'storage', tab: tabB, visitorId, d: { op: 'remove', key: 'token' } });
60
-
61
- await store.flush();
62
- const result = buildVisitorTimeline(store, visitorId);
63
- if ('error' in result) throw new Error(result.error);
64
-
65
- expect(result.eventCount).toBe(4);
66
- expect(result.sessionCount).toBe(2);
67
- expect(result.events.map((e) => e.ts)).toEqual([1000, 1500, 2000, 2500]);
68
- // tabId distribution proves both tabs contributed.
69
- const tabs = new Set(result.events.map((e) => e.tab));
70
- expect(tabs).toEqual(new Set([tabA, tabB]));
71
- });
72
-
73
- it('drops events from other visitors that landed in the same session', async () => {
74
- const visitorId = 'visitor-mine';
75
- const otherVisitor = 'visitor-stranger';
76
- const projectId = 'proj';
77
- const tabId = 'tab-shared';
78
-
79
- const sess = openSession(store, projectId, tabId);
80
- store.upsertVisitor(visitorId, { addTabId: tabId, addProjectId: projectId });
81
-
82
- store.appendEvent(sess, { ts: 1000, t: 'req', tab: tabId, visitorId, d: { url: '/mine' } });
83
- store.appendEvent(sess, { ts: 1500, t: 'req', tab: tabId, visitorId: otherVisitor, d: { url: '/other' } });
84
- store.appendEvent(sess, { ts: 2000, t: 'req', tab: tabId, visitorId, d: { url: '/mine2' } });
85
-
86
- await store.flush();
87
- const result = buildVisitorTimeline(store, visitorId);
88
- if ('error' in result) throw new Error(result.error);
89
-
90
- expect(result.eventCount).toBe(2);
91
- expect(result.events.every((e) => e.visitorId === visitorId)).toBe(true);
92
- });
93
-
94
- it('honors tabIds filter at both session-discovery and row level', async () => {
95
- const visitorId = 'visitor-1';
96
- const projectId = 'proj';
97
- const tabA = 'tab-a';
98
- const tabB = 'tab-b';
99
-
100
- const sessA = openSession(store, projectId, tabA);
101
- const sessB = openSession(store, projectId, tabB);
102
- store.upsertVisitor(visitorId, { addTabId: tabA, addProjectId: projectId });
103
- store.upsertVisitor(visitorId, { addTabId: tabB });
104
-
105
- store.appendEvent(sessA, { ts: 1000, t: 'req', tab: tabA, visitorId, d: {} });
106
- store.appendEvent(sessB, { ts: 2000, t: 'req', tab: tabB, visitorId, d: {} });
107
-
108
- await store.flush();
109
- const result = buildVisitorTimeline(store, visitorId, { tabIds: [tabA] });
110
- if ('error' in result) throw new Error(result.error);
111
-
112
- expect(result.eventCount).toBe(1);
113
- expect(result.events[0].tab).toBe(tabA);
114
- });
115
-
116
- it('honors types filter via store.tail', async () => {
117
- const visitorId = 'visitor-1';
118
- const projectId = 'proj';
119
- const tabId = 'tab-1';
120
-
121
- const sess = openSession(store, projectId, tabId);
122
- store.upsertVisitor(visitorId, { addTabId: tabId, addProjectId: projectId });
123
-
124
- store.appendEvent(sess, { ts: 1000, t: 'log', tab: tabId, visitorId, d: {} });
125
- store.appendEvent(sess, { ts: 2000, t: 'req', tab: tabId, visitorId, d: {} });
126
- store.appendEvent(sess, { ts: 3000, t: 'storage', tab: tabId, visitorId, d: {} });
127
-
128
- await store.flush();
129
- const result = buildVisitorTimeline(store, visitorId, { types: ['req', 'storage'] });
130
- if ('error' in result) throw new Error(result.error);
131
-
132
- expect(result.events.map((e) => e.t)).toEqual(['req', 'storage']);
133
- });
134
-
135
- it('limit takes the newest N events and reports truncated=true', async () => {
136
- const visitorId = 'visitor-1';
137
- const projectId = 'proj';
138
- const tabId = 'tab-1';
139
-
140
- const sess = openSession(store, projectId, tabId);
141
- store.upsertVisitor(visitorId, { addTabId: tabId, addProjectId: projectId });
142
-
143
- for (let i = 0; i < 10; i++) {
144
- store.appendEvent(sess, { ts: 1000 + i, t: 'log', tab: tabId, visitorId, d: { i } });
145
- }
146
-
147
- await store.flush();
148
- const result = buildVisitorTimeline(store, visitorId, { limit: 3 });
149
- if ('error' in result) throw new Error(result.error);
150
-
151
- expect(result.eventCount).toBe(3);
152
- expect(result.truncated).toBe(true);
153
- expect(result.events.map((e) => e.ts)).toEqual([1007, 1008, 1009]);
154
- });
155
-
156
- it('honors explicit sessionIds (skips visitor → session discovery)', async () => {
157
- const visitorId = 'visitor-1';
158
- const projectId = 'proj';
159
- const tabA = 'tab-a';
160
- const tabB = 'tab-b';
161
-
162
- const sessA = openSession(store, projectId, tabA);
163
- const sessB = openSession(store, projectId, tabB);
164
- store.upsertVisitor(visitorId, { addTabId: tabA, addProjectId: projectId });
165
- store.upsertVisitor(visitorId, { addTabId: tabB });
166
-
167
- store.appendEvent(sessA, { ts: 1000, t: 'log', tab: tabA, visitorId, d: {} });
168
- store.appendEvent(sessB, { ts: 2000, t: 'log', tab: tabB, visitorId, d: {} });
169
-
170
- await store.flush();
171
- const result = buildVisitorTimeline(store, visitorId, { sessionIds: [sessA] });
172
- if ('error' in result) throw new Error(result.error);
173
-
174
- expect(result.sessionCount).toBe(1);
175
- expect(result.eventCount).toBe(1);
176
- expect(result.events[0].tab).toBe(tabA);
177
- });
178
-
179
- it('honors since/until window', async () => {
180
- const visitorId = 'visitor-1';
181
- const projectId = 'proj';
182
- const tabId = 'tab-1';
183
-
184
- const sess = openSession(store, projectId, tabId);
185
- store.upsertVisitor(visitorId, { addTabId: tabId, addProjectId: projectId });
186
-
187
- store.appendEvent(sess, { ts: 1000, t: 'log', tab: tabId, visitorId, d: {} });
188
- store.appendEvent(sess, { ts: 2000, t: 'log', tab: tabId, visitorId, d: {} });
189
- store.appendEvent(sess, { ts: 3000, t: 'log', tab: tabId, visitorId, d: {} });
190
-
191
- await store.flush();
192
- const result = buildVisitorTimeline(store, visitorId, { since: 1500, until: 2500 });
193
- if ('error' in result) throw new Error(result.error);
194
-
195
- expect(result.events.map((e) => e.ts)).toEqual([2000]);
196
- });
197
- });
@@ -1,89 +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
-
7
- import type { IStore, StoreEvent } from './store/index.js';
8
-
9
- export interface VisitorTimelineOptions {
10
- since?: number;
11
- until?: number;
12
- types?: string | string[];
13
- tabIds?: string[];
14
- sessionIds?: string[];
15
- limit?: number;
16
- }
17
-
18
- export interface VisitorTimelineResult {
19
- visitorId: string;
20
- sessionCount: number;
21
- eventCount: number;
22
- truncated: boolean;
23
- events: StoreEvent[];
24
- }
25
-
26
- const DEFAULT_LIMIT = 200;
27
- const SESSION_DISCOVERY_PAGE = 200;
28
-
29
- export function buildVisitorTimeline(
30
- store: IStore,
31
- visitorId: string,
32
- opts: VisitorTimelineOptions = {},
33
- ): VisitorTimelineResult | { error: string } {
34
- const visitor = store.getVisitor(visitorId);
35
- if (!visitor) return { error: `visitor not found: ${visitorId}` };
36
-
37
- const cap = opts.limit ?? DEFAULT_LIMIT;
38
- const tabFilter = opts.tabIds && opts.tabIds.length > 0 ? new Set(opts.tabIds) : undefined;
39
-
40
- // 1. Discover candidate sessions (or honor the explicit list).
41
- const candidateIds = new Set<string>();
42
- if (opts.sessionIds && opts.sessionIds.length > 0) {
43
- for (const id of opts.sessionIds) candidateIds.add(id);
44
- } else {
45
- const visitorTabs = new Set(visitor.tabIds);
46
- for (const pid of visitor.projectIds) {
47
- for (const sess of store.listSessions({ projectId: pid, limit: SESSION_DISCOVERY_PAGE })) {
48
- if (candidateIds.has(sess.id)) continue;
49
- if (sess.tabId && !visitorTabs.has(sess.tabId)) continue;
50
- if (tabFilter && sess.tabId && !tabFilter.has(sess.tabId)) continue;
51
- candidateIds.add(sess.id);
52
- }
53
- }
54
- }
55
-
56
- // 2. Pull tail() from each session and merge. Over-fetch by 1 per session
57
- // so we can detect single-session truncation (tail returns exactly `cap`
58
- // when there are more, indistinguishable from "the session had exactly
59
- // `cap` events" otherwise).
60
- const merged: StoreEvent[] = [];
61
- let perSessionTruncated = false;
62
- for (const sid of candidateIds) {
63
- const events = store.tail(sid, {
64
- n: cap + 1,
65
- type: opts.types,
66
- since: opts.since,
67
- until: opts.until,
68
- });
69
- if (events.length > cap) perSessionTruncated = true;
70
- for (const ev of events) {
71
- if (ev.visitorId && ev.visitorId !== visitorId) continue;
72
- if (tabFilter && ev.tab && !tabFilter.has(ev.tab)) continue;
73
- merged.push(ev);
74
- }
75
- }
76
-
77
- // 3. Ascending sort, then trim to the newest `cap` events.
78
- merged.sort((a, b) => a.ts - b.ts);
79
- const truncated = perSessionTruncated || merged.length > cap;
80
- const slice = merged.length > cap ? merged.slice(merged.length - cap) : merged;
81
-
82
- return {
83
- visitorId,
84
- sessionCount: candidateIds.size,
85
- eventCount: slice.length,
86
- truncated,
87
- events: slice,
88
- };
89
- }