@femtomc/mu-orchestrator 26.2.19 → 26.2.22

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.
@@ -1 +1 @@
1
- {"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AA2BF,qBAAa,SAAS;;gBAWpB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAO;IAwKrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;CAiMjG"}
1
+ {"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAkBF,qBAAa,SAAS;;gBAYpB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAO;IAkLrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;CAiNjG"}
@@ -2,20 +2,12 @@ import { mkdir } from "node:fs/promises";
2
2
  import { join, relative } from "node:path";
3
3
  import { currentRunId, fsEventLogFromRepoRoot, getStorePaths, newRunId, runContext, } from "@femtomc/mu-core/node";
4
4
  import { resolveModelConfig } from "./model_resolution.js";
5
- import { parseMuRole, systemPromptForRole } from "./mu_roles.js";
5
+ import { roleFromTags, systemPromptForRole } from "./mu_roles.js";
6
6
  import { PiSdkBackend } from "./pi_sdk_backend.js";
7
7
  function roundTo(n, digits) {
8
8
  const f = 10 ** digits;
9
9
  return Math.round(n * f) / f;
10
10
  }
11
- function specRoleFromExecutionSpec(execution_spec) {
12
- const role = execution_spec?.role;
13
- if (typeof role !== "string") {
14
- return null;
15
- }
16
- const trimmed = role.trim();
17
- return trimmed.length > 0 ? trimmed : null;
18
- }
19
11
  function relPath(repoRoot, path) {
20
12
  try {
21
13
  const rel = relative(repoRoot, path);
@@ -33,6 +25,7 @@ export class DagRunner {
33
25
  #backend;
34
26
  #modelOverrides;
35
27
  #reorchestrateOutcomes = new Set(["failure", "needs_work"]);
28
+ #attempts = new Map();
36
29
  constructor(store, forum, repoRoot, opts = {}) {
37
30
  this.#store = store;
38
31
  this.#forum = forum;
@@ -41,11 +34,10 @@ export class DagRunner {
41
34
  this.#backend = opts.backend ?? new PiSdkBackend();
42
35
  this.#modelOverrides = opts.modelOverrides ?? {};
43
36
  }
44
- async #resolveConfig(issue) {
45
- void issue;
37
+ async #resolveConfig() {
46
38
  return resolveModelConfig(this.#modelOverrides);
47
39
  }
48
- async #renderUserPrompt(issue, rootId, step) {
40
+ async #renderUserPrompt(issue, rootId, step, attempt = 1) {
49
41
  let rendered = issue.title ?? "";
50
42
  if (issue.body) {
51
43
  rendered += `\n\n${issue.body}`;
@@ -55,17 +47,21 @@ export class DagRunner {
55
47
  if (runId) {
56
48
  rendered += `Run: ${runId}\n`;
57
49
  }
50
+ if (attempt > 1) {
51
+ rendered += `\nAttempt: ${attempt} (previous attempt failed — check \`mu forum read issue:${issue.id}\` for context)\n`;
52
+ }
58
53
  return rendered;
59
54
  }
60
55
  async #executeBackend(issue, cfg, rootId, step, opts = {}) {
61
- const role = parseMuRole(specRoleFromExecutionSpec(issue.execution_spec));
56
+ const role = roleFromTags(issue.tags);
62
57
  const logSuffix = opts.logSuffix ?? "";
63
- const rendered = await this.#renderUserPrompt(issue, rootId, step);
58
+ const rendered = await this.#renderUserPrompt(issue, rootId, step, opts.attempt ?? 1);
64
59
  const systemPrompt = systemPromptForRole(role);
65
60
  const { logsDir } = getStorePaths(this.#repoRoot);
66
- await mkdir(logsDir, { recursive: true });
61
+ const rootLogsDir = join(logsDir, rootId);
62
+ await mkdir(rootLogsDir, { recursive: true });
67
63
  const suffix = logSuffix ? `.${logSuffix}` : "";
68
- const teePath = join(logsDir, `${issue.id}${suffix}.jsonl`);
64
+ const teePath = join(rootLogsDir, `${issue.id}${suffix}.jsonl`);
69
65
  await this.#events.emit("backend.run.start", {
70
66
  source: "backend",
71
67
  issueId: issue.id,
@@ -116,7 +112,7 @@ export class DagRunner {
116
112
  const reopened = await this.#store.update(issueId, {
117
113
  status: "open",
118
114
  outcome: null,
119
- execution_spec: { role: "orchestrator" },
115
+ tags: [...before.tags.filter((t) => !t.startsWith("role:")), "role:orchestrator"],
120
116
  });
121
117
  await this.#events.emit("dag.unstick.reopen", {
122
118
  source: "dag_runner",
@@ -152,6 +148,9 @@ export class DagRunner {
152
148
  continue;
153
149
  if (row.status !== "closed")
154
150
  continue;
151
+ // Circuit breaker: skip issues that have exhausted their attempts.
152
+ if ((this.#attempts.get(row.id) ?? 0) >= 3)
153
+ continue;
155
154
  const outcome = row.outcome;
156
155
  if (outcome && this.#reorchestrateOutcomes.has(outcome)) {
157
156
  if (hasOpenChildren(row.id))
@@ -215,9 +214,8 @@ export class DagRunner {
215
214
  ...rootIssue,
216
215
  title: `Repair stuck DAG: ${rootIssue.title}`,
217
216
  body: `${(rootIssue.body || "").trim()}\n\n## Runner Diagnostics\n\n${diag}`.trim(),
218
- execution_spec: null,
219
217
  };
220
- const cfg = await this.#resolveConfig(repairIssue);
218
+ const cfg = await this.#resolveConfig();
221
219
  const logSuffix = "unstick";
222
220
  const onBackendLine = hooks?.onBackendLine;
223
221
  const { exitCode, elapsedS } = await this.#executeBackend(repairIssue, cfg, rootId, step, {
@@ -243,8 +241,7 @@ export class DagRunner {
243
241
  }
244
242
  const issue = candidates[0];
245
243
  const issueId = issue.id;
246
- // Validate role early so we don't claim/work an unsupported leaf.
247
- const role = parseMuRole(specRoleFromExecutionSpec(issue.execution_spec));
244
+ const role = roleFromTags(issue.tags);
248
245
  await this.#events.emit("dag.step.start", {
249
246
  source: "dag_runner",
250
247
  issueId,
@@ -260,12 +257,16 @@ export class DagRunner {
260
257
  payload: { root_id: rootId, step },
261
258
  });
262
259
  await this.#store.claim(issueId);
260
+ // Track attempt count for circuit breaker.
261
+ const attempt = (this.#attempts.get(issueId) ?? 0) + 1;
262
+ this.#attempts.set(issueId, attempt);
263
263
  // 4. Route + 5. Render + 6. Execute.
264
- const cfg = await this.#resolveConfig(issue);
265
- const logSuffix = "";
264
+ const cfg = await this.#resolveConfig();
265
+ const logSuffix = attempt > 1 ? `attempt-${attempt}` : "";
266
266
  const onBackendLine = hooks?.onBackendLine;
267
267
  const { exitCode, elapsedS } = await this.#executeBackend(issue, cfg, rootId, step, {
268
268
  logSuffix,
269
+ attempt,
269
270
  onLine: onBackendLine
270
271
  ? (line) => onBackendLine({ rootId, step, issueId, logSuffix, line })
271
272
  : undefined,
@@ -277,6 +278,11 @@ export class DagRunner {
277
278
  return final;
278
279
  }
279
280
  if (updated.status !== "closed") {
281
+ await this.#events.emit("dag.step.force_close", {
282
+ source: "dag_runner",
283
+ issueId,
284
+ payload: { root_id: rootId, step, role, attempt, reason: "agent_did_not_close" },
285
+ });
280
286
  updated = await this.#store.close(issueId, "failure");
281
287
  }
282
288
  // 8. Log to forum.
@@ -309,9 +315,18 @@ export class DagRunner {
309
315
  outcome: updated.outcome,
310
316
  },
311
317
  });
312
- // 9. Re-orchestrate on failure / needs_work.
318
+ // 9. Re-orchestrate on failure / needs_work (circuit breaker: max 3 attempts).
313
319
  if (updated.outcome && this.#reorchestrateOutcomes.has(updated.outcome)) {
314
- await this.#reopenForOrchestration(issueId, { reason: `outcome=${updated.outcome}`, step });
320
+ if (attempt < 3) {
321
+ await this.#reopenForOrchestration(issueId, { reason: `outcome=${updated.outcome}`, step });
322
+ }
323
+ else {
324
+ await this.#events.emit("dag.circuit_breaker", {
325
+ source: "dag_runner",
326
+ issueId,
327
+ payload: { root_id: rootId, step, attempt, outcome: updated.outcome },
328
+ });
329
+ }
315
330
  }
316
331
  }
317
332
  final = { status: "max_steps_exhausted", steps: maxSteps, error: "" };
@@ -1,4 +1,5 @@
1
1
  export type MuRole = "orchestrator" | "worker";
2
- export declare function parseMuRole(role: string | null | undefined): MuRole;
2
+ /** Determine role from tags. Defaults to orchestrator if no role tag present. */
3
+ export declare function roleFromTags(tags: readonly string[]): MuRole;
3
4
  export declare function systemPromptForRole(role: MuRole): string;
4
5
  //# sourceMappingURL=mu_roles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mu_roles.d.ts","sourceRoot":"","sources":["../src/mu_roles.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE/C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAanE;AA2DD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA2DxD"}
1
+ {"version":3,"file":"mu_roles.d.ts","sourceRoot":"","sources":["../src/mu_roles.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE/C,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAM5D;AAgED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwExD"}
package/dist/mu_roles.js CHANGED
@@ -1,12 +1,12 @@
1
- export function parseMuRole(role) {
2
- if (role == null) {
3
- return "orchestrator";
1
+ /** Determine role from tags. Defaults to orchestrator if no role tag present. */
2
+ export function roleFromTags(tags) {
3
+ for (const tag of tags) {
4
+ if (tag === "role:worker")
5
+ return "worker";
6
+ if (tag === "role:orchestrator")
7
+ return "orchestrator";
4
8
  }
5
- const trimmed = role.trim();
6
- if (trimmed === "orchestrator" || trimmed === "worker") {
7
- return trimmed;
8
- }
9
- throw new Error(`unsupported execution_spec.role=${JSON.stringify(trimmed)} (only "orchestrator" and "worker" are supported)`);
9
+ return "orchestrator";
10
10
  }
11
11
  /* ------------------------------------------------------------------ */
12
12
  /* mu CLI reference */
@@ -15,7 +15,12 @@ const MU_CLI_REFERENCE = `
15
15
  ## mu CLI
16
16
 
17
17
  You are running inside **mu**, an issue-driven orchestration system.
18
- You have four tools: bash, read, write, edit. Use the \`mu\` CLI via bash to interact with the issue DAG.
18
+ You have four tools: bash, read, write, edit.
19
+
20
+ - Orchestrator: use bash to run \`mu\` commands; do NOT use write/edit (and avoid read).
21
+ - Worker: use tools as needed to implement your assigned issue.
22
+
23
+ Tip: run \`mu <command> --help\` for details.
19
24
 
20
25
  ### Issues
21
26
 
@@ -67,28 +72,41 @@ export function systemPromptForRole(role) {
67
72
  return [
68
73
  "# Mu Orchestrator",
69
74
  "",
70
- "You are mu's orchestrator. You decompose goals into executable work.",
75
+ "You are mu's orchestrator: the hierarchical planner for the issue DAG.",
76
+ "",
77
+ "## Non-Negotiable Constraints",
71
78
  "",
72
- "## Responsibilities",
79
+ "1. You MUST NOT execute work directly. No code changes, no file edits, no git commits.",
80
+ "2. You MUST decompose the assigned issue into worker child issues, then close the assigned issue with `--outcome expanded`.",
81
+ "3. Decomposition MUST be deterministic and minimal. Use `blocks` edges for sequencing.",
73
82
  "",
74
- "- Break down the assigned issue into small, concrete child issues.",
75
- "- Set dependencies (blocks) to enforce ordering. Keep work items atomic.",
76
- "- Do NOT implement code changes directly — delegate to role=worker.",
77
- "- Keep plans deterministic and minimal.",
83
+ "Even if the task looks atomic: create exactly one worker child issue rather than doing the work yourself.",
84
+ "If you catch yourself about to implement: STOP and create/refine worker issues instead.",
85
+ "",
86
+ "Your only job is to create child issues, add any required `blocks` dependencies, and then close yourself with outcome=expanded.",
78
87
  "",
79
88
  "## Workflow",
80
89
  "",
81
- "1. Read the assigned issue to understand the goal.",
82
- "2. Explore the codebase as needed to inform decomposition.",
83
- "3. Create child issues with `mu issues create` (set `--parent` and `--role worker`).",
84
- "4. Set `mu issues dep <src> blocks <dst>` for ordering between children.",
85
- "5. Close your issue with `mu issues close <id> --outcome expanded`.",
90
+ "1. Investigate: `mu issues get <id>`, `mu forum read issue:<id> --limit 20`, `mu issues children <id>`.",
91
+ "2. Decompose: create child issues with `mu issues create` (always set `--parent` and `--role worker`).",
92
+ "3. Order: add `blocks` edges between children where sequencing matters.",
93
+ "4. Close: `mu issues close <id> --outcome expanded`.",
94
+ "",
95
+ "The ONLY valid outcome for you is `expanded`.",
96
+ "Never close with `success`, `failure`, `needs_work`, or `skipped` — those are for workers.",
86
97
  "",
87
98
  "## Rules",
88
99
  "",
89
100
  "- Use only roles: orchestrator, worker.",
90
- "- Assign executable leaves to role=worker.",
91
- "- If a child requires further decomposition, assign role=orchestrator.",
101
+ "- Every executable leaf MUST be `--role worker`.",
102
+ "- Never create a child without an explicit role.",
103
+ "",
104
+ "## Strategies For Good Plans",
105
+ "",
106
+ "- Include feedback loops in worker issues: tests, typecheck, build, lint, repro steps.",
107
+ "- Prefer small issues with crisp acceptance criteria over large ambiguous ones.",
108
+ "- If the work needs verification, add a worker review issue blocked by implementation.",
109
+ " If review fails, that worker should close with outcome=needs_work and describe what failed.",
92
110
  "",
93
111
  MU_CLI_REFERENCE,
94
112
  ].join("\n");
@@ -102,21 +120,21 @@ export function systemPromptForRole(role) {
102
120
  "",
103
121
  "- Implement the work described in your assigned issue.",
104
122
  "- Keep scope tight to the issue specification.",
105
- "- Verify results (tests, typecheck, build, etc.) and report what changed.",
123
+ "- Verify results (tests, typecheck, build, lint, etc.) and report what changed.",
106
124
  "- Close your issue with a terminal outcome when done.",
107
125
  "",
108
126
  "## Workflow",
109
127
  "",
110
- "1. Read the assigned issue to understand what to do.",
111
- "2. Implement the change — edit files, run commands, etc.",
112
- "3. Verify: run tests, build, or whatever validation is appropriate.",
128
+ "1. Inspect: `mu issues get <id>` and `mu forum read issue:<id> --limit 20`.",
129
+ "2. Implement: edit files, run commands, and keep changes scoped to the issue.",
130
+ "3. Verify: run tests/build/typecheck/lint as appropriate. Prefer hard feedback loops.",
113
131
  "4. Close: `mu issues close <id> --outcome success` (or `failure`/`skipped`).",
114
- "5. Optionally log progress: `mu forum post issue:<id> -m '...' --author worker`.",
132
+ "5. Log key notes: `mu forum post issue:<id> -m '...' --author worker`.",
115
133
  "",
116
134
  "## Rules",
117
135
  "",
118
136
  "- Do NOT create child issues — that is the orchestrator's job.",
119
- "- If the issue is too large or unclear, close with `--outcome needs_work`.",
137
+ "- If the issue is too large/unclear, close with `--outcome needs_work` and explain what is missing.",
120
138
  "",
121
139
  MU_CLI_REFERENCE,
122
140
  ].join("\n");
@@ -16,9 +16,11 @@ export type BackendRunOpts = {
16
16
  export interface BackendRunner {
17
17
  run(opts: BackendRunOpts): Promise<number>;
18
18
  }
19
+ export type PiCliArgvOpts = Pick<BackendRunOpts, "prompt" | "systemPrompt" | "provider" | "model" | "thinking">;
20
+ /** Build argv for the `pi` CLI. Exported for regression testing. */
21
+ export declare function buildPiCliArgv(opts: PiCliArgvOpts): string[];
19
22
  export declare function piStreamHasError(line: string): boolean;
20
23
  export declare class PiCliBackend implements BackendRunner {
21
- #private;
22
24
  run(opts: BackendRunOpts): Promise<number>;
23
25
  }
24
26
  //# sourceMappingURL=pi_backend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pi_backend.d.ts","sourceRoot":"","sources":["../src/pi_backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BtD;AAED,qBAAa,YAAa,YAAW,aAAa;;IAKpC,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAuEvD"}
1
+ {"version":3,"file":"pi_backend.d.ts","sourceRoot":"","sources":["../src/pi_backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC;AAEhH,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAgB5D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BtD;AAED,qBAAa,YAAa,YAAW,aAAa;IACpC,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAuEvD"}
@@ -3,6 +3,24 @@ import { mkdir, open } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
4
  import { createInterface } from "node:readline";
5
5
  import { PassThrough } from "node:stream";
6
+ /** Build argv for the `pi` CLI. Exported for regression testing. */
7
+ export function buildPiCliArgv(opts) {
8
+ return [
9
+ "pi",
10
+ "--mode",
11
+ "json",
12
+ "--no-session",
13
+ "--provider",
14
+ opts.provider,
15
+ "--model",
16
+ opts.model,
17
+ "--thinking",
18
+ opts.thinking,
19
+ "--system-prompt",
20
+ opts.systemPrompt,
21
+ opts.prompt,
22
+ ];
23
+ }
6
24
  export function piStreamHasError(line) {
7
25
  let event;
8
26
  try {
@@ -31,14 +49,11 @@ export function piStreamHasError(line) {
31
49
  return false;
32
50
  }
33
51
  export class PiCliBackend {
34
- #buildArgv(prompt, model, thinking) {
35
- return ["pi", "--mode", "json", "--no-session", "--model", model, "--thinking", thinking, prompt];
36
- }
37
52
  async run(opts) {
38
53
  if (opts.cli !== "pi") {
39
54
  throw new Error(`unsupported backend cli=${JSON.stringify(opts.cli)} (only "pi" is supported)`);
40
55
  }
41
- const argv = this.#buildArgv(opts.prompt, opts.model, opts.thinking);
56
+ const argv = buildPiCliArgv(opts);
42
57
  let teeFh = null;
43
58
  try {
44
59
  if (opts.teePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-orchestrator",
3
- "version": "26.2.19",
3
+ "version": "26.2.22",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  "dist/**"
15
15
  ],
16
16
  "dependencies": {
17
- "@femtomc/mu-core": "26.2.19",
18
- "@femtomc/mu-forum": "26.2.19",
19
- "@femtomc/mu-issue": "26.2.19",
17
+ "@femtomc/mu-core": "26.2.21",
18
+ "@femtomc/mu-forum": "26.2.21",
19
+ "@femtomc/mu-issue": "26.2.21",
20
20
  "@mariozechner/pi-agent-core": "^0.52.12",
21
21
  "@mariozechner/pi-coding-agent": "^0.52.12",
22
22
  "@mariozechner/pi-ai": "^0.52.12"