portable-agent-layer 0.115.0 → 0.115.1-canary.1
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.
|
@@ -136,11 +136,13 @@ The repo's `CLAUDE.md` is read from your checkout when the task starts, so a rol
|
|
|
136
136
|
- `"repoInstructions": false` in `.dark-factory.json` leaves out the repo's `CLAUDE.md`.
|
|
137
137
|
- `"userInstructions": false` in `$DARK_FACTORY_HOME/config.json` (`~/.dark-factory/config.json`, optional; a missing file means both parts are on) leaves out your `~/.claude/CLAUDE.md`.
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
Your own hooks never run in a role, and neither do MCP servers. The repo's hooks do run, so a repo `Stop` hook that fails its checks keeps the role working until they pass; the gate stays as the check for repos without hooks.
|
|
140
|
+
|
|
141
|
+
A role loads the repo's `.claude/settings.json` and `.claude/settings.local.json` from its worktree as Claude Code does, permissions included. Your `~/.claude/settings.json` (or `CLAUDE_CONFIG_DIR`) is passed in `--settings` without `hooks`, `permissions`, `enabledPlugins` and `extraKnownMarketplaces`, so commit attribution and your other preferences apply. Each role call records which of your keys it passed and dropped as a `role_settings` event, without their values.
|
|
140
142
|
|
|
141
143
|
## Permissions
|
|
142
144
|
|
|
143
|
-
- Roles run
|
|
145
|
+
- Roles run with `--setting-sources project,local`, the plugin from `--plugin-dir`, and their own tool policy from `roles.mjs`. Your user settings come only through the filtered `--settings` file, so your hooks don't reach them. A repo's `permissions.allow` can widen a role's tools; the role's denies still win.
|
|
144
146
|
- The PO session only needs `"Bash(node *dark-factory*/scripts/*)"`, for example in `.claude/settings.json`:
|
|
145
147
|
|
|
146
148
|
```json
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
userHome,
|
|
14
14
|
} from "./lib.mjs";
|
|
15
15
|
import { policyFor, schemaFor } from "./roles.mjs";
|
|
16
|
+
import { writeRoleSettings } from "./settings.mjs";
|
|
16
17
|
|
|
17
18
|
export const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
18
19
|
export const ONE_COMMAND_PER_CALL =
|
|
@@ -298,6 +299,7 @@ export function claudeInvocation(
|
|
|
298
299
|
resumeSessionId,
|
|
299
300
|
pluginDir = PLUGIN_ROOT,
|
|
300
301
|
instructionsFile,
|
|
302
|
+
settingsFile,
|
|
301
303
|
} = {}
|
|
302
304
|
) {
|
|
303
305
|
const policy = policyFor(role);
|
|
@@ -312,7 +314,8 @@ export function claudeInvocation(
|
|
|
312
314
|
"--output-format",
|
|
313
315
|
"json",
|
|
314
316
|
"--setting-sources",
|
|
315
|
-
"",
|
|
317
|
+
"project,local",
|
|
318
|
+
...optionalFlag("--settings", settingsFile),
|
|
316
319
|
"--plugin-dir",
|
|
317
320
|
pluginDir,
|
|
318
321
|
...optionalFlag("--append-system-prompt-file", instructionsFile),
|
|
@@ -353,6 +356,7 @@ function attempt(
|
|
|
353
356
|
try {
|
|
354
357
|
const task = readTask(taskId, env);
|
|
355
358
|
const instructionsFile = writeRoleInstructions(task, env);
|
|
359
|
+
const settingsFile = writeRoleSettings(task, env);
|
|
356
360
|
const invocation = claudeInvocation(task, role, {
|
|
357
361
|
budgetUsd,
|
|
358
362
|
extraArgs,
|
|
@@ -361,6 +365,7 @@ function attempt(
|
|
|
361
365
|
instructions,
|
|
362
366
|
resumeSessionId,
|
|
363
367
|
instructionsFile,
|
|
368
|
+
settingsFile,
|
|
364
369
|
});
|
|
365
370
|
const outcome = outcomeFrom(
|
|
366
371
|
taskId,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { appendEvent, taskDir, userHome } from "./lib.mjs";
|
|
4
|
+
|
|
5
|
+
const SETTINGS_FILE = "role-settings.json";
|
|
6
|
+
const KEYS_THAT_BREAK_ISOLATION = [
|
|
7
|
+
"hooks",
|
|
8
|
+
"permissions",
|
|
9
|
+
"enabledPlugins",
|
|
10
|
+
"extraKnownMarketplaces",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function isPlainObject(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readJsonObject(file) {
|
|
18
|
+
try {
|
|
19
|
+
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
20
|
+
return isPlainObject(value) ? value : null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function userSettings(env) {
|
|
27
|
+
const configDir = env.CLAUDE_CONFIG_DIR || join(userHome(env), ".claude");
|
|
28
|
+
return readJsonObject(join(configDir, "settings.json")) ?? {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function roleSettingsFrom(userSettings) {
|
|
32
|
+
const kept = Object.fromEntries(
|
|
33
|
+
Object.entries(userSettings).filter(
|
|
34
|
+
([key]) => !KEYS_THAT_BREAK_ISOLATION.includes(key)
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
return Object.keys(kept).length > 0 ? kept : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function recordRoleSettings(taskId, user, kept, env) {
|
|
41
|
+
appendEvent(
|
|
42
|
+
{
|
|
43
|
+
event: "role_settings",
|
|
44
|
+
task: taskId,
|
|
45
|
+
passed: Object.keys(kept ?? {}),
|
|
46
|
+
dropped: Object.keys(user).filter((key) => KEYS_THAT_BREAK_ISOLATION.includes(key)),
|
|
47
|
+
},
|
|
48
|
+
env
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeRoleSettings(task, env = process.env) {
|
|
53
|
+
const user = userSettings(env);
|
|
54
|
+
const kept = roleSettingsFrom(user);
|
|
55
|
+
recordRoleSettings(task.id, user, kept, env);
|
|
56
|
+
if (kept === null) return null;
|
|
57
|
+
const dir = taskDir(task.id, env);
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
const file = join(dir, SETTINGS_FILE);
|
|
60
|
+
writeFileSync(file, `${JSON.stringify(kept, null, 2)}\n`, { mode: 0o600 });
|
|
61
|
+
return file;
|
|
62
|
+
}
|