switchroom 0.20.10 → 0.20.12
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 +68 -3
- package/dist/auth-broker/index.js +211 -46
- package/dist/cli/notion-write-pretool.mjs +68 -3
- package/dist/cli/self-improve-apply-guard-pretool.mjs +357 -92
- package/dist/cli/self-improve-stop.mjs +889 -7
- package/dist/cli/skill-validate-pretool.mjs +82 -3
- package/dist/cli/switchroom.js +5063 -2961
- package/dist/host-control/main.js +93 -27
- package/dist/vault/approvals/kernel-server.js +92 -26
- package/dist/vault/broker/server.js +92 -26
- package/examples/personal-google-workspace-mcp/compose.yaml +1 -1
- package/package.json +1 -1
- package/profiles/_shared/agent-self-service.md.hbs +15 -22
- package/profiles/_shared/delegation-golden-rule.md.hbs +1 -1
- package/profiles/_shared/dev-protocol.md.hbs +1 -1
- package/profiles/_shared/execution-discipline.md.hbs +4 -4
- package/profiles/_shared/vault-protocol.md.hbs +2 -18
- package/profiles/default/CLAUDE.md.hbs +3 -5
- package/skills/switchroom-architecture/telegram.md +0 -1
- package/skills/switchroom-cli/SKILL.md +0 -1
- package/telegram-plugin/README.md +2 -11
- package/telegram-plugin/auto-fallback-fleet.ts +37 -2
- package/telegram-plugin/bridge/bridge.ts +0 -12
- package/telegram-plugin/chat-lock.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +0 -12
- package/telegram-plugin/dist/gateway/gateway.js +1454 -976
- package/telegram-plugin/dist/server.js +0 -12
- package/telegram-plugin/fallback-card-collapse.ts +1 -0
- package/telegram-plugin/gateway/auth-command.ts +11 -1
- package/telegram-plugin/gateway/callback-query-handlers.ts +100 -0
- package/telegram-plugin/gateway/eval-case-proposal-card.ts +86 -0
- package/telegram-plugin/gateway/fleet-fallback-notice-cooldown.test.ts +74 -0
- package/telegram-plugin/gateway/fleet-fallback-notice-cooldown.ts +71 -0
- package/telegram-plugin/gateway/gateway.ts +109 -149
- package/telegram-plugin/gateway/ipc-protocol.ts +43 -0
- package/telegram-plugin/gateway/ipc-server.ts +28 -0
- package/telegram-plugin/gateway/liveness-wiring.ts +6 -1
- package/telegram-plugin/gateway/narrative-lane.ts +33 -2
- package/telegram-plugin/gateway/privacy-reset.test.ts +216 -0
- package/telegram-plugin/gateway/privacy-reset.ts +87 -0
- package/telegram-plugin/gateway/privacy-state.test.ts +165 -0
- package/telegram-plugin/gateway/privacy-state.ts +206 -0
- package/telegram-plugin/gateway/self-improve-proposal-wiring.ts +176 -0
- package/telegram-plugin/gateway/stale-pin-sweep-wiring.ts +24 -14
- package/telegram-plugin/gateway/stale-pin-sweep.test.ts +123 -26
- package/telegram-plugin/gateway/stale-pin-sweep.ts +52 -35
- package/telegram-plugin/gateway/status-pin-store.ts +10 -9
- package/telegram-plugin/gateway/stream-render.ts +4 -4
- package/telegram-plugin/gateway/throttle-tier-wiring.ts +15 -4
- package/telegram-plugin/gateway/turn-record-status.ts +32 -1
- package/telegram-plugin/hooks/hooks.json +13 -12
- package/telegram-plugin/hooks/narration-classify.mjs +1 -2
- package/telegram-plugin/hooks/silent-end-scan.mjs +1 -1
- package/telegram-plugin/slot-banner-driver.ts +42 -5
- package/telegram-plugin/status-pin.ts +2 -5
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +24 -0
- package/telegram-plugin/tests/backstop-exactly-once.test.ts +8 -2
- package/telegram-plugin/tests/framework-fallback-duration-guard.test.ts +125 -0
- package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +2 -0
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +97 -0
- package/telegram-plugin/tests/pin-message-tool-retired.test.ts +64 -0
- package/telegram-plugin/tests/privacy-reset-call-sites.test.ts +120 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +38 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +25 -0
- package/telegram-plugin/tests/throttle-tier.test.ts +16 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +67 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +40 -1
- package/telegram-plugin/throttle-tier.ts +12 -3
- package/telegram-plugin/turn-flush-safety.ts +97 -0
- package/telegram-plugin/worker-activity-feed.ts +1 -1
- package/vendor/hindsight-memory/scripts/recall.py +140 -0
- package/vendor/hindsight-memory/scripts/retain.py +306 -0
- package/vendor/hindsight-memory/scripts/subagent_retain.py +29 -1
- package/vendor/hindsight-memory/scripts/tests/test_private_mode.py +415 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_latency_instrumentation.py +277 -0
- package/vendor/hindsight-memory/scripts/tests/test_self_improve_correction_tag.py +167 -0
|
@@ -4030,7 +4030,7 @@ var init_observation_scopes = __esm(() => {
|
|
|
4030
4030
|
});
|
|
4031
4031
|
|
|
4032
4032
|
// src/config/schema.ts
|
|
4033
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, BuzzChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleServiceTokenSchema, 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, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, BuzzChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleServiceTokenSchema, 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, ConfigRepoConfigSchema, SwitchroomConfigSchema;
|
|
4034
4034
|
var init_schema = __esm(() => {
|
|
4035
4035
|
init_zod();
|
|
4036
4036
|
init_observation_scopes();
|
|
@@ -4480,7 +4480,7 @@ var init_schema = __esm(() => {
|
|
|
4480
4480
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
|
|
4481
4481
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
|
|
4482
4482
|
}).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."),
|
|
4483
|
-
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT — the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S — the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing; and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS — the max chars of query text " + "dateparser.search_dates() is handed during temporal analysis (0 = " + "unlimited), made live by switchroom's temporal-offload image patch " + "(which moved the synchronous extraction off the shared asyncio loop onto " + "a single-worker thread and bounds its input, ending multi-second loop " + "stalls on multi-KB consolidation queries); unset means the image's baked " + "2000, set 0 to restore an unbounded full scan), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
4483
|
+
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT — the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S — the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_TEMPORAL_LANGUAGES — the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing; and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS — the max chars of query text " + "dateparser.search_dates() is handed during temporal analysis (0 = " + "unlimited), made live by switchroom's temporal-offload image patch " + "(which moved the synchronous extraction off the shared asyncio loop onto " + "a single-worker thread and bounds its input, ending multi-second loop " + "stalls on multi-KB consolidation queries); unset means the image's baked " + "2000, set 0 to restore an unbounded full scan); and " + "HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE — the Postgres " + "text-search regconfig the native BM25 arms and the search_vector index " + "write run to_tsvector/to_tsquery against; unset means upstream's " + "always-safe `english`, set `hindsight_english` to use switchroom's " + "junk-stopping config (dict + stopword file provisioned durably by the " + "image entrypoint, recall degrading to semantic-only if it is ever " + "missing); plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
4484
4484
|
});
|
|
4485
4485
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
4486
4486
|
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)."),
|
|
@@ -4727,8 +4727,10 @@ var init_schema = __esm(() => {
|
|
|
4727
4727
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
4728
4728
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("Per-agent IANA timezone override. Wins over any profile/defaults " + "value and over the top-level switchroom.timezone global. Controls " + "the UserPromptSubmit timezone hook's emitted local time and the " + "agent container's `environment.TZ` in compose."),
|
|
4729
4729
|
auth: exports_external.object({
|
|
4730
|
-
override: exports_external.string().min(1).optional().describe("Per-agent override of the fleet-wide `auth.active`. Edge-case use only — " + "this agent talks to the named account regardless of fleet active. See RFC H §4.5.")
|
|
4731
|
-
|
|
4730
|
+
override: exports_external.string().min(1).optional().describe("Per-agent override of the fleet-wide `auth.active`. Edge-case use only — " + "this agent talks to the named account regardless of fleet active. See RFC H §4.5."),
|
|
4731
|
+
strict: exports_external.boolean().optional().describe("Requires `override`. When true, the pin is a hard binding, not a " + "routing preference: the broker NEVER serves this agent from any " + "other account — no failover to `auth.fallback_order` while the " + "pinned account is quota-walled or exhausted. The agent rides out " + "the wall on its own account (surfacing the normal 429/quota " + "cards) instead of silently borrowing fleet quota. Use for " + "accounts that must never cross a billing/compliance boundary " + "(e.g. an employer-provided subscription)."),
|
|
4732
|
+
exclusive: exports_external.boolean().optional().describe("Requires `override`. When true, the pinned account belongs to " + "THIS agent alone: the broker refuses to serve it to any other " + "agent or consumer, refuses `auth use <label>` (set-active) onto " + "it, and refuses pinning another agent to it. Config that routes " + "others to the account (`auth.active`, `auth.fallback_order`, " + "another agent's `override`, a consumer pin) is rejected at load. " + "Usually paired with `strict: true` for full two-way isolation.")
|
|
4733
|
+
}).optional().describe("Account routing for switchroom-auth-broker. RFC H schema uses " + "fleet-wide `auth.active` plus per-agent `override:` for edge cases, " + "with optional `strict:` (never borrow another account) and " + "`exclusive:` (no one else may use the pinned account) hardening. " + "Pre-RFC-H `auth.accounts: [..]` and `auth_label:` are migrated in-place " + "on first apply (see src/auth/migrate-schema.ts)."),
|
|
4732
4734
|
dm_only: exports_external.boolean().optional().describe("Mark this agent as a DM-only bot — has its own bot_token and lives " + "exclusively in a private chat with the operator. Suppresses " + "scaffolding's default behavior of inheriting the global " + "telegram.forum_chat_id into the agent's access.json `groups` entry " + "(the forum chat the bot isn't a member of, which would otherwise " + "trigger a 'boot-probe-failed: 400 chat not found' warning every " + "restart). topic_name is still schema-required but unused — set it " + "to a display label like 'DM' for /switchroom status output."),
|
|
4733
4735
|
topic_name: exports_external.string().describe("Telegram forum topic display name"),
|
|
4734
4736
|
topic_emoji: exports_external.string().optional().describe("Emoji for the topic (e.g., '\uD83C\uDFCB️')"),
|
|
@@ -4803,6 +4805,17 @@ var init_schema = __esm(() => {
|
|
|
4803
4805
|
tmp_size: exports_external.string().regex(/^\d+(\.\d+)?[kmgKMG]?$/, "tmp_size must be a Docker size string like '1g', '4g', '512m'").refine((v) => parseFloat(v) > 0, "tmp_size must be greater than zero").optional()
|
|
4804
4806
|
}).optional()
|
|
4805
4807
|
}).superRefine((agent, ctx) => {
|
|
4808
|
+
if (agent.auth && !agent.auth.override) {
|
|
4809
|
+
for (const flag of ["strict", "exclusive"]) {
|
|
4810
|
+
if (agent.auth[flag]) {
|
|
4811
|
+
ctx.addIssue({
|
|
4812
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4813
|
+
message: `\`auth.${flag}: true\` requires \`auth.override: <account>\` — there is no pinned account for it to apply to.`,
|
|
4814
|
+
path: ["auth", flag]
|
|
4815
|
+
});
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4806
4819
|
if (agent.dm_only !== true)
|
|
4807
4820
|
return;
|
|
4808
4821
|
const tg = agent.channels?.telegram;
|
|
@@ -4915,6 +4928,15 @@ var init_schema = __esm(() => {
|
|
|
4915
4928
|
profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`)."),
|
|
4916
4929
|
person_id: exports_external.string().optional().describe("Free-text display name projected into the inbound `<channel>` " + 'tag\'s `user` attribute (e.g. "Lisa") so an agent can greet by ' + "name instead of only seeing the raw Telegram id/username. NOT a " + "stable identity system — just a label. Resolution is boot-time-only " + "(no hot-reload; a config change needs an agent restart) and " + "chat-scoped (a resolved name is only shown in a chat/group the " + "person is actually a member of, per that chat's access.json " + "membership — never broadcast into every chat the agent operates " + "in). Keep this broadly safe to show, the same discipline as " + "picking a `profile_bank` name: there is no automated enforcement " + "that a `person_id` stays safe if a group's membership changes " + "later — see docs/configuration.md.")
|
|
4917
4930
|
});
|
|
4931
|
+
ConfigRepoConfigSchema = exports_external.object({
|
|
4932
|
+
enabled: exports_external.boolean().default(false).describe("Master switch for config-repo change control. Default false (opt-in). " + "When false the `config-repo sync` verb still runs on demand but the " + "doctor check and (later) the scheduled tick treat the feature as off."),
|
|
4933
|
+
path: exports_external.string().regex(/^[a-zA-Z0-9~._\-/]+$/, "config_repo.path must not contain shell-special characters ($, `, \", ', \\, etc.)").default("~/.switchroom-config").describe("Filesystem path to the operator's private config repo. Must be a git " + "repo (else `switchroom doctor` FAILs). Tilde-expanded at read time."),
|
|
4934
|
+
push: exports_external.boolean().default(true).describe("When true, `config-repo sync` pushes after committing (subject to the " + "`require_private` gate). When false, it commits locally only — useful " + "for offline hosts or a review-before-push workflow."),
|
|
4935
|
+
remote: exports_external.string().min(1).default("origin").describe("Git remote name to push to. Default `origin`."),
|
|
4936
|
+
require_private: exports_external.boolean().default(true).describe("Refuse to PUSH unless the GitHub API confirms the remote repo is " + "private. On a public remote — or when the API is unreachable — the " + "push is skipped (commits still land locally) and a WARN is emitted. " + "Fail-safe against exfiltrating memory files / workspace state to a " + "repo that has been flipped public. Default true."),
|
|
4937
|
+
interval_minutes: exports_external.number().int().min(5).max(59).default(30).describe("Cadence (minutes) of the scheduled `config-repo sync` tick installed " + "by `switchroom config-repo --install-cron`. Rendered as a cron " + "`*/N * * * *` minute field, so it must be 5..59 (the 5-min floor " + "matches the rest of the fleet's cron floor). Default 30 (Ken's " + "approved cadence). Changing it takes effect on the next " + "`--install-cron` / `switchroom update` reconcile."),
|
|
4938
|
+
include_vault_backup: exports_external.enum(["off", "daily", "every_tick"]).default("daily").describe("Whether the scheduled tick also runs `switchroom vault backup` " + "(an encrypted vault/memory snapshot into <path>/vault-backups/) " + "before the sync. `off`: sync only. `daily`: an extra once-per-day " + "cron leg runs `vault backup && config-repo sync` (the 30-min legs " + "stay sync-only). `every_tick`: every tick runs the backup first. " + "Default `daily` — the first automated vault backup on this host.")
|
|
4939
|
+
});
|
|
4918
4940
|
SwitchroomConfigSchema = exports_external.object({
|
|
4919
4941
|
switchroom: exports_external.object({
|
|
4920
4942
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -4927,6 +4949,7 @@ var init_schema = __esm(() => {
|
|
|
4927
4949
|
memory: MemoryBackendConfigSchema.optional(),
|
|
4928
4950
|
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`."),
|
|
4929
4951
|
vault: VaultConfigSchema.optional(),
|
|
4952
|
+
config_repo: ConfigRepoConfigSchema.optional().describe("Change control for the operator's private ~/.switchroom-config git repo " + "(backup + audit trail of live host config, agent workspace state, and " + "mirrored personal skills). Consumed by `switchroom config-repo sync` and " + "the `config repo` doctor check. Optional and OFF by default."),
|
|
4930
4953
|
auth: exports_external.object({
|
|
4931
4954
|
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>`."),
|
|
4932
4955
|
fallback_order: exports_external.array(exports_external.string().min(1)).optional().describe("Ordered list of account labels for `switchroom auth rotate` to cycle " + "through when the active account hits a quota event. First entry is " + "normally the same as `auth.active`. When unset, `rotate` is a no-op."),
|
|
@@ -4994,6 +5017,48 @@ var init_schema = __esm(() => {
|
|
|
4994
5017
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4995
5018
|
checkServes(a.serves, ["agents", name, "serves"]);
|
|
4996
5019
|
}
|
|
5020
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
5021
|
+
const agentAuth = a.auth;
|
|
5022
|
+
if (!agentAuth?.exclusive || !agentAuth.override)
|
|
5023
|
+
continue;
|
|
5024
|
+
const acct = agentAuth.override;
|
|
5025
|
+
if (cfg.auth?.active === acct) {
|
|
5026
|
+
ctx.addIssue({
|
|
5027
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5028
|
+
message: `account '${acct}' is exclusive to agent '${name}' ` + `(agents.${name}.auth.exclusive) — it cannot be the fleet \`auth.active\``,
|
|
5029
|
+
path: ["auth", "active"]
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
const fbIdx = (cfg.auth?.fallback_order ?? []).indexOf(acct);
|
|
5033
|
+
if (fbIdx !== -1) {
|
|
5034
|
+
ctx.addIssue({
|
|
5035
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5036
|
+
message: `account '${acct}' is exclusive to agent '${name}' ` + `(agents.${name}.auth.exclusive) — it cannot appear in \`auth.fallback_order\``,
|
|
5037
|
+
path: ["auth", "fallback_order", fbIdx]
|
|
5038
|
+
});
|
|
5039
|
+
}
|
|
5040
|
+
for (const [other, oa] of Object.entries(cfg.agents ?? {})) {
|
|
5041
|
+
if (other === name)
|
|
5042
|
+
continue;
|
|
5043
|
+
const otherOverride = oa.auth?.override;
|
|
5044
|
+
if (otherOverride === acct) {
|
|
5045
|
+
ctx.addIssue({
|
|
5046
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5047
|
+
message: `account '${acct}' is exclusive to agent '${name}' ` + `(agents.${name}.auth.exclusive) — agent '${other}' cannot pin it`,
|
|
5048
|
+
path: ["agents", other, "auth", "override"]
|
|
5049
|
+
});
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
(cfg.auth?.consumers ?? []).forEach((c, i) => {
|
|
5053
|
+
if (c.account === acct) {
|
|
5054
|
+
ctx.addIssue({
|
|
5055
|
+
code: exports_external.ZodIssueCode.custom,
|
|
5056
|
+
message: `account '${acct}' is exclusive to agent '${name}' ` + `(agents.${name}.auth.exclusive) — consumer '${c.name}' cannot pin it`,
|
|
5057
|
+
path: ["auth", "consumers", i, "account"]
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
});
|
|
5061
|
+
}
|
|
4997
5062
|
const microsoftAccounts = cfg.microsoft_accounts;
|
|
4998
5063
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4999
5064
|
const mw = a.microsoft_workspace;
|
|
@@ -19631,6 +19696,27 @@ function allocateAgentUid(name) {
|
|
|
19631
19696
|
return AGENT_UID_MIN + u32 % range;
|
|
19632
19697
|
}
|
|
19633
19698
|
|
|
19699
|
+
// src/setup/hindsight-recall-passthrough.ts
|
|
19700
|
+
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
19701
|
+
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
19702
|
+
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
19703
|
+
budget: "low",
|
|
19704
|
+
maxTokens: 1024,
|
|
19705
|
+
preferObservations: true,
|
|
19706
|
+
contextTurns: 2,
|
|
19707
|
+
roles: ["user", "assistant"],
|
|
19708
|
+
promptPreamble: HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT,
|
|
19709
|
+
tags: [],
|
|
19710
|
+
tagsMatch: "any",
|
|
19711
|
+
tagGroups: {},
|
|
19712
|
+
tagWeights: HINDSIGHT_RECALL_TAG_WEIGHT_SEED,
|
|
19713
|
+
additionalBankFilters: {},
|
|
19714
|
+
transcriptFallback: true,
|
|
19715
|
+
transcriptTailBytes: 262144,
|
|
19716
|
+
maxQueryChars: 800,
|
|
19717
|
+
parallel: true
|
|
19718
|
+
});
|
|
19719
|
+
|
|
19634
19720
|
// src/setup/hindsight-recall-tunables.ts
|
|
19635
19721
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
19636
19722
|
var MIN_RECALL_HOOK_TIMEOUT_SECONDS = RECALL_DEADLINE_HEADROOM_SECONDS + 1;
|
|
@@ -20078,7 +20164,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
|
20078
20164
|
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
20079
20165
|
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S",
|
|
20080
20166
|
"HINDSIGHT_API_TEMPORAL_LANGUAGES",
|
|
20081
|
-
"HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS"
|
|
20167
|
+
"HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS",
|
|
20168
|
+
"HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE"
|
|
20082
20169
|
]);
|
|
20083
20170
|
var HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
20084
20171
|
"consolidation",
|
|
@@ -20500,27 +20587,6 @@ init_vault();
|
|
|
20500
20587
|
// src/setup/onboarding.ts
|
|
20501
20588
|
init_paths();
|
|
20502
20589
|
|
|
20503
|
-
// src/setup/hindsight-recall-passthrough.ts
|
|
20504
|
-
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
20505
|
-
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
20506
|
-
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
20507
|
-
budget: "low",
|
|
20508
|
-
maxTokens: 1024,
|
|
20509
|
-
preferObservations: true,
|
|
20510
|
-
contextTurns: 2,
|
|
20511
|
-
roles: ["user", "assistant"],
|
|
20512
|
-
promptPreamble: HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT,
|
|
20513
|
-
tags: [],
|
|
20514
|
-
tagsMatch: "any",
|
|
20515
|
-
tagGroups: {},
|
|
20516
|
-
tagWeights: HINDSIGHT_RECALL_TAG_WEIGHT_SEED,
|
|
20517
|
-
additionalBankFilters: {},
|
|
20518
|
-
transcriptFallback: true,
|
|
20519
|
-
transcriptTailBytes: 262144,
|
|
20520
|
-
maxQueryChars: 800,
|
|
20521
|
-
parallel: true
|
|
20522
|
-
});
|
|
20523
|
-
|
|
20524
20590
|
// src/repos/bare-clone.ts
|
|
20525
20591
|
init_paths();
|
|
20526
20592
|
|
|
@@ -38,7 +38,7 @@ services:
|
|
|
38
38
|
# Pinned: bumping is a deliberate operator decision, not a silent
|
|
39
39
|
# surprise. Bump in lockstep with retesting the OAuth flow.
|
|
40
40
|
- --from
|
|
41
|
-
- workspace-mcp==1.
|
|
41
|
+
- workspace-mcp==1.23.1
|
|
42
42
|
- workspace-mcp
|
|
43
43
|
# streamable-http (not stdio) is required for OAuth 2.1 PKCE per
|
|
44
44
|
# upstream README.
|
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.20.
|
|
4
|
+
"version": "0.20.12",
|
|
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": {
|
|
@@ -48,16 +48,12 @@ hot-reloads, no restart needed.
|
|
|
48
48
|
crons. `context: "agent"` pins a fire here when it genuinely needs your
|
|
49
49
|
accumulated context (this always wins).
|
|
50
50
|
- **Model-free tiers — you must REQUEST these from the operator** (they need a
|
|
51
|
-
config commit
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- **`kind: poll`** for "only act when X changes" — a model-free check where
|
|
58
|
-
only a *change* wakes you. Don't burn a frequent prompt cron on polling.
|
|
59
|
-
- **`reaction_dispatch`** for reaction-triggered work — an emoji reaction
|
|
60
|
-
wakes you instantly, zero polling.
|
|
51
|
+
config commit; you can only self-author plain prompt crons plus the
|
|
52
|
+
`model`/`context` hints): **`kind: action`** posts a FIXED text at zero
|
|
53
|
+
tokens (renders full GFM — set `parse_mode: text` for verbatim);
|
|
54
|
+
**`kind: poll`** is a model-free check where only a *change* wakes you —
|
|
55
|
+
don't burn a frequent prompt cron on polling; **`reaction_dispatch`** wakes
|
|
56
|
+
you instantly on an emoji reaction, zero polling.
|
|
61
57
|
|
|
62
58
|
Cron-fired sessions render rich markdown, so scheduled/digest output should use
|
|
63
59
|
**bold** headers and `code` for identifiers, just like a live reply.
|
|
@@ -87,11 +83,10 @@ fork is durable and auto-mirrors to `~/.switchroom-config/` when present.
|
|
|
87
83
|
|
|
88
84
|
### Confirm honestly
|
|
89
85
|
|
|
90
|
-
After a successful `schedule_add`, tell the user the human-readable schedule
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
tripped, and offer the closest legal alternative.
|
|
86
|
+
After a successful `schedule_add`, tell the user the human-readable schedule,
|
|
87
|
+
the cron expression you actually wrote, and the `name` slug (so they can
|
|
88
|
+
remove it later). After a failed write, surface the error verbatim, name
|
|
89
|
+
which rail tripped, and offer the closest legal alternative.
|
|
95
90
|
{{#if linearAgentEnabled}}
|
|
96
91
|
|
|
97
92
|
## Capture to Linear (👨💻 / 📌 reaction → new issue)
|
|
@@ -157,10 +152,8 @@ config or paste them into a normal message; pass them straight to the tool.
|
|
|
157
152
|
|
|
158
153
|
### Don't lie about scheduling
|
|
159
154
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
works. Don't claim "I've set a one-time reminder" and then leave a
|
|
166
|
-
recurring entry running silently.
|
|
155
|
+
Cron can't encode a one-shot — every entry recurs. For "at 5pm tomorrow,
|
|
156
|
+
remind me": either (a) schedule it recurring and offer to remove it after
|
|
157
|
+
the first fire, or (b) say one-shot isn't supported and ask what cadence
|
|
158
|
+
works. Never claim "one-time reminder" while leaving a recurring entry
|
|
159
|
+
running silently.
|
|
@@ -4,6 +4,6 @@ Execution belongs in sub-agents. **When in doubt, delegate.** A task is executio
|
|
|
4
4
|
|
|
5
5
|
Acting in-turn and delegating are the same move: for an execution-class task the in-turn act IS dispatching the worker, so dispatch and acknowledge — that IS the fast response, not a slower one. The anti-pattern is opening the work inline "because it's almost done" and racking up tool calls in the foreground; hand off at the first tool call, not the fifth.
|
|
6
6
|
|
|
7
|
-
If the user amends in-flight delegated work mid-turn, steer the running worker now (`SendMessage` by worker name
|
|
7
|
+
If the user amends in-flight delegated work mid-turn, steer the running worker now (`SendMessage` by worker name or agent id) rather than holding it for handback — and say in your reply which you did ("folded your update into the running worker" / "queued as a separate task"); never classify silently, never inferred. Unsure? Queue it and say so — queue is the default. If the worker is effectively done and the steer lands too late, say so and apply the update yourself in the parent.
|
|
8
8
|
|
|
9
9
|
If no sub-agents are configured, do the work yourself.
|
|
@@ -9,6 +9,6 @@ This governs HOW delegated work is done, and is **not a license to do it inline*
|
|
|
9
9
|
- **Design report before implementation, when the change is large.** Architecturally significant, cross-cutting, or ambiguous-approach work earns an evidence-grounded design report and a red-team pass before code, and ships as focused single-concern PRs.
|
|
10
10
|
- **Run `git fetch origin` first, then branch off `origin/main`.** Never branch off the working copy as-is: you do not start in the repo you are coding in, and the checkout on disk may be many commits stale. Scoped tests + lint locally; CI is the full-suite authority.
|
|
11
11
|
- **Adversarial review of the diff.** Read every change as an adversary would: what breaks, what's untested, what's inconsistent.
|
|
12
|
-
- **Blockers and majors block the merge; lows
|
|
12
|
+
- **Blockers and majors block the merge; prefer fixing lows before merge too** — ship reliable code with a slight delay over filing tech debt that never gets addressed. Fix a low now unless it's genuinely out of scope or disproportionate to the change; only then file it as a tracked follow-up (filing is mandatory, and a filed low is a commitment to fix it, not a way to forget it). Fix what blocks, then merge on CI green. Do not count review rounds and do not run a mandatory re-review pass: verifying your own fix is part of making it, not a separate step. When that fix-vs-defer call is a genuine judgment call, don't decide it silently — surface it to the user as choice / user-impact / recommendation: name the options (fix now vs defer), their user-outcome impact, and which you recommend.
|
|
13
13
|
- **Durable fixes over hack patches. Deterministic mechanisms over model-dependent behaviour** — if code can enforce a guarantee, don't leave it to prompt discipline. **Tests assert outcomes, not just code paths:** one that wouldn't fail on the bug it guards is not a test.
|
|
14
14
|
- **Communicate as you go.** Consolidated messages — one substantive update beats five fragments; never go dark; no foreground watch over 30 seconds (background it); max 15 parallel sub-agents.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
## Grounding — check before you assert
|
|
2
2
|
|
|
3
|
-
Your memory and prior context are leads, not facts. Anything you state as true —
|
|
3
|
+
Your memory and prior context are leads, not facts. Anything you state as true — code, config, a system's state, a number, a status, or what you can and can't do — must come from something you actually checked this turn.
|
|
4
4
|
|
|
5
|
-
- **Read the ground truth live.**
|
|
6
|
-
- **
|
|
7
|
-
- **Don't assert a limit you haven't tested.** Before saying "no access" / "operator-only" / "not editable"
|
|
5
|
+
- **Read the ground truth live.** Files, git status, config, versions, running services, schedules, permissions — read them with a tool, don't recall them.
|
|
6
|
+
- **Training memory is a lead, not a source — prefer finding over guessing.** A library's API, a version, a CLI flag, whether a repo exists — but equally a real-world fact: who a person is, what a company does, a product, a current event: validate against the real thing (the web, docs, the repo) before you assert it. Never answer "I don't recognize X" from model recall when X is one search away.
|
|
7
|
+
- **Don't assert a limit you haven't tested.** Before saying "no access" / "operator-only" / "not editable", verify it live. A fabricated boundary is as wrong as a fabricated fact.
|
|
8
8
|
- **A weak or empty result isn't a conclusion.** Vary the query, path, command, or source before deciding something doesn't exist.
|
|
9
9
|
- **Final answers carry evidence** — tool output, a file you read, a check you ran, or a named blocker; not "it should work."
|
|
10
10
|
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
Switchroom-managed: vault protocol.
|
|
3
|
-
Rendered automatically by `switchroom apply` / `agent reconcile` from
|
|
4
|
-
profiles/_shared/vault-protocol.md.hbs. Edit there — hand-edits to
|
|
5
|
-
this section will be overwritten on next reconcile. To add agent-
|
|
6
|
-
specific guidance, edit CLAUDE.md below the "Yours (preserved across
|
|
7
|
-
apply)" marker line — that section is preserved verbatim across every
|
|
8
|
-
apply.
|
|
9
|
-
-->
|
|
1
|
+
<!-- Switchroom-managed: rendered from profiles/_shared/vault-protocol.md.hbs — edit there; hand-edits here are overwritten on reconcile. -->
|
|
10
2
|
|
|
11
3
|
## Vault & secrets
|
|
12
4
|
|
|
@@ -65,15 +57,7 @@ there's an operator in the loop:
|
|
|
65
57
|
|
|
66
58
|
### When a sub-agent or tool reports a service/API/credential as "inaccessible"
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Don't escalate to the operator to paste credentials or open a dashboard. Instead:
|
|
71
|
-
|
|
72
|
-
1. Infer the likely key name (`<service>/<key>` convention — e.g. `coolify/api-token`, `github/token`, `<repo>/DATABASE_URL`).
|
|
73
|
-
2. Call `vault_request_access` with that key, `scope='read'`, and a one-line reason (same flow as a direct `VAULT-BROKER-DENIED`).
|
|
74
|
-
3. End your turn cleanly and resume when the grant is approved.
|
|
75
|
-
|
|
76
|
-
Only escalate to the operator if the grant is explicitly denied, or if you cannot infer a plausible key name.
|
|
60
|
+
That's the broker wall surfacing as prose — treat it as `VAULT-BROKER-DENIED`. Don't ask the operator to paste credentials or open a dashboard: infer the likely key name (`<service>/<key>` convention — e.g. `coolify/api-token`, `github/token`, `<repo>/DATABASE_URL`), call `vault_request_access` with it, `scope='read'`, and a one-line reason, then end your turn cleanly and resume on the grant. Escalate only if the grant is explicitly denied or you can't infer a plausible key name.
|
|
77
61
|
|
|
78
62
|
### Hint: the deny stderr tells you the exact recovery
|
|
79
63
|
|
|
@@ -28,7 +28,7 @@ You are operating in the **{{topicName}}** {{#if topicEmoji}}{{topicEmoji}} {{/i
|
|
|
28
28
|
- Prefer `trash` over `rm` when available (recoverable beats gone forever).
|
|
29
29
|
- Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
|
|
30
30
|
- Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
|
|
31
|
-
- **Batch foreseeable approvals; don't drip surprises.** When
|
|
31
|
+
- **Batch foreseeable approvals; don't drip surprises.** When several actions will each need the user's approval, say up front which are coming and why: request independent ones together so they decide once; for dependent ones, say what's next — a permission card should never arrive out of the blue.
|
|
32
32
|
- **A timed-out approval isn't a denial.** If a request came back denied only because the user was away (a timeout, not an explicit "no"), don't silently abandon it. When they're back, remind them it's still pending and re-offer it if they still want it.
|
|
33
33
|
|
|
34
34
|
## Memory — Hindsight is your single backend
|
|
@@ -52,7 +52,7 @@ Hard rules the agent must follow during reflect — guardrails that are always a
|
|
|
52
52
|
|
|
53
53
|
- `mcp__hindsight__create_directive(text)` — e.g., `create_directive("Always prefer TypeScript over JavaScript for this user's projects")`. When the user gives you a correction or "always do X" rule, create a directive instead of writing a feedback `.md` file. If the rule can be enforced deterministically — a settings.json hook, a permission rule, a skill/script edit, or a config change — prefer that (instead of, or in addition to, the directive) and say which you did; reserve directives for judgment rules code can't enforce.
|
|
54
54
|
|
|
55
|
-
(Read-only inspection tools
|
|
55
|
+
(Read-only inspection tools — `list_memories`, `list_mental_models`, `get_mental_model`, `list_directives` — exist under `mcp__hindsight__*` if you ever need them, but you rarely should: auto-recall surfaces what matters and the operator curates the bank out-of-band.)
|
|
56
56
|
|
|
57
57
|
### What to retain — and what NOT to retain
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ Don't retain:
|
|
|
68
68
|
- Sensitive content the user explicitly asked you to not remember
|
|
69
69
|
- Things already in a mental model — they'll be re-derived from underlying memories
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Auto-retain (see above) covers routine capture; use manual `retain` for high-signal observations you want immediately searchable.
|
|
72
72
|
|
|
73
73
|
### When to synthesize — concrete triggers
|
|
74
74
|
|
|
@@ -93,8 +93,6 @@ By default, every restart starts a **fresh `claude` session** — the in-flight
|
|
|
93
93
|
|
|
94
94
|
A config-summary greeting card is sent automatically by the SessionStart hook — you don't need to announce yourself. If your context feels thin (after compaction or any fresh session), proactively recall from Hindsight before proceeding.
|
|
95
95
|
|
|
96
|
-
(Operators can override the resume policy per-agent via `session_continuity.resume_mode` in switchroom.yaml — `auto`, `continue`, `handoff`, or `none`. The default is `handoff`.)
|
|
97
|
-
|
|
98
96
|
{{#if admin}}
|
|
99
97
|
## Admin surface
|
|
100
98
|
|
|
@@ -11,7 +11,6 @@ Switchroom ships an enhanced `switchroom-telegram` MCP plugin that replaces the
|
|
|
11
11
|
| `edit_message` | Update a previously sent message. Edits are silent (no push notification). |
|
|
12
12
|
| `delete_message` | Remove a bot-sent message (48h Telegram API limit). |
|
|
13
13
|
| `forward_message` | Quote/resurface earlier messages with thread support. |
|
|
14
|
-
| `pin_message` | Pin important outputs (requires bot admin). |
|
|
15
14
|
| `send_typing` | Show typing indicator (5s auto-expire). Use during long operations. |
|
|
16
15
|
| `download_attachment` | Fetch files attached to inbound messages. |
|
|
17
16
|
| `get_recent_messages` | Query SQLite history buffer with pagination and thread filtering. |
|
|
@@ -299,7 +299,6 @@ The `switchroom-telegram` plugin is an enhanced fork of the official Telegram MC
|
|
|
299
299
|
| `edit_message` | Modify an earlier bot message's text |
|
|
300
300
|
| `delete_message` | Remove an earlier bot message |
|
|
301
301
|
| `forward_message` | Forward a message from another chat |
|
|
302
|
-
| `pin_message` | Pin a message in the current chat |
|
|
303
302
|
| `send_typing` | Show the "typing…" indicator |
|
|
304
303
|
| `download_attachment` | Save a Telegram file attachment to the agent's inbox |
|
|
305
304
|
| `get_recent_messages` | Fetch recent history for context |
|
|
@@ -38,8 +38,8 @@ adds the ergonomics and reliability that an always-on agent fleet needs:
|
|
|
38
38
|
- **Switchroom slash-commands** — `/agents`, `/restart`, `/logs`, `/memory`,
|
|
39
39
|
`/grant`, `/dangerous`, `/permissions`, `/reconcile` etc., handled by the
|
|
40
40
|
plugin without consuming Claude Code tokens.
|
|
41
|
-
- **
|
|
42
|
-
`delete_message`, `forward_message`, `
|
|
41
|
+
- **10 MCP tools** — `reply`, `react`, `edit_message`,
|
|
42
|
+
`delete_message`, `forward_message`, `send_typing`,
|
|
43
43
|
`download_attachment`, `get_recent_messages`, `send_checklist`,
|
|
44
44
|
`update_checklist` (the latter two ship native Telegram checklists,
|
|
45
45
|
see #272).
|
|
@@ -228,15 +228,6 @@ Sends a typing indicator ("Bot is typing...") to a chat. Auto-expires after ~5 s
|
|
|
228
228
|
|-----------|----------|-------------|
|
|
229
229
|
| `chat_id` | yes | Target chat ID |
|
|
230
230
|
|
|
231
|
-
### `pin_message` tool
|
|
232
|
-
|
|
233
|
-
Pins a message in a chat. Requires admin rights in groups.
|
|
234
|
-
|
|
235
|
-
| Parameter | Required | Description |
|
|
236
|
-
|-----------|----------|-------------|
|
|
237
|
-
| `chat_id` | yes | Chat ID |
|
|
238
|
-
| `message_id` | yes | Message to pin |
|
|
239
|
-
|
|
240
231
|
### `forward_message` tool
|
|
241
232
|
|
|
242
233
|
Forwards an existing message to a chat, preserving original sender attribution. In forum topics, the forwarded message lands in the correct thread (auto-detected or explicit).
|
|
@@ -159,6 +159,18 @@ export type FleetFallbackOutcome =
|
|
|
159
159
|
oldLabel: string;
|
|
160
160
|
announcement: string;
|
|
161
161
|
oldQuota: QuotaUtilization | null;
|
|
162
|
+
}
|
|
163
|
+
| {
|
|
164
|
+
/** The triggering agent has a strict pin (`auth.strict`) — the broker
|
|
165
|
+
* marked its account but deliberately did not roll it. Distinct from
|
|
166
|
+
* 'no-eligible-target' so the gateway can apply its own notice
|
|
167
|
+
* cooldown: like all-blocked, this is a no-op outcome the ~60s
|
|
168
|
+
* quota_wall_detected re-trigger would otherwise re-broadcast for
|
|
169
|
+
* the life of the wall. */
|
|
170
|
+
kind: 'strict-pinned';
|
|
171
|
+
oldLabel: string;
|
|
172
|
+
announcement: string;
|
|
173
|
+
oldQuota: QuotaUtilization | null;
|
|
162
174
|
};
|
|
163
175
|
|
|
164
176
|
export interface FleetFallbackDeps {
|
|
@@ -177,7 +189,14 @@ export interface FleetFallbackDeps {
|
|
|
177
189
|
* /auth button uses) is gated to admin agents, so a non-admin agent that
|
|
178
190
|
* 429'd could never self-heal. mark-exhausted derives the account from the
|
|
179
191
|
* caller's own identity, so it needs no admin. */
|
|
180
|
-
failover: () => Promise<{
|
|
192
|
+
failover: () => Promise<{
|
|
193
|
+
rolledTo: string | null;
|
|
194
|
+
rolled: string[];
|
|
195
|
+
/** True when the triggering agent has a strict pin (`auth.strict`): its
|
|
196
|
+
* null `rolledTo` means it rides out the wall on its own account — NOT
|
|
197
|
+
* a fleet-wide all-blocked. Absent from pre-flag brokers → false. */
|
|
198
|
+
callerPinnedStrict?: boolean;
|
|
199
|
+
}>;
|
|
181
200
|
/** Agent that triggered this fallback (for the announcement byline). */
|
|
182
201
|
triggerAgent: string;
|
|
183
202
|
/** Operator timezone for absolute reset times in the announcement. */
|
|
@@ -259,7 +278,23 @@ export async function runFleetAutoFallback(
|
|
|
259
278
|
// choice (same `nextHealthyAccount` selection /auth rotate uses) rather than
|
|
260
279
|
// picking here, so the announcement matches what actually happened. Caller
|
|
261
280
|
// catches and surfaces failures — we don't double-wrap.
|
|
262
|
-
const { rolledTo } = await deps.failover();
|
|
281
|
+
const { rolledTo, callerPinnedStrict } = await deps.failover();
|
|
282
|
+
|
|
283
|
+
if (!rolledTo && callerPinnedStrict) {
|
|
284
|
+
// Strict pin: the broker marked the account but deliberately did not roll
|
|
285
|
+
// the caller (agents.<name>.auth.strict). Rendering the all-blocked card
|
|
286
|
+
// here would tell the operator the whole fleet is exhausted while the
|
|
287
|
+
// snapshots in that very card show healthy accounts.
|
|
288
|
+
return {
|
|
289
|
+
kind: 'strict-pinned',
|
|
290
|
+
oldLabel: oldSnap.label,
|
|
291
|
+
oldQuota: oldSnap.quota,
|
|
292
|
+
announcement:
|
|
293
|
+
`_**${escapeMarkdown(deps.triggerAgent)}** is strictly pinned to ` +
|
|
294
|
+
`${escapeMarkdown(oldSnap.label)} (\`auth.strict\`) — riding out the wall ` +
|
|
295
|
+
`on its own account. The rest of the fleet is unaffected._`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
263
298
|
|
|
264
299
|
if (!rolledTo) {
|
|
265
300
|
// All-blocked path: the broker found no non-exhausted fallback. The active
|
|
@@ -213,18 +213,6 @@ const TOOL_SCHEMAS = [
|
|
|
213
213
|
required: ['chat_id'],
|
|
214
214
|
},
|
|
215
215
|
},
|
|
216
|
-
{
|
|
217
|
-
name: 'pin_message',
|
|
218
|
-
description: 'Pin a message in a Telegram chat. Useful for important outputs the user wants to find later. Requires admin rights in groups.',
|
|
219
|
-
inputSchema: {
|
|
220
|
-
type: 'object',
|
|
221
|
-
properties: {
|
|
222
|
-
chat_id: { type: 'string' },
|
|
223
|
-
message_id: { type: 'string' },
|
|
224
|
-
},
|
|
225
|
-
required: ['chat_id', 'message_id'],
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
216
|
{
|
|
229
217
|
name: 'delete_message',
|
|
230
218
|
description: 'Delete a message the bot previously sent. Prefer edit_message if you just want to update text — delete_message is for true removal.',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Per-(chat, thread) FIFO serialization for outbound Telegram API calls.
|
|
3
3
|
*
|
|
4
4
|
* Without this, concurrent MCP tool handlers (reply, stream_reply, react,
|
|
5
|
-
* edit_message, delete_message,
|
|
5
|
+
* edit_message, delete_message, forward_message) all call
|
|
6
6
|
* `bot.api.*` independently. When two calls race, HTTP latency can flip
|
|
7
7
|
* their delivery order — a later `reply` can land before an earlier
|
|
8
8
|
* `stream_reply` edit, or a `react` can resolve before the `reply` it
|
|
@@ -25196,18 +25196,6 @@ var TOOL_SCHEMAS = [
|
|
|
25196
25196
|
required: ["chat_id"]
|
|
25197
25197
|
}
|
|
25198
25198
|
},
|
|
25199
|
-
{
|
|
25200
|
-
name: "pin_message",
|
|
25201
|
-
description: "Pin a message in a Telegram chat. Useful for important outputs the user wants to find later. Requires admin rights in groups.",
|
|
25202
|
-
inputSchema: {
|
|
25203
|
-
type: "object",
|
|
25204
|
-
properties: {
|
|
25205
|
-
chat_id: { type: "string" },
|
|
25206
|
-
message_id: { type: "string" }
|
|
25207
|
-
},
|
|
25208
|
-
required: ["chat_id", "message_id"]
|
|
25209
|
-
}
|
|
25210
|
-
},
|
|
25211
25199
|
{
|
|
25212
25200
|
name: "delete_message",
|
|
25213
25201
|
description: "Delete a message the bot previously sent. Prefer edit_message if you just want to update text \u2014 delete_message is for true removal.",
|