switchroom 0.19.19 → 0.19.23

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 (76) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +121 -1
  3. package/dist/cli/notion-write-pretool.mjs +2 -1
  4. package/dist/cli/switchroom.js +2995 -1583
  5. package/dist/host-control/main.js +122 -2
  6. package/dist/vault/approvals/kernel-server.js +124 -4
  7. package/dist/vault/broker/server.js +124 -4
  8. package/package.json +7 -4
  9. package/profiles/_base/start.sh.hbs +101 -0
  10. package/profiles/_shared/agent-self-service.md.hbs +64 -109
  11. package/profiles/_shared/delegation-golden-rule.md.hbs +5 -5
  12. package/profiles/_shared/dev-protocol.md.hbs +13 -42
  13. package/profiles/_shared/execution-discipline.md.hbs +7 -14
  14. package/profiles/coding/CLAUDE.md.hbs +0 -6
  15. package/profiles/default/CLAUDE.md.hbs +21 -50
  16. package/skills/dev-protocol/SKILL.md +90 -107
  17. package/skills/switchroom-release/SKILL.md +103 -20
  18. package/telegram-plugin/bunfig.toml +10 -0
  19. package/telegram-plugin/card-format.ts +92 -3
  20. package/telegram-plugin/dist/gateway/gateway.js +873 -184
  21. package/telegram-plugin/edit-flood-fuse.ts +477 -0
  22. package/telegram-plugin/format.ts +19 -7
  23. package/telegram-plugin/gateway/backstop-delivery.ts +97 -16
  24. package/telegram-plugin/gateway/boot-sweep-gate.ts +164 -0
  25. package/telegram-plugin/gateway/callback-query-handlers.ts +454 -81
  26. package/telegram-plugin/gateway/captured-answer-resume.ts +46 -17
  27. package/telegram-plugin/gateway/gateway.ts +75 -63
  28. package/telegram-plugin/gateway/inbound-interceptors.ts +27 -4
  29. package/telegram-plugin/gateway/narrative-lane.ts +49 -3
  30. package/telegram-plugin/gateway/outbound-send-path.ts +8 -1
  31. package/telegram-plugin/gateway/status-pin-api.ts +145 -0
  32. package/telegram-plugin/gateway/stream-render.ts +6 -0
  33. package/telegram-plugin/gateway/turn-record-status.ts +19 -0
  34. package/telegram-plugin/gateway/turns-jsonl-rotate.ts +65 -0
  35. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +325 -45
  36. package/telegram-plugin/retry-api-call.ts +15 -2
  37. package/telegram-plugin/send-gate.ts +1 -1
  38. package/telegram-plugin/status-no-truncate.ts +64 -1
  39. package/telegram-plugin/status-pin-driver.ts +50 -27
  40. package/telegram-plugin/status-pin.ts +43 -5
  41. package/telegram-plugin/tests/activity-card-send-gate.test.ts +275 -0
  42. package/telegram-plugin/tests/activity-card-wiring.test.ts +16 -7
  43. package/telegram-plugin/tests/agent-state-dir-preload.test.ts +33 -0
  44. package/telegram-plugin/tests/backstop-delivery.test.ts +204 -7
  45. package/telegram-plugin/tests/backstop-readback-probe.test.ts +12 -0
  46. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +101 -0
  47. package/telegram-plugin/tests/boot-sweep-gate.test.ts +293 -0
  48. package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
  49. package/telegram-plugin/tests/captured-answer-resume.test.ts +104 -0
  50. package/telegram-plugin/tests/edit-flood-fuse.test.ts +431 -0
  51. package/telegram-plugin/tests/pinned-card-collapse.test.ts +356 -0
  52. package/telegram-plugin/tests/status-pin-api.test.ts +178 -0
  53. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +94 -11
  54. package/telegram-plugin/tests/status-pin.test.ts +106 -5
  55. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +631 -1
  56. package/telegram-plugin/tests/tool-activity-summary.test.ts +19 -10
  57. package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +92 -1
  58. package/telegram-plugin/tests/vault-approval-posture.test.ts +6 -1
  59. package/telegram-plugin/tests/vault-passphrase-retry.test.ts +666 -0
  60. package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +42 -21
  61. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +233 -1
  62. package/telegram-plugin/tool-activity-summary.ts +85 -13
  63. package/telegram-plugin/worker-activity-feed.ts +5 -1
  64. package/vendor/hindsight-memory/scripts/drain_pending.py +304 -34
  65. package/vendor/hindsight-memory/scripts/lib/pending.py +886 -70
  66. package/vendor/hindsight-memory/scripts/lib/retain_split.py +71 -13
  67. package/vendor/hindsight-memory/scripts/recall.py +74 -5
  68. package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +1602 -14
  69. package/vendor/hindsight-memory/scripts/tests/test_pending_failure_class.py +105 -0
  70. package/vendor/hindsight-memory/scripts/tests/test_pending_wedge.py +300 -0
  71. package/vendor/hindsight-memory/scripts/tests/test_recall_degraded_notice.py +365 -0
  72. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +12 -4
  73. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +27 -2
  74. package/vendor/hindsight-memory/scripts/tests/test_retain_split.py +93 -13
  75. package/vendor/hindsight-memory/tests/test_drain_pending.py +44 -3
  76. package/vendor/hindsight-memory/tests/test_pending.py +12 -4
