openhermes 4.3.0 → 4.9.2
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/CONTEXT.md +9 -0
- package/README.md +26 -15
- package/bootstrap.ts +161 -124
- package/harness/agents/oh-browser.md +97 -0
- package/harness/agents/oh-builder.md +78 -0
- package/harness/agents/oh-facade.md +75 -0
- package/harness/agents/oh-fusion.md +45 -0
- package/harness/agents/oh-gauntlet.md +71 -0
- package/harness/agents/oh-grill.md +71 -0
- package/harness/agents/oh-investigate.md +60 -0
- package/harness/agents/oh-manifest.md +95 -0
- package/harness/agents/oh-plan-review.md +40 -0
- package/harness/agents/oh-planner.md +50 -0
- package/harness/agents/oh-refactor.md +37 -0
- package/harness/agents/oh-retro.md +46 -0
- package/harness/agents/oh-review.md +85 -0
- package/harness/agents/oh-security.md +83 -0
- package/harness/agents/oh-ship.md +76 -0
- package/harness/agents/oh-skill-craft.md +38 -0
- package/harness/agents/openhermes.md +107 -53
- package/harness/codex/AUTOPILOT.md +143 -91
- package/harness/codex/CHARTER.md +81 -0
- package/harness/commands/oh-doctor.md +193 -14
- package/harness/instructions/SHELL.md +76 -0
- package/harness/skills/oh-ascii/DEEP.md +292 -0
- package/harness/skills/oh-ascii/SKILL.md +31 -0
- package/harness/skills/oh-ascii/scripts/check_ascii_alignment.py +596 -0
- package/harness/skills/oh-browser/DEEP.md +54 -0
- package/harness/skills/oh-browser/SKILL.md +30 -0
- package/harness/skills/oh-builder/DEEP.md +63 -0
- package/harness/skills/oh-builder/SKILL.md +12 -90
- package/harness/skills/oh-expert/DEEP.md +85 -0
- package/harness/skills/oh-expert/SKILL.md +13 -106
- package/harness/skills/oh-facade/DEEP.md +182 -0
- package/harness/skills/oh-facade/SKILL.md +15 -279
- package/harness/skills/oh-freeze/DEEP.md +18 -0
- package/harness/skills/oh-freeze/SKILL.md +10 -19
- package/harness/skills/oh-full-output/DEEP.md +25 -0
- package/harness/skills/oh-full-output/SKILL.md +12 -65
- package/harness/skills/oh-fusion/DEEP.md +120 -0
- package/harness/skills/oh-fusion/SKILL.md +17 -295
- package/harness/skills/oh-gauntlet/DEEP.md +77 -0
- package/harness/skills/oh-gauntlet/SKILL.md +13 -105
- package/harness/skills/oh-grill/DEEP.md +51 -0
- package/harness/skills/oh-grill/SKILL.md +12 -63
- package/harness/skills/oh-guard/DEEP.md +19 -0
- package/harness/skills/oh-guard/SKILL.md +10 -24
- package/harness/skills/oh-handoff/DEEP.md +48 -0
- package/harness/skills/oh-handoff/SKILL.md +13 -23
- package/harness/skills/oh-health/DEEP.md +74 -0
- package/harness/skills/oh-health/SKILL.md +13 -76
- package/harness/skills/oh-init/DEEP.md +85 -0
- package/harness/skills/oh-init/SKILL.md +13 -127
- package/harness/skills/oh-investigate/DEEP.md +171 -0
- package/harness/skills/oh-investigate/SKILL.md +13 -66
- package/harness/skills/oh-issue/DEEP.md +21 -0
- package/harness/skills/oh-issue/SKILL.md +11 -27
- package/harness/skills/oh-learn/DEEP.md +44 -0
- package/harness/skills/oh-learn/SKILL.md +12 -83
- package/harness/skills/oh-manifest/DEEP.md +92 -0
- package/harness/skills/oh-manifest/SKILL.md +11 -108
- package/harness/skills/oh-plan-review/DEEP.md +90 -0
- package/harness/skills/oh-plan-review/SKILL.md +13 -115
- package/harness/skills/oh-planner/DEEP.md +172 -0
- package/harness/skills/oh-planner/SKILL.md +12 -149
- package/harness/skills/oh-prd/DEEP.md +45 -0
- package/harness/skills/oh-prd/SKILL.md +10 -26
- package/harness/skills/oh-refactor/DEEP.md +122 -0
- package/harness/skills/oh-refactor/SKILL.md +17 -410
- package/harness/skills/oh-retro/DEEP.md +26 -0
- package/harness/skills/oh-retro/SKILL.md +12 -24
- package/harness/skills/oh-review/DEEP.md +87 -0
- package/harness/skills/oh-review/SKILL.md +11 -97
- package/harness/skills/oh-security/DEEP.md +83 -0
- package/harness/skills/oh-security/SKILL.md +14 -96
- package/harness/skills/oh-ship/DEEP.md +141 -0
- package/harness/skills/oh-ship/SKILL.md +13 -31
- package/harness/skills/oh-skill-craft/DEEP.md +369 -0
- package/harness/skills/oh-skill-craft/SKILL.md +17 -178
- package/harness/skills/oh-skills-link/DEEP.md +16 -0
- package/harness/skills/oh-skills-link/SKILL.md +10 -20
- package/harness/skills/oh-skills-list/DEEP.md +20 -0
- package/harness/skills/oh-skills-list/SKILL.md +9 -22
- package/harness/skills/oh-triage/DEEP.md +23 -0
- package/harness/skills/oh-triage/SKILL.md +8 -24
- package/harness/skills/oh-worktree/DEEP.md +169 -0
- package/harness/skills/oh-worktree/SKILL.md +32 -0
- package/lib/harness-resolver.ts +8 -10
- package/package.json +5 -3
- package/scripts/count-tokens.mjs +158 -0
- package/scripts/oh-doctor.ps1 +342 -0
- package/harness/codex/CONSTITUTION.md +0 -73
- package/harness/codex/ROUTING.md +0 -92
- package/harness/instructions/RUNTIME.md +0 -30
- package/harness/skills/oh-caveman/SKILL.md +0 -42
- package/lib/logger.ts +0 -75
package/CONTEXT.md
CHANGED
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
**Instruction** — Markdown loaded through `AGENTS.md` or `opencode.json` instructions.
|
|
9
9
|
**Bootstrap** — The first-message context injected by the OpenHermes plugin.
|
|
10
10
|
|
|
11
|
+
### Confidence Gate Terms
|
|
12
|
+
**Confidence Gate** — Phase 0.5 protocol in the autopilot loop that evaluates signal strength before routing. Bounded to 1 conversational exchange max.
|
|
13
|
+
**Confidence Level** — One of HIGH, MEDIUM, LOW, derived from signal axis evaluation.
|
|
14
|
+
**Transparent Gate** — HIGH confidence behavior: zero conversational overhead, proceed directly to Auto-Classify.
|
|
15
|
+
**Echo Gate** — MEDIUM confidence behavior: one-liner echo to confirm understanding, then classify.
|
|
16
|
+
**Question Gate** — LOW confidence behavior: one targeted question, then classify. Fallback to oh-planner on no answer.
|
|
17
|
+
**1 Exchange** — One user response to one orchestrator prompt. The gate is bounded to exactly 0 (HIGH) or 1 (MEDIUM/LOW) exchanges.
|
|
18
|
+
**Signal** — Evidence in user input used to evaluate confidence across 6 axes (domain vocabulary, deliverable clarity, scope, ambiguity, file reference, domain count).
|
|
19
|
+
|
|
11
20
|
## Relationships
|
|
12
21
|
- OpenHermes contains many Skills, Commands, Agents, and Instructions.
|
|
13
22
|
- Skills are invoked on demand.
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">⟳ OpenHermes</h1>
|
|
3
|
-
<p align="center"><b>
|
|
4
|
-
<i>The AI orchestrator that never
|
|
3
|
+
<p align="center"><b>Pragmatic. Task-focused. Concise.</b><br>
|
|
4
|
+
<i>The AI orchestrator that never stalls — it classifies, delegates, and routes.</i></p>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
OpenHermes doesn't.
|
|
19
19
|
|
|
20
|
-
Drop it into OpenCode. Get a
|
|
20
|
+
Drop it into OpenCode. Get a closed-loop pipeline: auto-classify every request, delegate to specialists, route results automatically. No "can I?", no "shall I?", no "what next?" — just concise execution until the job is done.
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
23
|
{ "plugin": ["openhermes@git+https://github.com/nathwn12/openhermes.git"] }
|
|
@@ -55,12 +55,20 @@ One sentence. Nine automated steps. Each skill loaded on demand, executed in iso
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
-
###
|
|
58
|
+
### Four safety layers
|
|
59
59
|
|
|
60
60
|
The loop runs unsupervised because these never turn off:
|
|
61
61
|
|
|
62
|
-
- **🔁 Loop Guard** — stops if the same skill fires
|
|
62
|
+
- **🔁 Loop Guard** — stops if the same skill fires 5+ times or 8+ hops produce no progress
|
|
63
63
|
- **❓ Question Gate** — never routes into uncertainty; surfaces if input is missing
|
|
64
|
+
- **💬 Confidence Gate** — calibrates whether to skip, echo, or ask before classifying
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
HIGH ──→ classify silently (transparent gate)
|
|
68
|
+
MEDIUM ──→ echo + confirm, then classify
|
|
69
|
+
LOW ──→ ask + classify (defaults to oh-planner)
|
|
70
|
+
```
|
|
71
|
+
|
|
64
72
|
- **📋 Auto-Handoff** — writes a structured session artifact before context switches
|
|
65
73
|
|
|
66
74
|
---
|
|
@@ -69,15 +77,16 @@ The loop runs unsupervised because these never turn off:
|
|
|
69
77
|
|
|
70
78
|
| Capability | Why it matters |
|
|
71
79
|
|---|---|
|
|
72
|
-
| **Self-driving loop** | Type once. OpenHermes classifies, delegates, and routes — no pauses, no asking permission. |
|
|
73
|
-
| **
|
|
80
|
+
| **Self-driving loop** | Type once. OpenHermes classifies, delegates, and routes — no pauses, no asking permission, no verbosity. |
|
|
81
|
+
| **31 specialist skills** | Planning → building → testing → browser → security → review → shipping → retro. Every dev cycle phase. |
|
|
74
82
|
| **Auto-detected user skills** | Drop a skill in `~/.agents/skills/`. OpenHermes finds it. Same name as a built-in? Your version wins. Survives `npm update`. |
|
|
75
83
|
| **`/oh-doctor`** | Verify plugin load, skill discovery, command registration, config safety. |
|
|
76
84
|
| **`/oh-log`** | Session log — routing hops, skill loads, compaction events. |
|
|
77
|
-
| **Shared operating model** |
|
|
85
|
+
| **Shared operating model** | CHARTER + AUTOPILOT + CONTEXT + ETHOS injected every session. Every interaction grounded in the same rules. |
|
|
86
|
+
| **CORE/DEEP skill format** | Every skill is a two-file system: CORE (SKILL.md) handles 80% of passes in one read. DEEP.md loads on demand for hard cases. |
|
|
78
87
|
| **Plan file storage** | `~/.local/share/opencode/openhermes/plans/`. Survives `npm update`. |
|
|
79
88
|
|
|
80
|
-
##
|
|
89
|
+
## 31 skills — three tiers
|
|
81
90
|
|
|
82
91
|
### Tier 4 — Pipeline orchestrators
|
|
83
92
|
Full multi-phase workflows:
|
|
@@ -95,20 +104,23 @@ Span multiple phases and coordinate other skills:
|
|
|
95
104
|
|
|
96
105
|
| Skill | Purpose |
|
|
97
106
|
|---|---|
|
|
98
|
-
| **oh-
|
|
107
|
+
| **oh-browser** | Browser automation via agent-browser CLI. Navigate pages, fill forms, take screenshots, scrape data, test web apps. |
|
|
99
108
|
| **oh-grill** | Stress-test plans through relentless Socratic questioning |
|
|
100
109
|
| **oh-plan-review** | Multi-lens review: Engineering, Design, DX, Strategy |
|
|
110
|
+
| **oh-planner** | Brainstorm, architect, autoplan, decision pipeline |
|
|
101
111
|
| **oh-security** | Audit: secrets, supply chain, CI/CD, OWASP, LLM security |
|
|
102
112
|
| **oh-refactor** | Surgical behavior-preserving refactoring |
|
|
103
113
|
| **oh-review** | Two-axis review (Standards + Spec) in parallel sub-agents |
|
|
104
114
|
| **oh-fusion** | Skill ingestion pipeline: discover → analyze → adapt → fuse → integrate |
|
|
105
115
|
| **oh-retro** | Weekly retrospective — analyze commit history and patterns |
|
|
116
|
+
| **oh-worktree** | Workspace isolation via git worktrees. Detect existing isolation, create isolated workspaces, run project setup, verify clean baseline. |
|
|
106
117
|
|
|
107
118
|
### Tier 2 — Focused skills
|
|
108
119
|
Single-purpose, one thing well:
|
|
109
120
|
|
|
110
121
|
| Skill | Purpose |
|
|
111
122
|
|---|---|
|
|
123
|
+
| **oh-ascii** | Complete ASCII diagramming: design patterns, generation, structural validation |
|
|
112
124
|
| **oh-expert** | AI self-diagnosis: sycophancy, hallucination, attention dynamics |
|
|
113
125
|
| **oh-full-output** | Override truncation, ban placeholders, enforce complete generation |
|
|
114
126
|
| **oh-health** | Code quality dashboard: tools, composite score, trend |
|
|
@@ -119,7 +131,6 @@ Single-purpose, one thing well:
|
|
|
119
131
|
| **oh-triage** | Issue triage state machine — classify, prioritise, assign |
|
|
120
132
|
| **oh-issue** | Break a plan/spec/PRD into independently-grabbable issues |
|
|
121
133
|
| **oh-prd** | Conversation → PRD → GitHub issue |
|
|
122
|
-
| **oh-caveman** | Ultra-compressed mode — cut token usage ~75% |
|
|
123
134
|
| **oh-freeze** | Restrict file edits to a specific directory |
|
|
124
135
|
| **oh-learn** | Extract, evolve, promote session learnings as instincts |
|
|
125
136
|
| **oh-guard** | Safety confirmation — warn before destructive operations |
|
|
@@ -137,13 +148,13 @@ openhermes-pkg/
|
|
|
137
148
|
├── ETHOS.md # Operating principles
|
|
138
149
|
├── bootstrap.ts # Plugin entry — registers everything
|
|
139
150
|
├── index.ts # Package entrypoint
|
|
140
|
-
├── lib/ # harness-resolver.ts
|
|
151
|
+
├── lib/ # harness-resolver.ts
|
|
141
152
|
├── harness/
|
|
142
153
|
│ ├── agents/ # Agent manifests (OpenHermes primary)
|
|
143
|
-
│ ├── codex/ #
|
|
154
|
+
│ ├── codex/ # CHARTER, AUTOPILOT
|
|
144
155
|
│ ├── commands/ # Slash commands (/oh-doctor, /oh-log)
|
|
145
|
-
│ ├── instructions/ #
|
|
146
|
-
│ └── skills/ #
|
|
156
|
+
│ ├── instructions/ # SHELL.md
|
|
157
|
+
│ └── skills/ # 31 skill SKILL.md files (CORE/DEEP format)
|
|
147
158
|
└── test/
|
|
148
159
|
```
|
|
149
160
|
|
package/bootstrap.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
2
|
import fs from "node:fs"
|
|
3
3
|
import os from "node:os"
|
|
4
|
-
import { fileURLToPath } from "node:url"
|
|
5
4
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
6
|
-
import { createLogger } from "./lib/logger.ts"
|
|
7
5
|
import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.ts"
|
|
8
6
|
|
|
9
|
-
const log = createLogger("bootstrap")
|
|
10
|
-
const sessionLog = createLogger("session")
|
|
11
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const BOOTSTRAP_MARKER = "OPENHERMES_BOOTSTRAP"
|
|
13
7
|
const OPENHERMES_AGENT = "OpenHermes"
|
|
14
8
|
|
|
9
|
+
// User skill directories — auto-discovered on every session, survive npm updates
|
|
10
|
+
const USER_SKILL_DIRS: ReadonlyArray<string> = [
|
|
11
|
+
path.join(os.homedir(), ".agents", "skills"),
|
|
12
|
+
path.join(os.homedir(), ".config", "opencode", "skills"),
|
|
13
|
+
path.join(os.homedir(), ".claude", "skills"), // Claude Code backward compat
|
|
14
|
+
]
|
|
15
|
+
|
|
15
16
|
// Canonical storage under OpenCode's data directory — survives npm updates
|
|
16
17
|
let _planStorageOverride: string | undefined
|
|
17
18
|
export function setPlanStorageDirForTest(dir: string | undefined): void { _planStorageOverride = dir }
|
|
@@ -23,13 +24,8 @@ function getProjectName(projectDir: string): string {
|
|
|
23
24
|
return path.basename(projectDir)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
// User skill directories — auto-scanned on every session, survive npm updates
|
|
27
|
-
const USER_SKILL_DIRS: ReadonlyArray<string> = [
|
|
28
|
-
path.join(os.homedir(), ".agents", "skills"),
|
|
29
|
-
path.join(os.homedir(), ".config", "opencode", "skills"),
|
|
30
|
-
]
|
|
31
27
|
|
|
32
|
-
export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir }
|
|
28
|
+
export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir, ensurePlanFile }
|
|
33
29
|
|
|
34
30
|
function parseFrontmatter(raw: string | undefined): Record<string, string> {
|
|
35
31
|
const frontmatter: Record<string, string> = {}
|
|
@@ -128,9 +124,6 @@ function uniqueStrings(existing: string[] = [], additions: string[] = []): strin
|
|
|
128
124
|
return merged
|
|
129
125
|
}
|
|
130
126
|
|
|
131
|
-
function readText(filePath: string): string {
|
|
132
|
-
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : ""
|
|
133
|
-
}
|
|
134
127
|
|
|
135
128
|
function regexEscape(s: string): string {
|
|
136
129
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
@@ -182,15 +175,58 @@ function ensureDir(dir: string): void {
|
|
|
182
175
|
}
|
|
183
176
|
}
|
|
184
177
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Ensure a plan file exists for the project.
|
|
180
|
+
* Creates a skeleton plan if none exists or if the latest is complete/abandoned.
|
|
181
|
+
* Reuses an existing active or in-progress plan.
|
|
182
|
+
* Returns the path to the plan file.
|
|
183
|
+
*/
|
|
184
|
+
function ensurePlanFile(projectDir: string): string {
|
|
185
|
+
const projectName = getProjectName(projectDir)
|
|
186
|
+
const storage = planStorageDir()
|
|
187
|
+
ensureDir(storage)
|
|
188
|
+
|
|
189
|
+
// Reuse active or in-progress plan
|
|
190
|
+
const latest = findLatestPlanFile(projectDir)
|
|
191
|
+
if (latest) {
|
|
192
|
+
const content = fs.readFileSync(latest, "utf8")
|
|
193
|
+
const status = content.match(/^Status:\s*(.+)$/m)?.[1]?.trim()
|
|
194
|
+
if (status === "active" || status === "in-progress") {
|
|
195
|
+
return latest
|
|
196
|
+
}
|
|
193
197
|
}
|
|
198
|
+
|
|
199
|
+
// Determine next sequence number
|
|
200
|
+
let nextSeq = 1
|
|
201
|
+
if (latest) {
|
|
202
|
+
const m = path.basename(latest).match(/-plan-(\d{3})\.md$/)
|
|
203
|
+
if (m) nextSeq = parseInt(m[1], 10) + 1
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const planId = `${projectName}-plan-${String(nextSeq).padStart(3, "0")}`
|
|
207
|
+
const planPath = path.join(storage, `${planId}.md`)
|
|
208
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 16)
|
|
209
|
+
|
|
210
|
+
const content = [
|
|
211
|
+
`# PLAN: ${projectName}`,
|
|
212
|
+
"",
|
|
213
|
+
`Plan ID: ${planId}`,
|
|
214
|
+
`Project: ${projectName}`,
|
|
215
|
+
`Status: active`,
|
|
216
|
+
`Created: ${now}`,
|
|
217
|
+
`Updated: ${now}`,
|
|
218
|
+
`Project Path: ${projectDir}`,
|
|
219
|
+
`Plan Path: ${planPath}`,
|
|
220
|
+
`Objective: (pending classification)`,
|
|
221
|
+
"",
|
|
222
|
+
"## Tasks",
|
|
223
|
+
"",
|
|
224
|
+
"- [ ] (discoverable — pending classification)",
|
|
225
|
+
"",
|
|
226
|
+
].join("\n")
|
|
227
|
+
|
|
228
|
+
fs.writeFileSync(planPath, content, "utf8")
|
|
229
|
+
return planPath
|
|
194
230
|
}
|
|
195
231
|
|
|
196
232
|
export function buildCompactionContext(projectDir: string): string[] {
|
|
@@ -232,83 +268,8 @@ export function formatSessionEvent(event: SessionLifecycleEvent): { level: "info
|
|
|
232
268
|
}
|
|
233
269
|
}
|
|
234
270
|
|
|
235
|
-
function parseRouteYaml(raw: string): { pass: string; fail: string; blocker: string } {
|
|
236
|
-
const def: { pass: string; fail: string; blocker: string } = { pass: "surface", fail: "surface", blocker: "surface" }
|
|
237
|
-
const m = raw.match(/route:\n((?: [^\n]*\n?)*)/)
|
|
238
|
-
if (!m) return def
|
|
239
|
-
const block = m[1]
|
|
240
|
-
|
|
241
|
-
const kv = (key: string): string | undefined => {
|
|
242
|
-
// Single-line: pass: oh-builder (horizontal whitespace only, no newlines)
|
|
243
|
-
const s = block.match(new RegExp(` ${key}:[ \\t]*(\\S.*)`))
|
|
244
|
-
if (s) return s[1].trim()
|
|
245
|
-
// Multi-line array: pass:\n - oh-builder\n - oh-gauntlet
|
|
246
|
-
const a = block.match(new RegExp(` ${key}:\\n((?: - .+\\n?)*)`))
|
|
247
|
-
if (a) {
|
|
248
|
-
const items = a[1].match(/ - (.+)/g)?.map(i => i.replace(/ - /, "").trim()) ?? []
|
|
249
|
-
return items.length > 0 ? `[${items.join(", ")}]` : undefined
|
|
250
|
-
}
|
|
251
|
-
return undefined
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const p = kv("pass")
|
|
255
|
-
const f = kv("fail")
|
|
256
|
-
const b = kv("blocker")
|
|
257
|
-
if (p) def.pass = p
|
|
258
|
-
if (f) def.fail = f
|
|
259
|
-
if (b) def.blocker = b
|
|
260
|
-
return def
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function buildRoutingInventory(skillDirs: string[]): string {
|
|
264
|
-
const rows: string[] = []
|
|
265
|
-
for (const dir of skillDirs) {
|
|
266
|
-
let entries: string[] = []
|
|
267
|
-
try { entries = fs.readdirSync(dir).filter(e => fs.statSync(path.join(dir, e)).isDirectory()) } catch { continue }
|
|
268
|
-
for (const name of entries.sort()) {
|
|
269
|
-
const skPath = path.join(dir, name, "SKILL.md")
|
|
270
|
-
if (!fs.existsSync(skPath)) continue
|
|
271
|
-
const raw = fs.readFileSync(skPath, "utf8").replace(/\r\n/g, "\n")
|
|
272
|
-
const fm = raw.match(/^---\n([\s\S]*?)\n---/)
|
|
273
|
-
if (!fm) continue
|
|
274
|
-
const route = parseRouteYaml(fm[1])
|
|
275
|
-
rows.push(`| **${name}** | ${route.pass} | ${route.fail} | ${route.blocker} |`)
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (rows.length === 0) return ""
|
|
279
|
-
const header = "## Dynamic Routing Inventory\n\nAll skills and their routes:\n\n| Skill | pass | fail | blocker |\n|---|---|---|---|\n"
|
|
280
|
-
return header + rows.join("\n")
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function buildBootstrapContent(hDir: string, extraDirs: string[] = []): string {
|
|
284
|
-
const parts = [
|
|
285
|
-
`<${BOOTSTRAP_MARKER}>`,
|
|
286
|
-
`You are OpenHermes.`,
|
|
287
|
-
`OpenHermes is OpenCode-native: load skills on demand, always delegate, never execute tasks directly, and keep the surface small.`,
|
|
288
|
-
`Durable state is removed for now. Do not invent a persistence layer unless the user explicitly asks for one later.`,
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
const autopilot = readText(path.join(hDir, "codex", "AUTOPILOT.md"))
|
|
292
|
-
const constitution = readText(path.join(hDir, "codex", "CONSTITUTION.md"))
|
|
293
|
-
const runtime = readText(path.join(hDir, "instructions", "RUNTIME.md"))
|
|
294
|
-
const context = readText(path.join(__dirname, "CONTEXT.md"))
|
|
295
|
-
const ethos = readText(path.join(__dirname, "ETHOS.md"))
|
|
296
271
|
|
|
297
|
-
if (autopilot) parts.push(`<AUTOPILOT>\n${autopilot}\n</AUTOPILOT>`)
|
|
298
|
-
if (constitution) parts.push(`<CONSTITUTION>\n${constitution}\n</CONSTITUTION>`)
|
|
299
|
-
if (runtime) parts.push(`<RUNTIME>\n${runtime}\n</RUNTIME>`)
|
|
300
|
-
if (context) parts.push(`<CONTEXT>\n${context}\n</CONTEXT>`)
|
|
301
|
-
if (ethos) parts.push(`<ETHOS>\n${ethos}\n</ETHOS>`)
|
|
302
272
|
|
|
303
|
-
// Dynamic routing inventory: built-in skills + user skills
|
|
304
|
-
const allSkillDirs = [path.join(hDir, "skills"), ...extraDirs.filter(Boolean)]
|
|
305
|
-
const inventory = buildRoutingInventory(allSkillDirs)
|
|
306
|
-
if (inventory) parts.push(inventory)
|
|
307
|
-
|
|
308
|
-
parts.push(`</${BOOTSTRAP_MARKER}>`)
|
|
309
|
-
|
|
310
|
-
return parts.join("\n\n")
|
|
311
|
-
}
|
|
312
273
|
|
|
313
274
|
interface OpenHermesConfig {
|
|
314
275
|
skills?: { paths?: string[] }
|
|
@@ -323,23 +284,26 @@ export const BootstrapPlugin: Plugin = async (ctx) => {
|
|
|
323
284
|
const skillsDir = path.join(hDir, "skills")
|
|
324
285
|
const commandsDir = path.join(hDir, "commands")
|
|
325
286
|
const agentsDir = path.join(hDir, "agents")
|
|
287
|
+
const client = ctx.client // SDK client for structured logging
|
|
288
|
+
|
|
289
|
+
// Safe logging — uses OpenCode SDK when available, falls back to stdout for tests
|
|
290
|
+
async function logToOC(level: "info" | "warn" | "error" | "debug", message: string): Promise<void> {
|
|
291
|
+
if (client?.app?.log) {
|
|
292
|
+
await client.app.log({ body: { service: "openhermes", level, message } })
|
|
293
|
+
} else {
|
|
294
|
+
console.log(`[openhermes] [${level.toUpperCase()}] ${message}`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
326
298
|
// Auto-detect and wire user skills from ~/.agents/skills and ~/.config/opencode/skills
|
|
327
|
-
// (Must happen before bootstrapContent is built so routing inventory includes user skills)
|
|
328
299
|
const userSkillPaths: string[] = []
|
|
329
300
|
for (const userDir of USER_SKILL_DIRS) {
|
|
330
301
|
ensureDir(userDir)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
userSkillPaths.push(userDir)
|
|
334
|
-
log.info(`found ${count} user skill(s) in ${userDir}`)
|
|
335
|
-
}
|
|
302
|
+
userSkillPaths.push(userDir)
|
|
303
|
+
await logToOC("info", `wired user skills from ${userDir}`)
|
|
336
304
|
}
|
|
337
305
|
|
|
338
|
-
const bootstrapContent = buildBootstrapContent(hDir, userSkillPaths)
|
|
339
306
|
const compactionContext = buildCompactionContext(ctx.directory)
|
|
340
|
-
const builtInCount = countSkills(skillsDir)
|
|
341
|
-
const userCount = userSkillPaths.reduce((sum, d) => sum + countSkills(d), 0)
|
|
342
|
-
|
|
343
307
|
// Ensure plan storage exists
|
|
344
308
|
ensureDir(planStorageDir())
|
|
345
309
|
|
|
@@ -350,7 +314,13 @@ export const BootstrapPlugin: Plugin = async (ctx) => {
|
|
|
350
314
|
const allPaths = [skillsDir, ...userSkillPaths]
|
|
351
315
|
config.skills.paths = uniqueStrings(config.skills.paths || [], allPaths)
|
|
352
316
|
|
|
353
|
-
|
|
317
|
+
await logToOC("info", `skills: ${allPaths.length} path(s)`)
|
|
318
|
+
|
|
319
|
+
// Register harness docs as native OpenCode instructions — no prompt-embedding needed
|
|
320
|
+
config.instructions = uniqueStrings(config.instructions ?? [], [
|
|
321
|
+
path.join(hDir, "codex"),
|
|
322
|
+
path.join(hDir, "instructions"),
|
|
323
|
+
])
|
|
354
324
|
|
|
355
325
|
config.command = { ...(config.command ?? {}), ...commandDefinitions(commandsDir) }
|
|
356
326
|
|
|
@@ -361,18 +331,63 @@ export const BootstrapPlugin: Plugin = async (ctx) => {
|
|
|
361
331
|
prompt: "You are OpenHermes.",
|
|
362
332
|
}
|
|
363
333
|
|
|
334
|
+
// Subagent permissions — tier-4 and tier-3 get execution access but cannot spawn orchestrators
|
|
335
|
+
const SUBAGENT_PERMISSIONS: Record<string, Record<string, unknown>> = {
|
|
336
|
+
"oh-builder": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
337
|
+
"oh-browser": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
338
|
+
"oh-facade": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
339
|
+
"oh-gauntlet": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
340
|
+
"oh-manifest": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
341
|
+
"oh-ship": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
342
|
+
"oh-planner": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
343
|
+
"oh-grill": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
344
|
+
"oh-investigate": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
345
|
+
"oh-plan-review": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
346
|
+
"oh-security": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
347
|
+
"oh-refactor": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
348
|
+
"oh-review": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
349
|
+
"oh-fusion": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
350
|
+
"oh-retro": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
351
|
+
"oh-skill-craft": { bash: { "*": "allow" }, edit: "allow", read: "allow", glob: "allow", grep: "allow", task: { "oh-*": "deny" } },
|
|
352
|
+
}
|
|
353
|
+
|
|
364
354
|
config.agent = {
|
|
365
355
|
...(config.agent ?? {}),
|
|
366
356
|
...loadedAgents,
|
|
357
|
+
// Apply permissions + hidden flag to subagents
|
|
358
|
+
...Object.fromEntries(
|
|
359
|
+
Object.entries(loadedAgents)
|
|
360
|
+
.filter(([name]) => name !== OPENHERMES_AGENT)
|
|
361
|
+
.map(([name, agentDef]) => [
|
|
362
|
+
name,
|
|
363
|
+
{
|
|
364
|
+
...agentDef,
|
|
365
|
+
permission: SUBAGENT_PERMISSIONS[name] ?? { bash: { "*": "deny" }, edit: "deny", read: "allow" },
|
|
366
|
+
// Hide routing-internal subagents from @-menu
|
|
367
|
+
// Only agents with existing .md files can be hidden — names without files are no-ops
|
|
368
|
+
...(["oh-planner", "oh-grill", "oh-skill-craft"].includes(name) ? { hidden: true } : {}),
|
|
369
|
+
},
|
|
370
|
+
])
|
|
371
|
+
),
|
|
367
372
|
[OPENHERMES_AGENT]: {
|
|
368
373
|
...openHermesAgent,
|
|
369
374
|
description: openHermesAgent.description || "OpenHermes primary orchestrator",
|
|
370
375
|
mode: "primary",
|
|
376
|
+
steps: 15, // Max agentic iterations — prevents runaway loops
|
|
371
377
|
permission: {
|
|
372
|
-
bash: { "*": "
|
|
373
|
-
edit: "
|
|
374
|
-
read: "allow",
|
|
375
|
-
|
|
378
|
+
bash: { "*": "deny" }, // CANNOT execute commands
|
|
379
|
+
edit: "deny", // CANNOT write/edit files
|
|
380
|
+
read: "allow", // CAN read for classification
|
|
381
|
+
glob: "allow", // CAN search for files
|
|
382
|
+
grep: "allow", // CAN search content
|
|
383
|
+
task: { "*": "allow" }, // MUST delegate via subagents
|
|
384
|
+
skill: "allow", // CAN load skill instructions
|
|
385
|
+
webfetch: "allow", // CAN fetch docs for context
|
|
386
|
+
question: "allow", // CAN ask user questions
|
|
387
|
+
websearch: "allow", // CAN search web for research context
|
|
388
|
+
external_directory: { // CAN read/write plan files outside worktree
|
|
389
|
+
"~/.local/share/opencode/openhermes/**": "allow",
|
|
390
|
+
},
|
|
376
391
|
},
|
|
377
392
|
},
|
|
378
393
|
}
|
|
@@ -381,25 +396,47 @@ export const BootstrapPlugin: Plugin = async (ctx) => {
|
|
|
381
396
|
},
|
|
382
397
|
|
|
383
398
|
event: async ({ event }) => {
|
|
384
|
-
const
|
|
399
|
+
const typed = event as SessionLifecycleEvent
|
|
400
|
+
const record = formatSessionEvent(typed)
|
|
385
401
|
if (!record) return
|
|
386
|
-
|
|
402
|
+
await logToOC(record.level, record.message)
|
|
403
|
+
|
|
404
|
+
// NOTE: Plan files are NOT auto-created here. The LLM agent
|
|
405
|
+
// creates plans on demand (see Task Flow step 1 in agent prompt).
|
|
406
|
+
// Auto-creation produced ghost skeletons like plan-004.
|
|
407
|
+
|
|
408
|
+
// Reset delegation depth on session start/error
|
|
409
|
+
if (typed.type === "session.created" || typed.type === "session.error") {
|
|
410
|
+
delegationDepths.delete(`delegation:${ctx.directory}`)
|
|
411
|
+
}
|
|
387
412
|
},
|
|
388
413
|
|
|
389
414
|
"experimental.session.compacting": async (_input, output) => {
|
|
390
415
|
output.context.push(...compactionContext)
|
|
391
416
|
},
|
|
392
417
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
// Mechanical delegation loop guard — prevents runaway agent nesting
|
|
419
|
+
"tool.execute.before": async (input, output) => {
|
|
420
|
+
if (input.tool === "task") {
|
|
421
|
+
// Track delegation depth per project (one session per project at a time)
|
|
422
|
+
const depthKey = `delegation:${ctx.directory}`
|
|
423
|
+
const currentDepth = (delegationDepths.get(depthKey) ?? 0) + 1
|
|
424
|
+
delegationDepths.set(depthKey, currentDepth)
|
|
425
|
+
|
|
426
|
+
if (currentDepth >= 10) {
|
|
427
|
+
const errOutput = output as { args: unknown; isError?: boolean; content?: unknown[] }
|
|
428
|
+
errOutput.isError = true
|
|
429
|
+
errOutput.content = [{
|
|
430
|
+
type: "text",
|
|
431
|
+
text: "LOOP GUARD: Delegation depth exceeded (max 10). " +
|
|
432
|
+
"Surface to orchestrator with findings and stop delegating."
|
|
433
|
+
}]
|
|
434
|
+
}
|
|
402
435
|
}
|
|
403
436
|
},
|
|
437
|
+
|
|
404
438
|
}
|
|
405
439
|
}
|
|
440
|
+
|
|
441
|
+
// Module-level delegation depth tracker — reset per project session
|
|
442
|
+
const delegationDepths = new Map<string, number>()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oh-browser
|
|
3
|
+
description: "Browser automation via agent-browser CLI. Navigate pages, fill forms, click buttons, take screenshots, extract data, test web apps. Use when the user needs to interact with websites, automate browser tasks, scrape data, or test web applications."
|
|
4
|
+
mode: subagent
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Shell Pre-flight (Windows)
|
|
8
|
+
|
|
9
|
+
You are on Windows. Before ANY command execution, detect your shell:
|
|
10
|
+
- `$PSVersionTable` exists → PowerShell (`powershell` or `pwsh`)
|
|
11
|
+
- `%CMDCMDLINE%` is set → CMD
|
|
12
|
+
- `$0` or `$BASH` → Bash (Git Bash)
|
|
13
|
+
|
|
14
|
+
Operation → required shell:
|
|
15
|
+
- File ops (`Remove-Item`, `New-Item`), scoop, `.ps1` scripts, `$env:VAR` → **PowerShell**
|
|
16
|
+
- `git`, `bun`, `npm`, `node` → **any shell** (all work)
|
|
17
|
+
- `rm -rf`, `make`, Unix tools → **Git Bash**
|
|
18
|
+
- `.bat`/`.cmd` files → **CMD**
|
|
19
|
+
|
|
20
|
+
Wrong shell? Switch:
|
|
21
|
+
- → PowerShell: `powershell.exe -NoProfile -Command "..."`
|
|
22
|
+
- → Git Bash: `& "C:\Program Files\Git\bin\bash.exe" -c "..."`
|
|
23
|
+
- → CMD: `cmd.exe /c "..."`
|
|
24
|
+
|
|
25
|
+
Always know before you go.
|
|
26
|
+
|
|
27
|
+
# oh-browser
|
|
28
|
+
|
|
29
|
+
Browser automation via agent-browser CLI. Fast native Rust CLI wrapping Chrome/Chromium via CDP.
|
|
30
|
+
|
|
31
|
+
## Prerequisites
|
|
32
|
+
|
|
33
|
+
- agent-browser installed globally: `npm install -g agent-browser && agent-browser install`
|
|
34
|
+
- Chrome/Chromium (auto-downloaded by `agent-browser install`)
|
|
35
|
+
- State files contain session tokens — add to `.gitignore`, never commit
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
1. **Launch browser** — `agent-browser open <url>` or `agent-browser open` (blank page then navigate)
|
|
40
|
+
2. **Snapshot page state** — `agent-browser snapshot` returns accessibility tree with `@eN` refs
|
|
41
|
+
3. **Interact** — use `@eN` refs from snapshot:
|
|
42
|
+
- `agent-browser click @eN`
|
|
43
|
+
- `agent-browser fill @eN "value"`
|
|
44
|
+
- `agent-browser select @eN "option"`
|
|
45
|
+
- `agent-browser hover @eN`
|
|
46
|
+
4. **Extract data** — `agent-browser get text @eN`, `agent-browser get html @eN`, `agent-browser screenshot`
|
|
47
|
+
5. **Close** — `agent-browser close`
|
|
48
|
+
|
|
49
|
+
## Common Patterns
|
|
50
|
+
|
|
51
|
+
- **Annotated screenshots**: `agent-browser screenshot --annotate` — overlays numbered labels matching `@eN` refs.
|
|
52
|
+
- **Batch execution**: `agent-browser batch "open url" "snapshot" "click @e1"` — avoids per-command startup overhead.
|
|
53
|
+
- **Session persistence**: `--session-name <name>` — auto-saves/restores cookies and localStorage.
|
|
54
|
+
- **Auth vault**: `agent-browser auth save <name> --url <url> --username <user>` — encrypted credentials.
|
|
55
|
+
- **Diff**: `agent-browser diff snapshot` for change detection. `agent-browser diff screenshot --baseline before.png` for visual diff.
|
|
56
|
+
- **Chrome profile reuse**: `--profile Default` — use existing Chrome login state.
|
|
57
|
+
- **Tab labeling**: `agent-browser tab new --label docs <url>` — memorable labels.
|
|
58
|
+
- **Parallel scrape**: Use `batch --json` with piped command arrays.
|
|
59
|
+
|
|
60
|
+
## Common Commands Reference
|
|
61
|
+
|
|
62
|
+
| Task | Command |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Open URL | `agent-browser open <url>` |
|
|
65
|
+
| Get page state | `agent-browser snapshot -i` |
|
|
66
|
+
| Click | `agent-browser click @eN` or `agent-browser click "css-selector"` |
|
|
67
|
+
| Type text | `agent-browser fill @eN "text"` |
|
|
68
|
+
| Screenshot | `agent-browser screenshot --annotate` |
|
|
69
|
+
| Extract text | `agent-browser get text @eN` |
|
|
70
|
+
| Run JS | `agent-browser eval "document.title"` |
|
|
71
|
+
| Wait for element | `agent-browser wait ".selector"` |
|
|
72
|
+
| Scroll | `agent-browser scroll down 200` |
|
|
73
|
+
| Multi-step | `agent-browser batch "cmd1" "cmd2" "cmd3"` |
|
|
74
|
+
|
|
75
|
+
## Anti-patterns
|
|
76
|
+
|
|
77
|
+
- Forgetting `agent-browser install` first
|
|
78
|
+
- Not closing browser sessions (daemon processes leak)
|
|
79
|
+
- Using CSS selectors when `@eN` refs are faster
|
|
80
|
+
- Running individual commands instead of batch for multi-step
|
|
81
|
+
- Passing credentials in prompts instead of auth vault
|
|
82
|
+
- Committing state files with session tokens
|
|
83
|
+
|
|
84
|
+
## Security
|
|
85
|
+
|
|
86
|
+
- Use `--allowed-domains` to restrict navigation
|
|
87
|
+
- Use auth vault instead of passing credentials in prompts
|
|
88
|
+
- Session state files contain tokens — keep in `.gitignore`
|
|
89
|
+
- `--content-boundaries` wraps page output in delimiters
|
|
90
|
+
|
|
91
|
+
## Routing
|
|
92
|
+
|
|
93
|
+
| Outcome | Route |
|
|
94
|
+
|---------|-------|
|
|
95
|
+
| pass | → surface (results to user) |
|
|
96
|
+
| fail | → oh-browser (retry with corrected approach) |
|
|
97
|
+
| blocker | → surface with error details |
|