switchroom 0.18.29 → 0.18.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/handoff-briefing.sh +8 -2
- package/dist/agent-scheduler/index.js +111 -7
- package/dist/auth-broker/index.js +154 -16
- package/dist/cli/autoaccept-poll.js +8 -3
- package/dist/cli/drive-write-pretool.mjs +8 -3
- package/dist/cli/ms-365-write-pretool.mjs +158 -11
- package/dist/cli/notion-write-pretool.mjs +103 -4
- package/dist/cli/switchroom.js +2074 -1585
- package/dist/host-control/main.js +110 -13
- package/dist/vault/approvals/kernel-server.js +116 -13
- package/dist/vault/broker/server.js +314 -145
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +73 -20
- package/telegram-plugin/dist/bridge/bridge.js +71 -47
- package/telegram-plugin/dist/gateway/gateway.js +560 -96
- package/telegram-plugin/dist/server.js +89 -64
- package/telegram-plugin/gateway/gateway.ts +212 -17
- package/telegram-plugin/gateway/model-command.ts +104 -0
- package/telegram-plugin/gateway/session-model-file.ts +40 -0
- package/telegram-plugin/gateway/unhandled-message.ts +177 -0
- package/telegram-plugin/llm-error-present.ts +24 -0
- package/telegram-plugin/model-unavailable.ts +55 -0
- package/telegram-plugin/operator-events.ts +113 -0
- package/telegram-plugin/pending-user-notice.ts +88 -0
- package/telegram-plugin/shared/local-time.ts +43 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +103 -0
- package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +264 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +26 -2
- package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +278 -0
- package/telegram-plugin/tests/local-time.test.ts +68 -1
- package/telegram-plugin/tests/model-command.test.ts +133 -0
- package/telegram-plugin/tests/session-model-file.test.ts +23 -0
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +399 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/content.py +53 -1
- package/vendor/hindsight-memory/scripts/lib/turnlog.py +450 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +467 -0
- package/vendor/hindsight-memory/tests/test_content.py +35 -0
|
@@ -20036,7 +20036,8 @@ var init_protocol2 = __esm(() => {
|
|
|
20036
20036
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
20037
20037
|
op: exports_external.literal("get-credentials"),
|
|
20038
20038
|
id: exports_external.string().min(1),
|
|
20039
|
-
provider: ProviderNameSchema.optional()
|
|
20039
|
+
provider: ProviderNameSchema.optional(),
|
|
20040
|
+
account: exports_external.string().min(1).optional()
|
|
20040
20041
|
});
|
|
20041
20042
|
ListStateRequestSchema = exports_external.object({
|
|
20042
20043
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
@@ -20351,13 +20352,17 @@ class AuthBrokerClient {
|
|
|
20351
20352
|
sock.destroy();
|
|
20352
20353
|
}
|
|
20353
20354
|
}
|
|
20354
|
-
async getCredentials(provider) {
|
|
20355
|
+
async getCredentials(provider, account) {
|
|
20355
20356
|
const base = {
|
|
20356
20357
|
v: PROTOCOL_VERSION,
|
|
20357
20358
|
id: randomUUID(),
|
|
20358
20359
|
op: "get-credentials"
|
|
20359
20360
|
};
|
|
20360
|
-
|
|
20361
|
+
let req = base;
|
|
20362
|
+
if (provider !== undefined)
|
|
20363
|
+
req = { ...req, provider };
|
|
20364
|
+
if (account !== undefined)
|
|
20365
|
+
req = { ...req, account };
|
|
20361
20366
|
const data = await this.send(req);
|
|
20362
20367
|
return data;
|
|
20363
20368
|
}
|
|
@@ -20632,7 +20637,7 @@ var init_client2 = __esm(() => {
|
|
|
20632
20637
|
});
|
|
20633
20638
|
|
|
20634
20639
|
// ../src/config/schema.ts
|
|
20635
|
-
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, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
20640
|
+
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, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
20636
20641
|
var init_schema = __esm(() => {
|
|
20637
20642
|
init_zod();
|
|
20638
20643
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -21030,11 +21035,52 @@ var init_schema = __esm(() => {
|
|
|
21030
21035
|
approvers: exports_external.array(ApproverIdSchema).min(1).optional().describe("Per-agent approver override. When set, replaces (does not extend) " + "the top-level drive.approvers list for this agent's onboarding card."),
|
|
21031
21036
|
tier: GoogleWorkspaceTierSchema.optional().describe("Per-agent tier override (RFC G Phase 1). When set, replaces the " + "top-level google_workspace.tier for this agent. Common case: most " + "agents on `core`, one specialist on `extended` for Slides access.")
|
|
21032
21037
|
}).optional();
|
|
21038
|
+
MicrosoftAccountEmailSchema = exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
21039
|
+
message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
21040
|
+
}).transform((v) => v.trim().toLowerCase());
|
|
21041
|
+
MicrosoftToolTokenSchema = exports_external.string().min(1).regex(/^[a-z0-9-]+$/, {
|
|
21042
|
+
message: "microsoft_workspace tools[] tokens are forwarded to softeria's " + "--enabled-tools regex; each must be lowercase kebab-case " + "([a-z0-9-]+, e.g. 'mail', 'calendar', 'send-mail') so an unescaped " + "regex metacharacter can't crash the launcher"
|
|
21043
|
+
});
|
|
21044
|
+
MicrosoftAccountBindingSchema = exports_external.object({
|
|
21045
|
+
account: MicrosoftAccountEmailSchema.describe("The Microsoft account this binding uses. Must be a key in top-level " + "`microsoft_accounts:` with this agent in its `enabled_for[]`."),
|
|
21046
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist \u2192 softeria `--enabled-tools <regex>` " + "(tokens joined with `|`). Omitted = all tools exposed for this account."),
|
|
21047
|
+
org_mode: exports_external.boolean().optional().describe("Per-binding org_mode override (RFC #1873 \u00a76.4).")
|
|
21048
|
+
});
|
|
21033
21049
|
AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
21034
|
-
account:
|
|
21035
|
-
|
|
21036
|
-
|
|
21037
|
-
|
|
21050
|
+
account: MicrosoftAccountEmailSchema.optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized). Mutually exclusive with `accounts` (plural)."),
|
|
21051
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist for the SINGULAR `account` form \u2192 " + "softeria `--enabled-tools <regex>`. Omitted = all tools. Only " + "valid with the singular `account`; using it together with the " + "plural `accounts` (which carries per-binding `tools`) is an error."),
|
|
21052
|
+
org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 \u00a76.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false)."),
|
|
21053
|
+
accounts: exports_external.array(MicrosoftAccountBindingSchema).min(1).optional().describe("Plural multi-account form: bind MULTIPLE Microsoft accounts to " + "this agent, each with its own tool scope. Mutually exclusive with " + "the singular `account`. Each account gets its own `ms-365-<slug>` " + "MCP server.")
|
|
21054
|
+
}).superRefine((v, ctx) => {
|
|
21055
|
+
const hasSingular = v.account !== undefined;
|
|
21056
|
+
const hasPlural = v.accounts !== undefined;
|
|
21057
|
+
if (hasSingular && hasPlural) {
|
|
21058
|
+
ctx.addIssue({
|
|
21059
|
+
code: exports_external.ZodIssueCode.custom,
|
|
21060
|
+
message: "microsoft_workspace: use EITHER `account` (singular) OR " + "`accounts` (plural array), not both",
|
|
21061
|
+
path: ["accounts"]
|
|
21062
|
+
});
|
|
21063
|
+
}
|
|
21064
|
+
if (hasPlural && v.tools !== undefined) {
|
|
21065
|
+
ctx.addIssue({
|
|
21066
|
+
code: exports_external.ZodIssueCode.custom,
|
|
21067
|
+
message: "microsoft_workspace: block-level `tools` applies to the singular " + "`account` only; with `accounts` put `tools` inside each binding",
|
|
21068
|
+
path: ["tools"]
|
|
21069
|
+
});
|
|
21070
|
+
}
|
|
21071
|
+
if (hasPlural) {
|
|
21072
|
+
const seen = new Set;
|
|
21073
|
+
for (const b of v.accounts) {
|
|
21074
|
+
if (seen.has(b.account)) {
|
|
21075
|
+
ctx.addIssue({
|
|
21076
|
+
code: exports_external.ZodIssueCode.custom,
|
|
21077
|
+
message: `microsoft_workspace: duplicate account '${b.account}' in accounts[]`,
|
|
21078
|
+
path: ["accounts"]
|
|
21079
|
+
});
|
|
21080
|
+
}
|
|
21081
|
+
seen.add(b.account);
|
|
21082
|
+
}
|
|
21083
|
+
}
|
|
21038
21084
|
}).optional();
|
|
21039
21085
|
AgentNotionWorkspaceConfigSchema = exports_external.object({
|
|
21040
21086
|
databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
|
|
@@ -21409,6 +21455,26 @@ var init_schema = __esm(() => {
|
|
|
21409
21455
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
21410
21456
|
checkServes(a.serves, ["agents", name, "serves"]);
|
|
21411
21457
|
}
|
|
21458
|
+
const microsoftAccounts = cfg.microsoft_accounts;
|
|
21459
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
21460
|
+
const mw = a.microsoft_workspace;
|
|
21461
|
+
const accounts = mw?.accounts;
|
|
21462
|
+
if (!accounts)
|
|
21463
|
+
continue;
|
|
21464
|
+
accounts.forEach((b, i) => {
|
|
21465
|
+
const acct = b?.account?.trim().toLowerCase();
|
|
21466
|
+
if (!acct)
|
|
21467
|
+
return;
|
|
21468
|
+
const enabledFor = microsoftAccounts?.[acct]?.enabled_for ?? [];
|
|
21469
|
+
if (!enabledFor.includes(name)) {
|
|
21470
|
+
ctx.addIssue({
|
|
21471
|
+
code: exports_external.ZodIssueCode.custom,
|
|
21472
|
+
message: `agent '${name}' binds Microsoft account '${acct}' but is not in ` + `microsoft_accounts['${acct}'].enabled_for[] \u2014 operator must run ` + `\`switchroom auth microsoft enable ${acct} ${name}\``,
|
|
21473
|
+
path: ["agents", name, "microsoft_workspace", "accounts", i, "account"]
|
|
21474
|
+
});
|
|
21475
|
+
}
|
|
21476
|
+
});
|
|
21477
|
+
}
|
|
21412
21478
|
});
|
|
21413
21479
|
});
|
|
21414
21480
|
|
|
@@ -22024,6 +22090,25 @@ function validateNotionWorkspaceConfig(config) {
|
|
|
22024
22090
|
return issues;
|
|
22025
22091
|
}
|
|
22026
22092
|
|
|
22093
|
+
// ../src/config/timezone.ts
|
|
22094
|
+
function isResolvableTimezone(zone) {
|
|
22095
|
+
try {
|
|
22096
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
22097
|
+
return true;
|
|
22098
|
+
} catch {
|
|
22099
|
+
return false;
|
|
22100
|
+
}
|
|
22101
|
+
}
|
|
22102
|
+
var CONTAINER_DEFAULT_UTC_ZONES;
|
|
22103
|
+
var init_timezone = __esm(() => {
|
|
22104
|
+
CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
22105
|
+
"UTC",
|
|
22106
|
+
"Etc/UTC",
|
|
22107
|
+
"Etc/Universal",
|
|
22108
|
+
"Universal"
|
|
22109
|
+
]);
|
|
22110
|
+
});
|
|
22111
|
+
|
|
22027
22112
|
// ../src/config/loader.ts
|
|
22028
22113
|
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "node:fs";
|
|
22029
22114
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -22142,8 +22227,30 @@ function loadConfig(configPath) {
|
|
|
22142
22227
|
if (notionIssues.length > 0) {
|
|
22143
22228
|
throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
|
|
22144
22229
|
}
|
|
22230
|
+
validateAllTimezones(config, filePath);
|
|
22145
22231
|
return config;
|
|
22146
22232
|
}
|
|
22233
|
+
function validateAllTimezones(config, filePath) {
|
|
22234
|
+
const issues = [];
|
|
22235
|
+
const check = (zone, where) => {
|
|
22236
|
+
if (zone == null)
|
|
22237
|
+
return;
|
|
22238
|
+
if (!isResolvableTimezone(zone)) {
|
|
22239
|
+
issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists \u2014 check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
|
|
22240
|
+
}
|
|
22241
|
+
};
|
|
22242
|
+
check(config.switchroom?.timezone, "switchroom.timezone");
|
|
22243
|
+
check(config.defaults?.timezone, "defaults.timezone");
|
|
22244
|
+
for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
|
|
22245
|
+
check(profile?.timezone, `profiles.${profileName}.timezone`);
|
|
22246
|
+
}
|
|
22247
|
+
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
22248
|
+
check(agentRaw?.timezone, `agents.${agentName}.timezone`);
|
|
22249
|
+
}
|
|
22250
|
+
if (issues.length > 0) {
|
|
22251
|
+
throw new ConfigError(`Invalid timezone configuration in ${filePath}`, issues);
|
|
22252
|
+
}
|
|
22253
|
+
}
|
|
22147
22254
|
function validateAllCronTopicAliases(config, filePath) {
|
|
22148
22255
|
const issues = [];
|
|
22149
22256
|
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
@@ -22179,6 +22286,7 @@ var init_loader = __esm(() => {
|
|
|
22179
22286
|
init_paths();
|
|
22180
22287
|
init_overlay_loader();
|
|
22181
22288
|
init_merge();
|
|
22289
|
+
init_timezone();
|
|
22182
22290
|
import_yaml3 = __toESM(require_dist(), 1);
|
|
22183
22291
|
ConfigError = class ConfigError extends Error {
|
|
22184
22292
|
details;
|
|
@@ -40072,6 +40180,131 @@ function forwardOriginDateIso(o) {
|
|
|
40072
40180
|
return new Date(o.date * 1000).toISOString();
|
|
40073
40181
|
}
|
|
40074
40182
|
|
|
40183
|
+
// gateway/unhandled-message.ts
|
|
40184
|
+
var MESSAGE_ENVELOPE_KEYS = new Set([
|
|
40185
|
+
"message_id",
|
|
40186
|
+
"message_thread_id",
|
|
40187
|
+
"date",
|
|
40188
|
+
"chat",
|
|
40189
|
+
"from",
|
|
40190
|
+
"sender_chat",
|
|
40191
|
+
"forward_origin",
|
|
40192
|
+
"reply_to_message",
|
|
40193
|
+
"external_reply",
|
|
40194
|
+
"quote",
|
|
40195
|
+
"reply_to_story",
|
|
40196
|
+
"edit_date",
|
|
40197
|
+
"media_group_id",
|
|
40198
|
+
"author_signature",
|
|
40199
|
+
"is_topic_message",
|
|
40200
|
+
"is_automatic_forward",
|
|
40201
|
+
"via_bot",
|
|
40202
|
+
"sender_boost_count",
|
|
40203
|
+
"business_connection_id",
|
|
40204
|
+
"effect_id",
|
|
40205
|
+
"has_protected_content",
|
|
40206
|
+
"is_from_offline",
|
|
40207
|
+
"link_preview_options",
|
|
40208
|
+
"show_caption_above_media",
|
|
40209
|
+
"entities",
|
|
40210
|
+
"caption_entities",
|
|
40211
|
+
"paid_star_count"
|
|
40212
|
+
]);
|
|
40213
|
+
var SERVICE_NOISE_KEYS = new Set([
|
|
40214
|
+
"new_chat_members",
|
|
40215
|
+
"left_chat_member",
|
|
40216
|
+
"new_chat_title",
|
|
40217
|
+
"new_chat_photo",
|
|
40218
|
+
"delete_chat_photo",
|
|
40219
|
+
"group_chat_created",
|
|
40220
|
+
"supergroup_chat_created",
|
|
40221
|
+
"channel_chat_created",
|
|
40222
|
+
"message_auto_delete_timer_changed",
|
|
40223
|
+
"migrate_to_chat_id",
|
|
40224
|
+
"migrate_from_chat_id",
|
|
40225
|
+
"forum_topic_created",
|
|
40226
|
+
"forum_topic_edited",
|
|
40227
|
+
"forum_topic_closed",
|
|
40228
|
+
"forum_topic_reopened",
|
|
40229
|
+
"general_forum_topic_hidden",
|
|
40230
|
+
"general_forum_topic_unhidden",
|
|
40231
|
+
"video_chat_scheduled",
|
|
40232
|
+
"video_chat_started",
|
|
40233
|
+
"video_chat_ended",
|
|
40234
|
+
"video_chat_participants_invited",
|
|
40235
|
+
"giveaway_created",
|
|
40236
|
+
"giveaway",
|
|
40237
|
+
"giveaway_winners",
|
|
40238
|
+
"giveaway_completed",
|
|
40239
|
+
"boost_added",
|
|
40240
|
+
"chat_background_set",
|
|
40241
|
+
"write_access_allowed",
|
|
40242
|
+
"proximity_alert_triggered",
|
|
40243
|
+
"chat_set_theme",
|
|
40244
|
+
"connected_website",
|
|
40245
|
+
"direct_message_price_changed"
|
|
40246
|
+
]);
|
|
40247
|
+
function messageContentKeys(msg) {
|
|
40248
|
+
return Object.keys(msg).filter((k) => !MESSAGE_ENVELOPE_KEYS.has(k));
|
|
40249
|
+
}
|
|
40250
|
+
function planUnhandledMessage(msg) {
|
|
40251
|
+
const contentKeys = messageContentKeys(msg);
|
|
40252
|
+
if (contentKeys.length > 0 && contentKeys.every((k) => SERVICE_NOISE_KEYS.has(k))) {
|
|
40253
|
+
return { action: "log-only", contentKeys };
|
|
40254
|
+
}
|
|
40255
|
+
const contentType = contentKeys[0] ?? "unknown";
|
|
40256
|
+
const text = (typeof msg.text === "string" ? msg.text : undefined) ?? (typeof msg.caption === "string" ? msg.caption : undefined) ?? `(unhandled message content: ${contentType})`;
|
|
40257
|
+
return { action: "turn", text, contentKeys };
|
|
40258
|
+
}
|
|
40259
|
+
var TAP_MAX_LINES_PER_MINUTE = 300;
|
|
40260
|
+
function installUpdateTap(bot, log, nowMs = Date.now) {
|
|
40261
|
+
let windowStart = 0;
|
|
40262
|
+
let windowCount = 0;
|
|
40263
|
+
let suppressed = 0;
|
|
40264
|
+
bot.use(async (ctx, next) => {
|
|
40265
|
+
try {
|
|
40266
|
+
const now = nowMs();
|
|
40267
|
+
if (now - windowStart >= 60000) {
|
|
40268
|
+
if (suppressed > 0) {
|
|
40269
|
+
log(`telegram gateway: rx tap suppressed ${suppressed} update lines in the last minute (cap ${TAP_MAX_LINES_PER_MINUTE}/min)
|
|
40270
|
+
`);
|
|
40271
|
+
}
|
|
40272
|
+
windowStart = now;
|
|
40273
|
+
windowCount = 0;
|
|
40274
|
+
suppressed = 0;
|
|
40275
|
+
}
|
|
40276
|
+
if (windowCount < TAP_MAX_LINES_PER_MINUTE) {
|
|
40277
|
+
windowCount++;
|
|
40278
|
+
const upd = ctx.update;
|
|
40279
|
+
const updateType = Object.keys(upd).find((k) => k !== "update_id") ?? "unknown";
|
|
40280
|
+
const msg = ctx.message;
|
|
40281
|
+
const detail = msg ? ` content=[${messageContentKeys(msg).join(",")}]` : "";
|
|
40282
|
+
log(`telegram gateway: rx update_id=${ctx.update.update_id} type=${updateType}${detail}
|
|
40283
|
+
`);
|
|
40284
|
+
} else {
|
|
40285
|
+
suppressed++;
|
|
40286
|
+
}
|
|
40287
|
+
} catch {}
|
|
40288
|
+
await next();
|
|
40289
|
+
});
|
|
40290
|
+
}
|
|
40291
|
+
function installUnhandledMessageCatchAll(bot, onInbound, log) {
|
|
40292
|
+
bot.on("message", async (ctx) => {
|
|
40293
|
+
try {
|
|
40294
|
+
const msg = ctx.message;
|
|
40295
|
+
const plan = planUnhandledMessage(msg);
|
|
40296
|
+
log(`telegram gateway: catch-all inbound (no specific handler) ` + `update_id=${ctx.update.update_id} chat_id=${ctx.chat?.id ?? "?"} ` + `message_id=${ctx.message?.message_id ?? "?"} ` + `content_keys=[${plan.contentKeys.join(",")}] action=${plan.action}
|
|
40297
|
+
`);
|
|
40298
|
+
if (plan.action === "turn") {
|
|
40299
|
+
await onInbound(ctx, plan.text);
|
|
40300
|
+
}
|
|
40301
|
+
} catch (err) {
|
|
40302
|
+
log(`telegram gateway: catch-all handler error: ${err.message}
|
|
40303
|
+
`);
|
|
40304
|
+
}
|
|
40305
|
+
});
|
|
40306
|
+
}
|
|
40307
|
+
|
|
40075
40308
|
// shared/local-time.ts
|
|
40076
40309
|
function localDay2(ms, tz) {
|
|
40077
40310
|
return new Intl.DateTimeFormat("en-CA", {
|
|
@@ -40114,6 +40347,22 @@ function fmtLocalStamp2(ms, tz) {
|
|
|
40114
40347
|
return new Date(ms).toISOString();
|
|
40115
40348
|
}
|
|
40116
40349
|
}
|
|
40350
|
+
var LEADING_ISO_Z = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)(\s|$)/;
|
|
40351
|
+
function renderLogTimestampsLocal(text, tz) {
|
|
40352
|
+
if (!text)
|
|
40353
|
+
return text;
|
|
40354
|
+
return text.split(`
|
|
40355
|
+
`).map((line) => {
|
|
40356
|
+
const m = LEADING_ISO_Z.exec(line);
|
|
40357
|
+
if (!m)
|
|
40358
|
+
return line;
|
|
40359
|
+
const ms = Date.parse(m[1]);
|
|
40360
|
+
if (Number.isNaN(ms))
|
|
40361
|
+
return line;
|
|
40362
|
+
return `${fmtLocalStamp2(ms, tz)}${m[2] === "" ? "" : " "}${line.slice(m[0].length)}`;
|
|
40363
|
+
}).join(`
|
|
40364
|
+
`);
|
|
40365
|
+
}
|
|
40117
40366
|
|
|
40118
40367
|
// status-reactions.ts
|
|
40119
40368
|
var TELEGRAM_REACTION_WHITELIST = new Set([
|
|
@@ -63791,71 +64040,6 @@ function extractRequestId(raw) {
|
|
|
63791
64040
|
return m ? m[1] : undefined;
|
|
63792
64041
|
}
|
|
63793
64042
|
|
|
63794
|
-
// operator-events.ts
|
|
63795
|
-
function classifyClaudeError(raw) {
|
|
63796
|
-
try {
|
|
63797
|
-
return classifyInner(raw);
|
|
63798
|
-
} catch {
|
|
63799
|
-
return "unknown-4xx";
|
|
63800
|
-
}
|
|
63801
|
-
}
|
|
63802
|
-
function classifyInner(raw) {
|
|
63803
|
-
if (raw == null)
|
|
63804
|
-
return "unknown-4xx";
|
|
63805
|
-
const obj = typeof raw === "object" ? raw : {};
|
|
63806
|
-
const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
|
|
63807
|
-
const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
|
|
63808
|
-
const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
|
|
63809
|
-
const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
|
|
63810
|
-
const sdkCode = extractString(obj, "error_code") ?? "";
|
|
63811
|
-
if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
|
|
63812
|
-
const msg = message.toLowerCase();
|
|
63813
|
-
if (msg.includes("expired") || msg.includes("refresh")) {
|
|
63814
|
-
return "credentials-expired";
|
|
63815
|
-
}
|
|
63816
|
-
return "credentials-invalid";
|
|
63817
|
-
}
|
|
63818
|
-
if (errorType === "invalid_api_key" || errorCode === "invalid_api_key" || sdkCode === "invalid_api_key" || message.toLowerCase().includes("invalid_api_key") || message.toLowerCase().includes("invalid api key")) {
|
|
63819
|
-
return "credentials-invalid";
|
|
63820
|
-
}
|
|
63821
|
-
if (errorType === "credit_balance_too_low" || errorCode === "credit_balance_too_low" || sdkCode === "credit_balance_too_low" || message.toLowerCase().includes("credit_balance_too_low") || message.toLowerCase().includes("credit balance")) {
|
|
63822
|
-
return "credit-exhausted";
|
|
63823
|
-
}
|
|
63824
|
-
if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
|
|
63825
|
-
return "rate-limited";
|
|
63826
|
-
}
|
|
63827
|
-
if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
|
|
63828
|
-
return "rate-limited";
|
|
63829
|
-
}
|
|
63830
|
-
if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
|
|
63831
|
-
return "agent-crashed";
|
|
63832
|
-
}
|
|
63833
|
-
if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
|
|
63834
|
-
return "agent-restarted-unexpectedly";
|
|
63835
|
-
}
|
|
63836
|
-
if (status != null) {
|
|
63837
|
-
if (status >= 400 && status < 500)
|
|
63838
|
-
return "unknown-4xx";
|
|
63839
|
-
if (status >= 500 && status < 600)
|
|
63840
|
-
return "unknown-5xx";
|
|
63841
|
-
}
|
|
63842
|
-
return "unknown-4xx";
|
|
63843
|
-
}
|
|
63844
|
-
function extractString(obj, key) {
|
|
63845
|
-
const v = obj[key];
|
|
63846
|
-
return typeof v === "string" && v.length > 0 ? v : null;
|
|
63847
|
-
}
|
|
63848
|
-
function extractNumber(obj, key) {
|
|
63849
|
-
const v = obj[key];
|
|
63850
|
-
return typeof v === "number" ? v : null;
|
|
63851
|
-
}
|
|
63852
|
-
function getNestedObj(obj, key) {
|
|
63853
|
-
const v = obj[key];
|
|
63854
|
-
return typeof v === "object" && v != null ? v : {};
|
|
63855
|
-
}
|
|
63856
|
-
var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
|
|
63857
|
-
var cooldownMap = new Map;
|
|
63858
|
-
|
|
63859
64043
|
// model-unavailable.ts
|
|
63860
64044
|
init_quota_check();
|
|
63861
64045
|
init_card_format();
|
|
@@ -63896,6 +64080,18 @@ function isLitellmProxyLocal429(text4) {
|
|
|
63896
64080
|
return true;
|
|
63897
64081
|
return litellmV3LimiterSignalPair.every((s) => lower.includes(s));
|
|
63898
64082
|
}
|
|
64083
|
+
function isLitellmProxyAuthMisconfig(text4) {
|
|
64084
|
+
if (typeof text4 !== "string" || text4.length === 0)
|
|
64085
|
+
return false;
|
|
64086
|
+
const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
|
|
64087
|
+
const lower = sample.toLowerCase();
|
|
64088
|
+
if (lower.includes("x-api-key header is required"))
|
|
64089
|
+
return true;
|
|
64090
|
+
const isAuthErr = lower.includes("authentication_error") || lower.includes("authenticationerror");
|
|
64091
|
+
if (!isAuthErr)
|
|
64092
|
+
return false;
|
|
64093
|
+
return lower.includes("fallback") && lower.includes("x-api-key");
|
|
64094
|
+
}
|
|
63899
64095
|
function parseLitellmLimitDetail(text4, parseTimeNow = new Date) {
|
|
63900
64096
|
const empty2 = { limitType: null, limit: null, currentUsage: null, resetAtMs: null };
|
|
63901
64097
|
if (typeof text4 !== "string" || text4.length === 0)
|
|
@@ -64140,6 +64336,83 @@ function parseRelativeDuration(s) {
|
|
|
64140
64336
|
return matched && total > 0 ? total : null;
|
|
64141
64337
|
}
|
|
64142
64338
|
|
|
64339
|
+
// operator-events.ts
|
|
64340
|
+
function classifyClaudeError(raw) {
|
|
64341
|
+
try {
|
|
64342
|
+
return classifyInner(raw);
|
|
64343
|
+
} catch {
|
|
64344
|
+
return "unknown-4xx";
|
|
64345
|
+
}
|
|
64346
|
+
}
|
|
64347
|
+
function classifyInner(raw) {
|
|
64348
|
+
if (raw == null)
|
|
64349
|
+
return "unknown-4xx";
|
|
64350
|
+
const obj = typeof raw === "object" ? raw : {};
|
|
64351
|
+
const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
|
|
64352
|
+
const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
|
|
64353
|
+
const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
|
|
64354
|
+
const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
|
|
64355
|
+
const sdkCode = extractString(obj, "error_code") ?? "";
|
|
64356
|
+
if (isLitellmProxyAuthMisconfig(`${errorType}
|
|
64357
|
+
${errorCode}
|
|
64358
|
+
${sdkCode}
|
|
64359
|
+
${message}`)) {
|
|
64360
|
+
return "proxy-misconfig";
|
|
64361
|
+
}
|
|
64362
|
+
if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
|
|
64363
|
+
const msg = message.toLowerCase();
|
|
64364
|
+
if (msg.includes("expired") || msg.includes("refresh")) {
|
|
64365
|
+
return "credentials-expired";
|
|
64366
|
+
}
|
|
64367
|
+
return "credentials-invalid";
|
|
64368
|
+
}
|
|
64369
|
+
if (errorType === "invalid_api_key" || errorCode === "invalid_api_key" || sdkCode === "invalid_api_key" || message.toLowerCase().includes("invalid_api_key") || message.toLowerCase().includes("invalid api key")) {
|
|
64370
|
+
return "credentials-invalid";
|
|
64371
|
+
}
|
|
64372
|
+
if (errorType === "credit_balance_too_low" || errorCode === "credit_balance_too_low" || sdkCode === "credit_balance_too_low" || message.toLowerCase().includes("credit_balance_too_low") || message.toLowerCase().includes("credit balance")) {
|
|
64373
|
+
return "credit-exhausted";
|
|
64374
|
+
}
|
|
64375
|
+
if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
|
|
64376
|
+
return "rate-limited";
|
|
64377
|
+
}
|
|
64378
|
+
if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
|
|
64379
|
+
return "rate-limited";
|
|
64380
|
+
}
|
|
64381
|
+
if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
|
|
64382
|
+
return "agent-crashed";
|
|
64383
|
+
}
|
|
64384
|
+
if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
|
|
64385
|
+
return "agent-restarted-unexpectedly";
|
|
64386
|
+
}
|
|
64387
|
+
if (status != null) {
|
|
64388
|
+
if (status >= 400 && status < 500)
|
|
64389
|
+
return "unknown-4xx";
|
|
64390
|
+
if (status >= 500 && status < 600)
|
|
64391
|
+
return "unknown-5xx";
|
|
64392
|
+
}
|
|
64393
|
+
return "unknown-4xx";
|
|
64394
|
+
}
|
|
64395
|
+
function extractString(obj, key) {
|
|
64396
|
+
const v = obj[key];
|
|
64397
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
64398
|
+
}
|
|
64399
|
+
function extractNumber(obj, key) {
|
|
64400
|
+
const v = obj[key];
|
|
64401
|
+
return typeof v === "number" ? v : null;
|
|
64402
|
+
}
|
|
64403
|
+
function getNestedObj(obj, key) {
|
|
64404
|
+
const v = obj[key];
|
|
64405
|
+
return typeof v === "object" && v != null ? v : {};
|
|
64406
|
+
}
|
|
64407
|
+
var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
|
|
64408
|
+
var cooldownMap = new Map;
|
|
64409
|
+
var OPERATOR_ACTIONABLE_KINDS = new Set([
|
|
64410
|
+
"credentials-expired",
|
|
64411
|
+
"credentials-invalid",
|
|
64412
|
+
"credit-exhausted",
|
|
64413
|
+
"proxy-misconfig"
|
|
64414
|
+
]);
|
|
64415
|
+
|
|
64143
64416
|
// session-tail.ts
|
|
64144
64417
|
function sanitizeCwdToProjectName(cwd) {
|
|
64145
64418
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
@@ -65541,13 +65814,17 @@ class AuthBrokerClient2 {
|
|
|
65541
65814
|
sock.destroy();
|
|
65542
65815
|
}
|
|
65543
65816
|
}
|
|
65544
|
-
async getCredentials(provider) {
|
|
65817
|
+
async getCredentials(provider, account) {
|
|
65545
65818
|
const base = {
|
|
65546
65819
|
v: PROTOCOL_VERSION,
|
|
65547
65820
|
id: randomUUID4(),
|
|
65548
65821
|
op: "get-credentials"
|
|
65549
65822
|
};
|
|
65550
|
-
|
|
65823
|
+
let req = base;
|
|
65824
|
+
if (provider !== undefined)
|
|
65825
|
+
req = { ...req, provider };
|
|
65826
|
+
if (account !== undefined)
|
|
65827
|
+
req = { ...req, account };
|
|
65551
65828
|
const data = await this.send(req);
|
|
65552
65829
|
return data;
|
|
65553
65830
|
}
|
|
@@ -66757,6 +67034,20 @@ function renderOperatorEvent(ev) {
|
|
|
66757
67034
|
]
|
|
66758
67035
|
}
|
|
66759
67036
|
};
|
|
67037
|
+
case "proxy-misconfig":
|
|
67038
|
+
return {
|
|
67039
|
+
text: [
|
|
67040
|
+
`\uD83D\uDEE0\ufe0f **Model-gateway auth misconfig** for **${agent}**.`,
|
|
67041
|
+
detail ? `_${detail}_` : "",
|
|
67042
|
+
`The local LiteLLM proxy re-dispatched a fallback without forwarding the OAuth header (keyless deployment \u2192 Anthropic 401). Fix the proxy fallback config \u2014 this is NOT a login problem.`
|
|
67043
|
+
].filter(Boolean).join(`
|
|
67044
|
+
`),
|
|
67045
|
+
keyboard: {
|
|
67046
|
+
inline_keyboard: [
|
|
67047
|
+
[{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
|
|
67048
|
+
]
|
|
67049
|
+
}
|
|
67050
|
+
};
|
|
66760
67051
|
case "credit-exhausted":
|
|
66761
67052
|
return {
|
|
66762
67053
|
text: [
|
|
@@ -66917,6 +67208,55 @@ function shouldEmitOperatorEvent(agent, kind, now = Date.now(), cooldownMs = DEF
|
|
|
66917
67208
|
cooldownMap2.set(key, now);
|
|
66918
67209
|
return true;
|
|
66919
67210
|
}
|
|
67211
|
+
var OPERATOR_ACTIONABLE_KINDS2 = new Set([
|
|
67212
|
+
"credentials-expired",
|
|
67213
|
+
"credentials-invalid",
|
|
67214
|
+
"credit-exhausted",
|
|
67215
|
+
"proxy-misconfig"
|
|
67216
|
+
]);
|
|
67217
|
+
function isOperatorActionableKind(kind) {
|
|
67218
|
+
return OPERATOR_ACTIONABLE_KINDS2.has(kind);
|
|
67219
|
+
}
|
|
67220
|
+
function decideOperatorEventAudience(kind, allowFrom, operatorChatId) {
|
|
67221
|
+
if (!isOperatorActionableKind(kind)) {
|
|
67222
|
+
return { operatorChats: [...allowFrom], userNoticeChats: [] };
|
|
67223
|
+
}
|
|
67224
|
+
const operator = operatorChatId != null && allowFrom.includes(operatorChatId) ? operatorChatId : allowFrom[0];
|
|
67225
|
+
const operatorChats = operator != null ? [operator] : [];
|
|
67226
|
+
const userNoticeChats = allowFrom.filter((c) => c !== operator);
|
|
67227
|
+
return { operatorChats, userNoticeChats };
|
|
67228
|
+
}
|
|
67229
|
+
function renderUserFacingFailureNotice() {
|
|
67230
|
+
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.";
|
|
67231
|
+
}
|
|
67232
|
+
|
|
67233
|
+
// pending-user-notice.ts
|
|
67234
|
+
var PENDING_USER_NOTICE_TTL_MS = 10 * 60000;
|
|
67235
|
+
|
|
67236
|
+
class PendingUserNoticeGate {
|
|
67237
|
+
pending = [];
|
|
67238
|
+
schedule(notice) {
|
|
67239
|
+
this.prune(notice.atMs);
|
|
67240
|
+
this.pending = this.pending.filter((p) => p.agent !== notice.agent);
|
|
67241
|
+
this.pending.push(notice);
|
|
67242
|
+
}
|
|
67243
|
+
resolveTurnEnd(turnDeliveredReply, now = Date.now()) {
|
|
67244
|
+
this.prune(now);
|
|
67245
|
+
const out = turnDeliveredReply ? [] : [...this.pending];
|
|
67246
|
+
this.pending = [];
|
|
67247
|
+
return out;
|
|
67248
|
+
}
|
|
67249
|
+
hasPending(now = Date.now()) {
|
|
67250
|
+
return this.pending.some((p) => now - p.atMs < PENDING_USER_NOTICE_TTL_MS);
|
|
67251
|
+
}
|
|
67252
|
+
prune(now) {
|
|
67253
|
+
this.pending = this.pending.filter((p) => now - p.atMs < PENDING_USER_NOTICE_TTL_MS);
|
|
67254
|
+
}
|
|
67255
|
+
reset() {
|
|
67256
|
+
this.pending = [];
|
|
67257
|
+
}
|
|
67258
|
+
}
|
|
67259
|
+
var pendingUserNoticeGate = new PendingUserNoticeGate;
|
|
66920
67260
|
|
|
66921
67261
|
// operator-events-history.ts
|
|
66922
67262
|
var EVENT_TTL_MS = 60 * 60 * 1000;
|
|
@@ -67030,7 +67370,13 @@ function parseLlmError(raw, retryState) {
|
|
|
67030
67370
|
}
|
|
67031
67371
|
function classifyKindAndSource(text4) {
|
|
67032
67372
|
const lower = text4.toLowerCase();
|
|
67373
|
+
if (isLitellmProxyAuthMisconfig(text4)) {
|
|
67374
|
+
return { kind: "infra_misconfig", source: "litellm-local" };
|
|
67375
|
+
}
|
|
67033
67376
|
const claudeKind = classifyClaudeError({ message: text4, type: text4 });
|
|
67377
|
+
if (claudeKind === "proxy-misconfig") {
|
|
67378
|
+
return { kind: "infra_misconfig", source: "litellm-local" };
|
|
67379
|
+
}
|
|
67034
67380
|
if (claudeKind === "credentials-expired" || claudeKind === "credentials-invalid") {
|
|
67035
67381
|
return { kind: "auth", source: "anthropic" };
|
|
67036
67382
|
}
|
|
@@ -67073,6 +67419,8 @@ function buildCoreText(kind, source) {
|
|
|
67073
67419
|
return "Usage limit reached on this Claude subscription.";
|
|
67074
67420
|
case "auth":
|
|
67075
67421
|
return "Claude login needs re-authentication.";
|
|
67422
|
+
case "infra_misconfig":
|
|
67423
|
+
return "Local model-gateway auth misconfig (proxy fallback dropped the OAuth header).";
|
|
67076
67424
|
case "transient":
|
|
67077
67425
|
return source === "network" ? "Couldn't reach Anthropic (network) \u2014 retrying automatically." : "A temporary upstream hiccup \u2014 retrying automatically.";
|
|
67078
67426
|
case "unknown":
|
|
@@ -67118,6 +67466,8 @@ function buildRecommendation(parsed, tz) {
|
|
|
67118
67466
|
switch (parsed.kind) {
|
|
67119
67467
|
case "auth":
|
|
67120
67468
|
return "\u2192 Re-authenticate this account to continue.";
|
|
67469
|
+
case "infra_misconfig":
|
|
67470
|
+
return "\u2192 Fix the LiteLLM proxy fallback config (deployment missing OAuth passthrough).";
|
|
67121
67471
|
case "quota_wall": {
|
|
67122
67472
|
const reset2 = formatResetClock(parsed.resetAt, tz);
|
|
67123
67473
|
return reset2 ? `\u2192 Switch to another account, or wait for the quota to reset at ${reset2}.` : "\u2192 Switch to another account, or wait for the quota to reset.";
|
|
@@ -67159,6 +67509,8 @@ function kindEmoji(kind) {
|
|
|
67159
67509
|
return "\u26a0\ufe0f";
|
|
67160
67510
|
case "auth":
|
|
67161
67511
|
return "\uD83D\uDD11";
|
|
67512
|
+
case "infra_misconfig":
|
|
67513
|
+
return "\uD83D\uDEE0\ufe0f";
|
|
67162
67514
|
case "transient":
|
|
67163
67515
|
return "\uD83C\uDF10";
|
|
67164
67516
|
case "unknown":
|
|
@@ -71318,6 +71670,30 @@ function isValidModelArg(arg) {
|
|
|
71318
71670
|
function isSrModel(name) {
|
|
71319
71671
|
return name.startsWith("sr-");
|
|
71320
71672
|
}
|
|
71673
|
+
function parseModelSwitchTarget(reason) {
|
|
71674
|
+
const m = reason.match(/\/model\s+(\S+)/);
|
|
71675
|
+
return m ? m[1] : null;
|
|
71676
|
+
}
|
|
71677
|
+
function modelFamilyToken(token) {
|
|
71678
|
+
const t = token.trim().toLowerCase();
|
|
71679
|
+
if (t.startsWith("claude-")) {
|
|
71680
|
+
const family = t.slice("claude-".length).split("-").filter((p) => p.length > 0)[0];
|
|
71681
|
+
return family ?? t;
|
|
71682
|
+
}
|
|
71683
|
+
return t;
|
|
71684
|
+
}
|
|
71685
|
+
function classifyModelSwitchConfirmation(input) {
|
|
71686
|
+
const { reason, launched, configured } = input;
|
|
71687
|
+
const isApplyBoot = launched.length > 0 && launched !== configured;
|
|
71688
|
+
if (isApplyBoot)
|
|
71689
|
+
return { kind: "applied", launched };
|
|
71690
|
+
const target = parseModelSwitchTarget(reason);
|
|
71691
|
+
const revertedTo = launched.length > 0 ? launched : configured;
|
|
71692
|
+
if (target != null && target.toLowerCase() !== "default" && modelFamilyToken(target) !== modelFamilyToken(revertedTo)) {
|
|
71693
|
+
return { kind: "not-applied", target, revertedTo };
|
|
71694
|
+
}
|
|
71695
|
+
return { kind: "default", launched: revertedTo };
|
|
71696
|
+
}
|
|
71321
71697
|
function parseModelCommand(text4) {
|
|
71322
71698
|
const m = text4.match(/^\/model(?:@[A-Za-z0-9_]+)?(?:\s+([\s\S]*))?$/);
|
|
71323
71699
|
if (!m)
|
|
@@ -71761,6 +72137,7 @@ var MODEL_CALLBACK_ALIAS2 = "mdl:alias:";
|
|
|
71761
72137
|
// gateway/session-model-file.ts
|
|
71762
72138
|
var SESSION_MODEL_FILE = ".session-model";
|
|
71763
72139
|
var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
|
|
72140
|
+
var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
|
|
71764
72141
|
function atomicWrite(path2, content3) {
|
|
71765
72142
|
const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
|
|
71766
72143
|
writeFileSync21(tmp, content3, "utf8");
|
|
@@ -71792,6 +72169,14 @@ function clearSessionModelFile(agentDir) {
|
|
|
71792
72169
|
rmSync4(join30(agentDir, SESSION_MODEL_FILE), { force: true });
|
|
71793
72170
|
} catch {}
|
|
71794
72171
|
}
|
|
72172
|
+
function consumeSessionModelCarrierOnHealthyBoot(agentDir) {
|
|
72173
|
+
try {
|
|
72174
|
+
rmSync4(join30(agentDir, SESSION_MODEL_FILE), { force: true });
|
|
72175
|
+
} catch {}
|
|
72176
|
+
try {
|
|
72177
|
+
rmSync4(join30(agentDir, SESSION_MODEL_BOOT_ATTEMPTS_FILE), { force: true });
|
|
72178
|
+
} catch {}
|
|
72179
|
+
}
|
|
71795
72180
|
function restoreSessionModelFileRaw(agentDir, raw) {
|
|
71796
72181
|
if (raw == null) {
|
|
71797
72182
|
clearSessionModelFile(agentDir);
|
|
@@ -72165,14 +72550,7 @@ init_merge();
|
|
|
72165
72550
|
|
|
72166
72551
|
// ../src/agents/scaffold.ts
|
|
72167
72552
|
init_merge();
|
|
72168
|
-
|
|
72169
|
-
// ../src/config/timezone.ts
|
|
72170
|
-
var CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
72171
|
-
"UTC",
|
|
72172
|
-
"Etc/UTC",
|
|
72173
|
-
"Etc/Universal",
|
|
72174
|
-
"Universal"
|
|
72175
|
-
]);
|
|
72553
|
+
init_timezone();
|
|
72176
72554
|
|
|
72177
72555
|
// ../src/cli/agent-config.ts
|
|
72178
72556
|
import { join as join31 } from "node:path";
|
|
@@ -72922,6 +73300,7 @@ init_schema();
|
|
|
72922
73300
|
init_paths();
|
|
72923
73301
|
init_overlay_loader();
|
|
72924
73302
|
init_merge();
|
|
73303
|
+
init_timezone();
|
|
72925
73304
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
72926
73305
|
import { readFileSync as readFileSync27, existsSync as existsSync27 } from "node:fs";
|
|
72927
73306
|
import { homedir as homedir11 } from "node:os";
|
|
@@ -73049,8 +73428,30 @@ function loadConfig2(configPath) {
|
|
|
73049
73428
|
if (notionIssues.length > 0) {
|
|
73050
73429
|
throw new ConfigError2(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
|
|
73051
73430
|
}
|
|
73431
|
+
validateAllTimezones2(config, filePath);
|
|
73052
73432
|
return config;
|
|
73053
73433
|
}
|
|
73434
|
+
function validateAllTimezones2(config, filePath) {
|
|
73435
|
+
const issues = [];
|
|
73436
|
+
const check = (zone, where) => {
|
|
73437
|
+
if (zone == null)
|
|
73438
|
+
return;
|
|
73439
|
+
if (!isResolvableTimezone(zone)) {
|
|
73440
|
+
issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists \u2014 check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
|
|
73441
|
+
}
|
|
73442
|
+
};
|
|
73443
|
+
check(config.switchroom?.timezone, "switchroom.timezone");
|
|
73444
|
+
check(config.defaults?.timezone, "defaults.timezone");
|
|
73445
|
+
for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
|
|
73446
|
+
check(profile?.timezone, `profiles.${profileName}.timezone`);
|
|
73447
|
+
}
|
|
73448
|
+
for (const [agentName3, agentRaw] of Object.entries(config.agents)) {
|
|
73449
|
+
check(agentRaw?.timezone, `agents.${agentName3}.timezone`);
|
|
73450
|
+
}
|
|
73451
|
+
if (issues.length > 0) {
|
|
73452
|
+
throw new ConfigError2(`Invalid timezone configuration in ${filePath}`, issues);
|
|
73453
|
+
}
|
|
73454
|
+
}
|
|
73054
73455
|
function validateAllCronTopicAliases2(config, filePath) {
|
|
73055
73456
|
const issues = [];
|
|
73056
73457
|
for (const [agentName3, agentRaw] of Object.entries(config.agents)) {
|
|
@@ -84169,10 +84570,10 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
|
|
|
84169
84570
|
}
|
|
84170
84571
|
|
|
84171
84572
|
// ../src/build-info.ts
|
|
84172
|
-
var VERSION = "0.18.
|
|
84173
|
-
var COMMIT_SHA = "
|
|
84174
|
-
var COMMIT_DATE = "2026-07-
|
|
84175
|
-
var LATEST_PR =
|
|
84573
|
+
var VERSION = "0.18.30";
|
|
84574
|
+
var COMMIT_SHA = "7729c674";
|
|
84575
|
+
var COMMIT_DATE = "2026-07-17T10:52:43+10:00";
|
|
84576
|
+
var LATEST_PR = 3293;
|
|
84176
84577
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
84177
84578
|
|
|
84178
84579
|
// gateway/boot-version.ts
|
|
@@ -86203,6 +86604,7 @@ var TOPIC_ID = process.env.TELEGRAM_TOPIC_ID ? Number(process.env.TELEGRAM_TOPIC
|
|
|
86203
86604
|
var AGENT_ADMIN = process.env.SWITCHROOM_AGENT_ADMIN === "true";
|
|
86204
86605
|
var bot = new import_grammy13.Bot(TOKEN);
|
|
86205
86606
|
installTgPostLogger(bot);
|
|
86607
|
+
installUpdateTap(bot, (line) => process.stderr.write(line));
|
|
86206
86608
|
var lastGetUpdatesHeartbeatMs = Date.now();
|
|
86207
86609
|
bot.api.config.use(async (prev, method, payload, signal) => {
|
|
86208
86610
|
try {
|
|
@@ -87712,6 +88114,7 @@ function endCurrentTurnAtomic(turn, opts) {
|
|
|
87712
88114
|
turn.narrativeGate?.teardown();
|
|
87713
88115
|
purgeReactionTracking(statusKey(turn.sessionChatId, turn.sessionThreadId), turn);
|
|
87714
88116
|
armNoReplyDrainTimer(turn);
|
|
88117
|
+
flushPendingUserFailureNotices(turn.finalAnswerDelivered || turn.replyCalled);
|
|
87715
88118
|
return turnEndedAt;
|
|
87716
88119
|
}
|
|
87717
88120
|
function maybeProactiveCompact() {
|
|
@@ -89157,7 +89560,12 @@ function emitGatewayOperatorEvent(event) {
|
|
|
89157
89560
|
const opEventSupergroup = resolveAgentSupergroupChatId();
|
|
89158
89561
|
process.stderr.write(`telegram gateway: operator-event posting agent=${agent} kind=${kind} to ${access.allowFrom.length} chat(s)` + (opEventTopic != null ? ` topic=${opEventTopic}` : "") + `
|
|
89159
89562
|
`);
|
|
89160
|
-
|
|
89563
|
+
const { operatorChats, userNoticeChats } = decideOperatorEventAudience(kind, access.allowFrom, access.allowFrom[0]);
|
|
89564
|
+
if (userNoticeChats.length > 0) {
|
|
89565
|
+
process.stderr.write(`telegram gateway: operator-event operator-only routing agent=${agent} kind=${kind} operatorChats=${operatorChats.length} userNoticeChats=${userNoticeChats.length}
|
|
89566
|
+
`);
|
|
89567
|
+
}
|
|
89568
|
+
for (const chat_id of operatorChats) {
|
|
89161
89569
|
const opEventThread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: opEventTopic, supergroupChatId: opEventSupergroup });
|
|
89162
89570
|
const opts = {
|
|
89163
89571
|
...renderedKeyboard ? { reply_markup: renderedKeyboard } : {},
|
|
@@ -89177,6 +89585,38 @@ function emitGatewayOperatorEvent(event) {
|
|
|
89177
89585
|
`);
|
|
89178
89586
|
});
|
|
89179
89587
|
}
|
|
89588
|
+
if (userNoticeChats.length > 0) {
|
|
89589
|
+
pendingUserNoticeGate.schedule({
|
|
89590
|
+
chatIds: userNoticeChats,
|
|
89591
|
+
text: renderUserFacingFailureNotice(),
|
|
89592
|
+
agent,
|
|
89593
|
+
kind,
|
|
89594
|
+
atMs: Date.now()
|
|
89595
|
+
});
|
|
89596
|
+
process.stderr.write(`telegram gateway: operator-event user-notice deferred to turn-end agent=${agent} kind=${kind} chats=${userNoticeChats.length}
|
|
89597
|
+
`);
|
|
89598
|
+
}
|
|
89599
|
+
}
|
|
89600
|
+
function flushPendingUserFailureNotices(turnDeliveredReply) {
|
|
89601
|
+
const notices = pendingUserNoticeGate.resolveTurnEnd(turnDeliveredReply);
|
|
89602
|
+
if (notices.length === 0)
|
|
89603
|
+
return;
|
|
89604
|
+
const noticeTopic = resolveAgentOutboundTopic({ kind: "compact-watchdog" });
|
|
89605
|
+
const noticeSupergroup = resolveAgentSupergroupChatId();
|
|
89606
|
+
for (const notice of notices) {
|
|
89607
|
+
process.stderr.write(`telegram gateway: user-notice flush (turn died reply-less) agent=${notice.agent} kind=${notice.kind} chats=${notice.chatIds.length}
|
|
89608
|
+
`);
|
|
89609
|
+
for (const chat_id of notice.chatIds) {
|
|
89610
|
+
const thread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: noticeTopic, supergroupChatId: noticeSupergroup });
|
|
89611
|
+
const opts = {
|
|
89612
|
+
...thread != null ? { message_thread_id: thread } : {}
|
|
89613
|
+
};
|
|
89614
|
+
bot.api.sendRichMessage(chat_id, richMessage2(notice.text), opts).catch((e) => {
|
|
89615
|
+
process.stderr.write(`telegram gateway: user-notice send to ${chat_id} failed agent=${notice.agent} kind=${notice.kind}: ${e}
|
|
89616
|
+
`);
|
|
89617
|
+
});
|
|
89618
|
+
}
|
|
89619
|
+
}
|
|
89180
89620
|
}
|
|
89181
89621
|
function postLegacyBanner(chatId, threadId, ackMessageId, ageSec, site) {
|
|
89182
89622
|
const text5 = `\uD83C\uDF9B\uFE0F Switchroom restarted \u2014 ready. (took ~${ageSec}s)`;
|
|
@@ -89687,6 +90127,11 @@ function ensureIssuesCard(chatId, threadId) {
|
|
|
89687
90127
|
}
|
|
89688
90128
|
process.stderr.write(`telegram gateway: wrote PID file ${GATEWAY_PID_PATH} pid=${process.pid} startedAt=${GATEWAY_STARTED_AT_MS}
|
|
89689
90129
|
`);
|
|
90130
|
+
{
|
|
90131
|
+
const carrierAgentDir = resolveAgentDirFromEnv();
|
|
90132
|
+
if (carrierAgentDir != null)
|
|
90133
|
+
consumeSessionModelCarrierOnHealthyBoot(carrierAgentDir);
|
|
90134
|
+
}
|
|
89690
90135
|
runBootPinCleanupAndDmSweep();
|
|
89691
90136
|
} catch (err) {
|
|
89692
90137
|
process.stderr.write(`telegram gateway: boot.lock_acquire_failed err=${err.message} agent=${SWITCHROOM_AGENT_NAME}
|
|
@@ -89695,6 +90140,11 @@ function ensureIssuesCard(chatId, threadId) {
|
|
|
89695
90140
|
writePidFile(GATEWAY_PID_PATH, { pid: process.pid, startedAtMs: GATEWAY_STARTED_AT_MS });
|
|
89696
90141
|
process.stderr.write(`telegram gateway: wrote PID file ${GATEWAY_PID_PATH} pid=${process.pid} startedAt=${GATEWAY_STARTED_AT_MS} (mutex-fallback)
|
|
89697
90142
|
`);
|
|
90143
|
+
{
|
|
90144
|
+
const carrierAgentDir = resolveAgentDirFromEnv();
|
|
90145
|
+
if (carrierAgentDir != null)
|
|
90146
|
+
consumeSessionModelCarrierOnHealthyBoot(carrierAgentDir);
|
|
90147
|
+
}
|
|
89698
90148
|
runBootPinCleanupAndDmSweep();
|
|
89699
90149
|
} catch (writeErr) {
|
|
89700
90150
|
process.stderr.write(`telegram gateway: writePidFile failed: ${writeErr}
|
|
@@ -96288,9 +96738,10 @@ async function dispatchShortVerbViaHostd(ctx, req, label, legacyArgs) {
|
|
|
96288
96738
|
await switchroomReply(ctx, `\u274C **${escapeHtmlForTg2(label)} failed via hostd** (result=${escapeHtmlForTg2(hostdResp.result)}):
|
|
96289
96739
|
` + preBlock(stripAnsi3(errBody)), { html: true });
|
|
96290
96740
|
}
|
|
96291
|
-
async function runSwitchroomCommand(ctx, args, label, classification = "query") {
|
|
96741
|
+
async function runSwitchroomCommand(ctx, args, label, classification = "query", transformOutput) {
|
|
96292
96742
|
try {
|
|
96293
|
-
const
|
|
96743
|
+
const stripped = stripAnsi3(switchroomExec(args));
|
|
96744
|
+
const output = transformOutput ? transformOutput(stripped) : stripped;
|
|
96294
96745
|
const formatted = formatSwitchroomOutput(output);
|
|
96295
96746
|
if (formatted) {
|
|
96296
96747
|
await switchroomReply(ctx, preBlock(formatted), { html: true, classification });
|
|
@@ -98677,7 +99128,8 @@ bot.command("logs", async (ctx) => {
|
|
|
98677
99128
|
}
|
|
98678
99129
|
const lines = linesArg ? parseInt(linesArg, 10) : 20;
|
|
98679
99130
|
const lineCount = isNaN(lines) || lines < 1 ? 20 : Math.min(lines, 200);
|
|
98680
|
-
|
|
99131
|
+
const tz = resolveEnvTimezone2();
|
|
99132
|
+
await runSwitchroomCommand(ctx, ["agent", "logs", name, "--lines", String(lineCount), "--timestamps"], `logs ${name}`, "heavy", (raw) => renderLogTimestampsLocal(raw, tz));
|
|
98681
99133
|
});
|
|
98682
99134
|
bot.command("memory", async (ctx) => {
|
|
98683
99135
|
if (!isAuthorizedSender(ctx))
|
|
@@ -100073,6 +100525,7 @@ bot.on("message:pinned_message", async (ctx) => {
|
|
|
100073
100525
|
`);
|
|
100074
100526
|
}
|
|
100075
100527
|
});
|
|
100528
|
+
installUnhandledMessageCatchAll(bot, (ctx, text5) => handleInboundCoalesced(ctx, text5, undefined), (line) => process.stderr.write(line));
|
|
100076
100529
|
var reactionsCfg = null;
|
|
100077
100530
|
var reactionHourCap = null;
|
|
100078
100531
|
var reactionDebounce = null;
|
|
@@ -100833,12 +101286,23 @@ var didOneTimeSetup = false;
|
|
|
100833
101286
|
`);
|
|
100834
101287
|
if (modelSwitchReason != null && modelSwitchMarkerChat) {
|
|
100835
101288
|
const chat = modelSwitchMarkerChat;
|
|
100836
|
-
const
|
|
100837
|
-
|
|
100838
|
-
|
|
100839
|
-
|
|
100840
|
-
})
|
|
101289
|
+
const confirmation = classifyModelSwitchConfirmation({
|
|
101290
|
+
reason: modelSwitchReason,
|
|
101291
|
+
launched,
|
|
101292
|
+
configured
|
|
101293
|
+
});
|
|
101294
|
+
const hasSessionModelAlert = existsSync50(join55(smAgentDir, ".session-model-alert"));
|
|
101295
|
+
if (confirmation.kind === "not-applied" && hasSessionModelAlert) {
|
|
101296
|
+
process.stderr.write(`telegram gateway: gw /model relaunch applied \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=${getMyAgentName()} target=${confirmation.target}
|
|
101297
|
+
`);
|
|
101298
|
+
} else {
|
|
101299
|
+
const body = confirmation.kind === "applied" ? `\u2705 Now running \`${confirmation.launched}\` \u2014 session-only, reverts to the configured model on the next restart. Fresh session; memory and the handoff briefing carry the context.` : confirmation.kind === "not-applied" ? `\u26A0\uFE0F Your switch to \`${confirmation.target}\` didn't apply \u2014 the agent reverted to \`${confirmation.revertedTo}\` (the apply-boot didn't complete). Re-issue \`/model ${confirmation.target}\` to try again.` : `\u2705 Now running \`${confirmation.launched}\` (the configured default) \u2014 fresh session; memory and the handoff briefing carry the context.`;
|
|
101300
|
+
lockedBot.api.sendMessage(chat.chatId, body, {
|
|
101301
|
+
parse_mode: "Markdown",
|
|
101302
|
+
...chat.threadId != null ? { message_thread_id: chat.threadId } : {}
|
|
101303
|
+
}).catch((err) => process.stderr.write(`telegram gateway: model-switch confirmation send failed: ${err?.message ?? String(err)}
|
|
100841
101304
|
`));
|
|
101305
|
+
}
|
|
100842
101306
|
}
|
|
100843
101307
|
} catch {}
|
|
100844
101308
|
}
|