mandala-computer-mcp 0.1.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +544 -0
  3. package/dist/api.d.ts +186 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +932 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/cli.d.ts +55 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +292 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/errors.d.ts +560 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +873 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/events.d.ts +406 -0
  16. package/dist/events.d.ts.map +1 -0
  17. package/dist/events.js +1679 -0
  18. package/dist/events.js.map +1 -0
  19. package/dist/format.d.ts +125 -0
  20. package/dist/format.d.ts.map +1 -0
  21. package/dist/format.js +180 -0
  22. package/dist/format.js.map +1 -0
  23. package/dist/http.d.ts +46 -0
  24. package/dist/http.d.ts.map +1 -0
  25. package/dist/http.js +792 -0
  26. package/dist/http.js.map +1 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +12 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/paths.d.ts +394 -0
  32. package/dist/paths.d.ts.map +1 -0
  33. package/dist/paths.js +677 -0
  34. package/dist/paths.js.map +1 -0
  35. package/dist/server.d.ts +18 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +97 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/session.d.ts +78 -0
  40. package/dist/session.d.ts.map +1 -0
  41. package/dist/session.js +166 -0
  42. package/dist/session.js.map +1 -0
  43. package/dist/stdio.d.ts +11 -0
  44. package/dist/stdio.d.ts.map +1 -0
  45. package/dist/stdio.js +43 -0
  46. package/dist/stdio.js.map +1 -0
  47. package/dist/tools/agent.d.ts +16 -0
  48. package/dist/tools/agent.d.ts.map +1 -0
  49. package/dist/tools/agent.js +147 -0
  50. package/dist/tools/agent.js.map +1 -0
  51. package/dist/tools/computers.d.ts +3 -0
  52. package/dist/tools/computers.d.ts.map +1 -0
  53. package/dist/tools/computers.js +1037 -0
  54. package/dist/tools/computers.js.map +1 -0
  55. package/dist/tools/events.d.ts +3 -0
  56. package/dist/tools/events.d.ts.map +1 -0
  57. package/dist/tools/events.js +1077 -0
  58. package/dist/tools/events.js.map +1 -0
  59. package/dist/tools/guest.d.ts +3 -0
  60. package/dist/tools/guest.d.ts.map +1 -0
  61. package/dist/tools/guest.js +761 -0
  62. package/dist/tools/guest.js.map +1 -0
  63. package/dist/tools/input.d.ts +3 -0
  64. package/dist/tools/input.d.ts.map +1 -0
  65. package/dist/tools/input.js +240 -0
  66. package/dist/tools/input.js.map +1 -0
  67. package/dist/tools/snapshots.d.ts +3 -0
  68. package/dist/tools/snapshots.d.ts.map +1 -0
  69. package/dist/tools/snapshots.js +333 -0
  70. package/dist/tools/snapshots.js.map +1 -0
  71. package/dist/tools/templates.d.ts +3 -0
  72. package/dist/tools/templates.d.ts.map +1 -0
  73. package/dist/tools/templates.js +492 -0
  74. package/dist/tools/templates.js.map +1 -0
  75. package/dist/tools/types.d.ts +18 -0
  76. package/dist/tools/types.d.ts.map +1 -0
  77. package/dist/tools/types.js +2 -0
  78. package/dist/tools/types.js.map +1 -0
  79. package/dist/tools/webhooks.d.ts +3 -0
  80. package/dist/tools/webhooks.d.ts.map +1 -0
  81. package/dist/tools/webhooks.js +260 -0
  82. package/dist/tools/webhooks.js.map +1 -0
  83. package/package.json +59 -0
