frizz 0.7.8 → 0.8.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 (124) hide show
  1. package/README.md +5 -0
  2. package/dist/claude-agent-broker.js +7 -4
  3. package/dist/dev-child.js +225 -214
  4. package/dist/frizz.js +92 -7
  5. package/package.json +1 -1
  6. package/runtime/cc-worker/.claude-plugin/plugin.json +1 -1
  7. package/runtime/cc-worker/DECISIONS.md +29 -1
  8. package/runtime/cc-worker/agents/high.md +7 -0
  9. package/runtime/cc-worker/agents/low.md +7 -0
  10. package/runtime/cc-worker/agents/max.md +7 -0
  11. package/runtime/cc-worker/agents/medium.md +7 -0
  12. package/runtime/cc-worker/agents/xhigh.md +7 -0
  13. package/runtime/cc-worker/hooks/agent-dispatch.mjs +7 -6
  14. package/runtime/cc-worker/hooks/hooks.json +0 -2
  15. package/runtime/cc-worker/hooks/scratchpad.mjs +5 -3
  16. package/runtime/cc-worker/hooks/session-seed.mjs +17 -17
  17. package/runtime/cc-worker/skills/gh/SKILL.md +5 -19
  18. package/web-dist/assets/{TerminalPane-ubcEP7X3.js → TerminalPane-BIBHC61j.js} +1 -1
  19. package/web-dist/assets/{abnfDiagram-VRR7QNED-0brfnieb.js → abnfDiagram-VRR7QNED-CcFQGFPf.js} +1 -1
  20. package/web-dist/assets/architecture-TIHT7OUA-BZEZt7jZ.js +1 -0
  21. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-Dji-XBdF.js → architectureDiagram-ZJ3FMSHR-DErIQOIs.js} +1 -1
  22. package/web-dist/assets/{blockDiagram-677ZJIJ3-BQMyz3Q9.js → blockDiagram-677ZJIJ3-CpsUqur1.js} +1 -1
  23. package/web-dist/assets/{c4Diagram-LMCZKHZV-BMNws0Oi.js → c4Diagram-LMCZKHZV-D2dHmCEJ.js} +1 -1
  24. package/web-dist/assets/channel-DdFRPviT.js +1 -0
  25. package/web-dist/assets/{chunk-32BRIVSS-CVv1ae7n.js → chunk-32BRIVSS-aOCEfxRW.js} +1 -1
  26. package/web-dist/assets/{chunk-52WLFC77-BYhPlKCT.js → chunk-52WLFC77-2iw4nJPC.js} +1 -1
  27. package/web-dist/assets/{chunk-C7G6YPKG-Cn4HhNwb.js → chunk-C7G6YPKG-BPD3iHVE.js} +1 -1
  28. package/web-dist/assets/{chunk-EX3LRPZG-P3ZYFC1W.js → chunk-EX3LRPZG-DrefHift.js} +1 -1
  29. package/web-dist/assets/{chunk-FWX5IMBZ-Ba3NpEQa.js → chunk-FWX5IMBZ-By940EHT.js} +2 -2
  30. package/web-dist/assets/{chunk-HOUHSVGY-BCWyMhwJ.js → chunk-HOUHSVGY-B1Cqvz7e.js} +1 -1
  31. package/web-dist/assets/{chunk-ICXQ74PX-DWAAqHL7.js → chunk-ICXQ74PX-1LkVpWuI.js} +1 -1
  32. package/web-dist/assets/{chunk-MOJQB5TN-pc5VJ4Nq.js → chunk-MOJQB5TN-Ip1cy64r.js} +1 -1
  33. package/web-dist/assets/{chunk-OGEWGWER-DUxmbiuX.js → chunk-OGEWGWER-CNjVyu8F.js} +1 -1
  34. package/web-dist/assets/{chunk-PUDLZKDR-DRdZWzxX.js → chunk-PUDLZKDR-9HZR6DAU.js} +1 -1
  35. package/web-dist/assets/{chunk-Q4XR5HBZ-Bafzu7s-.js → chunk-Q4XR5HBZ-vtWwOCuQ.js} +1 -1
  36. package/web-dist/assets/{chunk-V7JOEXUC-Bj0A8ftW.js → chunk-V7JOEXUC-DC6YaHG0.js} +1 -1
  37. package/web-dist/assets/{chunk-VAUOI2AC-DBgVQKIO.js → chunk-VAUOI2AC-CP-xe7SD.js} +1 -1
  38. package/web-dist/assets/{chunk-VR4S4FIN-DU-1KtuM.js → chunk-VR4S4FIN-wbxDFptH.js} +1 -1
  39. package/web-dist/assets/{chunk-WYO6CB5R-DzVb3-Rj.js → chunk-WYO6CB5R-dibXmwpK.js} +1 -1
  40. package/web-dist/assets/{chunk-ZGVPDNZ5-BPKQvduj.js → chunk-ZGVPDNZ5-_y_itJHG.js} +1 -1
  41. package/web-dist/assets/classDiagram-OUVF2IWQ-Bf3duLb8.js +1 -0
  42. package/web-dist/assets/classDiagram-v2-EOCWNBFH-Bf3duLb8.js +1 -0
  43. package/web-dist/assets/{cynefin-VYW2F7L2-lo8PqVCk.js → cynefin-VYW2F7L2-D1RYokZP.js} +1 -1
  44. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BtQLaDV0.js → cynefinDiagram-TSTJHNR4-qofqoy3T.js} +1 -1
  45. package/web-dist/assets/{dagre-VKFMJZFB-BJ2-f-A0.js → dagre-VKFMJZFB-CgHLoGFk.js} +1 -1
  46. package/web-dist/assets/{diagram-FQU43EPY-CLM-8r74.js → diagram-FQU43EPY-JEAs4LOb.js} +1 -1
  47. package/web-dist/assets/{diagram-G47NLZAW-CfuoJGpN.js → diagram-G47NLZAW--tLd4Lf7.js} +1 -1
  48. package/web-dist/assets/{diagram-NH7WQ7WH-DfxxRwH8.js → diagram-NH7WQ7WH-DPbw9CJU.js} +1 -1
  49. package/web-dist/assets/{diagram-OA4YK3LP-D7knxZee.js → diagram-OA4YK3LP-CDrb7nWs.js} +1 -1
  50. package/web-dist/assets/{diagram-WEI45ONY-C2WfMXWl.js → diagram-WEI45ONY-DJswZe3f.js} +1 -1
  51. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-DRSZ24tF.js → ebnfDiagram-CCIWWBDH-BP8-PZMF.js} +1 -1
  52. package/web-dist/assets/{erDiagram-Q63AITRT-DXe0OrbY.js → erDiagram-Q63AITRT-BQXuebf5.js} +1 -1
  53. package/web-dist/assets/eventmodeling-45OFAUF4-DOLxRQQ5.js +1 -0
  54. package/web-dist/assets/flowDiagram-23GEKE2U-DoK1yb0H.js +1 -0
  55. package/web-dist/assets/{ganttDiagram-NO4QXBWP-wGqOgjvs.js → ganttDiagram-NO4QXBWP-DotMb935.js} +1 -1
  56. package/web-dist/assets/{gitGraph-TEB2WS4Q-BygokG6y.js → gitGraph-TEB2WS4Q-B1vbYyKQ.js} +1 -1
  57. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-CV1933_M.js → gitGraphDiagram-IHSO6WYX-BjDXKk4m.js} +1 -1
  58. package/web-dist/assets/{index-DB1d3eMJ.js → index-Cs0TDGMu.js} +124 -124
  59. package/web-dist/assets/{info-DKCQHKI2-DLavbzui.js → info-DKCQHKI2-xLTLqQju.js} +1 -1
  60. package/web-dist/assets/{infoDiagram-FWYZ7A6U-Biouxd9G.js → infoDiagram-FWYZ7A6U-BFfYUsrC.js} +1 -1
  61. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-DJxwwS3R.js → ishikawaDiagram-FXEZZL3T-45a7VMwm.js} +1 -1
  62. package/web-dist/assets/{journeyDiagram-5HDEW3XC-Clx_WkdW.js → journeyDiagram-5HDEW3XC-CEBUpu59.js} +1 -1
  63. package/web-dist/assets/{kanban-definition-HUTT4EX6-Bf7bX_96.js → kanban-definition-HUTT4EX6-RwKtDFzG.js} +1 -1
  64. package/web-dist/assets/{line-CV2v49IN.js → line-DStcMIry.js} +1 -1
  65. package/web-dist/assets/{mermaid-parser.core-BPsFhEi6.js → mermaid-parser.core-B0ekgQMA.js} +3 -3
  66. package/web-dist/assets/{mermaid.core-0EWDNL_R.js → mermaid.core-w1hcQaPy.js} +3 -3
  67. package/web-dist/assets/{mindmap-definition-LN4V7U3C-C1y4yy3Z.js → mindmap-definition-LN4V7U3C-D_n38Imb.js} +1 -1
  68. package/web-dist/assets/{packet-7NZHBO7P-CpcpxruB.js → packet-7NZHBO7P-CstGeBOX.js} +1 -1
  69. package/web-dist/assets/{pegDiagram-2B236MQR-CVlVP4cH.js → pegDiagram-2B236MQR-DvowVyTO.js} +1 -1
  70. package/web-dist/assets/{pie-RZYD4A2V-BcVPOhTZ.js → pie-RZYD4A2V-BOP2j_HH.js} +1 -1
  71. package/web-dist/assets/{pieDiagram-ENE6RG2P-DZv4Tfae.js → pieDiagram-ENE6RG2P-CKRqu_2f.js} +1 -1
  72. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DOzS_U_a.js → quadrantDiagram-ABIIQ3AL-zSArSS8R.js} +1 -1
  73. package/web-dist/assets/{radar-I7S5WNFK-8YADxkdT.js → radar-I7S5WNFK-BqRdNIBw.js} +1 -1
  74. package/web-dist/assets/{railroad-3IZDKUUU-DzndPrRy.js → railroad-3IZDKUUU-BaKcl_LU.js} +1 -1
  75. package/web-dist/assets/railroad-abnf-AHOZXSZD-itqDLg1p.js +1 -0
  76. package/web-dist/assets/railroad-ebnf-EBAXGLYW-ze7rLVCi.js +1 -0
  77. package/web-dist/assets/railroad-peg-LSFZ7HO6-DKsYkEuG.js +1 -0
  78. package/web-dist/assets/{railroadDiagram-RFXS5EU6-CPJAH-r7.js → railroadDiagram-RFXS5EU6-BrXTQge9.js} +1 -1
  79. package/web-dist/assets/{requirementDiagram-TGXJPOKE-BVFJZfwl.js → requirementDiagram-TGXJPOKE-DBBNi_SS.js} +1 -1
  80. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-CUhvFPmE.js → sankeyDiagram-HTMAVEWB-BwFi7xFy.js} +1 -1
  81. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-Cy9CGhNq.js → sequenceDiagram-DBY2YBRQ-CeivqMeT.js} +1 -1
  82. package/web-dist/assets/{stateDiagram-2N3HPSRC-CqOnmPLp.js → stateDiagram-2N3HPSRC-C6NZsnFm.js} +1 -1
  83. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-B3YRnI4W.js +1 -0
  84. package/web-dist/assets/{swimlanes-5IMT3BWC-c_zBqkb9.js → swimlanes-5IMT3BWC-C0MubXTG.js} +1 -1
  85. package/web-dist/assets/swimlanesDiagram-G3AALYLV-CXva4Ic9.js +8 -0
  86. package/web-dist/assets/{timeline-definition-FHXFAJF6-y_QM62SK.js → timeline-definition-FHXFAJF6-CtxwSvFu.js} +1 -1
  87. package/web-dist/assets/{treeView-QDETBFTQ-T3y_1KXV.js → treeView-QDETBFTQ-CXcwSjey.js} +1 -1
  88. package/web-dist/assets/{treemap-6X3UGDF4-CzvqucqY.js → treemap-6X3UGDF4-Uspgr-d1.js} +1 -1
  89. package/web-dist/assets/{vennDiagram-L72KCM5P-6-HNOpN0.js → vennDiagram-L72KCM5P-tZamQQAQ.js} +1 -1
  90. package/web-dist/assets/{wardley-OPB4EBWU-BrsaqsLL.js → wardley-OPB4EBWU-0_79c19P.js} +1 -1
  91. package/web-dist/assets/{wardleyDiagram-EHGQE667-Cr_rCzwd.js → wardleyDiagram-EHGQE667-uYmUsP66.js} +1 -1
  92. package/web-dist/assets/{xychartDiagram-FW5EYKEG-BnX6ccx4.js → xychartDiagram-FW5EYKEG-BsOU7Wf7.js} +1 -1
  93. package/web-dist/index.html +1 -1
  94. package/runtime/cc-worker/agents/fable-high.md +0 -8
  95. package/runtime/cc-worker/agents/fable-low.md +0 -8
  96. package/runtime/cc-worker/agents/fable-max.md +0 -8
  97. package/runtime/cc-worker/agents/fable-medium.md +0 -8
  98. package/runtime/cc-worker/agents/fable-xhigh.md +0 -8
  99. package/runtime/cc-worker/agents/haiku.md +0 -7
  100. package/runtime/cc-worker/agents/opus-high.md +0 -8
  101. package/runtime/cc-worker/agents/opus-low.md +0 -8
  102. package/runtime/cc-worker/agents/opus-max.md +0 -8
  103. package/runtime/cc-worker/agents/opus-medium.md +0 -8
  104. package/runtime/cc-worker/agents/opus-xhigh.md +0 -8
  105. package/runtime/cc-worker/agents/sonnet-high.md +0 -8
  106. package/runtime/cc-worker/agents/sonnet-low.md +0 -8
  107. package/runtime/cc-worker/agents/sonnet-max.md +0 -8
  108. package/runtime/cc-worker/agents/sonnet-medium.md +0 -8
  109. package/runtime/cc-worker/agents/sonnet-xhigh.md +0 -8
  110. package/runtime/cc-worker/bin/browser-mcp-tools.json +0 -1192
  111. package/runtime/cc-worker/bin/browser-mcp.mjs +0 -730
  112. package/runtime/cc-worker/hooks/precompact-instructions.mjs +0 -133
  113. package/runtime/cc-worker/skills/waits/SKILL.md +0 -84
  114. package/web-dist/assets/architecture-TIHT7OUA-DksosNDW.js +0 -1
  115. package/web-dist/assets/channel-DqoPvfQz.js +0 -1
  116. package/web-dist/assets/classDiagram-OUVF2IWQ-DwUs74RB.js +0 -1
  117. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DwUs74RB.js +0 -1
  118. package/web-dist/assets/eventmodeling-45OFAUF4-CUIjQ7kE.js +0 -1
  119. package/web-dist/assets/flowDiagram-23GEKE2U-Dka-3F3n.js +0 -1
  120. package/web-dist/assets/railroad-abnf-AHOZXSZD-CCnWa6K9.js +0 -1
  121. package/web-dist/assets/railroad-ebnf-EBAXGLYW-jtVdzv7e.js +0 -1
  122. package/web-dist/assets/railroad-peg-LSFZ7HO6-B8D6TyEe.js +0 -1
  123. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DWKW-4qu.js +0 -1
  124. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DZKoB7ae.js +0 -8
