frizz 0.12.1 → 0.12.3

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 +1161 -871
  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
@@ -16458,7 +16464,14 @@ function resolveProjectIdentity(dir, home = homedir5()) {
16458
16464
  return { id: ensureProjectIdFile(root, home, git2?.id), scope: git2?.scope ?? "repository", root };
16459
16465
  }
16460
16466
  function cwdSlug(absPath) {
16461
- return absPath.replace(/[/.]/g, "-");
16467
+ const slug = absPath.replace(/[^A-Za-z0-9]/g, "-");
16468
+ if (slug.length <= CWD_SLUG_MAX) return slug;
16469
+ return `${slug.slice(0, CWD_SLUG_MAX)}-${Math.abs(cwdSlugHash(absPath)).toString(36)}`;
16470
+ }
16471
+ function cwdSlugHash(absPath) {
16472
+ let hash = 0;
16473
+ for (let i = 0; i < absPath.length; i++) hash = (hash << 5) - hash + absPath.charCodeAt(i) | 0;
16474
+ return hash;
16462
16475
  }
16463
16476
  function registerAndReconcile(dir, id, home) {
16464
16477
  const remoteOwner = resolveProjectLabel(dir)?.split("/")[0];
@@ -16541,6 +16554,7 @@ function projectFromLaunchTarget(target, env = process.env) {
16541
16554
  ...target.identityScope === "worktree" ? { identityScope: "worktree" } : {}
16542
16555
  };
16543
16556
  }
