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