@illuminis/comprism 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -2
- package/out/agent/command.d.ts +75 -4
- package/out/agent/command.js +221 -26
- package/out/agent/render.d.ts +56 -8
- package/out/agent/render.js +441 -39
- package/out/agent/session.d.ts +156 -2
- package/out/agent/session.js +795 -154
- package/out/commands/agents.d.ts +2 -0
- package/out/commands/agents.js +79 -0
- package/out/commands/ask.d.ts +1 -1
- package/out/commands/ask.js +88 -12
- package/out/commands/commands-thin.js +79 -17
- package/out/commands/config.d.ts +1 -0
- package/out/commands/config.js +138 -0
- package/out/commands/cost.d.ts +1 -0
- package/out/commands/cost.js +167 -0
- package/out/commands/hooks.d.ts +1 -0
- package/out/commands/hooks.js +83 -0
- package/out/commands/install.d.ts +44 -1
- package/out/commands/install.js +198 -4
- package/out/commands/instructions.d.ts +1 -0
- package/out/commands/instructions.js +113 -0
- package/out/commands/integrations.d.ts +3 -0
- package/out/commands/integrations.js +215 -0
- package/out/commands/jobs.d.ts +5 -0
- package/out/commands/jobs.js +157 -0
- package/out/commands/login.js +249 -43
- package/out/commands/memory.d.ts +3 -0
- package/out/commands/memory.js +113 -0
- package/out/commands/permissions.d.ts +1 -0
- package/out/commands/permissions.js +94 -0
- package/out/commands/plugins.d.ts +4 -0
- package/out/commands/plugins.js +192 -0
- package/out/commands/privacy.d.ts +1 -0
- package/out/commands/privacy.js +57 -0
- package/out/commands/providerKey.d.ts +32 -0
- package/out/commands/providerKey.js +108 -0
- package/out/commands/repl.d.ts +12 -1
- package/out/commands/repl.js +1649 -122
- package/out/commands/report.d.ts +39 -0
- package/out/commands/report.js +115 -0
- package/out/commands/review.d.ts +5 -0
- package/out/commands/review.js +223 -0
- package/out/commands/sessions.d.ts +23 -0
- package/out/commands/sessions.js +115 -0
- package/out/commands/settings.d.ts +3 -1
- package/out/commands/settings.js +18 -16
- package/out/commands/skills.d.ts +21 -0
- package/out/commands/skills.js +207 -0
- package/out/commands/unattended.d.ts +7 -0
- package/out/commands/unattended.js +351 -0
- package/out/commands/update.d.ts +1 -0
- package/out/commands/update.js +123 -0
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +71 -35
- package/out/commands/worktrees.d.ts +5 -0
- package/out/commands/worktrees.js +186 -0
- package/out/executor/browser.d.ts +14 -0
- package/out/executor/browser.js +270 -0
- package/out/executor/diagnostics.d.ts +2 -0
- package/out/executor/diagnostics.js +181 -0
- package/out/executor/files.js +270 -40
- package/out/executor/git.js +42 -29
- package/out/executor/hooks.d.ts +42 -58
- package/out/executor/hooks.js +89 -182
- package/out/executor/index.d.ts +21 -5
- package/out/executor/index.js +160 -14
- package/out/executor/paths.d.ts +6 -1
- package/out/executor/paths.js +34 -6
- package/out/executor/sandbox.d.ts +40 -0
- package/out/executor/sandbox.js +299 -0
- package/out/executor/shell.d.ts +49 -4
- package/out/executor/shell.js +302 -56
- package/out/executor/toolservers.d.ts +20 -0
- package/out/executor/toolservers.js +189 -0
- package/out/executor/worktree.d.ts +9 -0
- package/out/executor/worktree.js +119 -0
- package/out/graph/read-python.js +2 -1
- package/out/lib/attach.d.ts +56 -12
- package/out/lib/attach.js +230 -63
- package/out/lib/clipboard.d.ts +23 -0
- package/out/lib/clipboard.js +182 -0
- package/out/lib/commandlist.d.ts +20 -0
- package/out/lib/commandlist.js +58 -0
- package/out/lib/decision.d.ts +22 -0
- package/out/lib/decision.js +50 -0
- package/out/lib/fingerprint.d.ts +25 -0
- package/out/lib/fingerprint.js +58 -0
- package/out/lib/gateway.d.ts +190 -2
- package/out/lib/gateway.js +59 -4
- package/out/lib/history.d.ts +24 -0
- package/out/lib/history.js +137 -0
- package/out/lib/ide.d.ts +19 -0
- package/out/lib/ide.js +131 -0
- package/out/lib/keyboard.d.ts +98 -0
- package/out/lib/keyboard.js +390 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/machine.d.ts +21 -0
- package/out/lib/machine.js +91 -0
- package/out/lib/notify.d.ts +4 -0
- package/out/lib/notify.js +52 -0
- package/out/lib/output.d.ts +48 -0
- package/out/lib/output.js +108 -0
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/project-ops.d.ts +19 -0
- package/out/lib/project-ops.js +146 -0
- package/out/lib/project.d.ts +28 -0
- package/out/lib/project.js +114 -0
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +36 -2
- package/out/lib/queue.d.ts +13 -0
- package/out/lib/queue.js +116 -0
- package/out/lib/readiness.d.ts +19 -0
- package/out/lib/readiness.js +170 -1
- package/out/lib/self.d.ts +23 -0
- package/out/lib/self.js +124 -0
- package/out/lib/sessions.d.ts +19 -0
- package/out/lib/sessions.js +221 -0
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/stdin.d.ts +32 -0
- package/out/lib/stdin.js +117 -0
- package/out/lib/store.d.ts +40 -0
- package/out/lib/store.js +138 -0
- package/out/lib/sync.d.ts +18 -0
- package/out/lib/sync.js +81 -0
- package/out/lib/ui.d.ts +10 -4
- package/out/lib/ui.js +65 -25
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +160 -1
- package/out/native/dictate +0 -0
- package/out/postinstall.js +42 -17
- package/out/providers/anthropic.d.ts +22 -0
- package/out/providers/anthropic.js +80 -0
- package/out/providers/index.d.ts +8 -0
- package/out/providers/index.js +83 -0
- package/out/providers/openai.d.ts +11 -0
- package/out/providers/openai.js +57 -0
- package/out/thin.js +595 -33
- package/package.json +11 -50
package/out/executor/index.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.NativeExecutor = exports.NATIVE_CAPABILITIES = exports.guessTestCommand = exports.stopEverything = void 0;
|
|
36
|
+
exports.NativeExecutor = exports.NATIVE_CAPABILITIES = exports.guessTestCommand = exports.stopForeground = exports.stopEverything = void 0;
|
|
37
37
|
/**
|
|
38
38
|
* The executor: one implementation of every action, for every native client.
|
|
39
39
|
*
|
|
@@ -50,19 +50,28 @@ exports.NativeExecutor = exports.NATIVE_CAPABILITIES = exports.guessTestCommand
|
|
|
50
50
|
* what it cost are all the server's business. This performs and reports.
|
|
51
51
|
*/
|
|
52
52
|
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
53
54
|
const files_1 = require("./files");
|
|
54
55
|
const documents_1 = require("./documents");
|
|
55
56
|
const hooks = __importStar(require("./hooks"));
|
|
57
|
+
const browser_1 = require("./browser");
|
|
56
58
|
const notebook_1 = require("./notebook");
|
|
59
|
+
const diagnostics_1 = require("./diagnostics");
|
|
60
|
+
const worktree_1 = require("./worktree");
|
|
61
|
+
const paths_1 = require("./paths");
|
|
57
62
|
const git_1 = require("./git");
|
|
63
|
+
const toolservers_1 = require("./toolservers");
|
|
58
64
|
const shell_1 = require("./shell");
|
|
59
65
|
Object.defineProperty(exports, "guessTestCommand", { enumerable: true, get: function () { return shell_1.guessTestCommand; } });
|
|
60
66
|
Object.defineProperty(exports, "stopEverything", { enumerable: true, get: function () { return shell_1.stopEverything; } });
|
|
67
|
+
Object.defineProperty(exports, "stopForeground", { enumerable: true, get: function () { return shell_1.stopForeground; } });
|
|
61
68
|
/** Everything a machine with a shell can do. Sent to the server on hello, and
|
|
62
69
|
* the catalog offered to the model is narrowed to it, so a client is never
|
|
63
70
|
* told about an action it cannot perform. */
|
|
64
71
|
exports.NATIVE_CAPABILITIES = [
|
|
65
72
|
"read_file", "read_notebook", "read_pdf", "list_dir", "file_map", "glob", "grep",
|
|
73
|
+
// The project's own checker for a changed file (manual 5.11).
|
|
74
|
+
"read_diagnostics",
|
|
66
75
|
"write_file", "edit_file", "multi_edit", "create_dir", "delete_file",
|
|
67
76
|
"move_file", "apply_patch",
|
|
68
77
|
// Bytes, for a document the server built. Never offered to the model; the
|
|
@@ -78,7 +87,27 @@ exports.NATIVE_CAPABILITIES = [
|
|
|
78
87
|
// on the server and are never offered to a client.
|
|
79
88
|
"git_push",
|
|
80
89
|
"todo_write",
|
|
90
|
+
// A helper's own copy of the project (manual 5.26). Never offered to the
|
|
91
|
+
// model: the service opens, reads and removes copies for editing helpers.
|
|
92
|
+
"worktree_open", "worktree_diff", "worktree_close",
|
|
93
|
+
// Tool servers on this machine the person approved (manual 9.10). Never
|
|
94
|
+
// offered to the model: the service lists and calls them by name.
|
|
95
|
+
"tool_server_list", "tool_server_call",
|
|
96
|
+
// The hooks of plugins installed here (manual 9.15), handed over by the
|
|
97
|
+
// service at the start of a job. Never offered to the model.
|
|
98
|
+
"plugin_hooks",
|
|
99
|
+
// This machine runs the hooks the service names, with each action and at
|
|
100
|
+
// the moments of a job (manual 10.9). Never offered to the model.
|
|
101
|
+
"run_hooks",
|
|
102
|
+
// A whole file, byte for byte, for a checkpoint (manual 7.3). Never offered
|
|
103
|
+
// to the model: `read_file` pages a long file and trims its last line break.
|
|
104
|
+
"read_raw",
|
|
81
105
|
];
|
|
106
|
+
/** What a helper may do inside its own copy (manual 5.26). */
|
|
107
|
+
const IN_COPY = new Set([
|
|
108
|
+
"read_file", "list_dir", "file_map", "glob", "grep",
|
|
109
|
+
"write_file", "edit_file", "multi_edit", "apply_patch", "create_dir", "git_status", "git_diff",
|
|
110
|
+
]);
|
|
82
111
|
/** How a package is added, per ecosystem.
|
|
83
112
|
*
|
|
84
113
|
* Named rather than passed through as a raw command, because "install a
|
|
@@ -101,42 +130,133 @@ function installCommand(root, pkg, manager, dev) {
|
|
|
101
130
|
default: return null;
|
|
102
131
|
}
|
|
103
132
|
}
|
|
133
|
+
/** A file's exact contents and their SHA-256, for a checkpoint (manual 7.3).
|
|
134
|
+
* Refused, never trimmed, when it is too large or not text. */
|
|
135
|
+
function readRaw(root, rel) {
|
|
136
|
+
const crypto = require("crypto");
|
|
137
|
+
const base = fs.realpathSync(root);
|
|
138
|
+
const where = path.resolve(base, rel);
|
|
139
|
+
if (!where.startsWith(base + path.sep))
|
|
140
|
+
return { isError: true, content: `${rel} is outside this project.` };
|
|
141
|
+
let bytes;
|
|
142
|
+
try {
|
|
143
|
+
bytes = fs.readFileSync(where);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return { isError: true, content: `${rel} does not exist.`, facts: { missing: 1 } };
|
|
147
|
+
}
|
|
148
|
+
if (bytes.length > 400_000)
|
|
149
|
+
return { isError: true, content: `${rel} is too large to copy.`, facts: { too_large: 1 } };
|
|
150
|
+
let text;
|
|
151
|
+
try {
|
|
152
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { isError: true, content: `${rel} is not text, so it cannot be copied.`, facts: { binary: 1 } };
|
|
156
|
+
}
|
|
157
|
+
return { isError: false, content: text, summary: `${bytes.length} bytes`,
|
|
158
|
+
facts: { sha256: crypto.createHash("sha256").update(bytes).digest("hex") } };
|
|
159
|
+
}
|
|
104
160
|
class NativeExecutor {
|
|
105
|
-
|
|
161
|
+
/** What this machine can do. The browser actions only where a browser is
|
|
162
|
+
* installed (manual 10.16); the service offers them only when switched on. */
|
|
163
|
+
capabilities = (0, browser_1.findBrowser)() ? [...exports.NATIVE_CAPABILITIES, ...browser_1.BROWSER_ACTIONS] : exports.NATIVE_CAPABILITIES;
|
|
164
|
+
/** The job's browser, started on first use (10.16). */
|
|
165
|
+
browser;
|
|
166
|
+
/** Close what this job opened on the machine: its browser. */
|
|
167
|
+
close() {
|
|
168
|
+
this.browser?.close();
|
|
169
|
+
this.browser = undefined;
|
|
170
|
+
}
|
|
106
171
|
root;
|
|
107
172
|
onOutput;
|
|
108
173
|
/** The test command, once it has been worked out or supplied. Remembered so a
|
|
109
174
|
* job that had to be told does not have to be told again. */
|
|
110
175
|
testCommand;
|
|
111
|
-
/** What this project asks to happen around the agent's actions, read once. */
|
|
112
|
-
hooks;
|
|
113
176
|
/** Whether the last test run passed. The grading signal the whole product
|
|
114
177
|
* rests on, kept so a session can report it when the job ends. */
|
|
115
178
|
lastTestsPassed;
|
|
179
|
+
/** The operating system's sandbox, set when the service turns it on for a
|
|
180
|
+
* job (manual 4.12). Absent, commands run with the person's own access. */
|
|
181
|
+
sandbox;
|
|
116
182
|
constructor(opts) {
|
|
117
183
|
this.root = fs.realpathSync(opts.root);
|
|
184
|
+
this.toolServers = new toolservers_1.ToolServers(this.root);
|
|
185
|
+
(0, paths_1.allowFolders)(this.root, opts.added ?? []);
|
|
118
186
|
this.onOutput = opts.onOutput;
|
|
119
187
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
188
|
+
/** One executor per helper copy, rooted there and nowhere else. */
|
|
189
|
+
inCopies = new Map();
|
|
190
|
+
/** Tool servers this job started (manual 9.10), stopped when it ends. */
|
|
191
|
+
toolServers;
|
|
192
|
+
async execute(name, a, plan) {
|
|
193
|
+
// An action for a helper's copy runs there, rooted in the copy, so it can
|
|
194
|
+
// never touch the person's own folder (manual 5.26).
|
|
195
|
+
if (typeof a._worktree === "string") {
|
|
196
|
+
const id = a._worktree;
|
|
197
|
+
const where = (0, worktree_1.worktreePath)(id);
|
|
198
|
+
if (!where)
|
|
199
|
+
return { isError: true, content: `There is no copy called ${id}.` };
|
|
200
|
+
if (!IN_COPY.has(name))
|
|
201
|
+
return { isError: true, content: `Refused: a helper cannot ${name} in its copy.` };
|
|
202
|
+
let inner = this.inCopies.get(id);
|
|
203
|
+
if (!inner) {
|
|
204
|
+
inner = new NativeExecutor({ root: where });
|
|
205
|
+
this.inCopies.set(id, inner);
|
|
206
|
+
}
|
|
207
|
+
const { _worktree: _dropped, ...rest } = a;
|
|
208
|
+
void _dropped;
|
|
209
|
+
return inner.execute(name, rest, plan);
|
|
210
|
+
}
|
|
211
|
+
// A tool server's list or call is carried, never gated by the project's
|
|
212
|
+
// own hooks: the service already decided whether it may happen.
|
|
213
|
+
if (name === "plugin_hooks") {
|
|
214
|
+
// An older service handing over plugin hooks in bulk. This machine runs
|
|
215
|
+
// the hooks the service names with each action instead, so nothing is
|
|
216
|
+
// kept here and nothing is matched here (manual 10.9).
|
|
217
|
+
const given = Array.isArray(a.hooks) ? a.hooks.length : 0;
|
|
218
|
+
return { isError: false, content: `${given} plugin hooks noted; the service names them per action` };
|
|
219
|
+
}
|
|
220
|
+
if (name === "read_raw") {
|
|
221
|
+
return readRaw(this.root, String(a.path ?? ""));
|
|
222
|
+
}
|
|
223
|
+
if (name === "run_hooks") {
|
|
224
|
+
// A moment of the job: job_start, job_end or waiting (manual 10.9).
|
|
225
|
+
const given = Array.isArray(a.hooks) ? a.hooks : [];
|
|
226
|
+
const done = await hooks.runHooks(this.root, given, {
|
|
227
|
+
when: String(a.when ?? ""), facts: (a.facts ?? {}),
|
|
228
|
+
});
|
|
229
|
+
const lines = hooks.reportLines(done);
|
|
230
|
+
return {
|
|
231
|
+
isError: done.failed.length > 0,
|
|
232
|
+
content: lines.join("\n") || "no hooks ran",
|
|
233
|
+
summary: done.failed.length ? `hook failed: ${done.failed.join("; ")}`
|
|
234
|
+
: `${done.runs.length} ${String(a.when)} hook${done.runs.length === 1 ? "" : "s"} ran`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (name === "tool_server_list") {
|
|
238
|
+
return this.toolServers.list(String(a.server ?? ""), String(a.command ?? ""));
|
|
239
|
+
}
|
|
240
|
+
if (name === "tool_server_call") {
|
|
241
|
+
return this.toolServers.call(String(a.server ?? ""), String(a.command ?? ""), String(a.tool ?? ""), (a.arguments ?? {}));
|
|
242
|
+
}
|
|
243
|
+
// The hooks the service named for this action, run in its order (10.9).
|
|
126
244
|
// A `before` hook may REFUSE this action, and that is the one place a
|
|
127
245
|
// project's own script changes what happens. It can only ever refuse: there
|
|
128
246
|
// is no exit code that approves something the agent was not already allowed
|
|
129
247
|
// to do, because a permission system inside the repository would live
|
|
130
248
|
// exactly where an attacker who got that far already is.
|
|
131
|
-
const gate = await hooks.
|
|
249
|
+
const gate = await hooks.runHooks(this.root, plan?.before ?? [], { when: "before", action: name, args: a });
|
|
132
250
|
if (gate.refusedBy) {
|
|
133
|
-
return { isError: true, content: gate.detail ?? "A
|
|
251
|
+
return { isError: true, content: gate.detail ?? "A hook in this project refused it.",
|
|
252
|
+
summary: gate.detail };
|
|
134
253
|
}
|
|
135
254
|
const shell = {
|
|
136
255
|
root: this.root,
|
|
137
256
|
cwd: typeof a.cwd === "string" ? a.cwd : undefined,
|
|
138
257
|
timeoutMs: typeof a.timeout_s === "number" ? a.timeout_s * 1000 : undefined,
|
|
139
258
|
onOutput: this.onOutput,
|
|
259
|
+
sandbox: this.sandbox,
|
|
140
260
|
};
|
|
141
261
|
try {
|
|
142
262
|
const done = await this.perform(name, a, shell);
|
|
@@ -144,13 +264,24 @@ class NativeExecutor {
|
|
|
144
264
|
// has already happened, and the overwhelming case is a formatter, which is
|
|
145
265
|
// worth knowing about and not worth losing the work over.
|
|
146
266
|
if (!done.isError) {
|
|
147
|
-
const ran = await hooks.
|
|
267
|
+
const ran = await hooks.runHooks(this.root, plan?.after ?? [], { when: "after", action: name, args: a });
|
|
268
|
+
const shown = hooks.reportLines(ran);
|
|
148
269
|
if (ran.failed.length) {
|
|
270
|
+
// Said on the action's own line, so the person sees it, and told to
|
|
271
|
+
// the model as what it is: the project's hook, not the file.
|
|
149
272
|
return {
|
|
150
273
|
...done,
|
|
151
|
-
|
|
274
|
+
summary: `${done.summary ? `${done.summary}; ` : ""}hook: ${ran.failed.join("; ")}`,
|
|
275
|
+
// The service words the action line from these (manual 10.9).
|
|
276
|
+
facts: { ...(done.facts ?? {}), hook_note: ran.failed.join("; ") },
|
|
277
|
+
content: `${done.content}\n\n[CompletionPrism ran this project's after hooks for this `
|
|
278
|
+
+ `action, as its settings ask. They reported: ${ran.failed.join("; ")}. This note is `
|
|
279
|
+
+ `from the tool, not part of the result.]`,
|
|
152
280
|
};
|
|
153
281
|
}
|
|
282
|
+
// Each hook's output, for the full view (manual 10.10).
|
|
283
|
+
if (shown.length)
|
|
284
|
+
return { ...done, content: `${done.content}\n\n${shown.join("\n")}` };
|
|
154
285
|
}
|
|
155
286
|
return done;
|
|
156
287
|
}
|
|
@@ -164,8 +295,15 @@ class NativeExecutor {
|
|
|
164
295
|
};
|
|
165
296
|
}
|
|
166
297
|
}
|
|
298
|
+
async browse(name, a) {
|
|
299
|
+
if (!this.browser)
|
|
300
|
+
this.browser = new browser_1.Browser(this.root);
|
|
301
|
+
return this.browser.perform(name, a);
|
|
302
|
+
}
|
|
167
303
|
async perform(name, a, shell) {
|
|
168
304
|
{
|
|
305
|
+
if (name.startsWith("browser_"))
|
|
306
|
+
return await this.browse(name, a);
|
|
169
307
|
switch (name) {
|
|
170
308
|
case "run_command":
|
|
171
309
|
return await (0, shell_1.runCommand)(String(a.command ?? ""), shell);
|
|
@@ -204,6 +342,14 @@ class NativeExecutor {
|
|
|
204
342
|
return await (0, documents_1.readPdf)(this.root, String(a.path ?? ""), a.pages, shell);
|
|
205
343
|
case "read_notebook":
|
|
206
344
|
return (0, notebook_1.readNotebook)(this.root, String(a.path ?? ""), a.include_output !== false);
|
|
345
|
+
case "read_diagnostics":
|
|
346
|
+
return await (0, diagnostics_1.readDiagnostics)(this.root, a.path, a.severity, shell);
|
|
347
|
+
case "worktree_open": return await (0, worktree_1.openWorktree)(String(a.id ?? ""), shell);
|
|
348
|
+
case "worktree_diff": return await (0, worktree_1.diffWorktree)(String(a.id ?? ""), shell);
|
|
349
|
+
case "worktree_close": {
|
|
350
|
+
this.inCopies.delete(String(a.id ?? ""));
|
|
351
|
+
return await (0, worktree_1.closeWorktree)(String(a.id ?? ""), shell);
|
|
352
|
+
}
|
|
207
353
|
case "git_status": return await (0, git_1.gitStatus)(shell);
|
|
208
354
|
case "git_diff": return await (0, git_1.gitDiff)({ ...shell, path: a.path, staged: Boolean(a.staged) });
|
|
209
355
|
case "git_log": return await (0, git_1.gitLog)({ ...shell, path: a.path, limit: a.limit });
|
package/out/executor/paths.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export declare class OutsideWorkspace extends Error {
|
|
2
2
|
readonly attempted: string;
|
|
3
|
-
|
|
3
|
+
/** `real` is where the path actually leads, when that differs from what was
|
|
4
|
+
* asked for: a link is refused by naming its real location (manual 2.4). */
|
|
5
|
+
constructor(attempted: string, real?: string);
|
|
4
6
|
}
|
|
7
|
+
export declare function allowFolders(root: string, added: string[]): void;
|
|
8
|
+
/** Every folder a job in `root` may touch, the project first. */
|
|
9
|
+
export declare function allowedFolders(root: string): string[];
|
|
5
10
|
export declare function isSecret(p: string): boolean;
|
|
6
11
|
/**
|
|
7
12
|
* An absolute path inside the workspace, or a refusal.
|
package/out/executor/paths.js
CHANGED
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.OutsideWorkspace = void 0;
|
|
37
|
+
exports.allowFolders = allowFolders;
|
|
38
|
+
exports.allowedFolders = allowedFolders;
|
|
37
39
|
exports.isSecret = isSecret;
|
|
38
40
|
exports.resolveInside = resolveInside;
|
|
39
41
|
/**
|
|
@@ -63,14 +65,40 @@ const fs = __importStar(require("fs"));
|
|
|
63
65
|
const path = __importStar(require("path"));
|
|
64
66
|
class OutsideWorkspace extends Error {
|
|
65
67
|
attempted;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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.`);
|
|
69
74
|
this.attempted = attempted;
|
|
70
75
|
this.name = "OutsideWorkspace";
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
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
|
+
}
|
|
74
102
|
/** Files never read, whatever the mode. Mirrors the server's own list. */
|
|
75
103
|
const SECRET = /^(\.env(\..*)?|.*\.pem|.*\.key|.*\.p12|.*\.pfx|id_rsa.*|id_ed25519.*|.*\.keystore|credentials|\.npmrc|\.netrc|\.git-credentials)$/i;
|
|
76
104
|
function isSecret(p) {
|
|
@@ -116,9 +144,9 @@ function resolveInside(root, rel, mustExist = false) {
|
|
|
116
144
|
}
|
|
117
145
|
// The comparison, with a separator on the end. Without it `/work/project-two`
|
|
118
146
|
// passes as being inside `/work/project`, because one string starts with the
|
|
119
|
-
// other.
|
|
120
|
-
if (
|
|
121
|
-
throw new OutsideWorkspace(rel);
|
|
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);
|
|
122
150
|
}
|
|
123
151
|
// Return the requested path, not the probe: the caller wants to write to the
|
|
124
152
|
// file it named, and the probe may be an ancestor of it.
|
|
@@ -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
|
+
}
|