frizz-server 0.13.7 → 0.13.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/claude-agent-broker.js +14659 -14376
- package/dist/dev-child.js +1978 -350
- package/package.json +1 -1
- package/web-dist/assets/{TerminalPane-P7iAI_rU.js → TerminalPane-uhdLUx67.js} +1 -1
- package/web-dist/assets/{abnfDiagram-VRR7QNED-B0JxaKjB.js → abnfDiagram-VRR7QNED-gpWvs3KP.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-DjmwW7vl.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-E8bWaWhF.js → architectureDiagram-ZJ3FMSHR-Di1xsjNG.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-BNg7FcxS.js → blockDiagram-677ZJIJ3-CO85-Qea.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-BHkry-JG.js → c4Diagram-LMCZKHZV-B32PSJlR.js} +1 -1
- package/web-dist/assets/channel-2QEC6BP8.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-BJLx0sIU.js → chunk-32BRIVSS-B-7ltfTH.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-DmMATfuh.js → chunk-52WLFC77-B68qDE3i.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-Ceg42lXA.js → chunk-C7G6YPKG-9I1vJXzs.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-CAx16zp2.js → chunk-EX3LRPZG-50O1GLJq.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-DWb22U8j.js → chunk-FWX5IMBZ-DavZMlKl.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-bKZVUQyT.js → chunk-HOUHSVGY-BGjNDQbr.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-C3i_eW5g.js → chunk-ICXQ74PX-D4yFEGU8.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-Bt6x6J8D.js → chunk-MOJQB5TN-CsQKxzjy.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-BwJ73hmt.js → chunk-OGEWGWER-B2ujmhTF.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-DfcEHpjL.js → chunk-PUDLZKDR-B_Cnk_kp.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-VYeS43tP.js → chunk-Q4XR5HBZ-CNJi7ivq.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-Cs1_YKkF.js → chunk-V7JOEXUC-qL-RXxQy.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-CCOn0B9D.js → chunk-VAUOI2AC-C_AO-SE_.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-CL_PLrUe.js → chunk-VR4S4FIN-NU1W8e1J.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-Dupt_rk_.js → chunk-WYO6CB5R-B3l7AohL.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-UwgE4khW.js → chunk-ZGVPDNZ5-gw3883_4.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-BZmKD91m.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-BZmKD91m.js +1 -0
- package/web-dist/assets/{cynefin-VYW2F7L2-DGZJYJNa.js → cynefin-VYW2F7L2-IYSCSMTe.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-B34DgCmC.js → cynefinDiagram-TSTJHNR4-CP8LReQc.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-C7HODJG3.js → dagre-VKFMJZFB-C_cDOY4h.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-CunP-lqf.js → diagram-FQU43EPY-Bdt-bbPx.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-C3O84bOk.js → diagram-G47NLZAW-D4bM_nDs.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-Dv0TR4Ic.js → diagram-NH7WQ7WH-_TSAwrFN.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-CUvcZIxp.js → diagram-OA4YK3LP-DWpUifZA.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-CwPr97NN.js → diagram-WEI45ONY-29lN_-MQ.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-Dq2O1mOC.js → ebnfDiagram-CCIWWBDH-vNac3Nkb.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-C6dJ8Dpx.js → erDiagram-Q63AITRT-Q8Lyeen0.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-DOaq-8am.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-DIB-knUj.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-sa-TSsB5.js → ganttDiagram-NO4QXBWP-wqnUf4eS.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-BdZIuH-o.js → gitGraph-TEB2WS4Q-Dp2PnWq2.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-C__OynR9.js → gitGraphDiagram-IHSO6WYX-BxGiqVQZ.js} +1 -1
- package/web-dist/assets/index-CzN-tCOl.css +1 -0
- package/web-dist/assets/index-DqXGLIwz.js +319 -0
- package/web-dist/assets/{info-DKCQHKI2-BEaDQqRu.js → info-DKCQHKI2-BhzY4Uni.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-RJlw-Bzq.js → infoDiagram-FWYZ7A6U-DOAJbOl-.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-xop_QrWs.js → ishikawaDiagram-FXEZZL3T-mBZHeiuC.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-O6nmpoje.js → journeyDiagram-5HDEW3XC-BIN13T_S.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-IXwPs_L0.js → kanban-definition-HUTT4EX6-BXIeyjSB.js} +1 -1
- package/web-dist/assets/{line-BMRguCzE.js → line-CXrqIQ_6.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-BCXFwFN1.js → mermaid-parser.core-B-ewc93S.js} +3 -3
- package/web-dist/assets/{mermaid.core-Cqq_9ya3.js → mermaid.core-4Ulv5sd_.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DEfWfnOg.js → mindmap-definition-LN4V7U3C-CUlmsgpu.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-CQGAnngT.js → packet-7NZHBO7P-DOU_OPIg.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-tOrSsHlA.js → pegDiagram-2B236MQR-ClVC15zM.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-C0KP3XF8.js → pie-RZYD4A2V-9fj9kCZg.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-FzmIWC1K.js → pieDiagram-ENE6RG2P-BQAuzbqi.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-N6zuxKqV.js → quadrantDiagram-ABIIQ3AL-B6v8OzIX.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-CB3WQRMp.js → radar-I7S5WNFK-ggR6yaoH.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-Ck8x0o2o.js → railroad-3IZDKUUU-NRPKG8Tz.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-icGR3Mej.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-7uKDbF2e.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-CJwY09fs.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-6TyF6de4.js → railroadDiagram-RFXS5EU6-CIJqRzYi.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-D6n7xpc4.js → requirementDiagram-TGXJPOKE-7Dvam68t.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-B7R6Natf.js → sankeyDiagram-HTMAVEWB-BYX8p13G.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CFbbuWF6.js → sequenceDiagram-DBY2YBRQ-Bmqmc3Hh.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-DwXu0kos.js → stateDiagram-2N3HPSRC-Cv1jr6R9.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-CWOLQn_o.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-Clie2Qk3.js → swimlanes-5IMT3BWC-BHlN3gYO.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-cd1Z89Mn.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-DWDsPzuQ.js → timeline-definition-FHXFAJF6-DO8eivQs.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-Ddd-Wxpb.js → treeView-QDETBFTQ-DOmZ_jrX.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-CuZ3jD4K.js → treemap-6X3UGDF4-BsObxxTm.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-BoE4aTMb.js → vennDiagram-L72KCM5P-BRy9Xi2q.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-CfaztQL0.js → wardley-OPB4EBWU-BYP-hY4S.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-pxcV16qp.js → wardleyDiagram-EHGQE667-BQVbSUex.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG--d5N7oDL.js → xychartDiagram-FW5EYKEG-tK-MC6wE.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
|
|
@@ -15233,7 +15290,9 @@ var init_src = __esm({
|
|
|
15233
15290
|
SetThreadProfileInput = external_exports.object({
|
|
15234
15291
|
slug: ThreadSlug,
|
|
15235
15292
|
model: external_exports.string().trim().min(1).max(200),
|
|
15236
|
-
|
|
15293
|
+
// Absent ONLY for an ACP thread, whose model slug (`acp:<agent>@<model>`) carries no effort axis; the
|
|
15294
|
+
// router refuses a Claude/Codex profile without one rather than storing "".
|
|
15295
|
+
effort: external_exports.string().trim().min(1).max(100).optional()
|
|
15237
15296
|
}).strict();
|
|
15238
15297
|
SetThreadProfileResult = external_exports.object({
|
|
15239
15298
|
effect: external_exports.enum(["applied", "next-resume"])
|
|
@@ -15379,9 +15438,9 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15379
15438
|
page: external_exports.number().int().positive(),
|
|
15380
15439
|
pageCount: external_exports.number().int().positive()
|
|
15381
15440
|
});
|
|
15382
|
-
GithubBatchInput =
|
|
15441
|
+
GithubBatchInput = DispatchProfileSnapshotShape.extend({
|
|
15383
15442
|
items: external_exports.array(external_exports.object({ kind: external_exports.enum(["issue", "pr"]), number: external_exports.number().int().positive() })).min(1)
|
|
15384
|
-
}).strict();
|
|
15443
|
+
}).strict().superRefine(requireEffortOutsideAcp);
|
|
15385
15444
|
GithubBatchResult = external_exports.object({
|
|
15386
15445
|
dispatched: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), slug: ThreadSlug })),
|
|
15387
15446
|
failed: external_exports.array(external_exports.object({ number: external_exports.number(), kind: external_exports.string(), error: external_exports.string() }))
|
|
@@ -19055,7 +19114,7 @@ var init_project_scope = __esm({
|
|
|
19055
19114
|
|
|
19056
19115
|
// packages/server/src/storage.ts
|
|
19057
19116
|
function isHeadlessRow(row) {
|
|
19058
|
-
return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker";
|
|
19117
|
+
return row.backend === "codex" && row.codex_runtime === "app-server" || row.backend === "claude" && row.claude_runtime === "broker" || row.backend === "acp";
|
|
19059
19118
|
}
|
|
19060
19119
|
function isBrokerClaudeRow(row) {
|
|
19061
19120
|
return row.backend === "claude" && row.claude_runtime === "broker";
|
|
@@ -19078,7 +19137,7 @@ function groupBySlug(rows) {
|
|
|
19078
19137
|
}
|
|
19079
19138
|
function ensureStorageSchema(db) {
|
|
19080
19139
|
db.exec(STORAGE_SCHEMA);
|
|
19081
|
-
for (const column of ["pinned_at TEXT"]) {
|
|
19140
|
+
for (const column of ["pinned_at TEXT", "acp_agent TEXT"]) {
|
|
19082
19141
|
try {
|
|
19083
19142
|
db.exec(`ALTER TABLE session ADD COLUMN ${column}`);
|
|
19084
19143
|
} catch {
|
|
@@ -19673,6 +19732,7 @@ function createStorage(source, projectId) {
|
|
|
19673
19732
|
const agentSessionStmt = scope.prepare("UPDATE session SET agent_session_id = ? WHERE project_id = @project_id AND slug = ?");
|
|
19674
19733
|
const codexRuntimeStmt = scope.prepare("UPDATE session SET codex_runtime = ? WHERE project_id = @project_id AND slug = ?");
|
|
19675
19734
|
const claudeRuntimeStmt = scope.prepare("UPDATE session SET claude_runtime = ? WHERE project_id = @project_id AND slug = ?");
|
|
19735
|
+
const acpAgentStmt = scope.prepare("UPDATE session SET acp_agent = ? WHERE project_id = @project_id AND slug = ?");
|
|
19676
19736
|
const profileStmt = scope.prepare("UPDATE session SET model = ?, effort = ?, profile_set_at = ? WHERE project_id = @project_id AND slug = ?");
|
|
19677
19737
|
const permissionModeStmt = scope.prepare("UPDATE session SET permission_mode = ?, permission_set_at = ? WHERE project_id = @project_id AND slug = ?");
|
|
19678
19738
|
const permissionPendingStmt = scope.prepare("UPDATE session SET permission_pending = ? WHERE project_id = @project_id AND slug = ?");
|
|
@@ -19802,7 +19862,8 @@ function createStorage(source, projectId) {
|
|
|
19802
19862
|
runtime_control: row.runtime_control ?? null,
|
|
19803
19863
|
runtime_control_revision: row.runtime_control_revision ?? 0,
|
|
19804
19864
|
codex_runtime: row.codex_runtime ?? null,
|
|
19805
|
-
claude_runtime: row.claude_runtime ?? null
|
|
19865
|
+
claude_runtime: row.claude_runtime ?? null,
|
|
19866
|
+
acp_agent: row.acp_agent ?? null
|
|
19806
19867
|
});
|
|
19807
19868
|
const getAdoptionRuntimeSnapshot = db.transaction((slug) => ({
|
|
19808
19869
|
// Claim first is intentional: a finalized claim disappearing before the current-row validation
|
|
@@ -19834,7 +19895,7 @@ function createStorage(source, projectId) {
|
|
|
19834
19895
|
const retireAdoptionAttempt = (claim, retiredAtMs = Date.now()) => {
|
|
19835
19896
|
putRetiredAdoptionAttempt.run(claim.attempt_token, claim.slug, claim.session_id, retiredAtMs);
|
|
19836
19897
|
};
|
|
19837
|
-
const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs,
|
|
19898
|
+
const withAdoptionSpawnFence = (slug, attemptToken, leaseExpiresAtMs, spawn8) => {
|
|
19838
19899
|
ThreadSlug.parse(slug);
|
|
19839
19900
|
if (!Number.isSafeInteger(leaseExpiresAtMs)) return { acquired: false };
|
|
19840
19901
|
db.exec("BEGIN IMMEDIATE");
|
|
@@ -19866,7 +19927,7 @@ function createStorage(source, projectId) {
|
|
|
19866
19927
|
bound = true;
|
|
19867
19928
|
return true;
|
|
19868
19929
|
};
|
|
19869
|
-
const value =
|
|
19930
|
+
const value = spawn8(bindPane);
|
|
19870
19931
|
if (!bound) {
|
|
19871
19932
|
if (db.inTransaction) db.exec("ROLLBACK");
|
|
19872
19933
|
throw new Error("adoption spawn returned without binding its exact pane");
|
|
@@ -20185,6 +20246,7 @@ function createStorage(source, projectId) {
|
|
|
20185
20246
|
setAgentSession: (slug, agentSessionId) => void agentSessionStmt.run(agentSessionId, slug),
|
|
20186
20247
|
setCodexRuntime: (slug, runtime) => void codexRuntimeStmt.run(runtime, slug),
|
|
20187
20248
|
setClaudeRuntime: (slug, runtime) => void claudeRuntimeStmt.run(runtime, slug),
|
|
20249
|
+
setAcpAgent: (slug, agentId) => void acpAgentStmt.run(agentId, slug),
|
|
20188
20250
|
setProfile: (slug, model, effort) => void profileStmt.run(model, effort, (/* @__PURE__ */ new Date()).toISOString(), slug),
|
|
20189
20251
|
setPermissionMode: (slug, permissionMode) => void permissionModeStmt.run(permissionMode, (/* @__PURE__ */ new Date()).toISOString(), slug),
|
|
20190
20252
|
setPermissionPending: (slug, permissionMode) => void permissionPendingStmt.run(permissionMode, slug),
|
|
@@ -20413,6 +20475,8 @@ var init_storage = __esm({
|
|
|
20413
20475
|
-- Claude transport discriminator: 'broker' = a session-broker-owned Agent SDK session; NULL/'tmux'
|
|
20414
20476
|
-- is the pre-broker legacy value, same story.
|
|
20415
20477
|
claude_runtime TEXT,
|
|
20478
|
+
-- ACP agent id (backend/acp-agents.ts) for backend='acp' rows; also in the ALTER list below.
|
|
20479
|
+
acp_agent TEXT,
|
|
20416
20480
|
-- THE RECURRING PROMPT (scheduler.ts SOURCES 4, 5 and 7): one text, three independent triggers \u2014
|
|
20417
20481
|
-- every time the thread rests, every N ms on a clock, and/or every time its context is compacted.
|
|
20418
20482
|
-- All flags 0 = off; there is no separate enable column, because another flag could only ever
|
|
@@ -20865,9 +20929,16 @@ function defaultDispatchPreferences(settings, codexModels = []) {
|
|
|
20865
20929
|
return {
|
|
20866
20930
|
backend,
|
|
20867
20931
|
claude: backend === "claude" ? selected : { permissionMode: "auto" },
|
|
20868
|
-
codex: backend === "codex" ? selected : { permissionMode: "default" }
|
|
20932
|
+
codex: backend === "codex" ? selected : { permissionMode: "default" },
|
|
20933
|
+
// The ACP slot exists only once the operator has chosen an agent: it has no permission axis (the
|
|
20934
|
+
// agent's own CLI decides), so there is no default worth writing, and every record written before
|
|
20935
|
+
// the backend existed lacks the key.
|
|
20936
|
+
...backend === "acp" ? { acp: selected } : {}
|
|
20869
20937
|
};
|
|
20870
20938
|
}
|
|
20939
|
+
function profileOf(prefs, backend) {
|
|
20940
|
+
return prefs[backend] ?? {};
|
|
20941
|
+
}
|
|
20871
20942
|
function getDispatchPreferences(storage, settings, home, codexModels = []) {
|
|
20872
20943
|
const machine = readMachineDispatchPreferences(home);
|
|
20873
20944
|
if (machine) return machine;
|
|
@@ -20884,13 +20955,13 @@ function setDispatchPreference(storage, settings, home, update, codexModels = []
|
|
|
20884
20955
|
...current,
|
|
20885
20956
|
backend: update.backend,
|
|
20886
20957
|
[update.backend]: {
|
|
20887
|
-
...current
|
|
20958
|
+
...profileOf(current, update.backend),
|
|
20888
20959
|
model: update.model,
|
|
20889
20960
|
effort: update.effort
|
|
20890
20961
|
}
|
|
20891
20962
|
};
|
|
20892
20963
|
} else {
|
|
20893
|
-
const profile = current
|
|
20964
|
+
const profile = profileOf(current, update.backend);
|
|
20894
20965
|
next = {
|
|
20895
20966
|
...current,
|
|
20896
20967
|
...update.field === "model" ? { backend: update.backend } : {},
|
|
@@ -20928,8 +20999,8 @@ function userInfoParts(raw2) {
|
|
|
20928
20999
|
const encoded = encodedMatch?.index ?? -1;
|
|
20929
21000
|
const index = literal === -1 ? encoded : encoded === -1 ? literal : Math.min(literal, encoded);
|
|
20930
21001
|
if (index <= 0) return null;
|
|
20931
|
-
const
|
|
20932
|
-
return { username: value.slice(0, index), delimiter:
|
|
21002
|
+
const delimiter3 = value.slice(index, index + (index === encoded ? 3 : 1));
|
|
21003
|
+
return { username: value.slice(0, index), delimiter: delimiter3, password: value.slice(index + delimiter3.length) };
|
|
20933
21004
|
}
|
|
20934
21005
|
function redactUserInfoToken(raw2, options) {
|
|
20935
21006
|
const parts = userInfoParts(raw2);
|
|
@@ -20952,11 +21023,11 @@ function redactCredentialSyntax(raw2, options = {}) {
|
|
|
20952
21023
|
let value = raw2;
|
|
20953
21024
|
value = value.replace(
|
|
20954
21025
|
URL_USERINFO_RE,
|
|
20955
|
-
(_whole, scheme, username,
|
|
21026
|
+
(_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
|
|
20956
21027
|
);
|
|
20957
21028
|
value = value.replace(
|
|
20958
21029
|
URL_ENCODED_USERINFO_RE,
|
|
20959
|
-
(_whole, scheme, username,
|
|
21030
|
+
(_whole, scheme, username, delimiter3, password) => `${scheme}${username}${delimiter3}${replacement(options, password)}@`
|
|
20960
21031
|
);
|
|
20961
21032
|
value = value.replace(
|
|
20962
21033
|
USER_FLAG_RE,
|
|
@@ -21605,6 +21676,7 @@ function threadProfileOptions(backend) {
|
|
|
21605
21676
|
}))
|
|
21606
21677
|
};
|
|
21607
21678
|
}
|
|
21679
|
+
if (backend === "acp") return { backend, options: [] };
|
|
21608
21680
|
throw new Error("This thread has an unknown backend; its runtime profile cannot be changed");
|
|
21609
21681
|
}
|
|
21610
21682
|
function validateThreadProfile(backend, model, effort) {
|
|
@@ -24897,7 +24969,7 @@ ${ask}`;
|
|
|
24897
24969
|
void nowMs;
|
|
24898
24970
|
void backend;
|
|
24899
24971
|
if (state.foreign) return { permPrompt: false };
|
|
24900
|
-
if (turn === "in-flight" && row.backend !== "codex" && permMarkerBlocks(state, row)) {
|
|
24972
|
+
if (turn === "in-flight" && row.backend !== "codex" && row.backend !== "acp" && permMarkerBlocks(state, row)) {
|
|
24901
24973
|
return { permPrompt: true };
|
|
24902
24974
|
}
|
|
24903
24975
|
return { permPrompt: false };
|
|
@@ -25221,7 +25293,7 @@ ${ask}`;
|
|
|
25221
25293
|
if (state.stallLogged) return;
|
|
25222
25294
|
if (row.exited && rowIsArchived(row)) return;
|
|
25223
25295
|
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)";
|
|
25296
|
+
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
25297
|
log.error(
|
|
25226
25298
|
"tailer",
|
|
25227
25299
|
`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 +25439,7 @@ ${detail2}
|
|
|
25367
25439
|
if (primeProgress && primed % PRIME_PROGRESS_EVERY === 0) primeProgress(primed, rows.length);
|
|
25368
25440
|
primed++;
|
|
25369
25441
|
const backend = resolveBackend(row.backend);
|
|
25370
|
-
const nativeId = row.agent_session_id ?? row.transcript_id ?? row.session_id;
|
|
25442
|
+
const nativeId = row.backend === "acp" ? row.session_id : row.agent_session_id ?? row.transcript_id ?? row.session_id;
|
|
25371
25443
|
const known = states.get(row.slug);
|
|
25372
25444
|
const runtimeGeneration = row.runtime_generation ?? 0;
|
|
25373
25445
|
const keepable = known !== void 0 && known.sessionId === row.session_id && known.nativeSessionId === nativeId && known.runtimeGeneration === runtimeGeneration;
|
|
@@ -25388,7 +25460,7 @@ ${detail2}
|
|
|
25388
25460
|
states.set(row.slug, state);
|
|
25389
25461
|
}
|
|
25390
25462
|
const prevNoTranscript = state.noTranscript;
|
|
25391
|
-
if (row.backend !== "codex" && !resolveTranscript(state, row, nowMs)) continue;
|
|
25463
|
+
if (row.backend !== "codex" && row.backend !== "acp" && !resolveTranscript(state, row, nowMs)) continue;
|
|
25392
25464
|
if (!state.primed) {
|
|
25393
25465
|
if (primeDeferred(row)) {
|
|
25394
25466
|
primeIncomplete = true;
|
|
@@ -26546,19 +26618,19 @@ function searchPath2(env) {
|
|
|
26546
26618
|
function vendoredNativeBeside(launcherJs, arch, access) {
|
|
26547
26619
|
const triple = WINDOWS_TRIPLE[arch];
|
|
26548
26620
|
if (!triple) return void 0;
|
|
26549
|
-
const { dirname:
|
|
26550
|
-
const pkgRoot =
|
|
26621
|
+
const { dirname: dirname27, join: join49 } = win322;
|
|
26622
|
+
const pkgRoot = dirname27(dirname27(launcherJs));
|
|
26551
26623
|
const platformPkg = `codex-win32-${arch}`;
|
|
26552
26624
|
const vendorRoots = [
|
|
26553
|
-
|
|
26625
|
+
join49(pkgRoot, "node_modules", "@openai", platformPkg, "vendor"),
|
|
26554
26626
|
// nested under the package
|
|
26555
|
-
|
|
26627
|
+
join49(dirname27(pkgRoot), platformPkg, "vendor"),
|
|
26556
26628
|
// hoisted beside it: <prefix>\node_modules\@openai\<pkg>
|
|
26557
|
-
|
|
26629
|
+
join49(pkgRoot, "vendor")
|
|
26558
26630
|
// the pre-platform-package layout, binaries inside @openai/codex itself
|
|
26559
26631
|
];
|
|
26560
26632
|
for (const root of vendorRoots) {
|
|
26561
|
-
const exe =
|
|
26633
|
+
const exe = join49(root, triple, "bin", "codex.exe");
|
|
26562
26634
|
if (access(exe, "exists")) return exe;
|
|
26563
26635
|
}
|
|
26564
26636
|
return void 0;
|
|
@@ -31659,6 +31731,62 @@ var init_auth_status = __esm({
|
|
|
31659
31731
|
}
|
|
31660
31732
|
});
|
|
31661
31733
|
|
|
31734
|
+
// packages/server/src/backend/codex-execution-health.ts
|
|
31735
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
31736
|
+
function probeHostMacOSServices() {
|
|
31737
|
+
return new Promise((resolve12) => {
|
|
31738
|
+
execFile5(MACOS_SERVICE_PROBE[0], MACOS_SERVICE_PROBE.slice(1), {
|
|
31739
|
+
timeout: MACOS_SERVICE_PROBE_TIMEOUT_MS,
|
|
31740
|
+
killSignal: "SIGKILL",
|
|
31741
|
+
maxBuffer: 64 * 1024
|
|
31742
|
+
}, (error) => resolve12(!error));
|
|
31743
|
+
});
|
|
31744
|
+
}
|
|
31745
|
+
var MACOS_SERVICE_PROBE, MACOS_SERVICE_PROBE_TIMEOUT_MS, CodexExecutionHealth;
|
|
31746
|
+
var init_codex_execution_health = __esm({
|
|
31747
|
+
"packages/server/src/backend/codex-execution-health.ts"() {
|
|
31748
|
+
"use strict";
|
|
31749
|
+
MACOS_SERVICE_PROBE = ["/bin/launchctl", "print", "system/com.apple.configd"];
|
|
31750
|
+
MACOS_SERVICE_PROBE_TIMEOUT_MS = 3e3;
|
|
31751
|
+
CodexExecutionHealth = class {
|
|
31752
|
+
constructor(platform3 = process.platform, hostProbe = probeHostMacOSServices) {
|
|
31753
|
+
this.platform = platform3;
|
|
31754
|
+
this.hostProbe = hostProbe;
|
|
31755
|
+
}
|
|
31756
|
+
checking = /* @__PURE__ */ new WeakMap();
|
|
31757
|
+
check(connection, scope) {
|
|
31758
|
+
if (this.platform !== "darwin") return Promise.resolve();
|
|
31759
|
+
const pending = this.checking.get(connection);
|
|
31760
|
+
if (pending) return pending;
|
|
31761
|
+
const check = this.probe(connection, scope);
|
|
31762
|
+
this.checking.set(connection, check);
|
|
31763
|
+
void check.finally(() => this.checking.delete(connection)).catch(() => void 0);
|
|
31764
|
+
return check;
|
|
31765
|
+
}
|
|
31766
|
+
async probe(connection, scope) {
|
|
31767
|
+
let detail2;
|
|
31768
|
+
try {
|
|
31769
|
+
const raw2 = await connection.request("command/exec", {
|
|
31770
|
+
command: MACOS_SERVICE_PROBE,
|
|
31771
|
+
cwd: "/",
|
|
31772
|
+
timeoutMs: MACOS_SERVICE_PROBE_TIMEOUT_MS,
|
|
31773
|
+
sandboxPolicy: { type: "dangerFullAccess" }
|
|
31774
|
+
}, MACOS_SERVICE_PROBE_TIMEOUT_MS + 1e3);
|
|
31775
|
+
const result = raw2;
|
|
31776
|
+
if (result?.exitCode === 0 && typeof result.stdout === "string" && result.stdout.includes("system/com.apple.configd = {")) return;
|
|
31777
|
+
detail2 = typeof result?.exitCode === "number" ? `launchctl exited ${result.exitCode}` : "invalid probe response";
|
|
31778
|
+
} catch {
|
|
31779
|
+
detail2 = "the local service probe failed or timed out";
|
|
31780
|
+
}
|
|
31781
|
+
const hostHealthy = await this.hostProbe().catch(() => false);
|
|
31782
|
+
throw new Error(
|
|
31783
|
+
`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.")
|
|
31784
|
+
);
|
|
31785
|
+
}
|
|
31786
|
+
};
|
|
31787
|
+
}
|
|
31788
|
+
});
|
|
31789
|
+
|
|
31662
31790
|
// packages/server/src/backend/codex-app-server.ts
|
|
31663
31791
|
import { createHash as createHash10, randomUUID as randomUUID11 } from "node:crypto";
|
|
31664
31792
|
import { StringDecoder as StringDecoder3 } from "node:string_decoder";
|
|
@@ -32320,15 +32448,16 @@ var init_codex_app_server = __esm({
|
|
|
32320
32448
|
init_auth_status();
|
|
32321
32449
|
init_codex_mcp();
|
|
32322
32450
|
init_codex_error();
|
|
32451
|
+
init_codex_execution_health();
|
|
32323
32452
|
init_types2();
|
|
32324
32453
|
init_logging();
|
|
32325
32454
|
CODEX_APP_SERVER_PROVIDER = "codex-app-server";
|
|
32326
32455
|
CODEX_NATIVE_LISTEN_FLAG = "FRIZZ_CODEX_NATIVE_LISTEN";
|
|
32327
|
-
CODEX_APP_SERVER_SUPPORTED_VERSION = "0.
|
|
32456
|
+
CODEX_APP_SERVER_SUPPORTED_VERSION = "0.155.0";
|
|
32328
32457
|
CODEX_APP_SERVER_PROTOCOL_REVISION = Object.freeze({
|
|
32329
32458
|
packageVersion: CODEX_APP_SERVER_SUPPORTED_VERSION,
|
|
32330
|
-
sourceTag: "rust-v0.
|
|
32331
|
-
sourceCommit: "
|
|
32459
|
+
sourceTag: "rust-v0.155.0",
|
|
32460
|
+
sourceCommit: "f0a1b8f0849d90960bc406b848f32e5a129b0457"
|
|
32332
32461
|
});
|
|
32333
32462
|
PROTOCOL_FINGERPRINT = [
|
|
32334
32463
|
CODEX_APP_SERVER_PROTOCOL_REVISION.sourceTag,
|
|
@@ -32603,7 +32732,7 @@ var init_codex_app_server = __esm({
|
|
|
32603
32732
|
child.on("exit", () => this.fail("exit", new Error("Codex app-server exited")));
|
|
32604
32733
|
child.on("error", () => this.fail("error", new Error("Codex app-server process failed")));
|
|
32605
32734
|
}
|
|
32606
|
-
async request(method, params) {
|
|
32735
|
+
async request(method, params, timeoutMs = this.timeoutMs) {
|
|
32607
32736
|
if (this.closed) throw new Error("Codex app-server connection is closed");
|
|
32608
32737
|
if (this.pending.size >= MAX_OUTBOUND_REQUESTS) throw new Error("Codex app-server outbound request queue is full");
|
|
32609
32738
|
if (!Number.isSafeInteger(this.nextId)) throw new Error("Codex app-server request id space is exhausted");
|
|
@@ -32612,7 +32741,7 @@ var init_codex_app_server = __esm({
|
|
|
32612
32741
|
const timer = setTimeout(() => {
|
|
32613
32742
|
this.pending.delete(id);
|
|
32614
32743
|
reject(new Error(`Codex app-server request timed out: ${method}`));
|
|
32615
|
-
},
|
|
32744
|
+
}, timeoutMs);
|
|
32616
32745
|
this.pending.set(id, { resolve: resolve12, reject, timer });
|
|
32617
32746
|
});
|
|
32618
32747
|
try {
|
|
@@ -32887,6 +33016,8 @@ var init_codex_app_server = __esm({
|
|
|
32887
33016
|
host;
|
|
32888
33017
|
codexBin;
|
|
32889
33018
|
timeoutMs;
|
|
33019
|
+
executionHealth;
|
|
33020
|
+
executionScope = "";
|
|
32890
33021
|
/** Identity of the app-server PROCESS behind the current connection (see CodexAppServerAttachment). */
|
|
32891
33022
|
daemonGeneration = "";
|
|
32892
33023
|
/** ChatGPT account the current app-server cached when its process started. */
|
|
@@ -32936,6 +33067,7 @@ var init_codex_app_server = __esm({
|
|
|
32936
33067
|
operationWaiters = /* @__PURE__ */ new Set();
|
|
32937
33068
|
shutdownPromise = null;
|
|
32938
33069
|
constructor(options) {
|
|
33070
|
+
this.executionHealth = options.executionHealth ?? new CodexExecutionHealth();
|
|
32939
33071
|
this.options = options;
|
|
32940
33072
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
32941
33073
|
this.makeId = options.id ?? randomUUID11;
|
|
@@ -33172,6 +33304,7 @@ var init_codex_app_server = __esm({
|
|
|
33172
33304
|
const releaseOperation = this.beginOperation();
|
|
33173
33305
|
try {
|
|
33174
33306
|
const connection = await this.ensureConnected();
|
|
33307
|
+
await this.checkExecutionHealth(connection);
|
|
33175
33308
|
const binding = this.bindingForScope(input.threadSlug, input.sessionId);
|
|
33176
33309
|
if (!binding) throw new Error("Codex app-server steer requires a bridge-owned session");
|
|
33177
33310
|
if (binding.connection_epoch !== this.connectionEpoch || binding.state !== "active") {
|
|
@@ -33365,7 +33498,8 @@ var init_codex_app_server = __esm({
|
|
|
33365
33498
|
if (bound === 0) return;
|
|
33366
33499
|
const releaseOperation = this.beginOperation();
|
|
33367
33500
|
try {
|
|
33368
|
-
await this.ensureConnected();
|
|
33501
|
+
const connection = await this.ensureConnected();
|
|
33502
|
+
await this.checkExecutionHealth(connection);
|
|
33369
33503
|
await this.autoResumeInterruptedTurns();
|
|
33370
33504
|
} catch {
|
|
33371
33505
|
} finally {
|
|
@@ -33609,6 +33743,7 @@ var init_codex_app_server = __esm({
|
|
|
33609
33743
|
for (let attempt = 0; ; attempt++) {
|
|
33610
33744
|
const desiredAccountId = (this.options.codexAuthAccountId ?? readCodexAccountId)();
|
|
33611
33745
|
const connection = await this.ensureConnected();
|
|
33746
|
+
await this.checkExecutionHealth(connection);
|
|
33612
33747
|
if (!desiredAccountId || this.processAuthAccountId === desiredAccountId) return connection;
|
|
33613
33748
|
if (attempt >= MAX_AUTH_REPLACEMENT_ATTEMPTS) return connection;
|
|
33614
33749
|
if (!this.authReplacement) {
|
|
@@ -33621,6 +33756,17 @@ var init_codex_app_server = __esm({
|
|
|
33621
33756
|
if (!await this.awaitAuthReplacement(this.authReplacement)) return connection;
|
|
33622
33757
|
}
|
|
33623
33758
|
}
|
|
33759
|
+
async checkExecutionHealth(connection) {
|
|
33760
|
+
try {
|
|
33761
|
+
await this.executionHealth.check(connection, this.executionScope);
|
|
33762
|
+
} catch (error) {
|
|
33763
|
+
const detail2 = error.message;
|
|
33764
|
+
this.options.diagnostic?.({ event: "execution-health-failed", detail: detail2 });
|
|
33765
|
+
log.error("codex", detail2);
|
|
33766
|
+
throw error;
|
|
33767
|
+
}
|
|
33768
|
+
if (this.connection !== connection) throw new Error("Codex app-server changed during its execution health check");
|
|
33769
|
+
}
|
|
33624
33770
|
/** Await an armed replacement for at most AUTH_REPLACEMENT_WAIT_MS. `false` = it is still waiting for
|
|
33625
33771
|
* the project to go idle, so the caller should proceed rather than hang; a rejection propagates. */
|
|
33626
33772
|
async awaitAuthReplacement(replacement2) {
|
|
@@ -33695,6 +33841,7 @@ var init_codex_app_server = __esm({
|
|
|
33695
33841
|
authAccountId
|
|
33696
33842
|
});
|
|
33697
33843
|
const child = attachment.process;
|
|
33844
|
+
this.executionScope = `project ${this.options.projectDir}, backend PID ${attachment.daemonPid}, generation ${attachment.generation}`;
|
|
33698
33845
|
let connection;
|
|
33699
33846
|
connection = new JsonlRpcConnection(
|
|
33700
33847
|
child,
|
|
@@ -35059,11 +35206,21 @@ var init_quota = __esm({
|
|
|
35059
35206
|
}
|
|
35060
35207
|
});
|
|
35061
35208
|
|
|
35209
|
+
// packages/server/src/local-path.ts
|
|
35210
|
+
function normalizeLocalPath(path3, platform3 = process.platform) {
|
|
35211
|
+
return platform3 === "win32" ? path3.replace(/^\/([a-zA-Z]:[\\/])/, "$1") : path3;
|
|
35212
|
+
}
|
|
35213
|
+
var init_local_path = __esm({
|
|
35214
|
+
"packages/server/src/local-path.ts"() {
|
|
35215
|
+
"use strict";
|
|
35216
|
+
}
|
|
35217
|
+
});
|
|
35218
|
+
|
|
35062
35219
|
// packages/server/src/local-file.ts
|
|
35063
35220
|
import { spawn as spawn5 } from "node:child_process";
|
|
35064
35221
|
import { existsSync as existsSync8, readFileSync as readFileSync18, realpathSync as realpathSync6, statSync as statSync13 } from "node:fs";
|
|
35065
35222
|
import { homedir as homedir11 } from "node:os";
|
|
35066
|
-
import { isAbsolute as isAbsolute4, join as join26, resolve as resolve5, sep as sep2 } from "node:path";
|
|
35223
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, join as join26, resolve as resolve5, sep as sep2 } from "node:path";
|
|
35067
35224
|
function isUnder(real, root) {
|
|
35068
35225
|
let rootReal;
|
|
35069
35226
|
try {
|
|
@@ -35071,9 +35228,25 @@ function isUnder(real, root) {
|
|
|
35071
35228
|
} catch {
|
|
35072
35229
|
return false;
|
|
35073
35230
|
}
|
|
35074
|
-
|
|
35231
|
+
if (process.platform !== "win32") {
|
|
35232
|
+
return real === rootReal || real.startsWith(rootReal.endsWith(sep2) ? rootReal : rootReal + sep2);
|
|
35233
|
+
}
|
|
35234
|
+
try {
|
|
35235
|
+
const trusted = statSync13(rootReal, { bigint: true });
|
|
35236
|
+
if (!trusted.isDirectory() || trusted.ino === 0n) return false;
|
|
35237
|
+
for (let ancestor = real; ; ) {
|
|
35238
|
+
const actual = statSync13(ancestor, { bigint: true });
|
|
35239
|
+
if (actual.dev === trusted.dev && actual.ino === trusted.ino) return true;
|
|
35240
|
+
const parent = dirname10(ancestor);
|
|
35241
|
+
if (parent === ancestor) return false;
|
|
35242
|
+
ancestor = parent;
|
|
35243
|
+
}
|
|
35244
|
+
} catch {
|
|
35245
|
+
return false;
|
|
35246
|
+
}
|
|
35075
35247
|
}
|
|
35076
35248
|
function resolveLocalFile(rawPath, roots) {
|
|
35249
|
+
rawPath = normalizeLocalPath(rawPath);
|
|
35077
35250
|
if (!isAbsolute4(rawPath)) throw new Error("Local path must be absolute");
|
|
35078
35251
|
let real;
|
|
35079
35252
|
try {
|
|
@@ -35178,6 +35351,7 @@ var MARKDOWN_FILE_EXT, MARKDOWN_READ_LIMIT, WINDOWS_EDITOR_EXE;
|
|
|
35178
35351
|
var init_local_file = __esm({
|
|
35179
35352
|
"packages/server/src/local-file.ts"() {
|
|
35180
35353
|
"use strict";
|
|
35354
|
+
init_local_path();
|
|
35181
35355
|
MARKDOWN_FILE_EXT = /\.(?:md|mdx|markdown)$/i;
|
|
35182
35356
|
MARKDOWN_READ_LIMIT = 1024 * 1024;
|
|
35183
35357
|
WINDOWS_EDITOR_EXE = {
|
|
@@ -35219,9 +35393,9 @@ var init_thread_links = __esm({
|
|
|
35219
35393
|
});
|
|
35220
35394
|
|
|
35221
35395
|
// packages/server/src/frizz.ts
|
|
35222
|
-
import { execFile as
|
|
35396
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
35223
35397
|
import { lstatSync as lstatSync2, realpathSync as realpathSync7 } from "node:fs";
|
|
35224
|
-
import { basename as basename5, dirname as
|
|
35398
|
+
import { basename as basename5, dirname as dirname11, join as join27, resolve as resolve6 } from "node:path";
|
|
35225
35399
|
import { promisify as promisify5 } from "node:util";
|
|
35226
35400
|
function frizzScriptsDir() {
|
|
35227
35401
|
if (process.env.FRIZZ_SCRIPTS_DIR) return process.env.FRIZZ_SCRIPTS_DIR;
|
|
@@ -35234,7 +35408,7 @@ function directFrizzRoot(projectDir) {
|
|
|
35234
35408
|
const stat2 = lstatSync2(path3);
|
|
35235
35409
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
35236
35410
|
const real = realpathSync7(path3);
|
|
35237
|
-
return
|
|
35411
|
+
return dirname11(real) === projectRoot && basename5(real) === ".frizz" ? real : null;
|
|
35238
35412
|
} catch {
|
|
35239
35413
|
return null;
|
|
35240
35414
|
}
|
|
@@ -35279,7 +35453,7 @@ var init_frizz = __esm({
|
|
|
35279
35453
|
"packages/server/src/frizz.ts"() {
|
|
35280
35454
|
"use strict";
|
|
35281
35455
|
init_src();
|
|
35282
|
-
execFileP = promisify5(
|
|
35456
|
+
execFileP = promisify5(execFile6);
|
|
35283
35457
|
}
|
|
35284
35458
|
});
|
|
35285
35459
|
|
|
@@ -35510,17 +35684,17 @@ var init_project_mcp_servers = __esm({
|
|
|
35510
35684
|
|
|
35511
35685
|
// packages/server/src/worker-plugin-dir.ts
|
|
35512
35686
|
import { existsSync as existsSync9 } from "node:fs";
|
|
35513
|
-
import { dirname as
|
|
35687
|
+
import { dirname as dirname12, join as join29 } from "node:path";
|
|
35514
35688
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
35515
35689
|
function resolveWorkerPluginDir(moduleUrl = import.meta.url, env = process.env) {
|
|
35516
35690
|
const override = env.FRIZZ_WORKER_PLUGIN_DIR;
|
|
35517
35691
|
if (override && existsSync9(join29(override, ".claude-plugin", "plugin.json")))
|
|
35518
35692
|
return override;
|
|
35519
|
-
let current =
|
|
35693
|
+
let current = dirname12(fileURLToPath3(moduleUrl));
|
|
35520
35694
|
for (; ; ) {
|
|
35521
35695
|
const candidate = join29(current, "cc-worker");
|
|
35522
35696
|
if (existsSync9(join29(candidate, ".claude-plugin", "plugin.json"))) return candidate;
|
|
35523
|
-
const parent =
|
|
35697
|
+
const parent = dirname12(current);
|
|
35524
35698
|
if (parent === current) return void 0;
|
|
35525
35699
|
current = parent;
|
|
35526
35700
|
}
|
|
@@ -35571,6 +35745,12 @@ var init_workerPrompt = __esm({
|
|
|
35571
35745
|
"codex": {
|
|
35572
35746
|
"SESSION_KIND": "codex",
|
|
35573
35747
|
"RESUME_CMD": "codex resume"
|
|
35748
|
+
},
|
|
35749
|
+
// An ACP agent has no resume command Frizz can name: the session lives inside the agent and Frizz
|
|
35750
|
+
// re-opens it over the protocol. The token still has to resolve, so it names the mechanism.
|
|
35751
|
+
"acp": {
|
|
35752
|
+
"SESSION_KIND": "coding-agent",
|
|
35753
|
+
"RESUME_CMD": "(Frizz re-opens the session over ACP)"
|
|
35574
35754
|
}
|
|
35575
35755
|
};
|
|
35576
35756
|
INTRO = `You are a dispatched worker agent \u2014 a top-level \`{{FRIZZ_SESSION_KIND}}\` session frizz spawned to drive ONE
|
|
@@ -36102,7 +36282,23 @@ emptied window. Whether to keep notes, and whether to arm anything, is yours to
|
|
|
36102
36282
|
even with \`fork_turns: "none"\`, and one undifferentiated "keep the doc current" mandate is what once
|
|
36103
36283
|
made a child replace a whole shared document with its task notes and then delete the replacement as a
|
|
36104
36284
|
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
|
|
36285
|
+
is nothing to merge, so there is nothing to clobber. Never edit or delete a file another agent wrote.`,
|
|
36286
|
+
acp: `## Your scratch directory
|
|
36287
|
+
|
|
36288
|
+
\`.frizz/threads/<session-id>/\` (exact path in your session-start context) \u2014 a folder that is YOURS, for
|
|
36289
|
+
as many files as you like, in whatever format you like. It starts EMPTY and nothing is expected in it.
|
|
36290
|
+
Frizz reads nothing here automatically.
|
|
36291
|
+
|
|
36292
|
+
**IT IS OPTIONAL, IT IS NOT A DELIVERABLE, AND WRITING NOTES IS NOT DOING THE WORK.** It exists in case
|
|
36293
|
+
you want it. A single direct task usually needs nothing here: just do the task. Never let a note stand
|
|
36294
|
+
in for an action \u2014 recording "next: X" when the human asked for X is not progress on X, and a turn that
|
|
36295
|
+
ends right after a scratch write is nearly always a turn that stopped for no reason. When something is
|
|
36296
|
+
worth writing, write it AS YOU GO, mid-work, and then keep working.
|
|
36297
|
+
|
|
36298
|
+
**Notes here can come back after a compaction, if you want that.** Nothing points you at this directory
|
|
36299
|
+
once your context is summarized \u2014 but the frizz \`goal\` tool can arm a goal, and one armed with
|
|
36300
|
+
\`post_compaction: true\` re-sends a prompt of your choosing (which can link a file here) into the
|
|
36301
|
+
emptied window. Whether to keep notes, and whether to arm anything, is yours to decide per effort.`
|
|
36106
36302
|
};
|
|
36107
36303
|
BACKEND = {
|
|
36108
36304
|
claude: `## Sub-agents
|
|
@@ -36361,7 +36557,31 @@ The sandbox governs what you may touch, and a denial is the
|
|
|
36361
36557
|
sandbox \u2014 not a bug: \`read-only\` (inspect, never write), \`workspace-write\` (edit inside the repo,
|
|
36362
36558
|
denied outside), or \`danger-full-access\` (unrestricted). Approvals are off (\`approvalPolicy: never\`), so a
|
|
36363
36559
|
sandbox-denied action fails straight back to you rather than prompting a human \u2014 adapt, or surface
|
|
36364
|
-
the blocker in your final message
|
|
36560
|
+
the blocker in your final message.`,
|
|
36561
|
+
acp: `## Your harness, and the frizz tools inside it
|
|
36562
|
+
|
|
36563
|
+
You are running as a coding agent that Frizz drives over the Agent Client Protocol (ACP). Frizz did
|
|
36564
|
+
not build your harness: your file, shell, search and sub-agent tools are your own, your permission
|
|
36565
|
+
prompts are your harness's (Frizz shows each one to the human as a card and relays the answer), and
|
|
36566
|
+
Frizz's own tools reach you as an MCP server named \`frizz\`. Your harness spells its tools with its own
|
|
36567
|
+
prefix \u2014 \`frizz_ask\`, \`frizz_done\`, \`frizz_watch\` \u2026 on OpenCode; \`mcp__frizz__ask\` on others \u2014 so
|
|
36568
|
+
wherever this contract writes \`mcp__frizz__<verb>\`, call the \`<verb>\` tool of the \`frizz\` server the
|
|
36569
|
+
way your harness lists it. Every verb below exists there: \`ask\`, \`unask\`, \`done\`, \`watch\`,
|
|
36570
|
+
\`unwatch\`, \`watch_pr\`, \`timer\`, \`goal\`, \`title\`, \`link\`, \`unlink\`, \`activity\`, \`spawn_thread\`.
|
|
36571
|
+
|
|
36572
|
+
## Sub-agents
|
|
36573
|
+
|
|
36574
|
+
Frizz does not dispatch sub-agents for you and cannot see the ones your harness runs, so a helper you
|
|
36575
|
+
spawn through your own tools is invisible on the dashboard until you report what it found. Prefer to
|
|
36576
|
+
do the work inline; when you do fan out, collect every child's result before you rest and fold it into
|
|
36577
|
+
your own write-up. \`SendMessage\` and the \`Agent\` tool named elsewhere in this contract are Claude
|
|
36578
|
+
Code's \u2014 use your harness's equivalent, or none.
|
|
36579
|
+
|
|
36580
|
+
## Follow-ups arrive between turns
|
|
36581
|
+
|
|
36582
|
+
A message the human sends while you are working is QUEUED and delivered as your next turn the moment
|
|
36583
|
+
this one ends \u2014 nothing can interrupt you mid-turn except a stop. So rest promptly when a turn's work is
|
|
36584
|
+
done rather than idling inside it, and read the newest human message first when a turn opens.`
|
|
36365
36585
|
};
|
|
36366
36586
|
SPAWN_THREAD = `## Spawning a separate frizz thread
|
|
36367
36587
|
|
|
@@ -36452,12 +36672,98 @@ Dispatches share a vocabulary for the deliverable and quality bar, not for fleet
|
|
|
36452
36672
|
For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
|
|
36453
36673
|
call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
|
|
36454
36674
|
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
|
|
36675
|
+
advice is evidence to judge, not a verdict to copy. Depth scales with blast radius.`,
|
|
36676
|
+
acp: `## Thread types
|
|
36677
|
+
|
|
36678
|
+
Recognize which KIND of effort you own and match the deliverable to it:
|
|
36679
|
+
|
|
36680
|
+
- **Research** \u2014 find out what's true. Deliverable is FINDINGS: traces, measurements, exact paths and
|
|
36681
|
+
errors, each load-bearing claim carrying a primary-source \`file:line\` or URL you actually opened. A
|
|
36682
|
+
bug investigation is headed for a FIX, so close with ranked fix options and one recommendation.
|
|
36683
|
+
- **Audit** \u2014 adversarially verify something that exists. Check every prong against the reference,
|
|
36684
|
+
re-verify load-bearing verdicts, cite evidence. Complete = every prong checked.
|
|
36685
|
+
- **Implementation** \u2014 land a DECIDED thing. Plan briefly \u2192 implement \u2192 run the repo's gates \u2192
|
|
36686
|
+
self-review the diff \u2192 fold in every real finding. Complete = MERGED into the project's mainline with
|
|
36687
|
+
docs updated and gates green.
|
|
36688
|
+
- **Planning** \u2014 the DESIGN is the deliverable: a durable plan file, open questions surfaced and then
|
|
36689
|
+
resolved into decisions in that file. A plan that exists only in chat has not been written.
|
|
36690
|
+
|
|
36691
|
+
## Substantive implementation
|
|
36692
|
+
|
|
36693
|
+
For a non-trivial change: plan, implement, run the repo's build/lint/test gates, inspect every changed
|
|
36694
|
+
call site and downstream effect, self-review the diff, fix confirmed findings, and rerun affected
|
|
36695
|
+
checks. Depth scales with blast radius and yields to the project's conventions.`
|
|
36456
36696
|
};
|
|
36457
36697
|
MONITORS_DIR_FALLBACK = "<frizz>/cc-worker/skills/gh/scripts";
|
|
36458
36698
|
}
|
|
36459
36699
|
});
|
|
36460
36700
|
|
|
36701
|
+
// packages/server/src/backend/acp-agents.ts
|
|
36702
|
+
import { accessSync as accessSync3, constants, statSync as statSync14 } from "node:fs";
|
|
36703
|
+
import { delimiter as delimiter2, isAbsolute as isAbsolute6, join as join30 } from "node:path";
|
|
36704
|
+
function isExecutableFile(path3) {
|
|
36705
|
+
try {
|
|
36706
|
+
if (!statSync14(path3).isFile()) return false;
|
|
36707
|
+
if (process.platform !== "win32") accessSync3(path3, constants.X_OK);
|
|
36708
|
+
return true;
|
|
36709
|
+
} catch {
|
|
36710
|
+
return false;
|
|
36711
|
+
}
|
|
36712
|
+
}
|
|
36713
|
+
function findOnPath(command, env = process.env) {
|
|
36714
|
+
if (isAbsolute6(command)) return isExecutableFile(command) ? command : void 0;
|
|
36715
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean);
|
|
36716
|
+
const exts = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")] : [""];
|
|
36717
|
+
for (const dir of dirs) {
|
|
36718
|
+
for (const ext of exts) {
|
|
36719
|
+
const candidate = join30(dir, command + ext.toLowerCase());
|
|
36720
|
+
if (isExecutableFile(candidate)) return candidate;
|
|
36721
|
+
if (ext && isExecutableFile(join30(dir, command + ext))) return join30(dir, command + ext);
|
|
36722
|
+
}
|
|
36723
|
+
}
|
|
36724
|
+
return void 0;
|
|
36725
|
+
}
|
|
36726
|
+
function acpAgentSpecs(custom2) {
|
|
36727
|
+
const byId = /* @__PURE__ */ new Map();
|
|
36728
|
+
for (const spec of ACP_AGENT_CATALOGUE) byId.set(spec.id, spec);
|
|
36729
|
+
for (const spec of custom2 ?? []) if (spec.id && spec.command) byId.set(spec.id, { ...spec, args: [...spec.args ?? []] });
|
|
36730
|
+
return [...byId.values()];
|
|
36731
|
+
}
|
|
36732
|
+
function listAcpAgents(custom2, env = process.env) {
|
|
36733
|
+
return acpAgentSpecs(custom2).map((spec) => {
|
|
36734
|
+
const bin = findOnPath(spec.command, env);
|
|
36735
|
+
return bin ? { ...spec, bin } : { ...spec };
|
|
36736
|
+
});
|
|
36737
|
+
}
|
|
36738
|
+
function resolveAcpAgent(id, custom2, env = process.env) {
|
|
36739
|
+
const spec = acpAgentSpecs(custom2).find((s) => s.id === id);
|
|
36740
|
+
if (!spec) return void 0;
|
|
36741
|
+
const bin = findOnPath(spec.command, env);
|
|
36742
|
+
return bin ? { ...spec, bin } : { ...spec };
|
|
36743
|
+
}
|
|
36744
|
+
var ACP_AGENT_CATALOGUE;
|
|
36745
|
+
var init_acp_agents = __esm({
|
|
36746
|
+
"packages/server/src/backend/acp-agents.ts"() {
|
|
36747
|
+
"use strict";
|
|
36748
|
+
init_src();
|
|
36749
|
+
ACP_AGENT_CATALOGUE = [
|
|
36750
|
+
{ id: "opencode", label: "OpenCode", command: "opencode", args: ["acp"] },
|
|
36751
|
+
{ id: "cursor", label: "Cursor agent", command: "cursor-agent", args: ["acp"] },
|
|
36752
|
+
{ id: "gemini", label: "Gemini CLI", command: "gemini", args: ["--acp"] },
|
|
36753
|
+
{ id: "copilot", label: "GitHub Copilot CLI", command: "copilot", args: ["--acp"] },
|
|
36754
|
+
{ id: "kilo", label: "Kilo", command: "kilo", args: ["acp"] },
|
|
36755
|
+
{ id: "qwen", label: "Qwen Code", command: "qwen", args: ["--acp"] },
|
|
36756
|
+
{ id: "goose", label: "goose", command: "goose", args: ["acp"] },
|
|
36757
|
+
{ id: "kimi", label: "Kimi CLI", command: "kimi", args: ["acp"] },
|
|
36758
|
+
// `grok agent stdio` is ACP over stdio (registry: `npx @xai-official/grok agent stdio`); verified
|
|
36759
|
+
// live 2026-09-16 against grok 1.0.13 — it mounted the probe MCP server and called its tool.
|
|
36760
|
+
{ id: "grok", label: "Grok Build", command: "grok", args: ["agent", "stdio"] },
|
|
36761
|
+
// pi has no native ACP mode; `pi-acp` is its registry wrapper and takes no arguments.
|
|
36762
|
+
{ id: "pi", label: "pi", command: "pi-acp", args: [] }
|
|
36763
|
+
];
|
|
36764
|
+
}
|
|
36765
|
+
});
|
|
36766
|
+
|
|
36461
36767
|
// packages/server/src/backend/claude-broker-client.ts
|
|
36462
36768
|
import net from "node:net";
|
|
36463
36769
|
function connectClaudeBroker(socketPath, handlers, options = {}) {
|
|
@@ -37360,8 +37666,8 @@ var init_claude_agent_broker_bridge = __esm({
|
|
|
37360
37666
|
});
|
|
37361
37667
|
|
|
37362
37668
|
// 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
|
|
37669
|
+
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";
|
|
37670
|
+
import { basename as basename6, join as join31, dirname as dirname13 } from "node:path";
|
|
37365
37671
|
import { createHash as createHash11, randomUUID as randomUUID13 } from "node:crypto";
|
|
37366
37672
|
function fallbackTitle(prompt) {
|
|
37367
37673
|
const firstLine2 = prompt.trim().split("\n", 1)[0].trim();
|
|
@@ -37380,7 +37686,7 @@ function fallbackTitle(prompt) {
|
|
|
37380
37686
|
}
|
|
37381
37687
|
function resolveSlug(frizzDir, base, taken) {
|
|
37382
37688
|
base = ThreadSlug.parse(base);
|
|
37383
|
-
const isTaken = (slug) => existsSync10(
|
|
37689
|
+
const isTaken = (slug) => existsSync10(join31(frizzDir, `${slug}.md`)) || (taken?.(slug) ?? false);
|
|
37384
37690
|
if (!isTaken(base)) return base;
|
|
37385
37691
|
for (let n = 2; ; n++) {
|
|
37386
37692
|
const suffix = `-${n}`;
|
|
@@ -37397,20 +37703,20 @@ function resolveLegacyThreadFile(projectDir, value) {
|
|
|
37397
37703
|
if (!parsed.success) return null;
|
|
37398
37704
|
try {
|
|
37399
37705
|
const projectRoot = realpathSync8(projectDir);
|
|
37400
|
-
const frizzPath =
|
|
37706
|
+
const frizzPath = join31(projectRoot, ".frizz");
|
|
37401
37707
|
const frizzStat = lstatSync3(frizzPath);
|
|
37402
37708
|
if (!frizzStat.isDirectory() || frizzStat.isSymbolicLink()) return null;
|
|
37403
37709
|
const realFrizz = realpathSync8(frizzPath);
|
|
37404
|
-
if (
|
|
37405
|
-
const path3 =
|
|
37710
|
+
if (dirname13(realFrizz) !== projectRoot || basename6(realFrizz) !== ".frizz") return null;
|
|
37711
|
+
const path3 = join31(realFrizz, `${parsed.data}.md`);
|
|
37406
37712
|
const before = lstatSync3(path3);
|
|
37407
37713
|
if (!before.isFile() || before.isSymbolicLink()) return null;
|
|
37408
37714
|
const realPath = realpathSync8(path3);
|
|
37409
|
-
if (
|
|
37715
|
+
if (dirname13(realPath) !== realFrizz || basename6(realPath) !== `${parsed.data}.md`) return null;
|
|
37410
37716
|
let contents;
|
|
37411
37717
|
let openedBefore;
|
|
37412
37718
|
let openedAfter;
|
|
37413
|
-
const fd = openSync13(path3,
|
|
37719
|
+
const fd = openSync13(path3, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
37414
37720
|
try {
|
|
37415
37721
|
openedBefore = fstatSync2(fd);
|
|
37416
37722
|
contents = readFileSync20(fd);
|
|
@@ -37446,7 +37752,7 @@ function boardAuthorizesAdoption(board, slug) {
|
|
|
37446
37752
|
return !board.errorItems.some((item) => item.file === `${slug}.md`);
|
|
37447
37753
|
}
|
|
37448
37754
|
function ensureSafeDirectDirectory(parent, name) {
|
|
37449
|
-
const path3 =
|
|
37755
|
+
const path3 = join31(parent, name);
|
|
37450
37756
|
try {
|
|
37451
37757
|
mkdirSync13(path3);
|
|
37452
37758
|
} catch (error) {
|
|
@@ -37456,7 +37762,7 @@ function ensureSafeDirectDirectory(parent, name) {
|
|
|
37456
37762
|
const stat2 = lstatSync3(path3);
|
|
37457
37763
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) throw new Error("unsafe project directory");
|
|
37458
37764
|
const real = realpathSync8(path3);
|
|
37459
|
-
if (
|
|
37765
|
+
if (dirname13(real) !== parent || basename6(real) !== name) throw new Error("unsafe project directory");
|
|
37460
37766
|
return real;
|
|
37461
37767
|
}
|
|
37462
37768
|
function scratchDirRelPath(sessionId) {
|
|
@@ -37476,8 +37782,8 @@ function loadWorkerPrompt(kind = "claude") {
|
|
|
37476
37782
|
function monitorScriptsDir() {
|
|
37477
37783
|
const plugin = workerPluginDir();
|
|
37478
37784
|
if (!plugin) return void 0;
|
|
37479
|
-
const dir =
|
|
37480
|
-
return existsSync10(
|
|
37785
|
+
const dir = join31(plugin, "skills", "gh", "scripts");
|
|
37786
|
+
return existsSync10(join31(dir, "ci-watch.mjs")) ? dir : void 0;
|
|
37481
37787
|
}
|
|
37482
37788
|
function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
37483
37789
|
if (!hookScript || !sessionId) return {};
|
|
@@ -37489,7 +37795,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
37489
37795
|
}
|
|
37490
37796
|
]
|
|
37491
37797
|
});
|
|
37492
|
-
const bashBackgroundHook =
|
|
37798
|
+
const bashBackgroundHook = join31(dirname13(hookScript), "bash-background.mjs");
|
|
37493
37799
|
return {
|
|
37494
37800
|
bypass_hook_trust: true,
|
|
37495
37801
|
hooks: {
|
|
@@ -37515,7 +37821,7 @@ function codexScratchpadHookConfig(hookScript, sessionId) {
|
|
|
37515
37821
|
}
|
|
37516
37822
|
function scratchpadHookScript() {
|
|
37517
37823
|
const plugin = workerPluginDir();
|
|
37518
|
-
return plugin ?
|
|
37824
|
+
return plugin ? join31(plugin, "hooks", "scratchpad.mjs") : void 0;
|
|
37519
37825
|
}
|
|
37520
37826
|
function composePrompt(sessionId, prompt, kind = "claude") {
|
|
37521
37827
|
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 +37837,10 @@ function scratchpadOrientation(sessionId, kind = "claude") {
|
|
|
37531
37837
|
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
37838
|
}
|
|
37533
37839
|
function frizzConfigBlock(projectDir) {
|
|
37534
|
-
const path3 =
|
|
37840
|
+
const path3 = join31(projectDir, "FRIZZ.md");
|
|
37535
37841
|
let body;
|
|
37536
37842
|
try {
|
|
37537
|
-
const st =
|
|
37843
|
+
const st = statSync15(path3);
|
|
37538
37844
|
if (!st.isFile() || st.size > FRIZZ_MD_MAX_BYTES) return "";
|
|
37539
37845
|
body = readFileSync20(path3, "utf8").trim();
|
|
37540
37846
|
} catch {
|
|
@@ -37575,7 +37881,7 @@ function resolveFrizzMcp(target, moduleUrl = import.meta.url, env = process.env,
|
|
|
37575
37881
|
const { stateDir, serverLock, projectId } = typeof target === "string" ? { stateDir: target } : target;
|
|
37576
37882
|
const pluginDir = resolveWorkerPluginDir(moduleUrl, env);
|
|
37577
37883
|
if (!pluginDir) return void 0;
|
|
37578
|
-
const scriptPath =
|
|
37884
|
+
const scriptPath = join31(pluginDir, "bin", FRIZZ_MCP.script);
|
|
37579
37885
|
if (!existsSync10(scriptPath)) return void 0;
|
|
37580
37886
|
return {
|
|
37581
37887
|
scriptPath,
|
|
@@ -37658,7 +37964,7 @@ function buildClaudeResumeCommand(opts) {
|
|
|
37658
37964
|
}
|
|
37659
37965
|
function createDispatcher(deps) {
|
|
37660
37966
|
const readBoardSource = deps.readBoard ?? readBoard;
|
|
37661
|
-
const frizzDir =
|
|
37967
|
+
const frizzDir = join31(deps.project.dir, ".frizz");
|
|
37662
37968
|
const adoptionRuntime = deps.adoptionRuntime ?? productionRuntime;
|
|
37663
37969
|
function cleanupPrewrites(built) {
|
|
37664
37970
|
for (const path3 of new Set(built.prewrite.map((file) => file.path))) {
|
|
@@ -37671,7 +37977,7 @@ function createDispatcher(deps) {
|
|
|
37671
37977
|
function cleanupDispatchFiles(scratchRel, built, sessionId) {
|
|
37672
37978
|
cleanupPrewrites(built);
|
|
37673
37979
|
try {
|
|
37674
|
-
rmSync9(
|
|
37980
|
+
rmSync9(join31(deps.project.dir, scratchRel), { force: true, recursive: true });
|
|
37675
37981
|
} catch {
|
|
37676
37982
|
}
|
|
37677
37983
|
cleanupAdoptionSessionFiles(deps.project.dir, sessionId);
|
|
@@ -37681,7 +37987,7 @@ function createDispatcher(deps) {
|
|
|
37681
37987
|
input = DispatchInput.parse(input);
|
|
37682
37988
|
const settings = deps.getSettings();
|
|
37683
37989
|
const kind = opts?.backend ?? "claude";
|
|
37684
|
-
if (deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
|
|
37990
|
+
if (kind !== "acp" && deps.preflightAuth && await deps.preflightAuth(kind).catch(() => "unknown") === "signed-out") {
|
|
37685
37991
|
throw new ProviderAuthRequiredError(kind);
|
|
37686
37992
|
}
|
|
37687
37993
|
if (kind === "codex" && deps.preflightCodexBinary && await deps.preflightCodexBinary().catch(() => "unknown") === "missing") {
|
|
@@ -37754,6 +38060,52 @@ function createDispatcher(deps) {
|
|
|
37754
38060
|
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
38061
|
}
|
|
37756
38062
|
}
|
|
38063
|
+
if (kind === "acp") {
|
|
38064
|
+
const bridge = deps.acpBridge;
|
|
38065
|
+
const agentId = acpAgentIdFromModel(model);
|
|
38066
|
+
if (!bridge || !agentId) {
|
|
38067
|
+
cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
|
|
38068
|
+
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)}.`);
|
|
38069
|
+
}
|
|
38070
|
+
const firstPrompt = [loadWorkerPrompt("acp"), scratchpadOrientation(sessionId, kind), frizzConfigBlock(deps.project.dir), prompt].filter(Boolean).join("\n\n");
|
|
38071
|
+
try {
|
|
38072
|
+
const spawned = await bridge.spawnDispatch({ threadSlug: slug, sessionId, cwd: deps.project.dir, agentId, modelId: acpModelIdFromModel(model), prompt: firstPrompt, userText: input.prompt });
|
|
38073
|
+
deps.storage.upsertSession({
|
|
38074
|
+
slug,
|
|
38075
|
+
session_id: sessionId,
|
|
38076
|
+
thread_name: threadIdentityName(slug),
|
|
38077
|
+
spawned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
38078
|
+
last_read_at: null,
|
|
38079
|
+
unread: 0,
|
|
38080
|
+
exited: 0,
|
|
38081
|
+
archived: 0,
|
|
38082
|
+
rested_at: null,
|
|
38083
|
+
title_auto: input.title?.trim() ? 0 : 1,
|
|
38084
|
+
title_locked: 0,
|
|
38085
|
+
title: registryTitle,
|
|
38086
|
+
state: "open",
|
|
38087
|
+
meta: null,
|
|
38088
|
+
seen_at: null,
|
|
38089
|
+
transcript_id: null,
|
|
38090
|
+
model: model ?? null,
|
|
38091
|
+
effort: null,
|
|
38092
|
+
permission_mode: permissionMode
|
|
38093
|
+
});
|
|
38094
|
+
deps.storage.setBackend(slug, "acp");
|
|
38095
|
+
deps.storage.setAgentSession(slug, spawned.acpSessionId);
|
|
38096
|
+
deps.storage.setAcpAgent(slug, agentId);
|
|
38097
|
+
void deps.board.rebuild().catch(() => {
|
|
38098
|
+
});
|
|
38099
|
+
return { slug, sessionId };
|
|
38100
|
+
} catch (err) {
|
|
38101
|
+
try {
|
|
38102
|
+
bridge.releaseSession(slug, sessionId, "session-deleted");
|
|
38103
|
+
} catch {
|
|
38104
|
+
}
|
|
38105
|
+
cleanupDispatchFiles(scratchRel, { argv: [], env: {}, prewrite: [] }, sessionId);
|
|
38106
|
+
throw new Error(`The ACP agent could not start this thread: ${err.message}`);
|
|
38107
|
+
}
|
|
38108
|
+
}
|
|
37757
38109
|
if (kind === "claude") {
|
|
37758
38110
|
const bridge = deps.claudeBroker;
|
|
37759
38111
|
if (!bridge) {
|
|
@@ -38059,6 +38411,8 @@ var init_dispatch = __esm({
|
|
|
38059
38411
|
init_worker_plugin_dir();
|
|
38060
38412
|
init_workerPrompt();
|
|
38061
38413
|
init_codex();
|
|
38414
|
+
init_acp_agents();
|
|
38415
|
+
init_src();
|
|
38062
38416
|
init_claude_agent_broker_bridge();
|
|
38063
38417
|
init_claude_effort();
|
|
38064
38418
|
init_auth_status();
|
|
@@ -38101,7 +38455,10 @@ var init_dispatch = __esm({
|
|
|
38101
38455
|
FRIZZ_MD_MAX_BYTES = 64 * 1024;
|
|
38102
38456
|
WORKER_DISPATCH_PERMISSION = {
|
|
38103
38457
|
claude: "auto",
|
|
38104
|
-
codex: "bypassPermissions"
|
|
38458
|
+
codex: "bypassPermissions",
|
|
38459
|
+
// An ACP agent's permissions are its own (plans/acp-backend.md, decision 7): Frizz sends no mode and
|
|
38460
|
+
// mirrors whatever the agent asks as a card. The value here is recorded on the row and nothing else.
|
|
38461
|
+
acp: "auto"
|
|
38105
38462
|
};
|
|
38106
38463
|
missingPluginReported = false;
|
|
38107
38464
|
}
|
|
@@ -38113,7 +38470,7 @@ import {
|
|
|
38113
38470
|
watch as fsWatch
|
|
38114
38471
|
} from "node:fs";
|
|
38115
38472
|
import { homedir as homedir14 } from "node:os";
|
|
38116
|
-
import { dirname as
|
|
38473
|
+
import { dirname as dirname14, join as join32 } from "node:path";
|
|
38117
38474
|
import watcher from "@parcel/watcher";
|
|
38118
38475
|
function appServerTurnStalled(liveness, lastActivityAt, nowMs) {
|
|
38119
38476
|
if (!liveness) return false;
|
|
@@ -38162,7 +38519,7 @@ function registeredLegacyFileIsTerminal(projectDir, slug) {
|
|
|
38162
38519
|
return status === "done" || status === "dismissed";
|
|
38163
38520
|
}
|
|
38164
38521
|
function isBoardRelevantFrizzPath(roots, path3) {
|
|
38165
|
-
return path3.endsWith(".md") && roots.has(
|
|
38522
|
+
return path3.endsWith(".md") && roots.has(dirname14(path3));
|
|
38166
38523
|
}
|
|
38167
38524
|
function futureSnooze(row, nowMs) {
|
|
38168
38525
|
const parsed = SnoozeUntil.safeParse(row.snoozed_until);
|
|
@@ -38653,7 +39010,7 @@ function sessionThreadView(projectDir, storage, row, rawTele, registeredLegacyTe
|
|
|
38653
39010
|
// from today's dispatch preference: unknown/migrated rows remain unmarked, while rows whose
|
|
38654
39011
|
// database default was explicitly normalized to "claude" get the same per-thread identity as
|
|
38655
39012
|
// Codex rows.
|
|
38656
|
-
backend: row.backend === "claude" || row.backend === "codex" ? row.backend : void 0,
|
|
39013
|
+
backend: row.backend === "claude" || row.backend === "codex" || row.backend === "acp" ? row.backend : void 0,
|
|
38657
39014
|
// Only a persisted, validated per-session value is exposed. A migrated row stays visibly unknown;
|
|
38658
39015
|
// never label it with today's global defaults (which may not match its running process).
|
|
38659
39016
|
permissionMode,
|
|
@@ -38960,7 +39317,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38960
39317
|
if (parcelSub || stopped) return Promise.resolve();
|
|
38961
39318
|
if (watchSetup) return watchSetup;
|
|
38962
39319
|
const setup = (async () => {
|
|
38963
|
-
const dir =
|
|
39320
|
+
const dir = join32(project.dir, ".frizz");
|
|
38964
39321
|
const roots = /* @__PURE__ */ new Set([dir]);
|
|
38965
39322
|
try {
|
|
38966
39323
|
roots.add(realpathSync9(dir));
|
|
@@ -38975,7 +39332,7 @@ function createBoard(project, storage, bus, tailer, bootId, deps = {}) {
|
|
|
38975
39332
|
}
|
|
38976
39333
|
if (events.some((event) => isBoardRelevantFrizzPath(roots, event.path))) scheduleRebuild();
|
|
38977
39334
|
},
|
|
38978
|
-
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) =>
|
|
39335
|
+
{ ignore: FRIZZ_WATCH_IGNORED_DIRS.map((name) => join32(dir, name)) }
|
|
38979
39336
|
);
|
|
38980
39337
|
if (stopped) {
|
|
38981
39338
|
await next.unsubscribe();
|
|
@@ -39293,7 +39650,7 @@ var init_wake_store = __esm({
|
|
|
39293
39650
|
});
|
|
39294
39651
|
|
|
39295
39652
|
// packages/server/src/github-review.ts
|
|
39296
|
-
import { execFile as
|
|
39653
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
39297
39654
|
import { promisify as promisify6 } from "node:util";
|
|
39298
39655
|
function refKey(ref) {
|
|
39299
39656
|
return `${ref.kind === "issue" ? "issue:" : ""}${ref.owner}/${ref.repo}#${ref.number}`;
|
|
@@ -39712,7 +40069,7 @@ var execFileAsync4, GH_TIMEOUT_MS, GH_MAX_BUFFER, REQUEST_TIMEOUT_MS, MAX_REFS_P
|
|
|
39712
40069
|
var init_github_review = __esm({
|
|
39713
40070
|
"packages/server/src/github-review.ts"() {
|
|
39714
40071
|
"use strict";
|
|
39715
|
-
execFileAsync4 = promisify6(
|
|
40072
|
+
execFileAsync4 = promisify6(execFile7);
|
|
39716
40073
|
GH_TIMEOUT_MS = 3e4;
|
|
39717
40074
|
GH_MAX_BUFFER = 1024 * 1024;
|
|
39718
40075
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -39849,7 +40206,7 @@ var init_pr_watch_noise = __esm({
|
|
|
39849
40206
|
});
|
|
39850
40207
|
|
|
39851
40208
|
// packages/server/src/scheduler.ts
|
|
39852
|
-
import { execFile as
|
|
40209
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
39853
40210
|
import { promisify as promisify7 } from "node:util";
|
|
39854
40211
|
import { createHash as createHash12, randomUUID as randomUUID14 } from "node:crypto";
|
|
39855
40212
|
function failureSignature(rollup) {
|
|
@@ -41740,7 +42097,7 @@ var init_scheduler = __esm({
|
|
|
41740
42097
|
init_pr_watch_noise();
|
|
41741
42098
|
init_github_review();
|
|
41742
42099
|
init_logging();
|
|
41743
|
-
execFileAsync5 = promisify7(
|
|
42100
|
+
execFileAsync5 = promisify7(execFile8);
|
|
41744
42101
|
LIMIT_FENCE_PREFIX = "limit";
|
|
41745
42102
|
LIMIT_HINT_PREFIX = "limit:";
|
|
41746
42103
|
LIMIT_RESUME_GRACE_MS = 6e4;
|
|
@@ -41862,7 +42219,7 @@ var init_resume = __esm({
|
|
|
41862
42219
|
});
|
|
41863
42220
|
|
|
41864
42221
|
// packages/server/src/backend/claude.ts
|
|
41865
|
-
import { join as
|
|
42222
|
+
import { join as join33 } from "node:path";
|
|
41866
42223
|
function toolResultText2(content) {
|
|
41867
42224
|
if (typeof content === "string") return content;
|
|
41868
42225
|
if (!Array.isArray(content)) return "";
|
|
@@ -41953,7 +42310,7 @@ function createClaudeBackend(opts) {
|
|
|
41953
42310
|
return { argv, env: claudeWorkerEnvironment(), prewrite: [] };
|
|
41954
42311
|
},
|
|
41955
42312
|
transcriptPath(sessionId) {
|
|
41956
|
-
return
|
|
42313
|
+
return join33(opts.logDir, `${sessionId}.jsonl`);
|
|
41957
42314
|
},
|
|
41958
42315
|
parseLine(line) {
|
|
41959
42316
|
return parseClaudeLine(line);
|
|
@@ -41977,6 +42334,1185 @@ var init_claude = __esm({
|
|
|
41977
42334
|
}
|
|
41978
42335
|
});
|
|
41979
42336
|
|
|
42337
|
+
// packages/server/src/backend/acp-transcript.ts
|
|
42338
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync14, readFileSync as readFileSync21 } from "node:fs";
|
|
42339
|
+
import { dirname as dirname15, join as join34 } from "node:path";
|
|
42340
|
+
function acpTranscriptPath(stateDir, sessionId) {
|
|
42341
|
+
return join34(stateDir, ACP_TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
42342
|
+
}
|
|
42343
|
+
function parseAcpRecord(line) {
|
|
42344
|
+
if (!line.trim()) return void 0;
|
|
42345
|
+
let raw2;
|
|
42346
|
+
try {
|
|
42347
|
+
raw2 = JSON.parse(line);
|
|
42348
|
+
} catch {
|
|
42349
|
+
return void 0;
|
|
42350
|
+
}
|
|
42351
|
+
if (!raw2 || typeof raw2 !== "object" || typeof raw2.kind !== "string") return void 0;
|
|
42352
|
+
const kind = raw2.kind;
|
|
42353
|
+
if (kind === "acp-session" || kind === "acp-note" || EVENT_KINDS.has(kind)) return raw2;
|
|
42354
|
+
return void 0;
|
|
42355
|
+
}
|
|
42356
|
+
function parseAcpLine(line) {
|
|
42357
|
+
const rec = parseAcpRecord(line);
|
|
42358
|
+
if (!rec || rec.kind === "acp-session" || rec.kind === "acp-note") return [];
|
|
42359
|
+
const { messageId: _m, acp: _a, ...ev } = rec;
|
|
42360
|
+
return [ev];
|
|
42361
|
+
}
|
|
42362
|
+
function createAcpBackend(opts) {
|
|
42363
|
+
return {
|
|
42364
|
+
kind: "acp",
|
|
42365
|
+
buildSpawn(_o) {
|
|
42366
|
+
throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
|
|
42367
|
+
},
|
|
42368
|
+
buildResume(_o) {
|
|
42369
|
+
throw new Error("acp runs via the ACP bridge; this argv builder has no live caller");
|
|
42370
|
+
},
|
|
42371
|
+
// Deterministic: the bridge names the file after the frizz session id, so no discovery step.
|
|
42372
|
+
transcriptPath(sessionId) {
|
|
42373
|
+
return acpTranscriptPath(opts.stateDir, sessionId);
|
|
42374
|
+
},
|
|
42375
|
+
parseLine: parseAcpLine,
|
|
42376
|
+
foldLine(state, line) {
|
|
42377
|
+
const rec = parseAcpRecord(line);
|
|
42378
|
+
if (!rec) return;
|
|
42379
|
+
if (rec.kind === "acp-session") {
|
|
42380
|
+
if (rec.model) {
|
|
42381
|
+
state.model = rec.model;
|
|
42382
|
+
state.profileAt = rec.at;
|
|
42383
|
+
state.profileRevision = (state.profileRevision ?? 0) + 1;
|
|
42384
|
+
}
|
|
42385
|
+
return;
|
|
42386
|
+
}
|
|
42387
|
+
for (const ev of parseAcpLine(line)) applyEvent(state, ev);
|
|
42388
|
+
}
|
|
42389
|
+
};
|
|
42390
|
+
}
|
|
42391
|
+
function clip2(text2, max) {
|
|
42392
|
+
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
42393
|
+
}
|
|
42394
|
+
function toolTarget(rawInput, acp) {
|
|
42395
|
+
const input = rawInput && typeof rawInput === "object" ? rawInput : void 0;
|
|
42396
|
+
const command = typeof input?.command === "string" ? input.command : void 0;
|
|
42397
|
+
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];
|
|
42398
|
+
return { command, path: path3, input };
|
|
42399
|
+
}
|
|
42400
|
+
function toolCallFor(ev, acp) {
|
|
42401
|
+
const { command, path: path3, input } = toolTarget(ev.input, acp);
|
|
42402
|
+
const detail2 = command ?? path3 ?? acp?.title ?? "";
|
|
42403
|
+
const call = { name: ev.name, status: "pending" };
|
|
42404
|
+
if (detail2) call.detail = clip2(detail2, 500);
|
|
42405
|
+
if (command) call.command = command;
|
|
42406
|
+
else if (input && Object.keys(input).length) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
|
|
42407
|
+
return call;
|
|
42408
|
+
}
|
|
42409
|
+
function upgradeToolTarget(call, acp) {
|
|
42410
|
+
if (!acp || call.command || call.detail && call.detail !== call.name) return;
|
|
42411
|
+
const { command, path: path3, input } = toolTarget(acp.input, acp);
|
|
42412
|
+
const detail2 = command ?? path3;
|
|
42413
|
+
if (!detail2) return;
|
|
42414
|
+
call.detail = clip2(detail2, 500);
|
|
42415
|
+
if (command) call.command = command;
|
|
42416
|
+
else if (input && Object.keys(input).length && !call.input) call.input = clip2(JSON.stringify(input, null, 2), TOOL_TEXT_MAX);
|
|
42417
|
+
}
|
|
42418
|
+
function projectAcpTranscript(raw2, identityPrefix = "acp") {
|
|
42419
|
+
const out = [];
|
|
42420
|
+
let current;
|
|
42421
|
+
let currentMessageId;
|
|
42422
|
+
const openTools = /* @__PURE__ */ new Map();
|
|
42423
|
+
const lines = raw2.split("\n");
|
|
42424
|
+
const close2 = () => {
|
|
42425
|
+
current = void 0;
|
|
42426
|
+
currentMessageId = void 0;
|
|
42427
|
+
};
|
|
42428
|
+
const openAssistant = (i, at) => {
|
|
42429
|
+
if (!current) {
|
|
42430
|
+
current = { sourceId: `${identityPrefix}:${i}`, role: "assistant", text: "", tools: [], parts: [], ...at ? { at } : {} };
|
|
42431
|
+
out.push(current);
|
|
42432
|
+
}
|
|
42433
|
+
return current;
|
|
42434
|
+
};
|
|
42435
|
+
const appendText = (msg, text2) => {
|
|
42436
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42437
|
+
if (last?.kind === "text") last.text += text2;
|
|
42438
|
+
else msg.parts.push({ kind: "text", text: text2 });
|
|
42439
|
+
msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
|
|
42440
|
+
};
|
|
42441
|
+
const appendTool = (msg, tool) => {
|
|
42442
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42443
|
+
if (last?.kind === "tools") last.tools.push(tool);
|
|
42444
|
+
else msg.parts.push({ kind: "tools", tools: [tool] });
|
|
42445
|
+
msg.tools.push(tool);
|
|
42446
|
+
};
|
|
42447
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42448
|
+
const rec = parseAcpRecord(lines[i]);
|
|
42449
|
+
if (!rec) continue;
|
|
42450
|
+
switch (rec.kind) {
|
|
42451
|
+
case "acp-session":
|
|
42452
|
+
case "acp-note":
|
|
42453
|
+
case "title":
|
|
42454
|
+
case "context-usage":
|
|
42455
|
+
case "compaction":
|
|
42456
|
+
case "agent-report":
|
|
42457
|
+
case "agent-instruction":
|
|
42458
|
+
break;
|
|
42459
|
+
case "user-message": {
|
|
42460
|
+
close2();
|
|
42461
|
+
const text2 = rec.text ?? "";
|
|
42462
|
+
out.push({ sourceId: `${identityPrefix}:${i}`, role: "user", text: text2, tools: [], parts: [], ...rec.at ? { at: rec.at } : {}, ...rec.synthetic ? { wake: true } : {} });
|
|
42463
|
+
break;
|
|
42464
|
+
}
|
|
42465
|
+
case "turn-start":
|
|
42466
|
+
break;
|
|
42467
|
+
case "turn-end":
|
|
42468
|
+
close2();
|
|
42469
|
+
break;
|
|
42470
|
+
case "reasoning": {
|
|
42471
|
+
close2();
|
|
42472
|
+
out.push({ sourceId: `${identityPrefix}:${i}`, role: "assistant", kind: "reasoning", text: rec.text, tools: [], parts: [], ...rec.at ? { at: rec.at } : {} });
|
|
42473
|
+
break;
|
|
42474
|
+
}
|
|
42475
|
+
case "assistant-text": {
|
|
42476
|
+
const msg = openAssistant(i, rec.at);
|
|
42477
|
+
const mid = rec.messageId;
|
|
42478
|
+
if (mid && mid === currentMessageId) {
|
|
42479
|
+
const last = msg.parts[msg.parts.length - 1];
|
|
42480
|
+
if (last?.kind === "text") {
|
|
42481
|
+
last.text += rec.text;
|
|
42482
|
+
msg.text = msg.parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n\n");
|
|
42483
|
+
break;
|
|
42484
|
+
}
|
|
42485
|
+
}
|
|
42486
|
+
currentMessageId = mid;
|
|
42487
|
+
appendText(msg, rec.text);
|
|
42488
|
+
break;
|
|
42489
|
+
}
|
|
42490
|
+
case "tool-call": {
|
|
42491
|
+
const msg = openAssistant(i, rec.at);
|
|
42492
|
+
currentMessageId = void 0;
|
|
42493
|
+
const call = toolCallFor(rec, rec.acp);
|
|
42494
|
+
openTools.set(rec.id, call);
|
|
42495
|
+
appendTool(msg, call);
|
|
42496
|
+
break;
|
|
42497
|
+
}
|
|
42498
|
+
case "tool-result": {
|
|
42499
|
+
const call = openTools.get(rec.id);
|
|
42500
|
+
if (!call) break;
|
|
42501
|
+
openTools.delete(rec.id);
|
|
42502
|
+
const failed = rec.acp?.status === "failed" || rec.acp?.kind === "failed";
|
|
42503
|
+
call.status = failed ? "failed" : "completed";
|
|
42504
|
+
upgradeToolTarget(call, rec.acp);
|
|
42505
|
+
if (rec.text) call.output = clip2(rec.text, TOOL_TEXT_MAX);
|
|
42506
|
+
break;
|
|
42507
|
+
}
|
|
42508
|
+
case "provider-error": {
|
|
42509
|
+
const msg = openAssistant(i, rec.at);
|
|
42510
|
+
msg.providerError = rec.error;
|
|
42511
|
+
close2();
|
|
42512
|
+
break;
|
|
42513
|
+
}
|
|
42514
|
+
}
|
|
42515
|
+
}
|
|
42516
|
+
return out;
|
|
42517
|
+
}
|
|
42518
|
+
var ACP_TRANSCRIPT_DIR, EVENT_KINDS, AcpTranscriptWriter, TOOL_TEXT_MAX;
|
|
42519
|
+
var init_acp_transcript = __esm({
|
|
42520
|
+
"packages/server/src/backend/acp-transcript.ts"() {
|
|
42521
|
+
"use strict";
|
|
42522
|
+
init_tailer();
|
|
42523
|
+
ACP_TRANSCRIPT_DIR = "acp";
|
|
42524
|
+
EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
42525
|
+
"turn-start",
|
|
42526
|
+
"turn-end",
|
|
42527
|
+
"provider-error",
|
|
42528
|
+
"assistant-text",
|
|
42529
|
+
"user-message",
|
|
42530
|
+
"tool-call",
|
|
42531
|
+
"tool-result",
|
|
42532
|
+
"reasoning",
|
|
42533
|
+
"agent-report",
|
|
42534
|
+
"agent-instruction",
|
|
42535
|
+
"title",
|
|
42536
|
+
"compaction",
|
|
42537
|
+
"context-usage"
|
|
42538
|
+
]);
|
|
42539
|
+
AcpTranscriptWriter = class {
|
|
42540
|
+
constructor(path3) {
|
|
42541
|
+
this.path = path3;
|
|
42542
|
+
mkdirSync14(dirname15(path3), { recursive: true });
|
|
42543
|
+
if (!existsSync12(path3)) appendFileSync3(path3, "");
|
|
42544
|
+
}
|
|
42545
|
+
append(record) {
|
|
42546
|
+
appendFileSync3(this.path, JSON.stringify(record) + "\n");
|
|
42547
|
+
}
|
|
42548
|
+
};
|
|
42549
|
+
TOOL_TEXT_MAX = 12e3;
|
|
42550
|
+
}
|
|
42551
|
+
});
|
|
42552
|
+
|
|
42553
|
+
// packages/server/src/backend/acp-types.ts
|
|
42554
|
+
function parseSessionUpdate(raw2) {
|
|
42555
|
+
const parsed = AcpSessionUpdateKnown.safeParse(raw2);
|
|
42556
|
+
return parsed.success ? parsed.data : { sessionUpdate: "unknown", raw: raw2 };
|
|
42557
|
+
}
|
|
42558
|
+
function contentText(block) {
|
|
42559
|
+
if (!block) return void 0;
|
|
42560
|
+
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
42561
|
+
if (block.type === "resource") {
|
|
42562
|
+
const r = block.resource;
|
|
42563
|
+
return typeof r?.text === "string" ? r.text : void 0;
|
|
42564
|
+
}
|
|
42565
|
+
return void 0;
|
|
42566
|
+
}
|
|
42567
|
+
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;
|
|
42568
|
+
var init_acp_types = __esm({
|
|
42569
|
+
"packages/server/src/backend/acp-types.ts"() {
|
|
42570
|
+
"use strict";
|
|
42571
|
+
init_zod();
|
|
42572
|
+
ACP_PROTOCOL_VERSION = 1;
|
|
42573
|
+
JsonRpcId = external_exports.union([external_exports.string(), external_exports.number()]);
|
|
42574
|
+
JsonRpcError = external_exports.object({
|
|
42575
|
+
code: external_exports.number(),
|
|
42576
|
+
message: external_exports.string(),
|
|
42577
|
+
data: external_exports.unknown().optional()
|
|
42578
|
+
}).passthrough();
|
|
42579
|
+
JsonRpcInbound = external_exports.union([
|
|
42580
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, result: external_exports.unknown() }).passthrough(),
|
|
42581
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, error: JsonRpcError }).passthrough(),
|
|
42582
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), id: JsonRpcId, method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough(),
|
|
42583
|
+
external_exports.object({ jsonrpc: external_exports.literal("2.0").optional(), method: external_exports.string(), params: external_exports.unknown().optional() }).passthrough()
|
|
42584
|
+
]);
|
|
42585
|
+
ACP_ERROR_AUTH_REQUIRED = -32e3;
|
|
42586
|
+
ACP_ERROR_METHOD_NOT_FOUND = -32601;
|
|
42587
|
+
ACP_ERROR_INTERNAL = -32603;
|
|
42588
|
+
AcpAuthMethod = external_exports.object({
|
|
42589
|
+
id: external_exports.string(),
|
|
42590
|
+
name: external_exports.string().optional(),
|
|
42591
|
+
description: external_exports.string().optional(),
|
|
42592
|
+
// Absent = "agent" (call `authenticate {methodId}`); "terminal" = relaunch the same program
|
|
42593
|
+
// interactively with `args`/`env` — which Frizz does not do; it tells the operator instead.
|
|
42594
|
+
type: external_exports.string().optional()
|
|
42595
|
+
}).passthrough();
|
|
42596
|
+
AcpAgentCapabilities = external_exports.object({
|
|
42597
|
+
loadSession: external_exports.boolean().optional(),
|
|
42598
|
+
promptCapabilities: external_exports.object({ image: external_exports.boolean().optional(), audio: external_exports.boolean().optional(), embeddedContext: external_exports.boolean().optional() }).passthrough().optional(),
|
|
42599
|
+
mcpCapabilities: external_exports.object({ http: external_exports.boolean().optional(), sse: external_exports.boolean().optional() }).passthrough().optional(),
|
|
42600
|
+
// Object markers: `{}` present = supported, absent = not.
|
|
42601
|
+
sessionCapabilities: external_exports.object({
|
|
42602
|
+
list: external_exports.unknown().optional(),
|
|
42603
|
+
resume: external_exports.unknown().optional(),
|
|
42604
|
+
close: external_exports.unknown().optional(),
|
|
42605
|
+
fork: external_exports.unknown().optional(),
|
|
42606
|
+
delete: external_exports.unknown().optional()
|
|
42607
|
+
}).passthrough().optional()
|
|
42608
|
+
}).passthrough();
|
|
42609
|
+
AcpInitializeResult = external_exports.object({
|
|
42610
|
+
protocolVersion: external_exports.number(),
|
|
42611
|
+
agentCapabilities: AcpAgentCapabilities.optional(),
|
|
42612
|
+
authMethods: external_exports.array(AcpAuthMethod).optional(),
|
|
42613
|
+
agentInfo: external_exports.object({ name: external_exports.string().optional(), title: external_exports.string().optional(), version: external_exports.string().optional() }).passthrough().optional()
|
|
42614
|
+
}).passthrough();
|
|
42615
|
+
AcpMcpServerStdio = external_exports.object({
|
|
42616
|
+
name: external_exports.string(),
|
|
42617
|
+
command: external_exports.string(),
|
|
42618
|
+
args: external_exports.array(external_exports.string()),
|
|
42619
|
+
env: external_exports.array(external_exports.object({ name: external_exports.string(), value: external_exports.string() }))
|
|
42620
|
+
});
|
|
42621
|
+
AcpConfigOption = external_exports.object({
|
|
42622
|
+
id: external_exports.string(),
|
|
42623
|
+
name: external_exports.string().optional(),
|
|
42624
|
+
category: external_exports.string().optional(),
|
|
42625
|
+
type: external_exports.string().optional(),
|
|
42626
|
+
currentValue: external_exports.unknown().optional(),
|
|
42627
|
+
options: external_exports.array(external_exports.object({ value: external_exports.string(), name: external_exports.string().optional() }).passthrough()).optional()
|
|
42628
|
+
}).passthrough();
|
|
42629
|
+
AcpNewSessionResult = external_exports.object({
|
|
42630
|
+
sessionId: external_exports.string().min(1),
|
|
42631
|
+
configOptions: external_exports.array(AcpConfigOption).nullish(),
|
|
42632
|
+
modes: external_exports.unknown().optional()
|
|
42633
|
+
}).passthrough();
|
|
42634
|
+
AcpSetConfigOptionResult = external_exports.object({ configOptions: external_exports.array(AcpConfigOption).nullish() }).passthrough();
|
|
42635
|
+
AcpLoadSessionResult = external_exports.object({
|
|
42636
|
+
configOptions: external_exports.array(AcpConfigOption).nullish(),
|
|
42637
|
+
modes: external_exports.unknown().optional()
|
|
42638
|
+
}).passthrough();
|
|
42639
|
+
AcpContentBlock = external_exports.union([
|
|
42640
|
+
external_exports.object({ type: external_exports.literal("text"), text: external_exports.string() }).passthrough(),
|
|
42641
|
+
external_exports.object({ type: external_exports.literal("resource_link"), uri: external_exports.string(), name: external_exports.string().optional() }).passthrough(),
|
|
42642
|
+
external_exports.object({ type: external_exports.literal("resource"), resource: external_exports.object({ uri: external_exports.string(), text: external_exports.string().optional() }).passthrough() }).passthrough(),
|
|
42643
|
+
external_exports.object({ type: external_exports.literal("image"), mimeType: external_exports.string().optional(), data: external_exports.string().optional() }).passthrough(),
|
|
42644
|
+
external_exports.object({ type: external_exports.string() }).passthrough()
|
|
42645
|
+
]);
|
|
42646
|
+
AcpToolKind = external_exports.enum(["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"]);
|
|
42647
|
+
AcpToolCallStatus = external_exports.enum(["pending", "in_progress", "completed", "failed"]);
|
|
42648
|
+
AcpToolCallContent = external_exports.union([
|
|
42649
|
+
external_exports.object({ type: external_exports.literal("content"), content: AcpContentBlock }).passthrough(),
|
|
42650
|
+
external_exports.object({ type: external_exports.literal("diff"), path: external_exports.string(), oldText: external_exports.string().nullish(), newText: external_exports.string() }).passthrough(),
|
|
42651
|
+
external_exports.object({ type: external_exports.literal("terminal"), terminalId: external_exports.string() }).passthrough(),
|
|
42652
|
+
external_exports.object({ type: external_exports.string() }).passthrough()
|
|
42653
|
+
]);
|
|
42654
|
+
AcpToolCallLocation = external_exports.object({ path: external_exports.string(), line: external_exports.number().nullish() }).passthrough();
|
|
42655
|
+
ToolCallFields = {
|
|
42656
|
+
toolCallId: external_exports.string(),
|
|
42657
|
+
title: external_exports.string().optional(),
|
|
42658
|
+
kind: external_exports.string().optional(),
|
|
42659
|
+
status: external_exports.string().optional(),
|
|
42660
|
+
content: external_exports.array(AcpToolCallContent).nullish(),
|
|
42661
|
+
locations: external_exports.array(AcpToolCallLocation).nullish(),
|
|
42662
|
+
rawInput: external_exports.unknown().optional(),
|
|
42663
|
+
rawOutput: external_exports.unknown().optional()
|
|
42664
|
+
};
|
|
42665
|
+
AcpToolCallUpdate = external_exports.object(ToolCallFields).passthrough();
|
|
42666
|
+
AcpPlanEntry = external_exports.object({ content: external_exports.string(), priority: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough();
|
|
42667
|
+
AcpSessionUpdateKnown = external_exports.discriminatedUnion("sessionUpdate", [
|
|
42668
|
+
external_exports.object({ sessionUpdate: external_exports.literal("user_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42669
|
+
external_exports.object({ sessionUpdate: external_exports.literal("agent_message_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42670
|
+
external_exports.object({ sessionUpdate: external_exports.literal("agent_thought_chunk"), content: AcpContentBlock, messageId: external_exports.string().optional() }).passthrough(),
|
|
42671
|
+
external_exports.object({ sessionUpdate: external_exports.literal("tool_call"), ...ToolCallFields }).passthrough(),
|
|
42672
|
+
external_exports.object({ sessionUpdate: external_exports.literal("tool_call_update"), ...ToolCallFields }).passthrough(),
|
|
42673
|
+
external_exports.object({ sessionUpdate: external_exports.literal("plan"), entries: external_exports.array(AcpPlanEntry) }).passthrough(),
|
|
42674
|
+
external_exports.object({ sessionUpdate: external_exports.literal("usage_update"), used: external_exports.number(), size: external_exports.number(), cost: external_exports.unknown().optional() }).passthrough(),
|
|
42675
|
+
external_exports.object({ sessionUpdate: external_exports.literal("session_info_update"), title: external_exports.string().nullish(), updatedAt: external_exports.string().nullish() }).passthrough(),
|
|
42676
|
+
external_exports.object({ sessionUpdate: external_exports.literal("config_option_update"), configOptions: external_exports.array(AcpConfigOption) }).passthrough(),
|
|
42677
|
+
external_exports.object({ sessionUpdate: external_exports.literal("compaction_update"), compactionId: external_exports.string().optional(), status: external_exports.string().optional() }).passthrough()
|
|
42678
|
+
]);
|
|
42679
|
+
AcpSessionNotification = external_exports.object({ sessionId: external_exports.string(), update: external_exports.unknown() }).passthrough();
|
|
42680
|
+
AcpStopReason = external_exports.enum(["end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"]);
|
|
42681
|
+
AcpPromptResult = external_exports.object({
|
|
42682
|
+
// A string, not the enum: a future reason must not turn a finished turn into a parse failure.
|
|
42683
|
+
stopReason: external_exports.string(),
|
|
42684
|
+
usage: external_exports.object({ inputTokens: external_exports.number().optional(), outputTokens: external_exports.number().optional(), totalTokens: external_exports.number().optional() }).passthrough().nullish()
|
|
42685
|
+
}).passthrough();
|
|
42686
|
+
AcpPermissionOptionKind = external_exports.enum(["allow_once", "allow_always", "reject_once", "reject_always"]);
|
|
42687
|
+
AcpPermissionOption = external_exports.object({
|
|
42688
|
+
optionId: external_exports.string(),
|
|
42689
|
+
name: external_exports.string().optional(),
|
|
42690
|
+
// A string: an unknown kind renders as an opaque choice rather than failing the whole request.
|
|
42691
|
+
kind: external_exports.string().optional()
|
|
42692
|
+
}).passthrough();
|
|
42693
|
+
AcpRequestPermissionParams = external_exports.object({
|
|
42694
|
+
sessionId: external_exports.string(),
|
|
42695
|
+
toolCall: AcpToolCallUpdate,
|
|
42696
|
+
options: external_exports.array(AcpPermissionOption)
|
|
42697
|
+
}).passthrough();
|
|
42698
|
+
}
|
|
42699
|
+
});
|
|
42700
|
+
|
|
42701
|
+
// packages/server/src/backend/acp-rpc.ts
|
|
42702
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
42703
|
+
import { StringDecoder as StringDecoder4 } from "node:string_decoder";
|
|
42704
|
+
var spawnAcpChild, AcpRequestError, AcpRemoteError, AcpConnectionClosed, AcpRequestTimeout, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_LINE_BYTES, AcpConnection;
|
|
42705
|
+
var init_acp_rpc = __esm({
|
|
42706
|
+
"packages/server/src/backend/acp-rpc.ts"() {
|
|
42707
|
+
"use strict";
|
|
42708
|
+
init_acp_types();
|
|
42709
|
+
spawnAcpChild = ({ command, args, cwd, env }) => spawn6(command, args, { cwd, env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
42710
|
+
AcpRequestError = class extends Error {
|
|
42711
|
+
constructor(code, message, data) {
|
|
42712
|
+
super(message);
|
|
42713
|
+
this.code = code;
|
|
42714
|
+
this.data = data;
|
|
42715
|
+
}
|
|
42716
|
+
};
|
|
42717
|
+
AcpRemoteError = class extends Error {
|
|
42718
|
+
constructor(method, error) {
|
|
42719
|
+
super(`${method}: ${error.message} (${error.code})`);
|
|
42720
|
+
this.method = method;
|
|
42721
|
+
this.error = error;
|
|
42722
|
+
}
|
|
42723
|
+
};
|
|
42724
|
+
AcpConnectionClosed = class extends Error {
|
|
42725
|
+
constructor(method, reason) {
|
|
42726
|
+
super(`${method}: agent connection closed (${reason})`);
|
|
42727
|
+
this.method = method;
|
|
42728
|
+
this.reason = reason;
|
|
42729
|
+
}
|
|
42730
|
+
};
|
|
42731
|
+
AcpRequestTimeout = class extends Error {
|
|
42732
|
+
constructor(method, timeoutMs) {
|
|
42733
|
+
super(`${method}: no response from the agent within ${Math.round(timeoutMs / 1e3)}s`);
|
|
42734
|
+
this.method = method;
|
|
42735
|
+
this.timeoutMs = timeoutMs;
|
|
42736
|
+
}
|
|
42737
|
+
};
|
|
42738
|
+
DEFAULT_TIMEOUT_MS = 6e4;
|
|
42739
|
+
DEFAULT_MAX_LINE_BYTES = 8 * 1024 * 1024;
|
|
42740
|
+
AcpConnection = class {
|
|
42741
|
+
constructor(process2, options) {
|
|
42742
|
+
this.process = process2;
|
|
42743
|
+
this.options = options;
|
|
42744
|
+
const maxLine = options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
|
|
42745
|
+
this.exitPromise = new Promise((resolve12) => {
|
|
42746
|
+
process2.on("exit", (code, signal) => {
|
|
42747
|
+
this.settleClosed(`exit ${signal ?? code ?? "?"}`);
|
|
42748
|
+
resolve12({ code, signal });
|
|
42749
|
+
});
|
|
42750
|
+
process2.on("error", (err) => {
|
|
42751
|
+
this.settleClosed(err.message);
|
|
42752
|
+
resolve12({ code: null, signal: null });
|
|
42753
|
+
});
|
|
42754
|
+
});
|
|
42755
|
+
process2.stdout?.on("data", (chunk) => {
|
|
42756
|
+
this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
42757
|
+
let nl;
|
|
42758
|
+
while ((nl = this.buffer.indexOf("\n")) >= 0) {
|
|
42759
|
+
const line = this.buffer.slice(0, nl);
|
|
42760
|
+
this.buffer = this.buffer.slice(nl + 1);
|
|
42761
|
+
if (this.overflowing) {
|
|
42762
|
+
this.overflowing = false;
|
|
42763
|
+
continue;
|
|
42764
|
+
}
|
|
42765
|
+
this.handleLine(line);
|
|
42766
|
+
}
|
|
42767
|
+
if (this.buffer.length > maxLine) {
|
|
42768
|
+
options.onDiagnostic?.({ kind: "oversized-line", message: `dropped a stdout line longer than ${maxLine} bytes` });
|
|
42769
|
+
this.buffer = "";
|
|
42770
|
+
this.overflowing = true;
|
|
42771
|
+
}
|
|
42772
|
+
});
|
|
42773
|
+
process2.stderr?.on("data", (chunk) => {
|
|
42774
|
+
const text2 = String(chunk);
|
|
42775
|
+
for (const line of text2.split("\n")) {
|
|
42776
|
+
if (!line.trim()) continue;
|
|
42777
|
+
this.stderrTail.push(line.slice(0, 2e3));
|
|
42778
|
+
if (this.stderrTail.length > 40) this.stderrTail.shift();
|
|
42779
|
+
options.onDiagnostic?.({ kind: "stderr", message: line.slice(0, 2e3) });
|
|
42780
|
+
}
|
|
42781
|
+
});
|
|
42782
|
+
}
|
|
42783
|
+
nextId = 1;
|
|
42784
|
+
pending = /* @__PURE__ */ new Map();
|
|
42785
|
+
decoder = new StringDecoder4("utf8");
|
|
42786
|
+
buffer = "";
|
|
42787
|
+
overflowing = false;
|
|
42788
|
+
closedReason;
|
|
42789
|
+
exitPromise;
|
|
42790
|
+
stderrTail = [];
|
|
42791
|
+
/** The last lines the agent wrote to stderr — what a failed dispatch shows the operator. */
|
|
42792
|
+
get recentStderr() {
|
|
42793
|
+
return this.stderrTail;
|
|
42794
|
+
}
|
|
42795
|
+
get closed() {
|
|
42796
|
+
return this.closedReason !== void 0;
|
|
42797
|
+
}
|
|
42798
|
+
/** Resolves when the process has exited (or failed to start). */
|
|
42799
|
+
get exited() {
|
|
42800
|
+
return this.exitPromise;
|
|
42801
|
+
}
|
|
42802
|
+
request(method, params, opts = {}) {
|
|
42803
|
+
if (this.closedReason !== void 0) return Promise.reject(new AcpConnectionClosed(method, this.closedReason));
|
|
42804
|
+
const id = this.nextId++;
|
|
42805
|
+
const timeoutMs = opts.timeoutMs ?? this.options.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
42806
|
+
return new Promise((resolve12, reject) => {
|
|
42807
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
42808
|
+
this.pending.delete(id);
|
|
42809
|
+
reject(new AcpRequestTimeout(method, timeoutMs));
|
|
42810
|
+
}, timeoutMs) : void 0;
|
|
42811
|
+
timer?.unref?.();
|
|
42812
|
+
this.pending.set(id, { method, resolve: resolve12, reject, timer });
|
|
42813
|
+
if (!this.write({ jsonrpc: "2.0", id, method, params })) {
|
|
42814
|
+
this.pending.delete(id);
|
|
42815
|
+
if (timer) clearTimeout(timer);
|
|
42816
|
+
reject(new AcpConnectionClosed(method, this.closedReason ?? "stdin closed"));
|
|
42817
|
+
}
|
|
42818
|
+
});
|
|
42819
|
+
}
|
|
42820
|
+
/** A prompt turn is long-lived by design (its response IS the end of the turn), so it is the one
|
|
42821
|
+
* request that must not time out; callers pass `timeoutMs: 0` through this alias for readability. */
|
|
42822
|
+
requestOpenEnded(method, params) {
|
|
42823
|
+
return this.request(method, params, { timeoutMs: 0 });
|
|
42824
|
+
}
|
|
42825
|
+
notify(method, params) {
|
|
42826
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
42827
|
+
}
|
|
42828
|
+
/** Close stdin (the protocol's teardown) and, if the agent has not exited after `graceMs`, kill it. */
|
|
42829
|
+
async close(graceMs = 3e3) {
|
|
42830
|
+
try {
|
|
42831
|
+
this.process.stdin?.end();
|
|
42832
|
+
} catch {
|
|
42833
|
+
}
|
|
42834
|
+
if (this.closedReason !== void 0) return;
|
|
42835
|
+
const exited = await Promise.race([this.exitPromise.then(() => true), new Promise((r) => setTimeout(() => r(false), graceMs).unref?.())]);
|
|
42836
|
+
if (!exited) {
|
|
42837
|
+
try {
|
|
42838
|
+
this.process.kill("SIGKILL");
|
|
42839
|
+
} catch {
|
|
42840
|
+
}
|
|
42841
|
+
}
|
|
42842
|
+
}
|
|
42843
|
+
write(frame) {
|
|
42844
|
+
if (this.closedReason !== void 0) return false;
|
|
42845
|
+
const stdin = this.process.stdin;
|
|
42846
|
+
if (!stdin || stdin.writableEnded || stdin.destroyed) return false;
|
|
42847
|
+
try {
|
|
42848
|
+
stdin.write(JSON.stringify(frame) + "\n");
|
|
42849
|
+
return true;
|
|
42850
|
+
} catch (err) {
|
|
42851
|
+
this.settleClosed(err.message);
|
|
42852
|
+
return false;
|
|
42853
|
+
}
|
|
42854
|
+
}
|
|
42855
|
+
settleClosed(reason) {
|
|
42856
|
+
if (this.closedReason !== void 0) return;
|
|
42857
|
+
this.closedReason = reason;
|
|
42858
|
+
for (const [id, p] of this.pending) {
|
|
42859
|
+
this.pending.delete(id);
|
|
42860
|
+
if (p.timer) clearTimeout(p.timer);
|
|
42861
|
+
p.reject(new AcpConnectionClosed(p.method, reason));
|
|
42862
|
+
}
|
|
42863
|
+
}
|
|
42864
|
+
handleLine(line) {
|
|
42865
|
+
if (!line.trim()) return;
|
|
42866
|
+
let raw2;
|
|
42867
|
+
try {
|
|
42868
|
+
raw2 = JSON.parse(line);
|
|
42869
|
+
} catch {
|
|
42870
|
+
this.options.onDiagnostic?.({ kind: "junk-line", message: line.slice(0, 500) });
|
|
42871
|
+
return;
|
|
42872
|
+
}
|
|
42873
|
+
const parsed = JsonRpcInbound.safeParse(raw2);
|
|
42874
|
+
if (!parsed.success) {
|
|
42875
|
+
this.options.onDiagnostic?.({ kind: "protocol", message: `unrecognized frame: ${line.slice(0, 500)}` });
|
|
42876
|
+
return;
|
|
42877
|
+
}
|
|
42878
|
+
const frame = parsed.data;
|
|
42879
|
+
if (typeof frame.method === "string") {
|
|
42880
|
+
if (frame.id !== void 0) this.handleRequest(frame.id, frame.method, frame.params);
|
|
42881
|
+
else this.options.onNotification(frame.method, frame.params);
|
|
42882
|
+
return;
|
|
42883
|
+
}
|
|
42884
|
+
const id = frame.id;
|
|
42885
|
+
const p = this.pending.get(id);
|
|
42886
|
+
if (!p) {
|
|
42887
|
+
this.options.onDiagnostic?.({ kind: "protocol", message: `response to unknown request id ${String(id)}` });
|
|
42888
|
+
return;
|
|
42889
|
+
}
|
|
42890
|
+
this.pending.delete(id);
|
|
42891
|
+
if (p.timer) clearTimeout(p.timer);
|
|
42892
|
+
if (frame.error !== void 0) p.reject(new AcpRemoteError(p.method, frame.error));
|
|
42893
|
+
else p.resolve(frame.result);
|
|
42894
|
+
}
|
|
42895
|
+
handleRequest(id, method, params) {
|
|
42896
|
+
void this.options.onRequest(method, params).then(
|
|
42897
|
+
(result) => {
|
|
42898
|
+
this.write({ jsonrpc: "2.0", id, result: result ?? {} });
|
|
42899
|
+
},
|
|
42900
|
+
(err) => {
|
|
42901
|
+
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) };
|
|
42902
|
+
if (!(err instanceof AcpRequestError)) this.options.onDiagnostic?.({ kind: "unhandled-request", message: `${method}: ${e.message}` });
|
|
42903
|
+
this.write({ jsonrpc: "2.0", id, error: e });
|
|
42904
|
+
}
|
|
42905
|
+
);
|
|
42906
|
+
}
|
|
42907
|
+
};
|
|
42908
|
+
}
|
|
42909
|
+
});
|
|
42910
|
+
|
|
42911
|
+
// packages/server/src/backend/acp-bridge.ts
|
|
42912
|
+
function clip3(text2, max) {
|
|
42913
|
+
return text2.length > max ? `${text2.slice(0, max - 1)}\u2026` : text2;
|
|
42914
|
+
}
|
|
42915
|
+
function frizzMcpServer(mcp, slug) {
|
|
42916
|
+
if (!mcp) return [];
|
|
42917
|
+
return [{
|
|
42918
|
+
name: FRIZZ_MCP.name,
|
|
42919
|
+
command: process.execPath,
|
|
42920
|
+
args: [mcp.scriptPath],
|
|
42921
|
+
env: Object.entries(frizzMcpEnv({ ...mcp, slug })).map(([name, value]) => ({ name, value }))
|
|
42922
|
+
}];
|
|
42923
|
+
}
|
|
42924
|
+
function toolResultText3(tc) {
|
|
42925
|
+
const parts = [];
|
|
42926
|
+
for (const c of tc.content ?? []) {
|
|
42927
|
+
if (c.type === "content") {
|
|
42928
|
+
const t = contentText(c.content);
|
|
42929
|
+
if (t) parts.push(t);
|
|
42930
|
+
} else if (c.type === "diff") parts.push(`--- ${c.path}
|
|
42931
|
+
${c.newText}`);
|
|
42932
|
+
}
|
|
42933
|
+
if (parts.length) return parts.join("\n");
|
|
42934
|
+
const raw2 = tc.rawOutput;
|
|
42935
|
+
if (raw2 && typeof raw2 === "object" && typeof raw2.output === "string") return raw2.output;
|
|
42936
|
+
if (typeof tc.rawOutput === "string") return tc.rawOutput;
|
|
42937
|
+
return "";
|
|
42938
|
+
}
|
|
42939
|
+
function buildAcpPermissionInteraction(params, owner, agent) {
|
|
42940
|
+
const tc = params.toolCall;
|
|
42941
|
+
const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
|
|
42942
|
+
const command = typeof input?.command === "string" ? input.command : "";
|
|
42943
|
+
const isCommand = tc.kind === "execute" && command !== "";
|
|
42944
|
+
const isFile = (tc.kind === "edit" || tc.kind === "delete" || tc.kind === "move") && (tc.locations?.[0]?.path || typeof input?.filePath === "string" || typeof input?.path === "string");
|
|
42945
|
+
const payloadKind = isCommand ? "command-approval" : isFile ? "file-approval" : "permission-approval";
|
|
42946
|
+
const optionByDecision = /* @__PURE__ */ new Map();
|
|
42947
|
+
const allowedDecisions = [];
|
|
42948
|
+
const add = (id, semantic, label, optionId, description) => {
|
|
42949
|
+
if (optionByDecision.has(id)) return;
|
|
42950
|
+
optionByDecision.set(id, optionId);
|
|
42951
|
+
allowedDecisions.push({ id, semantic, label: clip3(label, 150), ...description ? { description: clip3(description, 500) } : {} });
|
|
42952
|
+
};
|
|
42953
|
+
for (const opt of params.options) {
|
|
42954
|
+
const label = opt.name ?? opt.kind ?? opt.optionId;
|
|
42955
|
+
const once = payloadKind === "permission-approval" ? "grant-turn" : "accept";
|
|
42956
|
+
const always = payloadKind === "permission-approval" ? "grant-session" : "acceptForSession";
|
|
42957
|
+
const deny = payloadKind === "permission-approval" ? "deny" : "decline";
|
|
42958
|
+
if (opt.kind === "allow_once") add(once, "approve", label, opt.optionId, "Allow this once.");
|
|
42959
|
+
else if (opt.kind === "allow_always") add(always, "approve", label, opt.optionId, "Allow for the rest of this session.");
|
|
42960
|
+
else if (opt.kind === "reject_once" || opt.kind === "reject_always") add(deny, "deny", label, opt.optionId, "Block this tool call.");
|
|
42961
|
+
}
|
|
42962
|
+
if (!allowedDecisions.some((d) => d.semantic === "deny")) return null;
|
|
42963
|
+
const title = clip3(tc.title ?? (isCommand ? "Run a command?" : "Approve a tool call?"), 150);
|
|
42964
|
+
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);
|
|
42965
|
+
const preview = clip3(redactCredentialSyntax(previewLines.join("\n\n")), 6e3);
|
|
42966
|
+
const pathLabel = clip3(tc.locations?.[0]?.path ?? (typeof input?.filePath === "string" ? input.filePath : typeof input?.path === "string" ? input.path : ""), 1e3);
|
|
42967
|
+
const base = {
|
|
42968
|
+
protocolVersion: INTERACTION_PROTOCOL_VERSION,
|
|
42969
|
+
contentFormat: "plain-text",
|
|
42970
|
+
provider: { kind: "acp", name: agent.label },
|
|
42971
|
+
source: { kind: "tool", id: clip3(tc.toolCallId, 500), label: clip3(tc.title ?? tc.kind ?? "tool", 150) },
|
|
42972
|
+
owner: { projectId: owner.projectId, threadSlug: owner.threadSlug, sessionId: owner.sessionId, turnId: clip3(tc.toolCallId, 500), itemId: clip3(tc.toolCallId, 500), sessionEpoch: 0, capabilityRevision: 0 },
|
|
42973
|
+
providerRequestId: clip3(tc.toolCallId, 500),
|
|
42974
|
+
allowedDecisions,
|
|
42975
|
+
expiresAt: null
|
|
42976
|
+
};
|
|
42977
|
+
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) };
|
|
42978
|
+
const parsed = InteractionRequest.safeParse({ ...base, payload });
|
|
42979
|
+
return parsed.success ? { request: parsed.data, optionByDecision } : null;
|
|
42980
|
+
}
|
|
42981
|
+
function createAcpBridge(options) {
|
|
42982
|
+
return new AcpBridge(options);
|
|
42983
|
+
}
|
|
42984
|
+
var AUTH_HINT, ACP_MODEL_CACHE_MS, AcpBridge;
|
|
42985
|
+
var init_acp_bridge = __esm({
|
|
42986
|
+
"packages/server/src/backend/acp-bridge.ts"() {
|
|
42987
|
+
"use strict";
|
|
42988
|
+
init_src();
|
|
42989
|
+
init_credential_redaction();
|
|
42990
|
+
init_logging();
|
|
42991
|
+
init_acp_agents();
|
|
42992
|
+
init_acp_rpc();
|
|
42993
|
+
init_acp_transcript();
|
|
42994
|
+
init_acp_types();
|
|
42995
|
+
init_types2();
|
|
42996
|
+
init_worker_env();
|
|
42997
|
+
AUTH_HINT = "The agent needs a login first. ";
|
|
42998
|
+
ACP_MODEL_CACHE_MS = 10 * 6e4;
|
|
42999
|
+
AcpBridge = class {
|
|
43000
|
+
constructor(options) {
|
|
43001
|
+
this.options = options;
|
|
43002
|
+
this.unsubscribe = options.interactions?.subscribe((change) => {
|
|
43003
|
+
for (const live of this.sessions.values()) {
|
|
43004
|
+
const pending = live.pendingPerms.get(change.interactionId);
|
|
43005
|
+
if (!pending || change.lifecycle === "pending") continue;
|
|
43006
|
+
live.pendingPerms.delete(change.interactionId);
|
|
43007
|
+
const record = options.interactions.get(pending.scope, change.interactionId);
|
|
43008
|
+
const decisionId = change.lifecycle === "resolved" ? record?.resolution?.decisionId : void 0;
|
|
43009
|
+
const optionId = decisionId ? pending.optionByDecision.get(decisionId) : void 0;
|
|
43010
|
+
pending.resolve(optionId ? { outcome: { outcome: "selected", optionId } } : { outcome: { outcome: "cancelled" } });
|
|
43011
|
+
}
|
|
43012
|
+
});
|
|
43013
|
+
}
|
|
43014
|
+
sessions = /* @__PURE__ */ new Map();
|
|
43015
|
+
unsubscribe;
|
|
43016
|
+
closed = false;
|
|
43017
|
+
now() {
|
|
43018
|
+
return (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
43019
|
+
}
|
|
43020
|
+
diagnostic(slug, kind, message) {
|
|
43021
|
+
this.options.onDiagnostic?.({ threadSlug: slug, kind, message });
|
|
43022
|
+
if (kind !== "stderr") log.debug("acp", `[${slug}] ${kind}: ${message}`);
|
|
43023
|
+
}
|
|
43024
|
+
/** The agents this bridge can launch, with the operator's own entries merged in. */
|
|
43025
|
+
agents() {
|
|
43026
|
+
return acpAgentSpecs(this.options.customAgents?.());
|
|
43027
|
+
}
|
|
43028
|
+
resolveAgent(agentId) {
|
|
43029
|
+
const agent = resolveAcpAgent(agentId, this.options.customAgents?.(), this.options.env ?? process.env);
|
|
43030
|
+
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(", ")}.`);
|
|
43031
|
+
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.`);
|
|
43032
|
+
return agent;
|
|
43033
|
+
}
|
|
43034
|
+
// ---- session lifecycle ----------------------------------------------------------------------
|
|
43035
|
+
/** Spawn the agent, initialize, and open (or load) a session. Throws an actionable error on any
|
|
43036
|
+
* failure and leaves nothing behind. */
|
|
43037
|
+
async open(input) {
|
|
43038
|
+
const { threadSlug: slug, sessionId, cwd } = input;
|
|
43039
|
+
const agent = this.resolveAgent(input.agentId);
|
|
43040
|
+
const env = inheritWorkerEnvironment(this.options.env ?? process.env);
|
|
43041
|
+
const spawn8 = this.options.spawn ?? spawnAcpChild;
|
|
43042
|
+
const proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
|
|
43043
|
+
const writer = new AcpTranscriptWriter(acpTranscriptPath(this.options.stateDir, sessionId));
|
|
43044
|
+
let live;
|
|
43045
|
+
const conn = new AcpConnection(proc, {
|
|
43046
|
+
onRequest: (method, params) => live ? this.handleRequest(live, method, params) : Promise.reject(new AcpRequestError(ACP_ERROR_INTERNAL, "session not ready")),
|
|
43047
|
+
onNotification: (method, params) => {
|
|
43048
|
+
if (live && method === "session/update") this.handleNotification(live, params);
|
|
43049
|
+
},
|
|
43050
|
+
onDiagnostic: (d) => this.diagnostic(slug, d.kind, d.message),
|
|
43051
|
+
requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
|
|
43052
|
+
});
|
|
43053
|
+
const fail = async (err) => {
|
|
43054
|
+
await conn.close(500);
|
|
43055
|
+
const stderr = conn.recentStderr.slice(-3).join(" | ");
|
|
43056
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
43057
|
+
throw new Error(`${e.message}${stderr ? ` (agent stderr: ${clip3(stderr, 400)})` : ""}`);
|
|
43058
|
+
};
|
|
43059
|
+
let init;
|
|
43060
|
+
try {
|
|
43061
|
+
init = AcpInitializeResult.parse(await conn.request("initialize", {
|
|
43062
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
43063
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
43064
|
+
clientInfo: { name: "frizz", version: "0" }
|
|
43065
|
+
}));
|
|
43066
|
+
} catch (err) {
|
|
43067
|
+
return fail(err instanceof Error ? new Error(`${agent.label} did not complete the ACP handshake: ${err.message}`) : err);
|
|
43068
|
+
}
|
|
43069
|
+
const mcpServers = frizzMcpServer(this.options.frizzMcp, slug);
|
|
43070
|
+
live = { slug, sessionId, cwd, agent, conn, init, acpSessionId: "", writer, loading: false, queue: [], pendingPerms: /* @__PURE__ */ new Map(), tools: /* @__PURE__ */ new Map(), exited: false };
|
|
43071
|
+
void conn.exited.then(() => {
|
|
43072
|
+
if (live) this.onExit(live);
|
|
43073
|
+
});
|
|
43074
|
+
const authError = (err) => {
|
|
43075
|
+
const hint = (init.authMethods ?? []).map((m) => m.description ?? m.name ?? m.id).filter(Boolean).join("; ");
|
|
43076
|
+
return new Error(`${AUTH_HINT}${agent.label} refused to open a session: ${err.error.message}.${hint ? ` ${hint}.` : ""}`);
|
|
43077
|
+
};
|
|
43078
|
+
let resumed = "fresh";
|
|
43079
|
+
if (input.acpSessionId && init.agentCapabilities?.loadSession) {
|
|
43080
|
+
live.loading = true;
|
|
43081
|
+
try {
|
|
43082
|
+
const loaded = AcpLoadSessionResult.parse(await conn.request("session/load", { sessionId: input.acpSessionId, cwd, mcpServers }));
|
|
43083
|
+
live.acpSessionId = input.acpSessionId;
|
|
43084
|
+
this.adoptModel(live, loaded.configOptions);
|
|
43085
|
+
resumed = "loaded";
|
|
43086
|
+
} catch (err) {
|
|
43087
|
+
if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
|
|
43088
|
+
this.diagnostic(slug, "protocol", `session/load ${input.acpSessionId} failed (${err.message}); starting a fresh session`);
|
|
43089
|
+
} finally {
|
|
43090
|
+
live.loading = false;
|
|
43091
|
+
}
|
|
43092
|
+
}
|
|
43093
|
+
if (!live.acpSessionId) {
|
|
43094
|
+
try {
|
|
43095
|
+
const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers }));
|
|
43096
|
+
live.acpSessionId = created.sessionId;
|
|
43097
|
+
this.adoptModel(live, created.configOptions);
|
|
43098
|
+
} catch (err) {
|
|
43099
|
+
if (err instanceof AcpRemoteError && err.error.code === ACP_ERROR_AUTH_REQUIRED) return fail(authError(err));
|
|
43100
|
+
return fail(err instanceof Error ? new Error(`${agent.label} could not open a session: ${err.message}`) : err);
|
|
43101
|
+
}
|
|
43102
|
+
}
|
|
43103
|
+
const modelProblem = await this.applyModel(live, input.modelId);
|
|
43104
|
+
if (resumed === "fresh") {
|
|
43105
|
+
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 } : {} });
|
|
43106
|
+
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.` });
|
|
43107
|
+
}
|
|
43108
|
+
if (modelProblem) writer.append({ kind: "acp-note", at: this.now(), text: modelProblem });
|
|
43109
|
+
this.sessions.set(sessionId, live);
|
|
43110
|
+
return { live, resumed };
|
|
43111
|
+
}
|
|
43112
|
+
/** The agent's model option (`category: "model"`, else the one literally named `model`). */
|
|
43113
|
+
modelOption(configOptions) {
|
|
43114
|
+
return configOptions?.find((o) => o.category === "model") ?? configOptions?.find((o) => o.id === "model");
|
|
43115
|
+
}
|
|
43116
|
+
adoptModel(live, configOptions) {
|
|
43117
|
+
const option = this.modelOption(configOptions);
|
|
43118
|
+
if (!option) return;
|
|
43119
|
+
live.modelOptionId = option.id;
|
|
43120
|
+
if (typeof option.currentValue === "string") live.model = option.currentValue;
|
|
43121
|
+
}
|
|
43122
|
+
/** Ask the agent for the thread's model when it is not already the one in effect. Returns the note
|
|
43123
|
+
* to write when the agent refused — the thread runs on, on the agent's own model, and says so. */
|
|
43124
|
+
async applyModel(live, modelId) {
|
|
43125
|
+
if (!modelId || modelId === live.model) return void 0;
|
|
43126
|
+
try {
|
|
43127
|
+
const res = AcpSetConfigOptionResult.parse(await live.conn.request("session/set_config_option", { sessionId: live.acpSessionId, configId: live.modelOptionId ?? "model", value: modelId }));
|
|
43128
|
+
this.adoptModel(live, res.configOptions);
|
|
43129
|
+
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"}.`;
|
|
43130
|
+
if (!res.configOptions?.length) live.model = modelId;
|
|
43131
|
+
return void 0;
|
|
43132
|
+
} catch (err) {
|
|
43133
|
+
const message = err instanceof AcpRemoteError ? err.error.message : err.message;
|
|
43134
|
+
this.diagnostic(live.slug, "protocol", `session/set_config_option ${modelId} failed: ${message}`);
|
|
43135
|
+
return `${live.agent.label} could not switch to ${modelId} (${clip3(message, 200)}); the session runs on ${live.model ?? "its own default model"}.`;
|
|
43136
|
+
}
|
|
43137
|
+
}
|
|
43138
|
+
// ---- the model catalogue an agent advertises -----------------------------------------------------
|
|
43139
|
+
modelProbes = /* @__PURE__ */ new Map();
|
|
43140
|
+
modelCache = /* @__PURE__ */ new Map();
|
|
43141
|
+
/** The models an agent offers, read by opening a throwaway session (there is no other way to ask:
|
|
43142
|
+
* ACP advertises models only in a session's config options). Cached per agent for ten minutes; a
|
|
43143
|
+
* probe that cannot open a session (not installed, not logged in) yields an empty list with the
|
|
43144
|
+
* reason, never a throw, so the composer degrades to the agent's own default. */
|
|
43145
|
+
agentModels(agentId, cwd, opts = {}) {
|
|
43146
|
+
const cached2 = this.modelCache.get(agentId);
|
|
43147
|
+
if (cached2 && !opts.refresh && Date.now() - Date.parse(cached2.probedAt) < ACP_MODEL_CACHE_MS) return Promise.resolve(cached2);
|
|
43148
|
+
const inflight = this.modelProbes.get(agentId);
|
|
43149
|
+
if (inflight) return inflight;
|
|
43150
|
+
const probe = this.probeModels(agentId, cwd).then((result) => {
|
|
43151
|
+
if (!result.error) this.modelCache.set(agentId, result);
|
|
43152
|
+
return result;
|
|
43153
|
+
}).finally(() => this.modelProbes.delete(agentId));
|
|
43154
|
+
this.modelProbes.set(agentId, probe);
|
|
43155
|
+
return probe;
|
|
43156
|
+
}
|
|
43157
|
+
async probeModels(agentId, cwd) {
|
|
43158
|
+
const probedAt = this.now();
|
|
43159
|
+
const empty = (error) => ({ agentId, models: [], error, probedAt });
|
|
43160
|
+
let agent;
|
|
43161
|
+
try {
|
|
43162
|
+
agent = this.resolveAgent(agentId);
|
|
43163
|
+
} catch (err) {
|
|
43164
|
+
return empty(err.message);
|
|
43165
|
+
}
|
|
43166
|
+
const env = inheritWorkerEnvironment(this.options.env ?? process.env);
|
|
43167
|
+
const spawn8 = this.options.spawn ?? spawnAcpChild;
|
|
43168
|
+
let proc;
|
|
43169
|
+
try {
|
|
43170
|
+
proc = spawn8({ command: agent.bin, args: agent.args, cwd, env });
|
|
43171
|
+
} catch (err) {
|
|
43172
|
+
return empty(`${agent.label} could not start: ${err.message}`);
|
|
43173
|
+
}
|
|
43174
|
+
const conn = new AcpConnection(proc, {
|
|
43175
|
+
onRequest: () => Promise.reject(new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, "probe session takes no requests")),
|
|
43176
|
+
onNotification: () => {
|
|
43177
|
+
},
|
|
43178
|
+
onDiagnostic: (d) => this.diagnostic(`probe:${agentId}`, d.kind, d.message),
|
|
43179
|
+
requestTimeoutMs: this.options.initializeTimeoutMs ?? 6e4
|
|
43180
|
+
});
|
|
43181
|
+
try {
|
|
43182
|
+
const init = AcpInitializeResult.parse(await conn.request("initialize", {
|
|
43183
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
43184
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
43185
|
+
clientInfo: { name: "frizz", version: "0" }
|
|
43186
|
+
}));
|
|
43187
|
+
const created = AcpNewSessionResult.parse(await conn.request("session/new", { cwd, mcpServers: [] }));
|
|
43188
|
+
const option = this.modelOption(created.configOptions);
|
|
43189
|
+
const current = typeof option?.currentValue === "string" ? option.currentValue : void 0;
|
|
43190
|
+
void init;
|
|
43191
|
+
return {
|
|
43192
|
+
agentId,
|
|
43193
|
+
models: (option?.options ?? []).map((o) => ({ id: o.value, name: o.name ?? o.value })),
|
|
43194
|
+
...current ? { current } : {},
|
|
43195
|
+
probedAt
|
|
43196
|
+
};
|
|
43197
|
+
} catch (err) {
|
|
43198
|
+
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}`;
|
|
43199
|
+
return empty(clip3(message, 400));
|
|
43200
|
+
} finally {
|
|
43201
|
+
await conn.close(500);
|
|
43202
|
+
}
|
|
43203
|
+
}
|
|
43204
|
+
onExit(live) {
|
|
43205
|
+
if (live.exited) return;
|
|
43206
|
+
live.exited = true;
|
|
43207
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43208
|
+
if (live.turn) {
|
|
43209
|
+
}
|
|
43210
|
+
if (this.sessions.get(live.sessionId) === live) this.sessions.delete(live.sessionId);
|
|
43211
|
+
this.options.onStatusChange?.();
|
|
43212
|
+
}
|
|
43213
|
+
// ---- public API --------------------------------------------------------------------------------
|
|
43214
|
+
async spawnDispatch(input) {
|
|
43215
|
+
if (this.closed) throw new Error("ACP bridge is closed");
|
|
43216
|
+
const { live } = await this.open(input);
|
|
43217
|
+
void this.runTurn(live, input.prompt, input.userText, void 0);
|
|
43218
|
+
return this.info(live);
|
|
43219
|
+
}
|
|
43220
|
+
/** Deliver a follow-up: to the live session, to a re-opened one (load or fresh), or into the queue
|
|
43221
|
+
* when a turn is running. Returns the (possibly new) ACP session id so the caller can re-pin it. */
|
|
43222
|
+
async followUp(input) {
|
|
43223
|
+
if (this.closed) throw new Error("ACP bridge is closed");
|
|
43224
|
+
let live = this.sessions.get(input.sessionId);
|
|
43225
|
+
let resumed = "live";
|
|
43226
|
+
if (!live || live.exited || live.conn.closed) {
|
|
43227
|
+
const opened = await this.open(input);
|
|
43228
|
+
live = opened.live;
|
|
43229
|
+
resumed = opened.resumed;
|
|
43230
|
+
}
|
|
43231
|
+
if (live.turn) {
|
|
43232
|
+
live.queue.push({ text: input.text, ...input.deliveryId ? { deliveryId: input.deliveryId } : {} });
|
|
43233
|
+
return { ...this.info(live), state: "queued", resumed };
|
|
43234
|
+
}
|
|
43235
|
+
void this.runTurn(live, input.text, input.text, input.deliveryId);
|
|
43236
|
+
return { ...this.info(live), state: "delivered", resumed };
|
|
43237
|
+
}
|
|
43238
|
+
/** `session/cancel`, then wait (bounded) for the prompt to return. `interrupted: false` when no turn
|
|
43239
|
+
* was running. The queue is NOT discarded — a follow-up typed during the turn still goes out. */
|
|
43240
|
+
async interruptTurn(threadSlug, sessionId) {
|
|
43241
|
+
const live = this.sessions.get(sessionId);
|
|
43242
|
+
if (!live || live.slug !== threadSlug || !live.turn) return { interrupted: false };
|
|
43243
|
+
live.turn.cancelRequested = true;
|
|
43244
|
+
live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
|
|
43245
|
+
const settled = await Promise.race([live.turn.done.then(() => true), new Promise((r) => setTimeout(() => r(false), 15e3).unref?.())]);
|
|
43246
|
+
if (!settled) {
|
|
43247
|
+
await live.conn.close(1e3);
|
|
43248
|
+
return { interrupted: true };
|
|
43249
|
+
}
|
|
43250
|
+
return { interrupted: true };
|
|
43251
|
+
}
|
|
43252
|
+
releaseSession(threadSlug, sessionId, reason) {
|
|
43253
|
+
const live = this.sessions.get(sessionId);
|
|
43254
|
+
if (!live || live.slug !== threadSlug) return;
|
|
43255
|
+
this.sessions.delete(sessionId);
|
|
43256
|
+
live.queue.length = 0;
|
|
43257
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43258
|
+
if (live.turn) live.conn.notify("session/cancel", { sessionId: live.acpSessionId });
|
|
43259
|
+
void live.conn.close(reason === "shutdown" ? 1e3 : 3e3);
|
|
43260
|
+
}
|
|
43261
|
+
/** Switch a LIVE session's model (`session/set_config_option`); `applied: false` when the session is
|
|
43262
|
+
* not open, in which case the caller's stored slug reaches the agent on the next open. A refusal is
|
|
43263
|
+
* written to the transcript as a note, exactly as on open, and still counts as applied: the agent
|
|
43264
|
+
* answered, and the note says what it runs on. */
|
|
43265
|
+
async setModel(threadSlug, sessionId, modelId) {
|
|
43266
|
+
const live = this.sessions.get(sessionId);
|
|
43267
|
+
if (!live || live.slug !== threadSlug || live.exited || live.conn.closed) return { applied: false };
|
|
43268
|
+
const problem = await this.applyModel(live, modelId);
|
|
43269
|
+
if (problem) live.writer.append({ kind: "acp-note", at: this.now(), text: problem });
|
|
43270
|
+
return { applied: true, ...live.model ? { model: live.model } : {} };
|
|
43271
|
+
}
|
|
43272
|
+
turnLiveness(threadSlug, sessionId) {
|
|
43273
|
+
const live = this.sessions.get(sessionId);
|
|
43274
|
+
if (!live || live.slug !== threadSlug) return void 0;
|
|
43275
|
+
return { sessionLive: !live.exited && !live.conn.closed, turnActive: live.turn !== void 0, queued: live.queue.length };
|
|
43276
|
+
}
|
|
43277
|
+
session(threadSlug, sessionId) {
|
|
43278
|
+
const live = this.sessions.get(sessionId);
|
|
43279
|
+
return live && live.slug === threadSlug ? this.info(live) : void 0;
|
|
43280
|
+
}
|
|
43281
|
+
ownsInteraction(scope, interactionId) {
|
|
43282
|
+
const live = this.sessions.get(scope.sessionId);
|
|
43283
|
+
return live !== void 0 && live.slug === scope.threadSlug && live.pendingPerms.has(interactionId);
|
|
43284
|
+
}
|
|
43285
|
+
async shutdown() {
|
|
43286
|
+
this.closed = true;
|
|
43287
|
+
this.unsubscribe?.();
|
|
43288
|
+
const all = [...this.sessions.values()];
|
|
43289
|
+
this.sessions.clear();
|
|
43290
|
+
await Promise.all(all.map(async (live) => {
|
|
43291
|
+
this.retirePermissions(live, "provider-cancelled");
|
|
43292
|
+
await live.conn.close(1e3);
|
|
43293
|
+
}));
|
|
43294
|
+
}
|
|
43295
|
+
info(live) {
|
|
43296
|
+
return {
|
|
43297
|
+
acpSessionId: live.acpSessionId,
|
|
43298
|
+
agent: { id: live.agent.id, ...live.init.agentInfo?.name ? { name: live.init.agentInfo.name } : {}, ...live.init.agentInfo?.version ? { version: live.init.agentInfo.version } : {} },
|
|
43299
|
+
...live.model ? { model: live.model } : {}
|
|
43300
|
+
};
|
|
43301
|
+
}
|
|
43302
|
+
// ---- the turn ---------------------------------------------------------------------------------
|
|
43303
|
+
async runTurn(live, sendText, recordText, deliveryId) {
|
|
43304
|
+
let finish;
|
|
43305
|
+
const done = new Promise((r) => {
|
|
43306
|
+
finish = r;
|
|
43307
|
+
});
|
|
43308
|
+
const turn = { startedAt: this.now(), ...deliveryId ? { deliveryId } : {}, text: "", messageText: "", thought: "", cancelRequested: false, done, finish };
|
|
43309
|
+
live.turn = turn;
|
|
43310
|
+
live.writer.append({ kind: "user-message", at: turn.startedAt, text: recordText, synthetic: false });
|
|
43311
|
+
live.writer.append({ kind: "turn-start", at: turn.startedAt });
|
|
43312
|
+
this.options.onStatusChange?.();
|
|
43313
|
+
try {
|
|
43314
|
+
const raw2 = await live.conn.requestOpenEnded("session/prompt", { sessionId: live.acpSessionId, prompt: [{ type: "text", text: sendText }] });
|
|
43315
|
+
const result = AcpPromptResult.parse(raw2);
|
|
43316
|
+
this.flushThought(live);
|
|
43317
|
+
const finalText = this.flushText(live, true);
|
|
43318
|
+
live.writer.append({ kind: "turn-end", at: this.now(), ...finalText !== void 0 ? { finalText } : {}, successful: result.stopReason !== "refusal" && result.stopReason !== "cancelled" });
|
|
43319
|
+
if (result.stopReason === "cancelled") live.writer.append({ kind: "acp-note", at: this.now(), text: "Turn cancelled." });
|
|
43320
|
+
} catch (err) {
|
|
43321
|
+
this.flushThought(live);
|
|
43322
|
+
const finalText = this.flushText(live, true);
|
|
43323
|
+
const at = this.now();
|
|
43324
|
+
const abortedByUs = turn.cancelRequested && err instanceof AcpRemoteError && err.error.code === ACP_ERROR_INTERNAL;
|
|
43325
|
+
if (abortedByUs) {
|
|
43326
|
+
live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
|
|
43327
|
+
live.writer.append({ kind: "acp-note", at, text: "Turn cancelled." });
|
|
43328
|
+
} else {
|
|
43329
|
+
const message = err instanceof AcpRemoteError ? err.error.code === ACP_ERROR_AUTH_REQUIRED ? `${AUTH_HINT}${err.error.message}` : err.error.message : err.message;
|
|
43330
|
+
live.writer.append({ kind: "provider-error", at, error: { message: clip3(message, 2e3), ...err instanceof AcpRemoteError ? { code: String(err.error.code) } : {} } });
|
|
43331
|
+
live.writer.append({ kind: "turn-end", at, ...finalText !== void 0 ? { finalText } : {}, successful: false });
|
|
43332
|
+
}
|
|
43333
|
+
} finally {
|
|
43334
|
+
this.retirePermissions(live, "turn-ended");
|
|
43335
|
+
if (live.turn === turn) live.turn = void 0;
|
|
43336
|
+
turn.finish();
|
|
43337
|
+
this.options.onStatusChange?.();
|
|
43338
|
+
const next = live.queue.shift();
|
|
43339
|
+
if (next && !live.exited && !live.conn.closed) void this.runTurn(live, next.text, next.text, next.deliveryId);
|
|
43340
|
+
}
|
|
43341
|
+
}
|
|
43342
|
+
/** Write the buffered text as one record. Returns, for a FINAL flush, the whole text of the agent's
|
|
43343
|
+
* last message (every partial flush included) — what the turn-end carries as `finalText`, so the
|
|
43344
|
+
* fold's preview and fence parsing see the complete message, never the last chunk. */
|
|
43345
|
+
flushText(live, final) {
|
|
43346
|
+
const turn = live.turn;
|
|
43347
|
+
if (!turn) return void 0;
|
|
43348
|
+
if (turn.flushTimer) {
|
|
43349
|
+
clearTimeout(turn.flushTimer);
|
|
43350
|
+
turn.flushTimer = void 0;
|
|
43351
|
+
}
|
|
43352
|
+
if (turn.text) {
|
|
43353
|
+
const text2 = turn.text;
|
|
43354
|
+
turn.text = "";
|
|
43355
|
+
live.writer.append({ kind: "assistant-text", at: this.now(), text: text2, final, ...turn.messageId ? { messageId: turn.messageId } : {} });
|
|
43356
|
+
}
|
|
43357
|
+
return final && turn.messageText ? turn.messageText : void 0;
|
|
43358
|
+
}
|
|
43359
|
+
flushThought(live) {
|
|
43360
|
+
const turn = live.turn;
|
|
43361
|
+
if (!turn?.thought.trim()) {
|
|
43362
|
+
if (turn) turn.thought = "";
|
|
43363
|
+
return;
|
|
43364
|
+
}
|
|
43365
|
+
live.writer.append({ kind: "reasoning", at: this.now(), text: turn.thought });
|
|
43366
|
+
turn.thought = "";
|
|
43367
|
+
}
|
|
43368
|
+
scheduleFlush(live) {
|
|
43369
|
+
const turn = live.turn;
|
|
43370
|
+
if (!turn || turn.flushTimer) return;
|
|
43371
|
+
turn.flushTimer = setTimeout(() => {
|
|
43372
|
+
turn.flushTimer = void 0;
|
|
43373
|
+
this.flushText(live, false);
|
|
43374
|
+
}, this.options.flushMs ?? 1e3);
|
|
43375
|
+
turn.flushTimer.unref?.();
|
|
43376
|
+
}
|
|
43377
|
+
handleNotification(live, params) {
|
|
43378
|
+
if (live.loading) return;
|
|
43379
|
+
const parsed = AcpSessionNotification.safeParse(params);
|
|
43380
|
+
if (!parsed.success) {
|
|
43381
|
+
this.diagnostic(live.slug, "protocol", "malformed session/update");
|
|
43382
|
+
return;
|
|
43383
|
+
}
|
|
43384
|
+
const update = parseSessionUpdate(parsed.data.update);
|
|
43385
|
+
const turn = live.turn;
|
|
43386
|
+
switch (update.sessionUpdate) {
|
|
43387
|
+
case "agent_thought_chunk": {
|
|
43388
|
+
if (!turn) return;
|
|
43389
|
+
const t = contentText(update.content);
|
|
43390
|
+
if (t) turn.thought += t;
|
|
43391
|
+
return;
|
|
43392
|
+
}
|
|
43393
|
+
case "agent_message_chunk": {
|
|
43394
|
+
if (!turn) return;
|
|
43395
|
+
this.flushThought(live);
|
|
43396
|
+
const t = contentText(update.content);
|
|
43397
|
+
if (!t) return;
|
|
43398
|
+
if (update.messageId && turn.messageId && update.messageId !== turn.messageId) {
|
|
43399
|
+
this.flushText(live, false);
|
|
43400
|
+
turn.messageText = "";
|
|
43401
|
+
}
|
|
43402
|
+
if (update.messageId) turn.messageId = update.messageId;
|
|
43403
|
+
turn.text += t;
|
|
43404
|
+
turn.messageText += t;
|
|
43405
|
+
this.scheduleFlush(live);
|
|
43406
|
+
return;
|
|
43407
|
+
}
|
|
43408
|
+
case "tool_call": {
|
|
43409
|
+
this.flushThought(live);
|
|
43410
|
+
this.flushText(live, false);
|
|
43411
|
+
if (turn) turn.messageText = "";
|
|
43412
|
+
const meta = { ...update.kind ? { kind: update.kind } : {}, ...update.title ? { title: update.title } : {}, locations: (update.locations ?? []).map((l) => l.path), written: true };
|
|
43413
|
+
live.tools.set(update.toolCallId, meta);
|
|
43414
|
+
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 ?? [] } });
|
|
43415
|
+
if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
|
|
43416
|
+
return;
|
|
43417
|
+
}
|
|
43418
|
+
case "tool_call_update": {
|
|
43419
|
+
let meta = live.tools.get(update.toolCallId);
|
|
43420
|
+
if (!meta) {
|
|
43421
|
+
meta = { written: false };
|
|
43422
|
+
live.tools.set(update.toolCallId, meta);
|
|
43423
|
+
}
|
|
43424
|
+
if (update.kind) meta.kind = update.kind;
|
|
43425
|
+
if (update.title) meta.title = update.title;
|
|
43426
|
+
if (update.locations?.length) meta.locations = update.locations.map((l) => l.path);
|
|
43427
|
+
if (!meta.written) {
|
|
43428
|
+
this.flushThought(live);
|
|
43429
|
+
this.flushText(live, false);
|
|
43430
|
+
meta.written = true;
|
|
43431
|
+
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 ?? [] } });
|
|
43432
|
+
}
|
|
43433
|
+
if (update.status === "completed" || update.status === "failed") this.recordToolResult(live, update);
|
|
43434
|
+
return;
|
|
43435
|
+
}
|
|
43436
|
+
case "usage_update":
|
|
43437
|
+
this.flushText(live, false);
|
|
43438
|
+
if (update.used > 0) live.writer.append({ kind: "context-usage", at: this.now(), tokens: update.used, window: update.size });
|
|
43439
|
+
return;
|
|
43440
|
+
case "session_info_update":
|
|
43441
|
+
if (update.title) live.writer.append({ kind: "title", title: clip3(update.title, 200) });
|
|
43442
|
+
return;
|
|
43443
|
+
case "config_option_update":
|
|
43444
|
+
this.adoptModel(live, update.configOptions);
|
|
43445
|
+
return;
|
|
43446
|
+
case "plan":
|
|
43447
|
+
case "user_message_chunk":
|
|
43448
|
+
case "compaction_update":
|
|
43449
|
+
return;
|
|
43450
|
+
case "unknown":
|
|
43451
|
+
this.diagnostic(live.slug, "update", `unhandled session/update ${String(update.raw?.sessionUpdate)}`);
|
|
43452
|
+
return;
|
|
43453
|
+
}
|
|
43454
|
+
}
|
|
43455
|
+
recordToolResult(live, tc) {
|
|
43456
|
+
const meta = live.tools.get(tc.toolCallId);
|
|
43457
|
+
if (meta?.status === "completed" || meta?.status === "failed") return;
|
|
43458
|
+
if (meta) meta.status = tc.status;
|
|
43459
|
+
const input = tc.rawInput && typeof tc.rawInput === "object" ? tc.rawInput : void 0;
|
|
43460
|
+
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 } : {} } });
|
|
43461
|
+
}
|
|
43462
|
+
// ---- the agent asking us ------------------------------------------------------------------------
|
|
43463
|
+
async handleRequest(live, method, params) {
|
|
43464
|
+
if (method === "session/request_permission") return this.handlePermission(live, params);
|
|
43465
|
+
throw new AcpRequestError(ACP_ERROR_METHOD_NOT_FOUND, `frizz does not implement ${method}`);
|
|
43466
|
+
}
|
|
43467
|
+
handlePermission(live, params) {
|
|
43468
|
+
const parsed = AcpRequestPermissionParams.safeParse(params);
|
|
43469
|
+
const cancelled = { outcome: { outcome: "cancelled" } };
|
|
43470
|
+
if (!parsed.success) return Promise.resolve(cancelled);
|
|
43471
|
+
const store = this.options.interactions;
|
|
43472
|
+
const reject = parsed.data.options.find((o) => o.kind === "reject_once") ?? parsed.data.options.find((o) => o.kind === "reject_always");
|
|
43473
|
+
const failClosed = reject ? { outcome: { outcome: "selected", optionId: reject.optionId } } : cancelled;
|
|
43474
|
+
if (!store) {
|
|
43475
|
+
this.diagnostic(live.slug, "permission", "no interaction store; denying the request");
|
|
43476
|
+
return Promise.resolve(failClosed);
|
|
43477
|
+
}
|
|
43478
|
+
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 });
|
|
43479
|
+
if (!built) {
|
|
43480
|
+
this.diagnostic(live.slug, "permission", "request could not be represented as a card; denying");
|
|
43481
|
+
return Promise.resolve(failClosed);
|
|
43482
|
+
}
|
|
43483
|
+
const scope = { projectId: this.options.projectId, threadSlug: live.slug, sessionId: live.sessionId };
|
|
43484
|
+
let id;
|
|
43485
|
+
try {
|
|
43486
|
+
id = store.create(built.request).interaction.id;
|
|
43487
|
+
} catch (err) {
|
|
43488
|
+
this.diagnostic(live.slug, "permission", `could not record the approval: ${err.message}`);
|
|
43489
|
+
return Promise.resolve(failClosed);
|
|
43490
|
+
}
|
|
43491
|
+
return new Promise((resolve12) => {
|
|
43492
|
+
live.pendingPerms.set(id, { resolve: resolve12, optionByDecision: built.optionByDecision, scope });
|
|
43493
|
+
this.options.onStatusChange?.();
|
|
43494
|
+
});
|
|
43495
|
+
}
|
|
43496
|
+
/** Cancel every open card for a session that can no longer answer one, answering the agent
|
|
43497
|
+
* `cancelled` for each. Hygiene, so it never throws. */
|
|
43498
|
+
retirePermissions(live, reason) {
|
|
43499
|
+
if (!live.pendingPerms.size) return;
|
|
43500
|
+
const store = this.options.interactions;
|
|
43501
|
+
let swept = [];
|
|
43502
|
+
try {
|
|
43503
|
+
swept = store?.cancelForSession(live.slug, live.sessionId, reason) ?? [];
|
|
43504
|
+
} catch {
|
|
43505
|
+
}
|
|
43506
|
+
for (const record of swept) live.pendingPerms.get(record.id)?.resolve({ outcome: { outcome: "cancelled" } });
|
|
43507
|
+
for (const [id, pending] of live.pendingPerms) {
|
|
43508
|
+
pending.resolve({ outcome: { outcome: "cancelled" } });
|
|
43509
|
+
live.pendingPerms.delete(id);
|
|
43510
|
+
}
|
|
43511
|
+
}
|
|
43512
|
+
};
|
|
43513
|
+
}
|
|
43514
|
+
});
|
|
43515
|
+
|
|
41980
43516
|
// packages/server/src/login-utility.ts
|
|
41981
43517
|
import { randomBytes } from "node:crypto";
|
|
41982
43518
|
import pty from "node-pty";
|
|
@@ -42154,27 +43690,27 @@ var init_login_utility = __esm({
|
|
|
42154
43690
|
});
|
|
42155
43691
|
|
|
42156
43692
|
// packages/server/src/backend/codex-app-server-diagnostics.ts
|
|
42157
|
-
import { appendFileSync as
|
|
42158
|
-
import { join as
|
|
43693
|
+
import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync16 } from "node:fs";
|
|
43694
|
+
import { join as join35 } from "node:path";
|
|
42159
43695
|
function codexDiagnosticLogPath(stateDir, projectId) {
|
|
42160
|
-
return
|
|
43696
|
+
return join35(stateDir, "codex-app-server", `${projectId}.diagnostics.log`);
|
|
42161
43697
|
}
|
|
42162
43698
|
function createCodexDiagnosticSink(stateDir, projectId, now = () => /* @__PURE__ */ new Date()) {
|
|
42163
|
-
const dir =
|
|
43699
|
+
const dir = join35(stateDir, "codex-app-server");
|
|
42164
43700
|
const path3 = codexDiagnosticLogPath(stateDir, projectId);
|
|
42165
43701
|
let ensured = false;
|
|
42166
43702
|
return (event) => {
|
|
42167
43703
|
if (event.event === "stderr") return;
|
|
42168
43704
|
try {
|
|
42169
43705
|
if (!ensured) {
|
|
42170
|
-
|
|
43706
|
+
mkdirSync15(dir, { recursive: true });
|
|
42171
43707
|
ensured = true;
|
|
42172
43708
|
}
|
|
42173
43709
|
try {
|
|
42174
|
-
if (
|
|
43710
|
+
if (statSync16(path3).size > MAX_BYTES2) renameSync8(path3, `${path3}.1`);
|
|
42175
43711
|
} catch {
|
|
42176
43712
|
}
|
|
42177
|
-
|
|
43713
|
+
appendFileSync4(path3, `${JSON.stringify({ at: now().toISOString(), ...event })}
|
|
42178
43714
|
`);
|
|
42179
43715
|
} catch {
|
|
42180
43716
|
}
|
|
@@ -42189,10 +43725,10 @@ var init_codex_app_server_diagnostics = __esm({
|
|
|
42189
43725
|
});
|
|
42190
43726
|
|
|
42191
43727
|
// packages/server/src/backend/stale-socket-sweep.ts
|
|
42192
|
-
import { execFile as
|
|
43728
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
42193
43729
|
import { readdirSync as readdirSync8, unlinkSync as unlinkSync4 } from "node:fs";
|
|
42194
43730
|
import { connect as connectSocket } from "node:net";
|
|
42195
|
-
import { join as
|
|
43731
|
+
import { join as join36 } from "node:path";
|
|
42196
43732
|
function parseLsofSocketNames(stdout) {
|
|
42197
43733
|
const referenced = /* @__PURE__ */ new Set();
|
|
42198
43734
|
for (const line of stdout.split("\n")) {
|
|
@@ -42209,7 +43745,7 @@ function sweepStaleSockets(options, deps = {}) {
|
|
|
42209
43745
|
const keep = new Set(options.keep ?? []);
|
|
42210
43746
|
let candidates;
|
|
42211
43747
|
try {
|
|
42212
|
-
candidates = (deps.readdir ?? readdirSync8)(options.dir).filter((name) => name.startsWith(options.prefix) && name.endsWith(".sock")).map((name) =>
|
|
43748
|
+
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
43749
|
} catch {
|
|
42214
43750
|
return;
|
|
42215
43751
|
}
|
|
@@ -42236,7 +43772,7 @@ var init_stale_socket_sweep = __esm({
|
|
|
42236
43772
|
"packages/server/src/backend/stale-socket-sweep.ts"() {
|
|
42237
43773
|
"use strict";
|
|
42238
43774
|
defaultListReferenced = (prefix, done) => {
|
|
42239
|
-
|
|
43775
|
+
execFile9("lsof", ["-U", "-F", "n"], { timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
|
|
42240
43776
|
if (!stdout) return done(null);
|
|
42241
43777
|
if (error && !stdout.includes(prefix)) return done(null);
|
|
42242
43778
|
done(parseLsofSocketNames(stdout));
|
|
@@ -42259,8 +43795,8 @@ var init_stale_socket_sweep = __esm({
|
|
|
42259
43795
|
|
|
42260
43796
|
// packages/server/src/orphan-reaper.ts
|
|
42261
43797
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
42262
|
-
import { readFileSync as
|
|
42263
|
-
import { dirname as
|
|
43798
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
43799
|
+
import { dirname as dirname16 } from "node:path";
|
|
42264
43800
|
function firstTokenBasename(command) {
|
|
42265
43801
|
const first = command.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
42266
43802
|
const slash = first.lastIndexOf("/");
|
|
@@ -42488,7 +44024,7 @@ function sweepOrphansOnce(deps = {}) {
|
|
|
42488
44024
|
}
|
|
42489
44025
|
function sweepStaleBrokerSockets() {
|
|
42490
44026
|
try {
|
|
42491
|
-
sweepStaleSockets({ dir:
|
|
44027
|
+
sweepStaleSockets({ dir: dirname16(claudeBrokerSocketPath("", "")), prefix: CLAUDE_BROKER_SOCKET_PREFIX });
|
|
42492
44028
|
} catch {
|
|
42493
44029
|
}
|
|
42494
44030
|
}
|
|
@@ -42538,7 +44074,7 @@ var init_orphan_reaper = __esm({
|
|
|
42538
44074
|
defaultExec = (file, args) => execFileSync4(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 1e4 });
|
|
42539
44075
|
defaultReadEnv = (pid) => {
|
|
42540
44076
|
try {
|
|
42541
|
-
return
|
|
44077
|
+
return readFileSync22(`/proc/${pid}/environ`, "utf8");
|
|
42542
44078
|
} catch {
|
|
42543
44079
|
return null;
|
|
42544
44080
|
}
|
|
@@ -42686,7 +44222,7 @@ var init_thread_hibernation = __esm({
|
|
|
42686
44222
|
});
|
|
42687
44223
|
|
|
42688
44224
|
// packages/server/src/context.ts
|
|
42689
|
-
import { join as
|
|
44225
|
+
import { join as join37 } from "node:path";
|
|
42690
44226
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
42691
44227
|
import { homedir as homedir15 } from "node:os";
|
|
42692
44228
|
function reconcileSessions(storage) {
|
|
@@ -42713,6 +44249,9 @@ function partialContextCleanup(resources) {
|
|
|
42713
44249
|
codexAppServer: createRetryableCleanup(async () => {
|
|
42714
44250
|
await resources.codexAppServer?.shutdown();
|
|
42715
44251
|
}),
|
|
44252
|
+
acpBridge: createRetryableCleanup(async () => {
|
|
44253
|
+
await resources.acpBridge?.shutdown();
|
|
44254
|
+
}),
|
|
42716
44255
|
claudeBroker: createRetryableCleanup(async () => {
|
|
42717
44256
|
resources.claudeBroker?.close();
|
|
42718
44257
|
resources.claudeRuntimeIngest?.close();
|
|
@@ -42836,7 +44375,7 @@ async function createContext(opts = {}) {
|
|
|
42836
44375
|
function createContextUnchecked(opts, resources) {
|
|
42837
44376
|
const home = opts.home ?? homedir15();
|
|
42838
44377
|
const project = opts.project ?? resolveProject();
|
|
42839
|
-
const storage = createStorage(opts.database ??
|
|
44378
|
+
const storage = createStorage(opts.database ?? join37(project.stateDir, "ui.db"), project.id);
|
|
42840
44379
|
resources.storage = storage;
|
|
42841
44380
|
const bus = new Bus();
|
|
42842
44381
|
const transcriptChange = new Emitter();
|
|
@@ -42911,7 +44450,8 @@ function createContextUnchecked(opts, resources) {
|
|
|
42911
44450
|
};
|
|
42912
44451
|
const claudeBackend = createClaudeBackend({ logDir: defaultLogDir(project), claudeBin: opts.claudeBin });
|
|
42913
44452
|
const codexBackend = createCodexBackend({});
|
|
42914
|
-
const
|
|
44453
|
+
const acpBackend = createAcpBackend({ stateDir: project.stateDir });
|
|
44454
|
+
const backendFor = (kind) => kind === "codex" ? codexBackend : kind === "acp" ? acpBackend : claudeBackend;
|
|
42915
44455
|
const codexAppServer = codexAppServerBridgeEnabled() ? createCodexAppServerBridge({
|
|
42916
44456
|
projectId: project.id,
|
|
42917
44457
|
projectDir: project.dir,
|
|
@@ -42963,6 +44503,19 @@ function createContextUnchecked(opts, resources) {
|
|
|
42963
44503
|
}
|
|
42964
44504
|
void codexAppServer?.warmUp();
|
|
42965
44505
|
opts.startup?.afterPhase?.("Codex app-server bridge");
|
|
44506
|
+
const acpBridge = createAcpBridge({
|
|
44507
|
+
projectId: project.id,
|
|
44508
|
+
stateDir: project.stateDir,
|
|
44509
|
+
frizzMcp: resolveFrizzMcp(frizzMcpTarget),
|
|
44510
|
+
interactions: storage.interactions,
|
|
44511
|
+
env: process.env,
|
|
44512
|
+
customAgents: () => getSettings(storage, home).acpAgents,
|
|
44513
|
+
onStatusChange: () => board?.refresh()
|
|
44514
|
+
});
|
|
44515
|
+
resources.acpBridge = acpBridge;
|
|
44516
|
+
contextUnsubscribers.push(storage.subscribeSessionLifecycle((event) => {
|
|
44517
|
+
acpBridge.releaseSession(event.previous.slug, event.previous.session_id, event.type === "replaced" ? "session-replaced" : "session-deleted");
|
|
44518
|
+
}));
|
|
42966
44519
|
const claudeRuntimeIngest = claudeBrokerBridgeEnabled() ? createClaudeRuntimeIngest({ nudge: () => tailer.nudge?.() }) : void 0;
|
|
42967
44520
|
resources.claudeRuntimeIngest = claudeRuntimeIngest;
|
|
42968
44521
|
const claudeBroker = claudeBrokerBridgeEnabled() ? createClaudeAgentBrokerBridge({
|
|
@@ -43074,6 +44627,7 @@ function createContextUnchecked(opts, resources) {
|
|
|
43074
44627
|
claudeBin: opts.claudeBin,
|
|
43075
44628
|
backendFor,
|
|
43076
44629
|
codexAppServer,
|
|
44630
|
+
acpBridge,
|
|
43077
44631
|
claudeBroker,
|
|
43078
44632
|
// Auth preflight (claude-auth plan, Slice A): Claude reads its local credential and confirms only
|
|
43079
44633
|
// a positive signed-out against its CLI (readClaudePreflightAuth — the comment there records why
|
|
@@ -43113,6 +44667,21 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
43113
44667
|
}
|
|
43114
44668
|
return deliverCodexWake({ bridge, storage, cwd: project.dir, row, slug, deliveryMessage, deliveryId });
|
|
43115
44669
|
}
|
|
44670
|
+
if (row?.backend === "acp") {
|
|
44671
|
+
return acpBridge.followUp({
|
|
44672
|
+
threadSlug: slug,
|
|
44673
|
+
sessionId: row.session_id,
|
|
44674
|
+
cwd: project.dir,
|
|
44675
|
+
agentId: row.acp_agent ?? "",
|
|
44676
|
+
modelId: acpModelIdFromModel(row.model),
|
|
44677
|
+
acpSessionId: row.agent_session_id,
|
|
44678
|
+
text: deliveryMessage,
|
|
44679
|
+
deliveryId
|
|
44680
|
+
}).then((r) => {
|
|
44681
|
+
if (r.acpSessionId !== row.agent_session_id) storage.setAgentSession(slug, r.acpSessionId);
|
|
44682
|
+
if (row.exited === 1) storage.setExitedIfCurrent(slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
44683
|
+
});
|
|
44684
|
+
}
|
|
43116
44685
|
if (row?.backend === "claude" && row.claude_runtime === "broker") {
|
|
43117
44686
|
if (!claudeBroker) {
|
|
43118
44687
|
process.stderr.write(`[frizz] claude-broker wake for ${slug} dropped: the session broker is unavailable
|
|
@@ -43178,6 +44747,7 @@ ${wakeDeliveryToken(deliveryId)}`;
|
|
|
43178
44747
|
storage,
|
|
43179
44748
|
interactions: storage.interactions,
|
|
43180
44749
|
codexAppServer,
|
|
44750
|
+
acpBridge,
|
|
43181
44751
|
claudeBroker,
|
|
43182
44752
|
board,
|
|
43183
44753
|
tailer,
|
|
@@ -43219,6 +44789,8 @@ var init_context = __esm({
|
|
|
43219
44789
|
init_resume();
|
|
43220
44790
|
init_claude();
|
|
43221
44791
|
init_codex();
|
|
44792
|
+
init_acp_transcript();
|
|
44793
|
+
init_acp_bridge();
|
|
43222
44794
|
init_auth_status();
|
|
43223
44795
|
init_login_utility();
|
|
43224
44796
|
init_usage_limit();
|
|
@@ -45895,8 +47467,8 @@ var init_server = __esm({
|
|
|
45895
47467
|
});
|
|
45896
47468
|
|
|
45897
47469
|
// packages/server/src/repair.ts
|
|
45898
|
-
import { existsSync as
|
|
45899
|
-
import { basename as basename7, dirname as
|
|
47470
|
+
import { existsSync as existsSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync11 } from "node:fs";
|
|
47471
|
+
import { basename as basename7, dirname as dirname17, resolve as resolve8 } from "node:path";
|
|
45900
47472
|
function quoteValue(v) {
|
|
45901
47473
|
if (/^[\w./#:+-]+$/.test(v)) return v;
|
|
45902
47474
|
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
@@ -45908,10 +47480,10 @@ function deriveTitle(body, slug) {
|
|
|
45908
47480
|
function repairThreadFile(frizzDir, file) {
|
|
45909
47481
|
const root = resolve8(frizzDir);
|
|
45910
47482
|
const abs = resolve8(root, file);
|
|
45911
|
-
if (
|
|
47483
|
+
if (dirname17(abs) !== root) throw new RepairError(`refusing to repair "${file}": not directly under .frizz/`);
|
|
45912
47484
|
if (!abs.endsWith(".md")) throw new RepairError(`refusing to repair "${file}": not a .md thread file`);
|
|
45913
|
-
if (!
|
|
45914
|
-
const content =
|
|
47485
|
+
if (!existsSync13(abs)) throw new RepairError(`no thread file to repair: ${basename7(abs)}`);
|
|
47486
|
+
const content = readFileSync23(abs, "utf8");
|
|
45915
47487
|
const firstLine2 = content.replace(/^/, "").split(/\r?\n/).find((l) => l.trim() !== "");
|
|
45916
47488
|
if (firstLine2?.trim() === "---") {
|
|
45917
47489
|
throw new RepairError(`${basename7(abs)} already opens with a "---" block \u2014 repair only heals a MISSING frontmatter block`);
|
|
@@ -46104,7 +47676,7 @@ var init_repo_files = __esm({
|
|
|
46104
47676
|
});
|
|
46105
47677
|
|
|
46106
47678
|
// cc-worker/hooks/bash-background.mjs
|
|
46107
|
-
import { readFileSync as
|
|
47679
|
+
import { readFileSync as readFileSync24, realpathSync as realpathSync10 } from "node:fs";
|
|
46108
47680
|
import { basename as basename8 } from "node:path";
|
|
46109
47681
|
import { fileURLToPath as fileURLToPath4, pathToFileURL } from "node:url";
|
|
46110
47682
|
function emit(obj) {
|
|
@@ -46124,8 +47696,8 @@ function withoutHeredocBodies(command) {
|
|
|
46124
47696
|
return " ".repeat(line.length);
|
|
46125
47697
|
}
|
|
46126
47698
|
for (const match2 of line.matchAll(/<<-?\s*(?:'([^']+)'|"([^"]+)"|([A-Za-z_][A-Za-z0-9_-]*))/g)) {
|
|
46127
|
-
const
|
|
46128
|
-
if (
|
|
47699
|
+
const delimiter3 = match2[1] ?? match2[2] ?? match2[3];
|
|
47700
|
+
if (delimiter3) pending.push(delimiter3);
|
|
46129
47701
|
}
|
|
46130
47702
|
active = pending.shift();
|
|
46131
47703
|
return line;
|
|
@@ -46259,7 +47831,7 @@ var init_bash_background = __esm({
|
|
|
46259
47831
|
if (isDirectHookExecution(process.argv[1], import.meta.url)) {
|
|
46260
47832
|
try {
|
|
46261
47833
|
const env = process.argv.includes("--frizz-thread") ? { ...process.env, FRIZZ_THREAD: process.env.FRIZZ_THREAD || "codex-worker" } : process.env;
|
|
46262
|
-
emit(evaluateBashBackgroundHook(JSON.parse(
|
|
47834
|
+
emit(evaluateBashBackgroundHook(JSON.parse(readFileSync24(0, "utf8")), env));
|
|
46263
47835
|
} catch {
|
|
46264
47836
|
emit({});
|
|
46265
47837
|
}
|
|
@@ -46268,10 +47840,10 @@ var init_bash_background = __esm({
|
|
|
46268
47840
|
});
|
|
46269
47841
|
|
|
46270
47842
|
// packages/server/src/transcript.ts
|
|
46271
|
-
import { closeSync as closeSync13, existsSync as
|
|
47843
|
+
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
47844
|
import { createHash as createHash13 } from "node:crypto";
|
|
46273
|
-
import { StringDecoder as
|
|
46274
|
-
import { join as
|
|
47845
|
+
import { StringDecoder as StringDecoder5 } from "node:string_decoder";
|
|
47846
|
+
import { join as join38 } from "node:path";
|
|
46275
47847
|
import { homedir as homedir16 } from "node:os";
|
|
46276
47848
|
function formatTokens(n) {
|
|
46277
47849
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -46851,14 +48423,14 @@ function pruneScreenshotCache() {
|
|
|
46851
48423
|
if (entries2.length <= SCREENSHOT_CACHE_MAX) return;
|
|
46852
48424
|
const byMtime = entries2.map((n) => {
|
|
46853
48425
|
try {
|
|
46854
|
-
return { n, m:
|
|
48426
|
+
return { n, m: statSync17(join38(SCREENSHOT_CACHE_DIR, n)).mtimeMs };
|
|
46855
48427
|
} catch {
|
|
46856
48428
|
return { n, m: 0 };
|
|
46857
48429
|
}
|
|
46858
48430
|
}).sort((a, b) => b.m - a.m);
|
|
46859
48431
|
for (const { n } of byMtime.slice(SCREENSHOT_CACHE_MAX)) {
|
|
46860
48432
|
try {
|
|
46861
|
-
unlinkSync5(
|
|
48433
|
+
unlinkSync5(join38(SCREENSHOT_CACHE_DIR, n));
|
|
46862
48434
|
} catch {
|
|
46863
48435
|
}
|
|
46864
48436
|
}
|
|
@@ -46888,14 +48460,14 @@ function persistBase64Image(mediaType, data, idKey) {
|
|
|
46888
48460
|
const ext = IMAGE_MEDIA_EXT[typeof mediaType === "string" ? mediaType.toLowerCase() : ""];
|
|
46889
48461
|
if (!ext || !data) return void 0;
|
|
46890
48462
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46891
|
-
const path3 =
|
|
48463
|
+
const path3 = join38(SCREENSHOT_CACHE_DIR, `${name}.${ext}`);
|
|
46892
48464
|
try {
|
|
46893
|
-
if (
|
|
48465
|
+
if (existsSync14(path3)) return path3;
|
|
46894
48466
|
if (data.length > SCREENSHOT_MAX_BASE64) return void 0;
|
|
46895
48467
|
const buf = Buffer.from(data, "base64");
|
|
46896
48468
|
if (buf.length === 0 || !looksLikeImage(buf, ext)) return void 0;
|
|
46897
|
-
|
|
46898
|
-
const tmp =
|
|
48469
|
+
mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
48470
|
+
const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46899
48471
|
writeFileSync12(tmp, buf);
|
|
46900
48472
|
renameSync9(tmp, path3);
|
|
46901
48473
|
pruneScreenshotCache();
|
|
@@ -46910,14 +48482,14 @@ function persistSentFile(srcPath, idKey) {
|
|
|
46910
48482
|
const outExt = ext === "jpeg" ? "jpg" : ext;
|
|
46911
48483
|
try {
|
|
46912
48484
|
const name = createHash13("sha256").update(idKey).digest("hex").slice(0, 32);
|
|
46913
|
-
const dest =
|
|
46914
|
-
if (
|
|
46915
|
-
const size =
|
|
48485
|
+
const dest = join38(SCREENSHOT_CACHE_DIR, `${name}.${outExt}`);
|
|
48486
|
+
if (existsSync14(dest)) return dest;
|
|
48487
|
+
const size = statSync17(srcPath).size;
|
|
46916
48488
|
if (size === 0 || size > SENT_IMAGE_MAX_BYTES) return void 0;
|
|
46917
|
-
const buf =
|
|
48489
|
+
const buf = readFileSync25(srcPath);
|
|
46918
48490
|
if (!looksLikeImage(buf, outExt)) return void 0;
|
|
46919
|
-
|
|
46920
|
-
const tmp =
|
|
48491
|
+
mkdirSync16(SCREENSHOT_CACHE_DIR, { recursive: true });
|
|
48492
|
+
const tmp = join38(SCREENSHOT_CACHE_DIR, `.${name}.${process.pid}.${screenshotTmpSeq++}.tmp`);
|
|
46921
48493
|
writeFileSync12(tmp, buf);
|
|
46922
48494
|
renameSync9(tmp, dest);
|
|
46923
48495
|
pruneScreenshotCache();
|
|
@@ -46935,7 +48507,7 @@ function viewImageCall(path3, idKey) {
|
|
|
46935
48507
|
...image ? { outputImage: image } : {}
|
|
46936
48508
|
};
|
|
46937
48509
|
}
|
|
46938
|
-
function
|
|
48510
|
+
function toolResultText4(content) {
|
|
46939
48511
|
if (typeof content === "string") return content.trim() || null;
|
|
46940
48512
|
if (Array.isArray(content)) {
|
|
46941
48513
|
const joined = content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
@@ -46966,7 +48538,7 @@ function attachToolResults(rec, pending, backgroundShells, backgroundTaskIds, ch
|
|
|
46966
48538
|
const ackAgentId = typeof rec.toolUseResult?.agentId === "string" ? rec.toolUseResult.agentId.trim() : "";
|
|
46967
48539
|
const dispatchId = entry.calls.find((c) => typeof c.agentId === "string" && c.agentId)?.agentId;
|
|
46968
48540
|
if (ackAgentId && dispatchId) childDispatchIds.set(ackAgentId, dispatchId);
|
|
46969
|
-
const text2 =
|
|
48541
|
+
const text2 = toolResultText4(b.content);
|
|
46970
48542
|
if (text2 && entry.name === "TaskList") {
|
|
46971
48543
|
const todos = parseTodoListResult(text2);
|
|
46972
48544
|
if (todos) for (const call of entry.calls) call.todos = todos;
|
|
@@ -47319,7 +48891,7 @@ function retainedFoldEntry(path3, identityPrefix, fileId, size) {
|
|
|
47319
48891
|
}
|
|
47320
48892
|
const hit = entry !== void 0;
|
|
47321
48893
|
if (!entry) {
|
|
47322
|
-
entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new
|
|
48894
|
+
entry = { identityPrefix, fold: createTranscriptFold(identityPrefix), bytesRead: 0, decoder: new StringDecoder5("utf8"), fileId };
|
|
47323
48895
|
} else {
|
|
47324
48896
|
transcriptCache.delete(path3);
|
|
47325
48897
|
}
|
|
@@ -47355,7 +48927,7 @@ function readTranscript(project, sessionId) {
|
|
|
47355
48927
|
}
|
|
47356
48928
|
function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
47357
48929
|
try {
|
|
47358
|
-
const fresh = parseTranscript(
|
|
48930
|
+
const fresh = parseTranscript(readFileSync25(path3, "utf8"), identityPrefix);
|
|
47359
48931
|
const a = JSON.stringify(incremental);
|
|
47360
48932
|
const b = JSON.stringify(fresh);
|
|
47361
48933
|
if (a !== b) {
|
|
@@ -47375,16 +48947,16 @@ function verifyIncrementalParse(path3, identityPrefix, incremental) {
|
|
|
47375
48947
|
}
|
|
47376
48948
|
}
|
|
47377
48949
|
function logDirOf(project) {
|
|
47378
|
-
return
|
|
48950
|
+
return join38(homedir16(), ".claude", "projects", project.cwdSlug);
|
|
47379
48951
|
}
|
|
47380
48952
|
function resolveTranscriptPath(project, sessionId) {
|
|
47381
|
-
const path3 =
|
|
48953
|
+
const path3 = join38(logDirOf(project), `${sessionId}.jsonl`);
|
|
47382
48954
|
try {
|
|
47383
|
-
if (
|
|
48955
|
+
if (statSync17(path3).size > 0) return path3;
|
|
47384
48956
|
} catch {
|
|
47385
48957
|
}
|
|
47386
48958
|
const stranded = discoverTranscriptDir(logDirOf(project), sessionId);
|
|
47387
|
-
return stranded ?
|
|
48959
|
+
return stranded ? join38(stranded, `${sessionId}.jsonl`) : path3;
|
|
47388
48960
|
}
|
|
47389
48961
|
function projectCodexTranscript(raw2, identityPrefix = "codex") {
|
|
47390
48962
|
const out = [];
|
|
@@ -47678,6 +49250,17 @@ function parseCodexTranscript(raw2, identityPrefix = "codex") {
|
|
|
47678
49250
|
return latestTranscriptWindow(out);
|
|
47679
49251
|
}
|
|
47680
49252
|
function latestWindowStart(messages) {
|
|
49253
|
+
return byteBoundedStart(messages, countBoundedStart(messages));
|
|
49254
|
+
}
|
|
49255
|
+
function byteBoundedStart(messages, start) {
|
|
49256
|
+
let bytes = 0;
|
|
49257
|
+
for (let i = messages.length - 1; i >= start; i--) {
|
|
49258
|
+
bytes += messageBytes(messages[i]);
|
|
49259
|
+
if (bytes > LATEST_WINDOW_MAX_BYTES) return Math.min(i + 1, messages.length - 1);
|
|
49260
|
+
}
|
|
49261
|
+
return start;
|
|
49262
|
+
}
|
|
49263
|
+
function countBoundedStart(messages) {
|
|
47681
49264
|
const tail = Math.max(0, messages.length - MAX_MESSAGES);
|
|
47682
49265
|
if (tail === 0) return 0;
|
|
47683
49266
|
let boundary = -1;
|
|
@@ -47759,7 +49342,7 @@ function codexDirectToolCall(name, obj, callId) {
|
|
|
47759
49342
|
detail: strField(obj.task_name) ?? "sub-agent",
|
|
47760
49343
|
subagentType: codexAgentCell(obj),
|
|
47761
49344
|
agentId: callId,
|
|
47762
|
-
|
|
49345
|
+
prompt: typeof obj.message === "string" && obj.message.trim() && strField(obj.message) !== ENCRYPTED_PAYLOAD ? capAgentPrompt(obj.message) : void 0
|
|
47763
49346
|
};
|
|
47764
49347
|
case "send_message":
|
|
47765
49348
|
return codexPeerMessageCall("Send message", target, obj);
|
|
@@ -47871,14 +49454,6 @@ function codexAgentCell(obj) {
|
|
|
47871
49454
|
const effort = strField(obj.reasoning_effort);
|
|
47872
49455
|
return model && effort ? `${model}/${effort}` : model ?? effort;
|
|
47873
49456
|
}
|
|
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
49457
|
function formatCompactDuration(ms) {
|
|
47883
49458
|
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
47884
49459
|
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
@@ -48434,9 +50009,9 @@ function defaultCodexBackend() {
|
|
|
48434
50009
|
function sourceForThread(project, storage, slug, backendFor) {
|
|
48435
50010
|
const row = storage.getSession(slug);
|
|
48436
50011
|
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);
|
|
50012
|
+
const backend = row.backend === "codex" ? "codex" : row.backend === "acp" ? "acp" : "claude";
|
|
50013
|
+
const nativeId = backend === "codex" ? row.agent_session_id ?? row.session_id : backend === "acp" ? row.session_id : row.transcript_id ?? row.session_id;
|
|
50014
|
+
const path3 = backend === "codex" ? (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(nativeId) : backend === "acp" ? backendFor?.("acp")?.transcriptPath(nativeId) : resolveTranscriptPath(project, nativeId);
|
|
48440
50015
|
if (!path3) return void 0;
|
|
48441
50016
|
return {
|
|
48442
50017
|
slug,
|
|
@@ -48449,7 +50024,7 @@ function sourceForThread(project, storage, slug, backendFor) {
|
|
|
48449
50024
|
}
|
|
48450
50025
|
if (!FOREIGN_SESSION_ID_RE.test(slug)) return void 0;
|
|
48451
50026
|
const claudePath = resolveTranscriptPath(project, slug);
|
|
48452
|
-
if (
|
|
50027
|
+
if (existsSync14(claudePath)) {
|
|
48453
50028
|
return { slug, sessionId: slug, nativeId: slug, backend: "claude", runtimeGeneration: 0, path: claudePath };
|
|
48454
50029
|
}
|
|
48455
50030
|
const rollout = (backendFor?.("codex") ?? defaultCodexBackend()).transcriptPath(slug);
|
|
@@ -48543,6 +50118,7 @@ function fixedSnapshot(source) {
|
|
|
48543
50118
|
function projectSnapshot(snapshot) {
|
|
48544
50119
|
const prefix = `${snapshot.backend}:${snapshot.nativeId}`;
|
|
48545
50120
|
if (snapshot.backend === "codex") return retireStaleQueuedBubbles(projectCodexTranscript(snapshot.raw, prefix));
|
|
50121
|
+
if (snapshot.backend === "acp") return retireStaleQueuedBubbles(projectAcpTranscript(snapshot.raw, prefix));
|
|
48546
50122
|
const { entry } = retainedFoldEntry(snapshot.path, prefix, snapshot.fileKey, snapshot.size);
|
|
48547
50123
|
ingestBounded(entry, snapshot.size, (from, length) => snapshot.readRange(from, length));
|
|
48548
50124
|
return retireStaleQueuedBubbles([...entry.fold.allMessages()]);
|
|
@@ -48599,7 +50175,7 @@ function decodeTranscriptCursor(cursor) {
|
|
|
48599
50175
|
}
|
|
48600
50176
|
const p = value;
|
|
48601
50177
|
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");
|
|
50178
|
+
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
50179
|
return p;
|
|
48604
50180
|
}
|
|
48605
50181
|
function messageBytes(message) {
|
|
@@ -48645,7 +50221,7 @@ function emptyTranscriptPage(source) {
|
|
|
48645
50221
|
editedFiles: []
|
|
48646
50222
|
};
|
|
48647
50223
|
}
|
|
48648
|
-
function readLatestThreadTranscriptPage(project, storage, slug, backendFor) {
|
|
50224
|
+
function readLatestThreadTranscriptPage(project, storage, slug, backendFor, opts = {}) {
|
|
48649
50225
|
let source = sourceForThread(project, storage, slug, backendFor);
|
|
48650
50226
|
if (!source) return emptyTranscriptPage();
|
|
48651
50227
|
let snapshot = fixedSnapshot(source);
|
|
@@ -48681,7 +50257,7 @@ function readLatestThreadTranscriptPage(project, storage, slug, backendFor) {
|
|
|
48681
50257
|
// ones inside this checkout belong on the rail (edited-files.ts). Then git drops whatever the
|
|
48682
50258
|
// repository would not carry — scratch, build output — leaving the rail an account of repo work
|
|
48683
50259
|
// (repo-files.ts).
|
|
48684
|
-
editedFiles: repoCarriedEditedFiles(project.dir, editedFilesOf(projected, project.dir))
|
|
50260
|
+
...opts.editedFiles === false ? {} : { editedFiles: repoCarriedEditedFiles(project.dir, editedFilesOf(projected, project.dir)) }
|
|
48685
50261
|
};
|
|
48686
50262
|
}
|
|
48687
50263
|
function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backendFor) {
|
|
@@ -48715,41 +50291,11 @@ function readEarlierThreadTranscriptPage(project, storage, slug, cursor, backend
|
|
|
48715
50291
|
}
|
|
48716
50292
|
function readCodexTranscriptFile(absPath, nativeId = absPath) {
|
|
48717
50293
|
try {
|
|
48718
|
-
return parseCodexTranscript(
|
|
50294
|
+
return parseCodexTranscript(readFileSync25(absPath, "utf8"), `codex:${nativeId}`);
|
|
48719
50295
|
} catch {
|
|
48720
50296
|
return [];
|
|
48721
50297
|
}
|
|
48722
50298
|
}
|
|
48723
|
-
function readThreadTranscript(project, storage, slug, backendFor) {
|
|
48724
|
-
const row = storage.getSession(slug);
|
|
48725
|
-
if (row) {
|
|
48726
|
-
if (row.backend === "codex") {
|
|
48727
|
-
const backend = backendFor?.("codex") ?? defaultCodexBackend();
|
|
48728
|
-
const nativeId = row.agent_session_id ?? row.session_id;
|
|
48729
|
-
const path3 = backend.transcriptPath(nativeId);
|
|
48730
|
-
const codexLedger = parseDeliveryLedger(row.delivery_ledger);
|
|
48731
|
-
return projectDeliveryLedger(path3 ? readCodexTranscriptFile(path3, nativeId) : [], codexLedger);
|
|
48732
|
-
}
|
|
48733
|
-
const ledger = parseDeliveryLedger(row.delivery_ledger);
|
|
48734
|
-
const msgs = readTranscript(project, row.transcript_id ?? row.session_id);
|
|
48735
|
-
if (msgs.length || row.transcript_id) return projectDeliveryLedger(msgs, ledger);
|
|
48736
|
-
if (Date.now() - Date.parse(row.spawned_at) < DISCOVERY_GRACE_MS) return projectDeliveryLedger(msgs, ledger);
|
|
48737
|
-
const exclude = /* @__PURE__ */ new Set();
|
|
48738
|
-
for (const r of storage.allSessions()) {
|
|
48739
|
-
if (r.slug === row.slug) continue;
|
|
48740
|
-
exclude.add(r.session_id);
|
|
48741
|
-
if (r.transcript_id) exclude.add(r.transcript_id);
|
|
48742
|
-
}
|
|
48743
|
-
const found = discoverTranscriptId(logDirOf(project), row.session_id, { exclude });
|
|
48744
|
-
return projectDeliveryLedger(found ? readTranscript(project, found) : msgs, ledger);
|
|
48745
|
-
}
|
|
48746
|
-
if (FOREIGN_SESSION_ID_RE.test(slug)) {
|
|
48747
|
-
const source = sourceForThread(project, storage, slug, backendFor);
|
|
48748
|
-
if (!source) return [];
|
|
48749
|
-
return source.backend === "codex" ? readCodexTranscriptFile(source.path, slug) : readTranscript(project, slug);
|
|
48750
|
-
}
|
|
48751
|
-
return [];
|
|
48752
|
-
}
|
|
48753
50299
|
function projectTranscriptAgentLifecycles(messages, lookup) {
|
|
48754
50300
|
const cache2 = /* @__PURE__ */ new Map();
|
|
48755
50301
|
const projectTool = (tool) => {
|
|
@@ -48813,12 +50359,12 @@ function projectTranscriptPageAgentLifecycles(page, lookup, peerLookup) {
|
|
|
48813
50359
|
function readTranscriptFile(absPath) {
|
|
48814
50360
|
try {
|
|
48815
50361
|
const pathKey = createHash13("sha256").update(absPath).digest("base64url").slice(0, 16);
|
|
48816
|
-
return parseTranscript(
|
|
50362
|
+
return parseTranscript(readFileSync25(absPath, "utf8"), `claude-file:${pathKey}`);
|
|
48817
50363
|
} catch {
|
|
48818
50364
|
return [];
|
|
48819
50365
|
}
|
|
48820
50366
|
}
|
|
48821
|
-
var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, CLAUDE_COORDINATOR_INSTRUCTION_PREFIX, CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX, CLAUDE_PEER_INSTRUCTION_PREFIX, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
|
|
50367
|
+
var MAX_MESSAGES, LATEST_WINDOW_ASK_REACH_ITEMS, LATEST_WINDOW_ASK_REACH_BYTES, LATEST_WINDOW_MAX_BYTES, REST_LABEL, LEGACY_TASK_MARKER, LEGACY_BANNER_PREAMBLE, COMMAND_NAME_RE, COMMAND_ARGS_RE, PEER_SESSION_PREAMBLE, CLAUDE_COORDINATOR_INSTRUCTION_PREFIX, CLAUDE_COORDINATOR_INSTRUCTION_SUFFIX, CLAUDE_PEER_INSTRUCTION_PREFIX, QUEUED_STALE_MS, EDIT_CAP, TRUNC_MARKER, ENCRYPTED_PAYLOAD, AGENT_PROMPT_CAP, SEND_BODY_CAP, COMMAND_CAP, TOOL_INPUT_CAP, READ_LINE_CAP, READ_BYTE_CAP, IMAGE_MEDIA_EXT, SCREENSHOT_CACHE_DIR, SCREENSHOT_CACHE_MAX, SCREENSHOT_MAX_BASE64, screenshotTmpSeq, SENT_IMAGE_MAX_BYTES, SENT_FILES_MAX, TODO_ROWS_MAX, TODO_TEXT_CAP, TRANSCRIPT_CACHE_CAP, transcriptCache, PARSE_VERIFY_SAMPLE, TRANSCRIPT_DECODE_WINDOW, FOREIGN_SESSION_ID_RE, MAX_PINNED_BACKGROUND_OPERATIONS, CAPTION_MAX, _defaultCodexBackend, TRANSCRIPT_EARLIER_MAX_ITEMS, TRANSCRIPT_EARLIER_MAX_BYTES, fullDigestCache, FULL_DIGEST_CACHE_CAP, EMPTY_RETIRED_OPS;
|
|
48822
50368
|
var init_transcript = __esm({
|
|
48823
50369
|
"packages/server/src/transcript.ts"() {
|
|
48824
50370
|
"use strict";
|
|
@@ -48829,6 +50375,7 @@ var init_transcript = __esm({
|
|
|
48829
50375
|
init_delivery_marker();
|
|
48830
50376
|
init_completion_relay();
|
|
48831
50377
|
init_codex();
|
|
50378
|
+
init_acp_transcript();
|
|
48832
50379
|
init_discover();
|
|
48833
50380
|
init_tailer();
|
|
48834
50381
|
init_credential_redaction();
|
|
@@ -48838,6 +50385,7 @@ var init_transcript = __esm({
|
|
|
48838
50385
|
MAX_MESSAGES = 300;
|
|
48839
50386
|
LATEST_WINDOW_ASK_REACH_ITEMS = 100;
|
|
48840
50387
|
LATEST_WINDOW_ASK_REACH_BYTES = 512 * 1024;
|
|
50388
|
+
LATEST_WINDOW_MAX_BYTES = 3 * 1024 * 1024;
|
|
48841
50389
|
REST_LABEL = "Agent rested";
|
|
48842
50390
|
LEGACY_TASK_MARKER = "\nTASK:\n";
|
|
48843
50391
|
LEGACY_BANNER_PREAMBLE = "Everything ABOVE this line is frizz system orientation. Everything BELOW the `TASK:` marker is the human operator's own prompt, verbatim.";
|
|
@@ -48889,7 +50437,7 @@ var init_transcript = __esm({
|
|
|
48889
50437
|
});
|
|
48890
50438
|
|
|
48891
50439
|
// packages/server/src/open-external.ts
|
|
48892
|
-
import { spawn as
|
|
50440
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
48893
50441
|
function validateExternalUrl(raw2) {
|
|
48894
50442
|
let parsed;
|
|
48895
50443
|
try {
|
|
@@ -48911,7 +50459,7 @@ async function openExternalUrl(raw2, options = {}) {
|
|
|
48911
50459
|
const v = validateExternalUrl(raw2);
|
|
48912
50460
|
if (!v.ok) throw new Error(v.reason);
|
|
48913
50461
|
const spec = externalUrlOpenCommand(v.url, options.platform);
|
|
48914
|
-
const child = (options.spawn ??
|
|
50462
|
+
const child = (options.spawn ?? spawn7)(spec.command, [...spec.args], { detached: true, stdio: "ignore", windowsHide: true });
|
|
48915
50463
|
try {
|
|
48916
50464
|
await awaitOpenerStart(child, spec.command);
|
|
48917
50465
|
return { opened: true };
|
|
@@ -49210,7 +50758,7 @@ var init_github_hovercard = __esm({
|
|
|
49210
50758
|
});
|
|
49211
50759
|
|
|
49212
50760
|
// packages/server/src/backend/account-actions.ts
|
|
49213
|
-
import { execFile as
|
|
50761
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
49214
50762
|
import { promisify as promisify8 } from "node:util";
|
|
49215
50763
|
function liveThreadsForBackend(threads, backend) {
|
|
49216
50764
|
let count = 0;
|
|
@@ -49223,6 +50771,7 @@ function liveThreadsForBackend(threads, backend) {
|
|
|
49223
50771
|
return count;
|
|
49224
50772
|
}
|
|
49225
50773
|
async function runProviderLogout(opts) {
|
|
50774
|
+
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
50775
|
if (opts.liveThreads > 0) {
|
|
49227
50776
|
return { status: "blocked", activeThreads: opts.liveThreads, auth: (await readAuthSnapshot({ claudeBin: opts.claudeBin }))[opts.backend] };
|
|
49228
50777
|
}
|
|
@@ -49245,7 +50794,7 @@ var init_account_actions = __esm({
|
|
|
49245
50794
|
"packages/server/src/backend/account-actions.ts"() {
|
|
49246
50795
|
"use strict";
|
|
49247
50796
|
init_auth_status();
|
|
49248
|
-
execFileAsync6 = promisify8(
|
|
50797
|
+
execFileAsync6 = promisify8(execFile10);
|
|
49249
50798
|
LIVE_RUNTIMES = /* @__PURE__ */ new Set(["spawning", "running", "perm-prompt"]);
|
|
49250
50799
|
}
|
|
49251
50800
|
});
|
|
@@ -49378,12 +50927,12 @@ var init_background_shell_output = __esm({
|
|
|
49378
50927
|
});
|
|
49379
50928
|
|
|
49380
50929
|
// packages/server/src/image-header.ts
|
|
49381
|
-
import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as
|
|
50930
|
+
import { closeSync as closeSync15, openSync as openSync16, readSync as readSync9, statSync as statSync18 } from "node:fs";
|
|
49382
50931
|
import { extname } from "node:path";
|
|
49383
50932
|
function readHead3(path3, budget) {
|
|
49384
50933
|
let fd;
|
|
49385
50934
|
try {
|
|
49386
|
-
const size =
|
|
50935
|
+
const size = statSync18(path3).size;
|
|
49387
50936
|
if (size === 0) return void 0;
|
|
49388
50937
|
const buffer = Buffer.alloc(Math.min(size, budget));
|
|
49389
50938
|
fd = openSync16(path3, "r");
|
|
@@ -49520,7 +51069,7 @@ var init_image_header = __esm({
|
|
|
49520
51069
|
});
|
|
49521
51070
|
|
|
49522
51071
|
// packages/server/src/directory-picker.ts
|
|
49523
|
-
import { execFile as
|
|
51072
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
49524
51073
|
import { promisify as promisify9 } from "node:util";
|
|
49525
51074
|
async function pickImageFile(startIn, prompt = "Choose an image for this project", platform3 = process.platform) {
|
|
49526
51075
|
if (platform3 === "darwin") {
|
|
@@ -49602,20 +51151,22 @@ var run2, PICKER_TIMEOUT_MS, OSASCRIPT;
|
|
|
49602
51151
|
var init_directory_picker = __esm({
|
|
49603
51152
|
"packages/server/src/directory-picker.ts"() {
|
|
49604
51153
|
"use strict";
|
|
49605
|
-
run2 = promisify9(
|
|
51154
|
+
run2 = promisify9(execFile11);
|
|
49606
51155
|
PICKER_TIMEOUT_MS = 5 * 6e4;
|
|
49607
51156
|
OSASCRIPT = (prompt) => `POSIX path of (choose folder with prompt ${JSON.stringify(prompt)})`;
|
|
49608
51157
|
}
|
|
49609
51158
|
});
|
|
49610
51159
|
|
|
49611
51160
|
// packages/server/src/router.ts
|
|
49612
|
-
import { readFileSync as
|
|
49613
|
-
import { join as
|
|
51161
|
+
import { readFileSync as readFileSync26, statSync as statSync19 } from "node:fs";
|
|
51162
|
+
import { join as join39, resolve as resolve9 } from "node:path";
|
|
49614
51163
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
49615
|
-
import { basename as basename9, dirname as
|
|
49616
|
-
import { existsSync as
|
|
51164
|
+
import { basename as basename9, dirname as dirname18 } from "node:path";
|
|
51165
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync13 } from "node:fs";
|
|
49617
51166
|
import { homedir as homedir17 } from "node:os";
|
|
49618
51167
|
function validateGithubDispatchProfile(input) {
|
|
51168
|
+
if (input.backend === "acp") return;
|
|
51169
|
+
if (input.effort === void 0) throw new Error(`Unsupported ${input.backend} model/effort pair: ${input.model} / (no effort)`);
|
|
49619
51170
|
validateThreadProfile(input.backend, input.model, input.effort);
|
|
49620
51171
|
}
|
|
49621
51172
|
function githubDispatcherRequest(input, item) {
|
|
@@ -49638,7 +51189,11 @@ function isAppServerCodexRow(row) {
|
|
|
49638
51189
|
function appServerCodexTurnLive(codex, row) {
|
|
49639
51190
|
return codex?.turnLiveness(row.slug, row.session_id)?.bridgeTurn === true;
|
|
49640
51191
|
}
|
|
49641
|
-
async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51192
|
+
async function stopThreadRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
51193
|
+
if (row.backend === "acp") {
|
|
51194
|
+
if (!acp?.turnLiveness(row.slug, row.session_id)?.turnActive) return "absent";
|
|
51195
|
+
return (await acp.interruptTurn(row.slug, row.session_id)).interrupted ? "stopped" : "absent";
|
|
51196
|
+
}
|
|
49642
51197
|
if (isAppServerCodexRow(row)) {
|
|
49643
51198
|
if (!appServerCodexTurnLive(codex, row)) return "absent";
|
|
49644
51199
|
if (!codex) throw new Error("The Codex app-server is unavailable; nothing was stopped");
|
|
@@ -49679,9 +51234,9 @@ function stopRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator)
|
|
|
49679
51234
|
}
|
|
49680
51235
|
return "stopped";
|
|
49681
51236
|
}
|
|
49682
|
-
async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51237
|
+
async function stopRuntimeBySlug(storage, slug, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
49683
51238
|
const row = storage.getSession(slug);
|
|
49684
|
-
if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker), row };
|
|
51239
|
+
if (row) return { outcome: await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp), row };
|
|
49685
51240
|
if (storage.getAdoptionClaim(slug)) throw new Error("An adoption attempt is in progress; nothing was stopped");
|
|
49686
51241
|
throw new Error("No registered runtime identity is available; nothing was stopped");
|
|
49687
51242
|
}
|
|
@@ -49713,18 +51268,18 @@ function cutOffHold(telemetry) {
|
|
|
49713
51268
|
if (telemetry?.turn !== "in-flight") return void 0;
|
|
49714
51269
|
return { turnInFlight: true, cutOff: true, unobservable: false, subAgents: [], subAgentCount: 0, bgShells: [], bgShellCount: 0 };
|
|
49715
51270
|
}
|
|
49716
|
-
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker) {
|
|
51271
|
+
async function completeRegisteredThread(storage, row, terminateLive, runtime = cachedLivenessTerminator, telemetry, codex, claudeBroker, acp) {
|
|
49717
51272
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
49718
51273
|
if (binding.kind === "conflict") {
|
|
49719
51274
|
throw new Error("This thread has a competing adoption attempt; nothing was changed");
|
|
49720
51275
|
}
|
|
49721
51276
|
const appServerCodex = isAppServerCodexRow(row);
|
|
49722
51277
|
const brokerClaude = isBrokerClaudeRow(row);
|
|
49723
|
-
const live = brokerClaude ? claudeBroker?.isDaemonAlive(row.session_id) ?? false : appServerCodex ? appServerCodexTurnLive(codex, row) : false;
|
|
51278
|
+
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
51279
|
const hold = terminateLive ? void 0 : live ? completionConfirmationHold(telemetry) : cutOffHold(telemetry);
|
|
49725
51280
|
if (hold) return { needsConfirmation: true, hold };
|
|
49726
51281
|
if (live) {
|
|
49727
|
-
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
51282
|
+
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
|
|
49728
51283
|
if (!appServerCodex && !brokerClaude && binding.kind === "unbound" && runtime.isLive(row.slug)) {
|
|
49729
51284
|
throw new Error("The session could not be confirmed stopped; it was not marked done");
|
|
49730
51285
|
}
|
|
@@ -49735,7 +51290,7 @@ async function completeRegisteredThread(storage, row, terminateLive, runtime = c
|
|
|
49735
51290
|
}
|
|
49736
51291
|
return { needsConfirmation: false };
|
|
49737
51292
|
}
|
|
49738
|
-
async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker) {
|
|
51293
|
+
async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLivenessTerminator, codex, claudeBroker, acp) {
|
|
49739
51294
|
const binding = adoptionRuntimeBinding(storage, row);
|
|
49740
51295
|
if (binding.kind === "conflict") {
|
|
49741
51296
|
throw new Error("This thread changed while it was being dismissed; nothing was removed");
|
|
@@ -49745,7 +51300,7 @@ async function stopAndForgetRegisteredRuntime(storage, row, runtime = cachedLive
|
|
|
49745
51300
|
runtimeGeneration: row.runtime_generation ?? 0,
|
|
49746
51301
|
adoptionAttemptToken: binding.kind === "bound" ? binding.claim.attempt_token : null
|
|
49747
51302
|
};
|
|
49748
|
-
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker);
|
|
51303
|
+
await stopThreadRuntime(storage, row, runtime, codex, claudeBroker, acp);
|
|
49749
51304
|
const forgotten = storage.forgetSessionIfCurrent(row.slug, expected);
|
|
49750
51305
|
if (!forgotten) {
|
|
49751
51306
|
throw new Error("This thread resumed or was replaced while it was being dismissed; the new worker was preserved");
|
|
@@ -49777,11 +51332,11 @@ function projectCard(entry, stale) {
|
|
|
49777
51332
|
function addProjectAtPath(input, home = homedir17()) {
|
|
49778
51333
|
const typed = input.trim();
|
|
49779
51334
|
if (!typed) throw new Error("Enter a folder path.");
|
|
49780
|
-
const expanded = typed === "~" || typed.startsWith("~/") ?
|
|
51335
|
+
const expanded = typed === "~" || typed.startsWith("~/") ? join39(home, typed.slice(1)) : typed;
|
|
49781
51336
|
const absolute = resolve9(expanded);
|
|
49782
51337
|
let stats;
|
|
49783
51338
|
try {
|
|
49784
|
-
stats =
|
|
51339
|
+
stats = statSync19(absolute);
|
|
49785
51340
|
} catch {
|
|
49786
51341
|
throw new Error(`No folder at ${absolute}`);
|
|
49787
51342
|
}
|
|
@@ -49806,7 +51361,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
49806
51361
|
if (!entry) throw new Error("No such project.");
|
|
49807
51362
|
if (bytes.length === 0) throw new Error("That file is empty.");
|
|
49808
51363
|
const path3 = customIconPath(id, `.${extension2}`);
|
|
49809
|
-
|
|
51364
|
+
mkdirSync17(dirname18(path3), { recursive: true });
|
|
49810
51365
|
writeFileSync13(path3, bytes);
|
|
49811
51366
|
if (!imageDimensions(path3)) {
|
|
49812
51367
|
rmSync10(path3, { force: true });
|
|
@@ -49823,7 +51378,7 @@ function storeProjectIcon(id, name, bytes) {
|
|
|
49823
51378
|
function setProjectIconFromFile(id, file) {
|
|
49824
51379
|
let bytes;
|
|
49825
51380
|
try {
|
|
49826
|
-
bytes =
|
|
51381
|
+
bytes = readFileSync26(file);
|
|
49827
51382
|
} catch {
|
|
49828
51383
|
throw new Error("That file could not be read.");
|
|
49829
51384
|
}
|
|
@@ -49856,7 +51411,7 @@ function mergeSubAgentSteers(messages, steers) {
|
|
|
49856
51411
|
return merged;
|
|
49857
51412
|
}
|
|
49858
51413
|
function createRouter(ctx) {
|
|
49859
|
-
const frizzDir =
|
|
51414
|
+
const frizzDir = join39(ctx.project.dir, ".frizz");
|
|
49860
51415
|
const openRoots = openableFileRoots(ctx.project);
|
|
49861
51416
|
function isAutoTitledSession(slug) {
|
|
49862
51417
|
return ctx.storage.getSession(slug)?.title_auto === 1;
|
|
@@ -50483,6 +52038,28 @@ function createRouter(ctx) {
|
|
|
50483
52038
|
${gapNote}` : input.message;
|
|
50484
52039
|
if (row) reopenArchivedThreadForFollowUp(ctx, row);
|
|
50485
52040
|
if (row) wakeParkedThreadForFollowUp(ctx, row);
|
|
52041
|
+
if (row?.backend === "acp") {
|
|
52042
|
+
const bridge = ctx.acpBridge;
|
|
52043
|
+
if (!bridge) throw new Error("The ACP bridge is unavailable; cannot deliver this follow-up");
|
|
52044
|
+
const result = await bridge.followUp({
|
|
52045
|
+
threadSlug: input.slug,
|
|
52046
|
+
sessionId: row.session_id,
|
|
52047
|
+
cwd: ctx.project.dir,
|
|
52048
|
+
agentId: row.acp_agent ?? "",
|
|
52049
|
+
modelId: acpModelIdFromModel(row.model),
|
|
52050
|
+
acpSessionId: row.agent_session_id,
|
|
52051
|
+
text: messageForWorker,
|
|
52052
|
+
...input.deliveryId ? { deliveryId: input.deliveryId } : {}
|
|
52053
|
+
});
|
|
52054
|
+
if (result.acpSessionId !== row.agent_session_id) ctx.storage.setAgentSession(input.slug, result.acpSessionId);
|
|
52055
|
+
if (row.exited === 1) ctx.storage.setExitedIfCurrent(input.slug, row.session_id, row.runtime_generation ?? 0, false);
|
|
52056
|
+
if (input.deliveryId) {
|
|
52057
|
+
appendDelivery(ctx.storage, input.slug, { id: input.deliveryId, text: input.message, state: result.state === "queued" ? "enqueued" : "delivered" });
|
|
52058
|
+
ctx.transcriptChange.emit([input.slug]);
|
|
52059
|
+
}
|
|
52060
|
+
ctx.board.refresh();
|
|
52061
|
+
return;
|
|
52062
|
+
}
|
|
50486
52063
|
if (row?.backend === "codex") {
|
|
50487
52064
|
const bridge = ctx.codexAppServer;
|
|
50488
52065
|
if (!bridge) throw new Error("Codex app-server is unavailable; cannot deliver this follow-up");
|
|
@@ -50668,6 +52245,7 @@ ${gapNote}` : input.message;
|
|
|
50668
52245
|
const thread = (await ctx.board.snapshot()).threads.find((t) => t.id === input.slug);
|
|
50669
52246
|
if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
|
|
50670
52247
|
const permRow = ctx.storage.getSession(input.slug);
|
|
52248
|
+
if (permRow?.backend === "acp") throw new Error("An ACP agent enforces its own permissions; Frizz has no mode to set on it");
|
|
50671
52249
|
if (permRow?.backend === "codex") {
|
|
50672
52250
|
ctx.storage.setPermissionMode(input.slug, input.permissionMode);
|
|
50673
52251
|
ctx.board.refresh();
|
|
@@ -50713,7 +52291,9 @@ ${gapNote}` : input.message;
|
|
|
50713
52291
|
const row = ctx.storage.getSession(input.slug);
|
|
50714
52292
|
if (!row) throw new Error(`thread ${input.slug} has no session to ask for skills`);
|
|
50715
52293
|
let skills;
|
|
50716
|
-
if (row.backend === "
|
|
52294
|
+
if (row.backend === "acp") {
|
|
52295
|
+
skills = [];
|
|
52296
|
+
} else if (row.backend === "codex") {
|
|
50717
52297
|
if (!isAppServerCodexRow(row) || !ctx.codexAppServer) {
|
|
50718
52298
|
throw new Error("This Codex thread has no app-server session to ask for skills");
|
|
50719
52299
|
}
|
|
@@ -50734,6 +52314,16 @@ ${gapNote}` : input.message;
|
|
|
50734
52314
|
const thread = (await ctx.board.snapshot()).threads.find((candidate) => candidate.id === input.slug);
|
|
50735
52315
|
if (!thread || thread.foreign || thread.kind !== "session") throw new Error(`thread ${input.slug} is not editable`);
|
|
50736
52316
|
const profRow = ctx.storage.getSession(input.slug);
|
|
52317
|
+
if (profRow?.backend === "acp") {
|
|
52318
|
+
if (acpAgentIdFromModel(input.model) !== acpAgentIdFromModel(profRow.model ?? "")) {
|
|
52319
|
+
throw new Error("An ACP thread's agent cannot change; pick a model of the same agent");
|
|
52320
|
+
}
|
|
52321
|
+
ctx.storage.setProfile(input.slug, input.model, "");
|
|
52322
|
+
const live = ctx.acpBridge ? await ctx.acpBridge.setModel(input.slug, profRow.session_id, acpModelIdFromModel(input.model)) : { applied: false };
|
|
52323
|
+
ctx.board.refresh();
|
|
52324
|
+
return { effect: live.applied ? "applied" : "next-resume" };
|
|
52325
|
+
}
|
|
52326
|
+
if (!input.effort) throw new Error(`effort is required for a ${profRow?.backend ?? "claude"} thread`);
|
|
50737
52327
|
if (profRow?.backend === "codex") {
|
|
50738
52328
|
ctx.storage.setProfile(input.slug, input.model, input.effort);
|
|
50739
52329
|
ctx.board.refresh();
|
|
@@ -50806,7 +52396,8 @@ ${gapNote}` : input.message;
|
|
|
50806
52396
|
cachedLivenessTerminator,
|
|
50807
52397
|
ctx.tailer.get(input.slug),
|
|
50808
52398
|
ctx.codexAppServer,
|
|
50809
|
-
ctx.claudeBroker
|
|
52399
|
+
ctx.claudeBroker,
|
|
52400
|
+
ctx.acpBridge
|
|
50810
52401
|
);
|
|
50811
52402
|
if (!result.needsConfirmation) ctx.board.refresh();
|
|
50812
52403
|
return result;
|
|
@@ -51400,7 +52991,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51400
52991
|
if (t && t.runtime !== "exited") {
|
|
51401
52992
|
throw new Error("only a stalled or exited session can be dismissed \u2014 archive a live one instead");
|
|
51402
52993
|
}
|
|
51403
|
-
await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
52994
|
+
await stopAndForgetRegisteredRuntime(ctx.storage, row, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51404
52995
|
ctx.tailer.forget(input.slug);
|
|
51405
52996
|
ctx.board.refresh();
|
|
51406
52997
|
}
|
|
@@ -51518,7 +53109,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51518
53109
|
handler: async ({ input }) => {
|
|
51519
53110
|
assertLegacyMutationAllowed(input.slug);
|
|
51520
53111
|
if (input.status === "dismissed") {
|
|
51521
|
-
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
53112
|
+
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51522
53113
|
if (stopped.row && !ctx.storage.setExitedIfCurrent(
|
|
51523
53114
|
stopped.row.slug,
|
|
51524
53115
|
stopped.row.session_id,
|
|
@@ -51622,7 +53213,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51622
53213
|
killAgent: mutation({
|
|
51623
53214
|
input: SlugInput,
|
|
51624
53215
|
handler: async ({ input }) => {
|
|
51625
|
-
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker);
|
|
53216
|
+
const stopped = await stopRuntimeBySlug(ctx.storage, input.slug, cachedLivenessTerminator, ctx.codexAppServer, ctx.claudeBroker, ctx.acpBridge);
|
|
51626
53217
|
if (stopped.row && !ctx.storage.setExitedIfCurrent(
|
|
51627
53218
|
stopped.row.slug,
|
|
51628
53219
|
stopped.row.session_id,
|
|
@@ -51641,6 +53232,20 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51641
53232
|
output: external_exports.array(CodexModel),
|
|
51642
53233
|
handler: async () => readCodexModels()
|
|
51643
53234
|
}),
|
|
53235
|
+
// The ACP agents Frizz knows how to launch, with `available` for the ones on this machine's PATH.
|
|
53236
|
+
// The composer lists the available ones as `acp:<id>` models (plans/acp-backend.md, decision 9).
|
|
53237
|
+
acpAgents: query({
|
|
53238
|
+
output: external_exports.array(AcpAgent),
|
|
53239
|
+
handler: async () => listAcpAgents(ctx.getSettings().acpAgents).map((a) => ({ id: a.id, label: a.label, command: a.command, available: a.bin !== void 0 }))
|
|
53240
|
+
}),
|
|
53241
|
+
// The models one ACP agent advertises — read by opening a throwaway session in the project dir
|
|
53242
|
+
// (cached in the bridge), so it is a separate query the composer asks only once a model picker
|
|
53243
|
+
// needs it, never on every board load.
|
|
53244
|
+
acpAgentModels: query({
|
|
53245
|
+
input: AcpAgentModelsInput,
|
|
53246
|
+
output: AcpAgentModels,
|
|
53247
|
+
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() }
|
|
53248
|
+
}),
|
|
51644
53249
|
// Provider subscription quota (5h + weekly rate-limit windows) for the sidebar status bar. Codex
|
|
51645
53250
|
// reads live from the app-server's `account/rateLimits/read`, falling back to the rollout JSONL
|
|
51646
53251
|
// frizz already tails; Claude delegates to Claude Code's own non-interactive `/usage` command.
|
|
@@ -51690,7 +53295,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51690
53295
|
const { state, backend } = ctx.loginUtility.status(input.attemptId);
|
|
51691
53296
|
const auth = await readAuthSnapshot({ claudeBin: ctx.claudeBin });
|
|
51692
53297
|
if (state === "exited") ctx.loginUtility.cancel(input.attemptId);
|
|
51693
|
-
return { state, auth: auth[backend ?? "claude"] };
|
|
53298
|
+
return { state, auth: backend === "acp" ? "unknown" : auth[backend ?? "claude"] };
|
|
51694
53299
|
}
|
|
51695
53300
|
}),
|
|
51696
53301
|
accountLoginCancel: mutation({
|
|
@@ -51859,7 +53464,7 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51859
53464
|
}
|
|
51860
53465
|
const updated = clearProjectIcon(input.id);
|
|
51861
53466
|
if (!updated) throw new Error("No such project.");
|
|
51862
|
-
return projectCard(updated, !
|
|
53467
|
+
return projectCard(updated, !existsSync15(updated.path));
|
|
51863
53468
|
}
|
|
51864
53469
|
}),
|
|
51865
53470
|
/**
|
|
@@ -51928,6 +53533,16 @@ Say which way you went and why in your write-up, so the human can course-correct
|
|
|
51928
53533
|
output: Settings,
|
|
51929
53534
|
handler: async ({ input }) => ctx.setSettings(input)
|
|
51930
53535
|
}),
|
|
53536
|
+
// Context controls live in the new-thread picker. Patch only the chosen field against the
|
|
53537
|
+
// latest settings, synchronously, rather than replacing unrelated settings from a stale drawer.
|
|
53538
|
+
contextWindowSet: mutation({
|
|
53539
|
+
input: external_exports.object({ backend: external_exports.enum(["claude", "codex"]), tokens: external_exports.number().int().positive().nullable() }),
|
|
53540
|
+
output: Settings,
|
|
53541
|
+
handler: async ({ input }) => ctx.setSettings({
|
|
53542
|
+
...ctx.getSettings(),
|
|
53543
|
+
[input.backend === "claude" ? "autoCompactWindow" : "codexContextWindow"]: input.tokens ?? void 0
|
|
53544
|
+
})
|
|
53545
|
+
}),
|
|
51931
53546
|
// Clear the stored settings blob so defaults (incl. the shipped default preamble) apply again.
|
|
51932
53547
|
settingsReset: mutation({
|
|
51933
53548
|
input: external_exports.object({}),
|
|
@@ -52032,6 +53647,7 @@ var init_router5 = __esm({
|
|
|
52032
53647
|
init_server();
|
|
52033
53648
|
init_src();
|
|
52034
53649
|
init_context();
|
|
53650
|
+
init_acp_agents();
|
|
52035
53651
|
init_storage();
|
|
52036
53652
|
init_usage_limit();
|
|
52037
53653
|
init_board();
|
|
@@ -52219,7 +53835,7 @@ var init_local_origin = __esm({
|
|
|
52219
53835
|
});
|
|
52220
53836
|
|
|
52221
53837
|
// packages/server/src/compression.ts
|
|
52222
|
-
import { brotliCompressSync, gzipSync, constants as
|
|
53838
|
+
import { brotliCompressSync, gzipSync, constants as constants3 } from "node:zlib";
|
|
52223
53839
|
function negotiateEncoding(acceptEncoding) {
|
|
52224
53840
|
if (!acceptEncoding) return null;
|
|
52225
53841
|
const offers = /* @__PURE__ */ new Map();
|
|
@@ -52242,9 +53858,9 @@ function negotiateEncoding(acceptEncoding) {
|
|
|
52242
53858
|
function compress(body, encoding) {
|
|
52243
53859
|
const out = encoding === "br" ? brotliCompressSync(body, {
|
|
52244
53860
|
params: {
|
|
52245
|
-
[
|
|
53861
|
+
[constants3.BROTLI_PARAM_QUALITY]: 4,
|
|
52246
53862
|
// Telling brotli the input size lets it size its window instead of guessing.
|
|
52247
|
-
[
|
|
53863
|
+
[constants3.BROTLI_PARAM_SIZE_HINT]: body.byteLength
|
|
52248
53864
|
}
|
|
52249
53865
|
}) : gzipSync(body, { level: 6 });
|
|
52250
53866
|
return Uint8Array.from(out);
|
|
@@ -52265,10 +53881,11 @@ var init_compression = __esm({
|
|
|
52265
53881
|
});
|
|
52266
53882
|
|
|
52267
53883
|
// packages/server/src/local-image.ts
|
|
52268
|
-
import { readFileSync as
|
|
52269
|
-
import { extname as extname2, isAbsolute as
|
|
53884
|
+
import { readFileSync as readFileSync27, realpathSync as realpathSync11, statSync as statSync20 } from "node:fs";
|
|
53885
|
+
import { extname as extname2, isAbsolute as isAbsolute7 } from "node:path";
|
|
52270
53886
|
function resolveLocalImage(rawPath) {
|
|
52271
|
-
if (
|
|
53887
|
+
if (rawPath) rawPath = normalizeLocalPath(rawPath);
|
|
53888
|
+
if (!rawPath || !isAbsolute7(rawPath)) return { status: 400 };
|
|
52272
53889
|
const contentType = IMAGE_CONTENT_TYPE[extname2(rawPath).toLowerCase()];
|
|
52273
53890
|
if (!contentType) return { status: 400 };
|
|
52274
53891
|
let real;
|
|
@@ -52278,8 +53895,8 @@ function resolveLocalImage(rawPath) {
|
|
|
52278
53895
|
return { status: 404 };
|
|
52279
53896
|
}
|
|
52280
53897
|
try {
|
|
52281
|
-
if (!
|
|
52282
|
-
return { status: 200, contentType, body:
|
|
53898
|
+
if (!statSync20(real).isFile()) return { status: 404 };
|
|
53899
|
+
return { status: 200, contentType, body: readFileSync27(real) };
|
|
52283
53900
|
} catch {
|
|
52284
53901
|
return { status: 404 };
|
|
52285
53902
|
}
|
|
@@ -52288,6 +53905,7 @@ var IMAGE_CONTENT_TYPE;
|
|
|
52288
53905
|
var init_local_image = __esm({
|
|
52289
53906
|
"packages/server/src/local-image.ts"() {
|
|
52290
53907
|
"use strict";
|
|
53908
|
+
init_local_path();
|
|
52291
53909
|
IMAGE_CONTENT_TYPE = {
|
|
52292
53910
|
".png": "image/png",
|
|
52293
53911
|
".jpg": "image/jpeg",
|
|
@@ -52299,9 +53917,9 @@ var init_local_image = __esm({
|
|
|
52299
53917
|
});
|
|
52300
53918
|
|
|
52301
53919
|
// packages/server/src/project-icon.ts
|
|
52302
|
-
import { readFileSync as
|
|
53920
|
+
import { readFileSync as readFileSync28, readdirSync as readdirSync10, realpathSync as realpathSync12, statSync as statSync21 } from "node:fs";
|
|
52303
53921
|
import { homedir as homedir18 } from "node:os";
|
|
52304
|
-
import { basename as basename10, dirname as
|
|
53922
|
+
import { basename as basename10, dirname as dirname19, extname as extname3, join as join40, relative as relative2, sep as sep3 } from "node:path";
|
|
52305
53923
|
function normalizeStem(stem) {
|
|
52306
53924
|
const sized = stem.toLowerCase().replace(/[@-]\d+(\.\d+)?x(\d+)?$/u, "").replace(/[-_]\d{2,4}$/u, "");
|
|
52307
53925
|
const normalized = sized.replace(VARIANT_SUFFIX, "");
|
|
@@ -52319,7 +53937,7 @@ function sizeScore(dimensions) {
|
|
|
52319
53937
|
return Math.max(0, Math.min(40, Math.round(Math.log2(edge / 24) * 8)));
|
|
52320
53938
|
}
|
|
52321
53939
|
function under(root, relativeDirectory) {
|
|
52322
|
-
return relativeDirectory ?
|
|
53940
|
+
return relativeDirectory ? join40(root, relativeDirectory) : root;
|
|
52323
53941
|
}
|
|
52324
53942
|
function listFiles(directory) {
|
|
52325
53943
|
try {
|
|
@@ -52341,19 +53959,19 @@ function iconDirectories(root) {
|
|
|
52341
53959
|
for (const asset of ASSET_DIRECTORIES) directories.push(under(root, asset));
|
|
52342
53960
|
for (const host of HOST_DIRECTORIES) {
|
|
52343
53961
|
if (!children2.has(host)) continue;
|
|
52344
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
53962
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, host), asset));
|
|
52345
53963
|
}
|
|
52346
53964
|
for (const extra of EXTRA_DIRECTORIES) directories.push(under(root, extra));
|
|
52347
53965
|
for (const container of WORKSPACE_CONTAINERS) {
|
|
52348
53966
|
if (!children2.has(container)) continue;
|
|
52349
53967
|
let members;
|
|
52350
53968
|
try {
|
|
52351
|
-
members = readdirSync10(
|
|
53969
|
+
members = readdirSync10(join40(root, container), { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
52352
53970
|
} catch {
|
|
52353
53971
|
continue;
|
|
52354
53972
|
}
|
|
52355
53973
|
for (const member of members) {
|
|
52356
|
-
for (const asset of ASSET_DIRECTORIES) directories.push(under(
|
|
53974
|
+
for (const asset of ASSET_DIRECTORIES) directories.push(under(join40(root, container, member), asset));
|
|
52357
53975
|
}
|
|
52358
53976
|
}
|
|
52359
53977
|
return [...new Set(directories)];
|
|
@@ -52361,13 +53979,13 @@ function iconDirectories(root) {
|
|
|
52361
53979
|
function manifestIcons(manifestPath) {
|
|
52362
53980
|
let parsed;
|
|
52363
53981
|
try {
|
|
52364
|
-
parsed = JSON.parse(
|
|
53982
|
+
parsed = JSON.parse(readFileSync28(manifestPath, "utf8"));
|
|
52365
53983
|
} catch {
|
|
52366
53984
|
return [];
|
|
52367
53985
|
}
|
|
52368
53986
|
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) =>
|
|
53987
|
+
const directory = dirname19(manifestPath);
|
|
53988
|
+
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
53989
|
}
|
|
52372
53990
|
function projectIconCandidates(root) {
|
|
52373
53991
|
const projectStem = normalizeStem(basename10(root)).normalized;
|
|
@@ -52386,10 +54004,10 @@ function projectIconCandidates(root) {
|
|
|
52386
54004
|
for (const name of listFiles(directory)) {
|
|
52387
54005
|
const extension2 = extname3(name).toLowerCase();
|
|
52388
54006
|
if (MANIFEST_NAMES.includes(name.toLowerCase())) {
|
|
52389
|
-
for (const icon of manifestIcons(
|
|
54007
|
+
for (const icon of manifestIcons(join40(directory, name))) {
|
|
52390
54008
|
if (MEASURABLE_IMAGE_EXTENSIONS.has(extname3(icon).toLowerCase())) {
|
|
52391
54009
|
fromManifest.add(icon);
|
|
52392
|
-
files.push({ path: icon, directory:
|
|
54010
|
+
files.push({ path: icon, directory: dirname19(icon) });
|
|
52393
54011
|
}
|
|
52394
54012
|
}
|
|
52395
54013
|
continue;
|
|
@@ -52397,7 +54015,7 @@ function projectIconCandidates(root) {
|
|
|
52397
54015
|
if (!MEASURABLE_IMAGE_EXTENSIONS.has(extension2)) continue;
|
|
52398
54016
|
const stem = name.slice(0, name.length - extension2.length);
|
|
52399
54017
|
if (normalizeStem(stem).normalized === "favicon") iconHomes.add(directory);
|
|
52400
|
-
files.push({ path:
|
|
54018
|
+
files.push({ path: join40(directory, name), directory });
|
|
52401
54019
|
if (files.length >= MAX_CANDIDATES) break;
|
|
52402
54020
|
}
|
|
52403
54021
|
}
|
|
@@ -52449,7 +54067,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
52449
54067
|
let real;
|
|
52450
54068
|
try {
|
|
52451
54069
|
real = realpathSync12(path3);
|
|
52452
|
-
if (!
|
|
54070
|
+
if (!statSync21(real).isFile()) return { status: 404 };
|
|
52453
54071
|
} catch {
|
|
52454
54072
|
return { status: 404 };
|
|
52455
54073
|
}
|
|
@@ -52462,7 +54080,7 @@ function resolveProjectIconResponse(id, options = {}) {
|
|
|
52462
54080
|
});
|
|
52463
54081
|
if (!roots.some((root) => real === root || real.startsWith(root + sep3))) return { status: 404 };
|
|
52464
54082
|
try {
|
|
52465
|
-
return { status: 200, contentType, body:
|
|
54083
|
+
return { status: 200, contentType, body: readFileSync28(real) };
|
|
52466
54084
|
} catch {
|
|
52467
54085
|
return { status: 404 };
|
|
52468
54086
|
}
|
|
@@ -52574,8 +54192,8 @@ var init_project_icon = __esm({
|
|
|
52574
54192
|
});
|
|
52575
54193
|
|
|
52576
54194
|
// packages/server/src/local-visualization.ts
|
|
52577
|
-
import { readdirSync as readdirSync11, readFileSync as
|
|
52578
|
-
import { join as
|
|
54195
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync29, realpathSync as realpathSync13, statSync as statSync22 } from "node:fs";
|
|
54196
|
+
import { join as join41, sep as sep4 } from "node:path";
|
|
52579
54197
|
function children(path3, pattern) {
|
|
52580
54198
|
try {
|
|
52581
54199
|
return readdirSync11(path3, { withFileTypes: true }).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort().reverse();
|
|
@@ -52587,7 +54205,7 @@ function isUnder2(path3, root) {
|
|
|
52587
54205
|
return path3 === root || path3.startsWith(root.endsWith(sep4) ? root : root + sep4);
|
|
52588
54206
|
}
|
|
52589
54207
|
function resolveFragment(projectDir, sessionId, file) {
|
|
52590
|
-
const base =
|
|
54208
|
+
const base = join41(projectDir, ".codex", "visualizations");
|
|
52591
54209
|
let projectReal;
|
|
52592
54210
|
let baseReal;
|
|
52593
54211
|
try {
|
|
@@ -52598,15 +54216,15 @@ function resolveFragment(projectDir, sessionId, file) {
|
|
|
52598
54216
|
}
|
|
52599
54217
|
if (!isUnder2(baseReal, projectReal)) return null;
|
|
52600
54218
|
for (const year of children(base, YEAR_PART)) {
|
|
52601
|
-
const yearDir =
|
|
54219
|
+
const yearDir = join41(base, year);
|
|
52602
54220
|
for (const month of children(yearDir, DATE_PART)) {
|
|
52603
|
-
const monthDir =
|
|
54221
|
+
const monthDir = join41(yearDir, month);
|
|
52604
54222
|
for (const day of children(monthDir, DATE_PART)) {
|
|
52605
|
-
const sessionRoot =
|
|
52606
|
-
const candidate =
|
|
54223
|
+
const sessionRoot = join41(baseReal, year, month, day, sessionId);
|
|
54224
|
+
const candidate = join41(monthDir, day, sessionId, file);
|
|
52607
54225
|
try {
|
|
52608
54226
|
const real = realpathSync13(candidate);
|
|
52609
|
-
if (isUnder2(real, sessionRoot) &&
|
|
54227
|
+
if (isUnder2(real, sessionRoot) && statSync22(real).isFile()) return real;
|
|
52610
54228
|
} catch {
|
|
52611
54229
|
}
|
|
52612
54230
|
}
|
|
@@ -52687,9 +54305,9 @@ function resolveLocalVisualization(projectDir, sessionId, file) {
|
|
|
52687
54305
|
if (!path3) return { status: 404 };
|
|
52688
54306
|
let fragment;
|
|
52689
54307
|
try {
|
|
52690
|
-
const size =
|
|
54308
|
+
const size = statSync22(path3).size;
|
|
52691
54309
|
if (size > MAX_FRAGMENT_BYTES) return { status: 413 };
|
|
52692
|
-
fragment =
|
|
54310
|
+
fragment = readFileSync29(path3, "utf8");
|
|
52693
54311
|
} catch {
|
|
52694
54312
|
return { status: 404 };
|
|
52695
54313
|
}
|
|
@@ -52731,8 +54349,8 @@ var init_local_visualization = __esm({
|
|
|
52731
54349
|
});
|
|
52732
54350
|
|
|
52733
54351
|
// packages/server/src/app.ts
|
|
52734
|
-
import { mkdirSync as
|
|
52735
|
-
import { join as
|
|
54352
|
+
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync14 } from "node:fs";
|
|
54353
|
+
import { join as join42 } from "node:path";
|
|
52736
54354
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
52737
54355
|
function createApp(ctx, options = {}) {
|
|
52738
54356
|
const app = new Hono2();
|
|
@@ -52836,10 +54454,10 @@ function createApp(ctx, options = {}) {
|
|
|
52836
54454
|
if (typeof body.data !== "string" || body.data.length > ATTACHMENT_MAX_BASE64_CHARS) return c.json({ error: "bad payload" }, 400);
|
|
52837
54455
|
const ext = `.${attachmentExtension(name)}`;
|
|
52838
54456
|
const buf = Buffer.from(body.data, "base64");
|
|
52839
|
-
const dir =
|
|
52840
|
-
|
|
54457
|
+
const dir = join42(ctx.project.stateDir, "attachments");
|
|
54458
|
+
mkdirSync18(dir, { recursive: true });
|
|
52841
54459
|
const base = name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").slice(0, 40) || "file";
|
|
52842
|
-
const path3 =
|
|
54460
|
+
const path3 = join42(dir, `${Date.now()}-${randomUUID17().slice(0, 8)}-${base}${ext}`);
|
|
52843
54461
|
writeFileSync14(path3, buf);
|
|
52844
54462
|
return c.json({ path: path3 });
|
|
52845
54463
|
});
|
|
@@ -53305,10 +54923,19 @@ function isWsPath(url) {
|
|
|
53305
54923
|
}
|
|
53306
54924
|
function makeTranscriptReader(project, storage, backendFor, lifecycleFor, peerNameFor, ownerGone) {
|
|
53307
54925
|
return (slug) => {
|
|
53308
|
-
const
|
|
53309
|
-
const named = peerNameFor ? projectTranscriptPeerNames(messages, (taskId) => peerNameFor(slug, taskId)) : messages;
|
|
54926
|
+
const page = readLatestThreadTranscriptPage(project, storage, slug, backendFor, { editedFiles: false });
|
|
54927
|
+
const named = peerNameFor ? projectTranscriptPeerNames(page.messages, (taskId) => peerNameFor(slug, taskId)) : page.messages;
|
|
53310
54928
|
const projected = lifecycleFor ? projectTranscriptAgentLifecycles(named, (id) => lifecycleFor(slug, id)) : named;
|
|
53311
|
-
|
|
54929
|
+
const messages = projectRetiredBackgroundOps(projected, retiredOpsFor(storage, slug), ownerGone?.(slug) ?? false);
|
|
54930
|
+
return {
|
|
54931
|
+
messages,
|
|
54932
|
+
page: {
|
|
54933
|
+
beforeCursor: page.beforeCursor,
|
|
54934
|
+
hasEarlier: page.hasEarlier,
|
|
54935
|
+
reachedTurnBoundary: page.reachedTurnBoundary,
|
|
54936
|
+
transcriptKey: page.transcriptKey
|
|
54937
|
+
}
|
|
54938
|
+
};
|
|
53312
54939
|
};
|
|
53313
54940
|
}
|
|
53314
54941
|
function createAppSocketServer(deps) {
|
|
@@ -53589,9 +55216,9 @@ function createAppSocketServer(deps) {
|
|
|
53589
55216
|
if (cached2) return { kind: "ready", snapshot: cached2 };
|
|
53590
55217
|
const reservation = reserveTranscriptRead(origin);
|
|
53591
55218
|
if (reservation.kind === "limited") return reservation;
|
|
53592
|
-
const
|
|
53593
|
-
if (!
|
|
53594
|
-
const frame = encodeMsg({ t: "transcript", slug, messages });
|
|
55219
|
+
const read = readTranscript2(slug);
|
|
55220
|
+
if (!read) return { kind: "unavailable" };
|
|
55221
|
+
const frame = encodeMsg(read.page ? { t: "transcript", slug, messages: read.messages, page: read.page } : { t: "transcript", slug, messages: read.messages });
|
|
53595
55222
|
if (!frame) return { kind: "unavailable" };
|
|
53596
55223
|
if (frame.bytes > maxLogicalFrameBytes) return { kind: "oversized", actualBytes: frame.bytes };
|
|
53597
55224
|
const snapshot = { frame, sig: frameSignature(frame), weight: snapshotWeight(frame) };
|
|
@@ -54151,11 +55778,11 @@ var init_app_socket = __esm({
|
|
|
54151
55778
|
});
|
|
54152
55779
|
|
|
54153
55780
|
// packages/server/src/local-file-watch.ts
|
|
54154
|
-
import { statSync as
|
|
54155
|
-
import { basename as basename11, dirname as
|
|
55781
|
+
import { statSync as statSync23, watch } from "node:fs";
|
|
55782
|
+
import { basename as basename11, dirname as dirname20 } from "node:path";
|
|
54156
55783
|
function stampOf(path3) {
|
|
54157
55784
|
try {
|
|
54158
|
-
const s =
|
|
55785
|
+
const s = statSync23(path3, { bigint: true });
|
|
54159
55786
|
return `${s.ino}:${s.size}:${s.mtimeNs}`;
|
|
54160
55787
|
} catch {
|
|
54161
55788
|
return "gone";
|
|
@@ -54175,7 +55802,7 @@ function watchLocalFile(path3, onChange) {
|
|
|
54175
55802
|
let entry = entries.get(path3);
|
|
54176
55803
|
if (!entry) {
|
|
54177
55804
|
const name = basename11(path3);
|
|
54178
|
-
const watcher2 = watch(
|
|
55805
|
+
const watcher2 = watch(dirname20(path3), { persistent: false }, (_event, changed) => {
|
|
54179
55806
|
if (changed !== null && changed !== void 0 && changed.toString() !== name) return;
|
|
54180
55807
|
const current = entries.get(path3);
|
|
54181
55808
|
if (!current) return;
|
|
@@ -54211,10 +55838,10 @@ var init_local_file_watch = __esm({
|
|
|
54211
55838
|
});
|
|
54212
55839
|
|
|
54213
55840
|
// packages/server/src/boot-progress.ts
|
|
54214
|
-
import { mkdirSync as
|
|
54215
|
-
import { basename as basename12, dirname as
|
|
55841
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync30, renameSync as renameSync10, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
|
|
55842
|
+
import { basename as basename12, dirname as dirname21, join as join43 } from "node:path";
|
|
54216
55843
|
function bootProgressPath(stateDir) {
|
|
54217
|
-
return
|
|
55844
|
+
return join43(stateDir, BOOT_PROGRESS_NAME);
|
|
54218
55845
|
}
|
|
54219
55846
|
function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
54220
55847
|
if (!stateDir) {
|
|
@@ -54234,8 +55861,8 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
54234
55861
|
lastAt = now;
|
|
54235
55862
|
step++;
|
|
54236
55863
|
try {
|
|
54237
|
-
|
|
54238
|
-
const temp =
|
|
55864
|
+
mkdirSync19(dirname21(path3), { recursive: true, mode: 448 });
|
|
55865
|
+
const temp = join43(dirname21(path3), `.${basename12(path3)}.${process.pid}.tmp`);
|
|
54239
55866
|
writeFileSync15(temp, `${JSON.stringify({ pid: process.pid, step, phase, at: new Date(now).toISOString() })}
|
|
54240
55867
|
`, "utf8");
|
|
54241
55868
|
renameSync10(temp, path3);
|
|
@@ -54251,22 +55878,23 @@ function createBootProgressPublisher(stateDir, minIntervalMs = 200) {
|
|
|
54251
55878
|
}
|
|
54252
55879
|
});
|
|
54253
55880
|
}
|
|
54254
|
-
var BOOT_PROGRESS_NAME;
|
|
55881
|
+
var BOOT_PROGRESS_NAME, BOOT_HARD_TIMEOUT_MS;
|
|
54255
55882
|
var init_boot_progress = __esm({
|
|
54256
55883
|
"packages/server/src/boot-progress.ts"() {
|
|
54257
55884
|
"use strict";
|
|
54258
55885
|
BOOT_PROGRESS_NAME = "boot.progress";
|
|
55886
|
+
BOOT_HARD_TIMEOUT_MS = 10 * 6e4;
|
|
54259
55887
|
}
|
|
54260
55888
|
});
|
|
54261
55889
|
|
|
54262
55890
|
// packages/server/src/runtime-lease.ts
|
|
54263
|
-
import { existsSync as
|
|
54264
|
-
import { dirname as
|
|
55891
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync20, readdirSync as readdirSync12, readFileSync as readFileSync31, unlinkSync as unlinkSync6, writeFileSync as writeFileSync16 } from "node:fs";
|
|
55892
|
+
import { dirname as dirname22, join as join44 } from "node:path";
|
|
54265
55893
|
function runtimeVersionDir(bin) {
|
|
54266
|
-
let dir =
|
|
55894
|
+
let dir = dirname22(bin);
|
|
54267
55895
|
for (let step = 0; step < MAX_ASCENT; step++) {
|
|
54268
|
-
if (
|
|
54269
|
-
const parent =
|
|
55896
|
+
if (existsSync16(join44(dir, PROVISIONED_MARKER))) return dir;
|
|
55897
|
+
const parent = dirname22(dir);
|
|
54270
55898
|
if (parent === dir) return void 0;
|
|
54271
55899
|
dir = parent;
|
|
54272
55900
|
}
|
|
@@ -54284,9 +55912,9 @@ function leaseRuntime(bin, role) {
|
|
|
54284
55912
|
const versionDir = runtimeVersionDir(bin);
|
|
54285
55913
|
if (!versionDir) return () => {
|
|
54286
55914
|
};
|
|
54287
|
-
const path3 =
|
|
55915
|
+
const path3 = join44(versionDir, LEASES_DIR, `${process.pid}.json`);
|
|
54288
55916
|
try {
|
|
54289
|
-
|
|
55917
|
+
mkdirSync20(dirname22(path3), { recursive: true });
|
|
54290
55918
|
const lease = { pid: process.pid, role, bin, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
54291
55919
|
writeFileSync16(path3, JSON.stringify(lease));
|
|
54292
55920
|
} catch {
|
|
@@ -54301,7 +55929,7 @@ function leaseRuntime(bin, role) {
|
|
|
54301
55929
|
};
|
|
54302
55930
|
}
|
|
54303
55931
|
function liveRuntimeLeases(versionDir) {
|
|
54304
|
-
const dir =
|
|
55932
|
+
const dir = join44(versionDir, LEASES_DIR);
|
|
54305
55933
|
let names;
|
|
54306
55934
|
try {
|
|
54307
55935
|
names = readdirSync12(dir);
|
|
@@ -54311,10 +55939,10 @@ function liveRuntimeLeases(versionDir) {
|
|
|
54311
55939
|
const live = [];
|
|
54312
55940
|
for (const name of names) {
|
|
54313
55941
|
if (!name.endsWith(".json")) continue;
|
|
54314
|
-
const path3 =
|
|
55942
|
+
const path3 = join44(dir, name);
|
|
54315
55943
|
let lease;
|
|
54316
55944
|
try {
|
|
54317
|
-
lease = JSON.parse(
|
|
55945
|
+
lease = JSON.parse(readFileSync31(path3, "utf8"));
|
|
54318
55946
|
} catch {
|
|
54319
55947
|
continue;
|
|
54320
55948
|
}
|
|
@@ -54345,25 +55973,25 @@ import { createHash as createHash15 } from "node:crypto";
|
|
|
54345
55973
|
import {
|
|
54346
55974
|
chmodSync as chmodSync2,
|
|
54347
55975
|
closeSync as closeSync16,
|
|
54348
|
-
existsSync as
|
|
54349
|
-
mkdirSync as
|
|
55976
|
+
existsSync as existsSync17,
|
|
55977
|
+
mkdirSync as mkdirSync21,
|
|
54350
55978
|
openSync as openSync17,
|
|
54351
|
-
readFileSync as
|
|
55979
|
+
readFileSync as readFileSync32,
|
|
54352
55980
|
readdirSync as readdirSync13,
|
|
54353
55981
|
renameSync as renameSync11,
|
|
54354
55982
|
rmSync as rmSync12,
|
|
54355
|
-
statSync as
|
|
55983
|
+
statSync as statSync24,
|
|
54356
55984
|
writeFileSync as writeFileSync17,
|
|
54357
55985
|
writeSync as writeSync2
|
|
54358
55986
|
} from "node:fs";
|
|
54359
|
-
import { dirname as
|
|
55987
|
+
import { dirname as dirname23, isAbsolute as isAbsolute8, join as join45, normalize, relative as relative3, sep as sep5 } from "node:path";
|
|
54360
55988
|
import { Readable, Transform, Writable as Writable3 } from "node:stream";
|
|
54361
55989
|
import { pipeline } from "node:stream/promises";
|
|
54362
55990
|
import { createGunzip } from "node:zlib";
|
|
54363
55991
|
function runtimesRoot(env = process.env) {
|
|
54364
55992
|
const override = env[RUNTIMES_DIR_ENV];
|
|
54365
|
-
if (override &&
|
|
54366
|
-
return
|
|
55993
|
+
if (override && isAbsolute8(override)) return override;
|
|
55994
|
+
return join45(frizzRoots().cache, "runtimes");
|
|
54367
55995
|
}
|
|
54368
55996
|
function isMusl() {
|
|
54369
55997
|
if (process.platform !== "linux") return false;
|
|
@@ -54380,16 +56008,16 @@ function runtimeCoordinates(backend, platform3 = process.platform, arch = proces
|
|
|
54380
56008
|
return { pkg: "@openai/codex", packageVersion: `${CODEX_VERSION}-${platform3}-${arch}`, label: CODEX_VERSION, binary: `codex${exe}` };
|
|
54381
56009
|
}
|
|
54382
56010
|
function provisionedBinary(backend, root, label) {
|
|
54383
|
-
const dir =
|
|
56011
|
+
const dir = join45(root, backend, label);
|
|
54384
56012
|
let marker;
|
|
54385
56013
|
try {
|
|
54386
|
-
marker = JSON.parse(
|
|
56014
|
+
marker = JSON.parse(readFileSync32(join45(dir, MARKER), "utf8"));
|
|
54387
56015
|
} catch {
|
|
54388
56016
|
return void 0;
|
|
54389
56017
|
}
|
|
54390
56018
|
if (typeof marker.binary !== "string" || marker.label !== label) return void 0;
|
|
54391
|
-
const bin =
|
|
54392
|
-
return
|
|
56019
|
+
const bin = join45(dir, marker.binary);
|
|
56020
|
+
return existsSync17(bin) ? bin : void 0;
|
|
54393
56021
|
}
|
|
54394
56022
|
function octal(buffer, offset, length) {
|
|
54395
56023
|
const text2 = buffer.toString("latin1", offset, offset + length).replace(/\0.*$/su, "").trim();
|
|
@@ -54402,18 +56030,18 @@ function safeEntryPath(dest, name) {
|
|
|
54402
56030
|
const parts = name.split("/").filter((part) => part.length > 0);
|
|
54403
56031
|
if (parts.length < 2) return void 0;
|
|
54404
56032
|
const rel = normalize(parts.slice(1).join(sep5));
|
|
54405
|
-
if (
|
|
54406
|
-
const full =
|
|
56033
|
+
if (isAbsolute8(rel) || rel === ".." || rel.startsWith(`..${sep5}`)) return void 0;
|
|
56034
|
+
const full = join45(dest, rel);
|
|
54407
56035
|
const back = relative3(dest, full);
|
|
54408
|
-
if (back.startsWith("..") ||
|
|
56036
|
+
if (back.startsWith("..") || isAbsolute8(back)) return void 0;
|
|
54409
56037
|
return full;
|
|
54410
56038
|
}
|
|
54411
56039
|
function locateBinary(dir, backend, binary) {
|
|
54412
56040
|
if (backend === "claude") {
|
|
54413
|
-
const direct =
|
|
54414
|
-
return
|
|
56041
|
+
const direct = join45(dir, binary);
|
|
56042
|
+
return existsSync17(direct) ? direct : void 0;
|
|
54415
56043
|
}
|
|
54416
|
-
const vendor =
|
|
56044
|
+
const vendor = join45(dir, "vendor");
|
|
54417
56045
|
let triples;
|
|
54418
56046
|
try {
|
|
54419
56047
|
triples = readdirSync13(vendor);
|
|
@@ -54421,8 +56049,8 @@ function locateBinary(dir, backend, binary) {
|
|
|
54421
56049
|
return void 0;
|
|
54422
56050
|
}
|
|
54423
56051
|
for (const triple of triples) {
|
|
54424
|
-
const candidate =
|
|
54425
|
-
if (
|
|
56052
|
+
const candidate = join45(vendor, triple, "bin", binary);
|
|
56053
|
+
if (existsSync17(candidate)) return candidate;
|
|
54426
56054
|
}
|
|
54427
56055
|
return void 0;
|
|
54428
56056
|
}
|
|
@@ -54437,12 +56065,12 @@ async function provisionRuntime(backend, options) {
|
|
|
54437
56065
|
const registry = options.registry ?? REGISTRY;
|
|
54438
56066
|
const progress = options.onProgress ?? (() => {
|
|
54439
56067
|
});
|
|
54440
|
-
const backendDir =
|
|
54441
|
-
const finalDir =
|
|
54442
|
-
const partial =
|
|
54443
|
-
|
|
56068
|
+
const backendDir = join45(root, backend);
|
|
56069
|
+
const finalDir = join45(backendDir, coordinates.label);
|
|
56070
|
+
const partial = join45(backendDir, `.partial-${coordinates.label}-${process.pid}`);
|
|
56071
|
+
mkdirSync21(backendDir, { recursive: true });
|
|
54444
56072
|
rmSync12(partial, { recursive: true, force: true });
|
|
54445
|
-
|
|
56073
|
+
mkdirSync21(partial);
|
|
54446
56074
|
try {
|
|
54447
56075
|
const manifestUrl = `${registry}/${coordinates.pkg.replace("/", "%2F")}/${coordinates.packageVersion}`;
|
|
54448
56076
|
progress(`resolving ${coordinates.pkg}@${coordinates.packageVersion}`);
|
|
@@ -54486,7 +56114,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54486
56114
|
binary: relative3(partial, bin),
|
|
54487
56115
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
54488
56116
|
};
|
|
54489
|
-
writeFileSync17(
|
|
56117
|
+
writeFileSync17(join45(partial, MARKER), `${JSON.stringify(marker, null, 2)}
|
|
54490
56118
|
`, "utf8");
|
|
54491
56119
|
const rename2 = options.rename ?? renameSync11;
|
|
54492
56120
|
try {
|
|
@@ -54495,7 +56123,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54495
56123
|
rename2(partial, finalDir);
|
|
54496
56124
|
break;
|
|
54497
56125
|
} catch (err) {
|
|
54498
|
-
if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") ||
|
|
56126
|
+
if (attempt >= RENAME_ATTEMPTS || !TRANSIENT_HANDLE_CODES.has(errorCode4(err) ?? "") || existsSync17(finalDir)) throw err;
|
|
54499
56127
|
await new Promise((resolve12) => setTimeout(resolve12, options.retryDelayMs ?? RENAME_RETRY_DELAY_MS));
|
|
54500
56128
|
}
|
|
54501
56129
|
}
|
|
@@ -54506,7 +56134,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54506
56134
|
return { bin: theirs, label: coordinates.label, fetched: false };
|
|
54507
56135
|
}
|
|
54508
56136
|
progress("ready");
|
|
54509
|
-
return { bin:
|
|
56137
|
+
return { bin: join45(finalDir, marker.binary), label: coordinates.label, fetched: true };
|
|
54510
56138
|
} catch (err) {
|
|
54511
56139
|
rmSync12(partial, { recursive: true, force: true });
|
|
54512
56140
|
throw err;
|
|
@@ -54514,7 +56142,7 @@ async function provisionRuntime(backend, options) {
|
|
|
54514
56142
|
}
|
|
54515
56143
|
function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
|
|
54516
56144
|
const rm2 = deps.rm ?? defaultSweepRm;
|
|
54517
|
-
const backendDir =
|
|
56145
|
+
const backendDir = join45(root, backend);
|
|
54518
56146
|
let entries2;
|
|
54519
56147
|
try {
|
|
54520
56148
|
entries2 = readdirSync13(backendDir);
|
|
@@ -54524,10 +56152,10 @@ function sweepRuntimes(backend, root, keepLabel, now = Date.now(), deps = {}) {
|
|
|
54524
56152
|
const sweep = { removed: [], kept: [] };
|
|
54525
56153
|
for (const entry of entries2) {
|
|
54526
56154
|
if (entry === keepLabel) continue;
|
|
54527
|
-
const full =
|
|
56155
|
+
const full = join45(backendDir, entry);
|
|
54528
56156
|
if (entry.startsWith(".partial-")) {
|
|
54529
56157
|
try {
|
|
54530
|
-
if (now -
|
|
56158
|
+
if (now - statSync24(full).mtimeMs < 24 * 60 * 60 * 1e3) continue;
|
|
54531
56159
|
} catch {
|
|
54532
56160
|
continue;
|
|
54533
56161
|
}
|
|
@@ -54617,8 +56245,8 @@ var init_runtimes = __esm({
|
|
|
54617
56245
|
init_codex_app_server();
|
|
54618
56246
|
init_frizz_paths();
|
|
54619
56247
|
init_runtime_lease();
|
|
54620
|
-
CLAUDE_AGENT_SDK_VERSION = "0.3.
|
|
54621
|
-
CLAUDE_CODE_VERSION = "2.1.
|
|
56248
|
+
CLAUDE_AGENT_SDK_VERSION = "0.3.275";
|
|
56249
|
+
CLAUDE_CODE_VERSION = "2.1.275";
|
|
54622
56250
|
CODEX_VERSION = CODEX_APP_SERVER_SUPPORTED_VERSION;
|
|
54623
56251
|
RUNTIMES_MODE_ENV = "FRIZZ_RUNTIMES";
|
|
54624
56252
|
RUNTIMES_DIR_ENV = "FRIZZ_RUNTIMES_DIR";
|
|
@@ -54707,7 +56335,7 @@ var init_runtimes = __esm({
|
|
|
54707
56335
|
return;
|
|
54708
56336
|
case "5": {
|
|
54709
56337
|
const dir = safeEntryPath(this.dest, name);
|
|
54710
|
-
if (dir)
|
|
56338
|
+
if (dir) mkdirSync21(dir, { recursive: true });
|
|
54711
56339
|
return;
|
|
54712
56340
|
}
|
|
54713
56341
|
case "0":
|
|
@@ -54715,7 +56343,7 @@ var init_runtimes = __esm({
|
|
|
54715
56343
|
case "7": {
|
|
54716
56344
|
const file = safeEntryPath(this.dest, name);
|
|
54717
56345
|
if (!file) return;
|
|
54718
|
-
|
|
56346
|
+
mkdirSync21(dirname23(file), { recursive: true });
|
|
54719
56347
|
this.fd = openSync17(file, "w", octal(header, 100, 8) & 511 || 420);
|
|
54720
56348
|
this.written.push(file);
|
|
54721
56349
|
return;
|
|
@@ -55292,18 +56920,18 @@ var init_legacy_project_db = __esm({
|
|
|
55292
56920
|
});
|
|
55293
56921
|
|
|
55294
56922
|
// packages/server/src/frizz-db.ts
|
|
55295
|
-
import { existsSync as
|
|
55296
|
-
import { basename as basename13, dirname as
|
|
56923
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync22, readdirSync as readdirSync14, renameSync as renameSync12, statSync as statSync25 } from "node:fs";
|
|
56924
|
+
import { basename as basename13, dirname as dirname24, join as join46 } from "node:path";
|
|
55297
56925
|
function frizzDatabaseLocation(options) {
|
|
55298
56926
|
if (options.stateDir !== void 0) {
|
|
55299
|
-
const projectsDir =
|
|
56927
|
+
const projectsDir = dirname24(options.stateDir);
|
|
55300
56928
|
if (basename13(projectsDir) === "projects") {
|
|
55301
|
-
return { path: options.path ??
|
|
56929
|
+
return { path: options.path ?? join46(dirname24(projectsDir), "ui.db"), projectsDir };
|
|
55302
56930
|
}
|
|
55303
|
-
return { path: options.path ??
|
|
56931
|
+
return { path: options.path ?? join46(options.stateDir, "ui.db"), projectsDir: void 0 };
|
|
55304
56932
|
}
|
|
55305
56933
|
const data = options.home ? frizzPaths({ home: options.home }).data : frizzRoots().data;
|
|
55306
|
-
return { path: options.path ??
|
|
56934
|
+
return { path: options.path ?? join46(data, "ui.db"), projectsDir: join46(data, "projects") };
|
|
55307
56935
|
}
|
|
55308
56936
|
function ensureFrizzSchema(db) {
|
|
55309
56937
|
db.exec(`
|
|
@@ -55322,7 +56950,7 @@ function ensureFrizzSchema(db) {
|
|
|
55322
56950
|
}
|
|
55323
56951
|
function openFrizzDatabase(options = {}) {
|
|
55324
56952
|
const { path: path3, projectsDir } = frizzDatabaseLocation(options);
|
|
55325
|
-
|
|
56953
|
+
mkdirSync22(dirname24(path3), { recursive: true });
|
|
55326
56954
|
const db = new sqlite_default(path3);
|
|
55327
56955
|
db.pragma("busy_timeout = 5000");
|
|
55328
56956
|
db.pragma("journal_mode = WAL");
|
|
@@ -55334,10 +56962,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
55334
56962
|
);
|
|
55335
56963
|
for (const projectId of retiredLegacyProjectIds(projectsDir)) {
|
|
55336
56964
|
if (alreadyImported.has(projectId)) continue;
|
|
55337
|
-
const dir =
|
|
56965
|
+
const dir = join46(projectsDir, projectId);
|
|
55338
56966
|
try {
|
|
55339
|
-
renameSync12(
|
|
55340
|
-
db.prepare("INSERT INTO imported_project (project_id, source, rows, imported_at) VALUES (?, ?, 0, ?)").run(projectId,
|
|
56967
|
+
renameSync12(join46(dir, "ui.db.imported"), join46(dir, "ui.db"));
|
|
56968
|
+
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
56969
|
alreadyImported.add(projectId);
|
|
55342
56970
|
log.info("frizz-db", `${projectId}: restored ui.db.imported to ui.db and recorded it as imported`);
|
|
55343
56971
|
} catch (error) {
|
|
@@ -55347,7 +56975,7 @@ function openFrizzDatabase(options = {}) {
|
|
|
55347
56975
|
}
|
|
55348
56976
|
for (const projectId of legacyProjectIds(projectsDir)) {
|
|
55349
56977
|
if (alreadyImported.has(projectId)) continue;
|
|
55350
|
-
const legacyPath =
|
|
56978
|
+
const legacyPath = join46(projectsDir, projectId, "ui.db");
|
|
55351
56979
|
try {
|
|
55352
56980
|
const rows = importLegacyProject(db, projectId, legacyPath);
|
|
55353
56981
|
imported.push({ projectId, rows });
|
|
@@ -55377,10 +57005,10 @@ function openFrizzDatabase(options = {}) {
|
|
|
55377
57005
|
};
|
|
55378
57006
|
}
|
|
55379
57007
|
function projectIdsWithFile(projectsDir, file) {
|
|
55380
|
-
if (!
|
|
57008
|
+
if (!existsSync18(projectsDir)) return [];
|
|
55381
57009
|
return readdirSync14(projectsDir).filter((name) => {
|
|
55382
57010
|
try {
|
|
55383
|
-
return
|
|
57011
|
+
return statSync25(join46(projectsDir, name, file)).isFile();
|
|
55384
57012
|
} catch {
|
|
55385
57013
|
return false;
|
|
55386
57014
|
}
|
|
@@ -55390,7 +57018,7 @@ function legacyProjectIds(projectsDir) {
|
|
|
55390
57018
|
return projectIdsWithFile(projectsDir, "ui.db");
|
|
55391
57019
|
}
|
|
55392
57020
|
function retiredLegacyProjectIds(projectsDir) {
|
|
55393
|
-
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !
|
|
57021
|
+
return projectIdsWithFile(projectsDir, "ui.db.imported").filter((id) => !existsSync18(join46(projectsDir, id, "ui.db")));
|
|
55394
57022
|
}
|
|
55395
57023
|
function tableColumns(db, schema, table) {
|
|
55396
57024
|
return db.prepare(`PRAGMA ${schema}.table_info(${table})`).all().map((c) => c.name);
|
|
@@ -55546,7 +57174,7 @@ var init_tenant_prime = __esm({
|
|
|
55546
57174
|
// packages/server/src/project-teardown.ts
|
|
55547
57175
|
import { rmSync as rmSync13 } from "node:fs";
|
|
55548
57176
|
import { homedir as homedir19 } from "node:os";
|
|
55549
|
-
import { dirname as
|
|
57177
|
+
import { dirname as dirname25, resolve as resolve10 } from "node:path";
|
|
55550
57178
|
async function stopProjectWorkers(ctx) {
|
|
55551
57179
|
let stopped = 0;
|
|
55552
57180
|
for (const row of ctx.storage.allSessions()) {
|
|
@@ -55566,7 +57194,7 @@ function deleteProjectState(projectId, home = homedir19()) {
|
|
|
55566
57194
|
if (!/^[\w.-]+$/u.test(projectId) || projectId === "." || projectId === "..") {
|
|
55567
57195
|
throw new Error(`Refusing to delete state for a malformed project id: ${projectId}`);
|
|
55568
57196
|
}
|
|
55569
|
-
if (
|
|
57197
|
+
if (dirname25(resolve10(target)) !== resolve10(dirname25(projectStateDir("x", home)))) {
|
|
55570
57198
|
throw new Error(`Refusing to delete ${target} \u2014 it is not a project state directory`);
|
|
55571
57199
|
}
|
|
55572
57200
|
rmSync13(target, { recursive: true, force: true });
|
|
@@ -55595,11 +57223,11 @@ __export(index_exports, {
|
|
|
55595
57223
|
unknownProjectPage: () => unknownProjectPage
|
|
55596
57224
|
});
|
|
55597
57225
|
import { createServer } from "node:http";
|
|
55598
|
-
import { readFileSync as
|
|
55599
|
-
import { dirname as
|
|
57226
|
+
import { readFileSync as readFileSync33, existsSync as existsSync19, statSync as statSync26 } from "node:fs";
|
|
57227
|
+
import { dirname as dirname26, join as join47, resolve as resolve11, extname as extname4, posix as posix2, sep as sep6 } from "node:path";
|
|
55600
57228
|
function readServerAddressHolder(path3) {
|
|
55601
57229
|
try {
|
|
55602
|
-
const value = JSON.parse(
|
|
57230
|
+
const value = JSON.parse(readFileSync33(path3, "utf8"));
|
|
55603
57231
|
if (typeof value?.pid !== "number") return void 0;
|
|
55604
57232
|
return { pid: value.pid, port: typeof value.port === "number" ? value.port : -1 };
|
|
55605
57233
|
} catch {
|
|
@@ -55724,12 +57352,12 @@ function staticIsFresh(req, etag, mtimeMs) {
|
|
|
55724
57352
|
}
|
|
55725
57353
|
function serveStatic(distDir, req, res) {
|
|
55726
57354
|
const rel = posix2.normalize((req.url ?? "/").split("?")[0].replace(/\\/g, "/")).replace(/^(\.\.[/\\])+/, "");
|
|
55727
|
-
let file =
|
|
55728
|
-
if (!file.startsWith(distDir + sep6)) file =
|
|
55729
|
-
if (!
|
|
57355
|
+
let file = join47(distDir, rel === "/" ? "index.html" : rel);
|
|
57356
|
+
if (!file.startsWith(distDir + sep6)) file = join47(distDir, "index.html");
|
|
57357
|
+
if (!existsSync19(file)) file = join47(distDir, "index.html");
|
|
55730
57358
|
try {
|
|
55731
|
-
const stats =
|
|
55732
|
-
const cacheControl = file.startsWith(
|
|
57359
|
+
const stats = statSync26(file);
|
|
57360
|
+
const cacheControl = file.startsWith(join47(distDir, "assets") + sep6) ? STATIC_CACHE_IMMUTABLE : STATIC_CACHE_REVALIDATE;
|
|
55733
57361
|
const etag = `"${stats.size.toString(16)}-${Math.trunc(stats.mtimeMs).toString(16)}"`;
|
|
55734
57362
|
const headers = {
|
|
55735
57363
|
"content-type": MIME[extname4(file)] ?? "application/octet-stream",
|
|
@@ -55745,7 +57373,7 @@ function serveStatic(distDir, req, res) {
|
|
|
55745
57373
|
res.end();
|
|
55746
57374
|
return;
|
|
55747
57375
|
}
|
|
55748
|
-
let body =
|
|
57376
|
+
let body = readFileSync33(file);
|
|
55749
57377
|
const accept = req.headers["accept-encoding"];
|
|
55750
57378
|
const encoding = negotiateEncoding(Array.isArray(accept) ? accept.join(",") : accept);
|
|
55751
57379
|
if (encoding && shouldCompress(new Headers({ "content-type": headers["content-type"] }), body.byteLength)) {
|
|
@@ -56007,7 +57635,7 @@ async function startServer(opts = {}) {
|
|
|
56007
57635
|
if (statusPath && statusIdentity) runtime.removeStatus(statusPath, statusIdentity);
|
|
56008
57636
|
if (statusPath && statusIdentity) {
|
|
56009
57637
|
try {
|
|
56010
|
-
runtime.removeStatus(serverAddressPathForStateDir(
|
|
57638
|
+
runtime.removeStatus(serverAddressPathForStateDir(dirname26(statusPath)), statusIdentity);
|
|
56011
57639
|
} catch {
|
|
56012
57640
|
}
|
|
56013
57641
|
}
|
|
@@ -56145,7 +57773,7 @@ async function startServer(opts = {}) {
|
|
|
56145
57773
|
}
|
|
56146
57774
|
statusPath = serverLockPathFor(ctx.project);
|
|
56147
57775
|
const webRoot = resolve11(import.meta.dirname, "..", "..", "web");
|
|
56148
|
-
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) :
|
|
57776
|
+
const distDir = opts.webDistDir ? resolve11(opts.webDistDir) : join47(webRoot, "dist");
|
|
56149
57777
|
startupPhase = "Vite";
|
|
56150
57778
|
if (opts.dev) {
|
|
56151
57779
|
try {
|
|
@@ -56221,7 +57849,7 @@ async function startServer(opts = {}) {
|
|
|
56221
57849
|
if (vite) {
|
|
56222
57850
|
vite.middlewares(req, res, () => {
|
|
56223
57851
|
try {
|
|
56224
|
-
const html =
|
|
57852
|
+
const html = readFileSync33(join47(webRoot, "index.html"), "utf8");
|
|
56225
57853
|
void vite.transformIndexHtml(url, html).then((out) => {
|
|
56226
57854
|
res.writeHead(200, { "content-type": "text/html" });
|
|
56227
57855
|
res.end(out);
|
|
@@ -56233,7 +57861,7 @@ async function startServer(opts = {}) {
|
|
|
56233
57861
|
});
|
|
56234
57862
|
return;
|
|
56235
57863
|
}
|
|
56236
|
-
if (
|
|
57864
|
+
if (existsSync19(distDir)) {
|
|
56237
57865
|
serveStatic(distDir, req, res);
|
|
56238
57866
|
return;
|
|
56239
57867
|
}
|
|
@@ -56431,7 +58059,7 @@ var init_index = __esm({
|
|
|
56431
58059
|
}
|
|
56432
58060
|
};
|
|
56433
58061
|
isApiUrl = (url) => url === FRIZZ_ROUTE_PREFIX || url.startsWith(`${FRIZZ_ROUTE_PREFIX}/`);
|
|
56434
|
-
serverLockPathFor = (project) =>
|
|
58062
|
+
serverLockPathFor = (project) => join47(project.stateDir, "server.lock");
|
|
56435
58063
|
MIME = {
|
|
56436
58064
|
".html": "text/html",
|
|
56437
58065
|
".js": "text/javascript",
|
|
@@ -56454,8 +58082,8 @@ init_project();
|
|
|
56454
58082
|
init_project_launch();
|
|
56455
58083
|
init_shutdown();
|
|
56456
58084
|
init_logging();
|
|
56457
|
-
import { existsSync as
|
|
56458
|
-
import { join as
|
|
58085
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
58086
|
+
import { join as join48 } from "node:path";
|
|
56459
58087
|
|
|
56460
58088
|
// packages/server/src/native-helper.ts
|
|
56461
58089
|
import { chmodSync, statSync as statSync4 } from "node:fs";
|
|
@@ -56532,9 +58160,9 @@ try {
|
|
|
56532
58160
|
["FRIZZ_SCRIPTS_DIR", process.env.FRIZZ_SCRIPTS_DIR, "index.mjs"],
|
|
56533
58161
|
["FRIZZ_WORKER_PLUGIN_DIR", process.env.FRIZZ_WORKER_PLUGIN_DIR, ".claude-plugin/plugin.json"]
|
|
56534
58162
|
];
|
|
56535
|
-
if (!
|
|
58163
|
+
if (!existsSync20(stableWebDist)) throw new Error("stable artifact launch is missing its verified web directory");
|
|
56536
58164
|
for (const [name, directory, requiredFile] of required) {
|
|
56537
|
-
if (!directory || !
|
|
58165
|
+
if (!directory || !existsSync20(join48(directory, requiredFile)))
|
|
56538
58166
|
throw new Error(`stable artifact launch is missing verified ${name}`);
|
|
56539
58167
|
}
|
|
56540
58168
|
}
|