frizz 0.12.1 → 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 (92) hide show
  1. package/dist/claude-agent-broker.js +3 -7
  2. package/dist/codex-app-server-daemon.js +18 -4
  3. package/dist/dev-child.js +1151 -870
  4. package/dist/frizz.js +6 -0
  5. package/package.json +1 -1
  6. package/web-dist/assets/{TerminalPane-B1tNgepq.js → TerminalPane-D5Tx2hu9.js} +1 -1
  7. package/web-dist/assets/{abnfDiagram-VRR7QNED-C-0I2Feo.js → abnfDiagram-VRR7QNED-BO0tgRMu.js} +1 -1
  8. package/web-dist/assets/architecture-TIHT7OUA-lCPPJORL.js +1 -0
  9. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-5k4peR_3.js → architectureDiagram-ZJ3FMSHR-TCJFLj-i.js} +1 -1
  10. package/web-dist/assets/{blockDiagram-677ZJIJ3-BW9lpPO1.js → blockDiagram-677ZJIJ3-BRd9xJTz.js} +1 -1
  11. package/web-dist/assets/{c4Diagram-LMCZKHZV-gTvX_Y0U.js → c4Diagram-LMCZKHZV-BxlNYwY8.js} +1 -1
  12. package/web-dist/assets/channel-tGE7Gbbs.js +1 -0
  13. package/web-dist/assets/{chunk-32BRIVSS-C8vG0TZe.js → chunk-32BRIVSS-o0wtC3P5.js} +1 -1
  14. package/web-dist/assets/{chunk-52WLFC77-D0-irGOb.js → chunk-52WLFC77-C_bt24w7.js} +1 -1
  15. package/web-dist/assets/{chunk-C7G6YPKG-Damd-YiB.js → chunk-C7G6YPKG-DlsBv-nI.js} +1 -1
  16. package/web-dist/assets/{chunk-EX3LRPZG-BIBNY4nG.js → chunk-EX3LRPZG-Y_Debiw-.js} +1 -1
  17. package/web-dist/assets/{chunk-FWX5IMBZ-D4Ur--1p.js → chunk-FWX5IMBZ-D_9nCJ43.js} +2 -2
  18. package/web-dist/assets/{chunk-HOUHSVGY-CVXtpiCg.js → chunk-HOUHSVGY-Bzhu2UoJ.js} +1 -1
  19. package/web-dist/assets/{chunk-ICXQ74PX-C23GvVE0.js → chunk-ICXQ74PX-CwJVbXqL.js} +1 -1
  20. package/web-dist/assets/{chunk-MOJQB5TN-Bvmynh3h.js → chunk-MOJQB5TN-DPm_X7tz.js} +1 -1
  21. package/web-dist/assets/{chunk-OGEWGWER-BqQnr6op.js → chunk-OGEWGWER-1m3zk2ed.js} +1 -1
  22. package/web-dist/assets/{chunk-PUDLZKDR-BW1vhete.js → chunk-PUDLZKDR-D2Bnu5zc.js} +1 -1
  23. package/web-dist/assets/{chunk-Q4XR5HBZ-Dg2-yKFk.js → chunk-Q4XR5HBZ-DJZtjVxI.js} +1 -1
  24. package/web-dist/assets/{chunk-V7JOEXUC-iIIhDWCz.js → chunk-V7JOEXUC-BQVKbxnn.js} +1 -1
  25. package/web-dist/assets/{chunk-VAUOI2AC--SsJNJQ_.js → chunk-VAUOI2AC-TwOgVbMm.js} +1 -1
  26. package/web-dist/assets/{chunk-VR4S4FIN-SpDDIDKl.js → chunk-VR4S4FIN-DlG1lzSj.js} +1 -1
  27. package/web-dist/assets/{chunk-WYO6CB5R-DIObprNf.js → chunk-WYO6CB5R-Ckk8qoao.js} +1 -1
  28. package/web-dist/assets/{chunk-ZGVPDNZ5-CIjpBbL-.js → chunk-ZGVPDNZ5-CZxmPZzT.js} +1 -1
  29. package/web-dist/assets/classDiagram-OUVF2IWQ-CQo4x1hj.js +1 -0
  30. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CQo4x1hj.js +1 -0
  31. package/web-dist/assets/{cynefin-VYW2F7L2-CmAuhNNk.js → cynefin-VYW2F7L2-CioP0cgr.js} +1 -1
  32. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B-8sxYE3.js → cynefinDiagram-TSTJHNR4-BJZpJw_3.js} +1 -1
  33. package/web-dist/assets/{dagre-VKFMJZFB-rMqddpxv.js → dagre-VKFMJZFB-BGv33WEE.js} +1 -1
  34. package/web-dist/assets/{diagram-FQU43EPY-CcSGTzGC.js → diagram-FQU43EPY-CvhxVWTl.js} +1 -1
  35. package/web-dist/assets/{diagram-G47NLZAW-1bv1SDU9.js → diagram-G47NLZAW-DnrPOeE0.js} +1 -1
  36. package/web-dist/assets/{diagram-NH7WQ7WH-DP_7FVNi.js → diagram-NH7WQ7WH-CazuXXkX.js} +1 -1
  37. package/web-dist/assets/{diagram-OA4YK3LP-BC7zSQ_n.js → diagram-OA4YK3LP-DfbFhbQs.js} +1 -1
  38. package/web-dist/assets/{diagram-WEI45ONY-DnWoY3ss.js → diagram-WEI45ONY-BESpc3Oj.js} +1 -1
  39. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-DQvaFaem.js → ebnfDiagram-CCIWWBDH-C6h37W51.js} +1 -1
  40. package/web-dist/assets/{erDiagram-Q63AITRT-DrfafCjs.js → erDiagram-Q63AITRT-vCxGREHo.js} +1 -1
  41. package/web-dist/assets/eventmodeling-45OFAUF4-C517fJsQ.js +1 -0
  42. package/web-dist/assets/flowDiagram-23GEKE2U-IrjbjjxF.js +1 -0
  43. package/web-dist/assets/{ganttDiagram-NO4QXBWP-B1niKUL6.js → ganttDiagram-NO4QXBWP-B5l9XyZe.js} +1 -1
  44. package/web-dist/assets/{gitGraph-TEB2WS4Q-BmpQWnXu.js → gitGraph-TEB2WS4Q-CQpssLnk.js} +1 -1
  45. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DHbWnvhX.js → gitGraphDiagram-IHSO6WYX-j95eUOjO.js} +1 -1
  46. package/web-dist/assets/{index-ChKvnpjw.js → index-Dzq6GPUo.js} +6 -6
  47. package/web-dist/assets/{info-DKCQHKI2-C5dmsO4x.js → info-DKCQHKI2-CiW0hhEI.js} +1 -1
  48. package/web-dist/assets/{infoDiagram-FWYZ7A6U-Cdv7Pawh.js → infoDiagram-FWYZ7A6U-BJwJPEq2.js} +1 -1
  49. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-BwdMFTVa.js → ishikawaDiagram-FXEZZL3T-Ck4D0hT_.js} +1 -1
  50. package/web-dist/assets/{journeyDiagram-5HDEW3XC-DXwHf8Te.js → journeyDiagram-5HDEW3XC-Bz8PK7lM.js} +1 -1
  51. package/web-dist/assets/{kanban-definition-HUTT4EX6-Bwe2UMg8.js → kanban-definition-HUTT4EX6-Kqt3cah4.js} +1 -1
  52. package/web-dist/assets/{line-CHwK75-Q.js → line-Dl7tIumR.js} +1 -1
  53. package/web-dist/assets/{mermaid-parser.core-DPt_XLrX.js → mermaid-parser.core-C_aHybiE.js} +3 -3
  54. package/web-dist/assets/{mermaid.core-DTEprUMo.js → mermaid.core-Dv6lgZxU.js} +3 -3
  55. package/web-dist/assets/{mindmap-definition-LN4V7U3C-B6nU7yZ9.js → mindmap-definition-LN4V7U3C-EkN_2bUd.js} +1 -1
  56. package/web-dist/assets/{packet-7NZHBO7P-DgH2_Myt.js → packet-7NZHBO7P-LL-j9b42.js} +1 -1
  57. package/web-dist/assets/{pegDiagram-2B236MQR-CcJMsm4C.js → pegDiagram-2B236MQR-CrNUBIKh.js} +1 -1
  58. package/web-dist/assets/{pie-RZYD4A2V-D7GdDOZL.js → pie-RZYD4A2V-8wZ8AyeT.js} +1 -1
  59. package/web-dist/assets/{pieDiagram-ENE6RG2P-eER4uywi.js → pieDiagram-ENE6RG2P-BSUUt2Vt.js} +1 -1
  60. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DS7rTWdN.js → quadrantDiagram-ABIIQ3AL-BmRRE-b9.js} +1 -1
  61. package/web-dist/assets/{radar-I7S5WNFK-A0W49mjL.js → radar-I7S5WNFK-BZ8i3wPh.js} +1 -1
  62. package/web-dist/assets/{railroad-3IZDKUUU-BKzJ3mls.js → railroad-3IZDKUUU-Bvq-p3jW.js} +1 -1
  63. package/web-dist/assets/railroad-abnf-AHOZXSZD-DIZeZa-B.js +1 -0
  64. package/web-dist/assets/railroad-ebnf-EBAXGLYW-DVn3xHNX.js +1 -0
  65. package/web-dist/assets/railroad-peg-LSFZ7HO6-BvLcJu_d.js +1 -0
  66. package/web-dist/assets/{railroadDiagram-RFXS5EU6-Ov2VgnIL.js → railroadDiagram-RFXS5EU6-CH0DOBjJ.js} +1 -1
  67. package/web-dist/assets/{requirementDiagram-TGXJPOKE-BRJ85Yhv.js → requirementDiagram-TGXJPOKE-ytS-raWw.js} +1 -1
  68. package/web-dist/assets/{sankeyDiagram-HTMAVEWB---zsi4tQ.js → sankeyDiagram-HTMAVEWB-BU49ulmu.js} +1 -1
  69. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-AAMnjV2O.js → sequenceDiagram-DBY2YBRQ-C1edCLps.js} +1 -1
  70. package/web-dist/assets/{stateDiagram-2N3HPSRC-Rjiga0k5.js → stateDiagram-2N3HPSRC-C049S4Yj.js} +1 -1
  71. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-D-Et-Cj1.js +1 -0
  72. package/web-dist/assets/{swimlanes-5IMT3BWC-D0Lhg3tV.js → swimlanes-5IMT3BWC-KNWNnFDx.js} +1 -1
  73. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DQNRnHXT.js +8 -0
  74. package/web-dist/assets/{timeline-definition-FHXFAJF6-Cxq73h1K.js → timeline-definition-FHXFAJF6-D8Z-Q8JO.js} +1 -1
  75. package/web-dist/assets/{treeView-QDETBFTQ-CIwv1s2P.js → treeView-QDETBFTQ-CvyDkH5p.js} +1 -1
  76. package/web-dist/assets/{treemap-6X3UGDF4-BR7vnt3b.js → treemap-6X3UGDF4-COwCP95k.js} +1 -1
  77. package/web-dist/assets/{vennDiagram-L72KCM5P-B8nj-bxp.js → vennDiagram-L72KCM5P-DHKE_6N3.js} +1 -1
  78. package/web-dist/assets/{wardley-OPB4EBWU-D2n_leYo.js → wardley-OPB4EBWU-CYWcYdsx.js} +1 -1
  79. package/web-dist/assets/{wardleyDiagram-EHGQE667-Nc-IHRMn.js → wardleyDiagram-EHGQE667-BS3OkOU_.js} +1 -1
  80. package/web-dist/assets/{xychartDiagram-FW5EYKEG-C4B5u2-g.js → xychartDiagram-FW5EYKEG-Df4GdNEo.js} +1 -1
  81. package/web-dist/index.html +1 -1
  82. package/web-dist/assets/architecture-TIHT7OUA-WFjHvmXr.js +0 -1
  83. package/web-dist/assets/channel-B0ZL14bg.js +0 -1
  84. package/web-dist/assets/classDiagram-OUVF2IWQ-DRFJ6yYB.js +0 -1
  85. package/web-dist/assets/classDiagram-v2-EOCWNBFH-DRFJ6yYB.js +0 -1
  86. package/web-dist/assets/eventmodeling-45OFAUF4-5EP2o0h2.js +0 -1
  87. package/web-dist/assets/flowDiagram-23GEKE2U-DNJa8Ru6.js +0 -1
  88. package/web-dist/assets/railroad-abnf-AHOZXSZD-D4UPY4zZ.js +0 -1
  89. package/web-dist/assets/railroad-ebnf-EBAXGLYW-DlLwAaep.js +0 -1
  90. package/web-dist/assets/railroad-peg-LSFZ7HO6-C25TXnXG.js +0 -1
  91. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CQOSUkD8.js +0 -1
  92. package/web-dist/assets/swimlanesDiagram-G3AALYLV-B26FPpIT.js +0 -8
