@hyperspell/openclaw-hyperspell 0.21.1 → 0.23.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.
|
@@ -89,12 +89,19 @@ function contentToText(content) {
|
|
|
89
89
|
}
|
|
90
90
|
return "";
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Only conversational turns carry relationship signal. `system` is scaffolding
|
|
94
|
+
* and `tool`/`toolResult` are machinery — a Discord send receipt says nothing
|
|
95
|
+
* about how a conversation felt, and letting one through means the extractor
|
|
96
|
+
* distills API JSON into an emotional register. Same rule as hot-buffer.ts.
|
|
97
|
+
*/
|
|
98
|
+
const CONVERSATIONAL_ROLES = new Set(["user", "assistant"]);
|
|
99
|
+
export function messagesToTranscript(messages) {
|
|
93
100
|
const lines = [];
|
|
94
101
|
for (const m of messages) {
|
|
95
102
|
if (!m.role || !m.content)
|
|
96
103
|
continue;
|
|
97
|
-
if (m.role
|
|
104
|
+
if (!CONVERSATIONAL_ROLES.has(m.role))
|
|
98
105
|
continue;
|
|
99
106
|
const raw = contentToText(m.content);
|
|
100
107
|
if (!raw)
|
|
@@ -112,9 +119,13 @@ function messagesToTranscript(messages) {
|
|
|
112
119
|
* rather than a distilled emotional register. Real summaries are second-person
|
|
113
120
|
* prose ("Your relationship with this user…"); the placeholder echoes the input
|
|
114
121
|
* conversation, which has role-prefixed lines.
|
|
122
|
+
*
|
|
123
|
+
* Covers the full role vocabulary, not just what messagesToTranscript writes
|
|
124
|
+
* today: rows stored before tool roles were excluded above are still fetched
|
|
125
|
+
* until they age out, and they lead with `toolResult:`.
|
|
115
126
|
*/
|
|
116
127
|
export function looksLikeRawTranscript(summary) {
|
|
117
|
-
return /(^|\n)\s*(user|assistant)\s*:/i.test(summary);
|
|
128
|
+
return /(^|\n)\s*(?:user|assistant|system|tool(?:result|use|call)?)\s*:/i.test(summary);
|
|
118
129
|
}
|
|
119
130
|
/** Compact relative time for the arc labels (e.g. "just now", "3h ago", "2d ago"). */
|
|
120
131
|
function relativeWhen(iso) {
|
package/dist/hooks/hot-buffer.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { getWorkspaceDir } from "../config.js";
|
|
4
4
|
import { channelIdFromCtx } from "../lib/exclude-channels.js";
|
|
5
|
+
import { HOT_BUFFER_SOURCE } from "../lib/filters.js";
|
|
5
6
|
import { resolveUser } from "../lib/sender.js";
|
|
6
7
|
import { cleanupSpeakerSession, isMultiSpeaker, recordSender, senderIdFromCtx, } from "../lib/speaker-tracker.js";
|
|
7
8
|
import { log } from "../logger.js";
|
|
@@ -237,7 +238,7 @@ export function buildHotBufferHandler(client, cfg, opts) {
|
|
|
237
238
|
// would have blocked.
|
|
238
239
|
const channelId = channelIdFromCtx(ctx);
|
|
239
240
|
const metadata = {
|
|
240
|
-
openclaw_source:
|
|
241
|
+
openclaw_source: HOT_BUFFER_SOURCE,
|
|
241
242
|
openclaw_session_id: sessionId,
|
|
242
243
|
...(channelId ? { openclaw_channel_id: channelId } : {}),
|
|
243
244
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { excludeFilterFor } from "../lib/filters.js";
|
|
1
|
+
import { excludeFilterFor, HOT_BUFFER_SOURCE } from "../lib/filters.js";
|
|
2
2
|
import { resolveUser } from "../lib/sender.js";
|
|
3
3
|
import { resolveCurrentSessionId } from "../lib/session.js";
|
|
4
4
|
import { isMultiSpeaker } from "../lib/speaker-tracker.js";
|
|
@@ -144,13 +144,16 @@ async function fetchRecentTraces(client, cutoff, limit, userId) {
|
|
|
144
144
|
* modern source of "recent interactions" (the agent_end-trace path only works
|
|
145
145
|
* when auto-trace is on, which it usually isn't). The hot buffer writes one
|
|
146
146
|
* session-grouped Resource per conversation: `resource_id` = the session id (a
|
|
147
|
-
* UUID),
|
|
148
|
-
* `listMemories` returning resources newest-first (verified live) and take
|
|
149
|
-
* newest `limit` conversation resources — their metadata carries no date to
|
|
147
|
+
* UUID), tagged `openclaw_source: "hot_buffer"`, with a generated title. We rely
|
|
148
|
+
* on `listMemories` returning resources newest-first (verified live) and take
|
|
149
|
+
* the newest `limit` conversation resources — their metadata carries no date to
|
|
150
150
|
* filter on, so order is our recency signal.
|
|
151
151
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
152
|
+
* Selects ON the hot-buffer tag. This used to skip any row carrying an
|
|
153
|
+
* `openclaw_source` at all, which was correct only while hot rows were untagged;
|
|
154
|
+
* backend #1921 (2026-07-02) started tagging them, so that exclusion began
|
|
155
|
+
* dropping every row it was meant to keep and the block silently vanished.
|
|
156
|
+
* Sibling rows (memory_sync_section / command / agent_end) fail the same check.
|
|
154
157
|
*/
|
|
155
158
|
async function fetchRecentConversations(client, limit, userId) {
|
|
156
159
|
const out = [];
|
|
@@ -166,7 +169,7 @@ async function fetchRecentConversations(client, limit, userId) {
|
|
|
166
169
|
break;
|
|
167
170
|
if (!UUID_RE.test(memory.resourceId))
|
|
168
171
|
continue;
|
|
169
|
-
if (memory.metadata?.openclaw_source)
|
|
172
|
+
if (memory.metadata?.openclaw_source !== HOT_BUFFER_SOURCE)
|
|
170
173
|
continue;
|
|
171
174
|
const title = memory.title ?? "";
|
|
172
175
|
if (title.length === 0 || /^\[cron:/i.test(title))
|
package/dist/index.js
CHANGED
|
@@ -144,6 +144,14 @@ export default {
|
|
|
144
144
|
// Register exactly ONE of the two: cores between 2026-04 and 2026-07
|
|
145
145
|
// accept both and would double-inject. Both hooks share the same
|
|
146
146
|
// { prependContext } result contract and provide event.prompt.
|
|
147
|
+
//
|
|
148
|
+
// Do NOT rename this a third time. We were originally on
|
|
149
|
+
// `before_prompt_build`, moved to `before_agent_start`, and that is the
|
|
150
|
+
// only reason 2026.7.2 took injection down: `before_prompt_build` is
|
|
151
|
+
// still live in core's PROMPT_INJECTION_HOOK_NAMES and would have
|
|
152
|
+
// survived untouched. Staying on `agent_turn_prepare` (proven in
|
|
153
|
+
// production) — but the lesson is that "newer-sounding hook name" is not
|
|
154
|
+
// evidence of anything. Check core's hook table before moving again.
|
|
147
155
|
const injectionHook = typeof api
|
|
148
156
|
.enqueueNextTurnInjection === "function"
|
|
149
157
|
? "agent_turn_prepare"
|
package/dist/lib/filters.js
CHANGED
|
@@ -10,6 +10,13 @@
|
|
|
10
10
|
export const AGENT_END_SOURCE = "agent_end";
|
|
11
11
|
/** Metadata tag on mood-weather roll records (see `recordMoodRoll` in hooks/mood-weather.ts). */
|
|
12
12
|
export const MOOD_WEATHER_SOURCE = "mood_weather";
|
|
13
|
+
/**
|
|
14
|
+
* Metadata tag on hot-buffer conversation rows (written by hooks/hot-buffer.ts).
|
|
15
|
+
* Readers must select ON this value, never "any tag means not mine" — hot rows
|
|
16
|
+
* were untagged before backend #1921 (2026-07-02) and consumers written against
|
|
17
|
+
* that shape silently match nothing now.
|
|
18
|
+
*/
|
|
19
|
+
export const HOT_BUFFER_SOURCE = "hot_buffer";
|
|
13
20
|
/**
|
|
14
21
|
* Memories produced by the auto-trace session-end hook are tagged in metadata
|
|
15
22
|
* as `openclaw_source: "agent_end"` (see `sendTrace` in client.ts). Those should
|
package/openclaw.plugin.json
CHANGED