openhermes 1.12.1 → 2.5.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 (80) hide show
  1. package/README.md +126 -207
  2. package/autorecall.mjs +79 -12
  3. package/bootstrap.mjs +123 -24
  4. package/curator.mjs +4 -40
  5. package/harness/commands/harness-audit.md +1 -1
  6. package/harness/commands/learn.md +2 -2
  7. package/harness/commands/memory-search.md +2 -2
  8. package/harness/commands/ohc.md +13 -0
  9. package/harness/constitution/soul.md +16 -4
  10. package/harness/instructions/RUNTIME.md +6 -3
  11. package/harness/prompts/architect.txt +14 -0
  12. package/harness/prompts/build-cpp.md +15 -1
  13. package/harness/prompts/build-error-resolver.md +15 -9
  14. package/harness/prompts/build-go.md +14 -0
  15. package/harness/prompts/build-java.md +15 -1
  16. package/harness/prompts/build-kotlin.md +15 -1
  17. package/harness/prompts/build-rust.md +14 -0
  18. package/harness/prompts/code-reviewer.md +15 -9
  19. package/harness/prompts/doc-updater.md +13 -0
  20. package/harness/prompts/docs-lookup.md +11 -0
  21. package/harness/prompts/e2e-runner.txt +12 -0
  22. package/harness/prompts/explore.md +16 -4
  23. package/harness/prompts/harness-optimizer.md +12 -0
  24. package/harness/prompts/loop-operator.md +11 -0
  25. package/harness/prompts/planner.md +15 -9
  26. package/harness/prompts/refactor-cleaner.md +14 -0
  27. package/harness/prompts/review-cpp.md +14 -1
  28. package/harness/prompts/review-database.md +13 -0
  29. package/harness/prompts/review-go.md +13 -0
  30. package/harness/prompts/review-java.md +14 -1
  31. package/harness/prompts/review-kotlin.md +13 -0
  32. package/harness/prompts/review-python.md +14 -1
  33. package/harness/prompts/review-rust.md +13 -0
  34. package/harness/prompts/security-reviewer.md +15 -9
  35. package/harness/prompts/tdd-guide.md +14 -0
  36. package/harness/rules/audit.md +2 -2
  37. package/harness/rules/delegation.md +0 -2
  38. package/harness/rules/handoff.md +267 -0
  39. package/harness/rules/memory-management.md +4 -4
  40. package/harness/rules/precedence.md +1 -1
  41. package/harness/rules/retrieval.md +5 -5
  42. package/harness/rules/runtime-guards.md +1 -1
  43. package/harness/rules/self-heal.md +1 -1
  44. package/harness/rules/session-start.md +5 -5
  45. package/harness/rules/skills-management.md +2 -2
  46. package/harness/rules/verification.md +4 -4
  47. package/harness/scripts/sync-commands.mjs +259 -0
  48. package/index.mjs +6 -2
  49. package/lib/ambient-memory.mjs +167 -0
  50. package/lib/handoff.mjs +176 -0
  51. package/lib/hardening.mjs +13 -8
  52. package/lib/memory-tools-plugin.mjs +107 -54
  53. package/lib/ohc/block-sync.mjs +69 -0
  54. package/lib/ohc/compress/search.mjs +152 -0
  55. package/lib/ohc/compress/state.mjs +76 -0
  56. package/lib/ohc/config.mjs +172 -16
  57. package/lib/ohc/message-ids.mjs +168 -0
  58. package/lib/ohc/notify.mjs +150 -0
  59. package/lib/ohc/protected-patterns.mjs +54 -0
  60. package/lib/ohc/prune-apply.mjs +134 -0
  61. package/lib/ohc/pruner.mjs +406 -55
  62. package/lib/ohc/reaper.mjs +12 -3
  63. package/lib/ohc/state.mjs +246 -15
  64. package/lib/ohc/strategies/deduplication.mjs +72 -0
  65. package/lib/ohc/strategies/index.mjs +2 -0
  66. package/lib/ohc/strategies/purge-errors.mjs +43 -0
  67. package/lib/ohc/token-utils.mjs +26 -0
  68. package/lib/ohc/updater.mjs +36 -13
  69. package/lib/paths.mjs +0 -3
  70. package/lib/search.mjs +48 -0
  71. package/package.json +6 -2
  72. package/schemas/audit.schema.json +22 -1
  73. package/schemas/backlog.schema.json +23 -2
  74. package/schemas/checkpoint.schema.json +23 -2
  75. package/schemas/constraint.schema.json +23 -2
  76. package/schemas/decision.schema.json +23 -2
  77. package/schemas/instinct.schema.json +23 -2
  78. package/schemas/mistake.schema.json +23 -2
  79. package/schemas/verification_receipt.schema.json +23 -2
  80. package/skill-builder.mjs +12 -23
