@parall/cli 1.50.1 → 1.52.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.
@@ -1 +1 @@
1
- {"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAmQpD"}
1
+ {"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAiSpD"}
@@ -1,6 +1,6 @@
1
1
  import { resolveCredentials } from '../lib/client.js';
2
2
  import { invokeClipAwaitingActivation } from '../lib/clip-invoke.js';
3
- import { execEdgeClipAwaitingActivation } from '../lib/edge-exec.js';
3
+ import { execEdgeClipWithBoundedWait } from '../lib/edge-exec.js';
4
4
  import { printJson, printError, parsePositiveInt } from '../lib/output.js';
5
5
  /**
6
6
  * Derive a CLI-friendly alias from a registry source string.
@@ -134,6 +134,32 @@ export function registerClipCommands(program) {
134
134
  printError(err);
135
135
  }
136
136
  });
137
+ clip
138
+ .command('tools')
139
+ .description('List the MCP tool schemas (name, description, inputSchema) of an MCP clip by alias — the discovery step before `clip exec <clip> <tool>`')
140
+ .argument('<alias>', 'Clip alias')
141
+ .action(async (alias) => {
142
+ try {
143
+ const { client, orgId } = resolveCredentials();
144
+ // Resolve alias to clip ID — same lookup `clip connections`/`info` use.
145
+ const clips = await client.listClips(orgId);
146
+ const found = clips.find((c) => c.alias === alias);
147
+ if (!found) {
148
+ printError(new Error(`Clip with alias "${alias}" not found`));
149
+ }
150
+ // MCP tools are NOT frozen in the clip manifest, so `clip info` cannot
151
+ // show them; the live server snapshot behind getClipMCPConfig is the
152
+ // only source. name/description/inputSchema ride verbatim in each tool
153
+ // object — print it as-is (untrusted external data, not instructions).
154
+ // A non-MCP clip has no config here: the server answers 404 NOT_FOUND
155
+ // ("this clip has no MCP config"), which printError surfaces faithfully.
156
+ const config = await client.getClipMCPConfig(orgId, found.id);
157
+ printJson(config.tools ?? []);
158
+ }
159
+ catch (err) {
160
+ printError(err);
161
+ }
162
+ });
137
163
  clip
138
164
  .command('invoke')
139
165
  .description('Invoke a clip command')
@@ -225,13 +251,15 @@ export function registerClipCommands(program) {
225
251
  profile: opts?.profile,
226
252
  timeout: timeoutMs,
227
253
  };
228
- // A cold cloud profile answers 503 EDGE_ACTIVATING BEFORE anything is
229
- // dispatched; absorb that one retryable window here (bounded, one
230
- // correlation id) instead of making every agent hand-roll a loop.
231
- // Everything else OUTCOME_UNKNOWN above all propagates untouched:
232
- // a dispatched command may have executed, and retrying it is not this
233
- // tool's call. See lib/edge-exec.ts.
234
- const result = await execEdgeClipAwaitingActivation(client, orgId, req);
254
+ // The three provably-safe refusals EDGE_ACTIVATING (cold start),
255
+ // EDGE_BUSY (single exec slot), EDGE_CONCURRENCY_LIMIT (org capacity)
256
+ // are absorbed here with a bounded wait (per-code Retry-After
257
+ // pacing, one absolute deadline, one correlation id) instead of
258
+ // making every agent hand-roll a loop. Everything else
259
+ // OUTCOME_UNKNOWN above all — propagates untouched: a dispatched
260
+ // command may have executed, and retrying it is not this tool's
261
+ // call. See lib/edge-exec.ts.
262
+ const result = await execEdgeClipWithBoundedWait(client, orgId, req);
235
263
  if (result.success) {
236
264
  printJson(result.data ?? null);
237
265
  }
@@ -1,55 +1,71 @@
1
1
  import type { EdgeClipExecResult, ExecEdgeClipRequest, ParallClient } from '@parall/sdk';
2
2
  /**
3
- * Cloud (hosted) Edge activation wait — CLI-side bounded retry for `clip exec`.
3
+ * Cloud (hosted) Edge bounded wait — CLI-side retry for `clip exec` across the
4
+ * three refusals that are provably safe to retry
5
+ * (edge-concurrency-reclaim-redesign §2.3 / §3.1):
4
6
  *
5
- * A clip connection bound to a `placement=hosted` Edge whose pod is cold returns
6
- * 503 EDGE_ACTIVATING from the exec pre-flight: the platform is starting the
7
- * pod, and the command has NOT been dispatched (the server answers before any
8
- * publish), so retrying the identical request cannot double-execute anything.
9
- * This module absorbs that one precise error so a single `parall clip exec`
10
- * rides through cold start instead of forcing the caller (agent or human) into
11
- * a blind retry loop.
7
+ * - 503 EDGE_ACTIVATING — the pod is cold-starting. Answered before
8
+ * any publish; "nothing happened" is locally provable.
9
+ * - 429 EDGE_CONCURRENCY_LIMIT the org is at capacity. Same pre-publish
10
+ * guarantee, from the EnsureLease branch.
11
+ * - 409 EDGE_BUSY — the pod is running another exec. STRICTLY
12
+ * WEAKER guarantee: the request DID reach the pod, which refused it before
13
+ * starting the worker. Its safety rests on that reply being received — a
14
+ * reply lost on the way back surfaces as OUTCOME_UNKNOWN, never as
15
+ * EDGE_BUSY, so "thought busy, actually ran" cannot happen.
12
16
  *
13
- * Everything else propagates on first occurrence especially OUTCOME_UNKNOWN
17
+ * Each code follows its own pace: the server's Retry-After wins when present;
18
+ * without it (older servers) each code falls back to its own default schedule.
19
+ * All three share ONE absolute deadline — the CLI's task boundary — never a
20
+ * per-code sum. On expiry the LAST typed ApiError is rethrown as-is, so the
21
+ * error's code names which class of wait ran out.
22
+ *
23
+ * Everything else propagates on first occurrence — above all OUTCOME_UNKNOWN
14
24
  * (504): the command was dispatched and MAY HAVE EXECUTED; an automatic retry
15
25
  * could post, order or delete twice. That decision belongs to the caller, with
16
26
  * the request id from the error message, never to a loop in here.
17
27
  *
18
- * Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-503
28
+ * Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-error
19
29
  * semantics for every other consumer; only the CLI exec behavior waits.
20
- * Mirrors lib/clip-invoke.ts (the v2 hosted-browser wait) — same budget, same
21
- * schedule, same exact-code matching discipline.
22
30
  */
