frizz 0.12.9 → 0.12.11

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 (95) hide show
  1. package/dist/claude-agent-broker.js +7850 -7778
  2. package/dist/codex-app-server-daemon.js +49 -10
  3. package/dist/dev-child.js +653 -297
  4. package/dist/frizz.js +118 -21
  5. package/package.json +1 -1
  6. package/runtime/cc-worker/bin/frizz-mcp.mjs +58 -5
  7. package/web-dist/assets/{TerminalPane-CDk5zKv9.js → TerminalPane-DHvHCziB.js} +1 -1
  8. package/web-dist/assets/{abnfDiagram-VRR7QNED-DmkK2qjk.js → abnfDiagram-VRR7QNED-DIzM_IN8.js} +1 -1
  9. package/web-dist/assets/architecture-TIHT7OUA-DBvKOf0j.js +1 -0
  10. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-BFbA3OG6.js → architectureDiagram-ZJ3FMSHR-Bm_8vd5v.js} +1 -1
  11. package/web-dist/assets/{blockDiagram-677ZJIJ3-DTslTfq9.js → blockDiagram-677ZJIJ3-BXUW_N2o.js} +1 -1
  12. package/web-dist/assets/{c4Diagram-LMCZKHZV-Dmugni-f.js → c4Diagram-LMCZKHZV-C7rfqxUO.js} +1 -1
  13. package/web-dist/assets/channel-CbdXYswc.js +1 -0
  14. package/web-dist/assets/{chunk-32BRIVSS-DS7QIaUB.js → chunk-32BRIVSS-DbfUERLa.js} +1 -1
  15. package/web-dist/assets/{chunk-52WLFC77-CnnFIjVe.js → chunk-52WLFC77-BT_3wKSP.js} +1 -1
  16. package/web-dist/assets/{chunk-C7G6YPKG-BZxNdEVe.js → chunk-C7G6YPKG-JqApnOVt.js} +1 -1
  17. package/web-dist/assets/{chunk-EX3LRPZG-Zv7RLgbb.js → chunk-EX3LRPZG-Cj6hlA4t.js} +1 -1
  18. package/web-dist/assets/{chunk-FWX5IMBZ-6-Zb5eo-.js → chunk-FWX5IMBZ-BGthhuW_.js} +2 -2
  19. package/web-dist/assets/{chunk-HOUHSVGY-J5Ui6IxH.js → chunk-HOUHSVGY-uyLpm_YS.js} +1 -1
  20. package/web-dist/assets/{chunk-ICXQ74PX-DH6GE_Om.js → chunk-ICXQ74PX-BlwUMZAk.js} +1 -1
  21. package/web-dist/assets/{chunk-MOJQB5TN-Bjd1ChDq.js → chunk-MOJQB5TN-MXbNNk-P.js} +1 -1
  22. package/web-dist/assets/{chunk-OGEWGWER-BNvzjYRM.js → chunk-OGEWGWER-Da4My1Rf.js} +1 -1
  23. package/web-dist/assets/{chunk-PUDLZKDR-CdtfylsO.js → chunk-PUDLZKDR-C-PgvO__.js} +1 -1
  24. package/web-dist/assets/{chunk-Q4XR5HBZ-C7N3sKog.js → chunk-Q4XR5HBZ-BxZFe7gr.js} +1 -1
  25. package/web-dist/assets/{chunk-V7JOEXUC-vX-c4OzH.js → chunk-V7JOEXUC-DgERuXm_.js} +1 -1
  26. package/web-dist/assets/{chunk-VAUOI2AC-DtbheJmZ.js → chunk-VAUOI2AC-T5OoQE-_.js} +1 -1
  27. package/web-dist/assets/{chunk-VR4S4FIN-BpE1AOJR.js → chunk-VR4S4FIN--HFvJUG1.js} +1 -1
  28. package/web-dist/assets/{chunk-WYO6CB5R-BupZRR1a.js → chunk-WYO6CB5R-DlAvy41i.js} +1 -1
  29. package/web-dist/assets/{chunk-ZGVPDNZ5-XoWDM-02.js → chunk-ZGVPDNZ5-BdDg7X_H.js} +1 -1
  30. package/web-dist/assets/classDiagram-OUVF2IWQ-BVXwECjS.js +1 -0
  31. package/web-dist/assets/classDiagram-v2-EOCWNBFH-BVXwECjS.js +1 -0
  32. package/web-dist/assets/{cynefin-VYW2F7L2-DOIzVENe.js → cynefin-VYW2F7L2-BhjNO28R.js} +1 -1
  33. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-DGq-kl_q.js → cynefinDiagram-TSTJHNR4-B6VA5gCd.js} +1 -1
  34. package/web-dist/assets/{dagre-VKFMJZFB-Brs6wxcd.js → dagre-VKFMJZFB-6MXoEc6K.js} +1 -1
  35. package/web-dist/assets/{diagram-FQU43EPY-BrQd5FEw.js → diagram-FQU43EPY-D2RSPoK4.js} +1 -1
  36. package/web-dist/assets/{diagram-G47NLZAW-B-DN7aQk.js → diagram-G47NLZAW-BOSbtzOf.js} +1 -1
  37. package/web-dist/assets/{diagram-NH7WQ7WH-DB-NFs3X.js → diagram-NH7WQ7WH-Dt_u21nP.js} +1 -1
  38. package/web-dist/assets/{diagram-OA4YK3LP-DsrBhvMB.js → diagram-OA4YK3LP-CjuWW-7r.js} +1 -1
  39. package/web-dist/assets/{diagram-WEI45ONY-DYctq6PN.js → diagram-WEI45ONY-rANIKJ8N.js} +1 -1
  40. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-C6ghRrJP.js → ebnfDiagram-CCIWWBDH-Bf3kfaKZ.js} +1 -1
  41. package/web-dist/assets/{erDiagram-Q63AITRT-C_iK-Ja6.js → erDiagram-Q63AITRT-6vBp0ITn.js} +1 -1
  42. package/web-dist/assets/eventmodeling-45OFAUF4-STTT-geQ.js +1 -0
  43. package/web-dist/assets/flowDiagram-23GEKE2U-CC7DF1Fz.js +1 -0
  44. package/web-dist/assets/{ganttDiagram-NO4QXBWP-Y9GZAgbA.js → ganttDiagram-NO4QXBWP-C2qN2PMS.js} +1 -1
  45. package/web-dist/assets/{gitGraph-TEB2WS4Q-BgqW4vt7.js → gitGraph-TEB2WS4Q-CRASqc9n.js} +1 -1
  46. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-eImStPZ8.js → gitGraphDiagram-IHSO6WYX-gsXSPrH2.js} +1 -1
  47. package/web-dist/assets/{index-BOACK0Xu.js → index-DimQCLe8.js} +96 -96
  48. package/web-dist/assets/index-XhfdAXcw.css +1 -0
  49. package/web-dist/assets/{info-DKCQHKI2-BtbNWkY3.js → info-DKCQHKI2-rDkFlFOd.js} +1 -1
  50. package/web-dist/assets/{infoDiagram-FWYZ7A6U-DMIPEeRd.js → infoDiagram-FWYZ7A6U-03ql8Tqr.js} +1 -1
  51. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-08_raW8G.js → ishikawaDiagram-FXEZZL3T-CfDSAVNp.js} +1 -1
  52. package/web-dist/assets/{journeyDiagram-5HDEW3XC-hPkkufFj.js → journeyDiagram-5HDEW3XC-RWaA6AvH.js} +1 -1
  53. package/web-dist/assets/{kanban-definition-HUTT4EX6-CDBcUNEE.js → kanban-definition-HUTT4EX6-C89qresM.js} +1 -1
  54. package/web-dist/assets/{line-Njg200Gq.js → line-CZtSsvHu.js} +1 -1
  55. package/web-dist/assets/{mermaid-parser.core-1A-yH2Ql.js → mermaid-parser.core-DKNnvxt0.js} +3 -3
  56. package/web-dist/assets/{mermaid.core-D5Mvu4fo.js → mermaid.core-DX8cFeBF.js} +3 -3
  57. package/web-dist/assets/{mindmap-definition-LN4V7U3C-r-0LqhPG.js → mindmap-definition-LN4V7U3C-DW8ClyAX.js} +1 -1
  58. package/web-dist/assets/{packet-7NZHBO7P-Dj8tWs_Q.js → packet-7NZHBO7P-CUOv3apI.js} +1 -1
  59. package/web-dist/assets/{pegDiagram-2B236MQR-BHoUQUjJ.js → pegDiagram-2B236MQR-CFyxKEBL.js} +1 -1
  60. package/web-dist/assets/{pie-RZYD4A2V-GZmPkAjn.js → pie-RZYD4A2V-DyXXRie2.js} +1 -1
  61. package/web-dist/assets/{pieDiagram-ENE6RG2P-CL0ZHN1y.js → pieDiagram-ENE6RG2P-yUw7HA8l.js} +1 -1
  62. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-vZJzUcfC.js → quadrantDiagram-ABIIQ3AL-Mc7aea_i.js} +1 -1
  63. package/web-dist/assets/{radar-I7S5WNFK-D3x_tyhl.js → radar-I7S5WNFK-BKj0BsQy.js} +1 -1
  64. package/web-dist/assets/{railroad-3IZDKUUU-HVHtGCKZ.js → railroad-3IZDKUUU-DUK_kJIY.js} +1 -1
  65. package/web-dist/assets/railroad-abnf-AHOZXSZD-BDImJcFw.js +1 -0
  66. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BR2XIYqq.js +1 -0
  67. package/web-dist/assets/railroad-peg-LSFZ7HO6-DRCwBbJY.js +1 -0
  68. package/web-dist/assets/{railroadDiagram-RFXS5EU6-Dfw0JONh.js → railroadDiagram-RFXS5EU6-By0Xgb_U.js} +1 -1
  69. package/web-dist/assets/{requirementDiagram-TGXJPOKE-DLM4etHl.js → requirementDiagram-TGXJPOKE-CgTycsRY.js} +1 -1
  70. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-2nsrE-nL.js → sankeyDiagram-HTMAVEWB-mJ_-Grkn.js} +1 -1
  71. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-VkpQVaPX.js → sequenceDiagram-DBY2YBRQ-Dn6YyeNv.js} +1 -1
  72. package/web-dist/assets/{stateDiagram-2N3HPSRC-xvBH0825.js → stateDiagram-2N3HPSRC-BBws1E-e.js} +1 -1
  73. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-B2jOFVGG.js +1 -0
  74. package/web-dist/assets/{swimlanes-5IMT3BWC-DA8H4qPO.js → swimlanes-5IMT3BWC-Jab-07o9.js} +1 -1
  75. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DhYK_6fH.js +8 -0
  76. package/web-dist/assets/{timeline-definition-FHXFAJF6-npL1KNyH.js → timeline-definition-FHXFAJF6-Cq6RznHz.js} +1 -1
  77. package/web-dist/assets/{treeView-QDETBFTQ-Cea7MDUV.js → treeView-QDETBFTQ-KSelJnyL.js} +1 -1
  78. package/web-dist/assets/{treemap-6X3UGDF4-CGHINewA.js → treemap-6X3UGDF4-B-iiUurZ.js} +1 -1
  79. package/web-dist/assets/{vennDiagram-L72KCM5P-D-RIigY2.js → vennDiagram-L72KCM5P-B8t4NeQy.js} +1 -1
  80. package/web-dist/assets/{wardley-OPB4EBWU-BXdXDctR.js → wardley-OPB4EBWU-CtkAPZLQ.js} +1 -1
  81. package/web-dist/assets/{wardleyDiagram-EHGQE667-tLo8yxD2.js → wardleyDiagram-EHGQE667-DC-ms_sD.js} +1 -1
  82. package/web-dist/assets/{xychartDiagram-FW5EYKEG-I4H6c-9Q.js → xychartDiagram-FW5EYKEG-DW6fcgjF.js} +1 -1
  83. package/web-dist/index.html +2 -2
  84. package/web-dist/assets/architecture-TIHT7OUA-c0q2ebfx.js +0 -1
  85. package/web-dist/assets/channel-CZ15urUA.js +0 -1
  86. package/web-dist/assets/classDiagram-OUVF2IWQ-DFxJiEZ1.js +0 -1
  87. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DFxJiEZ1.js +0 -1
  88. package/web-dist/assets/eventmodeling-45OFAUF4-BdXQ9Pv4.js +0 -1
  89. package/web-dist/assets/flowDiagram-23GEKE2U-rTXtf38R.js +0 -1
  90. package/web-dist/assets/index-BIGkwpMf.css +0 -1
  91. package/web-dist/assets/railroad-abnf-AHOZXSZD-BD2umz4T.js +0 -1
  92. package/web-dist/assets/railroad-ebnf-EBAXGLYW-C1IGySZs.js +0 -1
  93. package/web-dist/assets/railroad-peg-LSFZ7HO6-BVCH7yeK.js +0 -1
  94. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-B3bLucl6.js +0 -1
  95. package/web-dist/assets/swimlanesDiagram-G3AALYLV-cYXAfa4Q.js +0 -8
package/dist/dev-child.js CHANGED
@@ -13273,17 +13273,27 @@ function splitAwaitingFrontmatter(raw2) {
13273
13273
  const after = delimiter2 === -1 ? [] : lines.slice(delimiter2 + 1);
13274
13274
  const rest = [];
13275
13275
  const yamlLines = [];
13276
+ const titleLines = [];
13276
13277
  let structural = true;
13278
+ let inTitle = false;
13277
13279
  for (const line of frontmatter) {
13278
13280
  const m = line.match(AWAITING_KEY_RE);
13279
13281
  const key = m?.[1].toLowerCase();
13280
- if (m && key) structural = AWAITING_YAML_KEYS.has(key);
13281
- else if (line.trim() !== "" && !/^\s/.test(line) && !/^\s*-\s/.test(line)) structural = false;
13282
- (structural ? yamlLines : rest).push(line);
13282
+ if (m && key) {
13283
+ structural = AWAITING_YAML_KEYS.has(key);
13284
+ inTitle = key === "title";
13285
+ } else if (line.trim() !== "" && !/^\s/.test(line) && !/^\s*-\s/.test(line)) {
13286
+ structural = false;
13287
+ inTitle = false;
13288
+ }
13289
+ if (inTitle) titleLines.push(m && key === "title" ? m[2] ?? "" : line);
13290
+ else (structural ? yamlLines : rest).push(line);
13283
13291
  }
13284
13292
  const parsed = parseAwaitingYaml(yamlLines.join("\n"));
13285
13293
  if (!parsed.ok) rest.push(...yamlLines);
13286
13294
  rest.push(...after);
13295
+ const title = trimAwaitingTitle(titleLines.join(" "));
13296
+ if (title) parsed.hints.push({ kind: "title", value: title });
13287
13297
  return { body: rest.join("\n").trim(), hints: parsed.hints.slice(0, AWAITING_HINT_MAX) };
13288
13298
  }
13289
13299
  function parseAwaitingYaml(text2) {
@@ -13309,8 +13319,6 @@ function parseAwaitingYaml(text2) {
13309
13319
  for (const entry of Array.isArray(raw2) ? raw2 : [raw2]) push(itemKind, entry);
13310
13320
  } else if (key === "for") {
13311
13321
  push("for", raw2);
13312
- } else if (key === "title") {
13313
- if (typeof raw2 === "string" || typeof raw2 === "number") push("title", trimAwaitingTitle(String(raw2)));
13314
13322
  }
13315
13323
  }
13316
13324
  return { ok: true, hints };
