@shardflux/sdk 0.15.0 → 0.16.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/CHANGELOG.md +42 -22
- package/README.md +99 -1
- package/dist/account.d.ts +2 -0
- package/dist/account.js +6 -0
- package/dist/cell.d.ts +2 -0
- package/dist/cell.js +2 -0
- package/dist/client.d.ts +25 -1
- package/dist/client.js +44 -3
- package/dist/computer.d.ts +11 -1
- package/dist/computer.js +89 -6
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +9 -0
- package/dist/executions.d.ts +2 -6
- package/dist/executions.js +9 -0
- package/dist/exit-code.d.ts +7 -0
- package/dist/exit-code.js +12 -0
- package/dist/generated/app-api.d.ts +565 -13
- package/dist/generated/cell-api.d.ts +2 -0
- package/dist/http.d.ts +7 -1
- package/dist/http.js +34 -13
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -2
- package/dist/ports.d.ts +7 -0
- package/dist/ports.js +1 -1
- package/dist/progress.d.ts +2 -2
- package/dist/progress.js +1 -1
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.js +585 -0
- package/dist/testing/seed.d.ts +433 -0
- package/dist/testing/seed.js +449 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +4 -2
- package/dist/tunnel-assets/linux-amd64.gz +0 -0
- package/dist/tunnel-assets/linux-arm64.gz +0 -0
- package/dist/tunnel-assets.d.ts +10 -0
- package/dist/tunnel-assets.js +11 -0
- package/dist/tunnel-packet.d.ts +3 -0
- package/dist/tunnel-packet.js +43 -0
- package/dist/tunnel-pty.d.ts +86 -0
- package/dist/tunnel-pty.js +243 -0
- package/dist/tunnels.d.ts +47 -0
- package/dist/tunnels.js +454 -0
- package/dist/workspace-ref.d.ts +4 -0
- package/dist/workspace-ref.js +24 -0
- package/dist/workspace.d.ts +18 -1
- package/dist/workspace.js +76 -3
- package/package.json +7 -2
package/dist/computer.js
CHANGED
|
@@ -6,6 +6,11 @@ export function decodeComputerImage(img) {
|
|
|
6
6
|
/** The workspace desktop (workspace.computer). */
|
|
7
7
|
export class WorkspaceComputer {
|
|
8
8
|
#deps;
|
|
9
|
+
#streams = new Map();
|
|
10
|
+
#minting = new Map();
|
|
11
|
+
#pendingStreams = new Set();
|
|
12
|
+
#stopping;
|
|
13
|
+
#streamGeneration = 0;
|
|
9
14
|
constructor(deps) {
|
|
10
15
|
this.#deps = deps;
|
|
11
16
|
}
|
|
@@ -16,7 +21,7 @@ export class WorkspaceComputer {
|
|
|
16
21
|
act(actions, opts = {}) {
|
|
17
22
|
return this.#deps.cell().computer.act({
|
|
18
23
|
actions,
|
|
19
|
-
...(opts.screenshot !== undefined ? { screenshot: opts.screenshot } : {}),
|
|
24
|
+
...(LOOKS.has(actions.at(-1)?.action ?? '') ? { screenshot: false } : opts.screenshot !== undefined ? { screenshot: opts.screenshot } : {}),
|
|
20
25
|
...(opts.settleMs !== undefined ? { settle_ms: opts.settleMs } : {}),
|
|
21
26
|
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
22
27
|
...(opts.quality !== undefined ? { quality: opts.quality } : {}),
|
|
@@ -47,14 +52,57 @@ export class WorkspaceComputer {
|
|
|
47
52
|
* it expires; `stopStream()` ends every view.
|
|
48
53
|
*/
|
|
49
54
|
async stream(opts = {}) {
|
|
55
|
+
if (opts.embed !== undefined)
|
|
56
|
+
opts = { ...opts, embed: structuredClone(opts.embed) };
|
|
57
|
+
if (this.#stopping)
|
|
58
|
+
await this.#stopping;
|
|
59
|
+
const kind = JSON.stringify({ interactive: opts.interactive ?? false, ttlSeconds: opts.ttlSeconds, embed: opts.embed });
|
|
60
|
+
const cached = this.#streams.get(kind);
|
|
61
|
+
if (!opts.fresh && cached && Date.parse(cached.expiresAt) - Date.now() > 60_000)
|
|
62
|
+
return { ...cached };
|
|
63
|
+
const pending = this.#minting.get(kind);
|
|
64
|
+
if (!opts.fresh && pending)
|
|
65
|
+
return { ...await pending };
|
|
66
|
+
const generation = this.#streamGeneration;
|
|
67
|
+
const mint = this.#stream(opts).then((link) => {
|
|
68
|
+
if (generation === this.#streamGeneration && this.#minting.get(kind) === mint)
|
|
69
|
+
this.#streams.set(kind, link);
|
|
70
|
+
return link;
|
|
71
|
+
});
|
|
72
|
+
this.#minting.set(kind, mint);
|
|
73
|
+
this.#pendingStreams.add(mint);
|
|
74
|
+
try {
|
|
75
|
+
return { ...await mint };
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
this.#pendingStreams.delete(mint);
|
|
79
|
+
if (this.#minting.get(kind) === mint)
|
|
80
|
+
this.#minting.delete(kind);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async #stream(opts) {
|
|
50
84
|
const info = await this.#deps.cell().computer.streamStart({ interactive: opts.interactive ?? false });
|
|
51
85
|
const ports = this.#deps.ports();
|
|
52
86
|
await ports.expose(info.port);
|
|
53
|
-
const link = await ports.link(info.port, { path: info.path, ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}) });
|
|
87
|
+
const link = await ports.link(info.port, { ...(opts.embed !== undefined ? { embed: opts.embed } : {}), path: info.path, ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}) });
|
|
54
88
|
return { url: link.url, expiresAt: link.expiresAt, port: info.port, interactive: info.interactive };
|
|
55
89
|
}
|
|
56
90
|
/** Ends every view and closes the viewer ports (their links stop working). */
|
|
57
|
-
|
|
91
|
+
stopStream() {
|
|
92
|
+
if (this.#stopping)
|
|
93
|
+
return this.#stopping;
|
|
94
|
+
const stop = this.#stopStream();
|
|
95
|
+
this.#stopping = stop;
|
|
96
|
+
void stop.finally(() => { if (this.#stopping === stop)
|
|
97
|
+
this.#stopping = undefined; }).catch(() => undefined);
|
|
98
|
+
return stop;
|
|
99
|
+
}
|
|
100
|
+
async #stopStream() {
|
|
101
|
+
this.#streamGeneration += 1;
|
|
102
|
+
this.#streams.clear();
|
|
103
|
+
const pending = [...this.#pendingStreams];
|
|
104
|
+
this.#minting.clear();
|
|
105
|
+
await Promise.allSettled(pending);
|
|
58
106
|
const ports = this.#deps.ports();
|
|
59
107
|
await Promise.all([ports.close(61002), ports.close(61003)]);
|
|
60
108
|
await this.#deps.cell().computer.streamStop();
|
|
@@ -96,23 +144,24 @@ function actionOf(block) {
|
|
|
96
144
|
* look, a screenshot is attached to the last result, so the model sees the outcome without another round trip. A
|
|
97
145
|
* batch the desktop refuses as invalid (a coordinate outside the screen) is answered with the reason on every call.
|
|
98
146
|
*/
|
|
99
|
-
export function computerToolset(workspace) {
|
|
147
|
+
export function computerToolset(workspace, defaults = {}) {
|
|
100
148
|
return {
|
|
101
149
|
definition: COMPUTER_TOOLSET,
|
|
102
150
|
async run(content, opts = {}) {
|
|
151
|
+
opts = { ...defaults, ...opts };
|
|
103
152
|
const calls = content.filter((b) => b !== null && typeof b === 'object' && b.type === 'tool_use' && b.toolset_name === 'computer');
|
|
104
153
|
if (calls.length === 0)
|
|
105
154
|
return [];
|
|
106
155
|
const lastLooks = LOOKS.has(calls[calls.length - 1].name);
|
|
107
156
|
let r;
|
|
108
157
|
try {
|
|
109
|
-
r = await workspace.computer.act(calls.map(actionOf), {
|
|
158
|
+
r = await runComputerActions((actions, options) => workspace.computer.act(actions, options), calls.map(actionOf), {
|
|
110
159
|
screenshot: !lastLooks,
|
|
111
160
|
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
112
161
|
...(opts.quality !== undefined ? { quality: opts.quality } : {}),
|
|
113
162
|
...(opts.settleMs !== undefined ? { settleMs: opts.settleMs } : {}),
|
|
114
163
|
...(opts.signal !== undefined ? { signal: opts.signal } : {}),
|
|
115
|
-
});
|
|
164
|
+
}, opts.beforeAction);
|
|
116
165
|
}
|
|
117
166
|
catch (err) {
|
|
118
167
|
if (err instanceof ShardfluxApiError && err.code === 'validation_failed') {
|
|
@@ -144,3 +193,37 @@ export function computerToolset(workspace) {
|
|
|
144
193
|
},
|
|
145
194
|
};
|
|
146
195
|
}
|
|
196
|
+
/** No hook: one batch as before. With a hook, check immediately before each individual action runs. */
|
|
197
|
+
export async function runComputerActions(act, actions, opts, beforeAction) {
|
|
198
|
+
if (!beforeAction)
|
|
199
|
+
return act(actions, opts);
|
|
200
|
+
const out = { results: [] };
|
|
201
|
+
for (const [i, action] of actions.entries()) {
|
|
202
|
+
const checked = structuredClone(action);
|
|
203
|
+
let refusal;
|
|
204
|
+
try {
|
|
205
|
+
const decision = await beforeAction(structuredClone(checked));
|
|
206
|
+
if (decision === false || typeof decision === 'string')
|
|
207
|
+
refusal = typeof decision === 'string' && decision ? decision : 'Computer action refused by beforeAction.';
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
refusal = (err instanceof Error ? err.message : String(err)) || 'Computer action refused by beforeAction.';
|
|
211
|
+
}
|
|
212
|
+
if (refusal !== undefined) {
|
|
213
|
+
out.results.push({ action: action.action, ok: false, took_ms: 0, error: { code: 'action_failed', message: refusal } });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const r = await act([checked], { ...opts, screenshot: i === actions.length - 1 && opts.screenshot !== false && !LOOKS.has(action.action) });
|
|
217
|
+
out.results.push(...r.results);
|
|
218
|
+
out.cursor = r.cursor;
|
|
219
|
+
out.display = r.display;
|
|
220
|
+
if (r.screenshot)
|
|
221
|
+
out.screenshot = r.screenshot;
|
|
222
|
+
}
|
|
223
|
+
if (!out.results.at(-1)?.ok) {
|
|
224
|
+
out.results.push(...actions.slice(i + 1).map((a) => ({ action: a.action, ok: false, skipped: true, took_ms: 0 })));
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return out;
|
|
229
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -82,7 +82,7 @@ export type ErrorCode = AppErrorCode | CellErrorCode;
|
|
|
82
82
|
* `retained_state` (details.limit_value and details.current in GiB) when opening a new key or forking with the plan's
|
|
83
83
|
* Retained state used up. `details.limit` is not a reason: these are not in this union.
|
|
84
84
|
*/
|
|
85
|
-
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found' | 'revision_mismatch' | 'edit_not_found' | 'edit_ambiguous' | 'edit_not_text' | 'patch_invalid' | 'host_capacity' | 'wake_failed' | 'workspace_fenced' | 'offline_unavailable' | 'offline_budget' | 'offline_changed' | 'host_feature_unavailable' | 'not_supported_for_mode' | 'mode_mismatch' | 'mode_not_available' | 'layout_unsupported' | 'tree_revision_mismatch' | 'outside_tree_root' | 'execution_in_progress' | 'execution_id_reused' | 'operation_id_reused' | 'no_execution_host' | 'lease_expired' | 'host_unreachable' | 'host_restarted' | 'tree_moved' | 'blob_missing' | 'blob_corrupt' | 'exec_failed_to_start' | 'invalid_cwd' | 'allowance_used' | 'overage_paused' | 'spend_cap_reached' | 'overage_unavailable' | 'spend_cap_required' | 'spend_cap_below_minimum' | 'spend_cap_above_plan_price' | 'spend_cap_below_charges' | 'version_mismatch' | 'allocation_mode_not_available' | 'requires_elastic' | 'exceeds_memory_mib' | 'burst_mode_not_supported' | 'burst_not_supported' | 'burst_size_exceeds_plan' | 'not_available' | 'shared_volumes' | 'fence_not_drained' | 'apply_pending' | 'park_failed' | 'workspace_resumed' | 'interrupted' | 'burst_lost' | 'disk_full' | 'apply_failed' | 'reverted' | 'revert_failed' | 'immutable_path_removed' | 'immutable_paths_unsupported_base' | 'read_only_path' | 'resize_not_available' | 'shrink_not_supported' | 'resize_failed' | 'host_lost' | 'inbound_ports_not_available' | 'port_not_exposed' | 'port_limit';
|
|
85
|
+
export type KnownErrorReason = 'invalid_recipe' | 'base_not_layered' | 'language_unavailable' | 'language_conflict' | 'invalid_package' | 'too_many_files' | 'platform_owned_path' | 'upload_required' | 'upload_missing' | 'upload_digest_mismatch' | 'upload_too_large' | 'extra_hosts_without_auto' | 'invalid_settings' | 'services_unsupported' | 'input_required' | 'input_unknown' | 'input_invalid' | 'egress_widening' | 'reserved_session_id' | 'env_collision' | 'reserved_template_slug' | 'package_index_unavailable' | 'package_not_found' | 'startup_failed' | 'service_not_ready' | 'secrets_unavailable' | 'workspace_not_running' | 'operation_in_progress' | 'workspace_deleted' | 'secret_not_available' | 'legacy_disk_layout' | 'already_current' | 'not_upgradable' | 'upgrade_base_unavailable' | 'upgrade_host_unavailable' | 'upgrade_host_unsupported' | 'upgrade_template_mismatch' | 'upgrade_runtime_mismatch' | 'checkpoint_template_mismatch' | 'not_session' | 'session_lifetime' | 'lifetime_mismatch' | 'not_resettable' | 'template_not_layered' | 'draft_exists' | 'draft_stale' | 'build_in_progress' | 'file_list_unavailable' | 'file_list_indexing' | 'guest_feature_unavailable' | 'confirm_destructive_required' | 'reserved_key_prefix' | 'invalid_defaults' | 'invalid_path' | 'too_many_acknowledged_findings' | 'template_dev_mode_role' | 'draft_not_found' | 'version_not_found' | 'path_not_found' | 'revision_mismatch' | 'edit_not_found' | 'edit_ambiguous' | 'edit_not_text' | 'patch_invalid' | 'host_capacity' | 'wake_failed' | 'workspace_fenced' | 'offline_unavailable' | 'offline_budget' | 'offline_changed' | 'host_feature_unavailable' | 'not_supported_for_mode' | 'mode_mismatch' | 'mode_not_available' | 'layout_unsupported' | 'tree_revision_mismatch' | 'outside_tree_root' | 'execution_in_progress' | 'execution_id_reused' | 'operation_id_reused' | 'no_execution_host' | 'lease_expired' | 'host_unreachable' | 'host_restarted' | 'tree_moved' | 'blob_missing' | 'blob_corrupt' | 'exec_failed_to_start' | 'invalid_cwd' | 'allowance_used' | 'overage_paused' | 'spend_cap_reached' | 'overage_unavailable' | 'spend_cap_required' | 'spend_cap_below_minimum' | 'spend_cap_above_plan_price' | 'spend_cap_below_charges' | 'version_mismatch' | 'allocation_mode_not_available' | 'requires_elastic' | 'exceeds_memory_mib' | 'burst_mode_not_supported' | 'burst_not_supported' | 'burst_size_exceeds_plan' | 'not_available' | 'shared_volumes' | 'fence_not_drained' | 'apply_pending' | 'park_failed' | 'workspace_resumed' | 'interrupted' | 'burst_lost' | 'disk_full' | 'apply_failed' | 'reverted' | 'revert_failed' | 'immutable_path_removed' | 'immutable_paths_unsupported_base' | 'read_only_path' | 'resize_not_available' | 'shrink_not_supported' | 'resize_failed' | 'host_lost' | 'inbound_ports_not_available' | 'port_not_exposed' | 'port_limit';
|
|
86
86
|
/** A known reason, or any other string the server sends (reasons are open-ended). */
|
|
87
87
|
export type ErrorReason = KnownErrorReason | (string & {});
|
|
88
88
|
export interface ErrorBodyLike {
|
|
@@ -117,6 +117,10 @@ export declare class ShardfluxApiError extends Error {
|
|
|
117
117
|
readonly treeRevision: number | undefined;
|
|
118
118
|
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number);
|
|
119
119
|
}
|
|
120
|
+
/** A cell file operation found that the requested path does not exist. */
|
|
121
|
+
export declare class FileNotFoundError extends ShardfluxApiError {
|
|
122
|
+
constructor(status: number, body: ErrorBodyLike, source: 'api' | 'cell', retryAfterSeconds?: number, treeRevision?: number);
|
|
123
|
+
}
|
|
120
124
|
/**
|
|
121
125
|
* The cell gateway's working-at-once refusal (0.14.0+): 429 `quota_exceeded`, retryable, with Retry-After. The gateway
|
|
122
126
|
* refuses the call at admission, before the workspace is touched, so nothing ran and any request (exec start, stdin,
|
package/dist/errors.js
CHANGED
|
@@ -40,6 +40,13 @@ export class ShardfluxApiError extends Error {
|
|
|
40
40
|
this.reason = typeof reason === 'string' ? reason : undefined;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/** A cell file operation found that the requested path does not exist. */
|
|
44
|
+
export class FileNotFoundError extends ShardfluxApiError {
|
|
45
|
+
constructor(status, body, source, retryAfterSeconds, treeRevision) {
|
|
46
|
+
super(status, body, source, retryAfterSeconds, treeRevision);
|
|
47
|
+
this.name = 'FileNotFoundError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
/**
|
|
44
51
|
* The cell gateway's working-at-once refusal (0.14.0+): 429 `quota_exceeded`, retryable, with Retry-After. The gateway
|
|
45
52
|
* refuses the call at admission, before the workspace is touched, so nothing ran and any request (exec start, stdin,
|
|
@@ -126,6 +133,8 @@ export class ExecStartError extends ShardfluxApiError {
|
|
|
126
133
|
* here, so `instanceof` works whichever call was refused.
|
|
127
134
|
*/
|
|
128
135
|
export function apiError(status, body, source, retryAfterSeconds, treeRevision) {
|
|
136
|
+
if (source === 'cell' && status === 404 && body.error.code === 'not_found' && body.error.details?.reason === 'path_not_found')
|
|
137
|
+
return new FileNotFoundError(status, body, source, retryAfterSeconds, treeRevision);
|
|
129
138
|
switch (body.error.details?.reason) {
|
|
130
139
|
case 'not_supported_for_mode':
|
|
131
140
|
return new NotSupportedForModeError(status, body, source, retryAfterSeconds, treeRevision);
|
package/dist/executions.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executions of file-first workspaces: each `POST /exec` runs one command in a fresh VM on the
|
|
3
|
-
* workspace's latest tree revision and answers when it ended; the files it changed under /home/user become the next
|
|
4
|
-
* revision. The execution id is the idempotency key: a request retried with the same id returns the recorded result
|
|
5
|
-
* (or waits for the running execution) and never runs the command a second time.
|
|
6
|
-
*/
|
|
7
1
|
import type { components } from './generated/cell-api.js';
|
|
8
2
|
type S = components['schemas'];
|
|
9
3
|
/** The cell's `ExecutionResult` as sent on the wire (stdout/stderr base64). */
|
|
@@ -105,6 +99,8 @@ export declare class ExecutionResult {
|
|
|
105
99
|
readonly replayed: boolean;
|
|
106
100
|
/** The body as the cell sent it. */
|
|
107
101
|
readonly raw: ExecutionResultBody;
|
|
102
|
+
/** POSIX status of the command. */
|
|
103
|
+
get exitCodePosix(): number;
|
|
108
104
|
constructor(body: ExecutionResultBody, replayed: boolean);
|
|
109
105
|
/** Still queued or running (only `executions.get()` returns such a result). */
|
|
110
106
|
get pending(): boolean;
|
package/dist/executions.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executions of file-first workspaces: each `POST /exec` runs one command in a fresh VM on the
|
|
3
|
+
* workspace's latest tree revision and answers when it ended; the files it changed under /home/user become the next
|
|
4
|
+
* revision. The execution id is the idempotency key: a request retried with the same id returns the recorded result
|
|
5
|
+
* (or waits for the running execution) and never runs the command a second time.
|
|
6
|
+
*/
|
|
7
|
+
import { exitCodePosix } from "./exit-code.js";
|
|
1
8
|
/** `^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$` (cell-api.yaml ExecutionIdValue). */
|
|
2
9
|
export const EXECUTION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
|
|
3
10
|
/** A fresh execution id: `ex-` plus a random UUID (39 characters, valid per EXECUTION_ID). */
|
|
@@ -43,6 +50,8 @@ export class ExecutionResult {
|
|
|
43
50
|
replayed;
|
|
44
51
|
/** The body as the cell sent it. */
|
|
45
52
|
raw;
|
|
53
|
+
/** POSIX status of the command. */
|
|
54
|
+
get exitCodePosix() { return exitCodePosix(this); }
|
|
46
55
|
constructor(body, replayed) {
|
|
47
56
|
this.raw = body;
|
|
48
57
|
this.executionId = body.execution_id;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The command's POSIX status, shared by exec results and the CLI. */
|
|
2
|
+
export function exitCodePosix(result) {
|
|
3
|
+
if (result.timedOut)
|
|
4
|
+
return 124;
|
|
5
|
+
if (result.canceled)
|
|
6
|
+
return 130;
|
|
7
|
+
if (result.exitCode !== null && result.exitCode >= 0)
|
|
8
|
+
return result.exitCode;
|
|
9
|
+
if (result.termSignal !== null && result.termSignal > 0)
|
|
10
|
+
return 128 + result.termSignal;
|
|
11
|
+
return 1;
|
|
12
|
+
}
|