package/dist/frizz.js CHANGED
@@ -12703,7 +12703,7 @@ var init_src = __esm({
12703
12703
  questions: external_exports.array(AskQuestion)
12704
12704
  });
12705
12705
  AwaitingHint = external_exports.object({
12706
- kind: external_exports.enum(["shell", "agent", "timer", "pr", "for"]),
12706
+ kind: external_exports.enum(["shell", "agent", "timer", "pr", "for", "title"]),
12707
12707
  value: external_exports.string()
12708
12708
  });
12709
12709
  RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
@@ -13280,6 +13280,14 @@ var init_src = __esm({
13280
13280
  // Default action for a vetted non-image local path in agent markdown. Image clicks always use the
13281
13281
  // OS default viewer so screenshots retain their expected behavior.
13282
13282
  localFileOpener: LocalFileOpener.optional(),
13283
+ // The token count at which a NEW Claude worker auto-compacts its conversation. Frizz requests the
13284
+ // 1M context window on every dispatch (resolveClaudeLaunchModel), so without this a worker grows
13285
+ // toward 1M before it ever compacts — and every turn past 200K re-sends up to 5x the conversation a
13286
+ // 200K TUI session would, which is the single largest reason a Frizz thread spends quota faster than
13287
+ // the TUI (measured 2026-08-26). Reaches the worker as CLAUDE_CODE_AUTO_COMPACT_WINDOW, which Claude
13288
+ // Code caps to the model's real window. Optional so an old blob parses; defaultSettings pins 500_000
13289
+ // (maintainer 2026-08-26: "a default compaction window of 500k by default").
13290
+ autoCompactWindow: external_exports.number().int().positive().optional(),
13283
13291
  // The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
13284
13292
  // unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
13285
13293
  // the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
@@ -22797,6 +22805,7 @@ var b64url = (bytes) => {
22797
22805
  for (const byte of view) binary += String.fromCharCode(byte);
22798
22806
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
22799
22807
  };
22808
+ var SETTLED_MS = 1e4;
22800
22809
  function defaultBackoff(attempt, random = Math.random) {
22801
22810
  const base = Math.min(3e4, 500 * 2 ** Math.min(attempt, 6));
22802
22811
  return Math.round(base / 2 + random() * (base / 2));
@@ -22846,8 +22855,11 @@ function connectRelay(options2) {
22846
22855
  const url = `${options2.relayOrigin.replace(/^http/, "ws").replace(/\/$/, "")}/_relay/connect?h=${encoded}`;
22847
22856
  const next = makeSocket(url);
22848
22857
  socket = next;
22858
+ let settled;
22849
22859
  next.addEventListener("open", () => {
22850
- attempt = 0;
22860
+ settled = setTimer(() => {
22861
+ attempt = 0;
22862
+ }, SETTLED_MS);
22851
22863
  options2.onStatus?.("connected");
22852
22864
  });
22853
22865
  next.addEventListener("message", (event) => {
@@ -22888,6 +22900,10 @@ function connectRelay(options2) {
22888
22900
  });
22889
22901
  const dropped = () => {
22890
22902
  if (socket !== next) return;
22903
+ if (settled !== void 0) {
22904
+ clearTimer(settled);
22905
+ settled = void 0;
22906
+ }
22891
22907
  socket = null;
22892
22908
  for (const session of nested.values()) session.close();
22893
22909
  nested.clear();
@@ -23343,6 +23359,7 @@ function createRemotePane(options2) {
23343
23359
  ...wrap(
23344
23360
  "Claims <name>.frizz.sh for your GitHub account. The board dials out to frizz.sh \u2014 no port, no DNS record, no tunnel binary. One name per account; a name nobody runs for 30 days is released."
23345
23361
  ),
23362
+ ...options2.sandbox ? ["", ...wrap("This is a sandbox, but a claim is real: it binds this machine's one name to your account, and your real board keeps it. Only the setup saved here is thrown away.")] : [],
23346
23363
  "",
23347
23364
  gh
23348
23365
  ];
@@ -23881,8 +23898,8 @@ init_src();
23881
23898
  init_project_registry();
23882
23899
  import { execFileSync as execFileSync3 } from "node:child_process";
23883
23900
  import { createServer as createServer2 } from "node:net";
23884
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync11, realpathSync as realpathSync5, writeFileSync as writeFileSync10 } from "node:fs";
23885
- import { homedir as homedir10 } from "node:os";
23901
+ import { existsSync as existsSync5, mkdirSync as mkdirSync10, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync11, realpathSync as realpathSync5, rmSync as rmSync8, symlinkSync as symlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
23902
+ import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
23886
23903
  import { basename as basename5, join as join13, resolve as resolve3 } from "node:path";
23887
23904
  import { setTimeout as delay2 } from "node:timers/promises";
23888
23905
  var PORT_SCAN_COUNT = 100;
@@ -23946,6 +23963,7 @@ function parseCliArgs(argv) {
23946
23963
  "--stop",
23947
23964
  "--status",
23948
23965
  "--link",
23966
+ "--sandbox",
23949
23967
  "--sessions",
23950
23968
  "--sign-out",
23951
23969
  "--help",
@@ -23973,6 +23991,12 @@ function parseCliArgs(argv) {
23973
23991
  throw new Error("--detach is no longer available; frizz-dev always runs in the foreground");
23974
23992
  if (args2.has("--app") && args2.has("--no-app"))
23975
23993
  throw new Error("choose either --app or --no-app");
23994
+ if (args2.has("--sandbox")) {
23995
+ for (const query of ["--stop", "--status", "--link", "--sessions", "--sign-out"]) {
23996
+ if (args2.has(query) || argv.some((arg) => arg.startsWith(`${query}=`)))
23997
+ throw new Error(`${query} asks the running board, and --sandbox starts a fresh disposable one \u2014 run ${query} without --sandbox`);
23998
+ }
23999
+ }
23976
24000
  return {
23977
24001
  noApp: args2.has("--no-app"),
23978
24002
  appMode: args2.has("--app"),
@@ -23980,6 +24004,7 @@ function parseCliArgs(argv) {
23980
24004
  foreground: true,
23981
24005
  stop: args2.has("--stop"),
23982
24006
  link: args2.has("--link"),
24007
+ sandbox: args2.has("--sandbox"),
23983
24008
  sessions: args2.has("--sessions"),
23984
24009
  ...rawSignOut !== void 0 ? { signOut: rawSignOut } : {},
23985
24010
  status: args2.has("--status"),
@@ -23992,6 +24017,57 @@ function parseCliArgs(argv) {
23992
24017
  function boardAddress(url) {
23993
24018
  return url.includes("?") || url.endsWith("/") ? url : `${url}/`;
23994
24019
  }
24020
+ function prepareSandbox(env = process.env, realHome = homedir10()) {
24021
+ const home = mkdtempSync(join13(tmpdir2(), "frizz-sandbox-"));
24022
+ shareCredentials(realHome, home);
24023
+ env.HOME = home;
24024
+ env.USERPROFILE = home;
24025
+ const project = join13(home, "sandbox");
24026
+ mkdirSync10(project, { recursive: true });
24027
+ writeFileSync10(join13(project, "README.md"), "# frizz sandbox\n\nA throwaway project; everything here is deleted when the sandbox exits.\n");
24028
+ try {
24029
+ execFileSync3("git", ["init", "-q"], { cwd: project, stdio: "ignore" });
24030
+ } catch {
24031
+ throw new Error("--sandbox mints a throwaway git repository and git is not available \u2014 install git, or play in a repo of your own with a temp HOME");
24032
+ }
24033
+ process.chdir(project);
24034
+ return { home, project };
24035
+ }
24036
+ function shareCredentials(realHome, home) {
24037
+ const link = (from, to) => {
24038
+ try {
24039
+ symlinkSync2(from, to);
24040
+ } catch {
24041
+ }
24042
+ };
24043
+ mkdirSync10(join13(realHome, ".frizz"), { recursive: true });
24044
+ mkdirSync10(join13(home, ".frizz"), { recursive: true });
24045
+ link(join13(realHome, ".frizz", "identity.key"), join13(home, ".frizz", "identity.key"));
24046
+ if (existsSync5(join13(realHome, ".config", "gh"))) {
24047
+ mkdirSync10(join13(home, ".config"), { recursive: true });
24048
+ link(join13(realHome, ".config", "gh"), join13(home, ".config", "gh"));
24049
+ }
24050
+ const cloudflared = join13(realHome, ".cloudflared");
24051
+ if (existsSync5(cloudflared)) {
24052
+ mkdirSync10(join13(home, ".cloudflared"), { recursive: true });
24053
+ for (const entry of readdirSync3(cloudflared)) {
24054
+ if (entry === "cert.pem" || entry.endsWith(".json")) link(join13(cloudflared, entry), join13(home, ".cloudflared", entry));
24055
+ }
24056
+ }
24057
+ for (const entry of [".claude", ".claude.json", ".codex"]) {
24058
+ if (existsSync5(join13(realHome, entry))) link(join13(realHome, entry), join13(home, entry));
24059
+ }
24060
+ if (process.platform === "darwin" && existsSync5(join13(realHome, "Library", "Keychains"))) {
24061
+ mkdirSync10(join13(home, "Library"), { recursive: true });
24062
+ link(join13(realHome, "Library", "Keychains"), join13(home, "Library", "Keychains"));
24063
+ }
24064
+ }
24065
+ function cleanupSandbox(home) {
24066
+ try {
24067
+ rmSync8(home, { recursive: true, force: true });
24068
+ } catch {
24069
+ }
24070
+ }
23995
24071
  function resolveLaunchIntent(cwd = process.cwd(), home = homedir10(), env = process.env) {
23996
24072
  const candidate = realpathSync5(discoverProjectRoot(cwd, home));
23997
24073
  const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
@@ -24246,7 +24322,7 @@ init_project_launch();
24246
24322
  init_project_launch();
24247
24323
  import { fork } from "node:child_process";
24248
24324
  import { randomUUID as randomUUID5 } from "node:crypto";
24249
- import { existsSync as existsSync5, mkdirSync as mkdirSync11, readFileSync as readFileSync12 } from "node:fs";
24325
+ import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync12 } from "node:fs";
24250
24326
  import { createServer as createNetServer } from "node:net";
24251
24327
  import { basename as basename6, dirname as dirname10, extname as extname2, isAbsolute as isAbsolute3, relative, resolve as resolve4, sep as sep2 } from "node:path";
24252
24328
  import { fileURLToPath } from "node:url";
@@ -24583,7 +24659,7 @@ var Supervisor = class {
24583
24659
  await this.publicProxy.listen();
24584
24660
  if (this.watchEnabled) {
24585
24661
  const settled = await Promise.allSettled(
24586
- this.roots.filter(existsSync5).map((root) => this.watchSubscribe(root, (err, events) => this.onWatch(err, events), {
24662
+ this.roots.filter(existsSync6).map((root) => this.watchSubscribe(root, (err, events) => this.onWatch(err, events), {
24587
24663
  ignore: DEV_WATCH_IGNORE
24588
24664
  }))
24589
24665
  );
@@ -25215,7 +25291,12 @@ for a source checkout.
25215
25291
  Options:
25216
25292
  --no-app print the URL without opening a browser
25217
25293
  --port <port> request a fixed port for a new workspace server
25294
+ --sandbox a disposable Frizz to try things in: throwaway home and project, its
25295
+ own port, deleted when this terminal closes; credentials (gh,
25296
+ cloudflared, Claude, Codex, the machine's frizz.sh key) are shared
25218
25297
  --link print a fresh single-use access link for the running board
25298
+ --sessions list the devices signed in to the running board
25299
+ --sign-out <id|all> sign one device out, or every one of them
25219
25300
  --debug stream the full event feed to the terminal instead of the compact readout
25220
25301
  -h, --help show this help
25221
25302
 
@@ -25228,6 +25309,8 @@ shows a single-use sign-in link as a QR; press L for a fresh one, or run --link
25228
25309
  process.exit(0);
25229
25310
  }
25230
25311
  if (options.dev || rawArgs.includes("--prod")) fail("--dev and --prod are not available from the registry launcher");
25312
+ var sandbox = options.sandbox ? prepareSandbox() : null;
25313
+ if (sandbox) process.on("exit", () => cleanupSandbox(sandbox.home));
25231
25314
  var remote = null;
25232
25315
  var paneHost = null;
25233
25316
  var accessPane = null;
@@ -25362,6 +25445,7 @@ async function openOrPrint(port, reused, path = "") {
25362
25445
  }
25363
25446
  const publicOrigin = reused ? void 0 : remote?.origin();
25364
25447
  const warnings = [];
25448
+ if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
25365
25449
  if (!readout) {
25366
25450
  console.log(`${reused ? "reusing" : "started"} Frizz ${PACKAGE_VERSION} for ${workspace.root}`);
25367
25451
  console.log(url);
@@ -25490,7 +25574,8 @@ async function runSupervisor(port, token) {
25490
25574
  claim: (name) => establishCloudConfig(name, port),
25491
25575
  issueLink: () => supervisor.issueAccessLink(),
25492
25576
  probes: { github: probeGithub, cloudflared: probeCloudflared, tailscale: probeTailscale },
25493
- onChanged: (config) => logger.info("remote", config ? `reached at https://${config.hostname}` : "loopback only")
25577
+ onChanged: (config) => logger.info("remote", config ? `reached at https://${config.hostname}` : "loopback only"),
25578
+ sandbox: sandbox !== null
25494
25579
  });
25495
25580
  paneHost = installPaneHost({ bindings: { l: accessPane, L: accessPane, r: remotePane, R: remotePane } });
25496
25581
  const stop = createSupervisorShutdownHandler({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz",
3
- "version": "0.7.8",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "A local web UI for running many coding agents at once",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz",
3
- "description": "Worker-side frizz plugin for frizz-spawned sessions. Each session is a WORKER driving ONE effort (slug via env FRIZZ_THREAD): it SIGNALS through its final message (done, human/timer awaiting, question), keeps automatable waits active with Claude wait tools, and keeps its own free-form scratch directory (.frizz/threads/<session-id>/). Supplies the frizz:<model>-<effort> sub-agent profiles (dispatch as frizz:opus-high, frizz:sonnet-medium, frizz:haiku, ), the on-demand frizz:gh skill, and hooks that seed the worker contract, enforce background/name-stripped sub-agent dispatch, and deny the interactive question/plan prompts (deny-ask, deny-plan) that would hang a dashboard worker. Hooks are inert unless FRIZZ_THREAD is set.",
3
+ "description": "Worker-side frizz plugin for frizz-spawned sessions. Each session is a WORKER driving ONE effort (slug via env FRIZZ_THREAD): it SIGNALS through its final message (done, human/timer awaiting, question), keeps automatable waits active with Claude wait tools, and keeps its own free-form scratch directory (.frizz/threads/<session-id>/). Supplies the effort-only frizz:<effort> sub-agent profiles (frizz:low frizz:max; the model rides the Agent tool's own parameter), the on-demand frizz:gh skill, and hooks that seed the worker contract, enforce background/name-stripped sub-agent dispatch, and deny the interactive question/plan prompts (deny-ask, deny-plan) that would hang a dashboard worker. Hooks are inert unless FRIZZ_THREAD is set.",
4
4
  "version": "0.1.10",
5
5
  "author": { "name": "Colin McDonnell" },
6
6
  "homepage": "https://github.com/colinhacks/frizz",
@@ -1032,7 +1032,7 @@ Maintainer's call, verbatim: *"This is obviously something that should not be a
1032
1032
 
1033
1033
  Three removals, one theme — Frizz's shipped worker contract states frizz MECHANICS, and everything else belongs to the repo or to the dispatch that starts the thread.
1034
1034
 
1035
- - **`runtimeGate` (setting) + `RUNTIME_GATE` (prompt module) — gone.** The browser-QA loop (drive it in Chrome, screenshot into the handoff, escalate to an adversarial reviewer) was frizz's own engineering norm shipped to every worker Frizz dispatches anywhere. That is what `FRIZZ.md` / `CLAUDE.md` are for: `frizzConfigBlock` already injects a repo's own conventions into every spawn/adopt/resume, and this repo keeps its full browser-QA section there. What remains in the shipped contract is the repo-agnostic line that was already in the Quality bar — *"Verify behavior end-to-end before calling anything done."* `VISUAL_EVIDENCE` **stays**: it documents Frizz's guarded local-image proxy, a platform capability, not a QA policy. `chrome-devtools` also stays always-mounted — giving a worker a browser is a capability, not an opinion about when to use it.
1035
+ - **`runtimeGate` (setting) + `RUNTIME_GATE` (prompt module) — gone.** The browser-QA loop (drive it in Chrome, screenshot into the handoff, escalate to an adversarial reviewer) was frizz's own engineering norm shipped to every worker Frizz dispatches anywhere. That is what `FRIZZ.md` / `CLAUDE.md` are for: `frizzConfigBlock` already injects a repo's own conventions into every spawn/adopt/resume, and this repo keeps its full browser-QA section there. What remains in the shipped contract is the repo-agnostic line that was already in the Quality bar — *"Verify behavior end-to-end before calling anything done."* `VISUAL_EVIDENCE` **stays**: it documents Frizz's guarded local-image proxy, a platform capability, not a QA policy. `chrome-devtools` also stays always-mounted — giving a worker a browser is a capability, not an opinion about when to use it. **(Reversed 2026-08-26 — see the entry at the end of this file. The reasoning above survives one step further than it should have: mounting a browser is itself an opinion, and it was billed to every worker in tokens.)**
1036
1036
  - **Model + Effort — removed from the settings drawer.** They are chosen per dispatch in the prompt box (`DispatchPreferences`, one profile per runtime); a second global copy only made it ambiguous which one applied. `Settings.model` / `Settings.effort` survive on the wire: `dispatch-preferences.ts` still seeds the composer's first-run profile from them and `dispatch.ts` still falls back to them for GitHub batch dispatch.
1037
1037
  - **`autoResumeOnLimit` — gone; auto-resume is unconditional.** A thread cut off mid-turn by an exhausted subscription window always gets its own "continue" when the window rolls. `ThreadView.limitPause.autoResume` survives on the wire but is now purely a STALENESS verdict (`resolveLimitPause`): a fault old enough that the wake will never arrive stops promising one.
1038
1038
 
@@ -1112,3 +1112,31 @@ Maintainer: *"Changing the model and effort in a prompt box. That should be reme
1112
1112
  - **The web cache treats `["dispatchPreferencesGet"]` as machine-wide** (`lib/queryKeyScope.ts`), so the composer's optimistic write on one project is what a client-side switch paints on the next, rather than that project's last-seen copy for the beat before a refetch.
1113
1113
 
1114
1114
  Verified on a real two-project stack (launcher + tenant on one server): a profile chosen through the tenant's RPC is what the launcher reads, and back; a per-project `settingsSet` on the launcher stayed invisible to the tenant, which is the control that the two prefixes reach different projects.
1115
+ ## 2026-08-26: Frizz mounts NO browser — a `chrome-devtools` server is the project's to bring
1116
+
1117
+ Maintainer: *"Is the Chrome DevTool stuff something that frizz automatically injects? Why not just let the user bring that themselves? That's very confusing to me. I feel like there's a level of opinionation here that we don't really want."*
1118
+
1119
+ This reverses the 2026-08-03 line above (*"`chrome-devtools` also stays always-mounted — giving a worker a browser is a capability, not an opinion"*). The capability argument stopped holding once the bill was measured: the server's 29 tool schemas sat in the prefix of **every** worker session at **~6,400 tokens**, and most workers never open a page. A capability nobody asked for, charged per turn, is an opinion.
1120
+
1121
+ - **Both backends stopped injecting it.** Claude's inline `--mcp-config` (`claudeMcpConfig`, `dispatch.ts`) and codex's `-c mcp_servers.chrome-devtools=…` app-server override (`codex-mcp.ts`) now mount the unified `frizz` server and nothing else. Parity was the point of the original pairing and it is the point of the removal.
1122
+ - **The lazy proxy went with it.** `cc-worker/bin/browser-mcp.mjs` and its committed schema snapshot `browser-mcp-tools.json` existed only to make the always-on mount affordable in MEMORY (159 MB → ~17 MB per worker, 2026-08-19). With no mount there is nothing to proxy, so they are deleted along with `chromeDevtoolsMcpSpec` / `chromeDevtoolsMcpMount` / `resolveBrowserMcpScript` / `CHROME_DEVTOOLS_MCP`, `scripts/harvest-browser-mcp-tools.mjs`, and the worker-plugin closure entries that required them. The memory win it bought is now the token win.
1123
+ - **Nothing about a browser is banned — it is just not Frizz's to decide.** A project adds a `.mcp.json` (plus `enabledMcpjsonServers` in `.claude/settings.json`, so a headless worker is never blocked on the approval prompt); an operator runs `claude mcp add --scope user`. Frizz's `--mcp-config` ADDS to whatever the CLI discovered rather than replacing it (frizz never passes `--strict-mcp-config`), so both paths reach a dispatched worker untouched.
1124
+ - **This repo does exactly that, and that is the whole demonstration.** Frizz's own skills drive Chrome headless, so the repo root carries the `.mcp.json` that used to be injected — pinned `chrome-devtools-mcp@1.7.0 --experimentalPageIdRouting --headless --isolated --no-usage-statistics`, the same argv the deleted spec rendered. Verified end-to-end on a disposable stack: a worker dispatched in a scratch project with no config reported `frizz` tools and no browser; a worker dispatched in this repo reported the full `mcp__chrome-devtools__*` set with no permission prompt.
1125
+ - **What deliberately stayed.** The orphan reaper still reaps `chrome-devtools-mcp` processes by name (a browser the USER brought leaks exactly the same way), and the transcript still renders a `take_screenshot` image result. Neither depends on who mounted the server.
1126
+
1127
+ ## 2026-08-26: Sub-agent profiles are effort-only, and the dispatch epilogue carries mechanics, not a handoff format
1128
+
1129
+ Follow-on to the browser decision above, from the same audit of what Frizz registers into a worker that the worker did not ask for. The maintainer's frame — *"a level of opinionation here that we don't really want"* — applied to two more registrations, and the frizz Goal had this thread decide them itself.
1130
+
1131
+ - **16 `frizz:<model>-<effort>` profiles → 5 `frizz:<effort>` ones.** The model×effort grid existed because the Agent tool once had no way to pin either; today its own `model` parameter overrides a profile's model, so the only knob a profile still has to carry is `effort`. The grid cost ~1,340 tokens of agent descriptions on every session, and those descriptions doubled as a routing doctrine ("Opus matches Fable and costs less", "Haiku for scripted harvest only") that no project asked for. `low` … `max` carry no model and inherit the worker's; Haiku, which takes no effort setting, is dispatched with `model: "haiku"` and no profile. The worker contract's Sub-agents section now states the two knobs and nothing about which tier deserves which task.
1132
+ - **The dispatch-hook epilogue kept only what a helper cannot discover.** Its first paragraph prescribed the shape of a helper's final message (status, files, SHA, evidence, caveats, next action); that is a handoff format, and it is gone — one sentence remains saying the final message is what the dispatcher reads. The rest stayed because each line is a mechanic learned from a real failure: the `SendMessage({to: "main"})` upward channel, the own-file rule for the scratch directory, no fan-out of its own unless asked, and the symlinked-`.output` wait-loop trap.
1133
+ - **Verified** by the live harness (`_live_broker_workerenv.mts`, now dispatching `frizz:low`): a real broker worker through the real server dispatched the profile and it ran.
1134
+
1135
+ ## 2026-08-26: three more worker registrations deleted — the waits skill, the pre-compaction brief, and the toon nudge
1136
+
1137
+ Third pass of the same audit that removed the browser mount and the model×effort profile grid above, and the same test: does Frizz register this into every worker because the worker needs it, or because Frizz has an opinion?
1138
+
1139
+ - **`skills/waits` is DELETED.** Maintainer: *"I feel like the waits skill is being replaced now. I don't think we need that. We have our own system now for registering watchers and whatnot as tools."* The skill's whole job was teaching a worker how to hold a wait open without falling out of the board's Active band, and the durable half of that is now TOOLS — `mcp__frizz__watch_pr`, `mcp__frizz__timer`, `mcp__frizz__recurring_prompt` — which carry their own descriptions and need no playbook. What remains (a sub-agent owns the wait, `run_in_background` does not hold a rest, never fake a wait with a foreground sleep) is already stated once in the system-prompt contract, which is where a mechanical rule belongs.
1140
+ - **The portable monitors STAYED.** `skills/gh/scripts/{ci-watch,github-watch,review-watch}.mjs` are generated byte-for-byte from `monitors/` and are load-bearing for two other readers: the codex worker prompt names their absolute directory (`monitorScriptsDir()`, `dispatch.ts`, pinned by `dispatch.test.ts`), and `skills/gh` documents them as its declared-tooling fallback. Only the skill went.
1141
+ - **`hooks/precompact-instructions.mjs` is DELETED**, with both its `PreCompact` registrations. Maintainer: *"the pre-compaction instructions are probably too opinionated, entirely too opinionated."* It handed the summarizer an editorial brief about WHAT to preserve — plan, alternatives rejected, rationale, at high fidelity — which is a prescription about how someone else's work should be remembered. `scratchpad.mjs --mode=precompact` keeps both matchers: it only says the worker's scratch files exist and names their paths, which is mechanics.
1142
+ - **The `toon` bullet is gone from the seed's `⟦gh available⟧` block and from `skills/gh`.** Maintainer: *"It seems pretty opinionated to tell it to pipe through toon, given that toon is probably not installed on most machines. That might be over the line."* The original reasoning (`plans/github-integration.md` §9: worker-side yes, server-side no) was measured against THIS machine's toolchain; a `command -v toon` guard makes the advice safe but not less opinionated, and it spent prompt tokens on every authed session to describe a binary almost no repo has. The one raw-API recipe that piped through it now uses `--jq`, which ships with `gh`.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: high
3
+ description: Frizz sub-agent at high effort. Set the model with the Agent tool's `model` parameter; omitted, the child inherits the worker's model.
4
+ effort: high
5
+ ---
6
+
7
+ Execute the self-contained task in your dispatch prompt.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: low
3
+ description: Frizz sub-agent at low effort. Set the model with the Agent tool's `model` parameter; omitted, the child inherits the worker's model.
4
+ effort: low
5
+ ---
6
+
7
+ Execute the self-contained task in your dispatch prompt.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: max
3
+ description: Frizz sub-agent at max effort. Set the model with the Agent tool's `model` parameter; omitted, the child inherits the worker's model.
4
+ effort: max
5
+ ---
6
+
7
+ Execute the self-contained task in your dispatch prompt.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: medium
3
+ description: Frizz sub-agent at medium effort. Set the model with the Agent tool's `model` parameter; omitted, the child inherits the worker's model.
4
+ effort: medium
5
+ ---
6
+
7
+ Execute the self-contained task in your dispatch prompt.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: xhigh
3
+ description: Frizz sub-agent at xhigh effort. Set the model with the Agent tool's `model` parameter; omitted, the child inherits the worker's model.
4
+ effort: xhigh
5
+ ---
6
+
7
+ Execute the self-contained task in your dispatch prompt.
@@ -6,10 +6,11 @@
6
6
  // foreground agent blocks the worker's turn; a human interjection orphans it).
7
7
  // 2) STRIP `name`/`team_name` — setting either strands a nested dispatch (its result routes
8
8
  // wrong and never returns cleanly), so scrub both silently.
9
- // 3) AUTO-APPEND a repo-neutral ORCHESTRATION EPILOGUE so helpers return a useful handoff,
10
- // know how to reach their dispatcher mid-flight, do NOT fan out a layer of their own unless
11
- // their prompt asked for it, and collect a helper correctly when it did without imposing
12
- // build, test, git, compilation, or process-lifecycle policy on arbitrary repos.
9
+ // 3) AUTO-APPEND a short ORCHESTRATION EPILOGUE carrying only what a helper cannot discover on
10
+ // its own: that its final message is the return value, the `SendMessage({to: "main"})` upward
11
+ // channel, the own-file rule for the scratch directory, no fan-out of its own unless asked, and
12
+ // how a helper it DID dispatch is collected. It carries no handoff format and no build, test,
13
+ // git or process policy — the maintainer had the handoff-format doctrine cut on 2026-08-26.
13
14
  //
14
15
  // WHY NESTING IS DEFAULT-OFF (2026-08-04, maintainer's call): the epilogue used to speak about a
15
16
  // helper's own helper only in the conditional ("if you dispatch a helper of your own…"), which reads
@@ -45,10 +46,10 @@ import { readFileSync } from 'node:fs';
45
46
  const EPILOGUE = `
46
47
 
47
48
  ---
48
- [ORCHESTRATION EPILOGUE — auto-appended by the frizz worker dispatch hook] You are a helper sub-agent for a frizz worker. Your final message is the handoff: report your outcome/status; what you did; changed files, artifacts, and commit SHA when applicable; verification or evidence and its result; caveats or unresolved work; and the next action when one remains. A bare "done" or progress-only message is not a complete handoff.
49
+ [ORCHESTRATION EPILOGUE — auto-appended by the frizz worker dispatch hook] You are a helper sub-agent for a frizz worker. Your final message is the handoff your dispatcher reads: say what you did, what you verified, and what remains.
49
50
  The worker has a scratch directory (it will name the path if it wants your notes on disk). Write your OWN file there — never edit or delete a file another agent wrote, and never treat one shared document as yours to rewrite. Writing your own file there is Frizz coordination state, not a project deliverable or source edit, so it remains allowed when a delegated task limits your deliverable paths with phrases such as "write only <path>" or "do not modify the repo"; never classify it as unauthorized or roll it back. Other project files, including repository-root files, remain governed by your delegated authority; their location alone neither permits nor forbids editing. Do not edit other \`.frizz/\` state unless your prompt explicitly asks you to.
50
51
  You have an upward channel while you work: \`SendMessage({to: "main", summary: "<5-10 words>", message: "…"})\` delivers to your dispatcher. Use it when the dispatcher acting before you finish could change the outcome—for example, when you hit an unresolved blocker, complete a milestone another task needs, or discover that your instructions should change. Do not use it for routine progress updates.
51
- Do the work yourself: do NOT dispatch sub-agents of your own unless your dispatch prompt explicitly tells you to. You are already one prong of someone else's fan-out, and another layer below you buys little — it splits the context you were handed, buries the real work one level further from whoever reads the tree, and leaves you collecting a handoff instead of doing the task. A slice that feels large is still yours to work through in your own turn.
52
+ Do the work yourself: do NOT dispatch sub-agents of your own unless your dispatch prompt explicitly tells you to. You are already one prong of someone else's fan-out; a slice that feels large is still yours to work through in your own turn.
52
53
  If your prompt DOES ask you to dispatch a helper, its completion is delivered to you automatically. Never hand-roll a wait loop over a helper's transcript or \`.output\` path to decide whether it finished: that path is a SYMLINK, so \`stat\` without \`-L\` reports the link's own size (the length of its target path, ~150 bytes) and its frozen creation mtime, and the \`"type":"result"\` record is not reliably written — so a helper that is working hard reads as tiny, stale, and dead, and you will discard live work and redo it. Judge a helper only by its completion notification or the text it returns. Give it a \`description\` naming its narrower slice rather than restating your own, so the dispatch tree stays readable.`;
53
54
 
54
55
  /** @param {unknown} obj @returns {never} */
@@ -58,14 +58,12 @@
58
58
  {
59
59
  "matcher": "auto",
60
60
  "hooks": [
61
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/precompact-instructions.mjs\"" },
62
61
  { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scratchpad.mjs\" --mode=precompact" }
63
62
  ]
64
63
  },
65
64
  {
66
65
  "matcher": "manual",
67
66
  "hooks": [
68
- { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/precompact-instructions.mjs\"" },
69
67
  { "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/scratchpad.mjs\" --mode=precompact" }
70
68
  ]
71
69
  }
@@ -280,9 +280,11 @@ if (mode === 'session-start') {
280
280
  }
281
281
 
282
282
  // ── mode: precompact ─────────────────────────────────────────────────────────────────────────────
283
- // PLAIN STDOUT — joined with the other PreCompact hooks' stdout into the summarizer's
284
- // `Additional Instructions:`. Worded as an ordinary editorial note: precompact-instructions.mjs
285
- // records that a summarizer REFUSES instructions that read like prompt-hijacking.
283
+ // PLAIN STDOUT — handed to the summarizer as its `Additional Instructions:`. cc's usual
284
+ // `hookSpecificOutput` JSON would be read as literal instructions instead. Worded as an ordinary
285
+ // editorial note: a summarizer REFUSES instructions that read like prompt-hijacking (DECISIONS.md).
286
+ // This is the ONLY PreCompact hook the plugin registers — `precompact-instructions.mjs`, which also
287
+ // steered the summarizer, was deleted on 2026-08-26 as too opinionated.
286
288
  if (mode === 'precompact') {
287
289
  const files = listScratch();
288
290
  if (files.length === 0) process.exit(0);
@@ -8,10 +8,9 @@
8
8
  // frontmatter, no status field — a worker SIGNALS through its final message (fences), and anything that
9
9
  // must outlive its context window is an arrangement it makes for itself. This hook injects, on every
10
10
  // session start (startup/resume/clear/compact):
11
- // 1. `core` — a runtime re-grounding + pointer, NOT a second copy of the contract: the full worker
12
- // contract lives ONCE in the system prompt (workerPrompt.ts) the server injects at spawn. This
13
- // carries only what a static system prompt can't: the runtime scratch-directory PATH + an essential
14
- // signal-at-rest anchor + a pointer to the system-prompt contract.
11
+ // 1. `core` — a runtime POINTER, not a copy of the contract: the full worker contract lives ONCE in
12
+ // the system prompt (workerPrompt.ts) the server injects at spawn. This carries only what a
13
+ // static system prompt can't: the runtime scratch-directory PATH, and a one-line pointer.
15
14
  // 2. the SCRATCH DIRECTORY — `.frizz/threads/<session_id>/`, a folder the worker may use as it likes.
16
15
  // 3. on `compact` — a short re-grounding (compaction drops the deep model + this orientation).
17
16
  //
@@ -57,21 +56,23 @@ const scratch = sid
57
56
  ? '.frizz/threads/' + sid + '/'
58
57
  : '.frizz/threads/<session-id>/';
59
58
 
60
- // A RUNTIME re-grounding + pointer, NOT a second copy of the contract. The full worker contract
61
- // (signal fences, scratch-directory rules, sub-agent rules, the question handback) lives
62
- // ONCE in the system prompt frizz injects at spawn (workerPrompt.ts / loadWorkerPrompt) — which is
63
- // re-applied on every resume and survives compaction. This hook adds only what a static system prompt
64
- // CANNOT carry: the runtime-derived scratch-directory PATH, an essential signal-at-rest anchor, and (below)
65
- // the compaction re-read nudge, gh guidance, and the defensive cc-orchestrator off-sentinel.
59
+ // A RUNTIME pointer, NOT a copy of the contract. The full worker contract (signal fences,
60
+ // scratch-directory rules, sub-agent rules, the question handback, the stop criterion) lives ONCE in
61
+ // the system prompt frizz injects at spawn (workerPrompt.ts / loadWorkerPrompt) — re-applied on every
62
+ // resume, and it survives compaction. This hook adds only what a static system prompt CANNOT carry:
63
+ // the runtime-derived scratch-directory PATH, plus (below) the compaction re-read nudge and the
64
+ // auth-gated gh guidance.
65
+ //
66
+ // It used to restate the fence protocol, the stop criterion and the autonomy rule in full — ~4.4 KB
67
+ // (~1,100 tokens) on every startup, resume, clear AND compact, all of it already in the 42 KB system
68
+ // prompt. Trimmed 2026-08-26 (maintainer: "Definitely trim the session seed hook if it's fully
69
+ // repetitive") as part of cutting the per-session token overhead Frizz adds over a plain TUI session.
66
70
  const core =
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
- '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
- '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 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
- '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.';
71
+ '⟦frizz worker contract⟧ You are a frizz WORKER driving EXACTLY ONE effort. Your FULL operating contract — the end-of-turn signal fences (```done / ```awaiting / ```question), the scratch-directory rules, the sub-agent rules, the question handback and the stop criterion — lives in your SYSTEM PROMPT; follow it there. Always sign off with a fence.\n' +
72
+ 'SCRATCH DIRECTORY (OPTIONAL): `' + scratch + '` — a folder kept FOR YOU, nothing in it read automatically, never a substitute for doing the work. Give each sub-agent its OWN file rather than a shared one.';
72
73
 
73
74
  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 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
+ '⟦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. Your system prompt still carries the full contract; sign off with a fence as it says.';
75
76
 
76
77
  // AUTH-GATED gh guidance — teach the worker to use `gh` well, but ONLY when signed in.
77
78
  // Shell `gh auth status --active`: exit 0 = an active gh account is authenticated. The whole gate is
@@ -84,7 +85,6 @@ const ghBlock =
84
85
  '• READ freely: `gh issue view N -R OWNER/REPO --comments`, `gh pr view N`, `gh pr diff N`, `gh pr checks N`, `gh run list`/`gh run view`, `gh api repos/OWNER/REPO/…`. Prefer `--json <fields>` over scraping human text.\n' +
85
86
  '• SEARCH across the repo (and GitHub) with `gh search issues`/`gh search prs` when hunting related work, duplicates, or prior art.\n' +
86
87
  '• READ-ONLY BOUNDARY: never comment, label, assign, close, review, approve, or merge — no mutation of any kind — UNLESS the human explicitly asks in this session. Default to producing your findings/review as your final message, not as a GitHub post.\n' +
87
- '• TOON: pipe LARGE, FLAT `gh … --json` output through `toon` when `command -v toon` finds it. Skip it when unavailable, for tiny payloads, or for deeply-nested output — the savings are noise and nesting defeats tabularization.\n' +
88
88
  'Load the `frizz:gh` skill for the full playbook (recipes + explicit project-local monitor selection + native Monitor/background-Bash CI/PR watches).';
89
89
 
90
90
  let ghAuthed = false;
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: gh
3
- description: The gh-CLI playbook for a frizz worker signed into GitHub (invoke as frizz:gh). Load this whenever your effort touches GitHub — reading or triaging an issue or PR, reviewing a diff, checking CI/release status, or searching issues/PRs — to use `gh` eagerly and correctly: the read-vs-write boundary (never comment/label/close/merge unless the human asks), optional toon use for large JSON, concrete read recipes, and active Monitor/background-Bash CI/PR watches. Only meaningful when you are signed in (`gh auth status --active` exit 0); the session-seed hook injects a pointer here when you are.
4
- version: 0.1.1
3
+ description: The gh-CLI playbook for a frizz worker signed into GitHub (invoke as frizz:gh). Load this whenever your effort touches GitHub — reading or triaging an issue or PR, reviewing a diff, checking CI/release status, or searching issues/PRs — to use `gh` eagerly and correctly: the read-vs-write boundary (never comment/label/close/merge unless the human asks), concrete read recipes, and active Monitor/background-Bash CI/PR watches. Only meaningful when you are signed in (`gh auth status --active` exit 0); the session-seed hook injects a pointer here when you are.
4
+ version: 0.1.2
5
5
  metadata:
6
6
  internal: true
7
7
  ---
@@ -10,7 +10,7 @@ metadata:
10
10
 
11
11
  You are a **frizz worker** and you are **signed into the `gh` CLI in a GitHub repo** (the session-seed hook confirmed `gh auth status --active` before pointing you here). `gh` is the fastest path to issue / PR / CI / release context — reach for it before guessing, and prefer it over scraping the web UI or reasoning from memory.
12
12
 
13
- This skill is the full playbook the injected `⟦gh available⟧` block summarizes: the **read-vs-write boundary**, optional **toon** use for large JSON, concrete **read recipes**, and how to keep a **CI/PR watch** active until the next actionable event.
13
+ This skill is the full playbook the injected `⟦gh available⟧` block summarizes: the **read-vs-write boundary**, concrete **read recipes**, and how to keep a **CI/PR watch** active until the next actionable event.
14
14
 
15
15
  ## The one hard rule — READ freely, WRITE only when asked
16
16
 
@@ -23,20 +23,6 @@ This skill is the full playbook the injected `⟦gh available⟧` block summariz
23
23
 
24
24
  There is no server-side enforcement of this; the boundary is yours to hold.
25
25
 
26
- ## toon — pipe LARGE, FLAT gh JSON through the shim
27
-
28
- `toon` (Token-Oriented Object Notation) losslessly re-encodes JSON ~30–40% smaller for LLM context. Use it only when a `gh … --json` result you're reading into YOUR context is **large and flat** (a list page: `gh issue list`, `gh pr list`, `gh search`, `gh api` list endpoints) and `command -v toon` succeeds. It is optional: do not install it or assume a home-directory-specific location.
29
-
30
- ```bash
31
- if command -v toon >/dev/null 2>&1; then
32
- gh issue list -R OWNER/REPO --json number,title,url,updatedAt --limit 50 | toon
33
- else
34
- gh issue list -R OWNER/REPO --json number,title,url,updatedAt --limit 50
35
- fi
36
- ```
37
-
38
- **Skip toon** for tiny payloads (a handful of fields, one item) and for **deeply-nested** JSON (`reactionGroups`, review threads, nested files) — nesting defeats tabularization, so the savings collapse to noise and you add a parse tax for yourself. A single `gh pr view N --json …` is small — read it raw.
39
-
40
26
  ## Read recipes
41
27
 
42
28
  Always scope with `-R OWNER/REPO` so a command is dir-independent, and prefer `--json <fields>` (+ `-q <jq>`) so you pull exactly what you need.
@@ -52,7 +38,7 @@ gh issue list -R OWNER/REPO --search "sort:reactions-desc is:open" --json number
52
38
  **PRs + diffs**
53
39
  ```bash
54
40
  gh pr view N -R OWNER/REPO --json title,body,state,labels,files,additions,deletions,url
55
- gh pr diff N -R OWNER/REPO # the unified diff — pipe through toon only if HUGE and you just need shape
41
+ gh pr diff N -R OWNER/REPO # the unified diff
56
42
  gh pr checks N -R OWNER/REPO # CI check rollup for the PR
57
43
  gh pr view N -R OWNER/REPO --comments # review threads + conversation
58
44
  ```
@@ -97,7 +83,7 @@ Use search to find duplicates, related work, and prior art before you conclude s
97
83
  **Raw API** for anything the porcelain doesn't cover:
98
84
  ```bash
99
85
  gh api repos/OWNER/REPO/commits/SHA/check-runs --jq '.check_runs[] | {name, conclusion}'
100
- gh api "repos/OWNER/REPO/issues?state=open&labels=bug&per_page=50" | { command -v toon >/dev/null 2>&1 && toon || cat; }
86
+ gh api "repos/OWNER/REPO/issues?state=open&labels=bug&per_page=50" --jq '.[] | {number, title, html_url}'
101
87
  ```
102
88
 
103
89
  ## Keep GitHub automation active