openhermes 2.8.0 → 4.0.0

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.
Files changed (158) hide show
  1. package/CONTEXT.md +18 -0
  2. package/ETHOS.md +15 -0
  3. package/README.md +135 -292
  4. package/bootstrap.mjs +174 -512
  5. package/harness/agents/openhermes.md +87 -0
  6. package/harness/codex/CONSTITUTION.md +70 -148
  7. package/harness/codex/ROUTING.md +126 -0
  8. package/harness/commands/oh-doctor.md +26 -0
  9. package/harness/instructions/CONVENTIONS.md +206 -206
  10. package/harness/instructions/RUNTIME.md +54 -31
  11. package/harness/skills/oh-builder/SKILL.md +98 -0
  12. package/harness/skills/oh-caveman/SKILL.md +33 -0
  13. package/harness/skills/oh-expert/SKILL.md +121 -0
  14. package/harness/skills/oh-freeze/SKILL.md +28 -0
  15. package/harness/skills/oh-gauntlet/SKILL.md +119 -0
  16. package/harness/skills/oh-grill/SKILL.md +77 -0
  17. package/harness/skills/oh-guard/SKILL.md +33 -0
  18. package/harness/skills/oh-handoff/SKILL.md +33 -0
  19. package/harness/skills/oh-health/SKILL.md +90 -0
  20. package/harness/skills/oh-init/SKILL.md +78 -0
  21. package/harness/skills/oh-investigate/SKILL.md +35 -0
  22. package/harness/skills/oh-issue/SKILL.md +36 -0
  23. package/harness/skills/oh-learn/SKILL.md +28 -0
  24. package/harness/skills/oh-manifest/SKILL.md +84 -0
  25. package/harness/skills/oh-plan-review/SKILL.md +128 -0
  26. package/harness/skills/oh-planner/SKILL.md +157 -0
  27. package/harness/skills/oh-prd/SKILL.md +35 -0
  28. package/harness/skills/oh-retro/SKILL.md +33 -0
  29. package/harness/skills/oh-review/SKILL.md +110 -0
  30. package/harness/skills/oh-security/SKILL.md +110 -0
  31. package/harness/skills/oh-ship/SKILL.md +39 -0
  32. package/harness/skills/oh-skill-craft/SKILL.md +107 -0
  33. package/harness/skills/oh-skills-link/SKILL.md +29 -0
  34. package/harness/skills/oh-skills-list/SKILL.md +31 -0
  35. package/harness/skills/oh-triage/SKILL.md +36 -0
  36. package/index.mjs +3 -60
  37. package/lib/harness-resolver.mjs +77 -0
  38. package/lib/logger.mjs +62 -0
  39. package/package.json +49 -53
  40. package/test/plugins-behavioral.test.mjs +64 -0
  41. package/test/plugins.test.mjs +62 -0
  42. package/autorecall.mjs +0 -237
  43. package/curator.mjs +0 -482
  44. package/harness/commands/build-fix.md +0 -60
  45. package/harness/commands/checkpoint.md +0 -68
  46. package/harness/commands/code-review.md +0 -71
  47. package/harness/commands/doctor.md +0 -42
  48. package/harness/commands/eval.md +0 -89
  49. package/harness/commands/go-build.md +0 -87
  50. package/harness/commands/go-review.md +0 -71
  51. package/harness/commands/harness-audit.md +0 -90
  52. package/harness/commands/learn.md +0 -37
  53. package/harness/commands/loop-start.md +0 -38
  54. package/harness/commands/loop-status.md +0 -30
  55. package/harness/commands/memory-search.md +0 -37
  56. package/harness/commands/model-route.md +0 -32
  57. package/harness/commands/ohc.md +0 -13
  58. package/harness/commands/orchestrate.md +0 -88
  59. package/harness/commands/plan.md +0 -53
  60. package/harness/commands/quality-gate.md +0 -35
  61. package/harness/commands/refactor-clean.md +0 -102
  62. package/harness/commands/rust-build.md +0 -78
  63. package/harness/commands/rust-review.md +0 -65
  64. package/harness/commands/security.md +0 -93
  65. package/harness/commands/setup-pm.md +0 -65
  66. package/harness/commands/skill-create.md +0 -99
  67. package/harness/commands/test-coverage.md +0 -80
  68. package/harness/commands/update-codemaps.md +0 -81
  69. package/harness/commands/update-docs.md +0 -67
  70. package/harness/commands/verify.md +0 -68
  71. package/harness/prompts/architect.txt +0 -189
  72. package/harness/prompts/build-cpp.md +0 -98
  73. package/harness/prompts/build-error-resolver.md +0 -44
  74. package/harness/prompts/build-go.md +0 -340
  75. package/harness/prompts/build-java.md +0 -140
  76. package/harness/prompts/build-kotlin.md +0 -137
  77. package/harness/prompts/build-rust.md +0 -108
  78. package/harness/prompts/code-reviewer.md +0 -40
  79. package/harness/prompts/doc-updater.md +0 -206
  80. package/harness/prompts/docs-lookup.md +0 -71
  81. package/harness/prompts/e2e-runner.txt +0 -317
  82. package/harness/prompts/explore.md +0 -42
  83. package/harness/prompts/harness-optimizer.md +0 -42
  84. package/harness/prompts/loop-operator.md +0 -53
  85. package/harness/prompts/planner.md +0 -37
  86. package/harness/prompts/refactor-cleaner.md +0 -256
  87. package/harness/prompts/review-cpp.md +0 -81
  88. package/harness/prompts/review-database.md +0 -261
  89. package/harness/prompts/review-go.md +0 -257
  90. package/harness/prompts/review-java.md +0 -113
  91. package/harness/prompts/review-kotlin.md +0 -143
  92. package/harness/prompts/review-python.md +0 -101
  93. package/harness/prompts/review-rust.md +0 -77
  94. package/harness/prompts/security-reviewer.md +0 -42
  95. package/harness/prompts/tdd-guide.md +0 -228
  96. package/harness/rules/audit.md +0 -84
  97. package/harness/rules/checkpointing.md +0 -75
  98. package/harness/rules/context-loading.md +0 -33
  99. package/harness/rules/credential-exposure.md +0 -0
  100. package/harness/rules/delegation.md +0 -80
  101. package/harness/rules/handoff.md +0 -267
  102. package/harness/rules/memory-management.md +0 -28
  103. package/harness/rules/precedence.md +0 -52
  104. package/harness/rules/promotion.md +0 -46
  105. package/harness/rules/ranking.md +0 -64
  106. package/harness/rules/retrieval.md +0 -94
  107. package/harness/rules/runtime-guards.md +0 -196
  108. package/harness/rules/self-heal.md +0 -79
  109. package/harness/rules/session-start.md +0 -34
  110. package/harness/rules/skills-management.md +0 -165
  111. package/harness/rules/state-drift.md +0 -192
  112. package/harness/rules/verification.md +0 -88
  113. package/harness/scripts/sync-commands.mjs +0 -259
  114. package/harness/skills/.bundled_manifest +0 -17
  115. package/harness/skills/.usage.json +0 -6
  116. package/harness/skills/api-design/SKILL.md +0 -523
  117. package/harness/skills/backend-patterns/SKILL.md +0 -598
  118. package/harness/skills/coding-standards/SKILL.md +0 -549
  119. package/harness/skills/e2e-testing/SKILL.md +0 -326
  120. package/harness/skills/frontend-patterns/SKILL.md +0 -642
  121. package/harness/skills/frontend-slides/SKILL.md +0 -184
  122. package/harness/skills/security-review/SKILL.md +0 -495
  123. package/harness/skills/strategic-compact/SKILL.md +0 -131
  124. package/harness/skills/tdd-workflow/SKILL.md +0 -463
  125. package/harness/skills/verification-loop/SKILL.md +0 -126
  126. package/lib/ambient-memory.mjs +0 -167
  127. package/lib/handoff.mjs +0 -171
  128. package/lib/hardening.mjs +0 -146
  129. package/lib/memory-tools-plugin.mjs +0 -368
  130. package/lib/ohc/block-sync.mjs +0 -69
  131. package/lib/ohc/compress/search.mjs +0 -152
  132. package/lib/ohc/compress/state.mjs +0 -76
  133. package/lib/ohc/config.mjs +0 -185
  134. package/lib/ohc/message-ids.mjs +0 -178
  135. package/lib/ohc/notify.mjs +0 -135
  136. package/lib/ohc/protected-patterns.mjs +0 -55
  137. package/lib/ohc/prune-apply.mjs +0 -134
  138. package/lib/ohc/pruner.mjs +0 -608
  139. package/lib/ohc/reaper.mjs +0 -70
  140. package/lib/ohc/state.mjs +0 -265
  141. package/lib/ohc/strategies/deduplication.mjs +0 -72
  142. package/lib/ohc/strategies/index.mjs +0 -2
  143. package/lib/ohc/strategies/purge-errors.mjs +0 -43
  144. package/lib/ohc/token-utils.mjs +0 -26
  145. package/lib/ohc/updater.mjs +0 -132
  146. package/lib/paths.mjs +0 -49
  147. package/lib/schema-validator.mjs +0 -79
  148. package/lib/search.mjs +0 -48
  149. package/schemas/audit.schema.json +0 -82
  150. package/schemas/backlog.schema.json +0 -63
  151. package/schemas/checkpoint.schema.json +0 -65
  152. package/schemas/constraint.schema.json +0 -62
  153. package/schemas/decision.schema.json +0 -63
  154. package/schemas/instinct.schema.json +0 -63
  155. package/schemas/loop-state.schema.json +0 -33
  156. package/schemas/mistake.schema.json +0 -64
  157. package/schemas/verification_receipt.schema.json +0 -88
  158. package/skill-builder.mjs +0 -88
