mandala-computer-mcp 0.11.0 → 0.13.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 (92) hide show
  1. package/README.md +159 -39
  2. package/dist/api.d.ts +9 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +15 -4
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +3 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/credits.d.ts +187 -0
  10. package/dist/credits.d.ts.map +1 -0
  11. package/dist/credits.js +119 -0
  12. package/dist/credits.js.map +1 -0
  13. package/dist/errors.d.ts +92 -26
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +206 -35
  16. package/dist/errors.js.map +1 -1
  17. package/dist/format.d.ts.map +1 -1
  18. package/dist/format.js +30 -8
  19. package/dist/format.js.map +1 -1
  20. package/dist/http.d.ts.map +1 -1
  21. package/dist/http.js +82 -8
  22. package/dist/http.js.map +1 -1
  23. package/dist/limits.d.ts +2 -0
  24. package/dist/limits.d.ts.map +1 -1
  25. package/dist/limits.js +2 -0
  26. package/dist/limits.js.map +1 -1
  27. package/dist/paths.d.ts +42 -1
  28. package/dist/paths.d.ts.map +1 -1
  29. package/dist/paths.js +46 -0
  30. package/dist/paths.js.map +1 -1
  31. package/dist/poll.d.ts +1 -1
  32. package/dist/poll.js +1 -1
  33. package/dist/server.d.ts +41 -0
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +129 -5
  36. package/dist/server.js.map +1 -1
  37. package/dist/session.d.ts +20 -4
  38. package/dist/session.d.ts.map +1 -1
  39. package/dist/session.js +5 -0
  40. package/dist/session.js.map +1 -1
  41. package/dist/stdio.d.ts.map +1 -1
  42. package/dist/stdio.js +11 -1
  43. package/dist/stdio.js.map +1 -1
  44. package/dist/tool-filters.d.ts +3 -2
  45. package/dist/tool-filters.d.ts.map +1 -1
  46. package/dist/tool-filters.js +3 -1
  47. package/dist/tool-filters.js.map +1 -1
  48. package/dist/tools/account.d.ts.map +1 -1
  49. package/dist/tools/account.js +10 -3
  50. package/dist/tools/account.js.map +1 -1
  51. package/dist/tools/activities.d.ts.map +1 -1
  52. package/dist/tools/activities.js +4 -0
  53. package/dist/tools/activities.js.map +1 -1
  54. package/dist/tools/agent.d.ts +12 -4
  55. package/dist/tools/agent.d.ts.map +1 -1
  56. package/dist/tools/agent.js +292 -132
  57. package/dist/tools/agent.js.map +1 -1
  58. package/dist/tools/chat.d.ts +11 -2
  59. package/dist/tools/chat.d.ts.map +1 -1
  60. package/dist/tools/chat.js +19 -9
  61. package/dist/tools/chat.js.map +1 -1
  62. package/dist/tools/computers.d.ts.map +1 -1
  63. package/dist/tools/computers.js +92 -11
  64. package/dist/tools/computers.js.map +1 -1
  65. package/dist/tools/credits.d.ts +9 -0
  66. package/dist/tools/credits.d.ts.map +1 -0
  67. package/dist/tools/credits.js +53 -0
  68. package/dist/tools/credits.js.map +1 -0
  69. package/dist/tools/guest.d.ts.map +1 -1
  70. package/dist/tools/guest.js +6 -7
  71. package/dist/tools/guest.js.map +1 -1
  72. package/dist/tools/input.d.ts.map +1 -1
  73. package/dist/tools/input.js +51 -18
  74. package/dist/tools/input.js.map +1 -1
  75. package/dist/tools/operations.js +2 -2
  76. package/dist/tools/operations.js.map +1 -1
  77. package/dist/tools/publicurls.d.ts +3 -0
  78. package/dist/tools/publicurls.d.ts.map +1 -0
  79. package/dist/tools/publicurls.js +287 -0
  80. package/dist/tools/publicurls.js.map +1 -0
  81. package/dist/tools/snapshots.d.ts.map +1 -1
  82. package/dist/tools/snapshots.js +1 -0
  83. package/dist/tools/snapshots.js.map +1 -1
  84. package/dist/tools/ssh.d.ts.map +1 -1
  85. package/dist/tools/ssh.js +4 -2
  86. package/dist/tools/ssh.js.map +1 -1
  87. package/dist/tools/webhooks.d.ts.map +1 -1
  88. package/dist/tools/webhooks.js +32 -23
  89. package/dist/tools/webhooks.js.map +1 -1
  90. package/dist/version.d.ts +1 -1
  91. package/dist/version.js +1 -1
  92. package/package.json +1 -1
package/dist/errors.d.ts CHANGED
@@ -4,10 +4,10 @@
4
4
  * The distinctions here are the ones an agent has to act on and cannot infer
5
5
  * from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
