switchroom 0.19.29 → 0.19.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/switchroom.js +2044 -900
- package/dist/host-control/main.js +162 -80
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +5 -1
- package/profiles/_base/start.sh.hbs +15 -1
- package/telegram-plugin/dist/gateway/gateway.js +1435 -551
- package/telegram-plugin/edit-flood-fuse.ts +70 -20
- package/telegram-plugin/gateway/boot-beacon.ts +364 -0
- package/telegram-plugin/gateway/boot-sweep-gate.ts +20 -15
- package/telegram-plugin/gateway/gateway.ts +87 -88
- package/telegram-plugin/gateway/inbound-spool.ts +39 -0
- package/telegram-plugin/gateway/narrative-lane.ts +12 -0
- package/telegram-plugin/gateway/obligation-store.ts +28 -0
- package/telegram-plugin/gateway/stale-pin-sweep-store.ts +221 -0
- package/telegram-plugin/gateway/stale-pin-sweep-wiring.ts +211 -0
- package/telegram-plugin/gateway/stale-pin-sweep.test.ts +804 -0
- package/telegram-plugin/gateway/stale-pin-sweep.ts +1146 -0
- package/telegram-plugin/gateway/status-pin-retarget.ts +15 -2
- package/telegram-plugin/gateway/status-pin-store.ts +33 -11
- package/telegram-plugin/registry/turns-schema.ts +21 -1
- package/telegram-plugin/retry-api-call.ts +46 -21
- package/telegram-plugin/shared/bot-runtime.ts +61 -17
- package/telegram-plugin/shared/gw-trace-gate.ts +18 -2
- package/telegram-plugin/tests/activity-card-wiring.test.ts +7 -7
- package/telegram-plugin/tests/activity-drain-fuse-drop-not-failure.test.ts +324 -0
- package/telegram-plugin/tests/agent-card-result-footer.test.ts +193 -0
- package/telegram-plugin/tests/boot-beacon.test.ts +462 -0
- package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +6 -6
- package/telegram-plugin/tests/boot-sweep-gate.test.ts +42 -31
- package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +2 -0
- package/telegram-plugin/tests/inbound-spool-progress.test.ts +2 -0
- package/telegram-plugin/tests/inbound-spool.test.ts +134 -5
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +28 -0
- package/telegram-plugin/tests/obligation-determinism.test.ts +2 -0
- package/telegram-plugin/tests/obligation-store.test.ts +67 -1
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
- package/telegram-plugin/tests/status-pin-store.test.ts +26 -5
- package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +161 -0
- package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +30 -0
- package/telegram-plugin/tool-activity-summary.ts +104 -38
- package/telegram-plugin/worker-activity-feed.ts +33 -16
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +0 -251
- package/telegram-plugin/gateway/dm-pin-sweep.ts +0 -178
|
@@ -19060,9 +19060,9 @@ var require_commander = __commonJS((exports2) => {
|
|
|
19060
19060
|
});
|
|
19061
19061
|
|
|
19062
19062
|
// src/host-control/main.ts
|
|
19063
|
-
import { homedir as
|
|
19064
|
-
import { existsSync as
|
|
19065
|
-
import { join as
|
|
19063
|
+
import { homedir as homedir8 } from "node:os";
|
|
19064
|
+
import { existsSync as existsSync16, readdirSync as readdirSync7 } from "node:fs";
|
|
19065
|
+
import { join as join14, resolve as resolve10 } from "node:path";
|
|
19066
19066
|
|
|
19067
19067
|
// src/config/loader.ts
|
|
19068
19068
|
init_zod();
|
|
@@ -20845,16 +20845,18 @@ function needsSelfBump(ownVersion, pin) {
|
|
|
20845
20845
|
return cmp !== null && cmp < 0;
|
|
20846
20846
|
}
|
|
20847
20847
|
function bumpHostdComposeImageTag(yaml, pin) {
|
|
20848
|
-
const re = /^(\s*image:\s*)(\S*switchroom-hostd):(\S+)\
|
|
20849
|
-
const
|
|
20850
|
-
if (
|
|
20848
|
+
const re = /^(\s*image:\s*)(\S*switchroom-hostd):(\S+)[ \t]*$/gm;
|
|
20849
|
+
const matches = [...yaml.matchAll(re)];
|
|
20850
|
+
if (matches.length === 0)
|
|
20851
20851
|
return null;
|
|
20852
|
-
const
|
|
20853
|
-
const
|
|
20852
|
+
const first = matches[0];
|
|
20853
|
+
const oldImageRef = `${first[2]}:${first[3]}`;
|
|
20854
|
+
const newImageRef = `${first[2]}:${pin}`;
|
|
20854
20855
|
return {
|
|
20855
|
-
yaml: yaml.replace(re, `$
|
|
20856
|
+
yaml: yaml.replace(re, (_m, indent, repo) => `${indent}${repo}:${pin}`),
|
|
20856
20857
|
oldImageRef,
|
|
20857
|
-
newImageRef
|
|
20858
|
+
newImageRef,
|
|
20859
|
+
bumpedCount: matches.length
|
|
20858
20860
|
};
|
|
20859
20861
|
}
|
|
20860
20862
|
function encodePendingRolloutMarker(m) {
|
|
@@ -21208,7 +21210,7 @@ function allocateAgentUid(name) {
|
|
|
21208
21210
|
}
|
|
21209
21211
|
|
|
21210
21212
|
// src/build-info.ts
|
|
21211
|
-
var VERSION = "0.19.
|
|
21213
|
+
var VERSION = "0.19.31";
|
|
21212
21214
|
|
|
21213
21215
|
// src/setup/hindsight-recall-tunables.ts
|
|
21214
21216
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -21765,8 +21767,8 @@ import { createServer } from "node:net";
|
|
|
21765
21767
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
21766
21768
|
import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
|
|
21767
21769
|
import {
|
|
21768
|
-
readdirSync as
|
|
21769
|
-
existsSync as
|
|
21770
|
+
readdirSync as readdirSync6,
|
|
21771
|
+
existsSync as existsSync15,
|
|
21770
21772
|
readFileSync as readFileSync11,
|
|
21771
21773
|
writeFileSync as writeFileSync6,
|
|
21772
21774
|
renameSync as renameSync5,
|
|
@@ -21779,7 +21781,7 @@ import {
|
|
|
21779
21781
|
copyFileSync as copyFileSync3,
|
|
21780
21782
|
unlinkSync as unlinkSync5
|
|
21781
21783
|
} from "node:fs";
|
|
21782
|
-
import { join as
|
|
21784
|
+
import { join as join13, dirname as dirname9, resolve as resolve9 } from "node:path";
|
|
21783
21785
|
import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
|
|
21784
21786
|
|
|
21785
21787
|
// src/host-control/protocol.ts
|
|
@@ -27737,7 +27739,51 @@ function recoverPinJournal(configPath, opts = {}) {
|
|
|
27737
27739
|
}
|
|
27738
27740
|
|
|
27739
27741
|
// src/cli/operator-uid.ts
|
|
27740
|
-
|
|
27742
|
+
import {
|
|
27743
|
+
chownSync,
|
|
27744
|
+
existsSync as existsSync12,
|
|
27745
|
+
lstatSync as lstatSync2,
|
|
27746
|
+
readdirSync as readdirSync5,
|
|
27747
|
+
realpathSync as realpathSync2,
|
|
27748
|
+
statSync as statSync7
|
|
27749
|
+
} from "node:fs";
|
|
27750
|
+
import { homedir as homedir6 } from "node:os";
|
|
27751
|
+
import { join as join9 } from "node:path";
|
|
27752
|
+
var CONTAINER_OPERATOR_HOME = "/host-home";
|
|
27753
|
+
var SWITCHROOM_HOME_MARKER = "switchroom.yaml";
|
|
27754
|
+
function operatorHomeCandidates(env, home2) {
|
|
27755
|
+
const roots = [env.SWITCHROOM_HOST_HOME?.trim(), home2, CONTAINER_OPERATOR_HOME];
|
|
27756
|
+
const out = [];
|
|
27757
|
+
for (const r of roots) {
|
|
27758
|
+
if (!r || r.length === 0)
|
|
27759
|
+
continue;
|
|
27760
|
+
const dir = join9(r, ".switchroom");
|
|
27761
|
+
if (!out.includes(dir))
|
|
27762
|
+
out.push(dir);
|
|
27763
|
+
}
|
|
27764
|
+
return out;
|
|
27765
|
+
}
|
|
27766
|
+
function resolveOperatorUidFromOwnership(deps = {}) {
|
|
27767
|
+
const env = deps.env ?? process.env;
|
|
27768
|
+
const home2 = deps.home ?? homedir6();
|
|
27769
|
+
const exists = deps.exists ?? ((p) => existsSync12(p));
|
|
27770
|
+
const statUid = deps.statUid ?? ((p) => {
|
|
27771
|
+
try {
|
|
27772
|
+
return statSync7(p).uid;
|
|
27773
|
+
} catch {
|
|
27774
|
+
return;
|
|
27775
|
+
}
|
|
27776
|
+
});
|
|
27777
|
+
for (const dir of operatorHomeCandidates(env, home2)) {
|
|
27778
|
+
if (!exists(join9(dir, SWITCHROOM_HOME_MARKER)))
|
|
27779
|
+
continue;
|
|
27780
|
+
const uid = statUid(dir);
|
|
27781
|
+
if (uid !== undefined && Number.isFinite(uid) && uid > 0)
|
|
27782
|
+
return uid;
|
|
27783
|
+
}
|
|
27784
|
+
return;
|
|
27785
|
+
}
|
|
27786
|
+
function resolveOperatorUid(ownershipDeps = {}) {
|
|
27741
27787
|
const sudoUid = process.env.SUDO_UID;
|
|
27742
27788
|
if (sudoUid !== undefined) {
|
|
27743
27789
|
const parsed = parseInt(sudoUid, 10);
|
|
@@ -27755,20 +27801,20 @@ function resolveOperatorUid() {
|
|
|
27755
27801
|
if (Number.isFinite(parsed) && parsed > 0)
|
|
27756
27802
|
return parsed;
|
|
27757
27803
|
}
|
|
27758
|
-
return;
|
|
27804
|
+
return resolveOperatorUidFromOwnership(ownershipDeps);
|
|
27759
27805
|
}
|
|
27760
27806
|
|
|
27761
27807
|
// src/cli/rollout.ts
|
|
27762
27808
|
init_atomic();
|
|
27763
27809
|
|
|
27764
27810
|
// src/cli/resolve-version.ts
|
|
27765
|
-
import { existsSync as
|
|
27766
|
-
import { dirname as dirname7, join as
|
|
27811
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
|
|
27812
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
27767
27813
|
function readPackageVersion() {
|
|
27768
27814
|
let dir = import.meta.dirname;
|
|
27769
27815
|
for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
|
|
27770
|
-
const p =
|
|
27771
|
-
if (
|
|
27816
|
+
const p = join10(dir, "package.json");
|
|
27817
|
+
if (existsSync13(p)) {
|
|
27772
27818
|
try {
|
|
27773
27819
|
const pkg = JSON.parse(readFileSync9(p, "utf-8"));
|
|
27774
27820
|
if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
|
|
@@ -27867,6 +27913,9 @@ function parseAuditLine(line) {
|
|
|
27867
27913
|
entry.failed_step = o.failed_step;
|
|
27868
27914
|
if (typeof o.failed_agent === "string")
|
|
27869
27915
|
entry.failed_agent = o.failed_agent;
|
|
27916
|
+
if (Array.isArray(o.drifted) && o.drifted.every((x) => typeof x === "string")) {
|
|
27917
|
+
entry.drifted = o.drifted;
|
|
27918
|
+
}
|
|
27870
27919
|
if (typeof o.agent === "string")
|
|
27871
27920
|
entry.agent = o.agent;
|
|
27872
27921
|
if (typeof o.n === "number")
|
|
@@ -27898,6 +27947,7 @@ function latestRolloutRowForRequest(raw, requestId) {
|
|
|
27898
27947
|
var DOCKER_INSPECT_TIMEOUT_MS = 60 * 1000;
|
|
27899
27948
|
|
|
27900
27949
|
// src/cli/rollout.ts
|
|
27950
|
+
var VERIFY_COMPONENTS_STEP = "verify-components";
|
|
27901
27951
|
function isVersionAssertable(target) {
|
|
27902
27952
|
return /^v?\d+\.\d+\.\d+$/.test(target.trim());
|
|
27903
27953
|
}
|
|
@@ -27971,7 +28021,7 @@ var ROLLOUT_RUN_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_RUN_TIMEOUT_MS", 20 * 60
|
|
|
27971
28021
|
var ROLLOUT_PROBE_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_PROBE_TIMEOUT_MS", 60 * 1000);
|
|
27972
28022
|
|
|
27973
28023
|
// src/cli/write-compose.ts
|
|
27974
|
-
import { chownSync } from "node:fs";
|
|
28024
|
+
import { chownSync as chownSync2 } from "node:fs";
|
|
27975
28025
|
import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
|
|
27976
28026
|
var AGENT_IMAGE_TAG_RE = /image:\s*\S*switchroom-agent:(\S+)/;
|
|
27977
28027
|
var BACKUP_SUFFIX = ".bak";
|
|
@@ -28060,7 +28110,7 @@ async function restoreComposeBackup(composePath, opts = {}) {
|
|
|
28060
28110
|
const operatorUid = resolveOperatorUid();
|
|
28061
28111
|
if (operatorUid !== undefined && process.geteuid?.() === 0) {
|
|
28062
28112
|
try {
|
|
28063
|
-
|
|
28113
|
+
chownSync2(composePath, operatorUid, operatorUid);
|
|
28064
28114
|
} catch {}
|
|
28065
28115
|
}
|
|
28066
28116
|
return { restored: true, imageTag: composeImageTag(content) };
|
|
@@ -28070,8 +28120,8 @@ async function restoreComposeBackup(composePath, opts = {}) {
|
|
|
28070
28120
|
var import__2 = __toESM(require_commander(), 1);
|
|
28071
28121
|
|
|
28072
28122
|
// src/cli/update.ts
|
|
28073
|
-
import { join as
|
|
28074
|
-
import { homedir as
|
|
28123
|
+
import { join as join11, dirname as dirname8, resolve as resolve8 } from "node:path";
|
|
28124
|
+
import { homedir as homedir7 } from "node:os";
|
|
28075
28125
|
|
|
28076
28126
|
// src/agents/lifecycle.ts
|
|
28077
28127
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -28167,7 +28217,7 @@ function getAllAgentStatuses(config) {
|
|
|
28167
28217
|
|
|
28168
28218
|
// src/cli/update.ts
|
|
28169
28219
|
init_atomic();
|
|
28170
|
-
var DEFAULT_COMPOSE_PATH =
|
|
28220
|
+
var DEFAULT_COMPOSE_PATH = join11(homedir7(), ".switchroom", "compose", "docker-compose.yml");
|
|
28171
28221
|
var UPDATE_RESULT_SENTINEL = "SWITCHROOM_UPDATE_RESULT:";
|
|
28172
28222
|
function parseUpdateResultLine(stdout) {
|
|
28173
28223
|
const idx = stdout.lastIndexOf(UPDATE_RESULT_SENTINEL);
|
|
@@ -28190,9 +28240,9 @@ function parseUpdateResultLine(stdout) {
|
|
|
28190
28240
|
|
|
28191
28241
|
// src/host-control/config-edit-validator.ts
|
|
28192
28242
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
28193
|
-
import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as
|
|
28243
|
+
import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
|
|
28194
28244
|
import { tmpdir } from "node:os";
|
|
28195
|
-
import { join as
|
|
28245
|
+
import { join as join12, isAbsolute as isAbsolute3, normalize, basename as basename4 } from "node:path";
|
|
28196
28246
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
28197
28247
|
import { isDeepStrictEqual } from "node:util";
|
|
28198
28248
|
var MAX_PATCH_BYTES = 1024 * 1024;
|
|
@@ -28332,7 +28382,7 @@ function validateShape(unifiedDiff, targetPath) {
|
|
|
28332
28382
|
return null;
|
|
28333
28383
|
}
|
|
28334
28384
|
function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
28335
|
-
if (!
|
|
28385
|
+
if (!existsSync14(configPath)) {
|
|
28336
28386
|
return {
|
|
28337
28387
|
ok: false,
|
|
28338
28388
|
code: "E_PATCH_APPLY_FAILED",
|
|
@@ -28340,13 +28390,13 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
|
28340
28390
|
};
|
|
28341
28391
|
}
|
|
28342
28392
|
const liveContent = readFileSync10(configPath, "utf8");
|
|
28343
|
-
const scratchDir = mkdtempSync(
|
|
28393
|
+
const scratchDir = mkdtempSync(join12(tmpdir(), "config-propose-edit-"));
|
|
28344
28394
|
try {
|
|
28345
28395
|
const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
|
|
28346
|
-
const scratchFile =
|
|
28396
|
+
const scratchFile = join12(scratchDir, targetBasename);
|
|
28347
28397
|
writeFileSync5(scratchFile, liveContent);
|
|
28348
28398
|
const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
|
|
28349
|
-
const patchFile =
|
|
28399
|
+
const patchFile = join12(scratchDir, "proposal.patch");
|
|
28350
28400
|
writeFileSync5(patchFile, normalizedDiff);
|
|
28351
28401
|
const bin = gitBin ?? "git";
|
|
28352
28402
|
const baseArgs = [
|
|
@@ -28574,7 +28624,7 @@ function validateConfigEdit(opts) {
|
|
|
28574
28624
|
return schemaErr;
|
|
28575
28625
|
let beforeData = {};
|
|
28576
28626
|
try {
|
|
28577
|
-
const beforeRaw =
|
|
28627
|
+
const beforeRaw = existsSync14(opts.configPath) ? readFileSync10(opts.configPath, "utf8") : "";
|
|
28578
28628
|
const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
|
|
28579
28629
|
beforeData = beforeDoc.toJS();
|
|
28580
28630
|
} catch {
|
|
@@ -28915,8 +28965,8 @@ function etaLine(s) {
|
|
|
28915
28965
|
function deferredLines(target) {
|
|
28916
28966
|
const bare = target.trim().replace(/^v/, "");
|
|
28917
28967
|
return [
|
|
28918
|
-
`**
|
|
28919
|
-
|
|
28968
|
+
`**Verified on ${target}** — every component this roll owned passed \`verify-components\`, so this is host convergence, not just the agents. Anything the roll was told to skip is named in its warnings.`,
|
|
28969
|
+
`**Still host-side (nothing in a roll can do these):**`,
|
|
28920
28970
|
`- host operator CLI — \`sudo npm i -g switchroom@${bare}\``,
|
|
28921
28971
|
`- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``
|
|
28922
28972
|
];
|
|
@@ -28946,6 +28996,21 @@ function renderWith(s, compact) {
|
|
|
28946
28996
|
if (s.deferred !== false)
|
|
28947
28997
|
parts2.push("", ...deferredLines(s.target));
|
|
28948
28998
|
return parts2.join(`
|
|
28999
|
+
`);
|
|
29000
|
+
}
|
|
29001
|
+
if (s.terminal === "error" && s.failedStep === VERIFY_COMPONENTS_STEP) {
|
|
29002
|
+
const drifted = s.drifted ?? [];
|
|
29003
|
+
const parts2 = [headerLine(s, "⚠️")];
|
|
29004
|
+
let summary = `**INCOMPLETE** — ${rolledCount}${s.m !== undefined ? `/${s.m}` : ""} ` + `agent(s) reached ${s.target}`;
|
|
29005
|
+
if (elapsedMs !== undefined)
|
|
29006
|
+
summary += ` in ${formatDurationMs(elapsedMs)}`;
|
|
29007
|
+
summary += `, but the host did NOT fully converge.`;
|
|
29008
|
+
parts2.push(summary);
|
|
29009
|
+
parts2.push("", `**Still behind ${s.target}:** ` + (drifted.length > 0 ? drifted.map((d) => `\`${d}\``).join(", ") : "one or more components (see the roll's warnings)"));
|
|
29010
|
+
parts2.push("", `Re-running the roll will NOT fix this — the agents are already on ` + `target. Finish the stale component(s), then \`switchroom update --check\`.`);
|
|
29011
|
+
if (checklist.length > 0)
|
|
29012
|
+
parts2.push("", ...checklist);
|
|
29013
|
+
return parts2.join(`
|
|
28949
29014
|
`);
|
|
28950
29015
|
}
|
|
28951
29016
|
if (s.terminal === "error") {
|
|
@@ -29076,9 +29141,9 @@ function resolveDigests(imageRefs) {
|
|
|
29076
29141
|
return out;
|
|
29077
29142
|
}
|
|
29078
29143
|
function readCachedInstallType(bindRoot) {
|
|
29079
|
-
const cacheDir =
|
|
29080
|
-
const cachePath =
|
|
29081
|
-
if (
|
|
29144
|
+
const cacheDir = join13(bindRoot, ".switchroom");
|
|
29145
|
+
const cachePath = join13(cacheDir, "install-type.json");
|
|
29146
|
+
if (existsSync15(cachePath)) {
|
|
29082
29147
|
try {
|
|
29083
29148
|
const raw = readFileSync11(cachePath, "utf-8");
|
|
29084
29149
|
const parsed = JSON.parse(raw);
|
|
@@ -29167,7 +29232,7 @@ class HostdServer {
|
|
|
29167
29232
|
fleetMutationInFlight = null;
|
|
29168
29233
|
lastAutoRolloutFailedPin = null;
|
|
29169
29234
|
autoRolloutLatchPath() {
|
|
29170
|
-
return
|
|
29235
|
+
return join13(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
|
|
29171
29236
|
}
|
|
29172
29237
|
readAutoRolloutLatch() {
|
|
29173
29238
|
try {
|
|
@@ -29210,7 +29275,7 @@ class HostdServer {
|
|
|
29210
29275
|
return resolveHostdConfigPath(this.opts.configPath);
|
|
29211
29276
|
}
|
|
29212
29277
|
fleetComposePath() {
|
|
29213
|
-
return
|
|
29278
|
+
return join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
29214
29279
|
}
|
|
29215
29280
|
recoverRolloutPinJournal(context) {
|
|
29216
29281
|
let note = null;
|
|
@@ -29249,7 +29314,7 @@ class HostdServer {
|
|
|
29249
29314
|
return note;
|
|
29250
29315
|
}
|
|
29251
29316
|
async start() {
|
|
29252
|
-
const hostdDir =
|
|
29317
|
+
const hostdDir = join13(this.opts.homeDir, ".switchroom", "hostd");
|
|
29253
29318
|
await mkdir2(hostdDir, { recursive: true });
|
|
29254
29319
|
await chmod(hostdDir, 493).catch(() => {
|
|
29255
29320
|
return;
|
|
@@ -29270,13 +29335,13 @@ class HostdServer {
|
|
|
29270
29335
|
}
|
|
29271
29336
|
try {
|
|
29272
29337
|
for (const name of agentNames) {
|
|
29273
|
-
const dir =
|
|
29274
|
-
const sockPath =
|
|
29338
|
+
const dir = join13(hostdDir, name);
|
|
29339
|
+
const sockPath = join13(dir, "sock");
|
|
29275
29340
|
await mkdir2(dir, { recursive: true });
|
|
29276
29341
|
await chmod(dir, 493).catch(() => {
|
|
29277
29342
|
return;
|
|
29278
29343
|
});
|
|
29279
|
-
if (
|
|
29344
|
+
if (existsSync15(sockPath))
|
|
29280
29345
|
await unlink(sockPath).catch(() => {
|
|
29281
29346
|
return;
|
|
29282
29347
|
});
|
|
@@ -29307,13 +29372,13 @@ class HostdServer {
|
|
|
29307
29372
|
process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
|
|
29308
29373
|
`);
|
|
29309
29374
|
} else {
|
|
29310
|
-
const dir =
|
|
29311
|
-
const sockPath =
|
|
29375
|
+
const dir = join13(hostdDir, "operator");
|
|
29376
|
+
const sockPath = join13(dir, "sock");
|
|
29312
29377
|
await mkdir2(dir, { recursive: true });
|
|
29313
29378
|
await chmod(dir, 493).catch(() => {
|
|
29314
29379
|
return;
|
|
29315
29380
|
});
|
|
29316
|
-
if (
|
|
29381
|
+
if (existsSync15(sockPath))
|
|
29317
29382
|
await unlink(sockPath).catch(() => {
|
|
29318
29383
|
return;
|
|
29319
29384
|
});
|
|
@@ -29349,7 +29414,7 @@ class HostdServer {
|
|
|
29349
29414
|
}
|
|
29350
29415
|
async reconcileOrphanedFleetMutations() {
|
|
29351
29416
|
const path3 = this.auditLogPath();
|
|
29352
|
-
if (!
|
|
29417
|
+
if (!existsSync15(path3))
|
|
29353
29418
|
return;
|
|
29354
29419
|
let raw;
|
|
29355
29420
|
try {
|
|
@@ -29752,11 +29817,11 @@ class HostdServer {
|
|
|
29752
29817
|
missingApplyAssets() {
|
|
29753
29818
|
const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
|
|
29754
29819
|
return [
|
|
29755
|
-
|
|
29756
|
-
|
|
29757
|
-
|
|
29758
|
-
|
|
29759
|
-
].filter((p) => !
|
|
29820
|
+
join13(root, "profiles"),
|
|
29821
|
+
join13(root, "profiles", "default"),
|
|
29822
|
+
join13(root, "vendor", "hindsight-memory"),
|
|
29823
|
+
join13(root, "skills")
|
|
29824
|
+
].filter((p) => !existsSync15(p));
|
|
29760
29825
|
}
|
|
29761
29826
|
applyAssetPreflight(request_id, started) {
|
|
29762
29827
|
const missing = this.missingApplyAssets();
|
|
@@ -30019,16 +30084,16 @@ class HostdServer {
|
|
|
30019
30084
|
return entry;
|
|
30020
30085
|
}
|
|
30021
30086
|
hostdDirPath() {
|
|
30022
|
-
return
|
|
30087
|
+
return join13(this.opts.homeDir, ".switchroom", "hostd");
|
|
30023
30088
|
}
|
|
30024
30089
|
hostdDirHostPath() {
|
|
30025
30090
|
const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
|
|
30026
|
-
return
|
|
30091
|
+
return join13(hostHome, ".switchroom", "hostd");
|
|
30027
30092
|
}
|
|
30028
30093
|
async beginSelfBump(req, caller, started) {
|
|
30029
30094
|
const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
|
|
30030
|
-
const composePath =
|
|
30031
|
-
if (!
|
|
30095
|
+
const composePath = join13(this.hostdDirPath(), "docker-compose.yml");
|
|
30096
|
+
if (!existsSync15(composePath)) {
|
|
30032
30097
|
return deniedResponse(req.request_id, `rollout: hostd's CLI (v${ownVersion}) is older than the target ` + `${req.args.pin} and needs a self-bump first, but its compose file ` + `is missing at ${composePath}. Run \`switchroom hostd install ` + `--tag ${req.args.pin}\` on the host, then re-run the roll. ` + `Nothing was changed.`, Date.now() - started);
|
|
30033
30098
|
}
|
|
30034
30099
|
const before = readFileSync11(composePath, "utf8");
|
|
@@ -30050,12 +30115,12 @@ class HostdServer {
|
|
|
30050
30115
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
30051
30116
|
const bakPath = `${composePath}.bak-selfbump-${ts}`;
|
|
30052
30117
|
try {
|
|
30053
|
-
const baks =
|
|
30118
|
+
const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
|
|
30054
30119
|
for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
|
|
30055
|
-
unlinkSync5(
|
|
30120
|
+
unlinkSync5(join13(this.hostdDirPath(), f));
|
|
30056
30121
|
}
|
|
30057
30122
|
} catch {}
|
|
30058
|
-
const markerPath =
|
|
30123
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30059
30124
|
const marker = {
|
|
30060
30125
|
v: 1,
|
|
30061
30126
|
request_id: req.request_id,
|
|
@@ -30069,6 +30134,8 @@ class HostdServer {
|
|
|
30069
30134
|
};
|
|
30070
30135
|
copyFileSync3(composePath, bakPath);
|
|
30071
30136
|
writeFileSync6(composePath, bumped.yaml, "utf8");
|
|
30137
|
+
process.stderr.write(`hostd: self-bump rewrote ${bumped.bumpedCount} switchroom-hostd ` + `image line(s) → ${bumped.newImageRef}
|
|
30138
|
+
`);
|
|
30072
30139
|
writeFileSync6(markerPath, encodePendingRolloutMarker(marker), {
|
|
30073
30140
|
mode: 384
|
|
30074
30141
|
});
|
|
@@ -30137,7 +30204,7 @@ class HostdServer {
|
|
|
30137
30204
|
rollbackBump();
|
|
30138
30205
|
entry.result = "error";
|
|
30139
30206
|
entry.finished_at = Date.now();
|
|
30140
|
-
entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${
|
|
30207
|
+
entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
|
|
30141
30208
|
if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
|
|
30142
30209
|
this.fleetMutationInFlight = null;
|
|
30143
30210
|
}
|
|
@@ -30161,8 +30228,8 @@ class HostdServer {
|
|
|
30161
30228
|
};
|
|
30162
30229
|
}
|
|
30163
30230
|
async resumePendingSelfBumpRollout() {
|
|
30164
|
-
const markerPath =
|
|
30165
|
-
if (!
|
|
30231
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30232
|
+
if (!existsSync15(markerPath))
|
|
30166
30233
|
return;
|
|
30167
30234
|
let raw;
|
|
30168
30235
|
try {
|
|
@@ -30217,7 +30284,7 @@ class HostdServer {
|
|
|
30217
30284
|
return;
|
|
30218
30285
|
}
|
|
30219
30286
|
if (needsSelfBump(ownVersion, marker.pin)) {
|
|
30220
|
-
await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${
|
|
30287
|
+
await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
|
|
30221
30288
|
return;
|
|
30222
30289
|
}
|
|
30223
30290
|
process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
|
|
@@ -30737,8 +30804,8 @@ ${output.recovery.stderr}` : "";
|
|
|
30737
30804
|
if (this.opts.imageRefsForDigests)
|
|
30738
30805
|
return this.opts.imageRefsForDigests();
|
|
30739
30806
|
try {
|
|
30740
|
-
const composePath =
|
|
30741
|
-
if (!
|
|
30807
|
+
const composePath = join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
30808
|
+
if (!existsSync15(composePath))
|
|
30742
30809
|
return [];
|
|
30743
30810
|
const r = spawnSync3("docker", [
|
|
30744
30811
|
"compose",
|
|
@@ -30809,6 +30876,9 @@ ${output.recovery.stderr}` : "";
|
|
|
30809
30876
|
entry.failed_step = parsed.failedStep;
|
|
30810
30877
|
if (parsed.failedAgent)
|
|
30811
30878
|
entry.failed_agent = parsed.failedAgent;
|
|
30879
|
+
if (parsed.drifted && parsed.drifted.length > 0) {
|
|
30880
|
+
entry.drifted = parsed.drifted;
|
|
30881
|
+
}
|
|
30812
30882
|
if (parsed.got !== undefined)
|
|
30813
30883
|
entry.got = parsed.got;
|
|
30814
30884
|
if (parsed.timedOut)
|
|
@@ -30864,7 +30934,10 @@ ${pinNote}`.trim();
|
|
|
30864
30934
|
});
|
|
30865
30935
|
}
|
|
30866
30936
|
async finishFailedAutoRollout(entry, priorPin) {
|
|
30867
|
-
const
|
|
30937
|
+
const drifted = entry.drifted ?? [];
|
|
30938
|
+
const notes = entry.failed_step === "verify-components" ? [
|
|
30939
|
+
`Unattended auto-update roll to ${entry.pin ?? "?"} rolled ` + `${(entry.rolled ?? []).length} agent(s) but did NOT converge the ` + `whole host: ${drifted.join(", ") || "one or more components"} ` + `still behind. Re-running the roll will not fix this — run the ` + `per-component install named in the rollout warnings, then ` + `\`switchroom update --check\` to confirm.`
|
|
30940
|
+
] : [
|
|
30868
30941
|
`Unattended auto-update roll to ${entry.pin ?? "?"} FAILED at ` + `${entry.failed_step ?? "unknown step"}` + `${entry.failed_agent ? ` (agent ${entry.failed_agent})` : ""}. ` + `Unattended retries of this version are disabled; fix the cause, ` + `then roll manually (\`switchroom rollout --pin ${entry.pin ?? "vX.Y.Z"}\`).`
|
|
30869
30942
|
];
|
|
30870
30943
|
try {
|
|
@@ -30887,6 +30960,10 @@ ${pinNote}`.trim();
|
|
|
30887
30960
|
async recoverFailedAutoRollout(entry, priorPin) {
|
|
30888
30961
|
const notes = [];
|
|
30889
30962
|
const rolledCount = (entry.rolled ?? []).length;
|
|
30963
|
+
if (entry.failed_step === "verify-components") {
|
|
30964
|
+
notes.push(`No automatic rollback: the agent fleet reached ` + `${entry.pin ?? "the target"} and the pin is committed — the gap is ` + `${(entry.drifted ?? []).join(", ") || "a component"}, not the fleet. ` + `Rolling back would move working agents backwards; converge the ` + `named component(s) forward instead.`);
|
|
30965
|
+
return notes;
|
|
30966
|
+
}
|
|
30890
30967
|
const beforeAnyAgent = entry.failed_step === "apply" || entry.failed_step === "restart-agent" && rolledCount === 0;
|
|
30891
30968
|
if (!beforeAnyAgent) {
|
|
30892
30969
|
notes.push(rolledCount > 0 ? `No automatic rollback: ${rolledCount} agent(s) already confirmed on ` + `${entry.pin ?? "the target"} past a green canary ` + `(${(entry.rolled ?? []).join(", ")}). The fleet is mixed — ` + `finish the roll or roll back manually.` : `No automatic rollback: the roll stopped at ` + `${entry.failed_step ?? "an unknown step"} before any change ` + `landed — nothing to revert. Verify host-side if unsure.`);
|
|
@@ -30933,7 +31010,8 @@ ${pinNote}`.trim();
|
|
|
30933
31010
|
...entry.finished_at !== null ? { elapsedMs: entry.finished_at - entry.started_at } : {},
|
|
30934
31011
|
...entry.failed_step ? { failedStep: entry.failed_step } : {},
|
|
30935
31012
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
30936
|
-
...entry.got !== undefined ? { got: entry.got } : {}
|
|
31013
|
+
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31014
|
+
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {}
|
|
30937
31015
|
});
|
|
30938
31016
|
this.opts.rolloutRelay.postTerminal({
|
|
30939
31017
|
requestId: entry.request_id,
|
|
@@ -30971,7 +31049,7 @@ ${pinNote}`.trim();
|
|
|
30971
31049
|
}
|
|
30972
31050
|
rolloutRowExistsInLog(targetRequestId) {
|
|
30973
31051
|
const path3 = this.auditLogPath();
|
|
30974
|
-
if (!
|
|
31052
|
+
if (!existsSync15(path3))
|
|
30975
31053
|
return false;
|
|
30976
31054
|
try {
|
|
30977
31055
|
const raw = readFileSync11(path3, "utf-8");
|
|
@@ -30982,7 +31060,7 @@ ${pinNote}`.trim();
|
|
|
30982
31060
|
}
|
|
30983
31061
|
rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
|
|
30984
31062
|
const path3 = this.auditLogPath();
|
|
30985
|
-
if (!
|
|
31063
|
+
if (!existsSync15(path3))
|
|
30986
31064
|
return null;
|
|
30987
31065
|
let raw;
|
|
30988
31066
|
try {
|
|
@@ -31002,6 +31080,7 @@ ${pinNote}`.trim();
|
|
|
31002
31080
|
...row.agent ? { agent: row.agent } : {},
|
|
31003
31081
|
...row.failed_step ? { failedStep: row.failed_step } : {},
|
|
31004
31082
|
...row.failed_agent ? { failedAgent: row.failed_agent } : {},
|
|
31083
|
+
...row.drifted && row.drifted.length > 0 ? { drifted: row.drifted } : {},
|
|
31005
31084
|
...row.pin ? { pin: row.pin } : {},
|
|
31006
31085
|
...row.prior_pin ? { prior_pin: row.prior_pin } : {}
|
|
31007
31086
|
});
|
|
@@ -31024,6 +31103,7 @@ ${pinNote}`.trim();
|
|
|
31024
31103
|
...entry.failed_step ? { failedStep: entry.failed_step } : {},
|
|
31025
31104
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
31026
31105
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31106
|
+
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
31027
31107
|
...entry.pin ? { pin: entry.pin } : {},
|
|
31028
31108
|
...entry.prior_pin ? { prior_pin: entry.prior_pin } : {}
|
|
31029
31109
|
}) : undefined;
|
|
@@ -31065,7 +31145,7 @@ ${pinNote}`.trim();
|
|
|
31065
31145
|
}
|
|
31066
31146
|
}
|
|
31067
31147
|
auditLogPath() {
|
|
31068
|
-
return this.opts.auditLogPath ??
|
|
31148
|
+
return this.opts.auditLogPath ?? join13(this.opts.homeDir, ".switchroom", "host-control-audit.log");
|
|
31069
31149
|
}
|
|
31070
31150
|
maybeRotateAuditLog(path3) {
|
|
31071
31151
|
const { maxBytes, maxFiles } = resolveRotationConfig({
|
|
@@ -31135,6 +31215,7 @@ ${pinNote}`.trim();
|
|
|
31135
31215
|
...entry.rolled ? { rolled: entry.rolled } : {},
|
|
31136
31216
|
...entry.failed_step ? { failed_step: entry.failed_step } : {},
|
|
31137
31217
|
...entry.failed_agent ? { failed_agent: entry.failed_agent } : {},
|
|
31218
|
+
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
31138
31219
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31139
31220
|
...entry.deferred ? { deferred: entry.deferred } : {},
|
|
31140
31221
|
...entry.prior_pin ? { prior_pin: entry.prior_pin } : {}
|
|
@@ -31852,6 +31933,7 @@ class LogTailRolloutNarrator {
|
|
|
31852
31933
|
...entry.failed_step ? { failedStep: entry.failed_step } : {},
|
|
31853
31934
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
31854
31935
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31936
|
+
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
31855
31937
|
...st.agents.length > 0 ? { agents: st.agents } : {},
|
|
31856
31938
|
...st.render.m !== undefined ? { m: st.render.m } : {},
|
|
31857
31939
|
requestId: entry.request_id,
|
|
@@ -32491,7 +32573,7 @@ import { appendFile as appendFile2 } from "node:fs/promises";
|
|
|
32491
32573
|
async function loadConfigResilient() {
|
|
32492
32574
|
try {
|
|
32493
32575
|
const config = loadConfig();
|
|
32494
|
-
clearStaleDegradedMarker(
|
|
32576
|
+
clearStaleDegradedMarker(homedir8(), (m) => process.stderr.write(`hostd: ${m}
|
|
32495
32577
|
`));
|
|
32496
32578
|
return config;
|
|
32497
32579
|
} catch (err2) {
|
|
@@ -32503,13 +32585,13 @@ async function loadConfigResilient() {
|
|
|
32503
32585
|
} catch {
|
|
32504
32586
|
configPath = undefined;
|
|
32505
32587
|
}
|
|
32506
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
32588
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir8(), ".switchroom", "agents");
|
|
32507
32589
|
const listGatewayCandidates = () => {
|
|
32508
32590
|
const out = [];
|
|
32509
32591
|
try {
|
|
32510
|
-
for (const name of
|
|
32592
|
+
for (const name of readdirSync7(agentsDir).sort()) {
|
|
32511
32593
|
const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
|
|
32512
|
-
if (
|
|
32594
|
+
if (existsSync16(sock))
|
|
32513
32595
|
out.push({ agent: name, sock });
|
|
32514
32596
|
}
|
|
32515
32597
|
} catch {}
|
|
@@ -32532,7 +32614,7 @@ async function loadConfigResilient() {
|
|
|
32532
32614
|
};
|
|
32533
32615
|
return waitForConfigRecovery({
|
|
32534
32616
|
initialError: err2,
|
|
32535
|
-
homeDir:
|
|
32617
|
+
homeDir: homedir8(),
|
|
32536
32618
|
loadFn: () => loadConfig(),
|
|
32537
32619
|
configPath,
|
|
32538
32620
|
notify,
|
|
@@ -32556,10 +32638,10 @@ async function main() {
|
|
|
32556
32638
|
`);
|
|
32557
32639
|
process.exit(2);
|
|
32558
32640
|
}
|
|
32559
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
32641
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir8(), ".switchroom", "agents");
|
|
32560
32642
|
const resolveGatewaySocket = (agentName) => {
|
|
32561
32643
|
const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
|
|
32562
|
-
return
|
|
32644
|
+
return existsSync16(sock) ? sock : null;
|
|
32563
32645
|
};
|
|
32564
32646
|
const approvalGateway = new SocketApprovalGateway({
|
|
32565
32647
|
resolveGatewaySocket,
|
|
@@ -32578,7 +32660,7 @@ async function main() {
|
|
|
32578
32660
|
}), { log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
32579
32661
|
`) });
|
|
32580
32662
|
const server = new HostdServer({
|
|
32581
|
-
homeDir:
|
|
32663
|
+
homeDir: homedir8(),
|
|
32582
32664
|
agentUids,
|
|
32583
32665
|
config: {
|
|
32584
32666
|
agents: Object.fromEntries(Object.entries(config.agents).map(([n, a]) => [
|
|
@@ -32607,7 +32689,7 @@ async function main() {
|
|
|
32607
32689
|
const autoRel = config.host_control?.auto_release_check;
|
|
32608
32690
|
const watcherMode = resolveReleaseWatcherMode(config);
|
|
32609
32691
|
if (watcherMode.mode !== "off") {
|
|
32610
|
-
const eventsLog =
|
|
32692
|
+
const eventsLog = join14(homedir8(), ".switchroom", "release-watcher-events.jsonl");
|
|
32611
32693
|
const log = (m) => {
|
|
32612
32694
|
process.stderr.write(`hostd: ${m}
|
|
32613
32695
|
`);
|
|
@@ -32666,7 +32748,7 @@ async function main() {
|
|
|
32666
32748
|
`);
|
|
32667
32749
|
} else
|
|
32668
32750
|
notifier = new UpdateNotifier({
|
|
32669
|
-
statePath:
|
|
32751
|
+
statePath: join14(homedir8(), ".switchroom", "release-notify-state.json"),
|
|
32670
32752
|
repostSuppressMs: cardTimeoutMs,
|
|
32671
32753
|
isFleetMutationInFlight: () => server.isFleetMutationLocked(),
|
|
32672
32754
|
startApply: (requestId) => {
|
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.19.
|
|
4
|
+
"version": "0.19.31",
|
|
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": {
|
|
@@ -127,7 +127,11 @@ if [ -n "${SWITCHROOM_LITELLM:-}" ] && command -v switchroom >/dev/null 2>&1; th
|
|
|
127
127
|
sr_ll_ok=""
|
|
128
128
|
sr_ll_unreachable=""
|
|
129
129
|
if [ -z "$sr_ll_key" ]; then
|
|
130
|
-
|
|
130
|
+
# Remediation, not just diagnosis. Unlike start.sh this block re-runs on
|
|
131
|
+
# EVERY fire, so no restart is needed — a provisioned key is picked up by
|
|
132
|
+
# the next fire on its own. Say that, so the operator does not bounce the
|
|
133
|
+
# agent chasing a cron-only problem.
|
|
134
|
+
echo "litellm(cron): no virtual key for '{{name}}' — falling back to direct OAuth (untracked, unguarded). FIX: re-run 'switchroom apply' to provision the key; the next fire picks it up (no restart needed)." >&2
|
|
131
135
|
elif command -v curl >/dev/null 2>&1 && [ -n "$ANTHROPIC_BASE_URL" ]; then
|
|
132
136
|
# Bounded retry probe — short budget for cron latency (3 attempts,
|
|
133
137
|
# worst-case ~25s: 3×5s curl timeouts + 2×5s sleeps).
|