Skip to content

Commit e896c9b

Browse files
authored
Merge pull request #166 from zevorn/fix/agent-defaults-permission-bypass
2 parents 3fccb60 + fee2ec7 commit e896c9b

4 files changed

Lines changed: 118 additions & 6 deletions

File tree

src/config.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
import { homedir } from "node:os";
33
import { dirname, join } from "node:path";
44

5-
import type { AgentId } from "./agents/types.js";
5+
import type { AgentId, PermissionMode, SandboxMode } from "./agents/types.js";
66

77
export const DEFAULT_RUN_TIMEOUT_MS = 6 * 60 * 60 * 1000;
88
export const DEFAULT_DASHBOARD_THEME: DashboardTheme = "dark";
@@ -12,6 +12,9 @@ export type DashboardTheme = "light" | "dark";
1212
export interface AgentModelDefaults {
1313
model?: string;
1414
reasoningEffort?: string;
15+
permissionMode?: PermissionMode;
16+
sandbox?: SandboxMode;
17+
extraArgs?: string[];
1518
}
1619

1720
export type AgentModelDefaultsByAgent = Partial<Record<AgentId, AgentModelDefaults>>;
@@ -155,7 +158,7 @@ function parseSimpleYaml(
155158
const values: Partial<Pick<HumanizeConfig, "cacheDir" | "defaultRunTimeoutMs" | "defaultTheme" | "agentDefaults" | "workflow">> = {
156159
agentDefaults: {}
157160
};
158-
let section: "agents" | "workflow" | "workflow.softEnforcement" | "workflow.scripts" | "workflow.scripts.allow" | undefined;
161+
let section: "agents" | "workflow" | "workflow.softEnforcement" | "workflow.scripts" | "workflow.scripts.allow" | "agents.extraArgs" | undefined;
159162
let currentAgent: AgentId | undefined;
160163

161164
for (const line of contents.split("\n")) {
@@ -176,6 +179,26 @@ function parseSimpleYaml(
176179
continue;
177180
}
178181

182+
// agent extraArgs list items (e.g., ` - --skip-git-repo-check`)
183+
if (section === "agents.extraArgs" && indent >= 6 && currentAgent !== undefined && trimmed.startsWith("- ")) {
184+
const item = trimmed.slice(2).trim().replace(/^["']|["']$/g, "");
185+
if (item.length > 0) {
186+
const defaults = values.agentDefaults?.[currentAgent] ?? {};
187+
defaults.extraArgs = [...(defaults.extraArgs ?? []), item];
188+
values.agentDefaults = {
189+
...values.agentDefaults,
190+
[currentAgent]: defaults
191+
};
192+
}
193+
continue;
194+
}
195+
196+
// extraArgs list is done — reset section so sibling fields (model,
197+
// permissionMode, sandbox) under the same agent are not silently ignored
198+
if (section === "agents.extraArgs" && indent >= 4 && currentAgent !== undefined && !trimmed.startsWith("- ")) {
199+
section = "agents";
200+
}
201+
179202
const separatorIndex = trimmed.indexOf(":");
180203
if (separatorIndex < 0) {
181204
continue;
@@ -233,8 +256,9 @@ function parseSimpleYaml(
233256
section = "workflow.scripts.allow";
234257
continue;
235258
}
236-
if (section === "agents" && indent === 2) {
259+
if ((section === "agents" || section === "agents.extraArgs") && indent === 2) {
237260
currentAgent = key === "codex" || key === "claude" ? key : undefined;
261+
section = "agents";
238262
continue;
239263
}
240264
if (section === "agents" && indent >= 4 && currentAgent !== undefined && value.length > 0) {
@@ -245,6 +269,25 @@ function parseSimpleYaml(
245269
if (key === "reasoningEffort") {
246270
defaults.reasoningEffort = value;
247271
}
272+
if (key === "permissionMode") {
273+
defaults.permissionMode = value as PermissionMode;
274+
}
275+
if (key === "sandbox") {
276+
defaults.sandbox = value as SandboxMode;
277+
}
278+
values.agentDefaults = {
279+
...values.agentDefaults,
280+
[currentAgent]: defaults
281+
};
282+
}
283+
if (section === "agents" && indent >= 4 && currentAgent !== undefined && key === "extraArgs") {
284+
const defaults = values.agentDefaults?.[currentAgent] ?? {};
285+
if (value === "[]" || value.length === 0) {
286+
defaults.extraArgs = [];
287+
section = "agents.extraArgs";
288+
} else {
289+
defaults.extraArgs = parseCommaList(value);
290+
}
248291
values.agentDefaults = {
249292
...values.agentDefaults,
250293
[currentAgent]: defaults
@@ -262,11 +305,13 @@ function mergeAgentDefaults(
262305
return {
263306
codex: {
264307
...defaults.codex,
265-
...overrides?.codex
308+
...overrides?.codex,
309+
extraArgs: overrides?.codex?.extraArgs ?? defaults.codex?.extraArgs
266310
},
267311
claude: {
268312
...defaults.claude,
269-
...overrides?.claude
313+
...overrides?.claude,
314+
extraArgs: overrides?.claude?.extraArgs ?? defaults.claude?.extraArgs
270315
}
271316
};
272317
}

src/hub-server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ async function main(): Promise<void> {
3030
jsonRpcUrl,
3131
store,
3232
initialRuns,
33-
defaultRunTimeoutMs: config.defaultRunTimeoutMs
33+
defaultRunTimeoutMs: config.defaultRunTimeoutMs,
34+
agentDefaults: config.agentDefaults
3435
});
3536
const workflowCoordinator = new WorkflowCoordinator(coordinator, {
3637
store: workflowStore,

src/hub/runs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ export class AgentRunCoordinator {
147147
...input,
148148
model: input.model ?? defaults.model,
149149
reasoningEffort: input.reasoningEffort ?? defaults.reasoningEffort,
150+
permissionMode: input.permissionMode ?? defaults.permissionMode,
151+
sandbox: input.sandbox ?? defaults.sandbox,
152+
extraArgs: input.extraArgs ?? defaults.extraArgs,
150153
cwd: input.cwd ?? parentRun?.cwd,
151154
workflowContext: input.workflowContext ?? inheritedWorkflowContext
152155
};

tests/config.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,69 @@ describe("humanize2 user config", () => {
9393
expect(config.defaultTheme).toBe("light");
9494
});
9595

96+
it("loads agent model fields after a prior agent's multiline extraArgs list", async () => {
97+
const home = await tempDirectory();
98+
const configPath = join(home, "multiline-extraargs-config.yaml");
99+
100+
const contents = [
101+
"version: 1",
102+
"cacheDir: /tmp/humanize2-cache",
103+
"defaultRunTimeoutMs: 12345",
104+
"defaultTheme: dark",
105+
"agents:",
106+
" codex:",
107+
" model: gpt-5.4",
108+
" extraArgs:",
109+
" - --temperature",
110+
" - 0.7",
111+
" - --max-tokens",
112+
" - 4096",
113+
" claude:",
114+
" model: claude-opus-4-7",
115+
" reasoningEffort: xhigh",
116+
""
117+
].join("\n");
118+
await writeFile(configPath, contents, "utf8");
119+
120+
const config = await loadHumanizeConfig({ HUMANIZE2_CONFIG: configPath }, home);
121+
122+
expect(config.agentDefaults.codex).toMatchObject({
123+
model: "gpt-5.4",
124+
extraArgs: ["--temperature", "0.7", "--max-tokens", "4096"]
125+
});
126+
expect(config.agentDefaults.claude).toMatchObject({
127+
model: "claude-opus-4-7",
128+
reasoningEffort: "xhigh"
129+
});
130+
});
131+
132+
it("loads sibling fields after empty extraArgs under the same agent", async () => {
133+
const home = await tempDirectory();
134+
const configPath = join(home, "sibling-after-extraargs-config.yaml");
135+
136+
const contents = [
137+
"version: 1",
138+
"cacheDir: /tmp/humanize2-cache",
139+
"defaultRunTimeoutMs: 12345",
140+
"defaultTheme: dark",
141+
"agents:",
142+
" codex:",
143+
" extraArgs: []",
144+
" model: gpt-5.5",
145+
" reasoningEffort: xhigh",
146+
""
147+
].join("\n");
148+
await writeFile(configPath, contents, "utf8");
149+
150+
const config = await loadHumanizeConfig({ HUMANIZE2_CONFIG: configPath }, home);
151+
152+
expect(config.agentDefaults.codex).toMatchObject({
153+
model: "gpt-5.5",
154+
reasoningEffort: "xhigh",
155+
extraArgs: []
156+
});
157+
});
158+
96159
it("loads workflow retry and script allowlist settings from the user config", async () => {
97160
const home = await tempDirectory();
98161
const configPath = join(home, "workflow-config.yaml");

0 commit comments

Comments
 (0)