16557
+ var CWD_SLUG_MAX;
16544
16558
  var init_project = __esm({
16545
16559
  "packages/server/src/project.ts"() {
16546
16560
  "use strict";
@@ -16551,6 +16565,7 @@ var init_project = __esm({
16551
16565
  init_project_identity();
16552
16566
  init_github();
16553
16567
  init_project_identity();
16568
+ CWD_SLUG_MAX = 200;
16554
16569
  }
16555
16570
  });
16556
16571
 
@@ -19419,6 +19434,7 @@ function createStorage(source, projectId) {
19419
19434
  `);
19420
19435
  const delThreadQuestions = scope.prepare("DELETE FROM thread_question WHERE project_id = @project_id AND thread_slug = ?");
19421
19436
  const delThreadDone = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
19437
+ const delSubAgentSteers = scope.prepare("DELETE FROM subagent_steer WHERE project_id = @project_id AND thread_slug = ?");
19422
19438
  const markThreadDoneStmt = scope.prepare(`
19423
19439
  INSERT INTO thread_done (project_id, thread_slug, body, done_at) VALUES (@project_id, ?, ?, ?)
19424
19440
  ON CONFLICT(project_id, thread_slug) DO UPDATE SET body = excluded.body, done_at = excluded.done_at
@@ -19428,6 +19444,17 @@ function createStorage(source, projectId) {
19428
19444
  "SELECT thread_slug, body, done_at FROM thread_done WHERE project_id = @project_id"
19429
19445
  );
19430
19446
  const clearThreadDoneStmt = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
19447
+ const recordSubAgentSteerStmt = scope.prepare(`
19448
+ INSERT OR IGNORE INTO subagent_steer (
19449
+ project_id, thread_slug, subagent_id, delivery_id, message, sent_at
19450
+ ) VALUES (@project_id, @slug, @subAgentId, @deliveryId, @message, @sentAtMs)
19451
+ `);
19452
+ const listSubAgentSteersStmt = scope.prepare(`
19453
+ SELECT thread_slug, subagent_id, delivery_id, message, sent_at
19454
+ FROM subagent_steer
19455
+ WHERE project_id = @project_id AND thread_slug = ? AND subagent_id = ?
19456
+ ORDER BY sent_at, rowid
19457
+ `);
19431
19458
  const clearExpiredSnoozesStmt = scope.prepare(`
19432
19459
  UPDATE session SET snoozed_until = NULL
19433
19460
  WHERE project_id = @project_id AND snoozed_until IS NOT NULL AND snoozed_until <= ? AND snooze_prompt IS NULL
@@ -19482,6 +19509,7 @@ function createStorage(source, projectId) {
19482
19509
  delThreadWatches.run(existing.slug);
19483
19510
  delThreadQuestions.run(existing.slug);
19484
19511
  delThreadDone.run(existing.slug);
19512
+ delSubAgentSteers.run(existing.slug);
19485
19513
  delSession.run(existing.slug);
19486
19514
  return existing;
19487
19515
  };
@@ -19975,6 +20003,10 @@ function createStorage(source, projectId) {
19975
20003
  return bySlug;
19976
20004
  },
19977
20005
  clearThreadDone: (slug) => clearThreadDoneStmt.run(slug).changes > 0,
20006
+ recordSubAgentSteer: (row) => {
20007
+ recordSubAgentSteerStmt.run(row);
20008
+ },
20009
+ listSubAgentSteers: (slug, subAgentId) => listSubAgentSteersStmt.all(slug, subAgentId),
19978
20010
  dropThreadWatch: (slug, id, settledAtMs) => dropThreadWatchStmt.run(settledAtMs, id, slug).changes === 1,
19979
20011
  settleThreadWatch: (id, settledAtMs, state = "settled") => settleThreadWatchStmt.run(state, settledAtMs, id).changes === 1,
19980
20012
  armThreadTimer: (timer) => void armTimerStmt.run(timer),
@@ -20473,6 +20505,20 @@ var init_storage = __esm({
20473
20505
  done_at INTEGER NOT NULL,
20474
20506
  PRIMARY KEY (project_id, thread_slug)
20475
20507
  );
20508
+ -- Operator-authored steers sent from a sub-agent drawer. The Claude broker intentionally does not
20509
+ -- persist these as user records in the child's provider transcript, but Frizz possesses the text
20510
+ -- at delivery time and the drawer must not forget what the operator sent.
20511
+ CREATE TABLE IF NOT EXISTS subagent_steer (
20512
+ project_id TEXT NOT NULL,
20513
+ thread_slug TEXT NOT NULL,
20514
+ subagent_id TEXT NOT NULL,
20515
+ delivery_id TEXT NOT NULL,
20516
+ message TEXT NOT NULL,
20517
+ sent_at INTEGER NOT NULL,
20518
+ PRIMARY KEY (project_id, thread_slug, subagent_id, delivery_id)
20519
+ );
20520
+ CREATE INDEX IF NOT EXISTS subagent_steer_timeline
20521
+ ON subagent_steer(project_id, thread_slug, subagent_id, sent_at);
20476
20522
  `;
20477
20523
  STORAGE_TABLES = [
20478
20524
  "session",
@@ -20485,7 +20531,8 @@ var init_storage = __esm({
20485
20531
  "pr_watch",
20486
20532
  "thread_watch",
20487
20533
  "thread_question",
20488
- "thread_done"
20534
+ "thread_done",
20535
+ "subagent_steer"
20489
20536
  ];
20490
20537
  }
20491
20538
  });
@@ -23746,6 +23793,7 @@ function applyEvent(state, ev) {
23746
23793
  state.sawRecords = true;
23747
23794
  break;
23748
23795
  case "agent-report":
23796
+ case "agent-instruction":
23749
23797
  state.sawRecords = true;
23750
23798
  break;
23751
23799
  case "title":
@@ -25534,32 +25582,47 @@ function parseCodexLine(line) {
25534
25582
  if (pt === "agent_message") {
25535
25583
  const author = typeof p.author === "string" ? p.author : "";
25536
25584
  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 }] : [];
25585
+ if (!author || !recipient) return [];
25586
+ const message = parseCodexAgentMessage(p.content);
25587
+ if (!message) return [];
25588
+ if (author.startsWith(`${recipient}/`)) {
25589
+ if (message.type !== "FINAL_ANSWER" && message.type !== "MESSAGE") return [];
25590
+ return [{ kind: "agent-report", at, author, text: message.body, final: message.type === "FINAL_ANSWER" }];
25591
+ }
25592
+ if (author !== recipient && (message.type === "NEW_TASK" || message.type === "MESSAGE")) {
25593
+ return [{
25594
+ kind: "agent-instruction",
25595
+ at,
25596
+ author,
25597
+ ...message.body ? { text: message.body } : {},
25598
+ encrypted: message.encrypted
25599
+ }];
25600
+ }
25601
+ return [];
25540
25602
  }
25541
25603
  return [];
25542
25604
  }
25543
25605
  return [];
25544
25606
  }
25545
- function parseCodexAgentReport(content) {
25607
+ function parseCodexAgentMessage(content) {
25546
25608
  if (!Array.isArray(content)) return void 0;
25547
25609
  let text2;
25610
+ let encrypted = false;
25548
25611
  for (const block of content) {
25549
25612
  if (!block || typeof block !== "object") continue;
25550
25613
  const b = block;
25614
+ if (b.type === "encrypted_content") encrypted = true;
25551
25615
  if (b.type === "input_text" && typeof b.text === "string") {
25552
25616
  text2 = b.text;
25553
- break;
25554
25617
  }
25555
25618
  }
25556
25619
  if (!text2) return void 0;
25557
25620
  const type = text2.match(/^Message Type:[ \t]*(\S+)/)?.[1];
25558
- if (type !== "FINAL_ANSWER" && type !== "MESSAGE") return void 0;
25621
+ if (!type) return void 0;
25559
25622
  const marker = text2.match(/^Payload:[ \t]*$/m);
25560
25623
  if (!marker || marker.index === void 0) return void 0;
25561
25624
  const body = text2.slice(marker.index + marker[0].length).replace(/^\n/, "").trim();
25562
- return { final: type === "FINAL_ANSWER", body };
25625
+ return { type, body, encrypted };
25563
25626
  }
25564
25627
  function parseCodexSubAgentLine(line, toolNameFor) {
25565
25628
  const s = line.trim();
@@ -29931,7 +29994,8 @@ function readNativeRecord(stateDir, projectId) {
29931
29994
  generation: value.generation,
29932
29995
  listenerPid: value.listenerPid,
29933
29996
  socketPath: value.socketPath,
29934
- createdAt: value.createdAt ?? ""
29997
+ createdAt: value.createdAt ?? "",
29998
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
29935
29999
  };
29936
30000
  } catch {
29937
30001
  return null;
@@ -30101,7 +30165,8 @@ async function startListener(options) {
30101
30165
  generation,
30102
30166
  listenerPid: child.pid ?? -1,
30103
30167
  socketPath,
30104
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
30168
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
30169
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {}
30105
30170
  };
30106
30171
  writeFileSync9(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
30107
30172
  const deadline = Date.now() + (options.timeoutMs ?? 3e4);
@@ -30131,7 +30196,8 @@ var init_codex_app_server_native = __esm({
30131
30196
  generation: existing.generation,
30132
30197
  reattached: true,
30133
30198
  daemonPid: existing.listenerPid,
30134
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30199
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30200
+ authAccountId: existing.authAccountId
30135
30201
  };
30136
30202
  } catch {
30137
30203
  try {
@@ -30147,7 +30213,8 @@ var init_codex_app_server_native = __esm({
30147
30213
  generation: record.generation,
30148
30214
  reattached: false,
30149
30215
  daemonPid: record.listenerPid,
30150
- droppedWhileDetached: 0
30216
+ droppedWhileDetached: 0,
30217
+ authAccountId: record.authAccountId
30151
30218
  };
30152
30219
  } catch (error) {
30153
30220
  const raced = liveNativeRecord(options.stateDir, options.projectId);
@@ -30157,12 +30224,13 @@ var init_codex_app_server_native = __esm({
30157
30224
  generation: raced.generation,
30158
30225
  reattached: true,
30159
30226
  daemonPid: raced.listenerPid,
30160
- droppedWhileDetached: PRESUMED_LOSSY_REJOIN
30227
+ droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
30228
+ authAccountId: raced.authAccountId
30161
30229
  };
30162
30230
  }
30163
30231
  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
30232
  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 };
30233
+ return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
30166
30234
  }
30167
30235
  };
30168
30236
  }
@@ -30204,7 +30272,8 @@ function readDaemonRecord(stateDir, projectId) {
30204
30272
  daemonPid: value.daemonPid,
30205
30273
  childPid: typeof value.childPid === "number" ? value.childPid : 0,
30206
30274
  socketPath: value.socketPath,
30207
- createdAt: value.createdAt ?? ""
30275
+ createdAt: value.createdAt ?? "",
30276
+ ...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
30208
30277
  };
30209
30278
  } catch {
30210
30279
  return null;
@@ -30298,6 +30367,7 @@ function forkDaemon(options) {
30298
30367
  generation,
30299
30368
  clientInfo: options.clientInfo,
30300
30369
  capabilities: options.capabilities,
30370
+ ...options.authAccountId ? { authAccountId: options.authAccountId } : {},
30301
30371
  // The argv is built HERE, not in the daemon: the daemon is a bare forked entry that should not have
30302
30372
  // to resolve frizz's MCP descriptors, and building it once keeps this transport identical to the
30303
30373
  // native listener's.
@@ -30425,7 +30495,7 @@ function inProcessCodexAppServer(options) {
30425
30495
  return false;
30426
30496
  }
30427
30497
  });
30428
- return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
30498
+ return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
30429
30499
  }
30430
30500
  function directChildHost(spawnChild, generationId = randomUUID10) {
30431
30501
  return async (options) => ({
@@ -30433,7 +30503,8 @@ function directChildHost(spawnChild, generationId = randomUUID10) {
30433
30503
  generation: generationId(),
30434
30504
  reattached: false,
30435
30505
  daemonPid: process.pid,
30436
- droppedWhileDetached: 0
30506
+ droppedWhileDetached: 0,
30507
+ authAccountId: options.authAccountId
30437
30508
  });
30438
30509
  }
30439
30510
  var daemonEntry, daemonCodexAppServerHost;
@@ -30452,7 +30523,7 @@ var init_codex_app_server_host = __esm({
30452
30523
  if (existing) {
30453
30524
  try {
30454
30525
  const attached = await attach2(existing, timeoutMs);
30455
- return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid };
30526
+ return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid, authAccountId: existing.authAccountId };
30456
30527
  } catch {
30457
30528
  try {
30458
30529
  unlinkSync3(recordPath(options.stateDir, options.projectId));
@@ -30472,7 +30543,7 @@ var init_codex_app_server_host = __esm({
30472
30543
  try {
30473
30544
  const record = await forkDaemon(options);
30474
30545
  const attached = await attach2(record, timeoutMs);
30475
- return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid };
30546
+ return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid, authAccountId: record.authAccountId };
30476
30547
  } catch (error) {
30477
30548
  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
30549
  return inProcessCodexAppServer(options);
@@ -30481,8 +30552,627 @@ var init_codex_app_server_host = __esm({
30481
30552
  }
30482
30553
  });
30483
30554
 
30555
+ // packages/server/src/backend/claude-quota.ts
30556
+ import { join as join22 } from "node:path";
30557
+ import { homedir as homedir9, platform } from "node:os";
30558
+ import { createHash as createHash9 } from "node:crypto";
30559
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
30560
+ import { execFile as execFile3 } from "node:child_process";
30561
+ import { promisify as promisify3 } from "node:util";
30562
+ function claudeConfigDir() {
30563
+ const override = process.env.CLAUDE_CONFIG_DIR;
30564
+ return override && override.trim() ? override : join22(homedir9(), ".claude");
30565
+ }
30566
+ function tokenFromCredentialsJson(raw2) {
30567
+ let doc;
30568
+ try {
30569
+ doc = JSON.parse(raw2);
30570
+ } catch {
30571
+ return void 0;
30572
+ }
30573
+ if (!doc || typeof doc !== "object") return void 0;
30574
+ const root = doc;
30575
+ const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
30576
+ const token = oauth.accessToken ?? oauth.access_token;
30577
+ return typeof token === "string" && token ? token : void 0;
30578
+ }
30579
+ async function readKeychainToken() {
30580
+ if (platform() !== "darwin") return void 0;
30581
+ try {
30582
+ const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
30583
+ encoding: "utf8",
30584
+ timeout: 4e3
30585
+ });
30586
+ return tokenFromCredentialsJson(String(stdout).trim());
30587
+ } catch {
30588
+ return void 0;
30589
+ }
30590
+ }
30591
+ async function readAccessToken(configDir) {
30592
+ try {
30593
+ const fromFile = tokenFromCredentialsJson(await readFile(join22(configDir, ".credentials.json"), "utf8"));
30594
+ if (fromFile) return fromFile;
30595
+ } catch {
30596
+ }
30597
+ return readKeychainToken();
30598
+ }
30599
+ function isoToUnix(value) {
30600
+ const ms = typeof value === "string" ? Date.parse(value) : NaN;
30601
+ return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
30602
+ }
30603
+ function slugify2(name) {
30604
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30605
+ }
30606
+ function toWindow(raw2, key, label) {
30607
+ if (!raw2 || typeof raw2 !== "object") return void 0;
30608
+ const w = raw2;
30609
+ const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
30610
+ if (util2 === void 0) return void 0;
30611
+ const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
30612
+ return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
30613
+ }
30614
+ function windowFromLimit(raw2) {
30615
+ if (!raw2 || typeof raw2 !== "object") return void 0;
30616
+ const l = raw2;
30617
+ const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
30618
+ if (percent === void 0) return void 0;
30619
+ const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
30620
+ const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
30621
+ const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
30622
+ const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
30623
+ let key;
30624
+ let label;
30625
+ if (group === "session") {
30626
+ key = "5h";
30627
+ label = "5h";
30628
+ } else if (group === "weekly") {
30629
+ key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
30630
+ label = scopeName ? `${scopeName} wk` : "Weekly";
30631
+ } else if (group) {
30632
+ key = slugify2(group);
30633
+ label = scopeName ? `${scopeName} ${group}` : group;
30634
+ } else {
30635
+ return void 0;
30636
+ }
30637
+ const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
30638
+ return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
30639
+ }
30640
+ function parseClaudeUsage(body, planType) {
30641
+ if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
30642
+ const b = body;
30643
+ const windows = [];
30644
+ const seen = /* @__PURE__ */ new Set();
30645
+ const add = (w) => {
30646
+ if (w && !seen.has(w.key)) {
30647
+ seen.add(w.key);
30648
+ windows.push(w);
30649
+ }
30650
+ };
30651
+ if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
30652
+ add(toWindow(b.five_hour, "5h", "5h"));
30653
+ add(toWindow(b.seven_day, "weekly", "Weekly"));
30654
+ add(toWindow(b.seven_day_opus, "weekly-opus", "Opus wk"));
30655
+ add(toWindow(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
30656
+ if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
30657
+ return { status: "ok", planType, windows };
30658
+ }
30659
+ function cachePaths(cacheDir, configDir) {
30660
+ const profile = createHash9("sha256").update(configDir).digest("hex").slice(0, 12);
30661
+ return {
30662
+ data: join22(cacheDir, `claude-${profile}.json`),
30663
+ lock: join22(cacheDir, `claude-${profile}.lock`)
30664
+ };
30665
+ }
30666
+ async function readShared(path3) {
30667
+ try {
30668
+ const parsed = JSON.parse(await readFile(path3, "utf8"));
30669
+ if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
30670
+ return parsed;
30671
+ } catch {
30672
+ return void 0;
30673
+ }
30674
+ }
30675
+ async function writeShared(path3, value) {
30676
+ const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
30677
+ try {
30678
+ await writeFile(tmp, JSON.stringify(value), { mode: 384 });
30679
+ await rename(tmp, path3);
30680
+ } finally {
30681
+ await rm(tmp, { force: true }).catch(() => {
30682
+ });
30683
+ }
30684
+ }
30685
+ function delay(ms) {
30686
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
30687
+ }
30688
+ async function acquireLock(path3) {
30689
+ const deadline = Date.now() + LOCK_WAIT_MS;
30690
+ for (; ; ) {
30691
+ try {
30692
+ await mkdir(path3);
30693
+ return () => rm(path3, { recursive: true, force: true });
30694
+ } catch (err) {
30695
+ if (err.code !== "EEXIST") throw err;
30696
+ try {
30697
+ if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
30698
+ await rm(path3, { recursive: true, force: true });
30699
+ continue;
30700
+ }
30701
+ } catch {
30702
+ continue;
30703
+ }
30704
+ if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
30705
+ await delay(LOCK_POLL_MS2);
30706
+ }
30707
+ }
30708
+ }
30709
+ async function runClaudeUsage(claudeBin) {
30710
+ const { stdout } = await execFileAsync2(
30711
+ // Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
30712
+ // EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
30713
+ // never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
30714
+ // A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
30715
+ resolveClaudeExecutableAbsolute(claudeBin),
30716
+ ["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
30717
+ { encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
30718
+ );
30719
+ return String(stdout);
30720
+ }
30721
+ async function refreshQuota(claudeBin, deps, now) {
30722
+ const doFetch = deps.fetchImpl ?? fetch;
30723
+ const readToken = deps.readToken ?? readAccessToken;
30724
+ let token;
30725
+ try {
30726
+ token = await readToken(claudeConfigDir());
30727
+ } catch {
30728
+ token = void 0;
30729
+ }
30730
+ if (token) {
30731
+ try {
30732
+ const res = await doFetch(USAGE_URL, {
30733
+ headers: {
30734
+ Authorization: `Bearer ${token}`,
30735
+ "anthropic-beta": OAUTH_BETA,
30736
+ "User-Agent": USER_AGENT,
30737
+ "Content-Type": "application/json"
30738
+ },
30739
+ signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
30740
+ });
30741
+ if (res.ok) {
30742
+ const body = await res.json();
30743
+ const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
30744
+ const quota2 = parseClaudeUsage(body, planType);
30745
+ if (quota2.status === "ok") return quota2;
30746
+ } else if (res.status === 429 || res.status === 529) {
30747
+ throw new Error(`Usage endpoint ${res.status}`);
30748
+ }
30749
+ } catch (err) {
30750
+ if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
30751
+ }
30752
+ }
30753
+ const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
30754
+ if (quota.status !== "ok") throw new Error(quota.detail);
30755
+ return quota;
30756
+ }
30757
+ function windowIdentity(title) {
30758
+ for (const [pattern, key, label] of LINE_WINDOWS) {
30759
+ if (pattern.test(title)) return [key, label];
30760
+ }
30761
+ const scoped = /^Current week \((.+)\)$/i.exec(title);
30762
+ if (!scoped) return void 0;
30763
+ const name = scoped[1].replace(/\s+only$/i, "");
30764
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
30765
+ return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
30766
+ }
30767
+ function parseResetLabel(label, now) {
30768
+ if (!label) return void 0;
30769
+ const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
30770
+ 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);
30771
+ const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
30772
+ const hasClock = parts?.[6] !== void 0;
30773
+ if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
30774
+ const date2 = new Date(now);
30775
+ if (hasClock) {
30776
+ let hour = Number(parts[4]) % 12;
30777
+ if (parts[6].toLowerCase() === "pm") hour += 12;
30778
+ date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
30779
+ } else {
30780
+ date2.setHours(0, 0, 0, 0);
30781
+ }
30782
+ if (month !== void 0) {
30783
+ date2.setMonth(month, Number(parts[2]));
30784
+ if (parts[3]) date2.setFullYear(Number(parts[3]));
30785
+ else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
30786
+ } else if (date2.getTime() <= now) {
30787
+ date2.setDate(date2.getDate() + 1);
30788
+ }
30789
+ return Math.round(date2.getTime() / 1e3);
30790
+ }
30791
+ const parsed = Date.parse(text2);
30792
+ if (!Number.isFinite(parsed)) return void 0;
30793
+ const date = new Date(parsed);
30794
+ if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
30795
+ return Math.round(date.getTime() / 1e3);
30796
+ }
30797
+ function parseClaudeUsageOutput(stdout, now = Date.now()) {
30798
+ let text2 = stdout;
30799
+ try {
30800
+ const envelope = JSON.parse(stdout);
30801
+ if (typeof envelope.result === "string") text2 = envelope.result;
30802
+ } catch {
30803
+ }
30804
+ const windows = [];
30805
+ for (const line of text2.split(/\r?\n/)) {
30806
+ const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
30807
+ if (!match2) continue;
30808
+ const identity = windowIdentity(match2[1]);
30809
+ if (!identity) continue;
30810
+ const resetsAt = parseResetLabel(match2[3], now);
30811
+ windows.push({
30812
+ key: identity[0],
30813
+ label: identity[1],
30814
+ usedPercent: Number(match2[2]),
30815
+ ...resetsAt === void 0 ? {} : { resetsAt }
30816
+ });
30817
+ }
30818
+ if (windows.length === 0) {
30819
+ return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
30820
+ }
30821
+ return { status: "ok", windows };
30822
+ }
30823
+ async function tryAcquireLock2(path3) {
30824
+ for (let attempt = 0; attempt < 2; attempt++) {
30825
+ try {
30826
+ await mkdir(path3);
30827
+ return () => rm(path3, { recursive: true, force: true });
30828
+ } catch (err) {
30829
+ if (err.code !== "EEXIST") return void 0;
30830
+ try {
30831
+ if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
30832
+ await rm(path3, { recursive: true, force: true });
30833
+ } catch {
30834
+ return void 0;
30835
+ }
30836
+ }
30837
+ }
30838
+ return void 0;
30839
+ }
30840
+ function refreshSharedInBackground(paths, claudeBin, deps, now) {
30841
+ if (backgroundRefreshes.has(paths.data)) return;
30842
+ const task = (async () => {
30843
+ const release = await tryAcquireLock2(paths.lock);
30844
+ if (!release) return;
30845
+ try {
30846
+ const quota = await refreshQuota(claudeBin, deps, now);
30847
+ await writeShared(paths.data, { at: now, quota });
30848
+ } finally {
30849
+ await release().catch(() => {
30850
+ });
30851
+ }
30852
+ })().catch(() => {
30853
+ }).finally(() => backgroundRefreshes.delete(paths.data));
30854
+ backgroundRefreshes.set(paths.data, task);
30855
+ }
30856
+ async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
30857
+ const now = (deps.now ?? Date.now)();
30858
+ const configDir = claudeConfigDir();
30859
+ const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
30860
+ try {
30861
+ await mkdir(cacheDir, { recursive: true, mode: 448 });
30862
+ } catch {
30863
+ return;
30864
+ }
30865
+ refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
30866
+ }
30867
+ async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
30868
+ const now = (deps.now ?? Date.now)();
30869
+ const configDir = claudeConfigDir();
30870
+ const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
30871
+ const paths = cachePaths(cacheDir, configDir);
30872
+ try {
30873
+ await mkdir(cacheDir, { recursive: true, mode: 448 });
30874
+ } catch {
30875
+ try {
30876
+ return await refreshQuota(claudeBin, deps, now);
30877
+ } catch {
30878
+ return { status: "unavailable", windows: [], detail: "Claude usage unavailable" };
30879
+ }
30880
+ }
30881
+ const initial = await readShared(paths.data);
30882
+ if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
30883
+ return initial.quota;
30884
+ }
30885
+ if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
30886
+ refreshSharedInBackground(paths, claudeBin, deps, now);
30887
+ const ageMs = now - initial.at;
30888
+ if (ageMs < 10 * 6e4) return initial.quota;
30889
+ return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
30890
+ }
30891
+ let release;
30892
+ try {
30893
+ release = await acquireLock(paths.lock);
30894
+ const afterLock = await readShared(paths.data);
30895
+ if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
30896
+ const quota = await refreshQuota(claudeBin, deps, now);
30897
+ await writeShared(paths.data, { at: now, quota }).catch(() => {
30898
+ });
30899
+ return quota;
30900
+ } catch {
30901
+ const fallback = await readShared(paths.data);
30902
+ if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
30903
+ return {
30904
+ ...fallback.quota,
30905
+ detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
30906
+ };
30907
+ }
30908
+ const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
30909
+ await writeShared(paths.data, { at: now, quota: unavailable }).catch(() => {
30910
+ });
30911
+ return unavailable;
30912
+ } finally {
30913
+ await release?.().catch(() => {
30914
+ });
30915
+ }
30916
+ }
30917
+ 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;
30918
+ var init_claude_quota = __esm({
30919
+ "packages/server/src/backend/claude-quota.ts"() {
30920
+ "use strict";
30921
+ init_frizz_paths();
30922
+ init_claude_broker_host();
30923
+ execFileAsync2 = promisify3(execFile3);
30924
+ USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
30925
+ OAUTH_BETA = "oauth-2025-04-20";
30926
+ USER_AGENT = "claude-code/2.0.0";
30927
+ ENDPOINT_TIMEOUT_MS = 5e3;
30928
+ OK_TTL_MS = 2 * 6e4;
30929
+ FAIL_TTL_MS = 1e4;
30930
+ STALE_MAX_AGE_MS = 24 * 60 * 6e4;
30931
+ CLI_TIMEOUT_MS = 3e4;
30932
+ LOCK_STALE_MS = 4e4;
30933
+ LOCK_WAIT_MS = 15e3;
30934
+ LOCK_POLL_MS2 = 50;
30935
+ LINE_WINDOWS = [
30936
+ [/^Current session$/i, "5h", "5h"],
30937
+ [/^Current week \(all models\)$/i, "weekly", "Weekly"],
30938
+ [/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
30939
+ [/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
30940
+ ];
30941
+ MONTH_INDEX = {
30942
+ jan: 0,
30943
+ feb: 1,
30944
+ mar: 2,
30945
+ apr: 3,
30946
+ may: 4,
30947
+ jun: 5,
30948
+ jul: 6,
30949
+ aug: 7,
30950
+ sep: 8,
30951
+ oct: 9,
30952
+ nov: 10,
30953
+ dec: 11
30954
+ };
30955
+ backgroundRefreshes = /* @__PURE__ */ new Map();
30956
+ }
30957
+ });
30958
+
30959
+ // packages/server/src/backend/auth-status.ts
30960
+ import { join as join23 } from "node:path";
30961
+ import { homedir as homedir10, platform as platform2 } from "node:os";
30962
+ import { readFileSync as readFileSync16, statSync as statSync10 } from "node:fs";
30963
+ import { execFile as execFile4 } from "node:child_process";
30964
+ import { promisify as promisify4 } from "node:util";
30965
+ function claudeConfigDir2() {
30966
+ const override = process.env.CLAUDE_CONFIG_DIR;
30967
+ return override && override.trim() ? override : join23(homedir10(), ".claude");
30968
+ }
30969
+ function claudeFileState(configDir) {
30970
+ let raw2;
30971
+ try {
30972
+ raw2 = readFileSync16(join23(configDir, ".credentials.json"), "utf8");
30973
+ } catch (err) {
30974
+ return err.code === "ENOENT" ? "absent" : "error";
30975
+ }
30976
+ return tokenFromCredentialsJson(raw2) ? "token" : "empty";
30977
+ }
30978
+ async function claudeKeychainState() {
30979
+ if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
30980
+ if (platform2() !== "darwin") return "absent";
30981
+ try {
30982
+ const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
30983
+ encoding: "utf8",
30984
+ timeout: 4e3
30985
+ });
30986
+ return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
30987
+ } catch (err) {
30988
+ const e = err;
30989
+ const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
30990
+ return notFound ? "absent" : "error";
30991
+ }
30992
+ }
30993
+ async function readClaudeAuthState(configDir = claudeConfigDir2()) {
30994
+ const file = claudeFileState(configDir);
30995
+ if (file === "token") return "authed";
30996
+ const keychain = await claudeKeychainState();
30997
+ if (keychain === "token") return "authed";
30998
+ if (file === "error" || keychain === "error") return "unknown";
30999
+ return "signed-out";
31000
+ }
31001
+ function readCodexAuthState(codexHome = defaultCodexHome()) {
31002
+ if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
31003
+ let raw2;
31004
+ try {
31005
+ raw2 = readFileSync16(join23(codexHome, "auth.json"), "utf8");
31006
+ } catch (err) {
31007
+ return err.code === "ENOENT" ? "signed-out" : "unknown";
31008
+ }
31009
+ let doc;
31010
+ try {
31011
+ doc = JSON.parse(raw2);
31012
+ } catch {
31013
+ return "unknown";
31014
+ }
31015
+ if (!doc || typeof doc !== "object") return "unknown";
31016
+ const root = doc;
31017
+ const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
31018
+ const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
31019
+ const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
31020
+ return apiKey || accessToken ? "authed" : "signed-out";
31021
+ }
31022
+ function readCodexAccountId(codexHome = defaultCodexHome()) {
31023
+ if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
31024
+ try {
31025
+ const doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31026
+ if (!doc || typeof doc !== "object") return void 0;
31027
+ const tokens = doc.tokens;
31028
+ if (!tokens || typeof tokens !== "object") return void 0;
31029
+ const accountId = tokens.account_id;
31030
+ return typeof accountId === "string" && accountId.trim() ? accountId : void 0;
31031
+ } catch {
31032
+ return void 0;
31033
+ }
31034
+ }
31035
+ async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
31036
+ try {
31037
+ await exec(codexBin, ["--version"], { timeout: 5e3 });
31038
+ return "present";
31039
+ } catch (err) {
31040
+ return err.code === "ENOENT" ? "missing" : "unknown";
31041
+ }
31042
+ }
31043
+ function parseClaudeAuthStatusJson(stdout) {
31044
+ const start = stdout.indexOf("{");
31045
+ const end = stdout.lastIndexOf("}");
31046
+ if (start === -1 || end <= start) return void 0;
31047
+ try {
31048
+ const doc = JSON.parse(stdout.slice(start, end + 1));
31049
+ return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
31050
+ } catch {
31051
+ return void 0;
31052
+ }
31053
+ }
31054
+ async function readClaudeAuthStatusCli(opts) {
31055
+ try {
31056
+ const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
31057
+ const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
31058
+ encoding: "utf8",
31059
+ timeout: opts?.timeoutMs ?? 5e3,
31060
+ ...opts?.cwd ? { cwd: opts.cwd } : {}
31061
+ });
31062
+ return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
31063
+ } catch (err) {
31064
+ const stdout = err.stdout;
31065
+ const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
31066
+ if (loggedIn === false) return "signed-out";
31067
+ if (loggedIn === true) return "authed";
31068
+ return "unknown";
31069
+ }
31070
+ }
31071
+ async function readClaudePreflightAuth(opts) {
31072
+ const local = await readClaudeAuthState();
31073
+ if (local !== "signed-out") return local;
31074
+ return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
31075
+ }
31076
+ function asEmail(value) {
31077
+ if (typeof value !== "string") return void 0;
31078
+ const email = value.trim();
31079
+ return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
31080
+ }
31081
+ function claudeAccountFile() {
31082
+ const override = process.env.CLAUDE_CONFIG_DIR;
31083
+ return override && override.trim() ? join23(override, ".claude.json") : join23(homedir10(), ".claude.json");
31084
+ }
31085
+ function readClaudeAccountEmail(path3 = claudeAccountFile()) {
31086
+ let stat2;
31087
+ try {
31088
+ stat2 = statSync10(path3);
31089
+ } catch {
31090
+ return void 0;
31091
+ }
31092
+ const memo3 = claudeAccountMemo;
31093
+ if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
31094
+ let email;
31095
+ try {
31096
+ const doc = JSON.parse(readFileSync16(path3, "utf8"));
31097
+ email = asEmail(doc?.oauthAccount?.emailAddress);
31098
+ } catch {
31099
+ email = void 0;
31100
+ }
31101
+ claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
31102
+ return email;
31103
+ }
31104
+ function readCodexAccountEmail(codexHome = defaultCodexHome()) {
31105
+ let doc;
31106
+ try {
31107
+ doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
31108
+ } catch {
31109
+ return void 0;
31110
+ }
31111
+ const idToken = doc?.tokens?.id_token;
31112
+ if (typeof idToken !== "string") return void 0;
31113
+ const payload = idToken.split(".")[1];
31114
+ if (!payload) return void 0;
31115
+ try {
31116
+ return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
31117
+ } catch {
31118
+ return void 0;
31119
+ }
31120
+ }
31121
+ function readAccountEmails() {
31122
+ const emails = {};
31123
+ const claude = tryRead(readClaudeAccountEmail);
31124
+ const codex = tryRead(readCodexAccountEmail);
31125
+ if (claude) emails.claude = claude;
31126
+ if (codex) emails.codex = codex;
31127
+ return emails;
31128
+ }
31129
+ function tryRead(read) {
31130
+ try {
31131
+ return read();
31132
+ } catch {
31133
+ return void 0;
31134
+ }
31135
+ }
31136
+ async function readAuthSnapshot(opts) {
31137
+ const [claude, codex] = await Promise.all([
31138
+ readClaudeAuthState().then(async (local) => {
31139
+ if (local !== "signed-out") return local;
31140
+ const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
31141
+ return cli === "authed" ? "authed" : local;
31142
+ }).catch(() => "unknown"),
31143
+ Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
31144
+ ]);
31145
+ const emails = readAccountEmails();
31146
+ return {
31147
+ claude,
31148
+ codex,
31149
+ emails: {
31150
+ ...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
31151
+ ...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
31152
+ }
31153
+ };
31154
+ }
31155
+ var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
31156
+ var init_auth_status = __esm({
31157
+ "packages/server/src/backend/auth-status.ts"() {
31158
+ "use strict";
31159
+ init_claude_quota();
31160
+ init_codex();
31161
+ init_claude_broker_host();
31162
+ execFileAsync3 = promisify4(execFile4);
31163
+ ProviderAuthRequiredError = class extends Error {
31164
+ backend;
31165
+ constructor(backend) {
31166
+ super(`AUTH_REQUIRED:${backend}`);
31167
+ this.name = "ProviderAuthRequiredError";
31168
+ this.backend = backend;
31169
+ }
31170
+ };
31171
+ }
31172
+ });
31173
+
30484
31174
  // packages/server/src/backend/codex-app-server.ts
