switchroom 0.15.40 → 0.15.42

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.
@@ -122,6 +122,15 @@
122
122
 
123
123
  .meta-item span { color: var(--text); }
124
124
 
125
+ /* Context-headroom gauge (RFC context-headroom-surface). */
126
+ .ctx-gauge { display: inline-flex; align-items: center; gap: 0.4rem; }
127
+ .ctx-text { color: var(--text); font-variant-numeric: tabular-nums; }
128
+ .ctx-bar {
129
+ display: inline-block; width: 48px; height: 6px; border-radius: 3px;
130
+ background: var(--bg-dim, rgba(255,255,255,0.12)); overflow: hidden;
131
+ }
132
+ .ctx-fill { display: block; height: 100%; border-radius: 3px; }
133
+
125
134
  .scope-toggle {
126
135
  cursor: pointer;
127
136
  color: var(--text);
@@ -1659,6 +1668,27 @@
1659
1668
  return 'inactive';
1660
1669
  }
1661
1670
 
1671
+ // Context-headroom gauge (RFC context-headroom-surface): occupancy/cap
1672
+ // with a fill bar, amber when tight (≥80%). '—' when no snapshot yet.
1673
+ function fmtTok(n) {
1674
+ return n >= 1000 ? (n / 1000).toFixed(n >= 10000 ? 0 : 1) + 'k' : String(n);
1675
+ }
1676
+ function formatContext(ctx) {
1677
+ if (!ctx || ctx.state === 'unknown' || typeof ctx.occupancy !== 'number') {
1678
+ return '<span style="color:var(--text-dim)">—</span>';
1679
+ }
1680
+ if (ctx.cap == null || ctx.pct == null) {
1681
+ return `<span>${fmtTok(ctx.occupancy)} <span style="color:var(--text-dim)">(no cap)</span></span>`;
1682
+ }
1683
+ const pct = Math.max(0, Math.min(1, ctx.pct));
1684
+ const tight = ctx.state === 'tight';
1685
+ const col = tight ? 'var(--amber, #d99)' : 'var(--green)';
1686
+ return `<span class="ctx-gauge" title="${fmtTok(ctx.occupancy)} / ${fmtTok(ctx.cap)} tokens — ${Math.round(pct * 100)}% of cap${tight ? ' (tight — /compact near)' : ''}">`
1687
+ + `<span class="ctx-text">${fmtTok(ctx.occupancy)}/${fmtTok(ctx.cap)} (${Math.round(pct * 100)}%)</span>`
1688
+ + `<span class="ctx-bar"><span class="ctx-fill" style="width:${(pct * 100).toFixed(0)}%;background:${col}"></span></span>`
1689
+ + `</span>`;
1690
+ }
1691
+
1662
1692
  function formatUptime(timestamp) {
1663
1693
  if (!timestamp) return '--';
1664
1694
  const d = new Date(timestamp);
@@ -1693,6 +1723,7 @@
1693
1723
  <div class="meta-item"><label>Uptime </label><span>${formatUptime(a.uptime)}</span></div>
1694
1724
  <div class="meta-item"><label>Mem </label><span>${a.memory || '--'}</span></div>
1695
1725
  <div class="meta-item"><label>Last turn </label><span>${a.lastTurnAt ? formatTimestamp(a.lastTurnAt) : '—'}</span></div>
1726
+ <div class="meta-item meta-context"><label>Context </label>${formatContext(a.context)}</div>
1696
1727
  <div class="meta-item"><label>Profile </label><span>${escapeHtml(a.extends)}</span></div>
1697
1728
  <div class="meta-item"><label>Auth </label><span>${a.auth.authenticated ? '✓' : '✗'}</span></div>
1698
1729
  <div class="meta-item"><label>Account </label><span>${a.primaryAccount ? escapeHtml(a.primaryAccount) : '<span style="color:var(--text-dim)">default</span>'}</span></div>
@@ -13811,6 +13811,8 @@ var AgentMemorySchema = exports_external.object({
13811
13811
  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)."),
13812
13812
  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."),
13813
13813
  min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
13814
+ types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
13815
+ skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
13814
13816
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
13815
13817
  }).optional().describe("Auto-recall tuning knobs")
13816
13818
  }).optional();
