frizz 0.12.11 → 0.12.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/codex-app-server-daemon.js +8 -1
  2. package/dist/dev-child.js +639 -293
  3. package/dist/frizz.js +499 -82
  4. package/package.json +1 -1
  5. package/runtime/cc-worker/bin/frizz-mcp.mjs +19 -14
  6. package/runtime/cc-worker/hooks/bash-background.d.mts +1 -1
  7. package/runtime/cc-worker/hooks/bash-background.mjs +17 -6
  8. package/runtime/cc-worker/hooks/deny-ask.mjs +5 -4
  9. package/runtime/cc-worker/hooks/deny-plan.mjs +3 -3
  10. package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
  11. package/runtime/cc-worker/skills/gh/SKILL.md +3 -3
  12. package/web-dist/assets/{TerminalPane-DHvHCziB.js → TerminalPane-Cv0c_SNS.js} +1 -1
  13. package/web-dist/assets/{abnfDiagram-VRR7QNED-DIzM_IN8.js → abnfDiagram-VRR7QNED-B-wED8ht.js} +1 -1
  14. package/web-dist/assets/architecture-TIHT7OUA-nsH-QfGr.js +1 -0
  15. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-Bm_8vd5v.js → architectureDiagram-ZJ3FMSHR-kHm_O2sR.js} +1 -1
  16. package/web-dist/assets/{blockDiagram-677ZJIJ3-BXUW_N2o.js → blockDiagram-677ZJIJ3-CdazUaKU.js} +1 -1
  17. package/web-dist/assets/{c4Diagram-LMCZKHZV-C7rfqxUO.js → c4Diagram-LMCZKHZV-rauLKZbJ.js} +1 -1
  18. package/web-dist/assets/channel-fvwjeUt3.js +1 -0
  19. package/web-dist/assets/{chunk-32BRIVSS-DbfUERLa.js → chunk-32BRIVSS-DjOqyEDX.js} +1 -1
  20. package/web-dist/assets/{chunk-52WLFC77-BT_3wKSP.js → chunk-52WLFC77-f1SThegZ.js} +1 -1
  21. package/web-dist/assets/{chunk-C7G6YPKG-JqApnOVt.js → chunk-C7G6YPKG-BfQsesjz.js} +1 -1
  22. package/web-dist/assets/{chunk-EX3LRPZG-Cj6hlA4t.js → chunk-EX3LRPZG-BIzZW7OQ.js} +1 -1
  23. package/web-dist/assets/{chunk-FWX5IMBZ-BGthhuW_.js → chunk-FWX5IMBZ-CATINZFC.js} +2 -2
  24. package/web-dist/assets/{chunk-HOUHSVGY-uyLpm_YS.js → chunk-HOUHSVGY-DbMBRCCE.js} +1 -1
  25. package/web-dist/assets/{chunk-ICXQ74PX-BlwUMZAk.js → chunk-ICXQ74PX-COcoSWx4.js} +1 -1
  26. package/web-dist/assets/{chunk-MOJQB5TN-MXbNNk-P.js → chunk-MOJQB5TN-CVnI-o_4.js} +1 -1
  27. package/web-dist/assets/{chunk-OGEWGWER-Da4My1Rf.js → chunk-OGEWGWER-U0vdZqB6.js} +1 -1
  28. package/web-dist/assets/{chunk-PUDLZKDR-C-PgvO__.js → chunk-PUDLZKDR-Bukb7rem.js} +1 -1
  29. package/web-dist/assets/{chunk-Q4XR5HBZ-BxZFe7gr.js → chunk-Q4XR5HBZ-Bl019PHd.js} +1 -1
  30. package/web-dist/assets/{chunk-V7JOEXUC-DgERuXm_.js → chunk-V7JOEXUC-YJR0vzI6.js} +1 -1
  31. package/web-dist/assets/{chunk-VAUOI2AC-T5OoQE-_.js → chunk-VAUOI2AC-CK6NU9kj.js} +1 -1
  32. package/web-dist/assets/{chunk-VR4S4FIN--HFvJUG1.js → chunk-VR4S4FIN-B3LBv3C3.js} +1 -1
  33. package/web-dist/assets/{chunk-WYO6CB5R-DlAvy41i.js → chunk-WYO6CB5R-DjRPyRuS.js} +1 -1
  34. package/web-dist/assets/{chunk-ZGVPDNZ5-BdDg7X_H.js → chunk-ZGVPDNZ5-BUJCayKP.js} +1 -1
  35. package/web-dist/assets/classDiagram-OUVF2IWQ-BRZQ_xnN.js +1 -0
  36. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BRZQ_xnN.js +1 -0
  37. package/web-dist/assets/{cynefin-VYW2F7L2-BhjNO28R.js → cynefin-VYW2F7L2-CE3ce51v.js} +1 -1
  38. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B6VA5gCd.js → cynefinDiagram-TSTJHNR4-DSXSPZQV.js} +1 -1
  39. package/web-dist/assets/{dagre-VKFMJZFB-6MXoEc6K.js → dagre-VKFMJZFB-VwBQdjum.js} +1 -1
  40. package/web-dist/assets/{diagram-FQU43EPY-D2RSPoK4.js → diagram-FQU43EPY-7Tf5_WDu.js} +1 -1
  41. package/web-dist/assets/{diagram-G47NLZAW-BOSbtzOf.js → diagram-G47NLZAW-mu4aclWA.js} +1 -1
  42. package/web-dist/assets/{diagram-NH7WQ7WH-Dt_u21nP.js → diagram-NH7WQ7WH-B3kAVNwv.js} +1 -1
  43. package/web-dist/assets/{diagram-OA4YK3LP-CjuWW-7r.js → diagram-OA4YK3LP-DAaV-2ou.js} +1 -1
  44. package/web-dist/assets/{diagram-WEI45ONY-rANIKJ8N.js → diagram-WEI45ONY-DijPfgly.js} +1 -1
  45. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Bf3kfaKZ.js → ebnfDiagram-CCIWWBDH-B1o42Wp-.js} +1 -1
  46. package/web-dist/assets/{erDiagram-Q63AITRT-6vBp0ITn.js → erDiagram-Q63AITRT-Dqej9Wi3.js} +1 -1
  47. package/web-dist/assets/eventmodeling-45OFAUF4-B8xeGcXU.js +1 -0
  48. package/web-dist/assets/flowDiagram-23GEKE2U-Cnyfcz6Z.js +1 -0
  49. package/web-dist/assets/{ganttDiagram-NO4QXBWP-C2qN2PMS.js → ganttDiagram-NO4QXBWP-UoyrLPUT.js} +1 -1
  50. package/web-dist/assets/{gitGraph-TEB2WS4Q-CRASqc9n.js → gitGraph-TEB2WS4Q-CwjIwQ3T.js} +1 -1
  51. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-gsXSPrH2.js → gitGraphDiagram-IHSO6WYX-BxU758_g.js} +1 -1
  52. package/web-dist/assets/index-CT6k_A5y.css +1 -0
  53. package/web-dist/assets/index-k8HCkyvh.js +319 -0
  54. package/web-dist/assets/{info-DKCQHKI2-rDkFlFOd.js → info-DKCQHKI2-CWtSI6aK.js} +1 -1
  55. package/web-dist/assets/{infoDiagram-FWYZ7A6U-03ql8Tqr.js → infoDiagram-FWYZ7A6U-n5xRfy_d.js} +1 -1
  56. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-CfDSAVNp.js → ishikawaDiagram-FXEZZL3T-DU1LTQY-.js} +1 -1
  57. package/web-dist/assets/{journeyDiagram-5HDEW3XC-RWaA6AvH.js → journeyDiagram-5HDEW3XC-BzZWHoPv.js} +1 -1
  58. package/web-dist/assets/{kanban-definition-HUTT4EX6-C89qresM.js → kanban-definition-HUTT4EX6-CeneyR7c.js} +1 -1
  59. package/web-dist/assets/{line-CZtSsvHu.js → line-DTfkLWnw.js} +1 -1
  60. package/web-dist/assets/{mermaid-parser.core-DKNnvxt0.js → mermaid-parser.core-CHzoJ5LT.js} +3 -3
  61. package/web-dist/assets/{mermaid.core-DX8cFeBF.js → mermaid.core-Cl84DLN2.js} +3 -3
  62. package/web-dist/assets/{mindmap-definition-LN4V7U3C-DW8ClyAX.js → mindmap-definition-LN4V7U3C-C39sBNqB.js} +1 -1
  63. package/web-dist/assets/{packet-7NZHBO7P-CUOv3apI.js → packet-7NZHBO7P-Bj73rlQn.js} +1 -1
  64. package/web-dist/assets/{pegDiagram-2B236MQR-CFyxKEBL.js → pegDiagram-2B236MQR-BEEM1vSv.js} +1 -1
  65. package/web-dist/assets/{pie-RZYD4A2V-DyXXRie2.js → pie-RZYD4A2V-CJ-fEfdy.js} +1 -1
  66. package/web-dist/assets/{pieDiagram-ENE6RG2P-yUw7HA8l.js → pieDiagram-ENE6RG2P-Dcqj3oyh.js} +1 -1
  67. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-Mc7aea_i.js → quadrantDiagram-ABIIQ3AL-D5cljQ3D.js} +1 -1
  68. package/web-dist/assets/{radar-I7S5WNFK-BKj0BsQy.js → radar-I7S5WNFK-G8JqqGPf.js} +1 -1
  69. package/web-dist/assets/{railroad-3IZDKUUU-DUK_kJIY.js → railroad-3IZDKUUU-CaGNL0iN.js} +1 -1
  70. package/web-dist/assets/railroad-abnf-AHOZXSZD-DWZmdy-r.js +1 -0
  71. package/web-dist/assets/railroad-ebnf-EBAXGLYW-C-XoPuo_.js +1 -0
  72. package/web-dist/assets/railroad-peg-LSFZ7HO6-BGpxqg46.js +1 -0
  73. package/web-dist/assets/{railroadDiagram-RFXS5EU6-By0Xgb_U.js → railroadDiagram-RFXS5EU6-BZGZXMY4.js} +1 -1
  74. package/web-dist/assets/{requirementDiagram-TGXJPOKE-CgTycsRY.js → requirementDiagram-TGXJPOKE-Dj5yn_9e.js} +1 -1
  75. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-mJ_-Grkn.js → sankeyDiagram-HTMAVEWB-7iJ_NDNf.js} +1 -1
  76. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-Dn6YyeNv.js → sequenceDiagram-DBY2YBRQ-Bl54CknA.js} +1 -1
  77. package/web-dist/assets/{stateDiagram-2N3HPSRC-BBws1E-e.js → stateDiagram-2N3HPSRC-DuvjeNgJ.js} +1 -1
  78. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BR-vrMgH.js +1 -0
  79. package/web-dist/assets/{swimlanes-5IMT3BWC-Jab-07o9.js → swimlanes-5IMT3BWC-B7-Jj4Sk.js} +1 -1
  80. package/web-dist/assets/swimlanesDiagram-G3AALYLV-C1HvMf2k.js +8 -0
  81. package/web-dist/assets/{timeline-definition-FHXFAJF6-Cq6RznHz.js → timeline-definition-FHXFAJF6-jsey2pEG.js} +1 -1
  82. package/web-dist/assets/{treeView-QDETBFTQ-KSelJnyL.js → treeView-QDETBFTQ-DhPmOzHr.js} +1 -1
  83. package/web-dist/assets/{treemap-6X3UGDF4-B-iiUurZ.js → treemap-6X3UGDF4-Dw2IaXqK.js} +1 -1
  84. package/web-dist/assets/{vennDiagram-L72KCM5P-B8t4NeQy.js → vennDiagram-L72KCM5P-CXKZkBab.js} +1 -1
  85. package/web-dist/assets/{wardley-OPB4EBWU-CtkAPZLQ.js → wardley-OPB4EBWU-CTCdDwyE.js} +1 -1
  86. package/web-dist/assets/{wardleyDiagram-EHGQE667-DC-ms_sD.js → wardleyDiagram-EHGQE667-B1w0xIqB.js} +1 -1
  87. package/web-dist/assets/{xychartDiagram-FW5EYKEG-DW6fcgjF.js → xychartDiagram-FW5EYKEG-gULk-uIl.js} +1 -1
  88. package/web-dist/index.html +2 -2
  89. package/web-dist/assets/architecture-TIHT7OUA-DBvKOf0j.js +0 -1
  90. package/web-dist/assets/channel-CbdXYswc.js +0 -1
  91. package/web-dist/assets/classDiagram-OUVF2IWQ-BVXwECjS.js +0 -1
  92. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BVXwECjS.js +0 -1
  93. package/web-dist/assets/eventmodeling-45OFAUF4-STTT-geQ.js +0 -1
  94. package/web-dist/assets/flowDiagram-23GEKE2U-CC7DF1Fz.js +0 -1
  95. package/web-dist/assets/index-DimQCLe8.js +0 -319
  96. package/web-dist/assets/index-XhfdAXcw.css +0 -1
  97. package/web-dist/assets/railroad-abnf-AHOZXSZD-BDImJcFw.js +0 -1
  98. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BR2XIYqq.js +0 -1
  99. package/web-dist/assets/railroad-peg-LSFZ7HO6-DRCwBbJY.js +0 -1
  100. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-B2jOFVGG.js +0 -1
  101. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DhYK_6fH.js +0 -8
package/dist/dev-child.js CHANGED
@@ -13456,8 +13456,8 @@ function parkExpiredWakeMessage(status) {
13456
13456
  ...status,
13457
13457
  "",
13458
13458
  "Re-park if they are genuinely still going \u2014 there is no limit on that, and a long job is not a",
13459
- "failure. If something is finished, read its result. If nothing is left, end in ```done or ask a",
13460
- "```question."
13459
+ "failure. If something is finished, read its result. If nothing is left, end in ```done or register",
13460
+ "a question with `mcp__frizz__ask`."
13461
13461
  ].join("\n");
13462
13462
  }
13463
13463
  function parkFinishedWakeMessage(status, several) {
@@ -13469,7 +13469,7 @@ function parkFinishedWakeMessage(status, several) {
13469
13469
  "READ ITS OUTPUT AND CARRY ON. This is not a broken fence and there is nothing to fix: the wait",
13470
13470
  "you declared simply ended. Do NOT relaunch the same work \u2014 its result is already on disk.",
13471
13471
  "",
13472
- "Then park on whatever comes next, or end in ```done or a ```question if nothing is left."
13472
+ "Then park on whatever comes next, or end in ```done or a registered question (`mcp__frizz__ask`) if nothing is left."
13473
13473
  ].join("\n");
13474
13474
  }
