switchroom 0.20.21 → 0.21.0
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/auth-broker/index.js +1 -1
- package/dist/cli/switchroom.js +1953 -1539
- package/dist/host-control/main.js +286 -122
- package/dist/vault/approvals/kernel-server.js +1 -1
- package/dist/vault/broker/server.js +1 -1
- package/package.json +1 -1
- package/skills/switchroom-release/SKILL.md +12 -1
- package/telegram-plugin/dist/gateway/gateway.js +1405 -851
- package/telegram-plugin/gateway/always-allow-persist-queue.ts +2 -2
- package/telegram-plugin/gateway/boot-beacon.ts +9 -2
- package/telegram-plugin/gateway/gateway-heartbeat.ts +4 -3
- package/telegram-plugin/gateway/gateway.ts +42 -15
- package/telegram-plugin/gateway/inbound-router.ts +31 -6
- package/telegram-plugin/gateway/missed-approvals-store.ts +2 -2
- package/telegram-plugin/gateway/pending-card-store.ts +2 -2
- package/telegram-plugin/gateway/privacy-state.ts +2 -2
- package/telegram-plugin/gateway/scoped-grant-store.ts +2 -2
- package/telegram-plugin/gateway/system-message-observer.ts +242 -0
- package/telegram-plugin/gateway/turn-active-marker.ts +3 -4
- package/telegram-plugin/history.ts +178 -11
- package/telegram-plugin/registry/turns-schema.ts +8 -2
- package/telegram-plugin/tests/buzz-mirror.test.ts +12 -1
- package/telegram-plugin/tests/card-history-lane.test.ts +394 -0
- package/telegram-plugin/tests/system-message-observer.test.ts +216 -0
- package/vendor/hindsight-memory/scripts/drain_pending.py +88 -4
- package/vendor/hindsight-memory/scripts/lib/config.py +112 -11
- package/vendor/hindsight-memory/scripts/recall.py +11 -2
- package/vendor/hindsight-memory/scripts/reconcile_tail.py +36 -0
- package/vendor/hindsight-memory/scripts/retain.py +6 -1
- package/vendor/hindsight-memory/scripts/tests/test_config_retain_env.py +99 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_types_filter.py +81 -0
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +113 -0
- package/vendor/hindsight-memory/settings.json +2 -2
- package/vendor/hindsight-memory/tests/test_config.py +8 -3
- package/vendor/hindsight-memory/tests/test_hooks.py +10 -1
- package/vendor/hindsight-memory/tests/test_retain_context.py +69 -0
|
@@ -21096,9 +21096,9 @@ var require_commander = __commonJS((exports2) => {
|
|
|
21096
21096
|
|
|
21097
21097
|
// src/host-control/main.ts
|
|
21098
21098
|
init_loader();
|
|
21099
|
-
import { homedir as
|
|
21100
|
-
import { existsSync as
|
|
21101
|
-
import { join as
|
|
21099
|
+
import { homedir as homedir9 } from "node:os";
|
|
21100
|
+
import { existsSync as existsSync18, readdirSync as readdirSync7 } from "node:fs";
|
|
21101
|
+
import { join as join14, resolve as resolve10 } from "node:path";
|
|
21102
21102
|
|
|
21103
21103
|
// src/host-control/config-degraded.ts
|
|
21104
21104
|
init_loader();
|
|
@@ -21565,13 +21565,13 @@ function allocateAgentUid(name) {
|
|
|
21565
21565
|
}
|
|
21566
21566
|
|
|
21567
21567
|
// src/build-info.ts
|
|
21568
|
-
var VERSION = "0.
|
|
21568
|
+
var VERSION = "0.21.0";
|
|
21569
21569
|
|
|
21570
21570
|
// src/setup/hindsight-recall-passthrough.ts
|
|
21571
21571
|
var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
|
|
21572
21572
|
var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
|
|
21573
21573
|
var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
21574
|
-
budget: "
|
|
21574
|
+
budget: "mid",
|
|
21575
21575
|
maxTokens: 1024,
|
|
21576
21576
|
preferObservations: true,
|
|
21577
21577
|
contextTurns: 2,
|
|
@@ -22356,11 +22356,11 @@ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
|
|
|
22356
22356
|
import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
|
|
22357
22357
|
import {
|
|
22358
22358
|
readdirSync as readdirSync6,
|
|
22359
|
-
existsSync as
|
|
22360
|
-
readFileSync as
|
|
22361
|
-
writeFileSync as
|
|
22362
|
-
renameSync as
|
|
22363
|
-
mkdirSync as
|
|
22359
|
+
existsSync as existsSync17,
|
|
22360
|
+
readFileSync as readFileSync13,
|
|
22361
|
+
writeFileSync as writeFileSync7,
|
|
22362
|
+
renameSync as renameSync6,
|
|
22363
|
+
mkdirSync as mkdirSync6,
|
|
22364
22364
|
openSync as openSync5,
|
|
22365
22365
|
ftruncateSync,
|
|
22366
22366
|
writeSync as writeSync3,
|
|
@@ -22369,7 +22369,7 @@ import {
|
|
|
22369
22369
|
copyFileSync as copyFileSync3,
|
|
22370
22370
|
unlinkSync as unlinkSync5
|
|
22371
22371
|
} from "node:fs";
|
|
22372
|
-
import { join as
|
|
22372
|
+
import { join as join13, dirname as dirname9, resolve as resolve9 } from "node:path";
|
|
22373
22373
|
import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
|
|
22374
22374
|
|
|
22375
22375
|
// src/host-control/protocol.ts
|
|
@@ -28540,6 +28540,169 @@ function resolveOperatorUid(ownershipDeps = {}) {
|
|
|
28540
28540
|
return resolveOperatorUidFromOwnership(ownershipDeps);
|
|
28541
28541
|
}
|
|
28542
28542
|
|
|
28543
|
+
// src/cli/host-cli-stamp.ts
|
|
28544
|
+
import {
|
|
28545
|
+
chownSync as chownSync2,
|
|
28546
|
+
existsSync as existsSync14,
|
|
28547
|
+
mkdirSync as mkdirSync5,
|
|
28548
|
+
readFileSync as readFileSync10,
|
|
28549
|
+
renameSync as renameSync5,
|
|
28550
|
+
statSync as statSync8,
|
|
28551
|
+
writeFileSync as writeFileSync5
|
|
28552
|
+
} from "node:fs";
|
|
28553
|
+
import { homedir as homedir7 } from "node:os";
|
|
28554
|
+
import { join as join9 } from "node:path";
|
|
28555
|
+
var HOST_CLI_STAMP_FILENAME = "host-cli.json";
|
|
28556
|
+
var SWITCHROOM_HOME_MARKER2 = "switchroom.yaml";
|
|
28557
|
+
var CONTAINER_OPERATOR_HOME2 = "/host-home";
|
|
28558
|
+
function resolveIo(io) {
|
|
28559
|
+
return {
|
|
28560
|
+
env: io.env ?? process.env,
|
|
28561
|
+
home: io.home ?? homedir7(),
|
|
28562
|
+
exists: io.exists ?? ((p) => existsSync14(p)),
|
|
28563
|
+
readFile: io.readFile ?? ((p) => readFileSync10(p, "utf8")),
|
|
28564
|
+
writeFile: io.writeFile ?? ((p, c) => {
|
|
28565
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
28566
|
+
writeFileSync5(tmp, c, { mode: 420 });
|
|
28567
|
+
renameSync5(tmp, p);
|
|
28568
|
+
}),
|
|
28569
|
+
statUid: io.statUid ?? ((p) => {
|
|
28570
|
+
try {
|
|
28571
|
+
return statSync8(p).uid;
|
|
28572
|
+
} catch {
|
|
28573
|
+
return;
|
|
28574
|
+
}
|
|
28575
|
+
}),
|
|
28576
|
+
userForUid: io.userForUid ?? userFromPasswd,
|
|
28577
|
+
getuid: io.getuid ?? (() => process.getuid?.()),
|
|
28578
|
+
chown: io.chown ?? ((p, uid, gid) => chownSync2(p, uid, gid)),
|
|
28579
|
+
statOwner: io.statOwner ?? ((p) => {
|
|
28580
|
+
try {
|
|
28581
|
+
const st = statSync8(p);
|
|
28582
|
+
return { uid: st.uid, gid: st.gid };
|
|
28583
|
+
} catch {
|
|
28584
|
+
return;
|
|
28585
|
+
}
|
|
28586
|
+
})
|
|
28587
|
+
};
|
|
28588
|
+
}
|
|
28589
|
+
function userFromPasswd(uid) {
|
|
28590
|
+
try {
|
|
28591
|
+
for (const line of readFileSync10("/etc/passwd", "utf8").split(`
|
|
28592
|
+
`)) {
|
|
28593
|
+
const f = line.split(":");
|
|
28594
|
+
if (f[2] !== undefined && parseInt(f[2], 10) === uid && f[0])
|
|
28595
|
+
return f[0];
|
|
28596
|
+
}
|
|
28597
|
+
} catch {}
|
|
28598
|
+
return;
|
|
28599
|
+
}
|
|
28600
|
+
function homeFromPasswd(user) {
|
|
28601
|
+
try {
|
|
28602
|
+
for (const line of readFileSync10("/etc/passwd", "utf8").split(`
|
|
28603
|
+
`)) {
|
|
28604
|
+
const f = line.split(":");
|
|
28605
|
+
if (f[0] === user && f[5])
|
|
28606
|
+
return f[5];
|
|
28607
|
+
}
|
|
28608
|
+
} catch {}
|
|
28609
|
+
return;
|
|
28610
|
+
}
|
|
28611
|
+
function stampHomeCandidates(env, home2, homeForUser = homeFromPasswd) {
|
|
28612
|
+
const sudoUser = env.SUDO_USER;
|
|
28613
|
+
const roots = [
|
|
28614
|
+
env.SWITCHROOM_HOST_HOME?.trim(),
|
|
28615
|
+
sudoUser && sudoUser !== "root" ? homeForUser(sudoUser) : undefined,
|
|
28616
|
+
home2,
|
|
28617
|
+
CONTAINER_OPERATOR_HOME2
|
|
28618
|
+
];
|
|
28619
|
+
const out = [];
|
|
28620
|
+
for (const r of roots) {
|
|
28621
|
+
if (!r)
|
|
28622
|
+
continue;
|
|
28623
|
+
const dir = join9(r, ".switchroom");
|
|
28624
|
+
if (!out.includes(dir))
|
|
28625
|
+
out.push(dir);
|
|
28626
|
+
}
|
|
28627
|
+
return out;
|
|
28628
|
+
}
|
|
28629
|
+
function resolveStampDir(io = {}) {
|
|
28630
|
+
const { env, home: home2, exists } = resolveIo(io);
|
|
28631
|
+
const homeForUser = (u) => homeFromPasswd(u);
|
|
28632
|
+
for (const dir of stampHomeCandidates(env, home2, homeForUser)) {
|
|
28633
|
+
if (exists(join9(dir, SWITCHROOM_HOME_MARKER2)))
|
|
28634
|
+
return dir;
|
|
28635
|
+
}
|
|
28636
|
+
return;
|
|
28637
|
+
}
|
|
28638
|
+
function readHostCliStamp(io = {}) {
|
|
28639
|
+
try {
|
|
28640
|
+
const dir = resolveStampDir(io);
|
|
28641
|
+
if (!dir)
|
|
28642
|
+
return;
|
|
28643
|
+
const { exists, readFile } = resolveIo(io);
|
|
28644
|
+
const path3 = join9(dir, HOST_CLI_STAMP_FILENAME);
|
|
28645
|
+
if (!exists(path3))
|
|
28646
|
+
return;
|
|
28647
|
+
const parsed = JSON.parse(readFile(path3));
|
|
28648
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0)
|
|
28649
|
+
return;
|
|
28650
|
+
return {
|
|
28651
|
+
version: parsed.version,
|
|
28652
|
+
installKind: parsed.installKind ?? "unknown",
|
|
28653
|
+
path: typeof parsed.path === "string" ? parsed.path : "",
|
|
28654
|
+
...parsed.npmPrefix ? { npmPrefix: parsed.npmPrefix } : {},
|
|
28655
|
+
...parsed.ownerUser ? { ownerUser: parsed.ownerUser } : {},
|
|
28656
|
+
...typeof parsed.ownerUid === "number" ? { ownerUid: parsed.ownerUid } : {}
|
|
28657
|
+
};
|
|
28658
|
+
} catch {
|
|
28659
|
+
return;
|
|
28660
|
+
}
|
|
28661
|
+
}
|
|
28662
|
+
function hostCliInstallCommand(stamp, target) {
|
|
28663
|
+
const bare = target.replace(/^v/, "");
|
|
28664
|
+
if (!stamp) {
|
|
28665
|
+
return `upgrade the host operator CLI to ${bare} host-side ` + `(\`switchroom update\` for a static-binary install; ` + `\`npm i -g switchroom@${bare}\` for an npm install — as the user who owns ` + `the npm prefix)`;
|
|
28666
|
+
}
|
|
28667
|
+
switch (stamp.installKind) {
|
|
28668
|
+
case "npm-global": {
|
|
28669
|
+
const npmCmd = `npm i -g${stamp.npmPrefix ? ` --prefix ${stamp.npmPrefix}` : ""} switchroom@${bare}`;
|
|
28670
|
+
if (stamp.ownerUid === 0)
|
|
28671
|
+
return `sudo ${npmCmd}`;
|
|
28672
|
+
const who = stamp.ownerUser ?? (stamp.ownerUid !== undefined ? `uid ${stamp.ownerUid}` : undefined);
|
|
28673
|
+
const asNote = who ? ` — run as ${who}, NOT under sudo` : ` — run as the user owning ${stamp.path}, NOT under sudo`;
|
|
28674
|
+
return `${npmCmd}${asNote}`;
|
|
28675
|
+
}
|
|
28676
|
+
case "static-binary":
|
|
28677
|
+
return `switchroom update --pin v${bare} (replaces the static binary at ${stamp.path})`;
|
|
28678
|
+
case "source-checkout":
|
|
28679
|
+
return `git pull && bun install && npm run build (source checkout at ${stamp.path})`;
|
|
28680
|
+
case "container":
|
|
28681
|
+
case "unknown":
|
|
28682
|
+
default:
|
|
28683
|
+
return `upgrade the host operator CLI at ${stamp.path} to ${bare} using the ` + `method it was installed with`;
|
|
28684
|
+
}
|
|
28685
|
+
}
|
|
28686
|
+
function hostCliInstallShellCommand(stamp, target) {
|
|
28687
|
+
if (!stamp)
|
|
28688
|
+
return;
|
|
28689
|
+
const bare = target.replace(/^v/, "");
|
|
28690
|
+
if (stamp.installKind === "static-binary")
|
|
28691
|
+
return `switchroom update --pin v${bare}`;
|
|
28692
|
+
if (stamp.installKind === "npm-global" && stamp.ownerUid === 0) {
|
|
28693
|
+
return `sudo npm i -g${stamp.npmPrefix ? ` --prefix ${stamp.npmPrefix}` : ""} switchroom@${bare}`;
|
|
28694
|
+
}
|
|
28695
|
+
return;
|
|
28696
|
+
}
|
|
28697
|
+
function compareHostCliToTarget(stamp, target) {
|
|
28698
|
+
if (!stamp)
|
|
28699
|
+
return "unknown";
|
|
28700
|
+
const cmp = compareReleaseTags(`v${stamp.version.replace(/^v/, "")}`, `v${target.replace(/^v/, "")}`);
|
|
28701
|
+
if (cmp === null)
|
|
28702
|
+
return "unknown";
|
|
28703
|
+
return cmp < 0 ? "behind" : "current-or-ahead";
|
|
28704
|
+
}
|
|
28705
|
+
|
|
28543
28706
|
// src/cli/rollout.ts
|
|
28544
28707
|
init_atomic();
|
|
28545
28708
|
|
|
@@ -28558,15 +28721,15 @@ function hostdTemplateRegenVerdict(target, fromVersion) {
|
|
|
28558
28721
|
}
|
|
28559
28722
|
|
|
28560
28723
|
// src/cli/resolve-version.ts
|
|
28561
|
-
import { existsSync as
|
|
28562
|
-
import { dirname as dirname7, join as
|
|
28724
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
|
|
28725
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
28563
28726
|
function readPackageVersion() {
|
|
28564
28727
|
let dir = import.meta.dirname;
|
|
28565
28728
|
for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
|
|
28566
|
-
const p =
|
|
28567
|
-
if (
|
|
28729
|
+
const p = join10(dir, "package.json");
|
|
28730
|
+
if (existsSync15(p)) {
|
|
28568
28731
|
try {
|
|
28569
|
-
const pkg = JSON.parse(
|
|
28732
|
+
const pkg = JSON.parse(readFileSync11(p, "utf-8"));
|
|
28570
28733
|
if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
|
|
28571
28734
|
return pkg.version;
|
|
28572
28735
|
}
|
|
@@ -28866,7 +29029,7 @@ var ROLLOUT_RUN_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_RUN_TIMEOUT_MS", 20 * 60
|
|
|
28866
29029
|
var ROLLOUT_PROBE_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_PROBE_TIMEOUT_MS", 60 * 1000);
|
|
28867
29030
|
|
|
28868
29031
|
// src/cli/write-compose.ts
|
|
28869
|
-
import { chownSync as
|
|
29032
|
+
import { chownSync as chownSync3 } from "node:fs";
|
|
28870
29033
|
import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
|
|
28871
29034
|
init_merge();
|
|
28872
29035
|
var AGENT_IMAGE_TAG_RE = /image:\s*\S*switchroom-agent:(\S+)/;
|
|
@@ -28956,7 +29119,7 @@ async function restoreComposeBackup(composePath, opts = {}) {
|
|
|
28956
29119
|
const operatorUid = resolveOperatorUid();
|
|
28957
29120
|
if (operatorUid !== undefined && process.geteuid?.() === 0) {
|
|
28958
29121
|
try {
|
|
28959
|
-
|
|
29122
|
+
chownSync3(composePath, operatorUid, operatorUid);
|
|
28960
29123
|
} catch {}
|
|
28961
29124
|
}
|
|
28962
29125
|
return { restored: true, imageTag: composeImageTag(content) };
|
|
@@ -28967,8 +29130,8 @@ var import__2 = __toESM(require_commander(), 1);
|
|
|
28967
29130
|
|
|
28968
29131
|
// src/cli/update.ts
|
|
28969
29132
|
init_loader();
|
|
28970
|
-
import { join as
|
|
28971
|
-
import { homedir as
|
|
29133
|
+
import { join as join11, dirname as dirname8 } from "node:path";
|
|
29134
|
+
import { homedir as homedir8 } from "node:os";
|
|
28972
29135
|
|
|
28973
29136
|
// src/agents/lifecycle.ts
|
|
28974
29137
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -29070,7 +29233,7 @@ function getAllAgentStatuses(config) {
|
|
|
29070
29233
|
|
|
29071
29234
|
// src/cli/update.ts
|
|
29072
29235
|
init_atomic();
|
|
29073
|
-
var DEFAULT_COMPOSE_PATH =
|
|
29236
|
+
var DEFAULT_COMPOSE_PATH = join11(homedir8(), ".switchroom", "compose", "docker-compose.yml");
|
|
29074
29237
|
var PACKAGED_INSTALL_KINDS = new Set([
|
|
29075
29238
|
"static-binary",
|
|
29076
29239
|
"npm-global",
|
|
@@ -29161,9 +29324,9 @@ function resolvePriorPinFromFleet(components, target, scope) {
|
|
|
29161
29324
|
// src/host-control/config-edit-validator.ts
|
|
29162
29325
|
init_schema();
|
|
29163
29326
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
29164
|
-
import { mkdtempSync, writeFileSync as
|
|
29327
|
+
import { mkdtempSync, writeFileSync as writeFileSync6, rmSync as rmSync2, existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
|
|
29165
29328
|
import { tmpdir } from "node:os";
|
|
29166
|
-
import { join as
|
|
29329
|
+
import { join as join12, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
|
|
29167
29330
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
29168
29331
|
import { isDeepStrictEqual } from "node:util";
|
|
29169
29332
|
var MAX_PATCH_BYTES = 1024 * 1024;
|
|
@@ -29303,22 +29466,22 @@ function validateShape(unifiedDiff, targetPath) {
|
|
|
29303
29466
|
return null;
|
|
29304
29467
|
}
|
|
29305
29468
|
function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
29306
|
-
if (!
|
|
29469
|
+
if (!existsSync16(configPath)) {
|
|
29307
29470
|
return {
|
|
29308
29471
|
ok: false,
|
|
29309
29472
|
code: "E_PATCH_APPLY_FAILED",
|
|
29310
29473
|
detail: `target config not found at ${configPath}`
|
|
29311
29474
|
};
|
|
29312
29475
|
}
|
|
29313
|
-
const liveContent =
|
|
29314
|
-
const scratchDir = mkdtempSync(
|
|
29476
|
+
const liveContent = readFileSync12(configPath, "utf8");
|
|
29477
|
+
const scratchDir = mkdtempSync(join12(tmpdir(), "config-propose-edit-"));
|
|
29315
29478
|
try {
|
|
29316
29479
|
const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
|
|
29317
|
-
const scratchFile =
|
|
29318
|
-
|
|
29480
|
+
const scratchFile = join12(scratchDir, targetBasename);
|
|
29481
|
+
writeFileSync6(scratchFile, liveContent);
|
|
29319
29482
|
const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
|
|
29320
|
-
const patchFile =
|
|
29321
|
-
|
|
29483
|
+
const patchFile = join12(scratchDir, "proposal.patch");
|
|
29484
|
+
writeFileSync6(patchFile, normalizedDiff);
|
|
29322
29485
|
const bin = gitBin ?? "git";
|
|
29323
29486
|
const baseArgs = [
|
|
29324
29487
|
"apply",
|
|
@@ -29347,7 +29510,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
|
29347
29510
|
detail: `git apply failed: ${(real.stderr || "").trim().slice(0, 500)}`
|
|
29348
29511
|
};
|
|
29349
29512
|
}
|
|
29350
|
-
const after =
|
|
29513
|
+
const after = readFileSync12(scratchFile, "utf8");
|
|
29351
29514
|
return { ok: true, after };
|
|
29352
29515
|
} finally {
|
|
29353
29516
|
rmSync2(scratchDir, { recursive: true, force: true });
|
|
@@ -29545,7 +29708,7 @@ function validateConfigEdit(opts) {
|
|
|
29545
29708
|
return schemaErr;
|
|
29546
29709
|
let beforeData = {};
|
|
29547
29710
|
try {
|
|
29548
|
-
const beforeRaw =
|
|
29711
|
+
const beforeRaw = existsSync16(opts.configPath) ? readFileSync12(opts.configPath, "utf8") : "";
|
|
29549
29712
|
const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
|
|
29550
29713
|
beforeData = beforeDoc.toJS();
|
|
29551
29714
|
} catch {
|
|
@@ -29918,33 +30081,30 @@ function etaLine(s) {
|
|
|
29918
30081
|
const meanMs = done.reduce((t, a) => t + (a.durationMs ?? 0), 0) / done.length;
|
|
29919
30082
|
return `~${formatDurationMs(meanMs * remaining)} left (rough est.)`;
|
|
29920
30083
|
}
|
|
29921
|
-
function deferredLines(target, fromVersion) {
|
|
29922
|
-
const bare = target.trim().replace(/^v/, "");
|
|
30084
|
+
function deferredLines(target, fromVersion, hostCli) {
|
|
29923
30085
|
const head = [
|
|
29924
|
-
`**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
|
|
29925
|
-
`**Still host-side (nothing in a roll can do these):**`
|
|
30086
|
+
`**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.`
|
|
29926
30087
|
];
|
|
30088
|
+
const posture = compareHostCliToTarget(hostCli, target);
|
|
30089
|
+
const hostCliOutstanding = posture !== "current-or-ahead";
|
|
30090
|
+
if (posture === "current-or-ahead" && hostCli) {
|
|
30091
|
+
head.push(`Host operator CLI: **on ${hostCli.version}** (read from \`~/.switchroom/host-cli.json\`) — nothing to do.`);
|
|
30092
|
+
}
|
|
30093
|
+
const cliCommand = hostCliInstallCommand(hostCli, target);
|
|
30094
|
+
const cliLine = !hostCli ? `- host operator CLI (version not observable from here — no \`~/.switchroom/host-cli.json\`) — ${cliCommand}` : posture === "behind" ? `- host operator CLI — observed **${hostCli.version}**, run: \`${cliCommand}\`` : `- host operator CLI — observed **${hostCli.version}**, which is not comparable to ${target} (a channel/pre-release build). Confirm host-side with \`switchroom --version\`; if it is behind: ${cliCommand}`;
|
|
29927
30095
|
const verdict = hostdTemplateRegenVerdict(target, fromVersion);
|
|
29928
|
-
|
|
30096
|
+
const collapsible = verdict === "required" && posture === "behind" ? hostCliInstallShellCommand(hostCli, target) : undefined;
|
|
30097
|
+
if (collapsible) {
|
|
29929
30098
|
return [
|
|
29930
30099
|
...head,
|
|
30100
|
+
`**Still host-side (nothing in a roll can do these):**`,
|
|
29931
30101
|
`- host operator CLI + hostd template regen — regen is **REQUIRED** for this roll (hostd mounts/env changed in ${HOSTD_TEMPLATE_LAST_CHANGED}). One copy-paste:`,
|
|
29932
|
-
`
|
|
30102
|
+
` \`${collapsible} && switchroom hostd install --tag ${target}\``
|
|
29933
30103
|
];
|
|
29934
30104
|
}
|
|
29935
|
-
const
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
...head,
|
|
29939
|
-
cliLine,
|
|
29940
|
-
`- hostd template regen: **not needed** for this release — hostd mounts/env unchanged since ${HOSTD_TEMPLATE_LAST_CHANGED}.`
|
|
29941
|
-
];
|
|
29942
|
-
}
|
|
29943
|
-
return [
|
|
29944
|
-
...head,
|
|
29945
|
-
cliLine,
|
|
29946
|
-
`- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``
|
|
29947
|
-
];
|
|
30105
|
+
const regenLine = verdict === "required" ? `- hostd template regen — **REQUIRED** for this roll (hostd mounts/env changed in ${HOSTD_TEMPLATE_LAST_CHANGED}): \`switchroom hostd install --tag ${target}\`` : verdict === "not-needed" ? `- hostd template regen: **not needed** for this release — hostd mounts/env unchanged since ${HOSTD_TEMPLATE_LAST_CHANGED}.` : `- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``;
|
|
30106
|
+
const outstanding = [...hostCliOutstanding ? [cliLine] : [], regenLine];
|
|
30107
|
+
return [...head, `**Still host-side (nothing in a roll can do these):**`, ...outstanding];
|
|
29948
30108
|
}
|
|
29949
30109
|
function warningLines(warnings) {
|
|
29950
30110
|
if (!warnings || warnings.length === 0)
|
|
@@ -29986,7 +30146,7 @@ function renderWith(s, compact) {
|
|
|
29986
30146
|
if (warnings.length > 0)
|
|
29987
30147
|
parts2.push("", ...warnings);
|
|
29988
30148
|
if (s.deferred !== false)
|
|
29989
|
-
parts2.push("", ...deferredLines(s.target, s.fromVersion));
|
|
30149
|
+
parts2.push("", ...deferredLines(s.target, s.fromVersion, s.hostCli));
|
|
29990
30150
|
return parts2.join(`
|
|
29991
30151
|
`);
|
|
29992
30152
|
}
|
|
@@ -30148,11 +30308,11 @@ function resolveDigests(imageRefs) {
|
|
|
30148
30308
|
return out;
|
|
30149
30309
|
}
|
|
30150
30310
|
function readCachedInstallType(bindRoot) {
|
|
30151
|
-
const cacheDir =
|
|
30152
|
-
const cachePath =
|
|
30153
|
-
if (
|
|
30311
|
+
const cacheDir = join13(bindRoot, ".switchroom");
|
|
30312
|
+
const cachePath = join13(cacheDir, "install-type.json");
|
|
30313
|
+
if (existsSync17(cachePath)) {
|
|
30154
30314
|
try {
|
|
30155
|
-
const raw =
|
|
30315
|
+
const raw = readFileSync13(cachePath, "utf-8");
|
|
30156
30316
|
const parsed = JSON.parse(raw);
|
|
30157
30317
|
if (parsed && typeof parsed.install_type === "string" && typeof parsed.detected_at === "string") {
|
|
30158
30318
|
return parsed;
|
|
@@ -30169,10 +30329,10 @@ function readCachedInstallType(bindRoot) {
|
|
|
30169
30329
|
source_paths: ctx.source_paths
|
|
30170
30330
|
};
|
|
30171
30331
|
try {
|
|
30172
|
-
|
|
30332
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
30173
30333
|
const tmp = `${cachePath}.tmp`;
|
|
30174
|
-
|
|
30175
|
-
|
|
30334
|
+
writeFileSync7(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
30335
|
+
renameSync6(tmp, cachePath);
|
|
30176
30336
|
} catch {}
|
|
30177
30337
|
return payload;
|
|
30178
30338
|
}
|
|
@@ -30204,7 +30364,7 @@ function writeFileInPlacePreservingInode(targetPath, content) {
|
|
|
30204
30364
|
} finally {
|
|
30205
30365
|
closeSync5(fd);
|
|
30206
30366
|
}
|
|
30207
|
-
const readBack =
|
|
30367
|
+
const readBack = readFileSync13(targetPath);
|
|
30208
30368
|
if (readBack.length !== buf.length) {
|
|
30209
30369
|
throw new Error(`in-place write short: wrote ${buf.length} bytes but read back ${readBack.length}`);
|
|
30210
30370
|
}
|
|
@@ -30239,11 +30399,11 @@ class HostdServer {
|
|
|
30239
30399
|
fleetMutationInFlight = null;
|
|
30240
30400
|
lastAutoRolloutFailedPin = null;
|
|
30241
30401
|
autoRolloutLatchPath() {
|
|
30242
|
-
return
|
|
30402
|
+
return join13(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
|
|
30243
30403
|
}
|
|
30244
30404
|
readAutoRolloutLatch() {
|
|
30245
30405
|
try {
|
|
30246
|
-
const raw =
|
|
30406
|
+
const raw = readFileSync13(this.autoRolloutLatchPath(), "utf-8");
|
|
30247
30407
|
const parsed = JSON.parse(raw);
|
|
30248
30408
|
if (parsed && parsed.v === 1 && typeof parsed.pin === "string" && parsed.pin.length > 0 && (parsed.outcome === "attempting" || parsed.outcome === "failed")) {
|
|
30249
30409
|
return parsed;
|
|
@@ -30258,8 +30418,8 @@ class HostdServer {
|
|
|
30258
30418
|
}
|
|
30259
30419
|
writeAutoRolloutLatch(latch) {
|
|
30260
30420
|
try {
|
|
30261
|
-
|
|
30262
|
-
|
|
30421
|
+
mkdirSync6(this.hostdDirPath(), { recursive: true });
|
|
30422
|
+
writeFileSync7(this.autoRolloutLatchPath(), JSON.stringify(latch, null, 2), {
|
|
30263
30423
|
mode: 384
|
|
30264
30424
|
});
|
|
30265
30425
|
} catch (e) {
|
|
@@ -30282,7 +30442,7 @@ class HostdServer {
|
|
|
30282
30442
|
return resolveHostdConfigPath(this.opts.configPath);
|
|
30283
30443
|
}
|
|
30284
30444
|
fleetComposePath() {
|
|
30285
|
-
return
|
|
30445
|
+
return join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
30286
30446
|
}
|
|
30287
30447
|
recoverRolloutPinJournal(context) {
|
|
30288
30448
|
let note = null;
|
|
@@ -30321,7 +30481,7 @@ class HostdServer {
|
|
|
30321
30481
|
return note;
|
|
30322
30482
|
}
|
|
30323
30483
|
async start() {
|
|
30324
|
-
const hostdDir =
|
|
30484
|
+
const hostdDir = join13(this.opts.homeDir, ".switchroom", "hostd");
|
|
30325
30485
|
await mkdir2(hostdDir, { recursive: true });
|
|
30326
30486
|
await chmod(hostdDir, 493).catch(() => {
|
|
30327
30487
|
return;
|
|
@@ -30342,13 +30502,13 @@ class HostdServer {
|
|
|
30342
30502
|
}
|
|
30343
30503
|
try {
|
|
30344
30504
|
for (const name of agentNames) {
|
|
30345
|
-
const dir =
|
|
30346
|
-
const sockPath =
|
|
30505
|
+
const dir = join13(hostdDir, name);
|
|
30506
|
+
const sockPath = join13(dir, "sock");
|
|
30347
30507
|
await mkdir2(dir, { recursive: true });
|
|
30348
30508
|
await chmod(dir, 493).catch(() => {
|
|
30349
30509
|
return;
|
|
30350
30510
|
});
|
|
30351
|
-
if (
|
|
30511
|
+
if (existsSync17(sockPath))
|
|
30352
30512
|
await unlink(sockPath).catch(() => {
|
|
30353
30513
|
return;
|
|
30354
30514
|
});
|
|
@@ -30379,13 +30539,13 @@ class HostdServer {
|
|
|
30379
30539
|
process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
|
|
30380
30540
|
`);
|
|
30381
30541
|
} else {
|
|
30382
|
-
const dir =
|
|
30383
|
-
const sockPath =
|
|
30542
|
+
const dir = join13(hostdDir, "operator");
|
|
30543
|
+
const sockPath = join13(dir, "sock");
|
|
30384
30544
|
await mkdir2(dir, { recursive: true });
|
|
30385
30545
|
await chmod(dir, 493).catch(() => {
|
|
30386
30546
|
return;
|
|
30387
30547
|
});
|
|
30388
|
-
if (
|
|
30548
|
+
if (existsSync17(sockPath))
|
|
30389
30549
|
await unlink(sockPath).catch(() => {
|
|
30390
30550
|
return;
|
|
30391
30551
|
});
|
|
@@ -30421,11 +30581,11 @@ class HostdServer {
|
|
|
30421
30581
|
}
|
|
30422
30582
|
async reconcileOrphanedFleetMutations() {
|
|
30423
30583
|
const path3 = this.auditLogPath();
|
|
30424
|
-
if (!
|
|
30584
|
+
if (!existsSync17(path3))
|
|
30425
30585
|
return;
|
|
30426
30586
|
let raw;
|
|
30427
30587
|
try {
|
|
30428
|
-
raw =
|
|
30588
|
+
raw = readFileSync13(path3, "utf-8");
|
|
30429
30589
|
} catch {
|
|
30430
30590
|
return;
|
|
30431
30591
|
}
|
|
@@ -30824,11 +30984,11 @@ class HostdServer {
|
|
|
30824
30984
|
missingApplyAssets() {
|
|
30825
30985
|
const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
|
|
30826
30986
|
return [
|
|
30827
|
-
|
|
30828
|
-
|
|
30829
|
-
|
|
30830
|
-
|
|
30831
|
-
].filter((p) => !
|
|
30987
|
+
join13(root, "profiles"),
|
|
30988
|
+
join13(root, "profiles", "default"),
|
|
30989
|
+
join13(root, "vendor", "hindsight-memory"),
|
|
30990
|
+
join13(root, "skills")
|
|
30991
|
+
].filter((p) => !existsSync17(p));
|
|
30832
30992
|
}
|
|
30833
30993
|
applyAssetPreflight(request_id, started) {
|
|
30834
30994
|
const missing = this.missingApplyAssets();
|
|
@@ -31086,19 +31246,19 @@ class HostdServer {
|
|
|
31086
31246
|
return entry;
|
|
31087
31247
|
}
|
|
31088
31248
|
hostdDirPath() {
|
|
31089
|
-
return
|
|
31249
|
+
return join13(this.opts.homeDir, ".switchroom", "hostd");
|
|
31090
31250
|
}
|
|
31091
31251
|
hostdDirHostPath() {
|
|
31092
31252
|
const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
|
|
31093
|
-
return
|
|
31253
|
+
return join13(hostHome, ".switchroom", "hostd");
|
|
31094
31254
|
}
|
|
31095
31255
|
async beginSelfBump(req, caller, started) {
|
|
31096
31256
|
const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
|
|
31097
|
-
const composePath =
|
|
31098
|
-
if (!
|
|
31257
|
+
const composePath = join13(this.hostdDirPath(), "docker-compose.yml");
|
|
31258
|
+
if (!existsSync17(composePath)) {
|
|
31099
31259
|
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);
|
|
31100
31260
|
}
|
|
31101
|
-
const before =
|
|
31261
|
+
const before = readFileSync13(composePath, "utf8");
|
|
31102
31262
|
const bumped = bumpHostdComposeImageTag(before, req.args.pin);
|
|
31103
31263
|
if (!bumped) {
|
|
31104
31264
|
return deniedResponse(req.request_id, `rollout: hostd needs a self-bump to ${req.args.pin} but no ` + `switchroom-hostd image line was found in ${composePath} ` + `(hand-edited compose?). Run \`switchroom hostd install --tag ` + `${req.args.pin}\` on the host, then re-run the roll. Nothing ` + `was changed.`, Date.now() - started);
|
|
@@ -31119,10 +31279,10 @@ class HostdServer {
|
|
|
31119
31279
|
try {
|
|
31120
31280
|
const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
|
|
31121
31281
|
for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
|
|
31122
|
-
unlinkSync5(
|
|
31282
|
+
unlinkSync5(join13(this.hostdDirPath(), f));
|
|
31123
31283
|
}
|
|
31124
31284
|
} catch {}
|
|
31125
|
-
const markerPath =
|
|
31285
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
31126
31286
|
const marker = {
|
|
31127
31287
|
v: 1,
|
|
31128
31288
|
request_id: req.request_id,
|
|
@@ -31135,10 +31295,10 @@ class HostdServer {
|
|
|
31135
31295
|
prior_hostd_version: ownVersion
|
|
31136
31296
|
};
|
|
31137
31297
|
copyFileSync3(composePath, bakPath);
|
|
31138
|
-
|
|
31298
|
+
writeFileSync7(composePath, bumped.yaml, "utf8");
|
|
31139
31299
|
process.stderr.write(`hostd: self-bump rewrote ${bumped.bumpedCount} switchroom-hostd ` + `image line(s) → ${bumped.newImageRef}
|
|
31140
31300
|
`);
|
|
31141
|
-
|
|
31301
|
+
writeFileSync7(markerPath, encodePendingRolloutMarker(marker), {
|
|
31142
31302
|
mode: 384
|
|
31143
31303
|
});
|
|
31144
31304
|
const entry = {
|
|
@@ -31206,7 +31366,7 @@ class HostdServer {
|
|
|
31206
31366
|
rollbackBump();
|
|
31207
31367
|
entry.result = "error";
|
|
31208
31368
|
entry.finished_at = Date.now();
|
|
31209
|
-
entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${
|
|
31369
|
+
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.`;
|
|
31210
31370
|
if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
|
|
31211
31371
|
this.fleetMutationInFlight = null;
|
|
31212
31372
|
}
|
|
@@ -31233,10 +31393,10 @@ class HostdServer {
|
|
|
31233
31393
|
try {
|
|
31234
31394
|
if (!Number.isInteger(messageId) || messageId <= 0)
|
|
31235
31395
|
return;
|
|
31236
|
-
const markerPath =
|
|
31237
|
-
if (!
|
|
31396
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
31397
|
+
if (!existsSync17(markerPath))
|
|
31238
31398
|
return;
|
|
31239
|
-
const marker = parsePendingRolloutMarker(
|
|
31399
|
+
const marker = parsePendingRolloutMarker(readFileSync13(markerPath, "utf8"));
|
|
31240
31400
|
if (!marker)
|
|
31241
31401
|
return;
|
|
31242
31402
|
if (marker.request_id !== requestId)
|
|
@@ -31246,17 +31406,17 @@ class HostdServer {
|
|
|
31246
31406
|
narration_message_id: messageId
|
|
31247
31407
|
};
|
|
31248
31408
|
const tmp = `${markerPath}.tmp`;
|
|
31249
|
-
|
|
31250
|
-
|
|
31409
|
+
writeFileSync7(tmp, encodePendingRolloutMarker(updated), { mode: 384 });
|
|
31410
|
+
renameSync6(tmp, markerPath);
|
|
31251
31411
|
} catch {}
|
|
31252
31412
|
}
|
|
31253
31413
|
async resumePendingSelfBumpRollout() {
|
|
31254
|
-
const markerPath =
|
|
31255
|
-
if (!
|
|
31414
|
+
const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
31415
|
+
if (!existsSync17(markerPath))
|
|
31256
31416
|
return;
|
|
31257
31417
|
let raw;
|
|
31258
31418
|
try {
|
|
31259
|
-
raw =
|
|
31419
|
+
raw = readFileSync13(markerPath, "utf8");
|
|
31260
31420
|
} finally {
|
|
31261
31421
|
try {
|
|
31262
31422
|
unlinkSync5(markerPath);
|
|
@@ -31307,7 +31467,7 @@ class HostdServer {
|
|
|
31307
31467
|
return;
|
|
31308
31468
|
}
|
|
31309
31469
|
if (needsSelfBump(ownVersion, marker.pin)) {
|
|
31310
|
-
await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${
|
|
31470
|
+
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.`);
|
|
31311
31471
|
return;
|
|
31312
31472
|
}
|
|
31313
31473
|
process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
|
|
@@ -31415,7 +31575,7 @@ class HostdServer {
|
|
|
31415
31575
|
}
|
|
31416
31576
|
let beforeContent;
|
|
31417
31577
|
try {
|
|
31418
|
-
beforeContent =
|
|
31578
|
+
beforeContent = readFileSync13(configPath, "utf-8");
|
|
31419
31579
|
} catch {
|
|
31420
31580
|
beforeContent = "";
|
|
31421
31581
|
}
|
|
@@ -31535,7 +31695,7 @@ class HostdServer {
|
|
|
31535
31695
|
try {
|
|
31536
31696
|
let snapshot;
|
|
31537
31697
|
try {
|
|
31538
|
-
snapshot =
|
|
31698
|
+
snapshot = readFileSync13(configPath, "utf-8");
|
|
31539
31699
|
} catch (e) {
|
|
31540
31700
|
await approval.finalize({
|
|
31541
31701
|
outcome: "reconcile_failed_rolled_back",
|
|
@@ -31833,8 +31993,8 @@ ${output.recovery.stderr}` : "";
|
|
|
31833
31993
|
if (this.opts.imageRefsForDigests)
|
|
31834
31994
|
return this.opts.imageRefsForDigests();
|
|
31835
31995
|
try {
|
|
31836
|
-
const composePath =
|
|
31837
|
-
if (!
|
|
31996
|
+
const composePath = join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
31997
|
+
if (!existsSync17(composePath))
|
|
31838
31998
|
return [];
|
|
31839
31999
|
const r = spawnSync4("docker", [
|
|
31840
32000
|
"compose",
|
|
@@ -32054,7 +32214,11 @@ ${pinNote}`.trim();
|
|
|
32054
32214
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
32055
32215
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
32056
32216
|
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
32057
|
-
...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {}
|
|
32217
|
+
...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {},
|
|
32218
|
+
...(() => {
|
|
32219
|
+
const stamp = readHostCliStamp();
|
|
32220
|
+
return stamp ? { hostCli: stamp } : {};
|
|
32221
|
+
})()
|
|
32058
32222
|
});
|
|
32059
32223
|
this.opts.rolloutRelay.postTerminal({
|
|
32060
32224
|
requestId: entry.request_id,
|
|
@@ -32092,10 +32256,10 @@ ${pinNote}`.trim();
|
|
|
32092
32256
|
}
|
|
32093
32257
|
rolloutRowExistsInLog(targetRequestId) {
|
|
32094
32258
|
const path3 = this.auditLogPath();
|
|
32095
|
-
if (!
|
|
32259
|
+
if (!existsSync17(path3))
|
|
32096
32260
|
return false;
|
|
32097
32261
|
try {
|
|
32098
|
-
const raw =
|
|
32262
|
+
const raw = readFileSync13(path3, "utf-8");
|
|
32099
32263
|
return latestRolloutRowForRequest(raw, targetRequestId) !== null;
|
|
32100
32264
|
} catch {
|
|
32101
32265
|
return false;
|
|
@@ -32103,11 +32267,11 @@ ${pinNote}`.trim();
|
|
|
32103
32267
|
}
|
|
32104
32268
|
rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
|
|
32105
32269
|
const path3 = this.auditLogPath();
|
|
32106
|
-
if (!
|
|
32270
|
+
if (!existsSync17(path3))
|
|
32107
32271
|
return null;
|
|
32108
32272
|
let raw;
|
|
32109
32273
|
try {
|
|
32110
|
-
raw =
|
|
32274
|
+
raw = readFileSync13(path3, "utf-8");
|
|
32111
32275
|
} catch {
|
|
32112
32276
|
return null;
|
|
32113
32277
|
}
|
|
@@ -32189,7 +32353,7 @@ ${pinNote}`.trim();
|
|
|
32189
32353
|
}
|
|
32190
32354
|
}
|
|
32191
32355
|
auditLogPath() {
|
|
32192
|
-
return this.opts.auditLogPath ??
|
|
32356
|
+
return this.opts.auditLogPath ?? join13(this.opts.homeDir, ".switchroom", "host-control-audit.log");
|
|
32193
32357
|
}
|
|
32194
32358
|
maybeRotateAuditLog(path3) {
|
|
32195
32359
|
const { maxBytes, maxFiles } = resolveRotationConfig({
|
|
@@ -33673,7 +33837,7 @@ function makeRestart(switchroomBin) {
|
|
|
33673
33837
|
}
|
|
33674
33838
|
|
|
33675
33839
|
// src/host-control/update-notifier.ts
|
|
33676
|
-
import { mkdirSync as
|
|
33840
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync as renameSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
33677
33841
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
33678
33842
|
import { dirname as dirname10 } from "node:path";
|
|
33679
33843
|
var FINALIZE_DETAIL_MAX = 450;
|
|
@@ -33790,7 +33954,7 @@ class UpdateNotifier {
|
|
|
33790
33954
|
}
|
|
33791
33955
|
readState() {
|
|
33792
33956
|
try {
|
|
33793
|
-
const raw =
|
|
33957
|
+
const raw = readFileSync14(this.opts.statePath, "utf8");
|
|
33794
33958
|
const parsed = JSON.parse(raw);
|
|
33795
33959
|
if (parsed === null || typeof parsed !== "object")
|
|
33796
33960
|
return null;
|
|
@@ -33810,11 +33974,11 @@ class UpdateNotifier {
|
|
|
33810
33974
|
}
|
|
33811
33975
|
writeState(state) {
|
|
33812
33976
|
try {
|
|
33813
|
-
|
|
33977
|
+
mkdirSync7(dirname10(this.opts.statePath), { recursive: true });
|
|
33814
33978
|
const tmp = `${this.opts.statePath}.tmp`;
|
|
33815
|
-
|
|
33979
|
+
writeFileSync8(tmp, JSON.stringify(state) + `
|
|
33816
33980
|
`, "utf8");
|
|
33817
|
-
|
|
33981
|
+
renameSync7(tmp, this.opts.statePath);
|
|
33818
33982
|
} catch (err2) {
|
|
33819
33983
|
this.log(`failed to persist notify state: ${errMsg2(err2)}`);
|
|
33820
33984
|
}
|
|
@@ -33919,7 +34083,7 @@ import { appendFile as appendFile2 } from "node:fs/promises";
|
|
|
33919
34083
|
async function loadConfigResilient() {
|
|
33920
34084
|
try {
|
|
33921
34085
|
const config = loadConfig();
|
|
33922
|
-
clearStaleDegradedMarker(
|
|
34086
|
+
clearStaleDegradedMarker(homedir9(), (m) => process.stderr.write(`hostd: ${m}
|
|
33923
34087
|
`));
|
|
33924
34088
|
return config;
|
|
33925
34089
|
} catch (err2) {
|
|
@@ -33931,13 +34095,13 @@ async function loadConfigResilient() {
|
|
|
33931
34095
|
} catch {
|
|
33932
34096
|
configPath = undefined;
|
|
33933
34097
|
}
|
|
33934
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
34098
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir9(), ".switchroom", "agents");
|
|
33935
34099
|
const listGatewayCandidates = () => {
|
|
33936
34100
|
const out = [];
|
|
33937
34101
|
try {
|
|
33938
34102
|
for (const name of readdirSync7(agentsDir).sort()) {
|
|
33939
34103
|
const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
|
|
33940
|
-
if (
|
|
34104
|
+
if (existsSync18(sock))
|
|
33941
34105
|
out.push({ agent: name, sock });
|
|
33942
34106
|
}
|
|
33943
34107
|
} catch {}
|
|
@@ -33960,7 +34124,7 @@ async function loadConfigResilient() {
|
|
|
33960
34124
|
};
|
|
33961
34125
|
return waitForConfigRecovery({
|
|
33962
34126
|
initialError: err2,
|
|
33963
|
-
homeDir:
|
|
34127
|
+
homeDir: homedir9(),
|
|
33964
34128
|
loadFn: () => loadConfig(),
|
|
33965
34129
|
configPath,
|
|
33966
34130
|
notify,
|
|
@@ -33984,10 +34148,10 @@ async function main() {
|
|
|
33984
34148
|
`);
|
|
33985
34149
|
process.exit(2);
|
|
33986
34150
|
}
|
|
33987
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
34151
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir9(), ".switchroom", "agents");
|
|
33988
34152
|
const resolveGatewaySocket = (agentName) => {
|
|
33989
34153
|
const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
|
|
33990
|
-
return
|
|
34154
|
+
return existsSync18(sock) ? sock : null;
|
|
33991
34155
|
};
|
|
33992
34156
|
const approvalGateway = new SocketApprovalGateway({
|
|
33993
34157
|
resolveGatewaySocket,
|
|
@@ -34011,7 +34175,7 @@ async function main() {
|
|
|
34011
34175
|
`)
|
|
34012
34176
|
});
|
|
34013
34177
|
const server = new HostdServer({
|
|
34014
|
-
homeDir:
|
|
34178
|
+
homeDir: homedir9(),
|
|
34015
34179
|
agentUids,
|
|
34016
34180
|
fleetComponents: () => dockerFleetComponents(),
|
|
34017
34181
|
config: {
|
|
@@ -34041,7 +34205,7 @@ async function main() {
|
|
|
34041
34205
|
const autoRel = config.host_control?.auto_release_check;
|
|
34042
34206
|
const watcherMode = resolveReleaseWatcherMode(config);
|
|
34043
34207
|
if (watcherMode.mode !== "off") {
|
|
34044
|
-
const eventsLog =
|
|
34208
|
+
const eventsLog = join14(homedir9(), ".switchroom", "release-watcher-events.jsonl");
|
|
34045
34209
|
const log = (m) => {
|
|
34046
34210
|
process.stderr.write(`hostd: ${m}
|
|
34047
34211
|
`);
|
|
@@ -34100,7 +34264,7 @@ async function main() {
|
|
|
34100
34264
|
`);
|
|
34101
34265
|
} else
|
|
34102
34266
|
notifier = new UpdateNotifier({
|
|
34103
|
-
statePath:
|
|
34267
|
+
statePath: join14(homedir9(), ".switchroom", "release-notify-state.json"),
|
|
34104
34268
|
repostSuppressMs: cardTimeoutMs,
|
|
34105
34269
|
isFleetMutationInFlight: () => server.isFleetMutationLocked(),
|
|
34106
34270
|
startApply: (requestId) => {
|