frizz-server 0.15.7 → 0.15.8

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.
Files changed (102) hide show
  1. package/dist/acp-daemon.js +27 -3
  2. package/dist/claude-agent-broker.js +27 -21
  3. package/dist/codex-app-server-daemon.js +27 -3
  4. package/dist/dev-child.js +421 -155
  5. package/package.json +1 -1
  6. package/runtime/cc-worker/bin/frizz-mcp.mjs +21 -17
  7. package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
  8. package/web-dist/assets/{abnfDiagram-VRR7QNED-DwSF6UVy.js → abnfDiagram-VRR7QNED-5ei_4zAM.js} +1 -1
  9. package/web-dist/assets/{arc-BO1i7GWM.js → arc-IM-a48Qs.js} +1 -1
  10. package/web-dist/assets/architecture-TIHT7OUA-bSjZeCDP.js +1 -0
  11. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-msNhjgsP.js → architectureDiagram-ZJ3FMSHR-2XGTphLh.js} +1 -1
  12. package/web-dist/assets/{blockDiagram-677ZJIJ3-JCCwJqsE.js → blockDiagram-677ZJIJ3-DZM9KutE.js} +1 -1
  13. package/web-dist/assets/{c4Diagram-LMCZKHZV-B5vS6rLH.js → c4Diagram-LMCZKHZV-CKa_m4-S.js} +1 -1
  14. package/web-dist/assets/channel-CZsSDpaO.js +1 -0
  15. package/web-dist/assets/{chunk-32BRIVSS-BeH_kPjp.js → chunk-32BRIVSS-BLSMcIRY.js} +1 -1
  16. package/web-dist/assets/{chunk-52WLFC77-D0OtliIj.js → chunk-52WLFC77-DevWfx8Y.js} +1 -1
  17. package/web-dist/assets/{chunk-C7G6YPKG-wbaKYBGi.js → chunk-C7G6YPKG-CsLDw_Vc.js} +1 -1
  18. package/web-dist/assets/{chunk-EX3LRPZG-JrdwUCV4.js → chunk-EX3LRPZG-CAVfNVN8.js} +1 -1
  19. package/web-dist/assets/{chunk-FWX5IMBZ-dk5peTlq.js → chunk-FWX5IMBZ-ba3X5gAQ.js} +2 -2
  20. package/web-dist/assets/{chunk-HOUHSVGY-DEVMSmR0.js → chunk-HOUHSVGY-Bm5rhDGu.js} +1 -1
  21. package/web-dist/assets/{chunk-ICXQ74PX-Q4B4QfXk.js → chunk-ICXQ74PX-DKF-sXIi.js} +1 -1
  22. package/web-dist/assets/{chunk-MOJQB5TN-CFljAhrA.js → chunk-MOJQB5TN-19qz3xL4.js} +1 -1
  23. package/web-dist/assets/{chunk-OGEWGWER-uDxaNMkK.js → chunk-OGEWGWER-DLdOho5W.js} +1 -1
  24. package/web-dist/assets/{chunk-PUDLZKDR-B1_PcfSW.js → chunk-PUDLZKDR-C1Nj5k8G.js} +1 -1
  25. package/web-dist/assets/{chunk-Q4XR5HBZ-CTG5ESkU.js → chunk-Q4XR5HBZ-CPx-9pEy.js} +1 -1
  26. package/web-dist/assets/{chunk-RYQCIY6F-C4Xbw0h0.js → chunk-RYQCIY6F-BVfqVyNV.js} +1 -1
  27. package/web-dist/assets/{chunk-V7JOEXUC-DzX7WzjT.js → chunk-V7JOEXUC-Bq5EG6OC.js} +1 -1
  28. package/web-dist/assets/{chunk-VAUOI2AC-C5BV4_RN.js → chunk-VAUOI2AC-CyfJDqu-.js} +1 -1
  29. package/web-dist/assets/{chunk-VR4S4FIN-WYsy9hUs.js → chunk-VR4S4FIN-DMki15_r.js} +1 -1
  30. package/web-dist/assets/{chunk-WYO6CB5R-D-1-l4MF.js → chunk-WYO6CB5R-C30gZnnl.js} +1 -1
  31. package/web-dist/assets/{chunk-XXDRQBXY-C9lzowcJ.js → chunk-XXDRQBXY-CP4D_ETY.js} +1 -1
  32. package/web-dist/assets/{chunk-ZGVPDNZ5-7KVpod10.js → chunk-ZGVPDNZ5-k3kNBN88.js} +1 -1
  33. package/web-dist/assets/classDiagram-OUVF2IWQ-he8mN_1d.js +1 -0
  34. package/web-dist/assets/classDiagram-v2-EOCWNBFH-he8mN_1d.js +1 -0
  35. package/web-dist/assets/{cose-bilkent-JH36ORCC-Cjxk1BZr.js → cose-bilkent-JH36ORCC-wO3EsovI.js} +1 -1
  36. package/web-dist/assets/{cynefin-VYW2F7L2-wOe2bHzn.js → cynefin-VYW2F7L2-C3EU9o4N.js} +1 -1
  37. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-aC9cGB5C.js → cynefinDiagram-TSTJHNR4-CGG-zKEM.js} +1 -1
  38. package/web-dist/assets/{dagre-VKFMJZFB-C8WIcb6F.js → dagre-VKFMJZFB-DyC8Hqa9.js} +1 -1
  39. package/web-dist/assets/{diagram-FQU43EPY-C3aPYv7S.js → diagram-FQU43EPY-B0kKGxoR.js} +1 -1
  40. package/web-dist/assets/{diagram-G47NLZAW-B4wmfzMx.js → diagram-G47NLZAW-B0HF-C4G.js} +1 -1
  41. package/web-dist/assets/{diagram-NH7WQ7WH-DVeUa_5D.js → diagram-NH7WQ7WH-ZnEvLrYH.js} +1 -1
  42. package/web-dist/assets/{diagram-OA4YK3LP-BaQwQu_F.js → diagram-OA4YK3LP-ynOqxQd4.js} +1 -1
  43. package/web-dist/assets/{diagram-WEI45ONY-DqprB85c.js → diagram-WEI45ONY-DeweEbe9.js} +1 -1
  44. package/web-dist/assets/{dist-rtzL4gLm.js → dist-DjR2jyYf.js} +1 -1
  45. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Mm1irNb1.js → ebnfDiagram-CCIWWBDH-PvzgPvcs.js} +1 -1
  46. package/web-dist/assets/{erDiagram-Q63AITRT-MKBulWN5.js → erDiagram-Q63AITRT-DXj9ksQt.js} +1 -1
  47. package/web-dist/assets/eventmodeling-45OFAUF4-sgc1I6jd.js +1 -0
  48. package/web-dist/assets/flowDiagram-23GEKE2U-CgBJXm5_.js +1 -0
  49. package/web-dist/assets/{ganttDiagram-NO4QXBWP-D2y3Bf3l.js → ganttDiagram-NO4QXBWP-jO6JaqFN.js} +1 -1
  50. package/web-dist/assets/{gitGraph-TEB2WS4Q-O-LVx0X5.js → gitGraph-TEB2WS4Q-CwIqpSfV.js} +1 -1
  51. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DcQEaYcT.js → gitGraphDiagram-IHSO6WYX-CX-kdWUp.js} +1 -1
  52. package/web-dist/assets/{index-DpPWC0ta.js → index-BSuA-nmx.js} +90 -90
  53. package/web-dist/assets/index-WdL7ce5m.css +2 -0
  54. package/web-dist/assets/{info-DKCQHKI2-CY6fWeew.js → info-DKCQHKI2-CvH1I_qF.js} +1 -1
  55. package/web-dist/assets/{infoDiagram-FWYZ7A6U-BVfQV-3W.js → infoDiagram-FWYZ7A6U-C9NZZW2e.js} +1 -1
  56. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-dwjcM4Ez.js → ishikawaDiagram-FXEZZL3T-uLWFep7Q.js} +1 -1
  57. package/web-dist/assets/{journeyDiagram-5HDEW3XC-CUaZBvLp.js → journeyDiagram-5HDEW3XC-CsXFiBiD.js} +1 -1
  58. package/web-dist/assets/{kanban-definition-HUTT4EX6-DE8wReP0.js → kanban-definition-HUTT4EX6-DK9QRwpc.js} +1 -1
  59. package/web-dist/assets/{line-zArFI85C.js → line-Cwz0rEtC.js} +1 -1
  60. package/web-dist/assets/{linear-BSG56Yjb.js → linear-BENsfGox.js} +1 -1
  61. package/web-dist/assets/{mermaid-parser.core-BxbE62as.js → mermaid-parser.core-DhmoiWF3.js} +3 -3
  62. package/web-dist/assets/{mermaid.core-C9Bw1zsy.js → mermaid.core-BDr_Kn5Z.js} +3 -3
  63. package/web-dist/assets/{mindmap-definition-LN4V7U3C-BhguO5c0.js → mindmap-definition-LN4V7U3C-CjIFm3K2.js} +1 -1
  64. package/web-dist/assets/{packet-7NZHBO7P-C9Him4Jg.js → packet-7NZHBO7P-CzCyao17.js} +1 -1
  65. package/web-dist/assets/{pegDiagram-2B236MQR-D4wv-hVO.js → pegDiagram-2B236MQR-BmDMeRcD.js} +1 -1
  66. package/web-dist/assets/{pie-RZYD4A2V-CXMTL6pQ.js → pie-RZYD4A2V-DMM2IkHs.js} +1 -1
  67. package/web-dist/assets/{pieDiagram-ENE6RG2P-CHBrF8Gr.js → pieDiagram-ENE6RG2P-CbwVr3qW.js} +1 -1
  68. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-yT8E8mdN.js → quadrantDiagram-ABIIQ3AL-De32pmiP.js} +1 -1
  69. package/web-dist/assets/{radar-I7S5WNFK-DTFJXgLK.js → radar-I7S5WNFK-CbsD9oXO.js} +1 -1
  70. package/web-dist/assets/{railroad-3IZDKUUU-9El5FU8p.js → railroad-3IZDKUUU-DBxDhaRB.js} +1 -1
  71. package/web-dist/assets/railroad-abnf-AHOZXSZD-CHHGvNsw.js +1 -0
  72. package/web-dist/assets/railroad-ebnf-EBAXGLYW-CNbn6hsV.js +1 -0
  73. package/web-dist/assets/railroad-peg-LSFZ7HO6-BsBO5cGs.js +1 -0
  74. package/web-dist/assets/{railroadDiagram-RFXS5EU6-DHRB37mq.js → railroadDiagram-RFXS5EU6-Cc2vK3CJ.js} +1 -1
  75. package/web-dist/assets/{requirementDiagram-TGXJPOKE-Cj41jOCz.js → requirementDiagram-TGXJPOKE-BindEO0A.js} +1 -1
  76. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-Czb26Wy-.js → sankeyDiagram-HTMAVEWB-4Y4Mv2hM.js} +1 -1
  77. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-C_WGyZ5J.js → sequenceDiagram-DBY2YBRQ-BjN0lvxQ.js} +1 -1
  78. package/web-dist/assets/{src-BH3865Hl.js → src-COVUvSFq.js} +1 -1
  79. package/web-dist/assets/{stateDiagram-2N3HPSRC-DwKIv4VY.js → stateDiagram-2N3HPSRC-CW38CSkC.js} +1 -1
  80. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-gZiZetc1.js +1 -0
  81. package/web-dist/assets/{swimlanes-5IMT3BWC-Bf3tF-DM.js → swimlanes-5IMT3BWC-TQBpYgBC.js} +1 -1
  82. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DWpWeiSt.js +8 -0
  83. package/web-dist/assets/{timeline-definition-FHXFAJF6-X_Wo2yjO.js → timeline-definition-FHXFAJF6-BET2st28.js} +1 -1
  84. package/web-dist/assets/{treeView-QDETBFTQ-Cdlwh2BX.js → treeView-QDETBFTQ-C8KMnMpR.js} +1 -1
  85. package/web-dist/assets/{treemap-6X3UGDF4-ByVScQ5B.js → treemap-6X3UGDF4-T0GBmSXN.js} +1 -1
  86. package/web-dist/assets/{vennDiagram-L72KCM5P-B7dNcpqz.js → vennDiagram-L72KCM5P-U1-fDVAv.js} +1 -1
  87. package/web-dist/assets/{wardley-OPB4EBWU-CEiXyYvs.js → wardley-OPB4EBWU-8e8yAPGj.js} +1 -1
  88. package/web-dist/assets/{wardleyDiagram-EHGQE667-CehzwZ8g.js → wardleyDiagram-EHGQE667-CWHLRs2-.js} +1 -1
  89. package/web-dist/assets/{xychartDiagram-FW5EYKEG-BmuVKE3h.js → xychartDiagram-FW5EYKEG-BNqGD1uM.js} +1 -1
  90. package/web-dist/index.html +2 -2
  91. package/web-dist/assets/architecture-TIHT7OUA-BbpJdZVL.js +0 -1
  92. package/web-dist/assets/channel-D0yqk-I_.js +0 -1
  93. package/web-dist/assets/classDiagram-OUVF2IWQ-BrNNI0PJ.js +0 -1
  94. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BrNNI0PJ.js +0 -1
  95. package/web-dist/assets/eventmodeling-45OFAUF4-JhoriiTk.js +0 -1
  96. package/web-dist/assets/flowDiagram-23GEKE2U-BY5NuqLK.js +0 -1
  97. package/web-dist/assets/index-BSpdKQMe.css +0 -2
  98. package/web-dist/assets/railroad-abnf-AHOZXSZD-DfSVqPIB.js +0 -1
  99. package/web-dist/assets/railroad-ebnf-EBAXGLYW-vIxB5rhr.js +0 -1
  100. package/web-dist/assets/railroad-peg-LSFZ7HO6-7hKGS43v.js +0 -1
  101. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-e_B-QQch.js +0 -1
  102. package/web-dist/assets/swimlanesDiagram-G3AALYLV-CrDVZVa-.js +0 -8
