@shardflux/cli 0.8.0 → 0.8.1
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 +37 -1
- package/README.md +12 -6
- package/dist/args.d.ts +1 -1
- package/dist/args.js +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +1 -0
- package/dist/format.js +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.js +23 -0
- package/dist/timing.d.ts +14 -2
- package/dist/timing.js +37 -4
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,43 @@
|
|
|
3
3
|
Every command and option the README shows is available from the version named here. Breaking changes ship in minor
|
|
4
4
|
releases and are marked **Breaking**.
|
|
5
5
|
|
|
6
|
-
## 0.
|
|
6
|
+
## 0.9.0 (not yet published)
|
|
7
|
+
|
|
8
|
+
Needs `@shardflux/sdk` 0.14.0 or later.
|
|
9
|
+
|
|
10
|
+
### Workspaces working at once
|
|
11
|
+
|
|
12
|
+
- A command whose workspace call finds every workspace the plan allows to work at once busy is sent again after
|
|
13
|
+
`Retry-After` (the SDK's retries; the call did not run). A refusal that lasts exits 1 with 429 `quota_exceeded`
|
|
14
|
+
(`details.limit` `concurrent_workspaces`) and the hint `every workspace your plan allows to work at once is busy
|
|
15
|
+
and the call did not run (it was already retried); retry in 2 s, or upgrade: shard billing plans`.
|
|
16
|
+
- The API's 403 `quota_exceeded` with `details.limit` `retained_state` (opening a new key or forking while the
|
|
17
|
+
plan's Retained state is used up) hints `delete workspaces you no longer need (shard ws delete <key> --yes), or
|
|
18
|
+
upgrade: shard billing plans`. Neither suggests `shard feedback`.
|
|
19
|
+
- `--json` errors carry `retry_after_seconds` when the refusal has `Retry-After` (e.g. 429 `quota_exceeded`, 429
|
|
20
|
+
`rate_limited`).
|
|
21
|
+
- `shard usage` says `retained state used up: new workspaces and forks are refused (...)` when the Retained state
|
|
22
|
+
allowance's `cap_state` is `storage_blocked` (`enforcement` `storage_block`).
|
|
23
|
+
|
|
24
|
+
## 0.8.1 (not yet published)
|
|
25
|
+
|
|
26
|
+
Needs `@shardflux/sdk` 0.13.1 or later.
|
|
27
|
+
|
|
28
|
+
### Workspaces recover by themselves
|
|
29
|
+
|
|
30
|
+
When the machine a workspace runs on fails, the workspace is suspended, and the next command on it resumes it.
|
|
31
|
+
|
|
32
|
+
- A resume that booted the workspace's own disk (`cold_boot_reason` `host_lost`) prints the cold-boot note with the
|
|
33
|
+
reason in words: `note: workspace <id> resumed from its saved disk; running processes were restarted (the machine it
|
|
34
|
+
ran on failed)`.
|
|
35
|
+
- A resume that restored its newest checkpoint prints `note: workspace <id> resumed from checkpoint <id> (state as of
|
|
36
|
+
<time>) after the machine it ran on failed; changes after that time are not in it` on stderr (also with `--json`,
|
|
37
|
+
`ws resume --wait`, `operations wait` and a command's wake).
|
|
38
|
+
- `--timing --json`: `server.host_lost` (`detected_at`, `restored_from`, `restored_checkpoint_id`, `state_as_of`).
|
|
39
|
+
`--timing` prints the SDK's `restored checkpoint <id> (host_lost; state as of <time>)`.
|
|
40
|
+
- `hostLostNotice()` is exported next to `coldBootNotice()`.
|
|
41
|
+
|
|
42
|
+
## 0.8.0
|
|
7
43
|
|
|
8
44
|
Needs `@shardflux/sdk` 0.13.0 or later.
|
|
9
45
|
|
package/README.md
CHANGED
|
@@ -607,11 +607,11 @@ without waking it (the state at suspension); `files search` then notes it on std
|
|
|
607
607
|
refused at once (exit 1, `workspace_not_running`).
|
|
608
608
|
|
|
609
609
|
**Detecting a cold resume (0.6.0+).** A resume restores memory and running processes. After a platform runtime
|
|
610
|
-
update,
|
|
611
|
-
workspace; check `memory_restored`. The command still runs
|
|
612
|
-
fresh, as after a reboot: start dev servers and background jobs again. `shard`
|
|
613
|
-
the output (also with `--json`, except on a failure, whose stderr is only the
|
|
614
|
-
pass `--timing`:
|
|
610
|
+
update, or after the machine the workspace ran on failed (0.8.1+), a resume can boot from the saved disk instead of
|
|
611
|
+
restoring memory (a cold boot), also when a command wakes the workspace; check `memory_restored`. The command still runs
|
|
612
|
+
with the files kept, and processes start fresh, as after a reboot: start dev servers and background jobs again. `shard`
|
|
613
|
+
then prints one line on stderr after the output (also with `--json`, except on a failure, whose stderr is only the
|
|
614
|
+
error document), whether or not you pass `--timing`:
|
|
615
615
|
|
|
616
616
|
```text
|
|
617
617
|
note: workspace 01a0e5a8-3edd-74ba-b489-d62b8925e342 resumed from its saved disk; running processes were restarted (runtime_changed)
|
|
@@ -621,6 +621,12 @@ note: workspace 01a0e5a8-3edd-74ba-b489-d62b8925e342 resumed from its saved disk
|
|
|
621
621
|
`cold_boot`, `memory_restored` false, `cold_boot_reason`), and `--timing` shows `resume from cold_boot: processes
|
|
622
622
|
restarted (runtime_changed)`. The same applies to `operations wait` of such a resume.
|
|
623
623
|
|
|
624
|
+
**Workspaces recover by themselves (0.8.1+).** When the machine a workspace runs on fails, the workspace is suspended,
|
|
625
|
+
and the next command on it resumes it; there is nothing extra to run. From its own disk, the note above ends in
|
|
626
|
+
`(the machine it ran on failed)`; from its newest checkpoint, the note names the checkpoint and the time its state is
|
|
627
|
+
from (`state as of <time>`). `--json` carries `result.host_lost` (`detected_at`, `restored_from` `disk` or
|
|
628
|
+
`checkpoint`, `restored_checkpoint_id`, `state_as_of`), and `--timing` adds `host_lost` to `server`.
|
|
629
|
+
|
|
624
630
|
## Suspend when idle (0.5.1+)
|
|
625
631
|
|
|
626
632
|
A running workspace is billed while it is awake. When an agent's turn ends, ask for a suspend once the workspace has
|
|
@@ -778,7 +784,7 @@ With `--json`, stdout carries the timing as a `timing` field of the command's JS
|
|
|
778
784
|
`workspace_id`, `operation_id`, `total_ms`, `phases` (`phase`, `reason`, `operation_id`, `start_ms`, `duration_ms`),
|
|
779
785
|
`retries`, `server` (`queued_ms`, `run_ms`, `total_ms`, `start_path`, `resume_path`, `memory_restored` and
|
|
780
786
|
`cold_boot_reason` (0.6.0+; `memory_restored` is `false` when a resume booted the saved disk, `null` when the API did
|
|
781
|
-
not say), `boot_to_ready_ms`, ...) and `outside_server_ms`. It is `null` when the command made no lifecycle call. On a failure the error document on stderr
|
|
787
|
+
not say), `host_lost` (0.8.1+), `boot_to_ready_ms`, ...) and `outside_server_ms`. It is `null` when the command made no lifecycle call. On a failure the error document on stderr
|
|
782
788
|
carries it: `{"error": {...}, "timing": {...}}`.
|
|
783
789
|
|
|
784
790
|
For live progress events, use the SDK's `onProgress`.
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { parseArgs } from 'node:util';
|
|
13
13
|
import { ACCOUNT_COMMANDS } from "./account-args.js";
|
|
14
14
|
import { UsageError } from "./errors.js";
|
|
15
|
-
export const CLI_VERSION = '0.8.
|
|
15
|
+
export const CLI_VERSION = '0.8.1';
|
|
16
16
|
export const GLOBAL_OPTIONS = {
|
|
17
17
|
'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
|
|
18
18
|
json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
|
package/dist/errors.d.ts
CHANGED
|
@@ -98,6 +98,8 @@ export interface ErrorInfo {
|
|
|
98
98
|
operation?: unknown;
|
|
99
99
|
/** A file-first execution the error is about (failed, lost, still running, or left running by Ctrl-C). */
|
|
100
100
|
execution_id?: string;
|
|
101
|
+
/** The refusal's Retry-After in seconds (0.9.0+), e.g. 429 quota_exceeded from the cell gateway. */
|
|
102
|
+
retry_after_seconds?: number;
|
|
101
103
|
}
|
|
102
104
|
/**
|
|
103
105
|
* Removes anything shaped like a Shardflux API key or user session token from text, and each of the given secrets
|
package/dist/errors.js
CHANGED
|
@@ -147,6 +147,7 @@ export function describeError(err) {
|
|
|
147
147
|
...(err.operationId ? { operation_id: err.operationId } : {}),
|
|
148
148
|
...(err.details ? { details: err.details } : {}),
|
|
149
149
|
source: err.source,
|
|
150
|
+
...(err.retryAfterSeconds !== undefined ? { retry_after_seconds: err.retryAfterSeconds } : {}),
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
if (err instanceof OperationTimeoutError) {
|
package/dist/format.js
CHANGED
|
@@ -396,6 +396,10 @@ export function usageText(summary) {
|
|
|
396
396
|
const trialEnded = isObject(s.plan) && s.plan.plan_key === 'trial';
|
|
397
397
|
head.push(['allowance exhausted', s.allowance_exhausted ? (trialEnded ? `yes (${reason}the trial ends; the organization is back on free)` : `yes (${reason}new opens/resumes are refused)`) : 'no']);
|
|
398
398
|
}
|
|
399
|
+
// cap_state storage_blocked (0.9.0): the Retained state allowance is used up; existing workspaces keep working.
|
|
400
|
+
if (Array.isArray(s.allowances) && s.allowances.some((a) => isObject(a) && a.cap_state === 'storage_blocked')) {
|
|
401
|
+
head.push(['retained state', 'used up: new workspaces and forks are refused (delete workspaces you no longer need, or upgrade)']);
|
|
402
|
+
}
|
|
399
403
|
for (const [k, v] of Object.entries(s))
|
|
400
404
|
if (!USAGE_KNOWN.has(k) && (typeof v !== 'object' || v === null))
|
|
401
405
|
head.push([k.replace(/_/g, ' '), scalar(v)]);
|
package/dist/index.d.ts
CHANGED
|
@@ -22,4 +22,4 @@ export { openUrl, openerFor } from './browser.js';
|
|
|
22
22
|
export { EXIT, AuthConfigError, CheckoutEndedError, ExecutionFailedError, ExecutionPendingError, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError, exitCodeFor, redact } from './errors.js';
|
|
23
23
|
export type { ErrorInfo } from './errors.js';
|
|
24
24
|
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from './format.js';
|
|
25
|
-
export { TimingRecorder, coldBootNotice, lostSuspendNotice, notices, timingJson, timingText } from './timing.js';
|
|
25
|
+
export { TimingRecorder, coldBootNotice, hostLostNotice, lostSuspendNotice, notices, timingJson, timingText } from './timing.js';
|
package/dist/index.js
CHANGED
|
@@ -14,4 +14,4 @@ export { CHECK_INTERVAL_MS, CLI_DISTRIBUTION, NOTICE_WAIT_MS, UPDATE_CHECK_FILE,
|
|
|
14
14
|
export { openUrl, openerFor } from "./browser.js";
|
|
15
15
|
export { EXIT, AuthConfigError, CheckoutEndedError, ExecutionFailedError, ExecutionPendingError, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError, exitCodeFor, redact } from "./errors.js";
|
|
16
16
|
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from "./format.js";
|
|
17
|
-
export { TimingRecorder, coldBootNotice, lostSuspendNotice, notices, timingJson, timingText } from "./timing.js";
|
|
17
|
+
export { TimingRecorder, coldBootNotice, hostLostNotice, lostSuspendNotice, notices, timingJson, timingText } from "./timing.js";
|
package/dist/main.js
CHANGED
|
@@ -76,6 +76,11 @@ function hintFor(info, cred, file) {
|
|
|
76
76
|
return 'this API has no billing configured: plans cannot be bought here';
|
|
77
77
|
if (info.code === 'allowance_exhausted')
|
|
78
78
|
return allowanceHint(info);
|
|
79
|
+
if (info.code === 'quota_exceeded') {
|
|
80
|
+
const quota = quotaHint(info);
|
|
81
|
+
if (quota)
|
|
82
|
+
return quota;
|
|
83
|
+
}
|
|
79
84
|
const policyHint = spendPolicyHint(info);
|
|
80
85
|
if (policyHint)
|
|
81
86
|
return policyHint;
|
|
@@ -135,6 +140,21 @@ function allowanceHint(info) {
|
|
|
135
140
|
return undefined;
|
|
136
141
|
}
|
|
137
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* quota_exceeded (0.9.0): the cell gateway's 429 when every workspace the plan allows to work at once is busy (the SDK
|
|
145
|
+
* already retried it after Retry-After), and the API's 403 when the plan's Retained state is used up (new keys and
|
|
146
|
+
* forks are refused). Other limits keep the API's message alone.
|
|
147
|
+
*/
|
|
148
|
+
function quotaHint(info) {
|
|
149
|
+
const limit = info.details?.limit;
|
|
150
|
+
if (limit === 'concurrent_workspaces' && info.status === 429) {
|
|
151
|
+
const s = info.retry_after_seconds;
|
|
152
|
+
return `every workspace your plan allows to work at once is busy and the call did not run (it was already retried); retry ${typeof s === 'number' ? `in ${s} s` : 'in a few seconds'}, or upgrade: shard billing plans`;
|
|
153
|
+
}
|
|
154
|
+
if (limit === 'retained_state')
|
|
155
|
+
return 'delete workspaces you no longer need (shard ws delete <key> --yes), or upgrade: shard billing plans';
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
138
158
|
/** Dollars for a hint: `9` for 900 minor units, `9.50` for 950. */
|
|
139
159
|
const dollars = (minor) => (typeof minor === 'number' ? (minor % 100 === 0 ? String(minor / 100) : (minor / 100).toFixed(2)) : undefined);
|
|
140
160
|
/**
|
|
@@ -300,6 +320,9 @@ const NO_NUDGE_REASONS = new Set([
|
|
|
300
320
|
function nudges(info, command) {
|
|
301
321
|
if (command === undefined || command === 'feedback')
|
|
302
322
|
return false;
|
|
323
|
+
// A plan limit whose hint names the next step (0.9.0): busy working slots, or Retained state used up.
|
|
324
|
+
if (info.code === 'quota_exceeded' && quotaHint(info) !== undefined)
|
|
325
|
+
return false;
|
|
303
326
|
const reason = reasonOf(info);
|
|
304
327
|
return !NO_NUDGE_CODES.has(info.code) && !(reason !== undefined && NO_NUDGE_REASONS.has(reason));
|
|
305
328
|
}
|
package/dist/timing.d.ts
CHANGED
|
@@ -15,6 +15,12 @@ export declare class TimingRecorder {
|
|
|
15
15
|
* --wait`, an `operations wait` of that resume, or a command's wake. `lostSuspendNotice()` says it.
|
|
16
16
|
*/
|
|
17
17
|
lostSuspend: LifecycleTiming | null;
|
|
18
|
+
/**
|
|
19
|
+
* (0.8.1+) The last traced resume that restored the workspace's newest checkpoint after the machine it ran on failed
|
|
20
|
+
* (`host_lost.restored_from` `checkpoint`): a `ws resume --wait`, an `operations wait` of that resume, or a command's
|
|
21
|
+
* wake. `hostLostNotice()` says it. A restore from the disk is a cold boot (`coldBootNotice()`).
|
|
22
|
+
*/
|
|
23
|
+
hostLost: LifecycleTiming | null;
|
|
18
24
|
readonly listener: ProgressListener;
|
|
19
25
|
/**
|
|
20
26
|
* The failed call's own timing (`err.timing`) when the SDK attached one, else the last recorded. A refused tool token
|
|
@@ -24,15 +30,21 @@ export declare class TimingRecorder {
|
|
|
24
30
|
}
|
|
25
31
|
/**
|
|
26
32
|
* The one-line stderr notice after a command whose resume booted the workspace's saved disk (0.6.0+), with or without
|
|
27
|
-
* `--timing`: the workspace runs
|
|
33
|
+
* `--timing`: the workspace runs with its files kept, but running processes were restarted. The reason follows in
|
|
34
|
+
* parentheses: `runtime_changed`, or in words for `host_lost` (0.8.1+).
|
|
28
35
|
*/
|
|
29
36
|
export declare function coldBootNotice(t: LifecycleTiming): string;
|
|
37
|
+
/**
|
|
38
|
+
* The one-line stderr notice after a command whose resume restored the workspace's newest checkpoint after the machine
|
|
39
|
+
* it ran on failed (0.8.1+; `host_lost.restored_from` `checkpoint`): the state is as of `state_as_of`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function hostLostNotice(t: LifecycleTiming): string;
|
|
30
42
|
/**
|
|
31
43
|
* The one-line stderr notice after a command whose resume restored an earlier checkpoint because the latest suspend
|
|
32
44
|
* could not be kept (0.7.0+; `lost_suspend`): the state the workspace runs with is as of `state_as_of`.
|
|
33
45
|
*/
|
|
34
46
|
export declare function lostSuspendNotice(t: LifecycleTiming): string;
|
|
35
|
-
/** The notices a command's traced calls leave on stderr (cold boot, lost suspend), in that order. */
|
|
47
|
+
/** The notices a command's traced calls leave on stderr (cold boot, lost suspend, host loss), in that order. */
|
|
36
48
|
export declare function notices(r: TimingRecorder): string;
|
|
37
49
|
/** The human form (stderr): formatTiming() of the SDK, one block. */
|
|
38
50
|
export declare function timingText(t: LifecycleTiming): string;
|
package/dist/timing.js
CHANGED
|
@@ -23,6 +23,12 @@ export class TimingRecorder {
|
|
|
23
23
|
* --wait`, an `operations wait` of that resume, or a command's wake. `lostSuspendNotice()` says it.
|
|
24
24
|
*/
|
|
25
25
|
lostSuspend = null;
|
|
26
|
+
/**
|
|
27
|
+
* (0.8.1+) The last traced resume that restored the workspace's newest checkpoint after the machine it ran on failed
|
|
28
|
+
* (`host_lost.restored_from` `checkpoint`): a `ws resume --wait`, an `operations wait` of that resume, or a command's
|
|
29
|
+
* wake. `hostLostNotice()` says it. A restore from the disk is a cold boot (`coldBootNotice()`).
|
|
30
|
+
*/
|
|
31
|
+
hostLost = null;
|
|
26
32
|
listener = (e) => {
|
|
27
33
|
if (e.action === 'token' || e.action === 'tool')
|
|
28
34
|
return;
|
|
@@ -35,6 +41,8 @@ export class TimingRecorder {
|
|
|
35
41
|
this.coldBoot = e.timing;
|
|
36
42
|
if (e.timing.server?.lostSuspend)
|
|
37
43
|
this.lostSuspend = e.timing;
|
|
44
|
+
if (e.timing.server?.hostLost?.restoredFrom === 'checkpoint')
|
|
45
|
+
this.hostLost = e.timing;
|
|
38
46
|
};
|
|
39
47
|
/**
|
|
40
48
|
* The failed call's own timing (`err.timing`) when the SDK attached one, else the last recorded. A refused tool token
|
|
@@ -49,13 +57,29 @@ export class TimingRecorder {
|
|
|
49
57
|
function restartedProcesses(t) {
|
|
50
58
|
return t.server?.resumePath === 'cold_boot' && t.server.memoryRestored === false;
|
|
51
59
|
}
|
|
60
|
+
/** Cold boot reasons the notice says in words (0.8.1+); any other reason is printed as the API sent it. */
|
|
61
|
+
const COLD_BOOT_WORDS = {
|
|
62
|
+
host_lost: 'the machine it ran on failed',
|
|
63
|
+
};
|
|
52
64
|
/**
|
|
53
65
|
* The one-line stderr notice after a command whose resume booted the workspace's saved disk (0.6.0+), with or without
|
|
54
|
-
* `--timing`: the workspace runs
|
|
66
|
+
* `--timing`: the workspace runs with its files kept, but running processes were restarted. The reason follows in
|
|
67
|
+
* parentheses: `runtime_changed`, or in words for `host_lost` (0.8.1+).
|
|
55
68
|
*/
|
|
56
69
|
export function coldBootNotice(t) {
|
|
57
70
|
const reason = t.server?.coldBootReason;
|
|
58
|
-
|
|
71
|
+
const why = reason ? (COLD_BOOT_WORDS[reason] ?? reason) : null;
|
|
72
|
+
return `note: ${t.workspaceId ? `workspace ${t.workspaceId} ` : ''}resumed from its saved disk; running processes were restarted${why ? ` (${why})` : ''}\n`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The one-line stderr notice after a command whose resume restored the workspace's newest checkpoint after the machine
|
|
76
|
+
* it ran on failed (0.8.1+; `host_lost.restored_from` `checkpoint`): the state is as of `state_as_of`.
|
|
77
|
+
*/
|
|
78
|
+
export function hostLostNotice(t) {
|
|
79
|
+
const h = t.server?.hostLost;
|
|
80
|
+
if (h?.restoredFrom !== 'checkpoint')
|
|
81
|
+
return '';
|
|
82
|
+
return `note: ${t.workspaceId ? `workspace ${t.workspaceId} ` : ''}resumed from checkpoint ${h.restoredCheckpointId ?? '-'}${h.stateAsOf ? ` (state as of ${h.stateAsOf})` : ''} after the machine it ran on failed; changes after that time are not in it\n`;
|
|
59
83
|
}
|
|
60
84
|
/**
|
|
61
85
|
* The one-line stderr notice after a command whose resume restored an earlier checkpoint because the latest suspend
|
|
@@ -67,9 +91,9 @@ export function lostSuspendNotice(t) {
|
|
|
67
91
|
return '';
|
|
68
92
|
return `note: ${t.workspaceId ? `workspace ${t.workspaceId} ` : ''}resumed from checkpoint ${l.restoredCheckpointId ?? '-'}${l.stateAsOf ? ` (state as of ${l.stateAsOf})` : ''}; the suspend of ${l.suspendedAt ?? l.checkpointId} was not kept${l.reason ? ` (${l.reason})` : ''}\n`;
|
|
69
93
|
}
|
|
70
|
-
/** The notices a command's traced calls leave on stderr (cold boot, lost suspend), in that order. */
|
|
94
|
+
/** The notices a command's traced calls leave on stderr (cold boot, lost suspend, host loss), in that order. */
|
|
71
95
|
export function notices(r) {
|
|
72
|
-
return `${r.coldBoot ? coldBootNotice(r.coldBoot) : ''}${r.lostSuspend ? lostSuspendNotice(r.lostSuspend) : ''}`;
|
|
96
|
+
return `${r.coldBoot ? coldBootNotice(r.coldBoot) : ''}${r.lostSuspend ? lostSuspendNotice(r.lostSuspend) : ''}${r.hostLost ? hostLostNotice(r.hostLost) : ''}`;
|
|
73
97
|
}
|
|
74
98
|
/** The human form (stderr): formatTiming() of the SDK, one block. */
|
|
75
99
|
export function timingText(t) {
|
|
@@ -126,6 +150,15 @@ export function timingJson(t) {
|
|
|
126
150
|
state_as_of: s.lostSuspend.stateAsOf,
|
|
127
151
|
}
|
|
128
152
|
: null,
|
|
153
|
+
// 0.8.1+: the machine the workspace ran on failed (resume: what it restored; suspend: detected_at only).
|
|
154
|
+
host_lost: s.hostLost
|
|
155
|
+
? {
|
|
156
|
+
detected_at: s.hostLost.detectedAt,
|
|
157
|
+
restored_from: s.hostLost.restoredFrom,
|
|
158
|
+
restored_checkpoint_id: s.hostLost.restoredCheckpointId,
|
|
159
|
+
state_as_of: s.hostLost.stateAsOf,
|
|
160
|
+
}
|
|
161
|
+
: null,
|
|
129
162
|
boot_to_ready_ms: s.bootToReadyMs,
|
|
130
163
|
host_timings_ms: s.hostTimingsMs,
|
|
131
164
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork persistent agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"yaml": "2.9.1",
|
|
47
|
-
"@shardflux/sdk": "^0.13.
|
|
47
|
+
"@shardflux/sdk": "^0.13.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "10.0.1",
|