@siuver/omp-debug-mode 0.1.5 → 0.1.7

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/src/machine.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseEvidencePlan } from "./evidence";
2
- import { decideGate, describeOpenReason } from "./gate";
2
+ import { type GateFacts, MAX_HANDOFF_NUDGES, MAX_PROBE_NUDGES, decideGate, describeHandoff } from "./gate";
3
3
  import {
4
4
  CLEANUP_NUDGE,
5
5
  buildFixedMessage,
@@ -12,6 +12,8 @@ import {
12
12
  type DebugState,
13
13
  type EvidenceArtifact,
14
14
  type EvidenceObservation,
15
+ type EvidenceRequest,
16
+ type HandoffMode,
15
17
  type Probe,
16
18
  type Round,
17
19
  INACTIVE,
@@ -21,8 +23,10 @@ import {
21
23
  declaresRuntimeProbe,
22
24
  evidenceSummary,
23
25
  freshSession,
24
- liveProbeIds,
26
+ liveProbeCount,
27
+ needsUserCapture,
25
28
  pendingRequests,
29
+ roundProbeIds,
26
30
  } from "./state";
27
31
 
28
32
  export const PROMPT_START = "debug-mode-start";
@@ -32,14 +36,25 @@ export const PROMPT_FIXED = "debug-mode-fixed";
32
36
  /** Artifact metadata gathered by the shell; the reducer only files it. */
33
37
  export type ArtifactCandidate = Omit<EvidenceArtifact, "id" | "addedAt" | "requestId">;
34
38
 
39
+ /** No nudges left: used when there is no agent turn to send anything back to. */
40
+ const SPENT_NUDGES = { handoff: MAX_HANDOFF_NUDGES, probes: MAX_PROBE_NUDGES };
41
+
35
42
  export type DebugEvent =
36
43
  | { t: "start"; problem: string; debugDir: string; runId: string; logFile: string }
37
44
  | { t: "turn_started" }
45
+ /** A user message landed in the transcript — the only thing that ends a user turn. */
46
+ | { t: "user_replied" }
38
47
  | { t: "assistant_message"; text: string }
48
+ /** Any tool call: the progress signal the reminder guard waits for. */
49
+ | { t: "tool_used" }
39
50
  | { t: "probes_found"; probes: Probe[] }
40
51
  | { t: "ledger_synced"; probes: Probe[] }
41
52
  | { t: "runs_observed"; runHistory: string[]; logCounts: Record<string, number> }
42
53
  | { t: "turn_settled" }
54
+ /** An explicit `hand_off_to_user` call: intent the machine can trust. */
55
+ | { t: "handoff"; mode: HandoffMode; steps: string[]; plan: EvidenceRequest[] | null }
56
+ /** The user takes the turn back from a round that never settled. */
57
+ | { t: "reclaim" }
43
58
  | { t: "proceed"; runId: string; logCount: number; hypotheses: string; details?: string; now: number }
44
59
  | { t: "mark_fixed" }
45
60
  | { t: "attach_artifact"; candidate: ArtifactCandidate; requestId: string | null; now: number }
@@ -94,18 +109,25 @@ export function reduce(state: DebugState, event: DebugEvent): Transition {
94
109
 
95
110
  switch (event.t) {
96
111
  case "turn_started":
97
- // A reply to an open round hands the turn back to the agent. The
98
- // reproduction gate deliberately survives ordinary conversation.
99
- if (session.stage !== "open" && !session.turnProduced) return unchanged(session);
100
- return unchanged({
101
- ...session,
102
- stage: session.stage === "open" ? "investigating" : session.stage,
103
- turnProduced: false,
104
- });
112
+ // Deliberately stage-blind. A turn can start without the user doing
113
+ // anything a todo reminder, a plan nudge or any other host
114
+ // continuation resumes the agent loop, which starts a turn while the
115
+ // round is still with the user. Reading "a turn began" as "the user
116
+ // replied" is what let those continuations take the user's turn away.
117
+ return session.turnProduced ? unchanged({ ...session, turnProduced: false }) : unchanged(session);
118
+
119
+ case "user_replied":
120
+ // A reply at a handoff hands the round back to the agent. Keeping the
121
+ // user stage through the turn would make the entire turn invisible to
122
+ // the machine and silently discard the plan and steps it produced.
123
+ return unchanged(session.stage === "user_turn" ? reopenRound(session) : session);
105
124
 
106
125
  case "assistant_message":
107
126
  return unchanged(absorbAssistantText(session, event.text));
108
127
 
128
+ case "tool_used":
129
+ return unchanged(clearNudgeWait(session));
130
+
109
131
  case "probes_found": {
110
132
  const fresh = event.probes.filter(probe => !session.probes.some(known => known.id === probe.id));
111
133
  if (fresh.length === 0) return unchanged(session);
@@ -130,6 +152,12 @@ export function reduce(state: DebugState, event: DebugEvent): Transition {
130
152
  case "turn_settled":
131
153
  return settle(session);
132
154
 
155
+ case "handoff":
156
+ return applyHandoff(session, event);
157
+
158
+ case "reclaim":
159
+ return reclaim(session);
160
+
133
161
  case "proceed":
134
162
  return advance(session, event);
135
163
 
@@ -149,6 +177,31 @@ export function reduce(state: DebugState, event: DebugEvent): Transition {
149
177
  }
150
178
  }
151
179
 
180
+ /**
181
+ * Give the round back to the agent. Nudge budgets are per agent turn rather
182
+ * than per round: a round the user replied to must still be repairable, or one
183
+ * spent budget would silence every later turn in the same round.
184
+ */
185
+ function reopenRound(session: ActiveState): ActiveState {
186
+ const round = currentRound(session);
187
+ return withRound(
188
+ { ...session, stage: "investigating", turnProduced: false },
189
+ { ...round, handoff: null, nudges: { handoff: 0, probes: 0 }, awaitingProgress: false },
190
+ );
191
+ }
192
+
193
+ /**
194
+ * Clear the reminder guard. A tool call means the agent acted on the reminder
195
+ * instead of restating itself, so the next settle may remind it again; a turn
196
+ * that only produces more prose keeps the guard set and is handed over.
197
+ */
198
+ function clearNudgeWait(session: ActiveState): ActiveState {
199
+ if (session.stage !== "investigating") return session;
200
+ const round = currentRound(session);
201
+ if (!round.awaitingProgress) return session;
202
+ return withRound(session, { ...round, awaitingProgress: false });
203
+ }
204
+
152
205
  /** Record the round artefacts a settled-but-not-yet-judged turn produced. */
153
206
  function absorbAssistantText(session: ActiveState, text: string): ActiveState {
154
207
  const round = currentRound(session);
@@ -157,70 +210,104 @@ function absorbAssistantText(session: ActiveState, text: string): ActiveState {
157
210
  const next: Round = {
158
211
  ...round,
159
212
  reproductionSteps: steps.length > 0 ? steps : round.reproductionSteps,
160
- plan: plan.found ? (plan.valid ? plan.requests : null) : round.plan,
213
+ // A later malformed block must never erase a plan that already validated:
214
+ // the round would lose evidence requests the user was already shown.
215
+ plan: plan.valid ? plan.requests : round.plan,
161
216
  };
162
217
  const roundChanged = next.reproductionSteps !== round.reproductionSteps || next.plan !== round.plan;
163
218
  if (!roundChanged) return session.turnProduced ? session : { ...session, turnProduced: true };
164
219
  return withRound({ ...session, turnProduced: true }, next);
165
220
  }
166
221
 
222
+ function gateFacts(session: ActiveState, round: Round): GateFacts {
223
+ return {
224
+ round: round.index,
225
+ hasReproductionSteps: round.reproductionSteps.length > 0,
226
+ hasEvidencePlan: round.plan !== null,
227
+ declaresRuntimeProbe: declaresRuntimeProbe(round),
228
+ needsUserCapture: needsUserCapture(round),
229
+ liveProbes: liveProbeCount(session),
230
+ probesAddedThisRound: roundProbeIds(session, round).length,
231
+ nudges: round.nudges,
232
+ awaitingProgress: round.awaitingProgress,
233
+ };
234
+ }
235
+
167
236
  function settle(session: ActiveState): Transition {
168
237
  if (session.stage === "cleaning_up") return finishCleanup(session);
169
238
  if (session.stage !== "investigating" || !session.turnProduced) return unchanged(session);
170
239
 
171
240
  const round = currentRound(session);
172
- const decision = decideGate({
173
- hasReproductionSteps: round.reproductionSteps.length > 0,
174
- hasEvidencePlan: round.plan !== null,
175
- declaresRuntimeProbe: declaresRuntimeProbe(round),
176
- liveProbes: liveProbeIds(session, round).length,
177
- nudges: round.nudges,
178
- });
241
+ const decision = decideGate(gateFacts(session, round));
179
242
 
180
243
  if (decision.kind === "nudge") {
181
244
  const nudges =
182
245
  decision.budget === "probes"
183
246
  ? { ...round.nudges, probes: round.nudges.probes + 1 }
184
- : { ...round.nudges, tags: round.nudges.tags + 1 };
247
+ : { ...round.nudges, handoff: round.nudges.handoff + 1 };
185
248
  return {
186
- state: withRound(session, { ...round, nudges }),
249
+ state: withRound(session, { ...round, nudges, awaitingProgress: true }),
187
250
  effects: [{ kind: "continue", context: decision.context }],
188
251
  };
189
252
  }
190
253
 
191
- if (decision.kind === "open") {
192
- return {
193
- state: withRound({ ...session, stage: "open" }, { ...round, openReason: decision.reason }),
194
- effects: [
195
- {
196
- kind: "notify",
197
- level: decision.reason === "awaiting_reply" ? "info" : "warning",
198
- text: describeOpenReason(decision.reason, round.index),
199
- },
200
- ],
201
- };
202
- }
254
+ return handOff(session, decision.mode, decision.missingPlan);
255
+ }
203
256
 
204
- const gated = withRound({ ...session, stage: "awaiting_evidence" }, { ...round, openReason: null });
205
- return { state: gated, effects: [{ kind: "notify", ...gateNotice(gated, decision.missingPlan) }] };
257
+ /**
258
+ * Move the round to the user. Every settled turn ends here: the mode only
259
+ * changes the wording, so a mode the agent misjudged can never make a command
260
+ * illegal. The notify text leads with the same sentence as the widget so the
261
+ * transcript and the widget cannot disagree about whose move it is.
262
+ */
263
+ function handOff(session: ActiveState, mode: HandoffMode, missingPlan: boolean): Transition {
264
+ const round = currentRound(session);
265
+ const next = withRound({ ...session, stage: "user_turn" }, { ...round, handoff: mode });
266
+ const pending = pendingRequests(next);
267
+ const detail = pending.length > 0 ? ` Pending: ${pending.map(request => request.id).join(", ")}.` : "";
268
+ return {
269
+ state: next,
270
+ effects: [
271
+ {
272
+ kind: "notify",
273
+ level: mode === "incomplete" || missingPlan ? "warning" : "info",
274
+ text: `${describeHandoff(mode, round.index, missingPlan)}${detail}`,
275
+ },
276
+ ],
277
+ };
206
278
  }
207
279
 
208
- function gateNotice(session: ActiveState, missingPlan: boolean): { level: "info" | "warning"; text: string } {
280
+ /**
281
+ * An explicit handoff call. The tool has already validated its arguments, so
282
+ * this records the declared plan and steps and hands over without consulting
283
+ * the gate: the agent said what it wants, and guessing from its prose is the
284
+ * failure mode the tool exists to remove.
285
+ */
286
+ function applyHandoff(session: ActiveState, event: Extract<DebugEvent, { t: "handoff" }>): Transition {
287
+ if (session.stage !== "investigating") return unchanged(session);
209
288
  const round = currentRound(session);
210
- if (missingPlan) {
211
- return {
212
- level: "warning",
213
- text: `Debug round ${round.index} paused, but it added no probes and declared no evidence plan — this round cannot produce runtime evidence. Use /debug-proceed to ask for instrumentation.`,
214
- };
215
- }
216
- const pending = pendingRequests(session, round);
217
- if (pending.length > 0) {
218
- return {
219
- level: "info",
220
- text: `Debug round ${round.index} paused. User evidence requested (${pending.length} pending: ${pending.map(r => r.id).join(", ")}) — attach via /debug-evidence <request-id> <path>, then press Proceed.`,
221
- };
222
- }
223
- return { level: "info", text: `Debug round ${round.index} paused. Reproduce the bug, then press Proceed.` };
289
+ const updated: Round = {
290
+ ...round,
291
+ plan: event.plan ?? round.plan,
292
+ reproductionSteps: event.steps.length > 0 ? event.steps : round.reproductionSteps,
293
+ };
294
+ const staged = withRound({ ...session, turnProduced: true }, updated);
295
+ const expectsPlan = event.mode !== "question";
296
+ return handOff(staged, event.mode, expectsPlan && updated.plan === null);
297
+ }
298
+
299
+ /**
300
+ * Classify a round whose turn never settled. An aborted turn never reaches
301
+ * `session_stop`, so without this the stage would stay `investigating` and
302
+ * every debug command would refuse for the rest of the session.
303
+ */
304
+ function reclaim(session: ActiveState): Transition {
305
+ if (session.stage !== "investigating") return unchanged(session);
306
+ const round = currentRound(session);
307
+ // There is no turn left to continue, so no nudge is available.
308
+ const decision = decideGate({ ...gateFacts(session, round), nudges: SPENT_NUDGES });
309
+ if (decision.kind === "nudge") return handOff(session, "incomplete", round.plan === null);
310
+ return handOff(session, decision.mode, decision.missingPlan);
224
311
  }
225
312
 
226
313
  /** Cleanup may only finish once the ledger is actually empty. */
package/src/main.ts CHANGED
@@ -1,24 +1,24 @@
1
- /**
2
- * Debug Mode Extension — Cursor-style human-in-the-loop debugging for omp.
3
- *
4
- * State machine (matches Cursor Debug Mode):
5
- * IDLE → /debug-mode <problem>
6
- * → agent writes 3-5 hypotheses and @omp-probe instrumentation (no product fix)
7
- * → WAITING_REPRO: agent stops; user reproduces out-of-band
8
- * → Proceed/add details → agent reads logs, evaluates hypotheses, fixes only
9
- * with evidence, keeps probes, and asks for a verification reproduce → loop
10
- * → Mark as fixed → agent removes probes + summarizes → teardown (logs deleted)
11
- *
12
- * - `state.ts` — persisted round state and the injected blackboard
13
- * - `probes.ts` — `@omp-probe` ledger and its on-disk ground truth
14
- * - `log-files.ts` — stable `<cwd>/.omp/debug/current.jsonl` and run archival
15
- * - `methodology.ts` — the Cursor Debug Mode prompt contract
16
- * - `debug-mode.ts` — state machine, commands, and lifecycle wiring
17
- */
18
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
19
- import { registerDebugMode } from "./debug-mode";
20
-
21
- export default function debugModeExtension(pi: ExtensionAPI) {
22
- pi.setLabel("Debug Mode");
23
- registerDebugMode(pi);
24
- }
1
+ /**
2
+ * Debug Mode Extension — Cursor-style human-in-the-loop debugging for omp.
3
+ *
4
+ * State machine (matches Cursor Debug Mode):
5
+ * IDLE → /debug-mode <problem>
6
+ * → agent writes 3-5 hypotheses and @omp-probe instrumentation (no product fix)
7
+ * → WAITING_REPRO: agent stops; user reproduces out-of-band
8
+ * → /debug-proceed [details] → agent reads logs, evaluates hypotheses, fixes only
9
+ * with evidence, keeps probes, and asks for a verification reproduce → loop
10
+ * → /debug-done → agent removes probes + summarizes → teardown (logs deleted)
11
+ *
12
+ * - `state.ts` — persisted round state and the injected blackboard
13
+ * - `probes.ts` — `@omp-probe` ledger and its on-disk ground truth
14
+ * - `log-files.ts` — stable `<cwd>/.omp/debug/current.jsonl` and run archival
15
+ * - `methodology.ts` — the Cursor Debug Mode prompt contract
16
+ * - `debug-mode.ts` — state machine, commands, and lifecycle wiring
17
+ */
18
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
19
+ import { registerDebugMode } from "./debug-mode";
20
+
21
+ export default function debugModeExtension(pi: ExtensionAPI) {
22
+ pi.setLabel("Debug Mode");
23
+ registerDebugMode(pi);
24
+ }
@@ -1,7 +1,36 @@
1
- export const PROCEED_REMINDER = "Press Proceed/Mark as fixed when done.";
1
+ /**
2
+ * The widget under the editor names every command already, so prose that
3
+ * repeats them is the same instruction three times on one screen: the call to
4
+ * action, the model's own closing line, and the actions strip all said
5
+ * `/debug-proceed`. A model told to "tell the user to run /debug-proceed" also
6
+ * writes it into `<reproduction_steps>`, where it becomes a numbered step the
7
+ * user cannot perform until every other step is done.
8
+ */
9
+ export const COMMAND_ECHO_RULE =
10
+ "Do NOT name /debug-proceed or /debug-done in your closing prose or inside <reproduction_steps>: the widget " +
11
+ "under the user's editor already offers both, and repeating them puts the same instruction on screen three " +
12
+ "times. Close with one short line saying what to do — reproduce, capture, answer — never which command to type.";
2
13
 
3
14
  export const EVIDENCE_PLAN_TAG = "evidence_plan";
4
15
 
16
+ /** The Agent tool that ends an agent turn and hands the round to the user. */
17
+ export const HANDOFF_TOOL = "hand_off_to_user";
18
+
19
+ /**
20
+ * Stated wherever a round is closed. Prose cannot move the session: the model
21
+ * announcing a reproduction is not the same event as the session reaching the
22
+ * user, and conflating them is what leaves a round unclosed while the user has
23
+ * already been told to act.
24
+ */
25
+ export const HANDOFF_RULE =
26
+ `Close every round by calling ${HANDOFF_TOOL} as your last action. Pass mode ("reproduce" when the user must run ` +
27
+ 'the app, "capture" when they only supply a report or file, "question" when you need an answer before you can ' +
28
+ "plan), the evidence plan array, and the numbered steps the USER performs now. That tool call is the only thing " +
29
+ "that hands the session to the user, and it replies with exactly what to fix when an argument is wrong, so a " +
30
+ `rejected call is repaired in this turn. <${EVIDENCE_PLAN_TAG}> and <reproduction_steps> in prose are still read ` +
31
+ "for their contents, but they never close a round: a turn that stops without the tool call is sent straight back " +
32
+ "to you to make it.";
33
+
5
34
  /** The least-user-intervention evidence method ordering, verbatim for prompts. */
6
35
  export const MINIMIZE_USER_INTERVENTION =
7
36
  "MINIMIZE USER INTERVENTION. Choose the cheapest reliable evidence method per hypothesis, in this exact order: " +
@@ -60,9 +89,10 @@ This is OMP Debug Mode. Follow the steps in order. Do not skip them.
60
89
  - The extension truncates the current log file at the start of each round.
61
90
  Do not delete, rename, or overwrite that file yourself.
62
91
 
63
- 4. Close the round only after step 3 is done for any runtime_probe plan. Emit
64
- exactly one <${EVIDENCE_PLAN_TAG}> block containing a
65
- non-empty JSON array covering EVERY hypothesis:
92
+ 4. Close the round only after step 3 is done for any runtime_probe plan.
93
+ ${HANDOFF_RULE}
94
+ The plan is a non-empty JSON array covering EVERY hypothesis, in the same
95
+ shape accepted inside a legacy <${EVIDENCE_PLAN_TAG}> block:
66
96
  <${EVIDENCE_PLAN_TAG}>
67
97
  [{"id":"E1","hypothesisIds":["A","B"],"method":"runtime_probe","title":"...","rationale":"The disputed runtime branches are not present in existing logs; one model-added probe set can capture both without a separate user artifact.","instructions":["..."],"artifactHint":"optional"}]
68
98
  </${EVIDENCE_PLAN_TAG}>
@@ -70,18 +100,22 @@ This is OMP Debug Mode. Follow the steps in order. Do not skip them.
70
100
  Use artifactHint only for user_artifact (expected file kind). Give actionable
71
101
  numbered capture/report instructions in instructions.
72
102
 
73
- 5. Ask the user to reproduce (or capture/report, per the plan). End your response
74
- with a <reproduction_steps> numbered list (no header inside the tag) describing
75
- the single combined reproduction/capture sequence the USER performs now, and
76
- this exact sentence after the tag: "${PROCEED_REMINDER}"
103
+ 5. Ask the user to reproduce (or capture/report, per the plan). Pass the single
104
+ combined reproduction/capture sequence the USER performs now as the steps
105
+ argument of ${HANDOFF_TOOL} the widget the user reads is built from that
106
+ call, not from your prose. A <reproduction_steps> numbered list (no header
107
+ inside the tag) is read as content but closes nothing, so make the call even
108
+ when you also wrote the prose.
77
109
  ${CLOSE_ROUND_RULES}
78
- Never say "click". Never ask the user to reply "done". Remind them to restart
79
- the app or service if the instrumented code would otherwise be stale.
110
+ ${COMMAND_ECHO_RULE}
111
+ Never say "click" or "press Proceed": those buttons do not exist, and never
112
+ ask the user to reply "done". Remind them to restart the app or service if
113
+ the instrumented code would otherwise be stale.
80
114
  Then STOP. The user reproduces out-of-band.
81
115
  No logs may be expected for user_report/user_artifact plans: evaluate the
82
116
  requested user evidence instead of treating absent probes as a failed round.
83
117
 
84
- 6. After Proceed: call list_debug_evidence, then read logs with get_debug_logs
118
+ 6. After /debug-proceed: call list_debug_evidence, then read logs with get_debug_logs
85
119
  (previous=true for the completed run). Evaluate EACH hypothesis as CONFIRMED,
86
120
  REJECTED, or INCONCLUSIVE citing the selected evidence method: hypothesis ID
87
121
  plus log-line numbers, a submitted observation, or an attached artifact/report
@@ -98,7 +132,7 @@ This is OMP Debug Mode. Follow the steps in order. Do not skip them.
98
132
  8. After a fix, ask the user to reproduce again. Compare before/after logs with
99
133
  cited entries. Do not claim success without that proof.
100
134
 
101
- 9. If verification proves success and the user chooses Mark as fixed: remove
135
+ 9. If verification proves success and the user runs /debug-done: remove
102
136
  every probe, verify with list_debug_probes that the ledger is empty, then
103
137
  summarize the root cause and the final fix in 1-2 lines.
104
138
 
@@ -121,9 +155,12 @@ export function buildStartMessage(problem: string, logFile: string): string {
121
155
  "Begin round 1: generate 3-5 precise hypotheses, decide an evidence method for each, and do NOT apply a product fix yet. " +
122
156
  CLOSE_ROUND_RULES +
123
157
  " " +
158
+ HANDOFF_RULE +
159
+ " " +
124
160
  "Call list_debug_evidence whenever you need the request/observation/artifact ledger. " +
125
161
  "Cite evidence as hypothesis ID plus log-line number or attached observation/artifact/report path. " +
126
- `Then close with exactly one <${EVIDENCE_PLAN_TAG}> JSON block, then <reproduction_steps> and "${PROCEED_REMINDER}" then STOP.`
162
+ `Then close with ${HANDOFF_TOOL} prose alone never closes a round, so make the call even if you also wrote ` +
163
+ `a <${EVIDENCE_PLAN_TAG}> block. ${COMMAND_ECHO_RULE} Then STOP.`
127
164
  );
128
165
  }
129
166
 
@@ -158,7 +195,9 @@ export function buildProceedMessage(args: {
158
195
  "If a previous fix failed, first revert code changes from rejected hypotheses. " +
159
196
  CLOSE_ROUND_RULES +
160
197
  " " +
161
- `Then end with <${EVIDENCE_PLAN_TAG}>, <reproduction_steps> and "${PROCEED_REMINDER}" then STOP.`
198
+ HANDOFF_RULE +
199
+ " " +
200
+ `Then end with ${HANDOFF_TOOL} — prose alone never closes a round. ${COMMAND_ECHO_RULE} Then STOP.`
162
201
  );
163
202
  }
164
203
 
@@ -174,7 +213,9 @@ The user confirmed the fix. Only two things remain:
174
213
  \`#region agent log\` wrapper, then call list_debug_probes and confirm the
175
214
  ledger is empty. Keep the proven fix; remove nothing else.
176
215
  2. Summarize in 1-2 lines: the root cause and the fix that is staying.
177
- Do not add probes, form new hypotheses, or ask for another reproduction.`;
216
+ Do not add probes, form new hypotheses, or ask for another reproduction, and do
217
+ not call ${HANDOFF_TOOL}: cleanup ends when the ledger is empty and you have
218
+ summarized.`;
178
219
 
179
220
  /** Sent back into a cleanup turn that ended with probes still in the code. */
180
221
  export const CLEANUP_NUDGE =