switchroom 0.18.28 → 0.18.30
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/bin/handoff-briefing.sh +15 -2
- package/dist/agent-scheduler/index.js +111 -7
- package/dist/auth-broker/index.js +154 -73
- package/dist/cli/autoaccept-poll.js +8 -3
- package/dist/cli/drive-write-pretool.mjs +8 -3
- package/dist/cli/ms-365-write-pretool.mjs +158 -11
- package/dist/cli/notion-write-pretool.mjs +103 -4
- package/dist/cli/switchroom.js +2712 -2219
- package/dist/host-control/main.js +110 -70
- package/dist/vault/approvals/kernel-server.js +116 -70
- package/dist/vault/broker/server.js +314 -202
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +105 -34
- package/telegram-plugin/dist/bridge/bridge.js +71 -47
- package/telegram-plugin/dist/gateway/gateway.js +1128 -666
- package/telegram-plugin/dist/server.js +89 -64
- package/telegram-plugin/gateway/backstop-delivery.ts +272 -0
- package/telegram-plugin/gateway/forward-origin.ts +9 -1
- package/telegram-plugin/gateway/gateway.ts +656 -388
- package/telegram-plugin/gateway/model-command.ts +331 -602
- package/telegram-plugin/gateway/session-model-file.ts +40 -0
- package/telegram-plugin/gateway/turn-record-status.ts +45 -0
- package/telegram-plugin/gateway/unhandled-message.ts +177 -0
- package/telegram-plugin/history.ts +153 -23
- package/telegram-plugin/llm-error-present.ts +24 -0
- package/telegram-plugin/model-unavailable.ts +55 -0
- package/telegram-plugin/operator-events.ts +113 -0
- package/telegram-plugin/pending-user-notice.ts +88 -0
- package/telegram-plugin/shared/local-time.ts +99 -0
- package/telegram-plugin/tests/backstop-delivery.test.ts +250 -0
- package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +103 -0
- package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +264 -0
- package/telegram-plugin/tests/forward-origin.test.ts +30 -3
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +111 -60
- package/telegram-plugin/tests/history.test.ts +88 -0
- package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +278 -0
- package/telegram-plugin/tests/local-time.test.ts +135 -0
- package/telegram-plugin/tests/model-command.test.ts +427 -1512
- package/telegram-plugin/tests/session-model-file.test.ts +23 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +34 -0
- package/telegram-plugin/tier-downgrade.ts +4 -3
- package/telegram-plugin/turn-flush-safety.ts +25 -1
- package/vendor/hindsight-memory/scripts/backfill_transcripts.py +399 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/content.py +93 -7
- package/vendor/hindsight-memory/scripts/lib/turnlog.py +450 -0
- package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +467 -0
- package/vendor/hindsight-memory/tests/test_content.py +63 -7
|
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
|
|
|
4013
4013
|
});
|
|
4014
4014
|
|
|
4015
4015
|
// src/config/schema.ts
|
|
4016
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4016
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
4017
4017
|
var init_schema = __esm(() => {
|
|
4018
4018
|
init_zod();
|
|
4019
4019
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -4411,11 +4411,52 @@ var init_schema = __esm(() => {
|
|
|
4411
4411
|
approvers: exports_external.array(ApproverIdSchema).min(1).optional().describe("Per-agent approver override. When set, replaces (does not extend) " + "the top-level drive.approvers list for this agent's onboarding card."),
|
|
4412
4412
|
tier: GoogleWorkspaceTierSchema.optional().describe("Per-agent tier override (RFC G Phase 1). When set, replaces the " + "top-level google_workspace.tier for this agent. Common case: most " + "agents on `core`, one specialist on `extended` for Slides access.")
|
|
4413
4413
|
}).optional();
|
|
4414
|
+
MicrosoftAccountEmailSchema = exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
4415
|
+
message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
|
|
4416
|
+
}).transform((v) => v.trim().toLowerCase());
|
|
4417
|
+
MicrosoftToolTokenSchema = exports_external.string().min(1).regex(/^[a-z0-9-]+$/, {
|
|
4418
|
+
message: "microsoft_workspace tools[] tokens are forwarded to softeria's " + "--enabled-tools regex; each must be lowercase kebab-case " + "([a-z0-9-]+, e.g. 'mail', 'calendar', 'send-mail') so an unescaped " + "regex metacharacter can't crash the launcher"
|
|
4419
|
+
});
|
|
4420
|
+
MicrosoftAccountBindingSchema = exports_external.object({
|
|
4421
|
+
account: MicrosoftAccountEmailSchema.describe("The Microsoft account this binding uses. Must be a key in top-level " + "`microsoft_accounts:` with this agent in its `enabled_for[]`."),
|
|
4422
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist → softeria `--enabled-tools <regex>` " + "(tokens joined with `|`). Omitted = all tools exposed for this account."),
|
|
4423
|
+
org_mode: exports_external.boolean().optional().describe("Per-binding org_mode override (RFC #1873 §6.4).")
|
|
4424
|
+
});
|
|
4414
4425
|
AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
4415
|
-
account:
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4426
|
+
account: MicrosoftAccountEmailSchema.optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized). Mutually exclusive with `accounts` (plural)."),
|
|
4427
|
+
tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist for the SINGULAR `account` form → " + "softeria `--enabled-tools <regex>`. Omitted = all tools. Only " + "valid with the singular `account`; using it together with the " + "plural `accounts` (which carries per-binding `tools`) is an error."),
|
|
4428
|
+
org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 §6.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false)."),
|
|
4429
|
+
accounts: exports_external.array(MicrosoftAccountBindingSchema).min(1).optional().describe("Plural multi-account form: bind MULTIPLE Microsoft accounts to " + "this agent, each with its own tool scope. Mutually exclusive with " + "the singular `account`. Each account gets its own `ms-365-<slug>` " + "MCP server.")
|
|
4430
|
+
}).superRefine((v, ctx) => {
|
|
4431
|
+
const hasSingular = v.account !== undefined;
|
|
4432
|
+
const hasPlural = v.accounts !== undefined;
|
|
4433
|
+
if (hasSingular && hasPlural) {
|
|
4434
|
+
ctx.addIssue({
|
|
4435
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4436
|
+
message: "microsoft_workspace: use EITHER `account` (singular) OR " + "`accounts` (plural array), not both",
|
|
4437
|
+
path: ["accounts"]
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
if (hasPlural && v.tools !== undefined) {
|
|
4441
|
+
ctx.addIssue({
|
|
4442
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4443
|
+
message: "microsoft_workspace: block-level `tools` applies to the singular " + "`account` only; with `accounts` put `tools` inside each binding",
|
|
4444
|
+
path: ["tools"]
|
|
4445
|
+
});
|
|
4446
|
+
}
|
|
4447
|
+
if (hasPlural) {
|
|
4448
|
+
const seen = new Set;
|
|
4449
|
+
for (const b of v.accounts) {
|
|
4450
|
+
if (seen.has(b.account)) {
|
|
4451
|
+
ctx.addIssue({
|
|
4452
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4453
|
+
message: `microsoft_workspace: duplicate account '${b.account}' in accounts[]`,
|
|
4454
|
+
path: ["accounts"]
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
4457
|
+
seen.add(b.account);
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4419
4460
|
}).optional();
|
|
4420
4461
|
AgentNotionWorkspaceConfigSchema = exports_external.object({
|
|
4421
4462
|
databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
|
|
@@ -4790,6 +4831,26 @@ var init_schema = __esm(() => {
|
|
|
4790
4831
|
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4791
4832
|
checkServes(a.serves, ["agents", name, "serves"]);
|
|
4792
4833
|
}
|
|
4834
|
+
const microsoftAccounts = cfg.microsoft_accounts;
|
|
4835
|
+
for (const [name, a] of Object.entries(cfg.agents ?? {})) {
|
|
4836
|
+
const mw = a.microsoft_workspace;
|
|
4837
|
+
const accounts = mw?.accounts;
|
|
4838
|
+
if (!accounts)
|
|
4839
|
+
continue;
|
|
4840
|
+
accounts.forEach((b, i) => {
|
|
4841
|
+
const acct = b?.account?.trim().toLowerCase();
|
|
4842
|
+
if (!acct)
|
|
4843
|
+
return;
|
|
4844
|
+
const enabledFor = microsoftAccounts?.[acct]?.enabled_for ?? [];
|
|
4845
|
+
if (!enabledFor.includes(name)) {
|
|
4846
|
+
ctx.addIssue({
|
|
4847
|
+
code: exports_external.ZodIssueCode.custom,
|
|
4848
|
+
message: `agent '${name}' binds Microsoft account '${acct}' but is not in ` + `microsoft_accounts['${acct}'].enabled_for[] — operator must run ` + `\`switchroom auth microsoft enable ${acct} ${name}\``,
|
|
4849
|
+
path: ["agents", name, "microsoft_workspace", "accounts", i, "account"]
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4793
4854
|
});
|
|
4794
4855
|
});
|
|
4795
4856
|
|
|
@@ -5184,6 +5245,25 @@ var init_merge = __esm(() => {
|
|
|
5184
5245
|
})(mergeAgentConfig ||= {});
|
|
5185
5246
|
});
|
|
5186
5247
|
|
|
5248
|
+
// src/config/timezone.ts
|
|
5249
|
+
function isResolvableTimezone(zone) {
|
|
5250
|
+
try {
|
|
5251
|
+
new Intl.DateTimeFormat("en-US", { timeZone: zone });
|
|
5252
|
+
return true;
|
|
5253
|
+
} catch {
|
|
5254
|
+
return false;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
var CONTAINER_DEFAULT_UTC_ZONES;
|
|
5258
|
+
var init_timezone = __esm(() => {
|
|
5259
|
+
CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
5260
|
+
"UTC",
|
|
5261
|
+
"Etc/UTC",
|
|
5262
|
+
"Etc/Universal",
|
|
5263
|
+
"Universal"
|
|
5264
|
+
]);
|
|
5265
|
+
});
|
|
5266
|
+
|
|
5187
5267
|
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
5188
5268
|
var require_identity = __commonJS((exports) => {
|
|
5189
5269
|
var ALIAS = Symbol.for("yaml.alias");
|
|
@@ -12492,8 +12572,30 @@ function loadConfig(configPath) {
|
|
|
12492
12572
|
if (notionIssues.length > 0) {
|
|
12493
12573
|
throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
|
|
12494
12574
|
}
|
|
12575
|
+
validateAllTimezones(config, filePath);
|
|
12495
12576
|
return config;
|
|
12496
12577
|
}
|
|
12578
|
+
function validateAllTimezones(config, filePath) {
|
|
12579
|
+
const issues = [];
|
|
12580
|
+
const check = (zone, where) => {
|
|
12581
|
+
if (zone == null)
|
|
12582
|
+
return;
|
|
12583
|
+
if (!isResolvableTimezone(zone)) {
|
|
12584
|
+
issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists — check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
|
|
12585
|
+
}
|
|
12586
|
+
};
|
|
12587
|
+
check(config.switchroom?.timezone, "switchroom.timezone");
|
|
12588
|
+
check(config.defaults?.timezone, "defaults.timezone");
|
|
12589
|
+
for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
|
|
12590
|
+
check(profile?.timezone, `profiles.${profileName}.timezone`);
|
|
12591
|
+
}
|
|
12592
|
+
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
12593
|
+
check(agentRaw?.timezone, `agents.${agentName}.timezone`);
|
|
12594
|
+
}
|
|
12595
|
+
if (issues.length > 0) {
|
|
12596
|
+
throw new ConfigError(`Invalid timezone configuration in ${filePath}`, issues);
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12497
12599
|
function validateAllCronTopicAliases(config, filePath) {
|
|
12498
12600
|
const issues = [];
|
|
12499
12601
|
for (const [agentName, agentRaw] of Object.entries(config.agents)) {
|
|
@@ -12536,6 +12638,7 @@ var init_loader = __esm(() => {
|
|
|
12536
12638
|
init_paths();
|
|
12537
12639
|
init_overlay_loader();
|
|
12538
12640
|
init_merge();
|
|
12641
|
+
init_timezone();
|
|
12539
12642
|
import_yaml2 = __toESM(require_dist(), 1);
|
|
12540
12643
|
ConfigError = class ConfigError extends Error {
|
|
12541
12644
|
details;
|
|
@@ -18109,7 +18212,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18109
18212
|
|
|
18110
18213
|
// src/vault/broker/server.ts
|
|
18111
18214
|
import * as net from "node:net";
|
|
18112
|
-
import { mkdirSync as
|
|
18215
|
+
import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync8 } from "node:fs";
|
|
18113
18216
|
|
|
18114
18217
|
// src/agents/compose.ts
|
|
18115
18218
|
init_schema();
|
|
@@ -18473,14 +18576,7 @@ init_merge();
|
|
|
18473
18576
|
|
|
18474
18577
|
// src/agents/scaffold.ts
|
|
18475
18578
|
init_merge();
|
|
18476
|
-
|
|
18477
|
-
// src/config/timezone.ts
|
|
18478
|
-
var CONTAINER_DEFAULT_UTC_ZONES = new Set([
|
|
18479
|
-
"UTC",
|
|
18480
|
-
"Etc/UTC",
|
|
18481
|
-
"Etc/Universal",
|
|
18482
|
-
"Universal"
|
|
18483
|
-
]);
|
|
18579
|
+
init_timezone();
|
|
18484
18580
|
|
|
18485
18581
|
// src/cli/agent-config.ts
|
|
18486
18582
|
import { join } from "node:path";
|
|
@@ -18698,63 +18794,6 @@ for (const name of SHARED_FRAGMENTS) {
|
|
|
18698
18794
|
}
|
|
18699
18795
|
}
|
|
18700
18796
|
|
|
18701
|
-
// src/agents/pane-lock.ts
|
|
18702
|
-
var tails = new Map;
|
|
18703
|
-
|
|
18704
|
-
// src/agents/inject.ts
|
|
18705
|
-
var INJECT_COMMANDS = new Map([
|
|
18706
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18707
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18708
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18709
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18710
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18711
|
-
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18712
|
-
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
18713
|
-
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18714
|
-
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
18715
|
-
[
|
|
18716
|
-
"/clear",
|
|
18717
|
-
{
|
|
18718
|
-
description: "Clear session screen",
|
|
18719
|
-
expectsOutput: false,
|
|
18720
|
-
silentNote: "context cleared — fresh slate",
|
|
18721
|
-
argsAllowed: false
|
|
18722
|
-
}
|
|
18723
|
-
],
|
|
18724
|
-
[
|
|
18725
|
-
"/compact",
|
|
18726
|
-
{
|
|
18727
|
-
description: "Compact conversation history",
|
|
18728
|
-
expectsOutput: false,
|
|
18729
|
-
silentNote: "compaction runs silently",
|
|
18730
|
-
argsAllowed: false
|
|
18731
|
-
}
|
|
18732
|
-
]
|
|
18733
|
-
]);
|
|
18734
|
-
var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
|
|
18735
|
-
var INJECT_BLOCKED = new Map([
|
|
18736
|
-
["/login", { reason: "would mutate auth state" }],
|
|
18737
|
-
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
18738
|
-
["/exit", { reason: "would kill the agent process" }],
|
|
18739
|
-
["/quit", { reason: "would kill the agent process" }],
|
|
18740
|
-
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
18741
|
-
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
18742
|
-
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
18743
|
-
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
18744
|
-
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
18745
|
-
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
18746
|
-
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
18747
|
-
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
18748
|
-
["/bug", { reason: "submits a bug report over the network" }],
|
|
18749
|
-
[
|
|
18750
|
-
"/effort",
|
|
18751
|
-
{
|
|
18752
|
-
reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
|
|
18753
|
-
}
|
|
18754
|
-
]
|
|
18755
|
-
]);
|
|
18756
|
-
var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
|
|
18757
|
-
|
|
18758
18797
|
// src/setup/hindsight.ts
|
|
18759
18798
|
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
18760
18799
|
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
@@ -19588,42 +19627,108 @@ var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
|
|
|
19588
19627
|
|
|
19589
19628
|
// src/agents/compose.ts
|
|
19590
19629
|
init_merge();
|
|
19630
|
+
init_timezone();
|
|
19591
19631
|
|
|
19592
19632
|
// src/setup/host-capabilities.ts
|
|
19593
19633
|
init_paths();
|
|
19594
19634
|
|
|
19635
|
+
// src/vault/grants-db-path.ts
|
|
19636
|
+
import * as os from "node:os";
|
|
19637
|
+
import * as path from "node:path";
|
|
19638
|
+
import * as fs from "node:fs";
|
|
19639
|
+
var GRANTS_DB_DIRNAME = "vault-broker";
|
|
19640
|
+
var GRANTS_DB_FILENAME = "vault-grants.db";
|
|
19641
|
+
var GRANTS_DB_CONTAINER_DIR = `/root/.switchroom/${GRANTS_DB_DIRNAME}`;
|
|
19642
|
+
var GRANTS_DB_CONTAINER_PATH = `${GRANTS_DB_CONTAINER_DIR}/${GRANTS_DB_FILENAME}`;
|
|
19643
|
+
function getGrantsDbDir(home2 = os.homedir()) {
|
|
19644
|
+
return path.join(home2, ".switchroom", GRANTS_DB_DIRNAME);
|
|
19645
|
+
}
|
|
19646
|
+
function getGrantsDbPath(home2 = os.homedir()) {
|
|
19647
|
+
return path.join(getGrantsDbDir(home2), GRANTS_DB_FILENAME);
|
|
19648
|
+
}
|
|
19649
|
+
function getLegacyGrantsDbPath(newDbPath = getGrantsDbPath()) {
|
|
19650
|
+
return path.join(path.dirname(path.dirname(newDbPath)), GRANTS_DB_FILENAME);
|
|
19651
|
+
}
|
|
19652
|
+
var WAL_SIDECAR_SUFFIXES = ["-wal", "-shm"];
|
|
19653
|
+
function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
|
|
19654
|
+
const rename = deps?.rename ?? fs.renameSync;
|
|
19655
|
+
const legacyPath = getLegacyGrantsDbPath(newDbPath);
|
|
19656
|
+
if (path.resolve(legacyPath) === path.resolve(newDbPath)) {
|
|
19657
|
+
return { migrated: false };
|
|
19658
|
+
}
|
|
19659
|
+
if (!fs.existsSync(legacyPath))
|
|
19660
|
+
return { migrated: false };
|
|
19661
|
+
if (fs.existsSync(newDbPath))
|
|
19662
|
+
return { migrated: false };
|
|
19663
|
+
fs.mkdirSync(path.dirname(newDbPath), { recursive: true, mode: 448 });
|
|
19664
|
+
const movedSidecars = [];
|
|
19665
|
+
const rollbackSidecars = () => {
|
|
19666
|
+
for (const m of [...movedSidecars].reverse()) {
|
|
19667
|
+
try {
|
|
19668
|
+
rename(m.to, m.from);
|
|
19669
|
+
} catch {}
|
|
19670
|
+
}
|
|
19671
|
+
};
|
|
19672
|
+
for (const suffix of WAL_SIDECAR_SUFFIXES) {
|
|
19673
|
+
const from = `${legacyPath}${suffix}`;
|
|
19674
|
+
const to = `${newDbPath}${suffix}`;
|
|
19675
|
+
if (!fs.existsSync(from))
|
|
19676
|
+
continue;
|
|
19677
|
+
try {
|
|
19678
|
+
rename(from, to);
|
|
19679
|
+
movedSidecars.push({ from, to });
|
|
19680
|
+
} catch (err) {
|
|
19681
|
+
rollbackSidecars();
|
|
19682
|
+
throw new Error(`grants-db migration: failed to move WAL sidecar ${from} -> ${to} ` + `(${err.message}); aborted with the legacy DB intact — ` + `will retry on the next apply/boot`);
|
|
19683
|
+
}
|
|
19684
|
+
}
|
|
19685
|
+
try {
|
|
19686
|
+
rename(legacyPath, newDbPath);
|
|
19687
|
+
} catch (err) {
|
|
19688
|
+
if (fs.existsSync(newDbPath) && !fs.existsSync(legacyPath)) {
|
|
19689
|
+
return { migrated: false };
|
|
19690
|
+
}
|
|
19691
|
+
rollbackSidecars();
|
|
19692
|
+
throw err;
|
|
19693
|
+
}
|
|
19694
|
+
try {
|
|
19695
|
+
fs.chmodSync(newDbPath, 384);
|
|
19696
|
+
} catch {}
|
|
19697
|
+
return { migrated: true, from: legacyPath, to: newDbPath };
|
|
19698
|
+
}
|
|
19699
|
+
|
|
19595
19700
|
// src/agents/compose.ts
|
|
19596
19701
|
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
19597
19702
|
|
|
19598
19703
|
// src/vault/broker/server.ts
|
|
19599
|
-
import { dirname as
|
|
19704
|
+
import { dirname as dirname6, resolve as resolve9, basename as basename4 } from "node:path";
|
|
19600
19705
|
import * as os3 from "node:os";
|
|
19601
|
-
import * as
|
|
19706
|
+
import * as path4 from "node:path";
|
|
19602
19707
|
|
|
19603
19708
|
// src/vault/migrate-layout.ts
|
|
19604
19709
|
import {
|
|
19605
19710
|
copyFileSync as copyFileSync2,
|
|
19606
|
-
chmodSync,
|
|
19607
|
-
existsSync as
|
|
19711
|
+
chmodSync as chmodSync2,
|
|
19712
|
+
existsSync as existsSync8,
|
|
19608
19713
|
fsyncSync as fsyncSync4,
|
|
19609
19714
|
lstatSync as lstatSync2,
|
|
19610
|
-
mkdirSync as
|
|
19715
|
+
mkdirSync as mkdirSync4,
|
|
19611
19716
|
openSync as openSync4,
|
|
19612
19717
|
closeSync as closeSync4,
|
|
19613
19718
|
readFileSync as readFileSync7,
|
|
19614
|
-
renameSync as
|
|
19719
|
+
renameSync as renameSync4,
|
|
19615
19720
|
statSync as statSync4,
|
|
19616
19721
|
symlinkSync,
|
|
19617
19722
|
unlinkSync as unlinkSync3
|
|
19618
19723
|
} from "node:fs";
|
|
19619
19724
|
import { createHash as createHash2 } from "node:crypto";
|
|
19620
|
-
import { basename as basename3, dirname as
|
|
19725
|
+
import { basename as basename3, dirname as dirname3, join as join6 } from "node:path";
|
|
19621
19726
|
function vaultLayoutPaths(home2) {
|
|
19622
|
-
const switchroomRoot =
|
|
19727
|
+
const switchroomRoot = join6(home2, ".switchroom");
|
|
19623
19728
|
return {
|
|
19624
|
-
oldPath:
|
|
19625
|
-
newPath:
|
|
19626
|
-
parent:
|
|
19729
|
+
oldPath: join6(switchroomRoot, "vault.enc"),
|
|
19730
|
+
newPath: join6(switchroomRoot, "vault", "vault.enc"),
|
|
19731
|
+
parent: join6(switchroomRoot, "vault"),
|
|
19627
19732
|
switchroomRoot
|
|
19628
19733
|
};
|
|
19629
19734
|
}
|
|
@@ -19632,11 +19737,11 @@ function inspectVaultLayout(home2) {
|
|
|
19632
19737
|
}
|
|
19633
19738
|
function runMigration(home2, opts) {
|
|
19634
19739
|
const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(home2);
|
|
19635
|
-
const lockTarget =
|
|
19740
|
+
const lockTarget = existsSync8(newPath) ? newPath : existsSync8(oldPath) ? oldPath : null;
|
|
19636
19741
|
const release = !opts.dryRun && lockTarget !== null ? acquireVaultLock(lockTarget) : null;
|
|
19637
19742
|
try {
|
|
19638
19743
|
const oldStat = lstatSyncOrNull(oldPath);
|
|
19639
|
-
const newExists =
|
|
19744
|
+
const newExists = existsSync8(newPath);
|
|
19640
19745
|
if (oldStat === null && !newExists) {
|
|
19641
19746
|
return { kind: "no-vault" };
|
|
19642
19747
|
}
|
|
@@ -19672,12 +19777,12 @@ function runMigration(home2, opts) {
|
|
|
19672
19777
|
if (oldStat?.isFile() && !newExists) {
|
|
19673
19778
|
if (opts.dryRun)
|
|
19674
19779
|
return { kind: "migrated" };
|
|
19675
|
-
|
|
19780
|
+
mkdirSync4(parent, { recursive: true, mode: 448 });
|
|
19676
19781
|
const tempNew = `${newPath}.tmp`;
|
|
19677
19782
|
copyFileSync2(oldPath, tempNew);
|
|
19678
|
-
|
|
19783
|
+
chmodSync2(tempNew, 384);
|
|
19679
19784
|
fsyncFile(tempNew);
|
|
19680
|
-
|
|
19785
|
+
renameSync4(tempNew, newPath);
|
|
19681
19786
|
fsyncDir(parent);
|
|
19682
19787
|
atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
|
|
19683
19788
|
fsyncDir(switchroomRoot);
|
|
@@ -19692,37 +19797,37 @@ function runMigration(home2, opts) {
|
|
|
19692
19797
|
}
|
|
19693
19798
|
}
|
|
19694
19799
|
}
|
|
19695
|
-
function lstatSyncOrNull(
|
|
19800
|
+
function lstatSyncOrNull(path2) {
|
|
19696
19801
|
try {
|
|
19697
|
-
return lstatSync2(
|
|
19802
|
+
return lstatSync2(path2);
|
|
19698
19803
|
} catch {
|
|
19699
19804
|
return null;
|
|
19700
19805
|
}
|
|
19701
19806
|
}
|
|
19702
|
-
function sha256File(
|
|
19703
|
-
const data = readFileSync7(
|
|
19807
|
+
function sha256File(path2) {
|
|
19808
|
+
const data = readFileSync7(path2);
|
|
19704
19809
|
return createHash2("sha256").update(data).digest("hex");
|
|
19705
19810
|
}
|
|
19706
19811
|
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
19707
|
-
const tmp =
|
|
19708
|
-
if (
|
|
19812
|
+
const tmp = join6(dirname3(target), `.${basename3(target)}.symlink-tmp`);
|
|
19813
|
+
if (existsSync8(tmp)) {
|
|
19709
19814
|
try {
|
|
19710
19815
|
unlinkSync3(tmp);
|
|
19711
19816
|
} catch {}
|
|
19712
19817
|
}
|
|
19713
19818
|
symlinkSync(linkTarget, tmp);
|
|
19714
|
-
|
|
19819
|
+
renameSync4(tmp, target);
|
|
19715
19820
|
}
|
|
19716
|
-
function fsyncFile(
|
|
19717
|
-
const fd = openSync4(
|
|
19821
|
+
function fsyncFile(path2) {
|
|
19822
|
+
const fd = openSync4(path2, "r+");
|
|
19718
19823
|
try {
|
|
19719
19824
|
fsyncSync4(fd);
|
|
19720
19825
|
} finally {
|
|
19721
19826
|
closeSync4(fd);
|
|
19722
19827
|
}
|
|
19723
19828
|
}
|
|
19724
|
-
function fsyncDir(
|
|
19725
|
-
const fd = openSync4(
|
|
19829
|
+
function fsyncDir(path2) {
|
|
19830
|
+
const fd = openSync4(path2, "r");
|
|
19726
19831
|
try {
|
|
19727
19832
|
fsyncSync4(fd);
|
|
19728
19833
|
} finally {
|
|
@@ -19736,14 +19841,14 @@ init_loader();
|
|
|
19736
19841
|
// src/vault/auto-unlock.ts
|
|
19737
19842
|
import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
|
|
19738
19843
|
import {
|
|
19739
|
-
chmodSync as
|
|
19844
|
+
chmodSync as chmodSync3,
|
|
19740
19845
|
closeSync as closeSync5,
|
|
19741
|
-
existsSync as
|
|
19846
|
+
existsSync as existsSync9,
|
|
19742
19847
|
fsyncSync as fsyncSync5,
|
|
19743
|
-
mkdirSync as
|
|
19848
|
+
mkdirSync as mkdirSync5,
|
|
19744
19849
|
openSync as openSync5,
|
|
19745
19850
|
readFileSync as readFileSync8,
|
|
19746
|
-
renameSync as
|
|
19851
|
+
renameSync as renameSync5,
|
|
19747
19852
|
unlinkSync as unlinkSync4,
|
|
19748
19853
|
writeSync as writeSync4
|
|
19749
19854
|
} from "node:fs";
|
|
@@ -19779,9 +19884,9 @@ function readMachineId() {
|
|
|
19779
19884
|
if (override && override.length > 0)
|
|
19780
19885
|
return override;
|
|
19781
19886
|
}
|
|
19782
|
-
for (const
|
|
19887
|
+
for (const path2 of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
|
|
19783
19888
|
try {
|
|
19784
|
-
const id = readFileSync8(
|
|
19889
|
+
const id = readFileSync8(path2, "utf8").trim();
|
|
19785
19890
|
if (id.length > 0)
|
|
19786
19891
|
return id;
|
|
19787
19892
|
} catch {}
|
|
@@ -19818,7 +19923,7 @@ function decryptAutoUnlock(blob, machineId) {
|
|
|
19818
19923
|
}
|
|
19819
19924
|
}
|
|
19820
19925
|
function readAutoUnlockFile(filePath) {
|
|
19821
|
-
if (!
|
|
19926
|
+
if (!existsSync9(filePath)) {
|
|
19822
19927
|
throw new AutoUnlockDecryptError("io");
|
|
19823
19928
|
}
|
|
19824
19929
|
let blob;
|
|
@@ -19832,9 +19937,9 @@ function readAutoUnlockFile(filePath) {
|
|
|
19832
19937
|
var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
19833
19938
|
|
|
19834
19939
|
// src/vault/broker/audit-log.ts
|
|
19835
|
-
import * as
|
|
19836
|
-
import * as
|
|
19837
|
-
import * as
|
|
19940
|
+
import * as fs2 from "node:fs";
|
|
19941
|
+
import * as os2 from "node:os";
|
|
19942
|
+
import * as path2 from "node:path";
|
|
19838
19943
|
|
|
19839
19944
|
// src/util/audit-hashchain.ts
|
|
19840
19945
|
import { createHash as createHash4 } from "node:crypto";
|
|
@@ -19856,10 +19961,10 @@ function chainRow(state, entry) {
|
|
|
19856
19961
|
next: { seq, lastHash: hash }
|
|
19857
19962
|
};
|
|
19858
19963
|
}
|
|
19859
|
-
function readTail(
|
|
19964
|
+
function readTail(path2, tailBytes = 64 * 1024) {
|
|
19860
19965
|
let fd;
|
|
19861
19966
|
try {
|
|
19862
|
-
fd = openSync6(
|
|
19967
|
+
fd = openSync6(path2, "r");
|
|
19863
19968
|
} catch {
|
|
19864
19969
|
return null;
|
|
19865
19970
|
}
|
|
@@ -19879,8 +19984,8 @@ function readTail(path, tailBytes = 64 * 1024) {
|
|
|
19879
19984
|
} catch {}
|
|
19880
19985
|
}
|
|
19881
19986
|
}
|
|
19882
|
-
function seedChain(
|
|
19883
|
-
const tail = readTail(
|
|
19987
|
+
function seedChain(path2) {
|
|
19988
|
+
const tail = readTail(path2);
|
|
19884
19989
|
if (tail === null || tail === "") {
|
|
19885
19990
|
return { seq: 0, lastHash: CHAIN_GENESIS };
|
|
19886
19991
|
}
|
|
@@ -19903,8 +20008,8 @@ function seedChain(path) {
|
|
|
19903
20008
|
|
|
19904
20009
|
// src/vault/broker/test-isolation-guard.ts
|
|
19905
20010
|
import { mkdtempSync } from "node:fs";
|
|
19906
|
-
import { homedir as
|
|
19907
|
-
import { join as
|
|
20011
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
20012
|
+
import { join as join7, resolve as resolve8, sep } from "node:path";
|
|
19908
20013
|
function isTestRuntime() {
|
|
19909
20014
|
return process.env.VITEST !== undefined;
|
|
19910
20015
|
}
|
|
@@ -19912,11 +20017,11 @@ function escapeHatchSet() {
|
|
|
19912
20017
|
return process.env.SWITCHROOM_ALLOW_PROD_VAULT_IN_TEST === "1";
|
|
19913
20018
|
}
|
|
19914
20019
|
function realSwitchroomHome() {
|
|
19915
|
-
return
|
|
20020
|
+
return resolve8(homedir5(), ".switchroom");
|
|
19916
20021
|
}
|
|
19917
20022
|
function isUnderRealSwitchroomHome(p) {
|
|
19918
20023
|
const home2 = realSwitchroomHome();
|
|
19919
|
-
const r =
|
|
20024
|
+
const r = resolve8(p);
|
|
19920
20025
|
return r === home2 || r.startsWith(home2 + sep);
|
|
19921
20026
|
}
|
|
19922
20027
|
var warnedVault = false;
|
|
@@ -19925,7 +20030,7 @@ function safeVaultPath(requestedPath) {
|
|
|
19925
20030
|
return requestedPath;
|
|
19926
20031
|
if (!isUnderRealSwitchroomHome(requestedPath))
|
|
19927
20032
|
return requestedPath;
|
|
19928
|
-
const redirected =
|
|
20033
|
+
const redirected = join7(mkdtempSync(join7(tmpdir(), "sw-test-vault-")), "vault.enc");
|
|
19929
20034
|
if (!warnedVault) {
|
|
19930
20035
|
warnedVault = true;
|
|
19931
20036
|
process.stderr.write(`[test-isolation guard] vault path redirected away from the ` + `production tree to an isolated tmpdir — a vault/broker test ` + `resolved its vault path inside ~/.switchroom. See CLAUDE.md ` + `"Vault & shared-state test discipline".
|
|
@@ -19941,7 +20046,7 @@ function safeAuditLogPath(requestedPath) {
|
|
|
19941
20046
|
if (!isUnderRealSwitchroomHome(requestedPath))
|
|
19942
20047
|
return requestedPath;
|
|
19943
20048
|
if (!cachedTmpAuditLog) {
|
|
19944
|
-
cachedTmpAuditLog =
|
|
20049
|
+
cachedTmpAuditLog = join7(mkdtempSync(join7(tmpdir(), "sw-test-audit-")), "vault-audit.log");
|
|
19945
20050
|
}
|
|
19946
20051
|
if (!warnedAudit) {
|
|
19947
20052
|
warnedAudit = true;
|
|
@@ -19957,7 +20062,7 @@ function failOpenStatePath(logPath) {
|
|
|
19957
20062
|
}
|
|
19958
20063
|
function readFailOpenState(statePath) {
|
|
19959
20064
|
try {
|
|
19960
|
-
const raw =
|
|
20065
|
+
const raw = fs2.readFileSync(statePath, "utf8");
|
|
19961
20066
|
const parsed = JSON.parse(raw);
|
|
19962
20067
|
const count = Number(parsed.failOpenCount);
|
|
19963
20068
|
return {
|
|
@@ -19980,9 +20085,9 @@ function resolveRotation(opts) {
|
|
|
19980
20085
|
}
|
|
19981
20086
|
function rotateAuditLog(logPath, maxFiles) {
|
|
19982
20087
|
const oldest = `${logPath}.${maxFiles}`;
|
|
19983
|
-
if (
|
|
20088
|
+
if (fs2.existsSync(oldest)) {
|
|
19984
20089
|
try {
|
|
19985
|
-
|
|
20090
|
+
fs2.unlinkSync(oldest);
|
|
19986
20091
|
} catch (err) {
|
|
19987
20092
|
process.stderr.write(`[vault-audit] ERROR: could not drop oldest rotation ${oldest}: ${err.message}
|
|
19988
20093
|
`);
|
|
@@ -19991,10 +20096,10 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
19991
20096
|
for (let n = maxFiles - 1;n >= 1; n--) {
|
|
19992
20097
|
const from = `${logPath}.${n}`;
|
|
19993
20098
|
const to = `${logPath}.${n + 1}`;
|
|
19994
|
-
if (!
|
|
20099
|
+
if (!fs2.existsSync(from))
|
|
19995
20100
|
continue;
|
|
19996
20101
|
try {
|
|
19997
|
-
|
|
20102
|
+
fs2.renameSync(from, to);
|
|
19998
20103
|
} catch (err) {
|
|
19999
20104
|
process.stderr.write(`[vault-audit] ERROR: could not rotate ${from} → ${to}: ${err.message}
|
|
20000
20105
|
`);
|
|
@@ -20003,18 +20108,18 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
20003
20108
|
}
|
|
20004
20109
|
const snapshotPath = `${logPath}.1`;
|
|
20005
20110
|
try {
|
|
20006
|
-
|
|
20111
|
+
fs2.copyFileSync(logPath, snapshotPath);
|
|
20007
20112
|
} catch (err) {
|
|
20008
20113
|
process.stderr.write(`[vault-audit] ERROR: could not snapshot active audit log ${logPath} → ${snapshotPath}: ${err.message}
|
|
20009
20114
|
`);
|
|
20010
20115
|
return;
|
|
20011
20116
|
}
|
|
20012
20117
|
try {
|
|
20013
|
-
const fd =
|
|
20118
|
+
const fd = fs2.openSync(snapshotPath, "r");
|
|
20014
20119
|
try {
|
|
20015
|
-
|
|
20120
|
+
fs2.fsyncSync(fd);
|
|
20016
20121
|
} finally {
|
|
20017
|
-
|
|
20122
|
+
fs2.closeSync(fd);
|
|
20018
20123
|
}
|
|
20019
20124
|
} catch (err) {
|
|
20020
20125
|
process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
|
|
@@ -20022,22 +20127,22 @@ function rotateAuditLog(logPath, maxFiles) {
|
|
|
20022
20127
|
return;
|
|
20023
20128
|
}
|
|
20024
20129
|
try {
|
|
20025
|
-
const dirFd =
|
|
20130
|
+
const dirFd = fs2.openSync(path2.dirname(snapshotPath), "r");
|
|
20026
20131
|
try {
|
|
20027
|
-
|
|
20132
|
+
fs2.fsyncSync(dirFd);
|
|
20028
20133
|
} finally {
|
|
20029
|
-
|
|
20134
|
+
fs2.closeSync(dirFd);
|
|
20030
20135
|
}
|
|
20031
20136
|
} catch {}
|
|
20032
20137
|
try {
|
|
20033
|
-
|
|
20138
|
+
fs2.truncateSync(logPath, 0);
|
|
20034
20139
|
} catch (err) {
|
|
20035
20140
|
process.stderr.write(`[vault-audit] ERROR: could not truncate active audit log ${logPath}: ${err.message}
|
|
20036
20141
|
`);
|
|
20037
20142
|
}
|
|
20038
20143
|
}
|
|
20039
20144
|
function defaultAuditLogPath() {
|
|
20040
|
-
return
|
|
20145
|
+
return path2.join(os2.homedir(), ".switchroom", "vault-audit.log");
|
|
20041
20146
|
}
|
|
20042
20147
|
function callerFromPeer(peer) {
|
|
20043
20148
|
if (peer.systemdUnit !== null && peer.systemdUnit.length > 0) {
|
|
@@ -20059,7 +20164,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20059
20164
|
lastError: String(err.message).slice(0, 300)
|
|
20060
20165
|
};
|
|
20061
20166
|
try {
|
|
20062
|
-
|
|
20167
|
+
fs2.writeFileSync(statePath, `${JSON.stringify(state)}
|
|
20063
20168
|
`, { mode: 384 });
|
|
20064
20169
|
} catch (persistErr) {
|
|
20065
20170
|
process.stderr.write(`[vault-audit] ERROR: could not persist fail-open counter ${statePath}: ${persistErr.message}
|
|
@@ -20073,7 +20178,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20073
20178
|
write(entry) {
|
|
20074
20179
|
if (rotation.maxBytes > 0) {
|
|
20075
20180
|
try {
|
|
20076
|
-
const size =
|
|
20181
|
+
const size = fs2.statSync(logPath).size;
|
|
20077
20182
|
if (size >= rotation.maxBytes) {
|
|
20078
20183
|
rotateAuditLog(logPath, rotation.maxFiles);
|
|
20079
20184
|
}
|
|
@@ -20082,7 +20187,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20082
20187
|
const { line, next } = chainRow(chain, entry);
|
|
20083
20188
|
let fd;
|
|
20084
20189
|
try {
|
|
20085
|
-
fd =
|
|
20190
|
+
fd = fs2.openSync(logPath, "a", 384);
|
|
20086
20191
|
} catch (err) {
|
|
20087
20192
|
process.stderr.write(`[vault-audit] ERROR: could not open audit log ${logPath}: ${err.message}
|
|
20088
20193
|
`);
|
|
@@ -20091,7 +20196,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20091
20196
|
}
|
|
20092
20197
|
let durable = false;
|
|
20093
20198
|
try {
|
|
20094
|
-
|
|
20199
|
+
fs2.writeSync(fd, line);
|
|
20095
20200
|
chain = next;
|
|
20096
20201
|
durable = true;
|
|
20097
20202
|
} catch (err) {
|
|
@@ -20100,7 +20205,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20100
20205
|
recordFailOpen(err);
|
|
20101
20206
|
} finally {
|
|
20102
20207
|
try {
|
|
20103
|
-
|
|
20208
|
+
fs2.closeSync(fd);
|
|
20104
20209
|
} catch (closeErr) {
|
|
20105
20210
|
process.stderr.write(`[vault-audit] ERROR: could not close audit log fd: ${closeErr.message}
|
|
20106
20211
|
`);
|
|
@@ -20169,13 +20274,13 @@ function genSalt(rounds, seed_length, callback) {
|
|
|
20169
20274
|
throw Error("Illegal callback: " + typeof callback);
|
|
20170
20275
|
_async(callback);
|
|
20171
20276
|
} else
|
|
20172
|
-
return new Promise(function(
|
|
20277
|
+
return new Promise(function(resolve9, reject) {
|
|
20173
20278
|
_async(function(err, res) {
|
|
20174
20279
|
if (err) {
|
|
20175
20280
|
reject(err);
|
|
20176
20281
|
return;
|
|
20177
20282
|
}
|
|
20178
|
-
|
|
20283
|
+
resolve9(res);
|
|
20179
20284
|
});
|
|
20180
20285
|
});
|
|
20181
20286
|
}
|
|
@@ -20195,13 +20300,13 @@ function hash(password, salt, callback, progressCallback) {
|
|
|
20195
20300
|
throw Error("Illegal callback: " + typeof callback);
|
|
20196
20301
|
_async(callback);
|
|
20197
20302
|
} else
|
|
20198
|
-
return new Promise(function(
|
|
20303
|
+
return new Promise(function(resolve9, reject) {
|
|
20199
20304
|
_async(function(err, res) {
|
|
20200
20305
|
if (err) {
|
|
20201
20306
|
reject(err);
|
|
20202
20307
|
return;
|
|
20203
20308
|
}
|
|
20204
|
-
|
|
20309
|
+
resolve9(res);
|
|
20205
20310
|
});
|
|
20206
20311
|
});
|
|
20207
20312
|
}
|
|
@@ -20234,13 +20339,13 @@ function compare(password, hashValue, callback, progressCallback) {
|
|
|
20234
20339
|
throw Error("Illegal callback: " + typeof callback);
|
|
20235
20340
|
_async(callback);
|
|
20236
20341
|
} else
|
|
20237
|
-
return new Promise(function(
|
|
20342
|
+
return new Promise(function(resolve9, reject) {
|
|
20238
20343
|
_async(function(err, res) {
|
|
20239
20344
|
if (err) {
|
|
20240
20345
|
reject(err);
|
|
20241
20346
|
return;
|
|
20242
20347
|
}
|
|
20243
|
-
|
|
20348
|
+
resolve9(res);
|
|
20244
20349
|
});
|
|
20245
20350
|
});
|
|
20246
20351
|
}
|
|
@@ -21834,6 +21939,11 @@ function rowToGrant(row) {
|
|
|
21834
21939
|
description: row.description ?? null
|
|
21835
21940
|
};
|
|
21836
21941
|
}
|
|
21942
|
+
function checkpointGrantsWal(db) {
|
|
21943
|
+
try {
|
|
21944
|
+
db.run("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
21945
|
+
} catch {}
|
|
21946
|
+
}
|
|
21837
21947
|
function keyMatchesPatterns(patterns, key) {
|
|
21838
21948
|
for (const pat of patterns) {
|
|
21839
21949
|
if (pat.endsWith("*")) {
|
|
@@ -21864,6 +21974,7 @@ async function mintGrant(db, agent_slug, key_allow, ttl_seconds, description, wr
|
|
|
21864
21974
|
now,
|
|
21865
21975
|
description ?? null
|
|
21866
21976
|
]);
|
|
21977
|
+
checkpointGrantsWal(db);
|
|
21867
21978
|
return { token, id, expires_at };
|
|
21868
21979
|
}
|
|
21869
21980
|
async function validateGrant(db, token, key) {
|
|
@@ -21942,6 +22053,7 @@ async function validateGrantForWrite(db, token, key) {
|
|
|
21942
22053
|
function revokeGrant(db, id) {
|
|
21943
22054
|
const now = Math.floor(Date.now() / 1000);
|
|
21944
22055
|
const result = db.run(`UPDATE vault_grants SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`, [now, id]);
|
|
22056
|
+
checkpointGrantsWal(db);
|
|
21945
22057
|
return (result.changes ?? 0) > 0;
|
|
21946
22058
|
}
|
|
21947
22059
|
function listGrants(db, agent_slug) {
|
|
@@ -21984,9 +22096,8 @@ function adminOnlyKeysBeingAdded(requestedKeys, existingKeys, patterns) {
|
|
|
21984
22096
|
}
|
|
21985
22097
|
|
|
21986
22098
|
// src/vault/grants-db.ts
|
|
21987
|
-
import * as
|
|
21988
|
-
import * as
|
|
21989
|
-
import * as fs2 from "node:fs";
|
|
22099
|
+
import * as path3 from "node:path";
|
|
22100
|
+
import * as fs3 from "node:fs";
|
|
21990
22101
|
import { Database } from "bun:sqlite";
|
|
21991
22102
|
|
|
21992
22103
|
// src/vault/approvals/schema.ts
|
|
@@ -22064,8 +22175,8 @@ function migrateApprovalSchema(db) {
|
|
|
22064
22175
|
var DEFAULT_MAX_TTL_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
|
|
22065
22176
|
|
|
22066
22177
|
// src/vault/grants-db.ts
|
|
22067
|
-
var DEFAULT_GRANTS_DB_PATH =
|
|
22068
|
-
var
|
|
22178
|
+
var DEFAULT_GRANTS_DB_PATH = getGrantsDbPath();
|
|
22179
|
+
var WAL_SIDECAR_SUFFIXES2 = ["-wal", "-shm"];
|
|
22069
22180
|
function isGrantsDbCorruption(err) {
|
|
22070
22181
|
if (!err || typeof err !== "object")
|
|
22071
22182
|
return false;
|
|
@@ -22079,12 +22190,12 @@ function isGrantsDbCorruption(err) {
|
|
|
22079
22190
|
function quarantineGrantsDb(dbPath) {
|
|
22080
22191
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
22081
22192
|
const quarantinePath = `${dbPath}.corrupt-${stamp}`;
|
|
22082
|
-
|
|
22083
|
-
for (const suffix of
|
|
22193
|
+
fs3.renameSync(dbPath, quarantinePath);
|
|
22194
|
+
for (const suffix of WAL_SIDECAR_SUFFIXES2) {
|
|
22084
22195
|
const sidecar = `${dbPath}${suffix}`;
|
|
22085
|
-
if (
|
|
22196
|
+
if (fs3.existsSync(sidecar)) {
|
|
22086
22197
|
try {
|
|
22087
|
-
|
|
22198
|
+
fs3.renameSync(sidecar, `${quarantinePath}${suffix}`);
|
|
22088
22199
|
} catch {}
|
|
22089
22200
|
}
|
|
22090
22201
|
}
|
|
@@ -22094,7 +22205,7 @@ function openAndMigrate(dbPath) {
|
|
|
22094
22205
|
const db = new Database(dbPath, { create: true });
|
|
22095
22206
|
try {
|
|
22096
22207
|
try {
|
|
22097
|
-
|
|
22208
|
+
fs3.chmodSync(dbPath, 384);
|
|
22098
22209
|
} catch {}
|
|
22099
22210
|
db.run("PRAGMA journal_mode=WAL");
|
|
22100
22211
|
db.run("PRAGMA busy_timeout=5000");
|
|
@@ -22110,8 +22221,9 @@ function openAndMigrate(dbPath) {
|
|
|
22110
22221
|
return db;
|
|
22111
22222
|
}
|
|
22112
22223
|
function openGrantsDb(dbPath = DEFAULT_GRANTS_DB_PATH, opener = openAndMigrate) {
|
|
22113
|
-
const dir =
|
|
22114
|
-
|
|
22224
|
+
const dir = path3.dirname(dbPath);
|
|
22225
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
22226
|
+
migrateLegacyGrantsDbLocation(dbPath);
|
|
22115
22227
|
try {
|
|
22116
22228
|
return opener(dbPath);
|
|
22117
22229
|
} catch (err) {
|
|
@@ -22462,7 +22574,7 @@ class VaultBroker {
|
|
|
22462
22574
|
if (process.platform !== "linux" && process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX !== "1") {
|
|
22463
22575
|
throw new Error(`vault-broker is Linux-only (running on ${process.platform}). ` + `The broker's ACL relies on cgroup-based systemd unit identification, ` + `which is not available on this platform. ` + `Use 'switchroom vault get --no-broker' for direct vault access. ` + `If you need to run the broker for development on this platform, ` + `set SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 — but understand that the ` + `broker will accept any same-user caller without per-cron ACL enforcement.`);
|
|
22464
22576
|
}
|
|
22465
|
-
this.socketPath =
|
|
22577
|
+
this.socketPath = resolve9(socketPath);
|
|
22466
22578
|
this.unlockSocketPath = unlockSocketFor(this.socketPath);
|
|
22467
22579
|
this.startedAt = Date.now();
|
|
22468
22580
|
if (this.testOpts._testConfig) {
|
|
@@ -22472,7 +22584,7 @@ class VaultBroker {
|
|
|
22472
22584
|
this.config = loadConfig2(configPath);
|
|
22473
22585
|
}
|
|
22474
22586
|
if (vaultPath) {
|
|
22475
|
-
this.vaultPath =
|
|
22587
|
+
this.vaultPath = resolve9(vaultPath);
|
|
22476
22588
|
} else {
|
|
22477
22589
|
this.vaultPath = resolvePath(this.config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
22478
22590
|
}
|
|
@@ -22484,13 +22596,13 @@ class VaultBroker {
|
|
|
22484
22596
|
this.passphrase = this.testOpts._testPassphrase;
|
|
22485
22597
|
}
|
|
22486
22598
|
process.umask(63);
|
|
22487
|
-
const parentDir =
|
|
22488
|
-
|
|
22599
|
+
const parentDir = dirname6(this.socketPath);
|
|
22600
|
+
mkdirSync7(parentDir, { recursive: true, mode: 448 });
|
|
22489
22601
|
try {
|
|
22490
|
-
|
|
22602
|
+
chmodSync5(parentDir, 448);
|
|
22491
22603
|
} catch {}
|
|
22492
22604
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
22493
|
-
if (
|
|
22605
|
+
if (existsSync12(p)) {
|
|
22494
22606
|
try {
|
|
22495
22607
|
unlinkSync6(p);
|
|
22496
22608
|
} catch {}
|
|
@@ -22526,7 +22638,7 @@ class VaultBroker {
|
|
|
22526
22638
|
try {
|
|
22527
22639
|
if (ready) {
|
|
22528
22640
|
writeFileSync3(p, "", { mode: 384 });
|
|
22529
|
-
} else if (
|
|
22641
|
+
} else if (existsSync12(p)) {
|
|
22530
22642
|
unlinkSync6(p);
|
|
22531
22643
|
}
|
|
22532
22644
|
} catch {}
|
|
@@ -22559,7 +22671,7 @@ class VaultBroker {
|
|
|
22559
22671
|
try {
|
|
22560
22672
|
entry.server.close();
|
|
22561
22673
|
} catch {}
|
|
22562
|
-
if (
|
|
22674
|
+
if (existsSync12(sockPath)) {
|
|
22563
22675
|
try {
|
|
22564
22676
|
unlinkSync6(sockPath);
|
|
22565
22677
|
} catch {}
|
|
@@ -22567,7 +22679,7 @@ class VaultBroker {
|
|
|
22567
22679
|
}
|
|
22568
22680
|
this.agentServers.clear();
|
|
22569
22681
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
22570
|
-
if (p &&
|
|
22682
|
+
if (p && existsSync12(p)) {
|
|
22571
22683
|
try {
|
|
22572
22684
|
unlinkSync6(p);
|
|
22573
22685
|
} catch {}
|
|
@@ -22575,7 +22687,7 @@ class VaultBroker {
|
|
|
22575
22687
|
}
|
|
22576
22688
|
try {
|
|
22577
22689
|
const pidPath = resolvePath(PID_FILE_DEFAULT);
|
|
22578
|
-
if (
|
|
22690
|
+
if (existsSync12(pidPath))
|
|
22579
22691
|
unlinkSync6(pidPath);
|
|
22580
22692
|
} catch {}
|
|
22581
22693
|
}
|
|
@@ -22593,7 +22705,7 @@ class VaultBroker {
|
|
|
22593
22705
|
return this.config;
|
|
22594
22706
|
}
|
|
22595
22707
|
bindAgentSocket(socketPath) {
|
|
22596
|
-
const abs =
|
|
22708
|
+
const abs = resolve9(socketPath);
|
|
22597
22709
|
const agentName = socketPathToAgent(abs);
|
|
22598
22710
|
if (agentName === null) {
|
|
22599
22711
|
return Promise.reject(new Error(`bindAgentSocket: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/<agent>.sock shape — refusing to bind without ` + `a verifiable agent identity`));
|
|
@@ -22601,16 +22713,16 @@ class VaultBroker {
|
|
|
22601
22713
|
return new Promise((resolveP, rejectP) => {
|
|
22602
22714
|
if (abs.endsWith("/sock")) {
|
|
22603
22715
|
const dir = abs.slice(0, -"/sock".length);
|
|
22604
|
-
if (
|
|
22716
|
+
if (existsSync12(dir)) {
|
|
22605
22717
|
try {
|
|
22606
22718
|
chownSync(dir, 0, 0);
|
|
22607
22719
|
} catch {}
|
|
22608
22720
|
try {
|
|
22609
|
-
|
|
22721
|
+
chmodSync5(dir, 448);
|
|
22610
22722
|
} catch {}
|
|
22611
22723
|
}
|
|
22612
22724
|
}
|
|
22613
|
-
if (
|
|
22725
|
+
if (existsSync12(abs)) {
|
|
22614
22726
|
try {
|
|
22615
22727
|
unlinkSync6(abs);
|
|
22616
22728
|
} catch (err) {
|
|
@@ -22625,7 +22737,7 @@ class VaultBroker {
|
|
|
22625
22737
|
server.on("error", (err) => rejectP(err));
|
|
22626
22738
|
server.listen(abs, () => {
|
|
22627
22739
|
try {
|
|
22628
|
-
|
|
22740
|
+
chmodSync5(abs, 432);
|
|
22629
22741
|
} catch {}
|
|
22630
22742
|
let agentUid = null;
|
|
22631
22743
|
try {
|
|
@@ -22642,7 +22754,7 @@ class VaultBroker {
|
|
|
22642
22754
|
} catch {}
|
|
22643
22755
|
this.agentServers.set(abs, { server, agentName });
|
|
22644
22756
|
const unlockAbs = unlockSocketFor(abs);
|
|
22645
|
-
if (
|
|
22757
|
+
if (existsSync12(unlockAbs)) {
|
|
22646
22758
|
try {
|
|
22647
22759
|
unlinkSync6(unlockAbs);
|
|
22648
22760
|
} catch {}
|
|
@@ -22659,7 +22771,7 @@ class VaultBroker {
|
|
|
22659
22771
|
});
|
|
22660
22772
|
unlockServer.listen(unlockAbs, () => {
|
|
22661
22773
|
try {
|
|
22662
|
-
|
|
22774
|
+
chmodSync5(unlockAbs, 432);
|
|
22663
22775
|
} catch {}
|
|
22664
22776
|
if (agentUid !== null) {
|
|
22665
22777
|
try {
|
|
@@ -22673,7 +22785,7 @@ class VaultBroker {
|
|
|
22673
22785
|
});
|
|
22674
22786
|
}
|
|
22675
22787
|
_bindDataSocket() {
|
|
22676
|
-
return new Promise((
|
|
22788
|
+
return new Promise((resolve10, reject) => {
|
|
22677
22789
|
const server = net.createServer((socket) => {
|
|
22678
22790
|
this._handleDataConnection(socket);
|
|
22679
22791
|
});
|
|
@@ -22682,10 +22794,10 @@ class VaultBroker {
|
|
|
22682
22794
|
});
|
|
22683
22795
|
server.listen(this.socketPath, () => {
|
|
22684
22796
|
try {
|
|
22685
|
-
|
|
22797
|
+
chmodSync5(this.socketPath, 384);
|
|
22686
22798
|
} catch {}
|
|
22687
22799
|
this.server = server;
|
|
22688
|
-
|
|
22800
|
+
resolve10();
|
|
22689
22801
|
});
|
|
22690
22802
|
});
|
|
22691
22803
|
}
|
|
@@ -22697,12 +22809,12 @@ class VaultBroker {
|
|
|
22697
22809
|
if (!Number.isFinite(uid) || uid <= 0)
|
|
22698
22810
|
return;
|
|
22699
22811
|
try {
|
|
22700
|
-
if (
|
|
22812
|
+
if (existsSync12(this.vaultPath))
|
|
22701
22813
|
chownSync(this.vaultPath, uid, uid);
|
|
22702
22814
|
} catch {}
|
|
22703
22815
|
}
|
|
22704
22816
|
bindOperatorListener(socketPath, operatorUid) {
|
|
22705
|
-
const abs =
|
|
22817
|
+
const abs = resolve9(socketPath);
|
|
22706
22818
|
const identity = socketPathToIdentity(abs);
|
|
22707
22819
|
if (identity?.kind !== "operator") {
|
|
22708
22820
|
return Promise.reject(new Error(`bindOperatorListener: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/operator/sock shape — refusing to bind`));
|
|
@@ -22710,17 +22822,17 @@ class VaultBroker {
|
|
|
22710
22822
|
const unlockAbs = unlockSocketFor(abs);
|
|
22711
22823
|
if (abs.endsWith("/sock")) {
|
|
22712
22824
|
const dir = abs.slice(0, -"/sock".length);
|
|
22713
|
-
if (
|
|
22825
|
+
if (existsSync12(dir)) {
|
|
22714
22826
|
try {
|
|
22715
22827
|
chownSync(dir, 0, 0);
|
|
22716
22828
|
} catch {}
|
|
22717
22829
|
try {
|
|
22718
|
-
|
|
22830
|
+
chmodSync5(dir, 448);
|
|
22719
22831
|
} catch {}
|
|
22720
22832
|
}
|
|
22721
22833
|
}
|
|
22722
22834
|
for (const p of [abs, unlockAbs]) {
|
|
22723
|
-
if (
|
|
22835
|
+
if (existsSync12(p)) {
|
|
22724
22836
|
try {
|
|
22725
22837
|
unlinkSync6(p);
|
|
22726
22838
|
} catch {}
|
|
@@ -22733,7 +22845,7 @@ class VaultBroker {
|
|
|
22733
22845
|
dataServer.on("error", (err) => rejectP(err));
|
|
22734
22846
|
dataServer.listen(abs, () => {
|
|
22735
22847
|
try {
|
|
22736
|
-
|
|
22848
|
+
chmodSync5(abs, 384);
|
|
22737
22849
|
} catch {}
|
|
22738
22850
|
try {
|
|
22739
22851
|
chownSync(abs, operatorUid, operatorUid);
|
|
@@ -22744,7 +22856,7 @@ class VaultBroker {
|
|
|
22744
22856
|
unlockServer.on("error", (err) => rejectP(err));
|
|
22745
22857
|
unlockServer.listen(unlockAbs, () => {
|
|
22746
22858
|
try {
|
|
22747
|
-
|
|
22859
|
+
chmodSync5(unlockAbs, 384);
|
|
22748
22860
|
} catch {}
|
|
22749
22861
|
try {
|
|
22750
22862
|
chownSync(unlockAbs, operatorUid, operatorUid);
|
|
@@ -22755,7 +22867,7 @@ class VaultBroker {
|
|
|
22755
22867
|
chownSync(dir, operatorUid, operatorUid);
|
|
22756
22868
|
} catch {}
|
|
22757
22869
|
try {
|
|
22758
|
-
|
|
22870
|
+
chmodSync5(dir, 448);
|
|
22759
22871
|
} catch {}
|
|
22760
22872
|
}
|
|
22761
22873
|
this.agentServers.set(abs, { server: dataServer, agentName: "__operator__" });
|
|
@@ -22766,7 +22878,7 @@ class VaultBroker {
|
|
|
22766
22878
|
});
|
|
22767
22879
|
}
|
|
22768
22880
|
_bindUnlockSocket() {
|
|
22769
|
-
return new Promise((
|
|
22881
|
+
return new Promise((resolve10, reject) => {
|
|
22770
22882
|
const server = net.createServer((socket) => {
|
|
22771
22883
|
this._handleUnlockConnection(socket);
|
|
22772
22884
|
});
|
|
@@ -22775,10 +22887,10 @@ class VaultBroker {
|
|
|
22775
22887
|
});
|
|
22776
22888
|
server.listen(this.unlockSocketPath, () => {
|
|
22777
22889
|
try {
|
|
22778
|
-
|
|
22890
|
+
chmodSync5(this.unlockSocketPath, 384);
|
|
22779
22891
|
} catch {}
|
|
22780
22892
|
this.unlockServer = server;
|
|
22781
|
-
|
|
22893
|
+
resolve10();
|
|
22782
22894
|
});
|
|
22783
22895
|
});
|
|
22784
22896
|
}
|
|
@@ -23617,13 +23729,13 @@ class VaultBroker {
|
|
|
23617
23729
|
return;
|
|
23618
23730
|
}
|
|
23619
23731
|
try {
|
|
23620
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23621
|
-
const tokenDir =
|
|
23622
|
-
|
|
23623
|
-
const tokenPath =
|
|
23732
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
|
|
23733
|
+
const tokenDir = path4.join(agentsDir, agent);
|
|
23734
|
+
mkdirSync7(tokenDir, { recursive: true });
|
|
23735
|
+
const tokenPath = path4.join(tokenDir, ".vault-token");
|
|
23624
23736
|
const tmpPath = `${tokenPath}.tmp.${process.pid}`;
|
|
23625
23737
|
writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
|
|
23626
|
-
|
|
23738
|
+
renameSync8(tmpPath, tokenPath);
|
|
23627
23739
|
try {
|
|
23628
23740
|
const uid = allocateAgentUid(agent);
|
|
23629
23741
|
chownSync(tokenPath, uid, uid);
|
|
@@ -23682,9 +23794,9 @@ class VaultBroker {
|
|
|
23682
23794
|
try {
|
|
23683
23795
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
23684
23796
|
if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
|
|
23685
|
-
const agentsDir = this.config ? resolveAgentsDir(this.config) :
|
|
23686
|
-
const tokenPath =
|
|
23687
|
-
if (
|
|
23797
|
+
const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
|
|
23798
|
+
const tokenPath = path4.join(agentsDir, row.agent_slug, ".vault-token");
|
|
23799
|
+
if (existsSync12(tokenPath)) {
|
|
23688
23800
|
try {
|
|
23689
23801
|
unlinkSync6(tokenPath);
|
|
23690
23802
|
} catch {}
|
|
@@ -23922,7 +24034,7 @@ class VaultBroker {
|
|
|
23922
24034
|
const envPath = process.env.SWITCHROOM_VAULT_BROKER_AUTO_UNLOCK_PATH;
|
|
23923
24035
|
const configuredPath = (envPath && envPath.length > 0 ? envPath : undefined) ?? this.config?.vault?.broker?.autoUnlockCredentialPath ?? DEFAULT_AUTO_UNLOCK_PATH;
|
|
23924
24036
|
const filePath = resolvePath(configuredPath);
|
|
23925
|
-
if (!
|
|
24037
|
+
if (!existsSync12(filePath))
|
|
23926
24038
|
return false;
|
|
23927
24039
|
try {
|
|
23928
24040
|
if (statSync6(filePath).size === 0)
|
|
@@ -24003,15 +24115,15 @@ class VaultBroker {
|
|
|
24003
24115
|
}
|
|
24004
24116
|
}
|
|
24005
24117
|
function detectVaultLayoutDrift(vaultPath) {
|
|
24006
|
-
const dir =
|
|
24118
|
+
const dir = dirname6(vaultPath);
|
|
24007
24119
|
if (basename4(dir) !== "vault")
|
|
24008
24120
|
return;
|
|
24009
24121
|
if (basename4(vaultPath) !== "vault.enc")
|
|
24010
24122
|
return;
|
|
24011
|
-
const switchroomDir =
|
|
24123
|
+
const switchroomDir = dirname6(dir);
|
|
24012
24124
|
if (basename4(switchroomDir) !== ".switchroom")
|
|
24013
24125
|
return;
|
|
24014
|
-
const home2 =
|
|
24126
|
+
const home2 = dirname6(switchroomDir);
|
|
24015
24127
|
const result = inspectVaultLayout(home2);
|
|
24016
24128
|
if (result.kind === "divergent") {
|
|
24017
24129
|
throw new VaultError(`Vault layout divergence detected at boot: ${result.details.oldPath} and ${result.details.newPath} are both regular files with different content. An older switchroom CLI may have written to the legacy path after migration ran. Run \`switchroom apply\` from the host to surface the recovery recipe (state E refusal with literal \`mv\` commands). See docs/operators/state-e-recovery.md.`);
|
|
@@ -24037,7 +24149,7 @@ async function main() {
|
|
|
24037
24149
|
const vaultPath = process.env.SWITCHROOM_VAULT_PATH;
|
|
24038
24150
|
let perAgentTargets = [];
|
|
24039
24151
|
try {
|
|
24040
|
-
if (
|
|
24152
|
+
if (existsSync12(perAgentDir)) {
|
|
24041
24153
|
const entries = readdirSync4(perAgentDir, { withFileTypes: true });
|
|
24042
24154
|
const flat = [];
|
|
24043
24155
|
const subdirs = [];
|
|
@@ -24045,11 +24157,11 @@ async function main() {
|
|
|
24045
24157
|
if (e.name.startsWith("."))
|
|
24046
24158
|
continue;
|
|
24047
24159
|
if ((e.isFile() || e.isSocket()) && e.name.endsWith(".sock")) {
|
|
24048
|
-
flat.push(
|
|
24160
|
+
flat.push(resolve9(perAgentDir, e.name));
|
|
24049
24161
|
continue;
|
|
24050
24162
|
}
|
|
24051
24163
|
if (e.isDirectory()) {
|
|
24052
|
-
const candidate =
|
|
24164
|
+
const candidate = resolve9(perAgentDir, e.name, "sock");
|
|
24053
24165
|
if (socketPathToAgent(candidate) !== null) {
|
|
24054
24166
|
subdirs.push(candidate);
|
|
24055
24167
|
}
|
|
@@ -24092,7 +24204,7 @@ async function main() {
|
|
|
24092
24204
|
}
|
|
24093
24205
|
const operatorUidStr = process.env.SWITCHROOM_BROKER_OPERATOR_UID;
|
|
24094
24206
|
const operatorDir = "/run/switchroom/broker/operator";
|
|
24095
|
-
if (operatorUidStr !== undefined &&
|
|
24207
|
+
if (operatorUidStr !== undefined && existsSync12(operatorDir)) {
|
|
24096
24208
|
const operatorUid = parseInt(operatorUidStr, 10);
|
|
24097
24209
|
if (!Number.isFinite(operatorUid) || operatorUid <= 0) {
|
|
24098
24210
|
process.stderr.write(`[vault-broker] SWITCHROOM_BROKER_OPERATOR_UID='${operatorUidStr}' is not a positive integer; skipping operator listener
|