frizz 0.12.0 → 0.12.2

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 +9675 -9610
  2. package/dist/codex-app-server-daemon.js +18 -4
  3. package/dist/dev-child.js +1455 -1158
  4. package/dist/frizz.js +6 -0
  5. package/package.json +1 -1
  6. package/runtime/cc-worker/DECISIONS.md +12 -0
  7. package/runtime/cc-worker/bin/frizz-mcp.mjs +8 -5
  8. package/runtime/cc-worker/hooks/agent-dispatch.mjs +27 -5
  9. package/web-dist/assets/{TerminalPane-C2BnRGVg.js → TerminalPane-D5Tx2hu9.js} +1 -1
  10. package/web-dist/assets/{abnfDiagram-VRR7QNED-CnMUakri.js → abnfDiagram-VRR7QNED-BO0tgRMu.js} +1 -1
  11. package/web-dist/assets/architecture-TIHT7OUA-lCPPJORL.js +1 -0
  12. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-Dw49H3Py.js → architectureDiagram-ZJ3FMSHR-TCJFLj-i.js} +1 -1
  13. package/web-dist/assets/{blockDiagram-677ZJIJ3-BWgasm3g.js → blockDiagram-677ZJIJ3-BRd9xJTz.js} +1 -1
  14. package/web-dist/assets/{c4Diagram-LMCZKHZV-CmiVMi-q.js → c4Diagram-LMCZKHZV-BxlNYwY8.js} +1 -1
  15. package/web-dist/assets/channel-tGE7Gbbs.js +1 -0
  16. package/web-dist/assets/{chunk-32BRIVSS-DsFcmMav.js → chunk-32BRIVSS-o0wtC3P5.js} +1 -1
  17. package/web-dist/assets/{chunk-52WLFC77-C8TmIbAt.js → chunk-52WLFC77-C_bt24w7.js} +1 -1
  18. package/web-dist/assets/{chunk-C7G6YPKG-CWaIqGh7.js → chunk-C7G6YPKG-DlsBv-nI.js} +1 -1
  19. package/web-dist/assets/{chunk-EX3LRPZG-BGtDaEG5.js → chunk-EX3LRPZG-Y_Debiw-.js} +1 -1
  20. package/web-dist/assets/{chunk-FWX5IMBZ-DIYLz03w.js → chunk-FWX5IMBZ-D_9nCJ43.js} +2 -2
  21. package/web-dist/assets/{chunk-HOUHSVGY-DEXmjuFh.js → chunk-HOUHSVGY-Bzhu2UoJ.js} +1 -1
  22. package/web-dist/assets/{chunk-ICXQ74PX-d7qYOhhi.js → chunk-ICXQ74PX-CwJVbXqL.js} +1 -1
  23. package/web-dist/assets/{chunk-MOJQB5TN-DclXMP_a.js → chunk-MOJQB5TN-DPm_X7tz.js} +1 -1
  24. package/web-dist/assets/{chunk-OGEWGWER-Dn3qS913.js → chunk-OGEWGWER-1m3zk2ed.js} +1 -1
  25. package/web-dist/assets/{chunk-PUDLZKDR-BVShtfU4.js → chunk-PUDLZKDR-D2Bnu5zc.js} +1 -1
  26. package/web-dist/assets/{chunk-Q4XR5HBZ-CGTdESx1.js → chunk-Q4XR5HBZ-DJZtjVxI.js} +1 -1
  27. package/web-dist/assets/{chunk-V7JOEXUC-DyJOjr9v.js → chunk-V7JOEXUC-BQVKbxnn.js} +1 -1
  28. package/web-dist/assets/{chunk-VAUOI2AC-D4IP434V.js → chunk-VAUOI2AC-TwOgVbMm.js} +1 -1
  29. package/web-dist/assets/{chunk-VR4S4FIN-BmthV8JY.js → chunk-VR4S4FIN-DlG1lzSj.js} +1 -1
  30. package/web-dist/assets/{chunk-WYO6CB5R-B7WZtej7.js → chunk-WYO6CB5R-Ckk8qoao.js} +1 -1
  31. package/web-dist/assets/{chunk-ZGVPDNZ5-C5udBDd8.js → chunk-ZGVPDNZ5-CZxmPZzT.js} +1 -1
  32. package/web-dist/assets/classDiagram-OUVF2IWQ-CQo4x1hj.js +1 -0
  33. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CQo4x1hj.js +1 -0
  34. package/web-dist/assets/{cynefin-VYW2F7L2-BGCAuICx.js → cynefin-VYW2F7L2-CioP0cgr.js} +1 -1
  35. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-DrcSp3T9.js → cynefinDiagram-TSTJHNR4-BJZpJw_3.js} +1 -1
  36. package/web-dist/assets/{dagre-VKFMJZFB-DpwUEsqR.js → dagre-VKFMJZFB-BGv33WEE.js} +1 -1
  37. package/web-dist/assets/{diagram-FQU43EPY-BpFw8lh3.js → diagram-FQU43EPY-CvhxVWTl.js} +1 -1
  38. package/web-dist/assets/{diagram-G47NLZAW-C19UAGgY.js → diagram-G47NLZAW-DnrPOeE0.js} +1 -1
  39. package/web-dist/assets/{diagram-NH7WQ7WH-BqCOCiLh.js → diagram-NH7WQ7WH-CazuXXkX.js} +1 -1
  40. package/web-dist/assets/{diagram-OA4YK3LP-DeX0lziw.js → diagram-OA4YK3LP-DfbFhbQs.js} +1 -1
  41. package/web-dist/assets/{diagram-WEI45ONY-C5CaoiNQ.js → diagram-WEI45ONY-BESpc3Oj.js} +1 -1
  42. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-CmVKBMPd.js → ebnfDiagram-CCIWWBDH-C6h37W51.js} +1 -1
  43. package/web-dist/assets/{erDiagram-Q63AITRT-DKbK71y9.js → erDiagram-Q63AITRT-vCxGREHo.js} +1 -1
  44. package/web-dist/assets/eventmodeling-45OFAUF4-C517fJsQ.js +1 -0
  45. package/web-dist/assets/flowDiagram-23GEKE2U-IrjbjjxF.js +1 -0
  46. package/web-dist/assets/{ganttDiagram-NO4QXBWP-CV9er-Ag.js → ganttDiagram-NO4QXBWP-B5l9XyZe.js} +1 -1
  47. package/web-dist/assets/{gitGraph-TEB2WS4Q-DrkGEklk.js → gitGraph-TEB2WS4Q-CQpssLnk.js} +1 -1
  48. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DnfkKTHs.js → gitGraphDiagram-IHSO6WYX-j95eUOjO.js} +1 -1
  49. package/web-dist/assets/{index-BZuSbTzC.js → index-Dzq6GPUo.js} +95 -95
  50. package/web-dist/assets/{info-DKCQHKI2-DDjeakJ3.js → info-DKCQHKI2-CiW0hhEI.js} +1 -1
  51. package/web-dist/assets/{infoDiagram-FWYZ7A6U-ClPdEwgm.js → infoDiagram-FWYZ7A6U-BJwJPEq2.js} +1 -1
  52. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-DQSe577k.js → ishikawaDiagram-FXEZZL3T-Ck4D0hT_.js} +1 -1
  53. package/web-dist/assets/{journeyDiagram-5HDEW3XC-CqGDwDnH.js → journeyDiagram-5HDEW3XC-Bz8PK7lM.js} +1 -1
  54. package/web-dist/assets/{kanban-definition-HUTT4EX6-Cm2ql3XT.js → kanban-definition-HUTT4EX6-Kqt3cah4.js} +1 -1
  55. package/web-dist/assets/{line-DYZIw46W.js → line-Dl7tIumR.js} +1 -1
  56. package/web-dist/assets/{mermaid-parser.core-BzAUCbYh.js → mermaid-parser.core-C_aHybiE.js} +3 -3
  57. package/web-dist/assets/{mermaid.core-ChBUFCit.js → mermaid.core-Dv6lgZxU.js} +3 -3
  58. package/web-dist/assets/{mindmap-definition-LN4V7U3C-D12k854u.js → mindmap-definition-LN4V7U3C-EkN_2bUd.js} +1 -1
  59. package/web-dist/assets/{packet-7NZHBO7P-DqfFhLqp.js → packet-7NZHBO7P-LL-j9b42.js} +1 -1
  60. package/web-dist/assets/{pegDiagram-2B236MQR-yG5jewLN.js → pegDiagram-2B236MQR-CrNUBIKh.js} +1 -1
  61. package/web-dist/assets/{pie-RZYD4A2V-qk1HY3l9.js → pie-RZYD4A2V-8wZ8AyeT.js} +1 -1
  62. package/web-dist/assets/{pieDiagram-ENE6RG2P-B-n6mYM1.js → pieDiagram-ENE6RG2P-BSUUt2Vt.js} +1 -1
  63. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-B35eq6Qw.js → quadrantDiagram-ABIIQ3AL-BmRRE-b9.js} +1 -1
  64. package/web-dist/assets/{radar-I7S5WNFK-BLlNJ62v.js → radar-I7S5WNFK-BZ8i3wPh.js} +1 -1
  65. package/web-dist/assets/{railroad-3IZDKUUU-Bo667-nZ.js → railroad-3IZDKUUU-Bvq-p3jW.js} +1 -1
  66. package/web-dist/assets/railroad-abnf-AHOZXSZD-DIZeZa-B.js +1 -0
  67. package/web-dist/assets/railroad-ebnf-EBAXGLYW-DVn3xHNX.js +1 -0
  68. package/web-dist/assets/railroad-peg-LSFZ7HO6-BvLcJu_d.js +1 -0
  69. package/web-dist/assets/{railroadDiagram-RFXS5EU6-Cpu40FP1.js → railroadDiagram-RFXS5EU6-CH0DOBjJ.js} +1 -1
  70. package/web-dist/assets/{requirementDiagram-TGXJPOKE-BnUyecuV.js → requirementDiagram-TGXJPOKE-ytS-raWw.js} +1 -1
  71. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-CPKNkdAc.js → sankeyDiagram-HTMAVEWB-BU49ulmu.js} +1 -1
  72. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ--Xh3MzFl.js → sequenceDiagram-DBY2YBRQ-C1edCLps.js} +1 -1
  73. package/web-dist/assets/{stateDiagram-2N3HPSRC-Dt9i46rM.js → stateDiagram-2N3HPSRC-C049S4Yj.js} +1 -1
  74. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-D-Et-Cj1.js +1 -0
  75. package/web-dist/assets/{swimlanes-5IMT3BWC-qTKs7JbH.js → swimlanes-5IMT3BWC-KNWNnFDx.js} +1 -1
  76. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DQNRnHXT.js +8 -0
  77. package/web-dist/assets/{timeline-definition-FHXFAJF6-6k-_GOW3.js → timeline-definition-FHXFAJF6-D8Z-Q8JO.js} +1 -1
  78. package/web-dist/assets/{treeView-QDETBFTQ-BHU4QpQM.js → treeView-QDETBFTQ-CvyDkH5p.js} +1 -1
  79. package/web-dist/assets/{treemap-6X3UGDF4-DlO97L99.js → treemap-6X3UGDF4-COwCP95k.js} +1 -1
  80. package/web-dist/assets/{vennDiagram-L72KCM5P-CxKMltcT.js → vennDiagram-L72KCM5P-DHKE_6N3.js} +1 -1
  81. package/web-dist/assets/{wardley-OPB4EBWU-DaE5ZjFv.js → wardley-OPB4EBWU-CYWcYdsx.js} +1 -1
  82. package/web-dist/assets/{wardleyDiagram-EHGQE667-Bb9Gwjc0.js → wardleyDiagram-EHGQE667-BS3OkOU_.js} +1 -1
  83. package/web-dist/assets/{xychartDiagram-FW5EYKEG-BdaqOb5a.js → xychartDiagram-FW5EYKEG-Df4GdNEo.js} +1 -1
  84. package/web-dist/index.html +1 -1
  85. package/web-dist/assets/architecture-TIHT7OUA-9QsSz9gn.js +0 -1
  86. package/web-dist/assets/channel-CLXyIE2F.js +0 -1
  87. package/web-dist/assets/classDiagram-OUVF2IWQ-CIWfuY4b.js +0 -1
  88. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CIWfuY4b.js +0 -1
  89. package/web-dist/assets/eventmodeling-45OFAUF4-P5CIYaPC.js +0 -1
  90. package/web-dist/assets/flowDiagram-23GEKE2U-B1lYV9tG.js +0 -1
  91. package/web-dist/assets/railroad-abnf-AHOZXSZD-1QyLbHfP.js +0 -1
  92. package/web-dist/assets/railroad-ebnf-EBAXGLYW-Cs3RBVGW.js +0 -1
  93. package/web-dist/assets/railroad-peg-LSFZ7HO6-d_2u0a_u.js +0 -1
  94. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BKy5vv2y.js +0 -1
  95. package/web-dist/assets/swimlanesDiagram-G3AALYLV-Ce1uKw_w.js +0 -8
