switchroom 0.20.0 → 0.20.2

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.
Files changed (29) hide show
  1. package/bin/handoff-briefing.sh +213 -74
  2. package/dist/agent-scheduler/index.js +2 -2
  3. package/dist/auth-broker/index.js +4 -3
  4. package/dist/buzz-gateway/index.js +166 -6
  5. package/dist/cli/notion-write-pretool.mjs +2 -2
  6. package/dist/cli/switchroom.js +24704 -16399
  7. package/dist/host-control/main.js +44 -10
  8. package/dist/vault/approvals/kernel-server.js +4 -3
  9. package/dist/vault/broker/server.js +4 -3
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +79 -10
  12. package/telegram-plugin/dist/gateway/gateway.js +1400 -964
  13. package/telegram-plugin/gateway/access-store.test.ts +234 -0
  14. package/telegram-plugin/gateway/access-store.ts +194 -0
  15. package/telegram-plugin/gateway/boot-briefing-builder.ts +135 -7
  16. package/telegram-plugin/gateway/boot-briefing-capability.ts +31 -0
  17. package/telegram-plugin/gateway/boot-briefing-wiring.ts +166 -4
  18. package/telegram-plugin/gateway/buzz-mirror-correlation-store.ts +285 -0
  19. package/telegram-plugin/gateway/buzz-mirror.ts +177 -12
  20. package/telegram-plugin/gateway/gateway.ts +43 -123
  21. package/telegram-plugin/gateway/inbound-router.ts +93 -3
  22. package/telegram-plugin/gateway/outbound-send-path.ts +48 -1
  23. package/telegram-plugin/gateway/pending-turn-env.ts +10 -1
  24. package/telegram-plugin/tests/boot-briefing-builder.test.ts +422 -31
  25. package/telegram-plugin/tests/buzz-mirror-correlation-store.test.ts +173 -0
  26. package/telegram-plugin/tests/buzz-mirror.test.ts +297 -1
  27. package/telegram-plugin/tests/outbound-send-path.test.ts +24 -0
  28. package/telegram-plugin/tests/reply-to-buffer-fallback.test.ts +273 -0
  29. package/telegram-plugin/tests/reply-to-buffer-history.test.ts +134 -0
@@ -1,5 +1,5 @@
1
1
  // src/buzz-gateway/index.ts
2
- import { join as join2 } from "node:path";
2
+ import { join as join3 } from "node:path";
3
3
 
4
4
  // node_modules/.bun/@noble+hashes@2.0.1/node_modules/@noble/hashes/utils.js
5
5
  /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
@@ -8874,6 +8874,20 @@ async function publishOutbound(req, secretKey, transport, nowSec) {
8874
8874
  }
8875
8875
  return { ok: true, eventId: signed.eventId };
8876
8876
  }
8877
+ async function publishOutboundTallied(req, secretKey, transport, tally, nowSec) {
8878
+ let result;
8879
+ try {
8880
+ result = await publishOutbound(req, secretKey, transport, nowSec);
8881
+ } catch {
8882
+ tally.failed += 1;
8883
+ return { ok: false, error: "publish failed: transport threw" };
8884
+ }
8885
+ if (result.ok)
8886
+ tally.ok += 1;
8887
+ else
8888
+ tally.failed += 1;
8889
+ return result;
8890
+ }
8877
8891
 
8878
8892
  // src/buzz-gateway/inbound-map.ts
8879
8893
  var BUZZ_MESSAGE_KIND = 9;
@@ -8898,6 +8912,22 @@ function resolveThreadRoot(ev) {
8898
8912
  return eTags[0][1];
8899
8913
  return ev.id;
8900
8914
  }