@@ -0,0 +1,560 @@
1
+ /**
2
+ * What the platform's status codes mean, as types.
3
+ *
4
+ * The distinctions here are the ones an agent has to act on and cannot infer
5
+ * from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
6
+ * limit, which no amount of waiting fixes and which the user — not the model —
7
+ * has to resolve. A 409 is the one that is not uniform: most of them are a
8
+ * passing state and worth retrying, and some are a decision about the request
9
+ * that no retry turns into a yes — see {@link ConflictError} and
10
+ * {@link MoveRequiredError}.
11
+ *
12
+ * Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
13
+ * deliberately so: two clients disagreeing about what a 402 is means the same
14
+ * failure reads differently depending on which one you reached for.
15
+ */
16
+ export declare class MandalaError extends Error {
17
+ name: string;
18
+ }
19
+ export declare class APIError extends MandalaError {
20
+ readonly status: number;
21
+ readonly body?: unknown | undefined;
22
+ name: string;
23
+ /**
24
+ * The platform's own word for what KIND of refusal this is, where it sent
25
+ * one: `contention`, `starting`, `unavailable` or `unsupported` (OPL-3898).
26
+ * `undefined` for most errors, and always will be — the platform is explicit
27
+ * that an absent value means unclassified rather than "none of the four".
28
+ *
29
+ * Read on the base class rather than on the one 409 it was filed for, because
30
+ * the platform keys it on the ERROR and not on the route: the same sentinel is
31
+ * reached from several endpoints, and `unavailable` arrives as a 400 as well
32
+ * as a 409 — whoever loses the race to the running check hears the same fact
33
+ * the caller who arrived a moment earlier heard.
34
+ */
35
+ readonly reason?: string;
36
+ constructor(message: string, status: number, body?: unknown | undefined);
37
+ }
38
+ /**
39
+ * What to tell a model about a refusal the platform classified, or `undefined`.
40
+ *
41
+ * The word itself is for a program, and the client here is a language model that
42
+ * cannot switch on a JSON key it never sees: {@link failed} renders an error as
43
+ * one sentence. So the classification travels as the clause it means, and the
44
+ * loop OPL-3898 was filed about — a blanket retry against a computer that is
45
+ * simply stopped — is the one these sentences exist to stop.
46
+ *
47
+ * Deliberately silent about anything else. A word this version does not know is
48
+ * not described at all, because inventing advice for it is the mistake the
49
+ * platform's "absent means unclassified" contract exists to prevent.
50
+ */
51
+ export declare function reasonAdvice(reason: string | undefined): string | undefined;
52
+ /** 401 — the key is missing, malformed, or revoked. */
53
+ export declare class AuthenticationError extends APIError {
54
+ name: string;
55
+ }
56
+ /** 402 — the account's plan will not allow this. Not a retry. */
57
+ export declare class PlanLimitError extends APIError {
58
+ name: string;
59
+ }
60
+ /** 403 — the key's role on the account is too low for this route. */
61
+ export declare class PermissionDeniedError extends APIError {
62
+ name: string;
63
+ }
64
+ /** 404 — no such computer, snapshot, or route. */
65
+ export declare class NotFoundError extends APIError {
66
+ name: string;
67
+ }
68
+ /**
69
+ * 409 — the thing exists but is in the wrong state for this right now.
70
+ *
71
+ * The one status on this list that is usually temporary: a guest still booting,
72
+ * a guest agent busy with another call, a desktop session that has not come up
73
+ * yet. Worth retrying; the others on this list are not.
74
+ *
75
+ * USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
76
+ * 409 clears is a property of the BODY and not of the status: some describe a
77
+ * passing state, and some describe a decision about the request — the size does
78
+ * not fit, the computer is the wrong one for this, the saved session cannot
79
+ * travel — which no amount of retrying turns into a yes. This class said
80
+ * "worth retrying" flatly, {@link isTransient} agreed with it, and that
81
+ * predicate is exported, so a host application wrapping a resize in
82
+ * `if (isTransient(err)) retry()` looped on a refusal that was never going to
83
+ * move (OPL-3775).
84
+ *
85
+ * Only the refusal that could be acted on has been given a class of its own so
86
+ * far, because a type is worth adding where a caller can DO something different
87
+ * with it. The rest stay here and are told apart by {@link APIError.reason},
88
+ * which is the word the platform added for that purpose (OPL-3898) and the one
89
+ * its own reference now says to switch on — never the sentence, which is prose
90
+ * written for a person and rewritten whenever a better one is. Where no word was
91
+ * sent this class means what it always did, and that fallback is the contract
92
+ * rather than a gap: not every refusal here has an answer yet.
93
+ */
94
+ export declare class ConflictError extends APIError {
95
+ name: string;
96
+ }
97
+ /**
98
+ * The 409 that is an OFFER rather than a refusal: a resize needs the computer
99
+ * moved to another host first.
100
+ *
101
+ * `PATCH /computers/{id}` growing `ram_mb` past what the computer's current host
102
+ * can run answers 409 with a `move` object on the body rather than only a
103
+ * sentence — `{"required":true,"possible":true}` means somewhere else in the
104
+ * region could run that size, and `POST /computers/{id}/move` is how a caller
105
+ * agrees to go there. `possible:false` means nowhere in the region can, and the
106
+ * size is the thing to change.
107
+ *
108
+ * Its own class for the reason {@link RangeNotSatisfiableError} has one: it is a
109
+ * refusal the caller can correct without knowing anything it did not just learn,
110
+ * and the correction is a different call rather than a smaller number. Reaching
111
+ * it through a bare `ConflictError` left the flag on `body` where nothing looked
112
+ * for it, and left the retry predicate saying yes.
113
+ *
114
+ * {@link movePossible} is the branch, and it is deliberately read off the body
115
+ * here rather than left to every caller: `move.required` is true in both cases
116
+ * and it is the second field that decides what to do.
117
+ */
118
+ export declare class MoveRequiredError extends ConflictError {
119
+ /** Whether a host in this region could run the size that was asked for. */
120
+ readonly movePossible: boolean;
121
+ name: string;
122
+ constructor(message: string, status: number, body: unknown,
123
+ /** Whether a host in this region could run the size that was asked for. */
124
+ movePossible: boolean);
125
+ }
126
+ /**
127
+ * 416 — the `Range` named no byte the file has.
128
+ *
129
+ * Its own class because it is the one refusal on the download route that a
130
+ * caller can correct without knowing anything it did not just learn: the
131
+ * response carries `Content-Range: bytes *\/<size>`, so the file's real length
132
+ * arrives with the complaint about the offset. {@link size} is that number,
133
+ * kept off the message so a tool can put the offset it sent beside it.
134
+ *
135
+ * A model paging a file it has not measured is the caller that meets this, and
136
+ * an offset past the end is the mistake it will actually make.
137
+ */
138
+ export declare class RangeNotSatisfiableError extends APIError {
139
+ /** The file's real length, off `Content-Range`, when the response sent one. */
140
+ readonly size?: number | undefined;
141
+ name: string;
142
+ constructor(message: string, status: number, body?: unknown,
143
+ /** The file's real length, off `Content-Range`, when the response sent one. */
144
+ size?: number | undefined);
145
+ }
146
+ /**
147
+ * 429 — the request is valid; the caller has spent a temporary rate budget.
148
+ *
149
+ * A class rather than a number in a list, and that is the OPL-3724 change in
150
+ * one line: {@link isTransient} used to reach 429 by matching `err.status`,
151
+ * which meant this client answered "is it worth retrying" by a mechanism the
152
+ * other two did not share. It is a moment rather than a property of the
153
+ * request, exactly like a 409, and it belongs in the same shape.
154
+ *
155
+ * {@link retryAfterMs} is set when the response carried a usable `Retry-After`.
156
+ * The poll loops honour it, because retrying at their own faster cadence is
157
+ * how a rate limit becomes a longer one.
158
+ */
159
+ export declare class RateLimitError extends APIError {
160
+ /** From `Retry-After`, in milliseconds from now, when the response sent one. */
161
+ readonly retryAfterMs?: number | undefined;
162
+ name: string;
163
+ constructor(message: string, status: number, body?: unknown,
164
+ /** From `Retry-After`, in milliseconds from now, when the response sent one. */
165
+ retryAfterMs?: number | undefined);
166
+ }
167
+ /** 503 — a hypervisor could not be reached, so an inventory would be short. */
168
+ export declare class UnavailableError extends APIError {
169
+ name: string;
170
+ }
171
+ /**
172
+ * The request was given up on before the platform answered.
173
+ *
174
+ * Not an API failure and not a connectivity failure, which is the whole reason
175
+ * it has a type: an aborted fetch surfaces from the client as a bare
176
+ * `TypeError: This operation was aborted`, and wrapping that as "could not
177
+ * reach <host>" told every reader the platform was down when in fact the caller
178
+ * had hung up. A cancellation is also the one failure here where the request
179
+ * may well have been received and acted on, so the message says so rather than
180
+ * claiming nothing happened.
181
+ *
182
+ * Deliberately not transient: retrying something nobody is waiting for is the
183
+ * behaviour `with(signal)` exists to stop.
184
+ */
185
+ export declare class CancelledError extends MandalaError {
186
+ name: string;
187
+ }
188
+ /**
189
+ * The request never left. Nothing was dispatched, so anything may be replayed.
190
+ *
191
+ * NARROWER than it used to be, and the narrowing is the point. This class once
192
+ * wrapped every rejection the transport produced, which meant it also carried
193
+ * the failures that happen AFTER the request reached the platform — a socket
194
+ * reset while the response body was being read, a protocol error on the way
195
+ * back. Those wear the opposite outcome: the platform may well have acted, and
196
+ * the answer is what was lost. They now get {@link ConnectivityInterruptedError},
197
+ * which is a subclass, so `catch (e) { if (e instanceof ConnectivityError) }`
198
+ * still sees both.
199
+ *
200
+ * What is left here is what the name always claimed: DNS that did not resolve,
201
+ * a socket that was refused, a connect that timed out, a TLS handshake that
202
+ * failed. Not one byte of the request was written, so {@link isTransient} can
203
+ * say yes to it even for a caller replaying a create.
204
+ *
205
+ * `Api` raises this one only for a cause it can positively identify as
206
+ * connect-phase; see `neverDispatched` in `src/api.ts`. Everything it cannot
207
+ * identify is the subclass, because the cost of the two wrong answers is not
208
+ * symmetric — see there.
209
+ */
210
+ export declare class ConnectivityError extends MandalaError {
211
+ name: string;
212
+ }
213
+ /**
214
+ * The request was dispatched and the answer was lost. Outcome unknown.
215
+ *
216
+ * A socket that resets while the response body is being read, an HTTP parser
217
+ * error on the way back, an undici body or headers timeout, a connection
218
+ * failure this client cannot place in either phase. The shared property is the
219
+ * one that matters: the platform may have received the request and acted on it,
220
+ * and nothing in the error says whether it did.
221
+ *
222
+ * So this is FATAL to {@link isTransient} and transparent to
223
+ * {@link isTransientForPoll}, and the split is the same one OPL-3724 made for
224
+ * 502 and 504. Its words apply here unchanged — "a status that is not safe for
225
+ * the riskiest caller of an exported predicate does not belong in it" — and
226
+ * this case had escaped them only because it wears a class whose name says the
227
+ * request never left. `computers.create()` reaches the platform, the platform
228
+ * builds the computer, the socket dies mid-response: an embedder asking
229
+ * {@link isTransient} used to be told yes, replayed the create, and paid for
230
+ * two computers.
231
+ *
232
+ * A SUBCLASS rather than a sibling, which is what keeps this from breaking
233
+ * anyone. `instanceof ConnectivityError` still matches, so existing catch
234
+ * blocks and {@link isTransientForPoll}'s floor need no change; only the one
235
+ * predicate that promises blind replay had to learn the difference. It is the
236
+ * same shape {@link MoveRequiredError} has under {@link ConflictError}, for the
237
+ * same reason: a case that is genuinely a kind of its parent and genuinely
238
+ * answers one question the other way.
239
+ *
240
+ * The poll predicate still rides it out, and that is not an oversight. The wait
241
+ * tools replay reads — a `GET /computers/:id`, an `exec 'exit 0'` probe — and a
242
+ * read whose outcome was lost can simply be read again. Only a caller who might
243
+ * be replaying a WRITE needs the distinction, which is exactly the caller
244
+ * {@link isTransient} is exported for.
245
+ */
246
+ export declare class ConnectivityInterruptedError extends ConnectivityError {
247
+ name: string;
248
+ }
249
+ /**
250
+ * 504, 524 — a proxy in front of the platform gave up before the platform answered.
251
+ *
252
+ * Not a refusal. The request arrived, is very likely still running, and nothing
253
+ * was cancelled; what ended was one hop's willingness to hold a connection open
254
+ * with no response crossing it.
255
+ *
256
+ * One class, two retry answers, and {@link isTransientForPoll} keeps them apart
257
+ * by status rather than by type on purpose — the ONE place a status number
258
+ * still decides anything, because a type cannot separate two statuses that
259
+ * share it. A 504 is worth polling again and a 524 is not, because of where
260
+ * each is reachable from: the wait tools are the only thing here that retries,
261
+ * they poll with short requests, and a 504 on one of those is infrastructure
262
+ * noise that clears. A 524 is only ever reached by holding a request open past
263
+ * the ceiling below — so retrying it unchanged reproduces it exactly, at the
264
+ * same place, because the hop that gave up never saw how long the caller asked
265
+ * to wait.
266
+ *
267
+ * Neither is in {@link isTransient}. That predicate is what an embedder may
268
+ * wrap a create in, and a gateway timeout is the case where the platform has
269
+ * most likely acted already (OPL-3724).
270
+ *
271
+ * Against `app.mandala.computer` that hop is Cloudflare and the ceiling is about
272
+ * two minutes. Measured 2026-08-20: `sleep 130` died at 125.2s with
273
+ * `timeout_s: 300` and at 125.3s with `timeout_s: 3600`, while `sleep 110`
274
+ * returned normally at 110.6s. A foreground `exec` slower than that always ends
275
+ * here; `background: true` is the shape that does not, because it answers as
276
+ * soon as the command has started.
277
+ *
278
+ * The abandoned command keeps running, which is why the next call on the same
279
+ * computer often raises {@link ConflictError} — the guest agent is still busy
280
+ * with it. That is this failure continuing, not a second one.
281
+ */
282
+ export declare class GatewayTimeoutError extends APIError {
283
+ name: string;
284
+ }
285
+ /**
286
+ * 521-523 — a proxy in front of the platform could not reach it.
287
+ *
288
+ * The range this once claimed — 520-523, 525, 526 — is the range before the two
289
+ * statuses that needed their own answers were split out of it: 520 to
290
+ * {@link OriginResponseError}, because the platform WAS reached, and 525-526 to
291
+ * {@link OriginTLSError}, because waiting does not fix a certificate.
292
+ *
293
+ * The rest of what an edge generates on its own, and the same bug as
294
+ * {@link GatewayTimeoutError} a few statuses along: with no class and no written
295
+ * message these fell through to the bare `HTTP 522`, which names no cause, no
296
+ * culprit and no way out — the exact reading that cost the debugging above.
297
+ *
298
+ * A different event from a gateway timeout, which is why it is a different type
299
+ * rather than more entries on that one. A 524 means the request arrived and is
300
+ * still being worked on; these mean it never arrived at all, so nothing was
301
+ * started and there is no command outliving anything. A caller branching on the
302
+ * class to decide whether its work survived gets opposite answers, correctly.
303
+ *
304
+ * Deliberately absent from mandala-computer-python's `_exceptions.py`, which
305
+ * this file otherwise mirrors. That mapping is of the platform's own statuses;
306
+ * these belong to whatever is deployed in front of it. The divergence is worth
307
+ * it here because this client's messages are read by a model, which cannot go
308
+ * and look up what a 523 is.
309
+ */
310
+ export declare class OriginUnreachableError extends APIError {
311
+ name: string;
312
+ }
313
+ /**
314
+ * 502, 520 — a proxy had no usable answer from the platform.
315
+ *
316
+ * Sits between the other two and must not be filed with either, because the
317
+ * question a caller is really asking is whether their work happened, and these
318
+ * are the statuses whose honest answer is "unknown".
319
+ *
320
+ * One class, two messages, because the two do not know the same amount. A 520 is
321
+ * Cloudflare naming its origin's reply unreadable, so arrival is established. A
322
+ * 502 is any proxy saying it has nothing it can use, which covers both an
323
+ * invalid reply and no reply at all — indistinguishable from here, so it claims
324
+ * neither. See BAD_GATEWAY_MESSAGE.
325
+ *
326
+ * A 524 means the request arrived and is still being worked on. 521-523 mean it
327
+ * never arrived, so nothing was started. A 520 means it **did** arrive — the
328
+ * platform received it and then returned an empty, unknown or oversized
329
+ * response, so it may have been carried out in full, in part, or not at all, and
330
+ * the answer was lost rather than never produced.
331
+ *
332
+ * Which makes a blind retry the thing to be careful about, and a model the
333
+ * likeliest caller to attempt one. Re-sending a read costs nothing; re-sending a
334
+ * create can leave two computers where one was meant, both billable, on the
335
+ * strength of a failure that said the first never happened.
336
+ *
337
+ * It was filed with {@link OriginUnreachableError} at first, on the reading that
338
+ * the whole 52x range is the edge failing to reach the platform. It is not, and
339
+ * the message that came with it — "the request never arrived, so nothing was
340
+ * started" — was exactly the confident falsehood this work exists to remove,
341
+ * pointed the other way. Caught by a review of the Python SDK, which had
342
+ * inherited the same grouping from this file.
343
+ */
344
+ export declare class OriginResponseError extends APIError {
345
+ name: string;
346
+ }
347
+ /**
348
+ * 525, 526 — a proxy and the platform could not agree on TLS.
349
+ *
350
+ * Split from {@link OriginUnreachableError}, which it used to share, because the
351
+ * two need opposite answers to "should I try again". An unreachable origin is a
352
+ * passing outage; an expired or mismatched certificate fails identically on
353
+ * every retry, and is a deployment somebody has to go and fix.
354
+ *
355
+ * The line was already drawn by status number, so the retry behaviour here was
356
+ * unchanged when this class appeared — but a caller reading the TYPE was told
357
+ * the two were the same thing, while the list beside it said they were not.
358
+ *
359
+ * The type is now what draws it: {@link isTransientForPoll} names this class in
360
+ * its fatal set, and 521-523 fall through to the poll (OPL-3724). Which is also
361
+ * why splitting it mattered more than it looked — the mandala-computer-python
362
+ * SDK had the same pairing and named classes in its fatal set, so a wait helper
363
+ * there could not tell 526 from 522 and spent its whole timeout retrying a
364
+ * certificate.
365
+ */
366
+ export declare class OriginTLSError extends APIError {
367
+ name: string;
368
+ }
369
+ /**
370
+ * The sentence a response carried in its own words, or `undefined` for one that
371
+ * carried none — the same test {@link platformNamed} asks, with the answer kept.
372
+ *
373
+ * Exported for the one caller that has to print a message the substitutions
374
+ * above would otherwise have written for it. A tool whose route contradicts the
375
+ * generic wording for a status — `window_action` and a 504, where
376
+ * {@link GatewayTimeoutError}'s tail says the same call again is the move and on
377
+ * that route it is not — cannot append its own paragraph under prose that says
378
+ * the opposite. It needs to know whether the sentence in `message` came from a
379
+ * hop that knew this request, in which case it is worth repeating, or from this
380
+ * file, in which case it is worth replacing (OPL-3910).
381
+ *
382
+ * Reads the BODY rather than the message, deliberately. By the time an error
383
+ * exists the two may differ — that is what the substitutions are — so asking the
384
+ * message whether it is the platform's would be asking the answer to vouch for
385
+ * itself.
386
+ */
387
+ export declare function platformSaid(body: unknown): string | undefined;
388
+ /** Build the error for a status, with the platform's own message when it sent one. */
389
+ export declare function errorForStatus(status: number, message: string, body?: unknown): APIError;
390
+ /**
391
+ * Whether an error is worth trying again without changing the request.
392
+ *
393
+ * The PUBLIC answer, exported from the package, and therefore a contract with
394
+ * embedders rather than a private note to this file. Its caller is a host
395
+ * application wrapping an arbitrary call in `if (isTransient(err)) retry()` —
396
+ * including one that creates something — so it names only failures that both
397
+ * clear on their own AND are safe to replay blind.
398
+ *
399
+ * Answered by TYPE, with no status numbers at all, which is the OPL-3724
400
+ * decision written down. Three clients had drifted into three mechanisms for
401
+ * one question: this file matched classes plus a list of numbers, the
402
+ * TypeScript SDK matched classes alone, and the Python SDK named the fatal
403
+ * exceptions and retried the rest. The status list is what let this one drift,
404
+ * because a number can be added to it without anyone having to say which of the
405
+ * three answers changed. It now reads identically in all three:
406
+ *
407
+ * - {@link ConflictError} — something is in flight that this cannot run
408
+ * alongside, minus the one that is a decision
409
+ * - {@link RateLimitError} — a cadence, and the response usually says how long
410
+ * - {@link UnavailableError} — a hypervisor briefly out of reach
411
+ * - {@link ConnectivityError} — the request never left
412
+ *
413
+ * That last line is now literally true, and it was not always. The class used
414
+ * to cover every transport rejection, a lost response body included, so this
415
+ * predicate told a caller replaying a create that the platform had not been
416
+ * reached when in fact it had been and the answer was what went missing.
417
+ * {@link ConnectivityInterruptedError} carries that case now and is excluded
418
+ * below — the same decision as the paragraph after this one, applied to the
419
+ * one class it had missed (OPL-3855).
420
+ *
421
+ * 502 and 504 USED to be here and are deliberately gone. The paragraph below
422
+ * had already conceded the point that removes them: both can arrive after the
423
+ * platform has acted, so replaying a `create_computer` through one can leave a
424
+ * second billable computer behind a failure that read as nothing having
425
+ * happened. A status that is not safe for the riskiest caller of an exported
426
+ * predicate does not belong in it. Nothing waits less as a result — the wait
427
+ * tools ask {@link isTransientForPoll}, which still rides both out.
428
+ *
429
+ * "Worth trying again" is not "the call definitely did not happen", and no
430
+ * predicate taking only an error can tell you the second. Even here, a 409 can
431
+ * be answered after a change landed. So retry reads freely, and check before
432
+ * repeating anything that creates something.
433
+ *
434
+ * Nor is a status enough on its own to answer it. 409 is the case: most of them
435
+ * are a passing state, and the move offer is a decision that no retry changes,
436
+ * which is why the check below leads with the type rather than the number. If a
437
+ * second such refusal earns a class, it belongs on that line too.
438
+ *
439
+ * One 409 could not be given a class and could not be seen from here at all: a
440
+ * clipboard read or write against a computer that is STOPPED does not clear on
441
+ * its own — a start is the fix, not another attempt — and nothing in the body
442
+ * told it apart from a conflict that is merely passing. The advice was to read
443
+ * the message, which is prose the platform is free to reword and exactly the
444
+ * matching OPL-3724 got three clients out of. The platform now says which kind
445
+ * it is, so {@link APIError.reason} is consulted BEFORE the types below, and an
446
+ * absent word — or one this version does not know — leaves the type answer
447
+ * standing unchanged (OPL-3898).
448
+ *
449
+ * One refusal is known to sit on the wrong side of that fallback, and it stays
450
+ * there deliberately (OPL-3909). A computer runs at most sixteen background
451
+ * commands, and the request for a seventeenth is refused 409 with no `reason` —
452
+ * correctly, since the slots may be held by servers and the platform will not
453
+ * advise a retry it cannot promise. The type answer therefore stands, and it
454
+ * says yes to something that may never clear. The alternative is to read the
455
+ * platform's sentence, which is the exported-contract version of the mistake
456
+ * OPL-3724 removed from three clients: a predicate that changes its answer when
457
+ * somebody rewords a message. So the next step is given where the sentence can
458
+ * be read safely — in `exec`, which knows it asked for a slot and which prints
459
+ * the platform's own words either way — and this predicate is left honest about
460
+ * what it can and cannot tell apart.
461
+ */
462
+ export declare function isTransient(err: unknown): boolean;
463
+ /**
464
+ * The same words, a different question: worth POLLING again.
465
+ *
466
+ * Asked only by the wait tools, and deliberately not exported. They replay a
467
+ * `GET /computers/:id`, a `GET /moves`, a build read or an `exec 'exit 0'`
468
+ * probe — every one of them idempotent, every one of them under a deadline the
469
+ * caller set. That pair of properties is what makes this predicate generous,
470
+ * and it is a property of what those calls DO rather than of the error, which
471
+ * is exactly why it cannot be published: the same `true` handed to an embedder
472
+ * retrying a create means something else entirely.
473
+ *
474
+ * DENY-LIST, and the inversion is the second half of the OPL-3724 decision.
475
+ * Where {@link isTransient} names what may be retried, this names what may not
476
+ * and polls through everything else. The polarity follows from who pays for a
477
+ * wrong answer. Retrying something unretryable costs one poll interval and,
478
+ * at worst, the deadline the caller chose. NOT retrying something that would
479
+ * have cleared costs a wait that reports a machine as unreachable while it was
480
+ * coming up — and every status the edge invents next year lands in that second
481
+ * category under an allow-list, silently, until somebody notices and adds a
482
+ * number. This way round, an unmapped 5xx is ridden out, which is what a poll
483
+ * loop was for.
484
+ *
485
+ * The line is REQUEST versus MOMENT. A failure describing the request answers
486
+ * the same way forever and is fatal here; a failure describing the moment is
487
+ * what a poll exists to outlast.
488
+ *
489
+ * Fatal, therefore:
490
+ *
491
+ * - anything that is not a failed REQUEST. That is the floor, and a deny-list
492
+ * needs one: only {@link APIError} and {@link ConnectivityError} describe an
493
+ * exchange with the platform that did not work, and only those can be worth
494
+ * making again. A `TypeError` from a bug in this file is not the platform
495
+ * being slow, and riding one out spends the caller's deadline before
496
+ * reporting the wrong cause.
497
+ *
498
+ * A bare {@link MandalaError} is caught by the same floor, and that is the
499
+ * half worth spelling out. `Api` raises them for a response that arrived and
500
+ * made no sense — "expected JSON from GET /computers/:id, got: <html>" — and
501
+ * a poll loop raises them as verdicts about a poll that SUCCEEDED. Neither is
502
+ * a moment to outlast: one is a defect and the other is an answer. Polling
503
+ * through a verdict is an infinite loop with a deadline on it, which is
504
+ * exactly what the TypeScript SDK's suite caught when this predicate was
505
+ * ported there with `MandalaError` as its floor.
506
+ *
507
+ * {@link ConnectivityInterruptedError} passes that floor and is meant to, on
508
+ * the strength of what the callers here DO. It says the outcome of one
509
+ * request is unknown; every request this predicate guards is a read, and a
510
+ * read whose outcome is unknown can be read again. Fatal to
511
+ * {@link isTransient} and transparent here is the whole point of there being
512
+ * two predicates.
513
+ * - {@link CancelledError} — the caller hung up. Excluded by the floor above,
514
+ * since it is neither, and retrying something nobody is waiting for is what
515
+ * `with(signal)` exists to stop.
516
+ * - {@link MoveRequiredError} — a decision about the size that was asked for.
517
+ * - {@link OriginTLSError} (525, 526) — a certificate the edge and the platform
518
+ * cannot agree on fails identically on every retry, so waiting one out spends
519
+ * the whole deadline to report the wrong cause. Its own message says to go and
520
+ * fix the deployment; this is what makes that true.
521
+ * - 524 — reached only by holding a request open past the edge's ceiling, so an
522
+ * identical retry reproduces it at the same place. It shares
523
+ * {@link GatewayTimeoutError} with 504, which is retryable, and that is why
524
+ * this one status is still matched by NUMBER: the type cannot separate them.
525
+ * - anything below 500 that is not named. A 4xx describes the request — a bad
526
+ * body, a revoked key, a plan limit, a deleted id, an offset past the end of a
527
+ * file — and repeating it unchanged cannot change the answer. Three are named
528
+ * because they describe the moment instead: 409 (something in flight), 429 (a
529
+ * cadence), and 408, which RFC 9110 defines as a request the client may repeat
530
+ * unchanged and which the edge in front of this surface does emit.
531
+ *
532
+ * A 3xx goes with the 4xx, which is why the test is `>= 500` rather than "not
533
+ * a 4xx". `Api` treats every non-2xx as an error and does not follow
534
+ * redirects, so a MANDALA_BASE_URL missing its trailing path answers 301 — and
535
+ * under a 4xx-only rule that was polled until the deadline, ending in a
536
+ * give-up that named nothing about the redirect. The mandala-computer-python
537
+ * SDK found that one; this is the same rule, and it is why all three now say
538
+ * `>= 500`.
539
+ *
540
+ * {@link APIError.reason} is deliberately NOT consulted here, and that is the
541
+ * one place this predicate and {@link isTransient} part company (OPL-3898).
542
+ * `unavailable` means the computer is not running, which is a permanent answer
543
+ * to whoever asked — and a poll under a deadline is the one caller for whom it
544
+ * may not be, since a computer coming up passes through it. The same generosity
545
+ * as every unmapped 5xx below, for the same reason: this only ever replays a
546
+ * read, and the loops above return a refusal of their own the moment the status
547
+ * they are watching says stopped or suspended. mandala-computer-python's
548
+ * `_is_transient_for_poll` draws the line in the same place.
549
+ *
550
+ * Everything at 5xx polls through, 502 and 520-523 included: they mean the
551
+ * outcome is unknown, and a read whose outcome is unknown can simply be read
552
+ * again.
553
+ *
554
+ * 5xx has an upper bound as well as a lower one, and it is not decoration. The
555
+ * HTTP parser under `fetch` accepts any three digits, so a broken or hostile
556
+ * origin can answer 700 — which `>= 500` alone called a passing moment and
557
+ * polled until the caller's deadline (Codex adversarial review, OPL-3724).
558
+ */
559
+ export declare function isTransientForPoll(err: unknown): boolean;
560
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}