@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 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.155",
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: "a0295ff9031591fc84944f77c47074e8345d129e"
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(request, assignment.policy, worktree, runApps(assignment.mcp)?.apps.map((app) => app.name)),
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 signalGroup(pgid, signal) {
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) signalGroup(pgid, "SIGKILL");
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 = spawn("/bin/sh", ["-c", wrapped.command], {
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
- signalGroup(pgid, "SIGKILL");
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
- signalGroup(pgid, "SIGTERM");
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
- signalGroup(pgid, "SIGKILL");
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 isAbsolute2, join as join7, relative as relative2, resolve as resolve4 } from "node:path";
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("..") && !isAbsolute2(rel);
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("..") || isAbsolute2(rel)) {
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(