switchroom 0.19.26 โ 0.19.27
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/bin/git-agent-attribution-hook.sh +144 -0
- package/dist/agent-scheduler/index.js +55 -0
- package/dist/auth-broker/index.js +116 -6
- package/dist/cli/notion-write-pretool.mjs +55 -0
- package/dist/cli/switchroom.js +2055 -1193
- package/dist/host-control/main.js +117 -7
- package/dist/vault/approvals/kernel-server.js +114 -6
- package/dist/vault/broker/server.js +114 -6
- package/package.json +6 -2
- package/profiles/_base/cron-session.sh.hbs +8 -0
- package/profiles/_base/start.sh.hbs +105 -0
- package/telegram-plugin/card-layout.ts +328 -0
- package/telegram-plugin/dist/bridge/bridge.js +93 -1
- package/telegram-plugin/dist/gateway/gateway.js +2009 -1166
- package/telegram-plugin/dist/server.js +96 -1
- package/telegram-plugin/edit-flood-fuse.ts +637 -56
- package/telegram-plugin/flood-429-ledger.ts +526 -0
- package/telegram-plugin/flood-circuit-breaker.ts +18 -0
- package/telegram-plugin/gateway/flood-reply-queue.ts +168 -0
- package/telegram-plugin/gateway/gateway.ts +58 -68
- package/telegram-plugin/gateway/narrative-lane.ts +14 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +36 -0
- package/telegram-plugin/gateway/outbox-sweep.ts +183 -6
- package/telegram-plugin/gateway/pinned-message-handler.ts +12 -16
- package/telegram-plugin/gateway/status-pin-retarget.ts +72 -36
- package/telegram-plugin/gateway/status-pin-store.ts +58 -9
- package/telegram-plugin/gateway/worker-pin-reaper.ts +56 -7
- package/telegram-plugin/llm-error-present.ts +61 -2
- package/telegram-plugin/model-unavailable.ts +8 -0
- package/telegram-plugin/operator-events.ts +72 -5
- package/telegram-plugin/outbound-class.ts +81 -0
- package/telegram-plugin/provider-credit.ts +237 -0
- package/telegram-plugin/scripts/bun-test-ci.sh +36 -6
- package/telegram-plugin/send-gate.ts +24 -2
- package/telegram-plugin/status-no-truncate.ts +10 -48
- package/telegram-plugin/status-pin-driver.ts +33 -45
- package/telegram-plugin/status-pin.ts +18 -1
- package/telegram-plugin/tests/card-golden.test.ts +69 -0
- package/telegram-plugin/tests/card-lifecycle-render.test.ts +362 -0
- package/telegram-plugin/tests/card-type-distinguishability.test.ts +187 -164
- package/telegram-plugin/tests/card-variants.golden.txt +211 -0
- package/telegram-plugin/tests/card-variants.ts +366 -0
- package/telegram-plugin/tests/edit-flood-fuse-ban-awareness.test.ts +316 -0
- package/telegram-plugin/tests/edit-flood-fuse-default-deny.test.ts +319 -0
- package/telegram-plugin/tests/edit-flood-fuse.test.ts +11 -2
- package/telegram-plugin/tests/feed-edit-rate-ceiling.test.ts +462 -0
- package/telegram-plugin/tests/fixtures/real-429-stream.ts +220 -0
- package/telegram-plugin/tests/flood-429-ledger.test.ts +278 -0
- package/telegram-plugin/tests/flood-429-recorder-wiring.test.ts +128 -0
- package/telegram-plugin/tests/flood-reply-queue.test.ts +418 -0
- package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +221 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +19 -24
- package/telegram-plugin/tests/pinned-message-handler.test.ts +15 -15
- package/telegram-plugin/tests/provider-credit-402.test.ts +243 -0
- package/telegram-plugin/tests/status-pin-api.test.ts +11 -11
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +36 -37
- package/telegram-plugin/tests/status-pin-lifecycle.test.ts +602 -0
- package/telegram-plugin/tests/status-pin-retarget.test.ts +90 -62
- package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +7 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +109 -60
- package/telegram-plugin/tests/status-pin.test.ts +56 -5
- package/telegram-plugin/tests/test-runner-coverage.test.ts +133 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +12 -10
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +23 -29
- package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +56 -59
- package/telegram-plugin/tests/worker-feed-terminal-edit-class.test.ts +335 -0
- package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
- package/telegram-plugin/tool-activity-summary.ts +239 -365
- package/telegram-plugin/uat/assertions.ts +22 -11
- package/telegram-plugin/uat/feed-matcher.test.ts +24 -17
- package/telegram-plugin/worker-activity-feed.ts +105 -47
- package/vendor/hindsight-memory/CLAUDE.md +45 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +33 -0
- package/vendor/hindsight-memory/scripts/recall.py +176 -7
- package/vendor/hindsight-memory/scripts/tests/test_config_recall_passthrough_env.py +170 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_min_score.py +464 -0
- package/vendor/hindsight-memory/settings.json +1 -1
|
@@ -23,8 +23,15 @@ import type { Driver, ObservedMessage, ObservedReaction } from "./driver.js";
|
|
|
23
23
|
*
|
|
24
24
|
* Single source of truth; the worker-feed scenario asserts against this,
|
|
25
25
|
* and recall/reply scenarios exclude it via {@link isWorkerFeedMessage}.
|
|
26
|
+
*
|
|
27
|
+
* `Workers?` is load-bearing, not cosmetic: the COMBINED (2+ worker) card's
|
|
28
|
+
* glance line reads `๐ WORKERS ยท N running ยท โฆ`, and `Worker\b` cannot match
|
|
29
|
+
* inside `WORKERS` (no word boundary between `r` and `S`). Without the optional
|
|
30
|
+
* `s` the whole 2+ worker surface was invisible to this predicate, so a
|
|
31
|
+
* recall/reply scenario running with two live workers could latch onto the
|
|
32
|
+
* combined card as the agent's answer. Found during the #3842 card audit.
|
|
26
33
|
*/
|
|
27
|
-
export const WORKER_FEED_RE = /๐ [๏ธ]?\s*
|
|
34
|
+
export const WORKER_FEED_RE = /๐ [๏ธ]?\s*Workers?\b|finished\s*ยท\s*(?:completed|failed)/i;
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
37
|
* True when `m` is a worker-activity-feed message rather than the agent's
|
|
@@ -74,22 +81,26 @@ const ACTIVITY_BODY_LINE_RE = /^[โโโณ]/u;
|
|
|
74
81
|
const LIVENESS_HEADER_L1_RE = /^(?:๐ค|๐ [๏ธ]?|โ[๏ธ]?)\s+\S/u;
|
|
75
82
|
|
|
76
83
|
/**
|
|
77
|
-
* Leading chrome a
|
|
78
|
-
*
|
|
79
|
-
*
|
|
84
|
+
* Leading indent chrome a card line can carry: a `U+2800` run, applied by
|
|
85
|
+
* `WORKER_STEP_INDENT` to the step lines under each worker header on the
|
|
86
|
+
* combined (2+ worker) card so one worker's steps are visibly separated from
|
|
87
|
+
* the next worker's.
|
|
80
88
|
*
|
|
81
89
|
* Every line-shape matcher below must normalise it away first: Telegram
|
|
82
|
-
* delivers
|
|
83
|
-
*
|
|
90
|
+
* delivers it verbatim and `String.trim()` does NOT remove it โ U+2800 is
|
|
91
|
+
* category So, not whitespace (that property is exactly why it survives
|
|
84
92
|
* Telegram's own left-trim; see WORKER_STEP_INDENT). Without this, a worker
|
|
85
|
-
* card's
|
|
86
|
-
*
|
|
93
|
+
* card's body lines stop matching `ACTIVITY_BODY_LINE_RE`.
|
|
94
|
+
*
|
|
95
|
+
* #3842 removed the whole-card `โโ ` header prefix and the whole-card indent
|
|
96
|
+
* that #3820/#3821 put on worker cards, so this no longer has to strip a `โโ `.
|
|
97
|
+
* The step indent is the only nesting a card emits now.
|
|
87
98
|
*/
|
|
88
|
-
const
|
|
99
|
+
const CARD_INDENT_PREFIX_RE = /^\u2800+/u;
|
|
89
100
|
|
|
90
|
-
/** A card line with its
|
|
101
|
+
/** A card line with its leading step-indent chrome removed, trimmed. */
|
|
91
102
|
export function stripCardNesting(line: string): string {
|
|
92
|
-
return line.replace(
|
|
103
|
+
return line.replace(CARD_INDENT_PREFIX_RE, "").trim();
|
|
93
104
|
}
|
|
94
105
|
const LIVENESS_ELAPSED = String.raw`(?:\d+m)?\d+s`;
|
|
95
106
|
const LIVENESS_HEADER_L2_RE = new RegExp(
|
|
@@ -52,29 +52,36 @@ describe("isWorkerFeedMessage", () => {
|
|
|
52
52
|
expect(WORKER_FEED_RE.test("๐ Worker ยท x")).toBe(true);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
it("
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// so every matcher here has to survive it or recall/reply scenarios start
|
|
61
|
-
// latching onto worker cards as answers.
|
|
62
|
-
const nested = "โโ ๐ WORKER ยท crawling changelog\nโ โ โ 55s ยท 9 tools ยท opus 5";
|
|
63
|
-
expect(isWorkerFeedMessage(feed(nested))).toBe(true);
|
|
55
|
+
it("matches the FLUSH worker card as Telegram delivers it (#3842)", () => {
|
|
56
|
+
// #3842 removed the whole-card `โโ ` prefix and whole-card indent that
|
|
57
|
+
// #3820/#3821 put on worker cards, so the single-worker card arrives flush.
|
|
58
|
+
const flush = "๐ WORKER ยท crawling changelog\n55s ยท 9 tools ยท opus 5";
|
|
59
|
+
expect(isWorkerFeedMessage(feed(flush))).toBe(true);
|
|
64
60
|
});
|
|
65
61
|
});
|
|
66
62
|
|
|
67
|
-
describe("#
|
|
68
|
-
it("classifies a
|
|
69
|
-
const
|
|
70
|
-
"
|
|
71
|
-
expect(isLivenessCardMessage(feed(
|
|
72
|
-
expect(isActivityFeedMessage(feed(
|
|
63
|
+
describe("#3842 the surviving step indent is transparent to the matchers", () => {
|
|
64
|
+
it("classifies a flush agent-shaped worker card as the liveness card, not an answer", () => {
|
|
65
|
+
const flush =
|
|
66
|
+
"๐ WORKER ยท crawl\n12s ยท 3 tools\nโ Reading CLAUDE.md\nโ Searching memory";
|
|
67
|
+
expect(isLivenessCardMessage(feed(flush))).toBe(true);
|
|
68
|
+
expect(isActivityFeedMessage(feed(flush))).toBe(true);
|
|
73
69
|
});
|
|
74
70
|
|
|
75
|
-
it("
|
|
76
|
-
|
|
71
|
+
it("classifies a combined card whose step lines carry WORKER_STEP_INDENT", () => {
|
|
72
|
+
// The one indent that survives #3842: step lines under a worker row header
|
|
73
|
+
// on the 2+ worker card. `String.trim()` does NOT remove U+2800 (category
|
|
74
|
+
// So, not whitespace), so the matchers must normalise it explicitly or a
|
|
75
|
+
// recall/reply scenario latches onto the combined card as an answer.
|
|
76
|
+
const combined =
|
|
77
|
+
"๐ WORKERS ยท 2 running ยท oldest 55s ยท 13 tools\n" +
|
|
78
|
+
"1. crawl ยท 55s ยท 9 tools\nโ โ โ โ Reading CLAUDE.md\nโ โ โ โ Searching memory";
|
|
79
|
+
expect(isWorkerFeedMessage(feed(combined))).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("strips the step indent and leaves ordinary prose alone", () => {
|
|
77
83
|
expect(stripCardNesting("โ โ โ โ Searching memory")).toBe("โ Searching memory");
|
|
84
|
+
expect(stripCardNesting("๐ WORKER ยท crawl")).toBe("๐ WORKER ยท crawl");
|
|
78
85
|
expect(stripCardNesting("on it, pulling the logs now")).toBe(
|
|
79
86
|
"on it, pulling the logs now",
|
|
80
87
|
);
|
|
@@ -53,13 +53,9 @@ import {
|
|
|
53
53
|
cleanWorkerResultParagraph,
|
|
54
54
|
stripMarkdown,
|
|
55
55
|
truncate,
|
|
56
|
-
COLLAPSE_SAFE_SEPARATOR,
|
|
57
56
|
} from './card-format.js'
|
|
58
|
-
import {
|
|
59
|
-
|
|
60
|
-
SUBORDINATE_HEADER_PREFIX,
|
|
61
|
-
SUBORDINATE_LINE_INDENT,
|
|
62
|
-
} from './status-no-truncate.js'
|
|
57
|
+
import { WORKER_HISTORY_MAX } from './status-no-truncate.js'
|
|
58
|
+
import { renderCardTitleLine } from './card-layout.js'
|
|
63
59
|
import {
|
|
64
60
|
renderStatusCard,
|
|
65
61
|
formatStepSuffix,
|
|
@@ -130,6 +126,49 @@ export interface WorkerActivityView {
|
|
|
130
126
|
model?: string
|
|
131
127
|
}
|
|
132
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Out-of-band metadata about ONE feed edit, handed to the transport adapter
|
|
131
|
+
* alongside the Bot API arguments (switchroom#3848).
|
|
132
|
+
*
|
|
133
|
+
* Deliberately a SEPARATE parameter rather than a field on `opts`: `opts` is
|
|
134
|
+
* forwarded verbatim to `editMessageText` and therefore onto the wire, and
|
|
135
|
+
* `outbound-class.ts` exists precisely so a priority signal never has to be
|
|
136
|
+
* smuggled into an outbound Bot API payload.
|
|
137
|
+
*/
|
|
138
|
+
export interface WorkerFeedEditMeta {
|
|
139
|
+
/**
|
|
140
|
+
* True when this edit paints the card's FINAL state โ the last worker's
|
|
141
|
+
* terminal recap, or the "superseded" note on a rotated-out message. False
|
|
142
|
+
* for every intermediate / liveness repaint.
|
|
143
|
+
*/
|
|
144
|
+
terminal: boolean
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The send-gate priority class a worker-feed edit must carry.
|
|
149
|
+
*
|
|
150
|
+
* Intermediate repaints are `cosmetic`: dropping one costs nothing, because
|
|
151
|
+
* the next render carries full state, and holding them to the fuse's tight
|
|
152
|
+
* cosmetic ceilings (4/message/60s, 6/chat/60s) is what stops the feed
|
|
153
|
+
* earning a per-chat flood ban (#3847).
|
|
154
|
+
*
|
|
155
|
+
* A TERMINAL frame is `useful` (#3848). It is the last frame the operator
|
|
156
|
+
* ever reads for that card and nothing newer is coming to repaint it, so
|
|
157
|
+
* classing it cosmetic made the one frame carrying the finished state
|
|
158
|
+
* compete in โ and get shed from โ the same starved budget as a heartbeat.
|
|
159
|
+
* `useful` rather than `critical` matches the activity-summary finalize in
|
|
160
|
+
* `gateway/narrative-lane.ts`: it may be DEFERRED under pressure, but it is
|
|
161
|
+
* not shed, and it does not spend the per-chat reply reservation that keeps
|
|
162
|
+
* the operator's real answer unstarvable.
|
|
163
|
+
*
|
|
164
|
+
* Exported so the mapping is unit-testable; `gateway.ts` only calls it.
|
|
165
|
+
*/
|
|
166
|
+
export function workerFeedEditPriorityClass(
|
|
167
|
+
meta?: WorkerFeedEditMeta,
|
|
168
|
+
): 'useful' | 'cosmetic' {
|
|
169
|
+
return meta?.terminal === true ? 'useful' : 'cosmetic'
|
|
170
|
+
}
|
|
171
|
+
|
|
133
172
|
export interface BotApiForWorkerFeed {
|
|
134
173
|
sendMessage(
|
|
135
174
|
chatId: string,
|
|
@@ -141,6 +180,12 @@ export interface BotApiForWorkerFeed {
|
|
|
141
180
|
messageId: number,
|
|
142
181
|
text: string,
|
|
143
182
|
opts?: Record<string, unknown>,
|
|
183
|
+
/**
|
|
184
|
+
* Out-of-band edit metadata (NOT part of the Bot API payload). The
|
|
185
|
+
* gateway adapter maps `terminal` to the send-gate priority class via
|
|
186
|
+
* {@link workerFeedEditPriorityClass}.
|
|
187
|
+
*/
|
|
188
|
+
meta?: WorkerFeedEditMeta,
|
|
144
189
|
): Promise<unknown>
|
|
145
190
|
}
|
|
146
191
|
|
|
@@ -168,30 +213,32 @@ export function repeatCountOf(line: string): number {
|
|
|
168
213
|
|
|
169
214
|
/**
|
|
170
215
|
* Thin adapter over the unified `renderStatusCard` primitive (emoji ๐ , label
|
|
171
|
-
* 'WORKER'
|
|
216
|
+
* 'WORKER'): builds the header, passes raw narrative steps
|
|
172
217
|
* (the primitive runs stripMarkdown โ collapse ws โ clip โ escape per line),
|
|
173
218
|
* and on finish passes the cleaned result paragraph as the `result` block.
|
|
174
219
|
*
|
|
175
220
|
* Layout (running):
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
221
|
+
* ๐ **WORKER** ยท _{description}_
|
|
222
|
+
* _{elapsed} ยท {n} tools_
|
|
223
|
+
* ~~_โ {earlier step}_~~
|
|
224
|
+
* **โ {newest step}**
|
|
180
225
|
*
|
|
181
226
|
* Layout (finished): the feed renders all-done, then a rule + cleaned result:
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
227
|
+
* ๐ **WORKER** ยท _{description}_
|
|
228
|
+
* _done ยท {n} tools ยท {elapsed}_
|
|
229
|
+
* ~~_โ {step}_~~
|
|
230
|
+
* โโโโโ
|
|
231
|
+
* โ
_{cleaned result paragraph}_
|
|
187
232
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* card
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
233
|
+
* FLUSH (#3842): this card sits at the left margin. #3820/#3821 indented the
|
|
234
|
+
* whole block and prefixed line 1 with `โโ ` to mark it subordinate to the ๐ค
|
|
235
|
+
* agent card; that was reverted because it burned a level of horizontal phone
|
|
236
|
+
* width and because this card does not always sit below the agent card, so a
|
|
237
|
+
* card-level subordination marker asserts a relationship that is not always
|
|
238
|
+
* true. The remaining type cue is line 1: the ๐ emoji plus the caps `WORKER`
|
|
239
|
+
* label against the agent card's ๐ค `Agent`. Keep that pair โ with identical
|
|
240
|
+
* step text and stats the two cards' BODY lines can now coincide byte for
|
|
241
|
+
* byte, so line 1 is doing all of the distinguishing work.
|
|
195
242
|
*/
|
|
196
243
|
export function renderWorkerActivity(v: WorkerActivityView, liveSuffix = ''): string {
|
|
197
244
|
const desc = truncate(stripMarkdown(v.description).trim() || 'background task', DESC_MAX)
|
|
@@ -239,9 +286,6 @@ export function renderWorkerActivity(v: WorkerActivityView, liveSuffix = ''): st
|
|
|
239
286
|
if (text.length > 0) result = { emoji: v.state === 'done' ? 'โ
' : 'โ ๏ธ', text }
|
|
240
287
|
}
|
|
241
288
|
|
|
242
|
-
// `renderStatusCard` always returns content when a header is supplied. When
|
|
243
|
-
// running with no steps it shows just the header โ append a "startingโฆ" line
|
|
244
|
-
// for parity with the prior behaviour.
|
|
245
289
|
const card = renderStatusCard({
|
|
246
290
|
header,
|
|
247
291
|
steps,
|
|
@@ -251,25 +295,18 @@ export function renderWorkerActivity(v: WorkerActivityView, liveSuffix = ''): st
|
|
|
251
295
|
// Lone-worker card: window to the w=1 point of Ken's curve (6) so it shows
|
|
252
296
|
// the full recent trail, not the 5-line agent-card default (#3349).
|
|
253
297
|
historyWindow: workerHistoryDepth(1),
|
|
254
|
-
//
|
|
255
|
-
|
|
298
|
+
// A just-dispatched worker has no narrative yet. The placeholder is a normal
|
|
299
|
+
// BODY line of the shared spec now (#3846): it used to be concatenated onto
|
|
300
|
+
// this function's return value, which put a user-visible card line outside
|
|
301
|
+
// the primitive that owns hard-break stacking, the collapse-safe separator
|
|
302
|
+
// and the char budget, and made the seam the one boundary that could mash in
|
|
303
|
+
// Telegram's pinned bar. Only while RUNNING โ a finished worker with no
|
|
304
|
+
// steps must not read as "startingโฆ".
|
|
305
|
+
emptyPlaceholder: finished ? undefined : '_startingโฆ_',
|
|
256
306
|
})
|
|
257
307
|
if (card == null) {
|
|
258
308
|
// Unreachable (header always present) โ defensive.
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
|
-
if (!finished && steps.length === 0) {
|
|
262
|
-
// Header-only running render โ append the starting placeholder with a GFM
|
|
263
|
-
// hard break (` \n`) so it stacks under the header instead of collapsing
|
|
264
|
-
// onto the header line in the rich-message renderer (matches stackCardLines).
|
|
265
|
-
// The collapse separator is carried here too (#3666) โ this card is pinned,
|
|
266
|
-
// and a hand-rolled seam would be the one boundary that still mashed in
|
|
267
|
-
// Telegram's pinned bar.
|
|
268
|
-
// The placeholder is a card BODY line, so it carries the subordinate indent
|
|
269
|
-
// like every other non-header line (#3820) โ otherwise the one render state
|
|
270
|
-
// a user sees first (a just-dispatched worker) would be the one that isn't
|
|
271
|
-
// visibly nested.
|
|
272
|
-
return `${card}${COLLAPSE_SAFE_SEPARATOR} \n${SUBORDINATE_LINE_INDENT}_startingโฆ_`
|
|
309
|
+
return renderCardTitleLine('๐ ', 'WORKER', 'startingโฆ')
|
|
273
310
|
}
|
|
274
311
|
return card
|
|
275
312
|
}
|
|
@@ -616,10 +653,21 @@ const COOLDOWN_JITTER_MS = 500
|
|
|
616
653
|
* rows and reads like a stuck worker. A single best-effort edit collapses it to
|
|
617
654
|
* an honest "moved" note โ issued once per rotation (โฅ cap interval), never per
|
|
618
655
|
* tick, so it adds no edit churn / pin storm. Plain voice, no em dash.
|
|
656
|
+
*
|
|
657
|
+
* Exported so the card-preview harness (`scripts/card-previews.ts`) renders the
|
|
658
|
+
* real string rather than a copy that can drift from it.
|
|
659
|
+
*
|
|
660
|
+
* DELIBERATELY still a constant, not a card render (#3846). It shows no live
|
|
661
|
+
* state โ no elapsed, no tool count, no steps โ so there is nothing for a
|
|
662
|
+
* renderer to compose and no rolling window or char budget to enforce; routing
|
|
663
|
+
* it through `renderStatusCard` would mean inventing a state to render. What it
|
|
664
|
+
* DOES share with every other card is its type chrome, so line 1 comes from the
|
|
665
|
+
* one title composer (`renderCardTitleLine`): if the ๐ / **WORKER** convention
|
|
666
|
+
* ever changes, this notice moves with it instead of being the one stale card.
|
|
619
667
|
*/
|
|
620
|
-
const WORKER_CARD_SUPERSEDED_BODY =
|
|
621
|
-
`${
|
|
622
|
-
|
|
668
|
+
export const WORKER_CARD_SUPERSEDED_BODY =
|
|
669
|
+
`${renderCardTitleLine('๐ ', 'WORKER', 'continued')}\n\n` +
|
|
670
|
+
'_Live progress moved to a fresh card to stay pinned._'
|
|
623
671
|
|
|
624
672
|
function extractRetryAfterSecs(err: unknown): number | null {
|
|
625
673
|
if (err == null || typeof err !== 'object') return null
|
|
@@ -1197,7 +1245,11 @@ export function createWorkerActivityFeed(opts: WorkerActivityFeedOpts): WorkerAc
|
|
|
1197
1245
|
}
|
|
1198
1246
|
|
|
1199
1247
|
try {
|
|
1200
|
-
|
|
1248
|
+
// #3848: tell the transport whether this is the card's LAST frame. A
|
|
1249
|
+
// terminal recap must not be shed with the heartbeat repaints.
|
|
1250
|
+
const res = await opts.bot.editMessageText(
|
|
1251
|
+
g.chatId, g.messageId, body, sendOptsFor(g), { terminal: isTerminal },
|
|
1252
|
+
)
|
|
1201
1253
|
// Shed honesty (#3084): a cosmetic edit the gate shed resolves the
|
|
1202
1254
|
// distinguishable SEND_GATE_SHED sentinel (NOT a bare `undefined`, which
|
|
1203
1255
|
// the gate reserves for a benign no-op drop whose payload IS on screen).
|
|
@@ -1486,8 +1538,14 @@ export function createWorkerActivityFeed(opts: WorkerActivityFeedOpts): WorkerAc
|
|
|
1486
1538
|
// doesn't sit frozen showing live-styled rows (mistakable for a stuck
|
|
1487
1539
|
// worker). ONE best-effort edit per rotation (โฅ cap interval), fired
|
|
1488
1540
|
// off-chain and swallowing errors โ never per-tick, never a burst.
|
|
1541
|
+
// #3848: terminal โ this is the retired message's LAST frame, and it
|
|
1542
|
+
// fires at most once per `groupMessageLifetimeCapMs`, so promoting it
|
|
1543
|
+
// out of the cosmetic budget costs nothing and stops the retired card
|
|
1544
|
+
// being left frozen showing live-styled rows.
|
|
1489
1545
|
void opts.bot
|
|
1490
|
-
.editMessageText(
|
|
1546
|
+
.editMessageText(
|
|
1547
|
+
g.chatId, retiredId, WORKER_CARD_SUPERSEDED_BODY, sendOptsFor(g), { terminal: true },
|
|
1548
|
+
)
|
|
1491
1549
|
.catch(() => {})
|
|
1492
1550
|
}
|
|
1493
1551
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CLAUDE.md โ vendor/hindsight-memory
|
|
2
|
+
|
|
3
|
+
**This directory is a VENDORED SNAPSHOT of the hindsight-memory plugin. It is
|
|
4
|
+
not the specification, and it is not what runs.** Treat it as evidence about
|
|
5
|
+
one commit of the implementation, nothing more.
|
|
6
|
+
|
|
7
|
+
The load-bearing copy of this rule is the repo-root `CLAUDE.md`
|
|
8
|
+
("Third-party docs โ the official site is the spec"); nothing auto-loads a
|
|
9
|
+
subdirectory `CLAUDE.md`, so this file only reaches an agent that opens it.
|
|
10
|
+
|
|
11
|
+
## Where the truth is
|
|
12
|
+
|
|
13
|
+
1. **Official docs site** โ <https://hindsight.vectorize.io/>. Curation and
|
|
14
|
+
the memory-unit endpoints: `/developer/api/memories`. Also
|
|
15
|
+
`/developer/api/retain`, `/developer/api/recall`,
|
|
16
|
+
`/developer/observations`, `/developer/configuration`, index at
|
|
17
|
+
`/api-reference`. (`/developer/` itself has no index page โ it 404s.)
|
|
18
|
+
2. **context7** โ prefer `/websites/hindsight_vectorize_io` (the docs site)
|
|
19
|
+
over `/vectorize-io/hindsight` (the OSS repo).
|
|
20
|
+
3. **This tree** โ last, and only for "what does the code here actually do".
|
|
21
|
+
|
|
22
|
+
Not finding something here is not evidence it doesn't exist. Search the docs
|
|
23
|
+
before you tell anyone a knob, endpoint or config path is unsupported.
|
|
24
|
+
|
|
25
|
+
## Trap: `settings.json` here is NOT what switchroom installs
|
|
26
|
+
|
|
27
|
+
`installHindsightPlugin` (`src/agents/scaffold.ts`) copies this tree into each
|
|
28
|
+
agent's `.claude/plugins/hindsight-memory/` and then **stamps switchroom's own
|
|
29
|
+
overrides over `settings.json`** โ additional banks, the retain cadence knobs,
|
|
30
|
+
and the recall types. Concretely, this file has
|
|
31
|
+
`"recallTypes": ["world", "experience"]` while switchroom writes
|
|
32
|
+
`["world", "experience", "observation"]` (`scaffold.ts:3449` as of writing;
|
|
33
|
+
line anchors drift โ grep `settings.recallTypes`).
|
|
34
|
+
|
|
35
|
+
So reading the vendored `settings.json` to learn live behaviour is actively
|
|
36
|
+
misleading. Read the override site in `scaffold.ts`, or the deployed
|
|
37
|
+
`~/.switchroom/agents/<name>/.claude/plugins/hindsight-memory/settings.json`.
|
|
38
|
+
|
|
39
|
+
## Editing
|
|
40
|
+
|
|
41
|
+
Don't hand-edit vendored upstream files without a reason (repo-root
|
|
42
|
+
`CLAUDE.md` ยง Secrets & safety rails). This file and switchroom's local
|
|
43
|
+
patches are the exceptions; changes here ship to every agent on the next
|
|
44
|
+
`apply`/reconcile, and the Python under `scripts/` is gated by the required
|
|
45
|
+
`python-ok` check (`python3 -m unittest discover` in `scripts`).
|
|
@@ -46,6 +46,20 @@ DEFAULTS = {
|
|
|
46
46
|
# cap of 6 its fleet actually deploys.
|
|
47
47
|
"recallOwnBankMinSlots": 0,
|
|
48
48
|
"recallAdditionalBankMinSlots": 0,
|
|
49
|
+
# Switchroom #3837: absolute floor on a result's engine relevance score
|
|
50
|
+
# (`scores.final`) for it to be injected. 0.0 (default) DISABLES the floor
|
|
51
|
+
# โ nothing is dropped and the injected set is byte-identical to the
|
|
52
|
+
# pre-#3837 behaviour. `recallMinScoreScope` decides which turns a
|
|
53
|
+
# non-zero floor binds on: "degraded" (default) = only turns where the
|
|
54
|
+
# agent's OWN bank timed out or was unreachable, which is the population
|
|
55
|
+
# where a below-floor score actually predicts noise (98.4% of degraded
|
|
56
|
+
# rows have a best injected score under 0.01, against 28.4% of healthy
|
|
57
|
+
# ones); "all" = every turn, which #3761's replay says empties ~28% of
|
|
58
|
+
# HEALTHY recalls at 0.01 and is not recommended as a fleet default. See
|
|
59
|
+
# the design note above `_filter_by_min_score` in recall.py. Env:
|
|
60
|
+
# HINDSIGHT_RECALL_MIN_SCORE / HINDSIGHT_RECALL_MIN_SCORE_SCOPE.
|
|
61
|
+
"recallMinScore": 0.0,
|
|
62
|
+
"recallMinScoreScope": "degraded",
|
|
49
63
|
"recallTypes": ["world", "experience"],
|
|
50
64
|
# Switchroom-local: when True (default; Ken-approved ON) recall biases
|
|
51
65
|
# toward synthesized `observation`-tier facts. Escape hatch: pin off via
|
|
@@ -324,6 +338,12 @@ ENV_OVERRIDES = {
|
|
|
324
338
|
# .additional_bank_min_slots (cascading through defaults). 0 = off.
|
|
325
339
|
"HINDSIGHT_RECALL_OWN_BANK_MIN_SLOTS": ("recallOwnBankMinSlots", int),
|
|
326
340
|
"HINDSIGHT_RECALL_ADDITIONAL_BANK_MIN_SLOTS": ("recallAdditionalBankMinSlots", int),
|
|
341
|
+
# Switchroom #3837: absolute `scores.final` floor + the population it binds
|
|
342
|
+
# on. Set by start.sh from agents.<name>.memory.recall.min_score /
|
|
343
|
+
# .min_score_scope (cascading through defaults), exported only when the
|
|
344
|
+
# operator opted in. 0.0 = off (the default, and the shipped behaviour).
|
|
345
|
+
"HINDSIGHT_RECALL_MIN_SCORE": ("recallMinScore", float),
|
|
346
|
+
"HINDSIGHT_RECALL_MIN_SCORE_SCOPE": ("recallMinScoreScope", str),
|
|
327
347
|
# Switchroom-local: recall fact types (comma-separated). Set by start.sh
|
|
328
348
|
# from agents.<name>.memory.recall.types only when the operator overrode
|
|
329
349
|
# the switchroom default (world,experience,observation) โ i.e. the
|
|
@@ -333,6 +353,19 @@ ENV_OVERRIDES = {
|
|
|
333
353
|
# from agents.<name>.memory.recall.skip_trivial only on override; the
|
|
334
354
|
# switchroom default is on (recall.py falls back to True).
|
|
335
355
|
"HINDSIGHT_RECALL_SKIP_TRIVIAL": ("recallSkipTrivial", bool),
|
|
356
|
+
# Switchroom #3841: the last three recall settings that had a config key but
|
|
357
|
+
# no env channel at all, so switchroom.yaml could not reach them and a
|
|
358
|
+
# hand-edit of the installed plugin did not survive `switchroom apply`. Set
|
|
359
|
+
# by start.sh from agents.<name>.memory.recall.prefer_observations / .roles /
|
|
360
|
+
# .prompt_preamble (cascading through defaults), always exported at their
|
|
361
|
+
# existing effective values, so an operator who sets none of them sees no
|
|
362
|
+
# change. The other #3841 knobs (budget, max_tokens, context_turns,
|
|
363
|
+
# max_query_chars, transcript_tail_bytes, tags, tags_match, tag_groups,
|
|
364
|
+
# tag_weights, additional_bank_filters, transcript_fallback, parallel)
|
|
365
|
+
# already had entries in this table and only needed the yaml surface.
|
|
366
|
+
"HINDSIGHT_RECALL_PREFER_OBSERVATIONS": ("recallPreferObservations", bool),
|
|
367
|
+
"HINDSIGHT_RECALL_ROLES": ("recallRoles", list),
|
|
368
|
+
"HINDSIGHT_RECALL_PROMPT_PREAMBLE": ("recallPromptPreamble", str),
|
|
336
369
|
# Switchroom #2848 Stage B: directive-capture nudge on/off. Set by
|
|
337
370
|
# start.sh from agents.<name>.memory.directive_capture_nudge only when
|
|
338
371
|
# the operator overrode it; the switchroom default is on (settings.json
|