switchroom 0.19.27 → 0.19.28
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 +5 -2
- package/dist/auth-broker/index.js +129 -8
- package/dist/cli/autoaccept-poll.js +225 -17
- package/dist/cli/notion-write-pretool.mjs +5 -2
- package/dist/cli/switchroom.js +796 -35
- package/dist/host-control/main.js +130 -9
- package/dist/vault/approvals/kernel-server.js +129 -8
- package/dist/vault/broker/server.js +129 -8
- package/package.json +3 -2
- package/profiles/_base/start.sh.hbs +70 -15
- package/telegram-plugin/dist/bridge/bridge.js +1 -0
- package/telegram-plugin/dist/gateway/gateway.js +568 -49
- 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/scripts/drain_pending.py +433 -11
- package/vendor/hindsight-memory/scripts/lib/pending.py +193 -28
- 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_pending_drops.py +817 -8
- package/vendor/hindsight-memory/settings.json +1 -1
- package/vendor/hindsight-memory/tests/test_hooks.py +11 -2
|
@@ -21944,7 +21944,10 @@ var init_schema = __esm(() => {
|
|
|
21944
21944
|
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
21945
|
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
21946
|
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).")
|
|
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)."),
|
|
21948
|
+
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."),
|
|
21949
|
+
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."),
|
|
21950
|
+
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
21951
|
}).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
21952
|
HindsightPerOpLlmSchema = exports_external.object({
|
|
21950
21953
|
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 +21966,7 @@ var init_schema = __esm(() => {
|
|
|
21963
21966
|
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
21967
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
21965
21968
|
}).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).")
|
|
21969
|
+
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), 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
21970
|
});
|
|
21968
21971
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
21969
21972
|
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)."),
|
|
@@ -46601,24 +46604,62 @@ async function applyTapAnnotationEdit(io, text) {
|
|
|
46601
46604
|
}
|
|
46602
46605
|
}
|
|
46603
46606
|
}
|
|
46607
|
+
function extractCallbackChatId(callbackQuery) {
|
|
46608
|
+
const msg = callbackQuery?.message;
|
|
46609
|
+
const chat = msg?.chat;
|
|
46610
|
+
const id = chat?.id;
|
|
46611
|
+
if (typeof id === "number" && Number.isFinite(id))
|
|
46612
|
+
return String(id);
|
|
46613
|
+
if (typeof id === "string" && id !== "")
|
|
46614
|
+
return id;
|
|
46615
|
+
return;
|
|
46616
|
+
}
|
|
46617
|
+
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.";
|
|
46618
|
+
async function disarmDeadCard(ctx, apiCall, scope, log) {
|
|
46619
|
+
try {
|
|
46620
|
+
await apiCall(() => ctx.editMessageReplyMarkup({ reply_markup: { inline_keyboard: [] } }), { ...scope, verb: "editMessageReplyMarkup" });
|
|
46621
|
+
log("finalizeCallback: repaint failed but the keyboard was stripped \u2014 " + `the card shows stale text and is no longer tappable
|
|
46622
|
+
`);
|
|
46623
|
+
return;
|
|
46624
|
+
} catch (err) {
|
|
46625
|
+
log(`finalizeCallback: editMessageReplyMarkup fallback failed: ${err.message}
|
|
46626
|
+
`);
|
|
46627
|
+
}
|
|
46628
|
+
try {
|
|
46629
|
+
await apiCall(() => ctx.reply(DEAD_CARD_NOTICE, { link_preview_options: { is_disabled: true } }), { ...scope, verb: "sendMessage" });
|
|
46630
|
+
} catch (err) {
|
|
46631
|
+
log(`finalizeCallback: dead-card notice failed: ${err.message}
|
|
46632
|
+
`);
|
|
46633
|
+
}
|
|
46634
|
+
}
|
|
46604
46635
|
async function finalizeCallback(ctx, opts) {
|
|
46605
46636
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
46606
|
-
|
|
46637
|
+
const apiCall = opts.apiCall;
|
|
46638
|
+
const chatId = extractCallbackChatId(ctx.callbackQuery);
|
|
46639
|
+
const scope = {
|
|
46640
|
+
...chatId != null ? { chat_id: chatId } : {},
|
|
46641
|
+
priorityClass: "critical"
|
|
46642
|
+
};
|
|
46643
|
+
apiCall(() => ctx.answerCallbackQuery({
|
|
46607
46644
|
text: opts.ackText,
|
|
46608
46645
|
...opts.alert ? { show_alert: true } : {}
|
|
46609
|
-
}).catch((err) => {
|
|
46646
|
+
}), { ...scope, verb: "answerCallbackQuery" }).catch((err) => {
|
|
46610
46647
|
log(`finalizeCallback: answerCallbackQuery failed: ${err.message}
|
|
46611
46648
|
`);
|
|
46612
46649
|
});
|
|
46650
|
+
let repainted = true;
|
|
46613
46651
|
try {
|
|
46614
|
-
await ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
46652
|
+
await apiCall(() => ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
46615
46653
|
reply_markup: { inline_keyboard: [] },
|
|
46616
46654
|
link_preview_options: { is_disabled: true }
|
|
46617
|
-
});
|
|
46655
|
+
}), { ...scope, verb: "editMessageText" });
|
|
46618
46656
|
} catch (err) {
|
|
46657
|
+
repainted = false;
|
|
46619
46658
|
log(`finalizeCallback: editMessageText failed: ${err.message}
|
|
46620
46659
|
`);
|
|
46621
46660
|
}
|
|
46661
|
+
if (!repainted)
|
|
46662
|
+
await disarmDeadCard(ctx, apiCall, scope, log);
|
|
46622
46663
|
if (opts.synthInbound != null) {
|
|
46623
46664
|
try {
|
|
46624
46665
|
const r = opts.synthInbound();
|
|
@@ -50153,6 +50194,7 @@ function createCallbackQueryHandlers(deps) {
|
|
|
50153
50194
|
|
|
50154
50195
|
\u2705 **${escapeHtmlForTg2(agentName)}** granted read access to ` + `\`${keyName}\` for 30 days ` + `(grant \`${id}\`). ` + `Re-run /vault audit to act on remaining denials.`;
|
|
50155
50196
|
await finalizeCallback(ctx, {
|
|
50197
|
+
apiCall: robustApiCall,
|
|
50156
50198
|
ackText: "\u2705 Grant minted",
|
|
50157
50199
|
newText: baseText ? `${baseText}${statusLine}` : statusLine
|
|
50158
50200
|
});
|
|
@@ -50731,6 +50773,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
|
|
|
50731
50773
|
|
|
50732
50774
|
\u270f\ufe0f **Rename mode** \u2014 send the new key name as your next message. ` + `The current proposed key is \`${pending.key}\`.`;
|
|
50733
50775
|
await finalizeCallback(ctx, {
|
|
50776
|
+
apiCall: robustApiCall,
|
|
50734
50777
|
ackText: "Send the new key name as your next message.",
|
|
50735
50778
|
newText: baseText ? `${baseText}${statusLine}` : statusLine
|
|
50736
50779
|
});
|
|
@@ -51129,6 +51172,7 @@ ${keyList}`,
|
|
|
51129
51172
|
const successText = `\u2705 Grant \`${id}\` created. Written to \`~/.switchroom/agents/${escapeHtmlForTg2(state.agent)}/.vault-token\``;
|
|
51130
51173
|
if (msgId != null) {
|
|
51131
51174
|
await finalizeCallback(ctx, {
|
|
51175
|
+
apiCall: robustApiCall,
|
|
51132
51176
|
ackText: "\u2705 Grant created",
|
|
51133
51177
|
newText: successText
|
|
51134
51178
|
});
|
|
@@ -51405,6 +51449,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51405
51449
|
|
|
51406
51450
|
\u2717 _Dismissed by operator._`;
|
|
51407
51451
|
await finalizeCallback(ctx, {
|
|
51452
|
+
apiCall: robustApiCall,
|
|
51408
51453
|
ackText: "Dismissed",
|
|
51409
51454
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status
|
|
51410
51455
|
});
|
|
@@ -51417,6 +51462,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51417
51462
|
|
|
51418
51463
|
\uD83D\uDD04 _**${escapeHtmlForTg2(agent)}** restart requested by operator._`;
|
|
51419
51464
|
await finalizeCallback(ctx, {
|
|
51465
|
+
apiCall: robustApiCall,
|
|
51420
51466
|
ackText: `Restarting ${agent}\u2026`,
|
|
51421
51467
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status
|
|
51422
51468
|
});
|
|
@@ -51433,6 +51479,7 @@ Reply \`rename NEW_NAME\` to relabel.`), { reply_markup: { inline_keyboard: [] }
|
|
|
51433
51479
|
|
|
51434
51480
|
\uD83D\uDD10 _Reauth started for **${escapeHtmlForTg2(agent)}** \u2014 follow the login URL below._`;
|
|
51435
51481
|
await finalizeCallback(ctx, {
|
|
51482
|
+
apiCall: robustApiCall,
|
|
51436
51483
|
ackText: `Starting reauth for ${agent}\u2026`,
|
|
51437
51484
|
newText: sourceMsgText ? `${sourceMsgText}${status}` : status,
|
|
51438
51485
|
synthInbound: async () => {
|
|
@@ -63318,6 +63365,7 @@ var EDIT_FLOOD_FUSE_DEFAULTS = {
|
|
|
63318
63365
|
cosmeticPerChatMaxPerWindow: 6,
|
|
63319
63366
|
perChatTotalMaxPerWindow: 20,
|
|
63320
63367
|
perChatReplyReserve: 8,
|
|
63368
|
+
perChatCriticalMinPerWindow: 3,
|
|
63321
63369
|
perTokenMaxPerWindow: 25,
|
|
63322
63370
|
perTokenWindowMs: 1000,
|
|
63323
63371
|
chatActionMaxDeferMs: 3000,
|
|
@@ -63335,6 +63383,12 @@ function envInt(raw) {
|
|
|
63335
63383
|
const n = Number(raw);
|
|
63336
63384
|
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;
|
|
63337
63385
|
}
|
|
63386
|
+
function envFactor(raw) {
|
|
63387
|
+
if (raw == null || raw.trim() === "")
|
|
63388
|
+
return;
|
|
63389
|
+
const n = Number(raw);
|
|
63390
|
+
return Number.isFinite(n) && n > 0 && n <= 1 ? n : undefined;
|
|
63391
|
+
}
|
|
63338
63392
|
function editFloodFuseConfigFromEnv(env) {
|
|
63339
63393
|
const cfg = { enabled: env.SWITCHROOM_EDIT_FUSE !== "0" };
|
|
63340
63394
|
const assign = (k, v) => {
|
|
@@ -63345,8 +63399,11 @@ function editFloodFuseConfigFromEnv(env) {
|
|
|
63345
63399
|
assign("cosmeticPerChatMaxPerWindow", envInt(env.SWITCHROOM_FEED_EDIT_MAX_PER_CHAT_PER_MIN));
|
|
63346
63400
|
assign("perChatTotalMaxPerWindow", envInt(env.SWITCHROOM_CHAT_TOTAL_MAX_PER_MIN));
|
|
63347
63401
|
assign("perChatReplyReserve", envInt(env.SWITCHROOM_CHAT_REPLY_RESERVE));
|
|
63402
|
+
assign("perChatCriticalMinPerWindow", envInt(env.SWITCHROOM_CHAT_CRITICAL_MIN_PER_MIN));
|
|
63348
63403
|
assign("perTokenMaxPerWindow", envInt(env.SWITCHROOM_TOKEN_MAX_PER_SEC));
|
|
63349
63404
|
assign("maxDeferMs", envInt(env.SWITCHROOM_EDIT_FUSE_MAX_DEFER_MS));
|
|
63405
|
+
assign("maxTightenLevel", envInt(env.SWITCHROOM_EDIT_FUSE_MAX_TIGHTEN_LEVEL));
|
|
63406
|
+
assign("tightenFactor", envFactor(env.SWITCHROOM_EDIT_FUSE_TIGHTEN_FACTOR));
|
|
63350
63407
|
const stateDir = env.TELEGRAM_STATE_DIR;
|
|
63351
63408
|
if (stateDir != null && stateDir !== "") {
|
|
63352
63409
|
cfg.floodWaitRemainingMs = makeFloodWaitProbe(floodStatePath(stateDir));
|
|
@@ -63367,6 +63424,8 @@ function createEditFloodFuse(config = {}) {
|
|
|
63367
63424
|
const perChatTotalMax = config.perChatTotalMaxPerWindow ?? D.perChatTotalMaxPerWindow;
|
|
63368
63425
|
const perChatWindowMs = config.perChatWindowMs ?? D.perChatWindowMs;
|
|
63369
63426
|
const perChatReplyReserve = Math.min(Math.max(0, config.perChatReplyReserve ?? D.perChatReplyReserve), Math.max(0, perChatTotalMax - 1));
|
|
63427
|
+
const replyReserveFraction = perChatTotalMax > 0 ? perChatReplyReserve / perChatTotalMax : 0;
|
|
63428
|
+
const perChatCriticalMin = Math.max(0, config.perChatCriticalMinPerWindow ?? D.perChatCriticalMinPerWindow);
|
|
63370
63429
|
const perTokenMax = Math.max(1, config.perTokenMaxPerWindow ?? D.perTokenMaxPerWindow);
|
|
63371
63430
|
const perTokenWindowMs = Math.max(1, config.perTokenWindowMs ?? D.perTokenWindowMs);
|
|
63372
63431
|
const tokenKey = `g:${config.botScopeKey ?? "unknown"}`;
|
|
@@ -63412,8 +63471,10 @@ function createEditFloodFuse(config = {}) {
|
|
|
63412
63471
|
tightenLevel--;
|
|
63413
63472
|
tightenedUntil = tightenLevel > 0 ? tightenedUntil + tightenMs : 0;
|
|
63414
63473
|
}
|
|
63415
|
-
|
|
63416
|
-
|
|
63474
|
+
const remainingMs = persistedFloodRemainingMs(now);
|
|
63475
|
+
if (remainingMs > 0) {
|
|
63476
|
+
return Math.max(tightenLevel, Math.min(maxTightenLevel, tightenStepFor(remainingMs / 1000)));
|
|
63477
|
+
}
|
|
63417
63478
|
return tightenLevel;
|
|
63418
63479
|
}
|
|
63419
63480
|
function isTightened(now) {
|
|
@@ -63425,6 +63486,24 @@ function createEditFloodFuse(config = {}) {
|
|
|
63425
63486
|
return base;
|
|
63426
63487
|
return Math.max(1, Math.floor(base * Math.pow(tightenFactor, level)));
|
|
63427
63488
|
}
|
|
63489
|
+
function classCeiling(base, cls, now) {
|
|
63490
|
+
const eff = ceiling(base, now);
|
|
63491
|
+
if (cls !== "critical")
|
|
63492
|
+
return eff;
|
|
63493
|
+
return Math.max(eff, Math.min(base, perChatCriticalMin));
|
|
63494
|
+
}
|
|
63495
|
+
function cosmeticTotalMax(now) {
|
|
63496
|
+
const eff = ceiling(perChatTotalMax, now);
|
|
63497
|
+
if (replyReserveFraction <= 0)
|
|
63498
|
+
return eff;
|
|
63499
|
+
const reserve = Math.min(eff, Math.max(1, Math.round(eff * replyReserveFraction)));
|
|
63500
|
+
return Math.max(0, eff - reserve);
|
|
63501
|
+
}
|
|
63502
|
+
function totalMaxFor(cls) {
|
|
63503
|
+
if (cls === "cosmetic")
|
|
63504
|
+
return cosmeticTotalMax;
|
|
63505
|
+
return (now) => classCeiling(perChatTotalMax, cls, now);
|
|
63506
|
+
}
|
|
63428
63507
|
function win(key) {
|
|
63429
63508
|
let w = windows.get(key);
|
|
63430
63509
|
if (w === undefined) {
|
|
@@ -63454,6 +63533,9 @@ function createEditFloodFuse(config = {}) {
|
|
|
63454
63533
|
}
|
|
63455
63534
|
function waitFor(w, now, windowMs, max) {
|
|
63456
63535
|
prune(w, now, windowMs);
|
|
63536
|
+
if (max <= 0) {
|
|
63537
|
+
return w.ts.length > 0 ? Math.max(1, w.ts[0] + windowMs - now) : Math.max(1, windowMs);
|
|
63538
|
+
}
|
|
63457
63539
|
if (w.ts.length < max)
|
|
63458
63540
|
return 0;
|
|
63459
63541
|
return Math.max(1, w.ts[0] + windowMs - now);
|
|
@@ -63502,12 +63584,12 @@ function createEditFloodFuse(config = {}) {
|
|
|
63502
63584
|
if (i >= 0)
|
|
63503
63585
|
w.ts.splice(i, 1);
|
|
63504
63586
|
}
|
|
63505
|
-
async function awaitRoom(key, windowMs,
|
|
63587
|
+
async function awaitRoom(key, windowMs, maxFor, method, mode, cls, dropGuard, deadline, lateReleaseKey) {
|
|
63506
63588
|
const w = win(key);
|
|
63507
63589
|
let counted = false;
|
|
63508
63590
|
for (;; ) {
|
|
63509
63591
|
const now = clock.now();
|
|
63510
|
-
const wait = waitFor(w, now, windowMs,
|
|
63592
|
+
const wait = waitFor(w, now, windowMs, maxFor(now));
|
|
63511
63593
|
if (wait === 0) {
|
|
63512
63594
|
w.ts.push(now);
|
|
63513
63595
|
return now;
|
|
@@ -63570,15 +63652,16 @@ function createEditFloodFuse(config = {}) {
|
|
|
63570
63652
|
evict(now);
|
|
63571
63653
|
const deadline = now + maxDeferMs;
|
|
63572
63654
|
const cls = currentOutboundClass() ?? (isChatAction ? "cosmetic" : defaultOutboundClass(isEdit));
|
|
63655
|
+
const tokenMaxFor = (t) => ceiling(perTokenMax, t);
|
|
63573
63656
|
if (chat == null) {
|
|
63574
63657
|
counters.chatless++;
|
|
63575
|
-
await awaitRoom(tokenKey, perTokenWindowMs,
|
|
63658
|
+
await awaitRoom(tokenKey, perTokenWindowMs, tokenMaxFor, method, "release", cls, undefined, deadline);
|
|
63576
63659
|
return runObserved(next);
|
|
63577
63660
|
}
|
|
63578
63661
|
const totalKey = `t:${chat}`;
|
|
63579
|
-
const
|
|
63662
|
+
const chatTotalMax = totalMaxFor(cls);
|
|
63580
63663
|
const passToken = async () => {
|
|
63581
|
-
await awaitRoom(tokenKey, perTokenWindowMs,
|
|
63664
|
+
await awaitRoom(tokenKey, perTokenWindowMs, tokenMaxFor, method, "release", cls, undefined, deadline);
|
|
63582
63665
|
return runObserved(next);
|
|
63583
63666
|
};
|
|
63584
63667
|
if (isEdit && msg != null) {
|
|
@@ -63586,7 +63669,7 @@ function createEditFloodFuse(config = {}) {
|
|
|
63586
63669
|
const mw = win(msgKey);
|
|
63587
63670
|
mw.inflight++;
|
|
63588
63671
|
try {
|
|
63589
|
-
const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? cosmeticPerMessageMax : perMessageMax, method, "supersede", cls, undefined, deadline);
|
|
63672
|
+
const msgSlot = await awaitRoom(msgKey, perMessageWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerMessageMax, t) : (t) => classCeiling(perMessageMax, cls, t), method, "supersede", cls, undefined, deadline);
|
|
63590
63673
|
if (msgSlot === null)
|
|
63591
63674
|
return DROPPED_RESULT;
|
|
63592
63675
|
const dropGuard = () => mw.inflight > 1;
|
|
@@ -63597,13 +63680,13 @@ function createEditFloodFuse(config = {}) {
|
|
|
63597
63680
|
unreserve(k, at);
|
|
63598
63681
|
};
|
|
63599
63682
|
const chatKey2 = `ce:${chat}`;
|
|
63600
|
-
const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? cosmeticPerChatMax : perChatEditMax, method, "drop", cls, dropGuard, deadline, lateKey);
|
|
63683
|
+
const chatSlot = await awaitRoom(chatKey2, perChatWindowMs, cls === "cosmetic" ? (t) => ceiling(cosmeticPerChatMax, t) : (t) => classCeiling(perChatEditMax, cls, t), method, "drop", cls, dropGuard, deadline, lateKey);
|
|
63601
63684
|
if (chatSlot === null) {
|
|
63602
63685
|
giveBack();
|
|
63603
63686
|
return DROPPED_RESULT;
|
|
63604
63687
|
}
|
|
63605
63688
|
reserved.push([chatKey2, chatSlot]);
|
|
63606
|
-
const totalSlot = await awaitRoom(totalKey, perChatWindowMs,
|
|
63689
|
+
const totalSlot = await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, cls === "cosmetic" ? "drop" : "release", cls, dropGuard, deadline, lateKey);
|
|
63607
63690
|
if (totalSlot === null) {
|
|
63608
63691
|
giveBack();
|
|
63609
63692
|
return DROPPED_RESULT;
|
|
@@ -63614,19 +63697,19 @@ function createEditFloodFuse(config = {}) {
|
|
|
63614
63697
|
}
|
|
63615
63698
|
}
|
|
63616
63699
|
if (isSend) {
|
|
63617
|
-
await awaitRoom(`cs:${chat}`, perChatWindowMs, perChatSendMax, method, "release", cls, undefined, deadline);
|
|
63618
|
-
await awaitRoom(totalKey, perChatWindowMs,
|
|
63700
|
+
await awaitRoom(`cs:${chat}`, perChatWindowMs, (t) => classCeiling(perChatSendMax, cls, t), method, "release", cls, undefined, deadline);
|
|
63701
|
+
await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "release", cls, undefined, deadline);
|
|
63619
63702
|
return passToken();
|
|
63620
63703
|
}
|
|
63621
63704
|
counters.meteredByDefault++;
|
|
63622
63705
|
if (isChatAction) {
|
|
63623
63706
|
const actionDeadline = Math.min(deadline, now + chatActionMaxDeferMs);
|
|
63624
|
-
const slot = await awaitRoom(totalKey, perChatWindowMs,
|
|
63707
|
+
const slot = await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "drop", cls, undefined, actionDeadline);
|
|
63625
63708
|
if (slot === null)
|
|
63626
63709
|
return DROPPED_RESULT;
|
|
63627
63710
|
return passToken();
|
|
63628
63711
|
}
|
|
63629
|
-
await awaitRoom(totalKey, perChatWindowMs,
|
|
63712
|
+
await awaitRoom(totalKey, perChatWindowMs, chatTotalMax, method, "release", cls, undefined, deadline);
|
|
63630
63713
|
return passToken();
|
|
63631
63714
|
}
|
|
63632
63715
|
async function runObserved(next) {
|
|
@@ -63657,6 +63740,8 @@ function createEditFloodFuse(config = {}) {
|
|
|
63657
63740
|
perMessageCeiling: ceiling(perMessageMax, now),
|
|
63658
63741
|
cosmeticPerMessageCeiling: ceiling(cosmeticPerMessageMax, now),
|
|
63659
63742
|
cosmeticPerChatCeiling: ceiling(cosmeticPerChatMax, now),
|
|
63743
|
+
cosmeticPerChatTotalCeiling: cosmeticTotalMax(now),
|
|
63744
|
+
criticalPerChatTotalCeiling: classCeiling(perChatTotalMax, "critical", now),
|
|
63660
63745
|
meteredByDefault: counters.meteredByDefault,
|
|
63661
63746
|
chatless: counters.chatless,
|
|
63662
63747
|
perTokenCeiling: ceiling(perTokenMax, now),
|
|
@@ -70869,6 +70954,7 @@ var OPERATOR_ACTIONABLE_KINDS = new Set([
|
|
|
70869
70954
|
"credentials-invalid",
|
|
70870
70955
|
"credit-exhausted",
|
|
70871
70956
|
"provider-credit-exhausted",
|
|
70957
|
+
"mcp-dependency-blocked",
|
|
70872
70958
|
"proxy-misconfig"
|
|
70873
70959
|
]);
|
|
70874
70960
|
|
|
@@ -73898,6 +73984,19 @@ function renderOperatorEvent(ev) {
|
|
|
73898
73984
|
}
|
|
73899
73985
|
};
|
|
73900
73986
|
}
|
|
73987
|
+
case "mcp-dependency-blocked":
|
|
73988
|
+
return {
|
|
73989
|
+
text: [
|
|
73990
|
+
`\uD83D\uDD0C **Paid dependency blocked**`,
|
|
73991
|
+
stripRawErrorBytes(ev.detail)
|
|
73992
|
+
].filter(Boolean).join(`
|
|
73993
|
+
`),
|
|
73994
|
+
keyboard: {
|
|
73995
|
+
inline_keyboard: [
|
|
73996
|
+
[{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
73997
|
+
]
|
|
73998
|
+
}
|
|
73999
|
+
};
|
|
73901
74000
|
case "quota-exhausted":
|
|
73902
74001
|
return {
|
|
73903
74002
|
text: [
|
|
@@ -74049,6 +74148,7 @@ var OPERATOR_ACTIONABLE_KINDS2 = new Set([
|
|
|
74049
74148
|
"credentials-invalid",
|
|
74050
74149
|
"credit-exhausted",
|
|
74051
74150
|
"provider-credit-exhausted",
|
|
74151
|
+
"mcp-dependency-blocked",
|
|
74052
74152
|
"proxy-misconfig"
|
|
74053
74153
|
]);
|
|
74054
74154
|
function isOperatorActionableKind(kind) {
|
|
@@ -74067,6 +74167,265 @@ function renderUserFacingFailureNotice() {
|
|
|
74067
74167
|
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
74168
|
}
|
|
74069
74169
|
|
|
74170
|
+
// mcp-credential-failure.ts
|
|
74171
|
+
var MCP_SERVER_VAULT_KEYS = {
|
|
74172
|
+
eraser: { label: "Eraser", vaultKey: "eraser/api-key", consoleUrl: "https://app.eraser.io/settings/api" },
|
|
74173
|
+
brevo: { label: "Brevo", vaultKey: "brevo/api-key", consoleUrl: "https://app.brevo.com/settings/keys/api" },
|
|
74174
|
+
postiz: { label: "Postiz", vaultKey: "postiz/api-key", consoleUrl: "https://platform.postiz.com/settings" },
|
|
74175
|
+
"meta-ads": { label: "Meta Ads", vaultKey: "meta-ads/access-token", consoleUrl: "https://business.facebook.com/settings" },
|
|
74176
|
+
"google-ads": { label: "Google Ads", vaultKey: "google-ads/developer-token", consoleUrl: "https://ads.google.com/aw/apicenter" },
|
|
74177
|
+
cloudflare: { label: "Cloudflare", vaultKey: "cloudflare/api-token", consoleUrl: "https://dash.cloudflare.com/profile/api-tokens" },
|
|
74178
|
+
context7: { label: "Context7", vaultKey: "context7/api-key", consoleUrl: "https://context7.com/dashboard" }
|
|
74179
|
+
};
|
|
74180
|
+
function parseMcpServerFromToolName(toolName) {
|
|
74181
|
+
if (typeof toolName !== "string")
|
|
74182
|
+
return null;
|
|
74183
|
+
if (!toolName.startsWith("mcp__"))
|
|
74184
|
+
return null;
|
|
74185
|
+
const rest = toolName.slice("mcp__".length);
|
|
74186
|
+
const end = rest.indexOf("__");
|
|
74187
|
+
const server = end === -1 ? rest : rest.slice(0, end);
|
|
74188
|
+
return server.length > 0 ? server : null;
|
|
74189
|
+
}
|
|
74190
|
+
function describeMcpServer(server) {
|
|
74191
|
+
const direct = MCP_SERVER_VAULT_KEYS[server];
|
|
74192
|
+
if (direct != null)
|
|
74193
|
+
return direct;
|
|
74194
|
+
const entry = PROVIDER_CREDIT_REGISTRY.find((e) => e.id === server);
|
|
74195
|
+
if (entry != null) {
|
|
74196
|
+
return { label: entry.label, vaultKey: entry.vaultKey, consoleUrl: entry.consoleUrl };
|
|
74197
|
+
}
|
|
74198
|
+
return null;
|
|
74199
|
+
}
|
|
74200
|
+
function isAlertingClass(c) {
|
|
74201
|
+
return c !== "ordinary";
|
|
74202
|
+
}
|
|
74203
|
+
var CLASS_RULES = [
|
|
74204
|
+
{
|
|
74205
|
+
cls: "credential",
|
|
74206
|
+
statuses: [401, 403],
|
|
74207
|
+
signals: [
|
|
74208
|
+
"invalid api key",
|
|
74209
|
+
"invalid_api_key",
|
|
74210
|
+
"incorrect api key",
|
|
74211
|
+
"invalid authentication",
|
|
74212
|
+
"authentication_error",
|
|
74213
|
+
"authentication failed",
|
|
74214
|
+
"unauthorized",
|
|
74215
|
+
"unauthorised",
|
|
74216
|
+
"forbidden",
|
|
74217
|
+
"api key not found",
|
|
74218
|
+
"no api key provided",
|
|
74219
|
+
"missing api key",
|
|
74220
|
+
"api key expired",
|
|
74221
|
+
"key has been disabled",
|
|
74222
|
+
"key has been revoked",
|
|
74223
|
+
"key is disabled",
|
|
74224
|
+
"account has been blocked",
|
|
74225
|
+
"account is blocked",
|
|
74226
|
+
"account suspended",
|
|
74227
|
+
"permission_denied",
|
|
74228
|
+
"token expired",
|
|
74229
|
+
"invalid token"
|
|
74230
|
+
]
|
|
74231
|
+
},
|
|
74232
|
+
{
|
|
74233
|
+
cls: "quota",
|
|
74234
|
+
statuses: [],
|
|
74235
|
+
signals: [
|
|
74236
|
+
"quota exceeded",
|
|
74237
|
+
"quota_exceeded",
|
|
74238
|
+
"over quota",
|
|
74239
|
+
"usage limit reached",
|
|
74240
|
+
"usage limit exceeded",
|
|
74241
|
+
"monthly limit",
|
|
74242
|
+
"monthly quota",
|
|
74243
|
+
"plan limit",
|
|
74244
|
+
"plan_limit",
|
|
74245
|
+
"limit reached for your plan",
|
|
74246
|
+
"upgrade your plan",
|
|
74247
|
+
"spending limit"
|
|
74248
|
+
]
|
|
74249
|
+
}
|
|
74250
|
+
];
|
|
74251
|
+
var TRANSIENT_SIGNALS = [
|
|
74252
|
+
"rate limit",
|
|
74253
|
+
"rate_limit",
|
|
74254
|
+
"too many requests",
|
|
74255
|
+
"retry after",
|
|
74256
|
+
"retry-after",
|
|
74257
|
+
"slow down",
|
|
74258
|
+
"timeout",
|
|
74259
|
+
"timed out",
|
|
74260
|
+
"etimedout",
|
|
74261
|
+
"econnreset",
|
|
74262
|
+
"econnrefused",
|
|
74263
|
+
"socket hang up",
|
|
74264
|
+
"network error",
|
|
74265
|
+
"service unavailable",
|
|
74266
|
+
"bad gateway",
|
|
74267
|
+
"temporarily unavailable"
|
|
74268
|
+
];
|
|
74269
|
+
var MAX_SCAN_CHARS2 = 16384;
|
|
74270
|
+
function sample2(text4) {
|
|
74271
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
74272
|
+
return "";
|
|
74273
|
+
return (text4.length > MAX_SCAN_CHARS2 ? text4.slice(0, MAX_SCAN_CHARS2) : text4).toLowerCase();
|
|
74274
|
+
}
|
|
74275
|
+
function extractHttpStatus(text4) {
|
|
74276
|
+
const lower = sample2(text4);
|
|
74277
|
+
if (lower.length === 0)
|
|
74278
|
+
return null;
|
|
74279
|
+
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);
|
|
74280
|
+
if (m == null)
|
|
74281
|
+
return null;
|
|
74282
|
+
const n = Number(m[1]);
|
|
74283
|
+
return Number.isFinite(n) ? n : null;
|
|
74284
|
+
}
|
|
74285
|
+
function classifyMcpFailure(text4, status) {
|
|
74286
|
+
const lower = sample2(text4);
|
|
74287
|
+
const httpStatus = typeof status === "number" ? status : extractHttpStatus(text4);
|
|
74288
|
+
if (isProviderCreditStatus(httpStatus) || hasCreditExhaustionWording(lower))
|
|
74289
|
+
return "credit";
|
|
74290
|
+
for (const rule of CLASS_RULES) {
|
|
74291
|
+
if (rule.signals.some((s) => lower.includes(s)))
|
|
74292
|
+
return rule.cls;
|
|
74293
|
+
}
|
|
74294
|
+
if (TRANSIENT_SIGNALS.some((s) => lower.includes(s)))
|
|
74295
|
+
return "ordinary";
|
|
74296
|
+
for (const rule of CLASS_RULES) {
|
|
74297
|
+
if (httpStatus != null && rule.statuses.includes(httpStatus))
|
|
74298
|
+
return rule.cls;
|
|
74299
|
+
}
|
|
74300
|
+
return "ordinary";
|
|
74301
|
+
}
|
|
74302
|
+
var RENOTIFY_MS = 6 * 60 * 60 * 1000;
|
|
74303
|
+
|
|
74304
|
+
class McpFailureLedger {
|
|
74305
|
+
renotifyMs;
|
|
74306
|
+
rows = new Map;
|
|
74307
|
+
constructor(renotifyMs = RENOTIFY_MS) {
|
|
74308
|
+
this.renotifyMs = renotifyMs;
|
|
74309
|
+
}
|
|
74310
|
+
note(input) {
|
|
74311
|
+
if (!isAlertingClass(input.cls))
|
|
74312
|
+
return null;
|
|
74313
|
+
const cls = input.cls;
|
|
74314
|
+
const key = `${input.server}::${cls}`;
|
|
74315
|
+
const row = this.rows.get(key) ?? { agents: new Set, occurrences: 0, lastAlertAt: -Infinity };
|
|
74316
|
+
row.agents.add(input.agent);
|
|
74317
|
+
row.occurrences += 1;
|
|
74318
|
+
this.rows.set(key, row);
|
|
74319
|
+
const due = input.now - row.lastAlertAt >= this.renotifyMs;
|
|
74320
|
+
if (!due)
|
|
74321
|
+
return null;
|
|
74322
|
+
const renotify = Number.isFinite(row.lastAlertAt);
|
|
74323
|
+
const meta = describeMcpServer(input.server);
|
|
74324
|
+
const alert = {
|
|
74325
|
+
server: input.server,
|
|
74326
|
+
label: meta?.label ?? input.server,
|
|
74327
|
+
vaultKey: meta?.vaultKey ?? `${input.server}/api-key`,
|
|
74328
|
+
consoleUrl: meta?.consoleUrl ?? "",
|
|
74329
|
+
cls,
|
|
74330
|
+
agents: [...row.agents].sort(),
|
|
74331
|
+
occurrences: row.occurrences,
|
|
74332
|
+
renotify
|
|
74333
|
+
};
|
|
74334
|
+
row.lastAlertAt = input.now;
|
|
74335
|
+
row.agents = new Set;
|
|
74336
|
+
row.occurrences = 0;
|
|
74337
|
+
return alert;
|
|
74338
|
+
}
|
|
74339
|
+
size() {
|
|
74340
|
+
return this.rows.size;
|
|
74341
|
+
}
|
|
74342
|
+
}
|
|
74343
|
+
var PENDING_TOOL_NAMES_MAX = 512;
|
|
74344
|
+
|
|
74345
|
+
class McpFailureWatcher {
|
|
74346
|
+
pending = new Map;
|
|
74347
|
+
ledger;
|
|
74348
|
+
constructor(renotifyMs = RENOTIFY_MS) {
|
|
74349
|
+
this.ledger = new McpFailureLedger(renotifyMs);
|
|
74350
|
+
}
|
|
74351
|
+
onToolUse(toolUseId, toolName) {
|
|
74352
|
+
if (typeof toolUseId !== "string" || toolUseId.length === 0)
|
|
74353
|
+
return;
|
|
74354
|
+
if (parseMcpServerFromToolName(toolName) == null)
|
|
74355
|
+
return;
|
|
74356
|
+
if (this.pending.size >= PENDING_TOOL_NAMES_MAX) {
|
|
74357
|
+
const oldest = this.pending.keys().next();
|
|
74358
|
+
if (!oldest.done)
|
|
74359
|
+
this.pending.delete(oldest.value);
|
|
74360
|
+
}
|
|
74361
|
+
this.pending.set(toolUseId, toolName);
|
|
74362
|
+
}
|
|
74363
|
+
onToolResult(input) {
|
|
74364
|
+
const id = typeof input.toolUseId === "string" ? input.toolUseId : "";
|
|
74365
|
+
const toolName = id.length > 0 ? this.pending.get(id) : undefined;
|
|
74366
|
+
if (id.length > 0)
|
|
74367
|
+
this.pending.delete(id);
|
|
74368
|
+
if (input.isError !== true)
|
|
74369
|
+
return null;
|
|
74370
|
+
if (toolName == null)
|
|
74371
|
+
return null;
|
|
74372
|
+
const server = parseMcpServerFromToolName(toolName);
|
|
74373
|
+
if (server == null)
|
|
74374
|
+
return null;
|
|
74375
|
+
const cls = classifyMcpFailure(input.errorText);
|
|
74376
|
+
return this.ledger.note({ server, agent: input.agent, cls, now: input.now });
|
|
74377
|
+
}
|
|
74378
|
+
}
|
|
74379
|
+
var CLASS_HEADLINE = {
|
|
74380
|
+
credit: "is out of credit",
|
|
74381
|
+
credential: "key is being rejected",
|
|
74382
|
+
quota: "has hit its usage quota"
|
|
74383
|
+
};
|
|
74384
|
+
var CLASS_ACTION = {
|
|
74385
|
+
credit: "Top up the balance",
|
|
74386
|
+
credential: "Re-issue the key and update the vault entry",
|
|
74387
|
+
quota: "Raise the plan limit or wait for the quota to reset"
|
|
74388
|
+
};
|
|
74389
|
+
function renderMcpFailureDetail(alert) {
|
|
74390
|
+
const agents = alert.agents.length > 0 ? alert.agents.map((a) => a.replace(/[^A-Za-z0-9._-]/g, "")).filter(Boolean).join(", ") : "unknown agent";
|
|
74391
|
+
const console_ = alert.consoleUrl.length > 0 ? ` \u2192 ${alert.consoleUrl}` : "";
|
|
74392
|
+
const repeat = alert.renotify ? " (still failing)" : "";
|
|
74393
|
+
const times = alert.occurrences === 1 ? "1 failure" : `${alert.occurrences} failures`;
|
|
74394
|
+
return `${alert.label} ${CLASS_HEADLINE[alert.cls]}${repeat} \u2014 ${times}, affecting: ${agents}. ` + `${CLASS_ACTION[alert.cls]}. Vault key: \`${alert.vaultKey}\`${console_}`;
|
|
74395
|
+
}
|
|
74396
|
+
|
|
74397
|
+
// gateway/mcp-failure-hook.ts
|
|
74398
|
+
function createMcpFailureHook(deps) {
|
|
74399
|
+
const watcher = new McpFailureWatcher;
|
|
74400
|
+
const clock = deps.now ?? (() => Date.now());
|
|
74401
|
+
return function noteMcpDependencyFailure(ev) {
|
|
74402
|
+
try {
|
|
74403
|
+
if (ev.kind === "tool_use") {
|
|
74404
|
+
watcher.onToolUse(ev.toolUseId, ev.toolName);
|
|
74405
|
+
return;
|
|
74406
|
+
}
|
|
74407
|
+
if (ev.kind !== "tool_result")
|
|
74408
|
+
return;
|
|
74409
|
+
const alert = watcher.onToolResult({
|
|
74410
|
+
toolUseId: ev.toolUseId,
|
|
74411
|
+
isError: ev.isError,
|
|
74412
|
+
errorText: ev.errorText,
|
|
74413
|
+
agent: deps.agent,
|
|
74414
|
+
now: clock()
|
|
74415
|
+
});
|
|
74416
|
+
if (alert == null)
|
|
74417
|
+
return;
|
|
74418
|
+
deps.emit({
|
|
74419
|
+
kind: "mcp-dependency-blocked",
|
|
74420
|
+
agent: deps.agent,
|
|
74421
|
+
detail: renderMcpFailureDetail(alert),
|
|
74422
|
+
suggestedActions: [],
|
|
74423
|
+
firstSeenAt: new Date(clock())
|
|
74424
|
+
});
|
|
74425
|
+
} catch {}
|
|
74426
|
+
};
|
|
74427
|
+
}
|
|
74428
|
+
|
|
74070
74429
|
// pending-user-notice.ts
|
|
74071
74430
|
var PENDING_USER_NOTICE_TTL_MS = 10 * 60000;
|
|
74072
74431
|
|
|
@@ -74120,8 +74479,8 @@ var accountScopedThrottleSignals = [
|
|
|
74120
74479
|
function isAccountScopedThrottle(text4) {
|
|
74121
74480
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74122
74481
|
return false;
|
|
74123
|
-
const
|
|
74124
|
-
const lower =
|
|
74482
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
74483
|
+
const lower = sample3.toLowerCase();
|
|
74125
74484
|
return accountScopedThrottleSignals.some((s) => lower.includes(s));
|
|
74126
74485
|
}
|
|
74127
74486
|
function classify429Detail(text4) {
|
|
@@ -74458,8 +74817,8 @@ var litellmV3LimiterSignalPair2 = ["rate limit exceeded for ", "limit type:"];
|
|
|
74458
74817
|
function isLitellmProxyLocal4292(text4) {
|
|
74459
74818
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74460
74819
|
return false;
|
|
74461
|
-
const
|
|
74462
|
-
const lower =
|
|
74820
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
74821
|
+
const lower = sample3.toLowerCase();
|
|
74463
74822
|
if (litellmProxyLocal429Signals2.some((s) => lower.includes(s)))
|
|
74464
74823
|
return true;
|
|
74465
74824
|
return litellmV3LimiterSignalPair2.every((s) => lower.includes(s));
|
|
@@ -74467,14 +74826,14 @@ function isLitellmProxyLocal4292(text4) {
|
|
|
74467
74826
|
function detectModelUnavailable2(stderr) {
|
|
74468
74827
|
if (typeof stderr !== "string" || stderr.length === 0)
|
|
74469
74828
|
return null;
|
|
74470
|
-
const
|
|
74471
|
-
const lower =
|
|
74829
|
+
const sample3 = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
|
|
74830
|
+
const lower = sample3.toLowerCase();
|
|
74472
74831
|
if (transientUpstreamSignals2.some((s) => lower.includes(s))) {
|
|
74473
|
-
const resetAt = parseResetTime2(
|
|
74832
|
+
const resetAt = parseResetTime2(sample3);
|
|
74474
74833
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74475
74834
|
}
|
|
74476
|
-
if (isLitellmProxyLocal4292(
|
|
74477
|
-
const resetAt = parseResetTime2(
|
|
74835
|
+
if (isLitellmProxyLocal4292(sample3)) {
|
|
74836
|
+
const resetAt = parseResetTime2(sample3);
|
|
74478
74837
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74479
74838
|
}
|
|
74480
74839
|
const quotaSignals = [
|
|
@@ -74494,7 +74853,7 @@ function detectModelUnavailable2(stderr) {
|
|
|
74494
74853
|
"session cap"
|
|
74495
74854
|
];
|
|
74496
74855
|
if (quotaSignals.some((s) => lower.includes(s))) {
|
|
74497
|
-
const resetAt = parseResetTime2(
|
|
74856
|
+
const resetAt = parseResetTime2(sample3);
|
|
74498
74857
|
return resetAt !== undefined ? { kind: "quota_exhausted", resetAt, raw: stderr } : { kind: "quota_exhausted", raw: stderr };
|
|
74499
74858
|
}
|
|
74500
74859
|
const overloadSignals = [
|
|
@@ -74514,7 +74873,7 @@ function detectModelUnavailable2(stderr) {
|
|
|
74514
74873
|
" 529 "
|
|
74515
74874
|
];
|
|
74516
74875
|
if (overloadSignals.some((s) => lower.includes(s))) {
|
|
74517
|
-
const resetAt = parseResetTime2(
|
|
74876
|
+
const resetAt = parseResetTime2(sample3);
|
|
74518
74877
|
return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
|
|
74519
74878
|
}
|
|
74520
74879
|
const networkSignals = [
|
|
@@ -74715,8 +75074,8 @@ var accountScopedThrottleSignals2 = [
|
|
|
74715
75074
|
function isAccountScopedThrottle2(text4) {
|
|
74716
75075
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
74717
75076
|
return false;
|
|
74718
|
-
const
|
|
74719
|
-
const lower =
|
|
75077
|
+
const sample3 = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
75078
|
+
const lower = sample3.toLowerCase();
|
|
74720
75079
|
return accountScopedThrottleSignals2.some((s) => lower.includes(s));
|
|
74721
75080
|
}
|
|
74722
75081
|
function classify429Detail2(text4) {
|
|
@@ -75795,8 +76154,8 @@ function resolveYear(parsed, todayY, todayIdx) {
|
|
|
75795
76154
|
}
|
|
75796
76155
|
return best;
|
|
75797
76156
|
}
|
|
75798
|
-
function matchCase(
|
|
75799
|
-
if (
|
|
76157
|
+
function matchCase(sample3, replacement) {
|
|
76158
|
+
if (sample3.length > 0 && sample3[0] === sample3[0].toUpperCase() && sample3[0] !== sample3[0].toLowerCase()) {
|
|
75800
76159
|
return replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
75801
76160
|
}
|
|
75802
76161
|
return replacement;
|
|
@@ -80873,24 +81232,62 @@ function createNarrativeLane(deps) {
|
|
|
80873
81232
|
// inline-keyboard-callbacks.ts
|
|
80874
81233
|
var AGENT_CALLBACK_PREFIX2 = "agent:";
|
|
80875
81234
|
var AGENT_CALLBACK_DATA_MAX2 = 64 - AGENT_CALLBACK_PREFIX2.length;
|
|
81235
|
+
function extractCallbackChatId2(callbackQuery) {
|
|
81236
|
+
const msg = callbackQuery?.message;
|
|
81237
|
+
const chat = msg?.chat;
|
|
81238
|
+
const id = chat?.id;
|
|
81239
|
+
if (typeof id === "number" && Number.isFinite(id))
|
|
81240
|
+
return String(id);
|
|
81241
|
+
if (typeof id === "string" && id !== "")
|
|
81242
|
+
return id;
|
|
81243
|
+
return;
|
|
81244
|
+
}
|
|
81245
|
+
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.";
|
|
81246
|
+
async function disarmDeadCard2(ctx, apiCall, scope, log) {
|
|
81247
|
+
try {
|
|
81248
|
+
await apiCall(() => ctx.editMessageReplyMarkup({ reply_markup: { inline_keyboard: [] } }), { ...scope, verb: "editMessageReplyMarkup" });
|
|
81249
|
+
log("finalizeCallback: repaint failed but the keyboard was stripped \u2014 " + `the card shows stale text and is no longer tappable
|
|
81250
|
+
`);
|
|
81251
|
+
return;
|
|
81252
|
+
} catch (err) {
|
|
81253
|
+
log(`finalizeCallback: editMessageReplyMarkup fallback failed: ${err.message}
|
|
81254
|
+
`);
|
|
81255
|
+
}
|
|
81256
|
+
try {
|
|
81257
|
+
await apiCall(() => ctx.reply(DEAD_CARD_NOTICE2, { link_preview_options: { is_disabled: true } }), { ...scope, verb: "sendMessage" });
|
|
81258
|
+
} catch (err) {
|
|
81259
|
+
log(`finalizeCallback: dead-card notice failed: ${err.message}
|
|
81260
|
+
`);
|
|
81261
|
+
}
|
|
81262
|
+
}
|
|
80876
81263
|
async function finalizeCallback2(ctx, opts) {
|
|
80877
81264
|
const log = opts.log ?? ((line) => process.stderr.write(line));
|
|
80878
|
-
|
|
81265
|
+
const apiCall = opts.apiCall;
|
|
81266
|
+
const chatId = extractCallbackChatId2(ctx.callbackQuery);
|
|
81267
|
+
const scope = {
|
|
81268
|
+
...chatId != null ? { chat_id: chatId } : {},
|
|
81269
|
+
priorityClass: "critical"
|
|
81270
|
+
};
|
|
81271
|
+
apiCall(() => ctx.answerCallbackQuery({
|
|
80879
81272
|
text: opts.ackText,
|
|
80880
81273
|
...opts.alert ? { show_alert: true } : {}
|
|
80881
|
-
}).catch((err) => {
|
|
81274
|
+
}), { ...scope, verb: "answerCallbackQuery" }).catch((err) => {
|
|
80882
81275
|
log(`finalizeCallback: answerCallbackQuery failed: ${err.message}
|
|
80883
81276
|
`);
|
|
80884
81277
|
});
|
|
81278
|
+
let repainted = true;
|
|
80885
81279
|
try {
|
|
80886
|
-
await ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
81280
|
+
await apiCall(() => ctx.editMessageText(opts.literalText ? opts.newText : { markdown: opts.newText }, {
|
|
80887
81281
|
reply_markup: { inline_keyboard: [] },
|
|
80888
81282
|
link_preview_options: { is_disabled: true }
|
|
80889
|
-
});
|
|
81283
|
+
}), { ...scope, verb: "editMessageText" });
|
|
80890
81284
|
} catch (err) {
|
|
81285
|
+
repainted = false;
|
|
80891
81286
|
log(`finalizeCallback: editMessageText failed: ${err.message}
|
|
80892
81287
|
`);
|
|
80893
81288
|
}
|
|
81289
|
+
if (!repainted)
|
|
81290
|
+
await disarmDeadCard2(ctx, apiCall, scope, log);
|
|
80894
81291
|
if (opts.synthInbound != null) {
|
|
80895
81292
|
try {
|
|
80896
81293
|
const r = opts.synthInbound();
|
|
@@ -84017,7 +84414,13 @@ var HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2;
|
|
|
84017
84414
|
var HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low";
|
|
84018
84415
|
var HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4;
|
|
84019
84416
|
var HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1;
|
|
84020
|
-
|
|
84417
|
+
function hindsightConsolidationLlmMaxConcurrentDefault(globalMaxConcurrent = HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT, retainMaxConcurrent = HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT) {
|
|
84418
|
+
const globalCap = Number.isFinite(globalMaxConcurrent) && globalMaxConcurrent >= 1 ? Math.floor(globalMaxConcurrent) : HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT;
|
|
84419
|
+
const retainCap = Number.isFinite(retainMaxConcurrent) && retainMaxConcurrent >= 0 ? Math.floor(retainMaxConcurrent) : HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT;
|
|
84420
|
+
const headroomBound = Math.max(1, globalCap - 1);
|
|
84421
|
+
return Math.min(headroomBound, Math.max(1, globalCap - retainCap - 1));
|
|
84422
|
+
}
|
|
84423
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT = hindsightConsolidationLlmMaxConcurrentDefault();
|
|
84021
84424
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true";
|
|
84022
84425
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128;
|
|
84023
84426
|
var HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true";
|
|
@@ -84032,6 +84435,8 @@ var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
|
84032
84435
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 500;
|
|
84033
84436
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
84034
84437
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
84438
|
+
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
84439
|
+
var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
|
|
84035
84440
|
var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
84036
84441
|
[
|
|
84037
84442
|
"HINDSIGHT_API_RECALL_MAX_CANDIDATES_PER_SOURCE",
|
|
@@ -84085,6 +84490,14 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
|
84085
84490
|
[
|
|
84086
84491
|
"HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND",
|
|
84087
84492
|
String(HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND)
|
|
84493
|
+
],
|
|
84494
|
+
[
|
|
84495
|
+
"HINDSIGHT_API_RECENCY_DECAY_FUNCTION",
|
|
84496
|
+
HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION
|
|
84497
|
+
],
|
|
84498
|
+
[
|
|
84499
|
+
"HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
|
|
84500
|
+
String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
|
|
84088
84501
|
]
|
|
84089
84502
|
];
|
|
84090
84503
|
var HINDSIGHT_PERF_DEFAULTS_GPU = [
|
|
@@ -84109,7 +84522,9 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
|
|
|
84109
84522
|
];
|
|
84110
84523
|
var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
84111
84524
|
"HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
|
|
84112
|
-
"HINDSIGHT_CE_DECISIVE_RELATIVE_GAP"
|
|
84525
|
+
"HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
|
|
84526
|
+
"HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
|
|
84527
|
+
"HINDSIGHT_API_WORKER_MAX_SLOTS"
|
|
84113
84528
|
]);
|
|
84114
84529
|
var HINDSIGHT_PERF_ENV_KEYS = new Set([
|
|
84115
84530
|
...[
|
|
@@ -84175,6 +84590,16 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
84175
84590
|
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84176
84591
|
` + ` system's self-reports are not memories.
|
|
84177
84592
|
` + `- Restatements of the user's current request or the task in progress.
|
|
84593
|
+
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
84594
|
+
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
84595
|
+
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
84596
|
+
` + ` fact whose text resembles any of these: "Switchroom fleet is running image
|
|
84597
|
+
` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
|
|
84598
|
+
` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
|
|
84599
|
+
` + ` currently green". If the claim is worth keeping, put the date INSIDE the
|
|
84600
|
+
` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
|
|
84601
|
+
` + ` cannot date it, drop it. An undated one is recalled forever as though it
|
|
84602
|
+
` + ` were still true, which is worse than not remembering it at all.
|
|
84178
84603
|
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84179
84604
|
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84180
84605
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
@@ -84196,18 +84621,108 @@ var SUPERSEDED_RETAIN_MISSIONS = [
|
|
|
84196
84621
|
` + "- 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
84622
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84198
84623
|
|
|
84199
|
-
` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list."
|
|
84624
|
+
` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list.",
|
|
84625
|
+
`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.
|
|
84626
|
+
` + `
|
|
84627
|
+
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
84628
|
+
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
84629
|
+
` + `the location where some output was written? If yes, drop it \u2014 it is transcript
|
|
84630
|
+
` + `exhaust, not memory.
|
|
84631
|
+
` + `
|
|
84632
|
+
` + `NEVER extract:
|
|
84633
|
+
` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
|
|
84634
|
+
` + ` text resembles any of these: "File created successfully at /path/to/file",
|
|
84635
|
+
` + ` "A background command with ID bctz4yskm is running, and its output will be
|
|
84636
|
+
` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
|
|
84637
|
+
` + ` and is running in the background", "User executed a Bash command to sleep for
|
|
84638
|
+
` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
|
|
84639
|
+
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
84640
|
+
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
84641
|
+
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
84642
|
+
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
84643
|
+
` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
|
|
84644
|
+
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
84645
|
+
` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
|
|
84646
|
+
` + ` reset assistant state").
|
|
84647
|
+
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84648
|
+
` + ` system's self-reports are not memories.
|
|
84649
|
+
` + `- Restatements of the user's current request or the task in progress.
|
|
84650
|
+
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84651
|
+
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84652
|
+
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84653
|
+
` + `
|
|
84654
|
+
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84655
|
+
` + "nothing durable remains, return an empty facts list."
|
|
84656
|
+
];
|
|
84657
|
+
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.
|
|
84658
|
+
` + `
|
|
84659
|
+
` + `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.
|
|
84660
|
+
` + `
|
|
84661
|
+
` + `Do NOT synthesise observations from:
|
|
84662
|
+
` + `- 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.
|
|
84663
|
+
` + `- Identifiers with no standing meaning: session, agent, request, batch or command IDs, UUIDs, hashes, error codes.
|
|
84664
|
+
` + `- In-flight process narration \u2014 a task started, paused, or still running. Record the outcome once it lands, not the running state.
|
|
84665
|
+
` + `- The memory system's own errors, retries, backlogs, or internal state.
|
|
84666
|
+
` + `- Transient state (what is running right now, unread counts, build status) unless the fact is explicitly dated, in which case record it as dated.
|
|
84667
|
+
` + `
|
|
84668
|
+
` + "If the new facts contain nothing durable, record nothing rather than synthesising a weak observation.";
|
|
84669
|
+
var SUPERSEDED_OBSERVATIONS_MISSIONS = [
|
|
84670
|
+
"Synthesise the person's wellbeing patterns, motivations, and emotional " + "context \u2014 how habits, setbacks, and encouragement connect over time."
|
|
84200
84671
|
];
|
|
84201
84672
|
var PROFILE_MEMORY_DEFAULTS = {
|
|
84202
84673
|
"health-coach": {
|
|
84203
84674
|
disposition: { skepticism: 2, literalism: 2, empathy: 5 },
|
|
84204
|
-
observations_mission:
|
|
84675
|
+
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.
|
|
84676
|
+
` + `
|
|
84677
|
+
` + `Synthesise into durable observations:
|
|
84678
|
+
` + `- Goals, targets, and the plan currently in force, with the reasoning behind each.
|
|
84679
|
+
` + `- Training, nutrition, sleep, and alcohol patterns as they hold over weeks \u2014 what the person reliably does, not what they did once.
|
|
84680
|
+
` + `- Constraints that shape the plan: injuries, medical guidance, schedule, equipment, foods and sessions they refuse.
|
|
84681
|
+
` + `- Motivations, and what actually helps or backfires when they slip.
|
|
84682
|
+
` + `- Trends in the numbers: direction and range over time, not any single reading.
|
|
84683
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84684
|
+
` + `
|
|
84685
|
+
` + `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.
|
|
84686
|
+
` + `
|
|
84687
|
+
` + `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.
|
|
84688
|
+
` + `
|
|
84689
|
+
` + "Word observations as the person's own pattern and framing, never as a verdict on them."
|
|
84205
84690
|
},
|
|
84206
84691
|
"executive-assistant": {
|
|
84207
|
-
disposition: { skepticism: 4, literalism: 4, empathy: 3 }
|
|
84692
|
+
disposition: { skepticism: 4, literalism: 4, empathy: 3 },
|
|
84693
|
+
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.
|
|
84694
|
+
` + `
|
|
84695
|
+
` + `Synthesise into durable observations:
|
|
84696
|
+
` + `- Standing rules and preferences: how they want things scheduled, written, and filed, and when they want to be interrupted.
|
|
84697
|
+
` + `- People and organisations, and the relationship: who they are, what they are involved in, how to reach them.
|
|
84698
|
+
` + `- Recurring commitments and routines, and the constraints around them.
|
|
84699
|
+
` + `- Obligations and their state: what was promised to whom, the deadline, and what is still outstanding.
|
|
84700
|
+
` + `- Decisions made and decisions deferred, with the reasoning and the trade accepted.
|
|
84701
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84702
|
+
` + `
|
|
84703
|
+
` + `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.
|
|
84704
|
+
` + `
|
|
84705
|
+
` + `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.
|
|
84706
|
+
` + `
|
|
84707
|
+
` + "Do not synthesise from transcript exhaust: tool calls and their results, message or event identifiers, or narration of what the assistant did."
|
|
84208
84708
|
},
|
|
84209
84709
|
coding: {
|
|
84210
|
-
disposition: { skepticism: 4, literalism: 5, empathy: 2 }
|
|
84710
|
+
disposition: { skepticism: 4, literalism: 5, empathy: 2 },
|
|
84711
|
+
observations_mission: `You consolidate the memory of a software-engineering agent. This bank records real work on real codebases.
|
|
84712
|
+
` + `
|
|
84713
|
+
` + `Synthesise into durable observations:
|
|
84714
|
+
` + `- Architecture and design decisions, each with its rationale and the trade accepted.
|
|
84715
|
+
` + `- Root causes, with the evidence chain, and negative results \u2014 what was ruled out matters as much as what was found.
|
|
84716
|
+
` + `- How the repository works: build, test, and lint commands, conventions, CI gates, and where things live.
|
|
84717
|
+
` + `- Outcomes of code work: issue and PR numbers, what changed, whether it merged, what review found.
|
|
84718
|
+
` + `- The user's standing rules, preferences, and corrections to this agent's behaviour.
|
|
84719
|
+
` + `- Corrections to earlier beliefs, recorded explicitly as corrections.
|
|
84720
|
+
` + `
|
|
84721
|
+
` + `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.
|
|
84722
|
+
` + `
|
|
84723
|
+
` + `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.
|
|
84724
|
+
` + `
|
|
84725
|
+
` + "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
84726
|
}
|
|
84212
84727
|
};
|
|
84213
84728
|
// ../src/agents/reconcile-default-skills.ts
|
|
@@ -97654,10 +98169,10 @@ function startOutboxSweep(deps) {
|
|
|
97654
98169
|
}
|
|
97655
98170
|
|
|
97656
98171
|
// ../src/build-info.ts
|
|
97657
|
-
var VERSION2 = "0.19.
|
|
97658
|
-
var COMMIT_SHA = "
|
|
97659
|
-
var COMMIT_DATE = "2026-07-
|
|
97660
|
-
var LATEST_PR =
|
|
98172
|
+
var VERSION2 = "0.19.28";
|
|
98173
|
+
var COMMIT_SHA = "d9a5f4ae";
|
|
98174
|
+
var COMMIT_DATE = "2026-07-28T03:46:02Z";
|
|
98175
|
+
var LATEST_PR = 3906;
|
|
97661
98176
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
97662
98177
|
|
|
97663
98178
|
// gateway/boot-version.ts
|
|
@@ -105584,7 +106099,9 @@ function surfaceConsolidationLegibility(rec, target) {
|
|
|
105584
106099
|
`);
|
|
105585
106100
|
});
|
|
105586
106101
|
}
|
|
106102
|
+
var noteMcpDependencyFailure = createMcpFailureHook({ agent: AGENT_NAME, emit: emitGatewayOperatorEvent });
|
|
105587
106103
|
function handleSessionEvent2(ev) {
|
|
106104
|
+
noteMcpDependencyFailure(ev);
|
|
105588
106105
|
handleSessionEvent(gatewayStreamRenderDeps(), ev);
|
|
105589
106106
|
}
|
|
105590
106107
|
function gatewayStreamRenderDeps() {
|
|
@@ -109966,6 +110483,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
|
|
|
109966
110483
|
const baseText2 = sourceMsg && "text" in sourceMsg && sourceMsg.text ? escapeHtmlForTg2(sourceMsg.text) : "";
|
|
109967
110484
|
const interimLabel = `\u23F3 **Rule applied for this session** \u2014 ${escapeHtmlForTg2(agentName3)} can ${escapeHtmlForTg2(grantPhrase)} ` + `without asking for now; saving durably in background\u2026`;
|
|
109968
110485
|
await finalizeCallback2(ctx, {
|
|
110486
|
+
apiCall: robustApiCall,
|
|
109969
110487
|
ackText: "Rule applied for this session; saving durably in background\u2026".slice(0, 200),
|
|
109970
110488
|
newText: baseText2 ? `${baseText2}
|
|
109971
110489
|
|
|
@@ -110131,6 +110649,7 @@ ${editLabel}` : editLabel), {
|
|
|
110131
110649
|
|
|
110132
110650
|
${resumeLine}` : htmlLabel;
|
|
110133
110651
|
await finalizeCallback2(ctx, {
|
|
110652
|
+
apiCall: robustApiCall,
|
|
110134
110653
|
ackText: ackText3.slice(0, 200),
|
|
110135
110654
|
newText: baseText ? `${baseText}
|
|
110136
110655
|
|