devharness 0.9.8 → 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.
- package/README.md +15 -0
- package/build/block-events.d.ts +3 -5
- package/build/block-events.d.ts.map +1 -1
- package/build/block-events.js +6 -24
- package/build/block-events.js.map +1 -1
- package/build/block-events.test.js +10 -7
- package/build/block-events.test.js.map +1 -1
- package/build/cdp-manager.d.ts +7 -0
- package/build/cdp-manager.d.ts.map +1 -1
- package/build/cdp-manager.js +9 -0
- package/build/cdp-manager.js.map +1 -1
- package/build/cli/index.d.ts +26 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +290 -0
- package/build/cli/index.js.map +1 -0
- package/build/cli/process-tree.d.ts +21 -0
- package/build/cli/process-tree.d.ts.map +1 -0
- package/build/cli/process-tree.js +51 -0
- package/build/cli/process-tree.js.map +1 -0
- package/build/cli/session-match.d.ts +37 -0
- package/build/cli/session-match.d.ts.map +1 -0
- package/build/cli/session-match.js +55 -0
- package/build/cli/session-match.js.map +1 -0
- package/build/cli/session-match.test.d.ts +17 -0
- package/build/cli/session-match.test.d.ts.map +1 -0
- package/build/cli/session-match.test.js +125 -0
- package/build/cli/session-match.test.js.map +1 -0
- package/build/config.d.ts +1 -1
- package/build/config.d.ts.map +1 -1
- package/build/config.js +1 -0
- package/build/config.js.map +1 -1
- package/build/connection-manager.d.ts.map +1 -1
- package/build/connection-manager.js +6 -1
- package/build/connection-manager.js.map +1 -1
- package/build/index.js +57 -4
- package/build/index.js.map +1 -1
- package/build/mcp-supervisor.js +43 -0
- package/build/mcp-supervisor.js.map +1 -1
- package/build/network-monitor.d.ts +13 -2
- package/build/network-monitor.d.ts.map +1 -1
- package/build/network-monitor.js +38 -2
- package/build/network-monitor.js.map +1 -1
- package/build/network-monitor.test.js +50 -0
- package/build/network-monitor.test.js.map +1 -1
- package/build/plugin-session-start-hook.test.d.ts +10 -0
- package/build/plugin-session-start-hook.test.d.ts.map +1 -0
- package/build/plugin-session-start-hook.test.js +187 -0
- package/build/plugin-session-start-hook.test.js.map +1 -0
- package/build/session-endpoint.d.ts +72 -0
- package/build/session-endpoint.d.ts.map +1 -0
- package/build/session-endpoint.js +220 -0
- package/build/session-endpoint.js.map +1 -0
- package/build/session-endpoint.test.d.ts +10 -0
- package/build/session-endpoint.test.d.ts.map +1 -0
- package/build/session-endpoint.test.js +202 -0
- package/build/session-endpoint.test.js.map +1 -0
- package/build/session-events.d.ts +28 -0
- package/build/session-events.d.ts.map +1 -0
- package/build/session-events.js +38 -0
- package/build/session-events.js.map +1 -0
- package/build/session-events.test.d.ts +8 -0
- package/build/session-events.test.d.ts.map +1 -0
- package/build/session-events.test.js +56 -0
- package/build/session-events.test.js.map +1 -0
- package/build/session-identity.d.ts +56 -0
- package/build/session-identity.d.ts.map +1 -0
- package/build/session-identity.js +96 -0
- package/build/session-identity.js.map +1 -0
- package/build/session-identity.test.d.ts +9 -0
- package/build/session-identity.test.d.ts.map +1 -0
- package/build/session-identity.test.js +126 -0
- package/build/session-identity.test.js.map +1 -0
- package/build/session-messages.d.ts +70 -0
- package/build/session-messages.d.ts.map +1 -0
- package/build/session-messages.js +139 -0
- package/build/session-messages.js.map +1 -0
- package/build/tool-response.d.ts +40 -1
- package/build/tool-response.d.ts.map +1 -1
- package/build/tool-response.js.map +1 -1
- package/build/tools/console-tools.d.ts +4 -0
- package/build/tools/console-tools.d.ts.map +1 -1
- package/build/tools/console-tools.js +55 -1
- package/build/tools/console-tools.js.map +1 -1
- package/build/tools/inspect-worker-meta.test.d.ts +9 -0
- package/build/tools/inspect-worker-meta.test.d.ts.map +1 -0
- package/build/tools/inspect-worker-meta.test.js +122 -0
- package/build/tools/inspect-worker-meta.test.js.map +1 -0
- package/build/tools/inspection-tools.d.ts +12 -5
- package/build/tools/inspection-tools.d.ts.map +1 -1
- package/build/tools/inspection-tools.js +86 -5
- package/build/tools/inspection-tools.js.map +1 -1
- package/build/tools/issues-tools.d.ts +3 -3
- package/build/tools/message-tools.d.ts +51 -0
- package/build/tools/message-tools.d.ts.map +1 -0
- package/build/tools/message-tools.js +250 -0
- package/build/tools/message-tools.js.map +1 -0
- package/build/tools/message-tools.test.d.ts +9 -0
- package/build/tools/message-tools.test.d.ts.map +1 -0
- package/build/tools/message-tools.test.js +332 -0
- package/build/tools/message-tools.test.js.map +1 -0
- package/build/tools/network-tools.js +1 -1
- package/build/tools/network-tools.js.map +1 -1
- package/build/tools/replay-tools.d.ts +3 -3
- package/build/tools/server-tools.d.ts +3 -3
- package/build/worker-targets.d.ts +75 -0
- package/build/worker-targets.d.ts.map +1 -0
- package/build/worker-targets.js +171 -0
- package/build/worker-targets.js.map +1 -0
- package/build/worker-targets.test.d.ts +8 -0
- package/build/worker-targets.test.d.ts.map +1 -0
- package/build/worker-targets.test.js +118 -0
- package/build/worker-targets.test.js.map +1 -0
- package/docs/instructions.md +23 -12
- package/docs/messages.md +150 -0
- package/package.json +2 -1
- package/plugin/skills/devharness/SKILL.md +36 -8
- package/plugin/skills/devharness/references/tool-categories.md +8 -1
|
@@ -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":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAa,MAAM,EAAE,YAAY,EAAiB,UAAU,EAAE,MAAM,IAAI,CAAC;AAC7F,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAwC,MAAM,oBAAoB,CAAC;AAC3F,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAE7B,IAAI,GAAW,CAAC;AAChB,IAAI,WAA+B,CAAC;AAEpC,MAAM,OAAO,GAAG,UAAU,CAAC;AAE3B,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC;SAC/B,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AACnC,CAAC;AAED,UAAU,CAAC,GAAG,EAAE;IACd,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACzC,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,GAAG,CAAC;IACjC,eAAe,EAAE,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;;QAC5D,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,WAAW,CAAC;IAC9C,eAAe,EAAE,CAAC;IAClB,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QACzE,MAAM,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAE5D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QACpF,MAAM,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAExC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACjF,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Claude Code session this process serves.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code exports CLAUDE_CODE_SESSION_ID into the environment it spawns
|
|
5
|
+
* the supervisor with, and the child inherits it. That value is present from
|
|
6
|
+
* the first instruction and unchanged when the supervisor replaces the child,
|
|
7
|
+
* so anything naming this session on disk - a mailbox filename, a presence
|
|
8
|
+
* record - keeps the same name across a rebuild.
|
|
9
|
+
*
|
|
10
|
+
* The session detector produces the same id, but only after a tool response
|
|
11
|
+
* carrying a `pid:` footer has been written into the session's .jsonl and
|
|
12
|
+
* matched. A session driven from the CLI writes no such footer, and a child
|
|
13
|
+
* restarted by a rebuild has not written one yet.
|
|
14
|
+
*/
|
|
15
|
+
/** The full session id from the environment, or undefined when this process
|
|
16
|
+
* was not spawned by Claude Code. */
|
|
17
|
+
export declare function getClaudeSessionId(): string | undefined;
|
|
18
|
+
/** The first 8 characters of the session id - the form the dashboard and the
|
|
19
|
+
* session detector both display. */
|
|
20
|
+
export declare function getClaudeShortId(): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The name this session is known by on disk - its mailbox filename, its event
|
|
23
|
+
* stream filename, its presence record.
|
|
24
|
+
*
|
|
25
|
+
* The hook's record comes first: it is the only source that follows the
|
|
26
|
+
* conversation through a `/clear`, which changes the id without restarting this
|
|
27
|
+
* process. The environment is next, correct from the first instruction and for
|
|
28
|
+
* as long as the conversation lasts. The detector covers a client that exports
|
|
29
|
+
* no session id. The pid form is last, and changes on every restart, which is
|
|
30
|
+
* why it is the fallback rather than the default.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveSessionName(detectedShortId?: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The Claude process this server belongs to.
|
|
35
|
+
*
|
|
36
|
+
* `CLAUDE_CODE_MESSAGING_SOCKET` carries that pid in its filename, and it is
|
|
37
|
+
* present in both the hook's environment and the server's, under `-p` and
|
|
38
|
+
* interactively. `CLAUDE_PID` carries it more directly and is absent from the
|
|
39
|
+
* server under `-p`, so the socket path is the derivation that works on both
|
|
40
|
+
* sides.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getClientPid(): number | undefined;
|
|
43
|
+
/** Where the plugin's SessionStart hook records the conversation a client is
|
|
44
|
+
* currently on. */
|
|
45
|
+
export declare function getClientsDir(): string;
|
|
46
|
+
export declare function getClientRecordPath(clientPid: number): string;
|
|
47
|
+
/**
|
|
48
|
+
* The conversation this client is on now, as last written by the hook.
|
|
49
|
+
*
|
|
50
|
+
* `CLAUDE_CODE_SESSION_ID` is read once when the process is spawned, and
|
|
51
|
+
* `/clear` gives the conversation a new id without restarting it. The hook runs
|
|
52
|
+
* on every start reason including `clear`, carrying the current id, so this
|
|
53
|
+
* file is the only source that tracks the change while the process lives.
|
|
54
|
+
*/
|
|
55
|
+
export declare function readCurrentSessionId(): string | undefined;
|
|
56
|
+
//# sourceMappingURL=session-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-identity.d.ts","sourceRoot":"","sources":["../src/session-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH;sCACsC;AACtC,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAIvD;AAED;qCACqC;AACrC,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAErD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAKnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAKjD;AAED;oBACoB;AACpB,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CAUzD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Claude Code session this process serves.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code exports CLAUDE_CODE_SESSION_ID into the environment it spawns
|
|
5
|
+
* the supervisor with, and the child inherits it. That value is present from
|
|
6
|
+
* the first instruction and unchanged when the supervisor replaces the child,
|
|
7
|
+
* so anything naming this session on disk - a mailbox filename, a presence
|
|
8
|
+
* record - keeps the same name across a rebuild.
|
|
9
|
+
*
|
|
10
|
+
* The session detector produces the same id, but only after a tool response
|
|
11
|
+
* carrying a `pid:` footer has been written into the session's .jsonl and
|
|
12
|
+
* matched. A session driven from the CLI writes no such footer, and a child
|
|
13
|
+
* restarted by a rebuild has not written one yet.
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'fs';
|
|
16
|
+
import { join, basename } from 'path';
|
|
17
|
+
import { getOutputPath } from './helpers/paths.js';
|
|
18
|
+
/** Ids become filenames; anything outside this set could escape the directory
|
|
19
|
+
* holding them. */
|
|
20
|
+
const SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
21
|
+
/** The full session id from the environment, or undefined when this process
|
|
22
|
+
* was not spawned by Claude Code. */
|
|
23
|
+
export function getClaudeSessionId() {
|
|
24
|
+
const value = process.env.CLAUDE_CODE_SESSION_ID;
|
|
25
|
+
if (!value || !SESSION_ID_PATTERN.test(value))
|
|
26
|
+
return undefined;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/** The first 8 characters of the session id - the form the dashboard and the
|
|
30
|
+
* session detector both display. */
|
|
31
|
+
export function getClaudeShortId() {
|
|
32
|
+
return getClaudeSessionId()?.slice(0, 8);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The name this session is known by on disk - its mailbox filename, its event
|
|
36
|
+
* stream filename, its presence record.
|
|
37
|
+
*
|
|
38
|
+
* The hook's record comes first: it is the only source that follows the
|
|
39
|
+
* conversation through a `/clear`, which changes the id without restarting this
|
|
40
|
+
* process. The environment is next, correct from the first instruction and for
|
|
41
|
+
* as long as the conversation lasts. The detector covers a client that exports
|
|
42
|
+
* no session id. The pid form is last, and changes on every restart, which is
|
|
43
|
+
* why it is the fallback rather than the default.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveSessionName(detectedShortId) {
|
|
46
|
+
return readCurrentSessionId()?.slice(0, 8)
|
|
47
|
+
?? getClaudeShortId()
|
|
48
|
+
?? detectedShortId
|
|
49
|
+
?? `pid-${process.pid}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The Claude process this server belongs to.
|
|
53
|
+
*
|
|
54
|
+
* `CLAUDE_CODE_MESSAGING_SOCKET` carries that pid in its filename, and it is
|
|
55
|
+
* present in both the hook's environment and the server's, under `-p` and
|
|
56
|
+
* interactively. `CLAUDE_PID` carries it more directly and is absent from the
|
|
57
|
+
* server under `-p`, so the socket path is the derivation that works on both
|
|
58
|
+
* sides.
|
|
59
|
+
*/
|
|
60
|
+
export function getClientPid() {
|
|
61
|
+
const socket = process.env.CLAUDE_CODE_MESSAGING_SOCKET;
|
|
62
|
+
if (!socket)
|
|
63
|
+
return undefined;
|
|
64
|
+
const pid = Number(basename(socket).replace(/\.sock$/, ''));
|
|
65
|
+
return Number.isInteger(pid) && pid > 0 ? pid : undefined;
|
|
66
|
+
}
|
|
67
|
+
/** Where the plugin's SessionStart hook records the conversation a client is
|
|
68
|
+
* currently on. */
|
|
69
|
+
export function getClientsDir() {
|
|
70
|
+
return getOutputPath('clients', { global: true });
|
|
71
|
+
}
|
|
72
|
+
export function getClientRecordPath(clientPid) {
|
|
73
|
+
return join(getClientsDir(), `${clientPid}.json`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The conversation this client is on now, as last written by the hook.
|
|
77
|
+
*
|
|
78
|
+
* `CLAUDE_CODE_SESSION_ID` is read once when the process is spawned, and
|
|
79
|
+
* `/clear` gives the conversation a new id without restarting it. The hook runs
|
|
80
|
+
* on every start reason including `clear`, carrying the current id, so this
|
|
81
|
+
* file is the only source that tracks the change while the process lives.
|
|
82
|
+
*/
|
|
83
|
+
export function readCurrentSessionId() {
|
|
84
|
+
const clientPid = getClientPid();
|
|
85
|
+
if (clientPid === undefined)
|
|
86
|
+
return undefined;
|
|
87
|
+
try {
|
|
88
|
+
const record = JSON.parse(readFileSync(getClientRecordPath(clientPid), 'utf-8'));
|
|
89
|
+
const id = record?.sessionId;
|
|
90
|
+
return typeof id === 'string' && SESSION_ID_PATTERN.test(id) ? id : undefined;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=session-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-identity.js","sourceRoot":"","sources":["../src/session-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;oBACoB;AACpB,MAAM,kBAAkB,GAAG,oCAAoC,CAAC;AAEhE;sCACsC;AACtC,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IACjD,IAAI,CAAC,KAAK,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChE,OAAO,KAAK,CAAC;AACf,CAAC;AAED;qCACqC;AACrC,MAAM,UAAU,gBAAgB;IAC9B,OAAO,kBAAkB,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,eAAwB;IACzD,OAAO,oBAAoB,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;WACrC,gBAAgB,EAAE;WAClB,eAAe;WACf,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC;IACxD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5D,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED;oBACoB;AACpB,MAAM,UAAU,aAAa;IAC3B,OAAO,aAAa,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACjF,MAAM,EAAE,GAAG,MAAM,EAAE,SAAS,CAAC;QAC7B,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for reading the Claude Code session id out of the environment.
|
|
3
|
+
*
|
|
4
|
+
* The id becomes a filename, so a value that could traverse out of the
|
|
5
|
+
* directory holding mailboxes and presence records is rejected rather than
|
|
6
|
+
* sanitised - a sanitised name would silently address a different session.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=session-identity.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-identity.test.d.ts","sourceRoot":"","sources":["../src/session-identity.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|