multiclaw 0.11.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 +31 -34
- 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,17 +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 { id: a.id, spec, explicitTools: hasExplicitTools(a) };
|
|
123
|
-
}).filter((a) => a.spec && !a.spec.tools && a.explicitTools).map((a) => a.id);
|
|
124
116
|
configReport = {
|
|
125
117
|
name: config.name,
|
|
126
118
|
defaultsProvider,
|
|
127
119
|
defaultsProviderOk,
|
|
128
|
-
agentsWithMissingProvider
|
|
129
|
-
agentsWithUnsupportedTools
|
|
120
|
+
agentsWithMissingProvider
|
|
130
121
|
};
|
|
131
122
|
}
|
|
132
123
|
return {
|
|
@@ -190,14 +181,6 @@ function formatDoctorReport(report, useColor = true) {
|
|
|
190
181
|
} else {
|
|
191
182
|
lines.push(c(GREEN, " all agents resolve to an installed provider \u2713"));
|
|
192
183
|
}
|
|
193
|
-
if (report.config.agentsWithUnsupportedTools.length > 0) {
|
|
194
|
-
lines.push(
|
|
195
|
-
c(
|
|
196
|
-
YELLOW,
|
|
197
|
-
` ${report.config.agentsWithUnsupportedTools.length} agent(s) use a provider that cannot enforce tool allowlists: ${report.config.agentsWithUnsupportedTools.join(", ")}`
|
|
198
|
-
)
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
184
|
}
|
|
202
185
|
lines.push("");
|
|
203
186
|
if (report.runnable) {
|
|
@@ -267,6 +250,8 @@ var PRESET_DESCRIPTIONS = {
|
|
|
267
250
|
function generateConfig(opts) {
|
|
268
251
|
const name = JSON.stringify(opts.projectName);
|
|
269
252
|
const workDir = JSON.stringify(opts.workDir);
|
|
253
|
+
const permissions = `
|
|
254
|
+
permissions: { mode: "${opts.permissionMode}" },`;
|
|
270
255
|
return `import { defineConfig, agents } from "multiclaw"
|
|
271
256
|
|
|
272
257
|
export default defineConfig({
|
|
@@ -277,7 +262,7 @@ export default defineConfig({
|
|
|
277
262
|
projectName: ${name},
|
|
278
263
|
},
|
|
279
264
|
defaults: {
|
|
280
|
-
provider: ${JSON.stringify(opts.provider)}
|
|
265
|
+
provider: ${JSON.stringify(opts.provider)},${permissions}
|
|
281
266
|
},
|
|
282
267
|
agents: [
|
|
283
268
|
${AGENT_BLOCKS[opts.preset]}
|
|
@@ -326,8 +311,18 @@ async function initCommand(options = {}) {
|
|
|
326
311
|
message: "Work directory (where agents read and write files):",
|
|
327
312
|
default: "."
|
|
328
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
|
+
});
|
|
329
321
|
try {
|
|
330
|
-
fs2.writeFileSync(
|
|
322
|
+
fs2.writeFileSync(
|
|
323
|
+
configPath,
|
|
324
|
+
generateConfig({ preset, workDir, projectName, provider, permissionMode })
|
|
325
|
+
);
|
|
331
326
|
if (workDir !== ".") {
|
|
332
327
|
fs2.mkdirSync(path2.resolve(process.cwd(), workDir), { recursive: true });
|
|
333
328
|
}
|
|
@@ -346,6 +341,7 @@ async function initCommand(options = {}) {
|
|
|
346
341
|
);
|
|
347
342
|
}
|
|
348
343
|
console.log(` workDir: ${workDir}`);
|
|
344
|
+
console.log(` mode: ${permissionMode}`);
|
|
349
345
|
console.log("");
|
|
350
346
|
if (detectedProvider?.status === "missing") {
|
|
351
347
|
console.log(`\x1B[33m\u26A0 ${spec.label} is not installed.\x1B[0m`);
|
|
@@ -625,22 +621,24 @@ import * as fs6 from "fs";
|
|
|
625
621
|
import * as path6 from "path";
|
|
626
622
|
import {
|
|
627
623
|
describeResumePlan,
|
|
628
|
-
Orchestrator as OrchestratorImpl
|
|
624
|
+
Orchestrator as OrchestratorImpl,
|
|
625
|
+
resolvePermissionMode
|
|
629
626
|
} from "@multiclawcli/core";
|
|
630
627
|
|
|
631
628
|
// src/lib.ts
|
|
632
629
|
import {
|
|
633
630
|
detectProviders as detectProviders3,
|
|
634
|
-
hasExplicitTools as hasExplicitTools2,
|
|
635
631
|
presentProviders as presentProviders2,
|
|
636
|
-
resolveCommand
|
|
637
|
-
runtimeRegistry
|
|
638
|
-
validateToolsPolicy
|
|
632
|
+
resolveCommand,
|
|
633
|
+
runtimeRegistry
|
|
639
634
|
} from "@multiclawcli/core";
|
|
640
635
|
import { agents, defineConfig } from "@multiclawcli/core";
|
|
641
636
|
async function assertRuntimesAvailable(config) {
|
|
642
637
|
const checked = /* @__PURE__ */ new Set();
|
|
643
|
-
const agents2 =
|
|
638
|
+
const agents2 = [
|
|
639
|
+
...config.leader && config.useLeader !== false ? [config.leader] : [],
|
|
640
|
+
...config.agents
|
|
641
|
+
];
|
|
644
642
|
for (const agent of agents2) {
|
|
645
643
|
const name = agent.runtime ?? "acp";
|
|
646
644
|
if (!runtimeRegistry.has(name)) {
|
|
@@ -648,7 +646,7 @@ async function assertRuntimesAvailable(config) {
|
|
|
648
646
|
console.error(`\x1B[31mError: Unknown runtime "${name}". Supported: ${supported}\x1B[0m`);
|
|
649
647
|
process.exit(1);
|
|
650
648
|
}
|
|
651
|
-
const command =
|
|
649
|
+
const command = resolveCommand(agent, config);
|
|
652
650
|
const key = `${name}\0${command}`;
|
|
653
651
|
if (checked.has(key)) continue;
|
|
654
652
|
checked.add(key);
|
|
@@ -661,12 +659,6 @@ async function assertRuntimesAvailable(config) {
|
|
|
661
659
|
process.exit(1);
|
|
662
660
|
}
|
|
663
661
|
}
|
|
664
|
-
try {
|
|
665
|
-
await validateToolsPolicy(config);
|
|
666
|
-
} catch (error) {
|
|
667
|
-
console.error(`\x1B[31mError: ${error instanceof Error ? error.message : String(error)}\x1B[0m`);
|
|
668
|
-
process.exit(1);
|
|
669
|
-
}
|
|
670
662
|
}
|
|
671
663
|
|
|
672
664
|
// src/reporters/ConsoleReporter.ts
|
|
@@ -993,6 +985,11 @@ async function runCommand(requirementArg, options) {
|
|
|
993
985
|
return;
|
|
994
986
|
}
|
|
995
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
|
+
}
|
|
996
993
|
const before = snapshotFiles(config.workDir);
|
|
997
994
|
const orchestrator = new OrchestratorImpl(config);
|
|
998
995
|
orchestrator.eventBus.subscribe((event) => {
|
|
@@ -1124,7 +1121,7 @@ function initialSession(options) {
|
|
|
1124
1121
|
...typeof options.resume === "string" ? { id: options.resume } : {}
|
|
1125
1122
|
};
|
|
1126
1123
|
}
|
|
1127
|
-
return
|
|
1124
|
+
return { kind: "new" };
|
|
1128
1125
|
}
|
|
1129
1126
|
async function tuiCommand(options) {
|
|
1130
1127
|
const config = await loadConfig(options.config);
|
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
|
},
|