@@ -0,0 +1,150 @@
1
+ function formatTokenCount(tokens) {
2
+ if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`.replace(".0K", "K")
3
+ return String(tokens)
4
+ }
5
+
6
+ function buildProgressBar(prunedCount, visibleCount, width) {
7
+ width = width || 30
8
+ const total = prunedCount + visibleCount
9
+ if (total === 0) return `\u2502${"\u2591".repeat(width)}\u2502 0% active`
10
+ const activeRatio = visibleCount / total
11
+ const activeW = Math.round(activeRatio * width)
12
+ const prunedW = width - activeW
13
+ const bar = "\u2588".repeat(Math.min(activeW, width)) + "\u2591".repeat(Math.min(prunedW, width))
14
+ return `\u2502${bar.slice(0, width)}\u2502 ${Math.round(activeRatio * 100)}% active`
15
+ }
16
+
17
+ function buildMinimal(count, tokensRemoved, savedTotal, blockCount) {
18
+ const label = "Compression"
19
+ return `\u25A3 OHC | ~${formatTokenCount(savedTotal)} saved total \u2014 ${label}`
20
+ }
21
+
22
+ function buildDetailed(count, tokensRemoved, savedTotal, blockCount, prunedCount, visibleCount, summary) {
23
+ const label = "Compression"
24
+ let msg = `\u25A3 OHC | ~${formatTokenCount(savedTotal)} saved total`
25
+ if (prunedCount + visibleCount > 0) {
26
+ msg += `\n\n${buildProgressBar(prunedCount, visibleCount)}`
27
+ }
28
+ msg += `\n\n\u25A3 ${label} #${blockCount}`
29
+ msg += `\n\u2192 ${count} message${count === 1 ? "" : "s"} removed`
30
+ if (summary) msg += `\n\u2192 Summary: ${summary}`
31
+ return msg
32
+ }
33
+
34
+ function buildStrategyNotification(strategy, count, detail) {
35
+ return `\u25A3 OHC | ${strategy}: ${count} pruned${detail ? ` (${detail})` : ""}`
36
+ }
37
+
38
+ export async function sendCompressNotification(client, sessionId, config, count, summary, tokensRemoved, savedTotal, blockCount, prunedCount, visibleCount) {
39
+ if (count === 0) return false
40
+
41
+ const notifType = config.notification ?? "toast"
42
+ const notifMode = config.notificationMode ?? "minimal"
43
+
44
+ if (notifType === "off") return false
45
+
46
+ if (notifType === "toast") {
47
+ const message = notifMode === "minimal"
48
+ ? buildMinimal(count, tokensRemoved, savedTotal, blockCount)
49
+ : buildDetailed(count, tokensRemoved, savedTotal, blockCount, prunedCount, visibleCount, summary)
50
+ try {
51
+ await client.tui.showToast({
52
+ body: {
53
+ title: "OHC: Compression",
54
+ message,
55
+ variant: "info",
56
+ duration: 5000,
57
+ },
58
+ })
59
+ } catch {}
60
+ return true
61
+ }
62
+
63
+ try {
64
+ await client.session.prompt({
65
+ path: { id: sessionId },
66
+ body: {
67
+ noReply: true,
68
+ parts: [{ type: "text", text: buildDetailed(count, tokensRemoved, savedTotal, blockCount, prunedCount, visibleCount, summary), ignored: true }],
69
+ },
70
+ })
71
+ } catch {}
72
+ return true
73
+ }
74
+
75
+ export async function sendStrategyNotification(client, sessionId, config, strategy, count, detail) {
76
+ if (count === 0) return false
77
+ const notifType = config.notification ?? "toast"
78
+ if (notifType === "off") return false
79
+
80
+ const message = buildStrategyNotification(strategy, count, detail)
81
+
82
+ if (notifType === "toast") {
83
+ try {
84
+ await client.tui.showToast({
85
+ body: {
86
+ title: `OHC: ${strategy}`,
87
+ message,
88
+ variant: "info",
89
+ duration: 3000,
90
+ },
91
+ })
92
+ } catch {}
93
+ return true
94
+ }
95
+
96
+ try {
97
+ await client.session.prompt({
98
+ path: { id: sessionId },
99
+ body: {
100
+ noReply: true,
101
+ parts: [{ type: "text", text: message, ignored: true }],
102
+ },
103
+ })
104
+ } catch {}
105
+ return true
106
+ }
107
+
108
+ export async function sendMemoryNotification(client, sessionId, config, action, cls, id, summary) {
109
+ const notifType = config.notification ?? "toast"
110
+ const notifMode = config.notificationMode ?? "minimal"
111
+
112
+ if (notifType === "off") return false
113
+
114
+ const buildHeader = (action, summary) => `[Memory] ${summary}`
115
+ const buildDetailed = (action, cls, id, summary) => {
116
+ let msg = `\u25A3 Memory ${action}`
117
+ if (cls) msg += `\n\u2192 Class: ${cls}`
118
+ if (id) msg += `\n\u2192 ID: ${id}`
119
+ if (summary) msg += `\n\u2192 ${summary}`
120
+ return msg
121
+ }
122
+
123
+ if (notifType === "toast") {
124
+ const message = notifMode === "minimal"
125
+ ? buildHeader(action, summary)
126
+ : buildDetailed(action, cls, id, summary)
127
+ try {
128
+ await client.tui.showToast({
129
+ body: {
130
+ title: "Memory",
131
+ message,
132
+ variant: "info",
133
+ duration: 4000,
134
+ },
135
+ })
136
+ } catch {}
137
+ return true
138
+ }
139
+
140
+ try {
141
+ await client.session.prompt({
142
+ path: { id: sessionId },
143
+ body: {
144
+ noReply: true,
145
+ parts: [{ type: "text", text: buildDetailed(action, cls, id, summary), ignored: true }],
146
+ },
147
+ })
148
+ } catch {}
149
+ return true
150
+ }
@@ -0,0 +1,54 @@
1
+ const DEFAULT_PROTECTED_TOOLS = new Set([
2
+ "task", "skill", "todowrite", "todoread",
3
+ "compress", "batch", "plan_enter", "plan_exit",
4
+ "write", "edit",
5
+ ])
6
+
7
+ export function isToolNameProtected(toolName, extraProtected = []) {
8
+ if (DEFAULT_PROTECTED_TOOLS.has(toolName)) return true
9
+ return extraProtected.includes(toolName)
10
+ }
11
+
12
+ export function getFilePathsFromParameters(tool, params) {
13
+ if (!params || typeof params !== "object") return []
14
+ if (params.filePath) return [params.filePath]
15
+ if (params.file_path) return [params.file_path]
16
+ if (params.path) return [params.path]
17
+ if (params.target) return [params.target]
18
+ if (params.directory) return [params.directory]
19
+ if (tool === "glob" && params.pattern) return [params.pattern]
20
+ return []
21
+ }
22
+
23
+ export function isFilePathProtected(filePaths, protectedPatterns = []) {
24
+ if (!protectedPatterns?.length) return false
25
+ if (!filePaths?.length) return false
26
+ for (const fp of filePaths) {
27
+ for (const pattern of protectedPatterns) {
28
+ if (globMatch(fp, pattern)) return true
29
+ }
30
+ }
31
+ return false
32
+ }
33
+
34
+ function globMatch(filePath, pattern) {
35
+ const regexStr = pattern
36
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
37
+ .replace(/\*/g, ".*")
38
+ .replace(/\?/g, ".")
39
+ try {
40
+ return new RegExp(`^${regexStr}$`, "i").test(filePath)
41
+ } catch {
42
+ return filePath.toLowerCase() === pattern.toLowerCase()
43
+ }
44
+ }
45
+
46
+ export function getTurnProtectionTags(state) {
47
+ const config = state.protectedTurns || {}
48
+ if (!config.enabled || !config.turns) return []
49
+ const threshold = state.currentTurn - config.turns
50
+ return [...state.toolIdList].filter(id => {
51
+ const entry = state.toolParameters.get(id)
52
+ return entry && entry.turn > threshold
53
+ })
54
+ }
@@ -0,0 +1,134 @@
1
+ const PRUNED_OUTPUT_PLACEHOLDER = "[Output removed — information superseded or no longer needed]"
2
+ const PRUNED_ERROR_INPUT_PLACEHOLDER = "[input removed — failed tool call]"
3
+ const PRUNED_QUESTION_INPUT_PLACEHOLDER = "[questions removed — see output for answers]"
4
+
5
+ export function applyPruneTools(state, messages) {
6
+ if (!state.prune.tools.size) return 0
7
+
8
+ let prunedCount = 0
9
+
10
+ for (const msg of messages) {
11
+ if (!Array.isArray(msg.parts)) continue
12
+
13
+ for (const part of msg.parts) {
14
+ if (part.type !== "tool" || !part.callID) continue
15
+ if (!state.prune.tools.has(part.callID)) continue
16
+
17
+ prunedCount++
18
+
19
+ if (part.state?.status === "completed") {
20
+ if (part.tool === "question") {
21
+ if (part.state.input?.questions !== undefined) {
22
+ part.state.input.questions = PRUNED_QUESTION_INPUT_PLACEHOLDER
23
+ }
24
+ } else if (part.tool !== "edit" && part.tool !== "write") {
25
+ part.state = {
26
+ ...part.state,
27
+ output: PRUNED_OUTPUT_PLACEHOLDER,
28
+ }
29
+ }
30
+ }
31
+
32
+ if (part.state?.status === "error") {
33
+ const input = part.state.input
34
+ if (input && typeof input === "object") {
35
+ for (const key of Object.keys(input)) {
36
+ if (typeof input[key] === "string") {
37
+ input[key] = PRUNED_ERROR_INPUT_PLACEHOLDER
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ return prunedCount
46
+ }
47
+
48
+ export function filterCompressedBlocks(state, messages) {
49
+ const ms = state.prune?.messages
50
+ if (!ms || !ms.activeBlockIds?.size || !ms.blocksById?.size) return { removed: 0, injected: 0 }
51
+
52
+ const coveredMessageIds = new Set()
53
+ for (const [rawId, entry] of ms.byMessageId) {
54
+ const hasActive = Array.isArray(entry.activeBlockIds) && entry.activeBlockIds.some(id => ms.activeBlockIds.has(id))
55
+ if (hasActive) coveredMessageIds.add(rawId)
56
+ }
57
+
58
+ if (!coveredMessageIds.size) return { removed: 0, injected: 0 }
59
+
60
+ const activeBlocks = [...ms.activeBlockIds].map(id => ms.blocksById.get(id)).filter(Boolean)
61
+ const summaryByAnchor = new Map()
62
+ for (const block of activeBlocks) {
63
+ if (block.anchorMessageId && !summaryByAnchor.has(block.anchorMessageId)) {
64
+ summaryByAnchor.set(block.anchorMessageId, block)
65
+ }
66
+ }
67
+
68
+ let removed = 0
69
+ let injected = 0
70
+ const result = []
71
+ const blocksDeployed = new Set()
72
+
73
+ for (const msg of messages) {
74
+ const mid = msg.info?.id
75
+
76
+ if (mid && coveredMessageIds.has(mid)) {
77
+ if (!blocksDeployed.has(mid)) {
78
+ blocksDeployed.add(mid)
79
+ const block = summaryByAnchor.get(mid)
80
+ if (block) {
81
+ result.push({
82
+ parts: [{ type: "text", text: block.summary }],
83
+ info: { role: "system" },
84
+ })
85
+ injected++
86
+ }
87
+ }
88
+ removed++
89
+ continue
90
+ }
91
+
92
+ result.push(msg)
93
+ }
94
+
95
+ messages.length = 0
96
+ messages.push(...result)
97
+
98
+ return { removed, injected }
99
+ }
100
+
101
+ export function applyFullToolRemoval(state, messages) {
102
+ if (!state.prune.tools.size) return 0
103
+
104
+ const removed = []
105
+
106
+ for (const msg of messages) {
107
+ if (!Array.isArray(msg.parts)) continue
108
+
109
+ const toRemove = []
110
+ for (const part of msg.parts) {
111
+ if (part.type !== "tool" || !part.callID) continue
112
+ if (!state.prune.tools.has(part.callID)) continue
113
+ if (part.tool !== "edit" && part.tool !== "write") continue
114
+ toRemove.push(part.callID)
115
+ }
116
+
117
+ if (!toRemove.length) continue
118
+
119
+ const before = msg.parts.length
120
+ msg.parts = msg.parts.filter(p => p.type !== "tool" || !toRemove.includes(p.callID))
121
+ const after = msg.parts.length
122
+ if (after === 0 && before > 0) {
123
+ removed.push(msg.info.id)
124
+ }
125
+ }
126
+
127
+ if (removed.length) {
128
+ const result = messages.filter(m => !removed.includes(m.info.id))
129
+ messages.length = 0
130
+ messages.push(...result)
131
+ }
132
+
133
+ return removed.length
134
+ }