@siuver/omp-debug-mode 0.1.1 → 0.1.2
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/CHANGELOG.md +6 -0
- package/README.md +22 -10
- package/package.json +1 -1
- package/src/main.ts +186 -90
- package/src/review-actions.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 - 2026-08-20
|
|
4
|
+
|
|
5
|
+
- Added an interactive round-review menu with Mark as fixed, Proceed, Add reproduction details, and Abort actions.
|
|
6
|
+
- Added `/debug-review` to reopen the menu and `/debug-note <details>` to return to the editor, add user evidence, and continue the workflow.
|
|
7
|
+
- Kept `/debug-done fixed|proceed` as compatible shortcuts and added interactive confirmation for zero-log completion, zero-log progression, and abort.
|
|
8
|
+
|
|
3
9
|
## 0.1.1 - 2026-08-20
|
|
4
10
|
|
|
5
11
|
- Replaced the localhost HTTP collector with direct JSONL file appends from instrumented runtime code.
|
package/README.md
CHANGED
|
@@ -9,20 +9,32 @@ The plugin makes the agent form hypotheses, add temporary runtime probes, attemp
|
|
|
9
9
|
| Command | Purpose |
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| `/debug-mode <problem>` | Starts a debugging session from a symptom, expected result, actual result, and reproduction description. |
|
|
12
|
+
| `/debug-review` | Reopens the interactive action menu for the completed round. |
|
|
13
|
+
| `/debug-note <details>` | Adds reproduction details from the editor and starts the next evidence-driven round. |
|
|
12
14
|
| `/debug-status` | Shows the current phase, round, run, live probes, and captured log counts. |
|
|
13
|
-
| `/debug-done fixed` |
|
|
14
|
-
| `/debug-done proceed` |
|
|
15
|
-
| `/debug-abort` | Stops debug mode and removes its logs while leaving code changes in the working tree. |
|
|
15
|
+
| `/debug-done fixed` | Non-menu shortcut that confirms the fix and asks the agent to remove every probe and summarize the result. |
|
|
16
|
+
| `/debug-done proceed` | Non-menu shortcut that analyzes the captured logs and starts another hypothesis/instrument/fix round. |
|
|
17
|
+
| `/debug-abort` | Stops debug mode and removes its logs after confirmation while leaving code changes in the working tree. |
|
|
18
|
+
|
|
19
|
+
## Interactive Round Review
|
|
20
|
+
|
|
21
|
+
When the Agent completes a debugging round, the plugin enters the reproduction gate and opens an action menu:
|
|
22
|
+
|
|
23
|
+
- **Mark as fixed** - asks the Agent to clean up probes and summarize the root cause and fix. If the round captured no runtime logs, the plugin asks for confirmation first.
|
|
24
|
+
- **Proceed with captured logs** - refreshes the log file at selection time, archives the completed run, and starts the next analysis and fix round. If no runtime observations exist, the plugin asks for confirmation before proceeding.
|
|
25
|
+
- **Add reproduction details** - closes the menu and pre-fills the editor with `/debug-note ` so you can add symptoms, environment details, or reproduction results before continuing.
|
|
26
|
+
- **Abort debug mode** - asks for confirmation, removes debug logs, and keeps applied code changes.
|
|
27
|
+
|
|
28
|
+
Pressing `Esc` simply closes the menu and leaves the workflow at the reproduction gate. After reproducing the issue in another application, run `/debug-review` to reopen it. The existing `/debug-done fixed|proceed` commands remain available for non-interactive and shortcut use.
|
|
16
29
|
|
|
17
30
|
## Workflow
|
|
18
31
|
|
|
19
32
|
1. Run `/debug-mode <problem description>`.
|
|
20
|
-
2. The
|
|
21
|
-
3. When the
|
|
22
|
-
4.
|
|
23
|
-
5. On success, the agent removes all probes, verifies the probe ledger is empty, and summarizes the root cause and fix.
|
|
33
|
+
2. The Agent investigates, records hypotheses, inserts minimal probes marked with `@omp-probe <id>`, and attempts a fix. The injected prompt strongly requires every probe to append runtime observations directly to the exact JSONL path; console output is supplemental only.
|
|
34
|
+
3. When the Agent stops, dismiss the review menu if necessary and reproduce the issue in the real application so the instrumented code writes its observations.
|
|
35
|
+
4. Reopen `/debug-review` and mark the issue fixed, proceed with logs, or return to the editor to submit `/debug-note <details>`.
|
|
24
36
|
|
|
25
|
-
The extension also provides the read-only `get_debug_logs` and `list_debug_probes` tools so the
|
|
37
|
+
The extension also provides the read-only `get_debug_logs` and `list_debug_probes` tools so the Agent can inspect runtime evidence and verify cleanup.
|
|
26
38
|
|
|
27
39
|
## Runtime Data
|
|
28
40
|
|
|
@@ -38,9 +50,9 @@ That exact stable path is injected into the Agent prompt. The prompt requires ev
|
|
|
38
50
|
{"probe":"player-state","ts":1787193600000,"data":{"isGrounded":false}}
|
|
39
51
|
```
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
If the log file contains zero entries, treat that as instrumentation or execution-path evidence. Check the build, path permissions, code path, and file append errors; do not ask the user to copy Unity Console output. Use `/debug-note` for reproduction details, not manual console transcription.
|
|
42
54
|
|
|
43
|
-
When you
|
|
55
|
+
When you proceed from the review menu, `/debug-done proceed`, or `/debug-note`, the plugin archives the completed file as `<run-id>.jsonl`, creates an empty `current.jsonl` for the next reproduction, and makes the Agent analyze the archived evidence through `get_debug_logs`. `/debug-status` shows the current absolute file path and log count.
|
|
44
56
|
|
|
45
57
|
## Install
|
|
46
58
|
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -5,26 +5,27 @@
|
|
|
5
5
|
* IDLE → /debug-mode <problem>
|
|
6
6
|
* → agent hypothesizes, adds @omp-probe instrumentation, attempts a fix
|
|
7
7
|
* → WAITING_REPRO: agent stops; user reproduces out-of-band
|
|
8
|
-
* →
|
|
9
|
-
* → /
|
|
8
|
+
* → Mark as fixed → agent removes probes + summarizes → teardown (logs deleted)
|
|
9
|
+
* → Proceed/add details → agent analyzes logs, re-instruments + fixes → loop
|
|
10
10
|
*
|
|
11
11
|
* - Stable JSONL file at <cwd>/.omp/debug/current.jsonl — runtime probes append
|
|
12
12
|
* observations directly with the target environment's native file APIs
|
|
13
13
|
* - Probe ledger: tool_call interception on edit/write records @omp-probe ids;
|
|
14
14
|
* ground truth is rescanned from disk (checkLedger)
|
|
15
15
|
* - Tools: get_debug_logs, list_debug_probes
|
|
16
|
-
* - Commands: /debug-mode, /debug-status, /debug-done
|
|
16
|
+
* - Commands: /debug-mode, /debug-review, /debug-note, /debug-status, /debug-done, /debug-abort
|
|
17
17
|
*/
|
|
18
18
|
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
19
19
|
import * as fs from "node:fs";
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import { ACTIVE_LOG_FILE, prepareRunLog, readJsonlLines, resolveRunLogFile } from "./log-files";
|
|
22
|
+
import { REVIEW_ABORT, REVIEW_ADD_DETAILS, REVIEW_MARK_FIXED, REVIEW_OPTIONS, REVIEW_PROCEED } from "./review-actions";
|
|
22
23
|
|
|
23
24
|
const DEBUG_ENTRY = "com.omp.debug-mode.state";
|
|
24
25
|
const PROBE_MARK = /@omp-probe\s+([A-Za-z0-9_-]+)/g;
|
|
25
|
-
|
|
26
26
|
type Phase = "idle" | "round" | "waiting" | "cleanup";
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
interface Probe {
|
|
29
30
|
id: string;
|
|
30
31
|
file: string;
|
|
@@ -48,7 +49,6 @@ interface DebugState {
|
|
|
48
49
|
function freshState(): DebugState {
|
|
49
50
|
return {
|
|
50
51
|
active: false,
|
|
51
|
-
phase: "idle",
|
|
52
52
|
problem: "",
|
|
53
53
|
round: 0,
|
|
54
54
|
runId: null,
|
|
@@ -86,8 +86,8 @@ Deployed probes (ground truth, maintained by the extension):
|
|
|
86
86
|
${probes}
|
|
87
87
|
|
|
88
88
|
Current run log file (absolute path): ${logFileFor(s) ?? "(not initialized)"}
|
|
89
|
-
Runtime probes MUST append JSONL directly to that exact file using the target
|
|
90
|
-
|
|
89
|
+
Runtime probes MUST append JSONL directly to that exact file using the target environment's native file APIs.
|
|
90
|
+
Console output such as Unity Debug.Log may supplement diagnostics but is never the runtime evidence for this workflow. Never ask the user to transcribe console output.
|
|
91
91
|
Logs by run: ${counts}
|
|
92
92
|
Current run id: ${s.runId ?? "(not started)"}`;
|
|
93
93
|
}
|
|
@@ -97,25 +97,26 @@ const METHODOLOGY = `\
|
|
|
97
97
|
Each round consists of, in order:
|
|
98
98
|
1. Restate hypotheses (mark each: pending / ruled-out / confirmed via runtime evidence).
|
|
99
99
|
2. Update instrumentation: remove probes that yielded no information, add probes that
|
|
100
|
-
discriminate between remaining hypotheses.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
100
|
+
discriminate between remaining hypotheses. Every runtime probe MUST append exactly
|
|
101
|
+
one compact JSON object plus a newline to the exact absolute log file shown above.
|
|
102
|
+
Use {"probe":"<id>","ts":<epoch-ms>,"data":<JSON-serializable-observation>}.
|
|
103
|
+
Append; never overwrite or truncate. Open, append, flush, and close promptly for each
|
|
104
|
+
observation. Do not use HTTP, POST, localhost, sockets, or any network transport.
|
|
105
|
+
Use the target environment's native file API and ensure the code path writes the file.
|
|
106
|
+
Console logging may supplement the file but never replaces it. Do not ask the user to
|
|
107
|
+
copy or summarize console output.
|
|
108
108
|
3. ATTEMPT A FIX for your leading hypothesis in the same round. Instrumentation without
|
|
109
109
|
a fix is an incomplete round — the loop only advances when you fix something.
|
|
110
110
|
4. End the round by writing concise reproduction steps for the user (exact commands or
|
|
111
|
-
actions, what to observe). Then STOP — the user reproduces out-of-band and
|
|
112
|
-
|
|
111
|
+
actions, what to observe). Then STOP — the user reproduces out-of-band and uses
|
|
112
|
+
the review menu, /debug-note, or /debug-done fixed|proceed.
|
|
113
113
|
When told the fix is confirmed: remove every probe, verify with the list_debug_probes
|
|
114
114
|
tool that the ledger is empty, then summarize root cause and the final fix.`;
|
|
115
115
|
|
|
116
116
|
export default function debugModeExtension(pi: ExtensionAPI) {
|
|
117
117
|
const z = pi.zod;
|
|
118
118
|
const state: DebugState = freshState();
|
|
119
|
+
let reviewMenuOpen = false;
|
|
119
120
|
let uiCtx: ExtensionContext | null = null;
|
|
120
121
|
|
|
121
122
|
// ============================== log files ==============================
|
|
@@ -171,8 +172,10 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
171
172
|
return run;
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
|
|
174
176
|
// ============================== probe ledger ==============================
|
|
175
177
|
|
|
178
|
+
|
|
176
179
|
pi.on("tool_call", async (event) => {
|
|
177
180
|
if (!state.active) return;
|
|
178
181
|
if (event.toolName !== "edit" && event.toolName !== "write") return;
|
|
@@ -184,8 +187,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
184
187
|
if (!state.probes.some(p => p.id === id)) state.probes.push({ id, file, round: state.round });
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
|
-
//
|
|
188
|
-
// rescans files for ground truth on demand.
|
|
190
|
+
// Removal detection rescans files for ground truth on demand.
|
|
189
191
|
});
|
|
190
192
|
|
|
191
193
|
/** Ground truth: which registered probes still exist in code. */
|
|
@@ -228,7 +230,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
228
230
|
if (state.phase === "waiting") {
|
|
229
231
|
refreshLogCounts();
|
|
230
232
|
const n = state.runId ? (state.logCounts[state.runId] ?? 0) : 0;
|
|
231
|
-
lines.push(ctx.ui.theme.fg("accent", "reproduce the bug, then: /debug-
|
|
233
|
+
lines.push(ctx.ui.theme.fg("accent", "reproduce the bug, then: /debug-review"));
|
|
232
234
|
lines.push(ctx.ui.theme.fg("dim", `run ${state.runId} — ${n} log entries`));
|
|
233
235
|
}
|
|
234
236
|
ctx.ui.setWidget("debug-mode", lines.length ? lines : undefined);
|
|
@@ -274,16 +276,20 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
274
276
|
if (!state.active) return;
|
|
275
277
|
if (state.phase === "cleanup") {
|
|
276
278
|
// Cleanup turn settled: keep fixes and remove the temporary logs.
|
|
277
|
-
if (state.cleanupReady) await teardown(ctx,
|
|
279
|
+
if (state.cleanupReady) await teardown(ctx, "finished");
|
|
278
280
|
return;
|
|
279
281
|
}
|
|
280
282
|
if (state.phase !== "round" || !state.hasRoundContent) return;
|
|
281
283
|
state.phase = "waiting";
|
|
284
|
+
pi.appendEntry(DEBUG_ENTRY, { ...state });
|
|
282
285
|
refreshUi();
|
|
283
286
|
ctx.ui.notify(
|
|
284
|
-
`Debug round ${state.round} paused
|
|
287
|
+
`Debug round ${state.round} paused. Reproduce the bug, then choose an action or run /debug-review.`,
|
|
285
288
|
"info",
|
|
286
289
|
);
|
|
290
|
+
if (ctx.hasUI) {
|
|
291
|
+
void openReviewMenu(ctx).catch(err => pi.logger.warn("debug-mode: review menu failed", { err }));
|
|
292
|
+
}
|
|
287
293
|
});
|
|
288
294
|
|
|
289
295
|
pi.registerCommand("debug-mode", {
|
|
@@ -291,7 +297,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
291
297
|
handler: async (args, ctx) => {
|
|
292
298
|
uiCtx = ctx;
|
|
293
299
|
if (state.active) {
|
|
294
|
-
ctx.ui.notify("debug-mode: already active (use /debug-done or /debug-abort)", "error");
|
|
300
|
+
ctx.ui.notify("debug-mode: already active (use /debug-review, /debug-done, or /debug-abort)", "error");
|
|
295
301
|
return;
|
|
296
302
|
}
|
|
297
303
|
const problem = args.trim();
|
|
@@ -331,65 +337,94 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
331
337
|
);
|
|
332
338
|
}
|
|
333
339
|
|
|
334
|
-
function
|
|
340
|
+
function ensureWaiting(ctx: ExtensionContext): boolean {
|
|
335
341
|
if (!state.active) {
|
|
336
342
|
ctx.ui.notify("debug-mode: not active", "error");
|
|
337
|
-
return;
|
|
343
|
+
return false;
|
|
338
344
|
}
|
|
339
345
|
if (state.phase !== "waiting") {
|
|
340
346
|
ctx.ui.notify(`debug-mode: not waiting for reproduction (phase: ${state.phase})`, "error");
|
|
341
|
-
return;
|
|
347
|
+
return false;
|
|
342
348
|
}
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function markDebugFixed(ctx: ExtensionContext): Promise<void> {
|
|
353
|
+
if (!ensureWaiting(ctx)) return;
|
|
343
354
|
refreshLogCounts();
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
{
|
|
350
|
-
customType: "debug-mode-fixed",
|
|
351
|
-
content:
|
|
352
|
-
"User marked the problem FIXED.\n" +
|
|
353
|
-
"1. Remove every debug probe from the code (probe ledger below; verify with list_debug_probes after edits).\n" +
|
|
354
|
-
"2. Then summarize: root cause, the fix applied, what remains in the working diff.\n" +
|
|
355
|
-
`Probe ledger: ${JSON.stringify(state.probes)}`,
|
|
356
|
-
display: true,
|
|
357
|
-
},
|
|
358
|
-
{ triggerTurn: true },
|
|
355
|
+
const logCount = state.runId ? (state.logCounts[state.runId] ?? 0) : 0;
|
|
356
|
+
if (logCount === 0 && ctx.hasUI) {
|
|
357
|
+
const confirmed = await ctx.ui.confirm(
|
|
358
|
+
"Mark as fixed without runtime logs?",
|
|
359
|
+
"No runtime observations were captured for this round. Mark the problem as fixed anyway?",
|
|
359
360
|
);
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
361
|
+
if (!confirmed) return;
|
|
362
|
+
}
|
|
363
|
+
if (!state.active || state.phase !== "waiting") return;
|
|
364
|
+
|
|
365
|
+
state.phase = "cleanup";
|
|
366
|
+
state.cleanupReady = false;
|
|
367
|
+
refreshUi();
|
|
368
|
+
pi.sendMessage(
|
|
369
|
+
{
|
|
370
|
+
customType: "debug-mode-fixed",
|
|
371
|
+
content:
|
|
372
|
+
"User marked the problem FIXED.\n" +
|
|
373
|
+
"1. Remove every debug probe from the code (probe ledger below; verify with list_debug_probes after edits).\n" +
|
|
374
|
+
"2. Then summarize: root cause, the fix applied, what remains in the working diff.\n" +
|
|
375
|
+
`Probe ledger: ${JSON.stringify(state.probes)}`,
|
|
376
|
+
display: true,
|
|
377
|
+
},
|
|
378
|
+
{ triggerTurn: true },
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function advanceDebug(ctx: ExtensionContext, reproductionDetails?: string): Promise<void> {
|
|
383
|
+
if (!ensureWaiting(ctx)) return;
|
|
384
|
+
refreshLogCounts();
|
|
385
|
+
const run = state.runId ?? "(none)";
|
|
386
|
+
const logCount = state.logCounts[run] ?? 0;
|
|
387
|
+
if (logCount === 0 && !reproductionDetails && ctx.hasUI) {
|
|
388
|
+
const confirmed = await ctx.ui.confirm(
|
|
389
|
+
"Proceed without runtime logs?",
|
|
390
|
+
"No runtime observations were captured for this round. Continue to another analysis and fix round anyway?",
|
|
388
391
|
);
|
|
392
|
+
if (!confirmed) return;
|
|
389
393
|
}
|
|
394
|
+
if (!state.active || state.phase !== "waiting") return;
|
|
395
|
+
|
|
396
|
+
const previousRound = state.round;
|
|
397
|
+
state.round += 1;
|
|
398
|
+
state.phase = "round";
|
|
399
|
+
state.hasRoundContent = false;
|
|
400
|
+
if (!newRun()) {
|
|
401
|
+
state.round = previousRound;
|
|
402
|
+
state.phase = "waiting";
|
|
403
|
+
ctx.ui.notify("debug-mode: could not initialize the next run log file; staying at the reproduction gate", "error");
|
|
404
|
+
refreshUi();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
refreshUi();
|
|
408
|
+
|
|
409
|
+
const userEvidence = reproductionDetails
|
|
410
|
+
? `User added reproduction details after run ${run}:\n\n${reproductionDetails}\n\nTreat these details as evidence alongside the captured logs.\n`
|
|
411
|
+
: `User chose PROCEED — the fix did not resolve it (run ${run} captured ${logCount} log entries).\n`;
|
|
412
|
+
pi.sendMessage(
|
|
413
|
+
{
|
|
414
|
+
customType: reproductionDetails ? "debug-mode-note" : "debug-mode-proceed",
|
|
415
|
+
content:
|
|
416
|
+
userEvidence +
|
|
417
|
+
(logCount === 0
|
|
418
|
+
? "No logs were captured: the instrumented code path may not have executed, the build may be stale, the path may be wrong, or the file append may have failed — treat that as a signal.\n"
|
|
419
|
+
: `Run ${run} captured ${logCount} log entries.\n`) +
|
|
420
|
+
"Read the previous run with get_debug_logs, update the hypotheses, re-instrument, attempt the next fix, give reproduction steps, and stop.",
|
|
421
|
+
display: true,
|
|
422
|
+
},
|
|
423
|
+
{ triggerTurn: true },
|
|
424
|
+
);
|
|
390
425
|
}
|
|
391
426
|
|
|
392
|
-
async function teardown(ctx: ExtensionContext,
|
|
427
|
+
async function teardown(ctx: ExtensionContext, outcome: "finished" | "aborted"): Promise<void> {
|
|
393
428
|
if (state.debugDir) {
|
|
394
429
|
try {
|
|
395
430
|
fs.rmSync(state.debugDir, { recursive: true, force: true });
|
|
@@ -401,13 +436,63 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
401
436
|
Object.assign(state, freshState());
|
|
402
437
|
pi.appendEntry(DEBUG_ENTRY, { ...state });
|
|
403
438
|
refreshUi();
|
|
439
|
+
const resultLabel = outcome === "finished" ? "finished" : "aborted";
|
|
404
440
|
if (probesLeft.length > 0) {
|
|
405
441
|
ctx.ui.notify(
|
|
406
|
-
`
|
|
442
|
+
`Debug mode ${resultLabel}, but ${probesLeft.length} probe(s) remain in code: ${probesLeft.map(p => p.id).join(", ")} — remove manually. Applied fixes remain in the working diff.`,
|
|
407
443
|
"warning",
|
|
408
444
|
);
|
|
409
|
-
} else
|
|
410
|
-
ctx.ui.notify(
|
|
445
|
+
} else {
|
|
446
|
+
ctx.ui.notify(
|
|
447
|
+
`Debug mode ${resultLabel}. Log files removed; applied fixes remain in the working diff for review.`,
|
|
448
|
+
"info",
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function abortDebug(ctx: ExtensionContext): Promise<void> {
|
|
454
|
+
if (!state.active) {
|
|
455
|
+
ctx.ui.notify("debug-mode: not active", "error");
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (ctx.hasUI) {
|
|
459
|
+
const confirmed = await ctx.ui.confirm(
|
|
460
|
+
"Abort debug mode?",
|
|
461
|
+
"Delete captured debug logs and stop the workflow? Applied code changes will remain in the working diff.",
|
|
462
|
+
);
|
|
463
|
+
if (!confirmed) return;
|
|
464
|
+
}
|
|
465
|
+
if (!state.active) return;
|
|
466
|
+
await teardown(ctx, "aborted");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function openReviewMenu(ctx: ExtensionContext): Promise<void> {
|
|
470
|
+
if (!ensureWaiting(ctx)) return;
|
|
471
|
+
if (!ctx.hasUI) {
|
|
472
|
+
ctx.ui.notify("/debug-review requires an interactive UI; use /debug-done fixed|proceed instead.", "warning");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (reviewMenuOpen) {
|
|
476
|
+
ctx.ui.notify("debug-mode: review menu is already open", "info");
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
reviewMenuOpen = true;
|
|
481
|
+
try {
|
|
482
|
+
const choice = await ctx.ui.select(`Review debug round ${state.round}`, [...REVIEW_OPTIONS]);
|
|
483
|
+
if (!choice) return;
|
|
484
|
+
if (choice === REVIEW_MARK_FIXED) {
|
|
485
|
+
await markDebugFixed(ctx);
|
|
486
|
+
} else if (choice === REVIEW_PROCEED) {
|
|
487
|
+
await advanceDebug(ctx);
|
|
488
|
+
} else if (choice === REVIEW_ADD_DETAILS) {
|
|
489
|
+
ctx.ui.setEditorText("/debug-note ");
|
|
490
|
+
ctx.ui.notify("Add reproduction details in the editor, then submit /debug-note.", "info");
|
|
491
|
+
} else if (choice === REVIEW_ABORT) {
|
|
492
|
+
await abortDebug(ctx);
|
|
493
|
+
}
|
|
494
|
+
} finally {
|
|
495
|
+
reviewMenuOpen = false;
|
|
411
496
|
}
|
|
412
497
|
}
|
|
413
498
|
|
|
@@ -419,11 +504,34 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
419
504
|
handler: async (args, ctx) => {
|
|
420
505
|
uiCtx = ctx;
|
|
421
506
|
const verdict = args.trim().toLowerCase();
|
|
422
|
-
if (verdict
|
|
507
|
+
if (verdict === "fixed") {
|
|
508
|
+
await markDebugFixed(ctx);
|
|
509
|
+
} else if (verdict === "proceed") {
|
|
510
|
+
await advanceDebug(ctx);
|
|
511
|
+
} else {
|
|
423
512
|
ctx.ui.notify("Usage: /debug-done fixed|proceed", "error");
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
pi.registerCommand("debug-review", {
|
|
518
|
+
description: "Open the interactive action menu for a completed debug round",
|
|
519
|
+
handler: async (_args, ctx) => {
|
|
520
|
+
uiCtx = ctx;
|
|
521
|
+
await openReviewMenu(ctx);
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
pi.registerCommand("debug-note", {
|
|
526
|
+
description: "Add reproduction details and continue: /debug-note <details>",
|
|
527
|
+
handler: async (args, ctx) => {
|
|
528
|
+
uiCtx = ctx;
|
|
529
|
+
const details = args.trim();
|
|
530
|
+
if (!details) {
|
|
531
|
+
ctx.ui.notify("Usage: /debug-note <reproduction details>", "error");
|
|
424
532
|
return;
|
|
425
533
|
}
|
|
426
|
-
|
|
534
|
+
await advanceDebug(ctx, details);
|
|
427
535
|
},
|
|
428
536
|
});
|
|
429
537
|
|
|
@@ -431,20 +539,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
431
539
|
description: "Abort debug mode: delete logs (fixes stay in the working diff)",
|
|
432
540
|
handler: async (_args, ctx) => {
|
|
433
541
|
uiCtx = ctx;
|
|
434
|
-
|
|
435
|
-
ctx.ui.notify("debug-mode: not active", "error");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const ledger = await checkLedger();
|
|
439
|
-
await teardown(ctx, true);
|
|
440
|
-
if (ledger.length > 0) {
|
|
441
|
-
ctx.ui.notify(
|
|
442
|
-
`Debug mode aborted. These probes remain (remove manually or ask the agent):\n${ledger.map(p => ` ${p.id} — ${p.file}`).join("\n")}\nApplied fixes are kept in the working diff.`,
|
|
443
|
-
"warning",
|
|
444
|
-
);
|
|
445
|
-
} else {
|
|
446
|
-
ctx.ui.notify("Debug mode aborted. No probes in code; applied fixes are kept in the working diff.", "info");
|
|
447
|
-
}
|
|
542
|
+
await abortDebug(ctx);
|
|
448
543
|
},
|
|
449
544
|
});
|
|
450
545
|
|
|
@@ -471,6 +566,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
471
566
|
|
|
472
567
|
// ============================== tools ==============================
|
|
473
568
|
|
|
569
|
+
|
|
474
570
|
pi.registerTool({
|
|
475
571
|
name: "get_debug_logs",
|
|
476
572
|
label: "Get Debug Logs",
|
|
@@ -572,7 +668,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
572
668
|
}
|
|
573
669
|
refreshLogCounts();
|
|
574
670
|
ctx.ui.notify(
|
|
575
|
-
`debug-mode resumed: phase=${state.phase} round=${state.round}, log=${currentFile ?? "unavailable"}. Use /debug-status, /debug-done fixed|proceed, or /debug-abort.`,
|
|
671
|
+
`debug-mode resumed: phase=${state.phase} round=${state.round}, log=${currentFile ?? "unavailable"}. Use /debug-status, /debug-review, /debug-done fixed|proceed, or /debug-abort.`,
|
|
576
672
|
"info",
|
|
577
673
|
);
|
|
578
674
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const REVIEW_MARK_FIXED = "Mark as fixed";
|
|
2
|
+
export const REVIEW_PROCEED = "Proceed with captured logs";
|
|
3
|
+
export const REVIEW_ADD_DETAILS = "Add reproduction details";
|
|
4
|
+
export const REVIEW_ABORT = "Abort debug mode";
|
|
5
|
+
|
|
6
|
+
export const REVIEW_OPTIONS = [
|
|
7
|
+
REVIEW_MARK_FIXED,
|
|
8
|
+
REVIEW_PROCEED,
|
|
9
|
+
REVIEW_ADD_DETAILS,
|
|
10
|
+
REVIEW_ABORT,
|
|
11
|
+
] as const;
|