@@ -19443,7 +19443,8 @@ var HindsightConfigSchema = exports_external.object({
19443
19443
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
19444
19444
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
19445
19445
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
19446
- }).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.")
19446
+ }).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."),
19447
+ 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, " + "LLM_MAX_CONCURRENT, RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
19447
19448
  });
19448
19449
  var MicrosoftWorkspaceConfigSchema = exports_external.object({
19449
19450
  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 — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -21120,7 +21121,7 @@ function allocateAgentUid(name) {
21120
21121
  }
21121
21122
 
21122
21123
  // src/build-info.ts
21123
- var VERSION = "0.19.19";
21124
+ var VERSION = "0.19.23";
21124
21125
 
21125
21126
  // src/cli/agent-config.ts
21126
21127
  import { join as join2 } from "node:path";
@@ -21172,6 +21173,113 @@ for (const name of SHARED_FRAGMENTS) {
21172
21173
  }
21173
21174
  }
21174
21175
 
21176
+ // src/litellm/timeout-budget.ts
21177
+ var LITELLM_ROUTER_MARGIN_S = 10;
21178
+ var LITELLM_TIMEOUT_TIERS = {
21179
+ interactive: {
21180
+ group: "gpt-oss-20b",
21181
+ localTimeoutS: 90,
21182
+ fallbackGroup: "gpt-oss-20b-openrouter",
21183
+ fallbackTimeoutS: 60
21184
+ },
21185
+ retain: {
21186
+ group: "gpt-oss-20b-retain",
21187
+ localTimeoutS: 200,
21188
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
21189
+ fallbackTimeoutS: 90
21190
+ },
21191
+ consolidation: {
21192
+ group: "gpt-oss-20b-consolidation",
21193
+ localTimeoutS: 200,
21194
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
21195
+ fallbackTimeoutS: 90
21196
+ }
21197
+ };
21198
+ function litellmChainSeconds(tier) {
21199
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
21200
+ if (tier.fallbackGroup === null)
21201
+ return tier.localTimeoutS;
21202
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
21203
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
21204
+ }
21205
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
21206
+ return litellmChainSeconds(tier) + marginS;
21207
+ }
21208
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
21209
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
21210
+ }
21211
+ function assertPositive(value, label) {
21212
+ if (!Number.isFinite(value) || value <= 0) {
21213
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
21214
+ }
21215
+ }
21216
+
21217
+ // src/setup/hindsight-perf-defaults.ts
21218
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150;
21219
+ var HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
21220
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50;
21221
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
21222
+ var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
21223
+ var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
21224
+ var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
21225
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = 1;
21226
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
21227
+ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
21228
+ [
21229
+ "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
21230
+ String(HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE)
21231
+ ],
21232
+ [
21233
+ "HINDSIGHT_API_LINK_EXPANSION_PER_ENTITY_LIMIT",
21234
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT)
21235
+ ],
21236
+ [
21237
+ "HINDSIGHT_API_LINK_EXPANSION_TIMEOUT",
21238
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S)
21239
+ ],
21240
+ ["HINDSIGHT_API_LLM_REASONING_EFFORT", HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT]
21241
+ ];
21242
+ var HINDSIGHT_PERF_DEFAULTS_GPU = [
21243
+ ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16]
21244
+ ];
21245
+ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
21246
+ ["HINDSIGHT_API_LLM_MAX_CONCURRENT", String(HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT)],
21247
+ [
21248
+ "HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT",
21249
+ String(HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT)
21250
+ ],
21251
+ [
21252
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT",
21253
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
21254
+ ]
21255
+ ];
21256
+ var HINDSIGHT_PERF_ENV_KEYS = new Set([
21257
+ ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
21258
+ ...HINDSIGHT_PERF_DEFAULTS_GPU,
21259
+ ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
21260
+ ].map(([k]) => k));
21261
+
21262
+ // src/setup/hindsight-pg-defaults.ts
21263
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
21264
+ var HINDSIGHT_PG_APP_ANON_MIB = 2560;
21265
+ var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
21266
+ var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
21267
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 1536;
21268
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 4096;
21269
+ function pgMib(mib) {
21270
+ return `${mib}MB`;
21271
+ }
21272
+ var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
21273
+ var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
21274
+ var HINDSIGHT_PG_DEFAULTS = [
21275
+ [
21276
+ HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
21277
+ pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
21278
+ ],
21279
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
21280
+ ];
21281
+ var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
21282
+
21175
21283
  // src/setup/hindsight.ts
