@chibitek/chibi 0.1.155 → 0.1.158
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/assets/{abnfDiagram-O67JEVCF-B8LdsCr1.js → abnfDiagram-O67JEVCF-B8-8zzMS.js} +1 -1
- package/dist/assets/{arc-DU5fSmKU.js → arc-CSHQ9Dx3.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-DdQSIFKv.js → architectureDiagram-NJMV4G6O-BCHM9rXW.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CaPcGYSS.js → blockDiagram-BEXU5L5S-B-5uxPFN.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-unt0ePgO.js → c4Diagram-YGBWAQC7-CuKNM_MR.js} +1 -1
- package/dist/assets/channel-bPN03jIt.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Babg5Fzu.js → chunk-2Q5K7J3B-BNF02GM4.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D1-dWVHo.js → chunk-3FUC2YCW-uEWgBd4M.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-DU7nSMbT.js → chunk-5VM5RSS4-eSZuXctY.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-QIjCJIhV.js → chunk-GTNCS2PH-CslS8nRS.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-pSByUE5R.js → chunk-GWA4HPMP-w71-MeKj.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Dj2AaR1b.js → chunk-JWPE2WC7-Bjju7WGh.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-pZbN0-FP.js → chunk-NETBCI7D-HxrroD4M.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-Bqarwe4D.js → chunk-WEXAMYUT-D7Rz470x.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-WV8lm3aa.js → chunk-XXDRQBXY-BWKnAeyM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BANBtky1.js → classDiagram-v2-NBCMYWYE-CeBDRHmP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CtrktgBC.js → cose-bilkent-JH36ORCC-BBbRKlvr.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DnhVVfba.js → cynefin-OW5HDTMX-W02cnJ0B.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-B42SRR6x.js → cynefinDiagram-VND7K2PF-Cs47eaxd.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DM--vFMK.js → dagre-6A5THRUB-8mG18hzT.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-ZBupBrPU.js → diagram-22UHCM2B-DF4Qrw67.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BPQzNIOd.js → diagram-3UASUU5V-3BAVoed1.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-5kO8vf8Z.js → diagram-ATOU4E4O-CKO3pECE.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtxogB86.js → diagram-CDSNMT55-CZhrGvur.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Co9-nYw_.js → diagram-MLGK6HIB-C07l9pdQ.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-DWRuMh5y.js → diagram-MPIPVDR6-D3or4lkZ.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DU_nmoG9.js → ebnfDiagram-ZINNZB2B-DV2epXp_.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bcje7S94.js → elk-276RUBZZ-DlvEn4-1.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-9aCo_2CI.js → erDiagram-OPXOYQCR-CB6X7Wt5.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-D4FYFyc5.js → flowDiagram-KWPJA3E3-C3bqz4lC.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-WQHX5rbx.js → ganttDiagram-FUAMR5RP-yo_Evhab.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DRyzBdiV.js → gitGraphDiagram-X574FWY7-Cq2oUlap.js} +1 -1
- package/dist/assets/{index-CG1OvRgO.js → index-CG4AFPb4.js} +1 -1
- package/dist/assets/{index-D_8SD_0K.js → index-CMcr5k3Z.js} +205 -200
- package/dist/assets/index-CZjRcUdn.css +1 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-D7SOmp4n.js → infoDiagram-VRGFBTTK-Y3ZrY7qL.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CdxOnDVX.js → ishikawaDiagram-OU5B5YK6-BpOtSfyi.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CH7477Oa.js → journeyDiagram-ZHPQQLJL-UQM727le.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BdpVvkHd.js → kanban-definition-PNTS6WVX-C3s5oHpY.js} +1 -1
- package/dist/assets/{linear-DTGq6qn5.js → linear-hX6nFOQr.js} +1 -1
- package/dist/assets/{mermaid.core-Sn2sv0Ti.js → mermaid.core-DZObGLN7.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-koCmkYuq.js → mindmap-definition-NLK3R4M7-Br-i2E3P.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-C3sqhm8e.js → pegDiagram-GJSIUBJH-D5SERB-N.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-BELBK122.js → pieDiagram-5QR66LMP-BPNf4J3D.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Cksi5LSm.js → quadrantDiagram-O4NWA36T-Bm_Wh7Jr.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-f1av_EZW.js → railroadDiagram-XR7U4H2S-D6bo0abD.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CW4O5nER.js → requirementDiagram-PLB6GJNP--1n5rkV7.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-Dz8ADHNY.js → sankeyDiagram-IPEJSGJF-Ct4tP2kK.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Cc3HE0iZ.js → sequenceDiagram-PO4LG4MO-CCG7O5fR.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-gdaZNHnK.js → sizeCapture-INFHLROL-CJJJra9c.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK--u8LCnab.js → stateDiagram-v2-GCMORJYK-BMdRvVEX.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-4MAw4kdO.js → swimlanes-2SLR337P-B1wnogip.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DWyjdGGR.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CL89YkCT.js → timeline-definition-EJHVYXUP-l-3yhXUP.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-C92l7YQe.js → usecaseDiagram-POWQR4AR-DtwtCOoJ.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-17siYhSj.js → vennDiagram-UO4OBE2U-BZAEce1B.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-CNwa9VmN.js → wardleyDiagram-VNRHLVJA-Na_BCMMO.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Dnu6Xzih.js → xychartDiagram-PMCCYNJV-BW9dlUfE.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +68 -14
- package/dist-server/index.js +5633 -4562
- package/dist-server/openmausbot.js +140 -81
- package/dist-server/pair-cli.js +140 -81
- package/dist-server/server/app-installers.js +107 -7
- package/dist-server/server/avatar-subscription.js +51 -0
- package/dist-server/server/cli-help.js +74 -0
- package/dist-server/server/cli.js +37 -78
- package/dist-server/server/colima-docker.js +136 -0
- package/dist-server/server/container-computer.js +31 -63
- package/dist-server/server/container-mcp.js +5 -0
- package/dist-server/server/device-routing.js +60 -0
- package/dist-server/server/drivers/claude.js +13 -11
- package/dist-server/server/drivers/local-inject.js +3 -4
- package/dist-server/server/engine-failover.js +22 -12
- package/dist-server/server/engine-tiers.js +5 -14
- package/dist-server/server/index.js +124 -120
- package/dist-server/server/personal-goal-runtime.js +86 -0
- package/dist-server/server/personal-goal-turn.js +125 -0
- package/dist-server/server/personal-goals.js +195 -0
- package/dist-server/server/port-in-use.js +21 -0
- package/dist-server/server/routes/local-models.js +3 -2
- package/dist-server/server/session-fallback.js +76 -0
- package/dist-server/server/skill-fetch.js +14 -0
- package/dist-server/server/vault/routes.js +43 -0
- package/dist-server/server/vault/runtime.js +48 -0
- package/dist-server/server/vault/store.js +177 -0
- package/dist-server/shared/desktop-private-port.js +1 -0
- package/dist-server/shared/engine-choices.js +15 -0
- package/dist-server/shared/engine-tiers.js +74 -2
- package/dist-server/shared/local-models.js +1 -3
- package/dist-server/shared/personal-goal.js +43 -0
- package/dist-server/shared/subscription-bots.js +3 -0
- package/dist-server/shared/vault.js +59 -0
- package/dist-server/vps-container-mcp.js +4 -2
- package/package.json +1 -1
- package/dist/assets/channel-GBaPOaz-.js +0 -1
- package/dist/assets/index-C8YP7Zxj.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CamSa0NE.js +0 -8
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
// `cua-driver mcp` inside the container; this module never reimplements clicks,
|
|
7
7
|
// typing, screenshots, accessibility, or window discovery.
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
|
-
import { existsSync } from "node:fs";
|
|
10
9
|
import { createHash, randomBytes } from "node:crypto";
|
|
11
10
|
import { chmod, lstat, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
12
11
|
import { tmpdir } from "node:os";
|
|
13
|
-
import { dirname, join, resolve
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
13
|
import { promisify } from "node:util";
|
|
15
14
|
import { augmentedPath, resolveCliSpawn } from "./env-path.js";
|
|
15
|
+
import { colimaStartPlan, dockerEnvForColima, startColima } from "./colima-docker.js";
|
|
16
16
|
import { spawnOptionsFor } from "./spawn-command.js";
|
|
17
17
|
import { DATA_DIR } from "./config.js";
|
|
18
18
|
import { SPAWNED_PROXIES } from "./proxy-paths.js";
|
|
@@ -188,7 +188,11 @@ async function sh(cmd, args, timeout = 8000) {
|
|
|
188
188
|
timeout,
|
|
189
189
|
encoding: "utf8",
|
|
190
190
|
maxBuffer: 16 * 1024 * 1024,
|
|
191
|
-
|
|
191
|
+
// Every docker call on a Mac goes to Colima's socket, whatever docker
|
|
192
|
+
// context Docker Desktop may have left active.
|
|
193
|
+
env: cmd === "docker"
|
|
194
|
+
? dockerEnvForColima({ ...process.env, PATH: augmentedPath() })
|
|
195
|
+
: { ...process.env, PATH: augmentedPath() },
|
|
192
196
|
}));
|
|
193
197
|
return { stdout };
|
|
194
198
|
}
|
|
@@ -233,77 +237,36 @@ export async function containerRuntimeStatus(runner = sh, platform = process.pla
|
|
|
233
237
|
daemonUp: healthyIndex >= 0,
|
|
234
238
|
};
|
|
235
239
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const desktop = { label: "Docker Desktop", command: "open", args: ["-a", "Docker"], timeoutMs: DESKTOP_LAUNCH_TIMEOUT_MS };
|
|
243
|
-
if (context === "desktop-linux" && desktopApp)
|
|
244
|
-
return desktop;
|
|
245
|
-
if (await installed("colima", runner, platform)) {
|
|
246
|
-
// `colima start` on a running profile is a no-op, so an instance the
|
|
247
|
-
// person started with their own flags is left exactly as it is.
|
|
248
|
-
const profile = context.startsWith("colima-") ? context.slice("colima-".length) : "";
|
|
249
|
-
return {
|
|
250
|
-
label: "Colima",
|
|
251
|
-
command: "colima",
|
|
252
|
-
args: ["start", ...(profile ? ["--profile", profile] : [])],
|
|
253
|
-
timeoutMs: COLIMA_START_TIMEOUT_MS,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
return desktopApp ? desktop : null;
|
|
257
|
-
}
|
|
258
|
-
if (platform === "win32") {
|
|
259
|
-
const exe = win32.join(env.ProgramFiles ?? "C:\\Program Files", "Docker", "Docker", "Docker Desktop.exe");
|
|
260
|
-
// Start-Process returns at once; Docker Desktop keeps running on its own.
|
|
261
|
-
return appExists(exe)
|
|
262
|
-
? {
|
|
263
|
-
label: "Docker Desktop",
|
|
264
|
-
command: "powershell.exe",
|
|
265
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", `Start-Process -FilePath '${exe.replaceAll("'", "''")}'`],
|
|
266
|
-
timeoutMs: DESKTOP_LAUNCH_TIMEOUT_MS,
|
|
267
|
-
}
|
|
268
|
-
: null;
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
240
|
+
/** How this machine's stopped Docker engine is started, or null when Atomic
|
|
241
|
+
* must not try: Colima on a Mac where it is installed, never Docker Desktop
|
|
242
|
+
* on any platform (colima-docker.ts). Linux needs root for the daemon and
|
|
243
|
+
* Windows runs the Local VM on Podman, so neither is started here. */
|
|
244
|
+
export async function containerRuntimeStartPlan(runner = sh, platform = process.platform, env = process.env) {
|
|
245
|
+
return platform === "darwin" && await installed("colima", runner, platform) ? colimaStartPlan(env) : null;
|
|
271
246
|
}
|
|
272
247
|
let runtimeStartInFlight = null;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
248
|
+
let runtimeStartError = null;
|
|
249
|
+
/** Why the last start left the Local VM without an engine; null once docker answers. */
|
|
250
|
+
export const containerRuntimeError = () => runtimeStartError;
|
|
251
|
+
/** Start the person's installed Colima when docker is not answering, with a
|
|
252
|
+
* retry and a restart, bounded (startColima). Never installs anything, never
|
|
253
|
+
* opens Docker Desktop, never touches Podman or Apple container. One start at
|
|
254
|
+
* a time: concurrent callers (startup and a turn) share the same attempt.
|
|
255
|
+
* Callers decide whether the person allowed it; this only does it. */
|
|
278
256
|
export function ensureContainerRuntime(opts = {}) {
|
|
279
257
|
if (runtimeStartInFlight)
|
|
280
258
|
return runtimeStartInFlight;
|
|
281
259
|
const attempt = (async () => {
|
|
282
260
|
const runner = opts.runner ?? sh;
|
|
283
261
|
const platform = opts.platform ?? process.platform;
|
|
284
|
-
const sleep = opts.sleep ?? ((ms) => new Promise((done) => setTimeout(done, ms)));
|
|
285
262
|
const before = await containerRuntimeStatus(runner, platform);
|
|
286
263
|
if (before.daemonUp || before.runtime !== "docker")
|
|
287
|
-
return { status: before, started: null };
|
|
288
|
-
const plan = await containerRuntimeStartPlan(runner, platform, opts.
|
|
289
|
-
|
|
290
|
-
return { status: before, started: null };
|
|
291
|
-
try {
|
|
292
|
-
await runner(plan.command, plan.args, plan.timeoutMs);
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
// Colima can exit non-zero while its VM is still coming up, and the
|
|
296
|
-
// wait below is the real test; a launch that truly failed times out.
|
|
297
|
-
}
|
|
298
|
-
const deadline = Date.now() + (opts.waitMs ?? 120_000);
|
|
299
|
-
let status = await containerRuntimeStatus(runner, platform);
|
|
300
|
-
while (!status.daemonUp && Date.now() < deadline) {
|
|
301
|
-
await sleep(opts.pollMs ?? 2000);
|
|
302
|
-
status = await containerRuntimeStatus(runner, platform);
|
|
303
|
-
}
|
|
304
|
-
return { status, started: plan.label };
|
|
264
|
+
return { status: before, started: null, error: null };
|
|
265
|
+
const plan = await containerRuntimeStartPlan(runner, platform, opts.env);
|
|
266
|
+
return startColima(plan, before, { runner, platform, status: () => containerRuntimeStatus(runner, platform), ...opts });
|
|
305
267
|
})();
|
|
306
268
|
runtimeStartInFlight = attempt;
|
|
269
|
+
void attempt.then((outcome) => { runtimeStartError = outcome.status.daemonUp ? null : outcome.error; }, () => { });
|
|
307
270
|
void attempt.finally(() => { if (runtimeStartInFlight === attempt)
|
|
308
271
|
runtimeStartInFlight = null; }).catch(() => { });
|
|
309
272
|
return attempt;
|
|
@@ -989,6 +952,11 @@ export function containerComputerMcp(runtime, control, target = SHARED_LOCAL_VM_
|
|
|
989
952
|
// through `ps` for the life of the bridge.
|
|
990
953
|
env: {
|
|
991
954
|
ELECTRON_RUN_AS_NODE: "1",
|
|
955
|
+
// The bridge pins docker to Colima itself too (container-mcp.ts); this
|
|
956
|
+
// keeps a host that spawns it with a scrubbed environment on Colima.
|
|
957
|
+
...(runtime === "docker" && process.platform === "darwin"
|
|
958
|
+
? { DOCKER_HOST: dockerEnvForColima(process.env, "darwin").DOCKER_HOST ?? "" }
|
|
959
|
+
: {}),
|
|
992
960
|
...(control ? { OMB_CONTROL_URL: control.url, OMB_CONTROL_TOKEN: control.token } : {}),
|
|
993
961
|
// Where the bridge remembers Cua Driver's tool list, and whether it
|
|
994
962
|
// must stand in for Cua Driver until the first tool call (the VM is
|
|
@@ -1011,7 +979,7 @@ export function setupCommands(runtime, platform = process.platform, target = SHA
|
|
|
1011
979
|
: runtime === "podman" && platform !== "linux"
|
|
1012
980
|
? "podman machine init; podman machine start"
|
|
1013
981
|
: runtime === "docker" && platform === "darwin"
|
|
1014
|
-
? "colima start
|
|
982
|
+
? "colima start"
|
|
1015
983
|
: runtime === "docker" && platform === "linux"
|
|
1016
984
|
? "sudo systemctl start docker"
|
|
1017
985
|
: null;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// piping, drain-safe exit, and watchdog live in mcp-bridge.ts, shared with
|
|
4
4
|
// the VPS entry point.
|
|
5
5
|
import { cuaExecArgs } from "./container-computer.js";
|
|
6
|
+
import { dockerEnvForColima } from "./colima-docker.js";
|
|
7
|
+
import { augmentedPath } from "./env-path.js";
|
|
6
8
|
import { runMcpBridge } from "./mcp-bridge.js";
|
|
7
9
|
const [runtime, container, socket] = process.argv.slice(2);
|
|
8
10
|
if (!runtime || !["docker", "podman", "container"].includes(runtime)) {
|
|
@@ -27,6 +29,9 @@ runMcpBridge({
|
|
|
27
29
|
command: runtime,
|
|
28
30
|
args: cuaExecArgs(["mcp", "--socket", socket], { container, interactive: true }),
|
|
29
31
|
label: "Cua Driver",
|
|
32
|
+
// On a Mac, docker always talks to Colima's socket, never to whatever
|
|
33
|
+
// context Docker Desktop left active.
|
|
34
|
+
...(runtime === "docker" ? { env: dockerEnvForColima({ ...process.env, PATH: augmentedPath() }) } : {}),
|
|
30
35
|
// No liveness watchdog: the runtime CLI talks to a local daemon and fails
|
|
31
36
|
// fast on its own — there is no silent WAN peer to wedge on.
|
|
32
37
|
...(controlUrl && controlToken ? { gate: { url: controlUrl, token: controlToken } } : {}),
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Device routing for approval requests (docs/specs/remote-approval/spec.md).
|
|
2
|
+
//
|
|
3
|
+
// The server tracks which user endpoint is live — the desktop's event
|
|
4
|
+
// stream or a paired phone's — and an approval request routes to the live
|
|
5
|
+
// user device. A remote session (no live desktop stream) routes to the
|
|
6
|
+
// phone automatically. It never broadcasts: the target is one device kind,
|
|
7
|
+
// picked here, and every client learns it from the stream's hello frame.
|
|
8
|
+
/** Where an approval goes. The desktop wins whenever it is live, so a
|
|
9
|
+
* person sitting at the Mac is never interrupted on the phone; a remote
|
|
10
|
+
* session routes to the phone; nothing live still answers "phone" with
|
|
11
|
+
* live: false, so the ask is queued for the one device that can travel to
|
|
12
|
+
* the person rather than broadcast. */
|
|
13
|
+
export function pickApprovalRoute(liveness) {
|
|
14
|
+
if (liveness.desktop > 0)
|
|
15
|
+
return { device: "desktop", live: true };
|
|
16
|
+
if (liveness.phone > 0)
|
|
17
|
+
return { device: "phone", live: true };
|
|
18
|
+
return { device: "phone", live: false };
|
|
19
|
+
}
|
|
20
|
+
/** The server's count of live user streams by device kind. Written by the
|
|
21
|
+
* SSE route's add/remove, read when an approval is raised or a client
|
|
22
|
+
* asks. Counts, never identities: routing is about which kind of device is
|
|
23
|
+
* present, not who is watching. */
|
|
24
|
+
export class DeviceRouteRegistry {
|
|
25
|
+
liveness = { desktop: 0, phone: 0 };
|
|
26
|
+
/** A stream opened. `kind` is the connection's auth kind: loopback is
|
|
27
|
+
* the desktop app on this machine; a paired session is a phone. */
|
|
28
|
+
streamOpened(kind) {
|
|
29
|
+
if (kind === "loopback")
|
|
30
|
+
this.liveness.desktop += 1;
|
|
31
|
+
else if (kind === "session")
|
|
32
|
+
this.liveness.phone += 1;
|
|
33
|
+
}
|
|
34
|
+
streamClosed(kind) {
|
|
35
|
+
if (kind === "loopback")
|
|
36
|
+
this.liveness.desktop = Math.max(0, this.liveness.desktop - 1);
|
|
37
|
+
else if (kind === "session")
|
|
38
|
+
this.liveness.phone = Math.max(0, this.liveness.phone - 1);
|
|
39
|
+
}
|
|
40
|
+
route() {
|
|
41
|
+
return pickApprovalRoute(this.liveness);
|
|
42
|
+
}
|
|
43
|
+
counts() {
|
|
44
|
+
return { ...this.liveness };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** End one stream: drop it from the fan-out set, end the response, and
|
|
48
|
+
* tell approval routing the device went away. Every removal path goes
|
|
49
|
+
* through here so the routing counts cannot drift from the stream set. */
|
|
50
|
+
export function removeStream(clients, registry, client) {
|
|
51
|
+
if (!clients.delete(client))
|
|
52
|
+
return;
|
|
53
|
+
registry.streamClosed(client.authKind);
|
|
54
|
+
try {
|
|
55
|
+
client.res.end();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* already gone */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1615,6 +1615,8 @@ export const ClaudeDriver = {
|
|
|
1615
1615
|
// A settled turn owns no retry budget. Retained CLI sessions may run
|
|
1616
1616
|
// many later turns on this thread, and each must start fresh.
|
|
1617
1617
|
retryState.delete(threadId);
|
|
1618
|
+
if (t.authFailed)
|
|
1619
|
+
closeSession(threadId, "sign-in required");
|
|
1618
1620
|
emit({ ...base(threadId, t.turnId), type: "turn.completed", ok, stopReason, cost, ...(usage ? { usage } : {}) });
|
|
1619
1621
|
if (session.child.exitCode === null && !session.closing)
|
|
1620
1622
|
armIdle(threadId);
|
|
@@ -1772,11 +1774,7 @@ export const ClaudeDriver = {
|
|
|
1772
1774
|
case "assistant": {
|
|
1773
1775
|
const msg = o.message ?? {};
|
|
1774
1776
|
const text = firstText(msg.content);
|
|
1775
|
-
//
|
|
1776
|
-
// only content is the CLI's own "run /login" instruction — a
|
|
1777
|
-
// command this app has no terminal to run, so relaying it as a
|
|
1778
|
-
// reply strands the user. Every other engine reports this as a
|
|
1779
|
-
// setup error; that is what routes them to the sign-in card.
|
|
1777
|
+
// CLI auth diagnostics enter the shared fallback path, never assistant text.
|
|
1780
1778
|
if (claudeAuthFailure(o, text)) {
|
|
1781
1779
|
if (session.turn)
|
|
1782
1780
|
session.turn.authFailed = true;
|
|
@@ -1853,12 +1851,16 @@ export const ClaudeDriver = {
|
|
|
1853
1851
|
if (o.origin?.kind === "task-notification")
|
|
1854
1852
|
break;
|
|
1855
1853
|
{
|
|
1856
|
-
//
|
|
1857
|
-
// so it is the turn's figure. cache reads count as input: they
|
|
1858
|
-
// are billed (at the cache rate) and they fill the window — but
|
|
1859
|
-
// they are reported separately too, so the UI can show how much
|
|
1860
|
-
// of the figure was context re-read rather than new text.
|
|
1854
|
+
// Result usage includes input, output and cached context.
|
|
1861
1855
|
const t = session.turn;
|
|
1856
|
+
// Some CLI failures exist only on the result, with no assistant frame.
|
|
1857
|
+
const resultError = [o.result, ...(Array.isArray(o.errors) ? o.errors : [])]
|
|
1858
|
+
.filter((value) => typeof value === "string").join("\n");
|
|
1859
|
+
if (o.is_error === true && !t?.authFailed && claudeAuthFailure({ error: o.error ?? "api_error" }, resultError)) {
|
|
1860
|
+
if (t)
|
|
1861
|
+
t.authFailed = true;
|
|
1862
|
+
emit({ ...base(threadId, currentTurnId()), type: "runtime.error", message: resultError, setup: true, failure: "auth_failed" });
|
|
1863
|
+
}
|
|
1862
1864
|
const usage = o.usage
|
|
1863
1865
|
? {
|
|
1864
1866
|
input: (o.usage.input_tokens || 0) + (o.usage.cache_read_input_tokens || 0) + (o.usage.cache_creation_input_tokens || 0),
|
|
@@ -1870,7 +1872,7 @@ export const ClaudeDriver = {
|
|
|
1870
1872
|
: undefined;
|
|
1871
1873
|
const earlier = t?.carried;
|
|
1872
1874
|
const result = {
|
|
1873
|
-
ok: o.is_error !== true,
|
|
1875
|
+
ok: o.is_error !== true && !t?.authFailed,
|
|
1874
1876
|
stopReason: t?.authFailed ? "auth_required"
|
|
1875
1877
|
: t?.refreshConflict && o.is_error === true ? "credential_refresh_conflict"
|
|
1876
1878
|
: claudeResultStopReason(o),
|
|
@@ -213,9 +213,8 @@ function idsFromModelsPayload(payload) {
|
|
|
213
213
|
const low = id.toLowerCase();
|
|
214
214
|
if (low.includes("embed") || low.includes("bge-") || low.includes("nomic"))
|
|
215
215
|
return [];
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
return [];
|
|
216
|
+
// Ollama Cloud tags a signed-in Ollama serves are listed too (Erick,
|
|
217
|
+
// 2026-10-24); the label says they bill a plan.
|
|
219
218
|
return [id];
|
|
220
219
|
});
|
|
221
220
|
}
|
|
@@ -349,7 +348,7 @@ export async function probeLocalInjects(env = process.env, fetchImpl = fetch) {
|
|
|
349
348
|
id: encodeInjectId(host.id, model),
|
|
350
349
|
host: host.id,
|
|
351
350
|
model,
|
|
352
|
-
label: `${model} (${host.label})`,
|
|
351
|
+
label: isOllamaCloudModel(model) ? `${model} (${host.label}, plan-billed)` : `${model} (${host.label})`,
|
|
353
352
|
loaded: loaded.has(model),
|
|
354
353
|
...(contextWindow ? { contextWindow } : {}),
|
|
355
354
|
});
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { classifyEngineFailure, isCredentialDead, mayBeReplyFailure, replyEngineFailure, statedReset } from "./engine-failure.js";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/** The two-try cap: at most this many engines take part in one turn, the
|
|
3
|
+
* first and one switch. Every failed attempt counts toward it, whatever the
|
|
4
|
+
* failure was (a usage limit, a sign-in, a stall, or an error no pattern
|
|
5
|
+
* recognises). When the second engine fails too, the turn stops with its
|
|
6
|
+
* one plain line rather than walking the rest of the ladder. */
|
|
7
|
+
export const MAX_ENGINES_PER_TURN = 2;
|
|
8
|
+
/** What the engine said when it ended a turn failed without any words. */
|
|
9
|
+
export const NO_ANSWER_FAILURE = "The engine ended the turn without an answer.";
|
|
10
|
+
/** Any failure counts: a failed end that no pattern recognises is a
|
|
11
|
+
* provider_error, whether or not part of the answer or a tool run already
|
|
12
|
+
* reached the person (the next engine continues from there, told what was
|
|
13
|
+
* said and which actions ran, as for a usage limit) and whether or not the
|
|
14
|
+
* engine said why. */
|
|
10
15
|
function unrecognisedFailure(turn) {
|
|
11
|
-
|
|
12
|
-
return undefined;
|
|
13
|
-
return { kind: "provider_error", message: turn.lastError };
|
|
16
|
+
return { kind: "provider_error", message: turn.lastError ?? NO_ANSWER_FAILURE };
|
|
14
17
|
}
|
|
15
18
|
/** Ways a turn ends failed that are not the engine's fault: the person (or
|
|
16
19
|
* the harness on their behalf) stopped it. Every other failed end moves the
|
|
@@ -196,7 +199,7 @@ export const ACCOUNTS_HINT = "Fix it in Settings > AI & accounts.";
|
|
|
196
199
|
* engines passed over because they cannot work on the turn's "Works on"
|
|
197
200
|
* place (`place`, "this computer"). When an account needs the person (a
|
|
198
201
|
* sign-in, an expired key), the line says where to fix it. */
|
|
199
|
-
export function restingLine(engines, formatTime, computer) {
|
|
202
|
+
export function restingLine(engines, formatTime, computer, options = {}) {
|
|
200
203
|
const seen = new Set();
|
|
201
204
|
const listed = engines.filter((engine) => !seen.has(engine.label) && seen.add(engine.label));
|
|
202
205
|
const parts = listed.map((engine) => (engine.keyExpired ? `${engine.label}'s API key has expired`
|
|
@@ -208,6 +211,13 @@ export function restingLine(engines, formatTime, computer) {
|
|
|
208
211
|
.map((label) => `${label} can't control ${computer.place}`);
|
|
209
212
|
if (cannot.length)
|
|
210
213
|
return `No engine that can work on ${computer.place} is free, so this turn stopped: ${[...parts, ...cannot].join("; ")}.${hint}`;
|
|
214
|
+
// The two-try cap, not an empty ladder: say so, with what each engine hit.
|
|
215
|
+
if (options.capped) {
|
|
216
|
+
const count = MAX_ENGINES_PER_TURN === 2 ? "two" : String(MAX_ENGINES_PER_TURN);
|
|
217
|
+
return parts.length
|
|
218
|
+
? `This turn stopped after ${count} engines failed: ${parts.join(", ")}.${hint}`
|
|
219
|
+
: `This turn stopped after ${count} engines failed.`;
|
|
220
|
+
}
|
|
211
221
|
if (parts.length === 0)
|
|
212
222
|
return "No engine is available for this turn, so it stopped.";
|
|
213
223
|
if (parts.length === 1)
|
|
@@ -3,21 +3,11 @@
|
|
|
3
3
|
// engine of its ladder that is ready. Everything here is pure except the
|
|
4
4
|
// snapshot cache, so the server wires it to the live registry and the tests
|
|
5
5
|
// drive it with plain candidates.
|
|
6
|
-
import { BOT_TIERS, DEFAULT_TIER_CLASSES, MODEL_CLASSES, classBelow, isBotTier, toModelClass } from "../shared/engine-tiers.js";
|
|
6
|
+
import { BOT_TIERS, DEFAULT_LADDERS, DEFAULT_TIER_CLASSES, MODEL_CLASSES, classBelow, isBotTier, toModelClass } from "../shared/engine-tiers.js";
|
|
7
7
|
import { modelHost } from "../shared/engine-choices.js";
|
|
8
|
+
export { DEFAULT_LADDERS };
|
|
8
9
|
import { subscriptionProviderForDriver } from "../shared/subscription-bots.js";
|
|
9
10
|
import { ATOMIC_ENGINE_INSTANCE, engineIntent, localModel, modelBilling, modelProvider, paidCandidate, perTokenModel } from "./harness-import/engine-policy.js";
|
|
10
|
-
/** Hand-written ladders, used only when no model has a class yet (the
|
|
11
|
-
* server always has the bundled baseline, so in practice the generated
|
|
12
|
-
* ladders below apply). Pay-per-token engines need no rung: they form the
|
|
13
|
-
* automatic last band of every ladder (resolveLadder). */
|
|
14
|
-
export const DEFAULT_LADDERS = {
|
|
15
|
-
orchestrator: [{ engine: "claudeAgent" }, { engine: "atomic-engine" }, { engine: "codex" }],
|
|
16
|
-
engineering: [{ engine: "claudeAgent" }, { engine: "codex" }, { engine: "cursorAgent" }, { engine: "kimiAgent" }],
|
|
17
|
-
research: [{ engine: "grokAgent" }, { engine: "antigravityAgent" }, { engine: "kimiAgent" }],
|
|
18
|
-
writing: [{ engine: "antigravityAgent" }, { engine: "kimiAgent" }, { engine: "codex" }, { engine: "claudeAgent" }],
|
|
19
|
-
routine: [{ engine: "qwenAgent" }, { engine: "kimiAgent" }, { engine: "antigravityAgent" }],
|
|
20
|
-
};
|
|
21
11
|
/** Engines a tier tries first, whatever the class catalog order says: Chibi
|
|
22
12
|
* runs on Claude (Erick, 2026-10-13: open-weight models broke its
|
|
23
13
|
* orchestration). Its ladder starts with Claude's best model graded in its
|
|
@@ -379,7 +369,7 @@ export function resolveFailover(request) {
|
|
|
379
369
|
const ids = candidate.models.options.map((option) => option.id);
|
|
380
370
|
const multi = ids.some((id) => modelProvider(candidate, id) !== undefined);
|
|
381
371
|
// (A local host's model, Ollama Cloud tags included, is not one of them:
|
|
382
|
-
//
|
|
372
|
+
// it joins when a ladder names it, else by Ollama Cloud or local rules.)
|
|
383
373
|
const models = multi ? ids.filter((id) => modelProvider(candidate, id) !== undefined && modelBilling(candidate, id) === "subscription")
|
|
384
374
|
: candidate.models.default && modelBilling(candidate, candidate.models.default) === undefined ? [candidate.models.default] : [];
|
|
385
375
|
for (const model of models) {
|
|
@@ -410,7 +400,8 @@ export function resolveFailover(request) {
|
|
|
410
400
|
return "tried";
|
|
411
401
|
if (!keys && (keyBased(candidate) || perTokenModel(candidate, model)))
|
|
412
402
|
return "key-later";
|
|
413
|
-
|
|
403
|
+
// a ladder-named local model is a normal rung (Erick, 2026-10-24)
|
|
404
|
+
if (!named && request.isLocal?.(candidate, model) && !(request.localAllowed && modelClass === "Añejo"))
|
|
414
405
|
return "local";
|
|
415
406
|
const step = named ? ladder.indexOf(entry) : ladder.length;
|
|
416
407
|
return effective(candidate, model, named ? entry : undefined, step) === modelClass ? undefined : "class";
|