@swmansion/argent 0.26.1-next.9 → 0.27.1-next.0

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.
Binary file
Binary file
Binary file
Binary file
package/dist/cli-cmds.mjs CHANGED
@@ -1675,7 +1675,9 @@ function spawnToolsServer(paths, port, options = {}) {
1675
1675
  } catch {
1676
1676
  logFd = fs.openSync("/dev/null", "w");
1677
1677
  }
1678
- const child = spawn("node", [paths.bundlePath, "start"], {
1678
+ const { bun, deno, electron } = process.versions;
1679
+ const nodeBin = !(bun || deno || electron) && fs.existsSync(process.execPath) ? process.execPath : "node";
1680
+ const child = spawn(nodeBin, [paths.bundlePath, "start"], {
1679
1681
  detached: true,
1680
1682
  stdio: ["ignore", "pipe", logFd],
1681
1683
  env: buildToolsServerEnv(paths, port, process.env, options)
@@ -1683,7 +1685,14 @@ function spawnToolsServer(paths, port, options = {}) {
1683
1685
  child.unref();
1684
1686
  const pid = child.pid;
1685
1687
  if (!pid) {
1686
- reject(new Error("Failed to get PID of spawned tools server"));
1688
+ child.once(
1689
+ "error",
1690
+ (err) => reject(
1691
+ new Error(
1692
+ err.code === "ENOENT" && nodeBin === "node" ? "Could not start the argent tool-server: `node` was not found on PATH. Install Node.js 20+ or add it to PATH." : `Could not start the argent tool-server: ${err.message}`
1693
+ )
1694
+ )
1695
+ );
1687
1696
  return;
1688
1697
  }
1689
1698
  let settled = false;
@@ -1847,14 +1856,20 @@ async function terminatePid(pid, stillOurs) {
1847
1856
  }
1848
1857
  async function killToolServer(bundlePath) {
1849
1858
  const state2 = await readState(bundlePath);
1850
- if (!state2) return;
1851
- await terminatePid(state2.pid);
1859
+ if (!state2) return false;
1860
+ const stillOurs = () => couldBeOurToolServer(state2.pid, state2.bundlePath);
1861
+ const alive = isProcessAlive(state2.pid);
1862
+ if (alive && !stillOurs()) return false;
1863
+ if (alive) await terminatePid(state2.pid, stillOurs);
1852
1864
  await clearToolsServerState(bundlePath ?? state2.bundlePath);
1865
+ return alive;
1853
1866
  }
1854
1867
  var PS_BIN = ["/bin/ps", "/usr/bin/ps"].find((p) => fs.existsSync(p)) ?? "ps";
1855
1868
  var PS_WIDTH_FLAGS = ["-ww"];
1856
- function readProcessCommandLine(pid, flags2 = PS_WIDTH_FLAGS) {
1869
+ var PS_LOCALE = process.platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
1870
+ function readProcessCommandLine(pid, flags2 = PS_WIDTH_FLAGS, env = { ...process.env, LC_ALL: PS_LOCALE }) {
1857
1871
  return execFileSync(PS_BIN, [...flags2, "-p", String(pid), "-o", "command="], {
1872
+ env,
1858
1873
  encoding: "utf8",
1859
1874
  timeout: 2e3,
1860
1875
  // A recycled pid can sit on a process with an argv past Node's 1 MiB exec
@@ -1869,19 +1884,27 @@ function readProcessCommandLine(pid, flags2 = PS_WIDTH_FLAGS) {
1869
1884
  function couldBeOurToolServer(pid, marker) {
1870
1885
  if (!marker) return false;
1871
1886
  if (process.platform === "win32") return true;
1872
- let cmd;
1873
- try {
1874
- cmd = readProcessCommandLine(pid);
1875
- } catch (err) {
1887
+ const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1888
+ const ours = new RegExp(`(?:^|[\\t\\n\\v\\f\\r ])${escaped} start(?:[\\t\\n\\v\\f\\r ]|$)`);
1889
+ const nonAscii = [...marker].some((c2) => c2.charCodeAt(0) > 127);
1890
+ let failure;
1891
+ for (const env of [void 0, process.env]) {
1892
+ try {
1893
+ if (ours.test(readProcessCommandLine(pid, PS_WIDTH_FLAGS, env))) return true;
1894
+ failure = void 0;
1895
+ if (!nonAscii) break;
1896
+ } catch (err) {
1897
+ failure = String(err);
1898
+ if (err.code === "ETIMEDOUT") break;
1899
+ }
1900
+ }
1901
+ if (failure !== void 0) {
1876
1902
  process.stderr.write(
1877
- `[launcher] ps could not read pid ${pid}'s command line; leaving it alone: ${String(err)}
1903
+ `[launcher] ps could not read pid ${pid}'s command line; leaving it alone: ${failure}
1878
1904
  `
1879
1905
  );
1880
- return false;
1881
1906
  }
1882
- if (!cmd) return false;
1883
- const escaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1884
- return new RegExp(`(?:^|\\s)${escaped} start(?:\\s|$)`).test(cmd);
1907
+ return false;
1885
1908
  }
1886
1909
  var LOCK_WAIT_TIMEOUT_MS = 3e4;
1887
1910
  var LOCK_STALE_MS = 45e3;
@@ -17130,7 +17153,7 @@ function sha256File(filePath) {
17130
17153
  createReadStream(filePath).on("data", (chunk) => hash2.update(chunk)).on("end", () => resolve11(hash2.digest("hex"))).on("error", reject);
17131
17154
  });
17132
17155
  }
17133
- async function uploadTar(tarPath, endpoint) {
17156
+ async function uploadTar(tarPath, endpoint, signal) {
17134
17157
  const init2 = {
17135
17158
  method: "POST",
17136
17159
  headers: {
@@ -17138,7 +17161,8 @@ async function uploadTar(tarPath, endpoint) {
17138
17161
  ...endpoint.token ? { Authorization: `Bearer ${endpoint.token}` } : {}
17139
17162
  },
17140
17163
  body: createReadStream(tarPath),
17141
- duplex: "half"
17164
+ duplex: "half",
17165
+ signal
17142
17166
  };
17143
17167
  const res = await fetch(`${endpoint.url}/upload`, init2);
17144
17168
  if (!res.ok) {
@@ -17190,7 +17214,7 @@ async function prepareFileInputs(specs, args, opts) {
17190
17214
  );
17191
17215
  tarPath = await tarball(filePath);
17192
17216
  wire.contentHash = await sha256File(tarPath);
17193
- wire.uploadId = await uploadTar(tarPath, opts.uploadEndpoint);
17217
+ wire.uploadId = await uploadTar(tarPath, opts.uploadEndpoint, opts.signal);
17194
17218
  } finally {
17195
17219
  if (tarPath) await rm2(tarPath, { force: true }).catch(() => {
17196
17220
  });
@@ -17259,7 +17283,7 @@ var ToolInvocationError = class extends Error {
17259
17283
  function authHeaders2(token) {
17260
17284
  return token ? { Authorization: `Bearer ${token}` } : {};
17261
17285
  }
17262
- async function consumeToolStream(body, onProgress) {
17286
+ async function consumeToolStream(body, onProgress, signal) {
17263
17287
  let final;
17264
17288
  const handleLine = (line) => {
17265
17289
  if (!line.trim()) return;
@@ -17283,6 +17307,7 @@ async function consumeToolStream(body, onProgress) {
17283
17307
  buffered += decoder.decode(value, { stream: true });
17284
17308
  let newline;
17285
17309
  while ((newline = buffered.indexOf("\n")) !== -1) {
17310
+ signal?.throwIfAborted();
17286
17311
  const line = buffered.slice(0, newline);
17287
17312
  buffered = buffered.slice(newline + 1);
17288
17313
  handleLine(line);
@@ -17295,6 +17320,7 @@ async function consumeToolStream(body, onProgress) {
17295
17320
  });
17296
17321
  throw err;
17297
17322
  }
17323
+ signal?.throwIfAborted();
17298
17324
  if (!final) {
17299
17325
  throw new Error("tool stream ended without a result \u2014 connection lost mid-run?");
17300
17326
  }
@@ -17321,27 +17347,33 @@ function createToolsClient(options = {}) {
17321
17347
  cached3 = await ensureToolsServer(options.paths);
17322
17348
  return cached3;
17323
17349
  }
17324
- async function fetchTools() {
17350
+ async function fetchTools(opts) {
17351
+ opts?.signal?.throwIfAborted();
17325
17352
  const { url: url2, token } = await baseUrl();
17326
- const res = await fetch(`${url2}/tools`, { headers: authHeaders2(token) });
17353
+ const res = await fetch(`${url2}/tools`, {
17354
+ headers: authHeaders2(token),
17355
+ signal: opts?.signal
17356
+ });
17327
17357
  if (!res.ok) throw new Error(`GET /tools failed: ${res.status} ${res.statusText}`);
17328
17358
  const json2 = await res.json();
17329
17359
  return json2.tools;
17330
17360
  }
17331
- async function fetchTool(name) {
17332
- const tools2 = await fetchTools();
17361
+ async function fetchTool(name, opts) {
17362
+ const tools2 = await fetchTools(opts);
17333
17363
  return tools2.find((t2) => t2.name === name) ?? null;
17334
17364
  }
17335
17365
  async function callTool(name, args, opts) {
17366
+ opts?.signal?.throwIfAborted();
17336
17367
  const { url: url2, token } = await baseUrl();
17337
17368
  let finalArgs = args;
17338
- const meta3 = await fetchTool(name);
17369
+ const meta3 = await fetchTool(name, { signal: opts?.signal });
17339
17370
  if (meta3?.fileInputs?.length) {
17340
17371
  const { url: routedUrl } = await getResolvedToolsUrl();
17341
17372
  const isRemote = routedUrl !== null;
17342
17373
  finalArgs = await prepareFileInputs(meta3.fileInputs, args ?? {}, {
17343
17374
  includeContent: isRemote,
17344
- uploadEndpoint: isRemote ? { url: url2, token } : void 0
17375
+ uploadEndpoint: isRemote ? { url: url2, token } : void 0,
17376
+ signal: opts?.signal
17345
17377
  });
17346
17378
  }
17347
17379
  const res = await fetch(`${url2}/tools/${encodeURIComponent(name)}`, {
@@ -17351,13 +17383,17 @@ function createToolsClient(options = {}) {
17351
17383
  ...opts?.onProgress ? { Accept: "application/x-ndjson" } : {},
17352
17384
  ...authHeaders2(token)
17353
17385
  },
17354
- body: JSON.stringify(finalArgs ?? {})
17386
+ body: JSON.stringify(finalArgs ?? {}),
17387
+ signal: opts?.signal
17355
17388
  });
17356
17389
  const contentType = res.headers.get("content-type") ?? "";
17357
17390
  if (opts?.onProgress && res.ok && res.body && contentType.includes("application/x-ndjson")) {
17358
- return consumeToolStream(res.body, opts.onProgress);
17391
+ return consumeToolStream(res.body, opts.onProgress, opts.signal);
17359
17392
  }
17360
- const json2 = await res.json().catch(() => ({}));
17393
+ const json2 = await res.json().catch(() => {
17394
+ opts?.signal?.throwIfAborted();
17395
+ return {};
17396
+ });
17361
17397
  if (!res.ok) {
17362
17398
  throw new ToolInvocationError(errorBodyMessage(json2) ?? `${res.status} ${res.statusText}`, {
17363
17399
  errorCode: json2.error_code,
@@ -17365,6 +17401,7 @@ function createToolsClient(options = {}) {
17365
17401
  issues: Array.isArray(json2.issues) ? json2.issues : void 0
17366
17402
  });
17367
17403
  }
17404
+ opts?.signal?.throwIfAborted();
17368
17405
  const { result: data } = await applyClientFileDirectives(json2.data);
17369
17406
  return { data, note: json2.note };
17370
17407
  }
@@ -18275,7 +18312,8 @@ async function materializeArtifacts(result, ctx) {
18275
18312
  return null;
18276
18313
  }
18277
18314
  const res = await fetchFn(`${ctx.toolsUrl}/artifacts/${value.id}`, {
18278
- headers: authHeaders3
18315
+ headers: authHeaders3,
18316
+ signal: ctx.signal
18279
18317
  });
18280
18318
  if (!res.ok) return null;
18281
18319
  const data = await readCapped(res, value.size);
@@ -18302,7 +18340,8 @@ async function materializeArtifacts(result, ctx) {
18302
18340
  }
18303
18341
  try {
18304
18342
  const res = await fetchFn(`${ctx.toolsUrl}/artifacts/${value.id}`, {
18305
- headers: authHeaders3
18343
+ headers: authHeaders3,
18344
+ signal: ctx.signal
18306
18345
  });
18307
18346
  if (!res.ok) return null;
18308
18347
  const data = Buffer.from(await res.arrayBuffer());
@@ -21877,7 +21916,7 @@ var _CI_VENDOR_COUNT_FOR_TEST = vendors_default.length;
21877
21916
  var SESSION_ID2 = randomUUID5();
21878
21917
  function readCliVersion() {
21879
21918
  if (true) {
21880
- return "0.26.1-next.9";
21919
+ return "0.27.1-next.0";
21881
21920
  }
21882
21921
  return "0.0.0";
21883
21922
  }
@@ -24188,8 +24227,13 @@ async function stopCmd(paths) {
24188
24227
  Stop one with: kill <pid>`);
24189
24228
  return;
24190
24229
  }
24191
- await killToolServer(paths?.bundlePath);
24192
- console.log(`tool-server stopped (pid ${state2.pid}).`);
24230
+ if (await killToolServer(paths?.bundlePath)) {
24231
+ console.log(`tool-server stopped (pid ${state2.pid}).`);
24232
+ } else {
24233
+ console.log(
24234
+ `tool-server: not running (pid ${state2.pid} is gone or not an argent tool-server).`
24235
+ );
24236
+ }
24193
24237
  }
24194
24238
  function logsCmd(follow) {
24195
24239
  if (!fs11.existsSync(LOG_FILE2)) {
@@ -0,0 +1,52 @@
1
+ export interface ArgentTool {
2
+ name: string;
3
+ description: string;
4
+ /** JSON Schema of the tool's arguments. */
5
+ inputSchema: Record<string, unknown>;
6
+ }
7
+ export interface ArgentToolResult<T = unknown> {
8
+ /** The tool's result. Artifacts (screenshots, recordings) are local file paths. */
9
+ data: T;
10
+ /** Optional advisory note the tool attached to the result. */
11
+ note?: string;
12
+ }
13
+ export interface CallToolOptions {
14
+ /** Receive progress events while a long-running tool works. */
15
+ onProgress?: (event: unknown) => void;
16
+ /** Stop waiting for the call. It rejects with the signal's reason, not ArgentToolError. */
17
+ signal?: AbortSignal;
18
+ }
19
+ /** The tool-server rejected or failed a call. */
20
+ export declare class ArgentToolError extends Error {
21
+ /** Stable failure code, when the server sent one. */
22
+ readonly code?: string;
23
+ /** Failure category, e.g. "subprocess", when the server sent one. */
24
+ readonly kind?: string;
25
+ /** Schema issues of a rejected argument object; set only on a validation failure. */
26
+ readonly issues?: readonly unknown[];
27
+ constructor(message: string, details?: {
28
+ code?: string;
29
+ kind?: string;
30
+ issues?: readonly unknown[];
31
+ });
32
+ }
33
+ export interface ArgentFlag {
34
+ name: string;
35
+ description: string;
36
+ /** Effective state, as `argent flags` reports it. */
37
+ enabled: boolean;
38
+ }
39
+ /** Feature flags and their effective state: project overrides global, then the default. */
40
+ export declare function listFlags(): ArgentFlag[];
41
+ export interface ArgentClient {
42
+ listTools(options?: {
43
+ signal?: AbortSignal;
44
+ }): Promise<ArgentTool[]>;
45
+ callTool<T = unknown>(name: string, args?: Record<string, unknown>, options?: CallToolOptions): Promise<ArgentToolResult<T>>;
46
+ /**
47
+ * Stop this install's local tool-server, like `argent server stop`. The next
48
+ * call starts a fresh one. Resolves false when none was running.
49
+ */
50
+ stopServer(): Promise<boolean>;
51
+ }
52
+ export declare function createArgentClient(): ArgentClient;