21176
21284
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
21177
21285
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -21181,6 +21289,18 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
21181
21289
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
21182
21290
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
21183
21291
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
21292
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
21293
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
21294
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
21295
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
21296
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
21297
+ }
21298
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
21299
+ }
21300
+ function hindsightRetainClientTimeoutSeconds() {
21301
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
21302
+ }
21303
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
21184
21304
  var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
21185
21305
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
21186
21306
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
@@ -4386,7 +4386,8 @@ var init_schema = __esm(() => {
4386
4386
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
4387
4387
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
4388
4388
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
4389
- }).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.")
4389
+ }).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."),
4390
+ 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, " + "LLM_MAX_CONCURRENT, RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4390
4391
  });
4391
4392
  MicrosoftWorkspaceConfigSchema = exports_external.object({
4392
4393
  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 — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -19147,6 +19148,116 @@ for (const name of SHARED_FRAGMENTS) {
19147
19148
  }
19148
19149
  }
19149
19150
 
19151
+ // src/litellm/timeout-budget.ts
19152
+ var LITELLM_ROUTER_MARGIN_S = 10;
19153
+ var LITELLM_TIMEOUT_TIERS = {
19154
+ interactive: {
19155
+ group: "gpt-oss-20b",
19156
+ localTimeoutS: 90,
19157
+ fallbackGroup: "gpt-oss-20b-openrouter",
19158
+ fallbackTimeoutS: 60
19159
+ },
19160
+ retain: {
19161
+ group: "gpt-oss-20b-retain",
19162
+ localTimeoutS: 200,
19163
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
19164
+ fallbackTimeoutS: 90
19165
+ },
19166
+ consolidation: {
19167
+ group: "gpt-oss-20b-consolidation",
19168
+ localTimeoutS: 200,
19169
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
19170
+ fallbackTimeoutS: 90
19171
+ }
19172
+ };
19173
+ function litellmChainSeconds(tier) {
19174
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
19175
+ if (tier.fallbackGroup === null)
19176
+ return tier.localTimeoutS;
19177
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
19178
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
19179
+ }
19180
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
19181
+ return litellmChainSeconds(tier) + marginS;
19182
+ }
19183
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
19184
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
19185
+ }
19186
+ function assertPositive(value, label) {
19187
+ if (!Number.isFinite(value) || value <= 0) {
19188
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
19189
+ }
19190
+ }
19191
+
19192
+ // src/setup/host-capabilities.ts
19193
+ init_paths();
19194
+
19195
+ // src/setup/hindsight-perf-defaults.ts
19196
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150;
19197
+ var HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
19198
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50;
19199
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
19200
+ var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
19201
+ var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
19202
+ var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
19203
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = 1;
19204
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
19205
+ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
19206
+ [
19207
+ "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
19208
+ String(HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE)
19209
+ ],
19210
+ [
19211
+ "HINDSIGHT_API_LINK_EXPANSION_PER_ENTITY_LIMIT",
19212
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT)
19213
+ ],
19214
+ [
19215
+ "HINDSIGHT_API_LINK_EXPANSION_TIMEOUT",
19216
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S)
19217
+ ],
19218
+ ["HINDSIGHT_API_LLM_REASONING_EFFORT", HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT]
19219
+ ];
19220
+ var HINDSIGHT_PERF_DEFAULTS_GPU = [
19221
+ ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16]
19222
+ ];
19223
+ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
19224
+ ["HINDSIGHT_API_LLM_MAX_CONCURRENT", String(HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT)],
19225
+ [
19226
+ "HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT",
19227
+ String(HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT)
19228
+ ],
19229
+ [
19230
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT",
19231
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
19232
+ ]
19233
+ ];
19234
+ var HINDSIGHT_PERF_ENV_KEYS = new Set([
19235
+ ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
19236
+ ...HINDSIGHT_PERF_DEFAULTS_GPU,
19237
+ ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
19238
+ ].map(([k]) => k));
19239
+
19240
+ // src/setup/hindsight-pg-defaults.ts
19241
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
19242
+ var HINDSIGHT_PG_APP_ANON_MIB = 2560;
19243
+ var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
19244
+ var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
19245
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 1536;
19246
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 4096;
19247
+ function pgMib(mib) {
19248
+ return `${mib}MB`;
19249
+ }
19250
+ var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
19251
+ var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
19252
+ var HINDSIGHT_PG_DEFAULTS = [
19253
+ [
19254
+ HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
19255
+ pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
19256
+ ],
19257
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
19258
+ ];
19259
+ var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
19260
+
19150
19261
  // src/setup/hindsight.ts
