switchroom 0.19.27 → 0.19.29
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 +16 -2
- package/dist/auth-broker/index.js +141 -8
- package/dist/cli/autoaccept-poll.js +225 -17
- package/dist/cli/notion-write-pretool.mjs +18 -2
- package/dist/cli/switchroom.js +864 -43
- package/dist/host-control/main.js +144 -9
- package/dist/vault/approvals/kernel-server.js +146 -9
- package/dist/vault/broker/server.js +146 -9
- package/package.json +3 -2
- package/profiles/_base/start.sh.hbs +79 -15
- package/telegram-plugin/dist/bridge/bridge.js +1 -0
- package/telegram-plugin/dist/gateway/gateway.js +585 -50
- package/telegram-plugin/dist/server.js +1 -0
- package/telegram-plugin/edit-flood-fuse.ts +230 -27
- package/telegram-plugin/gateway/callback-query-handlers.ts +6 -0
- package/telegram-plugin/gateway/gateway.ts +9 -2
- package/telegram-plugin/gateway/mcp-failure-hook.ts +74 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +202 -21
- package/telegram-plugin/mcp-credential-failure.ts +459 -0
- package/telegram-plugin/operator-events.ts +38 -0
- package/telegram-plugin/tests/edit-flood-fuse-ban-awareness.test.ts +58 -1
- package/telegram-plugin/tests/edit-flood-fuse-reply-reserve.test.ts +340 -0
- package/telegram-plugin/tests/finalize-callback-flood-policy.test.ts +298 -0
- package/telegram-plugin/tests/finalize-callback.test.ts +41 -8
- package/telegram-plugin/tests/mcp-credential-failure.test.ts +310 -0
- package/vendor/hindsight-memory/CHANGELOG.md +50 -0
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +1 -0
- package/vendor/hindsight-memory/scripts/drain_pending.py +437 -11
- package/vendor/hindsight-memory/scripts/lib/client.py +14 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +91 -0
- package/vendor/hindsight-memory/scripts/lib/pending.py +199 -28
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +1 -0
- package/vendor/hindsight-memory/scripts/retain.py +41 -1
- package/vendor/hindsight-memory/scripts/subagent_retain.py +1 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill.py +23 -1
- package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +5 -1
- package/vendor/hindsight-memory/scripts/tests/test_drain_circuit_breaker.py +401 -0
- package/vendor/hindsight-memory/scripts/tests/test_drain_serialisation.py +286 -0
- package/vendor/hindsight-memory/scripts/tests/test_observation_scopes.py +325 -0
- package/vendor/hindsight-memory/scripts/tests/test_pending_drops.py +817 -8
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +160 -1
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +40 -2
- package/vendor/hindsight-memory/settings.json +1 -1
- package/vendor/hindsight-memory/tests/test_hooks.py +11 -2
|
@@ -21552,10 +21552,22 @@ var require_dist = __commonJS((exports) => {
|
|
|
21552
21552
|
exports.visitAsync = visit.visitAsync;
|
|
21553
21553
|
});
|
|
21554
21554
|
|
|
21555
|
+
// ../src/memory/observation-scopes.ts
|
|
21556
|
+
var OBSERVATION_SCOPES;
|
|
21557
|
+
var init_observation_scopes = __esm(() => {
|
|
21558
|
+
OBSERVATION_SCOPES = [
|
|
21559
|
+
"per_tag",
|
|
21560
|
+
"combined",
|
|
21561
|
+
"all_combinations",
|
|
21562
|
+
"shared"
|
|
21563
|
+
];
|
|
21564
|
+
});
|
|
21565
|
+
|
|
21555
21566
|
// ../src/config/schema.ts
|
|
21556
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
21567
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
21557
21568
|
var init_schema = __esm(() => {
|
|
21558
21569
|
init_zod();
|
|
21570
|
+
init_observation_scopes();
|
|
21559
21571
|
CodeRepoEntrySchema = exports_external.object({
|
|
21560
21572
|
name: exports_external.string().describe("Short alias used when claiming (e.g. 'switchroom')"),
|
|
21561
21573
|
source: exports_external.string().describe("Absolute or home-relative path to the repo (e.g. ~/code/switchroom)"),
|
|
@@ -21667,6 +21679,7 @@ var init_schema = __esm(() => {
|
|
|
21667
21679
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
21668
21680
|
deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
|
|
21669
21681
|
}).optional();
|
|
21682
|
+
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Per-row observation scope stamped on every memory this agent " + `retains. "shared" makes Hindsight's consolidation write the ` + "resulting observations into ONE global untagged scope instead of a " + "scope per tag \u2014 what several agents pooling one bank need so their " + "observations actually merge rather than sitting in parallel " + "per-tag silos. OMITTED BY DEFAULT: unset means the field never goes " + "on the wire and the engine's own default stands, which is the " + "shipped behaviour. Applies to every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
21670
21683
|
AgentMemorySchema = exports_external.object({
|
|
21671
21684
|
collection: exports_external.string().describe("Hindsight collection name for this agent"),
|
|
21672
21685
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
@@ -21701,6 +21714,7 @@ var init_schema = __esm(() => {
|
|
|
21701
21714
|
empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
|
|
21702
21715
|
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis \u2014 a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
|
|
21703
21716
|
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true \u2014 Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never \u2026", "from now on \u2026", ' + `"stop doing \u2026", a stated preference, "that's wrong, it's \u2026") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex \u2014 the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
|
|
21717
|
+
observation_scopes: ObservationScopesSchema,
|
|
21704
21718
|
recall: exports_external.object({
|
|
21705
21719
|
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)."),
|
|
21706
21720
|
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."),
|
|
@@ -21944,7 +21958,10 @@ var init_schema = __esm(() => {
|
|
|
21944
21958
|
admin_key: exports_external.string().optional().describe("LiteLLM master/admin key used at apply time to provision the team + " + "virtual key. Supports a vault reference (e.g. " + "'vault:litellm/master-key') \u2014 resolution happens at apply time via " + "the vault-broker. Never injected into the agent container."),
|
|
21945
21959
|
team: exports_external.string().optional().describe("LiteLLM team alias the per-agent key is created under. Defaults to " + "'switchroom' (applied in code, not as a schema default)."),
|
|
21946
21960
|
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'."),
|
|
21947
|
-
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).")
|
|
21961
|
+
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)."),
|
|
21962
|
+
max_budget: exports_external.number().positive().optional().describe("HARD spend cap in USD for this agent's virtual key over one " + "`budget_duration` window. LiteLLM refuses the request once the key's " + "tracked spend exceeds it, so a runaway loop costs at most this much " + "before it is stopped. Defaults to " + "DEFAULT_KEY_MAX_BUDGET_USD (see src/litellm/budget.ts) \u2014 deliberately " + "conservative; raise it per-agent rather than removing it. Set 0 or " + "omit `budget_duration` at your own risk: an uncapped key is only as " + "bounded as the upstream account balance."),
|
|
21963
|
+
soft_budget: exports_external.number().positive().optional().describe("ADVISORY spend threshold in USD. LiteLLM keeps serving past it and " + "raises a budget alert instead. Must be < max_budget. NOTE: LiteLLM " + "accepts soft_budget only on POST /key/generate (GenerateKeyRequest); " + "UpdateKeyRequest does NOT carry it, so changing this value only takes " + "effect on a key that is (re)generated, not on an existing one."),
|
|
21964
|
+
budget_duration: exports_external.string().regex(/^\d+(s|m|h|d|mo)$/, "budget_duration must be a LiteLLM duration like '30d', '24h', '1mo'").optional().describe("Rolling window the budget resets on, in LiteLLM duration syntax " + "('30d', '24h', '1mo'). Defaults to DEFAULT_KEY_BUDGET_DURATION. " + "WITHOUT a duration LiteLLM treats max_budget as a LIFETIME cap that " + "never resets \u2014 the key silently dies for good once it is hit.")
|
|
21948
21965
|
}).optional().describe("LiteLLM routing config \u2014 opt-in per-agent virtual-key auto-provisioning " + "+ routing env. Default OFF. See LiteLLMConfigSchema doc for the full flow.");
|
|
21949
21966
|
HindsightPerOpLlmSchema = exports_external.object({
|
|
21950
21967
|
model: exports_external.string().min(1).optional().describe("Per-op model (upstream `HINDSIGHT_API_<OP>_LLM_MODEL`). Absent \u2192 " + "inherit the global `hindsight.llm.model`."),
|
|
@@ -21963,7 +21980,7 @@ var init_schema = __esm(() => {
|
|
|
21963
21980
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
|
|
21964
21981
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
21965
21982
|
}).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."),
|
|
21966
|
-
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_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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), 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
21983
|
+
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_MAX_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_MAX_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS \u2014 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), 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
21967
21984
|
});
|
|
21968
21985
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
21969
21986
|
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 \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -22096,6 +22113,7 @@ var init_schema = __esm(() => {
|
|
|
22096
22113
|
file: exports_external.boolean().optional(),
|
|
22097
22114
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
22098
22115
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
22116
|
+
observation_scopes: ObservationScopesSchema,
|
|
22099
22117
|
recall: exports_external.object({
|
|
22100
22118
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
22101
22119
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
@@ -46601,24 +46619,62 @@ async function applyTapAnnotationEdit(io, text) {
|
|
|
46601
46619
|
}
|
|
46602
46620
|
}
|
|
46603
46621
|
}
|
|
46622
|
+
function extractCallbackChatId(callbackQuery) {
|
|
46623
|
+
const msg = callbackQuery?.message;
|
|
46624
|
+
const chat = msg?.chat;
|
|
46625
|
+
const id = chat?.id;
|
|
46626
|
+
if (typeof id === "number" && Number.isFinite(id))
|
|
46627
|
+
return String(id);
|
|
46628
|
+
if (typeof id === "string" && id !== "")
|
|
46629
|
+
return id;
|
|
46630
|
+
return;
|
|
46631
|
+
}
|
|
46632
|
+
var DEAD_CARD_NOTICE = "\u26a0\ufe0f Your tap was applied, but this card could not be updated. " + "The buttons on it are STALE \u2014 tapping them again will not change anything. " + "Scroll down for the outcome, or ask the agent to re-send the card.";
|
|
46633
|
+
async function disarmDeadCard(ctx, apiCall, scope, log) {
|
|
46634
|
+
try {
|
|
46635
|
+
await apiCall(() => ctx.editMessageReplyMarkup({ reply_markup: { inline_keyboard: [] } }), { ...scope, verb: "editMessageReplyMarkup" });
|
|
46636
|
+
log("finalizeCallback: repaint failed but the keyboard was stripped \u2014 " + `the card shows stale text and is no longer tappable
|
|
46637
|
+
`);
|
|
46638
|
+
return;
|
|
46639
|
+
} catch (err) {
|
|
46640
|
+
log(`finalizeCallback: editMessageReplyMarkup fallback failed: ${err.message}
|
|
46641
|
+
`);
|
|
46642
|
+
}
|
|
46643
|
+
try {
|
|
46644
|
+
await apiCall(() => ctx.reply(DEAD_CARD_NOTICE, { link_preview_options: { is_disabled: true } }), { ...scope, verb: "sendMessage" });
|
|
46645
|
+
} catch (err) {
|
|
46646
|
+
log(`finalizeCallback: dead-card notice failed: ${err.message}
|
|
46647
|
+
`);
|
|
46648
|
+
}
|
|
46649
|
+
}
|
|
46604
46650
|
async function finalizeCallback(ctx, opts) {
|
|
46605
46651
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
46606
|
-
|
|
46652
|
+
const apiCall = opts.apiCall;
|
|
46653
|
+
const chatId = extractCallbackChatId(ctx.callbackQuery);
|
|
46654
|
+
const scope = {
|
|
46655
|
+
...chatId != null ? { chat_id: chatId } : {},
|
|
46656
|
+
priorityClass: "critical"
|
|
46657
|
+
};
|
|
46658
|
+
apiCall(() => ctx.answerCallbackQuery({
|
|
46607
46659
|
text: opts.ackText,
|
|
46608
46660
|
...opts.alert ? { show_alert: true } : {}
|
|
46609
|
-
}).catch((err) => {
|
|
46661
|
+
}), { ...scope, verb: "answerCallbackQuery" }).catch((err) => {
|
|
46610
46662
|
log(`finalizeCallback: answerCallbackQuery failed: ${err.message}
|
|
46611
46663
|
`);
|
|
46612
46664
|
});
|
|
46665
|
+
let repainted = true;
|
|
46613
46666
|
try {
|
|
46614
|
-
await ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
46667
|
+
await apiCall(() => ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
46615
46668
|
reply_markup: { inline_keyboard: [] },
|
|
46616
46669
|
link_preview_options: { is_disabled: true }
|
|
46617
|
-
});
|
|
46670
|
+
}), { ...scope, verb: "editMessageText" });
|
|
46618
46671
|
} catch (err) {
|
|
46672
|
+
repainted = false;
|
|
46619
46673
|
log(`finalizeCallback: editMessageText failed: ${err.message}
|
|
46620
46674
|
`);
|
|
46621
46675
|
}
|
|
46676
|
+
if (!repainted)
|
|
46677
|
+
await disarmDeadCard(ctx, apiCall, scope, log);
|
|
46622
46678
|
if (opts.synthInbound != null) {
|
|
46623
46679
|
try {
|
|
46624
46680
|
const r = opts.synthInbound();
|
|
@@ -50153,6 +50209,7 @@ function createCallbackQueryHandlers(deps) {
|
|
|
50153
50209
|
|
|
50154
50210
|
\u2705 **${escapeHtmlForTg2(agentName)}** granted read access to ` + `\`${keyName}\` for 30 days ` + `(grant \`${id}\`). ` + `Re-run /vault audit to act on remaining denials.`;
|
|
50155
50211
|
await finalizeCallback(ctx, {
|
|
50212
|
+
apiCall: robustApiCall,
|
|
50156
50213
|
ackText: "\u2705 Grant minted",
|
|
50157
50214
|
newText: baseText ? `${baseText}${statusLine}` : statusLine
|
|
50158
50215
|
});
|
|
@@ -50731,6 +50788,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
|
|
|
50731
50788
|
|
|
50732
50789
|
\u270f\ufe0f **Rename mode** \u2014 send the new key name as your next message. ` + `The current proposed key is \`${pending.key}\`.`;
|
|
50733
50790
|
await finalizeCallback(ctx, {
|
|
50791
|
+
apiCall: robustApiCall,
|
|
50734
50792
|
ackText: "Send the new key name as your next message.",
|
|
50735
50793
|
newText: baseText ? `${baseText}${statusLine}` : statusLine
|
|
50736
50794
|
});
|
|
@@ -51129,6 +51187,7 @@ ${keyList}`,
|
|
|
51129
51187
|
const successText = `\u2705 Grant \`${id}\` created. Written to \`~/.switchroom/agents/${escapeHtmlForTg2(state.agent)}/.vault-token\``;
|
|
51130
51188
|
if (msgId != null) {
|
|
51131
51189
|
await finalizeCallback(ctx, {
|
|
51190
|
+
apiCall: robustApiCall,
|
|
51132
51191
|
ackText: "\u2705 Grant created",
|
|
51133
51192
|
newText: successText
|
|
51134
51193
|
});
|
|
@@ -51405,6 +51464,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51405
51464
|
|
|
51406
51465
|
\u2717 _Dismissed by operator._`;
|
|
51407
51466
|
await finalizeCallback(ctx, {
|
|
51467
|
+
apiCall: robustApiCall,
|
|
51408
51468
|
ackText: "Dismissed",
|
|
51409
51469
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status
|
|
51410
51470
|
});
|
|
@@ -51417,6 +51477,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51417
51477
|
|
|
51418
51478
|
\uD83D\uDD04 _**${escapeHtmlForTg2(agent)}** restart requested by operator._`;
|
|
51419
51479
|
await finalizeCallback(ctx, {
|
|
51480
|
+
apiCall: robustApiCall,
|
|
51420
51481
|
ackText: `Restarting ${agent}\u2026`,
|
|
51421
51482
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status
|
|
51422
51483
|
});
|
|
@@ -51433,6 +51494,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51433
51494
|
|
|
51434
51495
|
\uD83D\uDD10 _Reauth started for **${escapeHtmlForTg2(agent)}** \u2014 follow the login URL below._`;
|
|
51435
51496
|
await finalizeCallback(ctx, {
|
|
51497
|
+
apiCall: robustApiCall,
|
|
51436
51498
|
ackText: `Starting reauth for ${agent}\u2026`,
|
|
51437
51499
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status,
|
|
51438
51500
|
synthInbound: async () => {
|
|
@@ -63318,6 +63380,7 @@ var EDIT_FLOOD_FUSE_DEFAULTS = {
|
|
|
63318
63380
|
cosmeticPerChatMaxPerWindow: 6,
|
|
63319
63381
|
perChatTotalMaxPerWindow: 20,
|
|
63320
63382
|
perChatReplyReserve: 8,
|
|
63383
|
+
perChatCriticalMinPerWindow: 3,
|
|
63321
63384
|
perTokenMaxPerWindow: 25,
|
|
63322
63385
|
perTokenWindowMs: 1000,
|
|
63323
63386
|
chatActionMaxDeferMs: 3000,
|
|
@@ -63335,6 +63398,12 @@ function envInt(raw) {
|
|
|
63335
63398
|
const n = Number(raw);
|
|
63336
63399
|
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;
|
|
63337
63400
|
}
|
|
63401
|
+
function envFactor(raw) {
|
|
63402
|
+
if (raw == null || raw.trim() === "")
|
|
63403
|
+
return;
|
|
63404
|
+
const n = Number(raw);
|
|
63405
|
+
return Number.isFinite(n) && n > 0 && n <= 1 ? n : undefined;
|
|
63406
|
+
}
|
|
63338
63407
|
function editFloodFuseConfigFromEnv(env) {
|
|
63339
63408
|
const cfg = { enabled: env.SWITCHROOM_EDIT_FUSE !== "0" };
|
|
63340
63409
|
const assign = (k, v) => {
|
|
@@ -63345,8 +63414,11 @@ function editFloodFuseConfigFromEnv(env) {
|
|
|
63345
63414
|
assign("cosmeticPerChatMaxPerWindow", envInt(env.SWITCHROOM_FEED_EDIT_MAX_PER_CHAT_PER_MIN));
|
|
63346
63415
|
assign("perChatTotalMaxPerWindow", envInt(env.SWITCHROOM_CHAT_TOTAL_MAX_PER_MIN));
|
|
63347
63416
|
assign("perChatReplyReserve", envInt(env.SWITCHROOM_CHAT_REPLY_RESERVE));
|
|
63417
|
+
assign("perChatCriticalMinPerWindow", envInt(env.SWITCHROOM_CHAT_CRITICAL_MIN_PER_MIN));
|
|
63348
63418
|
assign("perTokenMaxPerWindow", envInt(env.SWITCHROOM_TOKEN_MAX_PER_SEC));
|
|
63349
63419
|
assign("maxDeferMs", envInt(env.SWITCHROOM_EDIT_FUSE_MAX_DEFER_MS));
|
|
63420
|
+
assign("maxTightenLevel", envInt(env.SWITCHROOM_EDIT_FUSE_MAX_TIGHTEN_LEVEL));
|
|
63421
|
+
assign("tightenFactor", envFactor(env.SWITCHROOM_EDIT_FUSE_TIGHTEN_FACTOR));
|
|
63350
63422
|
const stateDir = env.TELEGRAM_STATE_DIR;
|
|
63351
63423
|
if (stateDir != null && stateDir !== "") {
|
|
63352
63424
|
cfg.floodWaitRemainingMs = makeFloodWaitProbe(floodStatePath(stateDir));
|
|
@@ -63367,6 +63439,8 @@ function createEditFloodFuse(config = {}) {
|
|
|
63367
63439
|
const perChatTotalMax = config.perChatTotalMaxPerWindow ?? D.perChatTotalMaxPerWindow;
|
|
63368
63440
|
const perChatWindowMs = config.perChatWindowMs ?? D.perChatWindowMs;
|
|
63369
63441
|
const perChatReplyReserve = Math.min(Math.max(0, config.perChatReplyReserve ?? D.perChatReplyReserve), Math.max(0, perChatTotalMax - 1));
|
|
63442
|
+
const replyReserveFraction = perChatTotalMax > 0 ? perChatReplyReserve / perChatTotalMax : 0;
|
|
63443
|
+
const perChatCriticalMin = Math.max(0, config.perChatCriticalMinPerWindow ?? D.perChatCriticalMinPerWindow);
|
|
63370
63444
|
const perTokenMax = Math.max(1, config.perTokenMaxPerWindow ?? D.perTokenMaxPerWindow);
|
|
63371
63445
|
const perTokenWindowMs = Math.max(1, config.perTokenWindowMs ?? D.perTokenWindowMs);
|
|
63372
63446
|
const tokenKey = `g:${config.botScopeKey ?? "unknown"}`;
|
|
@@ -63412,8 +63486,10 @@ function createEditFloodFuse(config = {}) {
|
|
|
63412
63486
|
tightenLevel--;
|
|
63413
63487
|
tightenedUntil = tightenLevel > 0 ? tightenedUntil + tightenMs : 0;
|
|
63414
63488
|
}
|
|
63415
|
-
|
|
63416
|
-
|
|
63489
|
+
const remainingMs = persistedFloodRemainingMs(now);
|
|
63490
|
+
if (remainingMs > 0) {
|
|
63491
|
+
return Math.max(tightenLevel, Math.min(maxTightenLevel, tightenStepFor(remainingMs / 1000)));
|
|
63492
|
+
}
|
|
63417
63493
|
return tightenLevel;
|
|
63418
63494
|
}
|
|
63419
63495
|
function isTightened(now) {
|
|
@@ -63425,6 +63501,24 @@ function createEditFloodFuse(config = {}) {
|
|
|
63425
63501
|
return base;
|
|
63426
63502
|
return Math.max(1, Math.floor(base * Math.pow(tightenFactor, level)));
|
|
63427
63503
|
}
|
|
63504
|
+
function classCeiling(base, cls, now) {
|
|
63505
|
+
const eff = ceiling(base, now);
|
|
63506
|
+
if (cls !== "critical")
|
|
63507
|
+
return eff;
|
|
63508
|
+
return Math.max(eff, Math.min(base, perChatCriticalMin));
|
|
63509
|
+
}
|
|
63510
|
+
function cosmeticTotalMax(now) {
|
|
63511
|
+
const eff = ceiling(perChatTotalMax, now);
|
|
63512
|
+
if (replyReserveFraction <= 0)
|
|
63513
|
+
return eff;
|
|
63514
|
+
const reserve = Math.min(eff, Math.max(1, Math.round(eff * replyReserveFraction)));
|
|
63515
|
+
return Math.max(0, eff - reserve);
|
|
63516
|
+
}
|
|
63517
|
+
function totalMaxFor(cls) {
|
|
63518
|
+
if (cls === "cosmetic")
|
|
63519
|
+
return cosmeticTotalMax;
|
|
63520
|
+
return (now) => classCeiling(perChatTotalMax, cls, now);
|
|
63521
|
+
}
|
|
63428
63522
|
function win(key) {
|
|
63429
63523
|
let w = windows.get(key);
|
|
63430
63524
|
if (w === undefined) {
|
|
@@ -63454,6 +63548,9 @@ function createEditFloodFuse(config = {}) {
|
|
|
63454
63548
|
}
|
|
63455
63549
|
function waitFor(w, now, windowMs, max) {
|
|
63456
63550
|
prune(w, now, windowMs);
|
|
63551
|
+
if (max <= 0) {
|
|
63552
|
+
return w.ts.length > 0 ? Math.max(1, w.ts[0] + windowMs - now) : Math.max(1, windowMs);
|
|
63553
|
+
}
|
|
63457
63554
|
if (w.ts.length < max)
|
|
63458
63555
|
return 0;
|
|
63459
63556
|
return Math.max(1, w.ts[0] + windowMs - now);
|
|
@@ -63502,12 +63599,12 @@ function createEditFloodFuse(config = {}) {
|
|
|
63502
63599
|
if (i >= 0)
|
|
63503
63600
|
w.ts.splice(i, 1);
|
|
63504
63601
|
}
|
|
63505
|
-
async function awaitRoom(key, windowMs,
|
|
63602
|
+
async function awaitRoom(key, windowMs, maxFor, method, mode, cls, dropGuard, deadline, lateReleaseKey) {
|
|
63506
63603
|
const w = win(key);
|
|
63507
63604
|
let counted = false;
|
|
63508
63605
|
for (;; ) {
|
|
63509
63606
|
const now = clock.now();
|
|
63510
|
-
const wait = waitFor(w, now, windowMs,
|
|
63607
|
+
const wait = waitFor(w, now, windowMs, maxFor(now));
|
|
63511
63608
|
if (wait === 0) {
|
|
63512
63609
|
w.ts.push(now);
|
|
63513
63610
|
return now;
|
|
@@ -63570,15 +63667,16 @@ function createEditFloodFuse(config = {}) {
|
|
|
63570
63667
|
evict(now);
|
|
63571
63668
|
const deadline = now + maxDeferMs;
|
|
63572
63669
|
const cls = currentOutboundClass() ?? (isChatAction ? "cosmetic" : defaultOutboundClass(isEdit));
|
|
63670
|
+
const tokenMaxFor = (t) => ceiling(perTokenMax, t);
|
|
63573
63671
|
if (chat == null) {
|
|
63574
63672
|
counters.chatless++;
|
|
63575
|
-
await awaitRoom(tokenKey, perTokenWindowMs,
|
|
63673
|
+
await awaitRoom(tokenKey, perTokenWindowMs, tokenMaxFor, method, "release", cls, undefined, deadline);
|
|
63576
63674
|
return runObserved(next);
|
|
63577
63675
|
}
|
|
63578
63676
|
const totalKey = `t:${chat}`;
|
|
63579
|
-
const
|
|
63677
|
+
const chatTotalMax = totalMaxFor(cls);
|
|
63580
63678
|
const passToken = async () => {
|
|
63581
|
-
await awaitRoom(tokenKey, perTokenWindowMs,
|
|
63679
|
+
await awaitRoom(tokenKey, perTokenWindowMs, tokenMaxFor, method, "release", cls, undefined, deadline);
|
|
63582
63680
|
return runObserved(next);
|
|
63583
63681
|
};
|
|
63584
63682
|
if (isEdit && msg != null) {
|
|
@@ -63586,7 +63684,7 @@ function createEditFloodFuse(config = {}) {
|
|
|
63586
63684
|
const mw = win(msgKey);
|
|
63587
63685
|
mw.inflight++;
|
|
63588
63686
|
try {
|
|
63589
|
-
const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? cosmeticPerMessageMax : perMessageMax, method, "supersede", cls, undefined, deadline);
|
|
63687
|
+
const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerMessageMax, t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, undefined, deadline);
|
|
63590
63688
|
if (msgSlot === null)
|
|
63591
63689
|
return DROPPED_RESULT;
|
|
63592
63690
|
const dropGuard = () => mw.inflight > 1;
|
|
@@ -63597,13 +63695,13 @@ function createEditFloodFuse(config = {}) {
|
|
|
63597
63695
|
unreserve(k, at);
|
|
63598
63696
|
};
|
|
63599
63697
|
const chatKey2 = `ce:${chat}`;
|
|
63600
|
-
const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? cosmeticPerChatMax : perChatEditMax, method, "drop", cls, dropGuard, deadline, lateKey);
|
|
63698
|
+
const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerChatMax, t) : (t) => classCeiling(perChatEditMax, cls, t), method, "drop", cls, dropGuard, deadline, lateKey);
|
|
63601
63699
|
if (chatSlot === null) {
|
|
63602
63700
|
giveBack();
|
|
63603
63701
|
return DROPPED_RESULT;
|
|
63604
63702
|
}
|
|
63605
63703
|
reserved.push([chatKey2, chatSlot]);
|
|
63606
|
-
const totalSlot = await awaitRoom(totalKey, perChatWindowMs,
|
|
63704
|
+
const totalSlot = await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, cls === "cosmetic" ? "drop" : "release", cls, dropGuard, deadline, lateKey);
|
|
63607
63705
|
if (totalSlot === null) {
|
|
63608
63706
|
giveBack();
|
|
63609
63707
|
return DROPPED_RESULT;
|
|
@@ -63614,19 +63712,19 @@ function createEditFloodFuse(config = {}) {
|
|
|
63614
63712
|
}
|
|
63615
63713
|
}
|
|
63616
63714
|
if (isSend) {
|
|
63617
|
-
await awaitRoom(`cs:${chat}`, perChatWindowMs, perChatSendMax, method, "release", cls, undefined, deadline);
|
|
63618
|
-
await awaitRoom(totalKey, perChatWindowMs,
|
|
63715
|
+
await awaitRoom(`cs:${chat}`, perChatWindowMs, (t) => classCeiling(perChatSendMax, cls, t), method, "release", cls, undefined, deadline);
|
|
63716
|
+
await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "release", cls, undefined, deadline);
|
|
63619
63717
|
return passToken();
|
|
63620
63718
|
}
|
|
63621
63719
|
counters.meteredByDefault++;
|
|
63622
63720
|
if (isChatAction) {
|
|
63623
63721
|
const actionDeadline = Math.min(deadline, now + chatActionMaxDeferMs);
|
|
63624
|
-
const slot = await awaitRoom(totalKey, perChatWindowMs,
|
|
63722
|
+
const slot = await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "drop", cls, undefined, actionDeadline);
|
|
63625
63723
|
if (slot === null)
|
|
63626
63724
|
return DROPPED_RESULT;
|
|
63627
63725
|
return passToken();
|
|
63628
63726
|
}
|
|
63629
|
-
await awaitRoom(totalKey, perChatWindowMs,
|
|
63727
|
+
await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "release", cls, undefined, deadline);
|
|
63630
63728
|
return passToken();
|
|
63631
63729
|
}
|
|
63632
63730
|
async function runObserved(next) {
|
|
@@ -63657,6 +63755,8 @@ function createEditFloodFuse(config = {}) {
|
|
|
63657
63755
|
perMessageCeiling: ceiling(perMessageMax, now),
|
|
63658
63756
|
cosmeticPerMessageCeiling: ceiling(cosmeticPerMessageMax, now),
|
|
63659
63757
|
cosmeticPerChatCeiling: ceiling(cosmeticPerChatMax, now),
|
|
63758
|
+
cosmeticPerChatTotalCeiling: cosmeticTotalMax(now),
|
|
63759
|
+
criticalPerChatTotalCeiling: classCeiling(perChatTotalMax, "critical", now),
|
|
63660
63760
|
meteredByDefault: counters.meteredByDefault,
|
|
63661
63761
|
chatless: counters.chatless,
|
|
63662
63762
|
perTokenCeiling: ceiling(perTokenMax, now),
|
|
@@ -70869,6 +70969,7 @@ var OPERATOR_ACTIONABLE_KINDS = new Set([
|
|
|
70869
70969
|
"credentials-invalid",
|
|
70870
70970
|
"credit-exhausted",
|
|
70871
70971
|
"provider-credit-exhausted",
|
|
70972
|
+
"mcp-dependency-blocked",
|
|
70872
70973
|
"proxy-misconfig"
|
|
70873
70974
|
]);
|
|
70874
70975
|
|
|
@@ -73898,6 +73999,19 @@ function renderOperatorEvent(ev) {
|
|
|
73898
73999
|
}
|
|
73899
74000
|
};
|
|
73900
74001
|
}
|
|
74002
|
+
case "mcp-dependency-blocked":
|
|
74003
|
+
return {
|
|
74004
|
+
text: [
|
|
74005
|
+
`\uD83D\uDD0C **Paid dependency blocked**`,
|
|
74006
|
+
stripRawErrorBytes(ev.detail)
|
|
74007
|
+
].filter(Boolean).join(`
|
|
74008
|
+
`),
|
|
74009
|
+
keyboard: {
|
|
74010
|
+
inline_keyboard: [
|
|
74011
|
+
[{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
74012
|
+
]
|
|
74013
|
+
}
|
|
74014
|
+
};
|
|
73901
74015
|
case "quota-exhausted":
|
|
73902
74016
|
return {
|
|
73903
74017
|
text: [
|
|
@@ -74049,6 +74163,7 @@ var OPERATOR_ACTIONABLE_KINDS2 = new Set([
|
|
|
74049
74163
|
"credentials-invalid",
|
|
74050
74164
|
"credit-exhausted",
|
|
74051
74165
|
"provider-credit-exhausted",
|
|
74166
|
+
"mcp-dependency-blocked",
|
|
74052
74167
|
"proxy-misconfig"
|
|
74053
74168
|
]);
|
|
74054
74169
|
function isOperatorActionableKind(kind) {
|
|
@@ -74067,6 +74182,265 @@ function renderUserFacingFailureNotice() {
|
|
|
74067
74182
|
return "\u26a0\ufe0f Sorry \u2014 I couldn't complete that just now. It's a problem on our side, not anything you did. Please try again shortly.";
|
|
74068
74183
|
}
|
|
74069
74184
|
|
|
74185
|
+
// mcp-credential-failure.ts
|
|
74186
|
+
var MCP_SERVER_VAULT_KEYS = {
|
|
74187
|
+
eraser: { label: "Eraser", vaultKey: "eraser/api-key", consoleUrl: "https://app.eraser.io/settings/api" },
|
|
74188
|
+
brevo: { label: "Brevo", vaultKey: "brevo/api-key", consoleUrl: "https://app.brevo.com/settings/keys/api" },
|
|
74189
|
+
postiz: { label: "Postiz", vaultKey: "postiz/api-key", consoleUrl: "https://platform.postiz.com/settings" },
|
|
74190
|
+
"meta-ads": { label: "Meta Ads", vaultKey: "meta-ads/access-token", consoleUrl: "https://business.facebook.com/settings" },
|
|
74191
|
+
"google-ads": { label: "Google Ads", vaultKey: "google-ads/developer-token", consoleUrl: "https://ads.google.com/aw/apicenter" },
|
|
74192
|
+
cloudflare: { label: "Cloudflare", vaultKey: "cloudflare/api-token", consoleUrl: "https://dash.cloudflare.com/profile/api-tokens" },
|
|
74193
|
+
context7: { label: "Context7", vaultKey: "context7/api-key", consoleUrl: "https://context7.com/dashboard" }
|
|
74194
|
+
};
|
|
74195
|
+
function parseMcpServerFromToolName(toolName) {
|
|
74196
|
+
if (typeof toolName !== "string")
|
|
74197
|
+
return null;
|
|
74198
|
+
if (!toolName.startsWith("mcp__"))
|
|
74199
|
+
return null;
|
|
74200
|
+
const rest = toolName.slice("mcp__".length);
|
|
74201
|
+
const end = rest.indexOf("__");
|
|
74202
|
+
const server = end === -1 ? rest : rest.slice(0, end);
|
|
74203
|
+
return server.length > 0 ? server : null;
|
|
74204
|
+
}
|
|
74205
|
+
function describeMcpServer(server) {
|
|
74206
|
+
const direct = MCP_SERVER_VAULT_KEYS[server];
|
|
74207
|
+
if (direct != null)
|
|
74208
|
+
return direct;
|
|
74209
|
+
const entry = PROVIDER_CREDIT_REGISTRY.find((e) => e.id === server);
|
|
74210
|
+
if (entry != null) {
|
|
74211
|
+
return { label: entry.label, vaultKey: entry.vaultKey, consoleUrl: entry.consoleUrl };
|
|
74212
|
+
}
|
|
74213
|
+
return null;
|
|
74214
|
+
}
|
|
74215
|
+
function isAlertingClass(c) {
|
|
74216
|
+
return c !== "ordinary";
|
|
74217
|
+
}
|
|
74218
|
+
var CLASS_RULES = [
|
|
74219
|
+
{
|
|
74220
|
+
cls: "credential",
|
|
74221
|
+
statuses: [401, 403],
|
|
74222
|
+
signals: [
|
|
74223
|
+
"invalid api key",
|
|
74224
|
+
"invalid_api_key",
|
|
74225
|
+
"incorrect api key",
|
|
74226
|
+
"invalid authentication",
|
|
74227
|
+
"authentication_error",
|
|
74228
|
+
"authentication failed",
|
|
74229
|
+
"unauthorized",
|
|
74230
|
+
"unauthorised",
|
|
74231
|
+
"forbidden",
|
|
74232
|
+
"api key not found",
|
|
74233
|
+
"no api key provided",
|
|
74234
|
+
"missing api key",
|
|
74235
|
+
"api key expired",
|
|
74236
|
+
"key has been disabled",
|
|
74237
|
+
"key has been revoked",
|
|
74238
|
+
"key is disabled",
|
|
74239
|
+
"account has been blocked",
|
|
74240
|
+
"account is blocked",
|
|
74241
|
+
"account suspended",
|
|
74242
|
+
"permission_denied",
|
|
74243
|
+
"token expired",
|
|
74244
|
+
"invalid token"
|
|
74245
|
+
]
|
|
74246
|
+
},
|
|
74247
|
+
{
|
|
74248
|
+
cls: "quota",
|
|
74249
|
+
statuses: [],
|
|
74250
|
+
signals: [
|
|
74251
|
+
"quota exceeded",
|
|
74252
|
+
"quota_exceeded",
|
|
74253
|
+
"over quota",
|
|
74254
|
+
"usage limit reached",
|
|
74255
|
+
"usage limit exceeded",
|
|
74256
|
+
"monthly limit",
|
|
74257
|
+
"monthly quota",
|
|
74258
|
+
"plan limit",
|
|
74259
|
+
"plan_limit",
|
|
74260
|
+
"limit reached for your plan",
|
|
74261
|
+
"upgrade your plan",
|
|
74262
|
+
"spending limit"
|
|
74263
|
+
]
|
|
74264
|
+
}
|
|
74265
|
+
];
|
|
74266
|
+
var TRANSIENT_SIGNALS = [
|
|
74267
|
+
"rate limit",
|
|
74268
|
+
"rate_limit",
|
|
74269
|
+
"too many requests",
|
|
74270
|
+
"retry after",
|
|
74271
|
+
"retry-after",
|
|
74272
|
+
"slow down",
|
|
74273
|
+
"timeout",
|
|
74274
|
+
"timed out",
|
|
74275
|
+
"etimedout",
|
|
74276
|
+
"econnreset",
|
|
74277
|
+
"econnrefused",
|
|
74278
|
+
"socket hang up",
|
|
74279
|
+
"network error",
|
|
74280
|
+
"service unavailable",
|
|
74281
|
+
"bad gateway",
|
|
74282
|
+
"temporarily unavailable"
|
|
74283
|
+
];
|
|
74284
|
+
var MAX_SCAN_CHARS2 = 16384;
|
|
74285
|
+
function sample2(text4) {
|
|
74286
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
74287
|
+
return "";
|
|
74288
|
+
return (text4.length > MAX_SCAN_CHARS2 ? text4.slice(0, MAX_SCAN_CHARS2) : text4).toLowerCase();
|
|
74289
|
+
}
|
|
74290
|
+
function extractHttpStatus(text4) {
|
|
74291
|
+
const lower = sample2(text4);
|
|
74292
|
+
if (lower.length === 0)
|
|
74293
|
+
return null;
|
|
74294
|
+
const m = /\b(?:http[ _-]?status|status[ _-]?code|statuscode|status|http|code|error)\b\D{0,4}\b([1-5]\d\d)\b/.exec(lower) ?? /\b([1-5]\d\d)\s+(?:unauthorized|unauthorised|forbidden|payment required|too many requests)\b/.exec(lower);
|
|
74295
|
+
if (m == null)
|
|
74296
|
+
return null;
|
|
74297
|
+
const n = Number(m[1]);
|
|
74298
|
+
return Number.isFinite(n) ? n : null;
|
|
74299
|
+
}
|
|
74300
|
+
function classifyMcpFailure(text4, status) {
|
|
74301
|
+
const lower = sample2(text4);
|
|
74302
|
+
const httpStatus = typeof status === "number" ? status : extractHttpStatus(text4);
|
|
74303
|
+
if (isProviderCreditStatus(httpStatus) || hasCreditExhaustionWording(lower))
|
|
74304
|
+
return "credit";
|
|
74305
|
+
for (const rule of CLASS_RULES) {
|
|
74306
|
+
if (rule.signals.some((s) => lower.includes(s)))
|
|
74307
|
+
return rule.cls;
|
|
74308
|
+
}
|
|
74309
|
+
if (TRANSIENT_SIGNALS.some((s) => lower.includes(s)))
|
|
74310
|
+
return "ordinary";
|
|
74311
|
+
for (const rule of CLASS_RULES) {
|
|
74312
|
+
if (httpStatus != null && rule.statuses.includes(httpStatus))
|
|
74313
|
+
return rule.cls;
|
|
74314
|
+
}
|
|
74315
|
+
return "ordinary";
|
|
74316
|
+
}
|
|
74317
|
+
var RENOTIFY_MS = 6 * 60 * 60 * 1000;
|
|
74318
|
+
|
|
74319
|
+
class McpFailureLedger {
|
|
74320
|
+
renotifyMs;
|
|
74321
|
+
rows = new Map;
|
|
74322
|
+
constructor(renotifyMs = RENOTIFY_MS) {
|
|
74323
|
+
this.renotifyMs = renotifyMs;
|
|
74324
|
+
}
|
|
74325
|
+
note(input) {
|
|
74326
|
+
if (!isAlertingClass(input.cls))
|
|
74327
|
+
return null;
|
|
74328
|
+
const cls = input.cls;
|
|
74329
|
+
const key = `${input.server}::${cls}`;
|
|
74330
|
+
const row = this.rows.get(key) ?? { agents: new Set, occurrences: 0, lastAlertAt: -Infinity };
|
|
74331
|
+
row.agents.add(input.agent);
|
|
74332
|
+
row.occurrences += 1;
|
|
74333
|
+
this.rows.set(key, row);
|
|
74334
|
+
const due = input.now - row.lastAlertAt >= this.renotifyMs;
|
|
74335
|
+
if (!due)
|
|
74336
|
+
return null;
|
|
74337
|
+
const renotify = Number.isFinite(row.lastAlertAt);
|
|
74338
|
+
const meta = describeMcpServer(input.server);
|
|
74339
|
+
const alert = {
|
|
74340
|
+
server: input.server,
|
|
74341
|
+
label: meta?.label ?? input.server,
|
|
74342
|
+
vaultKey: meta?.vaultKey ?? `${input.server}/api-key`,
|
|
74343
|
+
consoleUrl: meta?.consoleUrl ?? "",
|
|
74344
|
+
cls,
|
|
74345
|
+
agents: [...row.agents].sort(),
|
|
74346
|
+
occurrences: row.occurrences,
|
|
74347
|
+
renotify
|
|
74348
|
+
};
|
|
74349
|
+
row.lastAlertAt = input.now;
|
|
74350
|
+
row.agents = new Set;
|
|
74351
|
+
row.occurrences = 0;
|
|
74352
|
+
return alert;
|
|
74353
|
+
}
|
|
74354
|
+
size() {
|
|
74355
|
+
return this.rows.size;
|
|
74356
|
+
}
|
|
74357
|
+
}
|
|
74358
|
+
var PENDING_TOOL_NAMES_MAX = 512;
|
|
74359
|
+
|
|
74360
|
+
class McpFailureWatcher {
|
|
74361
|
+
pending = new Map;
|
|
74362
|
+
ledger;
|
|
74363
|
+
constructor(renotifyMs = RENOTIFY_MS) {
|
|
74364
|
+
this.ledger = new McpFailureLedger(renotifyMs);
|
|
74365
|
+
}
|
|
74366
|
+
onToolUse(toolUseId, toolName) {
|
|
74367
|
+
if (typeof toolUseId !== "string" || toolUseId.length === 0)
|
|
74368
|
+
return;
|
|
74369
|
+
if (parseMcpServerFromToolName(toolName) == null)
|
|
74370
|
+
return;
|
|
74371
|
+
if (this.pending.size >= PENDING_TOOL_NAMES_MAX) {
|
|
74372
|
+
const oldest = this.pending.keys().next();
|
|
74373
|
+
if (!oldest.done)
|
|
74374
|
+
this.pending.delete(oldest.value);
|
|
74375
|
+
}
|
|
74376
|
+
this.pending.set(toolUseId, toolName);
|
|
74377
|
+
}
|
|
74378
|
+
onToolResult(input) {
|
|
74379
|
+
const id = typeof input.toolUseId === "string" ? input.toolUseId : "";
|
|
74380
|
+
const toolName = id.length > 0 ? this.pending.get(id) : undefined;
|
|
74381
|
+
if (id.length > 0)
|
|
74382
|
+
this.pending.delete(id);
|
|
74383
|
+
if (input.isError !== true)
|
|
74384
|
+
return null;
|
|
74385
|
+
if (toolName == null)
|
|
74386
|
+
return null;
|
|
74387
|
+
const server = parseMcpServerFromToolName(toolName);
|
|
74388
|
+
if (server == null)
|
|
74389
|
+
return null;
|
|
74390
|
+
const cls = classifyMcpFailure(input.errorText);
|
|
74391
|
+
return this.ledger.note({ server, agent: input.agent, cls, now: input.now });
|
|
74392
|
+
}
|
|
74393
|
+
}
|
|
74394
|
+
var CLASS_HEADLINE = {
|
|
74395
|
+
credit: "is out of credit",
|
|
74396
|
+
credential: "key is being rejected",
|
|
74397
|
+
quota: "has hit its usage quota"
|
|
74398
|
+
};
|
|
74399
|
+
var CLASS_ACTION = {
|
|
74400
|
+
credit: "Top up the balance",
|
|
74401
|
+
credential: "Re-issue the key and update the vault entry",
|
|
74402
|
+
quota: "Raise the plan limit or wait for the quota to reset"
|
|
74403
|
+
};
|
|
74404
|
+
function renderMcpFailureDetail(alert) {
|
|
74405
|
+
const agents = alert.agents.length > 0 ? alert.agents.map((a) => a.replace(/[^A-Za-z0-9._-]/g, "")).filter(Boolean).join(", ") : "unknown agent";
|
|
74406
|
+
const console_ = alert.consoleUrl.length > 0 ? ` \u2192 ${alert.consoleUrl}` : "";
|
|
74407
|
+
const repeat = alert.renotify ? " (still failing)" : "";
|
|
74408
|
+
const times = alert.occurrences === 1 ? "1 failure" : `${alert.occurrences} failures`;
|
|
74409
|
+
return `${alert.label} ${CLASS_HEADLINE[alert.cls]}${repeat} \u2014 ${times}, affecting: ${agents}. ` + `${CLASS_ACTION[alert.cls]}. Vault key: \`${alert.vaultKey}\`${console_}`;
|
|
74410
|
+
}
|
|
74411
|
+
|
|
74412
|
+
// gateway/mcp-failure-hook.ts
|
|
74413
|
+
function createMcpFailureHook(deps) {
|
|
74414
|
+
const watcher = new McpFailureWatcher;
|
|
74415
|
+
const clock = deps.now ?? (() => Date.now());
|
|
74416
|
+
return function noteMcpDependencyFailure(ev) {
|
|
74417
|
+
try {
|
|
74418
|
+
if (ev.kind === "tool_use") {
|
|
74419
|
+
watcher.onToolUse(ev.toolUseId, ev.toolName);
|
|
74420
|
+
return;
|
|
74421
|
+
}
|
|
74422
|
+
if (ev.kind !== "tool_result")
|
|
74423
|
+
return;
|
|
74424
|
+
const alert = watcher.onToolResult({
|
|
74425
|
+
toolUseId: ev.toolUseId,
|
|
74426
|
+
isError: ev.isError,
|
|
74427
|
+
errorText: ev.errorText,
|
|
74428
|
+
agent: deps.agent,
|
|
74429
|
+
now: clock()
|
|
74430
|
+
});
|
|
74431
|
+
if (alert == null)
|
|
74432
|
+
return;
|
|
74433
|
+
deps.emit({
|
|
74434
|
+
kind: "mcp-dependency-blocked",
|
|
74435
|
+
agent: deps.agent,
|
|
74436
|
+
detail: renderMcpFailureDetail(alert),
|
|
74437
|
+
suggestedActions: [],
|
|
74438
|
+
firstSeenAt: new Date(clock())
|
|
74439
|
+
});
|
|
74440
|
+
} catch {}
|
|
74441
|
+
};
|
|
74442
|
+
}
|
|
74443
|
+
|
|
74070
74444
|
// pending-user-notice.ts
|
|
74071
74445
|
var PENDING_USER_NOTICE_TTL_MS = 10 * 60000;
|
|
74072
74446
|
|
|
@@ -74120,8 +74494,8 @@ var accountScopedThrottleSignals = [
|
|
|
74120
74494
|
function isAccountScopedThrottle(text4) {
|
|
74121
74495
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74122
74496
|
return false;
|
|
74123
|
-
const
|
|
74124
|
-
const lower =
|
|
74497
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
74498
|
+
const lower = sample3.toLowerCase();
|
|
74125
74499
|
return accountScopedThrottleSignals.some((s) => lower.includes(s));
|
|
74126
74500
|
}
|
|
74127
74501
|
function classify429Detail(text4) {
|
|
@@ -74458,8 +74832,8 @@ var litellmV3LimiterSignalPair2 = ["rate limit exceeded for ", "limit type:"];
|
|
|
74458
74832
|
function isLitellmProxyLocal4292(text4) {
|
|
74459
74833
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74460
74834
|
return false;
|
|
74461
|
-
const
|
|
74462
|
-
const lower =
|
|
74835
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
74836
|
+
const lower = sample3.toLowerCase();
|
|
74463
74837
|
if (litellmProxyLocal429Signals2.some((s) => lower.includes(s)))
|
|
74464
74838
|
return true;
|
|
74465
74839
|
return litellmV3LimiterSignalPair2.every((s) => lower.includes(s));
|
|
@@ -74467,14 +74841,14 @@ function isLitellmProxyLocal4292(text4) {
|
|
|
74467
74841
|
function detectModelUnavailable2(stderr) {
|
|
74468
74842
|
if (typeof stderr !== "string" || stderr.length === 0)
|
|
74469
74843
|
return null;
|
|
74470
|
-
const
|
|
74471
|
-
const lower =
|
|
74844
|
+
const sample3 = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
|
|
74845
|
+
const lower = sample3.toLowerCase();
|
|
74472
74846
|
if (transientUpstreamSignals2.some((s) => lower.includes(s))) {
|
|
74473
|
-
const resetAt = parseResetTime2(
|
|
74847
|
+
const resetAt = parseResetTime2(sample3);
|
|
74474
74848
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74475
74849
|
}
|
|
74476
|
-
if (isLitellmProxyLocal4292(
|
|
74477
|
-
const resetAt = parseResetTime2(
|
|
74850
|
+
if (isLitellmProxyLocal4292(sample3)) {
|
|
74851
|
+
const resetAt = parseResetTime2(sample3);
|
|
74478
74852
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74479
74853
|
}
|
|
74480
74854
|
const quotaSignals = [
|
|
@@ -74494,7 +74868,7 @@ function detectModelUnavailable2(stderr) {
|
|
|
74494
74868
|
"session cap"
|
|
74495
74869
|
];
|
|
74496
74870
|
if (quotaSignals.some((s) => lower.includes(s))) {
|
|
74497
|
-
const resetAt = parseResetTime2(
|
|
74871
|
+
const resetAt = parseResetTime2(sample3);
|
|
74498
74872
|
return resetAt !== undefined ? { kind: "quota_exhausted", resetAt, raw: stderr } : { kind: "quota_exhausted", raw: stderr };
|
|
74499
74873
|
}
|
|
74500
74874
|
const overloadSignals = [
|
|
@@ -74514,7 +74888,7 @@ function detectModelUnavailable2(stderr) {
|
|
|
74514
74888
|
" 529 "
|
|
74515
74889
|
];
|
|
74516
74890
|
if (overloadSignals.some((s) => lower.includes(s))) {
|
|
74517
|
-
const resetAt = parseResetTime2(
|
|
74891
|
+
const resetAt = parseResetTime2(sample3);
|
|
74518
74892
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74519
74893
|
}
|
|
74520
74894
|
const networkSignals = [
|
|
@@ -74715,8 +75089,8 @@ var accountScopedThrottleSignals2 = [
|
|
|
74715
75089
|
function isAccountScopedThrottle2(text4) {
|
|
74716
75090
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74717
75091
|
return false;
|
|
74718
|
-
const
|
|
74719
|
-
const lower =
|
|
75092
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
75093
|
+
const lower = sample3.toLowerCase();
|
|
74720
75094
|
return accountScopedThrottleSignals2.some((s) => lower.includes(s));
|
|
74721
75095
|
}
|
|
74722
75096
|
function classify429Detail2(text4) {
|
|
@@ -75795,8 +76169,8 @@ function resolveYear(parsed, todayY, todayIdx) {
|
|
|
75795
76169
|
}
|
|
75796
76170
|
return best;
|
|
75797
76171
|
}
|
|
75798
|
-
function matchCase(
|
|
75799
|
-
if (
|
|
76172
|
+
function matchCase(sample3, replacement) {
|
|
76173
|
+
if (sample3.length > 0 && sample3[0] === sample3[0].toUpperCase() && sample3[0] !== sample3[0].toLowerCase()) {
|
|
75800
76174
|
return replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
75801
76175
|
}
|
|
75802
76176
|
return replacement;
|
|
@@ -80873,24 +81247,62 @@ function createNarrativeLane(deps) {
|
|
|
80873
81247
|
// inline-keyboard-callbacks.ts
|
|
80874
81248
|
var AGENT_CALLBACK_PREFIX2 = "agent:";
|
|
80875
81249
|
var AGENT_CALLBACK_DATA_MAX2 = 64 - AGENT_CALLBACK_PREFIX2.length;
|
|
81250
|
+
function extractCallbackChatId2(callbackQuery) {
|
|
81251
|
+
const msg = callbackQuery?.message;
|
|
81252
|
+
const chat = msg?.chat;
|
|
81253
|
+
const id = chat?.id;
|
|
81254
|
+
if (typeof id === "number" && Number.isFinite(id))
|
|
81255
|
+
return String(id);
|
|
81256
|
+
if (typeof id === "string" && id !== "")
|
|
81257
|
+
return id;
|
|
81258
|
+
return;
|
|
81259
|
+
}
|
|
81260
|
+
var DEAD_CARD_NOTICE2 = "\u26a0\ufe0f Your tap was applied, but this card could not be updated. " + "The buttons on it are STALE \u2014 tapping them again will not change anything. " + "Scroll down for the outcome, or ask the agent to re-send the card.";
|
|
81261
|
+
async function disarmDeadCard2(ctx, apiCall, scope, log) {
|
|
81262
|
+
try {
|
|
81263
|
+
await apiCall(() => ctx.editMessageReplyMarkup({ reply_markup: { inline_keyboard: [] } }), { ...scope, verb: "editMessageReplyMarkup" });
|
|
81264
|
+
log("finalizeCallback: repaint failed but the keyboard was stripped \u2014 " + `the card shows stale text and is no longer tappable
|
|
81265
|
+
`);
|
|
81266
|
+
return;
|
|
81267
|
+
} catch (err) {
|
|
81268
|
+
log(`finalizeCallback: editMessageReplyMarkup fallback failed: ${err.message}
|
|
81269
|
+
`);
|
|
81270
|
+
}
|
|
81271
|
+
try {
|
|
81272
|
+
await apiCall(() => ctx.reply(DEAD_CARD_NOTICE2, { link_preview_options: { is_disabled: true } }), { ...scope, verb: "sendMessage" });
|
|
81273
|
+
} catch (err) {
|
|
81274
|
+
log(`finalizeCallback: dead-card notice failed: ${err.message}
|
|
81275
|
+
`);
|
|
81276
|
+
}
|
|
81277
|
+
}
|
|
80876
81278
|
async function finalizeCallback2(ctx, opts) {
|
|
80877
81279
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
80878
|
-
|
|
81280
|
+
const apiCall = opts.apiCall;
|
|
81281
|
+
const chatId = extractCallbackChatId2(ctx.callbackQuery);
|
|
81282
|
+
const scope = {
|
|
81283
|
+
...chatId != null ? { chat_id: chatId } : {},
|
|
81284
|
+
priorityClass: "critical"
|
|
81285
|
+
};
|
|
81286
|
+
apiCall(() => ctx.answerCallbackQuery({
|
|
80879
81287
|
text: opts.ackText,
|
|
80880
81288
|
...opts.alert ? { show_alert: true } : {}
|
|
80881
|
-
}).catch((err) => {
|
|
81289
|
+
}), { ...scope, verb: "answerCallbackQuery" }).catch((err) => {
|
|
80882
81290
|
log(`finalizeCallback: answerCallbackQuery failed: ${err.message}
|
|
80883
81291
|
`);
|
|
80884
81292
|
});
|
|
81293
|
+
let repainted = true;
|
|
80885
81294
|
try {
|
|
80886
|
-
await ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
81295
|
+
await apiCall(() => ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
80887
81296
|
reply_markup: { inline_keyboard: [] },
|
|
80888
81297
|
link_preview_options: { is_disabled: true }
|
|
80889
|
-
});
|
|
81298
|
+
}), { ...scope, verb: "editMessageText" });
|
|
80890
81299
|
} catch (err) {
|
|
81300
|
+
repainted = false;
|
|
80891
81301
|
log(`finalizeCallback: editMessageText failed: ${err.message}
|
|
80892
81302
|
`);
|
|
80893
81303
|
}
|
|
81304
|
+
if (!repainted)
|
|
81305
|
+
await disarmDeadCard2(ctx, apiCall, scope, log);
|
|
80894
81306
|
if (opts.synthInbound != null) {
|
|
80895
81307
|
try {
|
|
80896
81308
|
const r = opts.synthInbound();
|
|
@@ -84017,7 +84429,13 @@ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
|
|
|
84017
84429
|
var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
|
|
84018
84430
|
var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
|
|
84019
84431
|
var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
|
|
84020
|
-
|
|
84432
|
+
function hindsightConsolidationLlmMaxConcurrentDefault(globalMaxConcurrent = HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT, retainMaxConcurrent = HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT) {
|
|
84433
|
+
const globalCap = Number.isFinite(globalMaxConcurrent) && globalMaxConcurrent >= 1 ? Math.floor(globalMaxConcurrent) : HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT;
|
|
84434
|
+
const retainCap = Number.isFinite(retainMaxConcurrent) && retainMaxConcurrent >= 0 ? Math.floor(retainMaxConcurrent) : HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT;
|
|
84435
|
+
const headroomBound = Math.max(1, globalCap - 1);
|
|
84436
|
+
return Math.min(headroomBound, Math.max(1, globalCap - retainCap - 1));
|
|
84437
|
+
}
|
|
84438
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = hindsightConsolidationLlmMaxConcurrentDefault();
|
|
84021
84439
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
|
|
84022
84440
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128;
|
|
84023
84441
|
var HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true";
|
|
@@ -84032,6 +84450,8 @@ var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
|
84032
84450
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500;
|
|
84033
84451
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
84034
84452
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
84453
|
+
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
84454
|
+
var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
|
|
84035
84455
|
var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
84036
84456
|
[
|
|
84037
84457
|
"HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
|
|
@@ -84085,6 +84505,14 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
|
84085
84505
|
[
|
|
84086
84506
|
"HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND",
|
|
84087
84507
|
String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND)
|
|
84508
|
+
],
|
|
84509
|
+
[
|
|
84510
|
+
"HINDSIGHT_API_RECENCY_DECAY_FUNCTION",
|
|
84511
|
+
HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION
|
|
84512
|
+
],
|
|
84513
|
+
[
|
|
84514
|
+
"HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
|
|
84515
|
+
String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
|
|
84088
84516
|
]
|
|
84089
84517
|
];
|
|
84090
84518
|
var HINDSIGHT_PERF_DEFAULTS_GPU = [
|
|
@@ -84109,7 +84537,10 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
|
|
|
84109
84537
|
];
|
|
84110
84538
|
var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
84111
84539
|
"HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
|
|
84112
|
-
"HINDSIGHT_CE_DECISIVE_RELATIVE_GAP"
|
|
84540
|
+
"HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
|
|
84541
|
+
"HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
|
|
84542
|
+
"HINDSIGHT_API_WORKER_MAX_SLOTS",
|
|
84543
|
+
"HINDSIGHT_API_WORKER_RETAIN_MAX_SLOTS"
|
|
84113
84544
|
]);
|
|
84114
84545
|
var HINDSIGHT_PERF_ENV_KEYS = new Set([
|
|
84115
84546
|
...[
|
|
@@ -84175,6 +84606,16 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
84175
84606
|
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84176
84607
|
` + ` system's self-reports are not memories.
|
|
84177
84608
|
` + `- Restatements of the user's current request or the task in progress.
|
|
84609
|
+
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
84610
|
+
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
84611
|
+
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
84612
|
+
` + ` fact whose text resembles any of these: "Switchroom fleet is running image
|
|
84613
|
+
` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
|
|
84614
|
+
` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
|
|
84615
|
+
` + ` currently green". If the claim is worth keeping, put the date INSIDE the
|
|
84616
|
+
` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
|
|
84617
|
+
` + ` cannot date it, drop it. An undated one is recalled forever as though it
|
|
84618
|
+
` + ` were still true, which is worse than not remembering it at all.
|
|
84178
84619
|
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84179
84620
|
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84180
84621
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
@@ -84196,18 +84637,108 @@ var SUPERSEDED_RETAIN_MISSIONS = [
|
|
|
84196
84637
|
` + "- Transient state (unread counts, build status, what is running right now) " + "unless the fact is explicitly dated, in which case record it as a dated " + `observation.
|
|
84197
84638
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84198
84639
|
|
|
84199
|
-
` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list."
|
|
84640
|
+
` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list.",
|
|
84641
|
+
`Extract durable facts that will still be true and useful weeks from now: user preferences and standing rules, ongoing projects and recurring commitments, technical and architectural decisions with their rationale, and people/tool relationships. A preference revealed by a request is durable \u2014 record the preference (what the user likes, wants, or always does), not the request itself.
|
|
84642
|
+
` + `
|
|
84643
|
+
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
84644
|
+
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
84645
|
+
` + `the location where some output was written? If yes, drop it \u2014 it is transcript
|
|
84646
|
+
` + `exhaust, not memory.
|
|
84647
|
+
` + `
|
|
84648
|
+
` + `NEVER extract:
|
|
84649
|
+
` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
|
|
84650
|
+
` + ` text resembles any of these: "File created successfully at /path/to/file",
|
|
84651
|
+
` + ` "A background command with ID bctz4yskm is running, and its output will be
|
|
84652
|
+
` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
|
|
84653
|
+
` + ` and is running in the background", "User executed a Bash command to sleep for
|
|
84654
|
+
` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
|
|
84655
|
+
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
84656
|
+
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
84657
|
+
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
84658
|
+
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
84659
|
+
` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
|
|
84660
|
+
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
84661
|
+
` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
|
|
84662
|
+
` + ` reset assistant state").
|
|
84663
|
+
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84664
|
+
` + ` system's self-reports are not memories.
|
|
84665
|
+
` + `- Restatements of the user's current request or the task in progress.
|
|
84666
|
+
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84667
|
+
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84668
|
+
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84669
|
+
` + `
|
|
84670
|
+
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84671
|
+
` + "nothing durable remains, return an empty facts list."
|
|
84672
|
+
];
|
|
84673
|
+
var DEFAULT_OBSERVATIONS_MISSION = `Synthesise durable, standing knowledge about the people, projects, and systems this agent works with: preferences and standing rules, roles and relationships, skills and recurring patterns, technical and operational decisions with their rationale, and the state of long-running work once it lands.
|
|
84674
|
+
` + `
|
|
84675
|
+
` + `The test is durability, not notability: an observation must still be worth reading weeks from now. A single dated event belongs in an observation only when it establishes or changes a standing fact.
|
|
84676
|
+
` + `
|
|
84677
|
+
` + `Do NOT synthesise observations from:
|
|
84678
|
+
` + `- Transcript exhaust \u2014 tool calls and their results, file paths, temp or scratchpad directories, where some output was written, or narration of what an assistant did.
|
|
84679
|
+
` + `- Identifiers with no standing meaning: session, agent, request, batch or command IDs, UUIDs, hashes, error codes.
|
|
84680
|
+
` + `- In-flight process narration \u2014 a task started, paused, or still running. Record the outcome once it lands, not the running state.
|
|
84681
|
+
` + `- The memory system's own errors, retries, backlogs, or internal state.
|
|
84682
|
+
` + `- Transient state (what is running right now, unread counts, build status) unless the fact is explicitly dated, in which case record it as dated.
|
|
84683
|
+
` + `
|
|
84684
|
+
` + "If the new facts contain nothing durable, record nothing rather than synthesising a weak observation.";
|
|
84685
|
+
var SUPERSEDED_OBSERVATIONS_MISSIONS = [
|
|
84686
|
+
"Synthesise the person's wellbeing patterns, motivations, and emotional " + "context \u2014 how habits, setbacks, and encouragement connect over time."
|
|
84200
84687
|
];
|
|
84201
84688
|
var PROFILE_MEMORY_DEFAULTS = {
|
|
84202
84689
|
"health-coach": {
|
|
84203
84690
|
disposition: { skepticism: 2, literalism: 2, empathy: 5 },
|
|
84204
|
-
observations_mission:
|
|
84691
|
+
observations_mission: `You consolidate the memory of a health and fitness coach working with one person. This bank records how that person actually lives and trains.
|
|
84692
|
+
` + `
|
|
84693
|
+
` + `Synthesise into durable observations:
|
|
84694
|
+
` + `- Goals, targets, and the plan currently in force, with the reasoning behind each.
|
|
84695
|
+
` + `- Training, nutrition, sleep, and alcohol patterns as they hold over weeks \u2014 what the person reliably does, not what they did once.
|
|
84696
|
+
` + `- Constraints that shape the plan: injuries, medical guidance, schedule, equipment, foods and sessions they refuse.
|
|
84697
|
+
` + `- Motivations, and what actually helps or backfires when they slip.
|
|
84698
|
+
` + `- Trends in the numbers: direction and range over time, not any single reading.
|
|
84699
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84700
|
+
` + `
|
|
84701
|
+
` + `A single day's log, weight reading, or session is evidence, not an observation. Record one only when it establishes or changes a standing pattern, target, or constraint \u2014 then fold it into the observation for that pattern and say what changed and roughly when.
|
|
84702
|
+
` + `
|
|
84703
|
+
` + `Granularity: one observation per habit, target, constraint, or trend. Aggregate repeated daily evidence into the observation for that pattern rather than creating one per day, and keep training, nutrition, and sleep as separate observations.
|
|
84704
|
+
` + `
|
|
84705
|
+
` + "Word observations as the person's own pattern and framing, never as a verdict on them."
|
|
84205
84706
|
},
|
|
84206
84707
|
"executive-assistant": {
|
|
84207
|
-
disposition: { skepticism: 4, literalism: 4, empathy: 3 }
|
|
84708
|
+
disposition: { skepticism: 4, literalism: 4, empathy: 3 },
|
|
84709
|
+
observations_mission: `You consolidate the memory of an executive assistant working for one person. This bank records that person's commitments, people, and standing arrangements.
|
|
84710
|
+
` + `
|
|
84711
|
+
` + `Synthesise into durable observations:
|
|
84712
|
+
` + `- Standing rules and preferences: how they want things scheduled, written, and filed, and when they want to be interrupted.
|
|
84713
|
+
` + `- People and organisations, and the relationship: who they are, what they are involved in, how to reach them.
|
|
84714
|
+
` + `- Recurring commitments and routines, and the constraints around them.
|
|
84715
|
+
` + `- Obligations and their state: what was promised to whom, the deadline, and what is still outstanding.
|
|
84716
|
+
` + `- Decisions made and decisions deferred, with the reasoning and the trade accepted.
|
|
84717
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84718
|
+
` + `
|
|
84719
|
+
` + `Live commitment state IS durable knowledge here, not ephemeral chatter. An outstanding obligation, a travel window, an unanswered request, a "currently X" arrangement \u2014 these are precisely what this agent must recall later. Keep them, and embed the date inside the observation text so a later reader can judge staleness. Do not drop them as ephemeral.
|
|
84720
|
+
` + `
|
|
84721
|
+
` + `Granularity: one observation per person, arrangement, or obligation. Aggregate repeated mentions into that observation rather than creating siblings; never merge two different people or two different commitments into one.
|
|
84722
|
+
` + `
|
|
84723
|
+
` + "Do not synthesise from transcript exhaust: tool calls and their results, message or event identifiers, or narration of what the assistant did."
|
|
84208
84724
|
},
|
|
84209
84725
|
coding: {
|
|
84210
|
-
disposition: { skepticism: 4, literalism: 5, empathy: 2 }
|
|
84726
|
+
disposition: { skepticism: 4, literalism: 5, empathy: 2 },
|
|
84727
|
+
observations_mission: `You consolidate the memory of a software-engineering agent. This bank records real work on real codebases.
|
|
84728
|
+
` + `
|
|
84729
|
+
` + `Synthesise into durable observations:
|
|
84730
|
+
` + `- Architecture and design decisions, each with its rationale and the trade accepted.
|
|
84731
|
+
` + `- Root causes, with the evidence chain, and negative results \u2014 what was ruled out matters as much as what was found.
|
|
84732
|
+
` + `- How the repository works: build, test, and lint commands, conventions, CI gates, and where things live.
|
|
84733
|
+
` + `- Outcomes of code work: issue and PR numbers, what changed, whether it merged, what review found.
|
|
84734
|
+
` + `- The user's standing rules, preferences, and corrections to this agent's behaviour.
|
|
84735
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84736
|
+
` + `
|
|
84737
|
+
` + `Repository and service state IS durable knowledge here, not ephemeral chatter. Versions, a failing gate, an open PR, a measured number, a "currently X" claim \u2014 these are precisely what this agent must recall later. Keep them, and embed the date inside the observation text so a later reader can judge staleness. Do not drop them as ephemeral.
|
|
84738
|
+
` + `
|
|
84739
|
+
` + `Granularity: one observation per distinct decision, cause, convention, or work item. Aggregate repeated evidence about the same one into that observation rather than creating siblings; never merge two separate decisions into a single summary.
|
|
84740
|
+
` + `
|
|
84741
|
+
` + "Do not synthesise from transcript exhaust: tool calls and their results, scratch paths, session or request identifiers, or narration of what the agent did. Prefer specific and falsifiable \u2014 naming the file, the number, the commit, or the decision beats summarising the topic."
|
|
84211
84742
|
}
|
|
84212
84743
|
};
|
|
84213
84744
|
// ../src/agents/reconcile-default-skills.ts
|
|
@@ -97654,10 +98185,10 @@ function startOutboxSweep(deps) {
|
|
|
97654
98185
|
}
|
|
97655
98186
|
|
|
97656
98187
|
// ../src/build-info.ts
|
|
97657
|
-
var VERSION2 = "0.19.
|
|
97658
|
-
var COMMIT_SHA = "
|
|
97659
|
-
var COMMIT_DATE = "2026-07-
|
|
97660
|
-
var LATEST_PR =
|
|
98188
|
+
var VERSION2 = "0.19.29";
|
|
98189
|
+
var COMMIT_SHA = "c618451c";
|
|
98190
|
+
var COMMIT_DATE = "2026-07-28T17:39:27Z";
|
|
98191
|
+
var LATEST_PR = 3918;
|
|
97661
98192
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
97662
98193
|
|
|
97663
98194
|
// gateway/boot-version.ts
|
|
@@ -105584,7 +106115,9 @@ function surfaceConsolidationLegibility(rec, target) {
|
|
|
105584
106115
|
`);
|
|
105585
106116
|
});
|
|
105586
106117
|
}
|
|
106118
|
+
var noteMcpDependencyFailure = createMcpFailureHook({ agent: AGENT_NAME, emit: emitGatewayOperatorEvent });
|
|
105587
106119
|
function handleSessionEvent2(ev) {
|
|
106120
|
+
noteMcpDependencyFailure(ev);
|
|
105588
106121
|
handleSessionEvent(gatewayStreamRenderDeps(), ev);
|
|
105589
106122
|
}
|
|
105590
106123
|
function gatewayStreamRenderDeps() {
|
|
@@ -109966,6 +110499,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
|
|
|
109966
110499
|
const baseText2 = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg2(sourceMsg.text) : "";
|
|
109967
110500
|
const interimLabel = `\u23F3 **Rule applied for this session** \u2014 ${escapeHtmlForTg2(agentName3)} can ${escapeHtmlForTg2(grantPhrase)} ` + `without asking for now; saving durably in background\u2026`;
|
|
109968
110501
|
await finalizeCallback2(ctx, {
|
|
110502
|
+
apiCall: robustApiCall,
|
|
109969
110503
|
ackText: "Rule applied for this session; saving durably in background\u2026".slice(0, 200),
|
|
109970
110504
|
newText: baseText2 ? `${baseText2}
|
|
109971
110505
|
|
|
@@ -110131,6 +110665,7 @@ ${editLabel}` : editLabel), {
|
|
|
110131
110665
|
|
|
110132
110666
|
${resumeLine}` : htmlLabel;
|
|
110133
110667
|
await finalizeCallback2(ctx, {
|
|
110668
|
+
apiCall: robustApiCall,
|
|
110134
110669
|
ackText: ackText3.slice(0, 200),
|
|
110135
110670
|
newText: baseText ? `${baseText}
|
|
110136
110671
|
|