devharness 0.9.7 → 0.9.9

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 (119) hide show
  1. package/README.md +15 -0
  2. package/build/block-events.d.ts +3 -5
  3. package/build/block-events.d.ts.map +1 -1
  4. package/build/block-events.js +6 -24
  5. package/build/block-events.js.map +1 -1
  6. package/build/block-events.test.js +10 -7
  7. package/build/block-events.test.js.map +1 -1
  8. package/build/cdp-manager.d.ts +7 -0
  9. package/build/cdp-manager.d.ts.map +1 -1
  10. package/build/cdp-manager.js +9 -0
  11. package/build/cdp-manager.js.map +1 -1
  12. package/build/cli/index.d.ts +26 -0
  13. package/build/cli/index.d.ts.map +1 -0
  14. package/build/cli/index.js +290 -0
  15. package/build/cli/index.js.map +1 -0
  16. package/build/cli/process-tree.d.ts +21 -0
  17. package/build/cli/process-tree.d.ts.map +1 -0
  18. package/build/cli/process-tree.js +51 -0
  19. package/build/cli/process-tree.js.map +1 -0
  20. package/build/cli/session-match.d.ts +37 -0
  21. package/build/cli/session-match.d.ts.map +1 -0
  22. package/build/cli/session-match.js +55 -0
  23. package/build/cli/session-match.js.map +1 -0
  24. package/build/cli/session-match.test.d.ts +17 -0
  25. package/build/cli/session-match.test.d.ts.map +1 -0
  26. package/build/cli/session-match.test.js +125 -0
  27. package/build/cli/session-match.test.js.map +1 -0
  28. package/build/config.d.ts +1 -1
  29. package/build/config.d.ts.map +1 -1
  30. package/build/config.js +1 -0
  31. package/build/config.js.map +1 -1
  32. package/build/connection-manager.d.ts.map +1 -1
  33. package/build/connection-manager.js +6 -1
  34. package/build/connection-manager.js.map +1 -1
  35. package/build/index.js +57 -4
  36. package/build/index.js.map +1 -1
  37. package/build/mcp-supervisor.js +43 -0
  38. package/build/mcp-supervisor.js.map +1 -1
  39. package/build/network-monitor.d.ts +13 -2
  40. package/build/network-monitor.d.ts.map +1 -1
  41. package/build/network-monitor.js +54 -23
  42. package/build/network-monitor.js.map +1 -1
  43. package/build/network-monitor.test.d.ts +11 -0
  44. package/build/network-monitor.test.d.ts.map +1 -0
  45. package/build/network-monitor.test.js +151 -0
  46. package/build/network-monitor.test.js.map +1 -0
  47. package/build/plugin-session-start-hook.test.d.ts +10 -0
  48. package/build/plugin-session-start-hook.test.d.ts.map +1 -0
  49. package/build/plugin-session-start-hook.test.js +187 -0
  50. package/build/plugin-session-start-hook.test.js.map +1 -0
  51. package/build/session-endpoint.d.ts +72 -0
  52. package/build/session-endpoint.d.ts.map +1 -0
  53. package/build/session-endpoint.js +220 -0
  54. package/build/session-endpoint.js.map +1 -0
  55. package/build/session-endpoint.test.d.ts +10 -0
  56. package/build/session-endpoint.test.d.ts.map +1 -0
  57. package/build/session-endpoint.test.js +202 -0
  58. package/build/session-endpoint.test.js.map +1 -0
  59. package/build/session-events.d.ts +28 -0
  60. package/build/session-events.d.ts.map +1 -0
  61. package/build/session-events.js +38 -0
  62. package/build/session-events.js.map +1 -0
  63. package/build/session-events.test.d.ts +8 -0
  64. package/build/session-events.test.d.ts.map +1 -0
  65. package/build/session-events.test.js +56 -0
  66. package/build/session-events.test.js.map +1 -0
  67. package/build/session-identity.d.ts +56 -0
  68. package/build/session-identity.d.ts.map +1 -0
  69. package/build/session-identity.js +96 -0
  70. package/build/session-identity.js.map +1 -0
  71. package/build/session-identity.test.d.ts +9 -0
  72. package/build/session-identity.test.d.ts.map +1 -0
  73. package/build/session-identity.test.js +126 -0
  74. package/build/session-identity.test.js.map +1 -0
  75. package/build/session-messages.d.ts +70 -0
  76. package/build/session-messages.d.ts.map +1 -0
  77. package/build/session-messages.js +139 -0
  78. package/build/session-messages.js.map +1 -0
  79. package/build/tool-response.d.ts +40 -1
  80. package/build/tool-response.d.ts.map +1 -1
  81. package/build/tool-response.js.map +1 -1
  82. package/build/tools/console-tools.d.ts +4 -0
  83. package/build/tools/console-tools.d.ts.map +1 -1
  84. package/build/tools/console-tools.js +55 -1
  85. package/build/tools/console-tools.js.map +1 -1
  86. package/build/tools/inspect-worker-meta.test.d.ts +9 -0
  87. package/build/tools/inspect-worker-meta.test.d.ts.map +1 -0
  88. package/build/tools/inspect-worker-meta.test.js +122 -0
  89. package/build/tools/inspect-worker-meta.test.js.map +1 -0
  90. package/build/tools/inspection-tools.d.ts +12 -5
  91. package/build/tools/inspection-tools.d.ts.map +1 -1
  92. package/build/tools/inspection-tools.js +86 -5
  93. package/build/tools/inspection-tools.js.map +1 -1
  94. package/build/tools/issues-tools.d.ts +3 -3
  95. package/build/tools/message-tools.d.ts +51 -0
  96. package/build/tools/message-tools.d.ts.map +1 -0
  97. package/build/tools/message-tools.js +250 -0
  98. package/build/tools/message-tools.js.map +1 -0
  99. package/build/tools/message-tools.test.d.ts +9 -0
  100. package/build/tools/message-tools.test.d.ts.map +1 -0
  101. package/build/tools/message-tools.test.js +332 -0
  102. package/build/tools/message-tools.test.js.map +1 -0
  103. package/build/tools/network-tools.js +1 -1
  104. package/build/tools/network-tools.js.map +1 -1
  105. package/build/tools/replay-tools.d.ts +3 -3
  106. package/build/tools/server-tools.d.ts +3 -3
  107. package/build/worker-targets.d.ts +75 -0
  108. package/build/worker-targets.d.ts.map +1 -0
  109. package/build/worker-targets.js +171 -0
  110. package/build/worker-targets.js.map +1 -0
  111. package/build/worker-targets.test.d.ts +8 -0
  112. package/build/worker-targets.test.d.ts.map +1 -0
  113. package/build/worker-targets.test.js +118 -0
  114. package/build/worker-targets.test.js.map +1 -0
  115. package/docs/instructions.md +23 -12
  116. package/docs/messages.md +150 -0
  117. package/package.json +3 -1
  118. package/plugin/skills/devharness/SKILL.md +36 -8
  119. package/plugin/skills/devharness/references/tool-categories.md +8 -1
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Session endpoint - the private channel a CLI process uses to run a tool
3
+ * inside this MCP session, against the connections this session already holds.
4
+ *
5
+ * A unix socket, not a TCP port. The tools reachable here evaluate JavaScript
6
+ * in the session's browser and read its cookies; a web page can reach a
7
+ * localhost port and cannot open a socket file, so the address type is the
8
+ * access control.
9
+ *
10
+ * On Windows the address is a named pipe. Node's `net` API is identical for
11
+ * both, and libuv rejects remote clients, but a pipe carries no mode bits -
12
+ * the 0600 guarantee holds on POSIX only.
13
+ */
14
+ import { createServer } from 'net';
15
+ import { promises as fs, existsSync, readFileSync, readdirSync, unlinkSync, chmodSync } from 'fs';
16
+ import { join } from 'path';
17
+ import { getOutputPath, getTempPath } from './helpers/paths.js';
18
+ import { isProcessAlive } from './helpers/process-liveness.js';
19
+ /** sun_path is 104 bytes on macOS and 108 on Linux; a path near that length
20
+ * fails to bind with ENAMETOOLONG. */
21
+ const MAX_SOCKET_PATH = 96;
22
+ /** Owned by this feature alone: `~/.devharness/sessions/` already holds the
23
+ * supervisor presence records `server-claims.ts` writes, under a different
24
+ * schema. */
25
+ export function getEndpointsDir() {
26
+ return getOutputPath('endpoints', { global: true });
27
+ }
28
+ export function getPresencePath(pid) {
29
+ return join(getEndpointsDir(), `${pid}.json`);
30
+ }
31
+ /** The listen address for `pid`: a named pipe on Windows, a socket file under
32
+ * the sessions directory otherwise, falling back to the temp directory when
33
+ * that path would exceed the sockaddr limit. */
34
+ export function getEndpointAddress(pid) {
35
+ if (process.platform === 'win32') {
36
+ return `\\\\.\\pipe\\devharness-${pid}`;
37
+ }
38
+ const preferred = join(getEndpointsDir(), `${pid}.sock`);
39
+ if (preferred.length <= MAX_SOCKET_PATH)
40
+ return preferred;
41
+ return getTempPath(`session-${pid}.sock`);
42
+ }
43
+ /** Every session record on disk, newest first. Records naming a dead process
44
+ * are dropped and their files removed: a killed session leaves both behind,
45
+ * and a CLI matching against one would dial a socket nothing is listening on. */
46
+ export function listSessionRecords() {
47
+ let entries;
48
+ try {
49
+ entries = readdirSync(getEndpointsDir());
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ const records = [];
55
+ for (const entry of entries) {
56
+ if (!entry.endsWith('.json'))
57
+ continue;
58
+ const path = join(getEndpointsDir(), entry);
59
+ let record;
60
+ try {
61
+ record = JSON.parse(readFileSync(path, 'utf-8'));
62
+ }
63
+ catch {
64
+ continue;
65
+ }
66
+ if (!record?.pid || !record.address)
67
+ continue;
68
+ if (!isProcessAlive(record.pid)) {
69
+ try {
70
+ unlinkSync(path);
71
+ }
72
+ catch { /* another process swept it first */ }
73
+ if (process.platform !== 'win32') {
74
+ try {
75
+ unlinkSync(record.address);
76
+ }
77
+ catch { /* already gone */ }
78
+ }
79
+ continue;
80
+ }
81
+ records.push(record);
82
+ }
83
+ // A rebuild restarts every child at once, so startedAt ties are the norm
84
+ // rather than the exception; without the pid the order is readdir's, and
85
+ // `--session=<shortId>` would resolve to a different server run to run.
86
+ return records.sort((a, b) => b.startedAt - a.startedAt || a.pid - b.pid);
87
+ }
88
+ /**
89
+ * Listen for CLI tool calls and publish the presence record that locates this
90
+ * session. Returns null when the listener cannot bind - the MCP session keeps
91
+ * working, the CLI just cannot reach it.
92
+ */
93
+ export async function startSessionEndpoint(options) {
94
+ const pid = options.identity.pid;
95
+ const address = getEndpointAddress(pid);
96
+ const presencePath = getPresencePath(pid);
97
+ await fs.mkdir(getEndpointsDir(), { recursive: true });
98
+ // A previous process with this pid, or this process before a crash, leaves a
99
+ // socket file that bind() refuses. Nothing is listening on it - the pid check
100
+ // in listSessionRecords already proved that for every record we keep.
101
+ if (process.platform !== 'win32' && existsSync(address)) {
102
+ try {
103
+ unlinkSync(address);
104
+ }
105
+ catch { /* bind will report it */ }
106
+ }
107
+ const server = createServer((socket) => {
108
+ handleConnection(socket, options);
109
+ });
110
+ const bound = await new Promise((resolve) => {
111
+ server.once('error', (error) => {
112
+ console.error(`[devharness] session endpoint failed to listen on ${address}: ${error.message}`);
113
+ resolve(false);
114
+ });
115
+ server.listen(address, () => resolve(true));
116
+ });
117
+ if (!bound)
118
+ return null;
119
+ // Owner-only. On Windows the pipe carries no mode bits, so this is skipped
120
+ // and another account on the machine may be able to connect.
121
+ if (process.platform !== 'win32') {
122
+ try {
123
+ chmodSync(address, 0o600);
124
+ }
125
+ catch { /* best effort */ }
126
+ }
127
+ const record = {
128
+ ...options.identity,
129
+ address,
130
+ startedAt: Date.now(),
131
+ };
132
+ await writeRecord(presencePath, record);
133
+ return {
134
+ address,
135
+ presencePath,
136
+ async refresh(identity) {
137
+ Object.assign(record, identity);
138
+ await writeRecord(presencePath, record);
139
+ },
140
+ async close() {
141
+ await new Promise((resolve) => server.close(() => resolve()));
142
+ try {
143
+ unlinkSync(presencePath);
144
+ }
145
+ catch { /* already gone */ }
146
+ if (process.platform !== 'win32') {
147
+ try {
148
+ unlinkSync(address);
149
+ }
150
+ catch { /* already gone */ }
151
+ }
152
+ },
153
+ };
154
+ }
155
+ async function writeRecord(path, record) {
156
+ const temp = `${path}.${process.pid}.tmp`;
157
+ await fs.writeFile(temp, JSON.stringify(record), { mode: 0o600 });
158
+ await fs.rename(temp, path);
159
+ }
160
+ /**
161
+ * One request per connection: a single JSON line in, a single JSON line out,
162
+ * then close. A tool response carries no framing of its own, so the newline is
163
+ * what tells the caller the reply is complete.
164
+ */
165
+ function handleConnection(socket, options) {
166
+ let buffer = '';
167
+ let handled = false;
168
+ // A caller that gives up - its own timeout, or a Ctrl-C - takes the work with
169
+ // it. Without this a `message send` holding for a reply keeps waiting after
170
+ // the CLI has gone, receives the reply, marks it read and answers nobody.
171
+ const controller = new AbortController();
172
+ const abandon = () => controller.abort();
173
+ socket.setEncoding('utf-8');
174
+ socket.on('error', abandon);
175
+ socket.on('close', abandon);
176
+ socket.on('data', (chunk) => {
177
+ if (handled)
178
+ return;
179
+ buffer += chunk;
180
+ const newline = buffer.indexOf('\n');
181
+ if (newline === -1)
182
+ return;
183
+ handled = true;
184
+ const line = buffer.slice(0, newline);
185
+ void respond(socket, line, options, controller.signal);
186
+ });
187
+ }
188
+ async function respond(socket, line, options, abortSignal) {
189
+ const send = (reply) => {
190
+ socket.end(JSON.stringify(reply) + '\n');
191
+ };
192
+ let request;
193
+ try {
194
+ request = JSON.parse(line);
195
+ }
196
+ catch {
197
+ send({ ok: false, error: 'Request was not a single line of JSON' });
198
+ return;
199
+ }
200
+ if (!request?.tool || typeof request.tool !== 'string') {
201
+ send({ ok: false, error: 'Request needs a tool name' });
202
+ return;
203
+ }
204
+ try {
205
+ if (options.awaitReady)
206
+ await options.awaitReady();
207
+ const response = await options.executeToolCall(request.tool, request.args ?? {}, abortSignal);
208
+ send({ ok: true, response });
209
+ }
210
+ catch (error) {
211
+ // executeToolCall throws ToolError for an isError response; its payload is
212
+ // the response itself, which the caller renders like any other.
213
+ if (error?.response) {
214
+ send({ ok: true, response: error.response });
215
+ return;
216
+ }
217
+ send({ ok: false, error: error?.message || String(error) });
218
+ }
219
+ }
220
+ //# sourceMappingURL=session-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-endpoint.js","sourceRoot":"","sources":["../src/session-endpoint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Tests for the socket a CLI process calls tools through.
3
+ *
4
+ * The round-trip cases bind a real socket in a temp DEVHARNESS_DIR and connect
5
+ * to it, because the things worth asserting - that a ToolError comes back as a
6
+ * renderable response, that a half-written request does not reply early - are
7
+ * properties of the framing, not of a mocked handler.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=session-endpoint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-endpoint.test.d.ts","sourceRoot":"","sources":["../src/session-endpoint.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Tests for the socket a CLI process calls tools through.
3
+ *
4
+ * The round-trip cases bind a real socket in a temp DEVHARNESS_DIR and connect
5
+ * to it, because the things worth asserting - that a ToolError comes back as a
6
+ * renderable response, that a half-written request does not reply early - are
7
+ * properties of the framing, not of a mocked handler.
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
10
+ import { connect } from 'net';
11
+ import { mkdtempSync, rmSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs';
12
+ import { tmpdir } from 'os';
13
+ import { join } from 'path';
14
+ import { initializePaths } from './helpers/paths.js';
15
+ import { startSessionEndpoint, listSessionRecords, getEndpointsDir, getEndpointAddress, getPresencePath, } from './session-endpoint.js';
16
+ const posixOnly = process.platform === 'win32' ? it.skip : it;
17
+ let dir;
18
+ let previousDir;
19
+ let endpoint = null;
20
+ beforeEach(() => {
21
+ previousDir = process.env.DEVHARNESS_DIR;
22
+ dir = mkdtempSync(join(tmpdir(), 'devharness-endpoint-'));
23
+ process.env.DEVHARNESS_DIR = dir;
24
+ initializePaths();
25
+ });
26
+ afterEach(async () => {
27
+ if (endpoint) {
28
+ await endpoint.close();
29
+ endpoint = null;
30
+ }
31
+ if (previousDir === undefined)
32
+ delete process.env.DEVHARNESS_DIR;
33
+ else
34
+ process.env.DEVHARNESS_DIR = previousDir;
35
+ initializePaths();
36
+ rmSync(dir, { recursive: true, force: true });
37
+ });
38
+ /** One request over the socket, resolving with the parsed reply. */
39
+ function callEndpoint(address, payload) {
40
+ return new Promise((resolve, reject) => {
41
+ let buffer = '';
42
+ const socket = connect(address);
43
+ socket.setEncoding('utf-8');
44
+ socket.on('connect', () => socket.write(JSON.stringify(payload) + '\n'));
45
+ socket.on('data', (chunk) => {
46
+ buffer += chunk;
47
+ const newline = buffer.indexOf('\n');
48
+ if (newline === -1)
49
+ return;
50
+ socket.destroy();
51
+ try {
52
+ resolve(JSON.parse(buffer.slice(0, newline)));
53
+ }
54
+ catch (error) {
55
+ reject(error);
56
+ }
57
+ });
58
+ socket.on('error', reject);
59
+ });
60
+ }
61
+ const IDENTITY = { pid: process.pid, ppid: process.ppid, cwd: '/repo' };
62
+ // ---------------------------------------------------------------------------
63
+ // addresses
64
+ // ---------------------------------------------------------------------------
65
+ describe('getEndpointAddress', () => {
66
+ posixOnly('puts the socket beside the presence record', () => {
67
+ expect(getEndpointAddress(1234)).toBe(join(getEndpointsDir(), '1234.sock'));
68
+ expect(getPresencePath(1234)).toBe(join(getEndpointsDir(), '1234.json'));
69
+ });
70
+ posixOnly('falls back to the temp directory when the path would exceed the sockaddr limit', () => {
71
+ process.env.DEVHARNESS_DIR = join(dir, 'x'.repeat(120));
72
+ initializePaths();
73
+ const address = getEndpointAddress(1234);
74
+ expect(address).not.toContain('x'.repeat(120));
75
+ expect(address).toContain('session-1234.sock');
76
+ });
77
+ });
78
+ // ---------------------------------------------------------------------------
79
+ // round trip
80
+ // ---------------------------------------------------------------------------
81
+ describe('endpoint round trip', () => {
82
+ posixOnly('runs the named tool and returns its response', async () => {
83
+ const calls = [];
84
+ endpoint = await startSessionEndpoint({
85
+ executeToolCall: async (tool, args) => {
86
+ calls.push({ tool, args });
87
+ return { content: [{ type: 'text', text: 'ran it' }], _meta: { tool } };
88
+ },
89
+ identity: IDENTITY,
90
+ });
91
+ const reply = await callEndpoint(endpoint.address, { tool: 'config', args: { action: 'status' } });
92
+ expect(reply.ok).toBe(true);
93
+ expect(reply.response.content[0].text).toBe('ran it');
94
+ expect(calls).toEqual([{ tool: 'config', args: { action: 'status' } }]);
95
+ });
96
+ posixOnly('returns a ToolError\'s response, so an error renders like any other result', async () => {
97
+ const failure = { content: [{ type: 'text', text: 'nope' }], isError: true, _errorId: 'SOME_ERROR' };
98
+ endpoint = await startSessionEndpoint({
99
+ executeToolCall: async () => { throw Object.assign(new Error('nope'), { response: failure }); },
100
+ identity: IDENTITY,
101
+ });
102
+ const reply = await callEndpoint(endpoint.address, { tool: 'whatever', args: {} });
103
+ expect(reply.ok).toBe(true);
104
+ expect(reply.response).toEqual(failure);
105
+ });
106
+ posixOnly('reports a throw that carries no response as a failed call', async () => {
107
+ endpoint = await startSessionEndpoint({
108
+ executeToolCall: async () => { throw new Error('Unknown tool: nope'); },
109
+ identity: IDENTITY,
110
+ });
111
+ const reply = await callEndpoint(endpoint.address, { tool: 'nope', args: {} });
112
+ expect(reply.ok).toBe(false);
113
+ expect(reply.error).toBe('Unknown tool: nope');
114
+ });
115
+ posixOnly('rejects a request that is not JSON', async () => {
116
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({}), identity: IDENTITY });
117
+ const reply = await new Promise((resolve, reject) => {
118
+ let buffer = '';
119
+ const socket = connect(endpoint.address);
120
+ socket.setEncoding('utf-8');
121
+ socket.on('connect', () => socket.write('not json\n'));
122
+ socket.on('data', (chunk) => {
123
+ buffer += chunk;
124
+ if (!buffer.includes('\n'))
125
+ return;
126
+ socket.destroy();
127
+ resolve(JSON.parse(buffer.split('\n')[0]));
128
+ });
129
+ socket.on('error', reject);
130
+ });
131
+ expect(reply.ok).toBe(false);
132
+ expect(reply.error).toMatch(/single line of JSON/);
133
+ });
134
+ posixOnly('rejects a request with no tool name', async () => {
135
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({}), identity: IDENTITY });
136
+ const reply = await callEndpoint(endpoint.address, { args: {} });
137
+ expect(reply.ok).toBe(false);
138
+ expect(reply.error).toMatch(/tool name/);
139
+ });
140
+ posixOnly('binds over a socket file left by a process that was killed', async () => {
141
+ mkdirSync(getEndpointsDir(), { recursive: true });
142
+ writeFileSync(getEndpointAddress(process.pid), '');
143
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({ content: [] }), identity: IDENTITY });
144
+ expect(endpoint).not.toBeNull();
145
+ const reply = await callEndpoint(endpoint.address, { tool: 'x', args: {} });
146
+ expect(reply.ok).toBe(true);
147
+ });
148
+ posixOnly('gives the socket and the record owner-only permissions', async () => {
149
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({ content: [] }), identity: IDENTITY });
150
+ expect(statSync(endpoint.address).mode & 0o777).toBe(0o600);
151
+ expect(statSync(endpoint.presencePath).mode & 0o777).toBe(0o600);
152
+ });
153
+ });
154
+ // ---------------------------------------------------------------------------
155
+ // presence records
156
+ // ---------------------------------------------------------------------------
157
+ describe('presence records', () => {
158
+ posixOnly('publishes this session and picks it up again', async () => {
159
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({}), identity: IDENTITY });
160
+ const records = listSessionRecords();
161
+ expect(records).toHaveLength(1);
162
+ expect(records[0].pid).toBe(process.pid);
163
+ expect(records[0].address).toBe(endpoint.address);
164
+ expect(records[0].shortId).toBeUndefined();
165
+ });
166
+ posixOnly('carries the ids once refresh supplies them', async () => {
167
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({}), identity: IDENTITY });
168
+ await endpoint.refresh({ sessionId: 'full-uuid', shortId: 'abcd1234' });
169
+ expect(listSessionRecords()[0].shortId).toBe('abcd1234');
170
+ expect(listSessionRecords()[0].sessionId).toBe('full-uuid');
171
+ });
172
+ posixOnly('drops a record whose process is gone, and its socket with it', async () => {
173
+ mkdirSync(getEndpointsDir(), { recursive: true });
174
+ const deadPid = 2; // pid 2 is not a devharness session on any machine this runs on
175
+ const deadSocket = join(getEndpointsDir(), '999999.sock');
176
+ writeFileSync(deadSocket, '');
177
+ writeFileSync(join(getEndpointsDir(), '999999.json'), JSON.stringify({
178
+ pid: 999999, ppid: deadPid, cwd: '/gone', address: deadSocket, startedAt: 1,
179
+ }));
180
+ expect(listSessionRecords()).toEqual([]);
181
+ expect(existsSync(join(getEndpointsDir(), '999999.json'))).toBe(false);
182
+ expect(existsSync(deadSocket)).toBe(false);
183
+ });
184
+ posixOnly('ignores a file that is not one of ours', async () => {
185
+ mkdirSync(getEndpointsDir(), { recursive: true });
186
+ // The shape server-claims.ts writes - no address, so nothing to dial.
187
+ writeFileSync(join(getEndpointsDir(), '4242.json'), JSON.stringify({
188
+ supervisorPid: 4242, cwd: '/repo', childPid: 4243,
189
+ }));
190
+ expect(listSessionRecords()).toEqual([]);
191
+ expect(existsSync(join(getEndpointsDir(), '4242.json'))).toBe(true);
192
+ });
193
+ posixOnly('removes the record and the socket on close', async () => {
194
+ endpoint = await startSessionEndpoint({ executeToolCall: async () => ({}), identity: IDENTITY });
195
+ const { address, presencePath } = endpoint;
196
+ await endpoint.close();
197
+ endpoint = null;
198
+ expect(existsSync(address)).toBe(false);
199
+ expect(existsSync(presencePath)).toBe(false);
200
+ });
201
+ });
202
+ //# sourceMappingURL=session-endpoint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-endpoint.test.js","sourceRoot":"","sources":["../src/session-endpoint.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * One event stream per session.
3
+ *
4
+ * Every push event devharness produces - a guard block, an incoming message -
5
+ * appends one JSON line to `~/.devharness/events/<session>.jsonl`. One file
6
+ * means one watch: a session arms a single Monitor and receives every kind of
7
+ * event, including kinds added later, instead of one watch per feature.
8
+ *
9
+ * The plugin's SessionStart hook names this path to each session as it starts,
10
+ * which is what gets the watch armed. Nothing here verifies that a watch
11
+ * exists: a session with none simply receives nothing, the same as before the
12
+ * stream existed.
13
+ */
14
+ export type EventKind = 'block' | 'message';
15
+ export interface SessionEvent {
16
+ ts: string;
17
+ kind: EventKind;
18
+ [key: string]: unknown;
19
+ }
20
+ export declare function getEventsDir(): string;
21
+ export declare function getEventStreamPath(sessionName: string): string;
22
+ /**
23
+ * Append one event. Failures are logged and swallowed: the stream is a
24
+ * notification path, and losing a line must not fail the operation that
25
+ * produced it.
26
+ */
27
+ export declare function appendEvent(sessionName: string, kind: EventKind, payload: Record<string, unknown>): Promise<void>;
28
+ //# sourceMappingURL=session-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-events.d.ts","sourceRoot":"","sources":["../src/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE5C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAQf"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * One event stream per session.
3
+ *
4
+ * Every push event devharness produces - a guard block, an incoming message -
5
+ * appends one JSON line to `~/.devharness/events/<session>.jsonl`. One file
6
+ * means one watch: a session arms a single Monitor and receives every kind of
7
+ * event, including kinds added later, instead of one watch per feature.
8
+ *
9
+ * The plugin's SessionStart hook names this path to each session as it starts,
10
+ * which is what gets the watch armed. Nothing here verifies that a watch
11
+ * exists: a session with none simply receives nothing, the same as before the
12
+ * stream existed.
13
+ */
14
+ import { promises as fs } from 'fs';
15
+ import { join } from 'path';
16
+ import { getOutputPath } from './helpers/paths.js';
17
+ export function getEventsDir() {
18
+ return getOutputPath('events', { global: true });
19
+ }
20
+ export function getEventStreamPath(sessionName) {
21
+ return join(getEventsDir(), `${sessionName}.jsonl`);
22
+ }
23
+ /**
24
+ * Append one event. Failures are logged and swallowed: the stream is a
25
+ * notification path, and losing a line must not fail the operation that
26
+ * produced it.
27
+ */
28
+ export async function appendEvent(sessionName, kind, payload) {
29
+ const event = { ts: new Date().toISOString(), kind, ...payload };
30
+ try {
31
+ await fs.mkdir(getEventsDir(), { recursive: true });
32
+ await fs.appendFile(getEventStreamPath(sessionName), JSON.stringify(event) + '\n');
33
+ }
34
+ catch (error) {
35
+ console.error(`[devharness] Failed to append ${kind} event: ${error}`);
36
+ }
37
+ }
38
+ //# sourceMappingURL=session-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-events.js","sourceRoot":"","sources":["../src/session-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAUnD,MAAM,UAAU,YAAY;IAC1B,OAAO,aAAa,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,GAAG,WAAW,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,WAAmB,EACnB,IAAe,EACf,OAAgC;IAEhC,MAAM,KAAK,GAAiB,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IAC/E,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACrF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,iCAAiC,IAAI,WAAW,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests for the per-session event stream.
3
+ *
4
+ * The property worth pinning is that every kind lands in one file: a session
5
+ * arms one watch, and a kind added later arrives on it without a second one.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=session-events.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-events.test.d.ts","sourceRoot":"","sources":["../src/session-events.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tests for the per-session event stream.
3
+ *
4
+ * The property worth pinning is that every kind lands in one file: a session
5
+ * arms one watch, and a kind added later arrives on it without a second one.
6
+ */
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import { mkdtempSync, rmSync, readFileSync, existsSync } from 'fs';
9
+ import { tmpdir } from 'os';
10
+ import { join } from 'path';
11
+ import { initializePaths } from './helpers/paths.js';
12
+ import { appendEvent, getEventStreamPath, getEventsDir, } from './session-events.js';
13
+ let dir;
14
+ let previousDir;
15
+ const SESSION = 'aaaaaaaa';
16
+ function readStream() {
17
+ const path = getEventStreamPath(SESSION);
18
+ if (!existsSync(path))
19
+ return [];
20
+ return readFileSync(path, 'utf-8')
21
+ .split('\n')
22
+ .filter(line => line.trim())
23
+ .map(line => JSON.parse(line));
24
+ }
25
+ beforeEach(() => {
26
+ previousDir = process.env.DEVHARNESS_DIR;
27
+ dir = mkdtempSync(join(tmpdir(), 'devharness-events-'));
28
+ process.env.DEVHARNESS_DIR = dir;
29
+ initializePaths();
30
+ });
31
+ afterEach(() => {
32
+ if (previousDir === undefined)
33
+ delete process.env.DEVHARNESS_DIR;
34
+ else
35
+ process.env.DEVHARNESS_DIR = previousDir;
36
+ initializePaths();
37
+ rmSync(dir, { recursive: true, force: true });
38
+ });
39
+ describe('appendEvent', () => {
40
+ it('writes one line per event, each stamped and kinded', async () => {
41
+ await appendEvent(SESSION, 'block', { guard: 'port', tool: 'navigate' });
42
+ await appendEvent(SESSION, 'message', { from: 'bbbbbbbb' });
43
+ const events = readStream();
44
+ expect(events.map(e => e.kind)).toEqual(['block', 'message']);
45
+ expect(events[0]).toMatchObject({ kind: 'block', guard: 'port', tool: 'navigate' });
46
+ expect(typeof events[0].ts).toBe('string');
47
+ });
48
+ it('puts every kind in the one file, so one watch covers them all', async () => {
49
+ await appendEvent(SESSION, 'block', {});
50
+ await appendEvent(SESSION, 'message', {});
51
+ await appendEvent(SESSION, 'block', {});
52
+ expect(getEventStreamPath(SESSION)).toBe(join(getEventsDir(), 'aaaaaaaa.jsonl'));
53
+ expect(readStream()).toHaveLength(3);
54
+ });
55
+ });
56
+ //# sourceMappingURL=session-events.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-events.test.js","sourceRoot":"","sources":["../src/session-events.test.ts"],"names":[],"mappings":"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"}