openhermes 2.6.1 → 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 -499
  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 -58
  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 -455
  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 -176
  128. package/lib/hardening.mjs +0 -128
  129. package/lib/memory-tools-plugin.mjs +0 -365
  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 -186
  134. package/lib/ohc/message-ids.mjs +0 -168
  135. package/lib/ohc/notify.mjs +0 -154
  136. package/lib/ohc/protected-patterns.mjs +0 -54
  137. package/lib/ohc/prune-apply.mjs +0 -134
  138. package/lib/ohc/pruner.mjs +0 -610
  139. package/lib/ohc/reaper.mjs +0 -70
  140. package/lib/ohc/state.mjs +0 -266
  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 -133
  146. package/lib/paths.mjs +0 -50
  147. package/lib/schema-validator.mjs +0 -77
  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,266 +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
- lastDurationMs: state.compressionTiming?.lastDurationMs || 0,
154
- totalDurationMs: state.compressionTiming?.totalDurationMs || 0,
155
- },
156
- }
157
- }
158
-
159
- export function deserializeState(saved) {
160
- const state = createSessionState()
161
- if (!saved) return state
162
- state.sessionId = saved.sessionId || null
163
- state.manualMode = saved.manualMode || false
164
- state.lastCompaction = saved.lastCompaction || 0
165
- state.currentTurn = saved.currentTurn || 0
166
- state.modelContextLimit = saved.modelContextLimit
167
- state.systemPromptTokens = saved.systemPromptTokens
168
- if (saved.stats) Object.assign(state.stats, saved.stats)
169
- if (saved.nudges) {
170
- state.nudges.contextLimitAnchors = setFromArr(saved.nudges.contextLimitAnchors)
171
- state.nudges.turnNudgeAnchors = setFromArr(saved.nudges.turnNudgeAnchors)
172
- state.nudges.iterationNudgeAnchors = setFromArr(saved.nudges.iterationNudgeAnchors)
173
- }
174
- if (saved.prune?.tools) state.prune.tools = pruneMapFromObj(saved.prune.tools)
175
- if (saved.prune?.messages) {
176
- const pm = saved.prune.messages
177
- state.prune.messages.nextBlockId = pm.nextBlockId || 1
178
- state.prune.messages.nextRunId = pm.nextRunId || 1
179
- state.prune.messages.blocksById = pruneMapFromObj(pm.blocksById)
180
- state.prune.messages.byMessageId = pruneMapFromObj(pm.byMessageId)
181
- state.prune.messages.activeBlockIds = setFromArr(pm.activeBlockIds)
182
- }
183
- state.lastAutoPruneAt = saved.lastAutoPruneAt || null
184
- state.totalTokensSaved = saved.totalTokensSaved || 0
185
- state.totalMessagesRemoved = saved.totalMessagesRemoved || 0
186
- state.blockCount = saved.blockCount || 0
187
- state.summary = saved.summary || null
188
- state.anchorMessageId = saved.anchorMessageId || null
189
- state.prunedIds = setFromArr(saved.prunedIds)
190
- if (saved.compressionTiming) {
191
- state.compressionTiming.starts = pruneMapFromObj(saved.compressionTiming.starts)
192
- state.compressionTiming.lastDurationMs = saved.compressionTiming.lastDurationMs || 0
193
- state.compressionTiming.totalDurationMs = saved.compressionTiming.totalDurationMs || 0
194
- }
195
- if (saved.messageIds) {
196
- state.messageIds.byRawId = pruneMapFromObj(saved.messageIds.byRawId)
197
- state.messageIds.byRef = pruneMapFromObj(saved.messageIds.byRef)
198
- state.messageIds.nextRef = saved.messageIds.nextRef || 1
199
- }
200
- if (saved.isSubAgent !== undefined) state.isSubAgent = saved.isSubAgent
201
- return state
202
- }
203
-
204
- export function buildToolIdList(state, messages) {
205
- const ids = []
206
- for (const msg of messages) {
207
- if (!Array.isArray(msg.parts)) continue
208
- for (const part of msg.parts) {
209
- if (part.type === "tool" && part.callID) {
210
- ids.push(part.callID)
211
- }
212
- }
213
- }
214
- state.toolIdList = ids
215
- return ids
216
- }
217
-
218
- export function syncToolCache(state, messages) {
219
- let maxTurn = 0
220
- for (const msg of messages) {
221
- if (msg.info?.role === "user") {
222
- const lastUser = state.toolIdList.length > 0
223
- if (lastUser) maxTurn++
224
- }
225
- if (!Array.isArray(msg.parts)) continue
226
- for (const part of msg.parts) {
227
- if (part.type !== "tool" || !part.callID) continue
228
- const existing = state.toolParameters.get(part.callID)
229
- if (existing) {
230
- existing.status = part.state?.status || existing.status
231
- continue
232
- }
233
- state.toolParameters.set(part.callID, {
234
- tool: part.tool || "unknown",
235
- parameters: part.state?.input || {},
236
- status: part.state?.status || "pending",
237
- turn: maxTurn,
238
- tokenCount: estimateToolTokens(part),
239
- lastSeen: Date.now(),
240
- })
241
- }
242
- }
243
- state.currentTurn = Math.max(state.currentTurn, maxTurn)
244
- }
245
-
246
- function estimateToolTokens(part) {
247
- if (!part.state) return 0
248
- let t = 0
249
- if (part.state.input) t += JSON.stringify(part.state.input).length / 4
250
- if (part.state.output) {
251
- t += (typeof part.state.output === "string" ? part.state.output : JSON.stringify(part.state.output ?? "")).length / 4
252
- }
253
- return Math.ceil(t)
254
- }
255
-
256
- export function countTurns(state, messages) {
257
- let userCount = 0
258
- for (const msg of messages) {
259
- if (msg.info?.role === "user") {
260
- const parts = Array.isArray(msg.parts) ? msg.parts : []
261
- const hasText = parts.some(p => p.type === "text" && p.text?.trim())
262
- if (hasText) userCount++
263
- }
264
- }
265
- return userCount
266
- }
@@ -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,133 +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 cache found under OpenCode package/node_modules caches.
91
- Restart OpenCode to redownload from ${info.method === "git" ? "git HEAD" : "npm registry"}.`,
92
- })
93
- return
94
- }
95
-
96
- const deleted = []
97
- const failed = []
98
- for (const dir of cacheDirs) {
99
- try {
100
- fs.rmSync(dir.path, { recursive: true, force: true })
101
- deleted.push(dir.name)
102
- } catch (e) {
103
- failed.push({ name: dir.name, error: e.message })
104
- }
105
- }
106
-
107
- output.parts.length = 0
108
- let msg = `[Update-Me] OpenHermes update (${info.method})\n`
109
-
110
- if (deleted.length > 0) {
111
- msg += `\nCleared:\n`
112
- for (const d of deleted) msg += ` ✓ ${d}\n`
113
- }
114
-
115
- if (failed.length > 0) {
116
- msg += `\n⚠ Could not remove (file may be locked):\n`
117
- for (const f of failed) msg += ` ✗ ${f.name} — ${f.error}\n`
118
- msg += `Try deleting manually:\n ${CACHE_ROOTS.join("\n ")}\n`
119
- }
120
-
121
- msg += `\nRestart OpenCode to load the latest OpenHermes from ${info.method === "git" ? "git HEAD" : "npm registry"}.`
122
-
123
- output.parts.push({ type: "text", text: msg })
124
- }
125
-
126
- export function UpdaterPlugin(ctx) {
127
- return {
128
- "command.execute.before": async (input, output) => {
129
- if (input.command !== "update-me") return
130
- await handleUpdateMe(ctx, input, output)
131
- },
132
- }
133
- }
package/lib/paths.mjs DELETED
@@ -1,50 +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
-
6
- const HOME = process.env.USERPROFILE || os.homedir()
7
- const DATA_ROOT = path.join(HOME, ".local", "share", "opencode", "openhermes")
8
- const CACHE_ROOT = path.join(HOME, ".cache", "opencode", "openhermes")
9
- const PKG_DIR = path.dirname(fileURLToPath(import.meta.url))
10
-
11
- function resolveRoot(envVar, fallback) {
12
- if (!isTruthy(process.env.OPENCODE_ALLOW_PROJECT_HARNESS)) return fallback
13
- const cwd = process.cwd()
14
- const project = path.join(cwd, ".opencode", "openhermes")
15
- try { fs.accessSync(path.join(project, "memory")); return project } catch {}
16
- return fallback
17
- }
18
-
19
-
20
- export function getDataRoot() {
21
- return resolveRoot("OPENCODE_ALLOW_PROJECT_HARNESS", DATA_ROOT)
22
- }
23
-
24
- export function getCacheRoot() {
25
- return CACHE_ROOT
26
- }
27
-
28
- export function getMemoryRoot() {
29
- return path.join(getDataRoot(), "memory")
30
- }
31
-
32
- export function getRuntimeRoot() {
33
- return path.join(getDataRoot(), "runtime")
34
- }
35
-
36
- export function getRecallRoot() {
37
- return path.join(getCacheRoot(), "recall")
38
- }
39
-
40
- export function getArchiveRoot() {
41
- return path.join(getDataRoot(), "archive")
42
- }
43
-
44
- export function getSchemaRoot() {
45
- return path.resolve(PKG_DIR, "..", "schemas")
46
- }
47
-
48
- function isTruthy(value) {
49
- return /^(1|true|yes|on)$/i.test(String(value || ""))
50
- }
@@ -1,77 +0,0 @@
1
- const SUPPORTED_SCHEMA_KEYS = new Set(["type", "const", "enum", "format", "minimum", "maximum", "required", "properties", "items"])
2
- const SCHEMA_METADATA_KEYS = new Set(["$schema", "title", "description", "default"])
3
-
4
- function isPlainObject(value) {
5
- return !!value && typeof value === "object" && !Array.isArray(value)
6
- }
7
-
8
- function matchesType(type, value) {
9
- if (type === "null") return value === null
10
- if (type === "array") return Array.isArray(value)
11
- if (type === "object") return isPlainObject(value)
12
- if (type === "integer") return Number.isInteger(value)
13
- if (type === "number") return typeof value === "number" && Number.isFinite(value)
14
- return typeof value === type
15
- }
16
-
17
- function validateNode(schema, value, at, errors) {
18
- if (!isPlainObject(schema) || value === undefined) return
19
- if (schema.const !== undefined && JSON.stringify(value) !== JSON.stringify(schema.const)) errors.push(`${at} must equal ${JSON.stringify(schema.const)}`)
20
- 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(", ")}`)
21
-
22
- const types = Array.isArray(schema.type) ? schema.type : (schema.type ? [schema.type] : [])
23
- if (types.length && !types.some(type => matchesType(type, value))) {
24
- errors.push(`${at} must be ${types.join(" or ")}`)
25
- return
26
- }
27
-
28
- if (typeof value === "string" && schema.format === "date-time" && Number.isNaN(Date.parse(value))) errors.push(`${at} must be a valid date-time string`)
29
- if (typeof value === "number" || Number.isInteger(value)) {
30
- if (schema.minimum !== undefined && value < schema.minimum) errors.push(`${at} must be >= ${schema.minimum}`)
31
- if (schema.maximum !== undefined && value > schema.maximum) errors.push(`${at} must be <= ${schema.maximum}`)
32
- }
33
-
34
- if (Array.isArray(value)) {
35
- value.forEach((item, index) => validateNode(schema.items, item, `${at}[${index}]`, errors))
36
- return
37
- }
38
-
39
- if (isPlainObject(value)) {
40
- for (const key of Array.isArray(schema.required) ? schema.required : []) {
41
- if (value[key] === undefined) errors.push(`${at}.${key} is required`)
42
- }
43
- for (const [key, child] of Object.entries(isPlainObject(schema.properties) ? schema.properties : {})) {
44
- if (value[key] !== undefined) validateNode(child, value[key], `${at}.${key}`, errors)
45
- }
46
- }
47
- }
48
-
49
- function validateSchema(schema, value, at = "$") {
50
- const errors = []
51
- validateNode(schema, value, at, errors)
52
- return errors
53
- }
54
-
55
- function visitSchemaNode(schema, at, unsupported) {
56
- if (!isPlainObject(schema)) return
57
- for (const [key, value] of Object.entries(schema)) {
58
- if (key === "properties") {
59
- if (!isPlainObject(value)) unsupported.push(`${at}.properties must be an object`)
60
- else for (const [propertyName, propertySchema] of Object.entries(value)) visitSchemaNode(propertySchema, `${at}.properties.${propertyName}`, unsupported)
61
- continue
62
- }
63
- if (key === "items") {
64
- visitSchemaNode(value, `${at}.items`, unsupported)
65
- continue
66
- }
67
- if (!SUPPORTED_SCHEMA_KEYS.has(key) && !SCHEMA_METADATA_KEYS.has(key)) unsupported.push(`${at} uses unsupported schema keyword "${key}"`)
68
- }
69
- }
70
-
71
- function findUnsupportedSchemaKeywords(schema) {
72
- const unsupported = []
73
- visitSchemaNode(schema, "$", unsupported)
74
- return unsupported
75
- }
76
-
77
- export { findUnsupportedSchemaKeywords, isPlainObject, validateSchema }