@illuminis/comprism 0.1.5 → 0.1.6

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.
Files changed (100) hide show
  1. package/out/agent/command.js +4 -4
  2. package/out/agent/session.d.ts +6 -1
  3. package/out/agent/session.js +17 -8
  4. package/out/commands/agents.js +3 -3
  5. package/out/commands/ask.js +8 -8
  6. package/out/commands/codemap.d.ts +1 -1
  7. package/out/commands/codemap.js +3 -3
  8. package/out/commands/commands-thin.js +4 -4
  9. package/out/commands/config.js +1 -1
  10. package/out/commands/cost.js +4 -4
  11. package/out/commands/hooks.js +1 -1
  12. package/out/commands/install.d.ts +1 -1
  13. package/out/commands/install.js +3 -2
  14. package/out/commands/instructions.js +1 -1
  15. package/out/commands/integrations.js +4 -4
  16. package/out/commands/keys.js +6 -6
  17. package/out/commands/login.js +37 -21
  18. package/out/commands/permissions.js +2 -2
  19. package/out/commands/plugins.js +3 -3
  20. package/out/commands/privacy.js +2 -2
  21. package/out/commands/repl.js +107 -83
  22. package/out/commands/review.js +6 -6
  23. package/out/commands/settings.js +30 -123
  24. package/out/commands/skills.js +2 -2
  25. package/out/commands/unattended.js +6 -6
  26. package/out/commands/update.js +1 -1
  27. package/out/commands/welcome.js +20 -29
  28. package/out/commands/worktrees.d.ts +1 -1
  29. package/out/graph/sync.d.ts +1 -1
  30. package/out/graph/sync.js +6 -6
  31. package/out/lib/attach.js +4 -3
  32. package/out/lib/commandlist.d.ts +1 -1
  33. package/out/lib/commandlist.js +2 -2
  34. package/out/lib/config.d.ts +3 -2
  35. package/out/lib/config.js +7 -8
  36. package/out/lib/connection.js +1 -1
  37. package/out/lib/gateway.d.ts +5 -5
  38. package/out/lib/machine.js +1 -1
  39. package/out/lib/project-ops.d.ts +1 -1
  40. package/out/lib/project-ops.js +4 -4
  41. package/out/lib/queue.js +1 -1
  42. package/out/lib/readiness.d.ts +3 -30
  43. package/out/lib/readiness.js +13 -82
  44. package/out/lib/servicecommand.d.ts +14 -0
  45. package/out/lib/servicecommand.js +74 -0
  46. package/out/lib/sessions.js +3 -3
  47. package/out/lib/ui.d.ts +9 -6
  48. package/out/lib/ui.js +20 -26
  49. package/out/lib/voice.js +1 -1
  50. package/out/lib/words.d.ts +20 -0
  51. package/out/lib/words.js +27 -0
  52. package/out/machine/codemap/build.d.ts +45 -0
  53. package/out/machine/codemap/build.js +91 -0
  54. package/out/machine/codemap/facts.d.ts +47 -0
  55. package/out/machine/codemap/facts.js +12 -0
  56. package/out/machine/codemap/files.d.ts +45 -0
  57. package/out/machine/codemap/files.js +207 -0
  58. package/out/machine/codemap/read-locales.d.ts +29 -0
  59. package/out/machine/codemap/read-locales.js +246 -0
  60. package/out/machine/codemap/read-python.d.ts +11 -0
  61. package/out/machine/codemap/read-python.js +116 -0
  62. package/out/machine/codemap/read-typescript.d.ts +16 -0
  63. package/out/machine/codemap/read-typescript.js +292 -0
  64. package/out/machine/executor/browser.d.ts +14 -0
  65. package/out/machine/executor/browser.js +270 -0
  66. package/out/machine/executor/diagnostics.d.ts +2 -0
  67. package/out/machine/executor/diagnostics.js +181 -0
  68. package/out/machine/executor/documents.d.ts +40 -0
  69. package/out/machine/executor/documents.js +170 -0
  70. package/out/machine/executor/files.d.ts +2 -0
  71. package/out/machine/executor/files.js +590 -0
  72. package/out/machine/executor/git.d.ts +48 -0
  73. package/out/machine/executor/git.js +145 -0
  74. package/out/machine/executor/hooks.d.ts +51 -0
  75. package/out/machine/executor/hooks.js +154 -0
  76. package/out/machine/executor/index.d.ts +45 -0
  77. package/out/machine/executor/index.js +367 -0
  78. package/out/machine/executor/notebook.d.ts +2 -0
  79. package/out/machine/executor/notebook.js +147 -0
  80. package/out/machine/executor/paths.d.ts +20 -0
  81. package/out/machine/executor/paths.js +154 -0
  82. package/out/machine/executor/sandbox.d.ts +40 -0
  83. package/out/machine/executor/sandbox.js +299 -0
  84. package/out/machine/executor/shell.d.ts +86 -0
  85. package/out/machine/executor/shell.js +582 -0
  86. package/out/machine/executor/toolservers.d.ts +20 -0
  87. package/out/machine/executor/toolservers.js +189 -0
  88. package/out/machine/executor/worktree.d.ts +9 -0
  89. package/out/machine/executor/worktree.js +119 -0
  90. package/out/machine/folder/project.d.ts +28 -0
  91. package/out/machine/folder/project.js +114 -0
  92. package/out/machine/runtime/home.d.ts +2 -0
  93. package/out/machine/runtime/home.js +48 -0
  94. package/out/machine/runtime/needs.d.ts +30 -0
  95. package/out/machine/runtime/needs.js +89 -0
  96. package/out/machine/runtime/self.d.ts +23 -0
  97. package/out/machine/runtime/self.js +124 -0
  98. package/out/providers/index.js +2 -1
  99. package/out/thin.js +11 -10
  100. package/package.json +4 -4
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OutsideWorkspace = void 0;
37
+ exports.allowFolders = allowFolders;
38
+ exports.allowedFolders = allowedFolders;
39
+ exports.isSecret = isSecret;
40
+ exports.resolveInside = resolveInside;
41
+ /**
42
+ * The folder boundary, on a real filesystem.
43
+ *
44
+ * Specification: docs/modules/CODING_AGENT_BUILD_SPECIFICATION.md §8, register
45
+ * items F1 (the folder is a boundary) and F2 (shortcuts do not defeat it).
46
+ *
47
+ * The browser executor gets this property free: it walks directory handles, and
48
+ * a handle it was never given simply cannot be reached. A real filesystem has no
49
+ * such protection, so here it has to be enforced, and enforced in the one order
50
+ * that works.
51
+ *
52
+ * **Resolve first, then compare.** Checking the string somebody sent and then
53
+ * resolving it is the standard way out: `a/../../etc/passwd` passes a check for
54
+ * ".." at the start and becomes `/etc/passwd` afterwards. And on a real
55
+ * filesystem there is a second door the browser does not have, which is a
56
+ * symbolic link inside the project pointing outside it. `realpath` follows it;
57
+ * a string comparison does not.
58
+ *
59
+ * The server checks this too. Two checks is not belt and braces: the server
60
+ * cannot see the filesystem, so it can only reason about the path it was given,
61
+ * and this can see the filesystem but runs on a machine we do not control.
62
+ * Neither alone is the boundary.
63
+ */
64
+ const fs = __importStar(require("fs"));
65
+ const path = __importStar(require("path"));
66
+ class OutsideWorkspace extends Error {
67
+ attempted;
68
+ /** `real` is where the path actually leads, when that differs from what was
69
+ * asked for: a link is refused by naming its real location (manual 2.4). */
70
+ constructor(attempted, real) {
71
+ super(`Refused: ${attempted} is outside this project` +
72
+ (real && real !== attempted ? ` (it leads to ${real})` : "") +
73
+ `. The agent works only inside the folders that were opened.`);
74
+ this.attempted = attempted;
75
+ this.name = "OutsideWorkspace";
76
+ }
77
+ }
78
+ exports.OutsideWorkspace = OutsideWorkspace;
79
+ /**
80
+ * Folders the person added for this job (manual 2.3), keyed by the project's
81
+ * real root. Registered by the executor that owns the job, so every existing
82
+ * `resolveInside(root, ...)` call checks all allowed folders without each one
83
+ * having to carry the list. Only folders the person named reach here.
84
+ */
85
+ const addedFolders = new Map();
86
+ function allowFolders(root, added) {
87
+ const realRoot = fs.realpathSync(root);
88
+ const real = added.map((a) => fs.realpathSync(a)).filter((a) => a !== realRoot);
89
+ if (real.length)
90
+ addedFolders.set(realRoot, [...new Set(real)]);
91
+ else
92
+ addedFolders.delete(realRoot);
93
+ }
94
+ /** Every folder a job in `root` may touch, the project first. */
95
+ function allowedFolders(root) {
96
+ const realRoot = fs.realpathSync(root);
97
+ return [realRoot, ...(addedFolders.get(realRoot) ?? [])];
98
+ }
99
+ function inside(real, folder) {
100
+ return real === folder || real.startsWith(folder.endsWith(path.sep) ? folder : folder + path.sep);
101
+ }
102
+ /** Files never read, whatever the mode. Mirrors the server's own list. */
103
+ const SECRET = /^(\.env(\..*)?|.*\.pem|.*\.key|.*\.p12|.*\.pfx|id_rsa.*|id_ed25519.*|.*\.keystore|credentials|\.npmrc|\.netrc|\.git-credentials)$/i;
104
+ function isSecret(p) {
105
+ return SECRET.test(path.basename(p));
106
+ }
107
+ /**
108
+ * An absolute path inside the workspace, or a refusal.
109
+ *
110
+ * `mustExist` is false when the caller is about to CREATE the thing. A file that
111
+ * does not exist yet cannot be resolved, so the check is applied to the deepest
112
+ * parent that does exist: creating `src/new/deep/file.ts` is inside the project
113
+ * exactly when `src` is, and demanding the leaf exist first would make it
114
+ * impossible to write a new file at all.
115
+ */
116
+ function resolveInside(root, rel, mustExist = false) {
117
+ const realRoot = fs.realpathSync(root);
118
+ const asked = String(rel ?? "");
119
+ // An ABSOLUTE path is resolved as written, and refused if it lands outside.
120
+ // It used to have its leading slash stripped and be treated as relative to
121
+ // the project, which was safe and quietly wrong: asking to write
122
+ // `/tmp/notes.txt` created `<project>/tmp/notes.txt` and reported the path the
123
+ // caller gave. Nothing escaped, but the confirmation named a file that did not
124
+ // exist, so the next read failed and the model spent steps on a mystery. An
125
+ // absolute path inside the project still works, which is the only case anybody
126
+ // actually means.
127
+ const joined = path.isAbsolute(asked)
128
+ ? path.resolve(asked)
129
+ : path.resolve(realRoot, asked);
130
+ let probe = joined;
131
+ if (!mustExist) {
132
+ // Walk up to the deepest part that exists, so a path we are about to create
133
+ // is still checked against where it would actually land.
134
+ while (!fs.existsSync(probe) && path.dirname(probe) !== probe) {
135
+ probe = path.dirname(probe);
136
+ }
137
+ }
138
+ let real;
139
+ try {
140
+ real = fs.realpathSync(probe);
141
+ }
142
+ catch {
143
+ throw new OutsideWorkspace(rel);
144
+ }
145
+ // The comparison, with a separator on the end. Without it `/work/project-two`
146
+ // passes as being inside `/work/project`, because one string starts with the
147
+ // other. Every allowed folder is checked, and nothing else.
148
+ if (!allowedFolders(realRoot).some((folder) => inside(real, folder))) {
149
+ throw new OutsideWorkspace(rel, real);
150
+ }
151
+ // Return the requested path, not the probe: the caller wants to write to the
152
+ // file it named, and the probe may be an ancestor of it.
153
+ return mustExist ? real : joined;
154
+ }
@@ -0,0 +1,40 @@
1
+ export type SandboxSystem = "macOS" | "Linux";
2
+ /** Which sandbox this machine offers, or why none. */
3
+ export declare function availability(): {
4
+ system: SandboxSystem | null;
5
+ why: string;
6
+ };
7
+ /** Does this host match an allowed address: exact, or `*.example.com`. */
8
+ export declare function hostAllowed(host: string, allow: string[]): boolean;
9
+ export declare class Sandbox {
10
+ readonly system: SandboxSystem;
11
+ private readonly writable;
12
+ private allow;
13
+ private proxy;
14
+ private port;
15
+ /** Hosts refused since the last `takeBlocked`, for the result to name. */
16
+ private blocked;
17
+ constructor(system: SandboxSystem, root: string, added: string[]);
18
+ /** The allowed internet addresses, as the service sent them. */
19
+ setAllowed(allow: string[]): void;
20
+ /** Where the gatekeeper listens on Linux: a socket file the relay inside
21
+ * the sandbox connects to, and a folder for the relay's ready marks. */
22
+ private socketPath;
23
+ private marks;
24
+ private calls;
25
+ /** Start the gatekeeper. Idempotent. */
26
+ start(): Promise<void>;
27
+ close(): void;
28
+ /** The hosts refused since the last call. */
29
+ takeBlocked(): string[];
30
+ private profile;
31
+ /** The program and arguments that run `command` inside the sandbox, and
32
+ * the variables that send its web traffic to the gatekeeper. */
33
+ wrap(command: string, cwd: string): {
34
+ file: string;
35
+ args: string[];
36
+ env: Record<string, string>;
37
+ };
38
+ /** What was blocked, in the manual's words, or null (manual 4.12). */
39
+ explain(output: string, hosts: string[]): string | null;
40
+ }
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Sandbox = void 0;
37
+ exports.availability = availability;
38
+ exports.hostAllowed = hostAllowed;
39
+ /**
40
+ * Commands run inside the operating system's sandbox (manual 4.12).
41
+ *
42
+ * The service decides whether a job is sandboxed and which internet addresses
43
+ * are allowed; this file only adapts the facility the operating system
44
+ * provides, which the service cannot reach:
45
+ *
46
+ * - macOS: `sandbox-exec` with a generated profile. Writes are allowed only in
47
+ * the project, the added folders and the temporary folders. The network is
48
+ * closed except to this machine, where a small gatekeeper answers as the
49
+ * command's web proxy and lets through only the allowed addresses.
50
+ * - Linux: bubblewrap (`bwrap`). The whole machine is mounted read only except
51
+ * the project, the added folders and the temporary folder, and the command
52
+ * gets a network of its own with nothing on it. A small relay inside that
53
+ * network passes web traffic to the same gatekeeper, through a socket file,
54
+ * so only the allowed addresses are reached there too.
55
+ *
56
+ * Anywhere else `--sandbox` refuses to start rather than running without it.
57
+ * A blocked attempt is reported as blocked by the sandbox, which is different
58
+ * from refused by the person or by a rule.
59
+ */
60
+ const child_process_1 = require("child_process");
61
+ const fs = __importStar(require("fs"));
62
+ const http = __importStar(require("http"));
63
+ const net = __importStar(require("net"));
64
+ const os = __importStar(require("os"));
65
+ const self_1 = require("../runtime/self");
66
+ /** Which sandbox this machine offers, or why none. */
67
+ function availability() {
68
+ const has = (file, args) => {
69
+ try {
70
+ (0, child_process_1.execFileSync)(file, args, { stdio: "ignore", timeout: 5000 });
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ };
77
+ if (process.platform === "darwin") {
78
+ return fs.existsSync("/usr/bin/sandbox-exec")
79
+ ? { system: "macOS", why: "" }
80
+ : { system: null, why: "the macOS sandbox (sandbox-exec) is not on this machine" };
81
+ }
82
+ if (process.platform === "linux") {
83
+ if (!has("bwrap", ["--version"])) {
84
+ return { system: null, why: "bubblewrap (bwrap) is not installed; install it to use --sandbox" };
85
+ }
86
+ // A working sandbox, not merely an installed one: some systems forbid the
87
+ // namespaces bubblewrap needs.
88
+ return has("bwrap", ["--ro-bind", "/", "/", "--unshare-net", "true"])
89
+ ? { system: "Linux", why: "" }
90
+ : { system: null, why: "bubblewrap is installed but this system does not let it start" };
91
+ }
92
+ return { system: null, why: `there is no supported sandbox on ${process.platform}` };
93
+ }
94
+ function real(p) {
95
+ try {
96
+ return fs.realpathSync(p);
97
+ }
98
+ catch {
99
+ return p;
100
+ }
101
+ }
102
+ function quote(p) {
103
+ return `"${p.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
104
+ }
105
+ /** Does this host match an allowed address: exact, or `*.example.com`. */
106
+ function hostAllowed(host, allow) {
107
+ const h = host.toLowerCase().replace(/\.$/, "");
108
+ return allow.some((raw) => {
109
+ const a = raw.toLowerCase().trim();
110
+ if (!a)
111
+ return false;
112
+ if (a === "*")
113
+ return true;
114
+ if (a.startsWith("*."))
115
+ return h.endsWith(a.slice(1)) || h === a.slice(2);
116
+ return h === a;
117
+ });
118
+ }
119
+ /** The port the relay listens on inside a Linux sandbox's own network. Any
120
+ * port would do: that network holds nothing else. */
121
+ const LINUX_RELAY_PORT = 18080;
122
+ /** The relay, run by the same Node inside the sandbox: every connection to
123
+ * its port is passed to the gatekeeper's socket file, and it marks itself
124
+ * ready once listening. It decides nothing. */
125
+ const RELAY = [
126
+ 'const net=require("net"),fs=require("fs");',
127
+ "net.createServer((c)=>{const u=net.connect(process.env.COMPRISM_GATE);",
128
+ 'c.pipe(u);u.pipe(c);u.on("error",()=>c.destroy());c.on("error",()=>u.destroy());})',
129
+ `.listen(${LINUX_RELAY_PORT},"127.0.0.1",()=>fs.writeFileSync(process.env.COMPRISM_READY,""));`,
130
+ ].join("");
131
+ class Sandbox {
132
+ system;
133
+ writable;
134
+ allow = [];
135
+ proxy = null;
136
+ port = 0;
137
+ /** Hosts refused since the last `takeBlocked`, for the result to name. */
138
+ blocked = [];
139
+ constructor(system, root, added) {
140
+ this.system = system;
141
+ // This user's own temporary folder and the system one, and no wider: the
142
+ // parent of a user's temporary folder holds other programs' caches.
143
+ const tmp = [os.tmpdir(), "/tmp", "/var/tmp"];
144
+ this.writable = [...new Set([root, ...added, ...tmp].map(real))];
145
+ }
146
+ /** The allowed internet addresses, as the service sent them. */
147
+ setAllowed(allow) {
148
+ this.allow = allow.filter((a) => typeof a === "string" && a.trim());
149
+ }
150
+ /** Where the gatekeeper listens on Linux: a socket file the relay inside
151
+ * the sandbox connects to, and a folder for the relay's ready marks. */
152
+ socketPath = "";
153
+ marks = "";
154
+ calls = 0;
155
+ /** Start the gatekeeper. Idempotent. */
156
+ async start() {
157
+ if (this.proxy)
158
+ return;
159
+ const server = http.createServer((req, res) => {
160
+ // A plain http:// request through the proxy.
161
+ let target;
162
+ try {
163
+ target = new URL(req.url ?? "");
164
+ }
165
+ catch {
166
+ res.writeHead(400);
167
+ res.end();
168
+ return;
169
+ }
170
+ if (!hostAllowed(target.hostname, this.allow)) {
171
+ this.blocked.push(target.hostname);
172
+ res.writeHead(403, { "content-type": "text/plain" });
173
+ res.end(`blocked by the sandbox: network access to ${target.hostname} is not allowed\n`);
174
+ return;
175
+ }
176
+ const upstream = http.request(target, { method: req.method, headers: req.headers }, (up) => {
177
+ res.writeHead(up.statusCode ?? 502, up.headers);
178
+ up.pipe(res);
179
+ });
180
+ upstream.on("error", () => { res.writeHead(502); res.end(); });
181
+ req.pipe(upstream);
182
+ });
183
+ server.on("connect", (req, client, head) => {
184
+ // https:// and anything else tunnelled with CONNECT host:port.
185
+ const [host, portText] = String(req.url ?? "").split(":");
186
+ if (!host || !hostAllowed(host, this.allow)) {
187
+ if (host)
188
+ this.blocked.push(host);
189
+ client.end("HTTP/1.1 403 Forbidden\r\n\r\n");
190
+ return;
191
+ }
192
+ const upstream = net.connect(Number(portText) || 443, host, () => {
193
+ client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
194
+ if (head.length)
195
+ upstream.write(head);
196
+ upstream.pipe(client);
197
+ client.pipe(upstream);
198
+ });
199
+ upstream.on("error", () => client.destroy());
200
+ client.on("error", () => upstream.destroy());
201
+ });
202
+ if (this.system === "Linux") {
203
+ // A socket file in the temporary folder, which the sandbox can see.
204
+ this.marks = fs.mkdtempSync(`${real(os.tmpdir())}/comprism-gate-`);
205
+ this.socketPath = `${this.marks}/gate.sock`;
206
+ await new Promise((resolve) => server.listen(this.socketPath, () => resolve()));
207
+ }
208
+ else {
209
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
210
+ this.port = server.address().port;
211
+ }
212
+ this.proxy = server;
213
+ server.unref();
214
+ }
215
+ close() {
216
+ this.proxy?.close();
217
+ this.proxy = null;
218
+ if (this.marks)
219
+ fs.rmSync(this.marks, { recursive: true, force: true });
220
+ this.marks = "";
221
+ }
222
+ /** The hosts refused since the last call. */
223
+ takeBlocked() {
224
+ const out = [...new Set(this.blocked)];
225
+ this.blocked = [];
226
+ return out;
227
+ }
228
+ profile() {
229
+ const writes = this.writable.map((p) => ` (subpath ${quote(p)})`).join("\n");
230
+ return [
231
+ "(version 1)",
232
+ "(allow default)",
233
+ "(deny file-write*)",
234
+ "(allow file-write*",
235
+ writes,
236
+ ' (literal "/dev/null") (literal "/dev/zero") (literal "/dev/stdout")',
237
+ ' (literal "/dev/stderr") (literal "/dev/dtracehelper")',
238
+ ' (regex #"^/dev/tty") (regex #"^/dev/fd/"))',
239
+ "(deny network*)",
240
+ '(allow network* (remote ip "localhost:*"))',
241
+ '(allow network* (local ip "localhost:*"))',
242
+ ].join("\n");
243
+ }
244
+ /** The program and arguments that run `command` inside the sandbox, and
245
+ * the variables that send its web traffic to the gatekeeper. */
246
+ wrap(command, cwd) {
247
+ if (this.system === "macOS") {
248
+ const proxy = `http://127.0.0.1:${this.port}`;
249
+ return {
250
+ file: "/usr/bin/sandbox-exec",
251
+ args: ["-p", this.profile(), "/bin/sh", "-c", command],
252
+ env: {
253
+ HTTP_PROXY: proxy, HTTPS_PROXY: proxy, http_proxy: proxy, https_proxy: proxy,
254
+ ALL_PROXY: proxy, all_proxy: proxy, NO_PROXY: "", no_proxy: "",
255
+ },
256
+ };
257
+ }
258
+ const binds = this.writable.filter((p) => fs.existsSync(p)).flatMap((p) => ["--bind", p, p]);
259
+ // Inside the sandbox's own network, a relay on this port passes every
260
+ // connection to the gatekeeper's socket file, which decides.
261
+ const proxy = `http://127.0.0.1:${LINUX_RELAY_PORT}`;
262
+ const ready = this.marks ? `${this.marks}/ready-${++this.calls}` : "";
263
+ const script = this.socketPath
264
+ ? `${(0, self_1.isStandalone)() ? '"$COMPRISM_NODE" __relay' : '"$COMPRISM_NODE" -e "$COMPRISM_RELAY"'} & relay=$!; i=0; `
265
+ + `while [ ! -e "$COMPRISM_READY" ] && [ $i -lt 150 ]; do sleep 0.02; i=$((i+1)); done; `
266
+ + `/bin/sh -c "$COMPRISM_CMD"; rc=$?; kill $relay 2>/dev/null; exit $rc`
267
+ : `/bin/sh -c "$COMPRISM_CMD"`;
268
+ return {
269
+ file: "bwrap",
270
+ args: ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc", ...binds,
271
+ "--unshare-net", "--die-with-parent", "--chdir", cwd, "/bin/sh", "-c", script],
272
+ env: {
273
+ COMPRISM_CMD: command, COMPRISM_NODE: process.execPath, COMPRISM_RELAY: RELAY,
274
+ COMPRISM_GATE: this.socketPath, COMPRISM_READY: ready,
275
+ ...(this.socketPath ? {
276
+ HTTP_PROXY: proxy, HTTPS_PROXY: proxy, http_proxy: proxy, https_proxy: proxy,
277
+ ALL_PROXY: proxy, all_proxy: proxy, NO_PROXY: "", no_proxy: "",
278
+ } : {}),
279
+ },
280
+ };
281
+ }
282
+ /** What was blocked, in the manual's words, or null (manual 4.12). */
283
+ explain(output, hosts) {
284
+ if (hosts.length)
285
+ return `blocked by the sandbox: network access to ${hosts.join(", ")} is not allowed`;
286
+ const write = /(?:touch: |cannot touch )?'?([^'\n:]+?)'?: (?:Operation not permitted|Read-only file system)/.exec(output);
287
+ if (write) {
288
+ return `blocked by the sandbox: writing to ${write[1].trim()} is not allowed, `
289
+ + "only the project and temporary folders can be written";
290
+ }
291
+ const offline = /Could not resolve host: (\S+)|getaddrinfo \w+ (\S+)|Network is unreachable/.exec(output);
292
+ if (offline) {
293
+ const host = offline[1] ?? offline[2] ?? "the internet";
294
+ return `blocked by the sandbox: network access to ${host} is not allowed`;
295
+ }
296
+ return null;
297
+ }
298
+ }
299
+ exports.Sandbox = Sandbox;
@@ -0,0 +1,86 @@
1
+ export interface RunResult {
2
+ content: string;
3
+ isError?: boolean;
4
+ summary?: string;
5
+ /** Counts the service turns into the action line: the lines read, the
6
+ * matches found and how many were left out (manual 5.3, 5.4). Numbers only;
7
+ * the wording is the service's. */
8
+ facts?: Record<string, number | string>;
9
+ }
10
+ export declare function safeEnvironment(from?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
11
+ /** The default time limit on a command (manual 5.12). */
12
+ export declare const DEFAULT_TIMEOUT_MS = 120000;
13
+ export declare function keptOutput(n: number): {
14
+ command: string;
15
+ text: string;
16
+ } | undefined;
17
+ export interface ShellOptions {
18
+ root: string;
19
+ cwd?: string;
20
+ timeoutMs?: number;
21
+ onOutput?: (chunk: string) => void;
22
+ /** Extra variables for this one command.
23
+ *
24
+ * Used by hooks, so a project's own script can read which file the agent
25
+ * just touched without the agent having to rewrite the script's arguments.
26
+ * Still filtered by `safeEnvironment`, so a hook cannot be handed a
27
+ * credential this way either. */
28
+ extraEnv?: Record<string, string>;
29
+ /** What the command is called in `output N`. */
30
+ label?: string;
31
+ /** Written to the command's standard input, then closed. Hooks read their
32
+ * details this way (manual 10.9). Absent, standard input is closed. */
33
+ input?: string;
34
+ /** The operating system's sandbox, when the service turned it on for this
35
+ * job (manual 4.12). Every command then runs inside it. */
36
+ sandbox?: import("./sandbox").Sandbox;
37
+ }
38
+ /** Run a command and wait for it.
39
+ *
40
+ * Never throws for an ordinary failure. A command that exits non-zero is a
41
+ * RESULT: it is usually the most useful thing that can happen, because it is
42
+ * how the agent learns what is wrong.
43
+ */
44
+ /**
45
+ * The shell a command runs in: the system shell on macOS and Linux, and
46
+ * PowerShell on Windows (manual 1.2), which is what a Windows developer types
47
+ * into and what the agent is told it is writing for.
48
+ */
49
+ export declare function shellFor(command: string): [string, string[], boolean];
50
+ export declare function runCommand(command: string, opts: ShellOptions): Promise<RunResult>;
51
+ /** Run one program with its arguments as a list, and no shell in between.
52
+ *
53
+ * For commands this tool builds itself, such as git. A shell would have to be
54
+ * told how to quote each argument, and the quoting differs by shell: the POSIX
55
+ * single quotes that kept a commit message whole on macOS and Linux reached
56
+ * git on Windows as literal characters, so a commit read `'fix` and a branch
57
+ * name no longer matched. With no shell there is nothing to quote, on any
58
+ * system, and a model written message cannot become a second command. */
59
+ export declare function runProgram(file: string, args: string[], opts: ShellOptions): Promise<RunResult>;
60
+ /** Stop every foreground command and everything it started. */
61
+ export declare function stopForeground(): void;
62
+ export declare function startBackground(command: string, opts: ShellOptions): RunResult;
63
+ export declare function readOutput(id: string): RunResult;
64
+ /** Stop one process this tool started, and everything it started, by the
65
+ * number it was given. Never by name or by port (manual 5.13). */
66
+ export declare function stopProcess(id: string): RunResult;
67
+ /** What this session started and is still tracking, for `processes`. */
68
+ export declare function listProcesses(): Array<{
69
+ id: string;
70
+ command: string;
71
+ running: boolean;
72
+ seconds: number;
73
+ port?: number;
74
+ }>;
75
+ /** Everything still running, stopped, and what was stopped. Called when a
76
+ * session ends, so a job that started a dev server does not leave it holding
77
+ * a port after the person has closed the terminal (manual 5.13). */
78
+ export declare function stopEverything(): string[];
79
+ export declare function guessTestCommand(root: string): string | null;
80
+ export declare function runTests(opts: ShellOptions & {
81
+ command?: string;
82
+ path?: string;
83
+ }): Promise<RunResult & {
84
+ passed?: boolean;
85
+ command?: string;
86
+ }>;