switchroom 0.15.33 → 0.15.35
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
CHANGED
|
@@ -50574,8 +50574,8 @@ var {
|
|
|
50574
50574
|
} = import__.default;
|
|
50575
50575
|
|
|
50576
50576
|
// src/build-info.ts
|
|
50577
|
-
var VERSION = "0.15.
|
|
50578
|
-
var COMMIT_SHA = "
|
|
50577
|
+
var VERSION = "0.15.35";
|
|
50578
|
+
var COMMIT_SHA = "beafcff6";
|
|
50579
50579
|
|
|
50580
50580
|
// src/cli/agent.ts
|
|
50581
50581
|
init_source();
|
|
@@ -54544,6 +54544,23 @@ function resolveRelease(opts) {
|
|
|
54544
54544
|
return opts.root;
|
|
54545
54545
|
return;
|
|
54546
54546
|
}
|
|
54547
|
+
function parseSemverTag(tag) {
|
|
54548
|
+
if (!tag)
|
|
54549
|
+
return null;
|
|
54550
|
+
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(tag.trim());
|
|
54551
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
54552
|
+
}
|
|
54553
|
+
function compareReleaseTags(a, b) {
|
|
54554
|
+
const pa = parseSemverTag(a);
|
|
54555
|
+
const pb = parseSemverTag(b);
|
|
54556
|
+
if (!pa || !pb)
|
|
54557
|
+
return null;
|
|
54558
|
+
for (let i = 0;i < 3; i++) {
|
|
54559
|
+
if (pa[i] !== pb[i])
|
|
54560
|
+
return pa[i] < pb[i] ? -1 : 1;
|
|
54561
|
+
}
|
|
54562
|
+
return 0;
|
|
54563
|
+
}
|
|
54547
54564
|
// src/cli/operator-uid.ts
|
|
54548
54565
|
import {
|
|
54549
54566
|
chownSync,
|
|
@@ -86916,7 +86933,46 @@ init_helpers();
|
|
|
86916
86933
|
import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync72, writeFileSync as writeFileSync41, statSync as statSync35, copyFileSync as copyFileSync12 } from "node:fs";
|
|
86917
86934
|
import { homedir as homedir49 } from "node:os";
|
|
86918
86935
|
import { join as join83 } from "node:path";
|
|
86936
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
86937
|
+
|
|
86938
|
+
// src/cli/deploy-version-guard.ts
|
|
86919
86939
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
86940
|
+
var defaultRunner2 = (args) => {
|
|
86941
|
+
const r = spawnSync15("docker", args, { encoding: "utf8" });
|
|
86942
|
+
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
86943
|
+
};
|
|
86944
|
+
function deployedImageTag(container, run = defaultRunner2) {
|
|
86945
|
+
const r = run(["inspect", "-f", "{{.Config.Image}}", container]);
|
|
86946
|
+
if (!r.ok)
|
|
86947
|
+
return null;
|
|
86948
|
+
const ref = r.stdout.trim();
|
|
86949
|
+
if (!ref)
|
|
86950
|
+
return null;
|
|
86951
|
+
const noDigest = ref.split("@")[0];
|
|
86952
|
+
const colon = noDigest.lastIndexOf(":");
|
|
86953
|
+
if (colon < 0)
|
|
86954
|
+
return null;
|
|
86955
|
+
const tag = noDigest.slice(colon + 1);
|
|
86956
|
+
return tag && !tag.includes("/") ? tag : null;
|
|
86957
|
+
}
|
|
86958
|
+
function checkDowngrade(opts) {
|
|
86959
|
+
const running = deployedImageTag(opts.container, opts.run);
|
|
86960
|
+
if (opts.allowDowngrade)
|
|
86961
|
+
return { skip: false, running };
|
|
86962
|
+
const cmp = compareReleaseTags(opts.targetTag, running);
|
|
86963
|
+
if (cmp !== null && cmp < 0) {
|
|
86964
|
+
return {
|
|
86965
|
+
skip: true,
|
|
86966
|
+
running,
|
|
86967
|
+
message: `Skipping ${opts.container} deploy \u2014 it is already on ${running}, newer than the ` + `requested ${opts.targetTag}.
|
|
86968
|
+
` + ` Refusing to downgrade: this guards against a concurrent rollout/update ` + `reverting a newer build.
|
|
86969
|
+
` + ` To force the downgrade, re-run with --allow-downgrade.`
|
|
86970
|
+
};
|
|
86971
|
+
}
|
|
86972
|
+
return { skip: false, running };
|
|
86973
|
+
}
|
|
86974
|
+
|
|
86975
|
+
// src/cli/hostd.ts
|
|
86920
86976
|
init_audit_reader();
|
|
86921
86977
|
function resolveHostdImageTag(explicitTag, release) {
|
|
86922
86978
|
if (explicitTag)
|
|
@@ -87054,7 +87110,7 @@ function backupExistingCompose() {
|
|
|
87054
87110
|
return bak;
|
|
87055
87111
|
}
|
|
87056
87112
|
function runDocker(args) {
|
|
87057
|
-
const r =
|
|
87113
|
+
const r = spawnSync16("docker", args, { encoding: "utf8" });
|
|
87058
87114
|
return {
|
|
87059
87115
|
ok: r.status === 0,
|
|
87060
87116
|
stdout: r.stdout ?? "",
|
|
@@ -87081,6 +87137,15 @@ async function doInstall(opts, program3) {
|
|
|
87081
87137
|
const composePath = hostdComposePath();
|
|
87082
87138
|
mkdirSync47(dir, { recursive: true });
|
|
87083
87139
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
87140
|
+
const guard = checkDowngrade({
|
|
87141
|
+
container: "switchroom-hostd",
|
|
87142
|
+
targetTag: imageTag,
|
|
87143
|
+
allowDowngrade: opts.allowDowngrade
|
|
87144
|
+
});
|
|
87145
|
+
if (guard.skip) {
|
|
87146
|
+
console.log(source_default.yellow(` \u23ed ${opts.dryRun ? "[dry-run] " : ""}${guard.message}`));
|
|
87147
|
+
return;
|
|
87148
|
+
}
|
|
87084
87149
|
const yaml = renderHostdComposeFile({
|
|
87085
87150
|
hostHome: resolveHostdHostHome(),
|
|
87086
87151
|
imageTag,
|
|
@@ -87190,7 +87255,7 @@ ${down.stderr}`));
|
|
|
87190
87255
|
}
|
|
87191
87256
|
function registerHostdCommand(program3) {
|
|
87192
87257
|
const hostd = program3.command("hostd").description("Manage switchroom-hostd, the host-control daemon for admin agents (RFC C)");
|
|
87193
|
-
hostd.command("install").description("Install or refresh the hostd container (writes ~/.switchroom/hostd/docker-compose.yml + docker compose up -d)").option("--tag <tag>", "Image tag override (default: resolved from release.pin in switchroom.yaml, else latest)").option("--dry-run", "Print the compose file and the docker commands without writing or running anything").action(withConfigError(async (opts) => {
|
|
87258
|
+
hostd.command("install").description("Install or refresh the hostd container (writes ~/.switchroom/hostd/docker-compose.yml + docker compose up -d)").option("--tag <tag>", "Image tag override (default: resolved from release.pin in switchroom.yaml, else latest)").option("--dry-run", "Print the compose file and the docker commands without writing or running anything").option("--allow-downgrade", "Deploy even if the running container is on a newer version (overrides the anti-revert guard)").action(withConfigError(async (opts) => {
|
|
87194
87259
|
await doInstall(opts, program3);
|
|
87195
87260
|
}));
|
|
87196
87261
|
hostd.command("status").description("Show daemon state and bound sockets").action(() => doStatus());
|
|
@@ -87247,7 +87312,7 @@ init_helpers();
|
|
|
87247
87312
|
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
|
|
87248
87313
|
import { homedir as homedir50 } from "node:os";
|
|
87249
87314
|
import { join as join84 } from "node:path";
|
|
87250
|
-
import { spawnSync as
|
|
87315
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
87251
87316
|
function resolveWebImageTag(explicitTag, release) {
|
|
87252
87317
|
if (explicitTag)
|
|
87253
87318
|
return explicitTag;
|
|
@@ -87346,7 +87411,7 @@ function backupExistingCompose2() {
|
|
|
87346
87411
|
return bak;
|
|
87347
87412
|
}
|
|
87348
87413
|
function runDocker2(args) {
|
|
87349
|
-
const r =
|
|
87414
|
+
const r = spawnSync17("docker", args, { encoding: "utf8" });
|
|
87350
87415
|
return {
|
|
87351
87416
|
ok: r.status === 0,
|
|
87352
87417
|
stdout: r.stdout ?? "",
|
|
@@ -87366,6 +87431,15 @@ async function doInstall2(opts, program3) {
|
|
|
87366
87431
|
mkdirSync48(dir, { recursive: true });
|
|
87367
87432
|
const cfg = getConfig(program3);
|
|
87368
87433
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
87434
|
+
const guard = checkDowngrade({
|
|
87435
|
+
container: "switchroom-web",
|
|
87436
|
+
targetTag: imageTag,
|
|
87437
|
+
allowDowngrade: opts.allowDowngrade
|
|
87438
|
+
});
|
|
87439
|
+
if (guard.skip) {
|
|
87440
|
+
console.log(source_default.yellow(` \u23ed ${opts.dryRun ? "[dry-run] " : ""}${guard.message}`));
|
|
87441
|
+
return;
|
|
87442
|
+
}
|
|
87369
87443
|
const yaml = renderWebComposeFile({
|
|
87370
87444
|
hostHome: homedir50(),
|
|
87371
87445
|
imageTag,
|
|
@@ -87456,7 +87530,7 @@ ${down.stderr}`));
|
|
|
87456
87530
|
}
|
|
87457
87531
|
function registerWebdCommand(program3) {
|
|
87458
87532
|
const webd = program3.command("webd").description("Manage switchroom-web, the dashboard + GitHub-webhook receiver container");
|
|
87459
|
-
webd.command("install").description("Install or refresh the web container (writes ~/.switchroom/web/docker-compose.yml + docker compose up -d)").option("--tag <tag>", "Image tag override (default: resolved from release.pin in switchroom.yaml, else latest)").option("--dry-run", "Print the compose file and the docker commands without writing or running anything").action(withConfigError(async (opts) => {
|
|
87533
|
+
webd.command("install").description("Install or refresh the web container (writes ~/.switchroom/web/docker-compose.yml + docker compose up -d)").option("--tag <tag>", "Image tag override (default: resolved from release.pin in switchroom.yaml, else latest)").option("--dry-run", "Print the compose file and the docker commands without writing or running anything").option("--allow-downgrade", "Deploy even if the running container is on a newer version (overrides the anti-revert guard)").action(withConfigError(async (opts) => {
|
|
87460
87534
|
await doInstall2(opts, program3);
|
|
87461
87535
|
}));
|
|
87462
87536
|
webd.command("status").description("Show web-service container state").action(() => doStatus2());
|
package/dist/cli/ui/index.html
CHANGED
|
@@ -1015,23 +1015,41 @@
|
|
|
1015
1015
|
return provLine + body;
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
|
-
async function fetchConnections() {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
//
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1018
|
+
async function fetchConnections(opts) {
|
|
1019
|
+
opts = opts || {};
|
|
1020
|
+
const attempt = opts.attempt || 0;
|
|
1021
|
+
// Distinguish a FAILED fetch from a genuinely-empty result. The old
|
|
1022
|
+
// `safe` collapsed both to []/{}, so a transient broker/web blip
|
|
1023
|
+
// rendered identically to "nothing configured" — and because this tab
|
|
1024
|
+
// fetches only on open (NOT the 10s fleet poll), a one-time failure
|
|
1025
|
+
// stuck until a manual re-click. Track ok per fetch so renderConnections
|
|
1026
|
+
// can show an honest "couldn't load" state, and self-heal with bounded
|
|
1027
|
+
// retries. (A single endpoint failing still renders the others.)
|
|
1028
|
+
const safe = (p, fallback) => p
|
|
1029
|
+
.then(r => r.ok ? r.json().then(d => ({ ok: true, data: d })) : { ok: false, data: fallback })
|
|
1030
|
+
.catch(() => ({ ok: false, data: fallback }));
|
|
1025
1031
|
try {
|
|
1026
|
-
const [
|
|
1032
|
+
const [g, ms, n, ag] = await Promise.all([
|
|
1027
1033
|
safe(fetch(`${API}/api/google-accounts`, { headers: authHeaders() }), []),
|
|
1028
1034
|
safe(fetch(`${API}/api/microsoft-accounts`, { headers: authHeaders() }), []),
|
|
1029
1035
|
safe(fetch(`${API}/api/notion-workspace`, { headers: authHeaders() }), { configured: false, databases: [] }),
|
|
1030
1036
|
safe(fetch(`${API}/api/agents`, { headers: authHeaders() }), []),
|
|
1031
1037
|
]);
|
|
1032
|
-
|
|
1033
|
-
|
|
1038
|
+
// Notion legitimately reports unconfigured — not a failure. The OAuth
|
|
1039
|
+
// providers + the agent list are the ones whose failure must not read
|
|
1040
|
+
// as "empty".
|
|
1041
|
+
const fetchFailed = !g.ok || !ms.ok || !ag.ok;
|
|
1042
|
+
renderConnections({
|
|
1043
|
+
google: g.data, microsoft: ms.data, notion: n.data,
|
|
1044
|
+
agentNames: (ag.data || []).map(a => a.name).sort(),
|
|
1045
|
+
googleFailed: !g.ok, microsoftFailed: !ms.ok, fetchFailed,
|
|
1046
|
+
});
|
|
1034
1047
|
clearError();
|
|
1048
|
+
// Self-heal a transient blip without a manual re-click. Bounded
|
|
1049
|
+
// backoff (3s, 6s, 9s); stops the instant a fetch succeeds.
|
|
1050
|
+
if (fetchFailed && attempt < 3) {
|
|
1051
|
+
setTimeout(() => fetchConnections({ attempt: attempt + 1 }), 3000 * (attempt + 1));
|
|
1052
|
+
}
|
|
1035
1053
|
} catch (err) {
|
|
1036
1054
|
showError(`Failed to fetch connections: ${err.message}`);
|
|
1037
1055
|
}
|
|
@@ -1566,6 +1584,14 @@
|
|
|
1566
1584
|
detail: 'These accounts are from the config; the broker did not return live slot state, so connection status may be stale.',
|
|
1567
1585
|
remediation: { kind: 'none', label: 'Refresh the tab once the broker is reachable to see live status.' },
|
|
1568
1586
|
};
|
|
1587
|
+
case 'connections-unreachable':
|
|
1588
|
+
return {
|
|
1589
|
+
title: "Couldn't load live connection data — retrying",
|
|
1590
|
+
detail: 'The dashboard could not reach the account data source (the broker or web container may be restarting). ' +
|
|
1591
|
+
'This is NOT "no accounts configured" — your connected accounts are unchanged. ' +
|
|
1592
|
+
'Auto-retrying; the tab fills in once it recovers.',
|
|
1593
|
+
remediation: { kind: 'none', label: 'Or refresh the page if it persists.' },
|
|
1594
|
+
};
|
|
1569
1595
|
default:
|
|
1570
1596
|
return { title: String(kind || 'Problem'), remediation: { kind: 'none' } };
|
|
1571
1597
|
}
|
|
@@ -2093,13 +2119,21 @@
|
|
|
2093
2119
|
const notion = data.notion || { configured: false, databases: [] };
|
|
2094
2120
|
const agentNames = data.agentNames || [];
|
|
2095
2121
|
|
|
2122
|
+
// When a provider's fetch FAILED (not genuinely empty), its empty-state
|
|
2123
|
+
// must say "couldn't load", never "none configured" — otherwise a
|
|
2124
|
+
// transient blip reads as "you have no accounts".
|
|
2096
2125
|
const googleSection = _connectionSection(
|
|
2097
2126
|
'Google',
|
|
2098
|
-
|
|
2127
|
+
data.googleFailed
|
|
2128
|
+
? "Couldn't load Google accounts — the data source was unreachable (retrying)."
|
|
2129
|
+
: 'No Google accounts. Add one under <code>google_accounts:</code> and run <code>switchroom auth google account add</code>.',
|
|
2099
2130
|
google.map(a => renderOAuthAccountCard(a, { showType: false, provider: 'google', agentNames })).join(''),
|
|
2100
2131
|
);
|
|
2101
2132
|
|
|
2102
2133
|
const msCards = microsoft.map(a => renderOAuthAccountCard(a, { showType: true, provider: 'microsoft', agentNames })).join('');
|
|
2134
|
+
const msEmpty = data.microsoftFailed
|
|
2135
|
+
? "Couldn't load Microsoft accounts — the data source was unreachable (retrying)."
|
|
2136
|
+
: 'No Microsoft accounts yet — click <b>Connect a Microsoft account</b> above (or <code>/connect microsoft</code> from Telegram).';
|
|
2103
2137
|
const microsoftSection = `
|
|
2104
2138
|
<div style="margin-bottom:1.5rem">
|
|
2105
2139
|
<h3 style="margin:0 0 .6rem;font-size:.95rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em">
|
|
@@ -2109,7 +2143,7 @@
|
|
|
2109
2143
|
<div id="ms-connect-card"></div>
|
|
2110
2144
|
${msCards
|
|
2111
2145
|
? `<div class="accounts-grid">${msCards}</div>`
|
|
2112
|
-
: `<div class="loading" style="padding:.8rem"
|
|
2146
|
+
: `<div class="loading" style="padding:.8rem">${msEmpty}</div>`}
|
|
2113
2147
|
</div>`;
|
|
2114
2148
|
|
|
2115
2149
|
let notionCards = '';
|
|
@@ -2156,7 +2190,14 @@
|
|
|
2156
2190
|
? renderProblem(problemFor('connections-degraded', {}))
|
|
2157
2191
|
: '';
|
|
2158
2192
|
|
|
2159
|
-
|
|
2193
|
+
// A failed fetch must NOT masquerade as "nothing configured" — lead
|
|
2194
|
+
// with an honest banner so the operator knows the empty look is a
|
|
2195
|
+
// load failure (auto-retrying), not an absence of accounts.
|
|
2196
|
+
const unreachableBanner = data.fetchFailed
|
|
2197
|
+
? renderProblem(problemFor('connections-unreachable', {}))
|
|
2198
|
+
: '';
|
|
2199
|
+
|
|
2200
|
+
container.innerHTML = unreachableBanner + degradedBanner + googleSection + microsoftSection + notionSection;
|
|
2160
2201
|
}
|
|
2161
2202
|
|
|
2162
2203
|
function renderSchedule(data) {
|
package/package.json
CHANGED
|
@@ -54460,10 +54460,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
54460
54460
|
}
|
|
54461
54461
|
|
|
54462
54462
|
// ../src/build-info.ts
|
|
54463
|
-
var VERSION = "0.15.
|
|
54464
|
-
var COMMIT_SHA = "
|
|
54465
|
-
var COMMIT_DATE = "2026-06-
|
|
54466
|
-
var LATEST_PR =
|
|
54463
|
+
var VERSION = "0.15.35";
|
|
54464
|
+
var COMMIT_SHA = "beafcff6";
|
|
54465
|
+
var COMMIT_DATE = "2026-06-16T07:06:45Z";
|
|
54466
|
+
var LATEST_PR = 2391;
|
|
54467
54467
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
54468
54468
|
|
|
54469
54469
|
// gateway/boot-version.ts
|