frizz-server 0.13.7 → 0.13.8
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/dev-child.js +1892 -294
- package/package.json +1 -1
- package/web-dist/assets/{TerminalPane-P7iAI_rU.js → TerminalPane-CgFt4ple.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-B0JxaKjB.js → abnfDiagram-VRR7QNED-DoVWUENi.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-DJ_aT0bS.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-E8bWaWhF.js → architectureDiagram-ZJ3FMSHR-BE2vu5s4.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-BNg7FcxS.js → blockDiagram-677ZJIJ3-D6LvUZ6S.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-BHkry-JG.js → c4Diagram-LMCZKHZV-BDZel4B4.js} +1 -1
- package/web-dist/assets/channel-D3v1O4Wy.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-BJLx0sIU.js → chunk-32BRIVSS-fmjhgloi.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DmMATfuh.js → chunk-52WLFC77-sfh419xE.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-Ceg42lXA.js → chunk-C7G6YPKG-D-LGx-oa.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-CAx16zp2.js → chunk-EX3LRPZG-DriDj_NL.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-DWb22U8j.js → chunk-FWX5IMBZ-DetgkllZ.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-bKZVUQyT.js → chunk-HOUHSVGY-CDGGE0PL.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-C3i_eW5g.js → chunk-ICXQ74PX-CIgu37mb.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-Bt6x6J8D.js → chunk-MOJQB5TN-BNj77oeQ.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-BwJ73hmt.js → chunk-OGEWGWER-BRoVc26C.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-DfcEHpjL.js → chunk-PUDLZKDR-B9bHTS14.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-VYeS43tP.js → chunk-Q4XR5HBZ-CjqwAtw6.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-Cs1_YKkF.js → chunk-V7JOEXUC-ClYL5Jo9.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-CCOn0B9D.js → chunk-VAUOI2AC-DsyULg7-.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-CL_PLrUe.js → chunk-VR4S4FIN-x_8ywB7J.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-Dupt_rk_.js → chunk-WYO6CB5R-BDWUsOuN.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-UwgE4khW.js → chunk-ZGVPDNZ5-wAbPuGaE.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-LvSRVuLl.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-LvSRVuLl.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-DGZJYJNa.js → cynefin-VYW2F7L2-DLLNSytS.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B34DgCmC.js → cynefinDiagram-TSTJHNR4-6kBJrScm.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-C7HODJG3.js → dagre-VKFMJZFB-Z_OVqFBK.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-CunP-lqf.js → diagram-FQU43EPY-C_nG5FUG.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-C3O84bOk.js → diagram-G47NLZAW-CYQTveO9.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-Dv0TR4Ic.js → diagram-NH7WQ7WH-C2DkBAm5.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-CUvcZIxp.js → diagram-OA4YK3LP-CSDwzhMR.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-CwPr97NN.js → diagram-WEI45ONY-R1MvefNj.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Dq2O1mOC.js → ebnfDiagram-CCIWWBDH-DBXUjfz2.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-C6dJ8Dpx.js → erDiagram-Q63AITRT-D2XH_vxG.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DQtNFbyn.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-DPAGYT4s.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-sa-TSsB5.js → ganttDiagram-NO4QXBWP-BqJqGmJu.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-BdZIuH-o.js → gitGraph-TEB2WS4Q-BpED4Y4w.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-C__OynR9.js → gitGraphDiagram-IHSO6WYX-CpCzUYgA.js} +1 -1
- package/web-dist/assets/index-DfJaB5hp.css +1 -0
- package/web-dist/assets/index-vTgjYcOv.js +319 -0
- package/web-dist/assets/{info-DKCQHKI2-BEaDQqRu.js → info-DKCQHKI2-Bm1fWKNN.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-RJlw-Bzq.js → infoDiagram-FWYZ7A6U-C61BGKNf.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-xop_QrWs.js → ishikawaDiagram-FXEZZL3T-0Fmof1Bc.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-O6nmpoje.js → journeyDiagram-5HDEW3XC-DHuJoS_U.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-IXwPs_L0.js → kanban-definition-HUTT4EX6-ClLsOpPq.js} +1 -1
- package/web-dist/assets/{line-BMRguCzE.js → line-BQNlaa-L.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-BCXFwFN1.js → mermaid-parser.core-Dhg0FCF0.js} +3 -3
- package/web-dist/assets/{mermaid.core-Cqq_9ya3.js → mermaid.core-OSL7u28D.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DEfWfnOg.js → mindmap-definition-LN4V7U3C-CMh2RRdG.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-CQGAnngT.js → packet-7NZHBO7P-DqizytaZ.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-tOrSsHlA.js → pegDiagram-2B236MQR-C_xsQ7ey.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-C0KP3XF8.js → pie-RZYD4A2V-Dg0NeJ6P.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-FzmIWC1K.js → pieDiagram-ENE6RG2P-B2przwvg.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-N6zuxKqV.js → quadrantDiagram-ABIIQ3AL-B5xLDvCk.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CB3WQRMp.js → radar-I7S5WNFK-DItULivd.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-Ck8x0o2o.js → railroad-3IZDKUUU-BVSgw6fD.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-Du1T7UC_.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-Ck5QAc8a.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-CA1OLi4_.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-6TyF6de4.js → railroadDiagram-RFXS5EU6-DQh-iy6H.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-D6n7xpc4.js → requirementDiagram-TGXJPOKE-B2bjpE-s.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-B7R6Natf.js → sankeyDiagram-HTMAVEWB-C-vr6CRd.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CFbbuWF6.js → sequenceDiagram-DBY2YBRQ-C48rDu8k.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DwXu0kos.js → stateDiagram-2N3HPSRC-BEHBNPlt.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BiZG09Uh.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-Clie2Qk3.js → swimlanes-5IMT3BWC-BK0pUXUO.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-CenRab4t.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-DWDsPzuQ.js → timeline-definition-FHXFAJF6-CQ3rH4CA.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-Ddd-Wxpb.js → treeView-QDETBFTQ-DrgwDaib.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-CuZ3jD4K.js → treemap-6X3UGDF4-CGlOb_mN.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-BoE4aTMb.js → vennDiagram-L72KCM5P-BB9TjfQX.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-CfaztQL0.js → wardley-OPB4EBWU-C8EK8ow0.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-pxcV16qp.js → wardleyDiagram-EHGQE667-CUeOWZBB.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG--d5N7oDL.js → xychartDiagram-FW5EYKEG-8ZymnyMt.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-ZNmKXuTL.js +0 -1
- package/web-dist/assets/channel-CTEEc7oT.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-D5na7mob.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-D5na7mob.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-B8cYCwrU.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-C-EWCSnf.js +0 -1
- package/web-dist/assets/index-CGrszN1x.js +0 -319
- package/web-dist/assets/index-d_g5kIKJ.css +0 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-QzrMOCpt.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-CwEnUu4A.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-BoDEbNAA.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CQctakxF.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-CGOfs7yz.js +0 -8
package/dist/dev-child.js
CHANGED
|
@@ -12362,7 +12362,7 @@ var init_interactions = __esm({
|
|
|
12362
12362
|
InteractionFieldId = external_exports.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_.:-]*$/).refine((value) => !["__proto__", "constructor", "prototype"].includes(value), "reserved field id");
|
|
12363
12363
|
InteractionDecisionId = external_exports.string().min(1).max(80).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
12364
12364
|
InteractionProvider = external_exports.object({
|
|
12365
|
-
kind: external_exports.enum(["claude", "codex", "frizz"]),
|
|
12365
|
+
kind: external_exports.enum(["claude", "codex", "acp", "frizz"]),
|
|
12366
12366
|
name: InteractionLabel.optional(),
|
|
12367
12367
|
version: InteractionOpaqueId.optional()
|
|
12368
12368
|
}).strict();
|
|
@@ -13200,6 +13200,20 @@ function attachmentExtension(name) {
|
|
|
13200
13200
|
function isAllowedAttachmentName(name) {
|
|
13201
13201
|
return ATTACHMENT_EXT_SET.has(attachmentExtension(name));
|
|
13202
13202
|
}
|
|
13203
|
+
function acpSlugParts(model) {
|
|
13204
|
+
if (typeof model !== "string" || !model.startsWith(ACP_MODEL_PREFIX)) return void 0;
|
|
13205
|
+
const rest = model.slice(ACP_MODEL_PREFIX.length);
|
|
13206
|
+
const at = rest.indexOf("@");
|
|
13207
|
+
const agent = at === -1 ? rest : rest.slice(0, at);
|
|
13208
|
+
const id = at === -1 ? "" : rest.slice(at + 1);
|
|
13209
|
+
return agent ? { agent, ...id ? { model: id } : {} } : void 0;
|
|
13210
|
+
}
|
|
13211
|
+
function acpAgentIdFromModel(model) {
|
|
13212
|
+
return acpSlugParts(model)?.agent;
|
|
13213
|
+
}
|
|
13214
|
+
function acpModelIdFromModel(model) {
|
|
13215
|
+
return acpSlugParts(model)?.model;
|
|
13216
|
+
}
|
|
13203
13217
|
function isDirectSubAgent(agent) {
|
|
13204
13218
|
return (agent.depth ?? 1) === 1;
|
|
13205
13219
|
}
|
|
@@ -13268,9 +13282,9 @@ function trimAwaitingTitle(raw2) {
|
|
|
13268
13282
|
}
|
|
13269
13283
|
function splitAwaitingFrontmatter(raw2) {
|
|
13270
13284
|
const lines = raw2.split("\n").map((l) => l.replace(/\r$/, ""));
|
|
13271
|
-
const
|
|
13272
|
-
const frontmatter =
|
|
13273
|
-
const after =
|
|
13285
|
+
const delimiter3 = lines.findIndex((l) => /^\s*---+\s*$/.test(l));
|
|
13286
|
+
const frontmatter = delimiter3 === -1 ? lines : lines.slice(0, delimiter3);
|
|
13287
|
+
const after = delimiter3 === -1 ? [] : lines.slice(delimiter3 + 1);
|
|
13274
13288
|
const rest = [];
|
|
13275
13289
|
const yamlLines = [];
|
|
13276
13290
|
const titleLines = [];
|
|
@@ -13554,6 +13568,11 @@ function questionsCancelledWakeMessage(count) {
|
|
|
13554
13568
|
function queuedThread(t) {
|
|
13555
13569
|
return t.kind === "session" && t.foreign !== true && t.needsYou === true && t.state !== "archived";
|
|
13556
13570
|
}
|
|
13571
|
+
function requireEffortOutsideAcp(profile, ctx) {
|
|
13572
|
+
if (profile.backend !== "acp" && profile.effort === void 0) {
|
|
13573
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["effort"], message: `effort is required for a ${profile.backend} profile` });
|
|
13574
|
+
}
|
|
13575
|
+
}
|
|
13557
13576
|
function formatElapsed(ms) {
|
|
13558
13577
|
if (!Number.isFinite(ms) || ms < 0) return "an unknown time";
|
|
13559
13578
|
const s = Math.floor(ms / 1e3);
|
|
@@ -13704,7 +13723,7 @@ function parseGithubWakeSteer(text2) {
|
|
|
13704
13723
|
function frizzRoute(path3) {
|
|
13705
13724
|
return `${FRIZZ_ROUTE_PREFIX}${path3.startsWith("/") ? path3 : `/${path3}`}`;
|
|
13706
13725
|
}
|
|
13707
|
-
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, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, 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, QUESTION_FENCE_RETIRED_AT, 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, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, 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;
|
|
13726
|
+
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, AcpAgent, ACP_MODEL_PREFIX, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, 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, ISSUE_WATCH_SPENT_TRAILER, ISSUE_WATCH_ARMED_TRAILER, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, PARK_CORRECTION_NAMES_LEAD, PARK_CORRECTION_RETIRED_LEAD, PARK_CORRECTION_QUESTION_LEAD, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, 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, QUESTION_FENCE_RETIRED_AT, 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, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, AccountLoginStartInput, AccountLoginStartResult, AccountLoginStatusInput, AccountLoginStatusResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, 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;
|
|
13708
13727
|
var init_src = __esm({
|
|
13709
13728
|
"packages/shared/src/index.ts"() {
|
|
13710
13729
|
"use strict";
|
|
@@ -13823,7 +13842,7 @@ var init_src = __esm({
|
|
|
13823
13842
|
"exited"
|
|
13824
13843
|
// the worker process that owned this session is gone, or is no longer driving its turn
|
|
13825
13844
|
]);
|
|
13826
|
-
Backend = external_exports.enum(["claude", "codex"]);
|
|
13845
|
+
Backend = external_exports.enum(["claude", "codex", "acp"]);
|
|
13827
13846
|
CodexModel = external_exports.object({
|
|
13828
13847
|
slug: external_exports.string(),
|
|
13829
13848
|
displayName: external_exports.string(),
|
|
@@ -13837,6 +13856,24 @@ var init_src = __esm({
|
|
|
13837
13856
|
contextWindow: external_exports.number().int().positive().optional(),
|
|
13838
13857
|
maxContextWindow: external_exports.number().int().positive().optional()
|
|
13839
13858
|
});
|
|
13859
|
+
AcpAgent = external_exports.object({
|
|
13860
|
+
id: external_exports.string(),
|
|
13861
|
+
label: external_exports.string(),
|
|
13862
|
+
command: external_exports.string(),
|
|
13863
|
+
available: external_exports.boolean()
|
|
13864
|
+
});
|
|
13865
|
+
ACP_MODEL_PREFIX = "acp:";
|
|
13866
|
+
AcpAgentModel = external_exports.object({ id: external_exports.string(), name: external_exports.string() });
|
|
13867
|
+
AcpAgentModels = external_exports.object({
|
|
13868
|
+
agentId: external_exports.string(),
|
|
13869
|
+
models: external_exports.array(AcpAgentModel),
|
|
13870
|
+
/** The model the agent opens a session on when Frizz asks for none. */
|
|
13871
|
+
current: external_exports.string().optional(),
|
|
13872
|
+
/** Why the list is empty, when the probe could not open a session (not installed, not logged in…). */
|
|
13873
|
+
error: external_exports.string().optional(),
|
|
13874
|
+
probedAt: external_exports.string()
|
|
13875
|
+
});
|
|
13876
|
+
AcpAgentModelsInput = external_exports.object({ agentId: external_exports.string().min(1).max(100), refresh: external_exports.boolean().optional() }).strict();
|
|
13840
13877
|
ThreadProfileOption = external_exports.object({
|
|
13841
13878
|
model: external_exports.string().min(1),
|
|
13842
13879
|
label: external_exports.string().min(1),
|
|
@@ -14848,14 +14885,15 @@ var init_src = __esm({
|
|
|
14848
14885
|
codex: ProviderAuth,
|
|
14849
14886
|
emails: AccountEmails
|
|
14850
14887
|
});
|
|
14851
|
-
|
|
14888
|
+
AccountBackend = external_exports.enum(["claude", "codex"]);
|
|
14889
|
+
AccountLogoutInput = external_exports.object({ backend: AccountBackend }).strict();
|
|
14852
14890
|
AccountLogoutResult = external_exports.object({
|
|
14853
14891
|
status: external_exports.enum(["done", "blocked", "failed"]),
|
|
14854
14892
|
auth: ProviderAuth,
|
|
14855
14893
|
activeThreads: external_exports.number().int().positive().optional(),
|
|
14856
14894
|
detail: external_exports.string().max(200).optional()
|
|
14857
14895
|
});
|
|
14858
|
-
AccountLoginStartInput = external_exports.object({ backend:
|
|
14896
|
+
AccountLoginStartInput = external_exports.object({ backend: AccountBackend }).strict();
|
|
14859
14897
|
AccountLoginStartResult = external_exports.object({ attemptId: ThreadSlug });
|
|
14860
14898
|
AccountLoginStatusInput = external_exports.object({ attemptId: ThreadSlug }).strict();
|
|
14861
14899
|
AccountLoginStatusResult = external_exports.object({
|
|
@@ -14930,6 +14968,15 @@ var init_src = __esm({
|
|
|
14930
14968
|
// it (codex compacts at 90% of the resolved window). Unset ⇒ nothing is sent and the model's stock
|
|
14931
14969
|
// window applies. Optional so an old blob parses; defaultSettings leaves it unset.
|
|
14932
14970
|
codexContextWindow: external_exports.number().int().positive().optional(),
|
|
14971
|
+
// The operator's OWN Agent Client Protocol agents, merged over the built-in catalogue by id
|
|
14972
|
+
// (server/backend/acp-agents.ts). `command` is an executable name on PATH or an absolute path;
|
|
14973
|
+
// `args` is what puts it into ACP mode (`["acp"]`, `["--acp"]`). Optional so an old blob parses.
|
|
14974
|
+
acpAgents: external_exports.array(external_exports.object({
|
|
14975
|
+
id: external_exports.string().trim().min(1).max(64).regex(/^[a-z0-9][a-z0-9._-]*$/i),
|
|
14976
|
+
label: external_exports.string().trim().min(1).max(80),
|
|
14977
|
+
command: external_exports.string().trim().min(1).max(1024),
|
|
14978
|
+
args: external_exports.array(external_exports.string().max(1024)).max(32).optional()
|
|
14979
|
+
})).max(32).optional(),
|
|
14933
14980
|
// The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
|
|
14934
14981
|
// unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
|
|
14935
14982
|
// the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
|
|
@@ -14956,28 +15003,38 @@ var init_src = __esm({
|
|
|
14956
15003
|
DispatchPreferences = external_exports.object({
|
|
14957
15004
|
backend: Backend,
|
|
14958
15005
|
claude: DispatchProviderPreferences,
|
|
14959
|
-
codex: DispatchProviderPreferences
|
|
15006
|
+
codex: DispatchProviderPreferences,
|
|
15007
|
+
// Optional because every record written before the ACP backend existed lacks it, and a required
|
|
15008
|
+
// key would fail those records' parse and silently reset the operator's saved profile.
|
|
15009
|
+
acp: DispatchProviderPreferences.optional()
|
|
14960
15010
|
});
|
|
14961
|
-
|
|
15011
|
+
DispatchProfileSnapshotShape = external_exports.object({
|
|
14962
15012
|
backend: Backend,
|
|
14963
15013
|
model: external_exports.string().trim().min(1).max(200),
|
|
14964
|
-
|
|
15014
|
+
// Required for Claude and Codex (the refinement below); absent for an ACP agent, which runs with
|
|
15015
|
+
// whatever model and effort its own CLI is configured for — Frizz has no effort axis to offer there.
|
|
15016
|
+
effort: Settings.shape.effort,
|
|
14965
15017
|
// IGNORED: dispatch permission is decided server-side (workerDispatchPermission) from the
|
|
14966
15018
|
// non-interactive floor plus the operator's Settings choice, never per dispatch. Optional so old
|
|
14967
15019
|
// clients that still send it parse.
|
|
14968
15020
|
permissionMode: PermissionMode.optional()
|
|
14969
15021
|
}).strict();
|
|
15022
|
+
DispatchProfileSnapshot = DispatchProfileSnapshotShape.superRefine(requireEffortOutsideAcp);
|
|
14970
15023
|
SetDispatchPreferenceInput = external_exports.discriminatedUnion("field", [
|
|
14971
15024
|
external_exports.object({ field: external_exports.literal("backend"), value: Backend }),
|
|
14972
15025
|
external_exports.object({
|
|
14973
15026
|
field: external_exports.literal("profile"),
|
|
14974
15027
|
backend: Backend,
|
|
14975
15028
|
model: external_exports.string().trim().min(1).max(200),
|
|
14976
|
-
|
|
15029
|
+
// Optional ONLY for an ACP profile (see DispatchProfileSnapshot); a Claude/Codex profile without
|
|
15030
|
+
// an effort is refused below rather than silently stored as "default".
|
|
15031
|
+
effort: Settings.shape.effort
|
|
14977
15032
|
}),
|
|
14978
15033
|
external_exports.object({ field: external_exports.literal("model"), backend: Backend, value: external_exports.string().trim().min(1).max(200) }),
|
|
14979
15034
|
external_exports.object({ field: external_exports.literal("effort"), backend: Backend, value: Settings.shape.effort.unwrap() })
|
|
14980
|
-
])
|
|
15035
|
+
]).superRefine((update, ctx) => {
|
|
15036
|
+
if (update.field === "profile") requireEffortOutsideAcp(update, ctx);
|
|
15037
|
+
});
|
|
14981
15038
|
DispatchInput = external_exports.object({
|
|
14982
15039
|
// Optional: when omitted, dispatch derives a fallback title from the prompt (Claude later renames
|
|
14983
15040
|
// the session via ai-title, which the UI prefers for display). The thread FILE always gets a
|
|
@@ -15379,9 +15436,9 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15379
15436
|
page: external_exports.number().int().positive(),
|
|
15380
15437
|
pageCount: external_exports.number().int().positive()
|
|
15381
15438
|
});
|
|
15382
|
-
GithubBatchInput =
|
|
15439
|
+
GithubBatchInput = DispatchProfileSnapshotShape.extend({
|
|
15383
15440
|
items: external_exports.array(external_exports.object({ kind: external_exports.enum(["issue", "pr"]), number: external_exports.number().int().positive() })).min(1)
|
|
15384
|
-
}).strict();
|
|
15441
|
+
}).strict().superRefine(requireEffortOutsideAcp);
|
|
15385
15442
|
GithubBatchResult = external_exports.object({
|
|
15386
15443
|
dispatched: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), slug: ThreadSlug })),
|
|
15387
15444
|
failed: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), error: external_exports.string() }))
|
|
@@ -19055,7 +19112,7 @@ var init_project_scope = __esm({
|
|
|
19055
19112
|
|
|
19056
19113
|
// packages/server/src/storage.ts
|
|
19057
19114
|
function isHeadlessRow(row) {
|
|
19058
|
-
return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker";
|
|
19115
|
+
return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker" || row.backend === "acp";
|
|
19059
19116
|
}
|
|
19060
19117
|
function isBrokerClaudeRow(row) {
|
|
19061
19118
|
return row.backend === "claude" && row.claude_runtime === "broker";
|
|
@@ -19078,7 +19135,7 @@ function groupBySlug(rows) {
|
|
|
19078
19135
|
}
|
|
19079
19136
|
function ensureStorageSchema(db) {
|
|
19080
19137
|
db.exec(STORAGE_SCHEMA);
|
|
19081
|
-
for (const column of ["pinned_at TEXT"]) {
|
|
19138
|
+
for (const column of ["pinned_at TEXT", "acp_agent TEXT"]) {
|
|
19082
19139
|
try {
|
|
19083
19140
|
db.exec(`ALTER TABLE session ADD COLUMN ${column}`);
|
|
19084
19141
|
} catch {
|
|
@@ -19673,6 +19730,7 @@ function createStorage(source, projectId) {
|
|
|
19673
19730
|
const agentSessionStmt = scope.prepare("UPDATE session SET agent_session_id = ? WHERE project_id = @project_id AND slug = ?");
|
|
19674
19731
|
const codexRuntimeStmt = scope.prepare("UPDATE session SET codex_runtime = ? WHERE project_id = @project_id AND slug = ?");
|
|
19675
19732
|
const claudeRuntimeStmt = scope.prepare("UPDATE session SET claude_runtime = ? WHERE project_id = @project_id AND slug = ?");
|
|
19733
|
+
const acpAgentStmt = scope.prepare("UPDATE session SET acp_agent = ? WHERE project_id = @project_id AND slug = ?");
|
|
19676
19734
|
const profileStmt = scope.prepare("UPDATE session SET model = ?, effort = ?, profile_set_at = ? WHERE project_id = @project_id AND slug = ?");
|
|
19677
19735
|
const permissionModeStmt = scope.prepare("UPDATE session SET permission_mode = ?, permission_set_at = ? WHERE project_id = @project_id AND slug = ?");
|
|
19678
19736
|
const permissionPendingStmt = scope.prepare("UPDATE session SET permission_pending = ? WHERE project_id = @project_id AND slug = ?");
|
|
@@ -19802,7 +19860,8 @@ function createStorage(source, projectId) {
|
|
|
19802
19860
|
runtime_control: row.runtime_control ?? null,
|
|
19803
19861
|
runtime_control_revision: row.runtime_control_revision ?? 0,
|
|
19804
19862
|
codex_runtime: row.codex_runtime ?? null,
|
|
19805
|
-
claude_runtime: row.claude_runtime ?? null
|
|
19863
|
+
claude_runtime: row.claude_runtime ?? null,
|
|
19864
|
+
acp_agent: row.acp_agent ?? null
|
|
19806
19865
|
});
|
|
19807
19866
|
const getAdoptionRuntimeSnapshot = db.transaction((slug) => ({
|
|
19808
19867
|
// Claim first is intentional: a finalized claim disappearing before the current-row validation
|
|
@@ -19834,7 +19893,7 @@ function createStorage(source, projectId) {
|
|
|
19834
19893
|
const retireAdoptionAttempt = (claim, retiredAtMs = Date.now()) => {
|
|
19835
19894
|
putRetiredAdoptionAttempt.run(claim.attempt_token, claim.slug, claim.session_id, retiredAtMs);
|
|
19836
19895
|
};
|
|
19837
|
-
const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs,
|
|
19896
|
+
const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs, spawn8) => {
|
|
19838
19897
|
ThreadSlug.parse(slug);
|
|
19839
19898
|
if (!Number.isSafeInteger(leaseExpiresAtMs)) return { acquired: false };
|
|
19840
19899
|
db.exec("BEGIN IMMEDIATE");
|
|
@@ -19866,7 +19925,7 @@ function createStorage(source, projectId) {
|
|
|
19866
19925
|
bound = true;
|
|
19867
19926
|
return true;
|
|
19868
19927
|
};
|
|
19869
|
-
const value =
|
|
19928
|
+
const value = spawn8(bindPane);
|
|
19870
19929
|
if (!bound) {
|
|
19871
19930
|
if (db.inTransaction) db.exec("ROLLBACK");
|
|
19872
19931
|
throw new Error("adoption spawn returned without binding its exact pane");
|
|
@@ -20185,6 +20244,7 @@ function createStorage(source, projectId) {
|
|
|
20185
20244
|
setAgentSession: (slug, agentSessionId) => void agentSessionStmt.run(agentSessionId, slug),
|
|
20186
20245
|
setCodexRuntime: (slug, runtime) => void codexRuntimeStmt.run(runtime, slug),
|
|
20187
20246
|
setClaudeRuntime: (slug, runtime) => void claudeRuntimeStmt.run(runtime, slug),
|
|
20247
|
+
setAcpAgent: (slug, agentId) => void acpAgentStmt.run(agentId, slug),
|
|
20188
20248
|
setProfile: (slug, model, effort) => void profileStmt.run(model, effort, (/* @__PURE__ */ new Date()).toISOString(), slug),
|
|
20189
20249
|
setPermissionMode: (slug, permissionMode) => void permissionModeStmt.run(permissionMode, (/* @__PURE__ */ new Date()).toISOString(), slug),
|
|
20190
20250
|
setPermissionPending: (slug, permissionMode) => void permissionPendingStmt.run(permissionMode, slug),
|
|
@@ -20413,6 +20473,8 @@ var init_storage = __esm({
|
|
|
20413
20473
|
-- Claude transport discriminator: 'broker' = a session-broker-owned Agent SDK session; NULL/'tmux'
|
|
20414
20474
|
-- is the pre-broker legacy value, same story.
|
|
20415
20475
|
claude_runtime TEXT,
|
|
20476
|
+
-- ACP agent id (backend/acp-agents.ts) for backend='acp' rows; also in the ALTER list below.
|
|
20477
|
+
acp_agent TEXT,
|
|
20416
20478
|
-- THE RECURRING PROMPT (scheduler.ts SOURCES 4, 5 and 7): one text, three independent triggers \u2014
|
|
20417
20479
|
-- every time the thread rests, every N ms on a clock, and/or every time its context is compacted.
|
|
20418
20480
|
-- All flags 0 = off; there is no separate enable column, because another flag could only ever
|
|
@@ -20865,9 +20927,16 @@ function defaultDispatchPreferences(settings, codexModels = []) {
|
|
|
20865
20927
|
return {
|
|
20866
20928
|
backend,
|
|
20867
20929
|
claude: backend === "claude" ? selected : { permissionMode: "auto" },
|
|
20868
|
-
codex: backend === "codex" ? selected : { permissionMode: "default" }
|
|
20930
|
+
codex: backend === "codex" ? selected : { permissionMode: "default" },
|
|
20931
|
+
// The ACP slot exists only once the operator has chosen an agent: it has no permission axis (the
|
|
20932
|
+
// agent's own CLI decides), so there is no default worth writing, and every record written before
|
|
20933
|
+
// the backend existed lacks the key.
|
|
20934
|
+
...backend === "acp" ? { acp: selected } : {}
|
|
20869
20935
|
};
|
|
20870
20936
|
}
|
|
20937
|
+
function profileOf(prefs, backend) {
|
|
20938
|
+
return prefs[backend] ?? {};
|
|
20939
|
+
}
|
|
20871
20940
|
function getDispatchPreferences(storage, settings, home, codexModels = []) {
|
|
20872
20941
|
const machine = readMachineDispatchPreferences(home);
|
|
20873
20942
|
if (machine) return machine;
|
|
@@ -20884,13 +20953,13 @@ function setDispatchPreference(storage, settings, home, update, codexModels = []
|
|
|
20884
20953
|
...current,
|
|
20885
20954
|
backend: update.backend,
|
|
20886
20955
|
[update.backend]: {
|
|
20887
|
-
...current
|
|
20956
|
+
...profileOf(current, update.backend),
|
|
20888
20957
|
model: update.model,
|
|
20889
20958
|
effort: update.effort
|
|
20890
20959
|
}
|
|
20891
20960
|
};
|
|
20892
20961
|
} else {
|
|
20893
|
-
const profile = current
|
|
20962
|
+
const profile = profileOf(current, update.backend);
|
|
20894
20963
|
next = {
|
|
20895
20964
|
...current,
|
|
20896
20965
|
...update.field === "model" ? { backend: update.backend } : {},
|
|
@@ -20928,8 +20997,8 @@ function userInfoParts(raw2) {
|
|
|
20928
20997
|
const encoded = encodedMatch?.index ?? -1;
|
|
20929
20998
|
const index = literal === -1 ? encoded : encoded === -1 ? literal : Math.min(literal, encoded);
|
|
20930
20999
|
if (index <= 0) return null;
|
|
20931
|
-
const
|
|
20932
|
-
return { username: value.slice(0, index), delimiter:
|
|
21000
|
+
const delimiter3 = value.slice(index, index + (index === encoded ? 3 : 1));
|
|
21001
|
+
return { username: value.slice(0, index), delimiter: delimiter3, password: value.slice(index + delimiter3.length) };
|
|
20933
21002
|
}
|
|
20934
21003
|
function redactUserInfoToken(raw2, options) {
|
|
20935
21004
|
const parts = userInfoParts(raw2);
|
|
@@ -20952,11 +21021,11 @@ function redactCredentialSyntax(raw2, options = {}) {
|
|
|
20952
21021
|
let value = raw2;
|
|
20953
21022
|
value = value.replace(
|
|
20954
21023
|
URL_USERINFO_RE,
|
|
20955
|
-
(_whole, scheme, username,
|
|
21024
|
+
(_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
|
|
20956
21025
|
);
|
|
20957
21026
|
value = value.replace(
|
|
20958
21027
|
URL_ENCODED_USERINFO_RE,
|
|
20959
|
-
(_whole, scheme, username,
|
|
21028
|
+
(_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
|
|
20960
21029
|
);
|
|
20961
21030
|
value = value.replace(
|
|
20962
21031
|
USER_FLAG_RE,
|
|
@@ -21605,6 +21674,7 @@ function threadProfileOptions(backend) {
|
|
|
21605
21674
|
}))
|
|
21606
21675
|
};
|
|
21607
21676
|
}
|
|
21677
|
+
if (backend === "acp") return { backend, options: [] };
|
|
21608
21678
|
throw new Error("This thread has an unknown backend; its runtime profile cannot be changed");
|
|
21609
21679
|
}
|
|
21610
21680
|
function validateThreadProfile(backend, model, effort) {
|
|
@@ -24897,7 +24967,7 @@ ${ask}`;
|
|
|
24897
24967
|
void nowMs;
|
|
24898
24968
|
void backend;
|
|
24899
24969
|
if (state.foreign) return { permPrompt: false };
|
|
24900
|
-
if (turn === "in-flight" && row.backend !== "codex" && permMarkerBlocks(state, row)) {
|
|
24970
|
+
if (turn === "in-flight" && row.backend !== "codex" && row.backend !== "acp" && permMarkerBlocks(state, row)) {
|
|
24901
24971
|
return { permPrompt: true };
|
|
24902
24972
|
}
|
|
24903
24973
|
return { permPrompt: false };
|
|
@@ -25221,7 +25291,7 @@ ${ask}`;
|
|
|
25221
25291
|
if (state.stallLogged) return;
|
|
25222
25292
|
if (row.exited && rowIsArchived(row)) return;
|
|
25223
25293
|
state.stallLogged = true;
|
|
25224
|
-
const detail2 = isBrokerClaudeRow(row) && deps.project.stateDir ? `no worker terminal to capture (broker runtime). Daemon diagnostics: ${claudeBrokerDiagnosticLogPath(deps.project.stateDir, row.session_id)}` : isHeadlessRow(row) ? `no worker terminal to capture (headless ${row.backend === "codex" ? "codex app-server" : "claude broker"} runtime)` : "no worker output captured (this build captures no terminal)";
|
|
25294
|
+
const detail2 = isBrokerClaudeRow(row) && deps.project.stateDir ? `no worker terminal to capture (broker runtime). Daemon diagnostics: ${claudeBrokerDiagnosticLogPath(deps.project.stateDir, row.session_id)}` : isHeadlessRow(row) ? `no worker terminal to capture (headless ${row.backend === "codex" ? "codex app-server" : row.backend === "acp" ? "ACP agent" : "claude broker"} runtime)` : "no worker output captured (this build captures no terminal)";
|
|
25225
25295
|
log.error(
|
|
25226
25296
|
"tailer",
|
|
25227
25297
|
`thread ${row.slug} (session ${row.session_id}): no transcript ${DISCOVERY_GRACE_MS / 1e3}s after dispatch \u2014 likely a boot failure. ${detail2.slice(0, 4e3)}`
|
|
@@ -25367,7 +25437,7 @@ ${detail2}
|
|
|
25367
25437
|
if (primeProgress && primed % PRIME_PROGRESS_EVERY === 0) primeProgress(primed, rows.length);
|
|
25368
25438
|
primed++;
|
|
25369
25439
|
const backend = resolveBackend(row.backend);
|
|
25370
|
-
const nativeId = row.agent_session_id ?? row.transcript_id ?? row.session_id;
|
|
25440
|
+
const nativeId = row.backend === "acp" ? row.session_id : row.agent_session_id ?? row.transcript_id ?? row.session_id;
|
|
25371
25441
|
const known = states.get(row.slug);
|
|
25372
25442
|
const runtimeGeneration = row.runtime_generation ?? 0;
|
|
25373
25443
|
const keepable = known !== void 0 && known.sessionId === row.session_id && known.nativeSessionId === nativeId && known.runtimeGeneration === runtimeGeneration;
|
|
@@ -25388,7 +25458,7 @@ ${detail2}
|
|
|
25388
25458
|
states.set(row.slug, state);
|
|
25389
25459
|
}
|
|
25390
25460
|
const prevNoTranscript = state.noTranscript;
|
|
25391
|
-
if (row.backend !== "codex" && !resolveTranscript(state, row, nowMs)) continue;
|
|
25461
|
+
if (row.backend !== "codex" && row.backend !== "acp" && !resolveTranscript(state, row, nowMs)) continue;
|
|
25392
25462
|
if (!state.primed) {
|
|
25393
25463
|
if (primeDeferred(row)) {
|
|
25394
25464
|
primeIncomplete = true;
|
|
@@ -26546,19 +26616,19 @@ function searchPath2(env) {
|
|
|
26546
26616
|
function vendoredNativeBeside(launcherJs, arch, access) {
|
|
26547
26617
|
const triple = WINDOWS_TRIPLE[arch];
|
|
26548
26618
|
if (!triple) return void 0;
|
|
26549
|
-
const { dirname:
|
|
26550
|
-
const pkgRoot =
|
|
26619
|
+
const { dirname: dirname26, join: join49 } = win322;
|
|
26620
|
+
const pkgRoot = dirname26(dirname26(launcherJs));
|
|
26551
26621
|
const platformPkg = `codex-win32-${arch}`;
|
|
26552
26622
|
const vendorRoots = [
|
|
26553
|
-
|
|
26623
|
+
join49(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
|
|
26554
26624
|
// nested under the package
|
|
26555
|
-
|
|
26625
|
+
join49(dirname26(pkgRoot), platformPkg, "vendor"),
|
|
26556
26626
|
// hoisted beside it: <prefix>\node_modules\@openai\<pkg>
|
|
26557
|
-
|
|
26627
|
+
join49(pkgRoot, "vendor")
|
|
26558
26628
|
// the pre-platform-package layout, binaries inside @openai/codex itself
|
|
26559
26629
|
];
|
|
26560
26630
|
for (const root of vendorRoots) {
|
|
26561
|
-
const exe =
|
|
26631
|
+
const exe = join49(root, triple, "bin", "codex.exe");
|
|
26562
26632
|
if (access(exe, "exists")) return exe;
|
|
26563
26633
|
}
|
|
26564
26634
|
return void 0;
|
|
@@ -31659,6 +31729,62 @@ var init_auth_status = __esm({
|
|
|
31659
31729
|
}
|
|
31660
31730
|
});
|
|
31661
31731
|
|
|
31732
|
+
// packages/server/src/backend/codex-execution-health.ts
|
|
31733
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
31734
|
+
function probeHostMacOSServices() {
|
|
31735
|
+
return new Promise((resolve12) => {
|
|
31736
|
+
execFile5(MACOS_SERVICE_PROBE[0], MACOS_SERVICE_PROBE.slice(1), {
|
|
31737
|
+
timeout: MACOS_SERVICE_PROBE_TIMEOUT_MS,
|
|
31738
|
+
killSignal: "SIGKILL",
|
|
31739
|
+
maxBuffer: 64 * 1024
|
|
31740
|
+
}, (error) => resolve12(!error));
|
|
31741
|
+
});
|
|
31742
|
+
}
|
|
31743
|
+
var MACOS_SERVICE_PROBE, MACOS_SERVICE_PROBE_TIMEOUT_MS, CodexExecutionHealth;
|
|
31744
|
+
var init_codex_execution_health = __esm({
|
|
31745
|
+
"packages/server/src/backend/codex-execution-health.ts"() {
|
|
31746
|
+
"use strict";
|
|
31747
|
+
MACOS_SERVICE_PROBE = ["/bin/launchctl", "print", "system/com.apple.configd"];
|
|
31748
|
+
MACOS_SERVICE_PROBE_TIMEOUT_MS = 3e3;
|
|
31749
|
+
CodexExecutionHealth = class {
|
|
31750
|
+
constructor(platform3 = process.platform, hostProbe = probeHostMacOSServices) {
|
|
31751
|
+
this.platform = platform3;
|
|
31752
|
+
this.hostProbe = hostProbe;
|
|
31753
|
+
}
|
|
31754
|
+
checking = /* @__PURE__ */ new WeakMap();
|
|
31755
|
+
check(connection, scope) {
|
|
31756
|
+
if (this.platform !== "darwin") return Promise.resolve();
|
|
31757
|
+
const pending = this.checking.get(connection);
|
|
31758
|
+
if (pending) return pending;
|
|
31759
|
+
const check = this.probe(connection, scope);
|
|
31760
|
+
this.checking.set(connection, check);
|
|
31761
|
+
void check.finally(() => this.checking.delete(connection)).catch(() => void 0);
|
|
31762
|
+
return check;
|
|
31763
|
+
}
|
|
31764
|
+
async probe(connection, scope) {
|
|
31765
|
+
let detail2;
|
|
31766
|
+
try {
|
|
31767
|
+
const raw2 = await connection.request("command/exec", {
|
|
31768
|
+
command: MACOS_SERVICE_PROBE,
|
|
31769
|
+
cwd: "/",
|
|
31770
|
+
timeoutMs: MACOS_SERVICE_PROBE_TIMEOUT_MS,
|
|
31771
|
+
sandboxPolicy: { type: "dangerFullAccess" }
|
|
31772
|
+
}, MACOS_SERVICE_PROBE_TIMEOUT_MS + 1e3);
|
|
31773
|
+
const result = raw2;
|
|
31774
|
+
if (result?.exitCode === 0 && typeof result.stdout === "string" && result.stdout.includes("system/com.apple.configd = {")) return;
|
|
31775
|
+
detail2 = typeof result?.exitCode === "number" ? `launchctl exited ${result.exitCode}` : "invalid probe response";
|
|
31776
|
+
} catch {
|
|
31777
|
+
detail2 = "the local service probe failed or timed out";
|
|
31778
|
+
}
|
|
31779
|
+
const hostHealthy = await this.hostProbe().catch(() => false);
|
|
31780
|
+
throw new Error(
|
|
31781
|
+
`Codex cannot access macOS system services (${detail2}; ${scope}). New conversations and turns are blocked; existing work and conversation history were not changed. ` + (hostHealthy ? "Frizz's host context is healthy, but this backend is not. Stop this project's Codex turns before replacing its shared backend; restarting Frizz alone can reuse it." : "Frizz's host context also failed the check. Relaunch Frizz from a fresh terminal after stopping this project's Codex turns; its shared backend must also be replaced.")
|
|
31782
|
+
);
|
|
31783
|
+
}
|
|
31784
|
+
};
|
|
31785
|
+
}
|
|
31786
|
+
});
|
|
31787
|
+
|
|
31662
31788
|
// packages/server/src/backend/codex-app-server.ts
|
|
31663
31789
|
import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
|
|
31664
31790
|
import { StringDecoder as StringDecoder3 } from "node:string_decoder";
|
|
@@ -32320,6 +32446,7 @@ var init_codex_app_server = __esm({
|
|
|
32320
32446
|
init_auth_status();
|
|
32321
32447
|
init_codex_mcp();
|
|
32322
32448
|
init_codex_error();
|
|
32449
|
+
init_codex_execution_health();
|
|
32323
32450
|
init_types2();
|
|
32324
32451
|
init_logging();
|
|
32325
32452
|
CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
@@ -32603,7 +32730,7 @@ var init_codex_app_server = __esm({
|
|
|
32603
32730
|
child.on("exit", () => this.fail("exit", new Error("Codex app-server exited")));
|
|
32604
32731
|
child.on("error", () => this.fail("error", new Error("Codex app-server process failed")));
|
|
32605
32732
|
}
|
|
32606
|
-
async request(method, params) {
|
|
32733
|
+
async request(method, params, timeoutMs = this.timeoutMs) {
|
|
32607
32734
|
if (this.closed) throw new Error("Codex app-server connection is closed");
|
|
32608
32735
|
if (this.pending.size >= MAX_OUTBOUND_REQUESTS) throw new Error("Codex app-server outbound request queue is full");
|
|
32609
32736
|
if (!Number.isSafeInteger(this.nextId)) throw new Error("Codex app-server request id space is exhausted");
|
|
@@ -32612,7 +32739,7 @@ var init_codex_app_server = __esm({
|
|
|
32612
32739
|
const timer = setTimeout(() => {
|
|
32613
32740
|
this.pending.delete(id);
|
|
32614
32741
|
reject(new Error(`Codex app-server request timed out: ${method}`));
|
|
32615
|
-
},
|
|
32742
|
+
}, timeoutMs);
|
|
32616
32743
|
this.pending.set(id, { resolve: resolve12, reject, timer });
|
|
32617
32744
|
});
|
|
32618
32745
|
try {
|
|
@@ -32887,6 +33014,8 @@ var init_codex_app_server = __esm({
|
|
|
32887
33014
|
host;
|
|
32888
33015
|
codexBin;
|
|
32889
33016
|
timeoutMs;
|
|
33017
|
+
executionHealth;
|
|
33018
|
+
executionScope = "";
|
|
32890
33019
|
/** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
|
|
32891
33020
|
daemonGeneration = "";
|
|
32892
33021
|
/** ChatGPT account the current app-server cached when its process started. */
|
|
@@ -32936,6 +33065,7 @@ var init_codex_app_server = __esm({
|
|
|
32936
33065
|
operationWaiters = /* @__PURE__ */ new Set();
|
|
32937
33066
|
shutdownPromise = null;
|
|
32938
33067
|
constructor(options) {
|
|
33068
|
+
this.executionHealth = options.executionHealth ?? new CodexExecutionHealth();
|
|
32939
33069
|
this.options = options;
|
|
32940
33070
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
32941
33071
|
this.makeId = options.id ?? randomUUID11;
|
|
@@ -33172,6 +33302,7 @@ var init_codex_app_server = __esm({
|
|
|
33172
33302
|
const releaseOperation = this.beginOperation();
|
|
33173
33303
|
try {
|
|
33174
33304
|
const connection = await this.ensureConnected();
|
|
33305
|
+
await this.checkExecutionHealth(connection);
|
|
33175
33306
|
const binding = this.bindingForScope(input.threadSlug, input.sessionId);
|
|
33176
33307
|
if (!binding) throw new Error("Codex app-server steer requires a bridge-owned session");
|
|
33177
33308
|
if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
|
|
@@ -33365,7 +33496,8 @@ var init_codex_app_server = __esm({
|
|
|
33365
33496
|
if (bound === 0) return;
|
|
33366
33497
|
const releaseOperation = this.beginOperation();
|
|
33367
33498
|
try {
|
|
33368
|
-
await this.ensureConnected();
|
|
33499
|
+
const connection = await this.ensureConnected();
|
|
33500
|
+
await this.checkExecutionHealth(connection);
|
|
33369
33501
|
await this.autoResumeInterruptedTurns();
|
|
33370
33502
|
} catch {
|
|
33371
33503
|
} finally {
|
|
@@ -33609,6 +33741,7 @@ var init_codex_app_server = __esm({
|
|
|
33609
33741
|
for (let attempt = 0; ; attempt++) {
|
|
33610
33742
|
const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
|
|
33611
33743
|
const connection = await this.ensureConnected();
|
|
33744
|
+
await this.checkExecutionHealth(connection);
|
|
33612
33745
|
if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
|
|
33613
33746
|
if (attempt >= MAX_AUTH_REPLACEMENT_ATTEMPTS) return connection;
|
|
33614
33747
|
if (!this.authReplacement) {
|
|
@@ -33621,6 +33754,17 @@ var init_codex_app_server = __esm({
|
|
|
33621
33754
|
if (!await this.awaitAuthReplacement(this.authReplacement)) return connection;
|
|
33622
33755
|
}
|
|
33623
33756
|
}
|
|
33757
|
+
async checkExecutionHealth(connection) {
|
|
33758
|
+
try {
|
|
33759
|
+
await this.executionHealth.check(connection, this.executionScope);
|
|
33760
|
+
} catch (error) {
|
|
33761
|
+
const detail2 = error.message;
|
|
33762
|
+
this.options.diagnostic?.({ event: "execution-health-failed", detail: detail2 });
|
|
33763
|
+
log.error("codex", detail2);
|
|
33764
|
+
throw error;
|
|
33765
|
+
}
|
|
33766
|
+
if (this.connection !== connection) throw new Error("Codex app-server changed during its execution health check");
|
|
33767
|
+
}
|
|
33624
33768
|
/** Await an armed replacement for at most AUTH_REPLACEMENT_WAIT_MS. `false` = it is still waiting for
|
|
33625
33769
|
* the project to go idle, so the caller should proceed rather than hang; a rejection propagates. */
|
|
33626
33770
|
async awaitAuthReplacement(replacement2) {
|
|
@@ -33695,6 +33839,7 @@ var init_codex_app_server = __esm({
|
|
|
33695
33839
|
authAccountId
|
|
33696
33840
|
});
|
|
33697
33841
|
const child = attachment.process;
|
|
33842
|
+
this.executionScope = `project ${this.options.projectDir}, backend PID ${attachment.daemonPid}, generation ${attachment.generation}`;
|
|
33698
33843
|
let connection;
|
|
33699
33844
|
connection = new JsonlRpcConnection(
|
|
33700
33845
|
child,
|
|
@@ -35219,7 +35364,7 @@ var init_thread_links = __esm({
|
|
|
35219
35364
|
});
|
|
35220
35365
|
|
|
35221
35366
|
// packages/server/src/frizz.ts
|
|
35222
|
-
import { execFile as
|
|
35367
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
35223
35368
|
import { lstatSync as lstatSync2, realpathSync as realpathSync7 } from "node:fs";
|
|
35224
35369
|
import { basename as basename5, dirname as dirname10, join as join27, resolve as resolve6 } from "node:path";
|
|
35225
35370
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -35279,7 +35424,7 @@ var init_frizz = __esm({
|
|
|
35279
35424
|
"packages/server/src/frizz.ts"() {
|
|
35280
35425
|
"use strict";
|
|
35281
35426
|
init_src();
|
|
35282
|
-
execFileP = promisify5(
|
|
35427
|
+
execFileP = promisify5(execFile6);
|
|
35283
35428
|
}
|
|
35284
35429
|
});
|
|
35285
35430
|
|
|
@@ -35571,6 +35716,12 @@ var init_workerPrompt = __esm({
|
|
|
35571
35716
|
"codex": {
|
|
35572
35717
|
"SESSION_KIND": "codex",
|
|
35573
35718
|
"RESUME_CMD": "codex resume"
|
|
35719
|
+
},
|
|
35720
|
+
// An ACP agent has no resume command Frizz can name: the session lives inside the agent and Frizz
|
|
35721
|
+
// re-opens it over the protocol. The token still has to resolve, so it names the mechanism.
|
|
35722
|
+
"acp": {
|
|
35723
|
+
"SESSION_KIND": "coding-agent",
|
|
35724
|
+
"RESUME_CMD": "(Frizz re-opens the session over ACP)"
|
|
35574
35725
|
}
|
|
35575
35726
|
};
|
|
35576
35727
|
INTRO = `You are a dispatched worker agent \u2014 a top-level \`{{FRIZZ_SESSION_KIND}}\` session frizz spawned to drive ONE
|
|
@@ -36102,7 +36253,23 @@ emptied window. Whether to keep notes, and whether to arm anything, is yours to
|
|
|
36102
36253
|
even with \`fork_turns: "none"\`, and one undifferentiated "keep the doc current" mandate is what once
|
|
36103
36254
|
made a child replace a whole shared document with its task notes and then delete the replacement as a
|
|
36104
36255
|
misguided rollback. One file per writer \u2014 \`<agent>-<topic>.md\` \u2014 removes that failure entirely: there
|
|
36105
|
-
is nothing to merge, so there is nothing to clobber. Never edit or delete a file another agent wrote
|
|
36256
|
+
is nothing to merge, so there is nothing to clobber. Never edit or delete a file another agent wrote.`,
|
|
36257
|
+
acp: `## Your scratch directory
|
|
36258
|
+
|
|
36259
|
+
\`.frizz/threads/<session-id>/\` (exact path in your session-start context) \u2014 a folder that is YOURS, for
|
|
36260
|
+
as many files as you like, in whatever format you like. It starts EMPTY and nothing is expected in it.
|
|
36261
|
+
Frizz reads nothing here automatically.
|
|
36262
|
+
|
|
36263
|
+
**IT IS OPTIONAL, IT IS NOT A DELIVERABLE, AND WRITING NOTES IS NOT DOING THE WORK.** It exists in case
|
|
36264
|
+
you want it. A single direct task usually needs nothing here: just do the task. Never let a note stand
|
|
36265
|
+
in for an action \u2014 recording "next: X" when the human asked for X is not progress on X, and a turn that
|
|
36266
|
+
ends right after a scratch write is nearly always a turn that stopped for no reason. When something is
|
|
36267
|
+
worth writing, write it AS YOU GO, mid-work, and then keep working.
|
|
36268
|
+
|
|
36269
|
+
**Notes here can come back after a compaction, if you want that.** Nothing points you at this directory
|
|
36270
|
+
once your context is summarized \u2014 but the frizz \`goal\` tool can arm a goal, and one armed with
|
|
36271
|
+
\`post_compaction: true\` re-sends a prompt of your choosing (which can link a file here) into the
|
|
36272
|
+
emptied window. Whether to keep notes, and whether to arm anything, is yours to decide per effort.`
|
|
36106
36273
|
};
|
|
36107
36274
|
BACKEND = {
|
|
36108
36275
|
claude: `## Sub-agents
|
|
@@ -36361,7 +36528,31 @@ The sandbox governs what you may touch, and a denial is the
|
|
|
36361
36528
|
sandbox \u2014 not a bug: \`read-only\` (inspect, never write), \`workspace-write\` (edit inside the repo,
|
|
36362
36529
|
denied outside), or \`danger-full-access\` (unrestricted). Approvals are off (\`approvalPolicy: never\`), so a
|
|
36363
36530
|
sandbox-denied action fails straight back to you rather than prompting a human \u2014 adapt, or surface
|
|
36364
|
-
the blocker in your final message
|
|
36531
|
+
the blocker in your final message.`,
|
|
36532
|
+
acp: `## Your harness, and the frizz tools inside it
|
|
36533
|
+
|
|
36534
|
+
You are running as a coding agent that Frizz drives over the Agent Client Protocol (ACP). Frizz did
|
|
36535
|
+
not build your harness: your file, shell, search and sub-agent tools are your own, your permission
|
|
36536
|
+
prompts are your harness's (Frizz shows each one to the human as a card and relays the answer), and
|
|
36537
|
+
Frizz's own tools reach you as an MCP server named \`frizz\`. Your harness spells its tools with its own
|
|
36538
|
+
prefix \u2014 \`frizz_ask\`, \`frizz_done\`, \`frizz_watch\` \u2026 on OpenCode; \`mcp__frizz__ask\` on others \u2014 so
|
|
36539
|
+
wherever this contract writes \`mcp__frizz__<verb>\`, call the \`<verb>\` tool of the \`frizz\` server the
|
|
36540
|
+
way your harness lists it. Every verb below exists there: \`ask\`, \`unask\`, \`done\`, \`watch\`,
|
|
36541
|
+
\`unwatch\`, \`watch_pr\`, \`timer\`, \`goal\`, \`title\`, \`link\`, \`unlink\`, \`activity\`, \`spawn_thread\`.
|
|
36542
|
+
|
|
36543
|
+
## Sub-agents
|
|
36544
|
+
|
|
36545
|
+
Frizz does not dispatch sub-agents for you and cannot see the ones your harness runs, so a helper you
|
|
36546
|
+
spawn through your own tools is invisible on the dashboard until you report what it found. Prefer to
|
|
36547
|
+
do the work inline; when you do fan out, collect every child's result before you rest and fold it into
|
|
36548
|
+
your own write-up. \`SendMessage\` and the \`Agent\` tool named elsewhere in this contract are Claude
|
|
36549
|
+
Code's \u2014 use your harness's equivalent, or none.
|
|
36550
|
+
|
|
36551
|
+
## Follow-ups arrive between turns
|
|
36552
|
+
|
|
36553
|
+
A message the human sends while you are working is QUEUED and delivered as your next turn the moment
|
|
36554
|
+
this one ends \u2014 nothing can interrupt you mid-turn except a stop. So rest promptly when a turn's work is
|
|
36555
|
+
done rather than idling inside it, and read the newest human message first when a turn opens.`
|
|
36365
36556
|
};
|
|
36366
36557
|
SPAWN_THREAD = `## Spawning a separate frizz thread
|
|
36367
36558
|
|
|
@@ -36452,12 +36643,98 @@ Dispatches share a vocabulary for the deliverable and quality bar, not for fleet
|
|
|
36452
36643
|
For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
|
|
36453
36644
|
call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
|
|
36454
36645
|
checks. Add fresh-context reviewer agents only under the explicit delegation policy above. Review
|
|
36455
|
-
advice is evidence to judge, not a verdict to copy. Depth scales with blast radius
|
|
36646
|
+
advice is evidence to judge, not a verdict to copy. Depth scales with blast radius.`,
|
|
36647
|
+
acp: `## Thread types
|
|
36648
|
+
|
|
36649
|
+
Recognize which KIND of effort you own and match the deliverable to it:
|
|
36650
|
+
|
|
36651
|
+
- **Research** \u2014 find out what's true. Deliverable is FINDINGS: traces, measurements, exact paths and
|
|
36652
|
+
errors, each load-bearing claim carrying a primary-source \`file:line\` or URL you actually opened. A
|
|
36653
|
+
bug investigation is headed for a FIX, so close with ranked fix options and one recommendation.
|
|
36654
|
+
- **Audit** \u2014 adversarially verify something that exists. Check every prong against the reference,
|
|
36655
|
+
re-verify load-bearing verdicts, cite evidence. Complete = every prong checked.
|
|
36656
|
+
- **Implementation** \u2014 land a DECIDED thing. Plan briefly \u2192 implement \u2192 run the repo's gates \u2192
|
|
36657
|
+
self-review the diff \u2192 fold in every real finding. Complete = MERGED into the project's mainline with
|
|
36658
|
+
docs updated and gates green.
|
|
36659
|
+
- **Planning** \u2014 the DESIGN is the deliverable: a durable plan file, open questions surfaced and then
|
|
36660
|
+
resolved into decisions in that file. A plan that exists only in chat has not been written.
|
|
36661
|
+
|
|
36662
|
+
## Substantive implementation
|
|
36663
|
+
|
|
36664
|
+
For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
|
|
36665
|
+
call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
|
|
36666
|
+
checks. Depth scales with blast radius and yields to the project's conventions.`
|
|
36456
36667
|
};
|
|
36457
36668
|
MONITORS_DIR_FALLBACK = "<frizz>/cc-worker/skills/gh/scripts";
|
|
36458
36669
|
}
|
|
36459
36670
|
});
|
|
36460
36671
|
|
|
36672
|
+
// packages/server/src/backend/acp-agents.ts
|
|
36673
|
+
import { accessSync as accessSync3, constants, statSync as statSync14 } from "node:fs";
|
|
36674
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute6, join as join30 } from "node:path";
|
|
36675
|
+
function isExecutableFile(path3) {
|
|
36676
|
+
try {
|
|
36677
|
+
if (!statSync14(path3).isFile()) return false;
|
|
36678
|
+
if (process.platform !== "win32") accessSync3(path3, constants.X_OK);
|
|
36679
|
+
return true;
|
|
36680
|
+
} catch {
|
|
36681
|
+
return false;
|
|
36682
|
+
}
|
|
36683
|
+
}
|
|
36684
|
+
function findOnPath(command, env = process.env) {
|
|
36685
|
+
if (isAbsolute6(command)) return isExecutableFile(command) ? command : void 0;
|
|
36686
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean);
|
|
36687
|
+
const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
|
|
36688
|
+
for (const dir of dirs) {
|
|
36689
|
+
for (const ext of exts) {
|
|
36690
|
+
const candidate = join30(dir, command + ext.toLowerCase());
|
|
36691
|
+
if (isExecutableFile(candidate)) return candidate;
|
|
36692
|
+
if (ext && isExecutableFile(join30(dir, command + ext))) return join30(dir, command + ext);
|
|
36693
|
+
}
|
|
36694
|
+
}
|
|
36695
|
+
return void 0;
|
|
36696
|
+
}
|
|
36697
|
+
function acpAgentSpecs(custom2) {
|
|
36698
|
+
const byId = /* @__PURE__ */ new Map();
|
|
36699
|
+
for (const spec of ACP_AGENT_CATALOGUE) byId.set(spec.id, spec);
|
|
36700
|
+
for (const spec of custom2 ?? []) if (spec.id && spec.command) byId.set(spec.id, { ...spec, args: [...spec.args ?? []] });
|
|
36701
|
+
return [...byId.values()];
|
|
36702
|
+
}
|
|
36703
|
+
function listAcpAgents(custom2, env = process.env) {
|
|
36704
|
+
return acpAgentSpecs(custom2).map((spec) => {
|
|
36705
|
+
const bin = findOnPath(spec.command, env);
|
|
36706
|
+
return bin ? { ...spec, bin } : { ...spec };
|
|
36707
|
+
});
|
|
36708
|
+
}
|
|
36709
|
+
function resolveAcpAgent(id, custom2, env = process.env) {
|
|
36710
|
+
const spec = acpAgentSpecs(custom2).find((s) => s.id === id);
|
|
36711
|
+
if (!spec) return void 0;
|
|
36712
|
+
const bin = findOnPath(spec.command, env);
|
|
36713
|
+
return bin ? { ...spec, bin } : { ...spec };
|
|
36714
|
+
}
|
|
36715
|
+
var ACP_AGENT_CATALOGUE;
|
|
36716
|
+
var init_acp_agents = __esm({
|
|
36717
|
+
"packages/server/src/backend/acp-agents.ts"() {
|
|
36718
|
+
"use strict";
|
|
36719
|
+
init_src();
|
|
36720
|
+
ACP_AGENT_CATALOGUE = [
|
|
36721
|
+
{ id: "opencode", label: "OpenCode", command: "opencode", args: ["acp"] },
|
|
36722
|
+
{ id: "cursor", label: "Cursor agent", command: "cursor-agent", args: ["acp"] },
|
|
36723
|
+
{ id: "gemini", label: "Gemini CLI", command: "gemini", args: ["--acp"] },
|
|
36724
|
+
{ id: "copilot", label: "GitHub Copilot CLI", command: "copilot", args: ["--acp"] },
|
|
36725
|
+
{ id: "kilo", label: "Kilo", command: "kilo", args: ["acp"] },
|
|
36726
|
+
{ id: "qwen", label: "Qwen Code", command: "qwen", args: ["--acp"] },
|
|
36727
|
+
{ id: "goose", label: "goose", command: "goose", args: ["acp"] },
|
|
36728
|
+
{ id: "kimi", label: "Kimi CLI", command: "kimi", args: ["acp"] },
|
|
36729
|
+
// `grok agent stdio` is ACP over stdio (registry: `npx @xai-official/grok agent stdio`); verified
|
|
36730
|
+
// live 2026-09-16 against grok 1.0.13 — it mounted the probe MCP server and called its tool.
|
|
36731
|
+
{ id: "grok", label: "Grok Build", command: "grok", args: ["agent", "stdio"] },
|
|
36732
|
+
// pi has no native ACP mode; `pi-acp` is its registry wrapper and takes no arguments.
|
|
36733
|
+
{ id: "pi", label: "pi", command: "pi-acp", args: [] }
|
|
36734
|
+
];
|
|
36735
|
+
}
|
|
36736
|
+
});
|
|
36737
|
+
|
|
36461
36738
|
// packages/server/src/backend/claude-broker-client.ts
|
|
36462
36739
|
import net from "node:net";
|
|
36463
36740
|
function connectClaudeBroker(socketPath, handlers, options = {}) {
|
|
@@ -37360,8 +37637,8 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
37360
37637
|
});
|
|
37361
37638
|
|
|
37362
37639
|
// packages/server/src/dispatch.ts
|
|
37363
|
-
import { closeSync as closeSync12, constants, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync20, realpathSync as realpathSync8, statSync as
|
|
37364
|
-
import { basename as basename6, join as
|
|
37640
|
+
import { closeSync as closeSync12, constants as constants2, existsSync as existsSync10, fstatSync as fstatSync2, lstatSync as lstatSync3, openSync as openSync13, readFileSync as readFileSync20, realpathSync as realpathSync8, statSync as statSync15, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, rmSync as rmSync9 } from "node:fs";
|
|
37641
|
+
import { basename as basename6, join as join31, dirname as dirname12 } from "node:path";
|
|
37365
37642
|
import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
|
|
37366
37643
|
function fallbackTitle(prompt) {
|
|
37367
37644
|
const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
|
|
@@ -37380,7 +37657,7 @@ function fallbackTitle(prompt) {
|
|
|
37380
37657
|
}
|
|
37381
37658
|
function resolveSlug(frizzDir, base, taken) {
|
|
37382
37659
|
base = ThreadSlug.parse(base);
|
|
37383
|
-
const isTaken = (slug) => existsSync10(
|
|
37660
|
+
const isTaken = (slug) => existsSync10(join31(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
37384
37661
|
if (!isTaken(base)) return base;
|
|
37385
37662
|
for (let n = 2; ; n++) {
|
|
37386
37663
|
const suffix = `-${n}`;
|
|
@@ -37397,12 +37674,12 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
37397
37674
|
if (!parsed.success) return null;
|
|
37398
37675
|
try {
|
|
37399
37676
|
const projectRoot = realpathSync8(projectDir);
|
|
37400
|
-
const frizzPath =
|
|
37677
|
+
const frizzPath = join31(projectRoot, ".frizz");
|
|
37401
37678
|
const frizzStat = lstatSync3(frizzPath);
|
|
37402
37679
|
if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
|
|
37403
37680
|
const realFrizz = realpathSync8(frizzPath);
|
|
37404
37681
|
if (dirname12(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
|
|
37405
|
-
const path3 =
|
|
37682
|
+
const path3 = join31(realFrizz, `${parsed.data}.md`);
|
|
37406
37683
|
const before = lstatSync3(path3);
|
|
37407
37684
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
37408
37685
|
const realPath = realpathSync8(path3);
|
|
@@ -37410,7 +37687,7 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
37410
37687
|
let contents;
|
|
37411
37688
|
let openedBefore;
|
|
37412
37689
|
let openedAfter;
|
|
37413
|
-
const fd = openSync13(path3,
|
|
37690
|
+
const fd = openSync13(path3, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
37414
37691
|
try {
|
|
37415
37692
|
openedBefore = fstatSync2(fd);
|
|
37416
37693
|
contents = readFileSync20(fd);
|
|
@@ -37446,7 +37723,7 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
37446
37723
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
37447
37724
|
}
|
|
37448
37725
|
function ensureSafeDirectDirectory(parent, name) {
|
|
37449
|
-
const path3 =
|
|
37726
|
+
const path3 = join31(parent, name);
|
|
37450
37727
|
try {
|
|
37451
37728
|
mkdirSync13(path3);
|
|
37452
37729
|
} catch (error) {
|
|
@@ -37476,8 +37753,8 @@ function loadWorkerPrompt(kind = "claude") {
|
|
|
37476
37753
|
function monitorScriptsDir() {
|
|
37477
37754
|
const plugin = workerPluginDir();
|
|
37478
37755
|
if (!plugin) return void 0;
|
|
37479
|
-
const dir =
|
|
37480
|
-
return existsSync10(
|
|
37756
|
+
const dir = join31(plugin, "skills", "gh", "scripts");
|
|
37757
|
+
return existsSync10(join31(dir, "ci-watch.mjs")) ? dir : void 0;
|
|
37481
37758
|
}
|
|
37482
37759
|
function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
37483
37760
|
if (!hookScript || !sessionId) return {};
|
|
@@ -37489,7 +37766,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
37489
37766
|
}
|
|
37490
37767
|
]
|
|
37491
37768
|
});
|
|
37492
|
-
const bashBackgroundHook =
|
|
37769
|
+
const bashBackgroundHook = join31(dirname12(hookScript), "bash-background.mjs");
|
|
37493
37770
|
return {
|
|
37494
37771
|
bypass_hook_trust: true,
|
|
37495
37772
|
hooks: {
|
|
@@ -37515,7 +37792,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
37515
37792
|
}
|
|
37516
37793
|
function scratchpadHookScript() {
|
|
37517
37794
|
const plugin = workerPluginDir();
|
|
37518
|
-
return plugin ?
|
|
37795
|
+
return plugin ? join31(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
37519
37796
|
}
|
|
37520
37797
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
37521
37798
|
const children2 = kind === "codex" ? "Native sub-agents share it \u2014 have each write its OWN file rather than all editing one." : "Name it in a sub-agent's prompt when you want its notes to land somewhere you can read; give each child its OWN file rather than having them all edit one.";
|
|
@@ -37531,10 +37808,10 @@ function scratchpadOrientation(sessionId, kind = "claude") {
|
|
|
37531
37808
|
return `SCRATCH DIRECTORY: .frizz/threads/${sessionId}/ \u2014 yours, free-form, as many files as you like, and nothing is expected in it. A single direct task usually needs none; writing notes is never a substitute for doing the work (${children2}). Nothing in this directory is read automatically; if you want a note back after a compaction, mcp__frizz__goal with post_compaction: true re-sends a prompt of your choosing.`;
|
|
37532
37809
|
}
|
|
37533
37810
|
function frizzConfigBlock(projectDir) {
|
|
37534
|
-
const path3 =
|
|
37811
|
+
const path3 = join31(projectDir, "FRIZZ.md");
|
|
37535
37812
|
let body;
|
|
37536
37813
|
try {
|
|
37537
|
-
const st =
|
|
37814
|
+
const st = statSync15(path3);
|
|
37538
37815
|
if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
|
|
37539
37816
|
body = readFileSync20(path3, "utf8").trim();
|
|
37540
37817
|
} catch {
|
|
@@ -37575,7 +37852,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
37575
37852
|
const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
|
|
37576
37853
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
37577
37854
|
if (!pluginDir) return void 0;
|
|
37578
|
-
const scriptPath =
|
|
37855
|
+
const scriptPath = join31(pluginDir, "bin", FRIZZ_MCP.script);
|
|
37579
37856
|
if (!existsSync10(scriptPath)) return void 0;
|
|
37580
37857
|
return {
|
|
37581
37858
|
scriptPath,
|
|
@@ -37658,7 +37935,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
37658
37935
|
}
|
|
37659
37936
|
function createDispatcher(deps) {
|
|
37660
37937
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
37661
|
-
const frizzDir =
|
|
37938
|
+
const frizzDir = join31(deps.project.dir, ".frizz");
|
|
37662
37939
|
const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
|
|
37663
37940
|
function cleanupPrewrites(built) {
|
|
37664
37941
|
for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
|
|
@@ -37671,7 +37948,7 @@ function createDispatcher(deps) {
|
|
|
37671
37948
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
37672
37949
|
cleanupPrewrites(built);
|
|
37673
37950
|
try {
|
|
37674
|
-
rmSync9(
|
|
37951
|
+
rmSync9(join31(deps.project.dir, scratchRel), { force: true, recursive: true });
|
|
37675
37952
|
} catch {
|
|
37676
37953
|
}
|
|
37677
37954
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -37681,7 +37958,7 @@ function createDispatcher(deps) {
|
|
|
37681
37958
|
input = DispatchInput.parse(input);
|
|
37682
37959
|
const settings = deps.getSettings();
|
|
37683
37960
|
const kind = opts?.backend ?? "claude";
|
|
37684
|
-
if (deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
|
|
37961
|
+
if (kind !== "acp" && deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
|
|
37685
37962
|
throw new ProviderAuthRequiredError(kind);
|
|
37686
37963
|
}
|
|
37687
37964
|
if (kind === "codex" && deps.preflightCodexBinary && await deps.preflightCodexBinary().catch(() => "unknown") === "missing") {
|
|
@@ -37754,6 +38031,52 @@ function createDispatcher(deps) {
|
|
|
37754
38031
|
throw new Error(`Codex app-server could not start this thread: ${err.message}. Check that \`codex\` is installed and its app-server protocol matches the pinned revision (re-pin if you upgraded codex).`);
|
|
37755
38032
|
}
|
|
37756
38033
|
}
|
|
38034
|
+
if (kind === "acp") {
|
|
38035
|
+
const bridge = deps.acpBridge;
|
|
38036
|
+
const agentId = acpAgentIdFromModel(model);
|
|
38037
|
+
if (!bridge || !agentId) {
|
|
38038
|
+
cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
|
|
38039
|
+
throw new Error(!bridge ? "The ACP bridge is unavailable; cannot start this thread." : `An ACP dispatch needs an agent: pick one in the composer (model \`acp:<agent>\`), got ${JSON.stringify(model ?? null)}.`);
|
|
38040
|
+
}
|
|
38041
|
+
const firstPrompt = [loadWorkerPrompt("acp"), scratchpadOrientation(sessionId, kind), frizzConfigBlock(deps.project.dir), prompt].filter(Boolean).join("\n\n");
|
|
38042
|
+
try {
|
|
38043
|
+
const spawned = await bridge.spawnDispatch({ threadSlug: slug, sessionId, cwd: deps.project.dir, agentId, modelId: acpModelIdFromModel(model), prompt: firstPrompt, userText: input.prompt });
|
|
38044
|
+
deps.storage.upsertSession({
|
|
38045
|
+
slug,
|
|
38046
|
+
session_id: sessionId,
|
|
38047
|
+
thread_name: threadIdentityName(slug),
|
|
38048
|
+
spawned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38049
|
+
last_read_at: null,
|
|
38050
|
+
unread: 0,
|
|
38051
|
+
exited: 0,
|
|
38052
|
+
archived: 0,
|
|
38053
|
+
rested_at: null,
|
|
38054
|
+
title_auto: input.title?.trim() ? 0 : 1,
|
|
38055
|
+
title_locked: 0,
|
|
38056
|
+
title: registryTitle,
|
|
38057
|
+
state: "open",
|
|
38058
|
+
meta: null,
|
|
38059
|
+
seen_at: null,
|
|
38060
|
+
transcript_id: null,
|
|
38061
|
+
model: model ?? null,
|
|
38062
|
+
effort: null,
|
|
38063
|
+
permission_mode: permissionMode
|
|
38064
|
+
});
|
|
38065
|
+
deps.storage.setBackend(slug, "acp");
|
|
38066
|
+
deps.storage.setAgentSession(slug, spawned.acpSessionId);
|
|
38067
|
+
deps.storage.setAcpAgent(slug, agentId);
|
|
38068
|
+
void deps.board.rebuild().catch(() => {
|
|
38069
|
+
});
|
|
38070
|
+
return { slug, sessionId };
|
|
38071
|
+
} catch (err) {
|
|
38072
|
+
try {
|
|
38073
|
+
bridge.releaseSession(slug, sessionId, "session-deleted");
|
|
38074
|
+
} catch {
|
|
38075
|
+
}
|
|
38076
|
+
cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
|
|
38077
|
+
throw new Error(`The ACP agent could not start this thread: ${err.message}`);
|
|
38078
|
+
}
|
|
38079
|
+
}
|
|
37757
38080
|
if (kind === "claude") {
|
|
37758
38081
|
const bridge = deps.claudeBroker;
|
|
37759
38082
|
if (!bridge) {
|
|
@@ -38059,6 +38382,8 @@ var init_dispatch = __esm({
|
|
|
38059
38382
|
init_worker_plugin_dir();
|
|
38060
38383
|
init_workerPrompt();
|
|
38061
38384
|
init_codex();
|
|
38385
|
+
init_acp_agents();
|
|
38386
|
+
init_src();
|
|
38062
38387
|
init_claude_agent_broker_bridge();
|
|
38063
38388
|
init_claude_effort();
|
|
38064
38389
|
init_auth_status();
|
|
@@ -38101,7 +38426,10 @@ var init_dispatch = __esm({
|
|
|
38101
38426
|
FRIZZ_MD_MAX_BYTES = 64 * 1024;
|
|
38102
38427
|
WORKER_DISPATCH_PERMISSION = {
|
|
38103
38428
|
claude: "auto",
|
|
38104
|
-
codex: "bypassPermissions"
|
|
38429
|
+
codex: "bypassPermissions",
|
|
38430
|
+
// An ACP agent's permissions are its own (plans/acp-backend.md, decision 7): Frizz sends no mode and
|
|
38431
|
+
// mirrors whatever the agent asks as a card. The value here is recorded on the row and nothing else.
|
|
38432
|
+
acp: "auto"
|
|
38105
38433
|
};
|
|
38106
38434
|
missingPluginReported = false;
|
|
38107
38435
|
}
|
|
@@ -38113,7 +38441,7 @@ import {
|
|
|
38113
38441
|
watch as fsWatch
|
|
38114
38442
|
} from "node:fs";
|
|
38115
38443
|
import { homedir as homedir14 } from "node:os";
|
|
38116
|
-
import { dirname as dirname13, join as
|
|
38444
|
+
import { dirname as dirname13, join as join32 } from "node:path";
|
|
38117
38445
|
import watcher from "@parcel/watcher";
|
|
38118
38446
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
38119
38447
|
if (!liveness) return false;
|
|
@@ -38653,7 +38981,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
38653
38981
|
// from today's dispatch preference: unknown/migrated rows remain unmarked, while rows whose
|
|
38654
38982
|
// database default was explicitly normalized to "claude" get the same per-thread identity as
|
|
38655
38983
|
// Codex rows.
|
|
38656
|
-
backend: row.backend === "claude" || row.backend === "codex" ? row.backend : void 0,
|
|
38984
|
+
backend: row.backend === "claude" || row.backend === "codex" || row.backend === "acp" ? row.backend : void 0,
|
|
38657
38985
|
// Only a persisted, validated per-session value is exposed. A migrated row stays visibly unknown;
|
|
38658
38986
|
// never label it with today's global defaults (which may not match its running process).
|
|
38659
38987
|
permissionMode,
|
|
@@ -38960,7 +39288,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38960
39288
|
if (parcelSub || stopped) return Promise.resolve();
|
|
38961
39289
|
if (watchSetup) return watchSetup;
|
|
38962
39290
|
const setup = (async () => {
|
|
38963
|
-
const dir =
|
|
39291
|
+
const dir = join32(project.dir, ".frizz");
|
|
38964
39292
|
const roots = /* @__PURE__ */ new Set([dir]);
|
|
38965
39293
|
try {
|
|
38966
39294
|
roots.add(realpathSync9(dir));
|
|
@@ -38975,7 +39303,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38975
39303
|
}
|
|
38976
39304
|
if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
|
|
38977
39305
|
},
|
|
38978
|
-
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) =>
|
|
39306
|
+
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join32(dir, name)) }
|
|
38979
39307
|
);
|
|
38980
39308
|
if (stopped) {
|
|
38981
39309
|
await next.unsubscribe();
|
|
@@ -39293,7 +39621,7 @@ var init_wake_store = __esm({
|
|
|
39293
39621
|
});
|
|
39294
39622
|
|
|
39295
39623
|
// packages/server/src/github-review.ts
|
|
39296
|
-
import { execFile as
|
|
39624
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
39297
39625
|
import { promisify as promisify6 } from "node:util";
|
|
39298
39626
|
function refKey(ref) {
|
|
39299
39627
|
return `${ref.kind === "issue" ? "issue:" : ""}${ref.owner}/${ref.repo}#${ref.number}`;
|
|
@@ -39712,7 +40040,7 @@ var execFileAsync4, GH_TIMEOUT_MS, GH_MAX_BUFFER, REQUEST_TIMEOUT_MS, MAX_REFS_P
|
|
|
39712
40040
|
var init_github_review = __esm({
|
|
39713
40041
|
"packages/server/src/github-review.ts"() {
|
|
39714
40042
|
"use strict";
|
|
39715
|
-
execFileAsync4 = promisify6(
|
|
40043
|
+
execFileAsync4 = promisify6(execFile7);
|
|
39716
40044
|
GH_TIMEOUT_MS = 3e4;
|
|
39717
40045
|
GH_MAX_BUFFER = 1024 * 1024;
|
|
39718
40046
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -39849,7 +40177,7 @@ var init_pr_watch_noise = __esm({
|
|
|
39849
40177
|
});
|
|
39850
40178
|
|
|
39851
40179
|
// packages/server/src/scheduler.ts
|
|
39852
|
-
import { execFile as
|
|
40180
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
39853
40181
|
import { promisify as promisify7 } from "node:util";
|
|
39854
40182
|
import { createHash as createHash12, randomUUID as randomUUID14 } from "node:crypto";
|
|
39855
40183
|
function failureSignature(rollup) {
|
|
@@ -41740,7 +42068,7 @@ var init_scheduler = __esm({
|
|
|
41740
42068
|
init_pr_watch_noise();
|
|
41741
42069
|
init_github_review();
|
|
41742
42070
|
init_logging();
|
|
41743
|
-
execFileAsync5 = promisify7(
|
|
42071
|
+
execFileAsync5 = promisify7(execFile8);
|
|
41744
42072
|
LIMIT_FENCE_PREFIX = "limit";
|
|
41745
42073
|
LIMIT_HINT_PREFIX = "limit:";
|
|
41746
42074
|
LIMIT_RESUME_GRACE_MS = 6e4;
|
|
@@ -41862,7 +42190,7 @@ var init_resume = __esm({
|
|
|
41862
42190
|
});
|
|
41863
42191
|
|
|
41864
42192
|
// packages/server/src/backend/claude.ts
|
|
41865
|
-
import { join as
|
|
42193
|
+
import { join as join33 } from "node:path";
|
|
41866
42194
|
function toolResultText2(content) {
|
|
41867
42195
|
if (typeof content === "string") return content;
|
|
41868
42196
|
if (!Array.isArray(content)) return "";
|
|
@@ -41953,7 +42281,7 @@ function createClaudeBackend(opts) {
|
|
|
41953
42281
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
41954
42282
|
},
|
|
41955
42283
|
transcriptPath(sessionId) {
|
|
41956
|
-
return
|
|
42284
|
+
return join33(opts.logDir, `${sessionId}.jsonl`);
|
|
41957
42285
|
},
|
|
41958
42286
|
parseLine(line) {
|
|
41959
42287
|
return parseClaudeLine(line);
|
|
@@ -41977,6 +42305,1181 @@ var init_claude = __esm({
|
|
|
41977
42305
|
}
|
|
41978
42306
|
});
|
|
41979
42307
|
|
|
42308
|
+
// packages/server/src/backend/acp-transcript.ts
|
|
42309
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync14, readFileSync as readFileSync21 } from "node:fs";
|
|
42310
|
+
import { dirname as dirname14, join as join34 } from "node:path";
|
|
42311
|
+
function acpTranscriptPath(stateDir, sessionId) {
|
|
42312
|
+
return join34(stateDir, ACP_TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
42313
|
+
}
|
|
42314
|
+
function parseAcpRecord(line) {
|
|
42315
|
+
if (!line.trim()) return void 0;
|
|
42316
|
+
let raw2;
|
|
42317
|
+
try {
|
|
42318
|
+
raw2 = JSON.parse(line);
|
|
42319
|
+
} catch {
|
|
42320
|
+
return void 0;
|
|
42321
|
+
}
|
|
42322
|
+
if (!raw2 || typeof raw2 !== "object" || typeof raw2.kind !== "string") return void 0;
|
|
42323
|
+
const kind = raw2.kind;
|
|
42324
|
+
if (kind === "acp-session" || kind === "acp-note" || EVENT_KINDS.has(kind)) return raw2;
|
|
42325
|
+
return void 0;
|
|
42326
|
+
}
|
|
42327
|
+
function parseAcpLine(line) {
|
|
42328
|
+
const rec = parseAcpRecord(line);
|
|
42329
|
+
if (!rec || rec.kind === "acp-session" || rec.kind === "acp-note") return [];
|
|
42330
|
+
const { messageId: _m, acp: _a, ...ev } = rec;
|
|
42331
|
+
return [ev];
|
|
42332
|
+
}
|
|
42333
|
+
function createAcpBackend(opts) {
|
|
42334
|
+
return {
|
|
42335
|
+
kind: "acp",
|
|
42336
|
+
buildSpawn(_o) {
|
|
42337
|
+
throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
|
|
42338
|
+
},
|
|
42339
|
+
buildResume(_o) {
|
|
42340
|
+
throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
|
|
42341
|
+
},
|
|
42342
|
+
// Deterministic: the bridge names the file after the frizz session id, so no discovery step.
|
|
42343
|
+
transcriptPath(sessionId) {
|
|
42344
|
+
return acpTranscriptPath(opts.stateDir, sessionId);
|
|
42345
|
+
},
|
|
42346
|
+
parseLine: parseAcpLine,
|
|
42347
|
+
foldLine(state, line) {
|
|
42348
|
+
const rec = parseAcpRecord(line);
|
|
42349
|
+
if (!rec) return;
|
|
42350
|
+
if (rec.kind === "acp-session") {
|
|
42351
|
+
if (rec.model) {
|
|
42352
|
+
state.model = rec.model;
|
|
42353
|
+
state.profileAt = rec.at;
|
|
42354
|
+
state.profileRevision = (state.profileRevision ?? 0) + 1;
|
|
42355
|
+
}
|
|
42356
|
+
return;
|
|
42357
|
+
}
|
|
42358
|
+
for (const ev of parseAcpLine(line)) applyEvent(state, ev);
|
|
42359
|
+
}
|
|
42360
|
+
};
|
|
42361
|
+
}
|
|
42362
|
+
function clip2(text2, max) {
|
|
42363
|
+
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
42364
|
+
}
|
|
42365
|
+
function toolTarget(rawInput, acp) {
|
|
42366
|
+
const input = rawInput && typeof rawInput === "object" ? rawInput : void 0;
|
|
42367
|
+
const command = typeof input?.command === "string" ? input.command : void 0;
|
|
42368
|
+
const path3 = typeof input?.filePath === "string" ? input.filePath : typeof input?.file_path === "string" ? input.file_path : typeof input?.path === "string" ? input.path : acp?.locations?.[0];
|
|
42369
|
+
return { command, path: path3, input };
|
|
42370
|
+
}
|
|
42371
|
+
function toolCallFor(ev, acp) {
|
|
42372
|
+
const { command, path: path3, input } = toolTarget(ev.input, acp);
|
|
42373
|
+
const detail2 = command ?? path3 ?? acp?.title ?? "";
|
|
42374
|
+
const call = { name: ev.name, status: "pending" };
|
|
42375
|
+
if (detail2) call.detail = clip2(detail2, 500);
|
|
42376
|
+
if (command) call.command = command;
|
|
42377
|
+
else if (input && Object.keys(input).length) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
|
|
42378
|
+
return call;
|
|
42379
|
+
}
|
|
42380
|
+
function upgradeToolTarget(call, acp) {
|
|
42381
|
+
if (!acp || call.command || call.detail && call.detail !== call.name) return;
|
|
42382
|
+
const { command, path: path3, input } = toolTarget(acp.input, acp);
|
|
42383
|
+
const detail2 = command ?? path3;
|
|
42384
|
+
if (!detail2) return;
|
|
42385
|
+
call.detail = clip2(detail2, 500);
|
|
42386
|
+
if (command) call.command = command;
|
|
42387
|
+
else if (input && Object.keys(input).length && !call.input) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
|
|
42388
|
+
}
|
|
42389
|
+
function projectAcpTranscript(raw2, identityPrefix = "acp") {
|
|
42390
|
+
const out = [];
|
|
42391
|
+
let current;
|
|
42392
|
+
let currentMessageId;
|
|
42393
|
+
const openTools = /* @__PURE__ */ new Map();
|
|
42394
|
+
const lines = raw2.split("\n");
|
|
42395
|
+
const close2 = () => {
|
|
42396
|
+
current = void 0;
|
|
42397
|
+
currentMessageId = void 0;
|
|
42398
|
+
};
|
|
42399
|
+
const openAssistant = (i, at) => {
|
|
42400
|
+
if (!current) {
|
|
42401
|
+
current = { sourceId: `${identityPrefix}:${i}`, role: "assistant", text: "", tools: [], parts: [], ...at ? { at } : {} };
|
|
42402
|
+
out.push(current);
|
|
42403
|
+
}
|
|
42404
|
+
return current;
|
|
42405
|
+
};
|
|
42406
|
+
const appendText = (msg, text2) => {
|
|
42407
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42408
|
+
if (last?.kind === "text") last.text += text2;
|
|
42409
|
+
else msg.parts.push({ kind: "text", text: text2 });
|
|
42410
|
+
msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
|
|
42411
|
+
};
|
|
42412
|
+
const appendTool = (msg, tool) => {
|
|
42413
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42414
|
+
if (last?.kind === "tools") last.tools.push(tool);
|
|
42415
|
+
else msg.parts.push({ kind: "tools", tools: [tool] });
|
|
42416
|
+
msg.tools.push(tool);
|
|
42417
|
+
};
|
|
42418
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42419
|
+
const rec = parseAcpRecord(lines[i]);
|
|
42420
|
+
if (!rec) continue;
|
|
42421
|
+
switch (rec.kind) {
|
|
42422
|
+
case "acp-session":
|
|
42423
|
+
case "acp-note":
|
|
42424
|
+
case "title":
|
|
42425
|
+
case "context-usage":
|
|
42426
|
+
case "compaction":
|
|
42427
|
+
case "agent-report":
|
|
42428
|
+
case "agent-instruction":
|
|
42429
|
+
break;
|
|
42430
|
+
case "user-message": {
|
|
42431
|
+
close2();
|
|
42432
|
+
const text2 = rec.text ?? "";
|
|
42433
|
+
out.push({ sourceId: `${identityPrefix}:${i}`, role: "user", text: text2, tools: [], parts: [], ...rec.at ? { at: rec.at } : {}, ...rec.synthetic ? { wake: true } : {} });
|
|
42434
|
+
break;
|
|
42435
|
+
}
|
|
42436
|
+
case "turn-start":
|
|
42437
|
+
break;
|
|
42438
|
+
case "turn-end":
|
|
42439
|
+
close2();
|
|
42440
|
+
break;
|
|
42441
|
+
case "reasoning": {
|
|
42442
|
+
close2();
|
|
42443
|
+
out.push({ sourceId: `${identityPrefix}:${i}`, role: "assistant", kind: "reasoning", text: rec.text, tools: [], parts: [], ...rec.at ? { at: rec.at } : {} });
|
|
42444
|
+
break;
|
|
42445
|
+
}
|
|
42446
|
+
case "assistant-text": {
|
|
42447
|
+
const msg = openAssistant(i, rec.at);
|
|
42448
|
+
const mid = rec.messageId;
|
|
42449
|
+
if (mid && mid === currentMessageId) {
|
|
42450
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42451
|
+
if (last?.kind === "text") {
|
|
42452
|
+
last.text += rec.text;
|
|
42453
|
+
msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
|
|
42454
|
+
break;
|
|
42455
|
+
}
|
|
42456
|
+
}
|
|
42457
|
+
currentMessageId = mid;
|
|
42458
|
+
appendText(msg, rec.text);
|
|
42459
|
+
break;
|
|
42460
|
+
}
|
|
42461
|
+
case "tool-call": {
|
|
42462
|
+
const msg = openAssistant(i, rec.at);
|
|
42463
|
+
currentMessageId = void 0;
|
|
42464
|
+
const call = toolCallFor(rec, rec.acp);
|
|
42465
|
+
openTools.set(rec.id, call);
|
|
42466
|
+
appendTool(msg, call);
|
|
42467
|
+
break;
|
|
42468
|
+
}
|
|
42469
|
+
case "tool-result": {
|
|
42470
|
+
const call = openTools.get(rec.id);
|
|
42471
|
+
if (!call) break;
|
|
42472
|
+
openTools.delete(rec.id);
|
|
42473
|
+
const failed = rec.acp?.status === "failed" || rec.acp?.kind === "failed";
|
|
42474
|
+
call.status = failed ? "failed" : "completed";
|
|
42475
|
+
upgradeToolTarget(call, rec.acp);
|
|
42476
|
+
if (rec.text) call.output = clip2(rec.text, TOOL_TEXT_MAX);
|
|
42477
|
+
break;
|
|
42478
|
+
}
|
|
42479
|
+
case "provider-error": {
|
|
42480
|
+
const msg = openAssistant(i, rec.at);
|
|
42481
|
+
msg.providerError = rec.error;
|
|
42482
|
+
close2();
|
|
42483
|
+
break;
|
|
42484
|
+
}
|
|
42485
|
+
}
|
|
42486
|
+
}
|
|
42487
|
+
return out;
|
|
42488
|
+
}
|
|
42489
|
+
function readAcpTranscriptFile(absPath, nativeId = absPath) {
|
|
42490
|
+
try {
|
|
42491
|
+
return projectAcpTranscript(readFileSync21(absPath, "utf8"), `acp:${nativeId}`);
|
|
42492
|
+
} catch {
|
|
42493
|
+
return [];
|
|
42494
|
+
}
|
|
42495
|
+
}
|
|
42496
|
+
var ACP_TRANSCRIPT_DIR, EVENT_KINDS, AcpTranscriptWriter, TOOL_TEXT_MAX;
|
|
42497
|
+
var init_acp_transcript = __esm({
|
|
42498
|
+
"packages/server/src/backend/acp-transcript.ts"() {
|
|
42499
|
+
"use strict";
|
|
42500
|
+
init_tailer();
|
|
42501
|
+
ACP_TRANSCRIPT_DIR = "acp";
|
|
42502
|
+
EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
42503
|
+
"turn-start",
|
|
42504
|
+
"turn-end",
|
|
42505
|
+
"provider-error",
|
|
42506
|
+
"assistant-text",
|
|
42507
|
+
"user-message",
|
|
42508
|
+
"tool-call",
|
|
42509
|
+
"tool-result",
|
|
42510
|
+
"reasoning",
|
|
42511
|
+
"agent-report",
|
|
42512
|
+
"agent-instruction",
|
|
42513
|
+
"title",
|
|
42514
|
+
"compaction",
|
|
42515
|
+
"context-usage"
|
|
42516
|
+
]);
|
|
42517
|
+
AcpTranscriptWriter = class {
|
|
42518
|
+
constructor(path3) {
|
|
42519
|
+
this.path = path3;
|
|
42520
|
+
mkdirSync14(dirname14(path3), { recursive: true });
|
|
42521
|
+
if (!existsSync12(path3)) appendFileSync3(path3, "");
|
|
42522
|
+
}
|
|
42523
|
+
append(record) {
|
|
42524
|
+
appendFileSync3(this.path, JSON.stringify(record) + "\n");
|
|
42525
|
+
}
|
|
42526
|
+
};
|
|
42527
|
+
TOOL_TEXT_MAX = 12e3;
|
|
42528
|
+
}
|
|
42529
|
+
});
|
|
42530
|
+
|
|
42531
|
+
// packages/server/src/backend/acp-types.ts
|
|
42532
|
+
function parseSessionUpdate(raw2) {
|
|
42533
|
+
const parsed = AcpSessionUpdateKnown.safeParse(raw2);
|
|
42534
|
+
return parsed.success ? parsed.data : { sessionUpdate: "unknown", raw: raw2 };
|
|
42535
|
+
}
|
|
42536
|
+
function contentText(block) {
|
|
42537
|
+
if (!block) return void 0;
|
|
42538
|
+
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
42539
|
+
if (block.type === "resource") {
|
|
42540
|
+
const r = block.resource;
|
|
42541
|
+
return typeof r?.text === "string" ? r.text : void 0;
|
|
42542
|
+
}
|
|
42543
|
+
return void 0;
|
|
42544
|
+
}
|
|
42545
|
+
var ACP_PROTOCOL_VERSION, JsonRpcId, JsonRpcError, JsonRpcInbound, ACP_ERROR_AUTH_REQUIRED, ACP_ERROR_METHOD_NOT_FOUND, ACP_ERROR_INTERNAL, AcpAuthMethod, AcpAgentCapabilities, AcpInitializeResult, AcpMcpServerStdio, AcpConfigOption, AcpNewSessionResult, AcpSetConfigOptionResult, AcpLoadSessionResult, AcpContentBlock, AcpToolKind, AcpToolCallStatus, AcpToolCallContent, AcpToolCallLocation, ToolCallFields, AcpToolCallUpdate, AcpPlanEntry, AcpSessionUpdateKnown, AcpSessionNotification, AcpStopReason, AcpPromptResult, AcpPermissionOptionKind, AcpPermissionOption, AcpRequestPermissionParams;
|
|
42546
|
+
var init_acp_types = __esm({
|
|
42547
|
+
"packages/server/src/backend/acp-types.ts"() {
|
|
42548
|
+
"use strict";
|
|
42549
|
+
init_zod();
|
|
42550
|
+
ACP_PROTOCOL_VERSION = 1;
|
|
42551
|
+
JsonRpcId = external_exports.union([external_exports.string(), external_exports.number()]);
|
|
42552
|
+
JsonRpcError = external_exports.object({
|
|
42553
|
+
code: external_exports.number(),
|
|
42554
|
+
message: external_exports.string(),
|
|
42555
|
+
data: external_exports.unknown().optional()
|
|
42556
|
+
}).passthrough();
|
|
42557
|
+
JsonRpcInbound = external_exports.union([
|
|
42558
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, result: external_exports.unknown() }).passthrough(),
|
|
42559
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, error: JsonRpcError }).passthrough(),
|
|
42560
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough(),
|
|
42561
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough()
|
|
42562
|
+
]);
|
|
42563
|
+
ACP_ERROR_AUTH_REQUIRED = -32e3;
|
|
42564
|
+
ACP_ERROR_METHOD_NOT_FOUND = -32601;
|
|
42565
|
+
ACP_ERROR_INTERNAL = -32603;
|
|
42566
|
+
AcpAuthMethod = external_exports.object({
|
|
42567
|
+
id: external_exports.string(),
|
|
42568
|
+
name: external_exports.string().optional(),
|
|
42569
|
+
description: external_exports.string().optional(),
|
|
42570
|
+
// Absent = "agent" (call `authenticate {methodId}`); "terminal" = relaunch the same program
|
|
42571
|
+
// interactively with `args`/`env` — which Frizz does not do; it tells the operator instead.
|
|
42572
|
+
type: external_exports.string().optional()
|
|
42573
|
+
}).passthrough();
|
|
42574
|
+
AcpAgentCapabilities = external_exports.object({
|
|
42575
|
+
loadSession: external_exports.boolean().optional(),
|
|
42576
|
+
promptCapabilities: external_exports.object({ image: external_exports.boolean().optional(), audio: external_exports.boolean().optional(), embeddedContext: external_exports.boolean().optional() }).passthrough().optional(),
|
|
42577
|
+
mcpCapabilities: external_exports.object({ http: external_exports.boolean().optional(), sse: external_exports.boolean().optional() }).passthrough().optional(),
|
|
42578
|
+
// Object markers: `{}` present = supported, absent = not.
|
|
42579
|
+
sessionCapabilities: external_exports.object({
|
|
42580
|
+
list: external_exports.unknown().optional(),
|
|
42581
|
+
resume: external_exports.unknown().optional(),
|
|
42582
|
+
close: external_exports.unknown().optional(),
|
|
42583
|
+
fork: external_exports.unknown().optional(),
|
|
42584
|
+
delete: external_exports.unknown().optional()
|
|
42585
|
+
}).passthrough().optional()
|
|
42586
|
+
}).passthrough();
|
|
42587
|
+
AcpInitializeResult = external_exports.object({
|
|
42588
|
+
protocolVersion: external_exports.number(),
|
|
42589
|
+
agentCapabilities: AcpAgentCapabilities.optional(),
|
|
42590
|
+
authMethods: external_exports.array(AcpAuthMethod).optional(),
|
|
42591
|
+
agentInfo: external_exports.object({ name: external_exports.string().optional(), title: external_exports.string().optional(), version: external_exports.string().optional() }).passthrough().optional()
|
|
42592
|
+
}).passthrough();
|
|
42593
|
+
AcpMcpServerStdio = external_exports.object({
|
|
42594
|
+
name: external_exports.string(),
|
|
42595
|
+
command: external_exports.string(),
|
|
42596
|
+
args: external_exports.array(external_exports.string()),
|
|
42597
|
+
env: external_exports.array(external_exports.object({ name: external_exports.string(), value: external_exports.string() }))
|
|
42598
|
+
});
|
|
42599
|
+
AcpConfigOption = external_exports.object({
|
|
42600
|
+
id: external_exports.string(),
|
|
42601
|
+
name: external_exports.string().optional(),
|
|
42602
|
+
category: external_exports.string().optional(),
|
|
42603
|
+
type: external_exports.string().optional(),
|
|
42604
|
+
currentValue: external_exports.unknown().optional(),
|
|
42605
|
+
options: external_exports.array(external_exports.object({ value: external_exports.string(), name: external_exports.string().optional() }).passthrough()).optional()
|
|
42606
|
+
}).passthrough();
|
|
42607
|
+
AcpNewSessionResult = external_exports.object({
|
|
42608
|
+
sessionId: external_exports.string().min(1),
|
|
42609
|
+
configOptions: external_exports.array(AcpConfigOption).nullish(),
|
|
42610
|
+
modes: external_exports.unknown().optional()
|
|
42611
|
+
}).passthrough();
|
|
42612
|
+
AcpSetConfigOptionResult = external_exports.object({ configOptions: external_exports.array(AcpConfigOption).nullish() }).passthrough();
|
|
42613
|
+
AcpLoadSessionResult = external_exports.object({
|
|
42614
|
+
configOptions: external_exports.array(AcpConfigOption).nullish(),
|
|
42615
|
+
modes: external_exports.unknown().optional()
|
|
42616
|
+
}).passthrough();
|
|
42617
|
+
AcpContentBlock = external_exports.union([
|
|
42618
|
+
external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() }).passthrough(),
|
|
42619
|
+
external_exports.object({ type: external_exports.literal("resource_link"), uri: external_exports.string(), name: external_exports.string().optional() }).passthrough(),
|
|
42620
|
+
external_exports.object({ type: external_exports.literal("resource"), resource: external_exports.object({ uri: external_exports.string(), text: external_exports.string().optional() }).passthrough() }).passthrough(),
|
|
42621
|
+
external_exports.object({ type: external_exports.literal("image"), mimeType: external_exports.string().optional(), data: external_exports.string().optional() }).passthrough(),
|
|
42622
|
+
external_exports.object({ type: external_exports.string() }).passthrough()
|
|
42623
|
+
]);
|
|
42624
|
+
AcpToolKind = external_exports.enum(["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"]);
|
|
42625
|
+
AcpToolCallStatus = external_exports.enum(["pending", "in_progress", "completed", "failed"]);
|
|
42626
|
+
AcpToolCallContent = external_exports.union([
|
|
42627
|
+
external_exports.object({ type: external_exports.literal("content"), content: AcpContentBlock }).passthrough(),
|
|
42628
|
+
external_exports.object({ type: external_exports.literal("diff"), path: external_exports.string(), oldText: external_exports.string().nullish(), newText: external_exports.string() }).passthrough(),
|
|
42629
|
+
external_exports.object({ type: external_exports.literal("terminal"), terminalId: external_exports.string() }).passthrough(),
|
|
42630
|
+
external_exports.object({ type: external_exports.string() }).passthrough()
|
|
42631
|
+
]);
|
|
42632
|
+
AcpToolCallLocation = external_exports.object({ path: external_exports.string(), line: external_exports.number().nullish() }).passthrough();
|
|
42633
|
+
ToolCallFields = {
|
|
42634
|
+
toolCallId: external_exports.string(),
|
|
42635
|
+
title: external_exports.string().optional(),
|
|
42636
|
+
kind: external_exports.string().optional(),
|
|
42637
|
+
status: external_exports.string().optional(),
|
|
42638
|
+
content: external_exports.array(AcpToolCallContent).nullish(),
|
|
42639
|
+
locations: external_exports.array(AcpToolCallLocation).nullish(),
|
|
42640
|
+
rawInput: external_exports.unknown().optional(),
|
|
42641
|
+
rawOutput: external_exports.unknown().optional()
|
|
42642
|
+
};
|
|
42643
|
+
AcpToolCallUpdate = external_exports.object(ToolCallFields).passthrough();
|
|
42644
|
+
AcpPlanEntry = external_exports.object({ content: external_exports.string(), priority: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough();
|
|
42645
|
+
AcpSessionUpdateKnown = external_exports.discriminatedUnion("sessionUpdate", [
|
|
42646
|
+
external_exports.object({ sessionUpdate: external_exports.literal("user_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42647
|
+
external_exports.object({ sessionUpdate: external_exports.literal("agent_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42648
|
+
external_exports.object({ sessionUpdate: external_exports.literal("agent_thought_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42649
|
+
external_exports.object({ sessionUpdate: external_exports.literal("tool_call"), ...ToolCallFields }).passthrough(),
|
|
42650
|
+
external_exports.object({ sessionUpdate: external_exports.literal("tool_call_update"), ...ToolCallFields }).passthrough(),
|
|
42651
|
+
external_exports.object({ sessionUpdate: external_exports.literal("plan"), entries: external_exports.array(AcpPlanEntry) }).passthrough(),
|
|
42652
|
+
external_exports.object({ sessionUpdate: external_exports.literal("usage_update"), used: external_exports.number(), size: external_exports.number(), cost: external_exports.unknown().optional() }).passthrough(),
|
|
42653
|
+
external_exports.object({ sessionUpdate: external_exports.literal("session_info_update"), title: external_exports.string().nullish(), updatedAt: external_exports.string().nullish() }).passthrough(),
|
|
42654
|
+
external_exports.object({ sessionUpdate: external_exports.literal("config_option_update"), configOptions: external_exports.array(AcpConfigOption) }).passthrough(),
|
|
42655
|
+
external_exports.object({ sessionUpdate: external_exports.literal("compaction_update"), compactionId: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough()
|
|
42656
|
+
]);
|
|
42657
|
+
AcpSessionNotification = external_exports.object({ sessionId: external_exports.string(), update: external_exports.unknown() }).passthrough();
|
|
42658
|
+
AcpStopReason = external_exports.enum(["end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"]);
|
|
42659
|
+
AcpPromptResult = external_exports.object({
|
|
42660
|
+
// A string, not the enum: a future reason must not turn a finished turn into a parse failure.
|
|
42661
|
+
stopReason: external_exports.string(),
|
|
42662
|
+
usage: external_exports.object({ inputTokens: external_exports.number().optional(), outputTokens: external_exports.number().optional(), totalTokens: external_exports.number().optional() }).passthrough().nullish()
|
|
42663
|
+
}).passthrough();
|
|
42664
|
+
AcpPermissionOptionKind = external_exports.enum(["allow_once", "allow_always", "reject_once", "reject_always"]);
|
|
42665
|
+
AcpPermissionOption = external_exports.object({
|
|
42666
|
+
optionId: external_exports.string(),
|
|
42667
|
+
name: external_exports.string().optional(),
|
|
42668
|
+
// A string: an unknown kind renders as an opaque choice rather than failing the whole request.
|
|
42669
|
+
kind: external_exports.string().optional()
|
|
42670
|
+
}).passthrough();
|
|
42671
|
+
AcpRequestPermissionParams = external_exports.object({
|
|
42672
|
+
sessionId: external_exports.string(),
|
|
42673
|
+
toolCall: AcpToolCallUpdate,
|
|
42674
|
+
options: external_exports.array(AcpPermissionOption)
|
|
42675
|
+
}).passthrough();
|
|
42676
|
+
}
|
|
42677
|
+
});
|
|
42678
|
+
|
|
42679
|
+
// packages/server/src/backend/acp-rpc.ts
|
|
42680
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
42681
|
+
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
42682
|
+
var spawnAcpChild, AcpRequestError, AcpRemoteError, AcpConnectionClosed, AcpRequestTimeout, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINE_BYTES, AcpConnection;
|
|
42683
|
+
var init_acp_rpc = __esm({
|
|
42684
|
+
"packages/server/src/backend/acp-rpc.ts"() {
|
|
42685
|
+
"use strict";
|
|
42686
|
+
init_acp_types();
|
|
42687
|
+
spawnAcpChild = ({ command, args, cwd, env }) => spawn6(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
42688
|
+
AcpRequestError = class extends Error {
|
|
42689
|
+
constructor(code, message, data) {
|
|
42690
|
+
super(message);
|
|
42691
|
+
this.code = code;
|
|
42692
|
+
this.data = data;
|
|
42693
|
+
}
|
|
42694
|
+
};
|
|
42695
|
+
AcpRemoteError = class extends Error {
|
|
42696
|
+
constructor(method, error) {
|
|
42697
|
+
super(`${method}: ${error.message} (${error.code})`);
|
|
42698
|
+
this.method = method;
|
|
42699
|
+
this.error = error;
|
|
42700
|
+
}
|
|
42701
|
+
};
|
|
42702
|
+
AcpConnectionClosed = class extends Error {
|
|
42703
|
+
constructor(method, reason) {
|
|
42704
|
+
super(`${method}: agent connection closed (${reason})`);
|
|
42705
|
+
this.method = method;
|
|
42706
|
+
this.reason = reason;
|
|
42707
|
+
}
|
|
42708
|
+
};
|
|
42709
|
+
AcpRequestTimeout = class extends Error {
|
|
42710
|
+
constructor(method, timeoutMs) {
|
|
42711
|
+
super(`${method}: no response from the agent within ${Math.round(timeoutMs / 1e3)}s`);
|
|
42712
|
+
this.method = method;
|
|
42713
|
+
this.timeoutMs = timeoutMs;
|
|
42714
|
+
}
|
|
42715
|
+
};
|
|
42716
|
+
DEFAULT_TIMEOUT_MS = 6e4;
|
|
42717
|
+
DEFAULT_MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
42718
|
+
AcpConnection = class {
|
|
42719
|
+
constructor(process2, options) {
|
|
42720
|
+
this.process = process2;
|
|
42721
|
+
this.options = options;
|
|
42722
|
+
const maxLine = options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
|
|
42723
|
+
this.exitPromise = new Promise((resolve12) => {
|
|
42724
|
+
process2.on("exit", (code, signal) => {
|
|
42725
|
+
this.settleClosed(`exit ${signal ?? code ?? "?"}`);
|
|
42726
|
+
resolve12({ code, signal });
|
|
42727
|
+
});
|
|
42728
|
+
process2.on("error", (err) => {
|
|
42729
|
+
this.settleClosed(err.message);
|
|
42730
|
+
resolve12({ code: null, signal: null });
|
|
42731
|
+
});
|
|
42732
|
+
});
|
|
42733
|
+
process2.stdout?.on("data", (chunk) => {
|
|
42734
|
+
this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
42735
|
+
let nl;
|
|
42736
|
+
while ((nl = this.buffer.indexOf("\n")) >= 0) {
|
|
42737
|
+
const line = this.buffer.slice(0, nl);
|
|
42738
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
42739
|
+
if (this.overflowing) {
|
|
42740
|
+
this.overflowing = false;
|
|
42741
|
+
continue;
|
|
42742
|
+
}
|
|
42743
|
+
this.handleLine(line);
|
|
42744
|
+
}
|
|
42745
|
+
if (this.buffer.length > maxLine) {
|
|
42746
|
+
options.onDiagnostic?.({ kind: "oversized-line", message: `dropped a stdout line longer than ${maxLine} bytes` });
|
|
42747
|
+
this.buffer = "";
|
|
42748
|
+
this.overflowing = true;
|
|
42749
|
+
}
|
|
42750
|
+
});
|
|
42751
|
+
process2.stderr?.on("data", (chunk) => {
|
|
42752
|
+
const text2 = String(chunk);
|
|
42753
|
+
for (const line of text2.split("\n")) {
|
|
42754
|
+
if (!line.trim()) continue;
|
|
42755
|
+
this.stderrTail.push(line.slice(0, 2e3));
|
|
42756
|
+
if (this.stderrTail.length > 40) this.stderrTail.shift();
|
|
42757
|
+
options.onDiagnostic?.({ kind: "stderr", message: line.slice(0, 2e3) });
|
|
42758
|
+
}
|
|
42759
|
+
});
|
|
42760
|
+
}
|
|
42761
|
+
nextId = 1;
|
|
42762
|
+
pending = /* @__PURE__ */ new Map();
|
|
42763
|
+
decoder = new StringDecoder4("utf8");
|
|
42764
|
+
buffer = "";
|
|
42765
|
+
overflowing = false;
|
|
42766
|
+
closedReason;
|
|
42767
|
+
exitPromise;
|
|
42768
|
+
stderrTail = [];
|
|
42769
|
+
/** The last lines the agent wrote to stderr — what a failed dispatch shows the operator. */
|
|
42770
|
+
get recentStderr() {
|
|
42771
|
+
return this.stderrTail;
|
|
42772
|
+
}
|
|
42773
|
+
get closed() {
|
|
42774
|
+
return this.closedReason !== void 0;
|
|
42775
|
+
}
|
|
42776
|
+
/** Resolves when the process has exited (or failed to start). */
|
|
42777
|
+
get exited() {
|
|
42778
|
+
return this.exitPromise;
|
|
42779
|
+
}
|
|
42780
|
+
request(method, params, opts = {}) {
|
|
42781
|
+
if (this.closedReason !== void 0) return Promise.reject(new AcpConnectionClosed(method, this.closedReason));
|
|
42782
|
+
const id = this.nextId++;
|
|
42783
|
+
const timeoutMs = opts.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
42784
|
+
return new Promise((resolve12, reject) => {
|
|
42785
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
42786
|
+
this.pending.delete(id);
|
|
42787
|
+
reject(new AcpRequestTimeout(method, timeoutMs));
|
|
42788
|
+
}, timeoutMs) : void 0;
|
|
42789
|
+
timer?.unref?.();
|
|
42790
|
+
this.pending.set(id, { method, resolve: resolve12, reject, timer });
|
|
42791
|
+
if (!this.write({ jsonrpc: "2.0", id, method, params })) {
|
|
42792
|
+
this.pending.delete(id);
|
|
42793
|
+
if (timer) clearTimeout(timer);
|
|
42794
|
+
reject(new AcpConnectionClosed(method, this.closedReason ?? "stdin closed"));
|
|
42795
|
+
}
|
|
42796
|
+
});
|
|
42797
|
+
}
|
|
42798
|
+
/** A prompt turn is long-lived by design (its response IS the end of the turn), so it is the one
|
|
42799
|
+
* request that must not time out; callers pass `timeoutMs: 0` through this alias for readability. */
|
|
42800
|
+
requestOpenEnded(method, params) {
|
|
42801
|
+
return this.request(method, params, { timeoutMs: 0 });
|
|
42802
|
+
}
|
|
42803
|
+
notify(method, params) {
|
|
42804
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
42805
|
+
}
|
|
42806
|
+
/** Close stdin (the protocol's teardown) and, if the agent has not exited after `graceMs`, kill it. */
|
|
42807
|
+
async close(graceMs = 3e3) {
|
|
42808
|
+
try {
|
|
42809
|
+
this.process.stdin?.end();
|
|
42810
|
+
} catch {
|
|
42811
|
+
}
|
|
42812
|
+
if (this.closedReason !== void 0) return;
|
|
42813
|
+
const exited = await Promise.race([this.exitPromise.then(() => true), new Promise((r) => setTimeout(() => r(false), graceMs).unref?.())]);
|
|
42814
|
+
if (!exited) {
|
|
42815
|
+
try {
|
|
42816
|
+
this.process.kill("SIGKILL");
|
|
42817
|
+
} catch {
|
|
42818
|
+
}
|
|
42819
|
+
}
|
|
42820
|
+
}
|
|
42821
|
+
write(frame) {
|
|
42822
|
+
if (this.closedReason !== void 0) return false;
|
|
42823
|
+
const stdin = this.process.stdin;
|
|
42824
|
+
if (!stdin || stdin.writableEnded || stdin.destroyed) return false;
|
|
42825
|
+
try {
|
|
42826
|
+
stdin.write(JSON.stringify(frame) + "\n");
|
|
42827
|
+
return true;
|
|
42828
|
+
} catch (err) {
|
|
42829
|
+
this.settleClosed(err.message);
|
|
42830
|
+
return false;
|
|
42831
|
+
}
|
|
42832
|
+
}
|
|
42833
|
+
settleClosed(reason) {
|
|
42834
|
+
if (this.closedReason !== void 0) return;
|
|
42835
|
+
this.closedReason = reason;
|
|
42836
|
+
for (const [id, p] of this.pending) {
|
|
42837
|
+
this.pending.delete(id);
|
|
42838
|
+
if (p.timer) clearTimeout(p.timer);
|
|
42839
|
+
p.reject(new AcpConnectionClosed(p.method, reason));
|
|
42840
|
+
}
|
|
42841
|
+
}
|
|
42842
|
+
handleLine(line) {
|
|
42843
|
+
if (!line.trim()) return;
|
|
42844
|
+
let raw2;
|
|
42845
|
+
try {
|
|
42846
|
+
raw2 = JSON.parse(line);
|
|
42847
|
+
} catch {
|
|
42848
|
+
this.options.onDiagnostic?.({ kind: "junk-line", message: line.slice(0, 500) });
|
|
42849
|
+
return;
|
|
42850
|
+
}
|
|
42851
|
+
const parsed = JsonRpcInbound.safeParse(raw2);
|
|
42852
|
+
if (!parsed.success) {
|
|
42853
|
+
this.options.onDiagnostic?.({ kind: "protocol", message: `unrecognized frame: ${line.slice(0, 500)}` });
|
|
42854
|
+
return;
|
|
42855
|
+
}
|
|
42856
|
+
const frame = parsed.data;
|
|
42857
|
+
if (typeof frame.method === "string") {
|
|
42858
|
+
if (frame.id !== void 0) this.handleRequest(frame.id, frame.method, frame.params);
|
|
42859
|
+
else this.options.onNotification(frame.method, frame.params);
|
|
42860
|
+
return;
|
|
42861
|
+
}
|
|
42862
|
+
const id = frame.id;
|
|
42863
|
+
const p = this.pending.get(id);
|
|
42864
|
+
if (!p) {
|
|
42865
|
+
this.options.onDiagnostic?.({ kind: "protocol", message: `response to unknown request id ${String(id)}` });
|
|
42866
|
+
return;
|
|
42867
|
+
}
|
|
42868
|
+
this.pending.delete(id);
|
|
42869
|
+
if (p.timer) clearTimeout(p.timer);
|
|
42870
|
+
if (frame.error !== void 0) p.reject(new AcpRemoteError(p.method, frame.error));
|
|
42871
|
+
else p.resolve(frame.result);
|
|
42872
|
+
}
|
|
42873
|
+
handleRequest(id, method, params) {
|
|
42874
|
+
void this.options.onRequest(method, params).then(
|
|
42875
|
+
(result) => {
|
|
42876
|
+
this.write({ jsonrpc: "2.0", id, result: result ?? {} });
|
|
42877
|
+
},
|
|
42878
|
+
(err) => {
|
|
42879
|
+
const e = err instanceof AcpRequestError ? { code: err.code, message: err.message, ...err.data !== void 0 ? { data: err.data } : {} } : { code: -32603, message: err instanceof Error ? err.message : String(err) };
|
|
42880
|
+
if (!(err instanceof AcpRequestError)) this.options.onDiagnostic?.({ kind: "unhandled-request", message: `${method}: ${e.message}` });
|
|
42881
|
+
this.write({ jsonrpc: "2.0", id, error: e });
|
|
42882
|
+
}
|
|
42883
|
+
);
|
|
42884
|
+
}
|
|
42885
|
+
};
|
|
42886
|
+
}
|
|
42887
|
+
});
|
|
42888
|
+
|
|
42889
|
+
// packages/server/src/backend/acp-bridge.ts
|
|
42890
|
+
function clip3(text2, max) {
|
|
42891
|
+
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
42892
|
+
}
|
|
42893
|
+
function frizzMcpServer(mcp, slug) {
|
|
42894
|
+
if (!mcp) return [];
|
|
42895
|
+
return [{
|
|
42896
|
+
name: FRIZZ_MCP.name,
|
|
42897
|
+
command: process.execPath,
|
|
42898
|
+
args: [mcp.scriptPath],
|
|
42899
|
+
env: Object.entries(frizzMcpEnv({ ...mcp, slug })).map(([name, value]) => ({ name, value }))
|
|
42900
|
+
}];
|
|
42901
|
+
}
|
|
42902
|
+
function toolResultText3(tc) {
|
|
42903
|
+
const parts = [];
|
|
42904
|
+
for (const c of tc.content ?? []) {
|
|
42905
|
+
if (c.type === "content") {
|
|
42906
|
+
const t = contentText(c.content);
|
|
42907
|
+
if (t) parts.push(t);
|
|
42908
|
+
} else if (c.type === "diff") parts.push(`--- ${c.path}
|
|
42909
|
+
${c.newText}`);
|
|
42910
|
+
}
|
|
42911
|
+
if (parts.length) return parts.join("\n");
|
|
42912
|
+
const raw2 = tc.rawOutput;
|
|
42913
|
+
if (raw2 && typeof raw2 === "object" && typeof raw2.output === "string") return raw2.output;
|
|
42914
|
+
if (typeof tc.rawOutput === "string") return tc.rawOutput;
|
|
42915
|
+
return "";
|
|
42916
|
+
}
|
|
42917
|
+
function buildAcpPermissionInteraction(params, owner, agent) {
|
|
42918
|
+
const tc = params.toolCall;
|
|
42919
|
+
const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
|
|
42920
|
+
const command = typeof input?.command === "string" ? input.command : "";
|
|
42921
|
+
const isCommand = tc.kind === "execute" && command !== "";
|
|
42922
|
+
const isFile = (tc.kind === "edit" || tc.kind === "delete" || tc.kind === "move") && (tc.locations?.[0]?.path || typeof input?.filePath === "string" || typeof input?.path === "string");
|
|
42923
|
+
const payloadKind = isCommand ? "command-approval" : isFile ? "file-approval" : "permission-approval";
|
|
42924
|
+
const optionByDecision = /* @__PURE__ */ new Map();
|
|
42925
|
+
const allowedDecisions = [];
|
|
42926
|
+
const add = (id, semantic, label, optionId, description) => {
|
|
42927
|
+
if (optionByDecision.has(id)) return;
|
|
42928
|
+
optionByDecision.set(id, optionId);
|
|
42929
|
+
allowedDecisions.push({ id, semantic, label: clip3(label, 150), ...description ? { description: clip3(description, 500) } : {} });
|
|
42930
|
+
};
|
|
42931
|
+
for (const opt of params.options) {
|
|
42932
|
+
const label = opt.name ?? opt.kind ?? opt.optionId;
|
|
42933
|
+
const once = payloadKind === "permission-approval" ? "grant-turn" : "accept";
|
|
42934
|
+
const always = payloadKind === "permission-approval" ? "grant-session" : "acceptForSession";
|
|
42935
|
+
const deny = payloadKind === "permission-approval" ? "deny" : "decline";
|
|
42936
|
+
if (opt.kind === "allow_once") add(once, "approve", label, opt.optionId, "Allow this once.");
|
|
42937
|
+
else if (opt.kind === "allow_always") add(always, "approve", label, opt.optionId, "Allow for the rest of this session.");
|
|
42938
|
+
else if (opt.kind === "reject_once" || opt.kind === "reject_always") add(deny, "deny", label, opt.optionId, "Block this tool call.");
|
|
42939
|
+
}
|
|
42940
|
+
if (!allowedDecisions.some((d) => d.semantic === "deny")) return null;
|
|
42941
|
+
const title = clip3(tc.title ?? (isCommand ? "Run a command?" : "Approve a tool call?"), 150);
|
|
42942
|
+
const previewLines = [command || "", ...Object.entries(input ?? {}).filter(([k, v]) => k !== "command" && v !== "" && v !== null).map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`)].filter(Boolean);
|
|
42943
|
+
const preview = clip3(redactCredentialSyntax(previewLines.join("\n\n")), 6e3);
|
|
42944
|
+
const pathLabel = clip3(tc.locations?.[0]?.path ?? (typeof input?.filePath === "string" ? input.filePath : typeof input?.path === "string" ? input.path : ""), 1e3);
|
|
42945
|
+
const base = {
|
|
42946
|
+
protocolVersion: INTERACTION_PROTOCOL_VERSION,
|
|
42947
|
+
contentFormat: "plain-text",
|
|
42948
|
+
provider: { kind: "acp", name: agent.label },
|
|
42949
|
+
source: { kind: "tool", id: clip3(tc.toolCallId, 500), label: clip3(tc.title ?? tc.kind ?? "tool", 150) },
|
|
42950
|
+
owner: { projectId: owner.projectId, threadSlug: owner.threadSlug, sessionId: owner.sessionId, turnId: clip3(tc.toolCallId, 500), itemId: clip3(tc.toolCallId, 500), sessionEpoch: 0, capabilityRevision: 0 },
|
|
42951
|
+
providerRequestId: clip3(tc.toolCallId, 500),
|
|
42952
|
+
allowedDecisions,
|
|
42953
|
+
expiresAt: null
|
|
42954
|
+
};
|
|
42955
|
+
const payload = payloadKind === "command-approval" ? { kind: "command-approval", title, command: { summary: clip3(command.replace(/\s+/g, " "), 150), preview, redacted: true, workingDirectoryLabel: clip3(owner.cwd, 1e3) } } : payloadKind === "file-approval" ? { kind: "file-approval", title, operation: tc.kind === "delete" ? "delete" : tc.kind === "move" ? "move" : "write", pathLabel: pathLabel || "(unknown path)", ...preview ? { diffPreview: preview } : {} } : { kind: "permission-approval", title, permission: clip3(tc.kind ?? "tool", 250), ...preview ? { preview } : {}, workingDirectoryLabel: clip3(owner.cwd, 1e3) };
|
|
42956
|
+
const parsed = InteractionRequest.safeParse({ ...base, payload });
|
|
42957
|
+
return parsed.success ? { request: parsed.data, optionByDecision } : null;
|
|
42958
|
+
}
|
|
42959
|
+
function createAcpBridge(options) {
|
|
42960
|
+
return new AcpBridge(options);
|
|
42961
|
+
}
|
|
42962
|
+
var AUTH_HINT, ACP_MODEL_CACHE_MS, AcpBridge;
|
|
42963
|
+
var init_acp_bridge = __esm({
|
|
42964
|
+
"packages/server/src/backend/acp-bridge.ts"() {
|
|
42965
|
+
"use strict";
|
|
42966
|
+
init_src();
|
|
42967
|
+
init_credential_redaction();
|
|
42968
|
+
init_logging();
|
|
42969
|
+
init_acp_agents();
|
|
42970
|
+
init_acp_rpc();
|
|
42971
|
+
init_acp_transcript();
|
|
42972
|
+
init_acp_types();
|
|
42973
|
+
init_types2();
|
|
42974
|
+
init_worker_env();
|
|
42975
|
+
AUTH_HINT = "The agent needs a login first. ";
|
|
42976
|
+
ACP_MODEL_CACHE_MS = 10 * 6e4;
|
|
42977
|
+
AcpBridge = class {
|
|
42978
|
+
constructor(options) {
|
|
42979
|
+
this.options = options;
|
|
42980
|
+
this.unsubscribe = options.interactions?.subscribe((change) => {
|
|
42981
|
+
for (const live of this.sessions.values()) {
|
|
42982
|
+
const pending = live.pendingPerms.get(change.interactionId);
|
|
42983
|
+
if (!pending || change.lifecycle === "pending") continue;
|
|
42984
|
+
live.pendingPerms.delete(change.interactionId);
|
|
42985
|
+
const record = options.interactions.get(pending.scope, change.interactionId);
|
|
42986
|
+
const decisionId = change.lifecycle === "resolved" ? record?.resolution?.decisionId : void 0;
|
|
42987
|
+
const optionId = decisionId ? pending.optionByDecision.get(decisionId) : void 0;
|
|
42988
|
+
pending.resolve(optionId ? { outcome: { outcome: "selected", optionId } } : { outcome: { outcome: "cancelled" } });
|
|
42989
|
+
}
|
|
42990
|
+
});
|
|
42991
|
+
}
|
|
42992
|
+
sessions = /* @__PURE__ */ new Map();
|
|
42993
|
+
unsubscribe;
|
|
42994
|
+
closed = false;
|
|
42995
|
+
now() {
|
|
42996
|
+
return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
42997
|
+
}
|
|
42998
|
+
diagnostic(slug, kind, message) {
|
|
42999
|
+
this.options.onDiagnostic?.({ threadSlug: slug, kind, message });
|
|
43000
|
+
if (kind !== "stderr") log.debug("acp", `[${slug}] ${kind}: ${message}`);
|
|
43001
|
+
}
|
|
43002
|
+
/** The agents this bridge can launch, with the operator's own entries merged in. */
|
|
43003
|
+
agents() {
|
|
43004
|
+
return acpAgentSpecs(this.options.customAgents?.());
|
|
43005
|
+
}
|
|
43006
|
+
resolveAgent(agentId) {
|
|
43007
|
+
const agent = resolveAcpAgent(agentId, this.options.customAgents?.(), this.options.env ?? process.env);
|
|
43008
|
+
if (!agent) throw new Error(`Unknown ACP agent "${agentId}". Add it under settings.acpAgents or pick one of: ${this.agents().map((a) => a.id).join(", ")}.`);
|
|
43009
|
+
if (!agent.bin) throw new Error(`The ACP agent "${agent.label}" is not installed: \`${agent.command}\` is not on PATH. Install it, or point settings.acpAgents at its executable.`);
|
|
43010
|
+
return agent;
|
|
43011
|
+
}
|
|
43012
|
+
// ---- session lifecycle ----------------------------------------------------------------------
|
|
43013
|
+
/** Spawn the agent, initialize, and open (or load) a session. Throws an actionable error on any
|
|
43014
|
+
* failure and leaves nothing behind. */
|
|
43015
|
+
async open(input) {
|
|
43016
|
+
const { threadSlug: slug, sessionId, cwd } = input;
|
|
43017
|
+
const agent = this.resolveAgent(input.agentId);
|
|
43018
|
+
const env = inheritWorkerEnvironment(this.options.env ?? process.env);
|
|
43019
|
+
const spawn8 = this.options.spawn ?? spawnAcpChild;
|
|
43020
|
+
const proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
|
|
43021
|
+
const writer = new AcpTranscriptWriter(acpTranscriptPath(this.options.stateDir, sessionId));
|
|
43022
|
+
let live;
|
|
43023
|
+
const conn = new AcpConnection(proc, {
|
|
43024
|
+
onRequest: (method, params) => live ? this.handleRequest(live, method, params) : Promise.reject(new AcpRequestError(ACP_ERROR_INTERNAL, "session not ready")),
|
|
43025
|
+
onNotification: (method, params) => {
|
|
43026
|
+
if (live && method === "session/update") this.handleNotification(live, params);
|
|
43027
|
+
},
|
|
43028
|
+
onDiagnostic: (d) => this.diagnostic(slug, d.kind, d.message),
|
|
43029
|
+
requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
|
|
43030
|
+
});
|
|
43031
|
+
const fail = async (err) => {
|
|
43032
|
+
await conn.close(500);
|
|
43033
|
+
const stderr = conn.recentStderr.slice(-3).join(" | ");
|
|
43034
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
43035
|
+
throw new Error(`${e.message}${stderr ? ` (agent stderr: ${clip3(stderr, 400)})` : ""}`);
|
|
43036
|
+
};
|
|
43037
|
+
let init;
|
|
43038
|
+
try {
|
|
43039
|
+
init = AcpInitializeResult.parse(await conn.request("initialize", {
|
|
43040
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
43041
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
43042
|
+
clientInfo: { name: "frizz", version: "0" }
|
|
43043
|
+
}));
|
|
43044
|
+
} catch (err) {
|
|
43045
|
+
return fail(err instanceof Error ? new Error(`${agent.label} did not complete the ACP handshake: ${err.message}`) : err);
|
|
43046
|
+
}
|
|
43047
|
+
const mcpServers = frizzMcpServer(this.options.frizzMcp, slug);
|
|
43048
|
+
live = { slug, sessionId, cwd, agent, conn, init, acpSessionId: "", writer, loading: false, queue: [], pendingPerms: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Map(), exited: false };
|
|
43049
|
+
void conn.exited.then(() => {
|
|
43050
|
+
if (live) this.onExit(live);
|
|
43051
|
+
});
|
|
43052
|
+
const authError = (err) => {
|
|
43053
|
+
const hint = (init.authMethods ?? []).map((m) => m.description ?? m.name ?? m.id).filter(Boolean).join("; ");
|
|
43054
|
+
return new Error(`${AUTH_HINT}${agent.label} refused to open a session: ${err.error.message}.${hint ? ` ${hint}.` : ""}`);
|
|
43055
|
+
};
|
|
43056
|
+
let resumed = "fresh";
|
|
43057
|
+
if (input.acpSessionId && init.agentCapabilities?.loadSession) {
|
|
43058
|
+
live.loading = true;
|
|
43059
|
+
try {
|
|
43060
|
+
const loaded = AcpLoadSessionResult.parse(await conn.request("session/load", { sessionId: input.acpSessionId, cwd, mcpServers }));
|
|
43061
|
+
live.acpSessionId = input.acpSessionId;
|
|
43062
|
+
this.adoptModel(live, loaded.configOptions);
|
|
43063
|
+
resumed = "loaded";
|
|
43064
|
+
} catch (err) {
|
|
43065
|
+
if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
|
|
43066
|
+
this.diagnostic(slug, "protocol", `session/load ${input.acpSessionId} failed (${err.message}); starting a fresh session`);
|
|
43067
|
+
} finally {
|
|
43068
|
+
live.loading = false;
|
|
43069
|
+
}
|
|
43070
|
+
}
|
|
43071
|
+
if (!live.acpSessionId) {
|
|
43072
|
+
try {
|
|
43073
|
+
const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers }));
|
|
43074
|
+
live.acpSessionId = created.sessionId;
|
|
43075
|
+
this.adoptModel(live, created.configOptions);
|
|
43076
|
+
} catch (err) {
|
|
43077
|
+
if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
|
|
43078
|
+
return fail(err instanceof Error ? new Error(`${agent.label} could not open a session: ${err.message}`) : err);
|
|
43079
|
+
}
|
|
43080
|
+
}
|
|
43081
|
+
const modelProblem = await this.applyModel(live, input.modelId);
|
|
43082
|
+
if (resumed === "fresh") {
|
|
43083
|
+
writer.append({ kind: "acp-session", at: this.now(), agent: { id: agent.id, ...init.agentInfo?.name ? { name: init.agentInfo.name } : {}, ...init.agentInfo?.version ? { version: init.agentInfo.version } : {} }, acpSessionId: live.acpSessionId, cwd, ...live.model ? { model: live.model } : {} });
|
|
43084
|
+
if (input.acpSessionId) writer.append({ kind: "acp-note", at: this.now(), text: `Resumed with a fresh ${agent.label} session: the previous one (${input.acpSessionId}) could not be loaded, so the agent no longer has the earlier conversation.` });
|
|
43085
|
+
}
|
|
43086
|
+
if (modelProblem) writer.append({ kind: "acp-note", at: this.now(), text: modelProblem });
|
|
43087
|
+
this.sessions.set(sessionId, live);
|
|
43088
|
+
return { live, resumed };
|
|
43089
|
+
}
|
|
43090
|
+
/** The agent's model option (`category: "model"`, else the one literally named `model`). */
|
|
43091
|
+
modelOption(configOptions) {
|
|
43092
|
+
return configOptions?.find((o) => o.category === "model") ?? configOptions?.find((o) => o.id === "model");
|
|
43093
|
+
}
|
|
43094
|
+
adoptModel(live, configOptions) {
|
|
43095
|
+
const option = this.modelOption(configOptions);
|
|
43096
|
+
if (!option) return;
|
|
43097
|
+
live.modelOptionId = option.id;
|
|
43098
|
+
if (typeof option.currentValue === "string") live.model = option.currentValue;
|
|
43099
|
+
}
|
|
43100
|
+
/** Ask the agent for the thread's model when it is not already the one in effect. Returns the note
|
|
43101
|
+
* to write when the agent refused — the thread runs on, on the agent's own model, and says so. */
|
|
43102
|
+
async applyModel(live, modelId) {
|
|
43103
|
+
if (!modelId || modelId === live.model) return void 0;
|
|
43104
|
+
try {
|
|
43105
|
+
const res = AcpSetConfigOptionResult.parse(await live.conn.request("session/set_config_option", { sessionId: live.acpSessionId, configId: live.modelOptionId ?? "model", value: modelId }));
|
|
43106
|
+
this.adoptModel(live, res.configOptions);
|
|
43107
|
+
if (live.model !== modelId && res.configOptions?.length) return `${live.agent.label} did not switch to ${modelId}; the session runs on ${live.model ?? "its own default model"}.`;
|
|
43108
|
+
if (!res.configOptions?.length) live.model = modelId;
|
|
43109
|
+
return void 0;
|
|
43110
|
+
} catch (err) {
|
|
43111
|
+
const message = err instanceof AcpRemoteError ? err.error.message : err.message;
|
|
43112
|
+
this.diagnostic(live.slug, "protocol", `session/set_config_option ${modelId} failed: ${message}`);
|
|
43113
|
+
return `${live.agent.label} could not switch to ${modelId} (${clip3(message, 200)}); the session runs on ${live.model ?? "its own default model"}.`;
|
|
43114
|
+
}
|
|
43115
|
+
}
|
|
43116
|
+
// ---- the model catalogue an agent advertises -----------------------------------------------------
|
|
43117
|
+
modelProbes = /* @__PURE__ */ new Map();
|
|
43118
|
+
modelCache = /* @__PURE__ */ new Map();
|
|
43119
|
+
/** The models an agent offers, read by opening a throwaway session (there is no other way to ask:
|
|
43120
|
+
* ACP advertises models only in a session's config options). Cached per agent for ten minutes; a
|
|
43121
|
+
* probe that cannot open a session (not installed, not logged in) yields an empty list with the
|
|
43122
|
+
* reason, never a throw, so the composer degrades to the agent's own default. */
|
|
43123
|
+
agentModels(agentId, cwd, opts = {}) {
|
|
43124
|
+
const cached2 = this.modelCache.get(agentId);
|
|
43125
|
+
if (cached2 && !opts.refresh && Date.now() - Date.parse(cached2.probedAt) < ACP_MODEL_CACHE_MS) return Promise.resolve(cached2);
|
|
43126
|
+
const inflight = this.modelProbes.get(agentId);
|
|
43127
|
+
if (inflight) return inflight;
|
|
43128
|
+
const probe = this.probeModels(agentId, cwd).then((result) => {
|
|
43129
|
+
if (!result.error) this.modelCache.set(agentId, result);
|
|
43130
|
+
return result;
|
|
43131
|
+
}).finally(() => this.modelProbes.delete(agentId));
|
|
43132
|
+
this.modelProbes.set(agentId, probe);
|
|
43133
|
+
return probe;
|
|
43134
|
+
}
|
|
43135
|
+
async probeModels(agentId, cwd) {
|
|
43136
|
+
const probedAt = this.now();
|
|
43137
|
+
const empty = (error) => ({ agentId, models: [], error, probedAt });
|
|
43138
|
+
let agent;
|
|
43139
|
+
try {
|
|
43140
|
+
agent = this.resolveAgent(agentId);
|
|
43141
|
+
} catch (err) {
|
|
43142
|
+
return empty(err.message);
|
|
43143
|
+
}
|
|
43144
|
+
const env = inheritWorkerEnvironment(this.options.env ?? process.env);
|
|
43145
|
+
const spawn8 = this.options.spawn ?? spawnAcpChild;
|
|
43146
|
+
let proc;
|
|
43147
|
+
try {
|
|
43148
|
+
proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
|
|
43149
|
+
} catch (err) {
|
|
43150
|
+
return empty(`${agent.label} could not start: ${err.message}`);
|
|
43151
|
+
}
|
|
43152
|
+
const conn = new AcpConnection(proc, {
|
|
43153
|
+
onRequest: () => Promise.reject(new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, "probe session takes no requests")),
|
|
43154
|
+
onNotification: () => {
|
|
43155
|
+
},
|
|
43156
|
+
onDiagnostic: (d) => this.diagnostic(`probe:${agentId}`, d.kind, d.message),
|
|
43157
|
+
requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
|
|
43158
|
+
});
|
|
43159
|
+
try {
|
|
43160
|
+
const init = AcpInitializeResult.parse(await conn.request("initialize", {
|
|
43161
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
43162
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
43163
|
+
clientInfo: { name: "frizz", version: "0" }
|
|
43164
|
+
}));
|
|
43165
|
+
const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers: [] }));
|
|
43166
|
+
const option = this.modelOption(created.configOptions);
|
|
43167
|
+
const current = typeof option?.currentValue === "string" ? option.currentValue : void 0;
|
|
43168
|
+
void init;
|
|
43169
|
+
return {
|
|
43170
|
+
agentId,
|
|
43171
|
+
models: (option?.options ?? []).map((o) => ({ id: o.value, name: o.name ?? o.value })),
|
|
43172
|
+
...current ? { current } : {},
|
|
43173
|
+
probedAt
|
|
43174
|
+
};
|
|
43175
|
+
} catch (err) {
|
|
43176
|
+
const message = err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED ? `${AUTH_HINT}${agent.label} refused to open a session: ${err.error.message}` : `${agent.label}: ${err.message}`;
|
|
43177
|
+
return empty(clip3(message, 400));
|
|
43178
|
+
} finally {
|
|
43179
|
+
await conn.close(500);
|
|
43180
|
+
}
|
|
43181
|
+
}
|
|
43182
|
+
onExit(live) {
|
|
43183
|
+
if (live.exited) return;
|
|
43184
|
+
live.exited = true;
|
|
43185
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43186
|
+
if (live.turn) {
|
|
43187
|
+
}
|
|
43188
|
+
if (this.sessions.get(live.sessionId) === live) this.sessions.delete(live.sessionId);
|
|
43189
|
+
this.options.onStatusChange?.();
|
|
43190
|
+
}
|
|
43191
|
+
// ---- public API --------------------------------------------------------------------------------
|
|
43192
|
+
async spawnDispatch(input) {
|
|
43193
|
+
if (this.closed) throw new Error("ACP bridge is closed");
|
|
43194
|
+
const { live } = await this.open(input);
|
|
43195
|
+
void this.runTurn(live, input.prompt, input.userText, void 0);
|
|
43196
|
+
return this.info(live);
|
|
43197
|
+
}
|
|
43198
|
+
/** Deliver a follow-up: to the live session, to a re-opened one (load or fresh), or into the queue
|
|
43199
|
+
* when a turn is running. Returns the (possibly new) ACP session id so the caller can re-pin it. */
|
|
43200
|
+
async followUp(input) {
|
|
43201
|
+
if (this.closed) throw new Error("ACP bridge is closed");
|
|
43202
|
+
let live = this.sessions.get(input.sessionId);
|
|
43203
|
+
let resumed = "live";
|
|
43204
|
+
if (!live || live.exited || live.conn.closed) {
|
|
43205
|
+
const opened = await this.open(input);
|
|
43206
|
+
live = opened.live;
|
|
43207
|
+
resumed = opened.resumed;
|
|
43208
|
+
}
|
|
43209
|
+
if (live.turn) {
|
|
43210
|
+
live.queue.push({ text: input.text, ...input.deliveryId ? { deliveryId: input.deliveryId } : {} });
|
|
43211
|
+
return { ...this.info(live), state: "queued", resumed };
|
|
43212
|
+
}
|
|
43213
|
+
void this.runTurn(live, input.text, input.text, input.deliveryId);
|
|
43214
|
+
return { ...this.info(live), state: "delivered", resumed };
|
|
43215
|
+
}
|
|
43216
|
+
/** `session/cancel`, then wait (bounded) for the prompt to return. `interrupted: false` when no turn
|
|
43217
|
+
* was running. The queue is NOT discarded — a follow-up typed during the turn still goes out. */
|
|
43218
|
+
async interruptTurn(threadSlug, sessionId) {
|
|
43219
|
+
const live = this.sessions.get(sessionId);
|
|
43220
|
+
if (!live || live.slug !== threadSlug || !live.turn) return { interrupted: false };
|
|
43221
|
+
live.turn.cancelRequested = true;
|
|
43222
|
+
live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
|
|
43223
|
+
const settled = await Promise.race([live.turn.done.then(() => true), new Promise((r) => setTimeout(() => r(false), 15e3).unref?.())]);
|
|
43224
|
+
if (!settled) {
|
|
43225
|
+
await live.conn.close(1e3);
|
|
43226
|
+
return { interrupted: true };
|
|
43227
|
+
}
|
|
43228
|
+
return { interrupted: true };
|
|
43229
|
+
}
|
|
43230
|
+
releaseSession(threadSlug, sessionId, reason) {
|
|
43231
|
+
const live = this.sessions.get(sessionId);
|
|
43232
|
+
if (!live || live.slug !== threadSlug) return;
|
|
43233
|
+
this.sessions.delete(sessionId);
|
|
43234
|
+
live.queue.length = 0;
|
|
43235
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43236
|
+
if (live.turn) live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
|
|
43237
|
+
void live.conn.close(reason === "shutdown" ? 1e3 : 3e3);
|
|
43238
|
+
}
|
|
43239
|
+
turnLiveness(threadSlug, sessionId) {
|
|
43240
|
+
const live = this.sessions.get(sessionId);
|
|
43241
|
+
if (!live || live.slug !== threadSlug) return void 0;
|
|
43242
|
+
return { sessionLive: !live.exited && !live.conn.closed, turnActive: live.turn !== void 0, queued: live.queue.length };
|
|
43243
|
+
}
|
|
43244
|
+
session(threadSlug, sessionId) {
|
|
43245
|
+
const live = this.sessions.get(sessionId);
|
|
43246
|
+
return live && live.slug === threadSlug ? this.info(live) : void 0;
|
|
43247
|
+
}
|
|
43248
|
+
ownsInteraction(scope, interactionId) {
|
|
43249
|
+
const live = this.sessions.get(scope.sessionId);
|
|
43250
|
+
return live !== void 0 && live.slug === scope.threadSlug && live.pendingPerms.has(interactionId);
|
|
43251
|
+
}
|
|
43252
|
+
async shutdown() {
|
|
43253
|
+
this.closed = true;
|
|
43254
|
+
this.unsubscribe?.();
|
|
43255
|
+
const all = [...this.sessions.values()];
|
|
43256
|
+
this.sessions.clear();
|
|
43257
|
+
await Promise.all(all.map(async (live) => {
|
|
43258
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43259
|
+
await live.conn.close(1e3);
|
|
43260
|
+
}));
|
|
43261
|
+
}
|
|
43262
|
+
info(live) {
|
|
43263
|
+
return {
|
|
43264
|
+
acpSessionId: live.acpSessionId,
|
|
43265
|
+
agent: { id: live.agent.id, ...live.init.agentInfo?.name ? { name: live.init.agentInfo.name } : {}, ...live.init.agentInfo?.version ? { version: live.init.agentInfo.version } : {} },
|
|
43266
|
+
...live.model ? { model: live.model } : {}
|
|
43267
|
+
};
|
|
43268
|
+
}
|
|
43269
|
+
// ---- the turn ---------------------------------------------------------------------------------
|
|
43270
|
+
async runTurn(live, sendText, recordText, deliveryId) {
|
|
43271
|
+
let finish;
|
|
43272
|
+
const done = new Promise((r) => {
|
|
43273
|
+
finish = r;
|
|
43274
|
+
});
|
|
43275
|
+
const turn = { startedAt: this.now(), ...deliveryId ? { deliveryId } : {}, text: "", messageText: "", thought: "", cancelRequested: false, done, finish };
|
|
43276
|
+
live.turn = turn;
|
|
43277
|
+
live.writer.append({ kind: "user-message", at: turn.startedAt, text: recordText, synthetic: false });
|
|
43278
|
+
live.writer.append({ kind: "turn-start", at: turn.startedAt });
|
|
43279
|
+
this.options.onStatusChange?.();
|
|
43280
|
+
try {
|
|
43281
|
+
const raw2 = await live.conn.requestOpenEnded("session/prompt", { sessionId: live.acpSessionId, prompt: [{ type: "text", text: sendText }] });
|
|
43282
|
+
const result = AcpPromptResult.parse(raw2);
|
|
43283
|
+
this.flushThought(live);
|
|
43284
|
+
const finalText = this.flushText(live, true);
|
|
43285
|
+
live.writer.append({ kind: "turn-end", at: this.now(), ...finalText !== void 0 ? { finalText } : {}, successful: result.stopReason !== "refusal" && result.stopReason !== "cancelled" });
|
|
43286
|
+
if (result.stopReason === "cancelled") live.writer.append({ kind: "acp-note", at: this.now(), text: "Turn cancelled." });
|
|
43287
|
+
} catch (err) {
|
|
43288
|
+
this.flushThought(live);
|
|
43289
|
+
const finalText = this.flushText(live, true);
|
|
43290
|
+
const at = this.now();
|
|
43291
|
+
const abortedByUs = turn.cancelRequested && err instanceof AcpRemoteError && err.error.code === ACP_ERROR_INTERNAL;
|
|
43292
|
+
if (abortedByUs) {
|
|
43293
|
+
live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
|
|
43294
|
+
live.writer.append({ kind: "acp-note", at, text: "Turn cancelled." });
|
|
43295
|
+
} else {
|
|
43296
|
+
const message = err instanceof AcpRemoteError ? err.error.code === ACP_ERROR_AUTH_REQUIRED ? `${AUTH_HINT}${err.error.message}` : err.error.message : err.message;
|
|
43297
|
+
live.writer.append({ kind: "provider-error", at, error: { message: clip3(message, 2e3), ...err instanceof AcpRemoteError ? { code: String(err.error.code) } : {} } });
|
|
43298
|
+
live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
|
|
43299
|
+
}
|
|
43300
|
+
} finally {
|
|
43301
|
+
this.retirePermissions(live, "turn-ended");
|
|
43302
|
+
if (live.turn === turn) live.turn = void 0;
|
|
43303
|
+
turn.finish();
|
|
43304
|
+
this.options.onStatusChange?.();
|
|
43305
|
+
const next = live.queue.shift();
|
|
43306
|
+
if (next && !live.exited && !live.conn.closed) void this.runTurn(live, next.text, next.text, next.deliveryId);
|
|
43307
|
+
}
|
|
43308
|
+
}
|
|
43309
|
+
/** Write the buffered text as one record. Returns, for a FINAL flush, the whole text of the agent's
|
|
43310
|
+
* last message (every partial flush included) — what the turn-end carries as `finalText`, so the
|
|
43311
|
+
* fold's preview and fence parsing see the complete message, never the last chunk. */
|
|
43312
|
+
flushText(live, final) {
|
|
43313
|
+
const turn = live.turn;
|
|
43314
|
+
if (!turn) return void 0;
|
|
43315
|
+
if (turn.flushTimer) {
|
|
43316
|
+
clearTimeout(turn.flushTimer);
|
|
43317
|
+
turn.flushTimer = void 0;
|
|
43318
|
+
}
|
|
43319
|
+
if (turn.text) {
|
|
43320
|
+
const text2 = turn.text;
|
|
43321
|
+
turn.text = "";
|
|
43322
|
+
live.writer.append({ kind: "assistant-text", at: this.now(), text: text2, final, ...turn.messageId ? { messageId: turn.messageId } : {} });
|
|
43323
|
+
}
|
|
43324
|
+
return final && turn.messageText ? turn.messageText : void 0;
|
|
43325
|
+
}
|
|
43326
|
+
flushThought(live) {
|
|
43327
|
+
const turn = live.turn;
|
|
43328
|
+
if (!turn?.thought.trim()) {
|
|
43329
|
+
if (turn) turn.thought = "";
|
|
43330
|
+
return;
|
|
43331
|
+
}
|
|
43332
|
+
live.writer.append({ kind: "reasoning", at: this.now(), text: turn.thought });
|
|
43333
|
+
turn.thought = "";
|
|
43334
|
+
}
|
|
43335
|
+
scheduleFlush(live) {
|
|
43336
|
+
const turn = live.turn;
|
|
43337
|
+
if (!turn || turn.flushTimer) return;
|
|
43338
|
+
turn.flushTimer = setTimeout(() => {
|
|
43339
|
+
turn.flushTimer = void 0;
|
|
43340
|
+
this.flushText(live, false);
|
|
43341
|
+
}, this.options.flushMs ?? 1e3);
|
|
43342
|
+
turn.flushTimer.unref?.();
|
|
43343
|
+
}
|
|
43344
|
+
handleNotification(live, params) {
|
|
43345
|
+
if (live.loading) return;
|
|
43346
|
+
const parsed = AcpSessionNotification.safeParse(params);
|
|
43347
|
+
if (!parsed.success) {
|
|
43348
|
+
this.diagnostic(live.slug, "protocol", "malformed session/update");
|
|
43349
|
+
return;
|
|
43350
|
+
}
|
|
43351
|
+
const update = parseSessionUpdate(parsed.data.update);
|
|
43352
|
+
const turn = live.turn;
|
|
43353
|
+
switch (update.sessionUpdate) {
|
|
43354
|
+
case "agent_thought_chunk": {
|
|
43355
|
+
if (!turn) return;
|
|
43356
|
+
const t = contentText(update.content);
|
|
43357
|
+
if (t) turn.thought += t;
|
|
43358
|
+
return;
|
|
43359
|
+
}
|
|
43360
|
+
case "agent_message_chunk": {
|
|
43361
|
+
if (!turn) return;
|
|
43362
|
+
this.flushThought(live);
|
|
43363
|
+
const t = contentText(update.content);
|
|
43364
|
+
if (!t) return;
|
|
43365
|
+
if (update.messageId && turn.messageId && update.messageId !== turn.messageId) {
|
|
43366
|
+
this.flushText(live, false);
|
|
43367
|
+
turn.messageText = "";
|
|
43368
|
+
}
|
|
43369
|
+
if (update.messageId) turn.messageId = update.messageId;
|
|
43370
|
+
turn.text += t;
|
|
43371
|
+
turn.messageText += t;
|
|
43372
|
+
this.scheduleFlush(live);
|
|
43373
|
+
return;
|
|
43374
|
+
}
|
|
43375
|
+
case "tool_call": {
|
|
43376
|
+
this.flushThought(live);
|
|
43377
|
+
this.flushText(live, false);
|
|
43378
|
+
if (turn) turn.messageText = "";
|
|
43379
|
+
const meta = { ...update.kind ? { kind: update.kind } : {}, ...update.title ? { title: update.title } : {}, locations: (update.locations ?? []).map((l) => l.path), written: true };
|
|
43380
|
+
live.tools.set(update.toolCallId, meta);
|
|
43381
|
+
live.writer.append({ kind: "tool-call", at: this.now(), id: update.toolCallId, name: update.title ?? update.kind ?? "tool", input: update.rawInput ?? {}, acp: { ...meta.kind ? { kind: meta.kind } : {}, ...meta.title ? { title: meta.title } : {}, locations: meta.locations ?? [] } });
|
|
43382
|
+
if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
|
|
43383
|
+
return;
|
|
43384
|
+
}
|
|
43385
|
+
case "tool_call_update": {
|
|
43386
|
+
let meta = live.tools.get(update.toolCallId);
|
|
43387
|
+
if (!meta) {
|
|
43388
|
+
meta = { written: false };
|
|
43389
|
+
live.tools.set(update.toolCallId, meta);
|
|
43390
|
+
}
|
|
43391
|
+
if (update.kind) meta.kind = update.kind;
|
|
43392
|
+
if (update.title) meta.title = update.title;
|
|
43393
|
+
if (update.locations?.length) meta.locations = update.locations.map((l) => l.path);
|
|
43394
|
+
if (!meta.written) {
|
|
43395
|
+
this.flushThought(live);
|
|
43396
|
+
this.flushText(live, false);
|
|
43397
|
+
meta.written = true;
|
|
43398
|
+
live.writer.append({ kind: "tool-call", at: this.now(), id: update.toolCallId, name: meta.title ?? meta.kind ?? "tool", input: update.rawInput ?? {}, acp: { ...meta.kind ? { kind: meta.kind } : {}, ...meta.title ? { title: meta.title } : {}, locations: meta.locations ?? [] } });
|
|
43399
|
+
}
|
|
43400
|
+
if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
|
|
43401
|
+
return;
|
|
43402
|
+
}
|
|
43403
|
+
case "usage_update":
|
|
43404
|
+
this.flushText(live, false);
|
|
43405
|
+
if (update.used > 0) live.writer.append({ kind: "context-usage", at: this.now(), tokens: update.used, window: update.size });
|
|
43406
|
+
return;
|
|
43407
|
+
case "session_info_update":
|
|
43408
|
+
if (update.title) live.writer.append({ kind: "title", title: clip3(update.title, 200) });
|
|
43409
|
+
return;
|
|
43410
|
+
case "config_option_update":
|
|
43411
|
+
this.adoptModel(live, update.configOptions);
|
|
43412
|
+
return;
|
|
43413
|
+
case "plan":
|
|
43414
|
+
case "user_message_chunk":
|
|
43415
|
+
case "compaction_update":
|
|
43416
|
+
return;
|
|
43417
|
+
case "unknown":
|
|
43418
|
+
this.diagnostic(live.slug, "update", `unhandled session/update ${String(update.raw?.sessionUpdate)}`);
|
|
43419
|
+
return;
|
|
43420
|
+
}
|
|
43421
|
+
}
|
|
43422
|
+
recordToolResult(live, tc) {
|
|
43423
|
+
const meta = live.tools.get(tc.toolCallId);
|
|
43424
|
+
if (meta?.status === "completed" || meta?.status === "failed") return;
|
|
43425
|
+
if (meta) meta.status = tc.status;
|
|
43426
|
+
const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
|
|
43427
|
+
live.writer.append({ kind: "tool-result", at: this.now(), id: tc.toolCallId, text: clip3(toolResultText3(tc), 2e4), acp: { ...tc.status ? { kind: tc.status } : {}, ...meta?.locations?.length ? { locations: meta.locations } : {}, ...input && Object.keys(input).length ? { input } : {} } });
|
|
43428
|
+
}
|
|
43429
|
+
// ---- the agent asking us ------------------------------------------------------------------------
|
|
43430
|
+
async handleRequest(live, method, params) {
|
|
43431
|
+
if (method === "session/request_permission") return this.handlePermission(live, params);
|
|
43432
|
+
throw new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, `frizz does not implement ${method}`);
|
|
43433
|
+
}
|
|
43434
|
+
handlePermission(live, params) {
|
|
43435
|
+
const parsed = AcpRequestPermissionParams.safeParse(params);
|
|
43436
|
+
const cancelled = { outcome: { outcome: "cancelled" } };
|
|
43437
|
+
if (!parsed.success) return Promise.resolve(cancelled);
|
|
43438
|
+
const store = this.options.interactions;
|
|
43439
|
+
const reject = parsed.data.options.find((o) => o.kind === "reject_once") ?? parsed.data.options.find((o) => o.kind === "reject_always");
|
|
43440
|
+
const failClosed = reject ? { outcome: { outcome: "selected", optionId: reject.optionId } } : cancelled;
|
|
43441
|
+
if (!store) {
|
|
43442
|
+
this.diagnostic(live.slug, "permission", "no interaction store; denying the request");
|
|
43443
|
+
return Promise.resolve(failClosed);
|
|
43444
|
+
}
|
|
43445
|
+
const built = buildAcpPermissionInteraction(parsed.data, { projectId: this.options.projectId, threadSlug: live.slug, sessionId: live.sessionId, cwd: live.cwd }, { id: live.agent.id, label: live.agent.label });
|
|
43446
|
+
if (!built) {
|
|
43447
|
+
this.diagnostic(live.slug, "permission", "request could not be represented as a card; denying");
|
|
43448
|
+
return Promise.resolve(failClosed);
|
|
43449
|
+
}
|
|
43450
|
+
const scope = { projectId: this.options.projectId, threadSlug: live.slug, sessionId: live.sessionId };
|
|
43451
|
+
let id;
|
|
43452
|
+
try {
|
|
43453
|
+
id = store.create(built.request).interaction.id;
|
|
43454
|
+
} catch (err) {
|
|
43455
|
+
this.diagnostic(live.slug, "permission", `could not record the approval: ${err.message}`);
|
|
43456
|
+
return Promise.resolve(failClosed);
|
|
43457
|
+
}
|
|
43458
|
+
return new Promise((resolve12) => {
|
|
43459
|
+
live.pendingPerms.set(id, { resolve: resolve12, optionByDecision: built.optionByDecision, scope });
|
|
43460
|
+
this.options.onStatusChange?.();
|
|
43461
|
+
});
|
|
43462
|
+
}
|
|
43463
|
+
/** Cancel every open card for a session that can no longer answer one, answering the agent
|
|
43464
|
+
* `cancelled` for each. Hygiene, so it never throws. */
|
|
43465
|
+
retirePermissions(live, reason) {
|
|
43466
|
+
if (!live.pendingPerms.size) return;
|
|
43467
|
+
const store = this.options.interactions;
|
|
43468
|
+
let swept = [];
|
|
43469
|
+
try {
|
|
43470
|
+
swept = store?.cancelForSession(live.slug, live.sessionId, reason) ?? [];
|
|
43471
|
+
} catch {
|
|
43472
|
+
}
|
|
43473
|
+
for (const record of swept) live.pendingPerms.get(record.id)?.resolve({ outcome: { outcome: "cancelled" } });
|
|
43474
|
+
for (const [id, pending] of live.pendingPerms) {
|
|
43475
|
+
pending.resolve({ outcome: { outcome: "cancelled" } });
|
|
43476
|
+
live.pendingPerms.delete(id);
|
|
43477
|
+
}
|
|
43478
|
+
}
|
|
43479
|
+
};
|
|
43480
|
+
}
|
|
43481
|
+
});
|
|
43482
|
+
|
|
41980
43483
|
// packages/server/src/login-utility.ts
|
|
41981
43484
|
import { randomBytes } from "node:crypto";
|
|
41982
43485
|
import pty from "node-pty";
|
|
@@ -42154,27 +43657,27 @@ var init_login_utility = __esm({
|
|
|
42154
43657
|
});
|
|
42155
43658
|
|
|
42156
43659
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
42157
|
-
import { appendFileSync as
|
|
42158
|
-
import { join as
|
|
43660
|
+
import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync16 } from "node:fs";
|
|
43661
|
+
import { join as join35 } from "node:path";
|
|
42159
43662
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
42160
|
-
return
|
|
43663
|
+
return join35(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
42161
43664
|
}
|
|
42162
43665
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
42163
|
-
const dir =
|
|
43666
|
+
const dir = join35(stateDir, "codex-app-server");
|
|
42164
43667
|
const path3 = codexDiagnosticLogPath(stateDir, projectId);
|
|
42165
43668
|
let ensured = false;
|
|
42166
43669
|
return (event) => {
|
|
42167
43670
|
if (event.event === "stderr") return;
|
|
42168
43671
|
try {
|
|
42169
43672
|
if (!ensured) {
|
|
42170
|
-
|
|
43673
|
+
mkdirSync15(dir, { recursive: true });
|
|
42171
43674
|
ensured = true;
|
|
42172
43675
|
}
|
|
42173
43676
|
try {
|
|
42174
|
-
if (
|
|
43677
|
+
if (statSync16(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
|
|
42175
43678
|
} catch {
|
|
42176
43679
|
}
|
|
42177
|
-
|
|
43680
|
+
appendFileSync4(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
|
|
42178
43681
|
`);
|
|
42179
43682
|
} catch {
|
|
42180
43683
|
}
|
|
@@ -42189,10 +43692,10 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
42189
43692
|
});
|
|
42190
43693
|
|
|
42191
43694
|
// packages/server/src/backend/stale-socket-sweep.ts
|
|
42192
|
-
import { execFile as
|
|
43695
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
42193
43696
|
import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
|
|
42194
43697
|
import { connect as connectSocket } from "node:net";
|
|
42195
|
-
import { join as
|
|
43698
|
+
import { join as join36 } from "node:path";
|
|
42196
43699
|
function parseLsofSocketNames(stdout) {
|
|
42197
43700
|
const referenced = /* @__PURE__ */ new Set();
|
|
42198
43701
|
for (const line of stdout.split("\n")) {
|
|
@@ -42209,7 +43712,7 @@ function sweepStaleSockets(options, deps = {}) {
|
|
|
42209
43712
|
const keep = new Set(options.keep ?? []);
|
|
42210
43713
|
let candidates;
|
|
42211
43714
|
try {
|
|
42212
|
-
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) =>
|
|
43715
|
+
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) => join36(options.dir, name)).filter((path3) => !keep.has(path3));
|
|
42213
43716
|
} catch {
|
|
42214
43717
|
return;
|
|
42215
43718
|
}
|
|
@@ -42236,7 +43739,7 @@ var init_stale_socket_sweep = __esm({
|
|
|
42236
43739
|
"packages/server/src/backend/stale-socket-sweep.ts"() {
|
|
42237
43740
|
"use strict";
|
|
42238
43741
|
defaultListReferenced = (prefix, done) => {
|
|
42239
|
-
|
|
43742
|
+
execFile9("lsof", ["-U", "-F", "n"], { timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
|
|
42240
43743
|
if (!stdout) return done(null);
|
|
42241
43744
|
if (error && !stdout.includes(prefix)) return done(null);
|
|
42242
43745
|
done(parseLsofSocketNames(stdout));
|
|
@@ -42259,8 +43762,8 @@ var init_stale_socket_sweep = __esm({
|
|
|
42259
43762
|
|
|
42260
43763
|
// packages/server/src/orphan-reaper.ts
|
|
42261
43764
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
42262
|
-
import { readFileSync as
|
|
42263
|
-
import { dirname as
|
|
43765
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
43766
|
+
import { dirname as dirname15 } from "node:path";
|
|
42264
43767
|
function firstTokenBasename(command) {
|
|
42265
43768
|
const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
42266
43769
|
const slash = first.lastIndexOf("/");
|
|
@@ -42488,7 +43991,7 @@ function sweepOrphansOnce(deps = {}) {
|
|
|
42488
43991
|
}
|
|
42489
43992
|
function sweepStaleBrokerSockets() {
|
|
42490
43993
|
try {
|
|
42491
|
-
sweepStaleSockets({ dir:
|
|
43994
|
+
sweepStaleSockets({ dir: dirname15(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
|
|
42492
43995
|
} catch {
|
|
42493
43996
|
}
|
|
42494
43997
|
}
|
|
@@ -42538,7 +44041,7 @@ var init_orphan_reaper = __esm({
|
|
|
42538
44041
|
defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
|
|
42539
44042
|
defaultReadEnv = (pid) => {
|
|
42540
44043
|
try {
|
|
42541
|
-
return
|
|
44044
|
+
return readFileSync22(`/proc/${pid}/environ`, "utf8");
|
|
42542
44045
|
} catch {
|
|
42543
44046
|
return null;
|
|
42544
44047
|
}
|
|
@@ -42686,7 +44189,7 @@ var init_thread_hibernation = __esm({
|
|
|
42686
44189
|
});
|
|
42687
44190
|
|
|
42688
44191
|
// packages/server/src/context.ts
|
|
42689
|
-
import { join as
|
|
44192
|
+
import { join as join37 } from "node:path";
|
|
42690
44193
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
42691
44194
|
import { homedir as homedir15 } from "node:os";
|
|
42692
44195
|
function reconcileSessions(storage) {
|
|
@@ -42713,6 +44216,9 @@ function partialContextCleanup(resources) {
|
|
|
42713
44216
|
codexAppServer: createRetryableCleanup(async () => {
|
|
42714
44217
|
await resources.codexAppServer?.shutdown();
|
|
42715
44218
|
}),
|
|
44219
|
+
acpBridge: createRetryableCleanup(async () => {
|
|
44220
|
+
await resources.acpBridge?.shutdown();
|
|
44221
|
+
}),
|
|
42716
44222
|
claudeBroker: createRetryableCleanup(async () => {
|
|
42717
44223
|
resources.claudeBroker?.close();
|
|
42718
44224
|
resources.claudeRuntimeIngest?.close();
|
|
@@ -42836,7 +44342,7 @@ async function createContext(opts = {}) {
|
|
|
42836
44342
|
function createContextUnchecked(opts, resources) {
|
|
42837
44343
|
const home = opts.home ?? homedir15();
|
|
42838
44344
|
const project = opts.project ?? resolveProject();
|
|
42839
|
-
const storage = createStorage(opts.database ??
|
|
44345
|
+
const storage = createStorage(opts.database ?? join37(project.stateDir, "ui.db"), project.id);
|
|
42840
44346
|
resources.storage = storage;
|
|
42841
44347
|
const bus = new Bus();
|
|
42842
44348
|
const transcriptChange = new Emitter();
|
|
@@ -42911,7 +44417,8 @@ function createContextUnchecked(opts, resources) {
|
|
|
42911
44417
|
};
|
|
42912
44418
|
const claudeBackend = createClaudeBackend({ logDir: defaultLogDir(project), claudeBin: opts.claudeBin });
|
|
42913
44419
|
const codexBackend = createCodexBackend({});
|
|
42914
|
-
const
|
|
44420
|
+
const acpBackend = createAcpBackend({ stateDir: project.stateDir });
|
|
44421
|
+
const backendFor = (kind) => kind === "codex" ? codexBackend : kind === "acp" ? acpBackend : claudeBackend;
|
|
42915
44422
|
const codexAppServer = codexAppServerBridgeEnabled() ? createCodexAppServerBridge({
|
|
42916
44423
|
projectId: project.id,
|
|
42917
44424
|
projectDir: project.dir,
|
|
@@ -42963,6 +44470,19 @@ function createContextUnchecked(opts, resources) {
|
|
|
42963
44470
|
}
|
|
42964
44471
|
void codexAppServer?.warmUp();
|
|
42965
44472
|
opts.startup?.afterPhase?.("Codex app-server bridge");
|
|
44473
|
+
const acpBridge = createAcpBridge({
|
|
44474
|
+
projectId: project.id,
|
|
44475
|
+
stateDir: project.stateDir,
|
|
44476
|
+
frizzMcp: resolveFrizzMcp(frizzMcpTarget),
|
|
44477
|
+
interactions: storage.interactions,
|
|
44478
|
+
env: process.env,
|
|
44479
|
+
customAgents: () => getSettings(storage, home).acpAgents,
|
|
44480
|
+
onStatusChange: () => board?.refresh()
|
|
44481
|
+
});
|
|
44482
|
+
resources.acpBridge = acpBridge;
|
|
44483
|
+
contextUnsubscribers.push(storage.subscribeSessionLifecycle((event) => {
|
|
44484
|
+
acpBridge.releaseSession(event.previous.slug, event.previous.session_id, event.type === "replaced" ? "session-replaced" : "session-deleted");
|
|
44485
|
+
}));
|
|
42966
44486
|
const claudeRuntimeIngest = claudeBrokerBridgeEnabled() ? createClaudeRuntimeIngest({ nudge: () => tailer.nudge?.() }) : void 0;
|
|
42967
44487
|
resources.claudeRuntimeIngest = claudeRuntimeIngest;
|
|
42968
44488
|
const claudeBroker = claudeBrokerBridgeEnabled() ? createClaudeAgentBrokerBridge({
|
|
@@ -43074,6 +44594,7 @@ function createContextUnchecked(opts, resources) {
|
|
|
43074
44594
|
claudeBin: opts.claudeBin,
|
|
43075
44595
|
backendFor,
|
|
43076
44596
|
codexAppServer,
|
|
44597
|
+
acpBridge,
|
|
43077
44598
|
claudeBroker,
|
|
43078
44599
|
// Auth preflight (claude-auth plan, Slice A): Claude reads its local credential and confirms only
|
|
43079
44600
|
// a positive signed-out against its CLI (readClaudePreflightAuth — the comment there records why
|
|
@@ -43113,6 +44634,21 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
43113
44634
|
}
|
|
43114
44635
|
return deliverCodexWake({ bridge, storage, cwd: project.dir, row, slug, deliveryMessage, deliveryId });
|
|
43115
44636
|
}
|
|
44637
|
+
if (row?.backend === "acp") {
|
|
44638
|
+
return acpBridge.followUp({
|
|
44639
|
+
threadSlug: slug,
|
|
44640
|
+
sessionId: row.session_id,
|
|
44641
|
+
cwd: project.dir,
|
|
44642
|
+
agentId: row.acp_agent ?? "",
|
|
44643
|
+
modelId: acpModelIdFromModel(row.model),
|
|
44644
|
+
acpSessionId: row.agent_session_id,
|
|
44645
|
+
text: deliveryMessage,
|
|
44646
|
+
deliveryId
|
|
44647
|
+
}).then((r) => {
|
|
44648
|
+
if (r.acpSessionId !== row.agent_session_id) storage.setAgentSession(slug, r.acpSessionId);
|
|
44649
|
+
if (row.exited === 1) storage.setExitedIfCurrent(slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
44650
|
+
});
|
|
44651
|
+
}
|
|
43116
44652
|
if (row?.backend === "claude" && row.claude_runtime === "broker") {
|
|
43117
44653
|
if (!claudeBroker) {
|
|
43118
44654
|
process.stderr.write(`[frizz] claude-broker wake for ${slug} dropped: the session broker is unavailable
|
|
@@ -43178,6 +44714,7 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
43178
44714
|
storage,
|
|
43179
44715
|
interactions: storage.interactions,
|
|
43180
44716
|
codexAppServer,
|
|
44717
|
+
acpBridge,
|
|
43181
44718
|
claudeBroker,
|
|
43182
44719
|
board,
|
|
43183
44720
|
tailer,
|
|
@@ -43219,6 +44756,8 @@ var init_context = __esm({
|
|
|
43219
44756
|
init_resume();
|
|
43220
44757
|
init_claude();
|
|
43221
44758
|
init_codex();
|
|
44759
|
+
init_acp_transcript();
|
|
44760
|
+
init_acp_bridge();
|
|
43222
44761
|
init_auth_status();
|
|
43223
44762
|
init_login_utility();
|
|
43224
44763
|
init_usage_limit();
|
|
@@ -45895,8 +47434,8 @@ var init_server = __esm({
|
|
|
45895
47434
|
});
|
|
45896
47435
|
|
|
45897
47436
|
// packages/server/src/repair.ts
|
|
45898
|
-
import { existsSync as
|
|
45899
|
-
import { basename as basename7, dirname as
|
|
47437
|
+
import { existsSync as existsSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync11 } from "node:fs";
|
|
47438
|
+
import { basename as basename7, dirname as dirname16, resolve as resolve8 } from "node:path";
|
|
45900
47439
|
function quoteValue(v) {
|
|
45901
47440
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
45902
47441
|
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
@@ -45908,10 +47447,10 @@ function deriveTitle(body, slug) {
|
|
|
45908
47447
|
function repairThreadFile(frizzDir, file) {
|
|
45909
47448
|
const root = resolve8(frizzDir);
|
|
45910
47449
|
const abs = resolve8(root, file);
|
|
45911
|
-
if (
|
|
47450
|
+
if (dirname16(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
|
|
45912
47451
|
if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
|
|
45913
|
-
if (!
|
|
45914
|
-
const content =
|
|
47452
|
+
if (!existsSync13(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
|
|
47453
|
+
const content = readFileSync23(abs, "utf8");
|
|
45915
47454
|
const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
45916
47455
|
if (firstLine2?.trim() === "---") {
|
|
45917
47456
|
throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
|
|
@@ -46104,7 +47643,7 @@ var init_repo_files = __esm({
|
|
|
46104
47643
|
});
|
|
46105
47644
|
|
|
46106
47645
|
// cc-worker/hooks/bash-background.mjs
|
|
46107
|
-
import { readFileSync as
|
|
47646
|
+
import { readFileSync as readFileSync24, realpathSync as realpathSync10 } from "node:fs";
|
|
46108
47647
|
import { basename as basename8 } from "node:path";
|
|
46109
47648
|
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
|
|
46110
47649
|
function emit(obj) {
|
|
@@ -46124,8 +47663,8 @@ function withoutHeredocBodies(command) {
|
|
|
46124
47663
|
return " ".repeat(line.length);
|
|
46125
47664
|
}
|
|
46126
47665
|
for (const match2 of line.matchAll(/<<-?\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_][A-Za-z0-9_-]*))/g)) {
|
|
46127
|
-
const
|
|
46128
|
-
if (
|
|
47666
|
+
const delimiter3 = match2[1] ?? match2[2] ?? match2[3];
|
|
47667
|
+
if (delimiter3) pending.push(delimiter3);
|
|
46129
47668
|
}
|
|
46130
47669
|
active = pending.shift();
|
|
46131
47670
|
return line;
|
|
@@ -46259,7 +47798,7 @@ var init_bash_background = __esm({
|
|
|
46259
47798
|
if (isDirectHookExecution(process.argv[1], import.meta.url)) {
|
|
46260
47799
|
try {
|
|
46261
47800
|
const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
|
|
46262
|
-
emit(evaluateBashBackgroundHook(JSON.parse(
|
|
47801
|
+
emit(evaluateBashBackgroundHook(JSON.parse(readFileSync24(0, "utf8")), env));
|
|
46263
47802
|
} catch {
|
|
46264
47803
|
emit({});
|
|
46265
47804
|
}
|
|
@@ -46268,10 +47807,10 @@ var init_bash_background = __esm({
|
|
|
46268
47807
|
});
|
|
46269
47808
|
|
|
46270
47809
|
// packages/server/src/transcript.ts
|
|
46271
|
-
import { closeSync as closeSync13, existsSync as
|
|
47810
|
+
import { closeSync as closeSync13, existsSync as existsSync14, fstatSync as fstatSync3, mkdirSync as mkdirSync16, openSync as openSync14, readdirSync as readdirSync9, readFileSync as readFileSync25, readSync as readSync7, renameSync as renameSync9, statSync as statSync17, unlinkSync as unlinkSync5, writeFileSync as writeFileSync12 } from "node:fs";
|
|
46272
47811
|
import { createHash as createHash13 } from "node:crypto";
|
|
46273
|
-
import { StringDecoder as
|
|
46274
|
-
import { join as
|
|
47812
|
+
import { StringDecoder as StringDecoder5 } from "node:string_decoder";
|
|
47813
|
+
import { join as join38 } from "node:path";
|
|
46275
47814
|
import { homedir as homedir16 } from "node:os";
|
|
46276
47815
|
function formatTokens(n) {
|
|
46277
47816
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -46851,14 +48390,14 @@ function pruneScreenshotCache() {
|
|
|
46851
48390
|
if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
|
|
46852
48391
|
const byMtime = entries2.map((n) => {
|
|
46853
48392
|
try {
|
|
46854
|
-
return { n, m:
|
|
48393
|
+
return { n, m: statSync17(join38(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
46855
48394
|
} catch {
|
|
46856
48395
|
return { n, m: 0 };
|
|
46857
48396
|
}
|
|
46858
48397
|
}).sort((a, b) => b.m - a.m);
|
|
46859
48398
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
46860
48399
|
try {
|
|
46861
|
-
unlinkSync5(
|
|
48400
|
+
unlinkSync5(join38(SCREENSHOT_CACHE_DIR, n));
|
|
46862
48401
|
} catch {
|
|
46863
48402
|
}
|
|
46864
48403
|
}
|
|
@@ -46888,14 +48427,14 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
46888
48427
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
46889
48428
|
if (!ext || !data) return void 0;
|
|
46890
48429
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46891
|
-
const path3 =
|
|
48430
|
+
const path3 = join38(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
46892
48431
|
try {
|
|
46893
|
-
if (
|
|
48432
|
+
if (existsSync14(path3)) return path3;
|
|
46894
48433
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
46895
48434
|
const buf = Buffer.from(data, "base64");
|
|
46896
48435
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
46897
|
-
|
|
46898
|
-
const tmp =
|
|
48436
|
+
mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
48437
|
+
const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46899
48438
|
writeFileSync12(tmp, buf);
|
|
46900
48439
|
renameSync9(tmp, path3);
|
|
46901
48440
|
pruneScreenshotCache();
|
|
@@ -46910,14 +48449,14 @@ function persistSentFile(srcPath, idKey) {
|
|
|
46910
48449
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
46911
48450
|
try {
|
|
46912
48451
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46913
|
-
const dest =
|
|
46914
|
-
if (
|
|
46915
|
-
const size =
|
|
48452
|
+
const dest = join38(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
48453
|
+
if (existsSync14(dest)) return dest;
|
|
48454
|
+
const size = statSync17(srcPath).size;
|
|
46916
48455
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
46917
|
-
const buf =
|
|
48456
|
+
const buf = readFileSync25(srcPath);
|
|
46918
48457
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
46919
|
-
|
|
46920
|
-
const tmp =
|
|
48458
|
+
mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
48459
|
+
const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46921
48460
|
writeFileSync12(tmp, buf);
|
|
46922
48461
|
renameSync9(tmp, dest);
|
|
46923
48462
|
pruneScreenshotCache();
|
|
@@ -46935,7 +48474,7 @@ function viewImageCall(path3, idKey) {
|
|
|
46935
48474
|
...image ? { outputImage: image } : {}
|
|
46936
48475
|
};
|
|
46937
48476
|
}
|
|
46938
|
-
function
|
|
48477
|
+
function toolResultText4(content) {
|
|
46939
48478
|
if (typeof content === "string") return content.trim() || null;
|
|
46940
48479
|
if (Array.isArray(content)) {
|
|
46941
48480
|
const joined = content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
@@ -46966,7 +48505,7 @@ function attachToolResults(rec, pending, backgroundShells, backgroundTaskIds, ch
|
|
|
46966
48505
|
const ackAgentId = typeof rec.toolUseResult?.agentId === "string" ? rec.toolUseResult.agentId.trim() : "";
|
|
46967
48506
|
const dispatchId = entry.calls.find((c) => typeof c.agentId === "string" && c.agentId)?.agentId;
|
|
46968
48507
|
if (ackAgentId && dispatchId) childDispatchIds.set(ackAgentId, dispatchId);
|
|
46969
|
-
const text2 =
|
|
48508
|
+
const text2 = toolResultText4(b.content);
|
|
46970
48509
|
if (text2 && entry.name === "TaskList") {
|
|
46971
48510
|
const todos = parseTodoListResult(text2);
|
|
46972
48511
|
if (todos) for (const call of entry.calls) call.todos = todos;
|
|
@@ -47319,7 +48858,7 @@ function retainedFoldEntry(path3, identityPrefix, fileId, size) {
|
|
|
47319
48858
|
}
|
|
47320
48859
|
const hit = entry !== void 0;
|
|
47321
48860
|
if (!entry) {
|
|
47322
|
-
entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new
|
|
48861
|
+
entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new StringDecoder5("utf8"), fileId };
|
|
47323
48862
|
} else {
|
|
47324
48863
|
transcriptCache.delete(path3);
|
|
47325
48864
|
}
|
|
@@ -47355,7 +48894,7 @@ function readTranscript(project, sessionId) {
|
|
|
47355
48894
|
}
|
|
47356
48895
|
function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
47357
48896
|
try {
|
|
47358
|
-
const fresh = parseTranscript(
|
|
48897
|
+
const fresh = parseTranscript(readFileSync25(path3, "utf8"), identityPrefix);
|
|
47359
48898
|
const a = JSON.stringify(incremental);
|
|
47360
48899
|
const b = JSON.stringify(fresh);
|
|
47361
48900
|
if (a !== b) {
|
|
@@ -47375,16 +48914,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
|
47375
48914
|
}
|
|
47376
48915
|
}
|
|
47377
48916
|
function logDirOf(project) {
|
|
47378
|
-
return
|
|
48917
|
+
return join38(homedir16(), ".claude", "projects", project.cwdSlug);
|
|
47379
48918
|
}
|
|
47380
48919
|
function resolveTranscriptPath(project, sessionId) {
|
|
47381
|
-
const path3 =
|
|
48920
|
+
const path3 = join38(logDirOf(project), `${sessionId}.jsonl`);
|
|
47382
48921
|
try {
|
|
47383
|
-
if (
|
|
48922
|
+
if (statSync17(path3).size > 0) return path3;
|
|
47384
48923
|
} catch {
|
|
47385
48924
|
}
|
|
47386
48925
|
const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
|
|
47387
|
-
return stranded ?
|
|
48926
|
+
return stranded ? join38(stranded, `${sessionId}.jsonl`) : path3;
|
|
47388
48927
|
}
|
|
47389
48928
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
47390
48929
|
const out = [];
|
|
@@ -47759,7 +49298,7 @@ function codexDirectToolCall(name, obj, callId) {
|
|
|
47759
49298
|
detail: strField(obj.task_name) ?? "sub-agent",
|
|
47760
49299
|
subagentType: codexAgentCell(obj),
|
|
47761
49300
|
agentId: callId,
|
|
47762
|
-
|
|
49301
|
+
prompt: typeof obj.message === "string" && obj.message.trim() && strField(obj.message) !== ENCRYPTED_PAYLOAD ? capAgentPrompt(obj.message) : void 0
|
|
47763
49302
|
};
|
|
47764
49303
|
case "send_message":
|
|
47765
49304
|
return codexPeerMessageCall("Send message", target, obj);
|
|
@@ -47871,14 +49410,6 @@ function codexAgentCell(obj) {
|
|
|
47871
49410
|
const effort = strField(obj.reasoning_effort);
|
|
47872
49411
|
return model && effort ? `${model}/${effort}` : model ?? effort;
|
|
47873
49412
|
}
|
|
47874
|
-
function compactFields(obj, keys) {
|
|
47875
|
-
const projected = {};
|
|
47876
|
-
for (const key of keys) {
|
|
47877
|
-
const value = obj[key];
|
|
47878
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") projected[key] = value;
|
|
47879
|
-
}
|
|
47880
|
-
return Object.keys(projected).length ? renderToolInput(projected) : void 0;
|
|
47881
|
-
}
|
|
47882
49413
|
function formatCompactDuration(ms) {
|
|
47883
49414
|
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
47884
49415
|
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
@@ -48434,9 +49965,9 @@ function defaultCodexBackend() {
|
|
|
48434
49965
|
function sourceForThread(project, storage, slug, backendFor) {
|
|
48435
49966
|
const row = storage.getSession(slug);
|
|
48436
49967
|
if (row) {
|
|
48437
|
-
const backend = row.backend === "codex" ? "codex" : "claude";
|
|
48438
|
-
const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : row.transcript_id ?? row.session_id;
|
|
48439
|
-
const path3 = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : resolveTranscriptPath(project, nativeId);
|
|
49968
|
+
const backend = row.backend === "codex" ? "codex" : row.backend === "acp" ? "acp" : "claude";
|
|
49969
|
+
const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : backend === "acp" ? row.session_id : row.transcript_id ?? row.session_id;
|
|
49970
|
+
const path3 = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : backend === "acp" ? backendFor?.("acp")?.transcriptPath(nativeId) : resolveTranscriptPath(project, nativeId);
|
|
48440
49971
|
if (!path3) return void 0;
|
|
48441
49972
|
return {
|
|
48442
49973
|
slug,
|
|
@@ -48449,7 +49980,7 @@ function sourceForThread(project, storage, slug, backendFor) {
|
|
|
48449
49980
|
}
|
|
48450
49981
|
if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
|
|
48451
49982
|
const claudePath = resolveTranscriptPath(project, slug);
|
|
48452
|
-
if (
|
|
49983
|
+
if (existsSync14(claudePath)) {
|
|
48453
49984
|
return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
|
|
48454
49985
|
}
|
|
48455
49986
|
const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
|
|
@@ -48543,6 +50074,7 @@ function fixedSnapshot(source) {
|
|
|
48543
50074
|
function projectSnapshot(snapshot) {
|
|
48544
50075
|
const prefix = `${snapshot.backend}:${snapshot.nativeId}`;
|
|
48545
50076
|
if (snapshot.backend === "codex") return retireStaleQueuedBubbles(projectCodexTranscript(snapshot.raw, prefix));
|
|
50077
|
+
if (snapshot.backend === "acp") return retireStaleQueuedBubbles(projectAcpTranscript(snapshot.raw, prefix));
|
|
48546
50078
|
const { entry } = retainedFoldEntry(snapshot.path, prefix, snapshot.fileKey, snapshot.size);
|
|
48547
50079
|
ingestBounded(entry, snapshot.size, (from, length) => snapshot.readRange(from, length));
|
|
48548
50080
|
return retireStaleQueuedBubbles([...entry.fold.allMessages()]);
|
|
@@ -48599,7 +50131,7 @@ function decodeTranscriptCursor(cursor) {
|
|
|
48599
50131
|
}
|
|
48600
50132
|
const p = value;
|
|
48601
50133
|
const validText2 = (s, max) => typeof s === "string" && s.length > 0 && s.length <= max && !/[\0\r\n]/.test(s);
|
|
48602
|
-
if (!p || p.v !== 1 || !validText2(p.slug, 256) || !validText2(p.sessionId, 256) || !validText2(p.nativeId, 256) || p.backend !== "claude" && p.backend !== "codex" || !Number.isSafeInteger(p.runtimeGeneration) || (p.runtimeGeneration ?? -1) < 0 || !validText2(p.fileKey, 256) || !Number.isSafeInteger(p.snapshotBytes) || (p.snapshotBytes ?? -1) < 0 || !validText2(p.prefixDigest, 128) || !validText2(p.anchorSourceId, 768)) throw new Error("invalid transcript cursor");
|
|
50134
|
+
if (!p || p.v !== 1 || !validText2(p.slug, 256) || !validText2(p.sessionId, 256) || !validText2(p.nativeId, 256) || p.backend !== "claude" && p.backend !== "codex" && p.backend !== "acp" || !Number.isSafeInteger(p.runtimeGeneration) || (p.runtimeGeneration ?? -1) < 0 || !validText2(p.fileKey, 256) || !Number.isSafeInteger(p.snapshotBytes) || (p.snapshotBytes ?? -1) < 0 || !validText2(p.prefixDigest, 128) || !validText2(p.anchorSourceId, 768)) throw new Error("invalid transcript cursor");
|
|
48603
50135
|
return p;
|
|
48604
50136
|
}
|
|
48605
50137
|
function messageBytes(message) {
|
|
@@ -48715,7 +50247,7 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
|
|
|
48715
50247
|
}
|
|
48716
50248
|
function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
48717
50249
|
try {
|
|
48718
|
-
return parseCodexTranscript(
|
|
50250
|
+
return parseCodexTranscript(readFileSync25(absPath, "utf8"), `codex:${nativeId}`);
|
|
48719
50251
|
} catch {
|
|
48720
50252
|
return [];
|
|
48721
50253
|
}
|
|
@@ -48723,6 +50255,11 @@ function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
|
48723
50255
|
function readThreadTranscript(project, storage, slug, backendFor) {
|
|
48724
50256
|
const row = storage.getSession(slug);
|
|
48725
50257
|
if (row) {
|
|
50258
|
+
if (row.backend === "acp") {
|
|
50259
|
+
const path3 = backendFor?.("acp")?.transcriptPath(row.session_id);
|
|
50260
|
+
const acpLedger = parseDeliveryLedger(row.delivery_ledger);
|
|
50261
|
+
return projectDeliveryLedger(path3 ? readAcpTranscriptFile(path3, row.session_id) : [], acpLedger);
|
|
50262
|
+
}
|
|
48726
50263
|
if (row.backend === "codex") {
|
|
48727
50264
|
const backend = backendFor?.("codex") ?? defaultCodexBackend();
|
|
48728
50265
|
const nativeId = row.agent_session_id ?? row.session_id;
|
|
@@ -48813,7 +50350,7 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
|
|
|
48813
50350
|
function readTranscriptFile(absPath) {
|
|
48814
50351
|
try {
|
|
48815
50352
|
const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
|
|
48816
|
-
return parseTranscript(
|
|
50353
|
+
return parseTranscript(readFileSync25(absPath, "utf8"), `claude-file:${pathKey}`);
|
|
48817
50354
|
} catch {
|
|
48818
50355
|
return [];
|
|
48819
50356
|
}
|
|
@@ -48829,6 +50366,7 @@ var init_transcript = __esm({
|
|
|
48829
50366
|
init_delivery_marker();
|
|
48830
50367
|
init_completion_relay();
|
|
48831
50368
|
init_codex();
|
|
50369
|
+
init_acp_transcript();
|
|
48832
50370
|
init_discover();
|
|
48833
50371
|
init_tailer();
|
|
48834
50372
|
init_credential_redaction();
|
|
@@ -48889,7 +50427,7 @@ var init_transcript = __esm({
|
|
|
48889
50427
|
});
|
|
48890
50428
|
|
|
48891
50429
|
// packages/server/src/open-external.ts
|
|
48892
|
-
import { spawn as
|
|
50430
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
48893
50431
|
function validateExternalUrl(raw2) {
|
|
48894
50432
|
let parsed;
|
|
48895
50433
|
try {
|
|
@@ -48911,7 +50449,7 @@ async function openExternalUrl(raw2, options = {}) {
|
|
|
48911
50449
|
const v = validateExternalUrl(raw2);
|
|
48912
50450
|
if (!v.ok) throw new Error(v.reason);
|
|
48913
50451
|
const spec = externalUrlOpenCommand(v.url, options.platform);
|
|
48914
|
-
const child = (options.spawn ??
|
|
50452
|
+
const child = (options.spawn ?? spawn7)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
|
|
48915
50453
|
try {
|
|
48916
50454
|
await awaitOpenerStart(child, spec.command);
|
|
48917
50455
|
return { opened: true };
|
|
@@ -49210,7 +50748,7 @@ var init_github_hovercard = __esm({
|
|
|
49210
50748
|
});
|
|
49211
50749
|
|
|
49212
50750
|
// packages/server/src/backend/account-actions.ts
|
|
49213
|
-
import { execFile as
|
|
50751
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
49214
50752
|
import { promisify as promisify8 } from "node:util";
|
|
49215
50753
|
function liveThreadsForBackend(threads, backend) {
|
|
49216
50754
|
let count = 0;
|
|
@@ -49223,6 +50761,7 @@ function liveThreadsForBackend(threads, backend) {
|
|
|
49223
50761
|
return count;
|
|
49224
50762
|
}
|
|
49225
50763
|
async function runProviderLogout(opts) {
|
|
50764
|
+
if (opts.backend === "acp") return { status: "failed", detail: "Log out with the agent's own CLI; Frizz holds no credential for an ACP agent.", auth: "unknown" };
|
|
49226
50765
|
if (opts.liveThreads > 0) {
|
|
49227
50766
|
return { status: "blocked", activeThreads: opts.liveThreads, auth: (await readAuthSnapshot({ claudeBin: opts.claudeBin }))[opts.backend] };
|
|
49228
50767
|
}
|
|
@@ -49245,7 +50784,7 @@ var init_account_actions = __esm({
|
|
|
49245
50784
|
"packages/server/src/backend/account-actions.ts"() {
|
|
49246
50785
|
"use strict";
|
|
49247
50786
|
init_auth_status();
|
|
49248
|
-
execFileAsync6 = promisify8(
|
|
50787
|
+
execFileAsync6 = promisify8(execFile10);
|
|
49249
50788
|
LIVE_RUNTIMES = /* @__PURE__ */ new Set(["spawning", "running", "perm-prompt"]);
|
|
49250
50789
|
}
|
|
49251
50790
|
});
|
|
@@ -49378,12 +50917,12 @@ var init_background_shell_output = __esm({
|
|
|
49378
50917
|
});
|
|
49379
50918
|
|
|
49380
50919
|
// packages/server/src/image-header.ts
|
|
49381
|
-
import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as
|
|
50920
|
+
import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as statSync18 } from "node:fs";
|
|
49382
50921
|
import { extname } from "node:path";
|
|
49383
50922
|
function readHead3(path3, budget) {
|
|
49384
50923
|
let fd;
|
|
49385
50924
|
try {
|
|
49386
|
-
const size =
|
|
50925
|
+
const size = statSync18(path3).size;
|
|
49387
50926
|
if (size === 0) return void 0;
|
|
49388
50927
|
const buffer = Buffer.alloc(Math.min(size, budget));
|
|
49389
50928
|
fd = openSync16(path3, "r");
|
|
@@ -49520,7 +51059,7 @@ var init_image_header = __esm({
|
|
|
49520
51059
|
});
|
|
49521
51060
|
|
|
49522
51061
|
// packages/server/src/directory-picker.ts
|
|
49523
|
-
import { execFile as
|
|
51062
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
49524
51063
|
import { promisify as promisify9 } from "node:util";
|
|
49525
51064
|
async function pickImageFile(startIn, prompt = "Choose an image for this project", platform3 = process.platform) {
|
|
49526
51065
|
if (platform3 === "darwin") {
|
|
@@ -49602,20 +51141,22 @@ var run2, PICKER_TIMEOUT_MS, OSASCRIPT;
|
|
|
49602
51141
|
var init_directory_picker = __esm({
|
|
49603
51142
|
"packages/server/src/directory-picker.ts"() {
|
|
49604
51143
|
"use strict";
|
|
49605
|
-
run2 = promisify9(
|
|
51144
|
+
run2 = promisify9(execFile11);
|
|
49606
51145
|
PICKER_TIMEOUT_MS = 5 * 6e4;
|
|
49607
51146
|
OSASCRIPT = (prompt) => `POSIX path of (choose folder with prompt ${JSON.stringify(prompt)})`;
|
|
49608
51147
|
}
|
|
49609
51148
|
});
|
|
49610
51149
|
|
|
49611
51150
|
// packages/server/src/router.ts
|
|
49612
|
-
import { readFileSync as
|
|
49613
|
-
import { join as
|
|
51151
|
+
import { readFileSync as readFileSync26, statSync as statSync19 } from "node:fs";
|
|
51152
|
+
import { join as join39, resolve as resolve9 } from "node:path";
|
|
49614
51153
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
49615
|
-
import { basename as basename9, dirname as
|
|
49616
|
-
import { existsSync as
|
|
51154
|
+
import { basename as basename9, dirname as dirname17 } from "node:path";
|
|
51155
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
49617
51156
|
import { homedir as homedir17 } from "node:os";
|
|
49618
51157
|
function validateGithubDispatchProfile(input) {
|
|
51158
|
+
if (input.backend === "acp") return;
|
|
51159
|
+
if (input.effort === void 0) throw new Error(`Unsupported ${input.backend} model/effort pair: ${input.model} / (no effort)`);
|
|
49619
51160
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
49620
51161
|
}
|
|
49621
51162
|
function githubDispatcherRequest(input, item) {
|
|
@@ -49638,7 +51179,11 @@ function isAppServerCodexRow(row) {
|
|
|
49638
51179
|
function appServerCodexTurnLive(codex, row) {
|
|
49639
51180
|
return codex?.turnLiveness(row.slug, row.session_id)?.bridgeTurn === true;
|
|
49640
51181
|
}
|
|
49641
|
-
async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51182
|
+
async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
51183
|
+
if (row.backend === "acp") {
|
|
51184
|
+
if (!acp?.turnLiveness(row.slug, row.session_id)?.turnActive) return "absent";
|
|
51185
|
+
return (await acp.interruptTurn(row.slug, row.session_id)).interrupted ? "stopped" : "absent";
|
|
51186
|
+
}
|
|
49642
51187
|
if (isAppServerCodexRow(row)) {
|
|
49643
51188
|
if (!appServerCodexTurnLive(codex, row)) return "absent";
|
|
49644
51189
|
if (!codex) throw new Error("The Codex app-server is unavailable; nothing was stopped");
|
|
@@ -49679,9 +51224,9 @@ function stopRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator)
|
|
|
49679
51224
|
}
|
|
49680
51225
|
return "stopped";
|
|
49681
51226
|
}
|
|
49682
|
-
async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51227
|
+
async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
49683
51228
|
const row = storage.getSession(slug);
|
|
49684
|
-
if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker), row };
|
|
51229
|
+
if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp), row };
|
|
49685
51230
|
if (storage.getAdoptionClaim(slug)) throw new Error("An adoption attempt is in progress; nothing was stopped");
|
|
49686
51231
|
throw new Error("No registered runtime identity is available; nothing was stopped");
|
|
49687
51232
|
}
|
|
@@ -49713,18 +51258,18 @@ function cutOffHold(telemetry) {
|
|
|
49713
51258
|
if (telemetry?.turn !== "in-flight") return void 0;
|
|
49714
51259
|
return { turnInFlight: true, cutOff: true, unobservable: false, subAgents: [], subAgentCount: 0, bgShells: [], bgShellCount: 0 };
|
|
49715
51260
|
}
|
|
49716
|
-
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker) {
|
|
51261
|
+
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker, acp) {
|
|
49717
51262
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
49718
51263
|
if (binding.kind === "conflict") {
|
|
49719
51264
|
throw new Error("This thread has a competing adoption attempt; nothing was changed");
|
|
49720
51265
|
}
|
|
49721
51266
|
const appServerCodex = isAppServerCodexRow(row);
|
|
49722
51267
|
const brokerClaude = isBrokerClaudeRow(row);
|
|
49723
|
-
const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : false;
|
|
51268
|
+
const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : row.backend === "acp" ? acp?.turnLiveness(row.slug, row.session_id)?.turnActive ?? false : false;
|
|
49724
51269
|
const hold = terminateLive ? void 0 : live ? completionConfirmationHold(telemetry) : cutOffHold(telemetry);
|
|
49725
51270
|
if (hold) return { needsConfirmation: true, hold };
|
|
49726
51271
|
if (live) {
|
|
49727
|
-
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
51272
|
+
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
|
|
49728
51273
|
if (!appServerCodex && !brokerClaude && binding.kind === "unbound" && runtime.isLive(row.slug)) {
|
|
49729
51274
|
throw new Error("The session could not be confirmed stopped; it was not marked done");
|
|
49730
51275
|
}
|
|
@@ -49735,7 +51280,7 @@ async function completeRegisteredThread(storage, row, terminateLive, runtime = c
|
|
|
49735
51280
|
}
|
|
49736
51281
|
return { needsConfirmation: false };
|
|
49737
51282
|
}
|
|
49738
|
-
async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51283
|
+
async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
49739
51284
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
49740
51285
|
if (binding.kind === "conflict") {
|
|
49741
51286
|
throw new Error("This thread changed while it was being dismissed; nothing was removed");
|
|
@@ -49745,7 +51290,7 @@ async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLive
|
|
|
49745
51290
|
runtimeGeneration: row.runtime_generation ?? 0,
|
|
49746
51291
|
adoptionAttemptToken: binding.kind === "bound" ? binding.claim.attempt_token : null
|
|
49747
51292
|
};
|
|
49748
|
-
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
51293
|
+
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
|
|
49749
51294
|
const forgotten = storage.forgetSessionIfCurrent(row.slug, expected);
|
|
49750
51295
|
if (!forgotten) {
|
|
49751
51296
|
throw new Error("This thread resumed or was replaced while it was being dismissed; the new worker was preserved");
|
|
@@ -49777,11 +51322,11 @@ function projectCard(entry, stale) {
|
|
|
49777
51322
|
function addProjectAtPath(input, home = homedir17()) {
|
|
49778
51323
|
const typed = input.trim();
|
|
49779
51324
|
if (!typed) throw new Error("Enter a folder path.");
|
|
49780
|
-
const expanded = typed === "~" || typed.startsWith("~/") ?
|
|
51325
|
+
const expanded = typed === "~" || typed.startsWith("~/") ? join39(home, typed.slice(1)) : typed;
|
|
49781
51326
|
const absolute = resolve9(expanded);
|
|
49782
51327
|
let stats;
|
|
49783
51328
|
try {
|
|
49784
|
-
stats =
|
|
51329
|
+
stats = statSync19(absolute);
|
|
49785
51330
|
} catch {
|
|
49786
51331
|
throw new Error(`No folder at ${absolute}`);
|
|
49787
51332
|
}
|
|
@@ -49806,7 +51351,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
49806
51351
|
if (!entry) throw new Error("No such project.");
|
|
49807
51352
|
if (bytes.length === 0) throw new Error("That file is empty.");
|
|
49808
51353
|
const path3 = customIconPath(id, `.${extension2}`);
|
|
49809
|
-
|
|
51354
|
+
mkdirSync17(dirname17(path3), { recursive: true });
|
|
49810
51355
|
writeFileSync13(path3, bytes);
|
|
49811
51356
|
if (!imageDimensions(path3)) {
|
|
49812
51357
|
rmSync10(path3, { force: true });
|
|
@@ -49823,7 +51368,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
49823
51368
|
function setProjectIconFromFile(id, file) {
|
|
49824
51369
|
let bytes;
|
|
49825
51370
|
try {
|
|
49826
|
-
bytes =
|
|
51371
|
+
bytes = readFileSync26(file);
|
|
49827
51372
|
} catch {
|
|
49828
51373
|
throw new Error("That file could not be read.");
|
|
49829
51374
|
}
|
|
@@ -49856,7 +51401,7 @@ function mergeSubAgentSteers(messages, steers) {
|
|
|
49856
51401
|
return merged;
|
|
49857
51402
|
}
|
|
49858
51403
|
function createRouter(ctx) {
|
|
49859
|
-
const frizzDir =
|
|
51404
|
+
const frizzDir = join39(ctx.project.dir, ".frizz");
|
|
49860
51405
|
const openRoots = openableFileRoots(ctx.project);
|
|
49861
51406
|
function isAutoTitledSession(slug) {
|
|
49862
51407
|
return ctx.storage.getSession(slug)?.title_auto === 1;
|
|
@@ -50483,6 +52028,28 @@ function createRouter(ctx) {
|
|
|
50483
52028
|
${gapNote}` : input.message;
|
|
50484
52029
|
if (row) reopenArchivedThreadForFollowUp(ctx, row);
|
|
50485
52030
|
if (row) wakeParkedThreadForFollowUp(ctx, row);
|
|
52031
|
+
if (row?.backend === "acp") {
|
|
52032
|
+
const bridge = ctx.acpBridge;
|
|
52033
|
+
if (!bridge) throw new Error("The ACP bridge is unavailable; cannot deliver this follow-up");
|
|
52034
|
+
const result = await bridge.followUp({
|
|
52035
|
+
threadSlug: input.slug,
|
|
52036
|
+
sessionId: row.session_id,
|
|
52037
|
+
cwd: ctx.project.dir,
|
|
52038
|
+
agentId: row.acp_agent ?? "",
|
|
52039
|
+
modelId: acpModelIdFromModel(row.model),
|
|
52040
|
+
acpSessionId: row.agent_session_id,
|
|
52041
|
+
text: messageForWorker,
|
|
52042
|
+
...input.deliveryId ? { deliveryId: input.deliveryId } : {}
|
|
52043
|
+
});
|
|
52044
|
+
if (result.acpSessionId !== row.agent_session_id) ctx.storage.setAgentSession(input.slug, result.acpSessionId);
|
|
52045
|
+
if (row.exited === 1) ctx.storage.setExitedIfCurrent(input.slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
52046
|
+
if (input.deliveryId) {
|
|
52047
|
+
appendDelivery(ctx.storage, input.slug, { id: input.deliveryId, text: input.message, state: result.state === "queued" ? "enqueued" : "delivered" });
|
|
52048
|
+
ctx.transcriptChange.emit([input.slug]);
|
|
52049
|
+
}
|
|
52050
|
+
ctx.board.refresh();
|
|
52051
|
+
return;
|
|
52052
|
+
}
|
|
50486
52053
|
if (row?.backend === "codex") {
|
|
50487
52054
|
const bridge = ctx.codexAppServer;
|
|
50488
52055
|
if (!bridge) throw new Error("Codex app-server is unavailable; cannot deliver this follow-up");
|
|
@@ -50668,6 +52235,7 @@ ${gapNote}` : input.message;
|
|
|
50668
52235
|
const thread = (await ctx.board.snapshot()).threads.find((t) => t.id === input.slug);
|
|
50669
52236
|
if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
|
|
50670
52237
|
const permRow = ctx.storage.getSession(input.slug);
|
|
52238
|
+
if (permRow?.backend === "acp") throw new Error("An ACP agent enforces its own permissions; Frizz has no mode to set on it");
|
|
50671
52239
|
if (permRow?.backend === "codex") {
|
|
50672
52240
|
ctx.storage.setPermissionMode(input.slug, input.permissionMode);
|
|
50673
52241
|
ctx.board.refresh();
|
|
@@ -50713,7 +52281,9 @@ ${gapNote}` : input.message;
|
|
|
50713
52281
|
const row = ctx.storage.getSession(input.slug);
|
|
50714
52282
|
if (!row) throw new Error(`thread ${input.slug} has no session to ask for skills`);
|
|
50715
52283
|
let skills;
|
|
50716
|
-
if (row.backend === "
|
|
52284
|
+
if (row.backend === "acp") {
|
|
52285
|
+
skills = [];
|
|
52286
|
+
} else if (row.backend === "codex") {
|
|
50717
52287
|
if (!isAppServerCodexRow(row) || !ctx.codexAppServer) {
|
|
50718
52288
|
throw new Error("This Codex thread has no app-server session to ask for skills");
|
|
50719
52289
|
}
|
|
@@ -50734,6 +52304,7 @@ ${gapNote}` : input.message;
|
|
|
50734
52304
|
const thread = (await ctx.board.snapshot()).threads.find((candidate) => candidate.id === input.slug);
|
|
50735
52305
|
if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
|
|
50736
52306
|
const profRow = ctx.storage.getSession(input.slug);
|
|
52307
|
+
if (profRow?.backend === "acp") throw new Error("An ACP agent chooses its own model; Frizz offers no profile for it yet");
|
|
50737
52308
|
if (profRow?.backend === "codex") {
|
|
50738
52309
|
ctx.storage.setProfile(input.slug, input.model, input.effort);
|
|
50739
52310
|
ctx.board.refresh();
|
|
@@ -50806,7 +52377,8 @@ ${gapNote}` : input.message;
|
|
|
50806
52377
|
cachedLivenessTerminator,
|
|
50807
52378
|
ctx.tailer.get(input.slug),
|
|
50808
52379
|
ctx.codexAppServer,
|
|
50809
|
-
ctx.claudeBroker
|
|
52380
|
+
ctx.claudeBroker,
|
|
52381
|
+
ctx.acpBridge
|
|
50810
52382
|
);
|
|
50811
52383
|
if (!result.needsConfirmation) ctx.board.refresh();
|
|
50812
52384
|
return result;
|
|
@@ -51400,7 +52972,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51400
52972
|
if (t && t.runtime !== "exited") {
|
|
51401
52973
|
throw new Error("only a stalled or exited session can be dismissed \u2014 archive a live one instead");
|
|
51402
52974
|
}
|
|
51403
|
-
await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
52975
|
+
await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51404
52976
|
ctx.tailer.forget(input.slug);
|
|
51405
52977
|
ctx.board.refresh();
|
|
51406
52978
|
}
|
|
@@ -51518,7 +53090,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51518
53090
|
handler: async ({ input }) => {
|
|
51519
53091
|
assertLegacyMutationAllowed(input.slug);
|
|
51520
53092
|
if (input.status === "dismissed") {
|
|
51521
|
-
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
53093
|
+
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51522
53094
|
if (stopped.row && !ctx.storage.setExitedIfCurrent(
|
|
51523
53095
|
stopped.row.slug,
|
|
51524
53096
|
stopped.row.session_id,
|
|
@@ -51622,7 +53194,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51622
53194
|
killAgent: mutation({
|
|
51623
53195
|
input: SlugInput,
|
|
51624
53196
|
handler: async ({ input }) => {
|
|
51625
|
-
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
53197
|
+
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51626
53198
|
if (stopped.row && !ctx.storage.setExitedIfCurrent(
|
|
51627
53199
|
stopped.row.slug,
|
|
51628
53200
|
stopped.row.session_id,
|
|
@@ -51641,6 +53213,20 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51641
53213
|
output: external_exports.array(CodexModel),
|
|
51642
53214
|
handler: async () => readCodexModels()
|
|
51643
53215
|
}),
|
|
53216
|
+
// The ACP agents Frizz knows how to launch, with `available` for the ones on this machine's PATH.
|
|
53217
|
+
// The composer lists the available ones as `acp:<id>` models (plans/acp-backend.md, decision 9).
|
|
53218
|
+
acpAgents: query({
|
|
53219
|
+
output: external_exports.array(AcpAgent),
|
|
53220
|
+
handler: async () => listAcpAgents(ctx.getSettings().acpAgents).map((a) => ({ id: a.id, label: a.label, command: a.command, available: a.bin !== void 0 }))
|
|
53221
|
+
}),
|
|
53222
|
+
// The models one ACP agent advertises — read by opening a throwaway session in the project dir
|
|
53223
|
+
// (cached in the bridge), so it is a separate query the composer asks only once a model picker
|
|
53224
|
+
// needs it, never on every board load.
|
|
53225
|
+
acpAgentModels: query({
|
|
53226
|
+
input: AcpAgentModelsInput,
|
|
53227
|
+
output: AcpAgentModels,
|
|
53228
|
+
handler: async ({ input }) => ctx.acpBridge ? ctx.acpBridge.agentModels(input.agentId, ctx.project.dir, { refresh: input.refresh === true }) : { agentId: input.agentId, models: [], error: "The ACP bridge is unavailable", probedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
53229
|
+
}),
|
|
51644
53230
|
// Provider subscription quota (5h + weekly rate-limit windows) for the sidebar status bar. Codex
|
|
51645
53231
|
// reads live from the app-server's `account/rateLimits/read`, falling back to the rollout JSONL
|
|
51646
53232
|
// frizz already tails; Claude delegates to Claude Code's own non-interactive `/usage` command.
|
|
@@ -51690,7 +53276,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51690
53276
|
const { state, backend } = ctx.loginUtility.status(input.attemptId);
|
|
51691
53277
|
const auth = await readAuthSnapshot({ claudeBin: ctx.claudeBin });
|
|
51692
53278
|
if (state === "exited") ctx.loginUtility.cancel(input.attemptId);
|
|
51693
|
-
return { state, auth: auth[backend ?? "claude"] };
|
|
53279
|
+
return { state, auth: backend === "acp" ? "unknown" : auth[backend ?? "claude"] };
|
|
51694
53280
|
}
|
|
51695
53281
|
}),
|
|
51696
53282
|
accountLoginCancel: mutation({
|
|
@@ -51859,7 +53445,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51859
53445
|
}
|
|
51860
53446
|
const updated = clearProjectIcon(input.id);
|
|
51861
53447
|
if (!updated) throw new Error("No such project.");
|
|
51862
|
-
return projectCard(updated, !
|
|
53448
|
+
return projectCard(updated, !existsSync15(updated.path));
|
|
51863
53449
|
}
|
|
51864
53450
|
}),
|
|
51865
53451
|
/**
|
|
@@ -51928,6 +53514,16 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51928
53514
|
output: Settings,
|
|
51929
53515
|
handler: async ({ input }) => ctx.setSettings(input)
|
|
51930
53516
|
}),
|
|
53517
|
+
// Context controls live in the new-thread picker. Patch only the chosen field against the
|
|
53518
|
+
// latest settings, synchronously, rather than replacing unrelated settings from a stale drawer.
|
|
53519
|
+
contextWindowSet: mutation({
|
|
53520
|
+
input: external_exports.object({ backend: external_exports.enum(["claude", "codex"]), tokens: external_exports.number().int().positive().nullable() }),
|
|
53521
|
+
output: Settings,
|
|
53522
|
+
handler: async ({ input }) => ctx.setSettings({
|
|
53523
|
+
...ctx.getSettings(),
|
|
53524
|
+
[input.backend === "claude" ? "autoCompactWindow" : "codexContextWindow"]: input.tokens ?? void 0
|
|
53525
|
+
})
|
|
53526
|
+
}),
|
|
51931
53527
|
// Clear the stored settings blob so defaults (incl. the shipped default preamble) apply again.
|
|
51932
53528
|
settingsReset: mutation({
|
|
51933
53529
|
input: external_exports.object({}),
|
|
@@ -52032,6 +53628,7 @@ var init_router5 = __esm({
|
|
|
52032
53628
|
init_server();
|
|
52033
53629
|
init_src();
|
|
52034
53630
|
init_context();
|
|
53631
|
+
init_acp_agents();
|
|
52035
53632
|
init_storage();
|
|
52036
53633
|
init_usage_limit();
|
|
52037
53634
|
init_board();
|
|
@@ -52219,7 +53816,7 @@ var init_local_origin = __esm({
|
|
|
52219
53816
|
});
|
|
52220
53817
|
|
|
52221
53818
|
// packages/server/src/compression.ts
|
|
52222
|
-
import { brotliCompressSync, gzipSync, constants as
|
|
53819
|
+
import { brotliCompressSync, gzipSync, constants as constants3 } from "node:zlib";
|
|
52223
53820
|
function negotiateEncoding(acceptEncoding) {
|
|
52224
53821
|
if (!acceptEncoding) return null;
|
|
52225
53822
|
const offers = /* @__PURE__ */ new Map();
|
|
@@ -52242,9 +53839,9 @@ function negotiateEncoding(acceptEncoding) {
|
|
|
52242
53839
|
function compress(body, encoding) {
|
|
52243
53840
|
const out = encoding === "br" ? brotliCompressSync(body, {
|
|
52244
53841
|
params: {
|
|
52245
|
-
[
|
|
53842
|
+
[constants3.BROTLI_PARAM_QUALITY]: 4,
|
|
52246
53843
|
// Telling brotli the input size lets it size its window instead of guessing.
|
|
52247
|
-
[
|
|
53844
|
+
[constants3.BROTLI_PARAM_SIZE_HINT]: body.byteLength
|
|
52248
53845
|
}
|
|
52249
53846
|
}) : gzipSync(body, { level: 6 });
|
|
52250
53847
|
return Uint8Array.from(out);
|
|
@@ -52265,10 +53862,10 @@ var init_compression = __esm({
|
|
|
52265
53862
|
});
|
|
52266
53863
|
|
|
52267
53864
|
// packages/server/src/local-image.ts
|
|
52268
|
-
import { readFileSync as
|
|
52269
|
-
import { extname as extname2, isAbsolute as
|
|
53865
|
+
import { readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
|
|
53866
|
+
import { extname as extname2, isAbsolute as isAbsolute7 } from "node:path";
|
|
52270
53867
|
function resolveLocalImage(rawPath) {
|
|
52271
|
-
if (!rawPath || !
|
|
53868
|
+
if (!rawPath || !isAbsolute7(rawPath)) return { status: 400 };
|
|
52272
53869
|
const contentType = IMAGE_CONTENT_TYPE[extname2(rawPath).toLowerCase()];
|
|
52273
53870
|
if (!contentType) return { status: 400 };
|
|
52274
53871
|
let real;
|
|
@@ -52278,8 +53875,8 @@ function resolveLocalImage(rawPath) {
|
|
|
52278
53875
|
return { status: 404 };
|
|
52279
53876
|
}
|
|
52280
53877
|
try {
|
|
52281
|
-
if (!
|
|
52282
|
-
return { status: 200, contentType, body:
|
|
53878
|
+
if (!statSync20(real).isFile()) return { status: 404 };
|
|
53879
|
+
return { status: 200, contentType, body: readFileSync27(real) };
|
|
52283
53880
|
} catch {
|
|
52284
53881
|
return { status: 404 };
|
|
52285
53882
|
}
|
|
@@ -52299,9 +53896,9 @@ var init_local_image = __esm({
|
|
|
52299
53896
|
});
|
|
52300
53897
|
|
|
52301
53898
|
// packages/server/src/project-icon.ts
|
|
52302
|
-
import { readFileSync as
|
|
53899
|
+
import { readFileSync as readFileSync28, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync21 } from "node:fs";
|
|
52303
53900
|
import { homedir as homedir18 } from "node:os";
|
|
52304
|
-
import { basename as basename10, dirname as
|
|
53901
|
+
import { basename as basename10, dirname as dirname18, extname as extname3, join as join40, relative as relative2, sep as sep3 } from "node:path";
|
|
52305
53902
|
function normalizeStem(stem) {
|
|
52306
53903
|
const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
|
|
52307
53904
|
const normalized = sized.replace(VARIANT_SUFFIX, "");
|
|
@@ -52319,7 +53916,7 @@ function sizeScore(dimensions) {
|
|
|
52319
53916
|
return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
|
|
52320
53917
|
}
|
|
52321
53918
|
function under(root, relativeDirectory) {
|
|
52322
|
-
return relativeDirectory ?
|
|
53919
|
+
return relativeDirectory ? join40(root, relativeDirectory) : root;
|
|
52323
53920
|
}
|
|
52324
53921
|
function listFiles(directory) {
|
|
52325
53922
|
try {
|
|
@@ -52341,19 +53938,19 @@ function iconDirectories(root) {
|
|
|
52341
53938
|
for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
|
|
52342
53939
|
for (const host of HOST_DIRECTORIES) {
|
|
52343
53940
|
if (!children2.has(host)) continue;
|
|
52344
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
53941
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, host), asset));
|
|
52345
53942
|
}
|
|
52346
53943
|
for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
|
|
52347
53944
|
for (const container of WORKSPACE_CONTAINERS) {
|
|
52348
53945
|
if (!children2.has(container)) continue;
|
|
52349
53946
|
let members;
|
|
52350
53947
|
try {
|
|
52351
|
-
members = readdirSync10(
|
|
53948
|
+
members = readdirSync10(join40(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
52352
53949
|
} catch {
|
|
52353
53950
|
continue;
|
|
52354
53951
|
}
|
|
52355
53952
|
for (const member of members) {
|
|
52356
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
53953
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, container, member), asset));
|
|
52357
53954
|
}
|
|
52358
53955
|
}
|
|
52359
53956
|
return [...new Set(directories)];
|
|
@@ -52361,13 +53958,13 @@ function iconDirectories(root) {
|
|
|
52361
53958
|
function manifestIcons(manifestPath) {
|
|
52362
53959
|
let parsed;
|
|
52363
53960
|
try {
|
|
52364
|
-
parsed = JSON.parse(
|
|
53961
|
+
parsed = JSON.parse(readFileSync28(manifestPath, "utf8"));
|
|
52365
53962
|
} catch {
|
|
52366
53963
|
return [];
|
|
52367
53964
|
}
|
|
52368
53965
|
if (!Array.isArray(parsed?.icons)) return [];
|
|
52369
|
-
const directory =
|
|
52370
|
-
return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) =>
|
|
53966
|
+
const directory = dirname18(manifestPath);
|
|
53967
|
+
return parsed.icons.map((icon) => typeof icon?.src === "string" ? icon.src : void 0).filter((src) => !!src && !/^(https?:)?\/\//u.test(src) && !src.startsWith("data:")).map((src) => join40(directory, src.replace(/[?#].*$/u, "").replace(/^\//u, "")));
|
|
52371
53968
|
}
|
|
52372
53969
|
function projectIconCandidates(root) {
|
|
52373
53970
|
const projectStem = normalizeStem(basename10(root)).normalized;
|
|
@@ -52386,10 +53983,10 @@ function projectIconCandidates(root) {
|
|
|
52386
53983
|
for (const name of listFiles(directory)) {
|
|
52387
53984
|
const extension2 = extname3(name).toLowerCase();
|
|
52388
53985
|
if (MANIFEST_NAMES.includes(name.toLowerCase())) {
|
|
52389
|
-
for (const icon of manifestIcons(
|
|
53986
|
+
for (const icon of manifestIcons(join40(directory, name))) {
|
|
52390
53987
|
if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
|
|
52391
53988
|
fromManifest.add(icon);
|
|
52392
|
-
files.push({ path: icon, directory:
|
|
53989
|
+
files.push({ path: icon, directory: dirname18(icon) });
|
|
52393
53990
|
}
|
|
52394
53991
|
}
|
|
52395
53992
|
continue;
|
|
@@ -52397,7 +53994,7 @@ function projectIconCandidates(root) {
|
|
|
52397
53994
|
if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
|
|
52398
53995
|
const stem = name.slice(0, name.length - extension2.length);
|
|
52399
53996
|
if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
|
|
52400
|
-
files.push({ path:
|
|
53997
|
+
files.push({ path: join40(directory, name), directory });
|
|
52401
53998
|
if (files.length >= MAX_CANDIDATES) break;
|
|
52402
53999
|
}
|
|
52403
54000
|
}
|
|
@@ -52449,7 +54046,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
52449
54046
|
let real;
|
|
52450
54047
|
try {
|
|
52451
54048
|
real = realpathSync12(path3);
|
|
52452
|
-
if (!
|
|
54049
|
+
if (!statSync21(real).isFile()) return { status: 404 };
|
|
52453
54050
|
} catch {
|
|
52454
54051
|
return { status: 404 };
|
|
52455
54052
|
}
|
|
@@ -52462,7 +54059,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
52462
54059
|
});
|
|
52463
54060
|
if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
|
|
52464
54061
|
try {
|
|
52465
|
-
return { status: 200, contentType, body:
|
|
54062
|
+
return { status: 200, contentType, body: readFileSync28(real) };
|
|
52466
54063
|
} catch {
|
|
52467
54064
|
return { status: 404 };
|
|
52468
54065
|
}
|
|
@@ -52574,8 +54171,8 @@ var init_project_icon = __esm({
|
|
|
52574
54171
|
});
|
|
52575
54172
|
|
|
52576
54173
|
// packages/server/src/local-visualization.ts
|
|
52577
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
52578
|
-
import { join as
|
|
54174
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync29, realpathSync as realpathSync13, statSync as statSync22 } from "node:fs";
|
|
54175
|
+
import { join as join41, sep as sep4 } from "node:path";
|
|
52579
54176
|
function children(path3, pattern) {
|
|
52580
54177
|
try {
|
|
52581
54178
|
return readdirSync11(path3, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
|
|
@@ -52587,7 +54184,7 @@ function isUnder2(path3, root) {
|
|
|
52587
54184
|
return path3 === root || path3.startsWith(root.endsWith(sep4) ? root : root + sep4);
|
|
52588
54185
|
}
|
|
52589
54186
|
function resolveFragment(projectDir, sessionId, file) {
|
|
52590
|
-
const base =
|
|
54187
|
+
const base = join41(projectDir, ".codex", "visualizations");
|
|
52591
54188
|
let projectReal;
|
|
52592
54189
|
let baseReal;
|
|
52593
54190
|
try {
|
|
@@ -52598,15 +54195,15 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
52598
54195
|
}
|
|
52599
54196
|
if (!isUnder2(baseReal, projectReal)) return null;
|
|
52600
54197
|
for (const year of children(base, YEAR_PART)) {
|
|
52601
|
-
const yearDir =
|
|
54198
|
+
const yearDir = join41(base, year);
|
|
52602
54199
|
for (const month of children(yearDir, DATE_PART)) {
|
|
52603
|
-
const monthDir =
|
|
54200
|
+
const monthDir = join41(yearDir, month);
|
|
52604
54201
|
for (const day of children(monthDir, DATE_PART)) {
|
|
52605
|
-
const sessionRoot =
|
|
52606
|
-
const candidate =
|
|
54202
|
+
const sessionRoot = join41(baseReal, year, month, day, sessionId);
|
|
54203
|
+
const candidate = join41(monthDir, day, sessionId, file);
|
|
52607
54204
|
try {
|
|
52608
54205
|
const real = realpathSync13(candidate);
|
|
52609
|
-
if (isUnder2(real, sessionRoot) &&
|
|
54206
|
+
if (isUnder2(real, sessionRoot) && statSync22(real).isFile()) return real;
|
|
52610
54207
|
} catch {
|
|
52611
54208
|
}
|
|
52612
54209
|
}
|
|
@@ -52687,9 +54284,9 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
52687
54284
|
if (!path3) return { status: 404 };
|
|
52688
54285
|
let fragment;
|
|
52689
54286
|
try {
|
|
52690
|
-
const size =
|
|
54287
|
+
const size = statSync22(path3).size;
|
|
52691
54288
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
52692
|
-
fragment =
|
|
54289
|
+
fragment = readFileSync29(path3, "utf8");
|
|
52693
54290
|
} catch {
|
|
52694
54291
|
return { status: 404 };
|
|
52695
54292
|
}
|
|
@@ -52731,8 +54328,8 @@ var init_local_visualization = __esm({
|
|
|
52731
54328
|
});
|
|
52732
54329
|
|
|
52733
54330
|
// packages/server/src/app.ts
|
|
52734
|
-
import { mkdirSync as
|
|
52735
|
-
import { join as
|
|
54331
|
+
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync14 } from "node:fs";
|
|
54332
|
+
import { join as join42 } from "node:path";
|
|
52736
54333
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
52737
54334
|
function createApp(ctx, options = {}) {
|
|
52738
54335
|
const app = new Hono2();
|
|
@@ -52836,10 +54433,10 @@ function createApp(ctx, options = {}) {
|
|
|
52836
54433
|
if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
|
|
52837
54434
|
const ext = `.${attachmentExtension(name)}`;
|
|
52838
54435
|
const buf = Buffer.from(body.data, "base64");
|
|
52839
|
-
const dir =
|
|
52840
|
-
|
|
54436
|
+
const dir = join42(ctx.project.stateDir, "attachments");
|
|
54437
|
+
mkdirSync18(dir, { recursive: true });
|
|
52841
54438
|
const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
|
|
52842
|
-
const path3 =
|
|
54439
|
+
const path3 = join42(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
|
|
52843
54440
|
writeFileSync14(path3, buf);
|
|
52844
54441
|
return c.json({ path: path3 });
|
|
52845
54442
|
});
|
|
@@ -54151,11 +55748,11 @@ var init_app_socket = __esm({
|
|
|
54151
55748
|
});
|
|
54152
55749
|
|
|
54153
55750
|
// packages/server/src/local-file-watch.ts
|
|
54154
|
-
import { statSync as
|
|
54155
|
-
import { basename as basename11, dirname as
|
|
55751
|
+
import { statSync as statSync23, watch } from "node:fs";
|
|
55752
|
+
import { basename as basename11, dirname as dirname19 } from "node:path";
|
|
54156
55753
|
function stampOf(path3) {
|
|
54157
55754
|
try {
|
|
54158
|
-
const s =
|
|
55755
|
+
const s = statSync23(path3, { bigint: true });
|
|
54159
55756
|
return `${s.ino}:${s.size}:${s.mtimeNs}`;
|
|
54160
55757
|
} catch {
|
|
54161
55758
|
return "gone";
|
|
@@ -54175,7 +55772,7 @@ function watchLocalFile(path3, onChange) {
|
|
|
54175
55772
|
let entry = entries.get(path3);
|
|
54176
55773
|
if (!entry) {
|
|
54177
55774
|
const name = basename11(path3);
|
|
54178
|
-
const watcher2 = watch(
|
|
55775
|
+
const watcher2 = watch(dirname19(path3), { persistent: false }, (_event, changed) => {
|
|
54179
55776
|
if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
|
|
54180
55777
|
const current = entries.get(path3);
|
|
54181
55778
|
if (!current) return;
|
|
@@ -54211,10 +55808,10 @@ var init_local_file_watch = __esm({
|
|
|
54211
55808
|
});
|
|
54212
55809
|
|
|
54213
55810
|
// packages/server/src/boot-progress.ts
|
|
54214
|
-
import { mkdirSync as
|
|
54215
|
-
import { basename as basename12, dirname as
|
|
55811
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync30, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
55812
|
+
import { basename as basename12, dirname as dirname20, join as join43 } from "node:path";
|
|
54216
55813
|
function bootProgressPath(stateDir) {
|
|
54217
|
-
return
|
|
55814
|
+
return join43(stateDir, BOOT_PROGRESS_NAME);
|
|
54218
55815
|
}
|
|
54219
55816
|
function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
54220
55817
|
if (!stateDir) {
|
|
@@ -54234,8 +55831,8 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
54234
55831
|
lastAt = now;
|
|
54235
55832
|
step++;
|
|
54236
55833
|
try {
|
|
54237
|
-
|
|
54238
|
-
const temp =
|
|
55834
|
+
mkdirSync19(dirname20(path3), { recursive: true, mode: 448 });
|
|
55835
|
+
const temp = join43(dirname20(path3), `.${basename12(path3)}.${process.pid}.tmp`);
|
|
54239
55836
|
writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
|
|
54240
55837
|
`, "utf8");
|
|
54241
55838
|
renameSync10(temp, path3);
|
|
@@ -54251,22 +55848,23 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
54251
55848
|
}
|
|
54252
55849
|
});
|
|
54253
55850
|
}
|
|
54254
|
-
var BOOT_PROGRESS_NAME;
|
|
55851
|
+
var BOOT_PROGRESS_NAME, BOOT_HARD_TIMEOUT_MS;
|
|
54255
55852
|
var init_boot_progress = __esm({
|
|
54256
55853
|
"packages/server/src/boot-progress.ts"() {
|
|
54257
55854
|
"use strict";
|
|
54258
55855
|
BOOT_PROGRESS_NAME = "boot.progress";
|
|
55856
|
+
BOOT_HARD_TIMEOUT_MS = 10 * 6e4;
|
|
54259
55857
|
}
|
|
54260
55858
|
});
|
|
54261
55859
|
|
|
54262
55860
|
// packages/server/src/runtime-lease.ts
|
|
54263
|
-
import { existsSync as
|
|
54264
|
-
import { dirname as
|
|
55861
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync20, readdirSync as readdirSync12, readFileSync as readFileSync31, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
|
|
55862
|
+
import { dirname as dirname21, join as join44 } from "node:path";
|
|
54265
55863
|
function runtimeVersionDir(bin) {
|
|
54266
|
-
let dir =
|
|
55864
|
+
let dir = dirname21(bin);
|
|
54267
55865
|
for (let step = 0; step < MAX_ASCENT; step++) {
|
|
54268
|
-
if (
|
|
54269
|
-
const parent =
|
|
55866
|
+
if (existsSync16(join44(dir, PROVISIONED_MARKER))) return dir;
|
|
55867
|
+
const parent = dirname21(dir);
|
|
54270
55868
|
if (parent === dir) return void 0;
|
|
54271
55869
|
dir = parent;
|
|
54272
55870
|
}
|
|
@@ -54284,9 +55882,9 @@ function leaseRuntime(bin, role) {
|
|
|
54284
55882
|
const versionDir = runtimeVersionDir(bin);
|
|
54285
55883
|
if (!versionDir) return () => {
|
|
54286
55884
|
};
|
|
54287
|
-
const path3 =
|
|
55885
|
+
const path3 = join44(versionDir, LEASES_DIR, `${process.pid}.json`);
|
|
54288
55886
|
try {
|
|
54289
|
-
|
|
55887
|
+
mkdirSync20(dirname21(path3), { recursive: true });
|
|
54290
55888
|
const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
54291
55889
|
writeFileSync16(path3, JSON.stringify(lease));
|
|
54292
55890
|
} catch {
|
|
@@ -54301,7 +55899,7 @@ function leaseRuntime(bin, role) {
|
|
|
54301
55899
|
};
|
|
54302
55900
|
}
|
|
54303
55901
|
function liveRuntimeLeases(versionDir) {
|
|
54304
|
-
const dir =
|
|
55902
|
+
const dir = join44(versionDir, LEASES_DIR);
|
|
54305
55903
|
let names;
|
|
54306
55904
|
try {
|
|
54307
55905
|
names = readdirSync12(dir);
|
|
@@ -54311,10 +55909,10 @@ function liveRuntimeLeases(versionDir) {
|
|
|
54311
55909
|
const live = [];
|
|
54312
55910
|
for (const name of names) {
|
|
54313
55911
|
if (!name.endsWith(".json")) continue;
|
|
54314
|
-
const path3 =
|
|
55912
|
+
const path3 = join44(dir, name);
|
|
54315
55913
|
let lease;
|
|
54316
55914
|
try {
|
|
54317
|
-
lease = JSON.parse(
|
|
55915
|
+
lease = JSON.parse(readFileSync31(path3, "utf8"));
|
|
54318
55916
|
} catch {
|
|
54319
55917
|
continue;
|
|
54320
55918
|
}
|
|
@@ -54345,25 +55943,25 @@ import { createHash as createHash15 } from "node:crypto";
|
|
|
54345
55943
|
import {
|
|
54346
55944
|
chmodSync as chmodSync2,
|
|
54347
55945
|
closeSync as closeSync16,
|
|
54348
|
-
existsSync as
|
|
54349
|
-
mkdirSync as
|
|
55946
|
+
existsSync as existsSync17,
|
|
55947
|
+
mkdirSync as mkdirSync21,
|
|
54350
55948
|
openSync as openSync17,
|
|
54351
|
-
readFileSync as
|
|
55949
|
+
readFileSync as readFileSync32,
|
|
54352
55950
|
readdirSync as readdirSync13,
|
|
54353
55951
|
renameSync as renameSync11,
|
|
54354
55952
|
rmSync as rmSync12,
|
|
54355
|
-
statSync as
|
|
55953
|
+
statSync as statSync24,
|
|
54356
55954
|
writeFileSync as writeFileSync17,
|
|
54357
55955
|
writeSync as writeSync2
|
|
54358
55956
|
} from "node:fs";
|
|
54359
|
-
import { dirname as
|
|
55957
|
+
import { dirname as dirname22, isAbsolute as isAbsolute8, join as join45, normalize, relative as relative3, sep as sep5 } from "node:path";
|
|
54360
55958
|
import { Readable, Transform, Writable as Writable3 } from "node:stream";
|
|
54361
55959
|
import { pipeline } from "node:stream/promises";
|
|
54362
55960
|
import { createGunzip } from "node:zlib";
|
|
54363
55961
|
function runtimesRoot(env = process.env) {
|
|
54364
55962
|
const override = env[RUNTIMES_DIR_ENV];
|
|
54365
|
-
if (override &&
|
|
54366
|
-
return
|
|
55963
|
+
if (override && isAbsolute8(override)) return override;
|
|
55964
|
+
return join45(frizzRoots().cache, "runtimes");
|
|
54367
55965
|
}
|
|
54368
55966
|
function isMusl() {
|
|
54369
55967
|
if (process.platform !== "linux") return false;
|
|
@@ -54380,16 +55978,16 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
|
|
|
54380
55978
|
return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe}` };
|
|
54381
55979
|
}
|
|
54382
55980
|
function provisionedBinary(backend, root, label) {
|
|
54383
|
-
const dir =
|
|
55981
|
+
const dir = join45(root, backend, label);
|
|
54384
55982
|
let marker;
|
|
54385
55983
|
try {
|
|
54386
|
-
marker = JSON.parse(
|
|
55984
|
+
marker = JSON.parse(readFileSync32(join45(dir, MARKER), "utf8"));
|
|
54387
55985
|
} catch {
|
|
54388
55986
|
return void 0;
|
|
54389
55987
|
}
|
|
54390
55988
|
if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
|
|
54391
|
-
const bin =
|
|
54392
|
-
return
|
|
55989
|
+
const bin = join45(dir, marker.binary);
|
|
55990
|
+
return existsSync17(bin) ? bin : void 0;
|
|
54393
55991
|
}
|
|
54394
55992
|
function octal(buffer, offset, length) {
|
|
54395
55993
|
const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
|
|
@@ -54402,18 +56000,18 @@ function safeEntryPath(dest, name) {
|
|
|
54402
56000
|
const parts = name.split("/").filter((part) => part.length > 0);
|
|
54403
56001
|
if (parts.length < 2) return void 0;
|
|
54404
56002
|
const rel = normalize(parts.slice(1).join(sep5));
|
|
54405
|
-
if (
|
|
54406
|
-
const full =
|
|
56003
|
+
if (isAbsolute8(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
|
|
56004
|
+
const full = join45(dest, rel);
|
|
54407
56005
|
const back = relative3(dest, full);
|
|
54408
|
-
if (back.startsWith("..") ||
|
|
56006
|
+
if (back.startsWith("..") || isAbsolute8(back)) return void 0;
|
|
54409
56007
|
return full;
|
|
54410
56008
|
}
|
|
54411
56009
|
function locateBinary(dir, backend, binary) {
|
|
54412
56010
|
if (backend === "claude") {
|
|
54413
|
-
const direct =
|
|
54414
|
-
return
|
|
56011
|
+
const direct = join45(dir, binary);
|
|
56012
|
+
return existsSync17(direct) ? direct : void 0;
|
|
54415
56013
|
}
|
|
54416
|
-
const vendor =
|
|
56014
|
+
const vendor = join45(dir, "vendor");
|
|
54417
56015
|
let triples;
|
|
54418
56016
|
try {
|
|
54419
56017
|
triples = readdirSync13(vendor);
|
|
@@ -54421,8 +56019,8 @@ function locateBinary(dir, backend, binary) {
|
|
|
54421
56019
|
return void 0;
|
|
54422
56020
|
}
|
|
54423
56021
|
for (const triple of triples) {
|
|
54424
|
-
const candidate =
|
|
54425
|
-
if (
|
|
56022
|
+
const candidate = join45(vendor, triple, "bin", binary);
|
|
56023
|
+
if (existsSync17(candidate)) return candidate;
|
|
54426
56024
|
}
|
|
54427
56025
|
return void 0;
|
|
54428
56026
|
}
|
|
@@ -54437,12 +56035,12 @@ async function provisionRuntime(backend, options) {
|
|
|
54437
56035
|
const registry = options.registry ?? REGISTRY;
|
|
54438
56036
|
const progress = options.onProgress ?? (() => {
|
|
54439
56037
|
});
|
|
54440
|
-
const backendDir =
|
|
54441
|
-
const finalDir =
|
|
54442
|
-
const partial =
|
|
54443
|
-
|
|
56038
|
+
const backendDir = join45(root, backend);
|
|
56039
|
+
const finalDir = join45(backendDir, coordinates.label);
|
|
56040
|
+
const partial = join45(backendDir, `.partial-${coordinates.label}-${process.pid}`);
|
|
56041
|
+
mkdirSync21(backendDir, { recursive: true });
|
|
54444
56042
|
rmSync12(partial, { recursive: true, force: true });
|
|
54445
|
-
|
|
56043
|
+
mkdirSync21(partial);
|
|
54446
56044
|
try {
|
|
54447
56045
|
const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
|
|
54448
56046
|
progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
|
|
@@ -54486,7 +56084,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54486
56084
|
binary: relative3(partial, bin),
|
|
54487
56085
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
54488
56086
|
};
|
|
54489
|
-
writeFileSync17(
|
|
56087
|
+
writeFileSync17(join45(partial, MARKER), `${JSON.stringify(marker, null, 2)}
|
|
54490
56088
|
`, "utf8");
|
|
54491
56089
|
const rename2 = options.rename ?? renameSync11;
|
|
54492
56090
|
try {
|
|
@@ -54495,7 +56093,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54495
56093
|
rename2(partial, finalDir);
|
|
54496
56094
|
break;
|
|
54497
56095
|
} catch (err) {
|
|
54498
|
-
if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") ||
|
|
56096
|
+
if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync17(finalDir)) throw err;
|
|
54499
56097
|
await new Promise((resolve12) => setTimeout(resolve12, options.retryDelayMs ?? RENAME_RETRY_DELAY_MS));
|
|
54500
56098
|
}
|
|
54501
56099
|
}
|
|
@@ -54506,7 +56104,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54506
56104
|
return { bin: theirs, label: coordinates.label, fetched: false };
|
|
54507
56105
|
}
|
|
54508
56106
|
progress("ready");
|
|
54509
|
-
return { bin:
|
|
56107
|
+
return { bin: join45(finalDir, marker.binary), label: coordinates.label, fetched: true };
|
|
54510
56108
|
} catch (err) {
|
|
54511
56109
|
rmSync12(partial, { recursive: true, force: true });
|
|
54512
56110
|
throw err;
|
|
@@ -54514,7 +56112,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54514
56112
|
}
|
|
54515
56113
|
function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
|
|
54516
56114
|
const rm2 = deps.rm ?? defaultSweepRm;
|
|
54517
|
-
const backendDir =
|
|
56115
|
+
const backendDir = join45(root, backend);
|
|
54518
56116
|
let entries2;
|
|
54519
56117
|
try {
|
|
54520
56118
|
entries2 = readdirSync13(backendDir);
|
|
@@ -54524,10 +56122,10 @@ function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
|
|
|
54524
56122
|
const sweep = { removed: [], kept: [] };
|
|
54525
56123
|
for (const entry of entries2) {
|
|
54526
56124
|
if (entry === keepLabel) continue;
|
|
54527
|
-
const full =
|
|
56125
|
+
const full = join45(backendDir, entry);
|
|
54528
56126
|
if (entry.startsWith(".partial-")) {
|
|
54529
56127
|
try {
|
|
54530
|
-
if (now -
|
|
56128
|
+
if (now - statSync24(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
|
|
54531
56129
|
} catch {
|
|
54532
56130
|
continue;
|
|
54533
56131
|
}
|
|
@@ -54707,7 +56305,7 @@ var init_runtimes = __esm({
|
|
|
54707
56305
|
return;
|
|
54708
56306
|
case "5": {
|
|
54709
56307
|
const dir = safeEntryPath(this.dest, name);
|
|
54710
|
-
if (dir)
|
|
56308
|
+
if (dir) mkdirSync21(dir, { recursive: true });
|
|
54711
56309
|
return;
|
|
54712
56310
|
}
|
|
54713
56311
|
case "0":
|
|
@@ -54715,7 +56313,7 @@ var init_runtimes = __esm({
|
|
|
54715
56313
|
case "7": {
|
|
54716
56314
|
const file = safeEntryPath(this.dest, name);
|
|
54717
56315
|
if (!file) return;
|
|
54718
|
-
|
|
56316
|
+
mkdirSync21(dirname22(file), { recursive: true });
|
|
54719
56317
|
this.fd = openSync17(file, "w", octal(header, 100, 8) & 511 || 420);
|
|
54720
56318
|
this.written.push(file);
|
|
54721
56319
|
return;
|
|
@@ -55292,18 +56890,18 @@ var init_legacy_project_db = __esm({
|
|
|
55292
56890
|
});
|
|
55293
56891
|
|
|
55294
56892
|
// packages/server/src/frizz-db.ts
|
|
55295
|
-
import { existsSync as
|
|
55296
|
-
import { basename as basename13, dirname as
|
|
56893
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync22, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync25 } from "node:fs";
|
|
56894
|
+
import { basename as basename13, dirname as dirname23, join as join46 } from "node:path";
|
|
55297
56895
|
function frizzDatabaseLocation(options) {
|
|
55298
56896
|
if (options.stateDir !== void 0) {
|
|
55299
|
-
const projectsDir =
|
|
56897
|
+
const projectsDir = dirname23(options.stateDir);
|
|
55300
56898
|
if (basename13(projectsDir) === "projects") {
|
|
55301
|
-
return { path: options.path ??
|
|
56899
|
+
return { path: options.path ?? join46(dirname23(projectsDir), "ui.db"), projectsDir };
|
|
55302
56900
|
}
|
|
55303
|
-
return { path: options.path ??
|
|
56901
|
+
return { path: options.path ?? join46(options.stateDir, "ui.db"), projectsDir: void 0 };
|
|
55304
56902
|
}
|
|
55305
56903
|
const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
|
|
55306
|
-
return { path: options.path ??
|
|
56904
|
+
return { path: options.path ?? join46(data, "ui.db"), projectsDir: join46(data, "projects") };
|
|
55307
56905
|
}
|
|
55308
56906
|
function ensureFrizzSchema(db) {
|
|
55309
56907
|
db.exec(`
|
|
@@ -55322,7 +56920,7 @@ function ensureFrizzSchema(db) {
|
|
|
55322
56920
|
}
|
|
55323
56921
|
function openFrizzDatabase(options = {}) {
|
|
55324
56922
|
const { path: path3, projectsDir } = frizzDatabaseLocation(options);
|
|
55325
|
-
|
|
56923
|
+
mkdirSync22(dirname23(path3), { recursive: true });
|
|
55326
56924
|
const db = new sqlite_default(path3);
|
|
55327
56925
|
db.pragma("busy_timeout = 5000");
|
|
55328
56926
|
db.pragma("journal_mode = WAL");
|
|
@@ -55334,10 +56932,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
55334
56932
|
);
|
|
55335
56933
|
for (const projectId of retiredLegacyProjectIds(projectsDir)) {
|
|
55336
56934
|
if (alreadyImported.has(projectId)) continue;
|
|
55337
|
-
const dir =
|
|
56935
|
+
const dir = join46(projectsDir, projectId);
|
|
55338
56936
|
try {
|
|
55339
|
-
renameSync12(
|
|
55340
|
-
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId,
|
|
56937
|
+
renameSync12(join46(dir, "ui.db.imported"), join46(dir, "ui.db"));
|
|
56938
|
+
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId, join46(dir, "ui.db"), (/* @__PURE__ */ new Date()).toISOString());
|
|
55341
56939
|
alreadyImported.add(projectId);
|
|
55342
56940
|
log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
|
|
55343
56941
|
} catch (error) {
|
|
@@ -55347,7 +56945,7 @@ function openFrizzDatabase(options = {}) {
|
|
|
55347
56945
|
}
|
|
55348
56946
|
for (const projectId of legacyProjectIds(projectsDir)) {
|
|
55349
56947
|
if (alreadyImported.has(projectId)) continue;
|
|
55350
|
-
const legacyPath =
|
|
56948
|
+
const legacyPath = join46(projectsDir, projectId, "ui.db");
|
|
55351
56949
|
try {
|
|
55352
56950
|
const rows = importLegacyProject(db, projectId, legacyPath);
|
|
55353
56951
|
imported.push({ projectId, rows });
|
|
@@ -55377,10 +56975,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
55377
56975
|
};
|
|
55378
56976
|
}
|
|
55379
56977
|
function projectIdsWithFile(projectsDir, file) {
|
|
55380
|
-
if (!
|
|
56978
|
+
if (!existsSync18(projectsDir)) return [];
|
|
55381
56979
|
return readdirSync14(projectsDir).filter((name) => {
|
|
55382
56980
|
try {
|
|
55383
|
-
return
|
|
56981
|
+
return statSync25(join46(projectsDir, name, file)).isFile();
|
|
55384
56982
|
} catch {
|
|
55385
56983
|
return false;
|
|
55386
56984
|
}
|
|
@@ -55390,7 +56988,7 @@ function legacyProjectIds(projectsDir) {
|
|
|
55390
56988
|
return projectIdsWithFile(projectsDir, "ui.db");
|
|
55391
56989
|
}
|
|
55392
56990
|
function retiredLegacyProjectIds(projectsDir) {
|
|
55393
|
-
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !
|
|
56991
|
+
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync18(join46(projectsDir, id, "ui.db")));
|
|
55394
56992
|
}
|
|
55395
56993
|
function tableColumns(db, schema, table) {
|
|
55396
56994
|
return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
|
|
@@ -55546,7 +57144,7 @@ var init_tenant_prime = __esm({
|
|
|
55546
57144
|
// packages/server/src/project-teardown.ts
|
|
55547
57145
|
import { rmSync as rmSync13 } from "node:fs";
|
|
55548
57146
|
import { homedir as homedir19 } from "node:os";
|
|
55549
|
-
import { dirname as
|
|
57147
|
+
import { dirname as dirname24, resolve as resolve10 } from "node:path";
|
|
55550
57148
|
async function stopProjectWorkers(ctx) {
|
|
55551
57149
|
let stopped = 0;
|
|
55552
57150
|
for (const row of ctx.storage.allSessions()) {
|
|
@@ -55566,7 +57164,7 @@ function deleteProjectState(projectId, home = homedir19()) {
|
|
|
55566
57164
|
if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
|
|
55567
57165
|
throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
|
|
55568
57166
|
}
|
|
55569
|
-
if (
|
|
57167
|
+
if (dirname24(resolve10(target)) !== resolve10(dirname24(projectStateDir("x", home)))) {
|
|
55570
57168
|
throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
|
|
55571
57169
|
}
|
|
55572
57170
|
rmSync13(target, { recursive: true, force: true });
|
|
@@ -55595,11 +57193,11 @@ __export(index_exports, {
|
|
|
55595
57193
|
unknownProjectPage: () => unknownProjectPage
|
|
55596
57194
|
});
|
|
55597
57195
|
import { createServer } from "node:http";
|
|
55598
|
-
import { readFileSync as
|
|
55599
|
-
import { dirname as
|
|
57196
|
+
import { readFileSync as readFileSync33, existsSync as existsSync19, statSync as statSync26 } from "node:fs";
|
|
57197
|
+
import { dirname as dirname25, join as join47, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
|
|
55600
57198
|
function readServerAddressHolder(path3) {
|
|
55601
57199
|
try {
|
|
55602
|
-
const value = JSON.parse(
|
|
57200
|
+
const value = JSON.parse(readFileSync33(path3, "utf8"));
|
|
55603
57201
|
if (typeof value?.pid !== "number") return void 0;
|
|
55604
57202
|
return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
|
|
55605
57203
|
} catch {
|
|
@@ -55724,12 +57322,12 @@ function staticIsFresh(req, etag, mtimeMs) {
|
|
|
55724
57322
|
}
|
|
55725
57323
|
function serveStatic(distDir, req, res) {
|
|
55726
57324
|
const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
|
|
55727
|
-
let file =
|
|
55728
|
-
if (!file.startsWith(distDir + sep6)) file =
|
|
55729
|
-
if (!
|
|
57325
|
+
let file = join47(distDir, rel === "/" ? "index.html" : rel);
|
|
57326
|
+
if (!file.startsWith(distDir + sep6)) file = join47(distDir, "index.html");
|
|
57327
|
+
if (!existsSync19(file)) file = join47(distDir, "index.html");
|
|
55730
57328
|
try {
|
|
55731
|
-
const stats =
|
|
55732
|
-
const cacheControl = file.startsWith(
|
|
57329
|
+
const stats = statSync26(file);
|
|
57330
|
+
const cacheControl = file.startsWith(join47(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
|
|
55733
57331
|
const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
|
|
55734
57332
|
const headers = {
|
|
55735
57333
|
"content-type": MIME[extname4(file)] ?? "application/octet-stream",
|
|
@@ -55745,7 +57343,7 @@ function serveStatic(distDir, req, res) {
|
|
|
55745
57343
|
res.end();
|
|
55746
57344
|
return;
|
|
55747
57345
|
}
|
|
55748
|
-
let body =
|
|
57346
|
+
let body = readFileSync33(file);
|
|
55749
57347
|
const accept = req.headers["accept-encoding"];
|
|
55750
57348
|
const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
|
|
55751
57349
|
if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
|
|
@@ -56007,7 +57605,7 @@ async function startServer(opts = {}) {
|
|
|
56007
57605
|
if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
|
|
56008
57606
|
if (statusPath && statusIdentity) {
|
|
56009
57607
|
try {
|
|
56010
|
-
runtime.removeStatus(serverAddressPathForStateDir(
|
|
57608
|
+
runtime.removeStatus(serverAddressPathForStateDir(dirname25(statusPath)), statusIdentity);
|
|
56011
57609
|
} catch {
|
|
56012
57610
|
}
|
|
56013
57611
|
}
|
|
@@ -56145,7 +57743,7 @@ async function startServer(opts = {}) {
|
|
|
56145
57743
|
}
|
|
56146
57744
|
statusPath = serverLockPathFor(ctx.project);
|
|
56147
57745
|
const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
|
|
56148
|
-
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) :
|
|
57746
|
+
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join47(webRoot, "dist");
|
|
56149
57747
|
startupPhase = "Vite";
|
|
56150
57748
|
if (opts.dev) {
|
|
56151
57749
|
try {
|
|
@@ -56221,7 +57819,7 @@ async function startServer(opts = {}) {
|
|
|
56221
57819
|
if (vite) {
|
|
56222
57820
|
vite.middlewares(req, res, () => {
|
|
56223
57821
|
try {
|
|
56224
|
-
const html =
|
|
57822
|
+
const html = readFileSync33(join47(webRoot, "index.html"), "utf8");
|
|
56225
57823
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
56226
57824
|
res.writeHead(200, { "content-type": "text/html" });
|
|
56227
57825
|
res.end(out);
|
|
@@ -56233,7 +57831,7 @@ async function startServer(opts = {}) {
|
|
|
56233
57831
|
});
|
|
56234
57832
|
return;
|
|
56235
57833
|
}
|
|
56236
|
-
if (
|
|
57834
|
+
if (existsSync19(distDir)) {
|
|
56237
57835
|
serveStatic(distDir, req, res);
|
|
56238
57836
|
return;
|
|
56239
57837
|
}
|
|
@@ -56431,7 +58029,7 @@ var init_index = __esm({
|
|
|
56431
58029
|
}
|
|
56432
58030
|
};
|
|
56433
58031
|
isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
|
|
56434
|
-
serverLockPathFor = (project) =>
|
|
58032
|
+
serverLockPathFor = (project) => join47(project.stateDir, "server.lock");
|
|
56435
58033
|
MIME = {
|
|
56436
58034
|
".html": "text/html",
|
|
56437
58035
|
".js": "text/javascript",
|
|
@@ -56454,8 +58052,8 @@ init_project();
|
|
|
56454
58052
|
init_project_launch();
|
|
56455
58053
|
init_shutdown();
|
|
56456
58054
|
init_logging();
|
|
56457
|
-
import { existsSync as
|
|
56458
|
-
import { join as
|
|
58055
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
58056
|
+
import { join as join48 } from "node:path";
|
|
56459
58057
|
|
|
56460
58058
|
// packages/server/src/native-helper.ts
|
|
56461
58059
|
import { chmodSync, statSync as statSync4 } from "node:fs";
|
|
@@ -56532,9 +58130,9 @@ try {
|
|
|
56532
58130
|
["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
|
|
56533
58131
|
["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
|
|
56534
58132
|
];
|
|
56535
|
-
if (!
|
|
58133
|
+
if (!existsSync20(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
56536
58134
|
for (const [name, directory, requiredFile] of required) {
|
|
56537
|
-
if (!directory || !
|
|
58135
|
+
if (!directory || !existsSync20(join48(directory, requiredFile)))
|
|
56538
58136
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
56539
58137
|
}
|
|
56540
58138
|
}
|