frizz 0.7.8 → 0.8.1

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 (127) hide show
  1. package/README.md +5 -0
  2. package/dist/claude-agent-broker.js +65 -5
  3. package/dist/dev-child.js +457 -553
  4. package/dist/frizz.js +96 -9
  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 +10 -9
  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-BM_laA_I.js} +1 -1
  19. package/web-dist/assets/{abnfDiagram-VRR7QNED-0brfnieb.js → abnfDiagram-VRR7QNED-C5FCLi1n.js} +1 -1
  20. package/web-dist/assets/architecture-TIHT7OUA-ngY1F0r2.js +1 -0
  21. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-Dji-XBdF.js → architectureDiagram-ZJ3FMSHR-Dm7tUnwt.js} +1 -1
  22. package/web-dist/assets/{blockDiagram-677ZJIJ3-BQMyz3Q9.js → blockDiagram-677ZJIJ3-3EhpmwKM.js} +1 -1
  23. package/web-dist/assets/{c4Diagram-LMCZKHZV-BMNws0Oi.js → c4Diagram-LMCZKHZV-Ca-BjDxw.js} +1 -1
  24. package/web-dist/assets/channel-BpyNaJoi.js +1 -0
  25. package/web-dist/assets/{chunk-32BRIVSS-CVv1ae7n.js → chunk-32BRIVSS-xGbaUlTN.js} +1 -1
  26. package/web-dist/assets/{chunk-52WLFC77-BYhPlKCT.js → chunk-52WLFC77-YYhE3F1a.js} +1 -1
  27. package/web-dist/assets/{chunk-C7G6YPKG-Cn4HhNwb.js → chunk-C7G6YPKG-SYKXFtUg.js} +1 -1
  28. package/web-dist/assets/{chunk-EX3LRPZG-P3ZYFC1W.js → chunk-EX3LRPZG-cqjtdM_c.js} +1 -1
  29. package/web-dist/assets/{chunk-FWX5IMBZ-Ba3NpEQa.js → chunk-FWX5IMBZ-qyaTngKT.js} +2 -2
  30. package/web-dist/assets/{chunk-HOUHSVGY-BCWyMhwJ.js → chunk-HOUHSVGY-DBFt1HnE.js} +1 -1
  31. package/web-dist/assets/{chunk-ICXQ74PX-DWAAqHL7.js → chunk-ICXQ74PX-Bme_2_gm.js} +1 -1
  32. package/web-dist/assets/{chunk-MOJQB5TN-pc5VJ4Nq.js → chunk-MOJQB5TN-DupTmWhN.js} +1 -1
  33. package/web-dist/assets/{chunk-OGEWGWER-DUxmbiuX.js → chunk-OGEWGWER-DSqmbKCq.js} +1 -1
  34. package/web-dist/assets/{chunk-PUDLZKDR-DRdZWzxX.js → chunk-PUDLZKDR-DVnHUoFN.js} +1 -1
  35. package/web-dist/assets/{chunk-Q4XR5HBZ-Bafzu7s-.js → chunk-Q4XR5HBZ-BIZh_Yuh.js} +1 -1
  36. package/web-dist/assets/{chunk-V7JOEXUC-Bj0A8ftW.js → chunk-V7JOEXUC-e2G7UgOg.js} +1 -1
  37. package/web-dist/assets/{chunk-VAUOI2AC-DBgVQKIO.js → chunk-VAUOI2AC-uYgyYWPh.js} +1 -1
  38. package/web-dist/assets/{chunk-VR4S4FIN-DU-1KtuM.js → chunk-VR4S4FIN-BjGjcexQ.js} +1 -1
  39. package/web-dist/assets/{chunk-WYO6CB5R-DzVb3-Rj.js → chunk-WYO6CB5R-BKKd_W3B.js} +1 -1
  40. package/web-dist/assets/{chunk-ZGVPDNZ5-BPKQvduj.js → chunk-ZGVPDNZ5-D5_hsjnF.js} +1 -1
  41. package/web-dist/assets/classDiagram-OUVF2IWQ-DTEWwxt7.js +1 -0
  42. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DTEWwxt7.js +1 -0
  43. package/web-dist/assets/{cynefin-VYW2F7L2-lo8PqVCk.js → cynefin-VYW2F7L2-bkFv7EpV.js} +1 -1
  44. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BtQLaDV0.js → cynefinDiagram-TSTJHNR4-Du2-dyuZ.js} +1 -1
  45. package/web-dist/assets/{dagre-VKFMJZFB-BJ2-f-A0.js → dagre-VKFMJZFB-CU7w00Ul.js} +1 -1
  46. package/web-dist/assets/{diagram-FQU43EPY-CLM-8r74.js → diagram-FQU43EPY-BxvVgxWb.js} +1 -1
  47. package/web-dist/assets/{diagram-G47NLZAW-CfuoJGpN.js → diagram-G47NLZAW-D8VvetwQ.js} +1 -1
  48. package/web-dist/assets/{diagram-NH7WQ7WH-DfxxRwH8.js → diagram-NH7WQ7WH-CwsChkED.js} +1 -1
  49. package/web-dist/assets/{diagram-OA4YK3LP-D7knxZee.js → diagram-OA4YK3LP-BIPYbe6L.js} +1 -1
  50. package/web-dist/assets/{diagram-WEI45ONY-C2WfMXWl.js → diagram-WEI45ONY-BoyH4dKl.js} +1 -1
  51. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-DRSZ24tF.js → ebnfDiagram-CCIWWBDH-9A3WRDfU.js} +1 -1
  52. package/web-dist/assets/{erDiagram-Q63AITRT-DXe0OrbY.js → erDiagram-Q63AITRT-D3Y9WA4H.js} +1 -1
  53. package/web-dist/assets/eventmodeling-45OFAUF4-D7g9kDF0.js +1 -0
  54. package/web-dist/assets/flowDiagram-23GEKE2U-BqKnxVr3.js +1 -0
  55. package/web-dist/assets/{ganttDiagram-NO4QXBWP-wGqOgjvs.js → ganttDiagram-NO4QXBWP-Bk2-LClm.js} +1 -1
  56. package/web-dist/assets/{gitGraph-TEB2WS4Q-BygokG6y.js → gitGraph-TEB2WS4Q-CXvPrsio.js} +1 -1
  57. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-CV1933_M.js → gitGraphDiagram-IHSO6WYX-BC9D72kT.js} +1 -1
  58. package/web-dist/assets/index-CCtNIGi6.js +491 -0
  59. package/web-dist/assets/index-Cq79m8AR.css +1 -0
  60. package/web-dist/assets/{info-DKCQHKI2-DLavbzui.js → info-DKCQHKI2-Beag76kE.js} +1 -1
  61. package/web-dist/assets/{infoDiagram-FWYZ7A6U-Biouxd9G.js → infoDiagram-FWYZ7A6U-DaqYXHYB.js} +1 -1
  62. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-DJxwwS3R.js → ishikawaDiagram-FXEZZL3T-D-d4tmLU.js} +1 -1
  63. package/web-dist/assets/{journeyDiagram-5HDEW3XC-Clx_WkdW.js → journeyDiagram-5HDEW3XC-kkFYzNg1.js} +1 -1
  64. package/web-dist/assets/{kanban-definition-HUTT4EX6-Bf7bX_96.js → kanban-definition-HUTT4EX6-BYRPjbfv.js} +1 -1
  65. package/web-dist/assets/{line-CV2v49IN.js → line-C8l7y6rG.js} +1 -1
  66. package/web-dist/assets/{mermaid-parser.core-BPsFhEi6.js → mermaid-parser.core-BG3fgy77.js} +3 -3
  67. package/web-dist/assets/{mermaid.core-0EWDNL_R.js → mermaid.core-BCDzcbhr.js} +3 -3
  68. package/web-dist/assets/{mindmap-definition-LN4V7U3C-C1y4yy3Z.js → mindmap-definition-LN4V7U3C-CP_bgQm8.js} +1 -1
  69. package/web-dist/assets/{packet-7NZHBO7P-CpcpxruB.js → packet-7NZHBO7P-Dgd69pFS.js} +1 -1
  70. package/web-dist/assets/{pegDiagram-2B236MQR-CVlVP4cH.js → pegDiagram-2B236MQR-BJUb_DOD.js} +1 -1
  71. package/web-dist/assets/{pie-RZYD4A2V-BcVPOhTZ.js → pie-RZYD4A2V-DPbVRAuL.js} +1 -1
  72. package/web-dist/assets/{pieDiagram-ENE6RG2P-DZv4Tfae.js → pieDiagram-ENE6RG2P-DavUeOqM.js} +1 -1
  73. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DOzS_U_a.js → quadrantDiagram-ABIIQ3AL-BdvoSedu.js} +1 -1
  74. package/web-dist/assets/{radar-I7S5WNFK-8YADxkdT.js → radar-I7S5WNFK-C2TAta9K.js} +1 -1
  75. package/web-dist/assets/{railroad-3IZDKUUU-DzndPrRy.js → railroad-3IZDKUUU-DEMjzYqC.js} +1 -1
  76. package/web-dist/assets/railroad-abnf-AHOZXSZD-_4FByoRI.js +1 -0
  77. package/web-dist/assets/railroad-ebnf-EBAXGLYW-DKAvMIMu.js +1 -0
  78. package/web-dist/assets/railroad-peg-LSFZ7HO6-CkJl3c69.js +1 -0
  79. package/web-dist/assets/{railroadDiagram-RFXS5EU6-CPJAH-r7.js → railroadDiagram-RFXS5EU6-C4rERo6H.js} +1 -1
  80. package/web-dist/assets/{requirementDiagram-TGXJPOKE-BVFJZfwl.js → requirementDiagram-TGXJPOKE-DlIG9D0n.js} +1 -1
  81. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-CUhvFPmE.js → sankeyDiagram-HTMAVEWB-DVMrG-qY.js} +1 -1
  82. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-Cy9CGhNq.js → sequenceDiagram-DBY2YBRQ-DDcDM_-e.js} +1 -1
  83. package/web-dist/assets/{stateDiagram-2N3HPSRC-CqOnmPLp.js → stateDiagram-2N3HPSRC-DuCRKU5t.js} +1 -1
  84. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-90dqEQum.js +1 -0
  85. package/web-dist/assets/{swimlanes-5IMT3BWC-c_zBqkb9.js → swimlanes-5IMT3BWC-CY0fdvUm.js} +1 -1
  86. package/web-dist/assets/swimlanesDiagram-G3AALYLV-BETWueIt.js +8 -0
  87. package/web-dist/assets/{timeline-definition-FHXFAJF6-y_QM62SK.js → timeline-definition-FHXFAJF6-De0fDNdU.js} +1 -1
  88. package/web-dist/assets/{treeView-QDETBFTQ-T3y_1KXV.js → treeView-QDETBFTQ-AIoMhcen.js} +1 -1
  89. package/web-dist/assets/{treemap-6X3UGDF4-CzvqucqY.js → treemap-6X3UGDF4-CPZ1Ln05.js} +1 -1
  90. package/web-dist/assets/{vennDiagram-L72KCM5P-6-HNOpN0.js → vennDiagram-L72KCM5P-FrSO6pmh.js} +1 -1
  91. package/web-dist/assets/{wardley-OPB4EBWU-BrsaqsLL.js → wardley-OPB4EBWU-BbET6cjs.js} +1 -1
  92. package/web-dist/assets/{wardleyDiagram-EHGQE667-Cr_rCzwd.js → wardleyDiagram-EHGQE667-DqvcCBjr.js} +1 -1
  93. package/web-dist/assets/{xychartDiagram-FW5EYKEG-BnX6ccx4.js → xychartDiagram-FW5EYKEG-91xKEO07.js} +1 -1
  94. package/web-dist/index.html +2 -2
  95. package/runtime/cc-worker/agents/fable-high.md +0 -8
  96. package/runtime/cc-worker/agents/fable-low.md +0 -8
  97. package/runtime/cc-worker/agents/fable-max.md +0 -8
  98. package/runtime/cc-worker/agents/fable-medium.md +0 -8
  99. package/runtime/cc-worker/agents/fable-xhigh.md +0 -8
  100. package/runtime/cc-worker/agents/haiku.md +0 -7
  101. package/runtime/cc-worker/agents/opus-high.md +0 -8
  102. package/runtime/cc-worker/agents/opus-low.md +0 -8
  103. package/runtime/cc-worker/agents/opus-max.md +0 -8
  104. package/runtime/cc-worker/agents/opus-medium.md +0 -8
  105. package/runtime/cc-worker/agents/opus-xhigh.md +0 -8
  106. package/runtime/cc-worker/agents/sonnet-high.md +0 -8
  107. package/runtime/cc-worker/agents/sonnet-low.md +0 -8
  108. package/runtime/cc-worker/agents/sonnet-max.md +0 -8
  109. package/runtime/cc-worker/agents/sonnet-medium.md +0 -8
  110. package/runtime/cc-worker/agents/sonnet-xhigh.md +0 -8
  111. package/runtime/cc-worker/bin/browser-mcp-tools.json +0 -1192
  112. package/runtime/cc-worker/bin/browser-mcp.mjs +0 -730
  113. package/runtime/cc-worker/hooks/precompact-instructions.mjs +0 -133
  114. package/runtime/cc-worker/skills/waits/SKILL.md +0 -84
  115. package/web-dist/assets/architecture-TIHT7OUA-DksosNDW.js +0 -1
  116. package/web-dist/assets/channel-DqoPvfQz.js +0 -1
  117. package/web-dist/assets/classDiagram-OUVF2IWQ-DwUs74RB.js +0 -1
  118. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DwUs74RB.js +0 -1
  119. package/web-dist/assets/eventmodeling-45OFAUF4-CUIjQ7kE.js +0 -1
  120. package/web-dist/assets/flowDiagram-23GEKE2U-Dka-3F3n.js +0 -1
  121. package/web-dist/assets/index-CmrDpsD8.css +0 -1
  122. package/web-dist/assets/index-DB1d3eMJ.js +0 -491
  123. package/web-dist/assets/railroad-abnf-AHOZXSZD-CCnWa6K9.js +0 -1
  124. package/web-dist/assets/railroad-ebnf-EBAXGLYW-jtVdzv7e.js +0 -1
  125. package/web-dist/assets/railroad-peg-LSFZ7HO6-B8D6TyEe.js +0 -1
  126. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DWKW-4qu.js +0 -1
  127. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DZKoB7ae.js +0 -8
package/dist/dev-child.js CHANGED
@@ -12938,6 +12938,13 @@ function retiredAwaitingKindsIn(body) {
12938
12938
  }
12939
12939
  return found;
12940
12940
  }
12941
+ function trimAwaitingTitle(raw2) {
12942
+ const flat = raw2.replace(/\s+/g, " ").trim();
12943
+ if (flat.length <= AWAITING_TITLE_MAX) return flat;
12944
+ const cut = flat.slice(0, AWAITING_TITLE_MAX);
12945
+ const space = cut.lastIndexOf(" ");
12946
+ return `${(space > AWAITING_TITLE_MAX / 2 ? cut.slice(0, space) : cut).replace(/[.,;:–—-]$/, "").trimEnd()}\u2026`;
12947
+ }
12941
12948
  function splitAwaitingFrontmatter(raw2) {
12942
12949
  const lines = raw2.split("\n").map((l) => l.replace(/\r$/, ""));
12943
12950
  const delimiter2 = lines.findIndex((l) => /^\s*---+\s*$/.test(l));
@@ -12981,6 +12988,8 @@ function parseAwaitingYaml(text) {
12981
12988
  for (const entry of Array.isArray(raw2) ? raw2 : [raw2]) push(itemKind, entry);
12982
12989
  } else if (key === "for") {
12983
12990
  push("for", raw2);
12991
+ } else if (key === "title") {
12992
+ if (typeof raw2 === "string" || typeof raw2 === "number") push("title", trimAwaitingTitle(String(raw2)));
12984
12993
  }
12985
12994
  }
12986
12995
  return { ok: true, hints };