23
- /** Total activation wait budget. Independent of the clip command --timeout:
24
- * each attempt keeps its own full command timeout, so worst-case wall clock is
25
- * roughly this budget plus one command timeout. */
26
- export declare const EDGE_ACTIVATION_WAIT_BUDGET_MS = 60000;
27
- /** Backoff schedule between activation retries; the last entry repeats. */
31
+ /** One absolute wait deadline shared by ALL waitable codes (never per-code).
32
+ * Each attempt keeps its own full command timeout, so worst-case wall clock
33
+ * is roughly this budget plus one command timeout. */
34
+ export declare const EDGE_EXEC_WAIT_BUDGET_MS = 120000;
35
+ /** EDGE_ACTIVATING fallback schedule when the server sends no Retry-After
36
+ * (matches the pre-Retry-After deployed behavior); the last entry repeats. */
28
37
  export declare const EDGE_ACTIVATION_BACKOFF_MS: number[];
38
+ /** EDGE_BUSY fallback: the in-flight exec finishes within its command timeout,
39
+ * so poll on the order of seconds. */
40
+ export declare const EDGE_BUSY_FALLBACK_BACKOFF_MS = 5000;
41
+ /** EDGE_CONCURRENCY_LIMIT fallback: capacity frees only through another
42
+ * lease's full release + teardown — slower and high-variance. */
43
+ export declare const EDGE_CONCURRENCY_FALLBACK_BACKOFF_MS = 15000;
29
44
  /** Injectable time hooks so tests never really sleep. */