@@ -13671,7 +13679,7 @@ function parseGithubWakeSteer(text2) {
13671
13679
  function frizzRoute(path3) {
13672
13680
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
13673
13681
  }
13674
- 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, 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;
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;
13675
13683
  var init_src = __esm({
13676
13684
  "packages/shared/src/index.ts"() {
13677
13685
  "use strict";
@@ -13795,7 +13803,14 @@ var init_src = __esm({
13795
13803
  slug: external_exports.string(),
13796
13804
  displayName: external_exports.string(),
13797
13805
  defaultEffort: external_exports.string(),
13798
- efforts: external_exports.array(external_exports.string())
13806
+ efforts: external_exports.array(external_exports.string()),
13807
+ // The model's stock context window and the largest one it accepts, in tokens, straight from the
13808
+ // cache's `context_window` / `max_context_window` (272K / 872K on GPT-5.6, 128K / 128K on Spark).
13809
+ // The Settings "Context window" presets are built from these so the drawer names the numbers codex
13810
+ // will actually run at (maintainer 2026-09-11: "reflect the actual numbers"). Optional: a cache from
13811
+ // before the fields existed, or the degraded fallback, simply carries neither.
13812
+ contextWindow: external_exports.number().int().positive().optional(),
13813
+ maxContextWindow: external_exports.number().int().positive().optional()
13799
13814
  });
13800
13815
  ThreadProfileOption = external_exports.object({
13801
13816
  model: external_exports.string().min(1),
@@ -14436,6 +14451,20 @@ var init_src = __esm({
14436
14451
  retrying: external_exports.boolean().optional(),
14437
14452
  at: external_exports.string().optional()
14438
14453
  });
14454
+ ThreadLinkView = external_exports.object({
14455
+ id: external_exports.string(),
14456
+ kind: external_exports.enum(["link", "file"]),
14457
+ label: external_exports.string(),
14458
+ target: external_exports.string()
14459
+ }).strict();
14460
+ UpsertOwnLinkInput = external_exports.object({
14461
+ slug: ThreadSlug,
14462
+ label: external_exports.string().trim().min(1).max(240).regex(/^[^\x00-\x1f\x7f]+$/),
14463
+ target: external_exports.string().trim().min(1).max(8192).regex(/^[^\x00-\x1f\x7f]+$/)
14464
+ }).strict();
14465
+ UpsertOwnLinkResult = external_exports.object({ link: ThreadLinkView }).strict();
14466
+ DropOwnLinkInput = external_exports.object({ slug: ThreadSlug, id: external_exports.string().min(1) }).strict();
14467
+ DropOwnLinkResult = external_exports.object({ dropped: external_exports.boolean() }).strict();
14439
14468
  ThreadView = external_exports.object({
14440
14469
  id: ThreadSlug,
14441
14470
  // slug; filename is <slug>.md
@@ -14504,6 +14533,8 @@ var init_src = __esm({
14504
14533
  // Live background SHELLS the worker launched (tailer-derived). Same default-[] discipline. Rendered
14505
14534
  // in the anchored background-ops strip alongside sub-agents; ids make current rows drillable.
14506
14535
  bgShells: external_exports.array(BgShellView).default([]),
14536
+ // Optional for snapshots from a server that predates link registration.
14537
+ links: external_exports.array(ThreadLinkView).optional(),
14507
14538
  // The thread's ARMED WATCHERS — registry-derived, not folded from the transcript, which is what makes
14508
14539
  // them survive the worker saying one more sentence. Same default-[] discipline as the two above.
14509
14540
  //
@@ -14822,6 +14853,15 @@ var init_src = __esm({
14822
14853
  // writes were 51% of a day's spend while the entries were invalidated every 15–30 minutes anyway
14823
14854
  // (see claudePromptCacheEnv). Optional so an old blob parses; defaultSettings pins "auto".
14824
14855
  promptCacheTtl: external_exports.enum(["auto", "5m", "1h"]).optional(),
14856
+ // The context window a NEW Codex thread runs with, in tokens. Codex's own default is the model's
14857
+ // stock window (272K on GPT-5.6), but the catalogue also carries a larger `max_context_window` (872K
14858
+ // on GPT-5.6-sol/terra/luna and GPT-6-astra, measured 2026-09-11 in ~/.codex/models_cache.json) that
14859
+ // a thread only reaches when asked. Reaches codex as the `model_context_window` config override on
14860
+ // `thread/start` and every cold `thread/resume` (see codexContextWindowConfig); codex clamps it to
14861
+ // the model's maximum, so a value above it is harmless, and it moves the auto-compact threshold with
14862
+ // it (codex compacts at 90% of the resolved window). Unset ⇒ nothing is sent and the model's stock
14863
+ // window applies. Optional so an old blob parses; defaultSettings leaves it unset.
14864
+ codexContextWindow: external_exports.number().int().positive().optional(),
14825
14865
  // The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
14826
14866
  // unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
14827
14867
  // the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
@@ -15041,6 +15081,7 @@ var init_src = __esm({
15041
15081
  }).strict();
15042
15082
  OwnThreadActivityResult = external_exports.object({
15043
15083
  activity: external_exports.array(ThreadActivityItem),
15084
+ links: external_exports.array(ThreadLinkView).optional(),
15044
15085
  /** Every question still owed an answer. NOT a `ThreadActivityItem` and deliberately its own list: a
15045
15086
  * question is not running work, it waits on a PERSON, and there is no `questions:` key in the awaiting
15046
15087
  * fence for it to be written into. Until 2026-08-28 a worker could read its open questions back only
@@ -19377,6 +19418,20 @@ function createStorage(source, projectId) {
19377
19418
  `);
19378
19419
  const prWatchCursorStmt = scope.prepare("UPDATE pr_watch SET cursor = ? WHERE project_id = @project_id AND id = ? AND state = 'armed'");
19379
19420
  const delPrWatches = scope.prepare("DELETE FROM pr_watch WHERE project_id = @project_id AND thread_slug = ?");
19421
+ const upsertThreadLinkStmt = scope.prepare(`
19422
+ INSERT INTO thread_link (project_id, id, thread_slug, kind, label, target, created_at)
19423
+ VALUES (@project_id, @id, @slug, @kind, @label, @target, @createdAtMs)
19424
+ ON CONFLICT (project_id, thread_slug, label) DO UPDATE SET kind = excluded.kind, target = excluded.target
19425
+ RETURNING *
19426
+ `);
19427
+ const threadLinksStmt = scope.prepare(
19428
+ "SELECT * FROM thread_link WHERE project_id = @project_id ORDER BY created_at, rowid"
19429
+ );
19430
+ const threadLinksBySlugStmt = scope.prepare(
19431
+ "SELECT * FROM thread_link WHERE project_id = @project_id AND thread_slug = ? ORDER BY created_at, rowid"
19432
+ );
19433
+ const dropThreadLinkStmt = scope.prepare("DELETE FROM thread_link WHERE project_id = @project_id AND thread_slug = ? AND id = ?");
19434
+ const delThreadLinks = scope.prepare("DELETE FROM thread_link WHERE project_id = @project_id AND thread_slug = ?");
19380
19435
  const armThreadWatchStmt = scope.prepare(`
19381
19436
  INSERT INTO thread_watch (project_id, id, thread_slug, kind, target, state, created_at, expires_at, settled_at)
19382
19437
  VALUES (@project_id, @id, @slug, @kind, @target, 'armed', @createdAtMs, @expiresAtMs, NULL)
@@ -19516,6 +19571,7 @@ function createStorage(source, projectId) {
19516
19571
  delThreadTimers.run(existing.slug);
19517
19572
  delPrWatches.run(existing.slug);
19518
19573
  delThreadWatches.run(existing.slug);
19574
+ delThreadLinks.run(existing.slug);
19519
19575
  delThreadQuestions.run(existing.slug);
19520
19576
  delThreadDone.run(existing.slug);
19521
19577
  delSubAgentSteers.run(existing.slug);
@@ -19967,6 +20023,10 @@ function createStorage(source, projectId) {
19967
20023
  dropPrWatch: (slug, id, settledAtMs) => dropPrWatchStmt.run(settledAtMs, id, slug).changes === 1,
19968
20024
  settlePrWatch: (id, settledAtMs) => settlePrWatchStmt.run(settledAtMs, id).changes === 1,
19969
20025
  setPrWatchCursor: (id, cursor) => prWatchCursorStmt.run(cursor, id).changes === 1,
20026
+ upsertThreadLink: (link) => upsertThreadLinkStmt.get(link),
20027
+ listThreadLinks: (slug) => threadLinksBySlugStmt.all(slug),
20028
+ threadLinksBySlug: () => groupBySlug(threadLinksStmt.all()),
20029
+ dropThreadLink: (slug, id) => dropThreadLinkStmt.run(slug, id).changes === 1,
19970
20030
  // IDEMPOTENT BY (thread, kind, target), which is what the partial unique index enforces. A worker
19971
20031
  // woken by an expiry re-registers the same wait, and a worker that simply calls twice must not end
19972
20032
  // up with two rows to drop — so an existing armed row is RETURNED rather than replaced. Replacing
@@ -20428,6 +20488,17 @@ var init_storage = __esm({
20428
20488
  ON pr_watch(project_id, state);
20429
20489
  CREATE INDEX IF NOT EXISTS pr_watch_slug
20430
20490
  ON pr_watch(project_id, thread_slug, state, created_at);
20491
+ -- Saved destinations have no liveness or expiry. The label is a stable slot within one thread.
20492
+ CREATE TABLE IF NOT EXISTS thread_link (
20493
+ id TEXT PRIMARY KEY,
20494
+ project_id TEXT NOT NULL,
20495
+ thread_slug TEXT NOT NULL,
20496
+ kind TEXT NOT NULL CHECK (kind IN ('link', 'file')),
20497
+ label TEXT NOT NULL,
20498
+ target TEXT NOT NULL,
20499
+ created_at INTEGER NOT NULL,
20500
+ UNIQUE (project_id, thread_slug, label)
20501
+ );
20431
20502
  -- A worker's registered WATCHES on its own running work (2026-08-26). See
20432
20503
  -- plans/rest-by-registration.md: a wait stops being a line the worker re-writes at every rest and
20433
20504
  -- becomes a row it creates once, which the human sees in the queue and which wakes the thread itself.
@@ -20541,7 +20612,8 @@ var init_storage = __esm({
20541
20612
  "thread_watch",
20542
20613
  "thread_question",
20543
20614
  "thread_done",
20544
- "subagent_steer"
20615
+ "subagent_steer",
20616
+ "thread_link"
20545
20617
  ];
20546
20618
  }
20547
20619
  });
@@ -20686,6 +20758,9 @@ var init_settings = __esm({
20686
20758
  // "auto" = the CLI's own tier (1h on a subscription). See the schema and claudePromptCacheEnv for
20687
20759
  // when "5m" is the cheaper choice.
20688
20760
  promptCacheTtl: "auto",
20761
+ // Unset = codex's own window for the model (272K on GPT-5.6). The schema and codexContextWindowConfig
20762
+ // explain the 872K ceiling a larger value is clamped to.
20763
+ codexContextWindow: void 0,
20689
20764
  // Hidden until asked for — see the schema for why.
20690
20765
  projectRail: false
20691
20766
  });
@@ -21342,7 +21417,23 @@ function toCodexModel(raw2) {
21342
21417
  const def = typeof m.default_reasoning_level === "string" ? m.default_reasoning_level : "";
21343
21418
  const defaultEffort = def && efforts.includes(def) ? def : efforts[0];
21344
21419
  const priority = typeof m.priority === "number" && Number.isFinite(m.priority) ? m.priority : Number.MAX_SAFE_INTEGER;
21345
- return { model: { slug, displayName, defaultEffort, efforts }, priority };
21420
+ const window = (key) => {
21421
+ const v = m[key];
21422
+ return typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
21423
+ };
21424
+ const contextWindow = window("context_window");
21425
+ const maxContextWindow = window("max_context_window");
21426
+ return {
21427
+ model: {
21428
+ slug,
21429
+ displayName,
21430
+ defaultEffort,
21431
+ efforts,
21432
+ ...contextWindow === void 0 ? {} : { contextWindow },
21433
+ ...maxContextWindow === void 0 ? {} : { maxContextWindow }
21434
+ },
21435
+ priority
21436
+ };
21346
21437
  }
21347
21438
  function parseCodexModelsCache(raw2) {
21348
21439
  let doc;
@@ -22252,6 +22343,7 @@ function forkBroker(options) {
22252
22343
  const socketPath = claudeBrokerSocketPath(options.stateDir, options.sessionId);
22253
22344
  const recordPath2 = claudeBrokerRecordPath(options.stateDir, options.sessionId);
22254
22345
  mkdirSync9(dirname8(recordPath2), { recursive: true });
22346
+ const generation = randomUUID8();
22255
22347
  const config = {
22256
22348
  socketPath,
22257
22349
  cwd: options.cwd,
@@ -22260,7 +22352,7 @@ function forkBroker(options) {
22260
22352
  permissionMode: options.permissionMode,
22261
22353
  env: options.env,
22262
22354
  recordPath: recordPath2,
22263
- generation: randomUUID8(),
22355
+ generation,
22264
22356
  // The daemon writes its own death forensics here. Same dir as the record, so a session's socket,
22265
22357
  // record and diagnostics stay together and a project teardown removes all three.
22266
22358
  diagnosticLogPath: claudeBrokerDiagnosticLogPath(options.stateDir, options.sessionId),
@@ -22282,14 +22374,45 @@ function forkBroker(options) {
22282
22374
  });
22283
22375
  child.unref();
22284
22376
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
22377
+ const recordedCause = () => readClaudeBrokerExit(options.stateDir, options.sessionId, generation);
22285
22378
  return new Promise((resolve12, reject) => {
22379
+ let settled = false;
22380
+ let timer;
22381
+ const settle = (outcome) => {
22382
+ if (settled) return;
22383
+ settled = true;
22384
+ if (timer) clearTimeout(timer);
22385
+ outcome();
22386
+ };
22286
22387
  const poll = () => {
22287
22388
  const record = readBrokerRecord(recordPath2);
22288
- if (record && pidAlive(record.daemonPid)) return resolve12(record);
22289
- if (Date.now() > deadline) return reject(new Error(`Claude broker for session ${options.sessionId} did not become ready`));
22290
- setTimeout(poll, 50);
22389
+ if (record && pidAlive(record.daemonPid)) return settle(() => resolve12(record));
22390
+ if (Date.now() > deadline) {
22391
+ const exit = recordedCause();
22392
+ return settle(() => reject(new Error(
22393
+ `Claude broker for session ${options.sessionId} did not become ready${exit ? `: ${describeClaudeBrokerExit(exit)}` : ""}`
22394
+ )));
22395
+ }
22396
+ timer = setTimeout(poll, 50);
22291
22397
  };
22292
- child.once("error", reject);
22398
+ child.once("error", (error) => settle(() => reject(error)));
22399
+ child.once("exit", (code, signal) => {
22400
+ if (settled) return;
22401
+ const how = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
22402
+ const until = Date.now() + 500;
22403
+ const read = () => {
22404
+ if (settled) return;
22405
+ const exit = recordedCause();
22406
+ if (!exit && Date.now() < until) {
22407
+ setTimeout(read, 25);
22408
+ return;
22409
+ }
22410
+ settle(() => reject(new Error(
22411
+ `Claude broker for session ${options.sessionId} exited before it became ready (${how}): ${describeClaudeBrokerExit(exit)}`
22412
+ )));
22413
+ };
22414
+ read();
22415
+ });
22293
22416
  poll();
22294
22417
  });
22295
22418
  }
@@ -22788,6 +22911,7 @@ function createCodexSubAgentTracker(deps) {
22788
22911
  offset: 0,
22789
22912
  partial: "",
22790
22913
  live: true,
22914
+ liveSinceMs: Date.parse(startedAt),
22791
22915
  resolveDeadlineMs: 0
22792
22916
  // stamped on the first poll, which is the first time we know `now`
22793
22917
  };
@@ -22800,6 +22924,7 @@ function createCodexSubAgentTracker(deps) {
22800
22924
  const slot = slots.get(sig.path);
22801
22925
  if (!slot || slot.live) return;
22802
22926
  slot.live = true;
22927
+ slot.liveSinceMs = sig.at ? Date.parse(sig.at) : NaN;
22803
22928
  publish(slot);
22804
22929
  return;
22805
22930
  }
@@ -22822,6 +22947,7 @@ function createCodexSubAgentTracker(deps) {
22822
22947
  else if (agent.status === "interrupted") retire(slot, sig.at, "killed");
22823
22948
  else if (!slot.live) {
22824
22949
  slot.live = true;
22950
+ slot.liveSinceMs = sig.at ? Date.parse(sig.at) : NaN;
22825
22951
  publish(slot);
22826
22952
  }
22827
22953
  }
@@ -22898,6 +23024,7 @@ function createCodexSubAgentTracker(deps) {
22898
23024
  open = true;
22899
23025
  closedAt = void 0;
22900
23026
  } else if (ev.kind === "turn-end") {
23027
+ if (ev.at && Number.isFinite(slot.liveSinceMs) && Date.parse(ev.at) < slot.liveSinceMs) continue;
22901
23028
  open = false;
22902
23029
  closedAt = ev.at;
22903
23030
  }
@@ -23262,7 +23389,7 @@ var init_tail_cache = __esm({
23262
23389
  "use strict";
23263
23390
  SAMPLE_BYTES = 1024;
23264
23391
  MAX_STATE_BYTES = 256 * 1024;
23265
- FOLD_SOURCES = ["transcript.ts", "tailer.ts", "backend/claude.ts", "backend/codex.ts"];
23392
+ FOLD_SOURCES = ["transcript.ts", "tailer.ts", "backend/claude.ts", "backend/codex.ts", "../../shared/src/index.ts"];
23266
23393
  foldSchemaMemo = null;
23267
23394
  isEncodedMap = (value) => Boolean(value) && typeof value === "object" && Array.isArray(value.__map);
23268
23395
  isEncodedSet = (value) => Boolean(value) && typeof value === "object" && Array.isArray(value.__set);
@@ -25967,14 +26094,15 @@ function parseToolArguments(args) {
25967
26094
  }
25968
26095
  function imageField(output) {
25969
26096
  if (!Array.isArray(output)) return {};
26097
+ const images = [];
25970
26098
  for (const part of output) {
25971
26099
  if (!part || typeof part !== "object") continue;
25972
26100
  const p = part;
25973
26101
  if (p.type !== "input_image" && p.type !== "output_image" && p.type !== "image") continue;
25974
26102
  const url = typeof p.image_url === "string" ? p.image_url : typeof p.url === "string" ? p.url : void 0;
25975
- if (url?.startsWith("data:image/")) return { image: url };
26103
+ if (url?.startsWith("data:image/")) images.push(url);
25976
26104
  }
25977
- return {};
26105
+ return images.length ? { image: images[0], images } : {};
25978
26106
  }
25979
26107
  function codexItemText(content) {
25980
26108
  if (!Array.isArray(content)) return "";
@@ -29984,6 +30112,11 @@ function claudeCompactionEnv(settings) {
29984
30112
  if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
29985
30113
  return { [CLAUDE_AUTO_COMPACT_WINDOW_ENV]: String(window) };
29986
30114
  }
30115
+ function codexContextWindowConfig(settings) {
30116
+ const window = settings?.codexContextWindow;
30117
+ if (window === void 0 || !Number.isInteger(window) || window <= 0) return {};
30118
+ return { [CODEX_CONTEXT_WINDOW_KEY]: window };
30119
+ }
29987
30120
  function claudePromptCacheEnv(settings) {
29988
30121
  const ttl = settings?.promptCacheTtl;
29989
30122
  if (ttl !== "5m" && ttl !== "1h") return {};
@@ -29995,7 +30128,7 @@ function claudeCompactionWindowOf(env) {
29995
30128
  const window = Number(raw2);
29996
30129
  return Number.isInteger(window) && window > 0 ? window : void 0;
29997
30130
  }
29998
- var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, CLAUDE_AUTO_COMPACT_WINDOW_ENV, CLAUDE_PROMPT_CACHE_TTL_ENV, CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV, WORKER_DISALLOWED_TOOLS;
30131
+ var FRIZZ_MCP, WORKER_MAX_WEB_SEARCHES, WORKER_MAX_SUBAGENTS, WORKER_MAX_CONCURRENT_SUBAGENTS, CLAUDE_WORKER_ENV, CLAUDE_AUTO_COMPACT_WINDOW_ENV, CODEX_CONTEXT_WINDOW_KEY, CLAUDE_PROMPT_CACHE_TTL_ENV, CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV, WORKER_DISALLOWED_TOOLS;
29999
30132
  var init_types2 = __esm({
30000
30133
  "packages/server/src/backend/types.ts"() {
30001
30134
  "use strict";
@@ -30013,6 +30146,7 @@ var init_types2 = __esm({
30013
30146
  BASH_MAX_TIMEOUT_MS: String(24 * 60 * 60 * 1e3)
30014
30147
  };
30015
30148
  CLAUDE_AUTO_COMPACT_WINDOW_ENV = "CLAUDE_CODE_AUTO_COMPACT_WINDOW";
30149
+ CODEX_CONTEXT_WINDOW_KEY = "model_context_window";
30016
30150
  CLAUDE_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_PROMPT_CACHE_TTL";
30017
30151
  CLAUDE_SUBAGENT_PROMPT_CACHE_TTL_ENV = "CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL";
30018
30152
  WORKER_DISALLOWED_TOOLS = ["AskUserQuestion"];
@@ -31945,6 +32079,7 @@ var init_codex_app_server = __esm({
31945
32079
  init_auth_status();
31946
32080
  init_codex_mcp();
31947
32081
  init_codex_error();
32082
+ init_types2();
31948
32083
  init_logging();
31949
32084
  CODEX_APP_SERVER_PROVIDER = "codex-app-server";
31950
32085
  CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
@@ -33612,19 +33747,29 @@ var init_codex_app_server = __esm({
33612
33747
  */
33613
33748
  /**
33614
33749
  * The `config` bag for one thread's `thread/start` / `thread/resume`: frizz's MCP server mounted
33615
- * PER THREAD so it knows who is calling, plus whatever the caller passed.
33750
+ * PER THREAD so it knows who is calling, the operator's Codex context window from Settings, plus
33751
+ * whatever the caller passed.
33616
33752
  *
33617
33753
  * This is the only channel that can carry a caller identity on codex. The argv mount on the
33618
33754
  * app-server is process-wide and serves every thread in the project, so `FRIZZ_THREAD_SLUG` was
33619
33755
  * simply absent and every tool that acts on the caller's own thread failed at the moment of use —
33620
33756
  * see `codexThreadMcpConfig` for the list and the measurement. Returns `{}` when there is nothing
33621
- * to send, so a caller with no config and no resolved MCP descriptor sends no `config` key at all,
33622
- * exactly as before.
33757
+ * to send, so a caller with no config, no resolved MCP descriptor and no context-window setting
33758
+ * sends no `config` key at all, exactly as before.
33759
+ *
33760
+ * The context window rides the same bag rather than the process-wide argv because the app-server is
33761
+ * a long-lived daemon: an argv value would be frozen at its first spawn, while a per-thread override
33762
+ * reads Settings at every start and every cold resume — the same "next thread, and a thread that
33763
+ * comes back" semantics the Claude compaction window has (see codexContextWindowConfig).
33623
33764
  *
33624
33765
  * The caller's own config wins on a key collision: this is a default, not an override.
33625
33766
  */
33626
33767
  threadConfig(threadSlug, callerConfig) {
33627
- const config = { ...codexThreadMcpConfig(this.options.frizzMcp, threadSlug), ...callerConfig };
33768
+ const config = {
33769
+ ...codexThreadMcpConfig(this.options.frizzMcp, threadSlug),
33770
+ ...codexContextWindowConfig(this.options.getSettings?.()),
33771
+ ...callerConfig
33772
+ };
33628
33773
  return Object.keys(config).length ? { config } : {};
33629
33774
  }
33630
33775
  resumeSandboxOverride(row) {
@@ -34272,7 +34417,6 @@ var init_codex_app_server = __esm({
34272
34417
  if (binding.current_turn_id !== null) {
34273
34418
  return;
34274
34419
  }
34275
- if (!this.pendingTurnStarts.has(turnKey(binding))) return;
34276
34420
  this.providerErrors.delete(binding.frizz_session_id);
34277
34421
  this.scope.prepare(`
34278
34422
  UPDATE codex_app_server_session SET current_turn_id = ?, updated_at = ?
@@ -34670,22 +34814,140 @@ var init_quota = __esm({
34670
34814
  }
34671
34815
  });
34672
34816
 
34817
+ // packages/server/src/local-file.ts
34818
+ import { spawn as spawn5 } from "node:child_process";
34819
+ import { readFileSync as readFileSync17, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
34820
+ import { homedir as homedir11 } from "node:os";
34821
+ import { isAbsolute as isAbsolute4, join as join25, resolve as resolve5, sep as sep2 } from "node:path";
34822
+ function isUnder(real, root) {
34823
+ let rootReal;
34824
+ try {
34825
+ rootReal = realpathSync6(root);
34826
+ } catch {
34827
+ return false;
34828
+ }
34829
+ return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
34830
+ }
34831
+ function resolveLocalFile(rawPath, roots) {
34832
+ if (!isAbsolute4(rawPath)) throw new Error("Local path must be absolute");
34833
+ let real;
34834
+ try {
34835
+ real = realpathSync6(rawPath);
34836
+ } catch {
34837
+ throw new Error("Local file was not found");
34838
+ }
34839
+ if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Frizz's trusted roots");
34840
+ try {
34841
+ if (!statSync12(real).isFile()) throw new Error("Local path is not a regular file");
34842
+ } catch (error) {
34843
+ if (error instanceof Error && error.message === "Local path is not a regular file") throw error;
34844
+ throw new Error("Local file was not found");
34845
+ }
34846
+ return real;
34847
+ }
34848
+ function resolveOpenableFile(raw2, projectDir, roots, home = homedir11()) {
34849
+ const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
34850
+ if (!trimmed) return null;
34851
+ const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join25(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve5(projectDir, trimmed);
34852
+ try {
34853
+ return resolveLocalFile(abs, roots);
34854
+ } catch {
34855
+ return null;
34856
+ }
34857
+ }
34858
+ function readLocalTextFile(rawPath, roots) {
34859
+ const path3 = resolveLocalFile(rawPath, roots);
34860
+ const bytes = readFileSync17(path3);
34861
+ if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
34862
+ if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, text: bytes.toString("utf8"), truncated: false };
34863
+ const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
34864
+ const lastBreak = head.lastIndexOf(10);
34865
+ return { path: path3, text: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
34866
+ }
34867
+ function readLocalMarkdown(rawPath, roots) {
34868
+ if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
34869
+ const path3 = resolveLocalFile(rawPath, roots);
34870
+ if (!MARKDOWN_FILE_EXT.test(path3)) throw new Error("Local file is not a Markdown file");
34871
+ const bytes = readFileSync17(path3);
34872
+ if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, markdown: bytes.toString("utf8"), truncated: false };
34873
+ const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
34874
+ const lastBreak = head.lastIndexOf(10);
34875
+ return { path: path3, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
34876
+ }
34877
+ function resolveWatchableLocalFile(rawPath, roots) {
34878
+ const path3 = resolveLocalFile(rawPath, roots);
34879
+ if (MARKDOWN_FILE_EXT.test(rawPath.trim()) && !MARKDOWN_FILE_EXT.test(path3)) {
34880
+ throw new Error("Local file is not a Markdown file");
34881
+ }
34882
+ return path3;
34883
+ }
34884
+ function defaultSpawn(command, args, options) {
34885
+ return spawn5(command, [...args], options);
34886
+ }
34887
+ function openLocalFile(rawPath, opener, roots, options = {}) {
34888
+ const path3 = resolveLocalFile(rawPath, roots);
34889
+ const selected = options.forceSystem ? "system" : opener;
34890
+ 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();
34893
+ return { action: "opened", path: path3 };
34894
+ }
34895
+ var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT;
34896
+ var init_local_file = __esm({
34897
+ "packages/server/src/local-file.ts"() {
34898
+ "use strict";
34899
+ MARKDOWN_FILE_EXT = /\.(?:md|mdx|markdown)$/i;
34900
+ MARKDOWN_READ_LIMIT = 1024 * 1024;
34901
+ }
34902
+ });
34903
+
34904
+ // packages/server/src/thread-links.ts
34905
+ import { isAbsolute as isAbsolute5 } from "node:path";
34906
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
34907
+ function threadLinkView(row) {
34908
+ return { id: row.id, kind: row.kind, label: row.label, target: row.target };
34909
+ }
34910
+ function resolveThreadLink(target, projectDir, roots) {
34911
+ if (/^https?:\/\//i.test(target)) {
34912
+ const url = new URL(target);
34913
+ if (url.username || url.password) throw new Error("Link URLs must not contain credentials");
34914
+ return { kind: "link", target: url.href };
34915
+ }
34916
+ let path3 = target;
34917
+ if (/^file:/i.test(target)) {
34918
+ const url = new URL(target);
34919
+ if (url.search || url.hash) throw new Error("File links must not contain a query or fragment");
34920
+ path3 = fileURLToPath2(url);
34921
+ } else if (!isAbsolute5(target) && /^[a-z][a-z\d+.-]*:/i.test(target)) {
34922
+ throw new Error("Register an HTTP(S) URL or a local file");
34923
+ }
34924
+ const resolved = resolveOpenableFile(path3, projectDir, roots);
34925
+ if (!resolved) throw new Error("Local file was not found or is outside Frizz's trusted roots");
34926
+ return { kind: "file", target: resolved };
34927
+ }
34928
+ var init_thread_links = __esm({
34929
+ "packages/server/src/thread-links.ts"() {
34930
+ "use strict";
34931
+ init_local_file();
34932
+ }
34933
+ });
34934
+
34673
34935
  // packages/server/src/frizz.ts
34674
34936
  import { execFile as execFile5 } from "node:child_process";
34675
- import { lstatSync as lstatSync2, realpathSync as realpathSync6 } from "node:fs";
34676
- import { basename as basename5, dirname as dirname9, join as join25, resolve as resolve5 } from "node:path";
34937
+ import { lstatSync as lstatSync2, realpathSync as realpathSync7 } from "node:fs";
34938
+ import { basename as basename5, dirname as dirname9, join as join26, resolve as resolve6 } from "node:path";
34677
34939
  import { promisify as promisify5 } from "node:util";
34678
34940
  function frizzScriptsDir() {
34679
34941
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
34680
- return resolve5(import.meta.dirname, "..", "..", "..", "board");
34942
+ return resolve6(import.meta.dirname, "..", "..", "..", "board");
34681
34943
  }
34682
34944
  function directFrizzRoot(projectDir) {
34683
34945
  try {
34684
- const projectRoot = realpathSync6(projectDir);
34685
- const path3 = join25(projectRoot, ".frizz");
34946
+ const projectRoot = realpathSync7(projectDir);
34947
+ const path3 = join26(projectRoot, ".frizz");
34686
34948
  const stat2 = lstatSync2(path3);
34687
34949
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
34688
- const real = realpathSync6(path3);
34950
+ const real = realpathSync7(path3);
34689
34951
  return dirname9(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
34690
34952
  } catch {
34691
34953
  return null;
@@ -34696,7 +34958,7 @@ function frizzDirExists(projectDir) {
34696
34958
  }
34697
34959
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34698
34960
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
34699
- const { stdout } = await execFileP("node", [join25(scriptsDir, "index.mjs"), "--json"], {
34961
+ const { stdout } = await execFileP("node", [join26(scriptsDir, "index.mjs"), "--json"], {
34700
34962
  cwd: projectDir,
34701
34963
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
34702
34964
  maxBuffer: 32 * 1024 * 1024
@@ -34721,7 +34983,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34721
34983
  };
34722
34984
  }
34723
34985
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
34724
- await execFileP("node", [join25(scriptsDir, "thread-update.mjs"), slug, ...args], {
34986
+ await execFileP("node", [join26(scriptsDir, "thread-update.mjs"), slug, ...args], {
34725
34987
  cwd: projectDir,
34726
34988
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
34727
34989
  });
@@ -34806,12 +35068,12 @@ var init_awaiting = __esm({
34806
35068
  });
34807
35069
 
34808
35070
  // packages/server/src/backend/project-mcp-servers.ts
34809
- import { readFileSync as readFileSync17 } from "node:fs";
34810
- import { homedir as homedir11 } from "node:os";
34811
- import { join as join26 } from "node:path";
35071
+ import { readFileSync as readFileSync18 } from "node:fs";
35072
+ import { homedir as homedir12 } from "node:os";
35073
+ import { join as join27 } from "node:path";
34812
35074
  function readJsonObject(path3) {
34813
35075
  try {
34814
- const parsed = JSON.parse(readFileSync17(path3, "utf8"));
35076
+ const parsed = JSON.parse(readFileSync18(path3, "utf8"));
34815
35077
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
34816
35078
  } catch {
34817
35079
  return void 0;
@@ -34902,17 +35164,17 @@ function userScopeRemoteServers(account, cwd, env) {
34902
35164
  }
34903
35165
  function projectMcpServers(cwd, options = {}) {
34904
35166
  const env = options.env ?? process.env;
34905
- const home = options.home ?? homedir11();
35167
+ const home = options.home ?? homedir12();
34906
35168
  const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
34907
- const accountFile = configOverride ? join26(configOverride, ".claude.json") : join26(home, ".claude.json");
34908
- const userSettingsFile = join26(configOverride ?? join26(home, ".claude"), "settings.json");
35169
+ const accountFile = configOverride ? join27(configOverride, ".claude.json") : join27(home, ".claude.json");
35170
+ const userSettingsFile = join27(configOverride ?? join27(home, ".claude"), "settings.json");
34909
35171
  const account = readJsonObject(accountFile);
34910
35172
  const out = userScopeRemoteServers(account, cwd, env);
34911
- const declared = objectField(readJsonObject(join26(cwd, ".mcp.json")), "mcpServers");
35173
+ const declared = objectField(readJsonObject(join27(cwd, ".mcp.json")), "mcpServers");
34912
35174
  if (!declared) return out;
34913
35175
  const approval = collectApproval([
34914
- readJsonObject(join26(cwd, ".claude", "settings.json")),
34915
- readJsonObject(join26(cwd, ".claude", "settings.local.json")),
35176
+ readJsonObject(join27(cwd, ".claude", "settings.json")),
35177
+ readJsonObject(join27(cwd, ".claude", "settings.local.json")),
34916
35178
  readJsonObject(userSettingsFile),
34917
35179
  objectField(objectField(account, "projects"), cwd)
34918
35180
  ]);
@@ -34937,16 +35199,16 @@ var init_project_mcp_servers = __esm({
34937
35199
 
34938
35200
  // packages/server/src/worker-plugin-dir.ts
34939
35201
  import { existsSync as existsSync8 } from "node:fs";
34940
- import { dirname as dirname10, join as join27 } from "node:path";
34941
- import { fileURLToPath as fileURLToPath2 } from "node:url";
35202
+ import { dirname as dirname10, join as join28 } from "node:path";
35203
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
34942
35204
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
34943
35205
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
34944
- if (override && existsSync8(join27(override, ".claude-plugin", "plugin.json")))
35206
+ if (override && existsSync8(join28(override, ".claude-plugin", "plugin.json")))
34945
35207
  return override;
34946
- let current = dirname10(fileURLToPath2(moduleUrl));
35208
+ let current = dirname10(fileURLToPath3(moduleUrl));
34947
35209
  for (; ; ) {
34948
- const candidate = join27(current, "cc-worker");
34949
- if (existsSync8(join27(candidate, ".claude-plugin", "plugin.json"))) return candidate;
35210
+ const candidate = join28(current, "cc-worker");
35211
+ if (existsSync8(join28(candidate, ".claude-plugin", "plugin.json"))) return candidate;
34950
35212
  const parent = dirname10(current);
34951
35213
  if (parent === current) return void 0;
34952
35214
  current = parent;
@@ -34974,6 +35236,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
34974
35236
  lean ? null : THREAD_EXECUTION[kind],
34975
35237
  AGENT_COMPLETION,
34976
35238
  VISUAL_EVIDENCE,
35239
+ REGISTERED_LINKS,
34977
35240
  QUALITY_BAR,
34978
35241
  TOKEN_ECONOMY,
34979
35242
  QUESTIONS,
@@ -34985,7 +35248,7 @@ function buildWorkerPrompt(kind = "claude", opts = {}) {
34985
35248
  out = out.replaceAll("{{FRIZZ_MONITORS_DIR}}", opts.monitorsDir?.trim() || MONITORS_DIR_FALLBACK);
34986
35249
  return out;
34987
35250
  }
34988
- var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, QUALITY_BAR, TOKEN_ECONOMY, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, THREAD_NAME, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
35251
+ var INLINE, INTRO, DEFER, OPENING, ACTIVITY_CAPTIONS, SIGNALS, AGENT_COMPLETION, VISUAL_EVIDENCE, REGISTERED_LINKS, QUALITY_BAR, TOKEN_ECONOMY, QUESTIONS, STOP_CRITERION, TRIVIAL_PROMPTS, THREAD_NAME, SCRATCHPAD, BACKEND, SPAWN_THREAD, THREAD_EXECUTION, MONITORS_DIR_FALLBACK;
34989
35252
  var init_workerPrompt = __esm({
34990
35253
  "packages/server/src/workerPrompt.ts"() {
34991
35254
  "use strict";
@@ -35170,7 +35433,8 @@ exactly ONE of them.
35170
35433
 
35171
35434
  - **THE FRONTMATTER IS YAML. THE BODY IS MARKDOWN.** Four PLURAL keys taking LISTS (either list form;
35172
35435
  a single item may be written bare), plus the scalars \`for:\` and \`title:\`. **NO PROSE ABOVE THE
35173
- \`---\`, EVER** \u2014 a colon or a \` #\` inside a sentence breaks the parse. There is no \`reason:\` key any more;
35436
+ \`---\`, EVER** \u2014 a colon or a \` #\` inside a sentence breaks the parse. (The \`title:\` value is the
35437
+ one exception: frizz reads it verbatim, so \`#391\` and a colon are safe there.) There is no \`reason:\` key any more;
35174
35438
  the reason goes in the body, which is the handoff the human reads. **THE SINGULAR KEYS ARE GONE** \u2014 \`shell:\`,
35175
35439
  \`agent:\`, \`timer:\`, \`pr:\` and \`reason:\`, one per line, were the grammar until 2026-08-24; YAML
35176
35440
  cannot express a repeated key, so they became the lists above. Write one and frizz tells you what
@@ -35276,6 +35540,12 @@ image paths through its guarded local-image proxy; only eligible workspace or ex
35276
35540
  image files can embed, and a path outside that safe boundary stays non-navigable. Do not bulk-embed
35277
35541
  irrelevant screenshots. Always keep a concise textual finding alongside them, so the handoff still
35278
35542
  reads when images are unavailable.`;
35543
+ REGISTERED_LINKS = `## Saved links and files
35544
+
35545
+ Use \`mcp__frizz__link\` with \`label\` and \`target\` for a dev server, working document, report or download
35546
+ the human should keep at hand. It stays underneath the prompt. Reuse the label to update its destination;
35547
+ read registrations with \`activity\` and remove one with \`unlink\`. Register existing files, using an
35548
+ absolute path outside the project root. A saved reference is not a running process or a completion gate.`;
35279
35549
  QUALITY_BAR = `## Quality bar
35280
35550
 
35281
35551
  - Verify behavior end-to-end before calling anything done. A green suite over a stubbed implementation
@@ -36100,12 +36370,12 @@ var init_claude_broker_client = __esm({
36100
36370
  });
36101
36371
 
36102
36372
  // packages/server/src/backend/claude-permission-interactions.ts
36103
- import { homedir as homedir12 } from "node:os";
36373
+ import { homedir as homedir13 } from "node:os";
36104
36374
  function clip(text2, max) {
36105
36375
  return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
36106
36376
  }
36107
36377
  function tildePath(path3) {
36108
- const home = homedir12();
36378
+ const home = homedir13();
36109
36379
  if (!path3.startsWith(home)) return path3;
36110
36380
  const rest = path3.slice(home.length);
36111
36381
  return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
@@ -36755,8 +37025,8 @@ var init_claude_agent_broker_bridge = __esm({
36755
37025
  });
36756
37026
 
36757
37027
  // packages/server/src/dispatch.ts
36758
- import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync18, realpathSync as realpathSync7, statSync as statSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
36759
- import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
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";
37029
+ import { basename as basename6, join as join29, dirname as dirname11 } from "node:path";
36760
37030
  import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
36761
37031
  function fallbackTitle(prompt) {
36762
37032
  const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
@@ -36775,7 +37045,7 @@ function fallbackTitle(prompt) {
36775
37045
  }
36776
37046
  function resolveSlug(frizzDir, base, taken) {
36777
37047
  base = ThreadSlug.parse(base);
36778
- const isTaken = (slug) => existsSync9(join28(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
37048
+ const isTaken = (slug) => existsSync9(join29(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
36779
37049
  if (!isTaken(base)) return base;
36780
37050
  for (let n = 2; ; n++) {
36781
37051
  const suffix = `-${n}`;
@@ -36791,16 +37061,16 @@ function resolveLegacyThreadFile(projectDir, value) {
36791
37061
  const parsed = ThreadSlug.safeParse(value);
36792
37062
  if (!parsed.success) return null;
36793
37063
  try {
36794
- const projectRoot = realpathSync7(projectDir);
36795
- const frizzPath = join28(projectRoot, ".frizz");
37064
+ const projectRoot = realpathSync8(projectDir);
37065
+ const frizzPath = join29(projectRoot, ".frizz");
36796
37066
  const frizzStat = lstatSync3(frizzPath);
36797
37067
  if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
36798
- const realFrizz = realpathSync7(frizzPath);
37068
+ const realFrizz = realpathSync8(frizzPath);
36799
37069
  if (dirname11(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
36800
- const path3 = join28(realFrizz, `${parsed.data}.md`);
37070
+ const path3 = join29(realFrizz, `${parsed.data}.md`);
36801
37071
  const before = lstatSync3(path3);
36802
37072
  if (!before.isFile() || before.isSymbolicLink()) return null;
36803
- const realPath = realpathSync7(path3);
37073
+ const realPath = realpathSync8(path3);
36804
37074
  if (dirname11(realPath) !== realFrizz || basename6(realPath) !== `${parsed.data}.md`) return null;
36805
37075
  let contents;
36806
37076
  let openedBefore;
@@ -36808,7 +37078,7 @@ function resolveLegacyThreadFile(projectDir, value) {
36808
37078
  const fd = openSync13(path3, constants.O_RDONLY | constants.O_NOFOLLOW);
36809
37079
  try {
36810
37080
  openedBefore = fstatSync2(fd);
36811
- contents = readFileSync18(fd);
37081
+ contents = readFileSync19(fd);
36812
37082
  openedAfter = fstatSync2(fd);
36813
37083
  } finally {
36814
37084
  closeSync12(fd);
@@ -36841,7 +37111,7 @@ function boardAuthorizesAdoption(board, slug) {
36841
37111
  return !board.errorItems.some((item) => item.file === `${slug}.md`);
36842
37112
  }
36843
37113
  function ensureSafeDirectDirectory(parent, name) {
36844
- const path3 = join28(parent, name);
37114
+ const path3 = join29(parent, name);
36845
37115
  try {
36846
37116
  mkdirSync13(path3);
36847
37117
  } catch (error) {
@@ -36850,7 +37120,7 @@ function ensureSafeDirectDirectory(parent, name) {
36850
37120
  }
36851
37121
  const stat2 = lstatSync3(path3);
36852
37122
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) throw new Error("unsafe project directory");
36853
- const real = realpathSync7(path3);
37123
+ const real = realpathSync8(path3);
36854
37124
  if (dirname11(real) !== parent || basename6(real) !== name) throw new Error("unsafe project directory");
36855
37125
  return real;
36856
37126
  }
@@ -36859,7 +37129,7 @@ function scratchDirRelPath(sessionId) {
36859
37129
  }
36860
37130
  function writeScratchDir(projectDir, sessionId) {
36861
37131
  if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/.test(sessionId)) throw new Error("invalid session id");
36862
- const projectRoot = realpathSync7(projectDir);
37132
+ const projectRoot = realpathSync8(projectDir);
36863
37133
  const frizzDir = ensureSafeDirectDirectory(projectRoot, ".frizz");
36864
37134
  const threadsDir = ensureSafeDirectDirectory(frizzDir, "threads");
36865
37135
  ensureSafeDirectDirectory(threadsDir, sessionId);
@@ -36871,8 +37141,8 @@ function loadWorkerPrompt(kind = "claude") {
36871
37141
  function monitorScriptsDir() {
36872
37142
  const plugin = workerPluginDir();
36873
37143
  if (!plugin) return void 0;
36874
- const dir = join28(plugin, "skills", "gh", "scripts");
36875
- return existsSync9(join28(dir, "ci-watch.mjs")) ? dir : void 0;
37144
+ const dir = join29(plugin, "skills", "gh", "scripts");
37145
+ return existsSync9(join29(dir, "ci-watch.mjs")) ? dir : void 0;
36876
37146
  }
36877
37147
  function codexScratchpadHookConfig(hookScript, sessionId) {
36878
37148
  if (!hookScript || !sessionId) return {};
@@ -36884,7 +37154,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
36884
37154
  }
36885
37155
  ]
36886
37156
  });
36887
- const bashBackgroundHook = join28(dirname11(hookScript), "bash-background.mjs");
37157
+ const bashBackgroundHook = join29(dirname11(hookScript), "bash-background.mjs");
36888
37158
  return {
36889
37159
  bypass_hook_trust: true,
36890
37160
  hooks: {
@@ -36910,7 +37180,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
36910
37180
  }
36911
37181
  function scratchpadHookScript() {
36912
37182
  const plugin = workerPluginDir();
36913
- return plugin ? join28(plugin, "hooks", "scratchpad.mjs") : void 0;
37183
+ return plugin ? join29(plugin, "hooks", "scratchpad.mjs") : void 0;
36914
37184
  }
36915
37185
  function composePrompt(sessionId, prompt, kind = "claude") {
36916
37186
  const children2 = kind === "codex" ? "Native sub-agents share it \u2014 have each write its OWN file rather than all editing one." : "Name it in a sub-agent's prompt when you want its notes to land somewhere you can read; give each child its OWN file rather than having them all edit one.";
@@ -36926,12 +37196,12 @@ function scratchpadOrientation(sessionId, kind = "claude") {
36926
37196
  return `SCRATCH DIRECTORY: .frizz/threads/${sessionId}/ \u2014 yours, free-form, as many files as you like, and nothing is expected in it. A single direct task usually needs none; writing notes is never a substitute for doing the work (${children2}). Nothing in this directory is read automatically; if you want a note back after a compaction, mcp__frizz__goal with post_compaction: true re-sends a prompt of your choosing.`;
36927
37197
  }
36928
37198
  function frizzConfigBlock(projectDir) {
36929
- const path3 = join28(projectDir, "FRIZZ.md");
37199
+ const path3 = join29(projectDir, "FRIZZ.md");
36930
37200
  let body;
36931
37201
  try {
36932
- const st = statSync12(path3);
37202
+ const st = statSync13(path3);
36933
37203
  if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
36934
- body = readFileSync18(path3, "utf8").trim();
37204
+ body = readFileSync19(path3, "utf8").trim();
36935
37205
  } catch {
36936
37206
  return "";
36937
37207
  }
@@ -36970,7 +37240,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
36970
37240
  const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
36971
37241
  const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
36972
37242
  if (!pluginDir) return void 0;
36973
- const scriptPath = join28(pluginDir, "bin", FRIZZ_MCP.script);
37243
+ const scriptPath = join29(pluginDir, "bin", FRIZZ_MCP.script);
36974
37244
  if (!existsSync9(scriptPath)) return void 0;
36975
37245
  return {
36976
37246
  scriptPath,
@@ -37053,7 +37323,7 @@ function buildClaudeResumeCommand(opts) {
37053
37323
  }
37054
37324
  function createDispatcher(deps) {
37055
37325
  const readBoardSource = deps.readBoard ?? readBoard;
37056
- const frizzDir = join28(deps.project.dir, ".frizz");
37326
+ const frizzDir = join29(deps.project.dir, ".frizz");
37057
37327
  const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
37058
37328
  function cleanupPrewrites(built) {
37059
37329
  for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
@@ -37066,7 +37336,7 @@ function createDispatcher(deps) {
37066
37336
  function cleanupDispatchFiles(scratchRel, built, sessionId) {
37067
37337
  cleanupPrewrites(built);
37068
37338
  try {
37069
- rmSync9(join28(deps.project.dir, scratchRel), { force: true, recursive: true });
37339
+ rmSync9(join29(deps.project.dir, scratchRel), { force: true, recursive: true });
37070
37340
  } catch {
37071
37341
  }
37072
37342
  cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
@@ -37504,11 +37774,11 @@ var init_dispatch = __esm({
37504
37774
 
37505
37775
  // packages/server/src/board.ts
37506
37776
  import {
37507
- realpathSync as realpathSync8,
37777
+ realpathSync as realpathSync9,
37508
37778
  watch as fsWatch
37509
37779
  } from "node:fs";
37510
- import { homedir as homedir13 } from "node:os";
37511
- import { dirname as dirname12, join as join29 } from "node:path";
37780
+ import { homedir as homedir14 } from "node:os";
37781
+ import { dirname as dirname12, join as join30 } from "node:path";
37512
37782
  import watcher from "@parcel/watcher";
37513
37783
  function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
37514
37784
  if (!liveness) return false;
@@ -37891,6 +38161,7 @@ function registeredDoneFence(done, lastUserAt) {
37891
38161
  }
37892
38162
  function readThreadRegistries(storage) {
37893
38163
  return {
38164
+ links: storage.threadLinksBySlug(),
37894
38165
  prWatches: storage.armedPrWatchesBySlug(),
37895
38166
  timers: storage.armedThreadTimersBySlug(),
37896
38167
  questions: storage.threadQuestionsBySlug(),
@@ -37998,6 +38269,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
37998
38269
  lastAssistantAt: tele?.lastAssistantAt,
37999
38270
  subAgents: stampStoppable(tele?.subAgents ?? [], row),
38000
38271
  bgShells: stampStoppableShells(tele?.bgShells ?? [], row),
38272
+ links: (registries.links.get(row.slug) ?? []).map(threadLinkView),
38001
38273
  // ONE SOURCE: the FENCE. Both kinds are derived from what the worker wrote — `prs:` entries
38002
38274
  // become the github rows, `watch:` lines the shell rows — so this strip lists exactly what will
38003
38275
  // actually wake the thread, and the two cannot drift into claiming different things. There is no
@@ -38245,7 +38517,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
38245
38517
  ...project.githubRepo ? { githubRepo: project.githubRepo } : {},
38246
38518
  projectSlug: findByPath(project.dir)?.slug,
38247
38519
  // So the client can expand a `~` a worker wrote in prose (see BoardSnapshot.homeDir).
38248
- homeDir: homedir13()
38520
+ homeDir: homedir14()
38249
38521
  };
38250
38522
  const sessionThreads = buildSessionThreads(assembledAtMs);
38251
38523
  armSnoozeWake(sessionThreads, assembledAtMs);
@@ -38346,10 +38618,10 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
38346
38618
  if (parcelSub || stopped) return Promise.resolve();
38347
38619
  if (watchSetup) return watchSetup;
38348
38620
  const setup = (async () => {
38349
- const dir = join29(project.dir, ".frizz");
38621
+ const dir = join30(project.dir, ".frizz");
38350
38622
  const roots = /* @__PURE__ */ new Set([dir]);
38351
38623
  try {
38352
- roots.add(realpathSync8(dir));
38624
+ roots.add(realpathSync9(dir));
38353
38625
  } catch {
38354
38626
  }
38355
38627
  const next = await subscribe(
@@ -38361,7 +38633,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
38361
38633
  }
38362
38634
  if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
38363
38635
  },
38364
- { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join29(dir, name)) }
38636
+ { ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join30(dir, name)) }
38365
38637
  );
38366
38638
  if (stopped) {
38367
38639
  await next.unsubscribe();
@@ -38443,6 +38715,7 @@ var init_board = __esm({
38443
38715
  "use strict";
38444
38716
  init_src();
38445
38717
  init_storage();
38718
+ init_thread_links();
38446
38719
  init_thread_profiles();
38447
38720
  init_frizz();
38448
38721
  init_awaiting();
@@ -41038,7 +41311,7 @@ var init_resume = __esm({
41038
41311
  });
41039
41312
 
41040
41313
  // packages/server/src/backend/claude.ts
41041
- import { join as join30 } from "node:path";
41314
+ import { join as join31 } from "node:path";
41042
41315
  function toolResultText2(content) {
41043
41316
  if (typeof content === "string") return content;
41044
41317
  if (!Array.isArray(content)) return "";
@@ -41129,7 +41402,7 @@ function createClaudeBackend(opts) {
41129
41402
  return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
41130
41403
  },
41131
41404
  transcriptPath(sessionId) {
41132
- return join30(opts.logDir, `${sessionId}.jsonl`);
41405
+ return join31(opts.logDir, `${sessionId}.jsonl`);
41133
41406
  },
41134
41407
  parseLine(line) {
41135
41408
  return parseClaudeLine(line);
@@ -41299,13 +41572,13 @@ var init_login_utility = __esm({
41299
41572
  });
41300
41573
 
41301
41574
  // packages/server/src/backend/codex-app-server-diagnostics.ts
41302
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync13 } from "node:fs";
41303
- import { join as join31 } from "node:path";
41575
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, renameSync as renameSync8, statSync as statSync14 } from "node:fs";
41576
+ import { join as join32 } from "node:path";
41304
41577
  function codexDiagnosticLogPath(stateDir, projectId) {
41305
- return join31(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
41578
+ return join32(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
41306
41579
  }
41307
41580
  function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
41308
- const dir = join31(stateDir, "codex-app-server");
41581
+ const dir = join32(stateDir, "codex-app-server");
41309
41582
  const path3 = codexDiagnosticLogPath(stateDir, projectId);
41310
41583
  let ensured = false;
41311
41584
  return (event) => {
@@ -41316,7 +41589,7 @@ function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__
41316
41589
  ensured = true;
41317
41590
  }
41318
41591
  try {
41319
- if (statSync13(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
41592
+ if (statSync14(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
41320
41593
  } catch {
41321
41594
  }
41322
41595
  appendFileSync3(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
@@ -41337,7 +41610,7 @@ var init_codex_app_server_diagnostics = __esm({
41337
41610
  import { execFile as execFile8 } from "node:child_process";
41338
41611
  import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
41339
41612
  import { connect as connectSocket } from "node:net";
41340
- import { join as join32 } from "node:path";
41613
+ import { join as join33 } from "node:path";
41341
41614
  function parseLsofSocketNames(stdout) {
41342
41615
  const referenced = /* @__PURE__ */ new Set();
41343
41616
  for (const line of stdout.split("\n")) {
@@ -41354,7 +41627,7 @@ function sweepStaleSockets(options, deps = {}) {
41354
41627
  const keep = new Set(options.keep ?? []);
41355
41628
  let candidates;
41356
41629
  try {
41357
- candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join32(options.dir, name)).filter((path3) => !keep.has(path3));
41630
+ candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join33(options.dir, name)).filter((path3) => !keep.has(path3));
41358
41631
  } catch {
41359
41632
  return;
41360
41633
  }
@@ -41404,7 +41677,7 @@ var init_stale_socket_sweep = __esm({
41404
41677
 
41405
41678
  // packages/server/src/orphan-reaper.ts
41406
41679
  import { execFileSync as execFileSync4 } from "node:child_process";
41407
- import { readFileSync as readFileSync19 } from "node:fs";
41680
+ import { readFileSync as readFileSync20 } from "node:fs";
41408
41681
  import { dirname as dirname13 } from "node:path";
41409
41682
  function firstTokenBasename(command) {
41410
41683
  const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
@@ -41671,7 +41944,7 @@ var init_orphan_reaper = __esm({
41671
41944
  defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
41672
41945
  defaultReadEnv = (pid) => {
41673
41946
  try {
41674
- return readFileSync19(`/proc/${pid}/environ`, "utf8");
41947
+ return readFileSync20(`/proc/${pid}/environ`, "utf8");
41675
41948
  } catch {
41676
41949
  return null;
41677
41950
  }
@@ -41818,9 +42091,9 @@ var init_thread_hibernation = __esm({
41818
42091
  });
41819
42092
 
41820
42093
  // packages/server/src/context.ts
41821
- import { join as join33 } from "node:path";
42094
+ import { join as join34 } from "node:path";
41822
42095
  import { randomUUID as randomUUID15 } from "node:crypto";
41823
- import { homedir as homedir14 } from "node:os";
42096
+ import { homedir as homedir15 } from "node:os";
41824
42097
  function reconcileSessions(storage) {
41825
42098
  for (const row of storage.allSessions()) {
41826
42099
  if (isHeadlessRow(row)) continue;
@@ -41966,9 +42239,9 @@ async function createContext(opts = {}) {
41966
42239
  }
41967
42240
  }
41968
42241
  function createContextUnchecked(opts, resources) {
41969
- const home = opts.home ?? homedir14();
42242
+ const home = opts.home ?? homedir15();
41970
42243
  const project = opts.project ?? resolveProject();
41971
- const storage = createStorage(opts.database ?? join33(project.stateDir, "ui.db"), project.id);
42244
+ const storage = createStorage(opts.database ?? join34(project.stateDir, "ui.db"), project.id);
41972
42245
  resources.storage = storage;
41973
42246
  const bus = new Bus();
41974
42247
  const transcriptChange = new Emitter();
@@ -42078,7 +42351,10 @@ function createContextUnchecked(opts, resources) {
42078
42351
  const mode = PermissionMode.safeParse(row.permission_mode);
42079
42352
  if (!mode.success) return void 0;
42080
42353
  return codexSandbox(mode.data);
42081
- }
42354
+ },
42355
+ // Read at every thread/start and cold resume, so the Codex context window the operator picks in
42356
+ // Settings reaches the next thread without an app-server restart.
42357
+ getSettings: () => getSettings(storage, home)
42082
42358
  }) : void 0;
42083
42359
  resources.codexAppServer = codexAppServer;
42084
42360
  if (codexAppServer) {
@@ -45023,8 +45299,8 @@ var init_server = __esm({
45023
45299
  });
45024
45300
 
45025
45301
  // packages/server/src/repair.ts
45026
- import { existsSync as existsSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "node:fs";
45027
- import { basename as basename7, dirname as dirname14, resolve as resolve7 } from "node:path";
45302
+ import { existsSync as existsSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "node:fs";
45303
+ import { basename as basename7, dirname as dirname14, resolve as resolve8 } from "node:path";
45028
45304
  function quoteValue(v) {
45029
45305
  if (/^[\w./#:+-]+$/.test(v)) return v;
45030
45306
  return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -45034,12 +45310,12 @@ function deriveTitle(body, slug) {
45034
45310
  return m ? m[1].trim() : slug;
45035
45311
  }
45036
45312
  function repairThreadFile(frizzDir, file) {
45037
- const root = resolve7(frizzDir);
45038
- const abs = resolve7(root, file);
45313
+ const root = resolve8(frizzDir);
45314
+ const abs = resolve8(root, file);
45039
45315
  if (dirname14(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
45040
45316
  if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
45041
45317
  if (!existsSync11(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
45042
- const content = readFileSync20(abs, "utf8");
45318
+ const content = readFileSync21(abs, "utf8");
45043
45319
  const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
45044
45320
  if (firstLine2?.trim() === "---") {
45045
45321
  throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
@@ -45230,7 +45506,7 @@ var init_repo_files = __esm({
45230
45506
  });
45231
45507
 
45232
45508
  // cc-worker/hooks/bash-background.mjs
45233
- import { readFileSync as readFileSync21 } from "node:fs";
45509
+ import { readFileSync as readFileSync22 } from "node:fs";
45234
45510
  import { basename as basename8 } from "node:path";
45235
45511
  import { pathToFileURL } from "node:url";
45236
45512
  function emit(obj) {
@@ -45379,7 +45655,7 @@ var init_bash_background = __esm({
45379
45655
  if (isDirectHookExecution(process.argv[1], import.meta.url)) {
45380
45656
  try {
45381
45657
  const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
45382
- emit(evaluateBashBackgroundHook(JSON.parse(readFileSync21(0, "utf8")), env));
45658
+ emit(evaluateBashBackgroundHook(JSON.parse(readFileSync22(0, "utf8")), env));
45383
45659
  } catch {
45384
45660
  emit({});
45385
45661
  }
@@ -45388,11 +45664,11 @@ var init_bash_background = __esm({
45388
45664
  });
45389
45665
 
45390
45666
  // packages/server/src/transcript.ts
45391
- import { closeSync as closeSync13, existsSync as existsSync12, fstatSync as fstatSync3, mkdirSync as mkdirSync15, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync22, readSync as readSync7, renameSync as renameSync9, statSync as statSync14, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
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";
45392
45668
  import { createHash as createHash13 } from "node:crypto";
45393
45669
  import { StringDecoder as StringDecoder4 } from "node:string_decoder";
45394
- import { join as join34 } from "node:path";
45395
- import { homedir as homedir15 } from "node:os";
45670
+ import { join as join35 } from "node:path";
45671
+ import { homedir as homedir16 } from "node:os";
45396
45672
  function formatTokens(n) {
45397
45673
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
45398
45674
  if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
@@ -45971,14 +46247,14 @@ function pruneScreenshotCache() {
45971
46247
  if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
45972
46248
  const byMtime = entries2.map((n) => {
45973
46249
  try {
45974
- return { n, m: statSync14(join34(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
46250
+ return { n, m: statSync15(join35(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
45975
46251
  } catch {
45976
46252
  return { n, m: 0 };
45977
46253
  }
45978
46254
  }).sort((a, b) => b.m - a.m);
45979
46255
  for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
45980
46256
  try {
45981
- unlinkSync5(join34(SCREENSHOT_CACHE_DIR, n));
46257
+ unlinkSync5(join35(SCREENSHOT_CACHE_DIR, n));
45982
46258
  } catch {
45983
46259
  }
45984
46260
  }
@@ -46008,14 +46284,14 @@ function persistBase64Image(mediaType, data, idKey) {
46008
46284
  const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
46009
46285
  if (!ext || !data) return void 0;
46010
46286
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46011
- const path3 = join34(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
46287
+ const path3 = join35(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
46012
46288
  try {
46013
46289
  if (existsSync12(path3)) return path3;
46014
46290
  if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
46015
46291
  const buf = Buffer.from(data, "base64");
46016
46292
  if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
46017
46293
  mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
46018
- const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46294
+ const tmp = join35(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46019
46295
  writeFileSync12(tmp, buf);
46020
46296
  renameSync9(tmp, path3);
46021
46297
  pruneScreenshotCache();
@@ -46030,14 +46306,14 @@ function persistSentFile(srcPath, idKey) {
46030
46306
  const outExt = ext === "jpeg" ? "jpg" : ext;
46031
46307
  try {
46032
46308
  const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
46033
- const dest = join34(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
46309
+ const dest = join35(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
46034
46310
  if (existsSync12(dest)) return dest;
46035
- const size = statSync14(srcPath).size;
46311
+ const size = statSync15(srcPath).size;
46036
46312
  if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
46037
- const buf = readFileSync22(srcPath);
46313
+ const buf = readFileSync23(srcPath);
46038
46314
  if (!looksLikeImage(buf, outExt)) return void 0;
46039
46315
  mkdirSync15(SCREENSHOT_CACHE_DIR, { recursive: true });
46040
- const tmp = join34(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46316
+ const tmp = join35(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
46041
46317
  writeFileSync12(tmp, buf);
46042
46318
  renameSync9(tmp, dest);
46043
46319
  pruneScreenshotCache();
@@ -46475,7 +46751,7 @@ function readTranscript(project, sessionId) {
46475
46751
  }
46476
46752
  function verifyIncrementalParse(path3, identityPrefix, incremental) {
46477
46753
  try {
46478
- const fresh = parseTranscript(readFileSync22(path3, "utf8"), identityPrefix);
46754
+ const fresh = parseTranscript(readFileSync23(path3, "utf8"), identityPrefix);
46479
46755
  const a = JSON.stringify(incremental);
46480
46756
  const b = JSON.stringify(fresh);
46481
46757
  if (a !== b) {
@@ -46495,16 +46771,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
46495
46771
  }
46496
46772
  }
46497
46773
  function logDirOf(project) {
46498
- return join34(homedir15(), ".claude", "projects", project.cwdSlug);
46774
+ return join35(homedir16(), ".claude", "projects", project.cwdSlug);
46499
46775
  }
46500
46776
  function resolveTranscriptPath(project, sessionId) {
46501
- const path3 = join34(logDirOf(project), `${sessionId}.jsonl`);
46777
+ const path3 = join35(logDirOf(project), `${sessionId}.jsonl`);
46502
46778
  try {
46503
- if (statSync14(path3).size > 0) return path3;
46779
+ if (statSync15(path3).size > 0) return path3;
46504
46780
  } catch {
46505
46781
  }
46506
46782
  const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
46507
- return stranded ? join34(stranded, `${sessionId}.jsonl`) : path3;
46783
+ return stranded ? join35(stranded, `${sessionId}.jsonl`) : path3;
46508
46784
  }
46509
46785
  function projectCodexTranscript(raw2, identityPrefix = "codex") {
46510
46786
  const out = [];
@@ -46569,8 +46845,9 @@ ${text2}` : text2;
46569
46845
  }
46570
46846
  case "tool-call": {
46571
46847
  const m = openAssistant(ev.at, sourceId);
46572
- const call = codexToolCall(ev.name, ev.input, ev.id);
46573
- call.status = "pending";
46848
+ const split = codexToolCards(ev.name, ev.input, ev.id);
46849
+ const call = split.owner;
46850
+ for (const card of split.cards) card.status = "pending";
46574
46851
  if (pendingCaption) {
46575
46852
  if (!call.desc) call.desc = pendingCaption;
46576
46853
  pendingCaption = void 0;
@@ -46580,8 +46857,10 @@ ${text2}` : text2;
46580
46857
  const interruptedOwner = call.name === "Interrupt process" && call.sessionId !== void 0 ? shellSessions.get(runningKey("Poll process", call.sessionId)) : void 0;
46581
46858
  if (!owner) {
46582
46859
  if (isPoll) call.backgroundState = "unknown";
46583
- pushToolPart(m, call);
46584
- m.tools.push(call);
46860
+ for (const card of split.cards) {
46861
+ pushToolPart(m, card);
46862
+ m.tools.push(card);
46863
+ }
46585
46864
  }
46586
46865
  if (call.name === "Spawn agent" && call.detail) agentDispatches.set(call.detail, { call, at: ev.at });
46587
46866
  if (ev.id) pendingCalls.set(ev.id, {
@@ -46590,7 +46869,8 @@ ${text2}` : text2;
46590
46869
  owner,
46591
46870
  interruptedOwner,
46592
46871
  orphanPoll: isPoll && !owner,
46593
- explicitBackground: codexExplicitBackground(ev.name, ev.input)
46872
+ explicitBackground: codexExplicitBackground(ev.name, ev.input),
46873
+ ...split.cards.length > 1 ? { companions: split.cards.filter((card) => card !== call), views: split.views } : {}
46594
46874
  });
46595
46875
  break;
46596
46876
  }
@@ -46600,7 +46880,17 @@ ${text2}` : text2;
46600
46880
  pendingCalls.delete(ev.id);
46601
46881
  const result = codexToolResult(ev.text);
46602
46882
  if (result.durationMs === void 0) result.durationMs = elapsedBetween(pending.at, ev.at);
46603
- if (ev.image && result.status !== "failed" && result.status !== "cancelled") {
46883
+ if (pending.companions) {
46884
+ for (const card of pending.companions) card.status = result.status === "cancelled" ? "cancelled" : "completed";
46885
+ if (result.status !== "cancelled") {
46886
+ const inline = ev.images ?? (ev.image ? [ev.image] : []);
46887
+ pending.views?.forEach((view, i) => {
46888
+ if (view.outputImage || !inline[i]) return;
46889
+ const shot = persistDataUrlImage(inline[i], `${ev.id}:view:${i}`);
46890
+ if (shot) view.outputImage = shot;
46891
+ });
46892
+ }
46893
+ } else if (ev.image && result.status !== "failed" && result.status !== "cancelled") {
46604
46894
  const shot = persistDataUrlImage(ev.image, ev.id);
46605
46895
  if (shot) (pending.owner ?? pending).call.outputImage = shot;
46606
46896
  }
@@ -46829,6 +47119,11 @@ function latestTranscriptWindow(messages) {
46829
47119
  }
46830
47120
  return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46831
47121
  }
47122
+ function codexToolCards(name, input, callId) {
47123
+ if (name === "exec" && typeof input === "string") return codexExecWrapperCards(input, callId);
47124
+ const owner = codexToolCall(name, input, callId);
47125
+ return { owner, cards: [owner] };
47126
+ }
46832
47127
  function codexToolCall(name, input, callId) {
46833
47128
  if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
46834
47129
  const obj = input && typeof input === "object" && !Array.isArray(input) ? input : {};
@@ -46987,8 +47282,32 @@ function formatCompactDuration(ms) {
46987
47282
  if (minutes < 60) return `${minutes}m`;
46988
47283
  return minutes % 60 ? `${Math.floor(minutes / 60)}h ${minutes % 60}m` : `${Math.floor(minutes / 60)}h`;
46989
47284
  }
46990
- function codexExecWrapperCall(source, callId) {
47285
+ function codexExecWrapperCards(source, callId) {
46991
47286
  const calls = wrappedInvocations(source);
47287
+ const rest = [];
47288
+ const views = [];
47289
+ const order = [];
47290
+ if (calls.length > 1) {
47291
+ for (const call of calls) {
47292
+ if (call.name !== "view_image") {
47293
+ if (!rest.length) order.push("rest");
47294
+ rest.push(call);
47295
+ continue;
47296
+ }
47297
+ const view = viewImageCall(jsStringProperty(call.args, "path"), `${callId ?? source}:view:${views.length}`);
47298
+ views.push(view);
47299
+ order.push(view);
47300
+ }
47301
+ }
47302
+ if (!views.length) {
47303
+ const owner = codexExecWrapperCall(source, callId, calls);
47304
+ return { owner, cards: [owner] };
47305
+ }
47306
+ const remainder = rest.length ? codexExecWrapperCall(source, callId, rest) : void 0;
47307
+ const cards = order.map((entry) => entry === "rest" ? remainder : entry);
47308
+ return { owner: remainder ?? views[0], cards, views };
47309
+ }
47310
+ function codexExecWrapperCall(source, callId, calls = wrappedInvocations(source)) {
46992
47311
  if (calls.length !== 1) {
46993
47312
  return {
46994
47313
  name: "Exec",
@@ -46996,7 +47315,9 @@ function codexExecWrapperCall(source, callId) {
46996
47315
  input: capToolInput(source.trim())
46997
47316
  };
46998
47317
  }
46999
- const call = calls[0];
47318
+ return wrappedSingleCall(calls[0], source, callId);
47319
+ }
47320
+ function wrappedSingleCall(call, source, callId) {
47000
47321
  if (call.name === "exec_command") {
47001
47322
  const cmd = jsStringProperty(call.args, "cmd") ?? jsStringProperty(call.args, "command");
47002
47323
  const cwd = jsStringProperty(call.args, "workdir") ?? jsStringProperty(call.args, "cwd");
@@ -47372,7 +47693,7 @@ function unifiedToolResult(text2) {
47372
47693
  if (!header) return void 0;
47373
47694
  const wrapperStatus = header[1] === "failed" ? "failed" : header[1] === "terminated" ? "cancelled" : "completed";
47374
47695
  const wrapperDurationMs = Number(header[2]) * 1e3;
47375
- const body = raw2.slice(header[0].length).trim();
47696
+ const body = raw2.slice(header[0].length).split("[image output]").join("").trim();
47376
47697
  if (!body || body === "{}") return { status: wrapperStatus, durationMs: wrapperDurationMs, terminal: true };
47377
47698
  try {
47378
47699
  const parsed = JSON.parse(body);
@@ -47752,7 +48073,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
47752
48073
  }
47753
48074
  function readCodexTranscriptFile(absPath, nativeId = absPath) {
47754
48075
  try {
47755
- return parseCodexTranscript(readFileSync22(absPath, "utf8"), `codex:${nativeId}`);
48076
+ return parseCodexTranscript(readFileSync23(absPath, "utf8"), `codex:${nativeId}`);
47756
48077
  } catch {
47757
48078
  return [];
47758
48079
  }
@@ -47850,7 +48171,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
47850
48171
  function readTranscriptFile(absPath) {
47851
48172
  try {
47852
48173
  const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
47853
- return parseTranscript(readFileSync22(absPath, "utf8"), `claude-file:${pathKey}`);
48174
+ return parseTranscript(readFileSync23(absPath, "utf8"), `claude-file:${pathKey}`);
47854
48175
  } catch {
47855
48176
  return [];
47856
48177
  }
@@ -47926,7 +48247,7 @@ var init_transcript = __esm({
47926
48247
  });
47927
48248
 
47928
48249
  // packages/server/src/open-external.ts
47929
- import { spawn as spawn5 } from "node:child_process";
48250
+ import { spawn as spawn6 } from "node:child_process";
47930
48251
  function validateExternalUrl(raw2) {
47931
48252
  let parsed;
47932
48253
  try {
@@ -47943,7 +48264,7 @@ function openExternalUrl(raw2) {
47943
48264
  const v = validateExternalUrl(raw2);
47944
48265
  if (!v.ok) throw new Error(v.reason);
47945
48266
  const [cmd, args] = process.platform === "darwin" ? ["open", [v.url]] : ["xdg-open", [v.url]];
47946
- spawn5(cmd, args, { detached: true, stdio: "ignore" }).unref();
48267
+ spawn6(cmd, args, { detached: true, stdio: "ignore" }).unref();
47947
48268
  }
47948
48269
  var init_open_external = __esm({
47949
48270
  "packages/server/src/open-external.ts"() {
@@ -47951,93 +48272,6 @@ var init_open_external = __esm({
47951
48272
  }
47952
48273
  });
47953
48274
 
47954
- // packages/server/src/local-file.ts
47955
- import { spawn as spawn6 } from "node:child_process";
47956
- import { readFileSync as readFileSync23, realpathSync as realpathSync9, statSync as statSync15 } from "node:fs";
47957
- import { homedir as homedir16 } from "node:os";
47958
- import { isAbsolute as isAbsolute4, join as join35, resolve as resolve8, sep as sep2 } from "node:path";
47959
- function isUnder(real, root) {
47960
- let rootReal;
47961
- try {
47962
- rootReal = realpathSync9(root);
47963
- } catch {
47964
- return false;
47965
- }
47966
- return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
47967
- }
47968
- function resolveLocalFile(rawPath, roots) {
47969
- if (!isAbsolute4(rawPath)) throw new Error("Local path must be absolute");
47970
- let real;
47971
- try {
47972
- real = realpathSync9(rawPath);
47973
- } catch {
47974
- throw new Error("Local file was not found");
47975
- }
47976
- if (!roots.some((root) => isUnder(real, root))) throw new Error("Local file is outside Frizz's trusted roots");
47977
- try {
47978
- if (!statSync15(real).isFile()) throw new Error("Local path is not a regular file");
47979
- } catch (error) {
47980
- if (error instanceof Error && error.message === "Local path is not a regular file") throw error;
47981
- throw new Error("Local file was not found");
47982
- }
47983
- return real;
47984
- }
47985
- function resolveOpenableFile(raw2, projectDir, roots, home = homedir16()) {
47986
- const trimmed = raw2.trim().replace(/:\d+(?::\d+)?$/, "");
47987
- if (!trimmed) return null;
47988
- const abs = trimmed === "~" ? home : trimmed.startsWith("~/") ? join35(home, trimmed.slice(2)) : isAbsolute4(trimmed) ? trimmed : resolve8(projectDir, trimmed);
47989
- try {
47990
- return resolveLocalFile(abs, roots);
47991
- } catch {
47992
- return null;
47993
- }
47994
- }
47995
- function readLocalTextFile(rawPath, roots) {
47996
- const path3 = resolveLocalFile(rawPath, roots);
47997
- const bytes = readFileSync23(path3);
47998
- if (bytes.subarray(0, 8192).includes(0)) throw new Error("Local file is not a text file");
47999
- if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, text: bytes.toString("utf8"), truncated: false };
48000
- const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
48001
- const lastBreak = head.lastIndexOf(10);
48002
- return { path: path3, text: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
48003
- }
48004
- function readLocalMarkdown(rawPath, roots) {
48005
- if (!MARKDOWN_FILE_EXT.test(rawPath.trim())) throw new Error("Local file is not a Markdown file");
48006
- const path3 = resolveLocalFile(rawPath, roots);
48007
- if (!MARKDOWN_FILE_EXT.test(path3)) throw new Error("Local file is not a Markdown file");
48008
- const bytes = readFileSync23(path3);
48009
- if (bytes.length <= MARKDOWN_READ_LIMIT) return { path: path3, markdown: bytes.toString("utf8"), truncated: false };
48010
- const head = bytes.subarray(0, MARKDOWN_READ_LIMIT);
48011
- const lastBreak = head.lastIndexOf(10);
48012
- return { path: path3, markdown: head.subarray(0, lastBreak > 0 ? lastBreak : head.length).toString("utf8"), truncated: true };
48013
- }
48014
- function resolveWatchableLocalFile(rawPath, roots) {
48015
- const path3 = resolveLocalFile(rawPath, roots);
48016
- if (MARKDOWN_FILE_EXT.test(rawPath.trim()) && !MARKDOWN_FILE_EXT.test(path3)) {
48017
- throw new Error("Local file is not a Markdown file");
48018
- }
48019
- return path3;
48020
- }
48021
- function defaultSpawn(command, args, options) {
48022
- return spawn6(command, [...args], options);
48023
- }
48024
- function openLocalFile(rawPath, opener, roots, options = {}) {
48025
- const path3 = resolveLocalFile(rawPath, roots);
48026
- const selected = options.forceSystem ? "system" : opener;
48027
- if (selected === "copy") return { action: "copy", path: path3 };
48028
- 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]];
48029
- (options.spawn ?? defaultSpawn)(spec[0], spec[1], { detached: true, stdio: "ignore", shell: false }).unref();
48030
- return { action: "opened", path: path3 };
48031
- }
48032
- var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT;
48033
- var init_local_file = __esm({
48034
- "packages/server/src/local-file.ts"() {
48035
- "use strict";
48036
- MARKDOWN_FILE_EXT = /\.(?:md|mdx|markdown)$/i;
48037
- MARKDOWN_READ_LIMIT = 1024 * 1024;
48038
- }
48039
- });
48040
-
48041
48275
  // packages/server/src/github-hovercard.ts
48042
48276
  function parseRef(ref) {
48043
48277
  const match2 = REF_PATTERN.exec(ref);
@@ -49597,7 +49831,7 @@ ${gapNote}` : input.message;
49597
49831
  );
49598
49832
  const turnLive = ctx.tailer.get(input.slug)?.turn === "in-flight" && !stalled;
49599
49833
  if (turnLive && (!binding || binding.currentTurnId === null)) {
49600
- throw new Error("This thread is running in your terminal right now \u2014 frizz is mirroring it live. Wait for that turn to finish, then send your follow-up here.");
49834
+ throw new Error("This thread is being driven outside Frizz. Wait for that turn to end, then resend.");
49601
49835
  }
49602
49836
  if (!binding || binding.state !== "active") {
49603
49837
  await bridge.resumeOwnedSession(input.slug, row.session_id);
@@ -50152,7 +50386,7 @@ ${gapNote}` : input.message;
50152
50386
  since: new Date(w.created_at).toISOString()
50153
50387
  });
50154
50388
  }
50155
- return { activity, questions: openQuestionViews(input.slug) };
50389
+ return { activity, questions: openQuestionViews(input.slug), links: ctx.storage.listThreadLinks(input.slug).map(threadLinkView) };
50156
50390
  }
50157
50391
  }),
50158
50392
  // THE WATCHER REGISTRY WAS DELETED ON 2026-08-14 AND CAME BACK ON 2026-08-26, under two narrow verbs
@@ -50166,6 +50400,34 @@ ${gapNote}` : input.message;
50166
50400
  // binary naming them and gets a 404, which is the honest answer: its arguments do not fit this
50167
50401
  // registry (there is no `for:` in them at all), so an alias would have to invent the one field that
50168
50402
  // must not be guessed at.
50403
+ upsertOwnLink: mutation({
50404
+ input: UpsertOwnLinkInput,
50405
+ output: UpsertOwnLinkResult,
50406
+ handler: async ({ input }) => {
50407
+ const row = ctx.storage.getSession(input.slug);
50408
+ if (!row) throw new Error(`thread ${input.slug} is not registered`);
50409
+ if (row.state === "archived" || row.archived === 1) throw new Error("Reopen this thread before registering a link");
50410
+ const destination = resolveThreadLink(input.target, ctx.project.dir, openRoots);
50411
+ const link = ctx.storage.upsertThreadLink({
50412
+ id: `lnk_${randomUUID16().replace(/-/g, "").slice(0, 12)}`,
50413
+ slug: input.slug,
50414
+ label: input.label,
50415
+ ...destination,
50416
+ createdAtMs: Date.now()
50417
+ });
50418
+ ctx.board.refresh();
50419
+ return { link: threadLinkView(link) };
50420
+ }
50421
+ }),
50422
+ dropOwnLink: mutation({
50423
+ input: DropOwnLinkInput,
50424
+ output: DropOwnLinkResult,
50425
+ handler: async ({ input }) => {
50426
+ const dropped = ctx.storage.dropThreadLink(input.slug, input.id);
50427
+ if (dropped) ctx.board.refresh();
50428
+ return { dropped };
50429
+ }
50430
+ }),
50169
50431
  // ---- REGISTERED PR WATCHERS (add / drop / list) ---------------------------------------------
50170
50432
  // The worker's own PR watchers, from `mcp__frizz__watch_pr`. Same caller and therefore the same rules
50171
50433
  // as the timers above: slug-only (the MCP server outlives the session ids underneath it), and no
@@ -51109,6 +51371,7 @@ var init_router5 = __esm({
51109
51371
  init_open_external();
51110
51372
  init_local_file();
51111
51373
  init_project();
51374
+ init_thread_links();
51112
51375
  init_github();
51113
51376
  init_github_hovercard();
51114
51377
  init_dispatch();
@@ -51331,9 +51594,9 @@ var init_compression = __esm({
51331
51594
 
51332
51595
  // packages/server/src/local-image.ts
51333
51596
  import { readFileSync as readFileSync25, realpathSync as realpathSync10, statSync as statSync18 } from "node:fs";
51334
- import { extname as extname2, isAbsolute as isAbsolute5 } from "node:path";
51597
+ import { extname as extname2, isAbsolute as isAbsolute6 } from "node:path";
51335
51598
  function resolveLocalImage(rawPath) {
51336
- if (!rawPath || !isAbsolute5(rawPath)) return { status: 400 };
51599
+ if (!rawPath || !isAbsolute6(rawPath)) return { status: 400 };
51337
51600
  const contentType = IMAGE_CONTENT_TYPE[extname2(rawPath).toLowerCase()];
51338
51601
  if (!contentType) return { status: 400 };
51339
51602
  let real;
@@ -53324,30 +53587,111 @@ var init_boot_progress = __esm({
53324
53587
  }
53325
53588
  });
53326
53589
 
53590
+ // 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";
53592
+ import { dirname as dirname19, join as join41 } from "node:path";
53593
+ function runtimeVersionDir(bin) {
53594
+ let dir = dirname19(bin);
53595
+ for (let step = 0; step < MAX_ASCENT; step++) {
53596
+ if (existsSync14(join41(dir, PROVISIONED_MARKER))) return dir;
53597
+ const parent = dirname19(dir);
53598
+ if (parent === dir) return void 0;
53599
+ dir = parent;
53600
+ }
53601
+ return void 0;
53602
+ }
53603
+ function pidAlive4(pid) {
53604
+ try {
53605
+ process.kill(pid, 0);
53606
+ return true;
53607
+ } catch (error) {
53608
+ return error.code === "EPERM";
53609
+ }
53610
+ }
53611
+ function leaseRuntime(bin, role) {
53612
+ const versionDir = runtimeVersionDir(bin);
53613
+ if (!versionDir) return () => {
53614
+ };
53615
+ const path3 = join41(versionDir, LEASES_DIR, `${process.pid}.json`);
53616
+ try {
53617
+ mkdirSync19(dirname19(path3), { recursive: true });
53618
+ const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
53619
+ writeFileSync16(path3, JSON.stringify(lease));
53620
+ } catch {
53621
+ return () => {
53622
+ };
53623
+ }
53624
+ return () => {
53625
+ try {
53626
+ unlinkSync6(path3);
53627
+ } catch {
53628
+ }
53629
+ };
53630
+ }
53631
+ function liveRuntimeLeases(versionDir) {
53632
+ const dir = join41(versionDir, LEASES_DIR);
53633
+ let names;
53634
+ try {
53635
+ names = readdirSync12(dir);
53636
+ } catch {
53637
+ return [];
53638
+ }
53639
+ const live = [];
53640
+ for (const name of names) {
53641
+ if (!name.endsWith(".json")) continue;
53642
+ const path3 = join41(dir, name);
53643
+ let lease;
53644
+ try {
53645
+ lease = JSON.parse(readFileSync29(path3, "utf8"));
53646
+ } catch {
53647
+ continue;
53648
+ }
53649
+ if (typeof lease.pid !== "number" || !pidAlive4(lease.pid)) {
53650
+ try {
53651
+ unlinkSync6(path3);
53652
+ } catch {
53653
+ }
53654
+ ;
53655
+ continue;
53656
+ }
53657
+ live.push({ pid: lease.pid, role: typeof lease.role === "string" ? lease.role : "unknown", bin: typeof lease.bin === "string" ? lease.bin : "", at: typeof lease.at === "string" ? lease.at : "" });
53658
+ }
53659
+ return live;
53660
+ }
53661
+ var PROVISIONED_MARKER, LEASES_DIR, MAX_ASCENT;
53662
+ var init_runtime_lease = __esm({
53663
+ "packages/server/src/runtime-lease.ts"() {
53664
+ "use strict";
53665
+ PROVISIONED_MARKER = "provisioned.json";
53666
+ LEASES_DIR = ".leases";
53667
+ MAX_ASCENT = 6;
53668
+ }
53669
+ });
53670
+
53327
53671
  // packages/server/src/runtimes.ts
53328
53672
  import { createHash as createHash15 } from "node:crypto";
53329
53673
  import {
53330
53674
  chmodSync,
53331
53675
  closeSync as closeSync16,
53332
- existsSync as existsSync14,
53333
- mkdirSync as mkdirSync19,
53676
+ existsSync as existsSync15,
53677
+ mkdirSync as mkdirSync20,
53334
53678
  openSync as openSync17,
53335
- readFileSync as readFileSync29,
53336
- readdirSync as readdirSync12,
53679
+ readFileSync as readFileSync30,
53680
+ readdirSync as readdirSync13,
53337
53681
  renameSync as renameSync11,
53338
53682
  rmSync as rmSync12,
53339
53683
  statSync as statSync22,
53340
- writeFileSync as writeFileSync16,
53684
+ writeFileSync as writeFileSync17,
53341
53685
  writeSync as writeSync2
53342
53686
  } from "node:fs";
53343
- import { dirname as dirname19, isAbsolute as isAbsolute6, join as join41, normalize, relative as relative3, sep as sep5 } from "node:path";
53687
+ import { dirname as dirname20, isAbsolute as isAbsolute7, join as join42, normalize, relative as relative3, sep as sep5 } from "node:path";
53344
53688
  import { Readable, Transform, Writable as Writable3 } from "node:stream";
53345
53689
  import { pipeline } from "node:stream/promises";
53346
53690
  import { createGunzip } from "node:zlib";
53347
53691
  function runtimesRoot(env = process.env) {
53348
53692
  const override = env[RUNTIMES_DIR_ENV];
53349
- if (override && isAbsolute6(override)) return override;
53350
- return join41(frizzRoots().cache, "runtimes");
53693
+ if (override && isAbsolute7(override)) return override;
53694
+ return join42(frizzRoots().cache, "runtimes");
53351
53695
  }
53352
53696
  function isMusl() {
53353
53697
  if (process.platform !== "linux") return false;
@@ -53364,16 +53708,16 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
53364
53708
  return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe}` };
53365
53709
  }
53366
53710
  function provisionedBinary(backend, root, label) {
53367
- const dir = join41(root, backend, label);
53711
+ const dir = join42(root, backend, label);
53368
53712
  let marker;
53369
53713
  try {
53370
- marker = JSON.parse(readFileSync29(join41(dir, MARKER), "utf8"));
53714
+ marker = JSON.parse(readFileSync30(join42(dir, MARKER), "utf8"));
53371
53715
  } catch {
53372
53716
  return void 0;
53373
53717
  }
53374
53718
  if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
53375
- const bin = join41(dir, marker.binary);
53376
- return existsSync14(bin) ? bin : void 0;
53719
+ const bin = join42(dir, marker.binary);
53720
+ return existsSync15(bin) ? bin : void 0;
53377
53721
  }
53378
53722
  function octal(buffer, offset, length) {
53379
53723
  const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
@@ -53386,27 +53730,27 @@ function safeEntryPath(dest, name) {
53386
53730
  const parts = name.split("/").filter((part) => part.length > 0);
53387
53731
  if (parts.length < 2) return void 0;
53388
53732
  const rel = normalize(parts.slice(1).join(sep5));
53389
- if (isAbsolute6(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
53390
- const full = join41(dest, rel);
53733
+ if (isAbsolute7(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
53734
+ const full = join42(dest, rel);
53391
53735
  const back = relative3(dest, full);
53392
- if (back.startsWith("..") || isAbsolute6(back)) return void 0;
53736
+ if (back.startsWith("..") || isAbsolute7(back)) return void 0;
53393
53737
  return full;
53394
53738
  }
53395
53739
  function locateBinary(dir, backend, binary) {
53396
53740
  if (backend === "claude") {
53397
- const direct = join41(dir, binary);
53398
- return existsSync14(direct) ? direct : void 0;
53741
+ const direct = join42(dir, binary);
53742
+ return existsSync15(direct) ? direct : void 0;
53399
53743
  }
53400
- const vendor = join41(dir, "vendor");
53744
+ const vendor = join42(dir, "vendor");
53401
53745
  let triples;
53402
53746
  try {
53403
- triples = readdirSync12(vendor);
53747
+ triples = readdirSync13(vendor);
53404
53748
  } catch {
53405
53749
  return void 0;
53406
53750
  }
53407
53751
  for (const triple of triples) {
53408
- const candidate = join41(vendor, triple, "bin", binary);
53409
- if (existsSync14(candidate)) return candidate;
53752
+ const candidate = join42(vendor, triple, "bin", binary);
53753
+ if (existsSync15(candidate)) return candidate;
53410
53754
  }
53411
53755
  return void 0;
53412
53756
  }
@@ -53421,12 +53765,12 @@ async function provisionRuntime(backend, options) {
53421
53765
  const registry = options.registry ?? REGISTRY;
53422
53766
  const progress = options.onProgress ?? (() => {
53423
53767
  });
53424
- const backendDir = join41(root, backend);
53425
- const finalDir = join41(backendDir, coordinates.label);
53426
- const partial = join41(backendDir, `.partial-${coordinates.label}-${process.pid}`);
53427
- mkdirSync19(backendDir, { recursive: true });
53768
+ const backendDir = join42(root, backend);
53769
+ const finalDir = join42(backendDir, coordinates.label);
53770
+ const partial = join42(backendDir, `.partial-${coordinates.label}-${process.pid}`);
53771
+ mkdirSync20(backendDir, { recursive: true });
53428
53772
  rmSync12(partial, { recursive: true, force: true });
53429
- mkdirSync19(partial);
53773
+ mkdirSync20(partial);
53430
53774
  try {
53431
53775
  const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
53432
53776
  progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
@@ -53470,7 +53814,7 @@ async function provisionRuntime(backend, options) {
53470
53814
  binary: relative3(partial, bin),
53471
53815
  at: (/* @__PURE__ */ new Date()).toISOString()
53472
53816
  };
53473
- writeFileSync16(join41(partial, MARKER), `${JSON.stringify(marker, null, 2)}
53817
+ writeFileSync17(join42(partial, MARKER), `${JSON.stringify(marker, null, 2)}
53474
53818
  `, "utf8");
53475
53819
  try {
53476
53820
  renameSync11(partial, finalDir);
@@ -53481,35 +53825,41 @@ async function provisionRuntime(backend, options) {
53481
53825
  return { bin: theirs, label: coordinates.label, fetched: false };
53482
53826
  }
53483
53827
  progress("ready");
53484
- return { bin: join41(finalDir, marker.binary), label: coordinates.label, fetched: true };
53828
+ return { bin: join42(finalDir, marker.binary), label: coordinates.label, fetched: true };
53485
53829
  } catch (err) {
53486
53830
  rmSync12(partial, { recursive: true, force: true });
53487
53831
  throw err;
53488
53832
  }
53489
53833
  }
53490
53834
  function sweepRuntimes(backend, root, keepLabel, now = Date.now()) {
53491
- const backendDir = join41(root, backend);
53835
+ const backendDir = join42(root, backend);
53492
53836
  let entries2;
53493
53837
  try {
53494
- entries2 = readdirSync12(backendDir);
53838
+ entries2 = readdirSync13(backendDir);
53495
53839
  } catch {
53496
- return [];
53840
+ return { removed: [], kept: [] };
53497
53841
  }
53498
- const removed = [];
53842
+ const sweep = { removed: [], kept: [] };
53499
53843
  for (const entry of entries2) {
53500
53844
  if (entry === keepLabel) continue;
53501
- const full = join41(backendDir, entry);
53845
+ const full = join42(backendDir, entry);
53502
53846
  if (entry.startsWith(".partial-")) {
53503
53847
  try {
53504
53848
  if (now - statSync22(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
53505
53849
  } catch {
53506
53850
  continue;
53507
53851
  }
53852
+ } else {
53853
+ const leases = liveRuntimeLeases(full);
53854
+ if (leases.length > 0) {
53855
+ sweep.kept.push({ dir: full, leases: leases.map(({ pid, role }) => ({ pid, role })) });
53856
+ continue;
53857
+ }
53508
53858
  }
53509
53859
  rmSync12(full, { recursive: true, force: true });
53510
- removed.push(full);
53860
+ sweep.removed.push(full);
53511
53861
  }
53512
- return removed;
53862
+ return sweep;
53513
53863
  }
53514
53864
  async function resolveOne(backend, explicit, options) {
53515
53865
  const env = options.env ?? process.env;
@@ -53535,7 +53885,12 @@ async function resolveOne(backend, explicit, options) {
53535
53885
  onProgress: (message) => options.onProgress?.(backend, message)
53536
53886
  });
53537
53887
  if (provisioned.fetched) log3("info", `runtimes: provisioned ${backend} ${provisioned.label} at ${provisioned.bin}`);
53538
- for (const retired of sweepRuntimes(backend, root, coordinates.label)) log3("info", `runtimes: retired ${retired}`);
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
+ }
53539
53894
  return { bin: provisioned.bin, source: "provisioned", version: provisioned.label };
53540
53895
  } catch (err) {
53541
53896
  const note = err instanceof Error ? err.message : String(err);
@@ -53566,8 +53921,9 @@ var init_runtimes = __esm({
53566
53921
  "use strict";
53567
53922
  init_codex_app_server();
53568
53923
  init_frizz_paths();
53569
- CLAUDE_AGENT_SDK_VERSION = "0.3.267";
53570
- CLAUDE_CODE_VERSION = "2.1.267";
53924
+ init_runtime_lease();
53925
+ CLAUDE_AGENT_SDK_VERSION = "0.3.268";
53926
+ CLAUDE_CODE_VERSION = "2.1.268";
53571
53927
  CODEX_VERSION = CODEX_APP_SERVER_SUPPORTED_VERSION;
53572
53928
  RUNTIMES_MODE_ENV = "FRIZZ_RUNTIMES";
53573
53929
  RUNTIMES_DIR_ENV = "FRIZZ_RUNTIMES_DIR";
@@ -53656,7 +54012,7 @@ var init_runtimes = __esm({
53656
54012
  return;
53657
54013
  case "5": {
53658
54014
  const dir = safeEntryPath(this.dest, name);
53659
- if (dir) mkdirSync19(dir, { recursive: true });
54015
+ if (dir) mkdirSync20(dir, { recursive: true });
53660
54016
  return;
53661
54017
  }
53662
54018
  case "0":
@@ -53664,7 +54020,7 @@ var init_runtimes = __esm({
53664
54020
  case "7": {
53665
54021
  const file = safeEntryPath(this.dest, name);
53666
54022
  if (!file) return;
53667
- mkdirSync19(dirname19(file), { recursive: true });
54023
+ mkdirSync20(dirname20(file), { recursive: true });
53668
54024
  this.fd = openSync17(file, "w", octal(header, 100, 8) & 511 || 420);
53669
54025
  this.written.push(file);
53670
54026
  return;
@@ -54236,18 +54592,18 @@ var init_legacy_project_db = __esm({
54236
54592
  });
54237
54593
 
54238
54594
  // packages/server/src/frizz-db.ts
54239
- import { existsSync as existsSync15, mkdirSync as mkdirSync20, readdirSync as readdirSync13, renameSync as renameSync12, statSync as statSync23 } from "node:fs";
54240
- import { basename as basename13, dirname as dirname20, join as join42 } from "node:path";
54595
+ import { existsSync as existsSync16, mkdirSync as mkdirSync21, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync23 } from "node:fs";
54596
+ import { basename as basename13, dirname as dirname21, join as join43 } from "node:path";
54241
54597
  function frizzDatabaseLocation(options) {
54242
54598
  if (options.stateDir !== void 0) {
54243
- const projectsDir = dirname20(options.stateDir);
54599
+ const projectsDir = dirname21(options.stateDir);
54244
54600
  if (basename13(projectsDir) === "projects") {
54245
- return { path: options.path ?? join42(dirname20(projectsDir), "ui.db"), projectsDir };
54601
+ return { path: options.path ?? join43(dirname21(projectsDir), "ui.db"), projectsDir };
54246
54602
  }
54247
- return { path: options.path ?? join42(options.stateDir, "ui.db"), projectsDir: void 0 };
54603
+ return { path: options.path ?? join43(options.stateDir, "ui.db"), projectsDir: void 0 };
54248
54604
  }
54249
54605
  const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
54250
- return { path: options.path ?? join42(data, "ui.db"), projectsDir: join42(data, "projects") };
54606
+ return { path: options.path ?? join43(data, "ui.db"), projectsDir: join43(data, "projects") };
54251
54607
  }
54252
54608
  function ensureFrizzSchema(db) {
54253
54609
  db.exec(`
@@ -54266,7 +54622,7 @@ function ensureFrizzSchema(db) {
54266
54622
  }
54267
54623
  function openFrizzDatabase(options = {}) {
54268
54624
  const { path: path3, projectsDir } = frizzDatabaseLocation(options);
54269
- mkdirSync20(dirname20(path3), { recursive: true });
54625
+ mkdirSync21(dirname21(path3), { recursive: true });
54270
54626
  const db = new sqlite_default(path3);
54271
54627
  db.pragma("busy_timeout = 5000");
54272
54628
  db.pragma("journal_mode = WAL");
@@ -54278,10 +54634,10 @@ function openFrizzDatabase(options = {}) {
54278
54634
  );
54279
54635
  for (const projectId of retiredLegacyProjectIds(projectsDir)) {
54280
54636
  if (alreadyImported.has(projectId)) continue;
54281
- const dir = join42(projectsDir, projectId);
54637
+ const dir = join43(projectsDir, projectId);
54282
54638
  try {
54283
- renameSync12(join42(dir, "ui.db.imported"), join42(dir, "ui.db"));
54284
- db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join42(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
54639
+ renameSync12(join43(dir, "ui.db.imported"), join43(dir, "ui.db"));
54640
+ db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join43(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
54285
54641
  alreadyImported.add(projectId);
54286
54642
  log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
54287
54643
  } catch (error) {
@@ -54291,7 +54647,7 @@ function openFrizzDatabase(options = {}) {
54291
54647
  }
54292
54648
  for (const projectId of legacyProjectIds(projectsDir)) {
54293
54649
  if (alreadyImported.has(projectId)) continue;
54294
- const legacyPath = join42(projectsDir, projectId, "ui.db");
54650
+ const legacyPath = join43(projectsDir, projectId, "ui.db");
54295
54651
  try {
54296
54652
  const rows = importLegacyProject(db, projectId, legacyPath);
54297
54653
  imported.push({ projectId, rows });
@@ -54321,10 +54677,10 @@ function openFrizzDatabase(options = {}) {
54321
54677
  };
54322
54678
  }
54323
54679
  function projectIdsWithFile(projectsDir, file) {
54324
- if (!existsSync15(projectsDir)) return [];
54325
- return readdirSync13(projectsDir).filter((name) => {
54680
+ if (!existsSync16(projectsDir)) return [];
54681
+ return readdirSync14(projectsDir).filter((name) => {
54326
54682
  try {
54327
- return statSync23(join42(projectsDir, name, file)).isFile();
54683
+ return statSync23(join43(projectsDir, name, file)).isFile();
54328
54684
  } catch {
54329
54685
  return false;
54330
54686
  }
@@ -54334,7 +54690,7 @@ function legacyProjectIds(projectsDir) {
54334
54690
  return projectIdsWithFile(projectsDir, "ui.db");
54335
54691
  }
54336
54692
  function retiredLegacyProjectIds(projectsDir) {
54337
- return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync15(join42(projectsDir, id, "ui.db")));
54693
+ return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync16(join43(projectsDir, id, "ui.db")));
54338
54694
  }
54339
54695
  function tableColumns(db, schema, table) {
54340
54696
  return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
@@ -54490,7 +54846,7 @@ var init_tenant_prime = __esm({
54490
54846
  // packages/server/src/project-teardown.ts
54491
54847
  import { rmSync as rmSync13 } from "node:fs";
54492
54848
  import { homedir as homedir19 } from "node:os";
54493
- import { dirname as dirname21, resolve as resolve10 } from "node:path";
54849
+ import { dirname as dirname22, resolve as resolve10 } from "node:path";
54494
54850
  async function stopProjectWorkers(ctx) {
54495
54851
  let stopped = 0;
54496
54852
  for (const row of ctx.storage.allSessions()) {
@@ -54510,7 +54866,7 @@ function deleteProjectState(projectId, home = homedir19()) {
54510
54866
  if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
54511
54867
  throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
54512
54868
  }
54513
- if (dirname21(resolve10(target)) !== resolve10(dirname21(projectStateDir("x", home)))) {
54869
+ if (dirname22(resolve10(target)) !== resolve10(dirname22(projectStateDir("x", home)))) {
54514
54870
  throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
54515
54871
  }
54516
54872
  rmSync13(target, { recursive: true, force: true });
@@ -54539,11 +54895,11 @@ __export(index_exports, {
54539
54895
  unknownProjectPage: () => unknownProjectPage
54540
54896
  });
54541
54897
  import { createServer } from "node:http";
54542
- import { readFileSync as readFileSync30, existsSync as existsSync16, statSync as statSync24 } from "node:fs";
54543
- import { dirname as dirname22, join as join43, resolve as resolve11, extname as extname4, normalize as normalize2, sep as sep6 } from "node:path";
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";
54544
54900
  function readServerAddressHolder(path3) {
54545
54901
  try {
54546
- const value = JSON.parse(readFileSync30(path3, "utf8"));
54902
+ const value = JSON.parse(readFileSync31(path3, "utf8"));
54547
54903
  if (typeof value?.pid !== "number") return void 0;
54548
54904
  return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
54549
54905
  } catch {
@@ -54668,12 +55024,12 @@ function staticIsFresh(req, etag, mtimeMs) {
54668
55024
  }
54669
55025
  function serveStatic(distDir, req, res) {
54670
55026
  const rel = normalize2((req.url ?? "/").split("?")[0]).replace(/^(\.\.[/\\])+/, "");
54671
- let file = join43(distDir, rel === "/" ? "index.html" : rel);
54672
- if (!file.startsWith(distDir)) file = join43(distDir, "index.html");
54673
- if (!existsSync16(file)) file = join43(distDir, "index.html");
55027
+ 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");
54674
55030
  try {
54675
55031
  const stats = statSync24(file);
54676
- const cacheControl = file.startsWith(join43(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
55032
+ const cacheControl = file.startsWith(join44(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
54677
55033
  const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
54678
55034
  const headers = {
54679
55035
  "content-type": MIME[extname4(file)] ?? "application/octet-stream",
@@ -54689,7 +55045,7 @@ function serveStatic(distDir, req, res) {
54689
55045
  res.end();
54690
55046
  return;
54691
55047
  }
54692
- let body = readFileSync30(file);
55048
+ let body = readFileSync31(file);
54693
55049
  const accept = req.headers["accept-encoding"];
54694
55050
  const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
54695
55051
  if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
@@ -54951,7 +55307,7 @@ async function startServer(opts = {}) {
54951
55307
  if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
54952
55308
  if (statusPath && statusIdentity) {
54953
55309
  try {
54954
- runtime.removeStatus(serverAddressPathForStateDir(dirname22(statusPath)), statusIdentity);
55310
+ runtime.removeStatus(serverAddressPathForStateDir(dirname23(statusPath)), statusIdentity);
54955
55311
  } catch {
54956
55312
  }
54957
55313
  }
@@ -55089,7 +55445,7 @@ async function startServer(opts = {}) {
55089
55445
  }
55090
55446
  statusPath = serverLockPathFor(ctx.project);
55091
55447
  const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
55092
- const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join43(webRoot, "dist");
55448
+ const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join44(webRoot, "dist");
55093
55449
  startupPhase = "Vite";
55094
55450
  if (opts.dev) {
55095
55451
  try {
@@ -55165,7 +55521,7 @@ async function startServer(opts = {}) {
55165
55521
  if (vite) {
55166
55522
  vite.middlewares(req, res, () => {
55167
55523
  try {
55168
- const html = readFileSync30(join43(webRoot, "index.html"), "utf8");
55524
+ const html = readFileSync31(join44(webRoot, "index.html"), "utf8");
55169
55525
  void vite.transformIndexHtml(url, html).then((out) => {
55170
55526
  res.writeHead(200, { "content-type": "text/html" });
55171
55527
  res.end(out);
@@ -55177,7 +55533,7 @@ async function startServer(opts = {}) {
55177
55533
  });
55178
55534
  return;
55179
55535
  }
55180
- if (existsSync16(distDir)) {
55536
+ if (existsSync17(distDir)) {
55181
55537
  serveStatic(distDir, req, res);
55182
55538
  return;
55183
55539
  }
@@ -55375,7 +55731,7 @@ var init_index = __esm({
55375
55731
  }
55376
55732
  };
55377
55733
  isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
55378
- serverLockPathFor = (project) => join43(project.stateDir, "server.lock");
55734
+ serverLockPathFor = (project) => join44(project.stateDir, "server.lock");
55379
55735
  MIME = {
55380
55736
  ".html": "text/html",
55381
55737
  ".js": "text/javascript",
@@ -55398,8 +55754,8 @@ init_project();
55398
55754
  init_project_launch();
55399
55755
  init_shutdown();
55400
55756
  init_logging();
55401
- import { existsSync as existsSync17 } from "node:fs";
55402
- import { join as join44 } from "node:path";
55757
+ import { existsSync as existsSync18 } from "node:fs";
55758
+ import { join as join45 } from "node:path";
55403
55759
  process.on("uncaughtException", (error) => {
55404
55760
  log.error("dev-child", `uncaught exception: ${error instanceof Error ? error.stack ?? error.message : error}`);
55405
55761
  process.exit(1);
@@ -55437,9 +55793,9 @@ try {
55437
55793
  ["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
55438
55794
  ["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
55439
55795
  ];
55440
- if (!existsSync17(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
55796
+ if (!existsSync18(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
55441
55797
  for (const [name, directory, requiredFile] of required) {
55442
- if (!directory || !existsSync17(join44(directory, requiredFile)))
55798
+ if (!directory || !existsSync18(join45(directory, requiredFile)))
55443
55799
  throw new Error(`stable artifact launch is missing verified ${name}`);
55444
55800
  }
55445
55801
  }