package/dist/dev-child.js CHANGED
@@ -15575,6 +15575,12 @@ ${DISPATCH_TASK_BANNER}
15575
15575
  // disagree. Additive + optional: absent from a legacy transcript or an older server, and the client
15576
15576
  // falls back to `parseGithubWakeSteer` on the text.
15577
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(),
15578
15584
  // A SUB-AGENT (or peer session) wrote this user turn, not the human — the same defect class `wake`
15579
15585
  // above corrects. Claude Code's agent-to-agent channel (a background child calling
15580
15586
  // `SendMessage({to:"main"})`) delivers UPWARD into the parent's queue like any follow-up, so the
@@ -19419,6 +19425,7 @@ function createStorage(source, projectId) {
19419
19425
  `);
19420
19426
  const delThreadQuestions = scope.prepare("DELETE FROM thread_question WHERE project_id = @project_id AND thread_slug = ?");
19421
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 = ?");
19422
19429
  const markThreadDoneStmt = scope.prepare(`
19423
19430
  INSERT INTO thread_done (project_id, thread_slug, body, done_at) VALUES (@project_id, ?, ?, ?)
19424
19431
  ON CONFLICT(project_id, thread_slug) DO UPDATE SET body = excluded.body, done_at = excluded.done_at
@@ -19428,6 +19435,17 @@ function createStorage(source, projectId) {
19428
19435
  "SELECT thread_slug, body, done_at FROM thread_done WHERE project_id = @project_id"
19429
19436
  );
19430
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
+ `);
19431
19449
  const clearExpiredSnoozesStmt = scope.prepare(`
19432
19450
  UPDATE session SET snoozed_until = NULL
19433
19451
  WHERE project_id = @project_id AND snoozed_until IS NOT NULL AND snoozed_until <= ? AND snooze_prompt IS NULL
@@ -19482,6 +19500,7 @@ function createStorage(source, projectId) {
19482
19500
  delThreadWatches.run(existing.slug);
19483
19501
  delThreadQuestions.run(existing.slug);
19484
19502
  delThreadDone.run(existing.slug);
19503
+ delSubAgentSteers.run(existing.slug);
19485
19504
  delSession.run(existing.slug);
19486
19505
  return existing;
19487
19506
  };
@@ -19975,6 +19994,10 @@ function createStorage(source, projectId) {
19975
19994
  return bySlug;
19976
19995
  },
19977
19996
  clearThreadDone: (slug) => clearThreadDoneStmt.run(slug).changes > 0,
19997
+ recordSubAgentSteer: (row) => {
19998
+ recordSubAgentSteerStmt.run(row);
19999
+ },
20000
+ listSubAgentSteers: (slug, subAgentId) => listSubAgentSteersStmt.all(slug, subAgentId),
19978
20001
  dropThreadWatch: (slug, id, settledAtMs) => dropThreadWatchStmt.run(settledAtMs, id, slug).changes === 1,
19979
20002
  settleThreadWatch: (id, settledAtMs, state = "settled") => settleThreadWatchStmt.run(state, settledAtMs, id).changes === 1,
19980
20003
  armThreadTimer: (timer) => void armTimerStmt.run(timer),
@@ -20473,6 +20496,20 @@ var init_storage = __esm({
20473
20496
  done_at INTEGER NOT NULL,
20474
20497
  PRIMARY KEY (project_id, thread_slug)
20475
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);
20476
20513
  `;
20477
20514
  STORAGE_TABLES = [
20478
20515
  "session",
@@ -20485,7 +20522,8 @@ var init_storage = __esm({
20485
20522
  "pr_watch",
20486
20523
  "thread_watch",
20487
20524
  "thread_question",
20488
- "thread_done"
20525
+ "thread_done",
20526
+ "subagent_steer"
20489
20527
  ];
20490
20528
  }
20491
20529
  });
@@ -23746,6 +23784,7 @@ function applyEvent(state, ev) {
23746
23784
  state.sawRecords = true;
23747
23785
  break;
23748
23786
  case "agent-report":
23787
+ case "agent-instruction":
23749
23788
  state.sawRecords = true;
23750
23789
  break;
23751
23790
  case "title":
@@ -25534,32 +25573,47 @@ function parseCodexLine(line) {
25534
25573
  if (pt === "agent_message") {
25535
25574
  const author = typeof p.author === "string" ? p.author : "";
25536
25575
  const recipient = typeof p.recipient === "string" ? p.recipient : "";
25537
- if (!author || !recipient || !author.startsWith(`${recipient}/`)) return [];
25538
- const report = parseCodexAgentReport(p.content);
25539
- 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 [];
25540
25593
  }
25541
25594
  return [];
25542
25595
  }
25543
25596
  return [];
25544
25597
  }
25545
- function parseCodexAgentReport(content) {
25598
+ function parseCodexAgentMessage(content) {
25546
25599
  if (!Array.isArray(content)) return void 0;
25547
25600
  let text2;
25601
+ let encrypted = false;
25548
25602
  for (const block of content) {
25549
25603
  if (!block || typeof block !== "object") continue;
25550
25604
  const b = block;
25605
+ if (b.type === "encrypted_content") encrypted = true;
25551
25606
  if (b.type === "input_text" && typeof b.text === "string") {
25552
25607
  text2 = b.text;
25553
- break;
25554
25608
  }
25555
25609
  }
25556
25610
  if (!text2) return void 0;
25557
25611
  const type = text2.match(/^Message Type:[ \t]*(\S+)/)?.[1];
25558
- if (type !== "FINAL_ANSWER" && type !== "MESSAGE") return void 0;
25612
+ if (!type) return void 0;
25559
25613
  const marker = text2.match(/^Payload:[ \t]*$/m);
25560
25614
  if (!marker || marker.index === void 0) return void 0;
25561
25615
  const body = text2.slice(marker.index + marker[0].length).replace(/^\n/, "").trim();
25562
- return { final: type === "FINAL_ANSWER", body };
25616
+ return { type, body, encrypted };
25563
25617
  }
25564
25618
  function parseCodexSubAgentLine(line, toolNameFor) {
25565
25619
  const s = line.trim();
@@ -29931,7 +29985,8 @@ function readNativeRecord(stateDir, projectId) {
29931
29985
  generation: value.generation,
29932
29986
  listenerPid: value.listenerPid,
29933
29987
  socketPath: value.socketPath,
29934
- createdAt: value.createdAt ?? ""
29988
+ createdAt: value.createdAt ?? "",
29989
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
29935
29990
  };
29936
29991
  } catch {
29937
29992
  return null;
@@ -30101,7 +30156,8 @@ async function startListener(options) {
30101
30156
  generation,
30102
30157
  listenerPid: child.pid ?? -1,
30103
30158
  socketPath,
30104
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
30159
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
30160
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {}
30105
30161
  };
30106
30162
  writeFileSync9(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
30107
30163
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
@@ -30131,7 +30187,8 @@ var init_codex_app_server_native = __esm({
30131
30187
  generation: existing.generation,
30132
30188
  reattached: true,
30133
30189
  daemonPid: existing.listenerPid,
30134
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30190
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30191
+ authAccountId: existing.authAccountId
30135
30192
  };
30136
30193
  } catch {
30137
30194
  try {
@@ -30147,7 +30204,8 @@ var init_codex_app_server_native = __esm({
30147
30204
  generation: record.generation,
30148
30205
  reattached: false,
30149
30206
  daemonPid: record.listenerPid,
30150
- droppedWhileDetached: 0
30207
+ droppedWhileDetached: 0,
30208
+ authAccountId: record.authAccountId
30151
30209
  };
30152
30210
  } catch (error) {
30153
30211
  const raced = liveNativeRecord(options.stateDir, options.projectId);
@@ -30157,12 +30215,13 @@ var init_codex_app_server_native = __esm({
30157
30215
  generation: raced.generation,
30158
30216
  reattached: true,
30159
30217
  daemonPid: raced.listenerPid,
30160
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30218
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30219
+ authAccountId: raced.authAccountId
30161
30220
  };
30162
30221
  }
30163
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`);
30164
30223
  const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
30165
- 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 };
30166
30225
  }
30167
30226
  };
30168
30227
  }
@@ -30204,7 +30263,8 @@ function readDaemonRecord(stateDir, projectId) {
30204
30263
  daemonPid: value.daemonPid,
30205
30264
  childPid: typeof value.childPid === "number" ? value.childPid : 0,
30206
30265
  socketPath: value.socketPath,
30207
- createdAt: value.createdAt ?? ""
30266
+ createdAt: value.createdAt ?? "",
30267
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
30208
30268
  };
