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.
- package/dist/acp-daemon.js +27 -3
- package/dist/claude-agent-broker.js +27 -21
- package/dist/codex-app-server-daemon.js +27 -3
- package/dist/dev-child.js +421 -155
- package/package.json +1 -1
- package/runtime/cc-worker/bin/frizz-mcp.mjs +21 -17
- package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-DwSF6UVy.js → abnfDiagram-VRR7QNED-5ei_4zAM.js} +1 -1
- package/web-dist/assets/{arc-BO1i7GWM.js → arc-IM-a48Qs.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-bSjZeCDP.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-msNhjgsP.js → architectureDiagram-ZJ3FMSHR-2XGTphLh.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-JCCwJqsE.js → blockDiagram-677ZJIJ3-DZM9KutE.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-B5vS6rLH.js → c4Diagram-LMCZKHZV-CKa_m4-S.js} +1 -1
- package/web-dist/assets/channel-CZsSDpaO.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-BeH_kPjp.js → chunk-32BRIVSS-BLSMcIRY.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-D0OtliIj.js → chunk-52WLFC77-DevWfx8Y.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-wbaKYBGi.js → chunk-C7G6YPKG-CsLDw_Vc.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-JrdwUCV4.js → chunk-EX3LRPZG-CAVfNVN8.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-dk5peTlq.js → chunk-FWX5IMBZ-ba3X5gAQ.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-DEVMSmR0.js → chunk-HOUHSVGY-Bm5rhDGu.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-Q4B4QfXk.js → chunk-ICXQ74PX-DKF-sXIi.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-CFljAhrA.js → chunk-MOJQB5TN-19qz3xL4.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-uDxaNMkK.js → chunk-OGEWGWER-DLdOho5W.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-B1_PcfSW.js → chunk-PUDLZKDR-C1Nj5k8G.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-CTG5ESkU.js → chunk-Q4XR5HBZ-CPx-9pEy.js} +1 -1
- package/web-dist/assets/{chunk-RYQCIY6F-C4Xbw0h0.js → chunk-RYQCIY6F-BVfqVyNV.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-DzX7WzjT.js → chunk-V7JOEXUC-Bq5EG6OC.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-C5BV4_RN.js → chunk-VAUOI2AC-CyfJDqu-.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-WYsy9hUs.js → chunk-VR4S4FIN-DMki15_r.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-D-1-l4MF.js → chunk-WYO6CB5R-C30gZnnl.js} +1 -1
- package/web-dist/assets/{chunk-XXDRQBXY-C9lzowcJ.js → chunk-XXDRQBXY-CP4D_ETY.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-7KVpod10.js → chunk-ZGVPDNZ5-k3kNBN88.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-he8mN_1d.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-he8mN_1d.js +1 -0
- package/web-dist/assets/{cose-bilkent-JH36ORCC-Cjxk1BZr.js → cose-bilkent-JH36ORCC-wO3EsovI.js} +1 -1
- package/web-dist/assets/{cynefin-VYW2F7L2-wOe2bHzn.js → cynefin-VYW2F7L2-C3EU9o4N.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-aC9cGB5C.js → cynefinDiagram-TSTJHNR4-CGG-zKEM.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-C8WIcb6F.js → dagre-VKFMJZFB-DyC8Hqa9.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-C3aPYv7S.js → diagram-FQU43EPY-B0kKGxoR.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-B4wmfzMx.js → diagram-G47NLZAW-B0HF-C4G.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-DVeUa_5D.js → diagram-NH7WQ7WH-ZnEvLrYH.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-BaQwQu_F.js → diagram-OA4YK3LP-ynOqxQd4.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-DqprB85c.js → diagram-WEI45ONY-DeweEbe9.js} +1 -1
- package/web-dist/assets/{dist-rtzL4gLm.js → dist-DjR2jyYf.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Mm1irNb1.js → ebnfDiagram-CCIWWBDH-PvzgPvcs.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-MKBulWN5.js → erDiagram-Q63AITRT-DXj9ksQt.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-sgc1I6jd.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-CgBJXm5_.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-D2y3Bf3l.js → ganttDiagram-NO4QXBWP-jO6JaqFN.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-O-LVx0X5.js → gitGraph-TEB2WS4Q-CwIqpSfV.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DcQEaYcT.js → gitGraphDiagram-IHSO6WYX-CX-kdWUp.js} +1 -1
- package/web-dist/assets/{index-DpPWC0ta.js → index-BSuA-nmx.js} +90 -90
- package/web-dist/assets/index-WdL7ce5m.css +2 -0
- package/web-dist/assets/{info-DKCQHKI2-CY6fWeew.js → info-DKCQHKI2-CvH1I_qF.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-BVfQV-3W.js → infoDiagram-FWYZ7A6U-C9NZZW2e.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-dwjcM4Ez.js → ishikawaDiagram-FXEZZL3T-uLWFep7Q.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-CUaZBvLp.js → journeyDiagram-5HDEW3XC-CsXFiBiD.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-DE8wReP0.js → kanban-definition-HUTT4EX6-DK9QRwpc.js} +1 -1
- package/web-dist/assets/{line-zArFI85C.js → line-Cwz0rEtC.js} +1 -1
- package/web-dist/assets/{linear-BSG56Yjb.js → linear-BENsfGox.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-BxbE62as.js → mermaid-parser.core-DhmoiWF3.js} +3 -3
- package/web-dist/assets/{mermaid.core-C9Bw1zsy.js → mermaid.core-BDr_Kn5Z.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-BhguO5c0.js → mindmap-definition-LN4V7U3C-CjIFm3K2.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-C9Him4Jg.js → packet-7NZHBO7P-CzCyao17.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-D4wv-hVO.js → pegDiagram-2B236MQR-BmDMeRcD.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-CXMTL6pQ.js → pie-RZYD4A2V-DMM2IkHs.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-CHBrF8Gr.js → pieDiagram-ENE6RG2P-CbwVr3qW.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-yT8E8mdN.js → quadrantDiagram-ABIIQ3AL-De32pmiP.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-DTFJXgLK.js → radar-I7S5WNFK-CbsD9oXO.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-9El5FU8p.js → railroad-3IZDKUUU-DBxDhaRB.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-CHHGvNsw.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-CNbn6hsV.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-BsBO5cGs.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-DHRB37mq.js → railroadDiagram-RFXS5EU6-Cc2vK3CJ.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-Cj41jOCz.js → requirementDiagram-TGXJPOKE-BindEO0A.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-Czb26Wy-.js → sankeyDiagram-HTMAVEWB-4Y4Mv2hM.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-C_WGyZ5J.js → sequenceDiagram-DBY2YBRQ-BjN0lvxQ.js} +1 -1
- package/web-dist/assets/{src-BH3865Hl.js → src-COVUvSFq.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DwKIv4VY.js → stateDiagram-2N3HPSRC-CW38CSkC.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-gZiZetc1.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-Bf3tF-DM.js → swimlanes-5IMT3BWC-TQBpYgBC.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-DWpWeiSt.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-X_Wo2yjO.js → timeline-definition-FHXFAJF6-BET2st28.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-Cdlwh2BX.js → treeView-QDETBFTQ-C8KMnMpR.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-ByVScQ5B.js → treemap-6X3UGDF4-T0GBmSXN.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-B7dNcpqz.js → vennDiagram-L72KCM5P-U1-fDVAv.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-CEiXyYvs.js → wardley-OPB4EBWU-8e8yAPGj.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-CehzwZ8g.js → wardleyDiagram-EHGQE667-CWHLRs2-.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-BmuVKE3h.js → xychartDiagram-FW5EYKEG-BNqGD1uM.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-BbpJdZVL.js +0 -1
- package/web-dist/assets/channel-D0yqk-I_.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BrNNI0PJ.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BrNNI0PJ.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-JhoriiTk.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-BY5NuqLK.js +0 -1
- package/web-dist/assets/index-BSpdKQMe.css +0 -2
- package/web-dist/assets/railroad-abnf-AHOZXSZD-DfSVqPIB.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-vIxB5rhr.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-7hKGS43v.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-e_B-QQch.js +0 -1
- 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
|
|
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
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
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 ?
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
36634
|
-
later, with none of your context \u2014 and they have seen NOTHING since their own last
|
|
36635
|
-
prompt in between (the Goal, a sign-off reminder, a watcher wake) came from frizz
|
|
36636
|
-
whole stretch since their last message: what you did,
|
|
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\`
|
|
36645
|
-
|
|
36646
|
-
|
|
36647
|
-
|
|
36648
|
-
|
|
36649
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
**
|
|
36772
|
-
|
|
36773
|
-
frizz restart, and
|
|
36774
|
-
|
|
36775
|
-
|
|
36776
|
-
|
|
36777
|
-
|
|
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,
|
|
36807
|
-
|
|
36808
|
-
|
|
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
|
|
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
|
-
|
|
42066
|
-
if (
|
|
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,
|
|
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([
|
|
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
|
-
|
|
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,
|
|
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(),
|
|
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(),
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
77408
|
+
if (!discoveredSnapshot) return emptyLatestTranscriptPage(storage, slug, source);
|
|
77143
77409
|
source = discovered;
|
|
77144
77410
|
snapshot = discoveredSnapshot;
|
|
77145
77411
|
}
|