frizz 0.12.1 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-broker.js +3 -7
- package/dist/codex-app-server-daemon.js +18 -4
- package/dist/dev-child.js +1151 -870
- package/dist/frizz.js +6 -0
- package/package.json +1 -1
- package/web-dist/assets/{TerminalPane-B1tNgepq.js → TerminalPane-D5Tx2hu9.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-C-0I2Feo.js → abnfDiagram-VRR7QNED-BO0tgRMu.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-lCPPJORL.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-5k4peR_3.js → architectureDiagram-ZJ3FMSHR-TCJFLj-i.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-BW9lpPO1.js → blockDiagram-677ZJIJ3-BRd9xJTz.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-gTvX_Y0U.js → c4Diagram-LMCZKHZV-BxlNYwY8.js} +1 -1
- package/web-dist/assets/channel-tGE7Gbbs.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-C8vG0TZe.js → chunk-32BRIVSS-o0wtC3P5.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-D0-irGOb.js → chunk-52WLFC77-C_bt24w7.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-Damd-YiB.js → chunk-C7G6YPKG-DlsBv-nI.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-BIBNY4nG.js → chunk-EX3LRPZG-Y_Debiw-.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-D4Ur--1p.js → chunk-FWX5IMBZ-D_9nCJ43.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-CVXtpiCg.js → chunk-HOUHSVGY-Bzhu2UoJ.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-C23GvVE0.js → chunk-ICXQ74PX-CwJVbXqL.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-Bvmynh3h.js → chunk-MOJQB5TN-DPm_X7tz.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-BqQnr6op.js → chunk-OGEWGWER-1m3zk2ed.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-BW1vhete.js → chunk-PUDLZKDR-D2Bnu5zc.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-Dg2-yKFk.js → chunk-Q4XR5HBZ-DJZtjVxI.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-iIIhDWCz.js → chunk-V7JOEXUC-BQVKbxnn.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC--SsJNJQ_.js → chunk-VAUOI2AC-TwOgVbMm.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-SpDDIDKl.js → chunk-VR4S4FIN-DlG1lzSj.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-DIObprNf.js → chunk-WYO6CB5R-Ckk8qoao.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-CIjpBbL-.js → chunk-ZGVPDNZ5-CZxmPZzT.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-CQo4x1hj.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-CQo4x1hj.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-CmAuhNNk.js → cynefin-VYW2F7L2-CioP0cgr.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B-8sxYE3.js → cynefinDiagram-TSTJHNR4-BJZpJw_3.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-rMqddpxv.js → dagre-VKFMJZFB-BGv33WEE.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-CcSGTzGC.js → diagram-FQU43EPY-CvhxVWTl.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-1bv1SDU9.js → diagram-G47NLZAW-DnrPOeE0.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-DP_7FVNi.js → diagram-NH7WQ7WH-CazuXXkX.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-BC7zSQ_n.js → diagram-OA4YK3LP-DfbFhbQs.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-DnWoY3ss.js → diagram-WEI45ONY-BESpc3Oj.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-DQvaFaem.js → ebnfDiagram-CCIWWBDH-C6h37W51.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-DrfafCjs.js → erDiagram-Q63AITRT-vCxGREHo.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-C517fJsQ.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-IrjbjjxF.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-B1niKUL6.js → ganttDiagram-NO4QXBWP-B5l9XyZe.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-BmpQWnXu.js → gitGraph-TEB2WS4Q-CQpssLnk.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DHbWnvhX.js → gitGraphDiagram-IHSO6WYX-j95eUOjO.js} +1 -1
- package/web-dist/assets/{index-ChKvnpjw.js → index-Dzq6GPUo.js} +6 -6
- package/web-dist/assets/{info-DKCQHKI2-C5dmsO4x.js → info-DKCQHKI2-CiW0hhEI.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-Cdv7Pawh.js → infoDiagram-FWYZ7A6U-BJwJPEq2.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-BwdMFTVa.js → ishikawaDiagram-FXEZZL3T-Ck4D0hT_.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-DXwHf8Te.js → journeyDiagram-5HDEW3XC-Bz8PK7lM.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-Bwe2UMg8.js → kanban-definition-HUTT4EX6-Kqt3cah4.js} +1 -1
- package/web-dist/assets/{line-CHwK75-Q.js → line-Dl7tIumR.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-DPt_XLrX.js → mermaid-parser.core-C_aHybiE.js} +3 -3
- package/web-dist/assets/{mermaid.core-DTEprUMo.js → mermaid.core-Dv6lgZxU.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-B6nU7yZ9.js → mindmap-definition-LN4V7U3C-EkN_2bUd.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-DgH2_Myt.js → packet-7NZHBO7P-LL-j9b42.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-CcJMsm4C.js → pegDiagram-2B236MQR-CrNUBIKh.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-D7GdDOZL.js → pie-RZYD4A2V-8wZ8AyeT.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-eER4uywi.js → pieDiagram-ENE6RG2P-BSUUt2Vt.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DS7rTWdN.js → quadrantDiagram-ABIIQ3AL-BmRRE-b9.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-A0W49mjL.js → radar-I7S5WNFK-BZ8i3wPh.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-BKzJ3mls.js → railroad-3IZDKUUU-Bvq-p3jW.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-DIZeZa-B.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-DVn3xHNX.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-BvLcJu_d.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-Ov2VgnIL.js → railroadDiagram-RFXS5EU6-CH0DOBjJ.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-BRJ85Yhv.js → requirementDiagram-TGXJPOKE-ytS-raWw.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB---zsi4tQ.js → sankeyDiagram-HTMAVEWB-BU49ulmu.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-AAMnjV2O.js → sequenceDiagram-DBY2YBRQ-C1edCLps.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-Rjiga0k5.js → stateDiagram-2N3HPSRC-C049S4Yj.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-D-Et-Cj1.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-D0Lhg3tV.js → swimlanes-5IMT3BWC-KNWNnFDx.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-DQNRnHXT.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-Cxq73h1K.js → timeline-definition-FHXFAJF6-D8Z-Q8JO.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-CIwv1s2P.js → treeView-QDETBFTQ-CvyDkH5p.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-BR7vnt3b.js → treemap-6X3UGDF4-COwCP95k.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-B8nj-bxp.js → vennDiagram-L72KCM5P-DHKE_6N3.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-D2n_leYo.js → wardley-OPB4EBWU-CYWcYdsx.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-Nc-IHRMn.js → wardleyDiagram-EHGQE667-BS3OkOU_.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-C4B5u2-g.js → xychartDiagram-FW5EYKEG-Df4GdNEo.js} +1 -1
- package/web-dist/index.html +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-WFjHvmXr.js +0 -1
- package/web-dist/assets/channel-B0ZL14bg.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-DRFJ6yYB.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-DRFJ6yYB.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-5EP2o0h2.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-DNJa8Ru6.js +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-D4UPY4zZ.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-DlLwAaep.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-C25TXnXG.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CQOSUkD8.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-B26FPpIT.js +0 -8
package/dist/dev-child.js
CHANGED
|
@@ -15575,6 +15575,12 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15575
15575
|
// disagree. Additive + optional: absent from a legacy transcript or an older server, and the client
|
|
15576
15576
|
// falls back to `parseGithubWakeSteer` on the text.
|
|
15577
15577
|
wakeSteer: GithubWakeSteer.optional(),
|
|
15578
|
+
// Input delivered INTO a sub-agent by its coordinator or another agent. It is a user-side turn in
|
|
15579
|
+
// that CHILD's conversation, but it is not the human speaking. The drawer's live projection keeps
|
|
15580
|
+
// these messages even when the ordinary 300-message tail has moved past them — clicking a parent's
|
|
15581
|
+
// "Steered"/"Followed up" divider promises the corresponding instruction remains readable there.
|
|
15582
|
+
// Additive + optional: ordinary thread transcripts never set it.
|
|
15583
|
+
agentInstruction: external_exports.literal(true).optional(),
|
|
15578
15584
|
// A SUB-AGENT (or peer session) wrote this user turn, not the human — the same defect class `wake`
|
|
15579
15585
|
// above corrects. Claude Code's agent-to-agent channel (a background child calling
|
|
15580
15586
|
// `SendMessage({to:"main"})`) delivers UPWARD into the parent's queue like any follow-up, so the
|
|
@@ -19419,6 +19425,7 @@ function createStorage(source, projectId) {
|
|
|
19419
19425
|
`);
|
|
19420
19426
|
const delThreadQuestions = scope.prepare("DELETE FROM thread_question WHERE project_id = @project_id AND thread_slug = ?");
|
|
19421
19427
|
const delThreadDone = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
|
|
19428
|
+
const delSubAgentSteers = scope.prepare("DELETE FROM subagent_steer WHERE project_id = @project_id AND thread_slug = ?");
|
|
19422
19429
|
const markThreadDoneStmt = scope.prepare(`
|
|
19423
19430
|
INSERT INTO thread_done (project_id, thread_slug, body, done_at) VALUES (@project_id, ?, ?, ?)
|
|
19424
19431
|
ON CONFLICT(project_id, thread_slug) DO UPDATE SET body = excluded.body, done_at = excluded.done_at
|
|
@@ -19428,6 +19435,17 @@ function createStorage(source, projectId) {
|
|
|
19428
19435
|
"SELECT thread_slug, body, done_at FROM thread_done WHERE project_id = @project_id"
|
|
19429
19436
|
);
|
|
19430
19437
|
const clearThreadDoneStmt = scope.prepare("DELETE FROM thread_done WHERE project_id = @project_id AND thread_slug = ?");
|
|
19438
|
+
const recordSubAgentSteerStmt = scope.prepare(`
|
|
19439
|
+
INSERT OR IGNORE INTO subagent_steer (
|
|
19440
|
+
project_id, thread_slug, subagent_id, delivery_id, message, sent_at
|
|
19441
|
+
) VALUES (@project_id, @slug, @subAgentId, @deliveryId, @message, @sentAtMs)
|
|
19442
|
+
`);
|
|
19443
|
+
const listSubAgentSteersStmt = scope.prepare(`
|
|
19444
|
+
SELECT thread_slug, subagent_id, delivery_id, message, sent_at
|
|
19445
|
+
FROM subagent_steer
|
|
19446
|
+
WHERE project_id = @project_id AND thread_slug = ? AND subagent_id = ?
|
|
19447
|
+
ORDER BY sent_at, rowid
|
|
19448
|
+
`);
|
|
19431
19449
|
const clearExpiredSnoozesStmt = scope.prepare(`
|
|
19432
19450
|
UPDATE session SET snoozed_until = NULL
|
|
19433
19451
|
WHERE project_id = @project_id AND snoozed_until IS NOT NULL AND snoozed_until <= ? AND snooze_prompt IS NULL
|
|
@@ -19482,6 +19500,7 @@ function createStorage(source, projectId) {
|
|
|
19482
19500
|
delThreadWatches.run(existing.slug);
|
|
19483
19501
|
delThreadQuestions.run(existing.slug);
|
|
19484
19502
|
delThreadDone.run(existing.slug);
|
|
19503
|
+
delSubAgentSteers.run(existing.slug);
|
|
19485
19504
|
delSession.run(existing.slug);
|
|
19486
19505
|
return existing;
|
|
19487
19506
|
};
|
|
@@ -19975,6 +19994,10 @@ function createStorage(source, projectId) {
|
|
|
19975
19994
|
return bySlug;
|
|
19976
19995
|
},
|
|
19977
19996
|
clearThreadDone: (slug) => clearThreadDoneStmt.run(slug).changes > 0,
|
|
19997
|
+
recordSubAgentSteer: (row) => {
|
|
19998
|
+
recordSubAgentSteerStmt.run(row);
|
|
19999
|
+
},
|
|
20000
|
+
listSubAgentSteers: (slug, subAgentId) => listSubAgentSteersStmt.all(slug, subAgentId),
|
|
19978
20001
|
dropThreadWatch: (slug, id, settledAtMs) => dropThreadWatchStmt.run(settledAtMs, id, slug).changes === 1,
|
|
19979
20002
|
settleThreadWatch: (id, settledAtMs, state = "settled") => settleThreadWatchStmt.run(state, settledAtMs, id).changes === 1,
|
|
19980
20003
|
armThreadTimer: (timer) => void armTimerStmt.run(timer),
|
|
@@ -20473,6 +20496,20 @@ var init_storage = __esm({
|
|
|
20473
20496
|
done_at INTEGER NOT NULL,
|
|
20474
20497
|
PRIMARY KEY (project_id, thread_slug)
|
|
20475
20498
|
);
|
|
20499
|
+
-- Operator-authored steers sent from a sub-agent drawer. The Claude broker intentionally does not
|
|
20500
|
+
-- persist these as user records in the child's provider transcript, but Frizz possesses the text
|
|
20501
|
+
-- at delivery time and the drawer must not forget what the operator sent.
|
|
20502
|
+
CREATE TABLE IF NOT EXISTS subagent_steer (
|
|
20503
|
+
project_id TEXT NOT NULL,
|
|
20504
|
+
thread_slug TEXT NOT NULL,
|
|
20505
|
+
subagent_id TEXT NOT NULL,
|
|
20506
|
+
delivery_id TEXT NOT NULL,
|
|
20507
|
+
message TEXT NOT NULL,
|
|
20508
|
+
sent_at INTEGER NOT NULL,
|
|
20509
|
+
PRIMARY KEY (project_id, thread_slug, subagent_id, delivery_id)
|
|
20510
|
+
);
|
|
20511
|
+
CREATE INDEX IF NOT EXISTS subagent_steer_timeline
|
|
20512
|
+
ON subagent_steer(project_id, thread_slug, subagent_id, sent_at);
|
|
20476
20513
|
`;
|
|
20477
20514
|
STORAGE_TABLES = [
|
|
20478
20515
|
"session",
|
|
@@ -20485,7 +20522,8 @@ var init_storage = __esm({
|
|
|
20485
20522
|
"pr_watch",
|
|
20486
20523
|
"thread_watch",
|
|
20487
20524
|
"thread_question",
|
|
20488
|
-
"thread_done"
|
|
20525
|
+
"thread_done",
|
|
20526
|
+
"subagent_steer"
|
|
20489
20527
|
];
|
|
20490
20528
|
}
|
|
20491
20529
|
});
|
|
@@ -23746,6 +23784,7 @@ function applyEvent(state, ev) {
|
|
|
23746
23784
|
state.sawRecords = true;
|
|
23747
23785
|
break;
|
|
23748
23786
|
case "agent-report":
|
|
23787
|
+
case "agent-instruction":
|
|
23749
23788
|
state.sawRecords = true;
|
|
23750
23789
|
break;
|
|
23751
23790
|
case "title":
|
|
@@ -25534,32 +25573,47 @@ function parseCodexLine(line) {
|
|
|
25534
25573
|
if (pt === "agent_message") {
|
|
25535
25574
|
const author = typeof p.author === "string" ? p.author : "";
|
|
25536
25575
|
const recipient = typeof p.recipient === "string" ? p.recipient : "";
|
|
25537
|
-
if (!author || !recipient
|
|
25538
|
-
const
|
|
25539
|
-
|
|
25576
|
+
if (!author || !recipient) return [];
|
|
25577
|
+
const message = parseCodexAgentMessage(p.content);
|
|
25578
|
+
if (!message) return [];
|
|
25579
|
+
if (author.startsWith(`${recipient}/`)) {
|
|
25580
|
+
if (message.type !== "FINAL_ANSWER" && message.type !== "MESSAGE") return [];
|
|
25581
|
+
return [{ kind: "agent-report", at, author, text: message.body, final: message.type === "FINAL_ANSWER" }];
|
|
25582
|
+
}
|
|
25583
|
+
if (author !== recipient && (message.type === "NEW_TASK" || message.type === "MESSAGE")) {
|
|
25584
|
+
return [{
|
|
25585
|
+
kind: "agent-instruction",
|
|
25586
|
+
at,
|
|
25587
|
+
author,
|
|
25588
|
+
...message.body ? { text: message.body } : {},
|
|
25589
|
+
encrypted: message.encrypted
|
|
25590
|
+
}];
|
|
25591
|
+
}
|
|
25592
|
+
return [];
|
|
25540
25593
|
}
|
|
25541
25594
|
return [];
|
|
25542
25595
|
}
|
|
25543
25596
|
return [];
|
|
25544
25597
|
}
|
|
25545
|
-
function
|
|
25598
|
+
function parseCodexAgentMessage(content) {
|
|
25546
25599
|
if (!Array.isArray(content)) return void 0;
|
|
25547
25600
|
let text2;
|
|
25601
|
+
let encrypted = false;
|
|
25548
25602
|
for (const block of content) {
|
|
25549
25603
|
if (!block || typeof block !== "object") continue;
|
|
25550
25604
|
const b = block;
|
|
25605
|
+
if (b.type === "encrypted_content") encrypted = true;
|
|
25551
25606
|
if (b.type === "input_text" && typeof b.text === "string") {
|
|
25552
25607
|
text2 = b.text;
|
|
25553
|
-
break;
|
|
25554
25608
|
}
|
|
25555
25609
|
}
|
|
25556
25610
|
if (!text2) return void 0;
|
|
25557
25611
|
const type = text2.match(/^Message Type:[ \t]*(\S+)/)?.[1];
|
|
25558
|
-
if (type
|
|
25612
|
+
if (!type) return void 0;
|
|
25559
25613
|
const marker = text2.match(/^Payload:[ \t]*$/m);
|
|
25560
25614
|
if (!marker || marker.index === void 0) return void 0;
|
|
25561
25615
|
const body = text2.slice(marker.index + marker[0].length).replace(/^\n/, "").trim();
|
|
25562
|
-
return {
|
|
25616
|
+
return { type, body, encrypted };
|
|
25563
25617
|
}
|
|
25564
25618
|
function parseCodexSubAgentLine(line, toolNameFor) {
|
|
25565
25619
|
const s = line.trim();
|
|
@@ -29931,7 +29985,8 @@ function readNativeRecord(stateDir, projectId) {
|
|
|
29931
29985
|
generation: value.generation,
|
|
29932
29986
|
listenerPid: value.listenerPid,
|
|
29933
29987
|
socketPath: value.socketPath,
|
|
29934
|
-
createdAt: value.createdAt ?? ""
|
|
29988
|
+
createdAt: value.createdAt ?? "",
|
|
29989
|
+
...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
|
|
29935
29990
|
};
|
|
29936
29991
|
} catch {
|
|
29937
29992
|
return null;
|
|
@@ -30101,7 +30156,8 @@ async function startListener(options) {
|
|
|
30101
30156
|
generation,
|
|
30102
30157
|
listenerPid: child.pid ?? -1,
|
|
30103
30158
|
socketPath,
|
|
30104
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30159
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30160
|
+
...options.authAccountId ? { authAccountId: options.authAccountId } : {}
|
|
30105
30161
|
};
|
|
30106
30162
|
writeFileSync9(nativeRecordPath(stateDir, projectId), JSON.stringify(record));
|
|
30107
30163
|
const deadline = Date.now() + (options.timeoutMs ?? 3e4);
|
|
@@ -30131,7 +30187,8 @@ var init_codex_app_server_native = __esm({
|
|
|
30131
30187
|
generation: existing.generation,
|
|
30132
30188
|
reattached: true,
|
|
30133
30189
|
daemonPid: existing.listenerPid,
|
|
30134
|
-
droppedWhileDetached: PRESUMED_LOSSY_REJOIN
|
|
30190
|
+
droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
|
|
30191
|
+
authAccountId: existing.authAccountId
|
|
30135
30192
|
};
|
|
30136
30193
|
} catch {
|
|
30137
30194
|
try {
|
|
@@ -30147,7 +30204,8 @@ var init_codex_app_server_native = __esm({
|
|
|
30147
30204
|
generation: record.generation,
|
|
30148
30205
|
reattached: false,
|
|
30149
30206
|
daemonPid: record.listenerPid,
|
|
30150
|
-
droppedWhileDetached: 0
|
|
30207
|
+
droppedWhileDetached: 0,
|
|
30208
|
+
authAccountId: record.authAccountId
|
|
30151
30209
|
};
|
|
30152
30210
|
} catch (error) {
|
|
30153
30211
|
const raced = liveNativeRecord(options.stateDir, options.projectId);
|
|
@@ -30157,12 +30215,13 @@ var init_codex_app_server_native = __esm({
|
|
|
30157
30215
|
generation: raced.generation,
|
|
30158
30216
|
reattached: true,
|
|
30159
30217
|
daemonPid: raced.listenerPid,
|
|
30160
|
-
droppedWhileDetached: PRESUMED_LOSSY_REJOIN
|
|
30218
|
+
droppedWhileDetached: PRESUMED_LOSSY_REJOIN,
|
|
30219
|
+
authAccountId: raced.authAccountId
|
|
30161
30220
|
};
|
|
30162
30221
|
}
|
|
30163
30222
|
log.error("codex", `codex app-server native listener unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
|
|
30164
30223
|
const child = spawn2(options.codexBin, codexAppServerArgv(["--stdio"], options.frizzMcp), { cwd: options.cwd, env: options.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
30165
|
-
return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
|
|
30224
|
+
return { process: child, generation: randomUUID9(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
|
|
30166
30225
|
}
|
|
30167
30226
|
};
|
|
30168
30227
|
}
|
|
@@ -30204,7 +30263,8 @@ function readDaemonRecord(stateDir, projectId) {
|
|
|
30204
30263
|
daemonPid: value.daemonPid,
|
|
30205
30264
|
childPid: typeof value.childPid === "number" ? value.childPid : 0,
|
|
30206
30265
|
socketPath: value.socketPath,
|
|
30207
|
-
createdAt: value.createdAt ?? ""
|
|
30266
|
+
createdAt: value.createdAt ?? "",
|
|
30267
|
+
...typeof value.authAccountId === "string" && value.authAccountId ? { authAccountId: value.authAccountId } : {}
|
|
30208
30268
|
};
|
|
30209
30269
|
} catch {
|
|
30210
30270
|
return null;
|
|
@@ -30298,6 +30358,7 @@ function forkDaemon(options) {
|
|
|
30298
30358
|
generation,
|
|
30299
30359
|
clientInfo: options.clientInfo,
|
|
30300
30360
|
capabilities: options.capabilities,
|
|
30361
|
+
...options.authAccountId ? { authAccountId: options.authAccountId } : {},
|
|
30301
30362
|
// The argv is built HERE, not in the daemon: the daemon is a bare forked entry that should not have
|
|
30302
30363
|
// to resolve frizz's MCP descriptors, and building it once keeps this transport identical to the
|
|
30303
30364
|
// native listener's.
|
|
@@ -30425,7 +30486,7 @@ function inProcessCodexAppServer(options) {
|
|
|
30425
30486
|
return false;
|
|
30426
30487
|
}
|
|
30427
30488
|
});
|
|
30428
|
-
return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0 };
|
|
30489
|
+
return { process: child, generation: randomUUID10(), reattached: false, daemonPid: process.pid, droppedWhileDetached: 0, authAccountId: options.authAccountId };
|
|
30429
30490
|
}
|
|
30430
30491
|
function directChildHost(spawnChild, generationId = randomUUID10) {
|
|
30431
30492
|
return async (options) => ({
|
|
@@ -30433,7 +30494,8 @@ function directChildHost(spawnChild, generationId = randomUUID10) {
|
|
|
30433
30494
|
generation: generationId(),
|
|
30434
30495
|
reattached: false,
|
|
30435
30496
|
daemonPid: process.pid,
|
|
30436
|
-
droppedWhileDetached: 0
|
|
30497
|
+
droppedWhileDetached: 0,
|
|
30498
|
+
authAccountId: options.authAccountId
|
|
30437
30499
|
});
|
|
30438
30500
|
}
|
|
30439
30501
|
var daemonEntry, daemonCodexAppServerHost;
|
|
@@ -30452,7 +30514,7 @@ var init_codex_app_server_host = __esm({
|
|
|
30452
30514
|
if (existing) {
|
|
30453
30515
|
try {
|
|
30454
30516
|
const attached = await attach2(existing, timeoutMs);
|
|
30455
|
-
return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid };
|
|
30517
|
+
return { ...attached, generation: existing.generation, reattached: true, daemonPid: existing.daemonPid, authAccountId: existing.authAccountId };
|
|
30456
30518
|
} catch {
|
|
30457
30519
|
try {
|
|
30458
30520
|
unlinkSync3(recordPath(options.stateDir, options.projectId));
|
|
@@ -30472,7 +30534,7 @@ var init_codex_app_server_host = __esm({
|
|
|
30472
30534
|
try {
|
|
30473
30535
|
const record = await forkDaemon(options);
|
|
30474
30536
|
const attached = await attach2(record, timeoutMs);
|
|
30475
|
-
return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid };
|
|
30537
|
+
return { ...attached, generation: record.generation, reattached: false, daemonPid: record.daemonPid, authAccountId: record.authAccountId };
|
|
30476
30538
|
} catch (error) {
|
|
30477
30539
|
log.error("codex", `codex app-server daemon unavailable (${error.message}); falling back to an in-process app-server \u2014 turns will NOT survive a frizz restart`);
|
|
30478
30540
|
return inProcessCodexAppServer(options);
|
|
@@ -30481,8 +30543,627 @@ var init_codex_app_server_host = __esm({
|
|
|
30481
30543
|
}
|
|
30482
30544
|
});
|
|
30483
30545
|
|
|
30546
|
+
// packages/server/src/backend/claude-quota.ts
|
|
30547
|
+
import { join as join22 } from "node:path";
|
|
30548
|
+
import { homedir as homedir9, platform } from "node:os";
|
|
30549
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
30550
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
30551
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
30552
|
+
import { promisify as promisify3 } from "node:util";
|
|
30553
|
+
function claudeConfigDir() {
|
|
30554
|
+
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
30555
|
+
return override && override.trim() ? override : join22(homedir9(), ".claude");
|
|
30556
|
+
}
|
|
30557
|
+
function tokenFromCredentialsJson(raw2) {
|
|
30558
|
+
let doc;
|
|
30559
|
+
try {
|
|
30560
|
+
doc = JSON.parse(raw2);
|
|
30561
|
+
} catch {
|
|
30562
|
+
return void 0;
|
|
30563
|
+
}
|
|
30564
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
30565
|
+
const root = doc;
|
|
30566
|
+
const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
|
|
30567
|
+
const token = oauth.accessToken ?? oauth.access_token;
|
|
30568
|
+
return typeof token === "string" && token ? token : void 0;
|
|
30569
|
+
}
|
|
30570
|
+
async function readKeychainToken() {
|
|
30571
|
+
if (platform() !== "darwin") return void 0;
|
|
30572
|
+
try {
|
|
30573
|
+
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
30574
|
+
encoding: "utf8",
|
|
30575
|
+
timeout: 4e3
|
|
30576
|
+
});
|
|
30577
|
+
return tokenFromCredentialsJson(String(stdout).trim());
|
|
30578
|
+
} catch {
|
|
30579
|
+
return void 0;
|
|
30580
|
+
}
|
|
30581
|
+
}
|
|
30582
|
+
async function readAccessToken(configDir) {
|
|
30583
|
+
try {
|
|
30584
|
+
const fromFile = tokenFromCredentialsJson(await readFile(join22(configDir, ".credentials.json"), "utf8"));
|
|
30585
|
+
if (fromFile) return fromFile;
|
|
30586
|
+
} catch {
|
|
30587
|
+
}
|
|
30588
|
+
return readKeychainToken();
|
|
30589
|
+
}
|
|
30590
|
+
function isoToUnix(value) {
|
|
30591
|
+
const ms = typeof value === "string" ? Date.parse(value) : NaN;
|
|
30592
|
+
return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
|
|
30593
|
+
}
|
|
30594
|
+
function slugify2(name) {
|
|
30595
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
30596
|
+
}
|
|
30597
|
+
function toWindow(raw2, key, label) {
|
|
30598
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
30599
|
+
const w = raw2;
|
|
30600
|
+
const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
|
|
30601
|
+
if (util2 === void 0) return void 0;
|
|
30602
|
+
const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
|
|
30603
|
+
return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
|
|
30604
|
+
}
|
|
30605
|
+
function windowFromLimit(raw2) {
|
|
30606
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
30607
|
+
const l = raw2;
|
|
30608
|
+
const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
|
|
30609
|
+
if (percent === void 0) return void 0;
|
|
30610
|
+
const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
|
|
30611
|
+
const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
|
|
30612
|
+
const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
|
|
30613
|
+
const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
|
|
30614
|
+
let key;
|
|
30615
|
+
let label;
|
|
30616
|
+
if (group === "session") {
|
|
30617
|
+
key = "5h";
|
|
30618
|
+
label = "5h";
|
|
30619
|
+
} else if (group === "weekly") {
|
|
30620
|
+
key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
|
|
30621
|
+
label = scopeName ? `${scopeName} wk` : "Weekly";
|
|
30622
|
+
} else if (group) {
|
|
30623
|
+
key = slugify2(group);
|
|
30624
|
+
label = scopeName ? `${scopeName} ${group}` : group;
|
|
30625
|
+
} else {
|
|
30626
|
+
return void 0;
|
|
30627
|
+
}
|
|
30628
|
+
const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
|
|
30629
|
+
return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
|
|
30630
|
+
}
|
|
30631
|
+
function parseClaudeUsage(body, planType) {
|
|
30632
|
+
if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
|
|
30633
|
+
const b = body;
|
|
30634
|
+
const windows = [];
|
|
30635
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30636
|
+
const add = (w) => {
|
|
30637
|
+
if (w && !seen.has(w.key)) {
|
|
30638
|
+
seen.add(w.key);
|
|
30639
|
+
windows.push(w);
|
|
30640
|
+
}
|
|
30641
|
+
};
|
|
30642
|
+
if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
|
|
30643
|
+
add(toWindow(b.five_hour, "5h", "5h"));
|
|
30644
|
+
add(toWindow(b.seven_day, "weekly", "Weekly"));
|
|
30645
|
+
add(toWindow(b.seven_day_opus, "weekly-opus", "Opus wk"));
|
|
30646
|
+
add(toWindow(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
|
|
30647
|
+
if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
|
|
30648
|
+
return { status: "ok", planType, windows };
|
|
30649
|
+
}
|
|
30650
|
+
function cachePaths(cacheDir, configDir) {
|
|
30651
|
+
const profile = createHash9("sha256").update(configDir).digest("hex").slice(0, 12);
|
|
30652
|
+
return {
|
|
30653
|
+
data: join22(cacheDir, `claude-${profile}.json`),
|
|
30654
|
+
lock: join22(cacheDir, `claude-${profile}.lock`)
|
|
30655
|
+
};
|
|
30656
|
+
}
|
|
30657
|
+
async function readShared(path3) {
|
|
30658
|
+
try {
|
|
30659
|
+
const parsed = JSON.parse(await readFile(path3, "utf8"));
|
|
30660
|
+
if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
|
|
30661
|
+
return parsed;
|
|
30662
|
+
} catch {
|
|
30663
|
+
return void 0;
|
|
30664
|
+
}
|
|
30665
|
+
}
|
|
30666
|
+
async function writeShared(path3, value) {
|
|
30667
|
+
const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
30668
|
+
try {
|
|
30669
|
+
await writeFile(tmp, JSON.stringify(value), { mode: 384 });
|
|
30670
|
+
await rename(tmp, path3);
|
|
30671
|
+
} finally {
|
|
30672
|
+
await rm(tmp, { force: true }).catch(() => {
|
|
30673
|
+
});
|
|
30674
|
+
}
|
|
30675
|
+
}
|
|
30676
|
+
function delay(ms) {
|
|
30677
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
30678
|
+
}
|
|
30679
|
+
async function acquireLock(path3) {
|
|
30680
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
30681
|
+
for (; ; ) {
|
|
30682
|
+
try {
|
|
30683
|
+
await mkdir(path3);
|
|
30684
|
+
return () => rm(path3, { recursive: true, force: true });
|
|
30685
|
+
} catch (err) {
|
|
30686
|
+
if (err.code !== "EEXIST") throw err;
|
|
30687
|
+
try {
|
|
30688
|
+
if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
|
|
30689
|
+
await rm(path3, { recursive: true, force: true });
|
|
30690
|
+
continue;
|
|
30691
|
+
}
|
|
30692
|
+
} catch {
|
|
30693
|
+
continue;
|
|
30694
|
+
}
|
|
30695
|
+
if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
|
|
30696
|
+
await delay(LOCK_POLL_MS2);
|
|
30697
|
+
}
|
|
30698
|
+
}
|
|
30699
|
+
}
|
|
30700
|
+
async function runClaudeUsage(claudeBin) {
|
|
30701
|
+
const { stdout } = await execFileAsync2(
|
|
30702
|
+
// Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
|
|
30703
|
+
// EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
|
|
30704
|
+
// never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
|
|
30705
|
+
// A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
|
|
30706
|
+
resolveClaudeExecutableAbsolute(claudeBin),
|
|
30707
|
+
["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
|
|
30708
|
+
{ encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
|
|
30709
|
+
);
|
|
30710
|
+
return String(stdout);
|
|
30711
|
+
}
|
|
30712
|
+
async function refreshQuota(claudeBin, deps, now) {
|
|
30713
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
30714
|
+
const readToken = deps.readToken ?? readAccessToken;
|
|
30715
|
+
let token;
|
|
30716
|
+
try {
|
|
30717
|
+
token = await readToken(claudeConfigDir());
|
|
30718
|
+
} catch {
|
|
30719
|
+
token = void 0;
|
|
30720
|
+
}
|
|
30721
|
+
if (token) {
|
|
30722
|
+
try {
|
|
30723
|
+
const res = await doFetch(USAGE_URL, {
|
|
30724
|
+
headers: {
|
|
30725
|
+
Authorization: `Bearer ${token}`,
|
|
30726
|
+
"anthropic-beta": OAUTH_BETA,
|
|
30727
|
+
"User-Agent": USER_AGENT,
|
|
30728
|
+
"Content-Type": "application/json"
|
|
30729
|
+
},
|
|
30730
|
+
signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
|
|
30731
|
+
});
|
|
30732
|
+
if (res.ok) {
|
|
30733
|
+
const body = await res.json();
|
|
30734
|
+
const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
|
|
30735
|
+
const quota2 = parseClaudeUsage(body, planType);
|
|
30736
|
+
if (quota2.status === "ok") return quota2;
|
|
30737
|
+
} else if (res.status === 429 || res.status === 529) {
|
|
30738
|
+
throw new Error(`Usage endpoint ${res.status}`);
|
|
30739
|
+
}
|
|
30740
|
+
} catch (err) {
|
|
30741
|
+
if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
|
|
30742
|
+
}
|
|
30743
|
+
}
|
|
30744
|
+
const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
|
|
30745
|
+
if (quota.status !== "ok") throw new Error(quota.detail);
|
|
30746
|
+
return quota;
|
|
30747
|
+
}
|
|
30748
|
+
function windowIdentity(title) {
|
|
30749
|
+
for (const [pattern, key, label] of LINE_WINDOWS) {
|
|
30750
|
+
if (pattern.test(title)) return [key, label];
|
|
30751
|
+
}
|
|
30752
|
+
const scoped = /^Current week \((.+)\)$/i.exec(title);
|
|
30753
|
+
if (!scoped) return void 0;
|
|
30754
|
+
const name = scoped[1].replace(/\s+only$/i, "");
|
|
30755
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
30756
|
+
return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
|
|
30757
|
+
}
|
|
30758
|
+
function parseResetLabel(label, now) {
|
|
30759
|
+
if (!label) return void 0;
|
|
30760
|
+
const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
|
|
30761
|
+
const parts = /^(?:([A-Za-z]{3,9})\s+(\d{1,2})(?:,?\s+(\d{4}))?)?\s*(?:at\s+)?(?:(\d{1,2})(?::(\d{2}))?\s*(am|pm))?$/i.exec(text2);
|
|
30762
|
+
const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
|
|
30763
|
+
const hasClock = parts?.[6] !== void 0;
|
|
30764
|
+
if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
|
|
30765
|
+
const date2 = new Date(now);
|
|
30766
|
+
if (hasClock) {
|
|
30767
|
+
let hour = Number(parts[4]) % 12;
|
|
30768
|
+
if (parts[6].toLowerCase() === "pm") hour += 12;
|
|
30769
|
+
date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
|
|
30770
|
+
} else {
|
|
30771
|
+
date2.setHours(0, 0, 0, 0);
|
|
30772
|
+
}
|
|
30773
|
+
if (month !== void 0) {
|
|
30774
|
+
date2.setMonth(month, Number(parts[2]));
|
|
30775
|
+
if (parts[3]) date2.setFullYear(Number(parts[3]));
|
|
30776
|
+
else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
|
|
30777
|
+
} else if (date2.getTime() <= now) {
|
|
30778
|
+
date2.setDate(date2.getDate() + 1);
|
|
30779
|
+
}
|
|
30780
|
+
return Math.round(date2.getTime() / 1e3);
|
|
30781
|
+
}
|
|
30782
|
+
const parsed = Date.parse(text2);
|
|
30783
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
30784
|
+
const date = new Date(parsed);
|
|
30785
|
+
if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
|
|
30786
|
+
return Math.round(date.getTime() / 1e3);
|
|
30787
|
+
}
|
|
30788
|
+
function parseClaudeUsageOutput(stdout, now = Date.now()) {
|
|
30789
|
+
let text2 = stdout;
|
|
30790
|
+
try {
|
|
30791
|
+
const envelope = JSON.parse(stdout);
|
|
30792
|
+
if (typeof envelope.result === "string") text2 = envelope.result;
|
|
30793
|
+
} catch {
|
|
30794
|
+
}
|
|
30795
|
+
const windows = [];
|
|
30796
|
+
for (const line of text2.split(/\r?\n/)) {
|
|
30797
|
+
const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
|
|
30798
|
+
if (!match2) continue;
|
|
30799
|
+
const identity = windowIdentity(match2[1]);
|
|
30800
|
+
if (!identity) continue;
|
|
30801
|
+
const resetsAt = parseResetLabel(match2[3], now);
|
|
30802
|
+
windows.push({
|
|
30803
|
+
key: identity[0],
|
|
30804
|
+
label: identity[1],
|
|
30805
|
+
usedPercent: Number(match2[2]),
|
|
30806
|
+
...resetsAt === void 0 ? {} : { resetsAt }
|
|
30807
|
+
});
|
|
30808
|
+
}
|
|
30809
|
+
if (windows.length === 0) {
|
|
30810
|
+
return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
|
|
30811
|
+
}
|
|
30812
|
+
return { status: "ok", windows };
|
|
30813
|
+
}
|
|
30814
|
+
async function tryAcquireLock2(path3) {
|
|
30815
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
30816
|
+
try {
|
|
30817
|
+
await mkdir(path3);
|
|
30818
|
+
return () => rm(path3, { recursive: true, force: true });
|
|
30819
|
+
} catch (err) {
|
|
30820
|
+
if (err.code !== "EEXIST") return void 0;
|
|
30821
|
+
try {
|
|
30822
|
+
if (Date.now() - (await stat(path3)).mtimeMs <= LOCK_STALE_MS) return void 0;
|
|
30823
|
+
await rm(path3, { recursive: true, force: true });
|
|
30824
|
+
} catch {
|
|
30825
|
+
return void 0;
|
|
30826
|
+
}
|
|
30827
|
+
}
|
|
30828
|
+
}
|
|
30829
|
+
return void 0;
|
|
30830
|
+
}
|
|
30831
|
+
function refreshSharedInBackground(paths, claudeBin, deps, now) {
|
|
30832
|
+
if (backgroundRefreshes.has(paths.data)) return;
|
|
30833
|
+
const task = (async () => {
|
|
30834
|
+
const release = await tryAcquireLock2(paths.lock);
|
|
30835
|
+
if (!release) return;
|
|
30836
|
+
try {
|
|
30837
|
+
const quota = await refreshQuota(claudeBin, deps, now);
|
|
30838
|
+
await writeShared(paths.data, { at: now, quota });
|
|
30839
|
+
} finally {
|
|
30840
|
+
await release().catch(() => {
|
|
30841
|
+
});
|
|
30842
|
+
}
|
|
30843
|
+
})().catch(() => {
|
|
30844
|
+
}).finally(() => backgroundRefreshes.delete(paths.data));
|
|
30845
|
+
backgroundRefreshes.set(paths.data, task);
|
|
30846
|
+
}
|
|
30847
|
+
async function refreshClaudeQuotaInBackground(claudeBin = "claude", deps = {}) {
|
|
30848
|
+
const now = (deps.now ?? Date.now)();
|
|
30849
|
+
const configDir = claudeConfigDir();
|
|
30850
|
+
const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
|
|
30851
|
+
try {
|
|
30852
|
+
await mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
30853
|
+
} catch {
|
|
30854
|
+
return;
|
|
30855
|
+
}
|
|
30856
|
+
refreshSharedInBackground(cachePaths(cacheDir, configDir), claudeBin, deps, now);
|
|
30857
|
+
}
|
|
30858
|
+
async function readClaudeQuota(claudeBin = "claude", deps = {}, options = {}) {
|
|
30859
|
+
const now = (deps.now ?? Date.now)();
|
|
30860
|
+
const configDir = claudeConfigDir();
|
|
30861
|
+
const cacheDir = deps.cacheDir ?? join22(frizzRoots().cache, "quota-cache");
|
|
30862
|
+
const paths = cachePaths(cacheDir, configDir);
|
|
30863
|
+
try {
|
|
30864
|
+
await mkdir(cacheDir, { recursive: true, mode: 448 });
|
|
30865
|
+
} catch {
|
|
30866
|
+
try {
|
|
30867
|
+
return await refreshQuota(claudeBin, deps, now);
|
|
30868
|
+
} catch {
|
|
30869
|
+
return { status: "unavailable", windows: [], detail: "Claude usage unavailable" };
|
|
30870
|
+
}
|
|
30871
|
+
}
|
|
30872
|
+
const initial = await readShared(paths.data);
|
|
30873
|
+
if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
|
|
30874
|
+
return initial.quota;
|
|
30875
|
+
}
|
|
30876
|
+
if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
|
|
30877
|
+
refreshSharedInBackground(paths, claudeBin, deps, now);
|
|
30878
|
+
const ageMs = now - initial.at;
|
|
30879
|
+
if (ageMs < 10 * 6e4) return initial.quota;
|
|
30880
|
+
return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
|
|
30881
|
+
}
|
|
30882
|
+
let release;
|
|
30883
|
+
try {
|
|
30884
|
+
release = await acquireLock(paths.lock);
|
|
30885
|
+
const afterLock = await readShared(paths.data);
|
|
30886
|
+
if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
|
|
30887
|
+
const quota = await refreshQuota(claudeBin, deps, now);
|
|
30888
|
+
await writeShared(paths.data, { at: now, quota }).catch(() => {
|
|
30889
|
+
});
|
|
30890
|
+
return quota;
|
|
30891
|
+
} catch {
|
|
30892
|
+
const fallback = await readShared(paths.data);
|
|
30893
|
+
if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
|
|
30894
|
+
return {
|
|
30895
|
+
...fallback.quota,
|
|
30896
|
+
detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
|
|
30897
|
+
};
|
|
30898
|
+
}
|
|
30899
|
+
const unavailable = { status: "unavailable", windows: [], detail: "Claude Code usage unavailable" };
|
|
30900
|
+
await writeShared(paths.data, { at: now, quota: unavailable }).catch(() => {
|
|
30901
|
+
});
|
|
30902
|
+
return unavailable;
|
|
30903
|
+
} finally {
|
|
30904
|
+
await release?.().catch(() => {
|
|
30905
|
+
});
|
|
30906
|
+
}
|
|
30907
|
+
}
|
|
30908
|
+
var execFileAsync2, USAGE_URL, OAUTH_BETA, USER_AGENT, ENDPOINT_TIMEOUT_MS, OK_TTL_MS, FAIL_TTL_MS, STALE_MAX_AGE_MS, CLI_TIMEOUT_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_POLL_MS2, LINE_WINDOWS, MONTH_INDEX, backgroundRefreshes;
|
|
30909
|
+
var init_claude_quota = __esm({
|
|
30910
|
+
"packages/server/src/backend/claude-quota.ts"() {
|
|
30911
|
+
"use strict";
|
|
30912
|
+
init_frizz_paths();
|
|
30913
|
+
init_claude_broker_host();
|
|
30914
|
+
execFileAsync2 = promisify3(execFile3);
|
|
30915
|
+
USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
30916
|
+
OAUTH_BETA = "oauth-2025-04-20";
|
|
30917
|
+
USER_AGENT = "claude-code/2.0.0";
|
|
30918
|
+
ENDPOINT_TIMEOUT_MS = 5e3;
|
|
30919
|
+
OK_TTL_MS = 2 * 6e4;
|
|
30920
|
+
FAIL_TTL_MS = 1e4;
|
|
30921
|
+
STALE_MAX_AGE_MS = 24 * 60 * 6e4;
|
|
30922
|
+
CLI_TIMEOUT_MS = 3e4;
|
|
30923
|
+
LOCK_STALE_MS = 4e4;
|
|
30924
|
+
LOCK_WAIT_MS = 15e3;
|
|
30925
|
+
LOCK_POLL_MS2 = 50;
|
|
30926
|
+
LINE_WINDOWS = [
|
|
30927
|
+
[/^Current session$/i, "5h", "5h"],
|
|
30928
|
+
[/^Current week \(all models\)$/i, "weekly", "Weekly"],
|
|
30929
|
+
[/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
|
|
30930
|
+
[/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
|
|
30931
|
+
];
|
|
30932
|
+
MONTH_INDEX = {
|
|
30933
|
+
jan: 0,
|
|
30934
|
+
feb: 1,
|
|
30935
|
+
mar: 2,
|
|
30936
|
+
apr: 3,
|
|
30937
|
+
may: 4,
|
|
30938
|
+
jun: 5,
|
|
30939
|
+
jul: 6,
|
|
30940
|
+
aug: 7,
|
|
30941
|
+
sep: 8,
|
|
30942
|
+
oct: 9,
|
|
30943
|
+
nov: 10,
|
|
30944
|
+
dec: 11
|
|
30945
|
+
};
|
|
30946
|
+
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
30947
|
+
}
|
|
30948
|
+
});
|
|
30949
|
+
|
|
30950
|
+
// packages/server/src/backend/auth-status.ts
|
|
30951
|
+
import { join as join23 } from "node:path";
|
|
30952
|
+
import { homedir as homedir10, platform as platform2 } from "node:os";
|
|
30953
|
+
import { readFileSync as readFileSync16, statSync as statSync10 } from "node:fs";
|
|
30954
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
30955
|
+
import { promisify as promisify4 } from "node:util";
|
|
30956
|
+
function claudeConfigDir2() {
|
|
30957
|
+
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
30958
|
+
return override && override.trim() ? override : join23(homedir10(), ".claude");
|
|
30959
|
+
}
|
|
30960
|
+
function claudeFileState(configDir) {
|
|
30961
|
+
let raw2;
|
|
30962
|
+
try {
|
|
30963
|
+
raw2 = readFileSync16(join23(configDir, ".credentials.json"), "utf8");
|
|
30964
|
+
} catch (err) {
|
|
30965
|
+
return err.code === "ENOENT" ? "absent" : "error";
|
|
30966
|
+
}
|
|
30967
|
+
return tokenFromCredentialsJson(raw2) ? "token" : "empty";
|
|
30968
|
+
}
|
|
30969
|
+
async function claudeKeychainState() {
|
|
30970
|
+
if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
|
|
30971
|
+
if (platform2() !== "darwin") return "absent";
|
|
30972
|
+
try {
|
|
30973
|
+
const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
30974
|
+
encoding: "utf8",
|
|
30975
|
+
timeout: 4e3
|
|
30976
|
+
});
|
|
30977
|
+
return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
|
|
30978
|
+
} catch (err) {
|
|
30979
|
+
const e = err;
|
|
30980
|
+
const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
|
|
30981
|
+
return notFound ? "absent" : "error";
|
|
30982
|
+
}
|
|
30983
|
+
}
|
|
30984
|
+
async function readClaudeAuthState(configDir = claudeConfigDir2()) {
|
|
30985
|
+
const file = claudeFileState(configDir);
|
|
30986
|
+
if (file === "token") return "authed";
|
|
30987
|
+
const keychain = await claudeKeychainState();
|
|
30988
|
+
if (keychain === "token") return "authed";
|
|
30989
|
+
if (file === "error" || keychain === "error") return "unknown";
|
|
30990
|
+
return "signed-out";
|
|
30991
|
+
}
|
|
30992
|
+
function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
30993
|
+
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
30994
|
+
let raw2;
|
|
30995
|
+
try {
|
|
30996
|
+
raw2 = readFileSync16(join23(codexHome, "auth.json"), "utf8");
|
|
30997
|
+
} catch (err) {
|
|
30998
|
+
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
30999
|
+
}
|
|
31000
|
+
let doc;
|
|
31001
|
+
try {
|
|
31002
|
+
doc = JSON.parse(raw2);
|
|
31003
|
+
} catch {
|
|
31004
|
+
return "unknown";
|
|
31005
|
+
}
|
|
31006
|
+
if (!doc || typeof doc !== "object") return "unknown";
|
|
31007
|
+
const root = doc;
|
|
31008
|
+
const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
|
|
31009
|
+
const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
|
|
31010
|
+
const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
|
|
31011
|
+
return apiKey || accessToken ? "authed" : "signed-out";
|
|
31012
|
+
}
|
|
31013
|
+
function readCodexAccountId(codexHome = defaultCodexHome()) {
|
|
31014
|
+
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return void 0;
|
|
31015
|
+
try {
|
|
31016
|
+
const doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
|
|
31017
|
+
if (!doc || typeof doc !== "object") return void 0;
|
|
31018
|
+
const tokens = doc.tokens;
|
|
31019
|
+
if (!tokens || typeof tokens !== "object") return void 0;
|
|
31020
|
+
const accountId = tokens.account_id;
|
|
31021
|
+
return typeof accountId === "string" && accountId.trim() ? accountId : void 0;
|
|
31022
|
+
} catch {
|
|
31023
|
+
return void 0;
|
|
31024
|
+
}
|
|
31025
|
+
}
|
|
31026
|
+
async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
|
|
31027
|
+
try {
|
|
31028
|
+
await exec(codexBin, ["--version"], { timeout: 5e3 });
|
|
31029
|
+
return "present";
|
|
31030
|
+
} catch (err) {
|
|
31031
|
+
return err.code === "ENOENT" ? "missing" : "unknown";
|
|
31032
|
+
}
|
|
31033
|
+
}
|
|
31034
|
+
function parseClaudeAuthStatusJson(stdout) {
|
|
31035
|
+
const start = stdout.indexOf("{");
|
|
31036
|
+
const end = stdout.lastIndexOf("}");
|
|
31037
|
+
if (start === -1 || end <= start) return void 0;
|
|
31038
|
+
try {
|
|
31039
|
+
const doc = JSON.parse(stdout.slice(start, end + 1));
|
|
31040
|
+
return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
|
|
31041
|
+
} catch {
|
|
31042
|
+
return void 0;
|
|
31043
|
+
}
|
|
31044
|
+
}
|
|
31045
|
+
async function readClaudeAuthStatusCli(opts) {
|
|
31046
|
+
try {
|
|
31047
|
+
const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
|
|
31048
|
+
const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
|
|
31049
|
+
encoding: "utf8",
|
|
31050
|
+
timeout: opts?.timeoutMs ?? 5e3,
|
|
31051
|
+
...opts?.cwd ? { cwd: opts.cwd } : {}
|
|
31052
|
+
});
|
|
31053
|
+
return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
|
|
31054
|
+
} catch (err) {
|
|
31055
|
+
const stdout = err.stdout;
|
|
31056
|
+
const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
|
|
31057
|
+
if (loggedIn === false) return "signed-out";
|
|
31058
|
+
if (loggedIn === true) return "authed";
|
|
31059
|
+
return "unknown";
|
|
31060
|
+
}
|
|
31061
|
+
}
|
|
31062
|
+
async function readClaudePreflightAuth(opts) {
|
|
31063
|
+
const local = await readClaudeAuthState();
|
|
31064
|
+
if (local !== "signed-out") return local;
|
|
31065
|
+
return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
|
|
31066
|
+
}
|
|
31067
|
+
function asEmail(value) {
|
|
31068
|
+
if (typeof value !== "string") return void 0;
|
|
31069
|
+
const email = value.trim();
|
|
31070
|
+
return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
|
|
31071
|
+
}
|
|
31072
|
+
function claudeAccountFile() {
|
|
31073
|
+
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
31074
|
+
return override && override.trim() ? join23(override, ".claude.json") : join23(homedir10(), ".claude.json");
|
|
31075
|
+
}
|
|
31076
|
+
function readClaudeAccountEmail(path3 = claudeAccountFile()) {
|
|
31077
|
+
let stat2;
|
|
31078
|
+
try {
|
|
31079
|
+
stat2 = statSync10(path3);
|
|
31080
|
+
} catch {
|
|
31081
|
+
return void 0;
|
|
31082
|
+
}
|
|
31083
|
+
const memo3 = claudeAccountMemo;
|
|
31084
|
+
if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
|
|
31085
|
+
let email;
|
|
31086
|
+
try {
|
|
31087
|
+
const doc = JSON.parse(readFileSync16(path3, "utf8"));
|
|
31088
|
+
email = asEmail(doc?.oauthAccount?.emailAddress);
|
|
31089
|
+
} catch {
|
|
31090
|
+
email = void 0;
|
|
31091
|
+
}
|
|
31092
|
+
claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
|
|
31093
|
+
return email;
|
|
31094
|
+
}
|
|
31095
|
+
function readCodexAccountEmail(codexHome = defaultCodexHome()) {
|
|
31096
|
+
let doc;
|
|
31097
|
+
try {
|
|
31098
|
+
doc = JSON.parse(readFileSync16(join23(codexHome, "auth.json"), "utf8"));
|
|
31099
|
+
} catch {
|
|
31100
|
+
return void 0;
|
|
31101
|
+
}
|
|
31102
|
+
const idToken = doc?.tokens?.id_token;
|
|
31103
|
+
if (typeof idToken !== "string") return void 0;
|
|
31104
|
+
const payload = idToken.split(".")[1];
|
|
31105
|
+
if (!payload) return void 0;
|
|
31106
|
+
try {
|
|
31107
|
+
return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
|
|
31108
|
+
} catch {
|
|
31109
|
+
return void 0;
|
|
31110
|
+
}
|
|
31111
|
+
}
|
|
31112
|
+
function readAccountEmails() {
|
|
31113
|
+
const emails = {};
|
|
31114
|
+
const claude = tryRead(readClaudeAccountEmail);
|
|
31115
|
+
const codex = tryRead(readCodexAccountEmail);
|
|
31116
|
+
if (claude) emails.claude = claude;
|
|
31117
|
+
if (codex) emails.codex = codex;
|
|
31118
|
+
return emails;
|
|
31119
|
+
}
|
|
31120
|
+
function tryRead(read) {
|
|
31121
|
+
try {
|
|
31122
|
+
return read();
|
|
31123
|
+
} catch {
|
|
31124
|
+
return void 0;
|
|
31125
|
+
}
|
|
31126
|
+
}
|
|
31127
|
+
async function readAuthSnapshot(opts) {
|
|
31128
|
+
const [claude, codex] = await Promise.all([
|
|
31129
|
+
readClaudeAuthState().then(async (local) => {
|
|
31130
|
+
if (local !== "signed-out") return local;
|
|
31131
|
+
const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
|
|
31132
|
+
return cli === "authed" ? "authed" : local;
|
|
31133
|
+
}).catch(() => "unknown"),
|
|
31134
|
+
Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
|
|
31135
|
+
]);
|
|
31136
|
+
const emails = readAccountEmails();
|
|
31137
|
+
return {
|
|
31138
|
+
claude,
|
|
31139
|
+
codex,
|
|
31140
|
+
emails: {
|
|
31141
|
+
...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
|
|
31142
|
+
...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
|
|
31143
|
+
}
|
|
31144
|
+
};
|
|
31145
|
+
}
|
|
31146
|
+
var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
|
|
31147
|
+
var init_auth_status = __esm({
|
|
31148
|
+
"packages/server/src/backend/auth-status.ts"() {
|
|
31149
|
+
"use strict";
|
|
31150
|
+
init_claude_quota();
|
|
31151
|
+
init_codex();
|
|
31152
|
+
init_claude_broker_host();
|
|
31153
|
+
execFileAsync3 = promisify4(execFile4);
|
|
31154
|
+
ProviderAuthRequiredError = class extends Error {
|
|
31155
|
+
backend;
|
|
31156
|
+
constructor(backend) {
|
|
31157
|
+
super(`AUTH_REQUIRED:${backend}`);
|
|
31158
|
+
this.name = "ProviderAuthRequiredError";
|
|
31159
|
+
this.backend = backend;
|
|
31160
|
+
}
|
|
31161
|
+
};
|
|
31162
|
+
}
|
|
31163
|
+
});
|
|
31164
|
+
|
|
30484
31165
|
// packages/server/src/backend/codex-app-server.ts
|
|
30485
|
-
import { createHash as
|
|
31166
|
+
import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
|
|
30486
31167
|
import { StringDecoder as StringDecoder3 } from "node:string_decoder";
|
|
30487
31168
|
function selectCodexHostKind(flagValue, platform3, hasSpawn) {
|
|
30488
31169
|
if (hasSpawn) return "direct";
|
|
@@ -30829,7 +31510,7 @@ function canonicalJson2(value) {
|
|
|
30829
31510
|
return JSON.stringify(normalize3(value));
|
|
30830
31511
|
}
|
|
30831
31512
|
function logicalRequestId(method, parts) {
|
|
30832
|
-
const digest =
|
|
31513
|
+
const digest = createHash10("sha256").update(JSON.stringify([method, ...parts])).digest("hex");
|
|
30833
31514
|
return `codex-${digest}`;
|
|
30834
31515
|
}
|
|
30835
31516
|
function requestFingerprint(value) {
|
|
@@ -30839,7 +31520,7 @@ function requestFingerprint(value) {
|
|
|
30839
31520
|
delete record.startedAtMs;
|
|
30840
31521
|
delete record.autoResolutionMs;
|
|
30841
31522
|
}
|
|
30842
|
-
return
|
|
31523
|
+
return createHash10("sha256").update(canonicalJson2(normalized)).digest("hex");
|
|
30843
31524
|
}
|
|
30844
31525
|
function decision(id, semantic, label) {
|
|
30845
31526
|
return { id, semantic, label };
|
|
@@ -31127,7 +31808,7 @@ function createCodexAppServerBridge(options) {
|
|
|
31127
31808
|
function codexAppServerBridgeEnabled() {
|
|
31128
31809
|
return true;
|
|
31129
31810
|
}
|
|
31130
|
-
var CODEX_APP_SERVER_PROVIDER, CODEX_NATIVE_LISTEN_FLAG, CODEX_APP_SERVER_SUPPORTED_VERSION, CODEX_APP_SERVER_PROTOCOL_REVISION, aheadVersionWarned, PROTOCOL_FINGERPRINT, CLIENT_INFO, CLIENT_CAPABILITIES, CODEX_SANDBOX_MODES, CODEX_APPROVAL_POLICY, CODEX_DEFAULT_SANDBOX, SANDBOX_CONFIRM_TIMEOUT_MS, SANDBOX_NOOP_GRACE_MS, MAX_JSONL_BYTES, MAX_INBOUND_RECORDS, MAX_INBOUND_QUEUED_BYTES, MAX_OUTBOUND_REQUESTS, MAX_STDERR_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, BRIDGE_DB_SCHEMA_VERSION, RpcIdSchema, Opaque, SafeTimestampMs, NullableString, CommandAction, NetworkApprovalContext, FileSystemSpecialPath, FileSystemPath, FileSystemSandboxEntry, NetworkPermissions, FileSystemPermissions, RequestedPermissions, NetworkPolicyAmendment, CommandApprovalParams, FileApprovalParams, PatchChangeKind, FileUpdateChange, FileChangeItem, CommandExecutionItem, ItemStartedNotification, ItemCompletedNotification, FileChangePatchUpdatedNotification, PermissionsApprovalParams, UserInputQuestion, UserInputParams, McpBase, McpElicitationParams, ResolvedNotification, RpcProtocolError, JsonlRpcConnection, BindingRowSchema, BridgeMetaRowSchema, DISPLAY_REDACTION, UNSAFE_DISPLAY_TEXT, SECRET_NAME, commandDecisionMap, InitializeResponse, ThreadResponse, TurnResponse, TurnSteerResponse, SkillsListResponse, ThreadResumeSandbox, ThreadResumeStatus, ThreadSettingsUpdated, TurnStarted, TurnCompleted, MAX_CORRELATED_FILE_ITEMS, CODEX_APP_SERVER_TABLES, CodexAppServerBridge;
|
|
31811
|
+
var CODEX_APP_SERVER_PROVIDER, CODEX_NATIVE_LISTEN_FLAG, CODEX_APP_SERVER_SUPPORTED_VERSION, CODEX_APP_SERVER_PROTOCOL_REVISION, aheadVersionWarned, PROTOCOL_FINGERPRINT, CLIENT_INFO, CLIENT_CAPABILITIES, CODEX_SANDBOX_MODES, CODEX_APPROVAL_POLICY, CODEX_DEFAULT_SANDBOX, SANDBOX_CONFIRM_TIMEOUT_MS, SANDBOX_NOOP_GRACE_MS, MAX_JSONL_BYTES, MAX_INBOUND_JSONL_BYTES, MAX_INBOUND_RECORDS, MAX_INBOUND_QUEUED_BYTES, MAX_OUTBOUND_REQUESTS, MAX_STDERR_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, BRIDGE_DB_SCHEMA_VERSION, RpcIdSchema, Opaque, SafeTimestampMs, NullableString, CommandAction, NetworkApprovalContext, FileSystemSpecialPath, FileSystemPath, FileSystemSandboxEntry, NetworkPermissions, FileSystemPermissions, RequestedPermissions, NetworkPolicyAmendment, CommandApprovalParams, FileApprovalParams, PatchChangeKind, FileUpdateChange, FileChangeItem, CommandExecutionItem, ItemStartedNotification, ItemCompletedNotification, FileChangePatchUpdatedNotification, PermissionsApprovalParams, UserInputQuestion, UserInputParams, McpBase, McpElicitationParams, ResolvedNotification, RpcProtocolError, JsonlRpcConnection, BindingRowSchema, BridgeMetaRowSchema, DISPLAY_REDACTION, UNSAFE_DISPLAY_TEXT, SECRET_NAME, commandDecisionMap, InitializeResponse, ThreadResponse, TurnResponse, TurnSteerResponse, SkillsListResponse, ThreadResumeSandbox, ThreadResumeStatus, ThreadSettingsUpdated, TurnStarted, TurnCompleted, MAX_CORRELATED_FILE_ITEMS, CODEX_APP_SERVER_TABLES, CodexAppServerBridge;
|
|
31131
31812
|
var init_codex_app_server = __esm({
|
|
31132
31813
|
"packages/server/src/backend/codex-app-server.ts"() {
|
|
31133
31814
|
"use strict";
|
|
@@ -31139,15 +31820,16 @@ var init_codex_app_server = __esm({
|
|
|
31139
31820
|
init_credential_redaction();
|
|
31140
31821
|
init_codex_app_server_host();
|
|
31141
31822
|
init_codex_app_server_native();
|
|
31823
|
+
init_auth_status();
|
|
31142
31824
|
init_codex_mcp();
|
|
31143
31825
|
init_logging();
|
|
31144
31826
|
CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
31145
31827
|
CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
|
|
31146
|
-
CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.
|
|
31828
|
+
CODEX_APP_SERVER_SUPPORTED_VERSION = "0.153.4";
|
|
31147
31829
|
CODEX_APP_SERVER_PROTOCOL_REVISION = Object.freeze({
|
|
31148
31830
|
packageVersion: CODEX_APP_SERVER_SUPPORTED_VERSION,
|
|
31149
|
-
sourceTag: "rust-v0.153.
|
|
31150
|
-
sourceCommit: "
|
|
31831
|
+
sourceTag: "rust-v0.153.4",
|
|
31832
|
+
sourceCommit: "3d2ee51ca2d5db578f328aa75e20aa22c0197c9a"
|
|
31151
31833
|
});
|
|
31152
31834
|
PROTOCOL_FINGERPRINT = [
|
|
31153
31835
|
CODEX_APP_SERVER_PROTOCOL_REVISION.sourceTag,
|
|
@@ -31158,7 +31840,22 @@ var init_codex_app_server = __esm({
|
|
|
31158
31840
|
CLIENT_CAPABILITIES = Object.freeze({
|
|
31159
31841
|
experimentalApi: true,
|
|
31160
31842
|
requestAttestation: false,
|
|
31161
|
-
mcpServerOpenaiFormElicitation: false
|
|
31843
|
+
mcpServerOpenaiFormElicitation: false,
|
|
31844
|
+
// Unused cumulative diff snapshots caused project-wide disconnects under the old 256 KiB frame
|
|
31845
|
+
// limit. Suppress them at the producer regardless of the inbound budget; file approvals use
|
|
31846
|
+
// item/fileChange/*, not these snapshots. Native listeners negotiate on every attach.
|
|
31847
|
+
optOutNotificationMethods: [
|
|
31848
|
+
"turn/diff/updated",
|
|
31849
|
+
// Prose, reasoning, output and usage are read from the rollout, not this control connection.
|
|
31850
|
+
// A replay/burst of these used to consume the 256-record queue before lifecycle events drained.
|
|
31851
|
+
"item/agentMessage/delta",
|
|
31852
|
+
"item/reasoning/summaryTextDelta",
|
|
31853
|
+
"item/reasoning/summaryPartAdded",
|
|
31854
|
+
"item/reasoning/textDelta",
|
|
31855
|
+
"item/commandExecution/outputDelta",
|
|
31856
|
+
"thread/tokenUsage/updated",
|
|
31857
|
+
"account/rateLimits/updated"
|
|
31858
|
+
]
|
|
31162
31859
|
});
|
|
31163
31860
|
CODEX_SANDBOX_MODES = Object.freeze([
|
|
31164
31861
|
"read-only",
|
|
@@ -31170,8 +31867,9 @@ var init_codex_app_server = __esm({
|
|
|
31170
31867
|
SANDBOX_CONFIRM_TIMEOUT_MS = 8e3;
|
|
31171
31868
|
SANDBOX_NOOP_GRACE_MS = 750;
|
|
31172
31869
|
MAX_JSONL_BYTES = 256 * 1024;
|
|
31870
|
+
MAX_INBOUND_JSONL_BYTES = 8 * 1024 * 1024;
|
|
31173
31871
|
MAX_INBOUND_RECORDS = 256;
|
|
31174
|
-
MAX_INBOUND_QUEUED_BYTES =
|
|
31872
|
+
MAX_INBOUND_QUEUED_BYTES = MAX_INBOUND_JSONL_BYTES * 2;
|
|
31175
31873
|
MAX_OUTBOUND_REQUESTS = 128;
|
|
31176
31874
|
MAX_STDERR_BYTES = 16 * 1024;
|
|
31177
31875
|
DEFAULT_REQUEST_TIMEOUT_MS = 2e4;
|
|
@@ -31456,17 +32154,18 @@ var init_codex_app_server = __esm({
|
|
|
31456
32154
|
consume(chunk) {
|
|
31457
32155
|
if (this.closed) return;
|
|
31458
32156
|
this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
31459
|
-
if (Buffer.byteLength(this.buffer, "utf8") > MAX_JSONL_BYTES * 2) {
|
|
31460
|
-
this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
|
|
31461
|
-
return;
|
|
31462
|
-
}
|
|
31463
32157
|
while (true) {
|
|
31464
32158
|
const newline = this.buffer.indexOf("\n");
|
|
31465
|
-
if (newline < 0)
|
|
32159
|
+
if (newline < 0) {
|
|
32160
|
+
if (Buffer.byteLength(this.buffer, "utf8") > MAX_INBOUND_JSONL_BYTES) {
|
|
32161
|
+
this.fail("protocol", new Error("Codex app-server JSONL buffer exceeded its limit"));
|
|
32162
|
+
}
|
|
32163
|
+
return;
|
|
32164
|
+
}
|
|
31466
32165
|
const line = this.buffer.slice(0, newline);
|
|
31467
32166
|
this.buffer = this.buffer.slice(newline + 1);
|
|
31468
32167
|
if (line.trim().length === 0) continue;
|
|
31469
|
-
if (Buffer.byteLength(line, "utf8") >
|
|
32168
|
+
if (Buffer.byteLength(line, "utf8") > MAX_INBOUND_JSONL_BYTES) {
|
|
31470
32169
|
this.fail("protocol", new Error("Codex app-server JSONL message exceeded its limit"));
|
|
31471
32170
|
return;
|
|
31472
32171
|
}
|
|
@@ -31477,6 +32176,10 @@ var init_codex_app_server = __esm({
|
|
|
31477
32176
|
this.fail("protocol", new Error("Codex app-server emitted invalid JSONL"));
|
|
31478
32177
|
return;
|
|
31479
32178
|
}
|
|
32179
|
+
if (message !== null && typeof message === "object" && !Array.isArray(message)) {
|
|
32180
|
+
const envelope = message;
|
|
32181
|
+
if (!("id" in envelope) && !("jsonrpc" in envelope) && typeof envelope.method === "string" && CLIENT_CAPABILITIES.optOutNotificationMethods.includes(envelope.method)) continue;
|
|
32182
|
+
}
|
|
31480
32183
|
const bytes = Buffer.byteLength(line, "utf8");
|
|
31481
32184
|
if (this.inboundQueue.length >= MAX_INBOUND_RECORDS || this.inboundQueuedBytes + bytes > MAX_INBOUND_QUEUED_BYTES) {
|
|
31482
32185
|
this.fail("protocol", new Error("Codex app-server inbound queue exceeded its limit"));
|
|
@@ -31580,6 +32283,7 @@ var init_codex_app_server = __esm({
|
|
|
31580
32283
|
}
|
|
31581
32284
|
fail(reason, error) {
|
|
31582
32285
|
if (this.closed) return;
|
|
32286
|
+
if (reason === "protocol") this.diagnostic?.({ event: "protocol-failure", detail: error.message });
|
|
31583
32287
|
this.closed = true;
|
|
31584
32288
|
for (const pending of this.pending.values()) {
|
|
31585
32289
|
clearTimeout(pending.timer);
|
|
@@ -31684,6 +32388,11 @@ var init_codex_app_server = __esm({
|
|
|
31684
32388
|
timeoutMs;
|
|
31685
32389
|
/** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
|
|
31686
32390
|
daemonGeneration = "";
|
|
32391
|
+
/** ChatGPT account the current app-server cached when its process started. */
|
|
32392
|
+
processAuthAccountId;
|
|
32393
|
+
/** Serializes account-driven replacement across concurrent session/turn starts. */
|
|
32394
|
+
authReplacement = null;
|
|
32395
|
+
turnIdleWaiters = /* @__PURE__ */ new Set();
|
|
31687
32396
|
/**
|
|
31688
32397
|
* The handshake a FRESHLY FORKED daemon already gave us and we already rejected — i.e. what the
|
|
31689
32398
|
* codex binary on disk actually reports. Once we have heard it from a new process there is nothing
|
|
@@ -31760,7 +32469,7 @@ var init_codex_app_server = __esm({
|
|
|
31760
32469
|
if (this.scope.prepare("SELECT 1 FROM codex_app_server_session WHERE project_id = @project_id AND (thread_slug = ? OR frizz_session_id = ?)").get(input.threadSlug, input.sessionId)) {
|
|
31761
32470
|
throw new Error("Codex app-server thread slug or session id is already bound");
|
|
31762
32471
|
}
|
|
31763
|
-
const connection = await this.
|
|
32472
|
+
const connection = await this.ensureCurrentAuth();
|
|
31764
32473
|
const ephemeral = input.ephemeral ?? true;
|
|
31765
32474
|
const startedSandbox = input.permissions ? null : input.sandbox ?? "read-only";
|
|
31766
32475
|
const response = ThreadResponse.parse(await connection.request("thread/start", {
|
|
@@ -31905,7 +32614,7 @@ var init_codex_app_server = __esm({
|
|
|
31905
32614
|
const releaseOperation = this.beginOperation();
|
|
31906
32615
|
this.startingTurns.add(startKey);
|
|
31907
32616
|
try {
|
|
31908
|
-
const connection = await this.
|
|
32617
|
+
const connection = await this.ensureCurrentAuth();
|
|
31909
32618
|
let binding = this.bindingForScope(input.threadSlug, input.sessionId);
|
|
31910
32619
|
if (!binding) throw new Error("Codex app-server turn requires a bridge-owned session");
|
|
31911
32620
|
if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
|
|
@@ -31939,6 +32648,7 @@ var init_codex_app_server = __esm({
|
|
|
31939
32648
|
return { turnId: response.turn.id };
|
|
31940
32649
|
} finally {
|
|
31941
32650
|
this.pendingTurnStarts.delete(pendingKey);
|
|
32651
|
+
this.notifyTurnIdleWaiters();
|
|
31942
32652
|
}
|
|
31943
32653
|
} finally {
|
|
31944
32654
|
this.startingTurns.delete(startKey);
|
|
@@ -32338,6 +33048,8 @@ var init_codex_app_server = __esm({
|
|
|
32338
33048
|
this.openingConnection.close();
|
|
32339
33049
|
this.openingConnection = null;
|
|
32340
33050
|
}
|
|
33051
|
+
this.processAuthAccountId = void 0;
|
|
33052
|
+
this.notifyTurnIdleWaiters();
|
|
32341
33053
|
}
|
|
32342
33054
|
}
|
|
32343
33055
|
bindingForScope(threadSlug, sessionId) {
|
|
@@ -32363,11 +33075,61 @@ var init_codex_app_server = __esm({
|
|
|
32363
33075
|
this.connecting = null;
|
|
32364
33076
|
}
|
|
32365
33077
|
}
|
|
33078
|
+
/**
|
|
33079
|
+
* Codex intentionally snapshots auth.json once per AuthManager. A detached app-server therefore
|
|
33080
|
+
* keeps using the account it started with even after ccbroker atomically switches auth.json. Before
|
|
33081
|
+
* starting new model work, replace that stale process — but only after every turn already running
|
|
33082
|
+
* in the shared per-project process has reached its own completion event.
|
|
33083
|
+
*/
|
|
33084
|
+
async ensureCurrentAuth() {
|
|
33085
|
+
for (; ; ) {
|
|
33086
|
+
const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
|
|
33087
|
+
const connection = await this.ensureConnected();
|
|
33088
|
+
if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
|
|
33089
|
+
if (!this.authReplacement) {
|
|
33090
|
+
const replacement2 = this.replaceAppServerForAuthChange();
|
|
33091
|
+
this.authReplacement = replacement2;
|
|
33092
|
+
void replacement2.finally(() => {
|
|
33093
|
+
if (this.authReplacement === replacement2) this.authReplacement = null;
|
|
33094
|
+
}).catch(() => void 0);
|
|
33095
|
+
}
|
|
33096
|
+
await this.authReplacement;
|
|
33097
|
+
}
|
|
33098
|
+
}
|
|
33099
|
+
hasInFlightTurns() {
|
|
33100
|
+
if (this.pendingTurnStarts.size > 0) return true;
|
|
33101
|
+
return Boolean(this.scope.prepare(`
|
|
33102
|
+
SELECT 1 FROM codex_app_server_session
|
|
33103
|
+
WHERE project_id = @project_id AND state = 'active' AND connection_epoch = ? AND current_turn_id IS NOT NULL
|
|
33104
|
+
LIMIT 1
|
|
33105
|
+
`).get(this.connectionEpoch));
|
|
33106
|
+
}
|
|
33107
|
+
whenTurnsIdle() {
|
|
33108
|
+
if (!this.hasInFlightTurns()) return Promise.resolve();
|
|
33109
|
+
return new Promise((resolve12) => {
|
|
33110
|
+
this.turnIdleWaiters.add(resolve12);
|
|
33111
|
+
if (!this.hasInFlightTurns() && this.turnIdleWaiters.delete(resolve12)) resolve12();
|
|
33112
|
+
});
|
|
33113
|
+
}
|
|
33114
|
+
notifyTurnIdleWaiters() {
|
|
33115
|
+
if (this.dbReleased || this.hasInFlightTurns()) return;
|
|
33116
|
+
for (const resolve12 of this.turnIdleWaiters) resolve12();
|
|
33117
|
+
this.turnIdleWaiters.clear();
|
|
33118
|
+
}
|
|
33119
|
+
async replaceAppServerForAuthChange() {
|
|
33120
|
+
while (this.hasInFlightTurns()) await this.whenTurnsIdle();
|
|
33121
|
+
if (this.closed || this.dbReleased) throw new Error("Codex app-server bridge is closed");
|
|
33122
|
+
this.disconnectOwnedProcess();
|
|
33123
|
+
await stopCodexAppServerDaemon(this.options.stateDir ?? this.options.projectDir, this.options.projectId);
|
|
33124
|
+
await this.ensureConnected();
|
|
33125
|
+
this.options.diagnostic?.({ event: "auth-account-refreshed" });
|
|
33126
|
+
}
|
|
32366
33127
|
/**
|
|
32367
33128
|
* Attach to an app-server and negotiate.
|
|
32368
33129
|
*
|
|
32369
|
-
* `refork` is the version-skew recovery
|
|
32370
|
-
* daemon's life. The daemon performs `initialize`
|
|
33130
|
+
* `refork` is the version-skew recovery. Alongside the idle-boundary auth refresh above, it is one
|
|
33131
|
+
* of only two things in frizz that ever ends a Codex daemon's life. The daemon performs `initialize`
|
|
33132
|
+
* once and caches the answer for as long as it
|
|
32371
33133
|
* lives (up to six hours idle, unbounded while a client keeps reattaching). Bump
|
|
32372
33134
|
* CODEX_APP_SERVER_SUPPORTED_VERSION and Update & Restart — the ordinary upgrade path — and the
|
|
32373
33135
|
* surviving daemon happily serves the STALE userAgent to every new generation, so the gate below
|
|
@@ -32377,6 +33139,7 @@ var init_codex_app_server = __esm({
|
|
|
32377
33139
|
* for why a genuinely unsupported codex still fails loudly instead of reforking in a loop.
|
|
32378
33140
|
*/
|
|
32379
33141
|
async connect(refork = false) {
|
|
33142
|
+
const authAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
|
|
32380
33143
|
const attachment = await this.host({
|
|
32381
33144
|
projectId: this.options.projectId,
|
|
32382
33145
|
stateDir: this.options.stateDir ?? this.options.projectDir,
|
|
@@ -32387,7 +33150,8 @@ var init_codex_app_server = __esm({
|
|
|
32387
33150
|
env: codexAppServerEnvironment(),
|
|
32388
33151
|
clientInfo: CLIENT_INFO,
|
|
32389
33152
|
capabilities: CLIENT_CAPABILITIES,
|
|
32390
|
-
frizzMcp: this.options.frizzMcp
|
|
33153
|
+
frizzMcp: this.options.frizzMcp,
|
|
33154
|
+
authAccountId
|
|
32391
33155
|
});
|
|
32392
33156
|
const child = attachment.process;
|
|
32393
33157
|
let connection;
|
|
@@ -32450,6 +33214,7 @@ var init_codex_app_server = __esm({
|
|
|
32450
33214
|
this.connectionEpoch = negotiated.connectionEpoch;
|
|
32451
33215
|
this.capabilityRevision = negotiated.capabilityRevision;
|
|
32452
33216
|
this.daemonGeneration = attachment.generation;
|
|
33217
|
+
this.processAuthAccountId = attachment.authAccountId;
|
|
32453
33218
|
if (this.closed) throw new Error("Codex app-server bridge closed during negotiation");
|
|
32454
33219
|
this.connection = connection;
|
|
32455
33220
|
this.openingConnection = null;
|
|
@@ -32489,6 +33254,7 @@ var init_codex_app_server = __esm({
|
|
|
32489
33254
|
if (this.connection !== connection) return;
|
|
32490
33255
|
const epoch = this.connectionEpoch;
|
|
32491
33256
|
this.connection = null;
|
|
33257
|
+
this.processAuthAccountId = void 0;
|
|
32492
33258
|
this.forgetCorrelatedFileItems();
|
|
32493
33259
|
this.releaseSettingsWaiters();
|
|
32494
33260
|
if (this.closed || this.dbReleased) return;
|
|
@@ -32496,6 +33262,7 @@ var init_codex_app_server = __esm({
|
|
|
32496
33262
|
UPDATE codex_app_server_session SET state = 'detached', updated_at = ?
|
|
32497
33263
|
WHERE project_id = @project_id AND connection_epoch = ? AND state = 'active'
|
|
32498
33264
|
`).run(this.now().toISOString(), epoch);
|
|
33265
|
+
this.notifyTurnIdleWaiters();
|
|
32499
33266
|
this.options.diagnostic?.({ event: "disconnected", connectionEpoch: epoch, reason });
|
|
32500
33267
|
}
|
|
32501
33268
|
/**
|
|
@@ -33353,6 +34120,7 @@ var init_codex_app_server = __esm({
|
|
|
33353
34120
|
UPDATE codex_app_server_session SET current_turn_id = NULL, auto_resume_count = 0, updated_at = ?
|
|
33354
34121
|
WHERE project_id = @project_id AND codex_thread_id = ? AND connection_epoch = ? AND current_turn_id = ?
|
|
33355
34122
|
`).run(this.now().toISOString(), parsed.data.threadId, this.connectionEpoch, parsed.data.turn.id);
|
|
34123
|
+
this.notifyTurnIdleWaiters();
|
|
33356
34124
|
}
|
|
33357
34125
|
}
|
|
33358
34126
|
providerResponse(record, delivery2, input) {
|
|
@@ -33426,11 +34194,11 @@ var init_codex_app_server = __esm({
|
|
|
33426
34194
|
});
|
|
33427
34195
|
|
|
33428
34196
|
// packages/server/src/backend/codex-quota.ts
|
|
33429
|
-
import { join as
|
|
33430
|
-
import { readdirSync as readdirSync7, statSync as
|
|
34197
|
+
import { join as join24 } from "node:path";
|
|
34198
|
+
import { readdirSync as readdirSync7, statSync as statSync11, openSync as openSync12, readSync as readSync6, fstatSync, closeSync as closeSync11 } from "node:fs";
|
|
33431
34199
|
import { spawn as spawn4 } from "node:child_process";
|
|
33432
34200
|
function sessionsDir2(codexHome) {
|
|
33433
|
-
return
|
|
34201
|
+
return join24(codexHome, "sessions");
|
|
33434
34202
|
}
|
|
33435
34203
|
function newestRollouts(dir, out, budget) {
|
|
33436
34204
|
if (budget.n <= 0) return;
|
|
@@ -33444,610 +34212,206 @@ function newestRollouts(dir, out, budget) {
|
|
|
33444
34212
|
const files = entries2.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => e.name).sort(descByName2);
|
|
33445
34213
|
for (const d of dirs) {
|
|
33446
34214
|
if (budget.n <= 0) return;
|
|
33447
|
-
newestRollouts(
|
|
34215
|
+
newestRollouts(join24(dir, d), out, budget);
|
|
33448
34216
|
}
|
|
33449
34217
|
for (const f of files) {
|
|
33450
34218
|
if (budget.n <= 0) return;
|
|
33451
|
-
out.push(
|
|
34219
|
+
out.push(join24(dir, f));
|
|
33452
34220
|
budget.n--;
|
|
33453
34221
|
}
|
|
33454
34222
|
}
|
|
33455
|
-
function readTail(path3) {
|
|
33456
|
-
let fd;
|
|
33457
|
-
try {
|
|
33458
|
-
fd = openSync12(path3, "r");
|
|
33459
|
-
const size = fstatSync(fd).size;
|
|
33460
|
-
const start = Math.max(0, size - TAIL_BYTES);
|
|
33461
|
-
const len = size - start;
|
|
33462
|
-
const buf = Buffer.allocUnsafe(len);
|
|
33463
|
-
let read = 0;
|
|
33464
|
-
while (read < len) {
|
|
33465
|
-
const n = readSync6(fd, buf, read, len - read, start + read);
|
|
33466
|
-
if (n <= 0) break;
|
|
33467
|
-
read += n;
|
|
33468
|
-
}
|
|
33469
|
-
return buf.toString("utf8", 0, read);
|
|
33470
|
-
} catch {
|
|
33471
|
-
return "";
|
|
33472
|
-
} finally {
|
|
33473
|
-
if (fd !== void 0) {
|
|
33474
|
-
try {
|
|
33475
|
-
closeSync11(fd);
|
|
33476
|
-
} catch {
|
|
33477
|
-
}
|
|
33478
|
-
}
|
|
33479
|
-
}
|
|
33480
|
-
}
|
|
33481
|
-
function makeWindow(used, minutes, resetsAt) {
|
|
33482
|
-
if (used === void 0) return void 0;
|
|
33483
|
-
const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
|
|
33484
|
-
const label = key === "weekly" ? "Weekly" : key;
|
|
33485
|
-
return { key, label, usedPercent: used, resetsAt };
|
|
33486
|
-
}
|
|
33487
|
-
function toWindow(raw2) {
|
|
33488
|
-
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
33489
|
-
const w = raw2;
|
|
33490
|
-
return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
|
|
33491
|
-
}
|
|
33492
|
-
function parseCodexQuotaFromRollout(raw2) {
|
|
33493
|
-
const lines = raw2.split("\n");
|
|
33494
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
33495
|
-
const line = lines[i];
|
|
33496
|
-
if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
|
|
33497
|
-
let obj;
|
|
33498
|
-
try {
|
|
33499
|
-
obj = JSON.parse(line);
|
|
33500
|
-
} catch {
|
|
33501
|
-
continue;
|
|
33502
|
-
}
|
|
33503
|
-
if (!obj || typeof obj !== "object") continue;
|
|
33504
|
-
const top = obj;
|
|
33505
|
-
const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
|
|
33506
|
-
const rl = top.rate_limits ?? payload?.rate_limits;
|
|
33507
|
-
if (!rl || typeof rl !== "object") continue;
|
|
33508
|
-
const r = rl;
|
|
33509
|
-
const windows = [toWindow(r.primary), toWindow(r.secondary)].filter((x) => x !== void 0);
|
|
33510
|
-
if (windows.length === 0) continue;
|
|
33511
|
-
const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
|
|
33512
|
-
return { status: "ok", planType, windows };
|
|
33513
|
-
}
|
|
33514
|
-
return void 0;
|
|
33515
|
-
}
|
|
33516
|
-
function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
|
|
33517
|
-
let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
|
|
33518
|
-
try {
|
|
33519
|
-
const rollouts = [];
|
|
33520
|
-
newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
|
|
33521
|
-
const byMtime = rollouts.map((path3) => {
|
|
33522
|
-
try {
|
|
33523
|
-
return { path: path3, mtimeMs: statSync10(path3).mtimeMs };
|
|
33524
|
-
} catch {
|
|
33525
|
-
return void 0;
|
|
33526
|
-
}
|
|
33527
|
-
}).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
33528
|
-
for (const { path: path3 } of byMtime) {
|
|
33529
|
-
const parsed = parseCodexQuotaFromRollout(readTail(path3));
|
|
33530
|
-
if (parsed) {
|
|
33531
|
-
quota = parsed;
|
|
33532
|
-
break;
|
|
33533
|
-
}
|
|
33534
|
-
}
|
|
33535
|
-
} catch {
|
|
33536
|
-
quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
|
|
33537
|
-
}
|
|
33538
|
-
return quota;
|
|
33539
|
-
}
|
|
33540
|
-
function toLiveWindow(raw2) {
|
|
33541
|
-
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
33542
|
-
const w = raw2;
|
|
33543
|
-
return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
|
|
33544
|
-
}
|
|
33545
|
-
function parseCodexQuotaFromRateLimits(result) {
|
|
33546
|
-
if (!result || typeof result !== "object") return void 0;
|
|
33547
|
-
const rl = result.rateLimits;
|
|
33548
|
-
if (!rl || typeof rl !== "object") return void 0;
|
|
33549
|
-
const r = rl;
|
|
33550
|
-
const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
|
|
33551
|
-
if (windows.length === 0) return void 0;
|
|
33552
|
-
const planType = typeof r.planType === "string" ? r.planType : void 0;
|
|
33553
|
-
return { status: "ok", planType, windows };
|
|
33554
|
-
}
|
|
33555
|
-
function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
|
|
33556
|
-
return new Promise((resolve12) => {
|
|
33557
|
-
let child;
|
|
33558
|
-
try {
|
|
33559
|
-
child = spawn4(codexBin, ["app-server"], {
|
|
33560
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
33561
|
-
env: { ...process.env, CODEX_HOME: codexHome }
|
|
33562
|
-
});
|
|
33563
|
-
} catch {
|
|
33564
|
-
resolve12(void 0);
|
|
33565
|
-
return;
|
|
33566
|
-
}
|
|
33567
|
-
let settled = false;
|
|
33568
|
-
const finish = (quota) => {
|
|
33569
|
-
if (settled) return;
|
|
33570
|
-
settled = true;
|
|
33571
|
-
clearTimeout(timer);
|
|
33572
|
-
try {
|
|
33573
|
-
child.kill("SIGKILL");
|
|
33574
|
-
} catch {
|
|
33575
|
-
}
|
|
33576
|
-
resolve12(quota);
|
|
33577
|
-
};
|
|
33578
|
-
const timer = setTimeout(() => finish(void 0), timeoutMs);
|
|
33579
|
-
const send = (msg) => {
|
|
33580
|
-
try {
|
|
33581
|
-
child.stdin?.write(JSON.stringify(msg) + "\n");
|
|
33582
|
-
} catch {
|
|
33583
|
-
finish(void 0);
|
|
33584
|
-
}
|
|
33585
|
-
};
|
|
33586
|
-
child.on("error", () => finish(void 0));
|
|
33587
|
-
child.on("exit", () => finish(void 0));
|
|
33588
|
-
let buf = "";
|
|
33589
|
-
child.stdout?.on("data", (chunk) => {
|
|
33590
|
-
buf += chunk.toString("utf8");
|
|
33591
|
-
if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
|
|
33592
|
-
let nl;
|
|
33593
|
-
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
33594
|
-
const line = buf.slice(0, nl);
|
|
33595
|
-
buf = buf.slice(nl + 1);
|
|
33596
|
-
if (!line.trim()) continue;
|
|
33597
|
-
let msg;
|
|
33598
|
-
try {
|
|
33599
|
-
msg = JSON.parse(line);
|
|
33600
|
-
} catch {
|
|
33601
|
-
continue;
|
|
33602
|
-
}
|
|
33603
|
-
if (msg.id === 1) {
|
|
33604
|
-
send({ jsonrpc: "2.0", method: "initialized" });
|
|
33605
|
-
send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
|
|
33606
|
-
} else if (msg.id === 2) {
|
|
33607
|
-
finish(parseCodexQuotaFromRateLimits(msg.result));
|
|
33608
|
-
}
|
|
33609
|
-
}
|
|
33610
|
-
});
|
|
33611
|
-
send({
|
|
33612
|
-
jsonrpc: "2.0",
|
|
33613
|
-
id: 1,
|
|
33614
|
-
method: "initialize",
|
|
33615
|
-
params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
|
|
33616
|
-
});
|
|
33617
|
-
});
|
|
33618
|
-
}
|
|
33619
|
-
async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
|
|
33620
|
-
const now = Date.now();
|
|
33621
|
-
const hit = memo2.get(codexHome);
|
|
33622
|
-
if (hit && now - hit.at < TTL_MS2) return hit.quota;
|
|
33623
|
-
let quota;
|
|
33624
|
-
try {
|
|
33625
|
-
quota = await queryCodexRateLimits(codexHome, codexBin);
|
|
33626
|
-
} catch {
|
|
33627
|
-
quota = void 0;
|
|
33628
|
-
}
|
|
33629
|
-
if (!quota) {
|
|
33630
|
-
const fallback = readCodexQuotaFromRollouts(codexHome);
|
|
33631
|
-
quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
|
|
33632
|
-
}
|
|
33633
|
-
memo2.set(codexHome, { at: now, quota });
|
|
33634
|
-
return quota;
|
|
33635
|
-
}
|
|
33636
|
-
var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
|
|
33637
|
-
var init_codex_quota = __esm({
|
|
33638
|
-
"packages/server/src/backend/codex-quota.ts"() {
|
|
33639
|
-
"use strict";
|
|
33640
|
-
init_codex();
|
|
33641
|
-
init_codex_app_server();
|
|
33642
|
-
descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
|
|
33643
|
-
TAIL_BYTES = 512 * 1024;
|
|
33644
|
-
num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
33645
|
-
TTL_MS2 = 2e4;
|
|
33646
|
-
memo2 = /* @__PURE__ */ new Map();
|
|
33647
|
-
}
|
|
33648
|
-
});
|
|
33649
|
-
|
|
33650
|
-
// packages/server/src/backend/claude-quota.ts
|
|
33651
|
-
import { join as join23 } from "node:path";
|
|
33652
|
-
import { homedir as homedir9, platform } from "node:os";
|
|
33653
|
-
import { createHash as createHash10 } from "node:crypto";
|
|
33654
|
-
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
33655
|
-
import { execFile as execFile3 } from "node:child_process";
|
|
33656
|
-
import { promisify as promisify3 } from "node:util";
|
|
33657
|
-
function claudeConfigDir() {
|
|
33658
|
-
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
33659
|
-
return override && override.trim() ? override : join23(homedir9(), ".claude");
|
|
33660
|
-
}
|
|
33661
|
-
function tokenFromCredentialsJson(raw2) {
|
|
33662
|
-
let doc;
|
|
33663
|
-
try {
|
|
33664
|
-
doc = JSON.parse(raw2);
|
|
33665
|
-
} catch {
|
|
33666
|
-
return void 0;
|
|
33667
|
-
}
|
|
33668
|
-
if (!doc || typeof doc !== "object") return void 0;
|
|
33669
|
-
const root = doc;
|
|
33670
|
-
const oauth = root.claudeAiOauth && typeof root.claudeAiOauth === "object" ? root.claudeAiOauth : root;
|
|
33671
|
-
const token = oauth.accessToken ?? oauth.access_token;
|
|
33672
|
-
return typeof token === "string" && token ? token : void 0;
|
|
33673
|
-
}
|
|
33674
|
-
async function readKeychainToken() {
|
|
33675
|
-
if (platform() !== "darwin") return void 0;
|
|
33676
|
-
try {
|
|
33677
|
-
const { stdout } = await execFileAsync2("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
33678
|
-
encoding: "utf8",
|
|
33679
|
-
timeout: 4e3
|
|
33680
|
-
});
|
|
33681
|
-
return tokenFromCredentialsJson(String(stdout).trim());
|
|
33682
|
-
} catch {
|
|
33683
|
-
return void 0;
|
|
33684
|
-
}
|
|
33685
|
-
}
|
|
33686
|
-
async function readAccessToken(configDir) {
|
|
33687
|
-
try {
|
|
33688
|
-
const fromFile = tokenFromCredentialsJson(await readFile(join23(configDir, ".credentials.json"), "utf8"));
|
|
33689
|
-
if (fromFile) return fromFile;
|
|
33690
|
-
} catch {
|
|
33691
|
-
}
|
|
33692
|
-
return readKeychainToken();
|
|
33693
|
-
}
|
|
33694
|
-
function isoToUnix(value) {
|
|
33695
|
-
const ms = typeof value === "string" ? Date.parse(value) : NaN;
|
|
33696
|
-
return Number.isFinite(ms) ? Math.round(ms / 1e3) : void 0;
|
|
33697
|
-
}
|
|
33698
|
-
function slugify2(name) {
|
|
33699
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
33700
|
-
}
|
|
33701
|
-
function toWindow2(raw2, key, label) {
|
|
33702
|
-
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
33703
|
-
const w = raw2;
|
|
33704
|
-
const util2 = typeof w.utilization === "number" && Number.isFinite(w.utilization) ? w.utilization : void 0;
|
|
33705
|
-
if (util2 === void 0) return void 0;
|
|
33706
|
-
const resetsAt = isoToUnix(w.resets_at ?? w.reset_at);
|
|
33707
|
-
return { key, label, usedPercent: util2, ...resetsAt === void 0 ? {} : { resetsAt } };
|
|
33708
|
-
}
|
|
33709
|
-
function windowFromLimit(raw2) {
|
|
33710
|
-
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
33711
|
-
const l = raw2;
|
|
33712
|
-
const percent = typeof l.percent === "number" && Number.isFinite(l.percent) ? l.percent : void 0;
|
|
33713
|
-
if (percent === void 0) return void 0;
|
|
33714
|
-
const group = typeof l.group === "string" ? l.group : typeof l.kind === "string" ? l.kind : "";
|
|
33715
|
-
const scope = l.scope && typeof l.scope === "object" ? l.scope : void 0;
|
|
33716
|
-
const model = scope?.model && typeof scope.model === "object" ? scope.model : void 0;
|
|
33717
|
-
const scopeName = typeof model?.display_name === "string" && model.display_name ? model.display_name : void 0;
|
|
33718
|
-
let key;
|
|
33719
|
-
let label;
|
|
33720
|
-
if (group === "session") {
|
|
33721
|
-
key = "5h";
|
|
33722
|
-
label = "5h";
|
|
33723
|
-
} else if (group === "weekly") {
|
|
33724
|
-
key = scopeName ? `weekly-${slugify2(scopeName)}` : "weekly";
|
|
33725
|
-
label = scopeName ? `${scopeName} wk` : "Weekly";
|
|
33726
|
-
} else if (group) {
|
|
33727
|
-
key = slugify2(group);
|
|
33728
|
-
label = scopeName ? `${scopeName} ${group}` : group;
|
|
33729
|
-
} else {
|
|
33730
|
-
return void 0;
|
|
33731
|
-
}
|
|
33732
|
-
const resetsAt = isoToUnix(l.resets_at ?? l.reset_at);
|
|
33733
|
-
return { key, label, usedPercent: percent, ...resetsAt === void 0 ? {} : { resetsAt } };
|
|
33734
|
-
}
|
|
33735
|
-
function parseClaudeUsage(body, planType) {
|
|
33736
|
-
if (!body || typeof body !== "object") return { status: "unavailable", windows: [], detail: "Malformed usage response" };
|
|
33737
|
-
const b = body;
|
|
33738
|
-
const windows = [];
|
|
33739
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33740
|
-
const add = (w) => {
|
|
33741
|
-
if (w && !seen.has(w.key)) {
|
|
33742
|
-
seen.add(w.key);
|
|
33743
|
-
windows.push(w);
|
|
33744
|
-
}
|
|
33745
|
-
};
|
|
33746
|
-
if (Array.isArray(b.limits)) for (const entry of b.limits) add(windowFromLimit(entry));
|
|
33747
|
-
add(toWindow2(b.five_hour, "5h", "5h"));
|
|
33748
|
-
add(toWindow2(b.seven_day, "weekly", "Weekly"));
|
|
33749
|
-
add(toWindow2(b.seven_day_opus, "weekly-opus", "Opus wk"));
|
|
33750
|
-
add(toWindow2(b.seven_day_sonnet, "weekly-sonnet", "Sonnet wk"));
|
|
33751
|
-
if (windows.length === 0) return { status: "unavailable", windows: [], detail: "No usage windows reported" };
|
|
33752
|
-
return { status: "ok", planType, windows };
|
|
33753
|
-
}
|
|
33754
|
-
function cachePaths(cacheDir, configDir) {
|
|
33755
|
-
const profile = createHash10("sha256").update(configDir).digest("hex").slice(0, 12);
|
|
33756
|
-
return {
|
|
33757
|
-
data: join23(cacheDir, `claude-${profile}.json`),
|
|
33758
|
-
lock: join23(cacheDir, `claude-${profile}.lock`)
|
|
33759
|
-
};
|
|
33760
|
-
}
|
|
33761
|
-
async function readShared(path3) {
|
|
33762
|
-
try {
|
|
33763
|
-
const parsed = JSON.parse(await readFile(path3, "utf8"));
|
|
33764
|
-
if (!Number.isFinite(parsed.at) || !parsed.quota || !Array.isArray(parsed.quota.windows)) return void 0;
|
|
33765
|
-
return parsed;
|
|
33766
|
-
} catch {
|
|
33767
|
-
return void 0;
|
|
33768
|
-
}
|
|
33769
|
-
}
|
|
33770
|
-
async function writeShared(path3, value) {
|
|
33771
|
-
const tmp = `${path3}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
33772
|
-
try {
|
|
33773
|
-
await writeFile(tmp, JSON.stringify(value), { mode: 384 });
|
|
33774
|
-
await rename(tmp, path3);
|
|
33775
|
-
} finally {
|
|
33776
|
-
await rm(tmp, { force: true }).catch(() => {
|
|
33777
|
-
});
|
|
33778
|
-
}
|
|
33779
|
-
}
|
|
33780
|
-
function delay(ms) {
|
|
33781
|
-
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
33782
|
-
}
|
|
33783
|
-
async function acquireLock(path3) {
|
|
33784
|
-
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
33785
|
-
for (; ; ) {
|
|
33786
|
-
try {
|
|
33787
|
-
await mkdir(path3);
|
|
33788
|
-
return () => rm(path3, { recursive: true, force: true });
|
|
33789
|
-
} catch (err) {
|
|
33790
|
-
if (err.code !== "EEXIST") throw err;
|
|
33791
|
-
try {
|
|
33792
|
-
if (Date.now() - (await stat(path3)).mtimeMs > LOCK_STALE_MS) {
|
|
33793
|
-
await rm(path3, { recursive: true, force: true });
|
|
33794
|
-
continue;
|
|
33795
|
-
}
|
|
33796
|
-
} catch {
|
|
33797
|
-
continue;
|
|
33798
|
-
}
|
|
33799
|
-
if (Date.now() >= deadline) throw new Error("Claude quota refresh lock timed out");
|
|
33800
|
-
await delay(LOCK_POLL_MS2);
|
|
33801
|
-
}
|
|
33802
|
-
}
|
|
33803
|
-
}
|
|
33804
|
-
async function runClaudeUsage(claudeBin) {
|
|
33805
|
-
const { stdout } = await execFileAsync2(
|
|
33806
|
-
// Never a bare name: on Windows `execFile("claude")` is ENOENT and `execFile("claude.cmd")` is
|
|
33807
|
-
// EINVAL (node refuses .cmd/.bat without a shell since CVE-2024-27980), so this reader could
|
|
33808
|
-
// never reach the CLI there and every quota refresh threw. See auth-status.ts for the measurement.
|
|
33809
|
-
// A resolver throw is a refresh failure like any other — the callers' stale-serving catch owns it.
|
|
33810
|
-
resolveClaudeExecutableAbsolute(claudeBin),
|
|
33811
|
-
["-p", "/usage", "--safe-mode", "--output-format", "json", "--no-session-persistence", "--tools", ""],
|
|
33812
|
-
{ encoding: "utf8", timeout: CLI_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }
|
|
33813
|
-
);
|
|
33814
|
-
return String(stdout);
|
|
33815
|
-
}
|
|
33816
|
-
async function refreshQuota(claudeBin, deps, now) {
|
|
33817
|
-
const doFetch = deps.fetchImpl ?? fetch;
|
|
33818
|
-
const readToken = deps.readToken ?? readAccessToken;
|
|
33819
|
-
let token;
|
|
33820
|
-
try {
|
|
33821
|
-
token = await readToken(claudeConfigDir());
|
|
33822
|
-
} catch {
|
|
33823
|
-
token = void 0;
|
|
33824
|
-
}
|
|
33825
|
-
if (token) {
|
|
33826
|
-
try {
|
|
33827
|
-
const res = await doFetch(USAGE_URL, {
|
|
33828
|
-
headers: {
|
|
33829
|
-
Authorization: `Bearer ${token}`,
|
|
33830
|
-
"anthropic-beta": OAUTH_BETA,
|
|
33831
|
-
"User-Agent": USER_AGENT,
|
|
33832
|
-
"Content-Type": "application/json"
|
|
33833
|
-
},
|
|
33834
|
-
signal: AbortSignal.timeout(ENDPOINT_TIMEOUT_MS)
|
|
33835
|
-
});
|
|
33836
|
-
if (res.ok) {
|
|
33837
|
-
const body = await res.json();
|
|
33838
|
-
const planType = typeof body.plan_type === "string" ? body.plan_type : void 0;
|
|
33839
|
-
const quota2 = parseClaudeUsage(body, planType);
|
|
33840
|
-
if (quota2.status === "ok") return quota2;
|
|
33841
|
-
} else if (res.status === 429 || res.status === 529) {
|
|
33842
|
-
throw new Error(`Usage endpoint ${res.status}`);
|
|
33843
|
-
}
|
|
33844
|
-
} catch (err) {
|
|
33845
|
-
if (err instanceof Error && /^Usage endpoint (429|529)$/.test(err.message)) throw err;
|
|
33846
|
-
}
|
|
33847
|
-
}
|
|
33848
|
-
const quota = parseClaudeUsageOutput(await (deps.execUsage ?? runClaudeUsage)(claudeBin), now);
|
|
33849
|
-
if (quota.status !== "ok") throw new Error(quota.detail);
|
|
33850
|
-
return quota;
|
|
33851
|
-
}
|
|
33852
|
-
function windowIdentity(title) {
|
|
33853
|
-
for (const [pattern, key, label] of LINE_WINDOWS) {
|
|
33854
|
-
if (pattern.test(title)) return [key, label];
|
|
33855
|
-
}
|
|
33856
|
-
const scoped = /^Current week \((.+)\)$/i.exec(title);
|
|
33857
|
-
if (!scoped) return void 0;
|
|
33858
|
-
const name = scoped[1].replace(/\s+only$/i, "");
|
|
33859
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
33860
|
-
return slug ? [`weekly-${slug}`, `${name} wk`] : void 0;
|
|
33861
|
-
}
|
|
33862
|
-
function parseResetLabel(label, now) {
|
|
33863
|
-
if (!label) return void 0;
|
|
33864
|
-
const text2 = label.replace(/\s+\([^)]+\)\s*$/, "").trim();
|
|
33865
|
-
const parts = /^(?:([A-Za-z]{3,9})\s+(\d{1,2})(?:,?\s+(\d{4}))?)?\s*(?:at\s+)?(?:(\d{1,2})(?::(\d{2}))?\s*(am|pm))?$/i.exec(text2);
|
|
33866
|
-
const month = parts?.[1] ? MONTH_INDEX[parts[1].slice(0, 3).toLowerCase()] : void 0;
|
|
33867
|
-
const hasClock = parts?.[6] !== void 0;
|
|
33868
|
-
if (parts && (month !== void 0 || hasClock) && (parts[1] === void 0 || month !== void 0)) {
|
|
33869
|
-
const date2 = new Date(now);
|
|
33870
|
-
if (hasClock) {
|
|
33871
|
-
let hour = Number(parts[4]) % 12;
|
|
33872
|
-
if (parts[6].toLowerCase() === "pm") hour += 12;
|
|
33873
|
-
date2.setHours(hour, Number(parts[5] ?? 0), 0, 0);
|
|
33874
|
-
} else {
|
|
33875
|
-
date2.setHours(0, 0, 0, 0);
|
|
33876
|
-
}
|
|
33877
|
-
if (month !== void 0) {
|
|
33878
|
-
date2.setMonth(month, Number(parts[2]));
|
|
33879
|
-
if (parts[3]) date2.setFullYear(Number(parts[3]));
|
|
33880
|
-
else if (date2.getTime() <= now) date2.setFullYear(date2.getFullYear() + 1);
|
|
33881
|
-
} else if (date2.getTime() <= now) {
|
|
33882
|
-
date2.setDate(date2.getDate() + 1);
|
|
33883
|
-
}
|
|
33884
|
-
return Math.round(date2.getTime() / 1e3);
|
|
33885
|
-
}
|
|
33886
|
-
const parsed = Date.parse(text2);
|
|
33887
|
-
if (!Number.isFinite(parsed)) return void 0;
|
|
33888
|
-
const date = new Date(parsed);
|
|
33889
|
-
if (!/\b\d{4}\b/.test(text2) && date.getTime() <= now) date.setFullYear(date.getFullYear() + 1);
|
|
33890
|
-
return Math.round(date.getTime() / 1e3);
|
|
33891
|
-
}
|
|
33892
|
-
function parseClaudeUsageOutput(stdout, now = Date.now()) {
|
|
33893
|
-
let text2 = stdout;
|
|
33894
|
-
try {
|
|
33895
|
-
const envelope = JSON.parse(stdout);
|
|
33896
|
-
if (typeof envelope.result === "string") text2 = envelope.result;
|
|
33897
|
-
} catch {
|
|
33898
|
-
}
|
|
33899
|
-
const windows = [];
|
|
33900
|
-
for (const line of text2.split(/\r?\n/)) {
|
|
33901
|
-
const match2 = /^\s*(Current (?:session|week \(.+?\))):\s*(\d+(?:\.\d+)?)%\s+used\b(?:\s*·\s*resets\s+(.+?))?\s*$/i.exec(line);
|
|
33902
|
-
if (!match2) continue;
|
|
33903
|
-
const identity = windowIdentity(match2[1]);
|
|
33904
|
-
if (!identity) continue;
|
|
33905
|
-
const resetsAt = parseResetLabel(match2[3], now);
|
|
33906
|
-
windows.push({
|
|
33907
|
-
key: identity[0],
|
|
33908
|
-
label: identity[1],
|
|
33909
|
-
usedPercent: Number(match2[2]),
|
|
33910
|
-
...resetsAt === void 0 ? {} : { resetsAt }
|
|
33911
|
-
});
|
|
33912
|
-
}
|
|
33913
|
-
if (windows.length === 0) {
|
|
33914
|
-
return { status: "unavailable", windows: [], detail: "Claude Code did not report subscription quota" };
|
|
33915
|
-
}
|
|
33916
|
-
return { status: "ok", windows };
|
|
33917
|
-
}
|
|
33918
|
-
async function tryAcquireLock2(path3) {
|
|
33919
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
33920
|
-
try {
|
|
33921
|
-
await mkdir(path3);
|
|
33922
|
-
return () => rm(path3, { recursive: true, force: true });
|
|
33923
|
-
} catch (err) {
|
|
33924
|
-
if (err.code !== "EEXIST") return void 0;
|
|
34223
|
+
function readTail(path3) {
|
|
34224
|
+
let fd;
|
|
34225
|
+
try {
|
|
34226
|
+
fd = openSync12(path3, "r");
|
|
34227
|
+
const size = fstatSync(fd).size;
|
|
34228
|
+
const start = Math.max(0, size - TAIL_BYTES);
|
|
34229
|
+
const len = size - start;
|
|
34230
|
+
const buf = Buffer.allocUnsafe(len);
|
|
34231
|
+
let read = 0;
|
|
34232
|
+
while (read < len) {
|
|
34233
|
+
const n = readSync6(fd, buf, read, len - read, start + read);
|
|
34234
|
+
if (n <= 0) break;
|
|
34235
|
+
read += n;
|
|
34236
|
+
}
|
|
34237
|
+
return buf.toString("utf8", 0, read);
|
|
34238
|
+
} catch {
|
|
34239
|
+
return "";
|
|
34240
|
+
} finally {
|
|
34241
|
+
if (fd !== void 0) {
|
|
33925
34242
|
try {
|
|
33926
|
-
|
|
33927
|
-
await rm(path3, { recursive: true, force: true });
|
|
34243
|
+
closeSync11(fd);
|
|
33928
34244
|
} catch {
|
|
33929
|
-
return void 0;
|
|
33930
34245
|
}
|
|
33931
34246
|
}
|
|
33932
34247
|
}
|
|
33933
|
-
return void 0;
|
|
33934
34248
|
}
|
|
33935
|
-
function
|
|
33936
|
-
if (
|
|
33937
|
-
const
|
|
33938
|
-
|
|
33939
|
-
|
|
34249
|
+
function makeWindow(used, minutes, resetsAt) {
|
|
34250
|
+
if (used === void 0) return void 0;
|
|
34251
|
+
const key = minutes === 300 ? "5h" : minutes && minutes >= 10080 ? "weekly" : minutes ? `${Math.round(minutes / 60)}h` : "window";
|
|
34252
|
+
const label = key === "weekly" ? "Weekly" : key;
|
|
34253
|
+
return { key, label, usedPercent: used, resetsAt };
|
|
34254
|
+
}
|
|
34255
|
+
function toWindow2(raw2) {
|
|
34256
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
34257
|
+
const w = raw2;
|
|
34258
|
+
return makeWindow(num(w.used_percent), num(w.window_minutes), num(w.resets_at));
|
|
34259
|
+
}
|
|
34260
|
+
function parseCodexQuotaFromRollout(raw2) {
|
|
34261
|
+
const lines = raw2.split("\n");
|
|
34262
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
34263
|
+
const line = lines[i];
|
|
34264
|
+
if (!line.includes('"token_count"') || !line.includes("rate_limits")) continue;
|
|
34265
|
+
let obj;
|
|
33940
34266
|
try {
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
await release().catch(() => {
|
|
33945
|
-
});
|
|
34267
|
+
obj = JSON.parse(line);
|
|
34268
|
+
} catch {
|
|
34269
|
+
continue;
|
|
33946
34270
|
}
|
|
33947
|
-
|
|
33948
|
-
|
|
33949
|
-
|
|
34271
|
+
if (!obj || typeof obj !== "object") continue;
|
|
34272
|
+
const top = obj;
|
|
34273
|
+
const payload = top.payload && typeof top.payload === "object" ? top.payload : void 0;
|
|
34274
|
+
const rl = top.rate_limits ?? payload?.rate_limits;
|
|
34275
|
+
if (!rl || typeof rl !== "object") continue;
|
|
34276
|
+
const r = rl;
|
|
34277
|
+
const windows = [toWindow2(r.primary), toWindow2(r.secondary)].filter((x) => x !== void 0);
|
|
34278
|
+
if (windows.length === 0) continue;
|
|
34279
|
+
const planType = typeof r.plan_type === "string" ? r.plan_type : void 0;
|
|
34280
|
+
return { status: "ok", planType, windows };
|
|
34281
|
+
}
|
|
34282
|
+
return void 0;
|
|
33950
34283
|
}
|
|
33951
|
-
|
|
33952
|
-
|
|
33953
|
-
const configDir = claudeConfigDir();
|
|
33954
|
-
const cacheDir = deps.cacheDir ?? join23(frizzRoots().cache, "quota-cache");
|
|
34284
|
+
function readCodexQuotaFromRollouts(codexHome = defaultCodexHome()) {
|
|
34285
|
+
let quota = { status: "unavailable", windows: [], detail: "No recent Codex session" };
|
|
33955
34286
|
try {
|
|
33956
|
-
|
|
34287
|
+
const rollouts = [];
|
|
34288
|
+
newestRollouts(sessionsDir2(codexHome), rollouts, { n: 8 });
|
|
34289
|
+
const byMtime = rollouts.map((path3) => {
|
|
34290
|
+
try {
|
|
34291
|
+
return { path: path3, mtimeMs: statSync11(path3).mtimeMs };
|
|
34292
|
+
} catch {
|
|
34293
|
+
return void 0;
|
|
34294
|
+
}
|
|
34295
|
+
}).filter((x) => x !== void 0).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
34296
|
+
for (const { path: path3 } of byMtime) {
|
|
34297
|
+
const parsed = parseCodexQuotaFromRollout(readTail(path3));
|
|
34298
|
+
if (parsed) {
|
|
34299
|
+
quota = parsed;
|
|
34300
|
+
break;
|
|
34301
|
+
}
|
|
34302
|
+
}
|
|
33957
34303
|
} catch {
|
|
33958
|
-
|
|
34304
|
+
quota = { status: "unavailable", windows: [], detail: "Codex rollouts unreadable" };
|
|
33959
34305
|
}
|
|
33960
|
-
|
|
34306
|
+
return quota;
|
|
33961
34307
|
}
|
|
33962
|
-
|
|
33963
|
-
|
|
33964
|
-
const
|
|
33965
|
-
|
|
33966
|
-
|
|
33967
|
-
|
|
33968
|
-
|
|
33969
|
-
|
|
34308
|
+
function toLiveWindow(raw2) {
|
|
34309
|
+
if (!raw2 || typeof raw2 !== "object") return void 0;
|
|
34310
|
+
const w = raw2;
|
|
34311
|
+
return makeWindow(num(w.usedPercent), num(w.windowDurationMins), num(w.resetsAt));
|
|
34312
|
+
}
|
|
34313
|
+
function parseCodexQuotaFromRateLimits(result) {
|
|
34314
|
+
if (!result || typeof result !== "object") return void 0;
|
|
34315
|
+
const rl = result.rateLimits;
|
|
34316
|
+
if (!rl || typeof rl !== "object") return void 0;
|
|
34317
|
+
const r = rl;
|
|
34318
|
+
const windows = [toLiveWindow(r.primary), toLiveWindow(r.secondary)].filter((x) => x !== void 0);
|
|
34319
|
+
if (windows.length === 0) return void 0;
|
|
34320
|
+
const planType = typeof r.planType === "string" ? r.planType : void 0;
|
|
34321
|
+
return { status: "ok", planType, windows };
|
|
34322
|
+
}
|
|
34323
|
+
function queryCodexRateLimits(codexHome = defaultCodexHome(), codexBin = "codex", timeoutMs = 12e3) {
|
|
34324
|
+
return new Promise((resolve12) => {
|
|
34325
|
+
let child;
|
|
33970
34326
|
try {
|
|
33971
|
-
|
|
34327
|
+
child = spawn4(codexBin, ["app-server"], {
|
|
34328
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
34329
|
+
env: { ...process.env, CODEX_HOME: codexHome }
|
|
34330
|
+
});
|
|
33972
34331
|
} catch {
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
}
|
|
33976
|
-
const initial = await readShared(paths.data);
|
|
33977
|
-
if (!options.force && initial && now - initial.at < (initial.quota.status === "ok" ? OK_TTL_MS : FAIL_TTL_MS)) {
|
|
33978
|
-
return initial.quota;
|
|
33979
|
-
}
|
|
33980
|
-
if (!options.force && initial?.quota.status === "ok" && now - initial.at < STALE_MAX_AGE_MS) {
|
|
33981
|
-
refreshSharedInBackground(paths, claudeBin, deps, now);
|
|
33982
|
-
const ageMs = now - initial.at;
|
|
33983
|
-
if (ageMs < 10 * 6e4) return initial.quota;
|
|
33984
|
-
return { ...initial.quota, detail: `Refreshing \xB7 last updated ${Math.round(ageMs / 6e4)}m ago` };
|
|
33985
|
-
}
|
|
33986
|
-
let release;
|
|
33987
|
-
try {
|
|
33988
|
-
release = await acquireLock(paths.lock);
|
|
33989
|
-
const afterLock = await readShared(paths.data);
|
|
33990
|
-
if (afterLock && afterLock.at !== initial?.at && now - afterLock.at < OK_TTL_MS) return afterLock.quota;
|
|
33991
|
-
const quota = await refreshQuota(claudeBin, deps, now);
|
|
33992
|
-
await writeShared(paths.data, { at: now, quota }).catch(() => {
|
|
33993
|
-
});
|
|
33994
|
-
return quota;
|
|
33995
|
-
} catch {
|
|
33996
|
-
const fallback = await readShared(paths.data);
|
|
33997
|
-
if (fallback?.quota.status === "ok" && now - fallback.at < STALE_MAX_AGE_MS) {
|
|
33998
|
-
return {
|
|
33999
|
-
...fallback.quota,
|
|
34000
|
-
detail: `Could not refresh \xB7 last updated ${Math.max(1, Math.round((now - fallback.at) / 6e4))}m ago`
|
|
34001
|
-
};
|
|
34332
|
+
resolve12(void 0);
|
|
34333
|
+
return;
|
|
34002
34334
|
}
|
|
34003
|
-
|
|
34004
|
-
|
|
34335
|
+
let settled = false;
|
|
34336
|
+
const finish = (quota) => {
|
|
34337
|
+
if (settled) return;
|
|
34338
|
+
settled = true;
|
|
34339
|
+
clearTimeout(timer);
|
|
34340
|
+
try {
|
|
34341
|
+
child.kill("SIGKILL");
|
|
34342
|
+
} catch {
|
|
34343
|
+
}
|
|
34344
|
+
resolve12(quota);
|
|
34345
|
+
};
|
|
34346
|
+
const timer = setTimeout(() => finish(void 0), timeoutMs);
|
|
34347
|
+
const send = (msg) => {
|
|
34348
|
+
try {
|
|
34349
|
+
child.stdin?.write(JSON.stringify(msg) + "\n");
|
|
34350
|
+
} catch {
|
|
34351
|
+
finish(void 0);
|
|
34352
|
+
}
|
|
34353
|
+
};
|
|
34354
|
+
child.on("error", () => finish(void 0));
|
|
34355
|
+
child.on("exit", () => finish(void 0));
|
|
34356
|
+
let buf = "";
|
|
34357
|
+
child.stdout?.on("data", (chunk) => {
|
|
34358
|
+
buf += chunk.toString("utf8");
|
|
34359
|
+
if (buf.length > 4 * 1024 * 1024) buf = buf.slice(-64 * 1024);
|
|
34360
|
+
let nl;
|
|
34361
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
34362
|
+
const line = buf.slice(0, nl);
|
|
34363
|
+
buf = buf.slice(nl + 1);
|
|
34364
|
+
if (!line.trim()) continue;
|
|
34365
|
+
let msg;
|
|
34366
|
+
try {
|
|
34367
|
+
msg = JSON.parse(line);
|
|
34368
|
+
} catch {
|
|
34369
|
+
continue;
|
|
34370
|
+
}
|
|
34371
|
+
if (msg.id === 1) {
|
|
34372
|
+
send({ jsonrpc: "2.0", method: "initialized" });
|
|
34373
|
+
send({ jsonrpc: "2.0", id: 2, method: "account/rateLimits/read", params: {} });
|
|
34374
|
+
} else if (msg.id === 2) {
|
|
34375
|
+
finish(parseCodexQuotaFromRateLimits(msg.result));
|
|
34376
|
+
}
|
|
34377
|
+
}
|
|
34005
34378
|
});
|
|
34006
|
-
|
|
34007
|
-
|
|
34008
|
-
|
|
34379
|
+
send({
|
|
34380
|
+
jsonrpc: "2.0",
|
|
34381
|
+
id: 1,
|
|
34382
|
+
method: "initialize",
|
|
34383
|
+
params: { clientInfo: CLIENT_INFO, capabilities: CLIENT_CAPABILITIES }
|
|
34009
34384
|
});
|
|
34385
|
+
});
|
|
34386
|
+
}
|
|
34387
|
+
async function readCodexQuota(codexHome = defaultCodexHome(), codexBin = "codex") {
|
|
34388
|
+
const now = Date.now();
|
|
34389
|
+
const hit = memo2.get(codexHome);
|
|
34390
|
+
if (hit && now - hit.at < TTL_MS2) return hit.quota;
|
|
34391
|
+
let quota;
|
|
34392
|
+
try {
|
|
34393
|
+
quota = await queryCodexRateLimits(codexHome, codexBin);
|
|
34394
|
+
} catch {
|
|
34395
|
+
quota = void 0;
|
|
34010
34396
|
}
|
|
34397
|
+
if (!quota) {
|
|
34398
|
+
const fallback = readCodexQuotaFromRollouts(codexHome);
|
|
34399
|
+
quota = fallback.status === "ok" ? { ...fallback, detail: "From the last local session \u2014 sign-in state unconfirmed" } : fallback;
|
|
34400
|
+
}
|
|
34401
|
+
memo2.set(codexHome, { at: now, quota });
|
|
34402
|
+
return quota;
|
|
34011
34403
|
}
|
|
34012
|
-
var
|
|
34013
|
-
var
|
|
34014
|
-
"packages/server/src/backend/
|
|
34404
|
+
var descByName2, TAIL_BYTES, num, TTL_MS2, memo2;
|
|
34405
|
+
var init_codex_quota = __esm({
|
|
34406
|
+
"packages/server/src/backend/codex-quota.ts"() {
|
|
34015
34407
|
"use strict";
|
|
34016
|
-
|
|
34017
|
-
|
|
34018
|
-
|
|
34019
|
-
|
|
34020
|
-
|
|
34021
|
-
|
|
34022
|
-
|
|
34023
|
-
OK_TTL_MS = 2 * 6e4;
|
|
34024
|
-
FAIL_TTL_MS = 1e4;
|
|
34025
|
-
STALE_MAX_AGE_MS = 24 * 60 * 6e4;
|
|
34026
|
-
CLI_TIMEOUT_MS = 3e4;
|
|
34027
|
-
LOCK_STALE_MS = 4e4;
|
|
34028
|
-
LOCK_WAIT_MS = 15e3;
|
|
34029
|
-
LOCK_POLL_MS2 = 50;
|
|
34030
|
-
LINE_WINDOWS = [
|
|
34031
|
-
[/^Current session$/i, "5h", "5h"],
|
|
34032
|
-
[/^Current week \(all models\)$/i, "weekly", "Weekly"],
|
|
34033
|
-
[/^Current week \(Opus only\)$/i, "weekly-opus", "Opus wk"],
|
|
34034
|
-
[/^Current week \(Sonnet only\)$/i, "weekly-sonnet", "Sonnet wk"]
|
|
34035
|
-
];
|
|
34036
|
-
MONTH_INDEX = {
|
|
34037
|
-
jan: 0,
|
|
34038
|
-
feb: 1,
|
|
34039
|
-
mar: 2,
|
|
34040
|
-
apr: 3,
|
|
34041
|
-
may: 4,
|
|
34042
|
-
jun: 5,
|
|
34043
|
-
jul: 6,
|
|
34044
|
-
aug: 7,
|
|
34045
|
-
sep: 8,
|
|
34046
|
-
oct: 9,
|
|
34047
|
-
nov: 10,
|
|
34048
|
-
dec: 11
|
|
34049
|
-
};
|
|
34050
|
-
backgroundRefreshes = /* @__PURE__ */ new Map();
|
|
34408
|
+
init_codex();
|
|
34409
|
+
init_codex_app_server();
|
|
34410
|
+
descByName2 = (a, b) => a < b ? 1 : a > b ? -1 : 0;
|
|
34411
|
+
TAIL_BYTES = 512 * 1024;
|
|
34412
|
+
num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
34413
|
+
TTL_MS2 = 2e4;
|
|
34414
|
+
memo2 = /* @__PURE__ */ new Map();
|
|
34051
34415
|
}
|
|
34052
34416
|
});
|
|
34053
34417
|
|
|
@@ -34120,10 +34484,10 @@ var init_quota = __esm({
|
|
|
34120
34484
|
});
|
|
34121
34485
|
|
|
34122
34486
|
// packages/server/src/frizz.ts
|
|
34123
|
-
import { execFile as
|
|
34487
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
34124
34488
|
import { lstatSync as lstatSync2, realpathSync as realpathSync6 } from "node:fs";
|
|
34125
|
-
import { basename as basename5, dirname as dirname9, join as
|
|
34126
|
-
import { promisify as
|
|
34489
|
+
import { basename as basename5, dirname as dirname9, join as join25, resolve as resolve5 } from "node:path";
|
|
34490
|
+
import { promisify as promisify5 } from "node:util";
|
|
34127
34491
|
function frizzScriptsDir() {
|
|
34128
34492
|
if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
|
|
34129
34493
|
return resolve5(import.meta.dirname, "..", "..", "..", "board");
|
|
@@ -34131,7 +34495,7 @@ function frizzScriptsDir() {
|
|
|
34131
34495
|
function directFrizzRoot(projectDir) {
|
|
34132
34496
|
try {
|
|
34133
34497
|
const projectRoot = realpathSync6(projectDir);
|
|
34134
|
-
const path3 =
|
|
34498
|
+
const path3 = join25(projectRoot, ".frizz");
|
|
34135
34499
|
const stat2 = lstatSync2(path3);
|
|
34136
34500
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
34137
34501
|
const real = realpathSync6(path3);
|
|
@@ -34145,7 +34509,7 @@ function frizzDirExists(projectDir) {
|
|
|
34145
34509
|
}
|
|
34146
34510
|
async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
|
|
34147
34511
|
if (!directFrizzRoot(projectDir)) throw new Error("unsafe or missing .frizz directory");
|
|
34148
|
-
const { stdout } = await execFileP("node", [
|
|
34512
|
+
const { stdout } = await execFileP("node", [join25(scriptsDir, "index.mjs"), "--json"], {
|
|
34149
34513
|
cwd: projectDir,
|
|
34150
34514
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
|
|
34151
34515
|
maxBuffer: 32 * 1024 * 1024
|
|
@@ -34170,7 +34534,7 @@ async function readBoard(projectDir, scriptsDir = frizzScriptsDir()) {
|
|
|
34170
34534
|
};
|
|
34171
34535
|
}
|
|
34172
34536
|
async function runThreadUpdate(projectDir, slug, args, scriptsDir = frizzScriptsDir()) {
|
|
34173
|
-
await execFileP("node", [
|
|
34537
|
+
await execFileP("node", [join25(scriptsDir, "thread-update.mjs"), slug, ...args], {
|
|
34174
34538
|
cwd: projectDir,
|
|
34175
34539
|
env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir }
|
|
34176
34540
|
});
|
|
@@ -34180,7 +34544,7 @@ var init_frizz = __esm({
|
|
|
34180
34544
|
"packages/server/src/frizz.ts"() {
|
|
34181
34545
|
"use strict";
|
|
34182
34546
|
init_src();
|
|
34183
|
-
execFileP =
|
|
34547
|
+
execFileP = promisify5(execFile5);
|
|
34184
34548
|
}
|
|
34185
34549
|
});
|
|
34186
34550
|
|
|
@@ -34255,12 +34619,12 @@ var init_awaiting = __esm({
|
|
|
34255
34619
|
});
|
|
34256
34620
|
|
|
34257
34621
|
// packages/server/src/backend/project-mcp-servers.ts
|
|
34258
|
-
import { readFileSync as
|
|
34259
|
-
import { homedir as
|
|
34260
|
-
import { join as
|
|
34622
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
34623
|
+
import { homedir as homedir11 } from "node:os";
|
|
34624
|
+
import { join as join26 } from "node:path";
|
|
34261
34625
|
function readJsonObject(path3) {
|
|
34262
34626
|
try {
|
|
34263
|
-
const parsed = JSON.parse(
|
|
34627
|
+
const parsed = JSON.parse(readFileSync17(path3, "utf8"));
|
|
34264
34628
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
34265
34629
|
} catch {
|
|
34266
34630
|
return void 0;
|
|
@@ -34351,17 +34715,17 @@ function userScopeRemoteServers(account, cwd, env) {
|
|
|
34351
34715
|
}
|
|
34352
34716
|
function projectMcpServers(cwd, options = {}) {
|
|
34353
34717
|
const env = options.env ?? process.env;
|
|
34354
|
-
const home = options.home ??
|
|
34718
|
+
const home = options.home ?? homedir11();
|
|
34355
34719
|
const configOverride = options.claudeConfigDir ?? (env.CLAUDE_CONFIG_DIR?.trim() ? env.CLAUDE_CONFIG_DIR.trim() : void 0);
|
|
34356
|
-
const accountFile = configOverride ?
|
|
34357
|
-
const userSettingsFile =
|
|
34720
|
+
const accountFile = configOverride ? join26(configOverride, ".claude.json") : join26(home, ".claude.json");
|
|
34721
|
+
const userSettingsFile = join26(configOverride ?? join26(home, ".claude"), "settings.json");
|
|
34358
34722
|
const account = readJsonObject(accountFile);
|
|
34359
34723
|
const out = userScopeRemoteServers(account, cwd, env);
|
|
34360
|
-
const declared = objectField(readJsonObject(
|
|
34724
|
+
const declared = objectField(readJsonObject(join26(cwd, ".mcp.json")), "mcpServers");
|
|
34361
34725
|
if (!declared) return out;
|
|
34362
34726
|
const approval = collectApproval([
|
|
34363
|
-
readJsonObject(
|
|
34364
|
-
readJsonObject(
|
|
34727
|
+
readJsonObject(join26(cwd, ".claude", "settings.json")),
|
|
34728
|
+
readJsonObject(join26(cwd, ".claude", "settings.local.json")),
|
|
34365
34729
|
readJsonObject(userSettingsFile),
|
|
34366
34730
|
objectField(objectField(account, "projects"), cwd)
|
|
34367
34731
|
]);
|
|
@@ -34386,16 +34750,16 @@ var init_project_mcp_servers = __esm({
|
|
|
34386
34750
|
|
|
34387
34751
|
// packages/server/src/worker-plugin-dir.ts
|
|
34388
34752
|
import { existsSync as existsSync8 } from "node:fs";
|
|
34389
|
-
import { dirname as dirname10, join as
|
|
34753
|
+
import { dirname as dirname10, join as join27 } from "node:path";
|
|
34390
34754
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
34391
34755
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
34392
34756
|
const override = env.FRIZZ_WORKER_PLUGIN_DIR;
|
|
34393
|
-
if (override && existsSync8(
|
|
34757
|
+
if (override && existsSync8(join27(override, ".claude-plugin", "plugin.json")))
|
|
34394
34758
|
return override;
|
|
34395
34759
|
let current = dirname10(fileURLToPath2(moduleUrl));
|
|
34396
34760
|
for (; ; ) {
|
|
34397
|
-
const candidate =
|
|
34398
|
-
if (existsSync8(
|
|
34761
|
+
const candidate = join27(current, "cc-worker");
|
|
34762
|
+
if (existsSync8(join27(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
34399
34763
|
const parent = dirname10(current);
|
|
34400
34764
|
if (parent === current) return void 0;
|
|
34401
34765
|
current = parent;
|
|
@@ -35549,12 +35913,12 @@ var init_claude_broker_client = __esm({
|
|
|
35549
35913
|
});
|
|
35550
35914
|
|
|
35551
35915
|
// packages/server/src/backend/claude-permission-interactions.ts
|
|
35552
|
-
import { homedir as
|
|
35916
|
+
import { homedir as homedir12 } from "node:os";
|
|
35553
35917
|
function clip(text2, max) {
|
|
35554
35918
|
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
35555
35919
|
}
|
|
35556
35920
|
function tildePath(path3) {
|
|
35557
|
-
const home =
|
|
35921
|
+
const home = homedir12();
|
|
35558
35922
|
if (!path3.startsWith(home)) return path3;
|
|
35559
35923
|
const rest = path3.slice(home.length);
|
|
35560
35924
|
return rest === "" ? "~" : rest.startsWith("/") ? `~${rest}` : path3;
|
|
@@ -36203,208 +36567,6 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
36203
36567
|
}
|
|
36204
36568
|
});
|
|
36205
36569
|
|
|
36206
|
-
// packages/server/src/backend/auth-status.ts
|
|
36207
|
-
import { join as join27 } from "node:path";
|
|
36208
|
-
import { homedir as homedir12, platform as platform2 } from "node:os";
|
|
36209
|
-
import { readFileSync as readFileSync17, statSync as statSync11 } from "node:fs";
|
|
36210
|
-
import { execFile as execFile5 } from "node:child_process";
|
|
36211
|
-
import { promisify as promisify5 } from "node:util";
|
|
36212
|
-
function claudeConfigDir2() {
|
|
36213
|
-
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
36214
|
-
return override && override.trim() ? override : join27(homedir12(), ".claude");
|
|
36215
|
-
}
|
|
36216
|
-
function claudeFileState(configDir) {
|
|
36217
|
-
let raw2;
|
|
36218
|
-
try {
|
|
36219
|
-
raw2 = readFileSync17(join27(configDir, ".credentials.json"), "utf8");
|
|
36220
|
-
} catch (err) {
|
|
36221
|
-
return err.code === "ENOENT" ? "absent" : "error";
|
|
36222
|
-
}
|
|
36223
|
-
return tokenFromCredentialsJson(raw2) ? "token" : "empty";
|
|
36224
|
-
}
|
|
36225
|
-
async function claudeKeychainState() {
|
|
36226
|
-
if (process.env.FRIZZ_KEYCHAIN_DISABLED && process.env.NODE_ENV !== "production") return "absent";
|
|
36227
|
-
if (platform2() !== "darwin") return "absent";
|
|
36228
|
-
try {
|
|
36229
|
-
const { stdout } = await execFileAsync3("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
36230
|
-
encoding: "utf8",
|
|
36231
|
-
timeout: 4e3
|
|
36232
|
-
});
|
|
36233
|
-
return tokenFromCredentialsJson(String(stdout).trim()) ? "token" : "empty";
|
|
36234
|
-
} catch (err) {
|
|
36235
|
-
const e = err;
|
|
36236
|
-
const notFound = e.code === 44 || typeof e.stderr === "string" && /could not be found/i.test(e.stderr);
|
|
36237
|
-
return notFound ? "absent" : "error";
|
|
36238
|
-
}
|
|
36239
|
-
}
|
|
36240
|
-
async function readClaudeAuthState(configDir = claudeConfigDir2()) {
|
|
36241
|
-
const file = claudeFileState(configDir);
|
|
36242
|
-
if (file === "token") return "authed";
|
|
36243
|
-
const keychain = await claudeKeychainState();
|
|
36244
|
-
if (keychain === "token") return "authed";
|
|
36245
|
-
if (file === "error" || keychain === "error") return "unknown";
|
|
36246
|
-
return "signed-out";
|
|
36247
|
-
}
|
|
36248
|
-
function readCodexAuthState(codexHome = defaultCodexHome()) {
|
|
36249
|
-
if (process.env.OPENAI_API_KEY || process.env.CODEX_API_KEY || process.env.CODEX_ACCESS_TOKEN) return "authed";
|
|
36250
|
-
let raw2;
|
|
36251
|
-
try {
|
|
36252
|
-
raw2 = readFileSync17(join27(codexHome, "auth.json"), "utf8");
|
|
36253
|
-
} catch (err) {
|
|
36254
|
-
return err.code === "ENOENT" ? "signed-out" : "unknown";
|
|
36255
|
-
}
|
|
36256
|
-
let doc;
|
|
36257
|
-
try {
|
|
36258
|
-
doc = JSON.parse(raw2);
|
|
36259
|
-
} catch {
|
|
36260
|
-
return "unknown";
|
|
36261
|
-
}
|
|
36262
|
-
if (!doc || typeof doc !== "object") return "unknown";
|
|
36263
|
-
const root = doc;
|
|
36264
|
-
const apiKey = typeof root.OPENAI_API_KEY === "string" && root.OPENAI_API_KEY ? root.OPENAI_API_KEY : void 0;
|
|
36265
|
-
const tokens = root.tokens && typeof root.tokens === "object" ? root.tokens : void 0;
|
|
36266
|
-
const accessToken = tokens && typeof tokens.access_token === "string" && tokens.access_token ? tokens.access_token : void 0;
|
|
36267
|
-
return apiKey || accessToken ? "authed" : "signed-out";
|
|
36268
|
-
}
|
|
36269
|
-
async function readCodexBinaryState(codexBin = "codex", exec = execFileAsync3) {
|
|
36270
|
-
try {
|
|
36271
|
-
await exec(codexBin, ["--version"], { timeout: 5e3 });
|
|
36272
|
-
return "present";
|
|
36273
|
-
} catch (err) {
|
|
36274
|
-
return err.code === "ENOENT" ? "missing" : "unknown";
|
|
36275
|
-
}
|
|
36276
|
-
}
|
|
36277
|
-
function parseClaudeAuthStatusJson(stdout) {
|
|
36278
|
-
const start = stdout.indexOf("{");
|
|
36279
|
-
const end = stdout.lastIndexOf("}");
|
|
36280
|
-
if (start === -1 || end <= start) return void 0;
|
|
36281
|
-
try {
|
|
36282
|
-
const doc = JSON.parse(stdout.slice(start, end + 1));
|
|
36283
|
-
return typeof doc.loggedIn === "boolean" ? doc.loggedIn : void 0;
|
|
36284
|
-
} catch {
|
|
36285
|
-
return void 0;
|
|
36286
|
-
}
|
|
36287
|
-
}
|
|
36288
|
-
async function readClaudeAuthStatusCli(opts) {
|
|
36289
|
-
try {
|
|
36290
|
-
const bin = resolveClaudeExecutableAbsolute(opts?.claudeBin);
|
|
36291
|
-
const { stdout } = await execFileAsync3(bin, ["auth", "status", "--json"], {
|
|
36292
|
-
encoding: "utf8",
|
|
36293
|
-
timeout: opts?.timeoutMs ?? 5e3,
|
|
36294
|
-
...opts?.cwd ? { cwd: opts.cwd } : {}
|
|
36295
|
-
});
|
|
36296
|
-
return parseClaudeAuthStatusJson(String(stdout)) === false ? "signed-out" : "authed";
|
|
36297
|
-
} catch (err) {
|
|
36298
|
-
const stdout = err.stdout;
|
|
36299
|
-
const loggedIn = typeof stdout === "string" ? parseClaudeAuthStatusJson(stdout) : void 0;
|
|
36300
|
-
if (loggedIn === false) return "signed-out";
|
|
36301
|
-
if (loggedIn === true) return "authed";
|
|
36302
|
-
return "unknown";
|
|
36303
|
-
}
|
|
36304
|
-
}
|
|
36305
|
-
async function readClaudePreflightAuth(opts) {
|
|
36306
|
-
const local = await readClaudeAuthState();
|
|
36307
|
-
if (local !== "signed-out") return local;
|
|
36308
|
-
return readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin, ...opts?.cwd ? { cwd: opts.cwd } : {} });
|
|
36309
|
-
}
|
|
36310
|
-
function asEmail(value) {
|
|
36311
|
-
if (typeof value !== "string") return void 0;
|
|
36312
|
-
const email = value.trim();
|
|
36313
|
-
return email.length <= 320 && /^[^\s@]+@[^\s@]+$/.test(email) ? email : void 0;
|
|
36314
|
-
}
|
|
36315
|
-
function claudeAccountFile() {
|
|
36316
|
-
const override = process.env.CLAUDE_CONFIG_DIR;
|
|
36317
|
-
return override && override.trim() ? join27(override, ".claude.json") : join27(homedir12(), ".claude.json");
|
|
36318
|
-
}
|
|
36319
|
-
function readClaudeAccountEmail(path3 = claudeAccountFile()) {
|
|
36320
|
-
let stat2;
|
|
36321
|
-
try {
|
|
36322
|
-
stat2 = statSync11(path3);
|
|
36323
|
-
} catch {
|
|
36324
|
-
return void 0;
|
|
36325
|
-
}
|
|
36326
|
-
const memo3 = claudeAccountMemo;
|
|
36327
|
-
if (memo3 && memo3.path === path3 && memo3.mtimeMs === stat2.mtimeMs && memo3.size === stat2.size) return memo3.email;
|
|
36328
|
-
let email;
|
|
36329
|
-
try {
|
|
36330
|
-
const doc = JSON.parse(readFileSync17(path3, "utf8"));
|
|
36331
|
-
email = asEmail(doc?.oauthAccount?.emailAddress);
|
|
36332
|
-
} catch {
|
|
36333
|
-
email = void 0;
|
|
36334
|
-
}
|
|
36335
|
-
claudeAccountMemo = { path: path3, mtimeMs: stat2.mtimeMs, size: stat2.size, email };
|
|
36336
|
-
return email;
|
|
36337
|
-
}
|
|
36338
|
-
function readCodexAccountEmail(codexHome = defaultCodexHome()) {
|
|
36339
|
-
let doc;
|
|
36340
|
-
try {
|
|
36341
|
-
doc = JSON.parse(readFileSync17(join27(codexHome, "auth.json"), "utf8"));
|
|
36342
|
-
} catch {
|
|
36343
|
-
return void 0;
|
|
36344
|
-
}
|
|
36345
|
-
const idToken = doc?.tokens?.id_token;
|
|
36346
|
-
if (typeof idToken !== "string") return void 0;
|
|
36347
|
-
const payload = idToken.split(".")[1];
|
|
36348
|
-
if (!payload) return void 0;
|
|
36349
|
-
try {
|
|
36350
|
-
return asEmail(JSON.parse(Buffer.from(payload, "base64url").toString("utf8")).email);
|
|
36351
|
-
} catch {
|
|
36352
|
-
return void 0;
|
|
36353
|
-
}
|
|
36354
|
-
}
|
|
36355
|
-
function readAccountEmails() {
|
|
36356
|
-
const emails = {};
|
|
36357
|
-
const claude = tryRead(readClaudeAccountEmail);
|
|
36358
|
-
const codex = tryRead(readCodexAccountEmail);
|
|
36359
|
-
if (claude) emails.claude = claude;
|
|
36360
|
-
if (codex) emails.codex = codex;
|
|
36361
|
-
return emails;
|
|
36362
|
-
}
|
|
36363
|
-
function tryRead(read) {
|
|
36364
|
-
try {
|
|
36365
|
-
return read();
|
|
36366
|
-
} catch {
|
|
36367
|
-
return void 0;
|
|
36368
|
-
}
|
|
36369
|
-
}
|
|
36370
|
-
async function readAuthSnapshot(opts) {
|
|
36371
|
-
const [claude, codex] = await Promise.all([
|
|
36372
|
-
readClaudeAuthState().then(async (local) => {
|
|
36373
|
-
if (local !== "signed-out") return local;
|
|
36374
|
-
const cli = await readClaudeAuthStatusCli({ claudeBin: opts?.claudeBin });
|
|
36375
|
-
return cli === "authed" ? "authed" : local;
|
|
36376
|
-
}).catch(() => "unknown"),
|
|
36377
|
-
Promise.resolve().then(() => readCodexAuthState()).catch(() => "unknown")
|
|
36378
|
-
]);
|
|
36379
|
-
const emails = readAccountEmails();
|
|
36380
|
-
return {
|
|
36381
|
-
claude,
|
|
36382
|
-
codex,
|
|
36383
|
-
emails: {
|
|
36384
|
-
...claude === "signed-out" ? {} : emails.claude ? { claude: emails.claude } : {},
|
|
36385
|
-
...codex === "signed-out" ? {} : emails.codex ? { codex: emails.codex } : {}
|
|
36386
|
-
}
|
|
36387
|
-
};
|
|
36388
|
-
}
|
|
36389
|
-
var execFileAsync3, ProviderAuthRequiredError, claudeAccountMemo;
|
|
36390
|
-
var init_auth_status = __esm({
|
|
36391
|
-
"packages/server/src/backend/auth-status.ts"() {
|
|
36392
|
-
"use strict";
|
|
36393
|
-
init_claude_quota();
|
|
36394
|
-
init_codex();
|
|
36395
|
-
init_claude_broker_host();
|
|
36396
|
-
execFileAsync3 = promisify5(execFile5);
|
|
36397
|
-
ProviderAuthRequiredError = class extends Error {
|
|
36398
|
-
backend;
|
|
36399
|
-
constructor(backend) {
|
|
36400
|
-
super(`AUTH_REQUIRED:${backend}`);
|
|
36401
|
-
this.name = "ProviderAuthRequiredError";
|
|
36402
|
-
this.backend = backend;
|
|
36403
|
-
}
|
|
36404
|
-
};
|
|
36405
|
-
}
|
|
36406
|
-
});
|
|
36407
|
-
|
|
36408
36570
|
// packages/server/src/dispatch.ts
|
|
36409
36571
|
import { closeSync as closeSync12, constants, existsSync as existsSync9, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync18, realpathSync as realpathSync7, statSync as statSync12, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
|
|
36410
36572
|
import { basename as basename6, join as join28, dirname as dirname11 } from "node:path";
|
|
@@ -36601,6 +36763,10 @@ function workerDispatchPermission(kind, settings) {
|
|
|
36601
36763
|
if (kind === "claude" && settings.permissionMode === "bypassPermissions") return "bypassPermissions";
|
|
36602
36764
|
return WORKER_DISPATCH_PERMISSION[kind];
|
|
36603
36765
|
}
|
|
36766
|
+
function coldResumePermission(row, settings) {
|
|
36767
|
+
const saved = PermissionMode.safeParse(row.permission_mode);
|
|
36768
|
+
return saved.success ? saved.data : workerDispatchPermission("claude", settings);
|
|
36769
|
+
}
|
|
36604
36770
|
function effectivePermissionMode(kind, mode) {
|
|
36605
36771
|
if (kind === "claude") return workerPermissionMode(mode);
|
|
36606
36772
|
if (mode === "plan" || mode === "bypassPermissions") return mode;
|
|
@@ -36912,7 +37078,14 @@ function createDispatcher(deps) {
|
|
|
36912
37078
|
transcript_id: null,
|
|
36913
37079
|
state: "open",
|
|
36914
37080
|
meta: null,
|
|
36915
|
-
seen_at: null
|
|
37081
|
+
seen_at: null,
|
|
37082
|
+
// The SAME launch mode a dispatched worker gets. The follow-up that triggered this promotion
|
|
37083
|
+
// resumes the session with `row.permission_mode`, and a row with none fell through to the
|
|
37084
|
+
// bridge's `"default"` — Claude's prompt-on-everything mode, which the worker's own perm-policy
|
|
37085
|
+
// hook then DEFERS on every call (`restrictive-mode`), so each Edit became a card. Observed
|
|
37086
|
+
// 2026-09-03 on a terminal session driven from the board: every frizz-sent turn ran at
|
|
37087
|
+
// `default` while the human's own TUI on the same transcript sat at `auto`.
|
|
37088
|
+
permission_mode: workerDispatchPermission(backend, deps.getSettings())
|
|
36916
37089
|
});
|
|
36917
37090
|
deps.storage.setBackend(slug, backend);
|
|
36918
37091
|
if (backend === "codex") deps.storage.setAgentSession(slug, externalId);
|
|
@@ -38935,15 +39108,33 @@ function isHeartbeatFenceId(fenceId) {
|
|
|
38935
39108
|
function saidDone(tele) {
|
|
38936
39109
|
return tele.lastFence?.kind === "done" || tele.lastAssistantAllDone === true;
|
|
38937
39110
|
}
|
|
38938
|
-
function threadSaidDone(storage, slug, tele) {
|
|
38939
|
-
|
|
39111
|
+
function threadSaidDone(storage, slug, tele, armedAt) {
|
|
39112
|
+
const registered = registeredDoneFence(storage.getThreadDone(slug), tele.lastUserAt) !== void 0;
|
|
39113
|
+
const fenced = saidDone(tele);
|
|
39114
|
+
if (!registered && !fenced) return false;
|
|
39115
|
+
return !armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt);
|
|
39116
|
+
}
|
|
39117
|
+
function armReopenedTheLoop(storage, slug, tele, registered, fenced, armedAt) {
|
|
39118
|
+
const armed = armedAt ? Date.parse(armedAt) : Number.NaN;
|
|
39119
|
+
if (!Number.isFinite(armed)) return false;
|
|
39120
|
+
let signedOffAt = Number.NEGATIVE_INFINITY;
|
|
39121
|
+
if (registered) {
|
|
39122
|
+
const doneAt = storage.getThreadDone(slug)?.doneAt;
|
|
39123
|
+
if (doneAt !== void 0) signedOffAt = Math.max(signedOffAt, doneAt);
|
|
39124
|
+
}
|
|
39125
|
+
if (fenced) {
|
|
39126
|
+
const fencedAt = Date.parse(tele.lastAssistantAt ?? "");
|
|
39127
|
+
if (!Number.isFinite(fencedAt)) return false;
|
|
39128
|
+
signedOffAt = Math.max(signedOffAt, fencedAt);
|
|
39129
|
+
}
|
|
39130
|
+
return Number.isFinite(signedOffAt) && armed > signedOffAt;
|
|
38940
39131
|
}
|
|
38941
39132
|
function armedTimerIdsOf(storage, slug) {
|
|
38942
39133
|
return new Set(storage.listThreadTimers(slug, { armedOnly: true }).map((t) => t.id));
|
|
38943
39134
|
}
|
|
38944
|
-
function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set()) {
|
|
39135
|
+
function restMessageIsSignedOff(storage, slug, tele, _registeredPrWatches = /* @__PURE__ */ new Set(), _armedTimerIds = /* @__PURE__ */ new Set(), armedAt) {
|
|
38945
39136
|
if (tele.lastFence?.kind === "awaiting") return true;
|
|
38946
|
-
return threadSaidDone(storage, slug, tele);
|
|
39137
|
+
return threadSaidDone(storage, slug, tele, armedAt);
|
|
38947
39138
|
}
|
|
38948
39139
|
function liveActivityOf(tele, registeredPrWatches, armedTimerIds) {
|
|
38949
39140
|
const shells = /* @__PURE__ */ new Set();
|
|
@@ -39202,7 +39393,7 @@ function createScheduler(deps) {
|
|
|
39202
39393
|
if (isStopHookFenceId(item.fenceId)) {
|
|
39203
39394
|
const armed = armedRest(row);
|
|
39204
39395
|
if (!armed || item.fenceId !== stopHookFenceId(armed.armedAt, tele.lastAssistantAt ?? "")) return "superseded";
|
|
39205
|
-
if (restMessageIsSignedOff(deps.storage, item.slug, tele, registeredPrWatchesOf(deps.storage, item.slug), armedTimerIdsOf(deps.storage, item.slug))) return "superseded";
|
|
39396
|
+
if (restMessageIsSignedOff(deps.storage, item.slug, tele, registeredPrWatchesOf(deps.storage, item.slug), armedTimerIdsOf(deps.storage, item.slug), armed.armedAt)) return "superseded";
|
|
39206
39397
|
return tele.turn === "idle" ? "current-idle" : "current-busy";
|
|
39207
39398
|
}
|
|
39208
39399
|
if (isCompactFenceId(item.fenceId)) {
|
|
@@ -40059,7 +40250,7 @@ ${clock}`;
|
|
|
40059
40250
|
const armed = armedSchedule(row);
|
|
40060
40251
|
if (!armed || armed.dueAtMs > nowMs) continue;
|
|
40061
40252
|
const beatTele = deps.tailer.get(row.slug);
|
|
40062
|
-
if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele)) continue;
|
|
40253
|
+
if (beatTele && threadSaidDone(deps.storage, row.slug, beatTele, armed.armedAt)) continue;
|
|
40063
40254
|
if (openSchedulePrompt(row.slug, row.session_id)) continue;
|
|
40064
40255
|
const beat = beatIndex(row.recurring_schedule_fired_at ?? null, armed);
|
|
40065
40256
|
const fenceId = heartbeatFenceId(armed.armedAt, beat);
|
|
@@ -40109,7 +40300,7 @@ ${clock}`;
|
|
|
40109
40300
|
if (tele.lastUserAt && Date.parse(tele.lastUserAt) >= Date.parse(restedAt)) continue;
|
|
40110
40301
|
if (tele.authFault) continue;
|
|
40111
40302
|
if (tele.apiFault) continue;
|
|
40112
|
-
if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug))) continue;
|
|
40303
|
+
if (restMessageIsSignedOff(deps.storage, row.slug, tele, registeredPrWatchesOf(deps.storage, row.slug), armedTimerIdsOf(deps.storage, row.slug), armed.armedAt)) continue;
|
|
40113
40304
|
if (answersInFlight(deps.storage.listThreadQuestions(row.slug), tele.lastUserAt, true) !== void 0) continue;
|
|
40114
40305
|
const reminder = outbox.get(wakeDeliveryId(row.slug, row.session_id, signoffFenceId(restedAt)));
|
|
40115
40306
|
if (reminder && reminder.state !== "superseded" && reminder.state !== "exhausted") continue;
|
|
@@ -41486,7 +41677,7 @@ function deliverCodexWake(deps) {
|
|
|
41486
41677
|
})();
|
|
41487
41678
|
}
|
|
41488
41679
|
function deliverClaudeBrokerWake(deps) {
|
|
41489
|
-
const { bridge, slug, cwd, row, deliveryMessage, freshProcess } = deps;
|
|
41680
|
+
const { bridge, slug, cwd, row, settings, deliveryMessage, freshProcess } = deps;
|
|
41490
41681
|
const appendSystemPrompt = [
|
|
41491
41682
|
loadWorkerPrompt("claude"),
|
|
41492
41683
|
scratchpadOrientation(row.session_id, "claude"),
|
|
@@ -41497,7 +41688,7 @@ function deliverClaudeBrokerWake(deps) {
|
|
|
41497
41688
|
sessionId: row.session_id,
|
|
41498
41689
|
cwd,
|
|
41499
41690
|
text: deliveryMessage,
|
|
41500
|
-
permissionMode: row
|
|
41691
|
+
permissionMode: coldResumePermission(row, settings),
|
|
41501
41692
|
appendSystemPrompt,
|
|
41502
41693
|
model: row.model ?? void 0,
|
|
41503
41694
|
effort: row.effort ?? void 0,
|
|
@@ -41849,6 +42040,7 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
41849
42040
|
slug,
|
|
41850
42041
|
cwd: project.dir,
|
|
41851
42042
|
row,
|
|
42043
|
+
settings: getSettings(storage, home),
|
|
41852
42044
|
deliveryMessage,
|
|
41853
42045
|
// Recomputed here rather than carried on the delivery: the outbox stores a message, not a
|
|
41854
42046
|
// runtime decision, and the tail is the live answer to "is this thread still behind a wall
|
|
@@ -45127,6 +45319,21 @@ function peerSessionQueuedKey(deliveredText, pendingKeys) {
|
|
|
45127
45319
|
for (const key of pendingKeys) if (deliveredText.startsWith(key, PEER_SESSION_PREAMBLE.length)) return key;
|
|
45128
45320
|
return void 0;
|
|
45129
45321
|
}
|
|
45322
|
+
function claudeSidechainInstruction(text2) {
|
|
45323
|
+
const trimmed = text2.trim();
|
|
45324
|
+
if (trimmed.startsWith(CLAUDE_COORDINATOR_INSTRUCTION_PREFIX) && trimmed.endsWith(CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX)) {
|
|
45325
|
+
const body = trimmed.slice(
|
|
45326
|
+
CLAUDE_COORDINATOR_INSTRUCTION_PREFIX.length,
|
|
45327
|
+
-CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX.length
|
|
45328
|
+
).trim();
|
|
45329
|
+
return body || void 0;
|
|
45330
|
+
}
|
|
45331
|
+
if (!trimmed.startsWith(CLAUDE_PEER_INSTRUCTION_PREFIX)) return void 0;
|
|
45332
|
+
const wrapperStart = trimmed.indexOf("<agent-message ", CLAUDE_PEER_INSTRUCTION_PREFIX.length);
|
|
45333
|
+
const wrapperEnd = trimmed.lastIndexOf("</agent-message>");
|
|
45334
|
+
if (wrapperStart < 0 || wrapperEnd < 0) return void 0;
|
|
45335
|
+
return parseAgentMessage(trimmed.slice(wrapperStart, wrapperEnd + "</agent-message>".length))?.body.trim() || void 0;
|
|
45336
|
+
}
|
|
45130
45337
|
function retireStaleQueuedBubbles(messages, nowMs = Date.now()) {
|
|
45131
45338
|
let stale = false;
|
|
45132
45339
|
for (const m of messages) {
|
|
@@ -45315,6 +45522,19 @@ function createTranscriptFold(identityPrefix = "claude") {
|
|
|
45315
45522
|
const i = out.indexOf(pending.message);
|
|
45316
45523
|
if (i !== -1) out.splice(i, 1);
|
|
45317
45524
|
}
|
|
45525
|
+
const instruction = rec.isSidechain === true ? claudeSidechainInstruction(metaText) : void 0;
|
|
45526
|
+
if (instruction) {
|
|
45527
|
+
out.push({
|
|
45528
|
+
sourceId,
|
|
45529
|
+
role: "user",
|
|
45530
|
+
text: instruction,
|
|
45531
|
+
agentInstruction: true,
|
|
45532
|
+
tools: [],
|
|
45533
|
+
parts: [{ kind: "text", text: instruction }],
|
|
45534
|
+
at: rec.timestamp
|
|
45535
|
+
});
|
|
45536
|
+
lastAssistantId = null;
|
|
45537
|
+
}
|
|
45318
45538
|
}
|
|
45319
45539
|
return;
|
|
45320
45540
|
}
|
|
@@ -45457,15 +45677,11 @@ ${block.text}` : block.text;
|
|
|
45457
45677
|
return {
|
|
45458
45678
|
ingest,
|
|
45459
45679
|
finalize,
|
|
45460
|
-
// The SAME window the paged reader returns (
|
|
45680
|
+
// The SAME window the paged reader returns (latestTranscriptWindow), because this is the OTHER producer:
|
|
45461
45681
|
// the /ws push renders from here while the RPC page renders from there, and a push whose head sat
|
|
45462
45682
|
// further forward than the page's would splice the human's ask straight back out of a card that had
|
|
45463
45683
|
// just anchored on it (reconcileLiveMessages replaces the window wholesale).
|
|
45464
|
-
messages: () =>
|
|
45465
|
-
const all = projected();
|
|
45466
|
-
const start = latestWindowStart(all);
|
|
45467
|
-
return start === 0 ? all : all.slice(start);
|
|
45468
|
-
},
|
|
45684
|
+
messages: () => latestTranscriptWindow(projected()),
|
|
45469
45685
|
allMessages: projected,
|
|
45470
45686
|
consumedBytes: () => offset + Buffer.byteLength(buffer)
|
|
45471
45687
|
};
|
|
@@ -46091,6 +46307,7 @@ function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
|
46091
46307
|
let prevEventAt;
|
|
46092
46308
|
let lastContextTokens;
|
|
46093
46309
|
let openCompaction = null;
|
|
46310
|
+
let agentInstructionCount = 0;
|
|
46094
46311
|
const openAssistant = (at, sourceId, prose = false) => {
|
|
46095
46312
|
if (prose && cur?.text) cur = null;
|
|
46096
46313
|
if (cur) return cur;
|
|
@@ -46268,6 +46485,23 @@ ${text2}`;
|
|
|
46268
46485
|
});
|
|
46269
46486
|
break;
|
|
46270
46487
|
}
|
|
46488
|
+
case "agent-instruction": {
|
|
46489
|
+
cur = null;
|
|
46490
|
+
turnReasoning = null;
|
|
46491
|
+
const first = agentInstructionCount++ === 0;
|
|
46492
|
+
const text2 = ev.text || (ev.encrypted ? `${first ? "Task" : "Follow-up"} instructions received. Codex encrypted the message body, so Frizz can't display it.` : `${first ? "Task" : "Follow-up"} instructions received.`);
|
|
46493
|
+
out.push({
|
|
46494
|
+
sourceId,
|
|
46495
|
+
role: "user",
|
|
46496
|
+
text: text2,
|
|
46497
|
+
displayText: text2,
|
|
46498
|
+
agentInstruction: true,
|
|
46499
|
+
tools: [],
|
|
46500
|
+
parts: [{ kind: "text", text: text2 }],
|
|
46501
|
+
at: ev.at
|
|
46502
|
+
});
|
|
46503
|
+
break;
|
|
46504
|
+
}
|
|
46271
46505
|
case "compaction": {
|
|
46272
46506
|
const m = compactionMessage(sourceId, ev.at, lastContextTokens, void 0);
|
|
46273
46507
|
out.push(m);
|
|
@@ -46326,7 +46560,7 @@ function latestWindowStart(messages) {
|
|
|
46326
46560
|
let boundary = -1;
|
|
46327
46561
|
for (let i = tail - 1; i >= 0; i--) {
|
|
46328
46562
|
const m = messages[i];
|
|
46329
|
-
if (m.role === "user" && !m.wake && !m.queued) {
|
|
46563
|
+
if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
|
|
46330
46564
|
boundary = i;
|
|
46331
46565
|
break;
|
|
46332
46566
|
}
|
|
@@ -46345,6 +46579,7 @@ function latestWindowStart(messages) {
|
|
|
46345
46579
|
function latestTranscriptWindow(messages) {
|
|
46346
46580
|
const start = latestWindowStart(messages);
|
|
46347
46581
|
if (start === 0) return [...messages];
|
|
46582
|
+
const earlierInstructions = messages.slice(0, start).filter((message) => message.agentInstruction);
|
|
46348
46583
|
const pinned = [];
|
|
46349
46584
|
for (let i = 0; i < start; i++) {
|
|
46350
46585
|
const message = messages[i];
|
|
@@ -46363,7 +46598,7 @@ function latestTranscriptWindow(messages) {
|
|
|
46363
46598
|
at: message.at
|
|
46364
46599
|
});
|
|
46365
46600
|
}
|
|
46366
|
-
return [...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
|
|
46601
|
+
return [...earlierInstructions, ...messages.slice(start), ...pinned.slice(-MAX_PINNED_BACKGROUND_OPERATIONS)];
|
|
46367
46602
|
}
|
|
46368
46603
|
function codexToolCall(name, input, callId) {
|
|
46369
46604
|
if (name === "exec" && typeof input === "string") return codexExecWrapperCall(input, callId);
|
|
@@ -46492,7 +46727,7 @@ function codexReasoningCaption(text2) {
|
|
|
46492
46727
|
function codexPeerMessageCall(label, target, obj) {
|
|
46493
46728
|
const redacted = strField(obj.message);
|
|
46494
46729
|
const encrypted = redacted === ENCRYPTED_PAYLOAD;
|
|
46495
|
-
const body = !redacted ? void 0 : encrypted ? "_Codex encrypts inter-agent message bodies \u2014 the text is not recoverable from the rollout.
|
|
46730
|
+
const body = !redacted ? void 0 : encrypted ? "_Codex encrypts inter-agent message bodies \u2014 the text is not recoverable from the rollout. The recipient's transcript marks when it arrived, but can't display the encrypted text._" : capToolInput(redacted);
|
|
46496
46731
|
return {
|
|
46497
46732
|
name: label,
|
|
46498
46733
|
detail: target,
|
|
@@ -47185,7 +47420,7 @@ function pageProjectedTranscript(messages, anchor, limits = {}) {
|
|
|
47185
47420
|
let boundary = 0;
|
|
47186
47421
|
for (let i = anchor - 1; i >= 0; i--) {
|
|
47187
47422
|
const m = messages[i];
|
|
47188
|
-
if (m.role === "user" && !m.wake && !m.queued) {
|
|
47423
|
+
if (m.role === "user" && !m.wake && !m.queued && !m.agentInstruction) {
|
|
47189
47424
|
boundary = i;
|
|
47190
47425
|
break;
|
|
47191
47426
|
}
|
|
@@ -47391,7 +47626,7 @@ function readTranscriptFile(absPath) {
|
|
|
47391
47626
|
return [];
|
|
47392
47627
|
}
|
|
47393
47628
|
}
|
|
47394
|
-
var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
|
|
47629
|
+
var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, CLAUDE_COORDINATOR_INSTRUCTION_PREFIX, CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX, CLAUDE_PEER_INSTRUCTION_PREFIX, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
|
|
47395
47630
|
var init_transcript = __esm({
|
|
47396
47631
|
"packages/server/src/transcript.ts"() {
|
|
47397
47632
|
"use strict";
|
|
@@ -47417,6 +47652,9 @@ var init_transcript = __esm({
|
|
|
47417
47652
|
COMMAND_NAME_RE = /<command-name>([\s\S]*?)<\/command-name>/;
|
|
47418
47653
|
COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
|
|
47419
47654
|
PEER_SESSION_PREAMBLE = "Another Claude session sent a message:\n";
|
|
47655
|
+
CLAUDE_COORDINATOR_INSTRUCTION_PREFIX = "The coordinator sent a message while you were working:\n";
|
|
47656
|
+
CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX = "\n\nAddress this before completing your current task.";
|
|
47657
|
+
CLAUDE_PEER_INSTRUCTION_PREFIX = "Another Claude session sent a message while you were working:\n";
|
|
47420
47658
|
QUEUED_STALE_MS = 2 * 60 * 6e4;
|
|
47421
47659
|
EDIT_CAP = 4e3;
|
|
47422
47660
|
TRUNC_MARKER = "\n\u2026 (truncated)";
|
|
@@ -48465,6 +48703,31 @@ function setProjectIconFromFile(id, file) {
|
|
|
48465
48703
|
if (bytes.length > PROJECT_ICON_MAX_BASE64_CHARS / 4 * 3) throw new Error("That image is too large.");
|
|
48466
48704
|
return storeProjectIcon(id, file, bytes);
|
|
48467
48705
|
}
|
|
48706
|
+
function mergeSubAgentSteers(messages, steers) {
|
|
48707
|
+
if (!steers.length) return messages;
|
|
48708
|
+
const merged = [...messages];
|
|
48709
|
+
for (const steer of steers) {
|
|
48710
|
+
const at = new Date(steer.sent_at).toISOString();
|
|
48711
|
+
const duplicate = messages.some((message2) => {
|
|
48712
|
+
if (message2.role !== "user" || message2.text.trim() !== steer.message.trim() || !message2.at) return false;
|
|
48713
|
+
return Math.abs(Date.parse(message2.at) - steer.sent_at) <= 5e3;
|
|
48714
|
+
});
|
|
48715
|
+
if (duplicate) continue;
|
|
48716
|
+
const message = {
|
|
48717
|
+
sourceId: `subagent-steer:${steer.delivery_id}`,
|
|
48718
|
+
role: "user",
|
|
48719
|
+
text: steer.message,
|
|
48720
|
+
agentInstruction: true,
|
|
48721
|
+
tools: [],
|
|
48722
|
+
parts: [{ kind: "text", text: steer.message }],
|
|
48723
|
+
at
|
|
48724
|
+
};
|
|
48725
|
+
const next = merged.findIndex((candidate) => candidate.at !== void 0 && Date.parse(candidate.at) > steer.sent_at);
|
|
48726
|
+
if (next === -1) merged.push(message);
|
|
48727
|
+
else merged.splice(next, 0, message);
|
|
48728
|
+
}
|
|
48729
|
+
return merged;
|
|
48730
|
+
}
|
|
48468
48731
|
function createRouter(ctx) {
|
|
48469
48732
|
const frizzDir = join36(ctx.project.dir, ".frizz");
|
|
48470
48733
|
const openRoots = openableFileRoots(ctx.project);
|
|
@@ -48668,7 +48931,11 @@ function createRouter(ctx) {
|
|
|
48668
48931
|
sessionId: row.session_id,
|
|
48669
48932
|
cwd: ctx.project.dir,
|
|
48670
48933
|
text: `${shellStopNotice(label)} Whatever it wrote before the kill is still readable in its output file.`,
|
|
48671
|
-
|
|
48934
|
+
// `isDaemonAlive` above is a check, not a hold: the daemon can exit before this frame lands,
|
|
48935
|
+
// and followUp then COLD-RESUMES rather than failing. Take the same floor every other fork
|
|
48936
|
+
// takes, so a notice that loses the race cannot be the thing that rebuilds the worker at
|
|
48937
|
+
// Claude's `default` — see coldResumePermission.
|
|
48938
|
+
permissionMode: coldResumePermission(row, ctx.getSettings()),
|
|
48672
48939
|
model: row.model ?? void 0,
|
|
48673
48940
|
effort: row.effort ?? void 0
|
|
48674
48941
|
});
|
|
@@ -48853,7 +49120,10 @@ function createRouter(ctx) {
|
|
|
48853
49120
|
const info = ctx.tailer.subAgent(input.slug, input.id);
|
|
48854
49121
|
if (!info) return { messages: [], state: "gone", steerable: false, steerNote: null, stoppable: false, stopNote: null };
|
|
48855
49122
|
const read = info.outputFormat === "codex" ? readCodexTranscriptFile : readTranscriptFile;
|
|
48856
|
-
const messages =
|
|
49123
|
+
const messages = mergeSubAgentSteers(
|
|
49124
|
+
info.outputFile ? read(info.outputFile) : [],
|
|
49125
|
+
ctx.storage.listSubAgentSteers(input.slug, input.id)
|
|
49126
|
+
);
|
|
48857
49127
|
const steer = subAgentSteerable(input.slug, input.id);
|
|
48858
49128
|
const stop = subAgentStoppable(input.slug, input.id);
|
|
48859
49129
|
return {
|
|
@@ -48879,7 +49149,7 @@ function createRouter(ctx) {
|
|
|
48879
49149
|
// predicate). So an ungated steer is not a no-op, it is a misdelivery. `subAgentSteerable` is the
|
|
48880
49150
|
// single predicate that decides, and the drawer's prompt box is rendered off the same answer.
|
|
48881
49151
|
subAgentSteer: mutation({
|
|
48882
|
-
input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().optional() }).strict(),
|
|
49152
|
+
input: external_exports.object({ slug: ThreadSlug, id: external_exports.string(), message: external_exports.string().min(1), deliveryId: external_exports.string().min(1).max(200).optional() }).strict(),
|
|
48883
49153
|
output: external_exports.object({ delivered: external_exports.boolean() }),
|
|
48884
49154
|
handler: async ({ input }) => {
|
|
48885
49155
|
const target = subAgentSteerable(input.slug, input.id);
|
|
@@ -48888,12 +49158,21 @@ function createRouter(ctx) {
|
|
|
48888
49158
|
}
|
|
48889
49159
|
const bridge = ctx.claudeBroker;
|
|
48890
49160
|
if (!bridge) throw new Error("Claude session broker is unavailable; cannot steer this sub-agent");
|
|
49161
|
+
const deliveryId = input.deliveryId ?? randomUUID16();
|
|
49162
|
+
const sentAtMs = Date.now();
|
|
48891
49163
|
await bridge.steerSubAgent({
|
|
48892
49164
|
threadSlug: input.slug,
|
|
48893
49165
|
sessionId: target.sessionId,
|
|
48894
49166
|
subAgentId: input.id,
|
|
48895
49167
|
text: input.message,
|
|
48896
|
-
deliveryId
|
|
49168
|
+
deliveryId
|
|
49169
|
+
});
|
|
49170
|
+
ctx.storage.recordSubAgentSteer({
|
|
49171
|
+
slug: input.slug,
|
|
49172
|
+
subAgentId: input.id,
|
|
49173
|
+
deliveryId,
|
|
49174
|
+
message: input.message,
|
|
49175
|
+
sentAtMs
|
|
48897
49176
|
});
|
|
48898
49177
|
ctx.board.refresh();
|
|
48899
49178
|
return { delivered: true };
|
|
@@ -49127,7 +49406,9 @@ ${gapNote}` : input.message;
|
|
|
49127
49406
|
// Rides through to the SDK as this input's uuid, which the SDK echoes back on the record
|
|
49128
49407
|
// that delivers it — the ledger then correlates by identity rather than by text.
|
|
49129
49408
|
deliveryId: input.deliveryId,
|
|
49130
|
-
|
|
49409
|
+
// A persisted per-thread mode, or the dispatch floor for a row that has none — see
|
|
49410
|
+
// coldResumePermission for why a legacy row must not fall through to the bridge's `"default"`.
|
|
49411
|
+
permissionMode: coldResumePermission(row, ctx.getSettings()),
|
|
49131
49412
|
appendSystemPrompt,
|
|
49132
49413
|
model: row.model ?? void 0,
|
|
49133
49414
|
effort: row.effort ?? void 0,
|