@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
- throw new Error(`Refusing to clone into non-empty directory "${dir}" (no .git found) — move it aside or point at an empty path.`);
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 Error(`"${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.`);
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 Error(`Unknown coding action: ${action.kind}`);
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 Error(`No coding session: ${sessionId}`);
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 Error("A target must include a backend prefix like `tmux:main`, or pass `backend` separately.");
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 Error(`No tmux session "${t.id}".`);
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 Error("A `command` is required.");
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 Error(`No tmux session "${t.id}".`);
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 Error("Provide `text` and/or `keys` to send.");
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 Error(`No tmux session "${t.id}".`);
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 Error("iTerm2 generic key events are not supported yet; send text or run a command.");
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 Error("A `name` is required for tmux.");
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 Error(`Task is not waiting for approval: ${task.status}`);
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 Error("browser.open requires an http(s) url");
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 Error(`Unknown task type: ${task.type}`);
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 Error(`Task not found: ${id}`);
709
+ throw new RunnerInputError(`Task not found: ${id}`, 404);
710
710
  return task;
711
711
  }
712
712
  async getBrowserContext() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.143",
3
+ "version": "0.4.144",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",