switchroom 0.11.0 → 0.11.1
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 +2 -2
- package/dist/auth-broker/index.js +125 -3
- package/dist/cli/drive-write-pretool.mjs +20 -2
- package/dist/cli/switchroom.js +32 -7
- package/dist/host-control/main.js +2 -2
- package/dist/vault/approvals/kernel-server.js +2 -2
- package/dist/vault/broker/server.js +2 -2
- package/package.json +1 -1
- package/telegram-plugin/auto-fallback-fleet.ts +4 -4
- package/telegram-plugin/dist/gateway/gateway.js +242 -226
- package/telegram-plugin/gateway/auth-broker-client.ts +2 -0
- package/telegram-plugin/gateway/auth-command.ts +10 -0
- package/telegram-plugin/gateway/gateway.ts +51 -24
- package/telegram-plugin/gateway/hostd-dispatch.ts +10 -2
|
@@ -220,6 +220,16 @@ export interface AuthBrokerClient {
|
|
|
220
220
|
agent: string,
|
|
221
221
|
account: string | null,
|
|
222
222
|
): Promise<{ agent: string; account: string | null }>
|
|
223
|
+
/**
|
|
224
|
+
* Live Anthropic quota probe via the broker (#1336). The broker
|
|
225
|
+
* uses its stored accessTokens to hit `/v1/messages` server-side
|
|
226
|
+
* and returns parsed rate-limit headers. Tokens never reach the
|
|
227
|
+
* caller. Per-label results are returned in input order.
|
|
228
|
+
*/
|
|
229
|
+
probeQuota(
|
|
230
|
+
accounts: readonly string[],
|
|
231
|
+
timeoutMs?: number,
|
|
232
|
+
): Promise<{ results: Array<{ label: string; result: import('../quota-check.js').QuotaResult }> }>
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
export interface AuthCommandContext {
|
|
@@ -128,7 +128,6 @@ import {
|
|
|
128
128
|
resolveModelUnavailableFromOperatorEvent,
|
|
129
129
|
} from '../model-unavailable.js'
|
|
130
130
|
import { runFleetAutoFallback } from '../auto-fallback-fleet.js'
|
|
131
|
-
import { fetchAccountQuota } from '../quota-check.js'
|
|
132
131
|
import { startRestartWatchdog } from './restart-watchdog.js'
|
|
133
132
|
import { validateStringArray } from './access-validator.js'
|
|
134
133
|
|
|
@@ -8908,12 +8907,18 @@ async function doFireFleetAutoFallback(triggerAgent: string): Promise<boolean> {
|
|
|
8908
8907
|
return false
|
|
8909
8908
|
}
|
|
8910
8909
|
const state = await client.listState()
|
|
8911
|
-
// Probe live quota
|
|
8912
|
-
//
|
|
8913
|
-
//
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8910
|
+
// Probe live quota via the broker (#1336). Pre-fix this read
|
|
8911
|
+
// credentials.json off the agent HOME, which is never populated
|
|
8912
|
+
// post-RFC-H — every account looked "no credentials" and the
|
|
8913
|
+
// fallback logic rolled blindly. Broker-routed probes use the
|
|
8914
|
+
// canonical stored tokens.
|
|
8915
|
+
const probeResp = state.accounts.length > 0
|
|
8916
|
+
? await client.probeQuota(state.accounts.map((a) => a.label)).catch(() => ({ results: [] }))
|
|
8917
|
+
: { results: [] }
|
|
8918
|
+
const quotas = state.accounts.map((a) => {
|
|
8919
|
+
const hit = probeResp.results.find((r) => r.label === a.label)
|
|
8920
|
+
return hit?.result ?? { ok: false as const, reason: 'broker returned no result for account' }
|
|
8921
|
+
})
|
|
8917
8922
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? 'UTC'
|
|
8918
8923
|
const outcome = await runFleetAutoFallback({
|
|
8919
8924
|
state,
|
|
@@ -9098,17 +9103,31 @@ bot.command("auth", async ctx => {
|
|
|
9098
9103
|
isAdmin,
|
|
9099
9104
|
client,
|
|
9100
9105
|
chatId,
|
|
9101
|
-
// Format 2 enricher —
|
|
9102
|
-
//
|
|
9103
|
-
//
|
|
9104
|
-
//
|
|
9105
|
-
//
|
|
9106
|
-
//
|
|
9107
|
-
//
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9106
|
+
// Format 2 enricher — live quota probe via the broker (#1336).
|
|
9107
|
+
// Pre-broker this read `~/.switchroom/accounts/<label>/credentials.json`
|
|
9108
|
+
// off the agent's HOME, which post-RFC-H is never populated (broker
|
|
9109
|
+
// writes only the per-agent .claude/.credentials.json mirror) — so
|
|
9110
|
+
// every account showed "no credentials.json or accessToken" in
|
|
9111
|
+
// /auth show. The broker is the source of truth for tokens and now
|
|
9112
|
+
// does the Anthropic probe server-side via `probe-quota`. Tokens
|
|
9113
|
+
// never leave the broker container.
|
|
9114
|
+
liveQuotas: async (accounts) => {
|
|
9115
|
+
try {
|
|
9116
|
+
const { results } = await client.probeQuota(accounts.map((a) => a.label))
|
|
9117
|
+
// Preserve input order (broker also preserves it, but be defensive).
|
|
9118
|
+
return accounts.map((a) => {
|
|
9119
|
+
const hit = results.find((r) => r.label === a.label)
|
|
9120
|
+
if (!hit) return { ok: false as const, reason: "broker returned no result for account" }
|
|
9121
|
+
return hit.result
|
|
9122
|
+
})
|
|
9123
|
+
} catch (err) {
|
|
9124
|
+
// Surface a uniform per-account failure so the dashboard renders
|
|
9125
|
+
// gracefully (label badge stays UNKNOWN) instead of falling back
|
|
9126
|
+
// to the legacy table.
|
|
9127
|
+
const reason = `broker probe-quota failed: ${(err as Error)?.message ?? String(err)}`
|
|
9128
|
+
return accounts.map(() => ({ ok: false as const, reason }))
|
|
9129
|
+
}
|
|
9130
|
+
},
|
|
9112
9131
|
tz: process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ,
|
|
9113
9132
|
})
|
|
9114
9133
|
// Translate the handler's optional keyboard shape into grammy's
|
|
@@ -10855,9 +10874,14 @@ async function handleAuthDashboardCallback(ctx: Context): Promise<void> {
|
|
|
10855
10874
|
return
|
|
10856
10875
|
}
|
|
10857
10876
|
const state = await client.listState()
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10877
|
+
// Broker-routed probe (#1336) — see gateway.ts:8910 for diagnosis.
|
|
10878
|
+
const probeResp = state.accounts.length > 0
|
|
10879
|
+
? await client.probeQuota(state.accounts.map((a) => a.label)).catch(() => ({ results: [] }))
|
|
10880
|
+
: { results: [] }
|
|
10881
|
+
const quotas = state.accounts.map((a) => {
|
|
10882
|
+
const hit = probeResp.results.find((r) => r.label === a.label)
|
|
10883
|
+
return hit?.result ?? { ok: false as const, reason: 'broker returned no result for account' }
|
|
10884
|
+
})
|
|
10861
10885
|
const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? 'UTC'
|
|
10862
10886
|
const { renderAuthSnapshotFormat2, buildSnapshotsFromState, buildSnapshotKeyboard } = await import(
|
|
10863
10887
|
'../auth-snapshot-format.js'
|
|
@@ -11329,9 +11353,12 @@ bot.command('usage', async ctx => {
|
|
|
11329
11353
|
if (client) {
|
|
11330
11354
|
const state = await client.listState()
|
|
11331
11355
|
if (state.accounts.length > 0) {
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
)
|
|
11356
|
+
// Broker-routed probe (#1336) — see gateway.ts:8910 for diagnosis.
|
|
11357
|
+
const probeResp = await client.probeQuota(state.accounts.map((a) => a.label)).catch(() => ({ results: [] }))
|
|
11358
|
+
const quotas = state.accounts.map((a) => {
|
|
11359
|
+
const hit = probeResp.results.find((r) => r.label === a.label)
|
|
11360
|
+
return hit?.result ?? { ok: false as const, reason: 'broker returned no result for account' }
|
|
11361
|
+
})
|
|
11335
11362
|
const { renderAuthSnapshotFormat2, buildSnapshotsFromState } = await import(
|
|
11336
11363
|
'../auth-snapshot-format.js'
|
|
11337
11364
|
)
|
|
@@ -30,15 +30,23 @@ let _hostdEnabled: boolean | undefined;
|
|
|
30
30
|
* Cached for the gateway's lifetime — config doesn't change without a
|
|
31
31
|
* restart, and the file-read isn't free.
|
|
32
32
|
*
|
|
33
|
+
* Default-on since the RFC C Phase 2 default-flip: the schema gives
|
|
34
|
+
* `host_control.enabled` a `.default(true)` and the block itself a
|
|
35
|
+
* `.default({})`, so any config parsed through Zod will have the
|
|
36
|
+
* field populated. We use `!== false` semantics here so this helper
|
|
37
|
+
* also matches the default-on view on paths that bypass the parser
|
|
38
|
+
* (tests with partial mocks, code that constructs configs directly).
|
|
39
|
+
*
|
|
33
40
|
* Best-effort: if the config can't be loaded (gateway running in a
|
|
34
41
|
* dir where loadConfig fails), returns false so the dispatch helper
|
|
35
|
-
* falls through to the legacy spawn path
|
|
42
|
+
* falls through to the legacy spawn path — better to fail closed
|
|
43
|
+
* than to attempt a hostd RPC against a daemon that might not exist.
|
|
36
44
|
*/
|
|
37
45
|
export function isHostdEnabled(): boolean {
|
|
38
46
|
if (_hostdEnabled !== undefined) return _hostdEnabled;
|
|
39
47
|
try {
|
|
40
48
|
const cfg = loadSwitchroomConfig();
|
|
41
|
-
_hostdEnabled = cfg.host_control?.enabled
|
|
49
|
+
_hostdEnabled = cfg.host_control?.enabled !== false;
|
|
42
50
|
} catch {
|
|
43
51
|
_hostdEnabled = false;
|
|
44
52
|
}
|