19151
19262
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
19152
19263
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -19156,6 +19267,18 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
19156
19267
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
19157
19268
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
19158
19269
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
19270
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
19271
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
19272
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
19273
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
19274
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
19275
+ }
19276
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
19277
+ }
19278
+ function hindsightRetainClientTimeoutSeconds() {
19279
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
19280
+ }
19281
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
19159
19282
  var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
19160
19283
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
19161
19284
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
@@ -19247,9 +19370,6 @@ var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
19247
19370
  init_merge();
19248
19371
  init_timezone();
19249
19372
 
19250
- // src/setup/host-capabilities.ts
19251
- init_paths();
19252
-
19253
19373
  // src/vault/grants-db-path.ts
19254
19374
  var GRANTS_DB_DIRNAME = "vault-broker";
19255
19375
  var GRANTS_DB_FILENAME = "vault-grants.db";
@@ -4386,7 +4386,8 @@ var init_schema = __esm(() => {
4386
4386
  retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
4387
4387
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
4388
4388
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
4389
- }).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.")
4389
+ }).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."),
4390
+ 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, " + "LLM_MAX_CONCURRENT, RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT), 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 — 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 — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4390
4391
  });
4391
4392
  MicrosoftWorkspaceConfigSchema = exports_external.object({
4392
4393
  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 — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -18820,6 +18821,116 @@ for (const name of SHARED_FRAGMENTS) {
18820
18821
  }
18821
18822
  }
18822
18823
 
18824
+ // src/litellm/timeout-budget.ts
18825
+ var LITELLM_ROUTER_MARGIN_S = 10;
18826
+ var LITELLM_TIMEOUT_TIERS = {
18827
+ interactive: {
18828
+ group: "gpt-oss-20b",
18829
+ localTimeoutS: 90,
18830
+ fallbackGroup: "gpt-oss-20b-openrouter",
18831
+ fallbackTimeoutS: 60
18832
+ },
18833
+ retain: {
18834
+ group: "gpt-oss-20b-retain",
18835
+ localTimeoutS: 200,
18836
+ fallbackGroup: "gpt-oss-20b-retain-openrouter",
18837
+ fallbackTimeoutS: 90
18838
+ },
18839
+ consolidation: {
18840
+ group: "gpt-oss-20b-consolidation",
18841
+ localTimeoutS: 200,
18842
+ fallbackGroup: "gpt-oss-20b-consolidation-openrouter",
18843
+ fallbackTimeoutS: 90
18844
+ }
18845
+ };
18846
+ function litellmChainSeconds(tier) {
18847
+ assertPositive(tier.localTimeoutS, `${tier.group} localTimeoutS`);
18848
+ if (tier.fallbackGroup === null)
18849
+ return tier.localTimeoutS;
18850
+ assertPositive(tier.fallbackTimeoutS, `${tier.fallbackGroup} fallbackTimeoutS`);
18851
+ return tier.localTimeoutS + tier.fallbackTimeoutS;
18852
+ }
18853
+ function minimumClientBudgetSeconds(tier, marginS = LITELLM_ROUTER_MARGIN_S) {
18854
+ return litellmChainSeconds(tier) + marginS;
18855
+ }
18856
+ function clientBudgetSeconds(tier, floorS = 0, marginS = LITELLM_ROUTER_MARGIN_S) {
18857
+ return Math.max(Math.ceil(floorS), minimumClientBudgetSeconds(tier, marginS));
18858
+ }
18859
+ function assertPositive(value, label) {
18860
+ if (!Number.isFinite(value) || value <= 0) {
18861
+ throw new Error(`litellm timeout budget: ${label} must be a positive number, got ${value}`);
18862
+ }
18863
+ }
18864
+
18865
+ // src/setup/host-capabilities.ts
18866
+ init_paths();
18867
+
18868
+ // src/setup/hindsight-perf-defaults.ts
18869
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150;
18870
+ var HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
18871
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50;
18872
+ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
18873
+ var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
18874
+ var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
18875
+ var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
18876
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = 1;
18877
+ var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
18878
+ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
18879
+ [
18880
+ "HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
18881
+ String(HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE)
18882
+ ],
18883
+ [
18884
+ "HINDSIGHT_API_LINK_EXPANSION_PER_ENTITY_LIMIT",
18885
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT)
18886
+ ],
18887
+ [
18888
+ "HINDSIGHT_API_LINK_EXPANSION_TIMEOUT",
18889
+ String(HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S)
18890
+ ],
18891
+ ["HINDSIGHT_API_LLM_REASONING_EFFORT", HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT]
18892
+ ];
18893
+ var HINDSIGHT_PERF_DEFAULTS_GPU = [
18894
+ ["HINDSIGHT_API_RERANKER_LOCAL_FP16", HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16]
18895
+ ];
18896
+ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
18897
+ ["HINDSIGHT_API_LLM_MAX_CONCURRENT", String(HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT)],
18898
+ [
18899
+ "HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT",
18900
+ String(HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT)
18901
+ ],
18902
+ [
18903
+ "HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT",
18904
+ String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
18905
+ ]
18906
+ ];
18907
+ var HINDSIGHT_PERF_ENV_KEYS = new Set([
18908
+ ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
18909
+ ...HINDSIGHT_PERF_DEFAULTS_GPU,
18910
+ ...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
18911
+ ].map(([k]) => k));
18912
+
18913
+ // src/setup/hindsight-pg-defaults.ts
18914
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 8 * 1024;
18915
+ var HINDSIGHT_PG_APP_ANON_MIB = 2560;
18916
+ var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
18917
+ var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
18918
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 1536;
18919
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 4096;
18920
+ function pgMib(mib) {
18921
+ return `${mib}MB`;
18922
+ }
18923
+ var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
18924
+ var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
18925
+ var HINDSIGHT_PG_DEFAULTS = [
18926
+ [
18927
+ HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
18928
+ pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
18929
+ ],
18930
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
18931
+ ];
18932
+ var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
18933
+
18823
18934
  // src/setup/hindsight.ts
