openmausbot 0.1.87 → 0.1.88
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/README.md +2 -1
- package/dist/assets/{abnfDiagram-O67JEVCF-BTiuL79F.js → abnfDiagram-O67JEVCF-qd9VD3GV.js} +1 -1
- package/dist/assets/{arc-C88Blc2v.js → arc-3RwkUjRe.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-BZsJIZrb.js → architectureDiagram-NJMV4G6O-CFKSJS7a.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CVeMe9Ah.js → blockDiagram-BEXU5L5S-CG1rEfWD.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-Cedk3Bhx.js → c4Diagram-YGBWAQC7-CBW9N664.js} +1 -1
- package/dist/assets/channel-CnRnO3tF.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-D-VRqzTB.js → chunk-2Q5K7J3B-2mRfTksB.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-BChov01Y.js → chunk-3FUC2YCW-Cv1dpErj.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-C-TfwvHy.js → chunk-5VM5RSS4-B7IACJp-.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-Db7CGj5Y.js → chunk-GTNCS2PH-An0hZtYa.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CNvIfqKT.js → chunk-GWA4HPMP-BcJB2nEm.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-C_bEp5tq.js → chunk-JWPE2WC7-CV_W7TN7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Coi4BOls.js → chunk-NETBCI7D-Bcs_kwnA.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CzylEuL3.js → chunk-WEXAMYUT-lrx_DxFP.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-m7p7ztaD.js → chunk-XXDRQBXY-DabHlLGM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BMy6o9x2.js → classDiagram-v2-NBCMYWYE-BnHj6P7d.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BDQ__0_J.js → cose-bilkent-JH36ORCC-CfLp6UDi.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-Djv4ghUb.js → cynefin-OW5HDTMX-Dm4dbVA1.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-BULrlGYH.js → cynefinDiagram-VND7K2PF-B9mH-PID.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-D-jrT_hU.js → dagre-6A5THRUB-DDc_NVQJ.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-p2UhQmfL.js → diagram-22UHCM2B-lwr8Ip8-.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DHvGWZ7q.js → diagram-3UASUU5V-BLLe6dmI.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-ChhKDot-.js → diagram-ATOU4E4O-6eUIq4lz.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-B-T7A43R.js → diagram-CDSNMT55-O-pRN7VQ.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-CJm8ENNY.js → diagram-MLGK6HIB-BietvGAI.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-510tlza8.js → diagram-MPIPVDR6-Bd2Cl62X.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-PkvRPHel.js → ebnfDiagram-ZINNZB2B-B6iDy69W.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-BVBVPoX3.js → elk-276RUBZZ-DQVHfKe0.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-BgYMVm7L.js → erDiagram-OPXOYQCR-BWdVp2tQ.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-BGqsnc_3.js → flowDiagram-KWPJA3E3-WLE6rKB4.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-D_-UeReH.js → ganttDiagram-FUAMR5RP-BbjS-4gn.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-D0uooONx.js → gitGraphDiagram-X574FWY7-Dpy2QSGq.js} +1 -1
- package/dist/assets/index-BIMdIbaw.css +1 -0
- package/dist/assets/index-CHnODCr2.js +583 -0
- package/dist/assets/{index-zRsJERGJ.js → index-CIaHn5NS.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-B9e1z77f.js → infoDiagram-VRGFBTTK-D_jKWlKe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BB4vNwvD.js → ishikawaDiagram-OU5B5YK6-DLJohSuK.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-DpkosOTB.js → journeyDiagram-ZHPQQLJL-DPOFj0qN.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DbDP4kUu.js → kanban-definition-PNTS6WVX-BI7v-xpK.js} +1 -1
- package/dist/assets/{linear-DBJGPmjT.js → linear-C2pWPSxO.js} +1 -1
- package/dist/assets/{mermaid.core-DWPRi-ue.js → mermaid.core-CR-j5WQU.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-nszBbJmx.js → mindmap-definition-NLK3R4M7-lz9GvXRh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CWykJWu_.js → pegDiagram-GJSIUBJH-Dl4_ia61.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-g3Da-Vnv.js → pieDiagram-5QR66LMP-DWuT3n88.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Wzrlpzgh.js → quadrantDiagram-O4NWA36T-DXWe9Jx7.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-moHTFMC0.js → railroadDiagram-XR7U4H2S-COY1IBVY.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CwfXdFH8.js → requirementDiagram-PLB6GJNP-DnyVuhsQ.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-3ZROYvnq.js → sankeyDiagram-IPEJSGJF-BSc4NdGK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-E1TBebP8.js → sequenceDiagram-PO4LG4MO-kqEU2EGq.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-DHKEU5pu.js → sizeCapture-INFHLROL-BvB4bkWD.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BguEj09D.js → stateDiagram-v2-GCMORJYK-BIrqNv7y.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-C7IDYnhd.js → swimlanes-2SLR337P-CFnalQUE.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C4a8qNGj.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-DCW-F5PW.js → timeline-definition-EJHVYXUP-CsSWJnSQ.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-xBMFtWYV.js → usecaseDiagram-POWQR4AR-DkAmJ1EE.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-B5nIkuH8.js → vennDiagram-UO4OBE2U-C3vd9drL.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DYFAhdbr.js → wardleyDiagram-VNRHLVJA-j26CM-pi.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DeXwX6Cz.js → xychartDiagram-PMCCYNJV-BYu1Q_at.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/companion/src/routes.js +8 -1
- package/dist-server/connector-proxy.js +105 -1
- package/dist-server/container-mcp.js +3 -1
- package/dist-server/drivers/agents-proxy.js +277 -22
- package/dist-server/index.js +23150 -19947
- package/dist-server/local-computer-proxy.js +2 -0
- package/dist-server/mcp-gate.js +2 -0
- package/dist-server/mcp-server.js +5 -3
- package/dist-server/message-search.worker.js +52 -0
- package/dist-server/openmausbot.js +540 -298
- package/dist-server/pair-cli.js +540 -298
- package/dist-server/server/admission.js +81 -0
- package/dist-server/server/aside-queue.js +294 -0
- package/dist-server/server/attachments.js +43 -1
- package/dist-server/server/bot-attachment.js +107 -0
- package/dist-server/server/bot-overview.js +19 -0
- package/dist-server/server/cli-api-setup.js +1 -0
- package/dist-server/server/composio.js +297 -1
- package/dist-server/server/config.js +25 -3
- package/dist-server/server/connector-advertisement.js +165 -0
- package/dist-server/server/connector-proxy.js +14 -2
- package/dist-server/server/connector-verdict.js +31 -4
- package/dist-server/server/container-computer.js +60 -0
- package/dist-server/server/contracts.js +22 -0
- package/dist-server/server/drivers/acp/core.js +24 -6
- package/dist-server/server/drivers/acp/cursor.js +3 -7
- package/dist-server/server/drivers/acp/droid.js +4 -7
- package/dist-server/server/drivers/acp/gemini.js +4 -4
- package/dist-server/server/drivers/acp/grok.js +4 -9
- package/dist-server/server/drivers/acp/hermes.js +3 -5
- package/dist-server/server/drivers/acp/opencode-go.js +2 -5
- package/dist-server/server/drivers/acp/qwen.js +9 -15
- package/dist-server/server/drivers/agents-call.js +137 -10
- package/dist-server/server/drivers/agents-catalog.js +100 -6
- package/dist-server/server/drivers/boxagent.js +2 -16
- package/dist-server/server/drivers/claude.js +31 -17
- package/dist-server/server/drivers/codex-catalog.js +14 -6
- package/dist-server/server/drivers/codex-identity.js +2 -1
- package/dist-server/server/drivers/codex-release.js +48 -0
- package/dist-server/server/drivers/codex.js +7 -25
- package/dist-server/server/drivers/openai-chat.js +26 -2
- package/dist-server/server/drivers/pi.js +237 -30
- package/dist-server/server/drivers/prompt-split.js +32 -9
- package/dist-server/server/drivers/session-idle.js +29 -0
- package/dist-server/server/env-path.js +15 -0
- package/dist-server/server/guarded-requests.js +2 -2
- package/dist-server/server/harness-capabilities.js +46 -0
- package/dist-server/server/index.js +1037 -209
- package/dist-server/server/message-db.js +22 -42
- package/dist-server/server/message-file.js +9 -1
- package/dist-server/server/message-search-query.js +43 -0
- package/dist-server/server/message-search-worker.js +80 -0
- package/dist-server/server/message-search.worker.js +21 -0
- package/dist-server/server/model-requests.js +248 -0
- package/dist-server/server/options-card.js +21 -0
- package/dist-server/server/org-library.js +15 -1
- package/dist-server/server/peer-delivery.js +60 -0
- package/dist-server/server/peer-roster.js +57 -0
- package/dist-server/server/presets.js +32 -32
- package/dist-server/server/profile-requests.js +66 -39
- package/dist-server/server/profile-revision.js +9 -1
- package/dist-server/server/profile-versions.js +6 -3
- package/dist-server/server/routine-requests.js +29 -7
- package/dist-server/server/skill-fetch.js +72 -14
- package/dist-server/server/skills.js +1 -0
- package/dist-server/server/store.js +32 -2
- package/dist-server/server/system-prompt.js +26 -2
- package/dist-server/server/team-setup-requests.js +124 -23
- package/dist-server/server/thread-events.js +3 -1
- package/dist-server/server/tightening-requests.js +405 -0
- package/dist-server/server/tts/chatterbox.js +27 -5
- package/dist-server/server/usage-ledger.js +7 -1
- package/dist-server/server/workspace-backup.js +40 -3
- package/dist-server/server/workspace-backup.worker.js +10 -0
- package/dist-server/shared/ask-question.js +19 -0
- package/dist-server/shared/credential-request.js +5 -1
- package/dist-server/shared/line-diff.js +23 -0
- package/dist-server/shared/model-request.js +1 -0
- package/dist-server/shared/options-card.js +51 -0
- package/dist-server/shared/profile-request.js +5 -4
- package/dist-server/shared/tightening-request.js +87 -0
- package/dist-server/src/lib/composer-attachments.js +25 -3
- package/dist-server/vps-container-mcp.js +3 -1
- package/dist-server/workspace-backup.worker.js +19474 -0
- package/package.json +1 -1
- package/dist/assets/channel-taCNRlUU.js +0 -1
- package/dist/assets/index-BwdMAQ6p.css +0 -1
- package/dist/assets/index-ByJennr7.js +0 -581
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BpEdoB2e.js +0 -8
|
@@ -808,6 +808,66 @@ export async function containerComputerScreenshot(runner = sh, platform = proces
|
|
|
808
808
|
}
|
|
809
809
|
const screenshotStatusCache = new Map();
|
|
810
810
|
const containerMcpPath = SPAWNED_PROXIES.containerMcp;
|
|
811
|
+
const EXEC_DEFAULT_SECONDS = 60;
|
|
812
|
+
const EXEC_MAX_SECONDS = 300;
|
|
813
|
+
const EXEC_OUTPUT_LIMIT = 20_000;
|
|
814
|
+
/** Keep the start and the end: a failure message is nearly always at the end. */
|
|
815
|
+
export function clipExecOutput(text, limit = EXEC_OUTPUT_LIMIT) {
|
|
816
|
+
if (text.length <= limit)
|
|
817
|
+
return text;
|
|
818
|
+
const head = Math.floor(limit / 5);
|
|
819
|
+
return `${text.slice(0, head)}\n… ${text.length - limit} characters omitted …\n${text.slice(text.length - (limit - head))}`;
|
|
820
|
+
}
|
|
821
|
+
const defaultExecRunner = async (command, args, options) => {
|
|
822
|
+
const resolved = resolveCliSpawn(command, args);
|
|
823
|
+
try {
|
|
824
|
+
const { stdout, stderr } = await run(resolved.command, resolved.args, {
|
|
825
|
+
timeout: options.timeout,
|
|
826
|
+
encoding: "utf8",
|
|
827
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
828
|
+
env: { ...process.env, PATH: augmentedPath() },
|
|
829
|
+
});
|
|
830
|
+
return { stdout, stderr, code: 0 };
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
const failure = error;
|
|
834
|
+
// A non-zero exit still carries the output; anything else (runtime missing,
|
|
835
|
+
// client killed) is a real failure to run at all.
|
|
836
|
+
if (typeof failure.code === "number") {
|
|
837
|
+
return { stdout: failure.stdout ?? "", stderr: failure.stderr ?? "", code: failure.code };
|
|
838
|
+
}
|
|
839
|
+
throw Object.assign(new Error(failure.killed ? "the command client timed out" : failure.message ?? "could not run the command"), { status: 502 });
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
/** Run one shell command inside a Local VM as the desktop user, in its durable
|
|
843
|
+
* workspace, and return the exit code and text output. Bots use this instead of
|
|
844
|
+
* typing into a terminal window and reading screenshots. The limit is enforced
|
|
845
|
+
* inside the container so a runaway process is really stopped. */
|
|
846
|
+
export async function containerExec(target, command, options = {}) {
|
|
847
|
+
if (!command.trim())
|
|
848
|
+
throw Object.assign(new Error("command is required"), { status: 400 });
|
|
849
|
+
if (command.length > 20_000)
|
|
850
|
+
throw Object.assign(new Error("command is too long"), { status: 400 });
|
|
851
|
+
if (options.timeoutSeconds !== undefined && !Number.isFinite(options.timeoutSeconds)) {
|
|
852
|
+
throw Object.assign(new Error("timeout_seconds must be finite"), { status: 400 });
|
|
853
|
+
}
|
|
854
|
+
const runtime = options.runtime ?? (await containerRuntimeStatus()).runtime;
|
|
855
|
+
if (!runtime)
|
|
856
|
+
throw Object.assign(new Error("No container runtime is available for the Local VM"), { status: 409 });
|
|
857
|
+
const seconds = Math.min(Math.max(Math.floor(options.timeoutSeconds ?? EXEC_DEFAULT_SECONDS), 1), EXEC_MAX_SECONDS);
|
|
858
|
+
const result = await (options.exec ?? defaultExecRunner)(runtime, [
|
|
859
|
+
"exec",
|
|
860
|
+
"-u", "cua",
|
|
861
|
+
"-w", VM_WORKSPACE_GUEST,
|
|
862
|
+
"-e", "HOME=/home/cua",
|
|
863
|
+
"-e", `DISPLAY=${DISPLAY}`,
|
|
864
|
+
target.containerName,
|
|
865
|
+
"timeout", "-k", "5", String(seconds),
|
|
866
|
+
"sh", "-lc", command,
|
|
867
|
+
], { timeout: (seconds + 20) * 1000 });
|
|
868
|
+
const timedOut = result.code === 124 || result.code === 137;
|
|
869
|
+
return { exitCode: result.code, stdout: clipExecOutput(result.stdout), stderr: clipExecOutput(result.stderr), timedOut };
|
|
870
|
+
}
|
|
811
871
|
export function containerComputerMcp(runtime, control, target = SHARED_LOCAL_VM_TARGET) {
|
|
812
872
|
return {
|
|
813
873
|
command: process.execPath,
|
|
@@ -17,6 +17,28 @@ export function isModelVariant(value) {
|
|
|
17
17
|
return typeof value === "string" && value.length > 0 && value.length <= 256 &&
|
|
18
18
|
value.trim() === value && !/\p{Cc}/u.test(value);
|
|
19
19
|
}
|
|
20
|
+
/** The picker label for a model id when the catalog row carries no display
|
|
21
|
+
* name: split on the word breaks the catalog treats as separators and
|
|
22
|
+
* capitalize each part ("gpt-5.4-mini" → "Gpt 5.4 Mini"). Each harness
|
|
23
|
+
* passes its own break class so existing labels stay byte-for-byte. */
|
|
24
|
+
export function titleCaseModelId(id, wordBreaks) {
|
|
25
|
+
return id
|
|
26
|
+
.split(wordBreaks)
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
29
|
+
.join(" ");
|
|
30
|
+
}
|
|
31
|
+
/** A picker label that carries a second facet — a display name, a provider
|
|
32
|
+
* host — beside the model id, unless the facet is already what the id says.
|
|
33
|
+
* `redundantWhen` decides what counts as "already said" and `decorate` how
|
|
34
|
+
* the facet is appended, so every harness's labels stay exactly what they
|
|
35
|
+
* were while the shape is written once. */
|
|
36
|
+
export function qualifiedModelLabel(id, qualifier, options = {}) {
|
|
37
|
+
const { redundantWhen = () => false, decorate = (facet) => ` — ${facet}` } = options;
|
|
38
|
+
if (!qualifier || redundantWhen(id, qualifier))
|
|
39
|
+
return id;
|
|
40
|
+
return `${id}${decorate(qualifier)}`;
|
|
41
|
+
}
|
|
20
42
|
let eventCounter = 0;
|
|
21
43
|
export const newEventId = () => `ev-${Date.now().toString(36)}-${(eventCounter++).toString(36)}`;
|
|
22
44
|
export const newId = () => crypto.randomUUID();
|
|
@@ -50,6 +50,7 @@ import { commandSummary, toolDetailPreview } from "../../tool-summary.js";
|
|
|
50
50
|
import { extractMcpImages } from "../../mcp-tool-images.js";
|
|
51
51
|
import { redactSecretsInText } from "../../redact.js";
|
|
52
52
|
import { recoveryPromptFor } from "../../resume-recovery.js";
|
|
53
|
+
import { sessionIdlePolicy } from "../session-idle.js";
|
|
53
54
|
/** ACP vendors put the actionable cause in error.data while keeping the
|
|
54
55
|
* JSON-RPC message generic. Only surface known text fields, never a response
|
|
55
56
|
* body/config dump, and redact before bounding the displayed diagnostic. */
|
|
@@ -80,6 +81,9 @@ const INIT_TIMEOUT = envOr("OPENMAUS_ACP_INIT_TIMEOUT_MS", 300_000);
|
|
|
80
81
|
const SESSION_CONFIG_TIMEOUT = envOr("OPENMAUS_ACP_SESSION_CONFIG_TIMEOUT_MS", 300_000); // configureSession's per-request default
|
|
81
82
|
const NEW_SESSION_TIMEOUT = envOr("OPENMAUS_ACP_NEW_SESSION_TIMEOUT_MS", 300_000);
|
|
82
83
|
const LOAD_SESSION_TIMEOUT = envOr("OPENMAUS_ACP_LOAD_SESSION_TIMEOUT_MS", 120_000); // history replay on a long thread is slow
|
|
84
|
+
/** ACP agents may compact their own history without telling the client;
|
|
85
|
+
* re-send the full prompt after this many bare turns as a backstop. */
|
|
86
|
+
const ACP_PROMPT_RE_ANCHOR_TURNS = 8;
|
|
83
87
|
// Read lazily (not at import) so a test can shorten the window. Unlike the
|
|
84
88
|
// setup calls above, session/prompt legitimately streams for minutes, so a
|
|
85
89
|
// wall-clock deadline would false-positive: this guard only trips when the
|
|
@@ -212,6 +216,24 @@ export function versionFromProbe(stdout, stderr) {
|
|
|
212
216
|
const err = (stderr ?? "").trim().split(/\r\n|\n|\r/, 1)[0]?.trim() ?? "";
|
|
213
217
|
return err || null;
|
|
214
218
|
}
|
|
219
|
+
/** The first dotted numeric triple in a version banner, or null when there is
|
|
220
|
+
* none. A wrapper that prints its own banner first still parses: the triple
|
|
221
|
+
* is the version wherever it appears. */
|
|
222
|
+
export function parseVersionTriple(value) {
|
|
223
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(value);
|
|
224
|
+
if (!match)
|
|
225
|
+
return null;
|
|
226
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
227
|
+
}
|
|
228
|
+
/** Whether `installed` compares at or above `floor`, componentwise. Every
|
|
229
|
+
* version floor in the tree is a dotted triple. */
|
|
230
|
+
export function versionAtLeast(installed, floor) {
|
|
231
|
+
for (let i = 0; i < 3; i += 1) {
|
|
232
|
+
if (installed[i] !== floor[i])
|
|
233
|
+
return installed[i] > floor[i];
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
215
237
|
export function createAcpDriver(support) {
|
|
216
238
|
const DRIVER_KIND = support.driverKind;
|
|
217
239
|
const SOURCE = support.nativeSource;
|
|
@@ -274,11 +296,7 @@ export function createAcpDriver(support) {
|
|
|
274
296
|
// contract prompts the live session instead of paying the handshake
|
|
275
297
|
// again. An idle session closes after SESSION_IDLE_MS of quiet.
|
|
276
298
|
const sessions = new Map();
|
|
277
|
-
const
|
|
278
|
-
const sessionIdleMinimum = Number.isFinite(configuredIdleMinimum) && configuredIdleMinimum > 0
|
|
279
|
-
? configuredIdleMinimum
|
|
280
|
-
: 10_000;
|
|
281
|
-
const SESSION_IDLE_MS = Math.max(sessionIdleMinimum, Number(process.env.OMB_ACP_SESSION_IDLE_MS) || 10 * 60_000);
|
|
299
|
+
const { idleMs: SESSION_IDLE_MS } = sessionIdlePolicy("ACP");
|
|
282
300
|
const closeSession = (threadId, why) => {
|
|
283
301
|
const session = sessions.get(threadId);
|
|
284
302
|
if (!session || session.closing)
|
|
@@ -1320,7 +1338,7 @@ export function createAcpDriver(support) {
|
|
|
1320
1338
|
let pendingSplitReceipt = null;
|
|
1321
1339
|
if (halves.stable !== null) {
|
|
1322
1340
|
const receiptKey = JSON.stringify([threadId, sessionId]);
|
|
1323
|
-
const composed = splitSessionPrompt(halves.stable, halves.volatile, readPromptSplitReceipt(DRIVER_KIND, receiptKey), promptTurn.system, promptTurn.text, Boolean(turn.mentionTurn));
|
|
1341
|
+
const composed = splitSessionPrompt(halves.stable, halves.volatile, readPromptSplitReceipt(DRIVER_KIND, receiptKey), promptTurn.system, promptTurn.text, Boolean(turn.mentionTurn), ACP_PROMPT_RE_ANCHOR_TURNS);
|
|
1324
1342
|
promptInput = { ...promptTurn, system: "", text: composed.text };
|
|
1325
1343
|
pendingSplitReceipt = { key: receiptKey, receipt: composed.receipt };
|
|
1326
1344
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { titleCaseModelId } from "../../contracts.js";
|
|
1
2
|
import { execCli } from "../../procs.js";
|
|
2
3
|
import { createAcpDriver } from "./core.js";
|
|
3
4
|
/** Translate an argv `--model` slug into the id this ACP session will accept.
|
|
@@ -86,13 +87,8 @@ function asRecord(value) {
|
|
|
86
87
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
87
88
|
}
|
|
88
89
|
function labelFor(id, explicit) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return id
|
|
92
|
-
.split(/[-_./]+/g)
|
|
93
|
-
.filter(Boolean)
|
|
94
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
95
|
-
.join(" ");
|
|
90
|
+
const trimmed = explicit?.trim();
|
|
91
|
+
return trimmed || titleCaseModelId(id, /[-_./]+/);
|
|
96
92
|
}
|
|
97
93
|
function pushModel(options, seen, id, label, custom = false) {
|
|
98
94
|
if (!SLUG.test(id) || seen.has(id))
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
// options set over the wire (session/set_model, session/set_mode), which is
|
|
17
17
|
// why both live in configureSession() below and NOT in spawnArgs.
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
-
import { homedir } from "node:os";
|
|
20
19
|
import { join } from "node:path";
|
|
20
|
+
import { userHome } from "../../env-path.js";
|
|
21
21
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
22
22
|
import { createAcpDriver } from "./core.js";
|
|
23
23
|
// FACTORY_HOME_OVERRIDE replaces the HOME the CLI resolves, NOT the data root:
|
|
@@ -34,7 +34,7 @@ import { createAcpDriver } from "./core.js";
|
|
|
34
34
|
// out. Names read from the 0.196.0 binary.
|
|
35
35
|
const AUTH_FILES = ["auth.v2.file", "auth.v2.loginkeychain", "auth.v2.keyring"];
|
|
36
36
|
function authFilePaths(env) {
|
|
37
|
-
const home = env.FACTORY_HOME_OVERRIDE || env
|
|
37
|
+
const home = env.FACTORY_HOME_OVERRIDE || userHome(env);
|
|
38
38
|
return AUTH_FILES.map((name) => join(home, ".factory", name));
|
|
39
39
|
}
|
|
40
40
|
const INJECT_ID_PREFIX = "custom:openmausbot-";
|
|
@@ -42,9 +42,6 @@ export function droidInjectId(host, model) {
|
|
|
42
42
|
const safe = `${host}-${model}`.replace(/[^a-zA-Z0-9._+-]+/g, "-").replace(/-+/g, "-");
|
|
43
43
|
return `${INJECT_ID_PREFIX}${safe}`;
|
|
44
44
|
}
|
|
45
|
-
function factoryHome(env) {
|
|
46
|
-
return env.FACTORY_HOME_OVERRIDE || env.HOME || env.USERPROFILE || homedir();
|
|
47
|
-
}
|
|
48
45
|
/** Upsert a BYOK custom model so session/set_model can reach the local host. */
|
|
49
46
|
export function ensureDroidInjectModel(modelId, env = process.env) {
|
|
50
47
|
const inject = decodeInjectId(modelId);
|
|
@@ -54,7 +51,7 @@ export function ensureDroidInjectModel(modelId, env = process.env) {
|
|
|
54
51
|
if (!host)
|
|
55
52
|
return modelId;
|
|
56
53
|
const id = droidInjectId(inject.host, inject.model);
|
|
57
|
-
const dir = join(
|
|
54
|
+
const dir = join(env.FACTORY_HOME_OVERRIDE || userHome(env), ".factory");
|
|
58
55
|
mkdirSync(dir, { recursive: true });
|
|
59
56
|
const path = join(dir, "settings.json");
|
|
60
57
|
let settings = {};
|
|
@@ -105,7 +102,7 @@ export function applyDroidLocalAuthEnv(env, modelId) {
|
|
|
105
102
|
env.FACTORY_API_KEY = "openmausbot-local";
|
|
106
103
|
}
|
|
107
104
|
function readSettings(env) {
|
|
108
|
-
return JSON.parse(readFileSync(join(
|
|
105
|
+
return JSON.parse(readFileSync(join(env.FACTORY_HOME_OVERRIDE || userHome(env), ".factory", "settings.json"), "utf8"));
|
|
109
106
|
}
|
|
110
107
|
async function resolveModels(env) {
|
|
111
108
|
let catalog = MODELS;
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
// the ACP flag + auth method ids follow the published Gemini CLI ACP contract
|
|
27
27
|
// and should be re-verified end-to-end once the CLI is present.
|
|
28
28
|
import { existsSync, readFileSync } from "node:fs";
|
|
29
|
-
import { homedir } from "node:os";
|
|
30
29
|
import { join } from "node:path";
|
|
30
|
+
import { userHome } from "../../env-path.js";
|
|
31
31
|
import { createAcpDriver } from "./core.js";
|
|
32
32
|
// Prefer an explicit key method, then personal OAuth, then Vertex — but fall
|
|
33
33
|
// back to whatever the CLI advertises so a new method id still works.
|
|
@@ -37,9 +37,9 @@ const AUTH_PREFERENCE = ["gemini-api-key", "oauth-personal", "vertex-ai"];
|
|
|
37
37
|
* an enterprise licence may still redeem. A stale consumer credential —
|
|
38
38
|
* the common case since 2026-06-18 — reads as not signed in, which is the
|
|
39
39
|
* truthful answer for an engine that would fail its first turn anyway. */
|
|
40
|
-
function liveOauthCredential() {
|
|
40
|
+
function liveOauthCredential(env) {
|
|
41
41
|
try {
|
|
42
|
-
const creds = JSON.parse(readFileSync(join(
|
|
42
|
+
const creds = JSON.parse(readFileSync(join(userHome(env), ".gemini", "oauth_creds.json"), "utf8"));
|
|
43
43
|
if (typeof creds.access_token !== "string" || creds.access_token.trim().length === 0)
|
|
44
44
|
return false;
|
|
45
45
|
const hasRefresh = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
|
|
@@ -62,7 +62,7 @@ const nonBlank = (value) => Boolean(value?.trim());
|
|
|
62
62
|
export function geminiIsAuthenticated(env) {
|
|
63
63
|
return (nonBlank(env.GEMINI_API_KEY) ||
|
|
64
64
|
nonBlank(env.GOOGLE_API_KEY) ||
|
|
65
|
-
(existsSync(join(
|
|
65
|
+
(existsSync(join(userHome(env), ".gemini", "oauth_creds.json")) && liveOauthCredential(env)));
|
|
66
66
|
}
|
|
67
67
|
/** Gemini CLI's approval modes, passed through: `--approval-mode auto_edit`
|
|
68
68
|
* approves file edits, `--yolo` approves everything; Gemini has no
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// The generic protocol runtime lives in acp/core.ts; this file is only the
|
|
5
5
|
// per-harness quirks. Verified against grok 1.0.0.
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
7
|
import { join } from "node:path";
|
|
8
|
+
import { harnessHome } from "../../env-path.js";
|
|
9
9
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
10
10
|
import { createAcpDriver } from "./core.js";
|
|
11
11
|
export const STATIC_GROK_MODELS = {
|
|
@@ -17,11 +17,6 @@ export const STATIC_GROK_MODELS = {
|
|
|
17
17
|
],
|
|
18
18
|
};
|
|
19
19
|
const SLUG = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
20
|
-
function grokHome(env) {
|
|
21
|
-
if (env.GROK_HOME)
|
|
22
|
-
return env.GROK_HOME;
|
|
23
|
-
return join(env.HOME || env.USERPROFILE || homedir(), ".grok");
|
|
24
|
-
}
|
|
25
20
|
function unquote(raw) {
|
|
26
21
|
const value = raw.trim();
|
|
27
22
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
@@ -32,7 +27,7 @@ function unquote(raw) {
|
|
|
32
27
|
/** Local slugs from ~/.grok/config.toml, plus the cloud defaults.
|
|
33
28
|
* `grok -m <slug>` already accepts these; the picker just didn't list them. */
|
|
34
29
|
export function readGrokModelCatalog(env = process.env) {
|
|
35
|
-
const path = join(
|
|
30
|
+
const path = join(env.GROK_HOME || harnessHome("grok", env), "config.toml");
|
|
36
31
|
let text = "";
|
|
37
32
|
try {
|
|
38
33
|
text = readFileSync(path, "utf8");
|
|
@@ -114,7 +109,7 @@ export function ensureGrokInjectSlug(modelId, env = process.env) {
|
|
|
114
109
|
const host = localHost(inject.host);
|
|
115
110
|
if (!host)
|
|
116
111
|
return modelId;
|
|
117
|
-
const path = join(
|
|
112
|
+
const path = join(env.GROK_HOME || harnessHome("grok", env), "config.toml");
|
|
118
113
|
let text = "";
|
|
119
114
|
try {
|
|
120
115
|
text = readFileSync(path, "utf8");
|
|
@@ -258,7 +253,7 @@ const support = {
|
|
|
258
253
|
// an unauthenticated CLI is a user action, not something to paper over.
|
|
259
254
|
pickAuthMethod: (methods) => (methods.some((m) => m.id === "cached_token") ? "cached_token" : null),
|
|
260
255
|
authFailure: "fail",
|
|
261
|
-
isAuthenticated: () => existsSync(join(
|
|
256
|
+
isAuthenticated: (env) => existsSync(join(harnessHome("grok", env), "auth.json")),
|
|
262
257
|
// `--append-system-prompt`/`--rules` are accepted by the CLI but do NOT
|
|
263
258
|
// reach the agent-stdio system prompt (verified against 1.0.0), so the
|
|
264
259
|
// persona is prepended codex-style.
|
|
@@ -9,6 +9,7 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { parse as parseYaml } from "yaml";
|
|
12
|
+
import { harnessHome } from "../../env-path.js";
|
|
12
13
|
import { resolveCli } from "../../procs.js";
|
|
13
14
|
import { decodeInjectId, hostApiKey, INJECT_SEP, localHost, mergeLocalInject } from "../local-inject.js";
|
|
14
15
|
import { createAcpDriver } from "./core.js";
|
|
@@ -25,9 +26,6 @@ export function bindHermesScreenshotCompat(env, modelId) {
|
|
|
25
26
|
env[HERMES_OPENMAUS_SCREENSHOT_COMPAT] = "1";
|
|
26
27
|
env[HERMES_OPENMAUS_SCREENSHOT_COMPAT_MODEL] = inject.model;
|
|
27
28
|
}
|
|
28
|
-
function hermesHome(env) {
|
|
29
|
-
return env.HERMES_HOME || join(env.HOME || env.USERPROFILE || homedir(), ".hermes");
|
|
30
|
-
}
|
|
31
29
|
function quoteYaml(value) {
|
|
32
30
|
if (/^[\w./:+-]+$/.test(value))
|
|
33
31
|
return value;
|
|
@@ -69,7 +67,7 @@ export function ensureHermesInjectProvider(modelId, env = process.env) {
|
|
|
69
67
|
const host = localHost(inject.host);
|
|
70
68
|
if (!host)
|
|
71
69
|
return modelId;
|
|
72
|
-
const dir =
|
|
70
|
+
const dir = env.HERMES_HOME || harnessHome("hermes", env);
|
|
73
71
|
mkdirSync(dir, { recursive: true });
|
|
74
72
|
const path = join(dir, "config.yaml");
|
|
75
73
|
let text = "";
|
|
@@ -199,7 +197,7 @@ function hermesConfigDefault(text) {
|
|
|
199
197
|
* catalog exactly as it was for local-only setups.
|
|
200
198
|
*/
|
|
201
199
|
export function hermesConfiguredModel(env = process.env) {
|
|
202
|
-
const dir =
|
|
200
|
+
const dir = env.HERMES_HOME || harnessHome("hermes", env);
|
|
203
201
|
let secrets = "";
|
|
204
202
|
try {
|
|
205
203
|
secrets = readFileSync(join(dir, ".env"), "utf8");
|
|
@@ -6,6 +6,7 @@ import { homedir } from "node:os";
|
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
8
8
|
import { createAcpDriver } from "./core.js";
|
|
9
|
+
import { titleCaseModelId } from "../../contracts.js";
|
|
9
10
|
import { execCli } from "../../procs.js";
|
|
10
11
|
const STATIC_MODELS = {
|
|
11
12
|
default: "opencode/x-preview-f-free",
|
|
@@ -21,11 +22,7 @@ let lastSuccessfulCatalog = null;
|
|
|
21
22
|
const MODEL_PROBE_TTL_MS = 30_000;
|
|
22
23
|
const modelProbeCache = new Map();
|
|
23
24
|
function labelForModel(id) {
|
|
24
|
-
return id
|
|
25
|
-
.split(/[-_.]+/g)
|
|
26
|
-
.filter(Boolean)
|
|
27
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
28
|
-
.join(" ");
|
|
25
|
+
return titleCaseModelId(id, /[-_.]+/);
|
|
29
26
|
}
|
|
30
27
|
function providerLabel(id) {
|
|
31
28
|
if (id === "opencode")
|
|
@@ -3,30 +3,20 @@
|
|
|
3
3
|
// Custom and are written into ~/.qwen/settings.json modelProviders.
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import { join } from "node:path";
|
|
7
|
+
import { qualifiedModelLabel } from "../../contracts.js";
|
|
8
|
+
import { harnessHome } from "../../env-path.js";
|
|
8
9
|
import { decodeInjectId, hostApiKey, localHost, mergeLocalInject } from "../local-inject.js";
|
|
9
10
|
import { createAcpDriver } from "./core.js";
|
|
10
11
|
const EMPTY = { default: "", options: [] };
|
|
11
|
-
function qwenHome(env) {
|
|
12
|
-
const home = process.platform === "win32"
|
|
13
|
-
? env.USERPROFILE || env.HOME || homedir()
|
|
14
|
-
: env.HOME || env.USERPROFILE || homedir();
|
|
15
|
-
return join(home, ".qwen");
|
|
16
|
-
}
|
|
17
12
|
function isRecord(value) {
|
|
18
13
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19
14
|
}
|
|
20
|
-
function qwenModelLabel(id, name) {
|
|
21
|
-
if (!name || name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()))
|
|
22
|
-
return id;
|
|
23
|
-
return `${id} — ${name}`;
|
|
24
|
-
}
|
|
25
15
|
const PROTOCOLS = new Set(["openai", "anthropic", "gemini", "vertex-ai"]);
|
|
26
16
|
function readQwenRoutes(env) {
|
|
27
17
|
let settings;
|
|
28
18
|
try {
|
|
29
|
-
settings = JSON.parse(readFileSync(join(
|
|
19
|
+
settings = JSON.parse(readFileSync(join(harnessHome("qwen", env), "settings.json"), "utf8"));
|
|
30
20
|
}
|
|
31
21
|
catch {
|
|
32
22
|
return [];
|
|
@@ -102,7 +92,11 @@ function readQwenRoutes(env) {
|
|
|
102
92
|
/** Public metadata only. Use Qwen's provider-qualified ACP selectors, not raw model IDs. */
|
|
103
93
|
export function readQwenModelCatalog(env = process.env) {
|
|
104
94
|
const options = readQwenRoutes(env).map(({ id, model, label, provider }) => ({
|
|
105
|
-
id,
|
|
95
|
+
id,
|
|
96
|
+
label: qualifiedModelLabel(model, label, {
|
|
97
|
+
redundantWhen: (id, name) => name.toLocaleLowerCase().endsWith(id.toLocaleLowerCase()),
|
|
98
|
+
}),
|
|
99
|
+
custom: true, provider,
|
|
106
100
|
}));
|
|
107
101
|
return { default: options[0]?.id ?? "", options };
|
|
108
102
|
}
|
|
@@ -117,7 +111,7 @@ export function ensureQwenInjectModel(modelId, env = process.env) {
|
|
|
117
111
|
const host = localHost(inject.host);
|
|
118
112
|
if (!host)
|
|
119
113
|
return modelId;
|
|
120
|
-
const dir =
|
|
114
|
+
const dir = harnessHome("qwen", env);
|
|
121
115
|
mkdirSync(dir, { recursive: true });
|
|
122
116
|
const path = join(dir, "settings.json");
|
|
123
117
|
let settings = {};
|