frizz 0.6.0 → 0.7.0
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/README.md +8 -5
- package/dist/dev-child.js +49 -14
- package/dist/frizz.js +38 -17
- package/package.json +1 -1
- package/runtime/cc-worker/bin/frizz-mcp.mjs +2 -2
- package/runtime/cc-worker/hooks/session-seed.mjs +2 -2
- package/runtime/cc-worker/skills/gh/SKILL.md +8 -7
- package/runtime/cc-worker/skills/waits/SKILL.md +4 -3
- package/web-dist/assets/{TerminalPane-GfnCbZQ8.js → TerminalPane-DW91LtY6.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-By12QfQW.js → abnfDiagram-VRR7QNED-Cz9pKsnx.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-pEHgxrgQ.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CcXU45QO.js → architectureDiagram-ZJ3FMSHR-Cx1Inab9.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-CmzyO2OF.js → blockDiagram-677ZJIJ3-Dkow3KWq.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-DFGSW_ZS.js → c4Diagram-LMCZKHZV-D54f0v8B.js} +1 -1
- package/web-dist/assets/channel-DmtoXjBB.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-whgaFxPY.js → chunk-32BRIVSS-5HfLNqOq.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DCT0MLgl.js → chunk-52WLFC77-CoVH-jjk.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-Br0OaxIX.js → chunk-C7G6YPKG-w41h00On.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-CUnQkdEF.js → chunk-EX3LRPZG-BC_Frphy.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-D1NqKRFr.js → chunk-FWX5IMBZ-DLZVUhWr.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-7ZL78OrO.js → chunk-HOUHSVGY-DCl7dozs.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-QSgBAuYw.js → chunk-ICXQ74PX-I8bc3hpu.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-DOI-0kaG.js → chunk-MOJQB5TN-D1tbS5-A.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-CZ3ED-QB.js → chunk-OGEWGWER-CLa17J5_.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-BQJElUFX.js → chunk-PUDLZKDR-DcYIQuxf.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-5B6Qs7rQ.js → chunk-Q4XR5HBZ-DGHqsWli.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-DkMaAMFK.js → chunk-V7JOEXUC-DdVYI4tg.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-Bvq-8GYO.js → chunk-VAUOI2AC-Dn_uoDoN.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-DRVTU-HZ.js → chunk-VR4S4FIN-AM_J_EWt.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-CRxI5d1E.js → chunk-WYO6CB5R-BldKUlC0.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-D_3ow6xU.js → chunk-ZGVPDNZ5-Oh85mcuX.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-CUGsPtaX.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-CUGsPtaX.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-Bexf41pD.js → cynefin-VYW2F7L2-5lF8kTLT.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-9gbbl1EH.js → cynefinDiagram-TSTJHNR4-_otwu953.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-B04shbL0.js → dagre-VKFMJZFB-DxX5d4Jp.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-CGXlhEsB.js → diagram-FQU43EPY-JkCl7NEC.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-DmPCeTOa.js → diagram-G47NLZAW-ETwnVi8G.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-D8Hthvi7.js → diagram-NH7WQ7WH-DZXKHMOv.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-DbLR6tfL.js → diagram-OA4YK3LP--Yt9Hbcy.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-B6YWev_0.js → diagram-WEI45ONY-BXUskT8t.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-8G1uf683.js → ebnfDiagram-CCIWWBDH-C7E1X1Dl.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-U_VRdQHz.js → erDiagram-Q63AITRT-DOfGp1t7.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-D47al60X.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-Cfew-ywI.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-Cnsi9uVf.js → ganttDiagram-NO4QXBWP-P5HZV3uF.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-DYf7ka-G.js → gitGraph-TEB2WS4Q-CsJYjOme.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-Bvmr7dEH.js → gitGraphDiagram-IHSO6WYX-BNyrduBc.js} +1 -1
- package/web-dist/assets/{index-Czv3R-v0.js → index-CYVVF_ZU.js} +113 -113
- package/web-dist/assets/index-CjEngYN7.css +1 -0
- package/web-dist/assets/{info-DKCQHKI2-CdP9Q9r0.js → info-DKCQHKI2-WzpT_dbU.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-RBcOH3Pi.js → infoDiagram-FWYZ7A6U-CA_SxvAY.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-zS0gy3fS.js → ishikawaDiagram-FXEZZL3T-DXMy0GH9.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-cOciJlFY.js → journeyDiagram-5HDEW3XC-Bwmn4bNv.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-B-v0XClo.js → kanban-definition-HUTT4EX6-D265Z0Fg.js} +1 -1
- package/web-dist/assets/{line-z44YXdci.js → line-DNn_Towe.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-0Bg1x4p0.js → mermaid-parser.core-3CrMhKoT.js} +3 -3
- package/web-dist/assets/{mermaid.core-ByFqgcIE.js → mermaid.core-DfCFu8_x.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DiGaAQFh.js → mindmap-definition-LN4V7U3C-CQ5g8Qrh.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-Bv8ILo_S.js → packet-7NZHBO7P-CRDQ5t5W.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-CtOUW7n0.js → pegDiagram-2B236MQR-B0XFyb7d.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-Dz4Bm-Vx.js → pie-RZYD4A2V-DqjiBFGO.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-VMY_CK3i.js → pieDiagram-ENE6RG2P-C41GJ3-U.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-V7HZxM7j.js → quadrantDiagram-ABIIQ3AL-9cF0WJaM.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CWk6RruH.js → radar-I7S5WNFK-S1F7Z4Kj.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-m_8st_Ez.js → railroad-3IZDKUUU-da9Pjcp1.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-cPb8e4yf.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-BdArTI_P.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-cRtFMlRA.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-Dz80V-7-.js → railroadDiagram-RFXS5EU6-CkXBDByE.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-DrDDgTwm.js → requirementDiagram-TGXJPOKE-CL9Famg0.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-D4SeC-Bi.js → sankeyDiagram-HTMAVEWB-LyB-A0pD.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CA-ZO_qp.js → sequenceDiagram-DBY2YBRQ-DXfA9UDM.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-C_ZYvV2s.js → stateDiagram-2N3HPSRC-DgxMkPRv.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BVl1T6pD.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-DGMDseYx.js → swimlanes-5IMT3BWC-TvRb3Iib.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-CaBRomjp.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-CEiMp3_j.js → timeline-definition-FHXFAJF6-BBvC3bzZ.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-BSspY0US.js → treeView-QDETBFTQ-CvCYIkJk.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-DXNQxmQ7.js → treemap-6X3UGDF4-Bj60PrS0.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-C-khqkdm.js → vennDiagram-L72KCM5P-CLuLJiNi.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-B9QCQ_Tm.js → wardley-OPB4EBWU-C4tNB2Mi.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-vhVpAUzh.js → wardleyDiagram-EHGQE667-D_bno6Pc.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-B8ev9zqs.js → xychartDiagram-FW5EYKEG-CklFVNTA.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-XOYod28l.js +0 -1
- package/web-dist/assets/channel-Bqn3pFKp.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-DEMCehy_.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-DEMCehy_.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DIpO2jQx.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-aERDdceE.js +0 -1
- package/web-dist/assets/index-D8iyWUZ0.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-DIaRscxr.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-CeHpbK-W.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-CbhVIbOj.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BLevFa3H.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-00mQRq57.js +0 -8
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ Frizz is a browser tab, a queue, and the agent CLIs you already pay for. It brin
|
|
|
62
62
|
- 🐙 **GitHub integration.** Browse your repo's issues and pull requests without leaving the composer, and turn a selection of them into threads. Workers can read issues, diffs, and CI on their own.
|
|
63
63
|
- 👀 **Built-in CI and PR watchers.** A worker waiting on a build or a review doesn't hand the thread back to you to be told "keep going." It watches, and picks the work back up when the run goes green or a review lands.
|
|
64
64
|
- 📝 **No magic.** A thread behaves like a Claude Code session you started yourself. Frizz adds no worktrees, no branches, no dev server, no build integration, no workflow engine to fight with.
|
|
65
|
-
- 🔒 **Local only.** No cloud, no account, no telemetry. The server binds `127.0.0.1` by default and its state lives in your user directory, never in your checkout.
|
|
65
|
+
- 🔒 **Local only.** No cloud, no account, no telemetry. The server binds `127.0.0.1` by default and its state lives in your user directory, never in your checkout. To reach a board from a phone, put your own tunnel or proxy in front of it — see [Remote access](docs/remote-access.md).
|
|
66
66
|
|
|
67
67
|
### The queue
|
|
68
68
|
|
|
@@ -121,11 +121,12 @@ $ npx frizz --help
|
|
|
121
121
|
|
|
122
122
|
Frizz production launcher
|
|
123
123
|
|
|
124
|
-
Usage: npx frizz [options]
|
|
125
|
-
npx frizz up start the server plus its public tunnel
|
|
124
|
+
Usage: npx frizz [options]
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
Run it in the directory you want to work in. One server serves EVERY project on this machine,
|
|
127
|
+
each at its own /project/<name> URL, so a second run joins the one already going. Runs the
|
|
128
|
+
npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev only
|
|
129
|
+
for a source checkout.
|
|
129
130
|
|
|
130
131
|
Options:
|
|
131
132
|
--app use the legacy dedicated app window instead of a browser tab
|
|
@@ -134,6 +135,8 @@ Options:
|
|
|
134
135
|
--host [address] serve on a network address instead of loopback (bare --host means 0.0.0.0)
|
|
135
136
|
--allowed-host <name> with --host, also accept this DNS name as the board's address (repeatable)
|
|
136
137
|
--public-origin <url> serve behind a proxy/tunnel reachable at this exact origin
|
|
138
|
+
--cloud serve at the saved hostname and run its Cloudflare tunnel as a child
|
|
139
|
+
process; asked once, then remembered
|
|
137
140
|
--link print a fresh single-use access link for the running board
|
|
138
141
|
--debug stream the full event feed to the terminal instead of the compact readout
|
|
139
142
|
-h, --help show this help
|
package/dist/dev-child.js
CHANGED
|
@@ -13598,7 +13598,7 @@ var init_src = __esm({
|
|
|
13598
13598
|
].join("\n");
|
|
13599
13599
|
PARK_CORRECTION_NAMES_LEAD = "\u26A0\uFE0F Your ```awaiting fence names ";
|
|
13600
13600
|
PARK_CORRECTION_RETIRED_LEAD = "\u26D4 Your ```awaiting fence uses ";
|
|
13601
|
-
ThreadWatchKind = external_exports.enum(["shell", "github"]);
|
|
13601
|
+
ThreadWatchKind = external_exports.enum(["shell", "github", "timer"]);
|
|
13602
13602
|
GithubChecksState = external_exports.enum(["none", "running", "passing", "failing"]);
|
|
13603
13603
|
GithubMergeState = external_exports.enum(["mergeable", "blocked", "conflicting", "unknown"]);
|
|
13604
13604
|
GithubWatchStatus = external_exports.object({
|
|
@@ -13632,7 +13632,12 @@ var init_src = __esm({
|
|
|
13632
13632
|
state: external_exports.enum(["armed", "fired", "dropped"]),
|
|
13633
13633
|
createdAt: external_exports.string(),
|
|
13634
13634
|
/** `github` rows only, and absent until the first successful poll. */
|
|
13635
|
-
github: GithubWatchStatus.optional()
|
|
13635
|
+
github: GithubWatchStatus.optional(),
|
|
13636
|
+
/** `timer` rows only: the armed timer's own registration, which is everything the row renders — the
|
|
13637
|
+
* worker's prompt is the row's NAME (a `tmr_…` id names nothing to a human) and the fire instant is
|
|
13638
|
+
* its status. Unlike `github` there is no polled half to be absent: a timer that exists is fully
|
|
13639
|
+
* known, so a timer row always carries this. */
|
|
13640
|
+
timer: external_exports.object({ fireAt: external_exports.string(), prompt: external_exports.string() }).strict().optional()
|
|
13636
13641
|
}).strict();
|
|
13637
13642
|
PR_WATCH_DEFAULT_FOR_MS = 6 * 60 * 60 * 1e3;
|
|
13638
13643
|
PR_WATCH_MAX_ARMED = 32;
|
|
@@ -21112,13 +21117,13 @@ function dropCancelled(messages, items) {
|
|
|
21112
21117
|
const dropped = /* @__PURE__ */ new Set();
|
|
21113
21118
|
for (const item of items) {
|
|
21114
21119
|
if (item.state !== "cancelled") continue;
|
|
21115
|
-
const text =
|
|
21120
|
+
const text = renderMatchKey(item.text);
|
|
21116
21121
|
const from = Date.parse(item.at) - 5e3;
|
|
21117
21122
|
const to = Date.parse(item.updatedAt) + CANCEL_MATCH_SLACK_MS;
|
|
21118
21123
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
21119
21124
|
const m = messages[i];
|
|
21120
21125
|
if (m.role !== "user" || dropped.has(i)) continue;
|
|
21121
|
-
if (
|
|
21126
|
+
if (renderMatchKey(m.text) !== text) continue;
|
|
21122
21127
|
const at = m.at ? Date.parse(m.at) : NaN;
|
|
21123
21128
|
if (Number.isFinite(at) && Number.isFinite(from) && Number.isFinite(to) && (at < from || at > to)) continue;
|
|
21124
21129
|
dropped.add(i);
|
|
@@ -21134,13 +21139,13 @@ function projectDeliveryLedger(messages, items) {
|
|
|
21134
21139
|
if (!cancelled.live.length) return messages;
|
|
21135
21140
|
const claimed = /* @__PURE__ */ new Set();
|
|
21136
21141
|
for (const item of cancelled.live) {
|
|
21137
|
-
const text =
|
|
21142
|
+
const text = renderMatchKey(item.text);
|
|
21138
21143
|
const tag = deliveryTag(item.id);
|
|
21139
21144
|
let handled = false;
|
|
21140
21145
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
21141
21146
|
const m = messages[i];
|
|
21142
21147
|
if (m.role !== "user" || claimed.has(i)) continue;
|
|
21143
|
-
if (!decodeDeliveryMarkers(m.text).includes(tag) &&
|
|
21148
|
+
if (!decodeDeliveryMarkers(m.text).includes(tag) && renderMatchKey(m.text) !== text) continue;
|
|
21144
21149
|
if (m.queued) {
|
|
21145
21150
|
if (item.state === "delivered") {
|
|
21146
21151
|
messages[i] = { ...m, queued: false, deliveryId: item.id, deliveryState: item.state };
|
|
@@ -21171,10 +21176,11 @@ function projectDeliveryLedger(messages, items) {
|
|
|
21171
21176
|
}
|
|
21172
21177
|
return messages;
|
|
21173
21178
|
}
|
|
21174
|
-
var PENDING_TIMEOUT_MS, UNCONFIRMED_DROP_MS, MAX_LEDGER_ITEMS, MAX_CANCELLED_ITEMS, canon, COMPOSED_ANCHOR_MIN, CANCEL_MATCH_SLACK_MS;
|
|
21179
|
+
var PENDING_TIMEOUT_MS, UNCONFIRMED_DROP_MS, MAX_LEDGER_ITEMS, MAX_CANCELLED_ITEMS, canon, COMPOSED_ANCHOR_MIN, renderMatchKey, CANCEL_MATCH_SLACK_MS;
|
|
21175
21180
|
var init_delivery_ledger = __esm({
|
|
21176
21181
|
"packages/server/src/delivery-ledger.ts"() {
|
|
21177
21182
|
"use strict";
|
|
21183
|
+
init_src();
|
|
21178
21184
|
init_delivery_marker();
|
|
21179
21185
|
PENDING_TIMEOUT_MS = 6e4;
|
|
21180
21186
|
UNCONFIRMED_DROP_MS = 60 * 6e4;
|
|
@@ -21182,6 +21188,7 @@ var init_delivery_ledger = __esm({
|
|
|
21182
21188
|
MAX_CANCELLED_ITEMS = 12;
|
|
21183
21189
|
canon = (s) => s.replace(/\s+/g, " ").trim();
|
|
21184
21190
|
COMPOSED_ANCHOR_MIN = 24;
|
|
21191
|
+
renderMatchKey = (s) => canon(stripHumanGapNote(stripWakeDeliveryToken(stripDeliveryMarkers(s))));
|
|
21185
21192
|
CANCEL_MATCH_SLACK_MS = 5e3;
|
|
21186
21193
|
}
|
|
21187
21194
|
});
|
|
@@ -35651,9 +35658,14 @@ function hasDeclaredBackgroundPark(tele, nowMs) {
|
|
|
35651
35658
|
if (Number.isFinite(restedMs) && nowMs - restedMs > DECLARED_PARK_MAX_MS) return false;
|
|
35652
35659
|
return true;
|
|
35653
35660
|
}
|
|
35654
|
-
function
|
|
35661
|
+
function hasParkedTimerWatch(tele, armedTimerIds) {
|
|
35662
|
+
if (tele?.lastFence?.kind !== "awaiting") return false;
|
|
35663
|
+
return tele.lastFence.hints.some((hint) => hint.kind === "timer" && armedTimerIds.has(hint.value.trim()));
|
|
35664
|
+
}
|
|
35665
|
+
function hasDeclaredWait(tele, nowMs, armedTimerIds = /* @__PURE__ */ new Set()) {
|
|
35655
35666
|
if (hasDeclaredBackgroundPark(tele, nowMs)) return true;
|
|
35656
35667
|
if (tele?.lastFence?.kind === "awaiting" && hasParkedPrWatch(tele)) return true;
|
|
35668
|
+
if (hasParkedTimerWatch(tele, armedTimerIds)) return true;
|
|
35657
35669
|
return (tele?.subAgents ?? []).some((a) => isDirectSubAgent(a) && a.state === "running");
|
|
35658
35670
|
}
|
|
35659
35671
|
function heldByRunningChecks(tele, github, registered) {
|
|
@@ -35691,9 +35703,23 @@ function bgSnoozeArmed(row) {
|
|
|
35691
35703
|
function hasParkedExternalWait(tele, _nowMs) {
|
|
35692
35704
|
return false;
|
|
35693
35705
|
}
|
|
35694
|
-
function fenceWatchViews(slug, tele, fenceAt, github = {}, registered = []) {
|
|
35706
|
+
function fenceWatchViews(slug, tele, fenceAt, github = {}, registered = [], armedTimers = []) {
|
|
35695
35707
|
const seen = /* @__PURE__ */ new Set();
|
|
35696
35708
|
const out = [];
|
|
35709
|
+
for (const t of armedTimers) {
|
|
35710
|
+
if (seen.has(`timer:${t.id}`)) continue;
|
|
35711
|
+
seen.add(`timer:${t.id}`);
|
|
35712
|
+
out.push({
|
|
35713
|
+
id: `timer:${slug}:${t.id}`,
|
|
35714
|
+
kind: "timer",
|
|
35715
|
+
target: t.id,
|
|
35716
|
+
state: "armed",
|
|
35717
|
+
createdAt: t.createdAt,
|
|
35718
|
+
// Always present, unlike the github half above: a timer that exists is fully known — there is no
|
|
35719
|
+
// poller to wait for. The prompt is the row's NAME client-side (a `tmr_…` id names nothing).
|
|
35720
|
+
timer: { fireAt: t.fireAt, prompt: t.prompt }
|
|
35721
|
+
});
|
|
35722
|
+
}
|
|
35697
35723
|
for (const w of registered) {
|
|
35698
35724
|
if (seen.has(`github:${w.target}`)) continue;
|
|
35699
35725
|
seen.add(`github:${w.target}`);
|
|
@@ -35742,11 +35768,13 @@ function deriveNeedsYou(row, tele, runtime, hasActionableInteraction = false, no
|
|
|
35742
35768
|
if (tele?.lastFence?.kind === "done") return true;
|
|
35743
35769
|
return true;
|
|
35744
35770
|
}
|
|
35745
|
-
function deriveAwaitingBackground(row, tele, runtime, hasActionableInteraction = false, nowMs = Date.now(), limitPause = void 0, deliveryProcessGone = false, github = {}, registeredPrWatches = /* @__PURE__ */ new Set()) {
|
|
35746
|
-
if (runtime !== "turn-idle" || !hasDeclaredWait(tele, nowMs)) return false;
|
|
35771
|
+
function deriveAwaitingBackground(row, tele, runtime, hasActionableInteraction = false, nowMs = Date.now(), limitPause = void 0, deliveryProcessGone = false, github = {}, registeredPrWatches = /* @__PURE__ */ new Set(), armedTimerIds = /* @__PURE__ */ new Set()) {
|
|
35772
|
+
if (runtime !== "turn-idle" || !hasDeclaredWait(tele, nowMs, armedTimerIds)) return false;
|
|
35747
35773
|
if (hasActionableInteraction || tele?.pendingAsk || tele?.pendingQuestion) return false;
|
|
35748
35774
|
if (tele?.lastFence?.kind === "done") return false;
|
|
35749
|
-
if (tele?.lastFence?.kind === "awaiting" && !hasParkedPrWatch(tele) && !hasDeclaredBackgroundPark(tele, nowMs))
|
|
35775
|
+
if (tele?.lastFence?.kind === "awaiting" && !hasParkedPrWatch(tele) && !hasDeclaredBackgroundPark(tele, nowMs) && // A TIMER PARK IS THE SAME EXCEPTION AGAIN (2026-08-24): its fence has no park action either, so
|
|
35776
|
+
// suppressing this card left the wait stated nowhere but the fence's own machinery footer.
|
|
35777
|
+
!hasParkedTimerWatch(tele, armedTimerIds)) return false;
|
|
35750
35778
|
return deriveNeedsYou({ ...row, bg_snooze_rested_at: null }, tele, runtime, hasActionableInteraction, nowMs, limitPause, false, deliveryProcessGone);
|
|
35751
35779
|
}
|
|
35752
35780
|
function resolveSessionProfile(row, tele) {
|
|
@@ -35830,6 +35858,13 @@ function sessionThreadView(projectDir, storage, row, tele, registeredLegacyTermi
|
|
|
35830
35858
|
createdAt: new Date(w.created_at).toISOString()
|
|
35831
35859
|
}));
|
|
35832
35860
|
const registeredPrWatches = new Set(armedPrWatches.map((w) => w.target));
|
|
35861
|
+
const armedTimers = storage.listThreadTimers(row.slug, { armedOnly: true }).map((t) => ({
|
|
35862
|
+
id: t.id,
|
|
35863
|
+
prompt: t.prompt,
|
|
35864
|
+
fireAt: new Date(t.fire_at).toISOString(),
|
|
35865
|
+
createdAt: new Date(t.created_at).toISOString()
|
|
35866
|
+
}));
|
|
35867
|
+
const armedTimerIds = new Set(armedTimers.map((t) => t.id));
|
|
35833
35868
|
const headlessStalled = row.codex_runtime === "app-server" ? appServerTurnStalled(codexTurnLiveness(row.slug, row.session_id), tele?.lastActivityAt, nowMs) : isBrokerClaudeRow(row) ? !claudeBrokerDaemonAlive(row.session_id) : false;
|
|
35834
35869
|
const headlessLostWork = isBrokerClaudeRow(row) && headlessStalled && hasUnretiredOwnAgents(tele);
|
|
35835
35870
|
const deliveryProcessGone = isBrokerClaudeRow(row) && headlessStalled;
|
|
@@ -35847,7 +35882,7 @@ function sessionThreadView(projectDir, storage, row, tele, registeredLegacyTermi
|
|
|
35847
35882
|
const archived = state === "archived";
|
|
35848
35883
|
const limitPause = resolveLimitPause(row, tele, nowMs);
|
|
35849
35884
|
const needsYou = archived ? false : deriveNeedsYou(row, tele, runtime, interactionPresence.needsUser, nowMs, limitPause, true, deliveryProcessGone, github, registeredPrWatches);
|
|
35850
|
-
const awaitingBackground = archived ? false : deriveAwaitingBackground(row, tele, runtime, interactionPresence.needsUser, nowMs, limitPause, deliveryProcessGone, github, registeredPrWatches);
|
|
35885
|
+
const awaitingBackground = archived ? false : deriveAwaitingBackground(row, tele, runtime, interactionPresence.needsUser, nowMs, limitPause, deliveryProcessGone, github, registeredPrWatches, armedTimerIds);
|
|
35851
35886
|
const crashed = runtime === "exited" && (tele?.turn === "in-flight" || hasLiveBackgroundWork(tele) || headlessLostWork);
|
|
35852
35887
|
const snoozedUntil = futureSnooze(row, nowMs);
|
|
35853
35888
|
const profile = resolveSessionProfile(row, tele);
|
|
@@ -35883,7 +35918,7 @@ function sessionThreadView(projectDir, storage, row, tele, registeredLegacyTermi
|
|
|
35883
35918
|
// become the github rows, `watch:` lines the shell rows — so this strip lists exactly what will
|
|
35884
35919
|
// actually wake the thread, and the two cannot drift into claiming different things. There is no
|
|
35885
35920
|
// registry behind either any more (`thread_watch`, retired 2026-08-14).
|
|
35886
|
-
watches: fenceWatchViews(row.slug, tele, tele?.lastAssistantAt, github, armedPrWatches),
|
|
35921
|
+
watches: fenceWatchViews(row.slug, tele, tele?.lastAssistantAt, github, armedPrWatches, armedTimers),
|
|
35887
35922
|
pendingAsk: tele?.pendingAsk ? { questions: tele.pendingAsk.questions } : void 0,
|
|
35888
35923
|
pendingQuestion: tele?.pendingQuestion ?? false,
|
|
35889
35924
|
lastUserAt: tele?.lastUserAt,
|
package/dist/frizz.js
CHANGED
|
@@ -13988,7 +13988,7 @@ var init_src = __esm({
|
|
|
13988
13988
|
"seen nothing since their own last message \u2014 the Goal, this reminder, a watcher wake all came from",
|
|
13989
13989
|
"frizz \u2014 so anything you assumed they had followed, they have not."
|
|
13990
13990
|
].join("\n");
|
|
13991
|
-
ThreadWatchKind = external_exports.enum(["shell", "github"]);
|
|
13991
|
+
ThreadWatchKind = external_exports.enum(["shell", "github", "timer"]);
|
|
13992
13992
|
GithubChecksState = external_exports.enum(["none", "running", "passing", "failing"]);
|
|
13993
13993
|
GithubMergeState = external_exports.enum(["mergeable", "blocked", "conflicting", "unknown"]);
|
|
13994
13994
|
GithubWatchStatus = external_exports.object({
|
|
@@ -14022,7 +14022,12 @@ var init_src = __esm({
|
|
|
14022
14022
|
state: external_exports.enum(["armed", "fired", "dropped"]),
|
|
14023
14023
|
createdAt: external_exports.string(),
|
|
14024
14024
|
/** `github` rows only, and absent until the first successful poll. */
|
|
14025
|
-
github: GithubWatchStatus.optional()
|
|
14025
|
+
github: GithubWatchStatus.optional(),
|
|
14026
|
+
/** `timer` rows only: the armed timer's own registration, which is everything the row renders — the
|
|
14027
|
+
* worker's prompt is the row's NAME (a `tmr_…` id names nothing to a human) and the fire instant is
|
|
14028
|
+
* its status. Unlike `github` there is no polled half to be absent: a timer that exists is fully
|
|
14029
|
+
* known, so a timer row always carries this. */
|
|
14030
|
+
timer: external_exports.object({ fireAt: external_exports.string(), prompt: external_exports.string() }).strict().optional()
|
|
14026
14031
|
}).strict();
|
|
14027
14032
|
PR_WATCH_DEFAULT_FOR_MS = 6 * 60 * 60 * 1e3;
|
|
14028
14033
|
PrWatchView = external_exports.object({
|
|
@@ -16313,7 +16318,6 @@ function parseCliArgs(argv) {
|
|
|
16313
16318
|
let rawHost;
|
|
16314
16319
|
let rawPublicOrigin;
|
|
16315
16320
|
const rawAllowedHosts = [];
|
|
16316
|
-
let repoPath;
|
|
16317
16321
|
const consumed = /* @__PURE__ */ new Set();
|
|
16318
16322
|
for (let index = 0; index < argv.length; index++) {
|
|
16319
16323
|
const arg = argv[index];
|
|
@@ -16367,9 +16371,13 @@ function parseCliArgs(argv) {
|
|
|
16367
16371
|
continue;
|
|
16368
16372
|
}
|
|
16369
16373
|
if (arg.startsWith("-")) continue;
|
|
16370
|
-
if (
|
|
16371
|
-
throw new Error(
|
|
16372
|
-
|
|
16374
|
+
if (arg === "up")
|
|
16375
|
+
throw new Error(
|
|
16376
|
+
"the up command was removed \u2014 there is one way to launch Frizz, so use --cloud instead: it serves at the saved hostname and runs its tunnel"
|
|
16377
|
+
);
|
|
16378
|
+
throw new Error(
|
|
16379
|
+
`unexpected argument: ${arg} \u2014 Frizz takes no repository path, because one server serves every project on this machine. cd into the directory you want and run it with no arguments.`
|
|
16380
|
+
);
|
|
16373
16381
|
}
|
|
16374
16382
|
let port;
|
|
16375
16383
|
if (rawPort !== void 0) {
|
|
@@ -16405,7 +16413,6 @@ function parseCliArgs(argv) {
|
|
|
16405
16413
|
continue;
|
|
16406
16414
|
}
|
|
16407
16415
|
if (arg.startsWith("--port=") || arg.startsWith("--host=") || arg.startsWith("--allowed-host=") || arg.startsWith("--public-origin=")) continue;
|
|
16408
|
-
if (!arg.startsWith("-") && arg === repoPath) continue;
|
|
16409
16416
|
if (!known.has(arg)) throw new Error(`unknown option: ${arg}`);
|
|
16410
16417
|
}
|
|
16411
16418
|
if (args2.has("--detach"))
|
|
@@ -16427,8 +16434,7 @@ function parseCliArgs(argv) {
|
|
|
16427
16434
|
port,
|
|
16428
16435
|
host,
|
|
16429
16436
|
allowedHosts: normalizeAllowedHosts(rawAllowedHosts),
|
|
16430
|
-
...rawPublicOrigin === void 0 ? {} : { publicOrigin: normalizePublicOrigin(rawPublicOrigin) }
|
|
16431
|
-
repoPath
|
|
16437
|
+
...rawPublicOrigin === void 0 ? {} : { publicOrigin: normalizePublicOrigin(rawPublicOrigin) }
|
|
16432
16438
|
};
|
|
16433
16439
|
}
|
|
16434
16440
|
var EXPOSED_WARNING = "Anyone who can reach this address can run commands on this machine as you. Frizz has no login \u2014 only do this on a network you trust.";
|
|
@@ -17017,6 +17023,7 @@ var RestartSupervisorProxy = class {
|
|
|
17017
17023
|
}
|
|
17018
17024
|
status() {
|
|
17019
17025
|
const delegated = this.options.status?.();
|
|
17026
|
+
const updateVersion = this.options.updateVersion?.();
|
|
17020
17027
|
const ownerTransition = this.restartInFlight !== null || this.state === "failed";
|
|
17021
17028
|
return {
|
|
17022
17029
|
...delegated ?? {},
|
|
@@ -17027,6 +17034,10 @@ var RestartSupervisorProxy = class {
|
|
|
17027
17034
|
// Sent ONLY when the launcher can actually answer it, so an older client — and frizz-dev, which
|
|
17028
17035
|
// has no notion of "already current" — keeps today's behaviour on its absence.
|
|
17029
17036
|
...this.options.updateAvailable ? { updateAvailable: this.options.updateAvailable() === true } : {},
|
|
17037
|
+
// Version numbers ride the same launcher-only contract: absent for frizz-dev and legacy
|
|
17038
|
+
// supervisors, so their clients render exactly what they render today.
|
|
17039
|
+
...this.options.version ? { version: this.options.version } : {},
|
|
17040
|
+
...updateVersion ? { updateVersion } : {},
|
|
17030
17041
|
// Sent only when TRUE, so a published Frizz's payload is byte-identical to what it sends today
|
|
17031
17042
|
// and an older client is unaffected. Absent therefore means "not a development build".
|
|
17032
17043
|
...this.options.dev ? { dev: true } : {}
|
|
@@ -17576,6 +17587,8 @@ var Supervisor = class {
|
|
|
17576
17587
|
restart: () => this.restartFromBrowser(),
|
|
17577
17588
|
updateRestart: this.updateRestart ? () => this.updateFromBrowser() : void 0,
|
|
17578
17589
|
updateAvailable: opts.updateAvailable,
|
|
17590
|
+
version: opts.version,
|
|
17591
|
+
updateVersion: opts.updateVersion,
|
|
17579
17592
|
dev: opts.dev,
|
|
17580
17593
|
status: () => {
|
|
17581
17594
|
if (this.browserRestart || this.restartRunning) return { state: "restarting" };
|
|
@@ -18232,11 +18245,12 @@ if (options.help) {
|
|
|
18232
18245
|
console.log(
|
|
18233
18246
|
`Frizz production launcher
|
|
18234
18247
|
|
|
18235
|
-
Usage: npx ${PACKAGE_NAME} [options]
|
|
18236
|
-
npx ${PACKAGE_NAME} up start the server plus its public tunnel
|
|
18248
|
+
Usage: npx ${PACKAGE_NAME} [options]
|
|
18237
18249
|
|
|
18238
|
-
|
|
18239
|
-
|
|
18250
|
+
Run it in the directory you want to work in. One server serves EVERY project on this machine,
|
|
18251
|
+
each at its own /project/<name> URL, so a second run joins the one already going. Runs the
|
|
18252
|
+
npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev only
|
|
18253
|
+
for a source checkout.
|
|
18240
18254
|
|
|
18241
18255
|
Options:
|
|
18242
18256
|
--app use the legacy dedicated app window instead of a browser tab
|
|
@@ -18245,8 +18259,8 @@ Options:
|
|
|
18245
18259
|
--host [address] serve on a network address instead of loopback (bare --host means 0.0.0.0)
|
|
18246
18260
|
--allowed-host <name> with --host, also accept this DNS name as the board's address (repeatable)
|
|
18247
18261
|
--public-origin <url> serve behind a proxy/tunnel reachable at this exact origin
|
|
18248
|
-
--cloud
|
|
18249
|
-
|
|
18262
|
+
--cloud serve at the saved hostname and run its Cloudflare tunnel as a child
|
|
18263
|
+
process; asked once, then remembered
|
|
18250
18264
|
--link print a fresh single-use access link for the running board
|
|
18251
18265
|
--debug stream the full event feed to the terminal instead of the compact readout
|
|
18252
18266
|
-h, --help show this help
|
|
@@ -18288,7 +18302,7 @@ var workspace = (() => {
|
|
|
18288
18302
|
if (!pinned) throw new Error("registry successor is missing its pinned project identity");
|
|
18289
18303
|
return workspaceFromLaunchTarget(pinned);
|
|
18290
18304
|
}
|
|
18291
|
-
const intent = resolveLaunchIntent(
|
|
18305
|
+
const intent = resolveLaunchIntent();
|
|
18292
18306
|
if (intent.kind === "empty")
|
|
18293
18307
|
throw new Error(
|
|
18294
18308
|
intent.reason === "home" ? "frizz cannot open your home directory as a project, and there is no other project to show yet. cd into a repository and run frizz there." : `${intent.directory} is not a Frizz project yet, and there is no other project to show. Run frizz inside a repository, or add this one from the projects page once a board is open.`
|
|
@@ -18430,9 +18444,12 @@ async function runSupervisor(port, token) {
|
|
|
18430
18444
|
const childEntry = fileURLToPath2(new URL("./dev-child.js", import.meta.url));
|
|
18431
18445
|
let plannedUpdate;
|
|
18432
18446
|
let updateAvailable = true;
|
|
18447
|
+
let updateVersion;
|
|
18433
18448
|
const refreshUpdateAvailable = async () => {
|
|
18434
18449
|
try {
|
|
18435
|
-
|
|
18450
|
+
const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
|
|
18451
|
+
updateAvailable = plan !== null;
|
|
18452
|
+
updateVersion = plan?.latestVersion;
|
|
18436
18453
|
} catch {
|
|
18437
18454
|
}
|
|
18438
18455
|
};
|
|
@@ -18454,6 +18471,8 @@ async function runSupervisor(port, token) {
|
|
|
18454
18471
|
childEntry,
|
|
18455
18472
|
childEnvironment: () => ({ FRIZZ_STABLE_WEB_DIST: webDist, FRIZZ_STABLE_ARTIFACT: `npm:${PACKAGE_NAME}@${PACKAGE_VERSION}`, FRIZZ_SCRIPTS_DIR: scriptsDir, FRIZZ_WORKER_PLUGIN_DIR: workerPluginDir }),
|
|
18456
18473
|
updateAvailable: () => updateAvailable,
|
|
18474
|
+
version: PACKAGE_VERSION,
|
|
18475
|
+
updateVersion: () => updateVersion,
|
|
18457
18476
|
// The terminal that owns the board says so when the board goes down and comes back. Everything
|
|
18458
18477
|
// here is triggered from a browser tab or by a crash, so without this the foreground process is
|
|
18459
18478
|
// the last place to learn what happened to it.
|
|
@@ -18463,9 +18482,11 @@ async function runSupervisor(port, token) {
|
|
|
18463
18482
|
const plan = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter);
|
|
18464
18483
|
if (!plan) {
|
|
18465
18484
|
updateAvailable = false;
|
|
18485
|
+
updateVersion = void 0;
|
|
18466
18486
|
return { state: "failed", message: `Frizz ${PACKAGE_VERSION} is already current` };
|
|
18467
18487
|
}
|
|
18468
18488
|
plannedUpdate = plan;
|
|
18489
|
+
updateVersion = plan.latestVersion;
|
|
18469
18490
|
return { state: "ready", message: `Frizz ${plan.latestVersion} will start in a new npm execution cache` };
|
|
18470
18491
|
} catch (error) {
|
|
18471
18492
|
return { state: "failed", message: error instanceof Error ? error.message : String(error) };
|
package/package.json
CHANGED
|
@@ -273,7 +273,7 @@ const WATCH_PR = {
|
|
|
273
273
|
"wait at all. This tool is the wait.\n\n" +
|
|
274
274
|
"THE ```awaiting FENCE IS STILL WORTH WRITING, and it is a different job: it is how you come to REST " +
|
|
275
275
|
"without frizz asking you for a handoff, and how the human sees what you are waiting for. Register " +
|
|
276
|
-
"the watcher with this tool, then name the same PR
|
|
276
|
+
"the watcher with this tool, then name the same PR in your fence's `prs:` list.\n\n" +
|
|
277
277
|
"REGISTERING IS IDEMPOTENT per pull request: asking twice returns the SAME id and tells you it was " +
|
|
278
278
|
"already armed, so re-registering after a compaction is safe and is the right instinct. Use `list` " +
|
|
279
279
|
"when you want to know what you are holding without changing anything — it answers with each PR's " +
|
|
@@ -925,7 +925,7 @@ async function watchPr(args) {
|
|
|
925
925
|
: `Watching ${ref} as ${id}. Frizz wakes you when CI passes or fails and on every later review or ` +
|
|
926
926
|
"comment, and the registration survives your turn ending, a compaction and a frizz restart."
|
|
927
927
|
return (
|
|
928
|
-
`${head}\n\nNAME IT IN YOUR \`\`\`awaiting FENCE TOO (\`
|
|
928
|
+
`${head}\n\nNAME IT IN YOUR \`\`\`awaiting FENCE TOO (\`prs: [${ref}]\`) — the watcher does the ` +
|
|
929
929
|
`waking, the fence is what lets you come to rest and shows the human what you are waiting for.\n\n` +
|
|
930
930
|
`DROP IT when it stops mattering (\`action: "drop", id: "${id}"\`).\n\n${armedPrWatchList(result)}`
|
|
931
931
|
)
|
|
@@ -67,11 +67,11 @@ const core =
|
|
|
67
67
|
'⟦frizz worker contract⟧ You are a frizz WORKER driving EXACTLY ONE effort. Your FULL operating contract — the end-of-turn signal fences, scratch-directory rules, sub-agent rules, and the question handback — lives in your SYSTEM PROMPT; follow it there (this is a runtime re-grounding, not a second copy). The human + the frizz app are the ORCHESTRATOR; you drive ONE effort and never scan the board or touch other efforts. There is no orchestrator mode and no fleet to run: doing the work yourself is the default, and you dispatch a sub-agent only when the work genuinely decomposes into independent prongs.\n' +
|
|
68
68
|
'SCRATCH DIRECTORY (OPTIONAL): `' + scratch + '` — a folder kept FOR YOU: any files you like, no format expected, nothing in it read automatically, and never a substitute for doing the work. A single direct task usually needs nothing. On a long effort write the doc you would want if you lost your context — the approach, what you rejected, the human\'s decisions — AS YOU GO, mid-work, then KEEP WORKING, and arm mcp__frizz__recurring_prompt with post_compaction: true and a prompt LINKING that file, because the arming is what brings it back. Give each sub-agent its OWN file rather than a shared one.\n' +
|
|
69
69
|
'DO NOT REST WHILE THE INSTRUCTION HAS PARTS LEFT — finish them in THIS turn; a milestone, a green test run and a long turn are none of them stopping points, and announcing the next step or writing it into a scratch file is not doing it.\n' +
|
|
70
|
-
'ALWAYS SIGN OFF WITH A FENCE, per the fence rules in your system prompt. A rest with NO fence is not a handoff — it is an item nobody can triage, and frizz will tell you so. ```done only when the effort\'s real work is COMPLETE (code LANDED on the mainline — an open PR is NOT done, park it on ```awaiting until it MERGES) and is a DISMISSAL (its card files the thread away where nobody looks again), so if the thread points at future work AT ALL — a pre-fix investigation, a live code-change discussion, follow-up work you DISCOVERED even when someone else will do it — ask a ```question instead, and uncertain is not done; a verdict that ends in something the HUMAN must now do (post this comment, merge or decline, pick one of these) is a ```question carrying your recommendation as option A, never a done card, because a draft you wrote but did not send is filed away with the thread; when your OWN mandate is complete and what is left is a separable effort, DO it first — dispatch a sub-agent, whose result comes BACK to you, so it lands on YOUR card — and ASK second; mcp__frizz__spawn_thread is the LAST resort, for work that genuinely cannot ride on your card, because a spawned thread reports only to the human and nothing it learns returns to you or its siblings; the ONE exception is a planning session whose plan file is fully written and persisted, because that artifact outlives the thread; ```awaiting parks a
|
|
70
|
+
'ALWAYS SIGN OFF WITH A FENCE, per the fence rules in your system prompt. A rest with NO fence is not a handoff — it is an item nobody can triage, and frizz will tell you so. ```done only when the effort\'s real work is COMPLETE (code LANDED on the mainline — an open PR is NOT done, park it on ```awaiting until it MERGES) and is a DISMISSAL (its card files the thread away where nobody looks again), so if the thread points at future work AT ALL — a pre-fix investigation, a live code-change discussion, follow-up work you DISCOVERED even when someone else will do it — ask a ```question instead, and uncertain is not done; a verdict that ends in something the HUMAN must now do (post this comment, merge or decline, pick one of these) is a ```question carrying your recommendation as option A, never a done card, because a draft you wrote but did not send is filed away with the thread; when your OWN mandate is complete and what is left is a separable effort, DO it first — dispatch a sub-agent, whose result comes BACK to you, so it lands on YOUR card — and ASK second; mcp__frizz__spawn_thread is the LAST resort, for work that genuinely cannot ride on your card, because a spawned thread reports only to the human and nothing it learns returns to you or its siblings; the ONE exception is a planning session whose plan file is fully written and persisted, because that artifact outlives the thread; ```awaiting parks your OWN background work — a shell, a sub-agent, an armed timer, a registered PR watcher — never CI/releases/merge progression (those stay ACTIVE), and never a person: waiting on a person is a ```question; ```question is the operator ask. Waiting on your own background shell? Your shells and sub-agents are watched AUTOMATICALLY — frizz wakes you when one finishes, fence or no fence. NAME it in the fence\'s YAML frontmatter anyway — plural keys taking lists (`shells: [<id>]`, `agents:`, `timers:`, `prs:`) plus a required `for:` duration, then `---` and your prose — and rest: that is what lets you stop without frizz asking you for a handoff, and what shows the human what you are waiting for. Frizz checks every name against what you actually have running. Keep the write-up SHORT: 1-3 sentences, then bullets starting with a **bolded verb phrase**. The fence and the prose above it are TWO SURFACES, not one message written twice — the card is the ledger of what shipped, the prose is only what a ledger cannot hold, and a sentence that would read the same in either belongs in exactly one. And a done message says what HAPPENED: delete every dangling "one thing to carry forward" or "a follow-up could" — do it, spawn it onto its own card, ask about it, or DROP it, and if it is not worth a card it is not worth a sentence.\n' +
|
|
71
71
|
'DECIDE rather than ask: anything derivable from the code, the conventions, or ordinary engineering judgment is YOURS to settle — asking permission to do the work you were dispatched to do is not a question, it is the job. Reserve the operator for the irreversible and the genuinely human-owned.';
|
|
72
72
|
|
|
73
73
|
const grounding =
|
|
74
|
-
'⟦frizz worker re-grounding (post-compaction)⟧ Context was just compacted. You are still the frizz worker for effort `' + thread + '` — read whatever you left yourself in `' + scratch + '` NOW to recover your working state and to-do list before asserting anything, and re-read any code before claiming how it is structured. Signal at rest through your FINAL MESSAGE, always with a fence — a fenceless rest is an item nobody can triage: ```done queues a checked completion until Archive and is a DISMISSAL — completed work only, and never when the thread still points at future work (a pre-fix investigation, a live code-change discussion); use a question or bare rest; ```awaiting parks only
|
|
74
|
+
'⟦frizz worker re-grounding (post-compaction)⟧ Context was just compacted. You are still the frizz worker for effort `' + thread + '` — read whatever you left yourself in `' + scratch + '` NOW to recover your working state and to-do list before asserting anything, and re-read any code before claiming how it is structured. Signal at rest through your FINAL MESSAGE, always with a fence — a fenceless rest is an item nobody can triage: ```done queues a checked completion until Archive and is a DISMISSAL — completed work only, and never when the thread still points at future work (a pre-fix investigation, a live code-change discussion); use a question or bare rest; ```awaiting parks only your own background work, an armed timer, or a registered PR watcher (which wakes on any new review/comment, bot or human) — named in YAML frontmatter with plural list keys (`shells:`/`agents:`/`timers:`/`prs:`) plus a `for:` duration; waiting on a person is a ```question; ```question is the explicit higher-priority operator ask. CI/releases/merge progression stay active through Monitor/background Bash.';
|
|
75
75
|
|
|
76
76
|
// AUTH-GATED gh guidance — teach the worker to use `gh` well, but ONLY when signed in.
|
|
77
77
|
// Shell `gh auth status --active`: exit 0 = an active gh account is authenticated. The whole gate is
|
|
@@ -58,7 +58,7 @@ gh pr view N -R OWNER/REPO --comments # review threads + conversation
|
|
|
58
58
|
```
|
|
59
59
|
Read the changed files **in context**, not just the hunks — `gh pr diff` shows what changed, but correctness lives in the surrounding code.
|
|
60
60
|
|
|
61
|
-
**Reading ONE review (what a `
|
|
61
|
+
**Reading ONE review (what a `watch_pr` wake hands you)**
|
|
62
62
|
|
|
63
63
|
A wake permalink ending `#pullrequestreview-<id>` is a **review**, and a review's `body` is routinely
|
|
64
64
|
**empty** — review apps (pullfrog, coderabbit) and humans doing an inline pass put every word in the
|
|
@@ -139,12 +139,13 @@ such as `push` and `pull_request` both contribute to the aggregate verdict.
|
|
|
139
139
|
`TaskOutput` is deprecated. Do not fake waiting with `echo waiting` or sleep-only Bash calls.
|
|
140
140
|
|
|
141
141
|
Both mechanisms are session-bound. If the next check deliberately belongs at a named wall-clock
|
|
142
|
-
instant,
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
instant, set a durable timer with `mcp__frizz__timer` and park with its id in your fence's `timers:`
|
|
143
|
+
list. If a specific external human reviewer/approver is the only remaining gate, that is a
|
|
144
|
+
` ```question ` — waiting on a person is never a park. For a GitHub PR, register it with
|
|
145
|
+
`mcp__frizz__watch_pr` and name it in the fence's `prs:` list (`prs: [OWNER/REPO#NUMBER]`): frizz
|
|
146
|
+
baselines current reviews/comments and wakes on ANY new activity after registration — bot or human —
|
|
147
|
+
durably across restarts. The registration creates the wait; the fence only declares it. The dashboard
|
|
148
|
+
operator's own go/no-go remains a ` ```question ` block.
|
|
148
149
|
|
|
149
150
|
## Fitting gh work into your thread type
|
|
150
151
|
|
|
@@ -19,7 +19,7 @@ thread in Active.
|
|
|
19
19
|
| REST until the condition is met (the usual CI / PR / release wait) | a **sub-agent** that owns the wait |
|
|
20
20
|
| KEEP WORKING alongside a process you launched (dev server, log tail) | `Bash` + `run_in_background: true` |
|
|
21
21
|
| stream events into the turn you are actively working in | `Monitor` |
|
|
22
|
-
| re-check at a named wall-clock instant, across a process exit | `
|
|
22
|
+
| re-check at a named wall-clock instant, across a process exit | `mcp__frizz__timer`, named in a `timers:` fence list |
|
|
23
23
|
|
|
24
24
|
Never fake a wait with `echo waiting`, repeated foreground sleeps, or an `awaiting` fence for CI, bots,
|
|
25
25
|
releases, or merge progression.
|
|
@@ -66,8 +66,9 @@ Give the child, literally, in the prompt:
|
|
|
66
66
|
also inspect the workflow runs for the exact PR head, and treat `ACTION_REQUIRED` fork gates as pending,
|
|
67
67
|
not passing.
|
|
68
68
|
|
|
69
|
-
**PR review activity.** Do not build a watcher —
|
|
70
|
-
polls it for you, waking on any new review, approval,
|
|
69
|
+
**PR review activity.** Do not build a watcher — register the PR with `mcp__frizz__watch_pr`, then name
|
|
70
|
+
it in your `awaiting` fence's `prs:` list. Frizz polls it for you, waking on any new review, approval,
|
|
71
|
+
or comment, bot or human. The registration creates the wait; the fence only declares it.
|
|
71
72
|
|
|
72
73
|
**A release or deploy.** Poll the artifact that proves it, not the pipeline that promises it — the
|
|
73
74
|
published version on the registry, the health endpoint, the deployed asset hash.
|