switchroom 0.19.38 → 0.19.40

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 (45) hide show
  1. package/dist/agent-scheduler/index.js +10 -1
  2. package/dist/auth-broker/index.js +15 -6
  3. package/dist/cli/notion-write-pretool.mjs +10 -1
  4. package/dist/cli/switchroom.js +1141 -566
  5. package/dist/host-control/main.js +220 -8
  6. package/dist/vault/approvals/kernel-server.js +15 -6
  7. package/dist/vault/broker/server.js +15 -6
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +10 -0
  10. package/telegram-plugin/dist/bridge/bridge.js +8 -0
  11. package/telegram-plugin/dist/gateway/gateway.js +450 -206
  12. package/telegram-plugin/dist/server.js +8 -0
  13. package/telegram-plugin/gateway/backstop-delivery.ts +48 -0
  14. package/telegram-plugin/gateway/compaction-marker.ts +84 -0
  15. package/telegram-plugin/gateway/gateway.ts +3 -3
  16. package/telegram-plugin/gateway/handback-preturn-signal.ts +16 -0
  17. package/telegram-plugin/gateway/liveness-wiring.ts +15 -0
  18. package/telegram-plugin/gateway/outbound-send-path.ts +20 -0
  19. package/telegram-plugin/gateway/outbox-sweep.ts +116 -18
  20. package/telegram-plugin/gateway/silence-poke-session-event.ts +13 -0
  21. package/telegram-plugin/gateway/stream-render.ts +277 -18
  22. package/telegram-plugin/hooks/compaction-marker-precompact.mjs +70 -0
  23. package/telegram-plugin/hooks/hooks.json +11 -0
  24. package/telegram-plugin/hooks/tool-label-pretool.mjs +119 -1
  25. package/telegram-plugin/session-tail.ts +20 -0
  26. package/telegram-plugin/silence-poke.ts +28 -0
  27. package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +19 -12
  28. package/telegram-plugin/tests/fixtures/pretool-main-2.1.219.json +15 -0
  29. package/telegram-plugin/tests/fixtures/pretool-subagent-2.1.219.json +16 -0
  30. package/telegram-plugin/tests/outbox-delivery.test.ts +38 -1
  31. package/telegram-plugin/tests/outbox-flush-ack-claim-race.test.ts +213 -0
  32. package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +1 -1
  33. package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +4 -4
  34. package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +71 -8
  35. package/telegram-plugin/tests/queued-card-surface.test.ts +262 -0
  36. package/telegram-plugin/tests/send-reply-golden.test.ts +47 -0
  37. package/telegram-plugin/tests/sidechain-label-filter-pretool.test.ts +272 -0
  38. package/telegram-plugin/tests/silence-poke-compaction.test.ts +222 -0
  39. package/vendor/hindsight-memory/scripts/lib/client.py +7 -4
  40. package/vendor/hindsight-memory/scripts/lib/config.py +151 -0
  41. package/vendor/hindsight-memory/scripts/retain.py +19 -15
  42. package/vendor/hindsight-memory/scripts/tests/test_backfill.py +14 -1
  43. package/vendor/hindsight-memory/scripts/tests/test_observation_scopes.py +192 -3
  44. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +18 -2
  45. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +57 -4
@@ -10977,7 +10977,7 @@ var init_zod = __esm(() => {
10977
10977
  });
10978
10978
 
10979
10979
  // src/memory/observation-scopes.ts
