@parall/cli 1.46.0 → 1.48.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.
- package/dist/commands/clip.d.ts.map +1 -1
- package/dist/commands/clip.js +62 -1
- package/dist/commands/slack.d.ts.map +1 -1
- package/dist/commands/slack.js +97 -1
- package/dist/lib/edge-exec.d.ts +55 -0
- package/dist/lib/edge-exec.d.ts.map +1 -0
- package/dist/lib/edge-exec.js +76 -0
- package/package.json +4 -4
|
@@ -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;
|
|
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,QA4OpD"}
|
package/dist/commands/clip.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveCredentials } from '../lib/client.js';
|
|
2
2
|
import { invokeClipAwaitingActivation } from '../lib/clip-invoke.js';
|
|
3
|
-
import {
|
|
3
|
+
import { execEdgeClipAwaitingActivation } from '../lib/edge-exec.js';
|
|
4
|
+
import { printJson, printError, parsePositiveInt } from '../lib/output.js';
|
|
4
5
|
/**
|
|
5
6
|
* Derive a CLI-friendly alias from a registry source string.
|
|
6
7
|
* Handles scoped packages ("@scope/name@1.0.0" → "name") and
|
|
@@ -159,6 +160,66 @@ export function registerClipCommands(program) {
|
|
|
159
160
|
printError(err);
|
|
160
161
|
}
|
|
161
162
|
});
|
|
163
|
+
clip
|
|
164
|
+
.command('exec')
|
|
165
|
+
.description('Execute a registry (Edge) clip command on an Edge device. A cloud (hosted) profile is reachable ONLY via --connection; with neither --connection nor --edge, the server resolves just your OWN online desktop device (legacy BYOC fallback — never a cloud profile)')
|
|
166
|
+
.argument('<clip>', 'Clip name in the org clip registry')
|
|
167
|
+
.argument('<command>', 'Command name to execute')
|
|
168
|
+
.argument('[args]', 'Command arguments (JSON string or plain text)')
|
|
169
|
+
.option('--connection <ref>', 'Clip connection id (ccn_…) or alias. REQUIRED to reach a cloud (hosted) profile — the binding its maintainer created is the authorization')
|
|
170
|
+
.option('--edge <edgeId>', 'A desktop (BYOC) device you own. Mutually exclusive with --connection')
|
|
171
|
+
.option('--profile <name>', 'Browser profile (with --connection it may only restate the granted one)')
|
|
172
|
+
.option('--timeout <ms>', 'Execution timeout in milliseconds', '30000')
|
|
173
|
+
.action(async (clipName, command, args, opts) => {
|
|
174
|
+
try {
|
|
175
|
+
const { client, orgId } = resolveCredentials();
|
|
176
|
+
let parsedArgs;
|
|
177
|
+
if (args !== undefined) {
|
|
178
|
+
try {
|
|
179
|
+
parsedArgs = JSON.parse(args);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
parsedArgs = args;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Strict grammar, validated BEFORE the wire: parseInt would accept
|
|
186
|
+
// "5000ms" or "1.5", and the server silently replaces an out-of-range
|
|
187
|
+
// timeout with its 30s default — so a malformed flag would run with a
|
|
188
|
+
// deadline the caller never asked for. printError never returns.
|
|
189
|
+
let timeoutMs;
|
|
190
|
+
if (opts?.timeout) {
|
|
191
|
+
timeoutMs = parsePositiveInt(opts.timeout);
|
|
192
|
+
if (timeoutMs === undefined || timeoutMs > 120_000) {
|
|
193
|
+
printError(new Error('--timeout must be an integer between 1 and 120000 (milliseconds)'));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const req = {
|
|
197
|
+
clip: clipName,
|
|
198
|
+
command,
|
|
199
|
+
args: parsedArgs,
|
|
200
|
+
connection: opts?.connection,
|
|
201
|
+
edge_id: opts?.edge,
|
|
202
|
+
profile: opts?.profile,
|
|
203
|
+
timeout: timeoutMs,
|
|
204
|
+
};
|
|
205
|
+
// A cold cloud profile answers 503 EDGE_ACTIVATING BEFORE anything is
|
|
206
|
+
// dispatched; absorb that one retryable window here (bounded, one
|
|
207
|
+
// correlation id) instead of making every agent hand-roll a loop.
|
|
208
|
+
// Everything else — OUTCOME_UNKNOWN above all — propagates untouched:
|
|
209
|
+
// a dispatched command may have executed, and retrying it is not this
|
|
210
|
+
// tool's call. See lib/edge-exec.ts.
|
|
211
|
+
const result = await execEdgeClipAwaitingActivation(client, orgId, req);
|
|
212
|
+
if (result.success) {
|
|
213
|
+
printJson(result.data ?? null);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
printError(new Error(result.error ?? 'execution failed'));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
printError(err);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
162
223
|
clip
|
|
163
224
|
.command('remove')
|
|
164
225
|
.description('Remove a clip by alias')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/commands/slack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/commands/slack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QAgIrD"}
|
package/dist/commands/slack.js
CHANGED
|
@@ -16,7 +16,7 @@ export function registerSlackCommands(program) {
|
|
|
16
16
|
.description('Send a message to a Slack conversation this connection has seen inbound')
|
|
17
17
|
.requiredOption('--channel <channelId>', 'Vendor-native conversation id from the inbound event (C…/G…/D…)')
|
|
18
18
|
.requiredOption('--text <text>', 'Message text (plain text)')
|
|
19
|
-
.option('--reply-to <messageId>', 'Inbound message id you are answering ({channel}:{ts}); REQUIRED in channels (reply lands in its thread)
|
|
19
|
+
.option('--reply-to <messageId>', 'Inbound message id you are answering ({channel}:{ts}, thread children {channel}:{root}#{ts}); REQUIRED in channels (reply lands in its thread). In DMs pass it too: a thread-child id lands the reply in that thread, a bare id keeps the main flow — the session never forks either way')
|
|
20
20
|
.action(async (opts) => {
|
|
21
21
|
try {
|
|
22
22
|
const { client, orgId } = resolveCredentials();
|
|
@@ -32,4 +32,100 @@ export function registerSlackCommands(program) {
|
|
|
32
32
|
printError(err);
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
|
+
// Read verbs: workspace visibility as the bot sees it. Authorization is
|
|
36
|
+
// the bot's own Slack permissions (invite it to a channel to read that
|
|
37
|
+
// channel's history) — reads are not limited to seen conversations.
|
|
38
|
+
// Fail fast on a malformed --limit instead of silently falling back to
|
|
39
|
+
// the server default page size.
|
|
40
|
+
const page = (opts) => {
|
|
41
|
+
let limit;
|
|
42
|
+
if (opts.limit !== undefined) {
|
|
43
|
+
limit = Number(opts.limit);
|
|
44
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
45
|
+
throw new Error('--limit must be an integer between 1 and 200');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
...(opts.cursor ? { cursor: opts.cursor } : {}),
|
|
50
|
+
...(limit !== undefined ? { limit } : {}),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
slack
|
|
54
|
+
.command('channels')
|
|
55
|
+
.description('List workspace channels as the bot sees them (is_member marks joined ones)')
|
|
56
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous page')
|
|
57
|
+
.option('--limit <n>', 'Page size (max 200)')
|
|
58
|
+
.action(async (opts) => {
|
|
59
|
+
try {
|
|
60
|
+
const { client, orgId } = resolveCredentials();
|
|
61
|
+
printJson(await client.listSlackChannels(orgId, page(opts)));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
printError(err);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
slack
|
|
68
|
+
.command('users')
|
|
69
|
+
.description('List workspace members')
|
|
70
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous page')
|
|
71
|
+
.option('--limit <n>', 'Page size (max 200)')
|
|
72
|
+
.action(async (opts) => {
|
|
73
|
+
try {
|
|
74
|
+
const { client, orgId } = resolveCredentials();
|
|
75
|
+
printJson(await client.listSlackUsers(orgId, page(opts)));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
printError(err);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
slack
|
|
82
|
+
.command('history')
|
|
83
|
+
.description('Read a conversation message history (bot must be able to see it)')
|
|
84
|
+
.requiredOption('--conversation <id>', 'Vendor-native conversation id (C…/G…/D…)')
|
|
85
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous page')
|
|
86
|
+
.option('--limit <n>', 'Page size (max 200)')
|
|
87
|
+
.action(async (opts) => {
|
|
88
|
+
try {
|
|
89
|
+
const { client, orgId } = resolveCredentials();
|
|
90
|
+
printJson(await client.slackHistory(orgId, opts.conversation, page(opts)));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
printError(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
slack
|
|
97
|
+
.command('status')
|
|
98
|
+
.description('Set/clear the Agents-pane typing indicator on an assistant thread (cosmetic)')
|
|
99
|
+
.requiredOption('--conversation <id>', 'DM conversation id (D…)')
|
|
100
|
+
.requiredOption('--thread <rootTs>', "The assistant thread's root ts")
|
|
101
|
+
.option('--text <status>', 'Status text (omit to clear)', '')
|
|
102
|
+
.action(async (opts) => {
|
|
103
|
+
try {
|
|
104
|
+
const { client, orgId } = resolveCredentials();
|
|
105
|
+
await client.setSlackStatus(orgId, {
|
|
106
|
+
conversation: opts.conversation,
|
|
107
|
+
thread_ts: opts.thread,
|
|
108
|
+
status: opts.text ?? '',
|
|
109
|
+
});
|
|
110
|
+
printJson({ ok: true });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
printError(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
slack
|
|
117
|
+
.command('members')
|
|
118
|
+
.description('List a conversation member ids (resolve names via `parall slack users`)')
|
|
119
|
+
.requiredOption('--conversation <id>', 'Vendor-native conversation id (C…/G…)')
|
|
120
|
+
.option('--cursor <cursor>', 'Pagination cursor from a previous page')
|
|
121
|
+
.option('--limit <n>', 'Page size (max 200)')
|
|
122
|
+
.action(async (opts) => {
|
|
123
|
+
try {
|
|
124
|
+
const { client, orgId } = resolveCredentials();
|
|
125
|
+
printJson(await client.slackMembers(orgId, opts.conversation, page(opts)));
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
printError(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
35
131
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { EdgeClipExecResult, ExecEdgeClipRequest, ParallClient } from '@parall/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Cloud (hosted) Edge activation wait — CLI-side bounded retry for `clip exec`.
|
|
4
|
+
*
|
|
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.
|
|
12
|
+
*
|
|
13
|
+
* Everything else propagates on first occurrence — especially OUTCOME_UNKNOWN
|
|
14
|
+
* (504): the command was dispatched and MAY HAVE EXECUTED; an automatic retry
|
|
15
|
+
* could post, order or delete twice. That decision belongs to the caller, with
|
|
16
|
+
* the request id from the error message, never to a loop in here.
|
|
17
|
+
*
|
|
18
|
+
* Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-503
|
|
19
|
+
* 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
|
+
*/
|
|
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. */
|
|
28
|
+
export declare const EDGE_ACTIVATION_BACKOFF_MS: number[];
|
|
29
|
+
/** Injectable time hooks so tests never really sleep. */
|
|
30
|
+
export type EdgeActivationWaitHooks = {
|
|
31
|
+
sleep?: (ms: number) => Promise<void>;
|
|
32
|
+
now?: () => number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The ONLY retryable error: a typed ApiError carrying exactly 503 +
|
|
36
|
+
* EDGE_ACTIVATING. Everything else — routing 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
|
+
*/
|
|
41
|
+
export declare function isEdgeActivationPending(err: unknown): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Execute an Edge clip, transparently absorbing the cloud-profile activation
|
|
44
|
+
* window.
|
|
45
|
+
*
|
|
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.
|
|
53
|
+
*/
|
|
54
|
+
export declare function execEdgeClipAwaitingActivation(client: Pick<ParallClient, 'execEdgeClip'>, orgId: string, req: ExecEdgeClipRequest, hooks?: EdgeActivationWaitHooks): Promise<EdgeClipExecResult>;
|
|
55
|
+
//# sourceMappingURL=edge-exec.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ApiError } from '@parall/sdk';
|
|
3
|
+
/**
|
|
4
|
+
* Cloud (hosted) Edge activation wait — CLI-side bounded retry for `clip exec`.
|
|
5
|
+
*
|
|
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.
|
|
13
|
+
*
|
|
14
|
+
* Everything else propagates on first occurrence — especially OUTCOME_UNKNOWN
|
|
15
|
+
* (504): the command was dispatched and MAY HAVE EXECUTED; an automatic retry
|
|
16
|
+
* could post, order or delete twice. That decision belongs to the caller, with
|
|
17
|
+
* the request id from the error message, never to a loop in here.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately NOT in the SDK: `execEdgeClip()` keeps its throw-on-503
|
|
20
|
+
* 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
|
+
*/
|
|
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. */
|
|
29
|
+
export const EDGE_ACTIVATION_BACKOFF_MS = [500, 1_000, 2_000];
|
|
30
|
+
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
|
+
*/
|
|
38
|
+
export function isEdgeActivationPending(err) {
|
|
39
|
+
return err instanceof ApiError && err.status === 503 && err.code === 'EDGE_ACTIVATING';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Execute an Edge clip, transparently absorbing the cloud-profile activation
|
|
43
|
+
* window.
|
|
44
|
+
*
|
|
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.
|
|
52
|
+
*/
|
|
53
|
+
export async function execEdgeClipAwaitingActivation(client, orgId, req, hooks) {
|
|
54
|
+
const sleep = hooks?.sleep ?? realSleep;
|
|
55
|
+
const now = hooks?.now ?? Date.now;
|
|
56
|
+
const pinned = {
|
|
57
|
+
...req,
|
|
58
|
+
correlation_id: req.correlation_id ?? randomUUID(),
|
|
59
|
+
};
|
|
60
|
+
const deadline = now() + EDGE_ACTIVATION_WAIT_BUDGET_MS;
|
|
61
|
+
for (let attempt = 0;; attempt++) {
|
|
62
|
+
try {
|
|
63
|
+
return await client.execEdgeClip(orgId, pinned);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (!isEdgeActivationPending(err))
|
|
67
|
+
throw err;
|
|
68
|
+
const remaining = deadline - now();
|
|
69
|
+
if (remaining <= 0)
|
|
70
|
+
throw err;
|
|
71
|
+
const backoff = EDGE_ACTIVATION_BACKOFF_MS[Math.min(attempt, EDGE_ACTIVATION_BACKOFF_MS.length - 1)];
|
|
72
|
+
// Clamp to the remaining budget so the final sleep can't overrun it.
|
|
73
|
+
await sleep(Math.min(backoff, remaining));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.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.
|
|
40
|
-
"@parall/sdk": "1.
|
|
39
|
+
"@parall/agent-core": "1.48.0",
|
|
40
|
+
"@parall/sdk": "1.48.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.
|
|
46
|
+
"@parall/agent-core": "1.48.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc",
|