devharness 0.9.8 → 0.9.10
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 +16 -1
- 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 +2 -2
- package/build/config.d.ts.map +1 -1
- package/build/config.js +2 -1
- package/build/config.js.map +1 -1
- package/build/config.test.js +2 -2
- package/build/config.test.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/dashboard/dashboard-hub.js +2 -2
- package/build/index.js +60 -7
- package/build/index.js.map +1 -1
- package/build/interaction-recorder.js +2 -2
- package/build/interaction-recorder.js.map +1 -1
- package/build/issue-tracker.test.js +1 -1
- package/build/issue-tracker.test.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/persistent-profiles.test.js +2 -2
- package/build/persistent-profiles.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/runners/docker-compose-runner.js +1 -1
- package/build/runners/docker-compose-runner.js.map +1 -1
- package/build/runners/docker-runner.js +1 -1
- package/build/runners/docker-runner.js.map +1 -1
- package/build/runners/types.d.ts +1 -1
- package/build/runners/types.d.ts.map +1 -1
- package/build/server-manager.d.ts +1 -1
- package/build/server-manager.js +3 -3
- package/build/server-manager.js.map +1 -1
- package/build/server-manager.test.d.ts +1 -1
- package/build/server-manager.test.js +1 -1
- package/build/server-ownership.test.js +2 -2
- package/build/server-ownership.test.js.map +1 -1
- 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/supervisor/pidfile.test.js +1 -1
- package/build/supervisor/pidfile.test.js.map +1 -1
- 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/config-tools.d.ts +1 -1
- package/build/tools/config-tools.js +4 -4
- package/build/tools/config-tools.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/dashboard-tools.d.ts +1 -1
- package/build/tools/dashboard-tools.js +2 -2
- package/build/tools/dashboard-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-abort-hygiene.test.js +1 -1
- package/build/tools/issues-abort-hygiene.test.js.map +1 -1
- package/build/tools/issues-tools.d.ts +3 -3
- package/build/tools/issues-tools.test.js +1 -1
- package/build/tools/issues-tools.test.js.map +1 -1
- 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 +251 -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/replay-tools.js +3 -3
- package/build/tools/replay-tools.js.map +1 -1
- 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/README.md +2 -2
- package/docs/automation.md +1 -1
- package/docs/installation.md +4 -4
- package/docs/instructions.md +26 -15
- package/docs/message-system.md +1 -1
- package/docs/messages.md +153 -3
- package/docs/parser-plugins.md +1 -1
- package/docs/replay.md +1 -1
- package/docs/troubleshooting.md +3 -3
- package/package.json +2 -1
- package/plugin/skills/devharness/SKILL.md +36 -8
- package/plugin/skills/devharness/references/sequences.md +2 -2
- package/plugin/skills/devharness/references/tool-categories.md +10 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-targets.d.ts","sourceRoot":"","sources":["../src/worker-targets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,+DAA+D;AAC/D,QAAA,MAAM,YAAY,wDAAyD,CAAC;AAE5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,gBAAgB,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAC/B,GAAG,EAAE,MAAM;IAAS,SAAS,EAAE,YAAY,EAAE;gBAA7C,GAAG,EAAE,MAAM,EAAS,SAAS,EAAE,YAAY,EAAE;CAIjE;AAED,qBAAa,0BAA2B,SAAQ,KAAK;IAChC,GAAG,EAAE,MAAM;IAAS,OAAO,EAAE,YAAY,EAAE;gBAA3C,GAAG,EAAE,MAAM,EAAS,OAAO,EAAE,YAAY,EAAE;CAI/D;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IACR,OAAO,CAAC,EAAE,MAAM;gBAAxC,OAAO,EAAE,MAAM,EAAS,OAAO,CAAC,EAAE,MAAM,YAAA;CAIrD;AAUD;;;GAGG;AACH,qBAAa,oBAAoB;IAI7B,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,QAAQ,CAA0C;gBAGhD,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,WAAW,GAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAY,EACnF,OAAO,GAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAO;IAGjD,IAAI,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAYrC;;;;OAIG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAUjD;;;OAGG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAkBlF,oEAAoE;IAC9D,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAM9C,MAAM;IAsBpB,sEAAsE;IAChE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAW/B;AASD,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAOxF;AAED,yEAAyE;AACzE,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3F"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker targets - service workers, dedicated workers, shared workers.
|
|
3
|
+
*
|
|
4
|
+
* Every debugging tool runs against the page target, so code inside a worker
|
|
5
|
+
* has been unreachable: no evaluation, no console. A worker is its own CDP
|
|
6
|
+
* target with its own websocket, listed by the browser alongside the page, so
|
|
7
|
+
* a client per worker reaches it without touching the page's debugger state.
|
|
8
|
+
*/
|
|
9
|
+
import CDP from 'chrome-remote-interface';
|
|
10
|
+
/** The three target types that run script outside the page. */
|
|
11
|
+
const WORKER_TYPES = ['service_worker', 'worker', 'shared_worker'];
|
|
12
|
+
export class WorkerTargetNotFoundError extends Error {
|
|
13
|
+
ref;
|
|
14
|
+
available;
|
|
15
|
+
constructor(ref, available) {
|
|
16
|
+
super(`No worker target matches "${ref}"`);
|
|
17
|
+
this.ref = ref;
|
|
18
|
+
this.available = available;
|
|
19
|
+
this.name = 'WorkerTargetNotFoundError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class WorkerTargetAmbiguousError extends Error {
|
|
23
|
+
ref;
|
|
24
|
+
matches;
|
|
25
|
+
constructor(ref, matches) {
|
|
26
|
+
super(`"${ref}" matches ${matches.length} worker targets`);
|
|
27
|
+
this.ref = ref;
|
|
28
|
+
this.matches = matches;
|
|
29
|
+
this.name = 'WorkerTargetAmbiguousError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export class WorkerEvaluateError extends Error {
|
|
33
|
+
details;
|
|
34
|
+
constructor(message, details) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.details = details;
|
|
37
|
+
this.name = 'WorkerEvaluateError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Console output is capped per target; a chatty worker cannot grow unbounded. */
|
|
41
|
+
const MAX_MESSAGES = 200;
|
|
42
|
+
/**
|
|
43
|
+
* One browser's worker targets. Keyed by host and port, since that pair is
|
|
44
|
+
* what locates a target's websocket.
|
|
45
|
+
*/
|
|
46
|
+
export class WorkerTargetRegistry {
|
|
47
|
+
host;
|
|
48
|
+
port;
|
|
49
|
+
listTargets;
|
|
50
|
+
connect;
|
|
51
|
+
attached = new Map();
|
|
52
|
+
constructor(host, port, listTargets = CDP.List, connect = CDP) {
|
|
53
|
+
this.host = host;
|
|
54
|
+
this.port = port;
|
|
55
|
+
this.listTargets = listTargets;
|
|
56
|
+
this.connect = connect;
|
|
57
|
+
}
|
|
58
|
+
async list() {
|
|
59
|
+
const targets = await this.listTargets({ host: this.host, port: this.port });
|
|
60
|
+
return targets
|
|
61
|
+
.filter((t) => WORKER_TYPES.includes(t.type))
|
|
62
|
+
.map((t) => ({
|
|
63
|
+
targetId: t.id,
|
|
64
|
+
type: t.type,
|
|
65
|
+
url: t.url,
|
|
66
|
+
attached: this.attached.has(t.id),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A target id, or a substring of a target's URL. A substring matching more
|
|
71
|
+
* than one target is refused with both named - picking the first would
|
|
72
|
+
* evaluate in a worker the caller did not choose.
|
|
73
|
+
*/
|
|
74
|
+
async resolve(ref) {
|
|
75
|
+
const targets = await this.list();
|
|
76
|
+
const byId = targets.find((t) => t.targetId === ref);
|
|
77
|
+
if (byId)
|
|
78
|
+
return byId;
|
|
79
|
+
const matches = targets.filter((t) => t.url.includes(ref));
|
|
80
|
+
if (matches.length === 1)
|
|
81
|
+
return matches[0];
|
|
82
|
+
if (matches.length === 0)
|
|
83
|
+
throw new WorkerTargetNotFoundError(ref, targets);
|
|
84
|
+
throw new WorkerTargetAmbiguousError(ref, matches);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Evaluate inside the worker. Console output from the target is recorded
|
|
88
|
+
* from the moment of first attach, so a log emitted before that is not held.
|
|
89
|
+
*/
|
|
90
|
+
async evaluate(ref, expression, awaitPromise = true) {
|
|
91
|
+
const target = await this.resolve(ref);
|
|
92
|
+
const worker = await this.attach(target);
|
|
93
|
+
const result = await worker.client.Runtime.evaluate({
|
|
94
|
+
expression,
|
|
95
|
+
returnByValue: true,
|
|
96
|
+
awaitPromise,
|
|
97
|
+
});
|
|
98
|
+
if (result.exceptionDetails) {
|
|
99
|
+
const thrown = result.exceptionDetails.exception;
|
|
100
|
+
throw new WorkerEvaluateError(thrown?.description || thrown?.value || result.exceptionDetails.text, result.exceptionDetails.text);
|
|
101
|
+
}
|
|
102
|
+
return result.result?.value;
|
|
103
|
+
}
|
|
104
|
+
/** Console output recorded since this target was first attached. */
|
|
105
|
+
async messages(ref) {
|
|
106
|
+
const target = await this.resolve(ref);
|
|
107
|
+
const worker = await this.attach(target);
|
|
108
|
+
return [...worker.messages];
|
|
109
|
+
}
|
|
110
|
+
async attach(target) {
|
|
111
|
+
const existing = this.attached.get(target.targetId);
|
|
112
|
+
if (existing)
|
|
113
|
+
return existing;
|
|
114
|
+
const client = await this.connect({ host: this.host, port: this.port, target: target.targetId });
|
|
115
|
+
const worker = { client, messages: [] };
|
|
116
|
+
const record = (type, text) => {
|
|
117
|
+
worker.messages.push({ type, text, timestamp: Date.now() });
|
|
118
|
+
if (worker.messages.length > MAX_MESSAGES)
|
|
119
|
+
worker.messages.shift();
|
|
120
|
+
};
|
|
121
|
+
client.Runtime.consoleAPICalled((e) => {
|
|
122
|
+
record(e.type, (e.args || []).map(describeArg).join(' '));
|
|
123
|
+
});
|
|
124
|
+
client.Runtime.exceptionThrown((e) => {
|
|
125
|
+
const thrown = e.exceptionDetails?.exception;
|
|
126
|
+
record('error', thrown?.description || e.exceptionDetails?.text || 'exception');
|
|
127
|
+
});
|
|
128
|
+
await client.Runtime.enable();
|
|
129
|
+
this.attached.set(target.targetId, worker);
|
|
130
|
+
return worker;
|
|
131
|
+
}
|
|
132
|
+
/** Closes every client. A client left open holds its worker alive. */
|
|
133
|
+
async dispose() {
|
|
134
|
+
const workers = [...this.attached.values()];
|
|
135
|
+
this.attached.clear();
|
|
136
|
+
for (const worker of workers) {
|
|
137
|
+
try {
|
|
138
|
+
await worker.client.close();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Its target is already gone; the socket went with it.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function describeArg(arg) {
|
|
147
|
+
if (arg.value !== undefined)
|
|
148
|
+
return typeof arg.value === 'string' ? arg.value : JSON.stringify(arg.value);
|
|
149
|
+
return arg.description || arg.className || arg.type || '';
|
|
150
|
+
}
|
|
151
|
+
const registries = new Map();
|
|
152
|
+
/** One registry per browser, so a second call reuses the open clients. */
|
|
153
|
+
export function getWorkerTargetRegistry(host, port) {
|
|
154
|
+
const key = `${host}:${port}`;
|
|
155
|
+
const existing = registries.get(key);
|
|
156
|
+
if (existing)
|
|
157
|
+
return existing;
|
|
158
|
+
const registry = new WorkerTargetRegistry(host, port);
|
|
159
|
+
registries.set(key, registry);
|
|
160
|
+
return registry;
|
|
161
|
+
}
|
|
162
|
+
/** Called when a connection closes: its browser's clients go with it. */
|
|
163
|
+
export async function disposeWorkerTargetRegistry(host, port) {
|
|
164
|
+
const key = `${host}:${port}`;
|
|
165
|
+
const registry = registries.get(key);
|
|
166
|
+
if (!registry)
|
|
167
|
+
return;
|
|
168
|
+
registries.delete(key);
|
|
169
|
+
await registry.dispose();
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=worker-targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-targets.js","sourceRoot":"","sources":["../src/worker-targets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A worker's script runs in its own target, so a reference has to name exactly
|
|
3
|
+
* one. A substring matching two targets resolved to the first would evaluate
|
|
4
|
+
* inside a worker the caller did not choose, and a client left open holds its
|
|
5
|
+
* worker alive past the browser that owned it - these pin both.
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=worker-targets.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-targets.test.d.ts","sourceRoot":"","sources":["../src/worker-targets.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A worker's script runs in its own target, so a reference has to name exactly
|
|
3
|
+
* one. A substring matching two targets resolved to the first would evaluate
|
|
4
|
+
* inside a worker the caller did not choose, and a client left open holds its
|
|
5
|
+
* worker alive past the browser that owned it - these pin both.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { WorkerTargetRegistry, WorkerTargetNotFoundError, WorkerTargetAmbiguousError, WorkerEvaluateError, } from './worker-targets.js';
|
|
9
|
+
const TARGETS = [
|
|
10
|
+
{ id: 'page-1', type: 'page', url: 'http://localhost:5173/' },
|
|
11
|
+
{ id: 'sw-1', type: 'service_worker', url: 'http://localhost:5173/sw.js' },
|
|
12
|
+
{ id: 'w-1', type: 'worker', url: 'http://localhost:5173/worker.js' },
|
|
13
|
+
{ id: 'w-2', type: 'worker', url: 'http://localhost:5173/second-worker.js' },
|
|
14
|
+
];
|
|
15
|
+
function fakeClient(evaluate) {
|
|
16
|
+
const client = {
|
|
17
|
+
closed: false,
|
|
18
|
+
handlers: {},
|
|
19
|
+
Runtime: {
|
|
20
|
+
enable: async () => { },
|
|
21
|
+
evaluate: async (params) => (evaluate ? evaluate(params) : { result: { value: 'ok' } }),
|
|
22
|
+
consoleAPICalled: (h) => { client.handlers.console = h; },
|
|
23
|
+
exceptionThrown: (h) => { client.handlers.exception = h; },
|
|
24
|
+
},
|
|
25
|
+
close: async () => { client.closed = true; },
|
|
26
|
+
};
|
|
27
|
+
return client;
|
|
28
|
+
}
|
|
29
|
+
function registry(options = {}) {
|
|
30
|
+
const clients = [];
|
|
31
|
+
const connect = async () => {
|
|
32
|
+
const client = fakeClient(options.evaluate);
|
|
33
|
+
clients.push(client);
|
|
34
|
+
return client;
|
|
35
|
+
};
|
|
36
|
+
const instance = new WorkerTargetRegistry('localhost', 9222, async () => options.targets ?? TARGETS, connect);
|
|
37
|
+
return { instance, clients };
|
|
38
|
+
}
|
|
39
|
+
describe('WorkerTargetRegistry.list', () => {
|
|
40
|
+
it('lists only targets that run script outside the page', async () => {
|
|
41
|
+
const { instance } = registry();
|
|
42
|
+
const types = (await instance.list()).map((t) => t.type);
|
|
43
|
+
expect(types).toEqual(['service_worker', 'worker', 'worker']);
|
|
44
|
+
});
|
|
45
|
+
it('marks a target attached once a client is open on it', async () => {
|
|
46
|
+
const { instance } = registry();
|
|
47
|
+
await instance.evaluate('sw-1', '1');
|
|
48
|
+
const sw = (await instance.list()).find((t) => t.targetId === 'sw-1');
|
|
49
|
+
expect(sw?.attached).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('WorkerTargetRegistry.resolve', () => {
|
|
53
|
+
it('takes a target id', async () => {
|
|
54
|
+
const { instance } = registry();
|
|
55
|
+
expect((await instance.resolve('sw-1')).url).toBe('http://localhost:5173/sw.js');
|
|
56
|
+
});
|
|
57
|
+
it('takes a URL substring that matches one target', async () => {
|
|
58
|
+
const { instance } = registry();
|
|
59
|
+
expect((await instance.resolve('sw.js')).targetId).toBe('sw-1');
|
|
60
|
+
});
|
|
61
|
+
it('refuses a substring matching more than one, naming both', async () => {
|
|
62
|
+
const { instance } = registry();
|
|
63
|
+
await expect(instance.resolve('worker.js')).rejects.toBeInstanceOf(WorkerTargetAmbiguousError);
|
|
64
|
+
const error = await instance.resolve('worker.js').catch((e) => e);
|
|
65
|
+
expect(error.matches.map((t) => t.targetId)).toEqual(['w-1', 'w-2']);
|
|
66
|
+
});
|
|
67
|
+
it('refuses a reference matching nothing, listing what there is', async () => {
|
|
68
|
+
const { instance } = registry();
|
|
69
|
+
const error = await instance.resolve('absent.js').catch((e) => e);
|
|
70
|
+
expect(error).toBeInstanceOf(WorkerTargetNotFoundError);
|
|
71
|
+
expect(error.available.map((t) => t.targetId)).toEqual(['sw-1', 'w-1', 'w-2']);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('WorkerTargetRegistry.evaluate', () => {
|
|
75
|
+
it('returns the by-value result', async () => {
|
|
76
|
+
const { instance } = registry({ evaluate: () => ({ result: { value: 'ServiceWorkerGlobalScope' } }) });
|
|
77
|
+
expect(await instance.evaluate('sw-1', 'self.constructor.name')).toBe('ServiceWorkerGlobalScope');
|
|
78
|
+
});
|
|
79
|
+
it('raises what the worker threw', async () => {
|
|
80
|
+
const { instance } = registry({
|
|
81
|
+
evaluate: () => ({ exceptionDetails: { text: 'Uncaught', exception: { description: 'TypeError: nope' } } }),
|
|
82
|
+
});
|
|
83
|
+
await expect(instance.evaluate('sw-1', 'nope()')).rejects.toBeInstanceOf(WorkerEvaluateError);
|
|
84
|
+
});
|
|
85
|
+
it('opens one client per target, however many evaluations', async () => {
|
|
86
|
+
const { instance, clients } = registry();
|
|
87
|
+
await instance.evaluate('sw-1', '1');
|
|
88
|
+
await instance.evaluate('sw-1', '2');
|
|
89
|
+
expect(clients).toHaveLength(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('WorkerTargetRegistry console', () => {
|
|
93
|
+
it('records console output from the target', async () => {
|
|
94
|
+
const { instance, clients } = registry();
|
|
95
|
+
await instance.evaluate('sw-1', '1');
|
|
96
|
+
clients[0].handlers.console({ type: 'log', args: [{ value: 'inside' }, { value: 42 }] });
|
|
97
|
+
const messages = await instance.messages('sw-1');
|
|
98
|
+
expect(messages.map((m) => m.text)).toEqual(['inside 42']);
|
|
99
|
+
});
|
|
100
|
+
it('records an exception thrown in the target as an error', async () => {
|
|
101
|
+
const { instance, clients } = registry();
|
|
102
|
+
await instance.evaluate('sw-1', '1');
|
|
103
|
+
clients[0].handlers.exception({ exceptionDetails: { exception: { description: 'TypeError: nope' } } });
|
|
104
|
+
const messages = await instance.messages('sw-1');
|
|
105
|
+
expect(messages[0]).toMatchObject({ type: 'error', text: 'TypeError: nope' });
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('WorkerTargetRegistry.dispose', () => {
|
|
109
|
+
it('closes every client it opened', async () => {
|
|
110
|
+
const { instance, clients } = registry();
|
|
111
|
+
await instance.evaluate('sw-1', '1');
|
|
112
|
+
await instance.evaluate('w-1', '1');
|
|
113
|
+
await instance.dispose();
|
|
114
|
+
expect(clients.map((c) => c.closed)).toEqual([true, true]);
|
|
115
|
+
expect((await instance.list()).every((t) => !t.attached)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=worker-targets.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-targets.test.js","sourceRoot":"","sources":["../src/worker-targets.test.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -33,7 +33,7 @@ You don't have to set this up by hand: on startup, the server checks whether the
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
# Claude Code CLI
|
|
36
|
-
claude mcp add
|
|
36
|
+
claude mcp add devharness -- npx -y devharness
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
```javascript
|
|
@@ -61,7 +61,7 @@ devharness supports multiple simultaneous connections:
|
|
|
61
61
|
|
|
62
62
|
### Smart Element Caching
|
|
63
63
|
|
|
64
|
-
When you navigate to a page,
|
|
64
|
+
When you navigate to a page, devharness automatically:
|
|
65
65
|
|
|
66
66
|
1. **Caches all clickable elements** - Links, buttons, and inputs are discovered and stored
|
|
67
67
|
2. **Records viewport positions** - Tracks which elements are visible
|
package/docs/automation.md
CHANGED
|
@@ -500,7 +500,7 @@ server({ action: 'stop', serverId: 'my-app', global: true })
|
|
|
500
500
|
|
|
501
501
|
## Configuration Management
|
|
502
502
|
|
|
503
|
-
Manage
|
|
503
|
+
Manage devharness configuration across projects:
|
|
504
504
|
|
|
505
505
|
```javascript
|
|
506
506
|
// Check where config is loaded from
|
package/docs/installation.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Claude Code CLI
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
claude mcp add
|
|
6
|
+
claude mcp add devharness -- npx -y devharness
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
## Claude Desktop
|
|
@@ -17,7 +17,7 @@ Add to your config file:
|
|
|
17
17
|
```json
|
|
18
18
|
{
|
|
19
19
|
"mcpServers": {
|
|
20
|
-
"
|
|
20
|
+
"devharness": {
|
|
21
21
|
"command": "npx",
|
|
22
22
|
"args": ["-y", "devharness"]
|
|
23
23
|
}
|
|
@@ -71,7 +71,7 @@ The `defaultDebugPort` sets the starting port for Chrome debugging. If the port
|
|
|
71
71
|
|
|
72
72
|
### Idle Sessions
|
|
73
73
|
|
|
74
|
-
An editor window left open for days keeps its
|
|
74
|
+
An editor window left open for days keeps its devharness server alive, holding
|
|
75
75
|
Chrome instances, dev servers and monitor buffers nobody is using. Two things
|
|
76
76
|
stop that:
|
|
77
77
|
|
|
@@ -90,7 +90,7 @@ stop that:
|
|
|
90
90
|
- **Abandoned dev servers are collected.** A window closed for good used to
|
|
91
91
|
leave its dev servers running until reboot. The next session in that
|
|
92
92
|
directory now stops any whose owning sessions are all gone.
|
|
93
|
-
- **Orphan reaping.** When the client that launched
|
|
93
|
+
- **Orphan reaping.** When the client that launched devharness is gone, the
|
|
94
94
|
server tree shuts itself down, rather than waiting for a stdin EOF that never
|
|
95
95
|
arrives when an `npm exec` wrapper is holding the pipe open.
|
|
96
96
|
|
package/docs/instructions.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# devharness Debugger Usage
|
|
2
2
|
|
|
3
3
|
Chrome DevTools Protocol debugging for JavaScript/TypeScript in Chrome, Node.js, or CDP-compatible environments.
|
|
4
4
|
|
|
@@ -59,6 +59,9 @@ Chrome DevTools Protocol debugging for JavaScript/TypeScript in Chrome, Node.js,
|
|
|
59
59
|
- Note: DOM breakpoints use nodeIds which are invalidated on page reload
|
|
60
60
|
|
|
61
61
|
**Code search:**
|
|
62
|
+
- `inspect({ action: 'listTargets' })`: List worker targets (service, dedicated, shared)
|
|
63
|
+
- `inspect({ action: 'evaluateExpression', target })`: Evaluate inside a worker, addressed by target id or a URL substring
|
|
64
|
+
- `console({ action: 'list', target })`: Read a worker's console, which reaches no page listener
|
|
62
65
|
- `inspect({ action: 'searchCode' })`: Find patterns
|
|
63
66
|
- `inspect({ action: 'searchFunctions' })`: Locate definitions
|
|
64
67
|
- `getSourceCode`: View context
|
|
@@ -120,28 +123,31 @@ Chrome DevTools Protocol debugging for JavaScript/TypeScript in Chrome, Node.js,
|
|
|
120
123
|
- **Missing/invalid parameters**: the error includes a `continuationToken` and `missingParameters` (name/type/description/enum). Retry with just `{ continuationToken, <missing/bad field(s)> }` - don't resend everything. Expires after 5 min.
|
|
121
124
|
- **A validated call gets blocked by a guard** (port failure, dead server, breakpoint pause): the response footer shows `**Repeat:** replay({ action: 'repeat', indices: [N] })`. Acknowledge the guard (e.g. `server({ action: 'acknowledgePort' })`), then use that hint to resume the exact call. Don't reuse a `continuationToken` here - that's for fixing bad input, not for retrying an already-valid call.
|
|
122
125
|
|
|
123
|
-
##
|
|
126
|
+
## The event stream
|
|
124
127
|
|
|
125
|
-
|
|
128
|
+
Everything devharness pushes - a guard block, a message from another session - appends one JSON line to `~/.devharness/events/<sessionId>.jsonl`. One file per session, so one watch covers every kind, including kinds added later.
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
{"ts":"2026-08-08T03:21:45.588Z","guard":"pendingStartup","tool":"navigate","detail":"died before port detected: \"web\"","resolve":"server({ action: 'acknowledgeStartup', serverId: 'web' })"}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`guard` is one of `port`, `breakpoint`, `pendingStartup`, `bug`, `duplicateSession`. Lines are deduplicated: one per *new* block, not one per blocked call. The same block re-firing on later calls stays quiet until a call clears every guard and the block recurs.
|
|
132
|
-
|
|
133
|
-
In Claude Code, tail it with a persistent Monitor so blocks arrive as notifications while you work on something else:
|
|
130
|
+
Installed as a plugin, a `SessionStart` hook (`plugin/hooks/session-start.mjs`) creates that file and prints the `Monitor` call as session context before the first turn. In Claude Code, arm it:
|
|
134
131
|
|
|
135
132
|
```
|
|
136
133
|
Monitor({
|
|
137
|
-
command: "mkdir -p
|
|
138
|
-
description: "devharness
|
|
134
|
+
command: "mkdir -p ~/.devharness/events && touch <streamPath> && tail -f -n0 <streamPath>",
|
|
135
|
+
description: "devharness events",
|
|
139
136
|
persistent: true,
|
|
140
137
|
timeout_ms: 3600000
|
|
141
138
|
})
|
|
142
139
|
```
|
|
143
140
|
|
|
144
|
-
|
|
141
|
+
Nothing is lost without a watch - blocks and messages still surface on the next tool call. The watch is what makes them arrive while the session is doing something else.
|
|
142
|
+
|
|
143
|
+
Line kinds:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{"ts":"...","kind":"block","guard":"pendingStartup","tool":"navigate","detail":"died before port detected: \"web\"","resolve":"server({ action: 'acknowledgeStartup', serverId: 'web' })"}
|
|
147
|
+
{"ts":"...","kind":"message","from":"66ba2d65","id":"d94924a8-...","detail":"Message from 66ba2d65: ...","resolve":"message({ action: 'read' })"}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`guard` is one of `port`, `breakpoint`, `pendingStartup`, `bug`, `duplicateSession`. Blocks are deduplicated: one line per *new* block, not one per blocked call. Any client can tail the file.
|
|
145
151
|
|
|
146
152
|
## Restarting devharness
|
|
147
153
|
|
|
@@ -218,9 +224,14 @@ runs against (see Quick Start).
|
|
|
218
224
|
- **GitHub** (via the `gh` CLI; only `publish` and `sync` use the network): `publish` shows a draft and posts nothing until `confirm: true`; `sync` reconciles both ways and reports a conflict rather than overwriting when both sides changed; `import` materialises a GitHub-only issue locally; `link` stamps an existing number with no network call; `pullSequence` writes a sequence out of an issue body to disk (one authored by another GitHub account needs a person to read it and pass `confirm: true`)
|
|
219
225
|
- A sequence pulled from an issue is **never run automatically**, and one using `execution`, `saveToDisk`, `server`, `request` or `download` is refused unless you pass `allowPrivilegedSteps: true`. Read it first
|
|
220
226
|
|
|
227
|
+
**Messages**: `message` (actions: sessions, send, read, reply)
|
|
228
|
+
- Text between two devharness sessions on this machine - a session hitting a devharness bug talking to the session working on devharness itself. `sessions` lists reachable mailboxes and this session's own mailbox path
|
|
229
|
+
- `send({ to, text, waitForReplyMs })` holds the call open until something lands in this session's mailbox (max 300000ms) and returns it; without `waitForReplyMs` it returns as soon as the line is written. `reply({ replyTo, text })` routes back to the sender of that message. The wait returns on ANY arrival, not only a tagged reply, so two sessions blocking at the same moment both release
|
|
230
|
+
- The mailbox is one append-only JSONL file per session under `~/.devharness/messages/`, holding the conversation and the read cursor; the global directory is what makes it reach across project roots. Nothing watches it. An arrival announces itself on that session's event stream, `~/.devharness/events/<id>.jsonl`, so one watch covers messages and guard blocks alike; without one, messages surface on the next `message({ action: 'read' })`
|
|
231
|
+
|
|
221
232
|
**Server**: `server` (actions: start, stop, restart, list, logs, stopAll, setAutoRun, clearLogs, remove, monitorPort, unmonitorPort, listMonitored, acknowledgePort, acknowledgeStartup, extendStartup, cancelPendingRestart)
|
|
222
233
|
- Use `global: true` to access servers started from a different working directory
|
|
223
|
-
- `start({ watch: true, watchPaths?: [...] })`:
|
|
234
|
+
- `start({ watch: true, watchPaths?: [...] })`: devharness watches the given paths (default: cwd) and auto-restarts the server on file changes, instead of relying on `--watch`/nodemon. Pause-aware: if a breakpoint debugger is paused on that server's inspector port, the restart queues instead of firing immediately - `cancelPendingRestart` discards a queued restart to keep debugging without it firing on resume
|
|
224
235
|
|
|
225
236
|
**Replay**: `replay` (actions: history, create, list, get, delete, export, load, listSaved, deleteSaved, run, runAll, step, finish, insert, addConditional, status, cancel, repeat, runFromLog, recordInteraction)
|
|
226
237
|
- `recordInteraction`: record mouse, keyboard, and navigation events with a visual overlay
|
|
@@ -247,6 +258,6 @@ runs against (see Quick Start).
|
|
|
247
258
|
- `show`: Display current configuration
|
|
248
259
|
- `listTools`: List all toggleable tools with status and dependency conflicts
|
|
249
260
|
- `reload`: Re-read config.json now (also happens automatically on file edits, ~250ms debounce). Doesn't apply `tools.enabled`/`tools.disabled` - those need `restart`
|
|
250
|
-
- `restart`: Restart
|
|
261
|
+
- `restart`: Restart devharness itself via the mcp-supervisor (see "Restarting devharness" above) - use when the server seems stuck/broken, or to apply `tools.enabled`/`tools.disabled` changes
|
|
251
262
|
- `listProfiles`: List named persistent Chrome profiles and the root they live under
|
|
252
263
|
- `resetProfile`: Wipe and recreate a named profile (`config({ action: 'resetProfile', profile: 'device-a' })`). Refused while a live Chrome holds that profile - nothing is deleted in that case
|
package/docs/message-system.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The devharness debugger uses a centralized message system to provide consistent, markdown-formatted responses to all tool calls. This guide explains how to use the system and when to use different patterns.
|
|
6
6
|
|
|
7
7
|
## Architecture
|
|
8
8
|
|