pi-goal-list-loop-audit 0.15.0 → 0.15.1

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.
@@ -275,9 +275,29 @@ export function buildSeedGrillMessage(tmpl: string, seed: string, tool: string):
275
275
  * mechanism guarantees an interview HAPPENED; question quality is the
276
276
  * model's job (shaped by buildSeedGrillMessage).
277
277
  */
278
- export function draftProposalBlock(userReplies: number): string | null {
278
+ export function draftProposalBlock(userReplies: number, blockedAttempts = 0): string | null {
279
279
  if (userReplies > 0) return null;
280
- return "INTERVIEW FIRST — you have not received a single user reply since drafting started. Ask the user ONE sharp question about their objective (seed-specific, with a recommended default; challenge non-answers by offering concrete options), wait for the answer, and only then call the propose tool again. The Confirm dialog stays closed until the user has actually been heard.";
280
+ const base = "INTERVIEW FIRST — you have not received a single user reply since drafting started. Ask the user ONE sharp question about their objective (seed-specific, with a recommended default; challenge non-answers by offering concrete options), wait for the answer, and only then call the propose tool again. The Confirm dialog stays closed until the user has actually been heard.";
281
+ // v0.15.1 escape hatch: typed chat replies AND answered ask_user_question
282
+ // dialogs both count. If we have blocked 3+ proposals, the replies are
283
+ // arriving through a path this plugin cannot see — hand the user a manual
284
+ // unlock instead of manufacturing yet another interview round.
285
+ if (blockedAttempts >= 3) {
286
+ return base + " NOTE: proposals have been blocked repeatedly despite interviewing — the reply counter may not see your channel. Tell the user plainly: 'type any chat message (e.g. \"go on\") to unlock the Confirm dialog', wait for it, then propose again. Do NOT ask another interview question first.";
287
+ }
288
+ return base;
289
+ }
290
+
291
+ /**
292
+ * v0.15.1: an ask_user_question tool result counts as a user reply during
293
+ * drafting — dialog answers arrive as tool results, not chat messages.
294
+ * Answered = not cancelled (Esc) with at least one answer recorded.
295
+ */
296
+ export function askUserQuestionAnswered(toolName: string, details: unknown): boolean {
297
+ if (toolName !== "ask_user_question") return false;
298
+ if (!details || typeof details !== "object") return false;
299
+ const d = details as { answers?: unknown; cancelled?: unknown };
300
+ return d.cancelled === false && Array.isArray(d.answers) && d.answers.length > 0;
281
301
  }
282
302
 
283
303
  /**
@@ -39,6 +39,7 @@ import {
39
39
  takeAt,
40
40
  goalArgsNeedDrafting,
41
41
  buildSeedGrillMessage,
42
+ askUserQuestionAnswered,
42
43
  draftProposalBlock,
43
44
  type TaskProposal,
44
45
  validateTaskProposal,
@@ -110,6 +111,7 @@ let draftingTarget: "goal" | "list" | "loop" | null = null;
110
111
  // v0.14.0 drafting floor: user replies counted while drafting; the injected
111
112
  // seed prompt itself arrives as a user message — skip exactly that one.
112
113
  let draftingUserReplies = 0;
114
+ let draftingBlockedProposals = 0; // v0.15.1: stuck-gate escape hatch
113
115
  let draftingSeedInFlight = false;
114
116
 
115
117
  // Dedup set for token accounting (agent_end may replay seen messages).
@@ -458,6 +460,7 @@ async function startDrafting(ctx: ExtensionContext, target: "goal" | "list" | "l
458
460
  try {
459
461
  extensionApi?.sendUserMessage(tmpl, { deliverAs: ctx.isIdle() ? "followUp" : "steer" });
460
462
  draftingUserReplies = 0;
463
+ draftingBlockedProposals = 0;
461
464
  draftingSeedInFlight = true; // our injected prompt also arrives as a user message — don't count it
462
465
  } catch {
463
466
  draftingTarget = null;
@@ -1420,7 +1423,9 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1420
1423
  };
1421
1424
  }
1422
1425
  // v0.14.0: the interview floor — no Confirm until the user replied.
1423
- const block = draftProposalBlock(draftingUserReplies);
1426
+ // v0.14.0: the interview floor — no Confirm until the user replied.
1427
+ if (draftingUserReplies === 0) draftingBlockedProposals++;
1428
+ const block = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
1424
1429
  if (block) {
1425
1430
  return { content: [{ type: "text", text: block }], details: {} };
1426
1431
  }
@@ -1525,7 +1530,8 @@ function registerAgentTools(pi: any, ctx: ExtensionContext): void {
1525
1530
  };
1526
1531
  }
1527
1532
  // v0.14.0: the interview floor — no Confirm until the user replied.
1528
- const loopBlock = draftProposalBlock(draftingUserReplies);
1533
+ if (draftingUserReplies === 0) draftingBlockedProposals++;
1534
+ const loopBlock = draftProposalBlock(draftingUserReplies, draftingBlockedProposals);
1529
1535
  if (loopBlock) {
1530
1536
  return { content: [{ type: "text", text: loopBlock }], details: {} };
1531
1537
  }
@@ -2188,6 +2194,15 @@ export default function (pi: ExtensionAPI): void {
2188
2194
  draftingUserReplies++;
2189
2195
  });
2190
2196
 
2197
+ // v0.15.1: ask_user_question answers arrive as tool results, not chat
2198
+ // messages — count answered (non-cancelled) questionnaires as replies too.
2199
+ pi.on("tool_result", async (event: any) => {
2200
+ if (draftingTarget === null) return;
2201
+ if (askUserQuestionAnswered(String(event?.toolName ?? ""), event?.details)) {
2202
+ draftingUserReplies++;
2203
+ }
2204
+ });
2205
+
2191
2206
  pi.on("session_start", async (_event: any, ctx: ExtensionContext) => {
2192
2207
  rememberCtx(ctx);
2193
2208
  state = readState(ctx.cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goal-list-loop-audit",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Goal. Loop. Audit. Done. — a pi-coding-agent extension that supervises long-running work, with isolated auditor on each completion. Beat bamboozling by design: the auditor runs in a fresh session with no extensions, no skills, no editor — only the read tools needed to verify your goal.",
5
5
  "license": "MIT",
6
6
  "author": "dracon",