frizz-server 0.15.20 → 0.15.22

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 (101) hide show
  1. package/dist/claude-agent-broker.js +24 -12
  2. package/dist/dev-child.js +111 -45
  3. package/package.json +1 -1
  4. package/runtime/cc-worker/DECISIONS.md +6 -2
  5. package/runtime/cc-worker/bin/frizz-mcp.mjs +4 -2
  6. package/runtime/cc-worker/hooks/perm-policy.mjs +10 -1
  7. package/web-dist/assets/{abnfDiagram-VRR7QNED-B61nQEjt.js → abnfDiagram-VRR7QNED-DqTQZ2Ex.js} +1 -1
  8. package/web-dist/assets/{arc-CR8_qWI3.js → arc-t56zg8G8.js} +1 -1
  9. package/web-dist/assets/architecture-TIHT7OUA-Czzg2iAA.js +1 -0
  10. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-6sc4SDOh.js → architectureDiagram-ZJ3FMSHR-CSdC9-eQ.js} +1 -1
  11. package/web-dist/assets/{blockDiagram-677ZJIJ3-p-DPTDGB.js → blockDiagram-677ZJIJ3-Dmw8EopV.js} +1 -1
  12. package/web-dist/assets/{c4Diagram-LMCZKHZV-cqvAqZE2.js → c4Diagram-LMCZKHZV-BCRaFnis.js} +1 -1
  13. package/web-dist/assets/channel-CxehNUC2.js +1 -0
  14. package/web-dist/assets/{chunk-32BRIVSS-BNnoKILR.js → chunk-32BRIVSS-DqOJDRw_.js} +1 -1
  15. package/web-dist/assets/{chunk-52WLFC77-BmHIWiVu.js → chunk-52WLFC77-BsibludT.js} +1 -1
  16. package/web-dist/assets/{chunk-C7G6YPKG-D7XYFno8.js → chunk-C7G6YPKG-D7Me5JQU.js} +1 -1
  17. package/web-dist/assets/{chunk-EX3LRPZG-BUViIha7.js → chunk-EX3LRPZG-CTa4YlOO.js} +1 -1
  18. package/web-dist/assets/{chunk-FWX5IMBZ-3x5Zky3W.js → chunk-FWX5IMBZ-CjqOt5Jn.js} +2 -2
  19. package/web-dist/assets/{chunk-HOUHSVGY-C6_RBSov.js → chunk-HOUHSVGY-BDIBDR-8.js} +1 -1
  20. package/web-dist/assets/{chunk-ICXQ74PX-NbVxQ4i1.js → chunk-ICXQ74PX-C4exiYRm.js} +1 -1
  21. package/web-dist/assets/{chunk-MOJQB5TN-nRMfcSHe.js → chunk-MOJQB5TN-g8M_GVaO.js} +1 -1
  22. package/web-dist/assets/{chunk-OGEWGWER-BbSXfbEB.js → chunk-OGEWGWER-C_80lLFe.js} +1 -1
  23. package/web-dist/assets/{chunk-PUDLZKDR-Byubn9VY.js → chunk-PUDLZKDR-B73Odmxv.js} +1 -1
  24. package/web-dist/assets/{chunk-Q4XR5HBZ-DLq-aZ3t.js → chunk-Q4XR5HBZ-D-VkroM1.js} +1 -1
  25. package/web-dist/assets/{chunk-RYQCIY6F-Dbt66M6V.js → chunk-RYQCIY6F-DmPJ8s-8.js} +1 -1
  26. package/web-dist/assets/{chunk-V7JOEXUC-DNFTmXz6.js → chunk-V7JOEXUC-BQKrRLYX.js} +1 -1
  27. package/web-dist/assets/{chunk-VAUOI2AC-QWrszv3X.js → chunk-VAUOI2AC-DRKYvsWa.js} +1 -1
  28. package/web-dist/assets/{chunk-VR4S4FIN-CJdyjwam.js → chunk-VR4S4FIN-DaMMs6V-.js} +1 -1
  29. package/web-dist/assets/{chunk-WYO6CB5R-D_786t-3.js → chunk-WYO6CB5R-CQAWjeZH.js} +1 -1
  30. package/web-dist/assets/{chunk-XXDRQBXY-Bd_2jBj_.js → chunk-XXDRQBXY-DJvt-abB.js} +1 -1
  31. package/web-dist/assets/{chunk-ZGVPDNZ5-DklPG-LG.js → chunk-ZGVPDNZ5-B1v24MrX.js} +1 -1
  32. package/web-dist/assets/classDiagram-OUVF2IWQ-BwIDe0nq.js +1 -0
  33. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BwIDe0nq.js +1 -0
  34. package/web-dist/assets/{cose-bilkent-JH36ORCC-C4MtSvEs.js → cose-bilkent-JH36ORCC-CeNttASr.js} +1 -1
  35. package/web-dist/assets/{cynefin-VYW2F7L2-Dr0CmMQz.js → cynefin-VYW2F7L2-ClJzZ-Yx.js} +1 -1
  36. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BpHXKyDO.js → cynefinDiagram-TSTJHNR4-BCrL5Bu_.js} +1 -1
  37. package/web-dist/assets/{dagre-VKFMJZFB-CWAtKn43.js → dagre-VKFMJZFB-DO7qy19A.js} +1 -1
  38. package/web-dist/assets/{diagram-FQU43EPY-B_6PhpPG.js → diagram-FQU43EPY-KPbJc2w8.js} +1 -1
  39. package/web-dist/assets/{diagram-G47NLZAW-DPNrX-3a.js → diagram-G47NLZAW-9tqHFXzf.js} +1 -1
  40. package/web-dist/assets/{diagram-NH7WQ7WH-BL4nqAUm.js → diagram-NH7WQ7WH-BUbmU0cY.js} +1 -1
  41. package/web-dist/assets/{diagram-OA4YK3LP-CcFbtuCf.js → diagram-OA4YK3LP-QmMo96Qi.js} +1 -1
  42. package/web-dist/assets/{diagram-WEI45ONY-Bkl0nXre.js → diagram-WEI45ONY-BYAyhW16.js} +1 -1
  43. package/web-dist/assets/{dist-krVNNn4H.js → dist-Cewx01EF.js} +1 -1
  44. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-C_51YQqw.js → ebnfDiagram-CCIWWBDH-BIMiEDl1.js} +1 -1
  45. package/web-dist/assets/{erDiagram-Q63AITRT-D8oBhiQB.js → erDiagram-Q63AITRT-aLYQSs2d.js} +1 -1
  46. package/web-dist/assets/eventmodeling-45OFAUF4-jYGf30I4.js +1 -0
  47. package/web-dist/assets/flowDiagram-23GEKE2U-uJJwGAew.js +1 -0
  48. package/web-dist/assets/{ganttDiagram-NO4QXBWP-BOJJvkcQ.js → ganttDiagram-NO4QXBWP-Bu6_kuAX.js} +1 -1
  49. package/web-dist/assets/{gitGraph-TEB2WS4Q-DrZGs6X0.js → gitGraph-TEB2WS4Q-yz2DuHDq.js} +1 -1
  50. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-C0pqDVhD.js → gitGraphDiagram-IHSO6WYX-BXfbKaor.js} +1 -1
  51. package/web-dist/assets/{index-BwC7xvWu.js → index-DS1RBTF4.js} +37 -37
  52. package/web-dist/assets/index-DX1b-Y8G.css +2 -0
  53. package/web-dist/assets/{info-DKCQHKI2-0OcIuVxc.js → info-DKCQHKI2-BdItukhP.js} +1 -1
  54. package/web-dist/assets/{infoDiagram-FWYZ7A6U-CRAc-N7_.js → infoDiagram-FWYZ7A6U-DXrq8wS3.js} +1 -1
  55. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-D_bHCCxh.js → ishikawaDiagram-FXEZZL3T-iM2fPeJv.js} +1 -1
  56. package/web-dist/assets/{journeyDiagram-5HDEW3XC-Dbvh7D1e.js → journeyDiagram-5HDEW3XC-DNa58xSH.js} +1 -1
  57. package/web-dist/assets/{kanban-definition-HUTT4EX6-CxOWhFpB.js → kanban-definition-HUTT4EX6-DhSaBCRv.js} +1 -1
  58. package/web-dist/assets/{line-BGqbhtvK.js → line-CQn0ApgD.js} +1 -1
  59. package/web-dist/assets/{linear-DRvKJ3WJ.js → linear-DMSasryg.js} +1 -1
  60. package/web-dist/assets/{mermaid-parser.core-C5lIBam8.js → mermaid-parser.core-BiJ3pVQT.js} +3 -3
  61. package/web-dist/assets/{mermaid.core-ChLUqNEE.js → mermaid.core-8ofZrJs5.js} +3 -3
  62. package/web-dist/assets/{mindmap-definition-LN4V7U3C-BfPv_GOf.js → mindmap-definition-LN4V7U3C-BLr4IGuI.js} +1 -1
  63. package/web-dist/assets/{packet-7NZHBO7P-CH3B2i7_.js → packet-7NZHBO7P-D15LrSHU.js} +1 -1
  64. package/web-dist/assets/{pegDiagram-2B236MQR-DLuCHUaK.js → pegDiagram-2B236MQR-C9IcvZ_z.js} +1 -1
  65. package/web-dist/assets/{pie-RZYD4A2V-C-hDlp0h.js → pie-RZYD4A2V-79-1CSta.js} +1 -1
  66. package/web-dist/assets/{pieDiagram-ENE6RG2P-CKVu3PTC.js → pieDiagram-ENE6RG2P-dm_ORKO7.js} +1 -1
  67. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL--tZ3JdyA.js → quadrantDiagram-ABIIQ3AL-vQkbT0Ej.js} +1 -1
  68. package/web-dist/assets/{radar-I7S5WNFK-C10EMwWx.js → radar-I7S5WNFK-B8xvrM0r.js} +1 -1
  69. package/web-dist/assets/{railroad-3IZDKUUU-BYegvtJS.js → railroad-3IZDKUUU-G0rN-YuN.js} +1 -1
  70. package/web-dist/assets/railroad-abnf-AHOZXSZD-BaCc6741.js +1 -0
  71. package/web-dist/assets/railroad-ebnf-EBAXGLYW-cUMY4WQi.js +1 -0
  72. package/web-dist/assets/railroad-peg-LSFZ7HO6-JS2RjlNP.js +1 -0
  73. package/web-dist/assets/{railroadDiagram-RFXS5EU6-CfpRdkA8.js → railroadDiagram-RFXS5EU6-BtGt9H2_.js} +1 -1
  74. package/web-dist/assets/{requirementDiagram-TGXJPOKE-CUajgDbc.js → requirementDiagram-TGXJPOKE-oHUUvJjC.js} +1 -1
  75. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DRs5oKTx.js → sankeyDiagram-HTMAVEWB-BoO0DzUn.js} +1 -1
  76. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-BTeEqLFC.js → sequenceDiagram-DBY2YBRQ-B344KPVn.js} +1 -1
  77. package/web-dist/assets/{src-Cf1DXZ3M.js → src-BygbIYKj.js} +1 -1
  78. package/web-dist/assets/{stateDiagram-2N3HPSRC-BPcqMj-B.js → stateDiagram-2N3HPSRC-DMTN-MuE.js} +1 -1
  79. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-4HUOVSEd.js +1 -0
  80. package/web-dist/assets/{swimlanes-5IMT3BWC-CJTd58TJ.js → swimlanes-5IMT3BWC-DQEhwI0k.js} +1 -1
  81. package/web-dist/assets/swimlanesDiagram-G3AALYLV-PPFuhngt.js +8 -0
  82. package/web-dist/assets/{timeline-definition-FHXFAJF6-CqbaBVpe.js → timeline-definition-FHXFAJF6-CUoCgDT7.js} +1 -1
  83. package/web-dist/assets/{treeView-QDETBFTQ-B2OQ8r59.js → treeView-QDETBFTQ-BTHFGWtv.js} +1 -1
  84. package/web-dist/assets/{treemap-6X3UGDF4-DGpAdWTA.js → treemap-6X3UGDF4-BI2j1PYm.js} +1 -1
  85. package/web-dist/assets/{vennDiagram-L72KCM5P-BAS_eWHm.js → vennDiagram-L72KCM5P-Cc3sJIb2.js} +1 -1
  86. package/web-dist/assets/{wardley-OPB4EBWU-DpNHIH3W.js → wardley-OPB4EBWU-kYXYWMgL.js} +1 -1
  87. package/web-dist/assets/{wardleyDiagram-EHGQE667-2N7X1vj8.js → wardleyDiagram-EHGQE667-KHMHPO99.js} +1 -1
  88. package/web-dist/assets/{xychartDiagram-FW5EYKEG-C8mfLCei.js → xychartDiagram-FW5EYKEG-DzSZk-Dq.js} +1 -1
  89. package/web-dist/index.html +2 -2
  90. package/web-dist/assets/architecture-TIHT7OUA-CXyjbDft.js +0 -1
  91. package/web-dist/assets/channel-uFMfT294.js +0 -1
  92. package/web-dist/assets/classDiagram-OUVF2IWQ-D1koMbqS.js +0 -1
  93. package/web-dist/assets/classDiagram-v2-EOCWNBFH-D1koMbqS.js +0 -1
  94. package/web-dist/assets/eventmodeling-45OFAUF4-tWFB9ALb.js +0 -1
  95. package/web-dist/assets/flowDiagram-23GEKE2U-Dz_Opd7B.js +0 -1
  96. package/web-dist/assets/index-CuXKWysP.css +0 -2
  97. package/web-dist/assets/railroad-abnf-AHOZXSZD-DLmknJDm.js +0 -1
  98. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BoZF0VpA.js +0 -1
  99. package/web-dist/assets/railroad-peg-LSFZ7HO6-Dib7YVqg.js +0 -1
  100. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DiV9dOER.js +0 -1
  101. package/web-dist/assets/swimlanesDiagram-G3AALYLV-BDeobZmn.js +0 -8
