@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 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 和脚本化:
@@ -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). */
@@ -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 subagentMode = clampSubagentPermissionMode(parentMode, input.permission_mode);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhijiewang/openharness",
3
- "version": "2.36.0",
3
+ "version": "2.37.0",
4
4
  "description": "Open-source terminal coding agent. Works with any LLM.",
5
5
  "type": "module",
6
6
  "bin": {