10980
- var OBSERVATION_SCOPES;
10980
+ var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
10981
10981
  var init_observation_scopes = __esm(() => {
10982
10982
  OBSERVATION_SCOPES = [
10983
10983
  "per_tag",
@@ -10985,10 +10985,16 @@ var init_observation_scopes = __esm(() => {
10985
10985
  "all_combinations",
10986
10986
  "shared"
10987
10987
  ];
10988
+ OBSERVATION_SCOPE_STRATEGIES = [
10989
+ "curated",
10990
+ "shared",
10991
+ "combined",
10992
+ "off"
10993
+ ];
10988
10994
  });
10989
10995
 
10990
10996
  // src/config/schema.ts
10991
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
10997
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
10992
10998
  var init_schema = __esm(() => {
10993
10999
  init_zod();
10994
11000
  init_observation_scopes();
@@ -11103,7 +11109,8 @@ var init_schema = __esm(() => {
11103
11109
  allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
11104
11110
  deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
11105
11111
  }).optional();
11106
- ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag — what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
11112
+ ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
11113
+ ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
11107
11114
  AgentMemorySchema = exports_external.object({
11108
11115
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
11109
11116
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -11139,6 +11146,7 @@ var init_schema = __esm(() => {
11139
11146
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
11140
11147
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
11141
11148
  observation_scopes: ObservationScopesSchema,
11149
+ observation_scope_strategy: ObservationScopeStrategySchema,
11142
11150
  recall: exports_external.object({
11143
11151
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
11144
11152
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
@@ -11539,6 +11547,7 @@ var init_schema = __esm(() => {
11539
11547
  isolation: exports_external.enum(["default", "strict"]).optional(),
11540
11548
  directive_capture_nudge: exports_external.boolean().optional(),
11541
11549
  observation_scopes: ObservationScopesSchema,
11550
+ observation_scope_strategy: ObservationScopeStrategySchema,
11542
11551
  recall: exports_external.object({
11543
11552
  max_memories: exports_external.number().int().min(0).optional(),
11544
11553
  cache_ttl_secs: exports_external.number().int().min(0).optional(),
@@ -21311,7 +21320,7 @@ function allocateAgentUid(name) {
21311
21320
  }
21312
21321
 
21313
21322
  // src/build-info.ts
21314
- var VERSION = "0.19.38";
21323
+ var VERSION = "0.19.40";
21315
21324
 
21316
21325
  // src/setup/hindsight-recall-tunables.ts
21317
21326
  var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
@@ -21440,8 +21449,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
21440
21449
  var HINDSIGHT_PG_APP_ANON_MIB = 2560;
21441
21450
  var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
21442
21451
  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;
21443
- var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072;
21444
- var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168;
21452
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
21453
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
21445
21454
  function pgMib(mib) {
21446
21455
  return `${mib}MB`;
21447
21456
  }
@@ -21485,7 +21494,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
21485
21494
  var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
21486
21495
  var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
21487
21496
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
21488
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500;
21497
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
21489
21498
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
21490
21499
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
21491
21500
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
@@ -28405,6 +28414,10 @@ function parseRolloutPhaseLine(line) {
28405
28414
  "agent-done",
28406
28415
  "persist-pin",
28407
28416
  "web-refresh",
28417
+ "web-refresh-done",
28418
+ "hindsight-refresh",
28419
+ "hindsight-refresh-done",
28420
+ "hindsight-skipped",
28408
28421
  "hostd-web-deferred",
28409
28422
  "self-bump",
28410
28423
  "self-bump-done"
@@ -29385,6 +29398,14 @@ function phaseLine(s) {
29385
29398
  return "persisting pin";
29386
29399
  case "web-refresh":
29387
29400
  return "refreshing web dashboard (webd install)";
29401
+ case "web-refresh-done":
29402
+ return "web dashboard refreshed";
29403
+ case "hindsight-refresh":
29404
+ return "refreshing hindsight (memory backend recreate)";
29405
+ case "hindsight-refresh-done":
29406
+ return "hindsight refreshed";
29407
+ case "hindsight-skipped":
29408
+ return "hindsight — no container on this host; skipped";
29388
29409
  case "hostd-web-deferred":
29389
29410
  return "hostd refresh deferred (run host-side)";
29390
29411
  case "self-bump":
@@ -29452,6 +29473,33 @@ function checklistLines(s, compact) {
29452
29473
  }
29453
29474
  return lines;
29454
29475
  }
29476
+ function singletonGlyph(status) {
29477
+ switch (status) {
29478
+ case "updated":
29479
+ return "✓";
29480
+ case "updating":
29481
+ return "⏳";
29482
+ case "deferred":
29483
+ return "⧗";
29484
+ case "skipped":
29485
+ return "○";
29486
+ case "failed":
29487
+ return "✗";
29488
+ default:
29489
+ return "·";
29490
+ }
29491
+ }
29492
+ function singletonLines(s) {
29493
+ const singletons = s.singletons ?? [];
29494
+ if (singletons.length === 0)
29495
+ return [];
29496
+ const lines = ["**Singletons / shared:**"];
29497
+ for (const g of singletons) {
29498
+ const detail = g.detail ? ` — ${g.detail}` : "";
29499
+ lines.push(`- ${singletonGlyph(g.status)} \`${g.name}\`${detail}`);
29500
+ }
29501
+ return lines;
29502
+ }
29455
29503
  function etaLine(s) {
29456
29504
  if (s.m === undefined)
29457
29505
  return null;
@@ -29485,6 +29533,7 @@ function renderWith(s, compact) {
29485
29533
  const rolled = s.rolled ?? [];
29486
29534
  const rolledCount = rolled.length;
29487
29535
  const checklist = checklistLines(s, compact);
29536
+ const singletons = singletonLines(s);
29488
29537
  const elapsedMs = s.elapsedMs ?? (s.startedAtMs !== undefined && s.nowMs !== undefined ? s.nowMs - s.startedAtMs : undefined);
29489
29538
  if (s.terminal === "completed") {
29490
29539
  const parts2 = [headerLine(s, "✅")];
@@ -29495,6 +29544,8 @@ function renderWith(s, compact) {
29495
29544
  parts2.push(summary);
29496
29545
  if (checklist.length > 0)
29497
29546
  parts2.push("", ...checklist);
29547
+ if (singletons.length > 0)
29548
+ parts2.push("", ...singletons);
29498
29549
  if (s.deferred !== false)
29499
29550
  parts2.push("", ...deferredLines(s.target));
29500
29551
  return parts2.join(`
@@ -29512,6 +29563,8 @@ function renderWith(s, compact) {
29512
29563
  parts2.push("", `Re-running the roll will NOT fix this — the agents are already on ` + `target. Finish the stale component(s), then \`switchroom update --check\`.`);
29513
29564
  if (checklist.length > 0)
29514
29565
  parts2.push("", ...checklist);
29566
+ if (singletons.length > 0)
29567
+ parts2.push("", ...singletons);
29515
29568
  return parts2.join(`
29516
29569
  `);
29517
29570
  }
@@ -29525,12 +29578,16 @@ function renderWith(s, compact) {
29525
29578
  parts2.push(summary);
29526
29579
  if (checklist.length > 0)
29527
29580
  parts2.push("", ...checklist);
29581
+ if (singletons.length > 0)
29582
+ parts2.push("", ...singletons);
29528
29583
  return parts2.join(`
29529
29584
  `);
29530
29585
  }
29531
29586
  const parts = [headerLine(s, "⏳"), `**Phase:** ${phaseLine(s)}`];
29532
29587
  if (checklist.length > 0)
29533
29588
  parts.push("", ...checklist);
29589
+ if (singletons.length > 0)
29590
+ parts.push("", ...singletons);
29534
29591
  const footer = [];
29535
29592
  if (s.m !== undefined)
29536
29593
  footer.push(`${rolledCount}/${s.m} rolled`);
@@ -31446,6 +31503,8 @@ ${pinNote}`.trim();
31446
31503
  const drifted = entry.drifted ?? [];
31447
31504
  const notes = entry.failed_step === "verify-components" ? [
31448
31505
  `Unattended auto-update roll to ${entry.pin ?? "?"} rolled ` + `${(entry.rolled ?? []).length} agent(s) but did NOT converge the ` + `whole host: ${drifted.join(", ") || "one or more components"} ` + `still behind. Re-running the roll will not fix this — run the ` + `per-component install named in the rollout warnings, then ` + `\`switchroom update --check\` to confirm.`
31506
+ ] : entry.failed_step === "ensure-banks" ? [
31507
+ `Unattended auto-update roll to ${entry.pin ?? "?"} rolled ` + `${(entry.rolled ?? []).length} agent(s) but could NOT create the ` + `Hindsight bank for: ${drifted.join(", ") || "one or more agents"}. ` + `Re-running the roll will not fix this — restart the named agent(s) ` + `to re-run bank creation now that Hindsight is back up.`
31449
31508
  ] : [
31450
31509
  `Unattended auto-update roll to ${entry.pin ?? "?"} FAILED at ` + `${entry.failed_step ?? "unknown step"}` + `${entry.failed_agent ? ` (agent ${entry.failed_agent})` : ""}. ` + `Unattended retries of this version are disabled; fix the cause, ` + `then roll manually (\`switchroom rollout --pin ${entry.pin ?? "vX.Y.Z"}\`).`
31451
31510
  ];
@@ -31473,6 +31532,10 @@ ${pinNote}`.trim();
31473
31532
  notes.push(`No automatic rollback: the agent fleet reached ` + `${entry.pin ?? "the target"} and the pin is committed — the gap is ` + `${(entry.drifted ?? []).join(", ") || "a component"}, not the fleet. ` + `Rolling back would move working agents backwards; converge the ` + `named component(s) forward instead.`);
31474
31533
  return notes;
31475
31534
  }
31535
+ if (entry.failed_step === "ensure-banks") {
31536
+ notes.push(`No automatic rollback: the agent fleet reached ` + `${entry.pin ?? "the target"} and the pin is committed — the gap is a ` + `missing Hindsight bank for ${(entry.drifted ?? []).join(", ") || "an agent"}, ` + `not the fleet. Restart the named agent(s) to re-create the bank; do ` + `not roll back.`);
31537
+ return notes;
31538
+ }
31476
31539
  const beforeAnyAgent = entry.failed_step === "apply" || entry.failed_step === "restart-agent" && rolledCount === 0;
31477
31540
  if (!beforeAnyAgent) {
31478
31541
  notes.push(rolledCount > 0 ? `No automatic rollback: ${rolledCount} agent(s) already confirmed on ` + `${entry.pin ?? "the target"} past a green canary ` + `(${(entry.rolled ?? []).join(", ")}). The fleet is mixed — ` + `finish the roll or roll back manually.` : `No automatic rollback: the roll stopped at ` + `${entry.failed_step ?? "an unknown step"} before any change ` + `landed — nothing to revert. Verify host-side if unsure.`);
@@ -32314,6 +32377,26 @@ class SocketRolloutNarrationRelay {
32314
32377
  }
32315
32378
 
32316
32379
  // src/host-control/rollout-narrator.ts
32380
+ var SINGLETON_WEB = "switchroom-web";
32381
+ var SINGLETON_HINDSIGHT = "hindsight";
32382
+ var SINGLETON_HOSTD = "hostd";
32383
+ var SINGLETON_SHARED = "shared services (approval-kernel · auth-broker · vault-broker · voice)";
32384
+ function initialSingletons() {
32385
+ return [
32386
+ { name: SINGLETON_WEB, status: "pending" },
32387
+ { name: SINGLETON_HINDSIGHT, status: "pending" },
32388
+ {
32389
+ name: SINGLETON_HOSTD,
32390
+ status: "deferred",
32391
+ detail: "host-side (agent-invoked roll cannot recreate its own hostd)"
32392
+ },
32393
+ {
32394
+ name: SINGLETON_SHARED,
32395
+ status: "pending",
32396
+ detail: "self-heal with the first agent restart"
32397
+ }
32398
+ ];
32399
+ }
32317
32400
  var DEFAULT_DEBOUNCE_MS = 1500;
32318
32401
  var MAX_POST_ATTEMPTS = 2;
32319
32402
 
@@ -32366,6 +32449,68 @@ class LogTailRolloutNarrator {
32366
32449
  break;
32367
32450
  }
32368
32451
  }
32452
+ static patchSingleton(st, name, patch) {
32453
+ const row = st.singletons.find((g) => g.name === name);
32454
+ if (row)
32455
+ Object.assign(row, patch);
32456
+ }
32457
+ trackSingletonProgress(st, phase) {
32458
+ const patch = LogTailRolloutNarrator.patchSingleton;
32459
+ switch (phase.phase) {
32460
+ case "canary-pass":
32461
+ case "agent-done": {
32462
+ const shared = st.singletons.find((g) => g.name === SINGLETON_SHARED);
32463
+ if (shared && shared.status === "pending") {
32464
+ shared.status = "updating";
32465
+ shared.detail = "recreated with the first agent restart — verified at roll end";
32466
+ }
32467
+ break;
32468
+ }
32469
+ case "web-refresh":
32470
+ patch(st, SINGLETON_WEB, { status: "updating", detail: "webd install…" });
32471
+ break;
32472
+ case "web-refresh-done":
32473
+ patch(st, SINGLETON_WEB, { status: "updated", detail: undefined });
32474
+ break;
32475
+ case "hindsight-refresh":
32476
+ patch(st, SINGLETON_HINDSIGHT, {
32477
+ status: "updating",
32478
+ detail: "memory setup --recreate…"
32479
+ });
32480
+ break;
32481
+ case "hindsight-refresh-done":
32482
+ patch(st, SINGLETON_HINDSIGHT, { status: "updated", detail: undefined });
32483
+ break;
32484
+ case "hindsight-skipped":
32485
+ patch(st, SINGLETON_HINDSIGHT, {
32486
+ status: "skipped",
32487
+ detail: "no hindsight container on this host"
32488
+ });
32489
+ break;
32490
+ case "self-bump":
32491
+ patch(st, SINGLETON_HOSTD, {
32492
+ status: "updating",
32493
+ detail: "self-bump to the target…"
32494
+ });
32495
+ break;
32496
+ case "self-bump-done":
32497
+ patch(st, SINGLETON_HOSTD, {
32498
+ status: "updated",
32499
+ detail: "self-bump; template regen stays host-side"
32500
+ });
32501
+ break;
32502
+ case "hostd-web-deferred": {
32503
+ const hostd = st.singletons.find((g) => g.name === SINGLETON_HOSTD);
32504
+ if (hostd && hostd.status !== "updated") {
32505
+ hostd.status = "deferred";
32506
+ hostd.detail = "host-side";
32507
+ }
32508
+ break;
32509
+ }
32510
+ default:
32511
+ break;
32512
+ }
32513
+ }
32369
32514
  static seqFor(phase) {
32370
32515
  switch (phase.phase) {
32371
32516
  case "self-bump":
@@ -32385,7 +32530,12 @@ class LogTailRolloutNarrator {
32385
32530
  case "agent-done":
32386
32531
  return 2 * (phase.n ?? 1) + 1;
32387
32532
  case "web-refresh":
32388
- return Number.MAX_SAFE_INTEGER - 2;
32533
+ case "web-refresh-done":
32534
+ return Number.MAX_SAFE_INTEGER - 4;
32535
+ case "hindsight-refresh":
32536
+ case "hindsight-refresh-done":
32537
+ case "hindsight-skipped":
32538
+ return Number.MAX_SAFE_INTEGER - 3;
32389
32539
  case "hostd-web-deferred":
32390
32540
  return Number.MAX_SAFE_INTEGER - 1;
32391
32541
  default:
@@ -32402,6 +32552,7 @@ class LogTailRolloutNarrator {
32402
32552
  return;
32403
32553
  st.lastAppliedSeq = seq;
32404
32554
  this.trackAgentProgress(st, phase);
32555
+ this.trackSingletonProgress(st, phase);
32405
32556
  const doneNames = st.agents.filter((a) => a.status === "done").map((a) => a.name);
32406
32557
  const rolledSoFar = entry.rolled && entry.rolled.length > 0 ? entry.rolled : doneNames;
32407
32558
  st.render = {
@@ -32413,6 +32564,7 @@ class LogTailRolloutNarrator {
32413
32564
  ...phase.agent !== undefined ? { agent: phase.agent } : {},
32414
32565
  rolled: rolledSoFar,
32415
32566
  agents: st.agents,
32567
+ singletons: st.singletons,
32416
32568
  requestId: entry.request_id,
32417
32569
  ...entry.prior_pin ? { fromVersion: entry.prior_pin } : {},
32418
32570
  startedAtMs: entry.started_at
@@ -32435,6 +32587,7 @@ class LogTailRolloutNarrator {
32435
32587
  a.status = "pending";
32436
32588
  }
32437
32589
  }
32590
+ this.reconcileSingletonsAtTerminal(st, entry);
32438
32591
  st.render = {
32439
32592
  target: entry.pin ?? st.render.target,
32440
32593
  rolled: entry.rolled ?? st.render.rolled,
@@ -32444,6 +32597,7 @@ class LogTailRolloutNarrator {
32444
32597
  ...entry.got !== undefined ? { got: entry.got } : {},
32445
32598
  ...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
32446
32599
  ...st.agents.length > 0 ? { agents: st.agents } : {},
32600
+ ...st.singletons.length > 0 ? { singletons: st.singletons } : {},
32447
32601
  ...st.render.m !== undefined ? { m: st.render.m } : {},
32448
32602
  requestId: entry.request_id,
32449
32603
  ...entry.prior_pin ?? st.render.fromVersion ? { fromVersion: entry.prior_pin ?? st.render.fromVersion } : {},
@@ -32461,6 +32615,63 @@ class LogTailRolloutNarrator {
32461
32615
  this.opts.log?.(`onTerminal threw (non-fatal): ${e.message}`);
32462
32616
  }
32463
32617
  }
32618
+ reconcileSingletonsAtTerminal(st, entry) {
32619
+ const completed = entry.result === "completed";
32620
+ const drifted = new Set(entry.drifted ?? []);
32621
+ const driftedHits = (row) => {
32622
+ switch (row) {
32623
+ case SINGLETON_WEB:
32624
+ return [...drifted].filter((d) => d === "switchroom-web");
32625
+ case SINGLETON_HINDSIGHT:
32626
+ return [...drifted].filter((d) => d === "switchroom-hindsight");
32627
+ case SINGLETON_HOSTD:
32628
+ return [...drifted].filter((d) => d === "switchroom-hostd" || d === "switchroom-hindsight-autoheal");
32629
+ case SINGLETON_SHARED:
32630
+ return [...drifted].filter((d) => d.includes("broker") || d.includes("kernel") || d.includes("voice"));
32631
+ default:
32632
+ return [];
32633
+ }
32634
+ };
32635
+ for (const g of st.singletons) {
32636
+ const hits = driftedHits(g.name);
32637
+ if (hits.length > 0) {
32638
+ g.status = "failed";
32639
+ g.detail = `still behind (${hits.join(", ")}) — see the roll's warnings`;
32640
+ continue;
32641
+ }
32642
+ if (entry.failed_step === "refresh-hindsight" && g.name === SINGLETON_HINDSIGHT) {
32643
+ g.status = "failed";
32644
+ g.detail = "recreate FAILED — memory backend down; run `switchroom memory setup`";
32645
+ continue;
32646
+ }
32647
+ if (!completed) {
32648
+ if (g.status === "updating" && g.name !== SINGLETON_SHARED) {
32649
+ g.detail = "outcome unknown — the roll stopped; see warnings";
32650
+ }
32651
+ continue;
32652
+ }
32653
+ switch (g.name) {
32654
+ case SINGLETON_WEB:
32655
+ case SINGLETON_HINDSIGHT:
32656
+ if (g.status === "updated")
32657
+ break;
32658
+ if (g.status === "updating") {
32659
+ g.status = "updated";
32660
+ g.detail = "verified by the end-of-roll component check";
32661
+ } else if (g.status === "pending") {
32662
+ g.status = "skipped";
32663
+ g.detail = "no refresh observed in this roll — see warnings";
32664
+ }
32665
+ break;
32666
+ case SINGLETON_SHARED:
32667
+ g.status = "updated";
32668
+ g.detail = "self-healed with the first agent restart; end-of-roll check passed";
32669
+ break;
32670
+ default:
32671
+ break;
32672
+ }
32673
+ }
32674
+ }
32464
32675
  ensureState(entry) {
32465
32676
  let st = this.states.get(entry.request_id);
32466
32677
  if (!st) {
@@ -32474,6 +32685,7 @@ class LogTailRolloutNarrator {
32474
32685
  postFailed: false,
32475
32686
  render: { target: entry.pin ?? "" },
32476
32687
  agents: [],
32688
+ singletons: initialSingletons(),
32477
32689
  frozen: false,
32478
32690
  timer: null,
32479
32691
  pendingEditAfterPost: false
@@ -4417,7 +4417,7 @@ var init_protocol = __esm(() => {
4417
4417
  });
4418
4418
 
4419
4419
  // src/memory/observation-scopes.ts
4420
- var OBSERVATION_SCOPES;
4420
+ var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
4421
4421
  var init_observation_scopes = __esm(() => {
4422
4422
  OBSERVATION_SCOPES = [
4423
4423
  "per_tag",
@@ -4425,10 +4425,16 @@ var init_observation_scopes = __esm(() => {
4425
4425
  "all_combinations",
4426
4426
  "shared"
4427
4427
  ];
4428
+ OBSERVATION_SCOPE_STRATEGIES = [
4429
+ "curated",
4430
+ "shared",
4431
+ "combined",
4432
+ "off"
4433
+ ];
4428
4434
  });
4429
4435
 
4430
4436
  // src/config/schema.ts
4431
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4437
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4432
4438
  var init_schema = __esm(() => {
4433
4439
  init_zod();
4434
4440
  init_observation_scopes();
@@ -4543,7 +4549,8 @@ var init_schema = __esm(() => {
4543
4549
  allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
4544
4550
  deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
4545
4551
  }).optional();
4546
- ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag — what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4552
+ ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4553
+ ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4547
4554
  AgentMemorySchema = exports_external.object({
4548
4555
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
4549
4556
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -4579,6 +4586,7 @@ var init_schema = __esm(() => {
4579
4586
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4580
4587
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4581
4588
  observation_scopes: ObservationScopesSchema,
4589
+ observation_scope_strategy: ObservationScopeStrategySchema,
4582
4590
  recall: exports_external.object({
4583
4591
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
4584
4592
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
@@ -4979,6 +4987,7 @@ var init_schema = __esm(() => {
4979
4987
  isolation: exports_external.enum(["default", "strict"]).optional(),
4980
4988
  directive_capture_nudge: exports_external.boolean().optional(),
4981
4989
  observation_scopes: ObservationScopesSchema,
4990
+ observation_scope_strategy: ObservationScopeStrategySchema,
4982
4991
  recall: exports_external.object({
4983
4992
  max_memories: exports_external.number().int().min(0).optional(),
4984
4993
  cache_ttl_secs: exports_external.number().int().min(0).optional(),
@@ -19337,8 +19346,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
19337
19346
  var HINDSIGHT_PG_APP_ANON_MIB = 2560;
19338
19347
  var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
19339
19348
  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;
19340
- var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072;
19341
- var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168;
19349
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
19350
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
19342
19351
  function pgMib(mib) {
19343
19352
  return `${mib}MB`;
19344
19353
  }
@@ -19382,7 +19391,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
19382
19391
  var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
19383
19392
  var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
19384
19393
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
19385
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500;
19394
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
19386
19395
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
19387
19396
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
19388
19397
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
4013
4013
  });
4014
4014
 
4015
4015
  // src/memory/observation-scopes.ts
4016
- var OBSERVATION_SCOPES;
4016
+ var OBSERVATION_SCOPES, OBSERVATION_SCOPE_STRATEGIES;
4017
4017
  var init_observation_scopes = __esm(() => {
4018
4018
  OBSERVATION_SCOPES = [
4019
4019
  "per_tag",
@@ -4021,10 +4021,16 @@ var init_observation_scopes = __esm(() => {
4021
4021
  "all_combinations",
4022
4022
  "shared"
4023
4023
  ];
4024
+ OBSERVATION_SCOPE_STRATEGIES = [
4025
+ "curated",
4026
+ "shared",
4027
+ "combined",
4028
+ "off"
4029
+ ];
4024
4030
  });
4025
4031
 
4026
4032
  // src/config/schema.ts
4027
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4033
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4028
4034
  var init_schema = __esm(() => {
4029
4035
  init_zod();
4030
4036
  init_observation_scopes();
@@ -4139,7 +4145,8 @@ var init_schema = __esm(() => {
4139
4145
  allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
4140
4146
  deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
4141
4147
  }).optional();
4142
- ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag — what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4148
+ ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4149
+ ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4143
4150
  AgentMemorySchema = exports_external.object({
4144
4151
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
4145
4152
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -4175,6 +4182,7 @@ var init_schema = __esm(() => {
4175
4182
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4176
4183
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4177
4184
  observation_scopes: ObservationScopesSchema,
4185
+ observation_scope_strategy: ObservationScopeStrategySchema,
4178
4186
  recall: exports_external.object({
4179
4187
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
4180
4188
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
@@ -4575,6 +4583,7 @@ var init_schema = __esm(() => {
4575
4583
  isolation: exports_external.enum(["default", "strict"]).optional(),
4576
4584
  directive_capture_nudge: exports_external.boolean().optional(),
4577
4585
  observation_scopes: ObservationScopesSchema,
4586
+ observation_scope_strategy: ObservationScopeStrategySchema,
4578
4587
  recall: exports_external.object({
4579
4588
  max_memories: exports_external.number().int().min(0).optional(),
4580
4589
  cache_ttl_secs: exports_external.number().int().min(0).optional(),
@@ -19750,8 +19759,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
19750
19759
  var HINDSIGHT_PG_APP_ANON_MIB = 2560;
19751
19760
  var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
19752
19761
  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;
19753
- var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072;
19754
- var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168;
19762
+ var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
19763
+ var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
19755
19764
  function pgMib(mib) {
19756
19765
  return `${mib}MB`;
19757
19766
  }
@@ -19795,7 +19804,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
19795
19804
  var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
19796
19805
  var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
19797
19806
  var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
19798
- var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500;
19807
+ var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
19799
19808
  var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
19800
19809
  var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
19801
19810
  var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
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.38",
4
+ "version": "0.19.40",
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": {
@@ -1018,6 +1018,16 @@ export HINDSIGHT_DIRECTIVE_CAPTURE_NUDGE={{hindsightDirectiveCaptureNudge}}
1018
1018
  {{#if hindsightObservationScopesQ}}
1019
1019
  export HINDSIGHT_OBSERVATION_SCOPES={{{hindsightObservationScopesQ}}}
1020
1020
  {{/if}}
1021
+ # Per-retain observation-scope STRATEGY (memory.observation_scope_strategy
1022
+ # cascade). Selects curated-vs-not: "curated" (plugin default) strips volatile
1023
+ # per-session provenance tags so observations dedup across sessions; "shared"
1024
+ # forces the one global untagged scope; "combined"/"off" opt out (no per-row
1025
+ # scope on the wire, pre-feature engine default). Export ONLY when the operator
1026
+ # set it: unset leaves the plugin's on-by-default `curated` in force. A pinned
1027
+ # per-row scope (memory.observation_scopes) still wins over this in the resolver.
1028
+ {{#if hindsightObservationScopeStrategyQ}}
1029
+ export HINDSIGHT_OBSERVATION_SCOPE_STRATEGY={{{hindsightObservationScopeStrategyQ}}}
1030
+ {{/if}}
1021
1031
  # PR6 — supergroup-mode topic tagging. JSON map of {alias: thread_id}
1022
1032
  # parsed by retain.py + recall.py to (a) stamp chat_id/thread_id/topic_alias
1023
1033
  # into retained memory metadata and (b) emit a "Current topic: …" preamble
@@ -23543,6 +23543,14 @@ function projectTranscriptLine(line) {
23543
23543
  { kind: "turn_end", durationMs: obj.durationMs ?? 0 }
23544
23544
  ];
23545
23545
  }
23546
+ if (type === "system" && obj.subtype === "compact_boundary") {
23547
+ const meta = obj.compactMetadata;
23548
+ return [{
23549
+ kind: "compact_boundary",
23550
+ trigger: typeof meta?.trigger === "string" ? meta.trigger : null,
23551
+ compactDurationMs: typeof meta?.durationMs === "number" ? meta.durationMs : null
23552
+ }];
23553
+ }
23546
23554
  return [];
23547
23555
  }
23548
23556
  function projectSubagentLine(line, agentId, state) {