switchroom 0.15.37 → 0.15.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +89 -89
- package/dist/auth-broker/index.js +89 -89
- package/dist/cli/autoaccept-poll.js +13 -7
- package/dist/cli/drive-write-pretool.mjs +10 -10
- package/dist/cli/notion-write-pretool.mjs +91 -91
- package/dist/cli/skill-validate-pretool.mjs +72 -72
- package/dist/cli/switchroom.js +857 -572
- package/dist/cli/ui/index.html +87 -17
- package/dist/host-control/main.js +158 -158
- package/dist/vault/approvals/kernel-server.js +91 -91
- package/dist/vault/broker/server.js +92 -92
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +1 -1
- package/profiles/_base/start.sh.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +2 -0
- package/skills/switchroom-manage/SKILL.md +1 -1
- package/skills/switchroom-runtime/SKILL.md +1 -1
- package/telegram-plugin/answer-stream.ts +1 -1
- package/telegram-plugin/bridge/bridge.ts +18 -1
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/bridge/tool-filter.ts +77 -0
- package/telegram-plugin/chat-lock.ts +1 -1
- package/telegram-plugin/credits-watch.ts +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +141 -115
- package/telegram-plugin/dist/gateway/gateway.js +318 -207
- package/telegram-plugin/dist/server.js +193 -164
- package/telegram-plugin/gateway/auto-classify-mid-turn.ts +1 -1
- package/telegram-plugin/gateway/boot-card.ts +5 -1
- package/telegram-plugin/gateway/boot-probes.ts +62 -0
- package/telegram-plugin/gateway/cron-session.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +133 -12
- package/telegram-plugin/gateway/grant-restart.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +1 -1
- package/telegram-plugin/gateway/inbound-delivery-machine.ts +1 -1
- package/telegram-plugin/gateway/interrupt-defer.ts +1 -1
- package/telegram-plugin/gateway/ipc-protocol.ts +12 -0
- package/telegram-plugin/gateway/permission-card-origin.ts +62 -0
- package/telegram-plugin/gateway/permission-timeout.ts +70 -0
- package/telegram-plugin/gateway/prefix-warmup.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.test.ts +1 -1
- package/telegram-plugin/gateway/webhook-ingest-server.ts +1 -1
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +1 -1
- package/telegram-plugin/interrupt-marker.ts +1 -1
- package/telegram-plugin/over-ping-safety-net.ts +1 -1
- package/telegram-plugin/scoped-approval.ts +1 -1
- package/telegram-plugin/secret-detect/vault-error.ts +1 -1
- package/telegram-plugin/silence-poke.ts +2 -2
- package/telegram-plugin/silent-reply-anchor.ts +1 -1
- package/telegram-plugin/slot-banner-driver.ts +1 -1
- package/telegram-plugin/startup-reset.ts +1 -1
- package/telegram-plugin/tests/boot-probes-connections.test.ts +66 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +1 -1
- package/telegram-plugin/tests/inbound-delivery-machine.test.ts +1 -1
- package/telegram-plugin/tests/permission-card-origin.test.ts +97 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +23 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +76 -0
- package/telegram-plugin/tests/permission-timeout.test.ts +87 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +1 -1
- package/telegram-plugin/tests/silence-poke.test.ts +1 -1
- package/telegram-plugin/tests/tool-filter.test.ts +87 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +1 -1
- package/telegram-plugin/turn-flush-safety.ts +1 -1
- package/telegram-plugin/uat/assertions.ts +1 -1
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-ack-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-fast-trivial-dm.test.ts +2 -2
- package/telegram-plugin/uat/scenarios/jtbd-forwarded-burst-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-memory-survives-restart-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-reflective-status-reaction-dm.test.ts +1 -1
- package/telegram-plugin/uat/scenarios/jtbd-wake-audit-content-dm.test.ts +1 -1
package/dist/cli/ui/index.html
CHANGED
|
@@ -474,7 +474,14 @@
|
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
@media (min-width: 701px) {
|
|
477
|
-
|
|
477
|
+
/* The Accounts tab shows a wide-screen TABLE and a mobile card grid,
|
|
478
|
+
hiding the grid on desktop. Scope that hide to #accounts: the
|
|
479
|
+
Connections tab reuses .accounts-grid for its OAuth/Notion cards but
|
|
480
|
+
has NO table fallback, so an unscoped rule blanked every connection
|
|
481
|
+
card on any screen wider than 700px (the cards rendered but were
|
|
482
|
+
display:none, while the empty-state text — not in a grid — still
|
|
483
|
+
showed). */
|
|
484
|
+
#accounts .accounts-grid { display: none; }
|
|
478
485
|
}
|
|
479
486
|
|
|
480
487
|
@media (max-width: 600px) {
|
|
@@ -1029,20 +1036,21 @@
|
|
|
1029
1036
|
.then(r => r.ok ? r.json().then(d => ({ ok: true, data: d })) : { ok: false, data: fallback })
|
|
1030
1037
|
.catch(() => ({ ok: false, data: fallback }));
|
|
1031
1038
|
try {
|
|
1032
|
-
const [g, ms, n, ag] = await Promise.all([
|
|
1039
|
+
const [g, ms, n, li, ag] = await Promise.all([
|
|
1033
1040
|
safe(fetch(`${API}/api/google-accounts`, { headers: authHeaders() }), []),
|
|
1034
1041
|
safe(fetch(`${API}/api/microsoft-accounts`, { headers: authHeaders() }), []),
|
|
1035
1042
|
safe(fetch(`${API}/api/notion-workspace`, { headers: authHeaders() }), { configured: false, databases: [] }),
|
|
1043
|
+
safe(fetch(`${API}/api/linear-agents`, { headers: authHeaders() }), { configured: false, agents: [] }),
|
|
1036
1044
|
safe(fetch(`${API}/api/agents`, { headers: authHeaders() }), []),
|
|
1037
1045
|
]);
|
|
1038
|
-
// Notion legitimately
|
|
1039
|
-
// providers + the agent list are the ones whose failure must not
|
|
1040
|
-
// as "empty".
|
|
1046
|
+
// Notion + Linear legitimately report unconfigured — not a failure. The
|
|
1047
|
+
// OAuth providers + the agent list are the ones whose failure must not
|
|
1048
|
+
// read as "empty".
|
|
1041
1049
|
const fetchFailed = !g.ok || !ms.ok || !ag.ok;
|
|
1042
1050
|
renderConnections({
|
|
1043
|
-
google: g.data, microsoft: ms.data, notion: n.data,
|
|
1051
|
+
google: g.data, microsoft: ms.data, notion: n.data, linear: li.data,
|
|
1044
1052
|
agentNames: (ag.data || []).map(a => a.name).sort(),
|
|
1045
|
-
googleFailed: !g.ok, microsoftFailed: !ms.ok, fetchFailed,
|
|
1053
|
+
googleFailed: !g.ok, microsoftFailed: !ms.ok, linearFailed: !li.ok, fetchFailed,
|
|
1046
1054
|
});
|
|
1047
1055
|
clearError();
|
|
1048
1056
|
// Self-heal a transient blip without a manual re-click. Bounded
|
|
@@ -1240,12 +1248,33 @@
|
|
|
1240
1248
|
}
|
|
1241
1249
|
}
|
|
1242
1250
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1251
|
+
// Canonical tab list — kept in sync with the nav buttons above and with
|
|
1252
|
+
// SPA_TAB_ROUTES in src/web/server.ts (the server serves the SPA shell for
|
|
1253
|
+
// these paths so a reload/deep-link doesn't 404).
|
|
1254
|
+
const TAB_NAMES = ['summary', 'agents', 'accounts', 'system', 'memory', 'connections', 'schedule', 'approvals'];
|
|
1255
|
+
|
|
1256
|
+
// Derive the active tab from the URL path (`/connections` → 'connections',
|
|
1257
|
+
// '/' → 'summary'). Unknown paths fall back to summary.
|
|
1258
|
+
function tabFromPath() {
|
|
1259
|
+
const name = location.pathname.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
1260
|
+
return TAB_NAMES.includes(name) ? name : 'summary';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// opts.push === false suppresses the history push — used when the switch is
|
|
1264
|
+
// itself driven by navigation (initial load, back/forward) so we don't
|
|
1265
|
+
// double-stack history entries.
|
|
1266
|
+
function switchTab(tab, opts) {
|
|
1267
|
+
if (!TAB_NAMES.includes(tab)) tab = 'summary';
|
|
1268
|
+
for (const t of TAB_NAMES) {
|
|
1246
1269
|
document.getElementById(`tab-${t}`).classList.toggle('active', tab === t);
|
|
1247
1270
|
document.getElementById(t).style.display = tab === t ? '' : 'none';
|
|
1248
1271
|
}
|
|
1272
|
+
// Mirror the tab into the URL path so reload/deep-link/back-forward all
|
|
1273
|
+
// land here. Summary canonicalises to '/'.
|
|
1274
|
+
if (!opts || opts.push !== false) {
|
|
1275
|
+
const path = tab === 'summary' ? '/' : '/' + tab;
|
|
1276
|
+
if (location.pathname !== path) history.pushState({ tab }, '', path);
|
|
1277
|
+
}
|
|
1249
1278
|
if (tab === 'summary') fetchSummary();
|
|
1250
1279
|
if (tab === 'accounts') fetchAccounts();
|
|
1251
1280
|
if (tab === 'system') fetchSystemHealth();
|
|
@@ -1255,6 +1284,11 @@
|
|
|
1255
1284
|
if (tab === 'approvals') fetchApprovals();
|
|
1256
1285
|
}
|
|
1257
1286
|
|
|
1287
|
+
// Back/forward: restore the tab from the history entry (or the path).
|
|
1288
|
+
window.addEventListener('popstate', (e) => {
|
|
1289
|
+
switchTab((e.state && e.state.tab) || tabFromPath(), { push: false });
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1258
1292
|
// Fleet overview — ONE round-trip to the server-side aggregate
|
|
1259
1293
|
// (/api/summary), which pulls each part through the same per-tab
|
|
1260
1294
|
// cache so a Summary open warms the tabs and they can never disagree.
|
|
@@ -2117,6 +2151,7 @@
|
|
|
2117
2151
|
const google = data.google || [];
|
|
2118
2152
|
const microsoft = data.microsoft || [];
|
|
2119
2153
|
const notion = data.notion || { configured: false, databases: [] };
|
|
2154
|
+
const linear = data.linear || { configured: false, agents: [] };
|
|
2120
2155
|
const agentNames = data.agentNames || [];
|
|
2121
2156
|
|
|
2122
2157
|
// When a provider's fetch FAILED (not genuinely empty), its empty-state
|
|
@@ -2180,6 +2215,37 @@
|
|
|
2180
2215
|
${fullAccessBanner}${notionBody}
|
|
2181
2216
|
</div>`;
|
|
2182
2217
|
|
|
2218
|
+
// Linear — first-class OAuth app actors (config-only; the web tier never
|
|
2219
|
+
// reads the per-agent vault, so no token/expiry is shown — and never the
|
|
2220
|
+
// secret). One card per agent with linear_agent.enabled.
|
|
2221
|
+
const linearCards = (linear.agents || []).map(a => {
|
|
2222
|
+
const meta = [];
|
|
2223
|
+
if (a.workspaceId) meta.push(`<div class="meta-item"><label>Workspace </label><span>${escapeHtml(a.workspaceId)}</span></div>`);
|
|
2224
|
+
if (a.defaultTeamId) meta.push(`<div class="meta-item"><label>Default team </label><span>${escapeHtml(a.defaultTeamId)}</span></div>`);
|
|
2225
|
+
if (a.tokenVaultKey) meta.push(`<div class="meta-item"><label>Token </label><span><code>${escapeHtml(a.tokenVaultKey)}</code></span></div>`);
|
|
2226
|
+
return `
|
|
2227
|
+
<div class="account-card">
|
|
2228
|
+
<div class="account-card-header">
|
|
2229
|
+
<div class="account-label">${escapeHtml(a.agent)}</div>
|
|
2230
|
+
<span style="margin-left:auto" class="usage-pill primary">OAuth agent</span>
|
|
2231
|
+
</div>
|
|
2232
|
+
<div class="card-meta" style="padding:0">${meta.join('') || _dimC('—')}</div>
|
|
2233
|
+
</div>`;
|
|
2234
|
+
}).join('');
|
|
2235
|
+
const linearNote = (linear.agents && linear.agents.length)
|
|
2236
|
+
? `<div style="margin:0 0 .6rem;font-size:.85rem;color:var(--text-dim)">Connected as Linear <b>OAuth app actors</b> — each appears in the workspace as its own actor (not a personal API token for MCP).</div>`
|
|
2237
|
+
: '';
|
|
2238
|
+
const linearBody = linearCards
|
|
2239
|
+
? `<div class="accounts-grid">${linearCards}</div>`
|
|
2240
|
+
: `<div class="loading" style="padding:.8rem">${data.linearFailed
|
|
2241
|
+
? "Couldn't load Linear agents — the data source was unreachable (retrying)."
|
|
2242
|
+
: 'No Linear agents connected. Run <code>switchroom linear-agent setup --agent <name></code> to connect one as a Linear OAuth app actor.'}</div>`;
|
|
2243
|
+
const linearSection = `
|
|
2244
|
+
<div style="margin-bottom:1.5rem">
|
|
2245
|
+
<h3 style="margin:0 0 .6rem;font-size:.95rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:.04em">Linear</h3>
|
|
2246
|
+
${linearNote}${linearBody}
|
|
2247
|
+
</div>`;
|
|
2248
|
+
|
|
2183
2249
|
// Tab-level degraded banner: when every OAuth account (Google +
|
|
2184
2250
|
// Microsoft) is config-only (no live broker slot) and there's at least
|
|
2185
2251
|
// one, the broker data is unavailable — surface that ABOVE the sections
|
|
@@ -2197,7 +2263,7 @@
|
|
|
2197
2263
|
? renderProblem(problemFor('connections-unreachable', {}))
|
|
2198
2264
|
: '';
|
|
2199
2265
|
|
|
2200
|
-
container.innerHTML = unreachableBanner + degradedBanner + googleSection + microsoftSection + notionSection;
|
|
2266
|
+
container.innerHTML = unreachableBanner + degradedBanner + googleSection + microsoftSection + notionSection + linearSection;
|
|
2201
2267
|
}
|
|
2202
2268
|
|
|
2203
2269
|
function renderSchedule(data) {
|
|
@@ -2635,12 +2701,16 @@
|
|
|
2635
2701
|
}
|
|
2636
2702
|
}
|
|
2637
2703
|
|
|
2638
|
-
// Init.
|
|
2639
|
-
//
|
|
2640
|
-
//
|
|
2641
|
-
//
|
|
2642
|
-
//
|
|
2643
|
-
|
|
2704
|
+
// Init. The initial tab comes from the URL path (so a reload/deep-link of
|
|
2705
|
+
// `/connections` opens Connections, not Summary); '/' → Summary. We stamp
|
|
2706
|
+
// the history entry so the first back/forward works, then switchTab
|
|
2707
|
+
// (push:false) shows the tab and fetches just its data. fetchAgents still
|
|
2708
|
+
// runs unconditionally (keeps the 10s fleet poll warm + the log WS depends
|
|
2709
|
+
// on it). Per-tab data is fetched on switch only — deliberately NOT on the
|
|
2710
|
+
// 10s interval (it fans out to system-health etc.; on-demand is enough).
|
|
2711
|
+
const initialTab = tabFromPath();
|
|
2712
|
+
history.replaceState({ tab: initialTab }, '', location.pathname);
|
|
2713
|
+
switchTab(initialTab, { push: false });
|
|
2644
2714
|
fetchAgents();
|
|
2645
2715
|
connectWebSocket();
|
|
2646
2716
|
// Fleet poll — gated on tab visibility. A phone with the dashboard
|