@skrr-ai/cli 0.1.26 → 0.1.27

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.
Files changed (33) hide show
  1. package/dist/base-command.js +25 -0
  2. package/dist/commands/machines/dedicated/attach.js +4 -3
  3. package/dist/commands/machines/dedicated/current.d.ts +19 -0
  4. package/dist/commands/machines/dedicated/current.js +49 -0
  5. package/dist/commands/machines/dedicated/destroy.js +7 -6
  6. package/dist/commands/machines/dedicated/exec.d.ts +4 -0
  7. package/dist/commands/machines/dedicated/exec.js +19 -2
  8. package/dist/commands/machines/dedicated/grow.js +3 -2
  9. package/dist/commands/machines/dedicated/health-check.js +4 -3
  10. package/dist/commands/machines/dedicated/incident-close.js +3 -2
  11. package/dist/commands/machines/dedicated/index.js +2 -0
  12. package/dist/commands/machines/dedicated/restart.js +3 -2
  13. package/dist/commands/machines/dedicated/restore.d.ts +26 -0
  14. package/dist/commands/machines/dedicated/restore.js +64 -0
  15. package/dist/commands/machines/dedicated/show.js +3 -2
  16. package/dist/commands/machines/dedicated/snapshot.js +3 -2
  17. package/dist/commands/machines/dedicated/spending-limit.js +4 -3
  18. package/dist/commands/machines/dedicated/start.js +3 -2
  19. package/dist/commands/machines/dedicated/stop.js +3 -2
  20. package/dist/lib/dedicated-exec.d.ts +80 -0
  21. package/dist/lib/dedicated-exec.js +271 -0
  22. package/dist/lib/dedicated-lease-command.d.ts +9 -0
  23. package/dist/lib/dedicated-lease-command.js +24 -1
  24. package/dist/lib/dedicated-machines.d.ts +90 -1
  25. package/dist/lib/dedicated-machines.js +122 -5
  26. package/dist/lib/edge-error-page.d.ts +49 -0
  27. package/dist/lib/edge-error-page.js +120 -0
  28. package/dist/lib/harnesses.d.ts +9 -1
  29. package/dist/lib/harnesses.js +20 -2
  30. package/dist/lib/hosted-machines.js +7 -0
  31. package/dist/node_modules/@skrr-ai/data-provider/index.js +12 -0
  32. package/oclif.manifest.json +3341 -3147
  33. package/package.json +1 -1
@@ -0,0 +1,80 @@
1
+ import { execDedicatedRuntime, observeDedicatedRuntimeExecution, startDedicatedRuntimeExecution, type DedicatedRuntimeExecInput, type DedicatedRuntimeExecResult, type DedicatedRuntimeScope } from './dedicated-machines';
2
+ /**
3
+ * `skrr machines dedicated exec`, as a start and a series of short observations
4
+ * (OSK-8705).
5
+ *
6
+ * The command used to be one request held open for as long as it ran. Behind
7
+ * CloudFront that request died at 60 s with an HTML 504 while the command kept
8
+ * running, and the page's advice — try again — ran it twice. Now no request
9
+ * lasts longer than one bounded observation, so no proxy timeout in front of
10
+ * the API decides whether the caller learns the exit code:
11
+ *
12
+ * 1. START under one idempotency key. A start whose answer never arrived (a
13
+ * dropped connection, a gateway page) is sent again with the SAME key, and
14
+ * the server returns the execution it already started — never a second run.
15
+ * 2. OBSERVE, letting the server hold each answer up to 20 s, until the
16
+ * command ends. A failed observation is only a failed read; it is retried.
17
+ * 3. PRINT exactly what the synchronous route printed: the same result, the
18
+ * same refusal body, the same exit code.
19
+ *
20
+ * Against a server that predates `executions`, it falls back to the synchronous
21
+ * route and says what that costs — but only on the FIRST start of a request id
22
+ * this invocation minted. A 404 without a product code proves the task that
23
+ * answered has no such route, not that no other task started the command; in a
24
+ * mixed fleet (a rolling deploy, a rollback) only the first attempt can rely on
25
+ * it. Every other case retries the same id or stops with the id to repeat.
26
+ */
27
+ /** How long each observation lets the server hold the answer. */
28
+ export declare const EXEC_OBSERVE_WAIT_MS = 20000;
29
+ /** A request id is the idempotency key; the server accepts exactly this shape. */
30
+ export declare const EXEC_REQUEST_ID_PATTERN: RegExp;
31
+ export declare function newExecRequestId(): string;
32
+ /** The CLI stopped hearing about a command it started. The command may still run. */
33
+ export declare class DedicatedExecContactLostError extends Error {
34
+ readonly requestId: string;
35
+ readonly executionId: string;
36
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_CONTACT_LOST";
37
+ constructor(requestId: string, executionId: string, cause: unknown);
38
+ }
39
+ /** No start request got an answer from the API, so whether the command started is unknown. */
40
+ export declare class DedicatedExecStartUnconfirmedError extends Error {
41
+ readonly requestId: string;
42
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_START_UNCONFIRMED";
43
+ constructor(requestId: string, cause: unknown);
44
+ }
45
+ /**
46
+ * The server has no `executions` route, but this invocation carries a request id
47
+ * it chose: falling back to the single-request route would ignore that id, and a
48
+ * run it already started elsewhere would happen twice.
49
+ */
50
+ export declare class DedicatedExecIdempotencyUnsupportedError extends Error {
51
+ readonly requestId: string;
52
+ readonly code = "DEDICATED_RUNTIME_EXECUTION_REQUEST_ID_UNSUPPORTED";
53
+ constructor(requestId: string);
54
+ }
55
+ export interface DedicatedExecIo {
56
+ start: typeof startDedicatedRuntimeExecution;
57
+ observe: typeof observeDedicatedRuntimeExecution;
58
+ legacyExec: typeof execDedicatedRuntime;
59
+ sleep: (ms: number) => Promise<void>;
60
+ now: () => number;
61
+ /** A line about the invocation (not the command's output), for stderr. */
62
+ notice: (message: string) => void;
63
+ signal: (ms: number) => AbortSignal | undefined;
64
+ }
65
+ /** The server answered, but has no `executions` route: nothing was started. */
66
+ export declare function isMissingExecutionsEndpoint(err: unknown): boolean;
67
+ export interface RunDedicatedExecOptions extends DedicatedRuntimeScope {
68
+ /** Idempotency key. Pass one to be able to repeat the invocation safely. */
69
+ requestId?: string;
70
+ }
71
+ export declare function runDedicatedExec(leaseId: string, input: DedicatedRuntimeExecInput, options?: RunDedicatedExecOptions, io?: Partial<DedicatedExecIo>): Promise<DedicatedRuntimeExecResult>;
72
+ /**
73
+ * What was cut. A started command's stored outcome keeps at most the last
74
+ * 256 KiB of what the machine returned (`outputBytes` is the whole); the
75
+ * single-request route caps at 1 MB from the start.
76
+ */
77
+ export declare function describeTruncation(result: {
78
+ stdout: string;
79
+ outputBytes?: number;
80
+ }): string;
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DedicatedExecIdempotencyUnsupportedError = exports.DedicatedExecStartUnconfirmedError = exports.DedicatedExecContactLostError = exports.EXEC_REQUEST_ID_PATTERN = exports.EXEC_OBSERVE_WAIT_MS = void 0;
4
+ exports.newExecRequestId = newExecRequestId;
5
+ exports.isMissingExecutionsEndpoint = isMissingExecutionsEndpoint;
6
+ exports.runDedicatedExec = runDedicatedExec;
7
+ exports.describeTruncation = describeTruncation;
8
+ const node_crypto_1 = require("node:crypto");
9
+ const api_fetch_1 = require("./api-fetch");
10
+ const dedicated_machines_1 = require("./dedicated-machines");
11
+ /**
12
+ * `skrr machines dedicated exec`, as a start and a series of short observations
13
+ * (OSK-8705).
14
+ *
15
+ * The command used to be one request held open for as long as it ran. Behind
16
+ * CloudFront that request died at 60 s with an HTML 504 while the command kept
17
+ * running, and the page's advice — try again — ran it twice. Now no request
18
+ * lasts longer than one bounded observation, so no proxy timeout in front of
19
+ * the API decides whether the caller learns the exit code:
20
+ *
21
+ * 1. START under one idempotency key. A start whose answer never arrived (a
22
+ * dropped connection, a gateway page) is sent again with the SAME key, and
23
+ * the server returns the execution it already started — never a second run.
24
+ * 2. OBSERVE, letting the server hold each answer up to 20 s, until the
25
+ * command ends. A failed observation is only a failed read; it is retried.
26
+ * 3. PRINT exactly what the synchronous route printed: the same result, the
27
+ * same refusal body, the same exit code.
28
+ *
29
+ * Against a server that predates `executions`, it falls back to the synchronous
30
+ * route and says what that costs — but only on the FIRST start of a request id
31
+ * this invocation minted. A 404 without a product code proves the task that
32
+ * answered has no such route, not that no other task started the command; in a
33
+ * mixed fleet (a rolling deploy, a rollback) only the first attempt can rely on
34
+ * it. Every other case retries the same id or stops with the id to repeat.
35
+ */
36
+ /** How long each observation lets the server hold the answer. */
37
+ exports.EXEC_OBSERVE_WAIT_MS = 20_000;
38
+ /** A request that has not answered by then is treated as lost, and retried. */
39
+ const REQUEST_DEADLINE_MS = exports.EXEC_OBSERVE_WAIT_MS + 25_000;
40
+ const START_ATTEMPTS = 4;
41
+ /**
42
+ * How long past the command's own timeout to keep observing. Longer than the
43
+ * server's own outcome deadline (timeout + 30 s response grace + 60 s dispatch
44
+ * allowance), so the server reports `unknown` before this gives up on it.
45
+ */
46
+ const OBSERVE_SLACK_MS = 2 * 60 * 1000;
47
+ const MAX_BACKOFF_MS = 10_000;
48
+ /** A request id is the idempotency key; the server accepts exactly this shape. */
49
+ exports.EXEC_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:/-]{1,200}$/;
50
+ function newExecRequestId() {
51
+ return `exec:${(0, node_crypto_1.randomUUID)()}`;
52
+ }
53
+ /** How long an observation keeps asking after the server says the execution is not there. */
54
+ const NOT_FOUND_TOLERANCE_MS = 60_000;
55
+ /** The CLI stopped hearing about a command it started. The command may still run. */
56
+ class DedicatedExecContactLostError extends Error {
57
+ requestId;
58
+ executionId;
59
+ code = 'DEDICATED_RUNTIME_EXECUTION_CONTACT_LOST';
60
+ constructor(requestId, executionId, cause) {
61
+ super(`Lost track of the command while it was running (execution ${executionId}). ` +
62
+ 'It may still be running on the machine, or may have finished. To see how it ended ' +
63
+ `without running it again, repeat the same command with --request-id ${requestId} ` +
64
+ '(an outcome is kept for 5 minutes after it ends, and a minute after it is first read).' +
65
+ (cause instanceof Error && cause.message ? ` Last error: ${cause.message}` : ''));
66
+ this.requestId = requestId;
67
+ this.executionId = executionId;
68
+ this.name = 'DedicatedExecContactLostError';
69
+ }
70
+ }
71
+ exports.DedicatedExecContactLostError = DedicatedExecContactLostError;
72
+ /** No start request got an answer from the API, so whether the command started is unknown. */
73
+ class DedicatedExecStartUnconfirmedError extends Error {
74
+ requestId;
75
+ code = 'DEDICATED_RUNTIME_EXECUTION_START_UNCONFIRMED';
76
+ constructor(requestId, cause) {
77
+ super('Could not confirm whether the command started: no attempt got an answer from the API. ' +
78
+ 'It may be running. To find out without running it a second time, repeat the same ' +
79
+ `command with --request-id ${requestId}.` +
80
+ (cause instanceof Error && cause.message ? ` Last error: ${cause.message}` : ''));
81
+ this.requestId = requestId;
82
+ this.name = 'DedicatedExecStartUnconfirmedError';
83
+ }
84
+ }
85
+ exports.DedicatedExecStartUnconfirmedError = DedicatedExecStartUnconfirmedError;
86
+ /**
87
+ * The server has no `executions` route, but this invocation carries a request id
88
+ * it chose: falling back to the single-request route would ignore that id, and a
89
+ * run it already started elsewhere would happen twice.
90
+ */
91
+ class DedicatedExecIdempotencyUnsupportedError extends Error {
92
+ requestId;
93
+ code = 'DEDICATED_RUNTIME_EXECUTION_REQUEST_ID_UNSUPPORTED';
94
+ constructor(requestId) {
95
+ super('This server answered as if it predates long-running exec, which is the only way --request-id ' +
96
+ 'is honoured. The command was not run in a single request instead, because that could run ' +
97
+ `it a second time. During a deploy, retry shortly with --request-id ${requestId}; against ` +
98
+ 'an older server, run it without --request-id.');
99
+ this.requestId = requestId;
100
+ this.name = 'DedicatedExecIdempotencyUnsupportedError';
101
+ }
102
+ }
103
+ exports.DedicatedExecIdempotencyUnsupportedError = DedicatedExecIdempotencyUnsupportedError;
104
+ const defaultIo = {
105
+ start: dedicated_machines_1.startDedicatedRuntimeExecution,
106
+ observe: dedicated_machines_1.observeDedicatedRuntimeExecution,
107
+ legacyExec: dedicated_machines_1.execDedicatedRuntime,
108
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
109
+ now: () => Date.now(),
110
+ notice: (message) => process.stderr.write(`${message}\n`),
111
+ signal: (ms) => typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'
112
+ ? AbortSignal.timeout(ms)
113
+ : undefined,
114
+ };
115
+ function productCode(err) {
116
+ try {
117
+ const parsed = JSON.parse(err.body || '');
118
+ return typeof parsed.code === 'string' && parsed.code ? parsed.code : undefined;
119
+ }
120
+ catch {
121
+ return undefined;
122
+ }
123
+ }
124
+ /** The server answered, but has no `executions` route: nothing was started. */
125
+ function isMissingExecutionsEndpoint(err) {
126
+ return err instanceof api_fetch_1.ApiFetchError && err.status === 404 && !productCode(err);
127
+ }
128
+ /**
129
+ * No answer from the API itself: the connection failed, the request timed out
130
+ * client-side, or something in front of the API answered instead (a gateway
131
+ * page carries no product code). Whether the request arrived is unknown.
132
+ */
133
+ function isUnansweredRequest(err) {
134
+ if (err instanceof api_fetch_1.ApiFetchError) {
135
+ return [502, 503, 504].includes(err.status) && !productCode(err);
136
+ }
137
+ const e = err;
138
+ if (!e)
139
+ return false;
140
+ if (e.name === 'AbortError' || e.name === 'TimeoutError')
141
+ return true;
142
+ const code = e.code || e.cause?.code || '';
143
+ if (['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'ENOTFOUND', 'EAI_AGAIN'].includes(code)) {
144
+ return true;
145
+ }
146
+ return /fetch failed|socket hang up|network|terminated|other side closed/i.test(e.message || '');
147
+ }
148
+ function backoff(attempt) {
149
+ return Math.min(1000 * 2 ** Math.max(0, attempt - 1), MAX_BACKOFF_MS);
150
+ }
151
+ /** The refusal the synchronous route would have answered with, as its error. */
152
+ function executionFailure(execution) {
153
+ const { status, ...body } = execution.error || {};
154
+ const httpStatus = typeof status === 'number' ? status : 500;
155
+ const code = typeof body.code === 'string' ? body.code : undefined;
156
+ return new api_fetch_1.ApiFetchError(`HTTP ${httpStatus} — command ${execution.state} (execution ${execution.id})`, httpStatus, JSON.stringify(Object.keys(body).length ? body : { error: 'The command failed' }), code, 'POST');
157
+ }
158
+ async function runDedicatedExec(leaseId, input, options = {}, io = {}) {
159
+ const deps = { ...defaultIo, ...io };
160
+ const requestId = options.requestId || newExecRequestId();
161
+ const scope = options.workspaceId
162
+ ? { workspaceId: options.workspaceId }
163
+ : {};
164
+ let execution;
165
+ for (let attempt = 1; !execution; attempt += 1) {
166
+ try {
167
+ const started = await deps.start(leaseId, input, {
168
+ ...scope,
169
+ requestId,
170
+ signal: deps.signal(REQUEST_DEADLINE_MS),
171
+ });
172
+ execution = started.execution;
173
+ }
174
+ catch (err) {
175
+ const missing = isMissingExecutionsEndpoint(err);
176
+ // Fall back only when nothing could have started: this is the first start
177
+ // for a request id this invocation minted. On a later attempt a codeless
178
+ // 404 is an OLDER task answering during a rolling deploy or a rollback,
179
+ // after an earlier attempt may have reached a newer one and started the
180
+ // command; a person's own --request-id may name a run a previous
181
+ // invocation started the same way. Either way `/exec` would run it twice.
182
+ if (missing && attempt === 1 && !options.requestId) {
183
+ deps.notice('This server predates long-running exec, so the command runs in a single request: ' +
184
+ 'one that runs past about 60 seconds is cut off at the edge while it keeps running ' +
185
+ 'on the machine.');
186
+ return deps.legacyExec(leaseId, input, scope);
187
+ }
188
+ // Safe only because the key is the same: the server answers a repeated
189
+ // start with the execution it already has.
190
+ if (!missing && !isUnansweredRequest(err))
191
+ throw err;
192
+ if (attempt >= START_ATTEMPTS) {
193
+ if (missing && attempt === 1)
194
+ throw new DedicatedExecIdempotencyUnsupportedError(requestId);
195
+ throw new DedicatedExecStartUnconfirmedError(requestId, err);
196
+ }
197
+ await deps.sleep(backoff(attempt));
198
+ }
199
+ }
200
+ const timeoutMs = execution.timeoutMs || input.timeoutMs || dedicated_machines_1.DEDICATED_EXEC_DEFAULT_TIMEOUT_MS;
201
+ const giveUpAt = deps.now() + timeoutMs + OBSERVE_SLACK_MS;
202
+ let failures = 0;
203
+ let notFoundSince = null;
204
+ while (execution.state === 'running') {
205
+ try {
206
+ const observed = await deps.observe(leaseId, execution.id, {
207
+ ...scope,
208
+ waitMs: exports.EXEC_OBSERVE_WAIT_MS,
209
+ signal: deps.signal(REQUEST_DEADLINE_MS),
210
+ });
211
+ execution = observed.execution;
212
+ failures = 0;
213
+ notFoundSince = null;
214
+ }
215
+ catch (err) {
216
+ const code = err instanceof api_fetch_1.ApiFetchError ? productCode(err) : undefined;
217
+ // A codeless 404 is an older task, during a deploy or a rollback, that has
218
+ // never heard of `executions`; the command this id started is unaffected.
219
+ const olderServer = isMissingExecutionsEndpoint(err);
220
+ // Not found can be a result read and released moments ago, or a store that
221
+ // lost it. Keep asking briefly, then stop: one that is really gone does not
222
+ // come back, and waiting out the whole timeout would only delay the hint.
223
+ const notFound = code === 'DEDICATED_RUNTIME_EXECUTION_NOT_FOUND';
224
+ const transient = olderServer ||
225
+ notFound ||
226
+ isUnansweredRequest(err) ||
227
+ (err instanceof api_fetch_1.ApiFetchError &&
228
+ (err.status === 429 || code === 'DEDICATED_RUNTIME_UNAVAILABLE'));
229
+ if (!transient)
230
+ throw err;
231
+ failures += 1;
232
+ const now = deps.now();
233
+ if (notFound)
234
+ notFoundSince ??= now;
235
+ else
236
+ notFoundSince = null;
237
+ const gaveUp = now >= giveUpAt ||
238
+ (notFoundSince !== null && now - notFoundSince >= NOT_FOUND_TOLERANCE_MS);
239
+ if (gaveUp)
240
+ throw new DedicatedExecContactLostError(requestId, execution.id, err);
241
+ await deps.sleep(backoff(failures));
242
+ continue;
243
+ }
244
+ if (execution.state === 'running' && deps.now() >= giveUpAt) {
245
+ // The server should have declared the outcome unknown by now.
246
+ throw new DedicatedExecContactLostError(requestId, execution.id, undefined);
247
+ }
248
+ }
249
+ if (execution.state === 'completed' && execution.result) {
250
+ return execution.result;
251
+ }
252
+ throw executionFailure(execution);
253
+ }
254
+ function sizeOf(bytes) {
255
+ if (bytes >= 1024 * 1024)
256
+ return `${Math.round((bytes / (1024 * 1024)) * 10) / 10} MiB`;
257
+ if (bytes >= 1024)
258
+ return `${Math.round(bytes / 1024)} KiB`;
259
+ return `${bytes} B`;
260
+ }
261
+ /**
262
+ * What was cut. A started command's stored outcome keeps at most the last
263
+ * 256 KiB of what the machine returned (`outputBytes` is the whole); the
264
+ * single-request route caps at 1 MB from the start.
265
+ */
266
+ function describeTruncation(result) {
267
+ if (typeof result.outputBytes === 'number') {
268
+ return `[output truncated: showing the last ${sizeOf(Buffer.byteLength(result.stdout))} of ${sizeOf(result.outputBytes)}]`;
269
+ }
270
+ return '[output truncated at 1 MB]';
271
+ }
@@ -11,6 +11,9 @@ export declare const dedicatedWaitFlags: {
11
11
  timeout: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  interval: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
13
  };
14
+ /** The lease argument that means "the machine this command is running on". */
15
+ export declare const DEDICATED_SELF_LEASE = "self";
16
+ export declare const DEDICATED_LEASE_ARG_DESCRIPTION = "Dedicated Runtime lease id, or `self` when running on the machine itself";
14
17
  export declare abstract class DedicatedLeaseCommand extends BaseCommand {
15
18
  /**
16
19
  * `--workspace` is a BaseCommand flag every command already accepts (it sets
@@ -21,6 +24,12 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
21
24
  protected scopeFrom(flags: {
22
25
  workspace?: string;
23
26
  }): DedicatedRuntimeScope;
27
+ /**
28
+ * A lease argument as typed, or — for `self` — the lease of the machine this
29
+ * process runs on, so an agent working on a Dedicated Runtime can snapshot,
30
+ * inspect or stop its own machine without being told its id.
31
+ */
32
+ protected resolveLeaseArg(lease: string): Promise<string>;
24
33
  /**
25
34
  * Open an interactive terminal on a lease's machine and run it to the end,
26
35
  * exiting with the shell's own code.
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DedicatedLeaseCommand = exports.dedicatedWaitFlags = void 0;
3
+ exports.DedicatedLeaseCommand = exports.DEDICATED_LEASE_ARG_DESCRIPTION = exports.DEDICATED_SELF_LEASE = exports.dedicatedWaitFlags = void 0;
4
4
  const core_1 = require("@oclif/core");
5
5
  const base_command_1 = require("../base-command");
6
6
  const dedicated_machines_1 = require("./dedicated-machines");
7
7
  const dedicated_terminal_1 = require("./dedicated-terminal");
8
+ const daemonBroker_1 = require("./daemonBroker");
8
9
  const harnesses_1 = require("./harnesses");
9
10
  const dedicated_wait_1 = require("./dedicated-wait");
10
11
  const hosted_machines_1 = require("./hosted-machines");
@@ -28,6 +29,9 @@ exports.dedicatedWaitFlags = {
28
29
  }),
29
30
  };
30
31
  const TERMINAL_READY_STATES = new Set(['ready', 'active', 'idle']);
32
+ /** The lease argument that means "the machine this command is running on". */
33
+ exports.DEDICATED_SELF_LEASE = 'self';
34
+ exports.DEDICATED_LEASE_ARG_DESCRIPTION = 'Dedicated Runtime lease id, or `self` when running on the machine itself';
31
35
  class DedicatedLeaseCommand extends base_command_1.BaseCommand {
32
36
  /**
33
37
  * `--workspace` is a BaseCommand flag every command already accepts (it sets
@@ -38,6 +42,25 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
38
42
  scopeFrom(flags) {
39
43
  return flags.workspace ? { workspaceId: flags.workspace } : {};
40
44
  }
45
+ /**
46
+ * A lease argument as typed, or — for `self` — the lease of the machine this
47
+ * process runs on, so an agent working on a Dedicated Runtime can snapshot,
48
+ * inspect or stop its own machine without being told its id.
49
+ */
50
+ async resolveLeaseArg(lease) {
51
+ if (lease !== exports.DEDICATED_SELF_LEASE)
52
+ return lease;
53
+ const descriptor = (0, daemonBroker_1.readBootstrap)(daemonBroker_1.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR);
54
+ if (!descriptor?.daemonId) {
55
+ this.error('`self` means the Dedicated Runtime this command runs on, and this is not one (or its daemon is not running). Pass a lease id.', { exit: 2 });
56
+ }
57
+ const listed = await this.callDedicated(() => (0, harnesses_1.listHarnesses)());
58
+ const resolved = (0, dedicated_machines_1.dedicatedLeaseIdForDaemon)(descriptor.daemonId, listed?.harnesses ?? []);
59
+ if (!resolved) {
60
+ this.error("This machine's daemon has not registered a lease yet, so `self` cannot be resolved. Pass a lease id, or try again once `skrr machines dedicated list` shows the machine ready.", { exit: 1 });
61
+ }
62
+ return resolved;
63
+ }
41
64
  /**
42
65
  * Open an interactive terminal on a lease's machine and run it to the end,
43
66
  * exiting with the shell's own code.
@@ -43,6 +43,8 @@ export interface DedicatedLease extends Omit<HarnessLease, 'storage'> {
43
43
  unpaidSince?: string;
44
44
  deletionNoticeAt?: string;
45
45
  deleteAfter?: string;
46
+ /** A workspace deletion and its recovery window (D12). */
47
+ deletion?: DedicatedLeaseDeletion;
46
48
  } & Record<string, unknown>;
47
49
  recovery?: {
48
50
  incidentHold?: {
@@ -51,8 +53,22 @@ export interface DedicatedLease extends Omit<HarnessLease, 'storage'> {
51
53
  heldAt?: string;
52
54
  closedAt?: string;
53
55
  };
56
+ /** Set on a machine created by restoring a deleted one. */
57
+ restoredFromLeaseId?: string;
54
58
  };
55
59
  }
60
+ /**
61
+ * A deleted workspace: restorable into a new machine until `recoverableUntil`,
62
+ * then every copy is deleted and `completedAt` records it.
63
+ */
64
+ export interface DedicatedLeaseDeletion {
65
+ status?: 'recoverable' | 'purging' | 'completed';
66
+ requestedAt?: string | null;
67
+ recoverableUntil?: string | null;
68
+ restorable?: boolean;
69
+ restoredToLeaseId?: string | null;
70
+ completedAt?: string | null;
71
+ }
56
72
  export interface CreateDedicatedRuntimeInput extends DedicatedRuntimeScope {
57
73
  requestId: string;
58
74
  displayName: string;
@@ -114,6 +130,17 @@ export declare function performDedicatedLeaseAction(leaseId: string, action: Ded
114
130
  export declare function growDedicatedRuntime(leaseId: string, storageGb: number, options?: {
115
131
  requestId?: string;
116
132
  } & DedicatedRuntimeScope): Promise<DedicatedRuntimeResponse>;
133
+ /**
134
+ * Restore a deleted Dedicated Runtime into a NEW machine, from the final recovery
135
+ * point its deletion kept. Only inside the deletion's recovery window, and once
136
+ * per deletion; the response's `lease` is the new machine. The cap defaults to
137
+ * the deleted machine's.
138
+ */
139
+ export declare function restoreDeletedDedicatedRuntime(leaseId: string, options?: {
140
+ displayName?: string;
141
+ monthlySpendingLimitCents?: number;
142
+ requestId?: string;
143
+ } & DedicatedRuntimeScope): Promise<DedicatedRuntimeResponse>;
117
144
  /**
118
145
  * Change the monthly spending cap of one lease.
119
146
  *
@@ -142,17 +169,66 @@ export interface DedicatedRuntimeExecResult {
142
169
  timedOut: boolean;
143
170
  truncated: boolean;
144
171
  durationMs: number;
172
+ /**
173
+ * Present when a started command's stored outcome kept only the tail: the
174
+ * number of bytes the machine returned, of which `stdout` is the last part.
175
+ */
176
+ outputBytes?: number;
145
177
  }
146
178
  /** The server's ceiling for one exec; documented beside the route. */
147
179
  export declare const DEDICATED_EXEC_MAX_TIMEOUT_MS: number;
180
+ /** The server's default when a command names no timeout. */
181
+ export declare const DEDICATED_EXEC_DEFAULT_TIMEOUT_MS: number;
148
182
  /**
149
- * Run one command on the machine as the workload user.
183
+ * Run one command on the machine as the workload user, in ONE request.
184
+ *
185
+ * The synchronous route, kept for servers that predate `executions`. It holds
186
+ * the request open while the command runs, so behind CloudFront a command that
187
+ * runs past ~60 s is cut off at the edge while it keeps running (OSK-8705).
188
+ * `runDedicatedExec` (dedicated-exec.ts) starts and observes instead, and falls
189
+ * back to this only when the server has no `executions` route.
150
190
  *
151
191
  * Not idempotent and not retried: `apiFetch` gives each call a fresh
152
192
  * idempotency key, which is the rule for an unfenced write — running the same
153
193
  * command twice is two intents even when the text is identical.
154
194
  */
155
195
  export declare function execDedicatedRuntime(leaseId: string, input: DedicatedRuntimeExecInput, scope?: DedicatedRuntimeScope): Promise<DedicatedRuntimeExecResult>;
196
+ export type DedicatedRuntimeExecutionState = 'running' | 'completed' | 'failed' | 'unknown';
197
+ /** A started command, as `GET .../executions/:id` reports it. */
198
+ export interface DedicatedRuntimeExecution {
199
+ id: string;
200
+ state: DedicatedRuntimeExecutionState;
201
+ startedAt: string;
202
+ timeoutMs: number;
203
+ completedAt?: string;
204
+ /** `completed`: exactly what the synchronous exec route returns. */
205
+ result?: DedicatedRuntimeExecResult;
206
+ /** `failed` / `unknown`: the status and body the synchronous route would have answered with. */
207
+ error?: {
208
+ status?: number;
209
+ code?: string;
210
+ error?: string;
211
+ message?: string;
212
+ } & Record<string, unknown>;
213
+ }
214
+ export interface DedicatedRuntimeExecutionResponse {
215
+ execution: DedicatedRuntimeExecution;
216
+ replayed?: boolean;
217
+ }
218
+ /**
219
+ * Start one command. `requestId` is the idempotency key: the same id, for the
220
+ * same command on the same lease, returns the execution it already started —
221
+ * so a retry after a lost response never runs the command twice.
222
+ */
223
+ export declare function startDedicatedRuntimeExecution(leaseId: string, input: DedicatedRuntimeExecInput, options: {
224
+ requestId: string;
225
+ signal?: AbortSignal;
226
+ } & DedicatedRuntimeScope): Promise<DedicatedRuntimeExecutionResponse>;
227
+ /** Read a started command, letting the server hold the answer up to `waitMs` for it to end. */
228
+ export declare function observeDedicatedRuntimeExecution(leaseId: string, executionId: string, options: {
229
+ waitMs: number;
230
+ signal?: AbortSignal;
231
+ } & DedicatedRuntimeScope): Promise<DedicatedRuntimeExecutionResponse>;
156
232
  /** Mirrors the daemon's bounds (daemon/src/tools/file-transfer.ts). */
157
233
  export declare const DEDICATED_FILE_CHUNK_BYTES: number;
158
234
  export declare const DEDICATED_FILE_MAX_BYTES: number;
@@ -219,6 +295,8 @@ export declare function dedicatedSpendSummary(lease: DedicatedLease): string;
219
295
  * lease that is still `requested` has no resources, daemon or spend yet, and a
220
296
  * missing value is omitted rather than printed as a column of dashes.
221
297
  */
298
+ /** One line saying what a deletion still allows. */
299
+ export declare function describeDedicatedDeletion(deletion: DedicatedLeaseDeletion, leaseId?: string): string;
222
300
  export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?: readonly Harness[]): string[];
223
301
  /**
224
302
  * Every harness row a Dedicated Runtime lease's daemon registered, freshest
@@ -231,6 +309,17 @@ export declare function describeDedicatedLease(lease: DedicatedLease, harnesses?
231
309
  * when it announces none), all sharing the daemon id.
232
310
  */
233
311
  export declare function dedicatedLeaseHarnesses(lease: Pick<DedicatedLease, 'id'>, harnesses: readonly Harness[]): Harness[];
312
+ /**
313
+ * The lease of the machine this process is running on, from the harness rows.
314
+ *
315
+ * Nothing in a workload process's environment names its lease — the daemon's
316
+ * environment (lease, generation, credentials) sits on the other side of the uid
317
+ * split by design. The CLI hand-off descriptor the daemon publishes to the
318
+ * workload group does carry the daemon's id, and every harness row that daemon
319
+ * registered carries `metadata.machineLeaseId`. Joining the two answers "which
320
+ * machine am I" with no new daemon surface.
321
+ */
322
+ export declare function dedicatedLeaseIdForDaemon(daemonId: string | undefined | null, harnesses: readonly Harness[]): string | null;
234
323
  /**
235
324
  * The row an interactive terminal opens on: online, local, with a daemon.
236
325
  *