@proagentstore/cli 0.4.143 → 0.4.144
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { RunnerInputError } from "../errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* Inspect a workdir: does it exist, does it hold anything, is it a checkout. Never throws —
|
|
6
7
|
* every failure is a `false`, because this function exists to REPORT a broken path and one that
|
|
@@ -175,10 +176,11 @@ export function ensureRepo(dir, opts = {}) {
|
|
|
175
176
|
// The owner's own folder (a monorepo subfolder, a plain project) — run in it untouched.
|
|
176
177
|
if (opts.ownFolder)
|
|
177
178
|
return dir;
|
|
178
|
-
|
|
179
|
+
// A refusal about the owner's folder, answered as one (#1116) — never a runner crash.
|
|
180
|
+
throw new RunnerInputError(`Refusing to clone into non-empty directory "${dir}" (no .git found) — move it aside or point at an empty path.`, 409);
|
|
179
181
|
}
|
|
180
182
|
if (opts.ownFolder && checkWorkdir(dir).insideWorkTree) {
|
|
181
|
-
throw new
|
|
183
|
+
throw new RunnerInputError(`"${dir}" is an empty folder inside another git checkout — not cloning a second repository into it. Point this repo at the checkout itself, or remove the folder.`, 409);
|
|
182
184
|
}
|
|
183
185
|
rmSync(dir, { recursive: true, force: true });
|
|
184
186
|
}
|
|
@@ -314,7 +314,7 @@ export class CodingRuntime {
|
|
|
314
314
|
session.interrupt();
|
|
315
315
|
break;
|
|
316
316
|
default:
|
|
317
|
-
throw new
|
|
317
|
+
throw new RunnerInputError(`Unknown coding action: ${action.kind}`);
|
|
318
318
|
}
|
|
319
319
|
return this.snapshot(sessionId);
|
|
320
320
|
}
|
|
@@ -472,8 +472,9 @@ export class CodingRuntime {
|
|
|
472
472
|
}
|
|
473
473
|
require(sessionId) {
|
|
474
474
|
const session = this.sessions.get(sessionId);
|
|
475
|
+
// A caller naming a session this runner does not hold is a lookup, not a crash (#1116): 404, with the same sentence.
|
|
475
476
|
if (!session)
|
|
476
|
-
throw new
|
|
477
|
+
throw new RunnerInputError(`No coding session: ${sessionId}`, 404);
|
|
477
478
|
return session;
|
|
478
479
|
}
|
|
479
480
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
+
import { RunnerInputError } from "../errors.js";
|
|
4
5
|
import { capturePane, createSession, killSession, listSessionsDetailed, runCommand as tmuxRunCommand, sendKey as tmuxSendKey, sendText as tmuxSendText, sessionExists, tmuxExec } from "./tmux.js";
|
|
5
6
|
function shellPath() {
|
|
6
7
|
return process.env.SHELL || "/bin/zsh";
|
|
@@ -14,7 +15,7 @@ export function splitTerminalTarget(raw, fallback) {
|
|
|
14
15
|
if (m)
|
|
15
16
|
return { backend: m[1].toLowerCase(), id: m[2].trim() };
|
|
16
17
|
if (!fallback)
|
|
17
|
-
throw new
|
|
18
|
+
throw new RunnerInputError("A target must include a backend prefix like `tmux:main`, or pass `backend` separately.");
|
|
18
19
|
return { backend: fallback, id: value };
|
|
19
20
|
}
|
|
20
21
|
export function listTerminalTargets(backend = "all") {
|
|
@@ -43,7 +44,7 @@ export function captureTerminalTarget(target, opts = {}) {
|
|
|
43
44
|
switch (t.backend) {
|
|
44
45
|
case "tmux":
|
|
45
46
|
if (!sessionExists(t.id))
|
|
46
|
-
throw new
|
|
47
|
+
throw new RunnerInputError(`No tmux session "${t.id}".`, 404);
|
|
47
48
|
return capturePane(t.id, Math.min(Math.max(Number(opts.lines) || 200, 1), 2000));
|
|
48
49
|
case "kitty":
|
|
49
50
|
return kittyExec(["@", "get-text", "--match", `id:${t.id}`, "--extent", "all"], 5000);
|
|
@@ -227,11 +228,11 @@ function kittyForegroundCommand(id) {
|
|
|
227
228
|
export function runTerminalCommand(target, command, backend) {
|
|
228
229
|
const t = splitTerminalTarget(target, backend);
|
|
229
230
|
if (!command.trim())
|
|
230
|
-
throw new
|
|
231
|
+
throw new RunnerInputError("A `command` is required.");
|
|
231
232
|
switch (t.backend) {
|
|
232
233
|
case "tmux":
|
|
233
234
|
if (!sessionExists(t.id))
|
|
234
|
-
throw new
|
|
235
|
+
throw new RunnerInputError(`No tmux session "${t.id}".`, 404);
|
|
235
236
|
tmuxRunCommand(t.id, command);
|
|
236
237
|
return capturePane(t.id, 200);
|
|
237
238
|
case "kitty":
|
|
@@ -246,11 +247,11 @@ export function runTerminalCommand(target, command, backend) {
|
|
|
246
247
|
export function sendTerminalKeys(target, opts) {
|
|
247
248
|
const t = splitTerminalTarget(target, opts.backend);
|
|
248
249
|
if (opts.text == null && (!opts.keys || opts.keys.length === 0))
|
|
249
|
-
throw new
|
|
250
|
+
throw new RunnerInputError("Provide `text` and/or `keys` to send.");
|
|
250
251
|
switch (t.backend) {
|
|
251
252
|
case "tmux":
|
|
252
253
|
if (!sessionExists(t.id))
|
|
253
|
-
throw new
|
|
254
|
+
throw new RunnerInputError(`No tmux session "${t.id}".`, 404);
|
|
254
255
|
if (opts.text != null)
|
|
255
256
|
tmuxSendText(t.id, opts.text);
|
|
256
257
|
for (const key of opts.keys ?? [])
|
|
@@ -264,7 +265,7 @@ export function sendTerminalKeys(target, opts) {
|
|
|
264
265
|
return captureTerminalTarget(t.id, { backend: "kitty" });
|
|
265
266
|
case "iterm2":
|
|
266
267
|
if ((opts.keys ?? []).length > 0)
|
|
267
|
-
throw new
|
|
268
|
+
throw new RunnerInputError("iTerm2 generic key events are not supported yet; send text or run a command.");
|
|
268
269
|
itermSessionScript(t.id, `tell theSession to write text ${appleString(opts.text ?? "")}`);
|
|
269
270
|
return captureTerminalTarget(t.id, { backend: "iterm2" });
|
|
270
271
|
}
|
|
@@ -275,7 +276,7 @@ export function createTerminalTarget(opts) {
|
|
|
275
276
|
case "tmux": {
|
|
276
277
|
const name = String(opts.name || "").trim();
|
|
277
278
|
if (!name)
|
|
278
|
-
throw new
|
|
279
|
+
throw new RunnerInputError("A `name` is required for tmux.");
|
|
279
280
|
if (sessionExists(name))
|
|
280
281
|
return { backend: "tmux", id: name, name, existed: true, workDir };
|
|
281
282
|
createSession(name, workDir, opts.command);
|
|
@@ -242,7 +242,7 @@ export class LocalRunner {
|
|
|
242
242
|
async approveTask(id) {
|
|
243
243
|
const task = this.requireTask(id);
|
|
244
244
|
if (task.status !== "needs_approval") {
|
|
245
|
-
throw new
|
|
245
|
+
throw new RunnerInputError(`Task is not waiting for approval: ${task.status}`, 409);
|
|
246
246
|
}
|
|
247
247
|
task.status = "queued";
|
|
248
248
|
task.updatedAt = new Date().toISOString();
|
|
@@ -662,7 +662,7 @@ export class LocalRunner {
|
|
|
662
662
|
if (task.type === "browser.open") {
|
|
663
663
|
const url = String(task.input.url || "");
|
|
664
664
|
if (!url || !/^https?:\/\//.test(url)) {
|
|
665
|
-
throw new
|
|
665
|
+
throw new RunnerInputError("browser.open requires an http(s) url");
|
|
666
666
|
}
|
|
667
667
|
const context = await this.getBrowserContext();
|
|
668
668
|
const page = context.pages()[0] || (await context.newPage());
|
|
@@ -701,12 +701,12 @@ export class LocalRunner {
|
|
|
701
701
|
this.addTaskEvent(task, "browser.goto.completed", "Browser page loaded", output);
|
|
702
702
|
return output;
|
|
703
703
|
}
|
|
704
|
-
throw new
|
|
704
|
+
throw new RunnerInputError(`Unknown task type: ${task.type}`);
|
|
705
705
|
}
|
|
706
706
|
requireTask(id) {
|
|
707
707
|
const task = this.store.getTask(id);
|
|
708
708
|
if (!task)
|
|
709
|
-
throw new
|
|
709
|
+
throw new RunnerInputError(`Task not found: ${id}`, 404);
|
|
710
710
|
return task;
|
|
711
711
|
}
|
|
712
712
|
async getBrowserContext() {
|