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.
Files changed (97) hide show
  1. package/README.md +8 -5
  2. package/dist/dev-child.js +49 -14
  3. package/dist/frizz.js +38 -17
  4. package/package.json +1 -1
  5. package/runtime/cc-worker/bin/frizz-mcp.mjs +2 -2
  6. package/runtime/cc-worker/hooks/session-seed.mjs +2 -2
  7. package/runtime/cc-worker/skills/gh/SKILL.md +8 -7
  8. package/runtime/cc-worker/skills/waits/SKILL.md +4 -3
  9. package/web-dist/assets/{TerminalPane-GfnCbZQ8.js → TerminalPane-DW91LtY6.js} +1 -1
  10. package/web-dist/assets/{abnfDiagram-VRR7QNED-By12QfQW.js → abnfDiagram-VRR7QNED-Cz9pKsnx.js} +1 -1
  11. package/web-dist/assets/architecture-TIHT7OUA-pEHgxrgQ.js +1 -0
  12. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CcXU45QO.js → architectureDiagram-ZJ3FMSHR-Cx1Inab9.js} +1 -1
  13. package/web-dist/assets/{blockDiagram-677ZJIJ3-CmzyO2OF.js → blockDiagram-677ZJIJ3-Dkow3KWq.js} +1 -1
  14. package/web-dist/assets/{c4Diagram-LMCZKHZV-DFGSW_ZS.js → c4Diagram-LMCZKHZV-D54f0v8B.js} +1 -1
  15. package/web-dist/assets/channel-DmtoXjBB.js +1 -0
  16. package/web-dist/assets/{chunk-32BRIVSS-whgaFxPY.js → chunk-32BRIVSS-5HfLNqOq.js} +1 -1
  17. package/web-dist/assets/{chunk-52WLFC77-DCT0MLgl.js → chunk-52WLFC77-CoVH-jjk.js} +1 -1
  18. package/web-dist/assets/{chunk-C7G6YPKG-Br0OaxIX.js → chunk-C7G6YPKG-w41h00On.js} +1 -1
  19. package/web-dist/assets/{chunk-EX3LRPZG-CUnQkdEF.js → chunk-EX3LRPZG-BC_Frphy.js} +1 -1
  20. package/web-dist/assets/{chunk-FWX5IMBZ-D1NqKRFr.js → chunk-FWX5IMBZ-DLZVUhWr.js} +2 -2
  21. package/web-dist/assets/{chunk-HOUHSVGY-7ZL78OrO.js → chunk-HOUHSVGY-DCl7dozs.js} +1 -1
  22. package/web-dist/assets/{chunk-ICXQ74PX-QSgBAuYw.js → chunk-ICXQ74PX-I8bc3hpu.js} +1 -1
  23. package/web-dist/assets/{chunk-MOJQB5TN-DOI-0kaG.js → chunk-MOJQB5TN-D1tbS5-A.js} +1 -1
  24. package/web-dist/assets/{chunk-OGEWGWER-CZ3ED-QB.js → chunk-OGEWGWER-CLa17J5_.js} +1 -1
  25. package/web-dist/assets/{chunk-PUDLZKDR-BQJElUFX.js → chunk-PUDLZKDR-DcYIQuxf.js} +1 -1
  26. package/web-dist/assets/{chunk-Q4XR5HBZ-5B6Qs7rQ.js → chunk-Q4XR5HBZ-DGHqsWli.js} +1 -1
  27. package/web-dist/assets/{chunk-V7JOEXUC-DkMaAMFK.js → chunk-V7JOEXUC-DdVYI4tg.js} +1 -1
  28. package/web-dist/assets/{chunk-VAUOI2AC-Bvq-8GYO.js → chunk-VAUOI2AC-Dn_uoDoN.js} +1 -1
  29. package/web-dist/assets/{chunk-VR4S4FIN-DRVTU-HZ.js → chunk-VR4S4FIN-AM_J_EWt.js} +1 -1
  30. package/web-dist/assets/{chunk-WYO6CB5R-CRxI5d1E.js → chunk-WYO6CB5R-BldKUlC0.js} +1 -1
  31. package/web-dist/assets/{chunk-ZGVPDNZ5-D_3ow6xU.js → chunk-ZGVPDNZ5-Oh85mcuX.js} +1 -1
  32. package/web-dist/assets/classDiagram-OUVF2IWQ-CUGsPtaX.js +1 -0
  33. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CUGsPtaX.js +1 -0
  34. package/web-dist/assets/{cynefin-VYW2F7L2-Bexf41pD.js → cynefin-VYW2F7L2-5lF8kTLT.js} +1 -1
  35. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-9gbbl1EH.js → cynefinDiagram-TSTJHNR4-_otwu953.js} +1 -1
  36. package/web-dist/assets/{dagre-VKFMJZFB-B04shbL0.js → dagre-VKFMJZFB-DxX5d4Jp.js} +1 -1
  37. package/web-dist/assets/{diagram-FQU43EPY-CGXlhEsB.js → diagram-FQU43EPY-JkCl7NEC.js} +1 -1
  38. package/web-dist/assets/{diagram-G47NLZAW-DmPCeTOa.js → diagram-G47NLZAW-ETwnVi8G.js} +1 -1
  39. package/web-dist/assets/{diagram-NH7WQ7WH-D8Hthvi7.js → diagram-NH7WQ7WH-DZXKHMOv.js} +1 -1
  40. package/web-dist/assets/{diagram-OA4YK3LP-DbLR6tfL.js → diagram-OA4YK3LP--Yt9Hbcy.js} +1 -1
  41. package/web-dist/assets/{diagram-WEI45ONY-B6YWev_0.js → diagram-WEI45ONY-BXUskT8t.js} +1 -1
  42. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-8G1uf683.js → ebnfDiagram-CCIWWBDH-C7E1X1Dl.js} +1 -1
  43. package/web-dist/assets/{erDiagram-Q63AITRT-U_VRdQHz.js → erDiagram-Q63AITRT-DOfGp1t7.js} +1 -1
  44. package/web-dist/assets/eventmodeling-45OFAUF4-D47al60X.js +1 -0
  45. package/web-dist/assets/flowDiagram-23GEKE2U-Cfew-ywI.js +1 -0
  46. package/web-dist/assets/{ganttDiagram-NO4QXBWP-Cnsi9uVf.js → ganttDiagram-NO4QXBWP-P5HZV3uF.js} +1 -1
  47. package/web-dist/assets/{gitGraph-TEB2WS4Q-DYf7ka-G.js → gitGraph-TEB2WS4Q-CsJYjOme.js} +1 -1
  48. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-Bvmr7dEH.js → gitGraphDiagram-IHSO6WYX-BNyrduBc.js} +1 -1
  49. package/web-dist/assets/{index-Czv3R-v0.js → index-CYVVF_ZU.js} +113 -113
  50. package/web-dist/assets/index-CjEngYN7.css +1 -0
  51. package/web-dist/assets/{info-DKCQHKI2-CdP9Q9r0.js → info-DKCQHKI2-WzpT_dbU.js} +1 -1
  52. package/web-dist/assets/{infoDiagram-FWYZ7A6U-RBcOH3Pi.js → infoDiagram-FWYZ7A6U-CA_SxvAY.js} +1 -1
  53. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-zS0gy3fS.js → ishikawaDiagram-FXEZZL3T-DXMy0GH9.js} +1 -1
  54. package/web-dist/assets/{journeyDiagram-5HDEW3XC-cOciJlFY.js → journeyDiagram-5HDEW3XC-Bwmn4bNv.js} +1 -1
  55. package/web-dist/assets/{kanban-definition-HUTT4EX6-B-v0XClo.js → kanban-definition-HUTT4EX6-D265Z0Fg.js} +1 -1
  56. package/web-dist/assets/{line-z44YXdci.js → line-DNn_Towe.js} +1 -1
  57. package/web-dist/assets/{mermaid-parser.core-0Bg1x4p0.js → mermaid-parser.core-3CrMhKoT.js} +3 -3
  58. package/web-dist/assets/{mermaid.core-ByFqgcIE.js → mermaid.core-DfCFu8_x.js} +3 -3
  59. package/web-dist/assets/{mindmap-definition-LN4V7U3C-DiGaAQFh.js → mindmap-definition-LN4V7U3C-CQ5g8Qrh.js} +1 -1
  60. package/web-dist/assets/{packet-7NZHBO7P-Bv8ILo_S.js → packet-7NZHBO7P-CRDQ5t5W.js} +1 -1
  61. package/web-dist/assets/{pegDiagram-2B236MQR-CtOUW7n0.js → pegDiagram-2B236MQR-B0XFyb7d.js} +1 -1
  62. package/web-dist/assets/{pie-RZYD4A2V-Dz4Bm-Vx.js → pie-RZYD4A2V-DqjiBFGO.js} +1 -1
  63. package/web-dist/assets/{pieDiagram-ENE6RG2P-VMY_CK3i.js → pieDiagram-ENE6RG2P-C41GJ3-U.js} +1 -1
  64. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-V7HZxM7j.js → quadrantDiagram-ABIIQ3AL-9cF0WJaM.js} +1 -1
  65. package/web-dist/assets/{radar-I7S5WNFK-CWk6RruH.js → radar-I7S5WNFK-S1F7Z4Kj.js} +1 -1
  66. package/web-dist/assets/{railroad-3IZDKUUU-m_8st_Ez.js → railroad-3IZDKUUU-da9Pjcp1.js} +1 -1
  67. package/web-dist/assets/railroad-abnf-AHOZXSZD-cPb8e4yf.js +1 -0
  68. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BdArTI_P.js +1 -0
  69. package/web-dist/assets/railroad-peg-LSFZ7HO6-cRtFMlRA.js +1 -0
  70. package/web-dist/assets/{railroadDiagram-RFXS5EU6-Dz80V-7-.js → railroadDiagram-RFXS5EU6-CkXBDByE.js} +1 -1
  71. package/web-dist/assets/{requirementDiagram-TGXJPOKE-DrDDgTwm.js → requirementDiagram-TGXJPOKE-CL9Famg0.js} +1 -1
  72. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-D4SeC-Bi.js → sankeyDiagram-HTMAVEWB-LyB-A0pD.js} +1 -1
  73. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CA-ZO_qp.js → sequenceDiagram-DBY2YBRQ-DXfA9UDM.js} +1 -1
  74. package/web-dist/assets/{stateDiagram-2N3HPSRC-C_ZYvV2s.js → stateDiagram-2N3HPSRC-DgxMkPRv.js} +1 -1
  75. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BVl1T6pD.js +1 -0
  76. package/web-dist/assets/{swimlanes-5IMT3BWC-DGMDseYx.js → swimlanes-5IMT3BWC-TvRb3Iib.js} +1 -1
  77. package/web-dist/assets/swimlanesDiagram-G3AALYLV-CaBRomjp.js +8 -0
  78. package/web-dist/assets/{timeline-definition-FHXFAJF6-CEiMp3_j.js → timeline-definition-FHXFAJF6-BBvC3bzZ.js} +1 -1
  79. package/web-dist/assets/{treeView-QDETBFTQ-BSspY0US.js → treeView-QDETBFTQ-CvCYIkJk.js} +1 -1
  80. package/web-dist/assets/{treemap-6X3UGDF4-DXNQxmQ7.js → treemap-6X3UGDF4-Bj60PrS0.js} +1 -1
  81. package/web-dist/assets/{vennDiagram-L72KCM5P-C-khqkdm.js → vennDiagram-L72KCM5P-CLuLJiNi.js} +1 -1
  82. package/web-dist/assets/{wardley-OPB4EBWU-B9QCQ_Tm.js → wardley-OPB4EBWU-C4tNB2Mi.js} +1 -1
  83. package/web-dist/assets/{wardleyDiagram-EHGQE667-vhVpAUzh.js → wardleyDiagram-EHGQE667-D_bno6Pc.js} +1 -1
  84. package/web-dist/assets/{xychartDiagram-FW5EYKEG-B8ev9zqs.js → xychartDiagram-FW5EYKEG-CklFVNTA.js} +1 -1
  85. package/web-dist/index.html +2 -2
  86. package/web-dist/assets/architecture-TIHT7OUA-XOYod28l.js +0 -1
  87. package/web-dist/assets/channel-Bqn3pFKp.js +0 -1
  88. package/web-dist/assets/classDiagram-OUVF2IWQ-DEMCehy_.js +0 -1
  89. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DEMCehy_.js +0 -1
  90. package/web-dist/assets/eventmodeling-45OFAUF4-DIpO2jQx.js +0 -1
  91. package/web-dist/assets/flowDiagram-23GEKE2U-aERDdceE.js +0 -1
  92. package/web-dist/assets/index-D8iyWUZ0.css +0 -1
  93. package/web-dist/assets/railroad-abnf-AHOZXSZD-DIaRscxr.js +0 -1
  94. package/web-dist/assets/railroad-ebnf-EBAXGLYW-CeHpbK-W.js +0 -1
  95. package/web-dist/assets/railroad-peg-LSFZ7HO6-CbhVIbOj.js +0 -1
  96. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BLevFa3H.js +0 -1
  97. 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] [repository]
