openhermes 1.3.0 → 1.4.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/autorecall.mjs +15 -9
- package/bootstrap.mjs +18 -31
- package/curator.mjs +14 -11
- package/lib/memory-tools-plugin.mjs +3 -3
- package/lib/paths.mjs +5 -4
- package/package.json +1 -1
package/autorecall.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import fs from "node:fs"
|
|
|
4
4
|
import { atomicWriteJson, fingerprintEnvironment, isTruthy, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
|
|
5
5
|
import { getDataRoot, getCacheRoot, getMemoryRoot, getRecallRoot, getRuntimeRoot } from "./lib/paths.mjs"
|
|
6
6
|
|
|
7
|
+
const OLD_BASE = path.join(os.homedir(), ".config", "opencode", "openhermes")
|
|
8
|
+
|
|
7
9
|
function readJson(fp, fallback) {
|
|
8
10
|
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
|
|
9
11
|
}
|
|
@@ -75,24 +77,28 @@ function formatMemoryWriteGap(memory) {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
async function loadMemoryAndWriteCache(projectKey, directory) {
|
|
78
|
-
const OLD_MEMORY = path.join(os.homedir(), ".config", "opencode", "openhermes", "memory")
|
|
79
|
-
const OLD_CACHE = path.join(os.homedir(), ".config", "opencode", "openhermes", "memory", "recall")
|
|
80
80
|
const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
|
|
81
81
|
if (!fs.existsSync(SENTINEL)) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fs.
|
|
82
|
+
const oldMemory = path.join(OLD_BASE, "memory")
|
|
83
|
+
if (fs.existsSync(oldMemory)) {
|
|
84
|
+
fs.cpSync(oldMemory, getMemoryRoot(), { recursive: true })
|
|
85
|
+
fs.rmSync(oldMemory, { recursive: true, force: true })
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
+
const oldCache = path.join(OLD_BASE, "memory", "recall")
|
|
88
|
+
if (fs.existsSync(oldCache)) {
|
|
87
89
|
fs.mkdirSync(getRecallRoot(), { recursive: true })
|
|
88
|
-
const files = fs.readdirSync(
|
|
89
|
-
for (const f of files) fs.cpSync(path.join(
|
|
90
|
+
const files = fs.readdirSync(oldCache).filter(f => f.endsWith(".json"))
|
|
91
|
+
for (const f of files) fs.cpSync(path.join(oldCache, f), path.join(getRecallRoot(), f))
|
|
90
92
|
}
|
|
91
|
-
const oldRuntime = path.join(
|
|
93
|
+
const oldRuntime = path.join(OLD_BASE, "runtime")
|
|
92
94
|
if (fs.existsSync(oldRuntime)) {
|
|
93
95
|
fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
|
|
94
96
|
fs.rmSync(oldRuntime, { recursive: true, force: true })
|
|
95
97
|
}
|
|
98
|
+
const oldArchive = path.join(OLD_BASE, "archive")
|
|
99
|
+
if (fs.existsSync(oldArchive)) {
|
|
100
|
+
fs.rmSync(oldArchive, { recursive: true, force: true })
|
|
101
|
+
}
|
|
96
102
|
fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
|
|
97
103
|
}
|
|
98
104
|
|
package/bootstrap.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
2
|
import fs from "node:fs"
|
|
3
|
-
import os from "node:os"
|
|
4
3
|
import { fileURLToPath } from "node:url"
|
|
5
4
|
|
|
6
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -9,8 +8,7 @@ const RULES_DIR = path.join(HARNESS_DIR, "rules")
|
|
|
9
8
|
const SKILLS_DIR = path.join(HARNESS_DIR, "skills")
|
|
10
9
|
const CONSTITUTION_FILE = path.join(HARNESS_DIR, "constitution", "soul.md")
|
|
11
10
|
const RUNTIME_FILE = path.join(HARNESS_DIR, "instructions", "RUNTIME.md")
|
|
12
|
-
|
|
13
|
-
const USER_TOOLS_DIR = path.join(os.homedir(), ".config", "opencode", "tools")
|
|
11
|
+
|
|
14
12
|
|
|
15
13
|
let _bootstrapCache = undefined
|
|
16
14
|
|
|
@@ -116,35 +114,7 @@ function getOwnVersion() {
|
|
|
116
114
|
} catch { return "1.0.0" }
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
function installToolFiles() {
|
|
120
|
-
try {
|
|
121
|
-
if (!fs.existsSync(TOOLS_SOURCE_DIR)) return
|
|
122
|
-
const files = fs.readdirSync(TOOLS_SOURCE_DIR).filter(f => f.endsWith(".mjs") && f !== "_memory.mjs")
|
|
123
|
-
if (!files.length) return
|
|
124
|
-
fs.mkdirSync(USER_TOOLS_DIR, { recursive: true })
|
|
125
|
-
const pkgVersion = getOwnVersion()
|
|
126
|
-
const markerPath = path.join(USER_TOOLS_DIR, ".openhermes-version")
|
|
127
|
-
let installedVersion = ""
|
|
128
|
-
try { installedVersion = fs.readFileSync(markerPath, "utf8").trim() } catch {}
|
|
129
|
-
if (installedVersion === pkgVersion) {
|
|
130
|
-
const existing = fs.readdirSync(USER_TOOLS_DIR).filter(f => f.endsWith(".mjs"))
|
|
131
|
-
const needed = [...files, "_memory.mjs"]
|
|
132
|
-
if (needed.every(f => existing.includes(f))) return
|
|
133
|
-
}
|
|
134
|
-
for (const f of ["_memory.mjs", ...files]) {
|
|
135
|
-
const src = path.join(TOOLS_SOURCE_DIR, f)
|
|
136
|
-
const dst = path.join(USER_TOOLS_DIR, f)
|
|
137
|
-
if (fs.existsSync(src)) fs.copyFileSync(src, dst)
|
|
138
|
-
}
|
|
139
|
-
fs.writeFileSync(markerPath, pkgVersion, "utf8")
|
|
140
|
-
process.stderr.write(`[openhermes-bootstrap] installed ${files.length + 1} tool files (v${pkgVersion})\n`)
|
|
141
|
-
} catch (err) {
|
|
142
|
-
process.stderr.write(`[openhermes-bootstrap] tool install error: ${err.message}\n`)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
117
|
export const BootstrapPlugin = async ({ client, directory }) => {
|
|
147
|
-
installToolFiles()
|
|
148
118
|
|
|
149
119
|
const getContent = () => {
|
|
150
120
|
if (_bootstrapCache !== undefined) return _bootstrapCache
|
|
@@ -220,6 +190,23 @@ export const BootstrapPlugin = async ({ client, directory }) => {
|
|
|
220
190
|
if (!config.agent[name]) config.agent[name] = def
|
|
221
191
|
}
|
|
222
192
|
|
|
193
|
+
if (!config.agent["OpenHermes"]) {
|
|
194
|
+
config.agent["OpenHermes"] = {
|
|
195
|
+
description: "Fully autonomous primary coding agent (all tools allowed)",
|
|
196
|
+
mode: "primary",
|
|
197
|
+
color: "#F59E0B",
|
|
198
|
+
permission: {
|
|
199
|
+
bash: { "*": "allow" },
|
|
200
|
+
edit: "allow",
|
|
201
|
+
read: "allow",
|
|
202
|
+
task: { "*": "allow" }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!config.default_agent) {
|
|
207
|
+
config.default_agent = "OpenHermes"
|
|
208
|
+
}
|
|
209
|
+
|
|
223
210
|
config.command = config.command || {}
|
|
224
211
|
const COMMANDS_DIR = path.join(HARNESS_DIR, "commands")
|
|
225
212
|
const ct = (file) => `{file:${path.join(COMMANDS_DIR, file)}}\n\n$ARGUMENTS`
|
package/curator.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { findUnsupportedSchemaKeywords, validateSchema } from "./lib/schema-vali
|
|
|
5
5
|
import { atomicWriteJson, fingerprintEnvironment, fingerprintFile, isTruthy, redactSensitiveText, sanitizeRecord, truncateText } from "./lib/hardening.mjs"
|
|
6
6
|
import { fileURLToPath } from "node:url"
|
|
7
7
|
import { dirname } from "node:path"
|
|
8
|
-
import {
|
|
8
|
+
import { getDataRoot, getMemoryRoot, getRuntimeRoot, getArchiveRoot } from "./lib/paths.mjs"
|
|
9
9
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
11
11
|
|
|
@@ -78,10 +78,8 @@ function updateLoopState(root, patch) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function loadSchema(classId) {
|
|
81
|
-
const fp = path.join(
|
|
82
|
-
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch {}
|
|
83
|
-
const bundled = path.join(__dirname, "schemas", `${classId}.schema.json`)
|
|
84
|
-
try { return JSON.parse(fs.readFileSync(bundled, "utf8")) } catch { return null }
|
|
81
|
+
const fp = path.join(__dirname, "schemas", `${classId}.schema.json`)
|
|
82
|
+
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return null }
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
function validateRecordAgainstSchema(record) {
|
|
@@ -377,7 +375,7 @@ async function handlePermissionReplied(directory, project, event) {
|
|
|
377
375
|
environment_fingerprint: environmentFingerprint,
|
|
378
376
|
}
|
|
379
377
|
const safeRecord = sanitizeRecord(record, { maxStringLength: 4000 })
|
|
380
|
-
const auditSchema = loadSchema("audit")
|
|
378
|
+
const auditSchema = loadSchema("audit")
|
|
381
379
|
if (auditSchema) {
|
|
382
380
|
const unsupported = findUnsupportedSchemaKeywords(auditSchema)
|
|
383
381
|
if (!unsupported.length) {
|
|
@@ -398,18 +396,23 @@ async function handlePermissionReplied(directory, project, event) {
|
|
|
398
396
|
export const CuratorPlugin = async ({ project, directory }) => {
|
|
399
397
|
return {
|
|
400
398
|
event: async ({ event }) => {
|
|
401
|
-
const
|
|
399
|
+
const OLD_BASE = path.join(os.homedir(), ".config", "opencode", "openhermes")
|
|
402
400
|
const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
|
|
403
401
|
if (!fs.existsSync(SENTINEL)) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
fs.
|
|
402
|
+
const oldMemory = path.join(OLD_BASE, "memory")
|
|
403
|
+
if (fs.existsSync(oldMemory)) {
|
|
404
|
+
fs.cpSync(oldMemory, getMemoryRoot(), { recursive: true })
|
|
405
|
+
fs.rmSync(oldMemory, { recursive: true, force: true })
|
|
407
406
|
}
|
|
408
|
-
const oldRuntime = path.join(
|
|
407
|
+
const oldRuntime = path.join(OLD_BASE, "runtime")
|
|
409
408
|
if (fs.existsSync(oldRuntime)) {
|
|
410
409
|
fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
|
|
411
410
|
fs.rmSync(oldRuntime, { recursive: true, force: true })
|
|
412
411
|
}
|
|
412
|
+
const oldArchive = path.join(OLD_BASE, "archive")
|
|
413
|
+
if (fs.existsSync(oldArchive)) {
|
|
414
|
+
fs.rmSync(oldArchive, { recursive: true, force: true })
|
|
415
|
+
}
|
|
413
416
|
fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
|
|
414
417
|
}
|
|
415
418
|
if (event.type === "session.idle") {
|
|
@@ -6,10 +6,10 @@ import os from "os"
|
|
|
6
6
|
|
|
7
7
|
import { atomicWriteJson, fingerprintEnvironment, sanitizeRecord, truncateText } from "./hardening.mjs"
|
|
8
8
|
import { findUnsupportedSchemaKeywords, validateSchema } from "./schema-validator.mjs"
|
|
9
|
-
import { getMemoryRoot,
|
|
9
|
+
import { getMemoryRoot, getRuntimeRoot } from "../lib/paths.mjs"
|
|
10
10
|
|
|
11
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
-
const
|
|
12
|
+
const SCHEMAS_DIR = path.resolve(__dirname, "..", "schemas")
|
|
13
13
|
const MEMORY_DIR = getMemoryRoot()
|
|
14
14
|
|
|
15
15
|
const CLASSES = ["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]
|
|
@@ -141,7 +141,7 @@ function handlePut(cls, id, dataStr) {
|
|
|
141
141
|
const now = new Date().toISOString()
|
|
142
142
|
const record = { ...parsed, id, class: cls, source: parsed.source ?? "agent", status: parsed.status ?? "active", created_at: parsed.created_at ?? now, updated_at: now }
|
|
143
143
|
|
|
144
|
-
const schema = readJSON(path.join(
|
|
144
|
+
const schema = readJSON(path.join(SCHEMAS_DIR, `${cls}.schema.json`), null)
|
|
145
145
|
if (schema) {
|
|
146
146
|
const unsupported = findUnsupportedSchemaKeywords(schema)
|
|
147
147
|
if (unsupported.length) return `Unsupported schema keywords: ${unsupported.join(", ")}`
|
package/lib/paths.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
2
|
import os from "node:os"
|
|
3
3
|
import fs from "node:fs"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
4
5
|
|
|
5
6
|
const HOME = process.env.USERPROFILE || os.homedir()
|
|
6
|
-
const CONFIG_ROOT = path.join(HOME, ".config", "opencode", "openhermes")
|
|
7
7
|
const DATA_ROOT = path.join(HOME, ".local", "share", "opencode", "openhermes")
|
|
8
8
|
const CACHE_ROOT = path.join(HOME, ".cache", "opencode", "openhermes")
|
|
9
|
+
const PKG_DIR = path.dirname(fileURLToPath(import.meta.url))
|
|
9
10
|
|
|
10
11
|
function resolveRoot(envVar, fallback) {
|
|
11
12
|
if (!isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) return fallback
|
|
@@ -16,7 +17,7 @@ function resolveRoot(envVar, fallback) {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function getConfigRoot() {
|
|
19
|
-
return
|
|
20
|
+
return null // legacy — no longer used
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function getDataRoot() {
|
|
@@ -40,11 +41,11 @@ export function getRecallRoot() {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export function getArchiveRoot() {
|
|
43
|
-
return path.join(
|
|
44
|
+
return path.join(getDataRoot(), "archive")
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function getSchemaRoot() {
|
|
47
|
-
return path.
|
|
48
|
+
return path.resolve(PKG_DIR, "..", "schemas")
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function isTruthy(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openhermes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "OpenHermes plugin suite for OpenCode — autonomous checkpointing, native memory tools, subagent routing, slash commands, and skill-candidate detection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|