@brutalsystems/muster 0.7.14 → 0.7.15

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.
@@ -1,5 +1,6 @@
1
1
  import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
+ import TOML from "@iarna/toml";
3
4
  import { identityPath } from "./identity-store.js";
4
5
  /**
5
6
  * What a copy must contain: enough to start authenticated, onboarded, and
@@ -122,3 +123,62 @@ export async function setWorkspaceTrust(copyPath, cwd) {
122
123
  data.projects = projects;
123
124
  await writeFile(file, JSON.stringify(data), { mode: 0o600 });
124
125
  }
126
+ /**
127
+ * Set Codex's directory-trust flag for one directory, inside a copy Muster just
128
+ * created. Codex has its own gate, unrelated to Claude's: without a
129
+ * `[projects."<dir>"] trust_level = "trusted"` entry in the config.toml it
130
+ * reads, the TUI stops on "Do you trust the contents of this directory?" before
131
+ * it ever opens a thread. No thread is opened, so no thread-writer lock is
132
+ * written, so `resolveCodex` finds nothing and the launch burns its whole
133
+ * deadline reporting `no descendant runtime identity found`.
134
+ *
135
+ * A fresh identity copy has no `projects` table at all, which made EVERY Codex
136
+ * identity launch fail exactly that way — measured, not inferred: the pane was
137
+ * captured mid-launch sitting on the prompt while the copy filled with Codex's
138
+ * own sqlite state and never grew a `thread-writer-locks/` directory.
139
+ *
140
+ * `-c projects."<dir>".trust_level="trusted"` does NOT satisfy the gate. It
141
+ * parses and is accepted, and the prompt still appears: the check reads
142
+ * persisted config, so the entry has to be in the file.
143
+ *
144
+ * The user's real `~/.codex/config.toml` is never touched, for the same reason
145
+ * `setWorkspaceTrust` leaves the live `~/.claude.json` alone.
146
+ */
147
+ export async function setCodexWorkspaceTrust(copyPath, cwd) {
148
+ const file = join(copyPath, "config.toml");
149
+ let raw;
150
+ try {
151
+ raw = await readFile(file, "utf8");
152
+ }
153
+ catch (e) {
154
+ // A template that has never been logged into has no config.toml; Codex
155
+ // runs on defaults. Anything else — EACCES, EISDIR — is a real failure,
156
+ // and swallowing it would hand the agent a config missing every setting
157
+ // the template's own login wrote.
158
+ if (e.code !== "ENOENT")
159
+ throw e;
160
+ }
161
+ let data = {};
162
+ if (raw !== undefined)
163
+ try {
164
+ data = TOML.parse(raw);
165
+ }
166
+ catch (e) {
167
+ // A file that EXISTS and does not parse is not "no file yet". Codex would
168
+ // refuse it too, so writing over it trades one clear failure for a silent
169
+ // loss of the template's settings.
170
+ throw new Error(`${file} exists but is not valid TOML: ${String(e)}`);
171
+ }
172
+ const projects = (data.projects ?? {});
173
+ const existing = (projects[cwd] ?? {});
174
+ if (existing.trust_level === "trusted")
175
+ return;
176
+ projects[cwd] = { ...existing, trust_level: "trusted" };
177
+ data.projects = projects;
178
+ // Re-serialised rather than appended, so an entry the template already
179
+ // carries with a different trust_level is corrected instead of duplicated —
180
+ // a duplicate table is a TOML error and Codex would refuse the whole file.
181
+ // Comments and key order in the copy are lost; the copy is per-launch and
182
+ // machine-read, and the template it came from is untouched.
183
+ await writeFile(file, TOML.stringify(data), { mode: 0o600 });
184
+ }
package/dist/run.js CHANGED
@@ -9,7 +9,7 @@ import { enforcementGrade } from "./enforcement.js";
9
9
  import { LOCAL } from "./requester.js";
10
10
  import { assertEnforcementMeetsMinimum, assertLevelWithinCeiling, assertMayAct, resolveIdentityForRequest, resolveRequesterPolicy, RequesterPolicyRefusal, } from "./requester-policy.js";
11
11
  import { IdentityError, readIdentity, } from "./identity-store.js";
12
- import { copyIdentity, identityEnv, setWorkspaceTrust, } from "./identity-copy.js";
12
+ import { copyIdentity, identityEnv, setCodexWorkspaceTrust, setWorkspaceTrust, } from "./identity-copy.js";
13
13
  import { Registry } from "./registry.js";
14
14
  import { LaunchLog } from "./log.js";
15
15
  import { hosts, selectHost } from "./hosts/index.js";
@@ -314,6 +314,12 @@ export class Muster {
314
314
  // session never registers, and the launch burns its whole deadline.
315
315
  // `assertAllowedWorkspace` already realpaths for containment.
316
316
  await setWorkspaceTrust(copy, await realpath(req.cwd));
317
+ // Codex gates the same way behind a different file and a different key,
318
+ // and a fresh copy trusts nothing — so every Codex identity launch sat
319
+ // on "Do you trust the contents of this directory?" until the deadline.
320
+ // Resolved for the same reason: Codex keys `projects` by its own cwd.
321
+ else if (identityMeta.agent === "codex")
322
+ await setCodexWorkspaceTrust(copy, await realpath(req.cwd));
317
323
  runtimeEnv = {
318
324
  ...runtimeEnv,
319
325
  ...identityEnv(identityMeta, copy, this.sourceEnv),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brutalsystems/muster",
3
- "version": "0.7.14",
3
+ "version": "0.7.15",
4
4
  "description": "Launch instructed, reachable local agent sessions",
5
5
  "type": "module",
6
6
  "engines": {