@cabane/companion 0.6.125 → 0.6.126
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 +49 -0
- package/dist/cli.js +563 -167
- package/dist/pairing-config.js +6 -1
- package/dist/runtime.js +346 -119
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -541,6 +541,10 @@ var init_config = __esm({
|
|
|
541
541
|
// wraps the runner in an OS sandbox where the host supports one; `'none'`
|
|
542
542
|
// (the default) runs it as a plain child beside this process, a supported
|
|
543
543
|
// configuration in its own right rather than a degraded one.
|
|
544
|
+
// CT1684: `policy` is what the OS sandbox lets a turn reach, read and hold
|
|
545
|
+
// (`runner/sandbox-policy.ts`). It is validated there rather than here, so a
|
|
546
|
+
// bad policy refuses the turns it governs, with the field named, instead of
|
|
547
|
+
// keeping the companion from starting at all.
|
|
544
548
|
runners: z2.object({
|
|
545
549
|
enabled: z2.boolean().optional(),
|
|
546
550
|
provider: z2.enum(["local-process", "docker"]).optional(),
|
|
@@ -574,7 +578,8 @@ var init_config = __esm({
|
|
|
574
578
|
// container's git for an allowed host, e.g. `!gh auth git-credential`.
|
|
575
579
|
// Absent: git's own configured helpers on this device.
|
|
576
580
|
gitCredentialHelper: z2.string().min(1).optional()
|
|
577
|
-
}).strict().optional()
|
|
581
|
+
}).strict().optional(),
|
|
582
|
+
policy: z2.unknown().optional()
|
|
578
583
|
}).strict().optional()
|
|
579
584
|
});
|
|
580
585
|
}
|
|
@@ -6662,6 +6667,173 @@ var init_src = __esm({
|
|
|
6662
6667
|
}
|
|
6663
6668
|
});
|
|
6664
6669
|
|
|
6670
|
+
// src/runner/sandbox-policy.ts
|
|
6671
|
+
import { NetworkConfigSchema } from "@anthropic-ai/sandbox-runtime";
|
|
6672
|
+
import { globToRegex } from "@anthropic-ai/sandbox-runtime/dist/sandbox/sandbox-utils.js";
|
|
6673
|
+
import { z as z13 } from "zod";
|
|
6674
|
+
function parseSandboxPolicy(raw) {
|
|
6675
|
+
if (raw === void 0) return { ok: true, policy: {} };
|
|
6676
|
+
const result = sandboxPolicySchema.safeParse(raw);
|
|
6677
|
+
if (!result.success) {
|
|
6678
|
+
const issue = result.error.issues[0];
|
|
6679
|
+
return { ok: false, error: `${fieldPath(issue.path)}: ${issue.message}` };
|
|
6680
|
+
}
|
|
6681
|
+
const refused = unenforceable(result.data);
|
|
6682
|
+
return refused ? { ok: false, error: refused } : { ok: true, policy: result.data };
|
|
6683
|
+
}
|
|
6684
|
+
function fieldPath(path) {
|
|
6685
|
+
return ["runners", "policy", ...path.map(String)].join(".");
|
|
6686
|
+
}
|
|
6687
|
+
function unenforceable(policy) {
|
|
6688
|
+
const hostLists = [
|
|
6689
|
+
["hosts.allow", "allowedDomains", policy.hosts?.allow],
|
|
6690
|
+
["hosts.deny", "deniedDomains", policy.hosts?.deny],
|
|
6691
|
+
...Object.entries(policy.credentials?.env ?? {}).map(
|
|
6692
|
+
([name, entry]) => [
|
|
6693
|
+
`credentials.env.${name}.hosts`,
|
|
6694
|
+
"allowedDomains",
|
|
6695
|
+
entry.hosts
|
|
6696
|
+
]
|
|
6697
|
+
)
|
|
6698
|
+
];
|
|
6699
|
+
for (const [field, as, hosts] of hostLists) {
|
|
6700
|
+
for (const [i, host] of (hosts ?? []).entries()) {
|
|
6701
|
+
const check = NetworkConfigSchema.safeParse({
|
|
6702
|
+
allowedDomains: as === "allowedDomains" ? [host] : [],
|
|
6703
|
+
deniedDomains: as === "deniedDomains" ? [host] : []
|
|
6704
|
+
});
|
|
6705
|
+
if (!check.success) {
|
|
6706
|
+
const message = check.error.issues[0]?.message ?? "not a host pattern the sandbox accepts";
|
|
6707
|
+
return `runners.policy.${field}.${i}: ${message}`;
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
}
|
|
6711
|
+
const pathLists = [
|
|
6712
|
+
["filesystem.denyRead", policy.filesystem?.denyRead],
|
|
6713
|
+
["filesystem.allowRead", policy.filesystem?.allowRead],
|
|
6714
|
+
["filesystem.allowWrite", policy.filesystem?.allowWrite]
|
|
6715
|
+
];
|
|
6716
|
+
for (const [field, paths] of pathLists) {
|
|
6717
|
+
for (const [i, path] of (paths ?? []).entries()) {
|
|
6718
|
+
try {
|
|
6719
|
+
new RegExp(globToRegex(path));
|
|
6720
|
+
} catch (err) {
|
|
6721
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6722
|
+
return `runners.policy.${field}.${i}: not a pattern the sandbox can match (${message})`;
|
|
6723
|
+
}
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
return null;
|
|
6727
|
+
}
|
|
6728
|
+
function modelHostsFor(runtime) {
|
|
6729
|
+
if (runtime === "claude-code") return ANTHROPIC_HOSTS;
|
|
6730
|
+
if (runtime === "codex") return OPENAI_HOSTS;
|
|
6731
|
+
return [...ANTHROPIC_HOSTS, ...OPENAI_HOSTS];
|
|
6732
|
+
}
|
|
6733
|
+
function gitAuthVar(index) {
|
|
6734
|
+
return `CABANE_GIT_AUTH_${index}`;
|
|
6735
|
+
}
|
|
6736
|
+
function resolvePolicyCredentials(policy, store) {
|
|
6737
|
+
const missing = /* @__PURE__ */ new Set();
|
|
6738
|
+
const credentials = [];
|
|
6739
|
+
const lookup2 = (ref) => {
|
|
6740
|
+
const name = placeholderName(ref);
|
|
6741
|
+
const value = store.get(name);
|
|
6742
|
+
if (value === void 0) missing.add(name);
|
|
6743
|
+
return value;
|
|
6744
|
+
};
|
|
6745
|
+
for (const [name, entry] of Object.entries(policy.credentials?.env ?? {})) {
|
|
6746
|
+
const value = lookup2(entry.secret);
|
|
6747
|
+
if (value === void 0) continue;
|
|
6748
|
+
credentials.push({ name, value, hosts: entry.hosts });
|
|
6749
|
+
}
|
|
6750
|
+
(policy.credentials?.git ?? []).forEach((entry, i) => {
|
|
6751
|
+
const secret = lookup2(entry.secret);
|
|
6752
|
+
if (secret === void 0) return;
|
|
6753
|
+
const user = entry.username ?? "x-access-token";
|
|
6754
|
+
credentials.push({
|
|
6755
|
+
name: gitAuthVar(i),
|
|
6756
|
+
value: Buffer.from(`${user}:${secret}`).toString("base64"),
|
|
6757
|
+
hosts: [entry.host.replace(/:\d+$/, "")],
|
|
6758
|
+
gitHost: entry.host
|
|
6759
|
+
});
|
|
6760
|
+
});
|
|
6761
|
+
return { credentials, missing: [...missing].sort() };
|
|
6762
|
+
}
|
|
6763
|
+
function policyHasCredentials(policy) {
|
|
6764
|
+
return Object.keys(policy.credentials?.env ?? {}).length > 0 || (policy.credentials?.git ?? []).length > 0;
|
|
6765
|
+
}
|
|
6766
|
+
function postureLines(parsed) {
|
|
6767
|
+
if (!parsed.ok) return [`policy invalid \u2014 turns are refused until it's fixed: ${parsed.error}`];
|
|
6768
|
+
const { policy } = parsed;
|
|
6769
|
+
const mode = policy.filesystem?.mode ?? "blocklist";
|
|
6770
|
+
const defaults = policy.hosts?.defaults !== false;
|
|
6771
|
+
const lines2 = [
|
|
6772
|
+
`filesystem: ${mode}${mode === "strict" ? " (home unreadable except the checkout, harness state and allowRead)" : ""}`,
|
|
6773
|
+
`hosts: Cabane + the turn's model provider${defaults ? " + defaults (GitHub, npm, PyPI, crates.io)" : ""}${policy.hosts?.allow?.length ? ` + ${policy.hosts.allow.join(", ")}` : ""}`
|
|
6774
|
+
];
|
|
6775
|
+
if (policy.hosts?.deny?.length) lines2.push(`hosts denied: ${policy.hosts.deny.join(", ")}`);
|
|
6776
|
+
const extraDeny = policy.filesystem?.denyRead ?? [];
|
|
6777
|
+
if (extraDeny.length) lines2.push(`denied reads (extra): ${extraDeny.join(", ")}`);
|
|
6778
|
+
const allowRead = policy.filesystem?.allowRead ?? [];
|
|
6779
|
+
if (allowRead.length) lines2.push(`reads allowed back: ${allowRead.join(", ")}`);
|
|
6780
|
+
const allowWrite = policy.filesystem?.allowWrite ?? [];
|
|
6781
|
+
if (allowWrite.length) lines2.push(`writes allowed (extra): ${allowWrite.join(", ")}`);
|
|
6782
|
+
const env = Object.keys(policy.credentials?.env ?? {});
|
|
6783
|
+
const git = (policy.credentials?.git ?? []).map((g) => `git@${g.host}`);
|
|
6784
|
+
if (env.length || git.length) {
|
|
6785
|
+
lines2.push(`masked credentials: ${[...env, ...git].join(", ")}`);
|
|
6786
|
+
}
|
|
6787
|
+
return lines2;
|
|
6788
|
+
}
|
|
6789
|
+
var hostPattern, secretRef, envCredentialSchema, gitCredentialSchema, sandboxPolicySchema, DEFAULT_ALLOWED_HOSTS, ANTHROPIC_HOSTS, OPENAI_HOSTS, placeholderName, HarnessSandboxRefused;
|
|
6790
|
+
var init_sandbox_policy = __esm({
|
|
6791
|
+
"src/runner/sandbox-policy.ts"() {
|
|
6792
|
+
"use strict";
|
|
6793
|
+
hostPattern = z13.string().min(1);
|
|
6794
|
+
secretRef = z13.string().regex(/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/, "must be a ${NAME} reference into secrets.json");
|
|
6795
|
+
envCredentialSchema = z13.object({ secret: secretRef, hosts: z13.array(hostPattern).min(1) }).strict();
|
|
6796
|
+
gitCredentialSchema = z13.object({
|
|
6797
|
+
host: z13.string().regex(/^[A-Za-z0-9.-]+(:\d+)?$/, "must be a host name, like github.com"),
|
|
6798
|
+
username: z13.string().min(1).optional(),
|
|
6799
|
+
secret: secretRef
|
|
6800
|
+
}).strict();
|
|
6801
|
+
sandboxPolicySchema = z13.object({
|
|
6802
|
+
hosts: z13.object({
|
|
6803
|
+
allow: z13.array(hostPattern).optional(),
|
|
6804
|
+
deny: z13.array(hostPattern).optional(),
|
|
6805
|
+
defaults: z13.boolean().optional()
|
|
6806
|
+
}).strict().optional(),
|
|
6807
|
+
filesystem: z13.object({
|
|
6808
|
+
mode: z13.enum(["blocklist", "strict"]).optional(),
|
|
6809
|
+
denyRead: z13.array(z13.string().min(1)).optional(),
|
|
6810
|
+
allowRead: z13.array(z13.string().min(1)).optional(),
|
|
6811
|
+
allowWrite: z13.array(z13.string().min(1)).optional()
|
|
6812
|
+
}).strict().optional(),
|
|
6813
|
+
credentials: z13.object({
|
|
6814
|
+
env: z13.record(z13.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), envCredentialSchema).optional(),
|
|
6815
|
+
git: z13.array(gitCredentialSchema).optional()
|
|
6816
|
+
}).strict().optional()
|
|
6817
|
+
}).strict();
|
|
6818
|
+
DEFAULT_ALLOWED_HOSTS = [
|
|
6819
|
+
"github.com",
|
|
6820
|
+
"*.github.com",
|
|
6821
|
+
"*.githubusercontent.com",
|
|
6822
|
+
"registry.npmjs.org",
|
|
6823
|
+
"pypi.org",
|
|
6824
|
+
"files.pythonhosted.org",
|
|
6825
|
+
"crates.io",
|
|
6826
|
+
"static.crates.io",
|
|
6827
|
+
"index.crates.io"
|
|
6828
|
+
];
|
|
6829
|
+
ANTHROPIC_HOSTS = ["api.anthropic.com", "*.anthropic.com", "claude.ai", "*.claude.ai"];
|
|
6830
|
+
OPENAI_HOSTS = ["api.openai.com", "auth.openai.com", "chatgpt.com", "*.chatgpt.com"];
|
|
6831
|
+
placeholderName = (ref) => ref.slice(2, -1);
|
|
6832
|
+
HarnessSandboxRefused = class extends Error {
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
});
|
|
6836
|
+
|
|
6665
6837
|
// src/runner/opencode-lock.ts
|
|
6666
6838
|
function opencodeLockKey(serverUrl) {
|
|
6667
6839
|
return `opencode:${serverUrl}`;
|
|
@@ -7023,7 +7195,7 @@ var init_turn_containment = __esm({
|
|
|
7023
7195
|
// src/secrets.ts
|
|
7024
7196
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
7025
7197
|
import { join as join10 } from "path";
|
|
7026
|
-
import { z as
|
|
7198
|
+
import { z as z14 } from "zod";
|
|
7027
7199
|
function secretsPath() {
|
|
7028
7200
|
return join10(cabaneDir(), "secrets.json");
|
|
7029
7201
|
}
|
|
@@ -7108,19 +7280,30 @@ function resolveMcpSecrets(mcpServers, store) {
|
|
|
7108
7280
|
}
|
|
7109
7281
|
return { servers, missing: [...missing].sort() };
|
|
7110
7282
|
}
|
|
7111
|
-
|
|
7283
|
+
function missingSecretReason(missing) {
|
|
7284
|
+
const prefix = "missing_secret: ";
|
|
7285
|
+
for (let shown = missing.length; shown >= 0; shown--) {
|
|
7286
|
+
const omitted = missing.length - shown;
|
|
7287
|
+
const parts = [...missing.slice(0, shown), ...omitted > 0 ? [`+${omitted}`] : []];
|
|
7288
|
+
const reason = prefix + parts.join(",");
|
|
7289
|
+
if (reason.length <= ERROR_REASON_MAX) return reason;
|
|
7290
|
+
}
|
|
7291
|
+
return `${prefix}+${missing.length}`;
|
|
7292
|
+
}
|
|
7293
|
+
var PLACEHOLDER_RE, secretStoreSchema, ERROR_REASON_MAX;
|
|
7112
7294
|
var init_secrets = __esm({
|
|
7113
7295
|
"src/secrets.ts"() {
|
|
7114
7296
|
"use strict";
|
|
7115
7297
|
init_config();
|
|
7116
7298
|
init_errors();
|
|
7117
7299
|
PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
7118
|
-
secretStoreSchema =
|
|
7300
|
+
secretStoreSchema = z14.record(z14.string(), z14.string());
|
|
7301
|
+
ERROR_REASON_MAX = 200;
|
|
7119
7302
|
}
|
|
7120
7303
|
});
|
|
7121
7304
|
|
|
7122
7305
|
// src/run-config.ts
|
|
7123
|
-
import { z as
|
|
7306
|
+
import { z as z15 } from "zod";
|
|
7124
7307
|
function parseRunConfig(raw, onWarn) {
|
|
7125
7308
|
const result = runConfigSchema.safeParse(raw ?? {});
|
|
7126
7309
|
if (result.success) return result.data;
|
|
@@ -7133,38 +7316,38 @@ var mcpStdioServerSchema, mcpHttpServerSchema, mcpSseServerSchema, mcpServerDefS
|
|
|
7133
7316
|
var init_run_config = __esm({
|
|
7134
7317
|
"src/run-config.ts"() {
|
|
7135
7318
|
"use strict";
|
|
7136
|
-
mcpStdioServerSchema =
|
|
7137
|
-
type:
|
|
7138
|
-
command:
|
|
7139
|
-
args:
|
|
7140
|
-
env:
|
|
7319
|
+
mcpStdioServerSchema = z15.object({
|
|
7320
|
+
type: z15.literal("stdio").optional(),
|
|
7321
|
+
command: z15.string().min(1),
|
|
7322
|
+
args: z15.array(z15.string()).optional(),
|
|
7323
|
+
env: z15.record(z15.string(), z15.string()).optional()
|
|
7141
7324
|
});
|
|
7142
|
-
mcpHttpServerSchema =
|
|
7143
|
-
type:
|
|
7144
|
-
url:
|
|
7145
|
-
headers:
|
|
7325
|
+
mcpHttpServerSchema = z15.object({
|
|
7326
|
+
type: z15.literal("http"),
|
|
7327
|
+
url: z15.string().url(),
|
|
7328
|
+
headers: z15.record(z15.string(), z15.string()).optional()
|
|
7146
7329
|
});
|
|
7147
|
-
mcpSseServerSchema =
|
|
7148
|
-
type:
|
|
7149
|
-
url:
|
|
7150
|
-
headers:
|
|
7330
|
+
mcpSseServerSchema = z15.object({
|
|
7331
|
+
type: z15.literal("sse"),
|
|
7332
|
+
url: z15.string().url(),
|
|
7333
|
+
headers: z15.record(z15.string(), z15.string()).optional()
|
|
7151
7334
|
});
|
|
7152
|
-
mcpServerDefSchema =
|
|
7153
|
-
thinkingConfigSchema =
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7335
|
+
mcpServerDefSchema = z15.union([mcpHttpServerSchema, mcpSseServerSchema, mcpStdioServerSchema]);
|
|
7336
|
+
thinkingConfigSchema = z15.discriminatedUnion("type", [
|
|
7337
|
+
z15.object({ type: z15.literal("adaptive") }),
|
|
7338
|
+
z15.object({ type: z15.literal("enabled"), budgetTokens: z15.number().int().positive().optional() }),
|
|
7339
|
+
z15.object({ type: z15.literal("disabled") })
|
|
7157
7340
|
]);
|
|
7158
|
-
effortSchema =
|
|
7159
|
-
runConfigSchema =
|
|
7341
|
+
effortSchema = z15.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
7342
|
+
runConfigSchema = z15.object({
|
|
7160
7343
|
// CT788: the host-access binary replaced the `assistant`/`coding`/`custom` mode
|
|
7161
7344
|
// trio + its custom tool lists — `true` grants the host filesystem/shell, absent
|
|
7162
7345
|
// is the locked surface. Kept in lockstep with `@cabane/shared`'s
|
|
7163
7346
|
// `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
|
|
7164
7347
|
// stripped, so an older companion riding a newer server never rejects the config).
|
|
7165
|
-
hostAccess:
|
|
7166
|
-
mcpServers:
|
|
7167
|
-
model:
|
|
7348
|
+
hostAccess: z15.boolean().optional(),
|
|
7349
|
+
mcpServers: z15.record(z15.string(), mcpServerDefSchema).optional(),
|
|
7350
|
+
model: z15.string().min(1).optional(),
|
|
7168
7351
|
thinking: thinkingConfigSchema.optional(),
|
|
7169
7352
|
effort: effortSchema.optional()
|
|
7170
7353
|
});
|
|
@@ -7173,7 +7356,7 @@ var init_run_config = __esm({
|
|
|
7173
7356
|
});
|
|
7174
7357
|
|
|
7175
7358
|
// src/runner/protocol.ts
|
|
7176
|
-
import { z as
|
|
7359
|
+
import { z as z16 } from "zod";
|
|
7177
7360
|
function required(env, name) {
|
|
7178
7361
|
const value = env[name];
|
|
7179
7362
|
if (!value) throw new Error(`runner: ${name} is not set`);
|
|
@@ -7186,6 +7369,7 @@ function parseRunnerInput(env, stdin) {
|
|
|
7186
7369
|
return {
|
|
7187
7370
|
baseUrl: required(env, RUNNER_ENV.url),
|
|
7188
7371
|
credential: credentials.credential,
|
|
7372
|
+
sandboxCredentials: credentials.sandboxCredentials ?? [],
|
|
7189
7373
|
dispatch,
|
|
7190
7374
|
turnId: required(env, RUNNER_ENV.turnId),
|
|
7191
7375
|
resumed: env[RUNNER_ENV.resumed] === "1",
|
|
@@ -7202,7 +7386,10 @@ function runnerEnv(input) {
|
|
|
7202
7386
|
};
|
|
7203
7387
|
}
|
|
7204
7388
|
function runnerStdin(input) {
|
|
7205
|
-
return `${JSON.stringify({
|
|
7389
|
+
return `${JSON.stringify({
|
|
7390
|
+
credential: input.credential,
|
|
7391
|
+
...input.sandboxCredentials?.length ? { sandboxCredentials: input.sandboxCredentials } : {}
|
|
7392
|
+
})}
|
|
7206
7393
|
`;
|
|
7207
7394
|
}
|
|
7208
7395
|
function encodeLauncherMessage(message) {
|
|
@@ -7238,6 +7425,7 @@ var init_protocol = __esm({
|
|
|
7238
7425
|
"use strict";
|
|
7239
7426
|
init_config();
|
|
7240
7427
|
init_run_config();
|
|
7428
|
+
init_sandbox_policy();
|
|
7241
7429
|
RUNNER_ENV = {
|
|
7242
7430
|
// The wire the runner speaks.
|
|
7243
7431
|
url: "CABANE_URL",
|
|
@@ -7251,64 +7439,75 @@ var init_protocol = __esm({
|
|
|
7251
7439
|
// Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
|
|
7252
7440
|
spec: "CABANE_RUNNER_SPEC"
|
|
7253
7441
|
};
|
|
7254
|
-
runtimeHealthSchema =
|
|
7255
|
-
quotaState:
|
|
7256
|
-
limitedUntil:
|
|
7257
|
-
lastError:
|
|
7442
|
+
runtimeHealthSchema = z16.object({
|
|
7443
|
+
quotaState: z16.enum(["ok", "limited", "auth_expired", "unknown"]),
|
|
7444
|
+
limitedUntil: z16.string().nullable(),
|
|
7445
|
+
lastError: z16.string().nullable()
|
|
7258
7446
|
});
|
|
7259
|
-
runnerSpecSchema =
|
|
7260
|
-
deviceId:
|
|
7447
|
+
runnerSpecSchema = z16.object({
|
|
7448
|
+
deviceId: z16.string().optional(),
|
|
7261
7449
|
// The launcher's boot id: the one its heartbeat carries, so a runner's lease
|
|
7262
7450
|
// matches the device that holds it.
|
|
7263
|
-
bootId:
|
|
7264
|
-
workspaceId:
|
|
7265
|
-
workspaceSlug:
|
|
7266
|
-
agentId:
|
|
7267
|
-
agentUsername:
|
|
7268
|
-
agentName:
|
|
7451
|
+
bootId: z16.string(),
|
|
7452
|
+
workspaceId: z16.string(),
|
|
7453
|
+
workspaceSlug: z16.string(),
|
|
7454
|
+
agentId: z16.string(),
|
|
7455
|
+
agentUsername: z16.string(),
|
|
7456
|
+
agentName: z16.string(),
|
|
7269
7457
|
local: localAgentConfigSchema,
|
|
7270
7458
|
runConfig: runConfigSchema,
|
|
7271
|
-
opencodeServerUrl:
|
|
7272
|
-
claudeCodeAvailable:
|
|
7273
|
-
codexAvailable:
|
|
7274
|
-
idleTimeoutMs:
|
|
7275
|
-
totalTimeoutMs:
|
|
7276
|
-
leaseRenewalMs:
|
|
7277
|
-
preparingRowDelayMs:
|
|
7459
|
+
opencodeServerUrl: z16.string().optional(),
|
|
7460
|
+
claudeCodeAvailable: z16.boolean(),
|
|
7461
|
+
codexAvailable: z16.boolean(),
|
|
7462
|
+
idleTimeoutMs: z16.number().int().positive().optional(),
|
|
7463
|
+
totalTimeoutMs: z16.number().int().positive().optional(),
|
|
7464
|
+
leaseRenewalMs: z16.number().int().positive().optional(),
|
|
7465
|
+
preparingRowDelayMs: z16.number().int().nonnegative().optional(),
|
|
7278
7466
|
// Whether the turn writes a transcript. The directory is the runner's own
|
|
7279
7467
|
// (`transcriptsDir()`), so `CABANE_RUNNER_STATE_DIR` moves it with the outbox.
|
|
7280
|
-
transcripts:
|
|
7468
|
+
transcripts: z16.boolean(),
|
|
7281
7469
|
// Whether the harness starts under the OS sandbox. The launcher sends 'os'
|
|
7282
7470
|
// only when this host can make one.
|
|
7283
|
-
sandbox:
|
|
7284
|
-
|
|
7471
|
+
sandbox: z16.enum(["os", "none"]),
|
|
7472
|
+
// CT1684: the device's sandbox policy, validated by the launcher. Absent
|
|
7473
|
+
// when the sandbox is off.
|
|
7474
|
+
sandboxPolicy: sandboxPolicySchema.optional(),
|
|
7475
|
+
logLevel: z16.string(),
|
|
7285
7476
|
// The launcher's connector-health observations, by runtime, as of the start.
|
|
7286
|
-
connectorHealth:
|
|
7477
|
+
connectorHealth: z16.record(z16.string(), runtimeHealthSchema),
|
|
7287
7478
|
// True when this (conversation, agent) pair was already told its session
|
|
7288
7479
|
// could not be saved: the notice is once per pair, not once per process.
|
|
7289
|
-
sessionWriteNoticeClaimed:
|
|
7480
|
+
sessionWriteNoticeClaimed: z16.boolean(),
|
|
7290
7481
|
// CT1680: the runner's identity, minted with its credential. The start
|
|
7291
7482
|
// PATCH carries it so the lease knows which runner holds the span.
|
|
7292
|
-
runnerId:
|
|
7483
|
+
runnerId: z16.string(),
|
|
7293
7484
|
// CT1681: the launcher keeps the runtime's session files beside the attempt
|
|
7294
7485
|
// and restores them into this runner's home, so the runner names the attempt
|
|
7295
7486
|
// each context was composed for and waits for the launcher to say it's in
|
|
7296
7487
|
// place. Absent: nothing to wait for (a runner on the launcher's own disk).
|
|
7297
|
-
attemptState:
|
|
7488
|
+
attemptState: z16.boolean().optional(),
|
|
7298
7489
|
// CT1681: MCP `${NAME}` secrets come from the launcher, by name, instead of
|
|
7299
7490
|
// this machine's secrets.json. Absent: the runner reads the device's file.
|
|
7300
|
-
secretsFromLauncher:
|
|
7491
|
+
secretsFromLauncher: z16.boolean().optional()
|
|
7301
7492
|
});
|
|
7302
|
-
dispatchSchema =
|
|
7303
|
-
type:
|
|
7304
|
-
agentId:
|
|
7305
|
-
deviceId:
|
|
7306
|
-
conversationId:
|
|
7307
|
-
messageId:
|
|
7308
|
-
episode:
|
|
7493
|
+
dispatchSchema = z16.object({
|
|
7494
|
+
type: z16.literal("device:dispatch_requested"),
|
|
7495
|
+
agentId: z16.string(),
|
|
7496
|
+
deviceId: z16.string(),
|
|
7497
|
+
conversationId: z16.string(),
|
|
7498
|
+
messageId: z16.string(),
|
|
7499
|
+
episode: z16.number().int().optional()
|
|
7309
7500
|
});
|
|
7310
|
-
credentialsSchema =
|
|
7311
|
-
credential:
|
|
7501
|
+
credentialsSchema = z16.object({
|
|
7502
|
+
credential: z16.string().startsWith("cabrn_"),
|
|
7503
|
+
sandboxCredentials: z16.array(
|
|
7504
|
+
z16.object({
|
|
7505
|
+
name: z16.string(),
|
|
7506
|
+
value: z16.string(),
|
|
7507
|
+
hosts: z16.array(z16.string()),
|
|
7508
|
+
gitHost: z16.string().optional()
|
|
7509
|
+
})
|
|
7510
|
+
).optional()
|
|
7312
7511
|
});
|
|
7313
7512
|
}
|
|
7314
7513
|
});
|
|
@@ -7360,6 +7559,7 @@ function runnerSpec(opts, launch, duties, runnerId) {
|
|
|
7360
7559
|
sandbox: launch.sandbox ?? "none",
|
|
7361
7560
|
...launch.provider.bindAttempt ? { attemptState: true } : {},
|
|
7362
7561
|
...launch.provider.inheritsEnvironment === false ? { secretsFromLauncher: true } : {},
|
|
7562
|
+
...launch.sandbox === "os" && launch.policy?.ok ? { sandboxPolicy: launch.policy.policy } : {},
|
|
7363
7563
|
logLevel: opts.log.level,
|
|
7364
7564
|
connectorHealth: healthSnapshot(launch.connectorHealth),
|
|
7365
7565
|
sessionWriteNoticeClaimed: duties.sessionWriteNoticeClaimed()
|
|
@@ -7458,6 +7658,32 @@ async function settleUnstarted(opts, payload, turnId, errorReason, log) {
|
|
|
7458
7658
|
);
|
|
7459
7659
|
}
|
|
7460
7660
|
}
|
|
7661
|
+
function sandboxInputs(launch, log) {
|
|
7662
|
+
const policy = launch.policy ?? { ok: true, policy: {} };
|
|
7663
|
+
if (launch.sandbox !== "os") {
|
|
7664
|
+
if (policy.ok && policyHasCredentials(policy.policy)) {
|
|
7665
|
+
log.debug(
|
|
7666
|
+
"runners: runners.policy names credentials, but turns here run without the OS sandbox; none are given to the harness"
|
|
7667
|
+
);
|
|
7668
|
+
}
|
|
7669
|
+
return { ok: true, credentials: [] };
|
|
7670
|
+
}
|
|
7671
|
+
if (!policy.ok) {
|
|
7672
|
+
log.warn({ error: policy.error }, "runners: the sandbox policy is invalid; refusing the turn");
|
|
7673
|
+
return { ok: false, reason: `setup_failed: ${policy.error}`.slice(0, 200) };
|
|
7674
|
+
}
|
|
7675
|
+
if (!policyHasCredentials(policy.policy)) return { ok: true, credentials: [] };
|
|
7676
|
+
const store = (launch.secretStore ?? defaultSecretStore)(log);
|
|
7677
|
+
const { credentials, missing } = resolvePolicyCredentials(policy.policy, store);
|
|
7678
|
+
if (missing.length) {
|
|
7679
|
+
log.debug(
|
|
7680
|
+
{ missing },
|
|
7681
|
+
"runners: the sandbox policy names secrets this device does not declare"
|
|
7682
|
+
);
|
|
7683
|
+
return { ok: false, reason: missingSecretReason(missing) };
|
|
7684
|
+
}
|
|
7685
|
+
return { ok: true, credentials };
|
|
7686
|
+
}
|
|
7461
7687
|
async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
7462
7688
|
const startedAt = Date.now();
|
|
7463
7689
|
const { turnId } = handleOpts;
|
|
@@ -7481,7 +7707,8 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7481
7707
|
agentId: payload.agentId,
|
|
7482
7708
|
turnId
|
|
7483
7709
|
};
|
|
7484
|
-
const
|
|
7710
|
+
const sandboxed = sandboxInputs(launch, log);
|
|
7711
|
+
const minted = sandboxed.ok ? await mintForRunner(opts, payload, handleOpts, controller.signal, log) : { ok: false, reason: sandboxed.reason, errorReason: sandboxed.reason };
|
|
7485
7712
|
if (!minted.ok) {
|
|
7486
7713
|
controller.signal.removeEventListener("abort", forwardStop);
|
|
7487
7714
|
duties.releaseAbort(turnId, controller);
|
|
@@ -7534,6 +7761,7 @@ async function runTurnInRunner(opts, launch, duties, payload, handleOpts) {
|
|
|
7534
7761
|
const input = {
|
|
7535
7762
|
baseUrl: launch.runnerBaseUrl ?? opts.baseUrl,
|
|
7536
7763
|
credential: minted.credential,
|
|
7764
|
+
sandboxCredentials: sandboxed.ok ? sandboxed.credentials : [],
|
|
7537
7765
|
dispatch: payload,
|
|
7538
7766
|
turnId,
|
|
7539
7767
|
resumed: handleOpts.resumed,
|
|
@@ -7705,6 +7933,7 @@ var PINO_LEVELS;
|
|
|
7705
7933
|
var init_launch = __esm({
|
|
7706
7934
|
"src/runner/launch.ts"() {
|
|
7707
7935
|
"use strict";
|
|
7936
|
+
init_sandbox_policy();
|
|
7708
7937
|
init_opencode_lock();
|
|
7709
7938
|
init_api_error_shape();
|
|
7710
7939
|
init_errors();
|
|
@@ -7725,7 +7954,7 @@ var init_launch = __esm({
|
|
|
7725
7954
|
|
|
7726
7955
|
// src/turn-control-tools.ts
|
|
7727
7956
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
7728
|
-
import { z as
|
|
7957
|
+
import { z as z17 } from "zod";
|
|
7729
7958
|
function createSkipState() {
|
|
7730
7959
|
return { skipped: false, reason: null };
|
|
7731
7960
|
}
|
|
@@ -7776,7 +8005,7 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
7776
8005
|
SKIP_TURN_TOOL,
|
|
7777
8006
|
TURN_CONTROL_TOOL_DESCRIPTIONS.skip_turn,
|
|
7778
8007
|
{
|
|
7779
|
-
reason:
|
|
8008
|
+
reason: z17.string().min(1).max(MAX_SKIP_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.skip_turn.reason)
|
|
7780
8009
|
},
|
|
7781
8010
|
async (args) => {
|
|
7782
8011
|
skipState.skipped = true;
|
|
@@ -7792,42 +8021,42 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
7792
8021
|
ASK_TOOL,
|
|
7793
8022
|
TURN_CONTROL_TOOL_DESCRIPTIONS.ask,
|
|
7794
8023
|
{
|
|
7795
|
-
targetUserId:
|
|
7796
|
-
question:
|
|
7797
|
-
headline:
|
|
7798
|
-
options:
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
label:
|
|
7803
|
-
move:
|
|
7804
|
-
task:
|
|
7805
|
-
status:
|
|
8024
|
+
targetUserId: z17.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.targetUserId),
|
|
8025
|
+
question: z17.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.question),
|
|
8026
|
+
headline: z17.string().min(1).max(MAX_ASK_HEADLINE_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.headline),
|
|
8027
|
+
options: z17.array(
|
|
8028
|
+
z17.union([
|
|
8029
|
+
z17.string().min(1).max(MAX_ASK_OPTION_BYTES),
|
|
8030
|
+
z17.object({
|
|
8031
|
+
label: z17.string().min(1).max(MAX_ASK_OPTION_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["options.label"]),
|
|
8032
|
+
move: z17.object({
|
|
8033
|
+
task: z17.string().min(1).max(200).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["options.move.task"]),
|
|
8034
|
+
status: z17.enum(TASK_STAGES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["options.move.status"])
|
|
7806
8035
|
}).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["options.move"])
|
|
7807
8036
|
})
|
|
7808
8037
|
])
|
|
7809
8038
|
).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.options),
|
|
7810
|
-
questions:
|
|
7811
|
-
|
|
7812
|
-
headline:
|
|
7813
|
-
body:
|
|
7814
|
-
options:
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
label:
|
|
7819
|
-
move:
|
|
7820
|
-
task:
|
|
7821
|
-
status:
|
|
8039
|
+
questions: z17.array(
|
|
8040
|
+
z17.object({
|
|
8041
|
+
headline: z17.string().min(1).max(MAX_ASK_HEADLINE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.headline"]),
|
|
8042
|
+
body: z17.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.body"]),
|
|
8043
|
+
options: z17.array(
|
|
8044
|
+
z17.union([
|
|
8045
|
+
z17.string().min(1).max(MAX_ASK_OPTION_BYTES),
|
|
8046
|
+
z17.object({
|
|
8047
|
+
label: z17.string().min(1).max(MAX_ASK_OPTION_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options.label"]),
|
|
8048
|
+
move: z17.object({
|
|
8049
|
+
task: z17.string().min(1).max(200).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options.move.task"]),
|
|
8050
|
+
status: z17.enum(TASK_STAGES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options.move.status"])
|
|
7822
8051
|
}).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options.move"])
|
|
7823
8052
|
})
|
|
7824
8053
|
])
|
|
7825
8054
|
).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options"])
|
|
7826
8055
|
})
|
|
7827
8056
|
).min(1).max(MAX_ASK_ITEMS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.questions),
|
|
7828
|
-
replaces:
|
|
7829
|
-
askId:
|
|
7830
|
-
reason:
|
|
8057
|
+
replaces: z17.object({
|
|
8058
|
+
askId: z17.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.askId"]),
|
|
8059
|
+
reason: z17.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.reason"])
|
|
7831
8060
|
}).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.replaces)
|
|
7832
8061
|
},
|
|
7833
8062
|
async (args) => {
|
|
@@ -7868,9 +8097,9 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
7868
8097
|
WAKE_ME_TOOL,
|
|
7869
8098
|
TURN_CONTROL_TOOL_DESCRIPTIONS.wake_me,
|
|
7870
8099
|
{
|
|
7871
|
-
afterSeconds:
|
|
7872
|
-
at:
|
|
7873
|
-
note:
|
|
8100
|
+
afterSeconds: z17.number().int().positive().optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.afterSeconds),
|
|
8101
|
+
at: z17.string().datetime({ offset: true }).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.at),
|
|
8102
|
+
note: z17.string().min(1).max(MAX_WAKE_NOTE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.note)
|
|
7874
8103
|
},
|
|
7875
8104
|
async (args) => {
|
|
7876
8105
|
const hasAfter = args.afterSeconds !== void 0;
|
|
@@ -7904,8 +8133,8 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
7904
8133
|
WITHDRAW_ASK_TOOL,
|
|
7905
8134
|
TURN_CONTROL_TOOL_DESCRIPTIONS.withdraw_ask,
|
|
7906
8135
|
{
|
|
7907
|
-
askId:
|
|
7908
|
-
reason:
|
|
8136
|
+
askId: z17.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.askId),
|
|
8137
|
+
reason: z17.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.reason)
|
|
7909
8138
|
},
|
|
7910
8139
|
async (args) => performAct(acts, {
|
|
7911
8140
|
kind: "withdraw_ask",
|
|
@@ -8498,16 +8727,6 @@ var init_turn_runtime_integrity = __esm({
|
|
|
8498
8727
|
// src/turn-execution.ts
|
|
8499
8728
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
8500
8729
|
import { existsSync as existsSync9 } from "fs";
|
|
8501
|
-
function missingSecretReason(missing) {
|
|
8502
|
-
const prefix = "missing_secret: ";
|
|
8503
|
-
for (let shown = missing.length; shown >= 0; shown--) {
|
|
8504
|
-
const omitted = missing.length - shown;
|
|
8505
|
-
const parts = [...missing.slice(0, shown), ...omitted > 0 ? [`+${omitted}`] : []];
|
|
8506
|
-
const reason = prefix + parts.join(",");
|
|
8507
|
-
if (reason.length <= ERROR_REASON_MAX) return reason;
|
|
8508
|
-
}
|
|
8509
|
-
return `${prefix}+${missing.length}`;
|
|
8510
|
-
}
|
|
8511
8730
|
function resolveIdleTimeoutMs(override) {
|
|
8512
8731
|
return override ?? DEFAULT_AGENT_IDLE_TIMEOUT_MS;
|
|
8513
8732
|
}
|
|
@@ -8544,7 +8763,7 @@ function fingerprintSessionState(state) {
|
|
|
8544
8763
|
}
|
|
8545
8764
|
return createHash2("sha256").update(opaqueId).digest("hex").slice(0, 16);
|
|
8546
8765
|
}
|
|
8547
|
-
var PREPARING_TOOL_NAME,
|
|
8766
|
+
var PREPARING_TOOL_NAME, RUNTIME_UNAVAILABLE_PREFIX, NO_TURN_TOKEN_REASON, UNEXPECTED_ROLE_REASON, DEFAULT_PREPARING_ROW_DELAY_MS, DEFAULT_AGENT_IDLE_TIMEOUT_MS, DEFAULT_AGENT_TOTAL_TIMEOUT_MS, DEFAULT_LEASE_RENEWAL_MS, TurnConcluded, TurnExecution;
|
|
8548
8767
|
var init_turn_execution = __esm({
|
|
8549
8768
|
"src/turn-execution.ts"() {
|
|
8550
8769
|
"use strict";
|
|
@@ -8568,8 +8787,8 @@ var init_turn_execution = __esm({
|
|
|
8568
8787
|
init_turn_committer();
|
|
8569
8788
|
init_turn_runtime_integrity();
|
|
8570
8789
|
init_dispatcher();
|
|
8790
|
+
init_sandbox_policy();
|
|
8571
8791
|
PREPARING_TOOL_NAME = "preparing";
|
|
8572
|
-
ERROR_REASON_MAX = 200;
|
|
8573
8792
|
RUNTIME_UNAVAILABLE_PREFIX = "**This agent's runtime isn't available on this companion.** The model this agent uses needs a runtime this device isn't running, so I can't run this turn here. Details:";
|
|
8574
8793
|
NO_TURN_TOKEN_REASON = "no_turn_token: the server issued no turn credential for this turn";
|
|
8575
8794
|
UNEXPECTED_ROLE_REASON = `refused the wake trigger (role "system" isn't dispatchable on this companion) \u2014 the companion is likely running outdated code; refresh it, then re-address the agent`;
|
|
@@ -9133,7 +9352,18 @@ var init_turn_execution = __esm({
|
|
|
9133
9352
|
const { payload, workspaceId, turnId, turnLog } = this;
|
|
9134
9353
|
await guardBundledBinary(this.integrityContext(), (r, e) => this.concluded(r, e), this.outcome);
|
|
9135
9354
|
const onWarn = (msg, meta) => turnLog.debug(meta ?? {}, msg);
|
|
9136
|
-
|
|
9355
|
+
let sandbox = null;
|
|
9356
|
+
try {
|
|
9357
|
+
sandbox = this.opts.harnessSandbox ? await this.opts.harnessSandbox({
|
|
9358
|
+
cwd: this.effectiveCwd,
|
|
9359
|
+
runtime: this.turnContext.runtime,
|
|
9360
|
+
log: turnLog
|
|
9361
|
+
}) : null;
|
|
9362
|
+
} catch (err) {
|
|
9363
|
+
if (!(err instanceof HarnessSandboxRefused)) throw err;
|
|
9364
|
+
const reason = `setup_failed: ${err.message}`.slice(0, 200);
|
|
9365
|
+
throw this.concluded(reason, reason);
|
|
9366
|
+
}
|
|
9137
9367
|
const adapters = [];
|
|
9138
9368
|
if (this.opts.claudeCodeAvailable?.() ?? true) {
|
|
9139
9369
|
this.containment = createTurnContainment(this.turnId, {
|
|
@@ -10009,10 +10239,20 @@ var init_git_credential = __esm({
|
|
|
10009
10239
|
|
|
10010
10240
|
// src/runner/harness-sandbox.ts
|
|
10011
10241
|
import { lookup } from "dns/promises";
|
|
10012
|
-
import {
|
|
10242
|
+
import {
|
|
10243
|
+
chmodSync as chmodSync6,
|
|
10244
|
+
mkdirSync as mkdirSync11,
|
|
10245
|
+
mkdtempSync,
|
|
10246
|
+
readFileSync as readFileSync10,
|
|
10247
|
+
realpathSync as realpathSync3,
|
|
10248
|
+
rmSync as rmSync10,
|
|
10249
|
+
writeFileSync as writeFileSync7
|
|
10250
|
+
} from "fs";
|
|
10251
|
+
import { createRequire as createRequire2 } from "module";
|
|
10013
10252
|
import { isIP } from "net";
|
|
10014
10253
|
import { homedir as homedir3, networkInterfaces, tmpdir as tmpdir3, userInfo as userInfo2 } from "os";
|
|
10015
|
-
import { join as join18 } from "path";
|
|
10254
|
+
import { dirname as dirname8, join as join18 } from "path";
|
|
10255
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10016
10256
|
import { SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
10017
10257
|
function accountHome() {
|
|
10018
10258
|
try {
|
|
@@ -10042,32 +10282,117 @@ async function resolvedAddresses(baseUrl) {
|
|
|
10042
10282
|
return [];
|
|
10043
10283
|
}
|
|
10044
10284
|
}
|
|
10285
|
+
function companionPackageRoot(modulePath) {
|
|
10286
|
+
for (let dir2 = dirname8(modulePath); ; dir2 = dirname8(dir2)) {
|
|
10287
|
+
try {
|
|
10288
|
+
const manifest = JSON.parse(readFileSync10(join18(dir2, "package.json"), "utf8"));
|
|
10289
|
+
if (manifest.name === "@cabane/companion") return dir2;
|
|
10290
|
+
} catch {
|
|
10291
|
+
}
|
|
10292
|
+
if (dirname8(dir2) === dir2) return null;
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
function harnessRuntimePaths() {
|
|
10296
|
+
const paths = [dirname8(dirname8(process.execPath))];
|
|
10297
|
+
const root = companionPackageRoot(fileURLToPath4(import.meta.url));
|
|
10298
|
+
if (root) paths.push(root);
|
|
10299
|
+
try {
|
|
10300
|
+
const require2 = createRequire2(import.meta.url);
|
|
10301
|
+
paths.push(
|
|
10302
|
+
dirname8(realpathSync3(require2.resolve("@anthropic-ai/sandbox-runtime/package.json")))
|
|
10303
|
+
);
|
|
10304
|
+
} catch {
|
|
10305
|
+
}
|
|
10306
|
+
for (const runtime of ["claude-code", "codex"]) {
|
|
10307
|
+
const bundled = resolveBundledBinary(runtime).path;
|
|
10308
|
+
if (bundled) {
|
|
10309
|
+
try {
|
|
10310
|
+
paths.push(dirname8(realpathSync3(bundled)));
|
|
10311
|
+
} catch {
|
|
10312
|
+
}
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
return [...new Set(paths)];
|
|
10316
|
+
}
|
|
10045
10317
|
function ownAddresses() {
|
|
10046
10318
|
return Object.values(networkInterfaces()).flat().flatMap((i) => i ? [i.address] : []);
|
|
10047
10319
|
}
|
|
10320
|
+
function expandHome(path, home) {
|
|
10321
|
+
if (path === "~") return home;
|
|
10322
|
+
return path.startsWith("~/") ? join18(home, path.slice(2)) : path;
|
|
10323
|
+
}
|
|
10324
|
+
function stripPort(pattern) {
|
|
10325
|
+
return pattern.startsWith("[") ? pattern.replace(/^\[([^\]]+)\](:\d+)?$/, "$1") : pattern.replace(/:\d+$/, "");
|
|
10326
|
+
}
|
|
10327
|
+
function hostMatches(host, pattern) {
|
|
10328
|
+
return pattern.startsWith("*.") ? host.endsWith(pattern.slice(1)) : host === pattern;
|
|
10329
|
+
}
|
|
10330
|
+
function patternsOverlap(a, b) {
|
|
10331
|
+
if (a === b) return true;
|
|
10332
|
+
const bare = (p) => p.startsWith("*.") ? p.slice(1) : p;
|
|
10333
|
+
if (a.startsWith("*.") && b.startsWith("*.")) {
|
|
10334
|
+
return bare(a).endsWith(bare(b)) || bare(b).endsWith(bare(a));
|
|
10335
|
+
}
|
|
10336
|
+
return hostMatches(a, b) || hostMatches(b, a);
|
|
10337
|
+
}
|
|
10048
10338
|
function harnessSandboxConfig(input) {
|
|
10339
|
+
const { policy } = input;
|
|
10049
10340
|
const claudeDir = input.claudeConfigDir ?? join18(input.home, ".claude");
|
|
10050
10341
|
const codexDir = input.codexHome ?? join18(input.home, ".codex");
|
|
10342
|
+
const homes = [.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])];
|
|
10343
|
+
const expand = (paths) => (paths ?? []).map((p) => expandHome(p, input.home));
|
|
10344
|
+
const cabane = cabaneHostEntries(input.baseUrl, input.resolved, input.ownAddresses);
|
|
10345
|
+
const models = modelHostsFor(input.runtime);
|
|
10346
|
+
const reachable = [
|
|
10347
|
+
...policy.hosts?.defaults === false ? [] : DEFAULT_ALLOWED_HOSTS,
|
|
10348
|
+
...policy.hosts?.allow ?? []
|
|
10349
|
+
];
|
|
10350
|
+
const allowedDomains = [.../* @__PURE__ */ new Set([...cabane, ...models, ...reachable])];
|
|
10351
|
+
const envGlobs = input.cwd ? [join18(input.cwd, "**/.env"), join18(input.cwd, "**/.env.*")] : [];
|
|
10352
|
+
const secrets = [
|
|
10353
|
+
input.cabaneDir,
|
|
10354
|
+
...homes.flatMap((home) => SECRET_PATHS_UNDER_HOME.map((p) => join18(home, p))),
|
|
10355
|
+
...envGlobs,
|
|
10356
|
+
...expand(policy.filesystem?.denyRead)
|
|
10357
|
+
];
|
|
10358
|
+
const strict = policy.filesystem?.mode === "strict";
|
|
10359
|
+
const denyRead = strict ? [...homes, ...secrets] : secrets;
|
|
10360
|
+
const allowRead = strict ? [
|
|
10361
|
+
...input.cwd ? [input.cwd] : [],
|
|
10362
|
+
claudeDir,
|
|
10363
|
+
join18(input.home, ".claude.json"),
|
|
10364
|
+
codexDir,
|
|
10365
|
+
...input.runtimePaths ?? [],
|
|
10366
|
+
...expand(policy.filesystem?.allowRead)
|
|
10367
|
+
] : expand(policy.filesystem?.allowRead);
|
|
10368
|
+
const masked = input.credentials.map((c) => ({
|
|
10369
|
+
name: c.name,
|
|
10370
|
+
mode: "mask",
|
|
10371
|
+
injectHosts: [...new Set(c.hosts.map(stripPort))]
|
|
10372
|
+
}));
|
|
10373
|
+
const injectAt = masked.flatMap((c) => c.injectHosts);
|
|
10051
10374
|
return {
|
|
10052
10375
|
network: {
|
|
10053
|
-
allowedDomains
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10376
|
+
allowedDomains,
|
|
10377
|
+
deniedDomains: policy.hosts?.deny ?? [],
|
|
10378
|
+
allowLocalBinding: true,
|
|
10379
|
+
// A masked credential is swapped in by a proxy that reads the request,
|
|
10380
|
+
// so TLS is terminated — but only for the hosts a credential goes to.
|
|
10381
|
+
// Cabane, the model provider and every other host keep their own
|
|
10382
|
+
// end-to-end TLS through an opaque tunnel.
|
|
10383
|
+
...masked.length ? {
|
|
10384
|
+
tlsTerminate: {
|
|
10385
|
+
excludeDomains: [
|
|
10386
|
+
...new Set(
|
|
10387
|
+
allowedDomains.map(stripPort).filter((host) => !injectAt.some((p) => patternsOverlap(host, p)))
|
|
10388
|
+
)
|
|
10389
|
+
]
|
|
10390
|
+
}
|
|
10391
|
+
} : {}
|
|
10059
10392
|
},
|
|
10060
10393
|
filesystem: {
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
denyRead: [
|
|
10064
|
-
input.cabaneDir,
|
|
10065
|
-
...[.../* @__PURE__ */ new Set([input.home, input.accountHome ?? input.home])].flatMap((home) => [
|
|
10066
|
-
join18(home, ".ssh"),
|
|
10067
|
-
join18(home, ".aws"),
|
|
10068
|
-
join18(home, ".gnupg")
|
|
10069
|
-
])
|
|
10070
|
-
],
|
|
10394
|
+
denyRead,
|
|
10395
|
+
...allowRead.length ? { allowRead } : {},
|
|
10071
10396
|
// The checkout, scratch, and the harnesses' own session state — a turn
|
|
10072
10397
|
// resumes from `~/.claude` and `~/.codex`, and without write there every
|
|
10073
10398
|
// resume starts from nothing.
|
|
@@ -10076,12 +10401,27 @@ function harnessSandboxConfig(input) {
|
|
|
10076
10401
|
input.tmp,
|
|
10077
10402
|
claudeDir,
|
|
10078
10403
|
join18(input.home, ".claude.json"),
|
|
10079
|
-
codexDir
|
|
10404
|
+
codexDir,
|
|
10405
|
+
...expand(policy.filesystem?.allowWrite)
|
|
10080
10406
|
],
|
|
10081
10407
|
denyWrite: []
|
|
10082
|
-
}
|
|
10408
|
+
},
|
|
10409
|
+
...masked.length ? { credentials: { envVars: masked } } : {}
|
|
10083
10410
|
};
|
|
10084
10411
|
}
|
|
10412
|
+
function gitCredentialExports(credentials) {
|
|
10413
|
+
const git = credentials.filter((c) => c.gitHost);
|
|
10414
|
+
if (!git.length) return "";
|
|
10415
|
+
const lines2 = ["n=${GIT_CONFIG_COUNT:-0}"];
|
|
10416
|
+
for (const c of git) {
|
|
10417
|
+
lines2.push(
|
|
10418
|
+
`export "GIT_CONFIG_KEY_$n=http.https://${c.gitHost}/.extraHeader" "GIT_CONFIG_VALUE_$n=Authorization: Basic $${c.name}"`,
|
|
10419
|
+
"n=$((n+1))"
|
|
10420
|
+
);
|
|
10421
|
+
}
|
|
10422
|
+
lines2.push("export GIT_CONFIG_COUNT=$n");
|
|
10423
|
+
return lines2.join("; ") + "; ";
|
|
10424
|
+
}
|
|
10085
10425
|
function osSandboxUsable(log) {
|
|
10086
10426
|
if (!SandboxManager.isSupportedPlatform()) {
|
|
10087
10427
|
log.warn(
|
|
@@ -10102,7 +10442,9 @@ function osSandboxUsable(log) {
|
|
|
10102
10442
|
}
|
|
10103
10443
|
async function prepareHarnessSandbox(input) {
|
|
10104
10444
|
let wrapped;
|
|
10445
|
+
const held = input.credentials.map((c) => c.name);
|
|
10105
10446
|
try {
|
|
10447
|
+
for (const c of input.credentials) process.env[c.name] = c.value;
|
|
10106
10448
|
const config = harnessSandboxConfig({
|
|
10107
10449
|
cwd: input.cwd,
|
|
10108
10450
|
baseUrl: input.baseUrl,
|
|
@@ -10113,28 +10455,50 @@ async function prepareHarnessSandbox(input) {
|
|
|
10113
10455
|
tmp: tmpdir3(),
|
|
10114
10456
|
cabaneDir: cabaneDir(),
|
|
10115
10457
|
claudeConfigDir: process.env.CLAUDE_CONFIG_DIR,
|
|
10116
|
-
codexHome: process.env.CODEX_HOME
|
|
10458
|
+
codexHome: process.env.CODEX_HOME,
|
|
10459
|
+
runtime: input.runtime,
|
|
10460
|
+
policy: input.policy,
|
|
10461
|
+
credentials: input.credentials,
|
|
10462
|
+
runtimePaths: harnessRuntimePaths()
|
|
10117
10463
|
});
|
|
10118
10464
|
await SandboxManager.initialize(config);
|
|
10465
|
+
if (input.policy.filesystem?.mode === "strict") {
|
|
10466
|
+
input.log.info(
|
|
10467
|
+
{ readable: config.filesystem.allowRead },
|
|
10468
|
+
"runners: strict sandbox \u2014 the home is unreadable except these paths; a path missing inside a turn and not listed here is hidden by the policy (add it to runners.policy.filesystem.allowRead)"
|
|
10469
|
+
);
|
|
10470
|
+
}
|
|
10119
10471
|
input.log.debug(
|
|
10120
10472
|
{
|
|
10121
10473
|
allowWrite: config.filesystem.allowWrite,
|
|
10122
10474
|
denyRead: config.filesystem.denyRead,
|
|
10123
|
-
|
|
10475
|
+
allowRead: config.filesystem.allowRead,
|
|
10476
|
+
hosts: config.network.allowedDomains,
|
|
10477
|
+
masked: held
|
|
10124
10478
|
},
|
|
10125
10479
|
"runners: harness sandbox ready"
|
|
10126
10480
|
);
|
|
10127
10481
|
wrapped = await SandboxManager.wrapWithSandbox(
|
|
10128
|
-
|
|
10482
|
+
"export NO_PROXY= no_proxy=; " + gitCredentialExports(input.credentials) + 'eval "exec $CABANE_SANDBOXED_ARGV"'
|
|
10129
10483
|
);
|
|
10130
10484
|
} catch (err) {
|
|
10485
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10486
|
+
await SandboxManager.reset().catch(() => {
|
|
10487
|
+
});
|
|
10488
|
+
if (held.length || Object.keys(input.policy).length) {
|
|
10489
|
+
input.log.warn(
|
|
10490
|
+
{ err: message, masked: held },
|
|
10491
|
+
"runners: the OS sandbox could not start under this device's runners.policy; refusing the turn rather than running it unsandboxed"
|
|
10492
|
+
);
|
|
10493
|
+
throw new HarnessSandboxRefused(`the OS sandbox could not start: ${message}`);
|
|
10494
|
+
}
|
|
10131
10495
|
input.log.warn(
|
|
10132
|
-
{ err:
|
|
10496
|
+
{ err: message },
|
|
10133
10497
|
"runners: the OS sandbox could not start; running the turn unsandboxed"
|
|
10134
10498
|
);
|
|
10135
|
-
await SandboxManager.reset().catch(() => {
|
|
10136
|
-
});
|
|
10137
10499
|
return null;
|
|
10500
|
+
} finally {
|
|
10501
|
+
for (const name of held) delete process.env[name];
|
|
10138
10502
|
}
|
|
10139
10503
|
mkdirSync11(join18(cabaneDir(), "sandbox"), { recursive: true });
|
|
10140
10504
|
const dir2 = mkdtempSync(join18(cabaneDir(), "sandbox", "turn-"));
|
|
@@ -10167,23 +10531,28 @@ exec ${shellQuote(script)} ${shellQuote(real)} "$@"
|
|
|
10167
10531
|
}
|
|
10168
10532
|
};
|
|
10169
10533
|
}
|
|
10170
|
-
var
|
|
10534
|
+
var SECRET_PATHS_UNDER_HOME;
|
|
10171
10535
|
var init_harness_sandbox = __esm({
|
|
10172
10536
|
"src/runner/harness-sandbox.ts"() {
|
|
10173
10537
|
"use strict";
|
|
10538
|
+
init_bundled_binaries();
|
|
10174
10539
|
init_config();
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
"
|
|
10178
|
-
"
|
|
10179
|
-
"
|
|
10180
|
-
"
|
|
10181
|
-
"
|
|
10182
|
-
"
|
|
10183
|
-
"
|
|
10184
|
-
"
|
|
10185
|
-
"
|
|
10186
|
-
"
|
|
10540
|
+
init_sandbox_policy();
|
|
10541
|
+
SECRET_PATHS_UNDER_HOME = [
|
|
10542
|
+
".ssh",
|
|
10543
|
+
".aws",
|
|
10544
|
+
".gnupg",
|
|
10545
|
+
".config/gh",
|
|
10546
|
+
".git-credentials",
|
|
10547
|
+
".netrc",
|
|
10548
|
+
".npmrc",
|
|
10549
|
+
".pypirc",
|
|
10550
|
+
".docker/config.json",
|
|
10551
|
+
".kube",
|
|
10552
|
+
".config/gcloud",
|
|
10553
|
+
".azure",
|
|
10554
|
+
".config/op",
|
|
10555
|
+
".password-store"
|
|
10187
10556
|
];
|
|
10188
10557
|
}
|
|
10189
10558
|
});
|
|
@@ -10193,7 +10562,7 @@ import {
|
|
|
10193
10562
|
existsSync as existsSync14,
|
|
10194
10563
|
mkdirSync as mkdirSync12,
|
|
10195
10564
|
readdirSync as readdirSync5,
|
|
10196
|
-
readFileSync as
|
|
10565
|
+
readFileSync as readFileSync11,
|
|
10197
10566
|
renameSync as renameSync6,
|
|
10198
10567
|
rmSync as rmSync11,
|
|
10199
10568
|
writeFileSync as writeFileSync8
|
|
@@ -10263,7 +10632,7 @@ var init_outbox = __esm({
|
|
|
10263
10632
|
if (!name.endsWith(".json")) continue;
|
|
10264
10633
|
const full = join19(dir2, name);
|
|
10265
10634
|
try {
|
|
10266
|
-
const parsed = JSON.parse(
|
|
10635
|
+
const parsed = JSON.parse(readFileSync11(full, "utf8"));
|
|
10267
10636
|
if (parsed && typeof parsed.turnId === "string" && typeof parsed.seq === "number" && typeof parsed.path === "string") {
|
|
10268
10637
|
entries.push(parsed);
|
|
10269
10638
|
} else {
|
|
@@ -10367,12 +10736,12 @@ var init_outbox = __esm({
|
|
|
10367
10736
|
});
|
|
10368
10737
|
|
|
10369
10738
|
// src/version.ts
|
|
10370
|
-
import { createRequire as
|
|
10739
|
+
import { createRequire as createRequire3 } from "module";
|
|
10371
10740
|
var pkg, COMPANION_VERSION;
|
|
10372
10741
|
var init_version2 = __esm({
|
|
10373
10742
|
"src/version.ts"() {
|
|
10374
10743
|
"use strict";
|
|
10375
|
-
pkg =
|
|
10744
|
+
pkg = createRequire3(import.meta.url)("../package.json");
|
|
10376
10745
|
COMPANION_VERSION = pkg.version;
|
|
10377
10746
|
}
|
|
10378
10747
|
});
|
|
@@ -10609,10 +10978,13 @@ function runnerDispatcherOptions(input, log, emit, extras = {}) {
|
|
|
10609
10978
|
}
|
|
10610
10979
|
},
|
|
10611
10980
|
...spec.sandbox === "os" ? {
|
|
10612
|
-
harnessSandbox: async ({ cwd, log: turnLog }) => {
|
|
10981
|
+
harnessSandbox: async ({ cwd, runtime, log: turnLog }) => {
|
|
10613
10982
|
const sandbox = await prepareHarnessSandbox({
|
|
10614
10983
|
cwd,
|
|
10615
10984
|
baseUrl: input.baseUrl,
|
|
10985
|
+
runtime,
|
|
10986
|
+
policy: spec.sandboxPolicy ?? {},
|
|
10987
|
+
credentials: input.sandboxCredentials ?? [],
|
|
10616
10988
|
log: turnLog
|
|
10617
10989
|
});
|
|
10618
10990
|
if (sandbox) onSandbox?.(sandbox);
|
|
@@ -11886,7 +12258,7 @@ async function pair(opts = {}) {
|
|
|
11886
12258
|
}
|
|
11887
12259
|
|
|
11888
12260
|
// src/cli.ts
|
|
11889
|
-
import { readFileSync as
|
|
12261
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
11890
12262
|
|
|
11891
12263
|
// src/commands/logs.ts
|
|
11892
12264
|
import { once } from "events";
|
|
@@ -14287,6 +14659,7 @@ var DeviceLocks = class {
|
|
|
14287
14659
|
|
|
14288
14660
|
// src/supervisor.ts
|
|
14289
14661
|
init_harness_sandbox();
|
|
14662
|
+
init_sandbox_policy();
|
|
14290
14663
|
init_launch();
|
|
14291
14664
|
|
|
14292
14665
|
// src/opencode-models.ts
|
|
@@ -14629,6 +15002,13 @@ var CompanionSupervisor = class {
|
|
|
14629
15002
|
}
|
|
14630
15003
|
this.latchBootBundledState();
|
|
14631
15004
|
}
|
|
15005
|
+
if (this.runnerSandbox() === "os") {
|
|
15006
|
+
const policy = this.sandboxPolicy();
|
|
15007
|
+
const posture = postureLines(policy);
|
|
15008
|
+
const summary = posture.join("; ");
|
|
15009
|
+
if (policy.ok) this.log.info({ posture }, `runners: OS sandbox policy \u2014 ${summary}`);
|
|
15010
|
+
else this.log.error({ posture }, `runners: ${summary}`);
|
|
15011
|
+
}
|
|
14632
15012
|
const startupHarnessRefresh = this.refreshHarnessStatuses();
|
|
14633
15013
|
this.trackHeartbeat(startupHarnessRefresh);
|
|
14634
15014
|
if (!this.config.deviceToken) {
|
|
@@ -15070,6 +15450,13 @@ var CompanionSupervisor = class {
|
|
|
15070
15450
|
// CT1679: `runners.sandbox`, checked against this host once. A host that
|
|
15071
15451
|
// can't make the sandbox says so once and runs its turns unwrapped.
|
|
15072
15452
|
sandboxUsable;
|
|
15453
|
+
// CT1684: `runners.policy`, parsed once. An invalid one is kept as the
|
|
15454
|
+
// error, which every sandboxed turn is refused with.
|
|
15455
|
+
parsedSandboxPolicy;
|
|
15456
|
+
sandboxPolicy() {
|
|
15457
|
+
this.parsedSandboxPolicy ??= parseSandboxPolicy(this.config.runners?.policy);
|
|
15458
|
+
return this.parsedSandboxPolicy;
|
|
15459
|
+
}
|
|
15073
15460
|
runnerSandbox() {
|
|
15074
15461
|
if (this.config.runners?.sandbox !== "os" || this.dockerRunners()) return "none";
|
|
15075
15462
|
this.sandboxUsable ??= osSandboxUsable(this.log);
|
|
@@ -15189,7 +15576,8 @@ var CompanionSupervisor = class {
|
|
|
15189
15576
|
reportRunnerGone: (report) => this.reportRunnerGone(report),
|
|
15190
15577
|
connectorHealth: this.connectorHealth,
|
|
15191
15578
|
locks: this.runnerLocks,
|
|
15192
|
-
sandbox: this.runnerSandbox()
|
|
15579
|
+
sandbox: this.runnerSandbox(),
|
|
15580
|
+
policy: this.sandboxPolicy()
|
|
15193
15581
|
}
|
|
15194
15582
|
} : {}
|
|
15195
15583
|
});
|
|
@@ -15890,7 +16278,7 @@ function handleUncaught(log, err, origin) {
|
|
|
15890
16278
|
|
|
15891
16279
|
// src/crash-marker.ts
|
|
15892
16280
|
init_config();
|
|
15893
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as
|
|
16281
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync12, rmSync as rmSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
15894
16282
|
import { join as join20 } from "path";
|
|
15895
16283
|
function crashMarkerPath() {
|
|
15896
16284
|
return join20(cabaneDir(), "last-error.json");
|
|
@@ -16083,11 +16471,11 @@ import { closeSync as closeSync3, mkdirSync as mkdirSync14, openSync as openSync
|
|
|
16083
16471
|
|
|
16084
16472
|
// src/cli-entry.ts
|
|
16085
16473
|
import { existsSync as existsSync16 } from "fs";
|
|
16086
|
-
import { fileURLToPath as
|
|
16474
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
16087
16475
|
var RELATIVE_CANDIDATES = ["./cli.js", "../dist/cli.js", "../cli.js"];
|
|
16088
16476
|
function companionCliEntry(deps = {}) {
|
|
16089
16477
|
const exists = deps.exists ?? existsSync16;
|
|
16090
|
-
const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) =>
|
|
16478
|
+
const candidates = deps.candidates ?? RELATIVE_CANDIDATES.map((rel) => fileURLToPath5(new URL(rel, import.meta.url)));
|
|
16091
16479
|
for (const candidate of candidates) {
|
|
16092
16480
|
if (exists(candidate)) return candidate;
|
|
16093
16481
|
}
|
|
@@ -16481,6 +16869,7 @@ async function runAttached(runtime) {
|
|
|
16481
16869
|
// src/commands/status.ts
|
|
16482
16870
|
init_config();
|
|
16483
16871
|
import { readdirSync as readdirSync6 } from "fs";
|
|
16872
|
+
init_sandbox_policy();
|
|
16484
16873
|
init_secrets();
|
|
16485
16874
|
init_transcript_writer();
|
|
16486
16875
|
async function status() {
|
|
@@ -16531,6 +16920,13 @@ async function status() {
|
|
|
16531
16920
|
process.stdout.write(`prepare hook: ${cfg.prepareHook.command} (device-level, all agents)
|
|
16532
16921
|
`);
|
|
16533
16922
|
}
|
|
16923
|
+
if (runnersEnabled(cfg) && cfg.runners?.sandbox === "os") {
|
|
16924
|
+
process.stdout.write("sandbox: os\n");
|
|
16925
|
+
for (const line of postureLines(parseSandboxPolicy(cfg.runners.policy))) {
|
|
16926
|
+
process.stdout.write(` ${line}
|
|
16927
|
+
`);
|
|
16928
|
+
}
|
|
16929
|
+
}
|
|
16534
16930
|
const overrides2 = Object.keys(cfg.agents ?? {});
|
|
16535
16931
|
if (overrides2.length > 0) {
|
|
16536
16932
|
process.stdout.write(`local overrides for: ${overrides2.join(", ")}
|
|
@@ -16623,7 +17019,7 @@ function isAlive(kill, pid) {
|
|
|
16623
17019
|
init_errors();
|
|
16624
17020
|
init_src();
|
|
16625
17021
|
init_transcript_writer();
|
|
16626
|
-
import { existsSync as existsSync17, readFileSync as
|
|
17022
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
16627
17023
|
import { isAbsolute, join as join21 } from "path";
|
|
16628
17024
|
async function transcript(opts = {}) {
|
|
16629
17025
|
const dir2 = transcriptsDir();
|
|
@@ -16724,7 +17120,7 @@ function isComplete(content) {
|
|
|
16724
17120
|
async function followTranscripts(dir2) {
|
|
16725
17121
|
const follower = new TranscriptFollower({
|
|
16726
17122
|
listFiles: () => listFiles(dir2),
|
|
16727
|
-
read: (f) =>
|
|
17123
|
+
read: (f) => readFileSync13(join21(dir2, f), "utf8"),
|
|
16728
17124
|
write: (s) => process.stdout.write(s),
|
|
16729
17125
|
// CSI: cursor up `n` lines, then erase from cursor to end of screen.
|
|
16730
17126
|
clearLines: (n) => process.stdout.write(`\x1B[${n}A\x1B[0J`),
|
|
@@ -16785,7 +17181,7 @@ function peek(path) {
|
|
|
16785
17181
|
let meta;
|
|
16786
17182
|
let outcome;
|
|
16787
17183
|
try {
|
|
16788
|
-
for (const line of
|
|
17184
|
+
for (const line of readFileSync13(path, "utf8").split("\n")) {
|
|
16789
17185
|
if (!line.trim()) continue;
|
|
16790
17186
|
const o = safeParse(line);
|
|
16791
17187
|
const t = str2(rec(o)?.type);
|
|
@@ -16818,7 +17214,7 @@ function resolveTarget(dir2, target) {
|
|
|
16818
17214
|
function renderFile(path) {
|
|
16819
17215
|
let content;
|
|
16820
17216
|
try {
|
|
16821
|
-
content =
|
|
17217
|
+
content = readFileSync13(path, "utf8");
|
|
16822
17218
|
} catch (err) {
|
|
16823
17219
|
throw new CompanionError(
|
|
16824
17220
|
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -16980,7 +17376,7 @@ program.command("pair").description(
|
|
|
16980
17376
|
});
|
|
16981
17377
|
});
|
|
16982
17378
|
program.command("write-paired-config", { hidden: true }).description("persist an already-completed device enrollment payload from stdin.").action(() => {
|
|
16983
|
-
writeCompletedPairing(
|
|
17379
|
+
writeCompletedPairing(readFileSync14(0, "utf8"));
|
|
16984
17380
|
});
|
|
16985
17381
|
program.command("start").description(
|
|
16986
17382
|
"pair this device if needed, connect a coding agent on it, and run in the background."
|