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,492 @@
1
+ import { z } from 'zod';
2
+ import { CancelledError, isTransientForPoll } from '../errors.js';
3
+ import { guarded, incompleteWarning, json, refused, said } from '../format.js';
4
+ import * as P from '../paths.js';
5
+ /**
6
+ * The template store, and the builds that compile documents into images.
7
+ *
8
+ * Its own module rather than lines in computers.ts, because none of this is
9
+ * about a computer: a template is a document the account owns, and a build is a
10
+ * job that outlives the request that started it. Nothing here reads or writes
11
+ * the session's selected computer.
12
+ *
13
+ * WHAT A MODEL HAS TO BE TOLD, and the reason these descriptions are long. Two
14
+ * of these tools are irreversible in a way that is not obvious from their names.
15
+ * `retire_template` without a version takes EVERY version, and a retired ref can
16
+ * never be published again — so the description says both, in the tool the model
17
+ * reads before deciding, rather than in a 409 it reads afterwards.
18
+ */
19
+ const namespaceArg = {
20
+ namespace: z
21
+ .string()
22
+ .describe('The account id the template is published under. READ IT OFF A `ref`: every row of list_templates carries one shaped `<namespace>/<name>@<version>`, and the part before the slash is the namespace. Rows reading `system` are the templates we publish; anything else is your own account id. There is no separate `namespace` field on a template — the ref is where it lives.'),
23
+ name: z.string().describe('The template name, without the namespace or the version.'),
24
+ };
25
+ const versionArg = z
26
+ .string()
27
+ .optional()
28
+ .describe('A specific MAJOR.MINOR.PATCH. Send it or omit it entirely — an empty string is refused, because omitting it does not mean the same thing on both tools.');
29
+ export const registerTemplates = (server, session, opts) => {
30
+ // What to call the thing that launches a template, when there IS one.
31
+ //
32
+ // `create_computer` is not registered under MANDALA_NO_LIFECYCLE, and a name
33
+ // in a neighbouring tool's description is the same idea by a different route
34
+ // as a tool in the list: the model reads it and tries it. The server
35
+ // instructions were already parameterised on this; the descriptions were not.
36
+ const launcher = opts.lifecycle ? 'create_computer' : 'whatever creates computers on this setup';
37
+ server.registerTool('get_template_schema', {
38
+ title: 'Get the template document schema',
39
+ description: 'The JSON Schema for a `mandala/v1` template document — the declarative form a template is written in. Read this before writing one: it describes the ref, the image family, what the template is layered onto, and the shape a computer gets when a create names no numbers.',
40
+ inputSchema: {},
41
+ annotations: { readOnlyHint: true },
42
+ }, (_args, extra) => guarded(async () => json(await session.api.with(extra.signal).json('GET', P.TEMPLATE_SCHEMA))));
43
+ server.registerTool('check_template', {
44
+ title: 'Check a template document',
45
+ description: 'Check a document against the SCHEMA, without publishing it. Nothing is stored and no ref is claimed, so this is safe on a draft and safe to call repeatedly, and every problem comes back at once. Always check before publishing: a ref is immutable, so a document published with a mistake in it cannot be corrected under that version. WHAT IT CANNOT SEE: this reads the document alone and knows nothing about your account, so `valid: true` does not mean publish_template will succeed. Whether the namespace is yours, whether the family is yours, whether your plan may publish at all, and whether the ref is already taken or retired are all decided at publish time and can still refuse a document that checks out here.',
46
+ inputSchema: {
47
+ document: z
48
+ .string()
49
+ .describe('The document itself, as JSON or YAML — the file contents, not a wrapper.'),
50
+ },
51
+ annotations: { readOnlyHint: true },
52
+ }, ({ document }, extra) => guarded(async () => {
53
+ const body = await session.api
54
+ .with(extra.signal)
55
+ .json('POST', P.TEMPLATE_VALIDATE, {
56
+ raw: P.templateDocument(document),
57
+ });
58
+ // `valid` READ AS A BOOLEAN, not for truthiness (adversarial review,
59
+ // OPL-3835). `json<T>` is a cast and checks nothing at run time, so a
60
+ // body answering `{"valid": "false"}` — a proxy stringifying, a field
61
+ // that changes type — is truthy, and this tool then reported a document
62
+ // the platform had just rejected as valid. The one answer a check tool
63
+ // must never get wrong is "yes".
64
+ if (typeof body.valid !== 'boolean') {
65
+ return refused('POST /templates/validate answered without a boolean `valid`, so this says NOTHING about the document — do not publish on the strength of it. Check it again.', body);
66
+ }
67
+ // NOT `refused` for an invalid document. That is the answer to the
68
+ // question this tool asks — the platform says so with a 200 — and
69
+ // marking it isError would tell the model its request failed when what
70
+ // it actually got is the list of problems it asked for.
71
+ return body.valid
72
+ ? said('The document is valid — against the schema. Publishing can still refuse it for something only your account decides: the namespace, the family, your plan, or a ref already taken. `doc_digest` identifies the document and changes with anything that changes what it MEANS — a label included. Comments, key order, indentation and YAML-versus-JSON do not reach it: the digest is over the canonical re-marshalling of the parsed document, not over your file. `build_digest` covers only what decides the image, so comparing it against a previous check tells you whether an edit means a rebuild — but it is present ONLY for a document with no `spec.from`, and a document that declares build steps must have one, so a buildable document gets `build_digest_needs` instead, naming the parent whose digest would be required.', body)
73
+ : said('The document is NOT valid. Every problem is listed — fix them all and check again rather than one at a time.', body);
74
+ }));
75
+ server.registerTool('publish_template', {
76
+ title: 'Publish a template document',
77
+ description: `Store a document under a ref of your own, so ${launcher} can launch it by name. ` +
78
+ 'THE NAMESPACE IS YOUR ACCOUNT: `metadata.namespace` has to be your account id, and anything else is refused rather than rewritten — `system` included. A REF IS IMMUTABLE: publishing the identical document again succeeds and changes nothing, but publishing a DIFFERENT document under the same ref is refused, and the fix is to bump `metadata.version`. What counts as different is the digest, so a changed label is a change. Check the document first — a mistake published under a version can never be corrected under that version.',
79
+ inputSchema: {
80
+ document: z
81
+ .string()
82
+ .describe('The document itself, as JSON or YAML — the file contents, not a wrapper.'),
83
+ },
84
+ annotations: { openWorldHint: true },
85
+ }, ({ document }, extra) => guarded(async () => {
86
+ const body = await session.api
87
+ .with(extra.signal)
88
+ .json('POST', P.TEMPLATES, {
89
+ raw: P.templateDocument(document),
90
+ });
91
+ // The ref, checked rather than interpolated. This is a WRITE, so a
92
+ // malformed 2xx is the one answer that cannot be shrugged off:
93
+ // `Published undefined` reads as a success and names a ref nothing can
94
+ // resolve, and a model that responds by bumping `metadata.version` and
95
+ // publishing again has claimed two refs for one document — neither of
96
+ // which it can take back (adversarial review, OPL-3835).
97
+ if (typeof body.ref !== 'string' || !body.ref.trim()) {
98
+ return refused('POST /templates answered without a `ref`, so this server cannot say what was stored. THE PUBLISH MAY HAVE SUCCEEDED — read the name with get_template before publishing again, because a ref is immutable and a second version claims a second ref.', body);
99
+ }
100
+ // What it takes to LAUNCH the thing, rather than the flat "Launch it
101
+ // with create_computer" this used to end on. That sentence contradicted
102
+ // build_template's own description two tools down: a document declaring
103
+ // `spec.build` names a family the fleet does not advertise, so following
104
+ // this line led straight into a refusal the server already knew about
105
+ // (adversarial review, OPL-3835). Publishing and being launchable are
106
+ // different questions and this says so, without claiming to know which
107
+ // of the two this document is — the store answers the first, and only
108
+ // the document says whether it declares build steps.
109
+ return said(`Published ${body.ref}. A published template is named by its ref and by nothing else, so its short name still means one of ours. ` +
110
+ `WHETHER IT LAUNCHES depends on the document: pass the ref to ${launcher} as ` +
111
+ '`template` if it layers onto a family the fleet ships, but one declaring `spec.build` steps names a family that has to be built first — that is build_template — and the fleet does not yet advertise a family it built rather than shipped, so a create naming such a ref is still refused.', body);
112
+ }));
113
+ server.registerTool('get_template', {
114
+ title: 'Read a template document',
115
+ description: 'One template as the document it was written as — the lineage, the build steps and the digest, which list_templates drops. Works for your own namespace and for `system`, so you can read what you are layering onto before writing a document of your own. Without `version` this is the newest, which is also what a create naming the unpinned `namespace/name` resolves to.',
116
+ inputSchema: { ...namespaceArg, version: versionArg },
117
+ annotations: { readOnlyHint: true },
118
+ }, ({ namespace, name, version }, extra) => guarded(async () => json(await session.api.with(extra.signal).json('GET', P.templateRef(namespace, name), {
119
+ query: P.templateVersionQuery(version),
120
+ }))));
121
+ server.registerTool('retire_template', {
122
+ title: 'Retire a template you published',
123
+ description: 'Stop a template resolving, and give its row back against your ceiling. WITHOUT `version` THIS RETIRES EVERY VERSION OF THE NAME — that is what retiring a template means here, and it is deliberately not get_template\'s "the newest". Pass `version` to take exactly one. THIS CANNOT BE UNDONE: a retired ref is refused for ever, identical bytes included, so the version you retire can never be published again — publish the next version instead. Computers are NOT affected: a computer is built from the image the ref resolved to and holds no reference to the document, so anything already running, stopped or suspended keeps working. What a retire breaks is resolution — a NEW create naming the ref is refused. Say what it costs before you call it.',
124
+ inputSchema: {
125
+ ...namespaceArg,
126
+ version: versionArg,
127
+ // The same gate delete_computer, restore_snapshot and delete_snapshot
128
+ // take. This is strictly LESS recoverable than any of them — a deleted
129
+ // snapshot's name can be used again, a retired ref never can — and it
130
+ // was the only unrecoverable tool here without one. `destructiveHint`
131
+ // delegates the question to the host application; this asks it here.
132
+ confirm: z
133
+ .literal(true)
134
+ .describe('Must be true. Retiring cannot be undone, and without `version` it takes every version of the name.'),
135
+ },
136
+ annotations: { destructiveHint: true, idempotentHint: false },
137
+ }, ({ namespace, name, version }, extra) => guarded(async () => {
138
+ const body = await session.api
139
+ .with(extra.signal)
140
+ .json('DELETE', P.templateRef(namespace, name), {
141
+ query: P.templateVersionQuery(version),
142
+ });
143
+ // The one unreadable response in this module that must not be smoothed
144
+ // over. `Array.isArray(body.retired) ? … : []` turned a body this
145
+ // server could not read into "Retired 0 version(s)" — a confident report
146
+ // that nothing happened, about an irreversible DELETE the platform
147
+ // answered 2xx to and which may well have just taken every version of
148
+ // the name (adversarial review, OPL-3835).
149
+ if (!Array.isArray(body.retired) || !Array.isArray(body.versions)) {
150
+ return refused('The retire answered without the `retired` and `versions` lists, so this server cannot say what went. THE RETIRE MAY HAVE HAPPENED, and it cannot be undone — read the name with get_template before concluding anything, and do not repeat the call on the assumption that nothing was taken.', body);
151
+ }
152
+ const gone = body.retired;
153
+ const left = body.versions;
154
+ const templates = body.templates;
155
+ const claimed = body.refs_claimed;
156
+ // The lists above are the sentence that must not be wrong about an
157
+ // irreversible DELETE. The account totals are detail: interpolating
158
+ // them untyped turned a 2xx that had the lists but omitted the
159
+ // counters into "undefined template(s)" / "undefined ref(s)" in the
160
+ // prose a model reads first (adversarial review, OPL-4314). Omit the
161
+ // clause rather than invent a number; the JSON is still attached.
162
+ const totals = typeof templates === 'number' &&
163
+ Number.isFinite(templates) &&
164
+ typeof claimed === 'number' &&
165
+ Number.isFinite(claimed)
166
+ ? ` The account now holds ${templates} template(s), and has claimed ${claimed} ref(s) — that second number does not go down, because a retired ref still counts.`
167
+ : '';
168
+ return said(`Retired ${gone.length} version(s): ${gone.join(', ')}. ` +
169
+ (left.length
170
+ ? `${left.join(', ')} ${left.length === 1 ? 'is' : 'are'} still published under this name.`
171
+ : 'Nothing is published under this name any more.') +
172
+ totals, body);
173
+ }));
174
+ server.registerTool('build_template', {
175
+ title: 'Compile a template document into an image',
176
+ description: 'Compile a document that declares `spec.build` steps into a golden image. Returns IMMEDIATELY with a job — a build takes minutes, an agent image roughly fifteen — and watch_build is how you follow it. THE NAMESPACE AND THE FAMILY BOTH HAVE TO BE YOURS: `spec.family` is what the image is called on a hypervisor, in a directory shared with every computer on that machine, so a build may only write into `golden-<your account id>` or that and a `-` and a name of your choosing. A refusal saying a host is busy is not a problem with your document — one build runs per hypervisor — and is worth retrying. What you build is NOT launchable yet: the fleet does not advertise a family it built rather than shipped, so a create naming such a ref is still refused.',
177
+ inputSchema: {
178
+ document: z
179
+ .string()
180
+ .describe('The document itself, as JSON or YAML — the file contents, not a wrapper.'),
181
+ no_reuse: z
182
+ .boolean()
183
+ .optional()
184
+ .describe("Build again even when an image already carries this document's build digest. Identical documents normally share an image, which is what makes a repeated build cheap — so leave this off unless you specifically want the work done twice."),
185
+ },
186
+ annotations: { openWorldHint: true },
187
+ }, ({ document, no_reuse }, extra) => guarded(async () => {
188
+ const body = await session.api
189
+ .with(extra.signal)
190
+ .json('POST', P.BUILDS, {
191
+ raw: P.templateDocument(document),
192
+ query: P.buildQuery(no_reuse),
193
+ });
194
+ // The id, checked: it is the only handle on a job that outlives this
195
+ // request, and `Build undefined started` sends a model to watch_build
196
+ // with a build_id it cannot have (adversarial review, OPL-3835).
197
+ if (typeof body.id !== 'string' || !body.id.trim()) {
198
+ return refused('POST /builds answered without an `id`, so there is no handle to follow this build with. THE BUILD MAY HAVE STARTED — call list_builds to find it rather than building again, since a build is minutes of work on a hypervisor that runs one at a time.', body);
199
+ }
200
+ // The ref is named only when the platform sent one. Unchecked, a 2xx
201
+ // without it read `Build bld-1 started for undefined` — the same
202
+ // sentence-level failure the `id` guard above was added for
203
+ // (OPL-3835), and the one publish_template already guards its own ref
204
+ // against. The build handle is the checked `id` either way, so a
205
+ // missing ref costs nothing but the clause that names the document.
206
+ const ref = typeof body.ref === 'string' && body.ref.trim() ? body.ref : undefined;
207
+ return said(`Build ${body.id} started${ref ? ` for ${ref}` : ''}. It is not finished — call watch_build with that id, or get_build to check once.`, body);
208
+ }));
209
+ server.registerTool('list_builds', {
210
+ title: 'List builds',
211
+ description: 'Every build this account has started that the fleet still holds a record of, newest first. A build lives on the hypervisor that ran it, so this asks all of them — and WITHOUT allow_partial, one that cannot be reached makes the platform refuse rather than answer short, so an empty or small list is the truth rather than an outage. Pass allow_partial and that stops being so: you get the short answer, opening with an INCOMPLETE line, and nothing else in the result says how much is missing.',
212
+ inputSchema: {
213
+ allow_partial: z
214
+ .boolean()
215
+ .optional()
216
+ .describe('Accept a short list when a hypervisor cannot be reached, instead of the 503 the platform answers by default. Nothing in the rows will say the answer was partial — a short build list has no marker in it at all, so the INCOMPLETE line is the whole of the evidence. Read it before concluding anything about what this account has built.'),
217
+ },
218
+ annotations: { readOnlyHint: true },
219
+ }, ({ allow_partial }, extra) => guarded(async () => {
220
+ // `listing`, not `json` (OPL-3840). This route fans out, and like every
221
+ // other fan-out on the v1 surface it FAILS CLOSED: `forward` in
222
+ // lib/surface turns a response carrying X-GC-Incomplete into a 503. So
223
+ // without the flag a short list still cannot arrive — but WITH it one
224
+ // can, and then the header is the only news there is.
225
+ //
226
+ // The platform read `allow_partial` here from the day this route
227
+ // started fanning out and did not DOCUMENT it until OPL-3840, which is
228
+ // why this tool sent nothing and why the comment that stood here said a
229
+ // short list could not arrive at all. What that cost was a build
230
+ // listing being strictly less available than a computer listing.
231
+ const { items, incomplete } = await session.api
232
+ .with(extra.signal)
233
+ .listing(P.BUILDS, {
234
+ query: { allow_partial: allow_partial ? 1 : undefined },
235
+ });
236
+ if (!Array.isArray(items)) {
237
+ const got = items === undefined ? 'no body at all' : items === null ? 'null' : typeof items;
238
+ return refused(`GET /builds answered with ${got}, not a list of builds. This is not an empty list — do not conclude anything about what exists from it.`, items);
239
+ }
240
+ // Each ROW checked too, which list_computers and list_snapshots do and
241
+ // this one did not (adversarial review, OPL-3835). `Array.isArray`
242
+ // alone passed `[null]` and `["bad projection"]` through as an
243
+ // inventory, and a model reading a build row that is a string gets
244
+ // `undefined` for every field it asks about with nothing saying the row
245
+ // was never a build.
246
+ const malformed = items.filter((item) => item === null || typeof item !== 'object' || Array.isArray(item)).length;
247
+ const builds = items.filter((item) => item !== null && typeof item === 'object' && !Array.isArray(item));
248
+ const warning = incompleteWarning('builds', incomplete) +
249
+ (malformed
250
+ ? `WARNING: ignored ${malformed} malformed build entr${malformed === 1 ? 'y' : 'ies'} from the platform.\n\n`
251
+ : '');
252
+ if (!builds.length && malformed) {
253
+ return refused(`${warning}No valid builds remained. This is not an empty build list — do not conclude anything about what exists from it.`, items);
254
+ }
255
+ // `json`, with nothing to say, ONLY when there is nothing to say. The
256
+ // fast path used to be `if (!malformed) return json(builds)`, which
257
+ // returns bare data — so a warning added above it would have been
258
+ // dropped on exactly the answers that most needed it.
259
+ if (!warning)
260
+ return json(builds);
261
+ // An empty answer from a fleet that could not all be asked is not an
262
+ // empty account, and the two are told apart here rather than left to
263
+ // the model: with no rows and no stub rows there is nothing at all in
264
+ // the payload to suggest a hypervisor is away.
265
+ if (!builds.length) {
266
+ return said(`${warning}No builds came back from the part of the fleet that answered. This is NOT "no builds" — retry in a moment for a complete answer.`);
267
+ }
268
+ return said(`${warning}${builds.length} build(s).`, builds);
269
+ }));
270
+ server.registerTool('get_build', {
271
+ title: 'Get a build',
272
+ description: 'What became of one build, which template it was for, and which step it is on. Reads once and returns; watch_build is what follows a running one. It stays readable after the build has finished, so this is also how you find out which step failed on a build nobody was watching.',
273
+ inputSchema: { build_id: z.string().describe('The id build_template returned.') },
274
+ annotations: { readOnlyHint: true },
275
+ }, ({ build_id }, extra) => guarded(async () => {
276
+ // BOTH routes, because neither answer contains the other. The two
277
+ // projectors in the platform's lib/projection overlap only on `id`,
278
+ // `status` and `error`: publicTemplateBuild carries `ref` and both
279
+ // timestamps, publicBuildProgress carries the phase and the steps. This
280
+ // tool was pinned as "progress is a superset", which was simply untrue —
281
+ // read that way it could not tell a model WHICH TEMPLATE a build was for
282
+ // (/code-review, OPL-3835).
283
+ //
284
+ // One tool rather than two, still. An MCP client pays for every tool in
285
+ // the model's context before any is called, and "get_build" and
286
+ // "get_build_progress" differing by a word is how a model picks the
287
+ // wrong one. Two requests inside one call is the cheaper trade.
288
+ const api = session.api.with(extra.signal);
289
+ // allSettled, not all: the two legs are INDEPENDENT fleet walks — the job
290
+ // read forwards through hvAny, the progress read is a local handler that
291
+ // does its own — so one can fail while the other succeeds, and
292
+ // Promise.all threw the good half away. Progress is the half a model
293
+ // calls this for after a build nobody watched (the status, the phase, the
294
+ // failed step), so it is required and the job read is best effort: a
295
+ // fleet hiccup costs `ref` and the timestamps rather than the whole
296
+ // answer (/code-review, OPL-3835).
297
+ const [jobRead, progressRead] = await Promise.allSettled([
298
+ api.json('GET', P.build(build_id)),
299
+ api.json('GET', P.buildAction(build_id, 'progress')),
300
+ ]);
301
+ if (progressRead.status === 'rejected')
302
+ throw progressRead.reason;
303
+ const progress = progressRead.value;
304
+ // Best effort means AVAILABILITY, not everything (adversarial review,
305
+ // OPL-3835). `status === 'fulfilled' ? … : undefined` swallowed the
306
+ // rejection whatever it was, so a 401, a 403, a 404 or the caller's own
307
+ // cancellation came back as a successful partial answer with a note
308
+ // about the fleet — the shape a caller reading `isError` to decide
309
+ // whether it may act on the result cannot see through. Only the
310
+ // statuses that clear on their own are worth losing `ref` and the
311
+ // timestamps for; the rest are decisions, and a decision suppressed is a
312
+ // decision the model never learns about.
313
+ //
314
+ // `isTransientForPoll` rather than a list here, because it is the same
315
+ // question the wait loops in computers.ts ask and this is the same kind
316
+ // of call: a read, where replaying costs nothing, so a 52x during an
317
+ // outage counts as a hiccup too. It says no to CancelledError, which is
318
+ // the one that must never be suppressed — nobody is waiting for this
319
+ // answer.
320
+ if (jobRead.status === 'rejected' && !isTransientForPoll(jobRead.reason)) {
321
+ throw jobRead.reason;
322
+ }
323
+ let job = jobRead.status === 'fulfilled' ? jobRead.value : undefined;
324
+ // The two reads land at different instants, so a build that finishes
325
+ // between them would otherwise merge into a record contradicting itself.
326
+ // Progress owns `status` and `done` because that is what a model
327
+ // branches on.
328
+ //
329
+ // BOTH ORDERS, and the first fix only had one of them (adversarial
330
+ // review, OPL-3835). Job-then-finish-then-progress gives `running`
331
+ // beside a populated `finished_at`, which the delete below handles.
332
+ // Progress-then-finish-then-job gives the mirror image — `succeeded`,
333
+ // `done: true`, and no finish time at all, a build that completed
334
+ // without ever ending — and that one cannot be fixed by dropping a
335
+ // field, because the field that is missing is the true one. So the job
336
+ // record is read again: one extra request, only inside the race window,
337
+ // and by the time it is made the record it asks for has settled.
338
+ if (job && progress.done === true && job.finished_at === undefined) {
339
+ try {
340
+ job = await api.json('GET', P.build(build_id));
341
+ }
342
+ catch (err) {
343
+ // The first read already succeeded, so a second failure costs the
344
+ // reconciliation rather than the call. Except a cancellation, which
345
+ // means there is nobody left to answer.
346
+ if (err instanceof CancelledError)
347
+ throw err;
348
+ }
349
+ }
350
+ const merged = { ...job, ...progress };
351
+ if (job && progress.done !== true)
352
+ delete merged.finished_at;
353
+ if (!job) {
354
+ merged.partial = 'the build record could not be read; ref and timestamps are missing';
355
+ }
356
+ return json(merged);
357
+ }));
358
+ server.registerTool('watch_build', {
359
+ title: 'Watch a build until it finishes',
360
+ description: "Follow a build to its end and report what happened. Streams the platform's own progress — each event is sent only when something actually moved — and logs each one as it arrives, so a long build is visibly alive rather than indistinguishable from a hang. A build that FAILED is a normal answer here, not an error: read `status` and the failed step. Attaching to a build that has already finished is fine and returns immediately.",
361
+ inputSchema: { build_id: z.string().describe('The id build_template returned.') },
362
+ annotations: { readOnlyHint: true, openWorldHint: true },
363
+ }, ({ build_id }, extra) => guarded(async () => {
364
+ let last;
365
+ // The token the CLIENT sends when it wants progress. Without one there
366
+ // is nothing to address a progress notification to, and this falls back
367
+ // to logging alone.
368
+ const progressToken = extra._meta?.progressToken;
369
+ let sent = 0;
370
+ // Whether a well-formed `done` actually arrived, as against whether any
371
+ // event did (adversarial review, OPL-3835). Without it a stream that
372
+ // reached EOF after a `progress` — a proxy cutting it, a host going away
373
+ // — fell out of the loop with `last` set and was reported through
374
+ // `said(...)` as a finished build whose status happened to be `running`.
375
+ // A tool whose whole promise is "watch until it finishes" must not
376
+ // answer that way when it did not.
377
+ let sawDone = false;
378
+ for await (const ev of session.api.sse('GET', P.buildAction(build_id, 'events'), {
379
+ signal: extra.signal,
380
+ })) {
381
+ if (ev.event === 'error') {
382
+ // The STREAM failed, not the build. Said as such, because a model
383
+ // told "the build failed" would go and rewrite a document that is
384
+ // fine — and the build is very likely still running.
385
+ return refused(`The event stream for ${build_id} ended. This says nothing about the build itself, which is probably still running — call get_build to find out.`, ev.data);
386
+ }
387
+ if (ev.event !== 'progress' && ev.event !== 'done')
388
+ continue;
389
+ if (!isRecord(ev.data))
390
+ continue;
391
+ // An event about ANOTHER build is not this build's news. The stream is
392
+ // per-build so this should never arrive, but `id` is one of the three
393
+ // fields both of the platform's build projectors carry, so checking it
394
+ // is free — and a misrouted frame reported as this build's outcome is
395
+ // the kind of wrong answer nobody could spot afterwards.
396
+ if (typeof ev.data.id === 'string' && ev.data.id !== build_id)
397
+ continue;
398
+ if (ev.event === 'done') {
399
+ // A `done` is terminal only if it SAYS SO (adversarial review,
400
+ // OPL-3835). `isRecord` alone called `event: done\ndata: {}`
401
+ // well-formed, set the flag, and answered `Build bld-1 undefined.`;
402
+ // it accepted `{status: "running", done: false}` as a completed
403
+ // watch too, which is the very misreport `sawDone` was added to
404
+ // prevent, reached from the other side.
405
+ //
406
+ // `done: true` and a status that is a non-empty string, rather than
407
+ // a list of the statuses a build may end in. The platform owns that
408
+ // vocabulary — `succeeded` and `failed` are the two this server has
409
+ // seen and a cancelled build would be a third — so an enum here
410
+ // would refuse a real outcome the first time one was added, while
411
+ // these two facts are exactly what the sentence at the end reads.
412
+ if (ev.data.done !== true || typeof ev.data.status !== 'string' || !ev.data.status) {
413
+ continue;
414
+ }
415
+ sawDone = true;
416
+ }
417
+ last = ev.data;
418
+ const line = `${ev.data.phase ?? '?'} ${ev.data.step ?? 0}/${ev.data.of ?? 0} ${ev.data.note ?? ''}`.trim();
419
+ // A PROGRESS notification, not only a logging one, because it is the
420
+ // only frame that CAN hold a long request open: `_onprogress` in the
421
+ // SDK's shared/protocol.js resets a pending request's timer, and
422
+ // `notifications/message` — what sendLoggingMessage emits — never
423
+ // touches it.
424
+ //
425
+ // IT IS NOT SUFFICIENT ON ITS OWN, and the first version of this
426
+ // comment claimed it was (/code-review, OPL-3835). protocol.js reads
427
+ // `options?.resetTimeoutOnProgress ?? false`, so the reset happens only
428
+ // for a client that asked for it when it made the call. One that mints
429
+ // a progressToken and leaves that option alone is still cancelled at
430
+ // the 60s default on a build that takes fifteen minutes. What this does
431
+ // is make the keepalive AVAILABLE — without it no client could hold the
432
+ // request open at all; with it, one that opts in can. The rest is the
433
+ // client's to set, and get_build is the answer for a client that cannot.
434
+ if (progressToken !== undefined) {
435
+ sent += 1;
436
+ await extra
437
+ .sendNotification({
438
+ method: 'notifications/progress',
439
+ params: {
440
+ progressToken,
441
+ // `of` is the step count, which is 0 until the build reaches
442
+ // its first step — sent only once it means something, since a
443
+ // total of 0 renders as a finished bar.
444
+ // The event COUNT, not the step index. The SDK's ProgressSchema
445
+ // asks that this increase every time, and `step` is 0 for every
446
+ // pre-step phase — planning, staging, the multi-gigabyte base
447
+ // copy — and stays 0 for the whole life of a document with no
448
+ // build steps, so successive frames repeated `progress: 0`.
449
+ progress: sent,
450
+ message: line,
451
+ },
452
+ })
453
+ .catch(() => { });
454
+ }
455
+ // Kept as well: this is what a person watching a terminal sees, and it
456
+ // is the only channel when the client asked for no progress.
457
+ await server.server.sendLoggingMessage({ level: 'info', data: line }).catch(() => { });
458
+ if (ev.event === 'done')
459
+ break;
460
+ }
461
+ // `|| !last` as well, which is unreachable — a `done` sets both in the
462
+ // same breath — and is what lets the compiler narrow `last` below.
463
+ if (!sawDone || !last) {
464
+ // Both halves of the same failure, and both are refusals: the stream
465
+ // is the platform's contract that `done` is the last event, so one
466
+ // that ends without a well-formed one has been CUT rather than
467
+ // completed. Saying so points the model at the poll that can still
468
+ // answer, instead of letting it act on a status that was true
469
+ // whenever the connection died.
470
+ return refused(last
471
+ ? `The event stream for ${build_id} ended before the build did. This is the stream failing, not the build — it is probably still running. Call get_build for where it has got to.`
472
+ : `The event stream for ${build_id} ended without sending anything. Call get_build for the outcome.`, last);
473
+ }
474
+ // Not `refused` for a failed build. It is an outcome with a remedy —
475
+ // the failed step names what to fix — and marking it isError would tell
476
+ // the model its request failed rather than that its document did.
477
+ const failed = Array.isArray(last.steps)
478
+ ? last.steps.find((s) => s?.status === 'failed')
479
+ : undefined;
480
+ return said(last.status === 'succeeded'
481
+ ? `Build ${build_id} succeeded. The image exists, but the fleet does not yet advertise a family it built rather than shipped, so a create naming this ref is still refused.`
482
+ : `Build ${build_id} ${last.status}.` +
483
+ (failed
484
+ ? ` Step ${failed.n} (${failed.kind}: ${failed.label}) is the one that failed.`
485
+ : '') +
486
+ (last.error ? ` ${last.error}` : ''), last);
487
+ }));
488
+ };
489
+ function isRecord(v) {
490
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
491
+ }
492
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/tools/templates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Session } from '../session.js';
3
+ /** Knobs that change which tools exist, rather than what they do. */
4
+ export type ToolOptions = {
5
+ /**
6
+ * Whether this server may make and destroy computers.
7
+ *
8
+ * A knob rather than a constant because creation from inside an agent loop is
9
+ * where a runaway turns into an invoice. The tools are registered by default —
10
+ * a one-line install that cannot produce a desktop is not much of a demo — but
11
+ * an operator standing this up for somebody else can turn them off, and then
12
+ * they are absent from the tool list rather than present and refusing. A tool
13
+ * a model can see is a tool it will try.
14
+ */
15
+ lifecycle: boolean;
16
+ };
17
+ export type Registrar = (server: McpServer, session: Session, opts: ToolOptions) => void;
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,qEAAqE;AACrE,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;;;;;OASG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { Registrar } from './types.js';
2
+ export declare const registerWebhooks: Registrar;
3
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/tools/webhooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAyK5C,eAAO,MAAM,gBAAgB,EAAE,SA6M9B,CAAC"}