@siuver/omp-debug-mode 0.1.0 → 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 +12 -0
- package/README.md +33 -11
- package/package.json +1 -1
- package/src/log-files.ts +46 -0
- package/src/main.ts +283 -193
- package/src/review-actions.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.1.1 - 2026-08-20
|
|
10
|
+
|
|
11
|
+
- Replaced the localhost HTTP collector with direct JSONL file appends from instrumented runtime code.
|
|
12
|
+
- The injected prompt now supplies the exact absolute log file and explicitly forbids POST, sockets, and other network transports.
|
|
13
|
+
- Updated log analysis, status reporting, and session resume to read JSONL directly; active probes use a stable `current.jsonl` path while completed rounds are archived by run id.
|
|
14
|
+
|
|
3
15
|
## 0.1.0 - 2026-08-20
|
|
4
16
|
|
|
5
17
|
- Initial npm release of the Cursor Debug Mode replica for OMP.
|
package/README.md
CHANGED
|
@@ -9,28 +9,50 @@ 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
|
|
|
29
|
-
|
|
41
|
+
At the start of each round, the plugin creates an absolute log path:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
<project>/.omp/debug/current.jsonl
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That exact stable path is injected into the Agent prompt. The prompt requires every runtime probe to use the target environment's native file append API and write one compact JSON object plus a newline using this schema:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{"probe":"player-state","ts":1787193600000,"data":{"isGrounded":false}}
|
|
51
|
+
```
|
|
30
52
|
|
|
31
|
-
|
|
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.
|
|
32
54
|
|
|
33
|
-
|
|
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.
|
|
34
56
|
|
|
35
57
|
## Install
|
|
36
58
|
|
package/package.json
CHANGED
package/src/log-files.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const ACTIVE_LOG_FILE = "current.jsonl";
|
|
5
|
+
|
|
6
|
+
export function resolveRunLogFile(
|
|
7
|
+
debugDir: string | null,
|
|
8
|
+
run: string | null,
|
|
9
|
+
activeRun: string | null,
|
|
10
|
+
): string | null {
|
|
11
|
+
if (!debugDir || !run) return null;
|
|
12
|
+
return path.join(debugDir, run === activeRun ? ACTIVE_LOG_FILE : `${run}.jsonl`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readJsonlLines(file: string | null): string[] {
|
|
16
|
+
if (!file) return [];
|
|
17
|
+
try {
|
|
18
|
+
return fs
|
|
19
|
+
.readFileSync(file, "utf8")
|
|
20
|
+
.split(/\r?\n/)
|
|
21
|
+
.filter(line => line.trim().length > 0);
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function prepareRunLog(debugDir: string, previousRun: string | null): string {
|
|
28
|
+
const activeFile = path.join(debugDir, ACTIVE_LOG_FILE);
|
|
29
|
+
const archivedFile = previousRun ? path.join(debugDir, `${previousRun}.jsonl`) : null;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (archivedFile) {
|
|
33
|
+
if (fs.existsSync(activeFile)) fs.copyFileSync(activeFile, archivedFile);
|
|
34
|
+
else fs.writeFileSync(archivedFile, "", { flag: "a" });
|
|
35
|
+
}
|
|
36
|
+
fs.writeFileSync(activeFile, "", { flag: "w" });
|
|
37
|
+
return activeFile;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (archivedFile) {
|
|
40
|
+
try {
|
|
41
|
+
fs.rmSync(archivedFile, { force: true });
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -5,29 +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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* logs append to <cwd>/.omp/debug/<run>.jsonl
|
|
11
|
+
* - Stable JSONL file at <cwd>/.omp/debug/current.jsonl — runtime probes append
|
|
12
|
+
* observations directly with the target environment's native file APIs
|
|
14
13
|
* - Probe ledger: tool_call interception on edit/write records @omp-probe ids;
|
|
15
14
|
* ground truth is rescanned from disk (checkLedger)
|
|
16
15
|
* - Tools: get_debug_logs, list_debug_probes
|
|
17
|
-
* - Commands: /debug-mode, /debug-status, /debug-done
|
|
16
|
+
* - Commands: /debug-mode, /debug-review, /debug-note, /debug-status, /debug-done, /debug-abort
|
|
18
17
|
*/
|
|
19
18
|
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
20
|
-
import type { Server } from "bun";
|
|
21
|
-
import type { WriteStream } from "node:fs";
|
|
22
19
|
import * as fs from "node:fs";
|
|
23
20
|
import * as path from "node:path";
|
|
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";
|
|
24
23
|
|
|
25
24
|
const DEBUG_ENTRY = "com.omp.debug-mode.state";
|
|
26
25
|
const PROBE_MARK = /@omp-probe\s+([A-Za-z0-9_-]+)/g;
|
|
27
|
-
const DEFAULT_PORT = 7842;
|
|
28
|
-
|
|
29
26
|
type Phase = "idle" | "round" | "waiting" | "cleanup";
|
|
30
27
|
|
|
28
|
+
|
|
31
29
|
interface Probe {
|
|
32
30
|
id: string;
|
|
33
31
|
file: string;
|
|
@@ -47,17 +45,10 @@ interface DebugState {
|
|
|
47
45
|
cleanupReady: boolean;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
interface LogEntry {
|
|
51
|
-
run: string;
|
|
52
|
-
probe: string;
|
|
53
|
-
ts: number;
|
|
54
|
-
data: unknown;
|
|
55
|
-
}
|
|
56
48
|
|
|
57
49
|
function freshState(): DebugState {
|
|
58
50
|
return {
|
|
59
51
|
active: false,
|
|
60
|
-
phase: "idle",
|
|
61
52
|
problem: "",
|
|
62
53
|
round: 0,
|
|
63
54
|
runId: null,
|
|
@@ -76,6 +67,10 @@ function probeIdsIn(text: unknown): string[] {
|
|
|
76
67
|
return ids;
|
|
77
68
|
}
|
|
78
69
|
|
|
70
|
+
function logFileFor(s: DebugState, run = s.runId): string | null {
|
|
71
|
+
return resolveRunLogFile(s.debugDir, run, s.runId);
|
|
72
|
+
}
|
|
73
|
+
|
|
79
74
|
function blackboard(s: DebugState): string {
|
|
80
75
|
const probes = s.probes.map(p => `${p.id} (${p.file}, round ${p.round})`).join("\n ") || "(none)";
|
|
81
76
|
const counts = Object.entries(s.logCounts)
|
|
@@ -90,8 +85,9 @@ ${s.problem}
|
|
|
90
85
|
Deployed probes (ground truth, maintained by the extension):
|
|
91
86
|
${probes}
|
|
92
87
|
|
|
93
|
-
|
|
94
|
-
|
|
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 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.
|
|
95
91
|
Logs by run: ${counts}
|
|
96
92
|
Current run id: ${s.runId ?? "(not started)"}`;
|
|
97
93
|
}
|
|
@@ -101,99 +97,85 @@ const METHODOLOGY = `\
|
|
|
101
97
|
Each round consists of, in order:
|
|
102
98
|
1. Restate hypotheses (mark each: pending / ruled-out / confirmed via runtime evidence).
|
|
103
99
|
2. Update instrumentation: remove probes that yielded no information, add probes that
|
|
104
|
-
discriminate between remaining hypotheses.
|
|
105
|
-
JSON
|
|
106
|
-
|
|
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.
|
|
107
108
|
3. ATTEMPT A FIX for your leading hypothesis in the same round. Instrumentation without
|
|
108
109
|
a fix is an incomplete round — the loop only advances when you fix something.
|
|
109
110
|
4. End the round by writing concise reproduction steps for the user (exact commands or
|
|
110
|
-
actions, what to observe). Then STOP — the user reproduces out-of-band and
|
|
111
|
-
|
|
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.
|
|
112
113
|
When told the fix is confirmed: remove every probe, verify with the list_debug_probes
|
|
113
114
|
tool that the ledger is empty, then summarize root cause and the final fix.`;
|
|
114
115
|
|
|
115
116
|
export default function debugModeExtension(pi: ExtensionAPI) {
|
|
116
117
|
const z = pi.zod;
|
|
117
118
|
const state: DebugState = freshState();
|
|
118
|
-
let
|
|
119
|
-
let logBuffer: LogEntry[] = [];
|
|
120
|
-
let pendingWrite: WriteStream | null = null;
|
|
119
|
+
let reviewMenuOpen = false;
|
|
121
120
|
let uiCtx: ExtensionContext | null = null;
|
|
122
121
|
|
|
123
|
-
// ============================== log
|
|
122
|
+
// ============================== log files ==============================
|
|
124
123
|
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
const dir = path.join(ctx.cwd, ".omp", "debug");
|
|
124
|
+
function initializeLogDirectory(ctx: ExtensionContext): boolean {
|
|
125
|
+
const dir = path.resolve(ctx.cwd, ".omp", "debug");
|
|
128
126
|
try {
|
|
129
127
|
fs.mkdirSync(dir, { recursive: true });
|
|
130
|
-
|
|
131
|
-
pi.logger.error("debug-mode: cannot create log dir", { dir, err });
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
state.debugDir = dir;
|
|
135
|
-
logBuffer = [];
|
|
136
|
-
try {
|
|
137
|
-
server = Bun.serve({
|
|
138
|
-
port,
|
|
139
|
-
hostname: "127.0.0.1",
|
|
140
|
-
idleTimeout: 5,
|
|
141
|
-
async fetch(req) {
|
|
142
|
-
const url = new URL(req.url);
|
|
143
|
-
if (req.method === "POST" && url.pathname === "/log") {
|
|
144
|
-
try {
|
|
145
|
-
const body = (await req.json()) as { probe?: unknown; data?: unknown };
|
|
146
|
-
if (typeof body.probe !== "string") return new Response("bad probe", { status: 400 });
|
|
147
|
-
const entry: LogEntry = {
|
|
148
|
-
run: state.runId ?? "orphan",
|
|
149
|
-
probe: body.probe,
|
|
150
|
-
ts: Date.now(),
|
|
151
|
-
data: body.data ?? null,
|
|
152
|
-
};
|
|
153
|
-
logBuffer.push(entry);
|
|
154
|
-
state.logCounts[entry.run] = (state.logCounts[entry.run] ?? 0) + 1;
|
|
155
|
-
pendingWrite?.write(JSON.stringify(entry) + "\n");
|
|
156
|
-
refreshUi();
|
|
157
|
-
return new Response("ok");
|
|
158
|
-
} catch {
|
|
159
|
-
return new Response("bad json", { status: 400 });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (req.method === "GET" && url.pathname === "/health") {
|
|
163
|
-
return new Response("ok");
|
|
164
|
-
}
|
|
165
|
-
return new Response("not found", { status: 404 });
|
|
166
|
-
},
|
|
167
|
-
});
|
|
128
|
+
state.debugDir = dir;
|
|
168
129
|
return true;
|
|
169
130
|
} catch (err) {
|
|
170
|
-
pi.logger.error("debug-mode:
|
|
171
|
-
|
|
131
|
+
pi.logger.error("debug-mode: cannot create log dir", { dir, err });
|
|
132
|
+
state.debugDir = null;
|
|
172
133
|
return false;
|
|
173
134
|
}
|
|
174
135
|
}
|
|
175
136
|
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
137
|
+
function readRunLines(run: string): string[] {
|
|
138
|
+
return readJsonlLines(logFileFor(state, run));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function refreshLogCounts(): void {
|
|
142
|
+
const runs = new Set(Object.keys(state.logCounts));
|
|
143
|
+
if (state.debugDir) {
|
|
144
|
+
try {
|
|
145
|
+
for (const file of fs.readdirSync(state.debugDir)) {
|
|
146
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
147
|
+
if (file === ACTIVE_LOG_FILE) {
|
|
148
|
+
if (state.runId) runs.add(state.runId);
|
|
149
|
+
} else {
|
|
150
|
+
runs.add(file.replace(/\.jsonl$/, ""));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {}
|
|
154
|
+
}
|
|
155
|
+
for (const run of runs) state.logCounts[run] = readRunLines(run).length;
|
|
181
156
|
}
|
|
182
157
|
|
|
183
|
-
function newRun(): string {
|
|
184
|
-
|
|
185
|
-
pendingWrite = null;
|
|
158
|
+
function newRun(): string | null {
|
|
159
|
+
if (!state.debugDir) return null;
|
|
186
160
|
const run = `run${state.round}-${Date.now().toString(36)}`;
|
|
161
|
+
try {
|
|
162
|
+
prepareRunLog(state.debugDir, state.runId);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
pi.logger.error("debug-mode: cannot initialize run log", {
|
|
165
|
+
file: path.join(state.debugDir, ACTIVE_LOG_FILE),
|
|
166
|
+
err,
|
|
167
|
+
});
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
187
170
|
state.runId = run;
|
|
188
171
|
state.logCounts[run] = 0;
|
|
189
|
-
if (state.debugDir) {
|
|
190
|
-
pendingWrite = fs.createWriteStream(path.join(state.debugDir, `${run}.jsonl`), { flags: "a" });
|
|
191
|
-
}
|
|
192
172
|
return run;
|
|
193
173
|
}
|
|
194
174
|
|
|
175
|
+
|
|
195
176
|
// ============================== probe ledger ==============================
|
|
196
177
|
|
|
178
|
+
|
|
197
179
|
pi.on("tool_call", async (event) => {
|
|
198
180
|
if (!state.active) return;
|
|
199
181
|
if (event.toolName !== "edit" && event.toolName !== "write") return;
|
|
@@ -205,8 +187,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
205
187
|
if (!state.probes.some(p => p.id === id)) state.probes.push({ id, file, round: state.round });
|
|
206
188
|
}
|
|
207
189
|
}
|
|
208
|
-
//
|
|
209
|
-
// rescans files for ground truth on demand.
|
|
190
|
+
// Removal detection rescans files for ground truth on demand.
|
|
210
191
|
});
|
|
211
192
|
|
|
212
193
|
/** Ground truth: which registered probes still exist in code. */
|
|
@@ -247,8 +228,9 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
247
228
|
ctx.ui.setStatus("debug-mode", ctx.ui.theme.fg("warning", label));
|
|
248
229
|
const lines: string[] = [];
|
|
249
230
|
if (state.phase === "waiting") {
|
|
231
|
+
refreshLogCounts();
|
|
250
232
|
const n = state.runId ? (state.logCounts[state.runId] ?? 0) : 0;
|
|
251
|
-
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"));
|
|
252
234
|
lines.push(ctx.ui.theme.fg("dim", `run ${state.runId} — ${n} log entries`));
|
|
253
235
|
}
|
|
254
236
|
ctx.ui.setWidget("debug-mode", lines.length ? lines : undefined);
|
|
@@ -293,17 +275,21 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
293
275
|
pi.on("session_stop", async (_event, ctx) => {
|
|
294
276
|
if (!state.active) return;
|
|
295
277
|
if (state.phase === "cleanup") {
|
|
296
|
-
//
|
|
297
|
-
if (state.cleanupReady) await teardown(ctx,
|
|
278
|
+
// Cleanup turn settled: keep fixes and remove the temporary logs.
|
|
279
|
+
if (state.cleanupReady) await teardown(ctx, "finished");
|
|
298
280
|
return;
|
|
299
281
|
}
|
|
300
282
|
if (state.phase !== "round" || !state.hasRoundContent) return;
|
|
301
283
|
state.phase = "waiting";
|
|
284
|
+
pi.appendEntry(DEBUG_ENTRY, { ...state });
|
|
302
285
|
refreshUi();
|
|
303
286
|
ctx.ui.notify(
|
|
304
|
-
`Debug round ${state.round} paused
|
|
287
|
+
`Debug round ${state.round} paused. Reproduce the bug, then choose an action or run /debug-review.`,
|
|
305
288
|
"info",
|
|
306
289
|
);
|
|
290
|
+
if (ctx.hasUI) {
|
|
291
|
+
void openReviewMenu(ctx).catch(err => pi.logger.warn("debug-mode: review menu failed", { err }));
|
|
292
|
+
}
|
|
307
293
|
});
|
|
308
294
|
|
|
309
295
|
pi.registerCommand("debug-mode", {
|
|
@@ -311,7 +297,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
311
297
|
handler: async (args, ctx) => {
|
|
312
298
|
uiCtx = ctx;
|
|
313
299
|
if (state.active) {
|
|
314
|
-
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");
|
|
315
301
|
return;
|
|
316
302
|
}
|
|
317
303
|
const problem = args.trim();
|
|
@@ -324,6 +310,10 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
324
310
|
});
|
|
325
311
|
|
|
326
312
|
function startDebug(ctx: ExtensionContext, problem: string): void {
|
|
313
|
+
if (!initializeLogDirectory(ctx)) {
|
|
314
|
+
ctx.ui.notify("debug-mode: could not create the debug log directory; debug mode was not started", "error");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
327
317
|
state.active = true;
|
|
328
318
|
state.phase = "round";
|
|
329
319
|
state.problem = problem;
|
|
@@ -331,72 +321,110 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
331
321
|
state.probes = [];
|
|
332
322
|
state.logCounts = {};
|
|
333
323
|
state.hasRoundContent = false;
|
|
334
|
-
if (!
|
|
335
|
-
|
|
324
|
+
if (!newRun()) {
|
|
325
|
+
Object.assign(state, freshState());
|
|
326
|
+
ctx.ui.notify("debug-mode: could not initialize the run log file; debug mode was not started", "error");
|
|
327
|
+
return;
|
|
336
328
|
}
|
|
337
|
-
newRun();
|
|
338
329
|
refreshUi();
|
|
339
330
|
pi.sendMessage(
|
|
340
331
|
{
|
|
341
332
|
customType: "debug-mode-start",
|
|
342
|
-
content: `Starting debug mode. Problem report:\n\n${problem}\n\nBegin round 1: explore, hypothesize, instrument, and attempt a fix. Then give reproduction steps and stop.`,
|
|
333
|
+
content: `Starting debug mode. Problem report:\n\n${problem}\n\nBegin round 1: explore, hypothesize, instrument with direct file writes, and attempt a fix. Then give reproduction steps and stop.`,
|
|
343
334
|
display: true,
|
|
344
335
|
},
|
|
345
336
|
{ triggerTurn: true },
|
|
346
337
|
);
|
|
347
338
|
}
|
|
348
339
|
|
|
349
|
-
function
|
|
340
|
+
function ensureWaiting(ctx: ExtensionContext): boolean {
|
|
350
341
|
if (!state.active) {
|
|
351
342
|
ctx.ui.notify("debug-mode: not active", "error");
|
|
352
|
-
return;
|
|
343
|
+
return false;
|
|
353
344
|
}
|
|
354
345
|
if (state.phase !== "waiting") {
|
|
355
346
|
ctx.ui.notify(`debug-mode: not waiting for reproduction (phase: ${state.phase})`, "error");
|
|
356
|
-
return;
|
|
347
|
+
return false;
|
|
357
348
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
`Probe ledger: ${JSON.stringify(state.probes)}`,
|
|
370
|
-
display: true,
|
|
371
|
-
},
|
|
372
|
-
{ triggerTurn: true },
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function markDebugFixed(ctx: ExtensionContext): Promise<void> {
|
|
353
|
+
if (!ensureWaiting(ctx)) return;
|
|
354
|
+
refreshLogCounts();
|
|
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?",
|
|
373
360
|
);
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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?",
|
|
395
391
|
);
|
|
392
|
+
if (!confirmed) return;
|
|
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;
|
|
396
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
|
+
);
|
|
397
425
|
}
|
|
398
426
|
|
|
399
|
-
async function teardown(ctx: ExtensionContext,
|
|
427
|
+
async function teardown(ctx: ExtensionContext, outcome: "finished" | "aborted"): Promise<void> {
|
|
400
428
|
if (state.debugDir) {
|
|
401
429
|
try {
|
|
402
430
|
fs.rmSync(state.debugDir, { recursive: true, force: true });
|
|
@@ -404,17 +432,67 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
404
432
|
pi.logger.warn("debug-mode: failed to remove debug dir", { err });
|
|
405
433
|
}
|
|
406
434
|
}
|
|
407
|
-
stopServer();
|
|
408
435
|
const probesLeft = await checkLedger();
|
|
409
436
|
Object.assign(state, freshState());
|
|
437
|
+
pi.appendEntry(DEBUG_ENTRY, { ...state });
|
|
410
438
|
refreshUi();
|
|
439
|
+
const resultLabel = outcome === "finished" ? "finished" : "aborted";
|
|
411
440
|
if (probesLeft.length > 0) {
|
|
412
441
|
ctx.ui.notify(
|
|
413
|
-
`
|
|
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.`,
|
|
414
443
|
"warning",
|
|
415
444
|
);
|
|
416
|
-
} else
|
|
417
|
-
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;
|
|
418
496
|
}
|
|
419
497
|
}
|
|
420
498
|
|
|
@@ -426,11 +504,34 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
426
504
|
handler: async (args, ctx) => {
|
|
427
505
|
uiCtx = ctx;
|
|
428
506
|
const verdict = args.trim().toLowerCase();
|
|
429
|
-
if (verdict
|
|
507
|
+
if (verdict === "fixed") {
|
|
508
|
+
await markDebugFixed(ctx);
|
|
509
|
+
} else if (verdict === "proceed") {
|
|
510
|
+
await advanceDebug(ctx);
|
|
511
|
+
} else {
|
|
430
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");
|
|
431
532
|
return;
|
|
432
533
|
}
|
|
433
|
-
|
|
534
|
+
await advanceDebug(ctx, details);
|
|
434
535
|
},
|
|
435
536
|
});
|
|
436
537
|
|
|
@@ -438,20 +539,7 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
438
539
|
description: "Abort debug mode: delete logs (fixes stay in the working diff)",
|
|
439
540
|
handler: async (_args, ctx) => {
|
|
440
541
|
uiCtx = ctx;
|
|
441
|
-
|
|
442
|
-
ctx.ui.notify("debug-mode: not active", "error");
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const ledger = await checkLedger();
|
|
446
|
-
await teardown(ctx, true);
|
|
447
|
-
if (ledger.length > 0) {
|
|
448
|
-
ctx.ui.notify(
|
|
449
|
-
`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.`,
|
|
450
|
-
"warning",
|
|
451
|
-
);
|
|
452
|
-
} else {
|
|
453
|
-
ctx.ui.notify("Debug mode aborted. No probes in code; applied fixes are kept in the working diff.", "info");
|
|
454
|
-
}
|
|
542
|
+
await abortDebug(ctx);
|
|
455
543
|
},
|
|
456
544
|
});
|
|
457
545
|
|
|
@@ -463,12 +551,14 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
463
551
|
ctx.ui.notify("debug-mode: idle", "info");
|
|
464
552
|
return;
|
|
465
553
|
}
|
|
554
|
+
refreshLogCounts();
|
|
466
555
|
const alive = await checkLedger();
|
|
467
556
|
ctx.ui.notify(
|
|
468
557
|
`debug-mode: phase=${state.phase} round=${state.round} run=${state.runId}\n` +
|
|
469
558
|
`probes (ledger ${state.probes.length}, alive ${alive.length}):\n` +
|
|
470
559
|
(alive.map(p => ` ${p.id} — ${p.file}`).join("\n") || " (none)") +
|
|
471
|
-
`\nlogs: ${Object.entries(state.logCounts).map(([r, n]) => `${r}=${n}`).join(", ") || "(none)"}
|
|
560
|
+
`\nlogs: ${Object.entries(state.logCounts).map(([r, n]) => `${r}=${n}`).join(", ") || "(none)"}` +
|
|
561
|
+
`\ncurrent log file: ${logFileFor(state) ?? "(not initialized)"}`,
|
|
472
562
|
"info",
|
|
473
563
|
);
|
|
474
564
|
},
|
|
@@ -476,11 +566,12 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
476
566
|
|
|
477
567
|
// ============================== tools ==============================
|
|
478
568
|
|
|
569
|
+
|
|
479
570
|
pi.registerTool({
|
|
480
571
|
name: "get_debug_logs",
|
|
481
572
|
label: "Get Debug Logs",
|
|
482
573
|
description:
|
|
483
|
-
"Read
|
|
574
|
+
"Read JSONL observations appended directly by debug-mode runtime probes. Each entry: {probe, ts, data}. Call with previous=true to analyze the last completed reproduction run.",
|
|
484
575
|
parameters: z.object({
|
|
485
576
|
run: z.string().optional().describe("Run id filter (default: current run)"),
|
|
486
577
|
probe: z.string().optional().describe("Probe id filter"),
|
|
@@ -488,33 +579,42 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
488
579
|
}),
|
|
489
580
|
approval: "read",
|
|
490
581
|
async execute(_toolCallId, params) {
|
|
582
|
+
refreshLogCounts();
|
|
491
583
|
const runs = Object.keys(state.logCounts);
|
|
492
584
|
let run = params.run;
|
|
493
|
-
if (!run && params.previous)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
} catch {
|
|
501
|
-
text = "";
|
|
585
|
+
if (!run && params.previous) {
|
|
586
|
+
run = runs[runs.length - 2];
|
|
587
|
+
if (!run) {
|
|
588
|
+
return {
|
|
589
|
+
content: [{ type: "text", text: "(no completed previous debug run is available)" }],
|
|
590
|
+
details: { run: null, file: null, count: 0 },
|
|
591
|
+
};
|
|
502
592
|
}
|
|
503
593
|
}
|
|
504
|
-
if (!
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
594
|
+
if (!run) run = state.runId ?? undefined;
|
|
595
|
+
|
|
596
|
+
let lines = run ? readRunLines(run) : [];
|
|
597
|
+
if (params.probe) {
|
|
598
|
+
lines = lines.filter(line => {
|
|
599
|
+
try {
|
|
600
|
+
const entry = JSON.parse(line) as { probe?: unknown };
|
|
601
|
+
return entry.probe === params.probe;
|
|
602
|
+
} catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
509
606
|
}
|
|
607
|
+
const text = lines.join("\n");
|
|
510
608
|
return {
|
|
511
609
|
content: [
|
|
512
610
|
{
|
|
513
611
|
type: "text",
|
|
514
|
-
text:
|
|
612
|
+
text:
|
|
613
|
+
text ||
|
|
614
|
+
"(no logs captured — the instrumented path may not have executed, the build may be stale, the path may be wrong, or the file append may have failed)",
|
|
515
615
|
},
|
|
516
616
|
],
|
|
517
|
-
details: { run: run ?? null,
|
|
617
|
+
details: { run: run ?? null, file: run ? logFileFor(state, run) : null, count: lines.length },
|
|
518
618
|
};
|
|
519
619
|
},
|
|
520
620
|
});
|
|
@@ -553,29 +653,22 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
553
653
|
.pop() as { data?: DebugState } | undefined;
|
|
554
654
|
if (last?.data?.active) {
|
|
555
655
|
Object.assign(state, last.data);
|
|
556
|
-
if (state.debugDir
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const lines = (await Bun.file(path.join(state.debugDir!, f)).text()).split("\n").filter(Boolean);
|
|
564
|
-
state.logCounts[run] = lines.length;
|
|
565
|
-
for (const l of lines) {
|
|
566
|
-
try {
|
|
567
|
-
logBuffer.push(JSON.parse(l) as LogEntry);
|
|
568
|
-
} catch {}
|
|
569
|
-
}
|
|
570
|
-
} catch {}
|
|
656
|
+
if (!state.debugDir || !fs.existsSync(state.debugDir)) initializeLogDirectory(ctx);
|
|
657
|
+
const currentFile = logFileFor(state);
|
|
658
|
+
if (currentFile) {
|
|
659
|
+
try {
|
|
660
|
+
const legacyRunFile = state.debugDir && state.runId ? path.join(state.debugDir, `${state.runId}.jsonl`) : null;
|
|
661
|
+
if (!fs.existsSync(currentFile) && legacyRunFile && fs.existsSync(legacyRunFile)) {
|
|
662
|
+
fs.renameSync(legacyRunFile, currentFile);
|
|
571
663
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
664
|
+
fs.writeFileSync(currentFile, "", { flag: "a" });
|
|
665
|
+
} catch (err) {
|
|
666
|
+
pi.logger.error("debug-mode: cannot restore run log", { file: currentFile, err });
|
|
575
667
|
}
|
|
576
668
|
}
|
|
669
|
+
refreshLogCounts();
|
|
577
670
|
ctx.ui.notify(
|
|
578
|
-
`debug-mode resumed: phase=${state.phase} round=${state.round}. 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.`,
|
|
579
672
|
"info",
|
|
580
673
|
);
|
|
581
674
|
}
|
|
@@ -586,9 +679,6 @@ export default function debugModeExtension(pi: ExtensionAPI) {
|
|
|
586
679
|
if (state.active) pi.appendEntry(DEBUG_ENTRY, { ...state });
|
|
587
680
|
});
|
|
588
681
|
|
|
589
|
-
pi.on("session_shutdown", async () => {
|
|
590
|
-
stopServer();
|
|
591
|
-
});
|
|
592
682
|
|
|
593
683
|
pi.setLabel("Debug Mode");
|
|
594
684
|
}
|
|
@@ -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;
|