30485
- import { createHash as createHash9, randomUUID as randomUUID11 } from "node:crypto";
31175
+ import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
30486
31176
  import { StringDecoder as StringDecoder3 } from "node:string_decoder";
30487
31177
  function selectCodexHostKind(flagValue, platform3, hasSpawn) {
30488
31178
  if (hasSpawn) return "direct";
@@ -30829,7 +31519,7 @@ function canonicalJson2(value) {
30829
31519
  return JSON.stringify(normalize3(value));
30830
31520
  }
30831
31521
  function logicalRequestId(method, parts) {
30832
- const digest = createHash9("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
31522
+ const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
30833
31523
  return `codex-${digest}`;
30834
31524
  }
30835
31525
  function requestFingerprint(value) {
@@ -30839,7 +31529,7 @@ function requestFingerprint(value) {
30839
31529
  delete record.startedAtMs;
30840
31530
  delete record.autoResolutionMs;
30841
31531
  }
30842
- return createHash9("sha256").update(canonicalJson2(normalized)).digest("hex");
31532
+ return createHash10("sha256").update(canonicalJson2(normalized)).digest("hex");
30843
31533
  }
30844
31534
  function decision(id, semantic, label) {
30845
31535
  return { id, semantic, label };
@@ -31127,7 +31817,7 @@ function createCodexAppServerBridge(options) {
31127
31817
  function codexAppServerBridgeEnabled() {
31128
31818
  return true;
31129
31819
  }
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;
31820
+ 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
31821
  var init_codex_app_server = __esm({
31132
31822
  "packages/server/src/backend/codex-app-server.ts"() {
31133
31823
  "use strict";
@@ -31139,15 +31829,16 @@ var init_codex_app_server = __esm({
31139
31829
  init_credential_redaction();
31140
31830
  init_codex_app_server_host();
31141
31831
  init_codex_app_server_native();
31832
+ init_auth_status();
31142
31833
  init_codex_mcp();
31143
31834
  init_logging();
31144
31835
  CODEX_APP_SERVER_PROVIDER = "codex-app-server";
31145
31836
  CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
31146
- CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.2";
31837
+ CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.4";
31147
31838
  CODEX_APP_SERVER_PROTOCOL_REVISION = Object.freeze({
31148
31839
  packageVersion: CODEX_APP_SERVER_SUPPORTED_VERSION,
31149
- sourceTag: "rust-v0.153.2",
31150
- sourceCommit: "657a993cbee87acf52d14b758ce49dbd46d1b8eb"
31840
+ sourceTag: "rust-v0.153.4",
31841
+ sourceCommit: "3d2ee51ca2d5db578f328aa75e20aa22c0197c9a"
31151
31842
  });
31152
31843
  PROTOCOL_FINGERPRINT = [
31153
31844
  CODEX_APP_SERVER_PROTOCOL_REVISION.sourceTag,
@@ -31158,7 +31849,22 @@ var init_codex_app_server = __esm({
31158
31849
  CLIENT_CAPABILITIES = Object.freeze({
31159
31850
  experimentalApi: true,
31160
31851
  requestAttestation: false,
31161
- mcpServerOpenaiFormElicitation: false
31852
+ mcpServerOpenaiFormElicitation: false,
31853
+ // Unused cumulative diff snapshots caused project-wide disconnects under the old 256 KiB frame
31854
+ // limit. Suppress them at the producer regardless of the inbound budget; file approvals use
31855
+ // item/fileChange/*, not these snapshots. Native listeners negotiate on every attach.
31856
+ optOutNotificationMethods: [
31857
+ "turn/diff/updated",
31858
+ // Prose, reasoning, output and usage are read from the rollout, not this control connection.
31859
+ // A replay/burst of these used to consume the 256-record queue before lifecycle events drained.
31860
+ "item/agentMessage/delta",
31861
+ "item/reasoning/summaryTextDelta",
31862
+ "item/reasoning/summaryPartAdded",
31863
+ "item/reasoning/textDelta",
31864
+ "item/commandExecution/outputDelta",
31865
+ "thread/tokenUsage/updated",
31866
+ "account/rateLimits/updated"
31867
+ ]
31162
31868
  });
31163
31869
  CODEX_SANDBOX_MODES = Object.freeze([
31164
31870
  "read-only",
@@ -31170,8 +31876,9 @@ var init_codex_app_server = __esm({
31170
31876
  SANDBOX_CONFIRM_TIMEOUT_MS = 8e3;
31171
31877
  SANDBOX_NOOP_GRACE_MS = 750;
31172
31878
  MAX_JSONL_BYTES = 256 * 1024;
31879
+ MAX_INBOUND_JSONL_BYTES = 8 * 1024 * 1024;
31173
31880
  MAX_INBOUND_RECORDS = 256;
31174
- MAX_INBOUND_QUEUED_BYTES = MAX_JSONL_BYTES * 2;
31881
+ MAX_INBOUND_QUEUED_BYTES = MAX_INBOUND_JSONL_BYTES * 2;
31175
31882
  MAX_OUTBOUND_REQUESTS = 128;
31176
31883
  MAX_STDERR_BYTES = 16 * 1024;
31177
31884
  DEFAULT_REQUEST_TIMEOUT_MS = 2e4;
@@ -31456,17 +32163,18 @@ var init_codex_app_server = __esm({
31456
32163
  consume(chunk) {
31457
32164
  if (this.closed) return;
31458
32165
  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
32166
  while (true) {
31464
32167
  const newline = this.buffer.indexOf("\n");
31465
- if (newline < 0) return;
32168
+ if (newline < 0) {
32169
+ if (Buffer.byteLength(this.buffer, "utf8") > MAX_INBOUND_JSONL_BYTES) {
32170
+ this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
32171
+ }
32172
+ return;
32173
+ }
31466
32174
  const line = this.buffer.slice(0, newline);
31467
32175
  this.buffer = this.buffer.slice(newline + 1);
31468
32176
  if (line.trim().length === 0) continue;
31469
- if (Buffer.byteLength(line, "utf8") > MAX_JSONL_BYTES) {
32177
+ if (Buffer.byteLength(line, "utf8") > MAX_INBOUND_JSONL_BYTES) {
31470
32178
  this.fail("protocol", new Error("Codex app-server JSONL message exceeded its limit"));
31471
32179
  return;
31472
32180
  }
@@ -31477,6 +32185,10 @@ var init_codex_app_server = __esm({
31477
32185
  this.fail("protocol", new Error("Codex app-server emitted invalid JSONL"));
31478
32186
  return;
31479
32187
  }
32188
+ if (message !== null && typeof message === "object" && !Array.isArray(message)) {
32189
+ const envelope = message;
32190
+ if (!("id" in envelope) && !("jsonrpc" in envelope) && typeof envelope.method === "string" && CLIENT_CAPABILITIES.optOutNotificationMethods.includes(envelope.method)) continue;
32191
+ }
31480
32192
  const bytes = Buffer.byteLength(line, "utf8");
31481
32193
  if (this.inboundQueue.length >= MAX_INBOUND_RECORDS || this.inboundQueuedBytes + bytes > MAX_INBOUND_QUEUED_BYTES) {
31482
32194
  this.fail("protocol", new Error("Codex app-server inbound queue exceeded its limit"));
@@ -31580,6 +32292,7 @@ var init_codex_app_server = __esm({
31580
32292
  }
31581
32293
  fail(reason, error) {
31582
32294
  if (this.closed) return;
32295
+ if (reason === "protocol") this.diagnostic?.({ event: "protocol-failure", detail: error.message });
31583
32296
  this.closed = true;
31584
32297
  for (const pending of this.pending.values()) {
31585
32298
  clearTimeout(pending.timer);
@@ -31684,6 +32397,11 @@ var init_codex_app_server = __esm({
31684
32397
  timeoutMs;
31685
32398
  /** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
31686
32399
  daemonGeneration = "";
32400
+ /** ChatGPT account the current app-server cached when its process started. */
32401
+ processAuthAccountId;
32402
+ /** Serializes account-driven replacement across concurrent session/turn starts. */
32403
+ authReplacement = null;
32404
+ turnIdleWaiters = /* @__PURE__ */ new Set();
31687
32405
  /**
31688
32406
  * The handshake a FRESHLY FORKED daemon already gave us and we already rejected — i.e. what the
31689
32407
  * codex binary on disk actually reports. Once we have heard it from a new process there is nothing
@@ -31760,7 +32478,7 @@ var init_codex_app_server = __esm({
31760
32478
  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
32479
  throw new Error("Codex app-server thread slug or session id is already bound");
31762
32480
  }
31763
- const connection = await this.ensureConnected();
32481
+ const connection = await this.ensureCurrentAuth();
31764
32482
  const ephemeral = input.ephemeral ?? true;
31765
32483
  const startedSandbox = input.permissions ? null : input.sandbox ?? "read-only";
31766
32484
  const response = ThreadResponse.parse(await connection.request("thread/start", {
@@ -31905,7 +32623,7 @@ var init_codex_app_server = __esm({
31905
32623
  const releaseOperation = this.beginOperation();
31906
32624
  this.startingTurns.add(startKey);
31907
32625
  try {
31908
- const connection = await this.ensureConnected();
32626
+ const connection = await this.ensureCurrentAuth();
31909
32627
  let binding = this.bindingForScope(input.threadSlug, input.sessionId);
31910
32628
  if (!binding) throw new Error("Codex app-server turn requires a bridge-owned session");
31911
32629
  if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
@@ -31939,6 +32657,7 @@ var init_codex_app_server = __esm({
31939
32657
  return { turnId: response.turn.id };
31940
32658
  } finally {
31941
32659
  this.pendingTurnStarts.delete(pendingKey);
32660
+ this.notifyTurnIdleWaiters();
31942
32661
  }
31943
32662
  } finally {
31944
32663
  this.startingTurns.delete(startKey);
@@ -32338,6 +33057,8 @@ var init_codex_app_server = __esm({
32338
33057
  this.openingConnection.close();
32339
33058
  this.openingConnection = null;
32340
33059
  }
33060
+ this.processAuthAccountId = void 0;
33061
+ this.notifyTurnIdleWaiters();
32341
33062
  }
32342
33063
  }
32343
33064
  bindingForScope(threadSlug, sessionId) {
@@ -32363,11 +33084,61 @@ var init_codex_app_server = __esm({
32363
33084
  this.connecting = null;
32364
33085
  }
32365
33086
  }
33087
+ /**
33088
+ * Codex intentionally snapshots auth.json once per AuthManager. A detached app-server therefore
33089
+ * keeps using the account it started with even after ccbroker atomically switches auth.json. Before
33090
+ * starting new model work, replace that stale process — but only after every turn already running
33091
+ * in the shared per-project process has reached its own completion event.
33092
+ */
33093
+ async ensureCurrentAuth() {
33094
+ for (; ; ) {
33095
+ const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
33096
+ const connection = await this.ensureConnected();
33097
+ if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
33098
+ if (!this.authReplacement) {
33099
+ const replacement2 = this.replaceAppServerForAuthChange();
33100
+ this.authReplacement = replacement2;
33101
+ void replacement2.finally(() => {
33102
+ if (this.authReplacement === replacement2) this.authReplacement = null;
33103
+ }).catch(() => void 0);
33104
+ }
33105
+ await this.authReplacement;
33106
+ }
33107
+ }
33108
+ hasInFlightTurns() {
33109
+ if (this.pendingTurnStarts.size > 0) return true;
33110
+ return Boolean(this.scope.prepare(`
33111
+ SELECT 1 FROM codex_app_server_session
33112
+ WHERE project_id = @project_id AND state = 'active' AND connection_epoch = ? AND current_turn_id IS NOT NULL
33113
+ LIMIT 1
33114
+ `).get(this.connectionEpoch));
33115
+ }
33116
+ whenTurnsIdle() {
33117
+ if (!this.hasInFlightTurns()) return Promise.resolve();
33118
+ return new Promise((resolve12) => {
33119
+ this.turnIdleWaiters.add(resolve12);
33120
+ if (!this.hasInFlightTurns() && this.turnIdleWaiters.delete(resolve12)) resolve12();
33121
+ });
33122
+ }
33123
+ notifyTurnIdleWaiters() {
33124
+ if (this.dbReleased || this.hasInFlightTurns()) return;
33125
+ for (const resolve12 of this.turnIdleWaiters) resolve12();
33126
+ this.turnIdleWaiters.clear();
33127
+ }
33128
+ async replaceAppServerForAuthChange() {
33129
+ while (this.hasInFlightTurns()) await this.whenTurnsIdle();
33130
+ if (this.closed || this.dbReleased) throw new Error("Codex app-server bridge is closed");
33131
+ this.disconnectOwnedProcess();
33132
+ await stopCodexAppServerDaemon(this.options.stateDir ?? this.options.projectDir, this.options.projectId);
33133
+ await this.ensureConnected();
33134
+ this.options.diagnostic?.({ event: "auth-account-refreshed" });
33135
+ }
32366
33136
  /**
32367
33137
  * Attach to an app-server and negotiate.
32368
33138
  *
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
33139
+ * `refork` is the version-skew recovery. Alongside the idle-boundary auth refresh above, it is one
33140
+ * of only two things in frizz that ever ends a Codex daemon's life. The daemon performs `initialize`
33141
+ * once and caches the answer for as long as it
32371
33142
  * lives (up to six hours idle, unbounded while a client keeps reattaching). Bump
32372
33143
  * CODEX_APP_SERVER_SUPPORTED_VERSION and Update & Restart — the ordinary upgrade path — and the
32373
33144
  * surviving daemon happily serves the STALE userAgent to every new generation, so the gate below
@@ -32377,6 +33148,7 @@ var init_codex_app_server = __esm({
32377
33148
  * for why a genuinely unsupported codex still fails loudly instead of reforking in a loop.
32378
33149
  */
32379
33150
  async connect(refork = false) {
33151
+ const authAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
32380
33152
  const attachment = await this.host({
32381
33153
  projectId: this.options.projectId,
32382
33154
  stateDir: this.options.stateDir ?? this.options.projectDir,
@@ -32387,7 +33159,8 @@ var init_codex_app_server = __esm({
32387
33159
  env: codexAppServerEnvironment(),
32388
33160
  clientInfo: CLIENT_INFO,
32389
33161
  capabilities: CLIENT_CAPABILITIES,
32390
- frizzMcp: this.options.frizzMcp
33162
+ frizzMcp: this.options.frizzMcp,
33163
+ authAccountId
32391
33164
  });
32392
33165
  const child = attachment.process;
32393
33166
  let connection;
@@ -32450,6 +33223,7 @@ var init_codex_app_server = __esm({
32450
33223
  this.connectionEpoch = negotiated.connectionEpoch;
32451
33224
  this.capabilityRevision = negotiated.capabilityRevision;
32452
33225
  this.daemonGeneration = attachment.generation;
33226
+ this.processAuthAccountId = attachment.authAccountId;
32453
33227
  if (this.closed) throw new Error("Codex app-server bridge closed during negotiation");
32454
33228
  this.connection = connection;
32455
33229
  this.openingConnection = null;
@@ -32489,6 +33263,7 @@ var init_codex_app_server = __esm({
32489
33263
  if (this.connection !== connection) return;
32490
33264
  const epoch = this.connectionEpoch;
32491
33265
  this.connection = null;
33266
+ this.processAuthAccountId = void 0;
32492
33267
  this.forgetCorrelatedFileItems();
32493
33268
  this.releaseSettingsWaiters();
32494
33269
  if (this.closed || this.dbReleased) return;
@@ -32496,6 +33271,7 @@ var init_codex_app_server = __esm({
32496
33271
  UPDATE codex_app_server_session SET state = 'detached', updated_at = ?
32497
33272
  WHERE project_id = @project_id AND connection_epoch = ? AND state = 'active'
32498
33273
  `).run(this.now().toISOString(), epoch);
33274
+ this.notifyTurnIdleWaiters();
32499
33275
  this.options.diagnostic?.({ event: "disconnected", connectionEpoch: epoch, reason });
32500
33276
  }
32501
33277
  /**
@@ -33353,6 +34129,7 @@ var init_codex_app_server = __esm({
33353
34129
  UPDATE codex_app_server_session SET current_turn_id = NULL, auto_resume_count = 0, updated_at = ?
33354
34130
  WHERE project_id = @project_id AND codex_thread_id = ? AND connection_epoch = ? AND current_turn_id = ?
33355
34131
  `).run(this.now().toISOString(), parsed.data.threadId, this.connectionEpoch, parsed.data.turn.id);
34132
+ this.notifyTurnIdleWaiters();
33356
34133
  }
33357
34134
  }
33358
34135
  providerResponse(record, delivery2, input) {
@@ -33426,11 +34203,11 @@ var init_codex_app_server = __esm({
33426
34203
  });
33427
34204
 
33428
34205
  // 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";
34206
+ import { join as join24 } from "node:path";
34207
+ import { readdirSync as readdirSync7, statSync as statSync11, openSync as openSync12, readSync as readSync6, fstatSync, closeSync as closeSync11 } from "node:fs";
33431
34208
  import { spawn as spawn4 } from "node:child_process";
33432
34209
  function sessionsDir2(codexHome) {
33433
- return join22(codexHome, "sessions");
34210
+ return join24(codexHome, "sessions");
33434
34211
  }
33435
34212
  function newestRollouts(dir, out, budget) {
33436
34213
  if (budget.n <= 0) return;
@@ -33444,610 +34221,206 @@ function newestRollouts(dir, out, budget) {
33444
34221
  const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
33445
34222
  for (const d of dirs) {
33446
34223
  if (budget.n <= 0) return;
33447
- newestRollouts(join22(dir, d), out, budget);
34224
+ newestRollouts(join24(dir, d), out, budget);
33448
34225
  }
33449
34226
  for (const f of files) {
33450
34227
  if (budget.n <= 0) return;
33451
- out.push(join22(dir, f));
34228
+ out.push(join24(dir, f));
33452
34229
  budget.n--;
33453
34230
  }
33454
34231
  }
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;
34232
+ function readTail(path3) {
34233
+ let fd;
34234
+ try {
34235
+ fd = openSync12(path3, "r");
34236
+ const size = fstatSync(fd).size;
34237
+ const start = Math.max(0, size - TAIL_BYTES);
34238
+ const len = size - start;
34239
+ const buf = Buffer.allocUnsafe(len);
34240
+ let read = 0;
34241
+ while (read < len) {
34242
+ const n = readSync6(fd, buf, read, len - read, start + read);
34243
+ if (n <= 0) break;
34244
+ read += n;
34245
+ }
34246
+ return buf.toString("utf8", 0, read);
34247
+ } catch {
34248
+ return "";
34249
+ } finally {
34250
+ if (fd !== void 0) {
33925
34251
  try {
33926
- if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
33927
- await rm(path3, { recursive: true, force: true });
34252
+ closeSync11(fd);
33928
34253
  } catch {
33929
- return void 0;
33930
34254
  }
33931
34255
  }
33932
34256
  }
33933
- return void 0;
33934
34257
  }
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;
34258
+ function makeWindow(used, minutes, resetsAt) {
34259
+ if (used === void 0) return void 0;
34260
+ const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
34261
+ const label = key === "weekly" ? "Weekly" : key;
34262
+ return { key, label, usedPercent: used, resetsAt };
34263
+ }
34264
+ function toWindow2(raw2) {
34265
+ if (!raw2 || typeof raw2 !== "object") return void 0;
34266
+ const w = raw2;
34267
+ return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
34268
+ }
34269
+ function parseCodexQuotaFromRollout(raw2) {
34270
+ const lines = raw2.split("\n");
34271
+ for (let i = lines.length - 1; i >= 0; i--) {
34272
+ const line = lines[i];
34273
+ if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
34274
+ let obj;
33940
34275
  try {
33941
- const quota = await refreshQuota(claudeBin, deps, now);
33942
- await writeShared(paths.data, { at: now, quota });
33943
- } finally {
33944
- await release().catch(() => {
33945
- });
34276
+ obj = JSON.parse(line);
34277
+ } catch {
34278
+ continue;
33946
34279
  }
33947
- })().catch(() => {
33948
- }).finally(() => backgroundRefreshes.delete(paths.data));
33949
- backgroundRefreshes.set(paths.data, task);
34280
+ if (!obj || typeof obj !== "object") continue;
34281
+ const top = obj;
34282
+ const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
34283
+ const rl = top.rate_limits ?? payload?.rate_limits;
34284
+ if (!rl || typeof rl !== "object") continue;
34285
+ const r = rl;
34286
+ const windows = [toWindow2(r.primary), toWindow2(r.secondary)].filter((x) => x !== void 0);
34287
+ if (windows.length === 0) continue;
34288
+ const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
34289
+ return { status: "ok", planType, windows };
34290
+ }
34291
+ return void 0;
33950
34292
  }
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");
34293
+ function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
34294
+ let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
33955
34295
  try {
33956
- await mkdir(cacheDir, { recursive: true, mode: 448 });
34296
+ const rollouts = [];
34297
+ newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
34298
+ const byMtime = rollouts.map((path3) => {
34299
+ try {
34300
+ return { path: path3, mtimeMs: statSync11(path3).mtimeMs };
34301
+ } catch {
34302
+ return void 0;
34303
+ }
34304
+ }).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
34305
+ for (const { path: path3 } of byMtime) {
34306
+ const parsed = parseCodexQuotaFromRollout(readTail(path3));
34307
+ if (parsed) {
34308
+ quota = parsed;
34309
+ break;
34310
+ }
34311
+ }
33957
34312
  } catch {
33958
- return;
34313
+ quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
33959
34314
  }
33960
- refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
34315
+ return quota;
33961
34316
  }
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 {
34317
+ function toLiveWindow(raw2) {
34318
+ if (!raw2 || typeof raw2 !== "object") return void 0;
34319
+ const w = raw2;
34320
+ return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
34321
+ }
34322
+ function parseCodexQuotaFromRateLimits(result) {
34323
+ if (!result || typeof result !== "object") return void 0;
34324
+ const rl = result.rateLimits;
34325
+ if (!rl || typeof rl !== "object") return void 0;
34326
+ const r = rl;
34327
+ const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
34328
+ if (windows.length === 0) return void 0;
34329
+ const planType = typeof r.planType === "string" ? r.planType : void 0;
34330
+ return { status: "ok", planType, windows };
34331
+ }
34332
+ function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
34333
+ return new Promise((resolve12) => {
34334
+ let child;
33970
34335
  try {
33971
- return await refreshQuota(claudeBin, deps, now);
34336
+ child = spawn4(codexBin, ["app-server"], {
34337
+ stdio: ["pipe", "pipe", "ignore"],
34338
+ env: { ...process.env, CODEX_HOME: codexHome }
34339
+ });
33972
34340
  } 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
- };
34341
+ resolve12(void 0);
34342
+ return;
34002
34343
  }
34003
- const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
34004
- await writeShared(paths.data, { at: now, quota: unavailable }).catch(() => {
34344
+ let settled = false;
34345
+ const finish = (quota) => {
34346
+ if (settled) return;
34347
+ settled = true;
34348
+ clearTimeout(timer);
34349
+ try {
34350
+ child.kill("SIGKILL");
34351
+ } catch {
34352
+ }
34353
+ resolve12(quota);
34354
+ };
34355
+ const timer = setTimeout(() => finish(void 0), timeoutMs);
34356
+ const send = (msg) => {
34357
+ try {
34358
+ child.stdin?.write(JSON.stringify(msg) + "\n");
34359
+ } catch {
34360
+ finish(void 0);
34361
+ }
34362
+ };
34363
+ child.on("error", () => finish(void 0));
34364
+ child.on("exit", () => finish(void 0));
34365
+ let buf = "";
34366
+ child.stdout?.on("data", (chunk) => {
34367
+ buf += chunk.toString("utf8");
34368
+ if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
34369
+ let nl;
34370
+ while ((nl = buf.indexOf("\n")) >= 0) {
34371
+ const line = buf.slice(0, nl);
34372
+ buf = buf.slice(nl + 1);
34373
+ if (!line.trim()) continue;
34374
+ let msg;
34375
+ try {
34376
+ msg = JSON.parse(line);
34377
+ } catch {
34378
+ continue;
34379
+ }
34380
+ if (msg.id === 1) {
34381
+ send({ jsonrpc: "2.0", method: "initialized" });
34382
+ send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
34383
+ } else if (msg.id === 2) {
34384
+ finish(parseCodexQuotaFromRateLimits(msg.result));
34385
+ }
34386
+ }
34005
34387
  });
34006
- return unavailable;
34007
- } finally {
34008
- await release?.().catch(() => {
34388
+ send({
34389
+ jsonrpc: "2.0",
34390
+ id: 1,
34391
+ method: "initialize",
34392
+ params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
34009
34393
  });
34394
+ });
34395
+ }
34396
+ async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
34397
+ const now = Date.now();
34398
+ const hit = memo2.get(codexHome);
34399
+ if (hit && now - hit.at < TTL_MS2) return hit.quota;
34400
+ let quota;
34401
+ try {
34402
+ quota = await queryCodexRateLimits(codexHome, codexBin);
34403
+ } catch {
34404
+ quota = void 0;
34010
34405
  }
34406
+ if (!quota) {
34407
+ const fallback = readCodexQuotaFromRollouts(codexHome);
34408
+ quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
34409
+ }
34410
+ memo2.set(codexHome, { at: now, quota });
34411
+ return quota;
34011
34412
  }
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"() {
34413
+ var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
34414
+ var init_codex_quota = __esm({
34415
+ "packages/server/src/backend/codex-quota.ts"() {
34015
34416
  "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();
34417
+ init_codex();
34418
+ init_codex_app_server();
34419
+ descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
34420
+ TAIL_BYTES = 512 * 1024;
34421
+ num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
34422
+ TTL_MS2 = 2e4;
34423
+ memo2 = /* @__PURE__ */ new Map();
34051
34424
  }
34052
34425
  });
34053
34426
 
@@ -34120,10 +34493,10 @@ var init_quota = __esm({
34120
34493
  });
34121
34494
 
34122
34495
  // packages/server/src/frizz.ts
34123
- import { execFile as execFile4 } from "node:child_process";
34496
+ import { execFile as execFile5 } from "node:child_process";
34124
34497
  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";
34498
+ import { basename as basename5, dirname as dirname9, join as join25, resolve as resolve5 } from "node:path";
34499
+ import { promisify as promisify5 } from "node:util";
34127
34500
  function frizzScriptsDir() {
34128
34501
  if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
34129
34502
  return resolve5(import.meta.dirname, "..", "..", "..", "board");
@@ -34131,7 +34504,7 @@ function frizzScriptsDir() {
34131
34504
  function directFrizzRoot(projectDir) {
34132
34505
  try {
34133
34506
  const projectRoot = realpathSync6(projectDir);
34134
- const path3 = join24(projectRoot, ".frizz");
34507
+ const path3 = join25(projectRoot, ".frizz");
34135
34508
  const stat2 = lstatSync2(path3);
34136
34509
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
34137
34510
  const real = realpathSync6(path3);
@@ -34145,7 +34518,7 @@ function frizzDirExists(projectDir) {
34145
34518
  }
34146
34519
  async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34147
34520
  if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
34148
- const { stdout } = await execFileP("node", [join24(scriptsDir, "index.mjs"), "--json"], {
34521
+ const { stdout } = await execFileP("node", [join25(scriptsDir, "index.mjs"), "--json"], {
34149
34522
  cwd: projectDir,
34150
34523
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
34151
34524
  maxBuffer: 32 * 1024 * 1024
@@ -34170,7 +34543,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
34170
34543
  };
34171
34544
  }
34172
34545
  async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
34173
- await execFileP("node", [join24(scriptsDir, "thread-update.mjs"), slug, ...args], {
34546
+ await execFileP("node", [join25(scriptsDir, "thread-update.mjs"), slug, ...args], {
34174
34547
  cwd: projectDir,
34175
34548
  env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
34176
34549
  });
@@ -34180,7 +34553,7 @@ var init_frizz = __esm({
34180
34553
  "packages/server/src/frizz.ts"() {
34181
34554
  "use strict";
34182
34555
  init_src();
34183
- execFileP = promisify4(execFile4);
34556
+ execFileP = promisify5(execFile5);
34184
34557
  }
34185
34558
  });
34186
34559
 
@@ -34255,12 +34628,12 @@ var init_awaiting = __esm({
34255
34628
  });
34256
34629
 
34257
34630
  // 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";
34631
+ import { readFileSync as readFileSync17 } from "node:fs";
34632
+ import { homedir as homedir11 } from "node:os";
34633
+ import { join as join26 } from "node:path";
34261
34634
  function readJsonObject(path3) {
34262
34635
  try {
34263
- const parsed = JSON.parse(readFileSync16(path3, "utf8"));
34636
+ const parsed = JSON.parse(readFileSync17(path3, "utf8"));
34264
34637
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
34265
34638
  } catch {
34266
34639
  return void 0;
@@ -34351,17 +34724,17 @@ function userScopeRemoteServers(account, cwd, env) {
34351
34724
  }
34352
34725
  function projectMcpServers(cwd, options = {}) {
34353
34726
  const env = options.env ?? process.env;
34354
- const home = options.home ?? homedir10();
34727
+ const home = options.home ?? homedir11();
34355
34728
  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");
34729
+ const accountFile = configOverride ? join26(configOverride, ".claude.json") : join26(home, ".claude.json");
34730
+ const userSettingsFile = join26(configOverride ?? join26(home, ".claude"), "settings.json");
34358
34731
  const account = readJsonObject(accountFile);
34359
34732
  const out = userScopeRemoteServers(account, cwd, env);
34360
- const declared = objectField(readJsonObject(join25(cwd, ".mcp.json")), "mcpServers");
34733
+ const declared = objectField(readJsonObject(join26(cwd, ".mcp.json")), "mcpServers");
34361
34734
  if (!declared) return out;
34362
34735
  const approval = collectApproval([
34363
- readJsonObject(join25(cwd, ".claude", "settings.json")),
34364
- readJsonObject(join25(cwd, ".claude", "settings.local.json")),
34736
+ readJsonObject(join26(cwd, ".claude", "settings.json")),
34737
+ readJsonObject(join26(cwd, ".claude", "settings.local.json")),
34365
34738
  readJsonObject(userSettingsFile),
34366
34739
  objectField(objectField(account, "projects"), cwd)
34367
34740
  ]);
@@ -34386,16 +34759,16 @@ var init_project_mcp_servers = __esm({
34386
34759
 
34387
34760
  // packages/server/src/worker-plugin-dir.ts
34388
34761
  import { existsSync as existsSync8 } from "node:fs";
34389
- import { dirname as dirname10, join as join26 } from "node:path";
34762
+ import { dirname as dirname10, join as join27 } from "node:path";
34390
34763
  import { fileURLToPath as fileURLToPath2 } from "node:url";
34391
34764
  function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
34392
34765
  const override = env.FRIZZ_WORKER_PLUGIN_DIR;
34393
- if (override && existsSync8(join26(override, ".claude-plugin", "plugin.json")))
34766
+ if (override && existsSync8(join27(override, ".claude-plugin", "plugin.json")))
34394
34767
  return override;
34395
34768
  let current = dirname10(fileURLToPath2(moduleUrl));
34396
34769
  for (; ; ) {
34397
- const candidate = join26(current, "cc-worker");
34398
- if (existsSync8(join26(candidate, ".claude-plugin", "plugin.json"))) return candidate;
34770
+ const candidate = join27(current, "cc-worker");
34771
+ if (existsSync8(join27(candidate, ".claude-plugin", "plugin.json"))) return candidate;
34399
34772
  const parent = dirname10(current);
34400
34773
  if (parent === current) return void 0;
34401
34774
  current = parent;
@@ -35549,12 +35922,12 @@ var init_claude_broker_client = __esm({
35549
35922
  });
35550
35923
 
35551
35924
  // packages/server/src/backend/claude-permission-interactions.ts
35552
- import { homedir as homedir11 } from "node:os";
35925
+ import { homedir as homedir12 } from "node:os";
35553
35926
  function clip(text2, max) {
35554
35927
  return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
35555
35928
  }
35556
35929
  function tildePath(path3) {
35557
- const home = homedir11();
35930
+ const home = homedir12();
35558
35931
  if (!path3.startsWith(home)) return path3;
35559
35932
  const rest = path3.slice(home.length);
35560
35933
  return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
@@ -36203,208 +36576,6 @@ var init_claude_agent_broker_bridge = __esm({
36203
36576
  }
36204
36577
  });
36205
36578
 
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
36579
  // packages/server/src/dispatch.ts
36409
36580
  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
36581
  import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
@@ -36601,6 +36772,10 @@ function workerDispatchPermission(kind, settings) {
36601
36772
  if (kind === "claude" && settings.permissionMode === "bypassPermissions") return "bypassPermissions";
36602
36773
  return WORKER_DISPATCH_PERMISSION[kind];
36603
36774
  }
36775
+ function coldResumePermission(row, settings) {
36776
+ const saved = PermissionMode.safeParse(row.permission_mode);
36777
+ return saved.success ? saved.data : workerDispatchPermission("claude", settings);
36778
+ }
36604
36779
  function effectivePermissionMode(kind, mode) {
36605
36780
  if (kind === "claude") return workerPermissionMode(mode);
36606
36781
  if (mode === "plan" || mode === "bypassPermissions") return mode;
@@ -36912,7 +37087,14 @@ function createDispatcher(deps) {
36912
37087
  transcript_id: null,
36913
37088
  state: "open",
36914
37089
  meta: null,
36915
- seen_at: null
37090
+ seen_at: null,
37091
+ // The SAME launch mode a dispatched worker gets. The follow-up that triggered this promotion
37092
+ // resumes the session with `row.permission_mode`, and a row with none fell through to the
37093
+ // bridge's `"default"` — Claude's prompt-on-everything mode, which the worker's own perm-policy
37094
+ // hook then DEFERS on every call (`restrictive-mode`), so each Edit became a card. Observed
37095
+ // 2026-09-03 on a terminal session driven from the board: every frizz-sent turn ran at
37096
+ // `default` while the human's own TUI on the same transcript sat at `auto`.
37097
+ permission_mode: workerDispatchPermission(backend, deps.getSettings())
36916
37098
  });
36917
37099
  deps.storage.setBackend(slug, backend);
36918
37100
  if (backend === "codex") deps.storage.setAgentSession(slug, externalId);
@@ -38935,15 +39117,33 @@ function isHeartbeatFenceId(fenceId) {
38935
39117
  function saidDone(tele) {
38936
39118
  return tele.lastFence?.kind === "done" || tele.lastAssistantAllDone === true;
38937
39119
  }
38938
- function threadSaidDone(storage, slug, tele) {
38939
- return saidDone(tele) || registeredDoneFence(storage.getThreadDone(slug), tele.lastUserAt) !== void 0;
39120
+ function threadSaidDone(storage, slug, tele, armedAt) {
39121
+ const registered = registeredDoneFence(storage.getThreadDone(slug), tele.lastUserAt) !== void 0;
39122
+ const fenced = saidDone(tele);
39123
+ if (!registered && !fenced) return false;
39124
+ return !armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt);
39125
+ }
39126
+ function armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt) {
39127
+ const armed = armedAt ? Date.parse(armedAt) : Number.NaN;
39128
+ if (!Number.isFinite(armed)) return false;
39129
+ let signedOffAt = Number.NEGATIVE_INFINITY;
39130
+ if (registered) {
39131
+ const doneAt = storage.getThreadDone(slug)?.doneAt;
39132
+ if (doneAt !== void 0) signedOffAt = Math.max(signedOffAt, doneAt);
39133
+ }
39134
+ if (fenced) {
39135
+ const fencedAt = Date.parse(tele.lastAssistantAt ?? "");
39136
+ if (!Number.isFinite(fencedAt)) return false;
39137
+ signedOffAt = Math.max(signedOffAt, fencedAt);
39138
+ }
39139
+ return Number.isFinite(signedOffAt) && armed > signedOffAt;
38940
39140
  }
38941
39141
  function armedTimerIdsOf(storage, slug) {
38942
39142
  return new Set(storage.listThreadTimers(slug, { armedOnly: true }).map((t) => t.id));
38943
39143
  }
38944
- function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set()) {
39144
+ function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set(), armedAt) {
38945
39145
  if (tele.lastFence?.kind === "awaiting") return true;
38946
- return threadSaidDone(storage, slug, tele);
39146
+ return threadSaidDone(storage, slug, tele, armedAt);
38947
39147
  }
38948
39148
  function liveActivityOf(tele, registeredPrWatches, armedTimerIds) {
38949
39149
  const shells = /* @__PURE__ */ new Set();
@@ -39202,7 +39402,7 @@ function createScheduler(deps) {
39202
39402
  if (isStopHookFenceId(item.fenceId)) {
39203
39403
  const armed = armedRest(row);
39204
39404
  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";
39405
+ if (restMessageIsSignedOff(deps.storage, item.slug, tele, registeredPrWatchesOf(deps.storage, item.slug), armedTimerIdsOf(deps.storage, item.slug), armed.armedAt)) return "superseded";
39206
39406
  return tele.turn === "idle" ? "current-idle" : "current-busy";
39207
39407
  }
39208
39408
  if (isCompactFenceId(item.fenceId)) {
@@ -40059,7 +40259,7 @@ ${clock}`;
40059
40259
  const armed = armedSchedule(row);
40060
40260
  if (!armed || armed.dueAtMs > nowMs) continue;
40061
40261
  const beatTele = deps.tailer.get(row.slug);
40062
- if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele)) continue;
40262
+ if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele, armed.armedAt)) continue;
40063
40263
  if (openSchedulePrompt(row.slug, row.session_id)) continue;
40064
40264
  const beat = beatIndex(row.recurring_schedule_fired_at ?? null, armed);
40065
40265
  const fenceId = heartbeatFenceId(armed.armedAt, beat);
@@ -40109,7 +40309,7 @@ ${clock}`;
40109
40309
  if (tele.lastUserAt && Date.parse(tele.lastUserAt) >= Date.parse(restedAt)) continue;
40110
40310
  if (tele.authFault) continue;
40111
40311
  if (tele.apiFault) continue;
40112
- if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug))) continue;
40312
+ if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug), armed.armedAt)) continue;
40113
40313
  if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
40114
40314
  const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
40115
40315
  if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
@@ -41486,7 +41686,7 @@ function deliverCodexWake(deps) {
41486
41686
  })();
41487
41687
  }
41488
41688
  function deliverClaudeBrokerWake(deps) {
41489
- const { bridge, slug, cwd, row, deliveryMessage, freshProcess } = deps;
41689
+ const { bridge, slug, cwd, row, settings, deliveryMessage, freshProcess } = deps;
41490
41690
  const appendSystemPrompt = [
41491
41691
  loadWorkerPrompt("claude"),
41492
41692
  scratchpadOrientation(row.session_id, "claude"),
@@ -41497,7 +41697,7 @@ function deliverClaudeBrokerWake(deps) {
41497
41697
  sessionId: row.session_id,
41498
41698
  cwd,
41499
41699
  text: deliveryMessage,
41500
- permissionMode: row.permission_mode ?? void 0,
41700
+ permissionMode: coldResumePermission(row, settings),
41501
41701
  appendSystemPrompt,
41502
41702
  model: row.model ?? void 0,
41503
41703
  effort: row.effort ?? void 0,
@@ -41849,6 +42049,7 @@ ${wakeDeliveryToken(deliveryId)}`;
41849
42049
  slug,
41850
42050
  cwd: project.dir,
41851
42051
  row,
42052
+ settings: getSettings(storage, home),
41852
42053
  deliveryMessage,
41853
42054
  // Recomputed here rather than carried on the delivery: the outbox stores a message, not a
41854
42055
  // runtime decision, and the tail is the live answer to "is this thread still behind a wall
@@ -45127,6 +45328,21 @@ function peerSessionQueuedKey(deliveredText, pendingKeys) {
45127
45328
  for (const key of pendingKeys) if (deliveredText.startsWith(key, PEER_SESSION_PREAMBLE.length)) return key;
45128
45329
  return void 0;
45129
45330
  }
45331
+ function claudeSidechainInstruction(text2) {
45332
+ const trimmed = text2.trim();
45333
+ if (trimmed.startsWith(CLAUDE_COORDINATOR_INSTRUCTION_PREFIX) && trimmed.endsWith(CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX)) {
45334
+ const body = trimmed.slice(
45335
+ CLAUDE_COORDINATOR_INSTRUCTION_PREFIX.length,
45336
+ -CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX.length
45337
+ ).trim();
45338
+ return body || void 0;
45339
+ }
45340
+ if (!trimmed.startsWith(CLAUDE_PEER_INSTRUCTION_PREFIX)) return void 0;
45341
+ const wrapperStart = trimmed.indexOf("<agent-message ", CLAUDE_PEER_INSTRUCTION_PREFIX.length);
45342
+ const wrapperEnd = trimmed.lastIndexOf("</agent-message>");
45343
+ if (wrapperStart < 0 || wrapperEnd < 0) return void 0;
45344
+ return parseAgentMessage(trimmed.slice(wrapperStart, wrapperEnd + "</agent-message>".length))?.body.trim() || void 0;
45345
+ }
45130
45346
  function retireStaleQueuedBubbles(messages, nowMs = Date.now()) {
45131
45347
  let stale = false;
45132
45348
  for (const m of messages) {
@@ -45315,6 +45531,19 @@ function createTranscriptFold(identityPrefix = "claude") {
45315
45531
  const i = out.indexOf(pending.message);
45316
45532
  if (i !== -1) out.splice(i, 1);
45317
45533
  }
45534
+ const instruction = rec.isSidechain === true ? claudeSidechainInstruction(metaText) : void 0;
45535
+ if (instruction) {
45536
+ out.push({
45537
+ sourceId,
45538
+ role: "user",
45539
+ text: instruction,
45540
+ agentInstruction: true,
45541
+ tools: [],
45542
+ parts: [{ kind: "text", text: instruction }],
45543
+ at: rec.timestamp
45544
+ });
45545
+ lastAssistantId = null;
45546
+ }
45318
45547
  }
45319
45548
  return;
45320
45549
  }
@@ -45457,15 +45686,11 @@ ${block.text}` : block.text;
45457
45686
  return {
45458
45687
  ingest,
45459
45688
  finalize,
45460
- // The SAME window the paged reader returns (latestWindowStart), because this is the OTHER producer:
45689
+ // The SAME window the paged reader returns (latestTranscriptWindow), because this is the OTHER producer:
45461
45690
  // the /ws push renders from here while the RPC page renders from there, and a push whose head sat
45462
45691
  // further forward than the page's would splice the human's ask straight back out of a card that had
45463
45692
  // 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
- },
45693
+ messages: () => latestTranscriptWindow(projected()),
45469
45694
  allMessages: projected,
45470
45695
  consumedBytes: () => offset + Buffer.byteLength(buffer)
45471
45696
  };
@@ -46091,6 +46316,7 @@ function projectCodexTranscript(raw2, identityPrefix = "codex") {
46091
46316
  let prevEventAt;
46092
46317
  let lastContextTokens;
46093
46318
  let openCompaction = null;
46319
+ let agentInstructionCount = 0;
46094
46320
  const openAssistant = (at, sourceId, prose = false) => {
46095
46321
  if (prose && cur?.text) cur = null;
46096
46322
  if (cur) return cur;
@@ -46268,6 +46494,23 @@ ${text2}`;
46268
46494
  });
46269
46495
  break;
46270
46496
  }
46497
+ case "agent-instruction": {
46498
+ cur = null;
46499
+ turnReasoning = null;
46500
+ const first = agentInstructionCount++ === 0;
46501
+ 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.`);
46502
+ out.push({
46503
+ sourceId,
46504
+ role: "user",
46505
+ text: text2,
46506
+ displayText: text2,
46507
+ agentInstruction: true,
46508
+ tools: [],
46509
+ parts: [{ kind: "text", text: text2 }],
46510
+ at: ev.at
46511
+ });
46512
+ break;
46513
+ }
46271
46514
  case "compaction": {
46272
46515
  const m = compactionMessage(sourceId, ev.at, lastContextTokens, void 0);
46273
46516
  out.push(m);
@@ -46326,7 +46569,7 @@ function latestWindowStart(messages) {
46326
46569
  let boundary = -1;
46327
46570
  for (let i = tail - 1; i >= 0; i--) {
46328
46571
  const m = messages[i];
46329
- if (m.role === "user" && !m.wake && !m.queued) {
46572
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
46330
46573
  boundary = i;
46331
46574
  break;
46332
46575
  }
@@ -46345,6 +46588,7 @@ function latestWindowStart(messages) {
46345
46588
  function latestTranscriptWindow(messages) {
46346
46589
  const start = latestWindowStart(messages);
46347
46590
  if (start === 0) return [...messages];
46591
+ const earlierInstructions = messages.slice(0, start).filter((message) => message.agentInstruction);
46348
46592
  const pinned = [];
46349
46593
  for (let i = 0; i < start; i++) {
46350
46594
  const message = messages[i];
@@ -46363,7 +46607,7 @@ function latestTranscriptWindow(messages) {
46363
46607
  at: message.at
46364
46608
  });
46365
46609
  }
46366
- return [...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46610
+ return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
46367
46611
  }
46368
46612
  function codexToolCall(name, input, callId) {
46369
46613
  if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
@@ -46492,7 +46736,7 @@ function codexReasoningCaption(text2) {
46492
46736
  function codexPeerMessageCall(label, target, obj) {
46493
46737
  const redacted = strField(obj.message);
46494
46738
  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);
46739
+ 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
46740
  return {
46497
46741
  name: label,
46498
46742
  detail: target,
@@ -47185,7 +47429,7 @@ function pageProjectedTranscript(messages, anchor, limits = {}) {
47185
47429
  let boundary = 0;
47186
47430
  for (let i = anchor - 1; i >= 0; i--) {
47187
47431
  const m = messages[i];
47188
- if (m.role === "user" && !m.wake && !m.queued) {
47432
+ if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
47189
47433
  boundary = i;
47190
47434
  break;
47191
47435
  }
@@ -47391,7 +47635,7 @@ function readTranscriptFile(absPath) {
47391
47635
  return [];
47392
47636
  }
47393
47637
  }
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;
47638
+ 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
47639
  var init_transcript = __esm({
47396
47640
  "packages/server/src/transcript.ts"() {
47397
47641
  "use strict";
@@ -47417,6 +47661,9 @@ var init_transcript = __esm({
47417
47661
  COMMAND_NAME_RE = /<command-name>([\s\S]*?)<\/command-name>/;
47418
47662
  COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
47419
47663
  PEER_SESSION_PREAMBLE = "Another Claude session sent a message:\n";
47664
+ CLAUDE_COORDINATOR_INSTRUCTION_PREFIX = "The coordinator sent a message while you were working:\n";
47665
+ CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX = "\n\nAddress this before completing your current task.";
47666
+ CLAUDE_PEER_INSTRUCTION_PREFIX = "Another Claude session sent a message while you were working:\n";
47420
47667
  QUEUED_STALE_MS = 2 * 60 * 6e4;
47421
47668
  EDIT_CAP = 4e3;
47422
47669
  TRUNC_MARKER = "\n\u2026 (truncated)";
@@ -48465,6 +48712,31 @@ function setProjectIconFromFile(id, file) {
48465
48712
  if (bytes.length > PROJECT_ICON_MAX_BASE64_CHARS / 4 * 3) throw new Error("That image is too large.");
48466
48713
  return storeProjectIcon(id, file, bytes);
48467
48714
  }
48715
+ function mergeSubAgentSteers(messages, steers) {
48716
+ if (!steers.length) return messages;
48717
+ const merged = [...messages];
48718
+ for (const steer of steers) {
48719
+ const at = new Date(steer.sent_at).toISOString();
48720
+ const duplicate = messages.some((message2) => {
48721
+ if (message2.role !== "user" || message2.text.trim() !== steer.message.trim() || !message2.at) return false;
48722
+ return Math.abs(Date.parse(message2.at) - steer.sent_at) <= 5e3;
48723
+ });
48724
+ if (duplicate) continue;
48725
+ const message = {
48726
+ sourceId: `subagent-steer:${steer.delivery_id}`,
48727
+ role: "user",
48728
+ text: steer.message,
48729
+ agentInstruction: true,
48730
+ tools: [],
48731
+ parts: [{ kind: "text", text: steer.message }],
48732
+ at
48733
+ };
48734
+ const next = merged.findIndex((candidate) => candidate.at !== void 0 && Date.parse(candidate.at) > steer.sent_at);
48735
+ if (next === -1) merged.push(message);
48736
+ else merged.splice(next, 0, message);
48737
+ }
48738
+ return merged;
48739
+ }
48468
48740
  function createRouter(ctx) {
48469
48741
  const frizzDir = join36(ctx.project.dir, ".frizz");
48470
48742
  const openRoots = openableFileRoots(ctx.project);
@@ -48668,7 +48940,11 @@ function createRouter(ctx) {
48668
48940
  sessionId: row.session_id,
48669
48941
  cwd: ctx.project.dir,
48670
48942
  text: `${shellStopNotice(label)} Whatever it wrote before the kill is still readable in its output file.`,
48671
- permissionMode: row.permission_mode ?? void 0,
48943
+ // `isDaemonAlive` above is a check, not a hold: the daemon can exit before this frame lands,
48944
+ // and followUp then COLD-RESUMES rather than failing. Take the same floor every other fork
48945
+ // takes, so a notice that loses the race cannot be the thing that rebuilds the worker at
48946
+ // Claude's `default` — see coldResumePermission.
48947
+ permissionMode: coldResumePermission(row, ctx.getSettings()),
48672
48948
  model: row.model ?? void 0,
48673
48949
  effort: row.effort ?? void 0
48674
48950
  });
@@ -48853,7 +49129,10 @@ function createRouter(ctx) {
48853
49129
  const info = ctx.tailer.subAgent(input.slug, input.id);
48854
49130
  if (!info) return { messages: [], state: "gone", steerable: false, steerNote: null, stoppable: false, stopNote: null };
48855
49131
  const read = info.outputFormat === "codex" ? readCodexTranscriptFile : readTranscriptFile;
48856
- const messages = info.outputFile ? read(info.outputFile) : [];
49132
+ const messages = mergeSubAgentSteers(
49133
+ info.outputFile ? read(info.outputFile) : [],
49134
+ ctx.storage.listSubAgentSteers(input.slug, input.id)
49135
+ );
48857
49136
  const steer = subAgentSteerable(input.slug, input.id);
48858
49137
  const stop = subAgentStoppable(input.slug, input.id);
48859
49138
  return {
@@ -48879,7 +49158,7 @@ function createRouter(ctx) {
48879
49158
  // predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
48880
49159
  // single predicate that decides, and the drawer's prompt box is rendered off the same answer.
48881
49160
  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(),
49161
+ 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
49162
  output: external_exports.object({ delivered: external_exports.boolean() }),
48884
49163
  handler: async ({ input }) => {
48885
49164
  const target = subAgentSteerable(input.slug, input.id);
@@ -48888,12 +49167,21 @@ function createRouter(ctx) {
48888
49167
  }
48889
49168
  const bridge = ctx.claudeBroker;
48890
49169
  if (!bridge) throw new Error("Claude session broker is unavailable; cannot steer this sub-agent");
49170
+ const deliveryId = input.deliveryId ?? randomUUID16();
49171
+ const sentAtMs = Date.now();
48891
49172
  await bridge.steerSubAgent({
48892
49173
  threadSlug: input.slug,
48893
49174
  sessionId: target.sessionId,
48894
49175
  subAgentId: input.id,
48895
49176
  text: input.message,
48896
- deliveryId: input.deliveryId
49177
+ deliveryId
49178
+ });
49179
+ ctx.storage.recordSubAgentSteer({
49180
+ slug: input.slug,
49181
+ subAgentId: input.id,
49182
+ deliveryId,
49183
+ message: input.message,
49184
+ sentAtMs
48897
49185
  });
48898
49186
  ctx.board.refresh();
48899
49187
  return { delivered: true };
@@ -49127,7 +49415,9 @@ ${gapNote}` : input.message;
49127
49415
  // Rides through to the SDK as this input's uuid, which the SDK echoes back on the record
49128
49416
  // that delivers it — the ledger then correlates by identity rather than by text.
49129
49417
  deliveryId: input.deliveryId,
49130
- permissionMode: row.permission_mode ?? void 0,
49418
+ // A persisted per-thread mode, or the dispatch floor for a row that has none — see
49419
+ // coldResumePermission for why a legacy row must not fall through to the bridge's `"default"`.
49420
+ permissionMode: coldResumePermission(row, ctx.getSettings()),
49131
49421
  appendSystemPrompt,
49132
49422
  model: row.model ?? void 0,
49133
49423
  effort: row.effort ?? void 0,