poe-code 3.0.201 → 3.0.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli/commands/experiment.js +11 -4
  2. package/dist/cli/commands/experiment.js.map +1 -1
  3. package/dist/cli/commands/ralph.js +12 -5
  4. package/dist/cli/commands/ralph.js.map +1 -1
  5. package/dist/cli/commands/runtime-options.d.ts +10 -0
  6. package/dist/cli/commands/runtime-options.js +23 -0
  7. package/dist/cli/commands/runtime-options.js.map +1 -0
  8. package/dist/cli/commands/spawn.js +9 -3
  9. package/dist/cli/commands/spawn.js.map +1 -1
  10. package/dist/index.js +22975 -20513
  11. package/dist/index.js.map +4 -4
  12. package/dist/providers/claude-code.js +2741 -1706
  13. package/dist/providers/claude-code.js.map +4 -4
  14. package/dist/providers/codex.js +2770 -1735
  15. package/dist/providers/codex.js.map +4 -4
  16. package/dist/providers/goose.js +2640 -1605
  17. package/dist/providers/goose.js.map +4 -4
  18. package/dist/providers/kimi.js +2740 -1705
  19. package/dist/providers/kimi.js.map +4 -4
  20. package/dist/providers/opencode.js +2741 -1706
  21. package/dist/providers/opencode.js.map +4 -4
  22. package/dist/providers/poe-agent.js +19779 -17296
  23. package/dist/providers/poe-agent.js.map +4 -4
  24. package/dist/providers/spawn-options.d.ts +6 -0
  25. package/dist/sdk/experiment.js +5 -0
  26. package/dist/sdk/experiment.js.map +1 -1
  27. package/dist/sdk/ralph.js +5 -0
  28. package/dist/sdk/ralph.js.map +1 -1
  29. package/dist/sdk/spawn.js +17 -1
  30. package/dist/sdk/spawn.js.map +1 -1
  31. package/dist/sdk/types.d.ts +11 -0
  32. package/package.json +1 -1
  33. package/packages/memory/dist/index.js +2642 -444
  34. package/packages/memory/dist/index.js.map +4 -4
  35. package/packages/superintendent/dist/commands/run.d.ts +35 -0
  36. package/packages/superintendent/dist/commands/run.js +49 -1
  37. package/packages/superintendent/dist/commands/superintendent-group.d.ts +30 -0
  38. package/packages/superintendent/dist/runtime/agent-runner.d.ts +30 -0
  39. package/packages/superintendent/dist/runtime/agent-runner.js +119 -0
  40. package/packages/superintendent/dist/runtime/loop.d.ts +6 -1
  41. package/packages/superintendent/dist/runtime/loop.js +3 -11
  42. package/packages/superintendent/dist/runtime/run-builder.d.ts +1 -0
  43. package/packages/superintendent/dist/runtime/run-builder.js +3 -25
  44. package/packages/superintendent/dist/runtime/run-inspector.d.ts +1 -0
  45. package/packages/superintendent/dist/runtime/run-inspector.js +3 -25
  46. package/packages/superintendent/dist/runtime/run-owner-review.d.ts +1 -0
  47. package/packages/superintendent/dist/runtime/run-owner-review.js +3 -25
  48. package/packages/superintendent/dist/runtime/run-superintendent.d.ts +1 -0
  49. package/packages/superintendent/dist/runtime/run-superintendent.js +3 -25
@@ -52,19 +52,11 @@ var require_config_toml = __commonJS({
52
52
  }
53
53
  });
54
54
 
55
- // packages/agent-spawn/src/run-command.ts
56
- import { spawn } from "node:child_process";
55
+ // packages/agent-spawn/src/register-factories.ts
56
+ import { spawn as spawnChildProcess2 } from "node:child_process";
57
57
 
58
- // packages/agent-spawn/src/types.ts
59
- function resolveModeConfig(modeConfig) {
60
- if (Array.isArray(modeConfig)) {
61
- return { args: modeConfig };
62
- }
63
- return {
64
- args: modeConfig.args ?? [],
65
- env: modeConfig.env && Object.keys(modeConfig.env).length > 0 ? modeConfig.env : void 0
66
- };
67
- }
58
+ // packages/agent-harness-tools/src/paths.ts
59
+ import path from "node:path";
68
60
 
69
61
  // packages/agent-defs/src/agents/claude-code.ts
