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