switchroom 0.19.25 → 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 +61 -2
- package/dist/auth-broker/index.js +125 -8
- package/dist/cli/notion-write-pretool.mjs +61 -2
- package/dist/cli/switchroom.js +2347 -1104
- package/dist/host-control/main.js +126 -9
- package/dist/vault/approvals/kernel-server.js +124 -8
- package/dist/vault/broker/server.js +124 -8
- package/package.json +6 -2
- package/profiles/_base/cron-session.sh.hbs +14 -0
- package/profiles/_base/start.sh.hbs +145 -4
- package/telegram-plugin/card-layout.ts +328 -0
- package/telegram-plugin/dist/bridge/bridge.js +93 -1
- package/telegram-plugin/dist/gateway/gateway.js +2213 -1204
- 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 +103 -112
- 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/periodic-sweep-guard.ts +86 -0
- package/telegram-plugin/gateway/pinned-message-handler.ts +12 -16
- package/telegram-plugin/gateway/status-pin-retarget.ts +180 -0
- 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 +11 -0
- package/telegram-plugin/status-pin-driver.ts +33 -17
- package/telegram-plugin/status-pin.ts +51 -5
- 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 +291 -0
- 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/periodic-sweep-guard.test.ts +151 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +24 -18
- 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 +244 -0
- package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +7 -3
- package/telegram-plugin/tests/status-pin-shutdown-wiring.test.ts +94 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +179 -64
- package/telegram-plugin/tests/status-pin.test.ts +184 -7
- 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 +29 -19
- 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/tier-downgrade.ts +3 -2
- package/telegram-plugin/tool-activity-summary.ts +239 -322
- package/telegram-plugin/uat/assertions.ts +33 -3
- package/telegram-plugin/uat/feed-matcher.test.ts +36 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +9 -2
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +9 -2
- package/telegram-plugin/worker-activity-feed.ts +109 -30
- 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/scripts/tests/test_recall_request_timeout.py +241 -0
- package/vendor/hindsight-memory/settings.json +1 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* We cannot import gateway.ts directly (its module top-level runs the whole boot
|
|
12
12
|
* IIFE incl. the startup mutex + bot.start). Instead we model the gateway's
|
|
13
13
|
* status-pin subsystem faithfully with the SAME functions gateway.ts calls
|
|
14
|
-
* (
|
|
14
|
+
* (runStatusPinReconcile, executePinLeg, runStatusPinBootCleanup) over an
|
|
15
15
|
* in-memory fs + a fake Telegram whose pin set we can inspect — so a regression
|
|
16
16
|
* in the ordering / cleanup contract reds here.
|
|
17
17
|
*
|
|
@@ -25,18 +25,28 @@ import { readFileSync } from "node:fs";
|
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
26
|
import {
|
|
27
27
|
loadStatusPins,
|
|
28
|
+
type PersistedStatusPin,
|
|
28
29
|
persistStatusPins,
|
|
29
30
|
reconcileAndPersistStatusPin,
|
|
30
31
|
runStatusPinBootCleanup,
|
|
31
32
|
type StatusPinStoreFsSeam,
|
|
32
33
|
} from "../gateway/status-pin-store.js";
|
|
33
|
-
import {
|
|
34
|
-
import type {
|
|
35
|
-
import {
|
|
34
|
+
import { executePinLeg } from "../status-pin-driver.js";
|
|
35
|
+
import type { DesiredPin, PinState } from "../status-pin.js";
|
|
36
|
+
import {
|
|
37
|
+
runStatusPinReconcile,
|
|
38
|
+
type StatusPinClaim,
|
|
39
|
+
} from "../gateway/status-pin-retarget.js";
|
|
36
40
|
import { makeFloodWaitActiveError } from "../retry-api-call.js";
|
|
37
41
|
|
|
38
42
|
const PATH = "/state/agent/telegram/status-pins.json";
|
|
39
43
|
|
|
44
|
+
/** Drop the #3810 claim-age stamp so a row's identity fields can be compared
|
|
45
|
+
* literally. The age itself is asserted in status-pin-store.test.ts. */
|
|
46
|
+
function idOnly(rows: PersistedStatusPin[]): Omit<PersistedStatusPin, "pinnedAt">[] {
|
|
47
|
+
return rows.map(({ pinnedAt: _age, ...rest }) => rest);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
function memFs(seed: Record<string, string> = {}) {
|
|
41
51
|
const files = new Map<string, string>(Object.entries(seed));
|
|
42
52
|
const fs: StatusPinStoreFsSeam = {
|
|
@@ -78,33 +88,22 @@ function fakeTelegram() {
|
|
|
78
88
|
* with injectable fs + api so a single test can span a "crash" and a fresh boot.
|
|
79
89
|
*/
|
|
80
90
|
function makeGateway(fs: StatusPinStoreFsSeam, tg: ReturnType<typeof fakeTelegram>) {
|
|
81
|
-
|
|
82
|
-
const
|
|
91
|
+
// ONE claim registry — the same single record the gateway keeps (#3809).
|
|
92
|
+
const statusPinClaims = new Map<string, StatusPinClaim>();
|
|
83
93
|
|
|
84
94
|
async function reconcileStatusPin(pinKey: string, chatId: string, desired: DesiredPin) {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
action.kind === "pin"
|
|
89
|
-
? ({ kind: "pin", messageId: action.messageId } as const)
|
|
90
|
-
: ({ kind: "clear" } as const);
|
|
91
|
-
const next = await reconcileAndPersistStatusPin({
|
|
92
|
-
path: PATH,
|
|
93
|
-
fs,
|
|
95
|
+
const claim = statusPinClaims.get(pinKey);
|
|
96
|
+
const prev: PinState | null = claim == null ? null : { messageId: claim.messageId };
|
|
97
|
+
await runStatusPinReconcile({
|
|
94
98
|
pinKey,
|
|
95
99
|
chatId,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
prev,
|
|
101
|
+
desired,
|
|
102
|
+
persist: { path: PATH, fs },
|
|
103
|
+
runPin: (action, from) =>
|
|
104
|
+
executePinLeg({ api: tg.api, chatId, prevState: from, action }),
|
|
105
|
+
claims: statusPinClaims,
|
|
100
106
|
});
|
|
101
|
-
if (next == null) {
|
|
102
|
-
statusPinState.delete(pinKey);
|
|
103
|
-
statusPinChatIds.delete(pinKey);
|
|
104
|
-
} else {
|
|
105
|
-
statusPinState.set(pinKey, next);
|
|
106
|
-
statusPinChatIds.set(pinKey, chatId);
|
|
107
|
-
}
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
async function bootCleanup() {
|
|
@@ -116,7 +115,7 @@ function makeGateway(fs: StatusPinStoreFsSeam, tg: ReturnType<typeof fakeTelegra
|
|
|
116
115
|
});
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
return {
|
|
118
|
+
return { statusPinClaims, reconcileStatusPin, bootCleanup };
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
describe("status-pin boot recovery (gateway wiring)", () => {
|
|
@@ -130,7 +129,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
130
129
|
|
|
131
130
|
// Pin is live in Telegram and recorded on disk (confirmed).
|
|
132
131
|
expect(tg.pinned.has("-100123:715")).toBe(true);
|
|
133
|
-
expect(loadStatusPins(PATH, fs)).toEqual([
|
|
132
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([
|
|
134
133
|
{ pinKey: "fg:c:3", chatId: "-100123", messageId: 715 },
|
|
135
134
|
]);
|
|
136
135
|
|
|
@@ -144,7 +143,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
144
143
|
|
|
145
144
|
expect(res).toEqual({ cleared: 1, retained: 0, kept: 0, total: 1 });
|
|
146
145
|
expect(tg.pinned.has("-100123:715")).toBe(false); // orphan unpinned
|
|
147
|
-
expect(loadStatusPins(PATH, fs)).toEqual([]); // store emptied
|
|
146
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([]); // store emptied
|
|
148
147
|
});
|
|
149
148
|
|
|
150
149
|
it("(B) pending pin (crash inside the persist-after-pin window) → recovered next boot", async () => {
|
|
@@ -171,7 +170,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
171
170
|
|
|
172
171
|
// Orphan is live in Telegram; a PENDING record is on disk.
|
|
173
172
|
expect(tg.pinned.has("-100123:715")).toBe(true);
|
|
174
|
-
expect(loadStatusPins(PATH, fs)).toEqual([
|
|
173
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([
|
|
175
174
|
{ pinKey: "fg:c:3", chatId: "-100123", messageId: 715, pending: true },
|
|
176
175
|
]);
|
|
177
176
|
|
|
@@ -180,7 +179,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
180
179
|
const res = await gw2.bootCleanup();
|
|
181
180
|
expect(res).toEqual({ cleared: 1, retained: 0, kept: 0, total: 1 });
|
|
182
181
|
expect(tg.pinned.has("-100123:715")).toBe(false);
|
|
183
|
-
expect(loadStatusPins(PATH, fs)).toEqual([]);
|
|
182
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([]);
|
|
184
183
|
});
|
|
185
184
|
|
|
186
185
|
it("(C) #3664: a never-confirmed unpin (FLOOD_WAIT_ACTIVE) KEEPS the claim + the durable row, and the next boot clears the pin", async () => {
|
|
@@ -216,9 +215,9 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
216
215
|
// The pin is STILL UP …
|
|
217
216
|
expect(pinned.has("-100123:715")).toBe(true);
|
|
218
217
|
// … and BOTH records survive: the in-memory claim …
|
|
219
|
-
expect(gw1.
|
|
218
|
+
expect(gw1.statusPinClaims.get("fg:c:3")?.messageId).toBe(715);
|
|
220
219
|
// … and the durable row (what boot cleanup reads).
|
|
221
|
-
expect(loadStatusPins(PATH, fs)).toEqual([
|
|
220
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([
|
|
222
221
|
{ pinKey: "fg:c:3", chatId: "-100123", messageId: 715 },
|
|
223
222
|
]);
|
|
224
223
|
|
|
@@ -226,8 +225,8 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
226
225
|
floodOpen = false;
|
|
227
226
|
await gw1.reconcileStatusPin("fg:c:3", "-100123", { pinned: false });
|
|
228
227
|
expect(pinned.has("-100123:715")).toBe(false);
|
|
229
|
-
expect(gw1.
|
|
230
|
-
expect(loadStatusPins(PATH, fs)).toEqual([]);
|
|
228
|
+
expect(gw1.statusPinClaims.has("fg:c:3")).toBe(false);
|
|
229
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([]);
|
|
231
230
|
});
|
|
232
231
|
|
|
233
232
|
it("(D) #3664: if the process dies while the flood window is open, the retained row lets the NEXT boot clear the orphan", async () => {
|
|
@@ -257,7 +256,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
257
256
|
const res = await gw2.bootCleanup();
|
|
258
257
|
expect(res).toEqual({ cleared: 1, retained: 0, kept: 0, total: 1 });
|
|
259
258
|
expect(pinned.has("-100123:715")).toBe(false);
|
|
260
|
-
expect(loadStatusPins(PATH, fs)).toEqual([]);
|
|
259
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([]);
|
|
261
260
|
});
|
|
262
261
|
|
|
263
262
|
it("clean shutdown (sweep DID run) leaves nothing for boot cleanup to do", async () => {
|
|
@@ -271,7 +270,7 @@ describe("status-pin boot recovery (gateway wiring)", () => {
|
|
|
271
270
|
// Clean sweep: unpin the key.
|
|
272
271
|
await gw1.reconcileStatusPin("fg:c:3", "-100123", { pinned: false });
|
|
273
272
|
expect(tg.pinned.size).toBe(0);
|
|
274
|
-
expect(loadStatusPins(PATH, fs)).toEqual([]);
|
|
273
|
+
expect(idOnly(loadStatusPins(PATH, fs))).toEqual([]);
|
|
275
274
|
|
|
276
275
|
const gw2 = makeGateway(fs, tg);
|
|
277
276
|
expect(await gw2.bootCleanup()).toEqual({ cleared: 0, retained: 0, kept: 0, total: 0 });
|