@rallycry/conveyor-agent 8.12.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FSUVTOFP.js → chunk-JH7GUOGW.js} +6673 -8717
- package/dist/chunk-JH7GUOGW.js.map +1 -0
- package/dist/cli.js +148 -208
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -500
- package/dist/index.js +51 -1473
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-FDWECEDJ.js +0 -33
- package/dist/chunk-FDWECEDJ.js.map +0 -1
- package/dist/chunk-FSUVTOFP.js.map +0 -1
- package/dist/chunk-TN2YYT2V.js +0 -1444
- package/dist/chunk-TN2YYT2V.js.map +0 -1
- package/dist/tag-audit-handler-X3LEX63H.js +0 -222
- package/dist/tag-audit-handler-X3LEX63H.js.map +0 -1
- package/dist/task-audit-handler-BRCXB5V6.js +0 -801
- package/dist/task-audit-handler-BRCXB5V6.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1519 +1,97 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentConnection,
|
|
3
|
-
CommitWatcher,
|
|
4
|
-
DEFAULT_LIFECYCLE_CONFIG,
|
|
5
|
-
Lifecycle,
|
|
6
|
-
ModeController,
|
|
7
3
|
PlanSync,
|
|
8
|
-
ProjectConnection,
|
|
9
|
-
ProjectRunner,
|
|
10
4
|
SessionRunner,
|
|
11
|
-
detachWorktreeBranch,
|
|
12
|
-
ensureWorktree,
|
|
13
5
|
flushPendingChanges,
|
|
14
6
|
getCurrentBranch,
|
|
15
7
|
hasUncommittedChanges,
|
|
16
8
|
hasUnpushedCommits,
|
|
17
|
-
injectTelemetry,
|
|
18
9
|
loadConveyorConfig,
|
|
19
10
|
loadForwardPorts,
|
|
20
11
|
pushToOrigin,
|
|
21
|
-
removeWorktree,
|
|
22
12
|
runAuthTokenCommand,
|
|
23
13
|
runSetupCommand,
|
|
24
14
|
runStartCommand,
|
|
25
15
|
stageAndCommit,
|
|
26
16
|
unshallowRepo,
|
|
27
17
|
updateRemoteToken
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-FDWECEDJ.js";
|
|
30
|
-
import "./chunk-TN2YYT2V.js";
|
|
18
|
+
} from "./chunk-JH7GUOGW.js";
|
|
31
19
|
|
|
32
|
-
// src/runner/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (!entry) return null;
|
|
47
|
-
if (Date.now() - entry.createdAt > this.ttlMs) {
|
|
48
|
-
this.delete(fileId);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
this.cache.delete(fileId);
|
|
52
|
-
this.cache.set(fileId, entry);
|
|
53
|
-
return entry;
|
|
54
|
-
}
|
|
55
|
-
set(fileId, content, mimeType, fileName) {
|
|
56
|
-
if (this.cache.has(fileId)) {
|
|
57
|
-
this.delete(fileId);
|
|
58
|
-
}
|
|
59
|
-
const size = content.byteLength;
|
|
60
|
-
if (size > this.maxSizeBytes) return;
|
|
61
|
-
while (this.currentSize + size > this.maxSizeBytes && this.cache.size > 0) {
|
|
62
|
-
const oldestKey = this.cache.keys().next().value;
|
|
63
|
-
if (oldestKey !== void 0) {
|
|
64
|
-
this.delete(oldestKey);
|
|
20
|
+
// src/runner/worktree.ts
|
|
21
|
+
import { execSync } from "child_process";
|
|
22
|
+
import { existsSync } from "fs";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
var WORKTREE_DIR = ".worktrees";
|
|
25
|
+
function ensureWorktree(projectDir, taskId, branch) {
|
|
26
|
+
if (projectDir.includes(`/${WORKTREE_DIR}/`)) {
|
|
27
|
+
return projectDir;
|
|
28
|
+
}
|
|
29
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
30
|
+
if (existsSync(worktreePath)) {
|
|
31
|
+
if (branch) {
|
|
32
|
+
if (hasUncommittedChanges(worktreePath)) {
|
|
33
|
+
return worktreePath;
|
|
65
34
|
}
|
|
66
|
-
}
|
|
67
|
-
this.cache.set(fileId, {
|
|
68
|
-
content,
|
|
69
|
-
mimeType,
|
|
70
|
-
fileName,
|
|
71
|
-
createdAt: Date.now(),
|
|
72
|
-
size
|
|
73
|
-
});
|
|
74
|
-
this.currentSize += size;
|
|
75
|
-
}
|
|
76
|
-
delete(fileId) {
|
|
77
|
-
const entry = this.cache.get(fileId);
|
|
78
|
-
if (entry) {
|
|
79
|
-
this.currentSize -= entry.size;
|
|
80
|
-
this.cache.delete(fileId);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
clear() {
|
|
84
|
-
this.cache.clear();
|
|
85
|
-
this.currentSize = 0;
|
|
86
|
-
}
|
|
87
|
-
get stats() {
|
|
88
|
-
return {
|
|
89
|
-
entries: this.cache.size,
|
|
90
|
-
sizeBytes: this.currentSize,
|
|
91
|
-
maxSizeBytes: this.maxSizeBytes
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// src/debug/cdp-client.ts
|
|
97
|
-
var MAX_VARIABLE_VALUE_BYTES = 2048;
|
|
98
|
-
var MAX_VARIABLES_PER_SCOPE = 50;
|
|
99
|
-
var AUTO_RESUME_TIMEOUT_MS = 3e4;
|
|
100
|
-
var PROBE_BUFFER_MAX_SIZE = 1e3;
|
|
101
|
-
var CdpDebugClient = class {
|
|
102
|
-
ws = null;
|
|
103
|
-
requestId = 0;
|
|
104
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
105
|
-
breakpoints = /* @__PURE__ */ new Map();
|
|
106
|
-
probes = /* @__PURE__ */ new Map();
|
|
107
|
-
probeCounter = 0;
|
|
108
|
-
probeBufferInjected = false;
|
|
109
|
-
pausedState = null;
|
|
110
|
-
autoResumeTimer = null;
|
|
111
|
-
protocol = "cdp";
|
|
112
|
-
onPausedCallback = null;
|
|
113
|
-
onResumedCallback = null;
|
|
114
|
-
onAutoResumedCallback = null;
|
|
115
|
-
// ── Connection ─────────────────────────────────────────────────────────
|
|
116
|
-
// oxlint-disable-next-line require-await
|
|
117
|
-
async connect(wsUrl, protocol) {
|
|
118
|
-
if (this.ws) {
|
|
119
|
-
throw new Error("Already connected to CDP");
|
|
120
|
-
}
|
|
121
|
-
this.protocol = protocol ?? "cdp";
|
|
122
|
-
return new Promise((resolve, reject) => {
|
|
123
|
-
const ws = new WebSocket(wsUrl);
|
|
124
|
-
ws.addEventListener("open", async () => {
|
|
125
|
-
this.ws = ws;
|
|
126
|
-
try {
|
|
127
|
-
await this.send("Debugger.enable", {});
|
|
128
|
-
await this.send("Runtime.enable", {});
|
|
129
|
-
resolve();
|
|
130
|
-
} catch (err) {
|
|
131
|
-
reject(err);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
ws.addEventListener("message", (event) => {
|
|
135
|
-
const data = typeof event.data === "string" ? event.data : String(event.data);
|
|
136
|
-
this.handleMessage(data);
|
|
137
|
-
});
|
|
138
|
-
ws.addEventListener("error", () => {
|
|
139
|
-
reject(new Error(`CDP WebSocket connection failed: ${wsUrl}`));
|
|
140
|
-
});
|
|
141
|
-
ws.addEventListener("close", () => {
|
|
142
|
-
this.cleanup();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
disconnect() {
|
|
147
|
-
if (this.ws) {
|
|
148
|
-
this.clearAutoResumeTimer();
|
|
149
|
-
this.ws.close();
|
|
150
|
-
this.cleanup();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
isConnected() {
|
|
154
|
-
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
155
|
-
}
|
|
156
|
-
getProtocol() {
|
|
157
|
-
return this.protocol;
|
|
158
|
-
}
|
|
159
|
-
// ── Breakpoints ────────────────────────────────────────────────────────
|
|
160
|
-
async setBreakpoint(file, line, condition) {
|
|
161
|
-
const params = {
|
|
162
|
-
urlRegex: this.fileToUrlRegex(file),
|
|
163
|
-
// CDP uses 0-based lines
|
|
164
|
-
lineNumber: line - 1
|
|
165
|
-
};
|
|
166
|
-
if (condition) {
|
|
167
|
-
params.condition = condition;
|
|
168
|
-
}
|
|
169
|
-
const result = await this.send("Debugger.setBreakpointByUrl", params);
|
|
170
|
-
const breakpointId = result.breakpointId;
|
|
171
|
-
this.breakpoints.set(breakpointId, { breakpointId, file, line, condition });
|
|
172
|
-
return breakpointId;
|
|
173
|
-
}
|
|
174
|
-
async removeBreakpoint(breakpointId) {
|
|
175
|
-
await this.send("Debugger.removeBreakpoint", { breakpointId });
|
|
176
|
-
this.breakpoints.delete(breakpointId);
|
|
177
|
-
}
|
|
178
|
-
async removeAllBreakpoints() {
|
|
179
|
-
const ids = [...this.breakpoints.keys()];
|
|
180
|
-
for (const id of ids) {
|
|
181
35
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
listBreakpoints() {
|
|
189
|
-
return [...this.breakpoints.values()];
|
|
190
|
-
}
|
|
191
|
-
// ── Probes (Logpoints) ────────────────────────────────────────────────
|
|
192
|
-
async setLogpoint(file, line, expressions, label) {
|
|
193
|
-
await this.ensureProbeBuffer();
|
|
194
|
-
const probeId = `probe-${++this.probeCounter}`;
|
|
195
|
-
const probeLabel = label ?? `${file}:${line}`;
|
|
196
|
-
const dataEntries = expressions.map((expr) => `${JSON.stringify(expr)}: (${expr})`).join(", ");
|
|
197
|
-
const condition = `(globalThis.__conveyorProbes?.probe(${JSON.stringify(probeLabel)}, {${dataEntries}}), false)`;
|
|
198
|
-
const breakpointId = await this.setBreakpoint(file, line, condition);
|
|
199
|
-
const probe = { probeId, breakpointId, file, line, expressions, label: probeLabel };
|
|
200
|
-
this.probes.set(probeId, probe);
|
|
201
|
-
return probeId;
|
|
202
|
-
}
|
|
203
|
-
async removeProbe(probeId) {
|
|
204
|
-
const probe = this.probes.get(probeId);
|
|
205
|
-
if (!probe) {
|
|
206
|
-
throw new Error(`Probe ${probeId} not found`);
|
|
207
|
-
}
|
|
208
|
-
await this.removeBreakpoint(probe.breakpointId);
|
|
209
|
-
this.probes.delete(probeId);
|
|
210
|
-
}
|
|
211
|
-
listProbes() {
|
|
212
|
-
return [...this.probes.values()];
|
|
213
|
-
}
|
|
214
|
-
async getProbeResults(label, limit) {
|
|
215
|
-
const args = [
|
|
216
|
-
label === void 0 ? "undefined" : JSON.stringify(label),
|
|
217
|
-
limit === void 0 ? "undefined" : String(limit)
|
|
218
|
-
].join(", ");
|
|
219
|
-
const result = await this.evaluate(
|
|
220
|
-
`JSON.stringify(globalThis.__conveyorProbes?.read(${args}) ?? [])`
|
|
221
|
-
);
|
|
222
|
-
if (result.type === "error") {
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
const raw = result.value;
|
|
227
|
-
const parsed = JSON.parse(raw.startsWith('"') ? JSON.parse(raw) : raw);
|
|
228
|
-
return parsed;
|
|
229
|
-
} catch {
|
|
230
|
-
return [];
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
async clearProbeResults(label) {
|
|
234
|
-
const arg = label === void 0 ? "undefined" : JSON.stringify(label);
|
|
235
|
-
await this.evaluate(`(globalThis.__conveyorProbes?.clear(${arg}), undefined)`);
|
|
236
|
-
}
|
|
237
|
-
// ── Paused State ───────────────────────────────────────────────────────
|
|
238
|
-
isPaused() {
|
|
239
|
-
return this.pausedState !== null;
|
|
240
|
-
}
|
|
241
|
-
getPausedState() {
|
|
242
|
-
return this.pausedState;
|
|
243
|
-
}
|
|
244
|
-
getCallStack() {
|
|
245
|
-
return this.pausedState?.callFrames ?? [];
|
|
246
|
-
}
|
|
247
|
-
async getScopeVariables(callFrameId) {
|
|
248
|
-
if (!this.pausedState) {
|
|
249
|
-
throw new Error("Debugger is not paused");
|
|
250
|
-
}
|
|
251
|
-
const frame = this.pausedState.callFrames.find((f) => f.callFrameId === callFrameId);
|
|
252
|
-
if (!frame) {
|
|
253
|
-
throw new Error(`Call frame ${callFrameId} not found`);
|
|
254
|
-
}
|
|
255
|
-
const result = await this.send("Debugger.evaluateOnCallFrame", {
|
|
256
|
-
callFrameId,
|
|
257
|
-
expression: "this",
|
|
258
|
-
returnByValue: false
|
|
259
|
-
});
|
|
260
|
-
const variables = [];
|
|
261
|
-
try {
|
|
262
|
-
const scopeResult = await this.send("Runtime.getProperties", {
|
|
263
|
-
objectId: result.result?.objectId,
|
|
264
|
-
ownProperties: true,
|
|
265
|
-
generatePreview: false
|
|
266
|
-
});
|
|
267
|
-
const descriptors = scopeResult.result ?? [];
|
|
268
|
-
for (const desc of descriptors.slice(0, MAX_VARIABLES_PER_SCOPE)) {
|
|
269
|
-
if (desc.value) {
|
|
270
|
-
variables.push({
|
|
271
|
-
name: desc.name,
|
|
272
|
-
type: desc.value.type,
|
|
273
|
-
value: this.truncateValue(this.formatRemoteObject(desc.value))
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} catch {
|
|
278
|
-
}
|
|
279
|
-
return variables;
|
|
280
|
-
}
|
|
281
|
-
// ── Execution Control ──────────────────────────────────────────────────
|
|
282
|
-
async resume() {
|
|
283
|
-
this.clearAutoResumeTimer();
|
|
284
|
-
await this.send("Debugger.resume", {});
|
|
285
|
-
this.pausedState = null;
|
|
286
|
-
}
|
|
287
|
-
async stepOver() {
|
|
288
|
-
this.clearAutoResumeTimer();
|
|
289
|
-
await this.send("Debugger.stepOver", {});
|
|
290
|
-
}
|
|
291
|
-
async stepInto() {
|
|
292
|
-
this.clearAutoResumeTimer();
|
|
293
|
-
await this.send("Debugger.stepInto", {});
|
|
294
|
-
}
|
|
295
|
-
// ── Evaluation ─────────────────────────────────────────────────────────
|
|
296
|
-
async evaluate(expression, callFrameId) {
|
|
297
|
-
let result;
|
|
298
|
-
if (callFrameId && this.pausedState) {
|
|
299
|
-
result = await this.send("Debugger.evaluateOnCallFrame", {
|
|
300
|
-
callFrameId,
|
|
301
|
-
expression,
|
|
302
|
-
returnByValue: false,
|
|
303
|
-
generatePreview: true
|
|
304
|
-
});
|
|
305
|
-
} else {
|
|
306
|
-
result = await this.send("Runtime.evaluate", {
|
|
307
|
-
expression,
|
|
308
|
-
returnByValue: false,
|
|
309
|
-
generatePreview: true
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
const remoteObj = result.result;
|
|
313
|
-
const errorValue = this.extractEvalError(result, remoteObj);
|
|
314
|
-
if (errorValue !== null) {
|
|
315
|
-
return { type: "error", value: errorValue };
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
type: remoteObj?.type ?? "undefined",
|
|
319
|
-
value: this.truncateValue(this.formatRemoteObject(remoteObj))
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
// ── Event Callbacks ────────────────────────────────────────────────────
|
|
323
|
-
onPaused(callback) {
|
|
324
|
-
this.onPausedCallback = callback;
|
|
325
|
-
}
|
|
326
|
-
onResumed(callback) {
|
|
327
|
-
this.onResumedCallback = callback;
|
|
328
|
-
}
|
|
329
|
-
onAutoResumed(callback) {
|
|
330
|
-
this.onAutoResumedCallback = callback;
|
|
331
|
-
}
|
|
332
|
-
// ── Private: CDP Communication ─────────────────────────────────────────
|
|
333
|
-
send(method, params) {
|
|
334
|
-
return new Promise((resolve, reject) => {
|
|
335
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
336
|
-
reject(new Error("CDP WebSocket is not connected"));
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const id = ++this.requestId;
|
|
340
|
-
const request = { id, method, params };
|
|
341
|
-
this.pendingRequests.set(id, { resolve, reject });
|
|
342
|
-
this.ws.send(JSON.stringify(request));
|
|
343
|
-
setTimeout(() => {
|
|
344
|
-
if (this.pendingRequests.has(id)) {
|
|
345
|
-
this.pendingRequests.delete(id);
|
|
346
|
-
reject(new Error(`CDP request timed out: ${method}`));
|
|
347
|
-
}
|
|
348
|
-
}, 1e4);
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
handleMessage(raw) {
|
|
352
|
-
let msg;
|
|
353
|
-
try {
|
|
354
|
-
msg = JSON.parse(raw);
|
|
355
|
-
} catch {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if ("id" in msg && typeof msg.id === "number") {
|
|
359
|
-
const pending = this.pendingRequests.get(msg.id);
|
|
360
|
-
if (pending) {
|
|
361
|
-
this.pendingRequests.delete(msg.id);
|
|
362
|
-
const response = msg;
|
|
363
|
-
if (response.error) {
|
|
364
|
-
pending.reject(new Error(`CDP error: ${response.error.message}`));
|
|
365
|
-
} else {
|
|
366
|
-
pending.resolve(response.result ?? {});
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const event = msg;
|
|
372
|
-
if (event.method === "Debugger.paused") {
|
|
373
|
-
this.handlePaused(event.params ?? {});
|
|
374
|
-
} else if (event.method === "Debugger.resumed") {
|
|
375
|
-
this.pausedState = null;
|
|
376
|
-
this.clearAutoResumeTimer();
|
|
377
|
-
this.onResumedCallback?.();
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
handlePaused(params) {
|
|
381
|
-
const rawFrames = params.callFrames ?? [];
|
|
382
|
-
const reason = params.reason ?? "unknown";
|
|
383
|
-
const hitBreakpoints = params.hitBreakpoints;
|
|
384
|
-
const callFrames = rawFrames.map((frame, index) => ({
|
|
385
|
-
index,
|
|
386
|
-
functionName: frame.functionName || "(anonymous)",
|
|
387
|
-
file: frame.url,
|
|
388
|
-
// Convert to 1-based
|
|
389
|
-
line: frame.location.lineNumber + 1,
|
|
390
|
-
column: frame.location.columnNumber + 1,
|
|
391
|
-
callFrameId: frame.callFrameId
|
|
392
|
-
}));
|
|
393
|
-
this.pausedState = { callFrames, reason, hitBreakpoints };
|
|
394
|
-
this.clearAutoResumeTimer();
|
|
395
|
-
this.autoResumeTimer = setTimeout(() => {
|
|
396
|
-
if (this.pausedState) {
|
|
397
|
-
this.resume().then(() => {
|
|
398
|
-
this.onAutoResumedCallback?.();
|
|
399
|
-
}).catch(() => {
|
|
36
|
+
execSync(`git checkout --detach origin/${branch}`, {
|
|
37
|
+
cwd: worktreePath,
|
|
38
|
+
stdio: "ignore"
|
|
400
39
|
});
|
|
401
|
-
}
|
|
402
|
-
}, AUTO_RESUME_TIMEOUT_MS);
|
|
403
|
-
this.onPausedCallback?.(this.pausedState);
|
|
404
|
-
}
|
|
405
|
-
// ── Private: Probe Buffer ──────────────────────────────────────────────
|
|
406
|
-
async ensureProbeBuffer() {
|
|
407
|
-
if (this.probeBufferInjected) return;
|
|
408
|
-
await this.send("Runtime.evaluate", {
|
|
409
|
-
expression: `
|
|
410
|
-
globalThis.__conveyorProbes = globalThis.__conveyorProbes || {
|
|
411
|
-
_buffer: [],
|
|
412
|
-
_maxSize: ${PROBE_BUFFER_MAX_SIZE},
|
|
413
|
-
probe(label, data) {
|
|
414
|
-
this._buffer.push({ label, data, timestamp: Date.now() });
|
|
415
|
-
if (this._buffer.length > this._maxSize) this._buffer.shift();
|
|
416
|
-
},
|
|
417
|
-
read(label, limit) {
|
|
418
|
-
const items = label !== undefined
|
|
419
|
-
? this._buffer.filter(function(e) { return e.label === label; })
|
|
420
|
-
: this._buffer;
|
|
421
|
-
return limit !== undefined ? items.slice(-limit) : items;
|
|
422
|
-
},
|
|
423
|
-
clear(label) {
|
|
424
|
-
if (label !== undefined) {
|
|
425
|
-
this._buffer = this._buffer.filter(function(e) { return e.label !== label; });
|
|
426
|
-
} else {
|
|
427
|
-
this._buffer = [];
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
"ok"
|
|
432
|
-
`,
|
|
433
|
-
returnByValue: true
|
|
434
|
-
});
|
|
435
|
-
this.probeBufferInjected = true;
|
|
436
|
-
}
|
|
437
|
-
// ── Private: Helpers ───────────────────────────────────────────────────
|
|
438
|
-
extractEvalError(result, remoteObj) {
|
|
439
|
-
if (this.protocol === "webkit") {
|
|
440
|
-
if (result.wasThrown) {
|
|
441
|
-
return remoteObj?.description ?? remoteObj?.value?.toString() ?? "Unknown error";
|
|
442
|
-
}
|
|
443
|
-
return null;
|
|
444
|
-
}
|
|
445
|
-
const exceptionDetails = result.exceptionDetails;
|
|
446
|
-
if (exceptionDetails) {
|
|
447
|
-
return exceptionDetails.exception?.description ?? exceptionDetails.text ?? "Unknown error";
|
|
448
|
-
}
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
fileToUrlRegex(file) {
|
|
452
|
-
const escaped = file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
453
|
-
return `.*${escaped}$`;
|
|
454
|
-
}
|
|
455
|
-
formatRemoteObject(obj) {
|
|
456
|
-
if (!obj) return "undefined";
|
|
457
|
-
if (obj.type === "undefined") return "undefined";
|
|
458
|
-
if (obj.type === "boolean" || obj.type === "number") return String(obj.value);
|
|
459
|
-
if (obj.type === "string") return JSON.stringify(obj.value);
|
|
460
|
-
if (obj.value === null) return "null";
|
|
461
|
-
if (obj.description) return obj.description;
|
|
462
|
-
if (obj.subtype) return `[${obj.type}:${obj.subtype}]`;
|
|
463
|
-
return `[${obj.type}]`;
|
|
464
|
-
}
|
|
465
|
-
truncateValue(value) {
|
|
466
|
-
if (value.length <= MAX_VARIABLE_VALUE_BYTES) return value;
|
|
467
|
-
return value.slice(0, MAX_VARIABLE_VALUE_BYTES - 3) + "...";
|
|
468
|
-
}
|
|
469
|
-
clearAutoResumeTimer() {
|
|
470
|
-
if (this.autoResumeTimer) {
|
|
471
|
-
clearTimeout(this.autoResumeTimer);
|
|
472
|
-
this.autoResumeTimer = null;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
cleanup() {
|
|
476
|
-
this.clearAutoResumeTimer();
|
|
477
|
-
this.ws = null;
|
|
478
|
-
this.pausedState = null;
|
|
479
|
-
this.probes.clear();
|
|
480
|
-
this.probeBufferInjected = false;
|
|
481
|
-
for (const [, pending] of this.pendingRequests) {
|
|
482
|
-
pending.reject(new Error("CDP connection closed"));
|
|
483
|
-
}
|
|
484
|
-
this.pendingRequests.clear();
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
// src/debug/playwright-types.ts
|
|
489
|
-
var MAX_VARIABLE_VALUE_BYTES2 = 2048;
|
|
490
|
-
function formatRemoteObject(obj) {
|
|
491
|
-
if (!obj) return "undefined";
|
|
492
|
-
if (obj.type === "undefined") return "undefined";
|
|
493
|
-
if (obj.type === "boolean" || obj.type === "number") return String(obj.value);
|
|
494
|
-
if (obj.type === "string") return JSON.stringify(obj.value);
|
|
495
|
-
if (obj.value === null) return "null";
|
|
496
|
-
if (obj.description) return obj.description;
|
|
497
|
-
if (obj.subtype) return `[${obj.type}:${obj.subtype}]`;
|
|
498
|
-
return `[${obj.type}]`;
|
|
499
|
-
}
|
|
500
|
-
function truncateValue(value) {
|
|
501
|
-
if (value.length <= MAX_VARIABLE_VALUE_BYTES2) return value;
|
|
502
|
-
return value.slice(0, MAX_VARIABLE_VALUE_BYTES2 - 3) + "...";
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// src/debug/playwright-client.ts
|
|
506
|
-
var MAX_VARIABLES_PER_SCOPE2 = 50;
|
|
507
|
-
var AUTO_RESUME_TIMEOUT_MS2 = 3e4;
|
|
508
|
-
var PROBE_BUFFER_MAX_SIZE2 = 1e3;
|
|
509
|
-
var BUFFER_MAX = 500;
|
|
510
|
-
var ERROR_BUFFER_MAX = 200;
|
|
511
|
-
var INACTIVITY_TIMEOUT_MS = 6e5;
|
|
512
|
-
var PlaywrightDebugClient = class {
|
|
513
|
-
browser = null;
|
|
514
|
-
page = null;
|
|
515
|
-
cdpSession = null;
|
|
516
|
-
breakpoints = /* @__PURE__ */ new Map();
|
|
517
|
-
probes = /* @__PURE__ */ new Map();
|
|
518
|
-
probeCounter = 0;
|
|
519
|
-
probeBufferInjected = false;
|
|
520
|
-
pausedState = null;
|
|
521
|
-
autoResumeTimer = null;
|
|
522
|
-
inactivityTimer = null;
|
|
523
|
-
onPausedCallback = null;
|
|
524
|
-
onResumedCallback = null;
|
|
525
|
-
onAutoResumedCallback = null;
|
|
526
|
-
consoleMessages = [];
|
|
527
|
-
networkRequests = [];
|
|
528
|
-
pageErrors = [];
|
|
529
|
-
sourceMapDetected = null;
|
|
530
|
-
async launch(previewUrl) {
|
|
531
|
-
if (this.browser) throw new Error("Playwright browser is already running");
|
|
532
|
-
const { chromium } = await import("playwright-core");
|
|
533
|
-
this.browser = await chromium.launch({
|
|
534
|
-
headless: true,
|
|
535
|
-
args: [
|
|
536
|
-
"--no-sandbox",
|
|
537
|
-
"--disable-setuid-sandbox",
|
|
538
|
-
"--disable-dev-shm-usage",
|
|
539
|
-
"--disable-gpu"
|
|
540
|
-
]
|
|
541
|
-
});
|
|
542
|
-
const context = this.browser.contexts()[0] ?? await this.browser.newContext();
|
|
543
|
-
this.page = await context.newPage();
|
|
544
|
-
this.setupPassiveCapture();
|
|
545
|
-
await this.page.goto(previewUrl, { waitUntil: "domcontentloaded" });
|
|
546
|
-
this.cdpSession = await context.newCDPSession(this.page);
|
|
547
|
-
await this.cdpSession.send("Debugger.enable", {});
|
|
548
|
-
await this.cdpSession.send("Runtime.enable", {});
|
|
549
|
-
this.setupCdpEvents();
|
|
550
|
-
await this.checkSourceMaps();
|
|
551
|
-
this.resetInactivityTimer();
|
|
552
|
-
}
|
|
553
|
-
async close() {
|
|
554
|
-
this.clearAutoResumeTimer();
|
|
555
|
-
this.clearInactivityTimer();
|
|
556
|
-
if (this.cdpSession) {
|
|
557
|
-
try {
|
|
558
|
-
await this.cdpSession.detach();
|
|
559
|
-
} catch {
|
|
560
|
-
}
|
|
561
|
-
this.cdpSession = null;
|
|
562
|
-
}
|
|
563
|
-
if (this.browser) {
|
|
564
|
-
try {
|
|
565
|
-
await this.browser.close();
|
|
566
|
-
} catch {
|
|
567
|
-
}
|
|
568
|
-
this.browser = null;
|
|
569
|
-
}
|
|
570
|
-
this.page = null;
|
|
571
|
-
this.pausedState = null;
|
|
572
|
-
this.breakpoints.clear();
|
|
573
|
-
this.probes.clear();
|
|
574
|
-
this.probeBufferInjected = false;
|
|
575
|
-
this.consoleMessages = [];
|
|
576
|
-
this.networkRequests = [];
|
|
577
|
-
this.pageErrors = [];
|
|
578
|
-
}
|
|
579
|
-
isConnected() {
|
|
580
|
-
return this.browser !== null && this.cdpSession !== null;
|
|
581
|
-
}
|
|
582
|
-
hasSourceMaps() {
|
|
583
|
-
return this.sourceMapDetected;
|
|
584
|
-
}
|
|
585
|
-
async setBreakpoint(file, line, condition) {
|
|
586
|
-
const session = this.requireSession();
|
|
587
|
-
this.resetInactivityTimer();
|
|
588
|
-
const params = {
|
|
589
|
-
urlRegex: this.fileToUrlRegex(file),
|
|
590
|
-
// CDP uses 0-based lines
|
|
591
|
-
lineNumber: line - 1
|
|
592
|
-
};
|
|
593
|
-
if (condition) params.condition = condition;
|
|
594
|
-
const result = await session.send("Debugger.setBreakpointByUrl", params);
|
|
595
|
-
const breakpointId = result.breakpointId;
|
|
596
|
-
this.breakpoints.set(breakpointId, { breakpointId, file, line, condition });
|
|
597
|
-
return breakpointId;
|
|
598
|
-
}
|
|
599
|
-
async removeBreakpoint(breakpointId) {
|
|
600
|
-
const session = this.requireSession();
|
|
601
|
-
await session.send("Debugger.removeBreakpoint", { breakpointId });
|
|
602
|
-
this.breakpoints.delete(breakpointId);
|
|
603
|
-
}
|
|
604
|
-
async removeAllBreakpoints() {
|
|
605
|
-
for (const id of [...this.breakpoints.keys()]) {
|
|
606
|
-
try {
|
|
607
|
-
await this.removeBreakpoint(id);
|
|
608
40
|
} catch {
|
|
609
|
-
this.breakpoints.delete(id);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
listBreakpoints() {
|
|
614
|
-
return [...this.breakpoints.values()];
|
|
615
|
-
}
|
|
616
|
-
async setLogpoint(file, line, expressions, label) {
|
|
617
|
-
await this.ensureProbeBuffer();
|
|
618
|
-
const probeId = `client-probe-${++this.probeCounter}`;
|
|
619
|
-
const probeLabel = label ?? `${file}:${line}`;
|
|
620
|
-
const dataEntries = expressions.map((expr) => `${JSON.stringify(expr)}: (${expr})`).join(", ");
|
|
621
|
-
const condition = `(globalThis.__conveyorProbes?.probe(${JSON.stringify(probeLabel)}, {${dataEntries}}), false)`;
|
|
622
|
-
const breakpointId = await this.setBreakpoint(file, line, condition);
|
|
623
|
-
this.probes.set(probeId, { probeId, breakpointId, file, line, expressions, label: probeLabel });
|
|
624
|
-
return probeId;
|
|
625
|
-
}
|
|
626
|
-
async removeProbe(probeId) {
|
|
627
|
-
const probe = this.probes.get(probeId);
|
|
628
|
-
if (!probe) throw new Error(`Probe ${probeId} not found`);
|
|
629
|
-
await this.removeBreakpoint(probe.breakpointId);
|
|
630
|
-
this.probes.delete(probeId);
|
|
631
|
-
}
|
|
632
|
-
listProbes() {
|
|
633
|
-
return [...this.probes.values()];
|
|
634
|
-
}
|
|
635
|
-
async getProbeResults(label, limit) {
|
|
636
|
-
const args = [
|
|
637
|
-
label === void 0 ? "undefined" : JSON.stringify(label),
|
|
638
|
-
limit === void 0 ? "undefined" : String(limit)
|
|
639
|
-
].join(", ");
|
|
640
|
-
const result = await this.evaluate(
|
|
641
|
-
`JSON.stringify(globalThis.__conveyorProbes?.read(${args}) ?? [])`
|
|
642
|
-
);
|
|
643
|
-
if (result.type === "error") return [];
|
|
644
|
-
try {
|
|
645
|
-
const raw = result.value;
|
|
646
|
-
return JSON.parse(raw.startsWith('"') ? JSON.parse(raw) : raw);
|
|
647
|
-
} catch {
|
|
648
|
-
return [];
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
async clearProbeResults(label) {
|
|
652
|
-
const arg = label === void 0 ? "undefined" : JSON.stringify(label);
|
|
653
|
-
await this.evaluate(`(globalThis.__conveyorProbes?.clear(${arg}), undefined)`);
|
|
654
|
-
}
|
|
655
|
-
isPaused() {
|
|
656
|
-
return this.pausedState !== null;
|
|
657
|
-
}
|
|
658
|
-
getPausedState() {
|
|
659
|
-
return this.pausedState;
|
|
660
|
-
}
|
|
661
|
-
getCallStack() {
|
|
662
|
-
return this.pausedState?.callFrames ?? [];
|
|
663
|
-
}
|
|
664
|
-
async getScopeVariables(callFrameId) {
|
|
665
|
-
if (!this.pausedState) throw new Error("Debugger is not paused");
|
|
666
|
-
const session = this.requireSession();
|
|
667
|
-
const frame = this.pausedState.callFrames.find((f) => f.callFrameId === callFrameId);
|
|
668
|
-
if (!frame) throw new Error(`Call frame ${callFrameId} not found`);
|
|
669
|
-
const result = await session.send("Debugger.evaluateOnCallFrame", {
|
|
670
|
-
callFrameId,
|
|
671
|
-
expression: "this",
|
|
672
|
-
returnByValue: false
|
|
673
|
-
});
|
|
674
|
-
const variables = [];
|
|
675
|
-
try {
|
|
676
|
-
const scopeResult = await session.send("Runtime.getProperties", {
|
|
677
|
-
objectId: result.result?.objectId,
|
|
678
|
-
ownProperties: true,
|
|
679
|
-
generatePreview: false
|
|
680
|
-
});
|
|
681
|
-
for (const desc of (scopeResult.result ?? []).slice(
|
|
682
|
-
0,
|
|
683
|
-
MAX_VARIABLES_PER_SCOPE2
|
|
684
|
-
)) {
|
|
685
|
-
if (desc.value) {
|
|
686
|
-
variables.push({
|
|
687
|
-
name: desc.name,
|
|
688
|
-
type: desc.value.type,
|
|
689
|
-
value: truncateValue(formatRemoteObject(desc.value))
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
} catch {
|
|
694
|
-
}
|
|
695
|
-
return variables;
|
|
696
|
-
}
|
|
697
|
-
async resume() {
|
|
698
|
-
this.clearAutoResumeTimer();
|
|
699
|
-
await this.requireSession().send("Debugger.resume", {});
|
|
700
|
-
this.pausedState = null;
|
|
701
|
-
}
|
|
702
|
-
async stepOver() {
|
|
703
|
-
this.clearAutoResumeTimer();
|
|
704
|
-
await this.requireSession().send("Debugger.stepOver", {});
|
|
705
|
-
}
|
|
706
|
-
async stepInto() {
|
|
707
|
-
this.clearAutoResumeTimer();
|
|
708
|
-
await this.requireSession().send("Debugger.stepInto", {});
|
|
709
|
-
}
|
|
710
|
-
async evaluate(expression, callFrameId) {
|
|
711
|
-
const session = this.requireSession();
|
|
712
|
-
this.resetInactivityTimer();
|
|
713
|
-
let result;
|
|
714
|
-
if (callFrameId && this.pausedState) {
|
|
715
|
-
result = await session.send("Debugger.evaluateOnCallFrame", {
|
|
716
|
-
callFrameId,
|
|
717
|
-
expression,
|
|
718
|
-
returnByValue: false,
|
|
719
|
-
generatePreview: true
|
|
720
|
-
});
|
|
721
|
-
} else {
|
|
722
|
-
result = await session.send("Runtime.evaluate", {
|
|
723
|
-
expression,
|
|
724
|
-
returnByValue: false,
|
|
725
|
-
generatePreview: true
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
const remoteObj = result.result;
|
|
729
|
-
const exDetails = result.exceptionDetails;
|
|
730
|
-
if (exDetails) {
|
|
731
|
-
return {
|
|
732
|
-
type: "error",
|
|
733
|
-
value: exDetails.exception?.description ?? exDetails.text ?? "Unknown error"
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
return {
|
|
737
|
-
type: remoteObj?.type ?? "undefined",
|
|
738
|
-
value: truncateValue(formatRemoteObject(remoteObj))
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
|
-
async navigate(url) {
|
|
742
|
-
this.requirePage();
|
|
743
|
-
this.resetInactivityTimer();
|
|
744
|
-
await this.page?.goto(url, { waitUntil: "domcontentloaded" });
|
|
745
|
-
}
|
|
746
|
-
async click(selector) {
|
|
747
|
-
this.requirePage();
|
|
748
|
-
this.resetInactivityTimer();
|
|
749
|
-
await this.page?.click(selector, { timeout: 1e4 });
|
|
750
|
-
}
|
|
751
|
-
async screenshot() {
|
|
752
|
-
this.requirePage();
|
|
753
|
-
this.resetInactivityTimer();
|
|
754
|
-
const buffer = await this.page?.screenshot({ type: "png", encoding: "base64" });
|
|
755
|
-
return typeof buffer === "string" ? buffer : Buffer.from(buffer).toString("base64");
|
|
756
|
-
}
|
|
757
|
-
getCurrentUrl() {
|
|
758
|
-
this.requirePage();
|
|
759
|
-
return this.page?.url() ?? "";
|
|
760
|
-
}
|
|
761
|
-
getConsoleMessages(level, limit) {
|
|
762
|
-
let messages = this.consoleMessages;
|
|
763
|
-
if (level) messages = messages.filter((m) => m.level === level);
|
|
764
|
-
if (limit) messages = messages.slice(-limit);
|
|
765
|
-
return messages;
|
|
766
|
-
}
|
|
767
|
-
getNetworkRequests(filter, limit) {
|
|
768
|
-
let requests = this.networkRequests;
|
|
769
|
-
if (filter) {
|
|
770
|
-
const regex = new RegExp(filter, "i");
|
|
771
|
-
requests = requests.filter((r) => regex.test(r.url));
|
|
772
|
-
}
|
|
773
|
-
if (limit) requests = requests.slice(-limit);
|
|
774
|
-
return requests;
|
|
775
|
-
}
|
|
776
|
-
getPageErrors(limit) {
|
|
777
|
-
return limit ? this.pageErrors.slice(-limit) : [...this.pageErrors];
|
|
778
|
-
}
|
|
779
|
-
onPaused(callback) {
|
|
780
|
-
this.onPausedCallback = callback;
|
|
781
|
-
}
|
|
782
|
-
onResumed(callback) {
|
|
783
|
-
this.onResumedCallback = callback;
|
|
784
|
-
}
|
|
785
|
-
onAutoResumed(callback) {
|
|
786
|
-
this.onAutoResumedCallback = callback;
|
|
787
|
-
}
|
|
788
|
-
// ── Private ───────────────────────────────────────────────────────────
|
|
789
|
-
setupCdpEvents() {
|
|
790
|
-
if (!this.cdpSession) return;
|
|
791
|
-
this.cdpSession.on("Debugger.paused", (params) => this.handlePaused(params));
|
|
792
|
-
this.cdpSession.on("Debugger.resumed", () => {
|
|
793
|
-
this.pausedState = null;
|
|
794
|
-
this.clearAutoResumeTimer();
|
|
795
|
-
this.onResumedCallback?.();
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
handlePaused(params) {
|
|
799
|
-
const rawFrames = params.callFrames ?? [];
|
|
800
|
-
const callFrames = rawFrames.map((frame, index) => ({
|
|
801
|
-
index,
|
|
802
|
-
functionName: frame.functionName || "(anonymous)",
|
|
803
|
-
file: frame.url,
|
|
804
|
-
line: frame.location.lineNumber + 1,
|
|
805
|
-
column: frame.location.columnNumber + 1,
|
|
806
|
-
callFrameId: frame.callFrameId
|
|
807
|
-
}));
|
|
808
|
-
this.pausedState = {
|
|
809
|
-
callFrames,
|
|
810
|
-
reason: params.reason ?? "unknown",
|
|
811
|
-
hitBreakpoints: params.hitBreakpoints
|
|
812
|
-
};
|
|
813
|
-
this.clearAutoResumeTimer();
|
|
814
|
-
this.autoResumeTimer = setTimeout(() => {
|
|
815
|
-
if (this.pausedState) {
|
|
816
|
-
this.resume().then(() => this.onAutoResumedCallback?.()).catch(() => {
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
}, AUTO_RESUME_TIMEOUT_MS2);
|
|
820
|
-
this.onPausedCallback?.(this.pausedState);
|
|
821
|
-
}
|
|
822
|
-
setupPassiveCapture() {
|
|
823
|
-
if (!this.page) return;
|
|
824
|
-
this.page.on("console", (msg) => {
|
|
825
|
-
const m = msg;
|
|
826
|
-
this.consoleMessages.push({
|
|
827
|
-
level: m.type(),
|
|
828
|
-
text: m.text(),
|
|
829
|
-
timestamp: Date.now(),
|
|
830
|
-
url: m.location()?.url,
|
|
831
|
-
line: m.location()?.lineNumber
|
|
832
|
-
});
|
|
833
|
-
if (this.consoleMessages.length > BUFFER_MAX) this.consoleMessages.shift();
|
|
834
|
-
});
|
|
835
|
-
this.page.on("pageerror", (error) => {
|
|
836
|
-
const e = error;
|
|
837
|
-
this.pageErrors.push({ message: e.message, stack: e.stack, timestamp: Date.now() });
|
|
838
|
-
if (this.pageErrors.length > ERROR_BUFFER_MAX) this.pageErrors.shift();
|
|
839
|
-
});
|
|
840
|
-
this.page.on("request", (request) => {
|
|
841
|
-
const r = request;
|
|
842
|
-
this.networkRequests.push({
|
|
843
|
-
url: r.url(),
|
|
844
|
-
method: r.method(),
|
|
845
|
-
resourceType: r.resourceType(),
|
|
846
|
-
timestamp: Date.now()
|
|
847
|
-
});
|
|
848
|
-
if (this.networkRequests.length > BUFFER_MAX) this.networkRequests.shift();
|
|
849
|
-
});
|
|
850
|
-
this.page.on("response", (response) => {
|
|
851
|
-
const resp = response;
|
|
852
|
-
const url = resp.url();
|
|
853
|
-
for (let i = this.networkRequests.length - 1; i >= 0; i--) {
|
|
854
|
-
const req = this.networkRequests[i];
|
|
855
|
-
if (req.url === url && req.status === void 0) {
|
|
856
|
-
req.status = resp.status();
|
|
857
|
-
req.duration = Date.now() - req.timestamp;
|
|
858
|
-
break;
|
|
859
|
-
}
|
|
860
41
|
}
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
async checkSourceMaps() {
|
|
864
|
-
const hasMapFiles = this.networkRequests.some(
|
|
865
|
-
(r) => r.url.endsWith(".map") || r.url.includes("sourceMappingURL")
|
|
866
|
-
);
|
|
867
|
-
if (hasMapFiles) {
|
|
868
|
-
this.sourceMapDetected = true;
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
try {
|
|
872
|
-
const result = await this.requireSession().send("Runtime.evaluate", {
|
|
873
|
-
expression: "true",
|
|
874
|
-
returnByValue: true
|
|
875
|
-
});
|
|
876
|
-
this.sourceMapDetected = result.result ? null : false;
|
|
877
|
-
} catch {
|
|
878
|
-
this.sourceMapDetected = null;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
async ensureProbeBuffer() {
|
|
882
|
-
if (this.probeBufferInjected) return;
|
|
883
|
-
const session = this.requireSession();
|
|
884
|
-
await session.send("Runtime.evaluate", {
|
|
885
|
-
expression: `globalThis.__conveyorProbes=globalThis.__conveyorProbes||{_buffer:[],_maxSize:${PROBE_BUFFER_MAX_SIZE2},probe(l,d){this._buffer.push({label:l,data:d,timestamp:Date.now()});if(this._buffer.length>this._maxSize)this._buffer.shift()},read(l,n){var i=l!==void 0?this._buffer.filter(function(e){return e.label===l}):this._buffer;return n!==void 0?i.slice(-n):i},clear(l){if(l!==void 0){this._buffer=this._buffer.filter(function(e){return e.label!==l})}else{this._buffer=[]}}};"ok"`,
|
|
886
|
-
returnByValue: true
|
|
887
|
-
});
|
|
888
|
-
this.probeBufferInjected = true;
|
|
889
|
-
}
|
|
890
|
-
requireSession() {
|
|
891
|
-
if (!this.cdpSession) throw new Error("Playwright CDP session is not connected");
|
|
892
|
-
return this.cdpSession;
|
|
893
|
-
}
|
|
894
|
-
requirePage() {
|
|
895
|
-
if (!this.page) throw new Error("No page available");
|
|
896
|
-
}
|
|
897
|
-
fileToUrlRegex(file) {
|
|
898
|
-
return `.*${file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
|
|
899
|
-
}
|
|
900
|
-
clearAutoResumeTimer() {
|
|
901
|
-
if (this.autoResumeTimer) {
|
|
902
|
-
clearTimeout(this.autoResumeTimer);
|
|
903
|
-
this.autoResumeTimer = null;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
resetInactivityTimer() {
|
|
907
|
-
this.clearInactivityTimer();
|
|
908
|
-
this.inactivityTimer = setTimeout(() => {
|
|
909
|
-
this.close().catch(() => {
|
|
910
|
-
});
|
|
911
|
-
}, INACTIVITY_TIMEOUT_MS);
|
|
912
|
-
}
|
|
913
|
-
clearInactivityTimer() {
|
|
914
|
-
if (this.inactivityTimer) {
|
|
915
|
-
clearTimeout(this.inactivityTimer);
|
|
916
|
-
this.inactivityTimer = null;
|
|
917
42
|
}
|
|
43
|
+
return worktreePath;
|
|
918
44
|
}
|
|
919
|
-
};
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
this.startCommand = startCommand;
|
|
941
|
-
this.workingDir = workingDir;
|
|
942
|
-
this.eventHandler = eventHandler;
|
|
943
|
-
}
|
|
944
|
-
// ── Public API ─────────────────────────────────────────────────────────
|
|
945
|
-
isDebugMode() {
|
|
946
|
-
return this._isDebugMode;
|
|
947
|
-
}
|
|
948
|
-
isClientDebugMode() {
|
|
949
|
-
return this._isClientDebugMode;
|
|
950
|
-
}
|
|
951
|
-
getClient() {
|
|
952
|
-
return this.cdpClient;
|
|
953
|
-
}
|
|
954
|
-
getPlaywrightClient() {
|
|
955
|
-
return this.playwrightClient;
|
|
956
|
-
}
|
|
957
|
-
drainBreakpointHitQueue() {
|
|
958
|
-
const queued = [...this.breakpointHitQueue];
|
|
959
|
-
this.breakpointHitQueue = [];
|
|
960
|
-
return queued;
|
|
961
|
-
}
|
|
962
|
-
drainClientBreakpointHitQueue() {
|
|
963
|
-
const queued = [...this.clientBreakpointHitQueue];
|
|
964
|
-
this.clientBreakpointHitQueue = [];
|
|
965
|
-
return queued;
|
|
966
|
-
}
|
|
967
|
-
async enterDebugMode(killCurrentProcess, options) {
|
|
968
|
-
const serverSide = options?.serverSide ?? !options?.clientSide;
|
|
969
|
-
const clientSide = options?.clientSide ?? false;
|
|
970
|
-
if (serverSide && this._isDebugMode) {
|
|
971
|
-
throw new Error("Already in server debug mode");
|
|
972
|
-
}
|
|
973
|
-
if (clientSide && this._isClientDebugMode) {
|
|
974
|
-
throw new Error("Already in client debug mode");
|
|
975
|
-
}
|
|
976
|
-
if (serverSide && !this._isDebugMode) {
|
|
977
|
-
await this.activateServerDebug(killCurrentProcess);
|
|
978
|
-
}
|
|
979
|
-
if (clientSide && !this._isClientDebugMode) {
|
|
980
|
-
await this.activateClientDebug(options?.previewUrl);
|
|
981
|
-
}
|
|
982
|
-
this.eventHandler.onDebugModeEntered();
|
|
983
|
-
}
|
|
984
|
-
async exitDebugMode(restartNormalServer) {
|
|
985
|
-
if (!this._isDebugMode && !this._isClientDebugMode) {
|
|
986
|
-
throw new Error("Not in debug mode");
|
|
987
|
-
}
|
|
988
|
-
if (this.cdpClient) {
|
|
989
|
-
try {
|
|
990
|
-
await this.cdpClient.removeAllBreakpoints();
|
|
991
|
-
} catch {
|
|
992
|
-
}
|
|
993
|
-
this.cdpClient.disconnect();
|
|
994
|
-
this.cdpClient = null;
|
|
995
|
-
}
|
|
996
|
-
if (this.playwrightClient) {
|
|
997
|
-
try {
|
|
998
|
-
await this.playwrightClient.removeAllBreakpoints();
|
|
999
|
-
} catch {
|
|
1000
|
-
}
|
|
45
|
+
const ref = branch ? `origin/${branch}` : "HEAD";
|
|
46
|
+
execSync(`git worktree add --detach "${worktreePath}" ${ref}`, {
|
|
47
|
+
cwd: projectDir,
|
|
48
|
+
stdio: "ignore"
|
|
49
|
+
});
|
|
50
|
+
return worktreePath;
|
|
51
|
+
}
|
|
52
|
+
function detachWorktreeBranch(projectDir, branch) {
|
|
53
|
+
try {
|
|
54
|
+
const output = execSync("git worktree list --porcelain", {
|
|
55
|
+
cwd: projectDir,
|
|
56
|
+
encoding: "utf-8"
|
|
57
|
+
});
|
|
58
|
+
const entries = output.split("\n\n");
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
const lines = entry.trim().split("\n");
|
|
61
|
+
const worktreeLine = lines.find((l) => l.startsWith("worktree "));
|
|
62
|
+
const branchLine = lines.find((l) => l.startsWith("branch "));
|
|
63
|
+
if (!worktreeLine || branchLine !== `branch refs/heads/${branch}`) continue;
|
|
64
|
+
const worktreePath = worktreeLine.replace("worktree ", "");
|
|
65
|
+
if (!worktreePath.includes(`/${WORKTREE_DIR}/`)) continue;
|
|
1001
66
|
try {
|
|
1002
|
-
|
|
67
|
+
execSync("git checkout --detach", { cwd: worktreePath, stdio: "ignore" });
|
|
1003
68
|
} catch {
|
|
1004
69
|
}
|
|
1005
|
-
this.playwrightClient = null;
|
|
1006
|
-
}
|
|
1007
|
-
if (this.debugProcess && !this.debugProcess.killed) {
|
|
1008
|
-
this.debugProcess.kill("SIGTERM");
|
|
1009
|
-
this.debugProcess = null;
|
|
1010
70
|
}
|
|
1011
|
-
|
|
1012
|
-
this._isClientDebugMode = false;
|
|
1013
|
-
this.breakpointHitQueue = [];
|
|
1014
|
-
this.clientBreakpointHitQueue = [];
|
|
1015
|
-
restartNormalServer?.();
|
|
1016
|
-
this.eventHandler.onDebugModeExited();
|
|
1017
|
-
}
|
|
1018
|
-
// ── Debug Mode Activation ────────────────────────────────────────────
|
|
1019
|
-
async activateServerDebug(killCurrentProcess) {
|
|
1020
|
-
killCurrentProcess?.();
|
|
1021
|
-
const runtime = detectRuntime(this.startCommand);
|
|
1022
|
-
const inspectorUrl = await this.startWithInspector();
|
|
1023
|
-
const protocol = runtime === "bun" ? "webkit" : "cdp";
|
|
1024
|
-
this.cdpClient = new CdpDebugClient();
|
|
1025
|
-
this.cdpClient.onPaused((state) => {
|
|
1026
|
-
this.breakpointHitQueue.push(state);
|
|
1027
|
-
this.eventHandler.onBreakpointHit(state);
|
|
1028
|
-
});
|
|
1029
|
-
this.cdpClient.onAutoResumed(() => {
|
|
1030
|
-
this.eventHandler.onAutoResumed();
|
|
1031
|
-
});
|
|
1032
|
-
await this.cdpClient.connect(inspectorUrl, protocol);
|
|
1033
|
-
this._isDebugMode = true;
|
|
1034
|
-
try {
|
|
1035
|
-
await injectTelemetry(this.cdpClient);
|
|
1036
|
-
} catch {
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
async activateClientDebug(previewUrl) {
|
|
1040
|
-
if (!previewUrl) {
|
|
1041
|
-
throw new Error("previewUrl is required for client-side debug mode");
|
|
1042
|
-
}
|
|
1043
|
-
await this.ensurePlaywrightInstalled();
|
|
1044
|
-
this.playwrightClient = new PlaywrightDebugClient();
|
|
1045
|
-
this.playwrightClient.onPaused((state) => {
|
|
1046
|
-
this.clientBreakpointHitQueue.push(state);
|
|
1047
|
-
this.eventHandler.onClientBreakpointHit(state);
|
|
1048
|
-
});
|
|
1049
|
-
this.playwrightClient.onAutoResumed(() => {
|
|
1050
|
-
this.eventHandler.onAutoResumed();
|
|
1051
|
-
});
|
|
1052
|
-
await this.playwrightClient.launch(previewUrl);
|
|
1053
|
-
this._isClientDebugMode = true;
|
|
1054
|
-
}
|
|
1055
|
-
// ── Playwright Installation ───────────────────────────────────────────
|
|
1056
|
-
async ensurePlaywrightInstalled() {
|
|
1057
|
-
try {
|
|
1058
|
-
await import("playwright-core");
|
|
1059
|
-
const pw = await import("playwright-core");
|
|
1060
|
-
const executablePath = pw.chromium.executablePath();
|
|
1061
|
-
if (executablePath) return;
|
|
1062
|
-
} catch {
|
|
1063
|
-
}
|
|
1064
|
-
this.eventHandler.onOutput(
|
|
1065
|
-
"stderr",
|
|
1066
|
-
"Installing Playwright Chromium (first-time setup, ~250MB)...\n"
|
|
1067
|
-
);
|
|
1068
|
-
try {
|
|
1069
|
-
execSync("npx playwright install chromium --with-deps", {
|
|
1070
|
-
cwd: this.workingDir,
|
|
1071
|
-
timeout: PLAYWRIGHT_INSTALL_TIMEOUT_MS,
|
|
1072
|
-
stdio: "pipe"
|
|
1073
|
-
});
|
|
1074
|
-
this.eventHandler.onOutput("stderr", "Playwright Chromium installed successfully.\n");
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
throw new Error(
|
|
1077
|
-
`Failed to install Playwright Chromium: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1078
|
-
{ cause: error }
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
// ── Private ────────────────────────────────────────────────────────────
|
|
1083
|
-
// oxlint-disable-next-line require-await
|
|
1084
|
-
async startWithInspector() {
|
|
1085
|
-
const runtime = detectRuntime(this.startCommand);
|
|
1086
|
-
return new Promise((resolve, reject) => {
|
|
1087
|
-
let resolved = false;
|
|
1088
|
-
const timeout = setTimeout(() => {
|
|
1089
|
-
if (!resolved) {
|
|
1090
|
-
resolved = true;
|
|
1091
|
-
reject(new Error(getTimeoutMessage(runtime)));
|
|
1092
|
-
}
|
|
1093
|
-
}, CONNECTION_TIMEOUT_MS);
|
|
1094
|
-
let cmd = this.startCommand;
|
|
1095
|
-
let env = { ...process.env };
|
|
1096
|
-
if (runtime === "bun") {
|
|
1097
|
-
cmd = injectBunInspectFlag(this.startCommand);
|
|
1098
|
-
} else {
|
|
1099
|
-
env = { ...env, NODE_OPTIONS: "--inspect=0" };
|
|
1100
|
-
}
|
|
1101
|
-
const inspectorRegex = runtime === "bun" ? BUN_INSPECTOR_REGEX : NODE_INSPECTOR_REGEX;
|
|
1102
|
-
const child = runStartCommandWithEnv(cmd, this.workingDir, env, (stream, data) => {
|
|
1103
|
-
this.eventHandler.onOutput(stream, data);
|
|
1104
|
-
if (!resolved && stream === "stderr") {
|
|
1105
|
-
const match = data.match(inspectorRegex);
|
|
1106
|
-
if (match?.[1]) {
|
|
1107
|
-
resolved = true;
|
|
1108
|
-
clearTimeout(timeout);
|
|
1109
|
-
resolve(match[1]);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
this.debugProcess = child;
|
|
1114
|
-
child.on("error", (err) => {
|
|
1115
|
-
if (!resolved) {
|
|
1116
|
-
resolved = true;
|
|
1117
|
-
clearTimeout(timeout);
|
|
1118
|
-
reject(err);
|
|
1119
|
-
}
|
|
1120
|
-
});
|
|
1121
|
-
child.on("exit", (code) => {
|
|
1122
|
-
if (!resolved) {
|
|
1123
|
-
resolved = true;
|
|
1124
|
-
clearTimeout(timeout);
|
|
1125
|
-
reject(new Error(`Debug process exited with code ${code} before inspector started`));
|
|
1126
|
-
}
|
|
1127
|
-
});
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
};
|
|
1131
|
-
var BUN_COMMAND_REGEX = /\b(bun|bunx)\b/;
|
|
1132
|
-
var NODE_COMMAND_REGEX = /\b(node|npx|tsx|ts-node)\b/;
|
|
1133
|
-
function detectRuntime(startCommand) {
|
|
1134
|
-
if (BUN_COMMAND_REGEX.test(startCommand)) return "bun";
|
|
1135
|
-
if (NODE_COMMAND_REGEX.test(startCommand)) return "node";
|
|
1136
|
-
if (process.env.BUN_INSTALL) return "bun";
|
|
1137
|
-
return "node";
|
|
1138
|
-
}
|
|
1139
|
-
function injectBunInspectFlag(command) {
|
|
1140
|
-
return command.replace(/\b(bunx?)\b/, "$1 --inspect");
|
|
1141
|
-
}
|
|
1142
|
-
function getTimeoutMessage(runtime) {
|
|
1143
|
-
switch (runtime) {
|
|
1144
|
-
case "bun":
|
|
1145
|
-
return "Timed out waiting for Bun inspector (--inspect flag). Ensure your start command begins with 'bun'";
|
|
1146
|
-
case "node":
|
|
1147
|
-
return "Timed out waiting for Node.js inspector (NODE_OPTIONS='--inspect=0')";
|
|
1148
|
-
default:
|
|
1149
|
-
return "Timed out waiting for debugger inspector. Your runtime may not support CDP.";
|
|
71
|
+
} catch {
|
|
1150
72
|
}
|
|
1151
73
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
child.stdout?.on("data", (chunk) => {
|
|
1160
|
-
onOutput("stdout", chunk.toString());
|
|
1161
|
-
});
|
|
1162
|
-
child.stderr?.on("data", (chunk) => {
|
|
1163
|
-
onOutput("stderr", chunk.toString());
|
|
1164
|
-
});
|
|
1165
|
-
child.unref();
|
|
1166
|
-
return child;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// src/debug/lifecycle-manager.ts
|
|
1170
|
-
var DebugLifecycleManager = class {
|
|
1171
|
-
connection;
|
|
1172
|
-
debugManager = null;
|
|
1173
|
-
hypothesis;
|
|
1174
|
-
sessionStartedAt;
|
|
1175
|
-
breakpointsHitCount = 0;
|
|
1176
|
-
telemetryEvents = [];
|
|
1177
|
-
reproduceRequested = false;
|
|
1178
|
-
keyFindings = [];
|
|
1179
|
-
_isActive = false;
|
|
1180
|
-
constructor(connection) {
|
|
1181
|
-
this.connection = connection;
|
|
1182
|
-
}
|
|
1183
|
-
get isActive() {
|
|
1184
|
-
return this._isActive;
|
|
1185
|
-
}
|
|
1186
|
-
setDebugManager(manager) {
|
|
1187
|
-
this.debugManager = manager;
|
|
1188
|
-
}
|
|
1189
|
-
/**
|
|
1190
|
-
* Creates a DebugEventHandler that bridges DebugManager events
|
|
1191
|
-
* to the lifecycle manager and broadcasts state to the web UI.
|
|
1192
|
-
*/
|
|
1193
|
-
createEventHandler(baseOnOutput) {
|
|
1194
|
-
return {
|
|
1195
|
-
onDebugModeEntered: () => {
|
|
1196
|
-
this._isActive = true;
|
|
1197
|
-
this.sessionStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1198
|
-
this.breakpointsHitCount = 0;
|
|
1199
|
-
this.telemetryEvents = [];
|
|
1200
|
-
this.keyFindings = [];
|
|
1201
|
-
this.broadcastState();
|
|
1202
|
-
},
|
|
1203
|
-
onDebugModeExited: () => {
|
|
1204
|
-
this.broadcastState();
|
|
1205
|
-
},
|
|
1206
|
-
onBreakpointHit: (state) => {
|
|
1207
|
-
this.breakpointsHitCount++;
|
|
1208
|
-
const file = state.callFrames[0]?.file ?? "unknown";
|
|
1209
|
-
const line = state.callFrames[0]?.line ?? 0;
|
|
1210
|
-
this.keyFindings.push(`Breakpoint hit at ${file}:${line} (${state.reason})`);
|
|
1211
|
-
this.broadcastState();
|
|
1212
|
-
},
|
|
1213
|
-
onClientBreakpointHit: (state) => {
|
|
1214
|
-
this.breakpointsHitCount++;
|
|
1215
|
-
const file = state.callFrames[0]?.file ?? "unknown";
|
|
1216
|
-
const line = state.callFrames[0]?.line ?? 0;
|
|
1217
|
-
this.keyFindings.push(`Client breakpoint hit at ${file}:${line}`);
|
|
1218
|
-
this.broadcastState();
|
|
1219
|
-
},
|
|
1220
|
-
onAutoResumed: () => {
|
|
1221
|
-
this.broadcastState();
|
|
1222
|
-
},
|
|
1223
|
-
onDebugError: (message) => {
|
|
1224
|
-
this.keyFindings.push(`Debug error: ${message}`);
|
|
1225
|
-
this.broadcastState();
|
|
1226
|
-
},
|
|
1227
|
-
onOutput: baseOnOutput
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
/** Mark the hypothesis for the current debug session */
|
|
1231
|
-
setHypothesis(hypothesis) {
|
|
1232
|
-
this.hypothesis = hypothesis;
|
|
1233
|
-
this.broadcastState();
|
|
1234
|
-
}
|
|
1235
|
-
/** Signal that the user has been asked to reproduce the bug */
|
|
1236
|
-
requestReproduce(hypothesis) {
|
|
1237
|
-
this.reproduceRequested = true;
|
|
1238
|
-
if (hypothesis) this.hypothesis = hypothesis;
|
|
1239
|
-
this.connection.sendEvent({
|
|
1240
|
-
type: "debug_reproduce_requested",
|
|
1241
|
-
hypothesis: hypothesis ?? this.hypothesis
|
|
1242
|
-
});
|
|
1243
|
-
this.broadcastState();
|
|
1244
|
-
}
|
|
1245
|
-
/** Add a telemetry event to the debug session */
|
|
1246
|
-
addTelemetryEvent(event) {
|
|
1247
|
-
this.telemetryEvents.push(event);
|
|
1248
|
-
if (this.telemetryEvents.length > 100) {
|
|
1249
|
-
this.telemetryEvents = this.telemetryEvents.slice(-100);
|
|
1250
|
-
}
|
|
1251
|
-
this.broadcastState();
|
|
1252
|
-
}
|
|
1253
|
-
/** Generate and broadcast a debug session summary, then reset state */
|
|
1254
|
-
completeSession() {
|
|
1255
|
-
if (!this._isActive && !this.sessionStartedAt) return null;
|
|
1256
|
-
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1257
|
-
const startedAt = this.sessionStartedAt ?? endedAt;
|
|
1258
|
-
const durationMs = new Date(endedAt).getTime() - new Date(startedAt).getTime();
|
|
1259
|
-
const breakpoints = this.getBreakpoints();
|
|
1260
|
-
const probes = this.getProbes();
|
|
1261
|
-
const summary = {
|
|
1262
|
-
hypothesis: this.hypothesis,
|
|
1263
|
-
breakpointsSet: breakpoints.length,
|
|
1264
|
-
breakpointsHit: this.breakpointsHitCount,
|
|
1265
|
-
probesSet: probes.length,
|
|
1266
|
-
telemetryEventsCaptured: this.telemetryEvents.length,
|
|
1267
|
-
keyFindings: this.keyFindings.slice(0, 20),
|
|
1268
|
-
durationMs,
|
|
1269
|
-
startedAt,
|
|
1270
|
-
endedAt
|
|
1271
|
-
};
|
|
1272
|
-
this.connection.sendEvent({ type: "debug_session_complete", summary });
|
|
1273
|
-
this._isActive = false;
|
|
1274
|
-
this.hypothesis = void 0;
|
|
1275
|
-
this.sessionStartedAt = void 0;
|
|
1276
|
-
this.breakpointsHitCount = 0;
|
|
1277
|
-
this.telemetryEvents = [];
|
|
1278
|
-
this.reproduceRequested = false;
|
|
1279
|
-
this.keyFindings = [];
|
|
1280
|
-
this.broadcastState();
|
|
1281
|
-
return summary;
|
|
1282
|
-
}
|
|
1283
|
-
/** Auto-exit cleanup — called when agent session ends */
|
|
1284
|
-
autoCleanup() {
|
|
1285
|
-
if (this._isActive || this.sessionStartedAt) {
|
|
1286
|
-
this.completeSession();
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
// ── Private ────────────────────────────────────────────────────────
|
|
1290
|
-
getBreakpoints() {
|
|
1291
|
-
if (!this.debugManager) return [];
|
|
1292
|
-
const client = this.debugManager.getClient();
|
|
1293
|
-
if (!client?.isConnected()) return [];
|
|
1294
|
-
try {
|
|
1295
|
-
return client.listBreakpoints().map((bp) => ({
|
|
1296
|
-
id: bp.breakpointId,
|
|
1297
|
-
file: bp.file,
|
|
1298
|
-
line: bp.line,
|
|
1299
|
-
condition: bp.condition,
|
|
1300
|
-
hitCount: 0
|
|
1301
|
-
}));
|
|
1302
|
-
} catch {
|
|
1303
|
-
return [];
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
getProbes() {
|
|
1307
|
-
if (!this.debugManager) return [];
|
|
1308
|
-
const client = this.debugManager.getClient();
|
|
1309
|
-
if (!client?.isConnected()) return [];
|
|
1310
|
-
try {
|
|
1311
|
-
return client.listProbes().map((p) => ({
|
|
1312
|
-
id: p.probeId,
|
|
1313
|
-
file: p.file,
|
|
1314
|
-
line: p.line,
|
|
1315
|
-
expressions: p.expressions,
|
|
1316
|
-
label: p.label
|
|
1317
|
-
}));
|
|
1318
|
-
} catch {
|
|
1319
|
-
return [];
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
buildState() {
|
|
1323
|
-
const serverDebug = this.debugManager?.isDebugMode() ?? false;
|
|
1324
|
-
const clientDebug = this.debugManager?.isClientDebugMode() ?? false;
|
|
1325
|
-
const active = this._isActive && (serverDebug || clientDebug);
|
|
1326
|
-
let pausedAt;
|
|
1327
|
-
const client = this.debugManager?.getClient();
|
|
1328
|
-
if (client?.isPaused()) {
|
|
1329
|
-
const callStack = client.getCallStack();
|
|
1330
|
-
if (callStack[0]) {
|
|
1331
|
-
pausedAt = {
|
|
1332
|
-
file: callStack[0].file,
|
|
1333
|
-
line: callStack[0].line
|
|
1334
|
-
};
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
return {
|
|
1338
|
-
active,
|
|
1339
|
-
hypothesis: this.hypothesis,
|
|
1340
|
-
serverDebug,
|
|
1341
|
-
clientDebug,
|
|
1342
|
-
breakpoints: this.getBreakpoints(),
|
|
1343
|
-
probes: this.getProbes(),
|
|
1344
|
-
telemetryEvents: this.telemetryEvents.slice(-50),
|
|
1345
|
-
pausedAt,
|
|
1346
|
-
reproduceRequested: this.reproduceRequested
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
broadcastState() {
|
|
1350
|
-
this.connection.sendEvent({
|
|
1351
|
-
type: "debug_state_changed",
|
|
1352
|
-
state: this.buildState()
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
// src/debug/hypothesis-tracker.ts
|
|
1358
|
-
var MAX_ITERATIONS = 3;
|
|
1359
|
-
var HypothesisTracker = class {
|
|
1360
|
-
hypothesis = null;
|
|
1361
|
-
iteration = 0;
|
|
1362
|
-
observations = [];
|
|
1363
|
-
_active = false;
|
|
1364
|
-
conclusion = null;
|
|
1365
|
-
// ── Public API ─────────────────────────────────────────────────────────
|
|
1366
|
-
/**
|
|
1367
|
-
* Start or update the current debugging hypothesis.
|
|
1368
|
-
* Increments the iteration count if a hypothesis was already set.
|
|
1369
|
-
*/
|
|
1370
|
-
setHypothesis(text) {
|
|
1371
|
-
if (!this._active) {
|
|
1372
|
-
this._active = true;
|
|
1373
|
-
this.hypothesis = text;
|
|
1374
|
-
this.iteration = 1;
|
|
1375
|
-
this.observations = [];
|
|
1376
|
-
this.conclusion = null;
|
|
1377
|
-
return { accepted: true, message: `Hypothesis set (iteration 1/${MAX_ITERATIONS}): ${text}` };
|
|
1378
|
-
}
|
|
1379
|
-
if (this.iteration >= MAX_ITERATIONS) {
|
|
1380
|
-
return {
|
|
1381
|
-
accepted: false,
|
|
1382
|
-
message: `Max iterations (${MAX_ITERATIONS}) reached. You must make a decision based on what you've observed. Use concludeSession() to summarize findings and exit debug mode.`
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
this.iteration++;
|
|
1386
|
-
this.hypothesis = text;
|
|
1387
|
-
return {
|
|
1388
|
-
accepted: true,
|
|
1389
|
-
message: `Hypothesis updated (iteration ${this.iteration}/${MAX_ITERATIONS}): ${text}`
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
/**
|
|
1393
|
-
* Record an observation from debug data (breakpoint hit, telemetry, etc.).
|
|
1394
|
-
*/
|
|
1395
|
-
recordObservation(description, confirmsHypothesis) {
|
|
1396
|
-
this.observations.push({
|
|
1397
|
-
description,
|
|
1398
|
-
confirmsHypothesis,
|
|
1399
|
-
iteration: this.iteration,
|
|
1400
|
-
timestamp: Date.now()
|
|
74
|
+
function removeWorktree(projectDir, taskId) {
|
|
75
|
+
const worktreePath = join(projectDir, WORKTREE_DIR, taskId);
|
|
76
|
+
if (!existsSync(worktreePath)) return;
|
|
77
|
+
try {
|
|
78
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
79
|
+
cwd: projectDir,
|
|
80
|
+
stdio: "ignore"
|
|
1401
81
|
});
|
|
82
|
+
} catch {
|
|
1402
83
|
}
|
|
1403
|
-
|
|
1404
|
-
* End the debug session with a conclusion.
|
|
1405
|
-
*/
|
|
1406
|
-
concludeSession(conclusion) {
|
|
1407
|
-
this.conclusion = conclusion;
|
|
1408
|
-
this._active = false;
|
|
1409
|
-
const summary = this.getSessionSummary();
|
|
1410
|
-
this.reset();
|
|
1411
|
-
return summary;
|
|
1412
|
-
}
|
|
1413
|
-
/**
|
|
1414
|
-
* Get formatted debug context for injection into the agent's system prompt.
|
|
1415
|
-
* Returns empty string if no active session.
|
|
1416
|
-
*/
|
|
1417
|
-
getDebugContext() {
|
|
1418
|
-
if (!this._active || !this.hypothesis) return "";
|
|
1419
|
-
const lines = [
|
|
1420
|
-
`Current debug session (iteration ${this.iteration}/${MAX_ITERATIONS}):`,
|
|
1421
|
-
`Hypothesis: "${this.hypothesis}"`
|
|
1422
|
-
];
|
|
1423
|
-
if (this.observations.length > 0) {
|
|
1424
|
-
const byIteration = /* @__PURE__ */ new Map();
|
|
1425
|
-
for (const obs of this.observations) {
|
|
1426
|
-
const group = byIteration.get(obs.iteration) ?? [];
|
|
1427
|
-
group.push(obs);
|
|
1428
|
-
byIteration.set(obs.iteration, group);
|
|
1429
|
-
}
|
|
1430
|
-
for (const [iter, iterObs] of byIteration) {
|
|
1431
|
-
for (const obs of iterObs) {
|
|
1432
|
-
const verdict = obs.confirmsHypothesis ? "SUPPORTS" : "CONTRADICTS";
|
|
1433
|
-
lines.push(`Iteration ${iter}: ${obs.description} \u2014 ${verdict} hypothesis`);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
if (this.iteration >= MAX_ITERATIONS) {
|
|
1438
|
-
lines.push(
|
|
1439
|
-
`\u26A0\uFE0F Final iteration reached. Analyze observations and make a decision \u2014 no more debug iterations available.`
|
|
1440
|
-
);
|
|
1441
|
-
}
|
|
1442
|
-
return lines.join("\n");
|
|
1443
|
-
}
|
|
1444
|
-
/**
|
|
1445
|
-
* Get the full state (for serialization/testing).
|
|
1446
|
-
*/
|
|
1447
|
-
getState() {
|
|
1448
|
-
return {
|
|
1449
|
-
hypothesis: this.hypothesis,
|
|
1450
|
-
iteration: this.iteration,
|
|
1451
|
-
observations: [...this.observations],
|
|
1452
|
-
active: this._active,
|
|
1453
|
-
conclusion: this.conclusion
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
isActive() {
|
|
1457
|
-
return this._active;
|
|
1458
|
-
}
|
|
1459
|
-
getIteration() {
|
|
1460
|
-
return this.iteration;
|
|
1461
|
-
}
|
|
1462
|
-
getMaxIterations() {
|
|
1463
|
-
return MAX_ITERATIONS;
|
|
1464
|
-
}
|
|
1465
|
-
// ── Private ────────────────────────────────────────────────────────────
|
|
1466
|
-
getSessionSummary() {
|
|
1467
|
-
const parts = [`Debug session complete.`];
|
|
1468
|
-
if (this.hypothesis) {
|
|
1469
|
-
parts.push(`Final hypothesis: "${this.hypothesis}"`);
|
|
1470
|
-
}
|
|
1471
|
-
parts.push(`Total iterations: ${this.iteration}`);
|
|
1472
|
-
if (this.observations.length > 0) {
|
|
1473
|
-
parts.push("Observations:");
|
|
1474
|
-
for (const obs of this.observations) {
|
|
1475
|
-
const verdict = obs.confirmsHypothesis ? "\u2713" : "\u2717";
|
|
1476
|
-
parts.push(` ${verdict} [iter ${obs.iteration}] ${obs.description}`);
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
const supporting = this.observations.filter((o) => o.confirmsHypothesis).length;
|
|
1480
|
-
const contradicting = this.observations.length - supporting;
|
|
1481
|
-
parts.push(`Evidence: ${supporting} supporting, ${contradicting} contradicting`);
|
|
1482
|
-
if (this.conclusion) {
|
|
1483
|
-
parts.push(`Conclusion: ${this.conclusion}`);
|
|
1484
|
-
}
|
|
1485
|
-
return parts.join("\n");
|
|
1486
|
-
}
|
|
1487
|
-
reset() {
|
|
1488
|
-
this.hypothesis = null;
|
|
1489
|
-
this.iteration = 0;
|
|
1490
|
-
this.observations = [];
|
|
1491
|
-
this._active = false;
|
|
1492
|
-
this.conclusion = null;
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
84
|
+
}
|
|
1495
85
|
export {
|
|
1496
86
|
AgentConnection,
|
|
1497
|
-
CommitWatcher,
|
|
1498
|
-
DEFAULT_LIFECYCLE_CONFIG,
|
|
1499
|
-
DebugLifecycleManager,
|
|
1500
|
-
DebugManager,
|
|
1501
|
-
FileCache,
|
|
1502
|
-
HypothesisTracker,
|
|
1503
|
-
Lifecycle,
|
|
1504
|
-
ModeController,
|
|
1505
87
|
PlanSync,
|
|
1506
|
-
ProjectConnection,
|
|
1507
|
-
ProjectRunner,
|
|
1508
88
|
SessionRunner,
|
|
1509
89
|
detachWorktreeBranch,
|
|
1510
|
-
detectRuntime,
|
|
1511
90
|
ensureWorktree,
|
|
1512
91
|
flushPendingChanges,
|
|
1513
92
|
getCurrentBranch,
|
|
1514
93
|
hasUncommittedChanges,
|
|
1515
94
|
hasUnpushedCommits,
|
|
1516
|
-
injectBunInspectFlag,
|
|
1517
95
|
loadConveyorConfig,
|
|
1518
96
|
loadForwardPorts,
|
|
1519
97
|
pushToOrigin,
|