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 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
- tools: ["Read", "Write"],
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
- | `tools` | string[] | | Allowed tools. Default: `["Read", "Write", "Bash"]`. |
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 | Live output of the focused agent, with a `▼` live / `⏸` parked marker |
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` / `shift+tab` | Cycle the focused agent |
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` | Stop reading a past turn, clear the line, or close an overlay |
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 log |
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 for a filtered list.
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
- | `/agents` | List the configured team |
288
- | `/focus <id>` | Focus an agent by id |
289
- | `/clear` | Clear the focused agent's log |
290
- | `/history` | Browse finished turns |
291
- | `/cancel` | Cancel the running turn and its queue |
292
- | `/help` | Key map |
293
- | `/quit` | Exit |
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
- `/history` lists finished turns newest first, with each turn's outcome, duration, agent count and
310
- requirement. `↑` `↓` select and `enter` opens one for reading — the panes render that turn behind a
311
- `viewing turn N` banner, and `esc` returns to the live turn. Scrolling and `tab` apply to what you
312
- are reading, never to the turn that is still running. The turn on screen is not listed, and the last
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
@@ -20,7 +20,6 @@ export interface DoctorReport {
20
20
  defaultsProvider?: string;
21
21
  defaultsProviderOk: boolean;
22
22
  agentsWithMissingProvider: string[];
23
- agentsWithUnsupportedTools: string[];
24
23
  };
25
24
  runnable: boolean;
26
25
  }
@@ -14,6 +14,7 @@ export declare function generateConfig(opts: {
14
14
  workDir: string;
15
15
  projectName: string;
16
16
  provider: string;
17
+ permissionMode: "auto" | "ask";
17
18
  }): string;
18
19
  export declare function initCommand(options?: {
19
20
  force?: boolean;
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(configPath, generateConfig({ preset, workDir, projectName, provider }));
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 as resolveCommand2,
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 = config.leader && config.useLeader !== false && hasExplicitTools2(config.leader) ? [config.leader, ...config.agents] : config.agents;
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 = resolveCommand2(agent, config);
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 = config.leader && config.useLeader !== false && hasExplicitTools(config.leader) ? [config.leader, ...config.agents] : config.agents;
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.12.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.12.0",
34
- "@multiclawcli/dashboard": "0.12.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
  },