@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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEDICATED_FILE_MAX_BYTES = exports.DEDICATED_FILE_CHUNK_BYTES = exports.DEDICATED_EXEC_MAX_TIMEOUT_MS = exports.DEDICATED_MACHINE_PRODUCT = exports.DEDICATED_LEASE_ACTIONS = void 0;
3
+ exports.DEDICATED_FILE_MAX_BYTES = exports.DEDICATED_FILE_CHUNK_BYTES = exports.DEDICATED_EXEC_DEFAULT_TIMEOUT_MS = exports.DEDICATED_EXEC_MAX_TIMEOUT_MS = exports.DEDICATED_MACHINE_PRODUCT = exports.DEDICATED_LEASE_ACTIONS = void 0;
4
4
  exports.dedicatedRuntimeCreateInput = dedicatedRuntimeCreateInput;
5
5
  exports.isDedicatedRepositoryRef = isDedicatedRepositoryRef;
6
6
  exports.createDedicatedRuntime = createDedicatedRuntime;
@@ -8,10 +8,13 @@ exports.listDedicatedRuntimes = listDedicatedRuntimes;
8
8
  exports.getDedicatedRuntime = getDedicatedRuntime;
9
9
  exports.performDedicatedLeaseAction = performDedicatedLeaseAction;
10
10
  exports.growDedicatedRuntime = growDedicatedRuntime;
11
+ exports.restoreDeletedDedicatedRuntime = restoreDeletedDedicatedRuntime;
11
12
  exports.setDedicatedRuntimeSpendingLimit = setDedicatedRuntimeSpendingLimit;
12
13
  exports.closeDedicatedRuntimeIncidentHold = closeDedicatedRuntimeIncidentHold;
13
14
  exports.destroyDedicatedRuntime = destroyDedicatedRuntime;
14
15
  exports.execDedicatedRuntime = execDedicatedRuntime;
16
+ exports.startDedicatedRuntimeExecution = startDedicatedRuntimeExecution;
17
+ exports.observeDedicatedRuntimeExecution = observeDedicatedRuntimeExecution;
15
18
  exports.readDedicatedRuntimeFileChunk = readDedicatedRuntimeFileChunk;
16
19
  exports.writeDedicatedRuntimeFileChunk = writeDedicatedRuntimeFileChunk;
17
20
  exports.parseDedicatedCopyEndpoints = parseDedicatedCopyEndpoints;
@@ -20,14 +23,17 @@ exports.dedicatedLeaseState = dedicatedLeaseState;
20
23
  exports.dedicatedLeaseDisplayName = dedicatedLeaseDisplayName;
21
24
  exports.formatUsdCents = formatUsdCents;
22
25
  exports.dedicatedSpendSummary = dedicatedSpendSummary;
26
+ exports.describeDedicatedDeletion = describeDedicatedDeletion;
23
27
  exports.describeDedicatedLease = describeDedicatedLease;
24
28
  exports.dedicatedLeaseHarnesses = dedicatedLeaseHarnesses;
29
+ exports.dedicatedLeaseIdForDaemon = dedicatedLeaseIdForDaemon;
25
30
  exports.dedicatedTerminalHarness = dedicatedTerminalHarness;
26
31
  exports.selectDedicatedAttachHarness = selectDedicatedAttachHarness;
27
32
  exports.dedicatedCatalogFromProducts = dedicatedCatalogFromProducts;
28
33
  exports.dedicatedRuntimeApiErrorCode = dedicatedRuntimeApiErrorCode;
29
34
  exports.formatDedicatedRuntimeApiError = formatDedicatedRuntimeApiError;
30
35
  const api_fetch_1 = require("./api-fetch");
