switchroom 0.17.10 → 0.18.3
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/bin/workspace-dynamic-hook.sh +12 -13
- package/dist/agent-scheduler/index.js +27 -1
- package/dist/auth-broker/index.js +6161 -151
- package/dist/cli/notion-write-pretool.mjs +29 -2
- package/dist/cli/switchroom.js +578 -454
- package/dist/host-control/main.js +6182 -172
- package/dist/vault/approvals/kernel-server.js +5891 -164
- package/dist/vault/broker/server.js +6597 -881
- package/package.json +1 -1
- package/profiles/_base/settings.json.hbs +2 -2
- package/profiles/_base/start.sh.hbs +170 -21
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md +2 -2
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/auth-snapshot-format.ts +22 -24
- package/telegram-plugin/context-exhaustion.ts +124 -0
- package/telegram-plugin/dist/gateway/gateway.js +24086 -8727
- package/telegram-plugin/gateway/activity-card-store.ts +76 -0
- package/telegram-plugin/gateway/gateway.ts +480 -85
- package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
- package/telegram-plugin/gateway/model-command.ts +70 -10
- package/telegram-plugin/package.json +6 -0
- package/telegram-plugin/quota-watch.ts +4 -6
- package/telegram-plugin/registry/turns-schema.test.ts +97 -0
- package/telegram-plugin/registry/turns-schema.ts +78 -0
- package/telegram-plugin/render/ir.ts +209 -0
- package/telegram-plugin/render/parse.ts +363 -0
- package/telegram-plugin/render/render.ts +440 -0
- package/telegram-plugin/render/rich-render.ts +72 -0
- package/telegram-plugin/stream-controller.ts +14 -3
- package/telegram-plugin/tests/activity-card-store.test.ts +94 -0
- package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +30 -16
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +11 -0
- package/telegram-plugin/tests/feed-survival.test.ts +39 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
- package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
- package/telegram-plugin/tests/model-command.test.ts +193 -16
- package/telegram-plugin/tests/narrative-render.test.ts +125 -0
- package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
- package/telegram-plugin/tests/quota-watch.test.ts +1 -4
- package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
- package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
- package/telegram-plugin/tests/render/parse.test.ts +393 -0
- package/telegram-plugin/tests/render/render.test.ts +436 -0
- package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
- package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +98 -3
- package/telegram-plugin/turn-liveness-floor.ts +35 -1
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
- package/telegram-plugin/worktree-watch-cwds.ts +92 -17
- package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
- package/vendor/hindsight-memory/scripts/lib/config.py +9 -2
- package/vendor/hindsight-memory/scripts/recall.py +64 -6
- package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
- package/vendor/hindsight-memory/tests/test_client.py +43 -0
- package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
# Configuration is via env vars (set at start.sh time):
|
|
11
11
|
#
|
|
12
12
|
# SWITCHROOM_AGENT_NAME - The agent name (required, set in start.sh)
|
|
13
|
-
# SWITCHROOM_INJECT_ON_CHANGE -
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# the default).
|
|
13
|
+
# SWITCHROOM_INJECT_ON_CHANGE - Inject-on-change semantics are the default:
|
|
14
|
+
# content is only emitted when the session_id
|
|
15
|
+
# changes or file content changes. Set to "0"
|
|
16
|
+
# to opt out and emit every turn (legacy full
|
|
17
|
+
# emit) — scaffold.ts threads this negation
|
|
18
|
+
# only when an agent sets
|
|
19
|
+
# channels.telegram.inject_on_change: false.
|
|
21
20
|
#
|
|
22
21
|
# Failure modes (all silent — workspace injection must never block the turn):
|
|
23
22
|
# - switchroom CLI missing → exit 0 with no output
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
set -u
|
|
30
29
|
|
|
31
30
|
AGENT_NAME="${SWITCHROOM_AGENT_NAME:-}"
|
|
32
|
-
INJECT_ON_CHANGE="${SWITCHROOM_INJECT_ON_CHANGE:-
|
|
31
|
+
INJECT_ON_CHANGE="${SWITCHROOM_INJECT_ON_CHANGE:-1}"
|
|
33
32
|
|
|
34
33
|
if [ -z "$AGENT_NAME" ]; then
|
|
35
34
|
exit 0
|
|
@@ -43,7 +42,7 @@ fi
|
|
|
43
42
|
# Inject-on-change: read session_id from stdin JSON (when enabled)
|
|
44
43
|
# ---------------------------------------------------------------------------
|
|
45
44
|
SESSION_ID=""
|
|
46
|
-
if [ "$INJECT_ON_CHANGE"
|
|
45
|
+
if [ "$INJECT_ON_CHANGE" != "0" ]; then
|
|
47
46
|
# Read stdin (non-blocking: if no stdin supplied in tests, just skip).
|
|
48
47
|
if ! [ -t 0 ]; then
|
|
49
48
|
STDIN_JSON=$(cat 2>/dev/null || true)
|
|
@@ -118,7 +117,7 @@ if [ -f "$BODY_FILE" ]; then
|
|
|
118
117
|
# In inject-on-change mode, also check the session-state file — if the
|
|
119
118
|
# session_id matches the last-emitted session AND the hash matches, we
|
|
120
119
|
# can suppress entirely (model already has this content in context).
|
|
121
|
-
if [ "$INJECT_ON_CHANGE"
|
|
120
|
+
if [ "$INJECT_ON_CHANGE" != "0" ] && [ -n "$SESSION_ID" ]; then
|
|
122
121
|
SESSION_STATE_DIR="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}/.hook-state"
|
|
123
122
|
SESSION_STATE_FILE="$SESSION_STATE_DIR/ws-dynamic.$SESSION_ID"
|
|
124
123
|
if [ -f "$SESSION_STATE_FILE" ]; then
|
|
@@ -201,7 +200,7 @@ _ws_record_session_state() {
|
|
|
201
200
|
if [ -n "$NEW_HASH" ] && [ "$NEW_HASH" = "$OLD_HASH" ] && [ -f "$BODY_FILE" ]; then
|
|
202
201
|
# Content unchanged since last render. In inject-on-change mode, check if
|
|
203
202
|
# we already injected this content in the current session — if so, suppress.
|
|
204
|
-
if [ "$INJECT_ON_CHANGE"
|
|
203
|
+
if [ "$INJECT_ON_CHANGE" != "0" ] && [ -n "$SESSION_ID" ]; then
|
|
205
204
|
SESSION_STATE_DIR="${TELEGRAM_STATE_DIR:-${HOME:-/tmp}/.claude/switchroom-hookcache}/.hook-state"
|
|
206
205
|
SESSION_STATE_FILE="$SESSION_STATE_DIR/ws-dynamic.$SESSION_ID"
|
|
207
206
|
if [ -f "$SESSION_STATE_FILE" ]; then
|
|
@@ -222,7 +221,7 @@ else
|
|
|
222
221
|
if [ -n "$NEW_HASH" ]; then
|
|
223
222
|
printf '%s\n' "$NEW_HASH" > "$CACHE_FILE" 2>/dev/null || true
|
|
224
223
|
printf '%s\n' "$WS_DYNAMIC" > "$BODY_FILE" 2>/dev/null || true
|
|
225
|
-
if [ "$INJECT_ON_CHANGE"
|
|
224
|
+
if [ "$INJECT_ON_CHANGE" != "0" ] && [ -n "$SESSION_ID" ]; then
|
|
226
225
|
_ws_record_session_state "$NEW_HASH" "$SESSION_ID"
|
|
227
226
|
fi
|
|
228
227
|
fi
|
|
@@ -11108,7 +11108,11 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11108
11108
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender → extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username — a leading @ is " + "optional — or numeric user_id), merged " + "into its own results — so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
11109
11109
|
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."),
|
|
11110
11110
|
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).")
|
|
11111
|
-
}).optional().describe("Auto-recall tuning knobs")
|
|
11111
|
+
}).optional().describe("Auto-recall tuning knobs"),
|
|
11112
|
+
retain: exports_external.object({
|
|
11113
|
+
every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
|
|
11114
|
+
overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
|
|
11115
|
+
}).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
|
|
11112
11116
|
}).optional();
|
|
11113
11117
|
var HookEntrySchema = exports_external.object({
|
|
11114
11118
|
command: exports_external.string().describe("Shell command to run. Supports ${CLAUDE_CONFIG_DIR} and ${CLAUDE_PLUGIN_ROOT} substitution."),
|
|
@@ -11286,6 +11290,21 @@ var LiteLLMConfigSchema = exports_external.object({
|
|
|
11286
11290
|
small_fast_model: exports_external.string().optional().describe("Model id exported as ANTHROPIC_SMALL_FAST_MODEL for the claude CLI's " + "background/fast lane, e.g. 'claude-haiku-4-5-20251001'."),
|
|
11287
11291
|
tags: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra key/value metadata tags attached to the provisioned LiteLLM " + "virtual key. Merged per-key across cascade layers (agent wins).")
|
|
11288
11292
|
}).optional().describe("LiteLLM routing config — opt-in per-agent virtual-key auto-provisioning " + "+ routing env. Default OFF. See LiteLLMConfigSchema doc for the full flow.");
|
|
11293
|
+
var HindsightPerOpLlmSchema = exports_external.object({
|
|
11294
|
+
model: exports_external.string().min(1).optional().describe("Per-op model (upstream `HINDSIGHT_API_<OP>_LLM_MODEL`). Absent → " + "inherit the global `hindsight.llm.model`."),
|
|
11295
|
+
provider: exports_external.string().min(1).optional().describe("Per-op provider (upstream `HINDSIGHT_API_<OP>_LLM_PROVIDER`). " + "Absent → inherit the global `hindsight.llm.provider`."),
|
|
11296
|
+
base_url: exports_external.string().min(1).optional().describe("Per-op base URL (upstream `HINDSIGHT_API_<OP>_LLM_BASE_URL`). " + "Optional passthrough; absent → inherit the global."),
|
|
11297
|
+
api_key: exports_external.string().min(1).optional().describe("Per-op API key (upstream `HINDSIGHT_API_<OP>_LLM_API_KEY`). Literal " + "or `vault:` reference. Optional passthrough; absent → inherit global.")
|
|
11298
|
+
}).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback — switchroom emits only the vars set.");
|
|
11299
|
+
var HindsightConfigSchema = exports_external.object({
|
|
11300
|
+
llm: exports_external.object({
|
|
11301
|
+
provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
|
|
11302
|
+
model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
|
|
11303
|
+
retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent → uses the global model/provider."),
|
|
11304
|
+
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
|
|
11305
|
+
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
|
|
11306
|
+
}).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults.")
|
|
11307
|
+
});
|
|
11289
11308
|
var MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
11290
11309
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
11291
11310
|
microsoft_client_secret: exports_external.string().min(1).optional().describe("Microsoft OAuth client secret. Optional — public-client apps " + "(Mobile + Desktop platform with 'Allow public client flows' " + "enabled) work without a secret; confidential clients pass " + "one. Either literal or vault reference e.g. " + "'vault:microsoft-oauth-client-secret'."),
|
|
@@ -11534,6 +11553,7 @@ var MemoryBackendConfigSchema = exports_external.object({
|
|
|
11534
11553
|
config: exports_external.object({
|
|
11535
11554
|
provider: exports_external.string().default("ollama").describe("Embedding provider (ollama, openai, anthropic)"),
|
|
11536
11555
|
model: exports_external.string().optional().describe("Embedding model (e.g., 'nomic-embed-text')"),
|
|
11556
|
+
llm_model: exports_external.string().optional().describe("LiteLLM model name for Hindsight's LLM ops (retain/reflect/" + "consolidation) when the top-level `litellm.enabled` carve-out is " + "on. Defaults to a cheap OpenRouter model (routed via LiteLLM's " + "model-mapped path, not the Anthropic OAuth pass-through) to keep " + "background memory-op cost off the Claude subscription quota. Set " + "to a `claude-*` model name to route it back through the OAuth " + "pass-through instead. Has no effect when litellm is disabled."),
|
|
11537
11557
|
api_key: exports_external.string().optional().describe("API key or vault reference for embedding provider"),
|
|
11538
11558
|
docker_service: exports_external.boolean().default(true).describe("Whether to include Hindsight in docker-compose"),
|
|
11539
11559
|
url: exports_external.string().url("Hindsight URL must be a valid URL (no shell-special characters)").optional().describe("Hindsight MCP endpoint URL (e.g., http://localhost:18888/mcp/). Defaults to http://localhost:8888/mcp/.")
|
|
@@ -11611,6 +11631,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11611
11631
|
telegram: TelegramConfigSchema,
|
|
11612
11632
|
release: ReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "— mutually exclusive. Per-agent `release` REPLACES this entirely."),
|
|
11613
11633
|
memory: MemoryBackendConfigSchema.optional(),
|
|
11634
|
+
hindsight: HindsightConfigSchema.optional().describe("Fleet-singleton Hindsight (memory backend) configuration. Currently " + "just the LLM knob (provider + model) used for retain/reflect/" + "consolidation. Both fields optional; when unset the container falls " + "back to provider=claude-code + the hard-coded HINDSIGHT_DEFAULT_MODEL " + "so nothing changes for operators who don't set it. Read at container " + "launch by startHindsight() (src/setup/hindsight.ts) — takes effect on " + "the next `switchroom apply` / `memory setup --recreate`."),
|
|
11614
11635
|
vault: VaultConfigSchema.optional(),
|
|
11615
11636
|
auth: exports_external.object({
|
|
11616
11637
|
active: exports_external.string().min(1).optional().describe("Fleet-wide active Anthropic account label. Every agent without " + "an explicit `agent.auth.override` uses this account. See " + "docs/auth.md for the full model. Set by `switchroom auth use <label>`."),
|
|
@@ -12007,6 +12028,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
12007
12028
|
continue;
|
|
12008
12029
|
if (k === "recall" && base.recall && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
12009
12030
|
combined[k] = { ...base.recall, ...v };
|
|
12031
|
+
} else if (k === "retain" && base.retain && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
12032
|
+
combined[k] = {
|
|
12033
|
+
...base.retain,
|
|
12034
|
+
...v
|
|
12035
|
+
};
|
|
12010
12036
|
} else if (k === "disposition" && base.disposition && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
12011
12037
|
combined[k] = {
|
|
12012
12038
|
...base.disposition,
|