package/lib/ohc/state.mjs DELETED
@@ -1,265 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import os from "node:os"
4
-
5
- const STATE_DIR = path.join(os.homedir(), ".local", "share", "opencode", "ohc")
6
- const LEGACY_FILE = path.join(os.homedir(), ".local", "share", "opencode", "ohc-state.json")
7
-
8
- function sessionPath(sessionId) {
9
- const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_")
10
- return path.join(STATE_DIR, `${safe}.json`)
11
- }
12
-
13
- function ensureDir() {
14
- fs.mkdirSync(STATE_DIR, { recursive: true })
15
- }
16
-
17
- function migrateLegacy() {
18
- try {
19
- if (!fs.existsSync(LEGACY_FILE)) return
20
- const raw = JSON.parse(fs.readFileSync(LEGACY_FILE, "utf8"))
21
- if (typeof raw !== "object") return
22
- ensureDir()
23
- for (const [sid, data] of Object.entries(raw)) {
24
- const sp = sessionPath(sid)
25
- if (!fs.existsSync(sp)) {
26
- fs.writeFileSync(sp, JSON.stringify({ ...data, migratedFrom: "legacy" }, null, 2), "utf8")
27
- }
28
- }
29
- fs.renameSync(LEGACY_FILE, LEGACY_FILE + ".bak")
30
- } catch {}
31
- }
32
-
33
- migrateLegacy()
34
-
35
- export function loadOhcState(sessionId) {
36
- if (!sessionId) return null
37
- try {
38
- return JSON.parse(fs.readFileSync(sessionPath(sessionId), "utf8"))
39
- } catch {
40
- return null
41
- }
42
- }
43
-
44
- export function saveOhcState(sessionId, data) {
45
- if (!sessionId) return
46
- ensureDir()
47
- fs.writeFileSync(sessionPath(sessionId), JSON.stringify({ ...data, updatedAt: new Date().toISOString() }, null, 2), "utf8")
48
- }
49
-
50
-
51
- export function createSessionState() {
52
- return {
53
- sessionId: null,
54
- isSubAgent: false,
55
- manualMode: false,
56
- pendingManualTrigger: null,
57
- prune: {
58
- tools: new Map(),
59
- messages: {
60
- byMessageId: new Map(),
61
- blocksById: new Map(),
62
- activeBlockIds: new Set(),
63
- activeByAnchorMessageId: new Map(),
64
- nextBlockId: 1,
65
- nextRunId: 1,
66
- },
67
- },
68
- nudges: {
69
- contextLimitAnchors: new Set(),
70
- turnNudgeAnchors: new Set(),
71
- iterationNudgeAnchors: new Set(),
72
- },
73
- stats: {
74
- pruneTokenCounter: 0,
75
- totalPruneTokens: 0,
76
- },
77
- toolParameters: new Map(),
78
- toolIdList: [],
79
- messageIds: { byRawId: new Map(), byRef: new Map(), nextRef: 1 },
80
- lastCompaction: 0,
81
- currentTurn: 0,
82
- modelContextLimit: undefined,
83
- systemPromptTokens: undefined,
84
- protectedTurns: { enabled: false, turns: 0 },
85
- compressionTiming: { starts: new Map(), pendingByCallId: new Map(), lastDurationMs: 0, totalDurationMs: 0 },
86
- lastNudgePct: 0,
87
- lastAutoPruneAt: null,
88
- prunedIds: new Set(),
89
- summary: null,
90
- anchorMessageId: null,
91
- totalTokensSaved: 0,
92
- totalMessagesRemoved: 0,
93
- blockCount: 0,
94
- }
95
- }
96
-
97
- function pruneMapToObj(map) {
98
- return Object.fromEntries(map)
99
- }
100
-
101
- function pruneMapFromObj(obj) {
102
- if (!obj || typeof obj !== "object") return new Map()
103
- return new Map(Object.entries(obj))
104
- }
105
-
106
- function setToArr(s) {
107
- return [...s]
108
- }
109
-
110
- function setFromArr(a) {
111
- return new Set(Array.isArray(a) ? a : [])
112
- }
113
-
114
- export function serializeState(state) {
115
- return {
116
- sessionId: state.sessionId,
117
- manualMode: state.manualMode,
118
- lastCompaction: state.lastCompaction,
119
- currentTurn: state.currentTurn,
120
- modelContextLimit: state.modelContextLimit,
121
- systemPromptTokens: state.systemPromptTokens,
122
- stats: { ...state.stats },
123
- nudges: {
124
- contextLimitAnchors: setToArr(state.nudges.contextLimitAnchors),
125
- turnNudgeAnchors: setToArr(state.nudges.turnNudgeAnchors),
126
- iterationNudgeAnchors: setToArr(state.nudges.iterationNudgeAnchors),
127
- },
128
- prune: {
129
- tools: pruneMapToObj(state.prune.tools),
130
- messages: {
131
- nextBlockId: state.prune?.messages?.nextBlockId || 1,
132
- nextRunId: state.prune?.messages?.nextRunId || 1,
133
- blocksById: pruneMapToObj(state.prune?.messages?.blocksById),
134
- byMessageId: pruneMapToObj(state.prune?.messages?.byMessageId),
135
- activeBlockIds: setToArr(state.prune?.messages?.activeBlockIds),
136
- },
137
- },
138
- lastAutoPruneAt: state.lastAutoPruneAt,
139
- totalTokensSaved: state.totalTokensSaved,
140
- totalMessagesRemoved: state.totalMessagesRemoved,
141
- blockCount: state.blockCount,
142
- summary: state.summary,
143
- anchorMessageId: state.anchorMessageId,
144
- prunedIds: setToArr(state.prunedIds),
145
- isSubAgent: state.isSubAgent || false,
146
- messageIds: {
147
- byRawId: pruneMapToObj(state.messageIds?.byRawId),
148
- byRef: pruneMapToObj(state.messageIds?.byRef),
149
- nextRef: state.messageIds?.nextRef || 1,
150
- },
151
- compressionTiming: {
152
- starts: Object.fromEntries(state.compressionTiming?.starts || new Map()),
153
- pendingByCallId: pruneMapToObj(state.compressionTiming?.pendingByCallId || new Map()),
154
- lastDurationMs: state.compressionTiming?.lastDurationMs || 0,
155
- totalDurationMs: state.compressionTiming?.totalDurationMs || 0,
156
- },
157
- }
158
- }
159
-
160
- export function deserializeState(saved) {
161
- const state = createSessionState()
162
- if (!saved) return state
163
- state.sessionId = saved.sessionId || null
164
- state.manualMode = saved.manualMode || false
165
- state.lastCompaction = saved.lastCompaction || 0
166
- state.currentTurn = saved.currentTurn || 0
167
- state.modelContextLimit = saved.modelContextLimit
168
- state.systemPromptTokens = saved.systemPromptTokens
169
- if (saved.stats) Object.assign(state.stats, saved.stats)
170
- if (saved.nudges) {
171
- state.nudges.contextLimitAnchors = setFromArr(saved.nudges.contextLimitAnchors)
172
- state.nudges.turnNudgeAnchors = setFromArr(saved.nudges.turnNudgeAnchors)
173
- state.nudges.iterationNudgeAnchors = setFromArr(saved.nudges.iterationNudgeAnchors)
174
- }
175
- if (saved.prune?.tools) state.prune.tools = pruneMapFromObj(saved.prune.tools)
176
- if (saved.prune?.messages) {
177
- const pm = saved.prune.messages
178
- state.prune.messages.nextBlockId = pm.nextBlockId || 1
179
- state.prune.messages.nextRunId = pm.nextRunId || 1
180
- state.prune.messages.blocksById = pruneMapFromObj(pm.blocksById)
181
- state.prune.messages.byMessageId = pruneMapFromObj(pm.byMessageId)
182
- state.prune.messages.activeBlockIds = setFromArr(pm.activeBlockIds)
183
- }
184
- state.lastAutoPruneAt = saved.lastAutoPruneAt || null
185
- state.totalTokensSaved = saved.totalTokensSaved || 0
186
- state.totalMessagesRemoved = saved.totalMessagesRemoved || 0
187
- state.blockCount = saved.blockCount || 0
188
- state.summary = saved.summary || null
189
- state.anchorMessageId = saved.anchorMessageId || null
190
- state.prunedIds = setFromArr(saved.prunedIds)
191
- if (saved.compressionTiming) {
192
- state.compressionTiming.starts = pruneMapFromObj(saved.compressionTiming.starts)
193
- state.compressionTiming.lastDurationMs = saved.compressionTiming.lastDurationMs || 0
194
- state.compressionTiming.totalDurationMs = saved.compressionTiming.totalDurationMs || 0
195
- }
196
- if (saved.messageIds) {
197
- state.messageIds.byRawId = pruneMapFromObj(saved.messageIds.byRawId)
198
- state.messageIds.byRef = pruneMapFromObj(saved.messageIds.byRef)
199
- state.messageIds.nextRef = saved.messageIds.nextRef || 1
200
- }
201
- if (saved.isSubAgent !== undefined) state.isSubAgent = saved.isSubAgent
202
- return state
203
- }
204
-
205
- export function buildToolIdList(state, messages) {
206
- const ids = []
207
- for (const msg of messages) {
208
- if (!Array.isArray(msg.parts)) continue
209
- for (const part of msg.parts) {
210
- if (part.type === "tool" && part.callID) {
211
- ids.push(part.callID)
212
- }
213
- }
214
- }
215
- state.toolIdList = ids
216
- return ids
217
- }
218
-
219
- export function syncToolCache(state, messages) {
220
- let userTurn = 0
221
- for (const msg of messages) {
222
- const hasUserText = msg.info?.role === "user" && msg.parts?.some(p => p.type === "text" && p.text?.trim())
223
- if (hasUserText) userTurn++
224
- if (!Array.isArray(msg.parts)) continue
225
- for (const part of msg.parts) {
226
- if (part.type !== "tool" || !part.callID) continue
227
- const existing = state.toolParameters.get(part.callID)
228
- if (existing) {
229
- existing.status = part.state?.status || existing.status
230
- continue
231
- }
232
- state.toolParameters.set(part.callID, {
233
- tool: part.tool || "unknown",
234
- parameters: part.state?.input || {},
235
- status: part.state?.status || "pending",
236
- turn: userTurn,
237
- tokenCount: estimateToolTokens(part),
238
- lastSeen: Date.now(),
239
- })
240
- }
241
- }
242
- state.currentTurn = Math.max(state.currentTurn, userTurn)
243
- }
244
-
245
- function estimateToolTokens(part) {
246
- if (!part.state) return 0
247
- let t = 0
248
- if (part.state.input) t += JSON.stringify(part.state.input).length / 4
249
- if (part.state.output) {
250
- t += (typeof part.state.output === "string" ? part.state.output : JSON.stringify(part.state.output ?? "")).length / 4
251
- }
252
- return Math.ceil(t)
253
- }
254
-
255
- export function countTurns(state, messages) {
256
- let userCount = 0
257
- for (const msg of messages) {
258
- if (msg.info?.role === "user") {
259
- const parts = Array.isArray(msg.parts) ? msg.parts : []
260
- const hasText = parts.some(p => p.type === "text" && p.text?.trim())
261
- if (hasText) userCount++
262
- }
263
- }
264
- return userCount
265
- }
@@ -1,72 +0,0 @@
1
- import { isToolNameProtected, getFilePathsFromParameters, isFilePathProtected } from "../protected-patterns.mjs"
2
- import { getTotalToolTokens } from "../token-utils.mjs"
3
-
4
- export function deduplicate(state, config, messages) {
5
- if (state.manualMode && !config.manualMode?.automaticStrategies) return
6
-
7
- if (!config.strategies?.deduplication?.enabled) return
8
-
9
- const allIds = state.toolIdList
10
- if (!allIds?.length) return
11
-
12
- const unprunedIds = allIds.filter(id => !state.prune.tools.has(id))
13
- if (!unprunedIds.length) return
14
-
15
- const protectedTools = config.strategies.deduplication.protectedTools || []
16
-
17
- const sigMap = new Map()
18
-
19
- for (const id of unprunedIds) {
20
- const meta = state.toolParameters.get(id)
21
- if (!meta) continue
22
-
23
- if (isToolNameProtected(meta.tool, protectedTools)) continue
24
-
25
- const fps = getFilePathsFromParameters(meta.tool, meta.parameters)
26
- if (isFilePathProtected(fps, config.protectedFilePatterns)) continue
27
-
28
- const sig = createToolSignature(meta.tool, meta.parameters)
29
- if (!sigMap.has(sig)) sigMap.set(sig, [])
30
- sigMap.get(sig).push(id)
31
- }
32
-
33
- const toPrune = []
34
- for (const ids of sigMap.values()) {
35
- if (ids.length > 1) {
36
- toPrune.push(...ids.slice(0, -1))
37
- }
38
- }
39
-
40
- if (!toPrune.length) return
41
-
42
- state.stats.totalPruneTokens += getTotalToolTokens(state, toPrune)
43
- for (const id of toPrune) {
44
- const entry = state.toolParameters.get(id)
45
- state.prune.tools.set(id, entry?.tokenCount ?? 0)
46
- }
47
- }
48
-
49
- function createToolSignature(tool, params) {
50
- if (!params) return tool
51
- const norm = normalizeParams(params)
52
- const sorted = sortKeys(norm)
53
- return `${tool}::${JSON.stringify(sorted)}`
54
- }
55
-
56
- function normalizeParams(p) {
57
- if (typeof p !== "object" || p === null) return p
58
- if (Array.isArray(p)) return p
59
- const n = {}
60
- for (const [k, v] of Object.entries(p)) {
61
- if (v !== undefined && v !== null) n[k] = v
62
- }
63
- return n
64
- }
65
-
66
- function sortKeys(o) {
67
- if (typeof o !== "object" || o === null) return o
68
- if (Array.isArray(o)) return o.map(sortKeys)
69
- const s = {}
70
- for (const k of Object.keys(o).sort()) s[k] = sortKeys(o[k])
71
- return s
72
- }
@@ -1,2 +0,0 @@
1
- export { deduplicate } from "./deduplication.mjs"
2
- export { purgeErrors } from "./purge-errors.mjs"
@@ -1,43 +0,0 @@
1
- import { isToolNameProtected, getFilePathsFromParameters, isFilePathProtected } from "../protected-patterns.mjs"
2
- import { getTotalToolTokens } from "../token-utils.mjs"
3
-
4
- export function purgeErrors(state, config, messages) {
5
- if (state.manualMode && !config.manualMode?.automaticStrategies) return
6
-
7
- if (!config.strategies?.purgeErrors?.enabled) return
8
-
9
- const allIds = state.toolIdList
10
- if (!allIds?.length) return
11
-
12
- const unprunedIds = allIds.filter(id => !state.prune.tools.has(id))
13
- if (!unprunedIds.length) return
14
-
15
- const protectedTools = config.strategies.purgeErrors.protectedTools || []
16
- const threshold = Math.max(1, config.strategies.purgeErrors.turns ?? 4)
17
-
18
- const toPrune = []
19
- for (const id of unprunedIds) {
20
- const meta = state.toolParameters.get(id)
21
- if (!meta) continue
22
-
23
- if (isToolNameProtected(meta.tool, protectedTools)) continue
24
-
25
- const fps = getFilePathsFromParameters(meta.tool, meta.parameters)
26
- if (isFilePathProtected(fps, config.protectedFilePatterns)) continue
27
-
28
- if (meta.status !== "error") continue
29
-
30
- const turnAge = state.currentTurn - meta.turn
31
- if (turnAge >= threshold) {
32
- toPrune.push(id)
33
- }
34
- }
35
-
36
- if (!toPrune.length) return
37
-
38
- state.stats.totalPruneTokens += getTotalToolTokens(state, toPrune)
39
- for (const id of toPrune) {
40
- const entry = state.toolParameters.get(id)
41
- state.prune.tools.set(id, entry?.tokenCount ?? 0)
42
- }
43
- }
@@ -1,26 +0,0 @@
1
- import { totalTokens } from "./reaper.mjs"
2
-
3
- export { totalTokens }
4
-
5
- export function countTokens(value) {
6
- if (typeof value === "string") return Math.ceil(value.length / 4)
7
- if (typeof value === "object" && value !== null) return Math.ceil(JSON.stringify(value).length / 4)
8
- return 0
9
- }
10
-
11
- export function getTotalToolTokens(state, toolIds) {
12
- let total = 0
13
- for (const id of toolIds) {
14
- const entry = state.toolParameters.get(id)
15
- if (entry?.tokenCount) total += entry.tokenCount
16
- }
17
- return total
18
- }
19
-
20
- function estimateToolTokenCost(part) {
21
- if (part.type !== "tool") return 0
22
- let t = 0
23
- if (part.state?.input) t += countTokens(part.state.input)
24
- if (part.state?.output) t += countTokens(part.state.output)
25
- return t
26
- }
@@ -1,132 +0,0 @@
1
- import fs from "node:fs"
2
- import path from "node:path"
3
- import os from "node:os"
4
-
5
- const CONFIG_PATH = path.join(os.homedir(), ".config", "opencode", "opencode.json")
6
- const CACHE_ROOTS = [
7
- path.join(os.homedir(), ".cache", "opencode", "packages"),
8
- path.join(os.homedir(), ".cache", "opencode", "node_modules"),
9
- ]
10
-
11
- function detectInstallMethod() {
12
- let raw = {}
13
- try {
14
- raw = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"))
15
- } catch {
16
- return { method: "unknown", entry: null, reason: `cannot read ${CONFIG_PATH}` }
17
- }
18
-
19
- const plugins = Array.isArray(raw.plugin) ? raw.plugin : []
20
- for (const p of plugins) {
21
- const entry = typeof p === "string" ? p : (Array.isArray(p) && typeof p[0] === "string" ? p[0] : "")
22
- if (!entry.startsWith("openhermes")) continue
23
-
24
- if (entry.includes("@git+https://") || entry.includes("@git+ssh://")) {
25
- const repoUrl = entry.replace(/^openhermes@/, "")
26
- return { method: "git", entry, repoUrl }
27
- }
28
-
29
- if (entry === "openhermes") {
30
- return { method: "npm", entry }
31
- }
32
-
33
- return { method: "other", entry, reason: `unrecognized format: ${entry}` }
34
- }
35
-
36
- return { method: "unknown", entry: null, reason: "openhermes not found in plugin config" }
37
- }
38
-
39
- function walkCacheDirs(root, results) {
40
- if (!fs.existsSync(root)) return
41
- if (path.basename(root).startsWith("openhermes")) {
42
- results.push({ name: path.basename(root), path: root })
43
- }
44
- let entries = []
45
- try {
46
- entries = fs.readdirSync(root, { withFileTypes: true })
47
- } catch {
48
- return
49
- }
50
-
51
- for (const entry of entries) {
52
- if (!entry.isDirectory()) continue
53
- const full = path.join(root, entry.name)
54
- if (entry.name.startsWith("openhermes")) {
55
- results.push({ name: entry.name, path: full })
56
- }
57
- walkCacheDirs(full, results)
58
- }
59
- }
60
-
61
- export function findCacheDirs({ cacheRoots = CACHE_ROOTS } = {}) {
62
- const results = []
63
- for (const root of cacheRoots) walkCacheDirs(root, results)
64
- const seen = new Set()
65
- return results.filter(dir => {
66
- if (seen.has(dir.path)) return false
67
- seen.add(dir.path)
68
- return true
69
- })
70
- }
71
-
72
- async function handleUpdateMe(ctx, input, output) {
73
- const info = detectInstallMethod()
74
-
75
- if (info.method === "unknown") {
76
- output.parts.length = 0
77
- output.parts.push({
78
- type: "text",
79
- text: `[Update-Me] Could not detect installation method.\n${info.reason}\n\nEnsure 'openhermes' is in your opencode.json plugin list:\n https://github.com/nathwn12/openhermes#setup`,
80
- })
81
- return
82
- }
83
-
84
- const cacheDirs = findCacheDirs()
85
-
86
- if (cacheDirs.length === 0) {
87
- output.parts.length = 0
88
- output.parts.push({
89
- type: "text",
90
- text: `[Update-Me] No OpenHermes plugin cache found.\nRestart OpenCode to redownload from ${info.method === "git" ? "git HEAD" : "npm registry"}.`,
91
- })
92
- return
93
- }
94
-
95
- let clearedCount = 0
96
- let failedCount = 0
97
- let firstError = null
98
- for (const dir of cacheDirs) {
99
- try {
100
- fs.rmSync(dir.path, { recursive: true, force: true })
101
- clearedCount++
102
- } catch (e) {
103
- failedCount++
104
- if (!firstError) firstError = e.message
105
- }
106
- }
107
-
108
- output.parts.length = 0
109
- let msg = `[Update-Me] OpenHermes update (${info.method})\n`
110
-
111
- if (clearedCount > 0) {
112
- msg += `\n ✓ Cleared OpenHermes plugin cache (${clearedCount} entr${clearedCount === 1 ? "y" : "ies"}).`
113
- }
114
-
115
- if (failedCount > 0) {
116
- msg += `\n ⚠ ${failedCount} entr${failedCount === 1 ? "y" : "ies"} could not be removed (may be locked).`
117
- if (firstError) msg += `\n ${firstError}`
118
- }
119
-
120
- msg += `\n\nRestart OpenCode to load the latest OpenHermes from ${info.method === "git" ? "git HEAD" : "npm registry"}.`
121
-
122
- output.parts.push({ type: "text", text: msg })
123
- }
124
-
125
- export function UpdaterPlugin(ctx) {
126
- return {
127
- "command.execute.before": async (input, output) => {
128
- if (input.command !== "update-me") return
129
- await handleUpdateMe(ctx, input, output)
130
- },
131
- }
132
- }
package/lib/paths.mjs DELETED
@@ -1,49 +0,0 @@
1
- import path from "node:path"
2
- import os from "node:os"
3
- import fs from "node:fs"
4
- import { fileURLToPath } from "node:url"
5
- import { isTruthy } from "./hardening.mjs"
6
-
7
- const HOME = process.env.USERPROFILE || os.homedir()
8
- const DATA_ROOT = path.join(HOME, ".local", "share", "opencode", "openhermes")
9
- const CACHE_ROOT = path.join(HOME, ".cache", "opencode", "openhermes")
10
- const PKG_DIR = path.dirname(fileURLToPath(import.meta.url))
11
-
12
- function resolveRoot(envVar, fallback) {
13
- if (!isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) return fallback
14
- const cwd = process.cwd()
15
- const project = path.join(cwd, ".opencode", "openhermes")
16
- try { fs.accessSync(path.join(project, "memory")); return project } catch {}
17
- return fallback
18
- }
19
-
20
-
21
- export function getDataRoot() {
22
- return resolveRoot("OPENCODE_ALLOW_PROJECT_HARNESS", DATA_ROOT)
23
- }
24
-
25
- export function getCacheRoot() {
26
- return CACHE_ROOT
27
- }
28
-
29
- export function getMemoryRoot() {
30
- return path.join(getDataRoot(), "memory")
31
- }
32
-
33
- export function getRuntimeRoot() {
34
- return path.join(getDataRoot(), "runtime")
35
- }
36
-
37
- export function getRecallRoot() {
38
- return path.join(getCacheRoot(), "recall")
39
- }
40
-
41
- export function getArchiveRoot() {
42
- return path.join(getDataRoot(), "archive")
43
- }
44
-
45
- export function getSchemaRoot() {
46
- return path.resolve(PKG_DIR, "..", "schemas")
47
- }
48
-
49
-
@@ -1,79 +0,0 @@
1
- import { isPlainObject } from "./hardening.mjs"
2
-
3
- const SUPPORTED_SCHEMA_KEYS = new Set(["type", "const", "enum", "format", "minimum", "maximum", "required", "properties", "items"])
4
- const SCHEMA_METADATA_KEYS = new Set(["$schema", "title", "description", "default"])
5
-
6
- function matchesType(type, value) {
7
- if (type === "null") return value === null
8
- if (type === "array") return Array.isArray(value)
9
- if (type === "object") return isPlainObject(value)
10
- if (type === "integer") return Number.isInteger(value)
11
- if (type === "number") return typeof value === "number" && Number.isFinite(value)
12
- return typeof value === type
13
- }
14
-
15
- function validateNode(schema, value, at, errors) {
16
- if (!isPlainObject(schema) || value === undefined) return
17
- if (schema.const !== undefined && JSON.stringify(value) !== JSON.stringify(schema.const)) errors.push(`${at} must equal ${JSON.stringify(schema.const)}`)
18
- if (Array.isArray(schema.enum) && !schema.enum.some(option => JSON.stringify(option) === JSON.stringify(value))) errors.push(`${at} must be one of ${schema.enum.map(option => JSON.stringify(option)).join(", ")}`)
19
-
20
- const types = Array.isArray(schema.type) ? schema.type : (schema.type ? [schema.type] : [])
21
- if (types.length && !types.some(type => matchesType(type, value))) {
22
- errors.push(`${at} must be ${types.join(" or ")}`)
23
- return
24
- }
25
-
26
- if (typeof value === "string" && schema.format === "date-time" && Number.isNaN(Date.parse(value))) errors.push(`${at} must be a valid date-time string`)
27
- if (typeof value === "number") {
28
- if (schema.minimum !== undefined && value < schema.minimum) errors.push(`${at} must be >= ${schema.minimum}`)
29
- if (schema.maximum !== undefined && value > schema.maximum) errors.push(`${at} must be <= ${schema.maximum}`)
30
- }
31
-
32
- if (Array.isArray(value)) {
33
- value.forEach((item, index) => validateNode(schema.items, item, `${at}[${index}]`, errors))
34
- return
35
- }
36
-
37
- if (isPlainObject(value)) {
38
- for (const key of Array.isArray(schema.required) ? schema.required : []) {
39
- if (value[key] === undefined) errors.push(`${at}.${key} is required`)
40
- }
41
- for (const [key, child] of Object.entries(isPlainObject(schema.properties) ? schema.properties : {})) {
42
- if (value[key] !== undefined) validateNode(child, value[key], `${at}.${key}`, errors)
43
- }
44
- }
45
- }
46
-
47
- function validateSchema(schema, value, at = "$") {
48
- const errors = []
49
- validateNode(schema, value, at, errors)
50
- return errors
51
- }
52
-
53
- function visitSchemaNode(schema, at, unsupported) {
54
- if (!isPlainObject(schema)) return
55
- for (const [key, value] of Object.entries(schema)) {
56
- if (key === "properties") {
57
- if (!isPlainObject(value)) unsupported.push(`${at}.properties must be an object`)
58
- else for (const [propertyName, propertySchema] of Object.entries(value)) visitSchemaNode(propertySchema, `${at}.properties.${propertyName}`, unsupported)
59
- continue
60
- }
61
- if (key === "items") {
62
- visitSchemaNode(value, `${at}.items`, unsupported)
63
- continue
64
- }
65
- if (key === "$defs" || key === "definitions") {
66
- if (isPlainObject(value)) for (const defName of Object.keys(value)) visitSchemaNode(value[defName], `${at}.${key}.${defName}`, unsupported)
67
- continue
68
- }
69
- if (!SUPPORTED_SCHEMA_KEYS.has(key) && !SCHEMA_METADATA_KEYS.has(key)) unsupported.push(`${at} uses unsupported schema keyword "${key}"`)
70
- }
71
- }
72
-
73
- function findUnsupportedSchemaKeywords(schema) {
74
- const unsupported = []
75
- visitSchemaNode(schema, "$", unsupported)
76
- return unsupported
77
- }
78
-
79
- export { findUnsupportedSchemaKeywords, isPlainObject, validateSchema }