36
+ const edge_error_page_1 = require("./edge-error-page");
31
37
  exports.DEDICATED_LEASE_ACTIONS = [
32
38
  'start',
33
39
  'stop',
@@ -85,11 +91,20 @@ function isDedicatedRepositoryRef(value) {
85
91
  // unreachable by domain-api-parity while being perfectly reachable, which is the
86
92
  // reading that trains people to allowlist things that are fine. Same shape as
87
93
  // `withQuery` on the `request.*` side.
88
- function withDedicatedScope(path, scope) {
89
- if (!scope.workspaceId)
94
+ //
95
+ // Query parameters therefore go in `query`, never into the literal: the coverage
96
+ // reader matches the literal against the route table, and a `?name=` inside it
97
+ // makes the endpoint read as unreachable.
98
+ function withDedicatedScope(path, scope, query = {}) {
99
+ const params = Object.entries(query);
100
+ if (scope.workspaceId)
101
+ params.push(['workspaceId', scope.workspaceId]);
102
+ if (params.length === 0)
90
103
  return path;
91
104
  const separator = path.includes('?') ? '&' : '?';
92
- return `${path}${separator}workspaceId=${encodeURIComponent(scope.workspaceId)}`;
105
+ return `${path}${separator}${params
106
+ .map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
107
+ .join('&')}`;
93
108
  }
94
109
  async function createDedicatedRuntime(input) {
95
110
  const { requestId, workspaceId, ...body } = input;
@@ -117,6 +132,25 @@ async function growDedicatedRuntime(leaseId, storageGb, options = {}) {
117
132
  ...(options.requestId ? { headers: { 'Idempotency-Key': options.requestId } } : {}),
118
133
  });
119
134
  }
135
+ /**
136
+ * Restore a deleted Dedicated Runtime into a NEW machine, from the final recovery
137
+ * point its deletion kept. Only inside the deletion's recovery window, and once
138
+ * per deletion; the response's `lease` is the new machine. The cap defaults to
139
+ * the deleted machine's.
140
+ */
141
+ async function restoreDeletedDedicatedRuntime(leaseId, options = {}) {
142
+ const body = {};
143
+ if (options.displayName)
144
+ body.displayName = options.displayName;
145
+ if (options.monthlySpendingLimitCents !== undefined) {
146
+ body.monthlySpendingLimitCents = options.monthlySpendingLimitCents;
147
+ }
148
+ return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/restore`, options), {
149
+ method: 'POST',
150
+ body,
151
+ ...(options.requestId ? { headers: { 'Idempotency-Key': options.requestId } } : {}),
152
+ });
153
+ }
120
154
  /**
121
155
  * Change the monthly spending cap of one lease.
122
156
  *
@@ -143,8 +177,16 @@ async function destroyDedicatedRuntime(leaseId, options = {}) {
143
177
  }
144
178
  /** The server's ceiling for one exec; documented beside the route. */
145
179
  exports.DEDICATED_EXEC_MAX_TIMEOUT_MS = 10 * 60 * 1000;
180
+ /** The server's default when a command names no timeout. */
181
+ exports.DEDICATED_EXEC_DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
146
182
  /**
147
- * 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.
148
190
  *
149
191
  * Not idempotent and not retried: `apiFetch` gives each call a fresh
150
192
  * idempotency key, which is the rule for an unfenced write — running the same
@@ -160,6 +202,27 @@ async function execDedicatedRuntime(leaseId, input, scope = {}) {
160
202
  },
161
203
  });
162
204
  }
205
+ /**
206
+ * Start one command. `requestId` is the idempotency key: the same id, for the
207
+ * same command on the same lease, returns the execution it already started —
208
+ * so a retry after a lost response never runs the command twice.
209
+ */
210
+ async function startDedicatedRuntimeExecution(leaseId, input, options) {
211
+ return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/executions`, options), {
212
+ method: 'POST',
213
+ body: {
214
+ command: input.command,
215
+ ...(input.cwd ? { cwd: input.cwd } : {}),
216
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
217
+ },
218
+ headers: { 'X-Idempotency-Key': options.requestId },
219
+ ...(options.signal ? { signal: options.signal } : {}),
220
+ });
221
+ }
222
+ /** Read a started command, letting the server hold the answer up to `waitMs` for it to end. */
223
+ async function observeDedicatedRuntimeExecution(leaseId, executionId, options) {
224
+ return (0, api_fetch_1.apiFetch)(withDedicatedScope(`/api/machines/dedicated/${encodeURIComponent(leaseId)}/executions/${encodeURIComponent(executionId)}`, options, { waitMs: String(Math.max(0, Math.floor(options.waitMs))) }), options.signal ? { signal: options.signal } : {});
225
+ }
163
226
  /** Mirrors the daemon's bounds (daemon/src/tools/file-transfer.ts). */
164
227
  exports.DEDICATED_FILE_CHUNK_BYTES = 512 * 1024;
165
228
  exports.DEDICATED_FILE_MAX_BYTES = 64 * 1024 * 1024;
