@aspectlylabs/mikka 0.0.155 → 0.0.157
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/cli.js +277 -261
- package/client.js +2 -2
- package/http-link.js +41 -4
- package/login.js +6 -2
- package/main.js +19 -10
- package/mikka-release-manifest.json +18 -18
- package/mikka.js +277 -261
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/paths.js +2 -2
- package/sbom.cdx.json +7 -7
package/mikka.js
CHANGED
|
@@ -4481,7 +4481,7 @@ import { join as join4 } from "node:path";
|
|
|
4481
4481
|
// package.json
|
|
4482
4482
|
var package_default = {
|
|
4483
4483
|
name: "@mikka/daemon",
|
|
4484
|
-
version: "0.0.
|
|
4484
|
+
version: "0.0.157",
|
|
4485
4485
|
description: "Local Mikka daemon: runs agents through the user's own CLIs",
|
|
4486
4486
|
type: "module",
|
|
4487
4487
|
private: true,
|
|
@@ -4541,7 +4541,7 @@ var package_default = {
|
|
|
4541
4541
|
engines: {
|
|
4542
4542
|
node: ">=24"
|
|
4543
4543
|
},
|
|
4544
|
-
gitHead: "
|
|
4544
|
+
gitHead: "6b1b0ad1cf20baee870da2c08c5d57c49a02daba"
|
|
4545
4545
|
};
|
|
4546
4546
|
|
|
4547
4547
|
// src/paths.ts
|
|
@@ -4816,7 +4816,7 @@ function classifySegment(segment, depth) {
|
|
|
4816
4816
|
if (entry && verb && entry[1].includes(verb)) return "install_dependency";
|
|
4817
4817
|
return null;
|
|
4818
4818
|
}
|
|
4819
|
-
function claudePermissionRules(policy) {
|
|
4819
|
+
function claudePermissionRules(policy, agentDelivers = false) {
|
|
4820
4820
|
const install2 = INSTALL_COMMANDS.flatMap(
|
|
4821
4821
|
([tool2, verbs]) => verbs.map((verb) => `Bash(${tool2} ${verb}:*)`)
|
|
4822
4822
|
);
|
|
@@ -4826,7 +4826,7 @@ function claudePermissionRules(policy) {
|
|
|
4826
4826
|
ask: decision === "ask" ? install2 : [],
|
|
4827
4827
|
deny: [
|
|
4828
4828
|
...!rawCliShellAvailable(policy) ? ["Bash", "PowerShell", "Monitor"] : [],
|
|
4829
|
-
"Bash(git push:*)",
|
|
4829
|
+
...agentDelivers && !policy.readOnly ? [] : ["Bash(git push:*)"],
|
|
4830
4830
|
...policy.readOnly ? READ_ONLY_TOOLS : [],
|
|
4831
4831
|
...decision === "deny" ? install2 : []
|
|
4832
4832
|
]
|
|
@@ -4882,11 +4882,251 @@ function classifyWrite(worktree, target) {
|
|
|
4882
4882
|
return ok2 ? "write_in_worktree" : "write_outside_repo";
|
|
4883
4883
|
}
|
|
4884
4884
|
|
|
4885
|
+
// src/agents/run-sandbox-launcher.ts
|
|
4886
|
+
import { spawn } from "node:child_process";
|
|
4887
|
+
import { isIP } from "node:net";
|
|
4888
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
4889
|
+
import { fileURLToPath } from "node:url";
|
|
4890
|
+
import { NetworkConfigSchema } from "@anthropic-ai/sandbox-runtime";
|
|
4891
|
+
var MAX_PENDING_NETWORK = 128;
|
|
4892
|
+
var NETWORK_DECISION_MS = 5 * 6e4;
|
|
4893
|
+
function canonicalHost(host) {
|
|
4894
|
+
if (!host || /[\s\x00-\x1f\x7f%\\/@?#*]/u.test(host)) return;
|
|
4895
|
+
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
4896
|
+
if (isIP(bare) === 6) return new URL(`http://[${bare}]`).hostname.slice(1, -1);
|
|
4897
|
+
if (bare.includes(":")) return;
|
|
4898
|
+
try {
|
|
4899
|
+
return new URL(`http://${bare}`).hostname.toLowerCase();
|
|
4900
|
+
} catch {
|
|
4901
|
+
return void 0;
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
function matchesRunSandboxDomain(request, domains) {
|
|
4905
|
+
const host = canonicalHost(request.host);
|
|
4906
|
+
if (!host || !Number.isInteger(request.port) || request.port < 1 || request.port > 65535) return false;
|
|
4907
|
+
return domains.some((entry) => {
|
|
4908
|
+
if (entry === "*") return true;
|
|
4909
|
+
const parts = /^(\[[^\]]+\]|[^:]+?)(?::([1-9][0-9]{0,4}))?$/u.exec(entry);
|
|
4910
|
+
if (!parts || parts[2] !== void 0 && (Number(parts[2]) > 65535 || Number(parts[2]) !== request.port)) return false;
|
|
4911
|
+
const wildcard = parts[1].startsWith("*.");
|
|
4912
|
+
const pattern = canonicalHost(wildcard ? parts[1].slice(2) : parts[1]);
|
|
4913
|
+
if (!pattern) return false;
|
|
4914
|
+
if (!wildcard) return host === pattern;
|
|
4915
|
+
return !isIP(host) && pattern.includes(".") && host.endsWith(`.${pattern}`);
|
|
4916
|
+
});
|
|
4917
|
+
}
|
|
4918
|
+
function signalGroup(pid, signal) {
|
|
4919
|
+
if (pid === void 0) return;
|
|
4920
|
+
try {
|
|
4921
|
+
process.kill(-pid, signal);
|
|
4922
|
+
} catch (error) {
|
|
4923
|
+
if (error.code !== "ESRCH") throw error;
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
function spawnRunSandbox(options) {
|
|
4927
|
+
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
4928
|
+
throw new Error("run sandbox launcher requires Linux or macOS process groups");
|
|
4929
|
+
}
|
|
4930
|
+
if (!options.command || options.command.includes("\0") || !isAbsolute2(options.cwd)) {
|
|
4931
|
+
throw new Error("run sandbox requires a command and absolute cwd");
|
|
4932
|
+
}
|
|
4933
|
+
if (options.authToken !== void 0 && (!options.authToken || Buffer.byteLength(options.authToken) > 1048576)) {
|
|
4934
|
+
throw new Error("invalid run sandbox auth pipe payload length");
|
|
4935
|
+
}
|
|
4936
|
+
const maximum = Object.freeze([...options.maxAllowDomains]);
|
|
4937
|
+
const modelApiDomains = Object.freeze([...options.modelApiDomains ?? []]);
|
|
4938
|
+
NetworkConfigSchema.parse({ allowedDomains: maximum.filter((entry) => entry !== "*"), deniedDomains: [] });
|
|
4939
|
+
NetworkConfigSchema.parse({ allowedDomains: [...modelApiDomains], deniedDomains: [] });
|
|
4940
|
+
const onNetwork = options.onNetwork;
|
|
4941
|
+
const abortSignal = options.signal;
|
|
4942
|
+
const launch = {
|
|
4943
|
+
type: "init",
|
|
4944
|
+
command: options.command,
|
|
4945
|
+
args: [...options.args],
|
|
4946
|
+
cwd: options.cwd,
|
|
4947
|
+
env: { ...options.env },
|
|
4948
|
+
policy: {
|
|
4949
|
+
allowWrite: [...options.policy.allowWrite],
|
|
4950
|
+
denyWrite: [...options.policy.denyWrite ?? []],
|
|
4951
|
+
denyRead: [...options.policy.denyRead ?? []]
|
|
4952
|
+
},
|
|
4953
|
+
maxAllowDomains: [...maximum],
|
|
4954
|
+
modelApiDomains: [...modelApiDomains],
|
|
4955
|
+
hasAuthToken: options.authToken !== void 0
|
|
4956
|
+
};
|
|
4957
|
+
const child = spawn(process.execPath, [fileURLToPath(new URL("./run-sandbox-worker.mjs", import.meta.url))], {
|
|
4958
|
+
// Runtime helpers use the trusted daemon's environment. CLI env arrives
|
|
4959
|
+
// over IPC and is passed explicitly by the worker, without inheritance.
|
|
4960
|
+
env: process.env,
|
|
4961
|
+
cwd: options.cwd,
|
|
4962
|
+
stdio: ["pipe", "pipe", "pipe", "pipe", "ipc"],
|
|
4963
|
+
detached: true
|
|
4964
|
+
});
|
|
4965
|
+
const lifetime = new AbortController();
|
|
4966
|
+
const pending = /* @__PURE__ */ new Map();
|
|
4967
|
+
let stderr = "";
|
|
4968
|
+
let stopped = false;
|
|
4969
|
+
let gone = false;
|
|
4970
|
+
let outcome;
|
|
4971
|
+
let hardStop;
|
|
4972
|
+
let settle;
|
|
4973
|
+
const exited = new Promise((resolve13) => {
|
|
4974
|
+
settle = resolve13;
|
|
4975
|
+
});
|
|
4976
|
+
function send(message) {
|
|
4977
|
+
if (!child.connected || gone) return;
|
|
4978
|
+
child.send(message, (error) => {
|
|
4979
|
+
if (error) stop(0);
|
|
4980
|
+
});
|
|
4981
|
+
}
|
|
4982
|
+
function stop(graceMs = 3e3) {
|
|
4983
|
+
if (gone) return;
|
|
4984
|
+
if (!stopped) {
|
|
4985
|
+
stopped = true;
|
|
4986
|
+
lifetime.abort();
|
|
4987
|
+
for (const request of pending.values()) {
|
|
4988
|
+
clearTimeout(request.timer);
|
|
4989
|
+
request.controller.abort();
|
|
4990
|
+
}
|
|
4991
|
+
pending.clear();
|
|
4992
|
+
send({ type: "stop", graceMs });
|
|
4993
|
+
}
|
|
4994
|
+
if (!hardStop) hardStop = setTimeout(() => {
|
|
4995
|
+
stderr += "\nrun sandbox worker did not stop; terminating its process group";
|
|
4996
|
+
try {
|
|
4997
|
+
signalGroup(child.pid, "SIGKILL");
|
|
4998
|
+
} catch (error) {
|
|
4999
|
+
stderr += `
|
|
5000
|
+
run sandbox group termination failed: ${String(error)}`;
|
|
5001
|
+
}
|
|
5002
|
+
}, Math.max(0, Math.min(graceMs, 3e4)) + 5e3);
|
|
5003
|
+
}
|
|
5004
|
+
child.stderr.on("data", (data) => {
|
|
5005
|
+
stderr = (stderr + data.toString()).slice(-16e3);
|
|
5006
|
+
});
|
|
5007
|
+
child.stdin.on("error", () => void 0);
|
|
5008
|
+
child.on("message", (message) => {
|
|
5009
|
+
if (!message || typeof message !== "object") return;
|
|
5010
|
+
const value = message;
|
|
5011
|
+
if (value.type === "finished") {
|
|
5012
|
+
if ((value.code === null || Number.isInteger(value.code)) && (value.signal === null || typeof value.signal === "string")) {
|
|
5013
|
+
outcome = { code: value.code, signal: value.signal };
|
|
5014
|
+
}
|
|
5015
|
+
return;
|
|
5016
|
+
}
|
|
5017
|
+
if (value.type !== "network" || !Number.isSafeInteger(value.id) || value.id < 1 || pending.has(value.id)) return;
|
|
5018
|
+
const id4 = value.id;
|
|
5019
|
+
const request = {
|
|
5020
|
+
host: typeof value.host === "string" ? value.host : "",
|
|
5021
|
+
port: typeof value.port === "number" ? value.port : 0
|
|
5022
|
+
};
|
|
5023
|
+
if (typeof value.port !== "number" || stopped || lifetime.signal.aborted || pending.size >= MAX_PENDING_NETWORK || !matchesRunSandboxDomain(request, maximum)) {
|
|
5024
|
+
send({ type: "decision", id: id4, allow: false });
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
5027
|
+
const controller = new AbortController();
|
|
5028
|
+
const timer = setTimeout(() => {
|
|
5029
|
+
if (!pending.delete(id4)) return;
|
|
5030
|
+
controller.abort();
|
|
5031
|
+
send({ type: "decision", id: id4, allow: false });
|
|
5032
|
+
}, NETWORK_DECISION_MS);
|
|
5033
|
+
pending.set(id4, { controller, timer });
|
|
5034
|
+
void Promise.resolve().then(() => {
|
|
5035
|
+
if (stopped || lifetime.signal.aborted) return false;
|
|
5036
|
+
return onNetwork(request, controller.signal);
|
|
5037
|
+
}).then((allow) => {
|
|
5038
|
+
if (!pending.delete(id4)) return;
|
|
5039
|
+
clearTimeout(timer);
|
|
5040
|
+
send({ type: "decision", id: id4, allow: allow === true && !stopped && !lifetime.signal.aborted && !controller.signal.aborted });
|
|
5041
|
+
}, () => {
|
|
5042
|
+
if (pending.delete(id4)) {
|
|
5043
|
+
clearTimeout(timer);
|
|
5044
|
+
send({ type: "decision", id: id4, allow: false });
|
|
5045
|
+
}
|
|
5046
|
+
});
|
|
5047
|
+
});
|
|
5048
|
+
function onParentExit() {
|
|
5049
|
+
try {
|
|
5050
|
+
signalGroup(child.pid, "SIGKILL");
|
|
5051
|
+
} catch {
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
process.once("exit", onParentExit);
|
|
5055
|
+
function finish(code, signal) {
|
|
5056
|
+
if (gone) return;
|
|
5057
|
+
gone = true;
|
|
5058
|
+
stopped = true;
|
|
5059
|
+
lifetime.abort();
|
|
5060
|
+
for (const request of pending.values()) {
|
|
5061
|
+
clearTimeout(request.timer);
|
|
5062
|
+
request.controller.abort();
|
|
5063
|
+
}
|
|
5064
|
+
pending.clear();
|
|
5065
|
+
clearTimeout(hardStop);
|
|
5066
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
5067
|
+
process.removeListener("exit", onParentExit);
|
|
5068
|
+
try {
|
|
5069
|
+
signalGroup(child.pid, "SIGKILL");
|
|
5070
|
+
} catch (error) {
|
|
5071
|
+
stderr += `
|
|
5072
|
+
run sandbox group cleanup failed: ${String(error)}`;
|
|
5073
|
+
}
|
|
5074
|
+
settle(outcome ?? { code, signal });
|
|
5075
|
+
}
|
|
5076
|
+
child.once("error", (error) => {
|
|
5077
|
+
stderr += `
|
|
5078
|
+
${error.message}`;
|
|
5079
|
+
stop(0);
|
|
5080
|
+
if (child.pid === void 0) finish(null, null);
|
|
5081
|
+
});
|
|
5082
|
+
child.once("exit", finish);
|
|
5083
|
+
child.once("disconnect", () => {
|
|
5084
|
+
if (!outcome) stop(0);
|
|
5085
|
+
});
|
|
5086
|
+
const authPipe = child.stdio[3];
|
|
5087
|
+
authPipe.on("error", () => stop(0));
|
|
5088
|
+
authPipe.end(options.authToken);
|
|
5089
|
+
function onAbort() {
|
|
5090
|
+
stop(0);
|
|
5091
|
+
}
|
|
5092
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
5093
|
+
child.kill = (signal = "SIGTERM") => {
|
|
5094
|
+
if (gone) return false;
|
|
5095
|
+
if (signal === 0) {
|
|
5096
|
+
try {
|
|
5097
|
+
process.kill(child.pid, 0);
|
|
5098
|
+
return true;
|
|
5099
|
+
} catch {
|
|
5100
|
+
return false;
|
|
5101
|
+
}
|
|
5102
|
+
}
|
|
5103
|
+
stop(signal === "SIGKILL" ? 0 : 3e3);
|
|
5104
|
+
return true;
|
|
5105
|
+
};
|
|
5106
|
+
Object.defineProperty(child, "killed", { get: () => stopped });
|
|
5107
|
+
if (abortSignal?.aborted) stop(0);
|
|
5108
|
+
else send(launch);
|
|
5109
|
+
return {
|
|
5110
|
+
process: child,
|
|
5111
|
+
exited,
|
|
5112
|
+
stderrTail: () => stderr.slice(-4e3).trim(),
|
|
5113
|
+
async kill(graceMs = 3e3) {
|
|
5114
|
+
stop(graceMs);
|
|
5115
|
+
await exited;
|
|
5116
|
+
},
|
|
5117
|
+
async revoke() {
|
|
5118
|
+
stop(0);
|
|
5119
|
+
await exited;
|
|
5120
|
+
}
|
|
5121
|
+
};
|
|
5122
|
+
}
|
|
5123
|
+
|
|
4885
5124
|
// src/agents/tool-approval-policy.ts
|
|
4886
|
-
function toolApprovalDecision(request, policy, worktree, apps = []) {
|
|
5125
|
+
function toolApprovalDecision(request, policy, worktree, apps = [], runHosts = []) {
|
|
4887
5126
|
if (request.method === "sandbox/network" && policy) {
|
|
4888
5127
|
const value = request.input;
|
|
4889
5128
|
if (!value || typeof value.host !== "string" || /[*/:]/.test(value.host) || !networkDomainSchema.safeParse(value.host).success || !Number.isInteger(value.port) || Number(value.port) < 1 || Number(value.port) > 65535) return "deny";
|
|
5129
|
+
if (policy.permissions.network === "ask" && matchesRunSandboxDomain({ host: value.host, port: Number(value.port) }, [...policy.networkAllow, ...runHosts])) return "allow";
|
|
4890
5130
|
return policy.permissions.network;
|
|
4891
5131
|
}
|
|
4892
5132
|
if (request.method.startsWith("cli/") && policy && worktree) {
|
|
@@ -5249,7 +5489,13 @@ var HttpServerLink = class {
|
|
|
5249
5489
|
const response = z26.object({ decision: z26.enum(["pending", "allow", "deny"]) });
|
|
5250
5490
|
const liveness = new AbortController();
|
|
5251
5491
|
const gate = new ToolApprovalGate(assignment.runId, {
|
|
5252
|
-
permission: (request) => toolApprovalDecision(
|
|
5492
|
+
permission: (request) => toolApprovalDecision(
|
|
5493
|
+
request,
|
|
5494
|
+
assignment.policy,
|
|
5495
|
+
worktree,
|
|
5496
|
+
runApps(assignment.mcp)?.apps.map((app) => app.name),
|
|
5497
|
+
assignment.github ? [new URL(assignment.github.endpoint).host] : []
|
|
5498
|
+
),
|
|
5253
5499
|
// Keep the callback pending at the tool boundary. The adapter's current
|
|
5254
5500
|
// session and checkpoint remain intact; handback resolves this same call.
|
|
5255
5501
|
isLive: async () => {
|
|
@@ -5755,7 +6001,7 @@ function buildChildEnv(options = {}) {
|
|
|
5755
6001
|
}
|
|
5756
6002
|
|
|
5757
6003
|
// src/agents/child.ts
|
|
5758
|
-
import { spawn } from "node:child_process";
|
|
6004
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
5759
6005
|
import {
|
|
5760
6006
|
mkdirSync as mkdirSync4,
|
|
5761
6007
|
readFileSync as readFileSync3,
|
|
@@ -5920,7 +6166,7 @@ function endedSession(sessionId, exit) {
|
|
|
5920
6166
|
};
|
|
5921
6167
|
}
|
|
5922
6168
|
var STDERR_TAIL = 4e3;
|
|
5923
|
-
function
|
|
6169
|
+
function signalGroup2(pgid, signal) {
|
|
5924
6170
|
if (pgid === void 0) return;
|
|
5925
6171
|
try {
|
|
5926
6172
|
process.kill(-pgid, signal);
|
|
@@ -5942,7 +6188,7 @@ async function reapStale(pidFile2, marker) {
|
|
|
5942
6188
|
const [group, ...command] = line.trim().split(/\s+/);
|
|
5943
6189
|
return Number(group) === pgid && command.join(" ").includes(marker);
|
|
5944
6190
|
});
|
|
5945
|
-
if (member)
|
|
6191
|
+
if (member) signalGroup2(pgid, "SIGKILL");
|
|
5946
6192
|
}
|
|
5947
6193
|
async function claimRunDir(paths) {
|
|
5948
6194
|
ownDir(paths.runDir);
|
|
@@ -5953,7 +6199,7 @@ async function claimRunDir(paths) {
|
|
|
5953
6199
|
async function spawnCli(sandbox, argv, options) {
|
|
5954
6200
|
await reapStale(options.pidFile, options.marker);
|
|
5955
6201
|
const wrapped = await sandbox.wrap(argv, options.policy);
|
|
5956
|
-
const child =
|
|
6202
|
+
const child = spawn2("/bin/sh", ["-c", wrapped.command], {
|
|
5957
6203
|
cwd: options.cwd,
|
|
5958
6204
|
env: options.env,
|
|
5959
6205
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -5980,7 +6226,7 @@ ${error.message}`;
|
|
|
5980
6226
|
child.once("exit", (code, signal) => resolve13({ code, signal }));
|
|
5981
6227
|
}
|
|
5982
6228
|
).finally(() => {
|
|
5983
|
-
|
|
6229
|
+
signalGroup2(pgid, "SIGKILL");
|
|
5984
6230
|
rmSync3(options.pidFile, { force: true });
|
|
5985
6231
|
wrapped.release();
|
|
5986
6232
|
});
|
|
@@ -5990,7 +6236,7 @@ ${error.message}`;
|
|
|
5990
6236
|
exited,
|
|
5991
6237
|
stderrTail: () => wrapped.annotate(stderr.slice(-STDERR_TAIL)).trim(),
|
|
5992
6238
|
async kill(graceMs = 3e3) {
|
|
5993
|
-
|
|
6239
|
+
signalGroup2(pgid, "SIGTERM");
|
|
5994
6240
|
let timer;
|
|
5995
6241
|
await Promise.race([
|
|
5996
6242
|
exited,
|
|
@@ -5999,7 +6245,7 @@ ${error.message}`;
|
|
|
5999
6245
|
})
|
|
6000
6246
|
]);
|
|
6001
6247
|
clearTimeout(timer);
|
|
6002
|
-
|
|
6248
|
+
signalGroup2(pgid, "SIGKILL");
|
|
6003
6249
|
await exited;
|
|
6004
6250
|
}
|
|
6005
6251
|
};
|
|
@@ -6154,7 +6400,7 @@ import {
|
|
|
6154
6400
|
rmSync as rmSync4
|
|
6155
6401
|
} from "node:fs";
|
|
6156
6402
|
import { lstat, readdir } from "node:fs/promises";
|
|
6157
|
-
import { dirname as dirname6, isAbsolute as
|
|
6403
|
+
import { dirname as dirname6, isAbsolute as isAbsolute3, join as join7, relative as relative2, resolve as resolve4 } from "node:path";
|
|
6158
6404
|
import { execa as execa2 } from "execa";
|
|
6159
6405
|
import { simpleGit } from "simple-git";
|
|
6160
6406
|
var DISK_USAGE_TTL_MS = 5 * 6e4;
|
|
@@ -6172,7 +6418,7 @@ function runBranch(runId) {
|
|
|
6172
6418
|
}
|
|
6173
6419
|
function isInside(parent, child) {
|
|
6174
6420
|
const rel = relative2(parent, child);
|
|
6175
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
6421
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
6176
6422
|
}
|
|
6177
6423
|
var configWriters = /* @__PURE__ */ new Map();
|
|
6178
6424
|
function withRepoConfig(repoPath, work) {
|
|
@@ -6310,7 +6556,7 @@ var WorktreeManager = class {
|
|
|
6310
6556
|
const worktreeReal = realpathSync4(worktree);
|
|
6311
6557
|
for (const file of recipe.copyFiles) {
|
|
6312
6558
|
const rel = relative2(repoPath, resolve4(repoPath, file));
|
|
6313
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
6559
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute3(rel)) {
|
|
6314
6560
|
throw new Error(`setup recipe copies a file outside the repo: ${file}`);
|
|
6315
6561
|
}
|
|
6316
6562
|
const from = join7(repoPath, rel);
|
|
@@ -7231,6 +7477,14 @@ var RunContext = class {
|
|
|
7231
7477
|
tempEnv() {
|
|
7232
7478
|
return { TMPDIR: this.tmpDir, TMPPREFIX: join11(this.tmpDir, "zsh"), ...withGitConfig(this.githubEnv, RUN_GIT_CONFIG), ...this.options.policyHash ? { MIKKA_POLICY_HASH: this.options.policyHash } : {} };
|
|
7233
7479
|
}
|
|
7480
|
+
/**
|
|
7481
|
+
* The agent delivers this run itself: it commits, pushes to the issue
|
|
7482
|
+
* branch and opens the pull request with run-scoped GitHub credentials. The
|
|
7483
|
+
* server sends GitHub delivery only for a run whose push is allowed.
|
|
7484
|
+
*/
|
|
7485
|
+
get agentDelivers() {
|
|
7486
|
+
return Object.keys(this.githubEnv).length > 0;
|
|
7487
|
+
}
|
|
7234
7488
|
/** A hand-in from an earlier session would end this one at once. */
|
|
7235
7489
|
clearHandoff() {
|
|
7236
7490
|
rmSync8(this.handoffFile, { force: true });
|
|
@@ -7811,245 +8065,6 @@ import {
|
|
|
7811
8065
|
createSdkMcpServer as createSdkMcpServer2
|
|
7812
8066
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
7813
8067
|
|
|
7814
|
-
// src/agents/run-sandbox-launcher.ts
|
|
7815
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
7816
|
-
import { isIP } from "node:net";
|
|
7817
|
-
import { isAbsolute as isAbsolute3 } from "node:path";
|
|
7818
|
-
import { fileURLToPath } from "node:url";
|
|
7819
|
-
import { NetworkConfigSchema } from "@anthropic-ai/sandbox-runtime";
|
|
7820
|
-
var MAX_PENDING_NETWORK = 128;
|
|
7821
|
-
var NETWORK_DECISION_MS = 5 * 6e4;
|
|
7822
|
-
function canonicalHost(host) {
|
|
7823
|
-
if (!host || /[\s\x00-\x1f\x7f%\\/@?#*]/u.test(host)) return;
|
|
7824
|
-
const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
7825
|
-
if (isIP(bare) === 6) return new URL(`http://[${bare}]`).hostname.slice(1, -1);
|
|
7826
|
-
if (bare.includes(":")) return;
|
|
7827
|
-
try {
|
|
7828
|
-
return new URL(`http://${bare}`).hostname.toLowerCase();
|
|
7829
|
-
} catch {
|
|
7830
|
-
return void 0;
|
|
7831
|
-
}
|
|
7832
|
-
}
|
|
7833
|
-
function matchesRunSandboxDomain(request, domains) {
|
|
7834
|
-
const host = canonicalHost(request.host);
|
|
7835
|
-
if (!host || !Number.isInteger(request.port) || request.port < 1 || request.port > 65535) return false;
|
|
7836
|
-
return domains.some((entry) => {
|
|
7837
|
-
if (entry === "*") return true;
|
|
7838
|
-
const parts = /^(\[[^\]]+\]|[^:]+?)(?::([1-9][0-9]{0,4}))?$/u.exec(entry);
|
|
7839
|
-
if (!parts || parts[2] !== void 0 && (Number(parts[2]) > 65535 || Number(parts[2]) !== request.port)) return false;
|
|
7840
|
-
const wildcard = parts[1].startsWith("*.");
|
|
7841
|
-
const pattern = canonicalHost(wildcard ? parts[1].slice(2) : parts[1]);
|
|
7842
|
-
if (!pattern) return false;
|
|
7843
|
-
if (!wildcard) return host === pattern;
|
|
7844
|
-
return !isIP(host) && pattern.includes(".") && host.endsWith(`.${pattern}`);
|
|
7845
|
-
});
|
|
7846
|
-
}
|
|
7847
|
-
function signalGroup2(pid, signal) {
|
|
7848
|
-
if (pid === void 0) return;
|
|
7849
|
-
try {
|
|
7850
|
-
process.kill(-pid, signal);
|
|
7851
|
-
} catch (error) {
|
|
7852
|
-
if (error.code !== "ESRCH") throw error;
|
|
7853
|
-
}
|
|
7854
|
-
}
|
|
7855
|
-
function spawnRunSandbox(options) {
|
|
7856
|
-
if (process.platform !== "linux" && process.platform !== "darwin") {
|
|
7857
|
-
throw new Error("run sandbox launcher requires Linux or macOS process groups");
|
|
7858
|
-
}
|
|
7859
|
-
if (!options.command || options.command.includes("\0") || !isAbsolute3(options.cwd)) {
|
|
7860
|
-
throw new Error("run sandbox requires a command and absolute cwd");
|
|
7861
|
-
}
|
|
7862
|
-
if (options.authToken !== void 0 && (!options.authToken || Buffer.byteLength(options.authToken) > 1048576)) {
|
|
7863
|
-
throw new Error("invalid run sandbox auth pipe payload length");
|
|
7864
|
-
}
|
|
7865
|
-
const maximum = Object.freeze([...options.maxAllowDomains]);
|
|
7866
|
-
const modelApiDomains = Object.freeze([...options.modelApiDomains ?? []]);
|
|
7867
|
-
NetworkConfigSchema.parse({ allowedDomains: maximum.filter((entry) => entry !== "*"), deniedDomains: [] });
|
|
7868
|
-
NetworkConfigSchema.parse({ allowedDomains: [...modelApiDomains], deniedDomains: [] });
|
|
7869
|
-
const onNetwork = options.onNetwork;
|
|
7870
|
-
const abortSignal = options.signal;
|
|
7871
|
-
const launch = {
|
|
7872
|
-
type: "init",
|
|
7873
|
-
command: options.command,
|
|
7874
|
-
args: [...options.args],
|
|
7875
|
-
cwd: options.cwd,
|
|
7876
|
-
env: { ...options.env },
|
|
7877
|
-
policy: {
|
|
7878
|
-
allowWrite: [...options.policy.allowWrite],
|
|
7879
|
-
denyWrite: [...options.policy.denyWrite ?? []],
|
|
7880
|
-
denyRead: [...options.policy.denyRead ?? []]
|
|
7881
|
-
},
|
|
7882
|
-
maxAllowDomains: [...maximum],
|
|
7883
|
-
modelApiDomains: [...modelApiDomains],
|
|
7884
|
-
hasAuthToken: options.authToken !== void 0
|
|
7885
|
-
};
|
|
7886
|
-
const child = spawn2(process.execPath, [fileURLToPath(new URL("./run-sandbox-worker.mjs", import.meta.url))], {
|
|
7887
|
-
// Runtime helpers use the trusted daemon's environment. CLI env arrives
|
|
7888
|
-
// over IPC and is passed explicitly by the worker, without inheritance.
|
|
7889
|
-
env: process.env,
|
|
7890
|
-
cwd: options.cwd,
|
|
7891
|
-
stdio: ["pipe", "pipe", "pipe", "pipe", "ipc"],
|
|
7892
|
-
detached: true
|
|
7893
|
-
});
|
|
7894
|
-
const lifetime = new AbortController();
|
|
7895
|
-
const pending = /* @__PURE__ */ new Map();
|
|
7896
|
-
let stderr = "";
|
|
7897
|
-
let stopped = false;
|
|
7898
|
-
let gone = false;
|
|
7899
|
-
let outcome;
|
|
7900
|
-
let hardStop;
|
|
7901
|
-
let settle;
|
|
7902
|
-
const exited = new Promise((resolve13) => {
|
|
7903
|
-
settle = resolve13;
|
|
7904
|
-
});
|
|
7905
|
-
function send(message) {
|
|
7906
|
-
if (!child.connected || gone) return;
|
|
7907
|
-
child.send(message, (error) => {
|
|
7908
|
-
if (error) stop(0);
|
|
7909
|
-
});
|
|
7910
|
-
}
|
|
7911
|
-
function stop(graceMs = 3e3) {
|
|
7912
|
-
if (gone) return;
|
|
7913
|
-
if (!stopped) {
|
|
7914
|
-
stopped = true;
|
|
7915
|
-
lifetime.abort();
|
|
7916
|
-
for (const request of pending.values()) {
|
|
7917
|
-
clearTimeout(request.timer);
|
|
7918
|
-
request.controller.abort();
|
|
7919
|
-
}
|
|
7920
|
-
pending.clear();
|
|
7921
|
-
send({ type: "stop", graceMs });
|
|
7922
|
-
}
|
|
7923
|
-
if (!hardStop) hardStop = setTimeout(() => {
|
|
7924
|
-
stderr += "\nrun sandbox worker did not stop; terminating its process group";
|
|
7925
|
-
try {
|
|
7926
|
-
signalGroup2(child.pid, "SIGKILL");
|
|
7927
|
-
} catch (error) {
|
|
7928
|
-
stderr += `
|
|
7929
|
-
run sandbox group termination failed: ${String(error)}`;
|
|
7930
|
-
}
|
|
7931
|
-
}, Math.max(0, Math.min(graceMs, 3e4)) + 5e3);
|
|
7932
|
-
}
|
|
7933
|
-
child.stderr.on("data", (data) => {
|
|
7934
|
-
stderr = (stderr + data.toString()).slice(-16e3);
|
|
7935
|
-
});
|
|
7936
|
-
child.stdin.on("error", () => void 0);
|
|
7937
|
-
child.on("message", (message) => {
|
|
7938
|
-
if (!message || typeof message !== "object") return;
|
|
7939
|
-
const value = message;
|
|
7940
|
-
if (value.type === "finished") {
|
|
7941
|
-
if ((value.code === null || Number.isInteger(value.code)) && (value.signal === null || typeof value.signal === "string")) {
|
|
7942
|
-
outcome = { code: value.code, signal: value.signal };
|
|
7943
|
-
}
|
|
7944
|
-
return;
|
|
7945
|
-
}
|
|
7946
|
-
if (value.type !== "network" || !Number.isSafeInteger(value.id) || value.id < 1 || pending.has(value.id)) return;
|
|
7947
|
-
const id4 = value.id;
|
|
7948
|
-
const request = {
|
|
7949
|
-
host: typeof value.host === "string" ? value.host : "",
|
|
7950
|
-
port: typeof value.port === "number" ? value.port : 0
|
|
7951
|
-
};
|
|
7952
|
-
if (typeof value.port !== "number" || stopped || lifetime.signal.aborted || pending.size >= MAX_PENDING_NETWORK || !matchesRunSandboxDomain(request, maximum)) {
|
|
7953
|
-
send({ type: "decision", id: id4, allow: false });
|
|
7954
|
-
return;
|
|
7955
|
-
}
|
|
7956
|
-
const controller = new AbortController();
|
|
7957
|
-
const timer = setTimeout(() => {
|
|
7958
|
-
if (!pending.delete(id4)) return;
|
|
7959
|
-
controller.abort();
|
|
7960
|
-
send({ type: "decision", id: id4, allow: false });
|
|
7961
|
-
}, NETWORK_DECISION_MS);
|
|
7962
|
-
pending.set(id4, { controller, timer });
|
|
7963
|
-
void Promise.resolve().then(() => {
|
|
7964
|
-
if (stopped || lifetime.signal.aborted) return false;
|
|
7965
|
-
return onNetwork(request, controller.signal);
|
|
7966
|
-
}).then((allow) => {
|
|
7967
|
-
if (!pending.delete(id4)) return;
|
|
7968
|
-
clearTimeout(timer);
|
|
7969
|
-
send({ type: "decision", id: id4, allow: allow === true && !stopped && !lifetime.signal.aborted && !controller.signal.aborted });
|
|
7970
|
-
}, () => {
|
|
7971
|
-
if (pending.delete(id4)) {
|
|
7972
|
-
clearTimeout(timer);
|
|
7973
|
-
send({ type: "decision", id: id4, allow: false });
|
|
7974
|
-
}
|
|
7975
|
-
});
|
|
7976
|
-
});
|
|
7977
|
-
function onParentExit() {
|
|
7978
|
-
try {
|
|
7979
|
-
signalGroup2(child.pid, "SIGKILL");
|
|
7980
|
-
} catch {
|
|
7981
|
-
}
|
|
7982
|
-
}
|
|
7983
|
-
process.once("exit", onParentExit);
|
|
7984
|
-
function finish(code, signal) {
|
|
7985
|
-
if (gone) return;
|
|
7986
|
-
gone = true;
|
|
7987
|
-
stopped = true;
|
|
7988
|
-
lifetime.abort();
|
|
7989
|
-
for (const request of pending.values()) {
|
|
7990
|
-
clearTimeout(request.timer);
|
|
7991
|
-
request.controller.abort();
|
|
7992
|
-
}
|
|
7993
|
-
pending.clear();
|
|
7994
|
-
clearTimeout(hardStop);
|
|
7995
|
-
abortSignal?.removeEventListener("abort", onAbort);
|
|
7996
|
-
process.removeListener("exit", onParentExit);
|
|
7997
|
-
try {
|
|
7998
|
-
signalGroup2(child.pid, "SIGKILL");
|
|
7999
|
-
} catch (error) {
|
|
8000
|
-
stderr += `
|
|
8001
|
-
run sandbox group cleanup failed: ${String(error)}`;
|
|
8002
|
-
}
|
|
8003
|
-
settle(outcome ?? { code, signal });
|
|
8004
|
-
}
|
|
8005
|
-
child.once("error", (error) => {
|
|
8006
|
-
stderr += `
|
|
8007
|
-
${error.message}`;
|
|
8008
|
-
stop(0);
|
|
8009
|
-
if (child.pid === void 0) finish(null, null);
|
|
8010
|
-
});
|
|
8011
|
-
child.once("exit", finish);
|
|
8012
|
-
child.once("disconnect", () => {
|
|
8013
|
-
if (!outcome) stop(0);
|
|
8014
|
-
});
|
|
8015
|
-
const authPipe = child.stdio[3];
|
|
8016
|
-
authPipe.on("error", () => stop(0));
|
|
8017
|
-
authPipe.end(options.authToken);
|
|
8018
|
-
function onAbort() {
|
|
8019
|
-
stop(0);
|
|
8020
|
-
}
|
|
8021
|
-
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
8022
|
-
child.kill = (signal = "SIGTERM") => {
|
|
8023
|
-
if (gone) return false;
|
|
8024
|
-
if (signal === 0) {
|
|
8025
|
-
try {
|
|
8026
|
-
process.kill(child.pid, 0);
|
|
8027
|
-
return true;
|
|
8028
|
-
} catch {
|
|
8029
|
-
return false;
|
|
8030
|
-
}
|
|
8031
|
-
}
|
|
8032
|
-
stop(signal === "SIGKILL" ? 0 : 3e3);
|
|
8033
|
-
return true;
|
|
8034
|
-
};
|
|
8035
|
-
Object.defineProperty(child, "killed", { get: () => stopped });
|
|
8036
|
-
if (abortSignal?.aborted) stop(0);
|
|
8037
|
-
else send(launch);
|
|
8038
|
-
return {
|
|
8039
|
-
process: child,
|
|
8040
|
-
exited,
|
|
8041
|
-
stderrTail: () => stderr.slice(-4e3).trim(),
|
|
8042
|
-
async kill(graceMs = 3e3) {
|
|
8043
|
-
stop(graceMs);
|
|
8044
|
-
await exited;
|
|
8045
|
-
},
|
|
8046
|
-
async revoke() {
|
|
8047
|
-
stop(0);
|
|
8048
|
-
await exited;
|
|
8049
|
-
}
|
|
8050
|
-
};
|
|
8051
|
-
}
|
|
8052
|
-
|
|
8053
8068
|
// src/agents/run-network.ts
|
|
8054
8069
|
function runNetworkApproval(runId, harness, gate, maximum, localComputerEndpoint, appHost) {
|
|
8055
8070
|
if (gate.runId !== runId) throw new Error("Run approval scope mismatch");
|
|
@@ -8253,10 +8268,10 @@ function claudeSandboxSettings(policy) {
|
|
|
8253
8268
|
}
|
|
8254
8269
|
};
|
|
8255
8270
|
}
|
|
8256
|
-
function claudeSettings(policy, workspace) {
|
|
8271
|
+
function claudeSettings(policy, workspace, agentDelivers = false) {
|
|
8257
8272
|
return {
|
|
8258
8273
|
sandbox: claudeSandboxSettings(policy),
|
|
8259
|
-
permissions: claudePermissionRules(runPolicy(workspace))
|
|
8274
|
+
permissions: claudePermissionRules(runPolicy(workspace), agentDelivers)
|
|
8260
8275
|
};
|
|
8261
8276
|
}
|
|
8262
8277
|
function claudeChunks(message) {
|
|
@@ -8596,7 +8611,7 @@ var ClaudeAdapter = class {
|
|
|
8596
8611
|
},
|
|
8597
8612
|
settingSources: [],
|
|
8598
8613
|
settings: {
|
|
8599
|
-
...claudeSettings(ctx.policy(), options.policy),
|
|
8614
|
+
...claudeSettings(ctx.policy(), options.policy, ctx.agentDelivers),
|
|
8600
8615
|
// The official runtime launcher encloses the entire CLI. Nesting
|
|
8601
8616
|
// the CLI's Bash-only sandbox is unsupported on macOS.
|
|
8602
8617
|
...outerLocal ? { sandbox: { enabled: false, allowUnsandboxedCommands: true, autoAllowBashIfSandboxed: false } } : {},
|
|
@@ -9451,7 +9466,7 @@ var CodexAdapter = class {
|
|
|
9451
9466
|
import { Readable, Writable } from "node:stream";
|
|
9452
9467
|
import * as acp from "@agentclientprotocol/sdk";
|
|
9453
9468
|
import { join as join16 } from "node:path";
|
|
9454
|
-
function acpPermissionResponse(params, policy) {
|
|
9469
|
+
function acpPermissionResponse(params, policy, agentDelivers = false) {
|
|
9455
9470
|
const pick = (kinds) => {
|
|
9456
9471
|
for (const kind of kinds) {
|
|
9457
9472
|
const option = params.options.find((o) => o.kind === kind);
|
|
@@ -9463,7 +9478,7 @@ function acpPermissionResponse(params, policy) {
|
|
|
9463
9478
|
const raw = params.toolCall.rawInput;
|
|
9464
9479
|
const command = Array.isArray(raw?.command) ? raw.command.join(" ") : typeof raw?.command === "string" ? raw.command : params.toolCall.title ?? "";
|
|
9465
9480
|
const action = classifyCommand(command);
|
|
9466
|
-
const refused = action === "git_push" || action !== null && decide(action, { policy: runPolicy(policy) }) !== "allow";
|
|
9481
|
+
const refused = action === "git_push" && !agentDelivers || action !== null && decide(action, { policy: runPolicy(policy) }) !== "allow";
|
|
9467
9482
|
if (refused)
|
|
9468
9483
|
return { response: pick(["reject_once", "reject_always"]), refused };
|
|
9469
9484
|
const allowed = pick(["allow_always", "allow_once"]);
|
|
@@ -9646,7 +9661,8 @@ var CursorAdapter = class {
|
|
|
9646
9661
|
async requestPermission(params) {
|
|
9647
9662
|
const { response, refused } = acpPermissionResponse(
|
|
9648
9663
|
params,
|
|
9649
|
-
options.policy
|
|
9664
|
+
options.policy,
|
|
9665
|
+
ctx.agentDelivers
|
|
9650
9666
|
);
|
|
9651
9667
|
if (refused)
|
|
9652
9668
|
log.info(
|