openhermes 1.5.1 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -74
- package/autorecall.mjs +7 -6
- package/curator.mjs +35 -24
- package/lib/memory-tools-plugin.mjs +39 -5
- package/package.json +1 -1
- package/lib/tools/_memory.mjs +0 -230
- package/lib/tools/hm_get.mjs +0 -13
- package/lib/tools/hm_latest.mjs +0 -12
- package/lib/tools/hm_list.mjs +0 -13
- package/lib/tools/hm_put.mjs +0 -14
- package/lib/tools/hm_search.mjs +0 -16
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
**Your OpenCode agent, leveled up.** Add it to your plugins
|
|
15
|
+
**Your OpenCode agent, leveled up.** Add it to your plugins and your agent gains a personality, a memory, a conscience, 7 specialist subagents, 7 slash commands, 5 native memory tools, 10 procedural skills, autonomous checkpointing, and the discipline to self-improve.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm i openhermes
|
|
@@ -29,21 +29,25 @@ npm i openhermes
|
|
|
29
29
|
## What OpenHermes Does For Your Agent
|
|
30
30
|
|
|
31
31
|
<table>
|
|
32
|
-
<tr><td width="160"><b>🤖 Personality Layer</b></td><td>
|
|
32
|
+
<tr><td width="160"><b>🤖 Personality Layer</b></td><td>An 11-principle constitution (<code>soul.md</code>) injected into every session — pragmatic, concise, subagent-first, verify-don't-claim. Your agent stops rambling and starts delivering.</td></tr>
|
|
33
33
|
<tr><td><b>📌 Delegation Discipline</b></td><td>Mandated routing table — every non-trivial task goes to the right specialist subagent. Main context stays clean, coordination-only. No more bloated chat logs.</td></tr>
|
|
34
|
-
<tr><td><b>💾 9-Class Durable Memory</b></td><td>Checkpoints, decisions, constraints, instincts, mistakes, backlog items, audit reports, verification receipts, and session recall — all schema-validated, fingerprint-
|
|
34
|
+
<tr><td><b>💾 9-Class Durable Memory</b></td><td>Checkpoints, decisions, constraints, instincts, mistakes, backlog items, audit reports, verification receipts, and session recall — all schema-validated, fingerprint-aware, persisted to disk.</td></tr>
|
|
35
35
|
<tr><td><b>🧰 Precision-First Retrieval</b></td><td>Gated retrieval with anti-spam controls. <code>hm_latest</code> → <code>hm_search</code> → <code>hm_get</code> → <code>hm_list</code>. No full-index reads unless explicitly scoped. Memory stays lean.</td></tr>
|
|
36
|
-
<tr><td><b>🔥 Autonomous Checkpointing</b></td><td>Pre-compaction snapshots capture mission, current state, next actions,
|
|
36
|
+
<tr><td><b>🔥 Autonomous Checkpointing</b></td><td>Pre-compaction snapshots capture mission, current state, next actions, blockers, and risk notes so compaction never loses the plot.</td></tr>
|
|
37
37
|
<tr><td><b>🔄 Closed Learning Loop</b></td><td>Mistakes are logged with root cause + prevention rule. Complex sessions auto-generate skill-candidate backlogs. Strike tracking escalates repeat failures. The agent gets better — you don't have to teach it twice.</td></tr>
|
|
38
|
-
<tr><td><b>🛠 10 Bundled Procedural Skills</b></td><td>Pre-built skills for API design, backend patterns, coding standards, E2E testing, frontend patterns, security reviews, strategic compaction, TDD workflow, verification loops
|
|
39
|
-
<tr><td><b>🧩 Zero Infrastructure</b></td><td>No Python. No uv. No Docker. No PostgreSQL. No gateway. No cron daemon. Just Node.js
|
|
38
|
+
<tr><td><b>🛠 10 Bundled Procedural Skills</b></td><td>Pre-built skills for API design, backend patterns, coding standards, E2E testing, frontend patterns, frontend slides, security reviews, strategic compaction, TDD workflow, and verification loops. Discovered automatically — use <code>skill</code> to list and load.</td></tr>
|
|
39
|
+
<tr><td><b>🧩 Zero Infrastructure</b></td><td>No Python. No uv. No Docker. No PostgreSQL. No gateway. No cron daemon. Just Node.js and your existing OpenCode runtime.</td></tr>
|
|
40
40
|
</table>
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
44
44
|
## Setup
|
|
45
45
|
|
|
46
|
-
Add one line to your `opencode.json
|
|
46
|
+
Add one line to your `opencode.json`. Two good options:
|
|
47
|
+
|
|
48
|
+
### Published Release
|
|
49
|
+
|
|
50
|
+
Use npm when you want a stable release boundary.
|
|
47
51
|
|
|
48
52
|
```json
|
|
49
53
|
{
|
|
@@ -51,7 +55,17 @@ Add one line to your `opencode.json`:
|
|
|
51
55
|
}
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
### Git-Backed
|
|
59
|
+
|
|
60
|
+
Use GitHub when you want the latest pushed changes immediately.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"plugin": ["openhermes@git+https://github.com/nathwn12/openhermes.git"]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Either way, **no other config needed.** The plugin auto-registers:
|
|
55
69
|
|
|
56
70
|
| What | Details |
|
|
57
71
|
|------|---------|
|
|
@@ -67,17 +81,17 @@ You only need to define primary agents (like `build` or `OpenHermes`) in `openco
|
|
|
67
81
|
<summary><b>What happens on your next session</b></summary>
|
|
68
82
|
|
|
69
83
|
1. **Config hook** — BootstrapPlugin registers auto-config: 7 subagents, 7 commands, 10 skill dirs.
|
|
70
|
-
2. **Chat transform hook** —
|
|
71
|
-
- ★ **Constitution** (soul.md) — 11 immutable principles
|
|
72
|
-
- ★ **Runtime** (RUNTIME.md) — gather → delegate → verify → compress
|
|
73
|
-
- ★ **Router** (AGENTS.md) — delegation table, memory policy, escalation,
|
|
74
|
-
3. **Session created** — AutorecallPlugin builds recall cache from prior session memory
|
|
75
|
-
4. **Tools execute** — SkillBuilderPlugin watches tool calls and subagent spawns; MemoryToolsPlugin provides 5 native tools immediately
|
|
76
|
-
5. **Session idle** — CuratorPlugin snapshots checkpoint + verification receipt
|
|
77
|
-
6. **Session error** — CuratorPlugin logs mistake with root cause + prevention rule
|
|
78
|
-
7. **Compaction** — CuratorPlugin force-writes pre-compaction checkpoint
|
|
79
|
-
|
|
80
|
-
The LLM reads rules on demand via the injected paths. Memory directories auto-create. Everything Just Works
|
|
84
|
+
2. **Chat transform hook** — bootstrap content is injected into the first user message:
|
|
85
|
+
- ★ **Constitution** (`soul.md`) — 11 immutable principles
|
|
86
|
+
- ★ **Runtime** (`RUNTIME.md`) — gather → delegate → verify → compress
|
|
87
|
+
- ★ **Router** (`AGENTS.md`) — delegation table, memory policy, escalation, and rule paths
|
|
88
|
+
3. **Session created** — AutorecallPlugin builds recall cache from prior session memory.
|
|
89
|
+
4. **Tools execute** — SkillBuilderPlugin watches tool calls and subagent spawns; MemoryToolsPlugin provides 5 native tools immediately.
|
|
90
|
+
5. **Session idle** — CuratorPlugin snapshots checkpoint + verification receipt.
|
|
91
|
+
6. **Session error** — CuratorPlugin logs mistake with root cause + prevention rule.
|
|
92
|
+
7. **Compaction** — CuratorPlugin force-writes a pre-compaction checkpoint and injects state into the compaction buffer.
|
|
93
|
+
|
|
94
|
+
The LLM reads rules on demand via the injected paths. Memory directories auto-create. Everything Just Works.
|
|
81
95
|
|
|
82
96
|
</details>
|
|
83
97
|
|
|
@@ -91,13 +105,13 @@ The LLM reads rules on demand via the injected paths. Memory directories auto-cr
|
|
|
91
105
|
| **MemoryToolsPlugin** | — | Registers 5 native tools: `hm_put`, `hm_get`, `hm_list`, `hm_latest`, `hm_search`. Runs in-process — no MCP server needed. |
|
|
92
106
|
| **CuratorPlugin** | `session.idle`, `.compacted`, `.error`, `.compacting`, `permission.replied` | Writes checkpoints, logs mistakes, records audits, injects state into compaction. |
|
|
93
107
|
| **AutorecallPlugin** | `session.created` | Loads memory from disk, builds session recall cache. |
|
|
94
|
-
| **SkillBuilderPlugin** | `session.idle`, `.created`, `tool.execute.after` | Detects complex sessions (8+ tool calls or 2+ subagent spawns)
|
|
108
|
+
| **SkillBuilderPlugin** | `session.idle`, `.created`, `tool.execute.after` | Detects complex sessions (8+ tool calls or 2+ subagent spawns) and creates skill-candidate backlogs. |
|
|
95
109
|
|
|
96
110
|
---
|
|
97
111
|
|
|
98
112
|
## Memory Architecture
|
|
99
113
|
|
|
100
|
-
Nine memory classes, all schema-validated before persistence, stored
|
|
114
|
+
Nine memory classes, all schema-validated before persistence, stored under the standard OpenCode storage roots:
|
|
101
115
|
|
|
102
116
|
| Class | Format | Purpose |
|
|
103
117
|
|-------|--------|---------|
|
|
@@ -107,11 +121,11 @@ Nine memory classes, all schema-validated before persistence, stored at `~/.loca
|
|
|
107
121
|
| `instinct` | JSON | Reusable trigger→action patterns with success/failure tracking |
|
|
108
122
|
| `backlog` | JSON | Evidence-backed self-improvement items with acceptance criteria |
|
|
109
123
|
| `mistake` | JSONL | Failure registry: type, root cause, fix, prevention rule, strike count |
|
|
110
|
-
| `audit` | JSON | Structured quality
|
|
124
|
+
| `audit` | JSON | Structured quality and integrity evaluations with health scores |
|
|
111
125
|
| `verification_receipt` | JSON | Cached verification results keyed by artifact fingerprint |
|
|
112
126
|
| `recall` | JSON | Session-start cache aggregating active state for compaction injection |
|
|
113
127
|
|
|
114
|
-
OpenHermes follows the same storage contract as OpenCode itself
|
|
128
|
+
OpenHermes follows the same storage contract as OpenCode itself:
|
|
115
129
|
|
|
116
130
|
| What | Where |
|
|
117
131
|
|---|---|
|
|
@@ -119,7 +133,7 @@ OpenHermes follows the same storage contract as OpenCode itself — see [OpenCod
|
|
|
119
133
|
| Durable memory + runtime state | `~/.local/share/opencode/openhermes/` |
|
|
120
134
|
| Derived recall cache | `~/.cache/opencode/openhermes/recall/` |
|
|
121
135
|
|
|
122
|
-
**Runtime hardening**:
|
|
136
|
+
**Runtime hardening**: records pass through `sanitizeRecord()` (strips `__proto__`, `constructor`, `prototype`), `redactSensitiveText()` (strips bearer tokens, API keys, passwords), and `truncateText()` before persistence. Schema validation runs before every write.
|
|
123
137
|
|
|
124
138
|
---
|
|
125
139
|
|
|
@@ -159,50 +173,36 @@ permission.replied
|
|
|
159
173
|
|
|
160
174
|
---
|
|
161
175
|
|
|
176
|
+
## Verification Discipline
|
|
177
|
+
|
|
178
|
+
OpenHermes pushes the agent toward one core habit: **verify before claiming success**.
|
|
179
|
+
|
|
180
|
+
- read before editing
|
|
181
|
+
- run commands before announcing success
|
|
182
|
+
- cache verification receipts by artifact fingerprint
|
|
183
|
+
- re-verify when files change
|
|
184
|
+
- flag contradictions instead of smoothing over them
|
|
185
|
+
|
|
186
|
+
That is why verification receipts are a first-class memory type rather than an afterthought.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
162
190
|
## Bundled Harness
|
|
163
191
|
|
|
164
|
-
The full OpenHermes framework ships inside the package —
|
|
192
|
+
The full OpenHermes framework ships inside the package — prompts, rules, commands, constitution, and procedural skills included:
|
|
165
193
|
|
|
166
194
|
```
|
|
167
195
|
harness/
|
|
168
196
|
├── constitution/soul.md # 11 immutable personality principles
|
|
169
|
-
├── instructions/RUNTIME.md
|
|
170
|
-
├── rules/
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
│ ├── credential-exposure.md # Secret redaction and credential exposure guard
|
|
175
|
-
│ ├── self-heal.md # T0→T3 escalation tiers
|
|
176
|
-
│ ├── verification.md # Skeptical evidence protocol
|
|
177
|
-
│ ├── memory-management.md # Dual-target memory + anti-spam
|
|
178
|
-
│ ├── precedence.md # 9-level conflict resolution
|
|
179
|
-
│ ├── checkpointing.md # Compaction snapshot discipline
|
|
180
|
-
│ ├── audit.md # Structured health checks
|
|
181
|
-
│ ├── skills-management.md # Progressive disclosure loading
|
|
182
|
-
│ ├── context-loading.md # Priority chain + size limits
|
|
183
|
-
│ ├── promotion.md # High-signal-only promotion
|
|
184
|
-
│ ├── ranking.md # Metadata-first retrieval
|
|
185
|
-
│ ├── runtime-guards.md # Stale assumption prevention
|
|
186
|
-
│ └── state-drift.md # Hash-based fingerprinting
|
|
187
|
-
├── skills/ (10 directories)
|
|
188
|
-
│ ├── api-design/SKILL.md # REST API patterns (523 lines)
|
|
189
|
-
│ ├── backend-patterns/SKILL.md # Backend architecture (598 lines)
|
|
190
|
-
│ ├── coding-standards/SKILL.md # Baseline conventions (549 lines)
|
|
191
|
-
│ ├── e2e-testing/SKILL.md # Playwright E2E patterns (326 lines)
|
|
192
|
-
│ ├── frontend-patterns/SKILL.md # React/Next.js patterns (642 lines)
|
|
193
|
-
│ ├── frontend-slides/SKILL.md # HTML presentation builder (184 lines)
|
|
194
|
-
│ ├── security-review/SKILL.md # OWASP Top 10 checklist (495 lines)
|
|
195
|
-
│ ├── strategic-compact/SKILL.md # Context compaction strategy (131 lines)
|
|
196
|
-
│ ├── tdd-workflow/SKILL.md # Red-green-refactor discipline (463 lines)
|
|
197
|
-
│ └── verification-loop/SKILL.md # Pre-PR quality gates (126 lines)
|
|
198
|
-
├── prompts/ (7 files)
|
|
199
|
-
│ # Subagent prompt templates: architect, build-error-resolver,
|
|
200
|
-
│ # code-reviewer, e2e-runner, explore, planner, security-reviewer
|
|
201
|
-
└── commands/ (7 files)
|
|
202
|
-
# Slash command templates: build-fix, code-review, doctor,
|
|
203
|
-
# learn, memory-search, plan, security
|
|
197
|
+
├── instructions/RUNTIME.md # Session workflow: gather → delegate → verify
|
|
198
|
+
├── rules/ # retrieval, verification, checkpointing, audit, self-heal, etc.
|
|
199
|
+
├── skills/ # 10 bundled procedural skills
|
|
200
|
+
├── prompts/ # subagent prompt templates
|
|
201
|
+
└── commands/ # slash command templates
|
|
204
202
|
```
|
|
205
203
|
|
|
204
|
+
This matters because OpenHermes is not just a runtime shim. The operational doctrine ships with it.
|
|
205
|
+
|
|
206
206
|
---
|
|
207
207
|
|
|
208
208
|
## Self-Healing Escalation
|
|
@@ -233,17 +233,17 @@ No self-termination. No grandstanding. Narrow, log, recover, improve.
|
|
|
233
233
|
|
|
234
234
|
```
|
|
235
235
|
openhermes/
|
|
236
|
-
├── index.mjs
|
|
237
|
-
├── bootstrap.mjs
|
|
238
|
-
├── autorecall.mjs
|
|
239
|
-
├── curator.mjs
|
|
240
|
-
├── skill-builder.mjs
|
|
236
|
+
├── index.mjs # Re-exports all 5 plugins
|
|
237
|
+
├── bootstrap.mjs # Config hook (agents/commands/skills) + chat.transform
|
|
238
|
+
├── autorecall.mjs # Recall cache builder
|
|
239
|
+
├── curator.mjs # Lifecycle hooks engine
|
|
240
|
+
├── skill-builder.mjs # Complexity detection engine
|
|
241
241
|
├── lib/
|
|
242
242
|
│ ├── memory-tools-plugin.mjs # 5 native memory tools (hm_put/get/list/latest/search)
|
|
243
243
|
│ ├── hardening.mjs # atomicWriteJson, fingerprint, sanitize, redact
|
|
244
244
|
│ └── schema-validator.mjs # Draft-07 subset validator
|
|
245
|
-
├── schemas/
|
|
246
|
-
├── harness/
|
|
245
|
+
├── schemas/ # Memory schemas for validation
|
|
246
|
+
├── harness/ # Full framework bundle
|
|
247
247
|
└── package.json
|
|
248
248
|
```
|
|
249
249
|
|
|
@@ -251,13 +251,6 @@ openhermes/
|
|
|
251
251
|
|
|
252
252
|
---
|
|
253
253
|
|
|
254
|
-
## Dependencies
|
|
255
|
-
|
|
256
|
-
- **Node.js >= 18** — `node:path`, `node:fs`, `node:os`, `node:url`, `node:crypto`
|
|
257
|
-
- **OpenCode** — provides the Bun runtime, plugin loader, hook dispatcher, and `skill` tool
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
254
|
## Why OpenHermes ≠ Hermes Agent
|
|
262
255
|
|
|
263
256
|
| | Hermes Agent | OpenHermes |
|
package/autorecall.mjs
CHANGED
|
@@ -80,16 +80,16 @@ async function loadMemoryAndWriteCache(projectKey, directory) {
|
|
|
80
80
|
const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
|
|
81
81
|
if (!fs.existsSync(SENTINEL)) {
|
|
82
82
|
const oldMemory = path.join(OLD_BASE, "memory")
|
|
83
|
+
const oldCache = path.join(oldMemory, "recall")
|
|
83
84
|
if (fs.existsSync(oldMemory)) {
|
|
84
85
|
fs.cpSync(oldMemory, getMemoryRoot(), { recursive: true })
|
|
86
|
+
if (fs.existsSync(oldCache)) {
|
|
87
|
+
fs.mkdirSync(getRecallRoot(), { recursive: true })
|
|
88
|
+
const files = fs.readdirSync(oldCache).filter(f => f.endsWith(".json"))
|
|
89
|
+
for (const f of files) fs.cpSync(path.join(oldCache, f), path.join(getRecallRoot(), f))
|
|
90
|
+
}
|
|
85
91
|
fs.rmSync(oldMemory, { recursive: true, force: true })
|
|
86
92
|
}
|
|
87
|
-
const oldCache = path.join(OLD_BASE, "memory", "recall")
|
|
88
|
-
if (fs.existsSync(oldCache)) {
|
|
89
|
-
fs.mkdirSync(getRecallRoot(), { recursive: true })
|
|
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))
|
|
92
|
-
}
|
|
93
93
|
const oldRuntime = path.join(OLD_BASE, "runtime")
|
|
94
94
|
if (fs.existsSync(oldRuntime)) {
|
|
95
95
|
fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
|
|
@@ -99,6 +99,7 @@ async function loadMemoryAndWriteCache(projectKey, directory) {
|
|
|
99
99
|
if (fs.existsSync(oldArchive)) {
|
|
100
100
|
fs.rmSync(oldArchive, { recursive: true, force: true })
|
|
101
101
|
}
|
|
102
|
+
fs.mkdirSync(path.dirname(SENTINEL), { recursive: true })
|
|
102
103
|
fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
|
|
103
104
|
}
|
|
104
105
|
|
package/curator.mjs
CHANGED
|
@@ -393,33 +393,44 @@ async function handlePermissionReplied(directory, project, event) {
|
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
export const CuratorPlugin = async ({ project, directory }) => {
|
|
397
|
-
return {
|
|
398
|
-
event: async ({ event }) => {
|
|
399
|
-
const OLD_BASE = path.join(os.homedir(), ".config", "opencode", "openhermes")
|
|
400
|
-
const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
|
|
401
|
-
if (!fs.existsSync(SENTINEL)) {
|
|
402
|
-
const oldMemory = path.join(OLD_BASE, "memory")
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
fs.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
396
|
+
export const CuratorPlugin = async ({ project, directory }) => {
|
|
397
|
+
return {
|
|
398
|
+
event: async ({ event }) => {
|
|
399
|
+
const OLD_BASE = path.join(os.homedir(), ".config", "opencode", "openhermes")
|
|
400
|
+
const SENTINEL = path.join(getDataRoot(), ".migrated-from-v1")
|
|
401
|
+
if (!fs.existsSync(SENTINEL)) {
|
|
402
|
+
const oldMemory = path.join(OLD_BASE, "memory")
|
|
403
|
+
const oldCache = path.join(oldMemory, "recall")
|
|
404
|
+
if (fs.existsSync(oldMemory)) {
|
|
405
|
+
fs.cpSync(oldMemory, getMemoryRoot(), { recursive: true })
|
|
406
|
+
if (fs.existsSync(oldCache)) {
|
|
407
|
+
fs.mkdirSync(getRecallRoot(), { recursive: true })
|
|
408
|
+
const files = fs.readdirSync(oldCache).filter(f => f.endsWith(".json"))
|
|
409
|
+
for (const f of files) fs.cpSync(path.join(oldCache, f), path.join(getRecallRoot(), f))
|
|
410
|
+
}
|
|
411
|
+
fs.rmSync(oldMemory, { recursive: true, force: true })
|
|
412
|
+
}
|
|
413
|
+
const oldRuntime = path.join(OLD_BASE, "runtime")
|
|
414
|
+
if (fs.existsSync(oldRuntime)) {
|
|
415
|
+
fs.cpSync(oldRuntime, getRuntimeRoot(), { recursive: true })
|
|
410
416
|
fs.rmSync(oldRuntime, { recursive: true, force: true })
|
|
411
417
|
}
|
|
412
418
|
const oldArchive = path.join(OLD_BASE, "archive")
|
|
413
|
-
if (fs.existsSync(oldArchive)) {
|
|
414
|
-
fs.rmSync(oldArchive, { recursive: true, force: true })
|
|
415
|
-
}
|
|
416
|
-
fs.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
419
|
+
if (fs.existsSync(oldArchive)) {
|
|
420
|
+
fs.rmSync(oldArchive, { recursive: true, force: true })
|
|
421
|
+
}
|
|
422
|
+
fs.mkdirSync(path.dirname(SENTINEL), { recursive: true })
|
|
423
|
+
fs.writeFileSync(SENTINEL, new Date().toISOString(), "utf8")
|
|
424
|
+
}
|
|
425
|
+
if (event.type === "session.created") {
|
|
426
|
+
lastCheckpoint.ts = 0
|
|
427
|
+
writtenThisSession.length = 0
|
|
428
|
+
}
|
|
429
|
+
if (event.type === "session.idle") {
|
|
430
|
+
await handleSessionIdle(directory, project)
|
|
431
|
+
} else if (event.type === "session.compacted") {
|
|
432
|
+
await handleSessionCompacted(directory, project)
|
|
433
|
+
} else if (event.type === "session.error") {
|
|
423
434
|
await handleSessionError(directory, project, event)
|
|
424
435
|
} else if (event.type === "permission.replied") {
|
|
425
436
|
await handlePermissionReplied(directory, project, event)
|
|
@@ -132,6 +132,18 @@ function enforceAuditEvidence(record) {
|
|
|
132
132
|
return ["db_refs", "file_refs", "log_refs"].some(k => Array.isArray(prov[k]) && prov[k].some(i => typeof i === "string" && i.trim()))
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
function setToolTitle(context, title, metadata = {}) {
|
|
136
|
+
if (!context || typeof context.metadata !== "function") return
|
|
137
|
+
context.metadata({ title, metadata })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function classifyPutTitle(cls, id) {
|
|
141
|
+
const label = String(id || "").trim() || "unnamed"
|
|
142
|
+
if (cls === "checkpoint") return `Save checkpoint: ${label}`
|
|
143
|
+
if (cls === "verification_receipt") return `Save verification receipt: ${label}`
|
|
144
|
+
return `Save ${cls}: ${label}`
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
function handlePut(cls, id, dataStr) {
|
|
136
148
|
let parsed
|
|
137
149
|
try { parsed = JSON.parse(dataStr) } catch (e) { return `data must be valid JSON: ${e.message}` }
|
|
@@ -220,7 +232,10 @@ export const MemoryToolsPlugin = async () => {
|
|
|
220
232
|
id: tool.schema.string(),
|
|
221
233
|
data: tool.schema.string(),
|
|
222
234
|
},
|
|
223
|
-
async execute(args
|
|
235
|
+
async execute(args, context) {
|
|
236
|
+
setToolTitle(context, classifyPutTitle(args.class, args.id), { action: "put", class: args.class, id: args.id })
|
|
237
|
+
return handlePut(args.class, args.id, args.data)
|
|
238
|
+
},
|
|
224
239
|
}),
|
|
225
240
|
|
|
226
241
|
hm_get: tool({
|
|
@@ -229,7 +244,10 @@ export const MemoryToolsPlugin = async () => {
|
|
|
229
244
|
class: tool.schema.enum(CLASSES).describe("Memory class"),
|
|
230
245
|
id: tool.schema.string().describe("Record ID"),
|
|
231
246
|
},
|
|
232
|
-
async execute(args
|
|
247
|
+
async execute(args, context) {
|
|
248
|
+
setToolTitle(context, `Open ${args.class}: ${args.id}`, { action: "get", class: args.class, id: args.id })
|
|
249
|
+
return handleGet(args.class, args.id)
|
|
250
|
+
},
|
|
233
251
|
}),
|
|
234
252
|
|
|
235
253
|
hm_list: tool({
|
|
@@ -238,7 +256,10 @@ export const MemoryToolsPlugin = async () => {
|
|
|
238
256
|
class: tool.schema.enum(CLASSES).describe("Memory class"),
|
|
239
257
|
limit: tool.schema.number().optional().default(10).describe("Max results (max 100)"),
|
|
240
258
|
},
|
|
241
|
-
async execute(args
|
|
259
|
+
async execute(args, context) {
|
|
260
|
+
setToolTitle(context, `List ${args.class} records`, { action: "list", class: args.class, limit: args.limit })
|
|
261
|
+
return handleList(args.class, args.limit)
|
|
262
|
+
},
|
|
242
263
|
}),
|
|
243
264
|
|
|
244
265
|
hm_latest: tool({
|
|
@@ -246,7 +267,10 @@ export const MemoryToolsPlugin = async () => {
|
|
|
246
267
|
args: {
|
|
247
268
|
class: tool.schema.enum(CLASSES).describe("Memory class"),
|
|
248
269
|
},
|
|
249
|
-
async execute(args) {
|
|
270
|
+
async execute(args, context) {
|
|
271
|
+
setToolTitle(context, `Latest ${args.class}`, { action: "latest", class: args.class })
|
|
272
|
+
return handleLatest(args.class)
|
|
273
|
+
},
|
|
250
274
|
}),
|
|
251
275
|
|
|
252
276
|
hm_search: tool({
|
|
@@ -258,8 +282,18 @@ export const MemoryToolsPlugin = async () => {
|
|
|
258
282
|
project: tool.schema.string().optional().describe("Project filter"),
|
|
259
283
|
limit: tool.schema.number().optional().default(10).describe("Max results (max 50)"),
|
|
260
284
|
},
|
|
261
|
-
async execute(args
|
|
285
|
+
async execute(args, context) {
|
|
286
|
+
setToolTitle(context, `Search memory: ${truncateText(args.query, 48)}`, {
|
|
287
|
+
action: "search",
|
|
288
|
+
scope: args.scope,
|
|
289
|
+
classes: args.classes?.length ? args.classes : CLASSES,
|
|
290
|
+
project: args.project ?? null,
|
|
291
|
+
limit: args.limit,
|
|
292
|
+
})
|
|
293
|
+
return handleSearch(args.query, args.scope, args.classes, args.project, args.limit)
|
|
294
|
+
},
|
|
262
295
|
}),
|
|
296
|
+
|
|
263
297
|
},
|
|
264
298
|
}
|
|
265
299
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openhermes",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
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",
|
package/lib/tools/_memory.mjs
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import fs from "fs"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import os from "os"
|
|
4
|
-
import { getMemoryRoot, getDataRoot } from "../../lib/paths.mjs"
|
|
5
|
-
|
|
6
|
-
const MEMORY_DIR = getMemoryRoot()
|
|
7
|
-
|
|
8
|
-
const CLASSES = ["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]
|
|
9
|
-
const PLURALS = {
|
|
10
|
-
audit: "audits", checkpoint: "checkpoints", mistake: "mistakes",
|
|
11
|
-
instinct: "instincts", decision: "decisions", constraint: "constraints",
|
|
12
|
-
backlog: "backlog", verification_receipt: "verification_receipts",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v) }
|
|
16
|
-
|
|
17
|
-
function stableStringify(v, space = 0) {
|
|
18
|
-
function sort(o) {
|
|
19
|
-
if (Array.isArray(o)) return o.map(sort)
|
|
20
|
-
if (!isPlainObject(o)) return o
|
|
21
|
-
const r = {}
|
|
22
|
-
for (const k of Object.keys(o).sort()) r[k] = sort(o[k])
|
|
23
|
-
return r
|
|
24
|
-
}
|
|
25
|
-
return JSON.stringify(sort(v), null, space)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function classDir(cls) { return path.join(MEMORY_DIR, PLURALS[cls]) }
|
|
29
|
-
|
|
30
|
-
function atomicWriteJson(fp, data) {
|
|
31
|
-
const tmp = fp + ".tmp"
|
|
32
|
-
fs.writeFileSync(tmp, stableStringify(data, 2), "utf8")
|
|
33
|
-
fs.renameSync(tmp, fp)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function readJSON(fp, fallback) {
|
|
37
|
-
try { return JSON.parse(fs.readFileSync(fp, "utf8")) } catch { return fallback }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function readJSONL(fp) {
|
|
41
|
-
try {
|
|
42
|
-
return fs.readFileSync(fp, "utf8").split(/\r?\n/).map(l => l.trim()).filter(Boolean).map(l => JSON.parse(l))
|
|
43
|
-
} catch { return [] }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function buildEntry(cls, r) {
|
|
47
|
-
const e = {
|
|
48
|
-
id: r.id, summary: r.summary, status: r.status,
|
|
49
|
-
updated_at: r.updated_at ?? r.created_at,
|
|
50
|
-
path: path.join("openhermes", "memory", PLURALS[cls], `${r.id}.json`),
|
|
51
|
-
scope: r.scope ?? null, project: r.project ?? null,
|
|
52
|
-
}
|
|
53
|
-
if (cls === "audit") { e.target = r.target; e.overall_score = r.overall_score }
|
|
54
|
-
if (cls === "backlog") { e.priority = r.priority; e.trigger = r.trigger }
|
|
55
|
-
return e
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function hasExpired(r) {
|
|
59
|
-
if (r?.status === "expired" || r?.status === "decayed") return true
|
|
60
|
-
if (r?.decay_at && Date.parse(r.decay_at) < Date.now()) return true
|
|
61
|
-
if (r?.expires_at && Date.parse(r.expires_at) < Date.now()) return true
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function sortRecent(entries) {
|
|
66
|
-
function ts(e) { return e?.updated_at ?? e?.created_at ?? "" }
|
|
67
|
-
return [...entries].sort((a, b) => {
|
|
68
|
-
const at = Date.parse(ts(a)), bt = Date.parse(ts(b))
|
|
69
|
-
if (!Number.isNaN(at) && !Number.isNaN(bt) && at !== bt) return bt - at
|
|
70
|
-
return String(ts(b)).localeCompare(String(ts(a)))
|
|
71
|
-
})
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function filterActive(entries) { return entries.filter(e => !hasExpired(e)) }
|
|
75
|
-
|
|
76
|
-
function validateSchemaSimple(record) {
|
|
77
|
-
const required = {
|
|
78
|
-
audit: ["id", "class", "summary", "target", "overall_score", "checks"],
|
|
79
|
-
checkpoint: ["id", "class", "summary", "mission", "current_state", "provenance"],
|
|
80
|
-
mistake: ["id", "class", "summary", "failure", "root_cause", "type", "strike"],
|
|
81
|
-
instinct: ["id", "class", "summary", "claim", "provenance"],
|
|
82
|
-
decision: ["id", "class", "summary", "decision", "rationale", "provenance"],
|
|
83
|
-
constraint: ["id", "class", "summary", "constraint", "provenance"],
|
|
84
|
-
backlog: ["id", "class", "summary", "priority"],
|
|
85
|
-
verification_receipt: ["id", "class", "summary", "fingerprint", "method", "result"],
|
|
86
|
-
}
|
|
87
|
-
const cls = record.class
|
|
88
|
-
const reqs = required[cls]
|
|
89
|
-
if (!reqs) return { ok: true }
|
|
90
|
-
const missing = reqs.filter(r => !record[r] && record[r] !== null && record[r] !== undefined)
|
|
91
|
-
if (missing.length) return { ok: false, errors: missing.map(m => `$.${m} is required`) }
|
|
92
|
-
return { ok: true }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function queryList(cls, limit = 10) {
|
|
96
|
-
if (cls === "mistake") {
|
|
97
|
-
return sortRecent(filterActive(readJSONL(path.join(classDir(cls), "mistakes.jsonl")))).slice(0, limit)
|
|
98
|
-
}
|
|
99
|
-
const dir = classDir(cls)
|
|
100
|
-
let files = []
|
|
101
|
-
try { files = fs.readdirSync(dir).filter(f => f.endsWith(".json") && f !== "index.json").map(f => path.join(dir, f)) } catch { return [] }
|
|
102
|
-
const entries = files.map(f => readJSON(f, null)).filter(Boolean).map(r => buildEntry(cls, r))
|
|
103
|
-
return sortRecent(filterActive(entries)).slice(0, limit)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function queryGet(cls, id) {
|
|
107
|
-
if (cls === "mistake") return readJSONL(path.join(classDir(cls), "mistakes.jsonl")).find(e => e?.id === id) ?? null
|
|
108
|
-
return readJSON(path.join(classDir(cls), `${id}.json`), null)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function scoreRelevance(r, query, project) {
|
|
112
|
-
const q = query.toLowerCase()
|
|
113
|
-
let score = 0
|
|
114
|
-
const fields = [r.summary, r.id, r.description, r.mission, r.current_state, r.failure, r.root_cause, r.fix, r.prevention, r.command, r.project, r.scope, ...(Array.isArray(r.tags) ? r.tags : []), ...(Array.isArray(r.next_actions) ? r.next_actions : []), ...(Array.isArray(r.refs) ? r.refs : [])].filter(Boolean)
|
|
115
|
-
for (const f of fields) {
|
|
116
|
-
const str = String(f).toLowerCase()
|
|
117
|
-
let idx = 0; let count = 0
|
|
118
|
-
while ((idx = str.indexOf(q, idx)) !== -1) { count++; idx += q.length }
|
|
119
|
-
score += count * 10
|
|
120
|
-
if (str.startsWith(q)) score += 5
|
|
121
|
-
if (str.includes(q)) score += 2
|
|
122
|
-
}
|
|
123
|
-
if (r.project && r.project.toLowerCase() === (project || "").toLowerCase()) score += 20
|
|
124
|
-
if (r.project && project && r.project.toLowerCase().includes(project.toLowerCase())) score += 10
|
|
125
|
-
const age = Date.now() - Date.parse(r.updated_at || r.created_at || 0)
|
|
126
|
-
if (!Number.isNaN(age)) score += Math.max(0, 10 - age / 604800000)
|
|
127
|
-
if (r.status === "active") score += 3
|
|
128
|
-
if (r.status === "closed") score -= 2
|
|
129
|
-
return score
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function writeObject(cls, record) {
|
|
133
|
-
const dir = classDir(cls)
|
|
134
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
135
|
-
const fp = path.join(dir, `${record.id}.json`)
|
|
136
|
-
atomicWriteJson(fp, record)
|
|
137
|
-
const indexPath = path.join(dir, "index.json")
|
|
138
|
-
let index = readJSON(indexPath, [])
|
|
139
|
-
if (!Array.isArray(index)) index = []
|
|
140
|
-
const idx = index.findIndex(e => e?.id === record.id)
|
|
141
|
-
const entry = buildEntry(cls, record)
|
|
142
|
-
if (idx >= 0) index[idx] = entry; else index.push(entry)
|
|
143
|
-
atomicWriteJson(indexPath, index)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function upsertMistake(record) {
|
|
147
|
-
const dir = classDir("mistake")
|
|
148
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
149
|
-
const fp = path.join(dir, "mistakes.jsonl")
|
|
150
|
-
let entries = readJSONL(fp)
|
|
151
|
-
const idx = entries.findIndex(e => e?.id === record.id)
|
|
152
|
-
if (idx >= 0) entries[idx] = record; else entries.push(record)
|
|
153
|
-
const text = entries.map(e => stableStringify(e)).join("\n")
|
|
154
|
-
fs.writeFileSync(fp, text ? `${text}\n` : "", "utf8")
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export function handlePut(cls, id, dataStr) {
|
|
158
|
-
let parsed
|
|
159
|
-
try { parsed = JSON.parse(dataStr) } catch (e) { return `data must be valid JSON: ${e.message}` }
|
|
160
|
-
if (!isPlainObject(parsed)) return "data must be a JSON object"
|
|
161
|
-
if (!id?.trim()) return "non-blank id is required"
|
|
162
|
-
|
|
163
|
-
const now = new Date().toISOString()
|
|
164
|
-
const record = {
|
|
165
|
-
...parsed, id, class: cls,
|
|
166
|
-
source: parsed.source ?? "agent",
|
|
167
|
-
status: parsed.status ?? "active",
|
|
168
|
-
created_at: parsed.created_at ?? now,
|
|
169
|
-
updated_at: now,
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const validation = validateSchemaSimple(record)
|
|
173
|
-
if (!validation.ok) return `Validation errors: ${validation.errors.join("; ")}`
|
|
174
|
-
|
|
175
|
-
if (cls === "mistake") upsertMistake(record)
|
|
176
|
-
else writeObject(cls, record)
|
|
177
|
-
|
|
178
|
-
return stableStringify({ ok: true, id })
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function handleGet(cls, id) {
|
|
182
|
-
if (!id?.trim()) return "non-blank id is required"
|
|
183
|
-
const record = queryGet(cls, id.trim())
|
|
184
|
-
if (!record) return stableStringify({ ok: false, found: false })
|
|
185
|
-
return stableStringify({ ok: true, record })
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function handleList(cls, limit = 10) {
|
|
189
|
-
const entries = queryList(cls, Math.min(limit, 100))
|
|
190
|
-
return stableStringify({ ok: true, count: entries.length, entries })
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function handleLatest(cls) {
|
|
194
|
-
const list = queryList(cls, 100)
|
|
195
|
-
const active = filterActive(list)
|
|
196
|
-
if (!active[0]?.id) return stableStringify({ ok: false, found: false })
|
|
197
|
-
const record = queryGet(cls, active[0].id)
|
|
198
|
-
if (!record) return stableStringify({ ok: false, found: false })
|
|
199
|
-
return stableStringify({ ok: true, record })
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function handleSearch(query, scope, classes, project, limit) {
|
|
203
|
-
const q = (query || "").trim()
|
|
204
|
-
if (!q) return "non-blank query is required"
|
|
205
|
-
const clsList = Array.isArray(classes) && classes.length ? classes : CLASSES
|
|
206
|
-
const lim = Math.min(limit ?? 10, 50)
|
|
207
|
-
let records = []
|
|
208
|
-
for (const cls of clsList) {
|
|
209
|
-
if (cls === "mistake") {
|
|
210
|
-
for (const m of readJSONL(path.join(classDir(cls), "mistakes.jsonl"))) {
|
|
211
|
-
if (!hasExpired(m)) records.push(m)
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
const dir = classDir(cls)
|
|
215
|
-
let files = []
|
|
216
|
-
try { files = fs.readdirSync(dir).filter(f => f.endsWith(".json") && f !== "index.json") } catch { continue }
|
|
217
|
-
for (const f of files) {
|
|
218
|
-
const r = readJSON(path.join(dir, f), null)
|
|
219
|
-
if (r && !hasExpired(r)) records.push(r)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (scope === "global") records = records.filter(r => r.scope === "global" || !r.scope)
|
|
224
|
-
else if (scope === "local") records = records.filter(r => r.scope === "project" || r.scope === "session")
|
|
225
|
-
const scored = records.map(r => ({ ...buildEntry(r.class || "verification_receipt", r), score: scoreRelevance(r, q, project || "") }))
|
|
226
|
-
.filter(e => e.score > 0)
|
|
227
|
-
.sort((a, b) => b.score - a.score)
|
|
228
|
-
.slice(0, lim)
|
|
229
|
-
return stableStringify({ ok: true, count: scored.length, query: q, results: scored })
|
|
230
|
-
}
|
package/lib/tools/hm_get.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { handleGet } from "./_memory.mjs"
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: "Get a specific OpenHermes memory record by class and ID",
|
|
6
|
-
args: {
|
|
7
|
-
class: tool.schema.enum(["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]).describe("Memory class"),
|
|
8
|
-
id: tool.schema.string().describe("Record ID to retrieve"),
|
|
9
|
-
},
|
|
10
|
-
async execute(args) {
|
|
11
|
-
return handleGet(args.class, args.id)
|
|
12
|
-
},
|
|
13
|
-
})
|
package/lib/tools/hm_latest.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { handleLatest } from "./_memory.mjs"
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: "Get the latest active OpenHermes memory record by class (returns the full record, not just metadata)",
|
|
6
|
-
args: {
|
|
7
|
-
class: tool.schema.enum(["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]).describe("Memory class to get the latest from"),
|
|
8
|
-
},
|
|
9
|
-
async execute(args) {
|
|
10
|
-
return handleLatest(args.class)
|
|
11
|
-
},
|
|
12
|
-
})
|
package/lib/tools/hm_list.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { handleList } from "./_memory.mjs"
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: "List OpenHermes memory records by class, sorted by recency (newest first)",
|
|
6
|
-
args: {
|
|
7
|
-
class: tool.schema.enum(["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]).describe("Memory class to list"),
|
|
8
|
-
limit: tool.schema.number().optional().default(10).describe("Max results (max 100)"),
|
|
9
|
-
},
|
|
10
|
-
async execute(args) {
|
|
11
|
-
return handleList(args.class, args.limit)
|
|
12
|
-
},
|
|
13
|
-
})
|
package/lib/tools/hm_put.mjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { handlePut } from "./_memory.mjs"
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: "Create or update an OpenHermes memory record (checkpoints, mistakes, decisions, constraints, instincts, audits, backlog items, verification receipts)",
|
|
6
|
-
args: {
|
|
7
|
-
class: tool.schema.enum(["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]).describe("Memory class to write to"),
|
|
8
|
-
id: tool.schema.string().describe("Unique record ID (e.g. 'chk_2026-01-01T00-00-00-000Z')"),
|
|
9
|
-
data: tool.schema.string().describe("JSON string of the record fields (summary, scope, provenance, etc.)"),
|
|
10
|
-
},
|
|
11
|
-
async execute(args) {
|
|
12
|
-
return handlePut(args.class, args.id, args.data)
|
|
13
|
-
},
|
|
14
|
-
})
|
package/lib/tools/hm_search.mjs
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin"
|
|
2
|
-
import { handleSearch } from "./_memory.mjs"
|
|
3
|
-
|
|
4
|
-
export default tool({
|
|
5
|
-
description: "Search OpenHermes memory records with keyword matching and relevance ranking. Searches across all classes by default.",
|
|
6
|
-
args: {
|
|
7
|
-
query: tool.schema.string().describe("Search query string (matches against summary, id, description, tags, etc.)"),
|
|
8
|
-
scope: tool.schema.enum(["global", "local", "auto"]).optional().default("auto").describe("Search scope: global (only global-scope records), local (only project/session-scope), auto (all)"),
|
|
9
|
-
classes: tool.schema.array(tool.schema.enum(["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"])).optional().describe("Memory classes to search (default: all)"),
|
|
10
|
-
project: tool.schema.string().optional().describe("Project name filter for boosted relevance scoring"),
|
|
11
|
-
limit: tool.schema.number().optional().default(10).describe("Max results (max 50)"),
|
|
12
|
-
},
|
|
13
|
-
async execute(args) {
|
|
14
|
-
return handleSearch(args.query, args.scope, args.classes, args.project, args.limit)
|
|
15
|
-
},
|
|
16
|
-
})
|