30
45
  export type EdgeActivationWaitHooks = {
31
46
  sleep?: (ms: number) => Promise<void>;
32
47
  now?: () => number;
33
48
  };
34
- /**
35
- * The ONLY retryable error: a typed ApiError carrying exactly 503 +
36
- * EDGE_ACTIVATING. Everything elserouting errors, EDGE_BUSY, capacity,
37
- * repair, command failures, timeouts, OUTCOME_UNKNOWN, other 5xx — means
38
- * retrying is either pointless or unsafe, so it propagates unchanged on the
39
- * first occurrence.
40
- */
49
+ type WaitableCode = 'EDGE_ACTIVATING' | 'EDGE_CONCURRENCY_LIMIT' | 'EDGE_BUSY';
50
+ /** The waitable refusal this error carries, or null. Only a real SDK ApiError
51
+ * with the exact status+code pairing counts duck-typed shapes, bare
52
+ * statuses and everything else (routing errors, repair, command failures,
53
+ * timeouts, OUTCOME_UNKNOWN, other 5xx) mean waiting is pointless or unsafe. */
54
+ export declare function waitableEdgeRefusal(err: unknown): WaitableCode | null;
55
+ /** Back-compat predicate: exactly the cold-start signal. */
41
56
  export declare function isEdgeActivationPending(err: unknown): boolean;
42
57
  /**
43
- * Execute an Edge clip, transparently absorbing the cloud-profile activation
44
- * window.
58
+ * Execute an Edge clip, waiting (bounded) through activation, busy and
59
+ * capacity refusals.
45
60
  *
46
- * Retries the IDENTICAL request while the server reports EDGE_ACTIVATING,
47
- * backing off 500ms 1s → 2s (capped) until the 60s activation budget is
48
- * spent. The whole loop carries one `correlation_id` (the caller's, or one
49
- * minted here) so the server's exec audit reads the retries as a single
50
- * logical call. Once the budget is spent, the LAST typed ApiError is rethrown
51
- * as-is — status/code intact — so the printed error stays machine-readable.
52
- * Any other failure is rethrown immediately.
61
+ * Retries the IDENTICAL request, pacing each code by its server Retry-After
62
+ * (fallback: the code's own schedule), all under one absolute deadline. The
63
+ * whole loop carries one `correlation_id` (the caller's, or one minted here)
64
+ * so the server's exec audit reads the retries as a single logical call. Once
65
+ * the deadline passes, the LAST typed ApiError is rethrown as-is —
66
+ * status/code intact — so the printed error stays machine-readable and names
67
+ * the class of wait that ran out. Any other failure is rethrown immediately.
53
68
  */
54
- export declare function execEdgeClipAwaitingActivation(client: Pick<ParallClient, 'execEdgeClip'>, orgId: string, req: ExecEdgeClipRequest, hooks?: EdgeActivationWaitHooks): Promise<EdgeClipExecResult>;
69
+ export declare function execEdgeClipWithBoundedWait(client: Pick<ParallClient, 'execEdgeClip'>, orgId: string, req: ExecEdgeClipRequest, hooks?: EdgeActivationWaitHooks): Promise<EdgeClipExecResult>;
70
+ export {};
55
71
  //# sourceMappingURL=edge-exec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"edge-exec.d.ts","sourceRoot":"","sources":["../../src/lib/edge-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzF;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;oDAEoD;AACpD,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,UAAsB,CAAC;AAE9D,yDAAyD;AACzD,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,EAC1C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,mBAAmB,EACxB,KAAK,CAAC,EAAE,uBAAuB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAuB7B"}
