openhermes 2.6.0 → 2.6.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 +0 -11
- package/lib/ohc/compress/state.mjs +1 -1
- package/lib/ohc/notify.mjs +13 -9
- package/lib/ohc/pruner.mjs +30 -10
- package/lib/ohc/reaper.mjs +1 -1
- package/lib/ohc/state.mjs +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -259,17 +259,6 @@ openhermes/
|
|
|
259
259
|
|
|
260
260
|
---
|
|
261
261
|
|
|
262
|
-
## Environment Variables
|
|
263
|
-
|
|
264
|
-
Two knobs. That's it.
|
|
265
|
-
|
|
266
|
-
| Variable | Default | Effect |
|
|
267
|
-
|----------|---------|--------|
|
|
268
|
-
| `OPENCODE_ALLOW_PROJECT_HARNESS` | `false` | Enable project-local harness at `.opencode/openhermes/` |
|
|
269
|
-
| `OPENCODE_CURATOR_LOGS` | `false` | Pipe curator diagnostics to stderr for debugging |
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
262
|
## Why OpenHermes ≠ Hermes Agent
|
|
274
263
|
|
|
275
264
|
Same messenger emoji. Entirely different mediums.
|
|
@@ -33,7 +33,7 @@ export function applyCompressionState(state, input, selection, anchorMessageId,
|
|
|
33
33
|
endId: input.endId,
|
|
34
34
|
summary: storedSummary,
|
|
35
35
|
summaryTokens: input.summaryTokens || 0,
|
|
36
|
-
compressedTokens: 0,
|
|
36
|
+
compressedTokens: input.compressedTokens || 0,
|
|
37
37
|
consumedBlockIds: Array.isArray(consumedBlockIds) ? consumedBlockIds : [],
|
|
38
38
|
deactivatedByBlockId: undefined,
|
|
39
39
|
deactivatedByUser: false,
|
package/lib/ohc/notify.mjs
CHANGED
|
@@ -3,11 +3,11 @@ function formatTokenCount(tokens) {
|
|
|
3
3
|
return String(tokens)
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
function buildProgressBar(
|
|
6
|
+
function buildProgressBar(totalMessagesRemoved, currentMessageCount, width) {
|
|
7
7
|
width = width || 30
|
|
8
|
-
const total =
|
|
8
|
+
const total = totalMessagesRemoved + currentMessageCount
|
|
9
9
|
if (total === 0) return `\u2502${"\u2591".repeat(width)}\u2502 0% active`
|
|
10
|
-
const activeRatio =
|
|
10
|
+
const activeRatio = currentMessageCount / total
|
|
11
11
|
const activeW = Math.round(activeRatio * width)
|
|
12
12
|
const prunedW = width - activeW
|
|
13
13
|
const bar = "\u2588".repeat(Math.min(activeW, width)) + "\u2591".repeat(Math.min(prunedW, width))
|
|
@@ -19,11 +19,11 @@ function buildMinimal(count, tokensRemoved, savedTotal, blockCount) {
|
|
|
19
19
|
return `\u25A3 OHC | ~${formatTokenCount(savedTotal)} saved total \u2014 ${label}`
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function buildDetailed(count, tokensRemoved, savedTotal, blockCount,
|
|
22
|
+
function buildDetailed(count, tokensRemoved, savedTotal, blockCount, totalMessagesRemoved, currentMessageCount, summary) {
|
|
23
23
|
const label = "Compression"
|
|
24
24
|
let msg = `\u25A3 OHC | ~${formatTokenCount(savedTotal)} saved total`
|
|
25
|
-
if (
|
|
26
|
-
msg += `\n\n${buildProgressBar(
|
|
25
|
+
if (totalMessagesRemoved + currentMessageCount > 0) {
|
|
26
|
+
msg += `\n\n${buildProgressBar(totalMessagesRemoved, currentMessageCount)}`
|
|
27
27
|
}
|
|
28
28
|
msg += `\n\n\u25A3 ${label} #${blockCount}`
|
|
29
29
|
msg += `\n\u2192 ${count} message${count === 1 ? "" : "s"} removed`
|
|
@@ -35,9 +35,13 @@ function buildStrategyNotification(strategy, count, detail) {
|
|
|
35
35
|
return `\u25A3 OHC | ${strategy}: ${count} pruned${detail ? ` (${detail})` : ""}`
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export async function sendCompressNotification(client, sessionId, config, count, summary, tokensRemoved,
|
|
38
|
+
export async function sendCompressNotification(client, sessionId, config, count, summary, tokensRemoved, ss, currentMessageCount) {
|
|
39
39
|
if (count === 0) return false
|
|
40
40
|
|
|
41
|
+
const savedTotal = ss?.totalTokensSaved || 0
|
|
42
|
+
const blockCount = ss?.blockCount || 0
|
|
43
|
+
const totalMessagesRemoved = ss?.totalMessagesRemoved || 0
|
|
44
|
+
|
|
41
45
|
const notifType = config.notification ?? "toast"
|
|
42
46
|
const notifMode = config.notificationMode ?? "minimal"
|
|
43
47
|
|
|
@@ -46,7 +50,7 @@ export async function sendCompressNotification(client, sessionId, config, count,
|
|
|
46
50
|
if (notifType === "toast") {
|
|
47
51
|
const message = notifMode === "minimal"
|
|
48
52
|
? buildMinimal(count, tokensRemoved, savedTotal, blockCount)
|
|
49
|
-
: buildDetailed(count, tokensRemoved, savedTotal, blockCount,
|
|
53
|
+
: buildDetailed(count, tokensRemoved, savedTotal, blockCount, totalMessagesRemoved, currentMessageCount, summary)
|
|
50
54
|
try {
|
|
51
55
|
await client.tui.showToast({
|
|
52
56
|
body: {
|
|
@@ -65,7 +69,7 @@ export async function sendCompressNotification(client, sessionId, config, count,
|
|
|
65
69
|
path: { id: sessionId },
|
|
66
70
|
body: {
|
|
67
71
|
noReply: true,
|
|
68
|
-
parts: [{ type: "text", text: buildDetailed(count, tokensRemoved, savedTotal, blockCount,
|
|
72
|
+
parts: [{ type: "text", text: buildDetailed(count, tokensRemoved, savedTotal, blockCount, totalMessagesRemoved, currentMessageCount, summary), ignored: true }],
|
|
69
73
|
},
|
|
70
74
|
})
|
|
71
75
|
} catch {}
|
package/lib/ohc/pruner.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin"
|
|
2
2
|
import { loadConfig } from "./config.mjs"
|
|
3
|
-
import { selectMessagesToReap, totalTokens } from "./reaper.mjs"
|
|
3
|
+
import { selectMessagesToReap, totalTokens, msgTokens } from "./reaper.mjs"
|
|
4
4
|
import {
|
|
5
5
|
loadOhcState, saveOhcState, createSessionState,
|
|
6
6
|
serializeState, deserializeState,
|
|
@@ -75,7 +75,6 @@ async function applyCompress(ctx, sessionId, summary, max, min, targetTokens) {
|
|
|
75
75
|
for (const r of selected) ss.prunedIds.add(r.id)
|
|
76
76
|
ss.summary = summarizeRemoved(selected, summary)
|
|
77
77
|
ss.anchorMessageId = selected[0].id
|
|
78
|
-
saveOhcState(sessionId, serializeState(ss))
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
const tokensRemoved = selected.reduce((s, r) => s + r.tokens, 0)
|
|
@@ -84,6 +83,8 @@ async function applyCompress(ctx, sessionId, summary, max, min, targetTokens) {
|
|
|
84
83
|
if (ss) {
|
|
85
84
|
ss.blockCount++
|
|
86
85
|
ss.totalTokensSaved += tokensRemoved
|
|
86
|
+
ss.totalMessagesRemoved += selected.length
|
|
87
|
+
saveOhcState(sessionId, serializeState(ss))
|
|
87
88
|
}
|
|
88
89
|
return { removed: selected.length, afterTotal, tokensRemoved, beforeTotal, beforeCount: msgs.length, afterCount: msgs.length - selected.length }
|
|
89
90
|
}
|
|
@@ -133,12 +134,21 @@ async function executeRangeCompress(ctx, sessionId, callId, topic, content) {
|
|
|
133
134
|
|
|
134
135
|
const runId = allocateRunId(ss)
|
|
135
136
|
const notifications = []
|
|
137
|
+
let totalActualTokensRemoved = 0
|
|
138
|
+
const allMessageIds = []
|
|
136
139
|
|
|
137
140
|
for (const plan of plans) {
|
|
138
141
|
const blockId = allocateBlockId(ss)
|
|
139
142
|
const storedSummary = wrapBlockSummary(blockId, plan.entry.summary)
|
|
140
143
|
const summaryTokens = Math.ceil(storedSummary.length / 4)
|
|
141
144
|
|
|
145
|
+
const actualTokensRemoved = plan.selection.messageIds.reduce((sum, mid) => {
|
|
146
|
+
const msg = searchContext.rawMessagesById.get(mid)
|
|
147
|
+
return msg ? sum + msgTokens(msg) : sum
|
|
148
|
+
}, 0)
|
|
149
|
+
totalActualTokensRemoved += actualTokensRemoved
|
|
150
|
+
allMessageIds.push(...plan.selection.messageIds)
|
|
151
|
+
|
|
142
152
|
applyCompressionState(
|
|
143
153
|
ss,
|
|
144
154
|
{
|
|
@@ -151,6 +161,7 @@ async function executeRangeCompress(ctx, sessionId, callId, topic, content) {
|
|
|
151
161
|
compressMessageId: plan.selection.messageIds[0],
|
|
152
162
|
compressCallId: callId,
|
|
153
163
|
summaryTokens,
|
|
164
|
+
compressedTokens: actualTokensRemoved,
|
|
154
165
|
},
|
|
155
166
|
plan.selection,
|
|
156
167
|
plan.anchorMessageId,
|
|
@@ -160,7 +171,8 @@ async function executeRangeCompress(ctx, sessionId, callId, topic, content) {
|
|
|
160
171
|
)
|
|
161
172
|
|
|
162
173
|
ss.blockCount++
|
|
163
|
-
ss.totalTokensSaved +=
|
|
174
|
+
ss.totalTokensSaved += actualTokensRemoved
|
|
175
|
+
ss.totalMessagesRemoved += plan.selection.messageIds.length
|
|
164
176
|
|
|
165
177
|
notifications.push({
|
|
166
178
|
blockId,
|
|
@@ -170,11 +182,14 @@ async function executeRangeCompress(ctx, sessionId, callId, topic, content) {
|
|
|
170
182
|
})
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
saveOhcState(sessionId, serializeState(ss))
|
|
186
|
+
|
|
173
187
|
return {
|
|
174
|
-
messageIds:
|
|
175
|
-
compressedTokens:
|
|
188
|
+
messageIds: allMessageIds,
|
|
189
|
+
compressedTokens: totalActualTokensRemoved,
|
|
176
190
|
summaryRef: content[0]?.summary || topic,
|
|
177
191
|
blockCount: plans.length,
|
|
192
|
+
afterCount: rawMessages.length - allMessageIds.length,
|
|
178
193
|
}
|
|
179
194
|
}
|
|
180
195
|
|
|
@@ -283,10 +298,11 @@ export const OhcPlugin = async (ctx) => {
|
|
|
283
298
|
for (const r of selected) ss.prunedIds.add(r.id)
|
|
284
299
|
if (!ss.summary) ss.summary = summarizeRemoved(selected, null)
|
|
285
300
|
if (!ss.anchorMessageId) ss.anchorMessageId = selected[0].id
|
|
286
|
-
saveOhcState(sessionId, serializeState(ss))
|
|
287
301
|
const tokensRemoved = selected.reduce((s, r) => s + r.tokens, 0)
|
|
288
302
|
ss.blockCount++
|
|
289
303
|
ss.totalTokensSaved += tokensRemoved
|
|
304
|
+
ss.totalMessagesRemoved += selected.length
|
|
305
|
+
saveOhcState(sessionId, serializeState(ss))
|
|
290
306
|
ss.lastAutoPruneAt = now
|
|
291
307
|
ss._pruneCycleDone = true
|
|
292
308
|
}
|
|
@@ -416,7 +432,11 @@ export const OhcPlugin = async (ctx) => {
|
|
|
416
432
|
const timing = buildTimingStr(ss)
|
|
417
433
|
const activeBlockIds = [...(ss?.prune?.messages?.activeBlockIds || [])].filter(id => Number.isInteger(id)).sort((a, b) => a - b)
|
|
418
434
|
const blockLine = activeBlockIds.length ? ` Blocks: bk${activeBlockIds.join(", bk")}.` : ""
|
|
419
|
-
const
|
|
435
|
+
const summaryBufferTotal = config.compress?.summaryBuffer
|
|
436
|
+
? estimateSummaryTokens(msgs)
|
|
437
|
+
: 0
|
|
438
|
+
const effectiveMax = max + summaryBufferTotal
|
|
439
|
+
const text = `[OHC Status] ${msgs.length} messages visible (${prunedCount} auto-pruned, ${strategyPruned} strategy-pruned)${blockLine}${timing}. ~${Math.round(t / 1000)}K / ${effectiveMax.toLocaleString()} tokens (${Math.round((t / effectiveMax) * 100)}%). Soft floor: ${min.toLocaleString()}.`
|
|
420
440
|
await ctx.client.session.prompt({
|
|
421
441
|
path: { id: input.sessionID },
|
|
422
442
|
body: { noReply: true, parts: [{ type: "text", text, ignored: true }] },
|
|
@@ -475,7 +495,7 @@ export const OhcPlugin = async (ctx) => {
|
|
|
475
495
|
try {
|
|
476
496
|
const result = await applyCompress(ctx, input.sessionID, focus, max, min, targetTokens)
|
|
477
497
|
const cmdSs = getOrCreateState(input.sessionID)
|
|
478
|
-
await sendCompressNotification(ctx.client, input.sessionID, config, result.removed, focus, result.tokensRemoved, cmdSs
|
|
498
|
+
await sendCompressNotification(ctx.client, input.sessionID, config, result.removed, focus, result.tokensRemoved, cmdSs, result.afterCount)
|
|
479
499
|
output.parts.length = 0
|
|
480
500
|
output.parts.push({
|
|
481
501
|
type: "text",
|
|
@@ -574,14 +594,14 @@ export const OhcPlugin = async (ctx) => {
|
|
|
574
594
|
const result = await executeRangeCompress(ctx, sessionId, callId, args.topic || "Compression", args.content)
|
|
575
595
|
toolCtx.metadata({ title: "Compress Range" })
|
|
576
596
|
const resultSs = getOrCreateState(sessionId)
|
|
577
|
-
await sendCompressNotification(ctx.client, sessionId, config, result.messageIds.length, result.summaryRef, result.compressedTokens, resultSs
|
|
597
|
+
await sendCompressNotification(ctx.client, sessionId, config, result.messageIds.length, result.summaryRef, result.compressedTokens, resultSs, result.afterCount || 0)
|
|
578
598
|
return `Compressed ${result.messageIds.length} messages across ${args.content.length} range(s). Summary: "${truncateText(result.summaryRef, 200)}"`
|
|
579
599
|
}
|
|
580
600
|
|
|
581
601
|
const result = await applyCompress(ctx, sessionId, args.summary, max, min, args.targetTokens)
|
|
582
602
|
toolCtx.metadata({ title: "Compress" })
|
|
583
603
|
const toolSs = getOrCreateState(sessionId)
|
|
584
|
-
await sendCompressNotification(ctx.client, sessionId, config, result.removed, truncateText(args.summary, 200), result.tokensRemoved, toolSs
|
|
604
|
+
await sendCompressNotification(ctx.client, sessionId, config, result.removed, truncateText(args.summary, 200), result.tokensRemoved, toolSs, result.afterCount)
|
|
585
605
|
return `Compressed: ${result.removed} messages removed. Summary: "${truncateText(args.summary, 200)}"`
|
|
586
606
|
},
|
|
587
607
|
}),
|
package/lib/ohc/reaper.mjs
CHANGED
package/lib/ohc/state.mjs
CHANGED
|
@@ -89,6 +89,7 @@ export function createSessionState() {
|
|
|
89
89
|
summary: null,
|
|
90
90
|
anchorMessageId: null,
|
|
91
91
|
totalTokensSaved: 0,
|
|
92
|
+
totalMessagesRemoved: 0,
|
|
92
93
|
blockCount: 0,
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -136,6 +137,7 @@ export function serializeState(state) {
|
|
|
136
137
|
},
|
|
137
138
|
lastAutoPruneAt: state.lastAutoPruneAt,
|
|
138
139
|
totalTokensSaved: state.totalTokensSaved,
|
|
140
|
+
totalMessagesRemoved: state.totalMessagesRemoved,
|
|
139
141
|
blockCount: state.blockCount,
|
|
140
142
|
summary: state.summary,
|
|
141
143
|
anchorMessageId: state.anchorMessageId,
|
|
@@ -180,6 +182,7 @@ export function deserializeState(saved) {
|
|
|
180
182
|
}
|
|
181
183
|
state.lastAutoPruneAt = saved.lastAutoPruneAt || null
|
|
182
184
|
state.totalTokensSaved = saved.totalTokensSaved || 0
|
|
185
|
+
state.totalMessagesRemoved = saved.totalMessagesRemoved || 0
|
|
183
186
|
state.blockCount = saved.blockCount || 0
|
|
184
187
|
state.summary = saved.summary || null
|
|
185
188
|
state.anchorMessageId = saved.anchorMessageId || null
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openhermes",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "OpenHermes plugin suite for OpenCode — autonomous checkpointing, native memory tools, subagent routing, slash commands, and skill-candidate detection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|