switchroom 0.16.24 → 0.16.28
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/cli/switchroom.js +135 -37
- package/dist/host-control/main.js +13 -7
- package/package.json +2 -2
- package/telegram-plugin/answer-stream.ts +7 -6
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +255 -30
- package/telegram-plugin/dist/server.js +1 -1
- package/telegram-plugin/format.ts +335 -27
- package/telegram-plugin/gateway/drive-write-approval.test.ts +10 -10
- package/telegram-plugin/gateway/drive-write-approval.ts +14 -8
- package/telegram-plugin/gateway/gateway.ts +169 -12
- package/telegram-plugin/gateway/ipc-server.ts +11 -6
- package/telegram-plugin/gateway/permission-timeout.ts +76 -0
- package/telegram-plugin/permission-title.ts +3 -0
- package/telegram-plugin/retry-api-call.ts +27 -0
- package/telegram-plugin/rich-send.ts +29 -0
- package/telegram-plugin/shared/bot-runtime.ts +6 -1
- package/telegram-plugin/silent-reply-anchor.ts +9 -2
- package/telegram-plugin/status-no-truncate.ts +11 -5
- package/telegram-plugin/stream-reply-handler.ts +9 -1
- package/telegram-plugin/tests/ipc-server-validate-send-outbound.test.ts +6 -2
- package/telegram-plugin/tests/length-error-classify.test.ts +131 -0
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +273 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +12 -2
- package/telegram-plugin/tests/permission-timeout.test.ts +77 -0
- package/telegram-plugin/tests/permission-title.test.ts +43 -0
- package/telegram-plugin/tests/poll-health.test.ts +64 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -29230,7 +29230,8 @@ var init_protocol3 = __esm(() => {
|
|
|
29230
29230
|
skip_images: exports_external.boolean().optional(),
|
|
29231
29231
|
rebuild: exports_external.boolean().optional(),
|
|
29232
29232
|
channel: exports_external.enum(["dev", "rc", "latest"]).nullable().optional(),
|
|
29233
|
-
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional()
|
|
29233
|
+
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional(),
|
|
29234
|
+
reason: exports_external.string().max(512).optional()
|
|
29234
29235
|
}).optional()
|
|
29235
29236
|
});
|
|
29236
29237
|
ApplyRequestSchema = exports_external.object({
|
|
@@ -29245,21 +29246,24 @@ var init_protocol3 = __esm(() => {
|
|
|
29245
29246
|
pin: exports_external.string().regex(/^v\d+\.\d+\.\d+$/),
|
|
29246
29247
|
agents: exports_external.array(AgentNameSchema2).min(1).optional(),
|
|
29247
29248
|
skip_web: exports_external.boolean().optional(),
|
|
29248
|
-
allow_downgrade: exports_external.boolean().optional()
|
|
29249
|
+
allow_downgrade: exports_external.boolean().optional(),
|
|
29250
|
+
reason: exports_external.string().max(512).optional()
|
|
29249
29251
|
}).required({ pin: true })
|
|
29250
29252
|
});
|
|
29251
29253
|
AgentStartRequestSchema = exports_external.object({
|
|
29252
29254
|
...RequestEnvelope,
|
|
29253
29255
|
op: exports_external.literal("agent_start"),
|
|
29254
29256
|
args: exports_external.object({
|
|
29255
|
-
name: AgentNameSchema2
|
|
29257
|
+
name: AgentNameSchema2,
|
|
29258
|
+
reason: exports_external.string().max(512).optional()
|
|
29256
29259
|
})
|
|
29257
29260
|
});
|
|
29258
29261
|
AgentStopRequestSchema = exports_external.object({
|
|
29259
29262
|
...RequestEnvelope,
|
|
29260
29263
|
op: exports_external.literal("agent_stop"),
|
|
29261
29264
|
args: exports_external.object({
|
|
29262
|
-
name: AgentNameSchema2
|
|
29265
|
+
name: AgentNameSchema2,
|
|
29266
|
+
reason: exports_external.string().max(512).optional()
|
|
29263
29267
|
})
|
|
29264
29268
|
});
|
|
29265
29269
|
AgentLogsRequestSchema = exports_external.object({
|
|
@@ -29267,7 +29271,8 @@ var init_protocol3 = __esm(() => {
|
|
|
29267
29271
|
op: exports_external.literal("agent_logs"),
|
|
29268
29272
|
args: exports_external.object({
|
|
29269
29273
|
name: AgentNameSchema2,
|
|
29270
|
-
tail: exports_external.number().int().positive().max(2000).optional()
|
|
29274
|
+
tail: exports_external.number().int().positive().max(2000).optional(),
|
|
29275
|
+
reason: exports_external.string().max(512).optional()
|
|
29271
29276
|
})
|
|
29272
29277
|
});
|
|
29273
29278
|
AgentExecRequestSchema = exports_external.object({
|
|
@@ -29275,7 +29280,8 @@ var init_protocol3 = __esm(() => {
|
|
|
29275
29280
|
op: exports_external.literal("agent_exec"),
|
|
29276
29281
|
args: exports_external.object({
|
|
29277
29282
|
name: AgentNameSchema2,
|
|
29278
|
-
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32)
|
|
29283
|
+
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
29284
|
+
reason: exports_external.string().max(512).optional()
|
|
29279
29285
|
})
|
|
29280
29286
|
});
|
|
29281
29287
|
DoctorRequestSchema = exports_external.object({
|
|
@@ -51263,7 +51269,10 @@ async function dispatchTool2(name, args) {
|
|
|
51263
51269
|
v: 1,
|
|
51264
51270
|
op: "agent_start",
|
|
51265
51271
|
request_id: makeRequestId("mcp-start"),
|
|
51266
|
-
args: {
|
|
51272
|
+
args: {
|
|
51273
|
+
name: args.name,
|
|
51274
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51275
|
+
}
|
|
51267
51276
|
};
|
|
51268
51277
|
break;
|
|
51269
51278
|
}
|
|
@@ -51274,7 +51283,10 @@ async function dispatchTool2(name, args) {
|
|
|
51274
51283
|
v: 1,
|
|
51275
51284
|
op: "agent_stop",
|
|
51276
51285
|
request_id: makeRequestId("mcp-stop"),
|
|
51277
|
-
args: {
|
|
51286
|
+
args: {
|
|
51287
|
+
name: args.name,
|
|
51288
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51289
|
+
}
|
|
51278
51290
|
};
|
|
51279
51291
|
break;
|
|
51280
51292
|
}
|
|
@@ -51287,7 +51299,8 @@ async function dispatchTool2(name, args) {
|
|
|
51287
51299
|
request_id: makeRequestId("mcp-logs"),
|
|
51288
51300
|
args: {
|
|
51289
51301
|
name: args.name,
|
|
51290
|
-
...typeof args.tail === "number" ? { tail: args.tail } : {}
|
|
51302
|
+
...typeof args.tail === "number" ? { tail: args.tail } : {},
|
|
51303
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51291
51304
|
}
|
|
51292
51305
|
};
|
|
51293
51306
|
break;
|
|
@@ -51302,7 +51315,11 @@ async function dispatchTool2(name, args) {
|
|
|
51302
51315
|
v: 1,
|
|
51303
51316
|
op: "agent_exec",
|
|
51304
51317
|
request_id: makeRequestId("mcp-exec"),
|
|
51305
|
-
args: {
|
|
51318
|
+
args: {
|
|
51319
|
+
name: args.name,
|
|
51320
|
+
argv: args.argv,
|
|
51321
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51322
|
+
}
|
|
51306
51323
|
};
|
|
51307
51324
|
break;
|
|
51308
51325
|
}
|
|
@@ -51329,7 +51346,8 @@ async function dispatchTool2(name, args) {
|
|
|
51329
51346
|
...args.skip_images ? { skip_images: true } : {},
|
|
51330
51347
|
...args.rebuild ? { rebuild: true } : {},
|
|
51331
51348
|
...args.channel ? { channel: args.channel } : {},
|
|
51332
|
-
...args.pin ? { pin: args.pin } : {}
|
|
51349
|
+
...args.pin ? { pin: args.pin } : {},
|
|
51350
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51333
51351
|
}
|
|
51334
51352
|
};
|
|
51335
51353
|
break;
|
|
@@ -51352,7 +51370,8 @@ async function dispatchTool2(name, args) {
|
|
|
51352
51370
|
pin: args.pin,
|
|
51353
51371
|
...args.agents ? { agents: args.agents } : {},
|
|
51354
51372
|
...args.skip_web ? { skip_web: true } : {},
|
|
51355
|
-
...args.allow_downgrade ? { allow_downgrade: true } : {}
|
|
51373
|
+
...args.allow_downgrade ? { allow_downgrade: true } : {},
|
|
51374
|
+
...args.reason ? { reason: args.reason } : {}
|
|
51356
51375
|
}
|
|
51357
51376
|
};
|
|
51358
51377
|
break;
|
|
@@ -51503,11 +51522,16 @@ var init_server4 = __esm(() => {
|
|
|
51503
51522
|
},
|
|
51504
51523
|
{
|
|
51505
51524
|
name: "agent_start",
|
|
51506
|
-
description: "Start a stopped agent. Self-targeting allowed; cross-agent " + "requires admin. Equivalent to `switchroom agent start <name>`.",
|
|
51525
|
+
description: "Start a stopped agent. Self-targeting allowed; cross-agent " + "requires admin. Equivalent to `switchroom agent start <name>`. " + "This is operator-gated \u2014 every call surfaces a Telegram approval " + "card. ALWAYS pass a one-line `reason` explaining why you're " + "starting this agent; it renders on the card's `why:` line so the " + "operator can decide in context.",
|
|
51507
51526
|
inputSchema: {
|
|
51508
51527
|
type: "object",
|
|
51509
51528
|
required: ["name"],
|
|
51510
51529
|
properties: {
|
|
51530
|
+
reason: {
|
|
51531
|
+
type: "string",
|
|
51532
|
+
maxLength: 512,
|
|
51533
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51534
|
+
},
|
|
51511
51535
|
name: {
|
|
51512
51536
|
type: "string",
|
|
51513
51537
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
|
|
@@ -51517,11 +51541,16 @@ var init_server4 = __esm(() => {
|
|
|
51517
51541
|
},
|
|
51518
51542
|
{
|
|
51519
51543
|
name: "agent_stop",
|
|
51520
|
-
description: "Stop a running agent. Self-targeting allowed; cross-agent " + "requires admin. Equivalent to `switchroom agent stop <name>`.",
|
|
51544
|
+
description: "Stop a running agent. Self-targeting allowed; cross-agent " + "requires admin. Equivalent to `switchroom agent stop <name>`. " + "This is operator-gated \u2014 every call surfaces a Telegram approval " + "card. ALWAYS pass a one-line `reason` explaining why you're " + "stopping this agent; it renders on the card's `why:` line so the " + "operator can decide in context.",
|
|
51521
51545
|
inputSchema: {
|
|
51522
51546
|
type: "object",
|
|
51523
51547
|
required: ["name"],
|
|
51524
51548
|
properties: {
|
|
51549
|
+
reason: {
|
|
51550
|
+
type: "string",
|
|
51551
|
+
maxLength: 512,
|
|
51552
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51553
|
+
},
|
|
51525
51554
|
name: {
|
|
51526
51555
|
type: "string",
|
|
51527
51556
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
|
|
@@ -51539,11 +51568,16 @@ var init_server4 = __esm(() => {
|
|
|
51539
51568
|
},
|
|
51540
51569
|
{
|
|
51541
51570
|
name: "agent_logs",
|
|
51542
|
-
description: "Read recent docker logs of any peer agent. Self-target is " + "always allowed; cross-agent requires admin: true on the caller. " + "Returns the trailing `tail` lines (default 100, max 2000) as " + "`stdout_tail` / `stderr_tail` (each capped at 4 KiB). Use this " + "for triage when a user reports a peer agent is misbehaving.",
|
|
51571
|
+
description: "Read recent docker logs of any peer agent. Self-target is " + "always allowed; cross-agent requires admin: true on the caller. " + "Returns the trailing `tail` lines (default 100, max 2000) as " + "`stdout_tail` / `stderr_tail` (each capped at 4 KiB). Use this " + "for triage when a user reports a peer agent is misbehaving. " + "This is operator-gated \u2014 every call surfaces a Telegram approval " + "card. ALWAYS pass a one-line `reason` explaining what you're " + "triaging; it renders on the card's `why:` line.",
|
|
51543
51572
|
inputSchema: {
|
|
51544
51573
|
type: "object",
|
|
51545
51574
|
required: ["name"],
|
|
51546
51575
|
properties: {
|
|
51576
|
+
reason: {
|
|
51577
|
+
type: "string",
|
|
51578
|
+
maxLength: 512,
|
|
51579
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51580
|
+
},
|
|
51547
51581
|
name: {
|
|
51548
51582
|
type: "string",
|
|
51549
51583
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]*$",
|
|
@@ -51558,11 +51592,16 @@ var init_server4 = __esm(() => {
|
|
|
51558
51592
|
},
|
|
51559
51593
|
{
|
|
51560
51594
|
name: "agent_exec",
|
|
51561
|
-
description: "Run a read-only inspection command inside a peer agent's " + "container via `docker exec`. Self-target allowed; cross-agent " + "requires admin: true. argv[0] must be on the daemon's read-only " + "allowlist (cat, df, du, free, grep, head, hostname, id, " + "ls, ps, pwd, stat, tail, uname, uptime, wc, whoami). Anything " + "outside the allowlist returns `denied` with a pointer to the " + "deferred host_os.exec approval-kernel scope. Returns stdout/" + "stderr tails capped at 4 KiB each.",
|
|
51595
|
+
description: "Run a read-only inspection command inside a peer agent's " + "container via `docker exec`. Self-target allowed; cross-agent " + "requires admin: true. argv[0] must be on the daemon's read-only " + "allowlist (cat, df, du, free, grep, head, hostname, id, " + "ls, ps, pwd, stat, tail, uname, uptime, wc, whoami). Anything " + "outside the allowlist returns `denied` with a pointer to the " + "deferred host_os.exec approval-kernel scope. Returns stdout/" + "stderr tails capped at 4 KiB each. This is operator-gated \u2014 every " + "call surfaces a Telegram approval card. ALWAYS pass a one-line " + "`reason` explaining why you're running this inspection; it renders " + "on the card's `why:` line so the operator can decide in context.",
|
|
51562
51596
|
inputSchema: {
|
|
51563
51597
|
type: "object",
|
|
51564
51598
|
required: ["name", "argv"],
|
|
51565
51599
|
properties: {
|
|
51600
|
+
reason: {
|
|
51601
|
+
type: "string",
|
|
51602
|
+
maxLength: 512,
|
|
51603
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51604
|
+
},
|
|
51566
51605
|
name: {
|
|
51567
51606
|
type: "string",
|
|
51568
51607
|
pattern: "^[a-zA-Z0-9][a-zA-Z0-9_-]*$"
|
|
@@ -51579,10 +51618,15 @@ var init_server4 = __esm(() => {
|
|
|
51579
51618
|
},
|
|
51580
51619
|
{
|
|
51581
51620
|
name: "update_apply",
|
|
51582
|
-
description: "Execute a fleet-wide update: pull images, regenerate " + "scaffolds, recreate containers. Admin-only at the wire layer. " + "Returns `started` once dispatched \u2014 the actual work runs " + "async on the host and the caller's own agent container will " + "be recreated as part of the cycle.",
|
|
51621
|
+
description: "Execute a fleet-wide update: pull images, regenerate " + "scaffolds, recreate containers. Admin-only at the wire layer. " + "Returns `started` once dispatched \u2014 the actual work runs " + "async on the host and the caller's own agent container will " + "be recreated as part of the cycle. This is operator-gated \u2014 every " + "call surfaces a Telegram approval card. ALWAYS pass a one-line " + "`reason` explaining why you're applying this update; it renders on " + "the card's `why:` line so the operator can decide in context.",
|
|
51583
51622
|
inputSchema: {
|
|
51584
51623
|
type: "object",
|
|
51585
51624
|
properties: {
|
|
51625
|
+
reason: {
|
|
51626
|
+
type: "string",
|
|
51627
|
+
maxLength: 512,
|
|
51628
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51629
|
+
},
|
|
51586
51630
|
skip_images: {
|
|
51587
51631
|
type: "boolean",
|
|
51588
51632
|
description: "Skip the `docker compose pull` step. Useful when local " + "images are already at the desired tag."
|
|
@@ -51606,11 +51650,16 @@ var init_server4 = __esm(() => {
|
|
|
51606
51650
|
},
|
|
51607
51651
|
{
|
|
51608
51652
|
name: "rollout",
|
|
51609
|
-
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. The hostd/web self-refresh is DEFERRED on this path " + "(an agent-invoked rollout cannot recreate its own hostd container " + "without killing itself); run that host-side. By default downgrade pins " + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "Returns `started`; poll get_status for the structured outcome " + "(which agents rolled / where it stopped).",
|
|
51653
|
+
description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. The hostd/web self-refresh is DEFERRED on this path " + "(an agent-invoked rollout cannot recreate its own hostd container " + "without killing itself); run that host-side. By default downgrade pins " + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "ALWAYS pass a one-line `reason` explaining why you're rolling the " + "fleet to this pin; it renders on the card's `why:` line so the " + "operator can decide in context. " + "Returns `started`; poll get_status for the structured outcome " + "(which agents rolled / where it stopped).",
|
|
51610
51654
|
inputSchema: {
|
|
51611
51655
|
type: "object",
|
|
51612
51656
|
required: ["pin"],
|
|
51613
51657
|
properties: {
|
|
51658
|
+
reason: {
|
|
51659
|
+
type: "string",
|
|
51660
|
+
maxLength: 512,
|
|
51661
|
+
description: "One-line operator-facing rationale, rendered on the " + "approval card's `why:` line."
|
|
51662
|
+
},
|
|
51614
51663
|
pin: {
|
|
51615
51664
|
type: "string",
|
|
51616
51665
|
pattern: "^v\\d+\\.\\d+\\.\\d+$",
|
|
@@ -51692,8 +51741,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
51692
51741
|
import { dirname, join } from "node:path";
|
|
51693
51742
|
|
|
51694
51743
|
// src/build-info.ts
|
|
51695
|
-
var VERSION = "0.16.
|
|
51696
|
-
var COMMIT_SHA = "
|
|
51744
|
+
var VERSION = "0.16.28";
|
|
51745
|
+
var COMMIT_SHA = "08b2121e";
|
|
51697
51746
|
|
|
51698
51747
|
// src/cli/resolve-version.ts
|
|
51699
51748
|
function readPackageVersion() {
|
|
@@ -53099,8 +53148,10 @@ value back into chat.
|
|
|
53099
53148
|
|
|
53100
53149
|
### Formatting \u2014 make it scannable
|
|
53101
53150
|
|
|
53102
|
-
\`reply\` and \`stream_reply\` render Markdown
|
|
53103
|
-
\`**bold**\` becomes bold and backtick-wrapped text becomes monospace.
|
|
53151
|
+
\`reply\` and \`stream_reply\` render rich Markdown (Bot API 10.1) for you, so
|
|
53152
|
+
\`**bold**\` becomes bold and backtick-wrapped text becomes monospace. Your full
|
|
53153
|
+
formatting floor card \u2014 the toolkit and when to reach for each \u2014 is injected
|
|
53154
|
+
into your system prompt every session; this is the conversational summary.
|
|
53104
53155
|
|
|
53105
53156
|
- **A one- or two-line conversational reply needs almost no markup.** Keep
|
|
53106
53157
|
bold for the single fact that matters, never for decoration. "on it, pulling
|
|
@@ -53122,11 +53173,9 @@ value back into chat.
|
|
|
53122
53173
|
\u2022 flag anything user-facing
|
|
53123
53174
|
|
|
53124
53175
|
**Back in** ~2 min with a synthesized summary.
|
|
53125
|
-
-
|
|
53126
|
-
|
|
53127
|
-
|
|
53128
|
-
instead (\`**Blockers**\`, not \`## Blockers\`). Keep lines short; long
|
|
53129
|
-
unwrapped lines are hard to read on a phone.
|
|
53176
|
+
- Use backtick-wrapped \`inline code\` for filenames, commands, and
|
|
53177
|
+
identifiers. Keep lines short; long unwrapped lines are hard to read on a
|
|
53178
|
+
phone.
|
|
53130
53179
|
|
|
53131
53180
|
Every turn that answers a user message ends with a user-visible
|
|
53132
53181
|
\`reply\` (or \`stream_reply\` done=true) \u2014 Telegram is all the user
|
|
@@ -53211,6 +53260,47 @@ belongs in plain config, not the vault. If a tool call is **blocked for
|
|
|
53211
53260
|
containing a vaulted secret**, report that exact block message to the
|
|
53212
53261
|
operator and stop \u2014 don't theorise about auth or tokens; it just means
|
|
53213
53262
|
a stored value reached a tool argument.`;
|
|
53263
|
+
var TELEGRAM_FORMATTING_FLOOR_CARD = `## Formatting for Telegram
|
|
53264
|
+
|
|
53265
|
+
You're writing for a phone screen in Telegram. Every reply renders as rich Markdown
|
|
53266
|
+
(Bot API 10.1). Format to make the message **scannable and easy to read**, not
|
|
53267
|
+
decorated. These are communication tools \u2014 use them with judgment. Most short replies
|
|
53268
|
+
need none of them.
|
|
53269
|
+
|
|
53270
|
+
### Mechanical rules (always)
|
|
53271
|
+
- Separate paragraphs with a blank line (\`\\n\\n\`). A single newline collapses onto the
|
|
53272
|
+
same line and reads as a wall of text.
|
|
53273
|
+
- Keep paragraphs short \u2014 1 to 4 lines. Break before the reader has to work for it.
|
|
53274
|
+
- Hard cap is 32768 characters. Long before that, ask whether a wall of text is the
|
|
53275
|
+
right answer at all.
|
|
53276
|
+
|
|
53277
|
+
### The toolkit, and when to reach for each
|
|
53278
|
+
- **Bold** \u2014 the one key fact or the answer. Not every noun. If everything is bold,
|
|
53279
|
+
nothing is.
|
|
53280
|
+
- *Italic* \u2014 light emphasis, asides, labels.
|
|
53281
|
+
- \`code spans\` \u2014 every identifier: filenames, commands, config keys, agent / account /
|
|
53282
|
+
slot names, error codes. Tap-to-copy, and visually distinct from prose.
|
|
53283
|
+
- Code fences \u2014 multi-line output: diffs, logs, command blocks, JSON. Add a language
|
|
53284
|
+
hint (\` \`\`\`diff \`, \` \`\`\`json \`) when it sharpens the render.
|
|
53285
|
+
- Bulleted list \u2014 3+ parallel items the reader will scan or compare. Never for a single
|
|
53286
|
+
thought or a flowing narrative.
|
|
53287
|
+
- Numbered list \u2014 ordered steps or ranked items.
|
|
53288
|
+
- Tables \u2014 2-D data only (rows \u00d7 columns): per-account usage, status fields. Overkill
|
|
53289
|
+
for a flat list.
|
|
53290
|
+
- Headings \u2014 only in a long, multi-section answer. Clutter on a short reply.
|
|
53291
|
+
- Blockquotes (\`>\`) \u2014 quoted text or indented continuation. Telegram drops leading
|
|
53292
|
+
spaces, so use \`>\`, never literal indentation.
|
|
53293
|
+
- Dividers (\`---\`) \u2014 between genuinely separate sections. Heavy; use sparingly.
|
|
53294
|
+
|
|
53295
|
+
### The why
|
|
53296
|
+
Structure exists for the reader, not the writer. A two-item bullet list is worse than a
|
|
53297
|
+
sentence. A heading on a three-line reply is noise. Reach for structure only when it
|
|
53298
|
+
**reduces the reader's effort** \u2014 parallel options, scannable data, ordered steps \u2014 and
|
|
53299
|
+
stay in prose when the thought is connected. When in doubt: shorter and plainer wins.
|
|
53300
|
+
|
|
53301
|
+
Every turn that answers a user message ends with a user-visible \`reply\` (or
|
|
53302
|
+
\`stream_reply\` done=true) \u2014 Telegram is all the user sees; your terminal output
|
|
53303
|
+
never reaches them.`;
|
|
53214
53304
|
function renderFleetInvariants() {
|
|
53215
53305
|
return [
|
|
53216
53306
|
"<!--",
|
|
@@ -54148,7 +54238,10 @@ function buildWorkspaceContext(args) {
|
|
|
54148
54238
|
})(),
|
|
54149
54239
|
systemPromptAppendShellQuoted: (() => {
|
|
54150
54240
|
const baseAppend = agentConfig.system_prompt_append ?? "";
|
|
54151
|
-
|
|
54241
|
+
const combined = baseAppend.length > 0 ? `${TELEGRAM_FORMATTING_FLOOR_CARD}
|
|
54242
|
+
|
|
54243
|
+
${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
54244
|
+
return shellSingleQuote(combined);
|
|
54152
54245
|
})(),
|
|
54153
54246
|
extraCliArgs: (() => {
|
|
54154
54247
|
const parts = [];
|
|
@@ -55261,7 +55354,10 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
|
|
|
55261
55354
|
model: agentConfig.model,
|
|
55262
55355
|
systemPromptAppendShellQuoted: (() => {
|
|
55263
55356
|
const baseAppend = agentConfig.system_prompt_append ?? "";
|
|
55264
|
-
|
|
55357
|
+
const combined = baseAppend.length > 0 ? `${TELEGRAM_FORMATTING_FLOOR_CARD}
|
|
55358
|
+
|
|
55359
|
+
${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
55360
|
+
return shellSingleQuote(combined);
|
|
55265
55361
|
})(),
|
|
55266
55362
|
extraCliArgs: (() => {
|
|
55267
55363
|
const parts = [];
|
|
@@ -78047,7 +78143,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
78047
78143
|
break;
|
|
78048
78144
|
}
|
|
78049
78145
|
case "session.status": {
|
|
78050
|
-
const sessionId = String(params.session_id ?? "");
|
|
78146
|
+
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
78051
78147
|
if (!isKnownSession(config, sessionId)) {
|
|
78052
78148
|
sendResponse(ctx, rpcErr(id, -32602, `Unknown session: ${sessionId}`));
|
|
78053
78149
|
break;
|
|
@@ -86658,17 +86754,11 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
86658
86754
|
]);
|
|
86659
86755
|
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir46());
|
|
86660
86756
|
if (passphrase === null) {
|
|
86661
|
-
for (const { name } of optedIn) {
|
|
86662
|
-
failures.push({
|
|
86663
|
-
agent: name,
|
|
86664
|
-
message: `litellm: cannot write virtual key \u2014 no operator vault passphrase available ` + `(set SWITCHROOM_VAULT_PASSPHRASE, or enable auto-unlock with 'switchroom vault broker enable-auto-unlock'). The broker refuses new keys without operator attestation.`
|
|
86665
|
-
});
|
|
86666
|
-
}
|
|
86667
86757
|
const targets = [
|
|
86668
86758
|
...optedIn.map(({ name }) => name),
|
|
86669
86759
|
...needsHindsight ? ["hindsight (service)"] : []
|
|
86670
86760
|
];
|
|
86671
|
-
ctx.writeErr(source_default.
|
|
86761
|
+
ctx.writeErr(source_default.yellow(` \u26a0 litellm: vault passphrase unavailable \u2014 skipping new-key provisioning for ` + `${targets.join(", ")}. Already-provisioned keys are unaffected; run \`switchroom apply\` interactively to provision new agents.
|
|
86672
86762
|
`));
|
|
86673
86763
|
return;
|
|
86674
86764
|
}
|
|
@@ -90926,6 +91016,14 @@ services:
|
|
|
90926
91016
|
# have it, but hostd (auditing every shell-out via run-hook.sh's
|
|
90927
91017
|
# pattern) is the controlled chokepoint.
|
|
90928
91018
|
- /var/run/docker.sock:/var/run/docker.sock:rw
|
|
91019
|
+
# /etc/machine-id passthrough \u2014 the vault auto-unlock blob
|
|
91020
|
+
# (~/.switchroom/vault-auto-unlock) is encrypted with a key derived
|
|
91021
|
+
# from the host machine-id. Without this mount, readAutoUnlockFile
|
|
91022
|
+
# inside switchroom apply (called by rollout) cannot derive the
|
|
91023
|
+
# decryption key, resolveOperatorVaultPassphrase returns null, LiteLLM
|
|
91024
|
+
# provisioning fails for all agents, and rollout stops at apply.
|
|
91025
|
+
# Same mount the vault-broker compose carries (compose.ts:1364).
|
|
91026
|
+
- /etc/machine-id:/etc/machine-id:ro
|
|
90929
91027
|
environment:
|
|
90930
91028
|
# Hostd resolves homedir() to set the per-agent socket dir; pin
|
|
90931
91029
|
# it inside the container to /host-home (which bind-mounts to the
|
|
@@ -17243,7 +17243,8 @@ var UpdateApplyRequestSchema = exports_external.object({
|
|
|
17243
17243
|
skip_images: exports_external.boolean().optional(),
|
|
17244
17244
|
rebuild: exports_external.boolean().optional(),
|
|
17245
17245
|
channel: exports_external.enum(["dev", "rc", "latest"]).nullable().optional(),
|
|
17246
|
-
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional()
|
|
17246
|
+
pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).nullable().optional(),
|
|
17247
|
+
reason: exports_external.string().max(512).optional()
|
|
17247
17248
|
}).optional()
|
|
17248
17249
|
});
|
|
17249
17250
|
var ApplyRequestSchema = exports_external.object({
|
|
@@ -17258,21 +17259,24 @@ var RolloutRequestSchema = exports_external.object({
|
|
|
17258
17259
|
pin: exports_external.string().regex(/^v\d+\.\d+\.\d+$/),
|
|
17259
17260
|
agents: exports_external.array(AgentNameSchema).min(1).optional(),
|
|
17260
17261
|
skip_web: exports_external.boolean().optional(),
|
|
17261
|
-
allow_downgrade: exports_external.boolean().optional()
|
|
17262
|
+
allow_downgrade: exports_external.boolean().optional(),
|
|
17263
|
+
reason: exports_external.string().max(512).optional()
|
|
17262
17264
|
}).required({ pin: true })
|
|
17263
17265
|
});
|
|
17264
17266
|
var AgentStartRequestSchema = exports_external.object({
|
|
17265
17267
|
...RequestEnvelope,
|
|
17266
17268
|
op: exports_external.literal("agent_start"),
|
|
17267
17269
|
args: exports_external.object({
|
|
17268
|
-
name: AgentNameSchema
|
|
17270
|
+
name: AgentNameSchema,
|
|
17271
|
+
reason: exports_external.string().max(512).optional()
|
|
17269
17272
|
})
|
|
17270
17273
|
});
|
|
17271
17274
|
var AgentStopRequestSchema = exports_external.object({
|
|
17272
17275
|
...RequestEnvelope,
|
|
17273
17276
|
op: exports_external.literal("agent_stop"),
|
|
17274
17277
|
args: exports_external.object({
|
|
17275
|
-
name: AgentNameSchema
|
|
17278
|
+
name: AgentNameSchema,
|
|
17279
|
+
reason: exports_external.string().max(512).optional()
|
|
17276
17280
|
})
|
|
17277
17281
|
});
|
|
17278
17282
|
var AgentLogsRequestSchema = exports_external.object({
|
|
@@ -17280,7 +17284,8 @@ var AgentLogsRequestSchema = exports_external.object({
|
|
|
17280
17284
|
op: exports_external.literal("agent_logs"),
|
|
17281
17285
|
args: exports_external.object({
|
|
17282
17286
|
name: AgentNameSchema,
|
|
17283
|
-
tail: exports_external.number().int().positive().max(2000).optional()
|
|
17287
|
+
tail: exports_external.number().int().positive().max(2000).optional(),
|
|
17288
|
+
reason: exports_external.string().max(512).optional()
|
|
17284
17289
|
})
|
|
17285
17290
|
});
|
|
17286
17291
|
var AgentExecRequestSchema = exports_external.object({
|
|
@@ -17288,7 +17293,8 @@ var AgentExecRequestSchema = exports_external.object({
|
|
|
17288
17293
|
op: exports_external.literal("agent_exec"),
|
|
17289
17294
|
args: exports_external.object({
|
|
17290
17295
|
name: AgentNameSchema,
|
|
17291
|
-
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32)
|
|
17296
|
+
argv: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
17297
|
+
reason: exports_external.string().max(512).optional()
|
|
17292
17298
|
})
|
|
17293
17299
|
});
|
|
17294
17300
|
var DoctorRequestSchema = exports_external.object({
|
|
@@ -22587,7 +22593,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:f
|
|
|
22587
22593
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
22588
22594
|
|
|
22589
22595
|
// src/build-info.ts
|
|
22590
|
-
var VERSION = "0.16.
|
|
22596
|
+
var VERSION = "0.16.28";
|
|
22591
22597
|
|
|
22592
22598
|
// src/cli/resolve-version.ts
|
|
22593
22599
|
function readPackageVersion() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
|
-
"version": "0.16.
|
|
4
|
-
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw
|
|
3
|
+
"version": "0.16.28",
|
|
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": {
|
|
7
7
|
"switchroom": "./dist/cli/switchroom.js"
|
|
@@ -368,12 +368,13 @@ export function createAnswerStream(config: AnswerStreamConfig): AnswerStreamHand
|
|
|
368
368
|
return undefined
|
|
369
369
|
}
|
|
370
370
|
|
|
371
|
-
// Telegram caps a single message at
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
371
|
+
// Telegram caps a single rich message at TELEGRAM_MAX_CHARS (32768,
|
|
372
|
+
// the Bot API 10.1 rich-message wire cap post-#2669 — NOT the legacy
|
|
373
|
+
// 4096 plain-text limit). The streaming path already guards on this in
|
|
374
|
+
// sendOrEdit; materialize must too, or long answers silently drop the
|
|
375
|
+
// final push notification (Telegram returns 400, the catch swallows).
|
|
376
|
+
// Per the JTBD anti-pattern "silent failure of any kind", warn and bail
|
|
377
|
+
// explicitly so the operator can correlate.
|
|
377
378
|
if (textToSend.length > TELEGRAM_MAX_CHARS) {
|
|
378
379
|
warn?.(
|
|
379
380
|
`answer-stream: materialize — text exceeds ${TELEGRAM_MAX_CHARS} chars (got ${textToSend.length}); skipping. ` +
|
|
@@ -137,7 +137,7 @@ const TOOL_SCHEMAS = [
|
|
|
137
137
|
{
|
|
138
138
|
name: 'stream_reply',
|
|
139
139
|
description:
|
|
140
|
-
'Post the final answer for this turn. The plugin renders an event-driven progress card (Plan → Run → Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard
|
|
140
|
+
'Post the final answer for this turn. The plugin renders an event-driven progress card (Plan → Run → Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard cap is 32768 chars (the rich-message wire limit) — longer text is dropped by a defensive guard, so use `reply` for anything that long (it chunks). Calling with done=false is an error in this environment (the progress card already owns the mid-turn surface). inline_keyboard adds tappable buttons under the final message — see `reply` for shape and constraints.',
|
|
141
141
|
inputSchema: {
|
|
142
142
|
type: 'object',
|
|
143
143
|
properties: {
|
|
@@ -24687,7 +24687,7 @@ var TOOL_SCHEMAS = [
|
|
|
24687
24687
|
},
|
|
24688
24688
|
{
|
|
24689
24689
|
name: "stream_reply",
|
|
24690
|
-
description: "Post the final answer for this turn. The plugin renders an event-driven progress card (Plan \u2192 Run \u2192 Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard
|
|
24690
|
+
description: "Post the final answer for this turn. The plugin renders an event-driven progress card (Plan \u2192 Run \u2192 Done with live tool bullets, elapsed time, and status emoji) for free while the turn is in-flight, so you do not need to narrate intermediate progress. Call `stream_reply` exactly once per turn with done=true and the complete answer text. Hard cap is 32768 chars (the rich-message wire limit) \u2014 longer text is dropped by a defensive guard, so use `reply` for anything that long (it chunks). Calling with done=false is an error in this environment (the progress card already owns the mid-turn surface). inline_keyboard adds tappable buttons under the final message \u2014 see `reply` for shape and constraints.",
|
|
24691
24691
|
inputSchema: {
|
|
24692
24692
|
type: "object",
|
|
24693
24693
|
properties: {
|