@@ -13263,7 +13272,7 @@ function parseGithubWakeSteer(text) {
13263
13272
  function frizzRoute(path) {
13264
13273
  return `${FRIZZ_ROUTE_PREFIX}${path.startsWith("/") ? path : `/${path}`}`;
13265
13274
  }
13266
- var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, ThreadWatchView, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketClientMsg, ProjectCard, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13275
+ var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, RETIRED_AWAITING_REPLACEMENT, AWAITING_KEY_RE, AWAITING_YAML_KEYS, AWAITING_SEQUENCE_KEYS, AWAITING_HINT_MAX, AWAITING_HINT_VALUE_MAX, AWAITING_TITLE_MAX, AWAITING_ITEM_KINDS, AWAITING_DURATION_RE, DURATION_UNIT_MS, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, INTERRUPT_MARKERS, ALLDONE_SENTINEL, OPT_OUT_NOTE, OVER_QUESTION_NOTE, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TIMER_MAX_ARMED, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, ThreadWatchView, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, HUMAN_GAP_FLOOR_MS, HUMAN_GAP_NOTE_TAIL, WAKE_DELIVERY_TOKEN_TAIL, WAKE_DELIVERY_TOKEN_LINE, NOISE_PREFIXES, AGENT_MESSAGE_WRAPPER, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REVIEW_LEAD, WAKE_BACKLOG_TAIL, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, WAKE_ITEM, WAKE_MORE, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketClientMsg, ProjectCard, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13267
13276
  var init_src = __esm({
13268
13277
  "packages/shared/src/index.ts"() {
13269
13278
  "use strict";
@@ -13519,7 +13528,7 @@ var init_src = __esm({
13519
13528
  questions: external_exports.array(AskQuestion)
13520
13529
  });
13521
13530
  AwaitingHint = external_exports.object({
13522
- kind: external_exports.enum(["shell", "agent", "timer", "pr", "for"]),
13531
+ kind: external_exports.enum(["shell", "agent", "timer", "pr", "for", "title"]),
13523
13532
  value: external_exports.string()
13524
13533
  });
13525
13534
  RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
@@ -13541,7 +13550,7 @@ var init_src = __esm({
13541
13550
  "reason": "put it below the `---` as ordinary Markdown \u2014 the frontmatter is YAML now and takes no prose"
13542
13551
  };
13543
13552
  AWAITING_KEY_RE = /^([a-z][a-z-]*):\s*(\S.*)?$/i;
13544
- AWAITING_YAML_KEYS = /* @__PURE__ */ new Set(["shells", "agents", "timers", "prs", "for"]);
13553
+ AWAITING_YAML_KEYS = /* @__PURE__ */ new Set(["shells", "agents", "timers", "prs", "for", "title"]);
13545
13554
  AWAITING_SEQUENCE_KEYS = {
13546
13555
  shells: "shell",
13547
13556
  agents: "agent",
@@ -13550,6 +13559,7 @@ var init_src = __esm({
13550
13559
  };
13551
13560
  AWAITING_HINT_MAX = 8;
13552
13561
  AWAITING_HINT_VALUE_MAX = 200;
13562
+ AWAITING_TITLE_MAX = 40;
13553
13563
  AWAITING_ITEM_KINDS = ["shell", "agent", "timer", "pr"];
13554
13564
  AWAITING_DURATION_RE = /^(\d{1,5})(s|m|h|d)$/;
13555
13565
  DURATION_UNIT_MS = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
@@ -14133,6 +14143,14 @@ var init_src = __esm({
14133
14143
  // Default action for a vetted non-image local path in agent markdown. Image clicks always use the
14134
14144
  // OS default viewer so screenshots retain their expected behavior.
14135
14145
  localFileOpener: LocalFileOpener.optional(),
14146
+ // The token count at which a NEW Claude worker auto-compacts its conversation. Frizz requests the
14147
+ // 1M context window on every dispatch (resolveClaudeLaunchModel), so without this a worker grows
14148
+ // toward 1M before it ever compacts — and every turn past 200K re-sends up to 5x the conversation a
14149
+ // 200K TUI session would, which is the single largest reason a Frizz thread spends quota faster than
14150
+ // the TUI (measured 2026-08-26). Reaches the worker as CLAUDE_CODE_AUTO_COMPACT_WINDOW, which Claude
14151
+ // Code caps to the model's real window. Optional so an old blob parses; defaultSettings pins 500_000
14152
+ // (maintainer 2026-08-26: "a default compaction window of 500k by default").
14153
+ autoCompactWindow: external_exports.number().int().positive().optional(),
14136
14154
  // The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
14137
14155
  // unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
14138
14156
  // the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
@@ -14398,9 +14416,11 @@ var init_src = __esm({
14398
14416
  backend: Backend,
14399
14417
  options: external_exports.array(ThreadProfileOption)
14400
14418
  });
14419
+ ThreadSkillSource = external_exports.enum(["project", "user", "builtin", "plugin"]);
14401
14420
  ThreadSkill = external_exports.object({
14402
14421
  name: external_exports.string().min(1).max(512),
14403
- description: external_exports.string().max(1024)
14422
+ description: external_exports.string().max(1024),
14423
+ source: ThreadSkillSource.optional()
14404
14424
  }).strict();
14405
14425
  ThreadSkillsInput = external_exports.object({ slug: ThreadSlug }).strict();
14406
14426
  ThreadSkillsResult = external_exports.object({ skills: external_exports.array(ThreadSkill).max(1024) }).strict();
@@ -19652,6 +19672,9 @@ var init_settings = __esm({
19652
19672
  notifications: true,
19653
19673
  font: "sans",
19654
19674
  localFileOpener: "system",
19675
+ // 500K tokens: half the 1M window every Claude worker is dispatched with. See the schema for why a
19676
+ // worker left to compact at 1M is the biggest single reason a Frizz thread out-spends the TUI.
19677
+ autoCompactWindow: 5e5,
19655
19678
  // Hidden until asked for — see the schema for why.
19656
19679
  projectRail: false
19657
19680
  });
@@ -28586,32 +28609,7 @@ var init_wrapper = __esm({
28586
28609
  }
28587
28610
  });
28588
28611
 
28589
- // packages/server/src/worker-plugin-dir.ts
28590
- import { existsSync as existsSync6 } from "node:fs";
28591
- import { dirname as dirname9, join as join20 } from "node:path";
28592
- import { fileURLToPath as fileURLToPath2 } from "node:url";
28593
- function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
28594
- const override = env.FRIZZ_WORKER_PLUGIN_DIR;
28595
- if (override && existsSync6(join20(override, ".claude-plugin", "plugin.json")))
28596
- return override;
28597
- let current = dirname9(fileURLToPath2(moduleUrl));
28598
- for (; ; ) {
28599
- const candidate = join20(current, "cc-worker");
28600
- if (existsSync6(join20(candidate, ".claude-plugin", "plugin.json"))) return candidate;
28601
- const parent = dirname9(current);
28602
- if (parent === current) return void 0;
28603
- current = parent;
28604
- }
28605
- }
28606
- var init_worker_plugin_dir = __esm({
28607
- "packages/server/src/worker-plugin-dir.ts"() {
28608
- "use strict";
28609
- }
28610
- });
28611
-
28612
28612
  // packages/server/src/backend/types.ts
28613
- import { existsSync as existsSync7 } from "node:fs";
28614
- import { join as join21 } from "node:path";
28615
28613
  function frizzMcpEnv(mcp) {
28616
28614
  return {
28617
28615
  FRIZZ_STATE_DIR: mcp.stateDir,
@@ -28620,24 +28618,6 @@ function frizzMcpEnv(mcp) {
28620
28618
  ...mcp.slug ? { FRIZZ_THREAD_SLUG: mcp.slug } : {}
28621
28619
  };
28622
28620
  }
28623
- function chromeDevtoolsMcpSpec(scriptPath, nodeBin) {
28624
- const args = [...CHROME_DEVTOOLS_MCP.args];
28625
- if (!scriptPath) return { command: "npx", args: ["-y", `${CHROME_DEVTOOLS_MCP.package}@${CHROME_DEVTOOLS_MCP.version}`, ...args] };
28626
- return {
28627
- command: nodeBin,
28628
- args: [scriptPath, ...args],
28629
- env: { FRIZZ_BROWSER_MCP_PACKAGE: `${CHROME_DEVTOOLS_MCP.package}@${CHROME_DEVTOOLS_MCP.version}` }
28630
- };
28631
- }
28632
- function chromeDevtoolsMcpMount() {
28633
- return chromeDevtoolsMcpSpec(resolveBrowserMcpScript(), process.execPath);
28634
- }
28635
- function resolveBrowserMcpScript(moduleUrl = import.meta.url, env = process.env) {
28636
- const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
28637
- if (!pluginDir) return void 0;
28638
- const scriptPath = join21(pluginDir, "bin", CHROME_DEVTOOLS_MCP.script);
28639
- return existsSync7(scriptPath) ? scriptPath : void 0;
28640
- }
28641
28621
  function workerCap(name, lifted, env) {
28642
28622
  const override = env[name];
28643
28623
  if (override !== void 0 && /^[1-9][0-9]*$/.test(override)) return override;
@@ -28651,29 +28631,20 @@ function claudeWorkerEnv(env = process.env) {
28651
28631
  CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: workerCap("CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS", WORKER_MAX_CONCURRENT_SUBAGENTS, env)
28652
28632
  };
28653
28633
  }
28654
- var FRIZZ_MCP, CHROME_DEVTOOLS_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, WORKER_DISALLOWED_TOOLS;
28634
+ function claudeCompactionEnv(settings) {
28635
+ const window = settings?.autoCompactWindow;
28636
+ if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
28637
+ return { CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(window) };
28638
+ }
28639
+ var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, WORKER_DISALLOWED_TOOLS;
28655
28640
  var init_types2 = __esm({
28656
28641
  "packages/server/src/backend/types.ts"() {
28657
28642
  "use strict";
28658
- init_worker_plugin_dir();
28659
28643
  FRIZZ_MCP = {
28660
28644
  name: "frizz",
28661
28645
  script: "frizz-mcp.mjs"
28662
28646
  // resolved under <worker plugin dir>/bin/
28663
28647
  };
28664
- CHROME_DEVTOOLS_MCP = {
28665
- name: "chrome-devtools",
28666
- // Resolved under <worker plugin dir>/bin/, exactly like FRIZZ_MCP.script.
28667
- script: "browser-mcp.mjs",
28668
- package: "chrome-devtools-mcp",
28669
- version: "1.7.0",
28670
- args: [
28671
- "--experimentalPageIdRouting",
28672
- "--headless",
28673
- "--isolated",
28674
- "--no-usage-statistics"
28675
- ]
28676
- };
28677
28648
  WORKER_MAX_WEB_SEARCHES = 1e4;
28678
28649
  WORKER_MAX_SUBAGENTS = 1e4;
28679
28650
  WORKER_MAX_CONCURRENT_SUBAGENTS = 100;
@@ -28703,12 +28674,8 @@ function serverTable(command, args, env) {
28703
28674
  }
28704
28675
  return inlineTable(entries);
28705
28676
  }
28706
- function codexMcpConfigArgs(frizzMcp, nodeBin = process.execPath, browserMcpScript) {
28707
- const chrome = chromeDevtoolsMcpSpec(browserMcpScript ?? resolveBrowserMcpScript(), nodeBin);
28708
- const args = [
28709
- "-c",
28710
- `mcp_servers.${CHROME_DEVTOOLS_MCP.name}=${serverTable(chrome.command, chrome.args, chrome.env)}`
28711
- ];
28677
+ function codexMcpConfigArgs(frizzMcp, nodeBin = process.execPath) {
28678
+ const args = [];
28712
28679
  if (frizzMcp) {
28713
28680
  args.push(
28714
28681
  "-c",
@@ -28733,15 +28700,15 @@ import { spawn as spawn2 } from "node:child_process";
28733
28700
  import { connect } from "node:net";
28734
28701
  import { Agent as HttpAgent } from "node:http";
28735
28702
  import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
28736
- import { existsSync as existsSync8, mkdirSync as mkdirSync11, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
28737
- import { join as join22 } from "node:path";
28703
+ import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
28704
+ import { join as join20 } from "node:path";
28738
28705
  import { PassThrough, Writable } from "node:stream";
28739
28706
  import { StringDecoder } from "node:string_decoder";
28740
28707
  function nativeDir(stateDir) {
28741
- return join22(stateDir, "codex-app-server-native");
28708
+ return join20(stateDir, "codex-app-server-native");
28742
28709
  }
28743
28710
  function nativeRecordPath(stateDir, projectId) {
28744
- return join22(nativeDir(stateDir), `${projectId}.json`);
28711
+ return join20(nativeDir(stateDir), `${projectId}.json`);
28745
28712
  }
28746
28713
  function nativeListenSocketPath(stateDir, projectId) {
28747
28714
  const key = createHash7("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
@@ -28773,7 +28740,7 @@ function readNativeRecord(stateDir, projectId) {
28773
28740
  function liveNativeRecord(stateDir, projectId) {
28774
28741
  const record = readNativeRecord(stateDir, projectId);
28775
28742
  if (!record) return null;
28776
- if (pidAlive2(record.listenerPid) && existsSync8(record.socketPath)) return record;
28743
+ if (pidAlive2(record.listenerPid) && existsSync6(record.socketPath)) return record;
28777
28744
  try {
28778
28745
  unlinkSync2(nativeRecordPath(stateDir, projectId));
28779
28746
  } catch {
@@ -28915,7 +28882,7 @@ async function startListener(options) {
28915
28882
  const { stateDir, projectId } = options;
28916
28883
  mkdirSync11(nativeDir(stateDir), { recursive: true });
28917
28884
  const socketPath = nativeListenSocketPath(stateDir, projectId);
28918
- if (existsSync8(socketPath) && !await socketAccepting(socketPath)) {
28885
+ if (existsSync6(socketPath) && !await socketAccepting(socketPath)) {
28919
28886
  try {
28920
28887
  unlinkSync2(socketPath);
28921
28888
  } catch {
@@ -28939,7 +28906,7 @@ async function startListener(options) {
28939
28906
  writeFileSync9(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
28940
28907
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
28941
28908
  for (; ; ) {
28942
- if (existsSync8(socketPath) && await socketAccepting(socketPath)) return record;
28909
+ if (existsSync6(socketPath) && await socketAccepting(socketPath)) return record;
28943
28910
  if (!pidAlive2(record.listenerPid)) throw new Error("codex app-server listener exited before it became ready");
28944
28911
  if (Date.now() > deadline) throw new Error("codex app-server listener did not become ready");
28945
28912
  await new Promise((resolve10) => setTimeout(resolve10, 50));
@@ -29005,15 +28972,15 @@ var init_codex_app_server_native = __esm({
29005
28972
  import { spawn as spawn3, spawnSync } from "node:child_process";
29006
28973
  import { createConnection } from "node:net";
29007
28974
  import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
29008
- import { existsSync as existsSync9, mkdirSync as mkdirSync12, readFileSync as readFileSync15, unlinkSync as unlinkSync3 } from "node:fs";
29009
- import { join as join23 } from "node:path";
28975
+ import { existsSync as existsSync7, mkdirSync as mkdirSync12, readFileSync as readFileSync15, unlinkSync as unlinkSync3 } from "node:fs";
28976
+ import { join as join21 } from "node:path";
29010
28977
  import { PassThrough as PassThrough2, Writable as Writable2 } from "node:stream";
29011
28978
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
29012
28979
  function daemonDir(stateDir) {
29013
- return join23(stateDir, "codex-app-server");
28980
+ return join21(stateDir, "codex-app-server");
29014
28981
  }
29015
28982
  function recordPath(stateDir, projectId) {
29016
- return join23(daemonDir(stateDir), `${projectId}.json`);
28983
+ return join21(daemonDir(stateDir), `${projectId}.json`);
29017
28984
  }
29018
28985
  function codexAppServerSocketPath(stateDir, projectId) {
29019
28986
  const key = createHash8("sha256").update(stateDir).update("\0").update(projectId).digest("hex").slice(0, 16);
@@ -29295,7 +29262,7 @@ var init_codex_app_server_host = __esm({
29295
29262
  }
29296
29263
  if (process.platform !== "win32") {
29297
29264
  const stale = codexAppServerSocketPath(options.stateDir, options.projectId);
29298
- if (existsSync9(stale) && !liveDaemonRecord(options.stateDir, options.projectId)) {
29265
+ if (existsSync7(stale) && !liveDaemonRecord(options.stateDir, options.projectId)) {
29299
29266
  try {
29300
29267
  unlinkSync3(stale);
29301
29268
  } catch {
@@ -29842,6 +29809,12 @@ Suggested answers: ${question.options.map((option) => option.label).join(", ")}`
29842
29809
  return { ...base, input: "multiline", maxLength: 4e3 };
29843
29810
  });
29844
29811
  }
29812
+ function codexSkillSource(scope) {
29813
+ if (scope === "repo") return "project";
29814
+ if (scope === "user") return "user";
29815
+ if (scope === "system") return "builtin";
29816
+ return void 0;
29817
+ }
29845
29818
  function resumedThreadHasLiveTurn(rawResponse) {
29846
29819
  const parsed = ThreadResumeStatus.safeParse(rawResponse);
29847
29820
  return parsed.success && parsed.data.thread.status.type === "active";
@@ -30380,7 +30353,12 @@ var init_codex_app_server = __esm({
30380
30353
  skills: external_exports.array(external_exports.object({
30381
30354
  name: external_exports.string(),
30382
30355
  description: external_exports.string().optional(),
30383
- enabled: external_exports.boolean().optional()
30356
+ enabled: external_exports.boolean().optional(),
30357
+ // The root the app-server resolved this skill from — measured against codex-cli 0.146.0 as
30358
+ // `repo` (`<cwd>/.agents/skills/…`), `user` (`~/.agents/skills/…`) and `system`
30359
+ // (`~/.codex/skills/.system/…`). Kept as a free string, not an enum: an unmapped value must
30360
+ // leave the row unlabelled, never fail the listing (see codexSkillSource).
30361
+ scope: external_exports.string().optional()
30384
30362
  }).passthrough()).optional()
30385
30363
  }).passthrough()).optional()
30386
30364
  }).passthrough();
@@ -30875,7 +30853,11 @@ var init_codex_app_server = __esm({
30875
30853
  if (skills.length >= 1024) return skills;
30876
30854
  if (seen.has(skill.name)) continue;
30877
30855
  seen.add(skill.name);
30878
- skills.push({ name: skill.name.slice(0, 512), description: (skill.description ?? "").slice(0, 1024) });
30856
+ skills.push({
30857
+ name: skill.name.slice(0, 512),
30858
+ description: (skill.description ?? "").slice(0, 1024),
30859
+ source: codexSkillSource(skill.scope)
30860
+ });
30879
30861
  }
30880
30862
  }
30881
30863
  return skills;
@@ -32241,11 +32223,11 @@ var init_codex_app_server = __esm({
32241
32223
  });
32242
32224
 
32243
32225
  // packages/server/src/backend/codex-quota.ts
32244
- import { join as join24 } from "node:path";
32226
+ import { join as join22 } from "node:path";
32245
32227
  import { readdirSync as readdirSync7, statSync as statSync10, openSync as openSync12, readSync as readSync6, fstatSync, closeSync as closeSync11 } from "node:fs";
32246
32228
  import { spawn as spawn4 } from "node:child_process";
32247
32229
  function sessionsDir2(codexHome) {
32248
- return join24(codexHome, "sessions");
32230
+ return join22(codexHome, "sessions");
32249
32231
  }
32250
32232
  function newestRollouts(dir, out, budget) {
32251
32233
  if (budget.n <= 0) return;
@@ -32259,11 +32241,11 @@ function newestRollouts(dir, out, budget) {
32259
32241
  const files = entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
32260
32242
  for (const d of dirs) {
32261
32243
  if (budget.n <= 0) return;
32262
- newestRollouts(join24(dir, d), out, budget);
32244
+ newestRollouts(join22(dir, d), out, budget);
32263
32245
  }
32264
32246
  for (const f of files) {
32265
32247
  if (budget.n <= 0) return;
32266
- out.push(join24(dir, f));
32248
+ out.push(join22(dir, f));
32267
32249
  budget.n--;
32268
32250
  }
32269
32251
  }
@@ -32463,7 +32445,7 @@ var init_codex_quota = __esm({
32463
32445
  });
32464
32446
 
32465
32447
  // packages/server/src/backend/claude-quota.ts
32466
- import { join as join25 } from "node:path";
32448
+ import { join as join23 } from "node:path";
32467
32449
  import { homedir as homedir9, platform } from "node:os";
32468
32450
  import { createHash as createHash10 } from "node:crypto";
32469
32451
  import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -32471,7 +32453,7 @@ import { execFile as execFile2 } from "node:child_process";
32471
32453
  import { promisify as promisify2 } from "node:util";
32472
32454
  function claudeConfigDir() {
32473
32455
  const override = process.env.CLAUDE_CONFIG_DIR;
32474
- return override && override.trim() ? override : join25(homedir9(), ".claude");
32456
+ return override && override.trim() ? override : join23(homedir9(), ".claude");
32475
32457
  }
32476
32458
  function tokenFromCredentialsJson(raw2) {
32477
32459
  let doc;
@@ -32500,7 +32482,7 @@ async function readKeychainToken() {
32500
32482
  }
32501
32483
  async function readAccessToken(configDir) {
32502
32484
  try {
32503
- const fromFile = tokenFromCredentialsJson(await readFile(join25(configDir, ".credentials.json"), "utf8"));
32485
+ const fromFile = tokenFromCredentialsJson(await readFile(join23(configDir, ".credentials.json"), "utf8"));
32504
32486
  if (fromFile) return fromFile;
32505
32487
  } catch {
32506
32488
  }
@@ -32569,8 +32551,8 @@ function parseClaudeUsage(body, planType) {
32569
32551
  function cachePaths(cacheDir, configDir) {
32570
32552
  const profile = createHash10("sha256").update(configDir).digest("hex").slice(0, 12);
32571
32553
  return {
32572
- data: join25(cacheDir, `claude-${profile}.json`),
32573
- lock: join25(cacheDir, `claude-${profile}.lock`)
32554
+ data: join23(cacheDir, `claude-${profile}.json`),
32555
+ lock: join23(cacheDir, `claude-${profile}.lock`)
32574
32556
  };
32575
32557
  }
32576
32558
  async function readShared(path) {
@@ -32766,7 +32748,7 @@ function refreshSharedInBackground(paths, claudeBin, deps, now) {
32766
32748
  async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
32767
32749
  const now = (deps.now ?? Date.now)();
32768
32750
  const configDir = claudeConfigDir();
32769
- const cacheDir = deps.cacheDir ?? join25(frizzRoots().cache, "quota-cache");
32751
+ const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
32770
32752
  try {
32771
32753
  await mkdir(cacheDir, { recursive: true, mode: 448 });
32772
32754
  } catch {
@@ -32777,7 +32759,7 @@ async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
32777
32759
  async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
32778
32760
  const now = (deps.now ?? Date.now)();
32779
32761
  const configDir = claudeConfigDir();
32780
- const cacheDir = deps.cacheDir ?? join25(frizzRoots().cache, "quota-cache");
32762
+ const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
32781
32763
  const paths = cachePaths(cacheDir, configDir);
32782
32764
  try {
32783
32765
  await mkdir(cacheDir, { recursive: true, mode: 448 });
@@ -32937,7 +32919,7 @@ var init_quota = __esm({
32937
32919
  // packages/server/src/frizz.ts
32938
32920
  import { execFile as execFile3 } from "node:child_process";
32939
32921
  import { lstatSync as lstatSync2, realpathSync as realpathSync6 } from "node:fs";
32940
- import { basename as basename5, dirname as dirname10, join as join26, resolve as resolve4 } from "node:path";
32922
+ import { basename as basename5, dirname as dirname9, join as join24, resolve as resolve4 } from "node:path";
32941
32923
  import { promisify as promisify3 } from "node:util";
32942
32924
  function frizzScriptsDir() {
32943
32925
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
@@ -32946,11 +32928,11 @@ function frizzScriptsDir() {
32946
32928
  function directFrizzRoot(projectDir) {
32947
32929
  try {
32948
32930
  const projectRoot = realpathSync6(projectDir);
32949
- const path = join26(projectRoot, ".frizz");
32931
+ const path = join24(projectRoot, ".frizz");
32950
32932
  const stat2 = lstatSync2(path);
32951
32933
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
32952
32934
  const real = realpathSync6(path);
32953
- return dirname10(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
32935
+ return dirname9(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
32954
32936
  } catch {
32955
32937
  return null;
32956
32938
  }
@@ -32960,7 +32942,7 @@ function frizzDirExists(projectDir) {
32960
32942
  }
32961
32943
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
32962
32944
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
32963
- const { stdout } = await execFileP("node", [join26(scriptsDir, "index.mjs"), "--json"], {
32945
+ const { stdout } = await execFileP("node", [join24(scriptsDir, "index.mjs"), "--json"], {
32964
32946
  cwd: projectDir,
32965
32947
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
32966
32948
  maxBuffer: 32 * 1024 * 1024
@@ -32985,7 +32967,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
32985
32967
  };
32986
32968
  }
32987
32969
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
32988
- await execFileP("node", [join26(scriptsDir, "thread-update.mjs"), slug, ...args], {
32970
+ await execFileP("node", [join24(scriptsDir, "thread-update.mjs"), slug, ...args], {
32989
32971
  cwd: projectDir,
32990
32972
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
32991
32973
  });
@@ -33021,7 +33003,12 @@ function readAwaitingPark(hints) {
33021
33003
  return { items, forMs };
33022
33004
  }
33023
33005
  function unaccountedItems(items, live) {
33024
- return items.filter((i) => !live[LIVE_SET[i.kind]].has(i.value));
33006
+ return items.filter((i) => !live[LIVE_SET[i.kind]].has(liveKey(i)));
33007
+ }
33008
+ function liveKey(i) {
33009
+ if (i.kind !== "pr") return i.value;
33010
+ const ref = parsePrRef(i.value);
33011
+ return ref ? githubStatusKey(ref) : i.value;
33025
33012
  }
33026
33013
  function parkIsHonoured(park, live) {
33027
33014
  if (park.items.length === 0) return false;
@@ -33060,6 +33047,29 @@ var init_awaiting = __esm({
33060
33047
  }
33061
33048
  });
33062
33049
 
33050
+ // packages/server/src/worker-plugin-dir.ts
33051
+ import { existsSync as existsSync8 } from "node:fs";
33052
+ import { dirname as dirname10, join as join25 } from "node:path";
33053
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
33054
+ function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
33055
+ const override = env.FRIZZ_WORKER_PLUGIN_DIR;
33056
+ if (override && existsSync8(join25(override, ".claude-plugin", "plugin.json")))
33057
+ return override;
33058
+ let current = dirname10(fileURLToPath2(moduleUrl));
33059
+ for (; ; ) {
33060
+ const candidate = join25(current, "cc-worker");
33061
+ if (existsSync8(join25(candidate, ".claude-plugin", "plugin.json"))) return candidate;
33062
+ const parent = dirname10(current);
33063
+ if (parent === current) return void 0;
33064
+ current = parent;
33065
+ }
33066
+ }
33067
+ var init_worker_plugin_dir = __esm({
33068
+ "packages/server/src/worker-plugin-dir.ts"() {
33069
+ "use strict";
33070
+ }
33071
+ });
33072
+
33063
33073
  // packages/server/src/workerPrompt.ts
33064
33074
  function buildWorkerPrompt(kind = "claude", opts = {}) {
33065
33075
  const lean = kind === "claude";
@@ -33075,7 +33085,6 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
33075
33085
  lean ? null : THREAD_EXECUTION[kind],
33076
33086
  AGENT_COMPLETION,
33077
33087
  VISUAL_EVIDENCE,
33078
- GIT_DISCIPLINE,
33079
33088
  QUALITY_BAR,
33080
33089
  QUESTIONS,
33081
33090
  STOP_CRITERION,
@@ -33086,7 +33095,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
33086
33095
  out = out.replaceAll("{{FRIZZ_MONITORS_DIR}}", opts.monitorsDir?.trim() || MONITORS_DIR_FALLBACK);
33087
33096
  return out;
33088
33097
  }
33089
- var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, GIT_DISCIPLINE, QUALITY_BAR, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
33098
+ var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, QUALITY_BAR, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
33090
33099
  var init_workerPrompt = __esm({
33091
33100
  "packages/server/src/workerPrompt.ts"() {
33092
33101
  "use strict";
@@ -33149,114 +33158,81 @@ command pasted as prose. Never prefix a description with \`Running\` to force it
33149
33158
  verb itself, and use \`Running\` only when the thing you are doing is literally running something.`;
33150
33159
  SIGNALS = `## End-of-turn signals \u2014 your final message IS the interface
33151
33160
 
33152
- When you come to rest, your last message is the entire interface the human sees \u2014 they read it in a
33153
- queue, hours later, with none of your working context. Write it self-contained.
33161
+ When you come to rest, your last message is the whole interface: the human reads it in a queue, hours
33162
+ later, with none of your context \u2014 and they have seen NOTHING since their own last message; every
33163
+ prompt in between (the Goal, a sign-off reminder, a watcher wake) came from frizz. So summarise the
33164
+ whole stretch since their last message: what you did, found, changed, and would do next. Be concise
33165
+ but do not omit; use whatever markup reads well.
33154
33166
 
33155
33167
  **ALWAYS SIGN OFF WITH A FENCE.** A bare rest \u2014 no fence \u2014 is not a handoff, it is an item nobody can
33156
33168
  triage: it says neither "answer me" nor "this is finished", so it sits in the queue meaning nothing.
33157
- Frizz corrects it, twice, and then stops: rest without a fence and you will be told to pick one, which
33158
- costs you a turn each time.
33159
-
33160
- Pick \`question\` when you need the human, \`done\` when the work is genuinely finished, and \`awaiting\`
33161
- when you are WAITING \u2014 on a PR, or on your own background work \u2014 naming what you wait on, one line each.
33162
-
33163
- \`awaiting\` REGISTERS NOTHING. It is how you come to rest without being bumped for a handoff, and how
33164
- the human sees what you are waiting on. Your background shells and sub-agents are watched AUTOMATICALLY,
33165
- fence or no fence: frizz wakes you when one finishes, every time.
33166
-
33167
- **WRITE IT TO BE READ COLD.** The human reads your final message in a queue, hours later, with none of
33168
- your context \u2014 and they have NOT seen anything since their own last message. Every prompt you received
33169
- in between came from FRIZZ, not from them: the Goal, the sign-off reminder, a watcher waking you. So
33170
- summarise the whole stretch since their last message \u2014 what you did, what you found, what changed, what
33171
- you would do next \u2014 rather than only the last thing you touched. Do not assume they watched.
33172
-
33173
- Be concise, but do not omit. Use whatever markup makes it readable: headings, bullets, tables, code.
33174
- The one place brevity is a RULE is a \`done\` body \u2014 see its entry below.
33175
-
33176
- **NAME THE PULL REQUEST, EVERY TIME YOU REST.** If your effort has a PR \u2014 one you opened, one you are
33177
- reviewing, one you are fixing \u2014 write its number in EVERY resting message, not just the one where it
33178
- first appeared. The human reads these as a queue of cards from a dozen threads at once, and a card that
33179
- says "pushed the fix, CI is green" without a number is a card they cannot place: they have to open the
33180
- thread to learn which PR it was about (maintainer 2026-08-16: "I keep being unclear what PR is being
33181
- implemented in a given chat"). One \`#123\` in the first line or two costs you nothing and answers it.
33182
-
33183
- Several PRs in play \u21D2 name them all. No PR yet \u21D2 say what the work is against instead (a branch, an
33184
- issue, a file) \u2014 the point is that the card identifies its own subject without being opened.
33185
-
33186
- Frizz turns \`#123\`, \`owner/repo#123\` and bare commit hashes into github.com links itself, in every
33187
- surface it draws your prose, for any project whose origin remote is github.com. Write the bare form;
33188
- wrapping one in a \`[link](url)\` yourself is redundant. Everything else you want clickable \u2014 a file, a
33189
- thread, any other URL \u2014 is still yours to write as a real link.
33190
-
33191
- Use at most ONE fenced signal block, at the very END of the final message. The fence language is the
33192
- state; the body is the card the human reads. A \` \`\`\`question \` block and real permission prompts are
33193
- higher-priority asks than either fence below.
33169
+ Frizz bumps you for one, twice, then gives up \u2014 a turn wasted each time. Use at most ONE fenced signal
33170
+ block, at the very END. The fence language is the state; the body is the card the human reads. A
33171
+ \` \`\`\`question \` block and real permission prompts are higher-priority asks than \`done\` or \`awaiting\`.
33172
+
33173
+ **NAME THE PULL REQUEST, EVERY TIME YOU REST** \u2014 its number in EVERY resting message, not just the
33174
+ one where it first appeared: one \`#123\` in the first line or two. Several PRs in play \u21D2 name them
33175
+ all. No PR yet \u21D2 say what the work is against instead: a branch, an issue, a file. Cards from a dozen
33176
+ threads queue together, and a card without its subject cannot be placed (maintainer 2026-08-16: "I keep being
33177
+ unclear what PR is being implemented in a given chat"). Frizz links \`#123\`, \`owner/repo#123\` and bare
33178
+ commit hashes itself for github.com remotes; write the bare form. Anything else you want clickable \u2014 a
33179
+ file, a thread, a URL \u2014 is yours to write as a real link.
33194
33180
 
33195
33181
  **THE FENCE AND THE PROSE ABOVE IT ARE TWO SURFACES, NOT ONE MESSAGE WRITTEN TWICE.** The card is the
33196
- LEDGER: one bullet per deliverable, what shipped and where. The prose is only what a ledger cannot hold
33197
- \u2014 the reasoning, the caveat, the thing the human has to do. If a sentence would read the same in either,
33198
- it belongs in exactly ONE of them. Writing the summary and then restating it as bullets costs the human
33199
- a second read for nothing, and it is the commonest thing wrong with an otherwise good handoff.
33200
-
33201
- - \` \`\`\`done \` \u2014 you COMPLETED the effort's real work: code LANDED on the project's mainline, a plan or
33202
- doc written, or a commissioned research/audit report finished INTO A FILE. Body: ONE TO THREE SENTENCES, then a
33203
- BULLET LIST, one \`- \` item per task, each opening with a **bolded verb phrase** and naming what
33204
- shipped and where. This is the ONE place the short shape is a rule \u2014 a dismissal card is scanned, not
33205
- read, so it is a ledger. Everywhere else, say as much as the reader needs. The card renders inline markdown, so backtick every path,
33206
- identifier and command, and make FILE references real \`[links](url)\` \u2014 issue numbers and commit
33207
- hashes link themselves, per above. It renders as a
33208
- checked success card in the queue and stays there until the human archives it. The fence MUTATES
33209
- NOTHING.
33182
+ LEDGER: one bullet per deliverable, what shipped and where. The prose holds only what a ledger cannot \u2014
33183
+ the reasoning, the caveat, the thing the human has to do. If a sentence would read the same in either, it belongs in
33184
+ exactly ONE of them.
33185
+
33186
+ - \` \`\`\`done \` \u2014 you COMPLETED the effort's real work: code LANDED on the project's mainline, or a plan,
33187
+ doc or commissioned research/audit report written INTO A FILE. Body: ONE TO THREE SENTENCES, then a
33188
+ BULLET LIST, one \`- \` item per task, each opening with a **bolded verb phrase** naming what shipped
33189
+ and where; backtick every path, identifier and command, and make FILE references real \`[links](url)\`
33190
+ (issue numbers and commit hashes link themselves). It renders as a checked success card in the queue
33191
+ until the human archives it. The fence MUTATES NOTHING.
33210
33192
 
33211
33193
  \`\`\`done
33212
33194
  - Fixed the cache collision in [\`src/resolver.ts\`](https://github.com/acme/app/pull/391) \u2014 the lookup now keys on the normalized id.
33213
33195
  - Added a regression test; \`npm test\` green.
33214
33196
  \`\`\`
33215
33197
 
33216
- **\`done\` is a DISMISSAL, not a summary** \u2014 its card files the thread away where nobody looks again,
33217
- and anything living only in the conversation goes with it. So the test is never "have I stopped
33218
- working": it is **WHAT IS LOST IF NOBODY EVER OPENS THIS THREAD AGAIN?** Name one thing and it is not
33219
- done. If the thread points at future work AT ALL, you are not done \u2014 see **When the work is finished
33220
- but the thread found more** for the ways out of that, none of which is stretching \`done\`. Uncertain
33221
- is not done.
33222
-
33223
- **A RECOMMENDATION IS NOT A CONCLUSION, AND AN UNSENT DRAFT IS NOT A DELIVERABLE.** When the verdict
33224
- is that SOMEONE SHOULD NOW DO SOMETHING \u2014 merge it, decline it, post this comment, pick one of these
33225
- two designs, press the button you are not allowed to press \u2014 that someone is the human, so the effort
33226
- ends on a \` \`\`\`question \` carrying your recommendation as option A. Same for anything you WROTE but
33227
- did not SEND: a drafted comment, reply, issue body or release note you were right not to publish
33228
- unilaterally is one click short of delivered, and \`done\` files that draft away with the thread.
33229
-
33230
- **A \`done\` MESSAGE SAYS WHAT HAPPENED. DELETE EVERY DANGLING "WORTH DOING LATER".** "One thing to
33231
- carry forward\u2026", "a follow-up could\u2026", "someone should add a changelog line before this ships" \u2014 a
33232
- forward-reference parked in a dismissal card is clutter: too weak for anyone to act on, too present to
33233
- ignore, and archived unread. Each one resolves four ways and there is no fifth \u2014 DO it, SPAWN it onto
33234
- its own card, ASK about it, or DROP it. Something the human must do NOW ("re-pull before you restart")
33235
- is not a dangling idea; that is the handoff, and it belongs in the prose.
33236
-
33237
- What blocks \`done\` is work still OWED \u2014 by you, or by the human because of what you found \u2014 not a
33238
- BACKGROUND PROCESS that happens to still be running: a watcher, dev server, or poller you have already
33239
- moved on from does not hold it back \u2014 name it in the body and fence anyway. **Read that carve-out
33240
- narrowly: its subject is a running process, and nothing else.** Follow-up work you DISCOVERED blocks
33241
- \`done\` just as hard as work you were assigned, even when it is someone else's to do \u2014 "not mine" is
33242
- not "not owed", because the card takes the finding with it either way. A \`done\` fence REPLACES the
33243
- awaiting-background card with your own completion card, which is what turns a finished thread with a
33244
- live watcher into a one-click dismissal instead of a card the human has to keep deciding about. Code
33245
- written but not LANDED is not done (a commit, a pushed branch, an open PR is work still ahead of the
33246
- merge \u2014 where the project uses PRs, \`done\` waits for the MERGE, so park the PR on \` \`\`\`awaiting \`
33247
- with \`prs:\`). An investigation headed for a fix is NOT \`done\`; the fix is still owed. Two cases
33248
- DO earn \`done\` on something other than landed code, and BOTH earn it the same way \u2014 by passing the
33249
- test above, because the deliverable is a FILE the human opens without this thread: a commissioned
33250
- research or audit EFFORT, whose finished report is written to that file, earns \`done\`; and so does a
33251
- PLANNING session whose plan file is FULLY written and PERSISTED (wherever the dispatch or the
33252
- project's conventions put it \u2014 frizz prescribes no location), because
33253
- that artifact already lives outside the thread, so dismissing the thread loses nothing. Neither
33254
- exception stretches to a report that ENDS IN A DECISION the human has yet to make: write the file AND
33255
- ask the question.
33256
-
33257
- - \` \`\`\`awaiting \` \u2014 you have STOPPED, and you are waiting on work that is actually running. The
33258
- fence is PURE STRUCTURE: a list of things frizz can look up, how long the park may stand, and one line
33259
- of prose. Nothing else belongs in it.
33198
+ **\`done\` is a DISMISSAL, not a summary:** its card files the thread away where nobody looks again,
33199
+ and anything living only in the conversation goes with it. The test is never "have I stopped
33200
+ working" \u2014 it is **WHAT IS LOST IF NOBODY EVER OPENS THIS THREAD AGAIN?** Name one thing and it is
33201
+ not done; uncertain is not done. What blocks \`done\` is work still OWED \u2014 by you, or by the human
33202
+ because of what you found \u2014 not a BACKGROUND PROCESS that happens to still be running \u2014 a watcher, dev server or
33203
+ poller you have already moved on from: name it in the body and fence anyway (a \`done\` fence REPLACES
33204
+ the awaiting card).
33205
+ Read that carve-out narrowly: its subject is a running process, and nothing else.
33206
+ - Code written but not LANDED is not done: a commit, a pushed branch or an open PR is work still
33207
+ ahead of the merge \u2014 where the project uses PRs, \`done\` waits for the MERGE, so park the PR on
33208
+ \` \`\`\`awaiting \` with \`prs:\`. Opening the PR does NOT finish the thread \u2014 the MERGE does. An
33209
+ investigation headed for a fix is not done; the fix is still owed.
33210
+ - **A RECOMMENDATION IS NOT A CONCLUSION, AND AN UNSENT DRAFT IS NOT A DELIVERABLE.** When the
33211
+ verdict is that SOMEONE SHOULD NOW DO SOMETHING \u2014 merge it, decline it, post this comment, pick
33212
+ one of these designs, press the button you are not allowed to press \u2014 that someone is the human,
33213
+ so end on a \` \`\`\`question \` carrying your recommendation as option A. Same for anything you WROTE
33214
+ but did not SEND (a drafted comment, reply, issue body or release note): \`done\` files that draft
33215
+ away with the thread.
33216
+ - Follow-up work you DISCOVERED blocks \`done\` just as hard as work you were assigned, even when it
33217
+ is someone else's to do \u2014 "not mine" is not "not owed", because the card takes the finding with
33218
+ it either way. If the thread points at future work AT ALL, see **When the work is finished but
33219
+ the thread found more** \u2014 none of its exits is stretching \`done\`.
33220
+ - **A \`done\` MESSAGE SAYS WHAT HAPPENED. DELETE EVERY DANGLING "WORTH DOING LATER".** "One thing to
33221
+ carry forward\u2026", "a follow-up could\u2026" \u2014 a forward-reference in a dismissal card is too weak to act
33222
+ on and archived unread. Each one resolves four ways and there is no fifth \u2014 DO it, SPAWN it onto
33223
+ its own card, ASK about it, or DROP it. Something the human must do NOW ("re-pull before you
33224
+ restart") is not a dangling idea; that is the handoff, and it belongs in the prose.
33225
+ - Two cases earn \`done\` without landed code, by the same test \u2014 the deliverable is a FILE the human
33226
+ opens without this thread. A commissioned research or audit EFFORT whose finished report is
33227
+ written to that file earns \`done\`; so does a PLANNING session whose plan file is FULLY written and
33228
+ PERSISTED (wherever the dispatch or the project's conventions put it \u2014 frizz prescribes no
33229
+ location), because that artifact already lives outside the thread, so dismissing the thread loses
33230
+ nothing. The WRITTEN, PERSISTED file is the whole reason a planning thread may end on \`done\`: the
33231
+ design outlives the thread's dismissal. Neither exception stretches to a report that ENDS IN A DECISION the
33232
+ human has yet to make: write the file AND ask the question.
33233
+
33234
+ - \` \`\`\`awaiting \` \u2014 you have STOPPED, and you are waiting on work that is actually running (never on
33235
+ a human \u2014 that is a question). The fence is PURE STRUCTURE: YAML frontmatter naming what frizz can look up, then \`---\`, then Markdown prose.
33260
33236
 
33261
33237
  \`\`\`awaiting
33262
33238
  shells: [bzvtnt3ig]
@@ -33264,6 +33240,7 @@ a second read for nothing, and it is the commonest thing wrong with an otherwise
33264
33240
  timers: [tmr_a1b2c3d4e5f6]
33265
33241
  prs: [acme/app#391]
33266
33242
  for: 2h
33243
+ title: Three-platform CI run
33267
33244
  ---
33268
33245
  Waiting on the three-platform run before porting the v2 drivers.
33269
33246
 
@@ -33271,111 +33248,78 @@ a second read for nothing, and it is the commonest thing wrong with an otherwise
33271
33248
  - if it goes red I will bisect rather than re-run
33272
33249
  \`\`\`
33273
33250
 
33274
- **THE FRONTMATTER IS YAML. THE BODY IS MARKDOWN.** Everything above the \`---\` is a real YAML mapping \u2014
33275
- four PLURAL keys taking LISTS, plus \`for:\`. Everything after the \`---\` is ordinary Markdown, as much as
33276
- you want, with paragraphs, lists and links. That prose is what the human reads on your resting card, so
33277
- write it as a handoff, not as a label.
33278
-
33279
- **NO PROSE ABOVE THE \`---\`, EVER.** A sentence in the frontmatter is what makes it stop being YAML: a
33280
- colon inside it, or a \` #\` before an issue ref, and the parse either fails or silently eats half the
33281
- line. There is no \`reason:\` key any more \u2014 write the reason in the body.
33282
-
33283
- Both list forms work, and a single item may be written bare:
33284
-
33285
- \`\`\`awaiting
33286
- prs:
33287
- - acme/app#391
33288
- - acme/app#402
33289
- for: 6h
33290
- ---
33291
- Both are green; waiting on your merge.
33292
- \`\`\`
33293
-
33294
- - \`shells:\` / \`agents:\` \u2014 the runtime ids you were handed when you launched them ("Command running in
33295
- background with ID: bzvtnt3ig").
33296
-
33297
- **LOST AN ID? \`mcp__frizz__activity\` PRINTS THEM ALL** \u2014 every shell, sub-agent, timer and PR you have
33298
- out, each with the exact id your fence needs. Call it rather than guessing: a guessed id names
33299
- nothing, so frizz refuses the park and bumps you.
33251
+ - **THE FRONTMATTER IS YAML. THE BODY IS MARKDOWN.** Four PLURAL keys taking LISTS (either list form;
33252
+ a single item may be written bare), plus the scalars \`for:\` and \`title:\`. **NO PROSE ABOVE THE
33253
+ \`---\`, EVER** \u2014 a colon or a \` #\` inside a sentence breaks the parse. There is no \`reason:\` key any more;
33254
+ the reason goes in the body, which is the handoff the human reads. **THE SINGULAR KEYS ARE GONE** \u2014 \`shell:\`,
33255
+ \`agent:\`, \`timer:\`, \`pr:\` and \`reason:\`, one per line, were the grammar until 2026-08-24; YAML
33256
+ cannot express a repeated key, so they became the lists above. Write one and frizz tells you what
33257
+ replaced it while your thread sits in the queue.
33258
+ - \`shells:\` / \`agents:\` \u2014 the runtime ids you were handed at launch ("Command running in background
33259
+ with ID: bzvtnt3ig"). **LOST AN ID? \`mcp__frizz__activity\` PRINTS THEM ALL** \u2014 every shell,
33260
+ sub-agent, timer and PR you have out, with the exact id the fence needs. A guessed id names
33261
+ nothing, so frizz refuses the park and bumps you.
33300
33262
  - \`timers:\` \u2014 timer ids from \`mcp__frizz__timer\`. NOT instants; the tool creates the timer.
33301
- - \`prs:\` \u2014 pull requests you REGISTERED with \`mcp__frizz__watch_pr\`, written \`owner/repo#N\`. Register
33302
- FIRST: this line states the wait, it does not create one. That tool wakes you on CI turning green or red,
33303
- and on every later review, approval or comment, human or bot, for as long as the PR lives.
33263
+ - \`prs:\` \u2014 pull requests you REGISTERED with \`mcp__frizz__watch_pr\`, written \`owner/repo#N\`.
33264
+ Register FIRST: this line states the wait, it does not create one. That tool wakes you on CI
33265
+ turning green or red, and on every later review, approval or comment, for as long as the PR lives.
33304
33266
  - \`for:\` \u2014 **REQUIRED**, and a DURATION: \`30s\`, \`15m\`, \`2h\`, \`3d\`. Never an instant. When it runs
33305
33267
  out frizz brings you back to re-check everything; re-parking is fine and uncapped.
33306
-
33307
- **THE SINGULAR KEYS ARE GONE** \u2014 \`shell:\`, \`agent:\`, \`timer:\`, \`pr:\` and \`reason:\`, one per line, were
33308
- the grammar until 2026-08-24. YAML cannot express a repeated key, so they became the lists above. Write
33309
- one and frizz tells you what replaced it; your thread stays in the queue meanwhile.
33310
-
33311
- **REGISTERING IS NOT PARKING, AND PARKING IS NOT REGISTERING.** Launching a shell, dispatching a
33312
- sub-agent, setting a timer, registering a PR watcher \u2014 those just happen, and frizz watches them
33313
- whether or not you ever write a fence. This fence is only how you declare that you have STOPPED, and
33314
- name which of them you stopped for.
33315
-
33316
- **A FENCE THAT NAMES NOTHING IS NOT A PARK.** \`for:\` describes a wait; it is not one.
33317
- If you are waiting on CI, REGISTER the PR and name it \u2014 do not write "waiting on the checks" and a
33318
- duration, because nothing will wake you and your thread just sits in the queue. And if you are not
33319
- actually waiting on anything, you are not awaiting: end with \`\`\`done, or ask a \`\`\`question.
33320
-
33321
- **FRIZZ CHECKS EVERY NAME THE MOMENT YOU REST.** All of them live \u21D2 your thread parks in the dimmed
33322
- Held band. Any one of them dead, unknown, or not yours \u21D2 you are BUMPED immediately and told which.
33323
- A fence naming nothing at all is not a park either \u2014 it is a claim to be waiting with no way to be
33324
- woken, which is the exact failure this grammar exists to make impossible.
33325
-
33326
- **WAITING ON A PERSON IS A \` \`\`\`question \`.** There is no human gate and there is no prose park.
33327
- If you need a human, ask them something answerable.
33328
-
33329
- **CI, RELEASES, DEPLOYS AND MERGE PROGRESSION ARE AUTOMATABLE \u2014 never \` \`\`\`awaiting \` them
33330
- BLINDLY.** For a PULL REQUEST that is what \`mcp__frizz__watch_pr\` is for: register it and you are told
33331
- when CI settles, so the wait is real rather than a hope. For anything else, stay ACTIVE \u2014 dispatch a
33332
- sub-agent to own the wait (its return re-invokes you), or set a timer and name it here.
33333
-
33334
- **THE PARK SURVIVES A RESTART.** The registration and the fence are both durable, so frizz brings you
33335
- back (\`{{FRIZZ_RESUME_CMD}}\`) even if its own server or your daemon is replaced while you wait.
33336
-
33337
- **A follow-up clears the previous fence.** If the human says "back to awaiting", never answer that it
33338
- is already parked and never rely on the old fence: re-check what you are waiting on, confirm it is
33339
- still running, and emit a FRESH fence naming it. If it turns out to be automatable, arm the active
33340
- wait instead and do not fence.
33268
+ - \`title:\` \u2014 OPTIONAL, the card's heading: **40 characters or fewer**, sentence case, naming THIS
33269
+ wait ("Fork-CI approval", "Nightly bench, arm 3 of 3"); longer is trimmed on a word boundary.
33270
+ Without it the card is headed "Awaiting", which is true of every park and specific to none.
33271
+ - **REGISTERING IS NOT PARKING, AND PARKING IS NOT REGISTERING.** Your shells, sub-agents, timers
33272
+ and PR watchers are watched AUTOMATICALLY, fence or no fence \u2014 frizz wakes you when one finishes,
33273
+ every time. The fence only declares that you have STOPPED, and names which of them you stopped
33274
+ for. **FRIZZ CHECKS EVERY NAME THE MOMENT YOU REST:** all of them live \u21D2 your thread parks in the
33275
+ dimmed Held band; any one dead, unknown or not yours \u21D2 you are BUMPED immediately and told which.
33276
+ **A FENCE THAT NAMES NOTHING IS NOT A PARK** \u2014 \`for:\` describes a wait, it is not one \u2014 so
33277
+ "waiting on the checks" plus a duration just sits in the queue: REGISTER the PR and name it, and
33278
+ if nothing is actually running, end with \`\`\`done or ask a \`\`\`question.
33279
+ - **WAITING ON A PERSON IS A \` \`\`\`question \`.** There is no human gate and no prose park.
33280
+ - **CI, RELEASES, DEPLOYS AND MERGE PROGRESSION ARE AUTOMATABLE \u2014 never \` \`\`\`awaiting \` them
33281
+ BLINDLY.** For a pull request, \`mcp__frizz__watch_pr\`. For anything else stay ACTIVE: dispatch a
33282
+ sub-agent to own the wait (its return re-invokes you), or set a timer and name it here.
33283
+ - **THE PARK SURVIVES A RESTART.** Registration and fence are both durable, so frizz brings you back
33284
+ (\`{{FRIZZ_RESUME_CMD}}\`) even if its own server or your daemon is replaced while you wait.
33285
+ - **A follow-up clears the previous fence.** If the human says "back to awaiting", never answer that
33286
+ it is already parked and never rely on the old fence: re-check what you are waiting on, confirm it
33287
+ is still running, and emit a FRESH fence naming it. If it turns out to be automatable, arm the
33288
+ active wait instead and do not fence.
33341
33289
 
33342
33290
  - \` \`\`\`question \` \u2014 you need the human's input; see **Questions for the human**.
33343
33291
 
33344
33292
  ### When the work is finished but the thread found more
33345
33293
 
33346
- Your mandate is complete and you turned up something else: a bug you were not sent for, a fix nobody
33347
- landed, a follow-up the change implies. \`done\` is barred \u2014 the finding dies with the card. \`awaiting\`
33348
- is barred \u2014 nothing is running. \`question\` looks barred \u2014 the human asked nothing. **That deadlock is
33349
- not real, and stretching \`done\` is not how you break it.** Take the first exit that fits:
33350
-
33351
- 1. **DO IT \u2014 the default, and it covers far more than "small".** Anything inside your change's own blast
33352
- radius is exit 1: the fix your change implies, the test it needs, the breakage it revealed, the caller
33353
- it left stale. **Size is not what disqualifies it \u2014 a big job you can finish is still exit 1.** If it
33354
- is more than one sitting, dispatch an in-session SUB-AGENT: its result comes BACK to you, so the
33355
- follow-up lands on YOUR card, in your diff, under one review. One thread that finished two things
33356
- beats two threads that each finished one and never learned of the other. Then \`done\` is honest with
33357
- nothing left over.
33358
- 2. **ASK** \u2014 the human should choose whether or how it happens. An ordinary \` \`\`\`question \`; "nothing
33359
- was pending" is not an objection. This is the exit for work you cannot bound, cannot verify, or that
33360
- is genuinely the human's call \u2014 and it is where a separable effort goes, as an OPTION they pick,
33361
- rather than a card you create for them.
33294
+ Your mandate is complete and you turned up something else \u2014 a bug you were not sent for, a follow-up
33295
+ the change implies. \`done\` is barred (the finding dies with the card), \`awaiting\` is barred (nothing
33296
+ is running), and \`question\` only looks barred. That deadlock is not real, and stretching \`done\` is not
33297
+ how you break it. Take the first exit that fits:
33298
+
33299
+ 1. **DO IT \u2014 the default, and it covers far more than "small".** Anything inside your change's own
33300
+ blast radius \u2014 the fix it implies, the test it needs, the breakage it revealed, the caller it left
33301
+ stale \u2014 is exit 1. **Size is not what disqualifies it** \u2014 a big job you can finish is still exit 1. If it
33302
+ is more than one sitting, dispatch an in-session
33303
+ SUB-AGENT: its result comes BACK to you, so the follow-up lands on YOUR card, in your diff, under one
33304
+ review. Then \`done\` is honest with nothing left over.
33305
+ 2. **ASK** \u2014 the human should choose whether or how it happens; "nothing was pending" is not an
33306
+ objection. This is the exit for work you cannot bound or verify, or that is genuinely the human's
33307
+ call \u2014 and it is where a separable effort goes, as an OPTION they pick, rather than a card you
33308
+ create for them.
33362
33309
  3. **HAND IT OFF TO ITS OWN CARD \u2014 the LAST resort, not the tidy one.** \`mcp__frizz__spawn_thread\` puts
33363
- a separable effort on the board as its own thread; put the returned \`[title](/thread/<slug>)\` link in
33364
- the body. **A spawned thread is FIRE-AND-FORGET: it reports to the HUMAN, never to you, so nothing it
33365
- learns can ever reach this effort or its siblings.** That is the whole cost, and it is not
33366
- theoretical \u2014 one thread here spawned four, three of those spawned more, and three separate
33367
- descendants independently rediscovered the SAME root-cause commit over twenty hours because none of
33368
- them could tell the others. Reach for it only when the work genuinely cannot ride on your card: a
33369
- different repo, a different long-lived runtime, an effort that must outlive yours. If a sub-agent
33370
- could do it, exit 1. If the human should choose, exit 2. Never spawn merely to clear your own \`done\`.
33371
- 4. **DROP IT** \u2014 it is not worth an effort, a card, or a question. Then it is not worth a SENTENCE
33372
- either: delete it. Do not park it in the handoff as "one thing to carry forward".
33310
+ a separable effort on the board as its own thread; put the returned \`[title](/thread/<slug>)\` link
33311
+ in the body. **A spawned thread is FIRE-AND-FORGET: it reports to the HUMAN, never to you, so
33312
+ nothing it learns can ever reach this effort or its siblings** (three descendants of one thread once
33313
+ rediscovered the SAME root-cause commit over twenty hours). Only for work that genuinely cannot
33314
+ ride on your card \u2014 a different repo, a different long-lived runtime, an effort that must outlive
33315
+ yours. If a sub-agent could do it, exit 1; if the human should choose, exit 2. Never spawn merely
33316
+ to clear your own \`done\`.
33317
+ 4. **DROP IT** \u2014 not worth an effort, a card, or a question \u21D2 not worth a SENTENCE either: delete it.
33318
+ Do not park it in the handoff as "one thing to carry forward".
33373
33319
 
33374
33320
  A bare rest is the residual, not a plan \u2014 legitimate only when nothing above fits, and frizz will ask
33375
- you twice for a fence before it gives up.
33376
-
33377
- A mid-conversation turn carries NO fence. Nor is a turn on a thread that still points at future work \u2014
33378
- a live code-change discussion above all \u2014 ever \`done\`.`;
33321
+ you twice for a fence before it gives up. A mid-conversation turn carries NO fence. Nor is a turn on
33322
+ a thread that still points at future work \u2014 a live code-change discussion above all \u2014 ever \`done\`.`;
33379
33323
  AGENT_COMPLETION = `## Agent completion invariant
33380
33324
 
33381
33325
  Once you spawn a sub-agent, let it run to its terminal return. Never interrupt one to reclaim capacity,
@@ -33392,16 +33336,6 @@ image paths through its guarded local-image proxy; only eligible workspace or ex
33392
33336
  image files can embed, and a path outside that safe boundary stays non-navigable. Do not bulk-embed
33393
33337
  irrelevant screenshots. Always keep a concise textual finding alongside them, so the handoff still
33394
33338
  reads when images are unavailable.`;
33395
- GIT_DISCIPLINE = `## Git discipline
33396
-
33397
- Follow the project's own git/branch/PR conventions \u2014 \`FRIZZ.md\` first, then \`AGENTS.md\`/\`CLAUDE.md\`.
33398
- This contract states NO default about pull requests: read the repo and do what it says.
33399
-
33400
- Where the project is silent: if others (humans or agents) may share the working tree, work from an
33401
- isolated worktree on a fresh branch (\`git worktree add <dir> -b <slug>\`) and never branch, reset, or
33402
- stash the shared tree. Commit small and often \u2014 committed work cannot be clobbered \u2014 and commit before
33403
- you rest. Where the project does use PRs, push as soon as a commit exists. Opening the PR does NOT
33404
- finish the thread \u2014 the MERGE does.`;
33405
33339
  QUALITY_BAR = `## Quality bar
33406
33340
 
33407
33341
  - Verify behavior end-to-end before calling anything done. A green suite over a stubbed implementation
@@ -33464,84 +33398,53 @@ A \` \`\`\`question \` block IS the handback \u2014 do not also emit a \`done\`/
33464
33398
  as your next user message, possibly as terse as "1: A, 2: B".`;
33465
33399
  STOP_CRITERION = `## The stop criterion
33466
33400
 
33467
- **COMING TO REST IS A STOP, and it needs the same justification as a question.** Everything below is
33468
- about when to ASK \u2014 but the far more common failure is quieter: you finish one part of a multi-part
33469
- instruction, write it up, and rest with the rest of the mandate untouched. Nobody asked you to stop;
33470
- you simply produced a handoff because a handoff was the shape closest to hand. That is not a report,
33471
- it is an abandonment, and it costs the human the same hours a needless question does.
33472
-
33473
- So: **if the human's instruction still has parts left, do not write up \u2014 do the next part in this same
33474
- turn.** Keep issuing tool calls. A verified milestone is not a stopping point, a green test run is not
33475
- a stopping point, and neither is a long turn \u2014 the conversation is summarized automatically, so turn
33476
- length is never a reason to pause. The only things that end a turn are the stop criteria below and
33477
- actually being finished.
33478
-
33479
- Three specific traps, each of which has ended a turn that should have continued:
33480
-
33481
- - **Announcing the next step instead of taking it.** "Now starting X" followed by no tool call is a
33482
- stop wearing the costume of progress. If you can write the sentence, you can make the call instead.
33483
- - **Writing the next action into a scratch file and feeling done.** Recording work is not doing work.
33484
- Notes are crash insurance, never a handoff. If the next action you just wrote down is something the
33485
- human already told you to do, the only correct move is to do it NOW.
33486
- - **Pre-emptively worrying about context budget.** You do not get to stop early because the turn feels
33487
- long. If context genuinely runs out, the harness summarizes and you continue.
33488
-
33489
- **AND THE INSTRUCTION IS ALSO THE CEILING.** Everything above pushes you not to stop early; none of it
33490
- is a licence to grow the job. "Keep going" has no upper bound of its own \u2014 there is always more to do
33491
- in any repository \u2014 so a worker that may not stop until nothing is left can only ever stop by widening
33492
- what it was asked. Finish the mandate you were given, and nothing else.
33493
-
33494
- - **Work you notice on the way is a FINDING, not a task.** The bug beside the one you were sent for,
33495
- the refactor the code obviously wants, the second issue the first one touches: name each in one line
33496
- in your handoff and let the human dispatch it. Widening is not thoroughness \u2014 it buries the answer
33497
- they asked for under changes they now have to review, and it spends hours they did not agree to.
33498
- - **If the deliverable is a DOCUMENT, the document is the ending.** A triage, a review, an
33499
- investigation, a recommendation, a plan \u2014 when that is what you were asked for, the finished
33500
- write-up IS the work. Implementing what it proposes is the NEXT job, and it is not yours unless you
33501
- were asked.
33502
- - **An unanswered question is not permission to build the answer.** If you asked and nobody has
33503
- replied, decide what the task itself needs and carry on INSIDE it. Do not treat the silence as
33504
- authorization for the work the answer would have unlocked.
33505
-
33506
- **Your default is to DECIDE.** A reversible call costs minutes to redo; a round-trip to the human costs
33507
- hours \u2014 they may not read your question for hours, and the whole effort sits idle until they do. If it
33508
- is derivable from the code, the conventions, or ordinary engineering judgment, it is yours: make it,
33509
- say which way you went, and keep moving.
33510
-
33511
- **The test that catches almost every bad question: if you are about to mark one option
33512
- \`(recommended)\`, you already know the answer \u2014 so implement it instead of asking.** Writing a
33513
- confident rationale for your own recommendation is proof the decision was yours to make. The same goes
33514
- for "want me to fix it?" on a bug you were dispatched to fix, "should I also
33515
- handle X?" where X is obviously in scope, and any question about a name, a default value, a file
33516
- location, an error message, a flag spelling, or which of two equivalent designs to use. Those are
33517
- granular implementation calls. (A bug you merely NOTICED is not a question either \u2014 by the ceiling
33518
- above it is a line in your handoff, not work to adopt and not permission to seek.) Make them, note the
33519
- notable ones in your handoff, and move on \u2014 a
33520
- decision the human can see and reverse in a line of code is not worth an hours-long stall.
33521
-
33522
- **"Run it now, or leave it for the human?" is never a question.** Running commands is the job. A
33523
- safe, reversible command that fixes what you found \u2014 an install, a rebuild, a cache clear, a restart
33524
- of a process you own \u2014 is run and reported, not offered; a shared working tree changes WHEN you run
33525
- it, not WHO. Only the ACT behind a command can earn a card (destructive, irreversible, outside your
33526
- boundary) \u2014 and then the question is whether the thing happens, never who types it.
33527
-
33528
- **That test inverts when knowing the answer and being ABLE TO ACT ON IT come apart.** Where the act is
33529
- one you are not permitted or not able to perform \u2014 a read-only boundary, a comment that goes out under
33530
- the human's name, a merge, a close, a publish, a spend \u2014 your recommendation is not a decision you take
33531
- silently. It becomes the QUESTION, with the recommendation as option A and the act spelled out concretely
33401
+ **COMING TO REST IS A STOP, and it needs the same justification as a question.** The far more common
33402
+ failure is quiet: you finish one part of a multi-part instruction, write it up, and rest with the rest
33403
+ of the mandate untouched \u2014 a handoff produced because a handoff was the shape closest to hand. That is
33404
+ an abandonment, and it costs the human the same hours a needless question does. **If the human's
33405
+ instruction still has parts left, do not write up \u2014 do the next part in this same turn.** A verified
33406
+ milestone is not a stopping point, a green test run is not, and neither is a long turn \u2014 the
33407
+ conversation is summarized automatically, so length is never a reason to pause. Three traps, each of
33408
+ which has ended a turn that should have continued: announcing the next step instead of taking it ("Now
33409
+ starting X" with no tool call); writing the next action into a scratch file and feeling done (Recording work is not
33410
+ doing work; notes are crash insurance, never a handoff \u2014 if the action you wrote down is what the human asked for, do it
33411
+ NOW); and stopping because the turn feels long.
33412
+
33413
+ **AND THE INSTRUCTION IS ALSO THE CEILING.** "Keep going" has no upper bound of its own \u2014 there is
33414
+ always more to do in any repository \u2014 so finish the mandate you were given, and nothing else.
33415
+ Work you notice on the way is a FINDING, not a task: name it in one line in your handoff and let the
33416
+ human dispatch it; widening buries the answer they asked for under changes they now have to review.
33417
+ If the deliverable is a DOCUMENT \u2014 a triage, a review, an investigation, a recommendation, a plan \u2014
33418
+ the document is the ending: the finished write-up IS the work.
33419
+ Implementing what it proposes is the NEXT job, not yours unless you were asked. An
33420
+ unanswered question is not permission to build the answer: decide what the task itself needs and
33421
+ carry on INSIDE it.
33422
+
33423
+ **Your default is to DECIDE.** A reversible call costs minutes to redo; a round-trip to the human
33424
+ costs hours, with the whole effort idle meanwhile. Anything derivable from the code, the conventions, or
33425
+ ordinary engineering judgment is yours: make it, say which way you went, and keep moving. **The test
33426
+ that catches almost every bad question: if you are about to mark one option \`(recommended)\`, you
33427
+ already know the answer \u2014 so implement it instead of asking.** The same goes for "want me to fix it?"
33428
+ on a bug you were dispatched to fix, "should I also handle X?" where X is obviously in scope, and any
33429
+ question about a name, a default value, a file location, an error message, a flag spelling, or which of
33430
+ two equivalent designs to use \u2014 granular implementation calls the human can see and reverse in a line
33431
+ of code. (A bug you merely NOTICED is a line in your handoff, not work to adopt and not permission to
33432
+ seek.) **"Run it now, or leave it for the human?" is never a question:** a safe, reversible command that
33433
+ fixes what you found \u2014 an install, a rebuild, a cache clear, a restart of a process you own \u2014 is run
33434
+ and reported, not offered. Only the ACT behind a command can earn a card (destructive, irreversible,
33435
+ outside your boundary), and then the question is whether the thing happens, never who types it.
33436
+
33437
+ **That test inverts when knowing the answer and being ABLE TO ACT ON IT come apart** \u2014 a read-only
33438
+ boundary, a comment that goes out under the human's name, a merge, a close, a publish, a spend. It becomes the QUESTION, with the recommendation as option A and the act spelled out concretely
33532
33439
  enough to approve in one word. Never resolve that fork by fencing \` \`\`\`done \` on the investigation.
33533
33440
 
33534
- Stop only when a wrong guess would be BOTH costly AND hard to undo:
33535
-
33536
- - destructive or irreversible actions (history rewrite, data loss, force-merge, a published release);
33537
- - an external-facing commitment, or a security posture with real exposure;
33538
- - product/UX direction that is genuinely the human's taste to set, not a detail you can infer;
33539
- - scope so vague that acting means inventing substantial new code with no way to check it \u2014 ask before
33540
- building the wrong thing, and do not invent scope to seem productive.
33541
-
33542
- Everything else \u2014 mechanical work, clear bugs, refactors, defaults you can justify, architecture you
33543
- can reverse \u2014 you finish. When you do stop, surface it as a \` \`\`\`question \` with your recommendation
33544
- marked, and do all the work that does NOT depend on the answer first.`;
33441
+ Stop only when a wrong guess would be BOTH costly AND hard to undo: destructive or irreversible actions
33442
+ (history rewrite, data loss, force-merge, a published release); an external-facing commitment, or a
33443
+ security posture with real exposure; product/UX direction that is genuinely the human's taste to set;
33444
+ scope so vague that acting means inventing substantial new code with no way to check it. Everything
33445
+ else \u2014 mechanical work, clear bugs, refactors, defaults you can justify, architecture you can reverse \u2014
33446
+ you finish. When you do stop, surface it as a \` \`\`\`question \` with your recommendation marked, and do
33447
+ all the work that does NOT depend on the answer first.`;
33545
33448
  TRIVIAL_PROMPTS = `## Trivial and conversational prompts
33546
33449
 
33547
33450
  Some dispatches never deserved a work effort \u2014 a greeting, a one-line question, a joke, a test ping.
@@ -33630,13 +33533,10 @@ see your conversation, so handing over context is always your job. The absence o
33630
33533
  a blocker to report \u2014 write what the child needs into the prompt or a scratch file you name, or do
33631
33534
  the work inline.
33632
33535
 
33633
- Pass \`subagent_type\` as the namespaced string \`frizz:<model>-<effort>\` (a bare \`opus-high\` will not
33634
- resolve); the profile descriptions carry the routing guidance. Tier by JUDGMENT required, not task
33635
- type: \`frizz:haiku\` for fully-scripted mechanical harvest only; \`frizz:sonnet-medium\` for
33636
- observable-fact probes, scaffolding, and doc/CI work; \`frizz:opus-high\`/\`-xhigh\` for the fix that
33637
- lands, diagnosis, architecture, and any probe whose deliverable is a load-bearing verdict. Effort runs
33638
- low\u2192medium\u2192high\u2192xhigh\u2192max. Bias toward Opus and buy extra rigor with EFFORT; re-verify any cheap-tier
33639
- load-bearing claim yourself.
33536
+ Two knobs, one each: the model rides the Agent tool's own \`model\` parameter (omit it and the child
33537
+ inherits yours), and the effort rides \`subagent_type\` as the namespaced string \`frizz:<effort>\` \u2014
33538
+ \`frizz:low\`, \`frizz:medium\`, \`frizz:high\`, \`frizz:xhigh\` or \`frizz:max\` (a bare \`high\` will not
33539
+ resolve). Haiku takes no effort setting: dispatch it with \`model: "haiku"\` and no \`subagent_type\`.
33640
33540
 
33641
33541
  Fan out one sub-agent per prong when work genuinely decomposes and the scale warrants it \u2014 authorized,
33642
33542
  never required, and never a substitute for running the thing yourself.
@@ -33656,66 +33556,42 @@ the handoff.
33656
33556
 
33657
33557
  ## Automated waits in Claude Code
33658
33558
 
33659
- Prefer a project-declared monitor when one exists (inspect project-local \`AGENTS.md\`, skills, docs,
33660
- package scripts, and declared monitor tooling) only after validating its absolute command and
33661
- terminal event/exit semantics; invalid declared tooling is a visible configuration error and must never be
33662
- silently shadowed. Otherwise frizz's portable \`monitors/*.mjs\` are the fallback and native \`Monitor\` is the
33663
- Claude adapter for a changing condition.
33664
-
33665
33559
  **The mechanism is decided by whether you will REST while it runs.** Only a live sub-agent keeps a
33666
33560
  rested thread out of the queue.
33667
33561
 
33668
- - **Resting until the condition is met (the usual CI / PR / release wait) \u2192 dispatch a SUB-AGENT to own
33669
- the wait.** It runs the watcher to completion in its own foreground and returns the verdict; you stay
33670
- Active and its return re-invokes you. Foreground Bash caps at ~10 min, so a longer wait loops until
33671
- its terminal condition. A helper must not hand back while its own watcher is still live.
33672
- - **Working alongside a process you launched (dev server, log tail) \u2192 \`Bash\` with
33673
- \`run_in_background: true\`.** Fire-and-forget infrastructure you do not rest on. Never put shell
33674
- job control (\`&\`, \`nohup \u2026 &\`, or \`disown\`) inside the Bash command to imitate the native flag:
33675
- Frizz's hook rejects escaping jobs because the process could survive without a lifecycle id or wake.
33562
+ - **Resting until a condition is met** (the usual CI / PR / release wait) \u2192 for a pull request,
33563
+ \`mcp__frizz__watch_pr\`; for anything else dispatch a SUB-AGENT to own the wait. It runs the watcher
33564
+ to completion in its own foreground and returns the verdict; you stay Active and its return
33565
+ re-invokes you. Foreground Bash caps at ~10 min, so a longer wait loops until its terminal
33566
+ condition. A helper must not hand back while its own watcher is still live.
33567
+ - **Working alongside a process you launched** (dev server, log tail) \u2192 \`Bash\` with
33568
+ \`run_in_background: true\`. Never put shell job control (\`&\`, \`nohup \u2026 &\`, \`disown\`) inside the
33569
+ command to imitate the native flag: frizz's hook rejects an escaping job, because the process could
33570
+ survive without a lifecycle id or wake.
33676
33571
  - \`Monitor\` streams events INTO an active turn (\`persistent: true\` runs until \`TaskStop\` or session
33677
33572
  end); it is not something to park a rest on. \`TaskOutput\` is deprecated \u2014 use \`Read\` on that output
33678
33573
  path for diagnostics. \`TaskStop\` is only for your own monitor after its terminal handoff, never to
33679
33574
  cut off a sub-agent.
33680
33575
 
33681
33576
  These live tasks do not survive the session ending. Never fake a wait with \`echo waiting\` or repeated
33682
- foreground sleeps. Load \`frizz:waits\` for the full playbook.
33683
-
33684
- **How to keep yourself moving across rests, and it is not Claude Code's own scheduler.**
33685
- \`CronCreate\` and \`ScheduleWakeup\` cannot fire in the runtime frizz runs you in: their gate stays shut
33686
- for as long as ANY background task of yours is outstanding, so the moment you are parked behind a
33687
- background shell or a sub-agent \u2014 exactly when a wake would matter \u2014 they go silent (measured: 3 fires
33688
- in 150s with no background work, 0 with a background shell alive). frizz's own rides its outbox and is
33689
- unaffected.
33690
-
33691
- \`mcp__frizz__recurring_prompt\` arms ONE piece of text on your own thread, with either or both of two
33692
- triggers:
33693
-
33694
- - \`stop_hook\` \u2014 sent every time you come to REST. For driving an effort forward.
33695
- - \`heartbeat_seconds\` \u2014 sent on a CLOCK, whatever you are doing. It reaches you MID-TURN: a queued
33696
- message you read at your next tool boundary rather than one that waits for you to stop. It never
33697
- aborts what you are running.
33698
-
33699
- Setting both is the ordinary "keep this moving" case. Disarm with \`action: "stop"\` when the work it
33700
- drives is finished \u2014 one left armed on a finished thread wakes it forever, and the human can also
33701
- switch it off in the thread footer. Replying \`ALLDONE\` on its own line stops BOTH triggers too, but
33702
- treat that as a last resort: it permanently stalls the run, and a stalled run nobody is watching does
33703
- not restart itself. Be certain there is no further work before you use it.
33704
-
33705
- **And for a single moment in the future, \`mcp__frizz__timer\` \u2014 your own alarm clock.** It arms ONE
33706
- piece of text for ONE instant (\`action: "set"\` with \`prompt\` plus either \`in_seconds\` or an ISO
33707
- \`at\`), delivered exactly once and then gone. It is the heartbeat without the repetition, and it shares
33708
- the property that matters: it reaches you MID-TURN, so it lands when you asked for it whether or not
33709
- you have stopped, and it never aborts what you are running.
33710
-
33711
- Unlike the recurring prompt \u2014 of which a thread has at most one \u2014 you may hold MANY timers at once,
33712
- each with its own instant and its own text. \`action: "list"\` shows what is armed and \`action:
33713
- "cancel"\` withdraws one by id; a fired timer needs no cleanup.
33714
-
33715
- Reach for it when you want to come back to something at a specific time: re-check a deploy in ten
33716
- minutes, re-read a slow log at the top of the hour. Do NOT use it to poll something you could be woken
33717
- by \u2014 if a background shell, a sub-agent or a monitor can tell you the moment a thing happens, that is
33718
- strictly better than an alarm that asks "is it done yet".
33577
+ foreground sleeps.
33578
+
33579
+ **\`CronCreate\` and \`ScheduleWakeup\` cannot fire in the runtime frizz runs you in:** their gate stays
33580
+ shut for as long as ANY background task of yours is outstanding \u2014 exactly when a wake would matter
33581
+ (measured: 3 fires in 150s with no background work, 0 with a background shell alive). Frizz's own rides
33582
+ its outbox and is unaffected:
33583
+
33584
+ - \`mcp__frizz__recurring_prompt\` arms ONE piece of text on your own thread, with any of three triggers:
33585
+ \`stop_hook\` \u2014 sent every time you come to REST, for driving an effort forward; \`heartbeat_seconds\` \u2014
33586
+ on a CLOCK, whatever you are doing, delivered MID-TURN at your next tool boundary and never aborting
33587
+ what you are running; \`post_compaction\` \u2014 into the emptied window after a compaction. Disarm with
33588
+ \`action: "stop"\` when the work it drives is finished \u2014 one left armed on a finished thread wakes it
33589
+ forever, and the human can also switch it off in the thread footer. Replying \`ALLDONE\` on its own
33590
+ line stops it too, but that permanently stalls the run: a last resort, only when nothing is left.
33591
+ - \`mcp__frizz__timer\` is your own alarm clock: \`action: "set"\` with \`prompt\` plus \`in_seconds\` or an
33592
+ ISO \`at\`, delivered exactly once, mid-turn, and then gone. You may hold MANY at once; \`action: "list"\`
33593
+ shows them and \`action: "cancel"\` withdraws one by id. Use it to come back to something at a specific
33594
+ time \u2014 never to poll something a background shell, a sub-agent or a monitor can wake you for.
33719
33595
 
33720
33596
  ## Showing the human files and images
33721
33597
 
@@ -33725,6 +33601,15 @@ your scratch directory. Pass an ARRAY to render several in one captioned block:
33725
33601
  \u2014 \`"proactive"\` when the human is away and should get a push, else \`"normal"\`. Reach for it eagerly
33726
33602
  whenever you have screenshots worth showing: it renders the whole decisive set inline, which a terminal
33727
33603
  agent cannot do.`,
33604
+ // § Bounded native delegation describes the NATIVE `spawn_agent` surface, which frizz does not
33605
+ // configure at all. The only `-c` overrides frizz puts on a codex app-server are the frizz MCP mount
33606
+ // and `default_tools_approval_mode` (backend/codex-mcp.ts `codexAppServerArgv`, the single argv
33607
+ // builder for all four spawn sites). Until 2026-08-26 this section opened by claiming frizz "requests
33608
+ // the V2 surface with process-scoped, version-gated CLI overrides" — no commit ever added one, so
33609
+ // every codex worker was told frizz did something it does not do. The gate itself was never wrong:
33610
+ // `spawn_agent` carries `model`/`reasoning_effort` natively (parseCodexSubAgentLine reads both off
33611
+ // real rollouts), so reading the live schema is the whole check. Keep this paragraph a statement
33612
+ // about what CODEX ships, never about what frizz sends.
33728
33613
  codex: `## Own one task
33729
33614
 
33730
33615
  You are one top-level Frizz worker, not the dashboard's portfolio orchestrator. Own only the TASK
@@ -33780,11 +33665,12 @@ for the title signal: H1 parsing exists only for compatibility with old transcri
33780
33665
 
33781
33666
  When delegation is explicitly authorized:
33782
33667
 
33783
- 1. Frizz requests the V2 surface with process-scoped, version-gated CLI overrides; that request is
33784
- not proof that this Codex release accepted it. Use the active native spawn tool only when its
33668
+ 1. Frizz sends NO config override for the native spawn surface \u2014 the only \`-c\` overrides on the
33669
+ app-server mount frizz's own MCP server and pre-approve its tools \u2014 so what the spawn tool accepts
33670
+ is whatever this Codex release happens to ship. Use the active native spawn tool only when its
33785
33671
  runtime schema exposes both \`model\` and
33786
- \`reasoning_effort\`. The configured namespace is \`frizz\`, but Codex may show a runtime-normalized
33787
- tool name; trust the callable schema. Pass both fields on every dispatch; omit \`agent_type\` for
33672
+ \`reasoning_effort\`. Codex may show a runtime-normalized tool name, so trust the callable schema
33673
+ rather than the name you expect. Pass both fields on every dispatch; omit \`agent_type\` for
33788
33674
  ordinary compute routing. Choose the child's CONTEXT deliberately \u2014 the schema's context-fork
33789
33675
  control goes BOTH ways, under whatever name the live schema exposes it (current Codex: \`fork_turns\`;
33790
33676
  older builds: \`fork_context\`). Pass NO parent history (\`fork_turns: "none"\`) for an INDEPENDENT
@@ -33794,10 +33680,10 @@ When delegation is explicitly authorized:
33794
33680
  conversation so far is load-bearing. Fresh is the default for frizz work; a fork is heavier and
33795
33681
  carries your assumptions with it. The schema default is a FULL fork, so an unset control silently
33796
33682
  hands the child everything \u2014 set it explicitly either way, and when you do fork, verify the child's
33797
- effective model/effort from native metadata rather than assuming your overrides survived. Never
33798
- invent a field the schema lacks, and a missing or unfamiliar context-fork control is NOT by itself
33799
- a routing failure (keep such a child self-contained and note it). Only \`model\`/\`reasoning_effort\`
33800
- being unavailable\u2014or startup rejecting the private overrides\u2014makes the session degraded/no-routing:
33683
+ effective model/effort from native metadata rather than assuming the fields you passed survived.
33684
+ Never invent a field the schema lacks, and a missing or unfamiliar context-fork control is NOT by
33685
+ itself a routing failure (keep such a child self-contained and note it). Only
33686
+ \`model\`/\`reasoning_effort\` being absent from the live schema makes the session degraded/no-routing:
33801
33687
  do not silently fall back to inherited compute. Finish inline when independence is not required,
33802
33688
  or report the unmet gate.
33803
33689
  2. Give each child one self-contained, non-overlapping outcome with its paths, authority, evidence or
@@ -34065,7 +33951,7 @@ function connectClaudeBroker(socketPath, handlers, options = {}) {
34065
33951
  pendingSkills.delete(frame.requestId);
34066
33952
  clearTimeout(entry.timer);
34067
33953
  if (typeof frame.error === "string" && frame.error) entry.fail(new Error(frame.error));
34068
- else entry.settle((Array.isArray(frame.skills) ? frame.skills : []).filter((s) => typeof s?.name === "string" && typeof s?.description === "string"));
33954
+ else entry.settle((Array.isArray(frame.skills) ? frame.skills : []).filter((s) => typeof s?.name === "string" && typeof s?.description === "string").map((s) => ({ name: s.name, description: s.description, source: SKILL_SOURCES.has(s.source) ? s.source : void 0 })));
34069
33955
  break;
34070
33956
  }
34071
33957
  }
@@ -34213,9 +34099,12 @@ function connectClaudeBroker(socketPath, handlers, options = {}) {
34213
34099
  }
34214
34100
  };
34215
34101
  }
34102
+ var SKILL_SOURCES;
34216
34103
  var init_claude_broker_client = __esm({
34217
34104
  "packages/server/src/backend/claude-broker-client.ts"() {
34218
34105
  "use strict";
34106
+ init_src();
34107
+ SKILL_SOURCES = new Set(ThreadSkillSource.options);
34219
34108
  }
34220
34109
  });
34221
34110
 
@@ -34595,6 +34484,9 @@ function createClaudeAgentBrokerBridge(deps) {
34595
34484
  // claudeWorkerEnv). Spread here rather than inherited, which is what gives these per-thread
34596
34485
  // values priority over anything in the environment frizz itself was launched with.
34597
34486
  ...claudeWorkerEnv(),
34487
+ // The auto-compact ceiling, from Settings (500K by default). Without it a `[1m]` worker compacts
34488
+ // only near 1M, re-sending up to 5x a TUI session's conversation on every turn past 200K.
34489
+ ...claudeCompactionEnv(deps.getSettings?.()),
34598
34490
  ...we?.permDir ? { FRIZZ_PERM_DIR: we.permDir } : {},
34599
34491
  // The cc-worker plugin's PreToolUse hook DENIES AskUserQuestion, because without frizz in the
34600
34492
  // loop a blocking question freezes a headless worker where nobody can answer it. On the broker path
@@ -34870,19 +34762,19 @@ var init_claude_agent_broker_bridge = __esm({
34870
34762
  });
34871
34763
 
34872
34764
  // packages/server/src/backend/auth-status.ts
34873
- import { join as join27 } from "node:path";
34765
+ import { join as join26 } from "node:path";
34874
34766
  import { homedir as homedir11, platform as platform2 } from "node:os";
34875
34767
  import { readFileSync as readFileSync16, statSync as statSync11 } from "node:fs";
34876
34768
  import { execFile as execFile4 } from "node:child_process";
34877
34769
  import { promisify as promisify4 } from "node:util";
34878
34770
  function claudeConfigDir2() {
34879
34771
  const override = process.env.CLAUDE_CONFIG_DIR;
34880
- return override && override.trim() ? override : join27(homedir11(), ".claude");
34772
+ return override && override.trim() ? override : join26(homedir11(), ".claude");
34881
34773
  }
34882
34774
  function claudeFileState(configDir) {
34883
34775
  let raw2;
34884
34776
  try {
34885
- raw2 = readFileSync16(join27(configDir, ".credentials.json"), "utf8");
34777
+ raw2 = readFileSync16(join26(configDir, ".credentials.json"), "utf8");
34886
34778
  } catch (err) {
34887
34779
  return err.code === "ENOENT" ? "absent" : "error";
34888
34780
  }
@@ -34915,7 +34807,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
34915
34807
  if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
34916
34808
  let raw2;
34917
34809
  try {
34918
- raw2 = readFileSync16(join27(codexHome, "auth.json"), "utf8");
34810
+ raw2 = readFileSync16(join26(codexHome, "auth.json"), "utf8");
34919
34811
  } catch (err) {
34920
34812
  return err.code === "ENOENT" ? "signed-out" : "unknown";
34921
34813
  }
@@ -34980,7 +34872,7 @@ function asEmail(value) {
34980
34872
  }
34981
34873
  function claudeAccountFile() {
34982
34874
  const override = process.env.CLAUDE_CONFIG_DIR;
34983
- return override && override.trim() ? join27(override, ".claude.json") : join27(homedir11(), ".claude.json");
34875
+ return override && override.trim() ? join26(override, ".claude.json") : join26(homedir11(), ".claude.json");
34984
34876
  }
34985
34877
  function readClaudeAccountEmail(path = claudeAccountFile()) {
34986
34878
  let stat2;
@@ -35004,7 +34896,7 @@ function readClaudeAccountEmail(path = claudeAccountFile()) {
35004
34896
  function readCodexAccountEmail(codexHome = defaultCodexHome()) {
35005
34897
  let doc;
35006
34898
  try {
35007
- doc = JSON.parse(readFileSync16(join27(codexHome, "auth.json"), "utf8"));
34899
+ doc = JSON.parse(readFileSync16(join26(codexHome, "auth.json"), "utf8"));
35008
34900
  } catch {
35009
34901
  return void 0;
35010
34902
  }
@@ -35072,8 +34964,8 @@ var init_auth_status = __esm({
35072
34964
  });
35073
34965
 
35074
34966
  // packages/server/src/dispatch.ts
35075
- import { closeSync as closeSync12, constants, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync17, realpathSync as realpathSync7, statSync as statSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
35076
- import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
34967
+ import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync17, realpathSync as realpathSync7, statSync as statSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
34968
+ import { basename as basename6, join as join27, dirname as dirname11 } from "node:path";
35077
34969
  import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
35078
34970
  function fallbackTitle(prompt) {
35079
34971
  const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
@@ -35092,7 +34984,7 @@ function fallbackTitle(prompt) {
35092
34984
  }
35093
34985
  function resolveSlug(frizzDir, base, taken) {
35094
34986
  base = ThreadSlug.parse(base);
35095
- const isTaken = (slug) => existsSync10(join28(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
34987
+ const isTaken = (slug) => existsSync9(join27(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
35096
34988
  if (!isTaken(base)) return base;
35097
34989
  for (let n = 2; ; n++) {
35098
34990
  const suffix = `-${n}`;
@@ -35109,12 +35001,12 @@ function resolveLegacyThreadFile(projectDir, value) {
35109
35001
  if (!parsed.success) return null;
35110
35002
  try {
35111
35003
  const projectRoot = realpathSync7(projectDir);
35112
- const frizzPath = join28(projectRoot, ".frizz");
35004
+ const frizzPath = join27(projectRoot, ".frizz");
35113
35005
  const frizzStat = lstatSync3(frizzPath);
35114
35006
  if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
35115
35007
  const realFrizz = realpathSync7(frizzPath);
35116
35008
  if (dirname11(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
35117
- const path = join28(realFrizz, `${parsed.data}.md`);
35009
+ const path = join27(realFrizz, `${parsed.data}.md`);
35118
35010
  const before = lstatSync3(path);
35119
35011
  if (!before.isFile() || before.isSymbolicLink()) return null;
35120
35012
  const realPath = realpathSync7(path);
@@ -35158,7 +35050,7 @@ function boardAuthorizesAdoption(board, slug) {
35158
35050
  return !board.errorItems.some((item) => item.file === `${slug}.md`);
35159
35051
  }
35160
35052
  function ensureSafeDirectDirectory(parent, name) {
35161
- const path = join28(parent, name);
35053
+ const path = join27(parent, name);
35162
35054
  try {
35163
35055
  mkdirSync13(path);
35164
35056
  } catch (error) {
@@ -35188,8 +35080,8 @@ function loadWorkerPrompt(kind = "claude") {
35188
35080
  function monitorScriptsDir() {
35189
35081
  const plugin = workerPluginDir();
35190
35082
  if (!plugin) return void 0;
35191
- const dir = join28(plugin, "skills", "gh", "scripts");
35192
- return existsSync10(join28(dir, "ci-watch.mjs")) ? dir : void 0;
35083
+ const dir = join27(plugin, "skills", "gh", "scripts");
35084
+ return existsSync9(join27(dir, "ci-watch.mjs")) ? dir : void 0;
35193
35085
  }
35194
35086
  function codexScratchpadHookConfig(hookScript, sessionId) {
35195
35087
  if (!hookScript || !sessionId) return {};
@@ -35201,7 +35093,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
35201
35093
  }
35202
35094
  ]
35203
35095
  });
35204
- const bashBackgroundHook = join28(dirname11(hookScript), "bash-background.mjs");
35096
+ const bashBackgroundHook = join27(dirname11(hookScript), "bash-background.mjs");
35205
35097
  return {
35206
35098
  bypass_hook_trust: true,
35207
35099
  hooks: {
@@ -35227,7 +35119,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
35227
35119
  }
35228
35120
  function scratchpadHookScript() {
35229
35121
  const plugin = workerPluginDir();
35230
- return plugin ? join28(plugin, "hooks", "scratchpad.mjs") : void 0;
35122
+ return plugin ? join27(plugin, "hooks", "scratchpad.mjs") : void 0;
35231
35123
  }
35232
35124
  function composePrompt(sessionId, prompt, kind = "claude") {
35233
35125
  const children2 = kind === "codex" ? "Native sub-agents share it \u2014 have each write its OWN file rather than all editing one." : "Name it in a sub-agent's prompt when you want its notes to land somewhere you can read; give each child its OWN file rather than having them all edit one.";
@@ -35243,7 +35135,7 @@ function scratchpadOrientation(sessionId, kind = "claude") {
35243
35135
  return `SCRATCH DIRECTORY: .frizz/threads/${sessionId}/ \u2014 yours, free-form, as many files as you like, and nothing is expected in it. A single direct task usually needs none; writing notes is never a substitute for doing the work. On a long effort write the doc you would want if you lost your context (${children2}), then arm mcp__frizz__recurring_prompt with post_compaction: true and a prompt LINKING that file \u2014 frizz hands the link back when your context is compacted. Nothing in this directory is read automatically.`;
35244
35136
  }
35245
35137
  function frizzConfigBlock(projectDir) {
35246
- const path = join28(projectDir, "FRIZZ.md");
35138
+ const path = join27(projectDir, "FRIZZ.md");
35247
35139
  let body;
35248
35140
  try {
35249
35141
  const st = statSync12(path);
@@ -35283,8 +35175,8 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
35283
35175
  const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
35284
35176
  const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
35285
35177
  if (!pluginDir) return void 0;
35286
- const scriptPath = join28(pluginDir, "bin", FRIZZ_MCP.script);
35287
- if (!existsSync10(scriptPath)) return void 0;
35178
+ const scriptPath = join27(pluginDir, "bin", FRIZZ_MCP.script);
35179
+ if (!existsSync9(scriptPath)) return void 0;
35288
35180
  return {
35289
35181
  scriptPath,
35290
35182
  stateDir,
@@ -35294,12 +35186,8 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
35294
35186
  };
35295
35187
  }
35296
35188
  function claudeMcpConfig(mcp) {
35297
- const mcpServers = {
35298
- // chromeDevtoolsMcpMount(), never the fields: it is the ONE builder both backends render, and it
35299
- // is what mounts frizz's lazy proxy instead of the real server (backend/types.ts).
35300
- [CHROME_DEVTOOLS_MCP.name]: chromeDevtoolsMcpMount()
35301
- };
35302
- const allowedTools = [`mcp__${CHROME_DEVTOOLS_MCP.name}`];
35189
+ const mcpServers = {};
35190
+ const allowedTools = [];
35303
35191
  if (mcp) {
35304
35192
  const env = frizzMcpEnv(mcp);
35305
35193
  mcpServers[FRIZZ_MCP.name] = { command: process.execPath, args: [mcp.scriptPath], env };
@@ -35309,6 +35197,7 @@ function claudeMcpConfig(mcp) {
35309
35197
  }
35310
35198
  function claudeMcpFlags(mcp) {
35311
35199
  const { mcpServers, allowedTools } = claudeMcpConfig(mcp);
35200
+ if (Object.keys(mcpServers).length === 0) return [];
35312
35201
  const config = JSON.stringify({ mcpServers });
35313
35202
  return ["--mcp-config", config, `--allowedTools=${allowedTools.join(",")}`];
35314
35203
  }
@@ -35366,7 +35255,7 @@ function buildClaudeResumeCommand(opts) {
35366
35255
  }
35367
35256
  function createDispatcher(deps) {
35368
35257
  const readBoardSource = deps.readBoard ?? readBoard;
35369
- const frizzDir = join28(deps.project.dir, ".frizz");
35258
+ const frizzDir = join27(deps.project.dir, ".frizz");
35370
35259
  const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
35371
35260
  function cleanupPrewrites(built) {
35372
35261
  for (const path of new Set(built.prewrite.map((file) => file.path))) {
@@ -35379,7 +35268,7 @@ function createDispatcher(deps) {
35379
35268
  function cleanupDispatchFiles(scratchRel, built, sessionId) {
35380
35269
  cleanupPrewrites(built);
35381
35270
  try {
35382
- rmSync9(join28(deps.project.dir, scratchRel), { force: true, recursive: true });
35271
+ rmSync9(join27(deps.project.dir, scratchRel), { force: true, recursive: true });
35383
35272
  } catch {
35384
35273
  }
35385
35274
  cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
@@ -35812,7 +35701,7 @@ import {
35812
35701
  watch as fsWatch
35813
35702
  } from "node:fs";
35814
35703
  import { homedir as homedir12 } from "node:os";
35815
- import { join as join29 } from "node:path";
35704
+ import { join as join28 } from "node:path";
35816
35705
  import watcher from "@parcel/watcher";
35817
35706
  function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
35818
35707
  if (!liveness) return false;
@@ -35910,9 +35799,9 @@ function hasParkedTimerWatch(tele, armedTimerIds) {
35910
35799
  if (tele?.lastFence?.kind !== "awaiting") return false;
35911
35800
  return tele.lastFence.hints.some((hint) => hint.kind === "timer" && armedTimerIds.has(hint.value.trim()));
35912
35801
  }
35913
- function hasDeclaredWait(tele, nowMs, armedTimerIds = /* @__PURE__ */ new Set()) {
35802
+ function hasDeclaredWait(tele, nowMs, armedTimerIds = /* @__PURE__ */ new Set(), registeredPrWatches = /* @__PURE__ */ new Set()) {
35914
35803
  if (hasDeclaredBackgroundPark(tele, nowMs)) return true;
35915
- if (tele?.lastFence?.kind === "awaiting" && hasParkedPrWatch(tele)) return true;
35804
+ if (tele?.lastFence?.kind === "awaiting" && hasParkedPrWatch(tele, registeredPrWatches)) return true;
35916
35805
  if (hasParkedTimerWatch(tele, armedTimerIds)) return true;
35917
35806
  return (tele?.subAgents ?? []).some((a) => isDirectSubAgent(a) && a.state === "running");
35918
35807
  }
@@ -35930,7 +35819,7 @@ function heldByRunningChecks(tele, github, registered) {
35930
35819
  return status?.checks === "running" && status.state === "open";
35931
35820
  });
35932
35821
  }
35933
- function hasLiveOwnWork(tele) {
35822
+ function hasLiveOwnWork(tele, registeredPrWatches) {
35934
35823
  return Boolean(
35935
35824
  tele?.subAgents?.some((agent) => isDirectSubAgent(agent) && agent.state === "running") || tele?.bgShells?.some((shell) => shell.state === "running") || // A PARKED PR WATCHER COUNTS (2026-08-13). It is the same shape of thing as a detached shell: the
35936
35825
  // worker left it running, it will re-invoke the thread on its own, and until it does there is
@@ -35938,12 +35827,16 @@ function hasLiveOwnWork(tele) {
35938
35827
  // counting it — for that card's event-snooze, which is now the ONE control for hiding a parked
35939
35828
  // watcher (maintainer: "the user can just use the generic snooze card… now GitHub watchers can be
35940
35829
  // included in the ranks of those").
35941
- hasParkedPrWatch(tele)
35830
+ hasParkedPrWatch(tele, registeredPrWatches)
35942
35831
  );
35943
35832
  }
35944
- function hasParkedPrWatch(tele) {
35833
+ function hasParkedPrWatch(tele, registered) {
35945
35834
  if (tele?.lastFence?.kind !== "awaiting") return false;
35946
- return tele.lastFence.hints.some((hint) => hint.kind === "pr" && parsePrRef(hint.value) !== void 0);
35835
+ return tele.lastFence.hints.some((hint) => {
35836
+ if (hint.kind !== "pr") return false;
35837
+ const ref = parsePrRef(hint.value);
35838
+ return ref !== void 0 && registered.has(githubStatusKey(ref));
35839
+ });
35947
35840
  }
35948
35841
  function bgSnoozeArmed(row) {
35949
35842
  return row.bg_snooze_rested_at != null && row.rested_at != null && row.bg_snooze_rested_at === row.rested_at;
@@ -35985,7 +35878,7 @@ function fenceWatchViews(slug, tele, fenceAt, github = {}, registered = [], arme
35985
35878
  if (tele?.lastFence?.kind !== "awaiting") return out;
35986
35879
  const createdAt = fenceAt ?? tele.lastAssistantAt ?? (/* @__PURE__ */ new Date()).toISOString();
35987
35880
  for (const hint of tele.lastFence.hints) {
35988
- if (hint.kind !== "shell" && hint.kind !== "agent") continue;
35881
+ if (hint.kind !== "shell") continue;
35989
35882
  const target = hint.value.trim();
35990
35883
  if (!target || seen.has(`shell:${target}`) || !liveWaitHandles(tele).has(target)) continue;
35991
35884
  seen.add(`shell:${target}`);
@@ -36012,15 +35905,15 @@ function deriveNeedsYou(row, tele, runtime, hasActionableInteraction = false, no
36012
35905
  if (excuseLiveOwnWork && runtime !== "exited" && hasLiveBackgroundWork(tele) && !tele?.lastFence) return false;
36013
35906
  if (excuseLiveOwnWork && runtime !== "exited" && hasDeclaredBackgroundPark(tele, nowMs)) return false;
36014
35907
  if (excuseLiveOwnWork && runtime !== "exited" && heldByRunningChecks(tele, github, registeredPrWatches)) return false;
36015
- if (runtime !== "exited" && (hasLiveOwnWork(tele) || hasParkedTimerWatch(tele, armedTimerIds)) && tele?.lastFence?.kind !== "done") return !bgSnoozeArmed(row);
35908
+ if (runtime !== "exited" && (hasLiveOwnWork(tele, registeredPrWatches) || hasParkedTimerWatch(tele, armedTimerIds)) && tele?.lastFence?.kind !== "done") return !bgSnoozeArmed(row);
36016
35909
  if (tele?.lastFence?.kind === "done") return true;
36017
35910
  return true;
36018
35911
  }
36019
35912
  function deriveAwaitingBackground(row, tele, runtime, hasActionableInteraction = false, nowMs = Date.now(), limitPause = void 0, deliveryProcessGone = false, github = {}, registeredPrWatches = /* @__PURE__ */ new Set(), armedTimerIds = /* @__PURE__ */ new Set()) {
36020
- if (runtime !== "turn-idle" || !hasDeclaredWait(tele, nowMs, armedTimerIds)) return false;
35913
+ if (runtime !== "turn-idle" || !hasDeclaredWait(tele, nowMs, armedTimerIds, registeredPrWatches)) return false;
36021
35914
  if (hasActionableInteraction || tele?.pendingAsk || tele?.pendingQuestion) return false;
36022
35915
  if (tele?.lastFence?.kind === "done") return false;
36023
- if (tele?.lastFence?.kind === "awaiting" && !hasParkedPrWatch(tele) && !hasDeclaredBackgroundPark(tele, nowMs) && // A TIMER PARK IS THE SAME EXCEPTION AGAIN (2026-08-24): its fence has no park action either, so
35916
+ if (tele?.lastFence?.kind === "awaiting" && !hasParkedPrWatch(tele, registeredPrWatches) && !hasDeclaredBackgroundPark(tele, nowMs) && // A TIMER PARK IS THE SAME EXCEPTION AGAIN (2026-08-24): its fence has no park action either, so
36024
35917
  // suppressing this card left the wait stated nowhere but the fence's own machinery footer.
36025
35918
  !hasParkedTimerWatch(tele, armedTimerIds)) return false;
36026
35919
  return deriveNeedsYou({ ...row, bg_snooze_rested_at: null }, tele, runtime, hasActionableInteraction, nowMs, limitPause, false, deliveryProcessGone, {}, /* @__PURE__ */ new Set(), armedTimerIds);
@@ -36497,7 +36390,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
36497
36390
  if (parcelSub || stopped) return Promise.resolve();
36498
36391
  if (watchSetup) return watchSetup;
36499
36392
  const setup = (async () => {
36500
- const next = await subscribe(join29(project.dir, ".frizz"), () => scheduleRebuild());
36393
+ const next = await subscribe(join28(project.dir, ".frizz"), () => scheduleRebuild());
36501
36394
  if (stopped) {
36502
36395
  await next.unsubscribe();
36503
36396
  return;
@@ -37724,10 +37617,17 @@ ${wakeTimeHeader(now(), spokeAt)}`;
37724
37617
  for (const sh of tele.retiredShells ?? []) {
37725
37618
  for (const h of [sh.taskId, sh.id, sh.label]) if (h) finishedHandles.add(h);
37726
37619
  }
37727
- const finishedItem = (i) => finishedHandles.has(i.value);
37620
+ for (const a of tele.subAgents ?? []) {
37621
+ if (a.state !== "rested") continue;
37622
+ for (const h of [a.id, a.label]) if (h) finishedHandles.add(h);
37623
+ }
37624
+ const firedTimers = new Set(
37625
+ deps.storage.listThreadTimers(row.slug).filter((t) => t.state === "fired").map((t) => t.id)
37626
+ );
37627
+ const finishedItem = (i) => finishedHandles.has(i.value) || i.kind === "timer" && firedTimers.has(i.value);
37728
37628
  const status = park.items.map((i) => {
37729
37629
  const gone = dead.some((d) => d.kind === i.kind && d.value === i.value);
37730
- const note = !gone ? "still running" : finishedItem(i) ? "FINISHED \u2014 its result is waiting for you" : i.kind === "pr" ? "NOT REGISTERED \u2014 register it with `mcp__frizz__watch_pr` first, then name it here" : "NOT RUNNING (nothing by that name)";
37630
+ const note = !gone ? "still running" : i.kind === "timer" && firedTimers.has(i.value) ? "already FIRED \u2014 its wake was delivered; there is nothing left to wait on" : finishedItem(i) ? "FINISHED \u2014 its result is waiting for you" : i.kind === "pr" ? "NOT REGISTERED \u2014 register it with `mcp__frizz__watch_pr` first, then name it here" : "NOT RUNNING (nothing by that name)";
37731
37631
  return `- \`${AWAITING_KEY_OF[i.kind]}: [${i.value}]\` \u2014 ${note}`;
37732
37632
  });
37733
37633
  const allFinished = dead.length > 0 && dead.every(finishedItem);
@@ -38523,7 +38423,7 @@ var init_resume = __esm({
38523
38423
  });
38524
38424
 
38525
38425
  // packages/server/src/backend/claude.ts
38526
- import { join as join30 } from "node:path";
38426
+ import { join as join29 } from "node:path";
38527
38427
  function toolResultText2(content) {
38528
38428
  if (typeof content === "string") return content;
38529
38429
  if (!Array.isArray(content)) return "";
@@ -38612,7 +38512,7 @@ function createClaudeBackend(opts) {
38612
38512
  return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
38613
38513
  },
38614
38514
  transcriptPath(sessionId) {
38615
- return join30(opts.logDir, `${sessionId}.jsonl`);
38515
+ return join29(opts.logDir, `${sessionId}.jsonl`);
38616
38516
  },
38617
38517
  parseLine(line) {
38618
38518
  return parseClaudeLine(line);
@@ -38782,12 +38682,12 @@ var init_login_utility = __esm({
38782
38682
 
38783
38683
  // packages/server/src/backend/codex-app-server-diagnostics.ts
38784
38684
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync13 } from "node:fs";
38785
- import { join as join31 } from "node:path";
38685
+ import { join as join30 } from "node:path";
38786
38686
  function codexDiagnosticLogPath(stateDir, projectId) {
38787
- return join31(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
38687
+ return join30(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
38788
38688
  }
38789
38689
  function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
38790
- const dir = join31(stateDir, "codex-app-server");
38690
+ const dir = join30(stateDir, "codex-app-server");
38791
38691
  const path = codexDiagnosticLogPath(stateDir, projectId);
38792
38692
  let ensured = false;
38793
38693
  return (event) => {
@@ -38819,7 +38719,7 @@ var init_codex_app_server_diagnostics = __esm({
38819
38719
  import { execFile as execFile7 } from "node:child_process";
38820
38720
  import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
38821
38721
  import { connect as connectSocket } from "node:net";
38822
- import { join as join32 } from "node:path";
38722
+ import { join as join31 } from "node:path";
38823
38723
  function parseLsofSocketNames(stdout) {
38824
38724
  const referenced = /* @__PURE__ */ new Set();
38825
38725
  for (const line of stdout.split("\n")) {
@@ -38836,7 +38736,7 @@ function sweepStaleSockets(options, deps = {}) {
38836
38736
  const keep = new Set(options.keep ?? []);
38837
38737
  let candidates;
38838
38738
  try {
38839
- candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join32(options.dir, name)).filter((path) => !keep.has(path));
38739
+ candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join31(options.dir, name)).filter((path) => !keep.has(path));
38840
38740
  } catch {
38841
38741
  return;
38842
38742
  }
@@ -39300,7 +39200,7 @@ var init_thread_hibernation = __esm({
39300
39200
  });
39301
39201
 
39302
39202
  // packages/server/src/context.ts
39303
- import { join as join33 } from "node:path";
39203
+ import { join as join32 } from "node:path";
39304
39204
  import { randomUUID as randomUUID15 } from "node:crypto";
39305
39205
  import { homedir as homedir13 } from "node:os";
39306
39206
  function reconcileSessions(storage) {
@@ -39458,7 +39358,7 @@ async function createContext(opts = {}) {
39458
39358
  function createContextUnchecked(opts, resources) {
39459
39359
  const home = opts.home ?? homedir13();
39460
39360
  const project = opts.project ?? resolveProject();
39461
- const dbPath = join33(project.stateDir, "ui.db");
39361
+ const dbPath = join32(project.stateDir, "ui.db");
39462
39362
  const storage = createStorage(dbPath);
39463
39363
  resources.storage = storage;
39464
39364
  const bus = new Bus();
@@ -39603,12 +39503,16 @@ function createContextUnchecked(opts, resources) {
39603
39503
  projectId: project.id,
39604
39504
  // The frizz worker environment — the SDK equivalent of the CLI argv's --plugin-dir / --mcp-config.
39605
39505
  // Computed ONCE here (constant per project) and applied on every broker fork so a broker worker
39606
- // gets the frizz sub-agent profiles, the frizz + chrome-devtools MCP, and the cc-worker hooks.
39506
+ // gets the frizz sub-agent profiles, the frizz MCP server (the only one frizz mounts), and the
39507
+ // cc-worker hooks. A project's own `.mcp.json` servers load on top of these, not through them.
39607
39508
  workerEnv: {
39608
39509
  pluginDir: workerPluginDir(),
39609
39510
  ...claudeMcpConfig(resolveFrizzMcp(frizzMcpTarget)),
39610
39511
  permDir: permRequestDir(project)
39611
39512
  },
39513
+ // Read at every fork, not captured once: the compaction window is a Settings value the drawer
39514
+ // can change while the server runs, and the next dispatch or cold resume should carry it.
39515
+ getSettings: () => getSettings(storage, home),
39612
39516
  // Which broker threads warmUp() may reattach at boot. Same predicate codex's shouldAutoResume
39613
39517
  // applies: never wake a thread the human has already put away — a boot reattach is only for a
39614
39518
  // thread that is still open and still theirs to come back to. Broker-backed rows only; a
@@ -42499,7 +42403,7 @@ var init_server = __esm({
42499
42403
  });
42500
42404
 
42501
42405
  // packages/server/src/repair.ts
42502
- import { existsSync as existsSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "node:fs";
42406
+ import { existsSync as existsSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "node:fs";
42503
42407
  import { basename as basename7, dirname as dirname13, resolve as resolve6 } from "node:path";
42504
42408
  function quoteValue(v) {
42505
42409
  if (/^[\w./#:+-]+$/.test(v)) return v;
@@ -42514,7 +42418,7 @@ function repairThreadFile(frizzDir, file) {
42514
42418
  const abs = resolve6(root, file);
42515
42419
  if (dirname13(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
42516
42420
  if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
42517
- if (!existsSync12(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
42421
+ if (!existsSync11(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
42518
42422
  const content = readFileSync19(abs, "utf8");
42519
42423
  const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
42520
42424
  if (firstLine2?.trim() === "---") {
@@ -42706,10 +42610,10 @@ var init_bash_background = __esm({
42706
42610
  });
42707
42611
 
42708
42612
  // packages/server/src/transcript.ts
42709
- import { closeSync as closeSync13, existsSync as existsSync13, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync21, readSync as readSync7, renameSync as renameSync9, statSync as statSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
42613
+ import { closeSync as closeSync13, existsSync as existsSync12, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync21, readSync as readSync7, renameSync as renameSync9, statSync as statSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
42710
42614
  import { createHash as createHash13 } from "node:crypto";
42711
42615
  import { StringDecoder as StringDecoder4 } from "node:string_decoder";
42712
- import { join as join34 } from "node:path";
42616
+ import { join as join33 } from "node:path";
42713
42617
  import { homedir as homedir14 } from "node:os";
42714
42618
  function formatTokens(n) {
42715
42619
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
@@ -43259,14 +43163,14 @@ function pruneScreenshotCache() {
43259
43163
  if (entries.length <= SCREENSHOT_CACHE_MAX) return;
43260
43164
  const byMtime = entries.map((n) => {
43261
43165
  try {
43262
- return { n, m: statSync14(join34(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
43166
+ return { n, m: statSync14(join33(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
43263
43167
  } catch {
43264
43168
  return { n, m: 0 };
43265
43169
  }
43266
43170
  }).sort((a, b) => b.m - a.m);
43267
43171
  for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
43268
43172
  try {
43269
- unlinkSync5(join34(SCREENSHOT_CACHE_DIR, n));
43173
+ unlinkSync5(join33(SCREENSHOT_CACHE_DIR, n));
43270
43174
  } catch {
43271
43175
  }
43272
43176
  }
@@ -43296,14 +43200,14 @@ function persistBase64Image(mediaType, data, idKey) {
43296
43200
  const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
43297
43201
  if (!ext || !data) return void 0;
43298
43202
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
43299
- const path = join34(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
43203
+ const path = join33(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
43300
43204
  try {
43301
- if (existsSync13(path)) return path;
43205
+ if (existsSync12(path)) return path;
43302
43206
  if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
43303
43207
  const buf = Buffer.from(data, "base64");
43304
43208
  if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
43305
43209
  mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
43306
- const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
43210
+ const tmp = join33(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
43307
43211
  writeFileSync12(tmp, buf);
43308
43212
  renameSync9(tmp, path);
43309
43213
  pruneScreenshotCache();
@@ -43318,14 +43222,14 @@ function persistSentFile(srcPath, idKey) {
43318
43222
  const outExt = ext === "jpeg" ? "jpg" : ext;
43319
43223
  try {
43320
43224
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
43321
- const dest = join34(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
43322
- if (existsSync13(dest)) return dest;
43225
+ const dest = join33(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
43226
+ if (existsSync12(dest)) return dest;
43323
43227
  const size = statSync14(srcPath).size;
43324
43228
  if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
43325
43229
  const buf = readFileSync21(srcPath);
43326
43230
  if (!looksLikeImage(buf, outExt)) return void 0;
43327
43231
  mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
43328
- const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
43232
+ const tmp = join33(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
43329
43233
  writeFileSync12(tmp, buf);
43330
43234
  renameSync9(tmp, dest);
43331
43235
  pruneScreenshotCache();
@@ -43762,16 +43666,16 @@ function verifyIncrementalParse(path, identityPrefix, incremental) {
43762
43666
  }
43763
43667
  }
43764
43668
  function logDirOf(project) {
43765
- return join34(homedir14(), ".claude", "projects", project.cwdSlug);
43669
+ return join33(homedir14(), ".claude", "projects", project.cwdSlug);
43766
43670
  }
43767
43671
  function resolveTranscriptPath(project, sessionId) {
43768
- const path = join34(logDirOf(project), `${sessionId}.jsonl`);
43672
+ const path = join33(logDirOf(project), `${sessionId}.jsonl`);
43769
43673
  try {
43770
43674
  if (statSync14(path).size > 0) return path;
43771
43675
  } catch {
43772
43676
  }
43773
43677
  const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
43774
- return stranded ? join34(stranded, `${sessionId}.jsonl`) : path;
43678
+ return stranded ? join33(stranded, `${sessionId}.jsonl`) : path;
43775
43679
  }
43776
43680
  function projectCodexTranscript(raw2, identityPrefix = "codex") {
43777
43681
  const out = [];
@@ -44715,7 +44619,7 @@ function sourceForThread(project, storage, slug, backendFor) {
44715
44619
  }
44716
44620
  if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
44717
44621
  const claudePath = resolveTranscriptPath(project, slug);
44718
- if (existsSync13(claudePath)) {
44622
+ if (existsSync12(claudePath)) {
44719
44623
  return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
44720
44624
  }
44721
44625
  const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
@@ -45171,7 +45075,7 @@ var init_open_external = __esm({
45171
45075
  import { spawn as spawn6 } from "node:child_process";
45172
45076
  import { readFileSync as readFileSync22, realpathSync as realpathSync8, statSync as statSync15 } from "node:fs";
45173
45077
  import { homedir as homedir15 } from "node:os";
45174
- import { isAbsolute as isAbsolute3, join as join35, resolve as resolve7, sep } from "node:path";
45078
+ import { isAbsolute as isAbsolute3, join as join34, resolve as resolve7, sep } from "node:path";
45175
45079
  function isUnder(real, root) {
45176
45080
  let rootReal;
45177
45081
  try {
@@ -45201,7 +45105,7 @@ function resolveLocalFile(rawPath, roots) {
45201
45105
  function resolveOpenableFile(raw2, projectDir, roots, home = homedir15()) {
45202
45106
  const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
45203
45107
  if (!trimmed) return null;
45204
- const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join35(home, trimmed.slice(2)) : isAbsolute3(trimmed) ? trimmed : resolve7(projectDir, trimmed);
45108
+ const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join34(home, trimmed.slice(2)) : isAbsolute3(trimmed) ? trimmed : resolve7(projectDir, trimmed);
45205
45109
  try {
45206
45110
  return resolveLocalFile(abs, roots);
45207
45111
  } catch {
@@ -45912,10 +45816,10 @@ var init_directory_picker = __esm({
45912
45816
 
45913
45817
  // packages/server/src/router.ts
45914
45818
  import { readFileSync as readFileSync23, statSync as statSync17 } from "node:fs";
45915
- import { join as join36, resolve as resolve8 } from "node:path";
45819
+ import { join as join35, resolve as resolve8 } from "node:path";
45916
45820
  import { randomUUID as randomUUID16 } from "node:crypto";
45917
45821
  import { basename as basename9, dirname as dirname14 } from "node:path";
45918
- import { existsSync as existsSync14, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
45822
+ import { existsSync as existsSync13, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
45919
45823
  import { homedir as homedir16 } from "node:os";
45920
45824
  function validateGithubDispatchProfile(input) {
45921
45825
  validateThreadProfile(input.backend, input.model, input.effort);
@@ -46075,7 +45979,7 @@ function projectCard(entry, stale) {
46075
45979
  function addProjectAtPath(input) {
46076
45980
  const typed = input.trim();
46077
45981
  if (!typed) throw new Error("Enter a folder path.");
46078
- const expanded = typed === "~" || typed.startsWith("~/") ? join36(homedir16(), typed.slice(1)) : typed;
45982
+ const expanded = typed === "~" || typed.startsWith("~/") ? join35(homedir16(), typed.slice(1)) : typed;
46079
45983
  const absolute = resolve8(expanded);
46080
45984
  let stats;
46081
45985
  try {
@@ -46128,7 +46032,7 @@ function setProjectIconFromFile(id, file) {
46128
46032
  return storeProjectIcon(id, file, bytes);
46129
46033
  }
46130
46034
  function createRouter(ctx) {
46131
- const frizzDir = join36(ctx.project.dir, ".frizz");
46035
+ const frizzDir = join35(ctx.project.dir, ".frizz");
46132
46036
  const openRoots = openableFileRoots(ctx.project);
46133
46037
  function isAutoTitledSession(slug) {
46134
46038
  return ctx.storage.getSession(slug)?.title_auto === 1;
@@ -47739,7 +47643,7 @@ ${gapNote}` : input.message;
47739
47643
  }
47740
47644
  const updated = clearProjectIcon(input.id);
47741
47645
  if (!updated) throw new Error("No such project.");
47742
- return projectCard(updated, !existsSync14(updated.path));
47646
+ return projectCard(updated, !existsSync13(updated.path));
47743
47647
  }
47744
47648
  }),
47745
47649
  /**
@@ -47789,7 +47693,7 @@ ${gapNote}` : input.message;
47789
47693
  for (const entry of listProjects()) {
47790
47694
  if (entry.stale) continue;
47791
47695
  try {
47792
- const db = new sqlite_default(join36(projectStateDir(entry.id), "ui.db"), { readonly: true });
47696
+ const db = new sqlite_default(join35(projectStateDir(entry.id), "ui.db"), { readonly: true });
47793
47697
  try {
47794
47698
  const hit = db.prepare("SELECT 1 FROM session WHERE slug = ? LIMIT 1").get(input.slug);
47795
47699
  if (hit) found.push({ projectSlug: entry.slug, projectName: entry.name ?? entry.slug });
@@ -48181,7 +48085,7 @@ var init_local_image = __esm({
48181
48085
  // packages/server/src/project-icon.ts
48182
48086
  import { readFileSync as readFileSync25, readdirSync as readdirSync10, realpathSync as realpathSync10, statSync as statSync19 } from "node:fs";
48183
48087
  import { homedir as homedir17 } from "node:os";
48184
- import { basename as basename10, dirname as dirname15, extname as extname3, join as join37, relative, sep as sep2 } from "node:path";
48088
+ import { basename as basename10, dirname as dirname15, extname as extname3, join as join36, relative, sep as sep2 } from "node:path";
48185
48089
  function normalizeStem(stem) {
48186
48090
  const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
48187
48091
  const normalized = sized.replace(VARIANT_SUFFIX, "");
@@ -48199,7 +48103,7 @@ function sizeScore(dimensions) {
48199
48103
  return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
48200
48104
  }
48201
48105
  function under(root, relativeDirectory) {
48202
- return relativeDirectory ? join37(root, relativeDirectory) : root;
48106
+ return relativeDirectory ? join36(root, relativeDirectory) : root;
48203
48107
  }
48204
48108
  function listFiles(directory) {
48205
48109
  try {
@@ -48221,19 +48125,19 @@ function iconDirectories(root) {
48221
48125
  for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
48222
48126
  for (const host of HOST_DIRECTORIES) {
48223
48127
  if (!children2.has(host)) continue;
48224
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, host), asset));
48128
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join36(root, host), asset));
48225
48129
  }
48226
48130
  for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
48227
48131
  for (const container of WORKSPACE_CONTAINERS) {
48228
48132
  if (!children2.has(container)) continue;
48229
48133
  let members;
48230
48134
  try {
48231
- members = readdirSync10(join37(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
48135
+ members = readdirSync10(join36(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
48232
48136
  } catch {
48233
48137
  continue;
48234
48138
  }
48235
48139
  for (const member of members) {
48236
- for (const asset of ASSET_DIRECTORIES) directories.push(under(join37(root, container, member), asset));
48140
+ for (const asset of ASSET_DIRECTORIES) directories.push(under(join36(root, container, member), asset));
48237
48141
  }
48238
48142
  }
48239
48143
  return [...new Set(directories)];
@@ -48247,7 +48151,7 @@ function manifestIcons(manifestPath) {
48247
48151
  }
48248
48152
  if (!Array.isArray(parsed?.icons)) return [];
48249
48153
  const directory = dirname15(manifestPath);
48250
- return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join37(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
48154
+ return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join36(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
48251
48155
  }
48252
48156
  function projectIconCandidates(root) {
48253
48157
  const projectStem = normalizeStem(basename10(root)).normalized;
@@ -48266,7 +48170,7 @@ function projectIconCandidates(root) {
48266
48170
  for (const name of listFiles(directory)) {
48267
48171
  const extension2 = extname3(name).toLowerCase();
48268
48172
  if (MANIFEST_NAMES.includes(name.toLowerCase())) {
48269
- for (const icon of manifestIcons(join37(directory, name))) {
48173
+ for (const icon of manifestIcons(join36(directory, name))) {
48270
48174
  if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
48271
48175
  fromManifest.add(icon);
48272
48176
  files.push({ path: icon, directory: dirname15(icon) });
@@ -48277,7 +48181,7 @@ function projectIconCandidates(root) {
48277
48181
  if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
48278
48182
  const stem = name.slice(0, name.length - extension2.length);
48279
48183
  if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
48280
- files.push({ path: join37(directory, name), directory });
48184
+ files.push({ path: join36(directory, name), directory });
48281
48185
  if (files.length >= MAX_CANDIDATES) break;
48282
48186
  }
48283
48187
  }
@@ -48455,7 +48359,7 @@ var init_project_icon = __esm({
48455
48359
 
48456
48360
  // packages/server/src/local-visualization.ts
48457
48361
  import { readdirSync as readdirSync11, readFileSync as readFileSync26, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
48458
- import { join as join38, sep as sep3 } from "node:path";
48362
+ import { join as join37, sep as sep3 } from "node:path";
48459
48363
  function children(path, pattern) {
48460
48364
  try {
48461
48365
  return readdirSync11(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
@@ -48467,7 +48371,7 @@ function isUnder2(path, root) {
48467
48371
  return path === root || path.startsWith(root.endsWith(sep3) ? root : root + sep3);
48468
48372
  }
48469
48373
  function resolveFragment(projectDir, sessionId, file) {
48470
- const base = join38(projectDir, ".codex", "visualizations");
48374
+ const base = join37(projectDir, ".codex", "visualizations");
48471
48375
  let projectReal;
48472
48376
  let baseReal;
48473
48377
  try {
@@ -48478,12 +48382,12 @@ function resolveFragment(projectDir, sessionId, file) {
48478
48382
  }
48479
48383
  if (!isUnder2(baseReal, projectReal)) return null;
48480
48384
  for (const year of children(base, YEAR_PART)) {
48481
- const yearDir = join38(base, year);
48385
+ const yearDir = join37(base, year);
48482
48386
  for (const month of children(yearDir, DATE_PART)) {
48483
- const monthDir = join38(yearDir, month);
48387
+ const monthDir = join37(yearDir, month);
48484
48388
  for (const day of children(monthDir, DATE_PART)) {
48485
- const sessionRoot = join38(baseReal, year, month, day, sessionId);
48486
- const candidate = join38(monthDir, day, sessionId, file);
48389
+ const sessionRoot = join37(baseReal, year, month, day, sessionId);
48390
+ const candidate = join37(monthDir, day, sessionId, file);
48487
48391
  try {
48488
48392
  const real = realpathSync11(candidate);
48489
48393
  if (isUnder2(real, sessionRoot) && statSync20(real).isFile()) return real;
@@ -48612,7 +48516,7 @@ var init_local_visualization = __esm({
48612
48516
 
48613
48517
  // packages/server/src/app.ts
48614
48518
  import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync14 } from "node:fs";
48615
- import { join as join39 } from "node:path";
48519
+ import { join as join38 } from "node:path";
48616
48520
  import { randomUUID as randomUUID17 } from "node:crypto";
48617
48521
  function createApp(ctx, options = {}) {
48618
48522
  const app = new Hono2();
@@ -48716,10 +48620,10 @@ function createApp(ctx, options = {}) {
48716
48620
  if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
48717
48621
  const ext = `.${attachmentExtension(name)}`;
48718
48622
  const buf = Buffer.from(body.data, "base64");
48719
- const dir = join39(ctx.project.stateDir, "attachments");
48623
+ const dir = join38(ctx.project.stateDir, "attachments");
48720
48624
  mkdirSync17(dir, { recursive: true });
48721
48625
  const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
48722
- const path = join39(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
48626
+ const path = join38(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
48723
48627
  writeFileSync14(path, buf);
48724
48628
  return c.json({ path });
48725
48629
  });
@@ -49959,9 +49863,9 @@ var init_app_socket = __esm({
49959
49863
 
49960
49864
  // packages/server/src/boot-progress.ts
49961
49865
  import { mkdirSync as mkdirSync18, readFileSync as readFileSync27, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
49962
- import { basename as basename11, dirname as dirname16, join as join40 } from "node:path";
49866
+ import { basename as basename11, dirname as dirname16, join as join39 } from "node:path";
49963
49867
  function bootProgressPath(stateDir) {
49964
- return join40(stateDir, BOOT_PROGRESS_NAME);
49868
+ return join39(stateDir, BOOT_PROGRESS_NAME);
49965
49869
  }
49966
49870
  function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
49967
49871
  if (!stateDir) {
@@ -49982,7 +49886,7 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
49982
49886
  step++;
49983
49887
  try {
49984
49888
  mkdirSync18(dirname16(path), { recursive: true, mode: 448 });
49985
- const temp = join40(dirname16(path), `.${basename11(path)}.${process.pid}.tmp`);
49889
+ const temp = join39(dirname16(path), `.${basename11(path)}.${process.pid}.tmp`);
49986
49890
  writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
49987
49891
  `, "utf8");
49988
49892
  renameSync10(temp, path);
@@ -50099,8 +50003,8 @@ __export(index_exports, {
50099
50003
  unknownProjectPage: () => unknownProjectPage
50100
50004
  });
50101
50005
  import { createServer } from "node:http";
50102
- import { readFileSync as readFileSync28, existsSync as existsSync15 } from "node:fs";
50103
- import { dirname as dirname17, join as join41, resolve as resolve9, extname as extname4, normalize } from "node:path";
50006
+ import { readFileSync as readFileSync28, existsSync as existsSync14 } from "node:fs";
50007
+ import { dirname as dirname17, join as join40, resolve as resolve9, extname as extname4, normalize } from "node:path";
50104
50008
  function readServerAddressHolder(path) {
50105
50009
  try {
50106
50010
  const value = JSON.parse(readFileSync28(path, "utf8"));
@@ -50203,9 +50107,9 @@ function createShutdownSignalHandler(options) {
50203
50107
  }
50204
50108
  function serveStatic(distDir, req, res) {
50205
50109
  const rel = normalize((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
50206
- let file = join41(distDir, rel === "/" ? "index.html" : rel);
50207
- if (!file.startsWith(distDir)) file = join41(distDir, "index.html");
50208
- if (!existsSync15(file)) file = join41(distDir, "index.html");
50110
+ let file = join40(distDir, rel === "/" ? "index.html" : rel);
50111
+ if (!file.startsWith(distDir)) file = join40(distDir, "index.html");
50112
+ if (!existsSync14(file)) file = join40(distDir, "index.html");
50209
50113
  try {
50210
50114
  const body = readFileSync28(file);
50211
50115
  res.writeHead(200, { "content-type": MIME[extname4(file)] ?? "application/octet-stream" });
@@ -50556,7 +50460,7 @@ async function startServer(opts = {}) {
50556
50460
  }
50557
50461
  statusPath = serverLockPathFor(ctx.project);
50558
50462
  const webRoot = resolve9(import.meta.dirname, "..", "..", "web");
50559
- const distDir = opts.webDistDir ? resolve9(opts.webDistDir) : join41(webRoot, "dist");
50463
+ const distDir = opts.webDistDir ? resolve9(opts.webDistDir) : join40(webRoot, "dist");
50560
50464
  startupPhase = "Vite";
50561
50465
  if (opts.dev) {
50562
50466
  try {
@@ -50632,7 +50536,7 @@ async function startServer(opts = {}) {
50632
50536
  if (vite) {
50633
50537
  vite.middlewares(req, res, () => {
50634
50538
  try {
50635
- const html = readFileSync28(join41(webRoot, "index.html"), "utf8");
50539
+ const html = readFileSync28(join40(webRoot, "index.html"), "utf8");
50636
50540
  void vite.transformIndexHtml(url, html).then((out) => {
50637
50541
  res.writeHead(200, { "content-type": "text/html" });
50638
50542
  res.end(out);
@@ -50644,7 +50548,7 @@ async function startServer(opts = {}) {
50644
50548
  });
50645
50549
  return;
50646
50550
  }
50647
- if (existsSync15(distDir)) {
50551
+ if (existsSync14(distDir)) {
50648
50552
  serveStatic(distDir, req, res);
50649
50553
  return;
50650
50554
  }
@@ -50824,7 +50728,7 @@ var init_index = __esm({
50824
50728
  }
50825
50729
  };
50826
50730
  isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
50827
- serverLockPathFor = (project) => join41(project.stateDir, "server.lock");
50731
+ serverLockPathFor = (project) => join40(project.stateDir, "server.lock");
50828
50732
  MIME = {
50829
50733
  ".html": "text/html",
50830
50734
  ".js": "text/javascript",
@@ -50842,8 +50746,8 @@ init_project();
50842
50746
  init_project_launch();
50843
50747
  init_shutdown();
50844
50748
  init_logging();
50845
- import { existsSync as existsSync16 } from "node:fs";
50846
- import { join as join42 } from "node:path";
50749
+ import { existsSync as existsSync15 } from "node:fs";
50750
+ import { join as join41 } from "node:path";
50847
50751
  process.on("uncaughtException", (error) => {
50848
50752
  log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
50849
50753
  process.exit(1);
@@ -50881,9 +50785,9 @@ try {
50881
50785
  ["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
50882
50786
  ["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
50883
50787
  ];
50884
- if (!existsSync16(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
50788
+ if (!existsSync15(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
50885
50789
  for (const [name, directory, requiredFile] of required) {
50886
- if (!directory || !existsSync16(join42(directory, requiredFile)))
50790
+ if (!directory || !existsSync15(join41(directory, requiredFile)))
50887
50791
  throw new Error(`stable artifact launch is missing verified ${name}`);
50888
50792
  }
50889
50793
  }