switchroom 0.16.24 → 0.16.27

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.
@@ -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: { name: args.name }
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: { name: args.name }
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: { name: args.name, argv: args.argv }
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.23";
51696
- var COMMIT_SHA = "3c17304c";
51744
+ var VERSION = "0.16.27";
51745
+ var COMMIT_SHA = "68a4feed";
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 as Telegram HTML for you, so
53103
- \`**bold**\` becomes bold and backtick-wrapped text becomes monospace. Use it.
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
- - Bullets stay one level deep \u2014 Telegram flattens nested lists awkwardly. Use
53126
- backtick-wrapped \`inline code\` for filenames, commands, and identifiers.
53127
- - Don't use Markdown headings (\`#\` / \`##\`) in a reply \u2014 bold the label
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
- return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
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
- return baseAppend.length > 0 ? shellSingleQuote(baseAppend) : undefined;
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 = [];
@@ -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.red(` x litellm: no operator vault passphrase \u2014 skipping provisioning for ` + `${targets.join(", ")}. They will NOT get routing env until a key exists.
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.23";
22596
+ var VERSION = "0.16.27";
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.24",
4
- "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw \u2014 no API keys.",
3
+ "version": "0.16.27",
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 4096 chars. The streaming path
372
- // already guards on this in sendOrEdit; materialize must too, or
373
- // long answers silently drop the final push notification (Telegram
374
- // returns 400, the catch swallows). Per the JTBD anti-pattern
375
- // "silent failure of any kind", warn and bail explicitly so the
376
- // operator can correlate.
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-stops at 4096 chars — longer text throws; fall back to `reply`, which 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.',
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-stops at 4096 chars \u2014 longer text throws; fall back to `reply`, which 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.",
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: {