multiclaw 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -21
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/resume.d.ts +4 -5
- package/dist/index.js +81 -42
- package/dist/lib.js +5 -10
- package/dist/reporters/ConsoleReporter.d.ts +2 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -114,6 +114,7 @@ export default defineConfig({
|
|
|
114
114
|
defaults: {
|
|
115
115
|
provider: "claude", // which agent product runs the team
|
|
116
116
|
model: "sonnet", // default model (overridable per agent)
|
|
117
|
+
permissions: { mode: "auto" }, // optional; "auto" | "ask" | "deny"
|
|
117
118
|
},
|
|
118
119
|
|
|
119
120
|
agents: [
|
|
@@ -127,7 +128,7 @@ export default defineConfig({
|
|
|
127
128
|
systemPrompt: "You are a software architect.", // optional
|
|
128
129
|
taskPrompt: `Design the system for {{requirement}}.
|
|
129
130
|
Read the brief at {{file:brief.md}} and write architecture.md.`,
|
|
130
|
-
|
|
131
|
+
permissions: { mode: "ask" }, // optional; overrides the project mode here
|
|
131
132
|
dependsOn: [], // optional; list of agent ids this agent waits for
|
|
132
133
|
timeout: 600_000, // optional; ms, default: 600 000 (10 min)
|
|
133
134
|
retries: 0, // optional; retry count on failure, default: 0
|
|
@@ -138,7 +139,6 @@ Read the brief at {{file:brief.md}} and write architecture.md.`,
|
|
|
138
139
|
name: "Developer",
|
|
139
140
|
model: "sonnet", // override model for this agent
|
|
140
141
|
taskPrompt: "Read architecture.md and implement the code.",
|
|
141
|
-
tools: ["Read", "Write", "Bash"],
|
|
142
142
|
dependsOn: ["architect"],
|
|
143
143
|
},
|
|
144
144
|
],
|
|
@@ -159,6 +159,7 @@ Read the brief at {{file:brief.md}} and write architecture.md.`,
|
|
|
159
159
|
| `logDir` | string | | Log output directory. Default: `workDir/.multiclaw/logs`. |
|
|
160
160
|
| `continueOnError` | boolean | | Keep running when an agent fails. Default: `false`. |
|
|
161
161
|
| `maxConcurrency` | number | | Max agents running in parallel. Default: unlimited. |
|
|
162
|
+
| `permissions` | `{ mode: "auto" \| "ask" \| "deny", onRequest? }` | | Approval mode for tool calls, plus the handler `"ask"` escalates to. Default: `"auto"` for `run`, `"ask"` in the console. |
|
|
162
163
|
|
|
163
164
|
### Agent fields
|
|
164
165
|
|
|
@@ -175,7 +176,7 @@ Read the brief at {{file:brief.md}} and write architecture.md.`,
|
|
|
175
176
|
| `modelTier` | `"strong"` \| `"balanced"` \| `"fast"` | | Declares the capability this agent needs; the catalog maps each tier to a provider-specific model id. Ignored when `model` is set. |
|
|
176
177
|
| `model` | string | | Override the model id for this agent (provider-specific, e.g. `"opus"`, `"sonnet"`, `"haiku"` for Claude). Takes precedence over `modelTier` and `models`. |
|
|
177
178
|
| `models` | Record<string, string> | | Map of provider id (or the legacy `"acp"`) to a model id, e.g. `{ claude: "sonnet", opencode: "opencode/big-pickle" }`. Used when `model`/`modelTier` are unset. |
|
|
178
|
-
| `
|
|
179
|
+
| `permissions` | `{ mode: "auto" \| "ask" \| "deny" }` | | Approval mode for this agent's tool calls. Overrides `permissions` and `defaults.permissions`. |
|
|
179
180
|
| `dependsOn` | string[] | | Agent ids that must complete before this agent starts. |
|
|
180
181
|
| `timeout` | number | | Per-agent timeout in ms. Default: `600000` (10 min). |
|
|
181
182
|
| `retries` | number | | Number of retry attempts on failure. Default: `0`. |
|
|
@@ -229,9 +230,8 @@ TEAM 1/4 │ Architect · sonnet-4 · ⠹ running 4.2s
|
|
|
229
230
|
| Header | Project name, turn number, and the phase or elapsed time |
|
|
230
231
|
| Pipeline rail | The leader, then each stage in order, joined by `──▸` |
|
|
231
232
|
| Team panel | One row per agent: status glyph, name, live time, and progress (`1/4`) |
|
|
232
|
-
| Stream panel |
|
|
233
|
+
| Stream panel | The focused agent's output across every turn of this session, with a `▼` live / `⏸` parked marker |
|
|
233
234
|
| Reply panel | What the leader answered, when a turn needed no pipeline |
|
|
234
|
-
| Review banner | `viewing turn N …` while a past turn is open |
|
|
235
235
|
| Queue strip | Follow-ups typed mid-run, in the order they will run |
|
|
236
236
|
| Prompt | Where you type — live even while a turn is running |
|
|
237
237
|
| Hint bar | Aggregate status on the left, the keys that work *right now* on the right |
|
|
@@ -257,13 +257,14 @@ which is what keeps typing a requirement from tripping over shortcuts.
|
|
|
257
257
|
| Key | Action |
|
|
258
258
|
|-----|--------|
|
|
259
259
|
| `enter` | Run the typed requirement — or queue it while a turn is running |
|
|
260
|
-
| `tab`
|
|
260
|
+
| `tab` | Cycle the focused agent |
|
|
261
|
+
| `shift+tab` | Toggle the permission mode (`auto` ↔ `ask`) |
|
|
261
262
|
| `↑` `↓` `pgup` `pgdn` | Scroll the focused agent's stream |
|
|
262
263
|
| `←` `→` `home` `end` | Move the caret |
|
|
263
|
-
| `esc` |
|
|
264
|
+
| `esc` | Clear the line, or close an overlay |
|
|
264
265
|
| `?` | Key map (idle, empty line only) |
|
|
265
266
|
| `/` | Command menu (empty line only) |
|
|
266
|
-
| `ctrl+l` | Clear the focused agent's
|
|
267
|
+
| `ctrl+l` | Clear the focused agent's output for the current turn |
|
|
267
268
|
| `ctrl+c` | Cancel the running turn *and its queue*; again when idle to quit |
|
|
268
269
|
|
|
269
270
|
Scrolling up parks the stream and scrolling back to the bottom resumes it, so there is no follow
|
|
@@ -280,17 +281,36 @@ the background.
|
|
|
280
281
|
|
|
281
282
|
### Commands
|
|
282
283
|
|
|
283
|
-
Type `/` on an empty line
|
|
284
|
+
Type `/` on an empty line, select an operation and press Enter. Commands that need an
|
|
285
|
+
agent, mode or turn open a selector; search and steering open an input field. `Esc`
|
|
286
|
+
cancels the operation. The menu only offers actions relevant to the current state.
|
|
284
287
|
|
|
285
288
|
| Command | Action |
|
|
286
289
|
|---------|--------|
|
|
287
|
-
| `/
|
|
288
|
-
| `/
|
|
289
|
-
| `/
|
|
290
|
-
| `/
|
|
291
|
-
| `/
|
|
292
|
-
| `/
|
|
293
|
-
| `/
|
|
290
|
+
| `/new` | Open an empty new session; type the requirement afterwards (idle only) |
|
|
291
|
+
| `/sessions` | Select, switch or delete a saved session (idle only) |
|
|
292
|
+
| `/focus` | Select the agent to view |
|
|
293
|
+
| `/mode` | Select `ask`, `auto` or `deny` |
|
|
294
|
+
| `/verbose` | Toggle tool detail |
|
|
295
|
+
| `/search` | Enter search text; `Ctrl+N` / `Ctrl+P` move between matches |
|
|
296
|
+
| `/latest` | Return to the latest output |
|
|
297
|
+
| `/retry` | Select one failed/repair agent or all of them (idle only) |
|
|
298
|
+
| `/replay` | Select a finished turn to run again (idle only) |
|
|
299
|
+
| `/edit` | Select a finished turn and edit its requirement in the prompt |
|
|
300
|
+
| `/export` | Select a finished turn to export as Markdown |
|
|
301
|
+
| `/steer` | Enter a message for agents running now |
|
|
302
|
+
| `/pause` | Pause a non-empty follow-up queue |
|
|
303
|
+
| `/continue` | Resume a paused queue, or continue at a checkpoint |
|
|
304
|
+
|
|
305
|
+
For direct entry, `/focus <id>`, `/mode <mode>`, `/search <text>`,
|
|
306
|
+
`/retry <id>`, `/replay <turn>`, `/edit <turn>`, `/export <turn> [path]`
|
|
307
|
+
and `/steer <text>` are also supported. `/new` takes no arguments.
|
|
308
|
+
Editing a previous requirement does not branch the session or restore files.
|
|
309
|
+
|
|
310
|
+
Approvals and agent questions are handled in their panels. Leader clarification and
|
|
311
|
+
checkpoint feedback use ordinary input. `Ctrl+L` clears the current output, `?`
|
|
312
|
+
opens help, `Ctrl+C` cancels a running turn and its queue or exits when idle with
|
|
313
|
+
an empty prompt, and `Ctrl+D` exits with an empty prompt (also while running).
|
|
294
314
|
|
|
295
315
|
The console renders on the alternate screen buffer, so your shell scrollback is preserved.
|
|
296
316
|
|
|
@@ -306,11 +326,29 @@ are per-run: `workDir/.multiclaw/runs/run-<timestamp>/logs/`.
|
|
|
306
326
|
|
|
307
327
|
### History
|
|
308
328
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
20 finished turns are kept.
|
|
329
|
+
The stream panel shows the focused agent's whole session: every turn it took part in, oldest first,
|
|
330
|
+
separated by dim `──── turn N · <requirement> ────` rows. Scrolling up walks back through it — there
|
|
331
|
+
is no separate "open a past turn" mode and no `esc` to leave one. `tab` switches agent, and each
|
|
332
|
+
agent keeps its own timeline. The header appends `· N turns` once an agent has run more than once.
|
|
333
|
+
The last 20 finished turns are kept.
|
|
334
|
+
|
|
335
|
+
The team panel and the pipeline rail stay bound to the live turn, so the orchestration context does
|
|
336
|
+
not change while you are reading history.
|
|
337
|
+
|
|
338
|
+
### Permission modes
|
|
339
|
+
|
|
340
|
+
`permissions: { mode: ... }` decides what happens to a provider's tool-permission request:
|
|
341
|
+
|
|
342
|
+
| Mode | Behaviour |
|
|
343
|
+
|------|-----------|
|
|
344
|
+
| `"auto"` | Every request is approved. No prompts. |
|
|
345
|
+
| `"ask"` | Every request is escalated to a human — the console's permission bar, or `permissions.onRequest`. With nobody to ask, it is refused. |
|
|
346
|
+
| `"deny"` | Every request is refused. |
|
|
347
|
+
|
|
348
|
+
`multiclaw run` has nobody to ask, so `"ask"` without an `onRequest` handler refuses every tool call;
|
|
349
|
+
the run warns about this before it starts. The console starts in `"ask"` unless the config says
|
|
350
|
+
otherwise. `shift+tab` toggles between `auto` and `ask` at runtime, and `/mode auto|ask|deny` sets any
|
|
351
|
+
of the three; the active mode is shown in the header.
|
|
314
352
|
|
|
315
353
|
On a narrow terminal (under 72 columns) the team panel is dropped and the stream takes the full
|
|
316
354
|
width; on a short one (under 16 rows) the pipeline rail goes too. The header, prompt and hint bar are
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type OrchestratorResult } from "@multiclawcli/core";
|
|
2
2
|
/** A previous run's report, as much of it as resume needs. */
|
|
3
3
|
export interface ResumeTarget {
|
|
4
4
|
runDir: string;
|
|
@@ -22,14 +22,13 @@ export declare function readReport(runDir: string): OrchestratorResult;
|
|
|
22
22
|
/**
|
|
23
23
|
* Turn a report (plus an optional requirement override) into run config.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* both sides is an error rather than a silently empty run.
|
|
25
|
+
* An explicit requirement override wins. Missing requirements fail rather than
|
|
26
|
+
* silently starting an empty run.
|
|
28
27
|
*/
|
|
29
28
|
export declare function resumeOptionsFromReport(report: OrchestratorResult, requirementOverride?: string): {
|
|
30
29
|
requirement: string;
|
|
31
30
|
resume: {
|
|
32
31
|
agents: string[];
|
|
33
|
-
sessions?: Record<string,
|
|
32
|
+
sessions?: Record<string, import("@multiclawcli/core").SessionRecord>;
|
|
34
33
|
};
|
|
35
34
|
};
|
package/dist/index.js
CHANGED
|
@@ -9,11 +9,8 @@ import { Command } from "commander";
|
|
|
9
9
|
// src/commands/doctor.ts
|
|
10
10
|
import {
|
|
11
11
|
detectProviders,
|
|
12
|
-
hasExplicitTools,
|
|
13
12
|
presentProviders,
|
|
14
|
-
resolveCommand,
|
|
15
13
|
resolveProvider,
|
|
16
|
-
specForCommand,
|
|
17
14
|
specForProvider
|
|
18
15
|
} from "@multiclawcli/core";
|
|
19
16
|
|
|
@@ -116,21 +113,11 @@ function buildDoctorReport(detected, config) {
|
|
|
116
113
|
const agents2 = [...config.agents];
|
|
117
114
|
if (config.leader && config.useLeader !== false) agents2.push(config.leader);
|
|
118
115
|
const agentsWithMissingProvider = agents2.map((a) => ({ id: a.id, intended: resolveProvider(a, config) })).filter((a) => !present.includes(a.intended)).map((a) => a.id);
|
|
119
|
-
const agentsWithUnsupportedTools = agents2.map((a) => {
|
|
120
|
-
const command = resolveCommand(a, config);
|
|
121
|
-
const spec = specForCommand(command) ?? specForProvider(resolveProvider(a, config));
|
|
122
|
-
return {
|
|
123
|
-
id: a.id,
|
|
124
|
-
spec,
|
|
125
|
-
explicitTools: hasExplicitTools(a) || a.tools === void 0 && !!config.defaults?.tools
|
|
126
|
-
};
|
|
127
|
-
}).filter((a) => a.spec && !a.spec.tools && a.explicitTools).map((a) => a.id);
|
|
128
116
|
configReport = {
|
|
129
117
|
name: config.name,
|
|
130
118
|
defaultsProvider,
|
|
131
119
|
defaultsProviderOk,
|
|
132
|
-
agentsWithMissingProvider
|
|
133
|
-
agentsWithUnsupportedTools
|
|
120
|
+
agentsWithMissingProvider
|
|
134
121
|
};
|
|
135
122
|
}
|
|
136
123
|
return {
|
|
@@ -194,14 +181,6 @@ function formatDoctorReport(report, useColor = true) {
|
|
|
194
181
|
} else {
|
|
195
182
|
lines.push(c(GREEN, " all agents resolve to an installed provider \u2713"));
|
|
196
183
|
}
|
|
197
|
-
if (report.config.agentsWithUnsupportedTools.length > 0) {
|
|
198
|
-
lines.push(
|
|
199
|
-
c(
|
|
200
|
-
YELLOW,
|
|
201
|
-
` ${report.config.agentsWithUnsupportedTools.length} agent(s) use a provider that cannot enforce tool allowlists: ${report.config.agentsWithUnsupportedTools.join(", ")}`
|
|
202
|
-
)
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
184
|
}
|
|
206
185
|
lines.push("");
|
|
207
186
|
if (report.runnable) {
|
|
@@ -271,6 +250,8 @@ var PRESET_DESCRIPTIONS = {
|
|
|
271
250
|
function generateConfig(opts) {
|
|
272
251
|
const name = JSON.stringify(opts.projectName);
|
|
273
252
|
const workDir = JSON.stringify(opts.workDir);
|
|
253
|
+
const permissions = `
|
|
254
|
+
permissions: { mode: "${opts.permissionMode}" },`;
|
|
274
255
|
return `import { defineConfig, agents } from "multiclaw"
|
|
275
256
|
|
|
276
257
|
export default defineConfig({
|
|
@@ -281,7 +262,7 @@ export default defineConfig({
|
|
|
281
262
|
projectName: ${name},
|
|
282
263
|
},
|
|
283
264
|
defaults: {
|
|
284
|
-
provider: ${JSON.stringify(opts.provider)}
|
|
265
|
+
provider: ${JSON.stringify(opts.provider)},${permissions}
|
|
285
266
|
},
|
|
286
267
|
agents: [
|
|
287
268
|
${AGENT_BLOCKS[opts.preset]}
|
|
@@ -330,8 +311,18 @@ async function initCommand(options = {}) {
|
|
|
330
311
|
message: "Work directory (where agents read and write files):",
|
|
331
312
|
default: "."
|
|
332
313
|
});
|
|
314
|
+
const permissionMode = await select({
|
|
315
|
+
message: "How should multiclaw handle tool permissions?",
|
|
316
|
+
choices: [
|
|
317
|
+
{ value: "auto", name: "Auto \u2014 agents run without permission prompts (recommended)" },
|
|
318
|
+
{ value: "ask", name: "Manual \u2014 confirm each action before it runs" }
|
|
319
|
+
]
|
|
320
|
+
});
|
|
333
321
|
try {
|
|
334
|
-
fs2.writeFileSync(
|
|
322
|
+
fs2.writeFileSync(
|
|
323
|
+
configPath,
|
|
324
|
+
generateConfig({ preset, workDir, projectName, provider, permissionMode })
|
|
325
|
+
);
|
|
335
326
|
if (workDir !== ".") {
|
|
336
327
|
fs2.mkdirSync(path2.resolve(process.cwd(), workDir), { recursive: true });
|
|
337
328
|
}
|
|
@@ -350,6 +341,7 @@ async function initCommand(options = {}) {
|
|
|
350
341
|
);
|
|
351
342
|
}
|
|
352
343
|
console.log(` workDir: ${workDir}`);
|
|
344
|
+
console.log(` mode: ${permissionMode}`);
|
|
353
345
|
console.log("");
|
|
354
346
|
if (detectedProvider?.status === "missing") {
|
|
355
347
|
console.log(`\x1B[33m\u26A0 ${spec.label} is not installed.\x1B[0m`);
|
|
@@ -629,22 +621,24 @@ import * as fs6 from "fs";
|
|
|
629
621
|
import * as path6 from "path";
|
|
630
622
|
import {
|
|
631
623
|
describeResumePlan,
|
|
632
|
-
Orchestrator as OrchestratorImpl
|
|
624
|
+
Orchestrator as OrchestratorImpl,
|
|
625
|
+
resolvePermissionMode
|
|
633
626
|
} from "@multiclawcli/core";
|
|
634
627
|
|
|
635
628
|
// src/lib.ts
|
|
636
629
|
import {
|
|
637
630
|
detectProviders as detectProviders3,
|
|
638
|
-
hasExplicitTools as hasExplicitTools2,
|
|
639
631
|
presentProviders as presentProviders2,
|
|
640
|
-
resolveCommand
|
|
641
|
-
runtimeRegistry
|
|
642
|
-
validateToolsPolicy
|
|
632
|
+
resolveCommand,
|
|
633
|
+
runtimeRegistry
|
|
643
634
|
} from "@multiclawcli/core";
|
|
644
635
|
import { agents, defineConfig } from "@multiclawcli/core";
|
|
645
636
|
async function assertRuntimesAvailable(config) {
|
|
646
637
|
const checked = /* @__PURE__ */ new Set();
|
|
647
|
-
const agents2 =
|
|
638
|
+
const agents2 = [
|
|
639
|
+
...config.leader && config.useLeader !== false ? [config.leader] : [],
|
|
640
|
+
...config.agents
|
|
641
|
+
];
|
|
648
642
|
for (const agent of agents2) {
|
|
649
643
|
const name = agent.runtime ?? "acp";
|
|
650
644
|
if (!runtimeRegistry.has(name)) {
|
|
@@ -652,7 +646,7 @@ async function assertRuntimesAvailable(config) {
|
|
|
652
646
|
console.error(`\x1B[31mError: Unknown runtime "${name}". Supported: ${supported}\x1B[0m`);
|
|
653
647
|
process.exit(1);
|
|
654
648
|
}
|
|
655
|
-
const command =
|
|
649
|
+
const command = resolveCommand(agent, config);
|
|
656
650
|
const key = `${name}\0${command}`;
|
|
657
651
|
if (checked.has(key)) continue;
|
|
658
652
|
checked.add(key);
|
|
@@ -665,15 +659,12 @@ async function assertRuntimesAvailable(config) {
|
|
|
665
659
|
process.exit(1);
|
|
666
660
|
}
|
|
667
661
|
}
|
|
668
|
-
try {
|
|
669
|
-
await validateToolsPolicy(config);
|
|
670
|
-
} catch (error) {
|
|
671
|
-
console.error(`\x1B[31mError: ${error instanceof Error ? error.message : String(error)}\x1B[0m`);
|
|
672
|
-
process.exit(1);
|
|
673
|
-
}
|
|
674
662
|
}
|
|
675
663
|
|
|
676
664
|
// src/reporters/ConsoleReporter.ts
|
|
665
|
+
import {
|
|
666
|
+
describeSession
|
|
667
|
+
} from "@multiclawcli/core";
|
|
677
668
|
var isFancy = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY === true;
|
|
678
669
|
var S = isFancy ? { ok: "\u2713", fail: "\u2717", run: "\u203A", retry: "\u21BB", skip: "\u2298", warn: "\u26A0", bullet: "\xB7" } : { ok: "+", fail: "x", run: ">", retry: "~", skip: "-", warn: "!", bullet: "*" };
|
|
679
670
|
var C = isFancy ? {
|
|
@@ -697,6 +688,7 @@ var C = isFancy ? {
|
|
|
697
688
|
};
|
|
698
689
|
function markFor(status) {
|
|
699
690
|
if (status === "success") return { icon: S.ok, color: C.green };
|
|
691
|
+
if (status === "handed_back") return { icon: S.retry, color: C.yellow };
|
|
700
692
|
if (status === "cancelled") return { icon: S.skip, color: C.yellow };
|
|
701
693
|
return { icon: S.fail, color: C.red };
|
|
702
694
|
}
|
|
@@ -706,6 +698,13 @@ var ConsoleReporter = class {
|
|
|
706
698
|
}
|
|
707
699
|
handle(e) {
|
|
708
700
|
switch (e.type) {
|
|
701
|
+
case "agent:session": {
|
|
702
|
+
if (e.payload.action === "state_changed") break;
|
|
703
|
+
console.log(
|
|
704
|
+
e.payload.record.agentId + ": " + e.payload.action.replaceAll("_", " ") + (e.payload.requestedSessionId ? " target=" + e.payload.requestedSessionId : "") + " \u2014 " + describeSession(e.payload.record) + (e.payload.reason ? " \u2014 " + e.payload.reason : "")
|
|
705
|
+
);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
709
708
|
case "orchestration:start": {
|
|
710
709
|
const line = (isFancy ? "\u2550" : "=").repeat(50);
|
|
711
710
|
const edge = isFancy ? ["\u2554", "\u2551", "\u255A", "\u2557", "\u255D"] : ["+", "|", "+", "+", "+"];
|
|
@@ -723,6 +722,23 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
723
722
|
}
|
|
724
723
|
break;
|
|
725
724
|
}
|
|
725
|
+
case "orchestration:route": {
|
|
726
|
+
const route = JSON.stringify(e.payload.routing);
|
|
727
|
+
if (route === this.lastRoute) break;
|
|
728
|
+
this.lastRoute = route;
|
|
729
|
+
console.log(
|
|
730
|
+
"Route: " + e.payload.routing.mode + " work=" + (e.payload.routing.workId ?? "unknown") + (e.payload.routing.reroutes ? " (rerouting once)" : "")
|
|
731
|
+
);
|
|
732
|
+
break;
|
|
733
|
+
}
|
|
734
|
+
case "orchestration:routing-error": {
|
|
735
|
+
console.log("Routing incomplete: " + e.payload.message);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
case "agent:handed-back": {
|
|
739
|
+
console.log(e.payload.agentName + " handed back: " + e.payload.handoffReason);
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
726
742
|
case "orchestration:stages": {
|
|
727
743
|
console.log(
|
|
728
744
|
`${C.dim}${S.bullet} Plan applied \u2014 ${e.payload.stages.length} stages${C.reset}`
|
|
@@ -779,7 +795,7 @@ ${C.cyan}${mark} ${reply.message}${C.reset}`);
|
|
|
779
795
|
}
|
|
780
796
|
const line = (isFancy ? "\u2550" : "=").repeat(50);
|
|
781
797
|
const edge = isFancy ? ["\u2554", "\u2551", "\u255A", "\u2557", "\u255D"] : ["+", "|", "+", "+", "+"];
|
|
782
|
-
const title = reply ? reply.mode === "ask" ? `${S.warn} Needs your input` : `${S.ok} Answered` : acceptance ? acceptance.status === "approved" ? `${S.ok} Approved` : acceptance.status === "changes_required" ? `${S.warn} Changes required` : acceptance.status === "cancelled" ? `${S.skip} Acceptance cancelled` : `${S.fail} Acceptance failed` : e.payload.cancelled ? `${S.skip} Cancelled` : success ? `${S.ok} All done` : `${S.warn} Done (with failures)`;
|
|
798
|
+
const title = e.payload.routing?.error ? `${S.warn} Work incomplete` : reply ? reply.mode === "ask" ? `${S.warn} Needs your input` : `${S.ok} Answered` : acceptance ? acceptance.status === "approved" ? `${S.ok} Approved` : acceptance.status === "changes_required" ? `${S.warn} Changes required` : acceptance.status === "cancelled" ? `${S.skip} Acceptance cancelled` : `${S.fail} Acceptance failed` : e.payload.cancelled ? `${S.skip} Cancelled` : success ? `${S.ok} All done` : `${S.warn} Done (with failures)`;
|
|
783
799
|
console.log(`
|
|
784
800
|
${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
785
801
|
console.log(`${edge[1]} ${title.padEnd(48)}${edge[1]}`);
|
|
@@ -800,6 +816,7 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
800
816
|
const cost = e.payload.totalCostUsd;
|
|
801
817
|
const costStr = cost === void 0 ? "" : ` $${cost.toFixed(2)}`;
|
|
802
818
|
console.log(` ~ ${"Total".padEnd(nameWidth)} ${totalStr.padStart(7)}${costStr}`);
|
|
819
|
+
if (e.payload.routing?.reroutes) console.log(" Execution attempts (audit):");
|
|
803
820
|
for (const r of agentResults) {
|
|
804
821
|
const { icon, color: color2 } = markFor(r.status);
|
|
805
822
|
const dur = `${(r.duration / 1e3).toFixed(1)}s`;
|
|
@@ -807,6 +824,12 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
807
824
|
` ${color2}${icon}${C.reset} ${r.agentName.padEnd(nameWidth)} ${dur.padStart(7)}`
|
|
808
825
|
);
|
|
809
826
|
}
|
|
827
|
+
if (e.payload.routing?.reroutes) {
|
|
828
|
+
console.log(" Effective results:");
|
|
829
|
+
for (const result of e.payload.effectiveAgentResults ?? [])
|
|
830
|
+
console.log(" " + result.agentId + ": " + result.status);
|
|
831
|
+
}
|
|
832
|
+
if (e.payload.routing?.error) console.log(" " + e.payload.routing.error);
|
|
810
833
|
if (e.payload.deliverables && e.payload.deliverables.length > 0) {
|
|
811
834
|
console.log(`
|
|
812
835
|
Deliverables:`);
|
|
@@ -905,6 +928,7 @@ function errorMessage(err) {
|
|
|
905
928
|
// src/commands/resume.ts
|
|
906
929
|
import * as fs5 from "fs";
|
|
907
930
|
import * as path5 from "path";
|
|
931
|
+
import { isSessionRecord } from "@multiclawcli/core";
|
|
908
932
|
function resolveRunDir(runsRoot, target) {
|
|
909
933
|
if (typeof target !== "string" || target === "latest") {
|
|
910
934
|
return latestRunDir(runsRoot);
|
|
@@ -940,14 +964,18 @@ function readReport(runDir) {
|
|
|
940
964
|
}
|
|
941
965
|
}
|
|
942
966
|
function resumeOptionsFromReport(report, requirementOverride) {
|
|
943
|
-
const latest = new Map(
|
|
967
|
+
const latest = new Map(
|
|
968
|
+
(report.effectiveAgentResults ?? report.agentResults).map((result) => [result.agentId, result])
|
|
969
|
+
);
|
|
944
970
|
const agents2 = [...latest.keys()].filter((id) => id !== report.acceptance?.reviewer);
|
|
945
971
|
if (agents2.length === 0) {
|
|
946
972
|
throw new Error("The previous run has no agent results \u2014 nothing to resume");
|
|
947
973
|
}
|
|
948
974
|
const sessions2 = {};
|
|
949
975
|
for (const result of latest.values()) {
|
|
950
|
-
if (result.
|
|
976
|
+
if (result.session !== void 0 && !isSessionRecord(result.session))
|
|
977
|
+
throw new Error("Invalid Session metadata for " + result.agentId);
|
|
978
|
+
if (result.session?.sessionId) sessions2[result.agentId] = result.session;
|
|
951
979
|
}
|
|
952
980
|
const requirement = requirementOverride ?? report.requirement;
|
|
953
981
|
if (requirement === void 0) {
|
|
@@ -957,8 +985,7 @@ function resumeOptionsFromReport(report, requirementOverride) {
|
|
|
957
985
|
requirement,
|
|
958
986
|
resume: {
|
|
959
987
|
agents: agents2,
|
|
960
|
-
//
|
|
961
|
-
// fresh", which is exactly the single-agent retry of a failed agent.
|
|
988
|
+
// No established Session means the fixed retry starts a fresh executor.
|
|
962
989
|
...Object.keys(sessions2).length > 0 ? { sessions: sessions2 } : {}
|
|
963
990
|
}
|
|
964
991
|
};
|
|
@@ -979,6 +1006,7 @@ async function runCommand(requirementArg, options) {
|
|
|
979
1006
|
const { requirement, resume } = resumeOptionsFromReport(report, requirementArg);
|
|
980
1007
|
config.workDir = runDir;
|
|
981
1008
|
config.resume = resume;
|
|
1009
|
+
config.latestWork = report.work;
|
|
982
1010
|
config.context = { ...config.context, requirement };
|
|
983
1011
|
config.logDir = path6.join(runDir, `logs-resume-${stamp}`);
|
|
984
1012
|
const plan = describeResumePlan({
|
|
@@ -997,6 +1025,11 @@ async function runCommand(requirementArg, options) {
|
|
|
997
1025
|
return;
|
|
998
1026
|
}
|
|
999
1027
|
await assertRuntimesAvailable(config);
|
|
1028
|
+
if (resolvePermissionMode(config) === "ask" && config.permissions?.onRequest === void 0) {
|
|
1029
|
+
console.warn(
|
|
1030
|
+
'\x1B[33mWarning: permission mode is "ask" but no permissions.onRequest handler is configured \u2014 every tool call will be refused. Set mode to "auto", or supply a handler.\x1B[0m'
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1000
1033
|
const before = snapshotFiles(config.workDir);
|
|
1001
1034
|
const orchestrator = new OrchestratorImpl(config);
|
|
1002
1035
|
orchestrator.eventBus.subscribe((event) => {
|
|
@@ -1024,6 +1057,8 @@ async function runCommand(requirementArg, options) {
|
|
|
1024
1057
|
}
|
|
1025
1058
|
function printDryRun(preview, workDir) {
|
|
1026
1059
|
console.log("Dry run \u2014 no pipeline agents will execute.");
|
|
1060
|
+
if (preview.routing)
|
|
1061
|
+
console.log("Route: " + preview.routing.mode + " work=" + preview.routing.workId);
|
|
1027
1062
|
console.log(`Project: ${preview.name}`);
|
|
1028
1063
|
console.log(`Work dir: ${workDir}`);
|
|
1029
1064
|
if (preview.requirement) console.log(`Requirement: ${preview.requirement}`);
|
|
@@ -1031,6 +1066,10 @@ function printDryRun(preview, workDir) {
|
|
|
1031
1066
|
console.log(`${preview.reply.mode === "ask" ? "Question" : "Reply"}: ${preview.reply.message}`);
|
|
1032
1067
|
return;
|
|
1033
1068
|
}
|
|
1069
|
+
for (const session of preview.sessions ?? [])
|
|
1070
|
+
console.log(
|
|
1071
|
+
"Session intent: " + session.agentId + " " + session.action + " \u2014 " + session.reason
|
|
1072
|
+
);
|
|
1034
1073
|
console.log(`Agents: ${preview.totalAgents}`);
|
|
1035
1074
|
if (preview.selectionReason) console.log(`Why: ${preview.selectionReason}`);
|
|
1036
1075
|
for (const stage of preview.stages) {
|
package/dist/lib.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// src/lib.ts
|
|
2
2
|
import {
|
|
3
3
|
detectProviders,
|
|
4
|
-
hasExplicitTools,
|
|
5
4
|
presentProviders,
|
|
6
5
|
resolveCommand,
|
|
7
|
-
runtimeRegistry
|
|
8
|
-
validateToolsPolicy
|
|
6
|
+
runtimeRegistry
|
|
9
7
|
} from "@multiclawcli/core";
|
|
10
8
|
import { agents, defineConfig } from "@multiclawcli/core";
|
|
11
9
|
async function assertRuntimesAvailable(config) {
|
|
12
10
|
const checked = /* @__PURE__ */ new Set();
|
|
13
|
-
const agents2 =
|
|
11
|
+
const agents2 = [
|
|
12
|
+
...config.leader && config.useLeader !== false ? [config.leader] : [],
|
|
13
|
+
...config.agents
|
|
14
|
+
];
|
|
14
15
|
for (const agent of agents2) {
|
|
15
16
|
const name = agent.runtime ?? "acp";
|
|
16
17
|
if (!runtimeRegistry.has(name)) {
|
|
@@ -31,12 +32,6 @@ async function assertRuntimesAvailable(config) {
|
|
|
31
32
|
process.exit(1);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
try {
|
|
35
|
-
await validateToolsPolicy(config);
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.error(`\x1B[31mError: ${error instanceof Error ? error.message : String(error)}\x1B[0m`);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
35
|
}
|
|
41
36
|
async function availableRuntimes() {
|
|
42
37
|
const detected = await detectProviders();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"multiclaw": "dist/index.js"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@agentclientprotocol/claude-agent-acp": "^0.95.0",
|
|
31
31
|
"@agentclientprotocol/codex-acp": "^2.2.2",
|
|
32
32
|
"@inquirer/prompts": "^8.7.4",
|
|
33
|
-
"@multiclawcli/core": "0.
|
|
34
|
-
"@multiclawcli/dashboard": "0.
|
|
33
|
+
"@multiclawcli/core": "0.14.0",
|
|
34
|
+
"@multiclawcli/dashboard": "0.14.0",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|