1
+ {"version":3,"file":"edge-exec.d.ts","sourceRoot":"","sources":["../../src/lib/edge-exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;uDAEuD;AACvD,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAEhD;+EAC+E;AAC/E,eAAO,MAAM,0BAA0B,UAAsB,CAAC;AAE9D;uCACuC;AACvC,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD;kEACkE;AAClE,eAAO,MAAM,oCAAoC,QAAS,CAAC;AAE3D,yDAAyD;AACzD,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAIF,KAAK,YAAY,GAAG,iBAAiB,GAAG,wBAAwB,GAAG,WAAW,CAAC;AAgB/E;;;iFAGiF;AACjF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAIrE;AAED,4DAA4D;AAC5D,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,EAC1C,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,mBAAmB,EACxB,KAAK,CAAC,EAAE,uBAAuB,GAC9B,OAAO,CAAC,kBAAkB,CAAC,CAiC7B"}
@@ -1,74 +1,114 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { ApiError } from '@parall/sdk';
3
3
  /**
4
- * Cloud (hosted) Edge activation wait — CLI-side bounded retry for `clip exec`.
4
+ * Cloud (hosted) Edge bounded wait — CLI-side retry for `clip exec` across the
5
+ * three refusals that are provably safe to retry
6
+ * (edge-concurrency-reclaim-redesign §2.3 / §3.1):
5
7
  *
6
- * A clip connection bound to a `placement=hosted` Edge whose pod is cold returns
7
- * 503 EDGE_ACTIVATING from the exec pre-flight: the platform is starting the
8
- * pod, and the command has NOT been dispatched (the server answers before any
9
- * publish), so retrying the identical request cannot double-execute anything.
10
- * This module absorbs that one precise error so a single `parall clip exec`
11
- * rides through cold start instead of forcing the caller (agent or human) into
12
- * a blind retry loop.
8
+ * - 503 EDGE_ACTIVATING — the pod is cold-starting. Answered before
9
+ * any publish; "nothing happened" is locally provable.
10
+ * - 429 EDGE_CONCURRENCY_LIMIT the org is at capacity. Same pre-publish
11
+ * guarantee, from the EnsureLease branch.
12
+ * - 409 EDGE_BUSY — the pod is running another exec. STRICTLY
13
+ * WEAKER guarantee: the request DID reach the pod, which refused it before
14
+ * starting the worker. Its safety rests on that reply being received — a
15
+ * reply lost on the way back surfaces as OUTCOME_UNKNOWN, never as
16
+ * EDGE_BUSY, so "thought busy, actually ran" cannot happen.
13
17
  *
14
- * Everything else propagates on first occurrence especially OUTCOME_UNKNOWN
18
+ * Each code follows its own pace: the server's Retry-After wins when present;
19
+ * without it (older servers) each code falls back to its own default schedule.
20
+ * All three share ONE absolute deadline — the CLI's task boundary — never a
21
+ * per-code sum. On expiry the LAST typed ApiError is rethrown as-is, so the
22
+ * error's code names which class of wait ran out.
23
+ *
24
+ * Everything else propagates on first occurrence — above all OUTCOME_UNKNOWN
15
25
  * (504): the command was dispatched and MAY HAVE EXECUTED; an automatic retry
16
26
  * could post, order or delete twice. That decision belongs to the caller, with
17
27
  * the request id from the error message, never to a loop in here.
18
28
  *
19
- * Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-503
29
+ * Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-error
20
30
  * semantics for every other consumer; only the CLI exec behavior waits.
21
- * Mirrors lib/clip-invoke.ts (the v2 hosted-browser wait) — same budget, same
22
- * schedule, same exact-code matching discipline.
23
31
  */
24
- /** Total activation wait budget. Independent of the clip command --timeout:
25
- * each attempt keeps its own full command timeout, so worst-case wall clock is
26
- * roughly this budget plus one command timeout. */
27
- export const EDGE_ACTIVATION_WAIT_BUDGET_MS = 60_000;
28
- /** Backoff schedule between activation retries; the last entry repeats. */
32
+ /** One absolute wait deadline shared by ALL waitable codes (never per-code).
33
+ * Each attempt keeps its own full command timeout, so worst-case wall clock
34
+ * is roughly this budget plus one command timeout. */
35
+ export const EDGE_EXEC_WAIT_BUDGET_MS = 120_000;
36
+ /** EDGE_ACTIVATING fallback schedule when the server sends no Retry-After
37
+ * (matches the pre-Retry-After deployed behavior); the last entry repeats. */
29
38
  export const EDGE_ACTIVATION_BACKOFF_MS = [500, 1_000, 2_000];
39
+ /** EDGE_BUSY fallback: the in-flight exec finishes within its command timeout,
40
+ * so poll on the order of seconds. */
41
+ export const EDGE_BUSY_FALLBACK_BACKOFF_MS = 5_000;
42
+ /** EDGE_CONCURRENCY_LIMIT fallback: capacity frees only through another
43
+ * lease's full release + teardown — slower and high-variance. */
44
+ export const EDGE_CONCURRENCY_FALLBACK_BACKOFF_MS = 15_000;
30
45
  const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
31
- /**
32
- * The ONLY retryable error: a typed ApiError carrying exactly 503 +
33
- * EDGE_ACTIVATING. Everything else routing errors, EDGE_BUSY, capacity,
34
- * repair, command failures, timeouts, OUTCOME_UNKNOWN, other 5xx — means
35
- * retrying is either pointless or unsafe, so it propagates unchanged on the
36
- * first occurrence.
37
- */
46
+ /** Exact (status, code) pairs — a matching code on the wrong status is a
47
+ * malformed answer and must fail fast, not wait. attempt counts per code so
48
+ * the activation ramp is not consumed by interleaved busy/capacity waits. */
49
+ const WAITABLE = {
50
+ EDGE_ACTIVATING: {
51
+ status: 503,
52
+ fallbackMs: (attempt) => EDGE_ACTIVATION_BACKOFF_MS[Math.min(attempt, EDGE_ACTIVATION_BACKOFF_MS.length - 1)],
53
+ },
54
+ EDGE_CONCURRENCY_LIMIT: { status: 429, fallbackMs: () => EDGE_CONCURRENCY_FALLBACK_BACKOFF_MS },
55
+ EDGE_BUSY: { status: 409, fallbackMs: () => EDGE_BUSY_FALLBACK_BACKOFF_MS },
56
+ };
57
+ /** The waitable refusal this error carries, or null. Only a real SDK ApiError
58
+ * with the exact status+code pairing counts — duck-typed shapes, bare
59
+ * statuses and everything else (routing errors, repair, command failures,
60
+ * timeouts, OUTCOME_UNKNOWN, other 5xx) mean waiting is pointless or unsafe. */
61
+ export function waitableEdgeRefusal(err) {
62
+ if (!(err instanceof ApiError) || !err.code)
63
+ return null;
64
+ const spec = WAITABLE[err.code];
65
+ return spec && err.status === spec.status ? err.code : null;
66
+ }
67
+ /** Back-compat predicate: exactly the cold-start signal. */
38
68
  export function isEdgeActivationPending(err) {
39
- return err instanceof ApiError && err.status === 503 && err.code === 'EDGE_ACTIVATING';
69
+ return waitableEdgeRefusal(err) === 'EDGE_ACTIVATING';
40
70
  }
41
71
  /**
42
- * Execute an Edge clip, transparently absorbing the cloud-profile activation
43
- * window.
72
+ * Execute an Edge clip, waiting (bounded) through activation, busy and
73
+ * capacity refusals.
44
74
  *
45
- * Retries the IDENTICAL request while the server reports EDGE_ACTIVATING,
46
- * backing off 500ms 1s → 2s (capped) until the 60s activation budget is
47
- * spent. The whole loop carries one `correlation_id` (the caller's, or one
48
- * minted here) so the server's exec audit reads the retries as a single
49
- * logical call. Once the budget is spent, the LAST typed ApiError is rethrown
50
- * as-is — status/code intact — so the printed error stays machine-readable.
51
- * Any other failure is rethrown immediately.
75
+ * Retries the IDENTICAL request, pacing each code by its server Retry-After
76
+ * (fallback: the code's own schedule), all under one absolute deadline. The
77
+ * whole loop carries one `correlation_id` (the caller's, or one minted here)
78
+ * so the server's exec audit reads the retries as a single logical call. Once
79
+ * the deadline passes, the LAST typed ApiError is rethrown as-is —
80
+ * status/code intact — so the printed error stays machine-readable and names
81
+ * the class of wait that ran out. Any other failure is rethrown immediately.
52
82
  */
53
- export async function execEdgeClipAwaitingActivation(client, orgId, req, hooks) {
83
+ export async function execEdgeClipWithBoundedWait(client, orgId, req, hooks) {
54
84
  const sleep = hooks?.sleep ?? realSleep;
55
85
  const now = hooks?.now ?? Date.now;
56
86
  const pinned = {
57
87
  ...req,
58
88
  correlation_id: req.correlation_id ?? randomUUID(),
59
89
  };
60
- const deadline = now() + EDGE_ACTIVATION_WAIT_BUDGET_MS;
61
- for (let attempt = 0;; attempt++) {
90
+ const deadline = now() + EDGE_EXEC_WAIT_BUDGET_MS;
91
+ const attempts = {
92
+ EDGE_ACTIVATING: 0,
93
+ EDGE_CONCURRENCY_LIMIT: 0,
94
+ EDGE_BUSY: 0,
95
+ };
96
+ for (;;) {
62
97
  try {
63
98
  return await client.execEdgeClip(orgId, pinned);
64
99
  }
65
100
  catch (err) {
66
- if (!isEdgeActivationPending(err))
101
+ const code = waitableEdgeRefusal(err);
102
+ if (!code)
67
103
  throw err;
68
104
  const remaining = deadline - now();
69
105
  if (remaining <= 0)
70
106
  throw err;
71
- const backoff = EDGE_ACTIVATION_BACKOFF_MS[Math.min(attempt, EDGE_ACTIVATION_BACKOFF_MS.length - 1)];
107
+ const retryAfterSeconds = err.retryAfterSeconds;
108
+ const backoff = typeof retryAfterSeconds === 'number' && retryAfterSeconds > 0
109
+ ? retryAfterSeconds * 1000
110
+ : WAITABLE[code].fallbackMs(attempts[code]);
111
+ attempts[code] += 1;
72
112
  // Clamp to the remaining budget so the final sleep can't overrun it.
73
113
  await sleep(Math.min(backoff, remaining));
74
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/cli",
3
- "version": "1.50.1",
3
+ "version": "1.52.0",
4
4
  "description": "CLI client for Parall — universal agent & human access to Parall API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,14 +36,14 @@
36
36
  "diff": "^8.0.3",
37
37
  "js-yaml": "^4.1.0",
38
38
  "zod": "^4.3.6",
39
- "@parall/agent-core": "1.50.1",
40
- "@parall/sdk": "1.50.1"
39
+ "@parall/sdk": "1.52.0",
40
+ "@parall/agent-core": "1.52.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/js-yaml": "^4.0.9",
44
44
  "@types/node": "^22.0.0",
45
45
  "typescript": "^5.7.0",
46
- "@parall/agent-core": "1.50.1"
46
+ "@parall/agent-core": "1.52.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsc",