switchroom 0.16.28 → 0.16.38
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 +13 -2
- package/dist/auth-broker/index.js +13 -2
- package/dist/cli/notion-write-pretool.mjs +13 -2
- package/dist/cli/switchroom.js +60648 -59800
- package/dist/host-control/main.js +730 -18
- package/dist/vault/approvals/kernel-server.js +51 -37
- package/dist/vault/broker/server.js +72 -58
- package/package.json +2 -1
- package/profiles/_shared/agent-self-service.md.hbs +7 -2
- package/telegram-plugin/auth-snapshot-format.ts +173 -67
- package/telegram-plugin/auto-fallback-fleet.ts +3 -6
- package/telegram-plugin/card-format.ts +52 -2
- package/telegram-plugin/credits-watch.ts +4 -7
- package/telegram-plugin/dist/gateway/gateway.js +2855 -1034
- package/telegram-plugin/dist/server.js +0 -1
- package/telegram-plugin/format.ts +264 -7
- package/telegram-plugin/gateway/approval-card.ts +7 -14
- package/telegram-plugin/gateway/approvals-commands.ts +6 -9
- package/telegram-plugin/gateway/auth-command.ts +35 -38
- package/telegram-plugin/gateway/boot-card.ts +7 -2
- package/telegram-plugin/gateway/chat-id-fallback.ts +46 -0
- package/telegram-plugin/gateway/config-approval-handler.ts +6 -9
- package/telegram-plugin/gateway/diff-preview-card.ts +3 -6
- package/telegram-plugin/gateway/gateway.ts +1293 -97
- package/telegram-plugin/gateway/ipc-protocol.ts +66 -2
- package/telegram-plugin/gateway/ipc-server.ts +91 -1
- package/telegram-plugin/gateway/linear-activity.ts +2 -5
- package/telegram-plugin/gateway/obligation-turn-end.ts +27 -0
- package/telegram-plugin/gateway/permission-card-store.ts +104 -0
- package/telegram-plugin/gateway/permission-timeout.ts +25 -6
- package/telegram-plugin/gateway/status-pin-store.ts +302 -0
- package/telegram-plugin/gateway/turns-jsonl-rotate.ts +30 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +12 -1
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +35 -0
- package/telegram-plugin/gateway/vault-request-access-card.ts +61 -0
- package/telegram-plugin/history.ts +17 -7
- package/telegram-plugin/idle-footer.ts +2 -2
- package/telegram-plugin/issues-card.ts +12 -7
- package/telegram-plugin/model-unavailable.ts +3 -6
- package/telegram-plugin/operator-events.ts +4 -6
- package/telegram-plugin/quota-check.ts +2 -2
- package/telegram-plugin/quota-watch.ts +7 -10
- package/telegram-plugin/server.ts +3 -1
- package/telegram-plugin/status-pin-driver.ts +102 -0
- package/telegram-plugin/status-pin.ts +76 -0
- package/telegram-plugin/stream-reply-handler.ts +14 -1
- package/telegram-plugin/subagent-watcher.ts +6 -3
- package/telegram-plugin/tests/auth-command-format2.test.ts +6 -2
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +18 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +204 -45
- package/telegram-plugin/tests/card-format.test.ts +51 -0
- package/telegram-plugin/tests/chat-id-fallback.test.ts +74 -0
- package/telegram-plugin/tests/codespan-escaping-golden.test.ts +166 -0
- package/telegram-plugin/tests/credits-watch.test.ts +18 -0
- package/telegram-plugin/tests/history.test.ts +38 -2
- package/telegram-plugin/tests/idle-footer.test.ts +53 -9
- package/telegram-plugin/tests/ipc-server-validate-rollout-status.test.ts +66 -0
- package/telegram-plugin/tests/issues-card.test.ts +24 -0
- package/telegram-plugin/tests/obligation-turn-end.test.ts +76 -0
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +200 -1
- package/telegram-plugin/tests/permission-card-single-edit.test.ts +66 -0
- package/telegram-plugin/tests/permission-card-store.test.ts +83 -0
- package/telegram-plugin/tests/permission-timeout.test.ts +37 -6
- package/telegram-plugin/tests/permission-verdict-resume-guard.test.ts +23 -6
- package/telegram-plugin/tests/quota-check.test.ts +9 -2
- package/telegram-plugin/tests/quota-watch.test.ts +7 -7
- package/telegram-plugin/tests/rollout-status-wiring.test.ts +81 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +294 -0
- package/telegram-plugin/tests/status-pin-service-message-suppression.test.ts +210 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +450 -0
- package/telegram-plugin/tests/status-pin.test.ts +202 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +39 -0
- package/telegram-plugin/tests/telegram-format.test.ts +88 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +82 -24
- package/telegram-plugin/tests/turns-jsonl-rotate.test.ts +39 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +12 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +54 -0
- package/telegram-plugin/tests/vault-request-access-card.test.ts +94 -0
- package/telegram-plugin/tests/voice-normalize-text.test.ts +256 -0
- package/telegram-plugin/tests/voice-ondemand.test.ts +202 -0
- package/telegram-plugin/tests/voice-out-one-send.test.ts +153 -0
- package/telegram-plugin/tests/voice-synthesize-sidecar.test.ts +352 -0
- package/telegram-plugin/tests/voice-transcribe-sidecar.test.ts +332 -0
- package/telegram-plugin/tests/voice-transcribe.test.ts +188 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +28 -10
- package/telegram-plugin/tool-activity-summary.ts +11 -8
- package/telegram-plugin/uat/driver.ts +8 -1
- package/telegram-plugin/voice-normalize-text.ts +340 -0
- package/telegram-plugin/voice-ondemand.ts +138 -0
- package/telegram-plugin/voice-synthesize-sidecar.ts +259 -0
- package/telegram-plugin/voice-synthesize.ts +128 -0
- package/telegram-plugin/voice-transcribe-sidecar.ts +176 -0
- package/telegram-plugin/worker-activity-feed.ts +13 -4
|
@@ -4180,19 +4180,30 @@ var init_schema = __esm(() => {
|
|
|
4180
4180
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
4181
4181
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive in-place stream edits " + "during a turn. Lower = more responsive stream, higher = fewer API " + "calls. Floored at 250 by draft-stream itself. Default 400 ms for DMs " + "and 1000 ms for groups/forums (respects Telegram's ~1 edit/sec/message " + "practical ceiling). Override per-agent if a particular agent needs " + "snappier or quieter streaming."),
|
|
4182
4182
|
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
4183
|
+
pin_status_while_working: exports_external.boolean().optional().describe("When true (default), the framework SILENTLY pins the already-" + "rendered status message while its work is in-flight and auto-unpins " + "it on completion — the per-turn activity/status message (foreground) " + "and the '\uD83D\uDEE0 Worker' background-worker message. Keeps in-flight work " + "in view when the conversation scrolls past it (fast turns, stacked " + "background workers, long turns). No new surface is rendered; it pins " + "a message the chat already owns. The pin never buzzes the device. " + "The ONE sanctioned pin under chat-is-the-single-source-of-truth. " + "Set false to disable. Per-agent override; cascades defaults → " + "profile → agent (per-key)."),
|
|
4183
4184
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
4184
4185
|
inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes — suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
|
|
4185
4186
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
4186
4187
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
4187
4188
|
deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),
|
|
4189
|
+
approval_timeout_minutes: exports_external.number().int().nonnegative().optional().describe("Operator approval-card lifetime (minutes) for the tool-use 'Allow " + "once' card and the vault grant decision wait. After this long with " + "no operator tap, the card auto-denies (a TIMEOUT, not a denial — the " + "agent is told not to retry). Default 60. hostd-gated verbs " + "(mcp__hostd__*) keep their own longer window; the hostd " + "config-propose card is not governed by this key."),
|
|
4188
4190
|
sub_agent_tick_interval_ms: exports_external.number().int().nonnegative().optional().describe("Heartbeat tick interval (ms) for sub-agent rendering. Forces a " + "re-render of the elapsed-time counter while sub-agents are running, " + "even during silent stretches between tool calls. Default 10000 (10 s). " + "Set to 0 to disable the elapsed-ticker path."),
|
|
4189
4191
|
edit_budget_threshold: exports_external.number().int().nonnegative().optional().describe("Telegram API edit budget per minute before the progress-card driver " + "falls back to a slower coalesce window. When a chat accumulates more " + "than this many card edits in the trailing 60 s, the driver switches " + "to a wider coalesce interval until the rate drops back. Default 18. " + "Increase if your gateway frequently bumps the Telegram edit-rate ceiling " + "with many parallel sub-agents; decrease for a more conservative buffer."),
|
|
4190
4192
|
stickers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Sticker aliases for the `send_sticker` MCP tool (#576). Maps a " + "short alias name (e.g. 'happy', 'thinking') to a Telegram file_id. " + "Operator-curated — capture file_ids from inbound stickers the user " + "sends and add them here. The agent calls send_sticker(chat_id, " + "alias='happy') and the gateway resolves to the file_id at send " + "time. Aliases enable persona-flavored expressiveness without " + "exposing raw file_ids in the agent prompt. Personal-assistant / " + "health-coach personas benefit; coding agents typically don't " + "configure any."),
|
|
4191
4193
|
voice_in: exports_external.object({
|
|
4192
4194
|
enabled: exports_external.boolean().optional().describe("Master switch for voice-message transcription."),
|
|
4193
4195
|
provider: exports_external.enum(["openai"]).optional().describe("Transcription provider. Only 'openai' (Whisper API) supported in the spike (#578); " + "Groq/Deepgram/local-whisper-cli are follow-up choices."),
|
|
4194
|
-
language: exports_external.string().optional().describe("Optional ISO-639-1 language hint (e.g. 'en', 'fr'). Skips Whisper's auto-detection.")
|
|
4195
|
-
|
|
4196
|
+
language: exports_external.string().optional().describe("Optional ISO-639-1 language hint (e.g. 'en', 'fr'). Skips Whisper's auto-detection."),
|
|
4197
|
+
api_key: exports_external.string().optional().describe("Transcription-provider API key, as a `vault:<key>` reference " + "(e.g. 'vault:openai/api-key' — the default if omitted). The " + "gateway resolves it through the vault broker at use-time and " + "never writes the resolved value to disk or the agent prompt. " + "`switchroom enable voice-in` vault-stores the key and writes " + "this reference for you. A literal key is accepted but " + "discouraged — keep secrets in the vault.")
|
|
4198
|
+
}).optional().describe("Inbound voice-message transcription (#578). When enabled, voice/audio " + "messages from allowlisted users are downloaded, transcribed via the " + "configured provider, and surface to the agent as the user's text. " + "The provider API key is a `vault:` reference (`api_key`, default " + "`vault:openai/api-key`) resolved through the vault broker at " + "use-time — opt-in third-party key, honest-exception per the " + "subscription-honest outcome. Off by default — opt-in per agent. " + "Cascades from defaults.channels.telegram.voice_in. " + "(Migrated from per-agent root in #596 — see consistency unification.)"),
|
|
4199
|
+
voice_out: exports_external.object({
|
|
4200
|
+
enabled: exports_external.boolean().optional().describe("Master switch for spoken-reply (TTS) voice notes."),
|
|
4201
|
+
engine: exports_external.enum(["kokoro", "openai"]).default("kokoro").describe("Synthesis engine. 'kokoro' = local voice sidecar (POST /tts, " + "subscription-honest, no third-party key); 'openai' = OpenAI TTS " + "cloud (honest-exception, requires an `api_key` vault ref). " + "'kokoro' is only active when the host voice verdict is local " + "(SWITCHROOM_VOICE_ENGINE === 'local'); otherwise voice-out is a " + "no-op and replies stay text-only."),
|
|
4202
|
+
voice: exports_external.string().optional().describe("Engine-specific voice id (e.g. a Kokoro voice name or an OpenAI " + "voice like 'alloy'). Optional — the sidecar/engine has its own " + "default when omitted."),
|
|
4203
|
+
reply_mode: exports_external.enum(["voice+text", "voice-only", "on-demand"]).default("voice+text").describe("How the spoken reply accompanies the text. 'voice+text' sends " + "both the normal text reply and a voice note; 'voice-only' sends " + "the voice note and suppresses the text body. 'on-demand' sends " + "the text reply with a single '\uD83D\uDD0A Listen' inline button and " + "synthesizes NO audio until the user taps it — zero GPU/sidecar " + "work unless requested, which keeps the voice pipeline " + "subscription-honest and visible (nothing is generated behind the " + "user's back). The Listen button is injected ONLY when the reply " + "carries no agent-authored buttons, to avoid colliding with the " + "single_use keyboard-strip that protects agent buttons from " + "double-fire. In every mode the text reply is ALWAYS still sent " + "when synthesis fails or the reply exceeds max_chars — the user's " + "answer is never dropped silently."),
|
|
4204
|
+
max_chars: exports_external.number().int().positive().optional().describe("Per-voice-note chunk size (chars). Default 600; clamped to the " + "engine's hard cap (1200). A reply LONGER than this is NOT truncated " + "to text — it's split on sentence/paragraph boundaries and spoken " + "across several sequential voice notes, so the full answer is heard " + "even when the user can't read the screen (driving / cycling). Lower " + "it for snappier individual notes; raise it (up to 1200) for fewer, " + "longer notes."),
|
|
4205
|
+
api_key: exports_external.string().optional().describe("OpenAI TTS API key as a `vault:<key>` reference (only used when " + "engine='openai'; default 'vault:openai/api-key'). Resolved through " + "the vault broker at use-time and never written to disk or the agent " + "prompt. Ignored for engine='kokoro' (the sidecar needs no key).")
|
|
4206
|
+
}).optional().describe("Outbound spoken replies via TTS (PR-C2). When enabled, the gateway " + "synthesizes the agent's text reply into an OGG/Opus voice note and " + "sends it alongside (or instead of) the text, per reply_mode. The " + "'kokoro' engine uses the local voice sidecar (POST /tts) and is only " + "active when the host voice verdict is local; the 'openai' engine is " + "an honest-exception cloud path gated on an `api_key` vault ref. Voice " + "is best-effort and fully non-fatal — any TTS error falls back to the " + "text reply. Off by default — opt-in per agent. " + "Cascades from defaults.channels.telegram.voice_out."),
|
|
4196
4207
|
telegraph: exports_external.object({
|
|
4197
4208
|
enabled: exports_external.boolean().optional().describe("Master switch for Telegraph Instant View publishing."),
|
|
4198
4209
|
threshold: exports_external.number().int().positive().optional().describe("Char count above which a reply is published to Telegraph instead of " + "HTML-chunked into multiple Telegram messages. Default 3000 (≈3 chunks)."),
|
|
@@ -5029,6 +5040,41 @@ var init_merge = __esm(() => {
|
|
|
5029
5040
|
})(mergeAgentConfig ||= {});
|
|
5030
5041
|
});
|
|
5031
5042
|
|
|
5043
|
+
// src/config/paths.ts
|
|
5044
|
+
import { existsSync } from "node:fs";
|
|
5045
|
+
import { resolve } from "node:path";
|
|
5046
|
+
function home() {
|
|
5047
|
+
return process.env.HOME ?? "/root";
|
|
5048
|
+
}
|
|
5049
|
+
function warnLegacyStateOnce(legacy) {
|
|
5050
|
+
if (_legacyStateWarned)
|
|
5051
|
+
return;
|
|
5052
|
+
_legacyStateWarned = true;
|
|
5053
|
+
process.stderr.write(`[switchroom] DEPRECATED: reading legacy state from ${legacy}. ` + "Run `mv ~/.clerk ~/.switchroom` (and rename any top-level `clerk:` " + "key in switchroom.yaml to `switchroom:`). This back-compat shim is " + `REMOVED in v0.13.0 — no automatic migration exists.
|
|
5054
|
+
`);
|
|
5055
|
+
}
|
|
5056
|
+
function resolveDualPath(pathStr) {
|
|
5057
|
+
const h = home();
|
|
5058
|
+
if (pathStr.startsWith("~/")) {
|
|
5059
|
+
const rest = pathStr.slice(2);
|
|
5060
|
+
const absolute = resolve(h, rest);
|
|
5061
|
+
if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
|
|
5062
|
+
const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
|
|
5063
|
+
if (!existsSync(absolute)) {
|
|
5064
|
+
const legacy = resolve(h, LEGACY_STATE_DIR, frag);
|
|
5065
|
+
if (existsSync(legacy)) {
|
|
5066
|
+
warnLegacyStateOnce(legacy);
|
|
5067
|
+
return legacy;
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
return absolute;
|
|
5072
|
+
}
|
|
5073
|
+
return resolve(pathStr);
|
|
5074
|
+
}
|
|
5075
|
+
var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk", _legacyStateWarned = false;
|
|
5076
|
+
var init_paths = () => {};
|
|
5077
|
+
|
|
5032
5078
|
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
5033
5079
|
var require_identity = __commonJS((exports) => {
|
|
5034
5080
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -11993,41 +12039,6 @@ var init_dist = __esm(() => {
|
|
|
11993
12039
|
$visitAsync = visit.visitAsync;
|
|
11994
12040
|
});
|
|
11995
12041
|
|
|
11996
|
-
// src/config/paths.ts
|
|
11997
|
-
import { existsSync } from "node:fs";
|
|
11998
|
-
import { resolve } from "node:path";
|
|
11999
|
-
function home() {
|
|
12000
|
-
return process.env.HOME ?? "/root";
|
|
12001
|
-
}
|
|
12002
|
-
function warnLegacyStateOnce(legacy) {
|
|
12003
|
-
if (_legacyStateWarned)
|
|
12004
|
-
return;
|
|
12005
|
-
_legacyStateWarned = true;
|
|
12006
|
-
process.stderr.write(`[switchroom] DEPRECATED: reading legacy state from ${legacy}. ` + "Run `mv ~/.clerk ~/.switchroom` (and rename any top-level `clerk:` " + "key in switchroom.yaml to `switchroom:`). This back-compat shim is " + `REMOVED in v0.13.0 — no automatic migration exists.
|
|
12007
|
-
`);
|
|
12008
|
-
}
|
|
12009
|
-
function resolveDualPath(pathStr) {
|
|
12010
|
-
const h = home();
|
|
12011
|
-
if (pathStr.startsWith("~/")) {
|
|
12012
|
-
const rest = pathStr.slice(2);
|
|
12013
|
-
const absolute = resolve(h, rest);
|
|
12014
|
-
if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
|
|
12015
|
-
const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
|
|
12016
|
-
if (!existsSync(absolute)) {
|
|
12017
|
-
const legacy = resolve(h, LEGACY_STATE_DIR, frag);
|
|
12018
|
-
if (existsSync(legacy)) {
|
|
12019
|
-
warnLegacyStateOnce(legacy);
|
|
12020
|
-
return legacy;
|
|
12021
|
-
}
|
|
12022
|
-
}
|
|
12023
|
-
}
|
|
12024
|
-
return absolute;
|
|
12025
|
-
}
|
|
12026
|
-
return resolve(pathStr);
|
|
12027
|
-
}
|
|
12028
|
-
var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk", _legacyStateWarned = false;
|
|
12029
|
-
var init_paths = () => {};
|
|
12030
|
-
|
|
12031
12042
|
// src/config/overlay-schema.ts
|
|
12032
12043
|
var OverlayDocSchema;
|
|
12033
12044
|
var init_overlay_schema = __esm(() => {
|
|
@@ -13182,6 +13193,9 @@ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurr
|
|
|
13182
13193
|
// src/agents/reconcile-default-skills.ts
|
|
13183
13194
|
var warnedMissingPool = new Set;
|
|
13184
13195
|
|
|
13196
|
+
// src/setup/host-capabilities.ts
|
|
13197
|
+
init_paths();
|
|
13198
|
+
|
|
13185
13199
|
// src/agents/compose.ts
|
|
13186
13200
|
var AGENT_UID_MIN = 10001;
|
|
13187
13201
|
var AGENT_UID_MAX = 10999;
|
|
@@ -4180,19 +4180,30 @@ var init_schema = __esm(() => {
|
|
|
4180
4180
|
stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
|
|
4181
4181
|
stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive in-place stream edits " + "during a turn. Lower = more responsive stream, higher = fewer API " + "calls. Floored at 250 by draft-stream itself. Default 400 ms for DMs " + "and 1000 ms for groups/forums (respects Telegram's ~1 edit/sec/message " + "practical ceiling). Override per-agent if a particular agent needs " + "snappier or quieter streaming."),
|
|
4182
4182
|
clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
|
|
4183
|
+
pin_status_while_working: exports_external.boolean().optional().describe("When true (default), the framework SILENTLY pins the already-" + "rendered status message while its work is in-flight and auto-unpins " + "it on completion — the per-turn activity/status message (foreground) " + "and the '\uD83D\uDEE0 Worker' background-worker message. Keeps in-flight work " + "in view when the conversation scrolls past it (fast turns, stacked " + "background workers, long turns). No new surface is rendered; it pins " + "a message the chat already owns. The pin never buzzes the device. " + "The ONE sanctioned pin under chat-is-the-single-source-of-truth. " + "Set false to disable. Per-agent override; cascades defaults → " + "profile → agent (per-key)."),
|
|
4183
4184
|
hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
|
|
4184
4185
|
inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes — suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
|
|
4185
4186
|
orphan_promotion_ms: exports_external.number().int().nonnegative().optional().describe("How long (ms) a parent turn waits for a sub-agent JSONL watcher " + "to deliver sub_agent_started before the heartbeat promotes the spawn " + "to a synthesised 'running' row. Default 5000. Set to 0 to disable " + "orphan promotion entirely."),
|
|
4186
4187
|
cold_sub_agent_threshold_ms: exports_external.number().int().nonnegative().optional().describe("JSONL-cold threshold (ms). When a running sub-agent emits no events " + "for this long, the heartbeat synthesises a turn_end for it so the " + "deferred-completion path can proceed. Default 30000. Set to 0 to " + "disable the synthetic close."),
|
|
4187
4188
|
deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),
|
|
4189
|
+
approval_timeout_minutes: exports_external.number().int().nonnegative().optional().describe("Operator approval-card lifetime (minutes) for the tool-use 'Allow " + "once' card and the vault grant decision wait. After this long with " + "no operator tap, the card auto-denies (a TIMEOUT, not a denial — the " + "agent is told not to retry). Default 60. hostd-gated verbs " + "(mcp__hostd__*) keep their own longer window; the hostd " + "config-propose card is not governed by this key."),
|
|
4188
4190
|
sub_agent_tick_interval_ms: exports_external.number().int().nonnegative().optional().describe("Heartbeat tick interval (ms) for sub-agent rendering. Forces a " + "re-render of the elapsed-time counter while sub-agents are running, " + "even during silent stretches between tool calls. Default 10000 (10 s). " + "Set to 0 to disable the elapsed-ticker path."),
|
|
4189
4191
|
edit_budget_threshold: exports_external.number().int().nonnegative().optional().describe("Telegram API edit budget per minute before the progress-card driver " + "falls back to a slower coalesce window. When a chat accumulates more " + "than this many card edits in the trailing 60 s, the driver switches " + "to a wider coalesce interval until the rate drops back. Default 18. " + "Increase if your gateway frequently bumps the Telegram edit-rate ceiling " + "with many parallel sub-agents; decrease for a more conservative buffer."),
|
|
4190
4192
|
stickers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Sticker aliases for the `send_sticker` MCP tool (#576). Maps a " + "short alias name (e.g. 'happy', 'thinking') to a Telegram file_id. " + "Operator-curated — capture file_ids from inbound stickers the user " + "sends and add them here. The agent calls send_sticker(chat_id, " + "alias='happy') and the gateway resolves to the file_id at send " + "time. Aliases enable persona-flavored expressiveness without " + "exposing raw file_ids in the agent prompt. Personal-assistant / " + "health-coach personas benefit; coding agents typically don't " + "configure any."),
|
|
4191
4193
|
voice_in: exports_external.object({
|
|
4192
4194
|
enabled: exports_external.boolean().optional().describe("Master switch for voice-message transcription."),
|
|
4193
4195
|
provider: exports_external.enum(["openai"]).optional().describe("Transcription provider. Only 'openai' (Whisper API) supported in the spike (#578); " + "Groq/Deepgram/local-whisper-cli are follow-up choices."),
|
|
4194
|
-
language: exports_external.string().optional().describe("Optional ISO-639-1 language hint (e.g. 'en', 'fr'). Skips Whisper's auto-detection.")
|
|
4195
|
-
|
|
4196
|
+
language: exports_external.string().optional().describe("Optional ISO-639-1 language hint (e.g. 'en', 'fr'). Skips Whisper's auto-detection."),
|
|
4197
|
+
api_key: exports_external.string().optional().describe("Transcription-provider API key, as a `vault:<key>` reference " + "(e.g. 'vault:openai/api-key' — the default if omitted). The " + "gateway resolves it through the vault broker at use-time and " + "never writes the resolved value to disk or the agent prompt. " + "`switchroom enable voice-in` vault-stores the key and writes " + "this reference for you. A literal key is accepted but " + "discouraged — keep secrets in the vault.")
|
|
4198
|
+
}).optional().describe("Inbound voice-message transcription (#578). When enabled, voice/audio " + "messages from allowlisted users are downloaded, transcribed via the " + "configured provider, and surface to the agent as the user's text. " + "The provider API key is a `vault:` reference (`api_key`, default " + "`vault:openai/api-key`) resolved through the vault broker at " + "use-time — opt-in third-party key, honest-exception per the " + "subscription-honest outcome. Off by default — opt-in per agent. " + "Cascades from defaults.channels.telegram.voice_in. " + "(Migrated from per-agent root in #596 — see consistency unification.)"),
|
|
4199
|
+
voice_out: exports_external.object({
|
|
4200
|
+
enabled: exports_external.boolean().optional().describe("Master switch for spoken-reply (TTS) voice notes."),
|
|
4201
|
+
engine: exports_external.enum(["kokoro", "openai"]).default("kokoro").describe("Synthesis engine. 'kokoro' = local voice sidecar (POST /tts, " + "subscription-honest, no third-party key); 'openai' = OpenAI TTS " + "cloud (honest-exception, requires an `api_key` vault ref). " + "'kokoro' is only active when the host voice verdict is local " + "(SWITCHROOM_VOICE_ENGINE === 'local'); otherwise voice-out is a " + "no-op and replies stay text-only."),
|
|
4202
|
+
voice: exports_external.string().optional().describe("Engine-specific voice id (e.g. a Kokoro voice name or an OpenAI " + "voice like 'alloy'). Optional — the sidecar/engine has its own " + "default when omitted."),
|
|
4203
|
+
reply_mode: exports_external.enum(["voice+text", "voice-only", "on-demand"]).default("voice+text").describe("How the spoken reply accompanies the text. 'voice+text' sends " + "both the normal text reply and a voice note; 'voice-only' sends " + "the voice note and suppresses the text body. 'on-demand' sends " + "the text reply with a single '\uD83D\uDD0A Listen' inline button and " + "synthesizes NO audio until the user taps it — zero GPU/sidecar " + "work unless requested, which keeps the voice pipeline " + "subscription-honest and visible (nothing is generated behind the " + "user's back). The Listen button is injected ONLY when the reply " + "carries no agent-authored buttons, to avoid colliding with the " + "single_use keyboard-strip that protects agent buttons from " + "double-fire. In every mode the text reply is ALWAYS still sent " + "when synthesis fails or the reply exceeds max_chars — the user's " + "answer is never dropped silently."),
|
|
4204
|
+
max_chars: exports_external.number().int().positive().optional().describe("Per-voice-note chunk size (chars). Default 600; clamped to the " + "engine's hard cap (1200). A reply LONGER than this is NOT truncated " + "to text — it's split on sentence/paragraph boundaries and spoken " + "across several sequential voice notes, so the full answer is heard " + "even when the user can't read the screen (driving / cycling). Lower " + "it for snappier individual notes; raise it (up to 1200) for fewer, " + "longer notes."),
|
|
4205
|
+
api_key: exports_external.string().optional().describe("OpenAI TTS API key as a `vault:<key>` reference (only used when " + "engine='openai'; default 'vault:openai/api-key'). Resolved through " + "the vault broker at use-time and never written to disk or the agent " + "prompt. Ignored for engine='kokoro' (the sidecar needs no key).")
|
|
4206
|
+
}).optional().describe("Outbound spoken replies via TTS (PR-C2). When enabled, the gateway " + "synthesizes the agent's text reply into an OGG/Opus voice note and " + "sends it alongside (or instead of) the text, per reply_mode. The " + "'kokoro' engine uses the local voice sidecar (POST /tts) and is only " + "active when the host voice verdict is local; the 'openai' engine is " + "an honest-exception cloud path gated on an `api_key` vault ref. Voice " + "is best-effort and fully non-fatal — any TTS error falls back to the " + "text reply. Off by default — opt-in per agent. " + "Cascades from defaults.channels.telegram.voice_out."),
|
|
4196
4207
|
telegraph: exports_external.object({
|
|
4197
4208
|
enabled: exports_external.boolean().optional().describe("Master switch for Telegraph Instant View publishing."),
|
|
4198
4209
|
threshold: exports_external.number().int().positive().optional().describe("Char count above which a reply is published to Telegraph instead of " + "HTML-chunked into multiple Telegram messages. Default 3000 (≈3 chunks)."),
|
|
@@ -5029,6 +5040,41 @@ var init_merge = __esm(() => {
|
|
|
5029
5040
|
})(mergeAgentConfig ||= {});
|
|
5030
5041
|
});
|
|
5031
5042
|
|
|
5043
|
+
// src/config/paths.ts
|
|
5044
|
+
import { existsSync } from "node:fs";
|
|
5045
|
+
import { resolve } from "node:path";
|
|
5046
|
+
function home() {
|
|
5047
|
+
return process.env.HOME ?? "/root";
|
|
5048
|
+
}
|
|
5049
|
+
function warnLegacyStateOnce(legacy) {
|
|
5050
|
+
if (_legacyStateWarned)
|
|
5051
|
+
return;
|
|
5052
|
+
_legacyStateWarned = true;
|
|
5053
|
+
process.stderr.write(`[switchroom] DEPRECATED: reading legacy state from ${legacy}. ` + "Run `mv ~/.clerk ~/.switchroom` (and rename any top-level `clerk:` " + "key in switchroom.yaml to `switchroom:`). This back-compat shim is " + `REMOVED in v0.13.0 — no automatic migration exists.
|
|
5054
|
+
`);
|
|
5055
|
+
}
|
|
5056
|
+
function resolveDualPath(pathStr) {
|
|
5057
|
+
const h = home();
|
|
5058
|
+
if (pathStr.startsWith("~/")) {
|
|
5059
|
+
const rest = pathStr.slice(2);
|
|
5060
|
+
const absolute = resolve(h, rest);
|
|
5061
|
+
if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
|
|
5062
|
+
const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
|
|
5063
|
+
if (!existsSync(absolute)) {
|
|
5064
|
+
const legacy = resolve(h, LEGACY_STATE_DIR, frag);
|
|
5065
|
+
if (existsSync(legacy)) {
|
|
5066
|
+
warnLegacyStateOnce(legacy);
|
|
5067
|
+
return legacy;
|
|
5068
|
+
}
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
return absolute;
|
|
5072
|
+
}
|
|
5073
|
+
return resolve(pathStr);
|
|
5074
|
+
}
|
|
5075
|
+
var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk", _legacyStateWarned = false;
|
|
5076
|
+
var init_paths = () => {};
|
|
5077
|
+
|
|
5032
5078
|
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
5033
5079
|
var require_identity = __commonJS((exports) => {
|
|
5034
5080
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -11993,41 +12039,6 @@ var init_dist = __esm(() => {
|
|
|
11993
12039
|
$visitAsync = visit.visitAsync;
|
|
11994
12040
|
});
|
|
11995
12041
|
|
|
11996
|
-
// src/config/paths.ts
|
|
11997
|
-
import { existsSync as existsSync4 } from "node:fs";
|
|
11998
|
-
import { resolve as resolve2 } from "node:path";
|
|
11999
|
-
function home() {
|
|
12000
|
-
return process.env.HOME ?? "/root";
|
|
12001
|
-
}
|
|
12002
|
-
function warnLegacyStateOnce(legacy) {
|
|
12003
|
-
if (_legacyStateWarned)
|
|
12004
|
-
return;
|
|
12005
|
-
_legacyStateWarned = true;
|
|
12006
|
-
process.stderr.write(`[switchroom] DEPRECATED: reading legacy state from ${legacy}. ` + "Run `mv ~/.clerk ~/.switchroom` (and rename any top-level `clerk:` " + "key in switchroom.yaml to `switchroom:`). This back-compat shim is " + `REMOVED in v0.13.0 — no automatic migration exists.
|
|
12007
|
-
`);
|
|
12008
|
-
}
|
|
12009
|
-
function resolveDualPath(pathStr) {
|
|
12010
|
-
const h = home();
|
|
12011
|
-
if (pathStr.startsWith("~/")) {
|
|
12012
|
-
const rest = pathStr.slice(2);
|
|
12013
|
-
const absolute = resolve2(h, rest);
|
|
12014
|
-
if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
|
|
12015
|
-
const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
|
|
12016
|
-
if (!existsSync4(absolute)) {
|
|
12017
|
-
const legacy = resolve2(h, LEGACY_STATE_DIR, frag);
|
|
12018
|
-
if (existsSync4(legacy)) {
|
|
12019
|
-
warnLegacyStateOnce(legacy);
|
|
12020
|
-
return legacy;
|
|
12021
|
-
}
|
|
12022
|
-
}
|
|
12023
|
-
}
|
|
12024
|
-
return absolute;
|
|
12025
|
-
}
|
|
12026
|
-
return resolve2(pathStr);
|
|
12027
|
-
}
|
|
12028
|
-
var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk", _legacyStateWarned = false;
|
|
12029
|
-
var init_paths = () => {};
|
|
12030
|
-
|
|
12031
12042
|
// src/config/overlay-schema.ts
|
|
12032
12043
|
var OverlayDocSchema;
|
|
12033
12044
|
var init_overlay_schema = __esm(() => {
|
|
@@ -12726,6 +12737,9 @@ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurr
|
|
|
12726
12737
|
// src/agents/reconcile-default-skills.ts
|
|
12727
12738
|
var warnedMissingPool = new Set;
|
|
12728
12739
|
|
|
12740
|
+
// src/setup/host-capabilities.ts
|
|
12741
|
+
init_paths();
|
|
12742
|
+
|
|
12729
12743
|
// src/agents/compose.ts
|
|
12730
12744
|
var AGENT_UID_MIN = 10001;
|
|
12731
12745
|
var AGENT_UID_MAX = 10999;
|
|
@@ -12750,18 +12764,18 @@ import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:
|
|
|
12750
12764
|
import {
|
|
12751
12765
|
readFileSync as readFileSync3,
|
|
12752
12766
|
writeFileSync,
|
|
12753
|
-
existsSync as
|
|
12767
|
+
existsSync as existsSync3,
|
|
12754
12768
|
renameSync,
|
|
12755
12769
|
mkdirSync,
|
|
12756
12770
|
unlinkSync as unlinkSync2,
|
|
12757
12771
|
lstatSync,
|
|
12758
12772
|
realpathSync
|
|
12759
12773
|
} from "node:fs";
|
|
12760
|
-
import { dirname, basename, resolve } from "node:path";
|
|
12774
|
+
import { dirname, basename, resolve as resolve2 } from "node:path";
|
|
12761
12775
|
|
|
12762
12776
|
// src/vault/flock.ts
|
|
12763
12777
|
import {
|
|
12764
|
-
existsSync,
|
|
12778
|
+
existsSync as existsSync2,
|
|
12765
12779
|
openSync,
|
|
12766
12780
|
closeSync,
|
|
12767
12781
|
writeSync,
|
|
@@ -12795,7 +12809,7 @@ function readLockHolder(lockPath) {
|
|
|
12795
12809
|
}
|
|
12796
12810
|
function pidIsLive(pid) {
|
|
12797
12811
|
if (process.platform === "linux") {
|
|
12798
|
-
return
|
|
12812
|
+
return existsSync2(`/proc/${pid}`);
|
|
12799
12813
|
}
|
|
12800
12814
|
try {
|
|
12801
12815
|
process.kill(pid, 0);
|
|
@@ -12857,7 +12871,7 @@ function lockFileMtimeIsOlderThan(lockPath, budgetMs) {
|
|
|
12857
12871
|
var SENTINEL_DIR_RECENT_WRITE_MS = 60000;
|
|
12858
12872
|
function clearStaleSentinelDir(lockPath) {
|
|
12859
12873
|
try {
|
|
12860
|
-
if (!
|
|
12874
|
+
if (!existsSync2(lockPath))
|
|
12861
12875
|
return true;
|
|
12862
12876
|
const s = statSync(lockPath);
|
|
12863
12877
|
if (!s.isDirectory())
|
|
@@ -12990,18 +13004,18 @@ var SCRYPT_MAXMEM = 128 * 1024 * 1024;
|
|
|
12990
13004
|
function atomicWriteFileSync(path, data, mode) {
|
|
12991
13005
|
let effectivePath = path;
|
|
12992
13006
|
try {
|
|
12993
|
-
if (
|
|
13007
|
+
if (existsSync3(path) && lstatSync(path).isSymbolicLink()) {
|
|
12994
13008
|
effectivePath = realpathSync(path);
|
|
12995
13009
|
}
|
|
12996
13010
|
} catch {}
|
|
12997
|
-
const dir = dirname(
|
|
12998
|
-
const tmp =
|
|
13011
|
+
const dir = dirname(resolve2(effectivePath));
|
|
13012
|
+
const tmp = resolve2(dir, `.${basename(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
12999
13013
|
try {
|
|
13000
13014
|
writeFileSync(tmp, data, { encoding: "utf8", mode });
|
|
13001
13015
|
renameSync(tmp, effectivePath);
|
|
13002
13016
|
} catch (err) {
|
|
13003
13017
|
try {
|
|
13004
|
-
if (
|
|
13018
|
+
if (existsSync3(tmp))
|
|
13005
13019
|
unlinkSync2(tmp);
|
|
13006
13020
|
} catch {}
|
|
13007
13021
|
throw err;
|
|
@@ -13060,7 +13074,7 @@ function normalizeSecrets(raw) {
|
|
|
13060
13074
|
return out;
|
|
13061
13075
|
}
|
|
13062
13076
|
function openVault(passphrase, vaultPath) {
|
|
13063
|
-
if (!
|
|
13077
|
+
if (!existsSync3(vaultPath)) {
|
|
13064
13078
|
throw new VaultError(`Vault file not found: ${vaultPath}`);
|
|
13065
13079
|
}
|
|
13066
13080
|
let vaultFile;
|
|
@@ -13087,7 +13101,7 @@ function openVault(passphrase, vaultPath) {
|
|
|
13087
13101
|
return normalizeSecrets(vaultData.secrets ?? {});
|
|
13088
13102
|
}
|
|
13089
13103
|
function saveVault(passphrase, vaultPath, secrets) {
|
|
13090
|
-
if (!
|
|
13104
|
+
if (!existsSync3(vaultPath)) {
|
|
13091
13105
|
throw new VaultError(`Vault file not found: ${vaultPath}`);
|
|
13092
13106
|
}
|
|
13093
13107
|
let releaseLock = null;
|
|
@@ -13138,7 +13152,7 @@ function acquireVaultLock(vaultPath) {
|
|
|
13138
13152
|
import {
|
|
13139
13153
|
copyFileSync,
|
|
13140
13154
|
chmodSync,
|
|
13141
|
-
existsSync as
|
|
13155
|
+
existsSync as existsSync4,
|
|
13142
13156
|
fsyncSync as fsyncSync3,
|
|
13143
13157
|
lstatSync as lstatSync2,
|
|
13144
13158
|
mkdirSync as mkdirSync2,
|
|
@@ -13152,8 +13166,8 @@ import {
|
|
|
13152
13166
|
} from "node:fs";
|
|
13153
13167
|
import { createHash as createHash2 } from "node:crypto";
|
|
13154
13168
|
import { basename as basename2, dirname as dirname2, join } from "node:path";
|
|
13155
|
-
function vaultLayoutPaths(
|
|
13156
|
-
const switchroomRoot = join(
|
|
13169
|
+
function vaultLayoutPaths(home2) {
|
|
13170
|
+
const switchroomRoot = join(home2, ".switchroom");
|
|
13157
13171
|
return {
|
|
13158
13172
|
oldPath: join(switchroomRoot, "vault.enc"),
|
|
13159
13173
|
newPath: join(switchroomRoot, "vault", "vault.enc"),
|
|
@@ -13161,16 +13175,16 @@ function vaultLayoutPaths(home) {
|
|
|
13161
13175
|
switchroomRoot
|
|
13162
13176
|
};
|
|
13163
13177
|
}
|
|
13164
|
-
function inspectVaultLayout(
|
|
13165
|
-
return runMigration(
|
|
13178
|
+
function inspectVaultLayout(home2) {
|
|
13179
|
+
return runMigration(home2, { dryRun: true });
|
|
13166
13180
|
}
|
|
13167
|
-
function runMigration(
|
|
13168
|
-
const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(
|
|
13169
|
-
const lockTarget =
|
|
13181
|
+
function runMigration(home2, opts) {
|
|
13182
|
+
const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(home2);
|
|
13183
|
+
const lockTarget = existsSync4(newPath) ? newPath : existsSync4(oldPath) ? oldPath : null;
|
|
13170
13184
|
const release = !opts.dryRun && lockTarget !== null ? acquireVaultLock(lockTarget) : null;
|
|
13171
13185
|
try {
|
|
13172
13186
|
const oldStat = lstatSyncOrNull(oldPath);
|
|
13173
|
-
const newExists =
|
|
13187
|
+
const newExists = existsSync4(newPath);
|
|
13174
13188
|
if (oldStat === null && !newExists) {
|
|
13175
13189
|
return { kind: "no-vault" };
|
|
13176
13190
|
}
|
|
@@ -13239,7 +13253,7 @@ function sha256File(path) {
|
|
|
13239
13253
|
}
|
|
13240
13254
|
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
13241
13255
|
const tmp = join(dirname2(target), `.${basename2(target)}.symlink-tmp`);
|
|
13242
|
-
if (
|
|
13256
|
+
if (existsSync4(tmp)) {
|
|
13243
13257
|
try {
|
|
13244
13258
|
unlinkSync3(tmp);
|
|
13245
13259
|
} catch {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
|
-
"version": "
|
|
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.38",
|
|
4
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.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
@@ -43,7 +43,9 @@ tools is to let you do the edit yourself.
|
|
|
43
43
|
still shares your memory + tools but drops your accumulated conversation
|
|
44
44
|
context). A **daily/weekly** cron defaults to a **full turn in your live
|
|
45
45
|
session** (Tier 2: your model, your whole context). So routine frequent
|
|
46
|
-
checks are already cheap without you doing anything.
|
|
46
|
+
checks are already cheap without you doing anything. Cron-fresh sessions
|
|
47
|
+
render rich markdown too, so scheduled/digest/daily-memory output should
|
|
48
|
+
use **bold** section headers + `code` for identifiers, just like a live reply.
|
|
47
49
|
- *`model: "sonnet"` / `context: "fresh"`* — force the cheap Tier-1 session
|
|
48
50
|
even for a **daily/weekly** self-contained job (summarise a feed, format a
|
|
49
51
|
digest) — overrides the cadence default. *`context: "agent"`* does the
|
|
@@ -53,7 +55,10 @@ tools is to let you do the edit yourself.
|
|
|
53
55
|
ping — text fully determined, no thinking) — ask the **operator** for a
|
|
54
56
|
**`kind: action`**: it runs **model-free, zero tokens** (no session at all),
|
|
55
57
|
posting your fixed/templated text or firing a fixed request. The cheapest
|
|
56
|
-
tier there is.
|
|
58
|
+
tier there is. The action `text` is rendered as full GFM markdown
|
|
59
|
+
(**bold**, _italic_, `code`, ~~strikethrough~~, links) — so a literal `*`
|
|
60
|
+
or `_` in fixed text becomes formatting unless you set `parse_mode: text`,
|
|
61
|
+
which sends the string verbatim (the escape hatch).
|
|
57
62
|
- *"Only act when X changes"* — don't poll with a frequent prompt cron (every
|
|
58
63
|
fire is a wasted turn when nothing changed). Ask the **operator** for a
|
|
59
64
|
**`kind: poll`** (model-free check, e.g. a webpage/API — only a *change*
|