13475
13475
  function prWatchExpiredWakeMessage(ref) {
@@ -13483,6 +13483,12 @@ function ownWatchExpiredWakeMessage(kind, target) {
13483
13483
 
13484
13484
  If you are still waiting on it, register it again with \`mcp__frizz__watch\` and a fresh \`for:\`. If you are not, and it was the only thing you were waiting on, end in a proper terminal state instead of parking on it again.`;
13485
13485
  }
13486
+ function questionFencesLive(spawnedAt) {
13487
+ if (spawnedAt === void 0 || spawnedAt === null) return true;
13488
+ const at = typeof spawnedAt === "number" ? spawnedAt : Date.parse(spawnedAt);
13489
+ if (!Number.isFinite(at)) return true;
13490
+ return at < Date.parse(QUESTION_FENCE_RETIRED_AT);
13491
+ }
13486
13492
  function askedQuestionDepth(q) {
13487
13493
  let deepest = 1;
13488
13494
  for (const option of q.options ?? []) {
@@ -13679,7 +13685,7 @@ function parseGithubWakeSteer(text2) {
13679
13685
  function frizzRoute(path3) {
13680
13686
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
13681
13687
  }
13682
- var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_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, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, 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, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, 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_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13688
+ var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_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, PR_WATCH_SPENT_TRAILER, PR_WATCH_ARMED_TRAILER, SHELL_DONE_TRAILER, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PR_WATCH_MAX_ARMED, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, OWN_WATCH_MAX_ARMED, QUESTION_FENCE_RETIRED_AT, AskQuestionKind, ASK_MAX_DEPTH, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, BURIED_ANSWERS_HEADER, ANSWER_FOLLOW_UP_MARKER, DISMISSED_ANSWER, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, 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, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, GITHUB_DISPATCH_UI_BOUNDARY, WAKE_TIME_HEADER_TAIL, WAKE_TRAILERS, 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_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, PROJECT_ICON_EXTENSIONS, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT, threadIdentityName;
13683
13689
  var init_src = __esm({
13684
13690
  "packages/shared/src/index.ts"() {
13685
13691
  "use strict";
@@ -13997,7 +14003,7 @@ var init_src = __esm({
13997
14003
  RETIRED_AWAITING_REPLACEMENT = {
13998
14004
  "watch": "`shells: [<the id your runtime gave you>]` (or `agents: [<id>]`) \u2014 the same id, in the current sequence",
13999
14005
  "pr-watch": "register the PR with `mcp__frizz__watch_pr`, then name it `prs: [owner/repo#123]`",
14000
- "human": "there is no human gate any more \u2014 if you need a person, ask a ```question instead of parking",
14006
+ "human": "there is no human gate any more \u2014 if you need a person, register a question with `mcp__frizz__ask` instead of parking",
14001
14007
  "ci": "CI is not a wait of its own: register the PR with `mcp__frizz__watch_pr` and you are woken when its checks settle",
14002
14008
  "session": "there is no cross-session wait \u2014 name the sub-agent you dispatched with `agents: [<id>]`",
14003
14009
  // THE 2026-08-24 CUTOVER. The frontmatter is YAML now, and YAML has no repeated keys — so the four
@@ -14057,7 +14063,7 @@ var init_src = __esm({
14057
14063
  INTERRUPT_MARKERS = ["[Request interrupted by user]", "[Request interrupted by user for tool use]"];
14058
14064
  ALLDONE_SENTINEL = "ALLDONE";
14059
14065
  OPT_OUT_NOTE = "To stop these, sign off with a ```done fence \u2014 but ONLY when the work is genuinely finished: it files this thread away, and nothing but new work from the human reopens it.";
14060
- OVER_QUESTION_NOTE = "Your ```question is still unanswered, and a Goal armed at rest means the operator is not waiting to answer it: decide it yourself, say in one line which way you went and what would reverse it, and carry on. Do NOT re-ask it.";
14066
+ OVER_QUESTION_NOTE = "Your registered question is still unanswered, and a Goal armed at rest means the operator is not waiting to answer it: decide it yourself, say in one line which way you went and what would reverse it, and carry on. Do NOT re-ask it.";
14061
14067
  TIMER_PROMPT_MAX = SNOOZE_PROMPT_MAX;
14062
14068
  TIMER_MAX_DELAY_SECONDS = 30 * 24 * 60 * 60;
14063
14069
  TIMER_MAX_ARMED = 64;
@@ -14089,14 +14095,16 @@ var init_src = __esm({
14089
14095
  "the write-up.",
14090
14096
  "",
14091
14097
  "**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
14092
- "right now: ask that one in a question fence. Every other open choice INSIDE the task \u2014 a name, a",
14098
+ "right now: register that one with `mcp__frizz__ask`. Every other open choice INSIDE the task \u2014 a name, a",
14093
14099
  "default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
14094
14100
  "what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
14095
14101
  "unanswered question is not permission to go build the answer.",
14096
14102
  "",
14097
- "Otherwise, add a fence at the END of your next message:",
14103
+ "Otherwise, sign off \u2014 a registration, or a fence at the END of your next message:",
14098
14104
  "",
14099
- "- `` ```question `` \u2014 you need the human. One question per fence, lettered options, one recommended.",
14105
+ "- `mcp__frizz__ask` \u2014 you need the human. NOT a fence: the ```question fence is retired, and a fence",
14106
+ " with a question in its body is plain prose. Register it (options with one-line trade-offs, the",
14107
+ " recommended one first), then rest normally \u2014 an open registered question is the sign-off.",
14100
14108
  "- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
14101
14109
  " if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
14102
14110
  " **bolded verb phrase**.",
@@ -14311,6 +14319,7 @@ var init_src = __esm({
14311
14319
  watches: external_exports.array(OwnWatchView)
14312
14320
  }).strict();
14313
14321
  OWN_WATCH_MAX_ARMED = 24;
14322
+ QUESTION_FENCE_RETIRED_AT = "2026-09-11T17:30:00Z";
14314
14323
  AskQuestionKind = external_exports.enum(["question", "multi"]);
14315
14324
  ASK_MAX_DEPTH = 3;
14316
14325
  AskedOptionSchema = external_exports.lazy(() => external_exports.object({
@@ -17821,7 +17830,7 @@ function rpcRequestIdJson(value) {
17821
17830
  }
17822
17831
  function canonicalJson(value) {
17823
17832
  const visiting = /* @__PURE__ */ new WeakSet();
17824
- const normalize3 = (candidate) => {
17833
+ const normalize2 = (candidate) => {
17825
17834
  if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") return candidate;
17826
17835
  if (typeof candidate === "number") {
17827
17836
  if (!Number.isFinite(candidate)) throw new InteractionStoreError("invalid-response", "provider JSON contains a non-finite number");
@@ -17833,14 +17842,14 @@ function canonicalJson(value) {
17833
17842
  if (visiting.has(candidate)) throw new InteractionStoreError("invalid-response", "provider JSON contains a cycle");
17834
17843
  visiting.add(candidate);
17835
17844
  try {
17836
- if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize3(item));
17845
+ if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize2(item));
17837
17846
  const object = candidate;
17838
- return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize3(object[key])]));
17847
+ return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize2(object[key])]));
17839
17848
  } finally {
17840
17849
  visiting.delete(candidate);
17841
17850
  }
17842
17851
  };
17843
- return JSON.stringify(normalize3(value));
17852
+ return JSON.stringify(normalize2(value));
17844
17853
  }
17845
17854
  function boundedProviderJson(value) {
17846
17855
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -22181,6 +22190,35 @@ var init_detached_daemons = __esm({
22181
22190
  }
22182
22191
  });
22183
22192
 
22193
+ // packages/server/src/backend/daemon-tree.ts
22194
+ import { spawnSync } from "node:child_process";
22195
+ function endDaemonTree(pid, signal, deps = {}) {
22196
+ const kill = deps.kill ?? defaultKill;
22197
+ if ((deps.platform ?? process.platform) !== "win32") {
22198
+ try {
22199
+ kill(pid, signal);
22200
+ } catch {
22201
+ }
22202
+ return;
22203
+ }
22204
+ const killed = (deps.spawnSync ?? spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
22205
+ if (killed.error || killed.status !== 0) {
22206
+ try {
22207
+ kill(pid, signal);
22208
+ } catch {
22209
+ }
22210
+ }
22211
+ }
22212
+ var defaultKill;
22213
+ var init_daemon_tree = __esm({
22214
+ "packages/server/src/backend/daemon-tree.ts"() {
22215
+ "use strict";
22216
+ defaultKill = (pid, signal) => {
22217
+ process.kill(pid, signal);
22218
+ };
22219
+ }
22220
+ });
22221
+
22184
22222
  // packages/server/src/backend/ipc-path.ts
22185
22223
  import { join as join15 } from "node:path";
22186
22224
  function frizzIpcPath(name) {
@@ -22370,7 +22408,10 @@ function forkBroker(options) {
22370
22408
  cwd: options.cwd,
22371
22409
  env: { ...process.env, FRIZZ_CLAUDE_BROKER: JSON.stringify(config) },
22372
22410
  detached: true,
22373
- stdio: "ignore"
22411
+ stdio: "ignore",
22412
+ // A detached process owns no console on Windows; keep the daemon from ever being handed a
22413
+ // visible one (Windows audit 2026-09-11, finding 4 — the Codex daemon's spawn tells the story).
22414
+ windowsHide: true
22374
22415
  });
22375
22416
  child.unref();
22376
22417
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
@@ -22421,7 +22462,7 @@ async function adoptOrForkBroker(options) {
22421
22462
  if (existing) return { record: existing, reattached: true };
22422
22463
  return { record: await forkBroker(options), reattached: false };
22423
22464
  }
22424
- function killBroker(stateDir, sessionId, retireReason) {
22465
+ function killBroker(stateDir, sessionId, retireReason, deps = {}) {
22425
22466
  const recordPath2 = claudeBrokerRecordPath(stateDir, sessionId);
22426
22467
  const record = liveBrokerRecord(recordPath2);
22427
22468
  if (retireReason) {
@@ -22429,10 +22470,7 @@ function killBroker(stateDir, sessionId, retireReason) {
22429
22470
  } else takeBrokerRetirement(stateDir, sessionId);
22430
22471
  if (record) {
22431
22472
  rememberBrokerIdentity(recordPath2, record);
22432
- try {
22433
- process.kill(record.daemonPid, "SIGTERM");
22434
- } catch {
22435
- }
22473
+ endDaemonTree(record.daemonPid, "SIGTERM", deps);
22436
22474
  }
22437
22475
  try {
22438
22476
  unlinkSync(recordPath2);
@@ -22446,6 +22484,7 @@ var init_claude_broker_host = __esm({
22446
22484
  "use strict";
22447
22485
  init_detached_daemons();
22448
22486
  init_claude_broker_diagnostics();
22487
+ init_daemon_tree();
22449
22488
  init_ipc_path();
22450
22489
  WINDOWS_SHIM_TARGET = /^\s*"%dp0%\\(.+?)"/mu;
22451
22490
  }
@@ -23401,10 +23440,30 @@ var init_tail_cache = __esm({
23401
23440
  import { statSync as statSync8, openSync as openSync10, readSync as readSync4, closeSync as closeSync9, readdirSync as readdirSync5, realpathSync as realpathSync5, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, readFileSync as readFileSync12, existsSync as existsSync5 } from "node:fs";
23402
23441
  import { execFile as execFile2 } from "node:child_process";
23403
23442
  import { promisify as promisify2 } from "node:util";
23404
- import { basename as basename4, join as join18 } from "node:path";
23443
+ import { basename as basename4, join as join18, win32 } from "node:path";
23405
23444
  import { homedir as homedir7 } from "node:os";
23406
- async function probeShellsAlive(outputFiles) {
23445
+ async function probeShellsAlive(outputFiles, opts = {}) {
23446
+ const platform3 = opts.platform ?? process.platform;
23447
+ const exec = opts.exec ?? execFileAsync;
23407
23448
  const verdicts = /* @__PURE__ */ new Map();
23449
+ if (platform3 === "win32") {
23450
+ const present = outputFiles.filter((file) => {
23451
+ if (existsSync5(file)) return true;
23452
+ verdicts.set(file, void 0);
23453
+ return false;
23454
+ });
23455
+ if (present.length === 0) return verdicts;
23456
+ const [shell, args] = windowsShellHolderCommand(present, opts.env ?? process.env);
23457
+ let report;
23458
+ try {
23459
+ report = (await exec(shell, args, { encoding: "utf8", timeout: 8e3, windowsHide: true })).stdout;
23460
+ } catch {
23461
+ for (const file of present) verdicts.set(file, void 0);
23462
+ return verdicts;
23463
+ }
23464
+ for (const [file, alive] of parseWindowsShellHolderReport(String(report), present)) verdicts.set(file, alive);
23465
+ return verdicts;
23466
+ }
23408
23467
  const live = [];
23409
23468
  for (const file of outputFiles) {
23410
23469
  if (!existsSync5(file)) {
@@ -23420,7 +23479,7 @@ async function probeShellsAlive(outputFiles) {
23420
23479
  if (live.length === 0) return verdicts;
23421
23480
  let stdout = "";
23422
23481
  try {
23423
- stdout = (await execFileAsync("lsof", ["-F", "pn", "--", ...live.map((f) => f.real)], { encoding: "utf8", timeout: 8e3 })).stdout;
23482
+ stdout = (await exec("lsof", ["-F", "pn", "--", ...live.map((f) => f.real)], { encoding: "utf8", timeout: 8e3 })).stdout;
23424
23483
  } catch (err) {
23425
23484
  const e = err;
23426
23485
  const ran = typeof e.code === "number" && !e.killed;
@@ -23435,6 +23494,25 @@ async function probeShellsAlive(outputFiles) {
23435
23494
  for (const f of live) verdicts.set(f.requested, held.has(f.real));
23436
23495
  return verdicts;
23437
23496
  }
23497
+ function windowsShellHolderCommand(paths, env = process.env) {
23498
+ const shell = win32.join(env.SystemRoot ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
23499
+ const list = paths.map((p) => `'${p.replace(/'/g, "''")}'`).join(",");
23500
+ const script = `foreach($p in @(${list})){try{$f=[System.IO.File]::Open($p,[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None);$f.Close();'free'}catch [System.IO.IOException]{if((($_.Exception.HResult) -band 0xFFFF) -in 32,33){'held'}else{'unknown'}}catch{'unknown'}}`;
23501
+ return [shell, ["-NoProfile", "-NonInteractive", "-NoLogo", "-Command", script]];
23502
+ }
23503
+ function parseWindowsShellHolderReport(report, paths) {
23504
+ const verdicts = /* @__PURE__ */ new Map();
23505
+ const lines = report.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
23506
+ if (lines.length !== paths.length) {
23507
+ for (const file of paths) verdicts.set(file, void 0);
23508
+ return verdicts;
23509
+ }
23510
+ paths.forEach((file, i) => {
23511
+ const word = lines[i];
23512
+ verdicts.set(file, word === "held" ? true : word === "free" ? false : void 0);
23513
+ });
23514
+ return verdicts;
23515
+ }
23438
23516
  function activityMinute(at) {
23439
23517
  if (!at) return "";
23440
23518
  const ms = Date.parse(at);
@@ -25518,18 +25596,20 @@ ${detail2}
25518
25596
  }, delay2);
25519
25597
  nudgeTimer.unref?.();
25520
25598
  }
25521
- function registeredStateIsCurrent(state) {
25599
+ function currentRowFor(state) {
25522
25600
  const current = deps.storage.getSession(state.slug);
25523
- return Boolean(
25524
- current && current.session_id === state.sessionId && (current.runtime_generation ?? 0) === state.runtimeGeneration
25525
- );
25601
+ return current && current.session_id === state.sessionId && (current.runtime_generation ?? 0) === state.runtimeGeneration ? current : void 0;
25602
+ }
25603
+ function registeredStateIsCurrent(state) {
25604
+ return currentRowFor(state) !== void 0;
25526
25605
  }
25527
25606
  return {
25528
25607
  get(slug) {
25529
25608
  const registered = states.get(slug);
25530
- const s = registered && registeredStateIsCurrent(registered) ? registered : registered ? void 0 : foreignStates.get(slug);
25609
+ const row = registered ? currentRowFor(registered) : void 0;
25610
+ const s = registered ? row ? registered : void 0 : foreignStates.get(slug);
25531
25611
  if (!s) return void 0;
25532
- const pendingQuestion = s.lastAssistantHasQuestion;
25612
+ const pendingQuestion = s.lastAssistantHasQuestion && questionFencesLive(row?.spawned_at);
25533
25613
  const nowMs = now();
25534
25614
  return { turn: s.turn, permPrompt: s.permPrompt, permPolicy: s.permPolicy, permDenies: s.permDenies, model: s.model, effort: s.effort, profileAt: s.profileAt, profileRevision: s.profileRevision, permissionMode: s.permissionMode, permissionModeAt: s.permissionModeAt, permissionModeRevision: s.permissionModeRevision, lastActivityAt: s.lastActivityAt, lastAssistantAt: s.lastAssistantAt, lastAssistant: s.lastAssistant, aiTitle: s.aiTitle, customTitle: s.customTitle, customTitleRevision: s.customTitleRevision, subAgents: subAgentViews(s, nowMs), droppedReports: [...s.queuedReports.values()], bgShells: [...bgShellViews(s), ...codexBgShellViews(s)], retiredShells: retiredShellViews(s), pendingAsk: s.pendingAsk, pendingQuestion, lastAssistantAllDone: s.lastAssistantAllDone, lastUserAt: s.lastUserAt, lastUserText: s.lastUserText, firstUserText: s.firstUserText, lastFence: s.lastFence, noTranscript: s.noTranscript, authFault: s.authFault, apiFault: s.apiFault, providerError: s.providerError, limitFault: s.limitFault, contextTokens: s.contextTokens, contextWindow: s.contextWindow, lastCompactionAt: s.lastCompactionAt };
25535
25615
  },
@@ -26371,6 +26451,101 @@ var init_codex = __esm({
26371
26451
  }
26372
26452
  });
26373
26453
 
26454
+ // packages/server/src/backend/codex-executable.ts
26455
+ import { accessSync as accessSync2, constants as fsConstants2, readFileSync as readFileSync14 } from "node:fs";
26456
+ import { posix, win32 as win322 } from "node:path";
26457
+ function defaultAccess(path3, mode) {
26458
+ try {
26459
+ accessSync2(path3, mode === "executable" ? fsConstants2.X_OK : fsConstants2.F_OK);
26460
+ return true;
26461
+ } catch {
26462
+ return false;
26463
+ }
26464
+ }
26465
+ function defaultReadFile(path3) {
26466
+ try {
26467
+ return readFileSync14(path3, "utf8");
26468
+ } catch {
26469
+ return void 0;
26470
+ }
26471
+ }
26472
+ function searchPath2(env) {
26473
+ const direct = env.PATH ?? env.Path ?? env.path;
26474
+ if (direct !== void 0) return direct;
26475
+ for (const [key, value] of Object.entries(env)) if (key.toLowerCase() === "path") return value ?? "";
26476
+ return "";
26477
+ }
26478
+ function vendoredNativeBeside(launcherJs, arch, access) {
26479
+ const triple = WINDOWS_TRIPLE[arch];
26480
+ if (!triple) return void 0;
26481
+ const { dirname: dirname24, join: join46 } = win322;
26482
+ const pkgRoot = dirname24(dirname24(launcherJs));
26483
+ const platformPkg = `codex-win32-${arch}`;
26484
+ const vendorRoots = [
26485
+ join46(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
26486
+ // nested under the package
26487
+ join46(dirname24(pkgRoot), platformPkg, "vendor"),
26488
+ // hoisted beside it: <prefix>\node_modules\@openai\<pkg>
26489
+ join46(pkgRoot, "vendor")
26490
+ // the pre-platform-package layout, binaries inside @openai/codex itself
26491
+ ];
26492
+ for (const root of vendorRoots) {
26493
+ const exe = join46(root, triple, "bin", "codex.exe");
26494
+ if (access(exe, "exists")) return exe;
26495
+ }
26496
+ return void 0;
26497
+ }
26498
+ function resolveCodexExecutable(bin, deps = {}) {
26499
+ const env = deps.env ?? process.env;
26500
+ const platform3 = deps.platform ?? process.platform;
26501
+ const arch = deps.arch ?? process.arch;
26502
+ const execPath = deps.execPath ?? process.execPath;
26503
+ const access = deps.access ?? defaultAccess;
26504
+ const readFile2 = deps.readFile ?? defaultReadFile;
26505
+ const windows = platform3 === "win32";
26506
+ const path3 = windows ? win322 : posix;
26507
+ const candidate = bin && bin.length > 0 ? bin : "codex";
26508
+ if (path3.isAbsolute(candidate) || /[\\/]/u.test(candidate)) return { file: candidate, args: [] };
26509
+ for (const dir of searchPath2(env).split(path3.delimiter)) {
26510
+ if (!dir) continue;
26511
+ if (!windows) {
26512
+ const full2 = path3.join(dir, candidate);
26513
+ if (access(full2, "executable")) return { file: full2, args: [] };
26514
+ continue;
26515
+ }
26516
+ const exe = path3.join(dir, `${candidate}.exe`);
26517
+ if (access(exe, "exists")) return { file: exe, args: [] };
26518
+ const body = readFile2(path3.join(dir, `${candidate}.cmd`));
26519
+ const target = body === void 0 ? void 0 : WINDOWS_SHIM_TARGET2.exec(body)?.[1];
26520
+ if (!target) continue;
26521
+ const full = path3.join(dir, target);
26522
+ if (!access(full, "exists")) continue;
26523
+ if (!/\.(?:js|mjs|cjs)$/iu.test(full)) return { file: full, args: [] };
26524
+ const native = vendoredNativeBeside(full, arch, access);
26525
+ if (native) return { file: native, args: [] };
26526
+ return { file: execPath, args: [full] };
26527
+ }
26528
+ throw new CodexExecutableNotFoundError(candidate);
26529
+ }
26530
+ var CodexExecutableNotFoundError, WINDOWS_SHIM_TARGET2, WINDOWS_TRIPLE;
26531
+ var init_codex_executable = __esm({
26532
+ "packages/server/src/backend/codex-executable.ts"() {
26533
+ "use strict";
26534
+ CodexExecutableNotFoundError = class extends Error {
26535
+ code = "ENOENT";
26536
+ constructor(candidate) {
26537
+ super(`could not resolve '${candidate}' to an executable on PATH`);
26538
+ this.name = "CodexExecutableNotFoundError";
26539
+ }
26540
+ };
26541
+ WINDOWS_SHIM_TARGET2 = /"%dp0%\\([^"]+)"\s+%\*/u;
26542
+ WINDOWS_TRIPLE = {
26543
+ x64: "x86_64-pc-windows-msvc",
26544
+ arm64: "aarch64-pc-windows-msvc"
26545
+ };
26546
+ }
26547
+ });
26548
+
26374
26549
  // packages/server/src/backend/worker-env.ts
26375
26550
  function isFrizzInternalEnvKey(key) {
26376
26551
  return key.startsWith(FRIZZ_INTERNAL_PREFIX);
@@ -27739,14 +27914,14 @@ var require_receiver = __commonJS({
27739
27914
  * @return {(Error|RangeError)} The error
27740
27915
  * @private
27741
27916
  */
27742
- createError(ErrorCtor, message, prefix, statusCode, errorCode4) {
27917
+ createError(ErrorCtor, message, prefix, statusCode, errorCode5) {
27743
27918
  this._loop = false;
27744
27919
  this._errored = true;
27745
27920
  const err = new ErrorCtor(
27746
27921
  prefix ? `Invalid WebSocket frame: ${message}` : message
27747
27922
  );
27748
27923
  Error.captureStackTrace(err, this.createError);
27749
- err.code = errorCode4;
27924
+ err.code = errorCode5;
27750
27925
  err[kStatusCode] = statusCode;
27751
27926
  return err;
27752
27927
  }
@@ -30210,7 +30385,7 @@ import { spawn as spawn2 } from "node:child_process";
30210
30385
  import { connect } from "node:net";
30211
30386
  import { Agent as HttpAgent } from "node:http";
30212
30387
  import { createHash as createHash7, randomUUID as randomUUID9 } from "node:crypto";
30213
- import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
30388
+ import { existsSync as existsSync6, mkdirSync as mkdirSync11, readFileSync as readFileSync15, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
30214
30389
  import { join as join20 } from "node:path";
30215
30390
  import { PassThrough, Writable } from "node:stream";
30216
30391
  import { StringDecoder } from "node:string_decoder";
@@ -30234,7 +30409,7 @@ function pidAlive2(pid) {
30234
30409
  }
30235
30410
  function readNativeRecord(stateDir, projectId) {
30236
30411
  try {
30237
- const value = JSON.parse(readFileSync14(nativeRecordPath(stateDir, projectId), "utf8"));
30412
+ const value = JSON.parse(readFileSync15(nativeRecordPath(stateDir, projectId), "utf8"));
30238
30413
  if (typeof value.listenerPid !== "number" || typeof value.socketPath !== "string" || typeof value.generation !== "string") return null;
30239
30414
  return {
30240
30415
  projectId,
@@ -30404,7 +30579,10 @@ async function startListener(options) {
30404
30579
  cwd: options.cwd,
30405
30580
  env: options.env,
30406
30581
  detached: true,
30407
- stdio: "ignore"
30582
+ stdio: "ignore",
30583
+ // No console window for codex.exe on Windows (Windows audit 2026-09-11, finding 4; the story is on
30584
+ // the daemon's spawn in codex-app-server-daemon.ts).
30585
+ windowsHide: true
30408
30586
  });
30409
30587
  child.unref();
30410
30588
  const record = {
@@ -30476,7 +30654,7 @@ var init_codex_app_server_native = __esm({
30476
30654
  };
30477
30655
  }
30478
30656
  log.error("codex", `codex app-server native listener unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
30479
- const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
30657
+ const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
30480
30658
  return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
30481
30659
  }
30482
30660
  };
@@ -30484,10 +30662,10 @@ var init_codex_app_server_native = __esm({
30484
30662
  });
30485
30663
 
30486
30664
  // packages/server/src/backend/codex-app-server-host.ts
30487
- import { spawn as spawn3, spawnSync } from "node:child_process";
30665
+ import { spawn as spawn3 } from "node:child_process";
30488
30666
  import { createConnection } from "node:net";
30489
30667
  import { createHash as createHash8, randomUUID as randomUUID10 } from "node:crypto";
30490
- import { existsSync as existsSync7, mkdirSync as mkdirSync12, readFileSync as readFileSync15, unlinkSync as unlinkSync3 } from "node:fs";
30668
+ import { existsSync as existsSync7, mkdirSync as mkdirSync12, readFileSync as readFileSync16, unlinkSync as unlinkSync3 } from "node:fs";
30491
30669
  import { join as join21 } from "node:path";
30492
30670
  import { PassThrough as PassThrough2, Writable as Writable2 } from "node:stream";
30493
30671
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
@@ -30511,7 +30689,7 @@ function pidAlive3(pid) {
30511
30689
  }
30512
30690
  function readDaemonRecord(stateDir, projectId) {
30513
30691
  try {
30514
- const value = JSON.parse(readFileSync15(recordPath(stateDir, projectId), "utf8"));
30692
+ const value = JSON.parse(readFileSync16(recordPath(stateDir, projectId), "utf8"));
30515
30693
  if (typeof value.daemonPid !== "number" || typeof value.socketPath !== "string" || typeof value.generation !== "string") return null;
30516
30694
  return {
30517
30695
  projectId,
@@ -30528,7 +30706,7 @@ function readDaemonRecord(stateDir, projectId) {
30528
30706
  }
30529
30707
  function readDaemonExitBreadcrumb(stateDir, projectId) {
30530
30708
  try {
30531
- const value = JSON.parse(readFileSync15(`${recordPath(stateDir, projectId)}.exit`, "utf8"));
30709
+ const value = JSON.parse(readFileSync16(`${recordPath(stateDir, projectId)}.exit`, "utf8"));
30532
30710
  if (typeof value.generation !== "string" || typeof value.reason !== "string") return null;
30533
30711
  return {
30534
30712
  generation: value.generation,
@@ -30551,22 +30729,6 @@ function liveDaemonRecord(stateDir, projectId) {
30551
30729
  }
30552
30730
  return null;
30553
30731
  }
30554
- function endDaemonTree(pid, signal) {
30555
- if (process.platform !== "win32") {
30556
- try {
30557
- process.kill(pid, signal);
30558
- } catch {
30559
- }
30560
- return;
30561
- }
30562
- const killed = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
30563
- if (killed.error || killed.status !== 0) {
30564
- try {
30565
- process.kill(pid, signal);
30566
- } catch {
30567
- }
30568
- }
30569
- }
30570
30732
  function killCodexAppServerDaemon(stateDir, projectId) {
30571
30733
  const record = liveDaemonRecord(stateDir, projectId);
30572
30734
  if (!record) return;
@@ -30627,7 +30789,11 @@ function forkDaemon(options) {
30627
30789
  // the payload and is applied by the daemon, keeping the audited env allowlist authoritative.
30628
30790
  env: { ...process.env, FRIZZ_CODEX_APP_SERVER_DAEMON: payload },
30629
30791
  detached: true,
30630
- stdio: "ignore"
30792
+ stdio: "ignore",
30793
+ // Belt to the daemon's own braces: a detached process owns no console on Windows, so anything
30794
+ // console-subsystem it starts without CREATE_NO_WINDOW gets a fresh visible one (Windows audit
30795
+ // 2026-09-11, finding 4). The daemon hides its app-server; this keeps the daemon itself hidden.
30796
+ windowsHide: true
30631
30797
  });
30632
30798
  child.unref();
30633
30799
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
@@ -30732,7 +30898,11 @@ function inProcessCodexAppServer(options) {
30732
30898
  const child = spawn3(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), {
30733
30899
  cwd: options.cwd,
30734
30900
  env: options.env,
30735
- stdio: ["pipe", "pipe", "pipe"]
30901
+ stdio: ["pipe", "pipe", "pipe"],
30902
+ // No console window for codex.exe (Windows audit 2026-09-11, finding 4). The server child has a
30903
+ // console of its own so this site was not the visible-window bug, but a codex.exe that inherits
30904
+ // it dies with it, and the shape test in daemon-tree.test.ts holds every provider spawn to it.
30905
+ windowsHide: true
30736
30906
  });
30737
30907
  const kill = child.kill.bind(child);
30738
30908
  child.kill = ((signal) => {
@@ -30759,6 +30929,7 @@ var init_codex_app_server_host = __esm({
30759
30929
  "packages/server/src/backend/codex-app-server-host.ts"() {
30760
30930
  "use strict";
30761
30931
  init_detached_daemons();
30932
+ init_daemon_tree();
30762
30933
  init_codex_app_server_native();
30763
30934
  init_ipc_path();
30764
30935
  init_codex_mcp();
@@ -31206,7 +31377,7 @@ var init_claude_quota = __esm({
31206
31377
  // packages/server/src/backend/auth-status.ts
31207
31378
  import { join as join23 } from "node:path";
31208
31379
  import { homedir as homedir10, platform as platform2 } from "node:os";
31209
- import { readFileSync as readFileSync16, statSync as statSync10 } from "node:fs";
31380
+ import { readFileSync as readFileSync17, statSync as statSync10 } from "node:fs";
31210
31381
  import { execFile as execFile4 } from "node:child_process";
31211
31382
  import { promisify as promisify4 } from "node:util";
31212
31383
  function claudeConfigDir2() {
@@ -31216,7 +31387,7 @@ function claudeConfigDir2() {
31216
31387
  function claudeFileState(configDir) {
31217
31388
  let raw2;
31218
31389
  try {
31219
- raw2 = readFileSync16(join23(configDir, ".credentials.json"), "utf8");
31390
+ raw2 = readFileSync17(join23(configDir, ".credentials.json"), "utf8");
31220
31391
  } catch (err) {
31221
31392
  return err.code === "ENOENT" ? "absent" : "error";
31222
31393
  }
@@ -31249,7 +31420,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
31249
31420
  if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
31250
31421
  let raw2;
31251
31422
  try {
31252
- raw2 = readFileSync16(join23(codexHome, "auth.json"), "utf8");
31423
+ raw2 = readFileSync17(join23(codexHome, "auth.json"), "utf8");
31253
31424
  } catch (err) {
31254
31425
  return err.code === "ENOENT" ? "signed-out" : "unknown";
31255
31426
  }
@@ -31269,7 +31440,7 @@ function readCodexAuthState(codexHome = defaultCodexHome()) {
31269
31440
  function readCodexAccountId(codexHome = defaultCodexHome()) {
31270
31441
  if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
31271
31442
  try {
31272
- const doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31443
+ const doc = JSON.parse(readFileSync17(join23(codexHome, "auth.json"), "utf8"));
31273
31444
  if (!doc || typeof doc !== "object") return void 0;
31274
31445
  const tokens = doc.tokens;
31275
31446
  if (!tokens || typeof tokens !== "object") return void 0;
@@ -31279,9 +31450,10 @@ function readCodexAccountId(codexHome = defaultCodexHome()) {
31279
31450
  return void 0;
31280
31451
  }
31281
31452
  }
31282
- async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
31453
+ async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3, resolve12 = resolveCodexExecutable) {
31283
31454
  try {
31284
- await exec(codexBin, ["--version"], { timeout: 5e3 });
31455
+ const codex = resolve12(codexBin);
31456
+ await exec(codex.file, [...codex.args, "--version"], { timeout: 5e3 });
31285
31457
  return "present";
31286
31458
  } catch (err) {
31287
31459
  return err.code === "ENOENT" ? "missing" : "unknown";
@@ -31340,7 +31512,7 @@ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
31340
31512
  if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
31341
31513
  let email;
31342
31514
  try {
31343
- const doc = JSON.parse(readFileSync16(path3, "utf8"));
31515
+ const doc = JSON.parse(readFileSync17(path3, "utf8"));
31344
31516
  email = asEmail(doc?.oauthAccount?.emailAddress);
31345
31517
  } catch {
31346
31518
  email = void 0;
@@ -31351,7 +31523,7 @@ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
31351
31523
  function readCodexAccountEmail(codexHome = defaultCodexHome()) {
31352
31524
  let doc;
31353
31525
  try {
31354
- doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31526
+ doc = JSON.parse(readFileSync17(join23(codexHome, "auth.json"), "utf8"));
31355
31527
  } catch {
31356
31528
  return void 0;
31357
31529
  }
@@ -31406,6 +31578,7 @@ var init_auth_status = __esm({
31406
31578
  init_claude_quota();
31407
31579
  init_codex();
31408
31580
  init_claude_broker_host();
31581
+ init_codex_executable();
31409
31582
  execFileAsync3 = promisify4(execFile4);
31410
31583
  ProviderAuthRequiredError = class extends Error {
31411
31584
  backend;
@@ -31746,7 +31919,7 @@ function fileChangeDisplays(changes) {
31746
31919
  }
31747
31920
  function canonicalJson2(value) {
31748
31921
  const visiting = /* @__PURE__ */ new WeakSet();
31749
- const normalize3 = (candidate) => {
31922
+ const normalize2 = (candidate) => {
31750
31923
  if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean") return candidate;
31751
31924
  if (typeof candidate === "number") {
31752
31925
  if (!Number.isFinite(candidate)) throw new Error("non-finite JSON number");
@@ -31756,14 +31929,14 @@ function canonicalJson2(value) {
31756
31929
  if (visiting.has(candidate)) throw new Error("cyclic JSON value");
31757
31930
  visiting.add(candidate);
31758
31931
  try {
31759
- if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize3(item));
31932
+ if (Array.isArray(candidate)) return candidate.map((item) => item === void 0 ? null : normalize2(item));
31760
31933
  const object = candidate;
31761
- return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize3(object[key])]));
31934
+ return Object.fromEntries(Object.keys(object).sort().filter((key) => object[key] !== void 0).map((key) => [key, normalize2(object[key])]));
31762
31935
  } finally {
31763
31936
  visiting.delete(candidate);
31764
31937
  }
31765
31938
  };
31766
- return JSON.stringify(normalize3(value));
31939
+ return JSON.stringify(normalize2(value));
31767
31940
  }
31768
31941
  function logicalRequestId(method, parts) {
31769
31942
  const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
@@ -34655,9 +34828,12 @@ function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex"
34655
34828
  return new Promise((resolve12) => {
34656
34829
  let child;
34657
34830
  try {
34658
- child = spawn4(codexBin, ["app-server"], {
34831
+ const codex = resolveCodexExecutable(codexBin);
34832
+ child = spawn4(codex.file, [...codex.args, "app-server"], {
34659
34833
  stdio: ["pipe", "pipe", "ignore"],
34660
- env: { ...process.env, CODEX_HOME: codexHome }
34834
+ env: { ...process.env, CODEX_HOME: codexHome },
34835
+ // No console window for codex.exe (Windows audit 2026-09-11, finding 4).
34836
+ windowsHide: true
34661
34837
  });
34662
34838
  } catch {
34663
34839
  resolve12(void 0);
@@ -34737,6 +34913,7 @@ var init_codex_quota = __esm({
34737
34913
  "packages/server/src/backend/codex-quota.ts"() {
34738
34914
  "use strict";
34739
34915
  init_codex();
34916
+ init_codex_executable();
34740
34917
  init_codex_app_server();
34741
34918
  descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
34742
34919
  TAIL_BYTES = 512 * 1024;
@@ -34816,7 +34993,7 @@ var init_quota = __esm({
34816
34993
 
34817
34994
  // packages/server/src/local-file.ts
34818
34995
  import { spawn as spawn5 } from "node:child_process";
34819
- import { readFileSync as readFileSync17, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
34996
+ import { existsSync as existsSync8, readFileSync as readFileSync18, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
34820
34997
  import { homedir as homedir11 } from "node:os";
34821
34998
  import { isAbsolute as isAbsolute4, join as join25, resolve as resolve5, sep as sep2 } from "node:path";
34822
34999
  function isUnder(real, root) {
@@ -34848,7 +35025,7 @@ function resolveLocalFile(rawPath, roots) {
34848
35025
  function resolveOpenableFile(raw2, projectDir, roots, home = homedir11()) {
34849
35026
  const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
34850
35027
  if (!trimmed) return null;
34851
- const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join25(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve5(projectDir, trimmed);
35028
+ const abs = trimmed === "~" ? home : /^~[\/\\]/.test(trimmed) ? join25(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve5(projectDir, trimmed);
34852
35029
  try {
34853
35030
  return resolveLocalFile(abs, roots);
34854
35031
  } catch {
@@ -34857,7 +35034,7 @@ function resolveOpenableFile(raw2, projectDir, roots, home = homedir11()) {
34857
35034
  }
34858
35035
  function readLocalTextFile(rawPath, roots) {
34859
35036
  const path3 = resolveLocalFile(rawPath, roots);
34860
- const bytes = readFileSync17(path3);
35037
+ const bytes = readFileSync18(path3);
34861
35038
  if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
34862
35039
  if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, text: bytes.toString("utf8"), truncated: false };
34863
35040
  const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
@@ -34868,7 +35045,7 @@ function readLocalMarkdown(rawPath, roots) {
34868
35045
  if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
34869
35046
  const path3 = resolveLocalFile(rawPath, roots);
34870
35047
  if (!MARKDOWN_FILE_EXT.test(path3)) throw new Error("Local file is not a Markdown file");
34871
- const bytes = readFileSync17(path3);
35048
+ const bytes = readFileSync18(path3);
34872
35049
  if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, markdown: bytes.toString("utf8"), truncated: false };
34873
35050
  const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
34874
35051
  const lastBreak = head.lastIndexOf(10);
@@ -34884,20 +35061,61 @@ function resolveWatchableLocalFile(rawPath, roots) {
34884
35061
  function defaultSpawn(command, args, options) {
34885
35062
  return spawn5(command, [...args], options);
34886
35063
  }
34887
- function openLocalFile(rawPath, opener, roots, options = {}) {
35064
+ async function awaitOpenerStart(child, command) {
35065
+ child.unref();
35066
+ await new Promise((resolve12, reject) => {
35067
+ child.once("error", (error) => reject(new Error(
35068
+ error.code === "ENOENT" ? `${command} is not installed or not on PATH` : `could not start ${command}: ${error.message}`
35069
+ )));
35070
+ child.once("spawn", resolve12);
35071
+ });
35072
+ }
35073
+ function localFileOpenCommand(path3, selected, options = {}) {
35074
+ const platform3 = options.platform ?? process.platform;
35075
+ if (platform3 === "darwin") {
35076
+ return selected === "cursor" ? { command: "open", args: ["-a", "Cursor", path3] } : selected === "vscode" ? { command: "open", args: ["-a", "Visual Studio Code", path3] } : selected === "finder" ? { command: "open", args: ["-R", path3] } : { command: "open", args: [path3] };
35077
+ }
35078
+ if (platform3 === "win32") {
35079
+ if (selected === "system") return { command: "explorer.exe", args: [path3] };
35080
+ if (selected === "finder") return { command: "explorer.exe", args: [`/select,${path3}`] };
35081
+ const editor = WINDOWS_EDITOR_EXE[selected];
35082
+ const env = options.env ?? process.env;
35083
+ const exists = options.exists ?? existsSync8;
35084
+ const localAppData = env.LOCALAPPDATA;
35085
+ if (localAppData) {
35086
+ const exe = join25(localAppData, ...editor.exe);
35087
+ if (exists(exe)) return { command: exe, args: [path3] };
35088
+ }
35089
+ if (/["%\r\n]/u.test(path3)) throw new Error(`cannot hand ${path3} to the ${editor.shim} shim through cmd.exe`);
35090
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", `"${editor.shim} "${path3}""`], verbatim: true };
35091
+ }
35092
+ return selected === "cursor" ? { command: "cursor", args: [path3] } : selected === "vscode" ? { command: "code", args: [path3] } : { command: "xdg-open", args: [path3] };
35093
+ }
35094
+ async function openLocalFile(rawPath, opener, roots, options = {}) {
34888
35095
  const path3 = resolveLocalFile(rawPath, roots);
34889
35096
  const selected = options.forceSystem ? "system" : opener;
34890
35097
  if (selected === "copy") return { action: "copy", path: path3 };
34891
- const spec = process.platform === "darwin" ? selected === "cursor" ? ["open", ["-a", "Cursor", path3]] : selected === "vscode" ? ["open", ["-a", "Visual Studio Code", path3]] : selected === "finder" ? ["open", ["-R", path3]] : ["open", [path3]] : selected === "finder" ? ["xdg-open", [path3]] : selected === "cursor" ? ["cursor", [path3]] : selected === "vscode" ? ["code", [path3]] : ["xdg-open", [path3]];
34892
- (options.spawn ?? defaultSpawn)(spec[0], spec[1], { detached: true, stdio: "ignore", shell: false }).unref();
35098
+ const spec = localFileOpenCommand(path3, selected, options);
35099
+ const child = (options.spawn ?? defaultSpawn)(spec.command, spec.args, {
35100
+ detached: true,
35101
+ stdio: "ignore",
35102
+ shell: false,
35103
+ windowsHide: true,
35104
+ ...spec.verbatim ? { windowsVerbatimArguments: true } : {}
35105
+ });
35106
+ await awaitOpenerStart(child, spec.command);
34893
35107
  return { action: "opened", path: path3 };
34894
35108
  }
34895
- var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT;
35109
+ var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT, WINDOWS_EDITOR_EXE;
34896
35110
  var init_local_file = __esm({
34897
35111
  "packages/server/src/local-file.ts"() {
34898
35112
  "use strict";
34899
35113
  MARKDOWN_FILE_EXT = /\.(?:md|mdx|markdown)$/i;
34900
35114
  MARKDOWN_READ_LIMIT = 1024 * 1024;
35115
+ WINDOWS_EDITOR_EXE = {
35116
+ cursor: { exe: ["Programs", "cursor", "Cursor.exe"], shim: "cursor" },
35117
+ vscode: { exe: ["Programs", "Microsoft VS Code", "Code.exe"], shim: "code" }
35118
+ };
34901
35119
  }
34902
35120
  });
34903
35121
 
@@ -35068,12 +35286,12 @@ var init_awaiting = __esm({
35068
35286
  });
35069
35287
 
35070
35288
  // packages/server/src/backend/project-mcp-servers.ts
35071
- import { readFileSync as readFileSync18 } from "node:fs";
35289
+ import { readFileSync as readFileSync19 } from "node:fs";
35072
35290
  import { homedir as homedir12 } from "node:os";
35073
35291
  import { join as join27 } from "node:path";
35074
35292
  function readJsonObject(path3) {
35075
35293
  try {
35076
- const parsed = JSON.parse(readFileSync18(path3, "utf8"));
35294
+ const parsed = JSON.parse(readFileSync19(path3, "utf8"));
35077
35295
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
35078
35296
  } catch {
35079
35297
  return void 0;
@@ -35198,17 +35416,17 @@ var init_project_mcp_servers = __esm({
35198
35416
  });
35199
35417
 
35200
35418
  // packages/server/src/worker-plugin-dir.ts
35201
- import { existsSync as existsSync8 } from "node:fs";
35419
+ import { existsSync as existsSync9 } from "node:fs";
35202
35420
  import { dirname as dirname10, join as join28 } from "node:path";
35203
35421
  import { fileURLToPath as fileURLToPath3 } from "node:url";
35204
35422
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
35205
35423
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
35206
- if (override && existsSync8(join28(override, ".claude-plugin", "plugin.json")))
35424
+ if (override && existsSync9(join28(override, ".claude-plugin", "plugin.json")))
35207
35425
  return override;
35208
35426
  let current = dirname10(fileURLToPath3(moduleUrl));
35209
35427
  for (; ; ) {
35210
35428
  const candidate = join28(current, "cc-worker");
35211
- if (existsSync8(join28(candidate, ".claude-plugin", "plugin.json"))) return candidate;
35429
+ if (existsSync9(join28(candidate, ".claude-plugin", "plugin.json"))) return candidate;
35212
35430
  const parent = dirname10(current);
35213
35431
  if (parent === current) return void 0;
35214
35432
  current = parent;
@@ -35325,9 +35543,11 @@ nothing. Frizz bumps you for one, twice, then gives up \u2014 a turn wasted each
35325
35543
  \`mcp__frizz__ask\` and \`mcp__frizz__watch\` each record a ROW, and frizz reads all three as a sign-off
35326
35544
  \u2014 it will not bump you for a missing fence after any of them. A fence is a sentence with the lifetime
35327
35545
  of the message carrying it, so it has to be rewritten at every rest and is wrong the moment anything
35328
- changes; a row survives your turn ending, a compaction and a frizz restart. **The fences below all still
35329
- work**, they are still the right shape for the prose the human reads, and nothing is being taken away \u2014
35330
- but where a verb exists for what you are saying, call it.
35546
+ changes; a row survives your turn ending, a compaction and a frizz restart. **The \`done\` and
35547
+ \`awaiting\` fences below still work**, they are still the right shape for the prose the human reads \u2014
35548
+ but where a verb exists for what you are saying, call it. **A QUESTION HAS NO FENCE ANY MORE** (retired
35549
+ 2026-09-11): the only way to ask is \`mcp__frizz__ask\`, and a question written into a fence's body is
35550
+ plain prose \u2014 no card, no answer, and no sign-off.
35331
35551
 
35332
35552
  **AN OPEN QUESTION IS A STANDING SIGN-OFF \u2014 REST NORMALLY.** Any unanswered question counts, not only
35333
35553
  one you registered this turn, and frizz does not bump you for a missing fence while one is open. When
@@ -35341,8 +35561,9 @@ without it. Name what is still running in the prose (frizz lists every live shel
35341
35561
  watcher under the prompt box whether or not a fence names it). A question you no longer need answered
35342
35562
  is one you \`unask\`; only then can a park take.
35343
35563
 
35344
- Use at most ONE fenced signal block, at the very END. The fence language is the state; the body is the
35345
- card the human reads. A \` \`\`\`question \` block and real permission prompts are higher-priority asks
35564
+ Use at most ONE fenced signal block, at the very END (a question's PLACEMENT marker is not a signal
35565
+ block \u2014 see Questions for the human). The fence language is the state; the body is the card the human
35566
+ reads. An open registered question and real permission prompts are higher-priority asks
35346
35567
  than \`done\` or \`awaiting\`.
35347
35568
 
35348
35569
  **NAME THE PULL REQUEST, EVERY TIME YOU REST** \u2014 its number in EVERY resting message, not just the
@@ -35393,7 +35614,8 @@ exactly ONE of them.
35393
35614
  - **A RECOMMENDATION IS NOT A CONCLUSION, AND AN UNSENT DRAFT IS NOT A DELIVERABLE.** When the
35394
35615
  verdict is that SOMEONE SHOULD NOW DO SOMETHING \u2014 merge it, decline it, post this comment, pick
35395
35616
  one of these designs, press the button you are not allowed to press \u2014 that someone is the human,
35396
- so end on a \` \`\`\`question \` carrying your recommendation as option A. Same for anything you WROTE
35617
+ so register the question with \`mcp__frizz__ask\`, your recommendation as option A, and rest on
35618
+ it. Same for anything you WROTE
35397
35619
  but did not SEND (a drafted comment, reply, issue body or release note): \`done\` files that draft
35398
35620
  away with the thread.
35399
35621
  - Follow-up work you DISCOVERED blocks \`done\` just as hard as work you were assigned, even when it
@@ -35479,8 +35701,9 @@ exactly ONE of them.
35479
35701
  dimmed Snoozed band; any one dead, unknown or not yours \u21D2 you are BUMPED immediately and told which.
35480
35702
  **A FENCE THAT NAMES NOTHING IS NOT A PARK** \u2014 \`for:\` describes a wait, it is not one \u2014 so
35481
35703
  "waiting on the checks" plus a duration just sits in the queue: REGISTER the PR and name it, and
35482
- if nothing is actually running, end with \`\`\`done or ask a \`\`\`question.
35483
- - **WAITING ON A PERSON IS A \` \`\`\`question \`.** There is no human gate and no prose park.
35704
+ if nothing is actually running, end with \`\`\`done or register a question with \`mcp__frizz__ask\`.
35705
+ - **WAITING ON A PERSON IS A REGISTERED QUESTION** \u2014 \`mcp__frizz__ask\`. There is no human gate, no
35706
+ prose park, and no question fence.
35484
35707
  - **CI, RELEASES, DEPLOYS AND MERGE PROGRESSION ARE AUTOMATABLE \u2014 never \` \`\`\`awaiting \` them
35485
35708
  BLINDLY.** For a pull request, \`mcp__frizz__watch_pr\`. For anything else stay ACTIVE: dispatch a
35486
35709
  sub-agent to own the wait (its return re-invokes you), or set a timer and name it here.
@@ -35491,13 +35714,14 @@ exactly ONE of them.
35491
35714
  is still running, and emit a FRESH fence naming it. If it turns out to be automatable, arm the
35492
35715
  active wait instead and do not fence.
35493
35716
 
35494
- - \` \`\`\`question \` \u2014 you need the human's input; see **Questions for the human**.
35717
+ - A REGISTERED QUESTION \u2014 \`mcp__frizz__ask\`, not a fence: you need the human's input, and the open
35718
+ row IS the handback; see **Questions for the human**.
35495
35719
 
35496
35720
  ### When the work is finished but the thread found more
35497
35721
 
35498
35722
  Your mandate is complete and you turned up something else \u2014 a bug you were not sent for, a follow-up
35499
35723
  the change implies. \`done\` is barred (the finding dies with the card), \`awaiting\` is barred (nothing
35500
- is running), and \`question\` only looks barred. That deadlock is not real, and stretching \`done\` is not
35724
+ is running), and asking only looks barred. That deadlock is not real, and stretching \`done\` is not
35501
35725
  how you break it. Take the first exit that fits:
35502
35726
 
35503
35727
  1. **DO IT \u2014 the default, and it covers far more than "small".** Anything inside your change's own
@@ -35506,10 +35730,10 @@ how you break it. Take the first exit that fits:
35506
35730
  is more than one sitting, dispatch an in-session
35507
35731
  SUB-AGENT: its result comes BACK to you, so the follow-up lands on YOUR card, in your diff, under one
35508
35732
  review. Then \`done\` is honest with nothing left over.
35509
- 2. **ASK** \u2014 the human should choose whether or how it happens; "nothing was pending" is not an
35510
- objection. This is the exit for work you cannot bound or verify, or that is genuinely the human's
35511
- call \u2014 and it is where a separable effort goes, as an OPTION they pick, rather than a card you
35512
- create for them.
35733
+ 2. **ASK** \u2014 \`mcp__frizz__ask\`: the human should choose whether or how it happens; "nothing was
35734
+ pending" is not an objection. This is the exit for work you cannot bound or verify, or that is
35735
+ genuinely the human's call \u2014 and it is where a separable effort goes, as an OPTION they pick, rather
35736
+ than a card you create for them.
35513
35737
  3. **HAND IT OFF TO ITS OWN CARD \u2014 the LAST resort, not the tidy one.** \`mcp__frizz__spawn_thread\` puts
35514
35738
  a separable effort on the board as its own thread; put the returned \`[title](/thread/<slug>)\` link
35515
35739
  in the body. **A spawned thread is FIRE-AND-FORGET: it reports to the HUMAN, never to you, so
@@ -35577,88 +35801,101 @@ The API call is the unit of cost, not the tokens you type: each message re-reads
35577
35801
  do not rest on a step you could take now.`;
35578
35802
  QUESTIONS = `## Questions for the human
35579
35803
 
35580
- You run under a dashboard, not a live chat, so your FINAL MESSAGE is the whole interface. Open with 2-4
35581
- sentences of status (what's done, what's in flight, what this answer unblocks), then put each question
35582
- in its own fenced \`question\` block \u2014 the dashboard renders them as answerable cards:
35804
+ You run under a dashboard, not a live chat, so a question is a ROW the human still owes an answer to,
35805
+ never a sentence in a message: \`mcp__frizz__ask\` registers it, and the dashboard renders it as an
35806
+ answerable card. **THERE IS NO \`question\` FENCE ANY MORE.** A fence with a question written in its body
35807
+ is plain prose \u2014 it draws no card, nothing can answer it, and it is not a sign-off. (Retired 2026-09-11:
35808
+ the fence was taught beside the tool for two weeks, workers went on writing it on most days, and nothing
35809
+ tracks a fence's answer \u2014 a fence is bytes in a message; the row is the only lifecycle.)
35583
35810
 
35584
- \`\`\`question
35585
- Should the settings store use SQLite or a JSON file?
35586
-
35587
- - A. SQLite \u2014 transactional, matches how sessions are already stored (recommended: consistency)
35588
- - B. JSON file \u2014 zero deps, human-editable, racy under concurrent writes
35589
- \`\`\`
35590
-
35591
- Each block must stand alone: the specific question, lettered options with a one-line tradeoff each, and
35592
- enough context to answer cold. Options go in a markdown list so each renders on its own line, and the
35593
- question itself stays on ONE line \u2014 inside a fence a newline is a HARD break, so a hard-wrapped sentence
35594
- renders ragged in the card. Mark your recommendation by writing \`recommended\` on that option's line \u2014
35595
- \`(recommended)\` or \`(recommended: one-line why)\` \u2014 and put it FIRST as \`A\`; mark exactly one. Use
35596
- MULTIPLE blocks for multiple independent questions, never one bundled block. A bare "which approach?"
35597
- with no options is a broken handoff.
35598
-
35599
- NO "I" AND NO "you" ANYWHERE IN A QUESTION \u2014 this block, or any interactive prompt. Clicking an
35600
- option is the HUMAN speaking, so first and second person flip between writer and reader: in "A. Leave
35601
- it \u2014 you run the command yourself" / "B. I run it now", neither line says which actor is which. Write
35602
- the whole block actor-explicit \u2014 each option an instruction the human hands back ("Reinstall the
35603
- hooks now") or one that names its actor outright ("the worker retries nightly", "left for the
35811
+ \`ask\` takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
35812
+ \`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
35813
+ carrying a \`label\`, a \`description\` (ONE LINE by default \u2014 name the trade-off and stop; earn more
35814
+ than that and spend it on a shape the human can SCAN, a short list, a table, a code block, the diff an
35815
+ option would produce, the message that would be posted, and never a run of one-sentence paragraphs;
35816
+ it renders inside the option, so the human sees it BEFORE choosing), an optional \`recommended\`, and
35817
+ optional \`followUps\`: questions that become live ONLY if that option is taken, so you can ask "and if
35818
+ so, which?" without asking it of somebody who said no. Written out, one registration reads:
35819
+
35820
+ question: Should the settings store use SQLite or a JSON file?
35821
+ kind: question
35822
+ options:
35823
+ - label: SQLite
35824
+ description: transactional, matches how sessions are already stored
35825
+ recommended: true
35826
+ - label: JSON file
35827
+ description: zero deps, human-editable, racy under concurrent writes
35828
+
35829
+ Each question must stand alone: the specific question on ONE line, options each with a one-line
35830
+ trade-off, and enough context to answer cold. The card letters the options A, B, C in the order you
35831
+ give them, so put the one you would take FIRST and mark it \`recommended\`; mark exactly one. Several
35832
+ independent questions are several entries of ONE \`ask\` call, never one bundled question. A bare
35833
+ "which approach?" with no options is a broken handoff.
35834
+
35835
+ NO "I" AND NO "you" ANYWHERE IN A QUESTION \u2014 the question, its options, or any interactive prompt.
35836
+ Clicking an option is the HUMAN speaking, so first and second person flip between writer and reader: in
35837
+ "Leave it \u2014 you run the command yourself" / "I run it now", neither line says which actor is which.
35838
+ Write the whole question actor-explicit \u2014 each option an instruction the human hands back ("Reinstall
35839
+ the hooks now") or one that names its actor outright ("the worker retries nightly", "left for the
35604
35840
  maintainer's own terminal"):
35605
35841
 
35606
- \`\`\`question
35607
- A reply for issue #482 explaining the workaround is drafted. Post it from the maintainer's GitHub account, or leave it a draft?
35842
+ question: A reply for issue #482 explaining the workaround is drafted. Post it from the maintainer's GitHub account, or leave it a draft?
35843
+ kind: question
35844
+ options:
35845
+ - label: Post it
35846
+ description: the reporter is blocked and the workaround is verified
35847
+ recommended: true
35848
+ - label: Leave it in the handoff
35849
+ description: for edits before anything is posted
35608
35850
 
35609
- - A. Post it \u2014 the reporter is blocked and the workaround is verified (recommended)
35610
- - B. Leave it in the handoff, for edits before anything is posted
35611
- \`\`\`
35612
-
35613
- Write the block in the human's OWN vocabulary: they have their original prompt and nothing else \u2014 not
35851
+ Write the question in the human's OWN vocabulary: they have their original prompt and nothing else \u2014 not
35614
35852
  your plan, your notes, or the names you settled on while working. A name you coined mid-effort (a
35615
35853
  phase, lane, tier, step or section number, "the C path", "the second variant") means nothing to them, so
35616
35854
  translate it into what the thing does. Minimize code identifiers by default: lead with the behavior, and
35617
35855
  spend a file, symbol or flag where it genuinely reads clearest or the human already uses it. Every input
35618
35856
  the choice needs \u2014 what happens today, each option's user-visible consequence, any number that matters \u2014
35619
- belongs INSIDE the block; "as discussed above" points at something they cannot see.
35620
-
35621
- A GO/NO-GO gate is not a special fence \u2014 it is an ordinary \`question\` with two options (the go and the
35622
- decline, each a real choice the human can click). Tag the fence only to change how it renders:
35623
- \`question danger\` for the genuinely irreversible (force-merge, deletion, history rewrite, prod
35624
- rollback), \`question multi\` for select-several triage.
35625
-
35626
- **BETTER THAN A FENCED BLOCK \u2014 REGISTER THE QUESTION:** \`mcp__frizz__ask\` creates a ROW the human
35627
- still owes an answer to, instead of a block inside a message. That is the difference that matters: a
35628
- fence is answerable only while its message is on screen, so a question written into one is gone once
35629
- the transcript scrolls or your context is compacted, while a registration is still there tomorrow. It
35630
- takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
35631
- \`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
35632
- carrying a \`label\`, a \`description\` (ONE LINE by default \u2014 name the trade-off and stop; earn more
35633
- than that and spend it on a shape the human can SCAN, a short list, a table, a code block, the diff an
35634
- option would produce, the message that would be posted, and never a run of one-sentence paragraphs;
35635
- it renders inside the option, so the human sees it BEFORE choosing), an optional
35636
- \`recommended\`, and optional \`followUps\`: questions that become live ONLY if that option is taken,
35637
- so you can ask "and if so, which?" without asking it of somebody who said no. **REGISTERING A QUESTION DOES NOT END YOUR TURN** \u2014
35638
- it carries no timeout and waits on a person, so keep doing everything that does not depend on the
35639
- answer. The answer comes back as its own wake, restating what was asked. Withdraw one you no longer
35640
- need with \`mcp__frizz__unask\`, above all when you work the answer out yourself. **ON A THREAD
35641
- CARRYING A GOAL IT REFUSES** \u2014 a standing "keep going, decide for yourself" is autonomous mode, and the
35642
- refusal hands that instruction back at the moment it matters. Decide, and say which way you went. A
35643
- genuinely human-owned call still belongs in the prose of your FINAL MESSAGE; autonomous does not mean
35644
- nobody reads.
35857
+ belongs INSIDE the question and its options; "as discussed above" points at something they cannot see.
35858
+
35859
+ A GO/NO-GO gate is not a special kind \u2014 it is an ordinary \`question\` with two options (the go and the
35860
+ decline, each a real choice the human can click). Tag it only to change how it renders: \`danger\` for
35861
+ the genuinely irreversible (force-merge, deletion, history rewrite, prod rollback), \`kind: multi\` for
35862
+ select-several triage.
35863
+
35864
+ **REGISTERING A QUESTION DOES NOT END YOUR TURN** \u2014 it carries no timeout and waits on a person, so
35865
+ keep doing everything that does not depend on the answer, and register the question the moment you
35866
+ find it rather than saving it for the end. The answer comes back as its own wake, restating what was
35867
+ asked. Withdraw one you no longer need with \`mcp__frizz__unask\`, above all when you work the answer
35868
+ out yourself. **ON A THREAD CARRYING A GOAL IT REFUSES** \u2014 a standing "keep going, decide for yourself"
35869
+ is autonomous mode, and the refusal hands that instruction back at the moment it matters. Decide, and
35870
+ say which way you went. A genuinely human-owned call still belongs in the prose of your FINAL MESSAGE;
35871
+ autonomous does not mean nobody reads.
35645
35872
 
35646
35873
  **EVERY OPEN QUESTION DRAWS ITS OWN CARD AT ITS REST \u2014 never write it into the handoff.** The
35647
- registered card renders whether you mention it or not, and a fence that names or restates a registered
35648
- question draws NOTHING: one question, one card, and answering the registered one is what settles the
35649
- row and un-gates \`done\`. So the handoff's job is the reasoning around the ask, not the ask itself.
35650
- (Mid-prose placement \u2014 an empty \`\`\`question fence carrying the row's id \u2014 was retired 2026-08-30;
35651
- a marker you still write draws nothing.)
35874
+ registered card renders whether you mention it or not, and prose that restates a registered question
35875
+ draws NOTHING: one question, one card, and answering the registered one is what settles the row and
35876
+ un-gates \`done\`. So the handoff's job is the reasoning around the ask, not the ask itself.
35877
+
35878
+ **TO PLACE A QUESTION INSIDE YOUR PROSE, WRITE AN EMPTY FENCE NAMING ITS ID** \u2014 the id \`ask\` returned
35879
+ (\`mcp__frizz__activity\` lists them). At the point your write-up has earned the ask \u2014 after the
35880
+ paragraph that sets it up, before the one that says what happens either way:
35881
+
35882
+ \`\`\`question qst_ab12cd34
35883
+ \`\`\`
35884
+
35885
+ Frizz draws the registered card THERE instead of at the tail. Nothing goes in the body: the card comes
35886
+ from the ROW, so nothing about the question is ever guessed from prose, and a marker naming no open
35887
+ question of yours draws nothing. One marker per question. Placement is OPTIONAL \u2014 a question with no
35888
+ marker renders at the tail of the rest, and a handoff that writes no marker loses nothing. Every answer
35889
+ of the rest is sent together, with ONE Send button at the tail, wherever the cards sit. (Placement was
35890
+ retired 2026-08-30 and restored 2026-09-11 with the free-form fence's retirement: the marker references
35891
+ a row, which is exactly what a fence with a body never did.)
35652
35892
 
35653
35893
  A question you no longer want answered is not one you leave out of the write-up \u2014 it is one you
35654
35894
  \`unask\`, which is the difference between deciding something yourself and quietly hoping nobody
35655
35895
  answers. Questions asked in one \`ask\` call render together and send as one batch.
35656
35896
 
35657
- Fence a question you did NOT register only when you mean to ask it in the message itself.
35658
-
35659
- A \` \`\`\`question \` block IS the handback \u2014 do not also emit a \`done\`/\`awaiting\` fence, and the same
35660
- holds while a REGISTERED question stands open. Answers arrive as your next user message, possibly as
35661
- terse as "1: A, 2: B".`;
35897
+ **AN OPEN REGISTERED QUESTION IS THE HANDBACK** \u2014 emit no \`done\`/\`awaiting\` fence beside it, and
35898
+ write no question fence at all. The answer arrives as your next user message, as its own wake.`;
35662
35899
  STOP_CRITERION = `## The stop criterion
35663
35900
 
35664
35901
  **COMING TO REST IS A STOP, and it needs the same justification as a question.** The far more common
@@ -35706,14 +35943,14 @@ Stop only when a wrong guess would be BOTH costly AND hard to undo: destructive
35706
35943
  security posture with real exposure; product/UX direction that is genuinely the human's taste to set;
35707
35944
  scope so vague that acting means inventing substantial new code with no way to check it. Everything
35708
35945
  else \u2014 mechanical work, clear bugs, refactors, defaults you can justify, architecture you can reverse \u2014
35709
- you finish. When you do stop, surface it as a \` \`\`\`question \` with your recommendation marked, and do
35710
- all the work that does NOT depend on the answer first.`;
35946
+ you finish. When you do stop, register it with \`mcp__frizz__ask\` with your recommendation marked, and
35947
+ do all the work that does NOT depend on the answer first.`;
35711
35948
  TRIVIAL_PROMPTS = `## Trivial and conversational prompts
35712
35949
 
35713
35950
  Some dispatches never deserved a work effort \u2014 a greeting, a one-line question, a joke, a test ping.
35714
35951
  Resolve them with ZERO ceremony: answer inline in one message, and close with a \` \`\`\`done \` fence whose
35715
35952
  body is one line ("Answered inline \u2014 conversational prompt, nothing to ship."). If the answer genuinely
35716
- needs a reply, ask with a \` \`\`\`question \` instead. Do not manufacture scope, restate the "task", or ask
35953
+ needs a reply, register it with \`mcp__frizz__ask\` instead. Do not manufacture scope, restate the "task", or ask
35717
35954
  clarifying questions to seem busy.`;
35718
35955
  THREAD_NAME = `## Name your thread once you know what the work is
35719
35956
 
@@ -36093,7 +36330,7 @@ Dispatches share a vocabulary for the deliverable and quality bar, not for fleet
36093
36330
  first that works? Cover and synthesize every relevant prong inline unless delegation was
36094
36331
  explicitly requested. Close with a \` \`\`\`done \` fence listing the completed research/evidence \u2014
36095
36332
  the report IS this thread's deliverable, unlike a bug/issue investigation headed for a fix, which
36096
- bare-rests; use \` \`\`\`question \` for a human call.
36333
+ bare-rests; use \`mcp__frizz__ask\` for a human call.
36097
36334
  - **Audit thread** \u2014 adversarially verify correctness, safety, or compatibility of something that
36098
36335
  exists. Exercise proportionate cases and lenses until dry; re-check load-bearing verdicts and cite
36099
36336
  evidence. Thorough coverage is required, but the audit label alone does not authorize fan-out.
@@ -36374,11 +36611,10 @@ import { homedir as homedir13 } from "node:os";
36374
36611
  function clip(text2, max) {
36375
36612
  return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
36376
36613
  }
36377
- function tildePath(path3) {
36378
- const home = homedir13();
36614
+ function tildePath(path3, home = homedir13()) {
36379
36615
  if (!path3.startsWith(home)) return path3;
36380
36616
  const rest = path3.slice(home.length);
36381
- return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
36617
+ return rest === "" ? "~" : /^[\\/]/.test(rest) ? `~${rest}` : path3;
36382
36618
  }
36383
36619
  function inputPreview(input, shown) {
36384
36620
  if (!input) return "";
@@ -36612,8 +36848,8 @@ var init_claude_permission_interactions = __esm({
36612
36848
  HAS_UNSAFE_TEXT = new RegExp(UNSAFE_TEXT_SOURCE, "u");
36613
36849
  CLAUDE_NOTES_ONLY = "(notes only)";
36614
36850
  CLAUDE_QUESTION_DECLINED = "The operator did not answer this question from the frizz dashboard. Do not ask it again with this tool \u2014 decide with the information you already have, or raise it in your final message.";
36615
- CLAUDE_ASK_DENY_MESSAGE = "Frizz could not render this question as a card. Ask it in your FINAL MESSAGE instead, in a ```question fenced block \u2014 context, the question, lettered `- A. \u2026` options, a recommendation \u2014 then end your turn. The operator answers it from the queue and the reply arrives as your next user message.";
36616
- CLAUDE_ASK_WITHDRAWN_MESSAGE = "This question was withdrawn before anyone answered it \u2014 most likely because the operator sent a message instead. Read their next message and follow it. Do not ask this again with this tool; if you still need the decision, put it in your final message as a ```question fenced block.";
36851
+ CLAUDE_ASK_DENY_MESSAGE = "Frizz could not render this question as a card. Register it with `mcp__frizz__ask` instead \u2014 context, the question, options with a one-line trade-off each, the recommended one first \u2014 never as a ```question fence, which is retired. The operator answers it from the queue and the reply arrives as your next user message.";
36852
+ CLAUDE_ASK_WITHDRAWN_MESSAGE = "This question was withdrawn before anyone answered it \u2014 most likely because the operator sent a message instead. Read their next message and follow it. Do not ask this again with this tool; if you still need the decision, register it with `mcp__frizz__ask`.";
36617
36853
  }
36618
36854
  });
36619
36855
 
@@ -37025,7 +37261,7 @@ var init_claude_agent_broker_bridge = __esm({
37025
37261
  });
37026
37262
 
37027
37263
  // packages/server/src/dispatch.ts
37028
- import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync19, realpathSync as realpathSync8, statSync as statSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
37264
+ import { closeSync as closeSync12, constants, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync20, realpathSync as realpathSync8, statSync as statSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
37029
37265
  import { basename as basename6, join as join29, dirname as dirname11 } from "node:path";
37030
37266
  import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
37031
37267
  function fallbackTitle(prompt) {
@@ -37045,7 +37281,7 @@ function fallbackTitle(prompt) {
37045
37281
  }
37046
37282
  function resolveSlug(frizzDir, base, taken) {
37047
37283
  base = ThreadSlug.parse(base);
37048
- const isTaken = (slug) => existsSync9(join29(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
37284
+ const isTaken = (slug) => existsSync10(join29(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
37049
37285
  if (!isTaken(base)) return base;
37050
37286
  for (let n = 2; ; n++) {
37051
37287
  const suffix = `-${n}`;
@@ -37078,7 +37314,7 @@ function resolveLegacyThreadFile(projectDir, value) {
37078
37314
  const fd = openSync13(path3, constants.O_RDONLY | constants.O_NOFOLLOW);
37079
37315
  try {
37080
37316
  openedBefore = fstatSync2(fd);
37081
- contents = readFileSync19(fd);
37317
+ contents = readFileSync20(fd);
37082
37318
  openedAfter = fstatSync2(fd);
37083
37319
  } finally {
37084
37320
  closeSync12(fd);
@@ -37142,7 +37378,7 @@ function monitorScriptsDir() {
37142
37378
  const plugin = workerPluginDir();
37143
37379
  if (!plugin) return void 0;
37144
37380
  const dir = join29(plugin, "skills", "gh", "scripts");
37145
- return existsSync9(join29(dir, "ci-watch.mjs")) ? dir : void 0;
37381
+ return existsSync10(join29(dir, "ci-watch.mjs")) ? dir : void 0;
37146
37382
  }
37147
37383
  function codexScratchpadHookConfig(hookScript, sessionId) {
37148
37384
  if (!hookScript || !sessionId) return {};
@@ -37201,7 +37437,7 @@ function frizzConfigBlock(projectDir) {
37201
37437
  try {
37202
37438
  const st = statSync13(path3);
37203
37439
  if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
37204
- body = readFileSync19(path3, "utf8").trim();
37440
+ body = readFileSync20(path3, "utf8").trim();
37205
37441
  } catch {
37206
37442
  return "";
37207
37443
  }
@@ -37241,7 +37477,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
37241
37477
  const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
37242
37478
  if (!pluginDir) return void 0;
37243
37479
  const scriptPath = join29(pluginDir, "bin", FRIZZ_MCP.script);
37244
- if (!existsSync9(scriptPath)) return void 0;
37480
+ if (!existsSync10(scriptPath)) return void 0;
37245
37481
  return {
37246
37482
  scriptPath,
37247
37483
  stateDir,
@@ -38089,20 +38325,20 @@ function resolveSessionProfile(row, tele) {
38089
38325
  function resolveSessionPermission(row, tele) {
38090
38326
  const saved = PermissionMode.safeParse(row.permission_mode);
38091
38327
  const pending = PermissionMode.safeParse(row.permission_pending);
38092
- const normalize3 = (mode) => effectivePermissionMode(row.backend === "codex" ? "codex" : "claude", mode);
38093
- if (saved.success && pending.success && normalize3(saved.data) === normalize3(pending.data)) return normalize3(saved.data);
38328
+ const normalize2 = (mode) => effectivePermissionMode(row.backend === "codex" ? "codex" : "claude", mode);
38329
+ if (saved.success && pending.success && normalize2(saved.data) === normalize2(pending.data)) return normalize2(saved.data);
38094
38330
  if (row.backend === "codex" && saved.success) {
38095
38331
  const observedAt = tele?.permissionModeAt ? Date.parse(tele.permissionModeAt) : NaN;
38096
38332
  const spawnedAt = Date.parse(row.spawned_at);
38097
38333
  if (tele?.permissionMode && Number.isFinite(observedAt) && Number.isFinite(spawnedAt) && observedAt >= spawnedAt) {
38098
38334
  const setAt = row.permission_set_at ? Date.parse(row.permission_set_at) : NaN;
38099
- if (Number.isFinite(setAt) && setAt > observedAt) return normalize3(saved.data);
38100
- return normalize3(tele.permissionMode);
38335
+ if (Number.isFinite(setAt) && setAt > observedAt) return normalize2(saved.data);
38336
+ return normalize2(tele.permissionMode);
38101
38337
  }
38102
- return normalize3(saved.data);
38338
+ return normalize2(saved.data);
38103
38339
  }
38104
- if (saved.success) return normalize3(saved.data);
38105
- return tele?.permissionMode ? normalize3(tele.permissionMode) : void 0;
38340
+ if (saved.success) return normalize2(saved.data);
38341
+ return tele?.permissionMode ? normalize2(tele.permissionMode) : void 0;
38106
38342
  }
38107
38343
  function resolvePendingPermission(row) {
38108
38344
  const parsed = PermissionMode.safeParse(row.permission_pending);
@@ -40264,8 +40500,8 @@ ${wakeTimeHeader(nowMs, spokeAt)}`,
40264
40500
  "in a sentence breaks the YAML, which is why `reason:` is gone.",
40265
40501
  "",
40266
40502
  "`mcp__frizz__activity` lists everything you have running, and prints it back as a ready-to-paste",
40267
- "fence. If you are not waiting on anything, you are not awaiting \u2014 end with ```done, or ask a",
40268
- "```question."
40503
+ "fence. If you are not waiting on anything, you are not awaiting \u2014 end with ```done, or register",
40504
+ "a question with `mcp__frizz__ask`."
40269
40505
  ].join("\n") : nameless ? [
40270
40506
  `${PARK_CORRECTION_NAMES_LEAD}nothing to wait on, so it is not a park \u2014 your thread is still`,
40271
40507
  "in the queue, and nothing will wake you.",
@@ -40281,15 +40517,15 @@ ${wakeTimeHeader(nowMs, spokeAt)}`,
40281
40517
  "Use `mcp__frizz__activity` to read it all back as a ready-to-paste fence.",
40282
40518
  "",
40283
40519
  "AND IF YOU ARE NOT WAITING ON ANYTHING, you are not awaiting \u2014 you are done. End with ```done,",
40284
- "or ask a ```question if you need the human."
40520
+ "or register a question with `mcp__frizz__ask` if you need the human."
40285
40521
  ].join("\n") : expired ? parkExpiredWakeMessage(status) : allFinished ? parkFinishedWakeMessage(status, dead.length !== 1) : [
40286
40522
  `${PARK_CORRECTION_NAMES_LEAD}${dead.length === 1 ? "something that is" : "things that are"} not running, so it is not a park and your thread stayed in the queue.`,
40287
40523
  "",
40288
40524
  ...status,
40289
40525
  "",
40290
40526
  "Use `mcp__frizz__activity` to read back what you actually have out, with the exact id each line",
40291
- "needs, then re-fence naming only those \u2014 or end in ```done or a ```question if there is nothing",
40292
- "left to wait for."
40527
+ "needs, then re-fence naming only those \u2014 or end in ```done or a registered question",
40528
+ "(`mcp__frizz__ask`) if there is nothing left to wait for."
40293
40529
  ].join("\n");
40294
40530
  const cause = retired.length > 0 ? "retired" : nameless ? "nameless" : expired ? "expired" : "dead";
40295
40531
  const ops = cause === "expired" ? [] : liveOpsLines({
@@ -41431,10 +41667,31 @@ import { randomBytes } from "node:crypto";
41431
41667
  import pty from "node-pty";
41432
41668
  function createLoginUtility(deps) {
41433
41669
  const spawnPty = deps.spawnPty ?? pty.spawn;
41670
+ const env = deps.env ?? process.env;
41434
41671
  const lifetimeMs = deps.lifetimeMs ?? ATTEMPT_LIFETIME_MS;
41435
41672
  const attempts = /* @__PURE__ */ new Map();
41436
41673
  function loginArgv(backend) {
41437
- return backend === "codex" ? { file: deps.codexBin ?? "codex", args: ["login"] } : { file: deps.claudeBin ?? "claude", args: ["auth", "login"] };
41674
+ if (backend === "codex") {
41675
+ const codex = resolveCodexExecutable(deps.codexBin, { env });
41676
+ return { file: codex.file, args: [...codex.args, "login"] };
41677
+ }
41678
+ return { file: resolveClaudeExecutableAbsolute(deps.claudeBin, env), args: ["auth", "login"] };
41679
+ }
41680
+ function failedAttempt(id, backend, cause) {
41681
+ const reason = cause instanceof Error ? cause.message : String(cause);
41682
+ const error = `Could not start the ${backend === "codex" ? "Codex" : "Claude"} sign-in: ${reason}`;
41683
+ return {
41684
+ id,
41685
+ backend,
41686
+ timer: setTimeout(() => teardown(id), lifetimeMs),
41687
+ buffer: `${error}\r
41688
+ `,
41689
+ bufferBytes: Buffer.byteLength(error) + 2,
41690
+ exited: true,
41691
+ error,
41692
+ dataListeners: /* @__PURE__ */ new Set(),
41693
+ exitListeners: /* @__PURE__ */ new Set()
41694
+ };
41438
41695
  }
41439
41696
  function teardown(id) {
41440
41697
  const attempt = attempts.get(id);
@@ -41452,7 +41709,7 @@ function createLoginUtility(deps) {
41452
41709
  }
41453
41710
  attempt.exitListeners.clear();
41454
41711
  try {
41455
- attempt.term.kill();
41712
+ attempt.term?.kill();
41456
41713
  } catch {
41457
41714
  }
41458
41715
  }
@@ -41464,14 +41721,22 @@ function createLoginUtility(deps) {
41464
41721
  teardown(attempt2.id);
41465
41722
  }
41466
41723
  const id = `login-${randomBytes(8).toString("hex")}`;
41467
- const { file, args } = loginArgv(backend);
41468
- const term = spawnPty(file, args, {
41469
- name: process.platform === "win32" ? "xterm-color" : "xterm-256color",
41470
- cwd: deps.cwd,
41471
- env: process.env,
41472
- cols: 120,
41473
- rows: 30
41474
- });
41724
+ let term;
41725
+ try {
41726
+ const { file, args } = loginArgv(backend);
41727
+ term = spawnPty(file, args, {
41728
+ name: process.platform === "win32" ? "xterm-color" : "xterm-256color",
41729
+ cwd: deps.cwd,
41730
+ env,
41731
+ cols: 120,
41732
+ rows: 30
41733
+ });
41734
+ } catch (cause) {
41735
+ const failed = failedAttempt(id, backend, cause);
41736
+ failed.timer.unref?.();
41737
+ attempts.set(id, failed);
41738
+ return { attemptId: id };
41739
+ }
41475
41740
  const attempt = {
41476
41741
  id,
41477
41742
  backend,
@@ -41523,7 +41788,7 @@ function createLoginUtility(deps) {
41523
41788
  write: (data) => {
41524
41789
  if (!attempt.exited) {
41525
41790
  try {
41526
- attempt.term.write(data);
41791
+ attempt.term?.write(data);
41527
41792
  } catch {
41528
41793
  }
41529
41794
  }
@@ -41531,7 +41796,7 @@ function createLoginUtility(deps) {
41531
41796
  resize: (cols, rows) => {
41532
41797
  if (!attempt.exited) {
41533
41798
  try {
41534
- attempt.term.resize(cols, rows);
41799
+ attempt.term?.resize(cols, rows);
41535
41800
  } catch {
41536
41801
  }
41537
41802
  }
@@ -41552,7 +41817,7 @@ function createLoginUtility(deps) {
41552
41817
  status(attemptId) {
41553
41818
  const attempt = attempts.get(attemptId);
41554
41819
  if (!attempt) return { state: "exited" };
41555
- return { state: attempt.exited ? "exited" : "running", backend: attempt.backend };
41820
+ return { state: attempt.exited ? "exited" : "running", backend: attempt.backend, ...attempt.error ? { error: attempt.error } : {} };
41556
41821
  },
41557
41822
  cancel(attemptId) {
41558
41823
  teardown(attemptId);
@@ -41566,6 +41831,8 @@ var ATTEMPT_LIFETIME_MS, REPLAY_CAP_BYTES;
41566
41831
  var init_login_utility = __esm({
41567
41832
  "packages/server/src/login-utility.ts"() {
41568
41833
  "use strict";
41834
+ init_claude_broker_host();
41835
+ init_codex_executable();
41569
41836
  ATTEMPT_LIFETIME_MS = 10 * 60 * 1e3;
41570
41837
  REPLAY_CAP_BYTES = 256 * 1024;
41571
41838
  }
@@ -41677,7 +41944,7 @@ var init_stale_socket_sweep = __esm({
41677
41944
 
41678
41945
  // packages/server/src/orphan-reaper.ts
41679
41946
  import { execFileSync as execFileSync4 } from "node:child_process";
41680
- import { readFileSync as readFileSync20 } from "node:fs";
41947
+ import { readFileSync as readFileSync21 } from "node:fs";
41681
41948
  import { dirname as dirname13 } from "node:path";
41682
41949
  function firstTokenBasename(command) {
41683
41950
  const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
@@ -41833,7 +42100,7 @@ ${chunk}`;
41833
42100
  }
41834
42101
  return rows;
41835
42102
  }
41836
- function reapSubtrees(seed, rows, protectedPids, live, kill = defaultKill) {
42103
+ function reapSubtrees(seed, rows, protectedPids, live, kill = defaultKill2) {
41837
42104
  const keep = (row) => protectedPids.has(row.pid) || isSessionRoot(row.command) || isTmuxServer(row.command) || row.slug !== null && live.has(row.slug);
41838
42105
  const order = subtreeKillOrder(seed, rows, keep);
41839
42106
  for (const pid of order) kill(pid);
@@ -41885,8 +42152,9 @@ function sweepOrphansOnce(deps = {}) {
41885
42152
  let rows;
41886
42153
  try {
41887
42154
  rows = enumerateProcs(exec, { platform: deps.platform, readEnv: deps.readEnv });
41888
- } catch {
41889
- return { reaped: 0, deadSlugs: [], liveSlugs: [] };
42155
+ } catch (error) {
42156
+ const code = error?.code;
42157
+ return { reaped: 0, deadSlugs: [], liveSlugs: [], ...code === "ENOENT" ? { unavailable: "`ps` is not on PATH" } : {} };
41890
42158
  }
41891
42159
  const protectedPids = selfAndAncestors(rows, selfPid);
41892
42160
  const { reap, liveSlugs } = decideOrphans(rows, { minAgeMs, protectedPids });
@@ -41910,14 +42178,25 @@ function sweepStaleBrokerSockets() {
41910
42178
  }
41911
42179
  }
41912
42180
  function startOrphanReaper(deps = {}) {
42181
+ if ((deps.platform ?? process.platform) === "win32") {
42182
+ deps.log?.(UNAVAILABLE_ON_WINDOWS);
42183
+ return () => {
42184
+ };
42185
+ }
41913
42186
  const intervalMs = deps.intervalMs ?? ORPHAN_REAP_INTERVAL_MS;
42187
+ const unavailable = (result) => {
42188
+ if (!result.unavailable) return false;
42189
+ deps.log?.(`orphan-reaper: unavailable on this machine \u2014 ${result.unavailable}; background processes a stopped thread leaves behind are not collected here`);
42190
+ return true;
42191
+ };
41914
42192
  try {
41915
- sweepOrphansOnce(deps);
42193
+ if (unavailable(sweepOrphansOnce(deps))) return () => {
42194
+ };
41916
42195
  } catch {
41917
42196
  }
41918
42197
  const timer = setInterval(() => {
41919
42198
  try {
41920
- sweepOrphansOnce(deps);
42199
+ if (unavailable(sweepOrphansOnce(deps))) clearInterval(timer);
41921
42200
  } catch {
41922
42201
  }
41923
42202
  }, intervalMs);
@@ -41932,7 +42211,7 @@ function startOrphanReaper(deps = {}) {
41932
42211
  clearInterval(socketTimer);
41933
42212
  };
41934
42213
  }
41935
- var ORPHAN_REAP_INTERVAL_MS, ORPHAN_MIN_AGE_MS, SLUG_RE, defaultExec, defaultReadEnv, defaultKill, STALE_SOCKET_SWEEP_INTERVAL_MS, STALE_SOCKET_SWEEP_DELAY_MS, CLAUDE_BROKER_SOCKET_PREFIX;
42214
+ var ORPHAN_REAP_INTERVAL_MS, ORPHAN_MIN_AGE_MS, SLUG_RE, defaultExec, defaultReadEnv, defaultKill2, STALE_SOCKET_SWEEP_INTERVAL_MS, STALE_SOCKET_SWEEP_DELAY_MS, CLAUDE_BROKER_SOCKET_PREFIX, UNAVAILABLE_ON_WINDOWS;
41936
42215
  var init_orphan_reaper = __esm({
41937
42216
  "packages/server/src/orphan-reaper.ts"() {
41938
42217
  "use strict";
@@ -41944,12 +42223,12 @@ var init_orphan_reaper = __esm({
41944
42223
  defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
41945
42224
  defaultReadEnv = (pid) => {
41946
42225
  try {
41947
- return readFileSync20(`/proc/${pid}/environ`, "utf8");
42226
+ return readFileSync21(`/proc/${pid}/environ`, "utf8");
41948
42227
  } catch {
41949
42228
  return null;
41950
42229
  }
41951
42230
  };
41952
- defaultKill = (pid) => {
42231
+ defaultKill2 = (pid) => {
41953
42232
  try {
41954
42233
  process.kill(pid, "SIGKILL");
41955
42234
  } catch {
@@ -41958,6 +42237,7 @@ var init_orphan_reaper = __esm({
41958
42237
  STALE_SOCKET_SWEEP_INTERVAL_MS = 10 * 6e4;
41959
42238
  STALE_SOCKET_SWEEP_DELAY_MS = 3e4;
41960
42239
  CLAUDE_BROKER_SOCKET_PREFIX = "frizz-claude-";
42240
+ UNAVAILABLE_ON_WINDOWS = "orphan-reaper: unavailable on Windows \u2014 a worker's FRIZZ_THREAD is read from its process environment, which Windows does not expose; background processes a stopped thread leaves behind are not collected here, and a live thread's runaway work is not reported";
41961
42241
  }
41962
42242
  });
41963
42243
 
@@ -45299,7 +45579,7 @@ var init_server = __esm({
45299
45579
  });
45300
45580
 
45301
45581
  // packages/server/src/repair.ts
45302
- import { existsSync as existsSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
45582
+ import { existsSync as existsSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "node:fs";
45303
45583
  import { basename as basename7, dirname as dirname14, resolve as resolve8 } from "node:path";
45304
45584
  function quoteValue(v) {
45305
45585
  if (/^[\w./#:+-]+$/.test(v)) return v;
@@ -45314,8 +45594,8 @@ function repairThreadFile(frizzDir, file) {
45314
45594
  const abs = resolve8(root, file);
45315
45595
  if (dirname14(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
45316
45596
  if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
45317
- if (!existsSync11(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
45318
- const content = readFileSync21(abs, "utf8");
45597
+ if (!existsSync12(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
45598
+ const content = readFileSync22(abs, "utf8");
45319
45599
  const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
45320
45600
  if (firstLine2?.trim() === "---") {
45321
45601
  throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
@@ -45506,9 +45786,9 @@ var init_repo_files = __esm({
45506
45786
  });
45507
45787
 
45508
45788
  // cc-worker/hooks/bash-background.mjs
45509
- import { readFileSync as readFileSync22 } from "node:fs";
45789
+ import { readFileSync as readFileSync23, realpathSync as realpathSync10 } from "node:fs";
45510
45790
  import { basename as basename8 } from "node:path";
45511
- import { pathToFileURL } from "node:url";
45791
+ import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
45512
45792
  function emit(obj) {
45513
45793
  process.stdout.write(JSON.stringify(obj));
45514
45794
  process.exit(0);
@@ -45644,8 +45924,14 @@ function evaluateBashBackgroundHook(input, env = process.env) {
45644
45924
  }
45645
45925
  };
45646
45926
  }
45647
- function isDirectHookExecution(argv1, moduleUrl) {
45648
- return typeof argv1 === "string" && basename8(argv1) === "bash-background.mjs" && pathToFileURL(argv1).href === moduleUrl;
45927
+ function isDirectHookExecution(argv1, moduleUrl, realpath = realpathSync10) {
45928
+ if (typeof argv1 !== "string" || basename8(argv1) !== "bash-background.mjs") return false;
45929
+ if (pathToFileURL(argv1).href === moduleUrl) return true;
45930
+ try {
45931
+ return realpath(argv1) === realpath(fileURLToPath4(moduleUrl));
45932
+ } catch {
45933
+ return false;
45934
+ }
45649
45935
  }
45650
45936
  var LOCAL_SHELL_SCRIPT;
45651
45937
  var init_bash_background = __esm({
@@ -45655,7 +45941,7 @@ var init_bash_background = __esm({
45655
45941
  if (isDirectHookExecution(process.argv[1], import.meta.url)) {
45656
45942
  try {
45657
45943
  const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
45658
- emit(evaluateBashBackgroundHook(JSON.parse(readFileSync22(0, "utf8")), env));
45944
+ emit(evaluateBashBackgroundHook(JSON.parse(readFileSync23(0, "utf8")), env));
45659
45945
  } catch {
45660
45946
  emit({});
45661
45947
  }
@@ -45664,7 +45950,7 @@ var init_bash_background = __esm({
45664
45950
  });
45665
45951
 
45666
45952
  // packages/server/src/transcript.ts
45667
- import { closeSync as closeSync13, existsSync as existsSync12, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync23, readSync as readSync7, renameSync as renameSync9, statSync as statSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
45953
+ import { closeSync as closeSync13, existsSync as existsSync13, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync24, readSync as readSync7, renameSync as renameSync9, statSync as statSync15, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
45668
45954
  import { createHash as createHash13 } from "node:crypto";
45669
45955
  import { StringDecoder as StringDecoder4 } from "node:string_decoder";
45670
45956
  import { join as join35 } from "node:path";
@@ -46286,7 +46572,7 @@ function persistBase64Image(mediaType, data, idKey) {
46286
46572
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46287
46573
  const path3 = join35(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
46288
46574
  try {
46289
- if (existsSync12(path3)) return path3;
46575
+ if (existsSync13(path3)) return path3;
46290
46576
  if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
46291
46577
  const buf = Buffer.from(data, "base64");
46292
46578
  if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
@@ -46307,10 +46593,10 @@ function persistSentFile(srcPath, idKey) {
46307
46593
  try {
46308
46594
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46309
46595
  const dest = join35(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
46310
- if (existsSync12(dest)) return dest;
46596
+ if (existsSync13(dest)) return dest;
46311
46597
  const size = statSync15(srcPath).size;
46312
46598
  if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
46313
- const buf = readFileSync23(srcPath);
46599
+ const buf = readFileSync24(srcPath);
46314
46600
  if (!looksLikeImage(buf, outExt)) return void 0;
46315
46601
  mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
46316
46602
  const tmp = join35(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
@@ -46751,7 +47037,7 @@ function readTranscript(project, sessionId) {
46751
47037
  }
46752
47038
  function verifyIncrementalParse(path3, identityPrefix, incremental) {
46753
47039
  try {
46754
- const fresh = parseTranscript(readFileSync23(path3, "utf8"), identityPrefix);
47040
+ const fresh = parseTranscript(readFileSync24(path3, "utf8"), identityPrefix);
46755
47041
  const a = JSON.stringify(incremental);
46756
47042
  const b = JSON.stringify(fresh);
46757
47043
  if (a !== b) {
@@ -47807,7 +48093,7 @@ function sourceForThread(project, storage, slug, backendFor) {
47807
48093
  }
47808
48094
  if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
47809
48095
  const claudePath = resolveTranscriptPath(project, slug);
47810
- if (existsSync12(claudePath)) {
48096
+ if (existsSync13(claudePath)) {
47811
48097
  return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
47812
48098
  }
47813
48099
  const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
@@ -48073,7 +48359,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
48073
48359
  }
48074
48360
  function readCodexTranscriptFile(absPath, nativeId = absPath) {
48075
48361
  try {
48076
- return parseCodexTranscript(readFileSync23(absPath, "utf8"), `codex:${nativeId}`);
48362
+ return parseCodexTranscript(readFileSync24(absPath, "utf8"), `codex:${nativeId}`);
48077
48363
  } catch {
48078
48364
  return [];
48079
48365
  }
@@ -48171,7 +48457,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
48171
48457
  function readTranscriptFile(absPath) {
48172
48458
  try {
48173
48459
  const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
48174
- return parseTranscript(readFileSync23(absPath, "utf8"), `claude-file:${pathKey}`);
48460
+ return parseTranscript(readFileSync24(absPath, "utf8"), `claude-file:${pathKey}`);
48175
48461
  } catch {
48176
48462
  return [];
48177
48463
  }
@@ -48260,15 +48546,30 @@ function validateExternalUrl(raw2) {
48260
48546
  }
48261
48547
  return { ok: true, url: parsed.toString() };
48262
48548
  }
48263
- function openExternalUrl(raw2) {
48549
+ function externalUrlOpenCommand(url, platform3 = process.platform) {
48550
+ if (platform3 === "darwin") return { command: "open", args: [url] };
48551
+ if (platform3 === "win32") return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url] };
48552
+ return { command: "xdg-open", args: [url] };
48553
+ }
48554
+ async function openExternalUrl(raw2, options = {}) {
48264
48555
  const v = validateExternalUrl(raw2);
48265
48556
  if (!v.ok) throw new Error(v.reason);
48266
- const [cmd, args] = process.platform === "darwin" ? ["open", [v.url]] : ["xdg-open", [v.url]];
48267
- spawn6(cmd, args, { detached: true, stdio: "ignore" }).unref();
48557
+ const spec = externalUrlOpenCommand(v.url, options.platform);
48558
+ const child = (options.spawn ?? spawn6)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
48559
+ try {
48560
+ await awaitOpenerStart(child, spec.command);
48561
+ return { opened: true };
48562
+ } catch (error) {
48563
+ const message = error instanceof Error ? error.message : String(error);
48564
+ (options.onError ?? ((line) => log.warn("open-external", line)))(`could not open ${v.url}: ${message}`);
48565
+ return { opened: false, error: message };
48566
+ }
48268
48567
  }
48269
48568
  var init_open_external = __esm({
48270
48569
  "packages/server/src/open-external.ts"() {
48271
48570
  "use strict";
48571
+ init_local_file();
48572
+ init_logging();
48272
48573
  }
48273
48574
  });
48274
48575
 
@@ -48597,7 +48898,14 @@ var init_account_actions = __esm({
48597
48898
  function shellQuote(value) {
48598
48899
  return `'${value.replace(/'/g, `'"'"'`)}'`;
48599
48900
  }
48600
- function providerResumeCommand(backend, projectDir, sessionId) {
48901
+ function powershellQuote(value) {
48902
+ return `'${value.replace(/'/g, "''")}'`;
48903
+ }
48904
+ function providerResumeCommand(backend, projectDir, sessionId, platform3 = process.platform) {
48905
+ if (platform3 === "win32") {
48906
+ const resume2 = backend === "codex" ? `& codex resume ${powershellQuote(sessionId)} --dangerously-bypass-approvals-and-sandbox` : `& claude --resume ${powershellQuote(sessionId)} --dangerously-skip-permissions`;
48907
+ return `Set-Location -LiteralPath ${powershellQuote(projectDir)}; ${resume2}`;
48908
+ }
48601
48909
  const resume = backend === "codex" ? `codex resume ${shellQuote(sessionId)} --dangerously-bypass-approvals-and-sandbox` : `claude --resume ${shellQuote(sessionId)} --dangerously-skip-permissions`;
48602
48910
  return `cd ${shellQuote(projectDir)} && ${resume}`;
48603
48911
  }
@@ -48945,11 +49253,11 @@ var init_directory_picker = __esm({
48945
49253
  });
48946
49254
 
48947
49255
  // packages/server/src/router.ts
48948
- import { readFileSync as readFileSync24, statSync as statSync17 } from "node:fs";
49256
+ import { readFileSync as readFileSync25, statSync as statSync17 } from "node:fs";
48949
49257
  import { join as join36, resolve as resolve9 } from "node:path";
48950
49258
  import { randomUUID as randomUUID16 } from "node:crypto";
48951
49259
  import { basename as basename9, dirname as dirname15 } from "node:path";
48952
- import { existsSync as existsSync13, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
49260
+ import { existsSync as existsSync14, mkdirSync as mkdirSync16, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
48953
49261
  import { homedir as homedir17 } from "node:os";
48954
49262
  function validateGithubDispatchProfile(input) {
48955
49263
  validateThreadProfile(input.backend, input.model, input.effort);
@@ -49159,7 +49467,7 @@ function storeProjectIcon(id, name, bytes) {
49159
49467
  function setProjectIconFromFile(id, file) {
49160
49468
  let bytes;
49161
49469
  try {
49162
- bytes = readFileSync24(file);
49470
+ bytes = readFileSync25(file);
49163
49471
  } catch {
49164
49472
  throw new Error("That file could not be read.");
49165
49473
  }
@@ -50779,7 +51087,8 @@ Say which way you went and why in your write-up, so the human can course-correct
50779
51087
  openExternal: mutation({
50780
51088
  input: external_exports.object({ url: external_exports.string() }),
50781
51089
  handler: async ({ input }) => {
50782
- openExternalUrl(input.url);
51090
+ const result = await openExternalUrl(input.url);
51091
+ if (!result.opened) throw new Error(result.error);
50783
51092
  }
50784
51093
  }),
50785
51094
  // A local file can be opened only after its canonical real path is contained by the openable roots
@@ -51187,7 +51496,7 @@ Say which way you went and why in your write-up, so the human can course-correct
51187
51496
  }
51188
51497
  const updated = clearProjectIcon(input.id);
51189
51498
  if (!updated) throw new Error("No such project.");
51190
- return projectCard(updated, !existsSync13(updated.path));
51499
+ return projectCard(updated, !existsSync14(updated.path));
51191
51500
  }
51192
51501
  }),
51193
51502
  /**
@@ -51593,7 +51902,7 @@ var init_compression = __esm({
51593
51902
  });
51594
51903
 
51595
51904
  // packages/server/src/local-image.ts
51596
- import { readFileSync as readFileSync25, realpathSync as realpathSync10, statSync as statSync18 } from "node:fs";
51905
+ import { readFileSync as readFileSync26, realpathSync as realpathSync11, statSync as statSync18 } from "node:fs";
51597
51906
  import { extname as extname2, isAbsolute as isAbsolute6 } from "node:path";
51598
51907
  function resolveLocalImage(rawPath) {
51599
51908
  if (!rawPath || !isAbsolute6(rawPath)) return { status: 400 };
@@ -51601,13 +51910,13 @@ function resolveLocalImage(rawPath) {
51601
51910
  if (!contentType) return { status: 400 };
51602
51911
  let real;
51603
51912
  try {
51604
- real = realpathSync10(rawPath);
51913
+ real = realpathSync11(rawPath);
51605
51914
  } catch {
51606
51915
  return { status: 404 };
51607
51916
  }
51608
51917
  try {
51609
51918
  if (!statSync18(real).isFile()) return { status: 404 };
51610
- return { status: 200, contentType, body: readFileSync25(real) };
51919
+ return { status: 200, contentType, body: readFileSync26(real) };
51611
51920
  } catch {
51612
51921
  return { status: 404 };
51613
51922
  }
@@ -51627,7 +51936,7 @@ var init_local_image = __esm({
51627
51936
  });
51628
51937
 
51629
51938
  // packages/server/src/project-icon.ts
51630
- import { readFileSync as readFileSync26, readdirSync as readdirSync10, realpathSync as realpathSync11, statSync as statSync19 } from "node:fs";
51939
+ import { readFileSync as readFileSync27, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync19 } from "node:fs";
51631
51940
  import { homedir as homedir18 } from "node:os";
51632
51941
  import { basename as basename10, dirname as dirname16, extname as extname3, join as join37, relative as relative2, sep as sep3 } from "node:path";
51633
51942
  function normalizeStem(stem) {
@@ -51689,7 +51998,7 @@ function iconDirectories(root) {
51689
51998
  function manifestIcons(manifestPath) {
51690
51999
  let parsed;
51691
52000
  try {
51692
- parsed = JSON.parse(readFileSync26(manifestPath, "utf8"));
52001
+ parsed = JSON.parse(readFileSync27(manifestPath, "utf8"));
51693
52002
  } catch {
51694
52003
  return [];
51695
52004
  }
@@ -51776,21 +52085,21 @@ function resolveProjectIconResponse(id, options = {}) {
51776
52085
  if (!contentType) return { status: 404 };
51777
52086
  let real;
51778
52087
  try {
51779
- real = realpathSync11(path3);
52088
+ real = realpathSync12(path3);
51780
52089
  if (!statSync19(real).isFile()) return { status: 404 };
51781
52090
  } catch {
51782
52091
  return { status: 404 };
51783
52092
  }
51784
52093
  const roots = [entry.path, projectStateDir(id, home)].map((root) => {
51785
52094
  try {
51786
- return realpathSync11(root);
52095
+ return realpathSync12(root);
51787
52096
  } catch {
51788
52097
  return root;
51789
52098
  }
51790
52099
  });
51791
52100
  if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
51792
52101
  try {
51793
- return { status: 200, contentType, body: readFileSync26(real) };
52102
+ return { status: 200, contentType, body: readFileSync27(real) };
51794
52103
  } catch {
51795
52104
  return { status: 404 };
51796
52105
  }
@@ -51902,7 +52211,7 @@ var init_project_icon = __esm({
51902
52211
  });
51903
52212
 
51904
52213
  // packages/server/src/local-visualization.ts
51905
- import { readdirSync as readdirSync11, readFileSync as readFileSync27, realpathSync as realpathSync12, statSync as statSync20 } from "node:fs";
52214
+ import { readdirSync as readdirSync11, readFileSync as readFileSync28, realpathSync as realpathSync13, statSync as statSync20 } from "node:fs";
51906
52215
  import { join as join38, sep as sep4 } from "node:path";
51907
52216
  function children(path3, pattern) {
51908
52217
  try {
@@ -51919,8 +52228,8 @@ function resolveFragment(projectDir, sessionId, file) {
51919
52228
  let projectReal;
51920
52229
  let baseReal;
51921
52230
  try {
51922
- projectReal = realpathSync12(projectDir);
51923
- baseReal = realpathSync12(base);
52231
+ projectReal = realpathSync13(projectDir);
52232
+ baseReal = realpathSync13(base);
51924
52233
  } catch {
51925
52234
  return null;
51926
52235
  }
@@ -51933,7 +52242,7 @@ function resolveFragment(projectDir, sessionId, file) {
51933
52242
  const sessionRoot = join38(baseReal, year, month, day, sessionId);
51934
52243
  const candidate = join38(monthDir, day, sessionId, file);
51935
52244
  try {
51936
- const real = realpathSync12(candidate);
52245
+ const real = realpathSync13(candidate);
51937
52246
  if (isUnder2(real, sessionRoot) && statSync20(real).isFile()) return real;
51938
52247
  } catch {
51939
52248
  }
@@ -52017,7 +52326,7 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
52017
52326
  try {
52018
52327
  const size = statSync20(path3).size;
52019
52328
  if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
52020
- fragment = readFileSync27(path3, "utf8");
52329
+ fragment = readFileSync28(path3, "utf8");
52021
52330
  } catch {
52022
52331
  return { status: 404 };
52023
52332
  }
@@ -53539,7 +53848,7 @@ var init_local_file_watch = __esm({
53539
53848
  });
53540
53849
 
53541
53850
  // packages/server/src/boot-progress.ts
53542
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync28, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
53851
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync29, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
53543
53852
  import { basename as basename12, dirname as dirname18, join as join40 } from "node:path";
53544
53853
  function bootProgressPath(stateDir) {
53545
53854
  return join40(stateDir, BOOT_PROGRESS_NAME);
@@ -53588,12 +53897,12 @@ var init_boot_progress = __esm({
53588
53897
  });
53589
53898
 
53590
53899
  // packages/server/src/runtime-lease.ts
53591
- import { existsSync as existsSync14, mkdirSync as mkdirSync19, readdirSync as readdirSync12, readFileSync as readFileSync29, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
53900
+ import { existsSync as existsSync15, mkdirSync as mkdirSync19, readdirSync as readdirSync12, readFileSync as readFileSync30, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
53592
53901
  import { dirname as dirname19, join as join41 } from "node:path";
53593
53902
  function runtimeVersionDir(bin) {
53594
53903
  let dir = dirname19(bin);
53595
53904
  for (let step = 0; step < MAX_ASCENT; step++) {
53596
- if (existsSync14(join41(dir, PROVISIONED_MARKER))) return dir;
53905
+ if (existsSync15(join41(dir, PROVISIONED_MARKER))) return dir;
53597
53906
  const parent = dirname19(dir);
53598
53907
  if (parent === dir) return void 0;
53599
53908
  dir = parent;
@@ -53642,7 +53951,7 @@ function liveRuntimeLeases(versionDir) {
53642
53951
  const path3 = join41(dir, name);
53643
53952
  let lease;
53644
53953
  try {
53645
- lease = JSON.parse(readFileSync29(path3, "utf8"));
53954
+ lease = JSON.parse(readFileSync30(path3, "utf8"));
53646
53955
  } catch {
53647
53956
  continue;
53648
53957
  }
@@ -53673,10 +53982,10 @@ import { createHash as createHash15 } from "node:crypto";
53673
53982
  import {
53674
53983
  chmodSync,
53675
53984
  closeSync as closeSync16,
53676
- existsSync as existsSync15,
53985
+ existsSync as existsSync16,
53677
53986
  mkdirSync as mkdirSync20,
53678
53987
  openSync as openSync17,
53679
- readFileSync as readFileSync30,
53988
+ readFileSync as readFileSync31,
53680
53989
  readdirSync as readdirSync13,
53681
53990
  renameSync as renameSync11,
53682
53991
  rmSync as rmSync12,
@@ -53711,13 +54020,13 @@ function provisionedBinary(backend, root, label) {
53711
54020
  const dir = join42(root, backend, label);
53712
54021
  let marker;
53713
54022
  try {
53714
- marker = JSON.parse(readFileSync30(join42(dir, MARKER), "utf8"));
54023
+ marker = JSON.parse(readFileSync31(join42(dir, MARKER), "utf8"));
53715
54024
  } catch {
53716
54025
  return void 0;
53717
54026
  }
53718
54027
  if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
53719
54028
  const bin = join42(dir, marker.binary);
53720
- return existsSync15(bin) ? bin : void 0;
54029
+ return existsSync16(bin) ? bin : void 0;
53721
54030
  }
53722
54031
  function octal(buffer, offset, length) {
53723
54032
  const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
@@ -53739,7 +54048,7 @@ function safeEntryPath(dest, name) {
53739
54048
  function locateBinary(dir, backend, binary) {
53740
54049
  if (backend === "claude") {
53741
54050
  const direct = join42(dir, binary);
53742
- return existsSync15(direct) ? direct : void 0;
54051
+ return existsSync16(direct) ? direct : void 0;
53743
54052
  }
53744
54053
  const vendor = join42(dir, "vendor");
53745
54054
  let triples;
@@ -53750,7 +54059,7 @@ function locateBinary(dir, backend, binary) {
53750
54059
  }
53751
54060
  for (const triple of triples) {
53752
54061
  const candidate = join42(vendor, triple, "bin", binary);
53753
- if (existsSync15(candidate)) return candidate;
54062
+ if (existsSync16(candidate)) return candidate;
53754
54063
  }
53755
54064
  return void 0;
53756
54065
  }
@@ -53816,8 +54125,17 @@ async function provisionRuntime(backend, options) {
53816
54125
  };
53817
54126
  writeFileSync17(join42(partial, MARKER), `${JSON.stringify(marker, null, 2)}
53818
54127
  `, "utf8");
54128
+ const rename2 = options.rename ?? renameSync11;
53819
54129
  try {
53820
- renameSync11(partial, finalDir);
54130
+ for (let attempt = 1; ; attempt++) {
54131
+ try {
54132
+ rename2(partial, finalDir);
54133
+ break;
54134
+ } catch (err) {
54135
+ if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync16(finalDir)) throw err;
54136
+ await new Promise((resolve12) => setTimeout(resolve12, options.retryDelayMs ?? RENAME_RETRY_DELAY_MS));
54137
+ }
54138
+ }
53821
54139
  } catch (err) {
53822
54140
  const theirs = provisionedBinary(backend, root, coordinates.label);
53823
54141
  if (!theirs) throw err;
@@ -53831,7 +54149,8 @@ async function provisionRuntime(backend, options) {
53831
54149
  throw err;
53832
54150
  }
53833
54151
  }
53834
- function sweepRuntimes(backend, root, keepLabel, now = Date.now()) {
54152
+ function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
54153
+ const rm2 = deps.rm ?? defaultSweepRm;
53835
54154
  const backendDir = join42(root, backend);
53836
54155
  let entries2;
53837
54156
  try {
@@ -53856,8 +54175,14 @@ function sweepRuntimes(backend, root, keepLabel, now = Date.now()) {
53856
54175
  continue;
53857
54176
  }
53858
54177
  }
53859
- rmSync12(full, { recursive: true, force: true });
53860
- sweep.removed.push(full);
54178
+ try {
54179
+ rm2(full);
54180
+ sweep.removed.push(full);
54181
+ } catch (err) {
54182
+ const code = errorCode4(err);
54183
+ const reason = code && TRANSIENT_HANDLE_CODES.has(code) ? `in use (${code})` : `could not remove: ${err instanceof Error ? err.message : String(err)}`;
54184
+ sweep.kept.push({ dir: full, leases: [], reason });
54185
+ }
53861
54186
  }
53862
54187
  return sweep;
53863
54188
  }
@@ -53876,27 +54201,34 @@ async function resolveOne(backend, explicit, options) {
53876
54201
  return { bin: bare, source: "path", version: "unknown", note };
53877
54202
  }
53878
54203
  const root = options.root ?? runtimesRoot(env);
54204
+ let provisioned;
53879
54205
  try {
53880
- const provisioned = await provisionRuntime(backend, {
54206
+ provisioned = await provisionRuntime(backend, {
53881
54207
  root,
53882
54208
  coordinates,
53883
54209
  fetch: options.fetch,
53884
54210
  registry: options.registry,
54211
+ rename: options.rename,
53885
54212
  onProgress: (message) => options.onProgress?.(backend, message)
53886
54213
  });
53887
- if (provisioned.fetched) log3("info", `runtimes: provisioned ${backend} ${provisioned.label} at ${provisioned.bin}`);
53888
- leaseRuntime(provisioned.bin, "server");
53889
- const sweep = sweepRuntimes(backend, root, coordinates.label);
53890
- for (const retired of sweep.removed) log3("info", `runtimes: retired ${retired}`);
53891
- for (const { dir, leases } of sweep.kept) {
53892
- log3("info", `runtimes: kept ${dir} \u2014 still used by ${leases.map((lease) => `${lease.role} pid ${lease.pid}`).join(", ")}`);
53893
- }
53894
- return { bin: provisioned.bin, source: "provisioned", version: provisioned.label };
53895
54214
  } catch (err) {
53896
54215
  const note = err instanceof Error ? err.message : String(err);
53897
54216
  log3("warn", `runtimes: could not provision ${backend} ${coordinates.label} (${note}); using ${bare} from PATH \u2014 the version Frizz was built against is not what will run`);
53898
54217
  return { bin: bare, source: "path", version: "unknown", note };
53899
54218
  }
54219
+ if (provisioned.fetched) log3("info", `runtimes: provisioned ${backend} ${provisioned.label} at ${provisioned.bin}`);
54220
+ leaseRuntime(provisioned.bin, "server");
54221
+ try {
54222
+ const sweep = sweepRuntimes(backend, root, coordinates.label, Date.now(), { rm: options.rm });
54223
+ for (const retired of sweep.removed) log3("info", `runtimes: retired ${retired}`);
54224
+ for (const { dir, leases, reason } of sweep.kept) {
54225
+ const why = reason ?? `still used by ${leases.map((lease) => `${lease.role} pid ${lease.pid}`).join(", ")}`;
54226
+ log3("info", `runtimes: kept ${dir} \u2014 ${why}`);
54227
+ }
54228
+ } catch (err) {
54229
+ log3("warn", `runtimes: sweep of superseded ${backend} versions failed (${err instanceof Error ? err.message : String(err)}); the pin is unaffected`);
54230
+ }
54231
+ return { bin: provisioned.bin, source: "provisioned", version: provisioned.label };
53900
54232
  }
53901
54233
  async function resolveRuntimes(options = {}) {
53902
54234
  const [claude, codex] = await Promise.all([
@@ -53915,7 +54247,7 @@ function describeRuntime(backend, runtime) {
53915
54247
  return `${backend}: ${runtime.bin} from PATH${runtime.note ? ` \u2014 ${runtime.note}` : ""}`;
53916
54248
  }
53917
54249
  }
53918
- var CLAUDE_AGENT_SDK_VERSION, CLAUDE_CODE_VERSION, CODEX_VERSION, RUNTIMES_MODE_ENV, RUNTIMES_DIR_ENV, CLAUDE_BIN_ENV, CODEX_BIN_ENV, REGISTRY, MARKER, BLOCK, TarExtractor;
54250
+ var CLAUDE_AGENT_SDK_VERSION, CLAUDE_CODE_VERSION, CODEX_VERSION, RUNTIMES_MODE_ENV, RUNTIMES_DIR_ENV, CLAUDE_BIN_ENV, CODEX_BIN_ENV, REGISTRY, MARKER, BLOCK, TarExtractor, RENAME_ATTEMPTS, RENAME_RETRY_DELAY_MS, TRANSIENT_HANDLE_CODES, errorCode4, defaultSweepRm;
53919
54251
  var init_runtimes = __esm({
53920
54252
  "packages/server/src/runtimes.ts"() {
53921
54253
  "use strict";
@@ -54060,6 +54392,11 @@ var init_runtimes = __esm({
54060
54392
  }
54061
54393
  }
54062
54394
  };
54395
+ RENAME_ATTEMPTS = 5;
54396
+ RENAME_RETRY_DELAY_MS = 200;
54397
+ TRANSIENT_HANDLE_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
54398
+ errorCode4 = (err) => err?.code;
54399
+ defaultSweepRm = (path3) => rmSync12(path3, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
54063
54400
  }
54064
54401
  });
54065
54402
 
@@ -54592,7 +54929,7 @@ var init_legacy_project_db = __esm({
54592
54929
  });
54593
54930
 
54594
54931
  // packages/server/src/frizz-db.ts
54595
- import { existsSync as existsSync16, mkdirSync as mkdirSync21, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync23 } from "node:fs";
54932
+ import { existsSync as existsSync17, mkdirSync as mkdirSync21, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync23 } from "node:fs";
54596
54933
  import { basename as basename13, dirname as dirname21, join as join43 } from "node:path";
54597
54934
  function frizzDatabaseLocation(options) {
54598
54935
  if (options.stateDir !== void 0) {
@@ -54677,7 +55014,7 @@ function openFrizzDatabase(options = {}) {
54677
55014
  };
54678
55015
  }
54679
55016
  function projectIdsWithFile(projectsDir, file) {
54680
- if (!existsSync16(projectsDir)) return [];
55017
+ if (!existsSync17(projectsDir)) return [];
54681
55018
  return readdirSync14(projectsDir).filter((name) => {
54682
55019
  try {
54683
55020
  return statSync23(join43(projectsDir, name, file)).isFile();
@@ -54690,7 +55027,7 @@ function legacyProjectIds(projectsDir) {
54690
55027
  return projectIdsWithFile(projectsDir, "ui.db");
54691
55028
  }
54692
55029
  function retiredLegacyProjectIds(projectsDir) {
54693
- return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync16(join43(projectsDir, id, "ui.db")));
55030
+ return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync17(join43(projectsDir, id, "ui.db")));
54694
55031
  }
54695
55032
  function tableColumns(db, schema, table) {
54696
55033
  return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
@@ -54895,11 +55232,11 @@ __export(index_exports, {
54895
55232
  unknownProjectPage: () => unknownProjectPage
54896
55233
  });
54897
55234
  import { createServer } from "node:http";
54898
- import { readFileSync as readFileSync31, existsSync as existsSync17, statSync as statSync24 } from "node:fs";
54899
- import { dirname as dirname23, join as join44, resolve as resolve11, extname as extname4, normalize as normalize2, sep as sep6 } from "node:path";
55235
+ import { readFileSync as readFileSync32, existsSync as existsSync18, statSync as statSync24 } from "node:fs";
55236
+ import { dirname as dirname23, join as join44, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
54900
55237
  function readServerAddressHolder(path3) {
54901
55238
  try {
54902
- const value = JSON.parse(readFileSync31(path3, "utf8"));
55239
+ const value = JSON.parse(readFileSync32(path3, "utf8"));
54903
55240
  if (typeof value?.pid !== "number") return void 0;
54904
55241
  return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
54905
55242
  } catch {
@@ -55023,10 +55360,10 @@ function staticIsFresh(req, etag, mtimeMs) {
55023
55360
  return Number.isFinite(since) && Math.floor(mtimeMs / 1e3) <= Math.floor(since / 1e3);
55024
55361
  }
55025
55362
  function serveStatic(distDir, req, res) {
55026
- const rel = normalize2((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
55363
+ const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
55027
55364
  let file = join44(distDir, rel === "/" ? "index.html" : rel);
55028
- if (!file.startsWith(distDir)) file = join44(distDir, "index.html");
55029
- if (!existsSync17(file)) file = join44(distDir, "index.html");
55365
+ if (!file.startsWith(distDir + sep6)) file = join44(distDir, "index.html");
55366
+ if (!existsSync18(file)) file = join44(distDir, "index.html");
55030
55367
  try {
55031
55368
  const stats = statSync24(file);
55032
55369
  const cacheControl = file.startsWith(join44(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
@@ -55045,7 +55382,7 @@ function serveStatic(distDir, req, res) {
55045
55382
  res.end();
55046
55383
  return;
55047
55384
  }
55048
- let body = readFileSync31(file);
55385
+ let body = readFileSync32(file);
55049
55386
  const accept = req.headers["accept-encoding"];
55050
55387
  const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
55051
55388
  if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
@@ -55521,7 +55858,7 @@ async function startServer(opts = {}) {
55521
55858
  if (vite) {
55522
55859
  vite.middlewares(req, res, () => {
55523
55860
  try {
55524
- const html = readFileSync31(join44(webRoot, "index.html"), "utf8");
55861
+ const html = readFileSync32(join44(webRoot, "index.html"), "utf8");
55525
55862
  void vite.transformIndexHtml(url, html).then((out) => {
55526
55863
  res.writeHead(200, { "content-type": "text/html" });
55527
55864
  res.end(out);
@@ -55533,7 +55870,7 @@ async function startServer(opts = {}) {
55533
55870
  });
55534
55871
  return;
55535
55872
  }
55536
- if (existsSync17(distDir)) {
55873
+ if (existsSync18(distDir)) {
55537
55874
  serveStatic(distDir, req, res);
55538
55875
  return;
55539
55876
  }
@@ -55754,7 +56091,7 @@ init_project();
55754
56091
  init_project_launch();
55755
56092
  init_shutdown();
55756
56093
  init_logging();
55757
- import { existsSync as existsSync18 } from "node:fs";
56094
+ import { existsSync as existsSync19 } from "node:fs";
55758
56095
  import { join as join45 } from "node:path";
55759
56096
  process.on("uncaughtException", (error) => {
55760
56097
  log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
@@ -55777,6 +56114,15 @@ function notifySupervisor(message) {
55777
56114
  } catch {
55778
56115
  }
55779
56116
  }
56117
+ var shutdown;
56118
+ process.once("disconnect", () => {
56119
+ if (shutdown) {
56120
+ void shutdown();
56121
+ return;
56122
+ }
56123
+ log.info("dev-child", "supervisor disconnected before the server was up; exiting");
56124
+ process.exit(0);
56125
+ });
55780
56126
  try {
55781
56127
  const target = projectLaunchTargetFromEnvironment(process.env);
55782
56128
  const launchOwnerToken = projectLaunchOwnerTokenFromEnvironment(process.env);
@@ -55793,9 +56139,9 @@ try {
55793
56139
  ["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
55794
56140
  ["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
55795
56141
  ];
55796
- if (!existsSync18(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
56142
+ if (!existsSync19(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
55797
56143
  for (const [name, directory, requiredFile] of required) {
55798
- if (!directory || !existsSync18(join45(directory, requiredFile)))
56144
+ if (!directory || !existsSync19(join45(directory, requiredFile)))
55799
56145
  throw new Error(`stable artifact launch is missing verified ${name}`);
55800
56146
  }
55801
56147
  }
@@ -55816,7 +56162,7 @@ try {
55816
56162
  bootId: server.ctx.bootId
55817
56163
  });
55818
56164
  let shuttingDown = false;
55819
- const shutdown = async () => {
56165
+ const stop = async () => {
55820
56166
  if (shuttingDown) return;
55821
56167
  shuttingDown = true;
55822
56168
  const force = setTimeout(() => process.exit(1), 15e3);
@@ -55839,9 +56185,9 @@ try {
55839
56185
  process.exit(1);
55840
56186
  }
55841
56187
  };
55842
- process.on("SIGINT", () => void shutdown());
55843
- process.on("SIGTERM", () => void shutdown());
55844
- process.once("disconnect", () => void shutdown());
56188
+ shutdown = stop;
56189
+ process.on("SIGINT", () => void stop());
56190
+ process.on("SIGTERM", () => void stop());
55845
56191
  } catch (err) {
55846
56192
  log.error("dev-child", `failed to start: ${err instanceof Error ? err.stack ?? err.message : err}`);
55847
56193
  process.exit(1);