18824
18935
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
18825
18936
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -18829,6 +18940,18 @@ var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
18829
18940
  var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
18830
18941
  var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
18831
18942
  var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
18943
+ var HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384;
18944
+ var HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6;
18945
+ function hindsightRetainLlmTimeoutSeconds(maxCompletionTokens = HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS, tokensPerSecond = HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND) {
18946
+ if (!(maxCompletionTokens > 0) || !(tokensPerSecond > 0)) {
18947
+ throw new Error(`hindsight retain budget: maxCompletionTokens (${maxCompletionTokens}) and ` + `tokensPerSecond (${tokensPerSecond}) must both be positive`);
18948
+ }
18949
+ return Math.ceil(maxCompletionTokens / tokensPerSecond);
18950
+ }
18951
+ function hindsightRetainClientTimeoutSeconds() {
18952
+ return clientBudgetSeconds(LITELLM_TIMEOUT_TIERS.retain, hindsightRetainLlmTimeoutSeconds());
18953
+ }
18954
+ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() + LITELLM_ROUTER_MARGIN_S;
18832
18955
  var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
18833
18956
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
18834
18957
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
@@ -19675,9 +19798,6 @@ var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
19675
19798
  init_merge();
19676
19799
  init_timezone();
19677
19800
 
19678
- // src/setup/host-capabilities.ts
19679
- init_paths();
19680
-
19681
19801
  // src/vault/grants-db-path.ts
