@mjasnikovs/pi-task 0.17.23 → 0.17.25

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.
@@ -82,6 +82,18 @@ export interface VerificationDeps {
82
82
  * into the child's prompt. A/B-proven load-bearing: the prompt rule alone caught
83
83
  * the class 2/5, rule + probe finding 5/5. ABSENT or empty → no probe block. */
84
84
  probe?: () => Promise<string[]>;
85
+ /**
86
+ * Result of the git-state guard for the MOST RECENT runChild call (see
87
+ * git-state-guard.ts): did the child mutate repo state (stash/checkout/file
88
+ * rewrites), which the guard then restored? A verdict computed on a mutated
89
+ * tree is untrustworthy in BOTH directions — the mx5 run 6 child stashed the
90
+ * work away and judged an empty tree — so it is discarded: the first mutated
91
+ * run is retried once on the restored tree; a second mutation is a FAIL that
92
+ * names the behavior. ABSENT → no guard (tests / non-git repos), unchanged. */
93
+ mutationCheck?: () => {
94
+ mutated: boolean;
95
+ detail: string;
96
+ };
85
97
  }
86
98
  /**
87
99
  * Run the verification pass for one task. A missing spec is a pass. Otherwise run
@@ -320,6 +320,20 @@ export async function runWorkVerification(deps) {
320
320
  const msg = err instanceof Error ? err.message : String(err);
321
321
  return { ok: false, reason: `verification pass could not run: ${msg}` };
322
322
  }
323
+ // A child that mutated the repo (git-state guard fired) judged a tree it had
324
+ // itself changed — its verdict is meaningless in both directions, so discard
325
+ // it BEFORE parsing. The guard already restored the state, so one retry runs
326
+ // on the pristine tree; a child that mutates again is reported as the fault.
327
+ const mutation = deps.mutationCheck?.();
328
+ if (mutation?.mutated) {
329
+ if (attempt === 1)
330
+ continue;
331
+ return {
332
+ ok: false,
333
+ reason: 'verify child mutated repo state and its verdict was discarded '
334
+ + `(state restored: ${mutation.detail.slice(0, 200)})`
335
+ };
336
+ }
323
337
  const verdict = parseVerifyVerdict(text);
324
338
  if (verdict.pass)
325
339
  return { ok: true };
@@ -60,6 +60,13 @@ export function formatContextDetail(usage, theme) {
60
60
  return formatContextTokens(tokens);
61
61
  return null;
62
62
  }
63
+ /** The current-action string for the structured widget (the browser ellipsizes
64
+ * it to one line, so send it lightly capped rather than terminal-truncated). */
65
+ function widgetAction(lastLine) {
66
+ if (!lastLine)
67
+ return undefined;
68
+ return lastLine.length > 200 ? lastLine.slice(0, 199) + '…' : lastLine;
69
+ }
63
70
  /** Render the muted `↳ lastLine` trailer (truncated), or null when there's no line. */
64
71
  function lastLineTrailer(lastLine, theme) {
65
72
  if (!lastLine)
@@ -92,13 +99,17 @@ export function buildWidgetLines(s, theme) {
92
99
  export function buildWidgetData(s) {
93
100
  const idx = PHASE_INDEX[s.phase];
94
101
  const total = PHASE_ORDER.length;
95
- return {
102
+ const d = {
96
103
  title: `${s.taskId} · ${titleForDisplay(s)}`,
97
104
  phase: s.phase,
98
105
  done: Math.min(idx + 1, total),
99
106
  total,
100
107
  elapsed: formatDuration(Date.now() - s.startedAt)
101
108
  };
109
+ const action = widgetAction(s.lastLine);
110
+ if (action)
111
+ d.action = action;
112
+ return d;
102
113
  }
103
114
  // ─── Widget lifecycle ────────────────────────────────────────────────────────
104
115
  export function startWidget(ctx, getState) {
@@ -166,6 +177,9 @@ export function buildAutoLoaderData(s) {
166
177
  d.done = s.stepNum;
167
178
  d.total = s.stepTotal;
168
179
  }
180
+ const action = widgetAction(s.lastLine);
181
+ if (action)
182
+ d.action = action;
169
183
  return d;
170
184
  }
171
185
  /**
@@ -220,11 +234,15 @@ export function buildImplLines(s, theme) {
220
234
  /** Structured mirror of buildImplLines (the host implementation turn — no step
221
235
  * numbering, so no progress bar; just the phase badge and elapsed clock). */
222
236
  export function buildImplData(s) {
223
- return {
237
+ const d = {
224
238
  title: `${s.taskId} · ${titleForDisplay(s)}`,
225
239
  phase: 'implementing',
226
240
  elapsed: formatDuration(Date.now() - s.startedAt)
227
241
  };
242
+ const action = widgetAction(s.lastLine);
243
+ if (action)
244
+ d.action = action;
245
+ return d;
228
246
  }
229
247
  export function flashTerminalWidget(ctx, state, taskId, reason) {
230
248
  if (!ctx.hasUI)
@@ -0,0 +1,2 @@
1
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ export default function (pi: ExtensionAPI): void;
@@ -0,0 +1,6 @@
1
+ import { registerPiWorkerSearch } from './pi-worker-search.js';
2
+ import { registerPiWorkerFetch } from './pi-worker-fetch.js';
3
+ export default function (pi) {
4
+ registerPiWorkerSearch(pi);
5
+ registerPiWorkerFetch(pi);
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.17.23",
3
+ "version": "0.17.25",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",