@meego-harness/mara-worker 0.8.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.
@@ -0,0 +1,1424 @@
1
+ // src/constants.ts
2
+ var MIRA_WORKER_ROLE = "coder";
3
+ var MIRA_WORKER_WORKING_TEXT = "Mira CLI is working";
4
+ var MIRA_WORKER_CONFIG_DIR = ".meego-harness/mara-worker";
5
+ var COCO_WORKER_ROLE = MIRA_WORKER_ROLE;
6
+ var COCO_WORKER_WORKING_TEXT = MIRA_WORKER_WORKING_TEXT;
7
+ var COCO_WORKER_CONFIG_DIR = MIRA_WORKER_CONFIG_DIR;
8
+
9
+ // src/config.ts
10
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
11
+ import { homedir } from "os";
12
+ import { dirname, isAbsolute, join } from "path";
13
+ import { confirm, isCancel, select, text } from "@clack/prompts";
14
+ import { renderCliPrompt } from "@meego-harness/prompt-registry";
15
+ import { resolveDefaultWorkerDeviceIdentityFile } from "@meego-harness/worker-sdk";
16
+ import { z } from "zod";
17
+ var miraWorkerConfigSchema = z.object({
18
+ serverUrl: z.string().trim().min(1, "serverUrl is required"),
19
+ email: z.string().trim().min(1, "email is required"),
20
+ workerId: z.string().trim().min(1, "workerId is required"),
21
+ capabilitySummary: z.string().trim().min(1, "capabilitySummary is required"),
22
+ defaultWorkspace: z.string().trim().min(1, "defaultWorkspace is required"),
23
+ repoMappings: z.record(z.string(), z.string()),
24
+ enabled: z.boolean().default(true),
25
+ permissionPreset: z.enum(["safe", "default", "full-access"]),
26
+ modelSelection: z.string().trim().min(1, "modelSelection is required"),
27
+ reasoningEffortDefault: z.enum([
28
+ "use-mira-default",
29
+ "use-coco-default",
30
+ "low",
31
+ "medium",
32
+ "high",
33
+ "xhigh"
34
+ ])
35
+ });
36
+ var miraWorkerStateSchema = z.object({
37
+ contexts: z.array(
38
+ z.object({
39
+ contextId: z.string(),
40
+ repo: z.string().optional(),
41
+ cwd: z.string(),
42
+ sessionId: z.string().optional(),
43
+ reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]).optional(),
44
+ planMode: z.boolean()
45
+ })
46
+ )
47
+ });
48
+ function resolveMiraWorkerStorageDir(options = {}) {
49
+ return join(options.homeDir ?? homedir(), MIRA_WORKER_CONFIG_DIR);
50
+ }
51
+ function resolveMiraWorkerConfigFile(workerId, options = {}) {
52
+ return join(resolveMiraWorkerStorageDir(options), `${workerId}.json`);
53
+ }
54
+ function resolveMiraWorkerStateFile(workerId, options = {}) {
55
+ return join(resolveMiraWorkerStorageDir(options), `${workerId}.state.json`);
56
+ }
57
+ function loadMiraWorkerConfig(workerId, options = {}) {
58
+ const file = resolveMiraWorkerConfigFile(workerId, options);
59
+ if (!existsSync(file)) {
60
+ throw new Error(`Mira CLI worker config not found for ${workerId}: ${file}`);
61
+ }
62
+ return miraWorkerConfigSchema.parse(JSON.parse(readFileSync(file, "utf8")));
63
+ }
64
+ function writeMiraWorkerConfig(config, options = {}) {
65
+ mkdirSync(resolveMiraWorkerStorageDir(options), { recursive: true });
66
+ writeFileSync(
67
+ resolveMiraWorkerConfigFile(config.workerId, options),
68
+ `${JSON.stringify(config, null, 2)}
69
+ `,
70
+ "utf8"
71
+ );
72
+ }
73
+ function loadMiraWorkerState(stateFile) {
74
+ if (!existsSync(stateFile)) {
75
+ return { contexts: [] };
76
+ }
77
+ return miraWorkerStateSchema.parse(JSON.parse(readFileSync(stateFile, "utf8")));
78
+ }
79
+ function writeMiraWorkerState(stateFile, state) {
80
+ mkdirSync(dirname(stateFile), { recursive: true });
81
+ writeFileSync(stateFile, `${JSON.stringify(state, null, 2)}
82
+ `, "utf8");
83
+ }
84
+ async function runMiraWorkerSetup(dependencies, prompter, input = {}) {
85
+ const homeDir = dependencies.homeDir;
86
+ const serverUrlPrompt = renderCliPrompt("cli.mara-worker.server-url", void 0);
87
+ const serverUrl = await resolveTextInput(input.serverUrl, prompter, {
88
+ message: serverUrlPrompt.message,
89
+ placeholder: serverUrlPrompt.placeholder,
90
+ validate: requiredString("serverUrl")
91
+ });
92
+ const emailPrompt = renderCliPrompt("cli.mara-worker.email", void 0);
93
+ const email = await resolveTextInput(input.email, prompter, {
94
+ message: emailPrompt.message,
95
+ placeholder: emailPrompt.placeholder,
96
+ validate: requiredString("email")
97
+ });
98
+ const workerIdPrompt = renderCliPrompt("cli.mara-worker.worker-id", void 0);
99
+ const workerId = await resolveTextInput(input.workerId, prompter, {
100
+ message: workerIdPrompt.message,
101
+ placeholder: workerIdPrompt.placeholder,
102
+ validate: requiredString("workerId")
103
+ });
104
+ const capabilitySummaryPrompt = renderCliPrompt("cli.mara-worker.capability-summary", void 0);
105
+ const capabilitySummary = await resolveTextInput(input.capabilitySummary, prompter, {
106
+ message: capabilitySummaryPrompt.message,
107
+ placeholder: capabilitySummaryPrompt.placeholder,
108
+ validate: requiredString("capabilitySummary")
109
+ });
110
+ const defaultWorkspacePrompt = renderCliPrompt("cli.mara-worker.default-workspace", void 0);
111
+ const defaultWorkspace = await resolveTextInput(input.defaultWorkspace, prompter, {
112
+ message: defaultWorkspacePrompt.message,
113
+ placeholder: defaultWorkspacePrompt.placeholder,
114
+ validate: validateDirectory("defaultWorkspace")
115
+ });
116
+ const permissionPresetPrompt = renderCliPrompt("cli.mara-worker.permission-preset", void 0);
117
+ const permissionPreset = await resolveSelectInput(input.permissionPreset, prompter, {
118
+ message: permissionPresetPrompt.message,
119
+ options: [
120
+ { value: "safe", label: "safe" },
121
+ { value: "default", label: "default" },
122
+ { value: "full-access", label: "full-access" }
123
+ ],
124
+ initialValue: "default"
125
+ });
126
+ const modelSelectionPrompt = renderCliPrompt("cli.mara-worker.model-selection", void 0);
127
+ const modelSelection = await resolveSelectInput(input.modelSelection, prompter, {
128
+ message: modelSelectionPrompt.message,
129
+ options: requireCliPromptOptions(
130
+ modelSelectionPrompt.options,
131
+ "cli.mara-worker.model-selection"
132
+ ),
133
+ initialValue: "use-mira-default"
134
+ });
135
+ const reasoningEffortPrompt = renderCliPrompt("cli.mara-worker.reasoning-effort", void 0);
136
+ const reasoningEffortDefault = await resolveSelectInput(input.reasoningEffortDefault, prompter, {
137
+ message: reasoningEffortPrompt.message,
138
+ options: [
139
+ { value: "use-mira-default", label: "use-mira-default" },
140
+ { value: "use-coco-default", label: "use-coco-default" },
141
+ { value: "low", label: "low" },
142
+ { value: "medium", label: "medium" },
143
+ { value: "high", label: "high" },
144
+ { value: "xhigh", label: "xhigh" }
145
+ ],
146
+ initialValue: "use-mira-default"
147
+ });
148
+ const repoMappings = input.repoMappings ? normalizeRepoMappings(input.repoMappings) : await promptRepoMappings(prompter, defaultWorkspace);
149
+ const result = miraWorkerConfigSchema.parse({
150
+ serverUrl,
151
+ email,
152
+ workerId,
153
+ capabilitySummary,
154
+ defaultWorkspace,
155
+ repoMappings,
156
+ enabled: input.enabled ?? true,
157
+ permissionPreset,
158
+ modelSelection,
159
+ reasoningEffortDefault
160
+ });
161
+ writeMiraWorkerConfig(result, { homeDir });
162
+ dependencies.logger.info(
163
+ `Configured mara worker ${result.workerId} at ${resolveMiraWorkerConfigFile(result.workerId, { homeDir })}`
164
+ );
165
+ return result;
166
+ }
167
+ function listMiraWorkerConfigs(options = {}) {
168
+ const storageDir = resolveMiraWorkerStorageDir(options);
169
+ if (!existsSync(storageDir)) {
170
+ return [];
171
+ }
172
+ return readdirSync(storageDir).filter((file) => file.endsWith(".json") && !file.endsWith(".state.json")).map((file) => {
173
+ const workerId = file.replace(/\.json$/u, "");
174
+ const config = loadMiraWorkerConfig(workerId, options);
175
+ return {
176
+ ...config,
177
+ configFile: resolveMiraWorkerConfigFile(workerId, options),
178
+ stateFile: resolveMiraWorkerStateFile(workerId, options)
179
+ };
180
+ }).sort((left, right) => left.workerId.localeCompare(right.workerId));
181
+ }
182
+ function getMiraWorkerDoctorReport(options = {}, dependencies = {}) {
183
+ return {
184
+ workers: listMiraWorkerConfigs(options).map((config) => {
185
+ const runtimeErrors = [];
186
+ try {
187
+ dependencies.assertCocoCliAvailable?.();
188
+ } catch (error) {
189
+ runtimeErrors.push(error instanceof Error ? error.message : "Mira CLI is unavailable");
190
+ }
191
+ return {
192
+ ...config,
193
+ configStatus: "ok",
194
+ runtimeStatus: runtimeErrors.length > 0 ? "error" : "ok",
195
+ errors: runtimeErrors
196
+ };
197
+ })
198
+ };
199
+ }
200
+ function setMiraWorkerEnabled(workerId, enabled, options = {}) {
201
+ const config = loadMiraWorkerConfig(workerId, options);
202
+ writeMiraWorkerConfig(
203
+ {
204
+ ...config,
205
+ enabled
206
+ },
207
+ options
208
+ );
209
+ }
210
+ function uninstallMiraWorker(workerId, options = {}) {
211
+ const configFile = resolveMiraWorkerConfigFile(workerId, options);
212
+ const stateFile = resolveMiraWorkerStateFile(workerId, options);
213
+ const config = existsSync(configFile) ? loadMiraWorkerConfig(workerId, options) : void 0;
214
+ const credentialFile = config ? resolveDefaultWorkerDeviceIdentityFile(
215
+ config.serverUrl,
216
+ config.workerId,
217
+ config.email,
218
+ options.homeDir
219
+ ) : void 0;
220
+ const removedConfig = removeFileIfPresent(configFile);
221
+ const removedState = removeFileIfPresent(stateFile);
222
+ const removedCredential = credentialFile ? removeFileIfPresent(credentialFile) : false;
223
+ return {
224
+ configFile,
225
+ stateFile,
226
+ credentialFile,
227
+ removedConfig,
228
+ removedState,
229
+ removedCredential
230
+ };
231
+ }
232
+ function createClackPrompter() {
233
+ return {
234
+ async text(params) {
235
+ const value = await text({
236
+ message: params.message,
237
+ placeholder: params.placeholder,
238
+ defaultValue: params.initialValue,
239
+ validate: params.validate ? (input) => params.validate?.(input ?? "") : void 0
240
+ });
241
+ const resolved = unwrapCanceledValue(value, "Mira CLI worker setup canceled");
242
+ return resolved;
243
+ },
244
+ async select(params) {
245
+ const value = await select({
246
+ message: params.message,
247
+ initialValue: params.initialValue,
248
+ options: params.options.map((option) => ({
249
+ value: option.value,
250
+ label: option.label
251
+ }))
252
+ });
253
+ const resolved = unwrapCanceledValue(value, "Mira CLI worker setup canceled");
254
+ return resolved;
255
+ },
256
+ async confirm(params) {
257
+ const value = await confirm({
258
+ message: params.message,
259
+ initialValue: params.initialValue
260
+ });
261
+ const resolved = unwrapCanceledValue(value, "Mira CLI worker setup canceled");
262
+ return resolved;
263
+ }
264
+ };
265
+ }
266
+ function requiredString(field) {
267
+ return (value) => value.trim() ? void 0 : `${field} is required`;
268
+ }
269
+ function validateDirectory(field) {
270
+ return (value) => {
271
+ const normalized = value.trim();
272
+ if (!normalized) {
273
+ return `${field} is required`;
274
+ }
275
+ if (!isAbsolute(normalized)) {
276
+ return `${field} must be an absolute path`;
277
+ }
278
+ if (!existsSync(normalized)) {
279
+ return `${field} does not exist`;
280
+ }
281
+ return void 0;
282
+ };
283
+ }
284
+ async function resolveTextInput(value, prompter, params) {
285
+ const resolved = value ?? await prompter.text(params);
286
+ const normalized = resolved.trim();
287
+ const validationError = params.validate?.(normalized);
288
+ if (validationError) {
289
+ throw new Error(validationError);
290
+ }
291
+ return normalized;
292
+ }
293
+ async function resolveSelectInput(value, prompter, params) {
294
+ const resolved = value ?? await prompter.select(params);
295
+ const normalized = resolved.trim();
296
+ const optionValues = params.options.map((option) => option.value);
297
+ if (!optionValues.includes(normalized)) {
298
+ throw new Error(`${params.message} must be one of: ${optionValues.join(", ")}`);
299
+ }
300
+ return normalized;
301
+ }
302
+ async function promptRepoMappings(prompter, defaultWorkspace) {
303
+ const repoMappings = {};
304
+ const addFirstPrompt = renderCliPrompt("cli.mara-worker.repo-mapping.add-first", void 0);
305
+ let addAnotherMapping = await prompter.confirm({
306
+ message: addFirstPrompt.message,
307
+ initialValue: false
308
+ });
309
+ while (addAnotherMapping) {
310
+ const repoNamePrompt = renderCliPrompt("cli.mara-worker.repo-mapping.repo-name", void 0);
311
+ const repo = await prompter.text({
312
+ message: repoNamePrompt.message,
313
+ placeholder: repoNamePrompt.placeholder,
314
+ validate: requiredString("repo")
315
+ });
316
+ const directoryPrompt = renderCliPrompt("cli.mara-worker.repo-mapping.directory", {
317
+ repo,
318
+ defaultWorkspace
319
+ });
320
+ const cwd = await prompter.text({
321
+ message: directoryPrompt.message,
322
+ placeholder: directoryPrompt.placeholder,
323
+ validate: validateDirectory("cwd")
324
+ });
325
+ repoMappings[repo.trim()] = cwd.trim();
326
+ const addAnotherPrompt = renderCliPrompt("cli.mara-worker.repo-mapping.add-another", void 0);
327
+ addAnotherMapping = await prompter.confirm({
328
+ message: addAnotherPrompt.message,
329
+ initialValue: false
330
+ });
331
+ }
332
+ return repoMappings;
333
+ }
334
+ function requireCliPromptOptions(options, key) {
335
+ if (!options?.length) {
336
+ throw new Error(`CLI prompt ${key} did not render selectable options`);
337
+ }
338
+ return options;
339
+ }
340
+ function normalizeRepoMappings(repoMappings) {
341
+ const normalized = {};
342
+ for (const [repo, cwd] of Object.entries(repoMappings)) {
343
+ const repoName = repo.trim();
344
+ const directory = cwd.trim();
345
+ const repoError = requiredString("repo")(repoName);
346
+ const directoryError = validateDirectory("cwd")(directory);
347
+ if (repoError) {
348
+ throw new Error(repoError);
349
+ }
350
+ if (directoryError) {
351
+ throw new Error(directoryError);
352
+ }
353
+ normalized[repoName] = directory;
354
+ }
355
+ return normalized;
356
+ }
357
+ function unwrapCanceledValue(value, message) {
358
+ if (isCancel(value)) {
359
+ throw new Error(message);
360
+ }
361
+ return value;
362
+ }
363
+ function removeFileIfPresent(file) {
364
+ if (!existsSync(file)) {
365
+ return false;
366
+ }
367
+ rmSync(file, { force: true });
368
+ return true;
369
+ }
370
+ var resolveCocoWorkerStorageDir = resolveMiraWorkerStorageDir;
371
+ var resolveCocoWorkerConfigFile = resolveMiraWorkerConfigFile;
372
+ var resolveCocoWorkerStateFile = resolveMiraWorkerStateFile;
373
+ var loadCocoWorkerConfig = loadMiraWorkerConfig;
374
+ var writeCocoWorkerConfig = writeMiraWorkerConfig;
375
+ var loadCocoWorkerState = loadMiraWorkerState;
376
+ var writeCocoWorkerState = writeMiraWorkerState;
377
+ var runCocoWorkerSetup = runMiraWorkerSetup;
378
+ var listCocoWorkerConfigs = listMiraWorkerConfigs;
379
+ var getCocoWorkerDoctorReport = getMiraWorkerDoctorReport;
380
+ var setCocoWorkerEnabled = setMiraWorkerEnabled;
381
+ var uninstallCocoWorker = uninstallMiraWorker;
382
+
383
+ // src/parts.ts
384
+ function stringifyTaskMessageParts(parts) {
385
+ return parts.map((part) => {
386
+ if (part.type === "text") {
387
+ return part.text;
388
+ }
389
+ if (part.type === "data") {
390
+ return [
391
+ `[data${part.mimeType ? ` mimeType=${part.mimeType}` : ""}]`,
392
+ JSON.stringify(part.data, null, 2),
393
+ "[/data]"
394
+ ].join("\n");
395
+ }
396
+ return [
397
+ "[file-ref]",
398
+ `uri: ${part.uri}`,
399
+ part.name ? `name: ${part.name}` : void 0,
400
+ part.mimeType ? `mimeType: ${part.mimeType}` : void 0,
401
+ "[/file-ref]"
402
+ ].filter(Boolean).join("\n");
403
+ }).join("\n\n").trim();
404
+ }
405
+
406
+ // src/runtime.ts
407
+ import { spawn, spawnSync } from "child_process";
408
+ import { existsSync as existsSync2 } from "fs";
409
+ import process from "process";
410
+ import { createInterface } from "readline";
411
+ import { fileURLToPath } from "url";
412
+ var MiraCliTaskExecutor = class {
413
+ miraBin;
414
+ pythonBin;
415
+ runnerScript;
416
+ spawnChild;
417
+ constructor(config = {}) {
418
+ this.miraBin = config.miraBin ?? "mira";
419
+ this.pythonBin = config.pythonBin ?? "python3";
420
+ this.runnerScript = config.runnerScript ?? resolveDefaultRunnerScript();
421
+ this.spawnChild = config.spawn ?? spawn;
422
+ }
423
+ async runTurn(request) {
424
+ if (request.abortSignal.aborted) {
425
+ throw createAbortError();
426
+ }
427
+ const child = this.spawnChild(this.pythonBin, [this.runnerScript], {
428
+ cwd: request.cwd,
429
+ ...request.env ? { env: { ...process.env, ...request.env } } : {},
430
+ stdio: ["pipe", "pipe", "pipe"]
431
+ });
432
+ return await new Promise((resolve, reject) => {
433
+ let settled = false;
434
+ let stdoutBuffer = "";
435
+ const stderrLines = [];
436
+ function settle(callback) {
437
+ if (settled) {
438
+ return;
439
+ }
440
+ settled = true;
441
+ request.abortSignal.removeEventListener("abort", onAbort);
442
+ callback();
443
+ }
444
+ function onAbort() {
445
+ killChildProcess(child);
446
+ settle(() => reject(createAbortError()));
447
+ }
448
+ request.abortSignal.addEventListener("abort", onAbort, { once: true });
449
+ child.stdout?.on("data", (chunk) => {
450
+ stdoutBuffer += chunk.toString();
451
+ });
452
+ if (child.stderr) {
453
+ const stderr = createInterface({ input: child.stderr });
454
+ stderr.on("line", (line) => {
455
+ const trimmed = line.trim();
456
+ if (!trimmed) {
457
+ return;
458
+ }
459
+ stderrLines.push(trimmed);
460
+ while (stderrLines.length > 40) {
461
+ stderrLines.shift();
462
+ }
463
+ });
464
+ }
465
+ child.once("error", (error) => {
466
+ settle(() => reject(error));
467
+ });
468
+ child.once("close", (code, signal) => {
469
+ if (request.abortSignal.aborted) {
470
+ settle(() => reject(createAbortError()));
471
+ return;
472
+ }
473
+ let response;
474
+ try {
475
+ response = parseMiraJsonResponse(stdoutBuffer);
476
+ } catch (error) {
477
+ if (code === 0) {
478
+ settle(() => reject(error instanceof Error ? error : new Error("Failed to parse Mira output")));
479
+ return;
480
+ }
481
+ }
482
+ if (code !== 0) {
483
+ settle(() => reject(new Error(buildMiraExitError({
484
+ code,
485
+ signal,
486
+ stderrLines,
487
+ stdoutText: stdoutBuffer,
488
+ response
489
+ }))));
490
+ return;
491
+ }
492
+ if (!response) {
493
+ settle(() => reject(new Error("Mira completed without a JSON response")));
494
+ return;
495
+ }
496
+ const errorMessage = readString(response.error);
497
+ if (errorMessage) {
498
+ const sessionValue = readString(response.sessionId) || readString(response.session_id);
499
+ const sessionSuffix = sessionValue ? `; sessionId=${sessionValue}` : "";
500
+ settle(() => reject(new Error(`Mira returned an error${sessionSuffix}: ${errorMessage}`)));
501
+ return;
502
+ }
503
+ const text2 = readString(response.text) || readString(response.message?.content);
504
+ if (!text2) {
505
+ settle(() => reject(new Error(`Mira completed without assistant text for context ${request.contextId}`)));
506
+ return;
507
+ }
508
+ const sessionId = readString(response.sessionId) || readString(response.session_id);
509
+ if (!sessionId) {
510
+ settle(() => reject(new Error(`Mira did not emit a session id for context ${request.contextId}`)));
511
+ return;
512
+ }
513
+ settle(() => resolve({ text: text2, sessionId }));
514
+ });
515
+ child.stdin?.end(
516
+ JSON.stringify(buildMiraPayload(request)),
517
+ "utf8"
518
+ );
519
+ });
520
+ }
521
+ };
522
+ function assertMiraCliAvailable(miraBin = "mira", run = spawnSync, pythonBin = "python3") {
523
+ const miraResult = run(miraBin, ["--help"], {
524
+ stdio: "ignore"
525
+ });
526
+ if (miraResult.error) {
527
+ throw new Error(`Unable to execute ${miraBin}: ${miraResult.error.message}`);
528
+ }
529
+ if (typeof miraResult.status === "number" && miraResult.status !== 0) {
530
+ throw new Error(
531
+ `Mira CLI preflight failed for "${miraBin} --help" with exit code ${String(miraResult.status)}`
532
+ );
533
+ }
534
+ const pythonResult = run(pythonBin, ["--version"], {
535
+ stdio: "ignore"
536
+ });
537
+ if (pythonResult.error) {
538
+ throw new Error(`Unable to execute ${pythonBin}: ${pythonResult.error.message}`);
539
+ }
540
+ if (typeof pythonResult.status === "number" && pythonResult.status !== 0) {
541
+ throw new Error(
542
+ `Python preflight failed for "${pythonBin} --version" with exit code ${String(pythonResult.status)}`
543
+ );
544
+ }
545
+ }
546
+ function buildMiraPayload(request) {
547
+ return {
548
+ cwd: request.cwd,
549
+ prompt: request.prompt,
550
+ session_id: request.sessionId,
551
+ model: request.model,
552
+ reasoning_effort: request.reasoningEffort,
553
+ permission_preset: request.permissionPreset,
554
+ disable_remote_capabilities: true
555
+ };
556
+ }
557
+ function createAbortError() {
558
+ const error = new Error("Mira run aborted");
559
+ error.name = "AbortError";
560
+ return error;
561
+ }
562
+ function isAbortError(error) {
563
+ return error instanceof Error && error.name === "AbortError" || error instanceof Error && /abort/i.test(error.message);
564
+ }
565
+ function resolveDefaultRunnerScript() {
566
+ const runnerScript = fileURLToPath(new URL("../python/mira_worker_runner.py", import.meta.url));
567
+ if (!existsSync2(runnerScript)) {
568
+ throw new Error(`Mira CLI worker runner script not found: ${runnerScript}`);
569
+ }
570
+ return runnerScript;
571
+ }
572
+ function parseMiraJsonResponse(text2) {
573
+ const trimmed = text2.trim();
574
+ if (!trimmed) {
575
+ throw new Error("Mira produced empty stdout");
576
+ }
577
+ try {
578
+ return parseMiraJsonResponseObject(JSON.parse(trimmed));
579
+ } catch {
580
+ const firstBrace = trimmed.indexOf("{");
581
+ const lastBrace = trimmed.lastIndexOf("}");
582
+ if (firstBrace < 0 || lastBrace <= firstBrace) {
583
+ throw new Error(`Mira returned invalid JSON: ${summarizeOutput(trimmed.split(/\r?\n/u))}`);
584
+ }
585
+ return parseMiraJsonResponseObject(JSON.parse(trimmed.slice(firstBrace, lastBrace + 1)));
586
+ }
587
+ }
588
+ function parseMiraJsonResponseObject(value) {
589
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
590
+ throw new Error("Mira returned a non-object JSON response");
591
+ }
592
+ return value;
593
+ }
594
+ function buildMiraExitError(params) {
595
+ const base = `Mira CLI worker runner exited with code ${params.code}${params.signal ? ` (${params.signal})` : ""}`;
596
+ const responseError = params.response && typeof params.response.error === "string" ? params.response.error : "";
597
+ if (responseError) {
598
+ return `${base}; error=${responseError}`;
599
+ }
600
+ const stderrSummary = summarizeOutput(params.stderrLines);
601
+ if (stderrSummary) {
602
+ return `${base}; stderr=${stderrSummary}`;
603
+ }
604
+ const stdoutSummary = summarizeOutput(params.stdoutText.split(/\r?\n/u));
605
+ if (stdoutSummary) {
606
+ return `${base}; stdout=${stdoutSummary}`;
607
+ }
608
+ return base;
609
+ }
610
+ function summarizeOutput(lines) {
611
+ const compact = lines.map((line) => line.trim()).filter(Boolean);
612
+ if (!compact.length) {
613
+ return "";
614
+ }
615
+ return compact.slice(-5).join(" | ");
616
+ }
617
+ function readString(value) {
618
+ return typeof value === "string" ? value.trim() : "";
619
+ }
620
+ function killChildProcess(child) {
621
+ try {
622
+ child.kill("SIGTERM");
623
+ } catch {
624
+ }
625
+ }
626
+ var CocoCliTaskExecutor = MiraCliTaskExecutor;
627
+ var assertCocoCliAvailable = assertMiraCliAvailable;
628
+
629
+ // src/bridge.ts
630
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
631
+ import { dirname as dirname2, join as join2 } from "path";
632
+ import process2 from "process";
633
+ import { parseManagerRunResult } from "@meego-harness/manager-contract";
634
+ import { renderTextLlmPrompt } from "@meego-harness/prompt-registry";
635
+ import { WorkerClientSDK } from "@meego-harness/worker-sdk";
636
+ var CocoWorkerBridge = class {
637
+ config;
638
+ executor;
639
+ logger;
640
+ client;
641
+ activeTasks = /* @__PURE__ */ new Map();
642
+ pendingCanceledTaskIds = /* @__PURE__ */ new Set();
643
+ bindings = /* @__PURE__ */ new Map();
644
+ contextQueues = /* @__PURE__ */ new Map();
645
+ stateFile;
646
+ constructor(config, dependencies) {
647
+ this.config = config;
648
+ this.executor = dependencies.executor;
649
+ this.logger = dependencies.logger;
650
+ this.client = dependencies.client ?? new WorkerClientSDK({
651
+ serverUrl: config.serverUrl,
652
+ reconnectBaseDelayMs: config.reconnectBaseDelayMs,
653
+ reconnectMaxDelayMs: config.reconnectMaxDelayMs
654
+ });
655
+ this.stateFile = config.stateFile ?? join2(process2.cwd(), `${config.workerId}.state.json`);
656
+ for (const binding of loadCocoWorkerState(this.stateFile).contexts) {
657
+ this.bindings.set(binding.contextId, binding);
658
+ }
659
+ }
660
+ async start() {
661
+ this.client.onMessage(async (context) => {
662
+ await this.handleTaskMessage(context);
663
+ });
664
+ this.client.onCancel(async ({ taskId }) => {
665
+ await this.handleTaskCancel(taskId);
666
+ });
667
+ this.client.onRoleChange(async (request) => {
668
+ if (request.type === "promote-to-manager") {
669
+ await this.client.switchRole({
670
+ workerId: this.config.workerId,
671
+ email: this.config.email,
672
+ role: "manager",
673
+ managerGrant: request.managerGrant,
674
+ capabilitySummary: this.config.capabilitySummary,
675
+ credentialDeliveryEnabled: true,
676
+ initialAvailability: "available"
677
+ });
678
+ return;
679
+ }
680
+ await this.client.switchRole({
681
+ workerId: this.config.workerId,
682
+ email: this.config.email,
683
+ role: COCO_WORKER_ROLE,
684
+ capabilitySummary: this.config.capabilitySummary,
685
+ credentialDeliveryEnabled: true,
686
+ initialAvailability: "available"
687
+ });
688
+ });
689
+ await this.client.connect();
690
+ await this.client.login({
691
+ workerId: this.config.workerId,
692
+ email: this.config.email,
693
+ role: COCO_WORKER_ROLE,
694
+ capabilitySummary: this.config.capabilitySummary,
695
+ credentialDeliveryEnabled: true,
696
+ initialAvailability: "available"
697
+ });
698
+ return this;
699
+ }
700
+ async stop() {
701
+ for (const execution of this.activeTasks.values()) {
702
+ execution.abortController.abort();
703
+ }
704
+ this.activeTasks.clear();
705
+ this.pendingCanceledTaskIds.clear();
706
+ await this.client.disconnect();
707
+ }
708
+ async handleTaskMessage({
709
+ task,
710
+ message,
711
+ history,
712
+ delivery,
713
+ credentials,
714
+ controller
715
+ }) {
716
+ const previous = this.contextQueues.get(task.contextId) ?? Promise.resolve();
717
+ const current = previous.catch(() => void 0).then(async () => {
718
+ await this.processTaskMessage({ task, message, history, delivery, credentials, controller });
719
+ });
720
+ this.contextQueues.set(task.contextId, current);
721
+ try {
722
+ await current;
723
+ } finally {
724
+ if (this.contextQueues.get(task.contextId) === current) {
725
+ this.contextQueues.delete(task.contextId);
726
+ }
727
+ }
728
+ }
729
+ async processTaskMessage({
730
+ task,
731
+ message,
732
+ credentials,
733
+ controller
734
+ }) {
735
+ if (this.activeTasks.has(task.id)) {
736
+ this.logger.warn(`Ignoring duplicate active task delivery for ${task.id}`);
737
+ return;
738
+ }
739
+ if (this.pendingCanceledTaskIds.has(task.id)) {
740
+ this.pendingCanceledTaskIds.delete(task.id);
741
+ return;
742
+ }
743
+ const execution = {
744
+ abortController: new AbortController(),
745
+ canceled: false
746
+ };
747
+ this.activeTasks.set(task.id, execution);
748
+ const requestedRepo = readOptionalStringMetadata(task.metadata, "repo");
749
+ const existingBinding = this.bindings.get(task.contextId);
750
+ let binding = existingBinding;
751
+ let createdBinding = false;
752
+ try {
753
+ if (!binding) {
754
+ binding = this.createBinding(task.contextId, task.metadata);
755
+ this.bindings.set(task.contextId, binding);
756
+ this.persistState();
757
+ createdBinding = true;
758
+ } else {
759
+ const hydratedBinding = this.hydrateBindingFromMetadata(binding, task.metadata);
760
+ if (hydratedBinding !== binding) {
761
+ binding = hydratedBinding;
762
+ this.bindings.set(task.contextId, binding);
763
+ this.persistState();
764
+ }
765
+ }
766
+ const resolvedBinding = binding;
767
+ if (!resolvedBinding) {
768
+ throw new Error(`Missing context binding for ${task.contextId}`);
769
+ }
770
+ if (!await this.trySendTaskStatusUpdate(
771
+ task.id,
772
+ "working",
773
+ async () => await controller.working({
774
+ parts: [
775
+ {
776
+ type: "text",
777
+ text: COCO_WORKER_WORKING_TEXT
778
+ }
779
+ ]
780
+ })
781
+ )) {
782
+ return;
783
+ }
784
+ const managerRunInput = readManagerRunInput(task.metadata);
785
+ const prompt = managerRunInput ? buildManagerRunPrompt(managerRunInput) : stringifyTaskMessageParts(message.parts);
786
+ const planModePreamble = renderTextLlmPrompt("worker.mara.plan-mode-preamble", void 0).text;
787
+ const finalPrompt = !managerRunInput && createdBinding && resolvedBinding.planMode ? `${planModePreamble}
788
+
789
+ ${prompt}`.trim() : prompt;
790
+ const result = await this.executor.runTurn(buildCocoTaskExecutionRequest(
791
+ task,
792
+ finalPrompt,
793
+ resolvedBinding,
794
+ this.config,
795
+ execution,
796
+ credentials?.env
797
+ ));
798
+ if (this.isTaskCanceled(task.id)) {
799
+ return;
800
+ }
801
+ if (resolvedBinding.sessionId !== result.sessionId) {
802
+ resolvedBinding.sessionId = result.sessionId;
803
+ this.bindings.set(task.contextId, resolvedBinding);
804
+ this.persistState();
805
+ }
806
+ if (managerRunInput) {
807
+ const managerRunResult = parseManagerRunResult(parseJsonObjectOrThrow(result.text));
808
+ await this.trySendTaskStatusUpdate(
809
+ task.id,
810
+ "completed",
811
+ async () => await controller.complete({
812
+ parts: [
813
+ {
814
+ type: "text",
815
+ text: managerRunResult.summary
816
+ }
817
+ ],
818
+ metadata: buildCocoMetadata(resolvedBinding, requestedRepo, {
819
+ managerRunResult
820
+ })
821
+ })
822
+ );
823
+ return;
824
+ }
825
+ const structuredProtocol = readOptionalStringMetadata(
826
+ task.metadata,
827
+ "projectNodeResultProtocol"
828
+ );
829
+ const structuredResult = structuredProtocol ? await this.resolveStructuredProjectNodeResult(
830
+ task,
831
+ resolvedBinding,
832
+ structuredProtocol,
833
+ result.text,
834
+ execution,
835
+ credentials?.env
836
+ ) : {
837
+ status: "completed",
838
+ summary: result.text
839
+ };
840
+ if (structuredResult.status === "input_required") {
841
+ await this.trySendTaskStatusUpdate(
842
+ task.id,
843
+ "input-required",
844
+ async () => await controller.inputRequired({
845
+ parts: [
846
+ {
847
+ type: "text",
848
+ text: structuredResult.question
849
+ }
850
+ ],
851
+ metadata: buildCocoMetadata(resolvedBinding, requestedRepo, {
852
+ "mira.summary": structuredResult.summary,
853
+ "coco.summary": structuredResult.summary,
854
+ ...structuredResult.reasonType ? { projectNodeReasonType: structuredResult.reasonType } : {}
855
+ })
856
+ })
857
+ );
858
+ return;
859
+ }
860
+ await this.trySendTaskStatusUpdate(
861
+ task.id,
862
+ "completed",
863
+ async () => await controller.complete({
864
+ parts: [
865
+ {
866
+ type: "text",
867
+ text: structuredResult.summary
868
+ }
869
+ ],
870
+ metadata: buildCocoMetadata(
871
+ resolvedBinding,
872
+ requestedRepo,
873
+ structuredResult.projectNodeWrites || structuredResult.fieldPatches ? {
874
+ ...structuredResult.fieldPatches ? { projectNodeFieldPatches: structuredResult.fieldPatches } : {},
875
+ projectNodeWrites: structuredResult.projectNodeWrites ?? toProjectNodeWrites(structuredResult.fieldPatches ?? [])
876
+ } : void 0
877
+ )
878
+ })
879
+ );
880
+ } catch (error) {
881
+ if (this.isTaskCanceled(task.id) || isAbortError(error)) {
882
+ return;
883
+ }
884
+ await this.trySendTaskStatusUpdate(
885
+ task.id,
886
+ "failed",
887
+ async () => await controller.fail({
888
+ parts: [
889
+ {
890
+ type: "text",
891
+ text: error instanceof Error ? error.message : "Mira run failed"
892
+ }
893
+ ]
894
+ })
895
+ );
896
+ } finally {
897
+ this.activeTasks.delete(task.id);
898
+ this.pendingCanceledTaskIds.delete(task.id);
899
+ }
900
+ }
901
+ async trySendTaskStatusUpdate(taskId, targetState, action) {
902
+ try {
903
+ await action();
904
+ return true;
905
+ } catch (error) {
906
+ if (isTerminalTaskConflictError(error)) {
907
+ this.logger.warn(
908
+ `Ignoring stale terminal task update for ${taskId} while reporting ${targetState}: ${error.message}`
909
+ );
910
+ return false;
911
+ }
912
+ if (isWorkerConnectionClosedError(error)) {
913
+ this.logger.warn(
914
+ `Skipping ${targetState} update for ${taskId} because the worker connection is already closed`
915
+ );
916
+ return false;
917
+ }
918
+ throw error;
919
+ }
920
+ }
921
+ async handleTaskCancel(taskId) {
922
+ const execution = this.activeTasks.get(taskId);
923
+ if (!execution) {
924
+ this.pendingCanceledTaskIds.add(taskId);
925
+ return;
926
+ }
927
+ execution.canceled = true;
928
+ execution.abortController.abort();
929
+ }
930
+ createBinding(contextId, metadata) {
931
+ const repo = readOptionalStringMetadata(metadata, "repo");
932
+ const explicitReasoning = readOptionalReasoningEffort(metadata, "miraReasoningEffort") ?? readOptionalReasoningEffort(metadata, "cocoReasoningEffort");
933
+ const planMode = readOptionalBooleanMetadata(metadata, "miraPlanMode") ?? readOptionalBooleanMetadata(metadata, "cocoPlanMode") ?? false;
934
+ const cwd = this.resolveCwd(repo);
935
+ return {
936
+ contextId,
937
+ repo,
938
+ cwd,
939
+ reasoningEffort: explicitReasoning ?? (isDefaultReasoningSelection(this.config.reasoningEffortDefault) ? void 0 : this.config.reasoningEffortDefault),
940
+ planMode
941
+ };
942
+ }
943
+ hydrateBindingFromMetadata(binding, metadata) {
944
+ const repo = readOptionalStringMetadata(metadata, "repo");
945
+ if (!repo || binding.repo) {
946
+ return binding;
947
+ }
948
+ return {
949
+ ...binding,
950
+ repo,
951
+ cwd: this.resolveCwd(repo)
952
+ };
953
+ }
954
+ resolveCwd(repo) {
955
+ if (!repo) {
956
+ this.ensureDirectory(this.config.defaultWorkspace, "default workspace");
957
+ return this.config.defaultWorkspace;
958
+ }
959
+ const mapped = this.config.repoMappings[repo];
960
+ if (Object.keys(this.config.repoMappings).length > 0) {
961
+ if (!mapped) {
962
+ throw new Error(`No repo mapping configured for ${repo}`);
963
+ }
964
+ this.ensureDirectory(mapped, `mapped repo directory for ${repo}`);
965
+ return mapped;
966
+ }
967
+ const inferred = join2(this.config.defaultWorkspace, repo);
968
+ this.ensureDirectory(inferred, `workspace repo directory for ${repo}`);
969
+ return inferred;
970
+ }
971
+ ensureDirectory(path, label) {
972
+ if (!existsSync3(path)) {
973
+ throw new Error(`Missing ${label}: ${path}`);
974
+ }
975
+ }
976
+ persistState() {
977
+ mkdirSync2(dirname2(this.stateFile), { recursive: true });
978
+ writeCocoWorkerState(this.stateFile, {
979
+ contexts: Array.from(this.bindings.values())
980
+ });
981
+ }
982
+ isTaskCanceled(taskId) {
983
+ return this.activeTasks.get(taskId)?.canceled ?? this.pendingCanceledTaskIds.has(taskId);
984
+ }
985
+ async resolveStructuredProjectNodeResult(task, binding, protocol, firstText, execution, env) {
986
+ const firstParsed = parseProjectNodeResult(protocol, firstText);
987
+ if (firstParsed) {
988
+ return firstParsed;
989
+ }
990
+ const retryResult = await this.executor.runTurn(buildCocoTaskExecutionRequest(
991
+ task,
992
+ buildStructuredRetryPrompt(protocol),
993
+ binding,
994
+ this.config,
995
+ execution,
996
+ env
997
+ ));
998
+ if (this.isTaskCanceled(task.id)) {
999
+ throw new Error("task canceled before structured retry completed");
1000
+ }
1001
+ if (binding.sessionId !== retryResult.sessionId) {
1002
+ binding.sessionId = retryResult.sessionId;
1003
+ this.bindings.set(task.contextId, binding);
1004
+ this.persistState();
1005
+ }
1006
+ const retryParsed = parseProjectNodeResult(protocol, retryResult.text, {
1007
+ allowStringRepair: true
1008
+ });
1009
+ if (retryParsed) {
1010
+ return retryParsed;
1011
+ }
1012
+ throw new Error("Mira returned an invalid structured result after one retry");
1013
+ }
1014
+ };
1015
+ function buildCocoMetadata(binding, requestedRepo, extraMetadata) {
1016
+ return {
1017
+ "mira.sessionId": binding.sessionId,
1018
+ "mira.cwd": binding.cwd,
1019
+ "coco.sessionId": binding.sessionId,
1020
+ "coco.cwd": binding.cwd,
1021
+ ...binding.repo ? { "mira.repo": binding.repo, "coco.repo": binding.repo } : {},
1022
+ ...requestedRepo ? { "mira.requestedRepo": requestedRepo, "coco.requestedRepo": requestedRepo } : {},
1023
+ ...binding.reasoningEffort ? {
1024
+ "mira.reasoningEffort": binding.reasoningEffort,
1025
+ "coco.reasoningEffort": binding.reasoningEffort
1026
+ } : {},
1027
+ "mira.planMode": binding.planMode,
1028
+ "coco.planMode": binding.planMode,
1029
+ ...extraMetadata ? structuredClone(extraMetadata) : {}
1030
+ };
1031
+ }
1032
+ function buildCocoTaskExecutionRequest(task, prompt, binding, config, execution, env) {
1033
+ return {
1034
+ taskId: task.id,
1035
+ contextId: task.contextId,
1036
+ cwd: binding.cwd,
1037
+ prompt,
1038
+ sessionId: binding.sessionId,
1039
+ model: ["use-mira-default", "use-coco-default"].includes(config.modelSelection) ? void 0 : config.modelSelection,
1040
+ reasoningEffort: binding.reasoningEffort,
1041
+ permissionPreset: config.permissionPreset,
1042
+ abortSignal: execution.abortController.signal,
1043
+ ...env ? { env } : {}
1044
+ };
1045
+ }
1046
+ function buildStructuredRetryPrompt(_protocol) {
1047
+ return renderTextLlmPrompt("worker.mara.structured-retry", void 0).text;
1048
+ }
1049
+ function buildManagerRunPrompt(input) {
1050
+ return renderTextLlmPrompt("worker.mara.manager-run", {
1051
+ managerRunInput: input
1052
+ }).text;
1053
+ }
1054
+ function isTerminalTaskConflictError(error) {
1055
+ return error instanceof Error && (error.message.includes("task is already terminal") || error.message.includes("cannot continue a terminal task"));
1056
+ }
1057
+ function isWorkerConnectionClosedError(error) {
1058
+ return error instanceof Error && error.message.includes("WebSocket is not connected");
1059
+ }
1060
+ function parseProjectNodeResult(protocol, text2, options = {}) {
1061
+ const parsed = parseProjectNodeResultObject(text2, options);
1062
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1063
+ return null;
1064
+ }
1065
+ if (protocol !== "v1") {
1066
+ return null;
1067
+ }
1068
+ if ("status" in parsed) {
1069
+ const status = normalizeStatus(parsed.status);
1070
+ const summary = normalizeSummary(parsed.summary);
1071
+ if (!status || !summary) {
1072
+ return null;
1073
+ }
1074
+ if (status === "completed") {
1075
+ const projectNodeWrites = normalizeProjectNodeWrites(
1076
+ parsed.projectNodeWrites
1077
+ );
1078
+ const fieldPatches = normalizeFieldPatches(parsed.fieldPatches);
1079
+ return {
1080
+ status,
1081
+ summary,
1082
+ ...projectNodeWrites && projectNodeWrites.length > 0 ? { projectNodeWrites } : {},
1083
+ ...fieldPatches && fieldPatches.length > 0 ? { fieldPatches } : {}
1084
+ };
1085
+ }
1086
+ const question = normalizeSummary(parsed.question);
1087
+ if (!question) {
1088
+ return null;
1089
+ }
1090
+ const reasonType = normalizeProjectNodeReasonType(
1091
+ parsed.reasonType
1092
+ ) ?? void 0;
1093
+ return {
1094
+ status,
1095
+ summary,
1096
+ question,
1097
+ ...reasonType ? { reasonType } : {}
1098
+ };
1099
+ }
1100
+ const choice = normalizeChoicePrefixedResult(parsed);
1101
+ if (!choice) {
1102
+ return null;
1103
+ }
1104
+ return choice;
1105
+ }
1106
+ function parseProjectNodeResultObject(text2, options = {}) {
1107
+ const trimmed = text2.trim();
1108
+ if (!trimmed) {
1109
+ return null;
1110
+ }
1111
+ const jsonText = unwrapJsonCodeFence(trimmed) ?? trimmed;
1112
+ const parsedObject = parseJsonObjectWithOptionalRepair(
1113
+ jsonText,
1114
+ options.allowStringRepair ?? false
1115
+ );
1116
+ if (parsedObject) {
1117
+ return parsedObject;
1118
+ }
1119
+ const matched = jsonText.match(/^([12])\s+(\{[\s\S]+\})$/);
1120
+ if (!matched) {
1121
+ return null;
1122
+ }
1123
+ const choiceObject = parseJsonObjectWithOptionalRepair(
1124
+ matched[2],
1125
+ options.allowStringRepair ?? false
1126
+ );
1127
+ if (!choiceObject || typeof choiceObject !== "object" || Array.isArray(choiceObject)) {
1128
+ return null;
1129
+ }
1130
+ return {
1131
+ choice: matched[1],
1132
+ ...choiceObject
1133
+ };
1134
+ }
1135
+ function unwrapJsonCodeFence(text2) {
1136
+ if (!text2.startsWith("```") || !text2.endsWith("```")) {
1137
+ return null;
1138
+ }
1139
+ const inner = text2.slice(3, -3);
1140
+ const normalizedInner = inner.startsWith("\r\n") ? inner.slice(2) : inner.startsWith("\n") ? inner.slice(1) : inner;
1141
+ const newlineIndex = normalizedInner.search(/\r?\n/u);
1142
+ if (newlineIndex < 0) {
1143
+ return null;
1144
+ }
1145
+ const header = normalizedInner.slice(0, newlineIndex).trim().toLowerCase();
1146
+ if (header && header !== "json") {
1147
+ return null;
1148
+ }
1149
+ return normalizedInner.slice(newlineIndex + 1).trim();
1150
+ }
1151
+ function parseJsonObjectWithOptionalRepair(text2, allowStringRepair) {
1152
+ try {
1153
+ return JSON.parse(text2);
1154
+ } catch {
1155
+ if (!allowStringRepair) {
1156
+ return null;
1157
+ }
1158
+ }
1159
+ const repaired = repairUnescapedJsonStringQuotes(text2);
1160
+ if (!repaired || repaired === text2) {
1161
+ return null;
1162
+ }
1163
+ try {
1164
+ return JSON.parse(repaired);
1165
+ } catch {
1166
+ return null;
1167
+ }
1168
+ }
1169
+ function parseJsonObjectOrThrow(text2) {
1170
+ const trimmed = text2.trim();
1171
+ const jsonText = unwrapJsonCodeFence(trimmed) ?? trimmed;
1172
+ try {
1173
+ const parsed = JSON.parse(jsonText);
1174
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1175
+ throw new Error("expected a JSON object");
1176
+ }
1177
+ return parsed;
1178
+ } catch (error) {
1179
+ const suffix = error instanceof Error ? `: ${error.message}` : "";
1180
+ throw new Error(`Mira returned an invalid manager run result${suffix}`);
1181
+ }
1182
+ }
1183
+ function repairUnescapedJsonStringQuotes(text2) {
1184
+ let repaired = "";
1185
+ let inString = false;
1186
+ let escaped = false;
1187
+ let changed = false;
1188
+ for (let index = 0; index < text2.length; index += 1) {
1189
+ const character = text2[index];
1190
+ if (!inString) {
1191
+ repaired += character;
1192
+ if (character === '"') {
1193
+ inString = true;
1194
+ }
1195
+ continue;
1196
+ }
1197
+ if (escaped) {
1198
+ repaired += character;
1199
+ escaped = false;
1200
+ continue;
1201
+ }
1202
+ if (character === "\\") {
1203
+ repaired += character;
1204
+ escaped = true;
1205
+ continue;
1206
+ }
1207
+ if (character === '"') {
1208
+ const nextNonWhitespace = findNextNonWhitespaceCharacter(text2, index + 1);
1209
+ if (nextNonWhitespace === void 0 || nextNonWhitespace === "," || nextNonWhitespace === "}" || nextNonWhitespace === "]" || nextNonWhitespace === ":") {
1210
+ repaired += character;
1211
+ inString = false;
1212
+ continue;
1213
+ }
1214
+ repaired += '\\"';
1215
+ changed = true;
1216
+ continue;
1217
+ }
1218
+ repaired += character;
1219
+ }
1220
+ return changed ? repaired : null;
1221
+ }
1222
+ function findNextNonWhitespaceCharacter(text2, startIndex) {
1223
+ for (let index = startIndex; index < text2.length; index += 1) {
1224
+ const character = text2[index];
1225
+ if (!/\s/u.test(character)) {
1226
+ return character;
1227
+ }
1228
+ }
1229
+ return void 0;
1230
+ }
1231
+ function normalizeStatus(value) {
1232
+ if (value === "completed" || value === "input_required") {
1233
+ return value;
1234
+ }
1235
+ return null;
1236
+ }
1237
+ function normalizeSummary(value) {
1238
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1239
+ }
1240
+ function normalizeChoicePrefixedResult(parsed) {
1241
+ const choice = parsed.choice;
1242
+ const summary = normalizeSummary(parsed.summary);
1243
+ if (!summary || choice !== "1" && choice !== "2") {
1244
+ return null;
1245
+ }
1246
+ if (choice === "1") {
1247
+ return {
1248
+ status: "completed",
1249
+ summary
1250
+ };
1251
+ }
1252
+ const question = normalizeSummary(parsed.question);
1253
+ if (!question) {
1254
+ return null;
1255
+ }
1256
+ const reasonType = normalizeProjectNodeReasonType(parsed.reasonType) ?? void 0;
1257
+ return {
1258
+ status: "input_required",
1259
+ summary,
1260
+ question,
1261
+ ...reasonType ? { reasonType } : {}
1262
+ };
1263
+ }
1264
+ function normalizeFieldPatches(value) {
1265
+ if (!Array.isArray(value)) {
1266
+ return null;
1267
+ }
1268
+ const patches = value.flatMap((item) => {
1269
+ if (!item || typeof item !== "object") {
1270
+ return [];
1271
+ }
1272
+ const record = item;
1273
+ const fieldKey = normalizeSummary(record.fieldKey);
1274
+ const patchValue = normalizeSummary(record.value);
1275
+ const reason = normalizeSummary(record.reason) ?? void 0;
1276
+ if (!fieldKey || !patchValue) {
1277
+ return [];
1278
+ }
1279
+ return [{
1280
+ fieldKey,
1281
+ value: patchValue,
1282
+ ...reason ? { reason } : {}
1283
+ }];
1284
+ });
1285
+ return patches.length > 0 ? patches : null;
1286
+ }
1287
+ function normalizeProjectNodeWrites(value) {
1288
+ if (!Array.isArray(value)) {
1289
+ return null;
1290
+ }
1291
+ const writes = [];
1292
+ for (const item of value) {
1293
+ if (!item || typeof item !== "object") {
1294
+ continue;
1295
+ }
1296
+ const record = item;
1297
+ const kind = record.kind;
1298
+ const fieldKey = normalizeSummary(record.fieldKey);
1299
+ const reason = normalizeSummary(record.reason) ?? void 0;
1300
+ if (!fieldKey) {
1301
+ continue;
1302
+ }
1303
+ if (kind === "set-field") {
1304
+ if (!("value" in record) || record.value === void 0) {
1305
+ continue;
1306
+ }
1307
+ writes.push({
1308
+ kind,
1309
+ fieldKey,
1310
+ value: record.value,
1311
+ ...reason ? { reason } : {}
1312
+ });
1313
+ continue;
1314
+ }
1315
+ if (kind === "upload-attachment") {
1316
+ const artifactId = normalizeSummary(record.artifactId);
1317
+ if (!artifactId) {
1318
+ continue;
1319
+ }
1320
+ writes.push({
1321
+ kind,
1322
+ fieldKey,
1323
+ artifactId,
1324
+ ...reason ? { reason } : {}
1325
+ });
1326
+ }
1327
+ }
1328
+ return writes.length > 0 ? writes : null;
1329
+ }
1330
+ function toProjectNodeWrites(patches) {
1331
+ return patches.map((patch) => ({
1332
+ kind: "set-field",
1333
+ fieldKey: patch.fieldKey,
1334
+ value: patch.value,
1335
+ ...patch.reason ? { reason: patch.reason } : {}
1336
+ }));
1337
+ }
1338
+ function normalizeProjectNodeReasonType(value) {
1339
+ return value === "guidance_missing" || value === "guidance_conflict" || value === "business_fact_missing" || value === "permission_missing" || value === "external_dependency" || value === "unknown" ? value : null;
1340
+ }
1341
+ function readManagerRunInput(metadata) {
1342
+ const input = metadata?.managerRunInput;
1343
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
1344
+ return null;
1345
+ }
1346
+ return structuredClone(input);
1347
+ }
1348
+ function readOptionalStringMetadata(metadata, key) {
1349
+ if (!metadata || !(key in metadata) || metadata[key] == null) {
1350
+ return void 0;
1351
+ }
1352
+ const value = metadata[key];
1353
+ if (typeof value !== "string" || value.trim().length === 0) {
1354
+ throw new Error(`${key} must be a non-empty string`);
1355
+ }
1356
+ return value.trim();
1357
+ }
1358
+ function readOptionalBooleanMetadata(metadata, key) {
1359
+ if (!metadata || !(key in metadata) || metadata[key] == null) {
1360
+ return void 0;
1361
+ }
1362
+ const value = metadata[key];
1363
+ if (typeof value !== "boolean") {
1364
+ throw new TypeError(`${key} must be a boolean`);
1365
+ }
1366
+ return value;
1367
+ }
1368
+ function readOptionalReasoningEffort(metadata, key) {
1369
+ const value = readOptionalStringMetadata(metadata, key);
1370
+ if (!value) {
1371
+ return void 0;
1372
+ }
1373
+ if (!["low", "medium", "high", "xhigh"].includes(value)) {
1374
+ throw new Error(`${key} must be one of low, medium, high, xhigh`);
1375
+ }
1376
+ return value;
1377
+ }
1378
+ function isDefaultReasoningSelection(value) {
1379
+ return value === "use-mira-default" || value === "use-coco-default";
1380
+ }
1381
+
1382
+ export {
1383
+ MIRA_WORKER_ROLE,
1384
+ MIRA_WORKER_WORKING_TEXT,
1385
+ MIRA_WORKER_CONFIG_DIR,
1386
+ COCO_WORKER_ROLE,
1387
+ COCO_WORKER_WORKING_TEXT,
1388
+ COCO_WORKER_CONFIG_DIR,
1389
+ resolveMiraWorkerStorageDir,
1390
+ resolveMiraWorkerConfigFile,
1391
+ resolveMiraWorkerStateFile,
1392
+ loadMiraWorkerConfig,
1393
+ writeMiraWorkerConfig,
1394
+ loadMiraWorkerState,
1395
+ writeMiraWorkerState,
1396
+ runMiraWorkerSetup,
1397
+ listMiraWorkerConfigs,
1398
+ getMiraWorkerDoctorReport,
1399
+ setMiraWorkerEnabled,
1400
+ uninstallMiraWorker,
1401
+ createClackPrompter,
1402
+ resolveCocoWorkerStorageDir,
1403
+ resolveCocoWorkerConfigFile,
1404
+ resolveCocoWorkerStateFile,
1405
+ loadCocoWorkerConfig,
1406
+ writeCocoWorkerConfig,
1407
+ loadCocoWorkerState,
1408
+ writeCocoWorkerState,
1409
+ runCocoWorkerSetup,
1410
+ listCocoWorkerConfigs,
1411
+ getCocoWorkerDoctorReport,
1412
+ setCocoWorkerEnabled,
1413
+ uninstallCocoWorker,
1414
+ stringifyTaskMessageParts,
1415
+ MiraCliTaskExecutor,
1416
+ assertMiraCliAvailable,
1417
+ buildMiraPayload,
1418
+ createAbortError,
1419
+ isAbortError,
1420
+ CocoCliTaskExecutor,
1421
+ assertCocoCliAvailable,
1422
+ CocoWorkerBridge
1423
+ };
1424
+ //# sourceMappingURL=chunk-VUPAW5X2.js.map