@runtypelabs/cli 2.59.0 → 2.59.2

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 (84) hide show
  1. package/dist/chunk-0-T9NWjH.js +3899 -0
  2. package/dist/chunk-0x7UKC1U.js +17 -0
  3. package/dist/chunk-2ySxXtcg.js +418 -0
  4. package/dist/chunk-3An_BRHv.js +501 -0
  5. package/dist/chunk-6uTxgdiY.js +16218 -0
  6. package/dist/chunk-87nRnNRG.js +1363 -0
  7. package/dist/chunk-B3wo8uRx.js +322 -0
  8. package/dist/chunk-BGp3ZdBy.js +560 -0
  9. package/dist/chunk-BJQv9Elp.js +107 -0
  10. package/dist/chunk-BOpO6xE-.js +6053 -0
  11. package/dist/chunk-BQXXCL8B.js +102 -0
  12. package/dist/chunk-BV-8isOd.js +34 -0
  13. package/dist/chunk-BVMhjGVe.js +826 -0
  14. package/dist/chunk-BW2fg46T.js +141 -0
  15. package/dist/chunk-BWJdY5Ag.js +36 -0
  16. package/dist/chunk-BYJOrLhh.js +302 -0
  17. package/dist/chunk-BYab8EVL.js +127 -0
  18. package/dist/chunk-BfEKvp7h.js +82 -0
  19. package/dist/chunk-BhgLSfH6.js +1123 -0
  20. package/dist/chunk-BirUxTtH.js +296 -0
  21. package/dist/chunk-BmB6UVZ9.js +1559 -0
  22. package/dist/chunk-BnWhIqW1.js +109 -0
  23. package/dist/chunk-Bpfnfk0t.js +123 -0
  24. package/dist/chunk-BrhgdnM3.js +108 -0
  25. package/dist/chunk-Bt0XAgX5.js +327 -0
  26. package/dist/chunk-Bu0FJr6S.js +84 -0
  27. package/dist/chunk-BvEoKpN3.js +255 -0
  28. package/dist/chunk-BxNJgYbE.js +28 -0
  29. package/dist/chunk-C1OFhd7F.js +6131 -0
  30. package/dist/chunk-C7e7WwbU.js +331 -0
  31. package/dist/chunk-CDTYP6bN.js +558 -0
  32. package/dist/chunk-CGDQoqDC.js +24 -0
  33. package/dist/chunk-CJraEBVd.js +127 -0
  34. package/dist/chunk-CQrNhOtv.js +40 -0
  35. package/dist/chunk-CW6AzBH6.js +128 -0
  36. package/dist/chunk-CXgL4w-9.js +317 -0
  37. package/dist/chunk-CZrqoPyf.js +233 -0
  38. package/dist/chunk-CcFCyqbB.js +83 -0
  39. package/dist/chunk-CdaIvO-y.js +485 -0
  40. package/dist/chunk-CehSXtgK.js +624 -0
  41. package/dist/chunk-Ciqkop65.js +218 -0
  42. package/dist/chunk-CoAWdSiE.js +43 -0
  43. package/dist/chunk-Crb4y7t4.js +830 -0
  44. package/dist/chunk-CrlqIfmb.js +337 -0
  45. package/dist/chunk-Cs4pwCQ-.js +324 -0
  46. package/dist/chunk-CwM_9y01.js +519 -0
  47. package/dist/chunk-CyRbIJg8.js +440 -0
  48. package/dist/chunk-D0OHsyMy.js +212 -0
  49. package/dist/chunk-D3y2bURs.js +1347 -0
  50. package/dist/chunk-D4_mJk03.js +2338 -0
  51. package/dist/chunk-D6C8rned.js +578 -0
  52. package/dist/chunk-D9ZbUnks.js +11 -0
  53. package/dist/chunk-DEPmLshX.js +834 -0
  54. package/dist/chunk-DFj1UPB2.js +330 -0
  55. package/dist/chunk-DGdFdZVf.js +197 -0
  56. package/dist/chunk-DGv7X9S_.js +63 -0
  57. package/dist/chunk-DJQ47WYZ.js +664 -0
  58. package/dist/chunk-DJw3O61j.js +1974 -0
  59. package/dist/chunk-DK4FbWVK.js +100 -0
  60. package/dist/chunk-DSRYhUus.js +993 -0
  61. package/dist/chunk-DajZ9Eg4.js +299 -0
  62. package/dist/chunk-Df4dunvU.js +195 -0
  63. package/dist/chunk-DjNTPhfO.js +481 -0
  64. package/dist/chunk-Dn8eaJuu.js +1042 -0
  65. package/dist/chunk-DwDWqSxM.js +637 -0
  66. package/dist/chunk-Dz7cEtxl.js +84 -0
  67. package/dist/chunk-E_0bgjrS.js +359 -0
  68. package/dist/chunk-KPYN2vUp.js +776 -0
  69. package/dist/chunk-KcqPQJBi.js +86 -0
  70. package/dist/chunk-N9H0JnoG.js +349 -0
  71. package/dist/chunk-NONFcXkn.js +728 -0
  72. package/dist/chunk-PrFjcpmH.js +256 -0
  73. package/dist/chunk-QSfNYiw9.js +11 -0
  74. package/dist/chunk-R265bzj9.js +226 -0
  75. package/dist/chunk-X9V_r6jM.js +166 -0
  76. package/dist/chunk-ZBtPUtyA.js +55 -0
  77. package/dist/chunk-ZCTfmOs7.js +4939 -0
  78. package/dist/chunk-h6oavj0t.js +97 -0
  79. package/dist/chunk-i7DwkvvD.js +42 -0
  80. package/dist/chunk-mG-4eZ6S.js +203 -0
  81. package/dist/chunk-o0ygUbCf.js +313 -0
  82. package/dist/chunk-ucu5lrDZ.js +5715 -0
  83. package/dist/index.js +11367 -73764
  84. package/package.json +6 -6
