switchroom 0.18.14 → 0.18.17
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 +3 -0
- package/dist/auth-broker/index.js +473 -49
- package/dist/cli/notion-write-pretool.mjs +3 -0
- package/dist/cli/switchroom.js +1200 -1067
- package/dist/host-control/main.js +56 -51
- package/dist/vault/approvals/kernel-server.js +19 -12
- package/dist/vault/broker/server.js +675 -668
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +81 -139
- package/telegram-plugin/dist/bridge/bridge.js +21 -0
- package/telegram-plugin/dist/gateway/gateway.js +531 -259
- package/telegram-plugin/dist/server.js +22 -1
- package/telegram-plugin/draft-stream.ts +78 -3
- package/telegram-plugin/gateway/bridge-dead-watchdog.ts +3 -4
- package/telegram-plugin/gateway/effort-command.ts +9 -7
- package/telegram-plugin/gateway/gateway.ts +310 -219
- package/telegram-plugin/gateway/litellm-local-notice-wiring.ts +200 -0
- package/telegram-plugin/gateway/model-command.ts +96 -18
- package/telegram-plugin/gateway/pending-session-command.ts +10 -8
- package/telegram-plugin/gateway/session-model-file.ts +38 -172
- package/telegram-plugin/litellm-local-notice.ts +189 -0
- package/telegram-plugin/model-unavailable.ts +214 -0
- package/telegram-plugin/quota-watch.ts +16 -4
- package/telegram-plugin/runtime-metrics.ts +47 -0
- package/telegram-plugin/send-gate-degraded.test.ts +9 -7
- package/telegram-plugin/send-gate.ts +34 -4
- package/telegram-plugin/session-tail.ts +14 -2
- package/telegram-plugin/stream-controller.ts +143 -20
- package/telegram-plugin/stream-reply-handler.ts +12 -2
- package/telegram-plugin/tests/bot-api.harness.ts +7 -2
- package/telegram-plugin/tests/draft-stream.test.ts +110 -1
- package/telegram-plugin/tests/effort-command.test.ts +4 -4
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +2 -2
- package/telegram-plugin/tests/gateway-pending-command-wiring.test.ts +33 -19
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +47 -127
- package/telegram-plugin/tests/litellm-local-notice.test.ts +417 -0
- package/telegram-plugin/tests/model-command.test.ts +84 -1
- package/telegram-plugin/tests/model-unavailable.test.ts +187 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +55 -0
- package/telegram-plugin/tests/quota-watch.test.ts +21 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +2 -2
- package/telegram-plugin/tests/runtime-metrics.test.ts +24 -0
- package/telegram-plugin/tests/session-model-file.test.ts +7 -155
- package/telegram-plugin/tests/stream-controller-send-gate.test.ts +521 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +44 -0
- package/telegram-plugin/tests/throttle-tier.test.ts +176 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +207 -0
- package/telegram-plugin/throttle-tier.ts +98 -1
- package/telegram-plugin/worker-activity-feed.ts +83 -8
|
@@ -18861,6 +18861,9 @@ var TelegramChannelSchema = exports_external.object({
|
|
|
18861
18861
|
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
|
|
18862
18862
|
max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 — a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, …). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
|
|
18863
18863
|
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
18864
|
+
litellm_notice: exports_external.object({
|
|
18865
|
+
window_ms: exports_external.number().int().positive().optional().describe("Per-agent cooldown window (ms) for the litellm-local 429 notice. " + "When the agent trips the LiteLLM proxy's OWN tpm_limit/rpm_limit " + "cap (a `litellm-local` classified 429 — see docs/auth.md § " + "LiteLLM-proxy-local 429s), the gateway posts ONE calm notice " + "naming the fleet token limiter, then counts further hits silently " + "for this long; the first notice after the window expires says " + "how many were absorbed. Default 900000 (15 min). Invalid values " + "fall back to the default.")
|
|
18866
|
+
}).optional().describe("Debounce tuning for the litellm-local throttle notice — the calm " + "'fleet token limiter engaged' message posted when the LiteLLM " + "proxy's own rate cap trips (never an Anthropic account limit). " + "Cascades from defaults.channels.telegram.litellm_notice."),
|
|
18864
18867
|
interrupt: exports_external.object({
|
|
18865
18868
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
18866
18869
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short — the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
@@ -20496,13 +20499,62 @@ async function waitForConfigRecovery(opts) {
|
|
|
20496
20499
|
return config;
|
|
20497
20500
|
}
|
|
20498
20501
|
|
|
20499
|
-
// src/agents/compose.ts
|
|
20500
|
-
import { createHash } from "node:crypto";
|
|
20501
|
-
|
|
20502
20502
|
// src/agents/scaffold.ts
|
|
20503
20503
|
import { join as join5, resolve as resolve5 } from "node:path";
|
|
20504
20504
|
init_atomic();
|
|
20505
20505
|
|
|
20506
|
+
// src/agents/agent-uid.ts
|
|
20507
|
+
import { createHash } from "node:crypto";
|
|
20508
|
+
|
|
20509
|
+
// src/broker-common/peercred-path.ts
|
|
20510
|
+
function matchSocketName(socketPath, patterns, maxNameLen) {
|
|
20511
|
+
if (typeof socketPath !== "string" || socketPath.length === 0)
|
|
20512
|
+
return null;
|
|
20513
|
+
let name = null;
|
|
20514
|
+
for (const re of patterns) {
|
|
20515
|
+
const m = socketPath.match(re);
|
|
20516
|
+
if (m && typeof m[1] === "string") {
|
|
20517
|
+
name = m[1];
|
|
20518
|
+
break;
|
|
20519
|
+
}
|
|
20520
|
+
}
|
|
20521
|
+
if (name === null || name.length === 0)
|
|
20522
|
+
return null;
|
|
20523
|
+
if (maxNameLen !== undefined && name.length > maxNameLen)
|
|
20524
|
+
return null;
|
|
20525
|
+
return name;
|
|
20526
|
+
}
|
|
20527
|
+
function parseSocketIdentity(socketPath, spec) {
|
|
20528
|
+
const name = matchSocketName(socketPath, spec.patterns, spec.maxNameLen);
|
|
20529
|
+
if (name === null)
|
|
20530
|
+
return null;
|
|
20531
|
+
const operatorName = spec.operatorName ?? "operator";
|
|
20532
|
+
if (name === operatorName)
|
|
20533
|
+
return { kind: "operator" };
|
|
20534
|
+
if (spec.reservedNames.has(name))
|
|
20535
|
+
return null;
|
|
20536
|
+
return { kind: "agent", name };
|
|
20537
|
+
}
|
|
20538
|
+
|
|
20539
|
+
// src/vault/broker/peercred.ts
|
|
20540
|
+
var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
20541
|
+
function isReservedAgentName(name) {
|
|
20542
|
+
return RESERVED_AGENT_NAMES.has(name);
|
|
20543
|
+
}
|
|
20544
|
+
|
|
20545
|
+
// src/agents/agent-uid.ts
|
|
20546
|
+
var AGENT_UID_MIN = 10001;
|
|
20547
|
+
var AGENT_UID_MAX = 10999;
|
|
20548
|
+
function allocateAgentUid(name) {
|
|
20549
|
+
if (isReservedAgentName(name)) {
|
|
20550
|
+
throw new Error(`agent name '${name}' is reserved by switchroom for another identity kind ` + `(see vault/broker/peercred.ts:RESERVED_AGENT_NAMES). Pick a different name.`);
|
|
20551
|
+
}
|
|
20552
|
+
const hash = createHash("sha256").update(name).digest();
|
|
20553
|
+
const u32 = hash.readUInt32BE(0);
|
|
20554
|
+
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
20555
|
+
return AGENT_UID_MIN + u32 % range;
|
|
20556
|
+
}
|
|
20557
|
+
|
|
20506
20558
|
// src/config/timezone.ts
|
|
20507
20559
|
var CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
20508
20560
|
"UTC",
|
|
@@ -20648,42 +20700,6 @@ import * as fs from "node:fs";
|
|
|
20648
20700
|
import { homedir as homedir3 } from "node:os";
|
|
20649
20701
|
import { join as join4 } from "node:path";
|
|
20650
20702
|
|
|
20651
|
-
// src/broker-common/peercred-path.ts
|
|
20652
|
-
function matchSocketName(socketPath, patterns, maxNameLen) {
|
|
20653
|
-
if (typeof socketPath !== "string" || socketPath.length === 0)
|
|
20654
|
-
return null;
|
|
20655
|
-
let name = null;
|
|
20656
|
-
for (const re of patterns) {
|
|
20657
|
-
const m = socketPath.match(re);
|
|
20658
|
-
if (m && typeof m[1] === "string") {
|
|
20659
|
-
name = m[1];
|
|
20660
|
-
break;
|
|
20661
|
-
}
|
|
20662
|
-
}
|
|
20663
|
-
if (name === null || name.length === 0)
|
|
20664
|
-
return null;
|
|
20665
|
-
if (maxNameLen !== undefined && name.length > maxNameLen)
|
|
20666
|
-
return null;
|
|
20667
|
-
return name;
|
|
20668
|
-
}
|
|
20669
|
-
function parseSocketIdentity(socketPath, spec) {
|
|
20670
|
-
const name = matchSocketName(socketPath, spec.patterns, spec.maxNameLen);
|
|
20671
|
-
if (name === null)
|
|
20672
|
-
return null;
|
|
20673
|
-
const operatorName = spec.operatorName ?? "operator";
|
|
20674
|
-
if (name === operatorName)
|
|
20675
|
-
return { kind: "operator" };
|
|
20676
|
-
if (spec.reservedNames.has(name))
|
|
20677
|
-
return null;
|
|
20678
|
-
return { kind: "agent", name };
|
|
20679
|
-
}
|
|
20680
|
-
|
|
20681
|
-
// src/vault/broker/peercred.ts
|
|
20682
|
-
var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
20683
|
-
function isReservedAgentName(name) {
|
|
20684
|
-
return RESERVED_AGENT_NAMES.has(name);
|
|
20685
|
-
}
|
|
20686
|
-
|
|
20687
20703
|
// src/vault/broker/protocol.ts
|
|
20688
20704
|
var MAX_FRAME_BYTES = 64 * 1024;
|
|
20689
20705
|
var AgentNameSchema = exports_external.string().min(1).max(64, "agent name max 64 chars").regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII (alnum + _- only, first char alnum)").refine((s) => !isReservedAgentName(s), {
|
|
@@ -21131,17 +21147,6 @@ var DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin";
|
|
|
21131
21147
|
var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
21132
21148
|
|
|
21133
21149
|
// src/agents/compose.ts
|
|
21134
|
-
var AGENT_UID_MIN = 10001;
|
|
21135
|
-
var AGENT_UID_MAX = 10999;
|
|
21136
|
-
function allocateAgentUid(name) {
|
|
21137
|
-
if (isReservedAgentName(name)) {
|
|
21138
|
-
throw new Error(`agent name '${name}' is reserved by switchroom for another identity kind ` + `(see vault/broker/peercred.ts:RESERVED_AGENT_NAMES). Pick a different name.`);
|
|
21139
|
-
}
|
|
21140
|
-
const hash = createHash("sha256").update(name).digest();
|
|
21141
|
-
const u32 = hash.readUInt32BE(0);
|
|
21142
|
-
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
21143
|
-
return AGENT_UID_MIN + u32 % range;
|
|
21144
|
-
}
|
|
21145
21150
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
21146
21151
|
|
|
21147
21152
|
// src/host-control/server.ts
|
|
@@ -26587,7 +26592,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
|
|
|
26587
26592
|
import { dirname as dirname4, join as join7 } from "node:path";
|
|
26588
26593
|
|
|
26589
26594
|
// src/build-info.ts
|
|
26590
|
-
var VERSION = "0.18.
|
|
26595
|
+
var VERSION = "0.18.17";
|
|
26591
26596
|
|
|
26592
26597
|
// src/cli/resolve-version.ts
|
|
26593
26598
|
function readPackageVersion() {
|
|
@@ -4281,6 +4281,9 @@ var init_schema = __esm(() => {
|
|
|
4281
4281
|
window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
|
|
4282
4282
|
max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 — a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, …). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
|
|
4283
4283
|
}).optional().describe("Inbound coalescing — how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
|
|
4284
|
+
litellm_notice: exports_external.object({
|
|
4285
|
+
window_ms: exports_external.number().int().positive().optional().describe("Per-agent cooldown window (ms) for the litellm-local 429 notice. " + "When the agent trips the LiteLLM proxy's OWN tpm_limit/rpm_limit " + "cap (a `litellm-local` classified 429 — see docs/auth.md § " + "LiteLLM-proxy-local 429s), the gateway posts ONE calm notice " + "naming the fleet token limiter, then counts further hits silently " + "for this long; the first notice after the window expires says " + "how many were absorbed. Default 900000 (15 min). Invalid values " + "fall back to the default.")
|
|
4286
|
+
}).optional().describe("Debounce tuning for the litellm-local throttle notice — the calm " + "'fleet token limiter engaged' message posted when the LiteLLM " + "proxy's own rate cap trips (never an Anthropic account limit). " + "Cascades from defaults.channels.telegram.litellm_notice."),
|
|
4284
4287
|
interrupt: exports_external.object({
|
|
4285
4288
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
4286
4289
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short — the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
@@ -18893,12 +18896,27 @@ function getNonce(db, request_id) {
|
|
|
18893
18896
|
}
|
|
18894
18897
|
|
|
18895
18898
|
// src/agents/compose.ts
|
|
18896
|
-
import { createHash } from "node:crypto";
|
|
18897
18899
|
init_schema();
|
|
18898
18900
|
|
|
18899
18901
|
// src/agents/scaffold.ts
|
|
18900
18902
|
import { join as join4, resolve as resolve5 } from "node:path";
|
|
18901
18903
|
init_atomic();
|
|
18904
|
+
|
|
18905
|
+
// src/agents/agent-uid.ts
|
|
18906
|
+
import { createHash } from "node:crypto";
|
|
18907
|
+
var AGENT_UID_MIN = 10001;
|
|
18908
|
+
var AGENT_UID_MAX = 10999;
|
|
18909
|
+
function allocateAgentUid(name) {
|
|
18910
|
+
if (isReservedAgentName(name)) {
|
|
18911
|
+
throw new Error(`agent name '${name}' is reserved by switchroom for another identity kind ` + `(see vault/broker/peercred.ts:RESERVED_AGENT_NAMES). Pick a different name.`);
|
|
18912
|
+
}
|
|
18913
|
+
const hash = createHash("sha256").update(name).digest();
|
|
18914
|
+
const u32 = hash.readUInt32BE(0);
|
|
18915
|
+
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
18916
|
+
return AGENT_UID_MIN + u32 % range;
|
|
18917
|
+
}
|
|
18918
|
+
|
|
18919
|
+
// src/agents/scaffold.ts
|
|
18902
18920
|
init_schema();
|
|
18903
18921
|
|
|
18904
18922
|
// src/config/users.ts
|
|
@@ -19100,17 +19118,6 @@ init_merge();
|
|
|
19100
19118
|
init_paths();
|
|
19101
19119
|
|
|
19102
19120
|
// src/agents/compose.ts
|
|
19103
|
-
var AGENT_UID_MIN = 10001;
|
|
19104
|
-
var AGENT_UID_MAX = 10999;
|
|
19105
|
-
function allocateAgentUid(name) {
|
|
19106
|
-
if (isReservedAgentName(name)) {
|
|
19107
|
-
throw new Error(`agent name '${name}' is reserved by switchroom for another identity kind ` + `(see vault/broker/peercred.ts:RESERVED_AGENT_NAMES). Pick a different name.`);
|
|
19108
|
-
}
|
|
19109
|
-
const hash = createHash("sha256").update(name).digest();
|
|
19110
|
-
const u32 = hash.readUInt32BE(0);
|
|
19111
|
-
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
19112
|
-
return AGENT_UID_MIN + u32 % range;
|
|
19113
|
-
}
|
|
19114
19121
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
19115
19122
|
|
|
19116
19123
|
// src/vault/approvals/acl.ts
|