6
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}.
7
+ * has to resolve. A 409 is the one that is not uniform: some of them are a
8
+ * passing state and worth retrying, which the platform says with a `reason`,
9
+ * and the rest are a decision about the request that no retry turns into a
10
+ * yes — see {@link ConflictError} and {@link MoveRequiredError}.
11
11
  *
12
12
  * Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
13
13
  * deliberately so: two clients disagreeing about what a 402 is means the same
@@ -50,6 +50,13 @@ export declare class APIError extends MandalaError {
50
50
  * the caller who arrived a moment earlier heard.
51
51
  */
52
52
  readonly reason?: string;
53
+ /**
54
+ * The platform's machine-readable `code` beside `error`, where it sent one:
55
+ * `LABEL_TAKEN`, `WORKSPACE_MOVING`, `idempotency_in_progress` and so on.
56
+ * Top-level only, and only a nonblank string; `undefined` otherwise. Like
57
+ * `reason`, it is the word to switch on, never the sentence.
58
+ */
59
+ readonly code?: string;
53
60
  /** Response header first, then the top-level body ID; never an idempotency key. */
54
61
  readonly requestId?: string;
55
62
  /** The received Allow header, when supplied. */
@@ -108,6 +115,32 @@ export declare function reasonKind(reason: string | undefined): ReasonKind | und
108
115
  * platform's "absent means unclassified" contract exists to prevent.
109
116
  */
110
117
  export declare function reasonAdvice(reason: string | undefined): string | undefined;
118
+ /** Whether a 409 carries a code the platform documents as a passing state. */
119
+ export declare function conflictCodeClears(err: APIError): boolean;
120
+ /**
121
+ * What to tell a model about a refusal the platform named with a `code`, or
122
+ * `undefined` for a code with nothing to add. Ahead of {@link statusAdvice} in
123
+ * the formatter, because these codes are more specific than the status:
124
+ * `PUBLIC_URLS_BLOCKED` is a 403 that is neither a role nor a suspended
125
+ * account, and `PUBLIC_URLS_DISABLED` is a 404 that is not a missing computer.
126
+ */
127
+ export declare function codeAdvice(err: APIError): string | undefined;
128
+ /**
129
+ * A 409 the platform did not classify: no `reason` this version knows, no code
130
+ * from {@link codeAdvice} or {@link CODE_CLEARS}, and none of the 409s that
131
+ * carry their own class and say what they are. Not a passing state
132
+ * (OPL-5975): the platform says absent means no classification.
133
+ */
134
+ export declare function unclassifiedConflict(err: unknown): boolean;
135
+ /** What a model is told about an {@link unclassifiedConflict}. */
136
+ export declare const UNCLASSIFIED_CONFLICT_ADVICE = "the platform did not say whether this clears, so treat it as one that does not: do not send it again unchanged. The message usually says what to do instead";
137
+ /**
138
+ * Whether this is the keyed call's "still running" refusal as the platform
139
+ * documents it: a 409 with code `idempotency_in_progress` that the retry
140
+ * predicate calls transient. A permanent word beside the code, or another
141
+ * status, is not that refusal, and earns no "send it again with this key".
142
+ */
143
+ export declare function idempotencyInProgress(err: APIError): boolean;
111
144
  /** The platform's `code` on a keyed call's own refusals (OPL-5127), or `undefined`. */
112
145
  export declare function idempotencyCode(err: APIError): string | undefined;
113
146
  /**
@@ -240,8 +273,38 @@ export declare function statusAdvice(status: number | undefined, reason?: string
240
273
  * `undefined` for a status with nothing to add, so the caller keeps its own
241
274
  * wording. A 401 without `revoked` is left to {@link authenticationAdvice},
242
275
  * which already says it cannot tell the account key from the model key.
276
+ *
277
+ * The one 402 that is Mandala's own is a run on the account's AI credits that
278
+ * could not reserve its first model call (OPL-5866), and the platform says so
279
+ * by naming a top-up page. `topUp` is that page, resolved; given, the 402 is
280
+ * worded as credits run out rather than as the model provider's billing error,
281
+ * because the fix is on the Mandala billing page and not on a provider account.
282
+ */
283
+ export declare function agentRunAdvice(status: number | undefined, reason: string | undefined, tool: string, topUp?: string): string | undefined;
284
+ /**
285
+ * A run on the account's AI credits refused because they cannot cover it.
286
+ *
287
+ * Not the model provider, not the plan and not the computer, and waiting does
288
+ * not change it: the account's balance is what it is until a person tops it up
289
+ * or a model key is configured. Said so a model reports it rather than retrying.
290
+ */
291
+ export declare function creditsExhaustedAdvice(topUp: string, tool: string): string;
292
+ /**
293
+ * The platform's own fixed words for its AI service failing a run on credits,
294
+ * over the model provider's status. The provider's text never leaves the
295
+ * platform on such a run, so this sentence is how the failure is recognised.
296
+ */
297
+ export declare const FUNDED_UNAVAILABLE = "Mandala AI is unavailable";
298
+ /**
299
+ * A run on AI credits that the platform's AI service could not serve.
300
+ *
301
+ * Its status is the model provider's, answering Mandala's own key, so neither
302
+ * of the usual readings fits: a 401 or 403 is not the caller's credential or
303
+ * role, a 404 not the computer, a 413 not the request, a 402 not the plan, and
304
+ * there is no model key of the caller's to check. Steps already taken stand and
305
+ * were charged, so running the same prompt again would repeat them.
243
306
  */
244
- export declare function agentRunAdvice(status: number | undefined, reason: string | undefined, tool: string): string | undefined;
307
+ export declare function fundedUnavailableAdvice(tool: string): string;
245
308
  /** 401 — the key is missing, malformed, or revoked. */
246
309
  export declare class AuthenticationError extends APIError {
247
310
  name: string;
@@ -265,9 +328,10 @@ export declare class MethodNotAllowedError extends APIError {
265
328
  /**
266
329
  * 409 — the thing exists but is in the wrong state for this right now.
267
330
  *
268
- * The one status on this list that is usually temporary: a guest still booting,
269
- * a guest agent busy with another call, a desktop session that has not come up
270
- * yet. Worth retrying; the others on this list are not.
331
+ * The one status on this list that is SOMETIMES temporary: a guest still
332
+ * booting, a guest agent busy with another call, a desktop session that has not
333
+ * come up yet. Those are worth retrying, and the platform says so with a
334
+ * `reason`; one without a word is not (OPL-5975, see {@link isTransient}).
271
335
  *
272
336
  * USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
273
337
  * 409 clears is a property of the BODY and not of the status: some describe a
@@ -284,9 +348,11 @@ export declare class MethodNotAllowedError extends APIError {
284
348
  * with it. The rest stay here and are told apart by {@link APIError.reason},
285
349
  * which is the word the platform added for that purpose (OPL-3898) and the one
286
350
  * its own reference now says to switch on — never the sentence, which is prose
287
- * written for a person and rewritten whenever a better one is. Where no word was
288
- * sent this class means what it always did, and that fallback is the contract
289
- * rather than a gap: not every refusal here has an answer yet.
351
+ * written for a person and rewritten whenever a better one is — or by
352
+ * {@link APIError.code} where the platform documents a code as passing. Where
353
+ * neither says so, the refusal is unclassified and {@link isTransient} says no
354
+ * (OPL-5975): not every refusal here has an answer yet, and an unanswered one
355
+ * is not a promise that waiting helps.
290
356
  */
291
357
  export declare class ConflictError extends APIError {
292
358
  name: string;
@@ -719,8 +785,10 @@ export declare function errorForStatus(status: number, message: string, body?: u
719
785
  * because a number can be added to it without anyone having to say which of the
720
786
  * three answers changed. It now reads identically in all three:
721
787
  *
722
- * - {@link ConflictError} — something is in flight that this cannot run
723
- * alongside, minus the one that is a decision
788
+ * - {@link ConflictError} — only where the platform says it is passing: a
789
+ * `reason` of `contention` or `starting`, or a code it documents as
790
+ * passing (`idempotency_in_progress`, `WORKSPACE_MOVING`). A 409 with
791
+ * neither is unclassified and not transient (OPL-5975)
724
792
  * - {@link RateLimitError} — a cadence, and the response usually says how long
725
793
  * - {@link UnavailableError} — something briefly out of reach, for a GET or
726
794
  * HEAD only. Any change answered 503 may have happened, so it is not
@@ -748,7 +816,7 @@ export declare function errorForStatus(status: number, message: string, body?: u
748
816
  * be answered after a change landed. So retry reads freely, and check before
749
817
  * repeating anything that creates something.
750
818
  *
751
- * Nor is a status enough on its own to answer it. 409 is the case: most of them
819
+ * Nor is a status enough on its own to answer it. 409 is the case: some of them
752
820
  * are a passing state, and the move offer is a decision that no retry changes,
753
821
  * which is why the check below leads with the type rather than the number. If a
754
822
  * second such refusal earns a class, it belongs on that line too.
@@ -763,18 +831,16 @@ export declare function errorForStatus(status: number, message: string, body?: u
763
831
  * absent word — or one this version does not know — leaves the type answer
764
832
  * standing unchanged (OPL-3898).
765
833
  *
766
- * One refusal is known to sit on the wrong side of that fallback, and it stays
767
- * there deliberately (OPL-3909). A computer runs at most sixteen background
768
- * commands, and the request for a seventeenth is refused 409 with no `reason` —
769
- * correctly, since the slots may be held by servers and the platform will not
770
- * advise a retry it cannot promise. The type answer therefore stands, and it
771
- * says yes to something that may never clear. The alternative is to read the
772
- * platform's sentence, which is the exported-contract version of the mistake
773
- * OPL-3724 removed from three clients: a predicate that changes its answer when
774
- * somebody rewords a message. So the next step is given where the sentence can
775
- * be read safely — in `exec`, which knows it asked for a slot and which prints
776
- * the platform's own words either way — and this predicate is left honest about
777
- * what it can and cannot tell apart.
834
+ * And the fallback for a 409 with no word is NO, not yes (OPL-5975). It used
835
+ * to be yes — "a 409 is usually passing" — and that said yes to every refusal
836
+ * the platform had not classified: a label that is taken, a computer at its
837
+ * public URL limit, the webhook cap, an SSH key already registered, the
838
+ * seventeenth background command (OPL-3909, whose slots may be held by servers
839
+ * and never free). The platform's reference is explicit that an absent
840
+ * `reason` is no classification and that treating 409 as uniformly transient
841
+ * loops forever. So a 409 is transient here only when its word clears or its
842
+ * code is one the platform documents as passing ({@link conflictCodeClears}),
843
+ * and the same classification holds in both SDKs.
778
844
  */
779
845
  export declare function isTransient(err: unknown): boolean;
780
846
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js CHANGED
@@ -4,10 +4,10 @@
4
4
  * The distinctions here are the ones an agent has to act on and cannot infer
5
5
  * from prose. A 400 never clears and retrying it burns a turn. A 402 is a plan
6
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}.
7
+ * has to resolve. A 409 is the one that is not uniform: some of them are a
8
+ * passing state and worth retrying, which the platform says with a `reason`,
9
+ * and the rest are a decision about the request that no retry turns into a
10
+ * yes — see {@link ConflictError} and {@link MoveRequiredError}.
11
11
  *
12
12
  * Mirrors the mapping in mandala-computer-python's `_exceptions.py`, and
13
13
  * deliberately so: two clients disagreeing about what a 402 is means the same
@@ -38,6 +38,13 @@ export class APIError extends MandalaError {
38
38
  * the caller who arrived a moment earlier heard.
39
39
  */
40
40
  reason;
41
+ /**
42
+ * The platform's machine-readable `code` beside `error`, where it sent one:
43
+ * `LABEL_TAKEN`, `WORKSPACE_MOVING`, `idempotency_in_progress` and so on.
44
+ * Top-level only, and only a nonblank string; `undefined` otherwise. Like
45
+ * `reason`, it is the word to switch on, never the sentence.
46
+ */
47
+ code;
41
48
  /** Response header first, then the top-level body ID; never an idempotency key. */
42
49
  requestId;
43
50
  /** The received Allow header, when supplied. */
@@ -54,6 +61,7 @@ export class APIError extends MandalaError {
54
61
  this.body = body;
55
62
  this.retryAfterMs = retryAfterMs;
56
63
  this.reason = refusalReason(body);
64
+ this.code = record(body) ? nonblank(body.code) : undefined;
57
65
  this.requestId =
58
66
  nonblank(metadata?.requestId) ?? (record(body) ? nonblank(body.request_id) : undefined);
59
67
  this.allow = nonblank(metadata?.allow);
@@ -117,6 +125,8 @@ const REASON_PERMANENT = new Set([
117
125
  'running',
118
126
  'name_taken',
119
127
  'stale_revision',
128
+ // An evicted screenshot cannot be recovered by replaying its capture name.
129
+ 'stale_capture',
120
130
  ]);
121
131
  /**
122
132
  * How a refusal the platform classified behaves, or `undefined` for a word this
@@ -188,7 +198,11 @@ export function reasonAdvice(reason) {
188
198
  // reason code, not a computer — so it names the call that CAN.
189
199
  return 'the computer is not running; if nothing is starting it this will not clear on its own and start_computer is the fix, and if a start is already under way wait_for_computer says so without starting a second one';
190
200
  case 'unsupported':
191
- return 'this computer cannot do it at all, so do not retry it — the answer is the same forever';
201
+ // Subject-neutral (OPL-5975). It said "this computer cannot do it at
202
+ // all … the same forever", and the platform sends this word for refusals
203
+ // that are about no computer and that a step clears: a workspace that
204
+ // still holds computers or secrets, a workspace change still unsettled.
205
+ return 'waiting will not change this answer: either it cannot be done at all, or it needs another step first, which the message names. Do not send it again unchanged; take that step, if there is one, and then send it';
192
206
  case 'exists':
193
207
  // Only THIS attempt is known to have written nothing: a create-only
194
208
  // write whose earlier attempt lost its answer may have written the file
@@ -214,6 +228,108 @@ export function reasonAdvice(reason) {
214
228
  return undefined;
215
229
  }
216
230
  }
231
+ /**
232
+ * The `code`s that mark a 409 as a passing state when its `reason` does not
233
+ * say otherwise (OPL-5975), taken from the platform's own words for each:
234
+ *
235
+ * - `idempotency_in_progress`: the call with this key is still being carried
236
+ * out; send it again shortly for its answer. The platform sends it with
237
+ * `reason: "contention"` as well, which already says so; the code is named
238
+ * so that it is still read as passing if the word is ever missing.
239
+ * - `WORKSPACE_MOVING`: the computer's workspace change is unsettled; try
240
+ * again once the move has finished. Sent with `reason: "contention"` while
241
+ * the move is settling, and `reason: "unsupported"` when it is stuck and
242
+ * needs checking — the word, read first, decides those two.
243
+ *
244
+ * Every other 409 without a reason the platform documents as passing is NOT
245
+ * a passing state: the platform says an absent `reason` is no
246
+ * classification, and a client that treats 409 as uniformly transient "loops
247
+ * forever on half of them". That includes the ones that carry a code and no
248
+ * word — `LABEL_TAKEN`, `PUBLIC_URL_LIMIT` — and the ones that carry neither:
249
+ * the webhook cap, a test of a disabled webhook, an SSH key already
250
+ * registered, the SSH key limit, the sixteen background-command slots.
251
+ */
252
+ const CODE_CLEARS = new Set(['idempotency_in_progress', 'WORKSPACE_MOVING']);
253
+ /** Whether a 409 carries a code the platform documents as a passing state. */
254
+ export function conflictCodeClears(err) {
255
+ return err.code !== undefined && CODE_CLEARS.has(err.code);
256
+ }
257
+ /**
258
+ * What to tell a model about a refusal the platform named with a `code`, or
259
+ * `undefined` for a code with nothing to add. Ahead of {@link statusAdvice} in
260
+ * the formatter, because these codes are more specific than the status:
261
+ * `PUBLIC_URLS_BLOCKED` is a 403 that is neither a role nor a suspended
262
+ * account, and `PUBLIC_URLS_DISABLED` is a 404 that is not a missing computer.
263
+ */
264
+ export function codeAdvice(err) {
265
+ // Each code on the one status the platform sends it with. The same code on
266
+ // another status is not the refusal this advice describes, and the status's
267
+ // own advice — a 401 to 405 is never a retry — is left to speak.
268
+ if (CODE_STATUS[err.code ?? ''] !== err.status)
269
+ return undefined;
270
+ // The word first, as in isTransient: a reason this version knows, beside any
271
+ // code but WORKSPACE_MOVING (whose advice reads the word itself), is the
272
+ // more specific answer, and its own advice is given instead.
273
+ if (err.code !== 'WORKSPACE_MOVING' && reasonKind(err.reason) !== undefined)
274
+ return undefined;
275
+ switch (err.code) {
276
+ case 'LABEL_TAKEN':
277
+ return 'that label is taken, and this does not clear by waiting. A label is never given to a second account, so one another account has ever held, or one the platform has blocked, reads as taken, as does one a URL of this account holds now (list_public_urls shows those). Choose another label, or leave label out to have one picked';
278
+ case 'PUBLIC_URL_LIMIT':
279
+ return 'this computer already has as many public URLs as its plan allows, and this does not clear by waiting. Delete one it no longer needs (list_public_urls), point an existing one at this port with update_public_url, or say the plan is the limit';
280
+ case 'PUBLIC_URLS_BLOCKED':
281
+ return 'the platform has turned public URLs off for this computer or this account. It is not a role, a credential or anything wrong with the computer, and neither retrying nor waiting changes it: say so to the user, who can contact support if they think it is a mistake';
282
+ case 'PUBLIC_URLS_DISABLED':
283
+ return 'public URLs are not enabled here at all. This is not a missing computer or URL, and waiting does not change it: say that public URLs are unavailable';
284
+ case 'WORKSPACE_MOVING':
285
+ // The word decides, as it does in isTransient: `unsupported` is the
286
+ // stuck change, which a step clears. Otherwise "send it again" is said
287
+ // only where isTransient agrees — never over a permanent word, a nested
288
+ // word (which grants no retry), or anything else it calls final.
289
+ if (err.reason === 'unsupported')
290
+ return 'this computer\u2019s workspace change is not settled, and waiting alone will not settle it. Repeating the same move_computer_to_workspace call reconciles it, or check the workspace change in the dashboard; then send this again';
291
+ if (!isTransient(err))
292
+ return undefined;
293
+ return 'this computer is being moved to another workspace; this clears once the move has finished, so wait a moment and send it again';
294
+ default:
295
+ return undefined;
296
+ }
297
+ }
298
+ /** The status each code in {@link codeAdvice} arrives with. */
299
+ const CODE_STATUS = {
300
+ LABEL_TAKEN: 409,
301
+ PUBLIC_URL_LIMIT: 409,
302
+ PUBLIC_URLS_BLOCKED: 403,
303
+ PUBLIC_URLS_DISABLED: 404,
304
+ WORKSPACE_MOVING: 409,
305
+ };
306
+ /**
307
+ * A 409 the platform did not classify: no `reason` this version knows, no code
308
+ * from {@link codeAdvice} or {@link CODE_CLEARS}, and none of the 409s that
309
+ * carry their own class and say what they are. Not a passing state
310
+ * (OPL-5975): the platform says absent means no classification.
311
+ */
312
+ export function unclassifiedConflict(err) {
313
+ return (err instanceof ConflictError &&
314
+ !(err instanceof MoveRequiredError) &&
315
+ !(err instanceof FileExistsError) &&
316
+ !(err instanceof CreateOnlyConflictError) &&
317
+ reasonKind(err.reason) === undefined &&
318
+ !conflictCodeClears(err) &&
319
+ codeAdvice(err) === undefined &&
320
+ idempotencyCode(err) === undefined);
321
+ }
322
+ /** What a model is told about an {@link unclassifiedConflict}. */
323
+ export const UNCLASSIFIED_CONFLICT_ADVICE = 'the platform did not say whether this clears, so treat it as one that does not: do not send it again unchanged. The message usually says what to do instead';
324
+ /**
325
+ * Whether this is the keyed call's "still running" refusal as the platform
326
+ * documents it: a 409 with code `idempotency_in_progress` that the retry
327
+ * predicate calls transient. A permanent word beside the code, or another
328
+ * status, is not that refusal, and earns no "send it again with this key".
329
+ */
330
+ export function idempotencyInProgress(err) {
331
+ return (err.status === 409 && idempotencyCode(err) === 'idempotency_in_progress' && isTransient(err));
332
+ }
217
333
  /** The platform's `code` on a keyed call's own refusals (OPL-5127), or `undefined`. */
218
334
  export function idempotencyCode(err) {
219
335
  const code = record(err.body) ? err.body.code : undefined;
@@ -239,6 +355,11 @@ export function idempotencyAdvice(err, tool) {
239
355
  : undefined;
240
356
  switch (idempotencyCode(err)) {
241
357
  case 'idempotency_in_progress':
358
+ // Only the passing refusal the platform documents: a word beside the
359
+ // code that does not clear, or another status, is answered by its own
360
+ // advice instead (Codex, round 4 on #213).
361
+ if (!idempotencyInProgress(err))
362
+ return undefined;
242
363
  return `the same call with this idempotency_key is still running; wait and call again with the same idempotency_key for its answer${operation ? `, or read get_operation ${operation}` : ''}`;
243
364
  case 'idempotency_outcome_unknown':
244
365
  return `the platform did not hear how the call with this idempotency_key ended; do not send it again with this key, since the same key answers the same thing. ${resendAfterSpentKey(tool, operation, undefined, tool ? `call ${tool} again` : undefined)}`;
@@ -380,8 +501,7 @@ export function resendOnlyOnceFinal(operation, key, resend = 'send the call agai
380
501
  export function keyedOutcomeUnknown(err) {
381
502
  return (err instanceof ConnectivityInterruptedError ||
382
503
  (err instanceof APIError &&
383
- (idempotencyCode(err) === 'idempotency_in_progress' ||
384
- (err.status >= 500 && !platformAnsweredFailure(err)))));
504
+ (idempotencyInProgress(err) || (err.status >= 500 && !platformAnsweredFailure(err)))));
385
505
  }
386
506
  /**
387
507
  * Whether a `5xx` came from the platform itself rather than from a hop in
@@ -465,8 +585,16 @@ export function statusAdvice(status, reason) {
465
585
  * `undefined` for a status with nothing to add, so the caller keeps its own
466
586
  * wording. A 401 without `revoked` is left to {@link authenticationAdvice},
467
587
  * which already says it cannot tell the account key from the model key.
588
+ *
589
+ * The one 402 that is Mandala's own is a run on the account's AI credits that
590
+ * could not reserve its first model call (OPL-5866), and the platform says so
591
+ * by naming a top-up page. `topUp` is that page, resolved; given, the 402 is
592
+ * worded as credits run out rather than as the model provider's billing error,
593
+ * because the fix is on the Mandala billing page and not on a provider account.
468
594
  */
469
- export function agentRunAdvice(status, reason, tool) {
595
+ export function agentRunAdvice(status, reason, tool, topUp) {
596
+ if (status === 402 && topUp)
597
+ return creditsExhaustedAdvice(topUp, tool);
470
598
  const noReplay = `Do not call ${tool} again with the same task: completed steps were already billed, so read what they did first`;
471
599
  if (status === 403 && reason === 'revoked') {
472
600
  return `this Mandala account no longer permits the run — a role that changed or an account suspended, not anything wrong with the computer. Nothing you can send changes that, and a new credential does not either: say what was refused and stop. ${noReplay}`;
@@ -485,6 +613,34 @@ export function agentRunAdvice(status, reason, tool) {
485
613
  return undefined;
486
614
  return `this is the model provider's own status for the configured model key (its ${provider}), not the Mandala plan and not the computer — check the model-provider account${status === 402 ? '' : ' and its status page, and try later'}. ${noReplay}`;
487
615
  }
616
+ /**
617
+ * A run on the account's AI credits refused because they cannot cover it.
618
+ *
619
+ * Not the model provider, not the plan and not the computer, and waiting does
620
+ * not change it: the account's balance is what it is until a person tops it up
621
+ * or a model key is configured. Said so a model reports it rather than retrying.
622
+ */
623
+ export function creditsExhaustedAdvice(topUp, tool) {
624
+ return `this Mandala account's AI credits cannot cover the run — not the model provider, the plan or the computer. Waiting does not change it: a person can top up at ${topUp}, or configure their own model key. Say so and do not call ${tool} again until then`;
625
+ }
626
+ /**
627
+ * The platform's own fixed words for its AI service failing a run on credits,
628
+ * over the model provider's status. The provider's text never leaves the
629
+ * platform on such a run, so this sentence is how the failure is recognised.
630
+ */
631
+ export const FUNDED_UNAVAILABLE = 'Mandala AI is unavailable';
632
+ /**
633
+ * A run on AI credits that the platform's AI service could not serve.
634
+ *
635
+ * Its status is the model provider's, answering Mandala's own key, so neither
636
+ * of the usual readings fits: a 401 or 403 is not the caller's credential or
637
+ * role, a 404 not the computer, a 413 not the request, a 402 not the plan, and
638
+ * there is no model key of the caller's to check. Steps already taken stand and
639
+ * were charged, so running the same prompt again would repeat them.
640
+ */
641
+ export function fundedUnavailableAdvice(tool) {
642
+ return `the platform's AI service could not serve this run on credits — its model provider answered this status, which is not the computer, your credential or the plan, and there is no key of yours to check. Waiting can clear it. If no step ran, ${tool} can be tried again later; if steps ran, they stand and were charged, so read what they did and send only what is left`;
643
+ }
488
644
  /**
489
645
  * The platform's one-word classification off a refusal body, or `undefined`.
490
646
  *
@@ -527,9 +683,10 @@ export class MethodNotAllowedError extends APIError {
527
683
  /**
528
684
  * 409 — the thing exists but is in the wrong state for this right now.
529
685
  *
530
- * The one status on this list that is usually temporary: a guest still booting,
531
- * a guest agent busy with another call, a desktop session that has not come up
532
- * yet. Worth retrying; the others on this list are not.
686
+ * The one status on this list that is SOMETIMES temporary: a guest still
687
+ * booting, a guest agent busy with another call, a desktop session that has not
688
+ * come up yet. Those are worth retrying, and the platform says so with a
689
+ * `reason`; one without a word is not (OPL-5975, see {@link isTransient}).
533
690
  *
534
691
  * USUALLY, and the exception is why {@link MoveRequiredError} exists. Whether a
535
692
  * 409 clears is a property of the BODY and not of the status: some describe a
@@ -546,9 +703,11 @@ export class MethodNotAllowedError extends APIError {
546
703
  * with it. The rest stay here and are told apart by {@link APIError.reason},
547
704
  * which is the word the platform added for that purpose (OPL-3898) and the one
548
705
  * its own reference now says to switch on — never the sentence, which is prose
549
- * written for a person and rewritten whenever a better one is. Where no word was
550
- * sent this class means what it always did, and that fallback is the contract
551
- * rather than a gap: not every refusal here has an answer yet.
706
+ * written for a person and rewritten whenever a better one is — or by
707
+ * {@link APIError.code} where the platform documents a code as passing. Where
708
+ * neither says so, the refusal is unclassified and {@link isTransient} says no
709
+ * (OPL-5975): not every refusal here has an answer yet, and an unanswered one
710
+ * is not a promise that waiting helps.
552
711
  */
553
712
  export class ConflictError extends APIError {
554
713
  name = 'ConflictError';
@@ -1242,8 +1401,10 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
1242
1401
  * because a number can be added to it without anyone having to say which of the
1243
1402
  * three answers changed. It now reads identically in all three:
1244
1403
  *
1245
- * - {@link ConflictError} — something is in flight that this cannot run
1246
- * alongside, minus the one that is a decision
1404
+ * - {@link ConflictError} — only where the platform says it is passing: a
1405
+ * `reason` of `contention` or `starting`, or a code it documents as
1406
+ * passing (`idempotency_in_progress`, `WORKSPACE_MOVING`). A 409 with
1407
+ * neither is unclassified and not transient (OPL-5975)
1247
1408
  * - {@link RateLimitError} — a cadence, and the response usually says how long
1248
1409
  * - {@link UnavailableError} — something briefly out of reach, for a GET or
1249
1410
  * HEAD only. Any change answered 503 may have happened, so it is not
@@ -1271,7 +1432,7 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
1271
1432
  * be answered after a change landed. So retry reads freely, and check before
1272
1433
  * repeating anything that creates something.
1273
1434
  *
1274
- * Nor is a status enough on its own to answer it. 409 is the case: most of them
1435
+ * Nor is a status enough on its own to answer it. 409 is the case: some of them
1275
1436
  * are a passing state, and the move offer is a decision that no retry changes,
1276
1437
  * which is why the check below leads with the type rather than the number. If a
1277
1438
  * second such refusal earns a class, it belongs on that line too.
@@ -1286,18 +1447,16 @@ export function errorForStatus(status, message, body, retryAfterMs, metadata) {
1286
1447
  * absent word — or one this version does not know — leaves the type answer
1287
1448
  * standing unchanged (OPL-3898).
1288
1449
  *
1289
- * One refusal is known to sit on the wrong side of that fallback, and it stays
1290
- * there deliberately (OPL-3909). A computer runs at most sixteen background
1291
- * commands, and the request for a seventeenth is refused 409 with no `reason` —
1292
- * correctly, since the slots may be held by servers and the platform will not
1293
- * advise a retry it cannot promise. The type answer therefore stands, and it
1294
- * says yes to something that may never clear. The alternative is to read the
1295
- * platform's sentence, which is the exported-contract version of the mistake
1296
- * OPL-3724 removed from three clients: a predicate that changes its answer when
1297
- * somebody rewords a message. So the next step is given where the sentence can
1298
- * be read safely — in `exec`, which knows it asked for a slot and which prints
1299
- * the platform's own words either way — and this predicate is left honest about
1300
- * what it can and cannot tell apart.
1450
+ * And the fallback for a 409 with no word is NO, not yes (OPL-5975). It used
1451
+ * to be yes — "a 409 is usually passing" — and that said yes to every refusal
1452
+ * the platform had not classified: a label that is taken, a computer at its
1453
+ * public URL limit, the webhook cap, an SSH key already registered, the
1454
+ * seventeenth background command (OPL-3909, whose slots may be held by servers
1455
+ * and never free). The platform's reference is explicit that an absent
1456
+ * `reason` is no classification and that treating 409 as uniformly transient
1457
+ * loops forever. So a 409 is transient here only when its word clears or its
1458
+ * code is one the platform documents as passing ({@link conflictCodeClears}),
1459
+ * and the same classification holds in both SDKs.
1301
1460
  */
1302
1461
  export function isTransient(err) {
1303
1462
  if (err instanceof APIError && [401, 402, 403, 404, 405].includes(err.status))
@@ -1350,15 +1509,27 @@ export function isTransient(err) {
1350
1509
  // Through the exported classifier rather than the sets directly, so what an
1351
1510
  // embedder is told and what this server does are one answer and not two.
1352
1511
  const kind = reasonKind(err.reason);
1353
- // A nested run reason can arrive after billed work and grants no new replay permission.
1354
- if (kind === 'clears' && record(err.body) && typeof err.body.reason === 'string')
1355
- return true;
1512
+ if (kind === 'clears') {
1513
+ // A nested run reason can arrive after billed work and grants no replay
1514
+ // permission, whatever code sits beside it.
1515
+ if (!(record(err.body) && typeof err.body.reason === 'string'))
1516
+ return false;
1517
+ // The word describes a refusal: a 400 or a 409. On any other status the
1518
+ // status's own answer stands, as in both SDKs.
1519
+ if (err.status === 400 || err.status === 409)
1520
+ return true;
1521
+ }
1356
1522
  if (kind === 'permanent')
1357
1523
  return false;
1358
1524
  }
1359
- return (err instanceof ConflictError ||
1360
- err instanceof RateLimitError ||
1361
- err instanceof ConnectivityError);
1525
+ // A 409 is a passing state only where the platform says so (OPL-5975): a
1526
+ // word that clears, above, or a code it documents as passing. One with
1527
+ // neither is unclassified, and the platform's own reference says a client
1528
+ // treating 409 as uniformly transient loops forever on half of them — a
1529
+ // label that is taken, a computer at its URL limit, a webhook cap.
1530
+ if (err instanceof ConflictError)
1531
+ return conflictCodeClears(err);
1532
+ return err instanceof RateLimitError || err instanceof ConnectivityError;
1362
1533
  }
1363
1534
  /**
1364
1535
  * The same words, a different question: worth POLLING again.