@@ -26081,11 +26081,6 @@ var LEGACY_DIR_NAME = ".frizz";
26081
26081
  function legacyFrizzRoot(home = homedir()) {
26082
26082
  return join2(home, LEGACY_DIR_NAME);
26083
26083
  }
26084
- function isLegacyStateRoot(root, exists) {
26085
- if (!exists(root)) return false;
26086
- if (!exists(join2(root, ".id")) && !exists(join2(root, ".gitignore"))) return true;
26087
- return exists(join2(root, "projects")) || exists(join2(root, "registry.json"));
26088
- }
26089
26084
  function xdg(env, name) {
26090
26085
  const value = env[name];
26091
26086
  return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
@@ -26128,7 +26123,7 @@ function frizzPaths(options = {}) {
26128
26123
  const home = options.home ?? homedir();
26129
26124
  const exists = options.exists ?? existsSync2;
26130
26125
  const legacyRoot = legacyFrizzRoot(home);
26131
- if (isLegacyStateRoot(legacyRoot, exists)) {
26126
+ if (exists(legacyRoot)) {
26132
26127
  return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
26133
26128
  }
26134
26129
  const explicit = {
@@ -28401,7 +28396,6 @@ function startLazyMcpHost(options) {
28401
28396
  // packages/server/src/backend/types.ts
28402
28397
  var CLAUDE_WORKER_ENV = {
28403
28398
  CLAUDE_CODE_TOTAL_TOKENS_REMINDER: "infinite",
28404
- BASH_DEFAULT_TIMEOUT_MS: "60000",
28405
28399
  BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
28406
28400
  };
28407
28401
  var CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
@@ -28469,6 +28463,7 @@ function createClaudeBrokerExitWriter(path, meta, now = () => /* @__PURE__ */ ne
28469
28463
  // packages/server/src/backend/claude-agent-broker.ts
28470
28464
  var BROKER_CAPABILITIES = [CLAUDE_BROKER_CAPABILITY_SUBAGENT_STEER, CLAUDE_BROKER_CAPABILITY_CANCEL_INPUT, CLAUDE_BROKER_CAPABILITY_STOP_TASK, CLAUDE_BROKER_CAPABILITY_RELOAD_PLUGINS, CLAUDE_BROKER_CAPABILITY_RENAME, CLAUDE_BROKER_CAPABILITY_LIST_SKILLS, CLAUDE_BROKER_CAPABILITY_INPUT_ACK];
28471
28465
  var IDLE_EXIT_MS = 6 * 60 * 60 * 1e3;
28466
+ var PERMISSION_SHOW_HOLD_MS = 250;
28472
28467
  var REACHABILITY_CHECK_MS = 3e4;
28473
28468
  var REACHABILITY_STRIKES = 2;
28474
28469
  var EVENT_ERROR_TOLERANCE = 5;
@@ -28551,16 +28546,30 @@ function runClaudeBroker(config, options = {}) {
28551
28546
  const requestId = `perm-${++permSeq}`;
28552
28547
  return await new Promise((resolve3) => {
28553
28548
  let settled = false;
28549
+ let showTimer;
28550
+ const entry = { request, shown: false, resolve: (decision) => settle(decision) };
28554
28551
  const settle = (decision) => {
28555
28552
  if (settled) return;
28556
28553
  settled = true;
28554
+ clearTimeout(showTimer);
28557
28555
  pendingPermissions.delete(requestId);
28558
28556
  resolve3(decision);
28559
28557
  };
28560
- pendingPermissions.set(requestId, { request, resolve: settle });
28561
- if (context.signal.aborted) settle({ behavior: "deny", message: "The turn was interrupted before this was answered." });
28562
- else context.signal.addEventListener("abort", () => settle({ behavior: "deny", message: "The turn was interrupted before this was answered." }), { once: true });
28563
- if (!settled && client) write(client, { t: "permission-request", requestId, request });
28558
+ const withdraw = () => {
28559
+ const wasShown = entry.shown;
28560
+ settle({ behavior: "deny", message: "The turn was interrupted before this was answered." });
28561
+ if (wasShown && client) write(client, { t: "permission-withdrawn", requestId });
28562
+ };
28563
+ pendingPermissions.set(requestId, entry);
28564
+ if (context.signal.aborted) withdraw();
28565
+ else context.signal.addEventListener("abort", withdraw, { once: true });
28566
+ if (!settled) {
28567
+ showTimer = setTimeout(() => {
28568
+ if (settled || entry.shown || !client) return;
28569
+ entry.shown = true;
28570
+ write(client, { t: "permission-request", requestId, request });
28571
+ }, PERMISSION_SHOW_HOLD_MS);
28572
+ }
28564
28573
  });
28565
28574
  },
28566
28575
  onDiagnostic: (diagnostic) => {
@@ -28612,7 +28621,10 @@ function runClaudeBroker(config, options = {}) {
28612
28621
  client = sock;
28613
28622
  clearTimeout(idleTimer);
28614
28623
  write(sock, { t: "hello", sessionId: handle.sessionId, generation });
28615
- for (const [requestId, { request }] of pendingPermissions) write(sock, { t: "permission-request", requestId, request });
28624
+ for (const [requestId, entry] of pendingPermissions) {
28625
+ entry.shown = true;
28626
+ write(sock, { t: "permission-request", requestId, request: entry.request });
28627
+ }
28616
28628
  while (eventBacklog.length) sock.write(eventBacklog.shift());
28617
28629
  eventBacklogBytes = 0;
28618
28630
  let buf = "";
package/dist/dev-child.js CHANGED
@@ -46,11 +46,6 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
46
46
  function legacyFrizzRoot(home = homedir()) {
47
47
  return join(home, LEGACY_DIR_NAME);
48
48
  }
49
- function isLegacyStateRoot(root, exists) {
50
- if (!exists(root)) return false;
51
- if (!exists(join(root, ".id")) && !exists(join(root, ".gitignore"))) return true;
52
- return exists(join(root, "projects")) || exists(join(root, "registry.json"));
53
- }
54
49
  function xdg(env, name) {
55
50
  const value = env[name];
56
51
  return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
@@ -93,7 +88,7 @@ function frizzPaths(options = {}) {
93
88
  const home = options.home ?? homedir();
94
89
  const exists = options.exists ?? existsSync;
95
90
  const legacyRoot = legacyFrizzRoot(home);
96
- if (isLegacyStateRoot(legacyRoot, exists)) {
91
+ if (exists(legacyRoot)) {
97
92
  return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
98
93
  }
99
94
  const explicit = {
@@ -119,6 +114,9 @@ function frizzTempDir(name, key) {
119
114
  function projectStateDir(projectId, home) {
120
115
  return join(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
121
116
  }
117
+ function launchHostDir(home) {
118
+ return join(home ? frizzPaths({ home }).data : frizzRoots().data, "host");
119
+ }
122
120
  function isPromptAttachmentPath(path3, home) {
123
121
  const data = home ? frizzPaths({ home }).data : frizzRoots().data;
124
122
  const rel = relative(join(data, "projects"), resolve(path3));
@@ -746,13 +744,6 @@ function isHomeDirectory(dir, home = homedir3()) {
746
744
  return false;
747
745
  }
748
746
  }
749
- function isStateRootHome(dir, home = homedir3()) {
750
- return isHomeDirectory(dir, home) && frizzPaths({ home }).legacy;
751
- }
752
- function refuseStateRootHome(dir, home = homedir3()) {
753
- if (isStateRootHome(dir, home))
754
- throw new Error("Frizz keeps its own files in ~/.frizz on this machine, so the home folder cannot be a project here \u2014 choose a folder inside it.");
755
- }
756
747
  function isWorldWritable(dir, platform3 = process.platform) {
757
748
  if (platform3 === "win32") return false;
758
749
  try {
@@ -786,7 +777,7 @@ function discoverProjectRoot(cwd = process.cwd(), home = homedir3()) {
786
777
  const stop2 = resolve3(home);
787
778
  const filesystemRoot = parse(dir).root;
788
779
  for (let at2 = dir; ; at2 = dirname3(at2)) {
789
- if (at2 === stop2 || at2 === filesystemRoot || isHomeDirectory(at2, home)) break;
780
+ if (at2 === stop2 || at2 === filesystemRoot) break;
790
781
  if (existsSync2(projectIdPath(at2))) return at2;
791
782
  if (hasAny(at2, REPO_MARKERS)) return at2;
792
783
  if (hasAny(at2, PROJECT_MARKERS)) return at2;
@@ -803,7 +794,6 @@ var FRIZZ_DIR, ID_FILE, SELF_IGNORE, REPO_MARKERS, PROJECT_MARKERS;
803
794
  var init_project_root = __esm({
804
795
  "packages/server/src/project-root.ts"() {
805
796
  "use strict";
806
- init_frizz_paths();
807
797
  init_project_identity();
808
798
  FRIZZ_DIR = ".frizz";
809
799
  ID_FILE = ".id";
@@ -13820,7 +13810,7 @@ function askedQuestionFaults(q2) {
13820
13810
  if (node.secret && options.length > 0) faults.push(`${path3}: a secret request takes no options \u2014 it is one masked box`);
13821
13811
  if (node.secret && path3 !== "question") faults.push(`${path3}: a secret request cannot be a follow-up \u2014 register it on its own`);
13822
13812
  if (node.kind === "multi" && options.length === 0) faults.push(`${path3}: \`kind: "multi"\` needs options \u2014 a question with none is free text`);
13823
- if (options.filter((o) => o.recommended).length > 1) faults.push(`${path3}: only ONE option may be \`recommended\` \u2014 a recommendation on two of three choices says nothing`);
13813
+ if (node.kind !== "multi" && options.filter((o) => o.recommended).length > 1) faults.push(`${path3}: only ONE option may be \`recommended\` on a pick-one question \u2014 a recommendation on two of three choices says nothing (a \`multi\` may mark every option worth ticking)`);
13824
13814
  for (const [i, option] of options.entries()) {
13825
13815
  if ((option.followUps ?? []).length > 0 && node.kind === "multi") {
13826
13816
  faults.push(`${path3}: a \`multi\` option cannot carry follow-ups \u2014 several picked options would open several branches at once`);
@@ -14252,7 +14242,7 @@ var init_src = __esm({
14252
14242
  "running",
14253
14243
  // process alive, turn in flight
14254
14244
  "perm-prompt",
14255
- // process alive, paused on an interactive permission prompt (answer in the terminal)
14245
+ // process alive, parked on a permission request a person has not answered yet
14256
14246
  "turn-idle",
14257
14247
  // process alive, waiting at the prompt
14258
14248
  "exited"
@@ -16471,6 +16461,7 @@ function canonicalPath(dir) {
16471
16461
  }
16472
16462
  function registerProject(input, home = homedir4()) {
16473
16463
  input = { ...input, dir: canonicalPath(input.dir) };
16464
+ if (input.dir === canonicalPath(launchHostDir(home))) return { action: "unlisted" };
16474
16465
  const registry = readRegistry(home);
16475
16466
  if (registry.forgotten) {
16476
16467
  registry.forgotten = registry.forgotten.filter((id2) => id2 !== input.id);
@@ -17210,7 +17201,6 @@ function projectFromRegistryEntry(entry, home = homedir5()) {
17210
17201
  function resolveProject(cwd = process.cwd(), home = homedir5(), env = process.env) {
17211
17202
  const root = resolveProjectDir(cwd);
17212
17203
  refuseWorldWritableProject(root);
17213
- refuseStateRootHome(root, home);
17214
17204
  const identity = resolveProjectIdentity(root, home);
17215
17205
  const dir = identity.root;
17216
17206
  const id2 = registerAndReconcile(dir, identity.id, home);
@@ -19302,6 +19292,12 @@ function createInteractionStore(db2, options = {}) {
19302
19292
  SELECT COUNT(*) AS count FROM interaction_journal
19303
19293
  WHERE project_id = ? AND thread_slug = ? AND session_id = ? AND lifecycle = 'pending'
19304
19294
  `);
19295
+ const terminalSince = db2.prepare(`
19296
+ SELECT EXISTS (
19297
+ SELECT 1 FROM interaction_journal
19298
+ WHERE project_id = ? AND thread_slug = ? AND session_id = ? AND lifecycle != 'pending' AND completed_at >= ?
19299
+ ) AS found
19300
+ `);
19305
19301
  const insert = db2.prepare(`
19306
19302
  INSERT INTO interaction_journal (
19307
19303
  id, schema_version, project_id, thread_slug, session_id, session_epoch, capability_revision,
@@ -19921,6 +19917,13 @@ function createInteractionStore(db2, options = {}) {
19921
19917
  cancelSessionDeliveriesStmt.run({ threadSlug, sessionId, at: at2 });
19922
19918
  return before.map((row) => rowToRecord(byId.get(row.id)));
19923
19919
  });
19920
+ const withdrawTxn = db2.transaction((scope, interactionId) => {
19921
+ const row = byScopedId.get(interactionId, scope.projectId, scope.threadSlug, scope.sessionId);
19922
+ if (!row || row.lifecycle !== "pending") return void 0;
19923
+ if (deliveryByInteraction.get(row.id)) throw new InteractionStoreError("invalid-response", "a provider-delivered interaction is withdrawn through invalidateProviderRequest");
19924
+ if (providerCancelStmt.run({ id: row.id, at: iso(now) }).changes !== 1) return void 0;
19925
+ return rowToRecord(byId.get(row.id));
19926
+ });
19924
19927
  const expireTxn = db2.transaction((at2) => {
19925
19928
  const before = dueStmt.all(at2);
19926
19929
  if (before.length === 0) return [];
@@ -19955,6 +19958,10 @@ function createInteractionStore(db2, options = {}) {
19955
19958
  emit2(expired);
19956
19959
  return listPendingStmt.all(scope.projectId, scope.threadSlug, scope.sessionId, INTERACTION_LIST_MAX).map(rowToRecord);
19957
19960
  },
19961
+ settledSince(scope, since) {
19962
+ if ((pendingCount.get(scope.projectId, scope.threadSlug, scope.sessionId)?.count ?? 0) > 0) return false;
19963
+ return terminalSince.get(scope.projectId, scope.threadSlug, scope.sessionId, since)?.found === 1;
19964
+ },
19958
19965
  resolve(scope, input) {
19959
19966
  const expired = expireTxn(iso(now));
19960
19967
  emit2(expired);
@@ -20009,6 +20016,11 @@ function createInteractionStore(db2, options = {}) {
20009
20016
  if (result.effect === "cancelled") emit2([result.interaction]);
20010
20017
  return result;
20011
20018
  },
20019
+ withdraw(scope, interactionId) {
20020
+ const record = withdrawTxn.immediate(scope, interactionId);
20021
+ if (record) emit2([record]);
20022
+ return record;
20023
+ },
20012
20024
  cancelForSession(threadSlug, sessionId, reason) {
20013
20025
  const parsedReason = InteractionCancellationReason.safeParse(reason);
20014
20026
  if (!parsedReason.success || parsedReason.data === "expired") {
@@ -22413,7 +22425,6 @@ var init_types2 = __esm({
22413
22425
  WORKER_MAX_CONCURRENT_SUBAGENTS = 100;
22414
22426
  CLAUDE_WORKER_ENV = {
22415
22427
  CLAUDE_CODE_TOTAL_TOKENS_REMINDER: "infinite",
22416
- BASH_DEFAULT_TIMEOUT_MS: "60000",
22417
22428
  BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
22418
22429
  };
22419
22430
  CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
@@ -26535,16 +26546,21 @@ ${ask}`;
26535
26546
  if (!Number.isFinite(at2)) return false;
26536
26547
  if (priorGeneration(at2)) return false;
26537
26548
  const last = state.lastActivityAt ? Date.parse(state.lastActivityAt) : Number.NEGATIVE_INFINITY;
26538
- return at2 > last;
26549
+ if (at2 <= last) return false;
26550
+ try {
26551
+ if (deps.permissionSettled?.(row.slug, row.session_id, new Date(at2).toISOString())) return false;
26552
+ } catch {
26553
+ }
26554
+ return true;
26539
26555
  }
26540
26556
  function sniffPane(state, row, turn, nowMs, backend) {
26541
26557
  void nowMs;
26542
26558
  void backend;
26543
- if (state.foreign) return { permPrompt: false };
26544
- if (turn === "in-flight" && row.backend !== "codex" && row.backend !== "acp" && permMarkerBlocks(state, row)) {
26545
- return { permPrompt: true };
26546
- }
26547
- return { permPrompt: false };
26559
+ return { permPrompt: permBlocked(state, row, turn) };
26560
+ }
26561
+ function permBlocked(state, row, turn) {
26562
+ if (state.foreign) return false;
26563
+ return turn === "in-flight" && row.backend !== "codex" && row.backend !== "acp" && permMarkerBlocks(state, row);
26548
26564
  }
26549
26565
  function drainUnretiredOps(state, row) {
26550
26566
  if (!state.unretiredOps?.size) return;
@@ -27361,6 +27377,12 @@ ${detail2}
27361
27377
  state.permissionMode = permissionMode;
27362
27378
  }
27363
27379
  },
27380
+ permissionAnswered(slug) {
27381
+ const state = states.get(slug);
27382
+ if (!state?.primed || !state.permPrompt) return;
27383
+ const row = currentRowFor(state);
27384
+ if (row) state.permPrompt = permBlocked(state, row, state.turn);
27385
+ },
27364
27386
  start(onPrimeProgress) {
27365
27387
  if (timer) return;
27366
27388
  stopped = false;
@@ -37889,15 +37911,16 @@ exactly ONE of them.
37889
37911
  - the auth audit found the one issue worth deciding on before the rest land
37890
37912
  \`\`\`
37891
37913
 
37892
- And when the human must DO something you cannot \u2014 here, the release needs their npm session:
37914
+ And when the human must DO something you cannot \u2014 here, you started the npm sign-in yourself, and
37915
+ the one act left needs their account:
37893
37916
 
37894
37917
  \`\`\`awaiting
37895
- title: Sign in to npm so the release can publish
37918
+ title: Approve the npm sign-in so the release can publish
37896
37919
  steps:
37897
- - Run \`npm login --auth-type=web\` in a terminal on this machine.
37898
- - Approve the browser prompt with the maintainer account; if no prompt opens, sign in at [npmjs.com/login](https://www.npmjs.com/login) and run the command again.
37920
+ - Open [the npm sign-in this thread started](https://www.npmjs.com/login?next=/login/cli/9f3c2a71) and sign in with the maintainer account.
37921
+ - Approve the request with that account's security key or one-time code, and wait for the page to say the CLI is signed in.
37899
37922
  ---
37900
- The publish step runs as the maintainer, and no token for that account is available to this thread.
37923
+ \`npm login --auth-type=web\` is running in this thread and waiting on that approval. The publish follows as soon as it lands.
37901
37924
  \`\`\`
37902
37925
 
37903
37926
  - **\`status:\` \u2014 REQUIRED: where does the thread sit while the work runs?** One of three values, one
@@ -37976,10 +37999,25 @@ exactly ONE of them.
37976
37999
  followed COLD by someone who has read nothing else; frizz reads them verbatim. Steps NAME THE HUMAN
37977
38000
  as the wait, so the fence needs no other name and no \`for:\` (add one only if you want to be woken
37978
38001
  to re-check), and it always puts the thread in their queue \u2014 \`status:\` may be left out. Their
37979
- card shows the steps over one **Done** button, and its click comes back to you as their reply,
37980
- \`Done\`, the same as anything they type; anything else they need to tell you \u2014 a step that failed,
37981
- the account they used \u2014 comes as a message of their own. An act is not a decision: never ask
38002
+ card, headed **Awaiting human**, shows the steps over one **Done** button, and its click comes
38003
+ back to you as their reply, \`Done\`, the same as anything they type; anything else they need to
38004
+ tell you \u2014 a step that failed, the account they used \u2014 comes as a message of their own. An act is not a decision: never ask
37982
38005
  WHETHER they will do it, and never call \`done\` while it is still owed.
38006
+ **THE FIRST TEST OF EVERY STEP: COULD YOU DO IT YOURSELF? THEN IT IS NOT A STEP \u2014 DO IT.** This
38007
+ outranks every rule below. Steps are what is LEFT after you have done all you can: a command you
38008
+ can run is yours to run \u2014 an install, a build, a key generated, a \`git config\`, a file written \u2014
38009
+ and so is an act a CLI or an API performs as well as the page does (\`gh ssh-key add\`, not "open
38010
+ GitHub's settings and paste the key"). A step is only what needs the human's BODY or the human's
38011
+ IDENTITY: a fingerprint, a 2FA or OS permission prompt, a sign-in, a button behind a session you
38012
+ do not hold, an act a guardrail refuses you. TRY before you conclude you cannot \u2014 "I may lack
38013
+ access" is not a blocker until a command has been refused. Doubt about whether you MAY, not
38014
+ whether you can, is a decision: \`mcp__frizz__ask\` it, and run the command yourself on a yes.
38015
+ And ORDER the work so they are interrupted for their own acts alone: do everything up to the
38016
+ first one, hand over only that, and do what follows yourself when their reply wakes you \u2014 a
38017
+ command of yours never becomes their step 5 because it comes after their step 4.
38018
+ (Maintainer 2026-10-10, of a six-step card that opened with \`brew install rust\`, a \`cargo build\`
38019
+ and a \`git config\`: *"The number one criterion for the to-do block is that it should never tell
38020
+ the user to do something that the agent can do itself."*)
37983
38021
  **EVERY STEP IS CLICKABLE AND COMPLETE \u2014 A STEP THAT MAKES THE HUMAN GO LOOKING IS A BROKEN STEP.**
37984
38022
  The steps are the most actionable thing you hand over, so write each one to be DONE, not
37985
38023
  researched: every page it names is a real Markdown link to that exact page (the settings screen,
@@ -38169,9 +38207,10 @@ so, which?" without asking it of somebody who said no. Written out, one registra
38169
38207
  Each question must stand alone: the specific question on ONE line, options each with a one-line
38170
38208
  trade-off, and enough context to answer cold. The card numbers the options 1, 2, 3 in the order you
38171
38209
  give them \u2014 the human picks one by pressing its number \u2014 so put the one you would take FIRST and mark
38172
- it \`recommended\`; mark exactly one. Several independent questions are several entries of ONE
38173
- \`ask\` call, never one bundled question. A bare "which approach?" with no options is a broken
38174
- handoff.
38210
+ it \`recommended\`; mark exactly one \u2014 except on a \`multi\`, where the mark goes on EVERY option you
38211
+ would tick, since one mark alone reads as "pick only this". Several independent questions are several
38212
+ entries of ONE \`ask\` call, never one bundled question. A bare "which approach?" with no options is
38213
+ a broken handoff.
38175
38214
 
38176
38215
  NO "I" AND NO "you" ANYWHERE IN A QUESTION \u2014 the question, its options, or any interactive prompt.
38177
38216
  Clicking an option is the HUMAN speaking, so first and second person flip between writer and reader: in
@@ -38436,8 +38475,8 @@ sub-agent alone does not.
38436
38475
  \`mcp__frizz__watch_pr\`; for a GitHub issue, \`mcp__frizz__watch_issue\`; for anything else dispatch a
38437
38476
  SUB-AGENT to own the wait. It runs the watcher
38438
38477
  to completion in its own foreground and returns the verdict, and its return re-invokes you; rest on
38439
- it with \`status: watching\`. A Bash call that names no \`timeout\` moves to the background after a
38440
- minute, so the watcher names one sized to the wait (up to 24h) and loops until its terminal
38478
+ it with \`status: watching\`. A Bash call that names no \`timeout\` moves to the background at the
38479
+ tool's default, so the watcher names one sized to the wait (up to 24h) and loops until its terminal
38441
38480
  condition. A helper must not hand back while its own watcher is still live.
38442
38481
  - **Working alongside a process you launched** (dev server, log tail) \u2192 \`Bash\` with
38443
38482
  \`run_in_background: true\`. Never put shell job control (\`&\`, \`nohup \u2026 &\`, \`disown\`) inside the
@@ -38912,6 +38951,9 @@ function connectClaudeBroker(socketPath, handlers, options = {}) {
38912
38951
  case "permission-request":
38913
38952
  handlers.onPermissionRequest?.(frame.requestId, frame.request);
38914
38953
  break;
38954
+ case "permission-withdrawn":
38955
+ handlers.onPermissionWithdrawn?.(frame.requestId);
38956
+ break;
38915
38957
  case "diagnostic":
38916
38958
  handlers.onDiagnostic?.(frame.diagnostic);
38917
38959
  break;
@@ -39465,6 +39507,17 @@ function createClaudeAgentBrokerBridge(deps) {
39465
39507
  }
39466
39508
  pendingPerms.set(id2, { client, requestId, scope, ...ask ? { ask } : {} });
39467
39509
  };
39510
+ const withdrawPermission = (slug, sessionId, requestId) => {
39511
+ if (!deps.interactions) return;
39512
+ for (const [interactionId, pending] of pendingPerms) {
39513
+ if (pending.requestId !== requestId || pending.scope.threadSlug !== slug || pending.scope.sessionId !== sessionId) continue;
39514
+ pendingPerms.delete(interactionId);
39515
+ try {
39516
+ deps.interactions.withdraw(pending.scope, interactionId);
39517
+ } catch {
39518
+ }
39519
+ }
39520
+ };
39468
39521
  const unsubInteractions = deps.interactions?.subscribe((change) => {
39469
39522
  const pending = pendingPerms.get(change.interactionId);
39470
39523
  if (!pending || change.lifecycle === "pending") return;
@@ -39517,7 +39570,8 @@ function createClaudeAgentBrokerBridge(deps) {
39517
39570
  return;
39518
39571
  }
39519
39572
  void (deps.decidePermission?.(slug, sessionId, request) ?? Promise.resolve({ behavior: "allow" })).then((decision2) => client.answerPermission(requestId, decision2)).catch(() => client.answerPermission(requestId, { behavior: "deny", message: "permission decision failed" }));
39520
- }
39573
+ },
39574
+ onPermissionWithdrawn: (requestId) => withdrawPermission(slug, sessionId, requestId)
39521
39575
  });
39522
39576
  const session = { slug, sessionId, cwd, generation: record.generation, client, acksInput: record.capabilities?.includes(CLAUDE_BROKER_CAPABILITY_INPUT_ACK) === true };
39523
39577
  sessions.set(slug, session);
@@ -39528,7 +39582,7 @@ function createClaudeAgentBrokerBridge(deps) {
39528
39582
  const we2 = deps.workerEnv;
39529
39583
  const workerEnv = {
39530
39584
  FRIZZ_THREAD: slug,
39531
- // Every Claude worker's environment — the token budget, the bash timeouts, and the lifted
39585
+ // Every Claude worker's environment — the token budget, the bash timeout ceiling, and the lifted
39532
39586
  // web-search / sub-agent caps — as ONE record, so a cap added there cannot miss this path. It
39533
39587
  // used to spread CLAUDE_WORKER_ENV, which carried the first two and not the caps, and since the
39534
39588
  // broker is the only transport that meant no worker ever received a lift (fixed 2026-08-19; see
@@ -39624,7 +39678,7 @@ function createClaudeAgentBrokerBridge(deps) {
39624
39678
  input.permissionMode ?? "default",
39625
39679
  { resume: true, appendSystemPrompt: input.appendSystemPrompt, model: input.model, effort: input.effort }
39626
39680
  );
39627
- retirePendingFor(input.threadSlug, input.sessionId, "user-cancelled");
39681
+ if (!input.keepOpenCards) retirePendingFor(input.threadSlug, input.sessionId, "user-cancelled");
39628
39682
  await deliver(session, message);
39629
39683
  },
39630
39684
  interruptTurn(input) {
@@ -40646,7 +40700,7 @@ function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
40646
40700
  function deriveRuntime(slug, row, storage, turn, permPrompt, appServerStalled = false, headlessLostWork = false) {
40647
40701
  if (!row) return "none";
40648
40702
  if (isHeadlessRow(row)) {
40649
- if (permPrompt) return "perm-prompt";
40703
+ if (permPrompt && !appServerStalled) return "perm-prompt";
40650
40704
  if (headlessLostWork) return "exited";
40651
40705
  if (turn === "idle") return "turn-idle";
40652
40706
  return appServerStalled ? "exited" : "running";
@@ -73190,7 +73244,11 @@ function deliverClaudeBrokerWake(deps) {
73190
73244
  appendSystemPrompt,
73191
73245
  model: row.model ?? void 0,
73192
73246
  effort: row.effort ?? void 0,
73193
- freshProcess
73247
+ freshProcess,
73248
+ // A wake is not the operator answering: an approval or question card open on this thread stays open,
73249
+ // and the wake is read once they have answered it. Without this a timer coming due, a schedule beat
73250
+ // or a report held past the mid-turn ceiling DENIED the open approval on the operator's behalf.
73251
+ keepOpenCards: true
73194
73252
  });
73195
73253
  }
73196
73254
  function projectContextCleanups(get) {
@@ -73260,6 +73318,7 @@ function createContextUnchecked(opts, resources) {
73260
73318
  const transcriptChange = new Emitter();
73261
73319
  const bootId = randomUUID16();
73262
73320
  let board;
73321
+ let permissionAnswered;
73263
73322
  const contextUnsubscribers = [];
73264
73323
  let subscriptionsStopped = false;
73265
73324
  const stopSubscriptions = () => {
@@ -73293,6 +73352,7 @@ function createContextUnchecked(opts, resources) {
73293
73352
  lifecycle: change.lifecycle,
73294
73353
  recordRevision: change.recordRevision
73295
73354
  });
73355
+ if (change.lifecycle !== "pending") permissionAnswered?.(change.threadSlug);
73296
73356
  board?.interactionChanged?.(change);
73297
73357
  }));
73298
73358
  storage.interactions.expireDue();
@@ -73496,6 +73556,9 @@ function createContextUnchecked(opts, resources) {
73496
73556
  codexModels: () => readCodexModels(void 0, opts.codexVersion),
73497
73557
  onChange: () => board.refresh(),
73498
73558
  onTranscriptChange: (slugs) => transcriptChange.emit(slugs),
73559
+ // The journal's word on whether a deferred permission request has been answered — the transcript
73560
+ // only says so once the approved tool call returns.
73561
+ permissionSettled: (slug, sessionId, markerAt) => storage.interactions.settledSince({ projectId: project.id, threadSlug: slug, sessionId }, markerAt),
73499
73562
  // The SDK's own reading of a headless broker session: its turn (so the fold's 5s unknown-stop_reason
73500
73563
  // guess need not run out before a finished turn reaches the queue) and its event count (so a tick
73501
73564
  // can tell the provider has reported activity its own disk write has not caught up with yet).
@@ -73515,6 +73578,7 @@ function createContextUnchecked(opts, resources) {
73515
73578
  });
73516
73579
  resources.tailer = tailer;
73517
73580
  opts.startup?.afterPhase?.("tailer");
73581
+ permissionAnswered = (slug) => tailer.permissionAnswered?.(slug);
73518
73582
  board = createBoard(project, storage, bus, tailer, bootId, {
73519
73583
  codexTurnLiveness: (slug, sessionId) => codexAppServer?.turnLiveness(slug, sessionId),
73520
73584
  // Headless-stall signal for a broker row: the ownerless daemon's record. Absent bridge ⇒ default
@@ -80529,7 +80593,7 @@ function addProjectAtPath(input, home = homedir18()) {
80529
80593
  }
80530
80594
  if (!stats.isDirectory()) throw new Error(`That is a file, not a folder: ${absolute}`);
80531
80595
  const root = chosenProjectRoot(absolute, home);
80532
- if (isHomeDirectory(root, home)) throw new Error("The home folder cannot be added from here \u2014 choose a folder inside it.");
80596
+ if (isHomeDirectory(root, home)) throw new Error("The home folder cannot be a project \u2014 choose a folder inside it.");
80533
80597
  refuseWorldWritableProject(root);
80534
80598
  const id2 = ensureProjectIdFile(root, home, existingProjectId(root));
80535
80599
  const remoteOwner = resolveProjectLabel(root)?.split("/")[0];
@@ -81036,7 +81100,9 @@ ${gapNote}` : input.message;
81036
81100
  // Claude's `default` — see coldResumePermission.
81037
81101
  permissionMode: coldResumePermission(row, ctx.getSettings()),
81038
81102
  model: row.model ?? void 0,
81039
- effort: row.effort ?? void 0
81103
+ effort: row.effort ?? void 0,
81104
+ // Frizz's own notice, not the operator's reply to anything: an open card stays open.
81105
+ keepOpenCards: true
81040
81106
  });
81041
81107
  return null;
81042
81108
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz-server",
3
- "version": "0.15.20",
3
+ "version": "0.15.22",
4
4
  "private": false,
5
5
  "description": "Replaceable Frizz server runtime",
6
6
  "license": "MIT",
@@ -271,8 +271,12 @@ being indistinguishable from bypass.
271
271
  THE TABLE ships UNIVERSAL rules only; this plugin loads for every project frizz drives, so a rule that
272
272
  is right for one repo is wrong for the next. `catastrophic-delete` and `raw-disk-write` deny outright
273
273
  (strictly safer than bypass, which would have allowed both). `restrictive-mode` DEFERS whenever
274
- `permission_mode !== "auto"`, which is what makes a genuine lower-permission mode usable: move a thread
275
- to `default` with the live permission control and its prompts come back. `FRIZZ_PERM_POLICY=review`
274
+ `permission_mode` is anything but `auto` or `bypassPermissions`, which is what makes a genuine lower-permission mode usable: move a thread
275
+ to `default` with the live permission control and its prompts come back. (Until 2026-10-10 the test
276
+ was `!== "auto"`, which swept in `bypassPermissions` — the one mode MORE permissive than auto — so a
277
+ bypass thread was handed every prompt Claude Code still raises under bypass, as approval cards an auto
278
+ thread never showed. Bypass now falls through to `worker-autonomy`; any other or unknown mode still
279
+ defers.) `FRIZZ_PERM_POLICY=review`
276
280
  defers everything. Fail-safe INVERTS the old observer's fail-open — for a hook that can APPROVE, any
277
281
  error must fall back to asking, never to allowing.
278
282
 
@@ -505,8 +505,10 @@ function questionSchema(depth) {
505
505
  recommended: {
506
506
  type: "boolean",
507
507
  description:
508
- "Mark the ONE option you would take, and put it first. At most one per question — a " +
509
- "recommendation on two of three choices says nothing. IF YOU CAN MARK ONE, ASK YOURSELF WHY " +
508
+ "Mark the ONE option you would take, and put it first. At most one per pick-one question — a " +
509
+ "recommendation on two of three choices says nothing. ON A `multi`, MARK EVERY OPTION YOU " +
510
+ "WOULD TICK, or none: the human ticks several there, so one mark alone reads as \"pick only " +
511
+ "this\". IF YOU CAN MARK ONE, ASK YOURSELF WHY " +
510
512
  "YOU ARE ASKING: you already know the answer, so implement it and say which way you went. " +
511
513
  "This is for the fork you genuinely cannot take yourself.",
512
514
  },
@@ -94,8 +94,17 @@ const RULES = [
94
94
  // `default`/`plan` got there because a human moved it there (the live per-thread permission
95
95
  // control), and auto-approving would silently overrule that intent. This is what makes a genuine
96
96
  // lower-permission mode usable today: switch a thread to `default` and its prompts come back.
97
+ //
98
+ // `bypassPermissions` IS NOT ONE OF THEM. It is the one mode MORE permissive than `auto`, and the
99
+ // operator chose it (Settings, or the same per-thread control) precisely to stop being asked — but
100
+ // Claude Code still raises a prompt for some commands under bypass, and this rule read "not auto"
101
+ // as "restrictive", so a bypass thread parked on approval cards an auto thread never showed
102
+ // (maintainer 2026-10-10: all four markers on the machine that day were bypass-mode defers). It
103
+ // falls through to `worker-autonomy` now, behind the two refusals above. Any OTHER mode string,
104
+ // including one this table has never heard of, still defers: unknown must mean ask.
97
105
  id: 'restrictive-mode',
98
- test: (i) => typeof i.permission_mode === 'string' && i.permission_mode !== 'auto',
106
+ test: (i) =>
107
+ typeof i.permission_mode === 'string' && i.permission_mode !== 'auto' && i.permission_mode !== 'bypassPermissions',
99
108
  decision: 'defer',
100
109
  reason: 'The thread is in a restrictive permission mode, so this request is left for a human to answer.',
101
110
  },
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-C5lIBam8.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-Cf1DXZ3M.js";import"./chunk-WYO6CB5R-D_786t-3.js";import"./chunk-VAUOI2AC-QWrszv3X.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-nRMfcSHe.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
1
+ import{m as e,t}from"./mermaid-parser.core-BiJ3pVQT.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BygbIYKj.js";import"./chunk-WYO6CB5R-CQAWjeZH.js";import"./chunk-VAUOI2AC-DRKYvsWa.js";import{n as i,r as a,t as o}from"./chunk-MOJQB5TN-g8M_GVaO.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
@@ -1 +1 @@
1
- import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-krVNNn4H.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-Cewx01EF.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -0,0 +1 @@
1
+ import"./chunk-KEIR6QF5-BfrZ3jm6.js";import{M as e}from"./mermaid-parser.core-BiJ3pVQT.js";export{e as createArchitectureServices};