switchroom 0.13.52 → 0.13.53
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 +317 -132
- package/dist/auth-broker/index.js +494 -156
- package/dist/cli/drive-write-pretool.mjs +18 -3
- package/dist/cli/switchroom.js +2353 -994
- package/dist/host-control/main.js +246 -127
- package/dist/vault/approvals/kernel-server.js +8269 -8146
- package/dist/vault/broker/server.js +2811 -2688
- package/package.json +1 -1
- package/skills/switchroom-status/SKILL.md +8 -6
- package/telegram-plugin/chat-lock.ts +87 -19
- package/telegram-plugin/dist/gateway/gateway.js +752 -120
- package/telegram-plugin/gateway/disconnect-flush.ts +32 -0
- package/telegram-plugin/gateway/gateway.ts +258 -55
- package/telegram-plugin/gateway/inbound-coalesce.ts +19 -6
- package/telegram-plugin/stream-reply-handler.ts +10 -8
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +116 -0
- package/telegram-plugin/tests/inbound-coalesce.test.ts +20 -4
- package/telegram-plugin/tests/outbound-ordering.test.ts +228 -0
- package/telegram-plugin/tests/parallel-turns-deadlock-fix.test.ts +217 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +65 -8
- package/telegram-plugin/typing-wrap.ts +43 -21
- package/profiles/default/CLAUDE.md +0 -122
|
@@ -16105,11 +16105,11 @@ function decodeResponse(line) {
|
|
|
16105
16105
|
}
|
|
16106
16106
|
return ResponseSchema.parse(parsed);
|
|
16107
16107
|
}
|
|
16108
|
-
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
16108
|
+
var MAX_FRAME_BYTES, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema, ResponseSchema;
|
|
16109
16109
|
var init_protocol = __esm(() => {
|
|
16110
16110
|
init_zod();
|
|
16111
16111
|
MAX_FRAME_BYTES = 64 * 1024;
|
|
16112
|
-
ProviderNameSchema = exports_external.enum(["anthropic", "google"]);
|
|
16112
|
+
ProviderNameSchema = exports_external.enum(["anthropic", "google", "microsoft"]);
|
|
16113
16113
|
GetCredentialsRequestSchema = exports_external.object({
|
|
16114
16114
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
16115
16115
|
op: exports_external.literal("get-credentials"),
|
|
@@ -16162,9 +16162,24 @@ var init_protocol = __esm(() => {
|
|
|
16162
16162
|
tokenType: exports_external.literal("Bearer")
|
|
16163
16163
|
})
|
|
16164
16164
|
});
|
|
16165
|
+
MicrosoftCredentialsSchema = exports_external.object({
|
|
16166
|
+
microsoftOauth: exports_external.object({
|
|
16167
|
+
accessToken: exports_external.string(),
|
|
16168
|
+
refreshToken: exports_external.string(),
|
|
16169
|
+
expiresAt: exports_external.number(),
|
|
16170
|
+
scope: exports_external.string(),
|
|
16171
|
+
clientId: exports_external.string(),
|
|
16172
|
+
accountEmail: exports_external.string(),
|
|
16173
|
+
tokenType: exports_external.literal("Bearer"),
|
|
16174
|
+
tenantId: exports_external.string(),
|
|
16175
|
+
accountType: exports_external.enum(["personal", "work"]),
|
|
16176
|
+
homeAccountId: exports_external.string()
|
|
16177
|
+
})
|
|
16178
|
+
});
|
|
16165
16179
|
ProviderCredentialsSchema = exports_external.union([
|
|
16166
16180
|
AnthropicCredentialsSchema,
|
|
16167
|
-
GoogleCredentialsSchema
|
|
16181
|
+
GoogleCredentialsSchema,
|
|
16182
|
+
MicrosoftCredentialsSchema
|
|
16168
16183
|
]);
|
|
16169
16184
|
AddAccountRequestSchema = exports_external.object({
|
|
16170
16185
|
v: exports_external.literal(PROTOCOL_VERSION),
|
|
@@ -23592,7 +23607,7 @@ var init_dist = __esm(() => {
|
|
|
23592
23607
|
});
|
|
23593
23608
|
|
|
23594
23609
|
// ../src/config/schema.ts
|
|
23595
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
|
|
23610
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, MicrosoftWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
|
|
23596
23611
|
var init_schema = __esm(() => {
|
|
23597
23612
|
init_zod();
|
|
23598
23613
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -23609,7 +23624,11 @@ var init_schema = __esm(() => {
|
|
|
23609
23624
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
23610
23625
|
prompt: exports_external.string().describe("Prompt to send at the scheduled time"),
|
|
23611
23626
|
model: exports_external.string().optional().describe("DEPRECATED / IGNORED. Pre-v0.8 the singleton scheduler ran each " + "task as an isolated headless invocation and could set --model per " + "task. Post cron-fold-in (v0.8) the fire is injected into the agent's " + "running session, so it always uses the agent's configured model " + "\u2014 this field has no effect. Accepted (optional) only so existing " + "configs keep validating; set the model at the agent level instead. " + "See docs/scheduling.md."),
|
|
23612
|
-
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault key names this cron task may read via the vault-broker daemon. " + "Empty by default \u2014 broker requests for unlisted keys are denied. " + "Note: this is misconfiguration protection (a typo in cron-A doesn't " + "accidentally read cron-B's keys) rather than a security boundary \u2014 " + "anyone who can edit cron scripts can also edit switchroom.yaml, and " + "anyone with the vault passphrase can read the vault file directly. " + "See docs/configuration.md for the full framing.")
|
|
23627
|
+
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault key names this cron task may read via the vault-broker daemon. " + "Empty by default \u2014 broker requests for unlisted keys are denied. " + "Note: this is misconfiguration protection (a typo in cron-A doesn't " + "accidentally read cron-B's keys) rather than a security boundary \u2014 " + "anyone who can edit cron scripts can also edit switchroom.yaml, and " + "anyone with the vault passphrase can read the vault file directly. " + "See docs/configuration.md for the full framing."),
|
|
23628
|
+
topic: exports_external.union([
|
|
23629
|
+
exports_external.string().min(1, "topic alias must be non-empty"),
|
|
23630
|
+
exports_external.number().int().positive("topic ID must be a positive integer")
|
|
23631
|
+
]).optional().describe("Forum topic this cron fires into when the owning agent is in " + "supergroup-owned mode (channels.telegram.chat_id set). Either a " + 'string alias resolved against `topic_aliases` (e.g. "planning") ' + "or a numeric topic ID. Falls back to the agent's `default_topic_id` " + "when unset. Ignored for agents in fleet-shared or dm_only mode. " + "Alias-resolution happens at config-load \u2014 typos surface immediately. " + "See docs/rfcs/supergroup-mode.md.")
|
|
23613
23632
|
});
|
|
23614
23633
|
AgentSoulSchema = exports_external.object({
|
|
23615
23634
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
@@ -23721,8 +23740,35 @@ var init_schema = __esm(() => {
|
|
|
23721
23740
|
}).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
|
|
23722
23741
|
webhook_rate_limit: exports_external.object({
|
|
23723
23742
|
rpm: exports_external.number().int().positive()
|
|
23724
|
-
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
|
|
23725
|
-
|
|
23743
|
+
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit."),
|
|
23744
|
+
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
23745
|
+
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
23746
|
+
topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
|
|
23747
|
+
}).optional().superRefine((tg, ctx) => {
|
|
23748
|
+
if (!tg)
|
|
23749
|
+
return;
|
|
23750
|
+
if (tg.chat_id != null && tg.default_topic_id == null) {
|
|
23751
|
+
ctx.addIssue({
|
|
23752
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23753
|
+
message: "`channels.telegram.chat_id` requires `default_topic_id` \u2014 supergroup-mode agents need a fallback topic for unclassified outbounds.",
|
|
23754
|
+
path: ["default_topic_id"]
|
|
23755
|
+
});
|
|
23756
|
+
}
|
|
23757
|
+
if (tg.default_topic_id != null && tg.chat_id == null) {
|
|
23758
|
+
ctx.addIssue({
|
|
23759
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23760
|
+
message: "`channels.telegram.default_topic_id` requires `chat_id` \u2014 default_topic_id is only meaningful when the agent owns its own supergroup.",
|
|
23761
|
+
path: ["chat_id"]
|
|
23762
|
+
});
|
|
23763
|
+
}
|
|
23764
|
+
if (tg.topic_aliases != null && tg.chat_id == null) {
|
|
23765
|
+
ctx.addIssue({
|
|
23766
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23767
|
+
message: "`channels.telegram.topic_aliases` requires `chat_id` \u2014 aliases only resolve in supergroup-owned mode.",
|
|
23768
|
+
path: ["topic_aliases"]
|
|
23769
|
+
});
|
|
23770
|
+
}
|
|
23771
|
+
});
|
|
23726
23772
|
ChannelsSchema = exports_external.object({
|
|
23727
23773
|
telegram: TelegramChannelSchema
|
|
23728
23774
|
}).optional();
|
|
@@ -23739,6 +23785,12 @@ var init_schema = __esm(() => {
|
|
|
23739
23785
|
approvers: exports_external.array(ApproverIdSchema).min(1).describe("Array of numeric Telegram user IDs authorized to approve drive onboarding. " + "At least one must be specified."),
|
|
23740
23786
|
tier: GoogleWorkspaceTierSchema.optional().describe("RFC G Phase 1: which upstream MCP tier to expose. " + "core (default) = ~16 tools (Drive+Docs+Sheets+Calendar). " + "extended = ~40 tools (+Slides, Forms, Tasks, Chat). " + "complete = ~60+ tools (+Gmail; not recommended yet \u2014 see RFC G \u00a75).")
|
|
23741
23787
|
}).optional();
|
|
23788
|
+
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
23789
|
+
microsoft_client_id: exports_external.string().min(1).describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id')."),
|
|
23790
|
+
microsoft_client_secret: exports_external.string().min(1).optional().describe("Microsoft OAuth client secret. Optional \u2014 public-client apps " + "(Mobile + Desktop platform with 'Allow public client flows' " + "enabled) work without a secret; confidential clients pass " + "one. Either literal or vault reference e.g. " + "'vault:microsoft-oauth-client-secret'."),
|
|
23791
|
+
authority: exports_external.string().url().optional().describe("Microsoft authority endpoint. Defaults to " + "'https://login.microsoftonline.com/common' which accepts both " + "personal MSA and work/school tenants. Override only for " + "single-tenant deployments."),
|
|
23792
|
+
org_mode: exports_external.boolean().optional().describe("Opt-in to Teams + SharePoint surfaces (RFC \u00a76.4). When true, " + "the v1 scope set adds Sites.ReadWrite.All AND the launcher " + "spawns softeria with --org-mode. Defaults to false \u2014 personal " + "MSA + standard work surfaces only. Flipping for an existing " + "consented account requires re-running 'auth microsoft account " + "add --replace' to consent the additional scope.")
|
|
23793
|
+
}).optional();
|
|
23742
23794
|
AgentGoogleWorkspaceConfigSchema = exports_external.object({
|
|
23743
23795
|
account: exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
23744
23796
|
message: "google_workspace.account must be a Google account email like " + "'alice@example.com' (colons not allowed)"
|
|
@@ -23746,6 +23798,12 @@ var init_schema = __esm(() => {
|
|
|
23746
23798
|
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."),
|
|
23747
23799
|
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.")
|
|
23748
23800
|
}).optional();
|
|
23801
|
+
AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
23802
|
+
account: exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
23803
|
+
message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
23804
|
+
}).transform((v) => v.trim().toLowerCase()).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)."),
|
|
23805
|
+
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).")
|
|
23806
|
+
}).optional();
|
|
23749
23807
|
ReactionsSchema = exports_external.object({
|
|
23750
23808
|
enabled: exports_external.boolean().optional().describe("Master switch for the reaction-trigger path. When false, " + "reactions are still persisted via recordReaction but never " + "dispatched to the agent as synthetic inbound turns. Default true."),
|
|
23751
23809
|
trigger_emojis: exports_external.array(exports_external.string()).optional().describe("Emoji allowlist that triggers a synthetic inbound when reacted " + "to a bot message. Default ['\uD83D\uDC4E', '\u274c', '\uD83D\uDC4D', '\u2705']. Cascade " + "mode: REPLACE (not union) \u2014 setting this at a layer replaces " + "lower layers entirely, so an operator can narrow to [] to " + "disable triggering without flipping `enabled`."),
|
|
@@ -23881,6 +23939,7 @@ var init_schema = __esm(() => {
|
|
|
23881
23939
|
code_repos: exports_external.array(CodeRepoEntrySchema).optional().describe("Git repositories this agent is allowed to claim worktrees from. " + "Each entry provides a short name alias, a source path, and an " + "optional concurrency cap (default 5). When code_repos is set, " + "claim_worktree accepts the alias as the repo argument. " + "Absolute paths may always be passed regardless of this list."),
|
|
23882
23940
|
drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent \u2014 they live at the top level."),
|
|
23883
23941
|
google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides \u2014 currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent \u2014 they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
|
|
23942
|
+
microsoft_workspace: AgentMicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Per-agent Microsoft Workspace " + "override \u2014 pins the Microsoft account this agent reads via the " + "auth-broker (must be a key in top-level `microsoft_accounts:` with " + "this agent in its `enabled_for[]`) and optionally overrides org_mode. " + "microsoft_client_id/secret are not per-agent."),
|
|
23884
23943
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
23885
23944
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
23886
23945
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
@@ -23895,6 +23954,33 @@ var init_schema = __esm(() => {
|
|
|
23895
23954
|
pids_limit: exports_external.number().int().positive().optional(),
|
|
23896
23955
|
cpus: exports_external.number().positive().optional()
|
|
23897
23956
|
}).optional()
|
|
23957
|
+
}).superRefine((agent, ctx) => {
|
|
23958
|
+
if (agent.dm_only !== true)
|
|
23959
|
+
return;
|
|
23960
|
+
const tg = agent.channels?.telegram;
|
|
23961
|
+
if (tg == null)
|
|
23962
|
+
return;
|
|
23963
|
+
if (tg.chat_id != null) {
|
|
23964
|
+
ctx.addIssue({
|
|
23965
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23966
|
+
message: "`dm_only: true` forbids `channels.telegram.chat_id` \u2014 DM-only agents have their own private chat, not a supergroup.",
|
|
23967
|
+
path: ["channels", "telegram", "chat_id"]
|
|
23968
|
+
});
|
|
23969
|
+
}
|
|
23970
|
+
if (tg.default_topic_id != null) {
|
|
23971
|
+
ctx.addIssue({
|
|
23972
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23973
|
+
message: "`dm_only: true` forbids `channels.telegram.default_topic_id` \u2014 DMs don't have forum topics.",
|
|
23974
|
+
path: ["channels", "telegram", "default_topic_id"]
|
|
23975
|
+
});
|
|
23976
|
+
}
|
|
23977
|
+
if (tg.topic_aliases != null) {
|
|
23978
|
+
ctx.addIssue({
|
|
23979
|
+
code: exports_external.ZodIssueCode.custom,
|
|
23980
|
+
message: "`dm_only: true` forbids `channels.telegram.topic_aliases` \u2014 DMs don't have forum topics.",
|
|
23981
|
+
path: ["channels", "telegram", "topic_aliases"]
|
|
23982
|
+
});
|
|
23983
|
+
}
|
|
23898
23984
|
});
|
|
23899
23985
|
TelegramConfigSchema = exports_external.object({
|
|
23900
23986
|
bot_token: exports_external.string().describe("Telegram bot token or vault reference (e.g., 'vault:telegram-bot-token')"),
|
|
@@ -23976,6 +24062,7 @@ var init_schema = __esm(() => {
|
|
|
23976
24062
|
}).optional().describe("Switchroom-auth-broker configuration (RFC H). Fleet-wide active account, " + "fallback order, admin-agent ACL, and ephemeral-consumer surface. " + "Required from the v0.8+ schema onwards; pre-v0.8 fleets are migrated " + "in-place by `switchroom apply` (see src/auth/migrate-schema.ts)."),
|
|
23977
24063
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
23978
24064
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration \u2014 " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
24065
|
+
microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration \u2014 OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
|
|
23979
24066
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
23980
24067
|
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
23981
24068
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Currently scopes the opt-in flag and rate cap for the new " + "`config_propose_edit` verb (PR 1a \u2014 disabled by default)."),
|
|
@@ -23986,6 +24073,13 @@ var init_schema = __esm(() => {
|
|
|
23986
24073
|
message: "Agent name must match the standard agent-name pattern"
|
|
23987
24074
|
})).describe("Agent slugs that may read this account's vault slots " + "(`google:<account>:refresh_token` etc). Per-agent ACL is " + "enforced at the broker, not at the agent identity layer \u2014 " + "the agent still authenticates via socket-path-as-identity " + "per RFC D \u00a74.1, broker just gates the cross-agent token share.")
|
|
23988
24075
|
})).optional().describe("RFC G Phase 2: per-Google-account ACL for vault slots holding " + "OAuth refresh tokens. Maps account email \u2192 list of agents " + "permitted to read that account's slots. Written by `switchroom " + "auth google enable|disable` (Phase 3); read by the broker on " + "every Google slot access. Replaces RFC D's per-agent vault slot " + "scope (which can't express 'two agents share one Google account')."),
|
|
24076
|
+
microsoft_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
24077
|
+
message: "Account key must be a Microsoft account email like 'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
24078
|
+
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
24079
|
+
enabled_for: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
24080
|
+
message: "Agent name must match the standard agent-name pattern"
|
|
24081
|
+
})).describe("Agent slugs that may read this Microsoft account's broker " + "credentials. Per-agent ACL enforced at the broker; agents " + "still authenticate via socket-path-as-identity, broker just " + "gates the cross-agent token share. Mirrors google_accounts.")
|
|
24082
|
+
})).optional().describe("RFC #1873: per-Microsoft-account ACL. Maps account email \u2192 list of " + "agents permitted to use that account's broker credentials. Written " + "by `switchroom auth microsoft enable|disable`; read by the broker " + "on get-credentials with provider=microsoft."),
|
|
23989
24083
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
23990
24084
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
23991
24085
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
@@ -24165,6 +24259,317 @@ var init_overlay_loader = __esm(() => {
|
|
|
24165
24259
|
OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
24166
24260
|
});
|
|
24167
24261
|
|
|
24262
|
+
// ../src/config/merge.ts
|
|
24263
|
+
function dedupe(items) {
|
|
24264
|
+
const seen = new Set;
|
|
24265
|
+
const out = [];
|
|
24266
|
+
for (const item of items) {
|
|
24267
|
+
if (seen.has(item))
|
|
24268
|
+
continue;
|
|
24269
|
+
seen.add(item);
|
|
24270
|
+
out.push(item);
|
|
24271
|
+
}
|
|
24272
|
+
return out;
|
|
24273
|
+
}
|
|
24274
|
+
function deepMergeJson(base, override) {
|
|
24275
|
+
if (override === undefined)
|
|
24276
|
+
return base;
|
|
24277
|
+
if (base === undefined)
|
|
24278
|
+
return override;
|
|
24279
|
+
if (typeof base !== "object" || base === null || Array.isArray(base) || typeof override !== "object" || override === null || Array.isArray(override)) {
|
|
24280
|
+
return override;
|
|
24281
|
+
}
|
|
24282
|
+
const out = { ...base };
|
|
24283
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24284
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype")
|
|
24285
|
+
continue;
|
|
24286
|
+
out[k] = deepMergeJson(out[k], v);
|
|
24287
|
+
}
|
|
24288
|
+
return out;
|
|
24289
|
+
}
|
|
24290
|
+
function resolveAgentConfig(defaults, profiles, agent) {
|
|
24291
|
+
if (!mergeAgentConfig.suppressDeprecationLogs && !mergeAgentConfig.notifiedWorkerIsolationMove && defaults?.subagents?.worker?.isolation === "worktree") {
|
|
24292
|
+
mergeAgentConfig.notifiedWorkerIsolationMove = true;
|
|
24293
|
+
console.warn("[switchroom] NOTICE: defaults.subagents.worker.isolation moved to the " + "`coding` profile in switchroom 0.6.6 (#682). Agents extending coding " + "still get worktree-isolated workers; other agents would have hard-failed " + "the first time they delegated. See CHANGELOG.");
|
|
24294
|
+
}
|
|
24295
|
+
const name = agent.extends;
|
|
24296
|
+
const profile = name && profiles ? profiles[name] : undefined;
|
|
24297
|
+
if (!profile) {
|
|
24298
|
+
return mergeAgentConfig(defaults, agent);
|
|
24299
|
+
}
|
|
24300
|
+
const { extends: _unused, ...profileWithoutExtends } = profile;
|
|
24301
|
+
const layered = mergeAgentConfig(defaults, profileWithoutExtends);
|
|
24302
|
+
return mergeAgentConfig(layered, agent);
|
|
24303
|
+
}
|
|
24304
|
+
function foldDeprecatedTelegramFields(config) {
|
|
24305
|
+
const c = config;
|
|
24306
|
+
const root = c;
|
|
24307
|
+
const deprecations = [];
|
|
24308
|
+
const hasRoot = root.voice_in !== undefined || root.telegraph !== undefined || root.webhook_sources !== undefined;
|
|
24309
|
+
if (!hasRoot)
|
|
24310
|
+
return { config: c, deprecations };
|
|
24311
|
+
const channels = { ...c.channels ?? {} };
|
|
24312
|
+
const tg = { ...channels.telegram ?? {} };
|
|
24313
|
+
if (root.voice_in !== undefined) {
|
|
24314
|
+
if (tg.voice_in === undefined)
|
|
24315
|
+
tg.voice_in = root.voice_in;
|
|
24316
|
+
deprecations.push("voice_in at the agent root is deprecated; move under channels.telegram.voice_in (#596).");
|
|
24317
|
+
}
|
|
24318
|
+
if (root.telegraph !== undefined) {
|
|
24319
|
+
if (tg.telegraph === undefined)
|
|
24320
|
+
tg.telegraph = root.telegraph;
|
|
24321
|
+
deprecations.push("telegraph at the agent root is deprecated; move under channels.telegram.telegraph (#596).");
|
|
24322
|
+
}
|
|
24323
|
+
if (root.webhook_sources !== undefined) {
|
|
24324
|
+
if (tg.webhook_sources === undefined)
|
|
24325
|
+
tg.webhook_sources = root.webhook_sources;
|
|
24326
|
+
deprecations.push("webhook_sources at the agent root is deprecated; move under channels.telegram.webhook_sources (#596).");
|
|
24327
|
+
}
|
|
24328
|
+
channels.telegram = tg;
|
|
24329
|
+
const { voice_in: _vi, telegraph: _tg, webhook_sources: _ws, ...rest } = root;
|
|
24330
|
+
return {
|
|
24331
|
+
config: { ...rest, channels },
|
|
24332
|
+
deprecations
|
|
24333
|
+
};
|
|
24334
|
+
}
|
|
24335
|
+
function mergeAgentConfig(defaultsIn, agentIn) {
|
|
24336
|
+
const { config: agent, deprecations: agentDeprecations } = foldDeprecatedTelegramFields(agentIn);
|
|
24337
|
+
const defaultsMigration = defaultsIn ? foldDeprecatedTelegramFields(defaultsIn) : null;
|
|
24338
|
+
const defaults = defaultsMigration?.config;
|
|
24339
|
+
const allDeprecations = [
|
|
24340
|
+
...agentDeprecations,
|
|
24341
|
+
...defaultsMigration?.deprecations ?? []
|
|
24342
|
+
];
|
|
24343
|
+
if (allDeprecations.length > 0 && !mergeAgentConfig.suppressDeprecationLogs) {
|
|
24344
|
+
for (const msg of allDeprecations) {
|
|
24345
|
+
console.warn(`[switchroom] DEPRECATION: ${msg}`);
|
|
24346
|
+
}
|
|
24347
|
+
}
|
|
24348
|
+
if (!defaults)
|
|
24349
|
+
return agent;
|
|
24350
|
+
const merged = { ...agent };
|
|
24351
|
+
if (defaults.bot_token !== undefined && merged.bot_token === undefined) {
|
|
24352
|
+
merged.bot_token = defaults.bot_token;
|
|
24353
|
+
}
|
|
24354
|
+
if (defaults.timezone !== undefined && merged.timezone === undefined) {
|
|
24355
|
+
merged.timezone = defaults.timezone;
|
|
24356
|
+
}
|
|
24357
|
+
if (defaults.model !== undefined && merged.model === undefined) {
|
|
24358
|
+
merged.model = defaults.model;
|
|
24359
|
+
}
|
|
24360
|
+
if (defaults.dangerous_mode !== undefined && merged.dangerous_mode === undefined) {
|
|
24361
|
+
merged.dangerous_mode = defaults.dangerous_mode;
|
|
24362
|
+
}
|
|
24363
|
+
if (defaults.network_isolation !== undefined && merged.network_isolation === undefined) {
|
|
24364
|
+
merged.network_isolation = defaults.network_isolation;
|
|
24365
|
+
}
|
|
24366
|
+
if (defaults.thinking_effort !== undefined && merged.thinking_effort === undefined) {
|
|
24367
|
+
merged.thinking_effort = defaults.thinking_effort;
|
|
24368
|
+
}
|
|
24369
|
+
if (defaults.permission_mode !== undefined && merged.permission_mode === undefined) {
|
|
24370
|
+
merged.permission_mode = defaults.permission_mode;
|
|
24371
|
+
}
|
|
24372
|
+
if (defaults.fallback_model !== undefined && merged.fallback_model === undefined) {
|
|
24373
|
+
merged.fallback_model = defaults.fallback_model;
|
|
24374
|
+
}
|
|
24375
|
+
if (defaults.tools || merged.tools) {
|
|
24376
|
+
const dAllow = defaults.tools?.allow ?? [];
|
|
24377
|
+
const aAllow = merged.tools?.allow ?? [];
|
|
24378
|
+
const dDeny = defaults.tools?.deny ?? [];
|
|
24379
|
+
const aDeny = merged.tools?.deny ?? [];
|
|
24380
|
+
merged.tools = {
|
|
24381
|
+
allow: dedupe([...dAllow, ...aAllow]),
|
|
24382
|
+
deny: dedupe([...dDeny, ...aDeny])
|
|
24383
|
+
};
|
|
24384
|
+
}
|
|
24385
|
+
if (defaults.soul || merged.soul) {
|
|
24386
|
+
const base = defaults.soul ?? {};
|
|
24387
|
+
const override = merged.soul ?? {};
|
|
24388
|
+
const combined = { ...base };
|
|
24389
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24390
|
+
if (v !== undefined)
|
|
24391
|
+
combined[k] = v;
|
|
24392
|
+
}
|
|
24393
|
+
merged.soul = combined;
|
|
24394
|
+
}
|
|
24395
|
+
if (defaults.memory || merged.memory) {
|
|
24396
|
+
const base = defaults.memory ?? {};
|
|
24397
|
+
const override = merged.memory ?? {};
|
|
24398
|
+
const combined = { ...base };
|
|
24399
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24400
|
+
if (v === undefined)
|
|
24401
|
+
continue;
|
|
24402
|
+
if (k === "recall" && base.recall && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
24403
|
+
combined[k] = { ...base.recall, ...v };
|
|
24404
|
+
} else {
|
|
24405
|
+
combined[k] = v;
|
|
24406
|
+
}
|
|
24407
|
+
}
|
|
24408
|
+
merged.memory = combined;
|
|
24409
|
+
}
|
|
24410
|
+
if (defaults.mcp_servers || merged.mcp_servers) {
|
|
24411
|
+
merged.mcp_servers = {
|
|
24412
|
+
...defaults.mcp_servers ?? {},
|
|
24413
|
+
...merged.mcp_servers ?? {}
|
|
24414
|
+
};
|
|
24415
|
+
}
|
|
24416
|
+
const dBundled = defaults.bundled_skills;
|
|
24417
|
+
const mBundled = merged.bundled_skills;
|
|
24418
|
+
if (dBundled || mBundled) {
|
|
24419
|
+
merged.bundled_skills = {
|
|
24420
|
+
...dBundled ?? {},
|
|
24421
|
+
...mBundled ?? {}
|
|
24422
|
+
};
|
|
24423
|
+
}
|
|
24424
|
+
if (defaults.hooks || merged.hooks) {
|
|
24425
|
+
const result = {};
|
|
24426
|
+
const dHooks = defaults.hooks ?? {};
|
|
24427
|
+
const aHooks = merged.hooks ?? {};
|
|
24428
|
+
const events = new Set([
|
|
24429
|
+
...Object.keys(dHooks),
|
|
24430
|
+
...Object.keys(aHooks)
|
|
24431
|
+
]);
|
|
24432
|
+
for (const event of events) {
|
|
24433
|
+
const d = dHooks[event] ?? [];
|
|
24434
|
+
const a = aHooks[event] ?? [];
|
|
24435
|
+
result[event] = [...d, ...a];
|
|
24436
|
+
}
|
|
24437
|
+
merged.hooks = result;
|
|
24438
|
+
}
|
|
24439
|
+
if (defaults.env || merged.env) {
|
|
24440
|
+
merged.env = {
|
|
24441
|
+
...defaults.env ?? {},
|
|
24442
|
+
...merged.env ?? {}
|
|
24443
|
+
};
|
|
24444
|
+
}
|
|
24445
|
+
if (defaults.subagents || merged.subagents) {
|
|
24446
|
+
const dSub = defaults.subagents ?? {};
|
|
24447
|
+
const mSub = merged.subagents ?? {};
|
|
24448
|
+
const out = { ...dSub };
|
|
24449
|
+
for (const [name, override] of Object.entries(mSub)) {
|
|
24450
|
+
const base = dSub[name];
|
|
24451
|
+
if (base && typeof base === "object" && override && typeof override === "object") {
|
|
24452
|
+
const combined = { ...base };
|
|
24453
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24454
|
+
if (v !== undefined)
|
|
24455
|
+
combined[k] = v;
|
|
24456
|
+
}
|
|
24457
|
+
out[name] = combined;
|
|
24458
|
+
} else {
|
|
24459
|
+
out[name] = override;
|
|
24460
|
+
}
|
|
24461
|
+
}
|
|
24462
|
+
merged.subagents = out;
|
|
24463
|
+
}
|
|
24464
|
+
if (defaults.session || merged.session) {
|
|
24465
|
+
const base = defaults.session ?? {};
|
|
24466
|
+
const override = merged.session ?? {};
|
|
24467
|
+
const combined = { ...base };
|
|
24468
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24469
|
+
if (v !== undefined)
|
|
24470
|
+
combined[k] = v;
|
|
24471
|
+
}
|
|
24472
|
+
merged.session = combined;
|
|
24473
|
+
}
|
|
24474
|
+
if (defaults.session_continuity || merged.session_continuity) {
|
|
24475
|
+
const base = defaults.session_continuity ?? {};
|
|
24476
|
+
const override = merged.session_continuity ?? {};
|
|
24477
|
+
const combined = { ...base };
|
|
24478
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24479
|
+
if (v !== undefined)
|
|
24480
|
+
combined[k] = v;
|
|
24481
|
+
}
|
|
24482
|
+
merged.session_continuity = combined;
|
|
24483
|
+
}
|
|
24484
|
+
if (merged.release === undefined && defaults.release !== undefined) {
|
|
24485
|
+
merged.release = defaults.release;
|
|
24486
|
+
}
|
|
24487
|
+
if (defaults.channels || merged.channels) {
|
|
24488
|
+
const dChan = defaults.channels ?? {};
|
|
24489
|
+
const aChan = merged.channels ?? {};
|
|
24490
|
+
const combined = { ...dChan };
|
|
24491
|
+
for (const [key, value] of Object.entries(aChan)) {
|
|
24492
|
+
if (value === undefined)
|
|
24493
|
+
continue;
|
|
24494
|
+
const base = combined[key] ?? {};
|
|
24495
|
+
const override = value;
|
|
24496
|
+
const field = { ...base };
|
|
24497
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24498
|
+
if (v !== undefined)
|
|
24499
|
+
field[k] = v;
|
|
24500
|
+
}
|
|
24501
|
+
combined[key] = field;
|
|
24502
|
+
}
|
|
24503
|
+
merged.channels = combined;
|
|
24504
|
+
}
|
|
24505
|
+
if (defaults.system_prompt_append || merged.system_prompt_append) {
|
|
24506
|
+
const parts = [
|
|
24507
|
+
defaults.system_prompt_append,
|
|
24508
|
+
merged.system_prompt_append
|
|
24509
|
+
].filter((p) => typeof p === "string" && p.length > 0);
|
|
24510
|
+
merged.system_prompt_append = parts.join(`
|
|
24511
|
+
|
|
24512
|
+
`);
|
|
24513
|
+
}
|
|
24514
|
+
if (defaults.schedule && defaults.schedule.length > 0) {
|
|
24515
|
+
merged.schedule = [...defaults.schedule, ...merged.schedule ?? []];
|
|
24516
|
+
}
|
|
24517
|
+
if (defaults.skills || merged.skills) {
|
|
24518
|
+
const d = defaults.skills ?? [];
|
|
24519
|
+
const a = merged.skills ?? [];
|
|
24520
|
+
merged.skills = dedupe([...d, ...a]);
|
|
24521
|
+
}
|
|
24522
|
+
if (defaults.settings_raw || merged.settings_raw) {
|
|
24523
|
+
merged.settings_raw = deepMergeJson(defaults.settings_raw ?? {}, merged.settings_raw ?? {});
|
|
24524
|
+
}
|
|
24525
|
+
if (defaults.claude_md_raw || merged.claude_md_raw) {
|
|
24526
|
+
const parts = [defaults.claude_md_raw, merged.claude_md_raw].filter((p) => typeof p === "string" && p.length > 0);
|
|
24527
|
+
merged.claude_md_raw = parts.join(`
|
|
24528
|
+
|
|
24529
|
+
`);
|
|
24530
|
+
}
|
|
24531
|
+
if (defaults.cli_args || merged.cli_args) {
|
|
24532
|
+
merged.cli_args = [
|
|
24533
|
+
...defaults.cli_args ?? [],
|
|
24534
|
+
...merged.cli_args ?? []
|
|
24535
|
+
];
|
|
24536
|
+
}
|
|
24537
|
+
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
24538
|
+
const d = defaults.extra_stable_files ?? [];
|
|
24539
|
+
const a = merged.extra_stable_files ?? [];
|
|
24540
|
+
merged.extra_stable_files = dedupe([...d, ...a]);
|
|
24541
|
+
}
|
|
24542
|
+
const dReactions = defaults.reactions;
|
|
24543
|
+
const mReactions = merged.reactions;
|
|
24544
|
+
if (dReactions || mReactions) {
|
|
24545
|
+
const base = dReactions ?? {};
|
|
24546
|
+
const override = mReactions ?? {};
|
|
24547
|
+
const combined = { ...base };
|
|
24548
|
+
for (const [k, v] of Object.entries(override)) {
|
|
24549
|
+
if (v !== undefined)
|
|
24550
|
+
combined[k] = v;
|
|
24551
|
+
}
|
|
24552
|
+
merged.reactions = combined;
|
|
24553
|
+
}
|
|
24554
|
+
if (defaults.resources || merged.resources) {
|
|
24555
|
+
const d = defaults.resources ?? {};
|
|
24556
|
+
const a = merged.resources ?? {};
|
|
24557
|
+
merged.resources = { ...d, ...a };
|
|
24558
|
+
}
|
|
24559
|
+
if (defaults.experimental || merged.experimental) {
|
|
24560
|
+
const d = defaults.experimental ?? {};
|
|
24561
|
+
const a = merged.experimental ?? {};
|
|
24562
|
+
merged.experimental = { ...d, ...a };
|
|
24563
|
+
}
|
|
24564
|
+
return merged;
|
|
24565
|
+
}
|
|
24566
|
+
var init_merge = __esm(() => {
|
|
24567
|
+
((mergeAgentConfig) => {
|
|
24568
|
+
mergeAgentConfig.suppressDeprecationLogs = false;
|
|
24569
|
+
mergeAgentConfig.notifiedWorkerIsolationMove = false;
|
|
24570
|
+
})(mergeAgentConfig ||= {});
|
|
24571
|
+
});
|
|
24572
|
+
|
|
24168
24573
|
// ../src/config/loader.ts
|
|
24169
24574
|
import { readFileSync as readFileSync5, existsSync as existsSync9 } from "node:fs";
|
|
24170
24575
|
import { homedir as homedir5 } from "node:os";
|
|
@@ -24278,8 +24683,34 @@ function loadConfig(configPath) {
|
|
|
24278
24683
|
throw err;
|
|
24279
24684
|
}
|
|
24280
24685
|
applyAgentOverlays(config);
|
|
24686
|
+
validateAllCronTopicAliases(config, filePath);
|
|
24281
24687
|
return config;
|
|
24282
24688
|
}
|
|
24689
|
+
function validateAllCronTopicAliases(config, filePath) {
|
|
24690
|
+
const issues = [];
|
|
24691
|
+
for (const [agentName3, agentRaw] of Object.entries(config.agents)) {
|
|
24692
|
+
if (!agentRaw)
|
|
24693
|
+
continue;
|
|
24694
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentRaw);
|
|
24695
|
+
const schedule = resolved.schedule ?? [];
|
|
24696
|
+
if (schedule.length === 0)
|
|
24697
|
+
continue;
|
|
24698
|
+
const tg = resolved.channels?.telegram;
|
|
24699
|
+
const aliases = new Set(Object.keys(tg?.topic_aliases ?? {}));
|
|
24700
|
+
for (const entry of schedule) {
|
|
24701
|
+
if (entry.topic == null)
|
|
24702
|
+
continue;
|
|
24703
|
+
if (typeof entry.topic === "number")
|
|
24704
|
+
continue;
|
|
24705
|
+
if (!aliases.has(entry.topic)) {
|
|
24706
|
+
issues.push(` agents.${agentName3}.schedule cron \`${entry.cron}\`: ` + `topic alias "${entry.topic}" is not defined in ` + `channels.telegram.topic_aliases.`);
|
|
24707
|
+
}
|
|
24708
|
+
}
|
|
24709
|
+
}
|
|
24710
|
+
if (issues.length > 0) {
|
|
24711
|
+
throw new ConfigError(`Cron \`topic:\` alias references unknown topic_aliases in ${filePath}`, issues);
|
|
24712
|
+
}
|
|
24713
|
+
}
|
|
24283
24714
|
function resolvePath(pathStr) {
|
|
24284
24715
|
return resolveDualPath(pathStr);
|
|
24285
24716
|
}
|
|
@@ -24290,6 +24721,7 @@ var init_loader = __esm(() => {
|
|
|
24290
24721
|
init_schema();
|
|
24291
24722
|
init_paths();
|
|
24292
24723
|
init_overlay_loader();
|
|
24724
|
+
init_merge();
|
|
24293
24725
|
ConfigError = class ConfigError extends Error {
|
|
24294
24726
|
details;
|
|
24295
24727
|
constructor(message, details) {
|
|
@@ -30801,8 +31233,9 @@ function createInboundCoalescer(opts) {
|
|
|
30801
31233
|
}
|
|
30802
31234
|
};
|
|
30803
31235
|
}
|
|
30804
|
-
function inboundCoalesceKey(chatId, userId) {
|
|
30805
|
-
|
|
31236
|
+
function inboundCoalesceKey(chatId, threadId, userId) {
|
|
31237
|
+
const t = threadId == null || threadId === 0 ? "_" : String(threadId);
|
|
31238
|
+
return `${chatId}:${t}:${userId}`;
|
|
30806
31239
|
}
|
|
30807
31240
|
|
|
30808
31241
|
// status-reactions.ts
|
|
@@ -31270,29 +31703,41 @@ function prettifyServer(name) {
|
|
|
31270
31703
|
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
31271
31704
|
}
|
|
31272
31705
|
|
|
31706
|
+
// gateway/chat-key.ts
|
|
31707
|
+
function chatKey(chatId, threadId) {
|
|
31708
|
+
const t = threadId == null || threadId === 0 ? "_" : String(threadId);
|
|
31709
|
+
return `${chatId}:${t}`;
|
|
31710
|
+
}
|
|
31711
|
+
function chatKeyWithSuffix(chatId, threadId, suffix) {
|
|
31712
|
+
return `${chatKey(chatId, threadId)}:${suffix}`;
|
|
31713
|
+
}
|
|
31714
|
+
|
|
31273
31715
|
// typing-wrap.ts
|
|
31274
31716
|
function createTypingWrapper(deps) {
|
|
31275
31717
|
const debounceMs = deps.debounceMs ?? 500;
|
|
31276
31718
|
const pending = new Map;
|
|
31277
|
-
const
|
|
31719
|
+
const activeLanes = new Set;
|
|
31278
31720
|
return {
|
|
31279
|
-
onToolUse(toolUseId, chatId, toolName) {
|
|
31721
|
+
onToolUse(toolUseId, chatId, toolName, threadId) {
|
|
31280
31722
|
if (!toolUseId)
|
|
31281
31723
|
return;
|
|
31282
31724
|
if (deps.isSurfaceTool(toolName))
|
|
31283
31725
|
return;
|
|
31726
|
+
const tid = threadId ?? null;
|
|
31727
|
+
const lane = chatKey(chatId, tid);
|
|
31284
31728
|
const prior = pending.get(toolUseId);
|
|
31285
31729
|
if (prior) {
|
|
31286
31730
|
clearTimeout(prior.timer);
|
|
31287
31731
|
if (prior.started)
|
|
31288
|
-
deps.stopTypingLoop(prior.chatId);
|
|
31732
|
+
deps.stopTypingLoop(prior.chatId, prior.threadId);
|
|
31289
31733
|
pending.delete(toolUseId);
|
|
31290
31734
|
}
|
|
31291
|
-
if (!
|
|
31292
|
-
deps.startTypingLoop(chatId);
|
|
31293
|
-
|
|
31735
|
+
if (!activeLanes.has(lane)) {
|
|
31736
|
+
deps.startTypingLoop(chatId, tid);
|
|
31737
|
+
activeLanes.add(lane);
|
|
31294
31738
|
const entry2 = {
|
|
31295
31739
|
chatId,
|
|
31740
|
+
threadId: tid,
|
|
31296
31741
|
started: true,
|
|
31297
31742
|
timer: setTimeout(() => {}, 0)
|
|
31298
31743
|
};
|
|
@@ -31301,9 +31746,10 @@ function createTypingWrapper(deps) {
|
|
|
31301
31746
|
}
|
|
31302
31747
|
const entry = {
|
|
31303
31748
|
chatId,
|
|
31749
|
+
threadId: tid,
|
|
31304
31750
|
started: false,
|
|
31305
31751
|
timer: setTimeout(() => {
|
|
31306
|
-
deps.startTypingLoop(chatId);
|
|
31752
|
+
deps.startTypingLoop(chatId, tid);
|
|
31307
31753
|
entry.started = true;
|
|
31308
31754
|
}, debounceMs)
|
|
31309
31755
|
};
|
|
@@ -31317,8 +31763,8 @@ function createTypingWrapper(deps) {
|
|
|
31317
31763
|
return;
|
|
31318
31764
|
clearTimeout(entry.timer);
|
|
31319
31765
|
if (entry.started) {
|
|
31320
|
-
deps.stopTypingLoop(entry.chatId);
|
|
31321
|
-
|
|
31766
|
+
deps.stopTypingLoop(entry.chatId, entry.threadId);
|
|
31767
|
+
activeLanes.delete(chatKey(entry.chatId, entry.threadId));
|
|
31322
31768
|
}
|
|
31323
31769
|
pending.delete(toolUseId);
|
|
31324
31770
|
},
|
|
@@ -31326,10 +31772,10 @@ function createTypingWrapper(deps) {
|
|
|
31326
31772
|
for (const entry of pending.values()) {
|
|
31327
31773
|
clearTimeout(entry.timer);
|
|
31328
31774
|
if (entry.started)
|
|
31329
|
-
deps.stopTypingLoop(entry.chatId);
|
|
31775
|
+
deps.stopTypingLoop(entry.chatId, entry.threadId);
|
|
31330
31776
|
}
|
|
31331
31777
|
pending.clear();
|
|
31332
|
-
|
|
31778
|
+
activeLanes.clear();
|
|
31333
31779
|
}
|
|
31334
31780
|
};
|
|
31335
31781
|
}
|
|
@@ -32004,10 +32450,8 @@ function buildAccentHeader(accent) {
|
|
|
32004
32450
|
}
|
|
32005
32451
|
}
|
|
32006
32452
|
function streamKey2(chatId, threadId, lane, turnKey) {
|
|
32007
|
-
const
|
|
32008
|
-
|
|
32009
|
-
const withLane = lane != null && lane.length > 0 ? `${base}:${lane}` : base;
|
|
32010
|
-
return turnKey != null && turnKey.length > 0 ? `${withLane}:${turnKey}` : withLane;
|
|
32453
|
+
const base = lane != null && lane.length > 0 ? chatKeyWithSuffix(chatId, threadId ?? null, lane) : chatKey(chatId, threadId ?? null);
|
|
32454
|
+
return turnKey != null && turnKey.length > 0 ? `${base}:${turnKey}` : base;
|
|
32011
32455
|
}
|
|
32012
32456
|
async function handleStreamReply(args, state, deps) {
|
|
32013
32457
|
const chat_id = args.chat_id;
|
|
@@ -32202,14 +32646,14 @@ async function handleStreamReply(args, state, deps) {
|
|
|
32202
32646
|
// chat-lock.ts
|
|
32203
32647
|
function createChatLock() {
|
|
32204
32648
|
const chains = new Map;
|
|
32205
|
-
function run(
|
|
32206
|
-
const prior = chains.get(
|
|
32649
|
+
function run(key, fn) {
|
|
32650
|
+
const prior = chains.get(key) ?? Promise.resolve();
|
|
32207
32651
|
const next = prior.then(fn, fn);
|
|
32208
32652
|
const tracked = next.finally(() => {
|
|
32209
|
-
if (chains.get(
|
|
32210
|
-
chains.delete(
|
|
32653
|
+
if (chains.get(key) === tracked)
|
|
32654
|
+
chains.delete(key);
|
|
32211
32655
|
});
|
|
32212
|
-
chains.set(
|
|
32656
|
+
chains.set(key, tracked);
|
|
32213
32657
|
return next;
|
|
32214
32658
|
}
|
|
32215
32659
|
function wrapBot(bot) {
|
|
@@ -32220,8 +32664,21 @@ function createChatLock() {
|
|
|
32220
32664
|
return orig;
|
|
32221
32665
|
return function(...args) {
|
|
32222
32666
|
const first = args[0];
|
|
32223
|
-
|
|
32224
|
-
|
|
32667
|
+
if (typeof first !== "string" && typeof first !== "number") {
|
|
32668
|
+
return run("__global__", () => orig.apply(target, args));
|
|
32669
|
+
}
|
|
32670
|
+
const chatId = String(first);
|
|
32671
|
+
const last = args[args.length - 1];
|
|
32672
|
+
const lastIsOpts = last != null && typeof last === "object" && !Array.isArray(last);
|
|
32673
|
+
const threadFromOpts = lastIsOpts ? last.message_thread_id : undefined;
|
|
32674
|
+
let tid = typeof threadFromOpts === "number" ? threadFromOpts : null;
|
|
32675
|
+
if (tid === 1) {
|
|
32676
|
+
tid = null;
|
|
32677
|
+
const cleanedOpts = { ...last };
|
|
32678
|
+
delete cleanedOpts.message_thread_id;
|
|
32679
|
+
args = args.slice(0, -1).concat([cleanedOpts]);
|
|
32680
|
+
}
|
|
32681
|
+
return run(chatKey(chatId, tid), () => orig.apply(target, args));
|
|
32225
32682
|
};
|
|
32226
32683
|
}
|
|
32227
32684
|
});
|
|
@@ -41300,6 +41757,7 @@ function flushOnAgentDisconnect(deps) {
|
|
|
41300
41757
|
activeStatusReactions,
|
|
41301
41758
|
activeReactionMsgIds,
|
|
41302
41759
|
activeTurnStartedAt,
|
|
41760
|
+
claudeBusyKeys,
|
|
41303
41761
|
activeDraftStreams,
|
|
41304
41762
|
activeDraftParseModes,
|
|
41305
41763
|
clearActiveReactions: clearActiveReactions3,
|
|
@@ -41316,6 +41774,7 @@ function flushOnAgentDisconnect(deps) {
|
|
|
41316
41774
|
activeStatusReactions.delete(key);
|
|
41317
41775
|
activeReactionMsgIds.delete(key);
|
|
41318
41776
|
activeTurnStartedAt.delete(key);
|
|
41777
|
+
claudeBusyKeys.delete(key);
|
|
41319
41778
|
}
|
|
41320
41779
|
clearActiveReactions3();
|
|
41321
41780
|
const danglingKeys = [...activeTurnStartedAt.keys()];
|
|
@@ -41323,10 +41782,16 @@ function flushOnAgentDisconnect(deps) {
|
|
|
41323
41782
|
for (const k of danglingKeys) {
|
|
41324
41783
|
activeTurnStartedAt.delete(k);
|
|
41325
41784
|
activeReactionMsgIds.delete(k);
|
|
41785
|
+
claudeBusyKeys.delete(k);
|
|
41326
41786
|
}
|
|
41327
41787
|
log(`telegram gateway: disconnect-flush swept ${danglingKeys.length} dangling turn key(s) ` + `post-bridge-death (controller loop missed \u2014 finalize raced disconnect)`);
|
|
41328
41788
|
onDanglingTurnsSwept?.(danglingKeys);
|
|
41329
41789
|
}
|
|
41790
|
+
if (claudeBusyKeys.size > 0) {
|
|
41791
|
+
const orphanCount = claudeBusyKeys.size;
|
|
41792
|
+
claudeBusyKeys.clear();
|
|
41793
|
+
log(`telegram gateway: disconnect-flush cleared ${orphanCount} orphan claudeBusyKeys ` + `entr${orphanCount === 1 ? "y" : "ies"} (synthetic-inbound deliveries that never turn_ended)`);
|
|
41794
|
+
}
|
|
41330
41795
|
disposeProgressDriver();
|
|
41331
41796
|
for (const [key, stream] of activeDraftStreams.entries()) {
|
|
41332
41797
|
if (!stream.isFinal())
|
|
@@ -42715,6 +43180,7 @@ init_zod();
|
|
|
42715
43180
|
init_schema();
|
|
42716
43181
|
init_paths();
|
|
42717
43182
|
init_overlay_loader();
|
|
43183
|
+
init_merge();
|
|
42718
43184
|
import { readFileSync as readFileSync14, existsSync as existsSync18 } from "node:fs";
|
|
42719
43185
|
import { homedir as homedir8 } from "node:os";
|
|
42720
43186
|
import { resolve as resolve5 } from "node:path";
|
|
@@ -42836,11 +43302,37 @@ function loadConfig2(configPath) {
|
|
|
42836
43302
|
throw err;
|
|
42837
43303
|
}
|
|
42838
43304
|
applyAgentOverlays(config);
|
|
43305
|
+
validateAllCronTopicAliases2(config, filePath);
|
|
42839
43306
|
return config;
|
|
42840
43307
|
}
|
|
43308
|
+
function validateAllCronTopicAliases2(config, filePath) {
|
|
43309
|
+
const issues = [];
|
|
43310
|
+
for (const [agentName3, agentRaw] of Object.entries(config.agents)) {
|
|
43311
|
+
if (!agentRaw)
|
|
43312
|
+
continue;
|
|
43313
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentRaw);
|
|
43314
|
+
const schedule = resolved.schedule ?? [];
|
|
43315
|
+
if (schedule.length === 0)
|
|
43316
|
+
continue;
|
|
43317
|
+
const tg = resolved.channels?.telegram;
|
|
43318
|
+
const aliases = new Set(Object.keys(tg?.topic_aliases ?? {}));
|
|
43319
|
+
for (const entry of schedule) {
|
|
43320
|
+
if (entry.topic == null)
|
|
43321
|
+
continue;
|
|
43322
|
+
if (typeof entry.topic === "number")
|
|
43323
|
+
continue;
|
|
43324
|
+
if (!aliases.has(entry.topic)) {
|
|
43325
|
+
issues.push(` agents.${agentName3}.schedule cron \`${entry.cron}\`: ` + `topic alias "${entry.topic}" is not defined in ` + `channels.telegram.topic_aliases.`);
|
|
43326
|
+
}
|
|
43327
|
+
}
|
|
43328
|
+
}
|
|
43329
|
+
if (issues.length > 0) {
|
|
43330
|
+
throw new ConfigError2(`Cron \`topic:\` alias references unknown topic_aliases in ${filePath}`, issues);
|
|
43331
|
+
}
|
|
43332
|
+
}
|
|
42841
43333
|
|
|
42842
43334
|
// ../src/config/merge.ts
|
|
42843
|
-
function
|
|
43335
|
+
function dedupe2(items) {
|
|
42844
43336
|
const seen = new Set;
|
|
42845
43337
|
const out = [];
|
|
42846
43338
|
for (const item of items) {
|
|
@@ -42851,7 +43343,7 @@ function dedupe(items) {
|
|
|
42851
43343
|
}
|
|
42852
43344
|
return out;
|
|
42853
43345
|
}
|
|
42854
|
-
function
|
|
43346
|
+
function deepMergeJson2(base, override) {
|
|
42855
43347
|
if (override === undefined)
|
|
42856
43348
|
return base;
|
|
42857
43349
|
if (base === undefined)
|
|
@@ -42863,25 +43355,25 @@ function deepMergeJson(base, override) {
|
|
|
42863
43355
|
for (const [k, v] of Object.entries(override)) {
|
|
42864
43356
|
if (k === "__proto__" || k === "constructor" || k === "prototype")
|
|
42865
43357
|
continue;
|
|
42866
|
-
out[k] =
|
|
43358
|
+
out[k] = deepMergeJson2(out[k], v);
|
|
42867
43359
|
}
|
|
42868
43360
|
return out;
|
|
42869
43361
|
}
|
|
42870
|
-
function
|
|
42871
|
-
if (!
|
|
42872
|
-
|
|
43362
|
+
function resolveAgentConfig2(defaults, profiles, agent) {
|
|
43363
|
+
if (!mergeAgentConfig2.suppressDeprecationLogs && !mergeAgentConfig2.notifiedWorkerIsolationMove && defaults?.subagents?.worker?.isolation === "worktree") {
|
|
43364
|
+
mergeAgentConfig2.notifiedWorkerIsolationMove = true;
|
|
42873
43365
|
console.warn("[switchroom] NOTICE: defaults.subagents.worker.isolation moved to the " + "`coding` profile in switchroom 0.6.6 (#682). Agents extending coding " + "still get worktree-isolated workers; other agents would have hard-failed " + "the first time they delegated. See CHANGELOG.");
|
|
42874
43366
|
}
|
|
42875
43367
|
const name = agent.extends;
|
|
42876
43368
|
const profile = name && profiles ? profiles[name] : undefined;
|
|
42877
43369
|
if (!profile) {
|
|
42878
|
-
return
|
|
43370
|
+
return mergeAgentConfig2(defaults, agent);
|
|
42879
43371
|
}
|
|
42880
43372
|
const { extends: _unused, ...profileWithoutExtends } = profile;
|
|
42881
|
-
const layered =
|
|
42882
|
-
return
|
|
43373
|
+
const layered = mergeAgentConfig2(defaults, profileWithoutExtends);
|
|
43374
|
+
return mergeAgentConfig2(layered, agent);
|
|
42883
43375
|
}
|
|
42884
|
-
function
|
|
43376
|
+
function foldDeprecatedTelegramFields2(config) {
|
|
42885
43377
|
const c = config;
|
|
42886
43378
|
const root = c;
|
|
42887
43379
|
const deprecations = [];
|
|
@@ -42912,15 +43404,15 @@ function foldDeprecatedTelegramFields(config) {
|
|
|
42912
43404
|
deprecations
|
|
42913
43405
|
};
|
|
42914
43406
|
}
|
|
42915
|
-
function
|
|
42916
|
-
const { config: agent, deprecations: agentDeprecations } =
|
|
42917
|
-
const defaultsMigration = defaultsIn ?
|
|
43407
|
+
function mergeAgentConfig2(defaultsIn, agentIn) {
|
|
43408
|
+
const { config: agent, deprecations: agentDeprecations } = foldDeprecatedTelegramFields2(agentIn);
|
|
43409
|
+
const defaultsMigration = defaultsIn ? foldDeprecatedTelegramFields2(defaultsIn) : null;
|
|
42918
43410
|
const defaults = defaultsMigration?.config;
|
|
42919
43411
|
const allDeprecations = [
|
|
42920
43412
|
...agentDeprecations,
|
|
42921
43413
|
...defaultsMigration?.deprecations ?? []
|
|
42922
43414
|
];
|
|
42923
|
-
if (allDeprecations.length > 0 && !
|
|
43415
|
+
if (allDeprecations.length > 0 && !mergeAgentConfig2.suppressDeprecationLogs) {
|
|
42924
43416
|
for (const msg of allDeprecations) {
|
|
42925
43417
|
console.warn(`[switchroom] DEPRECATION: ${msg}`);
|
|
42926
43418
|
}
|
|
@@ -42958,8 +43450,8 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
42958
43450
|
const dDeny = defaults.tools?.deny ?? [];
|
|
42959
43451
|
const aDeny = merged.tools?.deny ?? [];
|
|
42960
43452
|
merged.tools = {
|
|
42961
|
-
allow:
|
|
42962
|
-
deny:
|
|
43453
|
+
allow: dedupe2([...dAllow, ...aAllow]),
|
|
43454
|
+
deny: dedupe2([...dDeny, ...aDeny])
|
|
42963
43455
|
};
|
|
42964
43456
|
}
|
|
42965
43457
|
if (defaults.soul || merged.soul) {
|
|
@@ -43097,10 +43589,10 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
43097
43589
|
if (defaults.skills || merged.skills) {
|
|
43098
43590
|
const d = defaults.skills ?? [];
|
|
43099
43591
|
const a = merged.skills ?? [];
|
|
43100
|
-
merged.skills =
|
|
43592
|
+
merged.skills = dedupe2([...d, ...a]);
|
|
43101
43593
|
}
|
|
43102
43594
|
if (defaults.settings_raw || merged.settings_raw) {
|
|
43103
|
-
merged.settings_raw =
|
|
43595
|
+
merged.settings_raw = deepMergeJson2(defaults.settings_raw ?? {}, merged.settings_raw ?? {});
|
|
43104
43596
|
}
|
|
43105
43597
|
if (defaults.claude_md_raw || merged.claude_md_raw) {
|
|
43106
43598
|
const parts = [defaults.claude_md_raw, merged.claude_md_raw].filter((p) => typeof p === "string" && p.length > 0);
|
|
@@ -43117,7 +43609,7 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
43117
43609
|
if (defaults.extra_stable_files || merged.extra_stable_files) {
|
|
43118
43610
|
const d = defaults.extra_stable_files ?? [];
|
|
43119
43611
|
const a = merged.extra_stable_files ?? [];
|
|
43120
|
-
merged.extra_stable_files =
|
|
43612
|
+
merged.extra_stable_files = dedupe2([...d, ...a]);
|
|
43121
43613
|
}
|
|
43122
43614
|
const dReactions = defaults.reactions;
|
|
43123
43615
|
const mReactions = merged.reactions;
|
|
@@ -43146,7 +43638,60 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
43146
43638
|
((mergeAgentConfig) => {
|
|
43147
43639
|
mergeAgentConfig.suppressDeprecationLogs = false;
|
|
43148
43640
|
mergeAgentConfig.notifiedWorkerIsolationMove = false;
|
|
43149
|
-
})(
|
|
43641
|
+
})(mergeAgentConfig2 ||= {});
|
|
43642
|
+
|
|
43643
|
+
// ../src/telegram/topic-router.ts
|
|
43644
|
+
var ALERTS_ALIAS = "alerts";
|
|
43645
|
+
var ADMIN_ALIAS = "admin";
|
|
43646
|
+
function aliasToId(config, name) {
|
|
43647
|
+
return config.topic_aliases?.[name];
|
|
43648
|
+
}
|
|
43649
|
+
function resolveOutboundTopic(config, event) {
|
|
43650
|
+
const cfg = config ?? {};
|
|
43651
|
+
const inSupergroupMode = cfg.default_topic_id != null;
|
|
43652
|
+
switch (event.kind) {
|
|
43653
|
+
case "reply":
|
|
43654
|
+
return event.originThreadId;
|
|
43655
|
+
case "subagent-progress":
|
|
43656
|
+
return event.parentThreadId;
|
|
43657
|
+
case "command-query":
|
|
43658
|
+
return event.originThreadId;
|
|
43659
|
+
case "vault":
|
|
43660
|
+
case "permission":
|
|
43661
|
+
if (event.turnInitiated) {
|
|
43662
|
+
return event.originThreadId ?? cfg.default_topic_id;
|
|
43663
|
+
}
|
|
43664
|
+
if (!inSupergroupMode)
|
|
43665
|
+
return;
|
|
43666
|
+
return aliasToId(cfg, ADMIN_ALIAS) ?? cfg.default_topic_id;
|
|
43667
|
+
case "hostd-approval":
|
|
43668
|
+
if (event.originThreadId != null)
|
|
43669
|
+
return event.originThreadId;
|
|
43670
|
+
if (!inSupergroupMode)
|
|
43671
|
+
return;
|
|
43672
|
+
return aliasToId(cfg, ADMIN_ALIAS) ?? cfg.default_topic_id;
|
|
43673
|
+
case "cron": {
|
|
43674
|
+
if (typeof event.entryTopic === "number")
|
|
43675
|
+
return event.entryTopic;
|
|
43676
|
+
if (typeof event.entryTopic === "string") {
|
|
43677
|
+
const resolved = aliasToId(cfg, event.entryTopic);
|
|
43678
|
+
if (resolved != null)
|
|
43679
|
+
return resolved;
|
|
43680
|
+
}
|
|
43681
|
+
return cfg.default_topic_id;
|
|
43682
|
+
}
|
|
43683
|
+
case "boot":
|
|
43684
|
+
case "compact-watchdog":
|
|
43685
|
+
if (!inSupergroupMode)
|
|
43686
|
+
return;
|
|
43687
|
+
return aliasToId(cfg, ALERTS_ALIAS) ?? cfg.default_topic_id;
|
|
43688
|
+
case "command-mutation":
|
|
43689
|
+
case "command-heavy":
|
|
43690
|
+
if (!inSupergroupMode)
|
|
43691
|
+
return;
|
|
43692
|
+
return aliasToId(cfg, ADMIN_ALIAS) ?? cfg.default_topic_id;
|
|
43693
|
+
}
|
|
43694
|
+
}
|
|
43150
43695
|
|
|
43151
43696
|
// ../src/agents/perf.ts
|
|
43152
43697
|
import { existsSync as existsSync19, readFileSync as readFileSync15 } from "node:fs";
|
|
@@ -44768,12 +45313,12 @@ function createPendingPermissionBuffer(opts = {}) {
|
|
|
44768
45313
|
}
|
|
44769
45314
|
|
|
44770
45315
|
// gateway/chat-key.ts
|
|
44771
|
-
function
|
|
45316
|
+
function chatKey2(chatId, threadId) {
|
|
44772
45317
|
const t = threadId == null || threadId === 0 ? "_" : String(threadId);
|
|
44773
45318
|
return `${chatId}:${t}`;
|
|
44774
45319
|
}
|
|
44775
|
-
function
|
|
44776
|
-
return `${
|
|
45320
|
+
function chatKeyWithSuffix2(chatId, threadId, suffix) {
|
|
45321
|
+
return `${chatKey2(chatId, threadId)}:${suffix}`;
|
|
44777
45322
|
}
|
|
44778
45323
|
function chatIdOfChatKey(key) {
|
|
44779
45324
|
const idx = key.indexOf(":");
|
|
@@ -48732,11 +49277,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
48732
49277
|
}
|
|
48733
49278
|
|
|
48734
49279
|
// ../src/build-info.ts
|
|
48735
|
-
var VERSION = "0.13.
|
|
48736
|
-
var COMMIT_SHA = "
|
|
48737
|
-
var COMMIT_DATE = "2026-05-
|
|
48738
|
-
var LATEST_PR =
|
|
48739
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
49280
|
+
var VERSION = "0.13.53";
|
|
49281
|
+
var COMMIT_SHA = "08726c33";
|
|
49282
|
+
var COMMIT_DATE = "2026-05-27T03:52:37Z";
|
|
49283
|
+
var LATEST_PR = 1885;
|
|
49284
|
+
var COMMITS_AHEAD_OF_TAG = 18;
|
|
48740
49285
|
|
|
48741
49286
|
// gateway/boot-version.ts
|
|
48742
49287
|
function formatRelativeAgo(iso) {
|
|
@@ -49650,6 +50195,16 @@ var outboundDedup = new OutboundDedupCache;
|
|
|
49650
50195
|
var chatAvailableReactions = new Map;
|
|
49651
50196
|
var chatProbesInFlight = new Set;
|
|
49652
50197
|
var activeTurnStartedAt = new Map;
|
|
50198
|
+
var claudeBusyKeys = new Set;
|
|
50199
|
+
function markClaudeBusyForInbound(m) {
|
|
50200
|
+
let tid = m.threadId ?? null;
|
|
50201
|
+
if (tid == null && m.meta?.message_thread_id != null) {
|
|
50202
|
+
const n = Number(m.meta.message_thread_id);
|
|
50203
|
+
if (Number.isFinite(n))
|
|
50204
|
+
tid = n;
|
|
50205
|
+
}
|
|
50206
|
+
claudeBusyKeys.add(chatKey2(m.chatId, tid));
|
|
50207
|
+
}
|
|
49653
50208
|
var pendingRestarts = new Map;
|
|
49654
50209
|
var lastSessionActiveFile = null;
|
|
49655
50210
|
var compactState = initialCompactState();
|
|
@@ -49689,10 +50244,10 @@ var CONTEXT_EXHAUSTION_COOLDOWN_MS = 600000;
|
|
|
49689
50244
|
var lastContextExhaustionWarningAt = 0;
|
|
49690
50245
|
var pendingPtyPartial = null;
|
|
49691
50246
|
function statusKey(chatId, threadId) {
|
|
49692
|
-
return
|
|
50247
|
+
return chatKey2(chatId, threadId);
|
|
49693
50248
|
}
|
|
49694
50249
|
function streamKey3(chatId, threadId) {
|
|
49695
|
-
return
|
|
50250
|
+
return chatKey2(chatId, threadId);
|
|
49696
50251
|
}
|
|
49697
50252
|
function purgeReactionTracking(key, endingTurn) {
|
|
49698
50253
|
const outboundEmitted = endingTurn != null ? endingTurn.replyCalled === true : currentTurn?.replyCalled === true;
|
|
@@ -49701,16 +50256,29 @@ function purgeReactionTracking(key, endingTurn) {
|
|
|
49701
50256
|
activeStatusReactions.delete(key);
|
|
49702
50257
|
activeReactionMsgIds.delete(key);
|
|
49703
50258
|
activeTurnStartedAt.delete(key);
|
|
49704
|
-
|
|
50259
|
+
claudeBusyKeys.delete(key);
|
|
50260
|
+
if (endingTurn != null) {
|
|
50261
|
+
stopTurnTypingLoop(endingTurn.sessionChatId, endingTurn.sessionThreadId ?? null);
|
|
50262
|
+
} else {
|
|
50263
|
+
const chatId = chatIdOfChatKey(key);
|
|
50264
|
+
const threadPart = key.slice(chatId.length + 1);
|
|
50265
|
+
const threadId = threadPart === "_" || threadPart === "" ? null : Number(threadPart);
|
|
50266
|
+
stopTurnTypingLoop(chatId, Number.isFinite(threadId) ? threadId : null);
|
|
50267
|
+
}
|
|
49705
50268
|
if (msgInfo) {
|
|
49706
50269
|
const agentDir = resolveAgentDirFromEnv();
|
|
49707
50270
|
if (agentDir != null)
|
|
49708
50271
|
removeActiveReaction(agentDir, msgInfo.chatId, msgInfo.messageId);
|
|
49709
50272
|
}
|
|
49710
|
-
if (
|
|
50273
|
+
if (claudeBusyKeys.size === 0) {
|
|
49711
50274
|
const selfAgentForFlush = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
49712
50275
|
if (pendingInboundBuffer.depth(selfAgentForFlush) > 0) {
|
|
49713
|
-
const fr = redeliverBufferedInbound(pendingInboundBuffer, selfAgentForFlush, (m) =>
|
|
50276
|
+
const fr = redeliverBufferedInbound(pendingInboundBuffer, selfAgentForFlush, (m) => {
|
|
50277
|
+
const d = ipcServer.sendToAgent(selfAgentForFlush, m);
|
|
50278
|
+
if (d)
|
|
50279
|
+
markClaudeBusyForInbound(m);
|
|
50280
|
+
return d;
|
|
50281
|
+
}, inboundSpool);
|
|
49714
50282
|
if (fr.redelivered > 0) {
|
|
49715
50283
|
process.stderr.write(`telegram gateway: turn-complete flushed ${fr.redelivered}/${fr.drained} held inbound for ${selfAgentForFlush}${fr.rebuffered > 0 ? ` (${fr.rebuffered} re-buffered)` : ""}
|
|
49716
50284
|
`);
|
|
@@ -49730,11 +50298,17 @@ function releaseTurnBufferGate(key) {
|
|
|
49730
50298
|
if (!activeTurnStartedAt.has(key))
|
|
49731
50299
|
return;
|
|
49732
50300
|
activeTurnStartedAt.delete(key);
|
|
50301
|
+
claudeBusyKeys.delete(key);
|
|
49733
50302
|
shadowEmit({ kind: "turnEnd", key, at: Date.now(), outboundEmitted: true });
|
|
49734
|
-
if (
|
|
50303
|
+
if (claudeBusyKeys.size === 0) {
|
|
49735
50304
|
const selfAgentForFlush = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
49736
50305
|
if (pendingInboundBuffer.depth(selfAgentForFlush) > 0) {
|
|
49737
|
-
const fr = redeliverBufferedInbound(pendingInboundBuffer, selfAgentForFlush, (m) =>
|
|
50306
|
+
const fr = redeliverBufferedInbound(pendingInboundBuffer, selfAgentForFlush, (m) => {
|
|
50307
|
+
const d = ipcServer.sendToAgent(selfAgentForFlush, m);
|
|
50308
|
+
if (d)
|
|
50309
|
+
markClaudeBusyForInbound(m);
|
|
50310
|
+
return d;
|
|
50311
|
+
}, inboundSpool);
|
|
49738
50312
|
if (fr.redelivered > 0) {
|
|
49739
50313
|
process.stderr.write(`telegram gateway: reply-released-gate flushed ${fr.redelivered}/${fr.drained} held inbound for ${selfAgentForFlush}${fr.rebuffered > 0 ? ` (${fr.rebuffered} re-buffered)` : ""}
|
|
49740
50314
|
`);
|
|
@@ -49758,7 +50332,7 @@ function maybeProactiveCompact() {
|
|
|
49758
50332
|
try {
|
|
49759
50333
|
const cfg = loadConfig2();
|
|
49760
50334
|
const rawAgent = cfg.agents?.[agentName3] ?? {};
|
|
49761
|
-
const resolved =
|
|
50335
|
+
const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
|
|
49762
50336
|
cap = resolved.session?.max_context_tokens;
|
|
49763
50337
|
} catch {
|
|
49764
50338
|
return;
|
|
@@ -49811,7 +50385,7 @@ async function postCompactCard(occ, cap) {
|
|
|
49811
50385
|
const chatId = loadAccess().allowFrom[0];
|
|
49812
50386
|
if (!chatId)
|
|
49813
50387
|
return;
|
|
49814
|
-
const threadId = chatThreadMap.get(chatId);
|
|
50388
|
+
const threadId = resolveAgentOutboundTopic({ kind: "compact-watchdog" }) ?? chatThreadMap.get(chatId);
|
|
49815
50389
|
const text = `\uD83D\uDDDC\uFE0F <b>Context compaction</b>
|
|
49816
50390
|
` + `Working context hit ~${occ.toLocaleString()} tokens (cap ${cap.toLocaleString()}) \u2014 running <code>/compact</code>. ` + `Older detail moves to Hindsight; I'll confirm here once the context has shrunk (may take a turn or two).`;
|
|
49817
50391
|
const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId, text, {
|
|
@@ -49986,53 +50560,59 @@ var CHAT_ACTION_WHITELIST = new Set([
|
|
|
49986
50560
|
"record_video_note",
|
|
49987
50561
|
"upload_video_note"
|
|
49988
50562
|
]);
|
|
49989
|
-
function startTypingLoop(chat_id, action = "typing") {
|
|
49990
|
-
stopTypingLoop(chat_id);
|
|
50563
|
+
function startTypingLoop(chat_id, thread_id = null, action = "typing") {
|
|
50564
|
+
stopTypingLoop(chat_id, thread_id);
|
|
50565
|
+
const key = chatKey2(chat_id, thread_id);
|
|
50566
|
+
const sendOpts = thread_id != null ? { message_thread_id: thread_id } : undefined;
|
|
49991
50567
|
const send = () => {
|
|
49992
|
-
bot.api.sendChatAction(chat_id, action).then(() => {
|
|
50568
|
+
bot.api.sendChatAction(chat_id, action, sendOpts).then(() => {
|
|
49993
50569
|
typingBackoffMs = 0;
|
|
49994
50570
|
}, (err) => {
|
|
49995
50571
|
const msg = err instanceof Error ? err.message : String(err);
|
|
49996
50572
|
if (msg.includes("401") || msg.includes("Unauthorized")) {
|
|
49997
50573
|
typingBackoffMs = Math.min(Math.max(typingBackoffMs * 2 || 1000, 1000), TYPING_BACKOFF_MAX);
|
|
49998
|
-
stopTypingLoop(chat_id);
|
|
50574
|
+
stopTypingLoop(chat_id, thread_id);
|
|
49999
50575
|
const retry = setTimeout(() => {
|
|
50000
|
-
typingRetryTimers.delete(
|
|
50001
|
-
startTypingLoop(chat_id, action);
|
|
50576
|
+
typingRetryTimers.delete(key);
|
|
50577
|
+
startTypingLoop(chat_id, thread_id, action);
|
|
50002
50578
|
}, typingBackoffMs);
|
|
50003
|
-
typingRetryTimers.set(
|
|
50579
|
+
typingRetryTimers.set(key, retry);
|
|
50004
50580
|
}
|
|
50005
50581
|
});
|
|
50006
50582
|
};
|
|
50007
50583
|
send();
|
|
50008
|
-
typingIntervals.set(
|
|
50584
|
+
typingIntervals.set(key, setInterval(send, 4000));
|
|
50009
50585
|
}
|
|
50010
|
-
function stopTypingLoop(chat_id) {
|
|
50011
|
-
const
|
|
50586
|
+
function stopTypingLoop(chat_id, thread_id = null) {
|
|
50587
|
+
const key = chatKey2(chat_id, thread_id);
|
|
50588
|
+
const iv = typingIntervals.get(key);
|
|
50012
50589
|
if (iv) {
|
|
50013
50590
|
clearInterval(iv);
|
|
50014
|
-
typingIntervals.delete(
|
|
50591
|
+
typingIntervals.delete(key);
|
|
50015
50592
|
}
|
|
50016
|
-
const retry = typingRetryTimers.get(
|
|
50593
|
+
const retry = typingRetryTimers.get(key);
|
|
50017
50594
|
if (retry) {
|
|
50018
50595
|
clearTimeout(retry);
|
|
50019
|
-
typingRetryTimers.delete(
|
|
50596
|
+
typingRetryTimers.delete(key);
|
|
50020
50597
|
}
|
|
50021
50598
|
}
|
|
50022
50599
|
var turnTypingIntervals = new Map;
|
|
50023
|
-
function startTurnTypingLoop(chat_id) {
|
|
50024
|
-
stopTurnTypingLoop(chat_id);
|
|
50600
|
+
function startTurnTypingLoop(chat_id, thread_id = null) {
|
|
50601
|
+
stopTurnTypingLoop(chat_id, thread_id);
|
|
50602
|
+
const key = chatKey2(chat_id, thread_id);
|
|
50603
|
+
const sendOpts = thread_id != null ? { message_thread_id: thread_id } : undefined;
|
|
50025
50604
|
const send = () => {
|
|
50026
|
-
bot.api.sendChatAction(chat_id, "typing").catch(() => {});
|
|
50605
|
+
bot.api.sendChatAction(chat_id, "typing", sendOpts).catch(() => {});
|
|
50027
50606
|
};
|
|
50028
50607
|
send();
|
|
50029
|
-
turnTypingIntervals.set(
|
|
50608
|
+
turnTypingIntervals.set(key, setInterval(send, 4000));
|
|
50030
50609
|
}
|
|
50031
|
-
function stopTurnTypingLoop(chat_id) {
|
|
50032
|
-
const
|
|
50610
|
+
function stopTurnTypingLoop(chat_id, thread_id = null) {
|
|
50611
|
+
const key = chatKey2(chat_id, thread_id);
|
|
50612
|
+
const iv = turnTypingIntervals.get(key);
|
|
50033
50613
|
if (iv) {
|
|
50034
50614
|
clearInterval(iv);
|
|
50035
|
-
turnTypingIntervals.delete(
|
|
50615
|
+
turnTypingIntervals.delete(key);
|
|
50036
50616
|
}
|
|
50037
50617
|
}
|
|
50038
50618
|
var typingWrapper = createTypingWrapper({
|
|
@@ -50635,7 +51215,12 @@ startTimer({
|
|
|
50635
51215
|
clearSilentEndState(fbKey);
|
|
50636
51216
|
} catch {}
|
|
50637
51217
|
const fbSelfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
50638
|
-
const fbRedeliver = redeliverBufferedInbound(pendingInboundBuffer, fbSelfAgent, (m) =>
|
|
51218
|
+
const fbRedeliver = redeliverBufferedInbound(pendingInboundBuffer, fbSelfAgent, (m) => {
|
|
51219
|
+
const d = ipcServer.sendToAgent(fbSelfAgent, m);
|
|
51220
|
+
if (d)
|
|
51221
|
+
markClaudeBusyForInbound(m);
|
|
51222
|
+
return d;
|
|
51223
|
+
}, inboundSpool);
|
|
50639
51224
|
process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} currentTurn_nulled=${turnMatchesFallback} drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
|
|
50640
51225
|
`);
|
|
50641
51226
|
}
|
|
@@ -50818,6 +51403,7 @@ var ipcServer = createIpcServer({
|
|
|
50818
51403
|
activeStatusReactions,
|
|
50819
51404
|
activeReactionMsgIds,
|
|
50820
51405
|
activeTurnStartedAt,
|
|
51406
|
+
claudeBusyKeys,
|
|
50821
51407
|
activeDraftStreams,
|
|
50822
51408
|
activeDraftParseModes,
|
|
50823
51409
|
clearActiveReactions: () => {
|
|
@@ -50931,7 +51517,7 @@ ${reminder}
|
|
|
50931
51517
|
onHeartbeat(_client, _msg) {},
|
|
50932
51518
|
onScheduleRestart(client3, msg) {
|
|
50933
51519
|
const { agentName: agentName3 } = msg;
|
|
50934
|
-
const turnInFlight =
|
|
51520
|
+
const turnInFlight = claudeBusyKeys.size > 0;
|
|
50935
51521
|
if (!turnInFlight) {
|
|
50936
51522
|
try {
|
|
50937
51523
|
client3.send({
|
|
@@ -51098,6 +51684,8 @@ ${reminder}
|
|
|
51098
51684
|
const promptKey = typeof msg.inbound.meta?.prompt_key === "string" ? msg.inbound.meta.prompt_key : "unknown";
|
|
51099
51685
|
const source = typeof msg.inbound.meta?.source === "string" ? msg.inbound.meta.source : "unknown";
|
|
51100
51686
|
const delivered = ipcServer.sendToAgent(msg.agentName, msg.inbound);
|
|
51687
|
+
if (delivered)
|
|
51688
|
+
markClaudeBusyForInbound(msg.inbound);
|
|
51101
51689
|
process.stderr.write(`telegram gateway: inject_inbound agent=${msg.agentName} source=${source} prompt_key=${promptKey} delivered=${delivered}
|
|
51102
51690
|
`);
|
|
51103
51691
|
if (!delivered) {
|
|
@@ -51112,11 +51700,16 @@ if (!STATIC) {
|
|
|
51112
51700
|
setInterval(() => {
|
|
51113
51701
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
51114
51702
|
const r = idleDrainTick(pendingInboundBuffer, selfAgent, () => {
|
|
51115
|
-
if (
|
|
51703
|
+
if (claudeBusyKeys.size > 0)
|
|
51116
51704
|
return false;
|
|
51117
51705
|
const c = ipcServer.getClient(selfAgent);
|
|
51118
51706
|
return c != null && c.isAlive();
|
|
51119
|
-
}, (m) =>
|
|
51707
|
+
}, (m) => {
|
|
51708
|
+
const d = ipcServer.sendToAgent(selfAgent, m);
|
|
51709
|
+
if (d)
|
|
51710
|
+
markClaudeBusyForInbound(m);
|
|
51711
|
+
return d;
|
|
51712
|
+
}, inboundSpool);
|
|
51120
51713
|
if (r != null && r.redelivered > 0) {
|
|
51121
51714
|
process.stderr.write(`telegram gateway: idle-drain flushed ${r.redelivered}/${r.drained} buffered inbound for ${selfAgent}${r.rebuffered > 0 ? ` (${r.rebuffered} re-buffered)` : ""}
|
|
51122
51715
|
`);
|
|
@@ -51395,7 +51988,7 @@ ${url}`;
|
|
|
51395
51988
|
await deleteStalePreview(previewMessageId);
|
|
51396
51989
|
previewMessageId = null;
|
|
51397
51990
|
}
|
|
51398
|
-
startTypingLoop(chat_id);
|
|
51991
|
+
startTypingLoop(chat_id, threadId ?? null);
|
|
51399
51992
|
let silentAnchorEditDone = false;
|
|
51400
51993
|
{
|
|
51401
51994
|
const turn2 = currentTurn;
|
|
@@ -51464,7 +52057,7 @@ ${url}`;
|
|
|
51464
52057
|
}
|
|
51465
52058
|
}
|
|
51466
52059
|
if (silentAnchorEditDone) {
|
|
51467
|
-
stopTypingLoop(chat_id);
|
|
52060
|
+
stopTypingLoop(chat_id, threadId ?? null);
|
|
51468
52061
|
return {
|
|
51469
52062
|
content: [
|
|
51470
52063
|
{
|
|
@@ -51558,7 +52151,7 @@ ${url}`;
|
|
|
51558
52151
|
const msg = err instanceof Error ? err.message : String(err);
|
|
51559
52152
|
throw new Error(`reply failed after ${sentIds.length} of ${chunks.length} chunk(s) sent: ${msg}`);
|
|
51560
52153
|
} finally {
|
|
51561
|
-
stopTypingLoop(chat_id);
|
|
52154
|
+
stopTypingLoop(chat_id, threadId ?? null);
|
|
51562
52155
|
}
|
|
51563
52156
|
if (replyButtonMeta != null && replyButtonMeta.size > 0 && sentIds.length >= chunks.length) {
|
|
51564
52157
|
const keyboardMsgId = sentIds[chunks.length - 1];
|
|
@@ -52397,8 +52990,9 @@ async function executeSendTyping(args) {
|
|
|
52397
52990
|
}
|
|
52398
52991
|
action = rawAction;
|
|
52399
52992
|
}
|
|
52400
|
-
|
|
52401
|
-
|
|
52993
|
+
const stThreadId = resolveThreadId(stChatId, args.message_thread_id) ?? null;
|
|
52994
|
+
startTypingLoop(stChatId, stThreadId, action);
|
|
52995
|
+
setTimeout(() => stopTypingLoop(stChatId, stThreadId), 30000);
|
|
52402
52996
|
for (const [key, ctrl] of activeStatusReactions.entries()) {
|
|
52403
52997
|
if (key.startsWith(`${stChatId}:`))
|
|
52404
52998
|
ctrl.setTool();
|
|
@@ -52535,7 +53129,7 @@ function resetOrphanedReplyTimeout() {
|
|
|
52535
53129
|
}
|
|
52536
53130
|
}
|
|
52537
53131
|
function closeActivityLane(chatId, threadId) {
|
|
52538
|
-
const key =
|
|
53132
|
+
const key = chatKeyWithSuffix2(chatId, threadId, "activity");
|
|
52539
53133
|
const stream = activeDraftStreams.get(key);
|
|
52540
53134
|
if (stream == null)
|
|
52541
53135
|
return;
|
|
@@ -52544,7 +53138,7 @@ function closeActivityLane(chatId, threadId) {
|
|
|
52544
53138
|
stream.finalize().catch(() => {});
|
|
52545
53139
|
}
|
|
52546
53140
|
function closeProgressLane(chatId, threadId) {
|
|
52547
|
-
const prefix =
|
|
53141
|
+
const prefix = chatKeyWithSuffix2(chatId, threadId, "progress");
|
|
52548
53142
|
for (const [key, stream] of activeDraftStreams) {
|
|
52549
53143
|
if (key.startsWith(prefix)) {
|
|
52550
53144
|
activeDraftStreams.delete(key);
|
|
@@ -52593,7 +53187,7 @@ function handleSessionEvent(ev) {
|
|
|
52593
53187
|
clearSilentEndState(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
|
|
52594
53188
|
if (turnsDb != null) {
|
|
52595
53189
|
const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
|
|
52596
|
-
const turnKey =
|
|
53190
|
+
const turnKey = chatKeyWithSuffix2(ev.chatId, evThreadIdNum, String(startedAt));
|
|
52597
53191
|
next.registryKey = turnKey;
|
|
52598
53192
|
const userPromptPreview = extractUserPromptPreview(ev.rawContent);
|
|
52599
53193
|
try {
|
|
@@ -52656,7 +53250,7 @@ function handleSessionEvent(ev) {
|
|
|
52656
53250
|
return;
|
|
52657
53251
|
ctrl.setTool(name);
|
|
52658
53252
|
if (ev.toolUseId) {
|
|
52659
|
-
typingWrapper.onToolUse(ev.toolUseId, turn.sessionChatId, name);
|
|
53253
|
+
typingWrapper.onToolUse(ev.toolUseId, turn.sessionChatId, name, turn.sessionThreadId ?? null);
|
|
52660
53254
|
}
|
|
52661
53255
|
return;
|
|
52662
53256
|
}
|
|
@@ -52774,7 +53368,7 @@ function handleSessionEvent(ev) {
|
|
|
52774
53368
|
return;
|
|
52775
53369
|
if (!ev.toolUseId)
|
|
52776
53370
|
return;
|
|
52777
|
-
typingWrapper.onToolUse(ev.toolUseId, turn.sessionChatId, ev.toolName);
|
|
53371
|
+
typingWrapper.onToolUse(ev.toolUseId, turn.sessionChatId, ev.toolName, turn.sessionThreadId ?? null);
|
|
52778
53372
|
return;
|
|
52779
53373
|
}
|
|
52780
53374
|
case "sub_agent_tool_result": {
|
|
@@ -52853,7 +53447,7 @@ function handleSessionEvent(ev) {
|
|
|
52853
53447
|
if (flushDecision.kind === "skip" && flushDecision.reason === "silent-marker") {
|
|
52854
53448
|
process.stderr.write(`telegram gateway: silent-turn-suppression: chat=${chatId} turnKey=${turn.startedAt} reason=silent-marker
|
|
52855
53449
|
`);
|
|
52856
|
-
const suppressPrefix =
|
|
53450
|
+
const suppressPrefix = chatKeyWithSuffix2(chatId, threadId, "progress");
|
|
52857
53451
|
for (const [key] of activeDraftStreams) {
|
|
52858
53452
|
if (key.startsWith(suppressPrefix)) {
|
|
52859
53453
|
activeDraftStreams.delete(key);
|
|
@@ -53284,7 +53878,7 @@ async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
|
53284
53878
|
if (!from)
|
|
53285
53879
|
return;
|
|
53286
53880
|
maybeEarlyAckReaction(ctx, from);
|
|
53287
|
-
const key = inboundCoalesceKey(String(ctx.chat.id), String(from.id));
|
|
53881
|
+
const key = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
53288
53882
|
const result = inboundCoalescer.enqueue(key, { text, ctx, downloadImage, attachment });
|
|
53289
53883
|
if (result.bypass)
|
|
53290
53884
|
return handleInbound(ctx, text, undefined, undefined);
|
|
@@ -53339,7 +53933,7 @@ async function handleInbound(ctx, text, downloadImage, attachment) {
|
|
|
53339
53933
|
},
|
|
53340
53934
|
at: Date.now()
|
|
53341
53935
|
});
|
|
53342
|
-
const turnInFlightAtReceipt =
|
|
53936
|
+
const turnInFlightAtReceipt = claudeBusyKeys.size > 0;
|
|
53343
53937
|
const access = result.access;
|
|
53344
53938
|
const from = ctx.from;
|
|
53345
53939
|
const chat_id = String(ctx.chat.id);
|
|
@@ -53435,11 +54029,12 @@ async function handleInbound(ctx, text, downloadImage, attachment) {
|
|
|
53435
54029
|
}
|
|
53436
54030
|
return;
|
|
53437
54031
|
}
|
|
53438
|
-
const
|
|
54032
|
+
const interceptKey = chatKey2(chat_id, messageThreadId);
|
|
54033
|
+
const pendingAdd = pendingAuthAddFlows.get(interceptKey);
|
|
53439
54034
|
if (pendingAdd && looksLikeAuthCode(text)) {
|
|
53440
54035
|
const elapsed = Date.now() - pendingAdd.startedAt;
|
|
53441
54036
|
if (elapsed < REAUTH_INTERCEPT_TTL_MS) {
|
|
53442
|
-
pendingAuthAddFlows.delete(
|
|
54037
|
+
pendingAuthAddFlows.delete(interceptKey);
|
|
53443
54038
|
try {
|
|
53444
54039
|
const credentials = await submitAccountAuthCode(pendingAdd, text.trim());
|
|
53445
54040
|
try {
|
|
@@ -53458,13 +54053,13 @@ The fleet's active account hasn't changed. Send <code>/auth use ${escapeHtmlForT
|
|
|
53458
54053
|
return;
|
|
53459
54054
|
}
|
|
53460
54055
|
cancelAccountAuthSession(pendingAdd);
|
|
53461
|
-
pendingAuthAddFlows.delete(
|
|
54056
|
+
pendingAuthAddFlows.delete(interceptKey);
|
|
53462
54057
|
}
|
|
53463
|
-
const pendingReauth = pendingReauthFlows.get(
|
|
54058
|
+
const pendingReauth = pendingReauthFlows.get(interceptKey);
|
|
53464
54059
|
if (pendingReauth && looksLikeAuthCode(text)) {
|
|
53465
54060
|
const elapsed = Date.now() - pendingReauth.startedAt;
|
|
53466
54061
|
if (elapsed < REAUTH_INTERCEPT_TTL_MS) {
|
|
53467
|
-
pendingReauthFlows.delete(
|
|
54062
|
+
pendingReauthFlows.delete(interceptKey);
|
|
53468
54063
|
const { result: result2, errorText } = execAuthCode(pendingReauth.agent, text.trim());
|
|
53469
54064
|
if (errorText) {
|
|
53470
54065
|
await switchroomReply(ctx, `<b>auth code failed:</b>
|
|
@@ -53483,7 +54078,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
53483
54078
|
redactAuthCodeMessage(bot.api, chat_id, msgId ?? null, (line) => process.stderr.write(line));
|
|
53484
54079
|
return;
|
|
53485
54080
|
}
|
|
53486
|
-
pendingReauthFlows.delete(
|
|
54081
|
+
pendingReauthFlows.delete(interceptKey);
|
|
53487
54082
|
}
|
|
53488
54083
|
const pendingVault = pendingVaultOps.get(chat_id);
|
|
53489
54084
|
if (pendingVault) {
|
|
@@ -53779,7 +54374,7 @@ ${preBlock(write.output)}`;
|
|
|
53779
54374
|
reset(statusKey(chat_id, messageThreadId), Date.now());
|
|
53780
54375
|
startTurn(statusKey(chat_id, messageThreadId), Date.now());
|
|
53781
54376
|
clearPending(statusKey(chat_id, messageThreadId), "inbound");
|
|
53782
|
-
startTurnTypingLoop(chat_id);
|
|
54377
|
+
startTurnTypingLoop(chat_id, messageThreadId ?? null);
|
|
53783
54378
|
emitRuntimeMetric({
|
|
53784
54379
|
kind: "turn_started",
|
|
53785
54380
|
chat_id,
|
|
@@ -53911,6 +54506,8 @@ ${preBlock(write.output)}`;
|
|
|
53911
54506
|
return;
|
|
53912
54507
|
}
|
|
53913
54508
|
const delivered = ipcServer.sendToAgent(selfAgent, inboundMsg);
|
|
54509
|
+
if (delivered)
|
|
54510
|
+
markClaudeBusyForInbound(inboundMsg);
|
|
53914
54511
|
if (!delivered) {
|
|
53915
54512
|
pendingInboundBuffer.push(selfAgent, inboundMsg);
|
|
53916
54513
|
const threadOpts = messageThreadId != null ? { message_thread_id: messageThreadId } : {};
|
|
@@ -54061,9 +54658,10 @@ function resolveBootChatId(marker, ageMs) {
|
|
|
54061
54658
|
ackMsgId: marker.ack_message_id ?? undefined
|
|
54062
54659
|
};
|
|
54063
54660
|
}
|
|
54661
|
+
const supergroupBootTopic = resolveAgentOutboundTopic({ kind: "boot" });
|
|
54064
54662
|
const envChat = process.env.SUBAGENT_OWNER_CHAT_ID;
|
|
54065
54663
|
if (envChat)
|
|
54066
|
-
return { chatId: envChat, threadId:
|
|
54664
|
+
return { chatId: envChat, threadId: supergroupBootTopic, ackMsgId: undefined };
|
|
54067
54665
|
if (HISTORY_ENABLED) {
|
|
54068
54666
|
try {
|
|
54069
54667
|
const access = loadAccess();
|
|
@@ -54071,12 +54669,30 @@ function resolveBootChatId(marker, ageMs) {
|
|
|
54071
54669
|
if (ownerChatId) {
|
|
54072
54670
|
const recent = query({ chat_id: ownerChatId, limit: 1 });
|
|
54073
54671
|
if (recent.length > 0)
|
|
54074
|
-
return { chatId: ownerChatId, threadId:
|
|
54672
|
+
return { chatId: ownerChatId, threadId: supergroupBootTopic, ackMsgId: undefined };
|
|
54075
54673
|
}
|
|
54076
54674
|
} catch {}
|
|
54077
54675
|
}
|
|
54078
54676
|
return null;
|
|
54079
54677
|
}
|
|
54678
|
+
function resolveAgentOutboundTopic(event) {
|
|
54679
|
+
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
54680
|
+
if (!agentName3)
|
|
54681
|
+
return;
|
|
54682
|
+
try {
|
|
54683
|
+
const cfg = loadConfig2();
|
|
54684
|
+
const rawAgent = cfg.agents?.[agentName3];
|
|
54685
|
+
if (!rawAgent)
|
|
54686
|
+
return;
|
|
54687
|
+
const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
|
|
54688
|
+
const tg = resolved.channels?.telegram;
|
|
54689
|
+
if (!tg)
|
|
54690
|
+
return;
|
|
54691
|
+
return resolveOutboundTopic(tg, event);
|
|
54692
|
+
} catch {
|
|
54693
|
+
return;
|
|
54694
|
+
}
|
|
54695
|
+
}
|
|
54080
54696
|
function stampUserRestartReason(reason) {
|
|
54081
54697
|
try {
|
|
54082
54698
|
writeCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH, {
|
|
@@ -55308,24 +55924,25 @@ bot.command("auth", async (ctx) => {
|
|
|
55308
55924
|
Set <code>admin: true</code> on this agent in switchroom.yaml to unlock (the same flag that gates <code>/agents</code>, <code>/restart</code>, <code>/update</code> etc.).`, { html: true });
|
|
55309
55925
|
return;
|
|
55310
55926
|
}
|
|
55927
|
+
const authAddKey = chatKey2(chatId, ctx.message?.message_thread_id ?? null);
|
|
55311
55928
|
if (parsed.kind === "cancel") {
|
|
55312
|
-
const existing = pendingAuthAddFlows.get(
|
|
55929
|
+
const existing = pendingAuthAddFlows.get(authAddKey);
|
|
55313
55930
|
if (!existing) {
|
|
55314
55931
|
await switchroomReply(ctx, "<i>No pending <code>/auth add</code> flow in this chat.</i>", { html: true });
|
|
55315
55932
|
return;
|
|
55316
55933
|
}
|
|
55317
55934
|
cancelAccountAuthSession(existing);
|
|
55318
|
-
pendingAuthAddFlows.delete(
|
|
55935
|
+
pendingAuthAddFlows.delete(authAddKey);
|
|
55319
55936
|
await switchroomReply(ctx, "Cancelled.", { html: true });
|
|
55320
55937
|
return;
|
|
55321
55938
|
}
|
|
55322
|
-
if (pendingAuthAddFlows.has(
|
|
55939
|
+
if (pendingAuthAddFlows.has(authAddKey)) {
|
|
55323
55940
|
await switchroomReply(ctx, "<i>An <code>/auth add</code> flow is already in progress for this chat. Finish the paste, or send <code>/auth cancel</code> to abort.</i>", { html: true });
|
|
55324
55941
|
return;
|
|
55325
55942
|
}
|
|
55326
55943
|
try {
|
|
55327
55944
|
const { loginUrl, scratchDir, child } = await startAccountAuthSession(parsed.label);
|
|
55328
|
-
pendingAuthAddFlows.set(
|
|
55945
|
+
pendingAuthAddFlows.set(authAddKey, {
|
|
55329
55946
|
label: parsed.label,
|
|
55330
55947
|
scratchDir,
|
|
55331
55948
|
child,
|
|
@@ -55569,6 +56186,8 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
|
|
|
55569
56186
|
operatorId: senderId
|
|
55570
56187
|
});
|
|
55571
56188
|
const delivered = ipcServer.sendToAgent(pending2.agent, synthetic);
|
|
56189
|
+
if (delivered)
|
|
56190
|
+
markClaudeBusyForInbound(synthetic);
|
|
55572
56191
|
process.stderr.write(`telegram gateway: vault_grant_approved injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${delivered}
|
|
55573
56192
|
`);
|
|
55574
56193
|
if (!delivered) {
|
|
@@ -55615,6 +56234,8 @@ async function handleVaultRequestAccessCallback(ctx, data) {
|
|
|
55615
56234
|
operatorId: senderId
|
|
55616
56235
|
});
|
|
55617
56236
|
const denyDelivered = ipcServer.sendToAgent(pending2.agent, denyInbound);
|
|
56237
|
+
if (denyDelivered)
|
|
56238
|
+
markClaudeBusyForInbound(denyInbound);
|
|
55618
56239
|
process.stderr.write(`telegram gateway: vault_grant_denied injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${denyDelivered}
|
|
55619
56240
|
`);
|
|
55620
56241
|
if (!denyDelivered) {
|
|
@@ -55698,6 +56319,8 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
55698
56319
|
operatorId: senderId
|
|
55699
56320
|
});
|
|
55700
56321
|
const dDelivered = ipcServer.sendToAgent(pending2.agent, discardInbound);
|
|
56322
|
+
if (dDelivered)
|
|
56323
|
+
markClaudeBusyForInbound(discardInbound);
|
|
55701
56324
|
process.stderr.write(`telegram gateway: vault_save_discarded injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${dDelivered}
|
|
55702
56325
|
`);
|
|
55703
56326
|
if (!dDelivered)
|
|
@@ -55758,6 +56381,8 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
55758
56381
|
reason: failReason
|
|
55759
56382
|
});
|
|
55760
56383
|
const fDelivered = ipcServer.sendToAgent(pending2.agent, failInbound);
|
|
56384
|
+
if (fDelivered)
|
|
56385
|
+
markClaudeBusyForInbound(failInbound);
|
|
55761
56386
|
process.stderr.write(`telegram gateway: vault_save_failed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${fDelivered}
|
|
55762
56387
|
`);
|
|
55763
56388
|
if (!fDelivered)
|
|
@@ -55775,6 +56400,8 @@ async function handleVaultRequestSaveCallback(ctx, data) {
|
|
|
55775
56400
|
operatorId: senderId
|
|
55776
56401
|
});
|
|
55777
56402
|
const okDelivered = ipcServer.sendToAgent(pending2.agent, okInbound);
|
|
56403
|
+
if (okDelivered)
|
|
56404
|
+
markClaudeBusyForInbound(okInbound);
|
|
55778
56405
|
process.stderr.write(`telegram gateway: vault_save_completed injection agent=${pending2.agent} key=${pending2.key} stage=${stageId} delivered=${okDelivered}
|
|
55779
56406
|
`);
|
|
55780
56407
|
if (!okDelivered)
|
|
@@ -56337,7 +56964,8 @@ async function handleOperatorEventCallback(ctx, data) {
|
|
|
56337
56964
|
parseMode: "HTML",
|
|
56338
56965
|
synthInbound: async () => {
|
|
56339
56966
|
await runSwitchroomAuthCommand(ctx, ["auth", "reauth", agent], `auth reauth ${agent}`);
|
|
56340
|
-
|
|
56967
|
+
const reauthThreadId = ctx.callbackQuery?.message?.message_thread_id;
|
|
56968
|
+
pendingReauthFlows.set(chatKey2(String(ctx.chat.id), reauthThreadId ?? null), { agent, startedAt: Date.now() });
|
|
56341
56969
|
}
|
|
56342
56970
|
});
|
|
56343
56971
|
return;
|
|
@@ -57216,6 +57844,8 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
|
|
|
57216
57844
|
`);
|
|
57217
57845
|
const selfAgentBtn = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
57218
57846
|
const btnDelivered = ipcServer.sendToAgent(selfAgentBtn, inboundMsg);
|
|
57847
|
+
if (btnDelivered)
|
|
57848
|
+
markClaudeBusyForInbound(inboundMsg);
|
|
57219
57849
|
if (!btnDelivered) {
|
|
57220
57850
|
pendingInboundBuffer.push(selfAgentBtn, inboundMsg);
|
|
57221
57851
|
swallowingApiCall(() => bot.api.sendMessage(cbChatId, "\u23F3 Agent is restarting \u2014 your button tap is queued and will be processed when it comes back.", cbThreadId != null ? { message_thread_id: cbThreadId } : {}), {
|
|
@@ -57766,7 +58396,7 @@ function getReactionsConfig() {
|
|
|
57766
58396
|
if (agentName3) {
|
|
57767
58397
|
const rawAgent = cfg.agents?.[agentName3];
|
|
57768
58398
|
if (rawAgent) {
|
|
57769
|
-
const resolved =
|
|
58399
|
+
const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
|
|
57770
58400
|
raw = resolved.reactions;
|
|
57771
58401
|
}
|
|
57772
58402
|
}
|
|
@@ -57812,6 +58442,8 @@ function flushReactionBatch(batch) {
|
|
|
57812
58442
|
meta
|
|
57813
58443
|
};
|
|
57814
58444
|
const delivered = ipcServer.sendToAgent(agentName3, inbound);
|
|
58445
|
+
if (delivered)
|
|
58446
|
+
markClaudeBusyForInbound(inbound);
|
|
57815
58447
|
process.stderr.write(`telegram gateway: reactions.dispatch agent=${agentName3} chat=${batch.chatId} count=${batch.reactions.length} batched=${batch.batched} delivered=${delivered}
|
|
57816
58448
|
`);
|
|
57817
58449
|
if (!delivered) {
|