multiclaw 0.12.0 → 0.13.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/index.js +30 -37
- package/dist/lib.js +5 -10
- 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
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,12 +659,6 @@ 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
|
|
@@ -997,6 +985,11 @@ async function runCommand(requirementArg, options) {
|
|
|
997
985
|
return;
|
|
998
986
|
}
|
|
999
987
|
await assertRuntimesAvailable(config);
|
|
988
|
+
if (resolvePermissionMode(config) === "ask" && config.permissions?.onRequest === void 0) {
|
|
989
|
+
console.warn(
|
|
990
|
+
'\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'
|
|
991
|
+
);
|
|
992
|
+
}
|
|
1000
993
|
const before = snapshotFiles(config.workDir);
|
|
1001
994
|
const orchestrator = new OrchestratorImpl(config);
|
|
1002
995
|
orchestrator.eventBus.subscribe((event) => {
|
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.13.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.13.0",
|
|
34
|
+
"@multiclawcli/dashboard": "0.13.0",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|