switchroom 0.10.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/README.md +5 -4
- package/dist/agent-scheduler/index.js +2 -2
- package/dist/auth-broker/index.js +125 -3
- package/dist/cli/drive-write-pretool.mjs +5436 -0
- package/dist/cli/switchroom.js +231 -29
- 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/admin-commands/dispatch.test.ts +1 -1
- package/telegram-plugin/admin-commands/index.ts +2 -0
- package/telegram-plugin/auth-snapshot-format.ts +612 -0
- package/telegram-plugin/auto-fallback-fleet.ts +215 -0
- package/telegram-plugin/auto-fallback.ts +28 -301
- package/telegram-plugin/dist/gateway/gateway.js +4314 -2143
- package/telegram-plugin/fleet-fallback-gate.ts +105 -0
- package/telegram-plugin/gateway/approval-callback.test.ts +104 -0
- package/telegram-plugin/gateway/approval-callback.ts +31 -3
- package/telegram-plugin/gateway/auth-broker-client.ts +2 -0
- package/telegram-plugin/gateway/auth-command.ts +131 -10
- package/telegram-plugin/gateway/auth-status-adapter.ts +101 -0
- package/telegram-plugin/gateway/boot-card.ts +1 -1
- package/telegram-plugin/gateway/boot-probes.ts +6 -9
- package/telegram-plugin/gateway/diff-preview-card.test.ts +192 -0
- package/telegram-plugin/gateway/diff-preview-card.ts +170 -0
- package/telegram-plugin/gateway/drive-write-approval.test.ts +312 -0
- package/telegram-plugin/gateway/drive-write-approval.ts +243 -0
- package/telegram-plugin/gateway/folder-picker-handler.test.ts +314 -0
- package/telegram-plugin/gateway/folder-picker-handler.ts +348 -0
- package/telegram-plugin/gateway/gateway.ts +903 -173
- package/telegram-plugin/gateway/hostd-dispatch.ts +137 -2
- package/telegram-plugin/gateway/ipc-protocol.ts +83 -2
- package/telegram-plugin/gateway/ipc-server.ts +69 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +103 -12
- package/telegram-plugin/model-unavailable.ts +28 -12
- package/telegram-plugin/silence-poke.ts +153 -1
- package/telegram-plugin/tests/auth-command-format2.test.ts +156 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +429 -0
- package/telegram-plugin/tests/auth-status-adapter.test.ts +129 -0
- package/telegram-plugin/tests/auto-fallback-fleet.test.ts +211 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +60 -358
- package/telegram-plugin/tests/boot-probes.test.ts +16 -18
- package/telegram-plugin/tests/fleet-fallback-gate.test.ts +197 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +30 -5
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +212 -2
- package/telegram-plugin/tests/silence-poke.test.ts +237 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +112 -0
- package/telegram-plugin/turn-flush-safety.ts +55 -1
- package/telegram-plugin/uat/SETUP.md +16 -12
- package/telegram-plugin/auto-fallback-dispatcher.ts +0 -68
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +0 -183
- package/telegram-plugin/tests/hostd-dispatch.test.ts +0 -129
package/dist/cli/switchroom.js
CHANGED
|
@@ -13850,7 +13850,7 @@ var init_schema = __esm(() => {
|
|
|
13850
13850
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
13851
13851
|
});
|
|
13852
13852
|
HostControlConfigSchema = exports_external.object({
|
|
13853
|
-
enabled: exports_external.boolean().
|
|
13853
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
13854
13854
|
});
|
|
13855
13855
|
SwitchroomConfigSchema = exports_external.object({
|
|
13856
13856
|
switchroom: exports_external.object({
|
|
@@ -13876,7 +13876,7 @@ var init_schema = __esm(() => {
|
|
|
13876
13876
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key \u2014 use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
13877
13877
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration \u2014 " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
13878
13878
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
13879
|
-
host_control: HostControlConfigSchema.
|
|
13879
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
13880
13880
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
13881
13881
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
13882
13882
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
@@ -22669,7 +22669,7 @@ function generateCompose(opts) {
|
|
|
22669
22669
|
const buildContext = opts.buildContext;
|
|
22670
22670
|
const homePrefix = opts.homeDir ?? "${HOME}";
|
|
22671
22671
|
const containerNamePrefix = opts.containerNamePrefix ?? "switchroom";
|
|
22672
|
-
const hostControlEnabled = config.host_control?.enabled
|
|
22672
|
+
const hostControlEnabled = config.host_control?.enabled !== false;
|
|
22673
22673
|
const hostHomeForChecks = opts.homeDir ?? process.env.HOME ?? "";
|
|
22674
22674
|
const switchroomConfigPath = opts.switchroomConfigPath;
|
|
22675
22675
|
const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
@@ -22860,6 +22860,7 @@ function generateCompose(opts) {
|
|
|
22860
22860
|
}
|
|
22861
22861
|
for (const c of authConsumers) {
|
|
22862
22862
|
lines.push(` auth-broker-${c.name}-sock:`);
|
|
22863
|
+
lines.push(` name: auth-broker-${c.name}-sock`);
|
|
22863
22864
|
}
|
|
22864
22865
|
lines.push("");
|
|
22865
22866
|
return lines.join(`
|
|
@@ -22951,6 +22952,9 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
|
|
|
22951
22952
|
if (existsSync13(`${hostHomeForChecks}/.switchroom/vault-audit.log`)) {
|
|
22952
22953
|
lines.push(` - ${homePrefix}/.switchroom/vault-audit.log:/state/agent/home/.switchroom/vault-audit.log:ro`);
|
|
22953
22954
|
}
|
|
22955
|
+
if (existsSync13(`${hostHomeForChecks}/.switchroom/host-control-audit.log`)) {
|
|
22956
|
+
lines.push(` - ${homePrefix}/.switchroom/host-control-audit.log:/state/agent/home/.switchroom/host-control-audit.log:ro`);
|
|
22957
|
+
}
|
|
22954
22958
|
if (hostControlEnabled && existsSync13(`${hostHomeForChecks}/.switchroom/hostd/${a.name}`)) {
|
|
22955
22959
|
lines.push(` - ${homePrefix}/.switchroom/hostd/${a.name}:/run/switchroom/hostd/${a.name}`);
|
|
22956
22960
|
}
|
|
@@ -25660,17 +25664,22 @@ function checkHindsightConsumer(config, opts) {
|
|
|
25660
25664
|
` + "then run `switchroom apply` to bind the per-consumer socket."
|
|
25661
25665
|
};
|
|
25662
25666
|
}
|
|
25663
|
-
const
|
|
25664
|
-
const
|
|
25665
|
-
|
|
25666
|
-
const altVolPath = join17(home2, ".local", "share", "docker", "volumes", "switchroom_auth-broker-hindsight-sock", "_data", "sock");
|
|
25667
|
-
const present = socketProbe(dockerVolPath) || socketProbe(altVolPath);
|
|
25668
|
-
if (!present) {
|
|
25667
|
+
const probe2 = opts?.socketProbe ?? probeAuthBrokerSocket;
|
|
25668
|
+
const state = probe2(entry.name);
|
|
25669
|
+
if (state === "unreachable") {
|
|
25669
25670
|
return {
|
|
25670
25671
|
name: "hindsight consumer",
|
|
25671
25672
|
status: "warn",
|
|
25672
|
-
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `
|
|
25673
|
-
fix: "
|
|
25673
|
+
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `couldn't query auth-broker container (not running / docker unavailable)`,
|
|
25674
|
+
fix: "Check `auth-broker: service health` row above; if the broker is " + "down, `switchroom apply` will bring it back and bind the socket."
|
|
25675
|
+
};
|
|
25676
|
+
}
|
|
25677
|
+
if (state === "missing") {
|
|
25678
|
+
return {
|
|
25679
|
+
name: "hindsight consumer",
|
|
25680
|
+
status: "warn",
|
|
25681
|
+
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0}); ` + `auth-broker is running but socket not bound at /run/switchroom/auth-broker/${entry.name}/sock`,
|
|
25682
|
+
fix: "Run `switchroom apply` to refresh compose and rebind per-consumer sockets."
|
|
25674
25683
|
};
|
|
25675
25684
|
}
|
|
25676
25685
|
return {
|
|
@@ -25679,6 +25688,17 @@ function checkHindsightConsumer(config, opts) {
|
|
|
25679
25688
|
detail: `auth.consumers[hindsight] -> ${entry.account} (uid ${entry.uid ?? 0})`
|
|
25680
25689
|
};
|
|
25681
25690
|
}
|
|
25691
|
+
function probeAuthBrokerSocket(consumerName) {
|
|
25692
|
+
const containerPath = `/run/switchroom/auth-broker/${consumerName}/sock`;
|
|
25693
|
+
const r = spawnSync3("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
|
|
25694
|
+
if (r.error || r.status === null)
|
|
25695
|
+
return "unreachable";
|
|
25696
|
+
if (r.status === 0)
|
|
25697
|
+
return "present";
|
|
25698
|
+
if (r.status >= 125)
|
|
25699
|
+
return "unreachable";
|
|
25700
|
+
return "missing";
|
|
25701
|
+
}
|
|
25682
25702
|
async function checkHindsight(config) {
|
|
25683
25703
|
const memoryBackend = config.memory?.backend;
|
|
25684
25704
|
if (memoryBackend !== "hindsight") {
|
|
@@ -27041,7 +27061,7 @@ function decodeResponse2(line) {
|
|
|
27041
27061
|
}
|
|
27042
27062
|
return ResponseSchema2.parse(parsed);
|
|
27043
27063
|
}
|
|
27044
|
-
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
27064
|
+
var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ProbeQuotaRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
|
|
27045
27065
|
var init_protocol2 = __esm(() => {
|
|
27046
27066
|
init_zod();
|
|
27047
27067
|
MAX_FRAME_BYTES2 = 64 * 1024;
|
|
@@ -27130,6 +27150,13 @@ var init_protocol2 = __esm(() => {
|
|
|
27130
27150
|
op: exports_external.literal("list-google-accounts"),
|
|
27131
27151
|
id: exports_external.string().min(1)
|
|
27132
27152
|
});
|
|
27153
|
+
ProbeQuotaRequestSchema = exports_external.object({
|
|
27154
|
+
v: exports_external.literal(PROTOCOL_VERSION),
|
|
27155
|
+
op: exports_external.literal("probe-quota"),
|
|
27156
|
+
id: exports_external.string().min(1),
|
|
27157
|
+
accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
|
|
27158
|
+
timeoutMs: exports_external.number().int().positive().max(60000).optional()
|
|
27159
|
+
});
|
|
27133
27160
|
RequestSchema2 = exports_external.discriminatedUnion("op", [
|
|
27134
27161
|
GetCredentialsRequestSchema,
|
|
27135
27162
|
ListStateRequestSchema,
|
|
@@ -27139,7 +27166,8 @@ var init_protocol2 = __esm(() => {
|
|
|
27139
27166
|
AddAccountRequestSchema,
|
|
27140
27167
|
RmAccountRequestSchema,
|
|
27141
27168
|
SetOverrideRequestSchema,
|
|
27142
|
-
ListGoogleAccountsRequestSchema
|
|
27169
|
+
ListGoogleAccountsRequestSchema,
|
|
27170
|
+
ProbeQuotaRequestSchema
|
|
27143
27171
|
]);
|
|
27144
27172
|
GetCredentialsDataSchema = exports_external.object({
|
|
27145
27173
|
account: exports_external.string(),
|
|
@@ -27308,6 +27336,16 @@ class AuthBrokerClient {
|
|
|
27308
27336
|
});
|
|
27309
27337
|
return data;
|
|
27310
27338
|
}
|
|
27339
|
+
async probeQuota(accounts, timeoutMs) {
|
|
27340
|
+
const data = await this.send({
|
|
27341
|
+
v: PROTOCOL_VERSION,
|
|
27342
|
+
id: randomUUID2(),
|
|
27343
|
+
op: "probe-quota",
|
|
27344
|
+
accounts: [...accounts],
|
|
27345
|
+
...timeoutMs !== undefined ? { timeoutMs } : {}
|
|
27346
|
+
});
|
|
27347
|
+
return data;
|
|
27348
|
+
}
|
|
27311
27349
|
async setActive(account) {
|
|
27312
27350
|
const data = await this.send({
|
|
27313
27351
|
v: PROTOCOL_VERSION,
|
|
@@ -27911,14 +27949,14 @@ var init_oauth = __esm(() => {
|
|
|
27911
27949
|
|
|
27912
27950
|
// src/drive/grants.ts
|
|
27913
27951
|
function scopeFor(target, action) {
|
|
27914
|
-
const
|
|
27952
|
+
const actionPrefix = action === "read" ? "" : `${action}:`;
|
|
27915
27953
|
switch (target.kind) {
|
|
27916
27954
|
case "all":
|
|
27917
|
-
return `doc:gdrive:${
|
|
27955
|
+
return `doc:gdrive:${actionPrefix}**`;
|
|
27918
27956
|
case "folder":
|
|
27919
|
-
return `doc:gdrive:${
|
|
27957
|
+
return `doc:gdrive:${actionPrefix}folder/${target.folder_id}/**`;
|
|
27920
27958
|
case "doc":
|
|
27921
|
-
return `doc:gdrive:${
|
|
27959
|
+
return `doc:gdrive:${actionPrefix}${target.doc_id}`;
|
|
27922
27960
|
}
|
|
27923
27961
|
}
|
|
27924
27962
|
|
|
@@ -45296,8 +45334,8 @@ var {
|
|
|
45296
45334
|
} = import__.default;
|
|
45297
45335
|
|
|
45298
45336
|
// src/build-info.ts
|
|
45299
|
-
var VERSION = "0.
|
|
45300
|
-
var COMMIT_SHA = "
|
|
45337
|
+
var VERSION = "0.11.1";
|
|
45338
|
+
var COMMIT_SHA = "f5d84dfb";
|
|
45301
45339
|
|
|
45302
45340
|
// src/cli/deprecated.ts
|
|
45303
45341
|
init_source();
|
|
@@ -47207,6 +47245,7 @@ Don't wait for a slash command. Don't ask permission. Memory work is table stake
|
|
|
47207
47245
|
}
|
|
47208
47246
|
var DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin";
|
|
47209
47247
|
var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
47248
|
+
var DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks";
|
|
47210
47249
|
var DOCKER_BIN_PATH = "/opt/switchroom/bin";
|
|
47211
47250
|
var DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml";
|
|
47212
47251
|
function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchroomConfig, userIdOverride, switchroomConfigPath) {
|
|
@@ -47735,6 +47774,16 @@ function buildSettingsHooksBlock(p) {
|
|
|
47735
47774
|
}
|
|
47736
47775
|
]
|
|
47737
47776
|
},
|
|
47777
|
+
{
|
|
47778
|
+
matcher: "^mcp__google-workspace__",
|
|
47779
|
+
hooks: [
|
|
47780
|
+
{
|
|
47781
|
+
type: "command",
|
|
47782
|
+
command: wrap("hook:drive-write-pretool", `node "${join8(DOCKER_BUNDLED_HOOKS_PATH, "drive-write-pretool.mjs")}"`),
|
|
47783
|
+
timeout: 5 * 60 + 30
|
|
47784
|
+
}
|
|
47785
|
+
]
|
|
47786
|
+
},
|
|
47738
47787
|
{
|
|
47739
47788
|
hooks: [
|
|
47740
47789
|
{
|
|
@@ -47757,7 +47806,7 @@ function buildSettingsHooksBlock(p) {
|
|
|
47757
47806
|
]
|
|
47758
47807
|
},
|
|
47759
47808
|
{
|
|
47760
|
-
matcher: ".*",
|
|
47809
|
+
matcher: "^(Edit|MultiEdit|Write|NotebookEdit|Bash|mcp__.*)$",
|
|
47761
47810
|
hooks: [
|
|
47762
47811
|
{
|
|
47763
47812
|
type: "command",
|
|
@@ -53214,6 +53263,10 @@ async function ensureHostMountSources(config) {
|
|
|
53214
53263
|
if (!existsSync23(auditLogPath)) {
|
|
53215
53264
|
writeFileSync13(auditLogPath, "", { mode: 420 });
|
|
53216
53265
|
}
|
|
53266
|
+
const hostdAuditLogPath = join18(home2, ".switchroom", "host-control-audit.log");
|
|
53267
|
+
if (!existsSync23(hostdAuditLogPath)) {
|
|
53268
|
+
writeFileSync13(hostdAuditLogPath, "", { mode: 420 });
|
|
53269
|
+
}
|
|
53217
53270
|
}
|
|
53218
53271
|
function detectComposeV2() {
|
|
53219
53272
|
try {
|
|
@@ -64973,6 +65026,8 @@ var HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000;
|
|
|
64973
65026
|
var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
64974
65027
|
var HINDSIGHT_DEFAULT_UID = 11000;
|
|
64975
65028
|
var HINDSIGHT_IMAGE = "ghcr.io/switchroom/switchroom-hindsight:latest";
|
|
65029
|
+
var HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
65030
|
+
var HINDSIGHT_DEFAULT_MCP_STATELESS = true;
|
|
64976
65031
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
64977
65032
|
function isPortFree(port) {
|
|
64978
65033
|
return new Promise((resolve28) => {
|
|
@@ -65040,7 +65095,11 @@ function startHindsight(ports) {
|
|
|
65040
65095
|
"-e",
|
|
65041
65096
|
`HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
65042
65097
|
"-e",
|
|
65043
|
-
"HINDSIGHT_API_LLM_PROVIDER=claude-code"
|
|
65098
|
+
"HINDSIGHT_API_LLM_PROVIDER=claude-code",
|
|
65099
|
+
"-e",
|
|
65100
|
+
`HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
65101
|
+
"-e",
|
|
65102
|
+
`HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`
|
|
65044
65103
|
];
|
|
65045
65104
|
const args = [
|
|
65046
65105
|
"run",
|
|
@@ -65058,7 +65117,7 @@ function startHindsight(ports) {
|
|
|
65058
65117
|
"-v",
|
|
65059
65118
|
`${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
|
|
65060
65119
|
"--tmpfs",
|
|
65061
|
-
|
|
65120
|
+
`/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
|
|
65062
65121
|
...envArgs,
|
|
65063
65122
|
HINDSIGHT_IMAGE
|
|
65064
65123
|
];
|
|
@@ -65093,11 +65152,13 @@ function generateHindsightComposeSnippet() {
|
|
|
65093
65152
|
" environment:",
|
|
65094
65153
|
` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
|
|
65095
65154
|
" - HINDSIGHT_API_LLM_PROVIDER=claude-code",
|
|
65155
|
+
` - HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
|
|
65156
|
+
` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
|
|
65096
65157
|
" volumes:",
|
|
65097
65158
|
" - switchroom-hindsight-data:/home/hindsight/.pg0",
|
|
65098
65159
|
` - ${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
|
|
65099
65160
|
" tmpfs:",
|
|
65100
|
-
|
|
65161
|
+
` - /run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
|
|
65101
65162
|
" restart: unless-stopped",
|
|
65102
65163
|
"",
|
|
65103
65164
|
"volumes:",
|
|
@@ -72574,10 +72635,109 @@ function registerMigrateCommand(program3) {
|
|
|
72574
72635
|
// src/cli/hostd.ts
|
|
72575
72636
|
init_source();
|
|
72576
72637
|
init_helpers();
|
|
72577
|
-
import { existsSync as existsSync65, mkdirSync as mkdirSync35, readdirSync as readdirSync26, writeFileSync as writeFileSync31, statSync as statSync26, copyFileSync as copyFileSync11 } from "node:fs";
|
|
72638
|
+
import { existsSync as existsSync65, mkdirSync as mkdirSync35, readdirSync as readdirSync26, readFileSync as readFileSync57, writeFileSync as writeFileSync31, statSync as statSync26, copyFileSync as copyFileSync11 } from "node:fs";
|
|
72639
|
+
import { homedir as homedir28 } from "node:os";
|
|
72640
|
+
import { join as join54 } from "node:path";
|
|
72641
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
72642
|
+
|
|
72643
|
+
// src/host-control/audit-reader.ts
|
|
72578
72644
|
import { homedir as homedir27 } from "node:os";
|
|
72579
72645
|
import { join as join53 } from "node:path";
|
|
72580
|
-
|
|
72646
|
+
function defaultAuditLogPath2(home2 = homedir27()) {
|
|
72647
|
+
return join53(home2, ".switchroom", "host-control-audit.log");
|
|
72648
|
+
}
|
|
72649
|
+
function parseAuditLine2(line) {
|
|
72650
|
+
const trimmed = line.trim();
|
|
72651
|
+
if (trimmed.length === 0)
|
|
72652
|
+
return null;
|
|
72653
|
+
let obj;
|
|
72654
|
+
try {
|
|
72655
|
+
obj = JSON.parse(trimmed);
|
|
72656
|
+
} catch {
|
|
72657
|
+
return null;
|
|
72658
|
+
}
|
|
72659
|
+
if (typeof obj !== "object" || obj === null)
|
|
72660
|
+
return null;
|
|
72661
|
+
const o = obj;
|
|
72662
|
+
if (typeof o.ts !== "string" || typeof o.op !== "string")
|
|
72663
|
+
return null;
|
|
72664
|
+
if (typeof o.request_id !== "string" || typeof o.result !== "string")
|
|
72665
|
+
return null;
|
|
72666
|
+
if (typeof o.duration_ms !== "number")
|
|
72667
|
+
return null;
|
|
72668
|
+
const callerRaw = o.caller;
|
|
72669
|
+
let caller;
|
|
72670
|
+
if (callerRaw && callerRaw.kind === "agent" && typeof callerRaw.name === "string") {
|
|
72671
|
+
caller = { kind: "agent", name: callerRaw.name };
|
|
72672
|
+
} else if (callerRaw && callerRaw.kind === "operator") {
|
|
72673
|
+
caller = { kind: "operator" };
|
|
72674
|
+
} else {
|
|
72675
|
+
return null;
|
|
72676
|
+
}
|
|
72677
|
+
const exit_code = o.exit_code === null || typeof o.exit_code === "number" ? o.exit_code : null;
|
|
72678
|
+
const entry = {
|
|
72679
|
+
ts: o.ts,
|
|
72680
|
+
op: o.op,
|
|
72681
|
+
caller,
|
|
72682
|
+
request_id: o.request_id,
|
|
72683
|
+
result: o.result,
|
|
72684
|
+
exit_code,
|
|
72685
|
+
duration_ms: o.duration_ms
|
|
72686
|
+
};
|
|
72687
|
+
if (typeof o.error === "string")
|
|
72688
|
+
entry.error = o.error;
|
|
72689
|
+
return entry;
|
|
72690
|
+
}
|
|
72691
|
+
function filterEntries(entries, filters) {
|
|
72692
|
+
return entries.filter((e) => {
|
|
72693
|
+
if (filters.agent != null) {
|
|
72694
|
+
if (e.caller.kind !== "agent")
|
|
72695
|
+
return false;
|
|
72696
|
+
if (e.caller.name !== filters.agent)
|
|
72697
|
+
return false;
|
|
72698
|
+
}
|
|
72699
|
+
if (filters.op != null && e.op !== filters.op)
|
|
72700
|
+
return false;
|
|
72701
|
+
if (filters.errorOnly) {
|
|
72702
|
+
if (e.result !== "error" && e.result !== "denied")
|
|
72703
|
+
return false;
|
|
72704
|
+
}
|
|
72705
|
+
return true;
|
|
72706
|
+
});
|
|
72707
|
+
}
|
|
72708
|
+
function readAndFilter(raw, filters, limit) {
|
|
72709
|
+
const lines = raw.split(`
|
|
72710
|
+
`);
|
|
72711
|
+
const parsed = [];
|
|
72712
|
+
for (const line of lines) {
|
|
72713
|
+
const e = parseAuditLine2(line);
|
|
72714
|
+
if (e != null)
|
|
72715
|
+
parsed.push(e);
|
|
72716
|
+
}
|
|
72717
|
+
const filtered = filterEntries(parsed, filters);
|
|
72718
|
+
return filtered.slice(-Math.max(1, limit));
|
|
72719
|
+
}
|
|
72720
|
+
function shortCaller(caller) {
|
|
72721
|
+
return caller.kind === "agent" ? caller.name : "operator";
|
|
72722
|
+
}
|
|
72723
|
+
function shortTs(ts) {
|
|
72724
|
+
return ts.replace("T", " ").replace(/\.\d+Z$/, "").slice(0, 19);
|
|
72725
|
+
}
|
|
72726
|
+
function formatForCli(entries) {
|
|
72727
|
+
const out = [];
|
|
72728
|
+
for (const e of entries) {
|
|
72729
|
+
const ts = shortTs(e.ts).padEnd(20);
|
|
72730
|
+
const caller = shortCaller(e.caller).padEnd(15);
|
|
72731
|
+
const op = e.op.padEnd(16);
|
|
72732
|
+
const result = e.result.padEnd(10);
|
|
72733
|
+
const exit = e.exit_code == null ? " -" : String(e.exit_code).padStart(3);
|
|
72734
|
+
const ms = `${e.duration_ms}ms`.padStart(8);
|
|
72735
|
+
out.push(`${ts} ${caller} ${op} ${result} ${exit} ${ms}`);
|
|
72736
|
+
}
|
|
72737
|
+
return out;
|
|
72738
|
+
}
|
|
72739
|
+
|
|
72740
|
+
// src/cli/hostd.ts
|
|
72581
72741
|
var DEFAULT_IMAGE_TAG = "latest";
|
|
72582
72742
|
var HOSTD_COMPOSE_PROJECT = "switchroom-hostd";
|
|
72583
72743
|
function renderHostdComposeFile(opts) {
|
|
@@ -72660,10 +72820,10 @@ networks:
|
|
|
72660
72820
|
`;
|
|
72661
72821
|
}
|
|
72662
72822
|
function hostdDir() {
|
|
72663
|
-
return
|
|
72823
|
+
return join54(homedir28(), ".switchroom", "hostd");
|
|
72664
72824
|
}
|
|
72665
72825
|
function hostdComposePath() {
|
|
72666
|
-
return
|
|
72826
|
+
return join54(hostdDir(), "docker-compose.yml");
|
|
72667
72827
|
}
|
|
72668
72828
|
function backupExistingCompose() {
|
|
72669
72829
|
const p = hostdComposePath();
|
|
@@ -72702,7 +72862,7 @@ async function doInstall(opts, program3) {
|
|
|
72702
72862
|
const composePath = hostdComposePath();
|
|
72703
72863
|
mkdirSync35(dir, { recursive: true });
|
|
72704
72864
|
const yaml = renderHostdComposeFile({
|
|
72705
|
-
hostHome:
|
|
72865
|
+
hostHome: homedir28(),
|
|
72706
72866
|
imageTag: opts.tag ?? DEFAULT_IMAGE_TAG
|
|
72707
72867
|
});
|
|
72708
72868
|
if (opts.dryRun) {
|
|
@@ -72770,7 +72930,7 @@ function doStatus() {
|
|
|
72770
72930
|
for (const name of readdirSync26(dir)) {
|
|
72771
72931
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
72772
72932
|
continue;
|
|
72773
|
-
const sockPath =
|
|
72933
|
+
const sockPath = join54(dir, name, "sock");
|
|
72774
72934
|
if (existsSync65(sockPath)) {
|
|
72775
72935
|
const st = statSync26(sockPath);
|
|
72776
72936
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -72812,6 +72972,48 @@ function registerHostdCommand(program3) {
|
|
|
72812
72972
|
}));
|
|
72813
72973
|
hostd.command("status").description("Show daemon state and bound sockets").action(() => doStatus());
|
|
72814
72974
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
72975
|
+
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
72976
|
+
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
72977
|
+
if (!existsSync65(logPath)) {
|
|
72978
|
+
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
72979
|
+
The log is created when hostd handles its first privileged-verb request.`));
|
|
72980
|
+
return;
|
|
72981
|
+
}
|
|
72982
|
+
const raw = readFileSync57(logPath, "utf-8");
|
|
72983
|
+
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
72984
|
+
const filters = {
|
|
72985
|
+
agent: opts.agent,
|
|
72986
|
+
op: opts.op,
|
|
72987
|
+
errorOnly: !!opts.error
|
|
72988
|
+
};
|
|
72989
|
+
const entries = readAndFilter(raw, filters, limit);
|
|
72990
|
+
if (entries.length === 0) {
|
|
72991
|
+
const parts = [];
|
|
72992
|
+
if (opts.agent)
|
|
72993
|
+
parts.push(`agent=${opts.agent}`);
|
|
72994
|
+
if (opts.op)
|
|
72995
|
+
parts.push(`op=${opts.op}`);
|
|
72996
|
+
if (opts.error)
|
|
72997
|
+
parts.push("errors-only");
|
|
72998
|
+
const desc = parts.length > 0 ? ` matching ${parts.join(", ")}` : "";
|
|
72999
|
+
console.log(source_default.dim(`No hostd audit entries${desc}.`));
|
|
73000
|
+
return;
|
|
73001
|
+
}
|
|
73002
|
+
const header = "ts".padEnd(20) + " " + "caller".padEnd(15) + " " + "op".padEnd(16) + " " + "result".padEnd(10) + " " + "exit".padStart(3) + " " + "dur".padStart(8);
|
|
73003
|
+
console.log(source_default.dim(header));
|
|
73004
|
+
console.log(source_default.dim("\u2500".repeat(header.length)));
|
|
73005
|
+
for (const line of formatForCli(entries)) {
|
|
73006
|
+
if (line.includes(" error ") || line.includes(" denied ")) {
|
|
73007
|
+
console.log(source_default.red(line));
|
|
73008
|
+
} else if (line.includes(" started ")) {
|
|
73009
|
+
console.log(source_default.yellow(line));
|
|
73010
|
+
} else {
|
|
73011
|
+
console.log(line);
|
|
73012
|
+
}
|
|
73013
|
+
}
|
|
73014
|
+
console.log();
|
|
73015
|
+
console.log(source_default.dim(`${entries.length} entr${entries.length === 1 ? "y" : "ies"} shown` + (entries.length === limit ? ` (--tail ${limit})` : "") + ` \u00b7 log: ${logPath}`));
|
|
73016
|
+
});
|
|
72815
73017
|
}
|
|
72816
73018
|
|
|
72817
73019
|
// src/cli/index.ts
|
|
@@ -11286,7 +11286,7 @@ var QuotaConfigSchema = exports_external.object({
|
|
|
11286
11286
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
11287
11287
|
});
|
|
11288
11288
|
var HostControlConfigSchema = exports_external.object({
|
|
11289
|
-
enabled: exports_external.boolean().
|
|
11289
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip — the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` — " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C §5.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
11290
11290
|
});
|
|
11291
11291
|
var SwitchroomConfigSchema = exports_external.object({
|
|
11292
11292
|
switchroom: exports_external.object({
|
|
@@ -11312,7 +11312,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11312
11312
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
11313
11313
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
11314
11314
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
11315
|
-
host_control: HostControlConfigSchema.
|
|
11315
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
11316
11316
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
11317
11317
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
11318
11318
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
@@ -11278,7 +11278,7 @@ var init_schema = __esm(() => {
|
|
|
11278
11278
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
11279
11279
|
});
|
|
11280
11280
|
HostControlConfigSchema = exports_external.object({
|
|
11281
|
-
enabled: exports_external.boolean().
|
|
11281
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip — the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` — " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C §5.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
11282
11282
|
});
|
|
11283
11283
|
SwitchroomConfigSchema = exports_external.object({
|
|
11284
11284
|
switchroom: exports_external.object({
|
|
@@ -11304,7 +11304,7 @@ var init_schema = __esm(() => {
|
|
|
11304
11304
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
11305
11305
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
11306
11306
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
11307
|
-
host_control: HostControlConfigSchema.
|
|
11307
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
11308
11308
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
11309
11309
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
11310
11310
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
@@ -11278,7 +11278,7 @@ var init_schema = __esm(() => {
|
|
|
11278
11278
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
11279
11279
|
});
|
|
11280
11280
|
HostControlConfigSchema = exports_external.object({
|
|
11281
|
-
enabled: exports_external.boolean().
|
|
11281
|
+
enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip — the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` — " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C §5.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
|
|
11282
11282
|
});
|
|
11283
11283
|
SwitchroomConfigSchema = exports_external.object({
|
|
11284
11284
|
switchroom: exports_external.object({
|
|
@@ -11304,7 +11304,7 @@ var init_schema = __esm(() => {
|
|
|
11304
11304
|
drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
|
|
11305
11305
|
google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
|
|
11306
11306
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
11307
|
-
host_control: HostControlConfigSchema.
|
|
11307
|
+
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (docs/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
11308
11308
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
11309
11309
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
11310
11310
|
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
package/package.json
CHANGED
|
@@ -41,7 +41,7 @@ describe('parseCommandName', () => {
|
|
|
41
41
|
|
|
42
42
|
describe('ADMIN_COMMAND_NAMES', () => {
|
|
43
43
|
it('contains the fleet-management admin commands', () => {
|
|
44
|
-
const required = ['agents', 'logs', 'restart', 'update', 'reconcile', 'stop', 'agentstart', 'grant', 'dangerous', 'permissions', 'vault']
|
|
44
|
+
const required = ['agents', 'logs', 'restart', 'update', 'reconcile', 'stop', 'agentstart', 'grant', 'dangerous', 'permissions', 'vault', 'audit']
|
|
45
45
|
for (const cmd of required) {
|
|
46
46
|
expect(ADMIN_COMMAND_NAMES.has(cmd)).toBe(true)
|
|
47
47
|
}
|