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.
- package/dist/agent-scheduler/index.js +4 -1
- package/dist/auth-broker/index.js +4 -1
- package/dist/cli/notion-write-pretool.mjs +4 -1
- package/dist/cli/switchroom.js +1469 -1247
- package/dist/cli/ui/index.html +31 -0
- package/dist/host-control/main.js +4 -1
- package/dist/vault/approvals/kernel-server.js +4 -1
- package/dist/vault/broker/server.js +23 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +15 -0
- package/telegram-plugin/dist/gateway/gateway.js +400 -227
- package/telegram-plugin/gateway/context-occupancy.ts +91 -0
- package/telegram-plugin/gateway/gateway.ts +204 -63
- package/telegram-plugin/gateway/hostd-dispatch.ts +1 -1
- package/telegram-plugin/gateway/idle-clear.ts +72 -0
- package/telegram-plugin/gateway/poll-health.ts +9 -4
- package/telegram-plugin/gateway/poll-stall-recovery.ts +59 -0
- package/telegram-plugin/tests/context-occupancy.test.ts +55 -0
- package/telegram-plugin/tests/idle-clear.test.ts +62 -0
- package/telegram-plugin/tests/poll-stall-recovery.test.ts +32 -0
- package/telegram-plugin/tests/welcome-text.test.ts +10 -11
- package/telegram-plugin/welcome-text.ts +11 -12
- package/vendor/hindsight-memory/scripts/lib/config.py +12 -0
- package/vendor/hindsight-memory/scripts/recall.py +64 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_trivial_skip.py +101 -0
- package/vendor/hindsight-memory/tests/test_config.py +3 -3
package/dist/cli/ui/index.html
CHANGED
|
@@ -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
|
@@ -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
|