@zhijiewang/openharness 2.36.0 → 2.37.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 +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/agents/roles.d.ts +14 -0
- package/dist/agents/roles.js +15 -0
- package/dist/tools/AgentTool/index.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -574,6 +574,8 @@ Agent({ subagent_type: 'security-auditor', prompt: '...', permission_mode: 'deny
|
|
|
574
574
|
|
|
575
575
|
If a less-restrictive mode is requested (e.g. parent is `ask`, subagent requests `trust`), the harness silently clamps to the parent — a model can never use a sub-agent to escape user-approval gates.
|
|
576
576
|
|
|
577
|
+
**Read-only roles default to `plan` automatically.** `code-reviewer`, `evaluator`, `security-auditor`, `architect`, and `planner` ship with `permissionMode: 'plan'` — spawn them under any parent and they're statically read-only, no `permission_mode` override needed. Markdown-defined agents in `.oh/agents/*.md` can set their own default with `permissionMode: plan` (or `permission-mode: plan`) frontmatter.
|
|
578
|
+
|
|
577
579
|
## Headless Mode
|
|
578
580
|
|
|
579
581
|
Run a single prompt without interactive UI — perfect for CI/CD and scripting:
|
package/README.zh-CN.md
CHANGED
|
@@ -574,6 +574,8 @@ Agent({ subagent_type: 'security-auditor', prompt: '...', permission_mode: 'deny
|
|
|
574
574
|
|
|
575
575
|
如果请求的模式比父级更宽松(比如父级 `ask`、子代理请求 `trust`),harness 会静默回退到父级的模式 —— 模型永远不能借助子代理绕过用户的批准门。
|
|
576
576
|
|
|
577
|
+
**只读角色自动默认 `plan` 模式。** `code-reviewer`、`evaluator`、`security-auditor`、`architect`、`planner` 内置 `permissionMode: 'plan'` —— 在任何父级权限下启动它们都是静态只读,无需在调用处再传 `permission_mode`。`.oh/agents/*.md` 里自定义的 markdown agent 也可以在 frontmatter 写 `permissionMode: plan`(或 `permission-mode: plan`)来设默认。
|
|
578
|
+
|
|
577
579
|
## 无头模式
|
|
578
580
|
|
|
579
581
|
跑一次提示词,不走交互 UI —— 适合 CI/CD 和脚本化:
|
package/dist/agents/roles.d.ts
CHANGED
|
@@ -21,6 +21,20 @@ export type AgentRole = {
|
|
|
21
21
|
model?: string;
|
|
22
22
|
/** Isolation mode for sub-agent execution. Default: inherits parent. */
|
|
23
23
|
isolation?: "none" | "worktree";
|
|
24
|
+
/**
|
|
25
|
+
* Default permission mode for this role. When the AgentTool caller doesn't
|
|
26
|
+
* pass `permission_mode`, this value is used. Always passes through the
|
|
27
|
+
* narrowing-only safety clamp (v2.36): a role marked `plan` can't loosen
|
|
28
|
+
* the parent's stricter mode, only set the floor when the parent is looser.
|
|
29
|
+
*
|
|
30
|
+
* Use cases:
|
|
31
|
+
* - `plan` for read-only roles (code-reviewer, evaluator, security-auditor,
|
|
32
|
+
* architect, planner) — locks them as read-only even when the parent
|
|
33
|
+
* is in `trust`, so the review pass can't accidentally write anything.
|
|
34
|
+
* - Leave undefined for roles that mutate (editor, migrator, refactorer,
|
|
35
|
+
* test-writer, debugger) — they need the parent's mode to do their job.
|
|
36
|
+
*/
|
|
37
|
+
permissionMode?: import("../types/permissions.js").PermissionMode;
|
|
24
38
|
/** Per-agent MCP servers injected only when this agent runs (parsed via raw passthrough; dispatcher wiring is a future task). */
|
|
25
39
|
mcpServers?: Record<string, unknown>;
|
|
26
40
|
/** Per-agent hooks (parsed via raw passthrough; dispatcher wiring is a future task). */
|
package/dist/agents/roles.js
CHANGED
|
@@ -22,6 +22,7 @@ const roles = [
|
|
|
22
22
|
|
|
23
23
|
Be specific: cite file paths, line numbers, and code snippets. Prioritize issues by severity (critical > major > minor). Don't mention things that look fine — focus on problems.`,
|
|
24
24
|
suggestedTools: ["Read", "Glob", "Grep", "LS"],
|
|
25
|
+
permissionMode: "plan",
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
id: "test-writer",
|
|
@@ -99,6 +100,7 @@ Do NOT add new features or change behavior. The refactored code must be function
|
|
|
99
100
|
|
|
100
101
|
Report findings with severity (Critical/High/Medium/Low), affected file:line, and recommended fix.`,
|
|
101
102
|
suggestedTools: ["Read", "Glob", "Grep", "Bash"],
|
|
103
|
+
permissionMode: "plan",
|
|
102
104
|
},
|
|
103
105
|
{
|
|
104
106
|
id: "evaluator",
|
|
@@ -113,6 +115,7 @@ Report findings with severity (Critical/High/Medium/Low), affected file:line, an
|
|
|
113
115
|
|
|
114
116
|
You CANNOT modify files. Only read, search, and run test/lint commands to evaluate.`,
|
|
115
117
|
suggestedTools: ["Read", "Glob", "Grep", "LS", "Bash", "Diagnostics"],
|
|
118
|
+
permissionMode: "plan",
|
|
116
119
|
},
|
|
117
120
|
{
|
|
118
121
|
id: "planner",
|
|
@@ -127,6 +130,7 @@ You CANNOT modify files. Only read, search, and run test/lint commands to evalua
|
|
|
127
130
|
|
|
128
131
|
Do NOT implement anything. Your output is a plan document, not code. Read widely before planning.`,
|
|
129
132
|
suggestedTools: ["Read", "Glob", "Grep", "LS", "Bash"],
|
|
133
|
+
permissionMode: "plan",
|
|
130
134
|
},
|
|
131
135
|
{
|
|
132
136
|
id: "architect",
|
|
@@ -152,6 +156,7 @@ When you've finished planning, output a structured "Plan" the editor can apply m
|
|
|
152
156
|
|
|
153
157
|
Keep each step small enough that an editor (a cheaper model) can apply it without re-deriving your reasoning. Group related edits together; surface dependencies between steps.`,
|
|
154
158
|
suggestedTools: ["Read", "Glob", "Grep", "LS"],
|
|
159
|
+
permissionMode: "plan",
|
|
155
160
|
},
|
|
156
161
|
{
|
|
157
162
|
id: "editor",
|
|
@@ -238,6 +243,7 @@ function parseAgentMarkdown(raw, filePath) {
|
|
|
238
243
|
const disallowedMatch = fm.match(/^disallowedTools:\s*(.+)$/m) ?? fm.match(/^disallowed-tools:\s*(.+)$/m);
|
|
239
244
|
const modelMatch = fm.match(/^model:\s*(.+)$/m);
|
|
240
245
|
const isolationMatch = fm.match(/^isolation:\s*(.+)$/m);
|
|
246
|
+
const permModeMatch = fm.match(/^permissionMode:\s*(.+)$/m) ?? fm.match(/^permission-mode:\s*(.+)$/m);
|
|
241
247
|
const fmEnd = raw.indexOf("---", raw.indexOf("---") + 3);
|
|
242
248
|
const content = fmEnd > 0 ? raw.slice(fmEnd + 3).trim() : "";
|
|
243
249
|
const id = basename(filePath, ".md")
|
|
@@ -245,6 +251,14 @@ function parseAgentMarkdown(raw, filePath) {
|
|
|
245
251
|
.replace(/[^a-z0-9]+/g, "-");
|
|
246
252
|
const isolation = isolationMatch?.[1]?.trim().replace(/^["']|["']$/g, "");
|
|
247
253
|
const validIsolation = isolation === "worktree" || isolation === "none" ? isolation : undefined;
|
|
254
|
+
// permissionMode: only honor known values; silently drop typos. Same
|
|
255
|
+
// pattern as `isolation` above — we'd rather a misspelled mode produce
|
|
256
|
+
// "no override" than a runtime crash.
|
|
257
|
+
const permModeRaw = permModeMatch?.[1]?.trim().replace(/^["']|["']$/g, "");
|
|
258
|
+
const validModes = ["ask", "trust", "deny", "acceptEdits", "plan", "auto", "bypassPermissions"];
|
|
259
|
+
const validPermissionMode = validModes.includes(permModeRaw)
|
|
260
|
+
? permModeRaw
|
|
261
|
+
: undefined;
|
|
248
262
|
// Parse optional inline-JSON fields (mcpServers, hooks). These are block fields in
|
|
249
263
|
// Anthropic's YAML but we support single-line JSON for lightweight frontmatter use.
|
|
250
264
|
const mcpServersMatch = fm.match(/^mcpServers:\s*(\{[\s\S]*?\})\s*$/m);
|
|
@@ -258,6 +272,7 @@ function parseAgentMarkdown(raw, filePath) {
|
|
|
258
272
|
disallowedTools: disallowedMatch ? parseAgentList(disallowedMatch[1]) : undefined,
|
|
259
273
|
model: modelMatch?.[1]?.trim().replace(/^["']|["']$/g, ""),
|
|
260
274
|
isolation: validIsolation,
|
|
275
|
+
permissionMode: validPermissionMode,
|
|
261
276
|
mcpServers: mcpServersMatch ? tryParseJson(mcpServersMatch[1]) : undefined,
|
|
262
277
|
hooks: hooksMatch ? tryParseJson(hooksMatch[1]) : undefined,
|
|
263
278
|
};
|
|
@@ -109,8 +109,16 @@ export const AgentTool = {
|
|
|
109
109
|
// Permission mode override — narrowing-only. Subagent can be same-or-stricter
|
|
110
110
|
// than parent; a less-restrictive request silently clamps to the parent so a
|
|
111
111
|
// model in `ask` can't spawn a `trust`-mode subagent to bypass user approval.
|
|
112
|
+
//
|
|
113
|
+
// Resolution order, most-specific-wins:
|
|
114
|
+
// 1. input.permission_mode (the call site's explicit choice)
|
|
115
|
+
// 2. role.permissionMode (the role's documented default — e.g.
|
|
116
|
+
// code-reviewer / security-auditor / planner default to "plan")
|
|
117
|
+
// 3. parent's mode (no narrowing — current default)
|
|
118
|
+
// The clamp applies in all cases so #1 and #2 can only narrow, not loosen.
|
|
112
119
|
const parentMode = context.permissionMode ?? "trust";
|
|
113
|
-
const
|
|
120
|
+
const requestedMode = input.permission_mode ?? role?.permissionMode;
|
|
121
|
+
const subagentMode = clampSubagentPermissionMode(parentMode, requestedMode);
|
|
114
122
|
const config = {
|
|
115
123
|
provider: context.provider,
|
|
116
124
|
tools: agentTools,
|