package/dist/dev-child.js CHANGED
@@ -13364,7 +13364,7 @@ function prWatchWakeMessage(input) {
13364
13364
  const lines = [];
13365
13365
  if (input.merged || input.closed) {
13366
13366
  lines.push(`\u23F0 ${input.target} was ${input.merged ? "MERGED" : "CLOSED"}.`, "");
13367
- lines.push("(This watcher is spent \u2014 there is nothing further to report on a finished PR.)");
13367
+ lines.push(PR_WATCH_SPENT_TRAILER);
13368
13368
  return lines.join("\n");
13369
13369
  }
13370
13370
  if (input.checks) {
@@ -13391,7 +13391,7 @@ function prWatchWakeMessage(input) {
13391
13391
  if (lines.length) lines.push("");
13392
13392
  lines.push(input.review);
13393
13393
  }
13394
- lines.push("", "(Registered PR watcher \u2014 STILL ARMED. It reports again on the next CI change, review, comment, label, conflict or review request. Drop it with `mcp__frizz__watch_pr` when it stops mattering.)");
13394
+ lines.push("", PR_WATCH_ARMED_TRAILER);
13395
13395
  return lines.join("\n");
13396
13396
  }
13397
13397
  function shellDoneMessage(shell) {
@@ -13399,7 +13399,7 @@ function shellDoneMessage(shell) {
13399
13399
  const verb = shell.status === "failed" ? "FAILED" : shell.status === "killed" ? "was STOPPED" : "finished";
13400
13400
  return `\u23F0 Your background shell ${verb}: ${what}.
13401
13401
 
13402
- (Frizz sends this because it finished after you came to rest, where your runtime's own completion notification does not reach you. Read its output if you still need it.)`;
13402
+ ${SHELL_DONE_TRAILER}`;
13403
13403
  }
13404
13404
  function liveOpsLines(ops) {
13405
13405
  const lines = [];
@@ -13543,6 +13543,15 @@ function wakeTimeHeader(nowMs, lastAssistantAt) {
13543
13543
  function stripWakeTimeHeader(text2) {
13544
13544
  return text2.replace(WAKE_TIME_HEADER_TAIL, "");
13545
13545
  }
13546
+ function stripWakeTrailer(text2) {
13547
+ for (const trailer of WAKE_TRAILERS) {
13548
+ const at = text2.lastIndexOf(trailer);
13549
+ if (at < 0 || text2.slice(at + trailer.length).trim() !== "") continue;
13550
+ const head = text2.slice(0, at).trimEnd();
13551
+ return head === "" ? text2 : head;
13552
+ }
13553
+ return text2;
13554
+ }
13546
13555
  function humanGapNote(nowMs, lastAssistantAt) {
13547
13556
  const since = lastAssistantAt ? Date.parse(lastAssistantAt) : NaN;
13548
13557
  if (!Number.isFinite(since) || nowMs - since < HUMAN_GAP_FLOOR_MS) return void 0;
@@ -13662,7 +13671,7 @@ function parseGithubWakeSteer(text2) {
13662
13671
  function frizzRoute(path3) {
13663
13672
  return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
13664
13673
  }
13665
- 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, 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, 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, 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;
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, 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;
13666
13675
  var init_src = __esm({
13667
13676
  "packages/shared/src/index.ts"() {
13668
13677
  "use strict";
@@ -14038,6 +14047,9 @@ var init_src = __esm({
14038
14047
  TIMER_MAX_DELAY_SECONDS = 30 * 24 * 60 * 60;
14039
14048
  TIMER_MAX_ARMED = 64;
14040
14049
  TimerPromptText = external_exports.string().trim().min(1).max(TIMER_PROMPT_MAX);
14050
+ PR_WATCH_SPENT_TRAILER = "(This watcher is spent \u2014 there is nothing further to report on a finished PR.)";
14051
+ PR_WATCH_ARMED_TRAILER = "(Registered PR watcher \u2014 STILL ARMED. It reports again on the next CI change, review, comment, label, conflict or review request. Drop it with `mcp__frizz__watch_pr` when it stops mattering.)";
14052
+ SHELL_DONE_TRAILER = "(Frizz sends this because it finished after you came to rest, where your runtime's own completion notification does not reach you. Read its output if you still need it.)";
14041
14053
  SIGNOFF_NUDGE_MARKER = "**This message is from frizz, not from the human.**";
14042
14054
  SIGNOFF_NUDGE_MESSAGE = [
14043
14055
  `${SIGNOFF_NUDGE_MARKER} Nothing about your task has changed, and no new work is being asked of you.`,
@@ -15120,6 +15132,7 @@ ${DISPATCH_TASK_BANNER}
15120
15132
  `;
15121
15133
  GITHUB_DISPATCH_UI_BOUNDARY = "<!-- frizz:github-dispatch-ui-boundary:v1 -->";
15122
15134
  WAKE_TIME_HEADER_TAIL = /\n+⏱ \d{4}-\d{2}-\d{2} \d{2}:\d{2}(?: — you last spoke [^\n]*? ago)?\.[ \t]*$/;
15135
+ WAKE_TRAILERS = [PR_WATCH_ARMED_TRAILER, PR_WATCH_SPENT_TRAILER, SHELL_DONE_TRAILER];
15123
15136
  HUMAN_GAP_FLOOR_MS = 20 * 6e4;
15124
15137
  HUMAN_GAP_NOTE_TAIL = /\n+⏱ Frizz: the message above arrived [^\n]* after your last one\. It is now \d{4}-\d{2}-\d{2} \d{2}:\d{2}\.[ \t]*$/;
15125
15138
  WAKE_DELIVERY_TOKEN_TAIL = /\n*<!-- frizz-wake:[A-Za-z0-9_-]+ -->\s*$/;
@@ -15562,6 +15575,12 @@ ${DISPATCH_TASK_BANNER}
15562
15575
  // disagree. Additive + optional: absent from a legacy transcript or an older server, and the client
15563
15576
  // falls back to `parseGithubWakeSteer` on the text.
15564
15577
  wakeSteer: GithubWakeSteer.optional(),
15578
+ // Input delivered INTO a sub-agent by its coordinator or another agent. It is a user-side turn in
15579
+ // that CHILD's conversation, but it is not the human speaking. The drawer's live projection keeps
15580
+ // these messages even when the ordinary 300-message tail has moved past them — clicking a parent's
15581
+ // "Steered"/"Followed up" divider promises the corresponding instruction remains readable there.
15582
+ // Additive + optional: ordinary thread transcripts never set it.
15583
+ agentInstruction: external_exports.literal(true).optional(),
15565
15584
  // A SUB-AGENT (or peer session) wrote this user turn, not the human — the same defect class `wake`
15566
15585
  // above corrects. Claude Code's agent-to-agent channel (a background child calling
15567
15586
  // `SendMessage({to:"main"})`) delivers UPWARD into the parent's queue like any follow-up, so the
@@ -19406,6 +19425,7 @@ function createStorage(source, projectId) {
19406
19425
  `);
19407
19426
  const delThreadQuestions = scope.prepare("DELETE FROM thread_question WHERE project_id = @project_id AND thread_slug = ?");
19408
19427
  const delThreadDone = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
19428
+ const delSubAgentSteers = scope.prepare("DELETE FROM subagent_steer WHERE project_id = @project_id AND thread_slug = ?");
19409
19429
  const markThreadDoneStmt = scope.prepare(`
19410
19430
  INSERT INTO thread_done (project_id, thread_slug, body, done_at) VALUES (@project_id, ?, ?, ?)
19411
19431
  ON CONFLICT(project_id, thread_slug) DO UPDATE SET body = excluded.body, done_at = excluded.done_at
@@ -19415,6 +19435,17 @@ function createStorage(source, projectId) {
19415
19435
  "SELECT thread_slug, body, done_at FROM thread_done WHERE project_id = @project_id"
19416
19436
  );
19417
19437
  const clearThreadDoneStmt = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
19438
+ const recordSubAgentSteerStmt = scope.prepare(`
19439
+ INSERT OR IGNORE INTO subagent_steer (
19440
+ project_id, thread_slug, subagent_id, delivery_id, message, sent_at
19441
+ ) VALUES (@project_id, @slug, @subAgentId, @deliveryId, @message, @sentAtMs)
19442
+ `);
19443
+ const listSubAgentSteersStmt = scope.prepare(`
19444
+ SELECT thread_slug, subagent_id, delivery_id, message, sent_at
19445
+ FROM subagent_steer
19446
+ WHERE project_id = @project_id AND thread_slug = ? AND subagent_id = ?
19447
+ ORDER BY sent_at, rowid
19448
+ `);
19418
19449
  const clearExpiredSnoozesStmt = scope.prepare(`
19419
19450
  UPDATE session SET snoozed_until = NULL
19420
19451
  WHERE project_id = @project_id AND snoozed_until IS NOT NULL AND snoozed_until <= ? AND snooze_prompt IS NULL
@@ -19469,6 +19500,7 @@ function createStorage(source, projectId) {
19469
19500
  delThreadWatches.run(existing.slug);
19470
19501
  delThreadQuestions.run(existing.slug);
19471
19502
  delThreadDone.run(existing.slug);
19503
+ delSubAgentSteers.run(existing.slug);
19472
19504
  delSession.run(existing.slug);
19473
19505
  return existing;
19474
19506
  };
@@ -19962,6 +19994,10 @@ function createStorage(source, projectId) {
19962
19994
  return bySlug;
19963
19995
  },
19964
19996
  clearThreadDone: (slug) => clearThreadDoneStmt.run(slug).changes > 0,
19997
+ recordSubAgentSteer: (row) => {
19998
+ recordSubAgentSteerStmt.run(row);
19999
+ },
20000
+ listSubAgentSteers: (slug, subAgentId) => listSubAgentSteersStmt.all(slug, subAgentId),
19965
20001
  dropThreadWatch: (slug, id, settledAtMs) => dropThreadWatchStmt.run(settledAtMs, id, slug).changes === 1,
19966
20002
  settleThreadWatch: (id, settledAtMs, state = "settled") => settleThreadWatchStmt.run(state, settledAtMs, id).changes === 1,
19967
20003
  armThreadTimer: (timer) => void armTimerStmt.run(timer),
@@ -20460,6 +20496,20 @@ var init_storage = __esm({
20460
20496
  done_at INTEGER NOT NULL,
20461
20497
  PRIMARY KEY (project_id, thread_slug)
20462
20498
  );
20499
+ -- Operator-authored steers sent from a sub-agent drawer. The Claude broker intentionally does not
20500
+ -- persist these as user records in the child's provider transcript, but Frizz possesses the text
20501
+ -- at delivery time and the drawer must not forget what the operator sent.
20502
+ CREATE TABLE IF NOT EXISTS subagent_steer (
20503
+ project_id TEXT NOT NULL,
20504
+ thread_slug TEXT NOT NULL,
20505
+ subagent_id TEXT NOT NULL,
20506
+ delivery_id TEXT NOT NULL,
20507
+ message TEXT NOT NULL,
20508
+ sent_at INTEGER NOT NULL,
20509
+ PRIMARY KEY (project_id, thread_slug, subagent_id, delivery_id)
20510
+ );
20511
+ CREATE INDEX IF NOT EXISTS subagent_steer_timeline
20512
+ ON subagent_steer(project_id, thread_slug, subagent_id, sent_at);
20463
20513
  `;
20464
20514
  STORAGE_TABLES = [
20465
20515
  "session",
@@ -20472,7 +20522,8 @@ var init_storage = __esm({
20472
20522
  "pr_watch",
20473
20523
  "thread_watch",
20474
20524
  "thread_question",
20475
- "thread_done"
20525
+ "thread_done",
20526
+ "subagent_steer"
20476
20527
  ];
20477
20528
  }
20478
20529
  });
@@ -23733,6 +23784,7 @@ function applyEvent(state, ev) {
23733
23784
  state.sawRecords = true;
23734
23785
  break;
23735
23786
  case "agent-report":
23787
+ case "agent-instruction":
23736
23788
  state.sawRecords = true;
23737
23789
  break;
23738
23790
  case "title":
@@ -25521,32 +25573,47 @@ function parseCodexLine(line) {
25521
25573
  if (pt === "agent_message") {
25522
25574
  const author = typeof p.author === "string" ? p.author : "";
25523
25575
  const recipient = typeof p.recipient === "string" ? p.recipient : "";
25524
- if (!author || !recipient || !author.startsWith(`${recipient}/`)) return [];
25525
- const report = parseCodexAgentReport(p.content);
25526
- return report ? [{ kind: "agent-report", at, author, text: report.body, final: report.final }] : [];
25576
+ if (!author || !recipient) return [];
25577
+ const message = parseCodexAgentMessage(p.content);
25578
+ if (!message) return [];
25579
+ if (author.startsWith(`${recipient}/`)) {
25580
+ if (message.type !== "FINAL_ANSWER" && message.type !== "MESSAGE") return [];
25581
+ return [{ kind: "agent-report", at, author, text: message.body, final: message.type === "FINAL_ANSWER" }];
25582
+ }
25583
+ if (author !== recipient && (message.type === "NEW_TASK" || message.type === "MESSAGE")) {
25584
+ return [{
25585
+ kind: "agent-instruction",
25586
+ at,
25587
+ author,
25588
+ ...message.body ? { text: message.body } : {},
25589
+ encrypted: message.encrypted
25590
+ }];
25591
+ }
25592
+ return [];
25527
25593
  }
25528
25594
  return [];
25529
25595
  }
25530
25596
  return [];
25531
25597
  }
25532
- function parseCodexAgentReport(content) {
25598
+ function parseCodexAgentMessage(content) {
25533
25599
  if (!Array.isArray(content)) return void 0;
25534
25600
  let text2;
25601
+ let encrypted = false;
25535
25602
  for (const block of content) {
25536
25603
  if (!block || typeof block !== "object") continue;
25537
25604
  const b = block;
25605
+ if (b.type === "encrypted_content") encrypted = true;
25538
25606
  if (b.type === "input_text" && typeof b.text === "string") {
25539
25607
  text2 = b.text;
25540
- break;
25541
25608
  }
25542
25609
  }
25543
25610
  if (!text2) return void 0;
25544
25611
  const type = text2.match(/^Message Type:[ \t]*(\S+)/)?.[1];
25545
- if (type !== "FINAL_ANSWER" && type !== "MESSAGE") return void 0;
25612
+ if (!type) return void 0;
25546
25613
  const marker = text2.match(/^Payload:[ \t]*$/m);
25547
25614
  if (!marker || marker.index === void 0) return void 0;
25548
25615
  const body = text2.slice(marker.index + marker[0].length).replace(/^\n/, "").trim();
25549
- return { final: type === "FINAL_ANSWER", body };
25616
+ return { type, body, encrypted };
25550
25617
  }
25551
25618
  function parseCodexSubAgentLine(line, toolNameFor) {
25552
25619
  const s = line.trim();
@@ -29918,7 +29985,8 @@ function readNativeRecord(stateDir, projectId) {
29918
29985
  generation: value.generation,
29919
29986
  listenerPid: value.listenerPid,
29920
29987
  socketPath: value.socketPath,
29921
- createdAt: value.createdAt ?? ""
29988
+ createdAt: value.createdAt ?? "",
29989
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
29922
29990
  };
29923
29991
  } catch {
29924
29992
  return null;
@@ -30088,7 +30156,8 @@ async function startListener(options) {
30088
30156
  generation,
30089
30157
  listenerPid: child.pid ?? -1,
30090
30158
  socketPath,
30091
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
30159
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
30160
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {}
30092
30161
  };
30093
30162
  writeFileSync9(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
30094
30163
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
@@ -30118,7 +30187,8 @@ var init_codex_app_server_native = __esm({
30118
30187
  generation: existing.generation,
30119
30188
  reattached: true,
30120
30189
  daemonPid: existing.listenerPid,
30121
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30190
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30191
+ authAccountId: existing.authAccountId
30122
30192
  };
30123
30193
  } catch {
30124
30194
  try {
@@ -30134,7 +30204,8 @@ var init_codex_app_server_native = __esm({
30134
30204
  generation: record.generation,
30135
30205
  reattached: false,
30136
30206
  daemonPid: record.listenerPid,
30137
- droppedWhileDetached: 0
30207
+ droppedWhileDetached: 0,
30208
+ authAccountId: record.authAccountId
30138
30209
  };
30139
30210
  } catch (error) {
30140
30211
  const raced = liveNativeRecord(options.stateDir, options.projectId);
@@ -30144,12 +30215,13 @@ var init_codex_app_server_native = __esm({
30144
30215
  generation: raced.generation,
30145
30216
  reattached: true,
30146
30217
  daemonPid: raced.listenerPid,
30147
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30218
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30219
+ authAccountId: raced.authAccountId
30148
30220
  };
30149
30221
  }
30150
30222
  log.error("codex", `codex app-server native listener unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
30151
30223
  const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
30152
- return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
30224
+ return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
30153
30225
  }
30154
30226
  };
30155
30227
  }
@@ -30191,7 +30263,8 @@ function readDaemonRecord(stateDir, projectId) {
30191
30263
  daemonPid: value.daemonPid,
30192
30264
  childPid: typeof value.childPid === "number" ? value.childPid : 0,
30193
30265
  socketPath: value.socketPath,
30194
- createdAt: value.createdAt ?? ""
30266
+ createdAt: value.createdAt ?? "",
30267
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
30195
30268
  };
30196
30269
  } catch {
30197
30270
  return null;
@@ -30285,6 +30358,7 @@ function forkDaemon(options) {
30285
30358
  generation,
30286
30359
  clientInfo: options.clientInfo,
30287
30360
  capabilities: options.capabilities,
30361
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {},
30288
30362
  // The argv is built HERE, not in the daemon: the daemon is a bare forked entry that should not have
30289
30363
  // to resolve frizz's MCP descriptors, and building it once keeps this transport identical to the
30290
30364
  // native listener's.
@@ -30412,7 +30486,7 @@ function inProcessCodexAppServer(options) {
30412
30486
  return false;
30413
30487
  }
30414
30488
  });
30415
- return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
30489
+ return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
30416
30490
  }
30417
30491
  function directChildHost(spawnChild, generationId = randomUUID10) {
30418
30492
  return async (options) => ({
@@ -30420,7 +30494,8 @@ function directChildHost(spawnChild, generationId = randomUUID10) {
30420
30494
  generation: generationId(),
30421
30495
  reattached: false,
30422
30496
  daemonPid: process.pid,
30423
- droppedWhileDetached: 0
30497
+ droppedWhileDetached: 0,
30498
+ authAccountId: options.authAccountId
30424
30499
  });
30425
30500
  }
30426
30501
  var daemonEntry, daemonCodexAppServerHost;
@@ -30439,7 +30514,7 @@ var init_codex_app_server_host = __esm({
30439
30514
  if (existing) {
30440
30515
  try {
30441
30516
  const attached = await attach2(existing, timeoutMs);
30442
- return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid };
30517
+ return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid, authAccountId: existing.authAccountId };
30443
30518
  } catch {
30444
30519
  try {
30445
30520
  unlinkSync3(recordPath(options.stateDir, options.projectId));
@@ -30459,7 +30534,7 @@ var init_codex_app_server_host = __esm({
30459
30534
  try {
30460
30535
  const record = await forkDaemon(options);
30461
30536
  const attached = await attach2(record, timeoutMs);
30462
- return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid };
30537
+ return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid, authAccountId: record.authAccountId };
30463
30538
  } catch (error) {
30464
30539
  log.error("codex", `codex app-server daemon unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
30465
30540
  return inProcessCodexAppServer(options);
@@ -30468,318 +30543,937 @@ var init_codex_app_server_host = __esm({
30468
30543
  }
30469
30544
  });
30470
30545
 
30471
- // packages/server/src/backend/codex-app-server.ts
30472
- import { createHash as createHash9, randomUUID as randomUUID11 } from "node:crypto";
30473
- import { StringDecoder as StringDecoder3 } from "node:string_decoder";
30474
- function selectCodexHostKind(flagValue, platform3, hasSpawn) {
30475
- if (hasSpawn) return "direct";
30476
- const nativeSupported = platform3 !== "win32";
30477
- if (flagValue === "0" || flagValue === "false") return "daemon";
30478
- if (flagValue === "1" || flagValue === "true") return nativeSupported ? "native" : "daemon";
30479
- return nativeSupported ? "native" : "daemon";
30480
- }
30481
- function compareCodexVersions(a, b) {
30482
- const parse2 = (v) => {
30483
- const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
30484
- return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : [-1, -1, -1];
30485
- };
30486
- const [x, y] = [parse2(a), parse2(b)];
30487
- for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
30488
- return 0;
30546
+ // packages/server/src/backend/claude-quota.ts
30547
+ import { join as join22 } from "node:path";
30548
+ import { homedir as homedir9, platform } from "node:os";
30549
+ import { createHash as createHash9 } from "node:crypto";
30550
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
30551
+ import { execFile as execFile3 } from "node:child_process";
30552
+ import { promisify as promisify3 } from "node:util";
30553
+ function claudeConfigDir() {
30554
+ const override = process.env.CLAUDE_CONFIG_DIR;
30555
+ return override && override.trim() ? override : join22(homedir9(), ".claude");
30489
30556
  }
30490
- function codexVersionVerdict(received, audited = CODEX_APP_SERVER_SUPPORTED_VERSION) {
30491
- if (!received) {
30492
- return { kind: "refused", message: `Codex app-server did not report a parseable frizz/<version> user agent; expected ${audited}` };
30493
- }
30494
- const cmp = compareCodexVersions(received, audited);
30495
- if (cmp === 0) return { kind: "audited" };
30496
- if (cmp < 0) {
30497
- return {
30498
- kind: "refused",
30499
- message: `Codex app-server ${received} is older than the audited protocol ${audited}. Upgrade codex (\`npm i -g @openai/codex\`) \u2014 frizz sends parameters this build may not accept.`
30500
- };
30557
+ function tokenFromCredentialsJson(raw2) {
30558
+ let doc;
30559
+ try {
30560
+ doc = JSON.parse(raw2);
30561
+ } catch {
30562
+ return void 0;
30501
30563
  }
30502
- return {
30503
- kind: "ahead",
30504
- message: `Codex app-server ${received} is NEWER than frizz's audited protocol ${audited} \u2014 running anyway (the protocol is additive and unknown fields are ignored). If Codex threads misbehave, re-audit and re-pin. Run \`npm test -- codex-protocol-conformance\` to check frizz's params against this binary's own schema.`
30505
- };
30506
- }
30507
- function isCodexSandboxMode(value) {
30508
- return typeof value === "string" && CODEX_SANDBOX_MODES.includes(value);
30564
+ if (!doc || typeof doc !== "object") return void 0;
30565
+ const root = doc;
30566
+ const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
30567
+ const token = oauth.accessToken ?? oauth.access_token;
30568
+ return typeof token === "string" && token ? token : void 0;
30509
30569
  }
30510
- function codexSandboxPolicy(mode) {
30511
- switch (mode) {
30512
- case "read-only":
30513
- return { type: "readOnly", networkAccess: false };
30514
- case "danger-full-access":
30515
- return { type: "dangerFullAccess" };
30516
- default:
30517
- return {
30518
- type: "workspaceWrite",
30519
- writableRoots: [],
30520
- networkAccess: false,
30521
- excludeTmpdirEnvVar: false,
30522
- excludeSlashTmp: false
30523
- };
30570
+ async function readKeychainToken() {
30571
+ if (platform() !== "darwin") return void 0;
30572
+ try {
30573
+ const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
30574
+ encoding: "utf8",
30575
+ timeout: 4e3
30576
+ });
30577
+ return tokenFromCredentialsJson(String(stdout).trim());
30578
+ } catch {
30579
+ return void 0;
30524
30580
  }
30525
30581
  }
30526
- function codexSandboxModeOfPolicy(policy) {
30527
- switch (policy?.type) {
30528
- case "readOnly":
30529
- return "read-only";
30530
- case "workspaceWrite":
30531
- return "workspace-write";
30532
- case "dangerFullAccess":
30533
- return "danger-full-access";
30534
- default:
30535
- return void 0;
30582
+ async function readAccessToken(configDir) {
30583
+ try {
30584
+ const fromFile = tokenFromCredentialsJson(await readFile(join22(configDir, ".credentials.json"), "utf8"));
30585
+ if (fromFile) return fromFile;
30586
+ } catch {
30536
30587
  }
30588
+ return readKeychainToken();
30537
30589
  }
30538
- function codexAppServerEnvironment(source = process.env) {
30539
- return inheritWorkerEnvironment(source);
30590
+ function isoToUnix(value) {
30591
+ const ms = typeof value === "string" ? Date.parse(value) : NaN;
30592
+ return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
30540
30593
  }
30541
- function checkedBindingRow(raw2) {
30542
- const parsed = BindingRowSchema.safeParse(raw2);
30543
- if (!parsed.success) throw new InteractionStoreError("corrupt-journal", "Codex app-server session binding is corrupt");
30544
- return parsed.data;
30594
+ function slugify2(name) {
30595
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30545
30596
  }
30546
- function effectiveResumeSandbox(rawResponse) {
30547
- const parsed = ThreadResumeSandbox.safeParse(rawResponse);
30548
- return parsed.success ? codexSandboxModeOfPolicy(parsed.data.sandbox) : void 0;
30597
+ function toWindow(raw2, key, label) {
30598
+ if (!raw2 || typeof raw2 !== "object") return void 0;
30599
+ const w = raw2;
30600
+ const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
30601
+ if (util2 === void 0) return void 0;
30602
+ const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
30603
+ return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
30549
30604
  }
30550
- function turnKey(row) {
30551
- return `${row.thread_slug}\0${row.frizz_session_id}\0${row.connection_epoch}`;
30605
+ function windowFromLimit(raw2) {
30606
+ if (!raw2 || typeof raw2 !== "object") return void 0;
30607
+ const l = raw2;
30608
+ const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
30609
+ if (percent === void 0) return void 0;
30610
+ const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
30611
+ const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
30612
+ const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
30613
+ const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
30614
+ let key;
30615
+ let label;
30616
+ if (group === "session") {
30617
+ key = "5h";
30618
+ label = "5h";
30619
+ } else if (group === "weekly") {
30620
+ key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
30621
+ label = scopeName ? `${scopeName} wk` : "Weekly";
30622
+ } else if (group) {
30623
+ key = slugify2(group);
30624
+ label = scopeName ? `${scopeName} ${group}` : group;
30625
+ } else {
30626
+ return void 0;
30627
+ }
30628
+ const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
30629
+ return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
30552
30630
  }
30553
- function bindingFromRow(row) {
30554
- return {
30555
- threadSlug: row.thread_slug,
30556
- sessionId: row.frizz_session_id,
30557
- codexThreadId: row.codex_thread_id,
30558
- codexSessionId: row.codex_session_id,
30559
- sessionEpoch: row.session_epoch,
30560
- capabilityRevision: row.capability_revision,
30561
- connectionEpoch: row.connection_epoch,
30562
- currentTurnId: row.current_turn_id,
30563
- cwd: row.cwd,
30564
- ephemeral: row.ephemeral === 1,
30565
- state: row.state,
30566
- ...isCodexSandboxMode(row.sandbox) ? { sandbox: row.sandbox } : {}
30631
+ function parseClaudeUsage(body, planType) {
30632
+ if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
30633
+ const b = body;
30634
+ const windows = [];
30635
+ const seen = /* @__PURE__ */ new Set();
30636
+ const add = (w) => {
30637
+ if (w && !seen.has(w.key)) {
30638
+ seen.add(w.key);
30639
+ windows.push(w);
30640
+ }
30567
30641
  };
30642
+ if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
30643
+ add(toWindow(b.five_hour, "5h", "5h"));
30644
+ add(toWindow(b.seven_day, "weekly", "Weekly"));
30645
+ add(toWindow(b.seven_day_opus, "weekly-opus", "Opus wk"));
30646
+ add(toWindow(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
30647
+ if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
30648
+ return { status: "ok", planType, windows };
30568
30649
  }
30569
- function cleanText(raw2, maxChars, fallback) {
30570
- if (!raw2) return fallback;
30571
- const cleaned = redactDisplaySecrets(
30572
- raw2.replace(/[\p{Cf}\p{Cs}\p{Zl}\p{Zp}\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, " ")
30573
- ).slice(0, maxChars).trim();
30574
- return cleaned || fallback;
30575
- }
30576
- function visibleControls(raw2) {
30577
- return raw2.replace(/\r\n?/gu, "\n").replace(UNSAFE_DISPLAY_TEXT, (value) => `[U+${value.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}]`);
30578
- }
30579
- function redactedSecretValue(raw2) {
30580
- const quote = raw2.length >= 2 && (raw2[0] === '"' || raw2[0] === "'") && raw2.at(-1) === raw2[0] ? raw2[0] : "";
30581
- const content = quote ? raw2.slice(1, -1) : raw2;
30582
- const executable = content.match(/\$\([^\r\n)]{0,4096}\)|`[^`\r\n]{0,4096}`|[<>]\([^\r\n)]{0,4096}\)/gu) ?? [];
30583
- const replacement2 = executable.length === 0 ? DISPLAY_REDACTION : `${DISPLAY_REDACTION} [embedded executable shell syntax: ${executable.join(" ")}]`;
30584
- return replacement2;
30585
- }
30586
- function redactDisplaySecrets(raw2) {
30587
- let value = redactCredentialSyntax(raw2, { replacement: redactedSecretValue });
30588
- value = value.replace(
30589
- /-----BEGIN [^-\r\n]{0,80}PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]{0,80}PRIVATE KEY-----/giu,
30590
- DISPLAY_REDACTION
30591
- );
30592
- value = value.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+):([^\s/@]+)@/giu, `$1${DISPLAY_REDACTION}@`);
30593
- value = value.replace(
30594
- /\b(authorization|proxy-authorization)(\s*[:=]\s*)(bearer|basic)(\s+)([^\s'";|]+)/giu,
30595
- (_whole, name, separator, scheme, whitespace, secret) => `${name}${separator}${scheme}${whitespace}${redactedSecretValue(secret)}`
30596
- );
30597
- value = value.replace(/\b(bearer|basic)(\s+)[A-Za-z0-9._~+/=-]{8,}/giu, `$1$2${DISPLAY_REDACTION}`);
30598
- value = value.replace(
30599
- /(^|[\s;&|([{])((?:--?|\/)(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|passwd|credential|private[-_]?key)(?:\s*=\s*|\s+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gimu,
30600
- (_whole, boundary, flag, secret) => `${boundary}${flag}${redactedSecretValue(secret)}`
30601
- );
30602
- value = value.replace(
30603
- /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*=\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gu,
30604
- (whole, name, separator, secret) => SECRET_NAME.test(name) ? `${name}${separator}${redactedSecretValue(secret)}` : whole
30605
- );
30606
- value = value.replace(
30607
- /(^|[\s;&|([{])((?:--?|\/)(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|passwd|credential|private[-_]?key)(?:\s*=\s*|\s+))(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gimu,
30608
- `$1$2${DISPLAY_REDACTION}`
30609
- );
30610
- value = value.replace(
30611
- /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*=\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gu,
30612
- (whole, name, separator) => SECRET_NAME.test(name) ? `${name}${separator}${DISPLAY_REDACTION}` : whole
30613
- );
30614
- value = value.replace(
30615
- /(["']?)([A-Za-z_][A-Za-z0-9_.-]{0,127})\1(\s*:\s*)(?:"[^"\r\n]*"|'[^'\r\n]*')/gu,
30616
- (whole, quote, name, separator) => SECRET_NAME.test(name) ? `${quote}${name}${quote}${separator}"${DISPLAY_REDACTION}"` : whole
30617
- );
30618
- value = value.replace(
30619
- /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*:\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;|}\]]+)/gu,
30620
- (whole, name, separator) => SECRET_NAME.test(name) ? `${name}${separator}${DISPLAY_REDACTION}` : whole
30621
- );
30622
- value = value.replace(/\b(?:sk-[A-Za-z0-9_-]{12,}|github_pat_[A-Za-z0-9_]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,})\b/gu, DISPLAY_REDACTION);
30623
- value = value.replace(/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu, DISPLAY_REDACTION);
30624
- value = value.replace(
30625
- /(?<![A-Za-z0-9_-])(?:AIza[A-Za-z0-9_-]{20,}|glpat-[A-Za-z0-9_-]{12,}|npm_[A-Za-z0-9_-]{12,}|(?:sk|pk)_(?:live|test)_[A-Za-z0-9_-]{12,})(?![A-Za-z0-9_-])/gu,
30626
- DISPLAY_REDACTION
30627
- );
30628
- value = value.replace(
30629
- /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/gu,
30630
- DISPLAY_REDACTION
30631
- );
30632
- return value;
30650
+ function cachePaths(cacheDir, configDir) {
30651
+ const profile = createHash9("sha256").update(configDir).digest("hex").slice(0, 12);
30652
+ return {
30653
+ data: join22(cacheDir, `claude-${profile}.json`),
30654
+ lock: join22(cacheDir, `claude-${profile}.lock`)
30655
+ };
30633
30656
  }
30634
- function completeDisplayText(raw2, limits, field2) {
30635
- if (raw2 === null || raw2 === void 0 || raw2.length === 0) return limits.fallback;
30636
- const value = redactDisplaySecrets(visibleControls(raw2));
30637
- if (value.split("\n").length > limits.maxLines || value.length > limits.maxChars || Buffer.byteLength(value, "utf8") > limits.maxBytes) {
30638
- throw new RpcProtocolError(-32602, `Codex ${field2} cannot be completely represented for approval`);
30657
+ async function readShared(path3) {
30658
+ try {
30659
+ const parsed = JSON.parse(await readFile(path3, "utf8"));
30660
+ if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
30661
+ return parsed;
30662
+ } catch {
30663
+ return void 0;
30639
30664
  }
30640
- return value.trim().length === 0 ? limits.fallback : value;
30641
- }
30642
- function displayLabel(raw2, fallback, maxChars = 1024, maxBytes = 2048, field2 = "label") {
30643
- return completeDisplayText(
30644
- raw2?.replace(/\r\n?|\n/gu, " \u23CE "),
30645
- { maxChars, maxBytes, maxLines: 1, fallback },
30646
- field2
30647
- );
30648
30665
  }
30649
- function displayDescription(raw2, fallback) {
30650
- return completeDisplayText(raw2, { maxChars: 4e3, maxBytes: 8e3, maxLines: 256, fallback }, "approval reason");
30666
+ async function writeShared(path3, value) {
30667
+ const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
30668
+ try {
30669
+ await writeFile(tmp, JSON.stringify(value), { mode: 384 });
30670
+ await rename(tmp, path3);
30671
+ } finally {
30672
+ await rm(tmp, { force: true }).catch(() => {
30673
+ });
30674
+ }
30651
30675
  }
30652
- function displayPreview(raw2, fallback) {
30653
- return completeDisplayText(raw2, { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback }, "command");
30676
+ function delay(ms) {
30677
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
30654
30678
  }
30655
- function displayActionPreview(raw2) {
30656
- return completeDisplayText(
30657
- raw2,
30658
- { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback: "Command detail unavailable" },
30659
- "parsed command action"
30660
- );
30679
+ async function acquireLock(path3) {
30680
+ const deadline = Date.now() + LOCK_WAIT_MS;
30681
+ for (; ; ) {
30682
+ try {
30683
+ await mkdir(path3);
30684
+ return () => rm(path3, { recursive: true, force: true });
30685
+ } catch (err) {
30686
+ if (err.code !== "EEXIST") throw err;
30687
+ try {
30688
+ if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
30689
+ await rm(path3, { recursive: true, force: true });
30690
+ continue;
30691
+ }
30692
+ } catch {
30693
+ continue;
30694
+ }
30695
+ if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
30696
+ await delay(LOCK_POLL_MS2);
30697
+ }
30698
+ }
30661
30699
  }
30662
- function displayDiff(raw2) {
30663
- if (!raw2) return void 0;
30664
- return completeDisplayText(
30665
- raw2,
30666
- { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback: "Diff detail unavailable" },
30667
- "file diff"
30700
+ async function runClaudeUsage(claudeBin) {
30701
+ const { stdout } = await execFileAsync2(
30702
+ // Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
30703
+ // EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
30704
+ // never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
30705
+ // A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
30706
+ resolveClaudeExecutableAbsolute(claudeBin),
30707
+ ["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
30708
+ { encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
30668
30709
  );
30710
+ return String(stdout);
30669
30711
  }
30670
- function fileSystemPathLabel(path3) {
30671
- if (path3.type === "path") return displayLabel(path3.path, "Filesystem path unavailable", 2048, 4096);
30672
- if (path3.type === "glob_pattern") return displayLabel(`Glob pattern: ${path3.pattern}`, "Glob pattern unavailable", 2048, 4096);
30673
- if (path3.value.kind === "root") return "Filesystem root (/)";
30674
- if (path3.value.kind === "minimal") return "Minimal filesystem set";
30675
- if (path3.value.kind === "tmpdir") return "System temporary directory";
30676
- if (path3.value.kind === "slash_tmp") return "/tmp";
30677
- if (path3.value.kind === "project_roots") {
30678
- return path3.value.subpath ? displayLabel(`Project roots, subpath: ${path3.value.subpath}`, "Project roots", 2048, 4096) : "Project roots";
30712
+ async function refreshQuota(claudeBin, deps, now) {
30713
+ const doFetch = deps.fetchImpl ?? fetch;
30714
+ const readToken = deps.readToken ?? readAccessToken;
30715
+ let token;
30716
+ try {
30717
+ token = await readToken(claudeConfigDir());
30718
+ } catch {
30719
+ token = void 0;
30679
30720
  }
30680
- return displayLabel(
30681
- path3.value.subpath ? `${path3.value.path}, subpath: ${path3.value.subpath}` : path3.value.path,
30682
- "Provider-defined filesystem path",
30683
- 2048,
30684
- 4096
30685
- );
30686
- }
30687
- function permissionCapabilities(permissions) {
30688
- if (!permissions) return [];
30689
- const capabilities = [];
30690
- if (permissions.network) capabilities.push({ kind: "network", enabled: permissions.network.enabled, hosts: [] });
30691
- if (permissions.fileSystem) {
30692
- const byAccess = { read: [], write: [], deny: [] };
30693
- for (const path3 of permissions.fileSystem.read ?? []) byAccess.read.push(displayLabel(path3, "Read path unavailable", 2048, 4096));
30694
- for (const path3 of permissions.fileSystem.write ?? []) byAccess.write.push(displayLabel(path3, "Write path unavailable", 2048, 4096));
30695
- for (const entry of permissions.fileSystem.entries ?? []) byAccess[entry.access].push(fileSystemPathLabel(entry.path));
30696
- for (const access of ["read", "write", "deny"]) {
30697
- const resources = byAccess[access];
30698
- if (resources.length > 32) {
30699
- throw new RpcProtocolError(-32602, `Codex filesystem ${access} scope has too many resources to display completely`);
30721
+ if (token) {
30722
+ try {
30723
+ const res = await doFetch(USAGE_URL, {
30724
+ headers: {
30725
+ Authorization: `Bearer ${token}`,
30726
+ "anthropic-beta": OAUTH_BETA,
30727
+ "User-Agent": USER_AGENT,
30728
+ "Content-Type": "application/json"
30729
+ },
30730
+ signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
30731
+ });
30732
+ if (res.ok) {
30733
+ const body = await res.json();
30734
+ const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
30735
+ const quota2 = parseClaudeUsage(body, planType);
30736
+ if (quota2.status === "ok") return quota2;
30737
+ } else if (res.status === 429 || res.status === 529) {
30738
+ throw new Error(`Usage endpoint ${res.status}`);
30700
30739
  }
30701
- if (resources.length > 0) capabilities.push({ kind: "filesystem", access, resources });
30702
- }
30703
- if (permissions.fileSystem.globScanMaxDepth !== void 0) {
30704
- capabilities.push({ kind: "glob-scan", depth: permissions.fileSystem.globScanMaxDepth });
30740
+ } catch (err) {
30741
+ if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
30705
30742
  }
30706
30743
  }
30707
- return capabilities;
30744
+ const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
30745
+ if (quota.status !== "ok") throw new Error(quota.detail);
30746
+ return quota;
30708
30747
  }
30709
- function commandActions(actions) {
30710
- if (!actions?.length) return void 0;
30711
- if (actions.length > 16) {
30712
- throw new RpcProtocolError(-32602, "Codex command approval has too many parsed actions to display completely");
30748
+ function windowIdentity(title) {
30749
+ for (const [pattern, key, label] of LINE_WINDOWS) {
30750
+ if (pattern.test(title)) return [key, label];
30713
30751
  }
30714
- return actions.map((action) => {
30715
- if (action.type === "read") {
30716
- return {
30717
- kind: "read",
30718
- commandPreview: displayActionPreview(action.command),
30719
- resourceLabel: displayLabel(`${action.path} (${action.name})`, "Read target unavailable", 2048, 4096)
30720
- };
30721
- }
30722
- if (action.type === "listFiles") {
30723
- return {
30724
- kind: "list-files",
30725
- commandPreview: displayActionPreview(action.command),
30726
- resourceLabel: action.path !== null ? displayLabel(action.path, "List target unavailable", 2048, 4096) : "Current working directory"
30727
- };
30752
+ const scoped = /^Current week \((.+)\)$/i.exec(title);
30753
+ if (!scoped) return void 0;
30754
+ const name = scoped[1].replace(/\s+only$/i, "");
30755
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30756
+ return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
30757
+ }
30758
+ function parseResetLabel(label, now) {
30759
+ if (!label) return void 0;
30760
+ const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
30761
+ const parts = /^(?:([A-Za-z]{3,9})\s+(\d{1,2})(?:,?\s+(\d{4}))?)?\s*(?:at\s+)?(?:(\d{1,2})(?::(\d{2}))?\s*(am|pm))?$/i.exec(text2);
30762
+ const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
30763
+ const hasClock = parts?.[6] !== void 0;
30764
+ if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
30765
+ const date2 = new Date(now);
30766
+ if (hasClock) {
30767
+ let hour = Number(parts[4]) % 12;
30768
+ if (parts[6].toLowerCase() === "pm") hour += 12;
30769
+ date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
30770
+ } else {
30771
+ date2.setHours(0, 0, 0, 0);
30728
30772
  }
30729
- if (action.type === "search") {
30730
- return {
30731
- kind: "search",
30732
- commandPreview: displayActionPreview(action.command),
30733
- ...action.path !== null ? { resourceLabel: displayLabel(action.path, "Search target unavailable", 2048, 4096) } : {},
30734
- ...action.query !== null ? { queryLabel: displayLabel(action.query, "Search query unavailable") } : {}
30735
- };
30773
+ if (month !== void 0) {
30774
+ date2.setMonth(month, Number(parts[2]));
30775
+ if (parts[3]) date2.setFullYear(Number(parts[3]));
30776
+ else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
30777
+ } else if (date2.getTime() <= now) {
30778
+ date2.setDate(date2.getDate() + 1);
30736
30779
  }
30737
- return { kind: "unknown", commandPreview: displayActionPreview(action.command) };
30738
- });
30780
+ return Math.round(date2.getTime() / 1e3);
30781
+ }
30782
+ const parsed = Date.parse(text2);
30783
+ if (!Number.isFinite(parsed)) return void 0;
30784
+ const date = new Date(parsed);
30785
+ if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
30786
+ return Math.round(date.getTime() / 1e3);
30739
30787
  }
30740
- function commandCapabilities(params) {
30741
- const capabilities = permissionCapabilities(params.additionalPermissions);
30742
- if (params.networkApprovalContext) {
30743
- const host = displayLabel(
30744
- `${params.networkApprovalContext.protocol}: ${params.networkApprovalContext.host}`,
30745
- "Network host unavailable",
30746
- 2048,
30747
- 4096
30748
- );
30749
- const network = capabilities.find((capability) => capability.kind === "network");
30750
- if (network) network.hosts = [...network.hosts, host];
30751
- else capabilities.unshift({ kind: "network", enabled: null, hosts: [host] });
30788
+ function parseClaudeUsageOutput(stdout, now = Date.now()) {
30789
+ let text2 = stdout;
30790
+ try {
30791
+ const envelope = JSON.parse(stdout);
30792
+ if (typeof envelope.result === "string") text2 = envelope.result;
30793
+ } catch {
30752
30794
  }
30753
- if (params.proposedExecpolicyAmendment?.length) {
30754
- if (params.proposedExecpolicyAmendment.length > 32) {
30755
- throw new RpcProtocolError(-32602, "Codex execution policy amendment has too many prefix tokens to display completely");
30756
- }
30757
- capabilities.push({
30758
- kind: "exec-policy",
30759
- prefixes: params.proposedExecpolicyAmendment.map((part) => displayLabel(part, "Command prefix token unavailable", 2048, 4096, "execution policy prefix"))
30795
+ const windows = [];
30796
+ for (const line of text2.split(/\r?\n/)) {
30797
+ const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
30798
+ if (!match2) continue;
30799
+ const identity = windowIdentity(match2[1]);
30800
+ if (!identity) continue;
30801
+ const resetsAt = parseResetLabel(match2[3], now);
30802
+ windows.push({
30803
+ key: identity[0],
30804
+ label: identity[1],
30805
+ usedPercent: Number(match2[2]),
30806
+ ...resetsAt === void 0 ? {} : { resetsAt }
30760
30807
  });
30761
30808
  }
30762
- for (const action of ["allow", "deny"]) {
30763
- const matching = (params.proposedNetworkPolicyAmendments ?? []).filter((amendment) => amendment.action === action);
30764
- if (matching.length > 24) {
30765
- throw new RpcProtocolError(-32602, `Codex ${action} network policy has too many hosts to display completely`);
30766
- }
30767
- const hosts = matching.map((amendment) => displayLabel(amendment.host, "Network host unavailable", 2048, 4096, "network policy host"));
30768
- if (hosts.length) capabilities.push({ kind: "network-policy", access: action, hosts });
30809
+ if (windows.length === 0) {
30810
+ return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
30769
30811
  }
30770
- return capabilities.length ? capabilities : void 0;
30812
+ return { status: "ok", windows };
30771
30813
  }
30772
- function fileChangeDisplays(changes) {
30773
- if (changes.length > 16) {
30774
- throw new RpcProtocolError(-32602, "Codex file approval has too many changes to display completely");
30775
- }
30776
- return {
30777
- totalChanges: changes.length,
30778
- changes: changes.map((change) => {
30779
- const diffPreview = displayDiff(change.diff);
30780
- const base = {
30781
- pathLabel: displayLabel(change.path, "Affected path unavailable", 2048, 4096),
30782
- ...diffPreview ? { diffPreview } : {}
30814
+ async function tryAcquireLock2(path3) {
30815
+ for (let attempt = 0; attempt < 2; attempt++) {
30816
+ try {
30817
+ await mkdir(path3);
30818
+ return () => rm(path3, { recursive: true, force: true });
30819
+ } catch (err) {
30820
+ if (err.code !== "EEXIST") return void 0;
30821
+ try {
30822
+ if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
30823
+ await rm(path3, { recursive: true, force: true });
30824
+ } catch {
30825
+ return void 0;
30826
+ }
30827
+ }
30828
+ }
30829
+ return void 0;
30830
+ }
30831
+ function refreshSharedInBackground(paths, claudeBin, deps, now) {
30832
+ if (backgroundRefreshes.has(paths.data)) return;
30833
+ const task = (async () => {
30834
+ const release = await tryAcquireLock2(paths.lock);
30835
+ if (!release) return;
30836
+ try {
30837
+ const quota = await refreshQuota(claudeBin, deps, now);
30838
+ await writeShared(paths.data, { at: now, quota });
30839
+ } finally {
30840
+ await release().catch(() => {
30841
+ });
30842
+ }
30843
+ })().catch(() => {
30844
+ }).finally(() => backgroundRefreshes.delete(paths.data));
30845
+ backgroundRefreshes.set(paths.data, task);
30846
+ }
30847
+ async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
30848
+ const now = (deps.now ?? Date.now)();
30849
+ const configDir = claudeConfigDir();
30850
+ const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
30851
+ try {
30852
+ await mkdir(cacheDir, { recursive: true, mode: 448 });
30853
+ } catch {
30854
+ return;
30855
+ }
30856
+ refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
30857
+ }
30858
+ async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
30859
+ const now = (deps.now ?? Date.now)();
30860
+ const configDir = claudeConfigDir();
30861
+ const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
30862
+ const paths = cachePaths(cacheDir, configDir);
30863
+ try {
30864
+ await mkdir(cacheDir, { recursive: true, mode: 448 });
30865
+ } catch {
30866
+ try {
30867
+ return await refreshQuota(claudeBin, deps, now);
30868
+ } catch {
30869
+ return { status: "unavailable", windows: [], detail: "Claude usage unavailable" };
30870
+ }
30871
+ }
30872
+ const initial = await readShared(paths.data);
30873
+ if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
30874
+ return initial.quota;
30875
+ }
30876
+ if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
30877
+ refreshSharedInBackground(paths, claudeBin, deps, now);
30878
+ const ageMs = now - initial.at;
30879
+ if (ageMs < 10 * 6e4) return initial.quota;
30880
+ return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
30881
+ }
30882
+ let release;
30883
+ try {
30884
+ release = await acquireLock(paths.lock);
30885
+ const afterLock = await readShared(paths.data);
30886
+ if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
30887
+ const quota = await refreshQuota(claudeBin, deps, now);
30888
+ await writeShared(paths.data, { at: now, quota }).catch(() => {
30889
+ });
30890
+ return quota;
30891
+ } catch {
30892
+ const fallback = await readShared(paths.data);
30893
+ if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
30894
+ return {
30895
+ ...fallback.quota,
30896
+ detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
30897
+ };
30898
+ }
30899
+ const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
30900
+ await writeShared(paths.data, { at: now, quota: unavailable }).catch(() => {
30901
+ });
30902
+ return unavailable;
30903
+ } finally {
30904
+ await release?.().catch(() => {
30905
+ });
30906
+ }
30907
+ }
30908
+ var execFileAsync2, USAGE_URL, OAUTH_BETA, USER_AGENT, ENDPOINT_TIMEOUT_MS, OK_TTL_MS, FAIL_TTL_MS, STALE_MAX_AGE_MS, CLI_TIMEOUT_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_POLL_MS2, LINE_WINDOWS, MONTH_INDEX, backgroundRefreshes;
30909
+ var init_claude_quota = __esm({
30910
+ "packages/server/src/backend/claude-quota.ts"() {
30911
+ "use strict";
30912
+ init_frizz_paths();
30913
+ init_claude_broker_host();
30914
+ execFileAsync2 = promisify3(execFile3);
30915
+ USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
30916
+ OAUTH_BETA = "oauth-2025-04-20";
30917
+ USER_AGENT = "claude-code/2.0.0";
30918
+ ENDPOINT_TIMEOUT_MS = 5e3;
30919
+ OK_TTL_MS = 2 * 6e4;
30920
+ FAIL_TTL_MS = 1e4;
30921
+ STALE_MAX_AGE_MS = 24 * 60 * 6e4;
30922
+ CLI_TIMEOUT_MS = 3e4;
30923
+ LOCK_STALE_MS = 4e4;
30924
+ LOCK_WAIT_MS = 15e3;
30925
+ LOCK_POLL_MS2 = 50;
30926
+ LINE_WINDOWS = [
30927
+ [/^Current session$/i, "5h", "5h"],
30928
+ [/^Current week \(all models\)$/i, "weekly", "Weekly"],
30929
+ [/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
30930
+ [/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
30931
+ ];
30932
+ MONTH_INDEX = {
30933
+ jan: 0,
30934
+ feb: 1,
30935
+ mar: 2,
30936
+ apr: 3,
30937
+ may: 4,
30938
+ jun: 5,
30939
+ jul: 6,
30940
+ aug: 7,
30941
+ sep: 8,
30942
+ oct: 9,
30943
+ nov: 10,
30944
+ dec: 11
30945
+ };
30946
+ backgroundRefreshes = /* @__PURE__ */ new Map();
30947
+ }
30948
+ });
30949
+
30950
+ // packages/server/src/backend/auth-status.ts
30951
+ import { join as join23 } from "node:path";
30952
+ import { homedir as homedir10, platform as platform2 } from "node:os";
30953
+ import { readFileSync as readFileSync16, statSync as statSync10 } from "node:fs";
30954
+ import { execFile as execFile4 } from "node:child_process";
30955
+ import { promisify as promisify4 } from "node:util";
30956
+ function claudeConfigDir2() {
30957
+ const override = process.env.CLAUDE_CONFIG_DIR;
30958
+ return override && override.trim() ? override : join23(homedir10(), ".claude");
30959
+ }
30960
+ function claudeFileState(configDir) {
30961
+ let raw2;
30962
+ try {
30963
+ raw2 = readFileSync16(join23(configDir, ".credentials.json"), "utf8");
30964
+ } catch (err) {
30965
+ return err.code === "ENOENT" ? "absent" : "error";
30966
+ }
30967
+ return tokenFromCredentialsJson(raw2) ? "token" : "empty";
30968
+ }
30969
+ async function claudeKeychainState() {
30970
+ if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
30971
+ if (platform2() !== "darwin") return "absent";
30972
+ try {
30973
+ const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
30974
+ encoding: "utf8",
30975
+ timeout: 4e3
30976
+ });
30977
+ return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
30978
+ } catch (err) {
30979
+ const e = err;
30980
+ const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
30981
+ return notFound ? "absent" : "error";
30982
+ }
30983
+ }
30984
+ async function readClaudeAuthState(configDir = claudeConfigDir2()) {
30985
+ const file = claudeFileState(configDir);
30986
+ if (file === "token") return "authed";
30987
+ const keychain = await claudeKeychainState();
30988
+ if (keychain === "token") return "authed";
30989
+ if (file === "error" || keychain === "error") return "unknown";
30990
+ return "signed-out";
30991
+ }
30992
+ function readCodexAuthState(codexHome = defaultCodexHome()) {
30993
+ if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
30994
+ let raw2;
30995
+ try {
30996
+ raw2 = readFileSync16(join23(codexHome, "auth.json"), "utf8");
30997
+ } catch (err) {
30998
+ return err.code === "ENOENT" ? "signed-out" : "unknown";
30999
+ }
31000
+ let doc;
31001
+ try {
31002
+ doc = JSON.parse(raw2);
31003
+ } catch {
31004
+ return "unknown";
31005
+ }
31006
+ if (!doc || typeof doc !== "object") return "unknown";
31007
+ const root = doc;
31008
+ const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
31009
+ const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
31010
+ const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
31011
+ return apiKey || accessToken ? "authed" : "signed-out";
31012
+ }
31013
+ function readCodexAccountId(codexHome = defaultCodexHome()) {
31014
+ if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
31015
+ try {
31016
+ const doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31017
+ if (!doc || typeof doc !== "object") return void 0;
31018
+ const tokens = doc.tokens;
31019
+ if (!tokens || typeof tokens !== "object") return void 0;
31020
+ const accountId = tokens.account_id;
31021
+ return typeof accountId === "string" && accountId.trim() ? accountId : void 0;
31022
+ } catch {
31023
+ return void 0;
31024
+ }
31025
+ }
31026
+ async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
31027
+ try {
31028
+ await exec(codexBin, ["--version"], { timeout: 5e3 });
31029
+ return "present";
31030
+ } catch (err) {
31031
+ return err.code === "ENOENT" ? "missing" : "unknown";
31032
+ }
31033
+ }
31034
+ function parseClaudeAuthStatusJson(stdout) {
31035
+ const start = stdout.indexOf("{");
31036
+ const end = stdout.lastIndexOf("}");
31037
+ if (start === -1 || end <= start) return void 0;
31038
+ try {
31039
+ const doc = JSON.parse(stdout.slice(start, end + 1));
31040
+ return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
31041
+ } catch {
31042
+ return void 0;
31043
+ }
31044
+ }
31045
+ async function readClaudeAuthStatusCli(opts) {
31046
+ try {
31047
+ const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
31048
+ const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
31049
+ encoding: "utf8",
31050
+ timeout: opts?.timeoutMs ?? 5e3,
31051
+ ...opts?.cwd ? { cwd: opts.cwd } : {}
31052
+ });
31053
+ return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
31054
+ } catch (err) {
31055
+ const stdout = err.stdout;
31056
+ const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
31057
+ if (loggedIn === false) return "signed-out";
31058
+ if (loggedIn === true) return "authed";
31059
+ return "unknown";
31060
+ }
31061
+ }
31062
+ async function readClaudePreflightAuth(opts) {
31063
+ const local = await readClaudeAuthState();
31064
+ if (local !== "signed-out") return local;
31065
+ return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
31066
+ }
31067
+ function asEmail(value) {
31068
+ if (typeof value !== "string") return void 0;
31069
+ const email = value.trim();
31070
+ return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
31071
+ }
31072
+ function claudeAccountFile() {
31073
+ const override = process.env.CLAUDE_CONFIG_DIR;
31074
+ return override && override.trim() ? join23(override, ".claude.json") : join23(homedir10(), ".claude.json");
31075
+ }
31076
+ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
31077
+ let stat2;
31078
+ try {
31079
+ stat2 = statSync10(path3);
31080
+ } catch {
31081
+ return void 0;
31082
+ }
31083
+ const memo3 = claudeAccountMemo;
31084
+ if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
31085
+ let email;
31086
+ try {
31087
+ const doc = JSON.parse(readFileSync16(path3, "utf8"));
31088
+ email = asEmail(doc?.oauthAccount?.emailAddress);
31089
+ } catch {
31090
+ email = void 0;
31091
+ }
31092
+ claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
31093
+ return email;
31094
+ }
31095
+ function readCodexAccountEmail(codexHome = defaultCodexHome()) {
31096
+ let doc;
31097
+ try {
31098
+ doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31099
+ } catch {
31100
+ return void 0;
31101
+ }
31102
+ const idToken = doc?.tokens?.id_token;
31103
+ if (typeof idToken !== "string") return void 0;
31104
+ const payload = idToken.split(".")[1];
31105
+ if (!payload) return void 0;
31106
+ try {
31107
+ return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
31108
+ } catch {
31109
+ return void 0;
31110
+ }
31111
+ }
31112
+ function readAccountEmails() {
31113
+ const emails = {};
31114
+ const claude = tryRead(readClaudeAccountEmail);
31115
+ const codex = tryRead(readCodexAccountEmail);
31116
+ if (claude) emails.claude = claude;
31117
+ if (codex) emails.codex = codex;
31118
+ return emails;
31119
+ }
31120
+ function tryRead(read) {
31121
+ try {
31122
+ return read();
31123
+ } catch {
31124
+ return void 0;
31125
+ }
31126
+ }
31127
+ async function readAuthSnapshot(opts) {
31128
+ const [claude, codex] = await Promise.all([
31129
+ readClaudeAuthState().then(async (local) => {
31130
+ if (local !== "signed-out") return local;
31131
+ const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
31132
+ return cli === "authed" ? "authed" : local;
31133
+ }).catch(() => "unknown"),
31134
+ Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
31135
+ ]);
31136
+ const emails = readAccountEmails();
31137
+ return {
31138
+ claude,
31139
+ codex,
31140
+ emails: {
31141
+ ...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
31142
+ ...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
31143
+ }
31144
+ };
31145
+ }
31146
+ var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
31147
+ var init_auth_status = __esm({
31148
+ "packages/server/src/backend/auth-status.ts"() {
31149
+ "use strict";
31150
+ init_claude_quota();
31151
+ init_codex();
31152
+ init_claude_broker_host();
31153
+ execFileAsync3 = promisify4(execFile4);
31154
+ ProviderAuthRequiredError = class extends Error {
31155
+ backend;
31156
+ constructor(backend) {
31157
+ super(`AUTH_REQUIRED:${backend}`);
31158
+ this.name = "ProviderAuthRequiredError";
31159
+ this.backend = backend;
31160
+ }
31161
+ };
31162
+ }
31163
+ });
31164
+
31165
+ // packages/server/src/backend/codex-app-server.ts
31166
+ import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
31167
+ import { StringDecoder as StringDecoder3 } from "node:string_decoder";
31168
+ function selectCodexHostKind(flagValue, platform3, hasSpawn) {
31169
+ if (hasSpawn) return "direct";
31170
+ const nativeSupported = platform3 !== "win32";
31171
+ if (flagValue === "0" || flagValue === "false") return "daemon";
31172
+ if (flagValue === "1" || flagValue === "true") return nativeSupported ? "native" : "daemon";
31173
+ return nativeSupported ? "native" : "daemon";
31174
+ }
31175
+ function compareCodexVersions(a, b) {
31176
+ const parse2 = (v) => {
31177
+ const m = v.match(/^(\d+)\.(\d+)\.(\d+)/);
31178
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : [-1, -1, -1];
31179
+ };
31180
+ const [x, y] = [parse2(a), parse2(b)];
31181
+ for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
31182
+ return 0;
31183
+ }
31184
+ function codexVersionVerdict(received, audited = CODEX_APP_SERVER_SUPPORTED_VERSION) {
31185
+ if (!received) {
31186
+ return { kind: "refused", message: `Codex app-server did not report a parseable frizz/<version> user agent; expected ${audited}` };
31187
+ }
31188
+ const cmp = compareCodexVersions(received, audited);
31189
+ if (cmp === 0) return { kind: "audited" };
31190
+ if (cmp < 0) {
31191
+ return {
31192
+ kind: "refused",
31193
+ message: `Codex app-server ${received} is older than the audited protocol ${audited}. Upgrade codex (\`npm i -g @openai/codex\`) \u2014 frizz sends parameters this build may not accept.`
31194
+ };
31195
+ }
31196
+ return {
31197
+ kind: "ahead",
31198
+ message: `Codex app-server ${received} is NEWER than frizz's audited protocol ${audited} \u2014 running anyway (the protocol is additive and unknown fields are ignored). If Codex threads misbehave, re-audit and re-pin. Run \`npm test -- codex-protocol-conformance\` to check frizz's params against this binary's own schema.`
31199
+ };
31200
+ }
31201
+ function isCodexSandboxMode(value) {
31202
+ return typeof value === "string" && CODEX_SANDBOX_MODES.includes(value);
31203
+ }
31204
+ function codexSandboxPolicy(mode) {
31205
+ switch (mode) {
31206
+ case "read-only":
31207
+ return { type: "readOnly", networkAccess: false };
31208
+ case "danger-full-access":
31209
+ return { type: "dangerFullAccess" };
31210
+ default:
31211
+ return {
31212
+ type: "workspaceWrite",
31213
+ writableRoots: [],
31214
+ networkAccess: false,
31215
+ excludeTmpdirEnvVar: false,
31216
+ excludeSlashTmp: false
31217
+ };
31218
+ }
31219
+ }
31220
+ function codexSandboxModeOfPolicy(policy) {
31221
+ switch (policy?.type) {
31222
+ case "readOnly":
31223
+ return "read-only";
31224
+ case "workspaceWrite":
31225
+ return "workspace-write";
31226
+ case "dangerFullAccess":
31227
+ return "danger-full-access";
31228
+ default:
31229
+ return void 0;
31230
+ }
31231
+ }
31232
+ function codexAppServerEnvironment(source = process.env) {
31233
+ return inheritWorkerEnvironment(source);
31234
+ }
31235
+ function checkedBindingRow(raw2) {
31236
+ const parsed = BindingRowSchema.safeParse(raw2);
31237
+ if (!parsed.success) throw new InteractionStoreError("corrupt-journal", "Codex app-server session binding is corrupt");
31238
+ return parsed.data;
31239
+ }
31240
+ function effectiveResumeSandbox(rawResponse) {
31241
+ const parsed = ThreadResumeSandbox.safeParse(rawResponse);
31242
+ return parsed.success ? codexSandboxModeOfPolicy(parsed.data.sandbox) : void 0;
31243
+ }
31244
+ function turnKey(row) {
31245
+ return `${row.thread_slug}\0${row.frizz_session_id}\0${row.connection_epoch}`;
31246
+ }
31247
+ function bindingFromRow(row) {
31248
+ return {
31249
+ threadSlug: row.thread_slug,
31250
+ sessionId: row.frizz_session_id,
31251
+ codexThreadId: row.codex_thread_id,
31252
+ codexSessionId: row.codex_session_id,
31253
+ sessionEpoch: row.session_epoch,
31254
+ capabilityRevision: row.capability_revision,
31255
+ connectionEpoch: row.connection_epoch,
31256
+ currentTurnId: row.current_turn_id,
31257
+ cwd: row.cwd,
31258
+ ephemeral: row.ephemeral === 1,
31259
+ state: row.state,
31260
+ ...isCodexSandboxMode(row.sandbox) ? { sandbox: row.sandbox } : {}
31261
+ };
31262
+ }
31263
+ function cleanText(raw2, maxChars, fallback) {
31264
+ if (!raw2) return fallback;
31265
+ const cleaned = redactDisplaySecrets(
31266
+ raw2.replace(/[\p{Cf}\p{Cs}\p{Zl}\p{Zp}\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, " ")
31267
+ ).slice(0, maxChars).trim();
31268
+ return cleaned || fallback;
31269
+ }
31270
+ function visibleControls(raw2) {
31271
+ return raw2.replace(/\r\n?/gu, "\n").replace(UNSAFE_DISPLAY_TEXT, (value) => `[U+${value.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}]`);
31272
+ }
31273
+ function redactedSecretValue(raw2) {
31274
+ const quote = raw2.length >= 2 && (raw2[0] === '"' || raw2[0] === "'") && raw2.at(-1) === raw2[0] ? raw2[0] : "";
31275
+ const content = quote ? raw2.slice(1, -1) : raw2;
31276
+ const executable = content.match(/\$\([^\r\n)]{0,4096}\)|`[^`\r\n]{0,4096}`|[<>]\([^\r\n)]{0,4096}\)/gu) ?? [];
31277
+ const replacement2 = executable.length === 0 ? DISPLAY_REDACTION : `${DISPLAY_REDACTION} [embedded executable shell syntax: ${executable.join(" ")}]`;
31278
+ return replacement2;
31279
+ }
31280
+ function redactDisplaySecrets(raw2) {
31281
+ let value = redactCredentialSyntax(raw2, { replacement: redactedSecretValue });
31282
+ value = value.replace(
31283
+ /-----BEGIN [^-\r\n]{0,80}PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]{0,80}PRIVATE KEY-----/giu,
31284
+ DISPLAY_REDACTION
31285
+ );
31286
+ value = value.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@:]+):([^\s/@]+)@/giu, `$1${DISPLAY_REDACTION}@`);
31287
+ value = value.replace(
31288
+ /\b(authorization|proxy-authorization)(\s*[:=]\s*)(bearer|basic)(\s+)([^\s'";|]+)/giu,
31289
+ (_whole, name, separator, scheme, whitespace, secret) => `${name}${separator}${scheme}${whitespace}${redactedSecretValue(secret)}`
31290
+ );
31291
+ value = value.replace(/\b(bearer|basic)(\s+)[A-Za-z0-9._~+/=-]{8,}/giu, `$1$2${DISPLAY_REDACTION}`);
31292
+ value = value.replace(
31293
+ /(^|[\s;&|([{])((?:--?|\/)(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|passwd|credential|private[-_]?key)(?:\s*=\s*|\s+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gimu,
31294
+ (_whole, boundary, flag, secret) => `${boundary}${flag}${redactedSecretValue(secret)}`
31295
+ );
31296
+ value = value.replace(
31297
+ /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*=\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gu,
31298
+ (whole, name, separator, secret) => SECRET_NAME.test(name) ? `${name}${separator}${redactedSecretValue(secret)}` : whole
31299
+ );
31300
+ value = value.replace(
31301
+ /(^|[\s;&|([{])((?:--?|\/)(?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|passwd|credential|private[-_]?key)(?:\s*=\s*|\s+))(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gimu,
31302
+ `$1$2${DISPLAY_REDACTION}`
31303
+ );
31304
+ value = value.replace(
31305
+ /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*=\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s;&|]+)/gu,
31306
+ (whole, name, separator) => SECRET_NAME.test(name) ? `${name}${separator}${DISPLAY_REDACTION}` : whole
31307
+ );
31308
+ value = value.replace(
31309
+ /(["']?)([A-Za-z_][A-Za-z0-9_.-]{0,127})\1(\s*:\s*)(?:"[^"\r\n]*"|'[^'\r\n]*')/gu,
31310
+ (whole, quote, name, separator) => SECRET_NAME.test(name) ? `${quote}${name}${quote}${separator}"${DISPLAY_REDACTION}"` : whole
31311
+ );
31312
+ value = value.replace(
31313
+ /\b([A-Za-z_][A-Za-z0-9_.-]{0,127})(\s*:\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;|}\]]+)/gu,
31314
+ (whole, name, separator) => SECRET_NAME.test(name) ? `${name}${separator}${DISPLAY_REDACTION}` : whole
31315
+ );
31316
+ value = value.replace(/\b(?:sk-[A-Za-z0-9_-]{12,}|github_pat_[A-Za-z0-9_]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,})\b/gu, DISPLAY_REDACTION);
31317
+ value = value.replace(/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu, DISPLAY_REDACTION);
31318
+ value = value.replace(
31319
+ /(?<![A-Za-z0-9_-])(?:AIza[A-Za-z0-9_-]{20,}|glpat-[A-Za-z0-9_-]{12,}|npm_[A-Za-z0-9_-]{12,}|(?:sk|pk)_(?:live|test)_[A-Za-z0-9_-]{12,})(?![A-Za-z0-9_-])/gu,
31320
+ DISPLAY_REDACTION
31321
+ );
31322
+ value = value.replace(
31323
+ /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/gu,
31324
+ DISPLAY_REDACTION
31325
+ );
31326
+ return value;
31327
+ }
31328
+ function completeDisplayText(raw2, limits, field2) {
31329
+ if (raw2 === null || raw2 === void 0 || raw2.length === 0) return limits.fallback;
31330
+ const value = redactDisplaySecrets(visibleControls(raw2));
31331
+ if (value.split("\n").length > limits.maxLines || value.length > limits.maxChars || Buffer.byteLength(value, "utf8") > limits.maxBytes) {
31332
+ throw new RpcProtocolError(-32602, `Codex ${field2} cannot be completely represented for approval`);
31333
+ }
31334
+ return value.trim().length === 0 ? limits.fallback : value;
31335
+ }
31336
+ function displayLabel(raw2, fallback, maxChars = 1024, maxBytes = 2048, field2 = "label") {
31337
+ return completeDisplayText(
31338
+ raw2?.replace(/\r\n?|\n/gu, " \u23CE "),
31339
+ { maxChars, maxBytes, maxLines: 1, fallback },
31340
+ field2
31341
+ );
31342
+ }
31343
+ function displayDescription(raw2, fallback) {
31344
+ return completeDisplayText(raw2, { maxChars: 4e3, maxBytes: 8e3, maxLines: 256, fallback }, "approval reason");
31345
+ }
31346
+ function displayPreview(raw2, fallback) {
31347
+ return completeDisplayText(raw2, { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback }, "command");
31348
+ }
31349
+ function displayActionPreview(raw2) {
31350
+ return completeDisplayText(
31351
+ raw2,
31352
+ { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback: "Command detail unavailable" },
31353
+ "parsed command action"
31354
+ );
31355
+ }
31356
+ function displayDiff(raw2) {
31357
+ if (!raw2) return void 0;
31358
+ return completeDisplayText(
31359
+ raw2,
31360
+ { maxChars: 16e3, maxBytes: 24e3, maxLines: 256, fallback: "Diff detail unavailable" },
31361
+ "file diff"
31362
+ );
31363
+ }
31364
+ function fileSystemPathLabel(path3) {
31365
+ if (path3.type === "path") return displayLabel(path3.path, "Filesystem path unavailable", 2048, 4096);
31366
+ if (path3.type === "glob_pattern") return displayLabel(`Glob pattern: ${path3.pattern}`, "Glob pattern unavailable", 2048, 4096);
31367
+ if (path3.value.kind === "root") return "Filesystem root (/)";
31368
+ if (path3.value.kind === "minimal") return "Minimal filesystem set";
31369
+ if (path3.value.kind === "tmpdir") return "System temporary directory";
31370
+ if (path3.value.kind === "slash_tmp") return "/tmp";
31371
+ if (path3.value.kind === "project_roots") {
31372
+ return path3.value.subpath ? displayLabel(`Project roots, subpath: ${path3.value.subpath}`, "Project roots", 2048, 4096) : "Project roots";
31373
+ }
31374
+ return displayLabel(
31375
+ path3.value.subpath ? `${path3.value.path}, subpath: ${path3.value.subpath}` : path3.value.path,
31376
+ "Provider-defined filesystem path",
31377
+ 2048,
31378
+ 4096
31379
+ );
31380
+ }
31381
+ function permissionCapabilities(permissions) {
31382
+ if (!permissions) return [];
31383
+ const capabilities = [];
31384
+ if (permissions.network) capabilities.push({ kind: "network", enabled: permissions.network.enabled, hosts: [] });
31385
+ if (permissions.fileSystem) {
31386
+ const byAccess = { read: [], write: [], deny: [] };
31387
+ for (const path3 of permissions.fileSystem.read ?? []) byAccess.read.push(displayLabel(path3, "Read path unavailable", 2048, 4096));
31388
+ for (const path3 of permissions.fileSystem.write ?? []) byAccess.write.push(displayLabel(path3, "Write path unavailable", 2048, 4096));
31389
+ for (const entry of permissions.fileSystem.entries ?? []) byAccess[entry.access].push(fileSystemPathLabel(entry.path));
31390
+ for (const access of ["read", "write", "deny"]) {
31391
+ const resources = byAccess[access];
31392
+ if (resources.length > 32) {
31393
+ throw new RpcProtocolError(-32602, `Codex filesystem ${access} scope has too many resources to display completely`);
31394
+ }
31395
+ if (resources.length > 0) capabilities.push({ kind: "filesystem", access, resources });
31396
+ }
31397
+ if (permissions.fileSystem.globScanMaxDepth !== void 0) {
31398
+ capabilities.push({ kind: "glob-scan", depth: permissions.fileSystem.globScanMaxDepth });
31399
+ }
31400
+ }
31401
+ return capabilities;
31402
+ }
31403
+ function commandActions(actions) {
31404
+ if (!actions?.length) return void 0;
31405
+ if (actions.length > 16) {
31406
+ throw new RpcProtocolError(-32602, "Codex command approval has too many parsed actions to display completely");
31407
+ }
31408
+ return actions.map((action) => {
31409
+ if (action.type === "read") {
31410
+ return {
31411
+ kind: "read",
31412
+ commandPreview: displayActionPreview(action.command),
31413
+ resourceLabel: displayLabel(`${action.path} (${action.name})`, "Read target unavailable", 2048, 4096)
31414
+ };
31415
+ }
31416
+ if (action.type === "listFiles") {
31417
+ return {
31418
+ kind: "list-files",
31419
+ commandPreview: displayActionPreview(action.command),
31420
+ resourceLabel: action.path !== null ? displayLabel(action.path, "List target unavailable", 2048, 4096) : "Current working directory"
31421
+ };
31422
+ }
31423
+ if (action.type === "search") {
31424
+ return {
31425
+ kind: "search",
31426
+ commandPreview: displayActionPreview(action.command),
31427
+ ...action.path !== null ? { resourceLabel: displayLabel(action.path, "Search target unavailable", 2048, 4096) } : {},
31428
+ ...action.query !== null ? { queryLabel: displayLabel(action.query, "Search query unavailable") } : {}
31429
+ };
31430
+ }
31431
+ return { kind: "unknown", commandPreview: displayActionPreview(action.command) };
31432
+ });
31433
+ }
31434
+ function commandCapabilities(params) {
31435
+ const capabilities = permissionCapabilities(params.additionalPermissions);
31436
+ if (params.networkApprovalContext) {
31437
+ const host = displayLabel(
31438
+ `${params.networkApprovalContext.protocol}: ${params.networkApprovalContext.host}`,
31439
+ "Network host unavailable",
31440
+ 2048,
31441
+ 4096
31442
+ );
31443
+ const network = capabilities.find((capability) => capability.kind === "network");
31444
+ if (network) network.hosts = [...network.hosts, host];
31445
+ else capabilities.unshift({ kind: "network", enabled: null, hosts: [host] });
31446
+ }
31447
+ if (params.proposedExecpolicyAmendment?.length) {
31448
+ if (params.proposedExecpolicyAmendment.length > 32) {
31449
+ throw new RpcProtocolError(-32602, "Codex execution policy amendment has too many prefix tokens to display completely");
31450
+ }
31451
+ capabilities.push({
31452
+ kind: "exec-policy",
31453
+ prefixes: params.proposedExecpolicyAmendment.map((part) => displayLabel(part, "Command prefix token unavailable", 2048, 4096, "execution policy prefix"))
31454
+ });
31455
+ }
31456
+ for (const action of ["allow", "deny"]) {
31457
+ const matching = (params.proposedNetworkPolicyAmendments ?? []).filter((amendment) => amendment.action === action);
31458
+ if (matching.length > 24) {
31459
+ throw new RpcProtocolError(-32602, `Codex ${action} network policy has too many hosts to display completely`);
31460
+ }
31461
+ const hosts = matching.map((amendment) => displayLabel(amendment.host, "Network host unavailable", 2048, 4096, "network policy host"));
31462
+ if (hosts.length) capabilities.push({ kind: "network-policy", access: action, hosts });
31463
+ }
31464
+ return capabilities.length ? capabilities : void 0;
31465
+ }
31466
+ function fileChangeDisplays(changes) {
31467
+ if (changes.length > 16) {
31468
+ throw new RpcProtocolError(-32602, "Codex file approval has too many changes to display completely");
31469
+ }
31470
+ return {
31471
+ totalChanges: changes.length,
31472
+ changes: changes.map((change) => {
31473
+ const diffPreview = displayDiff(change.diff);
31474
+ const base = {
31475
+ pathLabel: displayLabel(change.path, "Affected path unavailable", 2048, 4096),
31476
+ ...diffPreview ? { diffPreview } : {}
30783
31477
  };
30784
31478
  if (change.kind.type === "add") return { ...base, operation: "create" };
30785
31479
  if (change.kind.type === "delete") return { ...base, operation: "delete" };
@@ -30816,7 +31510,7 @@ function canonicalJson2(value) {
30816
31510
  return JSON.stringify(normalize3(value));
30817
31511
  }
30818
31512
  function logicalRequestId(method, parts) {
30819
- const digest = createHash9("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
31513
+ const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
30820
31514
  return `codex-${digest}`;
30821
31515
  }
30822
31516
  function requestFingerprint(value) {
@@ -30826,7 +31520,7 @@ function requestFingerprint(value) {
30826
31520
  delete record.startedAtMs;
30827
31521
  delete record.autoResolutionMs;
30828
31522
  }
30829
- return createHash9("sha256").update(canonicalJson2(normalized)).digest("hex");
31523
+ return createHash10("sha256").update(canonicalJson2(normalized)).digest("hex");
30830
31524
  }
30831
31525
  function decision(id, semantic, label) {
30832
31526
  return { id, semantic, label };
@@ -31114,7 +31808,7 @@ function createCodexAppServerBridge(options) {
31114
31808
  function codexAppServerBridgeEnabled() {
31115
31809
  return true;
31116
31810
  }
31117
- var CODEX_APP_SERVER_PROVIDER, CODEX_NATIVE_LISTEN_FLAG, CODEX_APP_SERVER_SUPPORTED_VERSION, CODEX_APP_SERVER_PROTOCOL_REVISION, aheadVersionWarned, PROTOCOL_FINGERPRINT, CLIENT_INFO, CLIENT_CAPABILITIES, CODEX_SANDBOX_MODES, CODEX_APPROVAL_POLICY, CODEX_DEFAULT_SANDBOX, SANDBOX_CONFIRM_TIMEOUT_MS, SANDBOX_NOOP_GRACE_MS, MAX_JSONL_BYTES, MAX_INBOUND_RECORDS, MAX_INBOUND_QUEUED_BYTES, MAX_OUTBOUND_REQUESTS, MAX_STDERR_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, BRIDGE_DB_SCHEMA_VERSION, RpcIdSchema, Opaque, SafeTimestampMs, NullableString, CommandAction, NetworkApprovalContext, FileSystemSpecialPath, FileSystemPath, FileSystemSandboxEntry, NetworkPermissions, FileSystemPermissions, RequestedPermissions, NetworkPolicyAmendment, CommandApprovalParams, FileApprovalParams, PatchChangeKind, FileUpdateChange, FileChangeItem, CommandExecutionItem, ItemStartedNotification, ItemCompletedNotification, FileChangePatchUpdatedNotification, PermissionsApprovalParams, UserInputQuestion, UserInputParams, McpBase, McpElicitationParams, ResolvedNotification, RpcProtocolError, JsonlRpcConnection, BindingRowSchema, BridgeMetaRowSchema, DISPLAY_REDACTION, UNSAFE_DISPLAY_TEXT, SECRET_NAME, commandDecisionMap, InitializeResponse, ThreadResponse, TurnResponse, TurnSteerResponse, SkillsListResponse, ThreadResumeSandbox, ThreadResumeStatus, ThreadSettingsUpdated, TurnStarted, TurnCompleted, MAX_CORRELATED_FILE_ITEMS, CODEX_APP_SERVER_TABLES, CodexAppServerBridge;
31811
+ var CODEX_APP_SERVER_PROVIDER, CODEX_NATIVE_LISTEN_FLAG, CODEX_APP_SERVER_SUPPORTED_VERSION, CODEX_APP_SERVER_PROTOCOL_REVISION, aheadVersionWarned, PROTOCOL_FINGERPRINT, CLIENT_INFO, CLIENT_CAPABILITIES, CODEX_SANDBOX_MODES, CODEX_APPROVAL_POLICY, CODEX_DEFAULT_SANDBOX, SANDBOX_CONFIRM_TIMEOUT_MS, SANDBOX_NOOP_GRACE_MS, MAX_JSONL_BYTES, MAX_INBOUND_JSONL_BYTES, MAX_INBOUND_RECORDS, MAX_INBOUND_QUEUED_BYTES, MAX_OUTBOUND_REQUESTS, MAX_STDERR_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, BRIDGE_DB_SCHEMA_VERSION, RpcIdSchema, Opaque, SafeTimestampMs, NullableString, CommandAction, NetworkApprovalContext, FileSystemSpecialPath, FileSystemPath, FileSystemSandboxEntry, NetworkPermissions, FileSystemPermissions, RequestedPermissions, NetworkPolicyAmendment, CommandApprovalParams, FileApprovalParams, PatchChangeKind, FileUpdateChange, FileChangeItem, CommandExecutionItem, ItemStartedNotification, ItemCompletedNotification, FileChangePatchUpdatedNotification, PermissionsApprovalParams, UserInputQuestion, UserInputParams, McpBase, McpElicitationParams, ResolvedNotification, RpcProtocolError, JsonlRpcConnection, BindingRowSchema, BridgeMetaRowSchema, DISPLAY_REDACTION, UNSAFE_DISPLAY_TEXT, SECRET_NAME, commandDecisionMap, InitializeResponse, ThreadResponse, TurnResponse, TurnSteerResponse, SkillsListResponse, ThreadResumeSandbox, ThreadResumeStatus, ThreadSettingsUpdated, TurnStarted, TurnCompleted, MAX_CORRELATED_FILE_ITEMS, CODEX_APP_SERVER_TABLES, CodexAppServerBridge;
31118
31812
  var init_codex_app_server = __esm({
31119
31813
  "packages/server/src/backend/codex-app-server.ts"() {
31120
31814
  "use strict";
@@ -31126,15 +31820,16 @@ var init_codex_app_server = __esm({
31126
31820
  init_credential_redaction();
31127
31821
  init_codex_app_server_host();
31128
31822
  init_codex_app_server_native();
31823
+ init_auth_status();
31129
31824
  init_codex_mcp();
31130
31825
  init_logging();
31131
31826
  CODEX_APP_SERVER_PROVIDER = "codex-app-server";
31132
31827
  CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
31133
- CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.2";
31828
+ CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.4";
31134
31829
  CODEX_APP_SERVER_PROTOCOL_REVISION = Object.freeze({
31135
31830
  packageVersion: CODEX_APP_SERVER_SUPPORTED_VERSION,
31136
- sourceTag: "rust-v0.153.2",
31137
- sourceCommit: "657a993cbee87acf52d14b758ce49dbd46d1b8eb"
31831
+ sourceTag: "rust-v0.153.4",
31832
+ sourceCommit: "3d2ee51ca2d5db578f328aa75e20aa22c0197c9a"
31138
31833
  });
31139
31834
  PROTOCOL_FINGERPRINT = [
31140
31835
  CODEX_APP_SERVER_PROTOCOL_REVISION.sourceTag,
@@ -31145,7 +31840,22 @@ var init_codex_app_server = __esm({
31145
31840
  CLIENT_CAPABILITIES = Object.freeze({
31146
31841
  experimentalApi: true,
31147
31842
  requestAttestation: false,
31148
- mcpServerOpenaiFormElicitation: false
31843
+ mcpServerOpenaiFormElicitation: false,
31844
+ // Unused cumulative diff snapshots caused project-wide disconnects under the old 256 KiB frame
31845
+ // limit. Suppress them at the producer regardless of the inbound budget; file approvals use
31846
+ // item/fileChange/*, not these snapshots. Native listeners negotiate on every attach.
31847
+ optOutNotificationMethods: [
31848
+ "turn/diff/updated",
31849
+ // Prose, reasoning, output and usage are read from the rollout, not this control connection.
31850
+ // A replay/burst of these used to consume the 256-record queue before lifecycle events drained.
31851
+ "item/agentMessage/delta",
31852
+ "item/reasoning/summaryTextDelta",
31853
+ "item/reasoning/summaryPartAdded",
31854
+ "item/reasoning/textDelta",
31855
+ "item/commandExecution/outputDelta",
31856
+ "thread/tokenUsage/updated",
31857
+ "account/rateLimits/updated"
31858
+ ]
31149
31859
  });
31150
31860
  CODEX_SANDBOX_MODES = Object.freeze([
31151
31861
  "read-only",
@@ -31157,8 +31867,9 @@ var init_codex_app_server = __esm({
31157
31867
  SANDBOX_CONFIRM_TIMEOUT_MS = 8e3;
31158
31868
  SANDBOX_NOOP_GRACE_MS = 750;
31159
31869
  MAX_JSONL_BYTES = 256 * 1024;
31870
+ MAX_INBOUND_JSONL_BYTES = 8 * 1024 * 1024;
31160
31871
  MAX_INBOUND_RECORDS = 256;
31161
- MAX_INBOUND_QUEUED_BYTES = MAX_JSONL_BYTES * 2;
31872
+ MAX_INBOUND_QUEUED_BYTES = MAX_INBOUND_JSONL_BYTES * 2;
31162
31873
  MAX_OUTBOUND_REQUESTS = 128;
31163
31874
  MAX_STDERR_BYTES = 16 * 1024;
31164
31875
  DEFAULT_REQUEST_TIMEOUT_MS = 2e4;
@@ -31443,17 +32154,18 @@ var init_codex_app_server = __esm({
31443
32154
  consume(chunk) {
31444
32155
  if (this.closed) return;
31445
32156
  this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
31446
- if (Buffer.byteLength(this.buffer, "utf8") > MAX_JSONL_BYTES * 2) {
31447
- this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
31448
- return;
31449
- }
31450
32157
  while (true) {
31451
32158
  const newline = this.buffer.indexOf("\n");
31452
- if (newline < 0) return;
32159
+ if (newline < 0) {
32160
+ if (Buffer.byteLength(this.buffer, "utf8") > MAX_INBOUND_JSONL_BYTES) {
32161
+ this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
32162
+ }
32163
+ return;
32164
+ }
31453
32165
  const line = this.buffer.slice(0, newline);
31454
32166
  this.buffer = this.buffer.slice(newline + 1);
31455
32167
  if (line.trim().length === 0) continue;
31456
- if (Buffer.byteLength(line, "utf8") > MAX_JSONL_BYTES) {
32168
+ if (Buffer.byteLength(line, "utf8") > MAX_INBOUND_JSONL_BYTES) {
31457
32169
  this.fail("protocol", new Error("Codex app-server JSONL message exceeded its limit"));
31458
32170
  return;
31459
32171
  }
@@ -31464,6 +32176,10 @@ var init_codex_app_server = __esm({
31464
32176
  this.fail("protocol", new Error("Codex app-server emitted invalid JSONL"));
31465
32177
  return;
31466
32178
  }
32179
+ if (message !== null && typeof message === "object" && !Array.isArray(message)) {
32180
+ const envelope = message;
32181
+ if (!("id" in envelope) && !("jsonrpc" in envelope) && typeof envelope.method === "string" && CLIENT_CAPABILITIES.optOutNotificationMethods.includes(envelope.method)) continue;
32182
+ }
31467
32183
  const bytes = Buffer.byteLength(line, "utf8");
31468
32184
  if (this.inboundQueue.length >= MAX_INBOUND_RECORDS || this.inboundQueuedBytes + bytes > MAX_INBOUND_QUEUED_BYTES) {
31469
32185
  this.fail("protocol", new Error("Codex app-server inbound queue exceeded its limit"));
@@ -31567,6 +32283,7 @@ var init_codex_app_server = __esm({
31567
32283
  }
31568
32284
  fail(reason, error) {
31569
32285
  if (this.closed) return;
32286
+ if (reason === "protocol") this.diagnostic?.({ event: "protocol-failure", detail: error.message });
31570
32287
  this.closed = true;
31571
32288
  for (const pending of this.pending.values()) {
31572
32289
  clearTimeout(pending.timer);
@@ -31671,6 +32388,11 @@ var init_codex_app_server = __esm({
31671
32388
  timeoutMs;
31672
32389
  /** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
31673
32390
  daemonGeneration = "";
32391
+ /** ChatGPT account the current app-server cached when its process started. */
32392
+ processAuthAccountId;
32393
+ /** Serializes account-driven replacement across concurrent session/turn starts. */
32394
+ authReplacement = null;
32395
+ turnIdleWaiters = /* @__PURE__ */ new Set();
31674
32396
  /**
31675
32397
  * The handshake a FRESHLY FORKED daemon already gave us and we already rejected — i.e. what the
31676
32398
  * codex binary on disk actually reports. Once we have heard it from a new process there is nothing
@@ -31747,7 +32469,7 @@ var init_codex_app_server = __esm({
31747
32469
  if (this.scope.prepare("SELECT 1 FROM codex_app_server_session WHERE project_id = @project_id AND (thread_slug = ? OR frizz_session_id = ?)").get(input.threadSlug, input.sessionId)) {
31748
32470
  throw new Error("Codex app-server thread slug or session id is already bound");
31749
32471
  }
31750
- const connection = await this.ensureConnected();
32472
+ const connection = await this.ensureCurrentAuth();
31751
32473
  const ephemeral = input.ephemeral ?? true;
31752
32474
  const startedSandbox = input.permissions ? null : input.sandbox ?? "read-only";
31753
32475
  const response = ThreadResponse.parse(await connection.request("thread/start", {
@@ -31892,7 +32614,7 @@ var init_codex_app_server = __esm({
31892
32614
  const releaseOperation = this.beginOperation();
31893
32615
  this.startingTurns.add(startKey);
31894
32616
  try {
31895
- const connection = await this.ensureConnected();
32617
+ const connection = await this.ensureCurrentAuth();
31896
32618
  let binding = this.bindingForScope(input.threadSlug, input.sessionId);
31897
32619
  if (!binding) throw new Error("Codex app-server turn requires a bridge-owned session");
31898
32620
  if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
@@ -31926,6 +32648,7 @@ var init_codex_app_server = __esm({
31926
32648
  return { turnId: response.turn.id };
31927
32649
  } finally {
31928
32650
  this.pendingTurnStarts.delete(pendingKey);
32651
+ this.notifyTurnIdleWaiters();
31929
32652
  }
31930
32653
  } finally {
31931
32654
  this.startingTurns.delete(startKey);
@@ -32325,6 +33048,8 @@ var init_codex_app_server = __esm({
32325
33048
  this.openingConnection.close();
32326
33049
  this.openingConnection = null;
32327
33050
  }
33051
+ this.processAuthAccountId = void 0;
33052
+ this.notifyTurnIdleWaiters();
32328
33053
  }
32329
33054
  }
32330
33055
  bindingForScope(threadSlug, sessionId) {
@@ -32350,11 +33075,61 @@ var init_codex_app_server = __esm({
32350
33075
  this.connecting = null;
32351
33076
  }
32352
33077
  }
33078
+ /**
33079
+ * Codex intentionally snapshots auth.json once per AuthManager. A detached app-server therefore
33080
+ * keeps using the account it started with even after ccbroker atomically switches auth.json. Before
33081
+ * starting new model work, replace that stale process — but only after every turn already running
33082
+ * in the shared per-project process has reached its own completion event.
33083
+ */
33084
+ async ensureCurrentAuth() {
33085
+ for (; ; ) {
33086
+ const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
33087
+ const connection = await this.ensureConnected();
33088
+ if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
33089
+ if (!this.authReplacement) {
33090
+ const replacement2 = this.replaceAppServerForAuthChange();
33091
+ this.authReplacement = replacement2;
33092
+ void replacement2.finally(() => {
33093
+ if (this.authReplacement === replacement2) this.authReplacement = null;
33094
+ }).catch(() => void 0);
33095
+ }
33096
+ await this.authReplacement;
33097
+ }
33098
+ }
33099
+ hasInFlightTurns() {
33100
+ if (this.pendingTurnStarts.size > 0) return true;
33101
+ return Boolean(this.scope.prepare(`
33102
+ SELECT 1 FROM codex_app_server_session
33103
+ WHERE project_id = @project_id AND state = 'active' AND connection_epoch = ? AND current_turn_id IS NOT NULL
33104
+ LIMIT 1
33105
+ `).get(this.connectionEpoch));
33106
+ }
33107
+ whenTurnsIdle() {
33108
+ if (!this.hasInFlightTurns()) return Promise.resolve();
33109
+ return new Promise((resolve12) => {
33110
+ this.turnIdleWaiters.add(resolve12);
33111
+ if (!this.hasInFlightTurns() && this.turnIdleWaiters.delete(resolve12)) resolve12();
33112
+ });
33113
+ }
33114
+ notifyTurnIdleWaiters() {
33115
+ if (this.dbReleased || this.hasInFlightTurns()) return;
33116
+ for (const resolve12 of this.turnIdleWaiters) resolve12();
33117
+ this.turnIdleWaiters.clear();
33118
+ }
33119
+ async replaceAppServerForAuthChange() {
33120
+ while (this.hasInFlightTurns()) await this.whenTurnsIdle();
33121
+ if (this.closed || this.dbReleased) throw new Error("Codex app-server bridge is closed");
33122
+ this.disconnectOwnedProcess();
33123
+ await stopCodexAppServerDaemon(this.options.stateDir ?? this.options.projectDir, this.options.projectId);
33124
+ await this.ensureConnected();
33125
+ this.options.diagnostic?.({ event: "auth-account-refreshed" });
33126
+ }
32353
33127
  /**
32354
33128
  * Attach to an app-server and negotiate.
32355
33129
  *
32356
- * `refork` is the version-skew recovery, and it is the ONLY thing in frizz that ever ends a Codex
32357
- * daemon's life. The daemon performs `initialize` once and caches the answer for as long as it
33130
+ * `refork` is the version-skew recovery. Alongside the idle-boundary auth refresh above, it is one
33131
+ * of only two things in frizz that ever ends a Codex daemon's life. The daemon performs `initialize`
33132
+ * once and caches the answer for as long as it
32358
33133
  * lives (up to six hours idle, unbounded while a client keeps reattaching). Bump
32359
33134
  * CODEX_APP_SERVER_SUPPORTED_VERSION and Update & Restart — the ordinary upgrade path — and the
32360
33135
  * surviving daemon happily serves the STALE userAgent to every new generation, so the gate below
@@ -32364,6 +33139,7 @@ var init_codex_app_server = __esm({
32364
33139
  * for why a genuinely unsupported codex still fails loudly instead of reforking in a loop.
32365
33140
  */
32366
33141
  async connect(refork = false) {
33142
+ const authAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
32367
33143
  const attachment = await this.host({
32368
33144
  projectId: this.options.projectId,
32369
33145
  stateDir: this.options.stateDir ?? this.options.projectDir,
@@ -32374,7 +33150,8 @@ var init_codex_app_server = __esm({
32374
33150
  env: codexAppServerEnvironment(),
32375
33151
  clientInfo: CLIENT_INFO,
32376
33152
  capabilities: CLIENT_CAPABILITIES,
32377
- frizzMcp: this.options.frizzMcp
33153
+ frizzMcp: this.options.frizzMcp,
33154
+ authAccountId
32378
33155
  });
32379
33156
  const child = attachment.process;
32380
33157
  let connection;
@@ -32437,6 +33214,7 @@ var init_codex_app_server = __esm({
32437
33214
  this.connectionEpoch = negotiated.connectionEpoch;
32438
33215
  this.capabilityRevision = negotiated.capabilityRevision;
32439
33216
  this.daemonGeneration = attachment.generation;
33217
+ this.processAuthAccountId = attachment.authAccountId;
32440
33218
  if (this.closed) throw new Error("Codex app-server bridge closed during negotiation");
32441
33219
  this.connection = connection;
32442
33220
  this.openingConnection = null;
@@ -32476,6 +33254,7 @@ var init_codex_app_server = __esm({
32476
33254
  if (this.connection !== connection) return;
32477
33255
  const epoch = this.connectionEpoch;
32478
33256
  this.connection = null;
33257
+ this.processAuthAccountId = void 0;
32479
33258
  this.forgetCorrelatedFileItems();
32480
33259
  this.releaseSettingsWaiters();
32481
33260
  if (this.closed || this.dbReleased) return;
@@ -32483,6 +33262,7 @@ var init_codex_app_server = __esm({
32483
33262
  UPDATE codex_app_server_session SET state = 'detached', updated_at = ?
32484
33263
  WHERE project_id = @project_id AND connection_epoch = ? AND state = 'active'
32485
33264
  `).run(this.now().toISOString(), epoch);
33265
+ this.notifyTurnIdleWaiters();
32486
33266
  this.options.diagnostic?.({ event: "disconnected", connectionEpoch: epoch, reason });
32487
33267
  }
32488
33268
  /**
@@ -33340,6 +34120,7 @@ var init_codex_app_server = __esm({
33340
34120
  UPDATE codex_app_server_session SET current_turn_id = NULL, auto_resume_count = 0, updated_at = ?
33341
34121
  WHERE project_id = @project_id AND codex_thread_id = ? AND connection_epoch = ? AND current_turn_id = ?
33342
34122
  `).run(this.now().toISOString(), parsed.data.threadId, this.connectionEpoch, parsed.data.turn.id);
34123
+ this.notifyTurnIdleWaiters();
33343
34124
  }
33344
34125
  }
33345
34126
  providerResponse(record, delivery2, input) {
@@ -33413,11 +34194,11 @@ var init_codex_app_server = __esm({
33413
34194
  });
33414
34195
 
33415
34196
  // packages/server/src/backend/codex-quota.ts
33416
- import { join as join22 } from "node:path";
33417
- import { readdirSync as readdirSync7, statSync as statSync10, openSync as openSync12, readSync as readSync6, fstatSync, closeSync as closeSync11 } from "node:fs";
34197
+ import { join as join24 } from "node:path";
34198
+ import { readdirSync as readdirSync7, statSync as statSync11, openSync as openSync12, readSync as readSync6, fstatSync, closeSync as closeSync11 } from "node:fs";
33418
34199
  import { spawn as spawn4 } from "node:child_process";
33419
34200
  function sessionsDir2(codexHome) {
33420
- return join22(codexHome, "sessions");
34201
+ return join24(codexHome, "sessions");
33421
34202
  }
33422
34203
  function newestRollouts(dir, out, budget) {
33423
34204
  if (budget.n <= 0) return;
@@ -33431,610 +34212,206 @@ function newestRollouts(dir, out, budget) {
33431
34212
  const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
33432
34213
  for (const d of dirs) {
33433
34214
  if (budget.n <= 0) return;
33434
- newestRollouts(join22(dir, d), out, budget);
34215
+ newestRollouts(join24(dir, d), out, budget);
33435
34216
  }
33436
34217
  for (const f of files) {
33437
34218
  if (budget.n <= 0) return;
33438
- out.push(join22(dir, f));
34219
+ out.push(join24(dir, f));
33439
34220
  budget.n--;
33440
34221
  }
33441
34222
  }
33442
- function readTail(path3) {
33443
- let fd;
33444
- try {
33445
- fd = openSync12(path3, "r");
33446
- const size = fstatSync(fd).size;
33447
- const start = Math.max(0, size - TAIL_BYTES);
33448
- const len = size - start;
33449
- const buf = Buffer.allocUnsafe(len);
33450
- let read = 0;
33451
- while (read < len) {
33452
- const n = readSync6(fd, buf, read, len - read, start + read);
33453
- if (n <= 0) break;
33454
- read += n;
33455
- }
33456
- return buf.toString("utf8", 0, read);
33457
- } catch {
33458
- return "";
33459
- } finally {
33460
- if (fd !== void 0) {
33461
- try {
33462
- closeSync11(fd);
33463
- } catch {
33464
- }
33465
- }
33466
- }
33467
- }
33468
- function makeWindow(used, minutes, resetsAt) {
33469
- if (used === void 0) return void 0;
33470
- const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
33471
- const label = key === "weekly" ? "Weekly" : key;
33472
- return { key, label, usedPercent: used, resetsAt };
33473
- }
33474
- function toWindow(raw2) {
33475
- if (!raw2 || typeof raw2 !== "object") return void 0;
33476
- const w = raw2;
33477
- return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
33478
- }
33479
- function parseCodexQuotaFromRollout(raw2) {
33480
- const lines = raw2.split("\n");
33481
- for (let i = lines.length - 1; i >= 0; i--) {
33482
- const line = lines[i];
33483
- if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
33484
- let obj;
33485
- try {
33486
- obj = JSON.parse(line);
33487
- } catch {
33488
- continue;
33489
- }
33490
- if (!obj || typeof obj !== "object") continue;
33491
- const top = obj;
33492
- const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
33493
- const rl = top.rate_limits ?? payload?.rate_limits;
33494
- if (!rl || typeof rl !== "object") continue;
33495
- const r = rl;
33496
- const windows = [toWindow(r.primary), toWindow(r.secondary)].filter((x) => x !== void 0);
33497
- if (windows.length === 0) continue;
33498
- const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
33499
- return { status: "ok", planType, windows };
33500
- }
33501
- return void 0;
33502
- }
33503
- function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
33504
- let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
33505
- try {
33506
- const rollouts = [];
33507
- newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
33508
- const byMtime = rollouts.map((path3) => {
33509
- try {
33510
- return { path: path3, mtimeMs: statSync10(path3).mtimeMs };
33511
- } catch {
33512
- return void 0;
33513
- }
33514
- }).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
33515
- for (const { path: path3 } of byMtime) {
33516
- const parsed = parseCodexQuotaFromRollout(readTail(path3));
33517
- if (parsed) {
33518
- quota = parsed;
33519
- break;
33520
- }
33521
- }
33522
- } catch {
33523
- quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
33524
- }
33525
- return quota;
33526
- }
33527
- function toLiveWindow(raw2) {
33528
- if (!raw2 || typeof raw2 !== "object") return void 0;
33529
- const w = raw2;
33530
- return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
33531
- }
33532
- function parseCodexQuotaFromRateLimits(result) {
33533
- if (!result || typeof result !== "object") return void 0;
33534
- const rl = result.rateLimits;
33535
- if (!rl || typeof rl !== "object") return void 0;
33536
- const r = rl;
33537
- const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
33538
- if (windows.length === 0) return void 0;
33539
- const planType = typeof r.planType === "string" ? r.planType : void 0;
33540
- return { status: "ok", planType, windows };
33541
- }
33542
- function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
33543
- return new Promise((resolve12) => {
33544
- let child;
33545
- try {
33546
- child = spawn4(codexBin, ["app-server"], {
33547
- stdio: ["pipe", "pipe", "ignore"],
33548
- env: { ...process.env, CODEX_HOME: codexHome }
33549
- });
33550
- } catch {
33551
- resolve12(void 0);
33552
- return;
33553
- }
33554
- let settled = false;
33555
- const finish = (quota) => {
33556
- if (settled) return;
33557
- settled = true;
33558
- clearTimeout(timer);
33559
- try {
33560
- child.kill("SIGKILL");
33561
- } catch {
33562
- }
33563
- resolve12(quota);
33564
- };
33565
- const timer = setTimeout(() => finish(void 0), timeoutMs);
33566
- const send = (msg) => {
33567
- try {
33568
- child.stdin?.write(JSON.stringify(msg) + "\n");
33569
- } catch {
33570
- finish(void 0);
33571
- }
33572
- };
33573
- child.on("error", () => finish(void 0));
33574
- child.on("exit", () => finish(void 0));
33575
- let buf = "";
33576
- child.stdout?.on("data", (chunk) => {
33577
- buf += chunk.toString("utf8");
33578
- if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
33579
- let nl;
33580
- while ((nl = buf.indexOf("\n")) >= 0) {
33581
- const line = buf.slice(0, nl);
33582
- buf = buf.slice(nl + 1);
33583
- if (!line.trim()) continue;
33584
- let msg;
33585
- try {
33586
- msg = JSON.parse(line);
33587
- } catch {
33588
- continue;
33589
- }
33590
- if (msg.id === 1) {
33591
- send({ jsonrpc: "2.0", method: "initialized" });
33592
- send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
33593
- } else if (msg.id === 2) {
33594
- finish(parseCodexQuotaFromRateLimits(msg.result));
33595
- }
33596
- }
33597
- });
33598
- send({
33599
- jsonrpc: "2.0",
33600
- id: 1,
33601
- method: "initialize",
33602
- params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
33603
- });
33604
- });
33605
- }
33606
- async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
33607
- const now = Date.now();
33608
- const hit = memo2.get(codexHome);
33609
- if (hit && now - hit.at < TTL_MS2) return hit.quota;
33610
- let quota;
33611
- try {
33612
- quota = await queryCodexRateLimits(codexHome, codexBin);
33613
- } catch {
33614
- quota = void 0;
33615
- }
33616
- if (!quota) {
33617
- const fallback = readCodexQuotaFromRollouts(codexHome);
33618
- quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
33619
- }
33620
- memo2.set(codexHome, { at: now, quota });
33621
- return quota;
33622
- }
33623
- var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
33624
- var init_codex_quota = __esm({
33625
- "packages/server/src/backend/codex-quota.ts"() {
33626
- "use strict";
33627
- init_codex();
33628
- init_codex_app_server();
33629
- descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
33630
- TAIL_BYTES = 512 * 1024;
33631
- num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
33632
- TTL_MS2 = 2e4;
33633
- memo2 = /* @__PURE__ */ new Map();
33634
- }
33635
- });
33636
-
33637
- // packages/server/src/backend/claude-quota.ts
33638
- import { join as join23 } from "node:path";
33639
- import { homedir as homedir9, platform } from "node:os";
33640
- import { createHash as createHash10 } from "node:crypto";
33641
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
33642
- import { execFile as execFile3 } from "node:child_process";
33643
- import { promisify as promisify3 } from "node:util";
33644
- function claudeConfigDir() {
33645
- const override = process.env.CLAUDE_CONFIG_DIR;
33646
- return override && override.trim() ? override : join23(homedir9(), ".claude");
33647
- }
33648
- function tokenFromCredentialsJson(raw2) {
33649
- let doc;
33650
- try {
33651
- doc = JSON.parse(raw2);
33652
- } catch {
33653
- return void 0;
33654
- }
33655
- if (!doc || typeof doc !== "object") return void 0;
33656
- const root = doc;
33657
- const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
33658
- const token = oauth.accessToken ?? oauth.access_token;
33659
- return typeof token === "string" && token ? token : void 0;
33660
- }
33661
- async function readKeychainToken() {
33662
- if (platform() !== "darwin") return void 0;
33663
- try {
33664
- const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
33665
- encoding: "utf8",
33666
- timeout: 4e3
33667
- });
33668
- return tokenFromCredentialsJson(String(stdout).trim());
33669
- } catch {
33670
- return void 0;
33671
- }
33672
- }
33673
- async function readAccessToken(configDir) {
33674
- try {
33675
- const fromFile = tokenFromCredentialsJson(await readFile(join23(configDir, ".credentials.json"), "utf8"));
33676
- if (fromFile) return fromFile;
33677
- } catch {
33678
- }
33679
- return readKeychainToken();
33680
- }
33681
- function isoToUnix(value) {
33682
- const ms = typeof value === "string" ? Date.parse(value) : NaN;
33683
- return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
33684
- }
33685
- function slugify2(name) {
33686
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
33687
- }
33688
- function toWindow2(raw2, key, label) {
33689
- if (!raw2 || typeof raw2 !== "object") return void 0;
33690
- const w = raw2;
33691
- const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
33692
- if (util2 === void 0) return void 0;
33693
- const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
33694
- return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
33695
- }
33696
- function windowFromLimit(raw2) {
33697
- if (!raw2 || typeof raw2 !== "object") return void 0;
33698
- const l = raw2;
33699
- const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
33700
- if (percent === void 0) return void 0;
33701
- const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
33702
- const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
33703
- const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
33704
- const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
33705
- let key;
33706
- let label;
33707
- if (group === "session") {
33708
- key = "5h";
33709
- label = "5h";
33710
- } else if (group === "weekly") {
33711
- key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
33712
- label = scopeName ? `${scopeName} wk` : "Weekly";
33713
- } else if (group) {
33714
- key = slugify2(group);
33715
- label = scopeName ? `${scopeName} ${group}` : group;
33716
- } else {
33717
- return void 0;
33718
- }
33719
- const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
33720
- return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
33721
- }
33722
- function parseClaudeUsage(body, planType) {
33723
- if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
33724
- const b = body;
33725
- const windows = [];
33726
- const seen = /* @__PURE__ */ new Set();
33727
- const add = (w) => {
33728
- if (w && !seen.has(w.key)) {
33729
- seen.add(w.key);
33730
- windows.push(w);
33731
- }
33732
- };
33733
- if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
33734
- add(toWindow2(b.five_hour, "5h", "5h"));
33735
- add(toWindow2(b.seven_day, "weekly", "Weekly"));
33736
- add(toWindow2(b.seven_day_opus, "weekly-opus", "Opus wk"));
33737
- add(toWindow2(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
33738
- if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
33739
- return { status: "ok", planType, windows };
33740
- }
33741
- function cachePaths(cacheDir, configDir) {
33742
- const profile = createHash10("sha256").update(configDir).digest("hex").slice(0, 12);
33743
- return {
33744
- data: join23(cacheDir, `claude-${profile}.json`),
33745
- lock: join23(cacheDir, `claude-${profile}.lock`)
33746
- };
33747
- }
33748
- async function readShared(path3) {
33749
- try {
33750
- const parsed = JSON.parse(await readFile(path3, "utf8"));
33751
- if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
33752
- return parsed;
33753
- } catch {
33754
- return void 0;
33755
- }
33756
- }
33757
- async function writeShared(path3, value) {
33758
- const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
33759
- try {
33760
- await writeFile(tmp, JSON.stringify(value), { mode: 384 });
33761
- await rename(tmp, path3);
33762
- } finally {
33763
- await rm(tmp, { force: true }).catch(() => {
33764
- });
33765
- }
33766
- }
33767
- function delay(ms) {
33768
- return new Promise((resolve12) => setTimeout(resolve12, ms));
33769
- }
33770
- async function acquireLock(path3) {
33771
- const deadline = Date.now() + LOCK_WAIT_MS;
33772
- for (; ; ) {
33773
- try {
33774
- await mkdir(path3);
33775
- return () => rm(path3, { recursive: true, force: true });
33776
- } catch (err) {
33777
- if (err.code !== "EEXIST") throw err;
33778
- try {
33779
- if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
33780
- await rm(path3, { recursive: true, force: true });
33781
- continue;
33782
- }
33783
- } catch {
33784
- continue;
33785
- }
33786
- if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
33787
- await delay(LOCK_POLL_MS2);
33788
- }
33789
- }
33790
- }
33791
- async function runClaudeUsage(claudeBin) {
33792
- const { stdout } = await execFileAsync2(
33793
- // Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
33794
- // EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
33795
- // never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
33796
- // A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
33797
- resolveClaudeExecutableAbsolute(claudeBin),
33798
- ["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
33799
- { encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
33800
- );
33801
- return String(stdout);
33802
- }
33803
- async function refreshQuota(claudeBin, deps, now) {
33804
- const doFetch = deps.fetchImpl ?? fetch;
33805
- const readToken = deps.readToken ?? readAccessToken;
33806
- let token;
33807
- try {
33808
- token = await readToken(claudeConfigDir());
33809
- } catch {
33810
- token = void 0;
33811
- }
33812
- if (token) {
33813
- try {
33814
- const res = await doFetch(USAGE_URL, {
33815
- headers: {
33816
- Authorization: `Bearer ${token}`,
33817
- "anthropic-beta": OAUTH_BETA,
33818
- "User-Agent": USER_AGENT,
33819
- "Content-Type": "application/json"
33820
- },
33821
- signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
33822
- });
33823
- if (res.ok) {
33824
- const body = await res.json();
33825
- const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
33826
- const quota2 = parseClaudeUsage(body, planType);
33827
- if (quota2.status === "ok") return quota2;
33828
- } else if (res.status === 429 || res.status === 529) {
33829
- throw new Error(`Usage endpoint ${res.status}`);
33830
- }
33831
- } catch (err) {
33832
- if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
33833
- }
33834
- }
33835
- const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
33836
- if (quota.status !== "ok") throw new Error(quota.detail);
33837
- return quota;
33838
- }
33839
- function windowIdentity(title) {
33840
- for (const [pattern, key, label] of LINE_WINDOWS) {
33841
- if (pattern.test(title)) return [key, label];
33842
- }
33843
- const scoped = /^Current week \((.+)\)$/i.exec(title);
33844
- if (!scoped) return void 0;
33845
- const name = scoped[1].replace(/\s+only$/i, "");
33846
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
33847
- return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
33848
- }
33849
- function parseResetLabel(label, now) {
33850
- if (!label) return void 0;
33851
- const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
33852
- const parts = /^(?:([A-Za-z]{3,9})\s+(\d{1,2})(?:,?\s+(\d{4}))?)?\s*(?:at\s+)?(?:(\d{1,2})(?::(\d{2}))?\s*(am|pm))?$/i.exec(text2);
33853
- const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
33854
- const hasClock = parts?.[6] !== void 0;
33855
- if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
33856
- const date2 = new Date(now);
33857
- if (hasClock) {
33858
- let hour = Number(parts[4]) % 12;
33859
- if (parts[6].toLowerCase() === "pm") hour += 12;
33860
- date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
33861
- } else {
33862
- date2.setHours(0, 0, 0, 0);
33863
- }
33864
- if (month !== void 0) {
33865
- date2.setMonth(month, Number(parts[2]));
33866
- if (parts[3]) date2.setFullYear(Number(parts[3]));
33867
- else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
33868
- } else if (date2.getTime() <= now) {
33869
- date2.setDate(date2.getDate() + 1);
33870
- }
33871
- return Math.round(date2.getTime() / 1e3);
33872
- }
33873
- const parsed = Date.parse(text2);
33874
- if (!Number.isFinite(parsed)) return void 0;
33875
- const date = new Date(parsed);
33876
- if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
33877
- return Math.round(date.getTime() / 1e3);
33878
- }
33879
- function parseClaudeUsageOutput(stdout, now = Date.now()) {
33880
- let text2 = stdout;
33881
- try {
33882
- const envelope = JSON.parse(stdout);
33883
- if (typeof envelope.result === "string") text2 = envelope.result;
33884
- } catch {
33885
- }
33886
- const windows = [];
33887
- for (const line of text2.split(/\r?\n/)) {
33888
- const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
33889
- if (!match2) continue;
33890
- const identity = windowIdentity(match2[1]);
33891
- if (!identity) continue;
33892
- const resetsAt = parseResetLabel(match2[3], now);
33893
- windows.push({
33894
- key: identity[0],
33895
- label: identity[1],
33896
- usedPercent: Number(match2[2]),
33897
- ...resetsAt === void 0 ? {} : { resetsAt }
33898
- });
33899
- }
33900
- if (windows.length === 0) {
33901
- return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
33902
- }
33903
- return { status: "ok", windows };
33904
- }
33905
- async function tryAcquireLock2(path3) {
33906
- for (let attempt = 0; attempt < 2; attempt++) {
33907
- try {
33908
- await mkdir(path3);
33909
- return () => rm(path3, { recursive: true, force: true });
33910
- } catch (err) {
33911
- if (err.code !== "EEXIST") return void 0;
34223
+ function readTail(path3) {
34224
+ let fd;
34225
+ try {
34226
+ fd = openSync12(path3, "r");
34227
+ const size = fstatSync(fd).size;
34228
+ const start = Math.max(0, size - TAIL_BYTES);
34229
+ const len = size - start;
34230
+ const buf = Buffer.allocUnsafe(len);
34231
+ let read = 0;
34232
+ while (read < len) {
34233
+ const n = readSync6(fd, buf, read, len - read, start + read);
34234
+ if (n <= 0) break;
34235
+ read += n;
34236
+ }
34237
+ return buf.toString("utf8", 0, read);
34238
+ } catch {
34239
+ return "";
34240
+ } finally {
34241
+ if (fd !== void 0) {
33912
34242
  try {
33913
- if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
33914
- await rm(path3, { recursive: true, force: true });
34243
+ closeSync11(fd);
33915
34244
  } catch {
33916
- return void 0;
33917
34245
  }
33918
34246
  }
33919
34247
  }
33920
- return void 0;
33921
34248
  }
33922
- function refreshSharedInBackground(paths, claudeBin, deps, now) {
33923
- if (backgroundRefreshes.has(paths.data)) return;
33924
- const task = (async () => {
33925
- const release = await tryAcquireLock2(paths.lock);
33926
- if (!release) return;
34249
+ function makeWindow(used, minutes, resetsAt) {
34250
+ if (used === void 0) return void 0;
34251
+ const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
34252
+ const label = key === "weekly" ? "Weekly" : key;
34253
+ return { key, label, usedPercent: used, resetsAt };
34254
+ }
34255
+ function toWindow2(raw2) {
34256
+ if (!raw2 || typeof raw2 !== "object") return void 0;
34257
+ const w = raw2;
34258
+ return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
34259
+ }
34260
+ function parseCodexQuotaFromRollout(raw2) {
34261
+ const lines = raw2.split("\n");
34262
+ for (let i = lines.length - 1; i >= 0; i--) {
34263
+ const line = lines[i];
34264
+ if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
34265
+ let obj;
33927
34266
  try {
33928
- const quota = await refreshQuota(claudeBin, deps, now);
33929
- await writeShared(paths.data, { at: now, quota });
33930
- } finally {
33931
- await release().catch(() => {
33932
- });
34267
+ obj = JSON.parse(line);
34268
+ } catch {
34269
+ continue;
33933
34270
  }
33934
- })().catch(() => {
33935
- }).finally(() => backgroundRefreshes.delete(paths.data));
33936
- backgroundRefreshes.set(paths.data, task);
34271
+ if (!obj || typeof obj !== "object") continue;
34272
+ const top = obj;
34273
+ const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
34274
+ const rl = top.rate_limits ?? payload?.rate_limits;
34275
+ if (!rl || typeof rl !== "object") continue;
34276
+ const r = rl;
34277
+ const windows = [toWindow2(r.primary), toWindow2(r.secondary)].filter((x) => x !== void 0);
34278
+ if (windows.length === 0) continue;
34279
+ const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
34280
+ return { status: "ok", planType, windows };
34281
+ }
34282
+ return void 0;
33937
34283
  }
33938
- async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
33939
- const now = (deps.now ?? Date.now)();
33940
- const configDir = claudeConfigDir();
33941
- const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
34284
+ function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
34285
+ let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
33942
34286
  try {
33943
- await mkdir(cacheDir, { recursive: true, mode: 448 });
34287
+ const rollouts = [];
34288
+ newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
34289
+ const byMtime = rollouts.map((path3) => {
34290
+ try {
34291
+ return { path: path3, mtimeMs: statSync11(path3).mtimeMs };
34292
+ } catch {
34293
+ return void 0;
34294
+ }
34295
+ }).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
34296
+ for (const { path: path3 } of byMtime) {
34297
+ const parsed = parseCodexQuotaFromRollout(readTail(path3));
34298
+ if (parsed) {
34299
+ quota = parsed;
34300
+ break;
34301
+ }
34302
+ }
33944
34303
  } catch {
33945
- return;
34304
+ quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
33946
34305
  }
33947
- refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
34306
+ return quota;
33948
34307
  }
33949
- async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
33950
- const now = (deps.now ?? Date.now)();
33951
- const configDir = claudeConfigDir();
33952
- const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
33953
- const paths = cachePaths(cacheDir, configDir);
33954
- try {
33955
- await mkdir(cacheDir, { recursive: true, mode: 448 });
33956
- } catch {
34308
+ function toLiveWindow(raw2) {
34309
+ if (!raw2 || typeof raw2 !== "object") return void 0;
34310
+ const w = raw2;
34311
+ return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
34312
+ }
34313
+ function parseCodexQuotaFromRateLimits(result) {
34314
+ if (!result || typeof result !== "object") return void 0;
34315
+ const rl = result.rateLimits;
34316
+ if (!rl || typeof rl !== "object") return void 0;
34317
+ const r = rl;
34318
+ const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
34319
+ if (windows.length === 0) return void 0;
34320
+ const planType = typeof r.planType === "string" ? r.planType : void 0;
34321
+ return { status: "ok", planType, windows };
34322
+ }
34323
+ function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
34324
+ return new Promise((resolve12) => {
34325
+ let child;
33957
34326
  try {
33958
- return await refreshQuota(claudeBin, deps, now);
34327
+ child = spawn4(codexBin, ["app-server"], {
34328
+ stdio: ["pipe", "pipe", "ignore"],
34329
+ env: { ...process.env, CODEX_HOME: codexHome }
34330
+ });
33959
34331
  } catch {
33960
- return { status: "unavailable", windows: [], detail: "Claude usage unavailable" };
33961
- }
33962
- }
33963
- const initial = await readShared(paths.data);
33964
- if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
33965
- return initial.quota;
33966
- }
33967
- if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
33968
- refreshSharedInBackground(paths, claudeBin, deps, now);
33969
- const ageMs = now - initial.at;
33970
- if (ageMs < 10 * 6e4) return initial.quota;
33971
- return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
33972
- }
33973
- let release;
33974
- try {
33975
- release = await acquireLock(paths.lock);
33976
- const afterLock = await readShared(paths.data);
33977
- if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
33978
- const quota = await refreshQuota(claudeBin, deps, now);
33979
- await writeShared(paths.data, { at: now, quota }).catch(() => {
33980
- });
33981
- return quota;
33982
- } catch {
33983
- const fallback = await readShared(paths.data);
33984
- if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
33985
- return {
33986
- ...fallback.quota,
33987
- detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
33988
- };
34332
+ resolve12(void 0);
34333
+ return;
33989
34334
  }
33990
- const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
33991
- await writeShared(paths.data, { at: now, quota: unavailable }).catch(() => {
34335
+ let settled = false;
34336
+ const finish = (quota) => {
34337
+ if (settled) return;
34338
+ settled = true;
34339
+ clearTimeout(timer);
34340
+ try {
34341
+ child.kill("SIGKILL");
34342
+ } catch {
34343
+ }
34344
+ resolve12(quota);
34345
+ };
34346
+ const timer = setTimeout(() => finish(void 0), timeoutMs);
34347
+ const send = (msg) => {
34348
+ try {
34349
+ child.stdin?.write(JSON.stringify(msg) + "\n");
34350
+ } catch {
34351
+ finish(void 0);
34352
+ }
34353
+ };
34354
+ child.on("error", () => finish(void 0));
34355
+ child.on("exit", () => finish(void 0));
34356
+ let buf = "";
34357
+ child.stdout?.on("data", (chunk) => {
34358
+ buf += chunk.toString("utf8");
34359
+ if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
34360
+ let nl;
34361
+ while ((nl = buf.indexOf("\n")) >= 0) {
34362
+ const line = buf.slice(0, nl);
34363
+ buf = buf.slice(nl + 1);
34364
+ if (!line.trim()) continue;
34365
+ let msg;
34366
+ try {
34367
+ msg = JSON.parse(line);
34368
+ } catch {
34369
+ continue;
34370
+ }
34371
+ if (msg.id === 1) {
34372
+ send({ jsonrpc: "2.0", method: "initialized" });
34373
+ send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
34374
+ } else if (msg.id === 2) {
34375
+ finish(parseCodexQuotaFromRateLimits(msg.result));
34376
+ }
34377
+ }
33992
34378
  });
33993
- return unavailable;
33994
- } finally {
33995
- await release?.().catch(() => {
34379
+ send({
34380
+ jsonrpc: "2.0",
34381
+ id: 1,
34382
+ method: "initialize",
34383
+ params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
33996
34384
  });
34385
+ });
34386
+ }
34387
+ async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
34388
+ const now = Date.now();
34389
+ const hit = memo2.get(codexHome);
34390
+ if (hit && now - hit.at < TTL_MS2) return hit.quota;
34391
+ let quota;
34392
+ try {
34393
+ quota = await queryCodexRateLimits(codexHome, codexBin);
34394
+ } catch {
34395
+ quota = void 0;
33997
34396
  }
34397
+ if (!quota) {
34398
+ const fallback = readCodexQuotaFromRollouts(codexHome);
34399
+ quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
34400
+ }
34401
+ memo2.set(codexHome, { at: now, quota });
34402
+ return quota;
33998
34403
  }
33999
- var execFileAsync2, USAGE_URL, OAUTH_BETA, USER_AGENT, ENDPOINT_TIMEOUT_MS, OK_TTL_MS, FAIL_TTL_MS, STALE_MAX_AGE_MS, CLI_TIMEOUT_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_POLL_MS2, LINE_WINDOWS, MONTH_INDEX, backgroundRefreshes;
34000
- var init_claude_quota = __esm({
34001
- "packages/server/src/backend/claude-quota.ts"() {
34404
+ var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
34405
+ var init_codex_quota = __esm({
34406
+ "packages/server/src/backend/codex-quota.ts"() {
34002
34407
  "use strict";
34003
- init_frizz_paths();
34004
- init_claude_broker_host();
34005
- execFileAsync2 = promisify3(execFile3);
34006
- USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
34007
- OAUTH_BETA = "oauth-2025-04-20";
34008
- USER_AGENT = "claude-code/2.0.0";
34009
- ENDPOINT_TIMEOUT_MS = 5e3;
34010
- OK_TTL_MS = 2 * 6e4;
34011
- FAIL_TTL_MS = 1e4;
34012
- STALE_MAX_AGE_MS = 24 * 60 * 6e4;
34013
- CLI_TIMEOUT_MS = 3e4;
34014
- LOCK_STALE_MS = 4e4;
34015
- LOCK_WAIT_MS = 15e3;
34016
- LOCK_POLL_MS2 = 50;
34017
- LINE_WINDOWS = [
34018
- [/^Current session$/i, "5h", "5h"],
34019
- [/^Current week \(all models\)$/i, "weekly", "Weekly"],
34020
- [/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
34021
- [/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
34022
- ];
34023
- MONTH_INDEX = {
34024
- jan: 0,
34025
- feb: 1,
34026
- mar: 2,
34027
- apr: 3,
34028
- may: 4,
34029
- jun: 5,
34030
- jul: 6,
34031
- aug: 7,
34032
- sep: 8,
34033
- oct: 9,
34034
- nov: 10,
34035
- dec: 11
34036
- };
34037
- backgroundRefreshes = /* @__PURE__ */ new Map();
34408
+ init_codex();
34409
+ init_codex_app_server();
34410
+ descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
34411
+ TAIL_BYTES = 512 * 1024;
34412
+ num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
34413
+ TTL_MS2 = 2e4;
34414
+ memo2 = /* @__PURE__ */ new Map();
34038
34415
  }
34039
34416
  });
34040
34417
 
@@ -34107,10 +34484,10 @@ var init_quota = __esm({
34107
34484
  });
34108
34485
 
34109
34486
  // packages/server/src/frizz.ts
34110
- import { execFile as execFile4 } from "node:child_process";
34487
+ import { execFile as execFile5 } from "node:child_process";
34111
34488
  import { lstatSync as lstatSync2, realpathSync as realpathSync6 } from "node:fs";
34112
- import { basename as basename5, dirname as dirname9, join as join24, resolve as resolve5 } from "node:path";
34113
- import { promisify as promisify4 } from "node:util";
34489
+ import { basename as basename5, dirname as dirname9, join as join25, resolve as resolve5 } from "node:path";
34490
+ import { promisify as promisify5 } from "node:util";
34114
34491
  function frizzScriptsDir() {
34115
34492
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
34116
34493
  return resolve5(import.meta.dirname, "..", "..", "..", "board");
@@ -34118,7 +34495,7 @@ function frizzScriptsDir() {
34118
34495
  function directFrizzRoot(projectDir) {
34119
34496
  try {
34120
34497
  const projectRoot = realpathSync6(projectDir);
34121
- const path3 = join24(projectRoot, ".frizz");
34498
+ const path3 = join25(projectRoot, ".frizz");
34122
34499
  const stat2 = lstatSync2(path3);
34123
34500
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
34124
34501
  const real = realpathSync6(path3);
@@ -34132,7 +34509,7 @@ function frizzDirExists(projectDir) {
34132
34509
  }
34133
34510
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34134
34511
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
34135
- const { stdout } = await execFileP("node", [join24(scriptsDir, "index.mjs"), "--json"], {
34512
+ const { stdout } = await execFileP("node", [join25(scriptsDir, "index.mjs"), "--json"], {
34136
34513
  cwd: projectDir,
34137
34514
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
34138
34515
  maxBuffer: 32 * 1024 * 1024
@@ -34157,7 +34534,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34157
34534
  };
34158
34535
  }
34159
34536
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
34160
- await execFileP("node", [join24(scriptsDir, "thread-update.mjs"), slug, ...args], {
34537
+ await execFileP("node", [join25(scriptsDir, "thread-update.mjs"), slug, ...args], {
34161
34538
  cwd: projectDir,
34162
34539
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
34163
34540
  });
@@ -34167,7 +34544,7 @@ var init_frizz = __esm({
34167
34544
  "packages/server/src/frizz.ts"() {
34168
34545
  "use strict";
34169
34546
  init_src();
34170
- execFileP = promisify4(execFile4);
34547
+ execFileP = promisify5(execFile5);
34171
34548
  }
34172
34549
  });
34173
34550
 
@@ -34242,12 +34619,12 @@ var init_awaiting = __esm({
34242
34619
  });
34243
34620
 
34244
34621
  // packages/server/src/backend/project-mcp-servers.ts
34245
- import { readFileSync as readFileSync16 } from "node:fs";
34246
- import { homedir as homedir10 } from "node:os";
34247
- import { join as join25 } from "node:path";
34622
+ import { readFileSync as readFileSync17 } from "node:fs";
34623
+ import { homedir as homedir11 } from "node:os";
34624
+ import { join as join26 } from "node:path";
34248
34625
  function readJsonObject(path3) {
34249
34626
  try {
34250
- const parsed = JSON.parse(readFileSync16(path3, "utf8"));
34627
+ const parsed = JSON.parse(readFileSync17(path3, "utf8"));
34251
34628
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
34252
34629
  } catch {
34253
34630
  return void 0;
@@ -34338,17 +34715,17 @@ function userScopeRemoteServers(account, cwd, env) {
34338
34715
  }
34339
34716
  function projectMcpServers(cwd, options = {}) {
34340
34717
  const env = options.env ?? process.env;
34341
- const home = options.home ?? homedir10();
34718
+ const home = options.home ?? homedir11();
34342
34719
  const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
34343
- const accountFile = configOverride ? join25(configOverride, ".claude.json") : join25(home, ".claude.json");
34344
- const userSettingsFile = join25(configOverride ?? join25(home, ".claude"), "settings.json");
34720
+ const accountFile = configOverride ? join26(configOverride, ".claude.json") : join26(home, ".claude.json");
34721
+ const userSettingsFile = join26(configOverride ?? join26(home, ".claude"), "settings.json");
34345
34722
  const account = readJsonObject(accountFile);
34346
34723
  const out = userScopeRemoteServers(account, cwd, env);
34347
- const declared = objectField(readJsonObject(join25(cwd, ".mcp.json")), "mcpServers");
34724
+ const declared = objectField(readJsonObject(join26(cwd, ".mcp.json")), "mcpServers");
34348
34725
  if (!declared) return out;
34349
34726
  const approval = collectApproval([
34350
- readJsonObject(join25(cwd, ".claude", "settings.json")),
34351
- readJsonObject(join25(cwd, ".claude", "settings.local.json")),
34727
+ readJsonObject(join26(cwd, ".claude", "settings.json")),
34728
+ readJsonObject(join26(cwd, ".claude", "settings.local.json")),
34352
34729
  readJsonObject(userSettingsFile),
34353
34730
  objectField(objectField(account, "projects"), cwd)
34354
34731
  ]);
@@ -34373,16 +34750,16 @@ var init_project_mcp_servers = __esm({
34373
34750
 
34374
34751
  // packages/server/src/worker-plugin-dir.ts
34375
34752
  import { existsSync as existsSync8 } from "node:fs";
34376
- import { dirname as dirname10, join as join26 } from "node:path";
34753
+ import { dirname as dirname10, join as join27 } from "node:path";
34377
34754
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34378
34755
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
34379
34756
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
34380
- if (override && existsSync8(join26(override, ".claude-plugin", "plugin.json")))
34757
+ if (override && existsSync8(join27(override, ".claude-plugin", "plugin.json")))
34381
34758
  return override;
34382
34759
  let current = dirname10(fileURLToPath2(moduleUrl));
34383
34760
  for (; ; ) {
34384
- const candidate = join26(current, "cc-worker");
34385
- if (existsSync8(join26(candidate, ".claude-plugin", "plugin.json"))) return candidate;
34761
+ const candidate = join27(current, "cc-worker");
34762
+ if (existsSync8(join27(candidate, ".claude-plugin", "plugin.json"))) return candidate;
34386
34763
  const parent = dirname10(current);
34387
34764
  if (parent === current) return void 0;
34388
34765
  current = parent;
@@ -34795,9 +35172,10 @@ fence is answerable only while its message is on screen, so a question written i
34795
35172
  the transcript scrolls or your context is compacted, while a registration is still there tomorrow. It
34796
35173
  takes \`questions\` \u2014 several at once, each with \`question\`, \`kind\` (\`question\` = pick one,
34797
35174
  \`multi\` = pick several, no options at all = free text), an optional \`danger\`, and \`options\` each
34798
- carrying a \`label\`, a \`description\` (one line for a simple fork, or FULL MARKDOWN over several
34799
- lines \u2014 a list of consequences, a code block, the diff an option would produce, the message that would
34800
- be posted; it renders inside the option, so the human sees it BEFORE choosing), an optional
35175
+ carrying a \`label\`, a \`description\` (ONE LINE by default \u2014 name the trade-off and stop; earn more
35176
+ than that and spend it on a shape the human can SCAN, a short list, a table, a code block, the diff an
35177
+ option would produce, the message that would be posted, and never a run of one-sentence paragraphs;
35178
+ it renders inside the option, so the human sees it BEFORE choosing), an optional
34801
35179
  \`recommended\`, and optional \`followUps\`: questions that become live ONLY if that option is taken,
34802
35180
  so you can ask "and if so, which?" without asking it of somebody who said no. **REGISTERING A QUESTION DOES NOT END YOUR TURN** \u2014
34803
35181
  it carries no timeout and waits on a person, so keep doing everything that does not depend on the
@@ -35535,12 +35913,12 @@ var init_claude_broker_client = __esm({
35535
35913
  });
35536
35914
 
35537
35915
  // packages/server/src/backend/claude-permission-interactions.ts
35538
- import { homedir as homedir11 } from "node:os";
35916
+ import { homedir as homedir12 } from "node:os";
35539
35917
  function clip(text2, max) {
35540
35918
  return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
35541
35919
  }
35542
35920
  function tildePath(path3) {
35543
- const home = homedir11();
35921
+ const home = homedir12();
35544
35922
  if (!path3.startsWith(home)) return path3;
35545
35923
  const rest = path3.slice(home.length);
35546
35924
  return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
@@ -36189,208 +36567,6 @@ var init_claude_agent_broker_bridge = __esm({
36189
36567
  }
36190
36568
  });
36191
36569
 
36192
- // packages/server/src/backend/auth-status.ts
36193
- import { join as join27 } from "node:path";
36194
- import { homedir as homedir12, platform as platform2 } from "node:os";
36195
- import { readFileSync as readFileSync17, statSync as statSync11 } from "node:fs";
36196
- import { execFile as execFile5 } from "node:child_process";
36197
- import { promisify as promisify5 } from "node:util";
36198
- function claudeConfigDir2() {
36199
- const override = process.env.CLAUDE_CONFIG_DIR;
36200
- return override && override.trim() ? override : join27(homedir12(), ".claude");
36201
- }
36202
- function claudeFileState(configDir) {
36203
- let raw2;
36204
- try {
36205
- raw2 = readFileSync17(join27(configDir, ".credentials.json"), "utf8");
36206
- } catch (err) {
36207
- return err.code === "ENOENT" ? "absent" : "error";
36208
- }
36209
- return tokenFromCredentialsJson(raw2) ? "token" : "empty";
36210
- }
36211
- async function claudeKeychainState() {
36212
- if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
36213
- if (platform2() !== "darwin") return "absent";
36214
- try {
36215
- const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
36216
- encoding: "utf8",
36217
- timeout: 4e3
36218
- });
36219
- return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
36220
- } catch (err) {
36221
- const e = err;
36222
- const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
36223
- return notFound ? "absent" : "error";
36224
- }
36225
- }
36226
- async function readClaudeAuthState(configDir = claudeConfigDir2()) {
36227
- const file = claudeFileState(configDir);
36228
- if (file === "token") return "authed";
36229
- const keychain = await claudeKeychainState();
36230
- if (keychain === "token") return "authed";
36231
- if (file === "error" || keychain === "error") return "unknown";
36232
- return "signed-out";
36233
- }
36234
- function readCodexAuthState(codexHome = defaultCodexHome()) {
36235
- if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
36236
- let raw2;
36237
- try {
36238
- raw2 = readFileSync17(join27(codexHome, "auth.json"), "utf8");
36239
- } catch (err) {
36240
- return err.code === "ENOENT" ? "signed-out" : "unknown";
36241
- }
36242
- let doc;
36243
- try {
36244
- doc = JSON.parse(raw2);
36245
- } catch {
36246
- return "unknown";
36247
- }
36248
- if (!doc || typeof doc !== "object") return "unknown";
36249
- const root = doc;
36250
- const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
36251
- const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
36252
- const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
36253
- return apiKey || accessToken ? "authed" : "signed-out";
36254
- }
36255
- async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
36256
- try {
36257
- await exec(codexBin, ["--version"], { timeout: 5e3 });
36258
- return "present";
36259
- } catch (err) {
36260
- return err.code === "ENOENT" ? "missing" : "unknown";
36261
- }
36262
- }
36263
- function parseClaudeAuthStatusJson(stdout) {
36264
- const start = stdout.indexOf("{");
36265
- const end = stdout.lastIndexOf("}");
36266
- if (start === -1 || end <= start) return void 0;
36267
- try {
36268
- const doc = JSON.parse(stdout.slice(start, end + 1));
36269
- return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
36270
- } catch {
36271
- return void 0;
36272
- }
36273
- }
36274
- async function readClaudeAuthStatusCli(opts) {
36275
- try {
36276
- const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
36277
- const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
36278
- encoding: "utf8",
36279
- timeout: opts?.timeoutMs ?? 5e3,
36280
- ...opts?.cwd ? { cwd: opts.cwd } : {}
36281
- });
36282
- return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
36283
- } catch (err) {
36284
- const stdout = err.stdout;
36285
- const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
36286
- if (loggedIn === false) return "signed-out";
36287
- if (loggedIn === true) return "authed";
36288
- return "unknown";
36289
- }
36290
- }
36291
- async function readClaudePreflightAuth(opts) {
36292
- const local = await readClaudeAuthState();
36293
- if (local !== "signed-out") return local;
36294
- return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
36295
- }
36296
- function asEmail(value) {
36297
- if (typeof value !== "string") return void 0;
36298
- const email = value.trim();
36299
- return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
36300
- }
36301
- function claudeAccountFile() {
36302
- const override = process.env.CLAUDE_CONFIG_DIR;
36303
- return override && override.trim() ? join27(override, ".claude.json") : join27(homedir12(), ".claude.json");
36304
- }
36305
- function readClaudeAccountEmail(path3 = claudeAccountFile()) {
36306
- let stat2;
36307
- try {
36308
- stat2 = statSync11(path3);
36309
- } catch {
36310
- return void 0;
36311
- }
36312
- const memo3 = claudeAccountMemo;
36313
- if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
36314
- let email;
36315
- try {
36316
- const doc = JSON.parse(readFileSync17(path3, "utf8"));
36317
- email = asEmail(doc?.oauthAccount?.emailAddress);
36318
- } catch {
36319
- email = void 0;
36320
- }
36321
- claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
36322
- return email;
36323
- }
36324
- function readCodexAccountEmail(codexHome = defaultCodexHome()) {
36325
- let doc;
36326
- try {
36327
- doc = JSON.parse(readFileSync17(join27(codexHome, "auth.json"), "utf8"));
36328
- } catch {
36329
- return void 0;
36330
- }
36331
- const idToken = doc?.tokens?.id_token;
36332
- if (typeof idToken !== "string") return void 0;
36333
- const payload = idToken.split(".")[1];
36334
- if (!payload) return void 0;
36335
- try {
36336
- return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
36337
- } catch {
36338
- return void 0;
36339
- }
36340
- }
36341
- function readAccountEmails() {
36342
- const emails = {};
36343
- const claude = tryRead(readClaudeAccountEmail);
36344
- const codex = tryRead(readCodexAccountEmail);
36345
- if (claude) emails.claude = claude;
36346
- if (codex) emails.codex = codex;
36347
- return emails;
36348
- }
36349
- function tryRead(read) {
36350
- try {
36351
- return read();
36352
- } catch {
36353
- return void 0;
36354
- }
36355
- }
36356
- async function readAuthSnapshot(opts) {
36357
- const [claude, codex] = await Promise.all([
36358
- readClaudeAuthState().then(async (local) => {
36359
- if (local !== "signed-out") return local;
36360
- const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
36361
- return cli === "authed" ? "authed" : local;
36362
- }).catch(() => "unknown"),
36363
- Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
36364
- ]);
36365
- const emails = readAccountEmails();
36366
- return {
36367
- claude,
36368
- codex,
36369
- emails: {
36370
- ...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
36371
- ...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
36372
- }
36373
- };
36374
- }
36375
- var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
36376
- var init_auth_status = __esm({
36377
- "packages/server/src/backend/auth-status.ts"() {
36378
- "use strict";
36379
- init_claude_quota();
36380
- init_codex();
36381
- init_claude_broker_host();
36382
- execFileAsync3 = promisify5(execFile5);
36383
- ProviderAuthRequiredError = class extends Error {
36384
- backend;
36385
- constructor(backend) {
36386
- super(`AUTH_REQUIRED:${backend}`);
36387
- this.name = "ProviderAuthRequiredError";
36388
- this.backend = backend;
36389
- }
36390
- };
36391
- }
36392
- });
36393
-
36394
36570
  // packages/server/src/dispatch.ts
36395
36571
  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";
36396
36572
  import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
@@ -36587,6 +36763,10 @@ function workerDispatchPermission(kind, settings) {
36587
36763
  if (kind === "claude" && settings.permissionMode === "bypassPermissions") return "bypassPermissions";
36588
36764
  return WORKER_DISPATCH_PERMISSION[kind];
36589
36765
  }
36766
+ function coldResumePermission(row, settings) {
36767
+ const saved = PermissionMode.safeParse(row.permission_mode);
36768
+ return saved.success ? saved.data : workerDispatchPermission("claude", settings);
36769
+ }
36590
36770
  function effectivePermissionMode(kind, mode) {
36591
36771
  if (kind === "claude") return workerPermissionMode(mode);
36592
36772
  if (mode === "plan" || mode === "bypassPermissions") return mode;
@@ -36898,7 +37078,14 @@ function createDispatcher(deps) {
36898
37078
  transcript_id: null,
36899
37079
  state: "open",
36900
37080
  meta: null,
36901
- seen_at: null
37081
+ seen_at: null,
37082
+ // The SAME launch mode a dispatched worker gets. The follow-up that triggered this promotion
37083
+ // resumes the session with `row.permission_mode`, and a row with none fell through to the
37084
+ // bridge's `"default"` — Claude's prompt-on-everything mode, which the worker's own perm-policy
37085
+ // hook then DEFERS on every call (`restrictive-mode`), so each Edit became a card. Observed
37086
+ // 2026-09-03 on a terminal session driven from the board: every frizz-sent turn ran at
37087
+ // `default` while the human's own TUI on the same transcript sat at `auto`.
37088
+ permission_mode: workerDispatchPermission(backend, deps.getSettings())
36902
37089
  });
36903
37090
  deps.storage.setBackend(slug, backend);
36904
37091
  if (backend === "codex") deps.storage.setAgentSession(slug, externalId);
@@ -38921,15 +39108,33 @@ function isHeartbeatFenceId(fenceId) {
38921
39108
  function saidDone(tele) {
38922
39109
  return tele.lastFence?.kind === "done" || tele.lastAssistantAllDone === true;
38923
39110
  }
38924
- function threadSaidDone(storage, slug, tele) {
38925
- return saidDone(tele) || registeredDoneFence(storage.getThreadDone(slug), tele.lastUserAt) !== void 0;
39111
+ function threadSaidDone(storage, slug, tele, armedAt) {
39112
+ const registered = registeredDoneFence(storage.getThreadDone(slug), tele.lastUserAt) !== void 0;
39113
+ const fenced = saidDone(tele);
39114
+ if (!registered && !fenced) return false;
39115
+ return !armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt);
39116
+ }
39117
+ function armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt) {
39118
+ const armed = armedAt ? Date.parse(armedAt) : Number.NaN;
39119
+ if (!Number.isFinite(armed)) return false;
39120
+ let signedOffAt = Number.NEGATIVE_INFINITY;
39121
+ if (registered) {
39122
+ const doneAt = storage.getThreadDone(slug)?.doneAt;
39123
+ if (doneAt !== void 0) signedOffAt = Math.max(signedOffAt, doneAt);
39124
+ }
39125
+ if (fenced) {
39126
+ const fencedAt = Date.parse(tele.lastAssistantAt ?? "");
39127
+ if (!Number.isFinite(fencedAt)) return false;
39128
+ signedOffAt = Math.max(signedOffAt, fencedAt);
39129
+ }
39130
+ return Number.isFinite(signedOffAt) && armed > signedOffAt;
38926
39131
  }
38927
39132
  function armedTimerIdsOf(storage, slug) {
38928
39133
  return new Set(storage.listThreadTimers(slug, { armedOnly: true }).map((t) => t.id));
38929
39134
  }
38930
- function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set()) {
39135
+ function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set(), armedAt) {
38931
39136
  if (tele.lastFence?.kind === "awaiting") return true;
38932
- return threadSaidDone(storage, slug, tele);
39137
+ return threadSaidDone(storage, slug, tele, armedAt);
38933
39138
  }
38934
39139
  function liveActivityOf(tele, registeredPrWatches, armedTimerIds) {
38935
39140
  const shells = /* @__PURE__ */ new Set();
@@ -39188,7 +39393,7 @@ function createScheduler(deps) {
39188
39393
  if (isStopHookFenceId(item.fenceId)) {
39189
39394
  const armed = armedRest(row);
39190
39395
  if (!armed || item.fenceId !== stopHookFenceId(armed.armedAt, tele.lastAssistantAt ?? "")) return "superseded";
39191
- if (restMessageIsSignedOff(deps.storage, item.slug, tele, registeredPrWatchesOf(deps.storage, item.slug), armedTimerIdsOf(deps.storage, item.slug))) return "superseded";
39396
+ if (restMessageIsSignedOff(deps.storage, item.slug, tele, registeredPrWatchesOf(deps.storage, item.slug), armedTimerIdsOf(deps.storage, item.slug), armed.armedAt)) return "superseded";
39192
39397
  return tele.turn === "idle" ? "current-idle" : "current-busy";
39193
39398
  }
39194
39399
  if (isCompactFenceId(item.fenceId)) {
@@ -40045,7 +40250,7 @@ ${clock}`;
40045
40250
  const armed = armedSchedule(row);
40046
40251
  if (!armed || armed.dueAtMs > nowMs) continue;
40047
40252
  const beatTele = deps.tailer.get(row.slug);
40048
- if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele)) continue;
40253
+ if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele, armed.armedAt)) continue;
40049
40254
  if (openSchedulePrompt(row.slug, row.session_id)) continue;
40050
40255
  const beat = beatIndex(row.recurring_schedule_fired_at ?? null, armed);
40051
40256
  const fenceId = heartbeatFenceId(armed.armedAt, beat);
@@ -40095,7 +40300,7 @@ ${clock}`;
40095
40300
  if (tele.lastUserAt && Date.parse(tele.lastUserAt) >= Date.parse(restedAt)) continue;
40096
40301
  if (tele.authFault) continue;
40097
40302
  if (tele.apiFault) continue;
40098
- if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug))) continue;
40303
+ if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug), armed.armedAt)) continue;
40099
40304
  if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
40100
40305
  const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
40101
40306
  if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
@@ -41472,7 +41677,7 @@ function deliverCodexWake(deps) {
41472
41677
  })();
41473
41678
  }
41474
41679
  function deliverClaudeBrokerWake(deps) {
41475
- const { bridge, slug, cwd, row, deliveryMessage, freshProcess } = deps;
41680
+ const { bridge, slug, cwd, row, settings, deliveryMessage, freshProcess } = deps;
41476
41681
  const appendSystemPrompt = [
41477
41682
  loadWorkerPrompt("claude"),
41478
41683
  scratchpadOrientation(row.session_id, "claude"),
@@ -41483,7 +41688,7 @@ function deliverClaudeBrokerWake(deps) {
41483
41688
  sessionId: row.session_id,
41484
41689
  cwd,
41485
41690
  text: deliveryMessage,
41486
- permissionMode: row.permission_mode ?? void 0,
41691
+ permissionMode: coldResumePermission(row, settings),
41487
41692
  appendSystemPrompt,
41488
41693
  model: row.model ?? void 0,
41489
41694
  effort: row.effort ?? void 0,
@@ -41835,6 +42040,7 @@ ${wakeDeliveryToken(deliveryId)}`;
41835
42040
  slug,
41836
42041
  cwd: project.dir,
41837
42042
  row,
42043
+ settings: getSettings(storage, home),
41838
42044
  deliveryMessage,
41839
42045
  // Recomputed here rather than carried on the delivery: the outbox stores a message, not a
41840
42046
  // runtime decision, and the tail is the live answer to "is this thread still behind a wall
@@ -45026,6 +45232,7 @@ function userDisplayText(text2, first) {
45026
45232
  projected = stripWakeDeliveryToken(projected);
45027
45233
  projected = stripWakeTimeHeader(projected);
45028
45234
  projected = stripHumanGapNote(projected);
45235
+ projected = stripWakeTrailer(projected);
45029
45236
  return projected === text2 ? void 0 : projected;
45030
45237
  }
45031
45238
  function userProjection(text2, first) {
@@ -45112,6 +45319,21 @@ function peerSessionQueuedKey(deliveredText, pendingKeys) {
45112
45319
  for (const key of pendingKeys) if (deliveredText.startsWith(key, PEER_SESSION_PREAMBLE.length)) return key;
45113
45320
  return void 0;
45114
45321
  }
45322
+ function claudeSidechainInstruction(text2) {
45323
+ const trimmed = text2.trim();
45324
+ if (trimmed.startsWith(CLAUDE_COORDINATOR_INSTRUCTION_PREFIX) && trimmed.endsWith(CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX)) {
45325
+ const body = trimmed.slice(
45326
+ CLAUDE_COORDINATOR_INSTRUCTION_PREFIX.length,
45327
+ -CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX.length
45328
+ ).trim();
45329
+ return body || void 0;
45330
+ }
45331
+ if (!trimmed.startsWith(CLAUDE_PEER_INSTRUCTION_PREFIX)) return void 0;
45332
+ const wrapperStart = trimmed.indexOf("<agent-message ", CLAUDE_PEER_INSTRUCTION_PREFIX.length);
45333
+ const wrapperEnd = trimmed.lastIndexOf("</agent-message>");
45334
+ if (wrapperStart < 0 || wrapperEnd < 0) return void 0;
45335
+ return parseAgentMessage(trimmed.slice(wrapperStart, wrapperEnd + "</agent-message>".length))?.body.trim() || void 0;
45336
+ }
45115
45337
  function retireStaleQueuedBubbles(messages, nowMs = Date.now()) {
45116
45338
  let stale = false;
45117
45339
  for (const m of messages) {
@@ -45300,6 +45522,19 @@ function createTranscriptFold(identityPrefix = "claude") {
45300
45522
  const i = out.indexOf(pending.message);
45301
45523
  if (i !== -1) out.splice(i, 1);
45302
45524
  }
45525
+ const instruction = rec.isSidechain === true ? claudeSidechainInstruction(metaText) : void 0;
45526
+ if (instruction) {
45527
+ out.push({
45528
+ sourceId,
45529
+ role: "user",
45530
+ text: instruction,
45531
+ agentInstruction: true,
45532
+ tools: [],
45533
+ parts: [{ kind: "text", text: instruction }],
45534
+ at: rec.timestamp
45535
+ });
45536
+ lastAssistantId = null;
45537
+ }
45303
45538
  }
45304
45539
  return;
45305
45540
  }
@@ -45442,15 +45677,11 @@ ${block.text}` : block.text;
45442
45677
  return {
45443
45678
  ingest,
45444
45679
  finalize,
45445
- // The SAME window the paged reader returns (latestWindowStart), because this is the OTHER producer:
45680
+ // The SAME window the paged reader returns (latestTranscriptWindow), because this is the OTHER producer:
45446
45681
  // the /ws push renders from here while the RPC page renders from there, and a push whose head sat
45447
45682
  // further forward than the page's would splice the human's ask straight back out of a card that had
45448
45683
  // just anchored on it (reconcileLiveMessages replaces the window wholesale).
45449
- messages: () => {
45450
- const all = projected();
45451
- const start = latestWindowStart(all);
45452
- return start === 0 ? all : all.slice(start);
45453
- },
45684
+ messages: () => latestTranscriptWindow(projected()),
45454
45685
  allMessages: projected,
45455
45686
  consumedBytes: () => offset + Buffer.byteLength(buffer)
45456
45687
  };
@@ -46076,7 +46307,9 @@ function projectCodexTranscript(raw2, identityPrefix = "codex") {
46076
46307
  let prevEventAt;
46077
46308
  let lastContextTokens;
46078
46309
  let openCompaction = null;
46079
- const openAssistant = (at, sourceId) => {
46310
+ let agentInstructionCount = 0;
46311
+ const openAssistant = (at, sourceId, prose = false) => {
46312
+ if (prose && cur?.text) cur = null;
46080
46313
  if (cur) return cur;
46081
46314
  cur = { sourceId, role: "assistant", text: "", tools: [], parts: [], at };
46082
46315
  out.push(cur);
@@ -46094,7 +46327,7 @@ function projectCodexTranscript(raw2, identityPrefix = "codex") {
46094
46327
  case "assistant-text": {
46095
46328
  let text2 = extractCodexFrizzTitle(ev.text, ev.final).text;
46096
46329
  if (text2) {
46097
- const m = openAssistant(ev.at, sourceId);
46330
+ const m = openAssistant(ev.at, sourceId, true);
46098
46331
  pushTextPart(m, text2);
46099
46332
  m.text = m.text ? `${m.text}
46100
46333
 
@@ -46252,6 +46485,23 @@ ${text2}`;
46252
46485
  });
46253
46486
  break;
46254
46487
  }
46488
+ case "agent-instruction": {
46489
+ cur = null;
46490
+ turnReasoning = null;
46491
+ const first = agentInstructionCount++ === 0;
46492
+ const text2 = ev.text || (ev.encrypted ? `${first ? "Task" : "Follow-up"} instructions received. Codex encrypted the message body, so Frizz can't display it.` : `${first ? "Task" : "Follow-up"} instructions received.`);
46493
+ out.push({
46494
+ sourceId,
46495
+ role: "user",
46496
+ text: text2,
46497
+ displayText: text2,
46498
+ agentInstruction: true,
46499
+ tools: [],
46500
+ parts: [{ kind: "text", text: text2 }],
46501
+ at: ev.at
46502
+ });
46503
+ break;
46504
+ }
46255
46505
  case "compaction": {
46256
46506
  const m = compactionMessage(sourceId, ev.at, lastContextTokens, void 0);
46257
46507
  out.push(m);
@@ -46277,7 +46527,7 @@ ${text2}`;
46277
46527
  if (finalText !== void 0) finalText = extractCodexFrizzTitle(finalText).text;
46278
46528
  const ft = finalText?.trim();
46279
46529
  if (ft && !sawFinalAnswer && ft !== lastFinalText?.trim()) {
46280
- const m = openAssistant(ev.at, sourceId);
46530
+ const m = openAssistant(ev.at, sourceId, true);
46281
46531
  pushTextPart(m, finalText);
46282
46532
  m.text = m.text ? `${m.text}
46283
46533
 
@@ -46310,7 +46560,7 @@ function latestWindowStart(messages) {
46310
46560
  let boundary = -1;
46311
46561
  for (let i = tail - 1; i >= 0; i--) {
46312
46562
  const m = messages[i];
46313
- if (m.role === "user" && !m.wake && !m.queued) {
46563
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
46314
46564
  boundary = i;
46315
46565
  break;
46316
46566
  }
@@ -46329,6 +46579,7 @@ function latestWindowStart(messages) {
46329
46579
  function latestTranscriptWindow(messages) {
46330
46580
  const start = latestWindowStart(messages);
46331
46581
  if (start === 0) return [...messages];
46582
+ const earlierInstructions = messages.slice(0, start).filter((message) => message.agentInstruction);
46332
46583
  const pinned = [];
46333
46584
  for (let i = 0; i < start; i++) {
46334
46585
  const message = messages[i];
@@ -46347,7 +46598,7 @@ function latestTranscriptWindow(messages) {
46347
46598
  at: message.at
46348
46599
  });
46349
46600
  }
46350
- return [...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46601
+ return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46351
46602
  }
46352
46603
  function codexToolCall(name, input, callId) {
46353
46604
  if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
@@ -46476,7 +46727,7 @@ function codexReasoningCaption(text2) {
46476
46727
  function codexPeerMessageCall(label, target, obj) {
46477
46728
  const redacted = strField(obj.message);
46478
46729
  const encrypted = redacted === ENCRYPTED_PAYLOAD;
46479
- const body = !redacted ? void 0 : encrypted ? "_Codex encrypts inter-agent message bodies \u2014 the text is not recoverable from the rollout. Open the recipient's own thread to read what it received._" : capToolInput(redacted);
46730
+ const body = !redacted ? void 0 : encrypted ? "_Codex encrypts inter-agent message bodies \u2014 the text is not recoverable from the rollout. The recipient's transcript marks when it arrived, but can't display the encrypted text._" : capToolInput(redacted);
46480
46731
  return {
46481
46732
  name: label,
46482
46733
  detail: target,
@@ -47169,7 +47420,7 @@ function pageProjectedTranscript(messages, anchor, limits = {}) {
47169
47420
  let boundary = 0;
47170
47421
  for (let i = anchor - 1; i >= 0; i--) {
47171
47422
  const m = messages[i];
47172
- if (m.role === "user" && !m.wake && !m.queued) {
47423
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
47173
47424
  boundary = i;
47174
47425
  break;
47175
47426
  }
@@ -47375,7 +47626,7 @@ function readTranscriptFile(absPath) {
47375
47626
  return [];
47376
47627
  }
47377
47628
  }
47378
- var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
47629
+ var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, CLAUDE_COORDINATOR_INSTRUCTION_PREFIX, CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX, CLAUDE_PEER_INSTRUCTION_PREFIX, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
47379
47630
  var init_transcript = __esm({
47380
47631
  "packages/server/src/transcript.ts"() {
47381
47632
  "use strict";
@@ -47401,6 +47652,9 @@ var init_transcript = __esm({
47401
47652
  COMMAND_NAME_RE = /<command-name>([\s\S]*?)<\/command-name>/;
47402
47653
  COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
47403
47654
  PEER_SESSION_PREAMBLE = "Another Claude session sent a message:\n";
47655
+ CLAUDE_COORDINATOR_INSTRUCTION_PREFIX = "The coordinator sent a message while you were working:\n";
47656
+ CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX = "\n\nAddress this before completing your current task.";
47657
+ CLAUDE_PEER_INSTRUCTION_PREFIX = "Another Claude session sent a message while you were working:\n";
47404
47658
  QUEUED_STALE_MS = 2 * 60 * 6e4;
47405
47659
  EDIT_CAP = 4e3;
47406
47660
  TRUNC_MARKER = "\n\u2026 (truncated)";
@@ -48449,6 +48703,31 @@ function setProjectIconFromFile(id, file) {
48449
48703
  if (bytes.length > PROJECT_ICON_MAX_BASE64_CHARS / 4 * 3) throw new Error("That image is too large.");
48450
48704
  return storeProjectIcon(id, file, bytes);
48451
48705
  }
48706
+ function mergeSubAgentSteers(messages, steers) {
48707
+ if (!steers.length) return messages;
48708
+ const merged = [...messages];
48709
+ for (const steer of steers) {
48710
+ const at = new Date(steer.sent_at).toISOString();
48711
+ const duplicate = messages.some((message2) => {
48712
+ if (message2.role !== "user" || message2.text.trim() !== steer.message.trim() || !message2.at) return false;
48713
+ return Math.abs(Date.parse(message2.at) - steer.sent_at) <= 5e3;
48714
+ });
48715
+ if (duplicate) continue;
48716
+ const message = {
48717
+ sourceId: `subagent-steer:${steer.delivery_id}`,
48718
+ role: "user",
48719
+ text: steer.message,
48720
+ agentInstruction: true,
48721
+ tools: [],
48722
+ parts: [{ kind: "text", text: steer.message }],
48723
+ at
48724
+ };
48725
+ const next = merged.findIndex((candidate) => candidate.at !== void 0 && Date.parse(candidate.at) > steer.sent_at);
48726
+ if (next === -1) merged.push(message);
48727
+ else merged.splice(next, 0, message);
48728
+ }
48729
+ return merged;
48730
+ }
48452
48731
  function createRouter(ctx) {
48453
48732
  const frizzDir = join36(ctx.project.dir, ".frizz");
48454
48733
  const openRoots = openableFileRoots(ctx.project);
@@ -48652,7 +48931,11 @@ function createRouter(ctx) {
48652
48931
  sessionId: row.session_id,
48653
48932
  cwd: ctx.project.dir,
48654
48933
  text: `${shellStopNotice(label)} Whatever it wrote before the kill is still readable in its output file.`,
48655
- permissionMode: row.permission_mode ?? void 0,
48934
+ // `isDaemonAlive` above is a check, not a hold: the daemon can exit before this frame lands,
48935
+ // and followUp then COLD-RESUMES rather than failing. Take the same floor every other fork
48936
+ // takes, so a notice that loses the race cannot be the thing that rebuilds the worker at
48937
+ // Claude's `default` — see coldResumePermission.
48938
+ permissionMode: coldResumePermission(row, ctx.getSettings()),
48656
48939
  model: row.model ?? void 0,
48657
48940
  effort: row.effort ?? void 0
48658
48941
  });
@@ -48837,7 +49120,10 @@ function createRouter(ctx) {
48837
49120
  const info = ctx.tailer.subAgent(input.slug, input.id);
48838
49121
  if (!info) return { messages: [], state: "gone", steerable: false, steerNote: null, stoppable: false, stopNote: null };
48839
49122
  const read = info.outputFormat === "codex" ? readCodexTranscriptFile : readTranscriptFile;
48840
- const messages = info.outputFile ? read(info.outputFile) : [];
49123
+ const messages = mergeSubAgentSteers(
49124
+ info.outputFile ? read(info.outputFile) : [],
49125
+ ctx.storage.listSubAgentSteers(input.slug, input.id)
49126
+ );
48841
49127
  const steer = subAgentSteerable(input.slug, input.id);
48842
49128
  const stop = subAgentStoppable(input.slug, input.id);
48843
49129
  return {
@@ -48863,7 +49149,7 @@ function createRouter(ctx) {
48863
49149
  // predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
48864
49150
  // single predicate that decides, and the drawer's prompt box is rendered off the same answer.
48865
49151
  subAgentSteer: mutation({
48866
- input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().optional() }).strict(),
49152
+ input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().min(1).max(200).optional() }).strict(),
48867
49153
  output: external_exports.object({ delivered: external_exports.boolean() }),
48868
49154
  handler: async ({ input }) => {
48869
49155
  const target = subAgentSteerable(input.slug, input.id);
@@ -48872,12 +49158,21 @@ function createRouter(ctx) {
48872
49158
  }
48873
49159
  const bridge = ctx.claudeBroker;
48874
49160
  if (!bridge) throw new Error("Claude session broker is unavailable; cannot steer this sub-agent");
49161
+ const deliveryId = input.deliveryId ?? randomUUID16();
49162
+ const sentAtMs = Date.now();
48875
49163
  await bridge.steerSubAgent({
48876
49164
  threadSlug: input.slug,
48877
49165
  sessionId: target.sessionId,
48878
49166
  subAgentId: input.id,
48879
49167
  text: input.message,
48880
- deliveryId: input.deliveryId
49168
+ deliveryId
49169
+ });
49170
+ ctx.storage.recordSubAgentSteer({
49171
+ slug: input.slug,
49172
+ subAgentId: input.id,
49173
+ deliveryId,
49174
+ message: input.message,
49175
+ sentAtMs
48881
49176
  });
48882
49177
  ctx.board.refresh();
48883
49178
  return { delivered: true };
@@ -49111,7 +49406,9 @@ ${gapNote}` : input.message;
49111
49406
  // Rides through to the SDK as this input's uuid, which the SDK echoes back on the record
49112
49407
  // that delivers it — the ledger then correlates by identity rather than by text.
49113
49408
  deliveryId: input.deliveryId,
49114
- permissionMode: row.permission_mode ?? void 0,
49409
+ // A persisted per-thread mode, or the dispatch floor for a row that has none — see
49410
+ // coldResumePermission for why a legacy row must not fall through to the bridge's `"default"`.
49411
+ permissionMode: coldResumePermission(row, ctx.getSettings()),
49115
49412
  appendSystemPrompt,
49116
49413
  model: row.model ?? void 0,
49117
49414
  effort: row.effort ?? void 0,
@@ -53040,8 +53337,8 @@ var init_runtimes = __esm({
53040
53337
  "use strict";
53041
53338
  init_codex_app_server();
53042
53339
  init_frizz_paths();
53043
- CLAUDE_AGENT_SDK_VERSION = "0.3.260";
53044
- CLAUDE_CODE_VERSION = "2.1.260";
53340
+ CLAUDE_AGENT_SDK_VERSION = "0.3.261";
53341
+ CLAUDE_CODE_VERSION = "2.1.261";
53045
53342
  CODEX_VERSION = CODEX_APP_SERVER_SUPPORTED_VERSION;
53046
53343
  RUNTIMES_MODE_ENV = "FRIZZ_RUNTIMES";
53047
53344
  RUNTIMES_DIR_ENV = "FRIZZ_RUNTIMES_DIR";