125
- npx frizz up start the server plus its public tunnel
124
+ Usage: npx frizz [options]
126
125
 
127
- Runs the npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev
128
- only for a source checkout.
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 = canon(item.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 (canon(stripDeliveryMarkers(m.text)) !== text) continue;
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 = canon(item.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) && canon(stripDeliveryMarkers(m.text)) !== text) continue;
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 hasDeclaredWait(tele, nowMs) {
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)) return false;
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 (repoPath !== void 0)
16371
- throw new Error("provide at most one repository path");
16372
- repoPath = arg;
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] [repository]
18236
- npx ${PACKAGE_NAME} up start the server plus its public tunnel
18248
+ Usage: npx ${PACKAGE_NAME} [options]
18237
18249
 
18238
- Runs the npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev
18239
- only for a source checkout.
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 what the up command sets: serve at the saved public hostname and run the
18249
- tunnel as a supervised child
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(options.repoPath);
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
- updateAvailable = await planRegistryUpdate(PACKAGE_NAME, PACKAGE_VERSION, npmRegistryReleaseAdapter) !== null;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "A local web UI for running many coding agents at once",
6
6
  "keywords": [
@@ -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 on a `pr-watch:` line in your fence.\n\n" +
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 (\`pr-watch: ${ref}\`) — the watcher does the ` +
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 human:/timer:/pr-watch: gate or your OWN background work, never CI/releases/merge progression (those stay ACTIVE); ```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 on a `watch: <id>` line anyway 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 the 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' +
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 a human:/timer: gate (or a pr-watch: PR watcher, which wakes on any new review/comment, bot or human); ```question is the explicit higher-priority operator ask. CI/releases/merge progression stay active through Monitor/background Bash.';
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 `pr-watch` wake hands you)**
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, park with a durable `timer:` fence. If a specific external human reviewer/approver is the
143
- only remaining gate, park with `human: <actor + exact action>`. For a GitHub PR, pair it with
144
- `pr-watch: OWNER/REPO#NUMBER`: frizz baselines current reviews/comments and wakes on ANY new
145
- activity after the fence bot or human durably across restarts. Otherwise optionally pair a timer for
146
- a scheduled recheck. The dashboard operator's own go/no-go remains a ` ```question ` block. `pr:` / `ci:` /
147
- `session:` awaiting hints are legacy compatibility only do not emit them for new automated waits.
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 | `timer:` awaiting fence |
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 — emit `awaiting` with `pr-watch: owner/repo#N` and frizz
70
- polls it for you, waking on any new review, approval, or comment, bot or human.
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.