@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.
- package/README.md +56 -0
- package/bin/meego-mara-worker.mjs +9 -0
- package/dist/chunk-VUPAW5X2.js +1424 -0
- package/dist/chunk-VUPAW5X2.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +351 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/package.json +218 -0
- package/python/mira_worker_runner.py +129 -0
|
@@ -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
|