19682
19802
  import * as os from "node:os";
19683
19803
  import * as path from "node:path";
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.19.19",
4
+ "version": "0.19.23",
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,22 +24,25 @@
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/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.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-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.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/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/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/catch-all-forwarded-history.test.ts telegram-plugin/tests/history.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-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.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/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/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/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/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-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.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/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/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/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/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-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.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/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/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-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-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-litellm-config-guard.mjs",
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-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-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs",
32
32
  "lint:tsc": "tsc --noEmit",
33
33
  "lint:plugin-references": "node scripts/check-plugin-references.mjs",
34
34
  "lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
35
35
  "lint:bun-test-imports": "node scripts/check-bun-test-imports.mjs",
36
+ "lint:bun-module-mock-scope": "node scripts/check-bun-module-mock-scope.mjs",
36
37
  "lint:no-pii": "node scripts/check-no-pii-secrets.mjs",
37
38
  "lint:auth-test-hermeticity": "node scripts/check-auth-test-hermeticity.mjs",
39
+ "lint:agent-state-dir-hermeticity": "node scripts/check-agent-state-dir-hermeticity.mjs",
38
40
  "lint:web-subscription-honest": "node scripts/check-web-subscription-honest.mjs",
39
41
  "lint:no-broadcast-delivery": "node scripts/check-no-broadcast-delivery.mjs",
