switchroom 0.8.1 → 0.10.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/README.md +49 -57
- package/bin/timezone-hook.sh +9 -7
- package/dist/agent-scheduler/index.js +285 -45
- package/dist/auth-broker/index.js +13932 -0
- package/dist/cli/switchroom.js +15931 -12778
- package/dist/host-control/main.js +582 -43
- package/dist/vault/approvals/kernel-server.js +276 -47
- package/dist/vault/broker/server.js +333 -69
- package/examples/minimal.yaml +63 -0
- package/examples/personal-google-workspace-mcp/.env.example +34 -0
- package/examples/personal-google-workspace-mcp/README.md +194 -0
- package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
- package/examples/switchroom.yaml +220 -0
- package/package.json +6 -4
- package/profiles/_base/start.sh.hbs +3 -3
- package/profiles/_shared/agent-self-service.md.hbs +126 -0
- package/profiles/default/CLAUDE.md +10 -0
- package/profiles/default/CLAUDE.md.hbs +16 -0
- package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
- package/skills/buildkite-agent-runtime/SKILL.md +44 -11
- package/skills/buildkite-api/SKILL.md +31 -8
- package/skills/buildkite-cli/SKILL.md +27 -9
- package/skills/buildkite-migration/SKILL.md +22 -9
- package/skills/buildkite-pipelines/SKILL.md +26 -9
- package/skills/buildkite-secure-delivery/SKILL.md +23 -9
- package/skills/buildkite-test-engine/SKILL.md +25 -8
- package/skills/docx/SKILL.md +1 -1
- package/skills/file-bug/SKILL.md +34 -6
- package/skills/humanizer/SKILL.md +15 -0
- package/skills/humanizer-calibrate/SKILL.md +7 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/pdf/SKILL.md +1 -1
- package/skills/pptx/SKILL.md +1 -1
- package/skills/skill-creator/SKILL.md +21 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/skills/switchroom-cli/SKILL.md +63 -64
- package/skills/switchroom-health/SKILL.md +23 -10
- package/skills/switchroom-install/SKILL.md +3 -3
- package/skills/switchroom-manage/SKILL.md +26 -19
- package/skills/switchroom-runtime/SKILL.md +67 -15
- package/skills/switchroom-status/SKILL.md +26 -1
- package/skills/telegram-test-harness/SKILL.md +3 -0
- package/skills/webapp-testing/SKILL.md +31 -1
- package/skills/xlsx/SKILL.md +1 -1
- package/telegram-plugin/admin-commands/index.ts +7 -5
- package/telegram-plugin/dist/gateway/gateway.js +13042 -12844
- package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
- package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
- package/telegram-plugin/gateway/auth-command.ts +794 -0
- package/telegram-plugin/gateway/auth-line.ts +123 -0
- package/telegram-plugin/gateway/boot-card.ts +22 -36
- package/telegram-plugin/gateway/boot-probes.ts +3 -3
- package/telegram-plugin/gateway/gateway.ts +313 -798
- package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
- package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
- package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
- package/telegram-plugin/permission-title.ts +56 -0
- package/telegram-plugin/quota-check.ts +19 -41
- package/telegram-plugin/scripts/build.mjs +0 -1
- package/telegram-plugin/shared/bot-runtime.ts +5 -4
- package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
- package/telegram-plugin/tests/boot-probes.test.ts +11 -4
- package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
- package/telegram-plugin/tests/permission-title.test.ts +31 -0
- package/telegram-plugin/tests/quota-check.test.ts +5 -35
- package/telegram-plugin/uat/SETUP.md +31 -1
- package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
- package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
- package/telegram-plugin/uat/runners/report.ts +150 -0
- package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
- package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
- package/telegram-plugin/uat/runners/scorer.ts +106 -0
- package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
- package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
- package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +7 -1
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +7 -1
- package/telegram-plugin/auth-dashboard.ts +0 -1104
- package/telegram-plugin/auth-slot-parser.ts +0 -497
- package/telegram-plugin/dist/foreman/foreman.js +0 -31358
- package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
- package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
- package/telegram-plugin/foreman/foreman.ts +0 -1165
- package/telegram-plugin/foreman/setup-flow.ts +0 -345
- package/telegram-plugin/foreman/setup-state.ts +0 -239
- package/telegram-plugin/foreman/state.ts +0 -203
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
- package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
- package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
- package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
- package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
- package/telegram-plugin/tests/foreman-state.test.ts +0 -164
- package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
- package/telegram-plugin/tests/setup-flow.test.ts +0 -510
- package/telegram-plugin/tests/setup-state.test.ts +0 -146
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-card auth row formatter (RFC H §7.3).
|
|
3
|
+
*
|
|
4
|
+
* The old auth-dashboard exported `formatAccountQuotaLine` + an
|
|
5
|
+
* `AccountSummary` shape that the boot card consumed for its
|
|
6
|
+
* "Accounts (N)" section. Both source-of-truth and shape moved to
|
|
7
|
+
* the auth-broker's `list-state` response. This module reformats that
|
|
8
|
+
* response into the same one-line-per-account block the boot card
|
|
9
|
+
* used to render — visual output unchanged, data source is now the
|
|
10
|
+
* broker.
|
|
11
|
+
*
|
|
12
|
+
* Inputs: a `list-state` data shape (see
|
|
13
|
+
* `src/auth/broker/protocol.ts` → `ListStateDataSchema`) plus the
|
|
14
|
+
* caller agent's name.
|
|
15
|
+
*
|
|
16
|
+
* Output: an array of HTML-safe lines. Empty array when there's
|
|
17
|
+
* nothing to show — preserves the boot-card's silent-when-healthy
|
|
18
|
+
* default.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ListStateData, AccountState } from '../../src/auth/broker/client.js'
|
|
22
|
+
|
|
23
|
+
export type { ListStateData, AccountState }
|
|
24
|
+
|
|
25
|
+
// Local HTML-escape (mirrors the helper formerly co-located in
|
|
26
|
+
// auth-dashboard.ts so we keep the same escaping discipline without
|
|
27
|
+
// pulling in a heavier util).
|
|
28
|
+
function escapeHtml(s: string): string {
|
|
29
|
+
return s
|
|
30
|
+
.replace(/&/g, '&')
|
|
31
|
+
.replace(/</g, '<')
|
|
32
|
+
.replace(/>/g, '>')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Format a duration in ms as a short relative string ("1h 22m", "12s"). */
|
|
36
|
+
function formatRelativeMs(ms: number): string {
|
|
37
|
+
if (ms <= 0) return '0s'
|
|
38
|
+
const totalSec = Math.floor(ms / 1000)
|
|
39
|
+
const days = Math.floor(totalSec / 86400)
|
|
40
|
+
const hours = Math.floor((totalSec % 86400) / 3600)
|
|
41
|
+
const mins = Math.floor((totalSec % 3600) / 60)
|
|
42
|
+
const secs = totalSec % 60
|
|
43
|
+
if (days > 0) return `${days}d ${hours}h`
|
|
44
|
+
if (hours > 0) return `${hours}h ${mins}m`
|
|
45
|
+
if (mins > 0) return `${mins}m ${secs}s`
|
|
46
|
+
return `${secs}s`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render the per-account quota inline for one account row. Returns
|
|
51
|
+
* null when there's nothing quota-shaped to say (account is healthy
|
|
52
|
+
* and we have no reset countdown to surface).
|
|
53
|
+
*/
|
|
54
|
+
export function formatAuthQuotaLine(acc: AccountState, now: number = Date.now()): string | null {
|
|
55
|
+
if (acc.exhausted) {
|
|
56
|
+
const until = acc.exhausted_until
|
|
57
|
+
if (until != null && until > now) {
|
|
58
|
+
return `<i>exhausted · resets in ${formatRelativeMs(until - now)}</i>`
|
|
59
|
+
}
|
|
60
|
+
return `<i>exhausted</i>`
|
|
61
|
+
}
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Boot-card auth-row block.
|
|
67
|
+
*
|
|
68
|
+
* Strategy:
|
|
69
|
+
* 1. Determine *which* account is active for `agentName` (per-agent
|
|
70
|
+
* override wins over fleet-active).
|
|
71
|
+
* 2. Emit one row for that account marked with `▶` plus a
|
|
72
|
+
* best-effort quota suffix.
|
|
73
|
+
* 3. Emit one row per other account in `fallback_order` marked with
|
|
74
|
+
* `↳` so the operator sees the rollover plan at a glance.
|
|
75
|
+
*
|
|
76
|
+
* Returns an empty array when `state` is empty (no accounts) — the
|
|
77
|
+
* boot card's silent-when-healthy contract.
|
|
78
|
+
*/
|
|
79
|
+
export function renderAuthLine(
|
|
80
|
+
state: ListStateData,
|
|
81
|
+
agentName: string,
|
|
82
|
+
now: number = Date.now(),
|
|
83
|
+
): string[] {
|
|
84
|
+
if (!state || state.accounts.length === 0) return []
|
|
85
|
+
|
|
86
|
+
const agentEntry = state.agents.find((a) => a.name === agentName)
|
|
87
|
+
const activeLabel = agentEntry?.override ?? agentEntry?.account ?? state.active
|
|
88
|
+
|
|
89
|
+
// Stable display order: active first, then `fallback_order` minus
|
|
90
|
+
// the active label, then any remaining accounts (defensive — should
|
|
91
|
+
// be empty in steady state) in account-list order.
|
|
92
|
+
const seen = new Set<string>()
|
|
93
|
+
const order: string[] = []
|
|
94
|
+
if (activeLabel) {
|
|
95
|
+
order.push(activeLabel)
|
|
96
|
+
seen.add(activeLabel)
|
|
97
|
+
}
|
|
98
|
+
for (const label of state.fallback_order) {
|
|
99
|
+
if (!seen.has(label)) {
|
|
100
|
+
order.push(label)
|
|
101
|
+
seen.add(label)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const acc of state.accounts) {
|
|
105
|
+
if (!seen.has(acc.label)) {
|
|
106
|
+
order.push(acc.label)
|
|
107
|
+
seen.add(acc.label)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const byLabel = new Map(state.accounts.map((a) => [a.label, a]))
|
|
112
|
+
const rows: string[] = []
|
|
113
|
+
rows.push(`<b>Accounts (${state.accounts.length})</b>`)
|
|
114
|
+
for (const label of order) {
|
|
115
|
+
const acc = byLabel.get(label)
|
|
116
|
+
if (!acc) continue
|
|
117
|
+
const marker = label === activeLabel ? '▶' : '↳'
|
|
118
|
+
const labelHtml = `<code>${escapeHtml(acc.label)}</code>`
|
|
119
|
+
const quotaLine = formatAuthQuotaLine(acc, now)
|
|
120
|
+
rows.push(quotaLine ? `${marker} ${labelHtml} ${quotaLine}` : `${marker} ${labelHtml}`)
|
|
121
|
+
}
|
|
122
|
+
return rows
|
|
123
|
+
}
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import type { ProbeResult, GatewayRuntimeInfo } from './boot-probes.js'
|
|
36
|
-
import type {
|
|
37
|
-
import {
|
|
36
|
+
import type { ListStateData } from './auth-line.js'
|
|
37
|
+
import { renderAuthLine } from './auth-line.js'
|
|
38
38
|
import {
|
|
39
39
|
probeAccount,
|
|
40
40
|
probeAgentProcess,
|
|
@@ -274,9 +274,12 @@ export interface RenderBootCardOpts {
|
|
|
274
274
|
* silent-when-healthy contract for callers that don't pass account
|
|
275
275
|
* data (tests, harnesses, gateways without the auth model).
|
|
276
276
|
*
|
|
277
|
-
*
|
|
277
|
+
* Post-RFC H (auth-broker rewire): this carries the broker's
|
|
278
|
+
* `list-state` shape — `renderAuthLine` consumes it to emit the
|
|
279
|
+
* same one-line-per-account rows as before. Callers that pass
|
|
280
|
+
* `null`/`undefined` get no section. Closes #708.
|
|
278
281
|
*/
|
|
279
|
-
accounts?:
|
|
282
|
+
accounts?: ListStateData | null
|
|
280
283
|
/** Probe keys for which the prior boot saw degraded/fail and this boot
|
|
281
284
|
* sees ok. Rendered as a small ✅ line above the degraded section so
|
|
282
285
|
* the user gets positive-feedback that a known issue is gone. */
|
|
@@ -380,11 +383,13 @@ export function renderBootCard(opts: RenderBootCardOpts): string {
|
|
|
380
383
|
}
|
|
381
384
|
}
|
|
382
385
|
|
|
383
|
-
// Per-account
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
const accountRows =
|
|
386
|
+
// Per-account auth section (issue #708, RFC H rewire) — one line
|
|
387
|
+
// per known account with the active account marked. Renders
|
|
388
|
+
// alongside the ack line so users see headroom without running
|
|
389
|
+
// /auth or /usage. Source of truth: auth-broker list-state.
|
|
390
|
+
const accountRows = opts.accounts
|
|
391
|
+
? renderAuthLine(opts.accounts, agentName, (opts.now ?? new Date()).getTime())
|
|
392
|
+
: []
|
|
388
393
|
|
|
389
394
|
const sections: string[] = [ack]
|
|
390
395
|
if (degradedRows.length > 0) sections.push('', ...degradedRows)
|
|
@@ -394,31 +399,12 @@ export function renderBootCard(opts: RenderBootCardOpts): string {
|
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
/**
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
* Reuses the dashboard's `formatAccountQuotaLine` so the two surfaces
|
|
402
|
-
* speak with one voice.
|
|
402
|
+
* Re-export the broker-fed auth-row renderer under its historical
|
|
403
|
+
* name so direct callers (tests, harnesses) keep working without
|
|
404
|
+
* importing two modules. New code should import `renderAuthLine`
|
|
405
|
+
* from `./auth-line.js` directly.
|
|
403
406
|
*/
|
|
404
|
-
export
|
|
405
|
-
accounts: ReadonlyArray<AccountSummary> | undefined,
|
|
406
|
-
now: Date,
|
|
407
|
-
): string[] {
|
|
408
|
-
if (!accounts || accounts.length === 0) return []
|
|
409
|
-
const rows: string[] = []
|
|
410
|
-
rows.push(`<b>Accounts (${accounts.length})</b>`)
|
|
411
|
-
const nowMs = now.getTime()
|
|
412
|
-
for (const a of accounts) {
|
|
413
|
-
const marker = a.activeForThisAgent ? '▶' : '↳'
|
|
414
|
-
const labelHtml = `<code>${escapeHtml(a.label)}</code>`
|
|
415
|
-
// formatAccountQuotaLine returns HTML (with <i> tags) so we don't
|
|
416
|
-
// re-escape — pass it through verbatim.
|
|
417
|
-
const quotaLine = formatAccountQuotaLine(a, nowMs)
|
|
418
|
-
rows.push(quotaLine ? `${marker} ${labelHtml} ${quotaLine}` : `${marker} ${labelHtml}`)
|
|
419
|
-
}
|
|
420
|
-
return rows
|
|
421
|
-
}
|
|
407
|
+
export { renderAuthLine as renderAccountRows } from './auth-line.js'
|
|
422
408
|
|
|
423
409
|
// ─── Probe orchestration ─────────────────────────────────────────────────────
|
|
424
410
|
|
|
@@ -480,9 +466,9 @@ export interface RunProbesOpts {
|
|
|
480
466
|
* during the post-settle re-render so the first paint stays fast.
|
|
481
467
|
*/
|
|
482
468
|
loadAccounts?: () =>
|
|
483
|
-
|
|
|
469
|
+
| ListStateData
|
|
484
470
|
| null
|
|
485
|
-
| Promise<
|
|
471
|
+
| Promise<ListStateData | null>
|
|
486
472
|
/** When true, resolve the agent PID via cgroup walk instead of MainPID
|
|
487
473
|
* (which is the tmux server pid under tmux supervisor). */
|
|
488
474
|
tmuxSupervisor?: boolean
|
|
@@ -604,7 +590,7 @@ export async function startBootCard(
|
|
|
604
590
|
// Per-account rows (issue #708). Loaded best-effort
|
|
605
591
|
// alongside probes; failures are swallowed so the card still
|
|
606
592
|
// renders correctly with no accounts section.
|
|
607
|
-
let accountRows:
|
|
593
|
+
let accountRows: ListStateData | null = null
|
|
608
594
|
if (opts.loadAccounts) {
|
|
609
595
|
try {
|
|
610
596
|
accountRows = await opts.loadAccounts()
|
|
@@ -861,7 +861,7 @@ export async function probeQuota(
|
|
|
861
861
|
status: 'degraded',
|
|
862
862
|
label: 'Quota',
|
|
863
863
|
detail: 'no OAuth token',
|
|
864
|
-
nextStep: 'No OAuth token on disk —
|
|
864
|
+
nextStep: 'No OAuth token on disk — register a fleet account: `switchroom auth add <label> --from-oauth` then `switchroom auth use <label>` (RFC H)',
|
|
865
865
|
}
|
|
866
866
|
}
|
|
867
867
|
|
|
@@ -880,8 +880,8 @@ export async function probeQuota(
|
|
|
880
880
|
label: 'Quota',
|
|
881
881
|
detail: probe.reason,
|
|
882
882
|
nextStep: isAuth
|
|
883
|
-
? 'Auth rejected by Anthropic —
|
|
884
|
-
: 'Anthropic quota probe failed — re-check after a minute;
|
|
883
|
+
? 'Auth rejected by Anthropic — broker auto-refreshes; if persistent, replace the account: `switchroom auth add <label> --from-oauth --replace`'
|
|
884
|
+
: 'Anthropic quota probe failed — re-check after a minute; broker auto-rotates per `auth.fallback_order`',
|
|
885
885
|
}
|
|
886
886
|
}
|
|
887
887
|
|