switchroom 0.16.38 → 0.16.47
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 +8 -2
- package/dist/auth-broker/index.js +7 -1
- package/dist/cli/notion-write-pretool.mjs +7 -1
- package/dist/cli/switchroom.js +1259 -375
- package/dist/cli/ui/index.html +877 -214
- package/dist/host-control/main.js +116 -84
- package/dist/vault/approvals/kernel-server.js +8 -2
- package/dist/vault/broker/server.js +8 -2
- package/examples/minimal.yaml +1 -1
- package/examples/switchroom.yaml +1 -1
- package/package.json +2 -2
- package/profiles/_shared/reply-discipline.md.hbs +9 -0
- package/skills/switchroom-status/SKILL.md +1 -1
- package/telegram-plugin/bridge/bridge.ts +2 -1
- package/telegram-plugin/card-format.ts +7 -1
- package/telegram-plugin/dist/bridge/bridge.js +20 -2
- package/telegram-plugin/dist/gateway/gateway.js +2197 -964
- package/telegram-plugin/dist/server.js +20 -2
- package/telegram-plugin/format.ts +305 -31
- package/telegram-plugin/gateway/gateway.ts +310 -70
- package/telegram-plugin/gateway/model-command.ts +173 -19
- package/telegram-plugin/hooks/tool-label-pretool.d.mts +12 -0
- package/telegram-plugin/hooks/tool-label-pretool.mjs +54 -16
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/session-tail.ts +47 -1
- package/telegram-plugin/stream-reply-handler.ts +19 -1
- package/telegram-plugin/tests/always-allow-grant.test.ts +34 -2
- package/telegram-plugin/tests/card-format.test.ts +28 -0
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +151 -0
- package/telegram-plugin/tests/format-consistency.test.ts +223 -0
- package/telegram-plugin/tests/formatting-parse-regression.test.ts +272 -0
- package/telegram-plugin/tests/formatting-torture-set.ts +218 -0
- package/telegram-plugin/tests/model-command.test.ts +213 -47
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +203 -21
- package/telegram-plugin/tests/rich-markdown-oracle.ts +469 -0
- package/telegram-plugin/tests/session-tail.test.ts +91 -0
- package/telegram-plugin/tests/status-vocabulary-unification.test.ts +125 -0
- package/telegram-plugin/tests/telegram-format.test.ts +33 -8
- package/telegram-plugin/tests/text-voice-scrub.test.ts +142 -22
- package/telegram-plugin/tests/tool-activity-summary.test.ts +6 -1
- package/telegram-plugin/tests/tts-normalize.test.ts +242 -0
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +24 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +46 -0
- package/telegram-plugin/tests/voice-ondemand.test.ts +99 -2
- package/telegram-plugin/tests/voice-presynth.test.ts +437 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +49 -0
- package/telegram-plugin/text-voice-scrub.ts +68 -18
- package/telegram-plugin/tool-activity-summary.ts +20 -108
- package/telegram-plugin/tts-normalize.ts +377 -0
- package/telegram-plugin/uat/driver.ts +472 -22
- package/telegram-plugin/uat/scenarios/jtbd-model-litellm-sr-dm.test.ts +34 -14
- package/telegram-plugin/uat/scenarios/jtbd-multipart-render-dm.test.ts +169 -0
- package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +134 -0
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +254 -0
- package/telegram-plugin/uat/scenarios/jtbd-status-phase-transitions-dm.test.ts +109 -0
- package/telegram-plugin/uat/uat-driver.test.ts +297 -0
- package/telegram-plugin/voice-ondemand.ts +161 -10
- package/telegram-plugin/voice-presynth.ts +242 -0
- package/telegram-plugin/worker-activity-feed.ts +9 -1
|
@@ -6961,6 +6961,55 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6961
6961
|
exports.stringify = stringify;
|
|
6962
6962
|
});
|
|
6963
6963
|
|
|
6964
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
|
|
6965
|
+
var require_dist = __commonJS((exports) => {
|
|
6966
|
+
var composer = require_composer();
|
|
6967
|
+
var Document = require_Document();
|
|
6968
|
+
var Schema = require_Schema();
|
|
6969
|
+
var errors = require_errors();
|
|
6970
|
+
var Alias = require_Alias();
|
|
6971
|
+
var identity = require_identity();
|
|
6972
|
+
var Pair = require_Pair();
|
|
6973
|
+
var Scalar = require_Scalar();
|
|
6974
|
+
var YAMLMap = require_YAMLMap();
|
|
6975
|
+
var YAMLSeq = require_YAMLSeq();
|
|
6976
|
+
var cst = require_cst();
|
|
6977
|
+
var lexer = require_lexer();
|
|
6978
|
+
var lineCounter = require_line_counter();
|
|
6979
|
+
var parser = require_parser();
|
|
6980
|
+
var publicApi = require_public_api();
|
|
6981
|
+
var visit = require_visit();
|
|
6982
|
+
exports.Composer = composer.Composer;
|
|
6983
|
+
exports.Document = Document.Document;
|
|
6984
|
+
exports.Schema = Schema.Schema;
|
|
6985
|
+
exports.YAMLError = errors.YAMLError;
|
|
6986
|
+
exports.YAMLParseError = errors.YAMLParseError;
|
|
6987
|
+
exports.YAMLWarning = errors.YAMLWarning;
|
|
6988
|
+
exports.Alias = Alias.Alias;
|
|
6989
|
+
exports.isAlias = identity.isAlias;
|
|
6990
|
+
exports.isCollection = identity.isCollection;
|
|
6991
|
+
exports.isDocument = identity.isDocument;
|
|
6992
|
+
exports.isMap = identity.isMap;
|
|
6993
|
+
exports.isNode = identity.isNode;
|
|
6994
|
+
exports.isPair = identity.isPair;
|
|
6995
|
+
exports.isScalar = identity.isScalar;
|
|
6996
|
+
exports.isSeq = identity.isSeq;
|
|
6997
|
+
exports.Pair = Pair.Pair;
|
|
6998
|
+
exports.Scalar = Scalar.Scalar;
|
|
6999
|
+
exports.YAMLMap = YAMLMap.YAMLMap;
|
|
7000
|
+
exports.YAMLSeq = YAMLSeq.YAMLSeq;
|
|
7001
|
+
exports.CST = cst;
|
|
7002
|
+
exports.Lexer = lexer.Lexer;
|
|
7003
|
+
exports.LineCounter = lineCounter.LineCounter;
|
|
7004
|
+
exports.Parser = parser.Parser;
|
|
7005
|
+
exports.parse = publicApi.parse;
|
|
7006
|
+
exports.parseAllDocuments = publicApi.parseAllDocuments;
|
|
7007
|
+
exports.parseDocument = publicApi.parseDocument;
|
|
7008
|
+
exports.stringify = publicApi.stringify;
|
|
7009
|
+
exports.visit = visit.visit;
|
|
7010
|
+
exports.visitAsync = visit.visitAsync;
|
|
7011
|
+
});
|
|
7012
|
+
|
|
6964
7013
|
// telegram-plugin/secret-detect/suppressor.ts
|
|
6965
7014
|
function isSuppressed(text, start, end) {
|
|
6966
7015
|
const left = Math.max(0, start - WINDOW);
|
|
@@ -9037,7 +9086,7 @@ function requireLodash_sortby() {
|
|
|
9037
9086
|
return value;
|
|
9038
9087
|
}
|
|
9039
9088
|
if (value == null) {
|
|
9040
|
-
return
|
|
9089
|
+
return identity;
|
|
9041
9090
|
}
|
|
9042
9091
|
if (typeof value == "object") {
|
|
9043
9092
|
return isArray2(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value);
|
|
@@ -9083,7 +9132,7 @@ function requireLodash_sortby() {
|
|
|
9083
9132
|
}
|
|
9084
9133
|
function baseOrderBy(collection, iteratees, orders) {
|
|
9085
9134
|
var index = -1;
|
|
9086
|
-
iteratees = arrayMap(iteratees.length ? iteratees : [
|
|
9135
|
+
iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(baseIteratee));
|
|
9087
9136
|
var result = baseMap(collection, function(value, key, collection2) {
|
|
9088
9137
|
var criteria = arrayMap(iteratees, function(iteratee) {
|
|
9089
9138
|
return iteratee(value);
|
|
@@ -9518,7 +9567,7 @@ function requireLodash_sortby() {
|
|
|
9518
9567
|
function keys(object) {
|
|
9519
9568
|
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
|
|
9520
9569
|
}
|
|
9521
|
-
function
|
|
9570
|
+
function identity(value) {
|
|
9522
9571
|
return value;
|
|
9523
9572
|
}
|
|
9524
9573
|
function property(path) {
|
|
@@ -9664,6 +9713,13 @@ var init_secretlint_source = __esm(() => {
|
|
|
9664
9713
|
init_suppressor();
|
|
9665
9714
|
});
|
|
9666
9715
|
|
|
9716
|
+
// src/util/atomic.ts
|
|
9717
|
+
import { closeSync as closeSync2, constants, fsyncSync, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
|
|
9718
|
+
var TMP_OPEN_FLAGS;
|
|
9719
|
+
var init_atomic = __esm(() => {
|
|
9720
|
+
TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
|
|
9721
|
+
});
|
|
9722
|
+
|
|
9667
9723
|
// node_modules/.bun/commander@13.1.0/node_modules/commander/lib/error.js
|
|
9668
9724
|
var require_error = __commonJS((exports2) => {
|
|
9669
9725
|
class CommanderError extends Error {
|
|
@@ -11704,56 +11760,11 @@ import { existsSync as existsSync9 } from "node:fs";
|
|
|
11704
11760
|
import { join as join6, resolve as resolve7 } from "node:path";
|
|
11705
11761
|
|
|
11706
11762
|
// src/config/loader.ts
|
|
11763
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
11707
11764
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "node:fs";
|
|
11708
11765
|
import { homedir } from "node:os";
|
|
11709
11766
|
import { resolve as resolve3 } from "node:path";
|
|
11710
11767
|
|
|
11711
|
-
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
|
|
11712
|
-
var composer = require_composer();
|
|
11713
|
-
var Document = require_Document();
|
|
11714
|
-
var Schema = require_Schema();
|
|
11715
|
-
var errors = require_errors();
|
|
11716
|
-
var Alias = require_Alias();
|
|
11717
|
-
var identity = require_identity();
|
|
11718
|
-
var Pair = require_Pair();
|
|
11719
|
-
var Scalar = require_Scalar();
|
|
11720
|
-
var YAMLMap = require_YAMLMap();
|
|
11721
|
-
var YAMLSeq = require_YAMLSeq();
|
|
11722
|
-
var cst = require_cst();
|
|
11723
|
-
var lexer = require_lexer();
|
|
11724
|
-
var lineCounter = require_line_counter();
|
|
11725
|
-
var parser = require_parser();
|
|
11726
|
-
var publicApi = require_public_api();
|
|
11727
|
-
var visit = require_visit();
|
|
11728
|
-
var $Composer = composer.Composer;
|
|
11729
|
-
var $Document = Document.Document;
|
|
11730
|
-
var $Schema = Schema.Schema;
|
|
11731
|
-
var $YAMLError = errors.YAMLError;
|
|
11732
|
-
var $YAMLParseError = errors.YAMLParseError;
|
|
11733
|
-
var $YAMLWarning = errors.YAMLWarning;
|
|
11734
|
-
var $Alias = Alias.Alias;
|
|
11735
|
-
var $isAlias = identity.isAlias;
|
|
11736
|
-
var $isCollection = identity.isCollection;
|
|
11737
|
-
var $isDocument = identity.isDocument;
|
|
11738
|
-
var $isMap = identity.isMap;
|
|
11739
|
-
var $isNode = identity.isNode;
|
|
11740
|
-
var $isPair = identity.isPair;
|
|
11741
|
-
var $isScalar = identity.isScalar;
|
|
11742
|
-
var $isSeq = identity.isSeq;
|
|
11743
|
-
var $Pair = Pair.Pair;
|
|
11744
|
-
var $Scalar = Scalar.Scalar;
|
|
11745
|
-
var $YAMLMap = YAMLMap.YAMLMap;
|
|
11746
|
-
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
11747
|
-
var $Lexer = lexer.Lexer;
|
|
11748
|
-
var $LineCounter = lineCounter.LineCounter;
|
|
11749
|
-
var $Parser = parser.Parser;
|
|
11750
|
-
var $parse = publicApi.parse;
|
|
11751
|
-
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
11752
|
-
var $parseDocument = publicApi.parseDocument;
|
|
11753
|
-
var $stringify = publicApi.stringify;
|
|
11754
|
-
var $visit = visit.visit;
|
|
11755
|
-
var $visitAsync = visit.visitAsync;
|
|
11756
|
-
|
|
11757
11768
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
11758
11769
|
var exports_external = {};
|
|
11759
11770
|
__export(exports_external, {
|
|
@@ -16188,7 +16199,7 @@ var AgentSchema = exports_external.object({
|
|
|
16188
16199
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
16189
16200
|
reactions: ReactionsSchema,
|
|
16190
16201
|
reaction_dispatch: ReactionDispatchSchema,
|
|
16191
|
-
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-
|
|
16202
|
+
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-5')"),
|
|
16192
16203
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
16193
16204
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Per-agent override wins over defaults.permission_mode. " + "Warning: bypassPermissions and dontAsk skip all safety checks — use only in trusted sandboxes."),
|
|
16194
16205
|
fallback_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Fallback model name must be alphanumeric with ._-/[]: only").optional().describe("Fallback model passed as --fallback-model to the claude CLI. " + "Per-agent override wins over defaults.fallback_model. " + "Used when the primary model is overloaded. Note: only functional in --print (non-interactive) mode per Claude CLI docs; silently no-ops in interactive sessions."),
|
|
@@ -16319,6 +16330,11 @@ var HostControlConfigSchema = exports_external.object({
|
|
|
16319
16330
|
var WebServiceConfigSchema = exports_external.object({
|
|
16320
16331
|
managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false — existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
|
|
16321
16332
|
});
|
|
16333
|
+
var FleetHealthConfigSchema = exports_external.object({
|
|
16334
|
+
owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
16335
|
+
message: "owner_agent must match the standard agent-name pattern"
|
|
16336
|
+
}).optional().describe("The admin agent that runs the Fleet Health sensor + deep-dive crons " + "(RFC fleet-health.md). Default unset → the feature is inert: no crons " + "are scheduled and the admin page renders its empty state. The named " + "agent must be admin: true. A dedicated owner (not any admin) keeps " + "the fleet-health work attributable, its memory scoped, and its token " + "spend accountable. The detection runs ONLY as operator-set schedules " + "on this agent — never a self-authored loop (on-leash, " + "no-self-escalation).")
|
|
16337
|
+
});
|
|
16322
16338
|
var HostdConfigSchema = exports_external.object({
|
|
16323
16339
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
16324
16340
|
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat.")
|
|
@@ -16367,6 +16383,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
16367
16383
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
16368
16384
|
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
16369
16385
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
16386
|
+
fleet_health: FleetHealthConfigSchema.default({}).describe("Fleet Health — job-spec-anchored, operator-facing issue tracker (RFC " + "fleet-health.md, serves fleet-stays-healthy). Assigns the owner agent " + "that runs the nightly model-free sensor + weekly deep-dive. Default " + "unset owner_agent → inert; the admin page renders an empty state."),
|
|
16370
16387
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
16371
16388
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
16372
16389
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
@@ -16458,6 +16475,7 @@ function resolveDualPath(pathStr) {
|
|
|
16458
16475
|
}
|
|
16459
16476
|
|
|
16460
16477
|
// src/config/overlay-loader.ts
|
|
16478
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
16461
16479
|
import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
|
|
16462
16480
|
import { basename, resolve as resolve2 } from "node:path";
|
|
16463
16481
|
|
|
@@ -16533,7 +16551,7 @@ function applyAgentOverlays(config) {
|
|
|
16533
16551
|
for (const file of files) {
|
|
16534
16552
|
try {
|
|
16535
16553
|
const raw = readFileSync(file, "utf-8");
|
|
16536
|
-
const parsed =
|
|
16554
|
+
const parsed = import_yaml.parse(raw);
|
|
16537
16555
|
const doc = OverlayDocSchema.parse(parsed);
|
|
16538
16556
|
const title = deriveOverlayTitle(raw, basename(file));
|
|
16539
16557
|
for (const entry of doc.schedule ?? []) {
|
|
@@ -16574,7 +16592,7 @@ function applyAgentOverlays(config) {
|
|
|
16574
16592
|
for (const file of skillFiles) {
|
|
16575
16593
|
try {
|
|
16576
16594
|
const raw = readFileSync(file, "utf-8");
|
|
16577
|
-
const parsed =
|
|
16595
|
+
const parsed = import_yaml.parse(raw);
|
|
16578
16596
|
const doc = OverlayDocSchema.parse(parsed);
|
|
16579
16597
|
for (const skillName of doc.skills ?? []) {
|
|
16580
16598
|
if (seen.has(skillName))
|
|
@@ -17091,7 +17109,7 @@ function loadConfig(configPath) {
|
|
|
17091
17109
|
}
|
|
17092
17110
|
let parsed;
|
|
17093
17111
|
try {
|
|
17094
|
-
parsed =
|
|
17112
|
+
parsed = import_yaml2.parse(raw);
|
|
17095
17113
|
} catch (err) {
|
|
17096
17114
|
throw new ConfigError(`Invalid YAML in ${filePath}`, [
|
|
17097
17115
|
` ${err.message}`
|
|
@@ -19472,8 +19490,8 @@ function createStackParser(platform, ...parsers) {
|
|
|
19472
19490
|
continue;
|
|
19473
19491
|
const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, "$1") : line;
|
|
19474
19492
|
if (!cleanedLine.match(/\S*Error: /)) {
|
|
19475
|
-
for (const
|
|
19476
|
-
const frame =
|
|
19493
|
+
for (const parser of parsers) {
|
|
19494
|
+
const frame = parser(cleanedLine, platform);
|
|
19477
19495
|
if (frame) {
|
|
19478
19496
|
frames.push(frame);
|
|
19479
19497
|
break;
|
|
@@ -21282,14 +21300,14 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
21282
21300
|
else {
|
|
21283
21301
|
requestId = flagsResponse.requestId;
|
|
21284
21302
|
evaluatedAt = flagsResponse.evaluatedAt;
|
|
21285
|
-
const
|
|
21303
|
+
const errors2 = [];
|
|
21286
21304
|
if (flagsResponse.errorsWhileComputingFlags)
|
|
21287
|
-
|
|
21305
|
+
errors2.push(FeatureFlagError2.ERRORS_WHILE_COMPUTING);
|
|
21288
21306
|
if (flagsResponse.quotaLimited?.includes("feature_flags"))
|
|
21289
|
-
|
|
21307
|
+
errors2.push(FeatureFlagError2.QUOTA_LIMITED);
|
|
21290
21308
|
const flagDetail = flagsResponse.flags[key];
|
|
21291
21309
|
if (flagDetail === undefined)
|
|
21292
|
-
|
|
21310
|
+
errors2.push(FeatureFlagError2.FLAG_MISSING);
|
|
21293
21311
|
else {
|
|
21294
21312
|
flagId = flagDetail.metadata?.id;
|
|
21295
21313
|
flagVersion = flagDetail.metadata?.version;
|
|
@@ -21308,8 +21326,8 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
21308
21326
|
payload: parsedPayload
|
|
21309
21327
|
};
|
|
21310
21328
|
}
|
|
21311
|
-
if (
|
|
21312
|
-
featureFlagError =
|
|
21329
|
+
if (errors2.length > 0)
|
|
21330
|
+
featureFlagError = errors2.join(",");
|
|
21313
21331
|
}
|
|
21314
21332
|
}
|
|
21315
21333
|
if (sendFeatureFlagEvents) {
|
|
@@ -22595,16 +22613,18 @@ function detectInstallType() {
|
|
|
22595
22613
|
}
|
|
22596
22614
|
}
|
|
22597
22615
|
|
|
22598
|
-
// src/
|
|
22599
|
-
|
|
22600
|
-
|
|
22616
|
+
// src/cli/release-yaml.ts
|
|
22617
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
22618
|
+
|
|
22619
|
+
// src/cli/rollout.ts
|
|
22620
|
+
init_atomic();
|
|
22601
22621
|
|
|
22602
22622
|
// src/cli/resolve-version.ts
|
|
22603
22623
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
22604
22624
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
22605
22625
|
|
|
22606
22626
|
// src/build-info.ts
|
|
22607
|
-
var VERSION = "0.
|
|
22627
|
+
var VERSION = "0.15.48";
|
|
22608
22628
|
|
|
22609
22629
|
// src/cli/resolve-version.ts
|
|
22610
22630
|
function readPackageVersion() {
|
|
@@ -22734,6 +22754,14 @@ function latestRolloutRowForRequest(raw, requestId) {
|
|
|
22734
22754
|
return latest;
|
|
22735
22755
|
}
|
|
22736
22756
|
|
|
22757
|
+
// src/setup/hindsight.ts
|
|
22758
|
+
var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
22759
|
+
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
22760
|
+
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
22761
|
+
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
22762
|
+
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
22763
|
+
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
22764
|
+
|
|
22737
22765
|
// src/cli/rollout.ts
|
|
22738
22766
|
function isVersionAssertable(target) {
|
|
22739
22767
|
return /^v?\d+\.\d+\.\d+$/.test(target.trim());
|
|
@@ -22809,6 +22837,7 @@ import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
22809
22837
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
22810
22838
|
|
|
22811
22839
|
// src/agents/singleton-reconcile.ts
|
|
22840
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
22812
22841
|
var CORE_SINGLETON_SERVICES = [
|
|
22813
22842
|
"vault-broker",
|
|
22814
22843
|
"approval-kernel",
|
|
@@ -22894,6 +22923,7 @@ function getAllAgentStatuses(config) {
|
|
|
22894
22923
|
}
|
|
22895
22924
|
|
|
22896
22925
|
// src/cli/update.ts
|
|
22926
|
+
init_atomic();
|
|
22897
22927
|
var DEFAULT_COMPOSE_PATH = join3(homedir3(), ".switchroom", "compose", "docker-compose.yml");
|
|
22898
22928
|
var UPDATE_RESULT_SENTINEL = "SWITCHROOM_UPDATE_RESULT:";
|
|
22899
22929
|
function parseUpdateResultLine(stdout) {
|
|
@@ -22916,6 +22946,7 @@ function parseUpdateResultLine(stdout) {
|
|
|
22916
22946
|
}
|
|
22917
22947
|
|
|
22918
22948
|
// src/host-control/config-edit-validator.ts
|
|
22949
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
22919
22950
|
import { mkdtempSync, writeFileSync as writeFileSync2, rmSync as rmSync2, existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
22920
22951
|
import { tmpdir } from "node:os";
|
|
22921
22952
|
import { join as join4, isAbsolute as isAbsolute2, normalize, basename as basename2 } from "node:path";
|
|
@@ -23085,7 +23116,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
|
23085
23116
|
function failsafeParse(source) {
|
|
23086
23117
|
let doc;
|
|
23087
23118
|
try {
|
|
23088
|
-
doc =
|
|
23119
|
+
doc = import_yaml5.parseDocument(source, { merge: false, strict: true });
|
|
23089
23120
|
} catch (err2) {
|
|
23090
23121
|
return {
|
|
23091
23122
|
ok: false,
|
|
@@ -23101,25 +23132,25 @@ function failsafeParse(source) {
|
|
|
23101
23132
|
};
|
|
23102
23133
|
}
|
|
23103
23134
|
let rejection = null;
|
|
23104
|
-
|
|
23135
|
+
import_yaml5.visit(doc, (_key, node, _path) => {
|
|
23105
23136
|
if (rejection)
|
|
23106
|
-
return
|
|
23107
|
-
if (
|
|
23137
|
+
return import_yaml5.visit.BREAK;
|
|
23138
|
+
if (import_yaml5.isAlias(node)) {
|
|
23108
23139
|
rejection = {
|
|
23109
23140
|
ok: false,
|
|
23110
23141
|
code: "E_YAML_UNSAFE_CONSTRUCT",
|
|
23111
23142
|
detail: "YAML aliases (`*name`) are not allowed"
|
|
23112
23143
|
};
|
|
23113
|
-
return
|
|
23144
|
+
return import_yaml5.visit.BREAK;
|
|
23114
23145
|
}
|
|
23115
|
-
if (
|
|
23146
|
+
if (import_yaml5.isNode(node)) {
|
|
23116
23147
|
if (node.anchor) {
|
|
23117
23148
|
rejection = {
|
|
23118
23149
|
ok: false,
|
|
23119
23150
|
code: "E_YAML_UNSAFE_CONSTRUCT",
|
|
23120
23151
|
detail: `YAML anchors (\`&${node.anchor}\`) are not allowed`
|
|
23121
23152
|
};
|
|
23122
|
-
return
|
|
23153
|
+
return import_yaml5.visit.BREAK;
|
|
23123
23154
|
}
|
|
23124
23155
|
const tag = node.tag;
|
|
23125
23156
|
if (typeof tag === "string" && tag.startsWith("!")) {
|
|
@@ -23128,18 +23159,18 @@ function failsafeParse(source) {
|
|
|
23128
23159
|
code: "E_YAML_UNSAFE_CONSTRUCT",
|
|
23129
23160
|
detail: `YAML explicit tags (\`${tag}\`) are not allowed`
|
|
23130
23161
|
};
|
|
23131
|
-
return
|
|
23162
|
+
return import_yaml5.visit.BREAK;
|
|
23132
23163
|
}
|
|
23133
23164
|
}
|
|
23134
|
-
if (
|
|
23165
|
+
if (import_yaml5.isPair(node)) {
|
|
23135
23166
|
const k = node.key;
|
|
23136
|
-
if (
|
|
23167
|
+
if (import_yaml5.isScalar(k) && k.value === "<<") {
|
|
23137
23168
|
rejection = {
|
|
23138
23169
|
ok: false,
|
|
23139
23170
|
code: "E_YAML_UNSAFE_CONSTRUCT",
|
|
23140
23171
|
detail: "YAML merge keys (`<<:`) are not allowed"
|
|
23141
23172
|
};
|
|
23142
|
-
return
|
|
23173
|
+
return import_yaml5.visit.BREAK;
|
|
23143
23174
|
}
|
|
23144
23175
|
}
|
|
23145
23176
|
return;
|
|
@@ -23275,7 +23306,7 @@ function validateConfigEdit(opts) {
|
|
|
23275
23306
|
let beforeData = {};
|
|
23276
23307
|
try {
|
|
23277
23308
|
const beforeRaw = existsSync7(opts.configPath) ? readFileSync5(opts.configPath, "utf8") : "";
|
|
23278
|
-
const beforeDoc =
|
|
23309
|
+
const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
|
|
23279
23310
|
beforeData = beforeDoc.toJS();
|
|
23280
23311
|
} catch {
|
|
23281
23312
|
beforeData = {};
|
|
@@ -23289,8 +23320,8 @@ function assertSelfScopedAllowEdit(beforeContent, afterContent, caller) {
|
|
|
23289
23320
|
let before;
|
|
23290
23321
|
let after;
|
|
23291
23322
|
try {
|
|
23292
|
-
before = toObject(
|
|
23293
|
-
after = toObject(
|
|
23323
|
+
before = toObject(import_yaml5.parseDocument(beforeContent, { merge: false, strict: false }).toJS());
|
|
23324
|
+
after = toObject(import_yaml5.parseDocument(afterContent, { merge: false, strict: false }).toJS());
|
|
23294
23325
|
} catch (e) {
|
|
23295
23326
|
return { ok: false, detail: `self-scope: config did not parse (${e.message})` };
|
|
23296
23327
|
}
|
|
@@ -23349,6 +23380,7 @@ function stripCallerAllow(cfg, caller) {
|
|
|
23349
23380
|
}
|
|
23350
23381
|
|
|
23351
23382
|
// src/host-control/config-blast-radius.ts
|
|
23383
|
+
var import_yaml6 = __toESM(require_dist(), 1);
|
|
23352
23384
|
function toObject2(v) {
|
|
23353
23385
|
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
23354
23386
|
}
|
|
@@ -23397,8 +23429,8 @@ function classifyBlastRadius(beforeYaml, afterYaml) {
|
|
|
23397
23429
|
let before;
|
|
23398
23430
|
let after;
|
|
23399
23431
|
try {
|
|
23400
|
-
before = toObject2(
|
|
23401
|
-
after = toObject2(
|
|
23432
|
+
before = toObject2(import_yaml6.parse(beforeYaml));
|
|
23433
|
+
after = toObject2(import_yaml6.parse(afterYaml));
|
|
23402
23434
|
} catch {
|
|
23403
23435
|
return { agents: [], fleetWide: true, changedPaths: ["<unparseable>"] };
|
|
23404
23436
|
}
|
|
@@ -23789,8 +23821,8 @@ class HostdServer {
|
|
|
23789
23821
|
this.idempotencyKeys.clear();
|
|
23790
23822
|
}
|
|
23791
23823
|
onConnection(socket, bindPath) {
|
|
23792
|
-
const
|
|
23793
|
-
if (!
|
|
23824
|
+
const identity = socketPathToIdentity(bindPath);
|
|
23825
|
+
if (!identity) {
|
|
23794
23826
|
socket.end();
|
|
23795
23827
|
return;
|
|
23796
23828
|
}
|
|
@@ -23809,7 +23841,7 @@ class HostdServer {
|
|
|
23809
23841
|
return;
|
|
23810
23842
|
const line = buf.slice(0, nl);
|
|
23811
23843
|
buf = buf.slice(nl + 1);
|
|
23812
|
-
this.handleLine(line,
|
|
23844
|
+
this.handleLine(line, identity, socket).catch((err2) => {
|
|
23813
23845
|
process.stderr.write(`hostd: handler error: ${err2.message}
|
|
23814
23846
|
`);
|
|
23815
23847
|
socket.end();
|
|
@@ -3984,7 +3984,7 @@ var init_zod = __esm(() => {
|
|
|
3984
3984
|
});
|
|
3985
3985
|
|
|
3986
3986
|
// src/config/schema.ts
|
|
3987
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
3987
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
3988
3988
|
var init_schema = __esm(() => {
|
|
3989
3989
|
init_zod();
|
|
3990
3990
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -4447,7 +4447,7 @@ var init_schema = __esm(() => {
|
|
|
4447
4447
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
4448
4448
|
reactions: ReactionsSchema,
|
|
4449
4449
|
reaction_dispatch: ReactionDispatchSchema,
|
|
4450
|
-
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-
|
|
4450
|
+
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-5')"),
|
|
4451
4451
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
4452
4452
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Per-agent override wins over defaults.permission_mode. " + "Warning: bypassPermissions and dontAsk skip all safety checks — use only in trusted sandboxes."),
|
|
4453
4453
|
fallback_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Fallback model name must be alphanumeric with ._-/[]: only").optional().describe("Fallback model passed as --fallback-model to the claude CLI. " + "Per-agent override wins over defaults.fallback_model. " + "Used when the primary model is overloaded. Note: only functional in --print (non-interactive) mode per Claude CLI docs; silently no-ops in interactive sessions."),
|
|
@@ -4578,6 +4578,11 @@ var init_schema = __esm(() => {
|
|
|
4578
4578
|
WebServiceConfigSchema = exports_external.object({
|
|
4579
4579
|
managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false — existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
|
|
4580
4580
|
});
|
|
4581
|
+
FleetHealthConfigSchema = exports_external.object({
|
|
4582
|
+
owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
4583
|
+
message: "owner_agent must match the standard agent-name pattern"
|
|
4584
|
+
}).optional().describe("The admin agent that runs the Fleet Health sensor + deep-dive crons " + "(RFC fleet-health.md). Default unset → the feature is inert: no crons " + "are scheduled and the admin page renders its empty state. The named " + "agent must be admin: true. A dedicated owner (not any admin) keeps " + "the fleet-health work attributable, its memory scoped, and its token " + "spend accountable. The detection runs ONLY as operator-set schedules " + "on this agent — never a self-authored loop (on-leash, " + "no-self-escalation).")
|
|
4585
|
+
});
|
|
4581
4586
|
HostdConfigSchema = exports_external.object({
|
|
4582
4587
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
4583
4588
|
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat.")
|
|
@@ -4626,6 +4631,7 @@ var init_schema = __esm(() => {
|
|
|
4626
4631
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
4627
4632
|
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
4628
4633
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
4634
|
+
fleet_health: FleetHealthConfigSchema.default({}).describe("Fleet Health — job-spec-anchored, operator-facing issue tracker (RFC " + "fleet-health.md, serves fleet-stays-healthy). Assigns the owner agent " + "that runs the nightly model-free sensor + weekly deep-dive. Default " + "unset owner_agent → inert; the admin page renders an empty state."),
|
|
4629
4635
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
4630
4636
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
4631
4637
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
@@ -3984,7 +3984,7 @@ var init_zod = __esm(() => {
|
|
|
3984
3984
|
});
|
|
3985
3985
|
|
|
3986
3986
|
// src/config/schema.ts
|
|
3987
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
3987
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
3988
3988
|
var init_schema = __esm(() => {
|
|
3989
3989
|
init_zod();
|
|
3990
3990
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -4447,7 +4447,7 @@ var init_schema = __esm(() => {
|
|
|
4447
4447
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
4448
4448
|
reactions: ReactionsSchema,
|
|
4449
4449
|
reaction_dispatch: ReactionDispatchSchema,
|
|
4450
|
-
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-
|
|
4450
|
+
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-5')"),
|
|
4451
4451
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
4452
4452
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Per-agent override wins over defaults.permission_mode. " + "Warning: bypassPermissions and dontAsk skip all safety checks — use only in trusted sandboxes."),
|
|
4453
4453
|
fallback_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Fallback model name must be alphanumeric with ._-/[]: only").optional().describe("Fallback model passed as --fallback-model to the claude CLI. " + "Per-agent override wins over defaults.fallback_model. " + "Used when the primary model is overloaded. Note: only functional in --print (non-interactive) mode per Claude CLI docs; silently no-ops in interactive sessions."),
|
|
@@ -4578,6 +4578,11 @@ var init_schema = __esm(() => {
|
|
|
4578
4578
|
WebServiceConfigSchema = exports_external.object({
|
|
4579
4579
|
managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false — existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
|
|
4580
4580
|
});
|
|
4581
|
+
FleetHealthConfigSchema = exports_external.object({
|
|
4582
|
+
owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
4583
|
+
message: "owner_agent must match the standard agent-name pattern"
|
|
4584
|
+
}).optional().describe("The admin agent that runs the Fleet Health sensor + deep-dive crons " + "(RFC fleet-health.md). Default unset → the feature is inert: no crons " + "are scheduled and the admin page renders its empty state. The named " + "agent must be admin: true. A dedicated owner (not any admin) keeps " + "the fleet-health work attributable, its memory scoped, and its token " + "spend accountable. The detection runs ONLY as operator-set schedules " + "on this agent — never a self-authored loop (on-leash, " + "no-self-escalation).")
|
|
4585
|
+
});
|
|
4581
4586
|
HostdConfigSchema = exports_external.object({
|
|
4582
4587
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
4583
4588
|
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat.")
|
|
@@ -4626,6 +4631,7 @@ var init_schema = __esm(() => {
|
|
|
4626
4631
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
4627
4632
|
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
4628
4633
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
4634
|
+
fleet_health: FleetHealthConfigSchema.default({}).describe("Fleet Health — job-spec-anchored, operator-facing issue tracker (RFC " + "fleet-health.md, serves fleet-stays-healthy). Assigns the owner agent " + "that runs the nightly model-free sensor + weekly deep-dive. Default " + "unset owner_agent → inert; the admin page renders an empty state."),
|
|
4629
4635
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container — then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
4630
4636
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
4631
4637
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
package/examples/minimal.yaml
CHANGED
package/examples/switchroom.yaml
CHANGED
|
@@ -58,7 +58,7 @@ vault:
|
|
|
58
58
|
# Every agent inherits this. Per-agent fields win on conflict.
|
|
59
59
|
# tools/skills/schedule are unioned; scalars are overridden.
|
|
60
60
|
defaults:
|
|
61
|
-
model: claude-sonnet-
|
|
61
|
+
model: claude-sonnet-5
|
|
62
62
|
# The switchroom-telegram fork is the default. Uncomment to fall back to
|
|
63
63
|
# the upstream marketplace plugin: channels.telegram.plugin: official
|
|
64
64
|
channels:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.16.
|
|
4
|
+
"version": "0.16.47",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"pretest": "npm run build",
|
|
27
27
|
"test": "vitest run && bun test telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.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/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.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 telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
28
28
|
"test:vitest": "vitest run",
|
|
29
|
-
"test:bun": "bun test src/
|
|
29
|
+
"test:bun": "bun test 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/server-admin-only-keys.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/litellm/provision-apply-e2e.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/approval-origin.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/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.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/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.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/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
31
|
"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 && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-web-subscription-honest.mjs",
|
|
32
32
|
"lint:tsc": "tsc --noEmit",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## ⛔ SENDING YOUR ANSWER (read first, applies every turn)
|
|
2
|
+
|
|
3
|
+
**Plain text you write is INVISIBLE to the user.** The only thing that reaches Telegram is text passed through the `mcp__switchroom-telegram__reply` tool. Writing the answer in your response is NOT sending it — the user sees nothing until you call the reply tool.
|
|
4
|
+
|
|
5
|
+
- Every turn that answers a user **MUST end with a `reply` tool call** carrying the full answer. No exceptions, no "I'll just write it out."
|
|
6
|
+
- If you did work first (tool calls, lookups), the reply call is still required at the end — don't let the turn end on a tool result or plain prose.
|
|
7
|
+
- If you genuinely have nothing to add, emit exactly `NO_REPLY` and nothing else.
|
|
8
|
+
|
|
9
|
+
If you skip this, the gateway's safety net eventually forces your answer out 2-4 minutes late and burns extra turns. Don't rely on the net. Send it yourself.
|
|
@@ -82,7 +82,7 @@ End with a one-line summary: "X of Y agents running."
|
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
assistant — running (2h 14m)
|
|
85
|
-
model: claude-sonnet-
|
|
85
|
+
model: claude-sonnet-5 collection: general
|
|
86
86
|
|
|
87
87
|
dev — running (45m)
|
|
88
88
|
model: claude-opus-4-8 collection: coding
|
|
@@ -434,7 +434,8 @@ const TOOL_SCHEMAS = [
|
|
|
434
434
|
chat_id: { type: 'string', description: 'Chat to render the approval card in (use the chat_id of the user message that triggered the workflow).' },
|
|
435
435
|
key: { type: 'string', description: 'Vault key the agent wants access to (matches the key shown in the VAULT-BROKER-DENIED error, e.g. `fatsecret/credentials`).' },
|
|
436
436
|
scope: { type: 'string', enum: ['read', 'write'], description: 'Access scope: "read" (default) for `vault:<key>` references; "write" if the agent needs to put new values.' },
|
|
437
|
-
reason: { type: 'string', description: 'REQUIRED in practice — short human-readable rationale rendered on the card (e.g. "to look up today\'s food log entries"). The approval card now renders "why: not provided" when this is omitted, which signals to the operator that the agent skipped its explanation — they will usually Deny. Always supply a one-line rationale.' },
|
|
437
|
+
reason: { type: 'string', description: 'REQUIRED in practice — short human-readable rationale rendered on the card (e.g. "to look up today\'s food log entries"). The approval card now renders "why: not provided" when this is omitted, which signals to the operator that the agent skipped its explanation — they will usually Deny. Always supply a one-line rationale. `why` is accepted as an alias (matching `vault_request_save`).' },
|
|
438
|
+
why: { type: 'string', description: 'Alias for `reason` (matches the sibling tool `vault_request_save`). If both are supplied, `reason` wins.' },
|
|
438
439
|
duration: { type: 'string', description: 'Requested grant TTL, like "30d" or "12h". Default 30d, capped at 90d. Beyond 90d the operator should use the host CLI explicitly.' },
|
|
439
440
|
message_thread_id: { type: 'string', description: 'Forum topic thread ID. Auto-applied from the last inbound message if not specified.' },
|
|
440
441
|
},
|