switchroom 0.18.6 → 0.18.7
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 +1 -0
- package/dist/auth-broker/index.js +1 -0
- package/dist/cli/autoaccept-poll.js +140 -33
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +269 -56
- package/dist/host-control/main.js +2 -1
- package/dist/vault/approvals/kernel-server.js +1 -0
- package/dist/vault/broker/server.js +1 -0
- package/package.json +3 -3
- package/profiles/_base/cron-session.sh.hbs +55 -16
- package/profiles/_base/start.sh.hbs +35 -16
- package/profiles/default/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +22 -0
- package/telegram-plugin/dist/gateway/gateway.js +1937 -580
- package/telegram-plugin/dist/server.js +24 -0
- package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
- package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
- package/telegram-plugin/gateway/gateway.ts +1071 -130
- package/telegram-plugin/gateway/inbound-spool.ts +2 -1
- package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
- package/telegram-plugin/gateway/inject-handler.ts +17 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
- package/telegram-plugin/gateway/ipc-server.ts +40 -0
- package/telegram-plugin/gateway/model-command.ts +212 -51
- package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
- package/telegram-plugin/gateway/pending-card-store.ts +173 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
- package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
- package/telegram-plugin/gateway/session-model-source.ts +73 -0
- package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
- package/telegram-plugin/model-label.ts +69 -0
- package/telegram-plugin/operator-events.ts +24 -0
- package/telegram-plugin/permission-diff.ts +128 -0
- package/telegram-plugin/registry/subagents-schema.ts +80 -1
- package/telegram-plugin/registry/subagents.test.ts +90 -0
- package/telegram-plugin/session-tail.ts +28 -0
- package/telegram-plugin/silent-end.ts +49 -4
- package/telegram-plugin/subagent-watcher.ts +222 -37
- package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
- package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
- package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +4 -2
- package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
- package/telegram-plugin/tests/model-command.test.ts +202 -42
- package/telegram-plugin/tests/model-label.test.ts +64 -0
- package/telegram-plugin/tests/operator-events.test.ts +1 -0
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
- package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
- package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
- package/telegram-plugin/tests/permission-diff.test.ts +111 -0
- package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
- package/telegram-plugin/tests/session-model-source.test.ts +67 -0
- package/telegram-plugin/tests/session-tail.test.ts +64 -0
- package/telegram-plugin/tests/silent-end.test.ts +46 -1
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
- package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
- package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
- package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
- package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +54 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +11 -0
- package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
- package/telegram-plugin/tool-activity-summary.ts +22 -2
- package/telegram-plugin/typing-wrap.ts +72 -25
- package/telegram-plugin/worker-activity-feed.ts +9 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -13741,6 +13741,7 @@ var init_schema = __esm(() => {
|
|
|
13741
13741
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
13742
13742
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
13743
13743
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
|
|
13744
|
+
boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
|
|
13744
13745
|
session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
|
|
13745
13746
|
session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
|
|
13746
13747
|
}).optional();
|
|
@@ -26944,6 +26945,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
26944
26945
|
handoffEnabled: agentConfig.session_continuity?.enabled !== false,
|
|
26945
26946
|
resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
|
|
26946
26947
|
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
|
|
26948
|
+
bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight",
|
|
26947
26949
|
resumeModeHasContinuePath: (() => {
|
|
26948
26950
|
const mode = agentConfig.session_continuity?.resume_mode ?? "handoff";
|
|
26949
26951
|
return mode === "auto" || mode === "continue";
|
|
@@ -28046,7 +28048,8 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
28046
28048
|
sessionMaxTurns: agentConfig.session?.max_turns,
|
|
28047
28049
|
handoffEnabled: agentConfig.session_continuity?.enabled !== false,
|
|
28048
28050
|
resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
|
|
28049
|
-
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000
|
|
28051
|
+
resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
|
|
28052
|
+
bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight"
|
|
28050
28053
|
};
|
|
28051
28054
|
const beforeStartSh = existsSync15(startShPath) ? readFileSync13(startShPath, "utf-8") : "";
|
|
28052
28055
|
const afterStartSh = renderTemplate(join10(basePath, "start.sh.hbs"), startShContext);
|
|
@@ -30047,7 +30050,25 @@ import { chownSync as chownSync2 } from "node:fs";
|
|
|
30047
30050
|
import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
|
|
30048
30051
|
import { homedir as homedir6 } from "node:os";
|
|
30049
30052
|
import { basename as basename4, dirname as dirname6, join as join13 } from "node:path";
|
|
30050
|
-
|
|
30053
|
+
function agentHadLiteLLMRouting(composeContent, agentName) {
|
|
30054
|
+
const lines = composeContent.split(`
|
|
30055
|
+
`);
|
|
30056
|
+
const header = ` agent-${agentName}:`;
|
|
30057
|
+
let i = lines.indexOf(header);
|
|
30058
|
+
if (i < 0)
|
|
30059
|
+
return false;
|
|
30060
|
+
for (i = i + 1;i < lines.length; i++) {
|
|
30061
|
+
const line = lines[i];
|
|
30062
|
+
if (/^ {2}\S/.test(line))
|
|
30063
|
+
break;
|
|
30064
|
+
if (/^\S/.test(line))
|
|
30065
|
+
break;
|
|
30066
|
+
if (/^\s+SWITCHROOM_LITELLM:\s*"1"\s*$/.test(line))
|
|
30067
|
+
return true;
|
|
30068
|
+
}
|
|
30069
|
+
return false;
|
|
30070
|
+
}
|
|
30071
|
+
async function resolveLiteLLMConfirmedAgents(config, previousCompose, deps) {
|
|
30051
30072
|
const confirmed = new Set;
|
|
30052
30073
|
const topEnabled = config.litellm?.enabled ?? false;
|
|
30053
30074
|
const optedIn = [];
|
|
@@ -30062,16 +30083,52 @@ async function resolveLiteLLMConfirmedAgents(config) {
|
|
|
30062
30083
|
}
|
|
30063
30084
|
if (optedIn.length === 0)
|
|
30064
30085
|
return confirmed;
|
|
30065
|
-
|
|
30066
|
-
|
|
30067
|
-
|
|
30068
|
-
|
|
30069
|
-
|
|
30070
|
-
|
|
30071
|
-
confirmed.add(name);
|
|
30072
|
-
} catch {}
|
|
30086
|
+
let getKey = deps?.getKey ?? null;
|
|
30087
|
+
if (!getKey) {
|
|
30088
|
+
try {
|
|
30089
|
+
({ getViaBrokerStructured: getKey } = await Promise.resolve().then(() => (init_client(), exports_client)));
|
|
30090
|
+
} catch {
|
|
30091
|
+
getKey = null;
|
|
30073
30092
|
}
|
|
30074
|
-
}
|
|
30093
|
+
}
|
|
30094
|
+
const unreachableAgents = [];
|
|
30095
|
+
for (const name of optedIn) {
|
|
30096
|
+
if (!getKey) {
|
|
30097
|
+
unreachableAgents.push(name);
|
|
30098
|
+
continue;
|
|
30099
|
+
}
|
|
30100
|
+
try {
|
|
30101
|
+
const r = await getKey(`litellm/${name}/api-key`);
|
|
30102
|
+
if (r.kind === "ok") {
|
|
30103
|
+
confirmed.add(name);
|
|
30104
|
+
} else if (r.kind === "unreachable") {
|
|
30105
|
+
unreachableAgents.push(name);
|
|
30106
|
+
} else if (r.kind === "denied" && r.code !== "DENIED") {
|
|
30107
|
+
unreachableAgents.push(name);
|
|
30108
|
+
}
|
|
30109
|
+
} catch {
|
|
30110
|
+
unreachableAgents.push(name);
|
|
30111
|
+
}
|
|
30112
|
+
}
|
|
30113
|
+
if (unreachableAgents.length > 0) {
|
|
30114
|
+
const preserved = [];
|
|
30115
|
+
const couldNotPreserve = [];
|
|
30116
|
+
for (const name of unreachableAgents) {
|
|
30117
|
+
if (previousCompose && agentHadLiteLLMRouting(previousCompose, name)) {
|
|
30118
|
+
confirmed.add(name);
|
|
30119
|
+
preserved.push(name);
|
|
30120
|
+
} else {
|
|
30121
|
+
couldNotPreserve.push(name);
|
|
30122
|
+
}
|
|
30123
|
+
}
|
|
30124
|
+
console.warn(`switchroom: vault-broker unreachable or unable to answer (locked/internal) while resolving LiteLLM key status for ${unreachableAgents.length} opted-in agent(s): ${unreachableAgents.join(", ")}. Refusing to silently strip proxy routing (that would boot them on untracked direct OAuth). Re-run \`switchroom apply\` once the broker is back to reconcile.`);
|
|
30125
|
+
if (preserved.length > 0) {
|
|
30126
|
+
console.warn(`switchroom: PRESERVED existing proxy routing from the on-disk compose for: ${preserved.join(", ")}.`);
|
|
30127
|
+
}
|
|
30128
|
+
if (couldNotPreserve.length > 0) {
|
|
30129
|
+
console.warn(`switchroom: no prior routing verdict on disk for: ${couldNotPreserve.join(", ")} ` + `\u2014 leaving them unrouted (they were not previously routed).`);
|
|
30130
|
+
}
|
|
30131
|
+
}
|
|
30075
30132
|
return confirmed;
|
|
30076
30133
|
}
|
|
30077
30134
|
function resolveHostSwitchroomConfigPath(rawPath) {
|
|
@@ -30109,7 +30166,13 @@ async function computeComposeContent(opts) {
|
|
|
30109
30166
|
const imageTag = resolveImageTag(release);
|
|
30110
30167
|
const operatorUid = resolveOperatorUid();
|
|
30111
30168
|
const resolvedConfigPath = opts.switchroomConfigPath !== undefined ? resolveHostSwitchroomConfigPath(opts.switchroomConfigPath) : undefined;
|
|
30112
|
-
|
|
30169
|
+
let previous = null;
|
|
30170
|
+
try {
|
|
30171
|
+
previous = await readFile(opts.composePath, "utf8");
|
|
30172
|
+
} catch {
|
|
30173
|
+
previous = null;
|
|
30174
|
+
}
|
|
30175
|
+
const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config, previous);
|
|
30113
30176
|
const content = generateCompose({
|
|
30114
30177
|
config: opts.config,
|
|
30115
30178
|
imageTag,
|
|
@@ -30121,12 +30184,6 @@ async function computeComposeContent(opts) {
|
|
|
30121
30184
|
switchroomConfigPath: resolvedConfigPath,
|
|
30122
30185
|
operatorUid
|
|
30123
30186
|
});
|
|
30124
|
-
let previous = null;
|
|
30125
|
-
try {
|
|
30126
|
-
previous = await readFile(opts.composePath, "utf8");
|
|
30127
|
-
} catch {
|
|
30128
|
-
previous = null;
|
|
30129
|
-
}
|
|
30130
30187
|
const previousImageTag = previous ? AGENT_IMAGE_TAG_RE.exec(previous)?.[1] ?? null : null;
|
|
30131
30188
|
return { content, imageTag, previous, previousImageTag };
|
|
30132
30189
|
}
|
|
@@ -48883,6 +48940,7 @@ var init_install_detect = () => {};
|
|
|
48883
48940
|
// src/litellm/provision.ts
|
|
48884
48941
|
var exports_provision = {};
|
|
48885
48942
|
__export(exports_provision, {
|
|
48943
|
+
validateKey: () => validateKey,
|
|
48886
48944
|
ensureTeam: () => ensureTeam,
|
|
48887
48945
|
ensureKey: () => ensureKey,
|
|
48888
48946
|
LiteLLMProvisionError: () => LiteLLMProvisionError
|
|
@@ -48904,6 +48962,12 @@ function looksLikeAlreadyExists(status, body) {
|
|
|
48904
48962
|
return true;
|
|
48905
48963
|
return false;
|
|
48906
48964
|
}
|
|
48965
|
+
function looksLikeUnknownKey(status, body) {
|
|
48966
|
+
if (status !== 400 && status !== 401 && status !== 403 && status !== 404)
|
|
48967
|
+
return false;
|
|
48968
|
+
const t = body.toLowerCase();
|
|
48969
|
+
return t.includes("not found") || t.includes("does not exist") || t.includes("no such key") || t.includes("no key found");
|
|
48970
|
+
}
|
|
48907
48971
|
async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
|
|
48908
48972
|
const url = `${normalizeBase(baseUrl)}/team/new`;
|
|
48909
48973
|
let resp;
|
|
@@ -48923,7 +48987,7 @@ async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
|
|
|
48923
48987
|
return;
|
|
48924
48988
|
throw new LiteLLMProvisionError(`LiteLLM /team/new returned ${resp.status}`, resp.status, body);
|
|
48925
48989
|
}
|
|
48926
|
-
async function
|
|
48990
|
+
async function generateKeyOnce(opts, fetchFn) {
|
|
48927
48991
|
const url = `${normalizeBase(opts.baseUrl)}/key/generate`;
|
|
48928
48992
|
const payload = {
|
|
48929
48993
|
key_alias: opts.alias
|
|
@@ -48943,23 +49007,129 @@ async function ensureKey(opts, fetchFn = fetch) {
|
|
|
48943
49007
|
body: JSON.stringify(payload)
|
|
48944
49008
|
});
|
|
48945
49009
|
} catch (err) {
|
|
48946
|
-
|
|
49010
|
+
return {
|
|
49011
|
+
kind: "error",
|
|
49012
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`)
|
|
49013
|
+
};
|
|
48947
49014
|
}
|
|
48948
49015
|
if (!resp.ok) {
|
|
48949
49016
|
const body = await safeText(resp);
|
|
48950
|
-
|
|
49017
|
+
if (looksLikeAlreadyExists(resp.status, body)) {
|
|
49018
|
+
return { kind: "duplicate-alias", status: resp.status, body };
|
|
49019
|
+
}
|
|
49020
|
+
return {
|
|
49021
|
+
kind: "error",
|
|
49022
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body)
|
|
49023
|
+
};
|
|
48951
49024
|
}
|
|
48952
49025
|
let json;
|
|
48953
49026
|
try {
|
|
48954
49027
|
json = await resp.json();
|
|
48955
49028
|
} catch (err) {
|
|
48956
|
-
|
|
49029
|
+
return {
|
|
49030
|
+
kind: "error",
|
|
49031
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status)
|
|
49032
|
+
};
|
|
48957
49033
|
}
|
|
48958
49034
|
const key = json?.key;
|
|
48959
49035
|
if (typeof key !== "string" || key.length === 0) {
|
|
48960
|
-
|
|
49036
|
+
return {
|
|
49037
|
+
kind: "error",
|
|
49038
|
+
error: new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json))
|
|
49039
|
+
};
|
|
48961
49040
|
}
|
|
48962
|
-
return { key };
|
|
49041
|
+
return { kind: "ok", key };
|
|
49042
|
+
}
|
|
49043
|
+
async function resolveTokensForAlias(baseUrl, masterKey, alias, fetchFn) {
|
|
49044
|
+
const url = `${normalizeBase(baseUrl)}/key/list?key_alias=${encodeURIComponent(alias)}` + `&return_full_object=true&include_team_keys=true`;
|
|
49045
|
+
let resp;
|
|
49046
|
+
try {
|
|
49047
|
+
resp = await fetchFn(url, { method: "GET", headers: authHeaders3(masterKey) });
|
|
49048
|
+
} catch {
|
|
49049
|
+
return [];
|
|
49050
|
+
}
|
|
49051
|
+
if (!resp.ok)
|
|
49052
|
+
return [];
|
|
49053
|
+
let json;
|
|
49054
|
+
try {
|
|
49055
|
+
json = await resp.json();
|
|
49056
|
+
} catch {
|
|
49057
|
+
return [];
|
|
49058
|
+
}
|
|
49059
|
+
const keys = json?.keys;
|
|
49060
|
+
if (!Array.isArray(keys))
|
|
49061
|
+
return [];
|
|
49062
|
+
const tokens = [];
|
|
49063
|
+
for (const k of keys) {
|
|
49064
|
+
if (typeof k === "string" && k.length > 0)
|
|
49065
|
+
tokens.push(k);
|
|
49066
|
+
else if (k && typeof k === "object") {
|
|
49067
|
+
const tok = k.token;
|
|
49068
|
+
const raw = k.key;
|
|
49069
|
+
if (typeof tok === "string" && tok.length > 0)
|
|
49070
|
+
tokens.push(tok);
|
|
49071
|
+
else if (typeof raw === "string" && raw.length > 0)
|
|
49072
|
+
tokens.push(raw);
|
|
49073
|
+
}
|
|
49074
|
+
}
|
|
49075
|
+
return tokens;
|
|
49076
|
+
}
|
|
49077
|
+
async function deleteOrphanedAlias(opts, fetchFn, log) {
|
|
49078
|
+
const tokens = await resolveTokensForAlias(opts.baseUrl, opts.masterKey, opts.alias, fetchFn);
|
|
49079
|
+
const body = { key_aliases: [opts.alias] };
|
|
49080
|
+
if (tokens.length > 0)
|
|
49081
|
+
body.keys = tokens;
|
|
49082
|
+
const manual = `Manual remediation: delete the key with alias '${opts.alias}' in the LiteLLM ` + `admin UI, or run ` + `\`curl -X POST "${normalizeBase(opts.baseUrl)}/key/delete" -H "Authorization: Bearer <master-key>" ` + `-H "content-type: application/json" -d '{"key_aliases":["${opts.alias}"]}'\`, ` + `then re-run \`switchroom apply\`.`;
|
|
49083
|
+
const url = `${normalizeBase(opts.baseUrl)}/key/delete`;
|
|
49084
|
+
let resp;
|
|
49085
|
+
try {
|
|
49086
|
+
resp = await fetchFn(url, {
|
|
49087
|
+
method: "POST",
|
|
49088
|
+
headers: authHeaders3(opts.masterKey),
|
|
49089
|
+
body: JSON.stringify(body)
|
|
49090
|
+
});
|
|
49091
|
+
} catch (err) {
|
|
49092
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/delete request failed while recovering orphaned alias ` + `'${opts.alias}': ${err.message}. ${manual}`);
|
|
49093
|
+
}
|
|
49094
|
+
if (!resp.ok) {
|
|
49095
|
+
const errBody = await safeText(resp);
|
|
49096
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/delete returned ${resp.status} while recovering orphaned ` + `alias '${opts.alias}'. ${manual}`, resp.status, errBody);
|
|
49097
|
+
}
|
|
49098
|
+
log(`litellm: deleted orphaned key(s) for alias '${opts.alias}'` + (tokens.length > 0 ? ` (${tokens.length} token(s) resolved)` : "") + ` \u2014 regenerating`);
|
|
49099
|
+
}
|
|
49100
|
+
async function ensureKey(opts, fetchFn = fetch) {
|
|
49101
|
+
const log = opts.log ?? (() => {});
|
|
49102
|
+
const first = await generateKeyOnce(opts, fetchFn);
|
|
49103
|
+
if (first.kind === "ok")
|
|
49104
|
+
return { key: first.key };
|
|
49105
|
+
if (first.kind === "error")
|
|
49106
|
+
throw first.error;
|
|
49107
|
+
log(`litellm: key_alias '${opts.alias}' already exists in LiteLLM but the vault ` + `has no recoverable key (orphaned by a prior failed provision) \u2014 ` + `self-healing: deleting the orphan and regenerating`);
|
|
49108
|
+
await deleteOrphanedAlias(opts, fetchFn, log);
|
|
49109
|
+
const second = await generateKeyOnce(opts, fetchFn);
|
|
49110
|
+
if (second.kind === "ok") {
|
|
49111
|
+
log(`litellm: regenerated key for alias '${opts.alias}' after orphan recovery`);
|
|
49112
|
+
return { key: second.key };
|
|
49113
|
+
}
|
|
49114
|
+
if (second.kind === "duplicate-alias") {
|
|
49115
|
+
throw new LiteLLMProvisionError(`LiteLLM /key/generate still reports alias '${opts.alias}' already exists ` + `after deleting the orphan \u2014 the delete did not take effect. Manual ` + `remediation: remove the key with alias '${opts.alias}' via the LiteLLM ` + `admin UI (or POST /key/delete {"key_aliases":["${opts.alias}"]}) then ` + `re-run \`switchroom apply\`.`, second.status, second.body);
|
|
49116
|
+
}
|
|
49117
|
+
throw second.error;
|
|
49118
|
+
}
|
|
49119
|
+
async function validateKey(opts, fetchFn = fetch) {
|
|
49120
|
+
const url = `${normalizeBase(opts.baseUrl)}/key/info?key=${encodeURIComponent(opts.key)}`;
|
|
49121
|
+
let resp;
|
|
49122
|
+
try {
|
|
49123
|
+
resp = await fetchFn(url, { method: "GET", headers: authHeaders3(opts.masterKey) });
|
|
49124
|
+
} catch (err) {
|
|
49125
|
+
return { kind: "unreachable", detail: err.message };
|
|
49126
|
+
}
|
|
49127
|
+
if (resp.ok)
|
|
49128
|
+
return { kind: "valid" };
|
|
49129
|
+
const body = await safeText(resp);
|
|
49130
|
+
if (looksLikeUnknownKey(resp.status, body))
|
|
49131
|
+
return { kind: "unknown" };
|
|
49132
|
+
return { kind: "unreachable", detail: `HTTP ${resp.status}: ${body.slice(0, 200)}` };
|
|
48963
49133
|
}
|
|
48964
49134
|
async function safeText(resp) {
|
|
48965
49135
|
try {
|
|
@@ -49154,7 +49324,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49154
49324
|
const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
|
|
49155
49325
|
if (optedIn.length === 0 && !needsHindsight)
|
|
49156
49326
|
return;
|
|
49157
|
-
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
|
|
49327
|
+
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
|
|
49158
49328
|
Promise.resolve().then(() => (init_client(), exports_client)),
|
|
49159
49329
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
49160
49330
|
Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
|
|
@@ -49176,7 +49346,21 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49176
49346
|
unprovisionedNew.push(name);
|
|
49177
49347
|
}
|
|
49178
49348
|
}
|
|
49179
|
-
|
|
49349
|
+
let hindsightPending = false;
|
|
49350
|
+
if (needsHindsight) {
|
|
49351
|
+
const h = await getViaBrokerStructured3("litellm/hindsight/api-key");
|
|
49352
|
+
if (h.kind === "ok") {
|
|
49353
|
+
hindsightPending = false;
|
|
49354
|
+
writeOut(source_default.gray(` ~ litellm/hindsight: key already provisioned (skipped) \u2014 unaffected by missing passphrase.
|
|
49355
|
+
`));
|
|
49356
|
+
} else if (h.kind === "unreachable") {
|
|
49357
|
+
hindsightPending = false;
|
|
49358
|
+
ctx.writeErr(source_default.yellow(` ! litellm/hindsight: vault-broker unreachable \u2014 could not verify the ` + `service key; it keeps its previous state.
|
|
49359
|
+
`));
|
|
49360
|
+
} else {
|
|
49361
|
+
hindsightPending = true;
|
|
49362
|
+
}
|
|
49363
|
+
}
|
|
49180
49364
|
if (alreadyProvisioned.length > 0) {
|
|
49181
49365
|
writeOut(source_default.gray(` ~ litellm: ${alreadyProvisioned.length} agent(s) already provisioned (${alreadyProvisioned.join(", ")}) \u2014 unaffected by missing passphrase.
|
|
49182
49366
|
`));
|
|
@@ -49227,43 +49411,62 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49227
49411
|
continue;
|
|
49228
49412
|
}
|
|
49229
49413
|
const vaultKey = `litellm/${name}/api-key`;
|
|
49414
|
+
const alias = `agent:${name}`;
|
|
49230
49415
|
const existing = await getViaBrokerStructured2(vaultKey);
|
|
49231
|
-
if (existing.kind === "ok") {
|
|
49232
|
-
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
|
|
49233
|
-
`));
|
|
49234
|
-
if (configText !== null) {
|
|
49235
|
-
const after = addAgentSecret2(configText, name, vaultKey);
|
|
49236
|
-
if (after !== configText) {
|
|
49237
|
-
configText = after;
|
|
49238
|
-
pendingConfigEdits = true;
|
|
49239
|
-
}
|
|
49240
|
-
}
|
|
49241
|
-
continue;
|
|
49242
|
-
}
|
|
49243
49416
|
if (existing.kind === "unreachable") {
|
|
49244
49417
|
ctx.writeErr(source_default.yellow(` ! litellm/${name}: vault-broker unreachable (${existing.msg ?? "no detail"}) \u2014 ` + `skipping key provisioning; agent keeps its previous routing env.
|
|
49245
49418
|
`));
|
|
49246
49419
|
continue;
|
|
49247
49420
|
}
|
|
49248
49421
|
let masterKey = adminKeyRef;
|
|
49422
|
+
let masterKeyErr = null;
|
|
49249
49423
|
if (isVaultReference(adminKeyRef)) {
|
|
49250
49424
|
const refKey = parseVaultReference(adminKeyRef);
|
|
49251
49425
|
const resolved = await getViaBrokerStructured2(refKey);
|
|
49252
49426
|
if (resolved.kind !== "ok") {
|
|
49253
|
-
|
|
49254
|
-
|
|
49255
|
-
|
|
49256
|
-
|
|
49257
|
-
|
|
49427
|
+
masterKey = null;
|
|
49428
|
+
masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`;
|
|
49429
|
+
} else if (resolved.entry.kind !== "string") {
|
|
49430
|
+
masterKey = null;
|
|
49431
|
+
masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`;
|
|
49432
|
+
} else {
|
|
49433
|
+
masterKey = resolved.entry.value;
|
|
49258
49434
|
}
|
|
49259
|
-
|
|
49260
|
-
|
|
49261
|
-
|
|
49262
|
-
|
|
49263
|
-
|
|
49435
|
+
}
|
|
49436
|
+
if (existing.kind === "ok") {
|
|
49437
|
+
const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
|
|
49438
|
+
let driftReprovision = false;
|
|
49439
|
+
if (storedKey && masterKey) {
|
|
49440
|
+
const validation = await validateKey2({ baseUrl, masterKey, key: storedKey });
|
|
49441
|
+
if (validation.kind === "unknown") {
|
|
49442
|
+
driftReprovision = true;
|
|
49443
|
+
ctx.writeErr(source_default.yellow(` ! litellm/${name}: stored virtual key not recognized by the proxy ` + `(DB drift) \u2014 re-provisioning a fresh key.
|
|
49444
|
+
`));
|
|
49445
|
+
} else if (validation.kind === "unreachable") {
|
|
49446
|
+
ctx.writeErr(source_default.yellow(` ! litellm/${name}: proxy unreachable during key validation (${validation.detail}) \u2014 keeping the stored key unverified.
|
|
49447
|
+
`));
|
|
49448
|
+
} else {
|
|
49449
|
+
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned + validated (skipped)
|
|
49450
|
+
`));
|
|
49451
|
+
}
|
|
49452
|
+
} else {
|
|
49453
|
+
writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
|
|
49454
|
+
`));
|
|
49455
|
+
}
|
|
49456
|
+
if (!driftReprovision) {
|
|
49457
|
+
if (configText !== null) {
|
|
49458
|
+
const after = addAgentSecret2(configText, name, vaultKey);
|
|
49459
|
+
if (after !== configText) {
|
|
49460
|
+
configText = after;
|
|
49461
|
+
pendingConfigEdits = true;
|
|
49462
|
+
}
|
|
49463
|
+
}
|
|
49264
49464
|
continue;
|
|
49265
49465
|
}
|
|
49266
|
-
|
|
49466
|
+
}
|
|
49467
|
+
if (!masterKey) {
|
|
49468
|
+
failures.push({ agent: name, message: masterKeyErr ?? `litellm: admin_key unresolved` });
|
|
49469
|
+
continue;
|
|
49267
49470
|
}
|
|
49268
49471
|
await ensureTeam2(baseUrl, masterKey, team);
|
|
49269
49472
|
const metadata = {
|
|
@@ -49281,9 +49484,11 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49281
49484
|
const { key } = await ensureKey2({
|
|
49282
49485
|
baseUrl,
|
|
49283
49486
|
masterKey,
|
|
49284
|
-
alias
|
|
49487
|
+
alias,
|
|
49285
49488
|
team,
|
|
49286
|
-
metadata
|
|
49489
|
+
metadata,
|
|
49490
|
+
log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/${name}: ${m}
|
|
49491
|
+
`))
|
|
49287
49492
|
});
|
|
49288
49493
|
const put = await putViaBroker2(vaultKey, { kind: "string", value: key }, { passphrase });
|
|
49289
49494
|
if (put.kind !== "ok") {
|
|
@@ -49345,7 +49550,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
49345
49550
|
masterKey,
|
|
49346
49551
|
alias: "service:hindsight",
|
|
49347
49552
|
team,
|
|
49348
|
-
metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} }
|
|
49553
|
+
metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} },
|
|
49554
|
+
log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/hindsight: ${m}
|
|
49555
|
+
`))
|
|
49349
49556
|
});
|
|
49350
49557
|
const put = await putViaBroker2(hindsightVaultKey, { kind: "string", value: key }, { passphrase });
|
|
49351
49558
|
if (put.kind === "ok") {
|
|
@@ -64682,8 +64889,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
64682
64889
|
import { dirname, join } from "node:path";
|
|
64683
64890
|
|
|
64684
64891
|
// src/build-info.ts
|
|
64685
|
-
var VERSION = "0.18.
|
|
64686
|
-
var COMMIT_SHA = "
|
|
64892
|
+
var VERSION = "0.18.7";
|
|
64893
|
+
var COMMIT_SHA = "21478348";
|
|
64687
64894
|
|
|
64688
64895
|
// src/cli/resolve-version.ts
|
|
64689
64896
|
function readPackageVersion() {
|
|
@@ -79554,7 +79761,8 @@ var SUBAGENTS_SCHEMA_SQL = `
|
|
|
79554
79761
|
status TEXT NOT NULL,
|
|
79555
79762
|
result_summary TEXT,
|
|
79556
79763
|
jsonl_agent_id TEXT,
|
|
79557
|
-
parent_agent_id TEXT
|
|
79764
|
+
parent_agent_id TEXT,
|
|
79765
|
+
model TEXT
|
|
79558
79766
|
);
|
|
79559
79767
|
CREATE INDEX IF NOT EXISTS subagents_turn ON subagents(parent_turn_key);
|
|
79560
79768
|
CREATE INDEX IF NOT EXISTS subagents_status ON subagents(status);
|
|
@@ -79570,6 +79778,10 @@ function applySubagentsSchema(db) {
|
|
|
79570
79778
|
if (!hasParentAgentId) {
|
|
79571
79779
|
db.exec("ALTER TABLE subagents ADD COLUMN parent_agent_id TEXT");
|
|
79572
79780
|
}
|
|
79781
|
+
const hasModel = cols.some((c) => c.name === "model");
|
|
79782
|
+
if (!hasModel) {
|
|
79783
|
+
db.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
|
|
79784
|
+
}
|
|
79573
79785
|
db.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
|
|
79574
79786
|
}
|
|
79575
79787
|
function mapSubagentRow(row) {
|
|
@@ -79586,7 +79798,8 @@ function mapSubagentRow(row) {
|
|
|
79586
79798
|
status: row.status,
|
|
79587
79799
|
result_summary: row.result_summary,
|
|
79588
79800
|
jsonl_agent_id: row.jsonl_agent_id,
|
|
79589
|
-
parent_agent_id: row.parent_agent_id ?? null
|
|
79801
|
+
parent_agent_id: row.parent_agent_id ?? null,
|
|
79802
|
+
model: row.model ?? null
|
|
79590
79803
|
};
|
|
79591
79804
|
}
|
|
79592
79805
|
function listSubagents(db, opts = {}) {
|
|
@@ -21495,6 +21495,7 @@ var SessionContinuitySchema = exports_external.object({
|
|
|
21495
21495
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
21496
21496
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
21497
21497
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
|
|
21498
|
+
boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart — a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart — but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
|
|
21498
21499
|
session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
|
|
21499
21500
|
session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
|
|
21500
21501
|
}).optional();
|
|
@@ -28737,7 +28738,7 @@ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:f
|
|
|
28737
28738
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
28738
28739
|
|
|
28739
28740
|
// src/build-info.ts
|
|
28740
|
-
var VERSION = "0.18.
|
|
28741
|
+
var VERSION = "0.18.7";
|
|
28741
28742
|
|
|
28742
28743
|
// src/cli/resolve-version.ts
|
|
28743
28744
|
function readPackageVersion() {
|
|
@@ -4212,6 +4212,7 @@ var init_schema = __esm(() => {
|
|
|
4212
4212
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
4213
4213
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
4214
4214
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
|
|
4215
|
+
boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart — a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart — but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
|
|
4215
4216
|
session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
|
|
4216
4217
|
session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
|
|
4217
4218
|
}).optional();
|
|
@@ -4212,6 +4212,7 @@ var init_schema = __esm(() => {
|
|
|
4212
4212
|
max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
|
|
4213
4213
|
resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
|
|
4214
4214
|
resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
|
|
4215
|
+
boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart — a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart — but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
|
|
4215
4216
|
session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
|
|
4216
4217
|
session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
|
|
4217
4218
|
}).optional();
|
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.18.
|
|
4
|
+
"version": "0.18.7",
|
|
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": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"build": "node scripts/build.mjs",
|
|
25
25
|
"build:cli": "node scripts/build.mjs && bun build --compile --target=bun-linux-x64 --minify bin/switchroom.ts --outfile switchroom-linux-amd64",
|
|
26
26
|
"pretest": "npm run build",
|
|
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/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/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.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/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.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",
|
|
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/ipc-server-query-pending-permission.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/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/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.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/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.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/vault/grants.test.ts src/vault/grants-db.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/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.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/status-query-telemetry.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",
|
|
29
|
+
"test:bun": "bun test src/vault/grants.test.ts src/vault/grants-db.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/ipc-server-query-pending-permission.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/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.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/status-query-telemetry.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",
|