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 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,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(configPath, generateConfig({ preset, workDir, projectName, provider }));
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 as resolveCommand2,
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 = 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
+ ];
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 = resolveCommand2(agent, config);
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 void 0;
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 = 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.11.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.11.0",
34
- "@multiclawcli/dashboard": "0.11.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
  },