openhermes 1.5.6 → 1.13.1
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/LICENSE +21 -0
- package/README.md +217 -111
- package/autorecall.mjs +2 -12
- package/bootstrap.mjs +160 -8
- package/curator.mjs +1 -5
- package/harness/commands/checkpoint.md +68 -0
- package/harness/commands/eval.md +89 -0
- package/harness/commands/go-build.md +87 -0
- package/harness/commands/go-review.md +71 -0
- package/harness/commands/harness-audit.md +90 -0
- package/harness/commands/learn.md +2 -2
- package/harness/commands/loop-start.md +38 -0
- package/harness/commands/loop-status.md +30 -0
- package/harness/commands/memory-search.md +2 -2
- package/harness/commands/model-route.md +32 -0
- package/harness/commands/ohc.md +13 -0
- package/harness/commands/orchestrate.md +88 -0
- package/harness/commands/quality-gate.md +35 -0
- package/harness/commands/refactor-clean.md +102 -0
- package/harness/commands/rust-build.md +78 -0
- package/harness/commands/rust-review.md +65 -0
- package/harness/commands/setup-pm.md +65 -0
- package/harness/commands/skill-create.md +99 -0
- package/harness/commands/test-coverage.md +80 -0
- package/harness/commands/update-codemaps.md +81 -0
- package/harness/commands/update-docs.md +67 -0
- package/harness/commands/verify.md +68 -0
- package/harness/instructions/CONVENTIONS.md +206 -0
- package/harness/instructions/RUNTIME.md +8 -1
- package/harness/prompts/build-cpp.md +84 -0
- package/harness/prompts/build-error-resolver.md +2 -1
- package/harness/prompts/build-go.md +326 -0
- package/harness/prompts/build-java.md +126 -0
- package/harness/prompts/build-kotlin.md +123 -0
- package/harness/prompts/build-rust.md +94 -0
- package/harness/prompts/code-reviewer.md +2 -1
- package/harness/prompts/doc-updater.md +193 -0
- package/harness/prompts/docs-lookup.md +60 -0
- package/harness/prompts/explore.md +1 -0
- package/harness/prompts/harness-optimizer.md +30 -0
- package/harness/prompts/loop-operator.md +42 -0
- package/harness/prompts/planner.md +3 -2
- package/harness/prompts/refactor-cleaner.md +242 -0
- package/harness/prompts/review-cpp.md +68 -0
- package/harness/prompts/review-database.md +248 -0
- package/harness/prompts/review-go.md +244 -0
- package/harness/prompts/review-java.md +100 -0
- package/harness/prompts/review-kotlin.md +130 -0
- package/harness/prompts/review-python.md +88 -0
- package/harness/prompts/review-rust.md +64 -0
- package/harness/prompts/security-reviewer.md +3 -2
- package/harness/prompts/tdd-guide.md +214 -0
- package/harness/rules/delegation.md +28 -22
- package/harness/rules/memory-management.md +4 -4
- package/harness/rules/retrieval.md +5 -5
- package/harness/rules/runtime-guards.md +1 -1
- package/harness/rules/session-start.md +4 -4
- package/harness/rules/skills-management.md +2 -2
- package/harness/rules/state-drift.md +1 -1
- package/harness/rules/verification.md +4 -4
- package/harness/scripts/sync-commands.mjs +259 -0
- package/harness/skills/coding-standards/SKILL.md +1 -1
- package/index.mjs +25 -4
- package/lib/hardening.mjs +11 -1
- package/lib/memory-tools-plugin.mjs +84 -71
- package/lib/ohc/config.mjs +30 -0
- package/lib/ohc/pruner.mjs +239 -0
- package/lib/ohc/reaper.mjs +61 -0
- package/lib/ohc/state.mjs +32 -0
- package/lib/ohc/updater.mjs +110 -0
- package/package.json +6 -2
- package/skill-builder.mjs +2 -6
|
@@ -130,7 +130,7 @@ The agent can create, update, and delete skills during sessions. This is the ski
|
|
|
130
130
|
|
|
131
131
|
- Never create a skill from a single data point.
|
|
132
132
|
- Minimum: 3 verified successes or 3 same-type mistakes in 7 days.
|
|
133
|
-
- Check existing skills via `
|
|
133
|
+
- Check existing skills via `search_memory` before creating to avoid duplicates.
|
|
134
134
|
|
|
135
135
|
### Skill Quality Gates
|
|
136
136
|
|
|
@@ -162,4 +162,4 @@ Skills live in three locations (discovered by OpenCode):
|
|
|
162
162
|
After creating or updating a skill:
|
|
163
163
|
1. Run the workflow defined in the SKILL.md.
|
|
164
164
|
2. Verify it produces the expected outcome.
|
|
165
|
-
3. Write a verification receipt via `
|
|
165
|
+
3. Write a verification receipt via `add_memory` with class `verification_receipt`.
|
|
@@ -15,7 +15,7 @@ This creates "phantom" compressed data that references stale environments.
|
|
|
15
15
|
{
|
|
16
16
|
"fingerprint": {
|
|
17
17
|
"cwd": "C:/path/to/project",
|
|
18
|
-
"harness_root": "C:/Users/nathan/.config/opencode
|
|
18
|
+
"harness_root": "C:/Users/nathan/.config/opencode",
|
|
19
19
|
"project_root": "C:/path/to/project",
|
|
20
20
|
"project": "my-project",
|
|
21
21
|
"session_id": "session-123",
|
|
@@ -14,7 +14,7 @@ Verification receipts prove that an artifact was observed in a particular state.
|
|
|
14
14
|
|
|
15
15
|
## Verification Cache (Memory-Backed)
|
|
16
16
|
|
|
17
|
-
Successful verifications are stored via `
|
|
17
|
+
Successful verifications are stored via `add_memory` so repeated checks of unchanged artifacts are skipped.
|
|
18
18
|
|
|
19
19
|
### Cache Key
|
|
20
20
|
|
|
@@ -24,14 +24,14 @@ Each verification receipt is keyed by:
|
|
|
24
24
|
|
|
25
25
|
### Cache Lifecycle
|
|
26
26
|
|
|
27
|
-
1. **Before trusting a claim**: search memory (`
|
|
27
|
+
1. **Before trusting a claim**: search memory (`fetch_memory` or `list_memory`) for matching receipt.
|
|
28
28
|
2. **Receipt found + fingerprint matches**: artifact unchanged. Trust cached result. Skip re-verify.
|
|
29
29
|
3. **Receipt found + fingerprint differs**: artifact changed. Re-verify. Stale receipt is invalid.
|
|
30
30
|
4. **No receipt found**: verify fresh. Store receipt on success.
|
|
31
31
|
|
|
32
32
|
### Receipt Storage
|
|
33
33
|
|
|
34
|
-
Use `
|
|
34
|
+
Use `add_memory` with class `verification_receipt` — a dedicated memory class (schema: `schemas\verification_receipt.schema.json`). Receipts are stored as file-per-object in `memory\verification_receipts\<id>.json`.
|
|
35
35
|
|
|
36
36
|
Required fields:
|
|
37
37
|
- **artifact**: path or logical identity of the verified artifact
|
|
@@ -69,7 +69,7 @@ Receipts stored under `decision` with `v:` prefix are deprecated. Migrate to `ve
|
|
|
69
69
|
When verification reveals evidence contradicts a document or user claim:
|
|
70
70
|
|
|
71
71
|
1. **Pause** — do not proceed on either source.
|
|
72
|
-
2. **Log** — `
|
|
72
|
+
2. **Log** — `add_memory` as `constraint` or `backlog` with both claim and contradictory evidence.
|
|
73
73
|
3. **Flag** — ask user about the discrepancy. Present both sides.
|
|
74
74
|
4. **Resolve** — let user decide which source is authoritative. Update document if needed.
|
|
75
75
|
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const REPO_ROOT = path.resolve(__dirname, "..", "..")
|
|
8
|
+
const COMMANDS_DIR = path.join(REPO_ROOT, "harness", "commands")
|
|
9
|
+
const BOOTSTRAP_FILE = path.join(REPO_ROOT, "bootstrap.mjs")
|
|
10
|
+
const README_FILE = path.join(REPO_ROOT, "README.md")
|
|
11
|
+
|
|
12
|
+
const COMMANDS_START = "<!-- COMMANDS:START -->"
|
|
13
|
+
const COMMANDS_END = "<!-- COMMANDS:END -->"
|
|
14
|
+
|
|
15
|
+
const HOOK_ONLY = new Set(["update-me"])
|
|
16
|
+
|
|
17
|
+
function parseRegistry(src) {
|
|
18
|
+
const commands = {}
|
|
19
|
+
const blockMatch = src.match(/config\.command\s*=\s*\{([\s\S]*?)\n\s*\}/)
|
|
20
|
+
if (!blockMatch) return commands
|
|
21
|
+
|
|
22
|
+
let rest = blockMatch[1]
|
|
23
|
+
const entryRe = /^\s*"([^"]+)"\s*:\s*\{([\s\S]*?)\},?\s*$/gm
|
|
24
|
+
let m
|
|
25
|
+
while ((m = entryRe.exec(rest)) !== null) {
|
|
26
|
+
const name = m[1]
|
|
27
|
+
const body = m[2]
|
|
28
|
+
const agent = body.match(/agent:\s*"([^"]+)"/)?.[1] ?? ""
|
|
29
|
+
const description = body.match(/description:\s*"([^"]+)"/)?.[1] ?? ""
|
|
30
|
+
const template = body.match(/template:\s*ct\("([^"]+)"\)/) ? m[0].match(/template:\s*(ct\("[^"]+"\)|""|'')/)?.[1] : ""
|
|
31
|
+
const hasFile = template.includes("ct(")
|
|
32
|
+
commands[name] = { name, agent, description, hasFile }
|
|
33
|
+
}
|
|
34
|
+
return commands
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseCommandFile(filePath) {
|
|
38
|
+
const src = fs.readFileSync(filePath, "utf8")
|
|
39
|
+
const name = path.basename(filePath, ".md")
|
|
40
|
+
const description = src.match(/^description:\s*(.+)$/m)?.[1] ?? ""
|
|
41
|
+
const agent = src.match(/^agent:\s*(.+)$/m)?.[1] ?? ""
|
|
42
|
+
return { name, description, agent }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scanCommandFiles() {
|
|
46
|
+
const files = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith(".md"))
|
|
47
|
+
const map = {}
|
|
48
|
+
for (const f of files) {
|
|
49
|
+
const parsed = parseCommandFile(path.join(COMMANDS_DIR, f))
|
|
50
|
+
map[parsed.name] = parsed
|
|
51
|
+
}
|
|
52
|
+
return map
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getHookCommands() {
|
|
56
|
+
return [...HOOK_ONLY].map(name => ({ name, agent: "", description: "Hook-handled (see plugin source)" }))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printReport(registry, filesOnDisk, hookCommands, cmdNames) {
|
|
60
|
+
console.log("")
|
|
61
|
+
console.log(" COMMAND SYNC REPORT")
|
|
62
|
+
console.log(" " + "=".repeat(50))
|
|
63
|
+
const registered = new Set(Object.keys(registry))
|
|
64
|
+
const onDisk = new Set(Object.keys(filesOnDisk))
|
|
65
|
+
const allHooks = new Set(hookCommands.map(h => h.name))
|
|
66
|
+
const hookNoReg = [...allHooks].filter(n => !registered.has(n) && !onDisk.has(n))
|
|
67
|
+
|
|
68
|
+
console.log(` Registered in bootstrap.mjs: ${registered.size}`)
|
|
69
|
+
console.log(` Files in harness/commands/: ${onDisk.size}`)
|
|
70
|
+
console.log(` Hook-only (no registry): ${hookNoReg.length}`)
|
|
71
|
+
console.log(` Total real commands: ${cmdNames.length}`)
|
|
72
|
+
console.log("")
|
|
73
|
+
|
|
74
|
+
const registeredNoFile = [...registered].filter(n => !onDisk.has(n))
|
|
75
|
+
const fileNotRegistered = [...onDisk].filter(n => !registered.has(n) && !allHooks.has(n))
|
|
76
|
+
|
|
77
|
+
if (registeredNoFile.length) {
|
|
78
|
+
console.log(" ⚠ REGISTERED, NO FILE:")
|
|
79
|
+
for (const n of registeredNoFile) {
|
|
80
|
+
const e = registry[n]
|
|
81
|
+
console.log(` /${n} — ${e.description}`)
|
|
82
|
+
}
|
|
83
|
+
console.log("")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (fileNotRegistered.length) {
|
|
87
|
+
console.log(" ⚠ FILE EXISTS, NOT REGISTERED:")
|
|
88
|
+
for (const n of fileNotRegistered) {
|
|
89
|
+
console.log(` /${n}`)
|
|
90
|
+
}
|
|
91
|
+
console.log("")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hookNoReg.length) {
|
|
95
|
+
console.log(" ℹ HOOK-ONLY (no registry, no file):")
|
|
96
|
+
for (const n of hookNoReg) {
|
|
97
|
+
console.log(` /${n}`)
|
|
98
|
+
}
|
|
99
|
+
console.log("")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!registeredNoFile.length && !fileNotRegistered.length) {
|
|
103
|
+
console.log(" ✓ Everything in sync — no gaps.")
|
|
104
|
+
console.log("")
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function generateMissingFiles(registry, filesOnDisk) {
|
|
109
|
+
let count = 0
|
|
110
|
+
for (const [name, entry] of Object.entries(registry)) {
|
|
111
|
+
if (filesOnDisk[name]) continue
|
|
112
|
+
|
|
113
|
+
const templateFile = `${name}.md`
|
|
114
|
+
const agent = entry.agent || "OpenHermes"
|
|
115
|
+
const desc = entry.description
|
|
116
|
+
|
|
117
|
+
let md = `---
|
|
118
|
+
description: ${desc}
|
|
119
|
+
agent: ${agent}
|
|
120
|
+
subtask: true
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
# ${name.charAt(0).toUpperCase() + name.slice(1)} Command
|
|
124
|
+
|
|
125
|
+
`
|
|
126
|
+
|
|
127
|
+
if (name === "ohc") {
|
|
128
|
+
md += `OHC context management — hook-handled.\n\nRun \`/ohc status\` to check context usage, \`/ohc compress [targetTokens] [focus]\` to free space.\n\n$ARGUMENTS\n`
|
|
129
|
+
} else {
|
|
130
|
+
md += `${desc}: $ARGUMENTS\n`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fs.writeFileSync(path.join(COMMANDS_DIR, templateFile), md, "utf8")
|
|
134
|
+
console.log(` ✓ Created harness/commands/${templateFile}`)
|
|
135
|
+
count++
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (count === 0) console.log(" No missing files to generate.")
|
|
139
|
+
return count
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getCommandNames(registry, hookCommands) {
|
|
143
|
+
const registered = new Set(Object.keys(registry))
|
|
144
|
+
const hookOnly = hookCommands.filter(h => !registered.has(h.name)).map(h => h.name)
|
|
145
|
+
const names = [...registered, ...hookOnly]
|
|
146
|
+
names.sort()
|
|
147
|
+
return names
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatCommandRow(names) {
|
|
151
|
+
const count = names.length
|
|
152
|
+
const list = names.map(n => `\`/${n}\``).join(", ")
|
|
153
|
+
return `| **${count} slash commands** | ${list} |`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function formatBacktickList(names) {
|
|
157
|
+
return ` ${names.map(n => `\\\`/${n}\\\``).join(", ")}`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function syncBootstrap(registry, hookCommands) {
|
|
161
|
+
let src = fs.readFileSync(BOOTSTRAP_FILE, "utf8")
|
|
162
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
163
|
+
const row = formatCommandRow(cmdNames)
|
|
164
|
+
|
|
165
|
+
const startIdx = src.indexOf(COMMANDS_START)
|
|
166
|
+
const endIdx = src.indexOf(COMMANDS_END)
|
|
167
|
+
|
|
168
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
169
|
+
console.log(" ✗ Markers not found in bootstrap.mjs. Inserting markers.")
|
|
170
|
+
const plugRowRe = /(\|\s+\*\*Plugins\s*\*\*.*\|)/
|
|
171
|
+
const match = src.match(plugRowRe)
|
|
172
|
+
if (!match) {
|
|
173
|
+
console.log(" ✗ Could not find Plugins row to anchor insertion.")
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
const insertAfter = match.index + match[0].length
|
|
177
|
+
const newRow = `\n| **Slash commands** | ${COMMANDS_START} ${COMMANDS_END} |`
|
|
178
|
+
src = src.slice(0, insertAfter) + newRow + src.slice(insertAfter)
|
|
179
|
+
// Re-find markers for content replacement below
|
|
180
|
+
const newStart = src.indexOf(COMMANDS_START)
|
|
181
|
+
const newEnd = src.indexOf(COMMANDS_END)
|
|
182
|
+
if (newStart !== -1 && newEnd !== -1 && newEnd > newStart) {
|
|
183
|
+
const content = formatBacktickList(cmdNames)
|
|
184
|
+
src = src.slice(0, newStart + COMMANDS_START.length) + content + src.slice(newEnd)
|
|
185
|
+
}
|
|
186
|
+
fs.writeFileSync(BOOTSTRAP_FILE, src, "utf8")
|
|
187
|
+
console.log(" ✓ Inserted Slash commands row in bootstrap.mjs")
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (endIdx <= startIdx) {
|
|
192
|
+
console.log(" ✗ Markers out of order in bootstrap.mjs")
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const content = formatBacktickList(cmdNames)
|
|
197
|
+
src = src.slice(0, startIdx + COMMANDS_START.length) + content + src.slice(endIdx)
|
|
198
|
+
fs.writeFileSync(BOOTSTRAP_FILE, src, "utf8")
|
|
199
|
+
console.log(" ✓ Updated Slash commands row in bootstrap.mjs")
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function syncReadme(registry, hookCommands) {
|
|
204
|
+
let src = fs.readFileSync(README_FILE, "utf8")
|
|
205
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
206
|
+
const row = formatCommandRow(cmdNames)
|
|
207
|
+
|
|
208
|
+
const tableRowRe = /^\|\s*\*\*(\d+)\s*slash commands\s*\*\*.*\|$/m
|
|
209
|
+
const match = src.match(tableRowRe)
|
|
210
|
+
if (!match) {
|
|
211
|
+
console.log(" ✗ Could not find slash commands row in README.md")
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
src = src.replace(tableRowRe, row)
|
|
216
|
+
fs.writeFileSync(README_FILE, src, "utf8")
|
|
217
|
+
console.log(` ✓ Updated README.md: ${cmdNames.length} commands`)
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Main
|
|
222
|
+
const args = process.argv.slice(2)
|
|
223
|
+
const doAudit = args.includes("--audit") || args.length === 0
|
|
224
|
+
const doGenerate = args.includes("--generate") || args.length === 0
|
|
225
|
+
const doSyncBootstrap = args.includes("--sync-bootstrap") || args.includes("--sync") || args.length === 0
|
|
226
|
+
const doSyncReadme = args.includes("--sync-readme") || args.includes("--sync") || args.length === 0
|
|
227
|
+
|
|
228
|
+
console.log(" ▸ syncing command definitions...")
|
|
229
|
+
console.log("")
|
|
230
|
+
|
|
231
|
+
const bootstrapSrc = fs.readFileSync(BOOTSTRAP_FILE, "utf8")
|
|
232
|
+
const registry = parseRegistry(bootstrapSrc)
|
|
233
|
+
const filesOnDisk = scanCommandFiles()
|
|
234
|
+
const hookCommands = getHookCommands()
|
|
235
|
+
const cmdNames = getCommandNames(registry, hookCommands)
|
|
236
|
+
|
|
237
|
+
if (doAudit) printReport(registry, filesOnDisk, hookCommands, cmdNames)
|
|
238
|
+
|
|
239
|
+
if (doGenerate) {
|
|
240
|
+
console.log(" [generate]")
|
|
241
|
+
generateMissingFiles(registry, filesOnDisk)
|
|
242
|
+
console.log("")
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (doSyncBootstrap) {
|
|
246
|
+
console.log(" [sync-bootstrap]")
|
|
247
|
+
const ok = syncBootstrap(registry, hookCommands)
|
|
248
|
+
if (!ok) process.exitCode = 1
|
|
249
|
+
console.log("")
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (doSyncReadme) {
|
|
253
|
+
console.log(" [sync-readme]")
|
|
254
|
+
const ok = syncReadme(registry, hookCommands)
|
|
255
|
+
if (!ok) process.exitCode = 1
|
|
256
|
+
console.log("")
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log(" done.")
|
|
@@ -34,7 +34,7 @@ Activate this skill for:
|
|
|
34
34
|
Do not use this skill as the primary source for:
|
|
35
35
|
- React composition, hooks, or rendering patterns
|
|
36
36
|
- backend architecture, API design, or database layering
|
|
37
|
-
- domain-specific framework guidance when a narrower
|
|
37
|
+
- domain-specific framework guidance when a narrower OpenHermes skill already exists
|
|
38
38
|
|
|
39
39
|
## Code Quality Principles
|
|
40
40
|
|
package/index.mjs
CHANGED
|
@@ -3,19 +3,40 @@ import { CuratorPlugin } from "./curator.mjs"
|
|
|
3
3
|
import { SkillBuilderPlugin } from "./skill-builder.mjs"
|
|
4
4
|
import { BootstrapPlugin } from "./bootstrap.mjs"
|
|
5
5
|
import { MemoryToolsPlugin } from "./lib/memory-tools-plugin.mjs"
|
|
6
|
+
import { OhcPlugin } from "./lib/ohc/pruner.mjs"
|
|
7
|
+
import { UpdaterPlugin } from "./lib/ohc/updater.mjs"
|
|
8
|
+
|
|
9
|
+
function chain(...fns) {
|
|
10
|
+
const h = fns.filter(Boolean)
|
|
11
|
+
if (!h.length) return undefined
|
|
12
|
+
if (h.length === 1) return h[0]
|
|
13
|
+
return async (i, o) => { for (const fn of h) await fn(i, o) }
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
export default async (input) => {
|
|
8
|
-
const [bootstrap, autorecall, curator, skillBuilder, memoryTools] = await Promise.all([
|
|
17
|
+
const [bootstrap, autorecall, curator, skillBuilder, memoryTools, ohc, updater] = await Promise.all([
|
|
9
18
|
BootstrapPlugin(input),
|
|
10
19
|
AutorecallPlugin(input),
|
|
11
20
|
CuratorPlugin(input),
|
|
12
21
|
SkillBuilderPlugin(input),
|
|
13
22
|
MemoryToolsPlugin(input),
|
|
23
|
+
OhcPlugin(input),
|
|
24
|
+
UpdaterPlugin(input),
|
|
14
25
|
])
|
|
15
26
|
|
|
16
27
|
const merged = {}
|
|
28
|
+
|
|
17
29
|
if (bootstrap.config) merged.config = bootstrap.config
|
|
18
|
-
|
|
30
|
+
|
|
31
|
+
const toolHandlers = { ...memoryTools.tool, ...ohc.tool }
|
|
32
|
+
if (Object.keys(toolHandlers).length) merged.tool = toolHandlers
|
|
33
|
+
|
|
34
|
+
merged["experimental.chat.system.transform"] = chain(ohc["experimental.chat.system.transform"])
|
|
35
|
+
merged["experimental.chat.messages.transform"] = chain(
|
|
36
|
+
bootstrap["experimental.chat.messages.transform"],
|
|
37
|
+
ohc["experimental.chat.messages.transform"],
|
|
38
|
+
)
|
|
39
|
+
merged["command.execute.before"] = chain(updater["command.execute.before"], ohc["command.execute.before"])
|
|
19
40
|
|
|
20
41
|
const eventHandlers = [autorecall.event, curator.event, skillBuilder.event].filter(Boolean)
|
|
21
42
|
if (eventHandlers.length) {
|
|
@@ -24,8 +45,8 @@ export default async (input) => {
|
|
|
24
45
|
}
|
|
25
46
|
}
|
|
26
47
|
|
|
27
|
-
for (const hook of ["experimental.
|
|
28
|
-
const handler =
|
|
48
|
+
for (const hook of ["experimental.session.compacting", "tool.execute.after"]) {
|
|
49
|
+
const handler = chain(curator[hook], skillBuilder[hook])
|
|
29
50
|
if (handler) merged[hook] = handler
|
|
30
51
|
}
|
|
31
52
|
|
package/lib/hardening.mjs
CHANGED
|
@@ -110,4 +110,14 @@ function isTruthy(value) {
|
|
|
110
110
|
return /^(1|true|yes|on)$/i.test(String(value || ""))
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
function readJson(fp, fallback) {
|
|
114
|
+
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readJsonl(fp) {
|
|
118
|
+
try {
|
|
119
|
+
return fs.readFileSync(fp, "utf8").trim().split("\n").filter(Boolean).map(l => JSON.parse(l))
|
|
120
|
+
} catch { return [] }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { atomicWriteJson, fingerprintEnvironment, fingerprintFile, isTruthy, readJson, readJsonl, redactSensitiveText, sanitizeRecord, truncateText }
|