70
62
  var claudeCodeAgent = {
@@ -205,1962 +197,3005 @@ function resolveAgentId(input) {
205
197
  return lookup.get(input.toLowerCase());
206
198
  }
207
199
 
208
- // packages/agent-spawn/src/configs/mcp.ts
209
- function toJsonMcpServers(servers) {
210
- const out = {};
211
- for (const [name, server] of Object.entries(servers)) {
212
- const mapped = { command: server.command };
213
- if (server.args && server.args.length > 0) {
214
- mapped.args = server.args;
215
- }
216
- if (server.env && Object.keys(server.env).length > 0) {
217
- mapped.env = server.env;
218
- }
219
- if (server.timeout !== void 0) {
220
- mapped.timeout = server.timeout;
200
+ // packages/agent-harness-tools/src/select-agent.ts
201
+ var loopAgents = allAgents.filter(
202
+ (agent) => agent.binaryName !== void 0 || agent.id === "poe-agent"
203
+ );
204
+ var supportedAgents = loopAgents.map((agent) => agent.id).join(", ");
205
+
206
+ // packages/file-lock/src/lock.ts
207
+ import * as fsPromises from "node:fs/promises";
208
+ import * as os from "node:os";
209
+
210
+ // packages/agent-harness-tools/src/run-logs.ts
211
+ import path2 from "node:path";
212
+
213
+ // packages/agent-harness-tools/src/log-stream.ts
214
+ import nodeFs from "node:fs";
215
+
216
+ // packages/agent-harness-tools/src/run-poe-command.ts
217
+ import { randomBytes } from "node:crypto";
218
+
219
+ // packages/agent-harness-tools/src/poe-command-execution.ts
220
+ import { existsSync as existsSync2, readFileSync } from "node:fs";
221
+ import os3 from "node:os";
222
+
223
+ // packages/poe-code-config/src/runtime.ts
224
+ import { existsSync } from "node:fs";
225
+ import path3 from "node:path";
226
+ var defaultWorkspaceExclude = [
227
+ ".git",
228
+ "node_modules",
229
+ "dist",
230
+ ".turbo",
231
+ ".next",
232
+ ".poe-code/state.json"
233
+ ];
234
+ var runtimeConfigScope = {
235
+ scope: "runtime",
236
+ schema: {
237
+ type: {
238
+ type: "string",
239
+ default: "host",
240
+ doc: "Runtime backend: host, docker, or e2b"
241
+ },
242
+ build_args: {
243
+ type: "json",
244
+ default: {},
245
+ parse: parseBuildArgs,
246
+ doc: "Build arguments passed to the runtime image build"
247
+ },
248
+ mounts: {
249
+ type: "json",
250
+ default: [],
251
+ parse: parseMounts,
252
+ doc: "Additional runtime mounts"
253
+ },
254
+ runner: {
255
+ type: "json",
256
+ default: createDefaultRunnerScope(),
257
+ parse: parseRunner,
258
+ doc: "Runner process and workspace transfer settings"
259
+ },
260
+ link: {
261
+ type: "string",
262
+ default: "",
263
+ doc: "Informational link for the runtime definition"
264
+ },
265
+ image: {
266
+ type: "string",
267
+ default: "",
268
+ doc: "Prebuilt Docker image"
269
+ },
270
+ dockerfile: {
271
+ type: "string",
272
+ default: "",
273
+ doc: "Path to the Dockerfile used for docker or e2b builds"
274
+ },
275
+ build_context: {
276
+ type: "string",
277
+ default: "",
278
+ doc: "Path to the Docker build context"
279
+ },
280
+ engine: {
281
+ type: "string",
282
+ default: "",
283
+ doc: "Container engine for Docker runtime"
284
+ },
285
+ network: {
286
+ type: "string",
287
+ default: "",
288
+ doc: "Docker network"
289
+ },
290
+ extra_args: {
291
+ type: "json",
292
+ default: void 0,
293
+ parse: parseOptionalStringArray,
294
+ doc: "Extra Docker runtime arguments"
295
+ },
296
+ template_id: {
297
+ type: "string",
298
+ default: "",
299
+ doc: "Prebuilt E2B template id"
300
+ },
301
+ cpu: {
302
+ type: "json",
303
+ default: void 0,
304
+ parse: parseOptionalNumber,
305
+ doc: "E2B CPU count"
306
+ },
307
+ memory_mb: {
308
+ type: "json",
309
+ default: void 0,
310
+ parse: parseOptionalNumber,
311
+ doc: "E2B memory in megabytes"
312
+ },
313
+ timeout_minutes: {
314
+ type: "json",
315
+ default: void 0,
316
+ parse: parseOptionalNumber,
317
+ doc: "E2B timeout in minutes"
318
+ },
319
+ preserve_after_exit_hours: {
320
+ type: "json",
321
+ default: void 0,
322
+ parse: parseOptionalNumber,
323
+ doc: "Hours to keep an E2B sandbox alive after job exit"
324
+ },
325
+ api_key_env: {
326
+ type: "string",
327
+ default: "",
328
+ doc: "Environment variable name containing the E2B API key"
221
329
  }
222
- out[name] = mapped;
223
330
  }
224
- return out;
225
- }
226
- function toTomlString(value) {
227
- return JSON.stringify(value);
331
+ };
332
+ function parseRunner(raw) {
333
+ if (raw === void 0) {
334
+ return createDefaultRunnerScope();
335
+ }
336
+ const record = asRecord(raw);
337
+ if (record === void 0) {
338
+ throw new Error("runner: expected an object.");
339
+ }
340
+ const uploadMaxFileMb = parseOptionalNumber(record.upload_max_file_mb, "runner.upload_max_file_mb") ?? 100;
341
+ if (uploadMaxFileMb <= 0) {
342
+ throw new Error("runner.upload_max_file_mb: expected a positive finite number.");
343
+ }
344
+ return omitUndefined({
345
+ detach: parseOptionalBoolean(record.detach, "runner.detach") ?? false,
346
+ upload_max_file_mb: uploadMaxFileMb,
347
+ download_conflict: parseDownloadConflict(record.download_conflict),
348
+ workspace: parseRunnerWorkspace(record.workspace)
349
+ });
228
350
  }
229
- function toTomlArray(values) {
230
- const serialized = values.map((value) => toTomlString(value));
231
- return `[${serialized.join(", ")}]`;
351
+ function createDefaultRunnerScope() {
352
+ return {
353
+ detach: false,
354
+ upload_max_file_mb: 100,
355
+ download_conflict: "refuse",
356
+ workspace: {
357
+ exclude: [...defaultWorkspaceExclude]
358
+ }
359
+ };
232
360
  }
233
- function toTomlInlineTable(values) {
234
- const parts = [];
235
- for (const [key, value] of Object.entries(values)) {
236
- parts.push(`${JSON.stringify(key)}=${toTomlString(value)}`);
361
+ function parseRunnerWorkspace(value) {
362
+ if (value === void 0) {
363
+ return {
364
+ exclude: [...defaultWorkspaceExclude]
365
+ };
237
366
  }
238
- return `{${parts.join(", ")}}`;
367
+ const record = asRecord(value);
368
+ if (record === void 0) {
369
+ throw new Error("runner.workspace: expected an object.");
370
+ }
371
+ return {
372
+ exclude: parseOptionalStringArray(record.exclude, "runner.workspace.exclude") ?? [
373
+ ...defaultWorkspaceExclude
374
+ ]
375
+ };
239
376
  }
240
- function serializeJsonMcpArgs(servers) {
241
- return ["--mcp-config", JSON.stringify({ mcpServers: toJsonMcpServers(servers) })];
377
+ function parseDownloadConflict(value) {
378
+ if (value === void 0) {
379
+ return "refuse";
380
+ }
381
+ if (value === "refuse" || value === "overwrite") {
382
+ return value;
383
+ }
384
+ throw new Error('runner.download_conflict: expected "refuse" or "overwrite".');
242
385
  }
243
- function serializeOpenCodeMcpEnv(servers) {
244
- const mcp = {};
245
- for (const [name, server] of Object.entries(servers)) {
246
- const entry = { type: "local", command: [server.command, ...server.args ?? []] };
247
- if (server.env && Object.keys(server.env).length > 0) {
248
- entry.environment = server.env;
386
+ function parseBuildArgs(value) {
387
+ if (value === void 0) {
388
+ return {};
389
+ }
390
+ const record = asRecord(value);
391
+ if (record === void 0) {
392
+ throw new Error("build_args: expected an object.");
393
+ }
394
+ const parsed = {};
395
+ for (const [key, entry] of Object.entries(record)) {
396
+ if (typeof entry !== "string") {
397
+ throw new Error(`build_args.${key}: expected a string.`);
249
398
  }
250
- mcp[name] = entry;
399
+ parsed[key] = entry;
251
400
  }
252
- return { OPENCODE_CONFIG_CONTENT: JSON.stringify({ mcp }) };
401
+ return parsed;
253
402
  }
254
- function serializeCodexMcpArgs(servers) {
255
- const args = [];
256
- for (const [name, server] of Object.entries(servers)) {
257
- const prefix = `mcp_servers.${name}`;
258
- args.push("-c", `${prefix}.command=${toTomlString(server.command)}`);
259
- if (server.args && server.args.length > 0) {
260
- args.push("-c", `${prefix}.args=${toTomlArray(server.args)}`);
403
+ function parseMounts(value) {
404
+ if (value === void 0) {
405
+ return [];
406
+ }
407
+ if (!Array.isArray(value)) {
408
+ throw new Error("mounts: expected an array.");
409
+ }
410
+ return value.map((entry, index) => {
411
+ const record = asRecord(entry);
412
+ if (record === void 0) {
413
+ throw new Error(`mounts[${index}]: expected an object.`);
261
414
  }
262
- if (server.env && Object.keys(server.env).length > 0) {
263
- args.push("-c", `${prefix}.env=${toTomlInlineTable(server.env)}`);
415
+ const source = record.source;
416
+ const target = record.target;
417
+ if (typeof source !== "string") {
418
+ throw new Error(`mounts[${index}].source: expected a string.`);
264
419
  }
265
- if (server.timeout !== void 0) {
266
- args.push("-c", `${prefix}.timeout=${server.timeout}`);
420
+ if (typeof target !== "string") {
421
+ throw new Error(`mounts[${index}].target: expected a string.`);
267
422
  }
423
+ return omitUndefined({
424
+ source,
425
+ target,
426
+ readonly: parseOptionalBoolean(record.readonly, `mounts[${index}].readonly`)
427
+ });
428
+ });
429
+ }
430
+ function parseOptionalStringArray(value, key = "") {
431
+ if (value === void 0) {
432
+ return void 0;
268
433
  }
269
- return args;
434
+ if (!Array.isArray(value)) {
435
+ throw new Error(`${key ? `${key}: ` : ""}expected an array.`);
436
+ }
437
+ return value.map((entry, index) => {
438
+ if (typeof entry !== "string") {
439
+ throw new Error(`${key}[${index}]: expected a string.`);
440
+ }
441
+ return entry;
442
+ });
270
443
  }
271
- function serializeGooseMcpArgs(servers) {
272
- return Object.values(servers).flatMap((server) => [
273
- "--with-extension",
274
- [server.command, ...server.args ?? []].join(" ")
275
- ]);
444
+ function parseOptionalNumber(value, key = "") {
445
+ if (value === void 0) {
446
+ return void 0;
447
+ }
448
+ if (typeof value !== "number" || !Number.isFinite(value)) {
449
+ throw new Error(`${key ? `${key}: ` : ""}expected a finite number.`);
450
+ }
451
+ return value;
452
+ }
453
+ function parseOptionalBoolean(value, key) {
454
+ if (value === void 0) {
455
+ return void 0;
456
+ }
457
+ if (typeof value !== "boolean") {
458
+ throw new Error(`${key}: expected a boolean.`);
459
+ }
460
+ return value;
461
+ }
462
+ function asRecord(value) {
463
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
464
+ return void 0;
465
+ }
466
+ return value;
467
+ }
468
+ function omitUndefined(value) {
469
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
276
470
  }
277
471
 
278
- // packages/agent-spawn/src/configs/claude-code.ts
279
- var claudeCodeSpawnConfig = {
280
- kind: "cli",
281
- agentId: "claude-code",
282
- // ACP adapter support: yes (adapter: "claude")
283
- adapter: "claude",
284
- promptFlag: "-p",
285
- modelFlag: "--model",
286
- modelStripProviderPrefix: true,
287
- modelTransform: (model) => model.replaceAll(".", "-"),
288
- defaultArgs: [
289
- "--output-format",
290
- "stream-json",
291
- "--verbose"
292
- ],
293
- mcpArgs: serializeJsonMcpArgs,
294
- modes: {
295
- yolo: ["--dangerously-skip-permissions"],
296
- edit: ["--permission-mode", "acceptEdits", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,NotebookEdit"],
297
- read: ["--permission-mode", "plan"]
298
- },
299
- stdinMode: {
300
- omitPrompt: true,
301
- extraArgs: ["--input-format", "text"]
302
- },
303
- interactive: {
304
- defaultArgs: []
305
- },
306
- resumeCommand: (threadId) => ["--resume", threadId]
307
- };
472
+ // packages/poe-code-config/src/schema.ts
473
+ function defineScope(scope, schema) {
474
+ return {
475
+ scope,
476
+ schema
477
+ };
478
+ }
308
479
 
309
- // packages/agent-spawn/src/configs/codex.ts
310
- var codexSpawnConfig = {
311
- kind: "cli",
312
- agentId: "codex",
313
- // ACP adapter support: yes (adapter: "codex")
314
- adapter: "codex",
315
- promptFlag: "exec",
316
- modelFlag: "--model",
317
- modelStripProviderPrefix: true,
318
- defaultArgs: ["--skip-git-repo-check", "--json"],
319
- mcpArgs: serializeCodexMcpArgs,
320
- mcpArgsBeforeCommand: true,
321
- modes: {
322
- yolo: ["-s", "danger-full-access"],
323
- edit: ["-s", "workspace-write"],
324
- read: ["-s", "read-only"]
325
- },
326
- stdinMode: {
327
- omitPrompt: true,
328
- extraArgs: ["-"]
329
- },
330
- interactive: {
331
- defaultArgs: ["-a", "never"]
332
- },
333
- resumeCommand: (threadId, cwd) => ["resume", "-C", cwd, threadId]
334
- };
480
+ // packages/poe-code-config/src/plan-scope.ts
481
+ var planConfigScope = defineScope("plan", {
482
+ plan_directory: {
483
+ type: "string",
484
+ default: "docs/plans",
485
+ env: "POE_PLAN_DIRECTORY",
486
+ doc: "Directory where planning documents are stored"
487
+ }
488
+ });
335
489
 
336
- // packages/agent-spawn/src/configs/opencode.ts
337
- var openCodeSpawnConfig = {
338
- kind: "cli",
339
- agentId: "opencode",
340
- // ACP adapter support: yes (adapter: "opencode").
341
- // OpenCode's `--format json` emits NDJSON events with `{ type, sessionID, part }`
342
- // (no `{ event, ... }` field), so it needs the OpenCode adapter (not "native").
343
- adapter: "opencode",
344
- promptFlag: "run",
345
- modelFlag: "--model",
346
- modelStripProviderPrefix: false,
347
- modelTransform: (model) => {
348
- return model.startsWith("poe/") ? model : `poe/${model}`;
349
- },
350
- defaultArgs: ["--format", "json"],
351
- modes: {
352
- yolo: [],
353
- edit: [],
354
- read: ["--agent", "plan"]
355
- },
356
- interactive: {
357
- defaultArgs: [],
358
- promptFlag: "--prompt"
359
- },
360
- resumeCommand: (threadId, cwd) => [cwd, "--session", threadId],
361
- mcpEnv: serializeOpenCodeMcpEnv
362
- };
363
- var openCodeAcpSpawnConfig = {
364
- kind: "acp",
365
- agentId: "opencode",
366
- acpArgs: ["acp"],
367
- skipAuth: true,
368
- mcpEnv: serializeOpenCodeMcpEnv
369
- };
490
+ // packages/poe-code-config/src/store.ts
491
+ import path8 from "node:path";
370
492
 
371
- // packages/agent-spawn/src/configs/kimi.ts
372
- var kimiSpawnConfig = {
373
- kind: "cli",
374
- agentId: "kimi",
375
- // ACP adapter support: yes (adapter: "kimi").
376
- // Kimi's `--output-format stream-json` emits OpenAI-style `{ role, content }` JSON
377
- // (no `{ event, ... }` field), so it needs the Kimi adapter (not "native").
378
- adapter: "kimi",
379
- promptFlag: "-p",
380
- modelStripProviderPrefix: true,
381
- defaultArgs: ["--print", "--output-format", "stream-json"],
382
- mcpArgs: serializeJsonMcpArgs,
383
- modes: {
384
- yolo: ["--yolo"],
385
- edit: [],
386
- read: []
387
- },
388
- stdinMode: {
389
- omitPrompt: true,
390
- extraArgs: ["--input-format", "stream-json"]
391
- },
392
- interactive: {
393
- defaultArgs: [],
394
- promptFlag: "-p"
395
- },
396
- resumeCommand: (threadId, cwd) => ["--session", threadId, "--work-dir", cwd]
397
- };
398
- var kimiAcpSpawnConfig = {
399
- kind: "acp",
400
- agentId: "kimi",
401
- acpArgs: ["acp"]
402
- };
493
+ // packages/config-extends/src/discover.ts
494
+ import path4 from "node:path";
403
495
 
404
- // packages/agent-spawn/src/configs/goose.ts
405
- var gooseFileSecretsEnv = { GOOSE_DISABLE_KEYRING: "1" };
406
- var gooseSpawnConfig = {
407
- kind: "cli",
408
- agentId: "goose",
409
- adapter: "native",
410
- promptFlag: "--text",
411
- modelFlag: "--model",
412
- modelStripProviderPrefix: false,
413
- defaultArgs: ["run", "--output-format", "stream-json"],
414
- defaultArgsPosition: "beforePrompt",
415
- mcpArgs: serializeGooseMcpArgs,
416
- mcpArgsPosition: "beforePrompt",
417
- modes: {
418
- yolo: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "auto" } },
419
- edit: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "smart_approve" } },
420
- read: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "chat" } }
421
- },
422
- stdinMode: {
423
- omitPrompt: true,
424
- extraArgs: ["--instructions", "-"]
425
- },
426
- interactive: {
427
- defaultArgs: ["session"],
428
- defaultArgsPosition: "beforePrompt"
429
- },
430
- resumeCommand: () => ["run", "--resume", "--text", "continue"]
431
- };
432
- var gooseAcpSpawnConfig = {
433
- kind: "acp",
434
- agentId: "goose",
435
- acpArgs: ["acp"],
436
- env: gooseFileSecretsEnv,
437
- skipAuth: true
438
- };
496
+ // packages/config-extends/src/parse.ts
497
+ import path5 from "node:path";
498
+ import matter from "gray-matter";
499
+ import { parse as parseYaml } from "yaml";
439
500
 
440
- // packages/agent-spawn/src/configs/index.ts
441
- var allSpawnConfigs = [
442
- claudeCodeSpawnConfig,
443
- codexSpawnConfig,
444
- openCodeSpawnConfig,
445
- kimiSpawnConfig,
446
- gooseSpawnConfig
447
- ];
448
- var lookup2 = /* @__PURE__ */ new Map();
449
- for (const config of allSpawnConfigs) {
450
- lookup2.set(config.agentId, config);
501
+ // packages/config-extends/src/resolve.ts
502
+ import path6 from "node:path";
503
+
504
+ // packages/config-mutations/src/mutations/config-mutation.ts
505
+ function merge(options) {
506
+ return {
507
+ kind: "configMerge",
508
+ target: options.target,
509
+ value: options.value,
510
+ format: options.format,
511
+ pruneByPrefix: options.pruneByPrefix,
512
+ label: options.label
513
+ };
451
514
  }
452
- var acpLookup = /* @__PURE__ */ new Map();
453
- acpLookup.set(openCodeAcpSpawnConfig.agentId, openCodeAcpSpawnConfig);
454
- acpLookup.set(kimiAcpSpawnConfig.agentId, kimiAcpSpawnConfig);
455
- acpLookup.set(gooseAcpSpawnConfig.agentId, gooseAcpSpawnConfig);
456
- function getSpawnConfig(input) {
457
- const resolvedId = resolveAgentId(input);
458
- if (!resolvedId) {
459
- return void 0;
460
- }
461
- return lookup2.get(resolvedId);
515
+ function prune(options) {
516
+ return {
517
+ kind: "configPrune",
518
+ target: options.target,
519
+ shape: options.shape,
520
+ format: options.format,
521
+ onlyIf: options.onlyIf,
522
+ label: options.label
523
+ };
462
524
  }
463
- function listMcpSupportedAgents() {
464
- const supported = [];
465
- for (const config of allSpawnConfigs) {
466
- if (config.kind !== "cli" || typeof config.mcpArgs !== "function" && typeof config.mcpEnv !== "function") {
467
- continue;
468
- }
469
- supported.push(config.agentId);
470
- }
471
- return supported;
525
+ function transform(options) {
526
+ return {
527
+ kind: "configTransform",
528
+ target: options.target,
529
+ format: options.format,
530
+ transform: options.transform,
531
+ label: options.label
532
+ };
472
533
  }
534
+ var configMutation = {
535
+ merge,
536
+ prune,
537
+ transform
538
+ };
473
539
 
474
- // packages/agent-spawn/src/spawn.ts
475
- import { spawn as spawnChildProcess } from "node:child_process";
476
- import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
477
- import path from "node:path";
478
-
479
- // packages/agent-spawn/src/configs/resolve-config.ts
480
- function resolveConfig(agentId) {
481
- const resolvedAgentId = resolveAgentId(agentId);
482
- if (!resolvedAgentId) {
483
- throw new Error(`Unknown agent "${agentId}".`);
484
- }
485
- const agentDefinition = allAgents.find((agent) => agent.id === resolvedAgentId);
486
- if (!agentDefinition) {
487
- throw new Error(`Unknown agent "${agentId}".`);
488
- }
489
- const spawnConfig = getSpawnConfig(resolvedAgentId);
490
- const binaryName = agentDefinition.binaryName;
491
- return { agentId: resolvedAgentId, binaryName, spawnConfig };
540
+ // packages/config-mutations/src/mutations/file-mutation.ts
541
+ function ensureDirectory(options) {
542
+ return {
543
+ kind: "ensureDirectory",
544
+ path: options.path,
545
+ label: options.label
546
+ };
492
547
  }
493
-
494
- // packages/agent-spawn/src/mcp-args.ts
495
- function hasMcpServers(servers) {
496
- if (!servers) {
497
- return false;
498
- }
499
- return Object.keys(servers).length > 0;
548
+ function remove(options) {
549
+ return {
550
+ kind: "removeFile",
551
+ target: options.target,
552
+ whenEmpty: options.whenEmpty,
553
+ whenContentMatches: options.whenContentMatches,
554
+ label: options.label
555
+ };
500
556
  }
501
- function getMcpArgs(config, servers) {
502
- if (!hasMcpServers(servers)) {
503
- return [];
504
- }
505
- if (!config.mcpArgs && !config.mcpEnv) {
506
- throw new Error(formatUnsupportedMcpSpawnMessage(config.agentId));
507
- }
508
- if (!config.mcpArgs) {
509
- return [];
510
- }
511
- return config.mcpArgs(servers);
557
+ function removeDirectory(options) {
558
+ return {
559
+ kind: "removeDirectory",
560
+ path: options.path,
561
+ force: options.force,
562
+ label: options.label
563
+ };
512
564
  }
513
- function formatUnsupportedMcpSpawnMessage(agentId) {
514
- const supported = listMcpSupportedAgents();
515
- const supportedText = supported.length > 0 ? supported.join(", ") : "(none)";
516
- return `Agent "${agentId}" does not support MCP servers at spawn time.
517
- Agents with spawn-time MCP support: ${supportedText}`;
565
+ function chmod(options) {
566
+ return {
567
+ kind: "chmod",
568
+ target: options.target,
569
+ mode: options.mode,
570
+ label: options.label
571
+ };
518
572
  }
519
-
520
- // packages/agent-spawn/src/model-utils.ts
521
- function stripModelNamespace(model) {
522
- const slashIndex = model.indexOf("/");
523
- return slashIndex === -1 ? model : model.slice(slashIndex + 1);
573
+ function backup(options) {
574
+ return {
575
+ kind: "backup",
576
+ target: options.target,
577
+ label: options.label
578
+ };
524
579
  }
580
+ var fileMutation = {
581
+ ensureDirectory,
582
+ remove,
583
+ removeDirectory,
584
+ chmod,
585
+ backup
586
+ };
525
587
 
526
- // packages/agent-spawn/src/spawn.ts
527
- function resolveCliConfig(agentId) {
528
- const resolved = resolveConfig(agentId);
529
- if (!resolved.spawnConfig) {
530
- throw new Error(`Agent "${resolved.agentId}" has no spawn config.`);
531
- }
532
- if (resolved.spawnConfig.kind !== "cli") {
533
- throw new Error(`Agent "${resolved.agentId}" does not support CLI spawn.`);
534
- }
535
- if (!resolved.binaryName) {
536
- throw new Error(`Agent "${resolved.agentId}" has no binaryName.`);
537
- }
588
+ // packages/config-mutations/src/mutations/template-mutation.ts
589
+ function write(options) {
538
590
  return {
539
- agentId: resolved.agentId,
540
- binaryName: resolved.binaryName,
541
- spawnConfig: resolved.spawnConfig
591
+ kind: "templateWrite",
592
+ target: options.target,
593
+ templateId: options.templateId,
594
+ context: options.context,
595
+ label: options.label
542
596
  };
543
597
  }
544
- function getDefaultArgsPosition(config) {
545
- return config.defaultArgsPosition ?? "afterPrompt";
598
+ function mergeToml(options) {
599
+ return {
600
+ kind: "templateMergeToml",
601
+ target: options.target,
602
+ templateId: options.templateId,
603
+ context: options.context,
604
+ label: options.label
605
+ };
546
606
  }
547
- function getMcpArgsPosition(config) {
548
- if (config.mcpArgsPosition) {
549
- return config.mcpArgsPosition;
550
- }
551
- return config.mcpArgsBeforeCommand ? "beforeCommand" : "afterCommand";
607
+ function mergeJson(options) {
608
+ return {
609
+ kind: "templateMergeJson",
610
+ target: options.target,
611
+ templateId: options.templateId,
612
+ context: options.context,
613
+ label: options.label
614
+ };
552
615
  }
553
- function buildCliArgs(config, options, stdinMode) {
554
- const mcpArgs = getMcpArgs(config, options.mcpServers);
555
- const defaultArgsPosition = getDefaultArgsPosition(config);
556
- const mcpArgsPosition = getMcpArgsPosition(config);
557
- const args = [];
558
- if (mcpArgsPosition === "beforeCommand") {
559
- args.push(...mcpArgs);
560
- }
561
- if (defaultArgsPosition === "beforePrompt") {
562
- args.push(...config.defaultArgs);
563
- }
564
- if (mcpArgsPosition === "beforePrompt") {
565
- args.push(...mcpArgs);
566
- }
567
- if (stdinMode) {
568
- args.push(
569
- config.promptFlag,
570
- ...stdinMode.omitPrompt ? [] : [options.prompt],
571
- ...stdinMode.extraArgs
572
- );
573
- } else {
574
- args.push(config.promptFlag, options.prompt);
616
+ var templateMutation = {
617
+ write,
618
+ mergeToml,
619
+ mergeJson
620
+ };
621
+
622
+ // packages/config-mutations/src/execution/apply-mutation.ts
623
+ import Mustache from "mustache";
624
+
625
+ // packages/config-mutations/src/formats/json.ts
626
+ import * as jsonc from "jsonc-parser";
627
+ function isConfigObject(value) {
628
+ return typeof value === "object" && value !== null && !Array.isArray(value);
629
+ }
630
+ function parse2(content) {
631
+ if (!content || content.trim() === "") {
632
+ return {};
575
633
  }
576
- if (options.model && config.modelFlag) {
577
- let model = config.modelStripProviderPrefix ? stripModelNamespace(options.model) : options.model;
578
- if (config.modelTransform) model = config.modelTransform(model);
579
- args.push(config.modelFlag, model);
634
+ const errors = [];
635
+ const parsed = jsonc.parse(content, errors, {
636
+ allowTrailingComma: true,
637
+ disallowComments: false
638
+ });
639
+ if (errors.length > 0) {
640
+ throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
580
641
  }
581
- if (defaultArgsPosition === "afterPrompt") {
582
- args.push(...config.defaultArgs);
642
+ if (parsed === null || parsed === void 0) {
643
+ return {};
583
644
  }
584
- if (mcpArgsPosition === "afterCommand") {
585
- args.push(...mcpArgs);
645
+ if (!isConfigObject(parsed)) {
646
+ throw new Error("Expected JSON object.");
586
647
  }
587
- const mode = resolveModeConfig(config.modes[options.mode ?? "yolo"]);
588
- args.push(...mode.args);
589
- if (options.args && options.args.length > 0) {
590
- args.push(...options.args);
648
+ return parsed;
649
+ }
650
+ function serialize(obj) {
651
+ return `${JSON.stringify(obj, null, 2)}
652
+ `;
653
+ }
654
+ function merge2(base, patch) {
655
+ const result = { ...base };
656
+ for (const [key, value] of Object.entries(patch)) {
657
+ if (value === void 0) {
658
+ continue;
659
+ }
660
+ const existing = result[key];
661
+ if (isConfigObject(existing) && isConfigObject(value)) {
662
+ result[key] = merge2(existing, value);
663
+ continue;
664
+ }
665
+ result[key] = value;
591
666
  }
592
- return { args, env: mode.env };
667
+ return result;
593
668
  }
594
- function buildSpawnArgs(agentId, options) {
595
- const { binaryName, spawnConfig } = resolveCliConfig(agentId);
596
- const stdinMode = options.useStdin && spawnConfig.stdinMode ? spawnConfig.stdinMode : void 0;
597
- const result = buildCliArgs(spawnConfig, options, stdinMode);
598
- return { binaryName, args: result.args, env: result.env };
669
+ function prune2(obj, shape) {
670
+ let changed = false;
671
+ const result = { ...obj };
672
+ for (const [key, pattern] of Object.entries(shape)) {
673
+ if (!(key in result)) {
674
+ continue;
675
+ }
676
+ const current = result[key];
677
+ if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
678
+ delete result[key];
679
+ changed = true;
680
+ continue;
681
+ }
682
+ if (isConfigObject(pattern) && isConfigObject(current)) {
683
+ const { changed: childChanged, result: childResult } = prune2(
684
+ current,
685
+ pattern
686
+ );
687
+ if (childChanged) {
688
+ changed = true;
689
+ }
690
+ if (Object.keys(childResult).length === 0) {
691
+ delete result[key];
692
+ } else {
693
+ result[key] = childResult;
694
+ }
695
+ continue;
696
+ }
697
+ delete result[key];
698
+ changed = true;
699
+ }
700
+ return { changed, result };
599
701
  }
600
-
601
- // packages/agent-spawn/src/spawn-interactive.ts
602
- import { spawn as spawnChildProcess2 } from "node:child_process";
603
-
604
- // packages/design-system/src/tokens/colors.ts
605
- import chalk from "chalk";
606
- var dark = {
607
- header: (text4) => chalk.magentaBright.bold(text4),
608
- divider: (text4) => chalk.dim(text4),
609
- prompt: (text4) => chalk.cyan(text4),
610
- number: (text4) => chalk.cyanBright(text4),
611
- intro: (text4) => chalk.bgMagenta.white(` Poe - ${text4} `),
612
- resolvedSymbol: chalk.magenta("\u25C7"),
613
- errorSymbol: chalk.red("\u25A0"),
614
- accent: (text4) => chalk.cyan(text4),
615
- muted: (text4) => chalk.dim(text4),
616
- success: (text4) => chalk.green(text4),
617
- warning: (text4) => chalk.yellow(text4),
618
- error: (text4) => chalk.red(text4),
619
- info: (text4) => chalk.magenta(text4),
620
- badge: (text4) => chalk.bgYellow.black(` ${text4} `)
621
- };
622
- var light = {
623
- header: (text4) => chalk.hex("#a200ff").bold(text4),
624
- divider: (text4) => chalk.hex("#666666")(text4),
625
- prompt: (text4) => chalk.hex("#006699").bold(text4),
626
- number: (text4) => chalk.hex("#0077cc").bold(text4),
627
- intro: (text4) => chalk.bgHex("#a200ff").white(` Poe - ${text4} `),
628
- resolvedSymbol: chalk.hex("#a200ff")("\u25C7"),
629
- errorSymbol: chalk.hex("#cc0000")("\u25A0"),
630
- accent: (text4) => chalk.hex("#006699").bold(text4),
631
- muted: (text4) => chalk.hex("#666666")(text4),
632
- success: (text4) => chalk.hex("#008800")(text4),
633
- warning: (text4) => chalk.hex("#cc6600")(text4),
634
- error: (text4) => chalk.hex("#cc0000")(text4),
635
- info: (text4) => chalk.hex("#a200ff")(text4),
636
- badge: (text4) => chalk.bgHex("#cc6600").white(` ${text4} `)
702
+ var jsonFormat = {
703
+ parse: parse2,
704
+ serialize,
705
+ merge: merge2,
706
+ prune: prune2
637
707
  };
638
708
 
639
- // packages/design-system/src/tokens/typography.ts
640
- import chalk2 from "chalk";
641
-
642
- // packages/design-system/src/components/text.ts
643
- import chalk3 from "chalk";
644
-
645
- // packages/design-system/src/internal/output-format.ts
646
- import { AsyncLocalStorage } from "node:async_hooks";
647
- var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
648
- var formatStorage = new AsyncLocalStorage();
649
- var cached;
650
- function resolveOutputFormat(env = process.env) {
651
- const scoped = formatStorage.getStore();
652
- if (scoped) {
653
- return scoped;
709
+ // packages/config-mutations/src/formats/toml.ts
710
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
711
+ function isConfigObject2(value) {
712
+ return typeof value === "object" && value !== null && !Array.isArray(value);
713
+ }
714
+ function parse3(content) {
715
+ if (!content || content.trim() === "") {
716
+ return {};
654
717
  }
655
- if (cached) {
656
- return cached;
718
+ const parsed = parseToml(content);
719
+ if (!isConfigObject2(parsed)) {
720
+ throw new Error("Expected TOML document to be a table.");
657
721
  }
658
- const raw = env.OUTPUT_FORMAT?.toLowerCase();
659
- cached = VALID_FORMATS.has(raw) ? raw : "terminal";
660
- return cached;
722
+ return parsed;
661
723
  }
662
-
663
- // packages/design-system/src/internal/theme-detect.ts
664
- function detectThemeFromEnv(env) {
665
- const apple = env.APPLE_INTERFACE_STYLE;
666
- if (typeof apple === "string") {
667
- return apple.toLowerCase() === "dark" ? "dark" : "light";
668
- }
669
- const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
670
- if (typeof vscodeKind === "string") {
671
- const normalized = vscodeKind.toLowerCase();
672
- if (normalized.includes("light")) {
673
- return "light";
724
+ function serialize2(obj) {
725
+ const serialized = stringifyToml(obj);
726
+ return serialized.endsWith("\n") ? serialized : `${serialized}
727
+ `;
728
+ }
729
+ function merge3(base, patch) {
730
+ const result = { ...base };
731
+ for (const [key, value] of Object.entries(patch)) {
732
+ if (value === void 0) {
733
+ continue;
674
734
  }
675
- if (normalized.includes("dark")) {
676
- return "dark";
735
+ const existing = result[key];
736
+ if (isConfigObject2(existing) && isConfigObject2(value)) {
737
+ result[key] = merge3(existing, value);
738
+ continue;
677
739
  }
740
+ result[key] = value;
678
741
  }
679
- const colorFGBG = env.COLORFGBG;
680
- if (typeof colorFGBG === "string") {
681
- const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
682
- const background = parts.at(-1);
683
- if (Number.isFinite(background)) {
684
- return background >= 8 ? "light" : "dark";
742
+ return result;
743
+ }
744
+ function prune3(obj, shape) {
745
+ let changed = false;
746
+ const result = { ...obj };
747
+ for (const [key, pattern] of Object.entries(shape)) {
748
+ if (!(key in result)) {
749
+ continue;
750
+ }
751
+ const current = result[key];
752
+ if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
753
+ delete result[key];
754
+ changed = true;
755
+ continue;
756
+ }
757
+ if (isConfigObject2(pattern) && isConfigObject2(current)) {
758
+ const { changed: childChanged, result: childResult } = prune3(
759
+ current,
760
+ pattern
761
+ );
762
+ if (childChanged) {
763
+ changed = true;
764
+ }
765
+ if (Object.keys(childResult).length === 0) {
766
+ delete result[key];
767
+ } else {
768
+ result[key] = childResult;
769
+ }
770
+ continue;
685
771
  }
772
+ delete result[key];
773
+ changed = true;
686
774
  }
687
- return void 0;
775
+ return { changed, result };
688
776
  }
689
- function resolveThemeName(env = process.env) {
690
- const raw = (env.POE_CODE_THEME ?? env.POE_THEME)?.toLowerCase();
691
- if (raw === "light" || raw === "dark") {
692
- return raw;
777
+ var tomlFormat = {
778
+ parse: parse3,
779
+ serialize: serialize2,
780
+ merge: merge3,
781
+ prune: prune3
782
+ };
783
+
784
+ // packages/config-mutations/src/formats/yaml.ts
785
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
786
+ function isConfigObject3(value) {
787
+ return typeof value === "object" && value !== null && !Array.isArray(value);
788
+ }
789
+ function parse4(content) {
790
+ if (!content || content.trim() === "") {
791
+ return {};
693
792
  }
694
- const detected = detectThemeFromEnv(env);
695
- if (detected) {
696
- return detected;
793
+ const parsed = parseYaml2(content);
794
+ if (parsed === null || parsed === void 0) {
795
+ return {};
697
796
  }
698
- return "dark";
699
- }
700
- var cachedTheme;
701
- function getTheme(env) {
702
- if (cachedTheme) {
703
- return cachedTheme;
797
+ if (!isConfigObject3(parsed)) {
798
+ throw new Error("Expected YAML object.");
704
799
  }
705
- const themeName = resolveThemeName(env);
706
- cachedTheme = themeName === "light" ? light : dark;
707
- return cachedTheme;
800
+ return parsed;
708
801
  }
709
-
710
- // packages/design-system/src/components/symbols.ts
711
- import chalk4 from "chalk";
712
- var symbols = {
713
- get info() {
714
- const format = resolveOutputFormat();
715
- if (format === "json") return "info";
716
- if (format === "markdown") return "(i)";
717
- return chalk4.magenta("\u25CF");
718
- },
719
- get success() {
720
- const format = resolveOutputFormat();
721
- if (format === "json") return "success";
722
- if (format === "markdown") return "[ok]";
723
- return chalk4.magenta("\u25C6");
724
- },
725
- get resolved() {
726
- const format = resolveOutputFormat();
727
- if (format === "json") return "resolved";
728
- if (format === "markdown") return ">";
729
- return getTheme().resolvedSymbol;
730
- },
731
- get errorResolved() {
732
- const format = resolveOutputFormat();
733
- if (format === "json") return "error";
734
- if (format === "markdown") return "[!]";
735
- return getTheme().errorSymbol;
736
- },
737
- get bar() {
738
- const format = resolveOutputFormat();
739
- if (format === "json") return "";
740
- if (format === "markdown") return "|";
741
- return "\u2502";
742
- },
743
- cornerTopRight: "\u256E",
744
- cornerBottomRight: "\u256F",
745
- get warning() {
746
- const format = resolveOutputFormat();
747
- if (format === "json") return "warning";
748
- if (format === "markdown") return "[!]";
749
- return "\u25B2";
750
- },
751
- get active() {
752
- const format = resolveOutputFormat();
753
- if (format === "json") return "active";
754
- if (format === "markdown") return "[x]";
755
- return "\u25C6";
756
- },
757
- get inactive() {
758
- const format = resolveOutputFormat();
759
- if (format === "json") return "inactive";
760
- if (format === "markdown") return "[ ]";
761
- return "\u25CB";
762
- }
763
- };
764
-
765
- // packages/design-system/src/components/logger.ts
766
- import chalk6 from "chalk";
767
-
768
- // packages/design-system/src/prompts/primitives/log.ts
769
- import chalk5 from "chalk";
770
-
771
- // packages/design-system/src/internal/strip-ansi.ts
772
- function stripAnsi(value) {
773
- return value.replace(/\u001b\[[0-9;]*m/g, "");
802
+ function serialize3(obj) {
803
+ const serialized = stringifyYaml(obj);
804
+ return serialized.endsWith("\n") ? serialized : `${serialized}
805
+ `;
774
806
  }
775
-
776
- // packages/design-system/src/prompts/primitives/log.ts
777
- function writeTerminalMessage(msg, {
778
- symbol = chalk5.gray("\u2502"),
779
- secondarySymbol = chalk5.gray("\u2502"),
780
- spacing: spacing2 = 1,
781
- withGuide = true
782
- } = {}) {
783
- const lines = [];
784
- const showGuide = withGuide !== false;
785
- const contentLines = msg.split("\n");
786
- const prefix = showGuide ? `${symbol} ` : "";
787
- const continuationPrefix = showGuide ? `${secondarySymbol} ` : "";
788
- const emptyGuide = showGuide ? secondarySymbol : "";
789
- for (let index = 0; index < spacing2; index += 1) {
790
- lines.push(emptyGuide);
791
- }
792
- if (contentLines.length === 0) {
793
- process.stdout.write("\n");
794
- return;
795
- }
796
- const [firstLine = "", ...continuationLines] = contentLines;
797
- if (firstLine.length > 0) {
798
- lines.push(`${prefix}${firstLine}`);
799
- } else {
800
- lines.push(showGuide ? symbol : "");
807
+ function merge4(base, patch) {
808
+ const result = { ...base };
809
+ for (const [key, value] of Object.entries(patch)) {
810
+ if (value === void 0) {
811
+ continue;
812
+ }
813
+ const existing = result[key];
814
+ if (isConfigObject3(existing) && isConfigObject3(value)) {
815
+ result[key] = merge4(existing, value);
816
+ continue;
817
+ }
818
+ result[key] = value;
801
819
  }
802
- for (const line of continuationLines) {
803
- if (line.length > 0) {
804
- lines.push(`${continuationPrefix}${line}`);
820
+ return result;
821
+ }
822
+ function prune4(obj, shape) {
823
+ let changed = false;
824
+ const result = { ...obj };
825
+ for (const [key, pattern] of Object.entries(shape)) {
826
+ if (!(key in result)) {
805
827
  continue;
806
828
  }
807
- lines.push(emptyGuide);
829
+ const current = result[key];
830
+ if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
831
+ delete result[key];
832
+ changed = true;
833
+ continue;
834
+ }
835
+ if (isConfigObject3(pattern) && isConfigObject3(current)) {
836
+ const { changed: childChanged, result: childResult } = prune4(current, pattern);
837
+ if (childChanged) {
838
+ changed = true;
839
+ }
840
+ if (Object.keys(childResult).length === 0) {
841
+ delete result[key];
842
+ } else {
843
+ result[key] = childResult;
844
+ }
845
+ continue;
846
+ }
847
+ delete result[key];
848
+ changed = true;
808
849
  }
809
- process.stdout.write(`${lines.join("\n")}
810
- `);
850
+ return { changed, result };
811
851
  }
812
- function message(msg, options) {
813
- const format = resolveOutputFormat();
814
- if (format === "markdown") {
815
- process.stdout.write(`- ${stripAnsi(msg)}
816
- `);
817
- return;
852
+ var yamlFormat = {
853
+ parse: parse4,
854
+ serialize: serialize3,
855
+ merge: merge4,
856
+ prune: prune4
857
+ };
858
+
859
+ // packages/config-mutations/src/formats/index.ts
860
+ var formatRegistry = {
861
+ json: jsonFormat,
862
+ toml: tomlFormat,
863
+ yaml: yamlFormat
864
+ };
865
+ var extensionMap = {
866
+ ".json": "json",
867
+ ".toml": "toml",
868
+ ".yaml": "yaml",
869
+ ".yml": "yaml"
870
+ };
871
+ function getConfigFormat(pathOrFormat) {
872
+ if (pathOrFormat in formatRegistry) {
873
+ return formatRegistry[pathOrFormat];
818
874
  }
819
- if (format === "json") {
820
- process.stdout.write(
821
- `${JSON.stringify({ level: "message", message: stripAnsi(msg) })}
822
- `
875
+ const ext = getExtension(pathOrFormat);
876
+ const formatName = extensionMap[ext];
877
+ if (!formatName) {
878
+ throw new Error(
879
+ `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
823
880
  );
824
- return;
825
881
  }
826
- writeTerminalMessage(msg, options);
882
+ return formatRegistry[formatName];
827
883
  }
828
- function info(msg) {
829
- const format = resolveOutputFormat();
830
- if (format === "markdown") {
831
- process.stdout.write(`- **info:** ${stripAnsi(msg)}
832
- `);
833
- return;
884
+ function detectFormat(path18) {
885
+ const ext = getExtension(path18);
886
+ return extensionMap[ext];
887
+ }
888
+ function getExtension(path18) {
889
+ const lastDot = path18.lastIndexOf(".");
890
+ if (lastDot === -1) {
891
+ return "";
834
892
  }
835
- if (format === "json") {
836
- process.stdout.write(
837
- `${JSON.stringify({ level: "info", message: stripAnsi(msg) })}
838
- `
839
- );
840
- return;
893
+ return path18.slice(lastDot).toLowerCase();
894
+ }
895
+
896
+ // packages/config-mutations/src/execution/path-utils.ts
897
+ import path7 from "node:path";
898
+ function expandHome(targetPath, homeDir) {
899
+ if (!targetPath?.startsWith("~")) {
900
+ return targetPath;
841
901
  }
842
- message(msg, { symbol: symbols.info });
902
+ if (targetPath.startsWith("~./")) {
903
+ targetPath = `~/.${targetPath.slice(3)}`;
904
+ }
905
+ let remainder = targetPath.slice(1);
906
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
907
+ remainder = remainder.slice(1);
908
+ } else if (remainder.startsWith(".")) {
909
+ remainder = remainder.slice(1);
910
+ if (remainder.startsWith("/") || remainder.startsWith("\\")) {
911
+ remainder = remainder.slice(1);
912
+ }
913
+ }
914
+ return remainder.length === 0 ? homeDir : path7.join(homeDir, remainder);
843
915
  }
844
- function success(msg) {
845
- const format = resolveOutputFormat();
846
- if (format === "markdown") {
847
- process.stdout.write(`- **success:** ${stripAnsi(msg)}
848
- `);
849
- return;
916
+ function validateHomePath(targetPath) {
917
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
918
+ throw new Error("Target path must be a non-empty string.");
850
919
  }
851
- if (format === "json") {
852
- process.stdout.write(
853
- `${JSON.stringify({ level: "success", message: stripAnsi(msg) })}
854
- `
920
+ if (!targetPath.startsWith("~")) {
921
+ throw new Error(
922
+ `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
855
923
  );
856
- return;
857
924
  }
858
- message(msg, { symbol: symbols.success });
859
925
  }
860
- function warn(msg) {
861
- const format = resolveOutputFormat();
862
- if (format === "markdown") {
863
- process.stdout.write(`- **warning:** ${stripAnsi(msg)}
864
- `);
865
- return;
866
- }
867
- if (format === "json") {
868
- process.stdout.write(
869
- `${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}
870
- `
871
- );
872
- return;
926
+ function resolvePath(rawPath, homeDir, pathMapper) {
927
+ validateHomePath(rawPath);
928
+ const expanded = expandHome(rawPath, homeDir);
929
+ if (!pathMapper) {
930
+ return expanded;
873
931
  }
874
- message(msg, { symbol: chalk5.yellow("\u25B2") });
932
+ const rawDirectory = path7.dirname(expanded);
933
+ const mappedDirectory = pathMapper.mapTargetDirectory({
934
+ targetDirectory: rawDirectory
935
+ });
936
+ const filename = path7.basename(expanded);
937
+ return filename.length === 0 ? mappedDirectory : path7.join(mappedDirectory, filename);
875
938
  }
876
- function error(msg) {
877
- const format = resolveOutputFormat();
878
- if (format === "markdown") {
879
- process.stdout.write(`- **error:** ${stripAnsi(msg)}
880
- `);
881
- return;
939
+
940
+ // packages/config-mutations/src/fs-utils.ts
941
+ function isNotFound(error2) {
942
+ return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
943
+ }
944
+ async function readFileIfExists(fs, target) {
945
+ try {
946
+ return await fs.readFile(target, "utf8");
947
+ } catch (error2) {
948
+ if (isNotFound(error2)) {
949
+ return null;
950
+ }
951
+ throw error2;
882
952
  }
883
- if (format === "json") {
884
- process.stdout.write(
885
- `${JSON.stringify({ level: "error", message: stripAnsi(msg) })}
886
- `
887
- );
888
- return;
953
+ }
954
+ async function pathExists(fs, target) {
955
+ try {
956
+ await fs.stat(target);
957
+ return true;
958
+ } catch (error2) {
959
+ if (isNotFound(error2)) {
960
+ return false;
961
+ }
962
+ throw error2;
889
963
  }
890
- message(msg, { symbol: chalk5.red("\u25A0") });
891
964
  }
892
- var log = {
893
- info,
894
- success,
895
- message,
896
- warn,
897
- error
898
- };
965
+ function createTimestamp() {
966
+ return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
967
+ }
899
968
 
900
- // packages/design-system/src/components/logger.ts
901
- function createLogger(emitter) {
902
- const emit = (level, message2) => {
903
- if (emitter) {
904
- emitter(message2);
905
- return;
906
- }
907
- if (level === "success") {
908
- log.success(message2);
909
- return;
969
+ // packages/config-mutations/src/execution/apply-mutation.ts
970
+ function resolveValue(resolver, options) {
971
+ if (typeof resolver === "function") {
972
+ return resolver(options);
973
+ }
974
+ return resolver;
975
+ }
976
+ function createInvalidDocumentBackupPath(targetPath) {
977
+ const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
978
+ return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
979
+ }
980
+ async function backupInvalidDocument(fs, targetPath, content) {
981
+ const backupPath = createInvalidDocumentBackupPath(targetPath);
982
+ await fs.writeFile(backupPath, content, { encoding: "utf8" });
983
+ }
984
+ function describeMutation(kind, targetPath) {
985
+ const displayPath = targetPath ?? "target";
986
+ switch (kind) {
987
+ case "ensureDirectory":
988
+ return `Create ${displayPath}`;
989
+ case "removeDirectory":
990
+ return `Remove directory ${displayPath}`;
991
+ case "backup":
992
+ return `Backup ${displayPath}`;
993
+ case "templateWrite":
994
+ return `Write ${displayPath}`;
995
+ case "chmod":
996
+ return `Set permissions on ${displayPath}`;
997
+ case "removeFile":
998
+ return `Remove ${displayPath}`;
999
+ case "configMerge":
1000
+ case "configPrune":
1001
+ case "configTransform":
1002
+ case "templateMergeToml":
1003
+ case "templateMergeJson":
1004
+ return `Update ${displayPath}`;
1005
+ default:
1006
+ return "Operation";
1007
+ }
1008
+ }
1009
+ function pruneKeysByPrefix(table, prefix) {
1010
+ const result = {};
1011
+ for (const [key, value] of Object.entries(table)) {
1012
+ if (!key.startsWith(prefix)) {
1013
+ result[key] = value;
910
1014
  }
911
- if (level === "warn") {
912
- log.warn(message2);
913
- return;
1015
+ }
1016
+ return result;
1017
+ }
1018
+ function isConfigObject4(value) {
1019
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1020
+ }
1021
+ function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
1022
+ const result = { ...base };
1023
+ const prefixMap = pruneByPrefix ?? {};
1024
+ for (const [key, value] of Object.entries(patch)) {
1025
+ const current = result[key];
1026
+ const prefix = prefixMap[key];
1027
+ if (isConfigObject4(current) && isConfigObject4(value)) {
1028
+ if (prefix) {
1029
+ const pruned = pruneKeysByPrefix(current, prefix);
1030
+ result[key] = { ...pruned, ...value };
1031
+ } else {
1032
+ result[key] = mergeWithPruneByPrefix(
1033
+ current,
1034
+ value,
1035
+ prefixMap
1036
+ );
1037
+ }
1038
+ continue;
914
1039
  }
915
- if (level === "error") {
916
- log.error(message2);
917
- return;
1040
+ result[key] = value;
1041
+ }
1042
+ return result;
1043
+ }
1044
+ async function applyMutation(mutation, context, options) {
1045
+ switch (mutation.kind) {
1046
+ case "ensureDirectory":
1047
+ return applyEnsureDirectory(mutation, context, options);
1048
+ case "removeDirectory":
1049
+ return applyRemoveDirectory(mutation, context, options);
1050
+ case "removeFile":
1051
+ return applyRemoveFile(mutation, context, options);
1052
+ case "chmod":
1053
+ return applyChmod(mutation, context, options);
1054
+ case "backup":
1055
+ return applyBackup(mutation, context, options);
1056
+ case "configMerge":
1057
+ return applyConfigMerge(mutation, context, options);
1058
+ case "configPrune":
1059
+ return applyConfigPrune(mutation, context, options);
1060
+ case "configTransform":
1061
+ return applyConfigTransform(mutation, context, options);
1062
+ case "templateWrite":
1063
+ return applyTemplateWrite(mutation, context, options);
1064
+ case "templateMergeToml":
1065
+ return applyTemplateMerge(mutation, context, options, "toml");
1066
+ case "templateMergeJson":
1067
+ return applyTemplateMerge(mutation, context, options, "json");
1068
+ default: {
1069
+ const never = mutation;
1070
+ throw new Error(`Unknown mutation kind: ${never.kind}`);
918
1071
  }
919
- log.info(message2);
1072
+ }
1073
+ }
1074
+ async function applyEnsureDirectory(mutation, context, options) {
1075
+ const rawPath = resolveValue(mutation.path, options);
1076
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1077
+ const details = {
1078
+ kind: mutation.kind,
1079
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1080
+ targetPath
920
1081
  };
1082
+ const existed = await pathExists(context.fs, targetPath);
1083
+ if (!context.dryRun) {
1084
+ await context.fs.mkdir(targetPath, { recursive: true });
1085
+ }
921
1086
  return {
922
- info(message2) {
923
- emit("info", message2);
924
- },
925
- success(message2) {
926
- emit("success", message2);
927
- },
928
- warn(message2) {
929
- emit("warn", message2);
930
- },
931
- error(message2) {
932
- emit("error", message2);
933
- },
934
- resolved(label, value) {
935
- if (emitter) {
936
- emitter(`${label}: ${value}`);
937
- return;
938
- }
939
- log.message(`${label}
940
- ${value}`, { symbol: symbols.resolved });
941
- },
942
- errorResolved(label, value) {
943
- if (emitter) {
944
- emitter(`${label}: ${value}`);
945
- return;
946
- }
947
- log.message(`${label}
948
- ${value}`, { symbol: symbols.errorResolved });
1087
+ outcome: {
1088
+ changed: !existed,
1089
+ effect: "mkdir",
1090
+ detail: existed ? "noop" : "create"
949
1091
  },
950
- message(message2, symbol) {
951
- if (emitter) {
952
- emitter(message2);
953
- return;
954
- }
955
- log.message(message2, { symbol: symbol ?? chalk6.gray("\u2502") });
956
- }
1092
+ details
1093
+ };
1094
+ }
1095
+ async function applyRemoveDirectory(mutation, context, options) {
1096
+ const rawPath = resolveValue(mutation.path, options);
1097
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1098
+ const details = {
1099
+ kind: mutation.kind,
1100
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1101
+ targetPath
1102
+ };
1103
+ const existed = await pathExists(context.fs, targetPath);
1104
+ if (!existed) {
1105
+ return {
1106
+ outcome: { changed: false, effect: "none", detail: "noop" },
1107
+ details
1108
+ };
1109
+ }
1110
+ if (typeof context.fs.rm !== "function") {
1111
+ return {
1112
+ outcome: { changed: false, effect: "none", detail: "noop" },
1113
+ details
1114
+ };
1115
+ }
1116
+ if (mutation.force) {
1117
+ if (!context.dryRun) {
1118
+ await context.fs.rm(targetPath, { recursive: true, force: true });
1119
+ }
1120
+ return {
1121
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1122
+ details
1123
+ };
1124
+ }
1125
+ const entries = await context.fs.readdir(targetPath);
1126
+ if (entries.length > 0) {
1127
+ return {
1128
+ outcome: { changed: false, effect: "none", detail: "noop" },
1129
+ details
1130
+ };
1131
+ }
1132
+ if (!context.dryRun) {
1133
+ await context.fs.rm(targetPath, { recursive: true, force: true });
1134
+ }
1135
+ return {
1136
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1137
+ details
1138
+ };
1139
+ }
1140
+ async function applyRemoveFile(mutation, context, options) {
1141
+ const rawPath = resolveValue(mutation.target, options);
1142
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1143
+ const details = {
1144
+ kind: mutation.kind,
1145
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1146
+ targetPath
1147
+ };
1148
+ try {
1149
+ const content = await context.fs.readFile(targetPath, "utf8");
1150
+ const trimmed = content.trim();
1151
+ if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
1152
+ return {
1153
+ outcome: { changed: false, effect: "none", detail: "noop" },
1154
+ details
1155
+ };
1156
+ }
1157
+ if (mutation.whenEmpty && trimmed.length > 0) {
1158
+ return {
1159
+ outcome: { changed: false, effect: "none", detail: "noop" },
1160
+ details
1161
+ };
1162
+ }
1163
+ if (!context.dryRun) {
1164
+ await context.fs.unlink(targetPath);
1165
+ }
1166
+ return {
1167
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1168
+ details
1169
+ };
1170
+ } catch (error2) {
1171
+ if (isNotFound(error2)) {
1172
+ return {
1173
+ outcome: { changed: false, effect: "none", detail: "noop" },
1174
+ details
1175
+ };
1176
+ }
1177
+ throw error2;
1178
+ }
1179
+ }
1180
+ async function applyChmod(mutation, context, options) {
1181
+ const rawPath = resolveValue(mutation.target, options);
1182
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1183
+ const details = {
1184
+ kind: mutation.kind,
1185
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1186
+ targetPath
1187
+ };
1188
+ if (typeof context.fs.chmod !== "function") {
1189
+ return {
1190
+ outcome: { changed: false, effect: "none", detail: "noop" },
1191
+ details
1192
+ };
1193
+ }
1194
+ try {
1195
+ const stat2 = await context.fs.stat(targetPath);
1196
+ const currentMode = typeof stat2.mode === "number" ? stat2.mode & 511 : null;
1197
+ if (currentMode === mutation.mode) {
1198
+ return {
1199
+ outcome: { changed: false, effect: "none", detail: "noop" },
1200
+ details
1201
+ };
1202
+ }
1203
+ if (!context.dryRun) {
1204
+ await context.fs.chmod(targetPath, mutation.mode);
1205
+ }
1206
+ return {
1207
+ outcome: { changed: true, effect: "chmod", detail: "update" },
1208
+ details
1209
+ };
1210
+ } catch (error2) {
1211
+ if (isNotFound(error2)) {
1212
+ return {
1213
+ outcome: { changed: false, effect: "none", detail: "noop" },
1214
+ details
1215
+ };
1216
+ }
1217
+ throw error2;
1218
+ }
1219
+ }
1220
+ async function applyBackup(mutation, context, options) {
1221
+ const rawPath = resolveValue(mutation.target, options);
1222
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1223
+ const details = {
1224
+ kind: mutation.kind,
1225
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1226
+ targetPath
1227
+ };
1228
+ const content = await readFileIfExists(context.fs, targetPath);
1229
+ if (content === null) {
1230
+ return {
1231
+ outcome: { changed: false, effect: "none", detail: "noop" },
1232
+ details
1233
+ };
1234
+ }
1235
+ if (!context.dryRun) {
1236
+ const backupPath = `${targetPath}.backup-${createTimestamp()}`;
1237
+ await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
1238
+ }
1239
+ return {
1240
+ outcome: { changed: true, effect: "copy", detail: "backup" },
1241
+ details
1242
+ };
1243
+ }
1244
+ async function applyConfigMerge(mutation, context, options) {
1245
+ const rawPath = resolveValue(mutation.target, options);
1246
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1247
+ const details = {
1248
+ kind: mutation.kind,
1249
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1250
+ targetPath
1251
+ };
1252
+ const formatName = mutation.format ?? detectFormat(rawPath);
1253
+ if (!formatName) {
1254
+ throw new Error(
1255
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1256
+ );
1257
+ }
1258
+ const format = getConfigFormat(formatName);
1259
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1260
+ let current;
1261
+ try {
1262
+ current = rawContent === null ? {} : format.parse(rawContent);
1263
+ } catch {
1264
+ if (rawContent !== null) {
1265
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1266
+ }
1267
+ current = {};
1268
+ }
1269
+ const value = resolveValue(mutation.value, options);
1270
+ let merged;
1271
+ if (mutation.pruneByPrefix) {
1272
+ merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
1273
+ } else {
1274
+ merged = format.merge(current, value);
1275
+ }
1276
+ const serialized = format.serialize(merged);
1277
+ const changed = serialized !== rawContent;
1278
+ if (changed && !context.dryRun) {
1279
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1280
+ }
1281
+ return {
1282
+ outcome: {
1283
+ changed,
1284
+ effect: changed ? "write" : "none",
1285
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1286
+ },
1287
+ details
1288
+ };
1289
+ }
1290
+ async function applyConfigPrune(mutation, context, options) {
1291
+ const rawPath = resolveValue(mutation.target, options);
1292
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1293
+ const details = {
1294
+ kind: mutation.kind,
1295
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1296
+ targetPath
1297
+ };
1298
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1299
+ if (rawContent === null) {
1300
+ return {
1301
+ outcome: { changed: false, effect: "none", detail: "noop" },
1302
+ details
1303
+ };
1304
+ }
1305
+ const formatName = mutation.format ?? detectFormat(rawPath);
1306
+ if (!formatName) {
1307
+ throw new Error(
1308
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1309
+ );
1310
+ }
1311
+ const format = getConfigFormat(formatName);
1312
+ let current;
1313
+ try {
1314
+ current = format.parse(rawContent);
1315
+ } catch {
1316
+ return {
1317
+ outcome: { changed: false, effect: "none", detail: "noop" },
1318
+ details
1319
+ };
1320
+ }
1321
+ if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
1322
+ return {
1323
+ outcome: { changed: false, effect: "none", detail: "noop" },
1324
+ details
1325
+ };
1326
+ }
1327
+ const shape = resolveValue(mutation.shape, options);
1328
+ const { changed, result } = format.prune(current, shape);
1329
+ if (!changed) {
1330
+ return {
1331
+ outcome: { changed: false, effect: "none", detail: "noop" },
1332
+ details
1333
+ };
1334
+ }
1335
+ if (Object.keys(result).length === 0) {
1336
+ if (!context.dryRun) {
1337
+ await context.fs.unlink(targetPath);
1338
+ }
1339
+ return {
1340
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1341
+ details
1342
+ };
1343
+ }
1344
+ const serialized = format.serialize(result);
1345
+ if (!context.dryRun) {
1346
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1347
+ }
1348
+ return {
1349
+ outcome: { changed: true, effect: "write", detail: "update" },
1350
+ details
1351
+ };
1352
+ }
1353
+ async function applyConfigTransform(mutation, context, options) {
1354
+ const rawPath = resolveValue(mutation.target, options);
1355
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1356
+ const details = {
1357
+ kind: mutation.kind,
1358
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1359
+ targetPath
1360
+ };
1361
+ const formatName = mutation.format ?? detectFormat(rawPath);
1362
+ if (!formatName) {
1363
+ throw new Error(
1364
+ `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1365
+ );
1366
+ }
1367
+ const format = getConfigFormat(formatName);
1368
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1369
+ let current;
1370
+ try {
1371
+ current = rawContent === null ? {} : format.parse(rawContent);
1372
+ } catch {
1373
+ if (rawContent !== null) {
1374
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1375
+ }
1376
+ current = {};
1377
+ }
1378
+ const { content: transformed, changed } = mutation.transform(current, options);
1379
+ if (!changed) {
1380
+ return {
1381
+ outcome: { changed: false, effect: "none", detail: "noop" },
1382
+ details
1383
+ };
1384
+ }
1385
+ if (transformed === null) {
1386
+ if (rawContent === null) {
1387
+ return {
1388
+ outcome: { changed: false, effect: "none", detail: "noop" },
1389
+ details
1390
+ };
1391
+ }
1392
+ if (!context.dryRun) {
1393
+ await context.fs.unlink(targetPath);
1394
+ }
1395
+ return {
1396
+ outcome: { changed: true, effect: "delete", detail: "delete" },
1397
+ details
1398
+ };
1399
+ }
1400
+ const serialized = format.serialize(transformed);
1401
+ if (!context.dryRun) {
1402
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1403
+ }
1404
+ return {
1405
+ outcome: {
1406
+ changed: true,
1407
+ effect: "write",
1408
+ detail: rawContent === null ? "create" : "update"
1409
+ },
1410
+ details
1411
+ };
1412
+ }
1413
+ async function applyTemplateWrite(mutation, context, options) {
1414
+ if (!context.templates) {
1415
+ throw new Error(
1416
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1417
+ );
1418
+ }
1419
+ const rawPath = resolveValue(mutation.target, options);
1420
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1421
+ const details = {
1422
+ kind: mutation.kind,
1423
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1424
+ targetPath
1425
+ };
1426
+ const template = await context.templates(mutation.templateId);
1427
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1428
+ const rendered = Mustache.render(template, templateContext);
1429
+ const existed = await pathExists(context.fs, targetPath);
1430
+ if (!context.dryRun) {
1431
+ await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
1432
+ }
1433
+ return {
1434
+ outcome: {
1435
+ changed: true,
1436
+ effect: "write",
1437
+ detail: existed ? "update" : "create"
1438
+ },
1439
+ details
1440
+ };
1441
+ }
1442
+ async function applyTemplateMerge(mutation, context, options, formatName) {
1443
+ if (!context.templates) {
1444
+ throw new Error(
1445
+ "Template mutations require a templates loader. Provide templates function to runMutations context."
1446
+ );
1447
+ }
1448
+ const rawPath = resolveValue(mutation.target, options);
1449
+ const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1450
+ const details = {
1451
+ kind: mutation.kind,
1452
+ label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1453
+ targetPath
1454
+ };
1455
+ const format = getConfigFormat(formatName);
1456
+ const template = await context.templates(mutation.templateId);
1457
+ const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
1458
+ const rendered = Mustache.render(template, templateContext);
1459
+ let templateDoc;
1460
+ try {
1461
+ templateDoc = format.parse(rendered);
1462
+ } catch (error2) {
1463
+ throw new Error(
1464
+ `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error2}`,
1465
+ { cause: error2 }
1466
+ );
1467
+ }
1468
+ const rawContent = await readFileIfExists(context.fs, targetPath);
1469
+ let current;
1470
+ try {
1471
+ current = rawContent === null ? {} : format.parse(rawContent);
1472
+ } catch {
1473
+ if (rawContent !== null) {
1474
+ await backupInvalidDocument(context.fs, targetPath, rawContent);
1475
+ }
1476
+ current = {};
1477
+ }
1478
+ const merged = format.merge(current, templateDoc);
1479
+ const serialized = format.serialize(merged);
1480
+ const changed = serialized !== rawContent;
1481
+ if (changed && !context.dryRun) {
1482
+ await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
1483
+ }
1484
+ return {
1485
+ outcome: {
1486
+ changed,
1487
+ effect: changed ? "write" : "none",
1488
+ detail: changed ? rawContent === null ? "create" : "update" : "noop"
1489
+ },
1490
+ details
957
1491
  };
958
1492
  }
959
- var logger = createLogger();
960
-
961
- // packages/design-system/src/components/table.ts
962
- import { Table } from "console-table-printer";
963
-
964
- // packages/design-system/src/acp/components.ts
965
- import chalk7 from "chalk";
966
-
967
- // packages/design-system/src/acp/writer.ts
968
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
969
- var storage = new AsyncLocalStorage2();
970
-
971
- // packages/design-system/src/acp/components.ts
972
- var AGENT_PREFIX = `${chalk7.green.bold("\u2713")} agent: `;
973
-
974
- // packages/design-system/src/dashboard/buffer.ts
975
- import chalk8 from "chalk";
976
-
977
- // packages/design-system/src/dashboard/terminal.ts
978
- import readline from "node:readline";
979
- import { PassThrough } from "node:stream";
980
-
981
- // packages/design-system/src/prompts/index.ts
982
- import chalk15 from "chalk";
983
- import * as clack from "@clack/prompts";
984
1493
 
985
- // packages/design-system/src/prompts/primitives/cancel.ts
986
- import chalk9 from "chalk";
987
- import { isCancel } from "@clack/prompts";
1494
+ // packages/config-mutations/src/execution/run-mutations.ts
1495
+ async function runMutations(mutations, context, options) {
1496
+ const effects = [];
1497
+ let anyChanged = false;
1498
+ const resolverOptions = options ?? {};
1499
+ for (const mutation of mutations) {
1500
+ const { outcome } = await executeMutation(
1501
+ mutation,
1502
+ context,
1503
+ resolverOptions
1504
+ );
1505
+ effects.push(outcome);
1506
+ if (outcome.changed) {
1507
+ anyChanged = true;
1508
+ }
1509
+ }
1510
+ return {
1511
+ changed: anyChanged,
1512
+ effects
1513
+ };
1514
+ }
1515
+ async function executeMutation(mutation, context, options) {
1516
+ context.observers?.onStart?.({
1517
+ kind: mutation.kind,
1518
+ label: mutation.label ?? mutation.kind,
1519
+ targetPath: void 0
1520
+ // Will be resolved during apply
1521
+ });
1522
+ try {
1523
+ const { outcome, details } = await applyMutation(mutation, context, options);
1524
+ context.observers?.onComplete?.(details, outcome);
1525
+ return { outcome, details };
1526
+ } catch (error2) {
1527
+ context.observers?.onError?.(
1528
+ {
1529
+ kind: mutation.kind,
1530
+ label: mutation.label ?? mutation.kind,
1531
+ targetPath: void 0
1532
+ },
1533
+ error2
1534
+ );
1535
+ throw error2;
1536
+ }
1537
+ }
988
1538
 
989
- // packages/design-system/src/prompts/primitives/intro.ts
990
- import chalk10 from "chalk";
1539
+ // packages/config-mutations/src/template/render.ts
1540
+ import Mustache2 from "mustache";
1541
+ var originalEscape = Mustache2.escape;
991
1542
 
992
- // packages/design-system/src/prompts/primitives/note.ts
993
- import chalk11 from "chalk";
1543
+ // packages/config-mutations/src/types.ts
1544
+ function isConfigObject5(value) {
1545
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1546
+ }
994
1547
 
995
- // packages/design-system/src/prompts/primitives/outro.ts
996
- import chalk12 from "chalk";
1548
+ // packages/poe-code-config/src/store.ts
1549
+ var EMPTY_DOCUMENT = `${JSON.stringify({}, null, 2)}
1550
+ `;
997
1551
 
998
- // packages/design-system/src/prompts/primitives/spinner.ts
999
- import chalk14 from "chalk";
1552
+ // packages/poe-code-config/src/inspect.ts
1553
+ import path9 from "node:path";
1554
+ var EMPTY_DOCUMENT2 = `${JSON.stringify({}, null, 2)}
1555
+ `;
1000
1556
 
1001
- // packages/design-system/src/static/spinner.ts
1002
- import chalk13 from "chalk";
1557
+ // packages/poe-code-config/src/state/index.ts
1558
+ import os2 from "node:os";
1003
1559
 
1004
- // packages/design-system/src/static/menu.ts
1005
- import chalk16 from "chalk";
1560
+ // packages/poe-code-config/src/state/jobs.ts
1561
+ import path10 from "node:path";
1006
1562
 
1007
- // packages/agent-spawn/src/autonomous.ts
1008
- var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
1563
+ // packages/poe-code-config/src/state/fs.ts
1564
+ import * as nodeFs2 from "node:fs/promises";
1009
1565
 
1010
- // packages/agent-spawn/src/acp/replay.ts
1011
- import path2 from "node:path";
1012
- import { homedir as homedir2 } from "node:os";
1013
- import { open, readdir } from "node:fs/promises";
1014
- import { createInterface } from "node:readline";
1566
+ // packages/poe-code-config/src/state/templates.ts
1567
+ import path11 from "node:path";
1015
1568
 
1016
- // packages/poe-acp-client/src/acp-client.ts
1017
- import { isAbsolute } from "node:path";
1569
+ // packages/agent-harness-tools/src/execution-env.ts
1570
+ var executionEnvFactories = /* @__PURE__ */ new Map();
1571
+ function registerExecutionEnvFactory(factory) {
1572
+ executionEnvFactories.set(factory.type, factory);
1573
+ }
1018
1574
 
1019
- // packages/poe-acp-client/src/acp-transport.ts
1020
- import {
1021
- spawn as spawnChildProcess3
1022
- } from "node:child_process";
1575
+ // packages/agent-harness-tools/src/workspace-transfer.ts
1576
+ import { createHash } from "node:crypto";
1577
+ import { promises as nodeFs3 } from "node:fs";
1578
+ import path12 from "node:path";
1023
1579
 
1024
- // packages/poe-acp-client/src/run-report.ts
1025
- import * as fsPromises from "node:fs/promises";
1026
- import { homedir } from "node:os";
1027
- import { join } from "node:path";
1580
+ // packages/process-runner/src/docker/context.ts
1581
+ import { execSync } from "node:child_process";
1582
+ function detectContext() {
1583
+ try {
1584
+ const output = execSync("colima list --json", {
1585
+ encoding: "utf-8",
1586
+ stdio: ["pipe", "pipe", "ignore"]
1587
+ });
1588
+ const lines = output.trim().split("\n").filter(Boolean);
1589
+ for (const line of lines) {
1590
+ const profile = JSON.parse(line);
1591
+ if (profile.status === "Running" && profile.runtime === "docker") {
1592
+ const name = profile.name ?? profile.profile;
1593
+ if (!name) {
1594
+ continue;
1595
+ }
1596
+ return name === "default" ? "colima" : `colima-${name}`;
1597
+ }
1598
+ }
1599
+ } catch {
1600
+ return null;
1601
+ }
1602
+ return null;
1603
+ }
1604
+ function buildContextArgs(engine, context) {
1605
+ if (engine === "docker" && context) {
1606
+ return ["--context", context];
1607
+ }
1608
+ return [];
1609
+ }
1028
1610
 
1029
- // packages/agent-spawn/src/acp/spawn.ts
1030
- import { spawn as spawnChildProcess4 } from "node:child_process";
1611
+ // packages/process-runner/src/docker/engine.ts
1612
+ import { execSync as execSync2 } from "node:child_process";
1613
+ function detectEngine() {
1614
+ if (isEngineAvailable("docker")) {
1615
+ return "docker";
1616
+ }
1617
+ if (isEngineAvailable("podman")) {
1618
+ return "podman";
1619
+ }
1620
+ throw new Error(
1621
+ "No container engine found. Please install Docker or Podman:\n - Docker Desktop: https://www.docker.com/products/docker-desktop\n - Colima (macOS): brew install colima && colima start\n - Podman: https://podman.io/docs/installation"
1622
+ );
1623
+ }
1624
+ function isEngineAvailable(engine) {
1625
+ try {
1626
+ execSync2(`${engine} --version`, {
1627
+ stdio: "ignore"
1628
+ });
1629
+ return true;
1630
+ } catch {
1631
+ return false;
1632
+ }
1633
+ }
1031
1634
 
1032
- // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
1033
- import path3 from "node:path";
1034
- import { homedir as homedir3 } from "node:os";
1035
- import { mkdir, open as open2 } from "node:fs/promises";
1635
+ // packages/process-runner/src/docker/docker-runner.ts
1636
+ import * as childProcess from "node:child_process";
1637
+ import { randomBytes as randomBytes2 } from "node:crypto";
1036
1638
 
1037
- // src/utils/command-checks.ts
1038
- function formatCommandRunnerResult(result) {
1039
- const stdout = result.stdout.length > 0 ? result.stdout : "<empty>";
1040
- const stderr = result.stderr.length > 0 ? result.stderr : "<empty>";
1041
- return `stdout:
1042
- ${stdout}
1043
- stderr:
1044
- ${stderr}`;
1639
+ // packages/process-runner/src/docker/args.ts
1640
+ import path13 from "node:path";
1641
+ function buildDockerRunArgs(input) {
1642
+ const args = [input.engine];
1643
+ if (input.engine === "docker" && input.context) {
1644
+ args.push("--context", input.context);
1645
+ }
1646
+ args.push("run");
1647
+ if (input.rm) {
1648
+ args.push("--rm");
1649
+ }
1650
+ if (input.detached) {
1651
+ args.push("-d");
1652
+ }
1653
+ if (input.interactive) {
1654
+ args.push("-i");
1655
+ }
1656
+ if (input.tty) {
1657
+ args.push("-t");
1658
+ }
1659
+ args.push("--name", input.containerName);
1660
+ if (input.cwd !== void 0) {
1661
+ args.push("-w", input.cwd);
1662
+ }
1663
+ for (const [key, value] of Object.entries(input.env ?? {})) {
1664
+ args.push("-e", `${key}=${value}`);
1665
+ }
1666
+ for (const mount of input.mounts) {
1667
+ const volume = `${path13.resolve(mount.source)}:${mount.target}${mount.readonly ? ":ro" : ""}`;
1668
+ args.push("-v", volume);
1669
+ }
1670
+ for (const port of input.ports) {
1671
+ const mapping = `${port.host}:${port.container}${port.protocol === void 0 || port.protocol === "tcp" ? "" : `/${port.protocol}`}`;
1672
+ args.push("-p", mapping);
1673
+ }
1674
+ if (input.network !== void 0) {
1675
+ args.push("--network", input.network);
1676
+ }
1677
+ args.push(...input.extraArgs, input.image, input.command, ...input.args);
1678
+ return args;
1045
1679
  }
1046
- function createSpawnHealthCheck(agentId, options) {
1047
- const prompt = `Output exactly: ${options.expectedOutput}`;
1048
- const { binaryName, args, env: modeEnv } = buildSpawnArgs(agentId, {
1049
- prompt,
1050
- model: options.model,
1051
- mode: "yolo"
1052
- });
1680
+
1681
+ // packages/process-runner/src/docker/docker-execution-env.ts
1682
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "node:crypto";
1683
+ import { mkdtempSync, rmSync } from "node:fs";
1684
+ import { readFile as readFile2 } from "node:fs/promises";
1685
+ import { tmpdir } from "node:os";
1686
+ import path14 from "node:path";
1687
+
1688
+ // packages/process-runner/src/host/host-runner.ts
1689
+ import { spawn as spawnChildProcess } from "node:child_process";
1690
+ function createHostRunner(options = {}) {
1691
+ const detached = options.detached === true;
1053
1692
  return {
1054
- id: `${agentId}-cli-health`,
1055
- description: `spawn ${agentId} (expecting "${options.expectedOutput}")`,
1056
- async run(context) {
1057
- if (context.isDryRun) {
1058
- context.logDryRun?.(
1059
- `Dry run: ${[binaryName, ...args].join(" ")} (expecting "${options.expectedOutput}")`
1060
- );
1061
- return;
1062
- }
1063
- const result = modeEnv ? await context.runCommand(binaryName, args, { env: modeEnv }) : await context.runCommand(binaryName, args);
1064
- if (result.exitCode !== 0) {
1065
- throw new Error(
1066
- `spawn ${agentId} failed with exit code ${result.exitCode}.
1067
- ${formatCommandRunnerResult(result)}`
1068
- );
1069
- }
1070
- if (!result.stdout.includes(options.expectedOutput)) {
1071
- throw new Error(
1072
- `spawn ${agentId}: expected "${options.expectedOutput}" in stdout.
1073
- ${formatCommandRunnerResult(result)}`
1074
- );
1693
+ name: "host",
1694
+ exec(spec) {
1695
+ const stdinMode = spec.stdin ?? "ignore";
1696
+ const stdoutMode = spec.stdout ?? "pipe";
1697
+ const stderrMode = spec.stderr ?? "pipe";
1698
+ const stdio = stdinMode === "inherit" && stdoutMode === "inherit" && stderrMode === "inherit" ? "inherit" : [stdinMode, stdoutMode, stderrMode];
1699
+ const child = spawnChildProcess(spec.command, spec.args ?? [], {
1700
+ cwd: spec.cwd,
1701
+ env: spec.env,
1702
+ stdio,
1703
+ ...detached ? { detached: true } : {}
1704
+ });
1705
+ if (detached) {
1706
+ child.unref();
1075
1707
  }
1076
- }
1077
- };
1078
- }
1079
- function createBinaryExistsCheck(binaryName, id, description) {
1080
- return {
1081
- id,
1082
- description,
1083
- async run({ runCommand: runCommand2 }) {
1084
- const commonPaths = [
1085
- `/usr/local/bin/${binaryName}`,
1086
- `/usr/bin/${binaryName}`,
1087
- `$HOME/.local/bin/${binaryName}`,
1088
- `$HOME/.claude/local/bin/${binaryName}`
1089
- ];
1090
- const detectors = [
1091
- {
1092
- command: "which",
1093
- args: [binaryName],
1094
- validate: (result) => result.exitCode === 0
1095
- },
1096
- {
1097
- command: "where",
1098
- args: [binaryName],
1099
- validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
1100
- },
1101
- // Check common installation paths using shell expansion for $HOME
1102
- {
1103
- command: "sh",
1104
- args: [
1105
- "-c",
1106
- commonPaths.map((p) => `test -f "${p}"`).join(" || ")
1107
- ],
1108
- validate: (result) => result.exitCode === 0
1109
- }
1110
- ];
1111
- for (const detector of detectors) {
1112
- const result = await runCommand2(detector.command, detector.args);
1113
- if (detector.validate(result)) {
1708
+ const kill = (signal) => {
1709
+ if (detached && process.platform !== "win32" && child.pid !== void 0) {
1710
+ process.kill(-child.pid, signal);
1114
1711
  return;
1115
1712
  }
1116
- }
1117
- throw new Error(`${binaryName} CLI binary not found on PATH.`);
1713
+ child.kill(signal);
1714
+ };
1715
+ let settled = false;
1716
+ let resolveResult = null;
1717
+ const result = new Promise((resolve2) => {
1718
+ resolveResult = resolve2;
1719
+ });
1720
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
1721
+ kill("SIGTERM");
1722
+ });
1723
+ child.once("close", (code) => {
1724
+ if (settled) return;
1725
+ settled = true;
1726
+ cleanupAbort();
1727
+ resolveResult?.({ exitCode: code ?? 1 });
1728
+ });
1729
+ child.once("error", () => {
1730
+ if (settled) return;
1731
+ settled = true;
1732
+ cleanupAbort();
1733
+ resolveResult?.({ exitCode: 1 });
1734
+ });
1735
+ return {
1736
+ pid: child.pid ?? null,
1737
+ stdin: child.stdin,
1738
+ stdout: child.stdout,
1739
+ stderr: child.stderr,
1740
+ result,
1741
+ kill
1742
+ };
1118
1743
  }
1119
1744
  };
1120
1745
  }
1121
-
1122
- // packages/config-mutations/src/mutations/config-mutation.ts
1123
- function merge(options) {
1124
- return {
1125
- kind: "configMerge",
1126
- target: options.target,
1127
- value: options.value,
1128
- format: options.format,
1129
- pruneByPrefix: options.pruneByPrefix,
1130
- label: options.label
1131
- };
1132
- }
1133
- function prune(options) {
1134
- return {
1135
- kind: "configPrune",
1136
- target: options.target,
1137
- shape: options.shape,
1138
- format: options.format,
1139
- onlyIf: options.onlyIf,
1140
- label: options.label
1141
- };
1142
- }
1143
- function transform(options) {
1144
- return {
1145
- kind: "configTransform",
1146
- target: options.target,
1147
- format: options.format,
1148
- transform: options.transform,
1149
- label: options.label
1746
+ function bindAbortSignal(signal, onAbort) {
1747
+ if (signal === void 0) {
1748
+ return () => {
1749
+ };
1750
+ }
1751
+ if (signal.aborted) {
1752
+ onAbort();
1753
+ return () => {
1754
+ };
1755
+ }
1756
+ signal.addEventListener("abort", onAbort, { once: true });
1757
+ return () => {
1758
+ signal.removeEventListener("abort", onAbort);
1150
1759
  };
1151
1760
  }
1152
- var configMutation = {
1153
- merge,
1154
- prune,
1155
- transform
1156
- };
1157
1761
 
1158
- // packages/config-mutations/src/mutations/file-mutation.ts
1159
- function ensureDirectory(options) {
1762
+ // packages/process-runner/src/docker/docker-execution-env.ts
1763
+ var containerCommand = ["sh", "-c", "while :; do sleep 3600; done"];
1764
+ var dockerExecutionEnvFactory = {
1765
+ type: "docker",
1766
+ supportsDetach: true,
1767
+ async open(spec) {
1768
+ const runtime = parseDockerRuntime(spec.runtime);
1769
+ const runner = spec.hostRunner ?? createHostRunner();
1770
+ const engine = runtime.engine ?? detectEngine();
1771
+ const context = detectContext();
1772
+ const image = await resolveImage({
1773
+ spec,
1774
+ runtime,
1775
+ runner,
1776
+ engine,
1777
+ context
1778
+ });
1779
+ const containerName = createContainerName();
1780
+ const runArgs = buildDockerRunArgs({
1781
+ engine,
1782
+ context,
1783
+ image,
1784
+ command: containerCommand[0],
1785
+ args: containerCommand.slice(1),
1786
+ cwd: void 0,
1787
+ env: void 0,
1788
+ mounts: runtime.mounts ?? [],
1789
+ ports: [],
1790
+ network: runtime.network,
1791
+ containerName,
1792
+ detached: true,
1793
+ interactive: true,
1794
+ tty: false,
1795
+ rm: false,
1796
+ extraArgs: runtime.extra_args ?? []
1797
+ });
1798
+ const [command, ...args] = runArgs;
1799
+ const id = (await runAndRead(runner, { command, args, stdout: "pipe", stderr: "pipe" })).trim();
1800
+ return createDockerEnv({
1801
+ id,
1802
+ spec,
1803
+ runner,
1804
+ engine,
1805
+ context
1806
+ });
1807
+ },
1808
+ async attach(envId) {
1809
+ const engine = detectEngine();
1810
+ return createDockerEnv({
1811
+ id: envId,
1812
+ spec: createAttachedSpec(),
1813
+ runner: createHostRunner(),
1814
+ engine,
1815
+ context: detectContext()
1816
+ });
1817
+ }
1818
+ };
1819
+ function createDockerEnv(input) {
1820
+ const containerRef = input.id;
1160
1821
  return {
1161
- kind: "ensureDirectory",
1162
- path: options.path,
1163
- label: options.label
1822
+ id: containerRef,
1823
+ job: null,
1824
+ async uploadWorkspace() {
1825
+ const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-upload-"));
1826
+ const archivePath = path14.join(tempDir, "workspace.tar");
1827
+ try {
1828
+ const excludeArgs = input.spec.uploadIgnoreFiles.flatMap((ignored) => [
1829
+ "--exclude",
1830
+ ignored
1831
+ ]);
1832
+ const tarArgs = [...excludeArgs, "-cf", archivePath, "-C", input.spec.cwd, "."];
1833
+ await runOrThrow(input.runner, {
1834
+ command: "tar",
1835
+ args: tarArgs,
1836
+ stdout: "pipe",
1837
+ stderr: "pipe"
1838
+ });
1839
+ await runOrThrow(input.runner, {
1840
+ command: input.engine,
1841
+ args: [
1842
+ ...buildContextArgs(input.engine, input.context),
1843
+ "cp",
1844
+ archivePath,
1845
+ `${containerRef}:/tmp/poe-workspace-upload.tar`
1846
+ ],
1847
+ stdout: "pipe",
1848
+ stderr: "pipe"
1849
+ });
1850
+ await runOrThrow(input.runner, {
1851
+ command: input.engine,
1852
+ args: [
1853
+ ...buildContextArgs(input.engine, input.context),
1854
+ "exec",
1855
+ containerRef,
1856
+ "sh",
1857
+ "-c",
1858
+ `mkdir -p ${shellQuote(input.spec.cwd)} && tar -xf /tmp/poe-workspace-upload.tar -C ${shellQuote(input.spec.cwd)}`
1859
+ ],
1860
+ stdout: "pipe",
1861
+ stderr: "pipe"
1862
+ });
1863
+ return { files: 0, bytes: 0, skipped: [] };
1864
+ } finally {
1865
+ rmSync(tempDir, { recursive: true, force: true });
1866
+ }
1867
+ },
1868
+ async downloadWorkspace(opts) {
1869
+ const tempDir = mkdtempSync(path14.join(tmpdir(), "poe-docker-download-"));
1870
+ const archivePath = path14.join(tempDir, "workspace.tar");
1871
+ try {
1872
+ await runOrThrow(input.runner, {
1873
+ command: input.engine,
1874
+ args: [
1875
+ ...buildContextArgs(input.engine, input.context),
1876
+ "exec",
1877
+ containerRef,
1878
+ "sh",
1879
+ "-c",
1880
+ `tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote(input.spec.cwd)} .`
1881
+ ],
1882
+ stdout: "pipe",
1883
+ stderr: "pipe"
1884
+ });
1885
+ await runOrThrow(input.runner, {
1886
+ command: input.engine,
1887
+ args: [
1888
+ ...buildContextArgs(input.engine, input.context),
1889
+ "cp",
1890
+ `${containerRef}:/tmp/poe-workspace-download.tar`,
1891
+ archivePath
1892
+ ],
1893
+ stdout: "pipe",
1894
+ stderr: "pipe"
1895
+ });
1896
+ const extractMode = opts.conflictPolicy === "refuse" ? "-xkf" : "-xf";
1897
+ await runOrThrow(input.runner, {
1898
+ command: "tar",
1899
+ args: [extractMode, archivePath, "-C", input.spec.cwd],
1900
+ stdout: "pipe",
1901
+ stderr: "pipe"
1902
+ });
1903
+ return { files: 0, bytes: 0, conflicts: [] };
1904
+ } finally {
1905
+ rmSync(tempDir, { recursive: true, force: true });
1906
+ }
1907
+ },
1908
+ exec(spec) {
1909
+ return input.runner.exec({
1910
+ command: input.engine,
1911
+ args: [
1912
+ ...buildContextArgs(input.engine, input.context),
1913
+ "exec",
1914
+ ...spec.stdin === "pipe" || spec.stdin === "inherit" ? ["-i"] : [],
1915
+ ...spec.tty === true ? ["-t"] : [],
1916
+ ...spec.cwd !== void 0 ? ["-w", spec.cwd] : [],
1917
+ ...buildEnvArgs(spec.env),
1918
+ containerRef,
1919
+ spec.command,
1920
+ ...spec.args ?? []
1921
+ ],
1922
+ stdin: spec.stdin,
1923
+ stdout: spec.stdout,
1924
+ stderr: spec.stderr,
1925
+ tty: spec.tty
1926
+ });
1927
+ },
1928
+ async detach() {
1929
+ return createContainerJob(containerRef, input.runner, input.engine, input.context);
1930
+ },
1931
+ shell() {
1932
+ const shellSpec = input.spec.shellSpec;
1933
+ return this.exec({
1934
+ command: shellSpec?.command ?? input.spec.env.SHELL ?? "sh",
1935
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
1936
+ cwd: input.spec.cwd,
1937
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env,
1938
+ stdin: "inherit",
1939
+ stdout: "inherit",
1940
+ stderr: "inherit",
1941
+ tty: true
1942
+ });
1943
+ },
1944
+ async close() {
1945
+ await runOrThrow(input.runner, {
1946
+ command: input.engine,
1947
+ args: [...buildContextArgs(input.engine, input.context), "rm", "-f", containerRef],
1948
+ stdout: "pipe",
1949
+ stderr: "pipe"
1950
+ });
1951
+ }
1164
1952
  };
1165
1953
  }
1166
- function remove(options) {
1167
- return {
1168
- kind: "removeFile",
1169
- target: options.target,
1170
- whenEmpty: options.whenEmpty,
1171
- whenContentMatches: options.whenContentMatches,
1172
- label: options.label
1173
- };
1954
+ async function resolveImage(input) {
1955
+ if (input.runtime.image !== void 0) {
1956
+ return input.runtime.image;
1957
+ }
1958
+ const dockerfilePath = path14.resolve(
1959
+ input.spec.cwd,
1960
+ input.runtime.dockerfile ?? path14.join(".poe-code", "Dockerfile")
1961
+ );
1962
+ const buildContext = path14.resolve(input.spec.cwd, input.runtime.build_context ?? ".");
1963
+ const dockerfileBytes = await readFile2(dockerfilePath);
1964
+ const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
1965
+ const cached2 = await input.spec.state?.templates.get("docker", hash);
1966
+ if (cached2?.image !== void 0) {
1967
+ return cached2.image;
1968
+ }
1969
+ const image = `poe-code/local:${hash}`;
1970
+ await buildImage({
1971
+ runner: input.runner,
1972
+ engine: input.engine,
1973
+ context: input.context,
1974
+ image,
1975
+ dockerfilePath,
1976
+ buildContext,
1977
+ buildArgs: input.runtime.build_args ?? {}
1978
+ });
1979
+ await input.spec.state?.templates.put("docker", {
1980
+ hash,
1981
+ image,
1982
+ runtime_type: "docker",
1983
+ dockerfile_path: dockerfilePath,
1984
+ built_at: (/* @__PURE__ */ new Date()).toISOString()
1985
+ });
1986
+ return image;
1987
+ }
1988
+ function hashDockerTemplate(dockerfileBytes, buildArgs) {
1989
+ const hash = createHash2("sha256");
1990
+ hash.update(dockerfileBytes);
1991
+ hash.update("\0");
1992
+ for (const [key, value] of sortedBuildArgs(buildArgs)) {
1993
+ hash.update(key);
1994
+ hash.update("=");
1995
+ hash.update(value);
1996
+ hash.update("\0");
1997
+ }
1998
+ return hash.digest("hex");
1999
+ }
2000
+ async function buildImage(input) {
2001
+ await runOrThrow(input.runner, {
2002
+ command: input.engine,
2003
+ args: [
2004
+ ...buildContextArgs(input.engine, input.context),
2005
+ "build",
2006
+ "--tag",
2007
+ input.image,
2008
+ "-f",
2009
+ input.dockerfilePath,
2010
+ ...sortedBuildArgs(input.buildArgs).flatMap(([key, value]) => [
2011
+ "--build-arg",
2012
+ `${key}=${value}`
2013
+ ]),
2014
+ input.buildContext
2015
+ ],
2016
+ stdout: "pipe",
2017
+ stderr: "pipe"
2018
+ });
1174
2019
  }
1175
- function removeDirectory(options) {
1176
- return {
1177
- kind: "removeDirectory",
1178
- path: options.path,
1179
- force: options.force,
1180
- label: options.label
1181
- };
2020
+ function parseDockerRuntime(runtime) {
2021
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
2022
+ throw new Error("docker runtime must be an object");
2023
+ }
2024
+ const record = runtime;
2025
+ if (record.type !== "docker") {
2026
+ throw new Error('docker runtime type must be "docker"');
2027
+ }
2028
+ return record;
1182
2029
  }
1183
- function chmod(options) {
2030
+ async function runAndRead(runner, spec) {
2031
+ const handle = runner.exec(spec);
2032
+ const stdout = readStream(handle.stdout);
2033
+ const stderr = readStream(handle.stderr);
2034
+ const result = await handle.result;
2035
+ const output = await stdout;
2036
+ if (result.exitCode !== 0) {
2037
+ const errorOutput = await stderr;
2038
+ throw new Error(
2039
+ `Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}${errorOutput ? `
2040
+ ${errorOutput}` : ""}`
2041
+ );
2042
+ }
2043
+ return output;
2044
+ }
2045
+ async function runOrThrow(runner, spec) {
2046
+ await runAndRead(runner, spec);
2047
+ }
2048
+ async function readStream(stream) {
2049
+ if (stream === null) {
2050
+ return "";
2051
+ }
2052
+ stream.setEncoding("utf8");
2053
+ const chunks = [];
2054
+ for await (const chunk of stream) {
2055
+ chunks.push(String(chunk));
2056
+ }
2057
+ return chunks.join("");
2058
+ }
2059
+ function sortedBuildArgs(buildArgs) {
2060
+ return Object.entries(buildArgs).sort(([left], [right]) => left.localeCompare(right));
2061
+ }
2062
+ function buildEnvArgs(env) {
2063
+ if (env === void 0) {
2064
+ return [];
2065
+ }
2066
+ return Object.entries(env).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
2067
+ }
2068
+ function createContainerName() {
2069
+ return `poe-env-${randomBytes3(6).toString("hex")}`;
2070
+ }
2071
+ async function createContainerJob(containerId, runner, engine, context) {
1184
2072
  return {
1185
- kind: "chmod",
1186
- target: options.target,
1187
- mode: options.mode,
1188
- label: options.label
2073
+ id: containerId,
2074
+ envId: containerId,
2075
+ tool: "docker",
2076
+ argv: ["attach", containerId],
2077
+ async status() {
2078
+ const handle = runner.exec({
2079
+ command: engine,
2080
+ args: [
2081
+ ...buildContextArgs(engine, context),
2082
+ "inspect",
2083
+ "-f",
2084
+ "{{.State.Status}}",
2085
+ containerId
2086
+ ],
2087
+ stdout: "pipe",
2088
+ stderr: "pipe"
2089
+ });
2090
+ const stdout = await readStream(handle.stdout);
2091
+ const result = await handle.result;
2092
+ if (result.exitCode !== 0) {
2093
+ return "lost";
2094
+ }
2095
+ return stdout.trim() === "running" ? "running" : "exited";
2096
+ },
2097
+ async *stream() {
2098
+ },
2099
+ async wait() {
2100
+ const handle = runner.exec({
2101
+ command: engine,
2102
+ args: [...buildContextArgs(engine, context), "wait", containerId],
2103
+ stdout: "pipe",
2104
+ stderr: "pipe"
2105
+ });
2106
+ const stdout = await readStream(handle.stdout);
2107
+ const result = await handle.result;
2108
+ return { exitCode: Number.parseInt(stdout.trim(), 10) || result.exitCode };
2109
+ },
2110
+ async kill(signal) {
2111
+ const args = signal === void 0 || signal === "SIGTERM" ? ["stop", containerId] : ["kill", ...signal === "SIGKILL" ? [] : [`--signal=${signal}`], containerId];
2112
+ await runOrThrow(runner, {
2113
+ command: engine,
2114
+ args: [...buildContextArgs(engine, context), ...args],
2115
+ stdout: "pipe",
2116
+ stderr: "pipe"
2117
+ });
2118
+ }
1189
2119
  };
1190
2120
  }
1191
- function backup(options) {
2121
+ function createAttachedSpec() {
1192
2122
  return {
1193
- kind: "backup",
1194
- target: options.target,
1195
- label: options.label
2123
+ cwd: "/workspace",
2124
+ runtime: {
2125
+ type: "docker",
2126
+ image: "attached",
2127
+ build_args: {},
2128
+ mounts: []
2129
+ },
2130
+ env: {},
2131
+ uploadIgnoreFiles: [],
2132
+ jobLabel: {
2133
+ tool: "docker",
2134
+ argv: []
2135
+ }
1196
2136
  };
1197
2137
  }
1198
- var fileMutation = {
1199
- ensureDirectory,
1200
- remove,
1201
- removeDirectory,
1202
- chmod,
1203
- backup
2138
+ function shellQuote(value) {
2139
+ return `'${value.replaceAll("'", "'\\''")}'`;
2140
+ }
2141
+
2142
+ // packages/process-runner/src/host/host-execution-env.ts
2143
+ var hostExecutionEnvFactory = {
2144
+ type: "host",
2145
+ supportsDetach: false,
2146
+ async open(openSpec) {
2147
+ return {
2148
+ id: "host",
2149
+ job: null,
2150
+ async uploadWorkspace() {
2151
+ return {
2152
+ files: 0,
2153
+ bytes: 0,
2154
+ skipped: []
2155
+ };
2156
+ },
2157
+ async downloadWorkspace() {
2158
+ return {
2159
+ files: 0,
2160
+ bytes: 0,
2161
+ conflicts: []
2162
+ };
2163
+ },
2164
+ exec(spec) {
2165
+ return createHostRunner().exec(spec);
2166
+ },
2167
+ async detach() {
2168
+ throw new Error("host runtime does not support detach because host has no addressable env");
2169
+ },
2170
+ shell() {
2171
+ const shellSpec = openSpec.shellSpec;
2172
+ return createHostRunner().exec({
2173
+ command: shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
2174
+ ...shellSpec?.args ? { args: shellSpec.args } : {},
2175
+ cwd: openSpec.cwd,
2176
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : openSpec.env,
2177
+ stdin: "inherit",
2178
+ stdout: "inherit",
2179
+ stderr: "inherit",
2180
+ tty: true
2181
+ });
2182
+ },
2183
+ async close() {
2184
+ }
2185
+ };
2186
+ },
2187
+ async attach() {
2188
+ throw new Error("host runtime does not support reattach");
2189
+ }
1204
2190
  };
1205
2191
 
1206
- // packages/config-mutations/src/mutations/template-mutation.ts
1207
- function write(options) {
1208
- return {
1209
- kind: "templateWrite",
1210
- target: options.target,
1211
- templateId: options.templateId,
1212
- context: options.context,
1213
- label: options.label
1214
- };
2192
+ // packages/process-runner/src/testing/mock-runner.ts
2193
+ import { Readable, Writable } from "node:stream";
2194
+
2195
+ // packages/agent-spawn/src/register-factories.ts
2196
+ registerExecutionEnvFactory(hostExecutionEnvFactory);
2197
+ registerExecutionEnvFactory(dockerExecutionEnvFactory);
2198
+ if (process.env.VITEST === "true") {
2199
+ registerExecutionEnvFactory(createTestHostExecutionEnvFactory());
1215
2200
  }
1216
- function mergeToml(options) {
2201
+ function createTestHostExecutionEnvFactory() {
1217
2202
  return {
1218
- kind: "templateMergeToml",
1219
- target: options.target,
1220
- templateId: options.templateId,
1221
- context: options.context,
1222
- label: options.label
2203
+ type: "host",
2204
+ supportsDetach: false,
2205
+ open: ((openSpec) => {
2206
+ return {
2207
+ id: "host",
2208
+ job: null,
2209
+ async uploadWorkspace() {
2210
+ return { files: 0, bytes: 0, skipped: [] };
2211
+ },
2212
+ async downloadWorkspace() {
2213
+ return { files: 0, bytes: 0, conflicts: [] };
2214
+ },
2215
+ exec(spec) {
2216
+ return runHost(spawnChildProcess2, spec);
2217
+ },
2218
+ async detach() {
2219
+ throw new Error(
2220
+ "host runtime does not support detach because host has no addressable env"
2221
+ );
2222
+ },
2223
+ shell() {
2224
+ return runHost(spawnChildProcess2, {
2225
+ command: openSpec.shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
2226
+ args: openSpec.shellSpec?.args,
2227
+ cwd: openSpec.cwd,
2228
+ env: openSpec.shellSpec && "env" in openSpec.shellSpec ? openSpec.shellSpec.env : openSpec.env,
2229
+ stdin: "inherit",
2230
+ stdout: "inherit",
2231
+ stderr: "inherit",
2232
+ tty: true
2233
+ });
2234
+ },
2235
+ async close() {
2236
+ }
2237
+ };
2238
+ }),
2239
+ async attach() {
2240
+ throw new Error("host runtime does not support reattach");
2241
+ }
1223
2242
  };
1224
2243
  }
1225
- function mergeJson(options) {
2244
+ function runHost(spawnProcess, spec) {
2245
+ const stdin = spec.stdin ?? "ignore";
2246
+ const stdout = spec.stdout ?? "pipe";
2247
+ const stderr = spec.stderr ?? "pipe";
2248
+ const stdio = stdin === "inherit" && stdout === "inherit" && stderr === "inherit" ? "inherit" : [stdin, stdout, stderr];
2249
+ const child = spawnProcess(spec.command, spec.args ?? [], {
2250
+ cwd: spec.cwd,
2251
+ env: spec.env,
2252
+ stdio
2253
+ });
2254
+ const result = new Promise((resolve2) => {
2255
+ child.once("close", (code) => {
2256
+ resolve2({ exitCode: code ?? 1 });
2257
+ });
2258
+ child.once("error", () => {
2259
+ resolve2({ exitCode: 1 });
2260
+ });
2261
+ });
2262
+ const kill = (signal) => {
2263
+ child.kill(signal);
2264
+ };
2265
+ if (spec.signal?.aborted) {
2266
+ kill("SIGTERM");
2267
+ } else {
2268
+ spec.signal?.addEventListener("abort", () => kill("SIGTERM"), { once: true });
2269
+ }
1226
2270
  return {
1227
- kind: "templateMergeJson",
1228
- target: options.target,
1229
- templateId: options.templateId,
1230
- context: options.context,
1231
- label: options.label
2271
+ pid: child.pid ?? null,
2272
+ stdin: child.stdin,
2273
+ stdout: child.stdout,
2274
+ stderr: child.stderr,
2275
+ result,
2276
+ kill
1232
2277
  };
1233
2278
  }
1234
- var templateMutation = {
1235
- write,
1236
- mergeToml,
1237
- mergeJson
1238
- };
1239
2279
 
1240
- // packages/config-mutations/src/execution/apply-mutation.ts
1241
- import Mustache from "mustache";
2280
+ // packages/agent-spawn/src/run-command.ts
2281
+ import { spawn as spawn2 } from "node:child_process";
1242
2282
 
1243
- // packages/config-mutations/src/formats/json.ts
1244
- import * as jsonc from "jsonc-parser";
1245
- function isConfigObject(value) {
1246
- return typeof value === "object" && value !== null && !Array.isArray(value);
1247
- }
1248
- function parse3(content) {
1249
- if (!content || content.trim() === "") {
1250
- return {};
1251
- }
1252
- const errors = [];
1253
- const parsed = jsonc.parse(content, errors, {
1254
- allowTrailingComma: true,
1255
- disallowComments: false
1256
- });
1257
- if (errors.length > 0) {
1258
- throw new Error(`JSON parse error: ${jsonc.printParseErrorCode(errors[0].error)}`);
1259
- }
1260
- if (parsed === null || parsed === void 0) {
1261
- return {};
1262
- }
1263
- if (!isConfigObject(parsed)) {
1264
- throw new Error("Expected JSON object.");
1265
- }
1266
- return parsed;
1267
- }
1268
- function serialize(obj) {
1269
- return `${JSON.stringify(obj, null, 2)}
1270
- `;
1271
- }
1272
- function merge2(base, patch) {
1273
- const result = { ...base };
1274
- for (const [key, value] of Object.entries(patch)) {
1275
- if (value === void 0) {
1276
- continue;
1277
- }
1278
- const existing = result[key];
1279
- if (isConfigObject(existing) && isConfigObject(value)) {
1280
- result[key] = merge2(existing, value);
1281
- continue;
1282
- }
1283
- result[key] = value;
2283
+ // packages/agent-spawn/src/types.ts
2284
+ function resolveModeConfig(modeConfig) {
2285
+ if (Array.isArray(modeConfig)) {
2286
+ return { args: modeConfig };
1284
2287
  }
1285
- return result;
2288
+ return {
2289
+ args: modeConfig.args ?? [],
2290
+ env: modeConfig.env && Object.keys(modeConfig.env).length > 0 ? modeConfig.env : void 0
2291
+ };
1286
2292
  }
1287
- function prune2(obj, shape) {
1288
- let changed = false;
1289
- const result = { ...obj };
1290
- for (const [key, pattern] of Object.entries(shape)) {
1291
- if (!(key in result)) {
1292
- continue;
2293
+
2294
+ // packages/agent-spawn/src/configs/mcp.ts
2295
+ function toJsonMcpServers(servers) {
2296
+ const out = {};
2297
+ for (const [name, server] of Object.entries(servers)) {
2298
+ const mapped = { command: server.command };
2299
+ if (server.args && server.args.length > 0) {
2300
+ mapped.args = server.args;
1293
2301
  }
1294
- const current = result[key];
1295
- if (isConfigObject(pattern) && Object.keys(pattern).length === 0) {
1296
- delete result[key];
1297
- changed = true;
1298
- continue;
2302
+ if (server.env && Object.keys(server.env).length > 0) {
2303
+ mapped.env = server.env;
1299
2304
  }
1300
- if (isConfigObject(pattern) && isConfigObject(current)) {
1301
- const { changed: childChanged, result: childResult } = prune2(
1302
- current,
1303
- pattern
1304
- );
1305
- if (childChanged) {
1306
- changed = true;
1307
- }
1308
- if (Object.keys(childResult).length === 0) {
1309
- delete result[key];
1310
- } else {
1311
- result[key] = childResult;
1312
- }
1313
- continue;
2305
+ if (server.timeout !== void 0) {
2306
+ mapped.timeout = server.timeout;
1314
2307
  }
1315
- delete result[key];
1316
- changed = true;
2308
+ out[name] = mapped;
1317
2309
  }
1318
- return { changed, result };
2310
+ return out;
1319
2311
  }
1320
- var jsonFormat = {
1321
- parse: parse3,
1322
- serialize,
1323
- merge: merge2,
1324
- prune: prune2
1325
- };
1326
-
1327
- // packages/config-mutations/src/formats/toml.ts
1328
- import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
1329
- function isConfigObject2(value) {
1330
- return typeof value === "object" && value !== null && !Array.isArray(value);
2312
+ function toTomlString(value) {
2313
+ return JSON.stringify(value);
1331
2314
  }
1332
- function parse4(content) {
1333
- if (!content || content.trim() === "") {
1334
- return {};
1335
- }
1336
- const parsed = parseToml(content);
1337
- if (!isConfigObject2(parsed)) {
1338
- throw new Error("Expected TOML document to be a table.");
2315
+ function toTomlArray(values) {
2316
+ const serialized = values.map((value) => toTomlString(value));
2317
+ return `[${serialized.join(", ")}]`;
2318
+ }
2319
+ function toTomlInlineTable(values) {
2320
+ const parts = [];
2321
+ for (const [key, value] of Object.entries(values)) {
2322
+ parts.push(`${JSON.stringify(key)}=${toTomlString(value)}`);
1339
2323
  }
1340
- return parsed;
2324
+ return `{${parts.join(", ")}}`;
1341
2325
  }
1342
- function serialize2(obj) {
1343
- const serialized = stringifyToml(obj);
1344
- return serialized.endsWith("\n") ? serialized : `${serialized}
1345
- `;
2326
+ function serializeJsonMcpArgs(servers) {
2327
+ return ["--mcp-config", JSON.stringify({ mcpServers: toJsonMcpServers(servers) })];
1346
2328
  }
1347
- function merge3(base, patch) {
1348
- const result = { ...base };
1349
- for (const [key, value] of Object.entries(patch)) {
1350
- if (value === void 0) {
1351
- continue;
1352
- }
1353
- const existing = result[key];
1354
- if (isConfigObject2(existing) && isConfigObject2(value)) {
1355
- result[key] = merge3(existing, value);
1356
- continue;
2329
+ function serializeOpenCodeMcpEnv(servers) {
2330
+ const mcp = {};
2331
+ for (const [name, server] of Object.entries(servers)) {
2332
+ const entry = { type: "local", command: [server.command, ...server.args ?? []] };
2333
+ if (server.env && Object.keys(server.env).length > 0) {
2334
+ entry.environment = server.env;
1357
2335
  }
1358
- result[key] = value;
2336
+ mcp[name] = entry;
1359
2337
  }
1360
- return result;
2338
+ return { OPENCODE_CONFIG_CONTENT: JSON.stringify({ mcp }) };
1361
2339
  }
1362
- function prune3(obj, shape) {
1363
- let changed = false;
1364
- const result = { ...obj };
1365
- for (const [key, pattern] of Object.entries(shape)) {
1366
- if (!(key in result)) {
1367
- continue;
2340
+ function serializeCodexMcpArgs(servers) {
2341
+ const args = [];
2342
+ for (const [name, server] of Object.entries(servers)) {
2343
+ const prefix = `mcp_servers.${name}`;
2344
+ args.push("-c", `${prefix}.command=${toTomlString(server.command)}`);
2345
+ if (server.args && server.args.length > 0) {
2346
+ args.push("-c", `${prefix}.args=${toTomlArray(server.args)}`);
1368
2347
  }
1369
- const current = result[key];
1370
- if (isConfigObject2(pattern) && Object.keys(pattern).length === 0) {
1371
- delete result[key];
1372
- changed = true;
1373
- continue;
2348
+ if (server.env && Object.keys(server.env).length > 0) {
2349
+ args.push("-c", `${prefix}.env=${toTomlInlineTable(server.env)}`);
1374
2350
  }
1375
- if (isConfigObject2(pattern) && isConfigObject2(current)) {
1376
- const { changed: childChanged, result: childResult } = prune3(
1377
- current,
1378
- pattern
1379
- );
1380
- if (childChanged) {
1381
- changed = true;
1382
- }
1383
- if (Object.keys(childResult).length === 0) {
1384
- delete result[key];
1385
- } else {
1386
- result[key] = childResult;
1387
- }
1388
- continue;
2351
+ if (server.timeout !== void 0) {
2352
+ args.push("-c", `${prefix}.timeout=${server.timeout}`);
1389
2353
  }
1390
- delete result[key];
1391
- changed = true;
1392
2354
  }
1393
- return { changed, result };
2355
+ return args;
1394
2356
  }
1395
- var tomlFormat = {
1396
- parse: parse4,
1397
- serialize: serialize2,
1398
- merge: merge3,
1399
- prune: prune3
2357
+ function serializeGooseMcpArgs(servers) {
2358
+ return Object.values(servers).flatMap((server) => [
2359
+ "--with-extension",
2360
+ [server.command, ...server.args ?? []].join(" ")
2361
+ ]);
2362
+ }
2363
+
2364
+ // packages/agent-spawn/src/configs/claude-code.ts
2365
+ var claudeCodeSpawnConfig = {
2366
+ kind: "cli",
2367
+ agentId: "claude-code",
2368
+ // ACP adapter support: yes (adapter: "claude")
2369
+ adapter: "claude",
2370
+ promptFlag: "-p",
2371
+ modelFlag: "--model",
2372
+ modelStripProviderPrefix: true,
2373
+ modelTransform: (model) => model.replaceAll(".", "-"),
2374
+ defaultArgs: [
2375
+ "--output-format",
2376
+ "stream-json",
2377
+ "--verbose"
2378
+ ],
2379
+ mcpArgs: serializeJsonMcpArgs,
2380
+ modes: {
2381
+ yolo: ["--dangerously-skip-permissions"],
2382
+ edit: ["--permission-mode", "acceptEdits", "--allowedTools", "Bash,Read,Write,Edit,Glob,Grep,NotebookEdit"],
2383
+ read: ["--permission-mode", "plan"]
2384
+ },
2385
+ stdinMode: {
2386
+ omitPrompt: true,
2387
+ extraArgs: ["--input-format", "text"]
2388
+ },
2389
+ interactive: {
2390
+ defaultArgs: []
2391
+ },
2392
+ resumeCommand: (threadId) => ["--resume", threadId]
2393
+ };
2394
+
2395
+ // packages/agent-spawn/src/configs/codex.ts
2396
+ var codexSpawnConfig = {
2397
+ kind: "cli",
2398
+ agentId: "codex",
2399
+ // ACP adapter support: yes (adapter: "codex")
2400
+ adapter: "codex",
2401
+ promptFlag: "exec",
2402
+ modelFlag: "--model",
2403
+ modelStripProviderPrefix: true,
2404
+ defaultArgs: ["--skip-git-repo-check", "--json"],
2405
+ mcpArgs: serializeCodexMcpArgs,
2406
+ mcpArgsBeforeCommand: true,
2407
+ modes: {
2408
+ yolo: ["-s", "danger-full-access"],
2409
+ edit: ["-s", "workspace-write"],
2410
+ read: ["-s", "read-only"]
2411
+ },
2412
+ stdinMode: {
2413
+ omitPrompt: true,
2414
+ extraArgs: ["-"]
2415
+ },
2416
+ interactive: {
2417
+ defaultArgs: ["-a", "never"]
2418
+ },
2419
+ resumeCommand: (threadId, cwd) => ["resume", "-C", cwd, threadId]
2420
+ };
2421
+
2422
+ // packages/agent-spawn/src/configs/opencode.ts
2423
+ var openCodeSpawnConfig = {
2424
+ kind: "cli",
2425
+ agentId: "opencode",
2426
+ // ACP adapter support: yes (adapter: "opencode").
2427
+ // OpenCode's `--format json` emits NDJSON events with `{ type, sessionID, part }`
2428
+ // (no `{ event, ... }` field), so it needs the OpenCode adapter (not "native").
2429
+ adapter: "opencode",
2430
+ promptFlag: "run",
2431
+ modelFlag: "--model",
2432
+ modelStripProviderPrefix: false,
2433
+ modelTransform: (model) => {
2434
+ return model.startsWith("poe/") ? model : `poe/${model}`;
2435
+ },
2436
+ defaultArgs: ["--format", "json"],
2437
+ modes: {
2438
+ yolo: [],
2439
+ edit: [],
2440
+ read: ["--agent", "plan"]
2441
+ },
2442
+ interactive: {
2443
+ defaultArgs: [],
2444
+ promptFlag: "--prompt"
2445
+ },
2446
+ resumeCommand: (threadId, cwd) => [cwd, "--session", threadId],
2447
+ mcpEnv: serializeOpenCodeMcpEnv
2448
+ };
2449
+ var openCodeAcpSpawnConfig = {
2450
+ kind: "acp",
2451
+ agentId: "opencode",
2452
+ acpArgs: ["acp"],
2453
+ skipAuth: true,
2454
+ mcpEnv: serializeOpenCodeMcpEnv
2455
+ };
2456
+
2457
+ // packages/agent-spawn/src/configs/kimi.ts
2458
+ var kimiSpawnConfig = {
2459
+ kind: "cli",
2460
+ agentId: "kimi",
2461
+ // ACP adapter support: yes (adapter: "kimi").
2462
+ // Kimi's `--output-format stream-json` emits OpenAI-style `{ role, content }` JSON
2463
+ // (no `{ event, ... }` field), so it needs the Kimi adapter (not "native").
2464
+ adapter: "kimi",
2465
+ promptFlag: "-p",
2466
+ modelStripProviderPrefix: true,
2467
+ defaultArgs: ["--print", "--output-format", "stream-json"],
2468
+ mcpArgs: serializeJsonMcpArgs,
2469
+ modes: {
2470
+ yolo: ["--yolo"],
2471
+ edit: [],
2472
+ read: []
2473
+ },
2474
+ stdinMode: {
2475
+ omitPrompt: true,
2476
+ extraArgs: ["--input-format", "stream-json"]
2477
+ },
2478
+ interactive: {
2479
+ defaultArgs: [],
2480
+ promptFlag: "-p"
2481
+ },
2482
+ resumeCommand: (threadId, cwd) => ["--session", threadId, "--work-dir", cwd]
2483
+ };
2484
+ var kimiAcpSpawnConfig = {
2485
+ kind: "acp",
2486
+ agentId: "kimi",
2487
+ acpArgs: ["acp"]
2488
+ };
2489
+
2490
+ // packages/agent-spawn/src/configs/goose.ts
2491
+ var gooseFileSecretsEnv = { GOOSE_DISABLE_KEYRING: "1" };
2492
+ var gooseSpawnConfig = {
2493
+ kind: "cli",
2494
+ agentId: "goose",
2495
+ adapter: "native",
2496
+ promptFlag: "--text",
2497
+ modelFlag: "--model",
2498
+ modelStripProviderPrefix: false,
2499
+ defaultArgs: ["run", "--output-format", "stream-json"],
2500
+ defaultArgsPosition: "beforePrompt",
2501
+ mcpArgs: serializeGooseMcpArgs,
2502
+ mcpArgsPosition: "beforePrompt",
2503
+ modes: {
2504
+ yolo: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "auto" } },
2505
+ edit: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "smart_approve" } },
2506
+ read: { env: { ...gooseFileSecretsEnv, GOOSE_MODE: "chat" } }
2507
+ },
2508
+ stdinMode: {
2509
+ omitPrompt: true,
2510
+ extraArgs: ["--instructions", "-"]
2511
+ },
2512
+ interactive: {
2513
+ defaultArgs: ["session"],
2514
+ defaultArgsPosition: "beforePrompt"
2515
+ },
2516
+ resumeCommand: () => ["run", "--resume", "--text", "continue"]
2517
+ };
2518
+ var gooseAcpSpawnConfig = {
2519
+ kind: "acp",
2520
+ agentId: "goose",
2521
+ acpArgs: ["acp"],
2522
+ env: gooseFileSecretsEnv,
2523
+ skipAuth: true
1400
2524
  };
1401
2525
 
1402
- // packages/config-mutations/src/formats/yaml.ts
1403
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
1404
- function isConfigObject3(value) {
1405
- return typeof value === "object" && value !== null && !Array.isArray(value);
1406
- }
1407
- function parse5(content) {
1408
- if (!content || content.trim() === "") {
1409
- return {};
1410
- }
1411
- const parsed = parseYaml(content);
1412
- if (parsed === null || parsed === void 0) {
1413
- return {};
1414
- }
1415
- if (!isConfigObject3(parsed)) {
1416
- throw new Error("Expected YAML object.");
1417
- }
1418
- return parsed;
1419
- }
1420
- function serialize3(obj) {
1421
- const serialized = stringifyYaml(obj);
1422
- return serialized.endsWith("\n") ? serialized : `${serialized}
1423
- `;
2526
+ // packages/agent-spawn/src/configs/index.ts
2527
+ var allSpawnConfigs = [
2528
+ claudeCodeSpawnConfig,
2529
+ codexSpawnConfig,
2530
+ openCodeSpawnConfig,
2531
+ kimiSpawnConfig,
2532
+ gooseSpawnConfig
2533
+ ];
2534
+ var lookup2 = /* @__PURE__ */ new Map();
2535
+ for (const config of allSpawnConfigs) {
2536
+ lookup2.set(config.agentId, config);
1424
2537
  }
1425
- function merge4(base, patch) {
1426
- const result = { ...base };
1427
- for (const [key, value] of Object.entries(patch)) {
1428
- if (value === void 0) {
1429
- continue;
1430
- }
1431
- const existing = result[key];
1432
- if (isConfigObject3(existing) && isConfigObject3(value)) {
1433
- result[key] = merge4(existing, value);
1434
- continue;
1435
- }
1436
- result[key] = value;
2538
+ var acpLookup = /* @__PURE__ */ new Map();
2539
+ acpLookup.set(openCodeAcpSpawnConfig.agentId, openCodeAcpSpawnConfig);
2540
+ acpLookup.set(kimiAcpSpawnConfig.agentId, kimiAcpSpawnConfig);
2541
+ acpLookup.set(gooseAcpSpawnConfig.agentId, gooseAcpSpawnConfig);
2542
+ function getSpawnConfig(input) {
2543
+ const resolvedId = resolveAgentId(input);
2544
+ if (!resolvedId) {
2545
+ return void 0;
1437
2546
  }
1438
- return result;
2547
+ return lookup2.get(resolvedId);
1439
2548
  }
1440
- function prune4(obj, shape) {
1441
- let changed = false;
1442
- const result = { ...obj };
1443
- for (const [key, pattern] of Object.entries(shape)) {
1444
- if (!(key in result)) {
1445
- continue;
1446
- }
1447
- const current = result[key];
1448
- if (isConfigObject3(pattern) && Object.keys(pattern).length === 0) {
1449
- delete result[key];
1450
- changed = true;
1451
- continue;
1452
- }
1453
- if (isConfigObject3(pattern) && isConfigObject3(current)) {
1454
- const { changed: childChanged, result: childResult } = prune4(current, pattern);
1455
- if (childChanged) {
1456
- changed = true;
1457
- }
1458
- if (Object.keys(childResult).length === 0) {
1459
- delete result[key];
1460
- } else {
1461
- result[key] = childResult;
1462
- }
2549
+ function listMcpSupportedAgents() {
2550
+ const supported = [];
2551
+ for (const config of allSpawnConfigs) {
2552
+ if (config.kind !== "cli" || typeof config.mcpArgs !== "function" && typeof config.mcpEnv !== "function") {
1463
2553
  continue;
1464
2554
  }
1465
- delete result[key];
1466
- changed = true;
2555
+ supported.push(config.agentId);
1467
2556
  }
1468
- return { changed, result };
2557
+ return supported;
1469
2558
  }
1470
- var yamlFormat = {
1471
- parse: parse5,
1472
- serialize: serialize3,
1473
- merge: merge4,
1474
- prune: prune4
1475
- };
1476
2559
 
1477
- // packages/config-mutations/src/formats/index.ts
1478
- var formatRegistry = {
1479
- json: jsonFormat,
1480
- toml: tomlFormat,
1481
- yaml: yamlFormat
1482
- };
1483
- var extensionMap = {
1484
- ".json": "json",
1485
- ".toml": "toml",
1486
- ".yaml": "yaml",
1487
- ".yml": "yaml"
1488
- };
1489
- function getConfigFormat(pathOrFormat) {
1490
- if (pathOrFormat in formatRegistry) {
1491
- return formatRegistry[pathOrFormat];
1492
- }
1493
- const ext = getExtension(pathOrFormat);
1494
- const formatName = extensionMap[ext];
1495
- if (!formatName) {
1496
- throw new Error(
1497
- `Unsupported config format. Cannot detect format from "${pathOrFormat}". Supported extensions: ${Object.keys(extensionMap).join(", ")}. Supported format names: ${Object.keys(formatRegistry).join(", ")}.`
1498
- );
1499
- }
1500
- return formatRegistry[formatName];
1501
- }
1502
- function detectFormat(path5) {
1503
- const ext = getExtension(path5);
1504
- return extensionMap[ext];
1505
- }
1506
- function getExtension(path5) {
1507
- const lastDot = path5.lastIndexOf(".");
1508
- if (lastDot === -1) {
1509
- return "";
1510
- }
1511
- return path5.slice(lastDot).toLowerCase();
1512
- }
2560
+ // packages/agent-spawn/src/spawn.ts
2561
+ import { mkdirSync, openSync, writeSync, closeSync } from "node:fs";
2562
+ import path15 from "node:path";
1513
2563
 
1514
- // packages/config-mutations/src/execution/path-utils.ts
1515
- import path4 from "node:path";
1516
- function expandHome(targetPath, homeDir) {
1517
- if (!targetPath?.startsWith("~")) {
1518
- return targetPath;
1519
- }
1520
- if (targetPath.startsWith("~./")) {
1521
- targetPath = `~/.${targetPath.slice(3)}`;
1522
- }
1523
- let remainder = targetPath.slice(1);
1524
- if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1525
- remainder = remainder.slice(1);
1526
- } else if (remainder.startsWith(".")) {
1527
- remainder = remainder.slice(1);
1528
- if (remainder.startsWith("/") || remainder.startsWith("\\")) {
1529
- remainder = remainder.slice(1);
1530
- }
1531
- }
1532
- return remainder.length === 0 ? homeDir : path4.join(homeDir, remainder);
1533
- }
1534
- function validateHomePath(targetPath) {
1535
- if (typeof targetPath !== "string" || targetPath.length === 0) {
1536
- throw new Error("Target path must be a non-empty string.");
1537
- }
1538
- if (!targetPath.startsWith("~")) {
1539
- throw new Error(
1540
- `All target paths must be home-relative (start with ~). Received: "${targetPath}"`
1541
- );
2564
+ // packages/agent-spawn/src/configs/resolve-config.ts
2565
+ function resolveConfig(agentId) {
2566
+ const resolvedAgentId = resolveAgentId(agentId);
2567
+ if (!resolvedAgentId) {
2568
+ throw new Error(`Unknown agent "${agentId}".`);
1542
2569
  }
1543
- }
1544
- function resolvePath(rawPath, homeDir, pathMapper) {
1545
- validateHomePath(rawPath);
1546
- const expanded = expandHome(rawPath, homeDir);
1547
- if (!pathMapper) {
1548
- return expanded;
2570
+ const agentDefinition = allAgents.find((agent) => agent.id === resolvedAgentId);
2571
+ if (!agentDefinition) {
2572
+ throw new Error(`Unknown agent "${agentId}".`);
1549
2573
  }
1550
- const rawDirectory = path4.dirname(expanded);
1551
- const mappedDirectory = pathMapper.mapTargetDirectory({
1552
- targetDirectory: rawDirectory
1553
- });
1554
- const filename = path4.basename(expanded);
1555
- return filename.length === 0 ? mappedDirectory : path4.join(mappedDirectory, filename);
2574
+ const spawnConfig = getSpawnConfig(resolvedAgentId);
2575
+ const binaryName = agentDefinition.binaryName;
2576
+ return { agentId: resolvedAgentId, binaryName, spawnConfig };
1556
2577
  }
1557
2578
 
1558
- // packages/config-mutations/src/fs-utils.ts
1559
- function isNotFound(error2) {
1560
- return typeof error2 === "object" && error2 !== null && "code" in error2 && error2.code === "ENOENT";
1561
- }
1562
- async function readFileIfExists(fs, target) {
1563
- try {
1564
- return await fs.readFile(target, "utf8");
1565
- } catch (error2) {
1566
- if (isNotFound(error2)) {
1567
- return null;
1568
- }
1569
- throw error2;
1570
- }
1571
- }
1572
- async function pathExists(fs, target) {
1573
- try {
1574
- await fs.stat(target);
1575
- return true;
1576
- } catch (error2) {
1577
- if (isNotFound(error2)) {
1578
- return false;
1579
- }
1580
- throw error2;
2579
+ // packages/agent-spawn/src/mcp-args.ts
2580
+ function hasMcpServers(servers) {
2581
+ if (!servers) {
2582
+ return false;
1581
2583
  }
2584
+ return Object.keys(servers).length > 0;
1582
2585
  }
1583
- function createTimestamp() {
1584
- return (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").replaceAll(".", "-");
1585
- }
1586
-
1587
- // packages/config-mutations/src/execution/apply-mutation.ts
1588
- function resolveValue(resolver, options) {
1589
- if (typeof resolver === "function") {
1590
- return resolver(options);
2586
+ function getMcpArgs(config, servers) {
2587
+ if (!hasMcpServers(servers)) {
2588
+ return [];
1591
2589
  }
1592
- return resolver;
1593
- }
1594
- function createInvalidDocumentBackupPath(targetPath) {
1595
- const ext = targetPath.includes(".") ? targetPath.split(".").pop() : "bak";
1596
- return `${targetPath}.invalid-${createTimestamp()}.${ext}`;
1597
- }
1598
- async function backupInvalidDocument(fs, targetPath, content) {
1599
- const backupPath = createInvalidDocumentBackupPath(targetPath);
1600
- await fs.writeFile(backupPath, content, { encoding: "utf8" });
1601
- }
1602
- function describeMutation(kind, targetPath) {
1603
- const displayPath = targetPath ?? "target";
1604
- switch (kind) {
1605
- case "ensureDirectory":
1606
- return `Create ${displayPath}`;
1607
- case "removeDirectory":
1608
- return `Remove directory ${displayPath}`;
1609
- case "backup":
1610
- return `Backup ${displayPath}`;
1611
- case "templateWrite":
1612
- return `Write ${displayPath}`;
1613
- case "chmod":
1614
- return `Set permissions on ${displayPath}`;
1615
- case "removeFile":
1616
- return `Remove ${displayPath}`;
1617
- case "configMerge":
1618
- case "configPrune":
1619
- case "configTransform":
1620
- case "templateMergeToml":
1621
- case "templateMergeJson":
1622
- return `Update ${displayPath}`;
1623
- default:
1624
- return "Operation";
2590
+ if (!config.mcpArgs && !config.mcpEnv) {
2591
+ throw new Error(formatUnsupportedMcpSpawnMessage(config.agentId));
1625
2592
  }
1626
- }
1627
- function pruneKeysByPrefix(table, prefix) {
1628
- const result = {};
1629
- for (const [key, value] of Object.entries(table)) {
1630
- if (!key.startsWith(prefix)) {
1631
- result[key] = value;
1632
- }
2593
+ if (!config.mcpArgs) {
2594
+ return [];
1633
2595
  }
1634
- return result;
2596
+ return config.mcpArgs(servers);
1635
2597
  }
1636
- function isConfigObject4(value) {
1637
- return typeof value === "object" && value !== null && !Array.isArray(value);
2598
+ function formatUnsupportedMcpSpawnMessage(agentId) {
2599
+ const supported = listMcpSupportedAgents();
2600
+ const supportedText = supported.length > 0 ? supported.join(", ") : "(none)";
2601
+ return `Agent "${agentId}" does not support MCP servers at spawn time.
2602
+ Agents with spawn-time MCP support: ${supportedText}`;
1638
2603
  }
1639
- function mergeWithPruneByPrefix(base, patch, pruneByPrefix) {
1640
- const result = { ...base };
1641
- const prefixMap = pruneByPrefix ?? {};
1642
- for (const [key, value] of Object.entries(patch)) {
1643
- const current = result[key];
1644
- const prefix = prefixMap[key];
1645
- if (isConfigObject4(current) && isConfigObject4(value)) {
1646
- if (prefix) {
1647
- const pruned = pruneKeysByPrefix(current, prefix);
1648
- result[key] = { ...pruned, ...value };
1649
- } else {
1650
- result[key] = mergeWithPruneByPrefix(
1651
- current,
1652
- value,
1653
- prefixMap
1654
- );
1655
- }
1656
- continue;
1657
- }
1658
- result[key] = value;
1659
- }
1660
- return result;
2604
+
2605
+ // packages/agent-spawn/src/model-utils.ts
2606
+ function stripModelNamespace(model) {
2607
+ const slashIndex = model.indexOf("/");
2608
+ return slashIndex === -1 ? model : model.slice(slashIndex + 1);
1661
2609
  }
1662
- async function applyMutation(mutation, context, options) {
1663
- switch (mutation.kind) {
1664
- case "ensureDirectory":
1665
- return applyEnsureDirectory(mutation, context, options);
1666
- case "removeDirectory":
1667
- return applyRemoveDirectory(mutation, context, options);
1668
- case "removeFile":
1669
- return applyRemoveFile(mutation, context, options);
1670
- case "chmod":
1671
- return applyChmod(mutation, context, options);
1672
- case "backup":
1673
- return applyBackup(mutation, context, options);
1674
- case "configMerge":
1675
- return applyConfigMerge(mutation, context, options);
1676
- case "configPrune":
1677
- return applyConfigPrune(mutation, context, options);
1678
- case "configTransform":
1679
- return applyConfigTransform(mutation, context, options);
1680
- case "templateWrite":
1681
- return applyTemplateWrite(mutation, context, options);
1682
- case "templateMergeToml":
1683
- return applyTemplateMerge(mutation, context, options, "toml");
1684
- case "templateMergeJson":
1685
- return applyTemplateMerge(mutation, context, options, "json");
1686
- default: {
1687
- const never = mutation;
1688
- throw new Error(`Unknown mutation kind: ${never.kind}`);
1689
- }
2610
+
2611
+ // packages/agent-spawn/src/spawn.ts
2612
+ function resolveCliConfig(agentId) {
2613
+ const resolved = resolveConfig(agentId);
2614
+ if (!resolved.spawnConfig) {
2615
+ throw new Error(`Agent "${resolved.agentId}" has no spawn config.`);
1690
2616
  }
1691
- }
1692
- async function applyEnsureDirectory(mutation, context, options) {
1693
- const rawPath = resolveValue(mutation.path, options);
1694
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1695
- const details = {
1696
- kind: mutation.kind,
1697
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1698
- targetPath
1699
- };
1700
- const existed = await pathExists(context.fs, targetPath);
1701
- if (!context.dryRun) {
1702
- await context.fs.mkdir(targetPath, { recursive: true });
2617
+ if (resolved.spawnConfig.kind !== "cli") {
2618
+ throw new Error(`Agent "${resolved.agentId}" does not support CLI spawn.`);
2619
+ }
2620
+ if (!resolved.binaryName) {
2621
+ throw new Error(`Agent "${resolved.agentId}" has no binaryName.`);
1703
2622
  }
1704
2623
  return {
1705
- outcome: {
1706
- changed: !existed,
1707
- effect: "mkdir",
1708
- detail: existed ? "noop" : "create"
1709
- },
1710
- details
2624
+ agentId: resolved.agentId,
2625
+ binaryName: resolved.binaryName,
2626
+ spawnConfig: resolved.spawnConfig
1711
2627
  };
1712
2628
  }
1713
- async function applyRemoveDirectory(mutation, context, options) {
1714
- const rawPath = resolveValue(mutation.path, options);
1715
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1716
- const details = {
1717
- kind: mutation.kind,
1718
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1719
- targetPath
1720
- };
1721
- const existed = await pathExists(context.fs, targetPath);
1722
- if (!existed) {
1723
- return {
1724
- outcome: { changed: false, effect: "none", detail: "noop" },
1725
- details
1726
- };
2629
+ function getDefaultArgsPosition(config) {
2630
+ return config.defaultArgsPosition ?? "afterPrompt";
2631
+ }
2632
+ function getMcpArgsPosition(config) {
2633
+ if (config.mcpArgsPosition) {
2634
+ return config.mcpArgsPosition;
1727
2635
  }
1728
- if (typeof context.fs.rm !== "function") {
1729
- return {
1730
- outcome: { changed: false, effect: "none", detail: "noop" },
1731
- details
1732
- };
2636
+ return config.mcpArgsBeforeCommand ? "beforeCommand" : "afterCommand";
2637
+ }
2638
+ function buildCliArgs(config, options, stdinMode) {
2639
+ const mcpArgs = getMcpArgs(config, options.mcpServers);
2640
+ const defaultArgsPosition = getDefaultArgsPosition(config);
2641
+ const mcpArgsPosition = getMcpArgsPosition(config);
2642
+ const args = [];
2643
+ if (mcpArgsPosition === "beforeCommand") {
2644
+ args.push(...mcpArgs);
1733
2645
  }
1734
- if (mutation.force) {
1735
- if (!context.dryRun) {
1736
- await context.fs.rm(targetPath, { recursive: true, force: true });
1737
- }
1738
- return {
1739
- outcome: { changed: true, effect: "delete", detail: "delete" },
1740
- details
1741
- };
2646
+ if (defaultArgsPosition === "beforePrompt") {
2647
+ args.push(...config.defaultArgs);
1742
2648
  }
1743
- const entries = await context.fs.readdir(targetPath);
1744
- if (entries.length > 0) {
1745
- return {
1746
- outcome: { changed: false, effect: "none", detail: "noop" },
1747
- details
1748
- };
2649
+ if (mcpArgsPosition === "beforePrompt") {
2650
+ args.push(...mcpArgs);
1749
2651
  }
1750
- if (!context.dryRun) {
1751
- await context.fs.rm(targetPath, { recursive: true, force: true });
2652
+ if (stdinMode) {
2653
+ args.push(
2654
+ config.promptFlag,
2655
+ ...stdinMode.omitPrompt ? [] : [options.prompt],
2656
+ ...stdinMode.extraArgs
2657
+ );
2658
+ } else {
2659
+ args.push(config.promptFlag, options.prompt);
1752
2660
  }
1753
- return {
1754
- outcome: { changed: true, effect: "delete", detail: "delete" },
1755
- details
1756
- };
2661
+ if (options.model && config.modelFlag) {
2662
+ let model = config.modelStripProviderPrefix ? stripModelNamespace(options.model) : options.model;
2663
+ if (config.modelTransform) model = config.modelTransform(model);
2664
+ args.push(config.modelFlag, model);
2665
+ }
2666
+ if (defaultArgsPosition === "afterPrompt") {
2667
+ args.push(...config.defaultArgs);
2668
+ }
2669
+ if (mcpArgsPosition === "afterCommand") {
2670
+ args.push(...mcpArgs);
2671
+ }
2672
+ const mode = resolveModeConfig(config.modes[options.mode ?? "yolo"]);
2673
+ args.push(...mode.args);
2674
+ if (options.args && options.args.length > 0) {
2675
+ args.push(...options.args);
2676
+ }
2677
+ return { args, env: mode.env };
1757
2678
  }
1758
- async function applyRemoveFile(mutation, context, options) {
1759
- const rawPath = resolveValue(mutation.target, options);
1760
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1761
- const details = {
1762
- kind: mutation.kind,
1763
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1764
- targetPath
1765
- };
1766
- try {
1767
- const content = await context.fs.readFile(targetPath, "utf8");
1768
- const trimmed = content.trim();
1769
- if (mutation.whenContentMatches && !mutation.whenContentMatches.test(trimmed)) {
1770
- return {
1771
- outcome: { changed: false, effect: "none", detail: "noop" },
1772
- details
1773
- };
1774
- }
1775
- if (mutation.whenEmpty && trimmed.length > 0) {
1776
- return {
1777
- outcome: { changed: false, effect: "none", detail: "noop" },
1778
- details
1779
- };
1780
- }
1781
- if (!context.dryRun) {
1782
- await context.fs.unlink(targetPath);
1783
- }
1784
- return {
1785
- outcome: { changed: true, effect: "delete", detail: "delete" },
1786
- details
1787
- };
1788
- } catch (error2) {
1789
- if (isNotFound(error2)) {
1790
- return {
1791
- outcome: { changed: false, effect: "none", detail: "noop" },
1792
- details
1793
- };
1794
- }
1795
- throw error2;
2679
+ function buildSpawnArgs(agentId, options) {
2680
+ const { binaryName, spawnConfig } = resolveCliConfig(agentId);
2681
+ const stdinMode = options.useStdin && spawnConfig.stdinMode ? spawnConfig.stdinMode : void 0;
2682
+ const result = buildCliArgs(spawnConfig, options, stdinMode);
2683
+ return { binaryName, args: result.args, env: result.env };
2684
+ }
2685
+
2686
+ // packages/design-system/src/tokens/colors.ts
2687
+ import chalk from "chalk";
2688
+ var dark = {
2689
+ header: (text4) => chalk.magentaBright.bold(text4),
2690
+ divider: (text4) => chalk.dim(text4),
2691
+ prompt: (text4) => chalk.cyan(text4),
2692
+ number: (text4) => chalk.cyanBright(text4),
2693
+ intro: (text4) => chalk.bgMagenta.white(` Poe - ${text4} `),
2694
+ resolvedSymbol: chalk.magenta("\u25C7"),
2695
+ errorSymbol: chalk.red("\u25A0"),
2696
+ accent: (text4) => chalk.cyan(text4),
2697
+ muted: (text4) => chalk.dim(text4),
2698
+ success: (text4) => chalk.green(text4),
2699
+ warning: (text4) => chalk.yellow(text4),
2700
+ error: (text4) => chalk.red(text4),
2701
+ info: (text4) => chalk.magenta(text4),
2702
+ badge: (text4) => chalk.bgYellow.black(` ${text4} `)
2703
+ };
2704
+ var light = {
2705
+ header: (text4) => chalk.hex("#a200ff").bold(text4),
2706
+ divider: (text4) => chalk.hex("#666666")(text4),
2707
+ prompt: (text4) => chalk.hex("#006699").bold(text4),
2708
+ number: (text4) => chalk.hex("#0077cc").bold(text4),
2709
+ intro: (text4) => chalk.bgHex("#a200ff").white(` Poe - ${text4} `),
2710
+ resolvedSymbol: chalk.hex("#a200ff")("\u25C7"),
2711
+ errorSymbol: chalk.hex("#cc0000")("\u25A0"),
2712
+ accent: (text4) => chalk.hex("#006699").bold(text4),
2713
+ muted: (text4) => chalk.hex("#666666")(text4),
2714
+ success: (text4) => chalk.hex("#008800")(text4),
2715
+ warning: (text4) => chalk.hex("#cc6600")(text4),
2716
+ error: (text4) => chalk.hex("#cc0000")(text4),
2717
+ info: (text4) => chalk.hex("#a200ff")(text4),
2718
+ badge: (text4) => chalk.bgHex("#cc6600").white(` ${text4} `)
2719
+ };
2720
+
2721
+ // packages/design-system/src/tokens/typography.ts
2722
+ import chalk2 from "chalk";
2723
+
2724
+ // packages/design-system/src/components/text.ts
2725
+ import chalk3 from "chalk";
2726
+
2727
+ // packages/design-system/src/internal/output-format.ts
2728
+ import { AsyncLocalStorage } from "node:async_hooks";
2729
+ var VALID_FORMATS = /* @__PURE__ */ new Set(["terminal", "markdown", "json"]);
2730
+ var formatStorage = new AsyncLocalStorage();
2731
+ var cached;
2732
+ function resolveOutputFormat(env = process.env) {
2733
+ const scoped = formatStorage.getStore();
2734
+ if (scoped) {
2735
+ return scoped;
1796
2736
  }
2737
+ if (cached) {
2738
+ return cached;
2739
+ }
2740
+ const raw = env.OUTPUT_FORMAT?.toLowerCase();
2741
+ cached = VALID_FORMATS.has(raw) ? raw : "terminal";
2742
+ return cached;
1797
2743
  }
1798
- async function applyChmod(mutation, context, options) {
1799
- const rawPath = resolveValue(mutation.target, options);
1800
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1801
- const details = {
1802
- kind: mutation.kind,
1803
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1804
- targetPath
1805
- };
1806
- if (typeof context.fs.chmod !== "function") {
1807
- return {
1808
- outcome: { changed: false, effect: "none", detail: "noop" },
1809
- details
1810
- };
2744
+
2745
+ // packages/design-system/src/internal/theme-detect.ts
2746
+ function detectThemeFromEnv(env) {
2747
+ const apple = env.APPLE_INTERFACE_STYLE;
2748
+ if (typeof apple === "string") {
2749
+ return apple.toLowerCase() === "dark" ? "dark" : "light";
1811
2750
  }
1812
- try {
1813
- const stat = await context.fs.stat(targetPath);
1814
- const currentMode = typeof stat.mode === "number" ? stat.mode & 511 : null;
1815
- if (currentMode === mutation.mode) {
1816
- return {
1817
- outcome: { changed: false, effect: "none", detail: "noop" },
1818
- details
1819
- };
2751
+ const vscodeKind = env.VSCODE_COLOR_THEME_KIND;
2752
+ if (typeof vscodeKind === "string") {
2753
+ const normalized = vscodeKind.toLowerCase();
2754
+ if (normalized.includes("light")) {
2755
+ return "light";
1820
2756
  }
1821
- if (!context.dryRun) {
1822
- await context.fs.chmod(targetPath, mutation.mode);
2757
+ if (normalized.includes("dark")) {
2758
+ return "dark";
1823
2759
  }
1824
- return {
1825
- outcome: { changed: true, effect: "chmod", detail: "update" },
1826
- details
1827
- };
1828
- } catch (error2) {
1829
- if (isNotFound(error2)) {
1830
- return {
1831
- outcome: { changed: false, effect: "none", detail: "noop" },
1832
- details
1833
- };
2760
+ }
2761
+ const colorFGBG = env.COLORFGBG;
2762
+ if (typeof colorFGBG === "string") {
2763
+ const parts = colorFGBG.split(";").map((part) => Number.parseInt(part, 10));
2764
+ const background = parts.at(-1);
2765
+ if (Number.isFinite(background)) {
2766
+ return background >= 8 ? "light" : "dark";
1834
2767
  }
1835
- throw error2;
1836
2768
  }
2769
+ return void 0;
1837
2770
  }
1838
- async function applyBackup(mutation, context, options) {
1839
- const rawPath = resolveValue(mutation.target, options);
1840
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1841
- const details = {
1842
- kind: mutation.kind,
1843
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1844
- targetPath
1845
- };
1846
- const content = await readFileIfExists(context.fs, targetPath);
1847
- if (content === null) {
1848
- return {
1849
- outcome: { changed: false, effect: "none", detail: "noop" },
1850
- details
1851
- };
2771
+ function resolveThemeName(env = process.env) {
2772
+ const raw = (env.POE_CODE_THEME ?? env.POE_THEME)?.toLowerCase();
2773
+ if (raw === "light" || raw === "dark") {
2774
+ return raw;
1852
2775
  }
1853
- if (!context.dryRun) {
1854
- const backupPath = `${targetPath}.backup-${createTimestamp()}`;
1855
- await context.fs.writeFile(backupPath, content, { encoding: "utf8" });
2776
+ const detected = detectThemeFromEnv(env);
2777
+ if (detected) {
2778
+ return detected;
1856
2779
  }
1857
- return {
1858
- outcome: { changed: true, effect: "copy", detail: "backup" },
1859
- details
1860
- };
2780
+ return "dark";
1861
2781
  }
1862
- async function applyConfigMerge(mutation, context, options) {
1863
- const rawPath = resolveValue(mutation.target, options);
1864
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1865
- const details = {
1866
- kind: mutation.kind,
1867
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1868
- targetPath
1869
- };
1870
- const formatName = mutation.format ?? detectFormat(rawPath);
1871
- if (!formatName) {
1872
- throw new Error(
1873
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1874
- );
1875
- }
1876
- const format = getConfigFormat(formatName);
1877
- const rawContent = await readFileIfExists(context.fs, targetPath);
1878
- let current;
1879
- try {
1880
- current = rawContent === null ? {} : format.parse(rawContent);
1881
- } catch {
1882
- if (rawContent !== null) {
1883
- await backupInvalidDocument(context.fs, targetPath, rawContent);
1884
- }
1885
- current = {};
1886
- }
1887
- const value = resolveValue(mutation.value, options);
1888
- let merged;
1889
- if (mutation.pruneByPrefix) {
1890
- merged = mergeWithPruneByPrefix(current, value, mutation.pruneByPrefix);
1891
- } else {
1892
- merged = format.merge(current, value);
1893
- }
1894
- const serialized = format.serialize(merged);
1895
- const changed = serialized !== rawContent;
1896
- if (changed && !context.dryRun) {
1897
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2782
+ var cachedTheme;
2783
+ function getTheme(env) {
2784
+ if (cachedTheme) {
2785
+ return cachedTheme;
1898
2786
  }
1899
- return {
1900
- outcome: {
1901
- changed,
1902
- effect: changed ? "write" : "none",
1903
- detail: changed ? rawContent === null ? "create" : "update" : "noop"
1904
- },
1905
- details
1906
- };
2787
+ const themeName = resolveThemeName(env);
2788
+ cachedTheme = themeName === "light" ? light : dark;
2789
+ return cachedTheme;
1907
2790
  }
1908
- async function applyConfigPrune(mutation, context, options) {
1909
- const rawPath = resolveValue(mutation.target, options);
1910
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1911
- const details = {
1912
- kind: mutation.kind,
1913
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1914
- targetPath
1915
- };
1916
- const rawContent = await readFileIfExists(context.fs, targetPath);
1917
- if (rawContent === null) {
1918
- return {
1919
- outcome: { changed: false, effect: "none", detail: "noop" },
1920
- details
1921
- };
1922
- }
1923
- const formatName = mutation.format ?? detectFormat(rawPath);
1924
- if (!formatName) {
1925
- throw new Error(
1926
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
1927
- );
2791
+
2792
+ // packages/design-system/src/components/symbols.ts
2793
+ import chalk4 from "chalk";
2794
+ var symbols = {
2795
+ get info() {
2796
+ const format = resolveOutputFormat();
2797
+ if (format === "json") return "info";
2798
+ if (format === "markdown") return "(i)";
2799
+ return chalk4.magenta("\u25CF");
2800
+ },
2801
+ get success() {
2802
+ const format = resolveOutputFormat();
2803
+ if (format === "json") return "success";
2804
+ if (format === "markdown") return "[ok]";
2805
+ return chalk4.magenta("\u25C6");
2806
+ },
2807
+ get resolved() {
2808
+ const format = resolveOutputFormat();
2809
+ if (format === "json") return "resolved";
2810
+ if (format === "markdown") return ">";
2811
+ return getTheme().resolvedSymbol;
2812
+ },
2813
+ get errorResolved() {
2814
+ const format = resolveOutputFormat();
2815
+ if (format === "json") return "error";
2816
+ if (format === "markdown") return "[!]";
2817
+ return getTheme().errorSymbol;
2818
+ },
2819
+ get bar() {
2820
+ const format = resolveOutputFormat();
2821
+ if (format === "json") return "";
2822
+ if (format === "markdown") return "|";
2823
+ return "\u2502";
2824
+ },
2825
+ cornerTopRight: "\u256E",
2826
+ cornerBottomRight: "\u256F",
2827
+ get warning() {
2828
+ const format = resolveOutputFormat();
2829
+ if (format === "json") return "warning";
2830
+ if (format === "markdown") return "[!]";
2831
+ return "\u25B2";
2832
+ },
2833
+ get active() {
2834
+ const format = resolveOutputFormat();
2835
+ if (format === "json") return "active";
2836
+ if (format === "markdown") return "[x]";
2837
+ return "\u25C6";
2838
+ },
2839
+ get inactive() {
2840
+ const format = resolveOutputFormat();
2841
+ if (format === "json") return "inactive";
2842
+ if (format === "markdown") return "[ ]";
2843
+ return "\u25CB";
1928
2844
  }
1929
- const format = getConfigFormat(formatName);
1930
- let current;
1931
- try {
1932
- current = format.parse(rawContent);
1933
- } catch {
1934
- return {
1935
- outcome: { changed: false, effect: "none", detail: "noop" },
1936
- details
1937
- };
2845
+ };
2846
+
2847
+ // packages/design-system/src/components/logger.ts
2848
+ import chalk6 from "chalk";
2849
+
2850
+ // packages/design-system/src/prompts/primitives/log.ts
2851
+ import chalk5 from "chalk";
2852
+
2853
+ // packages/design-system/src/internal/strip-ansi.ts
2854
+ function stripAnsi(value) {
2855
+ return value.replace(/\u001b\[[0-9;]*m/g, "");
2856
+ }
2857
+
2858
+ // packages/design-system/src/prompts/primitives/log.ts
2859
+ function writeTerminalMessage(msg, {
2860
+ symbol = chalk5.gray("\u2502"),
2861
+ secondarySymbol = chalk5.gray("\u2502"),
2862
+ spacing: spacing2 = 1,
2863
+ withGuide = true
2864
+ } = {}) {
2865
+ const lines = [];
2866
+ const showGuide = withGuide !== false;
2867
+ const contentLines = msg.split("\n");
2868
+ const prefix = showGuide ? `${symbol} ` : "";
2869
+ const continuationPrefix = showGuide ? `${secondarySymbol} ` : "";
2870
+ const emptyGuide = showGuide ? secondarySymbol : "";
2871
+ for (let index = 0; index < spacing2; index += 1) {
2872
+ lines.push(emptyGuide);
1938
2873
  }
1939
- if (mutation.onlyIf && !mutation.onlyIf(current, options)) {
1940
- return {
1941
- outcome: { changed: false, effect: "none", detail: "noop" },
1942
- details
1943
- };
2874
+ if (contentLines.length === 0) {
2875
+ process.stdout.write("\n");
2876
+ return;
1944
2877
  }
1945
- const shape = resolveValue(mutation.shape, options);
1946
- const { changed, result } = format.prune(current, shape);
1947
- if (!changed) {
1948
- return {
1949
- outcome: { changed: false, effect: "none", detail: "noop" },
1950
- details
1951
- };
2878
+ const [firstLine = "", ...continuationLines] = contentLines;
2879
+ if (firstLine.length > 0) {
2880
+ lines.push(`${prefix}${firstLine}`);
2881
+ } else {
2882
+ lines.push(showGuide ? symbol : "");
1952
2883
  }
1953
- if (Object.keys(result).length === 0) {
1954
- if (!context.dryRun) {
1955
- await context.fs.unlink(targetPath);
2884
+ for (const line of continuationLines) {
2885
+ if (line.length > 0) {
2886
+ lines.push(`${continuationPrefix}${line}`);
2887
+ continue;
1956
2888
  }
1957
- return {
1958
- outcome: { changed: true, effect: "delete", detail: "delete" },
1959
- details
1960
- };
1961
- }
1962
- const serialized = format.serialize(result);
1963
- if (!context.dryRun) {
1964
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2889
+ lines.push(emptyGuide);
1965
2890
  }
1966
- return {
1967
- outcome: { changed: true, effect: "write", detail: "update" },
1968
- details
1969
- };
2891
+ process.stdout.write(`${lines.join("\n")}
2892
+ `);
1970
2893
  }
1971
- async function applyConfigTransform(mutation, context, options) {
1972
- const rawPath = resolveValue(mutation.target, options);
1973
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
1974
- const details = {
1975
- kind: mutation.kind,
1976
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
1977
- targetPath
1978
- };
1979
- const formatName = mutation.format ?? detectFormat(rawPath);
1980
- if (!formatName) {
1981
- throw new Error(
1982
- `Cannot detect config format for "${rawPath}". Provide explicit format option.`
2894
+ function message(msg, options) {
2895
+ const format = resolveOutputFormat();
2896
+ if (format === "markdown") {
2897
+ process.stdout.write(`- ${stripAnsi(msg)}
2898
+ `);
2899
+ return;
2900
+ }
2901
+ if (format === "json") {
2902
+ process.stdout.write(
2903
+ `${JSON.stringify({ level: "message", message: stripAnsi(msg) })}
2904
+ `
1983
2905
  );
2906
+ return;
1984
2907
  }
1985
- const format = getConfigFormat(formatName);
1986
- const rawContent = await readFileIfExists(context.fs, targetPath);
1987
- let current;
1988
- try {
1989
- current = rawContent === null ? {} : format.parse(rawContent);
1990
- } catch {
1991
- if (rawContent !== null) {
1992
- await backupInvalidDocument(context.fs, targetPath, rawContent);
1993
- }
1994
- current = {};
2908
+ writeTerminalMessage(msg, options);
2909
+ }
2910
+ function info(msg) {
2911
+ const format = resolveOutputFormat();
2912
+ if (format === "markdown") {
2913
+ process.stdout.write(`- **info:** ${stripAnsi(msg)}
2914
+ `);
2915
+ return;
1995
2916
  }
1996
- const { content: transformed, changed } = mutation.transform(current, options);
1997
- if (!changed) {
1998
- return {
1999
- outcome: { changed: false, effect: "none", detail: "noop" },
2000
- details
2001
- };
2917
+ if (format === "json") {
2918
+ process.stdout.write(
2919
+ `${JSON.stringify({ level: "info", message: stripAnsi(msg) })}
2920
+ `
2921
+ );
2922
+ return;
2002
2923
  }
2003
- if (transformed === null) {
2004
- if (rawContent === null) {
2005
- return {
2006
- outcome: { changed: false, effect: "none", detail: "noop" },
2007
- details
2008
- };
2009
- }
2010
- if (!context.dryRun) {
2011
- await context.fs.unlink(targetPath);
2012
- }
2013
- return {
2014
- outcome: { changed: true, effect: "delete", detail: "delete" },
2015
- details
2016
- };
2924
+ message(msg, { symbol: symbols.info });
2925
+ }
2926
+ function success(msg) {
2927
+ const format = resolveOutputFormat();
2928
+ if (format === "markdown") {
2929
+ process.stdout.write(`- **success:** ${stripAnsi(msg)}
2930
+ `);
2931
+ return;
2017
2932
  }
2018
- const serialized = format.serialize(transformed);
2019
- if (!context.dryRun) {
2020
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2933
+ if (format === "json") {
2934
+ process.stdout.write(
2935
+ `${JSON.stringify({ level: "success", message: stripAnsi(msg) })}
2936
+ `
2937
+ );
2938
+ return;
2021
2939
  }
2022
- return {
2023
- outcome: {
2024
- changed: true,
2025
- effect: "write",
2026
- detail: rawContent === null ? "create" : "update"
2027
- },
2028
- details
2029
- };
2940
+ message(msg, { symbol: symbols.success });
2030
2941
  }
2031
- async function applyTemplateWrite(mutation, context, options) {
2032
- if (!context.templates) {
2033
- throw new Error(
2034
- "Template mutations require a templates loader. Provide templates function to runMutations context."
2035
- );
2942
+ function warn(msg) {
2943
+ const format = resolveOutputFormat();
2944
+ if (format === "markdown") {
2945
+ process.stdout.write(`- **warning:** ${stripAnsi(msg)}
2946
+ `);
2947
+ return;
2036
2948
  }
2037
- const rawPath = resolveValue(mutation.target, options);
2038
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2039
- const details = {
2040
- kind: mutation.kind,
2041
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2042
- targetPath
2043
- };
2044
- const template = await context.templates(mutation.templateId);
2045
- const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2046
- const rendered = Mustache.render(template, templateContext);
2047
- const existed = await pathExists(context.fs, targetPath);
2048
- if (!context.dryRun) {
2049
- await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
2949
+ if (format === "json") {
2950
+ process.stdout.write(
2951
+ `${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}
2952
+ `
2953
+ );
2954
+ return;
2050
2955
  }
2051
- return {
2052
- outcome: {
2053
- changed: true,
2054
- effect: "write",
2055
- detail: existed ? "update" : "create"
2056
- },
2057
- details
2058
- };
2956
+ message(msg, { symbol: chalk5.yellow("\u25B2") });
2059
2957
  }
2060
- async function applyTemplateMerge(mutation, context, options, formatName) {
2061
- if (!context.templates) {
2062
- throw new Error(
2063
- "Template mutations require a templates loader. Provide templates function to runMutations context."
2064
- );
2958
+ function error(msg) {
2959
+ const format = resolveOutputFormat();
2960
+ if (format === "markdown") {
2961
+ process.stdout.write(`- **error:** ${stripAnsi(msg)}
2962
+ `);
2963
+ return;
2065
2964
  }
2066
- const rawPath = resolveValue(mutation.target, options);
2067
- const targetPath = resolvePath(rawPath, context.homeDir, context.pathMapper);
2068
- const details = {
2069
- kind: mutation.kind,
2070
- label: mutation.label ?? describeMutation(mutation.kind, targetPath),
2071
- targetPath
2072
- };
2073
- const format = getConfigFormat(formatName);
2074
- const template = await context.templates(mutation.templateId);
2075
- const templateContext = mutation.context ? resolveValue(mutation.context, options) : {};
2076
- const rendered = Mustache.render(template, templateContext);
2077
- let templateDoc;
2078
- try {
2079
- templateDoc = format.parse(rendered);
2080
- } catch (error2) {
2081
- throw new Error(
2082
- `Failed to parse rendered template "${mutation.templateId}" as ${formatName.toUpperCase()}: ${error2}`,
2083
- { cause: error2 }
2965
+ if (format === "json") {
2966
+ process.stdout.write(
2967
+ `${JSON.stringify({ level: "error", message: stripAnsi(msg) })}
2968
+ `
2084
2969
  );
2970
+ return;
2085
2971
  }
2086
- const rawContent = await readFileIfExists(context.fs, targetPath);
2087
- let current;
2088
- try {
2089
- current = rawContent === null ? {} : format.parse(rawContent);
2090
- } catch {
2091
- if (rawContent !== null) {
2092
- await backupInvalidDocument(context.fs, targetPath, rawContent);
2972
+ message(msg, { symbol: chalk5.red("\u25A0") });
2973
+ }
2974
+ var log = {
2975
+ info,
2976
+ success,
2977
+ message,
2978
+ warn,
2979
+ error
2980
+ };
2981
+
2982
+ // packages/design-system/src/components/logger.ts
2983
+ function createLogger(emitter) {
2984
+ const emit = (level, message2) => {
2985
+ if (emitter) {
2986
+ emitter(message2);
2987
+ return;
2988
+ }
2989
+ if (level === "success") {
2990
+ log.success(message2);
2991
+ return;
2093
2992
  }
2094
- current = {};
2095
- }
2096
- const merged = format.merge(current, templateDoc);
2097
- const serialized = format.serialize(merged);
2098
- const changed = serialized !== rawContent;
2099
- if (changed && !context.dryRun) {
2100
- await context.fs.writeFile(targetPath, serialized, { encoding: "utf8" });
2101
- }
2993
+ if (level === "warn") {
2994
+ log.warn(message2);
2995
+ return;
2996
+ }
2997
+ if (level === "error") {
2998
+ log.error(message2);
2999
+ return;
3000
+ }
3001
+ log.info(message2);
3002
+ };
2102
3003
  return {
2103
- outcome: {
2104
- changed,
2105
- effect: changed ? "write" : "none",
2106
- detail: changed ? rawContent === null ? "create" : "update" : "noop"
3004
+ info(message2) {
3005
+ emit("info", message2);
2107
3006
  },
2108
- details
3007
+ success(message2) {
3008
+ emit("success", message2);
3009
+ },
3010
+ warn(message2) {
3011
+ emit("warn", message2);
3012
+ },
3013
+ error(message2) {
3014
+ emit("error", message2);
3015
+ },
3016
+ resolved(label, value) {
3017
+ if (emitter) {
3018
+ emitter(`${label}: ${value}`);
3019
+ return;
3020
+ }
3021
+ log.message(`${label}
3022
+ ${value}`, { symbol: symbols.resolved });
3023
+ },
3024
+ errorResolved(label, value) {
3025
+ if (emitter) {
3026
+ emitter(`${label}: ${value}`);
3027
+ return;
3028
+ }
3029
+ log.message(`${label}
3030
+ ${value}`, { symbol: symbols.errorResolved });
3031
+ },
3032
+ message(message2, symbol) {
3033
+ if (emitter) {
3034
+ emitter(message2);
3035
+ return;
3036
+ }
3037
+ log.message(message2, { symbol: symbol ?? chalk6.gray("\u2502") });
3038
+ }
2109
3039
  };
2110
3040
  }
3041
+ var logger = createLogger();
2111
3042
 
2112
- // packages/config-mutations/src/execution/run-mutations.ts
2113
- async function runMutations(mutations, context, options) {
2114
- const effects = [];
2115
- let anyChanged = false;
2116
- const resolverOptions = options ?? {};
2117
- for (const mutation of mutations) {
2118
- const { outcome } = await executeMutation(
2119
- mutation,
2120
- context,
2121
- resolverOptions
2122
- );
2123
- effects.push(outcome);
2124
- if (outcome.changed) {
2125
- anyChanged = true;
2126
- }
2127
- }
2128
- return {
2129
- changed: anyChanged,
2130
- effects
2131
- };
3043
+ // packages/design-system/src/components/table.ts
3044
+ import { Table } from "console-table-printer";
3045
+
3046
+ // packages/design-system/src/acp/components.ts
3047
+ import chalk7 from "chalk";
3048
+
3049
+ // packages/design-system/src/acp/writer.ts
3050
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
3051
+ var storage = new AsyncLocalStorage2();
3052
+
3053
+ // packages/design-system/src/acp/components.ts
3054
+ var AGENT_PREFIX = `${chalk7.green.bold("\u2713")} agent: `;
3055
+
3056
+ // packages/design-system/src/dashboard/buffer.ts
3057
+ import chalk8 from "chalk";
3058
+
3059
+ // packages/design-system/src/dashboard/terminal.ts
3060
+ import readline from "node:readline";
3061
+ import { PassThrough } from "node:stream";
3062
+
3063
+ // packages/design-system/src/prompts/index.ts
3064
+ import chalk15 from "chalk";
3065
+ import * as clack from "@clack/prompts";
3066
+
3067
+ // packages/design-system/src/prompts/primitives/cancel.ts
3068
+ import chalk9 from "chalk";
3069
+ import { isCancel } from "@clack/prompts";
3070
+
3071
+ // packages/design-system/src/prompts/primitives/intro.ts
3072
+ import chalk10 from "chalk";
3073
+
3074
+ // packages/design-system/src/prompts/primitives/note.ts
3075
+ import chalk11 from "chalk";
3076
+
3077
+ // packages/design-system/src/prompts/primitives/outro.ts
3078
+ import chalk12 from "chalk";
3079
+
3080
+ // packages/design-system/src/prompts/primitives/spinner.ts
3081
+ import chalk14 from "chalk";
3082
+
3083
+ // packages/design-system/src/static/spinner.ts
3084
+ import chalk13 from "chalk";
3085
+
3086
+ // packages/design-system/src/static/menu.ts
3087
+ import chalk16 from "chalk";
3088
+
3089
+ // packages/agent-spawn/src/autonomous.ts
3090
+ var DEFAULT_ACTIVITY_TIMEOUT_MS = 10 * 60 * 1e3;
3091
+
3092
+ // packages/agent-spawn/src/acp/replay.ts
3093
+ import path16 from "node:path";
3094
+ import { homedir as homedir2 } from "node:os";
3095
+ import { open as open2, readdir } from "node:fs/promises";
3096
+ import { createInterface } from "node:readline";
3097
+
3098
+ // packages/poe-acp-client/src/acp-client.ts
3099
+ import { isAbsolute } from "node:path";
3100
+
3101
+ // packages/poe-acp-client/src/acp-transport.ts
3102
+ import {
3103
+ spawn as spawnChildProcess3
3104
+ } from "node:child_process";
3105
+
3106
+ // packages/poe-acp-client/src/run-report.ts
3107
+ import * as fsPromises2 from "node:fs/promises";
3108
+ import { homedir } from "node:os";
3109
+ import { join } from "node:path";
3110
+
3111
+ // packages/agent-spawn/src/acp/middlewares/spawn-log.ts
3112
+ import path17 from "node:path";
3113
+ import { homedir as homedir3 } from "node:os";
3114
+ import { mkdir, open as open3 } from "node:fs/promises";
3115
+
3116
+ // src/utils/command-checks.ts
3117
+ function formatCommandRunnerResult(result) {
3118
+ const stdout = result.stdout.length > 0 ? result.stdout : "<empty>";
3119
+ const stderr = result.stderr.length > 0 ? result.stderr : "<empty>";
3120
+ return `stdout:
3121
+ ${stdout}
3122
+ stderr:
3123
+ ${stderr}`;
2132
3124
  }
2133
- async function executeMutation(mutation, context, options) {
2134
- context.observers?.onStart?.({
2135
- kind: mutation.kind,
2136
- label: mutation.label ?? mutation.kind,
2137
- targetPath: void 0
2138
- // Will be resolved during apply
3125
+ function createSpawnHealthCheck(agentId, options) {
3126
+ const prompt = `Output exactly: ${options.expectedOutput}`;
3127
+ const { binaryName, args, env: modeEnv } = buildSpawnArgs(agentId, {
3128
+ prompt,
3129
+ model: options.model,
3130
+ mode: "yolo"
2139
3131
  });
2140
- try {
2141
- const { outcome, details } = await applyMutation(mutation, context, options);
2142
- context.observers?.onComplete?.(details, outcome);
2143
- return { outcome, details };
2144
- } catch (error2) {
2145
- context.observers?.onError?.(
2146
- {
2147
- kind: mutation.kind,
2148
- label: mutation.label ?? mutation.kind,
2149
- targetPath: void 0
2150
- },
2151
- error2
2152
- );
2153
- throw error2;
2154
- }
3132
+ return {
3133
+ id: `${agentId}-cli-health`,
3134
+ description: `spawn ${agentId} (expecting "${options.expectedOutput}")`,
3135
+ async run(context) {
3136
+ if (context.isDryRun) {
3137
+ context.logDryRun?.(
3138
+ `Dry run: ${[binaryName, ...args].join(" ")} (expecting "${options.expectedOutput}")`
3139
+ );
3140
+ return;
3141
+ }
3142
+ const result = modeEnv ? await context.runCommand(binaryName, args, { env: modeEnv }) : await context.runCommand(binaryName, args);
3143
+ if (result.exitCode !== 0) {
3144
+ throw new Error(
3145
+ `spawn ${agentId} failed with exit code ${result.exitCode}.
3146
+ ${formatCommandRunnerResult(result)}`
3147
+ );
3148
+ }
3149
+ if (!result.stdout.includes(options.expectedOutput)) {
3150
+ throw new Error(
3151
+ `spawn ${agentId}: expected "${options.expectedOutput}" in stdout.
3152
+ ${formatCommandRunnerResult(result)}`
3153
+ );
3154
+ }
3155
+ }
3156
+ };
2155
3157
  }
2156
-
2157
- // packages/config-mutations/src/template/render.ts
2158
- import Mustache2 from "mustache";
2159
- var originalEscape = Mustache2.escape;
2160
-
2161
- // packages/config-mutations/src/types.ts
2162
- function isConfigObject5(value) {
2163
- return typeof value === "object" && value !== null && !Array.isArray(value);
3158
+ function createBinaryExistsCheck(binaryName, id, description) {
3159
+ return {
3160
+ id,
3161
+ description,
3162
+ async run({ runCommand: runCommand2 }) {
3163
+ const commonPaths = [
3164
+ `/usr/local/bin/${binaryName}`,
3165
+ `/usr/bin/${binaryName}`,
3166
+ `$HOME/.local/bin/${binaryName}`,
3167
+ `$HOME/.claude/local/bin/${binaryName}`
3168
+ ];
3169
+ const detectors = [
3170
+ {
3171
+ command: "which",
3172
+ args: [binaryName],
3173
+ validate: (result) => result.exitCode === 0
3174
+ },
3175
+ {
3176
+ command: "where",
3177
+ args: [binaryName],
3178
+ validate: (result) => result.exitCode === 0 && result.stdout.trim().length > 0
3179
+ },
3180
+ // Check common installation paths using shell expansion for $HOME
3181
+ {
3182
+ command: "sh",
3183
+ args: [
3184
+ "-c",
3185
+ commonPaths.map((p) => `test -f "${p}"`).join(" || ")
3186
+ ],
3187
+ validate: (result) => result.exitCode === 0
3188
+ }
3189
+ ];
3190
+ for (const detector of detectors) {
3191
+ const result = await runCommand2(detector.command, detector.args);
3192
+ if (detector.validate(result)) {
3193
+ return;
3194
+ }
3195
+ }
3196
+ throw new Error(`${binaryName} CLI binary not found on PATH.`);
3197
+ }
3198
+ };
2164
3199
  }
2165
3200
 
2166
3201
  // src/services/service-install.ts