40
42
  "lint:gateway-line-ratchet": "node scripts/check-gateway-line-ratchet.mjs",
41
43
  "lint:mcp-instructions-budget": "node scripts/check-mcp-instructions-budget.mjs",
42
44
  "lint:litellm-config-guard": "node scripts/check-litellm-config-guard.mjs",
45
+ "lint:release-asset-contract": "node scripts/check-release-asset-names.mjs",
43
46
  "prepublishOnly": "npm run build && npm run lint && npm test"
44
47
  },
45
48
  "dependencies": {
@@ -458,6 +458,107 @@ x-litellm-tags: agent:$SWITCHROOM_AGENT_NAME,profile:${SWITCHROOM_AGENT_PROFILE:
458
458
  fi
459
459
  {{/if}}
460
460
 
461
+ {{#if hindsightEnabled}}
462
+ # 5) hindsight backlog drain — the memory queue's only real consumer.
463
+ #
464
+ # WHY THIS EXISTS. `pending-retains` is written whenever a retain
465
+ # fails, but before this the ONLY automatic thing that drained it was
466
+ # the SessionStart hook (`session_start.py`), which runs once per
467
+ # session boot under a hard ~9s latency budget against work measured
468
+ # at 85-280s per entry. So an agent that simply stays up never drained
469
+ # at all: on this fleet 8 of 11 agents had not drained a single entry
470
+ # and the queue reached 1,060 files. A memory sitting in that queue is
471
+ # a memory the agent cannot recall — the job this feature exists for.
472
+ #
473
+ # A sidecar, not a cron entry: cron fires cost a model turn and depend
474
+ # on the agent remembering to keep the entry. This is a deterministic
475
+ # mechanism — it runs because the container is up, needs no LLM turn of
476
+ # its own, and cannot be prompted away.
477
+ #
478
+ # SERIALISED. `flock -n` on a per-agent lockfile makes overlapping runs
479
+ # impossible: a run that overshoots the interval causes the next tick to
480
+ # SKIP, never to stack. The lock path is deliberately the one the
481
+ # out-of-band recovery tooling also takes, so a manual sweep and this
482
+ # loop mutually exclude instead of double-processing an entry.
483
+ #
484
+ # LANE-RESPECTING. Retain extraction is served by a small fixed pool of
485
+ # LLM lanes (4 on this fleet, 2 boxes x 2 slots) SHARED with live
486
+ # retains, reflect and consolidation, already near saturation. So:
487
+ # `HINDSIGHT_DRAIN_CONCURRENCY` is left at its default of 1 (one
488
+ # in-flight POST per agent — do not raise it here), `drain_pending`'s
489
+ # own inter-retain sleep and p95 backoff still apply, and the first
490
+ # tick is delayed by a per-agent offset derived from the agent NAME so
491
+ # the fleet does not all wake together. The offset is a `cksum` of the
492
+ # name modulo the interval: deterministic (same agent, same offset,
493
+ # every boot) and evenly spread, with no shared coordinator.
494
+ # HONEST LIMIT: N agents x 1 lane still exceeds 4 lanes when every
495
+ # agent has a backlog. The offset spreads the START times; it does not
496
+ # cap fleet-wide demand. Sustained oversubscription is absorbed by the
497
+ # p95 backoff, and the real fix is more lanes.
498
+ #
499
+ # WHY NO `HINDSIGHT_DRAIN_BACKLOG_TIMEOUT` HERE, given the measurement.
500
+ # A host-side stopgap measured, on the SHIPPED defaults:
501
+ # timeout=310 conc=1, busy lane -> drained 0 of 2
502
+ # timeout=310 conc=3, FREE lane -> drained 0 of 3, stalled
503
+ # timeout=900 conc=1, free lane -> drained 5 of 5
504
+ # It failed on an IDLE lane, so scheduling and contention were never the
505
+ # whole story. The reading that matters is the third row's concurrency,
506
+ # not its timeout: raising concurrency made it WORSE, which is why 1 is
507
+ # the value above and is not a knob this sidecar exposes.
508
+ # The timeout is deliberately left DERIVED rather than pinned at 900.
509
+ # `_backlog_timeout()` defaults to `retain_client_deadline()` and the
510
+ # content bound is sized against that same number — they must stay one
511
+ # number, and hard-coding 900 here would silently unpick that. What
512
+ # those runs actually measured was the bound consuming ~100% of the
513
+ # deadline, so a maximally-sized part could never finish; that is fixed
514
+ # at its root in #3693 (the bound is now a FRACTION of the deadline),
515
+ # which brings a max part to 202s inside a 310s deadline. Entries
516
+ # already queued ABOVE the current bound are recovered by re-splitting
517
+ # them, not by waiting longer. An operator who still wants a longer
518
+ # deadline moves HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, which moves both.
519
+ #
520
+ # Failure is never terminal: the drain exits non-zero only when it
521
+ # promoted entries to `.dead`, and the loop below ignores the status
522
+ # and waits for the next tick. `--backlog` reads api_url/token/bank
523
+ # from each queue ENTRY, so this sidecar needs no HINDSIGHT_* env
524
+ # (those are exported later, in the inner pass) — only $HOME, to find
525
+ # the queue.
526
+ #
527
+ # Kill switch: SWITCHROOM_HINDSIGHT_DRAIN=0 at the container level.
528
+ # Interval: SWITCHROOM_HINDSIGHT_DRAIN_INTERVAL_S (default 900s).
529
+ _hs_drain_script="{{agentDir}}/.claude/plugins/hindsight-memory/scripts/drain_pending.py"
530
+ if [ "$SWITCHROOM_HINDSIGHT_DRAIN" != "0" ] \
531
+ && [ -f "$_hs_drain_script" ] \
532
+ && command -v python3 >/dev/null 2>&1 \
533
+ && command -v flock >/dev/null 2>&1; then
534
+ _switchroom_hindsight_drain_loop() {
535
+ local _script="$1"
536
+ local _interval="${SWITCHROOM_HINDSIGHT_DRAIN_INTERVAL_S:-900}"
537
+ # Reject a non-numeric or too-eager interval rather than busy-looping
538
+ # against the LLM lane. 60s is the floor.
539
+ case "$_interval" in ''|*[!0-9]*) _interval=900 ;; esac
540
+ [ "$_interval" -lt 60 ] && _interval=60
541
+ local _lock="$HOME/.hindsight/drain.lock"
542
+ mkdir -p "$HOME/.hindsight" 2>/dev/null || true
543
+ # Deterministic per-agent phase offset (see the note above).
544
+ local _offset=0
545
+ if command -v cksum >/dev/null 2>&1; then
546
+ _offset=$(( $(printf '%s' "{{name}}" | cksum | cut -d' ' -f1) % _interval ))
547
+ fi
548
+ echo "[hindsight-drain] starting: interval=${_interval}s offset=${_offset}s lock=${_lock}"
549
+ sleep "$_offset"
550
+ while true; do
551
+ # -n: if a drain (ours or an operator sweep) already holds the
552
+ # lock, skip this tick entirely instead of queueing behind it.
553
+ flock -n "$_lock" python3 "$_script" --backlog || true
554
+ sleep "$_interval"
555
+ done
556
+ }
557
+ _switchroom_supervise hindsight-drain /var/log/switchroom/hindsight-drain.log \
558
+ _switchroom_hindsight_drain_loop "$_hs_drain_script" &
559
+ fi
560
+ {{/if}}
561
+
461
562
  export SWITCHROOM_DOCKER_TMUX_INNER=1
462
563
  exec tmux -L "switchroom-{{name}}" \
463
564
  new-session -A -s "{{name}}" -x 400 -y 50 \