package/dist/dev-child.js CHANGED
@@ -13361,10 +13361,21 @@ function parseAwaitingYaml(text2) {
13361
13361
  for (const entry of Array.isArray(raw2) ? raw2 : [raw2]) push(itemKind, entry);
13362
13362
  } else if (key === "for") {
13363
13363
  push("for", raw2);
13364
+ } else if (key === "needs_input" || key === "needs-input") {
13365
+ push("needs_input", raw2);
13364
13366
  }
13365
13367
  }
13366
13368
  return { ok: true, hints };
13367
13369
  }
13370
+ function awaitingNeedsInput(hints) {
13371
+ let answer = null;
13372
+ for (const h10 of hints ?? []) {
13373
+ if (h10.kind !== "needs_input") continue;
13374
+ const value = h10.value.trim().toLowerCase();
13375
+ answer = value === "true" ? true : value === "false" ? false : null;
13376
+ }
13377
+ return answer;
13378
+ }
13368
13379
  function isAwaitingItemKind(kind) {
13369
13380
  return AWAITING_ITEM_KINDS.includes(kind);
13370
13381
  }
@@ -13477,7 +13488,7 @@ function interruptEndedSubAgentsMessage(agents) {
13477
13488
  "None of them will report back, and an `agents:` line naming one is refused as NOT RUNNING. Check what each left behind (commits, edited files, a half-finished step), then re-dispatch it or take its work over yourself before you rest."
13478
13489
  ].join("\n");
13479
13490
  }
