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