@@ -234,6 +297,22 @@ function dedicatedSpendSummary(lease) {
234
297
  * lease that is still `requested` has no resources, daemon or spend yet, and a
235
298
  * missing value is omitted rather than printed as a column of dashes.
236
299
  */
300
+ /** One line saying what a deletion still allows. */
301
+ function describeDedicatedDeletion(deletion, leaseId) {
302
+ if (deletion.restoredToLeaseId) {
303
+ return `restored into ${deletion.restoredToLeaseId}`;
304
+ }
305
+ if (deletion.status === 'completed') {
306
+ return `permanently deleted${deletion.completedAt ? ` ${deletion.completedAt}` : ''}; no copy remains`;
307
+ }
308
+ if (deletion.status === 'purging') {
309
+ return 'recovery window closed; every copy is being deleted';
310
+ }
311
+ if (deletion.restorable) {
312
+ return `restorable until ${deletion.recoverableUntil} — \`skrr machines dedicated restore ${leaseId || '<lease-id>'}\``;
313
+ }
314
+ return 'nothing to restore; every copy is deleted when the window closes';
315
+ }
237
316
  function describeDedicatedLease(lease, harnesses = []) {
238
317
  const lines = [dedicatedLeaseDisplayName(lease)];
239
318
  const row = (label, value) => {
@@ -291,6 +370,11 @@ function describeDedicatedLease(lease, harnesses = []) {
291
370
  .filter(Boolean)
292
371
  .join(' · '));
293
372
  }
373
+ const deletion = storage.deletion;
374
+ if (deletion?.status) {
375
+ row('deleted', describeDedicatedDeletion(deletion, dedicatedLeaseId(lease)));
376
+ }
377
+ row('restored from', lease.recovery?.restoredFromLeaseId);
294
378
  const hold = lease.recovery?.incidentHold;
295
379
  if (hold?.status === 'open') {
296
380
  row('incident', `hold open since ${hold.heldAt || '?'} — the failed source volume is billed until \`incident-close\``);
@@ -328,6 +412,29 @@ function dedicatedLeaseHarnesses(lease, harnesses) {
328
412
  })
329
413
  .sort((a, b) => seenAt(b) - seenAt(a));
330
414
  }
415
+ /**
416
+ * The lease of the machine this process is running on, from the harness rows.
417
+ *
418
+ * Nothing in a workload process's environment names its lease — the daemon's
419
+ * environment (lease, generation, credentials) sits on the other side of the uid
420
+ * split by design. The CLI hand-off descriptor the daemon publishes to the
421
+ * workload group does carry the daemon's id, and every harness row that daemon
422
+ * registered carries `metadata.machineLeaseId`. Joining the two answers "which
423
+ * machine am I" with no new daemon surface.
424
+ */
425
+ function dedicatedLeaseIdForDaemon(daemonId, harnesses) {
426
+ if (!daemonId)
427
+ return null;
428
+ const leaseIds = new Set(harnesses
429
+ .filter((harness) => harness.daemonId === daemonId &&
430
+ harness.metadata?.machineProduct === exports.DEDICATED_MACHINE_PRODUCT &&
431
+ typeof harness.metadata?.machineLeaseId === 'string')
432
+ .map((harness) => String(harness.metadata?.machineLeaseId)));
433
+ // One daemon serves one lease. Two would mean rows from a replaced
434
+ // generation disagree, and guessing between them is how `self` would act on
435
+ // the wrong machine.
436
+ return leaseIds.size === 1 ? [...leaseIds][0] : null;
437
+ }
331
438
  /**
332
439
  * The row an interactive terminal opens on: online, local, with a daemon.
333
440
  *
@@ -485,6 +592,10 @@ function dedicatedRuntimeApiErrorCode(err) {
485
592
  if (!(err instanceof api_fetch_1.ApiFetchError)) {
486
593
  return undefined;
487
594
  }
595
+ const edge = (0, edge_error_page_1.describeEdgeErrorPage)({ status: err.status, body: err.body, method: err.method });
596
+ if (edge) {
597
+ return edge.code;
598
+ }
488
599
  try {
489
600
  const parsed = JSON.parse(err.body || '');
490
601
  if (typeof parsed.code === 'string' && parsed.code) {
@@ -503,6 +614,12 @@ function formatDedicatedRuntimeApiError(err) {
503
614
  if (err.status === 401) {
504
615
  return null;
505
616
  }
617
+ // An HTML page is the edge answering in the API's place — a WAF block or a
618
+ // gateway timeout. Printing its markup told nobody anything (OSK-8700).
619
+ const edge = (0, edge_error_page_1.describeEdgeErrorPage)({ status: err.status, body: err.body, method: err.method });
620
+ if (edge) {
621
+ return edge.message;
622
+ }
506
623
  const notFound = describeLeaseNotFound(err);
507
624
  if (notFound) {
508
625
  return notFound;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * edge-error-page.ts — say what an HTML error page from the EDGE means, instead
3
+ * of printing it.
4
+ *
5
+ * Every API route answers in JSON. An HTML page in an error response therefore
6
+ * did not come from the API: it is CloudFront, its web application firewall, or
7
+ * a load balancer answering in the API's place. Printed as-is it is a screen of
8
+ * markup whose one useful line ("Request blocked", a request id) is buried, and
9
+ * treated as an API answer it is misleading in a specific way — a 403 reads as
10
+ * "you lack permission", and CloudFront's 504 page tells the reader to "try again
11
+ * later", which for a write that is still running repeats it.
12
+ *
13
+ * Both happened to `skrr machines dedicated exec` (OSK-8700, OSK-8705): a shell
14
+ * command containing `http://10.0.0.10/` came back as CloudFront's raw
15
+ * "403 ERROR / Request blocked" page, and a command running past 60 seconds as
16
+ * its raw 504 page while it kept running on the machine.
17
+ *
18
+ * General on purpose: nothing here knows which command asked.
19
+ */
20
+ export type EdgeErrorCode = 'EDGE_REQUEST_BLOCKED' | 'EDGE_GATEWAY_TIMEOUT' | 'EDGE_ORIGIN_UNAVAILABLE' | 'EDGE_ERROR_PAGE';
21
+ export interface EdgeErrorPage {
22
+ status: number;
23
+ code: EdgeErrorCode;
24
+ /** The edge's own id for the request (CloudFront prints one), when the page carries it. */
25
+ requestId?: string;
26
+ message: string;
27
+ /** Whether sending the identical request again can help. */
28
+ retryable: boolean;
29
+ }
30
+ /**
31
+ * True when an error response body is an HTML page rather than an API answer.
32
+ *
33
+ * A JSON body is never a page, whatever its text says: an API refusal that
34
+ * quotes "The request could not be satisfied" or an HTML fragment in its message
35
+ * must keep its own words (the base command's handler already gates on "no JSON
36
+ * body"; this makes every caller agree). The CloudFront wording counts only
37
+ * inside markup, never on its own.
38
+ */
39
+ export declare function isHtmlErrorPage(body: string | null | undefined): boolean;
40
+ /**
41
+ * Describe an HTML error page, or return null when the body is not one (a JSON
42
+ * refusal from the API must keep its own words).
43
+ */
44
+ export declare function describeEdgeErrorPage(input: {
45
+ status: number | undefined;
46
+ body: string | null | undefined;
47
+ method?: string | null;
48
+ host?: string | null;
49
+ }): EdgeErrorPage | null;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * edge-error-page.ts — say what an HTML error page from the EDGE means, instead
4
+ * of printing it.
5
+ *
6
+ * Every API route answers in JSON. An HTML page in an error response therefore
7
+ * did not come from the API: it is CloudFront, its web application firewall, or
8
+ * a load balancer answering in the API's place. Printed as-is it is a screen of
9
+ * markup whose one useful line ("Request blocked", a request id) is buried, and
10
+ * treated as an API answer it is misleading in a specific way — a 403 reads as
11
+ * "you lack permission", and CloudFront's 504 page tells the reader to "try again
12
+ * later", which for a write that is still running repeats it.
13
+ *
14
+ * Both happened to `skrr machines dedicated exec` (OSK-8700, OSK-8705): a shell
15
+ * command containing `http://10.0.0.10/` came back as CloudFront's raw
16
+ * "403 ERROR / Request blocked" page, and a command running past 60 seconds as
17
+ * its raw 504 page while it kept running on the machine.
18
+ *
19
+ * General on purpose: nothing here knows which command asked.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.isHtmlErrorPage = isHtmlErrorPage;
23
+ exports.describeEdgeErrorPage = describeEdgeErrorPage;
24
+ const HTML_START = /^\s*(<!doctype html\b|<html\b)/i;
25
+ const EDGE_MARK = /Generated by cloudfront|The request could not be satisfied/i;
26
+ const REQUEST_ID = /Request ID:\s*([A-Za-z0-9_=-]{8,128})/i;
27
+ const MARKUP = /<(html|head|body|title|h1|pre)\b/i;
28
+ function isJson(body) {
29
+ const trimmed = body.trim();
30
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('['))
31
+ return false;
32
+ try {
33
+ JSON.parse(trimmed);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ /**
41
+ * True when an error response body is an HTML page rather than an API answer.
42
+ *
43
+ * A JSON body is never a page, whatever its text says: an API refusal that
44
+ * quotes "The request could not be satisfied" or an HTML fragment in its message
45
+ * must keep its own words (the base command's handler already gates on "no JSON
46
+ * body"; this makes every caller agree). The CloudFront wording counts only
47
+ * inside markup, never on its own.
48
+ */
49
+ function isHtmlErrorPage(body) {
50
+ if (typeof body !== 'string' || !body || isJson(body))
51
+ return false;
52
+ return HTML_START.test(body) || (MARKUP.test(body) && EDGE_MARK.test(body));
53
+ }
54
+ function readOnly(method) {
55
+ const upper = (method || 'GET').toUpperCase();
56
+ return upper === 'GET' || upper === 'HEAD' || upper === 'OPTIONS';
57
+ }
58
+ function where(host) {
59
+ return host ? ` (${host})` : '';
60
+ }
61
+ /**
62
+ * Describe an HTML error page, or return null when the body is not one (a JSON
63
+ * refusal from the API must keep its own words).
64
+ */
65
+ function describeEdgeErrorPage(input) {
66
+ const { body, method, host } = input;
67
+ const status = Number(input.status) || 0;
68
+ if (!isHtmlErrorPage(body))
69
+ return null;
70
+ const requestId = REQUEST_ID.exec(String(body))?.[1];
71
+ const quote = requestId ? ` Edge request id: ${requestId} — quote it when reporting this.` : '';
72
+ const writeOutcome = readOnly(method)
73
+ ? 'This request changed nothing; retry shortly.'
74
+ : 'For a write, the request may still have been carried out, or may still be running — check before sending it again.';
75
+ if (status === 403) {
76
+ return {
77
+ status,
78
+ code: 'EDGE_REQUEST_BLOCKED',
79
+ ...(requestId ? { requestId } : {}),
80
+ message: `The request was blocked at the edge before it reached the API${where(host)}: a web ` +
81
+ 'application firewall rule matched something in it. This is not a problem with your ' +
82
+ 'account or permissions. Content that resembles an attack can be enough — a URL with an ' +
83
+ 'IP address, a relative path (../), markup or SQL, even inside a command or a document — ' +
84
+ 'and sending the same request again will be blocked again.' +
85
+ quote,
86
+ retryable: false,
87
+ };
88
+ }
89
+ if (status === 504) {
90
+ return {
91
+ status,
92
+ code: 'EDGE_GATEWAY_TIMEOUT',
93
+ ...(requestId ? { requestId } : {}),
94
+ message: `The edge stopped waiting for the API${where(host)} and answered HTTP 504. ` +
95
+ writeOutcome +
96
+ quote,
97
+ retryable: readOnly(method),
98
+ };
99
+ }
100
+ if (status === 502 || status === 503) {
101
+ return {
102
+ status,
103
+ code: 'EDGE_ORIGIN_UNAVAILABLE',
104
+ ...(requestId ? { requestId } : {}),
105
+ message: `The edge could not get an answer from the API${where(host)} (HTTP ${status}). ` +
106
+ writeOutcome +
107
+ quote,
108
+ retryable: readOnly(method),
109
+ };
110
+ }
111
+ return {
112
+ status,
113
+ code: 'EDGE_ERROR_PAGE',
114
+ ...(requestId ? { requestId } : {}),
115
+ message: `The request did not get an API response: something in front of the API${where(host)} ` +
116
+ `answered HTTP ${status || 'error'} with an HTML page.` +
117
+ quote,
118
+ retryable: false,
119
+ };
120
+ }
@@ -111,7 +111,15 @@ export interface HarnessLease {
111
111
  createdAt?: string;
112
112
  updatedAt?: string;
113
113
  }
114
- /** `pause | resume | destroy | terminate | health-check` (MachineLeaseService). */
114
+ /**
115
+ * Every action `POST /api/machines/leases/:id/actions/:action` accepts —
116
+ * `MACHINE_LEASE_ACTIONS` in MachineLeaseService. Which of them a given lease
117
+ * supports is its product's `lifecycleCapabilities`, decided by the server.
118
+ *
119
+ * `start`, `stop`, `restart` and `snapshot` were missing, so this command
120
+ * refused every Dedicated Runtime lifecycle verb at argument parsing — before
121
+ * the server that accepts them was asked — while claiming to mirror its set.
122
+ */
115
123
  export declare const HARNESS_LEASE_ACTIONS: string[];
116
124
  /** `user` (the default) or `workspace`. */
117
125
  export type HarnessDefaultScope = 'user' | 'workspace';
@@ -23,8 +23,26 @@ exports.releaseHarnessLease = releaseHarnessLease;
23
23
  exports.performHarnessLeaseAction = performHarnessLeaseAction;
24
24
  exports.setHarnessLeaseBilling = setHarnessLeaseBilling;
25
25
  const api_fetch_1 = require("./api-fetch");
26
- /** `pause | resume | destroy | terminate | health-check` (MachineLeaseService). */
27
- exports.HARNESS_LEASE_ACTIONS = ['pause', 'resume', 'destroy', 'terminate', 'health-check'];
26
+ /**
27
+ * Every action `POST /api/machines/leases/:id/actions/:action` accepts —
28
+ * `MACHINE_LEASE_ACTIONS` in MachineLeaseService. Which of them a given lease
29
+ * supports is its product's `lifecycleCapabilities`, decided by the server.
30
+ *
31
+ * `start`, `stop`, `restart` and `snapshot` were missing, so this command
32
+ * refused every Dedicated Runtime lifecycle verb at argument parsing — before
33
+ * the server that accepts them was asked — while claiming to mirror its set.
34
+ */
35
+ exports.HARNESS_LEASE_ACTIONS = [
36
+ 'pause',
37
+ 'resume',
38
+ 'start',
39
+ 'stop',
40
+ 'restart',
41
+ 'snapshot',
42
+ 'destroy',
43
+ 'terminate',
44
+ 'health-check',
45
+ ];
28
46
  // ── Fleet reads ────────────────────────────────────────────────────────────
29
47
  async function listHarnesses(filters = {}) {
30
48
  const params = new URLSearchParams();
@@ -25,6 +25,7 @@ exports.execHostedMachine = execHostedMachine;
25
25
  exports.waitForHostedMachineReady = waitForHostedMachineReady;
26
26
  exports.offlineForLabel = offlineForLabel;
27
27
  const api_fetch_1 = require("./api-fetch");
28
+ const edge_error_page_1 = require("./edge-error-page");
28
29
  function curateHostedMachineList(response) {
29
30
  return {
30
31
  object: 'list',
@@ -148,6 +149,12 @@ function formatHostedMachineApiError(err) {
148
149
  if (err.status === 401) {
149
150
  return null;
150
151
  }
152
+ // An HTML page is the edge answering in the API's place; describe it rather
153
+ // than printing its markup (OSK-8700).
154
+ const edge = (0, edge_error_page_1.describeEdgeErrorPage)({ status: err.status, body: err.body, method: err.method });
155
+ if (edge) {
156
+ return edge.message;
157
+ }
151
158
  try {
152
159
  const parsed = JSON.parse(err.body);
153
160
  return parsed.message || parsed.error || parsed.code || null;
@@ -41331,6 +41331,18 @@ var nI = { get: function(e2, t2) {
41331
41331
  }
41332
41332
  });
41333
41333
  });
41334
+ }, patchWithOptions: function(e2, t2, o2) {
41335
+ return v(this, void 0, void 0, function() {
41336
+ var r2;
41337
+ return h(this, function(a2) {
41338
+ switch (a2.label) {
41339
+ case 0:
41340
+ return r2 = zk("PATCH", e2, t2, null == o2 ? void 0 : o2.headers), Mk ? [2, Mk.patch(e2, t2, Ok(f(f({}, o2), { headers: r2 })))] : [4, n.patch(e2, JSON.stringify(t2), f(f({}, o2), { headers: f({ "Content-Type": "application/json" }, r2) }))];
41341
+ case 1:
41342
+ return [2, a2.sent().data];
41343
+ }
41344
+ });
41345
+ });
41334
41346
  }, refreshToken: Zk, dispatchTokenUpdatedEvent: eI, broadcastLogout: $k, classifyRefreshFailure: Xk, noteRefreshSuccess: function() {
41335
41347
  Yk();
41336
41348
  } };