switchroom 0.20.12 → 0.20.13

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.
@@ -11433,6 +11433,7 @@ var init_schema = __esm(() => {
11433
11433
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
11434
11434
  HindsightConfigSchema = exports_external.object({
11435
11435
  gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) → autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit — switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
11436
+ mem_limit: exports_external.string().regex(/^\d+(\.\d+)?\s*[bkmgt]?b?$/i, "hindsight.mem_limit must be a docker memory size, e.g. `24g` or `16384m`").optional().describe("Hard memory cap for the hindsight container — docker `--memory` on the " + "`memory setup` path and `mem_limit:` in the emitted compose snippet. A " + "docker size string (`24g`, `16384m`). Absent → switchroom's default " + "(`HINDSIGHT_DEFAULT_MEM_LIMIT`, 16g). This is a docker HostConfig flag, " + "not a container env var, which is why it is a first-class field here " + "rather than an `env:` key. **Move it together with " + "`env.SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS`**: Postgres pins the " + "buffer pool as unreclaimable shared memory inside this cap, so a cap " + "that does not clear `shared_buffers` by the container's app working " + "set plus its page-cache floor puts Postgres in a near-OOM " + "configuration. switchroom warns, naming both numbers, when it does " + "not. A value below the container's memory RESERVATION (4g) is " + "rejected outright — docker will not create such a container."),
11436
11437
  cp_access_key: exports_external.string().min(1).optional().describe("Access key for the hindsight control-plane DASHBOARD (upstream " + "`HINDSIGHT_CP_ACCESS_KEY`, port 9999). Literal or — strongly preferred " + "— a `vault:` reference such as `vault:hindsight_cp_access_key`, read " + "through the broker at container-launch time. This is the ONLY thing " + "that arms the dashboard's login: upstream's middleware short-circuits " + "to `next()` when the var is unset, so an unset key means the dashboard " + "has no authentication at all, not weak authentication. Because of that, " + "leaving it unset is FAIL-CLOSED: switchroom pins " + "`HINDSIGHT_CP_HOSTNAME=127.0.0.1` so the loginless dashboard is " + "reachable only from the host, and warns. Set this to serve the " + "dashboard on the LAN/tailnet."),
11437
11438
  llm: exports_external.object({
11438
11439
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
@@ -21558,7 +21559,7 @@ function allocateAgentUid(name) {
21558
21559
  }
21559
21560
 
21560
21561
  // src/build-info.ts
21561
- var VERSION = "0.20.12";
21562
+ var VERSION = "0.20.13";
21562
21563
 
21563
21564
  // src/setup/hindsight-recall-passthrough.ts
21564
21565
  var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
@@ -21708,6 +21709,7 @@ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
21708
21709
  function pgMib(mib) {
21709
21710
  return `${mib}MB`;
21710
21711
  }
21712
+ var HINDSIGHT_PG_MIN_NON_BUFFER_MIB = HINDSIGHT_PG_APP_ANON_MIB + HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
21711
21713
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
21712
21714
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
21713
21715
  var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
@@ -21755,6 +21757,7 @@ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
21755
21757
  var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
21756
21758
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
21757
21759
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
21760
+ var HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search";
21758
21761
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
21759
21762
  [
21760
21763
  "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
@@ -21820,7 +21823,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
21820
21823
  [
21821
21824
  "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
21822
21825
  String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
21823
- ]
21826
+ ],
21827
+ ["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION]
21824
21828
  ];
21825
21829
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
21826
21830
  ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
@@ -4873,6 +4873,7 @@ var init_schema = __esm(() => {
4873
4873
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
4874
4874
  HindsightConfigSchema = exports_external.object({
4875
4875
  gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) → autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit — switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
4876
+ mem_limit: exports_external.string().regex(/^\d+(\.\d+)?\s*[bkmgt]?b?$/i, "hindsight.mem_limit must be a docker memory size, e.g. `24g` or `16384m`").optional().describe("Hard memory cap for the hindsight container — docker `--memory` on the " + "`memory setup` path and `mem_limit:` in the emitted compose snippet. A " + "docker size string (`24g`, `16384m`). Absent → switchroom's default " + "(`HINDSIGHT_DEFAULT_MEM_LIMIT`, 16g). This is a docker HostConfig flag, " + "not a container env var, which is why it is a first-class field here " + "rather than an `env:` key. **Move it together with " + "`env.SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS`**: Postgres pins the " + "buffer pool as unreclaimable shared memory inside this cap, so a cap " + "that does not clear `shared_buffers` by the container's app working " + "set plus its page-cache floor puts Postgres in a near-OOM " + "configuration. switchroom warns, naming both numbers, when it does " + "not. A value below the container's memory RESERVATION (4g) is " + "rejected outright — docker will not create such a container."),
4876
4877
  cp_access_key: exports_external.string().min(1).optional().describe("Access key for the hindsight control-plane DASHBOARD (upstream " + "`HINDSIGHT_CP_ACCESS_KEY`, port 9999). Literal or — strongly preferred " + "— a `vault:` reference such as `vault:hindsight_cp_access_key`, read " + "through the broker at container-launch time. This is the ONLY thing " + "that arms the dashboard's login: upstream's middleware short-circuits " + "to `next()` when the var is unset, so an unset key means the dashboard " + "has no authentication at all, not weak authentication. Because of that, " + "leaving it unset is FAIL-CLOSED: switchroom pins " + "`HINDSIGHT_CP_HOSTNAME=127.0.0.1` so the loginless dashboard is " + "reachable only from the host, and warns. Set this to serve the " + "dashboard on the LAN/tailnet."),
4877
4878
  llm: exports_external.object({
4878
4879
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
@@ -19603,6 +19604,7 @@ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
19603
19604
  function pgMib(mib) {
19604
19605
  return `${mib}MB`;
19605
19606
  }
19607
+ var HINDSIGHT_PG_MIN_NON_BUFFER_MIB = HINDSIGHT_PG_APP_ANON_MIB + HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
19606
19608
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
19607
19609
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
19608
19610
  var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
@@ -19650,6 +19652,7 @@ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
19650
19652
  var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
19651
19653
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
19652
19654
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
19655
+ var HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search";
19653
19656
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19654
19657
  [
19655
19658
  "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
@@ -19715,7 +19718,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19715
19718
  [
19716
19719
  "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
19717
19720
  String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
19718
- ]
19721
+ ],
19722
+ ["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION]
19719
19723
  ];
19720
19724
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
19721
19725
  ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
@@ -4469,6 +4469,7 @@ var init_schema = __esm(() => {
4469
4469
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
4470
4470
  HindsightConfigSchema = exports_external.object({
4471
4471
  gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) → autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit — switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
4472
+ mem_limit: exports_external.string().regex(/^\d+(\.\d+)?\s*[bkmgt]?b?$/i, "hindsight.mem_limit must be a docker memory size, e.g. `24g` or `16384m`").optional().describe("Hard memory cap for the hindsight container — docker `--memory` on the " + "`memory setup` path and `mem_limit:` in the emitted compose snippet. A " + "docker size string (`24g`, `16384m`). Absent → switchroom's default " + "(`HINDSIGHT_DEFAULT_MEM_LIMIT`, 16g). This is a docker HostConfig flag, " + "not a container env var, which is why it is a first-class field here " + "rather than an `env:` key. **Move it together with " + "`env.SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS`**: Postgres pins the " + "buffer pool as unreclaimable shared memory inside this cap, so a cap " + "that does not clear `shared_buffers` by the container's app working " + "set plus its page-cache floor puts Postgres in a near-OOM " + "configuration. switchroom warns, naming both numbers, when it does " + "not. A value below the container's memory RESERVATION (4g) is " + "rejected outright — docker will not create such a container."),
4472
4473
  cp_access_key: exports_external.string().min(1).optional().describe("Access key for the hindsight control-plane DASHBOARD (upstream " + "`HINDSIGHT_CP_ACCESS_KEY`, port 9999). Literal or — strongly preferred " + "— a `vault:` reference such as `vault:hindsight_cp_access_key`, read " + "through the broker at container-launch time. This is the ONLY thing " + "that arms the dashboard's login: upstream's middleware short-circuits " + "to `next()` when the var is unset, so an unset key means the dashboard " + "has no authentication at all, not weak authentication. Because of that, " + "leaving it unset is FAIL-CLOSED: switchroom pins " + "`HINDSIGHT_CP_HOSTNAME=127.0.0.1` so the loginless dashboard is " + "reachable only from the host, and warns. Set this to serve the " + "dashboard on the LAN/tailnet."),
4473
4474
  llm: exports_external.object({
4474
4475
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
@@ -20016,6 +20017,7 @@ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
20016
20017
  function pgMib(mib) {
20017
20018
  return `${mib}MB`;
20018
20019
  }
20020
+ var HINDSIGHT_PG_MIN_NON_BUFFER_MIB = HINDSIGHT_PG_APP_ANON_MIB + HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
20019
20021
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
20020
20022
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
20021
20023
  var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
@@ -20063,6 +20065,7 @@ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
20063
20065
  var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
20064
20066
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
20065
20067
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
20068
+ var HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search";
20066
20069
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
20067
20070
  [
20068
20071
  "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
@@ -20128,7 +20131,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
20128
20131
  [
20129
20132
  "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
20130
20133
  String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
20131
- ]
20134
+ ],
20135
+ ["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION]
20132
20136
  ];
20133
20137
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
20134
20138
  ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.20.12",
4
+ "version": "0.20.13",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -24,11 +24,11 @@
24
24
  "build": "node scripts/build.mjs",
25
25
  "build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
26
26
  "pretest": "npm run build",
27
- "test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/boot-briefing-builder.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-buzz-dedup.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/progress-fallback-cap.test.ts telegram-plugin/tests/progress-cap.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/worker-origin-gap-dispatch.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
27
+ "test": "vitest run && bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/hindsight-bank-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/boot-briefing-builder.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-buzz-dedup.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/progress-fallback-cap.test.ts telegram-plugin/tests/progress-cap.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/worker-origin-gap-dispatch.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
28
28
  "test:vitest": "vitest run",
29
- "test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/boot-briefing-builder.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-buzz-dedup.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/progress-fallback-cap.test.ts telegram-plugin/tests/progress-cap.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/worker-origin-gap-dispatch.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
29
+ "test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/hindsight-bank-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/boot-briefing-builder.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-buzz-dedup.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/rollout-narration-edit-socket.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/progress-fallback-cap.test.ts telegram-plugin/tests/progress-cap.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/worker-origin-gap-dispatch.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
30
30
  "test:watch": "vitest",
31
- "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts && bun scripts/check-hostd-template-guard.ts",
31
+ "lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-bench-baseline-anonymised.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-hindsight-bank-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-changelog-entry.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts && bun scripts/check-hostd-template-guard.ts",
32
32
  "lint:tsc": "tsc --noEmit",
33
33
  "lint:hindsight-write-redaction": "node scripts/check-hindsight-write-redaction.mjs",
34
34
  "lint:secret-pattern-parity": "bun scripts/check-secret-pattern-parity.ts",
@@ -40,6 +40,7 @@
40
40
  "lint:no-pii": "node scripts/check-no-pii-secrets.mjs",
41
41
  "lint:auth-test-hermeticity": "node scripts/check-auth-test-hermeticity.mjs",
42
42
  "lint:agent-state-dir-hermeticity": "node scripts/check-agent-state-dir-hermeticity.mjs",
43
+ "lint:hindsight-bank-hermeticity": "node scripts/check-hindsight-bank-hermeticity.mjs",
43
44
  "lint:web-subscription-honest": "node scripts/check-web-subscription-honest.mjs",
44
45
  "lint:no-broadcast-delivery": "node scripts/check-no-broadcast-delivery.mjs",
45
46
  "lint:gateway-line-ratchet": "node scripts/check-gateway-line-ratchet.mjs",
@@ -49,8 +50,10 @@
49
50
  "lint:mcp-instructions-budget": "node scripts/check-mcp-instructions-budget.mjs",
50
51
  "lint:litellm-config-guard": "node scripts/check-litellm-config-guard.mjs",
51
52
  "lint:release-asset-contract": "node scripts/check-release-asset-names.mjs",
53
+ "lint:changelog-entry": "node scripts/check-changelog-entry.mjs",
52
54
  "lint:agent-attribution-trailers": "node scripts/check-agent-attribution-trailers.mjs",
53
55
  "lint:hostd-template-guard": "bun scripts/check-hostd-template-guard.ts",
56
+ "lint:bench-baseline-anonymised": "node scripts/check-bench-baseline-anonymised.mjs",
54
57
  "prepublishOnly": "npm run build && npm run lint && npm test"
55
58
  },
56
59
  "dependencies": {
@@ -22,12 +22,13 @@ Cut a release of `switchroom/switchroom` and get it live on the fleet. This is a
22
22
  1. `git fetch origin`, confirm `main` is at the commit you want released.
23
23
  2. `gh pr list --state open` — confirm no PR meant for this release is still open. Ask the operator if unsure.
24
24
  3. Confirm CI on `main` is green (`gh run list --branch main --limit 3`).
25
- 4. Read `CHANGELOG.md` — the `## Unreleased` section is the staging area for this release's notes. If it's empty, there's nothing to release.
25
+ 4. Read `CHANGELOG.md` — the `## Unreleased` section is the **continuously-maintained** staging area for this release's notes. Every PR since the last release stages its own entry there as it merges, enforced by `scripts/check-changelog-entry.mjs` (part of `npm run lint`), so by release time this section should already read as a near-complete draft — you tidy and summarise, you do NOT author it from scratch. **An empty (or missing) `## Unreleased` at release time is now an ANOMALY, not the normal state:** it means either there genuinely is nothing to release, OR the enforcement was bypassed (`no-changelog` labels / `[skip changelog]` tokens on their own line on PRs that should have staged entries). Before assuming the former, cross-check against `git log <last-tag>..origin/main` — if real shippable work landed but Unreleased is bare, investigate and reconstruct rather than shipping a hollow release (this is exactly the v0.20.12 failure mode the enforcement exists to prevent).
26
26
  5. Pick the next version: read the latest tag (`git tag --list 'v*' --sort=-v:refname | head -1`) and bump the patch (or minor if the operator asks). Confirm with the operator which.
27
27
 
28
28
  ## Step 1 — Consolidate the changelog (the release commit is CHANGELOG-only)
29
29
 
30
- - Move the `## Unreleased` entries under a new `## vX.Y.Z — <one-line summary>` heading.
30
+ - The section is maintained continuously (see pre-flight 4), so this step is now a **rename + tidy**, not authorship: change the `## Unreleased` header to `## vX.Y.Z — <one-line summary>`, then read through the staged entries and lightly edit for grouping/summary. If Unreleased was empty or thin, STOP and resolve the anomaly (pre-flight 4) before proceeding — do not invent a release note.
31
+ - **Re-seed a fresh empty `## Unreleased` block at the top** (header + the convention HTML comment) immediately below `# Changelog`, so the staging area exists for the next cycle and `scripts/check-changelog-entry.mjs` keeps enforcing it. (Copy the comment block from the section you just renamed.)
31
32
  - The release commit touches **CHANGELOG.md only**. Do NOT bump `package.json` (placeholder discipline).
32
33
  - Branch protection blocks direct push to `main`, so: create a `release/vX.Y.Z` branch, push it, open a `chore: release vX.Y.Z` PR (base `main`), arm auto-merge (squash, delete-branch) on green CI.
33
34
 
@@ -2,9 +2,13 @@
2
2
  # `bun-test-run` job (scripts/bun-test-ci.sh) and `bun run --cwd telegram-plugin`.
3
3
  #
4
4
  # bun reads the bunfig.toml in its CWD only, so this file exists purely to load
5
- # the same state-dir hermeticity preload as the repo-root bunfig.toml. Keep the
6
- # two in sync; `npm run lint:agent-state-dir-hermeticity` fails if either stops
7
- # loading the guard. Rationale for the guard itself lives in
8
- # tests/vitest-setup/agent-state-dir-guard.mjs.
5
+ # the same shared-state hermeticity preloads as the repo-root bunfig.toml. Keep
6
+ # the two in sync; `npm run lint:agent-state-dir-hermeticity` and
7
+ # `npm run lint:hindsight-bank-hermeticity` fail if either bunfig stops loading
8
+ # either guard. Rationale lives in tests/vitest-setup/agent-state-dir-guard.mjs
9
+ # and tests/vitest-setup/hindsight-bank-guard.mjs.
9
10
  [test]
10
- preload = ["../tests/vitest-setup/agent-state-dir-guard.mjs"]
11
+ preload = [
12
+ "../tests/vitest-setup/agent-state-dir-guard.mjs",
13
+ "../tests/vitest-setup/hindsight-bank-guard.mjs",
14
+ ]
@@ -22025,6 +22025,7 @@ var init_schema = __esm(() => {
22025
22025
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
22026
22026
  HindsightConfigSchema = exports_external.object({
22027
22027
  gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) \u2192 autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit \u2014 switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
22028
+ mem_limit: exports_external.string().regex(/^\d+(\.\d+)?\s*[bkmgt]?b?$/i, "hindsight.mem_limit must be a docker memory size, e.g. `24g` or `16384m`").optional().describe("Hard memory cap for the hindsight container \u2014 docker `--memory` on the " + "`memory setup` path and `mem_limit:` in the emitted compose snippet. A " + "docker size string (`24g`, `16384m`). Absent \u2192 switchroom's default " + "(`HINDSIGHT_DEFAULT_MEM_LIMIT`, 16g). This is a docker HostConfig flag, " + "not a container env var, which is why it is a first-class field here " + "rather than an `env:` key. **Move it together with " + "`env.SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS`**: Postgres pins the " + "buffer pool as unreclaimable shared memory inside this cap, so a cap " + "that does not clear `shared_buffers` by the container's app working " + "set plus its page-cache floor puts Postgres in a near-OOM " + "configuration. switchroom warns, naming both numbers, when it does " + "not. A value below the container's memory RESERVATION (4g) is " + "rejected outright \u2014 docker will not create such a container."),
22028
22029
  cp_access_key: exports_external.string().min(1).optional().describe("Access key for the hindsight control-plane DASHBOARD (upstream " + "`HINDSIGHT_CP_ACCESS_KEY`, port 9999). Literal or \u2014 strongly preferred " + "\u2014 a `vault:` reference such as `vault:hindsight_cp_access_key`, read " + "through the broker at container-launch time. This is the ONLY thing " + "that arms the dashboard's login: upstream's middleware short-circuits " + "to `next()` when the var is unset, so an unset key means the dashboard " + "has no authentication at all, not weak authentication. Because of that, " + "leaving it unset is FAIL-CLOSED: switchroom pins " + "`HINDSIGHT_CP_HOSTNAME=127.0.0.1` so the loginless dashboard is " + "reachable only from the host, and warns. Set this to serve the " + "dashboard on the LAN/tailnet."),
22029
22030
  llm: exports_external.object({
22030
22031
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
@@ -79042,6 +79043,17 @@ import { homedir as homedir9 } from "node:os";
79042
79043
  // hooks/audience-classify.mjs
79043
79044
  var AUDIENCE_USER = "user";
79044
79045
  var AUDIENCE_INTERNAL = "internal";
79046
+ function decideCaptureAudience(signals) {
79047
+ if (signals?.reviewOriginated === true) {
79048
+ return signals?.reviewTextIsCard === true ? AUDIENCE_USER : AUDIENCE_INTERNAL;
79049
+ }
79050
+ const threw = signals?.replyToolThrewThisTurn === true;
79051
+ if (!threw)
79052
+ return AUDIENCE_USER;
79053
+ if (signals?.openInboundObligation !== false)
79054
+ return AUDIENCE_USER;
79055
+ return AUDIENCE_INTERNAL;
79056
+ }
79045
79057
  function resolveRecordAudience(value) {
79046
79058
  return value === AUDIENCE_INTERNAL ? AUDIENCE_INTERNAL : AUDIENCE_USER;
79047
79059
  }
@@ -79068,6 +79080,31 @@ function formatReplyThrowFraming(s) {
79068
79080
  return `telegram gateway: outbox provenance framing nonce=${s.turnNonce} ` + `turnId=${s.turnId ?? "unknown"} chatId=${s.chatId ?? "unresolved"} ` + `sha=${(s.textSha256 ?? "").slice(0, 12)} source=${s.source ?? "unknown"} ` + `audience=user replyToolThrew=true \u2014 captured prose delivered with its ` + `provenance stated, not suppressed
79069
79081
  `;
79070
79082
  }
79083
+ var SELF_IMPROVEMENT_TITLE = "\uD83D\uDD27 **Self-improvement**";
79084
+ function isSelfImprovementCard(text4) {
79085
+ if (typeof text4 !== "string")
79086
+ return false;
79087
+ return text4.trimStart().startsWith(SELF_IMPROVEMENT_TITLE);
79088
+ }
79089
+ function shouldFrameSelfImprovement(record, opts) {
79090
+ if (opts?.frameEnabled === false)
79091
+ return false;
79092
+ return record?.reviewOriginated === true;
79093
+ }
79094
+ function applySelfImprovementFraming(text4) {
79095
+ const body = typeof text4 === "string" ? text4 : "";
79096
+ if (body.trim().length === 0)
79097
+ return body;
79098
+ if (isSelfImprovementCard(body))
79099
+ return body;
79100
+ return `${SELF_IMPROVEMENT_TITLE}
79101
+
79102
+ ${body}`;
79103
+ }
79104
+ function formatSelfImprovementFraming(s) {
79105
+ return `telegram gateway: outbox self-improvement framing nonce=${s.turnNonce} ` + `turnId=${s.turnId ?? "unknown"} chatId=${s.chatId ?? "unresolved"} ` + `sha=${(s.textSha256 ?? "").slice(0, 12)} source=${s.source ?? "unknown"} ` + `reviewOriginated=true \u2014 review-turn prose delivered with its self-improvement ` + `title, never as raw unlabelled reasoning
79106
+ `;
79107
+ }
79071
79108
  function formatInternalSuppression(s) {
79072
79109
  return `telegram gateway: outbox audience suppression nonce=${s.turnNonce} ` + `turnId=${s.turnId ?? "unknown"} chatId=${s.chatId ?? "unresolved"} ` + `sha=${(s.textSha256 ?? "").slice(0, 12)} ageMs=${s.ageMs ?? 0} ` + `source=${s.source ?? "unknown"} pastWindow=${s.pastWindow === true} ` + `audience=internal \u2014 internal prose withheld from chat, journaled terminally
79073
79110
  `;
@@ -79254,7 +79291,8 @@ function decideOutboxSweep(input) {
79254
79291
  quietMs = OUTBOX_QUIET_MS,
79255
79292
  maxAgeMs = OUTBOX_MAX_AGE_MS,
79256
79293
  shownLedgerHit = false,
79257
- provenanceFraming = true
79294
+ provenanceFraming = true,
79295
+ selfImprovementFraming = true
79258
79296
  } = input;
79259
79297
  if (deliveredNonces.has(record.turnNonce))
79260
79298
  return { action: "skip-journaled" };
@@ -79270,11 +79308,14 @@ function decideOutboxSweep(input) {
79270
79308
  const delayed = age > maxAgeMs;
79271
79309
  const prefix = (delayed ? "(delayed) " : "") + routePrefix;
79272
79310
  const framed = shouldFrameReplyThrow(record, { frameEnabled: provenanceFraming });
79273
- const body = framed ? applyReplyThrowFraming(record.text) : record.text;
79311
+ const provenanceBody = framed ? applyReplyThrowFraming(record.text) : record.text;
79312
+ const selfImproveFramed = shouldFrameSelfImprovement(record, { frameEnabled: selfImprovementFraming }) && !isSelfImprovementCard(record.text);
79313
+ const body = selfImproveFramed ? applySelfImprovementFraming(provenanceBody) : provenanceBody;
79274
79314
  return {
79275
79315
  action: delayed ? "send-delayed" : "send",
79276
79316
  text: prefix + body,
79277
- ...framed && body !== record.text ? { framedProvenance: "reply-throw" } : {}
79317
+ ...framed && provenanceBody !== record.text ? { framedProvenance: "reply-throw" } : {},
79318
+ ...selfImproveFramed && body !== provenanceBody ? { framedSelfImprovement: "self-improve" } : {}
79278
79319
  };
79279
79320
  }
79280
79321
  function resolveOutboxChat(record, deps) {
@@ -79605,7 +79646,10 @@ function journalExternalDelivery(args, stateDir, now = Date.now()) {
79605
79646
  ts: now,
79606
79647
  deliverySource: args.deliverySource ?? "reply-tool",
79607
79648
  ...args.replyAlreadyDeliveredThisTurn == null ? {} : { replyAlreadyDeliveredThisTurn: args.replyAlreadyDeliveredThisTurn },
79608
- ...args.framedProvenance == null ? {} : { framedProvenance: args.framedProvenance }
79649
+ ...args.framedProvenance == null ? {} : { framedProvenance: args.framedProvenance },
79650
+ ...args.audience == null ? {} : { audience: args.audience },
79651
+ ...args.suppressedAudience == null ? {} : { suppressedAudience: args.suppressedAudience },
79652
+ ...args.framedSelfImprovement == null ? {} : { framedSelfImprovement: args.framedSelfImprovement }
79609
79653
  }, stateDir);
79610
79654
  clearOutboxRecord(nonce, stateDir);
79611
79655
  }
@@ -80285,7 +80329,8 @@ function decideCapturedProseDelivery(args, deps) {
80285
80329
  deliver: true,
80286
80330
  text: text4,
80287
80331
  reason: "captured-prose",
80288
- ...state3.replyToolThrewThisTurn === true ? { replyToolThrewThisTurn: true } : {}
80332
+ ...state3.replyToolThrewThisTurn === true ? { replyToolThrewThisTurn: true } : {},
80333
+ ...state3.reviewOriginated === true ? { reviewOriginated: true } : {}
80289
80334
  };
80290
80335
  }
80291
80336
  function settleCapturedProseDelivery(outcome, effects) {
@@ -81366,67 +81411,104 @@ async function deliverCapturedProse(deps, args) {
81366
81411
  const { chatId, threadId, statusKeyStr, registryKey, originTurnId, text: text4, turnDurationMs } = args;
81367
81412
  const now = Date.now();
81368
81413
  const framed = shouldFrameReplyThrow({ replyToolThrewThisTurn: args.replyToolThrewThisTurn }, { frameEnabled: process.env.SWITCHROOM_TG_OUTBOX_PROVENANCE_FRAMING !== "0" });
81414
+ const audienceGateEnabled = process.env.SWITCHROOM_TG_OUTBOX_AUDIENCE_GATE !== "0";
81415
+ const audience = decideCaptureAudience({
81416
+ reviewOriginated: args.reviewOriginated === true,
81417
+ reviewTextIsCard: isSelfImprovementCard(text4)
81418
+ });
81419
+ const suppressed = audienceGateEnabled && audience === AUDIENCE_INTERNAL;
81420
+ const selfImproveFramed = !suppressed && shouldFrameSelfImprovement({ reviewOriginated: args.reviewOriginated }, { frameEnabled: process.env.SWITCHROOM_TG_OUTBOX_SELF_IMPROVE_FRAMING !== "0" }) && !isSelfImprovementCard(text4);
81369
81421
  let outcome;
81370
- const already = outboundDedup.check(chatId, threadId, text4, now, registryKey);
81371
- if (already == null) {
81372
- let out = normalizeParagraphBreaks(repairEscapedWhitespace(framed ? applyReplyThrowFraming(text4) : text4));
81373
- out = redactOutboundText(out, "captured_prose");
81374
- const chunks = splitMarkdownChunks(out, RICH_MESSAGE_MAX_CHARS);
81375
- const sentIds = [];
81376
- try {
81377
- let liveThreadId = threadId;
81378
- for (const c of chunks) {
81379
- const sent = await retryWithThreadFallback(robustApiCall, (tid) => {
81380
- const opts = {
81381
- link_preview_options: { is_disabled: true },
81382
- ...tid != null ? { message_thread_id: tid } : {}
81383
- };
81384
- return bot.api.sendRichMessage(chatId, richMessage(c), opts);
81385
- }, { threadId: liveThreadId, chat_id: chatId, verb: "captured-prose.sendMessage" });
81386
- if (liveThreadId != null && sent.message_thread_id == null) {
81387
- liveThreadId = undefined;
81422
+ if (suppressed) {
81423
+ journalExternalDelivery({
81424
+ turnNonce: originTurnId,
81425
+ text: text4,
81426
+ replyAlreadyDeliveredThisTurn: false,
81427
+ audience,
81428
+ suppressedAudience: AUDIENCE_INTERNAL
81429
+ });
81430
+ process.stderr.write(formatInternalSuppression({
81431
+ turnNonce: originTurnId,
81432
+ turnId: originTurnId,
81433
+ chatId,
81434
+ textSha256: sha256Hex(text4),
81435
+ source: "captured-prose-bridge"
81436
+ }));
81437
+ outcome = "suppressed-internal";
81438
+ } else {
81439
+ const already = outboundDedup.check(chatId, threadId, text4, now, registryKey);
81440
+ if (already == null) {
81441
+ let out = normalizeParagraphBreaks(repairEscapedWhitespace(framed ? applyReplyThrowFraming(text4) : text4));
81442
+ out = selfImproveFramed ? applySelfImprovementFraming(out) : out;
81443
+ out = redactOutboundText(out, "captured_prose");
81444
+ const chunks = splitMarkdownChunks(out, RICH_MESSAGE_MAX_CHARS);
81445
+ const sentIds = [];
81446
+ try {
81447
+ let liveThreadId = threadId;
81448
+ for (const c of chunks) {
81449
+ const sent = await retryWithThreadFallback(robustApiCall, (tid) => {
81450
+ const opts = {
81451
+ link_preview_options: { is_disabled: true },
81452
+ ...tid != null ? { message_thread_id: tid } : {}
81453
+ };
81454
+ return bot.api.sendRichMessage(chatId, richMessage(c), opts);
81455
+ }, { threadId: liveThreadId, chat_id: chatId, verb: "captured-prose.sendMessage" });
81456
+ if (liveThreadId != null && sent.message_thread_id == null) {
81457
+ liveThreadId = undefined;
81458
+ }
81459
+ sentIds.push(sent.message_id);
81388
81460
  }
81389
- sentIds.push(sent.message_id);
81390
- }
81391
- if (HISTORY_ENABLED && sentIds.length > 0) {
81392
- try {
81393
- recordOutbound2({
81394
- chat_id: chatId,
81395
- thread_id: threadId ?? null,
81396
- message_ids: sentIds,
81397
- texts: chunks
81398
- });
81399
- } catch {}
81400
- }
81401
- outboundDedup.record(chatId, threadId, text4, now, registryKey);
81402
- journalExternalDelivery({
81403
- turnNonce: originTurnId,
81404
- text: text4,
81405
- tgMessageId: sentIds[sentIds.length - 1],
81406
- replyAlreadyDeliveredThisTurn: false,
81407
- ...framed ? { framedProvenance: "reply-throw" } : {}
81408
- });
81409
- process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from ` + `transcript scan (chat=${chatId} origin=${originTurnId})
81410
- `);
81411
- if (framed) {
81412
- process.stderr.write(formatReplyThrowFraming({
81461
+ if (HISTORY_ENABLED && sentIds.length > 0) {
81462
+ try {
81463
+ recordOutbound2({
81464
+ chat_id: chatId,
81465
+ thread_id: threadId ?? null,
81466
+ message_ids: sentIds,
81467
+ texts: chunks
81468
+ });
81469
+ } catch {}
81470
+ }
81471
+ outboundDedup.record(chatId, threadId, text4, now, registryKey);
81472
+ journalExternalDelivery({
81413
81473
  turnNonce: originTurnId,
81414
- turnId: originTurnId,
81415
- chatId,
81416
- textSha256: sha256Hex(text4),
81417
- source: "captured-prose-bridge"
81418
- }));
81474
+ text: text4,
81475
+ tgMessageId: sentIds[sentIds.length - 1],
81476
+ replyAlreadyDeliveredThisTurn: false,
81477
+ ...framed ? { framedProvenance: "reply-throw" } : {},
81478
+ ...selfImproveFramed ? { framedSelfImprovement: "self-improve" } : {},
81479
+ audience
81480
+ });
81481
+ process.stderr.write(`telegram gateway: captured-prose delivery \u2014 sent ${out.length} chars recovered from ` + `transcript scan (chat=${chatId} origin=${originTurnId})
81482
+ `);
81483
+ if (framed) {
81484
+ process.stderr.write(formatReplyThrowFraming({
81485
+ turnNonce: originTurnId,
81486
+ turnId: originTurnId,
81487
+ chatId,
81488
+ textSha256: sha256Hex(text4),
81489
+ source: "captured-prose-bridge"
81490
+ }));
81491
+ }
81492
+ if (selfImproveFramed) {
81493
+ process.stderr.write(formatSelfImprovementFraming({
81494
+ turnNonce: originTurnId,
81495
+ turnId: originTurnId,
81496
+ chatId,
81497
+ textSha256: sha256Hex(text4),
81498
+ source: "captured-prose-bridge"
81499
+ }));
81500
+ }
81501
+ outcome = "sent";
81502
+ } catch (err) {
81503
+ process.stderr.write(`telegram gateway: captured-prose delivery failed: ${err.message} \u2014 ` + `arming the silent-end re-prompt net (recordUndeliveredTurnEnd) so the ` + `answer is recoverable (chat=${chatId} origin=${originTurnId})
81504
+ `);
81505
+ outcome = "failed";
81419
81506
  }
81420
- outcome = "sent";
81421
- } catch (err) {
81422
- process.stderr.write(`telegram gateway: captured-prose delivery failed: ${err.message} \u2014 ` + `arming the silent-end re-prompt net (recordUndeliveredTurnEnd) so the ` + `answer is recoverable (chat=${chatId} origin=${originTurnId})
81507
+ } else {
81508
+ process.stderr.write(`telegram gateway: captured-prose delivery skipped \u2014 this answer already went out ` + `(dedup age=${already.ageMs}ms chat=${chatId} origin=${originTurnId}); settling bookkeeping
81423
81509
  `);
81424
- outcome = "failed";
81510
+ outcome = "skipped-dedup";
81425
81511
  }
81426
- } else {
81427
- process.stderr.write(`telegram gateway: captured-prose delivery skipped \u2014 this answer already went out ` + `(dedup age=${already.ageMs}ms chat=${chatId} origin=${originTurnId}); settling bookkeeping
81428
- `);
81429
- outcome = "skipped-dedup";
81430
81512
  }
81431
81513
  const settlement = settleCapturedProseDelivery(outcome, {
81432
81514
  closeObligation: () => {
@@ -83909,6 +83991,7 @@ function handleSessionEvent(deps, ev) {
83909
83991
  originTurnId: turn.turnId,
83910
83992
  text: proseDecision.text,
83911
83993
  replyToolThrewThisTurn: proseDecision.replyToolThrewThisTurn === true,
83994
+ reviewOriginated: proseDecision.reviewOriginated === true,
83912
83995
  turnDurationMs
83913
83996
  });
83914
83997
  } else {
@@ -87898,6 +87981,7 @@ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
87898
87981
  function pgMib(mib) {
87899
87982
  return `${mib}MB`;
87900
87983
  }
87984
+ var HINDSIGHT_PG_MIN_NON_BUFFER_MIB = HINDSIGHT_PG_APP_ANON_MIB + HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
87901
87985
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
87902
87986
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
87903
87987
  var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
@@ -87945,6 +88029,7 @@ var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
87945
88029
  var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
87946
88030
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
87947
88031
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
88032
+ var HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search";
87948
88033
  var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
87949
88034
  [
87950
88035
  "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
@@ -88010,7 +88095,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
88010
88095
  [
88011
88096
  "HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
88012
88097
  String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
88013
- ]
88098
+ ],
88099
+ ["HINDSIGHT_API_TEXT_SEARCH_EXTENSION", HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION]
88014
88100
  ];
88015
88101
  var HINDSIGHT_PERF_DEFAULTS_GPU = [
88016
88102
  ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16],
@@ -94212,6 +94298,7 @@ function hydrateResumeLedger(ledger, turnId, snapshot, hasLanded) {
94212
94298
  }
94213
94299
  function createCapturedResumeDispatcher(ports) {
94214
94300
  const stderr = ports.stderr ?? (() => {});
94301
+ const hasOutboundWithText3 = ports.hasOutboundWithText ?? hasOutboundWithText2;
94215
94302
  const inFlight = new Set;
94216
94303
  function deliver(o, snapshot) {
94217
94304
  return ports.deliverAnswer({
@@ -94223,7 +94310,7 @@ function createCapturedResumeDispatcher(ports) {
94223
94310
  replyToMessageId: o.messageId,
94224
94311
  resume: {
94225
94312
  snapshot,
94226
- hydrate: (ledger, turnId) => hydrateResumeLedger(ledger, turnId, snapshot, (txt) => ports.historyEnabled && hasOutboundWithText2(o.chatId, txt, o.threadId ?? null, o.openedAt))
94313
+ hydrate: (ledger, turnId) => hydrateResumeLedger(ledger, turnId, snapshot, (txt) => ports.historyEnabled && hasOutboundWithText3(o.chatId, txt, o.threadId ?? null, o.openedAt))
94227
94314
  }
94228
94315
  });
94229
94316
  }
@@ -103030,6 +103117,8 @@ async function sweepOutbox(deps) {
103030
103117
  const escalateInternalSuppression = deps.escalateInternalSuppression ?? log;
103031
103118
  const provenanceFramingEnabled = deps.provenanceFramingEnabled?.() ?? process.env.SWITCHROOM_TG_OUTBOX_PROVENANCE_FRAMING !== "0";
103032
103119
  const logProvenanceFraming = deps.logProvenanceFraming ?? log;
103120
+ const selfImprovementFramingEnabled = deps.selfImprovementFramingEnabled?.() ?? process.env.SWITCHROOM_TG_OUTBOX_SELF_IMPROVE_FRAMING !== "0";
103121
+ const logSelfImprovementFraming = deps.logSelfImprovementFraming ?? log;
103033
103122
  for (const record2 of records) {
103034
103123
  summary.scanned++;
103035
103124
  if (shouldSuppressForAudience(record2, { gateEnabled: audienceGateEnabled })) {
@@ -103075,7 +103164,8 @@ async function sweepOutbox(deps) {
103075
103164
  routePrefix,
103076
103165
  quietMs: deps.quietMs ?? OUTBOX_QUIET_MS,
103077
103166
  shownLedgerHit: isShownBlock(record2.turnNonce, record2.text, deps.stateDir),
103078
- provenanceFraming: provenanceFramingEnabled
103167
+ provenanceFraming: provenanceFramingEnabled,
103168
+ selfImprovementFraming: selfImprovementFramingEnabled
103079
103169
  });
103080
103170
  if (decision.action !== "send" && decision.action !== "send-delayed") {
103081
103171
  summary.skipped++;
@@ -103119,7 +103209,8 @@ async function sweepOutbox(deps) {
103119
103209
  ts: now,
103120
103210
  deliverySource: "sweep",
103121
103211
  replyAlreadyDeliveredThisTurn: record2.replyAlreadyDeliveredThisTurn === true,
103122
- ...decision.framedProvenance != null ? { framedProvenance: decision.framedProvenance } : {}
103212
+ ...decision.framedProvenance != null ? { framedProvenance: decision.framedProvenance } : {},
103213
+ ...decision.framedSelfImprovement != null ? { framedSelfImprovement: decision.framedSelfImprovement } : {}
103123
103214
  }, deps.stateDir);
103124
103215
  if (decision.framedProvenance != null) {
103125
103216
  summary.provenanceFramed = (summary.provenanceFramed ?? 0) + 1;
@@ -103131,6 +103222,16 @@ async function sweepOutbox(deps) {
103131
103222
  source: record2.source
103132
103223
  }));
103133
103224
  }
103225
+ if (decision.framedSelfImprovement != null) {
103226
+ summary.selfImprovementFramed = (summary.selfImprovementFramed ?? 0) + 1;
103227
+ logSelfImprovementFraming(formatSelfImprovementFraming({
103228
+ turnNonce: record2.turnNonce,
103229
+ turnId: turnIdFromNonce(record2.turnNonce),
103230
+ chatId: resolvedChat.chatId,
103231
+ textSha256: record2.textSha256,
103232
+ source: record2.source
103233
+ }));
103234
+ }
103134
103235
  if (deps.recordOutbound != null && sendResult.chunks.length > 0) {
103135
103236
  try {
103136
103237
  deps.recordOutbound(resolvedChat.chatId, resolvedChat.threadId, sendResult.chunks.map((c) => c.messageId), sendResult.chunks.map((c) => c.text));
@@ -103337,10 +103438,10 @@ function startOutboxSweep(deps) {
103337
103438
  }
103338
103439
 
103339
103440
  // ../src/build-info.ts
103340
- var VERSION2 = "0.20.12";
103341
- var COMMIT_SHA = "298e1e15";
103342
- var COMMIT_DATE = "2026-08-06T12:14:41Z";
103343
- var LATEST_PR = 4468;
103441
+ var VERSION2 = "0.20.13";
103442
+ var COMMIT_SHA = "a73b0fd2";
103443
+ var COMMIT_DATE = "2026-08-07T03:01:37Z";
103444
+ var LATEST_PR = 4502;
103344
103445
  var COMMITS_AHEAD_OF_TAG = 0;
103345
103446
 
103346
103447
  // gateway/boot-version.ts