openhermes 1.13.1 → 2.5.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/README.md +123 -208
- package/autorecall.mjs +79 -12
- package/bootstrap.mjs +122 -25
- package/curator.mjs +4 -40
- package/harness/commands/harness-audit.md +1 -1
- package/harness/commands/learn.md +2 -2
- package/harness/commands/memory-search.md +2 -2
- package/harness/constitution/soul.md +16 -4
- package/harness/instructions/RUNTIME.md +6 -3
- package/harness/prompts/architect.txt +14 -0
- package/harness/prompts/build-cpp.md +15 -1
- package/harness/prompts/build-error-resolver.md +15 -9
- package/harness/prompts/build-go.md +14 -0
- package/harness/prompts/build-java.md +15 -1
- package/harness/prompts/build-kotlin.md +15 -1
- package/harness/prompts/build-rust.md +14 -0
- package/harness/prompts/code-reviewer.md +15 -9
- package/harness/prompts/doc-updater.md +13 -0
- package/harness/prompts/docs-lookup.md +11 -0
- package/harness/prompts/e2e-runner.txt +12 -0
- package/harness/prompts/explore.md +16 -4
- package/harness/prompts/harness-optimizer.md +12 -0
- package/harness/prompts/loop-operator.md +11 -0
- package/harness/prompts/planner.md +15 -9
- package/harness/prompts/refactor-cleaner.md +14 -0
- package/harness/prompts/review-cpp.md +14 -1
- package/harness/prompts/review-database.md +13 -0
- package/harness/prompts/review-go.md +13 -0
- package/harness/prompts/review-java.md +14 -1
- package/harness/prompts/review-kotlin.md +13 -0
- package/harness/prompts/review-python.md +14 -1
- package/harness/prompts/review-rust.md +13 -0
- package/harness/prompts/security-reviewer.md +15 -9
- package/harness/prompts/tdd-guide.md +14 -0
- package/harness/rules/audit.md +2 -2
- package/harness/rules/delegation.md +0 -2
- package/harness/rules/handoff.md +267 -0
- package/harness/rules/memory-management.md +4 -4
- package/harness/rules/precedence.md +1 -1
- package/harness/rules/retrieval.md +5 -5
- package/harness/rules/runtime-guards.md +1 -1
- package/harness/rules/self-heal.md +1 -1
- package/harness/rules/session-start.md +5 -5
- package/harness/rules/skills-management.md +2 -2
- package/harness/rules/verification.md +4 -4
- package/index.mjs +6 -2
- package/lib/ambient-memory.mjs +167 -0
- package/lib/handoff.mjs +176 -0
- package/lib/hardening.mjs +13 -8
- package/lib/memory-tools-plugin.mjs +107 -54
- package/lib/ohc/block-sync.mjs +69 -0
- package/lib/ohc/compress/search.mjs +152 -0
- package/lib/ohc/compress/state.mjs +76 -0
- package/lib/ohc/config.mjs +172 -16
- package/lib/ohc/message-ids.mjs +168 -0
- package/lib/ohc/notify.mjs +150 -0
- package/lib/ohc/protected-patterns.mjs +54 -0
- package/lib/ohc/prune-apply.mjs +134 -0
- package/lib/ohc/pruner.mjs +406 -55
- package/lib/ohc/reaper.mjs +12 -3
- package/lib/ohc/state.mjs +246 -15
- package/lib/ohc/strategies/deduplication.mjs +72 -0
- package/lib/ohc/strategies/index.mjs +2 -0
- package/lib/ohc/strategies/purge-errors.mjs +43 -0
- package/lib/ohc/token-utils.mjs +26 -0
- package/lib/ohc/updater.mjs +36 -13
- package/lib/paths.mjs +0 -3
- package/lib/search.mjs +48 -0
- package/package.json +1 -1
- package/schemas/audit.schema.json +22 -1
- package/schemas/backlog.schema.json +23 -2
- package/schemas/checkpoint.schema.json +23 -2
- package/schemas/constraint.schema.json +23 -2
- package/schemas/decision.schema.json +23 -2
- package/schemas/instinct.schema.json +23 -2
- package/schemas/mistake.schema.json +23 -2
- package/schemas/verification_receipt.schema.json +23 -2
- package/skill-builder.mjs +12 -23
|
@@ -35,7 +35,7 @@ Self-correction escalates through structured tiers. There is no self-termination
|
|
|
35
35
|
- Build failure → `build-error-resolver`
|
|
36
36
|
- Logic/scope/other → `diagnose` skill + `code-reviewer`
|
|
37
37
|
- Security → `security-reviewer`
|
|
38
|
-
- Config/tool → `
|
|
38
|
+
- Config/tool → `harness-optimizer` + openhermes audit
|
|
39
39
|
2. If structural (affects openhermes behavior across projects), generate a backlog item.
|
|
40
40
|
3. Run an openhermes audit to check for broken references, stale constraints, or provenance gaps.
|
|
41
41
|
4. Document findings and updated prevention rules.
|
|
@@ -8,13 +8,13 @@ Run this at the start of every new session and every resume before substantive w
|
|
|
8
8
|
2. Load openhermes status from `%USERPROFILE%\.config\opencode\ohc.json` if rule paths or memory locations are needed.
|
|
9
9
|
3. **Read autorecall cache**: If `openhermes\memory\recall\cache.json` exists, load it — it contains active checkpoint, constraints, decisions, and mistakes from the prior session. The autorecall plugin writes this at session start. Use this context before probing MCP tools.
|
|
10
10
|
4. Check only the smallest relevant curated memory slice in `openhermes\memory\`:
|
|
11
|
-
- latest checkpoint via `
|
|
12
|
-
- active decisions via `
|
|
13
|
-
- active constraints via `
|
|
11
|
+
- latest checkpoint via `ohc_latest`
|
|
12
|
+
- active decisions via `ohc_latest` or a narrow `ohc_search`
|
|
13
|
+
- active constraints via `ohc_latest` or a narrow `ohc_search`
|
|
14
14
|
- recent same-type mistakes only if the task matches a known pattern
|
|
15
15
|
- do not read whole memory indexes unless the task is explicitly about index auditing or repair
|
|
16
16
|
5. If no relevant memory exists, proceed fresh without pretending there is prior state.
|
|
17
|
-
6. If last openhermes audit is missing or older than 7 days, flag `/
|
|
17
|
+
6. If last openhermes audit is missing or older than 7 days, flag `/harness-audit` as due.
|
|
18
18
|
7. Before substantial work, choose the smallest correct path:
|
|
19
19
|
- native read/grep/glob for search/gather
|
|
20
20
|
- `explore` subagent for multi-file analysis
|
|
@@ -23,7 +23,7 @@ Run this at the start of every new session and every resume before substantive w
|
|
|
23
23
|
## User Entry Points
|
|
24
24
|
|
|
25
25
|
- `/openhermes`: bootstrap openhermes state, summarize current readiness, and surface due actions.
|
|
26
|
-
- `/
|
|
26
|
+
- `/harness-audit`: run an openhermes audit workflow and return findings.
|
|
27
27
|
|
|
28
28
|
## Output Contract
|
|
29
29
|
|
|
@@ -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 `ohc_search` 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 `ohc_save` with class `verification_receipt`.
|
|
@@ -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 `ohc_save` 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 (`ohc_get` or `ohc_list`) 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 `ohc_save` 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** — `ohc_save` 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
|
|
package/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ 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 { AmbientMemoryPlugin } from "./lib/ambient-memory.mjs"
|
|
6
7
|
import { OhcPlugin } from "./lib/ohc/pruner.mjs"
|
|
7
8
|
import { UpdaterPlugin } from "./lib/ohc/updater.mjs"
|
|
8
9
|
|
|
@@ -14,15 +15,17 @@ function chain(...fns) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export default async (input) => {
|
|
17
|
-
const
|
|
18
|
+
const results = await Promise.allSettled([
|
|
18
19
|
BootstrapPlugin(input),
|
|
19
20
|
AutorecallPlugin(input),
|
|
20
21
|
CuratorPlugin(input),
|
|
21
22
|
SkillBuilderPlugin(input),
|
|
22
23
|
MemoryToolsPlugin(input),
|
|
24
|
+
AmbientMemoryPlugin(input),
|
|
23
25
|
OhcPlugin(input),
|
|
24
26
|
UpdaterPlugin(input),
|
|
25
27
|
])
|
|
28
|
+
const [bootstrap, autorecall, curator, skillBuilder, memoryTools, ambient, ohc, updater] = results.map(r => r.status === 'fulfilled' ? r.value : {})
|
|
26
29
|
|
|
27
30
|
const merged = {}
|
|
28
31
|
|
|
@@ -33,6 +36,7 @@ export default async (input) => {
|
|
|
33
36
|
|
|
34
37
|
merged["experimental.chat.system.transform"] = chain(ohc["experimental.chat.system.transform"])
|
|
35
38
|
merged["experimental.chat.messages.transform"] = chain(
|
|
39
|
+
ambient["experimental.chat.messages.transform"],
|
|
36
40
|
bootstrap["experimental.chat.messages.transform"],
|
|
37
41
|
ohc["experimental.chat.messages.transform"],
|
|
38
42
|
)
|
|
@@ -41,7 +45,7 @@ export default async (input) => {
|
|
|
41
45
|
const eventHandlers = [autorecall.event, curator.event, skillBuilder.event].filter(Boolean)
|
|
42
46
|
if (eventHandlers.length) {
|
|
43
47
|
merged.event = async (payload) => {
|
|
44
|
-
await Promise.
|
|
48
|
+
await Promise.allSettled(eventHandlers.map(fn => fn(payload)))
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import { readJson, readJsonl, truncateText } from "./hardening.mjs"
|
|
4
|
+
import { getMemoryRoot, getRecallRoot, getDataRoot } from "./paths.mjs"
|
|
5
|
+
import { scoreRelevance } from "./search.mjs"
|
|
6
|
+
|
|
7
|
+
const PLURALS = { audit: "audits", checkpoint: "checkpoints", mistake: "mistakes", instinct: "instincts", decision: "decisions", constraint: "constraints", backlog: "backlog", verification_receipt: "verification_receipts" }
|
|
8
|
+
const CLASSES = ["audit", "checkpoint", "mistake", "instinct", "decision", "constraint", "backlog", "verification_receipt"]
|
|
9
|
+
|
|
10
|
+
const MEMORY_QUERY_PATTERNS = [
|
|
11
|
+
/where\s+(did|do)\s+we\s+(leave\s+off|left\s+off)/i,
|
|
12
|
+
/where\s+were\s+we/i,
|
|
13
|
+
/what\s+(was|is)\s+(the\s+)?(last|latest|most\s+recent)\s+(checkpoint|decision|constraint|feature|task|change|issue|fix|bug|request|request)/i,
|
|
14
|
+
/remind\s+me\s+(about|of)/i,
|
|
15
|
+
/what\s+were\s+we\s+(working\s+on|doing)/i,
|
|
16
|
+
/what\s+(happened|changed)\s+(last|yesterday|before|previously)/i,
|
|
17
|
+
/previous\s+session/i,
|
|
18
|
+
/last\s+time/i,
|
|
19
|
+
/recap/i,
|
|
20
|
+
/what\s+is\s+(the\s+)?(status|state)\s+of/i,
|
|
21
|
+
/did\s+we\s+(decide|agree|finish|resolve|discuss)/i,
|
|
22
|
+
/where\s+(did|do)\s+i\s+(leave\s+off|left\s+off)/i,
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
function hasExpired(r) {
|
|
26
|
+
if (r?.status === "expired" || r?.status === "decayed") return true
|
|
27
|
+
if (r?.decay_at && Date.parse(r.decay_at) < Date.now()) return true
|
|
28
|
+
if (r?.expires_at && Date.parse(r.expires_at) < Date.now()) return true
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function filterActive(entries) { return entries.filter(e => !hasExpired(e)) }
|
|
33
|
+
|
|
34
|
+
function detectMemoryQuery(text) {
|
|
35
|
+
return MEMORY_QUERY_PATTERNS.some(p => p.test(text))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function extractSearchTerms(text) {
|
|
39
|
+
const cleaned = text.replace(/<[^>]+>/g, "").replace(/[^\w\s-]/g, " ").trim()
|
|
40
|
+
return cleaned.slice(0, 200)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function classDir(cls) { return path.join(getMemoryRoot(), PLURALS[cls]) }
|
|
44
|
+
|
|
45
|
+
function readAllRecords() {
|
|
46
|
+
const records = []
|
|
47
|
+
for (const cls of CLASSES) {
|
|
48
|
+
if (cls === "mistake") {
|
|
49
|
+
for (const m of readJsonl(path.join(classDir(cls), "mistakes.jsonl"))) {
|
|
50
|
+
if (!hasExpired(m)) records.push({ ...m, _cls: cls })
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
const dir = classDir(cls)
|
|
54
|
+
const index = readJson(path.join(dir, "index.json"), [])
|
|
55
|
+
if (!Array.isArray(index)) continue
|
|
56
|
+
for (const entry of index) {
|
|
57
|
+
if (!hasExpired(entry)) {
|
|
58
|
+
records.push({ ...entry, _cls: cls })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return records
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function autoSearch(query) {
|
|
67
|
+
const q = (query || "").trim()
|
|
68
|
+
if (!q) return []
|
|
69
|
+
|
|
70
|
+
const records = readAllRecords()
|
|
71
|
+
const scored = records
|
|
72
|
+
.map(r => ({ ...r, _score: scoreRelevance(r, q, "") }))
|
|
73
|
+
.filter(r => r._score > 0)
|
|
74
|
+
.sort((a, b) => b._score - a._score)
|
|
75
|
+
.slice(0, 5)
|
|
76
|
+
|
|
77
|
+
return scored
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildMemoryBlock() {
|
|
81
|
+
const parts = []
|
|
82
|
+
|
|
83
|
+
const ckIndex = readJson(path.join(getMemoryRoot(), "checkpoints", "index.json"), [])
|
|
84
|
+
if (Array.isArray(ckIndex) && ckIndex.length > 0) {
|
|
85
|
+
const latest = [...ckIndex].sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))[0]
|
|
86
|
+
if (latest) {
|
|
87
|
+
const cp = readJson(path.join(getMemoryRoot(), "checkpoints", `${latest.id}.json`), null)
|
|
88
|
+
if (cp) {
|
|
89
|
+
parts.push(`Checkpoint: ${cp.summary || "N/A"}`)
|
|
90
|
+
if (cp.mission) parts.push(`Mission: ${truncateText(cp.mission, 200)}`)
|
|
91
|
+
if (Array.isArray(cp.next_actions) && cp.next_actions.length) parts.push(`Next: ${cp.next_actions.slice(0, 2).join("; ")}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ctIndex = readJson(path.join(getMemoryRoot(), "constraints", "index.json"), [])
|
|
97
|
+
if (Array.isArray(ctIndex)) {
|
|
98
|
+
const active = ctIndex.filter(e => e.status === "active")
|
|
99
|
+
if (active.length) parts.push(`Constraints (${active.length} active)`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const dcIndex = readJson(path.join(getMemoryRoot(), "decisions", "index.json"), [])
|
|
103
|
+
if (Array.isArray(dcIndex)) {
|
|
104
|
+
const recent = filterActive(dcIndex).slice(0, 2)
|
|
105
|
+
if (recent.length) parts.push(`Recent decisions: ${recent.map(d => truncateText(d.summary || "", 80)).join(" | ")}`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const bkIndex = readJson(path.join(getMemoryRoot(), "backlog", "index.json"), [])
|
|
109
|
+
if (Array.isArray(bkIndex)) {
|
|
110
|
+
const open = bkIndex.filter(e => e.status === "open")
|
|
111
|
+
if (open.length) parts.push(`Backlog: ${open.length} open`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return parts.length ? parts.join("\n") : "No active memory records found."
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const AmbientMemoryPlugin = async () => ({
|
|
118
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
119
|
+
if (!output.messages?.length) return
|
|
120
|
+
|
|
121
|
+
const firstUser = output.messages.find(m => m.info?.role === "user")
|
|
122
|
+
if (!firstUser?.parts?.length) return
|
|
123
|
+
|
|
124
|
+
const hasMemoryTag = firstUser.parts.some(p => p.type === "text" && p.text.includes("OPENHERMES_MEMORY"))
|
|
125
|
+
|
|
126
|
+
if (!hasMemoryTag) {
|
|
127
|
+
const block = buildMemoryBlock()
|
|
128
|
+
const tag = `<OPENHERMES_MEMORY>\n${block}\n</OPENHERMES_MEMORY>`
|
|
129
|
+
const textPart = firstUser.parts.find(p => p.type === "text")
|
|
130
|
+
if (textPart) {
|
|
131
|
+
textPart.text = `${tag}\n\n${textPart.text}`
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const lastUser = [...output.messages].reverse().find(m => m.info?.role === "user")
|
|
136
|
+
if (!lastUser) return
|
|
137
|
+
|
|
138
|
+
const text = lastUser.parts?.find(p => p.type === "text")?.text || ""
|
|
139
|
+
if (!text || !detectMemoryQuery(text)) return
|
|
140
|
+
|
|
141
|
+
const terms = extractSearchTerms(text)
|
|
142
|
+
if (!terms) return
|
|
143
|
+
|
|
144
|
+
const results = autoSearch(terms)
|
|
145
|
+
if (!results.length) return
|
|
146
|
+
|
|
147
|
+
const contextBlock = [
|
|
148
|
+
`<memory-context>`,
|
|
149
|
+
`[System note: auto-retrieved from durable memory — NOT new user input]`,
|
|
150
|
+
...results.slice(0, 3).map(r => {
|
|
151
|
+
const cls = r.class || "?";
|
|
152
|
+
const label = `${cls}:${r.id}`
|
|
153
|
+
return `- ${label}: ${truncateText(r.summary || "", 120)}`
|
|
154
|
+
}),
|
|
155
|
+
results.length > 3 ? ` ... and ${results.length - 3} more results` : null,
|
|
156
|
+
`</memory-context>`,
|
|
157
|
+
].filter(Boolean).join("\n")
|
|
158
|
+
|
|
159
|
+
const idx = output.messages.indexOf(lastUser)
|
|
160
|
+
if (idx > 0) {
|
|
161
|
+
output.messages.splice(idx, 0, {
|
|
162
|
+
parts: [{ type: "text", text: contextBlock }],
|
|
163
|
+
info: { role: "system" },
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
package/lib/handoff.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// OpenHermes Agent Handoff — lightweight helper library
|
|
2
|
+
// Convention-based, no runtime deps. Agents import and call.
|
|
3
|
+
|
|
4
|
+
let _idSeq = 0
|
|
5
|
+
const COMPLEXITY_RULES = [
|
|
6
|
+
{ maxFiles: 2, patterns: 0, label: "easy" },
|
|
7
|
+
{ maxFiles: 10, patterns: 0, label: "medium" },
|
|
8
|
+
{ maxFiles: 60, patterns: 0, label: "hard" },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
function nextId() {
|
|
12
|
+
_idSeq++
|
|
13
|
+
return `ho_${Date.now().toString(36)}_${_idSeq}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)) }
|
|
17
|
+
|
|
18
|
+
// Estimate task complexity from file count and description.
|
|
19
|
+
export function assessComplexity(fileCount = 1, description = "", patterns = []) {
|
|
20
|
+
const pScore = Array.isArray(patterns) ? patterns.length : 0
|
|
21
|
+
const level = fileCount > 50 || pScore > 5 ? "very-large"
|
|
22
|
+
: fileCount > 10 || pScore > 2 ? "hard"
|
|
23
|
+
: fileCount > 2 || pScore > 0 ? "medium"
|
|
24
|
+
: "easy"
|
|
25
|
+
return {
|
|
26
|
+
level,
|
|
27
|
+
fileCount: clamp(fileCount, 0, 9999),
|
|
28
|
+
patterns: pScore,
|
|
29
|
+
strategy: level === "easy" ? "direct"
|
|
30
|
+
: level === "medium" ? "sequential-or-fan-out"
|
|
31
|
+
: level === "hard" ? "sequential-multi"
|
|
32
|
+
: "fan-out"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Build a structured handoff request string for the `task` tool prompt.
|
|
37
|
+
export function handoffRequest(opts) {
|
|
38
|
+
const {
|
|
39
|
+
agent,
|
|
40
|
+
phase = "execute",
|
|
41
|
+
context = "",
|
|
42
|
+
goal = "",
|
|
43
|
+
expected = "",
|
|
44
|
+
permissions = "",
|
|
45
|
+
limits = "",
|
|
46
|
+
complexity,
|
|
47
|
+
} = opts || {}
|
|
48
|
+
const id = nextId()
|
|
49
|
+
const c = complexity || assessComplexity()
|
|
50
|
+
const lines = [
|
|
51
|
+
`## HANDOFF REQUEST`,
|
|
52
|
+
`Agent: ${agent}`,
|
|
53
|
+
`Task ID: ${id}`,
|
|
54
|
+
`Phase: ${phase}`,
|
|
55
|
+
`Complexity: ${c.level}`,
|
|
56
|
+
``,
|
|
57
|
+
`### Context`,
|
|
58
|
+
String(context).trim() || "(no context provided)",
|
|
59
|
+
``,
|
|
60
|
+
`### Goal`,
|
|
61
|
+
String(goal).trim() || "(no goal provided)",
|
|
62
|
+
``,
|
|
63
|
+
`### Expected Output`,
|
|
64
|
+
String(expected).trim() || "Return structured result with status, summary, details, receipts, next, learning.",
|
|
65
|
+
``,
|
|
66
|
+
`### Permissions`,
|
|
67
|
+
String(permissions).trim() || "(default permissions apply)",
|
|
68
|
+
``,
|
|
69
|
+
`### Limits`,
|
|
70
|
+
String(limits).trim() || "(no explicit limits)",
|
|
71
|
+
``,
|
|
72
|
+
`### Handoff Result Format`,
|
|
73
|
+
`When done, return:`,
|
|
74
|
+
`## HANDOFF RESULT`,
|
|
75
|
+
`Status: success | failure | partial`,
|
|
76
|
+
`Task ID: ${id}`,
|
|
77
|
+
`### Summary`,
|
|
78
|
+
`### Details`,
|
|
79
|
+
`### Receipts`,
|
|
80
|
+
`### Next`,
|
|
81
|
+
`### Learning`,
|
|
82
|
+
].join("\n")
|
|
83
|
+
return { id, prompt: lines }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validate a subagent's result string has required sections.
|
|
87
|
+
export function parseHandoffResult(text) {
|
|
88
|
+
if (!text || typeof text !== "string") return { status: "invalid", error: "no result text" }
|
|
89
|
+
const statusMatch = text.match(/Status:\s*(success|failure|partial)/i)
|
|
90
|
+
const hasSummary = /### Summary/i.test(text)
|
|
91
|
+
const hasDetails = /### Details/i.test(text)
|
|
92
|
+
const hasReceipts = /### Receipts/i.test(text)
|
|
93
|
+
const summaryMatch = text.match(/### Summary\s*\n\s*(.+)/i)
|
|
94
|
+
return {
|
|
95
|
+
status: statusMatch ? statusMatch[1].toLowerCase() : "unknown",
|
|
96
|
+
summary: summaryMatch ? summaryMatch[1].trim() : "",
|
|
97
|
+
hasSummary,
|
|
98
|
+
hasDetails,
|
|
99
|
+
hasReceipts,
|
|
100
|
+
valid: !!(statusMatch && hasSummary),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Agent capability/role definitions for reference.
|
|
105
|
+
export const AGENT_ROLES = {
|
|
106
|
+
OpenHermes: { tier: 3, edit: true, exec: true, desc: "Primary agent — all tools, all permissions" },
|
|
107
|
+
architect: { tier: 1, edit: false, exec: false, desc: "System architecture design" },
|
|
108
|
+
planner: { tier: 1, edit: false, exec: false, desc: "Feature/refactor planning" },
|
|
109
|
+
"code-reviewer": { tier: 1, edit: false, exec: false, desc: "Code quality review" },
|
|
110
|
+
"security-reviewer": { tier: 1, edit: false, exec: false, desc: "Security audit (report only, no patches)" },
|
|
111
|
+
explore: { tier: 1, edit: false, exec: false, desc: "Read-only codebase exploration" },
|
|
112
|
+
"build-error-resolver": { tier: 2, edit: true, exec: true, desc: "Build/type error fixes" },
|
|
113
|
+
"doc-updater": { tier: 2, edit: true, exec: true, desc: "Doc/codemap updates" },
|
|
114
|
+
"refactor-cleaner": { tier: 2, edit: true, exec: true, desc: "Dead code cleanup" },
|
|
115
|
+
"tdd-guide": { tier: 2, edit: true, exec: true, desc: "TDD red-green-refactor" },
|
|
116
|
+
"loop-operator": { tier: 3, edit: true, exec: true, desc: "Managed autonomous loops" },
|
|
117
|
+
"e2e-runner": { tier: 3, edit: true, exec: true, desc: "Playwright E2E tests" },
|
|
118
|
+
"docs-lookup": { tier: 1, edit: false, exec: true, desc: "MCP doc lookup" },
|
|
119
|
+
"harness-optimizer": { tier: 1, edit: false, exec: true, desc: "Harness config audit" },
|
|
120
|
+
"review-database": { tier: 1, edit: false, exec: true, desc: "PostgreSQL review" },
|
|
121
|
+
"review-go": { tier: 1, edit: false, exec: true, desc: "Go code review" },
|
|
122
|
+
"build-go": { tier: 2, edit: true, exec: true, desc: "Go build fix" },
|
|
123
|
+
"review-java": { tier: 1, edit: false, exec: true, desc: "Java review" },
|
|
124
|
+
"build-java": { tier: 2, edit: true, exec: true, desc: "Java build fix" },
|
|
125
|
+
"review-kotlin": { tier: 1, edit: false, exec: true, desc: "Kotlin review" },
|
|
126
|
+
"build-kotlin": { tier: 2, edit: true, exec: true, desc: "Kotlin build fix" },
|
|
127
|
+
"review-cpp": { tier: 1, edit: false, exec: true, desc: "C++ review" },
|
|
128
|
+
"build-cpp": { tier: 2, edit: true, exec: true, desc: "C++ build fix" },
|
|
129
|
+
"review-rust": { tier: 1, edit: false, exec: true, desc: "Rust review" },
|
|
130
|
+
"build-rust": { tier: 2, edit: true, exec: true, desc: "Rust build fix" },
|
|
131
|
+
"review-python": { tier: 1, edit: false, exec: true, desc: "Python review" },
|
|
132
|
+
"general": { tier: 2, edit: true, exec: true, desc: "General purpose" },
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Suggest best agent for a task type.
|
|
136
|
+
export function suggestAgent(taskType) {
|
|
137
|
+
const map = {
|
|
138
|
+
architecture: "architect",
|
|
139
|
+
plan: "planner",
|
|
140
|
+
"code-review": "code-reviewer",
|
|
141
|
+
security: "security-reviewer",
|
|
142
|
+
explore: "explore",
|
|
143
|
+
"build-fix": "build-error-resolver",
|
|
144
|
+
docs: "doc-updater",
|
|
145
|
+
"dead-code": "refactor-cleaner",
|
|
146
|
+
tdd: "tdd-guide",
|
|
147
|
+
e2e: "e2e-runner",
|
|
148
|
+
"doc-lookup": "docs-lookup",
|
|
149
|
+
"db-review": "review-database",
|
|
150
|
+
"go-review": "review-go",
|
|
151
|
+
"go-build": "build-go",
|
|
152
|
+
"java-review": "review-java",
|
|
153
|
+
"java-build": "build-java",
|
|
154
|
+
"kotlin-review": "review-kotlin",
|
|
155
|
+
"kotlin-build": "build-kotlin",
|
|
156
|
+
"cpp-review": "review-cpp",
|
|
157
|
+
"cpp-build": "build-cpp",
|
|
158
|
+
"rust-review": "review-rust",
|
|
159
|
+
"rust-build": "build-rust",
|
|
160
|
+
"python-review": "review-python",
|
|
161
|
+
loop: "loop-operator",
|
|
162
|
+
"harness-audit": "harness-optimizer",
|
|
163
|
+
}
|
|
164
|
+
return map[taskType] || null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Quick permission check: can agent X do action Y?
|
|
168
|
+
export function canAgent(agentName, action) {
|
|
169
|
+
const role = AGENT_ROLES[agentName]
|
|
170
|
+
if (!role) return false
|
|
171
|
+
if (action === "edit") return role.edit
|
|
172
|
+
if (action === "exec" || action === "bash") return role.exec
|
|
173
|
+
if (action === "read") return true
|
|
174
|
+
if (action === "delegate") return role.tier >= 1
|
|
175
|
+
return false
|
|
176
|
+
}
|
package/lib/hardening.mjs
CHANGED
|
@@ -96,13 +96,8 @@ function atomicWriteJson(filePath, data) {
|
|
|
96
96
|
try {
|
|
97
97
|
fs.renameSync(tmpPath, filePath)
|
|
98
98
|
} catch (err) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
fs.renameSync(tmpPath, filePath)
|
|
102
|
-
} catch (retryErr) {
|
|
103
|
-
try { fs.rmSync(tmpPath, { force: true }) } catch {}
|
|
104
|
-
throw retryErr
|
|
105
|
-
}
|
|
99
|
+
fs.copyFileSync(tmpPath, filePath)
|
|
100
|
+
fs.rmSync(tmpPath, { force: true })
|
|
106
101
|
}
|
|
107
102
|
}
|
|
108
103
|
|
|
@@ -120,4 +115,14 @@ function readJsonl(fp) {
|
|
|
120
115
|
} catch { return [] }
|
|
121
116
|
}
|
|
122
117
|
|
|
123
|
-
|
|
118
|
+
function buildEnvironmentFingerprint(root, directory, project) {
|
|
119
|
+
return fingerprintEnvironment({
|
|
120
|
+
cwd: directory,
|
|
121
|
+
harnessRoot: root,
|
|
122
|
+
projectRoot: directory,
|
|
123
|
+
project: project?.name || path.basename(directory),
|
|
124
|
+
sessionId: project?.session_id || null,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { atomicWriteJson, buildEnvironmentFingerprint, fingerprintEnvironment, fingerprintFile, isPlainObject, isTruthy, readJson, readJsonl, redactSensitiveText, sanitizeRecord, truncateText }
|