30209
30269
  } catch {
30210
30270
  return null;
@@ -30298,6 +30358,7 @@ function forkDaemon(options) {
30298
30358
  generation,
30299
30359
  clientInfo: options.clientInfo,
30300
30360
  capabilities: options.capabilities,
30361
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {},
30301
30362
  // The argv is built HERE, not in the daemon: the daemon is a bare forked entry that should not have
30302
30363
  // to resolve frizz's MCP descriptors, and building it once keeps this transport identical to the
30303
30364
  // native listener's.
@@ -30425,7 +30486,7 @@ function inProcessCodexAppServer(options) {
30425
30486
  return false;
30426
30487
  }
30427
30488
  });
30428
- 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 };
30429
30490
  }
30430
30491
  function directChildHost(spawnChild, generationId = randomUUID10) {
30431
30492
  return async (options) => ({
@@ -30433,7 +30494,8 @@ function directChildHost(spawnChild, generationId = randomUUID10) {
30433
30494
  generation: generationId(),
30434
30495
  reattached: false,
30435
30496
  daemonPid: process.pid,
30436
- droppedWhileDetached: 0
30497
+ droppedWhileDetached: 0,
30498
+ authAccountId: options.authAccountId
30437
30499
  });
30438
30500
  }
30439
30501
  var daemonEntry, daemonCodexAppServerHost;
@@ -30452,7 +30514,7 @@ var init_codex_app_server_host = __esm({
30452
30514
  if (existing) {
30453
30515
  try {
30454
30516
  const attached = await attach2(existing, timeoutMs);
30455
- return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid };
30517
+ return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid, authAccountId: existing.authAccountId };
30456
30518
  } catch {
30457
30519
  try {
30458
30520
  unlinkSync3(recordPath(options.stateDir, options.projectId));
@@ -30472,7 +30534,7 @@ var init_codex_app_server_host = __esm({
30472
30534
  try {
30473
30535
  const record = await forkDaemon(options);
30474
30536
  const attached = await attach2(record, timeoutMs);
30475
- return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid };
30537
+ return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid, authAccountId: record.authAccountId };
30476
30538
  } catch (error) {
30477
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`);
30478
30540
  return inProcessCodexAppServer(options);
@@ -30481,8 +30543,627 @@ var init_codex_app_server_host = __esm({
30481
30543
  }
30482
30544
  });
30483
30545
 
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");
30556
+ }
30557
+ function tokenFromCredentialsJson(raw2) {
30558
+ let doc;
30559
+ try {
30560
+ doc = JSON.parse(raw2);
30561
+ } catch {
30562
+ return void 0;
30563
+ }
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;
30569
+ }
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;
30580
+ }
30581
+ }
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 {
30587
+ }
30588
+ return readKeychainToken();
30589
+ }
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;
30593
+ }
30594
+ function slugify2(name) {
30595
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30596
+ }
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 } };
30604
+ }
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 } };
30630
+ }
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
+ }
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 };
30649
+ }
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
+ };
30656
+ }
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;
30664
+ }
30665
+ }
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
+ }
30675
+ }
30676
+ function delay(ms) {
30677
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
30678
+ }
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
+ }
30699
+ }
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 }
30709
+ );
30710
+ return String(stdout);
30711
+ }
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;
30720
+ }
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}`);
30739
+ }
30740
+ } catch (err) {
30741
+ if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
30742
+ }
30743
+ }
30744
+ const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
30745
+ if (quota.status !== "ok") throw new Error(quota.detail);
30746
+ return quota;
30747
+ }
30748
+ function windowIdentity(title) {
30749
+ for (const [pattern, key, label] of LINE_WINDOWS) {
30750
+ if (pattern.test(title)) return [key, label];
30751
+ }
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);
30772
+ }
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);
30779
+ }
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);
30787
+ }
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 {
30794
+ }
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 }
30807
+ });
30808
+ }
30809
+ if (windows.length === 0) {
30810
+ return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
30811
+ }
30812
+ return { status: "ok", windows };
30813
+ }
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
+
30484
31165
  // packages/server/src/backend/codex-app-server.ts
30485
- import { createHash as createHash9, randomUUID as randomUUID11 } from "node:crypto";
31166
+ import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
30486
31167
  import { StringDecoder as StringDecoder3 } from "node:string_decoder";
