@brutalsystems/muster 1.0.2 → 1.1.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.
@@ -527,3 +527,38 @@ convenient re-sync: `test/fixtures/canonical-id.json` is in `package.json`'s
527
527
  fixture moved `publish.yml`, `RELEASING.md` and `docs/verification.md` on
528
528
  their own. Only the two frozen hashes in `test/naming.test.ts` are hand-written,
529
529
  and those are the third site behaving as documented.
530
+
531
+ ## What the vendored copy covers, and what it deliberately does not
532
+
533
+ The copies pin **the address format Muster generates** — slugify, suffix,
534
+ assignment, resolution — and the refusals that resolution can produce:
535
+ `ambiguous`, `self`, `unknown`. That is the whole surface, and the boundary is
536
+ not an accident of what was convenient to copy.
537
+
538
+ They do **not** cover Tin Can's send-path vocabulary, because Muster never calls
539
+ `send_peer` in product code. Muster produces addresses; Tin Can does the
540
+ sending. The only mentions of that call in this repository are a doc comment in
541
+ `naming.ts` describing what an address is for, and the contract suite asserting
542
+ `outcome === "accepted"` on the happy path. Neither enumerates a refusal, so
543
+ there is no unknown-refusal path here for a new one to fall through.
544
+
545
+ Tin Can 2.0.0 is the worked example. It added a `duplicate_peer_moved` refusal,
546
+ splitting the retry-whose-name-has-moved case out of `duplicate_send`. A
547
+ consumer of send outcomes had to act on that — birddog did, and it was a real
548
+ change for them. Muster owed nothing, and the 2.0.0 re-sync above correctly
549
+ shows no such edit. The asymmetry is clean: Muster produces addresses and never
550
+ consumes send outcomes; birddog consumes send outcomes and never produces an
551
+ address. Neither repository inherits the other's obligations, and a vendored
552
+ contract that tried to cover both would be wrong in both directions.
553
+
554
+ **So the question at each re-sync is not "did Tin Can change".** It is: did
555
+ anything Muster *generates or parses* change. Copying more than that is not
556
+ caution, it is over-vendoring — it pins a surface Muster does not use, and the
557
+ frozen hashes in `test/naming.test.ts` then move for reasons that have nothing
558
+ to do with Muster, which is how a freeze stops meaning anything.
559
+
560
+ **If Muster ever grows a send path, this boundary moves and the copy must widen
561
+ with it** — `duplicate_peer_moved` and the rest of the send vocabulary would
562
+ become a genuine re-sync obligation, and a re-sync that kept ignoring them would
563
+ be a defect. Recorded now, while the reasoning is fresh, rather than discovered
564
+ by whoever adds that path.
package/dist/guard.js CHANGED
@@ -443,6 +443,14 @@ export function launchArgs(req, config, mcp = [], bridgePath, env = process.env)
443
443
  const settings = {
444
444
  ...profilePluginSettings(configDir),
445
445
  disableAllHooks: true,
446
+ // `--setting-sources ""` means an acceptance recorded in the profile's
447
+ // settings.json is never read, so without this the bypass warning stops
448
+ // every bypass launch before it registers (#45). Only on request, and
449
+ // `assertOptionsSupported` has already refused it outside bypass.
450
+ ...(req.options.includes("accept-bypass-warning") &&
451
+ policy.permissions === "bypass"
452
+ ? { skipDangerousModePermissionPrompt: true }
453
+ : {}),
446
454
  permissions: {
447
455
  defaultMode: mode,
448
456
  allow: mcp.flatMap((s) => s.config.tools.map((t) => `mcp__${s.name}__${t}`)),
package/dist/help.js CHANGED
@@ -60,7 +60,7 @@ export const COMMANDS = [
60
60
  ["--mcp NAME", "repeatable; replaces configured defaults"],
61
61
  [
62
62
  "--options NAME",
63
- "repeatable; muster-side launch options — auto-approve-path records the launch directory as trusted so its one-time dialog does not block the launch (claude and codex only, and refused if the directory ships hooks or MCP servers)",
63
+ "repeatable; muster-side launch options — auto-approve-path records the launch directory as trusted so its one-time dialog does not block the launch (claude and codex only, and refused if the directory ships hooks or MCP servers); accept-bypass-warning accepts Claude's one-time Bypass Permissions warning so a --level open launch can start (claude with bypass permissions only)",
64
64
  ],
65
65
  ["--no-mcp", "launch with no MCP servers"],
66
66
  ["--plugin NAME", "repeatable; replaces configured defaults (OpenCode)"],
@@ -64,22 +64,27 @@ export async function claudeWorkspaceTrusted(env, cwd) {
64
64
  * timeout looks identical to a hung or crashed launch. Several unrelated
65
65
  * screens can produce it, and the old message guessed at two of them together.
66
66
  * Trust is the one Muster can actually check, so it is either named or ruled
67
- * out. Nothing else is named: a first-use bypass-permissions confirmation was
68
- * a plausible account of one report, but no profile on this machine carries a
69
- * key for it the way they carry `hasSeenAutoModeEntryWarning` and its
70
- * siblings, so this says "another one-time confirmation" rather than sending
71
- * the reader after a screen nobody has demonstrated.
67
+ * out. The bypass-permissions warning is named only for a launch that ran in
68
+ * bypass without `accept-bypass-warning`: demonstrated in #45, and that launch
69
+ * is the only one it can stop. Anything else says "another one-time
70
+ * confirmation" rather than sending the reader after a screen nobody has
71
+ * demonstrated.
72
72
  *
73
73
  * A `--kind task` launch into the same directory is the cheap discriminator:
74
74
  * it runs headless and never sees the dialog, so it succeeds where a session
75
75
  * hangs.
76
76
  */
77
- export function claudeRegistryDiagnostic(trusted, cwd, configDir) {
77
+ export function claudeRegistryDiagnostic(trusted, cwd, configDir, opts = {}) {
78
78
  if (!trusted)
79
79
  return (`no Claude session registry found: workspace trust has not been ` +
80
80
  `accepted for ${cwd} in ${configDir} — accept it once with ` +
81
81
  `\`cd ${cwd} && claude\`, then retry (trust is per-directory and is ` +
82
82
  `not inherited from a parent directory)`);
83
+ if (opts.bypassWarningPending)
84
+ return (`no Claude session registry found (${cwd} is trusted for this profile): ` +
85
+ `this launch runs in bypass permissions, and Claude stops on its ` +
86
+ `one-time "Bypass Permissions mode" warning until someone answers it — ` +
87
+ `relaunch with --options accept-bypass-warning to accept it for the launch`);
83
88
  return (`no Claude session registry found (${cwd} is trusted for this profile, ` +
84
89
  `so the session is stopped on something else — check the terminal for a ` +
85
90
  `login prompt or another one-time confirmation)`);
package/dist/mcp-tools.js CHANGED
@@ -63,8 +63,11 @@ export const MCP_TOOLS = [
63
63
  },
64
64
  options: {
65
65
  type: "array",
66
- items: { type: "string", enum: ["auto-approve-path"] },
67
- description: "Muster-side launch options. auto-approve-path records the launch directory as trusted in the profile the agent will use, so its one-time dialog does not block the launch; refused for a runtime with no trust gate, and refused if the directory ships hooks or MCP servers. Not expressible remotely.",
66
+ items: {
67
+ type: "string",
68
+ enum: ["auto-approve-path", "accept-bypass-warning"],
69
+ },
70
+ description: "Muster-side launch options. auto-approve-path records the launch directory as trusted in the profile the agent will use, so its one-time dialog does not block the launch; refused for a runtime with no trust gate, and refused if the directory ships hooks or MCP servers. accept-bypass-warning accepts Claude's one-time Bypass Permissions warning for a bypass launch (level open); refused for other runtimes and without bypass permissions. Not expressible remotely.",
68
71
  },
69
72
  project: {
70
73
  type: "string",
package/dist/options.js CHANGED
@@ -10,17 +10,31 @@ import { AGENTS } from "./agents.js";
10
10
  * in its own way, or cannot express at all — so the set is closed, and naming
11
11
  * one an agent has no answer for is an error rather than a silent no-op.
12
12
  */
13
- export const LAUNCH_OPTIONS = ["auto-approve-path"];
13
+ export const LAUNCH_OPTIONS = [
14
+ "auto-approve-path",
15
+ "accept-bypass-warning",
16
+ ];
14
17
  /**
15
18
  * Refuses an option the runtime cannot honour, before anything is reserved or
16
19
  * spawned. `auto-approve-path` records that the launch directory is trusted, so
17
20
  * it needs an agent with a trust gate to record it in; OpenCode has none, and
18
21
  * accepting the flag there would promise something muster cannot deliver.
22
+ *
23
+ * `accept-bypass-warning` answers Claude's one-time "Bypass Permissions mode"
24
+ * warning, which only Claude shows and only in bypass. Anywhere else there is
25
+ * no warning to accept, so it is refused rather than carried as a no-op that
26
+ * reads like consent to something.
19
27
  */
20
- export function assertOptionsSupported(runtime, options) {
21
- for (const option of options)
28
+ export function assertOptionsSupported(runtime, options, permissions) {
29
+ for (const option of options) {
22
30
  if (option === "auto-approve-path" && !AGENTS[runtime].trustWorkspace)
23
31
  throw new Error(`--options auto-approve-path is not supported for ${runtime}: it has no workspace-trust gate`);
32
+ if (option === "accept-bypass-warning" && runtime !== "claude")
33
+ throw new Error(`--options accept-bypass-warning is not supported for ${runtime}: only Claude shows a bypass-permissions warning`);
34
+ if (option === "accept-bypass-warning" &&
35
+ permissions.permissions !== "bypass")
36
+ throw new Error(`--options accept-bypass-warning requires bypass permissions (--level open or --permissions bypass); this launch resolves to ${permissions.permissions}`);
37
+ }
24
38
  }
25
39
  /**
26
40
  * What a directory ships that would configure a session it is trusted by.
package/dist/run.js CHANGED
@@ -256,10 +256,10 @@ export class Muster {
256
256
  if (req.open &&
257
257
  (req.kind !== "session" || !["auto", "tmux"].includes(requestedHost)))
258
258
  throw new Error("--open requires a tmux session on macOS");
259
+ const permissions = resolvePermissions(effective, this.config);
259
260
  // Refused here, with the other checks that cost nothing to fail: an option
260
261
  // the runtime cannot honour must not leave a registry entry behind.
261
- assertOptionsSupported(req.runtime, req.options);
262
- const permissions = resolvePermissions(effective, this.config);
262
+ assertOptionsSupported(req.runtime, req.options, permissions);
263
263
  const enforcement = enforcementGrade(req.runtime, permissions);
264
264
  assertEnforcementMeetsMinimum(req.runtime, enforcement, policy);
265
265
  // Composed here, before any process of any kind is started — the version
@@ -736,7 +736,11 @@ export class Muster {
736
736
  // runs headless and never sees the dialog, so refusing an untrusted
737
737
  // directory up front would break task launches that work today.
738
738
  if (req.runtime === "claude" && diagnostic === neverRegistered)
739
- diagnostic = claudeRegistryDiagnostic(await claudeWorkspaceTrusted(resolveEnv, req.cwd), req.cwd, claudeConfigDir(resolveEnv));
739
+ diagnostic = claudeRegistryDiagnostic(await claudeWorkspaceTrusted(resolveEnv, req.cwd), req.cwd, claudeConfigDir(resolveEnv), {
740
+ bypassWarningPending: req.kind === "session" &&
741
+ permissions.permissions === "bypass" &&
742
+ !req.options.includes("accept-bypass-warning"),
743
+ });
740
744
  throw new Error(`${diagnostic} after ${this.config.launch_timeout_sec}s`);
741
745
  }
742
746
  async launch(input, requester) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brutalsystems/muster",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Launch instructed, reachable local agent sessions",
5
5
  "type": "module",
6
6
  "engines": {