13480
- function liveOpsLines(ops) {
13491
+ function liveOpsLines(ops, needsInput = false) {
13481
13492
  const lines = [];
13482
13493
  const section = (heading, key, items) => {
13483
13494
  if (!items.length) return;
@@ -13486,26 +13497,133 @@ function liveOpsLines(ops) {
13486
13497
  lines.push(`In a fence: \`${key}: [${items.map((i) => i.id ?? "?").join(", ")}]\``);
13487
13498
  };
13488
13499
  section("Background shells still running:", "shells", ops?.shells ?? []);
13489
- section("Sub-agents still running (they re-invoke you on their own, so parking on one is optional):", "agents", ops?.subAgents ?? []);
13500
+ section(needsInput ? "Sub-agents still running:" : "Sub-agents still running (they re-invoke you on their own, so parking on one is optional):", "agents", ops?.subAgents ?? []);
13490
13501
  section("Timers you have armed:", "timers", ops?.timers ?? []);
13491
13502
  section("Pull requests you registered:", "prs", ops?.prs ?? []);
13492
13503
  section("Issues you registered:", "issues", ops?.issues ?? []);
13493
13504
  return lines;
13494
13505
  }
13495
- function signoffNudgeMessage(ops) {
13496
- const lines = liveOpsLines(ops);
13506
+ function signoffNudgeMessage(ops, needsInput = false) {
13507
+ const base = needsInput ? SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT : SIGNOFF_NUDGE_MESSAGE;
13508
+ const lines = liveOpsLines(ops, needsInput);
13497
13509
  if (lines.length) {
13498
13510
  lines.push("", "An ```awaiting fence names only what you are ACTUALLY waiting on, one such list per kind, plus");
13499
- lines.push("a required `for:` duration (`30s`/`15m`/`2h`/`3d`), then a `---` line and whatever prose you want");
13500
- lines.push("(optional). Frizz checks every id: name something that is not running and you are bumped");
13501
- lines.push("rather than parked.");
13511
+ if (needsInput) {
13512
+ lines.push("a required `for:` duration (`30s`/`15m`/`2h`/`3d`) and a required `needs_input: true|false`, then");
13513
+ lines.push("a `---` line and whatever prose the human needs. Frizz checks every id: name something that is");
13514
+ lines.push("not running and you are bumped rather than parked.");
13515
+ } else {
13516
+ lines.push("a required `for:` duration (`30s`/`15m`/`2h`/`3d`), then a `---` line and whatever prose you want");
13517
+ lines.push("(optional). Frizz checks every id: name something that is not running and you are bumped");
13518
+ lines.push("rather than parked.");
13519
+ }
13502
13520
  }
13503
- return lines.length === 0 ? SIGNOFF_NUDGE_MESSAGE : `${SIGNOFF_NUDGE_MESSAGE}
13521
+ return lines.length === 0 ? base : `${base}
13504
13522
  ${lines.join("\n")}`;
13505
13523
  }
13524
+ function signoffNudgeAwaitingLines(needsInput) {
13525
+ if (!needsInput) {
13526
+ return [
13527
+ "- `` ```awaiting `` \u2014 you are WAITING on work that is actually running. FRONTMATTER, THEN MARKDOWN:",
13528
+ " one YAML list per kind of thing you are waiting on, a REQUIRED `for:` duration, then a `---` line and",
13529
+ " as much prose as you want. The prose is OPTIONAL; the lines above it are not.",
13530
+ "",
13531
+ " ```awaiting",
13532
+ " shells: [<the id your runtime gave you>]",
13533
+ " prs: [owner/repo#123]",
13534
+ " for: 2h",
13535
+ " ---",
13536
+ " What you are waiting for, in your own words \u2014 this is what the human reads on your card.",
13537
+ " ```"
13538
+ ];
13539
+ }
13540
+ return [
13541
+ "- `` ```awaiting `` \u2014 you are WAITING on work that is actually running: a background shell, a",
13542
+ " sub-agent, a timer or a registered PR. FRONTMATTER, THEN MARKDOWN: one YAML list per kind of thing",
13543
+ " you are waiting on, a REQUIRED `for:` duration, a REQUIRED `needs_input:` answer, then optionally a",
13544
+ " `---` line and prose.",
13545
+ "",
13546
+ " ```awaiting",
13547
+ " agents: [<the id your runtime gave you>]",
13548
+ " needs_input: false",
13549
+ " for: 2h",
13550
+ " ```",
13551
+ "",
13552
+ " `needs_input: false` \u2014 nothing for the human yet. The thread stays out of their queue until the work",
13553
+ " wakes you, and you owe NO write-up: the fence alone is the whole message. `needs_input: true` \u2014 the",
13554
+ " human can read, try or act on something NOW while the work runs (a partial result, a file you wrote,",
13555
+ " a server to try), even if you need nothing back from them; the thread goes into their queue, and the",
13556
+ " prose under `---` says what to look at. If you wrote ANY words for the human at this rest, it is",
13557
+ " `true` \u2014 a `false` rest is never put in front of them. A rest on running work with NO fence is a",
13558
+ " bare rest, and it lands in the human's queue."
13559
+ ];
13560
+ }
13561
+ function signoffNudgeText(needsInput) {
13562
+ return [
13563
+ `${SIGNOFF_NUDGE_MARKER} Nothing about your task has changed, and no new work is being asked of you.`,
13564
+ "",
13565
+ "You rested without a fence, so this thread cannot be triaged.",
13566
+ "",
13567
+ "**IF THE TASK STILL HAS PARTS LEFT, THE FENCE IS NOT WHAT YOU OWE \u2014 THE WORK IS.** If ANY part of the",
13568
+ "original task is unfinished, unverified, or deferred, resume it NOW, in THIS turn, and sign off once it",
13569
+ "is genuinely finished. A milestone, a green test run and a long turn are none of them endings, and",
13570
+ "neither is naming the next step or writing it into a scratch file.",
13571
+ "",
13572
+ "**AND THAT TASK IS ALSO THE CEILING \u2014 finish it, and nothing else.** Work you notice on the way is a",
13573
+ "FINDING TO REPORT in your sign-off, never work to take on: the bug beside the one you were sent for,",
13574
+ "the refactor the code obviously wants, the second issue the first one touches. Widening the job is not",
13575
+ "thoroughness \u2014 it is a different job nobody asked for, and it buries the answer they did ask for under",
13576
+ "changes they now have to review. If it should be done, name it in one line and let the human dispatch",
13577
+ "it.",
13578
+ "",
13579
+ "**IF WHAT YOU WERE ASKED FOR IS A DOCUMENT, THE DOCUMENT IS THE ENDING.** A triage, a review, an",
13580
+ "investigation, a recommendation, a plan \u2014 when that is the deliverable, the finished write-up IS the",
13581
+ "work. Implementing what it proposes is the NEXT job, and not yours unless you were asked. Sign off with",
13582
+ "the write-up.",
13583
+ "",
13584
+ "**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
13585
+ "right now: register that one with `mcp__frizz__ask`. Every other open choice INSIDE the task \u2014 a name, a",
13586
+ "default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
13587
+ "what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
13588
+ "unanswered question is not permission to go build the answer.",
13589
+ "",
13590
+ "Otherwise, sign off \u2014 a registration, or a fence at the END of your next message:",
13591
+ "",
13592
+ "- `mcp__frizz__ask` \u2014 you need the human. NOT a fence: the ```question fence is retired, and a fence",
13593
+ " with a question in its body is plain prose. Register it (options with one-line trade-offs, the",
13594
+ " recommended one first), then rest normally \u2014 an open registered question is the sign-off.",
13595
+ "- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
13596
+ " if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
13597
+ " **bolded verb phrase**.",
13598
+ ...signoffNudgeAwaitingLines(needsInput),
13599
+ "",
13600
+ " Frizz CHECKS every line: name something that is not running and you are bumped rather than parked.",
13601
+ " A fence that names NOTHING is not a park at all \u2014 if you are not waiting on anything, you are not",
13602
+ " awaiting, you are done. Register a PR with `mcp__frizz__watch_pr` and a timer with",
13603
+ " `mcp__frizz__timer`; `mcp__frizz__activity` reads back everything you have running, with its id.",
13604
+ "",
13605
+ "**STILL OWED counts things you are not going to do yourself.** A decision you are RECOMMENDING, a",
13606
+ "draft you wrote but did not send, follow-up work you discovered \u2014 all of it dies with the card, even",
13607
+ "the part that is someone else's to do. Each ends on a question with your recommendation as option A,",
13608
+ "or you DO it first \u2014 a sub-agent's result comes BACK to you, so it lands on your card; a new card via",
13609
+ "`mcp__frizz__spawn_thread` is the LAST resort, since nothing it learns returns to you or its siblings.",
13610
+ "None of them is a `done`. And what is not",
13611
+ 'worth a card is not worth a SENTENCE: delete the dangling "one thing to carry forward", never park it',
13612
+ "in the handoff.",
13613
+ "",
13614
+ "**DO NOT REPEAT YOURSELF.** If the message you just wrote already stands on its own, reply with the",
13615
+ "fence ALONE \u2014 the human reads both together, so restating it costs them the second read for nothing.",
13616
+ "The same holds inside one message: the card is the ledger of what shipped, the prose is only what a",
13617
+ "ledger cannot hold, and a sentence that reads the same in either belongs in exactly one of them.",
13618
+ "",
13619
+ "Only if it does NOT stand alone, fix that first, briefly. It has to be readable cold: the human has",
13620
+ "seen nothing since their own last message \u2014 the Goal, this reminder, a watcher wake all came from",
13621
+ "frizz \u2014 so anything you assumed they had followed, they have not."
13622
+ ].join("\n");
13623
+ }
13506
13624
  function isParkCorrection(text2) {
13507
13625
  const t = text2.trimStart();
13508
- return t.startsWith(PARK_CORRECTION_NAMES_LEAD) || t.startsWith(PARK_CORRECTION_RETIRED_LEAD) || t.startsWith(PARK_CORRECTION_QUESTION_LEAD);
13626
+ return t.startsWith(PARK_CORRECTION_NAMES_LEAD) || t.startsWith(PARK_CORRECTION_RETIRED_LEAD) || t.startsWith(PARK_CORRECTION_QUESTION_LEAD) || t.startsWith(PARK_CORRECTION_NEEDS_INPUT_LEAD);
13509
13627
  }
13510
13628
  function timerPromptMessage(prompt, fireAt) {
13511
13629
  return `${prompt.trim()}
@@ -13561,6 +13679,12 @@ function questionFencesLive(spawnedAt) {
13561
13679
  if (!Number.isFinite(at2)) return true;
13562
13680
  return at2 < Date.parse(QUESTION_FENCE_RETIRED_AT);
13563
13681
  }
13682
+ function needsInputRequired(spawnedAt) {
13683
+ if (spawnedAt === void 0 || spawnedAt === null) return false;
13684
+ const at2 = typeof spawnedAt === "number" ? spawnedAt : Date.parse(spawnedAt);
13685
+ if (!Number.isFinite(at2)) return false;
13686
+ return at2 >= Date.parse(NEEDS_INPUT_REQUIRED_AT);
13687
+ }
13564
13688
  function askedQuestionDepth(q10) {
13565
13689
  let deepest = 1;
13566
13690
  for (const option of q10.options ?? []) {
@@ -13830,7 +13954,7 @@ function parseGithubWakeSteer(text2) {
13830
13954
  function frizzRoute(path3) {
13831
13955
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
13832
13956
  }
13833
- var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ClaudeModel, AcpAgent, ACP_MODEL_PREFIX, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_TITLE_MAX, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, INTERRUPT_ENDED_LEAD, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, QUESTION_FENCE_RETIRED_AT, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, ANSWER_CONTINUATION_INDENT, indentAnswerContinuation, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13957
+ var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ClaudeModel, AcpAgent, ACP_MODEL_PREFIX, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_TITLE_MAX, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, INTERRUPT_ENDED_LEAD, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, PARK_CORRECTION_NEEDS_INPUT_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, QUESTION_FENCE_RETIRED_AT, NEEDS_INPUT_REQUIRED_AT, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, ANSWER_CONTINUATION_INDENT, indentAnswerContinuation, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13834
13958
  var init_src = __esm({
13835
13959
  "packages/shared/src/index.ts"() {
13836
13960
  "use strict";
@@ -14169,7 +14293,7 @@ var init_src = __esm({
14169
14293
  questions: external_exports.array(AskQuestion)
14170
14294
  });
14171
14295
  AwaitingHint = external_exports.object({
14172
- kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "for", "title"]),
14296
+ kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "for", "title", "needs_input"]),
14173
14297
  value: external_exports.string()
14174
14298
  });
14175
14299
  RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
@@ -14190,8 +14314,8 @@ var init_src = __esm({
14190
14314
  "pr": "`prs: [owner/repo#123]` \u2014 one YAML sequence, not one line per PR",
14191
14315
  "reason": "put it below the `---` as ordinary Markdown \u2014 the frontmatter is YAML now and takes no prose"
14192
14316
  };
14193
- AWAITING_KEY_RE = /^([a-z][a-z-]*):\s*(\S.*)?$/i;
14194
- AWAITING_YAML_KEYS = /* @__PURE__ */ new Set(["shells", "agents", "timers", "prs", "issues", "for", "title"]);
14317
+ AWAITING_KEY_RE = /^([a-z][a-z_-]*):\s*(\S.*)?$/i;
14318
+ AWAITING_YAML_KEYS = /* @__PURE__ */ new Set(["shells", "agents", "timers", "prs", "issues", "for", "title", "needs_input", "needs-input"]);
14195
14319
  AWAITING_SEQUENCE_KEYS = {
14196
14320
  shells: "shell",
14197
14321
  agents: "agent",
@@ -14253,80 +14377,12 @@ var init_src = __esm({
14253
14377
  ISSUE_WATCH_ARMED_TRAILER = "(Registered issue watcher \u2014 STILL ARMED. It reports again on the next comment, label or assignee change, and once more when the issue closes. Drop it with `mcp__frizz__watch_issue` when it stops mattering.)";
14254
14378
  INTERRUPT_ENDED_LEAD = "\u26A0\uFE0F The human's last follow-up was sent with INTERRUPT, which aborted your turn \u2014 and the runtime ends every background sub-agent with the turn. These did not return:";
14255
14379
  SIGNOFF_NUDGE_MARKER = "**This message is from frizz, not from the human.**";
14256
- SIGNOFF_NUDGE_MESSAGE = [
14257
- `${SIGNOFF_NUDGE_MARKER} Nothing about your task has changed, and no new work is being asked of you.`,
14258
- "",
14259
- "You rested without a fence, so this thread cannot be triaged.",
14260
- "",
14261
- "**IF THE TASK STILL HAS PARTS LEFT, THE FENCE IS NOT WHAT YOU OWE \u2014 THE WORK IS.** If ANY part of the",
14262
- "original task is unfinished, unverified, or deferred, resume it NOW, in THIS turn, and sign off once it",
14263
- "is genuinely finished. A milestone, a green test run and a long turn are none of them endings, and",
14264
- "neither is naming the next step or writing it into a scratch file.",
14265
- "",
14266
- "**AND THAT TASK IS ALSO THE CEILING \u2014 finish it, and nothing else.** Work you notice on the way is a",
14267
- "FINDING TO REPORT in your sign-off, never work to take on: the bug beside the one you were sent for,",
14268
- "the refactor the code obviously wants, the second issue the first one touches. Widening the job is not",
14269
- "thoroughness \u2014 it is a different job nobody asked for, and it buries the answer they did ask for under",
14270
- "changes they now have to review. If it should be done, name it in one line and let the human dispatch",
14271
- "it.",
14272
- "",
14273
- "**IF WHAT YOU WERE ASKED FOR IS A DOCUMENT, THE DOCUMENT IS THE ENDING.** A triage, a review, an",
14274
- "investigation, a recommendation, a plan \u2014 when that is the deliverable, the finished write-up IS the",
14275
- "work. Implementing what it proposes is the NEXT job, and not yours unless you were asked. Sign off with",
14276
- "the write-up.",
14277
- "",
14278
- "**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
14279
- "right now: register that one with `mcp__frizz__ask`. Every other open choice INSIDE the task \u2014 a name, a",
14280
- "default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
14281
- "what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
14282
- "unanswered question is not permission to go build the answer.",
14283
- "",
14284
- "Otherwise, sign off \u2014 a registration, or a fence at the END of your next message:",
14285
- "",
14286
- "- `mcp__frizz__ask` \u2014 you need the human. NOT a fence: the ```question fence is retired, and a fence",
14287
- " with a question in its body is plain prose. Register it (options with one-line trade-offs, the",
14288
- " recommended one first), then rest normally \u2014 an open registered question is the sign-off.",
14289
- "- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
14290
- " if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
14291
- " **bolded verb phrase**.",
14292
- "- `` ```awaiting `` \u2014 you are WAITING on work that is actually running. FRONTMATTER, THEN MARKDOWN:",
14293
- " one YAML list per kind of thing you are waiting on, a REQUIRED `for:` duration, then a `---` line and",
14294
- " as much prose as you want. The prose is OPTIONAL; the lines above it are not.",
14295
- "",
14296
- " ```awaiting",
14297
- " shells: [<the id your runtime gave you>]",
14298
- " prs: [owner/repo#123]",
14299
- " for: 2h",
14300
- " ---",
14301
- " What you are waiting for, in your own words \u2014 this is what the human reads on your card.",
14302
- " ```",
14303
- "",
14304
- " Frizz CHECKS every line: name something that is not running and you are bumped rather than parked.",
14305
- " A fence that names NOTHING is not a park at all \u2014 if you are not waiting on anything, you are not",
14306
- " awaiting, you are done. Register a PR with `mcp__frizz__watch_pr` and a timer with",
14307
- " `mcp__frizz__timer`; `mcp__frizz__activity` reads back everything you have running, with its id.",
14308
- "",
14309
- "**STILL OWED counts things you are not going to do yourself.** A decision you are RECOMMENDING, a",
14310
- "draft you wrote but did not send, follow-up work you discovered \u2014 all of it dies with the card, even",
14311
- "the part that is someone else's to do. Each ends on a question with your recommendation as option A,",
14312
- "or you DO it first \u2014 a sub-agent's result comes BACK to you, so it lands on your card; a new card via",
14313
- "`mcp__frizz__spawn_thread` is the LAST resort, since nothing it learns returns to you or its siblings.",
14314
- "None of them is a `done`. And what is not",
14315
- 'worth a card is not worth a SENTENCE: delete the dangling "one thing to carry forward", never park it',
14316
- "in the handoff.",
14317
- "",
14318
- "**DO NOT REPEAT YOURSELF.** If the message you just wrote already stands on its own, reply with the",
14319
- "fence ALONE \u2014 the human reads both together, so restating it costs them the second read for nothing.",
14320
- "The same holds inside one message: the card is the ledger of what shipped, the prose is only what a",
14321
- "ledger cannot hold, and a sentence that reads the same in either belongs in exactly one of them.",
14322
- "",
14323
- "Only if it does NOT stand alone, fix that first, briefly. It has to be readable cold: the human has",
14324
- "seen nothing since their own last message \u2014 the Goal, this reminder, a watcher wake all came from",
14325
- "frizz \u2014 so anything you assumed they had followed, they have not."
14326
- ].join("\n");
14380
+ SIGNOFF_NUDGE_MESSAGE = signoffNudgeText(false);
14381
+ SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT = signoffNudgeText(true);
14327
14382
  PARK_CORRECTION_NAMES_LEAD = "\u26A0\uFE0F Your ```awaiting fence names ";
14328
14383
  PARK_CORRECTION_RETIRED_LEAD = "\u26D4 Your ```awaiting fence uses ";
14329
14384
  PARK_CORRECTION_QUESTION_LEAD = "\u26A0\uFE0F Your ```awaiting fence landed while ";
14385
+ PARK_CORRECTION_NEEDS_INPUT_LEAD = "\u26A0\uFE0F Your ```awaiting fence gives no ";
14330
14386
  ThreadWatchKind = external_exports.enum(["shell", "github", "timer"]);
14331
14387
  GithubChecksState = external_exports.enum(["none", "running", "passing", "failing"]);
14332
14388
  GithubMergeState = external_exports.enum(["mergeable", "blocked", "conflicting", "unknown"]);
@@ -14535,6 +14591,7 @@ var init_src = __esm({
14535
14591
  }).strict();
14536
14592
  OWN_WATCH_MAX_ARMED = 24;
14537
14593
  QUESTION_FENCE_RETIRED_AT = "2026-09-11T17:30:00Z";
14594
+ NEEDS_INPUT_REQUIRED_AT = "2026-10-02T00:48:00Z";
14538
14595
  AskQuestionKind = external_exports.enum(["question", "multi"]);
14539
14596
  ASK_MAX_DEPTH = 3;
14540
14597
  AskedOptionSchema = external_exports.lazy(() => external_exports.object({
@@ -19359,7 +19416,7 @@ function ensureStorageSchema(db2) {
19359
19416
  } catch {
19360
19417
  }
19361
19418
  }
19362
- for (const [table, column] of [["pr_watch", "kind TEXT NOT NULL DEFAULT 'pull'"]]) {
19419
+ for (const [table, column] of [["pr_watch", "kind TEXT NOT NULL DEFAULT 'pull'"], ["thread_question", "delivery_id TEXT"]]) {
19363
19420
  try {
19364
19421
  db2.exec(`ALTER TABLE ${table} ADD COLUMN ${column}`);
19365
19422
  } catch {
@@ -19837,6 +19894,12 @@ function createStorage(source, projectId) {
19837
19894
  const markSettlementDeliveredStmt = scope.prepare(
19838
19895
  "UPDATE thread_question SET delivered = 1 WHERE project_id = @project_id AND id = ? AND delivered = 0"
19839
19896
  );
19897
+ const assignSettlementDeliveryStmt = scope.prepare(
19898
+ "UPDATE thread_question SET delivery_id = ? WHERE project_id = @project_id AND id = ? AND delivered = 0"
19899
+ );
19900
+ const markSettlementsDeliveredByStmt = scope.prepare(
19901
+ "UPDATE thread_question SET delivered = 1 WHERE project_id = @project_id AND delivery_id = ? AND delivered = 0"
19902
+ );
19840
19903
  const withdrawThreadQuestionStmt = scope.prepare(`
19841
19904
  UPDATE thread_question SET state = 'withdrawn', settled_at = ?
19842
19905
  WHERE project_id = @project_id AND id = ? AND thread_slug = ? AND state = 'open'
@@ -20405,6 +20468,8 @@ function createStorage(source, projectId) {
20405
20468
  openThreadQuestions: () => openThreadQuestionsStmt.all(),
20406
20469
  answerThreadQuestion: (id2, answer, atMs) => answerThreadQuestionStmt.run(answer, atMs, id2).changes === 1,
20407
20470
  undeliveredSettlements: () => undeliveredSettlementsStmt.all(),
20471
+ assignSettlementDelivery: (id2, deliveryId) => assignSettlementDeliveryStmt.run(deliveryId, id2).changes === 1,
20472
+ markSettlementsDeliveredBy: (deliveryId) => Number(markSettlementsDeliveredByStmt.run(deliveryId).changes),
20408
20473
  markSettlementDelivered: (id2) => markSettlementDeliveredStmt.run(id2).changes === 1,
20409
20474
  withdrawThreadQuestion: (slug, id2, atMs) => withdrawThreadQuestionStmt.run(atMs, id2, slug).changes === 1,
20410
20475
  dismissThreadQuestion: (id2, atMs) => dismissThreadQuestionStmt.run(atMs, id2).changes === 1,
@@ -20913,10 +20978,13 @@ var init_storage = __esm({
20913
20978
  -- worker never saw the id -- frizz minted it -- so an id alone cannot be correlated back. Null
20914
20979
  -- until the human sends; null forever on a withdrawn or dismissed row.
20915
20980
  answer TEXT,
20916
- -- Has the worker been handed this answer yet? A question is answered by the human and DELIVERED
20981
+ -- Has the worker RECEIVED this answer yet? A question is answered by the human and DELIVERED
20917
20982
  -- separately, exactly as a wake is: the row must survive the gap, or an answer given while the
20918
20983
  -- worker's process was down is lost in the same silence the fence used to lose the question in.
20984
+ -- Set when the carrying wake is confirmed delivered, not when it is queued (2026-10-01).
20919
20985
  delivered INTEGER NOT NULL DEFAULT 0,
20986
+ -- The wake_delivery row carrying it (ThreadQuestionRow.delivery_id). Added by ALTER on older files.
20987
+ delivery_id TEXT,
20920
20988
  asked_at INTEGER NOT NULL,
20921
20989
  settled_at INTEGER
20922
20990
  );
@@ -22549,6 +22617,10 @@ function droppedDeliveryId(diagnostic) {
22549
22617
  if (!diagnostic.message.startsWith(CLAUDE_INPUT_DROP_DIAGNOSTIC_PREFIX)) return void 0;
22550
22618
  return diagnostic.message.match(/\bid=([0-9a-fA-F-]{36})\b/)?.[1];
22551
22619
  }
22620
+ function droppedDeliveryReason(diagnostic) {
22621
+ if (diagnostic.kind !== "stderr" || !diagnostic.message.startsWith(CLAUDE_INPUT_DROP_DIAGNOSTIC_PREFIX)) return "";
22622
+ return diagnostic.message.slice(CLAUDE_INPUT_DROP_DIAGNOSTIC_PREFIX.length).replace(/^:\s*/, "").replace(/^id=\S+\s*/, "").trim();
22623
+ }
22552
22624
  function describeClaudeBrokerDiagnostic(diagnostic) {
22553
22625
  if (diagnostic.kind === "stderr") {
22554
22626
  return diagnostic.message.startsWith(CLAUDE_INPUT_DROP_DIAGNOSTIC_PREFIX) ? diagnostic.message : void 0;
@@ -22960,6 +23032,7 @@ function appendDelivery(storage, slug, item) {
22960
23032
  if (index >= 0) {
22961
23033
  const existing = items[index];
22962
23034
  if (existing.state !== "sending" && existing.state !== "failed") return;
23035
+ if (existing.dropped) return;
22963
23036
  const { error: _error, retryable: _retryable, ...rest } = existing;
22964
23037
  items[index] = { ...rest, state, updatedAt: now };
22965
23038
  storage.setDeliveryLedger(slug, serializeDeliveryLedger(trimLedger(items)));
@@ -23004,6 +23077,25 @@ function recordDeliveryFailure(storage, slug, id2, failure) {
23004
23077
  storage.setDeliveryLedger(slug, serializeDeliveryLedger(trimLedger(items)));
23005
23078
  return true;
23006
23079
  }
23080
+ function recordDeliveryDropped(storage, slug, id2, reason, now) {
23081
+ const row = storage.getSession(slug);
23082
+ if (!row) return false;
23083
+ const items = parseDeliveryLedger(row.delivery_ledger);
23084
+ const index = items.findIndex((existing) => existing.id === id2);
23085
+ if (index < 0 || items[index].state === "cancelled") return false;
23086
+ const { retryable: _retryable, ...rest } = items[index];
23087
+ items[index] = {
23088
+ ...rest,
23089
+ state: "failed",
23090
+ // The same words the acknowledged path's own refusal carries (claude-broker-client.ts), so the line
23091
+ // under the bubble reads the same whichever of the two reached the ledger last.
23092
+ error: deliveryErrorLine(`The Claude session refused this message: ${reason.trim() || "it was dropped"}`),
23093
+ dropped: true,
23094
+ updatedAt: new Date(now ?? Date.now()).toISOString()
23095
+ };
23096
+ storage.setDeliveryLedger(slug, serializeDeliveryLedger(trimLedger(items)));
23097
+ return true;
23098
+ }
23007
23099
  function deliveryErrorLine(error) {
23008
23100
  const line = error.split("\n", 1)[0].trim() || "Delivery failed";
23009
23101
  return line.length > DELIVERY_ERROR_MAX_CHARS ? `${line.slice(0, DELIVERY_ERROR_MAX_CHARS - 1)}\u2026` : line;
@@ -23153,7 +23245,7 @@ function accountFor(items, recordText, contemporaneous) {
23153
23245
  const wanted = new Set(tags);
23154
23246
  const owners = /* @__PURE__ */ new Map();
23155
23247
  items.forEach((item, index) => {
23156
- if (item.state === "cancelled") return;
23248
+ if (item.state === "cancelled" || item.dropped) return;
23157
23249
  const tag = deliveryTag(item.id);
23158
23250
  if (wanted.has(tag)) owners.set(tag, [...owners.get(tag) ?? [], index]);
23159
23251
  });
@@ -23177,6 +23269,7 @@ function matchComposedText(items, recordText, contemporaneous) {
23177
23269
  if (matched.has(index)) return null;
23178
23270
  const item = items[index];
23179
23271
  if (item.state === "cancelled") return null;
23272
+ if (item.dropped) return null;
23180
23273
  if (!contemporaneous(item)) return null;
23181
23274
  const text2 = canon(item.text);
23182
23275
  return text2 || null;
@@ -23295,7 +23388,7 @@ function projectDeliveryLedger(messages, items) {
23295
23388
  const text2 = renderMatchKey(item.text);
23296
23389
  const tag = deliveryTag(item.id);
23297
23390
  let handled = false;
23298
- for (let i = messages.length - 1; i >= 0; i--) {
23391
+ for (let i = item.dropped ? -1 : messages.length - 1; i >= 0; i--) {
23299
23392
  const m = messages[i];
23300
23393
  if (m.role !== "user" || claimed.has(i)) continue;
23301
23394
  if ((item.state === "sending" || item.state === "failed") && m.at) {
@@ -24451,6 +24544,9 @@ function notificationText(rec) {
24451
24544
  }
24452
24545
  return void 0;
24453
24546
  }
24547
+ function isMonitorTimeoutEvent(block) {
24548
+ return block.includes("<event>[Monitor timed out") || block.includes("<event>[Monitor expired after");
24549
+ }
24454
24550
  function trackCompletions(state, rec) {
24455
24551
  const raw2 = notificationText(rec);
24456
24552
  if (!raw2 || !raw2.includes("<task-notification>")) return;
@@ -24458,7 +24554,7 @@ function trackCompletions(state, rec) {
24458
24554
  if (state.subAgents.size === 0 && state.retiredSubAgents.size === 0) return;
24459
24555
  for (const block of raw2.match(/<task-notification>[\s\S]*?<\/task-notification>/g) ?? []) {
24460
24556
  const status = block.match(/<status>([^<]*)<\/status>/)?.[1];
24461
- const monitorTimedOut = block.includes("<event>[Monitor timed out");
24557
+ const monitorTimedOut = isMonitorTimeoutEvent(block);
24462
24558
  const terminal = status === "completed" || status === "failed" || status === "killed" ? status : status === "stopped" || monitorTimedOut ? "killed" : void 0;
24463
24559
  if (!terminal) continue;
24464
24560
  const fingerprint = createHash7("sha1").update(block).digest("hex").slice(0, 16);
@@ -36313,6 +36409,19 @@ function readAwaitingPark(hints) {
36313
36409
  }
36314
36410
  return { items, forMs };
36315
36411
  }
36412
+ function liveActivityOf(tele, registeredPrWatches, armedTimerIds, registeredIssueWatches = /* @__PURE__ */ new Set()) {
36413
+ const shells = /* @__PURE__ */ new Set();
36414
+ for (const sh2 of tele?.bgShells ?? []) {
36415
+ if (sh2.state !== "running") continue;
36416
+ for (const h10 of [sh2.taskId, sh2.id, sh2.label]) if (h10) shells.add(h10);
36417
+ }
36418
+ const agents = /* @__PURE__ */ new Set();
36419
+ for (const a of tele?.subAgents ?? []) {
36420
+ if (a.state !== "running") continue;
36421
+ for (const h10 of [a.taskId, a.id, a.label]) if (h10) agents.add(h10);
36422
+ }
36423
+ return { shells, agents, timers: armedTimerIds, prs: registeredPrWatches, issues: registeredIssueWatches };
36424
+ }
36316
36425
  function unaccountedItems(items, live) {
36317
36426
  return items.filter((i) => !live[LIVE_SET[i.kind]]?.has(liveKey(i)));
36318
36427
  }
@@ -36332,6 +36441,13 @@ function parkIsHonoured(park, live) {
36332
36441
  if (park.forMs === null) return false;
36333
36442
  return unaccountedItems(park.items, live).length === 0;
36334
36443
  }
36444
+ function needsInputParkHolds(hints, live, fenceAtMs, nowMs) {
36445
+ if (awaitingNeedsInput(hints) !== false) return false;
36446
+ const park = readAwaitingPark(hints);
36447
+ if (!parkIsHonoured(park, live)) return false;
36448
+ const expiresAt = parkExpiresAt(park, fenceAtMs);
36449
+ return expiresAt !== null && nowMs < expiresAt;
36450
+ }
36335
36451
  function parkForMaxMs(park) {
36336
36452
  if (park.items.length === 0) return AWAITING_FOR_MAX_MS;
36337
36453
  return park.items.every((i) => i.kind === "pr" || i.kind === "issue") ? PR_WATCH_FOR_MAX_MS : AWAITING_FOR_MAX_MS;
@@ -36630,23 +36746,35 @@ command pasted as prose. Never prefix a description with \`Running\` to force it
36630
36746
  verb itself, and use \`Running\` only when the thing you are doing is literally running something.`;
36631
36747
  SIGNALS = `## End-of-turn signals \u2014 your final message IS the interface
36632
36748
 
36633
- When you come to rest, your last message is the whole interface: the human reads it in a queue, hours
36634
- later, with none of your context \u2014 and they have seen NOTHING since their own last message; every
36635
- prompt in between (the Goal, a sign-off reminder, a watcher wake) came from frizz. So summarise the
36636
- whole stretch since their last message: what you did, found, changed, and would do next. Be concise
36637
- but do not omit; use whatever markup reads well.
36749
+ When you come to rest NEEDING the human, your last message is the whole interface: the human reads it
36750
+ in a queue, hours later, with none of your context \u2014 and they have seen NOTHING since their own last
36751
+ message; every prompt in between (the Goal, a sign-off reminder, a watcher wake) came from frizz, and
36752
+ so did every quiet park below. So summarise the whole stretch since their last message: what you did,
36753
+ found, changed, and would do next. Be concise but do not omit; use whatever markup reads well.
36754
+
36755
+ **A QUIET PARK NEEDS NO WRITE-UP.** When you rest on work that is still running and the human has
36756
+ nothing to read, try or act on yet \u2014 a sub-agent mid-task, a build, CI \u2014 end with an \` \`\`\`awaiting \`
36757
+ fence carrying \`needs_input: false\` and NOTHING else: no summary, no progress report, no prose. The
36758
+ thread stays out of the human's queue and nobody is meant to read that message, so writing one costs you
36759
+ a turn of tokens for no reader. The moment there IS something for them \u2014 a partial result, a file, a
36760
+ server, a question \u2014 the rest is \`needs_input: true\` instead. The write-up is owed at the rest that
36761
+ DOES need them \u2014 \`needs_input: true\`, a question, or \`done\` \u2014 and it covers everything since their
36762
+ last message, the quiet stretches included.
36638
36763
 
36639
36764
  **ALWAYS SIGN OFF.** A bare rest \u2014 nothing said about where you stand \u2014 is not a handoff, it is an item
36640
36765
  nobody can triage: it says neither "answer me" nor "this is finished", so it sits in the queue meaning
36641
36766
  nothing. Frizz bumps you for one, twice, then gives up \u2014 a turn wasted each time.
36642
36767
 
36643
- **THERE ARE TWO WAYS TO SAY IT, and a REGISTRATION is the better one.** \`mcp__frizz__done\`,
36644
- \`mcp__frizz__ask\` and \`mcp__frizz__watch\` each record a ROW, and frizz reads all three as a sign-off
36645
- \u2014 it will not bump you for a missing fence after any of them. A fence is a sentence with the lifetime
36646
- of the message carrying it, so it has to be rewritten at every rest and is wrong the moment anything
36647
- changes; a row survives your turn ending, a compaction and a frizz restart. **The \`done\` and
36648
- \`awaiting\` fences below still work**, they are still the right shape for the prose the human reads \u2014
36649
- but where a verb exists for what you are saying, call it. **A QUESTION HAS NO FENCE ANY MORE** (retired
36768
+ **THERE ARE TWO WAYS TO SAY IT, and a REGISTRATION is the better one.** \`mcp__frizz__done\` and
36769
+ \`mcp__frizz__ask\` each record a ROW, and frizz reads both as a sign-off \u2014 it will not bump you for a
36770
+ missing fence after either. A fence is a sentence with the lifetime of the message carrying it, so it
36771
+ has to be rewritten at every rest and is wrong the moment anything changes; a row survives your turn
36772
+ ending, a compaction and a frizz restart. **The \`done\` fence below still works**, and it is still the
36773
+ right shape for the prose the human reads \u2014 but where a verb exists for what you are saying, call it.
36774
+ **A WAIT IS THE ONE EXCEPTION, and it always takes the fence.** \`mcp__frizz__watch\` registers WHEN you
36775
+ wake; whether the human is needed meanwhile is an answer about THIS rest, and only the
36776
+ \` \`\`\`awaiting \` fence carries it (\`needs_input:\`, below). A rest on running work with no fence is a
36777
+ bare rest, and it lands in the human's queue. **A QUESTION HAS NO FENCE ANY MORE** (retired
36650
36778
  2026-09-11): the only way to ask is \`mcp__frizz__ask\`, and a question written into a fence's body is
36651
36779
  plain prose \u2014 no card, no answer, and no sign-off.
36652
36780
 
@@ -36738,24 +36866,46 @@ exactly ONE of them.
36738
36866
  human has yet to make: write the file AND ask the question.
36739
36867
 
36740
36868
  - \` \`\`\`awaiting \` \u2014 you have STOPPED, and you are waiting on work that is actually running (never on
36741
- a human \u2014 that is a question). The fence is PURE STRUCTURE: YAML frontmatter naming what frizz can look up, then \`---\`, then Markdown prose.
36869
+ a human \u2014 that is a question). The fence is PURE STRUCTURE: YAML frontmatter naming what frizz can
36870
+ look up and saying whether the human is needed, then \u2014 only when they are \u2014 \`---\` and Markdown prose.
36871
+ The everyday shape is a QUIET PARK, the fence alone:
36742
36872
 
36743
36873
  \`\`\`awaiting
36744
36874
  shells: [bzvtnt3ig]
36745
36875
  agents: [<the runtime agent id>]
36746
36876
  timers: [tmr_a1b2c3d4e5f6]
36747
36877
  prs: [acme/app#391]
36878
+ needs_input: false
36748
36879
  for: 2h
36749
- title: Three-platform CI run
36880
+ \`\`\`
36881
+
36882
+ And when the human can already act on something while the work runs:
36883
+
36884
+ \`\`\`awaiting
36885
+ agents: [<the runtime agent id>]
36886
+ needs_input: true
36887
+ for: 1h
36888
+ title: Three of five audits ready
36750
36889
  ---
36751
- Waiting on the three-platform run before porting the v2 drivers.
36890
+ Three of the five audits are written to \`audits/\` and can be read now; the other two are still running.
36752
36891
 
36753
- - the macOS leg is the one that has been flaky
36754
- - if it goes red I will bisect rather than re-run
36892
+ - the auth audit found the one issue worth deciding on before the rest land
36755
36893
  \`\`\`
36756
36894
 
36895
+ - **\`needs_input:\` \u2014 REQUIRED, \`true\` or \`false\`: does the human need to look NOW?** \`false\` keeps
36896
+ the thread out of their queue until the work wakes you, and the fence is the whole message.
36897
+ \`true\` puts it in their queue while the work keeps running \u2014 the live work is listed on the card \u2014
36898
+ and the prose under \`---\` says what to look at. **\`true\` IS NOT ONLY FOR A QUESTION.** Anything the
36899
+ human could read, try or act on right now \u2014 a partial result, a file you wrote, a server to click
36900
+ through \u2014 is \`true\`, even when you need nothing back from them. And if you wrote ANY words for the
36901
+ human at this rest, the answer is \`true\`: a \`false\` rest is never put in front of them, so those
36902
+ words would go unread. It is YOUR call, and frizz does not second-guess it:
36903
+ no rule about which kinds of wait need the human overrides what you say. A fence without the line \u2014
36904
+ or with anything but \`true\`/\`false\` \u2014 is not a park: the thread queues and frizz tells you which
36905
+ line is missing. And a \`false\` holds only while every name is live and \`for:\` has not run out, so a
36906
+ wrong \`false\` cannot hide the thread.
36757
36907
  - **THE FRONTMATTER IS YAML. THE BODY IS MARKDOWN.** Four PLURAL keys taking LISTS (either list form;
36758
- a single item may be written bare), plus the scalars \`for:\` and \`title:\`. **NO PROSE ABOVE THE
36908
+ a single item may be written bare), plus the scalars \`for:\`, \`needs_input:\` and \`title:\`. **NO PROSE ABOVE THE
36759
36909
  \`---\`, EVER** \u2014 a colon or a \` #\` inside a sentence breaks the parse. (The \`title:\` value is the
36760
36910
  one exception: frizz reads it verbatim, so \`#391\` and a colon are safe there.) There is no \`reason:\` key any more;
36761
36911
  the reason goes in the body, which is the handoff the human reads. **THE SINGULAR KEYS ARE GONE** \u2014 \`shell:\`,
@@ -36768,13 +36918,14 @@ exactly ONE of them.
36768
36918
  still owed an answer, with the exact id each one is named by. It changes nothing, and it is the only
36769
36919
  way to read your open questions without registering or withdrawing one. A guessed id names
36770
36920
  nothing, so frizz refuses the park and bumps you.
36771
- **BETTER THAN A \`shells:\` LINE \u2014 REGISTER THE WAIT:** \`mcp__frizz__watch\` (\`kind\`, \`target\`,
36772
- \`for\`) creates a ROW instead of a sentence, so it survives your turn ending, a compaction and a
36773
- frizz restart, and it keeps parking you whatever you write next \u2014 you never restate it, and
36774
- \`activity\` marks anything already covered. It refuses a handle nothing live answers to, and a
36775
- \`kind\` that disagrees with what frizz can see, so you cannot rest behind a wait that will not
36776
- fire. Withdraw one with \`mcp__frizz__unwatch\`; a sub-agent needs neither, because a running child
36777
- parks you on its own.
36921
+ **A LONG WAIT CAN ALSO BE REGISTERED:** \`mcp__frizz__watch\` (\`kind\`, \`target\`, \`for\`) creates a
36922
+ ROW beside the fence, so the wait and its own timeout wake survive your turn ending, a compaction
36923
+ and a frizz restart, and \`activity\` marks anything already covered. It refuses a handle nothing
36924
+ live answers to, and a \`kind\` that disagrees with what frizz can see, so you cannot register a
36925
+ wait that will not fire. It does NOT replace the fence: each rest still ends with one, naming the
36926
+ work and answering \`needs_input:\`. Withdraw a registration with \`mcp__frizz__unwatch\`. A sub-agent
36927
+ needs no registration \u2014 its return re-invokes you \u2014 but resting on one still takes the fence,
36928
+ \`agents: [<id>]\`.
36778
36929
  - \`timers:\` \u2014 timer ids from \`mcp__frizz__timer\`. NOT instants; the tool creates the timer.
36779
36930
  - \`prs:\` \u2014 pull requests you REGISTERED with \`mcp__frizz__watch_pr\`, written \`owner/repo#N\`.
36780
36931
  Register FIRST: this line states the wait, it does not create one. That tool wakes you on CI
@@ -36803,9 +36954,10 @@ exactly ONE of them.
36803
36954
  true of every park and specific to none.
36804
36955
  - **REGISTERING IS NOT PARKING, AND PARKING IS NOT REGISTERING.** Your shells, sub-agents, timers
36805
36956
  and PR watchers are watched AUTOMATICALLY, fence or no fence \u2014 frizz wakes you when one finishes,
36806
- every time. The fence only declares that you have STOPPED, and names which of them you stopped
36807
- for. **FRIZZ CHECKS EVERY NAME THE MOMENT YOU REST:** all of them live \u21D2 your thread parks in the
36808
- dimmed Snoozed band; any one dead, unknown or not yours \u21D2 you are BUMPED immediately and told which.
36957
+ every time. The fence only declares that you have STOPPED, names which of them you stopped for,
36958
+ and says whether the human is needed meanwhile. **FRIZZ CHECKS EVERY NAME THE MOMENT YOU REST:** all
36959
+ of them live \u21D2 your thread rests where \`needs_input:\` put it; any one dead, unknown or not yours \u21D2
36960
+ you are BUMPED immediately and told which, and the thread sits in the queue until you fix it.
36809
36961
  **A FENCE THAT NAMES NOTHING IS NOT A PARK** \u2014 \`for:\` describes a wait, it is not one \u2014 so
36810
36962
  "waiting on the checks" plus a duration just sits in the queue: REGISTER the PR and name it, and
36811
36963
  if nothing is actually running, end with \`\`\`done or register a question with \`mcp__frizz__ask\`.
@@ -37174,8 +37326,8 @@ the handoff.
37174
37326
 
37175
37327
  ## Automated waits in Claude Code
37176
37328
 
37177
- **The mechanism is decided by whether you will REST while it runs.** Only a live sub-agent keeps a
37178
- rested thread out of the queue.
37329
+ **The mechanism is decided by whether you will REST while it runs.** Only an \` \`\`\`awaiting \` fence
37330
+ answering \`needs_input: false\` keeps a rested thread out of the queue \u2014 a live sub-agent alone does not.
37179
37331
 
37180
37332
  - **Resting until a condition is met** (the usual CI / PR / release wait) \u2192 for a pull request,
37181
37333
  \`mcp__frizz__watch_pr\`; for a GitHub issue, \`mcp__frizz__watch_issue\`; for anything else dispatch a
@@ -39660,6 +39812,10 @@ function deriveNeedsYou(row, tele, runtime, hasActionableInteraction = false, no
39660
39812
  if (tele?.pendingQuestion) return true;
39661
39813
  if (openQuestions > 0) return true;
39662
39814
  if (limitPause) return true;
39815
+ const answered = tele?.lastFence?.kind === "awaiting" && awaitingNeedsInput(tele.lastFence.hints) !== null;
39816
+ if (answered || needsInputRequired(row.spawned_at)) {
39817
+ return needsInputQueues(row, tele, runtime, nowMs, excuseLiveOwnWork, registeredPrWatches, armedTimerIds);
39818
+ }
39663
39819
  if (hasParkedExternalWait(tele, nowMs)) return false;
39664
39820
  if (excuseLiveOwnWork && runtime !== "exited" && hasLiveBackgroundWork(tele) && !tele?.lastFence) return false;
39665
39821
  if (excuseLiveOwnWork && runtime !== "exited" && (hasDeclaredBackgroundPark(tele, nowMs) || hasRegisteredBackgroundPark(tele, armedWatches, nowMs))) return false;
@@ -39668,6 +39824,13 @@ function deriveNeedsYou(row, tele, runtime, hasActionableInteraction = false, no
39668
39824
  if (tele?.lastFence?.kind === "done") return true;
39669
39825
  return true;
39670
39826
  }
39827
+ function needsInputQueues(row, tele, runtime, nowMs, excuseLiveOwnWork, registeredPrWatches, armedTimerIds) {
39828
+ const fence = tele?.lastFence;
39829
+ if (excuseLiveOwnWork && runtime !== "exited" && fence?.kind === "awaiting" && // `registeredPrWatches` carries both kinds' refs (see hasParkedPrWatch), so it answers for `issues:` too.
39830
+ needsInputParkHolds(fence.hints, liveActivityOf(tele, registeredPrWatches, armedTimerIds, registeredPrWatches), Date.parse(tele?.lastAssistantAt ?? ""), nowMs)) return false;
39831
+ if (runtime !== "exited" && fence?.kind !== "done" && (hasLiveOwnWork(tele, registeredPrWatches) || hasParkedTimerWatch(tele, armedTimerIds))) return !bgSnoozeArmed(row);
39832
+ return true;
39833
+ }
39671
39834
  function deriveAwaitingBackground(row, tele, runtime, hasActionableInteraction = false, nowMs = Date.now(), limitPause = void 0, deliveryProcessGone = false, github = {}, registeredPrWatches = /* @__PURE__ */ new Set(), armedTimerIds = /* @__PURE__ */ new Set(), armedWatches = [], openQuestions = 0) {
39672
39835
  if (runtime !== "turn-idle" || !hasDeclaredWait(tele, nowMs, armedTimerIds, registeredPrWatches, armedWatches)) return false;
39673
39836
  if (limitPause || tele?.providerError) return false;
@@ -41407,19 +41570,6 @@ function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @
41407
41570
  if (tele.lastFence?.kind === "awaiting") return true;
41408
41571
  return threadSaidDone(storage, slug, tele, armedAt);
41409
41572
  }
41410
- function liveActivityOf(tele, registeredPrWatches, armedTimerIds, registeredIssueWatches = /* @__PURE__ */ new Set()) {
41411
- const shells = /* @__PURE__ */ new Set();
41412
- for (const sh2 of tele.bgShells ?? []) {
41413
- if (sh2.state !== "running") continue;
41414
- for (const h10 of [sh2.taskId, sh2.id, sh2.label]) if (h10) shells.add(h10);
41415
- }
41416
- const agents = /* @__PURE__ */ new Set();
41417
- for (const a of tele.subAgents ?? []) {
41418
- if (a.state !== "running") continue;
41419
- for (const h10 of [a.taskId, a.id, a.label]) if (h10) agents.add(h10);
41420
- }
41421
- return { shells, agents, timers: armedTimerIds, prs: registeredPrWatches, issues: registeredIssueWatches };
41422
- }
41423
41573
  function registeredPrWatchesOf(storage, slug) {
41424
41574
  return new Set(storage.listPrWatches(slug, { armedOnly: true }).filter((w3) => w3.kind !== "issue").map((w3) => `${w3.owner}/${w3.repo}#${w3.number}`));
41425
41575
  }
@@ -41634,6 +41784,7 @@ function createScheduler(deps) {
41634
41784
  const confirmGraceMs = Math.max(1, deps.confirmGraceMs ?? 6e4);
41635
41785
  const deliveryBatchSize = Math.max(0, deps.deliveryBatchSize ?? 50);
41636
41786
  const deliveryOwner = randomUUID14();
41787
+ const startedAtMs = now();
41637
41788
  const outbox = createWakeDeliveryStore(deps.storage.scope, { quietWindowMs: deps.wakeQuietWindowMs ?? WAKE_QUIET_WINDOW_MS });
41638
41789
  const reviewFailures = /* @__PURE__ */ new Map();
41639
41790
  let timer = null;
@@ -41961,8 +42112,9 @@ function createScheduler(deps) {
41961
42112
  if (tele.lastUserAt && Date.parse(tele.lastUserAt) >= Date.parse(spokeAt)) continue;
41962
42113
  if (tele.authFault) continue;
41963
42114
  if (tele.apiFault) continue;
42115
+ const needsInput = needsInputRequired(row.spawned_at);
41964
42116
  const questionRows = deps.storage.listThreadQuestions(row.slug);
41965
- if (tele.lastFence || tele.pendingQuestion || registeredDoneFence(deps.storage.getThreadDone(row.slug), tele.lastUserAt) !== void 0 || questionRows.some((q10) => q10.state === "open") || answersInFlight(questionRows, tele.lastUserAt, row.recurring_on_rest === 1 && Boolean(row.recurring_prompt?.trim())) !== void 0 || deps.storage.listThreadWatches(row.slug, { armedOnly: true }).length > 0) {
42117
+ if (tele.lastFence || tele.pendingQuestion || registeredDoneFence(deps.storage.getThreadDone(row.slug), tele.lastUserAt) !== void 0 || questionRows.some((q10) => q10.state === "open") || answersInFlight(questionRows, tele.lastUserAt, row.recurring_on_rest === 1 && Boolean(row.recurring_prompt?.trim())) !== void 0 || !needsInput && deps.storage.listThreadWatches(row.slug, { armedOnly: true }).length > 0) {
41966
42118
  if ((row.signoff_nudges ?? 0) > 0) deps.storage.resetSignoffNudges(row.slug);
41967
42119
  continue;
41968
42120
  }
@@ -41991,7 +42143,7 @@ function createScheduler(deps) {
41991
42143
  timers: deps.storage.listThreadTimers(row.slug, { armedOnly: true }).map((t) => ({ id: t.id, label: t.prompt.trim().replace(/\s+/g, " ").slice(0, 80) })),
41992
42144
  prs: deps.storage.listPrWatches(row.slug, { armedOnly: true }).filter((w3) => w3.kind !== "issue").map((w3) => ({ id: `${w3.owner}/${w3.repo}#${w3.number}`, label: `${w3.owner}/${w3.repo}#${w3.number}` })),
41993
42145
  issues: deps.storage.listPrWatches(row.slug, { armedOnly: true }).filter((w3) => w3.kind === "issue").map((w3) => ({ id: `${w3.owner}/${w3.repo}#${w3.number}`, label: `${w3.owner}/${w3.repo}#${w3.number}` }))
41994
- }), spokeAt),
42146
+ }, needsInput), spokeAt),
41995
42147
  reason: "rested without signing off"
41996
42148
  }, nowMs).delivery;
41997
42149
  log3(`waker: queued ${row.slug} \u2014 ${item.reason}`);
@@ -42062,8 +42214,42 @@ ${wakeTimeHeader(nowMs, spokeAt)}`,
42062
42214
  const expiresAt = parkExpiresAt(park, Date.parse(spokeAt));
42063
42215
  const expired = expiresAt !== null && nowMs >= expiresAt;
42064
42216
  const nameless = park.items.length === 0;
42065
- if (parkIsHonoured(park, live) && (row.park_bumps ?? 0) > 0) deps.storage.resetParkBumps(row.slug);
42066
- if (dead.length === 0 && !expired && !nameless) continue;
42217
+ const unanswered2 = needsInputRequired(row.spawned_at) && awaitingNeedsInput(tele.lastFence.hints) === null;
42218
+ if (parkIsHonoured(park, live) && !unanswered2 && (row.park_bumps ?? 0) > 0) deps.storage.resetParkBumps(row.slug);
42219
+ if (dead.length === 0 && !expired && !nameless) {
42220
+ if (!unanswered2 || (row.park_bumps ?? 0) >= PARK_BUMP_MAX) continue;
42221
+ const fenceId2 = parkFenceId("needs-input", spokeAt);
42222
+ const deliveryId2 = wakeDeliveryId(row.slug, row.session_id, fenceId2);
42223
+ if (outbox.get(deliveryId2)) continue;
42224
+ const written = tele.lastFence.hints.find((h10) => h10.kind === "needs_input")?.value;
42225
+ const message2 = [
42226
+ `${PARK_CORRECTION_NEEDS_INPUT_LEAD}\`needs_input: true\` or \`needs_input: false\`${written ? ` (it says \`needs_input: ${written}\`)` : ""}, so frizz did not park it \u2014 your thread is in the human's queue.`,
42227
+ "",
42228
+ "Every ```awaiting fence answers one question: does the human need to look NOW?",
42229
+ "",
42230
+ "- `needs_input: false` \u2014 nothing for them yet. The thread stays out of their queue until the work",
42231
+ " wakes you, and the fence alone is the whole message: no write-up.",
42232
+ "- `needs_input: true` \u2014 they can act on something now while the work runs. The thread goes into",
42233
+ " their queue, and the prose under `---` says what to look at.",
42234
+ "",
42235
+ "Re-send the same fence with that line added."
42236
+ ].join("\n");
42237
+ const item2 = outbox.enqueue({
42238
+ id: deliveryId2,
42239
+ slug: row.slug,
42240
+ sessionId: row.session_id,
42241
+ fenceId: fenceId2,
42242
+ hintKey: fenceId2,
42243
+ message: `${message2}
42244
+
42245
+ ${wakeTimeHeader(nowMs, spokeAt)}`,
42246
+ reason: "awaiting fence gives no needs_input answer"
42247
+ }, nowMs).delivery;
42248
+ deps.storage.countParkBump(row.slug, fenceId2);
42249
+ log3(`waker: queued ${row.slug} \u2014 ${item2.reason}`);
42250
+ checkpoint("after-enqueue", item2);
42251
+ continue;
42252
+ }
42067
42253
  if (park.forMs === null && !nameless) continue;
42068
42254
  const finishedHandles = /* @__PURE__ */ new Set();
42069
42255
  for (const sh2 of tele.retiredShells ?? []) {
@@ -42628,9 +42814,30 @@ ${clock}`;
42628
42814
  list.push(q10);
42629
42815
  bySlug.set(q10.thread_slug, list);
42630
42816
  }
42631
- for (const [slug, rows] of bySlug) {
42817
+ for (const [slug, settled] of bySlug) {
42632
42818
  const row = deps.storage.getSession(slug);
42633
42819
  if (!row || row.state === "archived" || row.archived === 1) continue;
42820
+ const rows = [];
42821
+ const failed = [];
42822
+ let fresh = false;
42823
+ let reofferDue = false;
42824
+ for (const q10 of settled) {
42825
+ if (!q10.delivery_id) {
42826
+ rows.push(q10);
42827
+ fresh = true;
42828
+ continue;
42829
+ }
42830
+ const carrier = outbox.get(q10.delivery_id);
42831
+ if (carrier?.state === "pending" || carrier?.state === "leased") continue;
42832
+ if (carrier?.state === "delivered") {
42833
+ deps.storage.markSettlementDelivered(q10.id);
42834
+ continue;
42835
+ }
42836
+ rows.push(q10);
42837
+ if (carrier && !failed.some((d) => d.id === carrier.id)) failed.push(carrier);
42838
+ if (!carrier || answerReofferDue(carrier, row, nowMs)) reofferDue = true;
42839
+ }
42840
+ if (rows.length === 0 || !fresh && !reofferDue) continue;
42634
42841
  const answers = [];
42635
42842
  const dismissed = [];
42636
42843
  const dismissedIds = [];
@@ -42646,9 +42853,14 @@ ${clock}`;
42646
42853
  }
42647
42854
  if (answers.length === 0 && !(row.recurring_on_rest === 1 && row.recurring_prompt?.trim())) continue;
42648
42855
  const cancelledCount = dismissedIds.length;
42649
- const fenceId = questionAnswerFenceId([...answers.map((a) => a.questionId), ...dismissedIds]);
42856
+ const fenceId = questionAnswerFenceId([
42857
+ ...answers.map((a) => a.questionId),
42858
+ ...dismissedIds,
42859
+ ...rows.flatMap((q10) => q10.delivery_id ? [`after:${q10.delivery_id}`] : [])
42860
+ ]);
42650
42861
  const deliveryId = wakeDeliveryId(row.slug, row.session_id, fenceId);
42651
- if (outbox.get(deliveryId)) continue;
42862
+ for (const q10 of rows) deps.storage.assignSettlementDelivery(q10.id, deliveryId);
42863
+ const reoffer = failed.length > 0 || rows.some((q10) => q10.delivery_id);
42652
42864
  const item = outbox.enqueue({
42653
42865
  id: deliveryId,
42654
42866
  slug: row.slug,
@@ -42659,13 +42871,29 @@ ${clock}`;
42659
42871
  answers.length === 0 ? questionsCancelledWakeMessage(cancelledCount) : questionAnswerMessage(answers, dismissed),
42660
42872
  deps.tailer.get(row.slug)?.lastAssistantAt
42661
42873
  ),
42662
- reason: answers.length === 0 ? `${cancelledCount} question(s) cancelled \u2014 autonomous` : `${answers.length} question answer(s)${cancelledCount ? ` + ${cancelledCount} dismissed` : ""}`
42874
+ reason: (answers.length === 0 ? `${cancelledCount} question(s) cancelled \u2014 autonomous` : `${answers.length} question answer(s)${cancelledCount ? ` + ${cancelledCount} dismissed` : ""}`) + (reoffer ? " \u2014 offered again: the last wake carrying them did not land" : "")
42663
42875
  }, nowMs).delivery;
42664
- for (const q10 of rows) deps.storage.markSettlementDelivered(q10.id);
42665
42876
  log3(`waker: queued ${row.slug} \u2014 ${item.reason}`);
42666
42877
  checkpoint("after-enqueue", item);
42667
42878
  }
42668
42879
  }
42880
+ function answerReofferDue(carrier, row, nowMs) {
42881
+ if (carrier.state === "superseded" && carrier.sentAt === null && carrier.lastError?.startsWith(CONTEXT_SUPERSEDED)) return true;
42882
+ const failedAt2 = carrier.terminalAt ?? carrier.updatedAt;
42883
+ if (failedAt2 < startedAtMs) return true;
42884
+ const tele = deps.tailer.get(row.slug);
42885
+ const ranAt = Math.max(Date.parse(tele?.lastAssistantAt ?? "") || 0, Date.parse(tele?.lastUserAt ?? "") || 0);
42886
+ if (ranAt > failedAt2) return true;
42887
+ return nowMs - failedAt2 >= ANSWER_REOFFER_BACKOFF_MS;
42888
+ }
42889
+ function settleQuestionAnswers(item) {
42890
+ if (!isQuestionAnswerFenceId(item.fenceId)) return;
42891
+ deps.storage.markSettlementsDeliveredBy(item.id);
42892
+ }
42893
+ function noteAnswersUndelivered(item) {
42894
+ if (!isQuestionAnswerFenceId(item.fenceId)) return;
42895
+ log3(`waker: answers for ${item.slug} stay UNDELIVERED \u2014 offered again once this server restarts, the worker runs a turn, or ${Math.round(ANSWER_REOFFER_BACKOFF_MS / 6e4)}m pass`);
42896
+ }
42669
42897
  function evalTimers(nowMs) {
42670
42898
  for (const timer2 of deps.storage.dueThreadTimers(nowMs)) {
42671
42899
  const row = deps.storage.getSession(timer2.thread_slug);
@@ -42824,7 +43052,7 @@ ${clock}`;
42824
43052
  continue;
42825
43053
  }
42826
43054
  if (context === "superseded") {
42827
- outbox.supersede(item.id, nowMs, "the exact awaiting fence or session was superseded");
43055
+ outbox.supersede(item.id, nowMs, CONTEXT_SUPERSEDED);
42828
43056
  settleSnooze(item);
42829
43057
  continue;
42830
43058
  }
@@ -42838,6 +43066,7 @@ ${clock}`;
42838
43066
  settleRestPrompt(item);
42839
43067
  settleCompactPrompt(item);
42840
43068
  settleTimer(item);
43069
+ settleQuestionAnswers(item);
42841
43070
  settleSignoffNudge(item);
42842
43071
  log3(`waker: delivered ${item.slug} \u2014 ${item.reason}${item.attempts > 1 ? ` (on attempt ${item.attempts})` : ""}`);
42843
43072
  }
@@ -42856,6 +43085,7 @@ ${clock}`;
42856
43085
  settleSnooze(item);
42857
43086
  settleTimer(item);
42858
43087
  log3(`waker: delivery EXHAUSTED for ${item.slug} after ${recovered2.attempts} attempts \u2014 ${recovered2.lastError ?? "unknown error"}`);
43088
+ noteAnswersUndelivered(item);
42859
43089
  } else {
42860
43090
  log3(`waker: wake LOST for ${item.slug} \u2014 ${item.reason}: the worker's process died before it read it; sending again (attempt ${item.attempts} of ${maxDeliveryAttempts} spent)`);
42861
43091
  }
@@ -42873,6 +43103,7 @@ ${clock}`;
42873
43103
  settleSnooze(item);
42874
43104
  settleTimer(item);
42875
43105
  log3(`waker: delivery EXHAUSTED for ${item.slug} after ${recovered.attempts} attempts \u2014 ${recovered.lastError ?? "unknown error"}`);
43106
+ noteAnswersUndelivered(item);
42876
43107
  }
42877
43108
  }
42878
43109
  }
@@ -42882,6 +43113,7 @@ ${clock}`;
42882
43113
  settleRestPrompt(item);
42883
43114
  settleCompactPrompt(item);
42884
43115
  settleTimer(item);
43116
+ settleQuestionAnswers(item);
42885
43117
  }
42886
43118
  function adoptCompanions(carrier, claimedAt) {
42887
43119
  if (isQuestionAnswerFenceId(carrier.fenceId)) return [];
@@ -42895,7 +43127,7 @@ ${clock}`;
42895
43127
  continue;
42896
43128
  }
42897
43129
  if (context === "superseded") {
42898
- outbox.supersede(sibling.id, now(), "the exact awaiting fence or session was superseded before delivery");
43130
+ outbox.supersede(sibling.id, now(), `${CONTEXT_SUPERSEDED} before delivery`);
42899
43131
  settleSnooze(sibling);
42900
43132
  continue;
42901
43133
  }
@@ -42952,7 +43184,7 @@ ${wakeTimeHeader(now(), spokeAt)}`;
42952
43184
  continue;
42953
43185
  }
42954
43186
  if (context === "superseded") {
42955
- outbox.supersede(item.id, now(), "the exact awaiting fence or session was superseded before delivery");
43187
+ outbox.supersede(item.id, now(), `${CONTEXT_SUPERSEDED} before delivery`);
42956
43188
  settleSnooze(item);
42957
43189
  continue;
42958
43190
  }
@@ -42985,6 +43217,7 @@ ${wakeTimeHeader(now(), spokeAt)}`;
42985
43217
  settleTimer(d);
42986
43218
  }
42987
43219
  log3(`waker: delivery ABANDONED for ${item.slug} (terminal, no retry): ${message2}`);
43220
+ for (const d of frame) noteAnswersUndelivered(d);
42988
43221
  continue;
42989
43222
  }
42990
43223
  for (const d of frame) {
@@ -43142,7 +43375,7 @@ ${wakeTimeHeader(now(), spokeAt)}`;
43142
43375
  }
43143
43376
  };
43144
43377
  }
43145
- var execFileAsync5, LIMIT_FENCE_PREFIX, LIMIT_HINT_PREFIX, LIMIT_RESUME_GRACE_MS, LIMIT_RESUME_HEADROOM_PERCENT, LIMIT_HEADROOM_MIN_FAULT_AGE_MS, REPORT_FENCE_PREFIX, REPORT_HINT_PREFIX, SNOOZE_FENCE_PREFIX, SNOOZE_HINT_PREFIX, HEARTBEAT_FENCE_PREFIX, HEARTBEAT_HINT_PREFIX, STOP_HOOK_FENCE_PREFIX, STOP_HOOK_HINT_KEY, COMPACT_FENCE_PREFIX, COMPACT_HINT_KEY, TIMER_FENCE_PREFIX, TIMER_HINT_PREFIX, SHELL_FENCE_PREFIX, INTERRUPT_ENDED_FENCE_PREFIX, PR_WATCH_FENCE_PREFIX, PR_WATCH_EXPIRED_FENCE_PREFIX, OWN_WATCH_EXPIRED_FENCE_PREFIX, QUESTION_ANSWER_FENCE_PREFIX, PR_WATCH_POLL_MS, PR_STATUS_FALLBACK_LIMIT, SIGNOFF_FENCE_PREFIX, SIGNOFF_HINT_KEY, SIGNOFF_NUDGE_MAX, PARK_BUMP_MAX, SIGNOFF_NUDGE_SETTING, PARK_FENCE_PREFIX, GITHUB_STATUS_CAP, REVIEW_SEEN_CAP, REVIEW_STEER_CAP, AWAITING_KEY_OF, MID_TURN_HOLD_MAX_MS, WAKE_HOLD_DEFERRAL;
43378
+ var execFileAsync5, LIMIT_FENCE_PREFIX, LIMIT_HINT_PREFIX, LIMIT_RESUME_GRACE_MS, LIMIT_RESUME_HEADROOM_PERCENT, LIMIT_HEADROOM_MIN_FAULT_AGE_MS, REPORT_FENCE_PREFIX, REPORT_HINT_PREFIX, SNOOZE_FENCE_PREFIX, SNOOZE_HINT_PREFIX, HEARTBEAT_FENCE_PREFIX, HEARTBEAT_HINT_PREFIX, STOP_HOOK_FENCE_PREFIX, STOP_HOOK_HINT_KEY, COMPACT_FENCE_PREFIX, COMPACT_HINT_KEY, TIMER_FENCE_PREFIX, TIMER_HINT_PREFIX, SHELL_FENCE_PREFIX, INTERRUPT_ENDED_FENCE_PREFIX, PR_WATCH_FENCE_PREFIX, PR_WATCH_EXPIRED_FENCE_PREFIX, OWN_WATCH_EXPIRED_FENCE_PREFIX, QUESTION_ANSWER_FENCE_PREFIX, ANSWER_REOFFER_BACKOFF_MS, CONTEXT_SUPERSEDED, PR_WATCH_POLL_MS, PR_STATUS_FALLBACK_LIMIT, SIGNOFF_FENCE_PREFIX, SIGNOFF_HINT_KEY, SIGNOFF_NUDGE_MAX, PARK_BUMP_MAX, SIGNOFF_NUDGE_SETTING, PARK_FENCE_PREFIX, GITHUB_STATUS_CAP, REVIEW_SEEN_CAP, REVIEW_STEER_CAP, AWAITING_KEY_OF, MID_TURN_HOLD_MAX_MS, WAKE_HOLD_DEFERRAL;
43146
43379
  var init_scheduler = __esm({
43147
43380
  "packages/server/src/scheduler.ts"() {
43148
43381
  "use strict";
@@ -43182,6 +43415,8 @@ var init_scheduler = __esm({
43182
43415
  PR_WATCH_EXPIRED_FENCE_PREFIX = "prwatch-expired";
43183
43416
  OWN_WATCH_EXPIRED_FENCE_PREFIX = "watch-expired";
43184
43417
  QUESTION_ANSWER_FENCE_PREFIX = "answers";
43418
+ ANSWER_REOFFER_BACKOFF_MS = 30 * 6e4;
43419
+ CONTEXT_SUPERSEDED = "the exact awaiting fence or session was superseded";
43185
43420
  PR_WATCH_POLL_MS = 6e4;
43186
43421
  PR_STATUS_FALLBACK_LIMIT = 4;
43187
43422
  SIGNOFF_FENCE_PREFIX = "signoff";
@@ -43814,6 +44049,7 @@ var init_acp_rpc = __esm({
43814
44049
  resolve13({ code: null, signal: null });
43815
44050
  });
43816
44051
  });
44052
+ process2.stdin?.on("error", (err) => this.settleClosed(`stdin: ${err.message}`));
43817
44053
  process2.stdout?.on("data", (chunk) => {
43818
44054
  this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
43819
44055
  let nl;
@@ -44378,6 +44614,8 @@ var init_acp_bridge = __esm({
44378
44614
  });
44379
44615
  }
44380
44616
  sessions = /* @__PURE__ */ new Map();
44617
+ // Opens in flight, by Frizz session id. See `openOnce`.
44618
+ opening = /* @__PURE__ */ new Map();
44381
44619
  unsubscribe;
44382
44620
  closed = false;
44383
44621
  now() {
@@ -44514,6 +44752,28 @@ var init_acp_bridge = __esm({
44514
44752
  this.sessions.set(sessionId, live);
44515
44753
  return { live, resumed };
44516
44754
  }
44755
+ /**
44756
+ * ONE open per session at a time; every concurrent caller shares it. The session's daemon is keyed by
44757
+ * session id — one record path, one socket path — so two opens racing for a session with no live
44758
+ * agent (the waker delivering answers while the operator sends, a boot `warmUp` under a follow-up)
44759
+ * each found no daemon and FORKED one. The second took the record and the socket path, and the two
44760
+ * attaches could reach the SAME daemon, which serves one client at a time: the later one hung up the
44761
+ * earlier, whose next stdin write failed with EPIPE — an unhandled stream error that took the whole
44762
+ * process down until acp-rpc.ts listened for it. Short of that, a failed open `stopAcpDaemon`s
44763
+ * whatever the record names (the other caller's agent), and the loser of the two `sessions.set`s
44764
+ * runs its turn on a second agent nobody tracks, appending to the same transcript.
44765
+ * The same race forked two Claude daemons on 2026-09-30 (claude-agent-broker-bridge.ts `attachOnce`);
44766
+ * scripts/verify-daemon-socket-takeover.mjs, scenario D, reproduces it here.
44767
+ */
44768
+ openOnce(input) {
44769
+ const inflight = this.opening.get(input.sessionId);
44770
+ if (inflight) return inflight;
44771
+ const opening = this.open(input).finally(() => {
44772
+ if (this.opening.get(input.sessionId) === opening) this.opening.delete(input.sessionId);
44773
+ });
44774
+ this.opening.set(input.sessionId, opening);
44775
+ return opening;
44776
+ }
44517
44777
  /** The agent's model option (`category: "model"`, else the one literally named `model`). */
44518
44778
  modelOption(configOptions) {
44519
44779
  return configOptions?.find((o) => o.category === "model") ?? configOptions?.find((o) => o.id === "model");
@@ -44618,7 +44878,7 @@ var init_acp_bridge = __esm({
44618
44878
  // ---- public API --------------------------------------------------------------------------------
44619
44879
  async spawnDispatch(input) {
44620
44880
  if (this.closed) throw new Error("ACP bridge is closed");
44621
- const { live } = await this.open(input);
44881
+ const { live } = await this.openOnce(input);
44622
44882
  void this.runTurn(live, input.prompt, input.userText, void 0);
44623
44883
  return this.info(live);
44624
44884
  }
@@ -44629,7 +44889,7 @@ var init_acp_bridge = __esm({
44629
44889
  let live = this.sessions.get(input.sessionId);
44630
44890
  let resumed = "live";
44631
44891
  if (!live || live.exited || live.conn.closed) {
44632
- const opened = await this.open(input);
44892
+ const opened = await this.openOnce(input);
44633
44893
  live = opened.live;
44634
44894
  resumed = opened.resumed === "reattached" ? "live" : opened.resumed;
44635
44895
  }
@@ -44679,7 +44939,7 @@ var init_acp_bridge = __esm({
44679
44939
  if (!liveAcpDaemonRecord(this.options.stateDir, row.sessionId)) continue;
44680
44940
  if (this.sessions.has(row.sessionId)) continue;
44681
44941
  try {
44682
- await this.open(row);
44942
+ await this.openOnce(row);
44683
44943
  } catch (err) {
44684
44944
  this.diagnostic(row.threadSlug, "reattach", `could not reattach to the running agent: ${err.message}`);
44685
44945
  }
@@ -71415,8 +71675,9 @@ function createContextUnchecked(opts, resources) {
71415
71675
  const line = describeClaudeBrokerDiagnostic(diagnostic);
71416
71676
  if (line) log.warn("broker", `claude broker ${slug}: ${line}`);
71417
71677
  const dropped = droppedDeliveryId(diagnostic);
71418
- if (dropped) {
71419
- cancelDelivery(storage, slug, dropped);
71678
+ if (dropped && recordDeliveryDropped(storage, slug, dropped, droppedDeliveryReason(diagnostic))) {
71679
+ log.warn("broker", `claude broker ${slug}: follow-up ${dropped} was dropped by the daemon and is kept for the operator`);
71680
+ transcriptChange.emit([slug]);
71420
71681
  resources.board?.refresh();
71421
71682
  }
71422
71683
  }
@@ -75676,7 +75937,7 @@ function backgroundWakeLabel(call, status, raw2) {
75676
75937
  const rawDesc = (call.desc ?? call.detail ?? "background command").trim();
75677
75938
  const desc = rawDesc.length > 64 ? `${rawDesc.slice(0, 63)}\u2026` : rawDesc;
75678
75939
  let outcome;
75679
- if (raw2.includes("<event>[Monitor timed out")) outcome = "timed out";
75940
+ if (isMonitorTimeoutEvent(raw2)) outcome = "timed out";
75680
75941
  else if (status === "completed") outcome = "finished";
75681
75942
  else if (status === "killed") outcome = "stopped";
75682
75943
  else {
@@ -75689,7 +75950,7 @@ function backgroundWakeLabel(call, status, raw2) {
75689
75950
  function uncorrelatedWakeLabel(rawDesc, status, raw2) {
75690
75951
  const desc = rawDesc.length > 64 ? `${rawDesc.slice(0, 63)}\u2026` : rawDesc;
75691
75952
  let outcome;
75692
- if (raw2.includes("<event>[Monitor timed out")) outcome = "timed out";
75953
+ if (isMonitorTimeoutEvent(raw2)) outcome = "timed out";
75693
75954
  else if (status === "completed") outcome = "finished";
75694
75955
  else if (status === "killed") outcome = "stopped";
75695
75956
  else {
@@ -75718,7 +75979,7 @@ function completionEvents(rec, dispatches, backgroundShells, backgroundTaskIds,
75718
75979
  const out = [];
75719
75980
  for (const block of raw2.match(/<task-notification>[\s\S]*?<\/task-notification>/g) ?? []) {
75720
75981
  const rawStatus = block.match(/<status>([^<]*)<\/status>/)?.[1];
75721
- const timedOut2 = block.includes("<event>[Monitor timed out");
75982
+ const timedOut2 = isMonitorTimeoutEvent(block);
75722
75983
  const status = rawStatus === "completed" || rawStatus === "failed" || rawStatus === "killed" ? rawStatus : rawStatus === "stopped" || timedOut2 ? "killed" : void 0;
75723
75984
  if (!status) continue;
75724
75985
  const ids = /* @__PURE__ */ new Set();
@@ -77132,14 +77393,19 @@ function emptyTranscriptPage(source) {
77132
77393
  editedFiles: []
77133
77394
  };
77134
77395
  }
77396
+ function emptyLatestTranscriptPage(storage, slug, source) {
77397
+ const page = emptyTranscriptPage(source);
77398
+ const row = storage.getSession(slug);
77399
+ return row ? { ...page, messages: projectDeliveryLedger([], parseDeliveryLedger(row.delivery_ledger)) } : page;
77400
+ }
77135
77401
  function readLatestThreadTranscriptPage(project, storage, slug, backendFor, opts = {}) {
77136
77402
  let source = sourceForThread(project, storage, slug, backendFor);
77137
- if (!source) return emptyTranscriptPage();
77403
+ if (!source) return emptyLatestTranscriptPage(storage, slug);
77138
77404
  let snapshot = fixedSnapshot(source);
77139
77405
  if (!snapshot) {
77140
77406
  const discovered = discoveredClaudeSource(project, storage, slug);
77141
77407
  const discoveredSnapshot = discovered ? fixedSnapshot(discovered) : void 0;
77142
- if (!discoveredSnapshot) return emptyTranscriptPage(source);
77408
+ if (!discoveredSnapshot) return emptyLatestTranscriptPage(storage, slug, source);
77143
77409
  source = discovered;
77144
77410
  snapshot = discoveredSnapshot;
77145
77411
  }