switchroom 0.15.36 → 0.15.38
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 +10 -9
- package/dist/auth-broker/index.js +9 -9
- package/dist/cli/autoaccept-poll.js +13 -7
- package/dist/cli/notion-write-pretool.mjs +9 -9
- package/dist/cli/switchroom.js +480 -217
- package/dist/cli/ui/index.html +87 -17
- package/dist/host-control/main.js +10 -10
- package/dist/vault/approvals/kernel-server.js +9 -9
- package/dist/vault/broker/server.js +9 -9
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +1 -1
- package/profiles/_base/start.sh.hbs +1 -1
- package/profiles/_shared/agent-self-service.md.hbs +25 -0
- package/skills/switchroom-manage/SKILL.md +1 -1
- package/skills/switchroom-runtime/SKILL.md +1 -1
- package/telegram-plugin/answer-stream.ts +1 -1
- package/telegram-plugin/bridge/bridge.ts +50 -1
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/bridge/tool-filter.ts +77 -0
- package/telegram-plugin/chat-lock.ts +1 -1
- package/telegram-plugin/credits-watch.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +60 -3
- package/telegram-plugin/dist/gateway/gateway.js +753 -207
- package/telegram-plugin/dist/server.js +64 -4
- package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
- package/telegram-plugin/gateway/boot-card.ts +5 -1
- package/telegram-plugin/gateway/boot-probes.ts +62 -0
- package/telegram-plugin/gateway/cron-session.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +254 -15
- package/telegram-plugin/gateway/grant-restart.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
- package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
- package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
- package/telegram-plugin/gateway/linear-activity.ts +56 -0
- package/telegram-plugin/gateway/linear-auth-watch.ts +102 -0
- package/telegram-plugin/gateway/linear-setup.ts +196 -0
- package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
- package/telegram-plugin/gateway/permission-timeout.ts +70 -0
- package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
- package/telegram-plugin/interrupt-marker.ts +1 -1
- package/telegram-plugin/over-ping-safety-net.ts +1 -1
- package/telegram-plugin/scoped-approval.ts +1 -1
- package/telegram-plugin/secret-detect/vault-error.ts +1 -1
- package/telegram-plugin/silence-poke.ts +2 -2
- package/telegram-plugin/silent-reply-anchor.ts +1 -1
- package/telegram-plugin/slot-banner-driver.ts +1 -1
- package/telegram-plugin/startup-reset.ts +1 -1
- package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
- package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
- package/telegram-plugin/tests/linear-agent-activity.test.ts +77 -0
- package/telegram-plugin/tests/linear-agent-setup.test.ts +132 -0
- package/telegram-plugin/tests/linear-auth-watch.test.ts +79 -0
- package/telegram-plugin/tests/linear-create-issue.test.ts +3 -1
- package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
- package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
- package/telegram-plugin/tests/silence-poke.test.ts +1 -1
- package/telegram-plugin/tests/tool-filter.test.ts +87 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
- package/telegram-plugin/turn-flush-safety.ts +1 -1
- package/telegram-plugin/uat/assertions.ts +1 -1
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
- package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
|
@@ -23847,7 +23847,7 @@ var init_schema = __esm(() => {
|
|
|
23847
23847
|
ScheduleEntrySchema = exports_external.object({
|
|
23848
23848
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
23849
23849
|
prompt: exports_external.string().optional().describe("Prompt to send at the scheduled time (the escalation prompt when " + "kind=poll; templated with {{diff}}). Required for kind prompt/poll; " + "absent for kind=action (an action has no model fire, so no prompt)."),
|
|
23850
|
-
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (
|
|
23850
|
+
kind: exports_external.enum(["poll", "prompt", "action"]).optional().describe("Tier-0 routing (reference/rfcs/cheap-cron-sessions.md). 'prompt' (default) " + "fires a model turn every tick (Tier 1/2 per `context`). 'poll' runs a " + "model-free deterministic check (requires `poll`) and only escalates to " + "a model fire on a hit. 'action' runs a model-free deterministic verb " + "(requires `action`) that COMPLETES the work and never escalates \u2014 zero " + "tokens, no session. poll/prompt tiering is on by default " + "(SWITCHROOM_CHEAP_CRON=0 is the kill-switch); an action is model-free " + "regardless (the kill-switch governs model tiering, not deterministic " + "actions)."),
|
|
23851
23851
|
poll: PollSpecSchema.optional().describe("Required iff kind=poll. The declarative poll spec."),
|
|
23852
23852
|
action: ActionSpecSchema.optional().describe("Required iff kind=action. The declarative action spec (telegram-message or webhook)."),
|
|
23853
23853
|
model: exports_external.string().optional().describe("Cron model hint. Reactivated by SWITCHROOM_CHEAP_CRON (was DEPRECATED/" + "IGNORED in v0.8). A known-cheap id (sonnet/haiku family) routes the " + "fire to a fresh cheap cron session (Tier 1, `context: fresh`); 'opus', " + "a custom id, or unset routes to the agent's live session (Tier 2, " + "`context: agent`) \u2014 the conservative default that preserves pre-v0.8 " + "behaviour. Note: a live session's model is fixed at launch, so on Tier " + "2 this is informational. See docs/scheduling.md."),
|
|
@@ -23856,7 +23856,7 @@ var init_schema = __esm(() => {
|
|
|
23856
23856
|
topic: exports_external.union([
|
|
23857
23857
|
exports_external.string().min(1, "topic alias must be non-empty"),
|
|
23858
23858
|
exports_external.number().int().positive("topic ID must be a positive integer")
|
|
23859
|
-
]).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
|
|
23859
|
+
]).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 reference/rfcs/supergroup-mode.md.")
|
|
23860
23860
|
}).superRefine((entry, ctx) => {
|
|
23861
23861
|
const kind = entry.kind ?? "prompt";
|
|
23862
23862
|
if (kind === "poll" && !entry.poll) {
|
|
@@ -24029,15 +24029,15 @@ var init_schema = __esm(() => {
|
|
|
24029
24029
|
webhook_rate_limit: exports_external.object({
|
|
24030
24030
|
rpm: exports_external.number().int().positive()
|
|
24031
24031
|
}).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."),
|
|
24032
|
-
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See
|
|
24033
|
-
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See
|
|
24032
|
+
webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See reference/rfcs/webhook-via-gateway-socket.md."),
|
|
24033
|
+
webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See reference/rfcs/webhook-cloudflare-edge-lock.md."),
|
|
24034
24034
|
linear_agent: exports_external.object({
|
|
24035
24035
|
enabled: exports_external.boolean(),
|
|
24036
24036
|
token: exports_external.string().describe("vault:<key> reference to the Linear OAuth app token (actor=app). " + "Resolved at runtime via the vault broker (canonically " + "vault:linear/<agent>/token). Never an inline literal."),
|
|
24037
24037
|
workspace_id: exports_external.string().optional().describe("Optional Linear workspace (organization) id this agent is " + "installed into. Informational \u2014 used for setup hints and " + "multi-workspace disambiguation; the token already scopes the " + "app to its workspace."),
|
|
24038
24038
|
default_team_id: exports_external.string().optional().describe("Optional Linear team id new captured issues file into when the " + "agent doesn't pass an explicit team_id. Unnecessary for a " + "single-team workspace (auto-resolved); set it only when the " + "workspace has multiple teams. Manage via " + "`switchroom linear-agent set-team <agent> <team>`.")
|
|
24039
24039
|
}).optional().describe("Linear first-class agent integration (#2298). When enabled, the " + "agent appears in a Linear workspace as an app actor (own name/" + "avatar, @-mentionable, delegate-assignable). Linear AgentSessionEvent " + "webhooks (mention / delegation) wake the agent instantly via the " + "same gateway inject path as webhook_dispatch, tagged " + 'meta.source="linear" with the agent_session_id, and the agent ' + "responds with structured AgentActivity (thought/message/complete/" + "error) via the linear_agent_activity MCP tool. Builds the " + "session-lifecycle layer on top of the plain webhook_sources:[linear] " + "+ webhook_dispatch support (#2272). The OAuth app token is stored in " + "the vault and referenced here as vault:linear/<agent>/token; run " + "`switchroom linear-agent setup <agent>` to provision it. Off by " + "default \u2014 opt in per agent. Cascades from " + "defaults.channels.telegram.linear_agent."),
|
|
24040
|
-
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
|
|
24040
|
+
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 reference/rfcs/supergroup-mode.md."),
|
|
24041
24041
|
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. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "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`."),
|
|
24042
24042
|
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.")
|
|
24043
24043
|
}).optional().superRefine((tg, ctx) => {
|
|
@@ -24263,7 +24263,7 @@ var init_schema = __esm(() => {
|
|
|
24263
24263
|
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."),
|
|
24264
24264
|
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)."),
|
|
24265
24265
|
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."),
|
|
24266
|
-
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC
|
|
24266
|
+
notion_workspace: AgentNotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Per-agent Notion access. " + "Presence opts the agent IN (launcher scaffolded, MCP entry emitted, " + "broker grants the integration token). Optional `databases:` filter " + "narrows which DBs this agent may read/write \u2014 names must resolve in " + "top-level notion_workspace.databases. Absence opts the agent OUT."),
|
|
24267
24267
|
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({
|
|
24268
24268
|
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."),
|
|
24269
24269
|
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.")
|
|
@@ -24398,9 +24398,9 @@ var init_schema = __esm(() => {
|
|
|
24398
24398
|
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."),
|
|
24399
24399
|
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)."),
|
|
24400
24400
|
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."),
|
|
24401
|
-
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC
|
|
24401
|
+
notion_workspace: NotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Top-level Notion integration " + "config \u2014 vault key for the integration token, friendly-name \u2192 " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
|
|
24402
24402
|
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."),
|
|
24403
|
-
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (
|
|
24403
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/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)."),
|
|
24404
24404
|
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. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
24405
24405
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container \u2014 then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
24406
24406
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
@@ -24422,7 +24422,7 @@ var init_schema = __esm(() => {
|
|
|
24422
24422
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
24423
24423
|
message: "Agent name must start with a letter/digit, contain only lowercase letters/digits/hyphens/underscores, and be at most 51 characters (Telegram callback_data byte limit)"
|
|
24424
24424
|
}), AgentSchema).describe("Map of agent name to agent configuration"),
|
|
24425
|
-
cron: CronConfigSchema.optional().describe("Cheap-cron settings (
|
|
24425
|
+
cron: CronConfigSchema.optional().describe("Cheap-cron settings (reference/rfcs/cheap-cron-sessions.md). Operator-owned " + "egress allowlist + host-pinned secret bindings for Tier-0 http-diff " + "polls (\u00a76.1). Required to enable any http-diff poll; not agent-writable.")
|
|
24426
24426
|
});
|
|
24427
24427
|
});
|
|
24428
24428
|
|
|
@@ -29729,6 +29729,33 @@ function renderBucketedSkills(switchroom, agent) {
|
|
|
29729
29729
|
parts.push(`Agent: ${agent.join(", ")}`);
|
|
29730
29730
|
return parts.length === 0 ? "none resolved" : parts.join(" \u00b7 ");
|
|
29731
29731
|
}
|
|
29732
|
+
async function probeConnections(agentDir, opts = {}) {
|
|
29733
|
+
return withTimeout("Connections", (async () => {
|
|
29734
|
+
const path = join24(agentDir, ".claude", "connection-health.json");
|
|
29735
|
+
const read = opts.readFileImpl ?? ((p) => readFileSync25(p, "utf8"));
|
|
29736
|
+
let issues = [];
|
|
29737
|
+
try {
|
|
29738
|
+
const parsed = JSON.parse(read(path));
|
|
29739
|
+
issues = Array.isArray(parsed.issues) ? parsed.issues : [];
|
|
29740
|
+
} catch {
|
|
29741
|
+
return { status: "ok", label: "Connections", detail: "no issues" };
|
|
29742
|
+
}
|
|
29743
|
+
if (issues.length === 0) {
|
|
29744
|
+
return { status: "ok", label: "Connections", detail: "all authed" };
|
|
29745
|
+
}
|
|
29746
|
+
const servers = [...new Set(issues.map((i) => i.server))];
|
|
29747
|
+
const named = servers.slice(0, 4).join(", ");
|
|
29748
|
+
const more = servers.length > 4 ? ` +${servers.length - 4} more` : "";
|
|
29749
|
+
const first = issues[0];
|
|
29750
|
+
const extra = issues.length > 1 ? ` (+${issues.length - 1} more \u2014 run \`switchroom doctor\`)` : "";
|
|
29751
|
+
return {
|
|
29752
|
+
status: "degraded",
|
|
29753
|
+
label: "Connections",
|
|
29754
|
+
detail: `${servers.length} integration(s) configured but not authed: ${named}${more}`,
|
|
29755
|
+
nextStep: `${first.fix}${extra}`
|
|
29756
|
+
};
|
|
29757
|
+
})());
|
|
29758
|
+
}
|
|
29732
29759
|
var execFile3, PROBE_TIMEOUT_MS = 2000, QUOTA_BROKER_TIMEOUT_MS = 7000, QUOTA_DIRECT_FALLBACK_TIMEOUT_MS = 5000, QUOTA_PROBE_OUTER_TIMEOUT_MS = 9000, TOKEN_EXPIRING_SOON_DAYS = 7, AGENT_RETRY_INTERVAL_MS = 1500, AGENT_RETRY_MAX_MS = 12000, AGENT_LIVE_WINDOW_MS = 45000, AGENT_LIVE_POLL_INTERVAL_MS = 2000, AGENT_LIVE_FOLLOWUP_REPOLL_MS = 30000, realProcFs, SCHEDULER_LOCK_PATH_DEFAULT = "/state/agent/scheduler.lock", SCHEDULER_JSONL_PATH_DEFAULT = "/state/agent/scheduler.jsonl", SCHEDULER_FRESH_BOOT_MS = 30000, realSchedulerFs, realSkillsFs;
|
|
29733
29760
|
var init_boot_probes = __esm(() => {
|
|
29734
29761
|
init_quota_cache();
|
|
@@ -30164,6 +30191,9 @@ async function runAllProbes(opts) {
|
|
|
30164
30191
|
}),
|
|
30165
30192
|
probeSkills(opts.agentDir, { agentName: opts.agentSlug ?? opts.agentName }).then((r) => {
|
|
30166
30193
|
probes.skills = r;
|
|
30194
|
+
}),
|
|
30195
|
+
probeConnections(opts.agentDir).then((r) => {
|
|
30196
|
+
probes.connections = r;
|
|
30167
30197
|
})
|
|
30168
30198
|
]);
|
|
30169
30199
|
return probes;
|
|
@@ -30404,7 +30434,8 @@ var init_boot_card = __esm(() => {
|
|
|
30404
30434
|
scheduler: "Scheduler",
|
|
30405
30435
|
broker: "Broker",
|
|
30406
30436
|
kernel: "Kernel",
|
|
30407
|
-
skills: "Skills"
|
|
30437
|
+
skills: "Skills",
|
|
30438
|
+
connections: "Connections"
|
|
30408
30439
|
};
|
|
30409
30440
|
PROBE_KEYS = [
|
|
30410
30441
|
"account",
|
|
@@ -30415,7 +30446,8 @@ var init_boot_card = __esm(() => {
|
|
|
30415
30446
|
"scheduler",
|
|
30416
30447
|
"broker",
|
|
30417
30448
|
"kernel",
|
|
30418
|
-
"skills"
|
|
30449
|
+
"skills",
|
|
30450
|
+
"connections"
|
|
30419
30451
|
];
|
|
30420
30452
|
REASON_EMOJI = {
|
|
30421
30453
|
planned: "\u2705",
|
|
@@ -32953,6 +32985,32 @@ function parseSourceMessageId(raw) {
|
|
|
32953
32985
|
return n;
|
|
32954
32986
|
}
|
|
32955
32987
|
|
|
32988
|
+
// gateway/permission-timeout.ts
|
|
32989
|
+
var SIGNATURE_SEP = String.fromCharCode(0);
|
|
32990
|
+
function permissionSignature(toolName, inputPreview) {
|
|
32991
|
+
return toolName + SIGNATURE_SEP + inputPreview;
|
|
32992
|
+
}
|
|
32993
|
+
function timeoutDenyMessage(timeoutMinutes) {
|
|
32994
|
+
return `No operator responded within ${timeoutMinutes} minutes, so this request timed out. ` + `This is a TIMEOUT, not a denial \u2014 the operator is likely away. ` + `Do NOT retry this exact action automatically. Tell the user it is still ` + `awaiting their approval, then continue with other work or stop.`;
|
|
32995
|
+
}
|
|
32996
|
+
var duplicateDenyMessage = `This exact action already timed out awaiting the operator, and they have not ` + `responded since. Do NOT keep re-requesting it \u2014 tell the user it needs their ` + `approval when they are back, and move on to other work or stop.`;
|
|
32997
|
+
function isRecentTimeoutDuplicate(timeouts, sig, now, windowMs) {
|
|
32998
|
+
const at = timeouts.get(sig);
|
|
32999
|
+
return at != null && now - at <= windowMs;
|
|
33000
|
+
}
|
|
33001
|
+
|
|
33002
|
+
// gateway/permission-card-origin.ts
|
|
33003
|
+
function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
|
|
33004
|
+
let best = null;
|
|
33005
|
+
for (const t of recentTurns) {
|
|
33006
|
+
if (now - t.startedAt > maxAgeMs)
|
|
33007
|
+
continue;
|
|
33008
|
+
if (best == null || t.startedAt >= best.startedAt)
|
|
33009
|
+
best = t;
|
|
33010
|
+
}
|
|
33011
|
+
return best == null ? null : { chatId: best.sessionChatId, threadId: best.sessionThreadId };
|
|
33012
|
+
}
|
|
33013
|
+
|
|
32956
33014
|
// tool-names.ts
|
|
32957
33015
|
var TELEGRAM_TOOL_PREFIX_RE = /^mcp__[^_].*?telegram__/;
|
|
32958
33016
|
function stripPrefix(toolName) {
|
|
@@ -46206,6 +46264,7 @@ function resolveOutboundTopic(config, event) {
|
|
|
46206
46264
|
}
|
|
46207
46265
|
case "boot":
|
|
46208
46266
|
case "compact-watchdog":
|
|
46267
|
+
case "linear-auth":
|
|
46209
46268
|
if (!inSupergroupMode)
|
|
46210
46269
|
return;
|
|
46211
46270
|
return aliasToId(cfg, ALERTS_ALIAS) ?? cfg.default_topic_id;
|
|
@@ -54460,11 +54519,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
54460
54519
|
}
|
|
54461
54520
|
|
|
54462
54521
|
// ../src/build-info.ts
|
|
54463
|
-
var VERSION = "0.15.
|
|
54464
|
-
var COMMIT_SHA = "
|
|
54465
|
-
var COMMIT_DATE = "2026-06-
|
|
54466
|
-
var LATEST_PR =
|
|
54467
|
-
var COMMITS_AHEAD_OF_TAG =
|
|
54522
|
+
var VERSION = "0.15.38";
|
|
54523
|
+
var COMMIT_SHA = "d28a331f";
|
|
54524
|
+
var COMMIT_DATE = "2026-06-18T11:43:40+10:00";
|
|
54525
|
+
var LATEST_PR = null;
|
|
54526
|
+
var COMMITS_AHEAD_OF_TAG = 13;
|
|
54468
54527
|
|
|
54469
54528
|
// gateway/boot-version.ts
|
|
54470
54529
|
function formatRelativeAgo(iso) {
|
|
@@ -54759,7 +54818,79 @@ init_client2();
|
|
|
54759
54818
|
|
|
54760
54819
|
// ../src/linear/oauth-refresh.ts
|
|
54761
54820
|
var LINEAR_TOKEN_ENDPOINT = "https://api.linear.app/oauth/token";
|
|
54821
|
+
var LINEAR_AUTHORIZE_ENDPOINT = "https://linear.app/oauth/authorize";
|
|
54822
|
+
var LINEAR_AGENT_SCOPES = [
|
|
54823
|
+
"read",
|
|
54824
|
+
"write",
|
|
54825
|
+
"app:assignable",
|
|
54826
|
+
"app:mentionable"
|
|
54827
|
+
];
|
|
54762
54828
|
var DEFAULT_REFRESH_SKEW_SEC = 2 * 3600;
|
|
54829
|
+
function buildLinearAuthorizeUrl(args) {
|
|
54830
|
+
const params = new URLSearchParams({
|
|
54831
|
+
client_id: args.clientId,
|
|
54832
|
+
redirect_uri: args.redirectUri,
|
|
54833
|
+
response_type: "code",
|
|
54834
|
+
scope: (args.scopes ?? LINEAR_AGENT_SCOPES).join(","),
|
|
54835
|
+
actor: "app",
|
|
54836
|
+
prompt: "consent"
|
|
54837
|
+
});
|
|
54838
|
+
if (args.state)
|
|
54839
|
+
params.set("state", args.state);
|
|
54840
|
+
return `${LINEAR_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
54841
|
+
}
|
|
54842
|
+
async function exchangeLinearAuthCode(args, opts = {}) {
|
|
54843
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
54844
|
+
const nowSec = opts.nowSec ?? (() => Math.floor(Date.now() / 1000));
|
|
54845
|
+
const form = new URLSearchParams({
|
|
54846
|
+
grant_type: "authorization_code",
|
|
54847
|
+
code: args.code,
|
|
54848
|
+
client_id: args.clientId,
|
|
54849
|
+
client_secret: args.clientSecret,
|
|
54850
|
+
redirect_uri: args.redirectUri
|
|
54851
|
+
});
|
|
54852
|
+
let resp;
|
|
54853
|
+
try {
|
|
54854
|
+
resp = await fetchImpl(LINEAR_TOKEN_ENDPOINT, {
|
|
54855
|
+
method: "POST",
|
|
54856
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
54857
|
+
body: form.toString()
|
|
54858
|
+
});
|
|
54859
|
+
} catch (err) {
|
|
54860
|
+
return { ok: false, reason: "network", detail: err.message };
|
|
54861
|
+
}
|
|
54862
|
+
if (!resp.ok) {
|
|
54863
|
+
const txt = await resp.text().catch(() => "");
|
|
54864
|
+
const badCode = resp.status === 400 || /invalid_grant|invalid_request/i.test(txt);
|
|
54865
|
+
return {
|
|
54866
|
+
ok: false,
|
|
54867
|
+
reason: badCode ? "bad_code" : "http_error",
|
|
54868
|
+
detail: `HTTP ${resp.status}${txt ? ` ${txt.slice(0, 200)}` : ""}`
|
|
54869
|
+
};
|
|
54870
|
+
}
|
|
54871
|
+
let json;
|
|
54872
|
+
try {
|
|
54873
|
+
json = await resp.json();
|
|
54874
|
+
} catch {
|
|
54875
|
+
return { ok: false, reason: "bad_response", detail: "non-JSON token response" };
|
|
54876
|
+
}
|
|
54877
|
+
const accessToken = json.access_token;
|
|
54878
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
54879
|
+
return { ok: false, reason: "bad_response", detail: "no access_token in response" };
|
|
54880
|
+
}
|
|
54881
|
+
const refreshToken = json.refresh_token;
|
|
54882
|
+
if (typeof refreshToken !== "string" || refreshToken.length === 0) {
|
|
54883
|
+
return { ok: false, reason: "bad_response", detail: "no refresh_token in response (was actor=app + a fresh consent used?)" };
|
|
54884
|
+
}
|
|
54885
|
+
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 86400;
|
|
54886
|
+
return {
|
|
54887
|
+
ok: true,
|
|
54888
|
+
accessToken,
|
|
54889
|
+
refreshToken,
|
|
54890
|
+
expiresAt: nowSec() + expiresIn,
|
|
54891
|
+
...typeof json.scope === "string" ? { scope: json.scope } : {}
|
|
54892
|
+
};
|
|
54893
|
+
}
|
|
54763
54894
|
async function refreshLinearAppToken(bundle, opts = {}) {
|
|
54764
54895
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
54765
54896
|
const nowSec = opts.nowSec ?? (() => Math.floor(Date.now() / 1000));
|
|
@@ -54808,6 +54939,11 @@ async function refreshLinearAppToken(bundle, opts = {}) {
|
|
|
54808
54939
|
...typeof json.scope === "string" ? { scope: json.scope } : {}
|
|
54809
54940
|
};
|
|
54810
54941
|
}
|
|
54942
|
+
function needsRefresh(expiresAt, nowSec, skewSec = DEFAULT_REFRESH_SKEW_SEC) {
|
|
54943
|
+
if (expiresAt == null)
|
|
54944
|
+
return false;
|
|
54945
|
+
return nowSec >= expiresAt - skewSec;
|
|
54946
|
+
}
|
|
54811
54947
|
function parseBundle(raw) {
|
|
54812
54948
|
if (raw == null || raw === "")
|
|
54813
54949
|
return null;
|
|
@@ -54863,6 +54999,17 @@ async function performLinearRefresh(io) {
|
|
|
54863
54999
|
|
|
54864
55000
|
// gateway/linear-activity.ts
|
|
54865
55001
|
var LINEAR_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
|
|
55002
|
+
function escapeHtmlMin(s) {
|
|
55003
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
55004
|
+
}
|
|
55005
|
+
function buildLinearAuthDeadMessage(agent, reason) {
|
|
55006
|
+
const a = escapeHtmlMin(agent);
|
|
55007
|
+
const why = reason === "no_bundle" ? `no refresh credentials are stored (<code>linear/${a}/oauth</code> is missing), so its daily-expiring token can't renew` : `its Linear refresh token was revoked`;
|
|
55008
|
+
return `\uD83D\uDD11 <b>Linear auth needs you</b>
|
|
55009
|
+
` + `<b>${a}</b> can't reach Linear \u2014 ${why}. ` + `Its access token will keep failing until you re-authorize.
|
|
55010
|
+
|
|
55011
|
+
` + `Re-auth (actor=app) then run <code>switchroom linear-agent setup --agent ${a} ` + `--token \u2026 --refresh-token \u2026 --client-id \u2026 --client-secret \u2026</code> on the host, ` + `or ask me to walk you through it.`;
|
|
55012
|
+
}
|
|
54866
55013
|
async function defaultResolveLinearToken(agent) {
|
|
54867
55014
|
const key = `linear/${agent}/token`;
|
|
54868
55015
|
const token = readVaultTokenFile(agent) ?? undefined;
|
|
@@ -54899,7 +55046,7 @@ function brokerRefreshIO(agent, fetchImpl) {
|
|
|
54899
55046
|
...fetchImpl ? { fetchImpl } : {}
|
|
54900
55047
|
};
|
|
54901
55048
|
}
|
|
54902
|
-
async function linearPostWithRefresh(body, token, agent, fetchImpl, log, refreshIO) {
|
|
55049
|
+
async function linearPostWithRefresh(body, token, agent, fetchImpl, log, refreshIO, onAuthUnrecoverable) {
|
|
54903
55050
|
const post = (t) => fetchImpl(LINEAR_GRAPHQL_ENDPOINT, {
|
|
54904
55051
|
method: "POST",
|
|
54905
55052
|
headers: { "Content-Type": "application/json", Authorization: t },
|
|
@@ -54914,6 +55061,11 @@ async function linearPostWithRefresh(body, token, agent, fetchImpl, log, refresh
|
|
|
54914
55061
|
if (refreshed.reason === "revoked") {
|
|
54915
55062
|
log(`telegram gateway: linear token REVOKED agent=${agent} \u2014 refresh token is dead; ` + `operator must re-authorize (linear-agent setup --refresh-token \u2026)
|
|
54916
55063
|
`);
|
|
55064
|
+
onAuthUnrecoverable?.({ agent, reason: "revoked", detail: refreshed.detail });
|
|
55065
|
+
} else if (refreshed.reason === "no_bundle") {
|
|
55066
|
+
log(`telegram gateway: linear token DEAD agent=${agent} \u2014 no refresh bundle stored ` + `(linear/${agent}/oauth absent); operator must re-authorize
|
|
55067
|
+
`);
|
|
55068
|
+
onAuthUnrecoverable?.({ agent, reason: "no_bundle", detail: refreshed.detail });
|
|
54917
55069
|
} else {
|
|
54918
55070
|
log(`telegram gateway: linear token refresh failed agent=${agent} reason=${refreshed.reason}
|
|
54919
55071
|
`);
|
|
@@ -54969,7 +55121,7 @@ async function emitLinearAgentActivity(args, deps = {}) {
|
|
|
54969
55121
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
54970
55122
|
let resp;
|
|
54971
55123
|
try {
|
|
54972
|
-
({ resp } = await linearPostWithRefresh(JSON.stringify({ query: mutation, variables }), tokenResult.token, agent, fetchImpl, log, deps.refreshIO));
|
|
55124
|
+
({ resp } = await linearPostWithRefresh(JSON.stringify({ query: mutation, variables }), tokenResult.token, agent, fetchImpl, log, deps.refreshIO, deps.onAuthUnrecoverable));
|
|
54973
55125
|
} catch (err) {
|
|
54974
55126
|
return {
|
|
54975
55127
|
content: [{ type: "text", text: `linear_agent_activity failed: request error: ${err.message}` }]
|
|
@@ -55033,7 +55185,7 @@ async function createLinearIssue(args, deps = {}) {
|
|
|
55033
55185
|
const gql = async (query2, variables) => {
|
|
55034
55186
|
let resp;
|
|
55035
55187
|
try {
|
|
55036
|
-
const out = await linearPostWithRefresh(JSON.stringify({ query: query2, variables }), activeToken, agent, fetchImpl, log, deps.refreshIO);
|
|
55188
|
+
const out = await linearPostWithRefresh(JSON.stringify({ query: query2, variables }), activeToken, agent, fetchImpl, log, deps.refreshIO, deps.onAuthUnrecoverable);
|
|
55037
55189
|
resp = out.resp;
|
|
55038
55190
|
activeToken = out.token;
|
|
55039
55191
|
} catch (err) {
|
|
@@ -55102,6 +55254,261 @@ async function createLinearIssue(args, deps = {}) {
|
|
|
55102
55254
|
return { content: [{ type: "text", text: `Filed: ${title} \u2192 ${issue.url}` }] };
|
|
55103
55255
|
}
|
|
55104
55256
|
|
|
55257
|
+
// gateway/linear-setup.ts
|
|
55258
|
+
init_client2();
|
|
55259
|
+
var tokenKey = (agent) => `linear/${agent}/token`;
|
|
55260
|
+
var bundleKey = (agent) => `linear/${agent}/oauth`;
|
|
55261
|
+
function defaultPut(agent, key, value) {
|
|
55262
|
+
const token = readVaultTokenFile(agent) ?? undefined;
|
|
55263
|
+
const opt = token ? { token } : {};
|
|
55264
|
+
return putViaBroker(key, { kind: "string", value }, opt).then((r) => {
|
|
55265
|
+
if (r.kind === "ok")
|
|
55266
|
+
return { kind: "ok" };
|
|
55267
|
+
if (r.kind === "unreachable")
|
|
55268
|
+
return { kind: "unreachable", msg: r.msg };
|
|
55269
|
+
if (r.kind === "not_found")
|
|
55270
|
+
return { kind: "not_found", msg: r.msg };
|
|
55271
|
+
return { kind: "denied", msg: r.msg };
|
|
55272
|
+
});
|
|
55273
|
+
}
|
|
55274
|
+
function text(s) {
|
|
55275
|
+
return { content: [{ type: "text", text: s }] };
|
|
55276
|
+
}
|
|
55277
|
+
function writeGrantGuidance(agent) {
|
|
55278
|
+
return `I need write access to store the Linear credentials. Call:
|
|
55279
|
+
` + `\u2022 vault_request_access(key: "${tokenKey(agent)}", scope: "write", reason: "store Linear app access token")
|
|
55280
|
+
` + `\u2022 vault_request_access(key: "${bundleKey(agent)}", scope: "write", reason: "store Linear OAuth refresh bundle")
|
|
55281
|
+
` + `Once the operator approves both, re-run linear_agent_setup with action "complete" (same code is single-use \u2014 if it expired, re-open the authorize URL first).`;
|
|
55282
|
+
}
|
|
55283
|
+
function durableConfigGuidance(agent) {
|
|
55284
|
+
return `Stored. To make this durable (survive restarts + enable auto-refresh), propose a config edit ` + `(config_propose_edit) that, under agents.${agent}:
|
|
55285
|
+
` + ` \u2022 adds channels.telegram.linear_agent: { enabled: true, token: "vault:${tokenKey(agent)}" }
|
|
55286
|
+
` + ` \u2022 adds "${tokenKey(agent)}" and "${bundleKey(agent)}" to secrets[]
|
|
55287
|
+
` + `Then the operator approves it and you restart to pick up the linear_agent block.`;
|
|
55288
|
+
}
|
|
55289
|
+
async function runLinearAgentSetup(args, deps = {}) {
|
|
55290
|
+
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
55291
|
+
const agent = deps.agent ?? process.env.SWITCHROOM_AGENT_NAME ?? "-";
|
|
55292
|
+
if (agent === "-" || !/^[a-z][a-z0-9_-]{0,63}$/.test(agent)) {
|
|
55293
|
+
return text(`linear_agent_setup failed: could not resolve a valid agent name (got '${agent}').`);
|
|
55294
|
+
}
|
|
55295
|
+
const action = args.action;
|
|
55296
|
+
if (action !== "authorize_url" && action !== "complete") {
|
|
55297
|
+
return text(`linear_agent_setup failed: action must be "authorize_url" or "complete".`);
|
|
55298
|
+
}
|
|
55299
|
+
const clientId = args.client_id?.trim();
|
|
55300
|
+
const redirectUri = args.redirect_uri?.trim();
|
|
55301
|
+
if (!clientId)
|
|
55302
|
+
return text("linear_agent_setup failed: client_id is required.");
|
|
55303
|
+
if (!redirectUri || !/^https?:\/\//.test(redirectUri)) {
|
|
55304
|
+
return text("linear_agent_setup failed: redirect_uri is required and must be an http(s) URL registered on the Linear OAuth app.");
|
|
55305
|
+
}
|
|
55306
|
+
if (action === "authorize_url") {
|
|
55307
|
+
const url = buildLinearAuthorizeUrl({ clientId, redirectUri });
|
|
55308
|
+
return text(`Open this URL in a browser to authorize <b>${agent}</b> as a Linear app actor (actor=app):
|
|
55309
|
+
|
|
55310
|
+
${url}
|
|
55311
|
+
|
|
55312
|
+
` + `After you approve, Linear redirects to ${redirectUri}?code=\u2026 (it may show a blank/error page \u2014 that's fine). ` + `Copy the code value from the URL bar, then run linear_agent_setup with action "complete", the same client_id + redirect_uri, ` + `your client_secret, and that code.`);
|
|
55313
|
+
}
|
|
55314
|
+
const clientSecret = args.client_secret?.trim();
|
|
55315
|
+
const code = args.code?.trim();
|
|
55316
|
+
if (!clientSecret)
|
|
55317
|
+
return text('linear_agent_setup failed: client_secret is required for action "complete".');
|
|
55318
|
+
if (!code)
|
|
55319
|
+
return text('linear_agent_setup failed: code (from the redirect URL) is required for action "complete".');
|
|
55320
|
+
const exchanged = await exchangeLinearAuthCode({ clientId, clientSecret, code, redirectUri }, deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {});
|
|
55321
|
+
if (!exchanged.ok) {
|
|
55322
|
+
log(`telegram gateway: linear_agent_setup exchange failed agent=${agent} reason=${exchanged.reason}
|
|
55323
|
+
`);
|
|
55324
|
+
if (exchanged.reason === "bad_code") {
|
|
55325
|
+
return text(`linear_agent_setup failed: Linear rejected the authorization code (expired, already used, or wrong redirect_uri). ` + `Re-run action "authorize_url", open the fresh URL, and copy a new code.`);
|
|
55326
|
+
}
|
|
55327
|
+
return text(`linear_agent_setup failed: token exchange ${exchanged.reason} \u2014 ${exchanged.detail}. Retry shortly.`);
|
|
55328
|
+
}
|
|
55329
|
+
const bundle = serializeBundle({
|
|
55330
|
+
clientId,
|
|
55331
|
+
clientSecret,
|
|
55332
|
+
refreshToken: exchanged.refreshToken,
|
|
55333
|
+
expiresAt: exchanged.expiresAt
|
|
55334
|
+
});
|
|
55335
|
+
const putBundle = deps.putBundle ?? ((a, j) => defaultPut(a, bundleKey(a), j));
|
|
55336
|
+
const putToken = deps.putToken ?? ((a, t2) => defaultPut(a, tokenKey(a), t2));
|
|
55337
|
+
const b = await putBundle(agent, bundle);
|
|
55338
|
+
if (b.kind !== "ok") {
|
|
55339
|
+
if (b.kind === "not_found" || b.kind === "denied") {
|
|
55340
|
+
return text(writeGrantGuidance(agent));
|
|
55341
|
+
}
|
|
55342
|
+
log(`telegram gateway: linear_agent_setup bundle write ${b.kind} agent=${agent}
|
|
55343
|
+
`);
|
|
55344
|
+
return text(`linear_agent_setup failed: couldn't store the refresh bundle (broker ${b.kind}: ${b.msg}).`);
|
|
55345
|
+
}
|
|
55346
|
+
const t = await putToken(agent, exchanged.accessToken);
|
|
55347
|
+
if (t.kind !== "ok") {
|
|
55348
|
+
if (t.kind === "not_found" || t.kind === "denied") {
|
|
55349
|
+
return text(writeGrantGuidance(agent));
|
|
55350
|
+
}
|
|
55351
|
+
log(`telegram gateway: linear_agent_setup token write ${t.kind} agent=${agent}
|
|
55352
|
+
`);
|
|
55353
|
+
return text(`linear_agent_setup failed: couldn't store the access token (broker ${t.kind}: ${t.msg}).`);
|
|
55354
|
+
}
|
|
55355
|
+
const hours = Math.max(1, Math.round((exchanged.expiresAt - Date.now() / 1000) / 3600));
|
|
55356
|
+
log(`telegram gateway: linear_agent_setup stored token+bundle agent=${agent} (expires ~${hours}h)
|
|
55357
|
+
`);
|
|
55358
|
+
return text(`\u2705 Linear app token + refresh bundle stored for ${agent} (access token expires in ~${hours}h; it now auto-renews).
|
|
55359
|
+
|
|
55360
|
+
` + durableConfigGuidance(agent));
|
|
55361
|
+
}
|
|
55362
|
+
|
|
55363
|
+
// gateway/linear-auth-watch.ts
|
|
55364
|
+
async function runLinearAuthCheck(deps) {
|
|
55365
|
+
const log = deps.log ?? (() => {});
|
|
55366
|
+
if (!deps.linearEnabled())
|
|
55367
|
+
return "disabled";
|
|
55368
|
+
let raw;
|
|
55369
|
+
try {
|
|
55370
|
+
raw = await deps.readBundle();
|
|
55371
|
+
} catch (err) {
|
|
55372
|
+
log(`telegram gateway: linear-auth-watch agent=${deps.agent} bundle read error: ${err.message}
|
|
55373
|
+
`);
|
|
55374
|
+
return "refresh_failed";
|
|
55375
|
+
}
|
|
55376
|
+
const bundle = parseBundle(raw);
|
|
55377
|
+
if (!bundle) {
|
|
55378
|
+
log(`telegram gateway: linear-auth-watch agent=${deps.agent} \u2014 no refresh bundle (proactive)
|
|
55379
|
+
`);
|
|
55380
|
+
deps.onAuthDead({ agent: deps.agent, reason: "no_bundle", detail: "proactive watch: linear/<agent>/oauth missing or invalid" });
|
|
55381
|
+
return "no_bundle";
|
|
55382
|
+
}
|
|
55383
|
+
const now = deps.nowSec ? deps.nowSec() : Math.floor(Date.now() / 1000);
|
|
55384
|
+
if (!needsRefresh(bundle.expiresAt, now)) {
|
|
55385
|
+
return "fresh";
|
|
55386
|
+
}
|
|
55387
|
+
const res = await deps.refresh();
|
|
55388
|
+
if (res.ok) {
|
|
55389
|
+
log(`telegram gateway: linear-auth-watch agent=${deps.agent} proactively refreshed (was near expiry)
|
|
55390
|
+
`);
|
|
55391
|
+
return "refreshed";
|
|
55392
|
+
}
|
|
55393
|
+
if (res.reason === "revoked") {
|
|
55394
|
+
log(`telegram gateway: linear-auth-watch agent=${deps.agent} refresh REVOKED (proactive)
|
|
55395
|
+
`);
|
|
55396
|
+
deps.onAuthDead({ agent: deps.agent, reason: "revoked", detail: res.detail });
|
|
55397
|
+
return "revoked";
|
|
55398
|
+
}
|
|
55399
|
+
if (res.reason === "no_bundle") {
|
|
55400
|
+
deps.onAuthDead({ agent: deps.agent, reason: "no_bundle", detail: res.detail });
|
|
55401
|
+
return "no_bundle";
|
|
55402
|
+
}
|
|
55403
|
+
log(`telegram gateway: linear-auth-watch agent=${deps.agent} proactive refresh failed reason=${res.reason}
|
|
55404
|
+
`);
|
|
55405
|
+
return "refresh_failed";
|
|
55406
|
+
}
|
|
55407
|
+
|
|
55408
|
+
// ../src/linear/oauth-refresh.ts
|
|
55409
|
+
var LINEAR_TOKEN_ENDPOINT2 = "https://api.linear.app/oauth/token";
|
|
55410
|
+
var DEFAULT_REFRESH_SKEW_SEC2 = 2 * 3600;
|
|
55411
|
+
async function refreshLinearAppToken2(bundle, opts = {}) {
|
|
55412
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
55413
|
+
const nowSec = opts.nowSec ?? (() => Math.floor(Date.now() / 1000));
|
|
55414
|
+
const form = new URLSearchParams({
|
|
55415
|
+
grant_type: "refresh_token",
|
|
55416
|
+
refresh_token: bundle.refreshToken,
|
|
55417
|
+
client_id: bundle.clientId,
|
|
55418
|
+
client_secret: bundle.clientSecret
|
|
55419
|
+
});
|
|
55420
|
+
let resp;
|
|
55421
|
+
try {
|
|
55422
|
+
resp = await fetchImpl(LINEAR_TOKEN_ENDPOINT2, {
|
|
55423
|
+
method: "POST",
|
|
55424
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
55425
|
+
body: form.toString()
|
|
55426
|
+
});
|
|
55427
|
+
} catch (err) {
|
|
55428
|
+
return { ok: false, reason: "network", detail: err.message };
|
|
55429
|
+
}
|
|
55430
|
+
if (!resp.ok) {
|
|
55431
|
+
const txt = await resp.text().catch(() => "");
|
|
55432
|
+
const revoked = resp.status === 400 || /invalid_grant|invalid_token/i.test(txt);
|
|
55433
|
+
return {
|
|
55434
|
+
ok: false,
|
|
55435
|
+
reason: revoked ? "revoked" : "http_error",
|
|
55436
|
+
detail: `HTTP ${resp.status}${txt ? ` ${txt.slice(0, 200)}` : ""}`
|
|
55437
|
+
};
|
|
55438
|
+
}
|
|
55439
|
+
let json;
|
|
55440
|
+
try {
|
|
55441
|
+
json = await resp.json();
|
|
55442
|
+
} catch {
|
|
55443
|
+
return { ok: false, reason: "bad_response", detail: "non-JSON token response" };
|
|
55444
|
+
}
|
|
55445
|
+
const accessToken = json.access_token;
|
|
55446
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
55447
|
+
return { ok: false, reason: "bad_response", detail: "no access_token in response" };
|
|
55448
|
+
}
|
|
55449
|
+
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 86400;
|
|
55450
|
+
const rotated = typeof json.refresh_token === "string" && json.refresh_token.length > 0 ? json.refresh_token : bundle.refreshToken;
|
|
55451
|
+
return {
|
|
55452
|
+
ok: true,
|
|
55453
|
+
accessToken,
|
|
55454
|
+
refreshToken: rotated,
|
|
55455
|
+
expiresAt: nowSec() + expiresIn,
|
|
55456
|
+
...typeof json.scope === "string" ? { scope: json.scope } : {}
|
|
55457
|
+
};
|
|
55458
|
+
}
|
|
55459
|
+
function parseBundle2(raw) {
|
|
55460
|
+
if (raw == null || raw === "")
|
|
55461
|
+
return null;
|
|
55462
|
+
let o;
|
|
55463
|
+
try {
|
|
55464
|
+
o = JSON.parse(raw);
|
|
55465
|
+
} catch {
|
|
55466
|
+
return null;
|
|
55467
|
+
}
|
|
55468
|
+
if (typeof o.client_id === "string" && typeof o.client_secret === "string" && typeof o.refresh_token === "string" && o.client_id.length > 0 && o.client_secret.length > 0 && o.refresh_token.length > 0) {
|
|
55469
|
+
return {
|
|
55470
|
+
clientId: o.client_id,
|
|
55471
|
+
clientSecret: o.client_secret,
|
|
55472
|
+
refreshToken: o.refresh_token,
|
|
55473
|
+
...typeof o.expires_at === "number" ? { expiresAt: o.expires_at } : {}
|
|
55474
|
+
};
|
|
55475
|
+
}
|
|
55476
|
+
return null;
|
|
55477
|
+
}
|
|
55478
|
+
function serializeBundle2(b) {
|
|
55479
|
+
return JSON.stringify({
|
|
55480
|
+
client_id: b.clientId,
|
|
55481
|
+
client_secret: b.clientSecret,
|
|
55482
|
+
refresh_token: b.refreshToken,
|
|
55483
|
+
...b.expiresAt != null ? { expires_at: b.expiresAt } : {}
|
|
55484
|
+
});
|
|
55485
|
+
}
|
|
55486
|
+
async function performLinearRefresh2(io) {
|
|
55487
|
+
const raw = await io.readBundle();
|
|
55488
|
+
const bundle = parseBundle2(raw);
|
|
55489
|
+
if (!bundle) {
|
|
55490
|
+
return { ok: false, reason: "no_bundle", detail: "no/invalid refresh bundle" };
|
|
55491
|
+
}
|
|
55492
|
+
const res = await refreshLinearAppToken2(bundle, {
|
|
55493
|
+
...io.fetchImpl ? { fetchImpl: io.fetchImpl } : {},
|
|
55494
|
+
...io.nowSec ? { nowSec: io.nowSec } : {}
|
|
55495
|
+
});
|
|
55496
|
+
if (!res.ok)
|
|
55497
|
+
return { ok: false, reason: res.reason, detail: res.detail };
|
|
55498
|
+
try {
|
|
55499
|
+
await io.writeBundle(serializeBundle2({
|
|
55500
|
+
clientId: bundle.clientId,
|
|
55501
|
+
clientSecret: bundle.clientSecret,
|
|
55502
|
+
refreshToken: res.refreshToken,
|
|
55503
|
+
expiresAt: res.expiresAt
|
|
55504
|
+
}));
|
|
55505
|
+
await io.writeToken(res.accessToken);
|
|
55506
|
+
} catch (err) {
|
|
55507
|
+
return { ok: false, reason: "persist_failed", detail: err.message };
|
|
55508
|
+
}
|
|
55509
|
+
return { ok: true, accessToken: res.accessToken, expiresAt: res.expiresAt };
|
|
55510
|
+
}
|
|
55511
|
+
|
|
55105
55512
|
// vault-approval-posture.ts
|
|
55106
55513
|
function resolveVaultApprovalPosture(broker) {
|
|
55107
55514
|
if (broker?.approvalAuth === "telegram-id") {
|
|
@@ -55637,10 +56044,10 @@ var GRAMMY_VERSION = (() => {
|
|
|
55637
56044
|
return "unknown";
|
|
55638
56045
|
}
|
|
55639
56046
|
})();
|
|
55640
|
-
var sendMessageDraftFn = !DRAFT_ANSWER_LANE_RETIRED && typeof _rawSendMessageDraft === "function" ? (chatId, draftId,
|
|
56047
|
+
var sendMessageDraftFn = !DRAFT_ANSWER_LANE_RETIRED && typeof _rawSendMessageDraft === "function" ? (chatId, draftId, text2, params) => _rawSendMessageDraft({
|
|
55641
56048
|
chat_id: Number(chatId),
|
|
55642
56049
|
draft_id: draftId,
|
|
55643
|
-
text,
|
|
56050
|
+
text: text2,
|
|
55644
56051
|
...params ?? {}
|
|
55645
56052
|
}) : undefined;
|
|
55646
56053
|
var _rawSendChecklist = bot.api.raw.sendChecklist;
|
|
@@ -56262,9 +56669,9 @@ function postQueuedStatus(chatId, bufferedThread, inFlightThread) {
|
|
|
56262
56669
|
if (queuedStatusMsgIds.has(key))
|
|
56263
56670
|
return;
|
|
56264
56671
|
const otherTopic = inFlightThread != null ? `another topic` : `another conversation`;
|
|
56265
|
-
const
|
|
56672
|
+
const text2 = `\u23F3 Queued \u2014 replying in ${otherTopic} first, then I'll get to this.`;
|
|
56266
56673
|
(async () => {
|
|
56267
|
-
const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId,
|
|
56674
|
+
const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId, text2, { message_thread_id: bufferedThread }), { chat_id: chatId, verb: "queued-status.post", threadId: bufferedThread });
|
|
56268
56675
|
const messageId = sent?.message_id;
|
|
56269
56676
|
if (typeof messageId !== "number")
|
|
56270
56677
|
return;
|
|
@@ -56558,9 +56965,9 @@ async function postCompactCard(occ, cap) {
|
|
|
56558
56965
|
resolvedTopic: resolveAgentOutboundTopic({ kind: "compact-watchdog" }) ?? chatThreadMap.get(chatId),
|
|
56559
56966
|
supergroupChatId: resolveAgentSupergroupChatId()
|
|
56560
56967
|
});
|
|
56561
|
-
const
|
|
56968
|
+
const text2 = `\uD83D\uDDDC\uFE0F <b>Context compaction</b>
|
|
56562
56969
|
` + `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).`;
|
|
56563
|
-
const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId,
|
|
56970
|
+
const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId, text2, {
|
|
56564
56971
|
parse_mode: "HTML",
|
|
56565
56972
|
...threadId != null ? { message_thread_id: threadId } : {}
|
|
56566
56973
|
}), { chat_id: chatId, verb: "proactiveCompact.start" });
|
|
@@ -56592,19 +56999,19 @@ async function resolveCompactCard(kind, occNow) {
|
|
|
56592
56999
|
clearTimeout(card.timer);
|
|
56593
57000
|
if (kind === "timeout")
|
|
56594
57001
|
compactNotifyState = idleCompactNotifyState();
|
|
56595
|
-
let
|
|
57002
|
+
let text2;
|
|
56596
57003
|
if (kind === "finished") {
|
|
56597
|
-
|
|
57004
|
+
text2 = `\u2705 <b>Context compacted</b>
|
|
56598
57005
|
` + `Working context reduced` + (occNow != null ? ` (~${card.occAtStart.toLocaleString()} \u2192 ` + `~${occNow.toLocaleString()} tokens)` : "") + `. Hindsight retains the detail.`;
|
|
56599
57006
|
} else if (kind === "superseded") {
|
|
56600
|
-
|
|
57007
|
+
text2 = `\u21A9\uFE0F <b>Context compaction superseded</b>
|
|
56601
57008
|
` + `A newer compaction started before this one confirmed.`;
|
|
56602
57009
|
} else {
|
|
56603
|
-
|
|
57010
|
+
text2 = `\u26A0\uFE0F <b>Compaction issued</b>
|
|
56604
57011
|
` + `<code>/compact</code> was requested but the context isn't confirmed reduced yet. Native compaction and Hindsight still apply.`;
|
|
56605
57012
|
}
|
|
56606
57013
|
try {
|
|
56607
|
-
await swallowingApiCall(() => bot.api.editMessageText(card.chatId, card.messageId,
|
|
57014
|
+
await swallowingApiCall(() => bot.api.editMessageText(card.chatId, card.messageId, text2, {
|
|
56608
57015
|
parse_mode: "HTML"
|
|
56609
57016
|
}), { chat_id: card.chatId, verb: `proactiveCompact.${kind}` });
|
|
56610
57017
|
} catch (err) {
|
|
@@ -56648,6 +57055,14 @@ function resolvePermissionCardTargets() {
|
|
|
56648
57055
|
if (turn != null) {
|
|
56649
57056
|
return [{ chatId: turn.sessionChatId, threadId: turn.sessionThreadId }];
|
|
56650
57057
|
}
|
|
57058
|
+
if (PERMISSION_CARD_ORIGIN_RECOVERY_ENABLED) {
|
|
57059
|
+
const recovered = pickRecoveredPermissionOrigin(recentTurnsById.values(), Date.now(), PERMISSION_CARD_ORIGIN_MAX_AGE_MS);
|
|
57060
|
+
if (recovered != null) {
|
|
57061
|
+
process.stderr.write(`telegram gateway: permission-card origin recovered from recent turn chat=${recovered.chatId} thread=${recovered.threadId ?? "-"} ` + `(currentTurn was null \u2014 force-closed turn)
|
|
57062
|
+
`);
|
|
57063
|
+
return [recovered];
|
|
57064
|
+
}
|
|
57065
|
+
}
|
|
56651
57066
|
const sg = resolveAgentSupergroupChatId();
|
|
56652
57067
|
const topic = resolveAgentOutboundTopic({
|
|
56653
57068
|
kind: "permission",
|
|
@@ -56662,7 +57077,7 @@ function resolvePermissionCardTargets() {
|
|
|
56662
57077
|
function postPermissionResumeMessage(opts) {
|
|
56663
57078
|
if (process.env.SWITCHROOM_RESUME_MSG === "0")
|
|
56664
57079
|
return;
|
|
56665
|
-
const
|
|
57080
|
+
const text2 = formatPermissionResumeMessage({
|
|
56666
57081
|
agentName: process.env.SWITCHROOM_AGENT_NAME ?? null,
|
|
56667
57082
|
behavior: opts.behavior,
|
|
56668
57083
|
action: opts.action,
|
|
@@ -56670,7 +57085,7 @@ function postPermissionResumeMessage(opts) {
|
|
|
56670
57085
|
});
|
|
56671
57086
|
const targets = resolvePermissionCardTargets();
|
|
56672
57087
|
for (const { chatId, threadId } of targets) {
|
|
56673
|
-
swallowingApiCall(() => bot.api.sendMessage(chatId,
|
|
57088
|
+
swallowingApiCall(() => bot.api.sendMessage(chatId, text2, {
|
|
56674
57089
|
parse_mode: "HTML",
|
|
56675
57090
|
...threadId != null ? { message_thread_id: threadId } : {}
|
|
56676
57091
|
}), { chat_id: chatId, verb: "permission-resume", ...threadId != null ? { threadId } : {} });
|
|
@@ -56712,11 +57127,11 @@ function probeAvailableReactions(chatId) {
|
|
|
56712
57127
|
})();
|
|
56713
57128
|
}
|
|
56714
57129
|
var PHOTO_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"]);
|
|
56715
|
-
function chunk2(
|
|
56716
|
-
if (
|
|
56717
|
-
return [
|
|
57130
|
+
function chunk2(text2, limit, mode) {
|
|
57131
|
+
if (text2.length <= limit)
|
|
57132
|
+
return [text2];
|
|
56718
57133
|
const out = [];
|
|
56719
|
-
let rest =
|
|
57134
|
+
let rest = text2;
|
|
56720
57135
|
while (rest.length > limit) {
|
|
56721
57136
|
let cut = limit;
|
|
56722
57137
|
if (mode === "newline") {
|
|
@@ -56735,20 +57150,20 @@ function chunk2(text, limit, mode) {
|
|
|
56735
57150
|
out.push(rest);
|
|
56736
57151
|
return out;
|
|
56737
57152
|
}
|
|
56738
|
-
function escapeMarkdownV2(
|
|
57153
|
+
function escapeMarkdownV2(text2) {
|
|
56739
57154
|
const specialChars = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
|
|
56740
57155
|
const parts = [];
|
|
56741
57156
|
let last = 0;
|
|
56742
57157
|
const codeRe = /(```[\s\S]*?```|`[^`\n]+`)/g;
|
|
56743
57158
|
let m;
|
|
56744
|
-
while ((m = codeRe.exec(
|
|
57159
|
+
while ((m = codeRe.exec(text2)) !== null) {
|
|
56745
57160
|
if (m.index > last)
|
|
56746
|
-
parts.push(
|
|
57161
|
+
parts.push(text2.slice(last, m.index).replace(specialChars, "\\$&"));
|
|
56747
57162
|
parts.push(m[0]);
|
|
56748
57163
|
last = m.index + m[0].length;
|
|
56749
57164
|
}
|
|
56750
|
-
if (last <
|
|
56751
|
-
parts.push(
|
|
57165
|
+
if (last < text2.length)
|
|
57166
|
+
parts.push(text2.slice(last).replace(specialChars, "\\$&"));
|
|
56752
57167
|
return parts.join("");
|
|
56753
57168
|
}
|
|
56754
57169
|
var typingIntervals = new Map;
|
|
@@ -56839,14 +57254,14 @@ function wrapBootCardApi(threadId) {
|
|
|
56839
57254
|
...threadId != null ? { threadId } : {}
|
|
56840
57255
|
});
|
|
56841
57256
|
return {
|
|
56842
|
-
sendMessage: async (cid,
|
|
56843
|
-
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid,
|
|
57257
|
+
sendMessage: async (cid, text2, sendOpts) => {
|
|
57258
|
+
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid, text2, sendOpts), opts(cid));
|
|
56844
57259
|
return sent;
|
|
56845
57260
|
},
|
|
56846
|
-
editMessageText: (cid, mid,
|
|
56847
|
-
editMessageTextStrict: async (cid, mid,
|
|
57261
|
+
editMessageText: (cid, mid, text2, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text2, editOpts), opts(cid)),
|
|
57262
|
+
editMessageTextStrict: async (cid, mid, text2, editOpts) => {
|
|
56848
57263
|
try {
|
|
56849
|
-
await lockedBot.api.editMessageText(cid, mid,
|
|
57264
|
+
await lockedBot.api.editMessageText(cid, mid, text2, editOpts);
|
|
56850
57265
|
return "edited";
|
|
56851
57266
|
} catch (err) {
|
|
56852
57267
|
const desc = err instanceof import_grammy9.GrammyError ? err.description : err instanceof Error ? err.message : String(err);
|
|
@@ -56864,11 +57279,11 @@ function wrapIssuesCardApi(threadId) {
|
|
|
56864
57279
|
...threadId != null ? { threadId } : {}
|
|
56865
57280
|
});
|
|
56866
57281
|
return {
|
|
56867
|
-
sendMessage: async (cid,
|
|
56868
|
-
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid,
|
|
57282
|
+
sendMessage: async (cid, text2, sendOpts) => {
|
|
57283
|
+
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid, text2, sendOpts), opts(cid));
|
|
56869
57284
|
return sent;
|
|
56870
57285
|
},
|
|
56871
|
-
editMessageText: (cid, mid,
|
|
57286
|
+
editMessageText: (cid, mid, text2, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text2, editOpts), opts(cid)),
|
|
56872
57287
|
deleteMessage: (cid, mid) => robustApiCall(() => lockedBot.api.deleteMessage(cid, mid), opts(cid))
|
|
56873
57288
|
};
|
|
56874
57289
|
}
|
|
@@ -56884,6 +57299,19 @@ var STATUS_QUERY_RE = /^\s*status\??\s*$/i;
|
|
|
56884
57299
|
var PERMISSION_REPLY_RE = /^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$/i;
|
|
56885
57300
|
var pendingPermissions = new Map;
|
|
56886
57301
|
var PERMISSION_TTL_MS = 600000;
|
|
57302
|
+
var PERMISSION_NO_REPEAT_ENABLED = process.env.SWITCHROOM_PERMISSION_NO_REPEAT !== "0";
|
|
57303
|
+
var PERMISSION_DUPLICATE_WINDOW_MS = 3600000;
|
|
57304
|
+
var permissionTimeoutSignatures = new Map;
|
|
57305
|
+
function clearPermissionTimeoutSuppression(reason) {
|
|
57306
|
+
if (permissionTimeoutSignatures.size === 0)
|
|
57307
|
+
return;
|
|
57308
|
+
const n = permissionTimeoutSignatures.size;
|
|
57309
|
+
permissionTimeoutSignatures.clear();
|
|
57310
|
+
process.stderr.write(`telegram gateway: permission no-repeat suppression cleared (${n} sig(s)) \u2014 ${reason}
|
|
57311
|
+
`);
|
|
57312
|
+
}
|
|
57313
|
+
var PERMISSION_CARD_ORIGIN_RECOVERY_ENABLED = process.env.SWITCHROOM_PERMISSION_CARD_ORIGIN_RECOVERY !== "0";
|
|
57314
|
+
var PERMISSION_CARD_ORIGIN_MAX_AGE_MS = 1800000;
|
|
56887
57315
|
var pendingAlwaysAllowCorrelations = new Map;
|
|
56888
57316
|
var ALWAYS_ALLOW_CORRELATION_TTL_MS = 30000;
|
|
56889
57317
|
function sweepStaleAlwaysAllowCorrelations(now = Date.now()) {
|
|
@@ -57113,18 +57541,31 @@ var pendingStateReaper = setInterval(() => {
|
|
|
57113
57541
|
}
|
|
57114
57542
|
for (const [k, v] of pendingPermissions) {
|
|
57115
57543
|
if (now - v.startedAt > PERMISSION_TTL_MS) {
|
|
57116
|
-
|
|
57544
|
+
const timeoutMinutes = Math.round(PERMISSION_TTL_MS / 60000);
|
|
57545
|
+
dispatchPermissionVerdict({
|
|
57546
|
+
type: "permission",
|
|
57547
|
+
requestId: k,
|
|
57548
|
+
behavior: "deny",
|
|
57549
|
+
message: timeoutDenyMessage(timeoutMinutes)
|
|
57550
|
+
});
|
|
57117
57551
|
resumeReactionAfterVerdict();
|
|
57118
57552
|
postPermissionResumeMessage({
|
|
57119
57553
|
behavior: "deny",
|
|
57120
57554
|
action: naturalAction(v.tool_name, v.input_preview),
|
|
57121
|
-
timeoutMinutes
|
|
57555
|
+
timeoutMinutes
|
|
57122
57556
|
});
|
|
57123
|
-
|
|
57557
|
+
if (PERMISSION_NO_REPEAT_ENABLED) {
|
|
57558
|
+
permissionTimeoutSignatures.set(permissionSignature(v.tool_name, v.input_preview), now);
|
|
57559
|
+
}
|
|
57560
|
+
process.stderr.write(`telegram gateway: permission TTL expired \u2014 auto-deny request=${k} tool=${v.tool_name} (no operator response in ${timeoutMinutes}m)
|
|
57124
57561
|
`);
|
|
57125
57562
|
pendingPermissions.delete(k);
|
|
57126
57563
|
}
|
|
57127
57564
|
}
|
|
57565
|
+
for (const [sig, at] of permissionTimeoutSignatures) {
|
|
57566
|
+
if (now - at > PERMISSION_DUPLICATE_WINDOW_MS)
|
|
57567
|
+
permissionTimeoutSignatures.delete(sig);
|
|
57568
|
+
}
|
|
57128
57569
|
for (const [k, v] of vaultPassphraseCache) {
|
|
57129
57570
|
if (now > v.expiresAt)
|
|
57130
57571
|
vaultPassphraseCache.delete(k);
|
|
@@ -57167,8 +57608,8 @@ var pendingStateReaper = setInterval(() => {
|
|
|
57167
57608
|
}
|
|
57168
57609
|
}, 60000);
|
|
57169
57610
|
pendingStateReaper.unref();
|
|
57170
|
-
function looksLikeAuthCode(
|
|
57171
|
-
const trimmed =
|
|
57611
|
+
function looksLikeAuthCode(text2) {
|
|
57612
|
+
const trimmed = text2.trim();
|
|
57172
57613
|
if (!trimmed || /\s/.test(trimmed))
|
|
57173
57614
|
return false;
|
|
57174
57615
|
if (trimmed.startsWith("session_"))
|
|
@@ -57266,10 +57707,10 @@ function emitGatewayOperatorEvent(event) {
|
|
|
57266
57707
|
}
|
|
57267
57708
|
}
|
|
57268
57709
|
function postLegacyBanner(chatId, threadId, ackMessageId, ageSec, site) {
|
|
57269
|
-
const
|
|
57710
|
+
const text2 = `\uD83C\uDF9B\uFE0F Switchroom restarted \u2014 ready. (took ~${ageSec}s)`;
|
|
57270
57711
|
process.stderr.write(`telegram gateway: ${site}: posting legacy banner chat_id=${chatId}
|
|
57271
57712
|
`);
|
|
57272
|
-
retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chatId,
|
|
57713
|
+
retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chatId, text2, {
|
|
57273
57714
|
parse_mode: "HTML",
|
|
57274
57715
|
link_preview_options: { is_disabled: true },
|
|
57275
57716
|
...tid != null ? { message_thread_id: tid } : {},
|
|
@@ -57281,7 +57722,7 @@ function postLegacyBanner(chatId, threadId, ackMessageId, ageSec, site) {
|
|
|
57281
57722
|
chat_id: chatId,
|
|
57282
57723
|
thread_id: threadId ?? null,
|
|
57283
57724
|
message_ids: [sent.message_id],
|
|
57284
|
-
texts: [
|
|
57725
|
+
texts: [text2],
|
|
57285
57726
|
attachment_kinds: []
|
|
57286
57727
|
});
|
|
57287
57728
|
} catch {}
|
|
@@ -57414,22 +57855,22 @@ startTimer({
|
|
|
57414
57855
|
endTurn(ctx.key);
|
|
57415
57856
|
return;
|
|
57416
57857
|
}
|
|
57417
|
-
let
|
|
57858
|
+
let text2 = null;
|
|
57418
57859
|
const upd = inFlightUpdate;
|
|
57419
57860
|
if (upd != null) {
|
|
57420
57861
|
try {
|
|
57421
57862
|
const st = await hostdGetStatusOnce(getMyAgentName(), upd.requestId);
|
|
57422
57863
|
if (st !== "not-configured" && st !== "unavailable") {
|
|
57423
|
-
|
|
57864
|
+
text2 = formatUpdateStatusLine(st, upd.startedAt, Date.now());
|
|
57424
57865
|
}
|
|
57425
57866
|
} catch {}
|
|
57426
57867
|
}
|
|
57427
|
-
if (
|
|
57868
|
+
if (text2 == null) {
|
|
57428
57869
|
const blockedOnApproval = activeStatusReactions.get(statusKey(ctx.chatId, ctx.threadId))?.isAwaiting() ?? false;
|
|
57429
|
-
|
|
57870
|
+
text2 = formatFrameworkFallbackText(ctx.fallbackKind, ctx.silenceMs, ctx.inFlightTools, blockedOnApproval);
|
|
57430
57871
|
}
|
|
57431
57872
|
try {
|
|
57432
|
-
await robustApiCall(() => bot.api.sendMessage(ctx.chatId,
|
|
57873
|
+
await robustApiCall(() => bot.api.sendMessage(ctx.chatId, text2, {
|
|
57433
57874
|
...ctx.threadId != null ? { message_thread_id: ctx.threadId } : {},
|
|
57434
57875
|
disable_notification: false
|
|
57435
57876
|
}), { chat_id: ctx.chatId, ...ctx.threadId != null ? { threadId: ctx.threadId } : {} });
|
|
@@ -57915,12 +58356,26 @@ var ipcServer = createIpcServer({
|
|
|
57915
58356
|
if (hit) {
|
|
57916
58357
|
dispatchPermissionVerdict({ type: "permission", requestId, behavior: "allow" });
|
|
57917
58358
|
process.stderr.write(`telegram gateway: scoped-approval auto-allow tool=${toolName} rule="${hit}" request=${requestId} (time-boxed window)
|
|
58359
|
+
`);
|
|
58360
|
+
return;
|
|
58361
|
+
}
|
|
58362
|
+
}
|
|
58363
|
+
if (PERMISSION_NO_REPEAT_ENABLED) {
|
|
58364
|
+
const sig = permissionSignature(toolName, inputPreview);
|
|
58365
|
+
if (isRecentTimeoutDuplicate(permissionTimeoutSignatures, sig, Date.now(), PERMISSION_DUPLICATE_WINDOW_MS)) {
|
|
58366
|
+
dispatchPermissionVerdict({
|
|
58367
|
+
type: "permission",
|
|
58368
|
+
requestId,
|
|
58369
|
+
behavior: "deny",
|
|
58370
|
+
message: duplicateDenyMessage
|
|
58371
|
+
});
|
|
58372
|
+
process.stderr.write(`telegram gateway: permission no-repeat short-circuit \u2014 duplicate of a ` + `timed-out request tool=${toolName} request=${requestId} (no card posted)
|
|
57918
58373
|
`);
|
|
57919
58374
|
return;
|
|
57920
58375
|
}
|
|
57921
58376
|
}
|
|
57922
58377
|
pendingPermissions.set(requestId, { tool_name: toolName, description, input_preview: inputPreview, startedAt: Date.now() });
|
|
57923
|
-
const
|
|
58378
|
+
const text2 = formatPermissionCardBody({
|
|
57924
58379
|
toolName,
|
|
57925
58380
|
inputPreview,
|
|
57926
58381
|
description,
|
|
@@ -57931,7 +58386,7 @@ var ipcServer = createIpcServer({
|
|
|
57931
58386
|
const activeTurn = currentTurn;
|
|
57932
58387
|
const targets = resolvePermissionCardTargets();
|
|
57933
58388
|
for (const { chatId, threadId } of targets) {
|
|
57934
|
-
retryWithThreadFallback(robustApiCall, (tid) => bot.api.sendMessage(chatId,
|
|
58389
|
+
retryWithThreadFallback(robustApiCall, (tid) => bot.api.sendMessage(chatId, text2, {
|
|
57935
58390
|
parse_mode: "HTML",
|
|
57936
58391
|
reply_markup: keyboard,
|
|
57937
58392
|
...tid != null ? { message_thread_id: tid } : {}
|
|
@@ -58365,7 +58820,8 @@ var ALLOWED_TOOLS = new Set([
|
|
|
58365
58820
|
"vault_request_access",
|
|
58366
58821
|
"request_secret",
|
|
58367
58822
|
"linear_agent_activity",
|
|
58368
|
-
"linear_create_issue"
|
|
58823
|
+
"linear_create_issue",
|
|
58824
|
+
"linear_agent_setup"
|
|
58369
58825
|
]);
|
|
58370
58826
|
async function executeToolCall(tool, args) {
|
|
58371
58827
|
if (!ALLOWED_TOOLS.has(tool)) {
|
|
@@ -58414,6 +58870,8 @@ async function executeToolCall(tool, args) {
|
|
|
58414
58870
|
return executeLinearAgentActivity(args);
|
|
58415
58871
|
case "linear_create_issue":
|
|
58416
58872
|
return executeLinearCreateIssue(args);
|
|
58873
|
+
case "linear_agent_setup":
|
|
58874
|
+
return executeLinearAgentSetup(args);
|
|
58417
58875
|
default:
|
|
58418
58876
|
throw new Error(`unknown tool: ${tool}`);
|
|
58419
58877
|
}
|
|
@@ -58444,11 +58902,45 @@ async function executeSendChecklist(args) {
|
|
|
58444
58902
|
`);
|
|
58445
58903
|
return { content: [{ type: "text", text: `checklist sent (id: ${sent.message_id})` }] };
|
|
58446
58904
|
}
|
|
58905
|
+
var linearAuthAlertLast = new Map;
|
|
58906
|
+
var LINEAR_AUTH_ALERT_COOLDOWN_MS = 21600000;
|
|
58907
|
+
function notifyLinearAuthDead(info) {
|
|
58908
|
+
if (process.env.SWITCHROOM_LINEAR_AUTH_ALERT === "0")
|
|
58909
|
+
return;
|
|
58910
|
+
const key = `${info.agent}:${info.reason}`;
|
|
58911
|
+
const now = Date.now();
|
|
58912
|
+
const last = linearAuthAlertLast.get(key);
|
|
58913
|
+
if (last != null && now - last < LINEAR_AUTH_ALERT_COOLDOWN_MS)
|
|
58914
|
+
return;
|
|
58915
|
+
(async () => {
|
|
58916
|
+
try {
|
|
58917
|
+
const chatId = loadAccess().allowFrom[0];
|
|
58918
|
+
if (!chatId)
|
|
58919
|
+
return;
|
|
58920
|
+
const threadId = topicForRecipient({
|
|
58921
|
+
recipientChatId: chatId,
|
|
58922
|
+
resolvedTopic: resolveAgentOutboundTopic({ kind: "linear-auth" }) ?? chatThreadMap.get(chatId),
|
|
58923
|
+
supergroupChatId: resolveAgentSupergroupChatId()
|
|
58924
|
+
});
|
|
58925
|
+
const text2 = buildLinearAuthDeadMessage(info.agent, info.reason);
|
|
58926
|
+
await swallowingApiCall(() => bot.api.sendMessage(chatId, text2, {
|
|
58927
|
+
parse_mode: "HTML",
|
|
58928
|
+
...threadId != null ? { message_thread_id: threadId } : {}
|
|
58929
|
+
}), { chat_id: chatId, verb: "linearAuthDead" });
|
|
58930
|
+
linearAuthAlertLast.set(key, now);
|
|
58931
|
+
process.stderr.write(`telegram gateway: linear auth-dead alert sent agent=${info.agent} reason=${info.reason}
|
|
58932
|
+
`);
|
|
58933
|
+
} catch {}
|
|
58934
|
+
})();
|
|
58935
|
+
}
|
|
58447
58936
|
async function executeLinearAgentActivity(args) {
|
|
58448
|
-
return emitLinearAgentActivity(args);
|
|
58937
|
+
return emitLinearAgentActivity(args, { onAuthUnrecoverable: notifyLinearAuthDead });
|
|
58449
58938
|
}
|
|
58450
58939
|
async function executeLinearCreateIssue(args) {
|
|
58451
|
-
return createLinearIssue(args);
|
|
58940
|
+
return createLinearIssue(args, { onAuthUnrecoverable: notifyLinearAuthDead });
|
|
58941
|
+
}
|
|
58942
|
+
async function executeLinearAgentSetup(args) {
|
|
58943
|
+
return runLinearAgentSetup(args);
|
|
58452
58944
|
}
|
|
58453
58945
|
async function executeUpdateChecklist(args) {
|
|
58454
58946
|
const chat_id = args.chat_id;
|
|
@@ -58465,9 +58957,9 @@ async function executeUpdateChecklist(args) {
|
|
|
58465
58957
|
`);
|
|
58466
58958
|
return { content: [{ type: "text", text: `checklist updated (id: ${message_id})` }] };
|
|
58467
58959
|
}
|
|
58468
|
-
function redactOutboundText(
|
|
58469
|
-
const masked = redact2(
|
|
58470
|
-
if (masked !==
|
|
58960
|
+
function redactOutboundText(text2, site) {
|
|
58961
|
+
const masked = redact2(text2);
|
|
58962
|
+
if (masked !== text2) {
|
|
58471
58963
|
process.stderr.write(`telegram gateway: outbound secret masked site=${site}
|
|
58472
58964
|
`);
|
|
58473
58965
|
}
|
|
@@ -58481,12 +58973,12 @@ async function executeReply(args) {
|
|
|
58481
58973
|
const rawText = args.text;
|
|
58482
58974
|
if (rawText == null || rawText === "")
|
|
58483
58975
|
throw new Error("reply: text is required and cannot be empty");
|
|
58484
|
-
let
|
|
58485
|
-
|
|
58976
|
+
let text2 = repairEscapedWhitespace(rawText);
|
|
58977
|
+
text2 = redactOutboundText(text2, "reply");
|
|
58486
58978
|
{
|
|
58487
|
-
const scrub = scrubVoice(
|
|
58979
|
+
const scrub = scrubVoice(text2);
|
|
58488
58980
|
if (scrub.replaced > 0) {
|
|
58489
|
-
|
|
58981
|
+
text2 = scrub.scrubbed;
|
|
58490
58982
|
emitRuntimeMetric({
|
|
58491
58983
|
kind: "voice_scrub_applied",
|
|
58492
58984
|
chatKey: statusKey(chat_id, args.message_thread_id != null ? Number(args.message_thread_id) : undefined),
|
|
@@ -58495,11 +58987,11 @@ async function executeReply(args) {
|
|
|
58495
58987
|
});
|
|
58496
58988
|
}
|
|
58497
58989
|
}
|
|
58498
|
-
process.stderr.write(`telegram channel: reply: invoked chatId=${chat_id} charCount=${
|
|
58990
|
+
process.stderr.write(`telegram channel: reply: invoked chatId=${chat_id} charCount=${text2.length} preview=${JSON.stringify(text2.slice(0, 80))}
|
|
58499
58991
|
`);
|
|
58500
58992
|
{
|
|
58501
58993
|
const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
58502
|
-
const dup = outboundDedup.check(chat_id, replyThreadId,
|
|
58994
|
+
const dup = outboundDedup.check(chat_id, replyThreadId, text2, Date.now(), currentTurn?.registryKey ?? null);
|
|
58503
58995
|
if (dup != null) {
|
|
58504
58996
|
process.stderr.write(`telegram gateway: reply: deduped (#546) chatId=${chat_id} ageMs=${dup.ageMs} preview=${JSON.stringify(dup.preview)}
|
|
58505
58997
|
`);
|
|
@@ -58543,13 +59035,13 @@ async function executeReply(args) {
|
|
|
58543
59035
|
}
|
|
58544
59036
|
const tg = access.telegraph;
|
|
58545
59037
|
const tgThreshold = tg?.threshold ?? 3000;
|
|
58546
|
-
if (tg?.enabled && files.length === 0 &&
|
|
59038
|
+
if (tg?.enabled && files.length === 0 && text2.length > tgThreshold) {
|
|
58547
59039
|
const agentSlug = process.env.SWITCHROOM_AGENT_NAME ?? "switchroom-agent";
|
|
58548
59040
|
const shortName = tg.short_name ?? agentSlug;
|
|
58549
|
-
const url = await publishToTelegraph(
|
|
59041
|
+
const url = await publishToTelegraph(text2, shortName, tg.author_name);
|
|
58550
59042
|
if (url != null) {
|
|
58551
|
-
const title = deriveTelegraphTitle(
|
|
58552
|
-
|
|
59043
|
+
const title = deriveTelegraphTitle(text2);
|
|
59044
|
+
text2 = `<b>${title.replace(/[<>&]/g, (c) => c === "<" ? "<" : c === ">" ? ">" : "&")}</b>
|
|
58553
59045
|
${url}`;
|
|
58554
59046
|
}
|
|
58555
59047
|
}
|
|
@@ -58557,13 +59049,13 @@ ${url}`;
|
|
|
58557
59049
|
let effectiveText;
|
|
58558
59050
|
if (format === "html") {
|
|
58559
59051
|
parseMode = "HTML";
|
|
58560
|
-
effectiveText = markdownToHtml(
|
|
59052
|
+
effectiveText = markdownToHtml(text2);
|
|
58561
59053
|
} else if (format === "markdownv2") {
|
|
58562
59054
|
parseMode = "MarkdownV2";
|
|
58563
|
-
effectiveText = escapeMarkdownV2(
|
|
59055
|
+
effectiveText = escapeMarkdownV2(text2);
|
|
58564
59056
|
} else {
|
|
58565
59057
|
parseMode = undefined;
|
|
58566
|
-
effectiveText =
|
|
59058
|
+
effectiveText = text2;
|
|
58567
59059
|
}
|
|
58568
59060
|
assertAllowedChat(chat_id);
|
|
58569
59061
|
let replyRoutedOriginTurn = null;
|
|
@@ -58920,7 +59412,7 @@ ${url}`;
|
|
|
58920
59412
|
process.stderr.write(`telegram channel: reply: finalized chatId=${chat_id} messageIds=[${sentIds.join(",")}] chunks=${chunks.length}
|
|
58921
59413
|
`);
|
|
58922
59414
|
if (sentIds.length > 0) {
|
|
58923
|
-
outboundDedup.record(chat_id, threadId,
|
|
59415
|
+
outboundDedup.record(chat_id, threadId, text2, Date.now(), currentTurn?.registryKey ?? null);
|
|
58924
59416
|
}
|
|
58925
59417
|
return { content: [{ type: "text", text: result }] };
|
|
58926
59418
|
}
|
|
@@ -59105,12 +59597,12 @@ async function executeProgressUpdate(args) {
|
|
|
59105
59597
|
if (!args.text)
|
|
59106
59598
|
throw new Error("progress_update: text is required");
|
|
59107
59599
|
const chat_id = args.chat_id;
|
|
59108
|
-
let
|
|
59600
|
+
let text2 = args.text;
|
|
59109
59601
|
const threadId = resolveThreadId(chat_id, args.message_thread_id);
|
|
59110
59602
|
const key = statusKey(chat_id, threadId);
|
|
59111
59603
|
assertAllowedChat(chat_id);
|
|
59112
|
-
if (
|
|
59113
|
-
|
|
59604
|
+
if (text2.length > 300) {
|
|
59605
|
+
text2 = text2.slice(0, 299) + "\u2026";
|
|
59114
59606
|
}
|
|
59115
59607
|
const now = Date.now();
|
|
59116
59608
|
const lastSent = progressUpdateLastSent.get(key);
|
|
@@ -59152,7 +59644,7 @@ async function executeProgressUpdate(args) {
|
|
|
59152
59644
|
toolUseIdHint
|
|
59153
59645
|
});
|
|
59154
59646
|
if (subAgent != null && progressDriver != null) {
|
|
59155
|
-
const cardText =
|
|
59647
|
+
const cardText = text2.length > 200 ? text2.slice(0, 199) + "\u2026" : text2;
|
|
59156
59648
|
const result = progressDriver.recordSubAgentNarrative({
|
|
59157
59649
|
chatId: chat_id,
|
|
59158
59650
|
threadId: threadId != null ? String(threadId) : undefined,
|
|
@@ -59177,7 +59669,7 @@ async function executeProgressUpdate(args) {
|
|
|
59177
59669
|
const access = loadAccess();
|
|
59178
59670
|
const configParseMode = access.parseMode ?? "html";
|
|
59179
59671
|
const parseMode = configParseMode === "html" ? "HTML" : undefined;
|
|
59180
|
-
const effectiveText = configParseMode === "html" ? markdownToHtml(
|
|
59672
|
+
const effectiveText = configParseMode === "html" ? markdownToHtml(text2) : text2;
|
|
59181
59673
|
const sendOpts = {
|
|
59182
59674
|
...parseMode ? { parse_mode: parseMode } : {},
|
|
59183
59675
|
...threadId != null ? { message_thread_id: threadId } : {}
|
|
@@ -59188,7 +59680,7 @@ async function executeProgressUpdate(args) {
|
|
|
59188
59680
|
chat_id,
|
|
59189
59681
|
thread_id: threadId ?? null,
|
|
59190
59682
|
message_ids: [sent.message_id],
|
|
59191
|
-
texts: [
|
|
59683
|
+
texts: [text2]
|
|
59192
59684
|
});
|
|
59193
59685
|
}
|
|
59194
59686
|
progressUpdateLastSent.set(key, now);
|
|
@@ -59355,7 +59847,7 @@ async function executeSendGif(rawArgs) {
|
|
|
59355
59847
|
}]
|
|
59356
59848
|
};
|
|
59357
59849
|
}
|
|
59358
|
-
async function publishToTelegraph(
|
|
59850
|
+
async function publishToTelegraph(text2, shortName, authorName) {
|
|
59359
59851
|
const accountPath = join35(STATE_DIR, "telegraph-account.json");
|
|
59360
59852
|
let account = null;
|
|
59361
59853
|
try {
|
|
@@ -59386,8 +59878,8 @@ async function publishToTelegraph(text, shortName, authorName) {
|
|
|
59386
59878
|
`);
|
|
59387
59879
|
}
|
|
59388
59880
|
}
|
|
59389
|
-
const title = deriveTelegraphTitle(
|
|
59390
|
-
const content = markdownToTelegraphNodes(
|
|
59881
|
+
const title = deriveTelegraphTitle(text2);
|
|
59882
|
+
const content = markdownToTelegraphNodes(text2);
|
|
59391
59883
|
const page = await createTelegraphPage({
|
|
59392
59884
|
accessToken: account.accessToken,
|
|
59393
59885
|
title,
|
|
@@ -59399,7 +59891,7 @@ async function publishToTelegraph(text, shortName, authorName) {
|
|
|
59399
59891
|
`);
|
|
59400
59892
|
return null;
|
|
59401
59893
|
}
|
|
59402
|
-
process.stderr.write(`telegram gateway: telegraph published url=${page.value.url} title=${JSON.stringify(title)} chars=${
|
|
59894
|
+
process.stderr.write(`telegram gateway: telegraph published url=${page.value.url} title=${JSON.stringify(title)} chars=${text2.length}
|
|
59403
59895
|
`);
|
|
59404
59896
|
return page.value.url;
|
|
59405
59897
|
}
|
|
@@ -59461,11 +59953,11 @@ async function executeVaultRequestSave(args) {
|
|
|
59461
59953
|
};
|
|
59462
59954
|
pendingVaultRequestSaves.set(stageId, pending2);
|
|
59463
59955
|
sweepPendingVaultRequestSaves();
|
|
59464
|
-
const
|
|
59956
|
+
const text2 = renderVaultRequestSaveCard(pending2, agentSlug);
|
|
59465
59957
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
59466
59958
|
if (threadId != null)
|
|
59467
59959
|
pending2.threadId = threadId;
|
|
59468
|
-
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id,
|
|
59960
|
+
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text2, {
|
|
59469
59961
|
parse_mode: "HTML",
|
|
59470
59962
|
reply_markup: buildVaultRequestSaveKeyboard(stageId),
|
|
59471
59963
|
...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
|
|
@@ -59537,11 +60029,11 @@ async function executeRequestSecret(args) {
|
|
|
59537
60029
|
const pending2 = { agent: agentSlug, chat_id, key, reason, staged_at: Date.now() };
|
|
59538
60030
|
pendingSecretRequests.set(stageId, pending2);
|
|
59539
60031
|
sweepSecretRequests();
|
|
59540
|
-
const
|
|
60032
|
+
const text2 = renderSecretRequestCard(pending2);
|
|
59541
60033
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
59542
60034
|
if (threadId != null)
|
|
59543
60035
|
pending2.threadId = threadId;
|
|
59544
|
-
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id,
|
|
60036
|
+
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text2, {
|
|
59545
60037
|
parse_mode: "HTML",
|
|
59546
60038
|
reply_markup: buildSecretRequestKeyboard(stageId),
|
|
59547
60039
|
...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
|
|
@@ -59782,11 +60274,11 @@ async function executeVaultRequestAccess(args) {
|
|
|
59782
60274
|
};
|
|
59783
60275
|
pendingVaultRequestAccesses.set(stageId, pending2);
|
|
59784
60276
|
sweepPendingVaultRequestAccesses();
|
|
59785
|
-
const
|
|
60277
|
+
const text2 = renderVaultRequestAccessCard(pending2);
|
|
59786
60278
|
const threadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
59787
60279
|
if (threadId != null)
|
|
59788
60280
|
pending2.threadId = threadId;
|
|
59789
|
-
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id,
|
|
60281
|
+
const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chat_id, text2, {
|
|
59790
60282
|
parse_mode: "HTML",
|
|
59791
60283
|
reply_markup: buildVaultRequestAccessKeyboard(stageId),
|
|
59792
60284
|
...tid != null && Number.isFinite(tid) ? { message_thread_id: tid } : {}
|
|
@@ -60344,10 +60836,10 @@ function handleSessionEvent(ev) {
|
|
|
60344
60836
|
isPrivateChat: turn.isDm,
|
|
60345
60837
|
threadId: turn.sessionThreadId,
|
|
60346
60838
|
...ANSWER_LANE.usesDraftTransport ? { sendMessageDraft: sendMessageDraftFn, minInitialChars: ANSWER_LANE.minInitialChars } : { minInitialChars: ANSWER_LANE.minInitialChars },
|
|
60347
|
-
sendMessage: async (chatId,
|
|
60839
|
+
sendMessage: async (chatId, text2, params) => {
|
|
60348
60840
|
const tid = params?.message_thread_id;
|
|
60349
60841
|
const silent = params?.purpose !== "materialize";
|
|
60350
|
-
const msg = await robustApiCall(() => bot.api.sendMessage(chatId,
|
|
60842
|
+
const msg = await robustApiCall(() => bot.api.sendMessage(chatId, text2, {
|
|
60351
60843
|
parse_mode: params?.parse_mode,
|
|
60352
60844
|
disable_notification: silent,
|
|
60353
60845
|
...tid != null ? { message_thread_id: tid } : {},
|
|
@@ -60360,9 +60852,9 @@ function handleSessionEvent(ev) {
|
|
|
60360
60852
|
});
|
|
60361
60853
|
return { message_id: msg.message_id };
|
|
60362
60854
|
},
|
|
60363
|
-
editMessageText: (chatId, messageId,
|
|
60855
|
+
editMessageText: (chatId, messageId, text2, params) => {
|
|
60364
60856
|
const tid = params?.message_thread_id;
|
|
60365
|
-
return robustApiCall(() => bot.api.editMessageText(chatId, messageId,
|
|
60857
|
+
return robustApiCall(() => bot.api.editMessageText(chatId, messageId, text2, {
|
|
60366
60858
|
parse_mode: params?.parse_mode,
|
|
60367
60859
|
...tid != null ? { message_thread_id: tid } : {},
|
|
60368
60860
|
...params?.link_preview_options != null ? { link_preview_options: params.link_preview_options } : {}
|
|
@@ -60386,13 +60878,13 @@ function handleSessionEvent(ev) {
|
|
|
60386
60878
|
}
|
|
60387
60879
|
}
|
|
60388
60880
|
},
|
|
60389
|
-
checkDedup: (
|
|
60390
|
-
return outboundDedup.check(turn.sessionChatId, turn.sessionThreadId,
|
|
60881
|
+
checkDedup: (text2) => {
|
|
60882
|
+
return outboundDedup.check(turn.sessionChatId, turn.sessionThreadId, text2, Date.now(), turn.registryKey ?? null) != null;
|
|
60391
60883
|
},
|
|
60392
|
-
recordDedup: (
|
|
60393
|
-
outboundDedup.record(turn.sessionChatId, turn.sessionThreadId,
|
|
60884
|
+
recordDedup: (text2) => {
|
|
60885
|
+
outboundDedup.record(turn.sessionChatId, turn.sessionThreadId, text2, Date.now(), turn.registryKey ?? null);
|
|
60394
60886
|
},
|
|
60395
|
-
recordOutbound: ({ messageId, text }) => {
|
|
60887
|
+
recordOutbound: ({ messageId, text: text2 }) => {
|
|
60396
60888
|
if (!HISTORY_ENABLED)
|
|
60397
60889
|
return;
|
|
60398
60890
|
try {
|
|
@@ -60400,7 +60892,7 @@ function handleSessionEvent(ev) {
|
|
|
60400
60892
|
chat_id: turn.sessionChatId,
|
|
60401
60893
|
thread_id: turn.sessionThreadId ?? null,
|
|
60402
60894
|
message_ids: [messageId],
|
|
60403
|
-
texts: [
|
|
60895
|
+
texts: [text2]
|
|
60404
60896
|
});
|
|
60405
60897
|
} catch {}
|
|
60406
60898
|
}
|
|
@@ -60791,7 +61283,7 @@ function handleSessionEvent(ev) {
|
|
|
60791
61283
|
}
|
|
60792
61284
|
}
|
|
60793
61285
|
}
|
|
60794
|
-
function handlePtyPartial(
|
|
61286
|
+
function handlePtyPartial(text2) {
|
|
60795
61287
|
const turn = currentTurn;
|
|
60796
61288
|
const state4 = {
|
|
60797
61289
|
currentSessionChatId: turn?.sessionChatId ?? null,
|
|
@@ -60802,7 +61294,7 @@ function handlePtyPartial(text) {
|
|
|
60802
61294
|
suppressPtyPreview,
|
|
60803
61295
|
lastPtyPreviewByChat
|
|
60804
61296
|
};
|
|
60805
|
-
handlePtyPartialPure(
|
|
61297
|
+
handlePtyPartialPure(text2, state4, {
|
|
60806
61298
|
bot,
|
|
60807
61299
|
retry: robustApiCall,
|
|
60808
61300
|
renderText: markdownToHtml,
|
|
@@ -60914,10 +61406,10 @@ function gate(ctx) {
|
|
|
60914
61406
|
}
|
|
60915
61407
|
function isMentioned(ctx, extraPatterns) {
|
|
60916
61408
|
const entities = ctx.message?.entities ?? ctx.message?.caption_entities ?? [];
|
|
60917
|
-
const
|
|
61409
|
+
const text2 = ctx.message?.text ?? ctx.message?.caption ?? "";
|
|
60918
61410
|
for (const e of entities) {
|
|
60919
61411
|
if (e.type === "mention") {
|
|
60920
|
-
const mentioned =
|
|
61412
|
+
const mentioned = text2.slice(e.offset, e.offset + e.length);
|
|
60921
61413
|
if (mentioned.toLowerCase() === `@${botUsername}`.toLowerCase())
|
|
60922
61414
|
return true;
|
|
60923
61415
|
}
|
|
@@ -60928,7 +61420,7 @@ function isMentioned(ctx, extraPatterns) {
|
|
|
60928
61420
|
return true;
|
|
60929
61421
|
for (const pat of extraPatterns ?? []) {
|
|
60930
61422
|
try {
|
|
60931
|
-
if (new RegExp(pat, "i").test(
|
|
61423
|
+
if (new RegExp(pat, "i").test(text2))
|
|
60932
61424
|
return true;
|
|
60933
61425
|
} catch {}
|
|
60934
61426
|
}
|
|
@@ -60957,14 +61449,14 @@ function isAuthorizedSender(ctx) {
|
|
|
60957
61449
|
function safeName(s) {
|
|
60958
61450
|
return s?.replace(/[<>\[\]\r\n;]/g, "_");
|
|
60959
61451
|
}
|
|
60960
|
-
async function handleInboundCoalesced(ctx,
|
|
60961
|
-
if (parseInterruptMarker(
|
|
60962
|
-
return handleInbound(ctx,
|
|
61452
|
+
async function handleInboundCoalesced(ctx, text2, downloadImage, attachment) {
|
|
61453
|
+
if (parseInterruptMarker(text2).isInterrupt) {
|
|
61454
|
+
return handleInbound(ctx, text2, downloadImage, attachment);
|
|
60963
61455
|
}
|
|
60964
61456
|
const hasAttachment = downloadImage != null || attachment != null;
|
|
60965
61457
|
const maxAttachments = coalesceMaxAttachments();
|
|
60966
61458
|
if (hasAttachment && ctx.message?.media_group_id != null && maxAttachments <= 1) {
|
|
60967
|
-
return handleInbound(ctx,
|
|
61459
|
+
return handleInbound(ctx, text2, downloadImage, attachment);
|
|
60968
61460
|
}
|
|
60969
61461
|
const from = ctx.from;
|
|
60970
61462
|
if (!from)
|
|
@@ -60972,14 +61464,14 @@ async function handleInboundCoalesced(ctx, text, downloadImage, attachment) {
|
|
|
60972
61464
|
if (hasAttachment) {
|
|
60973
61465
|
const probeKey = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
60974
61466
|
if ((bufferedAttachmentKeys.get(probeKey) ?? 0) >= maxAttachments) {
|
|
60975
|
-
return handleInbound(ctx,
|
|
61467
|
+
return handleInbound(ctx, text2, downloadImage, attachment);
|
|
60976
61468
|
}
|
|
60977
61469
|
}
|
|
60978
61470
|
maybeEarlyAckReaction(ctx, from);
|
|
60979
61471
|
const key = inboundCoalesceKey(String(ctx.chat.id), ctx.message?.message_thread_id, String(from.id));
|
|
60980
|
-
const result = inboundCoalescer.enqueue(key, { text, ctx, downloadImage, attachment });
|
|
61472
|
+
const result = inboundCoalescer.enqueue(key, { text: text2, ctx, downloadImage, attachment });
|
|
60981
61473
|
if (result.bypass)
|
|
60982
|
-
return handleInbound(ctx,
|
|
61474
|
+
return handleInbound(ctx, text2, downloadImage, attachment);
|
|
60983
61475
|
if (hasAttachment)
|
|
60984
61476
|
bufferedAttachmentKeys.set(key, (bufferedAttachmentKeys.get(key) ?? 0) + 1);
|
|
60985
61477
|
}
|
|
@@ -61002,7 +61494,7 @@ function maybeEarlyAckReaction(ctx, from) {
|
|
|
61002
61494
|
]).catch(() => {});
|
|
61003
61495
|
bot.api.sendChatAction(chatId, "typing").catch(() => {});
|
|
61004
61496
|
}
|
|
61005
|
-
async function handleInbound(ctx,
|
|
61497
|
+
async function handleInbound(ctx, text2, downloadImage, attachment, extraAttachments) {
|
|
61006
61498
|
const isTopicMessage = ctx.message?.is_topic_message ?? false;
|
|
61007
61499
|
const messageThreadId = ctx.message?.message_thread_id;
|
|
61008
61500
|
if (TOPIC_ID != null) {
|
|
@@ -61021,6 +61513,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61021
61513
|
/telegram:access pair ${result.code}`);
|
|
61022
61514
|
return;
|
|
61023
61515
|
}
|
|
61516
|
+
clearPermissionTimeoutSuppression("operator inbound");
|
|
61024
61517
|
const inboundReceivedAt = Date.now();
|
|
61025
61518
|
const _shadowKey = statusKey(ctx.chat?.id != null ? String(ctx.chat.id) : "0", ctx.message?.message_thread_id);
|
|
61026
61519
|
const machineInTurnAtReceipt = isDeliveryCutoverEnabled() ? isMachineInTurn() : null;
|
|
@@ -61042,7 +61535,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61042
61535
|
if (messageThreadId != null)
|
|
61043
61536
|
chatThreadMap.set(chat_id, messageThreadId);
|
|
61044
61537
|
try {
|
|
61045
|
-
const classification = classifyInbound(
|
|
61538
|
+
const classification = classifyInbound(text2);
|
|
61046
61539
|
if (classification.isStatusQuery) {
|
|
61047
61540
|
const priorKey = statusKey(chat_id, messageThreadId);
|
|
61048
61541
|
const priorTurnStartedAt2 = activeTurnStartedAt.get(priorKey);
|
|
@@ -61052,7 +61545,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61052
61545
|
chat_id,
|
|
61053
61546
|
message_id: msgId ?? null,
|
|
61054
61547
|
thread_id: messageThreadId ?? null,
|
|
61055
|
-
text_length:
|
|
61548
|
+
text_length: text2.length,
|
|
61056
61549
|
prior_turn_in_flight: priorTurnInFlight,
|
|
61057
61550
|
seconds_since_turn_start: priorTurnStartedAt2 != null ? Math.round((inboundReceivedAt - priorTurnStartedAt2) / 1000) : null
|
|
61058
61551
|
});
|
|
@@ -61061,7 +61554,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61061
61554
|
process.stderr.write(`telegram gateway: inbound classifier error: ${err.message}
|
|
61062
61555
|
`);
|
|
61063
61556
|
}
|
|
61064
|
-
const interrupt = parseInterruptMarker(
|
|
61557
|
+
const interrupt = parseInterruptMarker(text2);
|
|
61065
61558
|
let deferInterrupt = false;
|
|
61066
61559
|
if (interrupt.isInterrupt) {
|
|
61067
61560
|
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
@@ -61102,9 +61595,9 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61102
61595
|
});
|
|
61103
61596
|
return;
|
|
61104
61597
|
}
|
|
61105
|
-
|
|
61598
|
+
text2 = interrupt.body;
|
|
61106
61599
|
}
|
|
61107
|
-
if (STATUS_QUERY_RE.test(
|
|
61600
|
+
if (STATUS_QUERY_RE.test(text2)) {
|
|
61108
61601
|
try {
|
|
61109
61602
|
const threadKey = messageThreadId != null ? String(messageThreadId) : undefined;
|
|
61110
61603
|
const cardState = progressDriver?.peek(chat_id, threadKey);
|
|
@@ -61120,7 +61613,7 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61120
61613
|
`);
|
|
61121
61614
|
}
|
|
61122
61615
|
}
|
|
61123
|
-
const permMatch = PERMISSION_REPLY_RE.exec(
|
|
61616
|
+
const permMatch = PERMISSION_REPLY_RE.exec(text2);
|
|
61124
61617
|
if (permMatch) {
|
|
61125
61618
|
const behavior = permMatch[1].toLowerCase().startsWith("y") ? "allow" : "deny";
|
|
61126
61619
|
const request_id = permMatch[2].toLowerCase();
|
|
@@ -61145,12 +61638,12 @@ async function handleInbound(ctx, text, downloadImage, attachment, extraAttachme
|
|
|
61145
61638
|
}
|
|
61146
61639
|
const interceptKey = chatKey2(chat_id, messageThreadId);
|
|
61147
61640
|
const pendingAdd = pendingAuthAddFlows.get(interceptKey);
|
|
61148
|
-
if (pendingAdd && looksLikeAuthCode(
|
|
61641
|
+
if (pendingAdd && looksLikeAuthCode(text2)) {
|
|
61149
61642
|
const elapsed = Date.now() - pendingAdd.startedAt;
|
|
61150
61643
|
if (elapsed < REAUTH_INTERCEPT_TTL_MS) {
|
|
61151
61644
|
pendingAuthAddFlows.delete(interceptKey);
|
|
61152
61645
|
try {
|
|
61153
|
-
const credentials = await submitAccountAuthCode(pendingAdd,
|
|
61646
|
+
const credentials = await submitAccountAuthCode(pendingAdd, text2.trim());
|
|
61154
61647
|
try {
|
|
61155
61648
|
await addAccountViaBroker(pendingAdd.label, credentials, { replace: false });
|
|
61156
61649
|
cleanScratchDir(pendingAdd.scratchDir);
|
|
@@ -61170,11 +61663,11 @@ The fleet's active account hasn't changed. Send <code>/auth use ${escapeHtmlForT
|
|
|
61170
61663
|
pendingAuthAddFlows.delete(interceptKey);
|
|
61171
61664
|
}
|
|
61172
61665
|
const pendingReauth = pendingReauthFlows.get(interceptKey);
|
|
61173
|
-
if (pendingReauth && looksLikeAuthCode(
|
|
61666
|
+
if (pendingReauth && looksLikeAuthCode(text2)) {
|
|
61174
61667
|
const elapsed = Date.now() - pendingReauth.startedAt;
|
|
61175
61668
|
if (elapsed < REAUTH_INTERCEPT_TTL_MS) {
|
|
61176
61669
|
pendingReauthFlows.delete(interceptKey);
|
|
61177
|
-
const { result: result2, errorText } = execAuthCode(pendingReauth.agent,
|
|
61670
|
+
const { result: result2, errorText } = execAuthCode(pendingReauth.agent, text2.trim());
|
|
61178
61671
|
if (errorText) {
|
|
61179
61672
|
await switchroomReply(ctx, `<b>auth code failed:</b>
|
|
61180
61673
|
${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
@@ -61202,7 +61695,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61202
61695
|
} else {
|
|
61203
61696
|
pendingVaultOps.delete(chat_id);
|
|
61204
61697
|
if (pendingVault.kind === "passphrase") {
|
|
61205
|
-
const passphrase =
|
|
61698
|
+
const passphrase = text2.trim();
|
|
61206
61699
|
if (!passphrase) {
|
|
61207
61700
|
await switchroomReply(ctx, "Passphrase cannot be empty. Try /vault again.", { html: true });
|
|
61208
61701
|
return;
|
|
@@ -61212,7 +61705,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61212
61705
|
await deleteSensitiveMessage(chat_id, msgId, "vault passphrase");
|
|
61213
61706
|
await executeVaultOp(ctx, chat_id, pendingVault.op, pendingVault.key, passphrase, undefined);
|
|
61214
61707
|
} else if (pendingVault.kind === "unlock") {
|
|
61215
|
-
const passphrase =
|
|
61708
|
+
const passphrase = text2.trim();
|
|
61216
61709
|
if (!passphrase) {
|
|
61217
61710
|
await switchroomReply(ctx, "Passphrase cannot be empty. Try /vault unlock again.", { html: true });
|
|
61218
61711
|
return;
|
|
@@ -61226,7 +61719,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61226
61719
|
await switchroomReply(ctx, `<b>vault unlock failed:</b> ${escapeHtmlForTg(result2.msg ?? "unknown error")}`, { html: true });
|
|
61227
61720
|
}
|
|
61228
61721
|
} else if (pendingVault.kind === "passphrase-for-deferred") {
|
|
61229
|
-
const passphrase =
|
|
61722
|
+
const passphrase = text2.trim();
|
|
61230
61723
|
if (!passphrase) {
|
|
61231
61724
|
await switchroomReply(ctx, "Passphrase cannot be empty. Tap the unlock button again.", { html: true });
|
|
61232
61725
|
return;
|
|
@@ -61236,7 +61729,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61236
61729
|
await deleteSensitiveMessage(chat_id, msgId, "vault passphrase");
|
|
61237
61730
|
await executeDeferredSecretSave(ctx, pendingVault.deferKey, passphrase, pendingVault.cardMessageId);
|
|
61238
61731
|
} else if (pendingVault.kind === "passphrase-for-access-approve") {
|
|
61239
|
-
const passphrase =
|
|
61732
|
+
const passphrase = text2.trim();
|
|
61240
61733
|
if (!passphrase) {
|
|
61241
61734
|
await switchroomReply(ctx, "Passphrase cannot be empty. Ask the agent to re-issue the request card.", { html: true });
|
|
61242
61735
|
return;
|
|
@@ -61253,7 +61746,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61253
61746
|
await performVaultAccessApproval(ctx, stagedAccess, item.stageId, item.senderId, { kind: "passphrase", passphrase });
|
|
61254
61747
|
}
|
|
61255
61748
|
} else if (pendingVault.kind === "grant-wizard" && pendingVault.awaitingCustomDuration) {
|
|
61256
|
-
const input =
|
|
61749
|
+
const input = text2.trim();
|
|
61257
61750
|
const ttlSeconds = parseGrantDuration(input);
|
|
61258
61751
|
if (ttlSeconds === null) {
|
|
61259
61752
|
pendingVaultOps.set(chat_id, { ...pendingVault, startedAt: Date.now() });
|
|
@@ -61265,15 +61758,15 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61265
61758
|
} else if (pendingVault.kind === "grant-wizard") {
|
|
61266
61759
|
pendingVaultOps.set(chat_id, { ...pendingVault, startedAt: Date.now() });
|
|
61267
61760
|
} else if (pendingVault.kind === "value") {
|
|
61268
|
-
let value =
|
|
61269
|
-
const codeBlockMatch = /^```[\w]*\n?([\s\S]*?)```$/m.exec(
|
|
61761
|
+
let value = text2;
|
|
61762
|
+
const codeBlockMatch = /^```[\w]*\n?([\s\S]*?)```$/m.exec(text2);
|
|
61270
61763
|
if (codeBlockMatch)
|
|
61271
61764
|
value = codeBlockMatch[1];
|
|
61272
61765
|
if (msgId != null)
|
|
61273
61766
|
await deleteSensitiveMessage(chat_id, msgId, "vault secret value");
|
|
61274
61767
|
await executeVaultOp(ctx, chat_id, "set", pendingVault.key, pendingVault.passphrase, value.trim());
|
|
61275
61768
|
} else if (pendingVault.kind === "rename-vault-save") {
|
|
61276
|
-
const newKey =
|
|
61769
|
+
const newKey = text2.trim();
|
|
61277
61770
|
const staged = pendingVaultRequestSaves.get(pendingVault.stageId);
|
|
61278
61771
|
if (!staged) {
|
|
61279
61772
|
await switchroomReply(ctx, "\u231B That save card expired before you renamed. Ask the agent to re-issue.", { html: true });
|
|
@@ -61294,7 +61787,7 @@ ${preBlock(formatSwitchroomOutput(errorText))}`, { html: true });
|
|
|
61294
61787
|
return;
|
|
61295
61788
|
}
|
|
61296
61789
|
}
|
|
61297
|
-
const stagedMatch = /^\s*(stash|ignore|rename|forget)\b\s*(\S+)?/i.exec(
|
|
61790
|
+
const stagedMatch = /^\s*(stash|ignore|rename|forget)\b\s*(\S+)?/i.exec(text2);
|
|
61298
61791
|
if (stagedMatch) {
|
|
61299
61792
|
const staged = secretStaging.latestForChat(chat_id);
|
|
61300
61793
|
if (staged != null) {
|
|
@@ -61340,13 +61833,13 @@ ${preBlock(write.output)}`;
|
|
|
61340
61833
|
}
|
|
61341
61834
|
}
|
|
61342
61835
|
bot.api.sendChatAction(chat_id, "typing", messageThreadId != null ? { message_thread_id: messageThreadId } : {}).catch(() => {});
|
|
61343
|
-
const parsedSteer = parseSteerPrefix(
|
|
61836
|
+
const parsedSteer = parseSteerPrefix(text2);
|
|
61344
61837
|
const isSteerPrefix = parsedSteer.steering;
|
|
61345
|
-
const parsedQueue = isSteerPrefix ? { queued: false, body: parsedSteer.body } : parseQueuePrefix(
|
|
61838
|
+
const parsedQueue = isSteerPrefix ? { queued: false, body: parsedSteer.body } : parseQueuePrefix(text2);
|
|
61346
61839
|
const isQueuedPrefix = parsedQueue.queued;
|
|
61347
|
-
let effectiveText = isSteerPrefix ? parsedSteer.body : isQueuedPrefix ? parsedQueue.body :
|
|
61840
|
+
let effectiveText = isSteerPrefix ? parsedSteer.body : isQueuedPrefix ? parsedQueue.body : text2;
|
|
61348
61841
|
if (armedSecretCaptures.has(chat_id)) {
|
|
61349
|
-
const consumed = await captureProvidedSecret(ctx, chat_id, msgId ?? undefined,
|
|
61842
|
+
const consumed = await captureProvidedSecret(ctx, chat_id, msgId ?? undefined, text2);
|
|
61350
61843
|
if (consumed)
|
|
61351
61844
|
return;
|
|
61352
61845
|
}
|
|
@@ -61516,7 +62009,7 @@ ${preBlock(write.output)}`;
|
|
|
61516
62009
|
chat_id,
|
|
61517
62010
|
message_id: msgId,
|
|
61518
62011
|
thread_id: messageThreadId ?? null,
|
|
61519
|
-
inbound_classified_as_status_query: classifyInbound(
|
|
62012
|
+
inbound_classified_as_status_query: classifyInbound(text2).isStatusQuery
|
|
61520
62013
|
});
|
|
61521
62014
|
const agentDir = resolveAgentDirFromEnv();
|
|
61522
62015
|
if (agentDir != null) {
|
|
@@ -61762,21 +62255,21 @@ function formatSwitchroomOutput(output, maxLen = 4000) {
|
|
|
61762
62255
|
return trimmed.slice(0, maxLen - 20) + `
|
|
61763
62256
|
... (truncated)`;
|
|
61764
62257
|
}
|
|
61765
|
-
function stripAnsi2(
|
|
61766
|
-
return
|
|
62258
|
+
function stripAnsi2(text2) {
|
|
62259
|
+
return text2.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
61767
62260
|
}
|
|
61768
|
-
function escapeHtmlForTg(
|
|
61769
|
-
return
|
|
62261
|
+
function escapeHtmlForTg(text2) {
|
|
62262
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
61770
62263
|
}
|
|
61771
|
-
function preBlock(
|
|
61772
|
-
return "<pre>" + escapeHtmlForTg(
|
|
62264
|
+
function preBlock(text2) {
|
|
62265
|
+
return "<pre>" + escapeHtmlForTg(text2) + "</pre>";
|
|
61773
62266
|
}
|
|
61774
|
-
async function switchroomReply(ctx,
|
|
62267
|
+
async function switchroomReply(ctx, text2, options = {}) {
|
|
61775
62268
|
const chatId = String(ctx.chat.id);
|
|
61776
62269
|
const baseThreadId = resolveThreadId(chatId, ctx.message?.message_thread_id);
|
|
61777
62270
|
const routedOpts = options.classification ? slashCommandReplyOpts(ctx, options.classification) : {};
|
|
61778
62271
|
const threadId = routedOpts.message_thread_id ?? baseThreadId;
|
|
61779
|
-
await ctx.reply(
|
|
62272
|
+
await ctx.reply(text2, {
|
|
61780
62273
|
...threadId != null ? { message_thread_id: threadId } : {},
|
|
61781
62274
|
...options.html ? { parse_mode: "HTML", link_preview_options: { is_disabled: true } } : {},
|
|
61782
62275
|
...options.reply_markup ? { reply_markup: options.reply_markup } : {}
|
|
@@ -61800,8 +62293,8 @@ function getCommandArgs(ctx) {
|
|
|
61800
62293
|
const fromMatch = typeof ctx.match === "string" ? ctx.match.trim() : "";
|
|
61801
62294
|
if (fromMatch)
|
|
61802
62295
|
return fromMatch;
|
|
61803
|
-
const
|
|
61804
|
-
const m =
|
|
62296
|
+
const text2 = ctx.msg?.text ?? ctx.message?.text ?? "";
|
|
62297
|
+
const m = text2.match(/^\/\S+\s+([\s\S]*)$/);
|
|
61805
62298
|
return m ? m[1].trim() : "";
|
|
61806
62299
|
}
|
|
61807
62300
|
function assertSafeAgentName(name) {
|
|
@@ -61927,6 +62420,40 @@ function resolveAgentSupergroupChatId() {
|
|
|
61927
62420
|
return;
|
|
61928
62421
|
}
|
|
61929
62422
|
}
|
|
62423
|
+
function isSelfLinearAgentEnabled() {
|
|
62424
|
+
const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
|
|
62425
|
+
if (!agentName3)
|
|
62426
|
+
return false;
|
|
62427
|
+
try {
|
|
62428
|
+
const cfg = loadConfig2();
|
|
62429
|
+
const rawAgent = cfg.agents?.[agentName3];
|
|
62430
|
+
if (!rawAgent)
|
|
62431
|
+
return false;
|
|
62432
|
+
const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
|
|
62433
|
+
const la = resolved.channels?.telegram?.linear_agent;
|
|
62434
|
+
return la?.enabled === true;
|
|
62435
|
+
} catch {
|
|
62436
|
+
return false;
|
|
62437
|
+
}
|
|
62438
|
+
}
|
|
62439
|
+
async function runLinearAuthWatch() {
|
|
62440
|
+
const agent = process.env.SWITCHROOM_AGENT_NAME;
|
|
62441
|
+
if (!agent)
|
|
62442
|
+
return;
|
|
62443
|
+
const io = brokerRefreshIO(agent);
|
|
62444
|
+
const status = await runLinearAuthCheck({
|
|
62445
|
+
agent,
|
|
62446
|
+
linearEnabled: isSelfLinearAgentEnabled,
|
|
62447
|
+
readBundle: io.readBundle,
|
|
62448
|
+
refresh: () => performLinearRefresh2(io),
|
|
62449
|
+
onAuthDead: notifyLinearAuthDead,
|
|
62450
|
+
log: (s) => process.stderr.write(s)
|
|
62451
|
+
});
|
|
62452
|
+
if (status !== "disabled" && status !== "fresh") {
|
|
62453
|
+
process.stderr.write(`telegram gateway: linear-auth-watch agent=${agent} status=${status}
|
|
62454
|
+
`);
|
|
62455
|
+
}
|
|
62456
|
+
}
|
|
61930
62457
|
function stampUserRestartReason(reason) {
|
|
61931
62458
|
try {
|
|
61932
62459
|
writeCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH, {
|
|
@@ -62039,9 +62566,9 @@ function notifyDetachedFailure(chatId, threadId, label) {
|
|
|
62039
62566
|
return ({ code, tail }) => {
|
|
62040
62567
|
clearRestartMarker();
|
|
62041
62568
|
const snippet = tail ? tail.slice(-800) : "(no output captured)";
|
|
62042
|
-
const
|
|
62569
|
+
const text2 = `\u274C <b>${escapeHtmlForTg(label)} failed</b> (exit ${code}):
|
|
62043
62570
|
` + preBlock(snippet);
|
|
62044
|
-
swallowingApiCall(() => lockedBot.api.sendMessage(chatId,
|
|
62571
|
+
swallowingApiCall(() => lockedBot.api.sendMessage(chatId, text2, {
|
|
62045
62572
|
parse_mode: "HTML",
|
|
62046
62573
|
link_preview_options: { is_disabled: true },
|
|
62047
62574
|
...threadId != null ? { message_thread_id: threadId } : {}
|
|
@@ -62475,7 +63002,8 @@ async function buildLiveProbeRows(agentName3) {
|
|
|
62475
63002
|
"scheduler",
|
|
62476
63003
|
"broker",
|
|
62477
63004
|
"kernel",
|
|
62478
|
-
"skills"
|
|
63005
|
+
"skills",
|
|
63006
|
+
"connections"
|
|
62479
63007
|
];
|
|
62480
63008
|
for (const k of order) {
|
|
62481
63009
|
const r = probes[k];
|
|
@@ -62550,7 +63078,7 @@ bot.command("inject", async (ctx) => {
|
|
|
62550
63078
|
await handleInjectCommand(ctx, {
|
|
62551
63079
|
isAuthorized: isAuthorizedSender,
|
|
62552
63080
|
inject: injectSlashCommand,
|
|
62553
|
-
reply: async (ctx2,
|
|
63081
|
+
reply: async (ctx2, text2, opts) => switchroomReply(ctx2, text2, { html: opts?.html }),
|
|
62554
63082
|
getAgentName: getMyAgentName,
|
|
62555
63083
|
getArgs: getCommandArgs,
|
|
62556
63084
|
escapeHtml: escapeHtmlForTg,
|
|
@@ -62603,8 +63131,8 @@ function modelMenuReplyMarkup(reply) {
|
|
|
62603
63131
|
bot.command("model", async (ctx) => {
|
|
62604
63132
|
if (!isAuthorizedSender(ctx))
|
|
62605
63133
|
return;
|
|
62606
|
-
const
|
|
62607
|
-
const parsed = parseModelCommand(
|
|
63134
|
+
const text2 = ctx.message?.text ?? ctx.channelPost?.text ?? "";
|
|
63135
|
+
const parsed = parseModelCommand(text2) ?? { kind: "show" };
|
|
62608
63136
|
const deps = buildModelDeps();
|
|
62609
63137
|
if (parsed.kind === "show" && process.env.SWITCHROOM_MODEL_MENU !== "0") {
|
|
62610
63138
|
const menu = await buildModelMenu(deps);
|
|
@@ -62639,8 +63167,8 @@ function effortMenuReplyMarkup(reply) {
|
|
|
62639
63167
|
bot.command("effort", async (ctx) => {
|
|
62640
63168
|
if (!isAuthorizedSender(ctx))
|
|
62641
63169
|
return;
|
|
62642
|
-
const
|
|
62643
|
-
const parsed = parseEffortCommand(
|
|
63170
|
+
const text2 = ctx.message?.text ?? ctx.channelPost?.text ?? "";
|
|
63171
|
+
const parsed = parseEffortCommand(text2) ?? { kind: "show" };
|
|
62644
63172
|
const deps = buildEffortDeps();
|
|
62645
63173
|
if (parsed.kind === "show") {
|
|
62646
63174
|
const menu = buildEffortMenu(deps);
|
|
@@ -63071,6 +63599,7 @@ async function handlePermissionSlash(ctx, behavior) {
|
|
|
63071
63599
|
await switchroomReply(ctx, `No pending permission for id <code>${escapeHtmlForTg(request_id)}</code>. It may have already been answered or timed out.`, { html: true });
|
|
63072
63600
|
return;
|
|
63073
63601
|
}
|
|
63602
|
+
clearPermissionTimeoutSuppression("operator answered via /approve|/deny");
|
|
63074
63603
|
dispatchPermissionVerdict({ type: "permission", requestId: request_id, behavior });
|
|
63075
63604
|
resumeReactionAfterVerdict();
|
|
63076
63605
|
postPermissionResumeMessage({
|
|
@@ -63609,8 +64138,8 @@ bot.command("auth", async (ctx) => {
|
|
|
63609
64138
|
}
|
|
63610
64139
|
return;
|
|
63611
64140
|
}
|
|
63612
|
-
const
|
|
63613
|
-
const parsed = parseAuthCommand(
|
|
64141
|
+
const text2 = ctx.message?.text ?? "";
|
|
64142
|
+
const parsed = parseAuthCommand(text2);
|
|
63614
64143
|
if (!parsed)
|
|
63615
64144
|
return;
|
|
63616
64145
|
const currentAgent = getMyAgentName();
|
|
@@ -64249,14 +64778,14 @@ async function grantWizardStep2(ctx, chatId, agent, wizardMsgId) {
|
|
|
64249
64778
|
}
|
|
64250
64779
|
const selected = new Set;
|
|
64251
64780
|
const kb = buildGrantKeysKeyboard(keys, selected);
|
|
64252
|
-
const
|
|
64781
|
+
const text2 = `<b>Grant capability token \u2014 Step 2/3</b>
|
|
64253
64782
|
|
|
64254
64783
|
Which keys for <code>${escapeHtmlForTg(agent)}</code>?
|
|
64255
64784
|
<i>Tap to toggle; tap Continue when done.</i>`;
|
|
64256
64785
|
if (wizardMsgId != null) {
|
|
64257
|
-
await ctx.api.editMessageText(chatId, wizardMsgId,
|
|
64786
|
+
await ctx.api.editMessageText(chatId, wizardMsgId, text2, { parse_mode: "HTML", reply_markup: kb }).catch(() => {});
|
|
64258
64787
|
} else {
|
|
64259
|
-
const sent = await switchroomReply(ctx,
|
|
64788
|
+
const sent = await switchroomReply(ctx, text2, { html: true, reply_markup: kb });
|
|
64260
64789
|
wizardMsgId = sent?.message_id;
|
|
64261
64790
|
}
|
|
64262
64791
|
pendingVaultOps.set(chatId, {
|
|
@@ -64273,7 +64802,7 @@ async function grantWizardStep3(ctx, chatId, state4) {
|
|
|
64273
64802
|
const kb = buildGrantDurationKeyboard();
|
|
64274
64803
|
const keyList = state4.selectedKeys.map((k) => `\u2022 <code>${escapeHtmlForTg(k)}</code>`).join(`
|
|
64275
64804
|
`);
|
|
64276
|
-
const
|
|
64805
|
+
const text2 = `<b>Grant capability token \u2014 Step 3/3</b>
|
|
64277
64806
|
|
|
64278
64807
|
Keys for <code>${escapeHtmlForTg(state4.agent)}</code>:
|
|
64279
64808
|
${keyList}
|
|
@@ -64281,9 +64810,9 @@ ${keyList}
|
|
|
64281
64810
|
How long should this grant be valid?`;
|
|
64282
64811
|
const msgId = state4.wizardMsgId;
|
|
64283
64812
|
if (msgId != null) {
|
|
64284
|
-
await ctx.api.editMessageText(chatId, msgId,
|
|
64813
|
+
await ctx.api.editMessageText(chatId, msgId, text2, { parse_mode: "HTML", reply_markup: kb }).catch(() => {});
|
|
64285
64814
|
} else {
|
|
64286
|
-
const sent = await switchroomReply(ctx,
|
|
64815
|
+
const sent = await switchroomReply(ctx, text2, { html: true, reply_markup: kb });
|
|
64287
64816
|
state4.wizardMsgId = sent?.message_id;
|
|
64288
64817
|
}
|
|
64289
64818
|
pendingVaultOps.set(chatId, { ...state4, step: "duration" });
|
|
@@ -64293,7 +64822,7 @@ async function grantWizardConfirm(ctx, chatId, state4) {
|
|
|
64293
64822
|
const expiresLabel = formatGrantExpiry(state4.ttlSeconds);
|
|
64294
64823
|
const keyList = state4.selectedKeys.map((k) => `\u2022 <code>${escapeHtmlForTg(k)}</code>`).join(`
|
|
64295
64824
|
`);
|
|
64296
|
-
const
|
|
64825
|
+
const text2 = [
|
|
64297
64826
|
"<b>Confirm grant</b>",
|
|
64298
64827
|
"",
|
|
64299
64828
|
`Agent: <code>${escapeHtmlForTg(state4.agent)}</code>`,
|
|
@@ -64306,9 +64835,9 @@ ${keyList}`,
|
|
|
64306
64835
|
`);
|
|
64307
64836
|
const msgId = state4.wizardMsgId;
|
|
64308
64837
|
if (msgId != null) {
|
|
64309
|
-
await ctx.api.editMessageText(chatId, msgId,
|
|
64838
|
+
await ctx.api.editMessageText(chatId, msgId, text2, { parse_mode: "HTML", reply_markup: kb }).catch(() => {});
|
|
64310
64839
|
} else {
|
|
64311
|
-
const sent = await switchroomReply(ctx,
|
|
64840
|
+
const sent = await switchroomReply(ctx, text2, { html: true, reply_markup: kb });
|
|
64312
64841
|
state4.wizardMsgId = sent?.message_id;
|
|
64313
64842
|
}
|
|
64314
64843
|
const kernelRequestId = await mintGrantWizardKernelRequest(state4.agent, loadAccess().allowFrom, state4.selectedKeys, state4.ttlSeconds ?? null);
|
|
@@ -64778,7 +65307,7 @@ async function handleAuthDashboardCallback(ctx) {
|
|
|
64778
65307
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
64779
65308
|
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
64780
65309
|
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
64781
|
-
const
|
|
65310
|
+
const text2 = renderAuthSnapshotFormat23(snapshots, {
|
|
64782
65311
|
tz,
|
|
64783
65312
|
now: new Date,
|
|
64784
65313
|
liveProbedAtMs: Date.now()
|
|
@@ -64793,7 +65322,7 @@ async function handleAuthDashboardCallback(ctx) {
|
|
|
64793
65322
|
}));
|
|
64794
65323
|
const msg = ctx.callbackQuery?.message;
|
|
64795
65324
|
if (msg) {
|
|
64796
|
-
await swallowingApiCall(() => bot.api.editMessageText(msg.chat.id, msg.message_id,
|
|
65325
|
+
await swallowingApiCall(() => bot.api.editMessageText(msg.chat.id, msg.message_id, text2, {
|
|
64797
65326
|
parse_mode: "HTML",
|
|
64798
65327
|
reply_markup: { inline_keyboard }
|
|
64799
65328
|
}), { chat_id: String(msg.chat.id), verb: "auth:refresh:edit" });
|
|
@@ -65175,12 +65704,12 @@ bot.command("usage", async (ctx) => {
|
|
|
65175
65704
|
const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
|
|
65176
65705
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
|
|
65177
65706
|
const snapshots = buildSnapshotsFromState4(state4, quotas);
|
|
65178
|
-
const
|
|
65707
|
+
const text2 = renderAuthSnapshotFormat23(snapshots, {
|
|
65179
65708
|
tz,
|
|
65180
65709
|
now: new Date,
|
|
65181
65710
|
liveProbedAtMs: Date.now()
|
|
65182
65711
|
});
|
|
65183
|
-
await switchroomReply(ctx,
|
|
65712
|
+
await switchroomReply(ctx, text2, { html: true });
|
|
65184
65713
|
return;
|
|
65185
65714
|
}
|
|
65186
65715
|
}
|
|
@@ -65869,6 +66398,7 @@ ${editLabel}` : editLabel,
|
|
|
65869
66398
|
});
|
|
65870
66399
|
return;
|
|
65871
66400
|
}
|
|
66401
|
+
clearPermissionTimeoutSuppression("operator answered a permission card");
|
|
65872
66402
|
const pd = pendingPermissions.get(request_id);
|
|
65873
66403
|
const resumeAction = pd ? naturalAction(pd.tool_name, pd.input_preview) : "";
|
|
65874
66404
|
const scopedTtl = scopedApprovalTtlMs();
|
|
@@ -65956,10 +66486,10 @@ bot.on("message:voice", async (ctx) => {
|
|
|
65956
66486
|
if (voiceIn?.enabled && voiceIn?.provider === "openai") {
|
|
65957
66487
|
const transcript = await maybeTranscribeVoice(voice.file_id, voice.mime_type, voiceIn.language);
|
|
65958
66488
|
if (transcript != null) {
|
|
65959
|
-
const
|
|
66489
|
+
const text2 = ctx.message.caption ? `${ctx.message.caption}
|
|
65960
66490
|
|
|
65961
66491
|
[voice transcript] ${transcript}` : `[voice transcript] ${transcript}`;
|
|
65962
|
-
await handleInboundCoalesced(ctx,
|
|
66492
|
+
await handleInboundCoalesced(ctx, text2, undefined, {
|
|
65963
66493
|
kind: "voice",
|
|
65964
66494
|
file_id: voice.file_id,
|
|
65965
66495
|
size: voice.file_size,
|
|
@@ -66045,14 +66575,14 @@ bot.on("message:sticker", async (ctx) => {
|
|
|
66045
66575
|
parts.push(sticker.emoji);
|
|
66046
66576
|
if (sticker.set_name)
|
|
66047
66577
|
parts.push(`from "${sticker.set_name}"`);
|
|
66048
|
-
const
|
|
66049
|
-
await handleInboundCoalesced(ctx,
|
|
66578
|
+
const text2 = parts.length > 0 ? `(sticker \u2014 ${parts.join(" ")})` : "(sticker)";
|
|
66579
|
+
await handleInboundCoalesced(ctx, text2, undefined, { kind: "sticker", file_id: sticker.file_id, size: sticker.file_size });
|
|
66050
66580
|
});
|
|
66051
66581
|
bot.on("message:animation", async (ctx) => {
|
|
66052
66582
|
const animation = ctx.message.animation;
|
|
66053
66583
|
const caption = ctx.message.caption;
|
|
66054
|
-
const
|
|
66055
|
-
await handleInboundCoalesced(ctx,
|
|
66584
|
+
const text2 = caption ? `(gif) ${caption}` : "(gif)";
|
|
66585
|
+
await handleInboundCoalesced(ctx, text2, undefined, {
|
|
66056
66586
|
kind: "animation",
|
|
66057
66587
|
file_id: animation.file_id,
|
|
66058
66588
|
size: animation.file_size,
|
|
@@ -66127,10 +66657,10 @@ bot.on("message:contact", async (ctx) => {
|
|
|
66127
66657
|
const last = safeName(c.last_name) ?? "";
|
|
66128
66658
|
const name = [first, last].filter(Boolean).join(" ") || "?";
|
|
66129
66659
|
const userIdPart = c.user_id != null ? ` user_id=${c.user_id}` : "";
|
|
66130
|
-
const
|
|
66660
|
+
const text2 = `(contact: name="${name}" phone="${phone}"${userIdPart})`;
|
|
66131
66661
|
process.stderr.write(`telegram gateway: inbound contact from chat=${ctx.chat?.id ?? "?"}
|
|
66132
66662
|
`);
|
|
66133
|
-
await handleInbound(ctx,
|
|
66663
|
+
await handleInbound(ctx, text2, undefined);
|
|
66134
66664
|
} catch (err) {
|
|
66135
66665
|
process.stderr.write(`telegram gateway: contact handler error: ${err.message}
|
|
66136
66666
|
`);
|
|
@@ -66142,10 +66672,10 @@ bot.on("message:location", async (ctx) => {
|
|
|
66142
66672
|
const lat = typeof loc.latitude === "number" ? loc.latitude.toFixed(6) : "?";
|
|
66143
66673
|
const lon = typeof loc.longitude === "number" ? loc.longitude.toFixed(6) : "?";
|
|
66144
66674
|
const live = loc.live_period != null ? ` live_period=${loc.live_period}s` : "";
|
|
66145
|
-
const
|
|
66675
|
+
const text2 = `(location: lat=${lat} lon=${lon}${live})`;
|
|
66146
66676
|
process.stderr.write(`telegram gateway: inbound location from chat=${ctx.chat?.id ?? "?"}
|
|
66147
66677
|
`);
|
|
66148
|
-
await handleInbound(ctx,
|
|
66678
|
+
await handleInbound(ctx, text2, undefined);
|
|
66149
66679
|
} catch (err) {
|
|
66150
66680
|
process.stderr.write(`telegram gateway: location handler error: ${err.message}
|
|
66151
66681
|
`);
|
|
@@ -66158,10 +66688,10 @@ bot.on("message:venue", async (ctx) => {
|
|
|
66158
66688
|
const address = safeName(v.address) ?? "?";
|
|
66159
66689
|
const lat = typeof v.location?.latitude === "number" ? v.location.latitude.toFixed(6) : "?";
|
|
66160
66690
|
const lon = typeof v.location?.longitude === "number" ? v.location.longitude.toFixed(6) : "?";
|
|
66161
|
-
const
|
|
66691
|
+
const text2 = `(venue: title="${title}" address="${address}" lat=${lat} lon=${lon})`;
|
|
66162
66692
|
process.stderr.write(`telegram gateway: inbound venue from chat=${ctx.chat?.id ?? "?"}
|
|
66163
66693
|
`);
|
|
66164
|
-
await handleInbound(ctx,
|
|
66694
|
+
await handleInbound(ctx, text2, undefined);
|
|
66165
66695
|
} catch (err) {
|
|
66166
66696
|
process.stderr.write(`telegram gateway: venue handler error: ${err.message}
|
|
66167
66697
|
`);
|
|
@@ -66174,10 +66704,10 @@ bot.on("message:poll", async (ctx) => {
|
|
|
66174
66704
|
const optsCount = Array.isArray(p.options) ? p.options.length : 0;
|
|
66175
66705
|
const optsList = Array.isArray(p.options) ? p.options.slice(0, 10).map((o) => safeName(o.text) ?? "?").join(" | ") : "";
|
|
66176
66706
|
const anon = p.is_anonymous ? " anonymous" : "";
|
|
66177
|
-
const
|
|
66707
|
+
const text2 = `(poll: question="${q}" options=${optsCount}${anon}${optsList ? ` choices=[${optsList}]` : ""})`;
|
|
66178
66708
|
process.stderr.write(`telegram gateway: inbound poll from chat=${ctx.chat?.id ?? "?"}
|
|
66179
66709
|
`);
|
|
66180
|
-
await handleInbound(ctx,
|
|
66710
|
+
await handleInbound(ctx, text2, undefined);
|
|
66181
66711
|
} catch (err) {
|
|
66182
66712
|
process.stderr.write(`telegram gateway: poll handler error: ${err.message}
|
|
66183
66713
|
`);
|
|
@@ -66189,10 +66719,10 @@ bot.on("message:web_app_data", async (ctx) => {
|
|
|
66189
66719
|
const raw = typeof w.data === "string" ? w.data : "";
|
|
66190
66720
|
const data = raw.length > 4096 ? raw.slice(0, 4096) + "\u2026(truncated)" : raw;
|
|
66191
66721
|
const button = safeName(w.button_text) ?? "?";
|
|
66192
|
-
const
|
|
66722
|
+
const text2 = `(web_app_data: button="${button}" data=${JSON.stringify(data)})`;
|
|
66193
66723
|
process.stderr.write(`telegram gateway: inbound web_app_data from chat=${ctx.chat?.id ?? "?"} button="${button}" bytes=${raw.length}
|
|
66194
66724
|
`);
|
|
66195
|
-
await handleInbound(ctx,
|
|
66725
|
+
await handleInbound(ctx, text2, undefined);
|
|
66196
66726
|
} catch (err) {
|
|
66197
66727
|
process.stderr.write(`telegram gateway: web_app_data handler error: ${err.message}
|
|
66198
66728
|
`);
|
|
@@ -66203,10 +66733,10 @@ bot.on("message:users_shared", async (ctx) => {
|
|
|
66203
66733
|
const u = ctx.message.users_shared;
|
|
66204
66734
|
const users = Array.isArray(u.users) ? u.users : [];
|
|
66205
66735
|
const ids = users.map((usr) => String(usr.user_id ?? "?")).join(",");
|
|
66206
|
-
const
|
|
66736
|
+
const text2 = `(users_shared: request_id=${u.request_id ?? "?"} user_ids=[${ids}] count=${users.length})`;
|
|
66207
66737
|
process.stderr.write(`telegram gateway: inbound users_shared from chat=${ctx.chat?.id ?? "?"} count=${users.length}
|
|
66208
66738
|
`);
|
|
66209
|
-
await handleInbound(ctx,
|
|
66739
|
+
await handleInbound(ctx, text2, undefined);
|
|
66210
66740
|
} catch (err) {
|
|
66211
66741
|
process.stderr.write(`telegram gateway: users_shared handler error: ${err.message}
|
|
66212
66742
|
`);
|
|
@@ -66217,10 +66747,10 @@ bot.on("message:chat_shared", async (ctx) => {
|
|
|
66217
66747
|
const c = ctx.message.chat_shared;
|
|
66218
66748
|
const title = safeName(c.title) ?? "";
|
|
66219
66749
|
const titlePart = title ? ` title="${title}"` : "";
|
|
66220
|
-
const
|
|
66750
|
+
const text2 = `(chat_shared: request_id=${c.request_id ?? "?"} chat_id=${c.chat_id ?? "?"}${titlePart})`;
|
|
66221
66751
|
process.stderr.write(`telegram gateway: inbound chat_shared from chat=${ctx.chat?.id ?? "?"} shared_chat_id=${c.chat_id ?? "?"}
|
|
66222
66752
|
`);
|
|
66223
|
-
await handleInbound(ctx,
|
|
66753
|
+
await handleInbound(ctx, text2, undefined);
|
|
66224
66754
|
} catch (err) {
|
|
66225
66755
|
process.stderr.write(`telegram gateway: chat_shared handler error: ${err.message}
|
|
66226
66756
|
`);
|
|
@@ -66401,7 +66931,7 @@ function maybeDispatchReaction(args) {
|
|
|
66401
66931
|
`);
|
|
66402
66932
|
}
|
|
66403
66933
|
}
|
|
66404
|
-
const { text, meta } = buildReactionDispatchInbound({
|
|
66934
|
+
const { text: text2, meta } = buildReactionDispatchInbound({
|
|
66405
66935
|
emoji: args.emoji,
|
|
66406
66936
|
chatId: args.chatId,
|
|
66407
66937
|
messageId: args.messageId,
|
|
@@ -66419,7 +66949,7 @@ function maybeDispatchReaction(args) {
|
|
|
66419
66949
|
user: args.user,
|
|
66420
66950
|
userId: args.userId,
|
|
66421
66951
|
ts,
|
|
66422
|
-
text,
|
|
66952
|
+
text: text2,
|
|
66423
66953
|
meta
|
|
66424
66954
|
};
|
|
66425
66955
|
const delivered = ipcServer.sendToAgent(agentName3, inbound);
|
|
@@ -66439,7 +66969,7 @@ function flushReactionBatch(batch) {
|
|
|
66439
66969
|
return;
|
|
66440
66970
|
}
|
|
66441
66971
|
const head = batch.reactions[batch.reactions.length - 1];
|
|
66442
|
-
const
|
|
66972
|
+
const text2 = buildReactionInboundText(batch);
|
|
66443
66973
|
const meta = buildReactionInboundMeta(batch);
|
|
66444
66974
|
const ts = Date.now();
|
|
66445
66975
|
const inbound = {
|
|
@@ -66450,7 +66980,7 @@ function flushReactionBatch(batch) {
|
|
|
66450
66980
|
user: head.user,
|
|
66451
66981
|
userId: head.userId,
|
|
66452
66982
|
ts,
|
|
66453
|
-
text,
|
|
66983
|
+
text: text2,
|
|
66454
66984
|
meta
|
|
66455
66985
|
};
|
|
66456
66986
|
const delivered = ipcServer.sendToAgent(agentName3, inbound);
|
|
@@ -66675,6 +67205,7 @@ async function shutdown(signal) {
|
|
|
66675
67205
|
pendingReauthFlows.clear();
|
|
66676
67206
|
pendingVaultOps.clear();
|
|
66677
67207
|
pendingPermissions.clear();
|
|
67208
|
+
permissionTimeoutSignatures.clear();
|
|
66678
67209
|
try {
|
|
66679
67210
|
await ipcServer.close();
|
|
66680
67211
|
} catch (err) {
|
|
@@ -66988,6 +67519,21 @@ var didOneTimeSetup = false;
|
|
|
66988
67519
|
});
|
|
66989
67520
|
}, QUOTA_WATCH_POLL_MS).unref();
|
|
66990
67521
|
}
|
|
67522
|
+
const LINEAR_AUTH_WATCH_POLL_MS = Number(process.env.SWITCHROOM_LINEAR_AUTH_WATCH_POLL_MS ?? 21600000);
|
|
67523
|
+
if (LINEAR_AUTH_WATCH_POLL_MS > 0) {
|
|
67524
|
+
setTimeout(() => {
|
|
67525
|
+
runLinearAuthWatch().catch((err) => {
|
|
67526
|
+
process.stderr.write(`telegram gateway: linear-auth-watch initial run failed: ${err}
|
|
67527
|
+
`);
|
|
67528
|
+
});
|
|
67529
|
+
}, 35000);
|
|
67530
|
+
setInterval(() => {
|
|
67531
|
+
runLinearAuthWatch().catch((err) => {
|
|
67532
|
+
process.stderr.write(`telegram gateway: linear-auth-watch scheduled run failed: ${err}
|
|
67533
|
+
`);
|
|
67534
|
+
});
|
|
67535
|
+
}, LINEAR_AUTH_WATCH_POLL_MS).unref();
|
|
67536
|
+
}
|
|
66991
67537
|
const RESTART_WATCHDOG_POLL_MS = Number(process.env.SWITCHROOM_RESTART_WATCHDOG_POLL_MS ?? 30000);
|
|
66992
67538
|
const watchdogAgentName = process.env.SWITCHROOM_AGENT_NAME;
|
|
66993
67539
|
const watchdogDockerMode = process.env.SWITCHROOM_RUNTIME === "docker";
|
|
@@ -67022,11 +67568,11 @@ var didOneTimeSetup = false;
|
|
|
67022
67568
|
const orphanStatusEnabled = isOrphanSubagentStatusEnabled(process.env.SWITCHROOM_ORPHAN_SUBAGENT_STATUS);
|
|
67023
67569
|
const workerActivityFeed = createWorkerActivityFeed({
|
|
67024
67570
|
bot: {
|
|
67025
|
-
sendMessage: async (cid,
|
|
67026
|
-
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid,
|
|
67571
|
+
sendMessage: async (cid, text2, sendOpts) => {
|
|
67572
|
+
const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid, text2, sendOpts), { chat_id: cid, verb: "worker-feed" });
|
|
67027
67573
|
return sent;
|
|
67028
67574
|
},
|
|
67029
|
-
editMessageText: (cid, mid,
|
|
67575
|
+
editMessageText: (cid, mid, text2, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text2, editOpts), { chat_id: cid, verb: "worker-feed" })
|
|
67030
67576
|
},
|
|
67031
67577
|
log: (msg) => process.stderr.write(`telegram gateway: ${msg}
|
|
67032
67578
|
`)
|