@chibitek/chibi 0.1.169 → 0.1.171
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-aQkRN0ee.js → abnfDiagram-O67JEVCF-DrPBKmVh.js} +1 -1
- package/dist/assets/{arc-BT7qPVAa.js → arc-BEIChVSB.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-oMlARqKW.js → architectureDiagram-NJMV4G6O-Da60nTzs.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CKUrb4js.js → blockDiagram-BEXU5L5S-_NelwIQj.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-BkL5cRIA.js → c4Diagram-YGBWAQC7-DUdkQ3dy.js} +1 -1
- package/dist/assets/channel-DEJSSlVQ.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BMitkm2s.js → chunk-2Q5K7J3B-BQgYAptM.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-DOhVN2ed.js → chunk-3FUC2YCW-XXOXwcoW.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BFkFo_ng.js → chunk-5VM5RSS4-CUyMXCSt.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-QbxYUf3Q.js → chunk-GTNCS2PH-BwctbINm.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BZChfTYw.js → chunk-GWA4HPMP-DQ0bZK59.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BXpmYglA.js → chunk-JWPE2WC7-BADCk9AK.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CIZTrz1i.js → chunk-NETBCI7D-CoJxabSR.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-FhqQ0sO0.js → chunk-WEXAMYUT-fupByVJn.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-5ThtmFbP.js → chunk-XXDRQBXY-BavbGHGi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BzObC5Qw.js → classDiagram-v2-NBCMYWYE-B5wfUNKJ.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BASV9Okv.js → cose-bilkent-JH36ORCC-DlYFmKDe.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-B0zQYMFh.js → cynefin-OW5HDTMX-Y0U-21MH.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DMEO1cHA.js → cynefinDiagram-VND7K2PF-D1Fh1sU0.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BG04TSi_.js → dagre-6A5THRUB-BNU-Trog.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-CfpVNMhg.js → diagram-22UHCM2B-Bjie4nDQ.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-DG9L4I1L.js → diagram-3UASUU5V-Qnbrq1T5.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Db7Bf63S.js → diagram-ATOU4E4O-CNJasPCN.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-Cdzv1tJn.js → diagram-CDSNMT55-BAzGZBKX.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-BBMeXGKE.js → diagram-MLGK6HIB-I8ACRaw-.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-BAQ22zk3.js → diagram-MPIPVDR6-nLlrAEPz.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BDZgSFSF.js → ebnfDiagram-ZINNZB2B-DF9BG3pH.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bx4-nN8N.js → elk-276RUBZZ-C1AJYKMV.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-D2Pn-cqm.js → erDiagram-OPXOYQCR-MNNcRrPy.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-hsbvL3Ow.js → flowDiagram-KWPJA3E3-C7mfBfgt.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-DBUgYe4p.js → ganttDiagram-FUAMR5RP-a9zNyDbb.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-ChGeSINg.js → gitGraphDiagram-X574FWY7-OL8NhUsO.js} +1 -1
- package/dist/assets/{index-ByXr4FRl.js → index-DojGBgbN.js} +167 -164
- package/dist/assets/{index-Ch93Ygtq.js → index-xypOVpVT.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-DY2EeC-A.js → infoDiagram-VRGFBTTK-CF-3pzUZ.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BznYuY04.js → ishikawaDiagram-OU5B5YK6-B2HqD-sv.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CX74ZLbW.js → journeyDiagram-ZHPQQLJL-84gzOTb9.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-0DPavB-t.js → kanban-definition-PNTS6WVX-D7Ee23QC.js} +1 -1
- package/dist/assets/{linear-BHeqbvM8.js → linear-CdAXsmfc.js} +1 -1
- package/dist/assets/{mermaid.core-Dm-Ny6a0.js → mermaid.core-CE_M1v1e.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-BPrMElav.js → mindmap-definition-NLK3R4M7-KSTIET9y.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-CaD7wyyX.js → pegDiagram-GJSIUBJH-C7ogvddb.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-lbSp8R4K.js → pieDiagram-5QR66LMP-Bp1CgCrM.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Bs3Sn2of.js → quadrantDiagram-O4NWA36T-Bw7aN9Lh.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-C88ykXCV.js → railroadDiagram-XR7U4H2S-Dij4U4NY.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-mgIQnORW.js → requirementDiagram-PLB6GJNP-DOxTQvCl.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-K5esEw-m.js → sankeyDiagram-IPEJSGJF-DPCUmNbV.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-D5v8PJqQ.js → sequenceDiagram-PO4LG4MO-DPjC8zGe.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BHBBDTEI.js → sizeCapture-INFHLROL-WlmsNlh-.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-CeRdNmKQ.js → stateDiagram-v2-GCMORJYK-BwMPiapI.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-D8CbMCI8.js → swimlanes-2SLR337P-Cyja7ZPb.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-Dh25zxzq.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CMG02UV1.js → timeline-definition-EJHVYXUP-BehkSauz.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-Bm8h6ijK.js → usecaseDiagram-POWQR4AR-koUjcmpW.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-Dk9tFjlM.js → vennDiagram-UO4OBE2U-Ch8TXIpy.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-61vrb2Ft.js → wardleyDiagram-VNRHLVJA-Dm5kC4c2.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-C7uO44ng.js → xychartDiagram-PMCCYNJV-YA-1zyQz.js} +1 -1
- package/dist/handbook/atomic-handbook.html +1 -1
- package/dist/index.html +1 -1
- package/dist-server/companion/src/routes.js +28 -1
- package/dist-server/container-mcp.js +1 -0
- package/dist-server/hooks/plan-hook.js +22 -0
- package/dist-server/index.js +38538 -37905
- package/dist-server/local-computer.js +1 -0
- package/dist-server/openmausbot.js +389 -303
- package/dist-server/pair-cli.js +389 -303
- package/dist-server/proxy-paths.js +1 -0
- package/dist-server/server/atomic-engine-release.js +13 -13
- package/dist-server/server/bot-work-modes.js +212 -0
- package/dist-server/server/config.js +6 -5
- package/dist-server/server/drivers/acp/core.js +6 -4
- package/dist-server/server/drivers/acp/custom.js +51 -37
- package/dist-server/server/drivers/builtIn.js +2 -1
- package/dist-server/server/drivers/chat-mcp-tools.js +1 -0
- package/dist-server/server/drivers/claude.js +28 -25
- package/dist-server/server/drivers/codex-approval-presets.js +34 -0
- package/dist-server/server/drivers/codex.js +11 -35
- package/dist-server/server/drivers/openai-chat.js +4 -3
- package/dist-server/server/hooks/plan-hook.js +17 -0
- package/dist-server/server/index.js +92 -175
- package/dist-server/server/mcp-interactive-probe.js +60 -0
- package/dist-server/server/mcp-remote-bridge.js +13 -6
- package/dist-server/server/message-file.js +28 -11
- package/dist-server/server/plan-policy.js +14 -0
- package/dist-server/server/proxy-paths.js +1 -0
- package/dist-server/server/remote-desktop.js +142 -0
- package/dist-server/server/routes/message-file.js +115 -0
- package/dist-server/server/shared-deliverables.js +109 -0
- package/dist-server/server/skill-learn.js +4 -0
- package/dist-server/server/store.js +4 -4
- package/dist-server/server/subscription-bots.js +3 -2
- package/dist-server/shared/approval-mode.js +3 -1
- package/dist-server/shared/engine-choices.js +1 -1
- package/dist-server/shared/provider-accounts.js +5 -5
- package/dist-server/shared/skill-command.js +16 -0
- package/dist-server/vps-container-mcp.js +1 -0
- package/package.json +1 -1
- package/dist/assets/channel-BZDQCBTd.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CQHcTGnI.js +0 -8
|
@@ -343,16 +343,25 @@ function mimeFor(path) {
|
|
|
343
343
|
default: return "application/octet-stream";
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
|
-
|
|
347
|
-
* Open a message-linked file under one of the supplied roots. The returned
|
|
348
|
-
* handle, not the pathname, is what the caller streams. We canonicalise both
|
|
349
|
-
* before and after opening and compare the handle with the post-open path;
|
|
350
|
-
* this rejects symlink escapes and directory swaps instead of checking a
|
|
351
|
-
* path and then opening a potentially different file.
|
|
352
|
-
*/
|
|
353
|
-
export async function openMessageFile(href, roots) {
|
|
346
|
+
export async function openMessageFile(href, roots, access = {}) {
|
|
354
347
|
const requested = referencedPath(href);
|
|
355
|
-
const
|
|
348
|
+
const canonicalize = async (paths) => Promise.all(paths.map(async (path) => {
|
|
349
|
+
try {
|
|
350
|
+
return await realpath(path);
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return resolve(path);
|
|
354
|
+
}
|
|
355
|
+
}));
|
|
356
|
+
const blocked = [...(access.blockedRoots ?? []), ...await canonicalize(access.blockedRoots ?? [])];
|
|
357
|
+
const sharedBlocked = [...(access.sharedBlockedRoots ?? []), ...await canonicalize(access.sharedBlockedRoots ?? [])];
|
|
358
|
+
const isBlocked = (paths, candidate) => paths.some((root) => containedBy(resolve(root), candidate));
|
|
359
|
+
// Relative links keep their original conversation-relative meaning.
|
|
360
|
+
const shared = isAbsolute(requested) ? access.sharedRoots ?? [] : [];
|
|
361
|
+
const boundaries = await canonicalize(access.sharedRootBoundaries ?? []);
|
|
362
|
+
const canonicalShared = (await canonicalize(shared)).filter(root => !boundaries.some(boundary => containedBy(root, boundary)));
|
|
363
|
+
const canonicalOrdinary = await canonicalize(roots);
|
|
364
|
+
const canonicalRoots = (await Promise.all([...roots, ...canonicalShared].map(async (root) => {
|
|
356
365
|
try {
|
|
357
366
|
const canonical = await realpath(root);
|
|
358
367
|
return (await stat(canonical)).isDirectory() ? canonical : null;
|
|
@@ -375,7 +384,15 @@ export async function openMessageFile(href, roots) {
|
|
|
375
384
|
catch {
|
|
376
385
|
continue;
|
|
377
386
|
}
|
|
378
|
-
const
|
|
387
|
+
const permitted = (canonical) => {
|
|
388
|
+
if (isBlocked(blocked, candidate) || isBlocked(blocked, canonical))
|
|
389
|
+
return false;
|
|
390
|
+
if (canonicalOrdinary.some((root) => containedBy(root, canonical)))
|
|
391
|
+
return true;
|
|
392
|
+
return canonicalShared.some((root) => containedBy(root, canonical))
|
|
393
|
+
&& !isBlocked(sharedBlocked, candidate) && !isBlocked(sharedBlocked, canonical);
|
|
394
|
+
};
|
|
395
|
+
const root = permitted(canonicalBefore) && canonicalRoots.find((allowed) => containedBy(allowed, canonicalBefore));
|
|
379
396
|
if (!root) {
|
|
380
397
|
sawOutsideRoot = true;
|
|
381
398
|
continue;
|
|
@@ -391,7 +408,7 @@ export async function openMessageFile(href, roots) {
|
|
|
391
408
|
throw statusError(413, `file exceeds ${MESSAGE_FILE_MAX_BYTES} bytes`);
|
|
392
409
|
}
|
|
393
410
|
const canonicalAfter = await realpath(candidate);
|
|
394
|
-
if (!containedBy(root, canonicalAfter))
|
|
411
|
+
if (!containedBy(root, canonicalAfter) || !permitted(canonicalAfter))
|
|
395
412
|
throw statusError(403, "the linked file is outside this conversation's workspace");
|
|
396
413
|
const after = await stat(canonicalAfter);
|
|
397
414
|
if (opened.dev !== after.dev || opened.ino !== after.ino) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Atomic Engineering, 2026-10-31. Harness-owned Plan tool policy.
|
|
2
|
+
// Unknown tools, MCP annotations and shell command strings are never trusted.
|
|
3
|
+
export const PLAN_READ_TOOLS = ["Read", "Glob", "Grep"];
|
|
4
|
+
export const PLAN_REFUSAL = "Plan mode blocked this action. Review the plan and approve it in Atomic before making changes.";
|
|
5
|
+
export function planToolAllowed(tool) {
|
|
6
|
+
return typeof tool === "string" && PLAN_READ_TOOLS.includes(tool);
|
|
7
|
+
}
|
|
8
|
+
export function claudePlanHook(node, helper) {
|
|
9
|
+
const quote = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
10
|
+
const command = process.platform === "win32"
|
|
11
|
+
? '"%OMB_PLAN_NODE%" "%OMB_PLAN_HOOK%" || exit /b 2'
|
|
12
|
+
: `${quote(node)} ${quote(helper)} || exit 2`;
|
|
13
|
+
return [{ matcher: "", hooks: [{ type: "command", command, timeout: 5 }] }];
|
|
14
|
+
}
|
|
@@ -44,6 +44,7 @@ export const SPAWNED_PROXIES = {
|
|
|
44
44
|
hook: resolveProxy("hooks/omb-hook"),
|
|
45
45
|
// the RTK add-on's Claude PreToolUse hook (bypassPermissions turns only)
|
|
46
46
|
rtkHook: resolveProxy("hooks/rtk-hook"),
|
|
47
|
+
planHook: resolveProxy("hooks/plan-hook"),
|
|
47
48
|
// Loaded by the external `pi` process via `-e`, not by this server — but
|
|
48
49
|
// resolved through the same single source of truth so the packaged layout
|
|
49
50
|
// check can assert it ships.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// This Mac from the phone: a picture of the whole screen, and taps, keys and
|
|
2
|
+
// typing sent back through the same Cua Driver the bots already use. No VNC
|
|
3
|
+
// server and no second sign-in. The gates are the companion's per-device
|
|
4
|
+
// remote desktop capability and the person's Take control, which pauses the
|
|
5
|
+
// bot so a tap never races its next action.
|
|
6
|
+
//
|
|
7
|
+
// ponytail: one still frame per request (about 1 per second on a Retina Mac).
|
|
8
|
+
// A streamed video path is the upgrade if watching motion ever matters.
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { json, readBody } from "./harness/http.js";
|
|
16
|
+
import { readCuaConnection, readCuaUnavailableReason } from "./local-computer.js";
|
|
17
|
+
const run = promisify(execFile);
|
|
18
|
+
const coordinate = z.number().finite().min(0).max(100_000);
|
|
19
|
+
const key = z.string().regex(/^[a-z0-9]{1,12}$/i);
|
|
20
|
+
const DESKTOP = { kind: "desktop", display_id: "primary" };
|
|
21
|
+
export const remoteInputSchema = z.discriminatedUnion("type", [
|
|
22
|
+
z.object({
|
|
23
|
+
type: z.literal("click"),
|
|
24
|
+
x: coordinate,
|
|
25
|
+
y: coordinate,
|
|
26
|
+
button: z.enum(["left", "right"]).default("left"),
|
|
27
|
+
count: z.number().int().min(1).max(2).default(1),
|
|
28
|
+
}),
|
|
29
|
+
z.object({ type: z.literal("type"), text: z.string().min(1).max(2_000) }),
|
|
30
|
+
z.object({
|
|
31
|
+
type: z.literal("key"),
|
|
32
|
+
key,
|
|
33
|
+
modifiers: z.array(z.enum(["cmd", "shift", "option", "ctrl"])).max(4).default([]),
|
|
34
|
+
}),
|
|
35
|
+
z.object({
|
|
36
|
+
type: z.literal("scroll"),
|
|
37
|
+
x: coordinate,
|
|
38
|
+
y: coordinate,
|
|
39
|
+
direction: z.enum(["up", "down", "left", "right"]),
|
|
40
|
+
amount: z.number().int().min(1).max(20).default(3),
|
|
41
|
+
}),
|
|
42
|
+
]);
|
|
43
|
+
export function hostCuaCall(connection) {
|
|
44
|
+
const socket = connection.args[connection.args.indexOf("--socket") + 1];
|
|
45
|
+
if (!connection.args.includes("--socket") || !socket)
|
|
46
|
+
throw new Error("Cua Driver has no socket for this computer");
|
|
47
|
+
return async (tool, args, screenshotOut) => {
|
|
48
|
+
const { stdout } = await run(connection.command, ["call", tool, JSON.stringify(args), "--socket", socket, ...(screenshotOut ? ["--screenshot-out-file", screenshotOut] : [])], { env: { ...process.env, ...connection.env }, timeout: 15_000, maxBuffer: 4 * 1024 * 1024 });
|
|
49
|
+
return JSON.parse(stdout);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The whole primary display. `width`/`height` are the coordinate space
|
|
53
|
+
* clicks use; the image itself is a smaller JPEG to keep it phone-sized. */
|
|
54
|
+
export async function remoteFrame(call, shrink = sipsJpeg) {
|
|
55
|
+
const dir = await mkdtemp(join(tmpdir(), "atomic-remote-"));
|
|
56
|
+
try {
|
|
57
|
+
const png = join(dir, "screen.png");
|
|
58
|
+
const state = z.object({ screenshot_width: z.number().int().positive(), screenshot_height: z.number().int().positive() })
|
|
59
|
+
.parse(await call("get_desktop_state", {}, png));
|
|
60
|
+
const jpeg = join(dir, "screen.jpg");
|
|
61
|
+
await shrink(png, jpeg);
|
|
62
|
+
return {
|
|
63
|
+
image: `data:image/jpeg;base64,${(await readFile(jpeg)).toString("base64")}`,
|
|
64
|
+
width: state.screenshot_width,
|
|
65
|
+
height: state.screenshot_height,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
// The picture is the person's screen: never leave it in a temp folder.
|
|
70
|
+
await rm(dir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function sipsJpeg(input, output) {
|
|
74
|
+
await run("/usr/bin/sips", ["-s", "format", "jpeg", "-s", "formatOptions", "60", "-Z", "1600", input, "--out", output], { timeout: 15_000 });
|
|
75
|
+
}
|
|
76
|
+
/** Keys and typing go to the frontmost app, the one the person last tapped. */
|
|
77
|
+
async function frontmostPid(call) {
|
|
78
|
+
const { apps } = z.object({ apps: z.array(z.object({ pid: z.number().int().positive(), active: z.boolean().optional() }).passthrough()) })
|
|
79
|
+
.parse(await call("list_apps", {}));
|
|
80
|
+
const active = apps.find((app) => app.active);
|
|
81
|
+
if (!active)
|
|
82
|
+
throw Object.assign(new Error("No app is in front on this computer; tap a window first"), { status: 409 });
|
|
83
|
+
return active.pid;
|
|
84
|
+
}
|
|
85
|
+
export async function remoteInput(call, input) {
|
|
86
|
+
switch (input.type) {
|
|
87
|
+
case "click":
|
|
88
|
+
await call("click", { target: DESKTOP, x: input.x, y: input.y, button: input.button, count: input.count });
|
|
89
|
+
return;
|
|
90
|
+
case "scroll":
|
|
91
|
+
await call("scroll", { target: DESKTOP, x: input.x, y: input.y, direction: input.direction, amount: input.amount });
|
|
92
|
+
return;
|
|
93
|
+
case "type":
|
|
94
|
+
await call("type_text", { pid: await frontmostPid(call), text: input.text });
|
|
95
|
+
return;
|
|
96
|
+
case "key":
|
|
97
|
+
await call("press_key", { pid: await frontmostPid(call), key: input.key, modifiers: input.modifiers });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** `POST /api/bots/:id/computer/remote-frame|remote-input`. The companion
|
|
102
|
+
* admits these only for a device with remote desktop access; input also needs
|
|
103
|
+
* the person to hold Take control, which pauses the bot. */
|
|
104
|
+
export async function remoteDesktopRoute(path, method, req, res, deps) {
|
|
105
|
+
const m = path.match(/^\/api\/bots\/([\w-]+)\/computer\/remote-(frame|input)$/);
|
|
106
|
+
if (!m || method !== "POST")
|
|
107
|
+
return false;
|
|
108
|
+
const reply = (status, body) => (json(res, status, body), true);
|
|
109
|
+
const bot = deps.store.bot(m[1]);
|
|
110
|
+
if (!bot)
|
|
111
|
+
return reply(404, { error: "no such bot" });
|
|
112
|
+
if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
|
|
113
|
+
return reply(415, { error: "content-type must be application/json" });
|
|
114
|
+
}
|
|
115
|
+
if ((deps.platform ?? process.platform) !== "darwin" || await deps.surface(bot, bot.threadId) !== "local") {
|
|
116
|
+
return reply(409, { error: "Phone remote control works for This Mac for now. Choose This Mac for this bot, or open the cloud desktop." });
|
|
117
|
+
}
|
|
118
|
+
const call = (deps.cua ?? hostCall)();
|
|
119
|
+
if (!call)
|
|
120
|
+
return reply(409, { error: readCuaUnavailableReason() ?? "CUA Driver is not ready on this computer" });
|
|
121
|
+
res.setHeader("cache-control", "private, no-store");
|
|
122
|
+
try {
|
|
123
|
+
if (m[2] === "frame")
|
|
124
|
+
return reply(200, await remoteFrame(call));
|
|
125
|
+
if (!deps.controlHeld(bot.id))
|
|
126
|
+
return reply(409, { error: "Take control before using this computer" });
|
|
127
|
+
const input = remoteInputSchema.safeParse(await readBody(req));
|
|
128
|
+
if (!input.success)
|
|
129
|
+
return reply(400, { error: "invalid remote input" });
|
|
130
|
+
await remoteInput(call, input.data);
|
|
131
|
+
return reply(200, { ok: true });
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error.status !== 409)
|
|
135
|
+
throw error;
|
|
136
|
+
return reply(409, { error: error.message });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function hostCall() {
|
|
140
|
+
const connection = readCuaConnection();
|
|
141
|
+
return connection ? hostCuaCall(connection) : null;
|
|
142
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { messageAttachmentName, messageFileDisposition, messageFileDownloadName, messageFileRoots, messageImageTargetAt, messageReferencesFile, openMessageFile } from "../message-file.js";
|
|
2
|
+
import { PASS } from "./table.js";
|
|
3
|
+
export function createMessageFileRoutes({ store, attachments: ATTACHMENTS_DIR, workspaceDir, access }) {
|
|
4
|
+
return async ({ req, res, url, path, method, auth, json, readBody }) => {
|
|
5
|
+
const m = path.match(/^\/api\/threads\/([\w-]+)\/messages\/([\w-]+)\/file$/);
|
|
6
|
+
const streamsMessageImage = Boolean(m && method === "GET" && url.searchParams.get("preview") === "1");
|
|
7
|
+
if (m && (method === "POST" || streamsMessageImage)) {
|
|
8
|
+
if (method === "POST" && !String(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
|
|
9
|
+
return json(res, 415, { error: "content-type must be application/json" });
|
|
10
|
+
}
|
|
11
|
+
const threadId = m[1];
|
|
12
|
+
const directBot = store.botByThread(threadId);
|
|
13
|
+
const group = directBot ? undefined : store.groupByThread(threadId);
|
|
14
|
+
if (!directBot && !group)
|
|
15
|
+
return json(res, 404, { error: "no such conversation" });
|
|
16
|
+
const message = store.messagesFor(threadId).find((candidate) => candidate.id === m[2]);
|
|
17
|
+
if (!message)
|
|
18
|
+
return json(res, 404, { error: "no such message" });
|
|
19
|
+
if (message.kind !== "text") {
|
|
20
|
+
return json(res, 403, { error: "that message does not share this file" });
|
|
21
|
+
}
|
|
22
|
+
const body = method === "POST" ? await readBody(req) : null;
|
|
23
|
+
const rawReference = streamsMessageImage ? url.searchParams.get("ref") : null;
|
|
24
|
+
if (streamsMessageImage && (!rawReference || !/^\d+$/.test(rawReference))) {
|
|
25
|
+
return json(res, 400, { error: "ref must identify a rendered image" });
|
|
26
|
+
}
|
|
27
|
+
const href = method === "POST"
|
|
28
|
+
? (typeof body.path === "string" ? body.path : "")
|
|
29
|
+
: messageImageTargetAt(message.text ?? "", Number(rawReference));
|
|
30
|
+
if (!href)
|
|
31
|
+
return json(res, 400, { error: "path is required" });
|
|
32
|
+
// Generated image paths are durable capabilities on this exact message.
|
|
33
|
+
// They do not grant access to arbitrary workspace files or Markdown refs.
|
|
34
|
+
const generatedImage = method === "POST" && message.role === "bot" &&
|
|
35
|
+
message.attachments?.some((attachment) => attachment.kind === "image" && attachment.path === href) === true;
|
|
36
|
+
let roots;
|
|
37
|
+
let downloadName;
|
|
38
|
+
if (generatedImage) {
|
|
39
|
+
roots = [ATTACHMENTS_DIR];
|
|
40
|
+
}
|
|
41
|
+
else if (message.role === "user") {
|
|
42
|
+
downloadName = messageAttachmentName(message.text ?? "", href) ?? undefined;
|
|
43
|
+
if (!downloadName) {
|
|
44
|
+
return json(res, 403, { error: "that message does not share this file" });
|
|
45
|
+
}
|
|
46
|
+
roots = [ATTACHMENTS_DIR];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (!messageReferencesFile(message.text ?? "", href)) {
|
|
50
|
+
return json(res, 403, { error: "that bot message does not link to this file" });
|
|
51
|
+
}
|
|
52
|
+
const senderId = directBot?.id ?? message.from?.botId;
|
|
53
|
+
// The persisted bot-role message is the author record. Membership is
|
|
54
|
+
// intentionally not consulted: removing a bot must not break files it
|
|
55
|
+
// already shared in channel history.
|
|
56
|
+
if (!senderId) {
|
|
57
|
+
return json(res, 403, { error: "the file's bot author could not be verified" });
|
|
58
|
+
}
|
|
59
|
+
let pinnedCwd;
|
|
60
|
+
let configuredCwd;
|
|
61
|
+
if (directBot) {
|
|
62
|
+
pinnedCwd = store.taskByThread(directBot.id, threadId)?.cwd;
|
|
63
|
+
configuredCwd = directBot.cwd;
|
|
64
|
+
}
|
|
65
|
+
else if (group) {
|
|
66
|
+
const task = store.groupTaskByThread(group.id, threadId);
|
|
67
|
+
pinnedCwd = task ? task.pinnedCwd : group.threadId === threadId ? group.pinnedCwd : undefined;
|
|
68
|
+
configuredCwd = group.cwd;
|
|
69
|
+
}
|
|
70
|
+
roots = messageFileRoots({
|
|
71
|
+
senderWorkspace: workspaceDir(senderId),
|
|
72
|
+
attachments: ATTACHMENTS_DIR,
|
|
73
|
+
pinnedCwd,
|
|
74
|
+
configuredCwd,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const file = await openMessageFile(href, roots, message.role === "bot" && !generatedImage
|
|
78
|
+
? access(directBot?.id ?? message.from?.botId, auth) : {});
|
|
79
|
+
if ((streamsMessageImage || generatedImage) && !file.mime.startsWith("image/")) {
|
|
80
|
+
await file.handle.close();
|
|
81
|
+
return json(res, 415, { error: "only images can be previewed here" });
|
|
82
|
+
}
|
|
83
|
+
res.writeHead(200, {
|
|
84
|
+
"content-type": file.mime,
|
|
85
|
+
"content-length": String(file.bytes),
|
|
86
|
+
...(streamsMessageImage
|
|
87
|
+
? { "content-disposition": "inline" }
|
|
88
|
+
: { "content-disposition": messageFileDisposition(messageFileDownloadName(downloadName, file.name)) }),
|
|
89
|
+
"cache-control": streamsMessageImage ? "private, max-age=3600" : "private, no-store",
|
|
90
|
+
"cdn-cache-control": "no-store",
|
|
91
|
+
"cloudflare-cdn-cache-control": "no-store",
|
|
92
|
+
pragma: "no-cache",
|
|
93
|
+
vary: "Authorization",
|
|
94
|
+
"x-content-type-options": "nosniff",
|
|
95
|
+
...(streamsMessageImage
|
|
96
|
+
? {
|
|
97
|
+
"cross-origin-resource-policy": "same-origin",
|
|
98
|
+
"referrer-policy": "no-referrer",
|
|
99
|
+
}
|
|
100
|
+
: {}),
|
|
101
|
+
});
|
|
102
|
+
if (file.bytes === 0) {
|
|
103
|
+
await file.handle.close();
|
|
104
|
+
res.end();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const stream = file.handle.createReadStream({ start: 0, end: file.bytes - 1, autoClose: true });
|
|
108
|
+
stream.on("error", () => res.destroy());
|
|
109
|
+
res.on("close", () => stream.destroy());
|
|
110
|
+
stream.pipe(res);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
return PASS;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Shared file-link settings. This widens message downloads only, never bot tools.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { realpath, stat } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
8
|
+
import { isRestricted } from "./bot-visibility.js";
|
|
9
|
+
import { PASS } from "./routes/table.js";
|
|
10
|
+
const rootsSchema = z.array(z.string().trim().min(1).max(4096).refine(path => !/[\0\r\n]/.test(path))).max(32);
|
|
11
|
+
const inside = (root, path) => {
|
|
12
|
+
const suffix = relative(root, path);
|
|
13
|
+
return suffix === "" || (suffix !== ".." && !suffix.startsWith(`..${sep}`) && !isAbsolute(suffix));
|
|
14
|
+
};
|
|
15
|
+
export function sharedDeliverableAccess(settings, options) {
|
|
16
|
+
const { dataDir, pawRoot, store, senderId, canSeeBot, canSeeGroup } = options;
|
|
17
|
+
const botRoots = (id, cwd) => [
|
|
18
|
+
join(dataDir, "workspaces", id), join(dataDir, "task-workspaces", id), join(dataDir, "bots", id),
|
|
19
|
+
...(cwd ? [cwd] : []), ...store.tasks(id).flatMap(task => task.cwd ? [task.cwd] : []),
|
|
20
|
+
];
|
|
21
|
+
const personal = [join(pawRoot, "me", "personal"),
|
|
22
|
+
join(settings.home, "workspace", "PAW", "me", "personal")];
|
|
23
|
+
const protectedBots = store.bots.filter(bot => bot.personal === true || isRestricted(bot.visibility) || !canSeeBot(bot.id));
|
|
24
|
+
const protectedBotIds = new Set(protectedBots.map(bot => bot.id));
|
|
25
|
+
const protectedRooms = store.groups.filter(group => isRestricted(group.audienceFloor) ||
|
|
26
|
+
!canSeeGroup(group.id) || group.memberIds.some(id => protectedBotIds.has(id)));
|
|
27
|
+
const roomRoots = protectedRooms.flatMap(group => [group.cwd, group.pinnedCwd,
|
|
28
|
+
...store.groupTasks(group.id).map(task => task.pinnedCwd),
|
|
29
|
+
].filter((path) => typeof path === "string" && path.length > 0));
|
|
30
|
+
return {
|
|
31
|
+
sharedRoots: settings.current(),
|
|
32
|
+
sharedRootBoundaries: [join(settings.home, "workspace")],
|
|
33
|
+
sharedBlockedRoots: [dataDir, ...personal, ...protectedBots.flatMap(bot => botRoots(bot.id, bot.cwd)), ...roomRoots],
|
|
34
|
+
blockedRoots: [
|
|
35
|
+
...(store.bot(senderId ?? "")?.personal === true ? [] : personal),
|
|
36
|
+
...protectedBots.filter(bot => bot.id !== senderId).flatMap(bot => botRoots(bot.id, bot.cwd)),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Email workspace members, assistants and service loopback are not the machine owner. */
|
|
41
|
+
export function sharedDeliverablesOwner(auth) {
|
|
42
|
+
return auth.scopes.includes("admin") && (auth.kind === "loopback" ? auth.trust !== "service"
|
|
43
|
+
: auth.kind === "session" && !auth.session.email && !auth.session.userId);
|
|
44
|
+
}
|
|
45
|
+
export class SharedDeliverables {
|
|
46
|
+
file;
|
|
47
|
+
roots;
|
|
48
|
+
home;
|
|
49
|
+
constructor(dataDir, home = homedir()) {
|
|
50
|
+
this.home = home;
|
|
51
|
+
this.file = join(dataDir, "shared-deliverables.json");
|
|
52
|
+
try {
|
|
53
|
+
this.roots = rootsSchema.parse(JSON.parse(readFileSync(this.file, "utf8")));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.roots = error.code === "ENOENT"
|
|
57
|
+
? [join(home, "workspace", "Reports"), join(home, "workspace", "Atomic")] : [];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
current() { return [...this.roots]; }
|
|
61
|
+
async save(input, blockedRoots) {
|
|
62
|
+
const roots = rootsSchema.parse(input);
|
|
63
|
+
const canonicalBlocked = await Promise.all(blockedRoots.map(async (path) => {
|
|
64
|
+
try {
|
|
65
|
+
return await realpath(path);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return resolve(path);
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
const workspace = await realpath(join(this.home, "workspace")).catch(() => resolve(this.home, "workspace"));
|
|
72
|
+
const next = [];
|
|
73
|
+
for (const raw of roots) {
|
|
74
|
+
const path = raw.startsWith("~/") ? join(this.home, raw.slice(2)) : raw;
|
|
75
|
+
if (!isAbsolute(path))
|
|
76
|
+
throw new Error("Use an absolute folder path.");
|
|
77
|
+
const canonical = await realpath(path);
|
|
78
|
+
if (!(await stat(canonical)).isDirectory())
|
|
79
|
+
throw new Error("Choose a folder.");
|
|
80
|
+
if (inside(canonical, workspace) || canonicalBlocked.some(root => inside(root, canonical))) {
|
|
81
|
+
throw new Error("Choose a deliverable folder, not the workspace or a private folder.");
|
|
82
|
+
}
|
|
83
|
+
next.push(canonical);
|
|
84
|
+
}
|
|
85
|
+
const unique = [...new Set(next)];
|
|
86
|
+
writeFileAtomic(this.file, JSON.stringify(unique, null, 2) + "\n", { mode: 0o600 });
|
|
87
|
+
this.roots = unique;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function createSharedDeliverablesRoutes(settings, blockedRoots) {
|
|
91
|
+
return async ({ path, method, auth, req, res, json, readBody }) => {
|
|
92
|
+
if (path !== "/api/shared-deliverables")
|
|
93
|
+
return PASS;
|
|
94
|
+
if (!sharedDeliverablesOwner(auth))
|
|
95
|
+
return json(res, 403, { error: "Only the computer owner can manage shared deliverable folders." });
|
|
96
|
+
if (method === "GET")
|
|
97
|
+
return json(res, 200, { roots: settings.current() });
|
|
98
|
+
if (method !== "PUT")
|
|
99
|
+
return json(res, 405, { error: "Use GET or PUT." });
|
|
100
|
+
try {
|
|
101
|
+
const body = await readBody(req);
|
|
102
|
+
await settings.save(body.roots, blockedRoots());
|
|
103
|
+
return json(res, 200, { roots: settings.current() });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return json(res, 400, { error: "Choose existing deliverable folders. The whole workspace and private folders cannot be shared." });
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// There is no separate distillation engine. This module only builds the
|
|
6
6
|
// prompt and recognises the slash command, so it works on every engine
|
|
7
7
|
// that mounts the agents tools.
|
|
8
|
+
import { expandSkillCommand } from "../shared/skill-command.js";
|
|
9
|
+
export function expandSkillTurnText(text, authoring) {
|
|
10
|
+
return expandSkillCommand(authoring ? expandLearnTurnText(text) : text);
|
|
11
|
+
}
|
|
8
12
|
import { SAVE_RUN_AS_SKILL_LINE } from "../shared/learn-request.js";
|
|
9
13
|
export const LEARN_COMMAND = "/learn";
|
|
10
14
|
export const LEARN_SOURCE_PREFIX = "learn:";
|
|
@@ -2618,16 +2618,16 @@ export class Store {
|
|
|
2618
2618
|
return task;
|
|
2619
2619
|
}
|
|
2620
2620
|
/** One durable write: never leave only part of a bot's threads updated. */
|
|
2621
|
-
setAllThreadApprovalMode(botId, mode) {
|
|
2621
|
+
setAllThreadApprovalMode(botId, mode, preservePlan = false) {
|
|
2622
2622
|
const bot = this.bot(botId);
|
|
2623
2623
|
if (!bot)
|
|
2624
2624
|
return null;
|
|
2625
2625
|
const patch = { approvalMode: mode, autoApprove: false, alwaysAllow: [] };
|
|
2626
|
-
const tasks = (bot.tasks ?? []).map(task => ({ ...task, ...patch })
|
|
2627
|
-
const next = { ...bot, ...patch, approvalGrant: undefined, tasks };
|
|
2626
|
+
const tasks = (bot.tasks ?? []).map(task => preservePlan && approvalModeFor(this.projectBotForTask(botId, task.threadId)) === "plan" ? { ...task } : { ...task, ...patch });
|
|
2627
|
+
const next = { ...bot, ...(preservePlan && approvalModeFor(bot) === "plan" ? {} : patch), approvalGrant: undefined, tasks };
|
|
2628
2628
|
this.saveBots(this.bots.map(candidate => candidate === bot ? next : candidate));
|
|
2629
2629
|
bot.tasks?.forEach((task, index) => Object.assign(task, tasks[index]));
|
|
2630
|
-
Object.assign(bot,
|
|
2630
|
+
Object.assign(bot, { ...next, tasks: bot.tasks });
|
|
2631
2631
|
this.emit({ type: "bot", botId });
|
|
2632
2632
|
return bot;
|
|
2633
2633
|
}
|
|
@@ -127,9 +127,10 @@ export function hostDefaultModel(provider, candidate) {
|
|
|
127
127
|
* the first in `ranked` (the graded models, best value first within each
|
|
128
128
|
* class) of that class, else the class below, and so on, else the class
|
|
129
129
|
* above; with nothing graded, the account's own default when it is the
|
|
130
|
-
* provider's, else its first. Undefined when it offers none. */
|
|
130
|
+
* provider's, else its first. Undefined when it offers none it can use. */
|
|
131
131
|
export function bestProviderModel(input) {
|
|
132
|
-
const
|
|
132
|
+
const usable = input.usable ?? (() => true);
|
|
133
|
+
const models = providerModels(input.provider, input.candidate).filter(usable);
|
|
133
134
|
if (!models.length)
|
|
134
135
|
return undefined;
|
|
135
136
|
const order = new Map((input.ranked ?? []).map((id, index) => [id.toLowerCase(), index]));
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* permission mode, passed through: OpenMausBot never decides a permission
|
|
3
3
|
* itself (Full access aside, which answers residual prompts because that is
|
|
4
4
|
* what the person granted). The order is the order the selector shows. */
|
|
5
|
-
export const APPROVAL_MODES = ["ask", "edits", "auto", "full", "custom"];
|
|
5
|
+
export const APPROVAL_MODES = ["ask", "edits", "auto", "full", "custom", "plan"];
|
|
6
6
|
/** Only providers with an implemented permission mapping may expose a level.
|
|
7
7
|
* `edits` (auto-accept edits) exists where the engine has such a mode:
|
|
8
8
|
* Claude and Grok `acceptEdits`, Antigravity `auto_edit`, Qwen `auto-edit`,
|
|
9
9
|
* Gemini `auto_edit`, Mistral Vibe `accept-edits`. Codex's Ask already runs `workspace-write`, so an edits
|
|
10
10
|
* level would change nothing there. */
|
|
11
11
|
export function supportsApprovalMode(driverKind, mode) {
|
|
12
|
+
if (mode === "plan")
|
|
13
|
+
return ["atomicEngine", "codex", "claudeAgent", "grok", "openai-compat", "minimax", "mistral", "ollamaCloud"].includes(driverKind ?? "");
|
|
12
14
|
if (mode === "custom")
|
|
13
15
|
return driverKind === "codex";
|
|
14
16
|
if (mode === "edits")
|
|
@@ -127,7 +127,7 @@ export function engineBrand(engine) {
|
|
|
127
127
|
const name = engine.managed?.organizationName?.trim() || (generic(engine.displayName) ? "OpenAI-compatible endpoint" : engine.displayName.trim());
|
|
128
128
|
return { key: `endpoint:${name.toLowerCase()}`, name, group: "flatPlans" };
|
|
129
129
|
}
|
|
130
|
-
if (engine.driverKind
|
|
130
|
+
if (["customAcp", "atomicEngine"].includes(engine.driverKind)) {
|
|
131
131
|
if (engine.instanceId === ATOMIC_ENGINE_ID || /^atomic engine$/i.test(engine.displayName.trim()))
|
|
132
132
|
return { key: "atomic-engine", name: "Atomic Engine", group: "atomic" };
|
|
133
133
|
const name = generic(engine.displayName) ? "Custom agent" : engine.displayName.trim();
|
|
@@ -8,10 +8,10 @@ export const ACCOUNT_PROVIDERS = [
|
|
|
8
8
|
// Kimi Code reads its whole data root (login included) from KIMI_CODE_HOME.
|
|
9
9
|
{ id: "kimi", kind: "signIn", label: "Kimi Code", billing: "subscription", driver: "kimiAgent", homeEnv: "KIMI_CODE_HOME", loginArgs: ["login"], signupUrl: "https://www.kimi.com/code" },
|
|
10
10
|
// Droid resolves <FACTORY_HOME_OVERRIDE>/.factory, so the folder is a home, not the data root.
|
|
11
|
-
{ id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.
|
|
11
|
+
{ id: "droid", kind: "signIn", label: "Factory Droid", billing: "subscription", driver: "droidAgent", homeEnv: "FACTORY_HOME_OVERRIDE", loginArgs: [], signupUrl: "https://factory.com/pricing" },
|
|
12
12
|
{
|
|
13
13
|
id: "anthropic", kind: "apiKey", label: "Anthropic API", billing: "payPerUse", driver: "claudeAgent", envVar: "ANTHROPIC_API_KEY", icon: "anthropic",
|
|
14
|
-
config: {}, keyTest: { provider: "anthropic" }, keyUrl: "https://
|
|
14
|
+
config: {}, keyTest: { provider: "anthropic" }, keyUrl: "https://platform.claude.com/settings/keys",
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
id: "openai", kind: "apiKey", label: "OpenAI API", billing: "payPerUse", driver: "openai-compat", envVar: "OPENAI_API_KEY", icon: "openai",
|
|
@@ -28,14 +28,14 @@ export const ACCOUNT_PROVIDERS = [
|
|
|
28
28
|
// spending a request: there is no key test.
|
|
29
29
|
id: "perplexity", kind: "apiKey", label: "Perplexity API", billing: "payPerUse", driver: "openai-compat", envVar: "PERPLEXITY_API_KEY", icon: "perplexity",
|
|
30
30
|
config: { url: "https://api.perplexity.ai", apiKeyEnv: "PERPLEXITY_API_KEY", model: "sonar-pro", provider: "" },
|
|
31
|
-
keyUrl: "https://
|
|
31
|
+
keyUrl: "https://console.perplexity.ai/project/keys",
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
// Alibaba's flat-price Qwen Coding Plan: one plan key serves Qwen, GLM, Kimi and MiniMax models.
|
|
35
35
|
id: "qwenCodingPlan", kind: "apiKey", label: "Qwen Coding Plan", billing: "subscription", driver: "openai-compat", envVar: "QWEN_CODING_PLAN_API_KEY", icon: "qwen",
|
|
36
36
|
config: { url: "https://coding-intl.dashscope.aliyuncs.com/v1", apiKeyEnv: "QWEN_CODING_PLAN_API_KEY", model: "qwen3.7-plus", provider: "" },
|
|
37
37
|
keyTest: { provider: "openaiCompat", url: "https://coding-intl.dashscope.aliyuncs.com/v1" },
|
|
38
|
-
keyUrl: "https://modelstudio.console.alibabacloud.com/", signupUrl: "https://www.alibabacloud.com/help/en/model-studio/coding-plan",
|
|
38
|
+
keyUrl: "https://modelstudio.console.alibabacloud.com/ap-southeast-1/subscription/coding-plan", signupUrl: "https://www.alibabacloud.com/help/en/model-studio/coding-plan",
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
// MiniMax's flat-price M Plan (formerly the Coding Plan): an `sk-cp-` plan
|
|
@@ -61,7 +61,7 @@ export const ACCOUNT_PROVIDERS = [
|
|
|
61
61
|
{
|
|
62
62
|
// OpenCode Go is a flat monthly plan used through an OpenCode key.
|
|
63
63
|
id: "opencodeGo", kind: "apiKey", label: "OpenCode Go", billing: "subscription", driver: "opencodeGo", envVar: "OPENCODE_API_KEY",
|
|
64
|
-
config: {}, keyUrl: "https://opencode.ai/
|
|
64
|
+
config: {}, keyUrl: "https://opencode.ai/go", signupUrl: "https://opencode.ai/go",
|
|
65
65
|
},
|
|
66
66
|
];
|
|
67
67
|
export function accountProvider(id) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Skill commands remain ordinary user turns: they grant no extra permissions. */
|
|
2
|
+
export function expandSkillCommand(text) {
|
|
3
|
+
const match = /^\/(skills|skill)(?:\s+([\s\S]*))?$/i.exec(text.trim());
|
|
4
|
+
if (!match)
|
|
5
|
+
return text;
|
|
6
|
+
const request = match[2]?.trim() ?? "";
|
|
7
|
+
if (match[1].toLowerCase() === "skills" || !request) {
|
|
8
|
+
return "List the skills actually available to you in this conversation, with their exact names and a short description. " +
|
|
9
|
+
"Use the available skill catalog or discovery tools; do not invent skills or execute one. " +
|
|
10
|
+
(request ? `Filter the list for this request: ${request}\n` : "") +
|
|
11
|
+
"Explain that /skill <name> <task> invokes a skill and /learn saves a reusable procedure.";
|
|
12
|
+
}
|
|
13
|
+
return "Use the named available skill for the following request. Read its instructions before acting. " +
|
|
14
|
+
"If the skill is missing or the name is ambiguous, explain that and offer available matches; do not invent it. " +
|
|
15
|
+
"Existing permissions and approval requirements still apply. If no task is supplied, use the current task when clear, otherwise ask what to apply it to.\n\n" + request;
|
|
16
|
+
}
|
|
@@ -17174,6 +17174,7 @@ var SPAWNED_PROXIES = {
|
|
|
17174
17174
|
hook: resolveProxy("hooks/omb-hook"),
|
|
17175
17175
|
// the RTK add-on's Claude PreToolUse hook (bypassPermissions turns only)
|
|
17176
17176
|
rtkHook: resolveProxy("hooks/rtk-hook"),
|
|
17177
|
+
planHook: resolveProxy("hooks/plan-hook"),
|
|
17177
17178
|
// Loaded by the external `pi` process via `-e`, not by this server — but
|
|
17178
17179
|
// resolved through the same single source of truth so the packaged layout
|
|
17179
17180
|
// check can assert it ships.
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-Dm-Ny6a0.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{c as r,s as e}from"./flowDiagram-KWPJA3E3-hsbvL3Ow.js";import{_ as a}from"./mermaid.core-Dm-Ny6a0.js";import"./chunk-5VM5RSS4-BFkFo_ng.js";import"./chunk-XXDRQBXY-5ThtmFbP.js";import"./chunk-WEXAMYUT-FhqQ0sO0.js";import"./chunk-GWA4HPMP-BZChfTYw.js";import"./channel-BZDQCBTd.js";import"./index-ByXr4FRl.js";var o=a(t=>`${e(t)}
|
|
2
|
-
.swimlane.cluster:not([data-color-id]) rect {
|
|
3
|
-
stroke: ${t.clusterBorder} !important;
|
|
4
|
-
}
|
|
5
|
-
[data-look="neo"].cluster rect {
|
|
6
|
-
filter: none;
|
|
7
|
-
}
|
|
8
|
-
`,"getStyles"),s=o,u=r({styles:s});export{u as diagram};
|