@@ -0,0 +1,1347 @@
1
+ #!/usr/bin/env node
2
+ import { P as getRuntypeHomeDir, l as normalizeApiUrl, s as getInvocationOptions } from "./index.js";
3
+ import { i as HARNESS_TRACE_ARTIFACT, o as harnessTurnBindingSchema, r as HARNESS_BINDING_METADATA_KEY } from "./chunk-Bu0FJr6S.js";
4
+ import { i as getApiUrl, r as createCliClient } from "./chunk-BW2fg46T.js";
5
+ import { n as ensureAuth } from "./chunk-CJraEBVd.js";
6
+ import { i as createDeviceListenIdentityStore, r as runDeviceListen, t as DeviceListenCleanupError } from "./chunk-Dn8eaJuu.js";
7
+ import { t as HARNESS_LIMITS } from "./chunk-0x7UKC1U.js";
8
+ import { t as redactHarnessValue } from "./chunk-KcqPQJBi.js";
9
+ import { Command } from "commander";
10
+ import { join } from "node:path";
11
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
12
+ import { chmod, link, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
13
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
+ import { createServer, request } from "node:http";
15
+ // src/lib/local-harness/host/replay.ts
16
+ /** A content-free durable turn reservation survives process failure without replaying inference. */
17
+ async function reserveHarnessTurn(directory, traceId, expiresAt) {
18
+ if (!/^[0-9a-f]{32}$/.test(traceId)) throw new Error("Invalid harness trace identity.");
19
+ await mkdir(directory, {
20
+ recursive: true,
21
+ mode: 448
22
+ });
23
+ await chmod(directory, 448);
24
+ const names = await readdir(directory);
25
+ let retained = 0;
26
+ for (const name of names) {
27
+ if (!/^[0-9a-f]{32}\.json$/.test(name)) continue;
28
+ const file = join(directory, name);
29
+ try {
30
+ if ((await stat(file)).size > 128) throw new Error("Invalid harness replay reservation.");
31
+ const reservation = JSON.parse(await readFile(file, "utf8"));
32
+ if (typeof reservation.expiresAt !== "number" || !Number.isFinite(reservation.expiresAt)) throw new Error("Invalid harness replay reservation.");
33
+ if (reservation.expiresAt <= Date.now()) await unlink(file);
34
+ else retained += 1;
35
+ } catch (error) {
36
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") continue;
37
+ throw new Error("Harness replay state needs inspection before running more turns.", { cause: error });
38
+ }
39
+ }
40
+ if (retained >= Math.floor(HARNESS_LIMITS.maxSpoolBytes / HARNESS_LIMITS.maxControlReplyBytes)) throw new Error("Harness replay reservation capacity reached.");
41
+ let handle;
42
+ try {
43
+ handle = await open(join(directory, `${traceId}.json`), "wx", 384);
44
+ } catch (error) {
45
+ if (error instanceof Error && "code" in error && error.code === "EEXIST") return false;
46
+ throw error;
47
+ }
48
+ try {
49
+ await handle.writeFile(JSON.stringify({ expiresAt }));
50
+ await handle.sync();
51
+ } finally {
52
+ await handle.close();
53
+ }
54
+ return true;
55
+ }
56
+ // src/lib/local-harness/host/sessions.ts
57
+ const MAX_RETIRED_CONTEXTS = 4096;
58
+ const STRANDED_SESSION_MESSAGE = "A retired harness session did not stop; its slot stays reserved until the harness restarts.";
59
+ var HarnessAdmissionError = class extends Error {};
60
+ var HarnessCleanupError = class extends Error {
61
+ failures;
62
+ constructor(failures = []) {
63
+ super("Harness cleanup failed or timed out; owned execution may still be running." + (failures.length ? ` Cleanup stages: ${failures.join("; ")}.` : ""));
64
+ this.failures = failures;
65
+ }
66
+ };
67
+ async function cleanupFailure(stage, work, timeoutMs) {
68
+ let timer;
69
+ try {
70
+ return await Promise.race([work.then(() => void 0, () => `${stage} failed`), new Promise((resolve) => {
71
+ timer = setTimeout(() => resolve(`${stage} timed out`), timeoutMs);
72
+ })]);
73
+ } finally {
74
+ if (timer) clearTimeout(timer);
75
+ }
76
+ }
77
+ /** Keeps each native conversation sequential; retired and failed contexts stay refused as tombstones. */
78
+ var HarnessSessions = class {
79
+ backend;
80
+ config;
81
+ sessions = /* @__PURE__ */ new Map();
82
+ retired = /* @__PURE__ */ new Set();
83
+ releasing = /* @__PURE__ */ new Set();
84
+ stranded = /* @__PURE__ */ new Set();
85
+ closing = false;
86
+ cleanupFailures = /* @__PURE__ */ new Set();
87
+ uses = 0;
88
+ constructor(backend, config) {
89
+ this.backend = backend;
90
+ this.config = config;
91
+ }
92
+ get count() {
93
+ return this.sessions.size;
94
+ }
95
+ async prompt(contextId, input) {
96
+ if (this.closing) throw new HarnessAdmissionError("Harness is stopping.");
97
+ if (this.retired.has(contextId)) throw new HarnessAdmissionError("This session was retired to admit newer conversations; start a new conversation.");
98
+ let state = this.sessions.get(contextId);
99
+ if (!state) {
100
+ if (this.stranded.size >= HARNESS_LIMITS.maxSessions) throw new HarnessAdmissionError(STRANDED_SESSION_MESSAGE);
101
+ const released = this.sessions.size + this.stranded.size >= HARNESS_LIMITS.maxSessions ? this.retireIdle() : null;
102
+ if (released === void 0) throw new HarnessAdmissionError("Harness session limit reached; every session has a turn in progress.");
103
+ const admitted = {
104
+ tail: Promise.resolve(),
105
+ pending: 0,
106
+ turns: 0,
107
+ blocked: false,
108
+ lastUsed: 0
109
+ };
110
+ if (released) admitted.tail = released.then((stopped) => {
111
+ if (stopped) return;
112
+ admitted.predecessorStranded = true;
113
+ if (this.sessions.get(contextId) === admitted) this.sessions.delete(contextId);
114
+ });
115
+ state = admitted;
116
+ this.sessions.set(contextId, state);
117
+ }
118
+ state.lastUsed = ++this.uses;
119
+ if (state.blocked) throw new HarnessAdmissionError("This session cannot resume after an incomplete turn.");
120
+ if (state.turns + state.pending >= this.config.maxTurns) throw new HarnessAdmissionError("Harness turn limit reached.");
121
+ if (state.pending >= 1 + HARNESS_LIMITS.maxQueuedTurnsPerSession) throw new HarnessAdmissionError("Harness session queue is full.");
122
+ state.pending += 1;
123
+ const current = state;
124
+ const execute = async () => {
125
+ try {
126
+ input.signal.throwIfAborted();
127
+ if (current.predecessorStranded) throw new HarnessAdmissionError(STRANDED_SESSION_MESSAGE);
128
+ if (current.blocked || this.closing) throw new HarnessAdmissionError("Harness session is closed.");
129
+ const controller = new AbortController();
130
+ current.controller = controller;
131
+ const abort = () => controller.abort();
132
+ input.signal.addEventListener("abort", abort, { once: true });
133
+ if (input.signal.aborted) abort();
134
+ try {
135
+ current.turns += 1;
136
+ if (!current.session) current.launching = Promise.resolve().then(() => this.backend.start(this.config, {
137
+ sessionId: contextId,
138
+ signal: controller.signal,
139
+ deadlineAt: input.deadlineAt
140
+ })).then(async (session) => {
141
+ if (controller.signal.aborted || this.closing) {
142
+ const failure = await cleanupFailure("session close", Promise.resolve().then(() => session.close()), 2 * HARNESS_LIMITS.cancelGraceMs);
143
+ if (failure) this.cleanupFailures.add(failure);
144
+ throw new HarnessAdmissionError("Harness session stopped during launch.");
145
+ }
146
+ return session;
147
+ });
148
+ const session = current.session ?? await abortable(current.launching, controller.signal);
149
+ if (!session) throw new HarnessAdmissionError("Harness backend did not create a session.");
150
+ current.session = session;
151
+ controller.signal.throwIfAborted();
152
+ const result = await abortable(session.prompt({
153
+ ...input,
154
+ signal: controller.signal
155
+ }), controller.signal);
156
+ if (result.status !== "completed") current.blocked = true;
157
+ return result;
158
+ } finally {
159
+ input.signal.removeEventListener("abort", abort);
160
+ current.controller = void 0;
161
+ }
162
+ } catch (error) {
163
+ current.blocked = true;
164
+ if (current.session) {
165
+ const failure = await cleanupFailure("session cancellation", Promise.resolve().then(() => current.session.cancel()), HARNESS_LIMITS.cancelGraceMs);
166
+ if (failure) this.cleanupFailures.add(failure);
167
+ }
168
+ throw error;
169
+ } finally {
170
+ current.pending -= 1;
171
+ }
172
+ };
173
+ const result = current.tail.then(execute);
174
+ current.tail = result.then(() => {}, () => {});
175
+ return result;
176
+ }
177
+ retireIdle() {
178
+ const idle = [...this.sessions].filter(([, state]) => state.pending === 0);
179
+ const victim = idle.find(([, state]) => state.blocked) ?? idle.sort(([, a], [, b]) => a.lastUsed - b.lastUsed)[0];
180
+ if (!victim) return void 0;
181
+ const [contextId, state] = victim;
182
+ this.sessions.delete(contextId);
183
+ this.retired.add(contextId);
184
+ if (this.retired.size > MAX_RETIRED_CONTEXTS) this.retired.delete(this.retired.values().next().value);
185
+ state.blocked = true;
186
+ const release = this.release(state);
187
+ this.releasing.add(release);
188
+ release.then(() => this.releasing.delete(release));
189
+ return release;
190
+ }
191
+ /** Resolves false when the backend did not stop; that state stays reserved until its one close attempt settles. */
192
+ async release(state) {
193
+ const failures = [];
194
+ const pending = await cleanupFailure("session cancellation", state.tail, HARNESS_LIMITS.cancelGraceMs);
195
+ if (pending) failures.push(pending);
196
+ if (state.launching && !state.session) {
197
+ const failure = await cleanupFailure("session launch", state.launching.then(() => {}, () => {}), 2 * HARNESS_LIMITS.cancelGraceMs);
198
+ if (failure) failures.push(failure);
199
+ }
200
+ if (state.session) {
201
+ state.closing ??= Promise.resolve().then(() => state.session.close());
202
+ const failure = await cleanupFailure("session close", state.closing, 2 * HARNESS_LIMITS.cancelGraceMs);
203
+ if (failure) failures.push(failure);
204
+ }
205
+ const stopped = failures.length === 0;
206
+ state.cleanupFailures = failures;
207
+ if (stopped) this.stranded.delete(state);
208
+ else this.stranded.add(state);
209
+ return stopped;
210
+ }
211
+ async cancelAll() {
212
+ await Promise.all([...this.sessions.values(), ...this.stranded].map(async (state) => {
213
+ state.blocked = true;
214
+ state.controller?.abort();
215
+ if (state.session) {
216
+ const failure = await cleanupFailure("session cancellation", Promise.resolve().then(() => state.session.cancel()), HARNESS_LIMITS.cancelGraceMs);
217
+ if (failure) this.cleanupFailures.add(failure);
218
+ }
219
+ }));
220
+ }
221
+ async close() {
222
+ this.closing = true;
223
+ await this.cancelAll();
224
+ await Promise.all([...[...this.sessions.values(), ...this.stranded].map((state) => this.release(state)), ...this.releasing]);
225
+ for (const state of this.stranded) for (const failure of state.cleanupFailures ?? []) this.cleanupFailures.add(failure);
226
+ if (this.cleanupFailures.size) throw new HarnessCleanupError([...this.cleanupFailures]);
227
+ }
228
+ };
229
+ async function abortable(work, signal) {
230
+ let rejectAbort = () => {};
231
+ const interrupted = new Promise((_, reject) => {
232
+ rejectAbort = () => reject(new HarnessAdmissionError("Harness turn cancelled."));
233
+ signal.addEventListener("abort", rejectAbort, { once: true });
234
+ if (signal.aborted) rejectAbort();
235
+ });
236
+ try {
237
+ return await Promise.race([work, interrupted]);
238
+ } finally {
239
+ signal.removeEventListener("abort", rejectAbort);
240
+ }
241
+ }
242
+ // src/lib/local-harness/host/server.ts
243
+ function authorized(header, token) {
244
+ const supplied = Buffer.from(header?.startsWith("Bearer ") ? header.slice(7) : "");
245
+ const expected = Buffer.from(token);
246
+ return supplied.length === expected.length && timingSafeEqual(supplied, expected);
247
+ }
248
+ function record$1(value) {
249
+ return value !== null && typeof value === "object" && !Array.isArray(value);
250
+ }
251
+ async function readRequest(request) {
252
+ const chunks = [];
253
+ let bytes = 0;
254
+ for await (const chunk of request) {
255
+ bytes += Buffer.byteLength(chunk);
256
+ if (bytes > HARNESS_LIMITS.maxControlReplyBytes) throw new HarnessAdmissionError("Request exceeds the harness input limit.");
257
+ chunks.push(Buffer.from(chunk));
258
+ }
259
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
260
+ }
261
+ function send(response, status, value) {
262
+ if (response.destroyed || response.writableEnded) return;
263
+ response.writeHead(status, { "content-type": "application/json" });
264
+ response.end(JSON.stringify(value));
265
+ }
266
+ async function createHarnessHost(options) {
267
+ const sessions = new HarnessSessions(options.backend, options.config);
268
+ let captureFactory;
269
+ let accepting = false;
270
+ let stopping = false;
271
+ const replies = /* @__PURE__ */ new Map();
272
+ const active = /* @__PURE__ */ new Set();
273
+ const work = /* @__PURE__ */ new Set();
274
+ const handle = async (request, response) => {
275
+ if (!(request.method === "GET" && request.url === "/.well-known/agent-card.json") && !authorized(request.headers.authorization, options.token)) {
276
+ send(response, 401, { error: "Unauthorized" });
277
+ return;
278
+ }
279
+ if (request.method === "GET" && request.url === "/status") {
280
+ send(response, 200, {
281
+ accepting,
282
+ stopping,
283
+ sessions: sessions.count,
284
+ activeTurns: active.size,
285
+ delivery: captureFactory?.status?.() ?? null
286
+ });
287
+ return;
288
+ }
289
+ if (request.method === "POST" && request.url === "/disconnect") {
290
+ send(response, 200, { stopping: true });
291
+ options.onDisconnect?.();
292
+ return;
293
+ }
294
+ if (request.method === "GET") {
295
+ send(response, 200, {
296
+ name: "Runtype local harness",
297
+ url,
298
+ version: "1.0.0",
299
+ protocolVersion: "0.3.0",
300
+ capabilities: { streaming: false },
301
+ defaultInputModes: ["text"],
302
+ defaultOutputModes: ["text"],
303
+ skills: [{
304
+ id: "chat",
305
+ name: "Chat",
306
+ description: "Run one harness turn."
307
+ }]
308
+ });
309
+ return;
310
+ }
311
+ if (request.method !== "POST" || request.url !== "/") {
312
+ send(response, 405, { error: "Method not allowed" });
313
+ return;
314
+ }
315
+ if (!accepting || !captureFactory || stopping) {
316
+ send(response, 503, { error: "Harness is not accepting turns." });
317
+ return;
318
+ }
319
+ let rpc;
320
+ try {
321
+ rpc = await readRequest(request);
322
+ } catch {
323
+ send(response, 400, { error: "Invalid or oversized request." });
324
+ return;
325
+ }
326
+ if (!record$1(rpc) || rpc.method !== "message/send" && rpc.method !== "SendMessage" || !record$1(rpc.params) || !record$1(rpc.params.message)) {
327
+ send(response, 400, { error: "Expected A2A message/send." });
328
+ return;
329
+ }
330
+ const metadata = record$1(rpc.params.metadata) ? rpc.params.metadata : {};
331
+ const binding = harnessTurnBindingSchema.safeParse(metadata[HARNESS_BINDING_METADATA_KEY]);
332
+ if (!binding.success || binding.data.expiresAt <= Date.now()) {
333
+ send(response, 403, { error: "Missing, invalid or expired harness binding." });
334
+ return;
335
+ }
336
+ const message = rpc.params.message;
337
+ if (typeof message.contextId !== "string" || !message.contextId || message.contextId.length > 200 || !Array.isArray(message.parts)) {
338
+ send(response, 400, { error: "A bounded context ID and message parts are required." });
339
+ return;
340
+ }
341
+ const prompt = message.parts.map((part) => record$1(part) && (part.kind === "text" || part.type === "text") && typeof part.text === "string" ? part.text : "").join("");
342
+ const contextId = message.contextId;
343
+ const fingerprint = createHash("sha256").update(JSON.stringify([
344
+ contextId,
345
+ prompt,
346
+ binding.data.token
347
+ ])).digest("hex");
348
+ const previous = replies.get(binding.data.traceId);
349
+ if (previous) {
350
+ if (previous.fingerprint !== fingerprint) {
351
+ send(response, 409, { error: "Harness binding was reused with a different request." });
352
+ return;
353
+ }
354
+ if (previous.body) {
355
+ send(response, previous.status ?? 200, {
356
+ ...previous.body,
357
+ ...previous.body.jsonrpc ? { id: rpc.id ?? null } : {}
358
+ });
359
+ return;
360
+ }
361
+ send(response, 409, { error: "This harness turn is already running or its completion is unknown." });
362
+ return;
363
+ }
364
+ for (const [id, reply] of replies) if (reply.expiresAt <= Date.now()) replies.delete(id);
365
+ if (replies.size >= Math.floor(HARNESS_LIMITS.maxSpoolBytes / HARNESS_LIMITS.maxControlReplyBytes)) {
366
+ send(response, 503, { error: "Harness replay capacity reached; wait for prior bindings to expire." });
367
+ return;
368
+ }
369
+ const ledger = {
370
+ fingerprint,
371
+ expiresAt: binding.data.expiresAt
372
+ };
373
+ replies.set(binding.data.traceId, ledger);
374
+ const finishResponse = (status, body) => {
375
+ ledger.status = status;
376
+ ledger.body = body;
377
+ send(response, status, body);
378
+ };
379
+ const turnId = randomUUID();
380
+ const controller = new AbortController();
381
+ active.add(controller);
382
+ const supplied = metadata.runtypeTurnDeadlineAt;
383
+ const deadlineAt = Math.min(Date.now() + options.config.timeoutMs, binding.data.expiresAt, typeof supplied === "number" && Number.isFinite(supplied) ? supplied : Infinity);
384
+ const timer = setTimeout(() => controller.abort(), Math.max(0, deadlineAt - Date.now()));
385
+ const disconnected = () => {
386
+ if (!response.writableEnded) controller.abort();
387
+ };
388
+ response.once("close", disconnected);
389
+ let capture;
390
+ try {
391
+ capture = captureFactory({
392
+ binding: binding.data,
393
+ sessionId: contextId,
394
+ turnId,
395
+ provenance: options.provenance,
396
+ dataPolicy: options.config.dataPolicy
397
+ });
398
+ if (capture.prepare) await abortable(capture.prepare(), controller.signal);
399
+ } catch {
400
+ replies.delete(binding.data.traceId);
401
+ clearTimeout(timer);
402
+ active.delete(controller);
403
+ response.off("close", disconnected);
404
+ finishResponse(503, { error: "Harness capture is unavailable." });
405
+ return;
406
+ }
407
+ if (options.replayDirectory) try {
408
+ if (!await reserveHarnessTurn(options.replayDirectory, binding.data.traceId, binding.data.expiresAt)) {
409
+ finishResponse(409, { error: "This harness turn was already admitted; completion is unknown after restart." });
410
+ return;
411
+ }
412
+ } catch {
413
+ finishResponse(503, { error: "Harness replay state is unavailable or at capacity." });
414
+ return;
415
+ } finally {
416
+ if (ledger.body) {
417
+ clearTimeout(timer);
418
+ active.delete(controller);
419
+ response.off("close", disconnected);
420
+ }
421
+ }
422
+ let result;
423
+ try {
424
+ capture.observe({
425
+ kind: "message",
426
+ message: {
427
+ role: "user",
428
+ content: prompt
429
+ }
430
+ });
431
+ result = await sessions.prompt(contextId, {
432
+ turnId,
433
+ prompt,
434
+ deadlineAt,
435
+ signal: controller.signal,
436
+ emit: (observation) => capture.observe(observation)
437
+ });
438
+ } catch (error) {
439
+ result = {
440
+ text: error instanceof HarnessAdmissionError ? error.message : controller.signal.aborted ? "Harness turn cancelled." : "Harness turn failed.",
441
+ status: controller.signal.aborted ? "cancelled" : "failed"
442
+ };
443
+ }
444
+ const redactedText = redactHarnessValue(result.text, "full", [
445
+ options.token,
446
+ ...options.secrets ?? [],
447
+ ...options.config.mcpServers.flatMap((server) => Object.values(server.headers ?? {}))
448
+ ]);
449
+ result = {
450
+ ...result,
451
+ text: String(redactedText.value ?? "")
452
+ };
453
+ if (redactedText.changed || result.text.length > 16e3) result.coverage = {
454
+ ...result.coverage,
455
+ controlResponse: {
456
+ state: result.text.length > 16e3 ? "truncated" : "redacted",
457
+ reason: result.text.length > 16e3 ? "Final response exceeded the control text limit." : "Credentials were removed from the response."
458
+ }
459
+ };
460
+ let captured;
461
+ let deliveryTimedOut = false;
462
+ try {
463
+ let finishTimer;
464
+ try {
465
+ captured = await Promise.race([capture.finish(result), new Promise((_, reject) => {
466
+ finishTimer = setTimeout(() => {
467
+ deliveryTimedOut = true;
468
+ reject(/* @__PURE__ */ new Error("Harness evidence delivery timed out."));
469
+ }, HARNESS_LIMITS.deliveryTimeoutMs);
470
+ })]);
471
+ } finally {
472
+ if (finishTimer) clearTimeout(finishTimer);
473
+ }
474
+ } catch {
475
+ result = {
476
+ ...result,
477
+ status: "failed",
478
+ text: deliveryTimedOut ? "Harness evidence delivery timed out." : "Harness evidence delivery failed."
479
+ };
480
+ }
481
+ clearTimeout(timer);
482
+ response.off("close", disconnected);
483
+ active.delete(controller);
484
+ const body = {
485
+ jsonrpc: "2.0",
486
+ id: rpc.id ?? null,
487
+ result: {
488
+ kind: "task",
489
+ id: turnId,
490
+ contextId,
491
+ status: {
492
+ state: result.status === "completed" ? "completed" : result.status === "cancelled" ? "canceled" : "failed",
493
+ ...result.status !== "completed" ? { message: {
494
+ role: "agent",
495
+ parts: [{
496
+ kind: "text",
497
+ text: Array.from(result.text).slice(0, 1024).join("")
498
+ }]
499
+ } } : {}
500
+ },
501
+ artifacts: [{
502
+ artifactId: "response",
503
+ name: "response",
504
+ parts: [{
505
+ kind: "text",
506
+ text: Array.from(result.text).slice(0, 16e3).join("")
507
+ }]
508
+ }, ...captured ? [{
509
+ artifactId: "harness",
510
+ name: HARNESS_TRACE_ARTIFACT,
511
+ parts: [{
512
+ kind: "data",
513
+ data: captured.summary
514
+ }]
515
+ }, {
516
+ artifactId: "trace",
517
+ name: "runtype.trace",
518
+ parts: [{
519
+ kind: "data",
520
+ data: {
521
+ toolCalls: captured.toolCalls,
522
+ reportedUsage: captured.reportedUsage
523
+ }
524
+ }]
525
+ }] : []]
526
+ }
527
+ };
528
+ if (Buffer.byteLength(JSON.stringify(body)) > HARNESS_LIMITS.maxControlReplyBytes) {
529
+ body.result.artifacts = body.result.artifacts.filter((artifact) => artifact.name !== "runtype.trace");
530
+ if (captured) captured.summary.coverage.controlProjection = {
531
+ state: "truncated",
532
+ reason: "Legacy tool projection exceeded the control reply limit; canonical trace retained."
533
+ };
534
+ }
535
+ if (Buffer.byteLength(JSON.stringify(body)) > HARNESS_LIMITS.maxControlReplyBytes) {
536
+ finishResponse(502, { error: "Harness reply exceeded its bounded projection." });
537
+ return;
538
+ }
539
+ finishResponse(200, body);
540
+ };
541
+ const server = createServer((request, response) => {
542
+ const pending = handle(request, response).catch(() => send(response, 500, { error: "Harness request failed." }));
543
+ work.add(pending);
544
+ pending.then(() => work.delete(pending), () => work.delete(pending));
545
+ });
546
+ server.requestTimeout = 1e4;
547
+ server.headersTimeout = 5e3;
548
+ await new Promise((resolve, reject) => {
549
+ server.once("error", reject);
550
+ server.listen(options.port ?? 0, "127.0.0.1", resolve);
551
+ });
552
+ const address = server.address();
553
+ if (!address || typeof address === "string") throw new Error("Harness could not bind a loopback port.");
554
+ const url = `http://127.0.0.1:${address.port}/`;
555
+ return {
556
+ url,
557
+ activate(factory) {
558
+ captureFactory = factory;
559
+ accepting = true;
560
+ },
561
+ async suspend() {
562
+ accepting = false;
563
+ for (const controller of active) controller.abort();
564
+ await sessions.cancelAll();
565
+ },
566
+ async close() {
567
+ stopping = true;
568
+ accepting = false;
569
+ for (const controller of active) controller.abort();
570
+ const closed = new Promise((resolve) => server.close(() => resolve()));
571
+ const failures = [];
572
+ try {
573
+ await sessions.close();
574
+ } catch (error) {
575
+ failures.push(...error instanceof HarnessCleanupError && error.failures.length ? error.failures : ["session close failed"]);
576
+ } finally {
577
+ const deliveryFailure = await cleanupFailure("request delivery", Promise.all([...work]), HARNESS_LIMITS.deliveryTimeoutMs);
578
+ if (deliveryFailure) failures.push(deliveryFailure);
579
+ server.closeAllConnections();
580
+ const serverFailure = await cleanupFailure("HTTP close", closed, HARNESS_LIMITS.cancelGraceMs);
581
+ if (serverFailure) failures.push(serverFailure);
582
+ }
583
+ if (failures.length) throw new HarnessCleanupError(failures);
584
+ }
585
+ };
586
+ }
587
+ // src/lib/local-harness/lifecycle/config.ts
588
+ function record(value) {
589
+ return value !== null && typeof value === "object" && !Array.isArray(value);
590
+ }
591
+ function parseMcpServers(value) {
592
+ if (!record(value) || !record(value.mcpServers)) throw new Error("MCP config requires an mcpServers object.");
593
+ const entries = Object.entries(value.mcpServers);
594
+ if (!entries.length || entries.length > 32) throw new Error("Configure between 1 and 32 MCP servers.");
595
+ return entries.map(([name, server]) => {
596
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(name) || !record(server) || typeof server.url !== "string") throw new Error("Each MCP server needs a valid name and HTTP URL.");
597
+ if (server.command !== void 0 || server.type !== void 0 && server.type !== "http") throw new Error("The controlled profile supports HTTP MCP servers only.");
598
+ const url = new URL(server.url);
599
+ const loopback = [
600
+ "localhost",
601
+ "127.0.0.1",
602
+ "[::1]"
603
+ ].includes(url.hostname);
604
+ if (url.username || url.password || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) throw new Error("MCP URLs require HTTPS or loopback HTTP, without URL credentials or fragments.");
605
+ let headers;
606
+ if (server.headers !== void 0) {
607
+ if (!record(server.headers) || Object.keys(server.headers).length > 16 || Object.values(server.headers).some((v) => typeof v !== "string" || v.length > 8192 || /[\r\n]/.test(v))) throw new Error("MCP headers must be bounded strings without newlines.");
608
+ headers = server.headers;
609
+ }
610
+ return {
611
+ name,
612
+ url: url.toString(),
613
+ ...headers ? { headers } : {}
614
+ };
615
+ });
616
+ }
617
+ async function readMcpConfiguration(file) {
618
+ const source = await readFile(file);
619
+ if (source.length > 131072) throw new Error("MCP configuration exceeds 128 KiB.");
620
+ return parseMcpServers(JSON.parse(source.toString("utf8")));
621
+ }
622
+ function validateConfiguration(config) {
623
+ if (!["claude", "codex"].includes(config.harness) || !["native", "acp"].includes(config.backend)) throw new Error("Unsupported harness or backend.");
624
+ if (config.profile !== "controlled-mcp") throw new Error("The workspace profile is not qualified for this release.");
625
+ if (![
626
+ "metrics",
627
+ "redacted",
628
+ "full"
629
+ ].includes(config.dataPolicy)) throw new Error("Unsupported data policy.");
630
+ if (!Number.isInteger(config.maxTurns) || config.maxTurns < 1 || config.maxTurns > 100) throw new Error("maxTurns must be between 1 and 100.");
631
+ if (!Number.isInteger(config.timeoutMs) || config.timeoutMs < 1e3 || config.timeoutMs > HARNESS_LIMITS.maxTurnTimeoutMs) throw new Error("timeoutMs must be between 1000 and 300000.");
632
+ parseMcpServers({ mcpServers: Object.fromEntries(config.mcpServers.map(({ name, ...server }) => [name, server])) });
633
+ if (new Set(config.mcpServers.map((server) => server.name)).size !== config.mcpServers.length) throw new Error("MCP names must be unique.");
634
+ }
635
+ // src/lib/local-harness/lifecycle/registration.ts
636
+ const marker = (registration) => `[runtype-harness:${registration.id}]`;
637
+ const targetName = (registration) => `${registration.name.slice(0, 150)} ${marker(registration)}`;
638
+ const segment = encodeURIComponent;
639
+ function matchesAgent(agent, registration) {
640
+ return agent.description === marker(registration) && agent.agentType === "external" && agent.externalConfig?.transport === "device" && agent.externalConfig.protocol === "a2a" && agent.externalConfig.deviceId === registration.deviceId;
641
+ }
642
+ async function allOwned(client, path) {
643
+ const items = [];
644
+ let cursor;
645
+ for (let page = 0; page < 100; page += 1) {
646
+ const reply = await client.get(path, {
647
+ limit: 100,
648
+ offset: page * 100,
649
+ ...cursor ? { cursor } : {}
650
+ });
651
+ if (!Array.isArray(reply.data)) throw new Error("Cannot reconcile an invalid resource list.");
652
+ items.push(...reply.data);
653
+ cursor = reply.pagination?.nextCursor ?? void 0;
654
+ if (!cursor && (reply.pagination?.hasMore === false || reply.data.length < 100 || items.length >= (reply.total ?? reply.pagination?.total ?? Infinity))) return items;
655
+ }
656
+ throw new Error("Resource reconciliation exceeded its page limit; registration was preserved.");
657
+ }
658
+ function notFound(error) {
659
+ return error !== null && typeof error === "object" && (error.statusCode === 404 || error.status === 404);
660
+ }
661
+ async function ownedOrMissing(read) {
662
+ try {
663
+ return await read();
664
+ } catch (error) {
665
+ if (notFound(error)) return void 0;
666
+ throw error;
667
+ }
668
+ }
669
+ async function findAgent(client, registration) {
670
+ const candidates = (await allOwned(client, "/agents")).filter((agent) => agent.description === marker(registration) && agent.agentType === "external");
671
+ return single((await Promise.all(candidates.map((candidate) => ownedOrMissing(() => client.agents.get(candidate.id))))).filter((agent) => agent !== void 0 && matchesAgent(agent, registration)));
672
+ }
673
+ async function findTarget(client, registration) {
674
+ return single((await allOwned(client, "/simulations/targets")).filter((target) => target.name === targetName(registration) && target.config.kind === "external_agent" && target.config.agentId === registration.agentId));
675
+ }
676
+ /** Recovers IDs whose creation succeeded but whose reply was lost, so removal deletes those resources too. */
677
+ async function reconcileForRemoval(client, store, registration) {
678
+ if (registration.deviceId && !registration.agentId) {
679
+ const agent = await findAgent(client, registration);
680
+ if (agent) {
681
+ registration.agentId = agent.id;
682
+ await store.save(registration);
683
+ }
684
+ }
685
+ if (registration.agentId && !registration.targetId) {
686
+ const target = await findTarget(client, registration);
687
+ if (target) {
688
+ registration.targetId = target.id;
689
+ await store.save(registration);
690
+ }
691
+ }
692
+ }
693
+ function single(matches) {
694
+ if (matches.length > 1) throw new Error("Multiple resources match this registration; reconcile them before retrying.");
695
+ return matches[0];
696
+ }
697
+ async function registerHarness(client, store, registration) {
698
+ if (!registration.deviceId || !registration.endpoint) throw new Error("Device and loopback host must be ready before registration.");
699
+ if (registration.phase === "removed") throw new Error("This registration was removed; create a new one.");
700
+ const externalConfig = {
701
+ protocol: "a2a",
702
+ transport: "device",
703
+ deviceId: registration.deviceId,
704
+ endpoint: registration.endpoint,
705
+ auth: {
706
+ type: "bearer",
707
+ credentials: registration.token
708
+ },
709
+ timeoutMs: registration.config.timeoutMs
710
+ };
711
+ if (!registration.agentId) {
712
+ const found = await findAgent(client, registration);
713
+ if (found) registration.agentId = found.id;
714
+ else {
715
+ registration.phase = "agent-pending";
716
+ await store.save(registration);
717
+ const agent = await client.agents.create({
718
+ name: registration.name,
719
+ description: marker(registration),
720
+ agentType: "external",
721
+ externalConfig
722
+ });
723
+ if (!agent.id) throw new Error("Agent creation returned no ID; retry to reconcile the registration.");
724
+ registration.agentId = agent.id;
725
+ }
726
+ registration.phase = "agent";
727
+ await store.save(registration);
728
+ }
729
+ const agent = await client.agents.get(registration.agentId);
730
+ if (!matchesAgent(agent, registration)) throw new Error("The registered agent changed protocol, transport, device or registration identity.");
731
+ await client.agents.update(agent.id, { externalConfig });
732
+ if (!registration.targetId) {
733
+ const found = await findTarget(client, registration);
734
+ if (found) registration.targetId = found.id;
735
+ else {
736
+ registration.phase = "target-pending";
737
+ await store.save(registration);
738
+ const created = await client.post("/simulations/targets", {
739
+ name: targetName(registration),
740
+ config: {
741
+ kind: "external_agent",
742
+ agentId: registration.agentId
743
+ }
744
+ });
745
+ if (!created.target?.id) throw new Error("Target creation returned no ID; retry to reconcile the registration.");
746
+ registration.targetId = created.target.id;
747
+ }
748
+ }
749
+ const target = await client.get(`/simulations/targets/${segment(registration.targetId)}`);
750
+ if (target.target.config.kind !== "external_agent" || target.target.config.agentId !== registration.agentId || target.target.name !== targetName(registration)) throw new Error("The registered target changed; refusing to overwrite it.");
751
+ registration.phase = "registered";
752
+ await store.save(registration);
753
+ }
754
+ async function removeHarness(client, store, registration) {
755
+ await reconcileForRemoval(client, store, registration);
756
+ if (registration.targetId) {
757
+ const path = `/simulations/targets/${segment(registration.targetId)}`;
758
+ const target = await ownedOrMissing(() => client.get(path));
759
+ if (target) {
760
+ if (target.target.name !== targetName(registration) || target.target.config.agentId !== registration.agentId || target.target.config.kind !== "external_agent") throw new Error("The target is no longer owned by this registration.");
761
+ await ownedOrMissing(() => client.delete(path));
762
+ }
763
+ delete registration.targetId;
764
+ await store.save(registration);
765
+ }
766
+ if (registration.agentId) {
767
+ const agentId = registration.agentId;
768
+ const agent = await ownedOrMissing(() => client.agents.get(agentId));
769
+ if (agent) {
770
+ if (!matchesAgent(agent, registration)) throw new Error("The agent is no longer owned by this registration.");
771
+ await ownedOrMissing(() => client.agents.delete(agentId));
772
+ }
773
+ delete registration.agentId;
774
+ await store.save(registration);
775
+ }
776
+ if (registration.deviceId) {
777
+ const deviceId = registration.deviceId;
778
+ await ownedOrMissing(() => client.post(`/devices/${segment(deviceId)}/revoke`, {}));
779
+ delete registration.deviceId;
780
+ }
781
+ registration.phase = "removed";
782
+ delete registration.endpoint;
783
+ await store.save(registration);
784
+ }
785
+ // src/lib/local-harness/lifecycle/store.ts
786
+ function newRegistration(workspace, config, name) {
787
+ return {
788
+ version: 1,
789
+ id: randomUUID(),
790
+ workspace: {
791
+ ...workspace,
792
+ apiUrl: normalizeApiUrl(workspace.apiUrl)
793
+ },
794
+ name,
795
+ token: randomBytes(32).toString("base64url"),
796
+ config,
797
+ phase: "prepared",
798
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
799
+ };
800
+ }
801
+ var HarnessRegistrationStore = class {
802
+ workspace;
803
+ directory;
804
+ constructor(workspace, root = getRuntypeHomeDir()) {
805
+ this.workspace = workspace;
806
+ if (!workspace.userId || !workspace.orgId) throw new Error("An authenticated organization and device owner are required.");
807
+ const scope = createHash("sha256").update(JSON.stringify([
808
+ normalizeApiUrl(workspace.apiUrl),
809
+ workspace.userId,
810
+ workspace.orgId
811
+ ])).digest("hex");
812
+ this.directory = join(root, "harness", scope);
813
+ }
814
+ path(id) {
815
+ if (!/^[a-f0-9-]{36}$/.test(id)) throw new Error("Invalid harness registration ID.");
816
+ return join(this.directory, `${id}.json`);
817
+ }
818
+ async save(registration) {
819
+ this.assertWorkspace(registration);
820
+ await mkdir(this.directory, {
821
+ recursive: true,
822
+ mode: 448
823
+ });
824
+ await chmod(this.directory, 448);
825
+ const destination = this.path(registration.id);
826
+ const temporary = `${destination}.${randomUUID()}.tmp`;
827
+ const handle = await open(temporary, "wx", 384);
828
+ try {
829
+ await handle.writeFile(JSON.stringify(registration));
830
+ await handle.sync();
831
+ } finally {
832
+ await handle.close();
833
+ }
834
+ try {
835
+ await rename(temporary, destination);
836
+ } finally {
837
+ await rm(temporary, { force: true });
838
+ }
839
+ }
840
+ async load(id) {
841
+ const file = this.path(id);
842
+ const metadata = await stat(file);
843
+ if (metadata.size > 262144 || (metadata.mode & 63) !== 0) throw new Error("Harness registration must be a private bounded file.");
844
+ const value = JSON.parse(await readFile(file, "utf8"));
845
+ if (value.version !== 1 || value.id !== id || typeof value.token !== "string" || value.token.length < 32 || typeof value.name !== "string" || !value.config) throw new Error("Invalid harness registration.");
846
+ this.assertWorkspace(value);
847
+ return value;
848
+ }
849
+ assertWorkspace(registration) {
850
+ const owner = registration.workspace;
851
+ if (!owner || normalizeApiUrl(owner.apiUrl) !== normalizeApiUrl(this.workspace.apiUrl) || owner.userId !== this.workspace.userId || owner.orgId !== this.workspace.orgId) throw new Error("Harness registration belongs to a different authenticated workspace.");
852
+ }
853
+ /** Takes the registration's exclusive lock; a lock left by a dead process is recovered without racing a live one. */
854
+ async lock(id) {
855
+ await mkdir(this.directory, {
856
+ recursive: true,
857
+ mode: 448
858
+ });
859
+ const file = `${this.path(id)}.lock`;
860
+ const token = `${process.pid}\n${randomUUID()}`;
861
+ for (let attempt = 0; attempt < 3; attempt += 1) {
862
+ if (await createExclusive(file, token)) return async () => {
863
+ if (await readFile(file, "utf8").catch(() => null) === token) await unlink(file);
864
+ };
865
+ const held = await readLock(file);
866
+ if (!held) continue;
867
+ if (held.pid === null) throw new Error("Invalid harness lock; inspect the registration before removing it.");
868
+ if (processAlive(held.pid)) throw new Error("This harness registration is already running or locked.");
869
+ if (!await removeIfUnchanged(file, held.ino)) throw new Error("This harness registration is already running or locked.");
870
+ }
871
+ throw new Error("Could not lock harness registration.");
872
+ }
873
+ };
874
+ /** Publishes `content` at `file` only if nothing is there, so no reader ever sees a half-written lock. */
875
+ async function createExclusive(file, content) {
876
+ const staged = `${file}.${randomUUID()}.tmp`;
877
+ await writeFile(staged, content, {
878
+ mode: 384,
879
+ flag: "wx"
880
+ });
881
+ try {
882
+ await link(staged, file);
883
+ return true;
884
+ } catch (error) {
885
+ if (errorCode(error) === "EEXIST") return false;
886
+ throw error;
887
+ } finally {
888
+ await rm(staged, { force: true });
889
+ }
890
+ }
891
+ async function readLock(file) {
892
+ const handle = await open(file, "r").catch((error) => {
893
+ if (errorCode(error) === "ENOENT") return null;
894
+ throw error;
895
+ });
896
+ if (!handle) return null;
897
+ try {
898
+ const pid = Number((await handle.readFile("utf8")).split("\n")[0]);
899
+ return {
900
+ pid: Number.isInteger(pid) && pid > 0 ? pid : null,
901
+ ino: (await handle.stat()).ino
902
+ };
903
+ } finally {
904
+ await handle.close();
905
+ }
906
+ }
907
+ function processAlive(pid) {
908
+ try {
909
+ process.kill(pid, 0);
910
+ return true;
911
+ } catch (probe) {
912
+ return errorCode(probe) !== "ESRCH";
913
+ }
914
+ }
915
+ /** Moves the inspected stale lock aside; if another process replaced it first, puts the replacement back. */
916
+ async function removeIfUnchanged(file, ino) {
917
+ const aside = `${file}.${randomUUID()}.stale`;
918
+ try {
919
+ await rename(file, aside);
920
+ } catch (error) {
921
+ if (errorCode(error) === "ENOENT") return true;
922
+ throw error;
923
+ }
924
+ try {
925
+ if ((await stat(aside)).ino === ino) return true;
926
+ await link(aside, file).catch((error) => {
927
+ if (errorCode(error) !== "EEXIST") throw error;
928
+ });
929
+ return false;
930
+ } finally {
931
+ await rm(aside, { force: true });
932
+ }
933
+ }
934
+ function errorCode(error) {
935
+ return error instanceof Error && "code" in error ? error.code : void 0;
936
+ }
937
+ function publicRegistration(registration) {
938
+ return {
939
+ registrationId: registration.id,
940
+ name: registration.name,
941
+ phase: registration.phase,
942
+ deviceId: registration.deviceId,
943
+ agentId: registration.agentId,
944
+ targetId: registration.targetId,
945
+ harness: registration.config.harness,
946
+ backend: registration.config.backend,
947
+ profile: registration.config.profile,
948
+ dataPolicy: registration.config.dataPolicy,
949
+ coverage: registration.provenance?.coverage,
950
+ cleanupFailures: registration.cleanupFailures,
951
+ versions: registration.provenance ? {
952
+ harness: registration.provenance.harnessVersion,
953
+ sdk: registration.provenance.sdkVersion,
954
+ adapter: registration.provenance.adapterVersion,
955
+ protocol: registration.provenance.protocolVersion
956
+ } : void 0
957
+ };
958
+ }
959
+ // src/lib/local-harness/lifecycle/connect.ts
960
+ async function loadLockedRegistration(store, supplied) {
961
+ let registration;
962
+ try {
963
+ registration = await store.load(supplied.id);
964
+ } catch (error) {
965
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
966
+ registration = supplied;
967
+ await store.save(registration);
968
+ }
969
+ assertConnectable(registration);
970
+ return registration;
971
+ }
972
+ function assertConnectable(registration) {
973
+ if (registration.phase === "removed") throw new Error("This registration was removed; create a new one.");
974
+ validateConfiguration(registration.config);
975
+ }
976
+ async function connectHarness(options) {
977
+ const { store, client, backend } = options;
978
+ assertConnectable(options.registration);
979
+ try {
980
+ await client.post("/otel/v1/traces", { resourceSpans: [] }, void 0, { timeoutMs: 1e4 });
981
+ } catch (error) {
982
+ throw new Error("Telemetry readiness check failed. Use an API key with TELEMETRY:WRITE and a reachable ingest endpoint.", { cause: error });
983
+ }
984
+ const release = await store.lock(options.registration.id);
985
+ let registration;
986
+ try {
987
+ registration = await loadLockedRegistration(store, options.registration);
988
+ } catch (error) {
989
+ await release();
990
+ throw error;
991
+ }
992
+ const stop = new AbortController();
993
+ const abort = () => stop.abort();
994
+ options.signal?.addEventListener("abort", abort, { once: true });
995
+ if (options.signal?.aborted) abort();
996
+ let host;
997
+ let relay;
998
+ let failed = false;
999
+ let failure;
1000
+ const cleanupFailures = [];
1001
+ let relayPending = false;
1002
+ try {
1003
+ stop.signal.throwIfAborted();
1004
+ const doctor = await backend.doctor(registration.config);
1005
+ if (!doctor.ready) throw new Error(`Harness is not ready: ${doctor.diagnostics.join("; ")}`);
1006
+ registration.provenance = doctor.provenance;
1007
+ delete registration.cleanupFailures;
1008
+ await store.save(registration);
1009
+ host = await createHarnessHost({
1010
+ token: registration.token,
1011
+ config: registration.config,
1012
+ provenance: doctor.provenance,
1013
+ backend,
1014
+ port: options.port,
1015
+ onDisconnect: abort,
1016
+ secrets: options.secrets,
1017
+ replayDirectory: `${store.directory}/${registration.id}-replay`
1018
+ });
1019
+ registration.endpoint = host.url;
1020
+ let resolveReady = () => {};
1021
+ let rejectReady = () => {};
1022
+ const ready = new Promise((resolve, reject) => {
1023
+ resolveReady = resolve;
1024
+ rejectReady = reject;
1025
+ });
1026
+ let captureFactory;
1027
+ let online = false;
1028
+ const readyAbort = () => rejectReady(/* @__PURE__ */ new Error("Harness connection stopped before device readiness."));
1029
+ stop.signal.addEventListener("abort", readyAbort, { once: true });
1030
+ const setupTimer = setTimeout(() => {
1031
+ rejectReady(/* @__PURE__ */ new Error("Device connection timed out."));
1032
+ abort();
1033
+ }, 3e4);
1034
+ relay = (options.runListen ?? runDeviceListen)({
1035
+ client,
1036
+ apiUrl: registration.workspace.apiUrl,
1037
+ forwardTo: host.url,
1038
+ name: registration.name,
1039
+ quiet: true,
1040
+ signal: stop.signal,
1041
+ recoverRevokedIdentity: false,
1042
+ identityStore: createDeviceListenIdentityStore({
1043
+ workspace: registration.workspace,
1044
+ cwd: store.directory,
1045
+ namespace: registration.id
1046
+ }),
1047
+ installSignalHandlers: () => () => {},
1048
+ setExitCode: () => {},
1049
+ onReady: ({ deviceId }) => {
1050
+ if (registration.deviceId && registration.deviceId !== deviceId) {
1051
+ rejectReady(/* @__PURE__ */ new Error("The registered device changed."));
1052
+ abort();
1053
+ return;
1054
+ }
1055
+ resolveReady(deviceId);
1056
+ },
1057
+ onStatus: (status) => {
1058
+ online = status === "listening";
1059
+ if (online && captureFactory) host?.activate(captureFactory);
1060
+ if (status === "offline") host?.suspend();
1061
+ }
1062
+ }).then(() => {
1063
+ if (!stop.signal.aborted) {
1064
+ failed = true;
1065
+ failure = /* @__PURE__ */ new Error("Device listener stopped unexpectedly; the harness stopped.");
1066
+ }
1067
+ rejectReady(/* @__PURE__ */ new Error("Device listener stopped."));
1068
+ abort();
1069
+ }, (error) => {
1070
+ failed = true;
1071
+ if (error instanceof DeviceListenCleanupError) cleanupFailures.push("relay close failed");
1072
+ rejectReady(/* @__PURE__ */ new Error("Device listener failed or the device was revoked."));
1073
+ abort();
1074
+ });
1075
+ try {
1076
+ registration.deviceId = await ready;
1077
+ } finally {
1078
+ clearTimeout(setupTimer);
1079
+ stop.signal.removeEventListener("abort", readyAbort);
1080
+ }
1081
+ registration.phase = "device";
1082
+ await store.save(registration);
1083
+ stop.signal.throwIfAborted();
1084
+ await registerHarness(client, store, registration);
1085
+ stop.signal.throwIfAborted();
1086
+ if (!registration.agentId) throw new Error("Harness agent registration is missing.");
1087
+ captureFactory = options.createCaptureFactory({
1088
+ client,
1089
+ spoolDirectory: `${store.directory}/${registration.id}-spool`,
1090
+ agentId: registration.agentId,
1091
+ secrets: [
1092
+ registration.token,
1093
+ ...options.secrets ?? [],
1094
+ ...registration.config.mcpServers.flatMap((server) => Object.values(server.headers ?? {}))
1095
+ ]
1096
+ });
1097
+ if (online) host.activate(captureFactory);
1098
+ options.onReady?.(publicRegistration(registration), doctor.provenance);
1099
+ if (!stop.signal.aborted) await new Promise((resolve) => {
1100
+ stop.signal.addEventListener("abort", () => resolve(), { once: true });
1101
+ });
1102
+ if (failed) throw new Error("Device listener failed or the device was revoked; the harness stopped.");
1103
+ } catch (error) {
1104
+ failed = true;
1105
+ failure = error;
1106
+ } finally {
1107
+ stop.abort();
1108
+ try {
1109
+ await host?.close();
1110
+ } catch (error) {
1111
+ cleanupFailures.push(...error instanceof HarnessCleanupError && error.failures.length ? error.failures : ["session close failed"]);
1112
+ }
1113
+ if (relay) {
1114
+ const relayFailure = await cleanupFailure("relay close", relay, HARNESS_LIMITS.cancelGraceMs);
1115
+ if (relayFailure) cleanupFailures.push(relayFailure);
1116
+ relayPending = relayFailure === "relay close timed out";
1117
+ }
1118
+ failed ||= cleanupFailures.length > 0;
1119
+ if (cleanupFailures.length) registration.cleanupFailures = cleanupFailures;
1120
+ registration.phase = failed ? "failed" : "stopped";
1121
+ delete registration.endpoint;
1122
+ try {
1123
+ await store.save(registration);
1124
+ } finally {
1125
+ try {
1126
+ if (relayPending && relay) relay.then(release).catch(() => void 0);
1127
+ else await release();
1128
+ } finally {
1129
+ options.signal?.removeEventListener("abort", abort);
1130
+ }
1131
+ }
1132
+ }
1133
+ if (cleanupFailures.length) throw new HarnessCleanupError(cleanupFailures);
1134
+ if (failed) throw failure ?? /* @__PURE__ */ new Error("Harness connection failed.");
1135
+ }
1136
+ // src/lib/local-harness/lifecycle/control.ts
1137
+ async function disconnectHarness(store, registration) {
1138
+ if (registration.endpoint) try {
1139
+ await controlHarness(registration, "disconnect");
1140
+ } catch (error) {
1141
+ if ((await store.load(registration.id)).endpoint) throw error;
1142
+ }
1143
+ const deadline = Date.now() + HARNESS_LIMITS.deliveryTimeoutMs + 10 * HARNESS_LIMITS.cancelGraceMs;
1144
+ while (Date.now() < deadline) {
1145
+ const current = await store.load(registration.id);
1146
+ if (!current.endpoint && [
1147
+ "stopped",
1148
+ "failed",
1149
+ "removed"
1150
+ ].includes(current.phase)) {
1151
+ let release;
1152
+ try {
1153
+ release = await store.lock(current.id);
1154
+ } catch (error) {
1155
+ if (!(error instanceof Error && error.message.includes("already running or locked"))) throw error;
1156
+ }
1157
+ if (release) try {
1158
+ const terminal = await store.load(current.id);
1159
+ if (!terminal.endpoint) {
1160
+ if (terminal.phase === "failed") {
1161
+ if (terminal.cleanupFailures?.length) throw new HarnessCleanupError(terminal.cleanupFailures);
1162
+ throw new Error("The harness stopped after a connection failure; inspect its foreground log.");
1163
+ }
1164
+ return {
1165
+ ...publicRegistration(terminal),
1166
+ stopping: false
1167
+ };
1168
+ }
1169
+ } finally {
1170
+ await release();
1171
+ }
1172
+ }
1173
+ await setTimeout$1(25);
1174
+ }
1175
+ throw new Error("Harness shutdown is still in progress; inspect status before removing it.");
1176
+ }
1177
+ /** Uses only the registration's exact loopback origin and never follows redirects. */
1178
+ async function controlHarness(registration, action) {
1179
+ if (!registration.endpoint) throw new Error("This harness has no active local endpoint.");
1180
+ const url = new URL(registration.endpoint);
1181
+ if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || url.username || url.password || url.pathname !== "/") throw new Error("Invalid registered loopback endpoint.");
1182
+ url.pathname = `/${action}`;
1183
+ return new Promise((resolve, reject) => {
1184
+ const call = request(url, {
1185
+ method: action === "status" ? "GET" : "POST",
1186
+ headers: { Authorization: `Bearer ${registration.token}` },
1187
+ timeout: 2e3
1188
+ }, (response) => {
1189
+ let text = "";
1190
+ response.on("data", (data) => {
1191
+ text += data.toString("utf8");
1192
+ if (text.length > 16384) call.destroy(/* @__PURE__ */ new Error("Local harness status exceeded its limit."));
1193
+ });
1194
+ response.on("end", () => {
1195
+ if (response.statusCode !== 200) {
1196
+ reject(/* @__PURE__ */ new Error("Local harness control was refused."));
1197
+ return;
1198
+ }
1199
+ try {
1200
+ resolve(JSON.parse(text));
1201
+ } catch {
1202
+ reject(/* @__PURE__ */ new Error("Invalid local harness status."));
1203
+ }
1204
+ });
1205
+ response.on("error", reject);
1206
+ });
1207
+ call.on("error", () => reject(/* @__PURE__ */ new Error("The local harness is unavailable.")));
1208
+ call.on("timeout", () => call.destroy(/* @__PURE__ */ new Error("Local harness control timed out.")));
1209
+ call.end();
1210
+ });
1211
+ }
1212
+ // src/commands/harness.ts
1213
+ function common(command) {
1214
+ return command.option("--harness <name>", "claude or codex", "claude").option("--backend <backend>", "native or acp", "native").option("--profile <profile>", "Qualified permissions profile", "controlled-mcp").option("--data-policy <policy>", "metrics, redacted, or full", "redacted").option("--executable <path>", "Installed harness executable").option("--adapter <path>", "Pinned ACP adapter executable").option("--model <model>", "Model to request").option("--mcp-config <file>", "Explicit HTTP MCP configuration JSON").option("--max-turns <count>", "Maximum turns in one session", "20").option("--timeout-ms <milliseconds>", "Maximum turn duration", "300000");
1215
+ }
1216
+ async function configuration(options, requireMcp) {
1217
+ if (requireMcp && !options.mcpConfig) throw new Error("--mcp-config is required for a new controlled harness.");
1218
+ const config = {
1219
+ harness: options.harness,
1220
+ backend: options.backend,
1221
+ profile: options.profile,
1222
+ dataPolicy: options.dataPolicy,
1223
+ executable: options.executable,
1224
+ adapter: options.adapter,
1225
+ model: options.model,
1226
+ mcpServers: options.mcpConfig ? await readMcpConfiguration(options.mcpConfig) : [],
1227
+ maxTurns: Number(options.maxTurns),
1228
+ timeoutMs: Number(options.timeoutMs)
1229
+ };
1230
+ if (requireMcp) validateConfiguration(config);
1231
+ else {
1232
+ if (!["claude", "codex"].includes(config.harness) || !["native", "acp"].includes(config.backend)) throw new Error("Unsupported harness or backend.");
1233
+ if (config.profile !== "controlled-mcp") throw new Error("The workspace profile is not qualified for this release.");
1234
+ }
1235
+ return config;
1236
+ }
1237
+ function createHarnessCommand(dependencies) {
1238
+ const print = dependencies.print ?? ((value) => console.log(JSON.stringify(value, null, 2)));
1239
+ const command = new Command("harness").description("Connect a local coding harness to Runtype simulations");
1240
+ const action = (handler) => async (...args) => {
1241
+ try {
1242
+ await handler(...args);
1243
+ } catch (error) {
1244
+ print({ error: error instanceof Error ? error.message : "Harness command failed." });
1245
+ process.exitCode = 1;
1246
+ }
1247
+ };
1248
+ const authenticated = async () => {
1249
+ const key = await (dependencies.ensureAuth ?? ensureAuth)({ exitOnFailure: false });
1250
+ if (!key) throw new Error("Authentication required. Run runtype auth login.");
1251
+ const client = (dependencies.createClient ?? createCliClient)(key);
1252
+ const identity = await client.get("/auth/me");
1253
+ if (!identity.userId || typeof identity.orgId !== "string" || !identity.orgId) throw new Error("An authenticated organization and device owner are required.");
1254
+ const workspace = {
1255
+ apiUrl: (dependencies.apiUrl ?? getApiUrl)(),
1256
+ userId: identity.userId,
1257
+ orgId: identity.orgId
1258
+ };
1259
+ return {
1260
+ client,
1261
+ workspace,
1262
+ store: new HarnessRegistrationStore(workspace, (dependencies.stateRoot ?? getRuntypeHomeDir)()),
1263
+ key
1264
+ };
1265
+ };
1266
+ common(command.command("doctor").description("Check installed harness readiness without a model call")).action(action(async (options) => {
1267
+ const config = await configuration(options, false);
1268
+ const result = await dependencies.createBackend(config).doctor(config);
1269
+ print(result);
1270
+ if (!result.ready) process.exitCode = 1;
1271
+ }));
1272
+ common(command.command("connect").description("Run a registered harness in the foreground")).option("--registration <id>", "Resume an existing owned registration").option("--name <name>", "Display name", "Local coding harness").option("--port <port>", "Loopback listening port; default chooses a free port").action(action(async (options) => {
1273
+ const { client, workspace, store, key } = await authenticated();
1274
+ const registration = options.registration ? await store.load(options.registration) : newRegistration(workspace, await configuration(options, true), (options.name ?? "Local coding harness").slice(0, 120));
1275
+ const port = options.port === void 0 ? void 0 : Number(options.port);
1276
+ if (port !== void 0 && (!Number.isInteger(port) || port < 1 || port > 65535)) throw new Error("--port must be between 1 and 65535.");
1277
+ if (!options.registration) await store.save(registration);
1278
+ print({
1279
+ ...publicRegistration(registration),
1280
+ externalToolWrites: "real",
1281
+ isolation: "unverifiable",
1282
+ externalSpendCap: false
1283
+ });
1284
+ const controller = new AbortController();
1285
+ const stop = () => controller.abort();
1286
+ process.on("SIGINT", stop);
1287
+ process.on("SIGTERM", stop);
1288
+ try {
1289
+ await connectHarness({
1290
+ registration,
1291
+ store,
1292
+ client,
1293
+ backend: dependencies.createBackend(registration.config),
1294
+ createCaptureFactory: dependencies.createCaptureFactory,
1295
+ signal: controller.signal,
1296
+ port,
1297
+ secrets: [key, ...[
1298
+ process.env.ANTHROPIC_API_KEY,
1299
+ process.env.ANTHROPIC_AUTH_TOKEN,
1300
+ process.env.CLAUDE_CODE_OAUTH_TOKEN,
1301
+ process.env.OPENAI_API_KEY,
1302
+ process.env.CODEX_API_KEY
1303
+ ].filter((value) => Boolean(value))],
1304
+ onReady: (status, provenance) => print({
1305
+ ...status,
1306
+ provenance
1307
+ })
1308
+ });
1309
+ } finally {
1310
+ process.off("SIGINT", stop);
1311
+ process.off("SIGTERM", stop);
1312
+ }
1313
+ }));
1314
+ command.command("status <registration>").description("Show safe local registration and live status").option("--json", "Output JSON").action(action(async (id) => {
1315
+ const { store } = await authenticated();
1316
+ const registration = await store.load(id);
1317
+ let live;
1318
+ try {
1319
+ live = await controlHarness(registration, "status");
1320
+ } catch {
1321
+ live = { online: false };
1322
+ }
1323
+ print({
1324
+ ...publicRegistration(registration),
1325
+ live
1326
+ });
1327
+ }));
1328
+ command.command("disconnect <registration>").description("Stop the owned local host without removing registration").action(action(async (id) => {
1329
+ const { store } = await authenticated();
1330
+ print(await disconnectHarness(store, await store.load(id)));
1331
+ }));
1332
+ command.command("remove <registration>").description("Remove owned target, agent and paired device").option("--yes", "Confirm removal").action(action(async (id, options) => {
1333
+ if (!options.yes && !getInvocationOptions().yes) throw new Error("Pass --yes to remove this registration and its owned resources.");
1334
+ const { client, store } = await authenticated();
1335
+ const release = await store.lock(id);
1336
+ try {
1337
+ const registration = await store.load(id);
1338
+ await removeHarness(client, store, registration);
1339
+ print(publicRegistration(registration));
1340
+ } finally {
1341
+ await release();
1342
+ }
1343
+ }));
1344
+ return command;
1345
+ }
1346
+ export { createHarnessCommand };
1347
+