30487
31168
  function selectCodexHostKind(flagValue, platform3, hasSpawn) {
30488
31169
  if (hasSpawn) return "direct";
@@ -30829,7 +31510,7 @@ function canonicalJson2(value) {
30829
31510
  return JSON.stringify(normalize3(value));
30830
31511
  }
30831
31512
  function logicalRequestId(method, parts) {
30832
- const digest = createHash9("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
31513
+ const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
30833
31514
  return `codex-${digest}`;
30834
31515
  }
30835
31516
  function requestFingerprint(value) {
@@ -30839,7 +31520,7 @@ function requestFingerprint(value) {
30839
31520
  delete record.startedAtMs;
30840
31521
  delete record.autoResolutionMs;
30841
31522
  }
30842
- return createHash9("sha256").update(canonicalJson2(normalized)).digest("hex");
31523
+ return createHash10("sha256").update(canonicalJson2(normalized)).digest("hex");
30843
31524
  }
30844
31525
  function decision(id, semantic, label) {
30845
31526
  return { id, semantic, label };
@@ -31127,7 +31808,7 @@ function createCodexAppServerBridge(options) {
31127
31808
  function codexAppServerBridgeEnabled() {
31128
31809
  return true;
31129
31810
  }
31130
- 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;
31131
31812
  var init_codex_app_server = __esm({
31132
31813
  "packages/server/src/backend/codex-app-server.ts"() {
31133
31814
  "use strict";
@@ -31139,15 +31820,16 @@ var init_codex_app_server = __esm({
31139
31820
  init_credential_redaction();
31140
31821
  init_codex_app_server_host();
31141
31822
  init_codex_app_server_native();
31823
+ init_auth_status();
31142
31824
  init_codex_mcp();
31143
31825
  init_logging();
31144
31826
  CODEX_APP_SERVER_PROVIDER = "codex-app-server";
31145
31827
  CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
31146
- CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.2";
31828
+ CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.4";
31147
31829
  CODEX_APP_SERVER_PROTOCOL_REVISION = Object.freeze({
31148
31830
  packageVersion: CODEX_APP_SERVER_SUPPORTED_VERSION,
31149
- sourceTag: "rust-v0.153.2",
31150
- sourceCommit: "657a993cbee87acf52d14b758ce49dbd46d1b8eb"
31831
+ sourceTag: "rust-v0.153.4",
31832
+ sourceCommit: "3d2ee51ca2d5db578f328aa75e20aa22c0197c9a"
31151
31833
  });
31152
31834
  PROTOCOL_FINGERPRINT = [
31153
31835
  CODEX_APP_SERVER_PROTOCOL_REVISION.sourceTag,
@@ -31158,7 +31840,22 @@ var init_codex_app_server = __esm({
31158
31840
  CLIENT_CAPABILITIES = Object.freeze({
31159
31841
  experimentalApi: true,
31160
31842
  requestAttestation: false,
31161
- 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
+ ]
31162
31859
  });
31163
31860
  CODEX_SANDBOX_MODES = Object.freeze([
31164
31861
  "read-only",
@@ -31170,8 +31867,9 @@ var init_codex_app_server = __esm({
31170
31867
  SANDBOX_CONFIRM_TIMEOUT_MS = 8e3;
31171
31868
  SANDBOX_NOOP_GRACE_MS = 750;
31172
31869
  MAX_JSONL_BYTES = 256 * 1024;
31870
+ MAX_INBOUND_JSONL_BYTES = 8 * 1024 * 1024;
31173
31871
  MAX_INBOUND_RECORDS = 256;
31174
- MAX_INBOUND_QUEUED_BYTES = MAX_JSONL_BYTES * 2;
31872
+ MAX_INBOUND_QUEUED_BYTES = MAX_INBOUND_JSONL_BYTES * 2;
31175
31873
  MAX_OUTBOUND_REQUESTS = 128;
31176
31874
  MAX_STDERR_BYTES = 16 * 1024;
31177
31875
  DEFAULT_REQUEST_TIMEOUT_MS = 2e4;
@@ -31456,17 +32154,18 @@ var init_codex_app_server = __esm({
31456
32154
  consume(chunk) {
31457
32155
  if (this.closed) return;
31458
32156
  this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
31459
- if (Buffer.byteLength(this.buffer, "utf8") > MAX_JSONL_BYTES * 2) {
31460
- this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
31461
- return;
31462
- }
31463
32157
  while (true) {
31464
32158
  const newline = this.buffer.indexOf("\n");
31465
- 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
+ }
31466
32165
  const line = this.buffer.slice(0, newline);
31467
32166
  this.buffer = this.buffer.slice(newline + 1);
31468
32167
  if (line.trim().length === 0) continue;
31469
- if (Buffer.byteLength(line, "utf8") > MAX_JSONL_BYTES) {
32168
+ if (Buffer.byteLength(line, "utf8") > MAX_INBOUND_JSONL_BYTES) {
31470
32169
  this.fail("protocol", new Error("Codex app-server JSONL message exceeded its limit"));
31471
32170
  return;
31472
32171
  }
@@ -31477,6 +32176,10 @@ var init_codex_app_server = __esm({
31477
32176
  this.fail("protocol", new Error("Codex app-server emitted invalid JSONL"));
31478
32177
  return;
31479
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
+ }
31480
32183
  const bytes = Buffer.byteLength(line, "utf8");
31481
32184
  if (this.inboundQueue.length >= MAX_INBOUND_RECORDS || this.inboundQueuedBytes + bytes > MAX_INBOUND_QUEUED_BYTES) {
31482
32185
  this.fail("protocol", new Error("Codex app-server inbound queue exceeded its limit"));
@@ -31580,6 +32283,7 @@ var init_codex_app_server = __esm({
31580
32283
  }
31581
32284
  fail(reason, error) {
31582
32285
  if (this.closed) return;
32286
+ if (reason === "protocol") this.diagnostic?.({ event: "protocol-failure", detail: error.message });
31583
32287
  this.closed = true;
31584
32288
  for (const pending of this.pending.values()) {
31585
32289
  clearTimeout(pending.timer);
@@ -31684,6 +32388,11 @@ var init_codex_app_server = __esm({
31684
32388
  timeoutMs;
31685
32389
  /** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
31686
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();
31687
32396
  /**
31688
32397
  * The handshake a FRESHLY FORKED daemon already gave us and we already rejected — i.e. what the
31689
32398
  * codex binary on disk actually reports. Once we have heard it from a new process there is nothing
@@ -31760,7 +32469,7 @@ var init_codex_app_server = __esm({
31760
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)) {
31761
32470
  throw new Error("Codex app-server thread slug or session id is already bound");
31762
32471
  }
31763
- const connection = await this.ensureConnected();
32472
+ const connection = await this.ensureCurrentAuth();
31764
32473
  const ephemeral = input.ephemeral ?? true;
31765
32474
  const startedSandbox = input.permissions ? null : input.sandbox ?? "read-only";
31766
32475
  const response = ThreadResponse.parse(await connection.request("thread/start", {
@@ -31905,7 +32614,7 @@ var init_codex_app_server = __esm({
31905
32614
  const releaseOperation = this.beginOperation();
31906
32615
  this.startingTurns.add(startKey);
31907
32616
  try {
31908
- const connection = await this.ensureConnected();
32617
+ const connection = await this.ensureCurrentAuth();
31909
32618
  let binding = this.bindingForScope(input.threadSlug, input.sessionId);
31910
32619
  if (!binding) throw new Error("Codex app-server turn requires a bridge-owned session");
31911
32620
  if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
@@ -31939,6 +32648,7 @@ var init_codex_app_server = __esm({
31939
32648
  return { turnId: response.turn.id };
31940
32649
  } finally {
31941
32650
  this.pendingTurnStarts.delete(pendingKey);
32651
+ this.notifyTurnIdleWaiters();
31942
32652
  }
31943
32653
  } finally {
31944
32654
  this.startingTurns.delete(startKey);
@@ -32338,6 +33048,8 @@ var init_codex_app_server = __esm({
32338
33048
  this.openingConnection.close();
32339
33049
  this.openingConnection = null;
32340
33050
  }
33051
+ this.processAuthAccountId = void 0;
33052
+ this.notifyTurnIdleWaiters();
32341
33053
  }
32342
33054
  }
32343
33055
  bindingForScope(threadSlug, sessionId) {
@@ -32363,11 +33075,61 @@ var init_codex_app_server = __esm({
32363
33075
  this.connecting = null;
32364
33076
  }
32365
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
+ }
32366
33127
  /**
32367
33128
  * Attach to an app-server and negotiate.
32368
33129
  *
32369
- * `refork` is the version-skew recovery, and it is the ONLY thing in frizz that ever ends a Codex
32370
- * 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
32371
33133
  * lives (up to six hours idle, unbounded while a client keeps reattaching). Bump
32372
33134
  * CODEX_APP_SERVER_SUPPORTED_VERSION and Update & Restart — the ordinary upgrade path — and the
32373
33135
  * surviving daemon happily serves the STALE userAgent to every new generation, so the gate below
@@ -32377,6 +33139,7 @@ var init_codex_app_server = __esm({
32377
33139
  * for why a genuinely unsupported codex still fails loudly instead of reforking in a loop.
32378
33140
  */
32379
33141
  async connect(refork = false) {
33142
+ const authAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
32380
33143
  const attachment = await this.host({
32381
33144
  projectId: this.options.projectId,
32382
33145
  stateDir: this.options.stateDir ?? this.options.projectDir,
@@ -32387,7 +33150,8 @@ var init_codex_app_server = __esm({
32387
33150
  env: codexAppServerEnvironment(),
32388
33151
  clientInfo: CLIENT_INFO,
32389
33152
  capabilities: CLIENT_CAPABILITIES,
32390
- frizzMcp: this.options.frizzMcp
33153
+ frizzMcp: this.options.frizzMcp,
33154
+ authAccountId
32391
33155
  });
32392
33156
  const child = attachment.process;
32393
33157
  let connection;
@@ -32450,6 +33214,7 @@ var init_codex_app_server = __esm({
32450
33214
  this.connectionEpoch = negotiated.connectionEpoch;
32451
33215
  this.capabilityRevision = negotiated.capabilityRevision;
32452
33216
  this.daemonGeneration = attachment.generation;
33217
+ this.processAuthAccountId = attachment.authAccountId;
32453
33218
  if (this.closed) throw new Error("Codex app-server bridge closed during negotiation");
32454
33219
  this.connection = connection;
32455
33220
  this.openingConnection = null;
@@ -32489,6 +33254,7 @@ var init_codex_app_server = __esm({
32489
33254
  if (this.connection !== connection) return;
32490
33255
  const epoch = this.connectionEpoch;
32491
33256
  this.connection = null;
33257
+ this.processAuthAccountId = void 0;
32492
33258
  this.forgetCorrelatedFileItems();
32493
33259
  this.releaseSettingsWaiters();
32494
33260
  if (this.closed || this.dbReleased) return;
@@ -32496,6 +33262,7 @@ var init_codex_app_server = __esm({
32496
33262
  UPDATE codex_app_server_session SET state = 'detached', updated_at = ?
32497
33263
  WHERE project_id = @project_id AND connection_epoch = ? AND state = 'active'
32498
33264
  `).run(this.now().toISOString(), epoch);
33265
+ this.notifyTurnIdleWaiters();
32499
33266
  this.options.diagnostic?.({ event: "disconnected", connectionEpoch: epoch, reason });
32500
33267
  }
32501
33268
  /**
@@ -33353,6 +34120,7 @@ var init_codex_app_server = __esm({
33353
34120
  UPDATE codex_app_server_session SET current_turn_id = NULL, auto_resume_count = 0, updated_at = ?
33354
34121
  WHERE project_id = @project_id AND codex_thread_id = ? AND connection_epoch = ? AND current_turn_id = ?
33355
34122
  `).run(this.now().toISOString(), parsed.data.threadId, this.connectionEpoch, parsed.data.turn.id);
34123
+ this.notifyTurnIdleWaiters();
33356
34124
  }
33357
34125
  }
33358
34126
  providerResponse(record, delivery2, input) {
@@ -33426,11 +34194,11 @@ var init_codex_app_server = __esm({
33426
34194
  });
33427
34195
 
33428
34196
  // packages/server/src/backend/codex-quota.ts
33429
- import { join as join22 } from "node:path";
33430
- 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";
33431
34199
  import { spawn as spawn4 } from "node:child_process";
33432
34200
  function sessionsDir2(codexHome) {
33433
- return join22(codexHome, "sessions");
34201
+ return join24(codexHome, "sessions");
33434
34202
  }
33435
34203
  function newestRollouts(dir, out, budget) {
33436
34204
  if (budget.n <= 0) return;
@@ -33444,610 +34212,206 @@ function newestRollouts(dir, out, budget) {
33444
34212
  const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
33445
34213
  for (const d of dirs) {
33446
34214
  if (budget.n <= 0) return;
33447
- newestRollouts(join22(dir, d), out, budget);
34215
+ newestRollouts(join24(dir, d), out, budget);
33448
34216
  }
33449
34217
  for (const f of files) {
33450
34218
  if (budget.n <= 0) return;
33451
- out.push(join22(dir, f));
34219
+ out.push(join24(dir, f));
33452
34220
  budget.n--;
33453
34221
  }
33454
34222
  }
33455
- function readTail(path3) {
33456
- let fd;
33457
- try {
33458
- fd = openSync12(path3, "r");
33459
- const size = fstatSync(fd).size;
33460
- const start = Math.max(0, size - TAIL_BYTES);
33461
- const len = size - start;
33462
- const buf = Buffer.allocUnsafe(len);
33463
- let read = 0;
33464
- while (read < len) {
33465
- const n = readSync6(fd, buf, read, len - read, start + read);
33466
- if (n <= 0) break;
33467
- read += n;
33468
- }
33469
- return buf.toString("utf8", 0, read);
33470
- } catch {
33471
- return "";
33472
- } finally {
33473
- if (fd !== void 0) {
33474
- try {
33475
- closeSync11(fd);
33476
- } catch {
33477
- }
33478
- }
33479
- }
33480
- }
33481
- function makeWindow(used, minutes, resetsAt) {
33482
- if (used === void 0) return void 0;
33483
- const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
33484
- const label = key === "weekly" ? "Weekly" : key;
33485
- return { key, label, usedPercent: used, resetsAt };
33486
- }
33487
- function toWindow(raw2) {
33488
- if (!raw2 || typeof raw2 !== "object") return void 0;
33489
- const w = raw2;
33490
- return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
33491
- }
33492
- function parseCodexQuotaFromRollout(raw2) {
33493
- const lines = raw2.split("\n");
33494
- for (let i = lines.length - 1; i >= 0; i--) {
33495
- const line = lines[i];
33496
- if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
33497
- let obj;
33498
- try {
33499
- obj = JSON.parse(line);
33500
- } catch {
33501
- continue;
33502
- }
33503
- if (!obj || typeof obj !== "object") continue;
33504
- const top = obj;
33505
- const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
33506
- const rl = top.rate_limits ?? payload?.rate_limits;
33507
- if (!rl || typeof rl !== "object") continue;
33508
- const r = rl;
33509
- const windows = [toWindow(r.primary), toWindow(r.secondary)].filter((x) => x !== void 0);
33510
- if (windows.length === 0) continue;
33511
- const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
33512
- return { status: "ok", planType, windows };
33513
- }
33514
- return void 0;
33515
- }
33516
- function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
33517
- let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
33518
- try {
33519
- const rollouts = [];
33520
- newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
33521
- const byMtime = rollouts.map((path3) => {
33522
- try {
33523
- return { path: path3, mtimeMs: statSync10(path3).mtimeMs };
33524
- } catch {
33525
- return void 0;
33526
- }
33527
- }).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
33528
- for (const { path: path3 } of byMtime) {
33529
- const parsed = parseCodexQuotaFromRollout(readTail(path3));
33530
- if (parsed) {
33531
- quota = parsed;
33532
- break;
33533
- }
33534
- }
33535
- } catch {
33536
- quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
33537
- }
33538
- return quota;
33539
- }
33540
- function toLiveWindow(raw2) {
33541
- if (!raw2 || typeof raw2 !== "object") return void 0;
33542
- const w = raw2;
33543
- return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
33544
- }
33545
- function parseCodexQuotaFromRateLimits(result) {
33546
- if (!result || typeof result !== "object") return void 0;
33547
- const rl = result.rateLimits;
33548
- if (!rl || typeof rl !== "object") return void 0;
33549
- const r = rl;
33550
- const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
33551
- if (windows.length === 0) return void 0;
33552
- const planType = typeof r.planType === "string" ? r.planType : void 0;
33553
- return { status: "ok", planType, windows };
33554
- }
33555
- function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
33556
- return new Promise((resolve12) => {
33557
- let child;
33558
- try {
33559
- child = spawn4(codexBin, ["app-server"], {
33560
- stdio: ["pipe", "pipe", "ignore"],
33561
- env: { ...process.env, CODEX_HOME: codexHome }
33562
- });
33563
- } catch {
33564
- resolve12(void 0);
33565
- return;
33566
- }
33567
- let settled = false;
33568
- const finish = (quota) => {
33569
- if (settled) return;
33570
- settled = true;
33571
- clearTimeout(timer);
33572
- try {
33573
- child.kill("SIGKILL");
33574
- } catch {
33575
- }
33576
- resolve12(quota);
33577
- };
33578
- const timer = setTimeout(() => finish(void 0), timeoutMs);
33579
- const send = (msg) => {
33580
- try {
33581
- child.stdin?.write(JSON.stringify(msg) + "\n");
33582
- } catch {
33583
- finish(void 0);
33584
- }
33585
- };
33586
- child.on("error", () => finish(void 0));
33587
- child.on("exit", () => finish(void 0));
33588
- let buf = "";
33589
- child.stdout?.on("data", (chunk) => {
33590
- buf += chunk.toString("utf8");
33591
- if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
33592
- let nl;
33593
- while ((nl = buf.indexOf("\n")) >= 0) {
33594
- const line = buf.slice(0, nl);
33595
- buf = buf.slice(nl + 1);
33596
- if (!line.trim()) continue;
33597
- let msg;
33598
- try {
33599
- msg = JSON.parse(line);
33600
- } catch {
33601
- continue;
33602
- }
33603
- if (msg.id === 1) {
33604
- send({ jsonrpc: "2.0", method: "initialized" });
33605
- send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
33606
- } else if (msg.id === 2) {
33607
- finish(parseCodexQuotaFromRateLimits(msg.result));
33608
- }
33609
- }
33610
- });
33611
- send({
33612
- jsonrpc: "2.0",
33613
- id: 1,
33614
- method: "initialize",
33615
- params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
33616
- });
33617
- });
33618
- }
33619
- async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
33620
- const now = Date.now();
33621
- const hit = memo2.get(codexHome);
33622
- if (hit && now - hit.at < TTL_MS2) return hit.quota;
33623
- let quota;
33624
- try {
33625
- quota = await queryCodexRateLimits(codexHome, codexBin);
33626
- } catch {
33627
- quota = void 0;
33628
- }
33629
- if (!quota) {
33630
- const fallback = readCodexQuotaFromRollouts(codexHome);
33631
- quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
33632
- }
33633
- memo2.set(codexHome, { at: now, quota });
33634
- return quota;
33635
- }
33636
- var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
33637
- var init_codex_quota = __esm({
33638
- "packages/server/src/backend/codex-quota.ts"() {
33639
- "use strict";
33640
- init_codex();
33641
- init_codex_app_server();
33642
- descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
33643
- TAIL_BYTES = 512 * 1024;
33644
- num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
33645
- TTL_MS2 = 2e4;
33646
- memo2 = /* @__PURE__ */ new Map();
33647
- }
33648
- });
33649
-
33650
- // packages/server/src/backend/claude-quota.ts
33651
- import { join as join23 } from "node:path";
33652
- import { homedir as homedir9, platform } from "node:os";
33653
- import { createHash as createHash10 } from "node:crypto";
33654
- import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
33655
- import { execFile as execFile3 } from "node:child_process";
33656
- import { promisify as promisify3 } from "node:util";
33657
- function claudeConfigDir() {
33658
- const override = process.env.CLAUDE_CONFIG_DIR;
33659
- return override && override.trim() ? override : join23(homedir9(), ".claude");
33660
- }
33661
- function tokenFromCredentialsJson(raw2) {
33662
- let doc;
33663
- try {
33664
- doc = JSON.parse(raw2);
33665
- } catch {
33666
- return void 0;
33667
- }
33668
- if (!doc || typeof doc !== "object") return void 0;
33669
- const root = doc;
33670
- const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
33671
- const token = oauth.accessToken ?? oauth.access_token;
33672
- return typeof token === "string" && token ? token : void 0;
33673
- }
33674
- async function readKeychainToken() {
33675
- if (platform() !== "darwin") return void 0;
33676
- try {
33677
- const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
33678
- encoding: "utf8",
33679
- timeout: 4e3
33680
- });
33681
- return tokenFromCredentialsJson(String(stdout).trim());
33682
- } catch {
33683
- return void 0;
33684
- }
33685
- }
33686
- async function readAccessToken(configDir) {
33687
- try {
33688
- const fromFile = tokenFromCredentialsJson(await readFile(join23(configDir, ".credentials.json"), "utf8"));
33689
- if (fromFile) return fromFile;
33690
- } catch {
33691
- }
33692
- return readKeychainToken();
33693
- }
33694
- function isoToUnix(value) {
33695
- const ms = typeof value === "string" ? Date.parse(value) : NaN;
33696
- return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
33697
- }
33698
- function slugify2(name) {
33699
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
33700
- }
33701
- function toWindow2(raw2, key, label) {
33702
- if (!raw2 || typeof raw2 !== "object") return void 0;
33703
- const w = raw2;
33704
- const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
33705
- if (util2 === void 0) return void 0;
33706
- const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
33707
- return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
33708
- }
33709
- function windowFromLimit(raw2) {
33710
- if (!raw2 || typeof raw2 !== "object") return void 0;
33711
- const l = raw2;
33712
- const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
33713
- if (percent === void 0) return void 0;
33714
- const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
33715
- const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
33716
- const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
33717
- const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
33718
- let key;
33719
- let label;
33720
- if (group === "session") {
33721
- key = "5h";
33722
- label = "5h";
33723
- } else if (group === "weekly") {
33724
- key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
33725
- label = scopeName ? `${scopeName} wk` : "Weekly";
33726
- } else if (group) {
33727
- key = slugify2(group);
33728
- label = scopeName ? `${scopeName} ${group}` : group;
33729
- } else {
33730
- return void 0;
33731
- }
33732
- const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
33733
- return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
33734
- }
33735
- function parseClaudeUsage(body, planType) {
33736
- if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
33737
- const b = body;
33738
- const windows = [];
33739
- const seen = /* @__PURE__ */ new Set();
33740
- const add = (w) => {
33741
- if (w && !seen.has(w.key)) {
33742
- seen.add(w.key);
33743
- windows.push(w);
33744
- }
33745
- };
33746
- if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
33747
- add(toWindow2(b.five_hour, "5h", "5h"));
33748
- add(toWindow2(b.seven_day, "weekly", "Weekly"));
33749
- add(toWindow2(b.seven_day_opus, "weekly-opus", "Opus wk"));
33750
- add(toWindow2(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
33751
- if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
33752
- return { status: "ok", planType, windows };
33753
- }
33754
- function cachePaths(cacheDir, configDir) {
33755
- const profile = createHash10("sha256").update(configDir).digest("hex").slice(0, 12);
33756
- return {
33757
- data: join23(cacheDir, `claude-${profile}.json`),
33758
- lock: join23(cacheDir, `claude-${profile}.lock`)
33759
- };
33760
- }
33761
- async function readShared(path3) {
33762
- try {
33763
- const parsed = JSON.parse(await readFile(path3, "utf8"));
33764
- if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
33765
- return parsed;
33766
- } catch {
33767
- return void 0;
33768
- }
33769
- }
33770
- async function writeShared(path3, value) {
33771
- const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
33772
- try {
33773
- await writeFile(tmp, JSON.stringify(value), { mode: 384 });
33774
- await rename(tmp, path3);
33775
- } finally {
33776
- await rm(tmp, { force: true }).catch(() => {
33777
- });
33778
- }
33779
- }
33780
- function delay(ms) {
33781
- return new Promise((resolve12) => setTimeout(resolve12, ms));
33782
- }
33783
- async function acquireLock(path3) {
33784
- const deadline = Date.now() + LOCK_WAIT_MS;
33785
- for (; ; ) {
33786
- try {
33787
- await mkdir(path3);
33788
- return () => rm(path3, { recursive: true, force: true });
33789
- } catch (err) {
33790
- if (err.code !== "EEXIST") throw err;
33791
- try {
33792
- if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
33793
- await rm(path3, { recursive: true, force: true });
33794
- continue;
33795
- }
33796
- } catch {
33797
- continue;
33798
- }
33799
- if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
33800
- await delay(LOCK_POLL_MS2);
33801
- }
33802
- }
33803
- }
33804
- async function runClaudeUsage(claudeBin) {
33805
- const { stdout } = await execFileAsync2(
33806
- // Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
33807
- // EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
33808
- // never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
33809
- // A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
33810
- resolveClaudeExecutableAbsolute(claudeBin),
33811
- ["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
33812
- { encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
33813
- );
33814
- return String(stdout);
33815
- }
33816
- async function refreshQuota(claudeBin, deps, now) {
33817
- const doFetch = deps.fetchImpl ?? fetch;
33818
- const readToken = deps.readToken ?? readAccessToken;
33819
- let token;
33820
- try {
33821
- token = await readToken(claudeConfigDir());
33822
- } catch {
33823
- token = void 0;
33824
- }
33825
- if (token) {
33826
- try {
33827
- const res = await doFetch(USAGE_URL, {
33828
- headers: {
33829
- Authorization: `Bearer ${token}`,
33830
- "anthropic-beta": OAUTH_BETA,
33831
- "User-Agent": USER_AGENT,
33832
- "Content-Type": "application/json"
33833
- },
33834
- signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
33835
- });
33836
- if (res.ok) {
33837
- const body = await res.json();
33838
- const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
33839
- const quota2 = parseClaudeUsage(body, planType);
33840
- if (quota2.status === "ok") return quota2;
33841
- } else if (res.status === 429 || res.status === 529) {
33842
- throw new Error(`Usage endpoint ${res.status}`);
33843
- }
33844
- } catch (err) {
33845
- if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
33846
- }
33847
- }
33848
- const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
33849
- if (quota.status !== "ok") throw new Error(quota.detail);
33850
- return quota;
33851
- }
33852
- function windowIdentity(title) {
33853
- for (const [pattern, key, label] of LINE_WINDOWS) {
33854
- if (pattern.test(title)) return [key, label];
33855
- }
33856
- const scoped = /^Current week \((.+)\)$/i.exec(title);
33857
- if (!scoped) return void 0;
33858
- const name = scoped[1].replace(/\s+only$/i, "");
33859
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
33860
- return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
33861
- }
33862
- function parseResetLabel(label, now) {
33863
- if (!label) return void 0;
33864
- const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
33865
- 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);
33866
- const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
33867
- const hasClock = parts?.[6] !== void 0;
33868
- if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
33869
- const date2 = new Date(now);
33870
- if (hasClock) {
33871
- let hour = Number(parts[4]) % 12;
33872
- if (parts[6].toLowerCase() === "pm") hour += 12;
33873
- date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
33874
- } else {
33875
- date2.setHours(0, 0, 0, 0);
33876
- }
33877
- if (month !== void 0) {
33878
- date2.setMonth(month, Number(parts[2]));
33879
- if (parts[3]) date2.setFullYear(Number(parts[3]));
33880
- else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
33881
- } else if (date2.getTime() <= now) {
33882
- date2.setDate(date2.getDate() + 1);
33883
- }
33884
- return Math.round(date2.getTime() / 1e3);
33885
- }
33886
- const parsed = Date.parse(text2);
33887
- if (!Number.isFinite(parsed)) return void 0;
33888
- const date = new Date(parsed);
33889
- if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
33890
- return Math.round(date.getTime() / 1e3);
33891
- }
33892
- function parseClaudeUsageOutput(stdout, now = Date.now()) {
33893
- let text2 = stdout;
33894
- try {
33895
- const envelope = JSON.parse(stdout);
33896
- if (typeof envelope.result === "string") text2 = envelope.result;
33897
- } catch {
33898
- }
33899
- const windows = [];
33900
- for (const line of text2.split(/\r?\n/)) {
33901
- const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
33902
- if (!match2) continue;
33903
- const identity = windowIdentity(match2[1]);
33904
- if (!identity) continue;
33905
- const resetsAt = parseResetLabel(match2[3], now);
33906
- windows.push({
33907
- key: identity[0],
33908
- label: identity[1],
33909
- usedPercent: Number(match2[2]),
33910
- ...resetsAt === void 0 ? {} : { resetsAt }
33911
- });
33912
- }
33913
- if (windows.length === 0) {
33914
- return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
33915
- }
33916
- return { status: "ok", windows };
33917
- }
33918
- async function tryAcquireLock2(path3) {
33919
- for (let attempt = 0; attempt < 2; attempt++) {
33920
- try {
33921
- await mkdir(path3);
33922
- return () => rm(path3, { recursive: true, force: true });
33923
- } catch (err) {
33924
- 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) {
33925
34242
  try {
33926
- if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
33927
- await rm(path3, { recursive: true, force: true });
34243
+ closeSync11(fd);
33928
34244
  } catch {
33929
- return void 0;
33930
34245
  }
33931
34246
  }
33932
34247
  }
33933
- return void 0;
33934
34248
  }
33935
- function refreshSharedInBackground(paths, claudeBin, deps, now) {
33936
- if (backgroundRefreshes.has(paths.data)) return;
33937
- const task = (async () => {
33938
- const release = await tryAcquireLock2(paths.lock);
33939
- 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;
33940
34266
  try {
33941
- const quota = await refreshQuota(claudeBin, deps, now);
33942
- await writeShared(paths.data, { at: now, quota });
33943
- } finally {
33944
- await release().catch(() => {
33945
- });
34267
+ obj = JSON.parse(line);
34268
+ } catch {
34269
+ continue;
33946
34270
  }
33947
- })().catch(() => {
33948
- }).finally(() => backgroundRefreshes.delete(paths.data));
33949
- 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;
33950
34283
  }
33951
- async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
33952
- const now = (deps.now ?? Date.now)();
33953
- const configDir = claudeConfigDir();
33954
- 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" };
33955
34286
  try {
33956
- 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
+ }
33957
34303
  } catch {
33958
- return;
34304
+ quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
33959
34305
  }
33960
- refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
34306
+ return quota;
33961
34307
  }
33962
- async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
33963
- const now = (deps.now ?? Date.now)();
33964
- const configDir = claudeConfigDir();
33965
- const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
33966
- const paths = cachePaths(cacheDir, configDir);
33967
- try {
33968
- await mkdir(cacheDir, { recursive: true, mode: 448 });
33969
- } 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;
33970
34326
  try {
33971
- 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
+ });
33972
34331
  } catch {
33973
- return { status: "unavailable", windows: [], detail: "Claude usage unavailable" };
33974
- }
33975
- }
33976
- const initial = await readShared(paths.data);
33977
- if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
33978
- return initial.quota;
33979
- }
33980
- if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
33981
- refreshSharedInBackground(paths, claudeBin, deps, now);
33982
- const ageMs = now - initial.at;
33983
- if (ageMs < 10 * 6e4) return initial.quota;
33984
- return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
33985
- }
33986
- let release;
33987
- try {
33988
- release = await acquireLock(paths.lock);
33989
- const afterLock = await readShared(paths.data);
33990
- if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
33991
- const quota = await refreshQuota(claudeBin, deps, now);
33992
- await writeShared(paths.data, { at: now, quota }).catch(() => {
33993
- });
33994
- return quota;
33995
- } catch {
33996
- const fallback = await readShared(paths.data);
33997
- if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
33998
- return {
33999
- ...fallback.quota,
34000
- detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
34001
- };
34332
+ resolve12(void 0);
34333
+ return;
34002
34334
  }
34003
- const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
34004
- 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
+ }
34005
34378
  });
34006
- return unavailable;
34007
- } finally {
34008
- await release?.().catch(() => {
34379
+ send({
34380
+ jsonrpc: "2.0",
34381
+ id: 1,
34382
+ method: "initialize",
34383
+ params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
34009
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;
34010
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;
34011
34403
  }
34012
- 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;
34013
- var init_claude_quota = __esm({
34014
- "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"() {
34015
34407
  "use strict";
34016
- init_frizz_paths();
34017
- init_claude_broker_host();
34018
- execFileAsync2 = promisify3(execFile3);
34019
- USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
34020
- OAUTH_BETA = "oauth-2025-04-20";
34021
- USER_AGENT = "claude-code/2.0.0";
34022
- ENDPOINT_TIMEOUT_MS = 5e3;
34023
- OK_TTL_MS = 2 * 6e4;
34024
- FAIL_TTL_MS = 1e4;
34025
- STALE_MAX_AGE_MS = 24 * 60 * 6e4;
34026
- CLI_TIMEOUT_MS = 3e4;
34027
- LOCK_STALE_MS = 4e4;
34028
- LOCK_WAIT_MS = 15e3;
34029
- LOCK_POLL_MS2 = 50;
34030
- LINE_WINDOWS = [
34031
- [/^Current session$/i, "5h", "5h"],
34032
- [/^Current week \(all models\)$/i, "weekly", "Weekly"],
34033
- [/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
34034
- [/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
34035
- ];
34036
- MONTH_INDEX = {
34037
- jan: 0,
34038
- feb: 1,
34039
- mar: 2,
34040
- apr: 3,
34041
- may: 4,
34042
- jun: 5,
34043
- jul: 6,
34044
- aug: 7,
34045
- sep: 8,
34046
- oct: 9,
34047
- nov: 10,
34048
- dec: 11
34049
- };
34050
- 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();
34051
34415
  }
34052
34416
  });
34053
34417
 
@@ -34120,10 +34484,10 @@ var init_quota = __esm({
34120
34484
  });
34121
34485
 
34122
34486
  // packages/server/src/frizz.ts
34123
- import { execFile as execFile4 } from "node:child_process";
34487
+ import { execFile as execFile5 } from "node:child_process";
34124
34488
  import { lstatSync as lstatSync2, realpathSync as realpathSync6 } from "node:fs";
34125
- import { basename as basename5, dirname as dirname9, join as join24, resolve as resolve5 } from "node:path";
34126
- 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";
34127
34491
  function frizzScriptsDir() {
34128
34492
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
34129
34493
  return resolve5(import.meta.dirname, "..", "..", "..", "board");
@@ -34131,7 +34495,7 @@ function frizzScriptsDir() {
34131
34495
  function directFrizzRoot(projectDir) {
34132
34496
  try {
34133
34497
  const projectRoot = realpathSync6(projectDir);
34134
- const path3 = join24(projectRoot, ".frizz");
34498
+ const path3 = join25(projectRoot, ".frizz");
34135
34499
  const stat2 = lstatSync2(path3);
34136
34500
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
34137
34501
  const real = realpathSync6(path3);
@@ -34145,7 +34509,7 @@ function frizzDirExists(projectDir) {
34145
34509
  }
34146
34510
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34147
34511
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
34148
- const { stdout } = await execFileP("node", [join24(scriptsDir, "index.mjs"), "--json"], {
34512
+ const { stdout } = await execFileP("node", [join25(scriptsDir, "index.mjs"), "--json"], {
34149
34513
  cwd: projectDir,
34150
34514
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
34151
34515
  maxBuffer: 32 * 1024 * 1024
@@ -34170,7 +34534,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34170
34534
  };
34171
34535
  }
34172
34536
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
34173
- await execFileP("node", [join24(scriptsDir, "thread-update.mjs"), slug, ...args], {
34537
+ await execFileP("node", [join25(scriptsDir, "thread-update.mjs"), slug, ...args], {
34174
34538
  cwd: projectDir,
34175
34539
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
34176
34540
  });
@@ -34180,7 +34544,7 @@ var init_frizz = __esm({
34180
34544
  "packages/server/src/frizz.ts"() {
34181
34545
  "use strict";
34182
34546
  init_src();
34183
- execFileP = promisify4(execFile4);
34547
+ execFileP = promisify5(execFile5);
34184
34548
  }
34185
34549
  });
34186
34550
 
@@ -34255,12 +34619,12 @@ var init_awaiting = __esm({
34255
34619
  });
34256
34620
 
34257
34621
  // packages/server/src/backend/project-mcp-servers.ts
34258
- import { readFileSync as readFileSync16 } from "node:fs";
34259
- import { homedir as homedir10 } from "node:os";
34260
- 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";
34261
34625
  function readJsonObject(path3) {
34262
34626
  try {
34263
- const parsed = JSON.parse(readFileSync16(path3, "utf8"));
34627
+ const parsed = JSON.parse(readFileSync17(path3, "utf8"));
34264
34628
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
34265
34629
  } catch {
34266
34630
  return void 0;
@@ -34351,17 +34715,17 @@ function userScopeRemoteServers(account, cwd, env) {
34351
34715
  }
34352
34716
  function projectMcpServers(cwd, options = {}) {
34353
34717
  const env = options.env ?? process.env;
34354
- const home = options.home ?? homedir10();
34718
+ const home = options.home ?? homedir11();
34355
34719
  const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
34356
- const accountFile = configOverride ? join25(configOverride, ".claude.json") : join25(home, ".claude.json");
34357
- 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");
34358
34722
  const account = readJsonObject(accountFile);
34359
34723
  const out = userScopeRemoteServers(account, cwd, env);
34360
- const declared = objectField(readJsonObject(join25(cwd, ".mcp.json")), "mcpServers");
34724
+ const declared = objectField(readJsonObject(join26(cwd, ".mcp.json")), "mcpServers");
34361
34725
  if (!declared) return out;
34362
34726
  const approval = collectApproval([
34363
- readJsonObject(join25(cwd, ".claude", "settings.json")),
34364
- readJsonObject(join25(cwd, ".claude", "settings.local.json")),
34727
+ readJsonObject(join26(cwd, ".claude", "settings.json")),
34728
+ readJsonObject(join26(cwd, ".claude", "settings.local.json")),
34365
34729
  readJsonObject(userSettingsFile),
34366
34730
  objectField(objectField(account, "projects"), cwd)
34367
34731
  ]);
@@ -34386,16 +34750,16 @@ var init_project_mcp_servers = __esm({
34386
34750
 
34387
34751
  // packages/server/src/worker-plugin-dir.ts
34388
34752
  import { existsSync as existsSync8 } from "node:fs";
34389
- import { dirname as dirname10, join as join26 } from "node:path";
34753
+ import { dirname as dirname10, join as join27 } from "node:path";
34390
34754
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34391
34755
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
34392
34756
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
34393
- if (override && existsSync8(join26(override, ".claude-plugin", "plugin.json")))
34757
+ if (override && existsSync8(join27(override, ".claude-plugin", "plugin.json")))
34394
34758
  return override;
34395
34759
  let current = dirname10(fileURLToPath2(moduleUrl));
34396
34760
  for (; ; ) {
34397
- const candidate = join26(current, "cc-worker");
34398
- 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;
34399
34763
  const parent = dirname10(current);
34400
34764
  if (parent === current) return void 0;
34401
34765
  current = parent;
@@ -35549,12 +35913,12 @@ var init_claude_broker_client = __esm({
35549
35913
  });
35550
35914
 
35551
35915
  // packages/server/src/backend/claude-permission-interactions.ts
35552
- import { homedir as homedir11 } from "node:os";
35916
+ import { homedir as homedir12 } from "node:os";
35553
35917
  function clip(text2, max) {
35554
35918
  return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
35555
35919
  }
35556
35920
  function tildePath(path3) {
35557
- const home = homedir11();
35921
+ const home = homedir12();
35558
35922
  if (!path3.startsWith(home)) return path3;
35559
35923
  const rest = path3.slice(home.length);
35560
35924
  return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
@@ -36203,208 +36567,6 @@ var init_claude_agent_broker_bridge = __esm({
36203
36567
  }
36204
36568
  });
36205
36569
 
36206
- // packages/server/src/backend/auth-status.ts
36207
- import { join as join27 } from "node:path";
36208
- import { homedir as homedir12, platform as platform2 } from "node:os";
36209
- import { readFileSync as readFileSync17, statSync as statSync11 } from "node:fs";
36210
- import { execFile as execFile5 } from "node:child_process";
36211
- import { promisify as promisify5 } from "node:util";
36212
- function claudeConfigDir2() {
36213
- const override = process.env.CLAUDE_CONFIG_DIR;
36214
- return override && override.trim() ? override : join27(homedir12(), ".claude");
36215
- }
36216
- function claudeFileState(configDir) {
36217
- let raw2;
36218
- try {
36219
- raw2 = readFileSync17(join27(configDir, ".credentials.json"), "utf8");
36220
- } catch (err) {
36221
- return err.code === "ENOENT" ? "absent" : "error";
36222
- }
36223
- return tokenFromCredentialsJson(raw2) ? "token" : "empty";
36224
- }
36225
- async function claudeKeychainState() {
36226
- if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
36227
- if (platform2() !== "darwin") return "absent";
36228
- try {
36229
- const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
36230
- encoding: "utf8",
36231
- timeout: 4e3
36232
- });
36233
- return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
36234
- } catch (err) {
36235
- const e = err;
36236
- const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
36237
- return notFound ? "absent" : "error";
36238
- }
36239
- }
36240
- async function readClaudeAuthState(configDir = claudeConfigDir2()) {
36241
- const file = claudeFileState(configDir);
36242
- if (file === "token") return "authed";
36243
- const keychain = await claudeKeychainState();
36244
- if (keychain === "token") return "authed";
36245
- if (file === "error" || keychain === "error") return "unknown";
36246
- return "signed-out";
36247
- }
36248
- function readCodexAuthState(codexHome = defaultCodexHome()) {
36249
- if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
36250
- let raw2;
36251
- try {
36252
- raw2 = readFileSync17(join27(codexHome, "auth.json"), "utf8");
36253
- } catch (err) {
36254
- return err.code === "ENOENT" ? "signed-out" : "unknown";
36255
- }
36256
- let doc;
36257
- try {
36258
- doc = JSON.parse(raw2);
36259
- } catch {
36260
- return "unknown";
36261
- }
36262
- if (!doc || typeof doc !== "object") return "unknown";
36263
- const root = doc;
36264
- const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
36265
- const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
36266
- const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
36267
- return apiKey || accessToken ? "authed" : "signed-out";
36268
- }
36269
- async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
36270
- try {
36271
- await exec(codexBin, ["--version"], { timeout: 5e3 });
36272
- return "present";
36273
- } catch (err) {
36274
- return err.code === "ENOENT" ? "missing" : "unknown";
36275
- }
36276
- }
36277
- function parseClaudeAuthStatusJson(stdout) {
36278
- const start = stdout.indexOf("{");
36279
- const end = stdout.lastIndexOf("}");
36280
- if (start === -1 || end <= start) return void 0;
36281
- try {
36282
- const doc = JSON.parse(stdout.slice(start, end + 1));
36283
- return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
36284
- } catch {
36285
- return void 0;
36286
- }
36287
- }
36288
- async function readClaudeAuthStatusCli(opts) {
36289
- try {
36290
- const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
36291
- const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
36292
- encoding: "utf8",
36293
- timeout: opts?.timeoutMs ?? 5e3,
36294
- ...opts?.cwd ? { cwd: opts.cwd } : {}
36295
- });
36296
- return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
36297
- } catch (err) {
36298
- const stdout = err.stdout;
36299
- const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
36300
- if (loggedIn === false) return "signed-out";
36301
- if (loggedIn === true) return "authed";
36302
- return "unknown";
36303
- }
36304
- }
36305
- async function readClaudePreflightAuth(opts) {
36306
- const local = await readClaudeAuthState();
36307
- if (local !== "signed-out") return local;
36308
- return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
36309
- }
36310
- function asEmail(value) {
36311
- if (typeof value !== "string") return void 0;
36312
- const email = value.trim();
36313
- return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
36314
- }
36315
- function claudeAccountFile() {
36316
- const override = process.env.CLAUDE_CONFIG_DIR;
36317
- return override && override.trim() ? join27(override, ".claude.json") : join27(homedir12(), ".claude.json");
36318
- }
36319
- function readClaudeAccountEmail(path3 = claudeAccountFile()) {
36320
- let stat2;
36321
- try {
36322
- stat2 = statSync11(path3);
36323
- } catch {
36324
- return void 0;
36325
- }
36326
- const memo3 = claudeAccountMemo;
36327
- if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
36328
- let email;
36329
- try {
36330
- const doc = JSON.parse(readFileSync17(path3, "utf8"));
36331
- email = asEmail(doc?.oauthAccount?.emailAddress);
36332
- } catch {
36333
- email = void 0;
36334
- }
36335
- claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
36336
- return email;
36337
- }
36338
- function readCodexAccountEmail(codexHome = defaultCodexHome()) {
36339
- let doc;
36340
- try {
36341
- doc = JSON.parse(readFileSync17(join27(codexHome, "auth.json"), "utf8"));
36342
- } catch {
36343
- return void 0;
36344
- }
36345
- const idToken = doc?.tokens?.id_token;
36346
- if (typeof idToken !== "string") return void 0;
36347
- const payload = idToken.split(".")[1];
36348
- if (!payload) return void 0;
36349
- try {
36350
- return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
36351
- } catch {
36352
- return void 0;
36353
- }
36354
- }
36355
- function readAccountEmails() {
36356
- const emails = {};
36357
- const claude = tryRead(readClaudeAccountEmail);
36358
- const codex = tryRead(readCodexAccountEmail);
36359
- if (claude) emails.claude = claude;
36360
- if (codex) emails.codex = codex;
36361
- return emails;
36362
- }
36363
- function tryRead(read) {
36364
- try {
36365
- return read();
36366
- } catch {
36367
- return void 0;
36368
- }
36369
- }
36370
- async function readAuthSnapshot(opts) {
36371
- const [claude, codex] = await Promise.all([
36372
- readClaudeAuthState().then(async (local) => {
36373
- if (local !== "signed-out") return local;
36374
- const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
36375
- return cli === "authed" ? "authed" : local;
36376
- }).catch(() => "unknown"),
36377
- Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
36378
- ]);
36379
- const emails = readAccountEmails();
36380
- return {
36381
- claude,
36382
- codex,
36383
- emails: {
36384
- ...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
36385
- ...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
36386
- }
36387
- };
36388
- }
36389
- var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
36390
- var init_auth_status = __esm({
36391
- "packages/server/src/backend/auth-status.ts"() {
36392
- "use strict";
36393
- init_claude_quota();
36394
- init_codex();
36395
- init_claude_broker_host();
36396
- execFileAsync3 = promisify5(execFile5);
36397
- ProviderAuthRequiredError = class extends Error {
36398
- backend;
36399
- constructor(backend) {
36400
- super(`AUTH_REQUIRED:${backend}`);
36401
- this.name = "ProviderAuthRequiredError";
36402
- this.backend = backend;
36403
- }
36404
- };
36405
- }
36406
- });
36407
-
36408
36570
  // packages/server/src/dispatch.ts
36409
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";
36410
36572
  import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
@@ -36601,6 +36763,10 @@ function workerDispatchPermission(kind, settings) {
36601
36763
  if (kind === "claude" && settings.permissionMode === "bypassPermissions") return "bypassPermissions";
36602
36764
  return WORKER_DISPATCH_PERMISSION[kind];
36603
36765
  }
36766
+ function coldResumePermission(row, settings) {
36767
+ const saved = PermissionMode.safeParse(row.permission_mode);
36768
+ return saved.success ? saved.data : workerDispatchPermission("claude", settings);
36769
+ }
36604
36770
  function effectivePermissionMode(kind, mode) {
36605
36771
  if (kind === "claude") return workerPermissionMode(mode);
36606
36772
  if (mode === "plan" || mode === "bypassPermissions") return mode;
@@ -36912,7 +37078,14 @@ function createDispatcher(deps) {
36912
37078
  transcript_id: null,
36913
37079
  state: "open",
36914
37080
  meta: null,
36915
- 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())
36916
37089
  });
36917
37090
  deps.storage.setBackend(slug, backend);
36918
37091
  if (backend === "codex") deps.storage.setAgentSession(slug, externalId);
@@ -38935,15 +39108,33 @@ function isHeartbeatFenceId(fenceId) {
38935
39108
  function saidDone(tele) {
38936
39109
  return tele.lastFence?.kind === "done" || tele.lastAssistantAllDone === true;
38937
39110
  }
38938
- function threadSaidDone(storage, slug, tele) {
38939
- 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;
38940
39131
  }
38941
39132
  function armedTimerIdsOf(storage, slug) {
38942
39133
  return new Set(storage.listThreadTimers(slug, { armedOnly: true }).map((t) => t.id));
38943
39134
  }
38944
- 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) {
38945
39136
  if (tele.lastFence?.kind === "awaiting") return true;
38946
- return threadSaidDone(storage, slug, tele);
39137
+ return threadSaidDone(storage, slug, tele, armedAt);
38947
39138
  }
38948
39139
  function liveActivityOf(tele, registeredPrWatches, armedTimerIds) {
38949
39140
  const shells = /* @__PURE__ */ new Set();
@@ -39202,7 +39393,7 @@ function createScheduler(deps) {
39202
39393
  if (isStopHookFenceId(item.fenceId)) {
39203
39394
  const armed = armedRest(row);
39204
39395
  if (!armed || item.fenceId !== stopHookFenceId(armed.armedAt, tele.lastAssistantAt ?? "")) return "superseded";
39205
- 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";
39206
39397
  return tele.turn === "idle" ? "current-idle" : "current-busy";
39207
39398
  }
39208
39399
  if (isCompactFenceId(item.fenceId)) {
@@ -40059,7 +40250,7 @@ ${clock}`;
40059
40250
  const armed = armedSchedule(row);
40060
40251
  if (!armed || armed.dueAtMs > nowMs) continue;
40061
40252
  const beatTele = deps.tailer.get(row.slug);
40062
- if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele)) continue;
40253
+ if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele, armed.armedAt)) continue;
40063
40254
  if (openSchedulePrompt(row.slug, row.session_id)) continue;
40064
40255
  const beat = beatIndex(row.recurring_schedule_fired_at ?? null, armed);
40065
40256
  const fenceId = heartbeatFenceId(armed.armedAt, beat);
@@ -40109,7 +40300,7 @@ ${clock}`;
40109
40300
  if (tele.lastUserAt && Date.parse(tele.lastUserAt) >= Date.parse(restedAt)) continue;
40110
40301
  if (tele.authFault) continue;
40111
40302
  if (tele.apiFault) continue;
40112
- 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;
40113
40304
  if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
40114
40305
  const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
40115
40306
  if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
@@ -41486,7 +41677,7 @@ function deliverCodexWake(deps) {
41486
41677
  })();
41487
41678
  }
41488
41679
  function deliverClaudeBrokerWake(deps) {
41489
- const { bridge, slug, cwd, row, deliveryMessage, freshProcess } = deps;
41680
+ const { bridge, slug, cwd, row, settings, deliveryMessage, freshProcess } = deps;
41490
41681
  const appendSystemPrompt = [
41491
41682
  loadWorkerPrompt("claude"),
41492
41683
  scratchpadOrientation(row.session_id, "claude"),
@@ -41497,7 +41688,7 @@ function deliverClaudeBrokerWake(deps) {
41497
41688
  sessionId: row.session_id,
41498
41689
  cwd,
41499
41690
  text: deliveryMessage,
41500
- permissionMode: row.permission_mode ?? void 0,
41691
+ permissionMode: coldResumePermission(row, settings),
41501
41692
  appendSystemPrompt,
41502
41693
  model: row.model ?? void 0,
41503
41694
  effort: row.effort ?? void 0,
@@ -41849,6 +42040,7 @@ ${wakeDeliveryToken(deliveryId)}`;
41849
42040
  slug,
41850
42041
  cwd: project.dir,
41851
42042
  row,
42043
+ settings: getSettings(storage, home),
41852
42044
  deliveryMessage,
41853
42045
  // Recomputed here rather than carried on the delivery: the outbox stores a message, not a
41854
42046
  // runtime decision, and the tail is the live answer to "is this thread still behind a wall
@@ -45127,6 +45319,21 @@ function peerSessionQueuedKey(deliveredText, pendingKeys) {
45127
45319
  for (const key of pendingKeys) if (deliveredText.startsWith(key, PEER_SESSION_PREAMBLE.length)) return key;
45128
45320
  return void 0;
45129
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
+ }
45130
45337
  function retireStaleQueuedBubbles(messages, nowMs = Date.now()) {
45131
45338
  let stale = false;
45132
45339
  for (const m of messages) {
@@ -45315,6 +45522,19 @@ function createTranscriptFold(identityPrefix = "claude") {
45315
45522
  const i = out.indexOf(pending.message);
45316
45523
  if (i !== -1) out.splice(i, 1);
45317
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
+ }
45318
45538
  }
45319
45539
  return;
45320
45540
  }
@@ -45457,15 +45677,11 @@ ${block.text}` : block.text;
45457
45677
  return {
45458
45678
  ingest,
45459
45679
  finalize,
45460
- // 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:
45461
45681
  // the /ws push renders from here while the RPC page renders from there, and a push whose head sat
45462
45682
  // further forward than the page's would splice the human's ask straight back out of a card that had
45463
45683
  // just anchored on it (reconcileLiveMessages replaces the window wholesale).
45464
- messages: () => {
45465
- const all = projected();
45466
- const start = latestWindowStart(all);
45467
- return start === 0 ? all : all.slice(start);
45468
- },
45684
+ messages: () => latestTranscriptWindow(projected()),
45469
45685
  allMessages: projected,
45470
45686
  consumedBytes: () => offset + Buffer.byteLength(buffer)
45471
45687
  };
@@ -46091,6 +46307,7 @@ function projectCodexTranscript(raw2, identityPrefix = "codex") {
46091
46307
  let prevEventAt;
46092
46308
  let lastContextTokens;
46093
46309
  let openCompaction = null;
46310
+ let agentInstructionCount = 0;
46094
46311
  const openAssistant = (at, sourceId, prose = false) => {
46095
46312
  if (prose && cur?.text) cur = null;
46096
46313
  if (cur) return cur;
@@ -46268,6 +46485,23 @@ ${text2}`;
46268
46485
  });
46269
46486
  break;
46270
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
+ }
46271
46505
  case "compaction": {
46272
46506
  const m = compactionMessage(sourceId, ev.at, lastContextTokens, void 0);
46273
46507
  out.push(m);
@@ -46326,7 +46560,7 @@ function latestWindowStart(messages) {
46326
46560
  let boundary = -1;
46327
46561
  for (let i = tail - 1; i >= 0; i--) {
46328
46562
  const m = messages[i];
46329
- if (m.role === "user" && !m.wake && !m.queued) {
46563
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
46330
46564
  boundary = i;
46331
46565
  break;
46332
46566
  }
@@ -46345,6 +46579,7 @@ function latestWindowStart(messages) {
46345
46579
  function latestTranscriptWindow(messages) {
46346
46580
  const start = latestWindowStart(messages);
46347
46581
  if (start === 0) return [...messages];
46582
+ const earlierInstructions = messages.slice(0, start).filter((message) => message.agentInstruction);
46348
46583
  const pinned = [];
46349
46584
  for (let i = 0; i < start; i++) {
46350
46585
  const message = messages[i];
@@ -46363,7 +46598,7 @@ function latestTranscriptWindow(messages) {
46363
46598
  at: message.at
46364
46599
  });
46365
46600
  }
46366
- return [...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46601
+ return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46367
46602
  }
46368
46603
  function codexToolCall(name, input, callId) {
46369
46604
  if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
@@ -46492,7 +46727,7 @@ function codexReasoningCaption(text2) {
46492
46727
  function codexPeerMessageCall(label, target, obj) {
46493
46728
  const redacted = strField(obj.message);
46494
46729
  const encrypted = redacted === ENCRYPTED_PAYLOAD;
46495
- 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);
46496
46731
  return {
46497
46732
  name: label,
46498
46733
  detail: target,
@@ -47185,7 +47420,7 @@ function pageProjectedTranscript(messages, anchor, limits = {}) {
47185
47420
  let boundary = 0;
47186
47421
  for (let i = anchor - 1; i >= 0; i--) {
47187
47422
  const m = messages[i];
47188
- if (m.role === "user" && !m.wake && !m.queued) {
47423
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
47189
47424
  boundary = i;
47190
47425
  break;
47191
47426
  }
@@ -47391,7 +47626,7 @@ function readTranscriptFile(absPath) {
47391
47626
  return [];
47392
47627
  }
47393
47628
  }
47394
- 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;
47395
47630
  var init_transcript = __esm({
47396
47631
  "packages/server/src/transcript.ts"() {
47397
47632
  "use strict";
@@ -47417,6 +47652,9 @@ var init_transcript = __esm({
47417
47652
  COMMAND_NAME_RE = /<command-name>([\s\S]*?)<\/command-name>/;
47418
47653
  COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
47419
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";
47420
47658
  QUEUED_STALE_MS = 2 * 60 * 6e4;
47421
47659
  EDIT_CAP = 4e3;
47422
47660
  TRUNC_MARKER = "\n\u2026 (truncated)";
@@ -48465,6 +48703,31 @@ function setProjectIconFromFile(id, file) {
48465
48703
  if (bytes.length > PROJECT_ICON_MAX_BASE64_CHARS / 4 * 3) throw new Error("That image is too large.");
48466
48704
  return storeProjectIcon(id, file, bytes);
48467
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
+ }
48468
48731
  function createRouter(ctx) {
48469
48732
  const frizzDir = join36(ctx.project.dir, ".frizz");
48470
48733
  const openRoots = openableFileRoots(ctx.project);
@@ -48668,7 +48931,11 @@ function createRouter(ctx) {
48668
48931
  sessionId: row.session_id,
48669
48932
  cwd: ctx.project.dir,
48670
48933
  text: `${shellStopNotice(label)} Whatever it wrote before the kill is still readable in its output file.`,
48671
- 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()),
48672
48939
  model: row.model ?? void 0,
48673
48940
  effort: row.effort ?? void 0
48674
48941
  });
@@ -48853,7 +49120,10 @@ function createRouter(ctx) {
48853
49120
  const info = ctx.tailer.subAgent(input.slug, input.id);
48854
49121
  if (!info) return { messages: [], state: "gone", steerable: false, steerNote: null, stoppable: false, stopNote: null };
48855
49122
  const read = info.outputFormat === "codex" ? readCodexTranscriptFile : readTranscriptFile;
48856
- 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
+ );
48857
49127
  const steer = subAgentSteerable(input.slug, input.id);
48858
49128
  const stop = subAgentStoppable(input.slug, input.id);
48859
49129
  return {
@@ -48879,7 +49149,7 @@ function createRouter(ctx) {
48879
49149
  // predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
48880
49150
  // single predicate that decides, and the drawer's prompt box is rendered off the same answer.
48881
49151
  subAgentSteer: mutation({
48882
- 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(),
48883
49153
  output: external_exports.object({ delivered: external_exports.boolean() }),
48884
49154
  handler: async ({ input }) => {
48885
49155
  const target = subAgentSteerable(input.slug, input.id);
@@ -48888,12 +49158,21 @@ function createRouter(ctx) {
48888
49158
  }
48889
49159
  const bridge = ctx.claudeBroker;
48890
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();
48891
49163
  await bridge.steerSubAgent({
48892
49164
  threadSlug: input.slug,
48893
49165
  sessionId: target.sessionId,
48894
49166
  subAgentId: input.id,
48895
49167
  text: input.message,
48896
- 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
48897
49176
  });
48898
49177
  ctx.board.refresh();
48899
49178
  return { delivered: true };
@@ -49127,7 +49406,9 @@ ${gapNote}` : input.message;
49127
49406
  // Rides through to the SDK as this input's uuid, which the SDK echoes back on the record
49128
49407
  // that delivers it — the ledger then correlates by identity rather than by text.
49129
49408
  deliveryId: input.deliveryId,
49130
- 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()),
49131
49412
  appendSystemPrompt,
49132
49413
  model: row.model ?? void 0,
49133
49414
  effort: row.effort ?? void 0,