openhermes 4.1.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/ETHOS.md +6 -3
- package/LICENSE +21 -21
- package/README.md +120 -79
- package/bootstrap.ts +284 -41
- 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 +106 -62
- package/harness/codex/AUTOPILOT.md +178 -0
- package/harness/codex/CHARTER.md +81 -0
- package/harness/commands/oh-doctor.md +193 -14
- package/harness/commands/oh-log.md +18 -0
- 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 +16 -89
- package/harness/skills/oh-expert/DEEP.md +85 -0
- package/harness/skills/oh-expert/SKILL.md +19 -106
- package/harness/skills/oh-facade/DEEP.md +182 -0
- package/harness/skills/oh-facade/SKILL.md +34 -0
- package/harness/skills/oh-freeze/DEEP.md +18 -0
- package/harness/skills/oh-freeze/SKILL.md +15 -15
- package/harness/skills/oh-full-output/DEEP.md +25 -0
- package/harness/skills/oh-full-output/SKILL.md +28 -0
- package/harness/skills/oh-fusion/DEEP.md +120 -0
- package/harness/skills/oh-fusion/SKILL.md +36 -0
- package/harness/skills/oh-gauntlet/DEEP.md +77 -0
- package/harness/skills/oh-gauntlet/SKILL.md +17 -105
- package/harness/skills/oh-grill/DEEP.md +51 -0
- package/harness/skills/oh-grill/SKILL.md +16 -63
- package/harness/skills/oh-guard/DEEP.md +19 -0
- package/harness/skills/oh-guard/SKILL.md +15 -20
- package/harness/skills/oh-handoff/DEEP.md +48 -0
- package/harness/skills/oh-handoff/SKILL.md +18 -19
- package/harness/skills/oh-health/DEEP.md +74 -0
- package/harness/skills/oh-health/SKILL.md +17 -76
- package/harness/skills/oh-init/DEEP.md +85 -0
- package/harness/skills/oh-init/SKILL.md +17 -197
- package/harness/skills/oh-investigate/DEEP.md +171 -0
- package/harness/skills/oh-investigate/SKILL.md +18 -61
- package/harness/skills/oh-issue/DEEP.md +21 -0
- package/harness/skills/oh-issue/SKILL.md +16 -23
- package/harness/skills/oh-learn/DEEP.md +44 -0
- package/harness/skills/oh-learn/SKILL.md +17 -79
- package/harness/skills/oh-manifest/DEEP.md +92 -0
- package/harness/skills/oh-manifest/SKILL.md +15 -107
- package/harness/skills/oh-plan-review/DEEP.md +90 -0
- package/harness/skills/oh-plan-review/SKILL.md +19 -114
- package/harness/skills/oh-planner/DEEP.md +172 -0
- package/harness/skills/oh-planner/SKILL.md +16 -143
- package/harness/skills/oh-prd/DEEP.md +45 -0
- package/harness/skills/oh-prd/SKILL.md +15 -22
- package/harness/skills/oh-refactor/DEEP.md +122 -0
- package/harness/skills/oh-refactor/SKILL.md +33 -0
- package/harness/skills/oh-retro/DEEP.md +26 -0
- package/harness/skills/oh-retro/SKILL.md +17 -20
- package/harness/skills/oh-review/DEEP.md +87 -0
- package/harness/skills/oh-review/SKILL.md +17 -96
- package/harness/skills/oh-security/DEEP.md +83 -0
- package/harness/skills/oh-security/SKILL.md +18 -96
- package/harness/skills/oh-ship/DEEP.md +141 -0
- package/harness/skills/oh-ship/SKILL.md +18 -26
- package/harness/skills/oh-skill-craft/DEEP.md +369 -0
- package/harness/skills/oh-skill-craft/SKILL.md +20 -93
- package/harness/skills/oh-skills-link/DEEP.md +16 -0
- package/harness/skills/oh-skills-link/SKILL.md +15 -16
- package/harness/skills/oh-skills-list/DEEP.md +20 -0
- package/harness/skills/oh-skills-list/SKILL.md +14 -18
- package/harness/skills/oh-triage/DEEP.md +23 -0
- package/harness/skills/oh-triage/SKILL.md +15 -20
- package/harness/skills/oh-worktree/DEEP.md +169 -0
- package/harness/skills/oh-worktree/SKILL.md +32 -0
- package/lib/harness-resolver.ts +10 -12
- package/package.json +9 -4
- package/scripts/count-tokens.mjs +158 -0
- package/scripts/oh-doctor.ps1 +342 -0
- package/harness/codex/CONSTITUTION.md +0 -70
- package/harness/codex/ROUTING.md +0 -127
- package/harness/instructions/RUNTIME.md +0 -55
- package/harness/skills/oh-caveman/SKILL.md +0 -33
- package/lib/logger.ts +0 -69
package/bootstrap.ts
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
2
|
import fs from "node:fs"
|
|
3
|
-
import
|
|
3
|
+
import os from "node:os"
|
|
4
4
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
5
|
-
import { createLogger } from "./lib/logger.ts"
|
|
6
5
|
import { getHarnessDir, setHarnessRootForTest, resolveHarnessRoot } from "./lib/harness-resolver.ts"
|
|
7
6
|
|
|
8
|
-
const log = createLogger("bootstrap")
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
-
const BOOTSTRAP_MARKER = "OPENHERMES_BOOTSTRAP"
|
|
11
7
|
const OPENHERMES_AGENT = "OpenHermes"
|
|
12
8
|
|
|
13
|
-
|
|
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
|
+
|
|
16
|
+
// Canonical storage under OpenCode's data directory — survives npm updates
|
|
17
|
+
let _planStorageOverride: string | undefined
|
|
18
|
+
export function setPlanStorageDirForTest(dir: string | undefined): void { _planStorageOverride = dir }
|
|
19
|
+
function planStorageDir(): string {
|
|
20
|
+
return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "opencode", "openhermes", "plans")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getProjectName(projectDir: string): string {
|
|
24
|
+
return path.basename(projectDir)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export { resolveHarnessRoot, setHarnessRootForTest, getHarnessDir, ensurePlanFile }
|
|
14
29
|
|
|
15
30
|
function parseFrontmatter(raw: string | undefined): Record<string, string> {
|
|
16
31
|
const frontmatter: Record<string, string> = {}
|
|
@@ -109,32 +124,153 @@ function uniqueStrings(existing: string[] = [], additions: string[] = []): strin
|
|
|
109
124
|
return merged
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
|
|
128
|
+
function regexEscape(s: string): string {
|
|
129
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function findLatestPlanFile(projectDir: string): string | null {
|
|
133
|
+
const projectName = getProjectName(projectDir)
|
|
134
|
+
const storage = planStorageDir()
|
|
135
|
+
if (!fs.existsSync(storage)) return null
|
|
136
|
+
const pattern = new RegExp(`^${regexEscape(projectName)}-plan-(\\d{3})\\.md$`)
|
|
137
|
+
let latest: string | null = null
|
|
138
|
+
let highest = -1
|
|
139
|
+
try {
|
|
140
|
+
for (const entry of fs.readdirSync(storage)) {
|
|
141
|
+
const m = entry.match(pattern)
|
|
142
|
+
if (m) {
|
|
143
|
+
const n = parseInt(m[1], 10)
|
|
144
|
+
if (n > highest) {
|
|
145
|
+
highest = n
|
|
146
|
+
latest = path.join(storage, entry)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
return latest
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readPlanFromFile(filePath: string): string | null {
|
|
157
|
+
if (!fs.existsSync(filePath)) return null
|
|
158
|
+
const source = fs.readFileSync(filePath, "utf8")
|
|
159
|
+
const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim()
|
|
160
|
+
const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim()
|
|
161
|
+
if (!status && !objective) return null
|
|
162
|
+
const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
|
|
163
|
+
return `Active plan: ${parts.join(" | ")}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readPlanSummary(projectDir: string): string | null {
|
|
167
|
+
const planFile = findLatestPlanFile(projectDir)
|
|
168
|
+
if (!planFile) return null
|
|
169
|
+
return readPlanFromFile(planFile)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ensureDir(dir: string): void {
|
|
173
|
+
if (!fs.existsSync(dir)) {
|
|
174
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
175
|
+
}
|
|
114
176
|
}
|
|
115
177
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
}
|
|
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
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function buildCompactionContext(projectDir: string): string[] {
|
|
233
|
+
const context = [
|
|
234
|
+
"OpenHermes: native-first, verify before claim, always delegate, concise over verbose.",
|
|
235
|
+
"Preserve domain terms: skill, command, agent, bootstrap, compaction.",
|
|
236
|
+
"Preserve blockers, current task, and next steps; do not invent durable state.",
|
|
122
237
|
]
|
|
123
238
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
239
|
+
const planSummary = readPlanSummary(projectDir)
|
|
240
|
+
if (planSummary) context.push(planSummary)
|
|
241
|
+
|
|
242
|
+
return context
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
type SessionLifecycleEvent =
|
|
246
|
+
| { type: "session.created"; properties: { info: { id: string } } }
|
|
247
|
+
| { type: "session.compacted"; properties: { sessionID: string } }
|
|
248
|
+
| { type: "session.error"; properties: { sessionID?: string; error?: unknown } }
|
|
128
249
|
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
250
|
+
function readErrorMessage(error: unknown): string {
|
|
251
|
+
if (!error || typeof error !== "object") return "unknown error"
|
|
252
|
+
const value = error as { name?: unknown; message?: unknown; data?: { message?: unknown } }
|
|
253
|
+
const name = typeof value.name === "string" && value.name ? value.name : "Error"
|
|
254
|
+
const message = typeof value.data?.message === "string" && value.data.message ? value.data.message : typeof value.message === "string" && value.message ? value.message : ""
|
|
255
|
+
return message ? `${name}: ${message}` : name
|
|
256
|
+
}
|
|
134
257
|
|
|
135
|
-
|
|
258
|
+
export function formatSessionEvent(event: SessionLifecycleEvent): { level: "info" | "error"; message: string } | null {
|
|
259
|
+
switch (event.type) {
|
|
260
|
+
case "session.created":
|
|
261
|
+
return { level: "info", message: `session.created session=${event.properties.info.id}` }
|
|
262
|
+
case "session.compacted":
|
|
263
|
+
return { level: "info", message: `session.compacted session=${event.properties.sessionID}` }
|
|
264
|
+
case "session.error":
|
|
265
|
+
return { level: "error", message: `session.error session=${event.properties.sessionID ?? "unknown"} error=${readErrorMessage(event.properties.error)}` }
|
|
266
|
+
default:
|
|
267
|
+
return null
|
|
268
|
+
}
|
|
136
269
|
}
|
|
137
270
|
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
138
274
|
interface OpenHermesConfig {
|
|
139
275
|
skills?: { paths?: string[] }
|
|
140
276
|
command?: Record<string, unknown>
|
|
@@ -143,17 +279,48 @@ interface OpenHermesConfig {
|
|
|
143
279
|
default_agent?: string
|
|
144
280
|
}
|
|
145
281
|
|
|
146
|
-
export const BootstrapPlugin: Plugin = async () => {
|
|
282
|
+
export const BootstrapPlugin: Plugin = async (ctx) => {
|
|
147
283
|
const hDir = getHarnessDir()
|
|
148
284
|
const skillsDir = path.join(hDir, "skills")
|
|
149
285
|
const commandsDir = path.join(hDir, "commands")
|
|
150
286
|
const agentsDir = path.join(hDir, "agents")
|
|
151
|
-
const
|
|
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
|
+
|
|
298
|
+
// Auto-detect and wire user skills from ~/.agents/skills and ~/.config/opencode/skills
|
|
299
|
+
const userSkillPaths: string[] = []
|
|
300
|
+
for (const userDir of USER_SKILL_DIRS) {
|
|
301
|
+
ensureDir(userDir)
|
|
302
|
+
userSkillPaths.push(userDir)
|
|
303
|
+
await logToOC("info", `wired user skills from ${userDir}`)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const compactionContext = buildCompactionContext(ctx.directory)
|
|
307
|
+
// Ensure plan storage exists
|
|
308
|
+
ensureDir(planStorageDir())
|
|
152
309
|
|
|
153
310
|
return {
|
|
154
311
|
config: async (config: OpenHermesConfig) => {
|
|
155
312
|
config.skills = config.skills || {}
|
|
156
|
-
|
|
313
|
+
// Built-in paths first, user paths last → user skills override built-in on name conflict
|
|
314
|
+
const allPaths = [skillsDir, ...userSkillPaths]
|
|
315
|
+
config.skills.paths = uniqueStrings(config.skills.paths || [], allPaths)
|
|
316
|
+
|
|
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
|
+
])
|
|
157
324
|
|
|
158
325
|
config.command = { ...(config.command ?? {}), ...commandDefinitions(commandsDir) }
|
|
159
326
|
|
|
@@ -164,18 +331,63 @@ export const BootstrapPlugin: Plugin = async () => {
|
|
|
164
331
|
prompt: "You are OpenHermes.",
|
|
165
332
|
}
|
|
166
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
|
+
|
|
167
354
|
config.agent = {
|
|
168
355
|
...(config.agent ?? {}),
|
|
169
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
|
+
),
|
|
170
372
|
[OPENHERMES_AGENT]: {
|
|
171
373
|
...openHermesAgent,
|
|
172
374
|
description: openHermesAgent.description || "OpenHermes primary orchestrator",
|
|
173
375
|
mode: "primary",
|
|
376
|
+
steps: 15, // Max agentic iterations — prevents runaway loops
|
|
174
377
|
permission: {
|
|
175
|
-
bash: { "*": "
|
|
176
|
-
edit: "
|
|
177
|
-
read: "allow",
|
|
178
|
-
|
|
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
|
+
},
|
|
179
391
|
},
|
|
180
392
|
},
|
|
181
393
|
}
|
|
@@ -183,17 +395,48 @@ export const BootstrapPlugin: Plugin = async () => {
|
|
|
183
395
|
config.default_agent = OPENHERMES_AGENT
|
|
184
396
|
},
|
|
185
397
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
398
|
+
event: async ({ event }) => {
|
|
399
|
+
const typed = event as SessionLifecycleEvent
|
|
400
|
+
const record = formatSessionEvent(typed)
|
|
401
|
+
if (!record) return
|
|
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
|
+
}
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
"experimental.session.compacting": async (_input, output) => {
|
|
415
|
+
output.context.push(...compactionContext)
|
|
416
|
+
},
|
|
417
|
+
|
|
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
|
+
}
|
|
196
435
|
}
|
|
197
436
|
},
|
|
437
|
+
|
|
198
438
|
}
|
|
199
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 |
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oh-builder
|
|
3
|
+
description: "ALL-arounder builder — prototype, TDD, implement from plan, design interfaces. Consumes the plan file, produces working code."
|
|
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-builder
|
|
28
|
+
|
|
29
|
+
ALL-arounder builder. Prototyping, TDD, plan implementation, interface design. Consumes plan file from oh-planner or works standalone.
|
|
30
|
+
|
|
31
|
+
## Entry Modes
|
|
32
|
+
|
|
33
|
+
### Mode A: Prototype (exploratory)
|
|
34
|
+
Pick branch by question:
|
|
35
|
+
- **"Does this logic feel right?"** → Terminal branch. Tiny interactive app pushing state machine.
|
|
36
|
+
- **"What should this look like?"** → UI branch. Multiple visual variations switchable via param/control bar.
|
|
37
|
+
|
|
38
|
+
**Rules:** Throwaway from day one (clear name). One command to run. No persistence (memory state). Skip polish (no tests, minimal error handling). Surface state after every action. Delete/absorb answer when done.
|
|
39
|
+
|
|
40
|
+
### Mode B: TDD (test-first)
|
|
41
|
+
Red-green-refactor with vertical tracer bullets.
|
|
42
|
+
|
|
43
|
+
**Plan:** Confirm interface changes with user. Prioritize behaviors. Design for testability (public interface only). List behaviors, not implementation steps.
|
|
44
|
+
|
|
45
|
+
**Loop** per behavior:
|
|
46
|
+
```
|
|
47
|
+
RED: One test → fails
|
|
48
|
+
GREEN: Minimal code → passes
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Rules:** One test at a time. Only enough code to pass. Don't anticipate future tests. Tests through public interfaces. Never refactor while RED.
|
|
52
|
+
|
|
53
|
+
**Refactor** (all GREEN): Extract duplication, deepen modules, re-run tests after each step.
|
|
54
|
+
|
|
55
|
+
### Mode C: Design an Interface
|
|
56
|
+
"Design it twice" — generate multiple radically different designs, compare.
|
|
57
|
+
|
|
58
|
+
1. Gather requirements (problem, callers, operations, constraints)
|
|
59
|
+
2. Spawn 3+ parallel sub-agents with different constraints (min methods, max flexibility, optimize common case, specific paradigm)
|
|
60
|
+
3. Present designs (signature, examples, what it hides)
|
|
61
|
+
4. Compare (simplicity, generality, efficiency, depth)
|
|
62
|
+
5. Synthesize insights
|
|
63
|
+
|
|
64
|
+
### Mode D: From Plan
|
|
65
|
+
Plan exists → execute phases in order.
|
|
66
|
+
|
|
67
|
+
1. Read plan file
|
|
68
|
+
2. Each phase: implement per spec using TDD (Mode B)
|
|
69
|
+
3. Verify against criteria before moving on
|
|
70
|
+
4. Update plan with completed status
|
|
71
|
+
|
|
72
|
+
## Anti-patterns
|
|
73
|
+
- Polishing a prototype
|
|
74
|
+
- Writing all tests first (brittle, imaginary)
|
|
75
|
+
- Anticipating future tests
|
|
76
|
+
- Refactoring while RED
|
|
77
|
+
- Sub-agents producing similar designs (enforce radical difference)
|
|
78
|
+
- Implementing without verifying against plan criteria
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oh-facade
|
|
3
|
+
description: "Full UI pipeline: from concept to production frontend. Design system generation, premium component architecture, production-grade implementation, structured audit. Use when building any user-facing interface, component system, or visual application."
|
|
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-facade
|
|
28
|
+
|
|
29
|
+
Full UI pipeline: Concept → Design System → Build → Audit → Iterate. Closed-loop. Engineering, not decoration.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Sections
|
|
34
|
+
|
|
35
|
+
| Phase | Description |
|
|
36
|
+
|-------|-------------|
|
|
37
|
+
| [Phase 0: Redesign Entry](../skills/oh-facade/DEEP.md#phase-0-redesign-entry-existing-projects) | Existing project scan — detect framework/styling, run Phase 4 diagnosis, apply targeted upgrades without rewrite |
|
|
38
|
+
| [Phase 1: Concept](../skills/oh-facade/DEEP.md#phase-1-concept) | Context questions (product/user/problem/constraints), 4 visual archetypes (Warm Minimalist, Premium SaaS, Industrial Brutalist, Creative/Expressive), 3 metric dials (VARIANCE/MOTION/DENSITY), design brief output |
|
|
39
|
+
| [Phase 2: Design System](../skills/oh-facade/DEEP.md#phase-2-design-system) | Color tokens, typography stack, component specs, layout grid, motion/spring rules, GSAP ScrollTrigger patterns (pinning/scale/horizontal/stagger/text-split/python-randomization/bento) |
|
|
40
|
+
| [Phase 3: Build](../skills/oh-facade/DEEP.md#phase-3-build) | CSS foundations + token mapping, component library (all states: default/hover/active/focus/disabled/loading/empty/error), page assembly + responsive + viewport states, framework/a11y/meta |
|
|
41
|
+
| [Phase 4: Audit + Iterate](../skills/oh-facade/DEEP.md#phase-4-audit) | Priority-ranked audit P1-P5 (typography/color/layout, interactivity/states/motion, content quality, hardening/a11y, existing-project redesign). Fix in priority order, re-audit per level, surface blocker |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Hard Bans
|
|
46
|
+
|
|
47
|
+
- No emojis in code/content/alt/markup
|
|
48
|
+
- No Lorem Ipsum — write real draft copy
|
|
49
|
+
- No "Elevate", "Seamless", "Next-Gen", "Unleash", "Game-changer"
|
|
50
|
+
- No generic names (John Doe, Acme Corp)
|
|
51
|
+
- No rocket ship / shield cliche icons
|
|
52
|
+
- No purple/blue neon gradients
|
|
53
|
+
- No dark section in white page without committed dark mode
|
|
54
|
+
- No animated `top/left/width/height`
|
|
55
|
+
- No `window.addEventListener('scroll')`
|
|
56
|
+
- No `h-screen`
|
|
57
|
+
- No pill shapes on large containers, cards, or primary buttons
|
|
58
|
+
- No generic icon libraries (Lucide, Feather, Heroicons)
|
|
59
|
+
- No 3-equal-card feature rows — replace with 2-column zig-zag, asymmetric grid, or masonry
|
|
60
|
+
- No `#000000` backgrounds — use off-black `#0a0a0a` or `#121212`
|
|
61
|
+
- No even 45-degree linear gradients — break with radial, noise overlay, or mesh
|
|
62
|
+
- No mixing warm and cool grays — stick to one gray family throughout
|
|
63
|
+
- No random dark section in light page (or vice versa) — commit to one substrate
|
|
64
|
+
- No orphaned words — use `text-wrap: balance` or `text-wrap: pretty`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Design Principles
|
|
69
|
+
|
|
70
|
+
1. **Intentionality** — Commit to direction. Maximalism and minimalism both work if committed.
|
|
71
|
+
2. **Engineering** — Buttons have structure, physics, a11y. Not just "style."
|
|
72
|
+
3. **Consistency** — One palette, one font system, one architecture.
|
|
73
|
+
4. **Performance** — Beautiful + laggy = not beautiful. Transform-only, guarded backdrop-blur.
|
|
74
|
+
5. **Ship every state** — Default-only is not production.
|
|
75
|
+
6. **Redesign first** — For existing projects, diagnose before prescribing. Improve in-place. A targeted upgrade beats a rewrite.
|