@rallycry/conveyor-agent 8.13.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/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-L7Q2JZJX.js";
29
- import "./chunk-FDWECEDJ.js";
30
- import "./chunk-TN2YYT2V.js";
18
+ } from "./chunk-JH7GUOGW.js";
31
19
 
32
- // src/runner/file-cache.ts
33
- var DEFAULT_MAX_SIZE_BYTES = 50 * 1024 * 1024;
34
- var DEFAULT_TTL_MS = 60 * 60 * 1e3;
35
- var FileCache = class {
36
- cache = /* @__PURE__ */ new Map();
37
- currentSize = 0;
38
- maxSizeBytes;
39
- ttlMs;
40
- constructor(maxSizeBytes = DEFAULT_MAX_SIZE_BYTES, ttlMs = DEFAULT_TTL_MS) {
41
- this.maxSizeBytes = maxSizeBytes;
42
- this.ttlMs = ttlMs;
43
- }
44
- get(fileId) {
45
- const entry = this.cache.get(fileId);
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
- await this.removeBreakpoint(id);
183
- } catch {
184
- this.breakpoints.delete(id);
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
- // src/debug/debug-manager.ts
922
- import { spawn } from "child_process";
923
- import { execSync } from "child_process";
924
- var NODE_INSPECTOR_REGEX = /Debugger listening on (ws:\/\/127\.0\.0\.1:\d+\/[^\s]+)/;
925
- var BUN_INSPECTOR_REGEX = /(ws:\/\/[\w.-]+:\d+\/[\w-]+)/;
926
- var CONNECTION_TIMEOUT_MS = 1e4;
927
- var PLAYWRIGHT_INSTALL_TIMEOUT_MS = 12e4;
928
- var DebugManager = class {
929
- cdpClient = null;
930
- playwrightClient = null;
931
- debugProcess = null;
932
- _isDebugMode = false;
933
- _isClientDebugMode = false;
934
- startCommand;
935
- workingDir;
936
- eventHandler;
937
- breakpointHitQueue = [];
938
- clientBreakpointHitQueue = [];
939
- constructor(startCommand, workingDir, eventHandler) {
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
- await this.playwrightClient.close();
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
- this._isDebugMode = false;
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 runStartCommandWithEnv(cmd, cwd, env, onOutput) {
1153
- const child = spawn("sh", ["-c", cmd], {
1154
- cwd,
1155
- stdio: ["ignore", "pipe", "pipe"],
1156
- detached: true,
1157
- env: { ...env }
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,