switchroom 0.14.9 → 0.14.10
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 +80 -77
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +100 -10
- package/dist/host-control/main.js +1 -0
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +3 -3
- package/telegram-plugin/dist/gateway/gateway.js +1027 -282
- package/telegram-plugin/gateway/gateway.ts +85 -0
- package/telegram-plugin/gateway/webhook-ingest-server.test.ts +125 -0
- package/telegram-plugin/gateway/webhook-ingest-server.ts +218 -0
|
@@ -11091,6 +11091,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11091
11091
|
webhook_rate_limit: exports_external.object({
|
|
11092
11092
|
rpm: exports_external.number().int().positive()
|
|
11093
11093
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — 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."),
|
|
11094
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
11094
11095
|
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 — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
11095
11096
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11096
11097
|
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 → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
@@ -12120,6 +12121,52 @@ function validateAllCronTopicAliases(config, filePath) {
|
|
|
12120
12121
|
}
|
|
12121
12122
|
}
|
|
12122
12123
|
|
|
12124
|
+
// src/scheduler/dispatch.ts
|
|
12125
|
+
import { createHash } from "node:crypto";
|
|
12126
|
+
function collectScheduleEntries(config) {
|
|
12127
|
+
const out = [];
|
|
12128
|
+
const agentNames = Object.keys(config.agents).sort();
|
|
12129
|
+
for (const agent of agentNames) {
|
|
12130
|
+
const raw = config.agents[agent];
|
|
12131
|
+
if (!raw)
|
|
12132
|
+
continue;
|
|
12133
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, raw);
|
|
12134
|
+
const schedule = resolved.schedule ?? [];
|
|
12135
|
+
for (let i = 0;i < schedule.length; i++) {
|
|
12136
|
+
const entry = schedule[i];
|
|
12137
|
+
out.push({
|
|
12138
|
+
agent,
|
|
12139
|
+
scheduleIndex: i,
|
|
12140
|
+
cron: entry.cron,
|
|
12141
|
+
prompt: entry.prompt,
|
|
12142
|
+
promptKey: createHash("sha256").update(entry.prompt).digest("hex").slice(0, 12),
|
|
12143
|
+
...entry.topic !== undefined ? { topic: entry.topic } : {}
|
|
12144
|
+
});
|
|
12145
|
+
}
|
|
12146
|
+
}
|
|
12147
|
+
return out;
|
|
12148
|
+
}
|
|
12149
|
+
function dispatchAsInbound(entry, options, dispatcher) {
|
|
12150
|
+
const ts = (options.now ?? Date.now)();
|
|
12151
|
+
const message = {
|
|
12152
|
+
type: "inbound",
|
|
12153
|
+
chatId: options.chatId,
|
|
12154
|
+
...options.threadId !== undefined ? { threadId: options.threadId } : {},
|
|
12155
|
+
messageId: ts,
|
|
12156
|
+
user: "cron",
|
|
12157
|
+
userId: 0,
|
|
12158
|
+
ts,
|
|
12159
|
+
text: entry.prompt,
|
|
12160
|
+
meta: {
|
|
12161
|
+
source: "cron",
|
|
12162
|
+
schedule_index: String(entry.scheduleIndex),
|
|
12163
|
+
prompt_key: entry.promptKey
|
|
12164
|
+
}
|
|
12165
|
+
};
|
|
12166
|
+
const delivered = dispatcher.sendToAgent(entry.agent, message);
|
|
12167
|
+
return { delivered, message };
|
|
12168
|
+
}
|
|
12169
|
+
|
|
12123
12170
|
// src/telegram/topic-router.ts
|
|
12124
12171
|
var ALERTS_ALIAS = "alerts";
|
|
12125
12172
|
var ADMIN_ALIAS = "admin";
|
|
@@ -12173,50 +12220,41 @@ function resolveOutboundTopic(config, event) {
|
|
|
12173
12220
|
}
|
|
12174
12221
|
}
|
|
12175
12222
|
|
|
12176
|
-
// src/scheduler/
|
|
12177
|
-
|
|
12178
|
-
|
|
12179
|
-
const
|
|
12180
|
-
const
|
|
12181
|
-
|
|
12182
|
-
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
scheduleIndex: i,
|
|
12192
|
-
cron: entry.cron,
|
|
12193
|
-
prompt: entry.prompt,
|
|
12194
|
-
promptKey: createHash("sha256").update(entry.prompt).digest("hex").slice(0, 12),
|
|
12195
|
-
...entry.topic !== undefined ? { topic: entry.topic } : {}
|
|
12196
|
-
});
|
|
12197
|
-
}
|
|
12223
|
+
// src/agent-scheduler/channel-target.ts
|
|
12224
|
+
function resolveChannelTarget(config, agentName) {
|
|
12225
|
+
const agent = config.agents?.[agentName];
|
|
12226
|
+
const tgChannel = agent ? resolveAgentConfig(config.defaults, config.profiles, agent).channels?.telegram : undefined;
|
|
12227
|
+
const supergroupChatId = tgChannel?.chat_id;
|
|
12228
|
+
const supergroupDefaultTopic = tgChannel?.default_topic_id;
|
|
12229
|
+
if (typeof supergroupChatId === "string" && supergroupChatId.length > 0) {
|
|
12230
|
+
return {
|
|
12231
|
+
chatId: supergroupChatId,
|
|
12232
|
+
...typeof supergroupDefaultTopic === "number" ? { threadId: supergroupDefaultTopic } : {},
|
|
12233
|
+
routerConfig: {
|
|
12234
|
+
...typeof supergroupDefaultTopic === "number" ? { default_topic_id: supergroupDefaultTopic } : {},
|
|
12235
|
+
...tgChannel?.topic_aliases ? { topic_aliases: tgChannel.topic_aliases } : {}
|
|
12236
|
+
}
|
|
12237
|
+
};
|
|
12198
12238
|
}
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
const
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
...options.threadId !== undefined ? { threadId: options.threadId } : {},
|
|
12207
|
-
messageId: ts,
|
|
12208
|
-
user: "cron",
|
|
12209
|
-
userId: 0,
|
|
12210
|
-
ts,
|
|
12211
|
-
text: entry.prompt,
|
|
12212
|
-
meta: {
|
|
12213
|
-
source: "cron",
|
|
12214
|
-
schedule_index: String(entry.scheduleIndex),
|
|
12215
|
-
prompt_key: entry.promptKey
|
|
12216
|
-
}
|
|
12239
|
+
const forumChatId = config.telegram?.forum_chat_id;
|
|
12240
|
+
if (typeof forumChatId !== "string" || forumChatId.length === 0)
|
|
12241
|
+
return null;
|
|
12242
|
+
const threadId = agent?.topic_id;
|
|
12243
|
+
return {
|
|
12244
|
+
chatId: forumChatId,
|
|
12245
|
+
...typeof threadId === "number" ? { threadId } : {}
|
|
12217
12246
|
};
|
|
12218
|
-
|
|
12219
|
-
|
|
12247
|
+
}
|
|
12248
|
+
function resolveEntryThreadId(entry, channel) {
|
|
12249
|
+
if (channel.routerConfig) {
|
|
12250
|
+
const routed = resolveOutboundTopic(channel.routerConfig, {
|
|
12251
|
+
kind: "cron",
|
|
12252
|
+
entryTopic: entry.topic
|
|
12253
|
+
});
|
|
12254
|
+
if (routed !== undefined)
|
|
12255
|
+
return routed;
|
|
12256
|
+
}
|
|
12257
|
+
return channel.threadId;
|
|
12220
12258
|
}
|
|
12221
12259
|
|
|
12222
12260
|
// src/scheduler/audit.ts
|
|
@@ -12716,41 +12754,6 @@ function ipcDispatcher(client) {
|
|
|
12716
12754
|
}
|
|
12717
12755
|
};
|
|
12718
12756
|
}
|
|
12719
|
-
function resolveChannelTarget(config, agentName) {
|
|
12720
|
-
const agent = config.agents?.[agentName];
|
|
12721
|
-
const tgChannel = agent ? resolveAgentConfig(config.defaults, config.profiles, agent).channels?.telegram : undefined;
|
|
12722
|
-
const supergroupChatId = tgChannel?.chat_id;
|
|
12723
|
-
const supergroupDefaultTopic = tgChannel?.default_topic_id;
|
|
12724
|
-
if (typeof supergroupChatId === "string" && supergroupChatId.length > 0) {
|
|
12725
|
-
return {
|
|
12726
|
-
chatId: supergroupChatId,
|
|
12727
|
-
...typeof supergroupDefaultTopic === "number" ? { threadId: supergroupDefaultTopic } : {},
|
|
12728
|
-
routerConfig: {
|
|
12729
|
-
...typeof supergroupDefaultTopic === "number" ? { default_topic_id: supergroupDefaultTopic } : {},
|
|
12730
|
-
...tgChannel?.topic_aliases ? { topic_aliases: tgChannel.topic_aliases } : {}
|
|
12731
|
-
}
|
|
12732
|
-
};
|
|
12733
|
-
}
|
|
12734
|
-
const forumChatId = config.telegram?.forum_chat_id;
|
|
12735
|
-
if (typeof forumChatId !== "string" || forumChatId.length === 0)
|
|
12736
|
-
return null;
|
|
12737
|
-
const threadId = agent?.topic_id;
|
|
12738
|
-
return {
|
|
12739
|
-
chatId: forumChatId,
|
|
12740
|
-
...typeof threadId === "number" ? { threadId } : {}
|
|
12741
|
-
};
|
|
12742
|
-
}
|
|
12743
|
-
function resolveEntryThreadId(entry, channel) {
|
|
12744
|
-
if (channel.routerConfig) {
|
|
12745
|
-
const routed = resolveOutboundTopic(channel.routerConfig, {
|
|
12746
|
-
kind: "cron",
|
|
12747
|
-
entryTopic: entry.topic
|
|
12748
|
-
});
|
|
12749
|
-
if (routed !== undefined)
|
|
12750
|
-
return routed;
|
|
12751
|
-
}
|
|
12752
|
-
return channel.threadId;
|
|
12753
|
-
}
|
|
12754
12757
|
async function main() {
|
|
12755
12758
|
const agentName = process.env.SWITCHROOM_AGENT_NAME;
|
|
12756
12759
|
if (!agentName) {
|
|
@@ -11091,6 +11091,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11091
11091
|
webhook_rate_limit: exports_external.object({
|
|
11092
11092
|
rpm: exports_external.number().int().positive()
|
|
11093
11093
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — 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."),
|
|
11094
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
11094
11095
|
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 — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
11095
11096
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11096
11097
|
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 → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
@@ -11838,6 +11838,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
11838
11838
|
webhook_rate_limit: exports_external.object({
|
|
11839
11839
|
rpm: exports_external.number().int().positive()
|
|
11840
11840
|
}).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."),
|
|
11841
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
11841
11842
|
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
11842
11843
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11843
11844
|
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.")
|
package/dist/cli/switchroom.js
CHANGED
|
@@ -13655,6 +13655,7 @@ var init_schema = __esm(() => {
|
|
|
13655
13655
|
webhook_rate_limit: exports_external.object({
|
|
13656
13656
|
rpm: exports_external.number().int().positive()
|
|
13657
13657
|
}).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."),
|
|
13658
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
13658
13659
|
chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
13659
13660
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
13660
13661
|
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.")
|
|
@@ -23313,7 +23314,7 @@ function generateCompose(opts) {
|
|
|
23313
23314
|
telemetryDisabled,
|
|
23314
23315
|
posthogKeyOverride,
|
|
23315
23316
|
posthogHostOverride
|
|
23316
|
-
}, bundledSkillsPoolDir, hostControlEnabled);
|
|
23317
|
+
}, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid);
|
|
23317
23318
|
}
|
|
23318
23319
|
lines.push(`volumes:`);
|
|
23319
23320
|
for (const a of describeAgents(config)) {
|
|
@@ -23338,7 +23339,7 @@ function generateCompose(opts) {
|
|
|
23338
23339
|
return lines.join(`
|
|
23339
23340
|
`);
|
|
23340
23341
|
}
|
|
23341
|
-
function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled) {
|
|
23342
|
+
function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid) {
|
|
23342
23343
|
lines.push(` agent-${a.name}:`);
|
|
23343
23344
|
emitImageOrBuild(lines, "agent", imageTag, buildMode, buildContext);
|
|
23344
23345
|
lines.push(` container_name: ${containerNamePrefix}-${a.name}`);
|
|
@@ -23418,6 +23419,9 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
23418
23419
|
if (a.admin === true) {
|
|
23419
23420
|
env2.SWITCHROOM_AGENT_ADMIN = "true";
|
|
23420
23421
|
}
|
|
23422
|
+
if (operatorUid !== undefined) {
|
|
23423
|
+
env2.SWITCHROOM_WEBHOOK_RECEIVER_UID = String(operatorUid);
|
|
23424
|
+
}
|
|
23421
23425
|
for (const [k, v] of Object.entries(a.userEnv)) {
|
|
23422
23426
|
if (env2[k] === undefined)
|
|
23423
23427
|
env2[k] = v;
|
|
@@ -49404,8 +49408,8 @@ var {
|
|
|
49404
49408
|
} = import__.default;
|
|
49405
49409
|
|
|
49406
49410
|
// src/build-info.ts
|
|
49407
|
-
var VERSION = "0.14.
|
|
49408
|
-
var COMMIT_SHA = "
|
|
49411
|
+
var VERSION = "0.14.10";
|
|
49412
|
+
var COMMIT_SHA = "0cd391b5";
|
|
49409
49413
|
|
|
49410
49414
|
// src/cli/agent.ts
|
|
49411
49415
|
init_source();
|
|
@@ -71404,6 +71408,50 @@ function escapeHtml3(s) {
|
|
|
71404
71408
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
71405
71409
|
}
|
|
71406
71410
|
|
|
71411
|
+
// src/web/webhook-ingest-client.ts
|
|
71412
|
+
import net4 from "node:net";
|
|
71413
|
+
function forwardToGateway(socketPath, req, opts = {}) {
|
|
71414
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
71415
|
+
return new Promise((resolve28) => {
|
|
71416
|
+
let settled = false;
|
|
71417
|
+
let buf = "";
|
|
71418
|
+
const done = (value) => {
|
|
71419
|
+
if (settled)
|
|
71420
|
+
return;
|
|
71421
|
+
settled = true;
|
|
71422
|
+
try {
|
|
71423
|
+
conn.destroy();
|
|
71424
|
+
} catch {}
|
|
71425
|
+
resolve28(value);
|
|
71426
|
+
};
|
|
71427
|
+
const conn = net4.createConnection(socketPath);
|
|
71428
|
+
conn.setEncoding("utf8");
|
|
71429
|
+
conn.setTimeout(timeoutMs);
|
|
71430
|
+
conn.on("connect", () => {
|
|
71431
|
+
conn.write(JSON.stringify(req) + `
|
|
71432
|
+
`);
|
|
71433
|
+
});
|
|
71434
|
+
conn.on("data", (chunk) => {
|
|
71435
|
+
buf += chunk;
|
|
71436
|
+
const nl = buf.indexOf(`
|
|
71437
|
+
`);
|
|
71438
|
+
if (nl === -1)
|
|
71439
|
+
return;
|
|
71440
|
+
const line = buf.slice(0, nl);
|
|
71441
|
+
try {
|
|
71442
|
+
done(JSON.parse(line));
|
|
71443
|
+
} catch {
|
|
71444
|
+
done({ status: "error", error: "unparseable gateway response" });
|
|
71445
|
+
}
|
|
71446
|
+
});
|
|
71447
|
+
conn.on("timeout", () => done(null));
|
|
71448
|
+
conn.on("error", () => done(null));
|
|
71449
|
+
conn.on("close", () => {
|
|
71450
|
+
done(null);
|
|
71451
|
+
});
|
|
71452
|
+
});
|
|
71453
|
+
}
|
|
71454
|
+
|
|
71407
71455
|
// src/web/webhook-handler.ts
|
|
71408
71456
|
var KNOWN_SOURCES = ["github", "generic"];
|
|
71409
71457
|
function jsonReply(status, body, extraHeaders) {
|
|
@@ -71559,7 +71607,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
71559
71607
|
`);
|
|
71560
71608
|
return jsonReply(401, { ok: false, error: "unauthorized" });
|
|
71561
71609
|
}
|
|
71562
|
-
if (source === "github") {
|
|
71610
|
+
if (source === "github" && !args.viaGateway) {
|
|
71563
71611
|
const deliveryId = args.headers.get("x-github-delivery");
|
|
71564
71612
|
if (deliveryId) {
|
|
71565
71613
|
const originalTs = dedupStore.check(args.agent, deliveryId, now);
|
|
@@ -71573,10 +71621,12 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
71573
71621
|
const rpm = args.config.rateLimit?.rpm;
|
|
71574
71622
|
const retryAfter = rpm !== undefined ? rateLimiter.check(args.agent, source, rpm, now) : null;
|
|
71575
71623
|
if (retryAfter !== null) {
|
|
71576
|
-
|
|
71577
|
-
|
|
71578
|
-
|
|
71579
|
-
|
|
71624
|
+
if (!args.viaGateway) {
|
|
71625
|
+
const agentDir2 = resolveAgentDir(args.agent);
|
|
71626
|
+
const telegramDir2 = join39(agentDir2, "telegram");
|
|
71627
|
+
if (shouldWriteThrottleIssue(args.agent, source, now)) {
|
|
71628
|
+
writeThrottleIssue(args.agent, source, now, telegramDir2, log);
|
|
71629
|
+
}
|
|
71580
71630
|
}
|
|
71581
71631
|
log(`webhook-ingest: agent='${args.agent}' source='${source}' rate limited retry-after=${retryAfter}s
|
|
71582
71632
|
`);
|
|
@@ -71592,6 +71642,45 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
71592
71642
|
}
|
|
71593
71643
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : args.source;
|
|
71594
71644
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
71645
|
+
if (args.viaGateway) {
|
|
71646
|
+
const socketPath = join39(resolveAgentDir(args.agent), "telegram", "webhook.sock");
|
|
71647
|
+
const forward = deps.forwardFn ?? forwardToGateway;
|
|
71648
|
+
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
71649
|
+
let resp;
|
|
71650
|
+
try {
|
|
71651
|
+
resp = await forward(socketPath, {
|
|
71652
|
+
agent: args.agent,
|
|
71653
|
+
source,
|
|
71654
|
+
event_type: eventType,
|
|
71655
|
+
ts: now,
|
|
71656
|
+
rendered_text: rendered.text,
|
|
71657
|
+
payload,
|
|
71658
|
+
...deliveryId ? { delivery_id: deliveryId } : {}
|
|
71659
|
+
});
|
|
71660
|
+
} catch (err) {
|
|
71661
|
+
log(`webhook-ingest: agent='${args.agent}' source='${source}' gateway forward threw: ${err.message}
|
|
71662
|
+
`);
|
|
71663
|
+
resp = null;
|
|
71664
|
+
}
|
|
71665
|
+
if (resp === null) {
|
|
71666
|
+
log(`webhook-ingest: agent='${args.agent}' source='${source}' gateway unreachable
|
|
71667
|
+
`);
|
|
71668
|
+
return jsonReply(503, { ok: false, error: "gateway unavailable" });
|
|
71669
|
+
}
|
|
71670
|
+
if (resp.status === "error") {
|
|
71671
|
+
log(`webhook-ingest: agent='${args.agent}' source='${source}' gateway error: ${resp.error ?? "unknown"}
|
|
71672
|
+
`);
|
|
71673
|
+
return jsonReply(500, { ok: false, error: "gateway record failed" });
|
|
71674
|
+
}
|
|
71675
|
+
if (resp.status === "deduped") {
|
|
71676
|
+
log(`webhook-ingest: agent='${args.agent}' source='${source}' deduped (gateway) ts=${resp.ts}
|
|
71677
|
+
`);
|
|
71678
|
+
return jsonReply(200, { ok: true, deduped: true, ts: resp.ts });
|
|
71679
|
+
}
|
|
71680
|
+
log(`webhook-ingest: agent='${args.agent}' source='${source}' event='${eventType}' recorded via gateway ts=${resp.ts}
|
|
71681
|
+
`);
|
|
71682
|
+
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
71683
|
+
}
|
|
71595
71684
|
const agentDir = resolveAgentDir(args.agent);
|
|
71596
71685
|
const telegramDir = join39(agentDir, "telegram");
|
|
71597
71686
|
const logPath = join39(telegramDir, "webhook-events.jsonl");
|
|
@@ -71768,7 +71857,8 @@ async function handleWebhookRoute(req, agent, source, config) {
|
|
|
71768
71857
|
allowedSources,
|
|
71769
71858
|
config: { secrets: agentSecrets },
|
|
71770
71859
|
agentExists: agentConfig !== undefined,
|
|
71771
|
-
dispatchConfig: agentConfig?.channels?.telegram?.webhook_dispatch
|
|
71860
|
+
dispatchConfig: agentConfig?.channels?.telegram?.webhook_dispatch,
|
|
71861
|
+
viaGateway: agentConfig?.channels?.telegram?.webhook_via_gateway === true
|
|
71772
71862
|
}, {});
|
|
71773
71863
|
return new Response(result.body, {
|
|
71774
71864
|
status: result.status,
|
|
@@ -13826,6 +13826,7 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
13826
13826
|
webhook_rate_limit: exports_external.object({
|
|
13827
13827
|
rpm: exports_external.number().int().positive()
|
|
13828
13828
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — 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."),
|
|
13829
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
13829
13830
|
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 — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
13830
13831
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
13831
13832
|
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 → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
@@ -11406,6 +11406,7 @@ var init_schema = __esm(() => {
|
|
|
11406
11406
|
webhook_rate_limit: exports_external.object({
|
|
11407
11407
|
rpm: exports_external.number().int().positive()
|
|
11408
11408
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — 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."),
|
|
11409
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
11409
11410
|
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 — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
11410
11411
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11411
11412
|
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 → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
|
@@ -11406,6 +11406,7 @@ var init_schema = __esm(() => {
|
|
|
11406
11406
|
webhook_rate_limit: exports_external.object({
|
|
11407
11407
|
rpm: exports_external.number().int().positive()
|
|
11408
11408
|
}).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default — 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."),
|
|
11409
|
+
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 docs/rfcs/webhook-via-gateway-socket.md."),
|
|
11409
11410
|
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 — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
|
|
11410
11411
|
default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
|
|
11411
11412
|
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 → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.10",
|
|
4
4
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"build": "node scripts/build.mjs",
|
|
24
24
|
"build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
|
|
25
25
|
"pretest": "npm run build",
|
|
26
|
-
"test": "vitest run && bun test telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts",
|
|
26
|
+
"test": "vitest run && bun test telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/registry/api-registry.test.ts telegram-plugin/registry/turns-schema.test.ts telegram-plugin/tests/idle-footer-wiring.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts",
|
|
27
27
|
"test:vitest": "vitest run",
|
|
28
|
-
"test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts",
|
|
28
|
+
"test:bun": "bun test src/watchdog/state.test.ts src/watchdog/policy.test.ts src/vault/grants.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs",
|
|
31
31
|
"lint:tsc": "tsc --noEmit",
|