codeden 0.1.0 → 0.2.0
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 +25 -1
- package/dist/cli.js +214 -56089
- package/dist/cli.js.map +1 -1
- package/dist/drizzle/20261108041036_session_worktree_cleanup/migration.sql +2 -0
- package/dist/drizzle/20261108041036_session_worktree_cleanup/snapshot.json +3424 -0
- package/dist/drizzle/20261108045102_project_pin_order/migration.sql +4 -0
- package/dist/drizzle/20261108045102_project_pin_order/snapshot.json +3444 -0
- package/dist/drizzle/20261108052259_queued_message_sender/migration.sql +1 -0
- package/dist/drizzle/20261108052259_queued_message_sender/snapshot.json +3454 -0
- package/dist/main-CzmG0d8t.js +59 -0
- package/dist/main-CzmG0d8t.js.map +1 -0
- package/dist/main-pXwL7GDQ.js +58418 -0
- package/dist/main-pXwL7GDQ.js.map +1 -0
- package/dist/{runner-CoJjefjZ.js → runner-CJetH9jW.js} +2182 -490
- package/dist/runner-CJetH9jW.js.map +1 -0
- package/dist/runner.js +16 -52
- package/dist/runner.js.map +1 -1
- package/dist/web/assets/{core-DpRfWkWN.js → core-BeOBW-Wr.js} +1 -1
- package/dist/web/assets/engine-oniguruma-BLpLufIT.js +1 -0
- package/dist/web/assets/{highlighted-body-JLMTFACH-H03v731F.js → highlighted-body-JLMTFACH-D-L40tHI.js} +1 -1
- package/dist/web/assets/{highlights-Dz1cMTeO.js → highlights-ZCqssNA3.js} +1 -1
- package/dist/web/assets/index-D8qaPF_E.css +1 -0
- package/dist/web/assets/index-DR-I5EC5.js +892 -0
- package/dist/web/assets/langs-DKjK0GUN.js +1 -0
- package/dist/web/assets/mermaid-WFAXDIZX-fZTzmfWq.js +1 -0
- package/dist/web/assets/{shiki-NQ4ryC1Y.js → shiki-CWXnlX2q.js} +2 -2
- package/dist/web/assets/{stream-tokenizer-BxwNb7lW.js → stream-tokenizer-BpK1CZzW.js} +1 -1
- package/dist/web/assets/{themes-CNX2CYCy.js → themes-Bm7B3n10.js} +2 -2
- package/dist/web/favicon.svg +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/runner-CoJjefjZ.js.map +0 -1
- package/dist/web/assets/engine-oniguruma-C_TN-UZT.js +0 -1
- package/dist/web/assets/index-BG4dO7iI.js +0 -885
- package/dist/web/assets/index-BpVrEjZE.css +0 -1
- package/dist/web/assets/langs-D6g8tc-L.js +0 -1
- package/dist/web/assets/mermaid-WFAXDIZX-DbjU3Ezf.js +0 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DegdyBIC.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { accessSync, appendFileSync, chmodSync, closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
4
|
-
import {
|
|
3
|
+
import { accessSync, appendFileSync, chmodSync, closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import os, { homedir, tmpdir } from "node:os";
|
|
5
6
|
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
7
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
8
|
import { execFile, spawn } from "node:child_process";
|
|
8
|
-
import {
|
|
9
|
+
import { gunzipSync } from "node:zlib";
|
|
9
10
|
import { createInterface } from "node:readline";
|
|
10
|
-
import { copyFile, mkdir, readFile, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
11
|
+
import { copyFile, mkdir, readFile, rm, rmdir, stat, utimes, writeFile } from "node:fs/promises";
|
|
11
12
|
import { STATUS_CODES, ServerResponse, createServer } from "node:http";
|
|
12
13
|
import { Http2ServerRequest, constants as constants$1 } from "node:http2";
|
|
13
14
|
import { Readable } from "node:stream";
|
|
@@ -3086,7 +3087,7 @@ function bucketFor(state, inst) {
|
|
|
3086
3087
|
return bucket;
|
|
3087
3088
|
}
|
|
3088
3089
|
let handoff;
|
|
3089
|
-
const open = [];
|
|
3090
|
+
const open$1 = [];
|
|
3090
3091
|
const memo = {
|
|
3091
3092
|
alloc(_inst, payload, empty) {
|
|
3092
3093
|
const bucket = handoff;
|
|
@@ -3097,7 +3098,7 @@ const memo = {
|
|
|
3097
3098
|
issues: null
|
|
3098
3099
|
};
|
|
3099
3100
|
bucket.set(payload.value, entry);
|
|
3100
|
-
open.push(entry);
|
|
3101
|
+
open$1.push(entry);
|
|
3101
3102
|
return empty;
|
|
3102
3103
|
},
|
|
3103
3104
|
guard(inst) {
|
|
@@ -3163,10 +3164,10 @@ const memo = {
|
|
|
3163
3164
|
return payload;
|
|
3164
3165
|
}
|
|
3165
3166
|
handoff = bucket;
|
|
3166
|
-
const depth = open.length;
|
|
3167
|
+
const depth = open$1.length;
|
|
3167
3168
|
const result = base(payload, ctx);
|
|
3168
3169
|
handoff = void 0;
|
|
3169
|
-
const entry = open.length > depth ? open.pop() : void 0;
|
|
3170
|
+
const entry = open$1.length > depth ? open$1.pop() : void 0;
|
|
3170
3171
|
if (result instanceof Promise) return result.then((r) => {
|
|
3171
3172
|
if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;
|
|
3172
3173
|
return r;
|
|
@@ -6833,6 +6834,17 @@ const projectBase = {
|
|
|
6833
6834
|
killSwitch: boolean().default(false),
|
|
6834
6835
|
/** IANA timezone for cron automations; machine timezone when null. */
|
|
6835
6836
|
timezone: string().nullable(),
|
|
6837
|
+
/**
|
|
6838
|
+
* Pinned projects are listed above the rest (§13). Defaults to false so events written
|
|
6839
|
+
* before pinning existed still parse.
|
|
6840
|
+
*/
|
|
6841
|
+
pinned: boolean().default(false),
|
|
6842
|
+
/**
|
|
6843
|
+
* Manual position in the project list, lowest first (§13). Only the relative order within
|
|
6844
|
+
* the pinned and unpinned groups matters; ties fall back to the name. Set by
|
|
6845
|
+
* `projects.reordered`; archived projects keep theirs.
|
|
6846
|
+
*/
|
|
6847
|
+
sortOrder: int().nonnegative().default(0),
|
|
6836
6848
|
createdAt: Timestamp,
|
|
6837
6849
|
updatedAt: Timestamp,
|
|
6838
6850
|
archivedAt: Timestamp.nullable()
|
|
@@ -6856,6 +6868,20 @@ const Project = discriminatedUnion("kind", [object({
|
|
|
6856
6868
|
...projectBase,
|
|
6857
6869
|
kind: literal("local")
|
|
6858
6870
|
})]);
|
|
6871
|
+
/**
|
|
6872
|
+
* The canonical project order (§13): pinned first, then `sortOrder`, then name, then id.
|
|
6873
|
+
* The server lists projects in this order and the UI keeps it when applying live events.
|
|
6874
|
+
*/
|
|
6875
|
+
function compareProjects(a, b) {
|
|
6876
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
6877
|
+
if (a.sortOrder !== b.sortOrder) return a.sortOrder - b.sortOrder;
|
|
6878
|
+
if (a.name !== b.name) return a.name < b.name ? -1 : 1;
|
|
6879
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
6880
|
+
}
|
|
6881
|
+
/** A sorted copy of `projects` in the canonical order. */
|
|
6882
|
+
function sortProjects(projects) {
|
|
6883
|
+
return [...projects].sort(compareProjects);
|
|
6884
|
+
}
|
|
6859
6885
|
// ../../packages/contracts/src/work.ts
|
|
6860
6886
|
/**
|
|
6861
6887
|
* Situational awareness and conflict handling (§8.4). Every unit of work (a session or an
|
|
@@ -7013,7 +7039,12 @@ const ProviderCapabilities = object({
|
|
|
7013
7039
|
reasoningEffort: boolean(),
|
|
7014
7040
|
/** MCP servers can be injected (required for CodeDen tools). */
|
|
7015
7041
|
mcp: boolean(),
|
|
7016
|
-
skills: boolean()
|
|
7042
|
+
skills: boolean(),
|
|
7043
|
+
/**
|
|
7044
|
+
* An autonomy change reaches a running turn from its next tool call. When false (or absent),
|
|
7045
|
+
* a running turn keeps its autonomy and the change applies from the next turn.
|
|
7046
|
+
*/
|
|
7047
|
+
liveAutonomy: boolean().optional()
|
|
7017
7048
|
});
|
|
7018
7049
|
/** A model a provider offers. */
|
|
7019
7050
|
const ModelInfo = object({
|
|
@@ -7532,7 +7563,8 @@ const RunnerCommand = discriminatedUnion("name", [
|
|
|
7532
7563
|
name: literal("session.configure"),
|
|
7533
7564
|
sessionId: Id,
|
|
7534
7565
|
model: string().nullable().optional(),
|
|
7535
|
-
effort: ReasoningEffort.nullable().optional()
|
|
7566
|
+
effort: ReasoningEffort.nullable().optional(),
|
|
7567
|
+
autonomy: AutonomyLevel.optional()
|
|
7536
7568
|
}),
|
|
7537
7569
|
object({
|
|
7538
7570
|
name: literal("pty.open"),
|
|
@@ -7780,8 +7812,23 @@ const QueuedMessage = object({
|
|
|
7780
7812
|
id: Id,
|
|
7781
7813
|
sessionId: Id,
|
|
7782
7814
|
input: UserInput,
|
|
7815
|
+
/** Who queued it. Events written before senders were recorded were all the user's. */
|
|
7816
|
+
from: _enum([
|
|
7817
|
+
"user",
|
|
7818
|
+
"orchestrator",
|
|
7819
|
+
"pm",
|
|
7820
|
+
"automation",
|
|
7821
|
+
"codeden"
|
|
7822
|
+
]).default("user"),
|
|
7783
7823
|
createdAt: Timestamp
|
|
7784
7824
|
});
|
|
7825
|
+
object({ messages: array(QueuedMessage) });
|
|
7826
|
+
/**
|
|
7827
|
+
* `PATCH /api/sessions/:id/queue/:messageId`: replace a queued message's text (attachments
|
|
7828
|
+
* stay). Answers 409 `already_sent` once delivery has taken the message.
|
|
7829
|
+
*/
|
|
7830
|
+
const EditQueuedMessageRequest = object({ text: string().max(2e5) });
|
|
7831
|
+
object({ status: literal("steered") });
|
|
7785
7832
|
/** Structured result an agent reports with the `finish` tool (§8.3). */
|
|
7786
7833
|
const SessionResult = object({
|
|
7787
7834
|
outcome: _enum([
|
|
@@ -7981,6 +8028,13 @@ const Session = object({
|
|
|
7981
8028
|
rootThreadId: Id.nullable(),
|
|
7982
8029
|
/** Structured result from the agent's `finish` tool, once reported. */
|
|
7983
8030
|
result: SessionResult.nullable().optional(),
|
|
8031
|
+
/** When the session was archived: hidden from the sidebar, its worktree cleaned up. */
|
|
8032
|
+
archivedAt: Timestamp.nullable().optional(),
|
|
8033
|
+
/**
|
|
8034
|
+
* When CodeDen removed the session's worktree (archive or inactivity cleanup). The branch and
|
|
8035
|
+
* checkpoints stay; the worktree is recreated from the branch on the next message.
|
|
8036
|
+
*/
|
|
8037
|
+
worktreeRemovedAt: Timestamp.nullable().optional(),
|
|
7984
8038
|
createdAt: Timestamp,
|
|
7985
8039
|
updatedAt: Timestamp,
|
|
7986
8040
|
endedAt: Timestamp.nullable()
|
|
@@ -9142,6 +9196,291 @@ const ConflictRecord = object({
|
|
|
9142
9196
|
createdAt: Timestamp,
|
|
9143
9197
|
resolvedAt: Timestamp.nullable()
|
|
9144
9198
|
});
|
|
9199
|
+
// ../../packages/contracts/src/updates.ts
|
|
9200
|
+
/**
|
|
9201
|
+
* `settings.updates.mode` (§16): `off` never asks the registry; `notify` checks and offers a
|
|
9202
|
+
* one-click update; `auto` also downloads a new version in the background so it starts on the
|
|
9203
|
+
* next launch (or right away with "Restart now").
|
|
9204
|
+
*/
|
|
9205
|
+
const UpdateMode = _enum([
|
|
9206
|
+
"off",
|
|
9207
|
+
"notify",
|
|
9208
|
+
"auto"
|
|
9209
|
+
]);
|
|
9210
|
+
/**
|
|
9211
|
+
* Where the running CodeDen came from. `packaged`: an npm install (`npx`, global or a
|
|
9212
|
+
* side-by-side version in `<home>/app`), which can update itself. `source`: the repository or
|
|
9213
|
+
* a workspace build, which never switches versions (pull and rebuild instead). `desktop`: the
|
|
9214
|
+
* desktop app, which updates itself from GitHub Releases (the npm path is off; §17).
|
|
9215
|
+
*/
|
|
9216
|
+
const InstallSource = _enum([
|
|
9217
|
+
"packaged",
|
|
9218
|
+
"source",
|
|
9219
|
+
"desktop"
|
|
9220
|
+
]);
|
|
9221
|
+
/**
|
|
9222
|
+
* Self-update progress. `installing`: npm is fetching `target` into `<home>/app/<target>`;
|
|
9223
|
+
* `installed`: it is ready and starts on the next launch (or with a restart); `restarting`:
|
|
9224
|
+
* the server is handing over to `target`; `failed`: see `installError`.
|
|
9225
|
+
*/
|
|
9226
|
+
const UpdateState = _enum([
|
|
9227
|
+
"idle",
|
|
9228
|
+
"installing",
|
|
9229
|
+
"installed",
|
|
9230
|
+
"restarting",
|
|
9231
|
+
"failed"
|
|
9232
|
+
]);
|
|
9233
|
+
/** A CodeDen version installed side by side in `<home>/app/<version>`. */
|
|
9234
|
+
const InstalledVersion = object({
|
|
9235
|
+
version: string(),
|
|
9236
|
+
/** `<home>/app/current.json` points at it: the launcher starts it. */
|
|
9237
|
+
current: boolean(),
|
|
9238
|
+
/** The CLI entry exists and its package reports this version. */
|
|
9239
|
+
valid: boolean()
|
|
9240
|
+
});
|
|
9241
|
+
/**
|
|
9242
|
+
* `GET /api/updates` (§16) and the `update` WebSocket message: the npm registry check plus the
|
|
9243
|
+
* one-click self-update state. The registry is asked on start and every 6 hours while the
|
|
9244
|
+
* mode is not `off`; `?refresh=1` checks again now. Nothing but the package name is sent.
|
|
9245
|
+
*/
|
|
9246
|
+
const UpdateStatus = object({
|
|
9247
|
+
/** The running version. */
|
|
9248
|
+
current: string(),
|
|
9249
|
+
/** Latest published version, null until a check succeeded. */
|
|
9250
|
+
latest: string().nullable(),
|
|
9251
|
+
/** `latest` is newer than `current`. */
|
|
9252
|
+
updateAvailable: boolean(),
|
|
9253
|
+
/** The mode is not `off` (and `CODEDEN_UPDATE_CHECK` is not `0`); when false nothing is fetched. */
|
|
9254
|
+
enabled: boolean(),
|
|
9255
|
+
mode: UpdateMode,
|
|
9256
|
+
checkedAt: Timestamp.nullable(),
|
|
9257
|
+
/** Why the last check failed (offline, registry error), else null. */
|
|
9258
|
+
error: string().nullable(),
|
|
9259
|
+
/** The manual way to update, e.g. `npm i -g codeden@latest`. */
|
|
9260
|
+
command: string(),
|
|
9261
|
+
source: InstallSource,
|
|
9262
|
+
state: UpdateState,
|
|
9263
|
+
/** The version being installed, installed and waiting for a restart, or restarting into. */
|
|
9264
|
+
target: string().nullable(),
|
|
9265
|
+
/** The latest npm output line while installing. */
|
|
9266
|
+
progress: string().nullable(),
|
|
9267
|
+
/** Why the last install, switch or restart failed. */
|
|
9268
|
+
installError: string().nullable(),
|
|
9269
|
+
/** This server can restart itself into another version (started by the `codeden` CLI). */
|
|
9270
|
+
canRestart: boolean(),
|
|
9271
|
+
/** What to run to restart by hand when it can't, e.g. `npx codeden`. */
|
|
9272
|
+
restartCommand: string(),
|
|
9273
|
+
/** Versions in `<home>/app`, newest first. */
|
|
9274
|
+
installed: array(InstalledVersion),
|
|
9275
|
+
/** The version "Roll back" would start (the previous one, or the copy you launched). */
|
|
9276
|
+
rollbackTo: string().nullable(),
|
|
9277
|
+
/** "Roll back" is possible (a previous version is recorded). */
|
|
9278
|
+
canRollback: boolean(),
|
|
9279
|
+
/** Set when the launcher could not start the pinned version and fell back. */
|
|
9280
|
+
warning: string().nullable()
|
|
9281
|
+
});
|
|
9282
|
+
/** `POST /api/updates/install`: install `version` (default: the latest) and restart into it. */
|
|
9283
|
+
const UpdateInstallRequest = object({
|
|
9284
|
+
version: string().min(1).optional(),
|
|
9285
|
+
/** Restart once installed (default true); false leaves it for the next launch. */
|
|
9286
|
+
restart: boolean().optional()
|
|
9287
|
+
});
|
|
9288
|
+
object({
|
|
9289
|
+
version: string(),
|
|
9290
|
+
/** Markdown from the GitHub Release body or the CHANGELOG section; null when unreachable. */
|
|
9291
|
+
body: string().nullable(),
|
|
9292
|
+
/** Where to read them in the browser. */
|
|
9293
|
+
url: string()
|
|
9294
|
+
});
|
|
9295
|
+
/**
|
|
9296
|
+
* `<home>/app/current.json`: which side-by-side version the `codeden` launcher starts. Written
|
|
9297
|
+
* by the server or `codeden update`; read (without zod, on the hot path) by the launcher in
|
|
9298
|
+
* `apps/cli/src/launcher.ts`, which must accept everything this schema accepts.
|
|
9299
|
+
*/
|
|
9300
|
+
const AppPointer = object({
|
|
9301
|
+
version: string().min(1),
|
|
9302
|
+
/** The version that ran before the switch ("Roll back" target), null when unknown. */
|
|
9303
|
+
previous: string().nullable(),
|
|
9304
|
+
updatedAt: Timestamp
|
|
9305
|
+
});
|
|
9306
|
+
/** True when version `a` is newer than `b` (`x.y.z`, prerelease tags sort before releases). */
|
|
9307
|
+
function isNewerVersion(a, b) {
|
|
9308
|
+
return compareVersions$1(a, b) > 0;
|
|
9309
|
+
}
|
|
9310
|
+
/** Compare two `x.y.z[-pre]` versions: negative when `a < b`, 0 when equal, positive when newer. */
|
|
9311
|
+
function compareVersions$1(a, b) {
|
|
9312
|
+
const parse = (v) => {
|
|
9313
|
+
const trimmed = v.trim().replace(/^v/, "");
|
|
9314
|
+
const dash = trimmed.indexOf("-");
|
|
9315
|
+
const core = dash === -1 ? trimmed : trimmed.slice(0, dash);
|
|
9316
|
+
const pre = dash === -1 ? "" : trimmed.slice(dash + 1);
|
|
9317
|
+
return {
|
|
9318
|
+
nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0),
|
|
9319
|
+
pre
|
|
9320
|
+
};
|
|
9321
|
+
};
|
|
9322
|
+
const x = parse(a);
|
|
9323
|
+
const y = parse(b);
|
|
9324
|
+
for (let i = 0; i < 3; i++) {
|
|
9325
|
+
const d = (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
9326
|
+
if (d !== 0) return d;
|
|
9327
|
+
}
|
|
9328
|
+
if (x.pre === y.pre) return 0;
|
|
9329
|
+
if (!x.pre) return 1;
|
|
9330
|
+
if (!y.pre) return -1;
|
|
9331
|
+
return comparePrerelease(x.pre, y.pre);
|
|
9332
|
+
}
|
|
9333
|
+
/** Dot-separated prerelease identifiers: numeric ones compare as numbers and sort first. */
|
|
9334
|
+
function comparePrerelease(a, b) {
|
|
9335
|
+
const xs = a.split(".");
|
|
9336
|
+
const ys = b.split(".");
|
|
9337
|
+
for (let i = 0; i < Math.max(xs.length, ys.length); i++) {
|
|
9338
|
+
const x = xs[i];
|
|
9339
|
+
const y = ys[i];
|
|
9340
|
+
if (x === void 0) return -1;
|
|
9341
|
+
if (y === void 0) return 1;
|
|
9342
|
+
const xn = /^\d+$/.test(x);
|
|
9343
|
+
const yn = /^\d+$/.test(y);
|
|
9344
|
+
if (xn && yn) {
|
|
9345
|
+
const d = Number(x) - Number(y);
|
|
9346
|
+
if (d !== 0) return d;
|
|
9347
|
+
} else if (xn !== yn) return xn ? -1 : 1;
|
|
9348
|
+
else if (x !== y) return x < y ? -1 : 1;
|
|
9349
|
+
}
|
|
9350
|
+
return 0;
|
|
9351
|
+
}
|
|
9352
|
+
// ../../packages/contracts/src/voice-api.ts
|
|
9353
|
+
/**
|
|
9354
|
+
* Voice engines on the server (§11.3). The browser's speech APIs stay the default; these
|
|
9355
|
+
* routes proxy ElevenLabs (text to speech, Scribe) and OpenAI (transcription) with API keys
|
|
9356
|
+
* from the OS keychain, so keys never reach the browser, and run local Whisper with a
|
|
9357
|
+
* runtime and model downloaded on demand.
|
|
9358
|
+
*/
|
|
9359
|
+
/** ElevenLabs MP3 output formats (`codec_sampleRate_bitrate`). */
|
|
9360
|
+
const TtsOutputFormat = _enum([
|
|
9361
|
+
"mp3_22050_32",
|
|
9362
|
+
"mp3_44100_64",
|
|
9363
|
+
"mp3_44100_128",
|
|
9364
|
+
"mp3_44100_192"
|
|
9365
|
+
]);
|
|
9366
|
+
/**
|
|
9367
|
+
* `POST /api/tts`: streams `audio/mpeg`. `voiceId` defaults to
|
|
9368
|
+
* `settings.voice.readAloud.voice`, then a stock voice; `modelId` to a low-latency model.
|
|
9369
|
+
*/
|
|
9370
|
+
const TtsRequest = object({
|
|
9371
|
+
text: string().min(1).max(5e3),
|
|
9372
|
+
voiceId: string().min(1).max(100).optional(),
|
|
9373
|
+
modelId: string().min(1).max(100).optional(),
|
|
9374
|
+
outputFormat: TtsOutputFormat.optional()
|
|
9375
|
+
});
|
|
9376
|
+
object({ voices: array(object({
|
|
9377
|
+
id: string(),
|
|
9378
|
+
name: string(),
|
|
9379
|
+
category: string().optional(),
|
|
9380
|
+
description: string().optional(),
|
|
9381
|
+
previewUrl: string().optional(),
|
|
9382
|
+
labels: record(string(), string()).optional()
|
|
9383
|
+
})) });
|
|
9384
|
+
/** Engines that transcribe on the server: the cloud engines and local Whisper. */
|
|
9385
|
+
const SttEngine = _enum([
|
|
9386
|
+
"elevenlabs",
|
|
9387
|
+
"openai",
|
|
9388
|
+
"local"
|
|
9389
|
+
]);
|
|
9390
|
+
/**
|
|
9391
|
+
* `POST /api/stt?engine=&language=`: the body is the recorded audio (raw bytes with an
|
|
9392
|
+
* `audio/*` content type, or multipart with a `file` field), up to 25 MB. `engine` defaults
|
|
9393
|
+
* to `settings.voice.dictation.engine` (when that is `browser`, ElevenLabs). The `local`
|
|
9394
|
+
* engine only takes WAV, 16-bit PCM, mono at `LOCAL_STT_SAMPLE_RATE`, at most
|
|
9395
|
+
* `LOCAL_STT_MAX_SECONDS` long (the UI converts the recording before uploading).
|
|
9396
|
+
*/
|
|
9397
|
+
const SttQuery = object({
|
|
9398
|
+
engine: SttEngine.optional(),
|
|
9399
|
+
/** ISO 639-1 hint, e.g. `en`. */
|
|
9400
|
+
language: string().min(2).max(10).optional()
|
|
9401
|
+
});
|
|
9402
|
+
object({
|
|
9403
|
+
text: string(),
|
|
9404
|
+
language: string().nullable(),
|
|
9405
|
+
engine: SttEngine,
|
|
9406
|
+
/** How long the server spent transcribing, in milliseconds (local engine). */
|
|
9407
|
+
elapsedMs: number$1().nonnegative().optional()
|
|
9408
|
+
});
|
|
9409
|
+
/**
|
|
9410
|
+
* Dictation engines (`settings.voice.dictation.engine`): the browser's speech recognition,
|
|
9411
|
+
* a cloud engine through the server, or `local` Whisper running on the CodeDen server.
|
|
9412
|
+
*/
|
|
9413
|
+
const DictationEngine = _enum([
|
|
9414
|
+
"browser",
|
|
9415
|
+
"elevenlabs",
|
|
9416
|
+
"openai",
|
|
9417
|
+
"local"
|
|
9418
|
+
]);
|
|
9419
|
+
/**
|
|
9420
|
+
* Local Whisper models CodeDen can download. `.en` models are English-only (more accurate
|
|
9421
|
+
* for English at the same size); the others detect or take a language.
|
|
9422
|
+
*/
|
|
9423
|
+
const WhisperModelId = _enum([
|
|
9424
|
+
"tiny",
|
|
9425
|
+
"tiny.en",
|
|
9426
|
+
"base",
|
|
9427
|
+
"base.en",
|
|
9428
|
+
"small",
|
|
9429
|
+
"small.en"
|
|
9430
|
+
]);
|
|
9431
|
+
/** Audio format the local engine accepts: WAV, 16-bit PCM, mono, at this sample rate. */
|
|
9432
|
+
const LOCAL_STT_SAMPLE_RATE = 16e3;
|
|
9433
|
+
const downloadFields = {
|
|
9434
|
+
state: _enum([
|
|
9435
|
+
"missing",
|
|
9436
|
+
"downloading",
|
|
9437
|
+
"installed",
|
|
9438
|
+
"failed"
|
|
9439
|
+
]),
|
|
9440
|
+
/** Bytes received so far (while downloading). */
|
|
9441
|
+
receivedBytes: int().nonnegative(),
|
|
9442
|
+
/** Download size in bytes (pinned, so known before the download starts). */
|
|
9443
|
+
totalBytes: int().nonnegative(),
|
|
9444
|
+
/** Size on disk when installed. */
|
|
9445
|
+
sizeOnDisk: int().nonnegative().nullable(),
|
|
9446
|
+
/** Why the last attempt failed. */
|
|
9447
|
+
error: string().nullable()
|
|
9448
|
+
};
|
|
9449
|
+
/** The Whisper runtime: a prebuilt native library for this platform, downloaded on demand. */
|
|
9450
|
+
const LocalSttRuntime = object({
|
|
9451
|
+
/** Runtime version CodeDen pins. */
|
|
9452
|
+
version: string(),
|
|
9453
|
+
...downloadFields
|
|
9454
|
+
});
|
|
9455
|
+
/** One downloadable model, with a speed and accuracy hint for the picker. */
|
|
9456
|
+
const LocalSttModel = object({
|
|
9457
|
+
id: WhisperModelId,
|
|
9458
|
+
/** e.g. `Base (English)`. */
|
|
9459
|
+
label: string(),
|
|
9460
|
+
/** Speed and accuracy hint, e.g. `Fast, good accuracy`. */
|
|
9461
|
+
hint: string(),
|
|
9462
|
+
englishOnly: boolean(),
|
|
9463
|
+
/** The suggested default. */
|
|
9464
|
+
recommended: boolean(),
|
|
9465
|
+
...downloadFields
|
|
9466
|
+
});
|
|
9467
|
+
object({
|
|
9468
|
+
/** False on platforms without a prebuilt runtime (`unsupportedReason` says why). */
|
|
9469
|
+
supported: boolean(),
|
|
9470
|
+
/** `process.platform-process.arch` of the server, e.g. `win32-x64`. */
|
|
9471
|
+
platform: string(),
|
|
9472
|
+
unsupportedReason: string().nullable(),
|
|
9473
|
+
runtime: LocalSttRuntime,
|
|
9474
|
+
models: array(LocalSttModel),
|
|
9475
|
+
/** Folder holding the models. */
|
|
9476
|
+
location: string()
|
|
9477
|
+
});
|
|
9478
|
+
/**
|
|
9479
|
+
* `POST /api/voice/local/models/:id/download` starts (or joins) the download of the runtime
|
|
9480
|
+
* and that model; `DELETE /api/voice/local/models/:id` cancels or deletes it. Both return
|
|
9481
|
+
* the new `LocalSttStatus`. The UI polls `GET /api/voice/local` while anything downloads.
|
|
9482
|
+
*/
|
|
9483
|
+
const LocalSttModelParams = object({ id: WhisperModelId });
|
|
9145
9484
|
// ../../packages/contracts/src/settings.ts
|
|
9146
9485
|
/** `auto` lets CodeDen pick the first ready provider (Claude, then Codex). */
|
|
9147
9486
|
const ProviderChoice = union([literal("auto"), ProviderId]);
|
|
@@ -9172,6 +9511,17 @@ const RoleDefaults = object({
|
|
|
9172
9511
|
model: string().nullable().default(null),
|
|
9173
9512
|
effort: ReasoningEffort.nullable().default(null)
|
|
9174
9513
|
});
|
|
9514
|
+
/** A model named by its provider and the provider's model id. */
|
|
9515
|
+
const ModelRef = object({
|
|
9516
|
+
provider: ProviderId,
|
|
9517
|
+
model: string().min(1)
|
|
9518
|
+
});
|
|
9519
|
+
/** A provider's custom models: ids are unique. */
|
|
9520
|
+
const CustomModelList = array(object({
|
|
9521
|
+
id: string().trim().min(1).max(200),
|
|
9522
|
+
/** Shown instead of the id in the picker. */
|
|
9523
|
+
label: string().trim().min(1).max(100).optional()
|
|
9524
|
+
})).refine((list) => new Set(list.map((m) => m.id)).size === list.length, { message: "Custom model ids must be unique" });
|
|
9175
9525
|
/**
|
|
9176
9526
|
* User settings persisted in `<home>/settings.json` (non-secret; API keys live in the OS
|
|
9177
9527
|
* keychain). Every field has a default, so `Settings.parse({})` yields a complete object and
|
|
@@ -9202,6 +9552,25 @@ const Settings = object({
|
|
|
9202
9552
|
noticeSummaries: boolean().default(true)
|
|
9203
9553
|
}).prefault({})
|
|
9204
9554
|
}).prefault({}),
|
|
9555
|
+
/** The model picker (§13). */
|
|
9556
|
+
models: object({
|
|
9557
|
+
/**
|
|
9558
|
+
* Starred models, listed in this order in the picker's Favourites section. Entries whose
|
|
9559
|
+
* model a provider no longer lists are kept (it may come back) but not shown.
|
|
9560
|
+
*/
|
|
9561
|
+
favorites: array(ModelRef).default([]),
|
|
9562
|
+
/**
|
|
9563
|
+
* Model ids left out of the picker, by provider id. A hidden model that is still chosen
|
|
9564
|
+
* somewhere (a session, a default, an automation) keeps showing there.
|
|
9565
|
+
*/
|
|
9566
|
+
hidden: record(ProviderId, array(string().min(1))).default({}),
|
|
9567
|
+
/**
|
|
9568
|
+
* Model ids added by the user, by provider id, merged into the picker after the models
|
|
9569
|
+
* the provider lists (a listed id wins). Their effort levels are those of the provider's
|
|
9570
|
+
* default model, or none when the provider has no effort levels.
|
|
9571
|
+
*/
|
|
9572
|
+
custom: record(ProviderId, CustomModelList).default({})
|
|
9573
|
+
}).prefault({}),
|
|
9205
9574
|
/** Follow-up prompt chips (§7.5). */
|
|
9206
9575
|
followups: object({
|
|
9207
9576
|
enabled: boolean().default(true),
|
|
@@ -9211,13 +9580,15 @@ const Settings = object({
|
|
|
9211
9580
|
/** Voice (§11.3). */
|
|
9212
9581
|
voice: object({
|
|
9213
9582
|
dictation: object({
|
|
9214
|
-
engine:
|
|
9215
|
-
"browser",
|
|
9216
|
-
"elevenlabs",
|
|
9217
|
-
"openai"
|
|
9218
|
-
]).default("browser"),
|
|
9583
|
+
engine: DictationEngine.default("browser"),
|
|
9219
9584
|
pushToTalkShortcut: string().nullable().default(null),
|
|
9220
|
-
|
|
9585
|
+
/**
|
|
9586
|
+
* BCP 47 tag such as `en-US`; null is auto (the browser engine follows the browser
|
|
9587
|
+
* language, the server engines detect it). English-only Whisper models ignore it.
|
|
9588
|
+
*/
|
|
9589
|
+
language: string().nullable().default(null),
|
|
9590
|
+
/** Local Whisper: which downloaded model transcribes (§11.3). */
|
|
9591
|
+
local: object({ model: WhisperModelId.default("base.en") }).prefault({})
|
|
9221
9592
|
}).prefault({}),
|
|
9222
9593
|
readAloud: object({
|
|
9223
9594
|
enabled: boolean().default(false),
|
|
@@ -9245,15 +9616,39 @@ const Settings = object({
|
|
|
9245
9616
|
}).prefault({}),
|
|
9246
9617
|
notifications: object({
|
|
9247
9618
|
toasts: boolean().default(true),
|
|
9248
|
-
browser: boolean().default(false)
|
|
9619
|
+
browser: boolean().default(false),
|
|
9620
|
+
/** A short chime when an agent is blocked on the user (an approval or a question). */
|
|
9621
|
+
sound: boolean().default(true)
|
|
9249
9622
|
}).prefault({}),
|
|
9250
9623
|
/** Global factory controls (§10.1). */
|
|
9251
9624
|
factory: object({
|
|
9252
9625
|
/** Stops every project's factory loop and all factory sessions. */
|
|
9253
9626
|
killSwitch: boolean().default(false) }).prefault({}),
|
|
9627
|
+
/**
|
|
9628
|
+
* Worktree cleanup (§9). Archiving a session always removes its worktree; with `cleanup` on,
|
|
9629
|
+
* worktrees of sessions inactive for `afterDays` are removed too, unless the session's pull
|
|
9630
|
+
* request is still open or the worktree has uncommitted changes.
|
|
9631
|
+
*/
|
|
9632
|
+
worktrees: object({
|
|
9633
|
+
cleanup: boolean().default(true),
|
|
9634
|
+
afterDays: int().min(1).max(365).default(7)
|
|
9635
|
+
}).prefault({}),
|
|
9254
9636
|
/** Master switch; individual automations are still off by default (§10). */
|
|
9255
9637
|
automations: object({ enabled: boolean().default(true) }).prefault({}),
|
|
9256
|
-
|
|
9638
|
+
/**
|
|
9639
|
+
* Update checks and one-click self-update (§16). Settings saved before `mode` existed had
|
|
9640
|
+
* `check: false` to turn checks off; that reads as `mode: "off"`.
|
|
9641
|
+
*/
|
|
9642
|
+
updates: preprocess((value) => typeof value === "object" && value !== null && !("mode" in value) && value.check === false ? { mode: "off" } : value, object({ mode: UpdateMode.default("notify") }).prefault({})),
|
|
9643
|
+
/** The desktop app (§17). Read by the desktop app only; `npx codeden` ignores it. */
|
|
9644
|
+
desktop: object({
|
|
9645
|
+
/** Start CodeDen (in the tray, window hidden) when you log in. */
|
|
9646
|
+
launchAtLogin: boolean().default(false),
|
|
9647
|
+
/** Closing the window keeps CodeDen running in the tray / menu bar; Quit really quits. */
|
|
9648
|
+
closeToTray: boolean().default(true),
|
|
9649
|
+
/** Keep the computer from sleeping while an agent is running. */
|
|
9650
|
+
keepAwake: boolean().default(true)
|
|
9651
|
+
}).prefault({}),
|
|
9257
9652
|
/** First-run setup (§14): set once the user finishes the detection checklist. */
|
|
9258
9653
|
setup: object({
|
|
9259
9654
|
completed: boolean().default(false),
|
|
@@ -9315,6 +9710,7 @@ discriminatedUnion("type", [
|
|
|
9315
9710
|
globalEvent("project.created", object({ project: Project })),
|
|
9316
9711
|
globalEvent("project.updated", object({ project: Project })),
|
|
9317
9712
|
globalEvent("project.archived", object({ projectId: Id })),
|
|
9713
|
+
globalEvent("projects.reordered", object({ projectIds: array(Id) })),
|
|
9318
9714
|
globalEvent("session.created", object({ session: Session })),
|
|
9319
9715
|
globalEvent("session.updated", object({ session: Session })),
|
|
9320
9716
|
globalEvent("inbox.created", object({ item: InboxItem })),
|
|
@@ -9350,7 +9746,17 @@ discriminatedUnion("type", [
|
|
|
9350
9746
|
globalEvent("message.dequeued", object({
|
|
9351
9747
|
sessionId: Id,
|
|
9352
9748
|
messageId: Id,
|
|
9353
|
-
reason: _enum([
|
|
9749
|
+
reason: _enum([
|
|
9750
|
+
"sent",
|
|
9751
|
+
"dropped",
|
|
9752
|
+
"deleted"
|
|
9753
|
+
])
|
|
9754
|
+
})),
|
|
9755
|
+
globalEvent("message.edited", object({
|
|
9756
|
+
sessionId: Id,
|
|
9757
|
+
messageId: Id,
|
|
9758
|
+
text: string(),
|
|
9759
|
+
at: Timestamp
|
|
9354
9760
|
})),
|
|
9355
9761
|
globalEvent("notice.created", object({ notice: Notice })),
|
|
9356
9762
|
globalEvent("notice.delivered", object({
|
|
@@ -9473,6 +9879,10 @@ const ServerToClientMessage = discriminatedUnion("type", [
|
|
|
9473
9879
|
type: literal("runner"),
|
|
9474
9880
|
runner: RunnerConnectionStatus
|
|
9475
9881
|
}),
|
|
9882
|
+
object({
|
|
9883
|
+
type: literal("update"),
|
|
9884
|
+
update: UpdateStatus
|
|
9885
|
+
}),
|
|
9476
9886
|
object({
|
|
9477
9887
|
type: literal("pong"),
|
|
9478
9888
|
id: string()
|
|
@@ -9539,9 +9949,17 @@ const UpdateProjectRequest = object({
|
|
|
9539
9949
|
defaultProvider: ProviderId.nullable().optional(),
|
|
9540
9950
|
autonomy: AutonomyLevel.nullable().optional(),
|
|
9541
9951
|
timezone: string().nullable().optional(),
|
|
9542
|
-
defaultBranch: string().nullable().optional()
|
|
9952
|
+
defaultBranch: string().nullable().optional(),
|
|
9953
|
+
/** Pin or unpin the project in the project list (§13). */
|
|
9954
|
+
pinned: boolean().optional()
|
|
9543
9955
|
});
|
|
9544
9956
|
/**
|
|
9957
|
+
* `PUT /api/projects/order`: the new manual order of the project list (§13). Lists every
|
|
9958
|
+
* non-archived project exactly once; the server rejects anything else (`invalid_order`).
|
|
9959
|
+
*/
|
|
9960
|
+
const ReorderProjectsRequest = object({ projectIds: array(Id).max(1e3) });
|
|
9961
|
+
object({ projects: array(Project) });
|
|
9962
|
+
/**
|
|
9545
9963
|
* Where a session runs (§9): a fresh worktree (default for agents), the repo root, or an
|
|
9546
9964
|
* existing directory (`path`).
|
|
9547
9965
|
*/
|
|
@@ -10236,6 +10654,125 @@ discriminatedUnion("type", [
|
|
|
10236
10654
|
message: string()
|
|
10237
10655
|
})
|
|
10238
10656
|
]);
|
|
10657
|
+
object({
|
|
10658
|
+
/** Sessions with a turn starting or in progress. */
|
|
10659
|
+
running: int().nonnegative(),
|
|
10660
|
+
/** Sessions blocked on the user (an approval or a question). */
|
|
10661
|
+
waiting: int().nonnegative(),
|
|
10662
|
+
/** Open inbox items (questions, approvals, PRs to review, failures, ...). */
|
|
10663
|
+
inbox: int().nonnegative()
|
|
10664
|
+
});
|
|
10665
|
+
/** Count running and waiting sessions and open inbox items. */
|
|
10666
|
+
function summarizeActivity(sessions, inbox) {
|
|
10667
|
+
let running = 0;
|
|
10668
|
+
let waiting = 0;
|
|
10669
|
+
for (const s of sessions) if (s.status === "running" || s.status === "starting") running++;
|
|
10670
|
+
else if (s.status === "waiting") waiting++;
|
|
10671
|
+
return {
|
|
10672
|
+
running,
|
|
10673
|
+
waiting,
|
|
10674
|
+
inbox: inbox.filter((i) => i.status === "open").length
|
|
10675
|
+
};
|
|
10676
|
+
}
|
|
10677
|
+
/** The desktop updater's state, mirrored into `GET /api/updates` for the update banner. */
|
|
10678
|
+
const DesktopUpdateState = object({
|
|
10679
|
+
phase: _enum([
|
|
10680
|
+
"idle",
|
|
10681
|
+
"checking",
|
|
10682
|
+
"available",
|
|
10683
|
+
"downloading",
|
|
10684
|
+
"downloaded",
|
|
10685
|
+
"installing",
|
|
10686
|
+
"error"
|
|
10687
|
+
]),
|
|
10688
|
+
/** The running app version. */
|
|
10689
|
+
current: string(),
|
|
10690
|
+
/** The newest release found, null until a check succeeded. */
|
|
10691
|
+
latest: string().nullable(),
|
|
10692
|
+
checkedAt: Timestamp.nullable(),
|
|
10693
|
+
/** Download progress (0-100) while `downloading`. */
|
|
10694
|
+
percent: number$1().min(0).max(100).nullable(),
|
|
10695
|
+
/** Why the last check or download failed. */
|
|
10696
|
+
error: string().nullable(),
|
|
10697
|
+
/** Set when this build cannot update itself (a development run, an unsigned macOS app). */
|
|
10698
|
+
unsupported: string().nullable()
|
|
10699
|
+
});
|
|
10700
|
+
/** What the server asks the desktop updater to do (driven by the update banner and policy). */
|
|
10701
|
+
const DesktopUpdateCommand = _enum([
|
|
10702
|
+
"check",
|
|
10703
|
+
"download",
|
|
10704
|
+
"install"
|
|
10705
|
+
]);
|
|
10706
|
+
discriminatedUnion("type", [
|
|
10707
|
+
object({
|
|
10708
|
+
type: literal("ready"),
|
|
10709
|
+
url: string(),
|
|
10710
|
+
port: int().min(1).max(65535),
|
|
10711
|
+
launchUrl: string(),
|
|
10712
|
+
version: string()
|
|
10713
|
+
}),
|
|
10714
|
+
object({
|
|
10715
|
+
type: literal("failed"),
|
|
10716
|
+
message: string(),
|
|
10717
|
+
code: string().nullable()
|
|
10718
|
+
}),
|
|
10719
|
+
object({
|
|
10720
|
+
type: literal("update.command"),
|
|
10721
|
+
id: string(),
|
|
10722
|
+
command: DesktopUpdateCommand
|
|
10723
|
+
})
|
|
10724
|
+
]);
|
|
10725
|
+
discriminatedUnion("type", [
|
|
10726
|
+
object({
|
|
10727
|
+
type: literal("update.state"),
|
|
10728
|
+
state: DesktopUpdateState
|
|
10729
|
+
}),
|
|
10730
|
+
object({
|
|
10731
|
+
type: literal("update.done"),
|
|
10732
|
+
id: string(),
|
|
10733
|
+
error: string().nullable()
|
|
10734
|
+
}),
|
|
10735
|
+
object({
|
|
10736
|
+
type: literal("shutdown"),
|
|
10737
|
+
stopRunner: boolean()
|
|
10738
|
+
})
|
|
10739
|
+
]);
|
|
10740
|
+
discriminatedUnion("kind", [
|
|
10741
|
+
object({ kind: literal("home") }),
|
|
10742
|
+
object({ kind: literal("inbox") }),
|
|
10743
|
+
object({
|
|
10744
|
+
kind: literal("session"),
|
|
10745
|
+
sessionId: Id
|
|
10746
|
+
}),
|
|
10747
|
+
object({
|
|
10748
|
+
kind: literal("project"),
|
|
10749
|
+
projectId: Id
|
|
10750
|
+
}),
|
|
10751
|
+
object({
|
|
10752
|
+
kind: literal("settings"),
|
|
10753
|
+
section: string().nullable()
|
|
10754
|
+
})
|
|
10755
|
+
]);
|
|
10756
|
+
object({
|
|
10757
|
+
version: string(),
|
|
10758
|
+
platform: _enum([
|
|
10759
|
+
"win32",
|
|
10760
|
+
"darwin",
|
|
10761
|
+
"linux"
|
|
10762
|
+
]),
|
|
10763
|
+
/** True for an installed build (false when run from the repository). */
|
|
10764
|
+
packaged: boolean(),
|
|
10765
|
+
/** The app started this server (false when it attached to one started by `codeden`). */
|
|
10766
|
+
ownsServer: boolean(),
|
|
10767
|
+
/** The OS lets the app start at login (not on Linux AppImage, for example). */
|
|
10768
|
+
launchAtLoginSupported: boolean()
|
|
10769
|
+
});
|
|
10770
|
+
object({
|
|
10771
|
+
title: string().min(1).max(200),
|
|
10772
|
+
body: string().max(2e3).optional(),
|
|
10773
|
+
/** Passed back to `onNotificationClick` when the user clicks it. */
|
|
10774
|
+
tag: string().max(200).optional()
|
|
10775
|
+
});
|
|
10239
10776
|
// ../../packages/contracts/src/images-api.ts
|
|
10240
10777
|
/**
|
|
10241
10778
|
* Image generation settings screen and test button (§12.5). The `generate_image` MCP tool
|
|
@@ -10340,14 +10877,40 @@ object({
|
|
|
10340
10877
|
result: StartSessionResponse
|
|
10341
10878
|
});
|
|
10342
10879
|
/**
|
|
10343
|
-
* `PATCH /api/sessions/:id`: change the model or
|
|
10344
|
-
* (a running turn keeps its settings)
|
|
10880
|
+
* `PATCH /api/sessions/:id`: change the model, effort, autonomy or title mid-session.
|
|
10881
|
+
* Model and effort apply on the next turn (a running turn keeps its settings); `null` returns
|
|
10882
|
+
* to the role default.
|
|
10345
10883
|
*/
|
|
10346
10884
|
const UpdateSessionRequest = object({
|
|
10347
10885
|
model: string().min(1).nullable().optional(),
|
|
10348
10886
|
effort: ReasoningEffort.nullable().optional(),
|
|
10887
|
+
/**
|
|
10888
|
+
* Autonomy level (§11.2), saved on the session and pushed to the live provider session.
|
|
10889
|
+
* It applies from the next tool call when the provider reports `liveAutonomy`, otherwise
|
|
10890
|
+
* from the next turn. Approvals already waiting stay waiting.
|
|
10891
|
+
*/
|
|
10892
|
+
autonomy: AutonomyLevel.optional(),
|
|
10349
10893
|
title: string().trim().min(1).max(200).optional()
|
|
10350
|
-
}).refine((r) => r.model !== void 0 || r.effort !== void 0 || r.title !== void 0, { message: "Nothing to change" });
|
|
10894
|
+
}).refine((r) => r.model !== void 0 || r.effort !== void 0 || r.autonomy !== void 0 || r.title !== void 0, { message: "Nothing to change" });
|
|
10895
|
+
/**
|
|
10896
|
+
* `POST /api/sessions/:id/archive`: stop the session if it is running, hide it from the
|
|
10897
|
+
* sidebar and remove its worktree. The branch and checkpoints stay.
|
|
10898
|
+
*/
|
|
10899
|
+
const ArchiveSessionRequest = object({
|
|
10900
|
+
/** Remove the worktree even when it has uncommitted changes (those changes are lost). */
|
|
10901
|
+
force: boolean().default(false) });
|
|
10902
|
+
object({
|
|
10903
|
+
session: Session,
|
|
10904
|
+
worktree: object({
|
|
10905
|
+
status: _enum([
|
|
10906
|
+
"removed",
|
|
10907
|
+
"kept_changes",
|
|
10908
|
+
"none",
|
|
10909
|
+
"failed"
|
|
10910
|
+
]),
|
|
10911
|
+
message: string().nullable()
|
|
10912
|
+
})
|
|
10913
|
+
});
|
|
10351
10914
|
// ../../packages/contracts/src/runner/events.ts
|
|
10352
10915
|
/**
|
|
10353
10916
|
* Durable runner -> server events. Journaled by the runner and delivered with a `seq`
|
|
@@ -10452,8 +11015,9 @@ const RunnerStream = discriminatedUnion("kind", [
|
|
|
10452
11015
|
* 4. runner replays journaled events with `seq > lastAckedSeq`, then streams live.
|
|
10453
11016
|
*
|
|
10454
11017
|
* After the handshake the server sends `command`s (answered by `result` with the same `id`)
|
|
10455
|
-
* and cumulative `ack`s; the runner sends `event`s (durable, with `seq`)
|
|
10456
|
-
* (ephemeral). Either side
|
|
11018
|
+
* and cumulative `ack`s; the runner sends `event`s (durable, with `seq`), `stream`s
|
|
11019
|
+
* (ephemeral) and `providers` (the current provider list, whenever it changes). Either side
|
|
11020
|
+
* may `ping`.
|
|
10457
11021
|
*/
|
|
10458
11022
|
/** Request id correlating a `command` with its `result`. */
|
|
10459
11023
|
const RequestId = string().min(1).max(100);
|
|
@@ -10552,6 +11116,10 @@ const RunnerToServerMessage = discriminatedUnion("type", [
|
|
|
10552
11116
|
ts: Timestamp,
|
|
10553
11117
|
stream: RunnerStream
|
|
10554
11118
|
}),
|
|
11119
|
+
object({
|
|
11120
|
+
type: literal("providers"),
|
|
11121
|
+
providers: array(ProviderInfo)
|
|
11122
|
+
}),
|
|
10555
11123
|
Ping,
|
|
10556
11124
|
Pong
|
|
10557
11125
|
]);
|
|
@@ -10618,41 +11186,6 @@ object({
|
|
|
10618
11186
|
/** Token id, used for revocation. */
|
|
10619
11187
|
jti: string().min(1)
|
|
10620
11188
|
});
|
|
10621
|
-
object({
|
|
10622
|
-
/** The running version. */
|
|
10623
|
-
current: string(),
|
|
10624
|
-
/** Latest published version, null until a check succeeded. */
|
|
10625
|
-
latest: string().nullable(),
|
|
10626
|
-
/** `latest` is newer than `current`. */
|
|
10627
|
-
updateAvailable: boolean(),
|
|
10628
|
-
/** `settings.updates.check`; when off, nothing is fetched. */
|
|
10629
|
-
enabled: boolean(),
|
|
10630
|
-
checkedAt: Timestamp.nullable(),
|
|
10631
|
-
/** Why the last check failed (offline, registry error), else null. */
|
|
10632
|
-
error: string().nullable(),
|
|
10633
|
-
/** How to update, e.g. `npm i -g codeden@latest`. */
|
|
10634
|
-
command: string()
|
|
10635
|
-
});
|
|
10636
|
-
/** True when version `a` is newer than `b` (`x.y.z`, prerelease tags sort before releases). */
|
|
10637
|
-
function isNewerVersion(a, b) {
|
|
10638
|
-
const parse = (v) => {
|
|
10639
|
-
const [core = "", pre = ""] = v.trim().replace(/^v/, "").split("-", 2);
|
|
10640
|
-
return {
|
|
10641
|
-
nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0),
|
|
10642
|
-
pre
|
|
10643
|
-
};
|
|
10644
|
-
};
|
|
10645
|
-
const x = parse(a);
|
|
10646
|
-
const y = parse(b);
|
|
10647
|
-
for (let i = 0; i < 3; i++) {
|
|
10648
|
-
const d = (x.nums[i] ?? 0) - (y.nums[i] ?? 0);
|
|
10649
|
-
if (d !== 0) return d > 0;
|
|
10650
|
-
}
|
|
10651
|
-
if (x.pre === y.pre) return false;
|
|
10652
|
-
if (!x.pre) return true;
|
|
10653
|
-
if (!y.pre) return false;
|
|
10654
|
-
return x.pre > y.pre;
|
|
10655
|
-
}
|
|
10656
11189
|
// ../../packages/contracts/src/usage.ts
|
|
10657
11190
|
/**
|
|
10658
11191
|
* Usage and cost reports (§12.6). The server keeps a ledger with one row per turn: the
|
|
@@ -10899,58 +11432,18 @@ object({
|
|
|
10899
11432
|
budgets: array(BudgetStatus),
|
|
10900
11433
|
billing: record(ProviderId, BillingMode)
|
|
10901
11434
|
});
|
|
10902
|
-
// ../../packages/contracts/src/version.ts
|
|
10903
|
-
/**
|
|
10904
|
-
* CodeDen release version. Kept in sync with `apps/cli/package.json` (a test enforces it).
|
|
10905
|
-
* Server and runner exchange it during the runner handshake to detect upgrades.
|
|
10906
|
-
*/
|
|
10907
|
-
const CODEDEN_VERSION = "0.1.0";
|
|
10908
|
-
// ../../packages/contracts/src/voice-api.ts
|
|
10909
|
-
/**
|
|
10910
|
-
* Optional cloud voice engines (§11.3). The browser's speech APIs stay the default; these
|
|
10911
|
-
* routes proxy ElevenLabs (text to speech, Scribe) and OpenAI (transcription) with API keys
|
|
10912
|
-
* from the OS keychain, so keys never reach the browser.
|
|
10913
|
-
*/
|
|
10914
|
-
/** ElevenLabs MP3 output formats (`codec_sampleRate_bitrate`). */
|
|
10915
|
-
const TtsOutputFormat = _enum([
|
|
10916
|
-
"mp3_22050_32",
|
|
10917
|
-
"mp3_44100_64",
|
|
10918
|
-
"mp3_44100_128",
|
|
10919
|
-
"mp3_44100_192"
|
|
10920
|
-
]);
|
|
10921
|
-
/**
|
|
10922
|
-
* `POST /api/tts`: streams `audio/mpeg`. `voiceId` defaults to
|
|
10923
|
-
* `settings.voice.readAloud.voice`, then a stock voice; `modelId` to a low-latency model.
|
|
10924
|
-
*/
|
|
10925
|
-
const TtsRequest = object({
|
|
10926
|
-
text: string().min(1).max(5e3),
|
|
10927
|
-
voiceId: string().min(1).max(100).optional(),
|
|
10928
|
-
modelId: string().min(1).max(100).optional(),
|
|
10929
|
-
outputFormat: TtsOutputFormat.optional()
|
|
10930
|
-
});
|
|
10931
|
-
object({ voices: array(object({
|
|
10932
|
-
id: string(),
|
|
10933
|
-
name: string(),
|
|
10934
|
-
category: string().optional(),
|
|
10935
|
-
description: string().optional(),
|
|
10936
|
-
previewUrl: string().optional(),
|
|
10937
|
-
labels: record(string(), string()).optional()
|
|
10938
|
-
})) });
|
|
10939
11435
|
/**
|
|
10940
|
-
*
|
|
10941
|
-
*
|
|
10942
|
-
*
|
|
11436
|
+
* CodeDen release version. Server and runner exchange it during the runner handshake to detect
|
|
11437
|
+
* upgrades. The published entries (`dist/cli.js`, `dist/runner.js`) set
|
|
11438
|
+
* `globalThis.__CODEDEN_PACKAGE_VERSION__` from their own `package.json` before loading the
|
|
11439
|
+
* rest of the bundle, so the package version is the single source of truth at runtime;
|
|
11440
|
+
* everywhere else (workspace, tests) it is `SOURCE_VERSION`.
|
|
10943
11441
|
*/
|
|
10944
|
-
const
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
}
|
|
10949
|
-
object({
|
|
10950
|
-
text: string(),
|
|
10951
|
-
language: string().nullable(),
|
|
10952
|
-
engine: _enum(["elevenlabs", "openai"])
|
|
10953
|
-
});
|
|
11442
|
+
const CODEDEN_VERSION = packageVersion() ?? "0.2.0";
|
|
11443
|
+
function packageVersion() {
|
|
11444
|
+
const value = globalThis.__CODEDEN_PACKAGE_VERSION__;
|
|
11445
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
11446
|
+
}
|
|
10954
11447
|
// ../../node_modules/.pnpm/ws@8.22.1/node_modules/ws/lib/constants.js
|
|
10955
11448
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
10956
11449
|
const BINARY_TYPES = [
|
|
@@ -14701,6 +15194,43 @@ var AsyncQueue = class {
|
|
|
14701
15194
|
};
|
|
14702
15195
|
}
|
|
14703
15196
|
};
|
|
15197
|
+
// ../../packages/providers/src/models.ts
|
|
15198
|
+
function copy(m) {
|
|
15199
|
+
return {
|
|
15200
|
+
...m,
|
|
15201
|
+
efforts: [...m.efforts]
|
|
15202
|
+
};
|
|
15203
|
+
}
|
|
15204
|
+
/**
|
|
15205
|
+
* An adapter's current model list. It starts from a fallback and is replaced by what the
|
|
15206
|
+
* provider reports (model discovery, a running session). Listeners hear about real changes
|
|
15207
|
+
* only, so the runner can forward them to the server (§4.3).
|
|
15208
|
+
*/
|
|
15209
|
+
var ModelCatalog = class {
|
|
15210
|
+
models;
|
|
15211
|
+
listeners = /* @__PURE__ */ new Set();
|
|
15212
|
+
constructor(fallback) {
|
|
15213
|
+
this.models = fallback.map(copy);
|
|
15214
|
+
}
|
|
15215
|
+
/** A copy of the current list. */
|
|
15216
|
+
list() {
|
|
15217
|
+
return this.models.map(copy);
|
|
15218
|
+
}
|
|
15219
|
+
/** Replace the list. Returns false (and notifies nobody) when nothing changed. */
|
|
15220
|
+
set(models) {
|
|
15221
|
+
if (JSON.stringify(models) === JSON.stringify(this.models)) return false;
|
|
15222
|
+
this.models = models.map(copy);
|
|
15223
|
+
for (const listener of this.listeners) try {
|
|
15224
|
+
listener();
|
|
15225
|
+
} catch {}
|
|
15226
|
+
return true;
|
|
15227
|
+
}
|
|
15228
|
+
/** Call `listener` after every change. Returns the unsubscribe function. */
|
|
15229
|
+
subscribe(listener) {
|
|
15230
|
+
this.listeners.add(listener);
|
|
15231
|
+
return () => this.listeners.delete(listener);
|
|
15232
|
+
}
|
|
15233
|
+
};
|
|
14704
15234
|
// ../../packages/providers/src/util/exec.ts
|
|
14705
15235
|
function isFile(candidate) {
|
|
14706
15236
|
try {
|
|
@@ -14772,11 +15302,51 @@ function scriptFromCmdShim(shimPath, content) {
|
|
|
14772
15302
|
if (content === void 0 && !existsSync(script)) return void 0;
|
|
14773
15303
|
return script;
|
|
14774
15304
|
}
|
|
15305
|
+
/** True inside the desktop app, where `process.execPath` is the Electron binary (§17). */
|
|
15306
|
+
function isElectronRuntime(versions = process.versions) {
|
|
15307
|
+
return typeof versions.electron === "string";
|
|
15308
|
+
}
|
|
15309
|
+
/**
|
|
15310
|
+
* Environment for running one of CodeDen's own scripts (the runner, npm, Playwright's CLI)
|
|
15311
|
+
* with `process.execPath`. In the desktop app that is the Electron binary, which only acts
|
|
15312
|
+
* as Node.js with `ELECTRON_RUN_AS_NODE=1`; elsewhere the environment is returned unchanged.
|
|
15313
|
+
*/
|
|
15314
|
+
function ownNodeEnv(env = process.env, versions = process.versions) {
|
|
15315
|
+
return isElectronRuntime(versions) ? {
|
|
15316
|
+
...env,
|
|
15317
|
+
ELECTRON_RUN_AS_NODE: "1"
|
|
15318
|
+
} : env;
|
|
15319
|
+
}
|
|
15320
|
+
let systemNode;
|
|
15321
|
+
/**
|
|
15322
|
+
* The Node.js that runs `node-script` commands (npm-style shims of the user's own tools, such
|
|
15323
|
+
* as a globally installed Codex on Windows). Normally the Node running CodeDen. In the desktop
|
|
15324
|
+
* app the user's `node` from PATH is preferred, so their tools run exactly as in a terminal
|
|
15325
|
+
* and `ELECTRON_RUN_AS_NODE` never leaks into agent processes; without one, the Electron
|
|
15326
|
+
* binary runs them as Node.js.
|
|
15327
|
+
*/
|
|
15328
|
+
function scriptRuntime() {
|
|
15329
|
+
if (!isElectronRuntime()) return {
|
|
15330
|
+
path: process.execPath,
|
|
15331
|
+
electron: false
|
|
15332
|
+
};
|
|
15333
|
+
if (systemNode === void 0) {
|
|
15334
|
+
const found = resolveExecutable("node");
|
|
15335
|
+
systemNode = found?.kind === "native" ? found.path : null;
|
|
15336
|
+
}
|
|
15337
|
+
return systemNode ? {
|
|
15338
|
+
path: systemNode,
|
|
15339
|
+
electron: false
|
|
15340
|
+
} : {
|
|
15341
|
+
path: process.execPath,
|
|
15342
|
+
electron: true
|
|
15343
|
+
};
|
|
15344
|
+
}
|
|
14775
15345
|
/** The `[command, args]` to pass to `spawn` for a resolved executable (never needs `shell`). */
|
|
14776
15346
|
function spawnArgs(cmd, args) {
|
|
14777
15347
|
switch (cmd.kind) {
|
|
14778
15348
|
case "native": return [cmd.path, [...args]];
|
|
14779
|
-
case "node-script": return [
|
|
15349
|
+
case "node-script": return [scriptRuntime().path, [cmd.script, ...args]];
|
|
14780
15350
|
case "shell-script": return [process.env.ComSpec ?? "cmd.exe", [
|
|
14781
15351
|
"/d",
|
|
14782
15352
|
"/s",
|
|
@@ -14793,9 +15363,11 @@ function quoteCmdArg(arg) {
|
|
|
14793
15363
|
/** Spawn a resolved executable. Uses `windowsVerbatimArguments` for the cmd.exe fallback. */
|
|
14794
15364
|
function spawnResolved(cmd, args, options = {}) {
|
|
14795
15365
|
const [file, argv] = spawnArgs(cmd, args);
|
|
15366
|
+
const asNode = cmd.kind === "node-script" && scriptRuntime().electron;
|
|
14796
15367
|
return spawn(file, argv, {
|
|
14797
15368
|
windowsHide: true,
|
|
14798
15369
|
...options,
|
|
15370
|
+
...asNode ? { env: ownNodeEnv(options.env ?? process.env) } : {},
|
|
14799
15371
|
...cmd.kind === "shell-script" ? { windowsVerbatimArguments: true } : {}
|
|
14800
15372
|
});
|
|
14801
15373
|
}
|
|
@@ -15008,6 +15580,9 @@ function parseClaudeAuthStatus(stdout) {
|
|
|
15008
15580
|
return unknown;
|
|
15009
15581
|
}
|
|
15010
15582
|
}
|
|
15583
|
+
// ../../packages/providers/src/util/model.ts
|
|
15584
|
+
/** The id the model pickers use for "the provider's own default model". */
|
|
15585
|
+
const DEFAULT_MODEL_ID = "default";
|
|
15011
15586
|
/**
|
|
15012
15587
|
* The model to pass to a provider, or undefined to leave it out so the provider uses its own
|
|
15013
15588
|
* default (the user's config). `"default"` and empty values are never sent as model ids.
|
|
@@ -15016,6 +15591,143 @@ function explicitModel(model) {
|
|
|
15016
15591
|
const id = model?.trim();
|
|
15017
15592
|
return id && id !== "default" ? id : void 0;
|
|
15018
15593
|
}
|
|
15594
|
+
// ../../packages/providers/src/policy/read-only.ts
|
|
15595
|
+
/**
|
|
15596
|
+
* Read-only sessions (the orchestrator and PMs, §11.2) may inspect a repository but never
|
|
15597
|
+
* change it. This allowlist decides which shell commands they may run: commands that only
|
|
15598
|
+
* read, with no output redirection and no command substitution. Anything else is denied.
|
|
15599
|
+
*/
|
|
15600
|
+
/**
|
|
15601
|
+
* Split on every `;`, `&&`, `||`, `|` and newline, even inside quotes. Deliberately cruder
|
|
15602
|
+
* than the risk classifier's lexer: for an allowlist, over-splitting only denies more.
|
|
15603
|
+
*/
|
|
15604
|
+
function splitCommands(command) {
|
|
15605
|
+
return command.split(/\r?\n|;|&&|\|\||\|/).map((s) => s.trim()).filter(Boolean);
|
|
15606
|
+
}
|
|
15607
|
+
/** Tokenize one simple command, honouring single and double quotes. */
|
|
15608
|
+
function tokenize(command) {
|
|
15609
|
+
const tokens = [];
|
|
15610
|
+
for (const m of command.matchAll(/"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g)) tokens.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
15611
|
+
return tokens;
|
|
15612
|
+
}
|
|
15613
|
+
/** Programs that only read (or print). */
|
|
15614
|
+
const READ_ONLY_BINS = /* @__PURE__ */ new Set([
|
|
15615
|
+
"ls",
|
|
15616
|
+
"dir",
|
|
15617
|
+
"cat",
|
|
15618
|
+
"type",
|
|
15619
|
+
"head",
|
|
15620
|
+
"tail",
|
|
15621
|
+
"less",
|
|
15622
|
+
"more",
|
|
15623
|
+
"wc",
|
|
15624
|
+
"grep",
|
|
15625
|
+
"egrep",
|
|
15626
|
+
"fgrep",
|
|
15627
|
+
"rg",
|
|
15628
|
+
"find",
|
|
15629
|
+
"fd",
|
|
15630
|
+
"tree",
|
|
15631
|
+
"pwd",
|
|
15632
|
+
"echo",
|
|
15633
|
+
"printf",
|
|
15634
|
+
"which",
|
|
15635
|
+
"where",
|
|
15636
|
+
"whoami",
|
|
15637
|
+
"date",
|
|
15638
|
+
"stat",
|
|
15639
|
+
"file",
|
|
15640
|
+
"du",
|
|
15641
|
+
"df",
|
|
15642
|
+
"sort",
|
|
15643
|
+
"uniq",
|
|
15644
|
+
"cut",
|
|
15645
|
+
"diff",
|
|
15646
|
+
"jq",
|
|
15647
|
+
"basename",
|
|
15648
|
+
"dirname",
|
|
15649
|
+
"realpath",
|
|
15650
|
+
"true",
|
|
15651
|
+
"get-childitem",
|
|
15652
|
+
"gci",
|
|
15653
|
+
"get-content",
|
|
15654
|
+
"gc",
|
|
15655
|
+
"get-item",
|
|
15656
|
+
"gi",
|
|
15657
|
+
"get-location",
|
|
15658
|
+
"gl",
|
|
15659
|
+
"select-string",
|
|
15660
|
+
"sls",
|
|
15661
|
+
"measure-object",
|
|
15662
|
+
"test-path",
|
|
15663
|
+
"resolve-path",
|
|
15664
|
+
"write-output",
|
|
15665
|
+
"write-host",
|
|
15666
|
+
"select-object",
|
|
15667
|
+
"where-object",
|
|
15668
|
+
"format-list",
|
|
15669
|
+
"format-table",
|
|
15670
|
+
"out-string",
|
|
15671
|
+
"sort-object",
|
|
15672
|
+
"gh"
|
|
15673
|
+
]);
|
|
15674
|
+
/** Git subcommands that never change the repository. */
|
|
15675
|
+
const READ_ONLY_GIT = /* @__PURE__ */ new Set([
|
|
15676
|
+
"status",
|
|
15677
|
+
"log",
|
|
15678
|
+
"show",
|
|
15679
|
+
"diff",
|
|
15680
|
+
"blame",
|
|
15681
|
+
"grep",
|
|
15682
|
+
"ls-files",
|
|
15683
|
+
"ls-tree",
|
|
15684
|
+
"rev-parse",
|
|
15685
|
+
"rev-list",
|
|
15686
|
+
"describe",
|
|
15687
|
+
"shortlog",
|
|
15688
|
+
"cat-file",
|
|
15689
|
+
"remote",
|
|
15690
|
+
"branch",
|
|
15691
|
+
"tag",
|
|
15692
|
+
"config",
|
|
15693
|
+
"reflog",
|
|
15694
|
+
"merge-base",
|
|
15695
|
+
"name-rev",
|
|
15696
|
+
"for-each-ref",
|
|
15697
|
+
"worktree"
|
|
15698
|
+
]);
|
|
15699
|
+
/** `gh` subcommand verbs that only read. */
|
|
15700
|
+
const READ_ONLY_GH = /* @__PURE__ */ new Set([
|
|
15701
|
+
"view",
|
|
15702
|
+
"list",
|
|
15703
|
+
"status",
|
|
15704
|
+
"diff",
|
|
15705
|
+
"checks"
|
|
15706
|
+
]);
|
|
15707
|
+
const bin = (token) => (token ?? "").replace(/\\/g, "/").split("/").pop()?.toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/, "") ?? "";
|
|
15708
|
+
/** Why a command is refused in a read-only session, or undefined when it may run. */
|
|
15709
|
+
function readOnlyCommandDenial(command) {
|
|
15710
|
+
if (/\$\(|`|<\(|>\(/.test(command)) return "Command substitution is not allowed in a read-only session";
|
|
15711
|
+
for (const simple of splitCommands(command)) {
|
|
15712
|
+
if (/(^|[^0-9&<])>>?(?!\s*(&\d|\/dev\/null|nul\b|\$null))/i.test(simple)) return "Writing files is not allowed in a read-only session";
|
|
15713
|
+
const tokens = tokenize(simple);
|
|
15714
|
+
const name = bin(tokens[0]);
|
|
15715
|
+
const args = tokens.slice(1).map((a) => a.toLowerCase());
|
|
15716
|
+
if (name === "git") {
|
|
15717
|
+
const sub = args.find((a) => !a.startsWith("-"));
|
|
15718
|
+
if (!sub || !READ_ONLY_GIT.has(sub)) return `git ${sub ?? ""} may change the repository`;
|
|
15719
|
+
if ((sub === "branch" || sub === "tag" || sub === "remote" || sub === "worktree") && args.some((a) => /^-(d|D|m|M|f|-delete|-move|-force)$/.test(a) || a === "add")) return `git ${sub} with these options changes the repository`;
|
|
15720
|
+
if (sub === "config" && !args.some((a) => a === "--get" || a === "--list" || a === "-l")) return "Only reading git config is allowed";
|
|
15721
|
+
continue;
|
|
15722
|
+
}
|
|
15723
|
+
if (name === "gh") {
|
|
15724
|
+
if (!args.some((a) => READ_ONLY_GH.has(a)) || args.includes("api")) return "Only read-only gh commands are allowed (view, list, status, diff, checks)";
|
|
15725
|
+
continue;
|
|
15726
|
+
}
|
|
15727
|
+
if (name === "find" && args.some((a) => a === "-delete" || a === "-exec" || a === "-execdir")) return "find with -delete or -exec is not allowed in a read-only session";
|
|
15728
|
+
if (!READ_ONLY_BINS.has(name)) return `${name || "This command"} is not on the read-only allowlist`;
|
|
15729
|
+
}
|
|
15730
|
+
}
|
|
15019
15731
|
// ../../packages/providers/src/util/text.ts
|
|
15020
15732
|
/** Max characters of tool output kept inline in an item (§6: larger outputs are truncated). */
|
|
15021
15733
|
const TOOL_OUTPUT_PREVIEW_CHARS = 16e3;
|
|
@@ -15070,50 +15782,110 @@ function unixToIso(value) {
|
|
|
15070
15782
|
const ms = value < 0xe8d4a51000 ? value * 1e3 : value;
|
|
15071
15783
|
return new Date(ms).toISOString();
|
|
15072
15784
|
}
|
|
15073
|
-
// ../../packages/providers/src/utility.ts
|
|
15074
|
-
/** Abort signal that fires on the caller's signal or after `timeoutMs`. */
|
|
15075
|
-
function oneShotSignal(request) {
|
|
15076
|
-
const controller = new AbortController();
|
|
15077
|
-
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("Utility model timed out")), request.timeoutMs ?? 6e4);
|
|
15078
|
-
const onAbort = () => controller.abort(request.signal?.reason);
|
|
15079
|
-
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
15080
|
-
return {
|
|
15081
|
-
signal: controller.signal,
|
|
15082
|
-
dispose: () => {
|
|
15083
|
-
clearTimeout(timer);
|
|
15084
|
-
request.signal?.removeEventListener("abort", onAbort);
|
|
15085
|
-
}
|
|
15086
|
-
};
|
|
15087
|
-
}
|
|
15088
15785
|
// ../../packages/providers/src/policy/paths.ts
|
|
15786
|
+
const isWin = (ctx) => (ctx.platform ?? process.platform) === "win32";
|
|
15089
15787
|
function pathApi(ctx) {
|
|
15090
|
-
return (ctx
|
|
15788
|
+
return isWin(ctx) ? path.win32 : path.posix;
|
|
15091
15789
|
}
|
|
15092
|
-
|
|
15790
|
+
function homeOf(ctx) {
|
|
15791
|
+
return ctx.home ?? process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
15792
|
+
}
|
|
15793
|
+
function tempRootsOf(ctx) {
|
|
15794
|
+
return ctx.tempRoots ?? osTempRoots(ctx.platform);
|
|
15795
|
+
}
|
|
15796
|
+
const HOME_VARS = /^(\$HOME|\$\{HOME\}|\$env:USERPROFILE|%USERPROFILE%)(?=$|[\\/])/i;
|
|
15797
|
+
const TEMP_VARS = /^(\$TMPDIR|\$\{TMPDIR\}|\$TEMP|\$TMP|\$env:TEMP|\$env:TMP|%TEMP%|%TMP%)(?=$|[\\/])/i;
|
|
15798
|
+
/**
|
|
15799
|
+
* Git Bash paths on Windows: `/c/Users/x` is `C:\Users\x`, and `/tmp` is the user's temp
|
|
15800
|
+
* directory.
|
|
15801
|
+
*/
|
|
15802
|
+
function fromGitBash(value, temp) {
|
|
15803
|
+
if (temp && /^\/tmp(?=$|\/)/.test(value)) return temp + value.slice(4);
|
|
15804
|
+
const drive = /^\/([a-z])(?=$|\/)/i.exec(value);
|
|
15805
|
+
if (drive?.[1]) return `${drive[1].toUpperCase()}:\\${value.slice(3)}`;
|
|
15806
|
+
return value;
|
|
15807
|
+
}
|
|
15808
|
+
/**
|
|
15809
|
+
* Resolve `target` (absolute, relative to `dir`, `~`-prefixed, or starting with a home or
|
|
15810
|
+
* temp variable) to an absolute path.
|
|
15811
|
+
*/
|
|
15093
15812
|
function resolveAgainst(target, ctx) {
|
|
15094
15813
|
const p = pathApi(ctx);
|
|
15095
15814
|
let value = target.trim().replace(/^["']|["']$/g, "");
|
|
15096
|
-
const home = ctx
|
|
15815
|
+
const home = homeOf(ctx);
|
|
15097
15816
|
if (value === "~" || value.startsWith("~/") || value.startsWith("~\\")) value = home + value.slice(1);
|
|
15098
|
-
value = value.replace(
|
|
15099
|
-
|
|
15817
|
+
value = value.replace(HOME_VARS, home);
|
|
15818
|
+
const temp = tempRootsOf(ctx)[0];
|
|
15819
|
+
if (temp) value = value.replace(TEMP_VARS, temp);
|
|
15820
|
+
if (isWin(ctx)) value = fromGitBash(value, temp);
|
|
15821
|
+
return p.resolve(ctx.dir ?? ctx.cwd, value);
|
|
15100
15822
|
}
|
|
15101
15823
|
/** True when `target` resolves inside `root` (or equals it). Case-insensitive on Windows. */
|
|
15102
15824
|
function isInside$1(target, root, ctx) {
|
|
15825
|
+
const rel = relative$1(root, target, ctx);
|
|
15826
|
+
return rel === "" || !rel.startsWith("..") && !pathApi(ctx).isAbsolute(rel);
|
|
15827
|
+
}
|
|
15828
|
+
function relative$1(root, target, ctx) {
|
|
15103
15829
|
const p = pathApi(ctx);
|
|
15104
|
-
const
|
|
15105
|
-
|
|
15106
|
-
const r = p.resolve(s);
|
|
15107
|
-
return win ? r.toLowerCase() : r;
|
|
15108
|
-
};
|
|
15109
|
-
const rel = p.relative(norm(root), norm(target));
|
|
15110
|
-
return rel === "" || !rel.startsWith("..") && !p.isAbsolute(rel);
|
|
15830
|
+
const norm = (s) => isWin(ctx) ? p.resolve(s).toLowerCase() : p.resolve(s);
|
|
15831
|
+
return p.relative(norm(root), norm(target));
|
|
15111
15832
|
}
|
|
15112
15833
|
/** True when `target` is inside the worktree or one of the extra roots. */
|
|
15113
15834
|
function isInWorkspace(target, ctx) {
|
|
15114
15835
|
const abs = resolveAgainst(target, ctx);
|
|
15115
15836
|
return [ctx.cwd, ...ctx.extraRoots ?? []].some((root) => isInside$1(abs, root, ctx));
|
|
15116
15837
|
}
|
|
15838
|
+
/** True when `target` is strictly inside one of the OS temp directories (never the root). */
|
|
15839
|
+
function isInTemp(target, ctx) {
|
|
15840
|
+
const abs = resolveAgainst(target, ctx);
|
|
15841
|
+
return tempRootsOf(ctx).some((root) => {
|
|
15842
|
+
return relative$1(root, abs, ctx) !== "" && isInside$1(abs, root, ctx);
|
|
15843
|
+
});
|
|
15844
|
+
}
|
|
15845
|
+
/** True when agents may write `target` without asking: the worktree, extra roots or temp. */
|
|
15846
|
+
function isWritable(target, ctx) {
|
|
15847
|
+
return isInWorkspace(target, ctx) || isInTemp(target, ctx);
|
|
15848
|
+
}
|
|
15849
|
+
/**
|
|
15850
|
+
* Usable temp roots from `candidates`: absolute, deduplicated, and never a filesystem root,
|
|
15851
|
+
* the home directory or one of its parents (a stray `TMPDIR=/` must not open everything).
|
|
15852
|
+
*/
|
|
15853
|
+
function tempRootsFrom(candidates, platform, home) {
|
|
15854
|
+
const ctx = {
|
|
15855
|
+
cwd: "/",
|
|
15856
|
+
platform
|
|
15857
|
+
};
|
|
15858
|
+
const p = pathApi(ctx);
|
|
15859
|
+
const out = [];
|
|
15860
|
+
for (const candidate of candidates) {
|
|
15861
|
+
if (!candidate || !p.isAbsolute(candidate)) continue;
|
|
15862
|
+
const root = p.resolve(candidate);
|
|
15863
|
+
if (p.parse(root).root === root || p.dirname(root) === root) continue;
|
|
15864
|
+
if (home && isInside$1(home, root, ctx)) continue;
|
|
15865
|
+
if (!out.some((r) => relative$1(r, root, ctx) === "")) out.push(root);
|
|
15866
|
+
}
|
|
15867
|
+
return out;
|
|
15868
|
+
}
|
|
15869
|
+
const realpath = (p) => {
|
|
15870
|
+
try {
|
|
15871
|
+
return realpathSync.native(p);
|
|
15872
|
+
} catch {
|
|
15873
|
+
return;
|
|
15874
|
+
}
|
|
15875
|
+
};
|
|
15876
|
+
let cachedTempRoots;
|
|
15877
|
+
/**
|
|
15878
|
+
* The OS temp directories for the running platform: `os.tmpdir()`, `%TEMP%` and `%TMP%` on
|
|
15879
|
+
* Windows, `$TMPDIR` and `/tmp` elsewhere, each also by its real path (macOS symlinks
|
|
15880
|
+
* `/tmp`, Windows may report a short 8.3 name). Empty when asked about another platform.
|
|
15881
|
+
*/
|
|
15882
|
+
function osTempRoots(platform = process.platform) {
|
|
15883
|
+
if (platform !== process.platform) return [];
|
|
15884
|
+
if (cachedTempRoots) return cachedTempRoots;
|
|
15885
|
+
const env = process.env;
|
|
15886
|
+
cachedTempRoots = tempRootsFrom([os.tmpdir(), ...platform === "win32" ? [env.TEMP, env.TMP] : [env.TMPDIR, "/tmp"]].flatMap((c) => c ? [c, realpath(c)] : []), platform, os.homedir());
|
|
15887
|
+
return cachedTempRoots;
|
|
15888
|
+
}
|
|
15117
15889
|
const SECRET_BASENAMES = /* @__PURE__ */ new Set([
|
|
15118
15890
|
".env",
|
|
15119
15891
|
".npmrc",
|
|
@@ -15157,33 +15929,277 @@ function isSecretPath(target) {
|
|
|
15157
15929
|
if (SECRET_EXTENSIONS.some((ext) => base.endsWith(ext))) return true;
|
|
15158
15930
|
return /(^|\/)\.(ssh|aws|gnupg|docker)\//i.test(normalized) && !base.endsWith(".pub");
|
|
15159
15931
|
}
|
|
15160
|
-
// ../../packages/providers/src/policy/
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
|
|
15166
|
-
|
|
15167
|
-
/**
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15172
|
-
|
|
15173
|
-
|
|
15174
|
-
|
|
15175
|
-
|
|
15176
|
-
|
|
15177
|
-
/**
|
|
15178
|
-
|
|
15932
|
+
// ../../packages/providers/src/policy/shell.ts
|
|
15933
|
+
/** Parse a command line into its simple commands. */
|
|
15934
|
+
function parseShell(src, dialect = "auto") {
|
|
15935
|
+
return new Lexer(src, dialect).list(null);
|
|
15936
|
+
}
|
|
15937
|
+
/** Placeholder for heredoc and here-string bodies, which are data, never commands. */
|
|
15938
|
+
const BODY = "<body>";
|
|
15939
|
+
/** The command being built and the word being read. */
|
|
15940
|
+
var Builder = class {
|
|
15941
|
+
out;
|
|
15942
|
+
cmd = {
|
|
15943
|
+
words: [],
|
|
15944
|
+
writes: [],
|
|
15945
|
+
nested: []
|
|
15946
|
+
};
|
|
15947
|
+
word = "";
|
|
15948
|
+
inWord = false;
|
|
15949
|
+
/** Where the current word goes: an argument, a redirect target or a heredoc delimiter. */
|
|
15950
|
+
target = "word";
|
|
15951
|
+
heredocStrip = false;
|
|
15952
|
+
heredocs = [];
|
|
15953
|
+
constructor(out) {
|
|
15954
|
+
this.out = out;
|
|
15955
|
+
}
|
|
15956
|
+
add(text) {
|
|
15957
|
+
this.word += text;
|
|
15958
|
+
this.inWord = true;
|
|
15959
|
+
}
|
|
15960
|
+
endWord() {
|
|
15961
|
+
if (!this.inWord) return;
|
|
15962
|
+
if (this.target === "write") this.cmd.writes.push(this.word);
|
|
15963
|
+
else if (this.target === "heredoc") this.heredocs.push({
|
|
15964
|
+
delim: this.word.replace(/\\/g, ""),
|
|
15965
|
+
strip: this.heredocStrip
|
|
15966
|
+
});
|
|
15967
|
+
else if (this.target === "word") this.cmd.words.push(this.word);
|
|
15968
|
+
this.target = "word";
|
|
15969
|
+
this.word = "";
|
|
15970
|
+
this.inWord = false;
|
|
15971
|
+
}
|
|
15972
|
+
endCommand() {
|
|
15973
|
+
this.endWord();
|
|
15974
|
+
this.target = "word";
|
|
15975
|
+
const { cmd } = this;
|
|
15976
|
+
if (cmd.words.length || cmd.writes.length || cmd.nested.length) this.out.push(cmd);
|
|
15977
|
+
this.cmd = {
|
|
15978
|
+
words: [],
|
|
15979
|
+
writes: [],
|
|
15980
|
+
nested: []
|
|
15981
|
+
};
|
|
15982
|
+
}
|
|
15983
|
+
};
|
|
15984
|
+
var Lexer = class {
|
|
15985
|
+
src;
|
|
15986
|
+
dialect;
|
|
15987
|
+
i = 0;
|
|
15988
|
+
constructor(src, dialect) {
|
|
15989
|
+
this.src = src;
|
|
15990
|
+
this.dialect = dialect;
|
|
15991
|
+
}
|
|
15992
|
+
/** Read simple commands until `closer` (`)` or a backtick) or the end of the input. */
|
|
15993
|
+
list(closer) {
|
|
15994
|
+
const out = [];
|
|
15995
|
+
const b = new Builder(out);
|
|
15996
|
+
const { src } = this;
|
|
15997
|
+
while (this.i < src.length) {
|
|
15998
|
+
const c = src[this.i];
|
|
15999
|
+
const next = src[this.i + 1];
|
|
16000
|
+
if (closer && c === closer) {
|
|
16001
|
+
this.i += 1;
|
|
16002
|
+
b.endCommand();
|
|
16003
|
+
return out;
|
|
16004
|
+
}
|
|
16005
|
+
if (c === "\n" || c === "\r") {
|
|
16006
|
+
b.endCommand();
|
|
16007
|
+
this.i += c === "\r" && next === "\n" ? 2 : 1;
|
|
16008
|
+
this.skipHeredocs(b.heredocs.splice(0));
|
|
16009
|
+
} else if (c === " " || c === " ") {
|
|
16010
|
+
b.endWord();
|
|
16011
|
+
this.i += 1;
|
|
16012
|
+
} else if (c === "#" && !b.inWord) while (this.i < src.length && src[this.i] !== "\n" && src[this.i] !== "\r") this.i += 1;
|
|
16013
|
+
else if (c === ";") {
|
|
16014
|
+
b.endCommand();
|
|
16015
|
+
this.i += 1;
|
|
16016
|
+
} else if (c === "|") {
|
|
16017
|
+
b.endCommand();
|
|
16018
|
+
this.i += next === "|" || next === "&" ? 2 : 1;
|
|
16019
|
+
} else if (c === "&" && next === ">") {
|
|
16020
|
+
b.endWord();
|
|
16021
|
+
b.target = "write";
|
|
16022
|
+
this.i += src[this.i + 2] === ">" ? 3 : 2;
|
|
16023
|
+
} else if (c === "&") {
|
|
16024
|
+
b.endCommand();
|
|
16025
|
+
this.i += next === "&" ? 2 : 1;
|
|
16026
|
+
} else if (c === ">" || c === "<") this.redirect(b, c);
|
|
16027
|
+
else if (c === "'" || c === "\"") {
|
|
16028
|
+
if (b.inWord && b.word.endsWith("@") && this.lineEndsAt(this.i + 1)) {
|
|
16029
|
+
this.skipHereString(c);
|
|
16030
|
+
b.add(BODY);
|
|
16031
|
+
} else if (c === "'") b.add(this.readSingle());
|
|
16032
|
+
else b.add(this.readDouble(b));
|
|
16033
|
+
} else if (c === "\\" && this.dialect !== "powershell") {
|
|
16034
|
+
if (next === "\n" || next === "\r") this.i += next === "\r" && src[this.i + 2] === "\n" ? 3 : 2;
|
|
16035
|
+
else {
|
|
16036
|
+
b.add(src.slice(this.i, this.i + 2));
|
|
16037
|
+
this.i += 2;
|
|
16038
|
+
}
|
|
16039
|
+
} else if (c === "`") this.backtick(b);
|
|
16040
|
+
else if (c === "(") {
|
|
16041
|
+
const start = this.i;
|
|
16042
|
+
this.i += 1;
|
|
16043
|
+
b.cmd.nested.push(this.list(")"));
|
|
16044
|
+
b.add(src.slice(start, this.i));
|
|
16045
|
+
} else {
|
|
16046
|
+
b.add(c);
|
|
16047
|
+
this.i += 1;
|
|
16048
|
+
}
|
|
16049
|
+
}
|
|
16050
|
+
b.endCommand();
|
|
16051
|
+
return out;
|
|
16052
|
+
}
|
|
16053
|
+
/** `>`, `>>`, `>|`, `2>`, `*>`, `2>&1`, `<`, `<<EOF`, `<<-EOF`, `<<<`, `<(…)`, `>(…)`. */
|
|
16054
|
+
redirect(b, c) {
|
|
16055
|
+
const { src } = this;
|
|
16056
|
+
if (b.inWord && /^(\d+|\*)$/.test(b.word)) {
|
|
16057
|
+
b.word = "";
|
|
16058
|
+
b.inWord = false;
|
|
16059
|
+
} else b.endWord();
|
|
16060
|
+
const next = src[this.i + 1];
|
|
16061
|
+
if (next === "(") {
|
|
16062
|
+
const start = this.i;
|
|
16063
|
+
this.i += 2;
|
|
16064
|
+
b.cmd.nested.push(this.list(")"));
|
|
16065
|
+
b.add(src.slice(start, this.i));
|
|
16066
|
+
return;
|
|
16067
|
+
}
|
|
16068
|
+
if (c === "<") {
|
|
16069
|
+
if (next === "<" && src[this.i + 2] === "<") {
|
|
16070
|
+
b.target = "skip";
|
|
16071
|
+
this.i += 3;
|
|
16072
|
+
} else if (next === "<") {
|
|
16073
|
+
this.i += 2;
|
|
16074
|
+
b.heredocStrip = src[this.i] === "-";
|
|
16075
|
+
if (b.heredocStrip) this.i += 1;
|
|
16076
|
+
b.target = "heredoc";
|
|
16077
|
+
} else this.i += 1;
|
|
16078
|
+
return;
|
|
16079
|
+
}
|
|
16080
|
+
let j = this.i + 1;
|
|
16081
|
+
if (src[j] === ">") j += 1;
|
|
16082
|
+
if (src[j] === "|") j += 1;
|
|
16083
|
+
if (src[j] === "&") {
|
|
16084
|
+
const dup = /^&(\d+|-)/.exec(src.slice(j));
|
|
16085
|
+
if (dup) {
|
|
16086
|
+
this.i = j + dup[0].length;
|
|
16087
|
+
return;
|
|
16088
|
+
}
|
|
16089
|
+
j += 1;
|
|
16090
|
+
}
|
|
16091
|
+
b.target = "write";
|
|
16092
|
+
this.i = j;
|
|
16093
|
+
}
|
|
16094
|
+
/**
|
|
16095
|
+
* PowerShell escapes the next character; POSIX starts a command substitution. (A closing
|
|
16096
|
+
* backtick never gets here: `list` checks for its closer first.)
|
|
16097
|
+
*/
|
|
16098
|
+
backtick(b) {
|
|
16099
|
+
const { src } = this;
|
|
16100
|
+
const next = src[this.i + 1];
|
|
16101
|
+
if (this.dialect === "powershell" || this.dialect === "auto" && (next === void 0 || /\s/.test(next))) {
|
|
16102
|
+
if (next === "\n" || next === "\r") this.i += next === "\r" && src[this.i + 2] === "\n" ? 3 : 2;
|
|
16103
|
+
else {
|
|
16104
|
+
b.add(src.slice(this.i + 1, this.i + 2));
|
|
16105
|
+
this.i += 2;
|
|
16106
|
+
}
|
|
16107
|
+
return;
|
|
16108
|
+
}
|
|
16109
|
+
const start = this.i;
|
|
16110
|
+
this.i += 1;
|
|
16111
|
+
b.cmd.nested.push(this.list("`"));
|
|
16112
|
+
b.add(src.slice(start, this.i));
|
|
16113
|
+
}
|
|
16114
|
+
readSingle() {
|
|
16115
|
+
const end = this.src.indexOf("'", this.i + 1);
|
|
16116
|
+
const stop = end === -1 ? this.src.length : end;
|
|
16117
|
+
const text = this.src.slice(this.i + 1, stop);
|
|
16118
|
+
this.i = stop + 1;
|
|
16119
|
+
return text;
|
|
16120
|
+
}
|
|
16121
|
+
/** A double-quoted string: escapes, and `$(…)` substitutions run as nested commands. */
|
|
16122
|
+
readDouble(b) {
|
|
16123
|
+
const { src } = this;
|
|
16124
|
+
let text = "";
|
|
16125
|
+
this.i += 1;
|
|
16126
|
+
while (this.i < src.length) {
|
|
16127
|
+
const c = src[this.i];
|
|
16128
|
+
const next = src[this.i + 1];
|
|
16129
|
+
if (c === "\"") {
|
|
16130
|
+
this.i += 1;
|
|
16131
|
+
return text;
|
|
16132
|
+
}
|
|
16133
|
+
if (c === "\\" && this.dialect !== "powershell" && next && "\"\\$`\n".includes(next)) {
|
|
16134
|
+
text += next;
|
|
16135
|
+
this.i += 2;
|
|
16136
|
+
} else if (c === "`" && this.dialect !== "posix" && next !== void 0) {
|
|
16137
|
+
text += next;
|
|
16138
|
+
this.i += 2;
|
|
16139
|
+
} else if (c === "`") {
|
|
16140
|
+
const start = this.i;
|
|
16141
|
+
this.i += 1;
|
|
16142
|
+
b.cmd.nested.push(this.list("`"));
|
|
16143
|
+
text += src.slice(start, this.i);
|
|
16144
|
+
} else if (c === "$" && next === "(") {
|
|
16145
|
+
const start = this.i;
|
|
16146
|
+
this.i += 2;
|
|
16147
|
+
b.cmd.nested.push(this.list(")"));
|
|
16148
|
+
text += src.slice(start, this.i);
|
|
16149
|
+
} else {
|
|
16150
|
+
text += c;
|
|
16151
|
+
this.i += 1;
|
|
16152
|
+
}
|
|
16153
|
+
}
|
|
16154
|
+
return text;
|
|
16155
|
+
}
|
|
16156
|
+
/** True when only spaces or tabs remain before the end of the line at `j`. */
|
|
16157
|
+
lineEndsAt(j) {
|
|
16158
|
+
return /^[ \t]*(\r?\n|$)/.exec(this.src.slice(j)) !== null;
|
|
16159
|
+
}
|
|
16160
|
+
/** Skip a PowerShell here-string (`@'` … `'@`) whose opening quote is at `this.i`. */
|
|
16161
|
+
skipHereString(quote) {
|
|
16162
|
+
const close = new RegExp(`(^|\\n)${quote}@`, "g");
|
|
16163
|
+
close.lastIndex = this.i + 1;
|
|
16164
|
+
const m = close.exec(this.src);
|
|
16165
|
+
this.i = m ? m.index + m[0].length : this.src.length;
|
|
16166
|
+
}
|
|
16167
|
+
/** Skip the bodies of heredocs opened on the line that just ended. */
|
|
16168
|
+
skipHeredocs(pending) {
|
|
16169
|
+
const { src } = this;
|
|
16170
|
+
for (const { delim, strip } of pending) while (this.i < src.length) {
|
|
16171
|
+
const nl = src.indexOf("\n", this.i);
|
|
16172
|
+
const end = nl === -1 ? src.length : nl;
|
|
16173
|
+
let line = src.slice(this.i, end).replace(/\r$/, "");
|
|
16174
|
+
if (strip) line = line.replace(/^\t+/, "");
|
|
16175
|
+
this.i = nl === -1 ? src.length : nl + 1;
|
|
16176
|
+
if (line === delim) break;
|
|
16177
|
+
}
|
|
16178
|
+
}
|
|
16179
|
+
};
|
|
16180
|
+
// ../../packages/providers/src/policy/risk.ts
|
|
16181
|
+
const SAFE = { risky: false };
|
|
16182
|
+
const risky = (action, reason) => ({
|
|
16183
|
+
risky: true,
|
|
16184
|
+
action,
|
|
16185
|
+
reason
|
|
16186
|
+
});
|
|
16187
|
+
/** How deep `bash -c "…"` style inline scripts are followed. */
|
|
16188
|
+
const MAX_DEPTH = 4;
|
|
16189
|
+
/**
|
|
16190
|
+
* Words that don't change what runs: elevation (`sudo`), wrappers (`env X=1`, `command`,
|
|
16191
|
+
* `exec`) and shell keywords that start a compound command (`if`, `then`, `do`, `{`, ...).
|
|
16192
|
+
*/
|
|
16193
|
+
function unwrap(words) {
|
|
15179
16194
|
let elevated = false;
|
|
15180
|
-
let rest =
|
|
16195
|
+
let rest = words;
|
|
15181
16196
|
for (;;) {
|
|
15182
16197
|
const head = rest[0]?.toLowerCase();
|
|
15183
16198
|
if (head === "sudo" || head === "doas" || head === "runas" || head === "gsudo") {
|
|
15184
16199
|
elevated = true;
|
|
15185
16200
|
rest = rest.slice(1).filter((t, i) => !(i === 0 && t.startsWith("-")));
|
|
15186
16201
|
} else if (head === "env" || head === "command" || head === "exec" || head === "nohup") rest = rest.slice(1);
|
|
16202
|
+
else if (head && KEYWORDS.has(head)) rest = rest.slice(1);
|
|
15187
16203
|
else if (head && /^[A-Za-z_][A-Za-z0-9_]*=/.test(head)) rest = rest.slice(1);
|
|
15188
16204
|
else break;
|
|
15189
16205
|
}
|
|
@@ -15192,6 +16208,18 @@ function unwrap(tokens) {
|
|
|
15192
16208
|
elevated
|
|
15193
16209
|
};
|
|
15194
16210
|
}
|
|
16211
|
+
const KEYWORDS = /* @__PURE__ */ new Set([
|
|
16212
|
+
"if",
|
|
16213
|
+
"then",
|
|
16214
|
+
"else",
|
|
16215
|
+
"elif",
|
|
16216
|
+
"do",
|
|
16217
|
+
"while",
|
|
16218
|
+
"until",
|
|
16219
|
+
"time",
|
|
16220
|
+
"!",
|
|
16221
|
+
"{"
|
|
16222
|
+
]);
|
|
15195
16223
|
const binName = (token) => (token ?? "").replace(/\\/g, "/").split("/").pop()?.toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/, "") ?? "";
|
|
15196
16224
|
const DOWNLOADERS = /\b(curl|wget|iwr|irm|invoke-webrequest|invoke-restmethod)\b|downloadstring/i;
|
|
15197
16225
|
const SHELLS = /^\s*(sudo\s+)?(sh|bash|zsh|fish|dash|ksh|iex|invoke-expression|python3?|node|pwsh|powershell|perl|ruby)(\s|$)/i;
|
|
@@ -15201,18 +16229,109 @@ function classifyCommand(command, ctx) {
|
|
|
15201
16229
|
for (let i = 1; i < pipeline.length; i += 1) if (DOWNLOADERS.test(pipeline[i - 1] ?? "") && SHELLS.test(pipeline[i] ?? "")) return risky("network", "Pipes a downloaded script straight into a shell");
|
|
15202
16230
|
if (/\b(iex|invoke-expression)\b[^|]*\b(irm|iwr|invoke-restmethod|invoke-webrequest|downloadstring)\b/i.test(command)) return risky("network", "Executes a downloaded script");
|
|
15203
16231
|
if (/\b(bash|sh|zsh)\s+(-c\s+)?["']?\$\(\s*(curl|wget)\b/i.test(command) || /<\(\s*(curl|wget)\b/i.test(command)) return risky("network", "Executes a downloaded script");
|
|
15204
|
-
|
|
15205
|
-
|
|
16232
|
+
return classifyList(parseShell(command, ctx.shell ?? "auto"), ctx, 0);
|
|
16233
|
+
}
|
|
16234
|
+
/**
|
|
16235
|
+
* Classify commands in order. A `cd` moves where later relative paths resolve; commands in
|
|
16236
|
+
* substitutions and subshells are classified too, without their `cd` leaking out.
|
|
16237
|
+
*/
|
|
16238
|
+
function classifyList(list, ctx, depth) {
|
|
16239
|
+
let here = ctx;
|
|
16240
|
+
for (const cmd of list) {
|
|
16241
|
+
for (const nested of cmd.nested) {
|
|
16242
|
+
const result = classifyList(nested, here, depth);
|
|
16243
|
+
if (result.risky) return result;
|
|
16244
|
+
}
|
|
16245
|
+
const result = classifySimple(cmd, here, depth);
|
|
15206
16246
|
if (result.risky) return result;
|
|
16247
|
+
here = afterCd(cmd, here);
|
|
15207
16248
|
}
|
|
15208
16249
|
return SAFE;
|
|
15209
16250
|
}
|
|
15210
|
-
|
|
15211
|
-
|
|
16251
|
+
const CD = /* @__PURE__ */ new Set([
|
|
16252
|
+
"cd",
|
|
16253
|
+
"pushd",
|
|
16254
|
+
"chdir",
|
|
16255
|
+
"set-location",
|
|
16256
|
+
"sl",
|
|
16257
|
+
"push-location"
|
|
16258
|
+
]);
|
|
16259
|
+
/** The context after `cmd` when it changes directory (`cd dir`, `Set-Location -Path dir`). */
|
|
16260
|
+
function afterCd(cmd, ctx) {
|
|
16261
|
+
const { tokens } = unwrap(cmd.words);
|
|
16262
|
+
if (!CD.has(binName(tokens[0]))) return ctx;
|
|
16263
|
+
const args = tokens.slice(1);
|
|
16264
|
+
if (args.includes("-")) return ctx;
|
|
16265
|
+
const target = namedArg(args, ["-path", "-literalpath"]) ?? args.find((a) => !a.startsWith("-")) ?? "~";
|
|
16266
|
+
return {
|
|
16267
|
+
...ctx,
|
|
16268
|
+
dir: resolveAgainst(target, ctx)
|
|
16269
|
+
};
|
|
16270
|
+
}
|
|
16271
|
+
/** The value after a PowerShell-style named parameter (`-Path x`), if present. */
|
|
16272
|
+
function namedArg(args, names) {
|
|
16273
|
+
const i = args.findIndex((a) => names.includes(a.toLowerCase()));
|
|
16274
|
+
return i >= 0 ? args[i + 1] : void 0;
|
|
16275
|
+
}
|
|
16276
|
+
/** The script of an inline shell invocation (`bash -c "…"`, `pwsh -Command …`, `cmd /c …`). */
|
|
16277
|
+
function inlineScript(bin, args) {
|
|
16278
|
+
if ([
|
|
16279
|
+
"sh",
|
|
16280
|
+
"bash",
|
|
16281
|
+
"zsh",
|
|
16282
|
+
"dash",
|
|
16283
|
+
"ksh",
|
|
16284
|
+
"ash",
|
|
16285
|
+
"fish"
|
|
16286
|
+
].includes(bin)) {
|
|
16287
|
+
const flag = args.findIndex((a) => /^-[a-z]*c[a-z]*$/i.test(a));
|
|
16288
|
+
const text = flag >= 0 ? args.slice(flag + 1).find((a) => !a.startsWith("-")) : void 0;
|
|
16289
|
+
return text ? {
|
|
16290
|
+
text,
|
|
16291
|
+
dialect: "posix"
|
|
16292
|
+
} : void 0;
|
|
16293
|
+
}
|
|
16294
|
+
if (bin === "pwsh" || bin === "powershell") {
|
|
16295
|
+
const flag = args.findIndex((a) => /^-(c|command)$/i.test(a));
|
|
16296
|
+
return flag >= 0 ? {
|
|
16297
|
+
text: args.slice(flag + 1).join(" "),
|
|
16298
|
+
dialect: "powershell"
|
|
16299
|
+
} : void 0;
|
|
16300
|
+
}
|
|
16301
|
+
if (bin === "cmd") {
|
|
16302
|
+
const flag = args.findIndex((a) => /^\/[ck]$/i.test(a));
|
|
16303
|
+
return flag >= 0 ? {
|
|
16304
|
+
text: args.slice(flag + 1).join(" "),
|
|
16305
|
+
dialect: "auto"
|
|
16306
|
+
} : void 0;
|
|
16307
|
+
}
|
|
16308
|
+
}
|
|
16309
|
+
/**
|
|
16310
|
+
* The argument as a path, or undefined when it can't be one: inline code, multi-line text
|
|
16311
|
+
* or anything with shell or code punctuation (`selected={row.key`) is not a file name.
|
|
16312
|
+
*/
|
|
16313
|
+
function asPath(arg) {
|
|
16314
|
+
const value = arg.replace(/^-+[a-z-]*=/i, "");
|
|
16315
|
+
if (!value || /[\n\r{}()<>|;=`]/.test(value)) return void 0;
|
|
16316
|
+
return value;
|
|
16317
|
+
}
|
|
16318
|
+
const DISCARD = /^(\/dev\/(null|stdout|stderr|tty)|nul|\$null)$/i;
|
|
16319
|
+
function classifySimple(cmd, ctx, depth) {
|
|
16320
|
+
const { tokens, elevated } = unwrap(cmd.words);
|
|
15212
16321
|
if (elevated) return risky("other", "Runs a command with elevated privileges");
|
|
15213
16322
|
const bin = binName(tokens[0]);
|
|
15214
16323
|
const args = tokens.slice(1);
|
|
15215
16324
|
const lower = args.map((a) => a.toLowerCase());
|
|
16325
|
+
const script = inlineScript(bin, args);
|
|
16326
|
+
if (script && depth < MAX_DEPTH) {
|
|
16327
|
+
const inner = classifyList(parseShell(script.text, script.dialect), ctx, depth + 1);
|
|
16328
|
+
if (inner.risky) return inner;
|
|
16329
|
+
}
|
|
16330
|
+
for (const target of cmd.writes) {
|
|
16331
|
+
if (DISCARD.test(target)) continue;
|
|
16332
|
+
const write = classifyWrite(target, ctx);
|
|
16333
|
+
if (write.risky) return write;
|
|
16334
|
+
}
|
|
15216
16335
|
if (bin === "git") return classifyGit(lower);
|
|
15217
16336
|
const install = classifyInstall(bin, lower);
|
|
15218
16337
|
if (install.risky) return install;
|
|
@@ -15230,44 +16349,51 @@ function classifySimple(simple, ctx) {
|
|
|
15230
16349
|
const targets = args.filter((a) => !a.startsWith("-") && !/^\/[a-z]$/i.test(a));
|
|
15231
16350
|
for (const target of targets) {
|
|
15232
16351
|
if (target === "*" || /^[\\/]$/.test(target) || /^[a-z]:[\\/]?$/i.test(target)) return risky("outside_worktree", `Deletes ${target}`);
|
|
15233
|
-
if (!
|
|
16352
|
+
if (!isWritable(target, ctx)) return risky("outside_worktree", `Deletes ${target}, outside the worktree`);
|
|
15234
16353
|
}
|
|
15235
16354
|
}
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
if (
|
|
15240
|
-
if (isSecretPath(target)) return risky("secrets", `Writes the secrets file ${target}`);
|
|
16355
|
+
const dest = copyDestination(bin, args);
|
|
16356
|
+
if (dest) {
|
|
16357
|
+
const write = classifyWrite(dest, ctx);
|
|
16358
|
+
if (write.risky) return write;
|
|
15241
16359
|
}
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
"move",
|
|
15247
|
-
"copy-item",
|
|
15248
|
-
"move-item",
|
|
15249
|
-
"xcopy",
|
|
15250
|
-
"robocopy",
|
|
15251
|
-
"tee",
|
|
15252
|
-
"set-content",
|
|
15253
|
-
"out-file",
|
|
15254
|
-
"add-content"
|
|
15255
|
-
].includes(bin)) {
|
|
15256
|
-
const positional = args.filter((a) => !a.startsWith("-") && !/^\/[a-z]+$/i.test(a));
|
|
15257
|
-
const dest = [
|
|
15258
|
-
"tee",
|
|
15259
|
-
"set-content",
|
|
15260
|
-
"out-file",
|
|
15261
|
-
"add-content"
|
|
15262
|
-
].includes(bin) ? positional[0] : positional.at(-1);
|
|
15263
|
-
if (dest && !isInWorkspace(dest, ctx)) return risky("outside_worktree", `Writes ${dest}, outside the worktree`);
|
|
15264
|
-
}
|
|
15265
|
-
const secret = args.find((a) => isSecretPath(a.replace(/^-+[a-z-]*=/i, "")));
|
|
16360
|
+
const secret = args.find((a) => {
|
|
16361
|
+
const value = asPath(a);
|
|
16362
|
+
return value !== void 0 && isSecretPath(value);
|
|
16363
|
+
});
|
|
15266
16364
|
if (secret) return risky("secrets", `Accesses the secrets file ${secret}`);
|
|
15267
16365
|
const listsEnvDrive = /^(get-childitem|gci|ls|dir)$/.test(bin) && lower.some((a) => a.startsWith("env:"));
|
|
15268
16366
|
if (bin === "printenv" || listsEnvDrive) return risky("secrets", "Dumps environment variables");
|
|
15269
16367
|
return SAFE;
|
|
15270
16368
|
}
|
|
16369
|
+
const COPIES = [
|
|
16370
|
+
"cp",
|
|
16371
|
+
"mv",
|
|
16372
|
+
"copy",
|
|
16373
|
+
"move",
|
|
16374
|
+
"copy-item",
|
|
16375
|
+
"move-item",
|
|
16376
|
+
"xcopy",
|
|
16377
|
+
"robocopy"
|
|
16378
|
+
];
|
|
16379
|
+
const WRITERS = [
|
|
16380
|
+
"tee",
|
|
16381
|
+
"set-content",
|
|
16382
|
+
"out-file",
|
|
16383
|
+
"add-content"
|
|
16384
|
+
];
|
|
16385
|
+
/** Where a copy, move or file-writing command writes. */
|
|
16386
|
+
function copyDestination(bin, args) {
|
|
16387
|
+
if (!COPIES.includes(bin) && !WRITERS.includes(bin)) return void 0;
|
|
16388
|
+
const named = WRITERS.includes(bin) ? namedArg(args, [
|
|
16389
|
+
"-path",
|
|
16390
|
+
"-filepath",
|
|
16391
|
+
"-literalpath"
|
|
16392
|
+
]) : namedArg(args, ["-destination"]);
|
|
16393
|
+
if (named) return named;
|
|
16394
|
+
const positional = args.filter((a) => !a.startsWith("-") && !/^\/[a-z]+$/i.test(a));
|
|
16395
|
+
return WRITERS.includes(bin) ? positional[0] : positional.at(-1);
|
|
16396
|
+
}
|
|
15271
16397
|
function classifyGit(args) {
|
|
15272
16398
|
let i = 0;
|
|
15273
16399
|
while (i < args.length && args[i]?.startsWith("-")) i += args[i] === "-c" || args[i] === "-C" ? 2 : 1;
|
|
@@ -15344,11 +16470,16 @@ function classifyInstall(bin, args) {
|
|
|
15344
16470
|
if (bin === "install-module" || bin === "install-package") return risky("other", `Installs a system-wide module (${bin})`);
|
|
15345
16471
|
return SAFE;
|
|
15346
16472
|
}
|
|
15347
|
-
/**
|
|
16473
|
+
/**
|
|
16474
|
+
* Classify a file write/edit. The worktree, extra roots and OS temp are fine; secrets ask. A
|
|
16475
|
+
* secrets file in temp counts as outside the worktree, so Full autonomy (which only asks for
|
|
16476
|
+
* writes outside the worktree) still asks for it.
|
|
16477
|
+
*/
|
|
15348
16478
|
function classifyWrite(target, ctx) {
|
|
15349
|
-
if (!
|
|
15350
|
-
if (isSecretPath(target)) return
|
|
15351
|
-
return
|
|
16479
|
+
if (!isWritable(target, ctx)) return risky("outside_worktree", `Writes ${target}, outside the worktree`);
|
|
16480
|
+
if (!isSecretPath(target)) return SAFE;
|
|
16481
|
+
if (!isInWorkspace(target, ctx)) return risky("outside_worktree", `Writes the secrets file ${target} in the temp directory`);
|
|
16482
|
+
return risky("secrets", `Writes the secrets file ${target}`);
|
|
15352
16483
|
}
|
|
15353
16484
|
/** Classify a file read or search. Reads anywhere are fine except credential files. */
|
|
15354
16485
|
function classifyRead(target, _ctx) {
|
|
@@ -15369,130 +16500,6 @@ function classifyFetch(url, ctx) {
|
|
|
15369
16500
|
return rule.startsWith("*.") ? host.endsWith(rule.slice(1)) : host === rule;
|
|
15370
16501
|
}) ? SAFE : risky("network", `Fetches ${host}, which is not on the allowed host list`);
|
|
15371
16502
|
}
|
|
15372
|
-
// ../../packages/providers/src/policy/read-only.ts
|
|
15373
|
-
/**
|
|
15374
|
-
* Read-only sessions (the orchestrator and PMs, §11.2) may inspect a repository but never
|
|
15375
|
-
* change it. This allowlist decides which shell commands they may run: commands that only
|
|
15376
|
-
* read, with no output redirection and no command substitution. Anything else is denied.
|
|
15377
|
-
*/
|
|
15378
|
-
/** Programs that only read (or print). */
|
|
15379
|
-
const READ_ONLY_BINS = /* @__PURE__ */ new Set([
|
|
15380
|
-
"ls",
|
|
15381
|
-
"dir",
|
|
15382
|
-
"cat",
|
|
15383
|
-
"type",
|
|
15384
|
-
"head",
|
|
15385
|
-
"tail",
|
|
15386
|
-
"less",
|
|
15387
|
-
"more",
|
|
15388
|
-
"wc",
|
|
15389
|
-
"grep",
|
|
15390
|
-
"egrep",
|
|
15391
|
-
"fgrep",
|
|
15392
|
-
"rg",
|
|
15393
|
-
"find",
|
|
15394
|
-
"fd",
|
|
15395
|
-
"tree",
|
|
15396
|
-
"pwd",
|
|
15397
|
-
"echo",
|
|
15398
|
-
"printf",
|
|
15399
|
-
"which",
|
|
15400
|
-
"where",
|
|
15401
|
-
"whoami",
|
|
15402
|
-
"date",
|
|
15403
|
-
"stat",
|
|
15404
|
-
"file",
|
|
15405
|
-
"du",
|
|
15406
|
-
"df",
|
|
15407
|
-
"sort",
|
|
15408
|
-
"uniq",
|
|
15409
|
-
"cut",
|
|
15410
|
-
"diff",
|
|
15411
|
-
"jq",
|
|
15412
|
-
"basename",
|
|
15413
|
-
"dirname",
|
|
15414
|
-
"realpath",
|
|
15415
|
-
"true",
|
|
15416
|
-
"get-childitem",
|
|
15417
|
-
"gci",
|
|
15418
|
-
"get-content",
|
|
15419
|
-
"gc",
|
|
15420
|
-
"get-item",
|
|
15421
|
-
"gi",
|
|
15422
|
-
"get-location",
|
|
15423
|
-
"gl",
|
|
15424
|
-
"select-string",
|
|
15425
|
-
"sls",
|
|
15426
|
-
"measure-object",
|
|
15427
|
-
"test-path",
|
|
15428
|
-
"resolve-path",
|
|
15429
|
-
"write-output",
|
|
15430
|
-
"write-host",
|
|
15431
|
-
"select-object",
|
|
15432
|
-
"where-object",
|
|
15433
|
-
"format-list",
|
|
15434
|
-
"format-table",
|
|
15435
|
-
"out-string",
|
|
15436
|
-
"sort-object",
|
|
15437
|
-
"gh"
|
|
15438
|
-
]);
|
|
15439
|
-
/** Git subcommands that never change the repository. */
|
|
15440
|
-
const READ_ONLY_GIT = /* @__PURE__ */ new Set([
|
|
15441
|
-
"status",
|
|
15442
|
-
"log",
|
|
15443
|
-
"show",
|
|
15444
|
-
"diff",
|
|
15445
|
-
"blame",
|
|
15446
|
-
"grep",
|
|
15447
|
-
"ls-files",
|
|
15448
|
-
"ls-tree",
|
|
15449
|
-
"rev-parse",
|
|
15450
|
-
"rev-list",
|
|
15451
|
-
"describe",
|
|
15452
|
-
"shortlog",
|
|
15453
|
-
"cat-file",
|
|
15454
|
-
"remote",
|
|
15455
|
-
"branch",
|
|
15456
|
-
"tag",
|
|
15457
|
-
"config",
|
|
15458
|
-
"reflog",
|
|
15459
|
-
"merge-base",
|
|
15460
|
-
"name-rev",
|
|
15461
|
-
"for-each-ref",
|
|
15462
|
-
"worktree"
|
|
15463
|
-
]);
|
|
15464
|
-
/** `gh` subcommand verbs that only read. */
|
|
15465
|
-
const READ_ONLY_GH = /* @__PURE__ */ new Set([
|
|
15466
|
-
"view",
|
|
15467
|
-
"list",
|
|
15468
|
-
"status",
|
|
15469
|
-
"diff",
|
|
15470
|
-
"checks"
|
|
15471
|
-
]);
|
|
15472
|
-
const bin = (token) => (token ?? "").replace(/\\/g, "/").split("/").pop()?.toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/, "") ?? "";
|
|
15473
|
-
/** Why a command is refused in a read-only session, or undefined when it may run. */
|
|
15474
|
-
function readOnlyCommandDenial(command) {
|
|
15475
|
-
if (/\$\(|`|<\(|>\(/.test(command)) return "Command substitution is not allowed in a read-only session";
|
|
15476
|
-
for (const simple of splitCommands(command)) {
|
|
15477
|
-
if (/(^|[^0-9&<])>>?(?!\s*(&\d|\/dev\/null|nul\b|\$null))/i.test(simple)) return "Writing files is not allowed in a read-only session";
|
|
15478
|
-
const tokens = tokenize(simple);
|
|
15479
|
-
const name = bin(tokens[0]);
|
|
15480
|
-
const args = tokens.slice(1).map((a) => a.toLowerCase());
|
|
15481
|
-
if (name === "git") {
|
|
15482
|
-
const sub = args.find((a) => !a.startsWith("-"));
|
|
15483
|
-
if (!sub || !READ_ONLY_GIT.has(sub)) return `git ${sub ?? ""} may change the repository`;
|
|
15484
|
-
if ((sub === "branch" || sub === "tag" || sub === "remote" || sub === "worktree") && args.some((a) => /^-(d|D|m|M|f|-delete|-move|-force)$/.test(a) || a === "add")) return `git ${sub} with these options changes the repository`;
|
|
15485
|
-
if (sub === "config" && !args.some((a) => a === "--get" || a === "--list" || a === "-l")) return "Only reading git config is allowed";
|
|
15486
|
-
continue;
|
|
15487
|
-
}
|
|
15488
|
-
if (name === "gh") {
|
|
15489
|
-
if (!args.some((a) => READ_ONLY_GH.has(a)) || args.includes("api")) return "Only read-only gh commands are allowed (view, list, status, diff, checks)";
|
|
15490
|
-
continue;
|
|
15491
|
-
}
|
|
15492
|
-
if (name === "find" && args.some((a) => a === "-delete" || a === "-exec" || a === "-execdir")) return "find with -delete or -exec is not allowed in a read-only session";
|
|
15493
|
-
if (!READ_ONLY_BINS.has(name)) return `${name || "This command"} is not on the read-only allowlist`;
|
|
15494
|
-
}
|
|
15495
|
-
}
|
|
15496
16503
|
// ../../packages/providers/src/claude/tools.ts
|
|
15497
16504
|
/** Claude tools that are not rendered as `tool_call` items. */
|
|
15498
16505
|
const SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
@@ -15691,7 +16698,13 @@ function completeToolCall(item, result) {
|
|
|
15691
16698
|
}
|
|
15692
16699
|
/** Risk of a Claude tool call under the `auto` policy (§11.2). */
|
|
15693
16700
|
function assessClaudeTool(name, input, ctx) {
|
|
15694
|
-
if (COMMAND_TOOLS.has(name))
|
|
16701
|
+
if (COMMAND_TOOLS.has(name)) {
|
|
16702
|
+
const shell = name === "PowerShell" ? "powershell" : "posix";
|
|
16703
|
+
return classifyCommand(str(input, "command") ?? "", {
|
|
16704
|
+
...ctx,
|
|
16705
|
+
shell
|
|
16706
|
+
});
|
|
16707
|
+
}
|
|
15695
16708
|
if (EDIT_TOOLS.has(name)) return classifyWrite(editPath(input), ctx);
|
|
15696
16709
|
if (name === "Read") return classifyRead(str(input, "file_path") ?? "", ctx);
|
|
15697
16710
|
if (name === "Grep" || name === "Glob") {
|
|
@@ -15728,7 +16741,8 @@ function approvalTitle(name, input) {
|
|
|
15728
16741
|
* Native permission mode per autonomy level (§11.2):
|
|
15729
16742
|
* - supervised -> `default`, and every edit/command is forced to ask via a PreToolUse hook
|
|
15730
16743
|
* - auto -> `acceptEdits`, plus the risky-action classifier
|
|
15731
|
-
* - full -> `bypassPermissions`, asking only for writes outside the worktree
|
|
16744
|
+
* - full -> `bypassPermissions`, asking only for writes outside the worktree (the OS
|
|
16745
|
+
* temp directory counts as inside, except for secrets files there)
|
|
15732
16746
|
* Plan mode overrides all of them with `plan`.
|
|
15733
16747
|
*/
|
|
15734
16748
|
function permissionModeFor(autonomy, planMode) {
|
|
@@ -15752,6 +16766,13 @@ var ClaudePolicy = class {
|
|
|
15752
16766
|
this.ctx = ctx;
|
|
15753
16767
|
this.readOnly = readOnly;
|
|
15754
16768
|
}
|
|
16769
|
+
get level() {
|
|
16770
|
+
return this.autonomy;
|
|
16771
|
+
}
|
|
16772
|
+
/** Switch the level; the next `gate()` uses it. `allow_session` decisions are kept. */
|
|
16773
|
+
setAutonomy(level) {
|
|
16774
|
+
this.autonomy = level;
|
|
16775
|
+
}
|
|
15755
16776
|
/** Why a call is refused outright (read-only sessions), or undefined. */
|
|
15756
16777
|
denial(toolName, input) {
|
|
15757
16778
|
if (!this.readOnly) return void 0;
|
|
@@ -15761,7 +16782,11 @@ var ClaudePolicy = class {
|
|
|
15761
16782
|
return reason ? `This session is read-only: ${reason}.` : void 0;
|
|
15762
16783
|
}
|
|
15763
16784
|
}
|
|
15764
|
-
|
|
16785
|
+
/**
|
|
16786
|
+
* Decide one tool call. `dir` is the shell's current directory as Claude reports it (an
|
|
16787
|
+
* earlier `cd` persists between Bash calls); relative paths resolve from it.
|
|
16788
|
+
*/
|
|
16789
|
+
gate(toolName, input, dir) {
|
|
15765
16790
|
if (toolName === "AskUserQuestion") return {
|
|
15766
16791
|
ask: true,
|
|
15767
16792
|
action: "other",
|
|
@@ -15773,7 +16798,10 @@ var ClaudePolicy = class {
|
|
|
15773
16798
|
reason: "The agent finished planning"
|
|
15774
16799
|
};
|
|
15775
16800
|
if (this.sessionAllow.has(this.key(toolName, input))) return { ask: false };
|
|
15776
|
-
const risk = assessClaudeTool(toolName, input,
|
|
16801
|
+
const risk = assessClaudeTool(toolName, input, dir ? {
|
|
16802
|
+
...this.ctx,
|
|
16803
|
+
dir
|
|
16804
|
+
} : this.ctx);
|
|
15777
16805
|
if (this.autonomy === "supervised") {
|
|
15778
16806
|
if (risk.risky) return {
|
|
15779
16807
|
ask: true,
|
|
@@ -15799,8 +16827,8 @@ var ClaudePolicy = class {
|
|
|
15799
16827
|
* Whether a `canUseTool` call (Claude's own permission prompt) needs the user. In supervised
|
|
15800
16828
|
* mode everything Claude asks about goes to the user; otherwise only gated calls do.
|
|
15801
16829
|
*/
|
|
15802
|
-
needsUser(toolName, input) {
|
|
15803
|
-
const gate = this.gate(toolName, input);
|
|
16830
|
+
needsUser(toolName, input, dir) {
|
|
16831
|
+
const gate = this.gate(toolName, input, dir);
|
|
15804
16832
|
if (gate.ask || this.autonomy !== "supervised") return gate;
|
|
15805
16833
|
if (this.sessionAllow.has(this.key(toolName, input))) return gate;
|
|
15806
16834
|
return {
|
|
@@ -15936,7 +16964,84 @@ function toSdkUserMessage(input) {
|
|
|
15936
16964
|
parent_tool_use_id: null
|
|
15937
16965
|
};
|
|
15938
16966
|
}
|
|
15939
|
-
|
|
16967
|
+
/** The SDK's model rows as CodeDen models. The first row is Claude Code's default. */
|
|
16968
|
+
function toModelInfos(models) {
|
|
16969
|
+
return models.map((m, i) => ({
|
|
16970
|
+
id: m.value,
|
|
16971
|
+
label: m.displayName || m.value,
|
|
16972
|
+
...m.description ? { description: m.description } : {},
|
|
16973
|
+
efforts: m.supportedEffortLevels ?? [],
|
|
16974
|
+
...i === 0 ? { isDefault: true } : {}
|
|
16975
|
+
}));
|
|
16976
|
+
}
|
|
16977
|
+
/**
|
|
16978
|
+
* List Claude Code's models without a turn. `query()` with a streaming prompt that never
|
|
16979
|
+
* yields starts the CLI and sends only the `initialize` control request, whose answer carries
|
|
16980
|
+
* the model list (`supportedModels()`); no user message is sent, so no model request is made.
|
|
16981
|
+
* Nothing from the user's or a project's settings is loaded (no hooks run), nothing is
|
|
16982
|
+
* persisted, and the CLI is closed as soon as the list arrives or `timeoutMs` passes.
|
|
16983
|
+
* Rejects on timeout or when the CLI fails to start.
|
|
16984
|
+
*/
|
|
16985
|
+
async function listClaudeModels(query, executable, opts = {}) {
|
|
16986
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
16987
|
+
const abortController = new AbortController();
|
|
16988
|
+
const stopped = new Promise((resolve) => abortController.signal.addEventListener("abort", () => resolve(), { once: true }));
|
|
16989
|
+
const idle = () => ({ [Symbol.asyncIterator]: () => ({ next: async () => {
|
|
16990
|
+
await stopped;
|
|
16991
|
+
return {
|
|
16992
|
+
done: true,
|
|
16993
|
+
value: void 0
|
|
16994
|
+
};
|
|
16995
|
+
} }) });
|
|
16996
|
+
let q;
|
|
16997
|
+
let timer;
|
|
16998
|
+
try {
|
|
16999
|
+
q = query({
|
|
17000
|
+
prompt: idle(),
|
|
17001
|
+
options: {
|
|
17002
|
+
...executableOptions(executable),
|
|
17003
|
+
cwd: tmpdir(),
|
|
17004
|
+
settingSources: [],
|
|
17005
|
+
persistSession: false,
|
|
17006
|
+
tools: [],
|
|
17007
|
+
mcpServers: {},
|
|
17008
|
+
strictMcpConfig: true,
|
|
17009
|
+
env: claudeEnv(void 0),
|
|
17010
|
+
abortController
|
|
17011
|
+
}
|
|
17012
|
+
});
|
|
17013
|
+
const stream = q;
|
|
17014
|
+
(async () => {
|
|
17015
|
+
for await (const _message of stream);
|
|
17016
|
+
})().catch(() => void 0);
|
|
17017
|
+
const timeout = new Promise((_, reject) => {
|
|
17018
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Claude Code did not list its models within ${timeoutMs}ms`)), timeoutMs);
|
|
17019
|
+
});
|
|
17020
|
+
return toModelInfos(await Promise.race([q.supportedModels(), timeout]));
|
|
17021
|
+
} finally {
|
|
17022
|
+
clearTimeout(timer);
|
|
17023
|
+
abortController.abort();
|
|
17024
|
+
try {
|
|
17025
|
+
q?.close();
|
|
17026
|
+
} catch {}
|
|
17027
|
+
}
|
|
17028
|
+
}
|
|
17029
|
+
// ../../packages/providers/src/utility.ts
|
|
17030
|
+
/** Abort signal that fires on the caller's signal or after `timeoutMs`. */
|
|
17031
|
+
function oneShotSignal(request) {
|
|
17032
|
+
const controller = new AbortController();
|
|
17033
|
+
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("Utility model timed out")), request.timeoutMs ?? 6e4);
|
|
17034
|
+
const onAbort = () => controller.abort(request.signal?.reason);
|
|
17035
|
+
request.signal?.addEventListener("abort", onAbort, { once: true });
|
|
17036
|
+
return {
|
|
17037
|
+
signal: controller.signal,
|
|
17038
|
+
dispose: () => {
|
|
17039
|
+
clearTimeout(timer);
|
|
17040
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
17041
|
+
}
|
|
17042
|
+
};
|
|
17043
|
+
}
|
|
17044
|
+
// ../../packages/providers/src/util/install-lock.ts
|
|
15940
17045
|
function isAlive$1(pid) {
|
|
15941
17046
|
try {
|
|
15942
17047
|
process.kill(pid, 0);
|
|
@@ -15988,27 +17093,243 @@ async function acquireInstallLock(file, opts = {}) {
|
|
|
15988
17093
|
await new Promise((resolve) => setTimeout(resolve, opts.pollMs ?? 500));
|
|
15989
17094
|
}
|
|
15990
17095
|
}
|
|
15991
|
-
// ../../packages/providers/src/
|
|
15992
|
-
/**
|
|
15993
|
-
* How to run npm for on-demand installs. Prefers the npm that started us (`npm_execpath`,
|
|
15994
|
-
* set under `npx`), then the npm shipped next to this Node binary, then `npm` on PATH.
|
|
15995
|
-
* Running `node <npm-cli.js>` avoids spawning a Windows `.cmd` shim through a shell.
|
|
15996
|
-
*/
|
|
15997
|
-
function resolveNpm(env = process.env, execPath = process.execPath, platform = process.platform) {
|
|
15998
|
-
const fromEnv = env.npm_execpath;
|
|
15999
|
-
if (fromEnv && path.basename(fromEnv) === "npm-cli.js" && existsSync(fromEnv)) return {
|
|
16000
|
-
kind: "node-script",
|
|
16001
|
-
path: execPath,
|
|
16002
|
-
script: fromEnv
|
|
16003
|
-
};
|
|
16004
|
-
const nodeDir = path.dirname(execPath);
|
|
16005
|
-
const bundled = platform === "win32" ? path.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js") : path.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
16006
|
-
if (existsSync(bundled)) return {
|
|
16007
|
-
kind: "node-script",
|
|
16008
|
-
path: execPath,
|
|
16009
|
-
script: bundled
|
|
16010
|
-
};
|
|
16011
|
-
return resolveExecutable("npm");
|
|
17096
|
+
// ../../packages/providers/src/util/npm.ts
|
|
17097
|
+
/**
|
|
17098
|
+
* How to run npm for on-demand installs. Prefers the npm that started us (`npm_execpath`,
|
|
17099
|
+
* set under `npx`), then the npm shipped next to this Node binary, then `npm` on PATH.
|
|
17100
|
+
* Running `node <npm-cli.js>` avoids spawning a Windows `.cmd` shim through a shell.
|
|
17101
|
+
*/
|
|
17102
|
+
function resolveNpm(env = process.env, execPath = process.execPath, platform = process.platform) {
|
|
17103
|
+
const fromEnv = env.npm_execpath;
|
|
17104
|
+
if (fromEnv && path.basename(fromEnv) === "npm-cli.js" && existsSync(fromEnv)) return {
|
|
17105
|
+
kind: "node-script",
|
|
17106
|
+
path: execPath,
|
|
17107
|
+
script: fromEnv
|
|
17108
|
+
};
|
|
17109
|
+
const nodeDir = path.dirname(execPath);
|
|
17110
|
+
const bundled = platform === "win32" ? path.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js") : path.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
17111
|
+
if (existsSync(bundled)) return {
|
|
17112
|
+
kind: "node-script",
|
|
17113
|
+
path: execPath,
|
|
17114
|
+
script: bundled
|
|
17115
|
+
};
|
|
17116
|
+
return resolveExecutable("npm");
|
|
17117
|
+
}
|
|
17118
|
+
// ../../packages/providers/src/util/npm-run.ts
|
|
17119
|
+
/**
|
|
17120
|
+
* Run npm with `args` (on-demand installs: Claude Code support in `<home>/deps`, CodeDen
|
|
17121
|
+
* versions in `<home>/app`), streaming its output lines. Rejects with npm's own error summary
|
|
17122
|
+
* on a non-zero exit, and kills the process tree on timeout.
|
|
17123
|
+
*/
|
|
17124
|
+
function runNpm(npm, args, opts) {
|
|
17125
|
+
return new Promise((resolve, reject) => {
|
|
17126
|
+
const child = spawnResolved(npm, args, {
|
|
17127
|
+
cwd: opts.cwd,
|
|
17128
|
+
env: {
|
|
17129
|
+
...process.env,
|
|
17130
|
+
npm_config_update_notifier: "false",
|
|
17131
|
+
npm_config_progress: "false",
|
|
17132
|
+
...opts.env
|
|
17133
|
+
},
|
|
17134
|
+
stdio: [
|
|
17135
|
+
"ignore",
|
|
17136
|
+
"pipe",
|
|
17137
|
+
"pipe"
|
|
17138
|
+
],
|
|
17139
|
+
windowsHide: true
|
|
17140
|
+
});
|
|
17141
|
+
let output = "";
|
|
17142
|
+
const onData = (chunk) => {
|
|
17143
|
+
output = (output + chunk).slice(-2e4);
|
|
17144
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
17145
|
+
const text = line.trim();
|
|
17146
|
+
if (text) opts.onLine(text.slice(0, 300));
|
|
17147
|
+
}
|
|
17148
|
+
};
|
|
17149
|
+
child.stdout?.setEncoding("utf8").on("data", onData);
|
|
17150
|
+
child.stderr?.setEncoding("utf8").on("data", onData);
|
|
17151
|
+
const timer = setTimeout(() => {
|
|
17152
|
+
killTree$1(child);
|
|
17153
|
+
reject(/* @__PURE__ */ new Error(`npm timed out after ${Math.round(opts.timeoutMs / 1e3)}s`));
|
|
17154
|
+
}, opts.timeoutMs);
|
|
17155
|
+
child.on("error", (error) => {
|
|
17156
|
+
clearTimeout(timer);
|
|
17157
|
+
reject(/* @__PURE__ */ new Error(`Could not start npm: ${error.message}`));
|
|
17158
|
+
});
|
|
17159
|
+
child.on("close", (code) => {
|
|
17160
|
+
clearTimeout(timer);
|
|
17161
|
+
if (code === 0) return resolve();
|
|
17162
|
+
const reason = npmFailure(output);
|
|
17163
|
+
reject(/* @__PURE__ */ new Error(`npm install exited with code ${code}${reason ? `: ${reason}` : ""}`));
|
|
17164
|
+
});
|
|
17165
|
+
});
|
|
17166
|
+
}
|
|
17167
|
+
/**
|
|
17168
|
+
* The useful part of npm's error output: its error code and the first descriptive line
|
|
17169
|
+
* (`npm error code E404` + `npm error 404 Not Found - GET ...`), without stack frames.
|
|
17170
|
+
*/
|
|
17171
|
+
function npmFailure(output) {
|
|
17172
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^npm (error|ERR!)/.test(line)).map((line) => line.replace(/^npm (error|ERR!)\s*/, "")).filter((line) => line && !/^(at |\{|\}|[a-z]\w*: |syscall|errno |A complete log)/.test(line));
|
|
17173
|
+
const summary = [lines.find((line) => line.startsWith("code ")), lines.find((line) => !line.startsWith("code "))].filter(Boolean).join(": ").slice(0, 500);
|
|
17174
|
+
if (summary) return summary;
|
|
17175
|
+
return output.trim().split(/\r?\n/).slice(-3).join(" ").slice(0, 500);
|
|
17176
|
+
}
|
|
17177
|
+
// ../../packages/providers/src/util/tar.ts
|
|
17178
|
+
const BLOCK = 512;
|
|
17179
|
+
/**
|
|
17180
|
+
* Extract a gzipped tarball (an npm package) into `dest`, dropping the first `strip` path
|
|
17181
|
+
* components (`package/` in npm tarballs). Only regular files and directories are written;
|
|
17182
|
+
* links and devices are skipped. Entries that would land outside `dest` (absolute paths,
|
|
17183
|
+
* `..`) fail the whole extraction. Returns the relative paths written.
|
|
17184
|
+
*/
|
|
17185
|
+
function extractTarGz(archive, dest, opts = {}) {
|
|
17186
|
+
const tar = gunzipSync(archive);
|
|
17187
|
+
const root = path.resolve(dest);
|
|
17188
|
+
const written = [];
|
|
17189
|
+
let offset = 0;
|
|
17190
|
+
let longName = null;
|
|
17191
|
+
let paxPath = null;
|
|
17192
|
+
while (offset + BLOCK <= tar.length) {
|
|
17193
|
+
const header = tar.subarray(offset, offset + BLOCK);
|
|
17194
|
+
if (header.every((b) => b === 0)) break;
|
|
17195
|
+
if (!checksumOk(header)) throw new Error("Corrupt tarball (bad header checksum)");
|
|
17196
|
+
const size = readOctal(header, 124, 12);
|
|
17197
|
+
const type = String.fromCharCode(header[156] ?? 0);
|
|
17198
|
+
const dataStart = offset + BLOCK;
|
|
17199
|
+
const data = tar.subarray(dataStart, dataStart + size);
|
|
17200
|
+
if (data.length < size) throw new Error("Corrupt tarball (truncated entry)");
|
|
17201
|
+
offset = dataStart + Math.ceil(size / BLOCK) * BLOCK;
|
|
17202
|
+
if (type === "L") {
|
|
17203
|
+
longName = cString(data);
|
|
17204
|
+
continue;
|
|
17205
|
+
}
|
|
17206
|
+
if (type === "x") {
|
|
17207
|
+
paxPath = paxRecord(data, "path");
|
|
17208
|
+
continue;
|
|
17209
|
+
}
|
|
17210
|
+
if (type === "g") continue;
|
|
17211
|
+
const name = paxPath ?? longName ?? headerName(header);
|
|
17212
|
+
longName = null;
|
|
17213
|
+
paxPath = null;
|
|
17214
|
+
if (!(type === "0" || type === "\0" || type === "7") && type !== "5") continue;
|
|
17215
|
+
const rel = stripComponents(name, opts.strip ?? 0);
|
|
17216
|
+
if (!rel) continue;
|
|
17217
|
+
const target = path.resolve(root, rel);
|
|
17218
|
+
if (target !== root && !target.startsWith(root + path.sep)) throw new Error(`Unsafe path in tarball: ${name}`);
|
|
17219
|
+
if (type === "5") {
|
|
17220
|
+
mkdirSync(target, { recursive: true });
|
|
17221
|
+
continue;
|
|
17222
|
+
}
|
|
17223
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
17224
|
+
writeFileSync(target, data);
|
|
17225
|
+
written.push(rel);
|
|
17226
|
+
}
|
|
17227
|
+
return written;
|
|
17228
|
+
}
|
|
17229
|
+
function stripComponents(name, strip) {
|
|
17230
|
+
const parts = name.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
17231
|
+
if (name.startsWith("/") || /^[A-Za-z]:/.test(name)) throw new Error(`Unsafe path in tarball: ${name}`);
|
|
17232
|
+
if (parts.includes("..")) throw new Error(`Unsafe path in tarball: ${name}`);
|
|
17233
|
+
const rest = parts.slice(strip).filter((p) => p !== ".");
|
|
17234
|
+
return rest.length ? rest.join("/") : null;
|
|
17235
|
+
}
|
|
17236
|
+
function headerName(header) {
|
|
17237
|
+
const name = cString(header.subarray(0, 100));
|
|
17238
|
+
if (!header.subarray(257, 263).toString("latin1").startsWith("ustar")) return name;
|
|
17239
|
+
const prefix = cString(header.subarray(345, 500));
|
|
17240
|
+
return prefix ? `${prefix}/${name}` : name;
|
|
17241
|
+
}
|
|
17242
|
+
function cString(buf) {
|
|
17243
|
+
const end = buf.indexOf(0);
|
|
17244
|
+
return buf.subarray(0, end === -1 ? buf.length : end).toString("utf8");
|
|
17245
|
+
}
|
|
17246
|
+
function readOctal(buf, start, length) {
|
|
17247
|
+
const text = cString(buf.subarray(start, start + length)).trim();
|
|
17248
|
+
const value = text ? Number.parseInt(text, 8) : 0;
|
|
17249
|
+
if (!Number.isFinite(value) || value < 0) throw new Error("Corrupt tarball (bad size)");
|
|
17250
|
+
return value;
|
|
17251
|
+
}
|
|
17252
|
+
function checksumOk(header) {
|
|
17253
|
+
const expected = readOctal(header, 148, 8);
|
|
17254
|
+
let sum = 0;
|
|
17255
|
+
for (let i = 0; i < BLOCK; i++) sum += i >= 148 && i < 156 ? 32 : header[i] ?? 0;
|
|
17256
|
+
return sum === expected;
|
|
17257
|
+
}
|
|
17258
|
+
/** One `key=value` record from a pax extended header (`<len> <key>=<value>\n` lines). */
|
|
17259
|
+
function paxRecord(data, key) {
|
|
17260
|
+
let i = 0;
|
|
17261
|
+
while (i < data.length) {
|
|
17262
|
+
const space = data.indexOf(32, i);
|
|
17263
|
+
if (space === -1) break;
|
|
17264
|
+
const len = Number.parseInt(data.subarray(i, space).toString("latin1"), 10);
|
|
17265
|
+
if (!Number.isFinite(len) || len <= 0) break;
|
|
17266
|
+
const record = data.subarray(space + 1, i + len - 1).toString("utf8");
|
|
17267
|
+
const eq = record.indexOf("=");
|
|
17268
|
+
if (eq !== -1 && record.slice(0, eq) === key) return record.slice(eq + 1);
|
|
17269
|
+
i += len;
|
|
17270
|
+
}
|
|
17271
|
+
return null;
|
|
17272
|
+
}
|
|
17273
|
+
/** The registry to use: an explicit one, npm's configured one, or the public registry. */
|
|
17274
|
+
function registryUrl(explicit, env = process.env) {
|
|
17275
|
+
return (explicit ?? env.npm_config_registry ?? "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
17276
|
+
}
|
|
17277
|
+
/**
|
|
17278
|
+
* Install one exact package version straight from an npm registry, without npm (the desktop
|
|
17279
|
+
* app has no npm, §17): read the version manifest, download the tarball, check it against the
|
|
17280
|
+
* manifest's `dist.integrity` (sha512), extract it into a staging folder and move it into
|
|
17281
|
+
* `<nodeModules>/<name>`. Dependencies are not installed: use it only for packages whose
|
|
17282
|
+
* runtime needs none (the Claude Agent SDK, installed without optional and peer deps).
|
|
17283
|
+
*/
|
|
17284
|
+
async function installFromRegistry(opts) {
|
|
17285
|
+
const doFetch = opts.fetch ?? fetch;
|
|
17286
|
+
const progress = opts.onProgress ?? (() => {});
|
|
17287
|
+
const signal = AbortSignal.timeout(opts.timeoutMs ?? 3e5);
|
|
17288
|
+
const base = registryUrl(opts.registry);
|
|
17289
|
+
const spec = `${opts.name}@${opts.version}`;
|
|
17290
|
+
const manifestUrl = `${base}/${opts.name.replace("/", "%2f")}/${encodeURIComponent(opts.version)}`;
|
|
17291
|
+
progress(`Looking up ${spec}`);
|
|
17292
|
+
const res = await doFetch(manifestUrl, {
|
|
17293
|
+
headers: { accept: "application/json" },
|
|
17294
|
+
signal
|
|
17295
|
+
});
|
|
17296
|
+
if (!res.ok) throw new Error(`the registry answered ${res.status} for ${spec}`);
|
|
17297
|
+
const manifest = await res.json();
|
|
17298
|
+
if (manifest.name !== opts.name || manifest.version !== opts.version) throw new Error(`the registry returned the wrong package for ${spec}`);
|
|
17299
|
+
const tarball = manifest.dist?.tarball;
|
|
17300
|
+
const integrity = manifest.dist?.integrity;
|
|
17301
|
+
if (typeof tarball !== "string" || !/^https?:\/\//.test(tarball)) throw new Error(`${spec} has no tarball URL`);
|
|
17302
|
+
const expected = typeof integrity === "string" ? sha512Of(integrity) : null;
|
|
17303
|
+
if (!expected) throw new Error(`${spec} has no sha512 integrity to check the download against`);
|
|
17304
|
+
progress(`Downloading ${spec}`);
|
|
17305
|
+
const download = await doFetch(tarball, { signal });
|
|
17306
|
+
if (!download.ok) throw new Error(`downloading ${spec} failed (${download.status})`);
|
|
17307
|
+
const archive = Buffer.from(await download.arrayBuffer());
|
|
17308
|
+
if (createHash("sha512").update(archive).digest("base64") !== expected) throw new Error(`the download of ${spec} does not match its published integrity (sha512)`);
|
|
17309
|
+
progress(`Unpacking ${spec}`);
|
|
17310
|
+
const target = path.join(opts.nodeModules, ...opts.name.split("/"));
|
|
17311
|
+
const staging = `${target}.staging-${randomBytes(4).toString("hex")}`;
|
|
17312
|
+
try {
|
|
17313
|
+
mkdirSync(staging, { recursive: true });
|
|
17314
|
+
extractTarGz(archive, staging, { strip: 1 });
|
|
17315
|
+
rmSync(target, {
|
|
17316
|
+
recursive: true,
|
|
17317
|
+
force: true,
|
|
17318
|
+
maxRetries: 3
|
|
17319
|
+
});
|
|
17320
|
+
renameSync(staging, target);
|
|
17321
|
+
} finally {
|
|
17322
|
+
rmSync(staging, {
|
|
17323
|
+
recursive: true,
|
|
17324
|
+
force: true,
|
|
17325
|
+
maxRetries: 2
|
|
17326
|
+
});
|
|
17327
|
+
}
|
|
17328
|
+
}
|
|
17329
|
+
/** The base64 sha512 digest in a Subresource Integrity string (`sha512-<base64> ...`). */
|
|
17330
|
+
function sha512Of(integrity) {
|
|
17331
|
+
for (const part of integrity.trim().split(/\s+/)) if (part.startsWith("sha512-")) return part.slice(7).replace(/\?.*$/, "");
|
|
17332
|
+
return null;
|
|
16012
17333
|
}
|
|
16013
17334
|
// ../../packages/providers/src/claude/sdk/install.ts
|
|
16014
17335
|
/** The Claude Agent SDK package. */
|
|
@@ -16036,6 +17357,18 @@ async function importClaudeSdkFrom(depsDir) {
|
|
|
16036
17357
|
if (typeof sdk.query !== "function") throw new Error(`${entry} does not export query()`);
|
|
16037
17358
|
return sdk;
|
|
16038
17359
|
}
|
|
17360
|
+
/** Which installer `installClaudeSdk` uses, and the npm to run when it is npm. */
|
|
17361
|
+
function chooseInstaller(opts, env = process.env) {
|
|
17362
|
+
const wanted = opts.installer ?? env.CODEDEN_DEPS_INSTALLER;
|
|
17363
|
+
if (wanted === "registry") return { kind: "registry" };
|
|
17364
|
+
const npm = opts.npm ?? resolveNpm(env);
|
|
17365
|
+
if (npm) return {
|
|
17366
|
+
kind: "npm",
|
|
17367
|
+
npm
|
|
17368
|
+
};
|
|
17369
|
+
if (wanted === "npm") throw new Error("npm was not found; install Node.js with npm and try again");
|
|
17370
|
+
return { kind: "registry" };
|
|
17371
|
+
}
|
|
16039
17372
|
const inflight = /* @__PURE__ */ new Map();
|
|
16040
17373
|
/**
|
|
16041
17374
|
* Install the tested SDK version into `depsDir` with npm, skipping its optional platform
|
|
@@ -16063,25 +17396,37 @@ async function runInstall(opts) {
|
|
|
16063
17396
|
name: "codeden-deps",
|
|
16064
17397
|
private: true
|
|
16065
17398
|
}, null, 2)}\n`);
|
|
16066
|
-
const
|
|
16067
|
-
if (!npm) throw new Error("npm was not found; install Node.js with npm and try again");
|
|
17399
|
+
const installer = chooseInstaller(opts);
|
|
16068
17400
|
const spec = `${CLAUDE_SDK_PACKAGE}@${CLAUDE_SDK_VERSION}`;
|
|
16069
|
-
|
|
16070
|
-
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
17401
|
+
if (installer.kind === "registry") {
|
|
17402
|
+
await installFromRegistry({
|
|
17403
|
+
name: CLAUDE_SDK_PACKAGE,
|
|
17404
|
+
version: CLAUDE_SDK_VERSION,
|
|
17405
|
+
nodeModules: join(opts.depsDir, "node_modules"),
|
|
17406
|
+
registry: opts.registry,
|
|
17407
|
+
fetch: opts.fetch,
|
|
17408
|
+
timeoutMs: opts.timeoutMs,
|
|
17409
|
+
onProgress: progress
|
|
17410
|
+
});
|
|
17411
|
+
recordDependency(manifest, CLAUDE_SDK_PACKAGE, CLAUDE_SDK_VERSION);
|
|
17412
|
+
} else {
|
|
17413
|
+
progress(`Downloading ${spec} from npm`);
|
|
17414
|
+
await runNpm(installer.npm, [
|
|
17415
|
+
"install",
|
|
17416
|
+
spec,
|
|
17417
|
+
"--omit=optional",
|
|
17418
|
+
"--omit=peer",
|
|
17419
|
+
"--no-audit",
|
|
17420
|
+
"--no-fund",
|
|
17421
|
+
"--save-exact",
|
|
17422
|
+
"--prefix",
|
|
17423
|
+
opts.depsDir
|
|
17424
|
+
], {
|
|
17425
|
+
cwd: opts.depsDir,
|
|
17426
|
+
timeoutMs: opts.timeoutMs ?? 3e5,
|
|
17427
|
+
onLine: progress
|
|
17428
|
+
});
|
|
17429
|
+
}
|
|
16085
17430
|
if (installedClaudeSdkVersion(opts.depsDir) !== "0.3.318") throw new Error(`npm finished but ${spec} is not in ${opts.depsDir}`);
|
|
16086
17431
|
progress("Checking the install");
|
|
16087
17432
|
await importClaudeSdkFrom(opts.depsDir);
|
|
@@ -16090,57 +17435,18 @@ async function runInstall(opts) {
|
|
|
16090
17435
|
release();
|
|
16091
17436
|
}
|
|
16092
17437
|
}
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
"pipe"
|
|
16106
|
-
],
|
|
16107
|
-
windowsHide: true
|
|
16108
|
-
});
|
|
16109
|
-
let output = "";
|
|
16110
|
-
const onData = (chunk) => {
|
|
16111
|
-
output = (output + chunk).slice(-2e4);
|
|
16112
|
-
for (const line of chunk.split(/\r?\n/)) {
|
|
16113
|
-
const text = line.trim();
|
|
16114
|
-
if (text) opts.onLine(text.slice(0, 300));
|
|
16115
|
-
}
|
|
16116
|
-
};
|
|
16117
|
-
child.stdout?.setEncoding("utf8").on("data", onData);
|
|
16118
|
-
child.stderr?.setEncoding("utf8").on("data", onData);
|
|
16119
|
-
const timer = setTimeout(() => {
|
|
16120
|
-
killTree$1(child);
|
|
16121
|
-
reject(/* @__PURE__ */ new Error(`npm timed out after ${Math.round(opts.timeoutMs / 1e3)}s`));
|
|
16122
|
-
}, opts.timeoutMs);
|
|
16123
|
-
child.on("error", (error) => {
|
|
16124
|
-
clearTimeout(timer);
|
|
16125
|
-
reject(/* @__PURE__ */ new Error(`Could not start npm: ${error.message}`));
|
|
16126
|
-
});
|
|
16127
|
-
child.on("close", (code) => {
|
|
16128
|
-
clearTimeout(timer);
|
|
16129
|
-
if (code === 0) return resolve();
|
|
16130
|
-
const reason = npmFailure(output);
|
|
16131
|
-
reject(/* @__PURE__ */ new Error(`npm install exited with code ${code}${reason ? `: ${reason}` : ""}`));
|
|
16132
|
-
});
|
|
16133
|
-
});
|
|
16134
|
-
}
|
|
16135
|
-
/**
|
|
16136
|
-
* The useful part of npm's error output: its error code and the first descriptive line
|
|
16137
|
-
* (`npm error code E404` + `npm error 404 Not Found - GET ...`), without stack frames.
|
|
16138
|
-
*/
|
|
16139
|
-
function npmFailure(output) {
|
|
16140
|
-
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^npm (error|ERR!)/.test(line)).map((line) => line.replace(/^npm (error|ERR!)\s*/, "")).filter((line) => line && !/^(at |\{|\}|[a-z]\w*: |syscall|errno |A complete log)/.test(line));
|
|
16141
|
-
const summary = [lines.find((line) => line.startsWith("code ")), lines.find((line) => !line.startsWith("code "))].filter(Boolean).join(": ").slice(0, 500);
|
|
16142
|
-
if (summary) return summary;
|
|
16143
|
-
return output.trim().split(/\r?\n/).slice(-3).join(" ").slice(0, 500);
|
|
17438
|
+
/** Note the installed package in `<deps>/package.json`, as `npm install --save-exact` would. */
|
|
17439
|
+
function recordDependency(manifest, name, version) {
|
|
17440
|
+
let pkg = {};
|
|
17441
|
+
try {
|
|
17442
|
+
pkg = JSON.parse(readFileSync(manifest, "utf8"));
|
|
17443
|
+
} catch {}
|
|
17444
|
+
const deps = typeof pkg.dependencies === "object" && pkg.dependencies || {};
|
|
17445
|
+
pkg.dependencies = {
|
|
17446
|
+
...deps,
|
|
17447
|
+
[name]: version
|
|
17448
|
+
};
|
|
17449
|
+
writeFileSync(manifest, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
16144
17450
|
}
|
|
16145
17451
|
// ../../packages/providers/src/claude/sdk/load.ts
|
|
16146
17452
|
/** Where the SDK comes from in this process. `CODEDEN_CLAUDE_SDK=deps|workspace` overrides. */
|
|
@@ -16159,6 +17465,15 @@ function configureClaudeSdk(opts) {
|
|
|
16159
17465
|
loaded = void 0;
|
|
16160
17466
|
}
|
|
16161
17467
|
/**
|
|
17468
|
+
* True when the SDK can be loaded without installing it first: from the workspace, already
|
|
17469
|
+
* loaded, or already installed in the deps folder. Background work (model discovery) checks
|
|
17470
|
+
* this so it never triggers the on-demand install by itself.
|
|
17471
|
+
*/
|
|
17472
|
+
function claudeSdkReady() {
|
|
17473
|
+
if (claudeSdkSource() === "workspace" || loaded) return true;
|
|
17474
|
+
return depsDir !== void 0 && isClaudeSdkInstalled(depsDir);
|
|
17475
|
+
}
|
|
17476
|
+
/**
|
|
16162
17477
|
* The Claude Agent SDK module, installed first when needed. Failures are not cached, so the
|
|
16163
17478
|
* next session retries the install.
|
|
16164
17479
|
*/
|
|
@@ -17036,6 +18351,10 @@ var ClaudeSession = class {
|
|
|
17036
18351
|
abort;
|
|
17037
18352
|
stderrTail = "";
|
|
17038
18353
|
closed = false;
|
|
18354
|
+
/** The shell's current directory from the latest PreToolUse hook, for relative paths. */
|
|
18355
|
+
shellDir;
|
|
18356
|
+
/** Current autonomy: a query restarted after the CLI exits starts with it. */
|
|
18357
|
+
autonomy;
|
|
17039
18358
|
constructor(opts, config) {
|
|
17040
18359
|
this.opts = opts;
|
|
17041
18360
|
this.config = config;
|
|
@@ -17046,6 +18365,7 @@ var ClaudeSession = class {
|
|
|
17046
18365
|
emit
|
|
17047
18366
|
});
|
|
17048
18367
|
this.broker = new ApprovalBroker(emit);
|
|
18368
|
+
this.autonomy = opts.autonomy;
|
|
17049
18369
|
this.policy = new ClaudePolicy(opts.autonomy, {
|
|
17050
18370
|
cwd: opts.cwd,
|
|
17051
18371
|
extraRoots: opts.additionalDirectories
|
|
@@ -17056,6 +18376,26 @@ var ClaudeSession = class {
|
|
|
17056
18376
|
resumeToken() {
|
|
17057
18377
|
return this.mapper.resumeToken ?? this.opts.resumeToken;
|
|
17058
18378
|
}
|
|
18379
|
+
/**
|
|
18380
|
+
* Switch autonomy. The policy changes at once, so the next tool call (even in a running
|
|
18381
|
+
* turn) follows the new level; approvals already waiting stay waiting. The CLI's own
|
|
18382
|
+
* permission mode follows where it can (`setPermissionMode`); when the CLI refuses a mode
|
|
18383
|
+
* (`bypassPermissions` needs a launch flag), it keeps `acceptEdits` and CodeDen's policy
|
|
18384
|
+
* decides through the PreToolUse hook and `canUseTool` either way. Plan mode is left alone.
|
|
18385
|
+
*/
|
|
18386
|
+
async setAutonomy(level) {
|
|
18387
|
+
this.autonomy = level;
|
|
18388
|
+
this.policy.setAutonomy(level);
|
|
18389
|
+
const q = this.q;
|
|
18390
|
+
if (!q || this.opts.planMode) return;
|
|
18391
|
+
const mode = permissionModeFor(level, false);
|
|
18392
|
+
try {
|
|
18393
|
+
await q.setPermissionMode(mode);
|
|
18394
|
+
} catch {
|
|
18395
|
+
if (mode !== "bypassPermissions") return;
|
|
18396
|
+
await q.setPermissionMode("acceptEdits").catch(() => {});
|
|
18397
|
+
}
|
|
18398
|
+
}
|
|
17059
18399
|
/** Model/effort for the following turns, applied live through the SDK's streaming query. */
|
|
17060
18400
|
async configure(change) {
|
|
17061
18401
|
const q = this.q;
|
|
@@ -17129,13 +18469,15 @@ var ClaudeSession = class {
|
|
|
17129
18469
|
const preToolUse = async (hookInput) => {
|
|
17130
18470
|
const name = str(hookInput, "tool_name") ?? "";
|
|
17131
18471
|
const toolInput = hookInput.tool_input ?? {};
|
|
18472
|
+
const cwd = str(hookInput, "cwd");
|
|
18473
|
+
if (cwd && (path.isAbsolute(cwd) || path.win32.isAbsolute(cwd))) this.shellDir = cwd;
|
|
17132
18474
|
const denied = this.policy.denial(name, toolInput);
|
|
17133
18475
|
if (denied) return { hookSpecificOutput: {
|
|
17134
18476
|
hookEventName: "PreToolUse",
|
|
17135
18477
|
permissionDecision: "deny",
|
|
17136
18478
|
permissionDecisionReason: denied
|
|
17137
18479
|
} };
|
|
17138
|
-
const gate = this.policy.gate(name, toolInput);
|
|
18480
|
+
const gate = this.policy.gate(name, toolInput, this.shellDir);
|
|
17139
18481
|
if (!gate.ask) return {};
|
|
17140
18482
|
return { hookSpecificOutput: {
|
|
17141
18483
|
hookEventName: "PreToolUse",
|
|
@@ -17146,7 +18488,10 @@ var ClaudeSession = class {
|
|
|
17146
18488
|
const q = this.config.query({
|
|
17147
18489
|
prompt: prompts,
|
|
17148
18490
|
options: buildQueryOptions({
|
|
17149
|
-
session:
|
|
18491
|
+
session: {
|
|
18492
|
+
...this.opts,
|
|
18493
|
+
autonomy: this.autonomy
|
|
18494
|
+
},
|
|
17150
18495
|
executable: this.config.executable,
|
|
17151
18496
|
canUseTool: this.canUseTool,
|
|
17152
18497
|
hooks: { PreToolUse: [{ hooks: [preToolUse] }] },
|
|
@@ -17197,7 +18542,7 @@ var ClaudeSession = class {
|
|
|
17197
18542
|
behavior: "deny",
|
|
17198
18543
|
message: denied
|
|
17199
18544
|
};
|
|
17200
|
-
const gate = this.policy.needsUser(toolName, input);
|
|
18545
|
+
const gate = this.policy.needsUser(toolName, input, this.shellDir);
|
|
17201
18546
|
if (!gate.ask) return {
|
|
17202
18547
|
behavior: "allow",
|
|
17203
18548
|
updatedInput: input
|
|
@@ -17303,7 +18648,8 @@ const capabilities$2 = {
|
|
|
17303
18648
|
planMode: true,
|
|
17304
18649
|
reasoningEffort: true,
|
|
17305
18650
|
mcp: true,
|
|
17306
|
-
skills: true
|
|
18651
|
+
skills: true,
|
|
18652
|
+
liveAutonomy: true
|
|
17307
18653
|
};
|
|
17308
18654
|
const EFFORTS = [
|
|
17309
18655
|
"low",
|
|
@@ -17312,8 +18658,8 @@ const EFFORTS = [
|
|
|
17312
18658
|
"xhigh",
|
|
17313
18659
|
"max"
|
|
17314
18660
|
];
|
|
17315
|
-
/** Fallback model list (CLI aliases) until a session reports the real one. */
|
|
17316
|
-
const FALLBACK_MODELS
|
|
18661
|
+
/** Fallback model list (CLI aliases) until discovery or a session reports the real one. */
|
|
18662
|
+
const FALLBACK_MODELS = [
|
|
17317
18663
|
{
|
|
17318
18664
|
id: "default",
|
|
17319
18665
|
label: "Default",
|
|
@@ -17342,7 +18688,8 @@ var ClaudeAdapter = class {
|
|
|
17342
18688
|
options;
|
|
17343
18689
|
id = CLAUDE_PROVIDER_ID;
|
|
17344
18690
|
displayName = "Claude Code";
|
|
17345
|
-
|
|
18691
|
+
catalog = new ModelCatalog(FALLBACK_MODELS);
|
|
18692
|
+
refreshing;
|
|
17346
18693
|
cachedExe;
|
|
17347
18694
|
constructor(options = {}) {
|
|
17348
18695
|
this.options = options;
|
|
@@ -17359,7 +18706,22 @@ var ClaudeAdapter = class {
|
|
|
17359
18706
|
return { ...capabilities$2 };
|
|
17360
18707
|
}
|
|
17361
18708
|
models() {
|
|
17362
|
-
return this.
|
|
18709
|
+
return this.catalog.list();
|
|
18710
|
+
}
|
|
18711
|
+
onModelsChanged(listener) {
|
|
18712
|
+
return this.catalog.subscribe(listener);
|
|
18713
|
+
}
|
|
18714
|
+
/**
|
|
18715
|
+
* Load Claude Code's model list without a turn (`listClaudeModels`: the CLI starts and
|
|
18716
|
+
* answers `initialize`; no message is sent). Concurrent calls share one discovery; a failed
|
|
18717
|
+
* discovery keeps the last list. Skipped when Claude Code is not installed, or when the SDK
|
|
18718
|
+
* would have to be installed first. Never throws.
|
|
18719
|
+
*/
|
|
18720
|
+
refreshModels() {
|
|
18721
|
+
this.refreshing ??= this.discoverModels().finally(() => {
|
|
18722
|
+
this.refreshing = void 0;
|
|
18723
|
+
});
|
|
18724
|
+
return this.refreshing;
|
|
17363
18725
|
}
|
|
17364
18726
|
async openSession(opts) {
|
|
17365
18727
|
const executable = this.executable();
|
|
@@ -17369,20 +18731,24 @@ var ClaudeAdapter = class {
|
|
|
17369
18731
|
executable,
|
|
17370
18732
|
query,
|
|
17371
18733
|
onModels: (models) => {
|
|
17372
|
-
if (
|
|
17373
|
-
this.modelList = models.map((m, i) => ({
|
|
17374
|
-
id: m.value,
|
|
17375
|
-
label: m.displayName || m.value,
|
|
17376
|
-
...m.description ? { description: m.description } : {},
|
|
17377
|
-
efforts: m.supportedEffortLevels ?? [],
|
|
17378
|
-
...i === 0 ? { isDefault: true } : {}
|
|
17379
|
-
}));
|
|
18734
|
+
if (models.length) this.catalog.set(toModelInfos(models));
|
|
17380
18735
|
}
|
|
17381
18736
|
});
|
|
17382
18737
|
}
|
|
17383
18738
|
utilityModel() {
|
|
17384
18739
|
return new ClaudeUtilityModel(() => this.executable());
|
|
17385
18740
|
}
|
|
18741
|
+
async discoverModels() {
|
|
18742
|
+
const exe = this.executable();
|
|
18743
|
+
if (!exe) return;
|
|
18744
|
+
const load = this.options.loadSdk ?? (claudeSdkReady() ? loadClaudeSdk : void 0);
|
|
18745
|
+
if (!load) return;
|
|
18746
|
+
try {
|
|
18747
|
+
const { query } = await load();
|
|
18748
|
+
const models = await listClaudeModels(query, exe, { timeoutMs: this.options.modelListTimeoutMs });
|
|
18749
|
+
if (models.length) this.catalog.set(models);
|
|
18750
|
+
} catch {}
|
|
18751
|
+
}
|
|
17386
18752
|
};
|
|
17387
18753
|
// ../../packages/providers/src/codex/detect.ts
|
|
17388
18754
|
/** Oldest Codex version whose app-server protocol matches the generated types. */
|
|
@@ -17632,7 +18998,10 @@ const OPT_OUT = [
|
|
|
17632
18998
|
"fuzzyFileSearch/sessionUpdated",
|
|
17633
18999
|
"fuzzyFileSearch/sessionCompleted"
|
|
17634
19000
|
];
|
|
17635
|
-
/**
|
|
19001
|
+
/**
|
|
19002
|
+
* Spawn `codex app-server` (stdio) and complete the `initialize` handshake. Aborting `signal`
|
|
19003
|
+
* stops the process at any point, including during the handshake.
|
|
19004
|
+
*/
|
|
17636
19005
|
async function startAppServer(exe, handlers, opts = {}) {
|
|
17637
19006
|
const rpc = new CodexRpc(spawnResolved(exe, ["app-server"], {
|
|
17638
19007
|
stdio: [
|
|
@@ -17646,6 +19015,10 @@ async function startAppServer(exe, handlers, opts = {}) {
|
|
|
17646
19015
|
},
|
|
17647
19016
|
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
17648
19017
|
}), handlers);
|
|
19018
|
+
if (opts.signal) {
|
|
19019
|
+
if (opts.signal.aborted) rpc.close();
|
|
19020
|
+
else opts.signal.addEventListener("abort", () => void rpc.close(), { once: true });
|
|
19021
|
+
}
|
|
17649
19022
|
const params = {
|
|
17650
19023
|
clientInfo: {
|
|
17651
19024
|
name: "codeden",
|
|
@@ -17670,6 +19043,69 @@ async function startAppServer(exe, handlers, opts = {}) {
|
|
|
17670
19043
|
throw error;
|
|
17671
19044
|
}
|
|
17672
19045
|
}
|
|
19046
|
+
/** `model/list` pages are followed up to this many (a guard against a looping cursor). */
|
|
19047
|
+
const MAX_PAGES = 20;
|
|
19048
|
+
/**
|
|
19049
|
+
* Map an app-server `Model` to CodeDen's `ModelInfo`. The id is the model slug Codex accepts
|
|
19050
|
+
* in `thread/start` and `turn/start`. Efforts CodeDen does not know are dropped, and missing
|
|
19051
|
+
* fields (older app-servers) are tolerated.
|
|
19052
|
+
*/
|
|
19053
|
+
function modelInfoFromCodex(m) {
|
|
19054
|
+
const efforts = (m.supportedReasoningEfforts ?? []).map((e) => e.reasoningEffort).filter((e) => ReasoningEffort.safeParse(e).success);
|
|
19055
|
+
const defaultEffort = ReasoningEffort.safeParse(m.defaultReasoningEffort);
|
|
19056
|
+
return {
|
|
19057
|
+
id: m.model,
|
|
19058
|
+
label: m.displayName || m.model,
|
|
19059
|
+
...m.description ? { description: m.description } : {},
|
|
19060
|
+
efforts,
|
|
19061
|
+
...defaultEffort.success ? { defaultEffort: defaultEffort.data } : {},
|
|
19062
|
+
...m.isDefault ? { isDefault: true } : {}
|
|
19063
|
+
};
|
|
19064
|
+
}
|
|
19065
|
+
/** The models Codex shows in its own picker, as `ModelInfo`. */
|
|
19066
|
+
function visibleCodexModels(models) {
|
|
19067
|
+
return models.filter((m) => !m.hidden && m.model).map(modelInfoFromCodex);
|
|
19068
|
+
}
|
|
19069
|
+
/**
|
|
19070
|
+
* Ask a short-lived `codex app-server` for its model list (`model/list`, every page). No
|
|
19071
|
+
* thread is started and no turn runs. Rejects on failure or after `timeoutMs`; the process
|
|
19072
|
+
* is stopped either way.
|
|
19073
|
+
*/
|
|
19074
|
+
async function listCodexModels(exe, opts = {}) {
|
|
19075
|
+
const timeoutMs = opts.timeoutMs ?? 2e4;
|
|
19076
|
+
const abort = new AbortController();
|
|
19077
|
+
let timer;
|
|
19078
|
+
const timeout = new Promise((_, reject) => {
|
|
19079
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`codex app-server did not list models within ${timeoutMs}ms`)), timeoutMs);
|
|
19080
|
+
});
|
|
19081
|
+
try {
|
|
19082
|
+
return await Promise.race([fetchModels(exe, abort.signal), timeout]);
|
|
19083
|
+
} finally {
|
|
19084
|
+
clearTimeout(timer);
|
|
19085
|
+
abort.abort();
|
|
19086
|
+
}
|
|
19087
|
+
}
|
|
19088
|
+
async function fetchModels(exe, signal) {
|
|
19089
|
+
const { rpc } = await startAppServer(exe, {
|
|
19090
|
+
onNotification: () => {},
|
|
19091
|
+
onRequest: async () => {
|
|
19092
|
+
throw new Error("CodeDen's model discovery answers no requests");
|
|
19093
|
+
},
|
|
19094
|
+
onExit: () => {}
|
|
19095
|
+
}, {
|
|
19096
|
+
cwd: tmpdir(),
|
|
19097
|
+
signal
|
|
19098
|
+
});
|
|
19099
|
+
const models = [];
|
|
19100
|
+
let cursor = null;
|
|
19101
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
19102
|
+
const res = await rpc.request("model/list", cursor ? { cursor } : {});
|
|
19103
|
+
models.push(...res.data ?? []);
|
|
19104
|
+
cursor = res.nextCursor ?? null;
|
|
19105
|
+
if (!cursor) break;
|
|
19106
|
+
}
|
|
19107
|
+
return visibleCodexModels(models);
|
|
19108
|
+
}
|
|
17673
19109
|
/**
|
|
17674
19110
|
* One-shot Codex call for the utility model: an ephemeral thread in a read-only sandbox with
|
|
17675
19111
|
* approvals off, low reasoning effort, and no MCP servers. Returns the final agent message.
|
|
@@ -17867,10 +19303,11 @@ async function handleServerRequest(req, ac) {
|
|
|
17867
19303
|
/**
|
|
17868
19304
|
* Native sandbox + approval policy per autonomy level (§11.2):
|
|
17869
19305
|
* - supervised -> `untrusted`: every command that isn't known-safe and every patch asks
|
|
17870
|
-
* - auto -> workspace-write sandbox + `on-request`: the sandbox confines writes
|
|
17871
|
-
*
|
|
19306
|
+
* - auto -> workspace-write sandbox + `on-request`: the sandbox confines writes (to the
|
|
19307
|
+
* worktree, extra roots and the OS temp directory) and network; escaping it
|
|
19308
|
+
* (outside the worktree, network, force pushes) asks
|
|
17872
19309
|
* - full -> workspace-write + `never`, with network on: no approvals, still confined
|
|
17873
|
-
* to the worktree
|
|
19310
|
+
* to the worktree, extra roots and temp
|
|
17874
19311
|
*/
|
|
17875
19312
|
function codexPolicy(autonomy, readOnly = false) {
|
|
17876
19313
|
if (readOnly) return {
|
|
@@ -17895,13 +19332,41 @@ function codexPolicy(autonomy, readOnly = false) {
|
|
|
17895
19332
|
};
|
|
17896
19333
|
}
|
|
17897
19334
|
/**
|
|
19335
|
+
* Extra writable roots for the workspace-write sandbox: the session's additional directories,
|
|
19336
|
+
* plus the OS temp directories in `auto` and `full`, so writing a log or scratch file there
|
|
19337
|
+
* never needs an approval (§11.2).
|
|
19338
|
+
*/
|
|
19339
|
+
function codexWritableRoots(opts, tempRoots = osTempRoots()) {
|
|
19340
|
+
const temp = opts.readOnly || opts.autonomy === "supervised" ? [] : tempRoots;
|
|
19341
|
+
return [.../* @__PURE__ */ new Set([...opts.additionalDirectories, ...temp])];
|
|
19342
|
+
}
|
|
19343
|
+
/**
|
|
19344
|
+
* The `turn/start` sandbox override for an autonomy level: the same sandbox `thread/start`
|
|
19345
|
+
* configures, so a mid-session change applies from the next turn.
|
|
19346
|
+
*/
|
|
19347
|
+
function codexSandboxPolicy(opts, tempRoots = osTempRoots()) {
|
|
19348
|
+
const { sandbox, networkAccess } = codexPolicy(opts.autonomy, opts.readOnly);
|
|
19349
|
+
if (sandbox === "read-only") return {
|
|
19350
|
+
type: "readOnly",
|
|
19351
|
+
networkAccess
|
|
19352
|
+
};
|
|
19353
|
+
return {
|
|
19354
|
+
type: "workspaceWrite",
|
|
19355
|
+
writableRoots: codexWritableRoots(opts, tempRoots),
|
|
19356
|
+
networkAccess,
|
|
19357
|
+
excludeTmpdirEnvVar: false,
|
|
19358
|
+
excludeSlashTmp: false
|
|
19359
|
+
};
|
|
19360
|
+
}
|
|
19361
|
+
/**
|
|
17898
19362
|
* Config overrides for `thread/start` / `thread/resume` (same semantics as `-c key=value`).
|
|
17899
19363
|
* Sent over stdin as JSON, so MCP bearer tokens never appear on a command line.
|
|
17900
19364
|
*/
|
|
17901
|
-
function codexConfigOverrides(opts) {
|
|
19365
|
+
function codexConfigOverrides(opts, tempRoots = osTempRoots()) {
|
|
17902
19366
|
const { networkAccess } = codexPolicy(opts.autonomy, opts.readOnly);
|
|
17903
19367
|
const config = { "sandbox_workspace_write.network_access": networkAccess };
|
|
17904
|
-
|
|
19368
|
+
const roots = codexWritableRoots(opts, tempRoots);
|
|
19369
|
+
if (roots.length) config["sandbox_workspace_write.writable_roots"] = roots;
|
|
17905
19370
|
for (const server of opts.mcpServers) Object.assign(config, mcpOverride(server));
|
|
17906
19371
|
return config;
|
|
17907
19372
|
}
|
|
@@ -18556,10 +20021,14 @@ function rateLimits(snapshot) {
|
|
|
18556
20021
|
return { rateLimits: out };
|
|
18557
20022
|
}
|
|
18558
20023
|
// ../../packages/providers/src/codex/session.ts
|
|
18559
|
-
/**
|
|
18560
|
-
|
|
20024
|
+
/**
|
|
20025
|
+
* CodeDen effort -> Codex reasoning effort. `max` is sent only to models that list it
|
|
20026
|
+
* (`supported`); older models top out at `xhigh`.
|
|
20027
|
+
*/
|
|
20028
|
+
function codexEffort(effort, supported) {
|
|
18561
20029
|
if (!effort) return void 0;
|
|
18562
|
-
|
|
20030
|
+
if (effort !== "max") return effort;
|
|
20031
|
+
return supported?.includes("max") ? "max" : "xhigh";
|
|
18563
20032
|
}
|
|
18564
20033
|
/**
|
|
18565
20034
|
* A Codex session over `codex app-server` (§7.2): one app-server process and one thread per
|
|
@@ -18576,7 +20045,12 @@ var CodexSession = class {
|
|
|
18576
20045
|
turnPending = false;
|
|
18577
20046
|
/** Model/effort changes from `configure`, applied on every following turn. */
|
|
18578
20047
|
nextTurn = {};
|
|
20048
|
+
/** Current autonomy; after `setAutonomy`, every following `turn/start` carries it. */
|
|
20049
|
+
autonomy;
|
|
20050
|
+
autonomyChanged = false;
|
|
18579
20051
|
closed = false;
|
|
20052
|
+
/** The model the thread started with (the user's Codex config when none was chosen). */
|
|
20053
|
+
threadModel;
|
|
18580
20054
|
filePaths = /* @__PURE__ */ new Map();
|
|
18581
20055
|
constructor(opts, config) {
|
|
18582
20056
|
this.opts = opts;
|
|
@@ -18588,6 +20062,7 @@ var CodexSession = class {
|
|
|
18588
20062
|
this.mapper = new CodexEventMapper(opts.sessionId, emit);
|
|
18589
20063
|
this.mapper.codexThreadId = opts.resumeToken;
|
|
18590
20064
|
this.broker = new ApprovalBroker(emit);
|
|
20065
|
+
this.autonomy = opts.autonomy;
|
|
18591
20066
|
}
|
|
18592
20067
|
/** Start the app-server and the thread. Called by the adapter before returning the session. */
|
|
18593
20068
|
async start() {
|
|
@@ -18607,19 +20082,29 @@ var CodexSession = class {
|
|
|
18607
20082
|
...change
|
|
18608
20083
|
};
|
|
18609
20084
|
}
|
|
20085
|
+
/**
|
|
20086
|
+
* Autonomy from the next `turn/start` on, as approval-policy and sandbox overrides. A running
|
|
20087
|
+
* turn keeps the policy it started with (the app-server fixes it per turn).
|
|
20088
|
+
*/
|
|
20089
|
+
async setAutonomy(level) {
|
|
20090
|
+
this.autonomy = level;
|
|
20091
|
+
this.autonomyChanged = true;
|
|
20092
|
+
}
|
|
18610
20093
|
async send(input) {
|
|
18611
20094
|
if (this.closed) throw new ProviderSessionError("closed", "Session is closed");
|
|
18612
20095
|
if (this.turnPending || this.mapper.currentTurnId) throw new ProviderSessionError("turn_running", "A turn is already running");
|
|
18613
20096
|
this.turnPending = true;
|
|
18614
20097
|
try {
|
|
18615
20098
|
const rpc = await this.ensureServer();
|
|
18616
|
-
const effort = codexEffort(this.nextTurn.effort !== void 0 ? this.nextTurn.effort ?? void 0 : this.opts.effort);
|
|
18617
20099
|
const model = await this.turnModel(rpc);
|
|
20100
|
+
const inUse = model ?? explicitModel(this.opts.model) ?? this.threadModel;
|
|
20101
|
+
const effort = codexEffort(this.nextTurn.effort !== void 0 ? this.nextTurn.effort ?? void 0 : this.opts.effort, inUse ? this.config.modelEfforts?.(inUse) : void 0);
|
|
18618
20102
|
const res = await rpc.request("turn/start", {
|
|
18619
20103
|
threadId: this.threadId(),
|
|
18620
20104
|
input: toCodexInput(input),
|
|
18621
20105
|
...effort ? { effort } : {},
|
|
18622
|
-
...model !== void 0 ? { model } : {}
|
|
20106
|
+
...model !== void 0 ? { model } : {},
|
|
20107
|
+
...this.autonomyChanged ? this.policyOverride() : {}
|
|
18623
20108
|
});
|
|
18624
20109
|
this.mapper.beginTurn(res.turn.id);
|
|
18625
20110
|
} catch (error) {
|
|
@@ -18661,6 +20146,21 @@ var CodexSession = class {
|
|
|
18661
20146
|
this.events.close();
|
|
18662
20147
|
await this.rpc?.close();
|
|
18663
20148
|
}
|
|
20149
|
+
/** Session options with the current autonomy (a restarted app-server resumes with it). */
|
|
20150
|
+
currentOptions() {
|
|
20151
|
+
return {
|
|
20152
|
+
...this.opts,
|
|
20153
|
+
autonomy: this.autonomy
|
|
20154
|
+
};
|
|
20155
|
+
}
|
|
20156
|
+
/** `turn/start` overrides that carry the current autonomy (§11.2). */
|
|
20157
|
+
policyOverride() {
|
|
20158
|
+
const opts = this.currentOptions();
|
|
20159
|
+
return {
|
|
20160
|
+
approvalPolicy: codexPolicy(opts.autonomy, opts.readOnly).approvalPolicy,
|
|
20161
|
+
sandboxPolicy: codexSandboxPolicy(opts)
|
|
20162
|
+
};
|
|
20163
|
+
}
|
|
18664
20164
|
threadId() {
|
|
18665
20165
|
const id = this.mapper.codexThreadId;
|
|
18666
20166
|
if (!id) throw new ProviderSessionError("not_started", "Codex thread is not started");
|
|
@@ -18694,12 +20194,13 @@ var CodexSession = class {
|
|
|
18694
20194
|
cwd: this.opts.cwd
|
|
18695
20195
|
});
|
|
18696
20196
|
this.rpc = rpc;
|
|
18697
|
-
const
|
|
20197
|
+
const opts = this.currentOptions();
|
|
20198
|
+
const { approvalPolicy, sandbox } = codexPolicy(opts.autonomy, opts.readOnly);
|
|
18698
20199
|
const common = {
|
|
18699
20200
|
cwd: this.opts.cwd,
|
|
18700
20201
|
approvalPolicy,
|
|
18701
20202
|
sandbox,
|
|
18702
|
-
config: codexConfigOverrides(
|
|
20203
|
+
config: codexConfigOverrides(opts),
|
|
18703
20204
|
...explicitModel(this.opts.model) ? { model: explicitModel(this.opts.model) } : {},
|
|
18704
20205
|
...this.opts.systemPrompt ? { developerInstructions: this.opts.systemPrompt } : {}
|
|
18705
20206
|
};
|
|
@@ -18726,6 +20227,7 @@ var CodexSession = class {
|
|
|
18726
20227
|
}
|
|
18727
20228
|
res ??= await rpc.request("thread/start", common);
|
|
18728
20229
|
this.mapper.codexThreadId = res.thread.id;
|
|
20230
|
+
this.threadModel = res.model || void 0;
|
|
18729
20231
|
this.events.push({
|
|
18730
20232
|
type: "session.updated",
|
|
18731
20233
|
resumeToken: res.thread.id,
|
|
@@ -18806,40 +20308,38 @@ const capabilities$1 = {
|
|
|
18806
20308
|
planMode: false,
|
|
18807
20309
|
reasoningEffort: true,
|
|
18808
20310
|
mcp: true,
|
|
18809
|
-
skills: true
|
|
20311
|
+
skills: true,
|
|
20312
|
+
liveAutonomy: false
|
|
18810
20313
|
};
|
|
18811
|
-
const
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
"
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18824
|
-
|
|
18825
|
-
|
|
18826
|
-
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
label: m.displayName || m.model,
|
|
18831
|
-
...m.description ? { description: m.description } : {},
|
|
18832
|
-
efforts,
|
|
18833
|
-
...defaultEffort.success ? { defaultEffort: defaultEffort.data } : {},
|
|
18834
|
-
...m.isDefault ? { isDefault: true } : {}
|
|
18835
|
-
};
|
|
20314
|
+
const CONFIGURED_MODEL = "The model configured in ~/.codex/config.toml";
|
|
20315
|
+
/**
|
|
20316
|
+
* Until Codex has listed its models, one entry that leaves the choice to the user's Codex
|
|
20317
|
+
* config. `note` says why the real list is missing when discovery failed.
|
|
20318
|
+
*/
|
|
20319
|
+
function fallbackModels(note) {
|
|
20320
|
+
return [{
|
|
20321
|
+
id: DEFAULT_MODEL_ID,
|
|
20322
|
+
label: "Default",
|
|
20323
|
+
description: note ? `${CONFIGURED_MODEL}. ${note}` : CONFIGURED_MODEL,
|
|
20324
|
+
efforts: [
|
|
20325
|
+
"minimal",
|
|
20326
|
+
"low",
|
|
20327
|
+
"medium",
|
|
20328
|
+
"high",
|
|
20329
|
+
"xhigh"
|
|
20330
|
+
],
|
|
20331
|
+
isDefault: true
|
|
20332
|
+
}];
|
|
18836
20333
|
}
|
|
18837
20334
|
/** Codex through `codex app-server` JSON-RPC (§7.2). */
|
|
18838
20335
|
var CodexAdapter = class {
|
|
18839
20336
|
options;
|
|
18840
20337
|
id = CODEX_PROVIDER_ID;
|
|
18841
20338
|
displayName = "Codex";
|
|
18842
|
-
|
|
20339
|
+
catalog = new ModelCatalog(fallbackModels());
|
|
20340
|
+
/** True once Codex reported a real model list (discovery or a session). */
|
|
20341
|
+
listed = false;
|
|
20342
|
+
refreshing;
|
|
18843
20343
|
cachedExe;
|
|
18844
20344
|
constructor(options = {}) {
|
|
18845
20345
|
this.options = options;
|
|
@@ -18856,7 +20356,20 @@ var CodexAdapter = class {
|
|
|
18856
20356
|
return { ...capabilities$1 };
|
|
18857
20357
|
}
|
|
18858
20358
|
models() {
|
|
18859
|
-
return this.
|
|
20359
|
+
return this.catalog.list();
|
|
20360
|
+
}
|
|
20361
|
+
onModelsChanged(listener) {
|
|
20362
|
+
return this.catalog.subscribe(listener);
|
|
20363
|
+
}
|
|
20364
|
+
/**
|
|
20365
|
+
* Load Codex's model list through a short-lived `codex app-server` (`model/list`; no thread,
|
|
20366
|
+
* no turn). Concurrent calls share one discovery. Never throws.
|
|
20367
|
+
*/
|
|
20368
|
+
refreshModels() {
|
|
20369
|
+
this.refreshing ??= this.discoverModels().finally(() => {
|
|
20370
|
+
this.refreshing = void 0;
|
|
20371
|
+
});
|
|
20372
|
+
return this.refreshing;
|
|
18860
20373
|
}
|
|
18861
20374
|
async openSession(opts) {
|
|
18862
20375
|
const executable = this.executable();
|
|
@@ -18864,9 +20377,10 @@ var CodexAdapter = class {
|
|
|
18864
20377
|
const session = new CodexSession(opts, {
|
|
18865
20378
|
executable,
|
|
18866
20379
|
onModels: (models) => {
|
|
18867
|
-
const visible = models
|
|
18868
|
-
if (visible.length) this.
|
|
18869
|
-
}
|
|
20380
|
+
const visible = visibleCodexModels(models);
|
|
20381
|
+
if (visible.length) this.setModels(visible);
|
|
20382
|
+
},
|
|
20383
|
+
modelEfforts: (model) => this.catalog.list().find((m) => m.id === model)?.efforts
|
|
18870
20384
|
});
|
|
18871
20385
|
try {
|
|
18872
20386
|
await session.start();
|
|
@@ -18879,7 +20393,65 @@ var CodexAdapter = class {
|
|
|
18879
20393
|
utilityModel() {
|
|
18880
20394
|
return new CodexUtilityModel(() => this.executable());
|
|
18881
20395
|
}
|
|
20396
|
+
async discoverModels() {
|
|
20397
|
+
const exe = this.executable();
|
|
20398
|
+
if (!exe) return;
|
|
20399
|
+
try {
|
|
20400
|
+
const models = await listCodexModels(exe, { timeoutMs: this.options.modelListTimeoutMs });
|
|
20401
|
+
if (models.length) this.setModels(models);
|
|
20402
|
+
else this.discoveryFailed("Codex listed no models.");
|
|
20403
|
+
} catch (error) {
|
|
20404
|
+
const reason = (error instanceof Error ? error.message : String(error)).slice(0, 200);
|
|
20405
|
+
this.discoveryFailed(`Codex's model list could not be loaded: ${reason}`);
|
|
20406
|
+
}
|
|
20407
|
+
}
|
|
20408
|
+
setModels(models) {
|
|
20409
|
+
this.listed = true;
|
|
20410
|
+
this.catalog.set(models);
|
|
20411
|
+
}
|
|
20412
|
+
/** Keep the last real list; without one, say why only the configured default is offered. */
|
|
20413
|
+
discoveryFailed(note) {
|
|
20414
|
+
if (!this.listed) this.catalog.set(fallbackModels(note));
|
|
20415
|
+
}
|
|
20416
|
+
};
|
|
20417
|
+
// ../../packages/providers/src/fake/mcp-result.ts
|
|
20418
|
+
/** A long, unbroken token, like the ids, hashes and base64 blobs real tool results carry. */
|
|
20419
|
+
const blob = "c0de".repeat(12) + "d3n".repeat(60);
|
|
20420
|
+
/** Input of the scripted `[mcp]` tool call: an issue reference with a long unbroken URL. */
|
|
20421
|
+
const fakeMcpInput = {
|
|
20422
|
+
issue: {
|
|
20423
|
+
number: 3,
|
|
20424
|
+
url: `https://github.com/codeden-e2e/demo-repo/issues/3?trace=${blob}`
|
|
20425
|
+
},
|
|
20426
|
+
priority: "low"
|
|
18882
20427
|
};
|
|
20428
|
+
/**
|
|
20429
|
+
* Result text of the scripted `[mcp]` tool call: one compact line of JSON, several kilobytes
|
|
20430
|
+
* long, shaped like a triage result. Lets tests check that long tool output stays inside the
|
|
20431
|
+
* transcript column.
|
|
20432
|
+
*/
|
|
20433
|
+
function fakeMcpResult() {
|
|
20434
|
+
const body = Array.from({ length: 12 }, (_, i) => `Step ${i + 1}: list the models the provider reports before a session starts.`).join(" ");
|
|
20435
|
+
return JSON.stringify({ issue: {
|
|
20436
|
+
projectId: "a72f9074-7b37-4693-bbae-8b108576aefc",
|
|
20437
|
+
number: 3,
|
|
20438
|
+
title: "Discover the real model list without running a session first",
|
|
20439
|
+
state: "open",
|
|
20440
|
+
url: `https://github.com/codeden-e2e/demo-repo/issues/3?trace=${blob}`,
|
|
20441
|
+
labels: [
|
|
20442
|
+
"enhancement",
|
|
20443
|
+
"providers",
|
|
20444
|
+
"low-priority"
|
|
20445
|
+
],
|
|
20446
|
+
body,
|
|
20447
|
+
trace: blob,
|
|
20448
|
+
blockedBy: [{
|
|
20449
|
+
number: 2,
|
|
20450
|
+
title: "Model picker redesign",
|
|
20451
|
+
state: "open"
|
|
20452
|
+
}]
|
|
20453
|
+
} });
|
|
20454
|
+
}
|
|
18883
20455
|
// ../../packages/providers/src/fake/session.ts
|
|
18884
20456
|
var Interrupted$1 = class extends Error {};
|
|
18885
20457
|
/** A scripted, deterministic session. Ids are counters scoped to the session. */
|
|
@@ -18895,6 +20467,7 @@ var FakeSession = class {
|
|
|
18895
20467
|
constructor(opts, fake) {
|
|
18896
20468
|
this.opts = opts;
|
|
18897
20469
|
this.fake = fake;
|
|
20470
|
+
this.autonomy = opts.autonomy;
|
|
18898
20471
|
this.token = opts.resumeToken ?? `fake-${opts.sessionId}`;
|
|
18899
20472
|
this.rootThreadId = `${opts.sessionId}:thread-root`;
|
|
18900
20473
|
this.events.push({
|
|
@@ -18922,6 +20495,11 @@ var FakeSession = class {
|
|
|
18922
20495
|
model: change.model ?? "fake-1"
|
|
18923
20496
|
});
|
|
18924
20497
|
}
|
|
20498
|
+
/** Current autonomy. Full skips the scripted command approval, as it would for real. */
|
|
20499
|
+
autonomy;
|
|
20500
|
+
async setAutonomy(level) {
|
|
20501
|
+
this.autonomy = level;
|
|
20502
|
+
}
|
|
18925
20503
|
async send(input) {
|
|
18926
20504
|
if (this.events.isClosed) throw new ProviderSessionError("closed", "Session is closed");
|
|
18927
20505
|
if (this.turn) throw new ProviderSessionError("turn_running", "A turn is already running");
|
|
@@ -19003,7 +20581,8 @@ var FakeSession = class {
|
|
|
19003
20581
|
}
|
|
19004
20582
|
await this.streamAssistant(turn, threadId, this.replyFor(prompt));
|
|
19005
20583
|
await this.todoList(turn);
|
|
19006
|
-
if (
|
|
20584
|
+
if (prompt.includes("[mcp]")) await this.mcpToolCall(turn);
|
|
20585
|
+
if ((this.fake.approval || prompt.includes("[approval]")) && this.autonomy !== "full" ? await this.approval(turn) : true) await this.commandToolCall(turn);
|
|
19007
20586
|
if (this.fake.subagent || prompt.includes("[subagent]")) await this.subagent(turn);
|
|
19008
20587
|
if (this.fake.followups ?? true) {
|
|
19009
20588
|
await this.step(turn);
|
|
@@ -19188,6 +20767,45 @@ var FakeSession = class {
|
|
|
19188
20767
|
});
|
|
19189
20768
|
return allowed;
|
|
19190
20769
|
}
|
|
20770
|
+
/** An MCP tool call with a long, one-line JSON result (`[mcp]` in the prompt). */
|
|
20771
|
+
async mcpToolCall(turn) {
|
|
20772
|
+
const { threadId, turnId } = turn;
|
|
20773
|
+
const base = {
|
|
20774
|
+
id: this.nextId("item"),
|
|
20775
|
+
kind: "tool_call",
|
|
20776
|
+
toolKind: "mcp",
|
|
20777
|
+
toolName: "mcp__codeden__triage_issue",
|
|
20778
|
+
server: "codeden",
|
|
20779
|
+
tool: "triage_issue",
|
|
20780
|
+
title: "codeden: triage_issue",
|
|
20781
|
+
input: fakeMcpInput
|
|
20782
|
+
};
|
|
20783
|
+
await this.step(turn);
|
|
20784
|
+
this.emit({
|
|
20785
|
+
type: "item.started",
|
|
20786
|
+
threadId,
|
|
20787
|
+
turnId,
|
|
20788
|
+
item: {
|
|
20789
|
+
...base,
|
|
20790
|
+
status: "in_progress"
|
|
20791
|
+
}
|
|
20792
|
+
});
|
|
20793
|
+
await this.step(turn);
|
|
20794
|
+
this.emit({
|
|
20795
|
+
type: "item.completed",
|
|
20796
|
+
threadId,
|
|
20797
|
+
turnId,
|
|
20798
|
+
item: {
|
|
20799
|
+
...base,
|
|
20800
|
+
status: "completed",
|
|
20801
|
+
output: {
|
|
20802
|
+
text: fakeMcpResult(),
|
|
20803
|
+
truncated: false
|
|
20804
|
+
},
|
|
20805
|
+
durationMs: 12
|
|
20806
|
+
}
|
|
20807
|
+
});
|
|
20808
|
+
}
|
|
19191
20809
|
async commandToolCall(turn) {
|
|
19192
20810
|
const { threadId, turnId } = turn;
|
|
19193
20811
|
const id = this.nextId("item");
|
|
@@ -19356,13 +20974,15 @@ const capabilities = {
|
|
|
19356
20974
|
planMode: false,
|
|
19357
20975
|
reasoningEffort: false,
|
|
19358
20976
|
mcp: false,
|
|
19359
|
-
skills: false
|
|
20977
|
+
skills: false,
|
|
20978
|
+
liveAutonomy: true
|
|
19360
20979
|
};
|
|
19361
20980
|
/**
|
|
19362
20981
|
* Deterministic scripted provider for demos and tests (§7.2). Every turn streams:
|
|
19363
20982
|
* user message, assistant message (word deltas), a todo list, a command tool call,
|
|
19364
20983
|
* optionally an approval request (waits for `respond`) and a subagent child thread,
|
|
19365
|
-
* followups, usage, then `turn.completed`. Prompts containing `[fail]` fail the turn
|
|
20984
|
+
* followups, usage, then `turn.completed`. Prompts containing `[fail]` fail the turn;
|
|
20985
|
+
* `[mcp]` adds an MCP tool call with a long JSON result before the command.
|
|
19366
20986
|
*/
|
|
19367
20987
|
var FakeProvider = class {
|
|
19368
20988
|
options;
|
|
@@ -20315,12 +21935,14 @@ function createImageEngineRegistry(opts = {}) {
|
|
|
20315
21935
|
/** The set of adapters a runner has loaded, keyed by id. */
|
|
20316
21936
|
var ProviderRegistry = class {
|
|
20317
21937
|
adapters = /* @__PURE__ */ new Map();
|
|
21938
|
+
listeners = /* @__PURE__ */ new Set();
|
|
20318
21939
|
constructor(adapters = []) {
|
|
20319
21940
|
for (const adapter of adapters) this.register(adapter);
|
|
20320
21941
|
}
|
|
20321
21942
|
register(adapter) {
|
|
20322
21943
|
if (this.adapters.has(adapter.id)) throw new Error(`Provider "${adapter.id}" is already registered`);
|
|
20323
21944
|
this.adapters.set(adapter.id, adapter);
|
|
21945
|
+
adapter.onModelsChanged?.(() => this.notify());
|
|
20324
21946
|
}
|
|
20325
21947
|
get(id) {
|
|
20326
21948
|
return this.adapters.get(id);
|
|
@@ -20331,6 +21953,24 @@ var ProviderRegistry = class {
|
|
|
20331
21953
|
info() {
|
|
20332
21954
|
return this.list().map(providerInfo);
|
|
20333
21955
|
}
|
|
21956
|
+
/**
|
|
21957
|
+
* Refresh the model lists of all adapters (or those in `ids`) that support it. Never throws;
|
|
21958
|
+
* changes reach `onChange` listeners.
|
|
21959
|
+
*/
|
|
21960
|
+
async refreshModels(ids) {
|
|
21961
|
+
const adapters = ids ? this.list().filter((a) => ids.includes(a.id)) : this.list();
|
|
21962
|
+
await Promise.all(adapters.map((a) => a.refreshModels?.().catch(() => void 0)));
|
|
21963
|
+
}
|
|
21964
|
+
/** Call `listener` whenever `info()` changes (a provider's models). Returns unsubscribe. */
|
|
21965
|
+
onChange(listener) {
|
|
21966
|
+
this.listeners.add(listener);
|
|
21967
|
+
return () => this.listeners.delete(listener);
|
|
21968
|
+
}
|
|
21969
|
+
notify() {
|
|
21970
|
+
for (const listener of this.listeners) try {
|
|
21971
|
+
listener();
|
|
21972
|
+
} catch {}
|
|
21973
|
+
}
|
|
20334
21974
|
};
|
|
20335
21975
|
/** Fresh instances of the built-in real providers (Claude Code, Codex). */
|
|
20336
21976
|
function builtinProviders() {
|
|
@@ -23849,7 +25489,8 @@ function installChromium(log) {
|
|
|
23849
25489
|
"chromium"
|
|
23850
25490
|
], {
|
|
23851
25491
|
windowsHide: true,
|
|
23852
|
-
timeout: 6e5
|
|
25492
|
+
timeout: 6e5,
|
|
25493
|
+
env: ownNodeEnv()
|
|
23853
25494
|
}, (error) => error ? reject(error) : resolve());
|
|
23854
25495
|
});
|
|
23855
25496
|
}
|
|
@@ -23888,13 +25529,18 @@ function firstLine(error) {
|
|
|
23888
25529
|
/** Names of CodeDen-internal variables that must never reach agents, shells or setup scripts. */
|
|
23889
25530
|
const SECRET = /^CODEDEN_.*(TOKEN|SECRET|KEY|PASSWORD)/i;
|
|
23890
25531
|
/**
|
|
25532
|
+
* Set when the desktop app runs CodeDen's own processes on its Electron binary (§17). It must
|
|
25533
|
+
* not reach the user's shells and agents: an Electron app started there would run as Node.js.
|
|
25534
|
+
*/
|
|
25535
|
+
const RUNTIME_ONLY = /* @__PURE__ */ new Set(["ELECTRON_RUN_AS_NODE"]);
|
|
25536
|
+
/**
|
|
23891
25537
|
* Environment for child processes (§15): the user's environment minus CodeDen's own
|
|
23892
25538
|
* secrets. `CODEDEN_HOME` and similar non-secret variables are kept.
|
|
23893
25539
|
*/
|
|
23894
25540
|
function childEnv(extra = {}) {
|
|
23895
25541
|
const out = {};
|
|
23896
25542
|
for (const [key, value] of Object.entries(process.env)) {
|
|
23897
|
-
if (value === void 0 || SECRET.test(key)) continue;
|
|
25543
|
+
if (value === void 0 || SECRET.test(key) || RUNTIME_ONLY.has(key)) continue;
|
|
23898
25544
|
out[key] = value;
|
|
23899
25545
|
}
|
|
23900
25546
|
return {
|
|
@@ -24211,6 +25857,22 @@ async function topLevel(cwd) {
|
|
|
24211
25857
|
return resolve(await gitText(cwd, ["rev-parse", "--show-toplevel"]));
|
|
24212
25858
|
}
|
|
24213
25859
|
/**
|
|
25860
|
+
* Copy the index for use as a temporary index, keeping its modification time. Git trusts an
|
|
25861
|
+
* entry's cached stat data only when the entry is older than the index file; an entry written
|
|
25862
|
+
* in the same timestamp tick as the index is "racily clean" and its content is re-read. A
|
|
25863
|
+
* copy stamped with the current time (Linux `copyFile` does that) would make such entries
|
|
25864
|
+
* look safe, and a same-size rewrite in that tick would be missed by diffs and checkpoints.
|
|
25865
|
+
*/
|
|
25866
|
+
async function copyIndex(realIndex, tmp) {
|
|
25867
|
+
try {
|
|
25868
|
+
await copyFile(realIndex, tmp);
|
|
25869
|
+
} catch {
|
|
25870
|
+
return;
|
|
25871
|
+
}
|
|
25872
|
+
const { atime, mtime } = await stat(realIndex);
|
|
25873
|
+
await utimes(tmp, atime, mtime);
|
|
25874
|
+
}
|
|
25875
|
+
/**
|
|
24214
25876
|
* Write the full working tree (tracked + untracked, minus ignored files) as a tree object
|
|
24215
25877
|
* using a temporary index seeded from a copy of the real one (for its stat cache). The
|
|
24216
25878
|
* user's index, HEAD and branch are never modified.
|
|
@@ -24225,7 +25887,7 @@ async function snapshotTree(cwd) {
|
|
|
24225
25887
|
]);
|
|
24226
25888
|
const tmp = join(tmpdir(), `codeden-index-${randomBytes(8).toString("hex")}`);
|
|
24227
25889
|
try {
|
|
24228
|
-
await
|
|
25890
|
+
await copyIndex(realIndex, tmp);
|
|
24229
25891
|
const env = { GIT_INDEX_FILE: tmp };
|
|
24230
25892
|
await git(top, [
|
|
24231
25893
|
"add",
|
|
@@ -24874,6 +26536,15 @@ async function prepareWorkspace(cmd, ctx) {
|
|
|
24874
26536
|
setup
|
|
24875
26537
|
};
|
|
24876
26538
|
}
|
|
26539
|
+
/**
|
|
26540
|
+
* Refuse to remove a worktree with uncommitted changes (`worktree_dirty`) unless forced. Runs
|
|
26541
|
+
* before terminals and processes in the worktree are stopped, so a dirty worktree is left
|
|
26542
|
+
* exactly as it was. Ignored files (build output, `node_modules`) don't count.
|
|
26543
|
+
*/
|
|
26544
|
+
async function assertRemovable(cmd) {
|
|
26545
|
+
if (cmd.force || !existsSync(cmd.path) || !await isWorkTree(cmd.path)) return;
|
|
26546
|
+
if ((await git(cmd.path, ["status", "--porcelain"])).stdout.toString("utf8").trim()) throw new CommandError("worktree_dirty", `${cmd.path} has uncommitted changes; commit them or remove it with force`);
|
|
26547
|
+
}
|
|
24877
26548
|
/** `workspace.remove`: remove a worktree (retrying briefly for Windows file locks). */
|
|
24878
26549
|
async function removeWorkspace(cmd) {
|
|
24879
26550
|
const path = resolve(cmd.path);
|
|
@@ -24935,7 +26606,9 @@ async function executeCommand(core, command) {
|
|
|
24935
26606
|
return {};
|
|
24936
26607
|
case "provider.detect": {
|
|
24937
26608
|
const adapters = command.providerId ? registry.list().filter((a) => a.id === command.providerId) : registry.list();
|
|
24938
|
-
|
|
26609
|
+
const statuses = await Promise.all(adapters.map((a) => a.detect()));
|
|
26610
|
+
registry.refreshModels(adapters.map((a) => a.id));
|
|
26611
|
+
return { statuses };
|
|
24939
26612
|
}
|
|
24940
26613
|
case "provider.oneShot": {
|
|
24941
26614
|
const utility = registry.get(command.providerId)?.utilityModel?.();
|
|
@@ -24959,6 +26632,7 @@ async function executeCommand(core, command) {
|
|
|
24959
26632
|
emit: core.emit
|
|
24960
26633
|
});
|
|
24961
26634
|
case "workspace.remove":
|
|
26635
|
+
await assertRemovable(command);
|
|
24962
26636
|
await terminals.closeUnder(command.path);
|
|
24963
26637
|
await core.supervisor.killUnder(command.path);
|
|
24964
26638
|
return removeWorkspace(command);
|
|
@@ -26674,6 +28348,10 @@ var ServerConnection = class {
|
|
|
26674
28348
|
for (const event of this.core.journal.after(msg.lastAckedSeq)) this.send(event);
|
|
26675
28349
|
this.attached = true;
|
|
26676
28350
|
this.onAttach(this);
|
|
28351
|
+
this.send({
|
|
28352
|
+
type: "providers",
|
|
28353
|
+
providers: this.core.registry.info()
|
|
28354
|
+
});
|
|
26677
28355
|
return;
|
|
26678
28356
|
case "ack":
|
|
26679
28357
|
this.core.journal.ack(msg.seq);
|
|
@@ -26833,14 +28511,20 @@ var SessionHost = class {
|
|
|
26833
28511
|
async interrupt(sessionId) {
|
|
26834
28512
|
await this.get(sessionId).session.interrupt();
|
|
26835
28513
|
}
|
|
26836
|
-
/**
|
|
28514
|
+
/**
|
|
28515
|
+
* Model/effort for the next turn and the autonomy level, when the provider can change them
|
|
28516
|
+
* live. Checks every requested setting before applying any.
|
|
28517
|
+
*/
|
|
26837
28518
|
async configure(cmd) {
|
|
26838
28519
|
const { session } = this.get(cmd.sessionId);
|
|
26839
|
-
if (!session.configure) throw new CommandError("unsupported", "This provider cannot change model or effort live");
|
|
26840
28520
|
const change = {};
|
|
26841
28521
|
if (cmd.model !== void 0) change.model = cmd.model;
|
|
26842
28522
|
if (cmd.effort !== void 0) change.effort = cmd.effort;
|
|
26843
|
-
|
|
28523
|
+
const modelOrEffort = Object.keys(change).length > 0;
|
|
28524
|
+
if (modelOrEffort && !session.configure) throw new CommandError("unsupported", "This provider cannot change model or effort live");
|
|
28525
|
+
if (cmd.autonomy !== void 0 && !session.setAutonomy) throw new CommandError("unsupported", "This provider cannot change autonomy live");
|
|
28526
|
+
if (modelOrEffort) await session.configure?.(change);
|
|
28527
|
+
if (cmd.autonomy !== void 0) await session.setAutonomy?.(cmd.autonomy);
|
|
26844
28528
|
}
|
|
26845
28529
|
async respond(cmd) {
|
|
26846
28530
|
await this.get(cmd.sessionId).session.respond(cmd.requestId, cmd.response);
|
|
@@ -26968,7 +28652,7 @@ const DEFAULT_DRAIN_TIMEOUT = 3e5;
|
|
|
26968
28652
|
*/
|
|
26969
28653
|
async function startRunner(opts) {
|
|
26970
28654
|
const log = opts.log ?? (() => {});
|
|
26971
|
-
const version = opts.version ??
|
|
28655
|
+
const version = opts.version ?? CODEDEN_VERSION;
|
|
26972
28656
|
const dir = join(opts.home, "runner");
|
|
26973
28657
|
mkdirSync(dir, { recursive: true });
|
|
26974
28658
|
const lockFile = join(dir, "runner.json");
|
|
@@ -27007,6 +28691,12 @@ async function startRunner(opts) {
|
|
|
27007
28691
|
stream: data
|
|
27008
28692
|
});
|
|
27009
28693
|
};
|
|
28694
|
+
const stopProviderUpdates = registry.onChange(() => {
|
|
28695
|
+
if (!stopped) attached?.deliver({
|
|
28696
|
+
type: "providers",
|
|
28697
|
+
providers: registry.info()
|
|
28698
|
+
});
|
|
28699
|
+
});
|
|
27010
28700
|
const supervisor = new ProcessSupervisor();
|
|
27011
28701
|
const host = new SessionHost(registry, emit, { log });
|
|
27012
28702
|
const terminals = new TerminalHost({
|
|
@@ -27103,11 +28793,13 @@ async function startRunner(opts) {
|
|
|
27103
28793
|
writeFileSync(tmp, `${JSON.stringify(lock, null, 2)}\n`, { mode: 384 });
|
|
27104
28794
|
renameSync(tmp, lockFile);
|
|
27105
28795
|
log(`runner ${journal.runnerId} listening on 127.0.0.1:${port}`);
|
|
28796
|
+
registry.refreshModels();
|
|
27106
28797
|
const removeLock = () => removeOwnLock(lockFile);
|
|
27107
28798
|
process.once("exit", removeLock);
|
|
27108
28799
|
async function close() {
|
|
27109
28800
|
closing ??= (async () => {
|
|
27110
28801
|
removeLock();
|
|
28802
|
+
stopProviderUpdates();
|
|
27111
28803
|
await host.closeAll();
|
|
27112
28804
|
await terminals.closeAll();
|
|
27113
28805
|
await browserManager.shutdown().catch((error) => log(`browser: ${String(error)}`));
|
|
@@ -27132,6 +28824,6 @@ async function startRunner(opts) {
|
|
|
27132
28824
|
close
|
|
27133
28825
|
};
|
|
27134
28826
|
}
|
|
27135
|
-
export {
|
|
28827
|
+
export { ListAutomationRunsQuery as $, literal as $n, FactoryTuning as $t, SessionFileQuery as A, FactoryLimits as An, LocalSttModelParams as At, ImageTestRequest as B, McpServerUpsertRequest as Bn, CreateGithubIssueRequest as Bt, isElectronRuntime as C, ProjectConfigFile as Cn, ThreadItemsQuery as Ct, CODEDEN_VERSION as D, Project as Dn, agentEventToNewEvent as Dt, wrapper_default as E, WorkKey as En, HealthResponse as Et, ListConflictsQuery as F, AgentConfigListQuery as Fn, UpdateStatus as Ft, BrowserNavigateRequest as G, _enum as Gn, GithubPr as Gt, BrowserClientMessage as H, ProviderId as Hn, GithubIssue as Ht, ListNoticesQuery as I, AgentConfigLocator as In, compareVersions$1 as It, BrowserViewportRequest as J, boolean as Jn, githubPrId as Jt, BrowserNewTabRequest as K, _null as Kn, MergePrRequest as Kt, ResolveConflictRequest as L, AgentConfigWriteRequest as Ln, isNewerVersion as Lt, RunnerToServerMessage as M, InboxItemKind as Mn, TtsRequest as Mt, ServerLock as N, AgentConfigDeleteQuery as Nn, AppPointer as Nt, UsageQuery as O, sortProjects as On, Settings as Ot, ArchiveSessionRequest as P, AgentConfigDiffRequest as Pn, UpdateInstallRequest as Pt, CreateAutomationRequest as Q, intersection as Qn, FactoryTriageRequest as Qt, RoleMessageRequest as R, McpServerDiffRequest as Rn, ConflictRecord as Rt, acquireInstallLock as S, SessionOptions as Sn, SteerRequest as St, import_websocket_server as T, StartedBy as Tn, UpdateProjectRequest as Tt, BrowserDevServerRequest as U, ReasoningEffort as Un, GithubLabelRequest as Ut, summarizeActivity as V, AutonomyLevel as Vn, GithubCommentRequest as Vt, BrowserHistoryRequest as W, datetime as Wn, GithubListQuery as Wt, AutomationFileFrontmatter as X, discriminatedUnion as Xn, DiscoveryArea as Xt, BrowserWsQuery as Y, custom as Yn, parseGithubPrId as Yt, AutomationProjectRequest as Z, int as Zn, FactoryCandidate as Zt, importClaudeSdkFrom as _, SessionResult as _n, SecretKey as _t, Hono as a, Artifact as an, record as ar, TriageReport as at, runNpm as b, TerminalQuery as bn, SetSecretRequest as bt, serve as c, AutomationRun as cn, unknown as cr, UpdateAutomationRequest as ct, createImageEngineRegistry as d, SessionStatus as dn, safeParse$1 as dr, FileWriteRequest as dt, KillSwitchRequest as en, looseObject as er, ListAutomationsQuery as et, IMAGE_ASPECT_RATIOS as f, DiffScope as fn, prettifyError as fr, ListInboxQuery as ft, CLAUDE_SDK_VERSION as g, RestoreFilesRequest as gn, RespondRequest as gt, claudeSdkSource as h, GitPushRequest as hn, ResolveInboxRequest as ht, killTree as i, isFactoryMode as in, preprocess as ir, TriageDecisionRequest as it, RunnerLock as j, InboxItem as jn, SttQuery as jt, UsageSettings as k, DEFAULT_VISION_PATH as kn, LOCAL_STT_SAMPLE_RATE as kt, upgradeWebSocket as l, TriagePolicy as ln, ZodError as lr, CreateProjectRequest as lt, ImageGenerationError as m, GitCommitRequest as mn, ReorderProjectsRequest as mt, readLock as n, ReviewVerdict as nn, object as nr, SetAutomationEnabledRequest as nt, tryDecodeURIComponent as o, Automation as on, string as or, UpcomingRunsQuery as ot, resolveImageOutput as p, EditQueuedMessageRequest as pn, ListSessionsQuery as pt, BrowserOpenRequest as q, array as qn, TriageIssueRequest as qt, isAlive as r, SetAutonomyModeRequest as rn, optional as rr, StarterSetRequest as rt, bufferToFormData as s, AutomationGuards as sn, union as sr, UpdateAutomationProjectSettingsRequest as st, startRunner as t, PutVisionRequest as tn, number$1 as tr, RunAutomationRequest as tt, ImageEngineRegistry as u, Session as un, toJSONSchema as ur, FileReadQuery as ut, installClaudeSdk as v, SpeechSummaryRequest as vn, SendMessageRequest as vt, ownNodeEnv as w, ConflictDecision as wn, UiClientMessage as wt, resolveNpm as x, RunnerCommandResults as xn, StartSessionRequest as xt, isClaudeSdkInstalled as y, TerminalClientMessage as yn, SessionStartMeta as yt, UpdateSessionRequest as z, McpServerTarget as zn, NOTICE_BATCH_CODE as zt };
|
|
27136
28828
|
|
|
27137
|
-
//# sourceMappingURL=runner-
|
|
28829
|
+
//# sourceMappingURL=runner-CJetH9jW.js.map
|