8915
+ function resolveReplyParent(ev) {
8916
+ const eTags = ev.tags.filter((t) => t[0] === "e" && typeof t[1] === "string");
8917
+ if (eTags.length === 0)
8918
+ return;
8919
+ const hasMarkers = eTags.some((t) => t[3] === "root" || t[3] === "reply" || t[3] === "mention");
8920
+ if (hasMarkers) {
8921
+ const reply = eTags.find((t) => t[3] === "reply");
8922
+ if (reply)
8923
+ return reply[1];
8924
+ const root = eTags.find((t) => t[3] === "root");
8925
+ if (root)
8926
+ return root[1];
8927
+ return;
8928
+ }
8929
+ return eTags[eTags.length - 1][1];
8930
+ }
8901
8931
  function resolveChannelId(ev, fallback) {
8902
8932
  const h = ev.tags.find((t) => t[0] === "h" && typeof t[1] === "string");
8903
8933
  return h ? h[1] : fallback;
@@ -8907,14 +8937,16 @@ function mapBuzzEvent(ev, ctx) {
8907
8937
  return null;
8908
8938
  const channelId = resolveChannelId(ev, ctx.groupId);
8909
8939
  const threadRoot = resolveThreadRoot(ev);
8940
+ const replyTo = resolveReplyParent(ev);
8910
8941
  const user = senderLabel(ev.pubkey, ctx.pubkeyNames);
8911
- const text = `<channel source="buzz" ` + `buzz_channel_id="${escapeAttr(channelId)}" ` + `buzz_event_id="${escapeAttr(ev.id)}" ` + `buzz_pubkey="${escapeAttr(ev.pubkey)}" ` + `buzz_thread_root="${escapeAttr(threadRoot)}" ` + `user="${escapeAttr(user)}">` + escapeBody(ev.content) + `</channel>`;
8942
+ const text = `<channel source="buzz" ` + `buzz_channel_id="${escapeAttr(channelId)}" ` + `buzz_event_id="${escapeAttr(ev.id)}" ` + `buzz_pubkey="${escapeAttr(ev.pubkey)}" ` + `buzz_thread_root="${escapeAttr(threadRoot)}" ` + (replyTo !== undefined ? `buzz_reply_to="${escapeAttr(replyTo)}" ` : "") + `user="${escapeAttr(user)}">` + escapeBody(ev.content) + `</channel>`;
8912
8943
  const meta = {
8913
8944
  source: "buzz",
8914
8945
  buzz_channel_id: channelId,
8915
8946
  buzz_event_id: ev.id,
8916
8947
  buzz_pubkey: ev.pubkey,
8917
8948
  buzz_thread_root: threadRoot,
8949
+ ...replyTo !== undefined ? { buzz_reply_to: replyTo } : {},
8918
8950
  user
8919
8951
  };
8920
8952
  return {
@@ -9052,6 +9084,110 @@ function createRetryQueue(deps) {
9052
9084
  };
9053
9085
  }
9054
9086
 
9087
+ // src/buzz-gateway/heartbeat.ts
9088
+ import {
9089
+ mkdirSync as realMkdirSync,
9090
+ writeFileSync as realWriteFileSync
9091
+ } from "node:fs";
9092
+ import { dirname as dirname2, join as join2 } from "node:path";
9093
+ var BUZZ_HEARTBEAT_SUBDIR = "buzz";
9094
+ var BUZZ_HEARTBEAT_FILE = "buzz-sidecar.heartbeat.json";
9095
+ var BUZZ_HEARTBEAT_INTERVAL_MS = 60 * 1000;
9096
+ var BUZZ_HEARTBEAT_STALE_MULTIPLIER = 3;
9097
+ var BUZZ_HEARTBEAT_STALE_MS = BUZZ_HEARTBEAT_INTERVAL_MS * BUZZ_HEARTBEAT_STALE_MULTIPLIER;
9098
+ var BUZZ_HEARTBEAT_MAX_INTERVAL_MS = BUZZ_HEARTBEAT_STALE_MS / BUZZ_HEARTBEAT_STALE_MULTIPLIER;
9099
+ function resolveStatsIntervalMs(raw) {
9100
+ const requested = Number(raw);
9101
+ if (!Number.isFinite(requested) || requested <= 0) {
9102
+ return BUZZ_HEARTBEAT_INTERVAL_MS;
9103
+ }
9104
+ return Math.min(requested, BUZZ_HEARTBEAT_MAX_INTERVAL_MS);
9105
+ }
9106
+ function buzzHeartbeatStatePath(stateDir) {
9107
+ return join2(stateDir, BUZZ_HEARTBEAT_SUBDIR, BUZZ_HEARTBEAT_FILE);
9108
+ }
9109
+ function writeBuzzHeartbeat(path, hb, io = {}) {
9110
+ const mkdir = io.mkdirSync ?? realMkdirSync;
9111
+ const write = io.writeFileSync ?? realWriteFileSync;
9112
+ mkdir(dirname2(path), { recursive: true });
9113
+ write(path, JSON.stringify(hb));
9114
+ }
9115
+
9116
+ // src/buzz-gateway/stats.ts
9117
+ var REJECT_PREFIX = "rejected:";
9118
+ function summarizePipeline(pumpStats, mirror) {
9119
+ let received = 0;
9120
+ let authFailures = 0;
9121
+ for (const [key, count] of Object.entries(pumpStats)) {
9122
+ received += count;
9123
+ if (key.startsWith(REJECT_PREFIX))
9124
+ authFailures += count;
9125
+ }
9126
+ return {
9127
+ received,
9128
+ injected: pumpStats.injected ?? 0,
9129
+ duplicate: pumpStats.duplicate ?? 0,
9130
+ queued: pumpStats.queued ?? 0,
9131
+ injectFailed: pumpStats.inject_failed ?? 0,
9132
+ droppedByKind: pumpStats.unmapped ?? 0,
9133
+ channelOff: pumpStats.channel_off ?? 0,
9134
+ authFailures,
9135
+ mirrorOk: mirror.ok,
9136
+ mirrorFailed: mirror.failed
9137
+ };
9138
+ }
9139
+ function formatStatsLine(s) {
9140
+ return `buzz stats: received=${s.received} injected=${s.injected} ` + `duplicate=${s.duplicate} queued=${s.queued} inject_failed=${s.injectFailed} ` + `dropped_by_kind=${s.droppedByKind} channel_off=${s.channelOff} ` + `auth_failures=${s.authFailures} mirror_ok=${s.mirrorOk} mirror_failed=${s.mirrorFailed}`;
9141
+ }
9142
+ function createStatsReporter(deps) {
9143
+ const intervalMs = deps.intervalMs && deps.intervalMs > 0 ? deps.intervalMs : 60000;
9144
+ const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
9145
+ const clearTimer = deps.clearTimer ?? ((t) => clearTimeout(t));
9146
+ let timer = null;
9147
+ let stopped = false;
9148
+ let lastLine = null;
9149
+ function tick() {
9150
+ const sample = deps.sample();
9151
+ const line = formatStatsLine(sample.summary);
9152
+ if (line !== lastLine) {
9153
+ deps.emit(line);
9154
+ lastLine = line;
9155
+ }
9156
+ try {
9157
+ deps.persist?.(sample);
9158
+ } catch {}
9159
+ }
9160
+ function schedule() {
9161
+ if (stopped)
9162
+ return;
9163
+ timer = setTimer(() => {
9164
+ timer = null;
9165
+ if (stopped)
9166
+ return;
9167
+ tick();
9168
+ schedule();
9169
+ }, intervalMs);
9170
+ if (timer && typeof timer.unref === "function") {
9171
+ timer.unref();
9172
+ }
9173
+ }
9174
+ return {
9175
+ start() {
9176
+ stopped = false;
9177
+ tick();
9178
+ schedule();
9179
+ },
9180
+ stop() {
9181
+ stopped = true;
9182
+ if (timer !== null) {
9183
+ clearTimer(timer);
9184
+ timer = null;
9185
+ }
9186
+ },
9187
+ tick
9188
+ };
9189
+ }
9190
+
9055
9191
  // src/buzz-gateway/index.ts
9056
9192
  function log(msg) {
9057
9193
  process.stderr.write(`buzz-gateway: ${msg}
@@ -9123,8 +9259,8 @@ async function main() {
9123
9259
  const agentPubkey = getPublicKey(secretKey).toLowerCase();
9124
9260
  log(`booted agent=${config.agentName} relay=${config.relayUrl} group=${config.groupId} allowlist=${config.authorized.size}`);
9125
9261
  const stateDir = process.env.TELEGRAM_STATE_DIR ?? "/state/agent/telegram";
9126
- const socketPath = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join2(stateDir, "gateway.sock");
9127
- const journalPath = process.env.BUZZ_JOURNAL_PATH ?? join2(stateDir, "buzz", "journal.jsonl");
9262
+ const socketPath = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join3(stateDir, "gateway.sock");
9263
+ const journalPath = process.env.BUZZ_JOURNAL_PATH ?? join3(stateDir, "buzz", "journal.jsonl");
9128
9264
  const dedup = createDedupStore({ journalPath, log });
9129
9265
  const ipcClient = createInjectIpcClient({ socketPath, log });
9130
9266
  const inject = makeInject(ipcClient, config.agentName);
@@ -9158,16 +9294,17 @@ async function main() {
9158
9294
  log
9159
9295
  });
9160
9296
  const publishTransport = (event, timeoutMs) => nostr.publish(event, timeoutMs);
9297
+ const mirror = { ok: 0, failed: 0 };
9161
9298
  const buzzPeer = createBuzzPeerClient({
9162
9299
  socketPath,
9163
9300
  agentName: config.agentName,
9164
9301
  onOutbound: async (req) => {
9165
- const result = await publishOutbound({
9302
+ const result = await publishOutboundTallied({
9166
9303
  channelId: req.channelId,
9167
9304
  replyToEventId: req.replyToEventId,
9168
9305
  threadRootId: req.threadRootId,
9169
9306
  payload: req.payload
9170
- }, secretKey, publishTransport);
9307
+ }, secretKey, publishTransport, mirror);
9171
9308
  return {
9172
9309
  type: "buzz_publish_result",
9173
9310
  correlationId: req.correlationId,
@@ -9178,8 +9315,30 @@ async function main() {
9178
9315
  },
9179
9316
  log
9180
9317
  });
9318
+ const bootTs = Date.now();
9319
+ const heartbeatPath = buzzHeartbeatStatePath(stateDir);
9320
+ const statsIntervalMs = resolveStatsIntervalMs(process.env.BUZZ_STATS_INTERVAL_MS);
9321
+ const statsReporter = createStatsReporter({
9322
+ intervalMs: statsIntervalMs,
9323
+ sample: () => ({
9324
+ summary: summarizePipeline(pump.stats, mirror),
9325
+ subscribed: nostr.isSubscribed()
9326
+ }),
9327
+ emit: (line) => log(line),
9328
+ persist: (sample) => writeBuzzHeartbeat(heartbeatPath, {
9329
+ v: 1,
9330
+ agent: config.agentName,
9331
+ ts: Date.now(),
9332
+ bootTs,
9333
+ subscribed: sample.subscribed,
9334
+ stats: sample.summary
9335
+ })
9336
+ });
9181
9337
  const shutdown = () => {
9182
9338
  log("shutting down");
9339
+ try {
9340
+ statsReporter.stop();
9341
+ } catch {}
9183
9342
  try {
9184
9343
  nostr.stop();
9185
9344
  } catch {}
@@ -9200,6 +9359,7 @@ async function main() {
9200
9359
  process.on("SIGTERM", shutdown);
9201
9360
  process.on("SIGINT", shutdown);
9202
9361
  nostr.start();
9362
+ statsReporter.start();
9203
9363
  }
9204
9364
  main().catch((err) => {
9205
9365
  log(`FATAL: ${err.message}`);
@@ -12123,7 +12123,7 @@ var BuzzChannelSchema = exports_external.object({
12123
12123
  default_channel_id: exports_external.string().describe("Relay-minted group UUID (the NIP-29 `h` tag) the sidecar subscribes " + "to and stamps on injected turns."),
12124
12124
  channel_map: exports_external.record(exports_external.string(), exports_external.string()).default({}).describe("Optional map of extra group UUIDs \u2192 friendly labels."),
12125
12125
  pubkey_names: exports_external.record(exports_external.string(), exports_external.string()).default({}).describe("Optional petnames: hex/npub pubkey \u2192 display name, used to label " + "the sender on injected turns."),
12126
- pinned_relay_digest: exports_external.string().optional().describe("Pinned relay image digest (M4). The compat-check warns on mismatch; " + "advisory in Phase 1.")
12126
+ pinned_relay_digest: exports_external.string().optional().describe("Pinned relay image digest (M4). RESERVED \u2014 no consumer of this field " + "exists; the existing compat-check (compat-check.ts) validates only " + "the wire contract (AUTH kind, message kind, tag names) and does not " + "read this field. Kept in the schema so the intended digest-pin can " + "be wired without a config shape change.")
12127
12127
  }).strict();
12128
12128
  var ChannelsSchema = exports_external.object({
12129
12129
  telegram: TelegramChannelSchema,
@@ -12180,7 +12180,7 @@ var HindsightConfigSchema = exports_external.object({
12180
12180
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
12181
12181
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
12182
12182
  }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
12183
- env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT \u2014 the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
12183
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS \u2014 only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS \u2014 the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY \u2014 the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE \u2014 the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT \u2014 upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT \u2014 the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_TEMPORAL_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS \u2014 a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
12184
12184
  });
12185
12185
  var MicrosoftWorkspaceConfigSchema = exports_external.object({
12186
12186
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),