@@ -13847,7 +13849,8 @@ var SubagentSchema = exports_external.object({
13847
13849
  var SessionSchema = exports_external.object({
13848
13850
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
13849
13851
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
13850
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
13852
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
13853
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
13851
13854
  }).optional();
13852
13855
  var SessionContinuitySchema = exports_external.object({
13853
13856
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11419,6 +11419,8 @@ var init_schema = __esm(() => {
11419
11419
  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)."),
11420
11420
  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."),
11421
11421
  min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
11422
+ types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
11423
+ skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
11422
11424
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
11423
11425
  }).optional().describe("Auto-recall tuning knobs")
11424
11426
  }).optional();
@@ -11455,7 +11457,8 @@ var init_schema = __esm(() => {
11455
11457
  SessionSchema = exports_external.object({
11456
11458
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11457
11459
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11458
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11460
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11461
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11459
11462
  }).optional();
11460
11463
  SessionContinuitySchema = exports_external.object({
11461
11464
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -11419,6 +11419,8 @@ var init_schema = __esm(() => {
11419
11419
  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)."),
11420
11420
  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."),
11421
11421
  min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0–1.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default — " + "current behaviour). Try 0.10–0.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
11422
+ types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] — the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
11423
+ skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true — saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
11422
11424
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) → soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: …' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
11423
11425
  }).optional().describe("Auto-recall tuning knobs")
11424
11426
  }).optional();
@@ -11455,7 +11457,8 @@ var init_schema = __esm(() => {
11455
11457
  SessionSchema = exports_external.object({
11456
11458
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
11457
11459
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
11458
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
11460
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
11461
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
11459
11462
  }).optional();
11460
11463
  SessionContinuitySchema = exports_external.object({
11461
11464
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -16355,6 +16358,9 @@ class VaultBroker {
16355
16358
  this.passphrase = passphrase;
16356
16359
  this._setReadinessSentinel(true);
16357
16360
  }
16361
+ reload(config) {
16362
+ this.config = config;
16363
+ }
16358
16364
  _setReadinessSentinel(ready) {
16359
16365
  const p = process.env.SWITCHROOM_VAULT_BROKER_READY_PATH;
16360
16366
  if (!p || p.length === 0)
@@ -16425,6 +16431,9 @@ class VaultBroker {
16425
16431
  _getSecretsRef() {
16426
16432
  return this.secrets;
16427
16433
  }
16434
+ _getConfigRef() {
16435
+ return this.config;
16436
+ }
16428
16437
  bindAgentSocket(socketPath) {
16429
16438
  const abs = resolve6(socketPath);
16430
16439
  const agentName = socketPathToAgent(abs);
@@ -17856,6 +17865,19 @@ async function main() {
17856
17865
  }
17857
17866
  const broker = new VaultBroker;
17858
17867
  registerShutdownHandlers(broker);
17868
+ process.on("SIGHUP", () => {
17869
+ (async () => {
17870
+ try {
17871
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_loader(), exports_loader));
17872
+ broker.reload(loadConfig2(configPath));
17873
+ process.stdout.write(`vault-broker: SIGHUP reload — config refreshed
17874
+ `);
17875
+ } catch (err) {
17876
+ process.stderr.write(`vault-broker: SIGHUP reload failed (keeping previous config): ${err.message}
17877
+ `);
17878
+ }
17879
+ })();
17880
+ });
17859
17881
  if (perAgentTargets.length > 0) {
17860
17882
  await broker.start(legacySocketPath, configPath, vaultPath);
17861
17883
  process.stdout.write(`vault-broker: legacy socket listening on ${legacySocketPath}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.40",
3
+ "version": "0.15.42",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -332,6 +332,21 @@ export HINDSIGHT_RECALL_CACHE_TTL_SECS={{hindsightRecallCacheTtlSecs}}
332
332
  {{#if (isNumber hindsightRecallMinOverlap)}}
333
333
  export HINDSIGHT_RECALL_MIN_OVERLAP={{hindsightRecallMinOverlap}}
334
334
  {{/if}}
335
+ # Recall fact types (memory.recall.types cascade). Switchroom default is
336
+ # world,experience,observation (the synthesized `observation` tier is ON
337
+ # by default, set in the plugin settings.json). Export only when the
338
+ # operator OPTED OUT via switchroom.yaml — comma-separated; the env value
339
+ # wins over settings.json. e.g. ["world","experience"] drops
340
+ # observation-backed recall for this agent.
341
+ {{#if hindsightRecallTypes}}
342
+ export HINDSIGHT_RECALL_TYPES="{{hindsightRecallTypes}}"
343
+ {{/if}}
344
+ # Trivial-turn recall skip (memory.recall.skip_trivial cascade). On by
345
+ # default (plugin settings.json). Export only when the operator overrode
346
+ # it; set false to always run recall.
347
+ {{#if hindsightRecallSkipTrivial}}
348
+ export HINDSIGHT_RECALL_SKIP_TRIVIAL={{hindsightRecallSkipTrivial}}
349
+ {{/if}}
335
350
  # PR6 — supergroup-mode topic tagging. JSON map of {alias: thread_id}
336
351
  # parsed by retain.py + recall.py to (a) stamp chat_id/thread_id/topic_alias
337
352
  # into retained memory metadata and (b) emit a "Current topic: …" preamble