switchroom 0.7.15 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -59
- package/bin/run-hook.sh +27 -11
- package/bin/timezone-hook.sh +9 -7
- package/dist/agent-scheduler/index.js +410 -133
- package/dist/auth-broker/index.js +13932 -0
- package/dist/cli/switchroom.js +26937 -5601
- package/dist/host-control/main.js +12702 -0
- package/dist/vault/approvals/kernel-server.js +467 -184
- package/dist/vault/broker/server.js +1430 -724
- package/examples/minimal.yaml +63 -0
- package/examples/personal-google-workspace-mcp/.env.example +34 -0
- package/examples/personal-google-workspace-mcp/README.md +194 -0
- package/examples/personal-google-workspace-mcp/compose.yaml +66 -0
- package/examples/switchroom.yaml +220 -0
- package/package.json +7 -4
- package/profiles/_base/settings.json.hbs +20 -5
- package/profiles/_base/start.sh.hbs +16 -3
- package/profiles/_shared/agent-self-service.md.hbs +126 -0
- package/profiles/_shared/telegram-style.md.hbs +20 -90
- package/profiles/_shared/vault-protocol.md.hbs +68 -0
- package/profiles/default/CLAUDE.md +50 -96
- package/profiles/default/CLAUDE.md.hbs +36 -6
- package/profiles/default/workspace/SOUL.md.hbs +12 -5
- package/skills/buildkite-agent-infrastructure/SKILL.md +30 -11
- package/skills/buildkite-agent-runtime/SKILL.md +44 -11
- package/skills/buildkite-api/SKILL.md +31 -8
- package/skills/buildkite-cli/SKILL.md +27 -9
- package/skills/buildkite-migration/SKILL.md +22 -9
- package/skills/buildkite-pipelines/SKILL.md +26 -9
- package/skills/buildkite-secure-delivery/SKILL.md +23 -9
- package/skills/buildkite-test-engine/SKILL.md +25 -8
- package/skills/docx/SKILL.md +1 -1
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/file-bug/SKILL.md +34 -6
- package/skills/humanizer/SKILL.md +15 -0
- package/skills/humanizer-calibrate/SKILL.md +7 -1
- package/skills/mcp-builder/SKILL.md +1 -1
- package/skills/pdf/SKILL.md +1 -1
- package/skills/pptx/SKILL.md +1 -1
- package/skills/skill-creator/SKILL.md +21 -1
- package/skills/skill-creator/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/generate_report.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/improve_description.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_eval.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/run_loop.cpython-313.pyc +0 -0
- package/skills/skill-creator/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/skills/switchroom-cli/SKILL.md +63 -64
- package/skills/switchroom-health/SKILL.md +23 -10
- package/skills/switchroom-install/SKILL.md +3 -3
- package/skills/switchroom-manage/SKILL.md +26 -19
- package/skills/switchroom-runtime/SKILL.md +191 -0
- package/skills/switchroom-status/SKILL.md +27 -2
- package/skills/telegram-test-harness/SKILL.md +3 -0
- package/skills/token-helpers/SKILL.md +24 -1
- package/skills/webapp-testing/SKILL.md +31 -1
- package/skills/xlsx/SKILL.md +1 -1
- package/telegram-plugin/admin-commands/index.ts +7 -5
- package/telegram-plugin/analytics-posthog.ts +191 -0
- package/telegram-plugin/bridge/bridge.ts +69 -0
- package/telegram-plugin/bridge/ipc-client.ts +4 -1
- package/telegram-plugin/dist/bridge/bridge.js +194 -119
- package/telegram-plugin/dist/gateway/gateway.js +23611 -19671
- package/telegram-plugin/dist/server.js +245 -189
- package/telegram-plugin/first-paint.ts +3 -24
- package/telegram-plugin/gateway/auth-add-flow.ts +326 -0
- package/telegram-plugin/gateway/auth-broker-client.ts +75 -0
- package/telegram-plugin/gateway/auth-command.ts +794 -0
- package/telegram-plugin/gateway/auth-line.ts +123 -0
- package/telegram-plugin/gateway/boot-card.ts +169 -40
- package/telegram-plugin/gateway/boot-issue-cache.ts +308 -0
- package/telegram-plugin/gateway/boot-probes.ts +166 -123
- package/telegram-plugin/gateway/boot-reason.ts +41 -7
- package/telegram-plugin/gateway/boot-version.ts +66 -0
- package/telegram-plugin/gateway/gateway.ts +3499 -1885
- package/telegram-plugin/gateway/hostd-dispatch.ts +117 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +18 -0
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +106 -0
- package/telegram-plugin/gateway/quarantine.ts +69 -0
- package/telegram-plugin/gateway/quota-cache.ts +9 -4
- package/telegram-plugin/gateway/reaction-trigger.ts +401 -0
- package/telegram-plugin/gateway/recent-denials.test.ts +103 -0
- package/telegram-plugin/gateway/recent-denials.ts +77 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +109 -31
- package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +125 -0
- package/telegram-plugin/history.ts +91 -0
- package/telegram-plugin/hooks/hooks.json +10 -0
- package/telegram-plugin/hooks/sandbox-hint-posttool.mjs +130 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +19 -2
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +22 -2
- package/telegram-plugin/hooks/tool-label-pretool.mjs +11 -0
- package/telegram-plugin/hooks/wedge-detect-posttool.mjs +303 -0
- package/telegram-plugin/inbound-classifier.ts +50 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +136 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/package.json +4 -2
- package/telegram-plugin/permission-rule.ts +51 -0
- package/telegram-plugin/permission-title.ts +56 -0
- package/telegram-plugin/quota-check.ts +19 -41
- package/telegram-plugin/registry/reaper.ts +223 -0
- package/telegram-plugin/retry-api-call.ts +80 -0
- package/telegram-plugin/runtime-metrics.ts +177 -0
- package/telegram-plugin/scripts/build.mjs +0 -1
- package/telegram-plugin/secret-detect/index.ts +24 -0
- package/telegram-plugin/secret-detect/vault-error.test.ts +64 -12
- package/telegram-plugin/secret-detect/vault-error.ts +78 -11
- package/telegram-plugin/secret-detect/vault-write.ts +14 -2
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/session-tail.ts +6 -1
- package/telegram-plugin/shared/bot-runtime.ts +5 -4
- package/telegram-plugin/silence-poke.ts +420 -0
- package/telegram-plugin/silent-end.ts +174 -0
- package/telegram-plugin/stream-controller.ts +13 -0
- package/telegram-plugin/stream-reply-handler.ts +7 -0
- package/telegram-plugin/subagent-watcher.ts +213 -4
- package/telegram-plugin/tests/auth-add-flow.test.ts +559 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +8 -4
- package/telegram-plugin/tests/auth-command-vernacular.test.ts +531 -0
- package/telegram-plugin/tests/boot-card-issue-dedup.test.ts +247 -0
- package/telegram-plugin/tests/boot-card-reason-to-render.test.ts +182 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +65 -2
- package/telegram-plugin/tests/boot-card-render.test.ts +146 -0
- package/telegram-plugin/tests/boot-card-silent-on-operator.test.ts +103 -0
- package/telegram-plugin/tests/boot-probes.test.ts +216 -10
- package/telegram-plugin/tests/boot-version-string.test.ts +0 -0
- package/telegram-plugin/tests/finalize-callback.test.ts +190 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +26 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +12 -3
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +104 -0
- package/telegram-plugin/tests/history-reaper.test.ts +378 -0
- package/telegram-plugin/tests/hostd-dispatch.test.ts +129 -0
- package/telegram-plugin/tests/inbound-classifier.test.ts +76 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +267 -0
- package/telegram-plugin/tests/issues-card.test.ts +49 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +132 -0
- package/telegram-plugin/tests/permission-rule.test.ts +80 -1
- package/telegram-plugin/tests/permission-title.test.ts +31 -0
- package/telegram-plugin/tests/quota-check.test.ts +5 -35
- package/telegram-plugin/tests/races.test.ts +179 -0
- package/telegram-plugin/tests/reaction-trigger-flow.test.ts +353 -0
- package/telegram-plugin/tests/reaction-trigger.test.ts +397 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +152 -1
- package/telegram-plugin/tests/runtime-metrics.test.ts +145 -0
- package/telegram-plugin/tests/sandbox-hint-posttool.test.ts +155 -0
- package/telegram-plugin/tests/secret-detect-delete-must-surface-failures.test.ts +133 -0
- package/telegram-plugin/tests/secret-detect-false-positives.test.ts +137 -0
- package/telegram-plugin/tests/silence-poke.test.ts +493 -0
- package/telegram-plugin/tests/silent-end.test.ts +206 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +107 -0
- package/telegram-plugin/tests/subagent-watcher-env-thresholds.test.ts +224 -0
- package/telegram-plugin/tests/subagent-watcher-stall-terminal.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +263 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +81 -0
- package/telegram-plugin/tests/vault-approval-posture.test.ts +256 -0
- package/telegram-plugin/tests/vault-grant-auto-resume.test.ts +73 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +226 -0
- package/telegram-plugin/tests/vault-grant-union.test.ts +130 -0
- package/telegram-plugin/tests/vault-key-regex-allows-slash.test.ts +140 -0
- package/telegram-plugin/tests/vault-posture-quarantine.test.ts +104 -0
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +114 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +106 -0
- package/telegram-plugin/turn-signal-tracker.ts +100 -24
- package/telegram-plugin/uat/SETUP.md +210 -35
- package/telegram-plugin/uat/assertions.ts +264 -37
- package/telegram-plugin/uat/driver-info.ts +57 -0
- package/telegram-plugin/uat/driver.ts +590 -51
- package/telegram-plugin/uat/harness.ts +140 -94
- package/telegram-plugin/uat/load-env.test.ts +72 -0
- package/telegram-plugin/uat/load-env.ts +48 -0
- package/telegram-plugin/uat/login.ts +96 -53
- package/telegram-plugin/uat/runners/agent-self-sufficiency.ts +457 -0
- package/telegram-plugin/uat/runners/paraphrases.ts +231 -0
- package/telegram-plugin/uat/runners/report.ts +150 -0
- package/telegram-plugin/uat/runners/run-agent-self-sufficiency.sh +50 -0
- package/telegram-plugin/uat/runners/scorer.test.ts +196 -0
- package/telegram-plugin/uat/runners/scorer.ts +106 -0
- package/telegram-plugin/uat/runners/skill-coverage.test.ts +100 -0
- package/telegram-plugin/uat/runners/skill-coverage.ts +620 -0
- package/telegram-plugin/uat/scenarios/ask-user-button-tap-dm.test.ts +141 -0
- package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/fuzz-extended-dm.test.ts +255 -0
- package/telegram-plugin/uat/scenarios/fuzz-human-style-dm.test.ts +275 -0
- package/telegram-plugin/uat/scenarios/fuzz-random-prompts-dm.test.ts +146 -0
- package/telegram-plugin/uat/scenarios/fuzz-status-ask-dm.test.ts +486 -0
- package/telegram-plugin/uat/scenarios/jtbd-interrupt-marker-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-rapid-followup-dm.test.ts +100 -0
- package/telegram-plugin/uat/scenarios/jtbd-soft-commit-dm.test.ts +67 -0
- package/telegram-plugin/uat/scenarios/jtbd-status-query-dm.test.ts +49 -0
- package/telegram-plugin/uat/scenarios/location-inbound-dm.test.ts +65 -0
- package/telegram-plugin/uat/scenarios/midturn-silent-dm.test.ts +175 -0
- package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +142 -0
- package/telegram-plugin/uat/scenarios/reactions-trigger-turn-dm.test.ts +96 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-deletes-original-dm.test.ts +123 -0
- package/telegram-plugin/uat/scenarios/secret-redaction-no-false-positive-dm.test.ts +87 -0
- package/telegram-plugin/uat/scenarios/silence-poke-soft-dm.test.ts +155 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +95 -0
- package/telegram-plugin/uat/scenarios/smoke-dm-reply.test.ts +57 -0
- package/telegram-plugin/uat/scenarios/subagent-watcher-no-rerun-dm.test.ts +135 -0
- package/telegram-plugin/uat/scenarios/vault-approval-posture-telegram-id-dm.test.ts +191 -0
- package/telegram-plugin/uat/scenarios/vault-audit-allow-dm.test.ts +108 -0
- package/telegram-plugin/uat/scenarios/vault-grant-auto-resume-dm.test.ts +121 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-concurrent-dm.test.ts +161 -0
- package/telegram-plugin/uat/scenarios/vault-request-access-end-to-end-dm.test.ts +158 -0
- package/telegram-plugin/uat/scenarios/voice-inbound-dm.test.ts +65 -0
- package/telegram-plugin/vault-approval-posture.ts +42 -0
- package/telegram-plugin/welcome-text.ts +1 -0
- package/telegram-plugin/active-pins-sweep.ts +0 -204
- package/telegram-plugin/active-pins.ts +0 -146
- package/telegram-plugin/auth-dashboard.ts +0 -1104
- package/telegram-plugin/auth-slot-parser.ts +0 -497
- package/telegram-plugin/card-event-log.ts +0 -138
- package/telegram-plugin/dist/foreman/foreman.js +0 -31106
- package/telegram-plugin/docs/multi-agent-card-design.md +0 -847
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +0 -144
- package/telegram-plugin/foreman/foreman-create-flow.ts +0 -202
- package/telegram-plugin/foreman/foreman-handlers.ts +0 -493
- package/telegram-plugin/foreman/foreman.ts +0 -1165
- package/telegram-plugin/foreman/setup-flow.ts +0 -345
- package/telegram-plugin/foreman/setup-state.ts +0 -239
- package/telegram-plugin/foreman/state.ts +0 -203
- package/telegram-plugin/pin-event-log.ts +0 -76
- package/telegram-plugin/progress-card-driver.ts +0 -2886
- package/telegram-plugin/progress-card-pin-manager.ts +0 -589
- package/telegram-plugin/progress-card-pin-watchdog.ts +0 -98
- package/telegram-plugin/progress-card.ts +0 -1409
- package/telegram-plugin/tests/HARNESS.md +0 -340
- package/telegram-plugin/tests/_progress-card-harness.ts +0 -109
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +0 -211
- package/telegram-plugin/tests/active-pins-sweep.test.ts +0 -309
- package/telegram-plugin/tests/active-pins.test.ts +0 -187
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +0 -118
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +0 -260
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +0 -140
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +0 -559
- package/telegram-plugin/tests/auth-dashboard.test.ts +0 -1045
- package/telegram-plugin/tests/auth-slot-commands.test.ts +0 -640
- package/telegram-plugin/tests/bg-agent-progress-card-757.test.ts +0 -201
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +0 -137
- package/telegram-plugin/tests/card-event-log.test.ts +0 -145
- package/telegram-plugin/tests/first-paint.test.ts +0 -257
- package/telegram-plugin/tests/foreman-create-flow.test.ts +0 -359
- package/telegram-plugin/tests/foreman-handlers.test.ts +0 -347
- package/telegram-plugin/tests/foreman-state.test.ts +0 -164
- package/telegram-plugin/tests/foreman-write-ops.test.ts +0 -214
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +0 -243
- package/telegram-plugin/tests/pin-event-log.test.ts +0 -124
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +0 -73
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +0 -272
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +0 -258
- package/telegram-plugin/tests/progress-card-delay-842.test.ts +0 -160
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +0 -81
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +0 -80
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +0 -215
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +0 -123
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +0 -76
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +0 -62
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +0 -84
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +0 -139
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +0 -773
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +0 -66
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +0 -64
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +0 -190
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +0 -146
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +0 -123
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +0 -82
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +0 -114
- package/telegram-plugin/tests/real-gateway-harness.ts +0 -699
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +0 -313
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +0 -299
- package/telegram-plugin/tests/real-gateway-spec.test.ts +0 -487
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +0 -101
- package/telegram-plugin/tests/setup-flow.test.ts +0 -510
- package/telegram-plugin/tests/setup-state.test.ts +0 -146
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +0 -116
- package/telegram-plugin/tests/turn-end-regressions.test.ts +0 -489
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +0 -218
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +0 -131
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +0 -120
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +0 -116
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +0 -87
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +0 -211
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +0 -62
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +0 -78
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +0 -110
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +0 -128
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +0 -58
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +0 -133
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +0 -155
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +0 -117
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +0 -187
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +0 -149
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +0 -101
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +0 -114
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +0 -105
- package/telegram-plugin/tests/waiting-ux-harness.ts +0 -381
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +0 -233
- package/telegram-plugin/turn-flush-prose-recovery.ts +0 -40
- package/telegram-plugin/two-zone-card.ts +0 -269
- package/telegram-plugin/uat/scenarios/smoke-clerk-reply.test.ts +0 -61
|
@@ -17,7 +17,7 @@ var __export = (target, all) => {
|
|
|
17
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// node_modules/yaml/dist/nodes/identity.js
|
|
20
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
21
21
|
var require_identity = __commonJS((exports) => {
|
|
22
22
|
var ALIAS = Symbol.for("yaml.alias");
|
|
23
23
|
var DOC = Symbol.for("yaml.document");
|
|
@@ -71,7 +71,7 @@ var require_identity = __commonJS((exports) => {
|
|
|
71
71
|
exports.isSeq = isSeq;
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
// node_modules/yaml/dist/visit.js
|
|
74
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/visit.js
|
|
75
75
|
var require_visit = __commonJS((exports) => {
|
|
76
76
|
var identity = require_identity();
|
|
77
77
|
var BREAK = Symbol("break visit");
|
|
@@ -226,7 +226,7 @@ var require_visit = __commonJS((exports) => {
|
|
|
226
226
|
exports.visitAsync = visitAsync;
|
|
227
227
|
});
|
|
228
228
|
|
|
229
|
-
// node_modules/yaml/dist/doc/directives.js
|
|
229
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/directives.js
|
|
230
230
|
var require_directives = __commonJS((exports) => {
|
|
231
231
|
var identity = require_identity();
|
|
232
232
|
var visit = require_visit();
|
|
@@ -378,7 +378,7 @@ var require_directives = __commonJS((exports) => {
|
|
|
378
378
|
exports.Directives = Directives;
|
|
379
379
|
});
|
|
380
380
|
|
|
381
|
-
// node_modules/yaml/dist/doc/anchors.js
|
|
381
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/anchors.js
|
|
382
382
|
var require_anchors = __commonJS((exports) => {
|
|
383
383
|
var identity = require_identity();
|
|
384
384
|
var visit = require_visit();
|
|
@@ -440,7 +440,7 @@ var require_anchors = __commonJS((exports) => {
|
|
|
440
440
|
exports.findNewAnchor = findNewAnchor;
|
|
441
441
|
});
|
|
442
442
|
|
|
443
|
-
// node_modules/yaml/dist/doc/applyReviver.js
|
|
443
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/applyReviver.js
|
|
444
444
|
var require_applyReviver = __commonJS((exports) => {
|
|
445
445
|
function applyReviver(reviver, obj, key, val) {
|
|
446
446
|
if (val && typeof val === "object") {
|
|
@@ -487,7 +487,7 @@ var require_applyReviver = __commonJS((exports) => {
|
|
|
487
487
|
exports.applyReviver = applyReviver;
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
-
// node_modules/yaml/dist/nodes/toJS.js
|
|
490
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/toJS.js
|
|
491
491
|
var require_toJS = __commonJS((exports) => {
|
|
492
492
|
var identity = require_identity();
|
|
493
493
|
function toJS(value, arg, ctx) {
|
|
@@ -514,7 +514,7 @@ var require_toJS = __commonJS((exports) => {
|
|
|
514
514
|
exports.toJS = toJS;
|
|
515
515
|
});
|
|
516
516
|
|
|
517
|
-
// node_modules/yaml/dist/nodes/Node.js
|
|
517
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Node.js
|
|
518
518
|
var require_Node = __commonJS((exports) => {
|
|
519
519
|
var applyReviver = require_applyReviver();
|
|
520
520
|
var identity = require_identity();
|
|
@@ -551,7 +551,7 @@ var require_Node = __commonJS((exports) => {
|
|
|
551
551
|
exports.NodeBase = NodeBase;
|
|
552
552
|
});
|
|
553
553
|
|
|
554
|
-
// node_modules/yaml/dist/nodes/Alias.js
|
|
554
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Alias.js
|
|
555
555
|
var require_Alias = __commonJS((exports) => {
|
|
556
556
|
var anchors = require_anchors();
|
|
557
557
|
var visit = require_visit();
|
|
@@ -659,7 +659,7 @@ var require_Alias = __commonJS((exports) => {
|
|
|
659
659
|
exports.Alias = Alias;
|
|
660
660
|
});
|
|
661
661
|
|
|
662
|
-
// node_modules/yaml/dist/nodes/Scalar.js
|
|
662
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Scalar.js
|
|
663
663
|
var require_Scalar = __commonJS((exports) => {
|
|
664
664
|
var identity = require_identity();
|
|
665
665
|
var Node = require_Node();
|
|
@@ -687,7 +687,7 @@ var require_Scalar = __commonJS((exports) => {
|
|
|
687
687
|
exports.isScalarValue = isScalarValue;
|
|
688
688
|
});
|
|
689
689
|
|
|
690
|
-
// node_modules/yaml/dist/doc/createNode.js
|
|
690
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/createNode.js
|
|
691
691
|
var require_createNode = __commonJS((exports) => {
|
|
692
692
|
var Alias = require_Alias();
|
|
693
693
|
var identity = require_identity();
|
|
@@ -759,7 +759,7 @@ var require_createNode = __commonJS((exports) => {
|
|
|
759
759
|
exports.createNode = createNode;
|
|
760
760
|
});
|
|
761
761
|
|
|
762
|
-
// node_modules/yaml/dist/nodes/Collection.js
|
|
762
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Collection.js
|
|
763
763
|
var require_Collection = __commonJS((exports) => {
|
|
764
764
|
var createNode = require_createNode();
|
|
765
765
|
var identity = require_identity();
|
|
@@ -874,7 +874,7 @@ var require_Collection = __commonJS((exports) => {
|
|
|
874
874
|
exports.isEmptyPath = isEmptyPath;
|
|
875
875
|
});
|
|
876
876
|
|
|
877
|
-
// node_modules/yaml/dist/stringify/stringifyComment.js
|
|
877
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyComment.js
|
|
878
878
|
var require_stringifyComment = __commonJS((exports) => {
|
|
879
879
|
var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
|
|
880
880
|
function indentComment(comment, indent) {
|
|
@@ -891,7 +891,7 @@ var require_stringifyComment = __commonJS((exports) => {
|
|
|
891
891
|
exports.stringifyComment = stringifyComment;
|
|
892
892
|
});
|
|
893
893
|
|
|
894
|
-
// node_modules/yaml/dist/stringify/foldFlowLines.js
|
|
894
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/foldFlowLines.js
|
|
895
895
|
var require_foldFlowLines = __commonJS((exports) => {
|
|
896
896
|
var FOLD_FLOW = "flow";
|
|
897
897
|
var FOLD_BLOCK = "block";
|
|
@@ -1028,7 +1028,7 @@ ${indent}${text.slice(fold + 1, end2)}`;
|
|
|
1028
1028
|
exports.foldFlowLines = foldFlowLines;
|
|
1029
1029
|
});
|
|
1030
1030
|
|
|
1031
|
-
// node_modules/yaml/dist/stringify/stringifyString.js
|
|
1031
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyString.js
|
|
1032
1032
|
var require_stringifyString = __commonJS((exports) => {
|
|
1033
1033
|
var Scalar = require_Scalar();
|
|
1034
1034
|
var foldFlowLines = require_foldFlowLines();
|
|
@@ -1326,7 +1326,7 @@ ${indent}`);
|
|
|
1326
1326
|
exports.stringifyString = stringifyString;
|
|
1327
1327
|
});
|
|
1328
1328
|
|
|
1329
|
-
// node_modules/yaml/dist/stringify/stringify.js
|
|
1329
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringify.js
|
|
1330
1330
|
var require_stringify = __commonJS((exports) => {
|
|
1331
1331
|
var anchors = require_anchors();
|
|
1332
1332
|
var identity = require_identity();
|
|
@@ -1447,7 +1447,7 @@ ${ctx.indent}${str}`;
|
|
|
1447
1447
|
exports.stringify = stringify;
|
|
1448
1448
|
});
|
|
1449
1449
|
|
|
1450
|
-
// node_modules/yaml/dist/stringify/stringifyPair.js
|
|
1450
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyPair.js
|
|
1451
1451
|
var require_stringifyPair = __commonJS((exports) => {
|
|
1452
1452
|
var identity = require_identity();
|
|
1453
1453
|
var Scalar = require_Scalar();
|
|
@@ -1583,7 +1583,7 @@ ${ctx.indent}`;
|
|
|
1583
1583
|
exports.stringifyPair = stringifyPair;
|
|
1584
1584
|
});
|
|
1585
1585
|
|
|
1586
|
-
// node_modules/yaml/dist/log.js
|
|
1586
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/log.js
|
|
1587
1587
|
var require_log = __commonJS((exports) => {
|
|
1588
1588
|
var node_process = __require("process");
|
|
1589
1589
|
function debug(logLevel, ...messages) {
|
|
@@ -1602,7 +1602,7 @@ var require_log = __commonJS((exports) => {
|
|
|
1602
1602
|
exports.warn = warn;
|
|
1603
1603
|
});
|
|
1604
1604
|
|
|
1605
|
-
// node_modules/yaml/dist/schema/yaml-1.1/merge.js
|
|
1605
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/merge.js
|
|
1606
1606
|
var require_merge = __commonJS((exports) => {
|
|
1607
1607
|
var identity = require_identity();
|
|
1608
1608
|
var Scalar = require_Scalar();
|
|
@@ -1656,7 +1656,7 @@ var require_merge = __commonJS((exports) => {
|
|
|
1656
1656
|
exports.merge = merge;
|
|
1657
1657
|
});
|
|
1658
1658
|
|
|
1659
|
-
// node_modules/yaml/dist/nodes/addPairToJSMap.js
|
|
1659
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js
|
|
1660
1660
|
var require_addPairToJSMap = __commonJS((exports) => {
|
|
1661
1661
|
var log = require_log();
|
|
1662
1662
|
var merge = require_merge();
|
|
@@ -1717,7 +1717,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
|
|
|
1717
1717
|
exports.addPairToJSMap = addPairToJSMap;
|
|
1718
1718
|
});
|
|
1719
1719
|
|
|
1720
|
-
// node_modules/yaml/dist/nodes/Pair.js
|
|
1720
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Pair.js
|
|
1721
1721
|
var require_Pair = __commonJS((exports) => {
|
|
1722
1722
|
var createNode = require_createNode();
|
|
1723
1723
|
var stringifyPair = require_stringifyPair();
|
|
@@ -1755,7 +1755,7 @@ var require_Pair = __commonJS((exports) => {
|
|
|
1755
1755
|
exports.createPair = createPair;
|
|
1756
1756
|
});
|
|
1757
1757
|
|
|
1758
|
-
// node_modules/yaml/dist/stringify/stringifyCollection.js
|
|
1758
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyCollection.js
|
|
1759
1759
|
var require_stringifyCollection = __commonJS((exports) => {
|
|
1760
1760
|
var identity = require_identity();
|
|
1761
1761
|
var stringify = require_stringify();
|
|
@@ -1907,7 +1907,7 @@ ${indent}${end}`;
|
|
|
1907
1907
|
exports.stringifyCollection = stringifyCollection;
|
|
1908
1908
|
});
|
|
1909
1909
|
|
|
1910
|
-
// node_modules/yaml/dist/nodes/YAMLMap.js
|
|
1910
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLMap.js
|
|
1911
1911
|
var require_YAMLMap = __commonJS((exports) => {
|
|
1912
1912
|
var stringifyCollection = require_stringifyCollection();
|
|
1913
1913
|
var addPairToJSMap = require_addPairToJSMap();
|
|
@@ -2034,7 +2034,7 @@ var require_YAMLMap = __commonJS((exports) => {
|
|
|
2034
2034
|
exports.findPair = findPair;
|
|
2035
2035
|
});
|
|
2036
2036
|
|
|
2037
|
-
// node_modules/yaml/dist/schema/common/map.js
|
|
2037
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/map.js
|
|
2038
2038
|
var require_map = __commonJS((exports) => {
|
|
2039
2039
|
var identity = require_identity();
|
|
2040
2040
|
var YAMLMap = require_YAMLMap();
|
|
@@ -2053,7 +2053,7 @@ var require_map = __commonJS((exports) => {
|
|
|
2053
2053
|
exports.map = map;
|
|
2054
2054
|
});
|
|
2055
2055
|
|
|
2056
|
-
// node_modules/yaml/dist/nodes/YAMLSeq.js
|
|
2056
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLSeq.js
|
|
2057
2057
|
var require_YAMLSeq = __commonJS((exports) => {
|
|
2058
2058
|
var createNode = require_createNode();
|
|
2059
2059
|
var stringifyCollection = require_stringifyCollection();
|
|
@@ -2146,7 +2146,7 @@ var require_YAMLSeq = __commonJS((exports) => {
|
|
|
2146
2146
|
exports.YAMLSeq = YAMLSeq;
|
|
2147
2147
|
});
|
|
2148
2148
|
|
|
2149
|
-
// node_modules/yaml/dist/schema/common/seq.js
|
|
2149
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/seq.js
|
|
2150
2150
|
var require_seq = __commonJS((exports) => {
|
|
2151
2151
|
var identity = require_identity();
|
|
2152
2152
|
var YAMLSeq = require_YAMLSeq();
|
|
@@ -2165,7 +2165,7 @@ var require_seq = __commonJS((exports) => {
|
|
|
2165
2165
|
exports.seq = seq;
|
|
2166
2166
|
});
|
|
2167
2167
|
|
|
2168
|
-
// node_modules/yaml/dist/schema/common/string.js
|
|
2168
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/string.js
|
|
2169
2169
|
var require_string = __commonJS((exports) => {
|
|
2170
2170
|
var stringifyString = require_stringifyString();
|
|
2171
2171
|
var string = {
|
|
@@ -2181,7 +2181,7 @@ var require_string = __commonJS((exports) => {
|
|
|
2181
2181
|
exports.string = string;
|
|
2182
2182
|
});
|
|
2183
2183
|
|
|
2184
|
-
// node_modules/yaml/dist/schema/common/null.js
|
|
2184
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/null.js
|
|
2185
2185
|
var require_null = __commonJS((exports) => {
|
|
2186
2186
|
var Scalar = require_Scalar();
|
|
2187
2187
|
var nullTag = {
|
|
@@ -2196,7 +2196,7 @@ var require_null = __commonJS((exports) => {
|
|
|
2196
2196
|
exports.nullTag = nullTag;
|
|
2197
2197
|
});
|
|
2198
2198
|
|
|
2199
|
-
// node_modules/yaml/dist/schema/core/bool.js
|
|
2199
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/bool.js
|
|
2200
2200
|
var require_bool = __commonJS((exports) => {
|
|
2201
2201
|
var Scalar = require_Scalar();
|
|
2202
2202
|
var boolTag = {
|
|
@@ -2217,7 +2217,7 @@ var require_bool = __commonJS((exports) => {
|
|
|
2217
2217
|
exports.boolTag = boolTag;
|
|
2218
2218
|
});
|
|
2219
2219
|
|
|
2220
|
-
// node_modules/yaml/dist/stringify/stringifyNumber.js
|
|
2220
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyNumber.js
|
|
2221
2221
|
var require_stringifyNumber = __commonJS((exports) => {
|
|
2222
2222
|
function stringifyNumber({ format, minFractionDigits, tag, value }) {
|
|
2223
2223
|
if (typeof value === "bigint")
|
|
@@ -2241,7 +2241,7 @@ var require_stringifyNumber = __commonJS((exports) => {
|
|
|
2241
2241
|
exports.stringifyNumber = stringifyNumber;
|
|
2242
2242
|
});
|
|
2243
2243
|
|
|
2244
|
-
// node_modules/yaml/dist/schema/core/float.js
|
|
2244
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/float.js
|
|
2245
2245
|
var require_float = __commonJS((exports) => {
|
|
2246
2246
|
var Scalar = require_Scalar();
|
|
2247
2247
|
var stringifyNumber = require_stringifyNumber();
|
|
@@ -2284,7 +2284,7 @@ var require_float = __commonJS((exports) => {
|
|
|
2284
2284
|
exports.floatNaN = floatNaN;
|
|
2285
2285
|
});
|
|
2286
2286
|
|
|
2287
|
-
// node_modules/yaml/dist/schema/core/int.js
|
|
2287
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/int.js
|
|
2288
2288
|
var require_int = __commonJS((exports) => {
|
|
2289
2289
|
var stringifyNumber = require_stringifyNumber();
|
|
2290
2290
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
@@ -2326,7 +2326,7 @@ var require_int = __commonJS((exports) => {
|
|
|
2326
2326
|
exports.intOct = intOct;
|
|
2327
2327
|
});
|
|
2328
2328
|
|
|
2329
|
-
// node_modules/yaml/dist/schema/core/schema.js
|
|
2329
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/schema.js
|
|
2330
2330
|
var require_schema = __commonJS((exports) => {
|
|
2331
2331
|
var map = require_map();
|
|
2332
2332
|
var _null = require_null();
|
|
@@ -2351,7 +2351,7 @@ var require_schema = __commonJS((exports) => {
|
|
|
2351
2351
|
exports.schema = schema;
|
|
2352
2352
|
});
|
|
2353
2353
|
|
|
2354
|
-
// node_modules/yaml/dist/schema/json/schema.js
|
|
2354
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/json/schema.js
|
|
2355
2355
|
var require_schema2 = __commonJS((exports) => {
|
|
2356
2356
|
var Scalar = require_Scalar();
|
|
2357
2357
|
var map = require_map();
|
|
@@ -2415,7 +2415,7 @@ var require_schema2 = __commonJS((exports) => {
|
|
|
2415
2415
|
exports.schema = schema;
|
|
2416
2416
|
});
|
|
2417
2417
|
|
|
2418
|
-
// node_modules/yaml/dist/schema/yaml-1.1/binary.js
|
|
2418
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/binary.js
|
|
2419
2419
|
var require_binary = __commonJS((exports) => {
|
|
2420
2420
|
var node_buffer = __require("buffer");
|
|
2421
2421
|
var Scalar = require_Scalar();
|
|
@@ -2470,7 +2470,7 @@ var require_binary = __commonJS((exports) => {
|
|
|
2470
2470
|
exports.binary = binary;
|
|
2471
2471
|
});
|
|
2472
2472
|
|
|
2473
|
-
// node_modules/yaml/dist/schema/yaml-1.1/pairs.js
|
|
2473
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
|
|
2474
2474
|
var require_pairs = __commonJS((exports) => {
|
|
2475
2475
|
var identity = require_identity();
|
|
2476
2476
|
var Pair = require_Pair();
|
|
@@ -2545,7 +2545,7 @@ ${cn.comment}` : item.comment;
|
|
|
2545
2545
|
exports.resolvePairs = resolvePairs;
|
|
2546
2546
|
});
|
|
2547
2547
|
|
|
2548
|
-
// node_modules/yaml/dist/schema/yaml-1.1/omap.js
|
|
2548
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/omap.js
|
|
2549
2549
|
var require_omap = __commonJS((exports) => {
|
|
2550
2550
|
var identity = require_identity();
|
|
2551
2551
|
var toJS = require_toJS();
|
|
@@ -2617,7 +2617,7 @@ var require_omap = __commonJS((exports) => {
|
|
|
2617
2617
|
exports.omap = omap;
|
|
2618
2618
|
});
|
|
2619
2619
|
|
|
2620
|
-
// node_modules/yaml/dist/schema/yaml-1.1/bool.js
|
|
2620
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/bool.js
|
|
2621
2621
|
var require_bool2 = __commonJS((exports) => {
|
|
2622
2622
|
var Scalar = require_Scalar();
|
|
2623
2623
|
function boolStringify({ value, source }, ctx) {
|
|
@@ -2646,7 +2646,7 @@ var require_bool2 = __commonJS((exports) => {
|
|
|
2646
2646
|
exports.trueTag = trueTag;
|
|
2647
2647
|
});
|
|
2648
2648
|
|
|
2649
|
-
// node_modules/yaml/dist/schema/yaml-1.1/float.js
|
|
2649
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/float.js
|
|
2650
2650
|
var require_float2 = __commonJS((exports) => {
|
|
2651
2651
|
var Scalar = require_Scalar();
|
|
2652
2652
|
var stringifyNumber = require_stringifyNumber();
|
|
@@ -2692,7 +2692,7 @@ var require_float2 = __commonJS((exports) => {
|
|
|
2692
2692
|
exports.floatNaN = floatNaN;
|
|
2693
2693
|
});
|
|
2694
2694
|
|
|
2695
|
-
// node_modules/yaml/dist/schema/yaml-1.1/int.js
|
|
2695
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/int.js
|
|
2696
2696
|
var require_int2 = __commonJS((exports) => {
|
|
2697
2697
|
var stringifyNumber = require_stringifyNumber();
|
|
2698
2698
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
@@ -2768,7 +2768,7 @@ var require_int2 = __commonJS((exports) => {
|
|
|
2768
2768
|
exports.intOct = intOct;
|
|
2769
2769
|
});
|
|
2770
2770
|
|
|
2771
|
-
// node_modules/yaml/dist/schema/yaml-1.1/set.js
|
|
2771
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/set.js
|
|
2772
2772
|
var require_set = __commonJS((exports) => {
|
|
2773
2773
|
var identity = require_identity();
|
|
2774
2774
|
var Pair = require_Pair();
|
|
@@ -2851,7 +2851,7 @@ var require_set = __commonJS((exports) => {
|
|
|
2851
2851
|
exports.set = set;
|
|
2852
2852
|
});
|
|
2853
2853
|
|
|
2854
|
-
// node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
|
|
2854
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
|
|
2855
2855
|
var require_timestamp = __commonJS((exports) => {
|
|
2856
2856
|
var stringifyNumber = require_stringifyNumber();
|
|
2857
2857
|
function parseSexagesimal(str, asBigInt) {
|
|
@@ -2933,7 +2933,7 @@ var require_timestamp = __commonJS((exports) => {
|
|
|
2933
2933
|
exports.timestamp = timestamp;
|
|
2934
2934
|
});
|
|
2935
2935
|
|
|
2936
|
-
// node_modules/yaml/dist/schema/yaml-1.1/schema.js
|
|
2936
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/schema.js
|
|
2937
2937
|
var require_schema3 = __commonJS((exports) => {
|
|
2938
2938
|
var map = require_map();
|
|
2939
2939
|
var _null = require_null();
|
|
@@ -2974,7 +2974,7 @@ var require_schema3 = __commonJS((exports) => {
|
|
|
2974
2974
|
exports.schema = schema;
|
|
2975
2975
|
});
|
|
2976
2976
|
|
|
2977
|
-
// node_modules/yaml/dist/schema/tags.js
|
|
2977
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/tags.js
|
|
2978
2978
|
var require_tags = __commonJS((exports) => {
|
|
2979
2979
|
var map = require_map();
|
|
2980
2980
|
var _null = require_null();
|
|
@@ -3065,7 +3065,7 @@ var require_tags = __commonJS((exports) => {
|
|
|
3065
3065
|
exports.getTags = getTags;
|
|
3066
3066
|
});
|
|
3067
3067
|
|
|
3068
|
-
// node_modules/yaml/dist/schema/Schema.js
|
|
3068
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/Schema.js
|
|
3069
3069
|
var require_Schema = __commonJS((exports) => {
|
|
3070
3070
|
var identity = require_identity();
|
|
3071
3071
|
var map = require_map();
|
|
@@ -3095,7 +3095,7 @@ var require_Schema = __commonJS((exports) => {
|
|
|
3095
3095
|
exports.Schema = Schema;
|
|
3096
3096
|
});
|
|
3097
3097
|
|
|
3098
|
-
// node_modules/yaml/dist/stringify/stringifyDocument.js
|
|
3098
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyDocument.js
|
|
3099
3099
|
var require_stringifyDocument = __commonJS((exports) => {
|
|
3100
3100
|
var identity = require_identity();
|
|
3101
3101
|
var stringify = require_stringify();
|
|
@@ -3175,7 +3175,7 @@ var require_stringifyDocument = __commonJS((exports) => {
|
|
|
3175
3175
|
exports.stringifyDocument = stringifyDocument;
|
|
3176
3176
|
});
|
|
3177
3177
|
|
|
3178
|
-
// node_modules/yaml/dist/doc/Document.js
|
|
3178
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/Document.js
|
|
3179
3179
|
var require_Document = __commonJS((exports) => {
|
|
3180
3180
|
var Alias = require_Alias();
|
|
3181
3181
|
var Collection = require_Collection();
|
|
@@ -3410,7 +3410,7 @@ var require_Document = __commonJS((exports) => {
|
|
|
3410
3410
|
exports.Document = Document;
|
|
3411
3411
|
});
|
|
3412
3412
|
|
|
3413
|
-
// node_modules/yaml/dist/errors.js
|
|
3413
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/errors.js
|
|
3414
3414
|
var require_errors = __commonJS((exports) => {
|
|
3415
3415
|
class YAMLError extends Error {
|
|
3416
3416
|
constructor(name, pos, code, message) {
|
|
@@ -3475,7 +3475,7 @@ ${pointer}
|
|
|
3475
3475
|
exports.prettifyError = prettifyError;
|
|
3476
3476
|
});
|
|
3477
3477
|
|
|
3478
|
-
// node_modules/yaml/dist/compose/resolve-props.js
|
|
3478
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-props.js
|
|
3479
3479
|
var require_resolve_props = __commonJS((exports) => {
|
|
3480
3480
|
function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
|
|
3481
3481
|
let spaceBefore = false;
|
|
@@ -3605,7 +3605,7 @@ var require_resolve_props = __commonJS((exports) => {
|
|
|
3605
3605
|
exports.resolveProps = resolveProps;
|
|
3606
3606
|
});
|
|
3607
3607
|
|
|
3608
|
-
// node_modules/yaml/dist/compose/util-contains-newline.js
|
|
3608
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-contains-newline.js
|
|
3609
3609
|
var require_util_contains_newline = __commonJS((exports) => {
|
|
3610
3610
|
function containsNewline(key) {
|
|
3611
3611
|
if (!key)
|
|
@@ -3645,7 +3645,7 @@ var require_util_contains_newline = __commonJS((exports) => {
|
|
|
3645
3645
|
exports.containsNewline = containsNewline;
|
|
3646
3646
|
});
|
|
3647
3647
|
|
|
3648
|
-
// node_modules/yaml/dist/compose/util-flow-indent-check.js
|
|
3648
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-flow-indent-check.js
|
|
3649
3649
|
var require_util_flow_indent_check = __commonJS((exports) => {
|
|
3650
3650
|
var utilContainsNewline = require_util_contains_newline();
|
|
3651
3651
|
function flowIndentCheck(indent, fc, onError) {
|
|
@@ -3660,7 +3660,7 @@ var require_util_flow_indent_check = __commonJS((exports) => {
|
|
|
3660
3660
|
exports.flowIndentCheck = flowIndentCheck;
|
|
3661
3661
|
});
|
|
3662
3662
|
|
|
3663
|
-
// node_modules/yaml/dist/compose/util-map-includes.js
|
|
3663
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-map-includes.js
|
|
3664
3664
|
var require_util_map_includes = __commonJS((exports) => {
|
|
3665
3665
|
var identity = require_identity();
|
|
3666
3666
|
function mapIncludes(ctx, items, search) {
|
|
@@ -3673,7 +3673,7 @@ var require_util_map_includes = __commonJS((exports) => {
|
|
|
3673
3673
|
exports.mapIncludes = mapIncludes;
|
|
3674
3674
|
});
|
|
3675
3675
|
|
|
3676
|
-
// node_modules/yaml/dist/compose/resolve-block-map.js
|
|
3676
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-map.js
|
|
3677
3677
|
var require_resolve_block_map = __commonJS((exports) => {
|
|
3678
3678
|
var Pair = require_Pair();
|
|
3679
3679
|
var YAMLMap = require_YAMLMap();
|
|
@@ -3780,7 +3780,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3780
3780
|
exports.resolveBlockMap = resolveBlockMap;
|
|
3781
3781
|
});
|
|
3782
3782
|
|
|
3783
|
-
// node_modules/yaml/dist/compose/resolve-block-seq.js
|
|
3783
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-seq.js
|
|
3784
3784
|
var require_resolve_block_seq = __commonJS((exports) => {
|
|
3785
3785
|
var YAMLSeq = require_YAMLSeq();
|
|
3786
3786
|
var resolveProps = require_resolve_props();
|
|
@@ -3828,7 +3828,7 @@ var require_resolve_block_seq = __commonJS((exports) => {
|
|
|
3828
3828
|
exports.resolveBlockSeq = resolveBlockSeq;
|
|
3829
3829
|
});
|
|
3830
3830
|
|
|
3831
|
-
// node_modules/yaml/dist/compose/resolve-end.js
|
|
3831
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-end.js
|
|
3832
3832
|
var require_resolve_end = __commonJS((exports) => {
|
|
3833
3833
|
function resolveEnd(end, offset, reqSpace, onError) {
|
|
3834
3834
|
let comment = "";
|
|
@@ -3868,7 +3868,7 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
3868
3868
|
exports.resolveEnd = resolveEnd;
|
|
3869
3869
|
});
|
|
3870
3870
|
|
|
3871
|
-
// node_modules/yaml/dist/compose/resolve-flow-collection.js
|
|
3871
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-collection.js
|
|
3872
3872
|
var require_resolve_flow_collection = __commonJS((exports) => {
|
|
3873
3873
|
var identity = require_identity();
|
|
3874
3874
|
var Pair = require_Pair();
|
|
@@ -4059,7 +4059,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
4059
4059
|
exports.resolveFlowCollection = resolveFlowCollection;
|
|
4060
4060
|
});
|
|
4061
4061
|
|
|
4062
|
-
// node_modules/yaml/dist/compose/compose-collection.js
|
|
4062
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-collection.js
|
|
4063
4063
|
var require_compose_collection = __commonJS((exports) => {
|
|
4064
4064
|
var identity = require_identity();
|
|
4065
4065
|
var Scalar = require_Scalar();
|
|
@@ -4121,7 +4121,7 @@ var require_compose_collection = __commonJS((exports) => {
|
|
|
4121
4121
|
exports.composeCollection = composeCollection;
|
|
4122
4122
|
});
|
|
4123
4123
|
|
|
4124
|
-
// node_modules/yaml/dist/compose/resolve-block-scalar.js
|
|
4124
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-scalar.js
|
|
4125
4125
|
var require_resolve_block_scalar = __commonJS((exports) => {
|
|
4126
4126
|
var Scalar = require_Scalar();
|
|
4127
4127
|
function resolveBlockScalar(ctx, scalar, onError) {
|
|
@@ -4314,7 +4314,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
4314
4314
|
exports.resolveBlockScalar = resolveBlockScalar;
|
|
4315
4315
|
});
|
|
4316
4316
|
|
|
4317
|
-
// node_modules/yaml/dist/compose/resolve-flow-scalar.js
|
|
4317
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-scalar.js
|
|
4318
4318
|
var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
4319
4319
|
var Scalar = require_Scalar();
|
|
4320
4320
|
var resolveEnd = require_resolve_end();
|
|
@@ -4530,7 +4530,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
|
4530
4530
|
exports.resolveFlowScalar = resolveFlowScalar;
|
|
4531
4531
|
});
|
|
4532
4532
|
|
|
4533
|
-
// node_modules/yaml/dist/compose/compose-scalar.js
|
|
4533
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-scalar.js
|
|
4534
4534
|
var require_compose_scalar = __commonJS((exports) => {
|
|
4535
4535
|
var identity = require_identity();
|
|
4536
4536
|
var Scalar = require_Scalar();
|
|
@@ -4608,7 +4608,7 @@ var require_compose_scalar = __commonJS((exports) => {
|
|
|
4608
4608
|
exports.composeScalar = composeScalar;
|
|
4609
4609
|
});
|
|
4610
4610
|
|
|
4611
|
-
// node_modules/yaml/dist/compose/util-empty-scalar-position.js
|
|
4611
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-empty-scalar-position.js
|
|
4612
4612
|
var require_util_empty_scalar_position = __commonJS((exports) => {
|
|
4613
4613
|
function emptyScalarPosition(offset, before, pos) {
|
|
4614
4614
|
if (before) {
|
|
@@ -4635,7 +4635,7 @@ var require_util_empty_scalar_position = __commonJS((exports) => {
|
|
|
4635
4635
|
exports.emptyScalarPosition = emptyScalarPosition;
|
|
4636
4636
|
});
|
|
4637
4637
|
|
|
4638
|
-
// node_modules/yaml/dist/compose/compose-node.js
|
|
4638
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-node.js
|
|
4639
4639
|
var require_compose_node = __commonJS((exports) => {
|
|
4640
4640
|
var Alias = require_Alias();
|
|
4641
4641
|
var identity = require_identity();
|
|
@@ -4738,7 +4738,7 @@ var require_compose_node = __commonJS((exports) => {
|
|
|
4738
4738
|
exports.composeNode = composeNode;
|
|
4739
4739
|
});
|
|
4740
4740
|
|
|
4741
|
-
// node_modules/yaml/dist/compose/compose-doc.js
|
|
4741
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-doc.js
|
|
4742
4742
|
var require_compose_doc = __commonJS((exports) => {
|
|
4743
4743
|
var Document = require_Document();
|
|
4744
4744
|
var composeNode = require_compose_node();
|
|
@@ -4778,7 +4778,7 @@ var require_compose_doc = __commonJS((exports) => {
|
|
|
4778
4778
|
exports.composeDoc = composeDoc;
|
|
4779
4779
|
});
|
|
4780
4780
|
|
|
4781
|
-
// node_modules/yaml/dist/compose/composer.js
|
|
4781
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/composer.js
|
|
4782
4782
|
var require_composer = __commonJS((exports) => {
|
|
4783
4783
|
var node_process = __require("process");
|
|
4784
4784
|
var directives = require_directives();
|
|
@@ -4967,7 +4967,7 @@ ${end.comment}` : end.comment;
|
|
|
4967
4967
|
exports.Composer = Composer;
|
|
4968
4968
|
});
|
|
4969
4969
|
|
|
4970
|
-
// node_modules/yaml/dist/parse/cst-scalar.js
|
|
4970
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-scalar.js
|
|
4971
4971
|
var require_cst_scalar = __commonJS((exports) => {
|
|
4972
4972
|
var resolveBlockScalar = require_resolve_block_scalar();
|
|
4973
4973
|
var resolveFlowScalar = require_resolve_flow_scalar();
|
|
@@ -5157,7 +5157,7 @@ var require_cst_scalar = __commonJS((exports) => {
|
|
|
5157
5157
|
exports.setScalarValue = setScalarValue;
|
|
5158
5158
|
});
|
|
5159
5159
|
|
|
5160
|
-
// node_modules/yaml/dist/parse/cst-stringify.js
|
|
5160
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-stringify.js
|
|
5161
5161
|
var require_cst_stringify = __commonJS((exports) => {
|
|
5162
5162
|
var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
|
|
5163
5163
|
function stringifyToken(token) {
|
|
@@ -5215,7 +5215,7 @@ var require_cst_stringify = __commonJS((exports) => {
|
|
|
5215
5215
|
exports.stringify = stringify;
|
|
5216
5216
|
});
|
|
5217
5217
|
|
|
5218
|
-
// node_modules/yaml/dist/parse/cst-visit.js
|
|
5218
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-visit.js
|
|
5219
5219
|
var require_cst_visit = __commonJS((exports) => {
|
|
5220
5220
|
var BREAK = Symbol("break visit");
|
|
5221
5221
|
var SKIP = Symbol("skip children");
|
|
@@ -5274,7 +5274,7 @@ var require_cst_visit = __commonJS((exports) => {
|
|
|
5274
5274
|
exports.visit = visit;
|
|
5275
5275
|
});
|
|
5276
5276
|
|
|
5277
|
-
// node_modules/yaml/dist/parse/cst.js
|
|
5277
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst.js
|
|
5278
5278
|
var require_cst = __commonJS((exports) => {
|
|
5279
5279
|
var cstScalar = require_cst_scalar();
|
|
5280
5280
|
var cstStringify = require_cst_stringify();
|
|
@@ -5375,7 +5375,7 @@ var require_cst = __commonJS((exports) => {
|
|
|
5375
5375
|
exports.tokenType = tokenType;
|
|
5376
5376
|
});
|
|
5377
5377
|
|
|
5378
|
-
// node_modules/yaml/dist/parse/lexer.js
|
|
5378
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/lexer.js
|
|
5379
5379
|
var require_lexer = __commonJS((exports) => {
|
|
5380
5380
|
var cst = require_cst();
|
|
5381
5381
|
function isEmpty(ch) {
|
|
@@ -5961,7 +5961,7 @@ var require_lexer = __commonJS((exports) => {
|
|
|
5961
5961
|
exports.Lexer = Lexer;
|
|
5962
5962
|
});
|
|
5963
5963
|
|
|
5964
|
-
// node_modules/yaml/dist/parse/line-counter.js
|
|
5964
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/line-counter.js
|
|
5965
5965
|
var require_line_counter = __commonJS((exports) => {
|
|
5966
5966
|
class LineCounter {
|
|
5967
5967
|
constructor() {
|
|
@@ -5989,7 +5989,7 @@ var require_line_counter = __commonJS((exports) => {
|
|
|
5989
5989
|
exports.LineCounter = LineCounter;
|
|
5990
5990
|
});
|
|
5991
5991
|
|
|
5992
|
-
// node_modules/yaml/dist/parse/parser.js
|
|
5992
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/parser.js
|
|
5993
5993
|
var require_parser = __commonJS((exports) => {
|
|
5994
5994
|
var node_process = __require("process");
|
|
5995
5995
|
var cst = require_cst();
|
|
@@ -6838,7 +6838,7 @@ var require_parser = __commonJS((exports) => {
|
|
|
6838
6838
|
exports.Parser = Parser;
|
|
6839
6839
|
});
|
|
6840
6840
|
|
|
6841
|
-
// node_modules/yaml/dist/public-api.js
|
|
6841
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/public-api.js
|
|
6842
6842
|
var require_public_api = __commonJS((exports) => {
|
|
6843
6843
|
var composer = require_composer();
|
|
6844
6844
|
var Document = require_Document();
|
|
@@ -6932,7 +6932,7 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6932
6932
|
exports.stringify = stringify;
|
|
6933
6933
|
});
|
|
6934
6934
|
|
|
6935
|
-
// node_modules/yaml/dist/index.js
|
|
6935
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
|
|
6936
6936
|
var composer, Document, Schema, errors, Alias, identity, Pair, Scalar, YAMLMap, YAMLSeq, cst, lexer, lineCounter, parser, publicApi, visit, $Composer, $Document, $Schema, $YAMLError, $YAMLParseError, $YAMLWarning, $Alias, $isAlias, $isCollection, $isDocument, $isMap, $isNode, $isPair, $isScalar, $isSeq, $Pair, $Scalar, $YAMLMap, $YAMLSeq, $Lexer, $LineCounter, $Parser, $parse, $parseAllDocuments, $parseDocument, $stringify, $visit, $visitAsync;
|
|
6937
6937
|
var init_dist = __esm(() => {
|
|
6938
6938
|
composer = require_composer();
|
|
@@ -6981,7 +6981,7 @@ var init_dist = __esm(() => {
|
|
|
6981
6981
|
$visitAsync = visit.visitAsync;
|
|
6982
6982
|
});
|
|
6983
6983
|
|
|
6984
|
-
// node_modules/zod/v3/helpers/util.js
|
|
6984
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
6985
6985
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
6986
6986
|
const t = typeof data;
|
|
6987
6987
|
switch (t) {
|
|
@@ -7112,7 +7112,7 @@ var init_util = __esm(() => {
|
|
|
7112
7112
|
]);
|
|
7113
7113
|
});
|
|
7114
7114
|
|
|
7115
|
-
// node_modules/zod/v3/ZodError.js
|
|
7115
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
7116
7116
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
7117
7117
|
const json = JSON.stringify(obj, null, 2);
|
|
7118
7118
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -7233,7 +7233,7 @@ var init_ZodError = __esm(() => {
|
|
|
7233
7233
|
};
|
|
7234
7234
|
});
|
|
7235
7235
|
|
|
7236
|
-
// node_modules/zod/v3/locales/en.js
|
|
7236
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
7237
7237
|
var errorMap = (issue, _ctx) => {
|
|
7238
7238
|
let message;
|
|
7239
7239
|
switch (issue.code) {
|
|
@@ -7340,7 +7340,7 @@ var init_en = __esm(() => {
|
|
|
7340
7340
|
en_default = errorMap;
|
|
7341
7341
|
});
|
|
7342
7342
|
|
|
7343
|
-
// node_modules/zod/v3/errors.js
|
|
7343
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
7344
7344
|
function setErrorMap(map) {
|
|
7345
7345
|
overrideErrorMap = map;
|
|
7346
7346
|
}
|
|
@@ -7353,7 +7353,7 @@ var init_errors = __esm(() => {
|
|
|
7353
7353
|
overrideErrorMap = en_default;
|
|
7354
7354
|
});
|
|
7355
7355
|
|
|
7356
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
7356
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
7357
7357
|
function addIssueToContext(ctx, issueData) {
|
|
7358
7358
|
const overrideMap = getErrorMap();
|
|
7359
7359
|
const issue = makeIssue({
|
|
@@ -7458,10 +7458,10 @@ var init_parseUtil = __esm(() => {
|
|
|
7458
7458
|
});
|
|
7459
7459
|
});
|
|
7460
7460
|
|
|
7461
|
-
// node_modules/zod/v3/helpers/typeAliases.js
|
|
7461
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
7462
7462
|
var init_typeAliases = () => {};
|
|
7463
7463
|
|
|
7464
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
7464
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
7465
7465
|
var errorUtil;
|
|
7466
7466
|
var init_errorUtil = __esm(() => {
|
|
7467
7467
|
(function(errorUtil2) {
|
|
@@ -7470,7 +7470,7 @@ var init_errorUtil = __esm(() => {
|
|
|
7470
7470
|
})(errorUtil || (errorUtil = {}));
|
|
7471
7471
|
});
|
|
7472
7472
|
|
|
7473
|
-
// node_modules/zod/v3/types.js
|
|
7473
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
7474
7474
|
class ParseInputLazyPath {
|
|
7475
7475
|
constructor(parent, value, path, key) {
|
|
7476
7476
|
this._cachedPath = [];
|
|
@@ -10821,7 +10821,7 @@ var init_types = __esm(() => {
|
|
|
10821
10821
|
NEVER = INVALID;
|
|
10822
10822
|
});
|
|
10823
10823
|
|
|
10824
|
-
// node_modules/zod/v3/external.js
|
|
10824
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
10825
10825
|
var exports_external = {};
|
|
10826
10826
|
__export(exports_external, {
|
|
10827
10827
|
void: () => voidType,
|
|
@@ -10941,14 +10941,14 @@ var init_external = __esm(() => {
|
|
|
10941
10941
|
init_ZodError();
|
|
10942
10942
|
});
|
|
10943
10943
|
|
|
10944
|
-
// node_modules/zod/index.js
|
|
10944
|
+
// node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
10945
10945
|
var init_zod = __esm(() => {
|
|
10946
10946
|
init_external();
|
|
10947
10947
|
init_external();
|
|
10948
10948
|
});
|
|
10949
10949
|
|
|
10950
10950
|
// src/config/schema.ts
|
|
10951
|
-
var CodeRepoEntrySchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema,
|
|
10951
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, HostControlConfigSchema, SwitchroomConfigSchema;
|
|
10952
10952
|
var init_schema = __esm(() => {
|
|
10953
10953
|
init_zod();
|
|
10954
10954
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -10956,10 +10956,15 @@ var init_schema = __esm(() => {
|
|
|
10956
10956
|
source: exports_external.string().describe("Absolute or home-relative path to the repo (e.g. ~/code/switchroom)"),
|
|
10957
10957
|
concurrency: exports_external.number().int().positive().optional().describe("Max simultaneous worktrees for this repo (default 5)")
|
|
10958
10958
|
});
|
|
10959
|
+
AgentBindMountSchema = exports_external.object({
|
|
10960
|
+
source: exports_external.string().describe("Absolute host path to bind-mount into the container. Tilde-expansion " + "is not performed — use the literal absolute path (e.g. " + "'/home/me/code/switchroom'). The compose generator refuses sources " + "under system paths (/, /etc, /proc, /sys, /dev, /run, /var/run, " + "/boot, /var/lib/docker) and the docker socket."),
|
|
10961
|
+
target: exports_external.string().optional().describe("Container path the source mounts to. Must be absolute. Defaults to " + "the same path as `source` (matches switchroom's existing dual-mount " + "convention so absolute paths in scaffolded scripts Just Work)."),
|
|
10962
|
+
mode: exports_external.enum(["ro", "rw"]).optional().describe("Read-only (default) or read-write. Use `rw` only when the agent " + "must mutate the host path (e.g. editing switchroom source). " + "Default: 'ro'.")
|
|
10963
|
+
});
|
|
10959
10964
|
ScheduleEntrySchema = exports_external.object({
|
|
10960
10965
|
cron: exports_external.string().describe("Cron expression (e.g., '0 8 * * *')"),
|
|
10961
10966
|
prompt: exports_external.string().describe("Prompt to send at the scheduled time"),
|
|
10962
|
-
model: exports_external.string().optional().describe("Model for this task. Defaults to claude-sonnet-4-6 (cheap, fast). " + "Use claude-opus-4-
|
|
10967
|
+
model: exports_external.string().optional().describe("Model for this task. Defaults to claude-sonnet-4-6 (cheap, fast). " + "Use claude-opus-4-7 for tasks needing complex reasoning."),
|
|
10963
10968
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).default([]).describe("Vault key names this cron task may read via the vault-broker daemon. " + "Empty by default — broker requests for unlisted keys are denied. " + "Note: this is misconfiguration protection (a typo in cron-A doesn't " + "accidentally read cron-B's keys) rather than a security boundary — " + "anyone who can edit cron scripts can also edit switchroom.yaml, and " + "anyone with the vault passphrase can read the vault file directly. " + "See docs/configuration.md for the full framing.")
|
|
10964
10969
|
});
|
|
10965
10970
|
AgentSoulSchema = exports_external.object({
|
|
@@ -10999,7 +11004,7 @@ var init_schema = __esm(() => {
|
|
|
10999
11004
|
SessionEnd: exports_external.array(HookEntrySchema).optional()
|
|
11000
11005
|
}).catchall(exports_external.array(HookEntrySchema)).optional();
|
|
11001
11006
|
SubagentSchema = exports_external.object({
|
|
11002
|
-
description: exports_external.string().describe("When the main agent should delegate to this sub-agent"),
|
|
11007
|
+
description: exports_external.string().optional().describe("When the main agent should delegate to this sub-agent"),
|
|
11003
11008
|
model: exports_external.string().optional().describe("Model: 'sonnet', 'opus', 'haiku', full ID, or 'inherit' (default)"),
|
|
11004
11009
|
background: exports_external.boolean().optional().describe("Run in background by default (non-blocking). Default false"),
|
|
11005
11010
|
isolation: exports_external.enum(["worktree"]).optional().describe("'worktree' gives the sub-agent its own git branch"),
|
|
@@ -11036,10 +11041,6 @@ var init_schema = __esm(() => {
|
|
|
11036
11041
|
deferred_completion_timeout_ms: exports_external.number().int().nonnegative().optional().describe("Force-close timeout (ms) for deferred sub-agent completion. After " + "the parent turn_end arrives while sub-agents are still running, the " + "card is force-closed after this many ms even if sub-agents never " + "finish. Watcher-disconnect safety net. Default 180000 (3 min)."),
|
|
11037
11042
|
sub_agent_tick_interval_ms: exports_external.number().int().nonnegative().optional().describe("Heartbeat tick interval (ms) for sub-agent rendering. Forces a " + "re-render of the elapsed-time counter while sub-agents are running, " + "even during silent stretches between tool calls. Default 10000 (10 s). " + "Set to 0 to disable the elapsed-ticker path."),
|
|
11038
11043
|
edit_budget_threshold: exports_external.number().int().nonnegative().optional().describe("Telegram API edit budget per minute before the progress-card driver " + "falls back to a slower coalesce window. When a chat accumulates more " + "than this many card edits in the trailing 60 s, the driver switches " + "to a wider coalesce interval until the rate drops back. Default 18. " + "Increase if your gateway frequently bumps the Telegram edit-rate ceiling " + "with many parallel sub-agents; decrease for a more conservative buffer."),
|
|
11039
|
-
progress_card: exports_external.object({
|
|
11040
|
-
delay_ms: exports_external.number().int().nonnegative().optional().describe("First-render delay (ms) for the pinned progress card (#842). The " + "driver buffers SessionEvents for this long after the turn starts; " + "if the turn ends before the threshold trips, no card is ever " + "posted. When the threshold trips, the card renders the full " + "buffered event stream and the live-update loop takes over. " + "Default 45000 (45 s). Set to 0 for the legacy immediate-render " + "behaviour."),
|
|
11041
|
-
delay_ms_background: exports_external.number().int().nonnegative().optional().describe("First-render delay (ms) override for explicit background " + "sub-agent dispatches (#842). When the agent calls " + "`Agent({ run_in_background: true })`, the card is promoted out " + "of the suppression window using this delay instead of " + "`delay_ms`. Default 0 (immediate render — backgrounded work " + "should be visible right away).")
|
|
11042
|
-
}).optional().describe("Progress-card first-render gating (#842). Defers the card until the " + "turn looks meaningful — short turns never flash a card at all."),
|
|
11043
11044
|
stickers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Sticker aliases for the `send_sticker` MCP tool (#576). Maps a " + "short alias name (e.g. 'happy', 'thinking') to a Telegram file_id. " + "Operator-curated — capture file_ids from inbound stickers the user " + "sends and add them here. The agent calls send_sticker(chat_id, " + "alias='happy') and the gateway resolves to the file_id at send " + "time. Aliases enable persona-flavored expressiveness without " + "exposing raw file_ids in the agent prompt. Personal-assistant / " + "health-coach personas benefit; coding agents typically don't " + "configure any."),
|
|
11044
11045
|
voice_in: exports_external.object({
|
|
11045
11046
|
enabled: exports_external.boolean().optional().describe("Master switch for voice-message transcription."),
|
|
@@ -11082,13 +11083,27 @@ var init_schema = __esm(() => {
|
|
|
11082
11083
|
}).optional();
|
|
11083
11084
|
TIMEZONE_REGEX = /^UTC$|^[A-Z][A-Za-z0-9_+-]+(\/[A-Z][A-Za-z0-9_+-]+){1,2}$/;
|
|
11084
11085
|
ApproverIdSchema = exports_external.union([exports_external.number(), exports_external.string().regex(/^\d+$/)]);
|
|
11085
|
-
|
|
11086
|
+
GoogleWorkspaceTierSchema = exports_external.enum([
|
|
11087
|
+
"core",
|
|
11088
|
+
"extended",
|
|
11089
|
+
"complete"
|
|
11090
|
+
]);
|
|
11091
|
+
GoogleWorkspaceConfigSchema = exports_external.object({
|
|
11086
11092
|
google_client_id: exports_external.string().min(1).describe("Google OAuth client ID (literal string or vault reference e.g. 'vault:google-oauth-client-id')"),
|
|
11087
11093
|
google_client_secret: exports_external.string().min(1).describe("Google OAuth client secret (literal string or vault reference e.g. 'vault:google-oauth-client-secret')"),
|
|
11088
|
-
approvers: exports_external.array(ApproverIdSchema).min(1).describe("Array of numeric Telegram user IDs authorized to approve drive onboarding. " + "At least one must be specified.")
|
|
11094
|
+
approvers: exports_external.array(ApproverIdSchema).min(1).describe("Array of numeric Telegram user IDs authorized to approve drive onboarding. " + "At least one must be specified."),
|
|
11095
|
+
tier: GoogleWorkspaceTierSchema.optional().describe("RFC G Phase 1: which upstream MCP tier to expose. " + "core (default) = ~16 tools (Drive+Docs+Sheets+Calendar). " + "extended = ~40 tools (+Slides, Forms, Tasks, Chat). " + "complete = ~60+ tools (+Gmail; not recommended yet — see RFC G §5).")
|
|
11089
11096
|
}).optional();
|
|
11090
|
-
|
|
11091
|
-
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.")
|
|
11097
|
+
AgentGoogleWorkspaceConfigSchema = exports_external.object({
|
|
11098
|
+
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."),
|
|
11099
|
+
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.")
|
|
11100
|
+
}).optional();
|
|
11101
|
+
ReactionsSchema = exports_external.object({
|
|
11102
|
+
enabled: exports_external.boolean().optional().describe("Master switch for the reaction-trigger path. When false, " + "reactions are still persisted via recordReaction but never " + "dispatched to the agent as synthetic inbound turns. Default true."),
|
|
11103
|
+
trigger_emojis: exports_external.array(exports_external.string()).optional().describe("Emoji allowlist that triggers a synthetic inbound when reacted " + "to a bot message. Default ['\uD83D\uDC4E', '❌', '\uD83D\uDC4D', '✅']. Cascade " + "mode: REPLACE (not union) — setting this at a layer replaces " + "lower layers entirely, so an operator can narrow to [] to " + "disable triggering without flipping `enabled`."),
|
|
11104
|
+
debounce_ms: exports_external.number().int().nonnegative().optional().describe("Per-chat debounce window in ms. A qualifying reaction holds for " + "this long; a second qualifying reaction within the window " + "collapses both into a single batched synthetic turn. Default 30000."),
|
|
11105
|
+
per_hour_cap: exports_external.number().int().nonnegative().optional().describe("Max reaction-triggered synthetic turns per chat per rolling hour. " + "Refusals are stderr-logged but not surfaced to the agent. " + "Default 10. Set to 0 to disable triggering via the cap path."),
|
|
11106
|
+
group_admin_only: exports_external.boolean().optional().describe("In groups/supergroups (negative chat_id), only trigger a synthetic " + "turn when the reacter is a chat admin (creator or administrator). " + "Failing the lookup is treated as non-admin (fail-closed). " + "DMs are never affected by this flag — the reacter IS the user. " + "Default true.")
|
|
11092
11107
|
}).optional();
|
|
11093
11108
|
profileFields = {
|
|
11094
11109
|
extends: exports_external.string().optional(),
|
|
@@ -11114,6 +11129,7 @@ var init_schema = __esm(() => {
|
|
|
11114
11129
|
}).optional()
|
|
11115
11130
|
}).optional(),
|
|
11116
11131
|
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11132
|
+
reactions: ReactionsSchema,
|
|
11117
11133
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional(),
|
|
11118
11134
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
11119
11135
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Omit to use Claude's default (acceptEdits for switchroom agents). " + "Warning: bypassPermissions and dontAsk skip all safety checks — use only in trusted sandboxes."),
|
|
@@ -11134,6 +11150,12 @@ var init_schema = __esm(() => {
|
|
|
11134
11150
|
claude_md_raw: exports_external.string().optional(),
|
|
11135
11151
|
cli_args: exports_external.array(exports_external.string()).optional(),
|
|
11136
11152
|
extra_stable_files: exports_external.array(exports_external.string()).optional().describe("Extra filenames (relative to the agent's workspace directory) to append " + "to the stable bootstrap render. Loaded once at session start via " + "`--append-system-prompt`. Missing files are silently skipped. " + "Example: ['BRIEF.md', 'CONTEXT.md']."),
|
|
11153
|
+
resources: exports_external.object({
|
|
11154
|
+
memory: exports_external.string().regex(/^\d+(\.\d+)?[kmgKMG]?$/, "memory must be a Docker size string like '6g', '512m', '1.5g'").optional().describe("Hard memory cap (Docker `mem_limit` → cgroup memory.max). When the " + "container exceeds this, the kernel OOM-kills processes in the cgroup. " + "Format: '6g', '1.5g', '512m'. When unset at every cascade layer the " + "compose generator falls back to the hard-coded per-profile defaults " + "in src/agents/compose.ts (klanker 6g, coding 2g, conversational 1.5g, " + "lightweight 1g, default 1.5g)."),
|
|
11155
|
+
memory_reservation: exports_external.string().regex(/^\d+(\.\d+)?[kmgKMG]?$/, "memory_reservation must be a Docker size string like '4g', '256m'").optional().describe("Soft memory floor (Docker `mem_reservation` → cgroup memory.low). " + "Under host-wide memory pressure, the kernel protects at least this " + "much from being reclaimed from the cgroup. Must be ≤ memory. Use to " + "keep an agent RAM-resident when the host has other tenants that " + "might push the box (Coolify apps, build jobs). Default: unset."),
|
|
11156
|
+
pids_limit: exports_external.number().int().positive().optional().describe("Max processes the cgroup can spawn (cgroup pids.max). Prevents " + "fork bombs and runaway test runners. Counts every process in the " + "cgroup including bash subprocesses, claude itself, sidecars, and " + "any test/build worker. A typical agent at idle uses ~30 PIDs; " + "`npm test`-style workloads can spike to 200+. Set generously " + "(2000 is a comfortable cap for test-running agents). Default: " + "unset (no cgroup pid cap)."),
|
|
11157
|
+
cpus: exports_external.number().positive().optional().describe("CPU quota (Docker `cpus`). Fractional values OK (e.g. 0.5, 2.0). " + "When unset at every cascade layer the compose generator falls " + "back to the per-profile default (klanker/coding 2.0, default 1.0, " + "lightweight 0.5).")
|
|
11158
|
+
}).optional().describe("Per-agent resource limits. Cascades through defaults → profile → " + "per-agent with per-field merge (agent wins on each field independently). " + "Any field left unset at every layer falls back to the hard-coded " + "per-profile defaults in src/agents/compose.ts."),
|
|
11137
11159
|
experimental: exports_external.object({
|
|
11138
11160
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent under " + "the legacy PTY supervisor instead. Default: false (tmux is the default)."),
|
|
11139
11161
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script behaviour " + "instead of the tmux send-keys path. Default: false.")
|
|
@@ -11147,13 +11169,13 @@ var init_schema = __esm(() => {
|
|
|
11147
11169
|
bot_token: exports_external.string().optional().describe("Per-agent Telegram bot token or vault reference (overrides global telegram.bot_token)"),
|
|
11148
11170
|
bot_username: exports_external.string().optional().describe("Per-agent Telegram bot username (without leading @) when it doesn't " + "contain the agent slug. Replaces the default 'username includes slug' " + "preflight check with an exact (case-insensitive) match. Use when an " + "agent and its bot have intentionally divergent names (e.g. agent " + "'lawgpt' paired with bot '@meken_law_bot')."),
|
|
11149
11171
|
timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC' " + "(three-letter aliases like EST/PST and bare offsets like UTC+10 are not accepted)").optional().describe("Per-agent IANA timezone override. Wins over any profile/defaults " + "value and over the top-level switchroom.timezone global. Controls " + "the UserPromptSubmit timezone hook's emitted local time and the " + "systemd unit's TZ= env."),
|
|
11150
|
-
auth_label: exports_external.string().optional().describe("Human-readable identity for the session-start greeting (e.g. 'user@example.com'). " + "Anthropic does not expose a public user-profile endpoint for OAuth tokens, so the " + "email/account cannot be read locally; the user declares it here. Appears in the Auth " + "row as '✓ max · <label> · expires ...'."),
|
|
11151
11172
|
auth: exports_external.object({
|
|
11152
|
-
|
|
11153
|
-
}).optional().describe("Account routing for switchroom-auth-broker.
|
|
11173
|
+
override: exports_external.string().min(1).optional().describe("Per-agent override of the fleet-wide `auth.active`. Edge-case use only — " + "this agent talks to the named account regardless of fleet active. See RFC H §4.5.")
|
|
11174
|
+
}).optional().describe("Account routing for switchroom-auth-broker. RFC H schema uses " + "fleet-wide `auth.active` plus per-agent `override:` for edge cases. " + "Pre-RFC-H `auth.accounts: [..]` and `auth_label:` are migrated in-place " + "on first apply (see src/auth/migrate-schema.ts)."),
|
|
11154
11175
|
dm_only: exports_external.boolean().optional().describe("Mark this agent as a DM-only bot — has its own bot_token and lives " + "exclusively in a private chat with the operator. Suppresses " + "scaffolding's default behavior of inheriting the global " + "telegram.forum_chat_id into the agent's access.json `groups` entry " + "(the forum chat the bot isn't a member of, which would otherwise " + "trigger a 'boot-probe-failed: 400 chat not found' warning every " + "restart). topic_name is still schema-required but unused — set it " + "to a display label like 'DM' for /switchroom status output."),
|
|
11155
11176
|
topic_name: exports_external.string().describe("Telegram forum topic display name"),
|
|
11156
11177
|
topic_emoji: exports_external.string().optional().describe("Emoji for the topic (e.g., '\uD83C\uDFCB️')"),
|
|
11178
|
+
purpose: exports_external.string().max(140).optional().describe("One-line description of what this agent does (≤140 chars). Shown to " + "peer agents when they call the agent-config MCP `peers_list` tool, so " + "every agent on the instance can answer 'is there an agent that does X' " + "without baking the fleet into prompts. Sourced live from " + "switchroom.yaml — never memorized into Hindsight. Falls back to " + "`topic_name` when absent."),
|
|
11157
11179
|
role: exports_external.enum(["assistant", "foreman"]).optional().describe("Agent role. Default (omitted) is `assistant` — a fleet agent doing " + "user-facing tasks. `foreman` opts the agent in to switchroom's bundled " + "operator skills (switchroom-architecture / cli / health / install / manage " + "/ status), auto-symlinked into the agent's .claude/skills/ on scaffold and " + "reconcile. Fleet agents (assistant role) get no operator skills; reconcile " + "actively retracts them if the role flips back. See docs/skills.md for the model."),
|
|
11158
11180
|
topic_id: exports_external.number().optional().describe("Telegram topic thread ID (auto-populated by switchroom topics sync)"),
|
|
11159
11181
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic"])).optional().describe("[DEPRECATED — moved to channels.telegram.webhook_sources in #596] " + "Old per-agent location. Still read but logs a deprecation warning. " + "See channels.telegram.webhook_sources for the canonical spot."),
|
|
@@ -11172,6 +11194,7 @@ var init_schema = __esm(() => {
|
|
|
11172
11194
|
tools: AgentToolsSchema,
|
|
11173
11195
|
memory: AgentMemorySchema,
|
|
11174
11196
|
schedule: exports_external.array(ScheduleEntrySchema).default([]),
|
|
11197
|
+
reactions: ReactionsSchema,
|
|
11175
11198
|
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-4-6')"),
|
|
11176
11199
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
11177
11200
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Per-agent override wins over defaults.permission_mode. " + "Warning: bypassPermissions and dontAsk skip all safety checks — use only in trusted sandboxes."),
|
|
@@ -11188,17 +11211,19 @@ var init_schema = __esm(() => {
|
|
|
11188
11211
|
session_continuity: SessionContinuitySchema.describe("Handoff-briefing settings. When enabled (default), a Stop hook " + "summarizes each session at shutdown and start.sh injects that " + "briefing into the next session via --append-system-prompt."),
|
|
11189
11212
|
channels: ChannelsSchema.describe("Per-channel configuration. Today only `telegram` is defined; the " + "shape is designed to expand to other channels (Slack, Discord, " + "Matrix, Email) as they're added."),
|
|
11190
11213
|
dangerous_mode: exports_external.boolean().optional().describe("If true, include --dangerously-skip-permissions in start.sh"),
|
|
11191
|
-
skip_permission_prompt: exports_external.boolean().optional().describe("
|
|
11214
|
+
skip_permission_prompt: exports_external.boolean().optional().describe("DEPRECATED no-op (accepted for backwards compatibility). Claude Code " + "ignores skipDangerousModePermissionPrompt at project scope; autoaccept " + "(src/agents/autoaccept.ts) handles the bypass-mode prompt instead. " + "Safe to remove from switchroom.yaml."),
|
|
11192
11215
|
admin: exports_external.boolean().optional().describe("If true, the agent's Telegram gateway intercepts admin slash commands " + "(/agents, /logs, /restart, /delete, /update, /auth, /reconcile, etc.) " + "locally before forwarding to Claude. Commands are handled silently — " + "Claude never sees them. Requires the agent to use the switchroom-telegram " + "plugin. When false or absent, all messages pass through to Claude unchanged."),
|
|
11193
11216
|
settings_raw: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Escape hatch: raw object deep-merged into the generated " + "settings.json as the final step. Use for Claude Code settings " + "keys switchroom doesn't wrap directly (e.g. effort, apiKeyHelper). " + "Power-user-only — prefer the typed fields when they exist."),
|
|
11194
11217
|
claude_md_raw: exports_external.string().optional().describe("Escape hatch: markdown text appended verbatim to CLAUDE.md on " + "initial scaffold. Not re-applied on reconcile (CLAUDE.md is " + "user-protected). Use for one-off persona tuning that isn't " + "worth a template."),
|
|
11195
11218
|
cli_args: exports_external.array(exports_external.string()).optional().describe("Escape hatch: extra arguments appended to the `exec claude` " + "invocation in start.sh. Use for Claude Code CLI flags switchroom " + "doesn't expose directly (e.g. --effort high, " + "--exclude-dynamic-system-prompt-sections)."),
|
|
11196
|
-
add_dirs: exports_external.array(exports_external.string()).optional().describe("Additional filesystem paths the agent's tools can access. Passed " + "as repeated --add-dir <path> on the claude invocation. Use to grant " + "an agent reach into shared dirs (e.g. '/share/collab') without " + "scaffold hacks. Per-agent only — paths are persona-specific. See #199."),
|
|
11219
|
+
add_dirs: exports_external.array(exports_external.string()).optional().describe("Additional filesystem paths the agent's tools can access. Passed " + "as repeated --add-dir <path> on the claude invocation. Use to grant " + "an agent reach into shared dirs (e.g. '/share/collab') without " + "scaffold hacks. Per-agent only — paths are persona-specific. See #199. " + "Note: this only adjusts the claude CLI's --add-dir tool-reach allowlist. " + "If the path is not already inside the agent's container, also declare " + "it in `bind_mounts:` (admin agents only) — otherwise the path doesn't " + "exist inside the sandbox and --add-dir is a no-op."),
|
|
11220
|
+
bind_mounts: exports_external.array(AgentBindMountSchema).optional().describe("Extra host paths bind-mounted into this agent's container, on top of " + "the standard dual-mount baseline. ADMIN-ONLY: the compose generator " + "refuses to emit bind_mounts unless `admin: true` is also set on the " + "same agent. Use to dogfood / self-modify switchroom or another repo " + "(see issue #1164). Pair with `add_dirs:` so claude's tool-reach " + "allowlist also covers the mounted path. System paths (/, /etc, " + "/proc, /sys, /dev, /run, /var/run, /boot, /var/lib/docker, " + "/var/run/docker.sock) are denylisted regardless of mode."),
|
|
11197
11221
|
allowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool allowlist passed verbatim to Claude Code's --allowedTools " + "flag. Supports patterns like 'Bash(git *)' or 'Edit(*.md)' that the " + "coarse `tools.allow` field can't express. When set, Claude Code OR-merges " + "with `tools.allow` (granular only when present, otherwise coarse — chosen " + "via #199 to keep blast radius minimal for existing operators on tools.allow). " + "See #199."),
|
|
11198
11222
|
disallowed_tools: exports_external.array(exports_external.string()).optional().describe("Granular tool denylist passed verbatim to Claude Code's --disallowedTools " + "flag. Same pattern syntax as allowed_tools (e.g. 'Bash(rm *)'). See #199."),
|
|
11199
11223
|
extra_stable_files: exports_external.array(exports_external.string()).optional().describe("Extra filenames (relative to the agent's workspace directory) to append " + "to the stable bootstrap render. Loaded once at session start via " + "`--append-system-prompt`. Missing files are silently skipped. " + "Example: ['BRIEF.md', 'CONTEXT.md']."),
|
|
11200
11224
|
code_repos: exports_external.array(CodeRepoEntrySchema).optional().describe("Git repositories this agent is allowed to claim worktrees from. " + "Each entry provides a short name alias, a source path, and an " + "optional concurrency cap (default 5). When code_repos is set, " + "claim_worktree accepts the alias as the repo argument. " + "Absolute paths may always be passed regardless of this list."),
|
|
11201
|
-
drive:
|
|
11225
|
+
drive: AgentGoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Per-agent " + "google_workspace overrides (currently approvers + tier). When set, " + "replaces the top-level approvers list for this agent. " + "google_client_id/secret are not per-agent — they live at the top level."),
|
|
11226
|
+
google_workspace: AgentGoogleWorkspaceConfigSchema.describe("RFC G canonical key. Per-agent Google Workspace overrides — currently " + "approvers (replaces, does not extend the top-level list) and tier " + "(`core` | `extended` | `complete`, replaces top-level default). " + "google_client_id/secret are not per-agent — they live at the top level. " + "Mutually exclusive with `drive:` on the same agent (loader fails fast " + "if both are set)."),
|
|
11202
11227
|
repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
|
|
11203
11228
|
url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
|
|
11204
11229
|
branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
|
|
@@ -11206,7 +11231,13 @@ var init_schema = __esm(() => {
|
|
|
11206
11231
|
experimental: exports_external.object({
|
|
11207
11232
|
legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent " + "under the legacy PTY supervisor instead. Default: false."),
|
|
11208
11233
|
legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script " + "behaviour instead of the tmux send-keys path. Default: false.")
|
|
11209
|
-
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent.")
|
|
11234
|
+
}).optional().describe("Opt-in flags for experimental / legacy behaviours. Cascades through " + "defaults → profile → per-agent."),
|
|
11235
|
+
resources: exports_external.object({
|
|
11236
|
+
memory: exports_external.string().regex(/^\d+(\.\d+)?[kmgKMG]?$/).optional(),
|
|
11237
|
+
memory_reservation: exports_external.string().regex(/^\d+(\.\d+)?[kmgKMG]?$/).optional(),
|
|
11238
|
+
pids_limit: exports_external.number().int().positive().optional(),
|
|
11239
|
+
cpus: exports_external.number().positive().optional()
|
|
11240
|
+
}).optional()
|
|
11210
11241
|
});
|
|
11211
11242
|
TelegramConfigSchema = exports_external.object({
|
|
11212
11243
|
bot_token: exports_external.string().describe("Telegram bot token or vault reference (e.g., 'vault:telegram-bot-token')"),
|
|
@@ -11229,13 +11260,26 @@ var init_schema = __esm(() => {
|
|
|
11229
11260
|
socket: exports_external.string().default("~/.switchroom/vault-broker.sock").describe("Unix domain socket path for the vault-broker daemon"),
|
|
11230
11261
|
enabled: exports_external.boolean().default(true).describe("Whether to start the vault-broker daemon on agent launch"),
|
|
11231
11262
|
autoUnlock: exports_external.boolean().default(false).describe("Auto-unlock the vault at broker start using a machine-bound " + "encrypted blob. Off by default. When enabled, the broker reads " + "the configured blob path, derives the AES key from /etc/machine-id, " + "decrypts the passphrase, and unlocks the vault — no sudo, no " + "systemd-creds, no TPM. Run `switchroom vault broker " + "enable-auto-unlock` once to write the blob."),
|
|
11232
|
-
autoUnlockCredentialPath: exports_external.string().default("~/.switchroom/vault-auto-unlock").describe("Path to the machine-bound auto-unlock blob (see " + "src/vault/auto-unlock.ts for the format). Default lives under " + "~/.switchroom so it can be bind-mounted into the vault-broker " + "container by docker compose. Tilde-expansion happens " + "at read time.")
|
|
11233
|
-
|
|
11263
|
+
autoUnlockCredentialPath: exports_external.string().default("~/.switchroom/vault-auto-unlock").describe("Path to the machine-bound auto-unlock blob (see " + "src/vault/auto-unlock.ts for the format). Default lives under " + "~/.switchroom so it can be bind-mounted into the vault-broker " + "container by docker compose. Tilde-expansion happens " + "at read time."),
|
|
11264
|
+
approvalAuth: exports_external.enum(["passphrase", "telegram-id"]).default("passphrase").describe("Posture for tap-to-Approve on vault grant cards. `passphrase` " + "(default) prompts the operator to type the vault passphrase on " + "every Approve — two-factor (Telegram ID + passphrase). " + "`telegram-id` mints immediately on Approve with no passphrase " + "prompt — single-factor (Telegram ID only); REQUIRES " + "`autoUnlock: true` so the broker already holds the passphrase. " + "Trades a factor of security for smoother UX; opt-in only."),
|
|
11265
|
+
postureMintAgents: exports_external.array(exports_external.string().min(1)).default([]).describe("Per-agent opt-in for posture-attested broker calls (`mint_grant` / " + "`list_grants` / `put` with `attest_via_posture: true`). Only agents " + "whose names are in this list can use the silent-mint path under " + "`approvalAuth: telegram-id`. Default `[]` — no agent can self-mint " + "until the operator explicitly opts it in. The request's `agent` " + "field must also equal the calling peer's resolved agent name " + "(broker rejects cross-agent posture mints). When `approvalAuth` is " + "`passphrase` this list is ignored — passphrase attestation still " + "works as before. Each entry is an agent slug exactly as it appears " + "under `agents:` in this config.")
|
|
11266
|
+
}).default({}).superRefine((broker, ctx) => {
|
|
11267
|
+
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
11268
|
+
ctx.addIssue({
|
|
11269
|
+
code: exports_external.ZodIssueCode.custom,
|
|
11270
|
+
message: "`vault.broker.approvalAuth: telegram-id` requires `autoUnlock: true` — single-factor approval needs the broker already unlocked at startup.",
|
|
11271
|
+
path: ["approvalAuth"]
|
|
11272
|
+
});
|
|
11273
|
+
}
|
|
11274
|
+
}).describe("Vault-broker daemon configuration. The broker holds the decrypted vault " + "in memory and serves secrets to cron scripts via a Unix socket, so the " + "vault passphrase is entered once at startup rather than per-cron invocation.")
|
|
11234
11275
|
});
|
|
11235
11276
|
QuotaConfigSchema = exports_external.object({
|
|
11236
11277
|
weekly_budget_usd: exports_external.number().positive().optional().describe("Weekly USD spend budget. If unset, the greeting shows raw usage only."),
|
|
11237
11278
|
monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
|
|
11238
11279
|
});
|
|
11280
|
+
HostControlConfigSchema = exports_external.object({
|
|
11281
|
+
enabled: exports_external.boolean().optional().describe("Opt-in to the host-control daemon. Default: false. " + "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. " + "Since Phase 2 (#1175 PR γ) the gateway's /restart, /new, /reset, " + "and /update apply slash-commands automatically dispatch through " + "hostd when enabled — replacing the in-container " + "`spawnSwitchroomDetached` shellout that requires docker access. " + "Set enabled: true on docker-mode installs to make those verbs work " + "(they otherwise fail because the agent container has no docker " + "binary/socket).")
|
|
11282
|
+
});
|
|
11239
11283
|
SwitchroomConfigSchema = exports_external.object({
|
|
11240
11284
|
switchroom: exports_external.object({
|
|
11241
11285
|
version: exports_external.literal(1).describe("Config schema version"),
|
|
@@ -11246,8 +11290,28 @@ var init_schema = __esm(() => {
|
|
|
11246
11290
|
telegram: TelegramConfigSchema,
|
|
11247
11291
|
memory: MemoryBackendConfigSchema.optional(),
|
|
11248
11292
|
vault: VaultConfigSchema.optional(),
|
|
11249
|
-
|
|
11293
|
+
auth: exports_external.object({
|
|
11294
|
+
active: exports_external.string().min(1).optional().describe("Fleet-wide active Anthropic account label. Every agent without " + "an explicit `agent.auth.override` uses this account. See " + "docs/auth.md for the full model. Set by `switchroom auth use <label>`."),
|
|
11295
|
+
fallback_order: exports_external.array(exports_external.string().min(1)).optional().describe("Ordered list of account labels for `switchroom auth rotate` to cycle " + "through when the active account hits a quota event. First entry is " + "normally the same as `auth.active`. When unset, `rotate` is a no-op."),
|
|
11296
|
+
consumers: exports_external.array(exports_external.object({
|
|
11297
|
+
name: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, {
|
|
11298
|
+
message: "Consumer name must be a path-safe slug (letters, digits, underscore, hyphen)"
|
|
11299
|
+
}).describe("Socket-path identity; binds at /run/switchroom/auth-broker/<name>/sock"),
|
|
11300
|
+
account: exports_external.string().min(1).describe("Pinned account label for this consumer. `get-credentials` returns " + "this account's credentials; `mark-exhausted` from this consumer " + "only affects this account."),
|
|
11301
|
+
uid: exports_external.number().int().nonnegative().optional().describe("Optional UID to chown the consumer socket to (defaults to 0 = root, " + "suitable for sibling containers running as root).")
|
|
11302
|
+
})).optional().describe("Non-agent peers that hold a broker socket (RFC H §4.8). Each gets " + "its own `/run/switchroom/auth-broker/<name>/sock` chowned to its UID. " + "Consumers cannot be admins; a consumer name that collides with an " + "agent (whether that agent has `admin: true` or not) is a config " + "error caught at schema validation.")
|
|
11303
|
+
}).optional().describe("Switchroom-auth-broker configuration (RFC H). Fleet-wide active account, " + "fallback order, admin-agent ACL, and ephemeral-consumer surface. " + "Required from the v0.8+ schema onwards; pre-v0.8 fleets are migrated " + "in-place by `switchroom apply` (see src/auth/migrate-schema.ts)."),
|
|
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
|
+
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)."),
|
|
11250
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.optional().describe("Optional host-control daemon configuration. See RFC C " + "(docs/rfcs/host-control-daemon.md) and the field-level help on " + "`enabled` for the Phase 1 scope."),
|
|
11308
|
+
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
11309
|
+
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
11310
|
+
}).transform((v) => v.trim().toLowerCase()), exports_external.object({
|
|
11311
|
+
enabled_for: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
11312
|
+
message: "Agent name must match the standard agent-name pattern"
|
|
11313
|
+
})).describe("Agent slugs that may read this account's vault slots " + "(`google:<account>:refresh_token` etc). Per-agent ACL is " + "enforced at the broker, not at the agent identity layer — " + "the agent still authenticates via socket-path-as-identity " + "per RFC D §4.1, broker just gates the cross-agent token share.")
|
|
11314
|
+
})).optional().describe("RFC G Phase 2: per-Google-account ACL for vault slots holding " + "OAuth refresh tokens. Maps account email → list of agents " + "permitted to read that account's slots. Written by `switchroom " + "auth google enable|disable` (Phase 3); read by the broker on " + "every Google slot access. Replaces RFC D's per-agent vault slot " + "scope (which can't express 'two agents share one Google account')."),
|
|
11251
11315
|
defaults: AgentDefaultsSchema.describe("Implicit bottom-of-cascade profile applied to every agent before " + "per-agent config and `extends:` resolution. Tools, mcp_servers, and " + "schedule are unioned/concatenated; scalars and nested objects are " + "shallow-merged with per-agent values winning."),
|
|
11252
11316
|
profiles: exports_external.record(exports_external.string(), ProfileSchema).optional().describe("Named profile definitions. Agents reference via `extends: <name>`. " + "Inline profiles declared here take priority over filesystem " + "profiles/<name>/ directories when both exist."),
|
|
11253
11317
|
agents: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
@@ -11282,6 +11346,142 @@ function resolveDualPath(pathStr) {
|
|
|
11282
11346
|
var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk";
|
|
11283
11347
|
var init_paths = () => {};
|
|
11284
11348
|
|
|
11349
|
+
// src/config/overlay-schema.ts
|
|
11350
|
+
var OverlayDocSchema;
|
|
11351
|
+
var init_overlay_schema = __esm(() => {
|
|
11352
|
+
init_zod();
|
|
11353
|
+
init_schema();
|
|
11354
|
+
OverlayDocSchema = exports_external.object({
|
|
11355
|
+
schedule: exports_external.array(ScheduleEntrySchema).optional(),
|
|
11356
|
+
skills: exports_external.array(exports_external.string()).optional()
|
|
11357
|
+
}).strict();
|
|
11358
|
+
});
|
|
11359
|
+
|
|
11360
|
+
// src/config/overlay-loader.ts
|
|
11361
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
|
|
11362
|
+
import { resolve as resolve3 } from "node:path";
|
|
11363
|
+
function overlayDirFor(agentName, subdir) {
|
|
11364
|
+
const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
|
|
11365
|
+
return resolve3(base);
|
|
11366
|
+
}
|
|
11367
|
+
function listYamlFiles(dir) {
|
|
11368
|
+
if (!existsSync5(dir))
|
|
11369
|
+
return [];
|
|
11370
|
+
let entries;
|
|
11371
|
+
try {
|
|
11372
|
+
entries = readdirSync2(dir);
|
|
11373
|
+
} catch {
|
|
11374
|
+
return [];
|
|
11375
|
+
}
|
|
11376
|
+
const out = [];
|
|
11377
|
+
for (const name of entries) {
|
|
11378
|
+
if (!/\.ya?ml$/i.test(name))
|
|
11379
|
+
continue;
|
|
11380
|
+
const full = resolve3(dir, name);
|
|
11381
|
+
try {
|
|
11382
|
+
if (statSync3(full).isFile())
|
|
11383
|
+
out.push(full);
|
|
11384
|
+
} catch {}
|
|
11385
|
+
}
|
|
11386
|
+
return out.sort();
|
|
11387
|
+
}
|
|
11388
|
+
function stampOverlay(entry) {
|
|
11389
|
+
Object.defineProperty(entry, OVERLAY_SOURCE, {
|
|
11390
|
+
value: true,
|
|
11391
|
+
enumerable: false,
|
|
11392
|
+
configurable: false,
|
|
11393
|
+
writable: false
|
|
11394
|
+
});
|
|
11395
|
+
return entry;
|
|
11396
|
+
}
|
|
11397
|
+
function applyAgentOverlays(config) {
|
|
11398
|
+
const warnings = [];
|
|
11399
|
+
const agents = config.agents ?? {};
|
|
11400
|
+
for (const [agentName, agentCfg] of Object.entries(agents)) {
|
|
11401
|
+
try {
|
|
11402
|
+
const scheduleDir = overlayDirFor(agentName, "schedule.d");
|
|
11403
|
+
const files = listYamlFiles(scheduleDir);
|
|
11404
|
+
if (files.length > 0) {
|
|
11405
|
+
const merged = [...agentCfg.schedule ?? []];
|
|
11406
|
+
for (const file of files) {
|
|
11407
|
+
try {
|
|
11408
|
+
const raw = readFileSync5(file, "utf-8");
|
|
11409
|
+
const parsed = $parse(raw);
|
|
11410
|
+
const doc = OverlayDocSchema.parse(parsed);
|
|
11411
|
+
for (const entry of doc.schedule ?? []) {
|
|
11412
|
+
if (entry.secrets && entry.secrets.length > 0) {
|
|
11413
|
+
const w = {
|
|
11414
|
+
agent: agentName,
|
|
11415
|
+
file,
|
|
11416
|
+
reason: "Overlay schedule entry declares secrets — dropped pending Phase E operator approval"
|
|
11417
|
+
};
|
|
11418
|
+
warnings.push(w);
|
|
11419
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
11420
|
+
continue;
|
|
11421
|
+
}
|
|
11422
|
+
merged.push(stampOverlay(entry));
|
|
11423
|
+
}
|
|
11424
|
+
} catch (err) {
|
|
11425
|
+
const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
|
|
11426
|
+
warnings.push({ agent: agentName, file, reason });
|
|
11427
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${reason}`);
|
|
11428
|
+
}
|
|
11429
|
+
}
|
|
11430
|
+
agentCfg.schedule = merged;
|
|
11431
|
+
}
|
|
11432
|
+
} catch (err) {
|
|
11433
|
+
warnings.push({
|
|
11434
|
+
agent: agentName,
|
|
11435
|
+
file: "(agent schedule overlay scan)",
|
|
11436
|
+
reason: `unexpected error: ${err.message}`
|
|
11437
|
+
});
|
|
11438
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' schedule.d: unexpected error: ${err.message}`);
|
|
11439
|
+
}
|
|
11440
|
+
try {
|
|
11441
|
+
const skillsDir = overlayDirFor(agentName, "skills.d");
|
|
11442
|
+
const skillFiles = listYamlFiles(skillsDir);
|
|
11443
|
+
if (skillFiles.length === 0) {} else {
|
|
11444
|
+
const merged = [...agentCfg.skills ?? []];
|
|
11445
|
+
const seen = new Set(merged);
|
|
11446
|
+
for (const file of skillFiles) {
|
|
11447
|
+
try {
|
|
11448
|
+
const raw = readFileSync5(file, "utf-8");
|
|
11449
|
+
const parsed = $parse(raw);
|
|
11450
|
+
const doc = OverlayDocSchema.parse(parsed);
|
|
11451
|
+
for (const skillName of doc.skills ?? []) {
|
|
11452
|
+
if (seen.has(skillName))
|
|
11453
|
+
continue;
|
|
11454
|
+
seen.add(skillName);
|
|
11455
|
+
merged.push(skillName);
|
|
11456
|
+
}
|
|
11457
|
+
} catch (err) {
|
|
11458
|
+
const reason = err instanceof ZodError ? `schema rejection: ${err.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join("; ")}` : `parse error: ${err.message}`;
|
|
11459
|
+
warnings.push({ agent: agentName, file, reason });
|
|
11460
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${reason}`);
|
|
11461
|
+
}
|
|
11462
|
+
}
|
|
11463
|
+
agentCfg.skills = merged;
|
|
11464
|
+
}
|
|
11465
|
+
} catch (err) {
|
|
11466
|
+
warnings.push({
|
|
11467
|
+
agent: agentName,
|
|
11468
|
+
file: "(agent skills overlay scan)",
|
|
11469
|
+
reason: `unexpected error: ${err.message}`
|
|
11470
|
+
});
|
|
11471
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' skills.d: unexpected error: ${err.message}`);
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
return { config, warnings };
|
|
11475
|
+
}
|
|
11476
|
+
var OVERLAY_SOURCE;
|
|
11477
|
+
var init_overlay_loader = __esm(() => {
|
|
11478
|
+
init_dist();
|
|
11479
|
+
init_zod();
|
|
11480
|
+
init_overlay_schema();
|
|
11481
|
+
init_paths();
|
|
11482
|
+
OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
11483
|
+
});
|
|
11484
|
+
|
|
11285
11485
|
// src/config/loader.ts
|
|
11286
11486
|
var exports_loader = {};
|
|
11287
11487
|
__export(exports_loader, {
|
|
@@ -11291,36 +11491,74 @@ __export(exports_loader, {
|
|
|
11291
11491
|
findConfigFile: () => findConfigFile,
|
|
11292
11492
|
ConfigError: () => ConfigError
|
|
11293
11493
|
});
|
|
11294
|
-
import { readFileSync as
|
|
11494
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
|
|
11295
11495
|
import { homedir } from "node:os";
|
|
11296
|
-
import { resolve as
|
|
11496
|
+
import { resolve as resolve4 } from "node:path";
|
|
11297
11497
|
function formatZodErrors(error) {
|
|
11298
11498
|
return error.errors.map((e) => {
|
|
11299
11499
|
const path = e.path.join(".");
|
|
11300
11500
|
return ` ${path}: ${e.message}`;
|
|
11301
11501
|
});
|
|
11302
11502
|
}
|
|
11503
|
+
function coerceLegacyGoogleWorkspaceKeys(parsed, filePath) {
|
|
11504
|
+
const stableStringify = (v) => {
|
|
11505
|
+
if (v === null || typeof v !== "object")
|
|
11506
|
+
return JSON.stringify(v);
|
|
11507
|
+
if (Array.isArray(v))
|
|
11508
|
+
return `[${v.map(stableStringify).join(",")}]`;
|
|
11509
|
+
const obj = v;
|
|
11510
|
+
const keys = Object.keys(obj).sort();
|
|
11511
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
11512
|
+
};
|
|
11513
|
+
const aliasInPlace = (obj, where) => {
|
|
11514
|
+
const a = obj.drive;
|
|
11515
|
+
const b = obj.google_workspace;
|
|
11516
|
+
if (a !== undefined && b !== undefined) {
|
|
11517
|
+
if (stableStringify(a) !== stableStringify(b)) {
|
|
11518
|
+
throw new ConfigError(`Both \`drive:\` and \`google_workspace:\` are set on ${where} in ${filePath} with different values.`, [
|
|
11519
|
+
" These are aliases — pick one and remove the other.",
|
|
11520
|
+
" `google_workspace:` is the RFC G canonical key; `drive:` is the legacy alias.",
|
|
11521
|
+
" Allowed during transition: setting both with identical values."
|
|
11522
|
+
]);
|
|
11523
|
+
}
|
|
11524
|
+
return;
|
|
11525
|
+
}
|
|
11526
|
+
if (a !== undefined && b === undefined)
|
|
11527
|
+
obj.google_workspace = a;
|
|
11528
|
+
if (b !== undefined && a === undefined)
|
|
11529
|
+
obj.drive = b;
|
|
11530
|
+
};
|
|
11531
|
+
aliasInPlace(parsed, "the top level");
|
|
11532
|
+
const agents = parsed.agents;
|
|
11533
|
+
if (agents && typeof agents === "object" && !Array.isArray(agents)) {
|
|
11534
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
11535
|
+
if (agent && typeof agent === "object" && !Array.isArray(agent)) {
|
|
11536
|
+
aliasInPlace(agent, `agent \`${name}\``);
|
|
11537
|
+
}
|
|
11538
|
+
}
|
|
11539
|
+
}
|
|
11540
|
+
}
|
|
11303
11541
|
function findConfigFile(startDir) {
|
|
11304
11542
|
const envPath = process.env.SWITCHROOM_CONFIG;
|
|
11305
11543
|
const home2 = homedir();
|
|
11306
|
-
const userDir =
|
|
11544
|
+
const userDir = resolve4(home2, ".switchroom");
|
|
11307
11545
|
const searchPaths = [
|
|
11308
|
-
envPath ?
|
|
11309
|
-
startDir ?
|
|
11310
|
-
startDir ?
|
|
11311
|
-
startDir ?
|
|
11312
|
-
startDir ?
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11546
|
+
envPath ? resolve4(envPath) : null,
|
|
11547
|
+
startDir ? resolve4(startDir, "switchroom.yaml") : null,
|
|
11548
|
+
startDir ? resolve4(startDir, "switchroom.yml") : null,
|
|
11549
|
+
startDir ? resolve4(startDir, "clerk.yaml") : null,
|
|
11550
|
+
startDir ? resolve4(startDir, "clerk.yml") : null,
|
|
11551
|
+
resolve4(process.cwd(), "switchroom.yaml"),
|
|
11552
|
+
resolve4(process.cwd(), "switchroom.yml"),
|
|
11553
|
+
resolve4(process.cwd(), "clerk.yaml"),
|
|
11554
|
+
resolve4(process.cwd(), "clerk.yml"),
|
|
11555
|
+
resolve4(userDir, "switchroom.yaml"),
|
|
11556
|
+
resolve4(userDir, "switchroom.yml"),
|
|
11557
|
+
resolve4(userDir, "clerk.yaml"),
|
|
11558
|
+
resolve4(userDir, "clerk.yml")
|
|
11321
11559
|
].filter(Boolean);
|
|
11322
11560
|
for (const path of searchPaths) {
|
|
11323
|
-
if (
|
|
11561
|
+
if (existsSync6(path)) {
|
|
11324
11562
|
return path;
|
|
11325
11563
|
}
|
|
11326
11564
|
}
|
|
@@ -11328,12 +11566,12 @@ function findConfigFile(startDir) {
|
|
|
11328
11566
|
}
|
|
11329
11567
|
function loadConfig(configPath) {
|
|
11330
11568
|
const filePath = configPath ?? findConfigFile();
|
|
11331
|
-
if (!
|
|
11569
|
+
if (!existsSync6(filePath)) {
|
|
11332
11570
|
throw new ConfigError(`Config file not found: ${filePath}`);
|
|
11333
11571
|
}
|
|
11334
11572
|
let raw;
|
|
11335
11573
|
try {
|
|
11336
|
-
raw =
|
|
11574
|
+
raw = readFileSync6(filePath, "utf-8");
|
|
11337
11575
|
} catch (err) {
|
|
11338
11576
|
throw new ConfigError(`Failed to read config file: ${filePath}`, [
|
|
11339
11577
|
` ${err.message}`
|
|
@@ -11352,16 +11590,26 @@ function loadConfig(configPath) {
|
|
|
11352
11590
|
obj.switchroom = obj.clerk;
|
|
11353
11591
|
delete obj.clerk;
|
|
11354
11592
|
}
|
|
11593
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11594
|
+
coerceLegacyGoogleWorkspaceKeys(parsed, filePath);
|
|
11595
|
+
}
|
|
11596
|
+
let config;
|
|
11355
11597
|
try {
|
|
11356
|
-
|
|
11598
|
+
config = SwitchroomConfigSchema.parse(parsed);
|
|
11357
11599
|
} catch (err) {
|
|
11358
11600
|
if (err instanceof ZodError) {
|
|
11359
11601
|
throw new ConfigError("Invalid switchroom.yaml configuration", formatZodErrors(err));
|
|
11360
11602
|
}
|
|
11361
11603
|
throw err;
|
|
11362
11604
|
}
|
|
11605
|
+
applyAgentOverlays(config);
|
|
11606
|
+
return config;
|
|
11363
11607
|
}
|
|
11364
11608
|
function resolveAgentsDir(config) {
|
|
11609
|
+
const override = process.env.SWITCHROOM_AGENTS_DIR;
|
|
11610
|
+
if (override && override.length > 0 && override.startsWith("/")) {
|
|
11611
|
+
return override;
|
|
11612
|
+
}
|
|
11365
11613
|
return resolveDualPath(config.switchroom.agents_dir);
|
|
11366
11614
|
}
|
|
11367
11615
|
function resolvePath(pathStr) {
|
|
@@ -11373,6 +11621,7 @@ var init_loader = __esm(() => {
|
|
|
11373
11621
|
init_zod();
|
|
11374
11622
|
init_schema();
|
|
11375
11623
|
init_paths();
|
|
11624
|
+
init_overlay_loader();
|
|
11376
11625
|
ConfigError = class ConfigError extends Error {
|
|
11377
11626
|
details;
|
|
11378
11627
|
constructor(message, details) {
|
|
@@ -11385,7 +11634,7 @@ var init_loader = __esm(() => {
|
|
|
11385
11634
|
|
|
11386
11635
|
// src/vault/broker/server.ts
|
|
11387
11636
|
import * as net from "node:net";
|
|
11388
|
-
import { mkdirSync as mkdirSync5, chmodSync as chmodSync4, chownSync, existsSync as
|
|
11637
|
+
import { mkdirSync as mkdirSync5, chmodSync as chmodSync4, chownSync, existsSync as existsSync8, readFileSync as readFileSync8, readdirSync as readdirSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3, renameSync as renameSync3 } from "node:fs";
|
|
11389
11638
|
|
|
11390
11639
|
// src/agents/compose.ts
|
|
11391
11640
|
import { createHash } from "node:crypto";
|
|
@@ -11653,6 +11902,23 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11653
11902
|
const a = merged.extra_stable_files ?? [];
|
|
11654
11903
|
merged.extra_stable_files = dedupe([...d, ...a]);
|
|
11655
11904
|
}
|
|
11905
|
+
const dReactions = defaults.reactions;
|
|
11906
|
+
const mReactions = merged.reactions;
|
|
11907
|
+
if (dReactions || mReactions) {
|
|
11908
|
+
const base = dReactions ?? {};
|
|
11909
|
+
const override = mReactions ?? {};
|
|
11910
|
+
const combined = { ...base };
|
|
11911
|
+
for (const [k, v] of Object.entries(override)) {
|
|
11912
|
+
if (v !== undefined)
|
|
11913
|
+
combined[k] = v;
|
|
11914
|
+
}
|
|
11915
|
+
merged.reactions = combined;
|
|
11916
|
+
}
|
|
11917
|
+
if (defaults.resources || merged.resources) {
|
|
11918
|
+
const d = defaults.resources ?? {};
|
|
11919
|
+
const a = merged.resources ?? {};
|
|
11920
|
+
merged.resources = { ...d, ...a };
|
|
11921
|
+
}
|
|
11656
11922
|
if (defaults.experimental || merged.experimental) {
|
|
11657
11923
|
const d = defaults.experimental ?? {};
|
|
11658
11924
|
const a = merged.experimental ?? {};
|
|
@@ -11665,118 +11931,501 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11665
11931
|
mergeAgentConfig.notifiedWorkerIsolationMove = false;
|
|
11666
11932
|
})(mergeAgentConfig ||= {});
|
|
11667
11933
|
|
|
11668
|
-
// src/
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
function allocateAgentUid(name) {
|
|
11672
|
-
const hash = createHash("sha256").update(name).digest();
|
|
11673
|
-
const u32 = hash.readUInt32BE(0);
|
|
11674
|
-
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
11675
|
-
return AGENT_UID_MIN + u32 % range;
|
|
11676
|
-
}
|
|
11677
|
-
|
|
11678
|
-
// src/vault/broker/server.ts
|
|
11679
|
-
import { dirname as dirname4, resolve as resolve4, basename as basename3 } from "node:path";
|
|
11680
|
-
import * as os3 from "node:os";
|
|
11681
|
-
import * as path3 from "node:path";
|
|
11682
|
-
|
|
11683
|
-
// src/vault/vault.ts
|
|
11684
|
-
import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
|
|
11685
|
-
import {
|
|
11686
|
-
readFileSync as readFileSync2,
|
|
11687
|
-
writeFileSync,
|
|
11688
|
-
existsSync as existsSync2,
|
|
11689
|
-
renameSync,
|
|
11690
|
-
mkdirSync,
|
|
11691
|
-
unlinkSync as unlinkSync2
|
|
11692
|
-
} from "node:fs";
|
|
11693
|
-
import { dirname, basename, resolve } from "node:path";
|
|
11934
|
+
// src/vault/broker/peercred.ts
|
|
11935
|
+
import { execFileSync } from "node:child_process";
|
|
11936
|
+
import { readFileSync, readlinkSync, fstatSync } from "node:fs";
|
|
11694
11937
|
|
|
11695
|
-
// src/vault/
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
closeSync,
|
|
11700
|
-
writeSync,
|
|
11701
|
-
fsyncSync,
|
|
11702
|
-
unlinkSync,
|
|
11703
|
-
readFileSync,
|
|
11704
|
-
readdirSync,
|
|
11705
|
-
rmdirSync,
|
|
11706
|
-
statSync,
|
|
11707
|
-
constants as fsConstants
|
|
11708
|
-
} from "node:fs";
|
|
11709
|
-
var DEFAULT_LOCK_RETRY_MS = 5000;
|
|
11710
|
-
function lockPathFor(vaultPath) {
|
|
11711
|
-
return `${vaultPath}.lock`;
|
|
11712
|
-
}
|
|
11713
|
-
function readLockHolder(lockPath) {
|
|
11938
|
+
// src/vault/broker/peercred-ffi.ts
|
|
11939
|
+
function getPeerCred(fd) {
|
|
11940
|
+
if (process.platform !== "linux")
|
|
11941
|
+
return null;
|
|
11714
11942
|
try {
|
|
11715
|
-
const
|
|
11716
|
-
const
|
|
11943
|
+
const ffi = __require("bun:ffi");
|
|
11944
|
+
const { dlopen, FFIType, ptr } = ffi;
|
|
11945
|
+
const SOL_SOCKET = 1;
|
|
11946
|
+
const SO_PEERCRED = 17;
|
|
11947
|
+
const UCRED_SIZE = 12;
|
|
11948
|
+
const cache = getPeerCred;
|
|
11949
|
+
const lib = cache._lib ?? (() => {
|
|
11950
|
+
const candidates = ["libc.so.6", "libc.so"];
|
|
11951
|
+
const symbolSpec = {
|
|
11952
|
+
getsockopt: {
|
|
11953
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
|
|
11954
|
+
returns: FFIType.i32
|
|
11955
|
+
}
|
|
11956
|
+
};
|
|
11957
|
+
const errors = [];
|
|
11958
|
+
for (const name of candidates) {
|
|
11959
|
+
try {
|
|
11960
|
+
const opened = dlopen(name, symbolSpec);
|
|
11961
|
+
cache._lib = opened;
|
|
11962
|
+
return opened;
|
|
11963
|
+
} catch (e) {
|
|
11964
|
+
errors.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
11965
|
+
}
|
|
11966
|
+
}
|
|
11967
|
+
process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors.join("; ")}); falling back to ss-parsing.
|
|
11717
11968
|
`);
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11969
|
+
throw new Error("no libc candidate could be opened");
|
|
11970
|
+
})();
|
|
11971
|
+
const credBuf = new ArrayBuffer(UCRED_SIZE);
|
|
11972
|
+
const lenBuf = new Uint32Array(1);
|
|
11973
|
+
lenBuf[0] = UCRED_SIZE;
|
|
11974
|
+
const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
|
|
11975
|
+
if (rc !== 0)
|
|
11721
11976
|
return null;
|
|
11722
|
-
if (
|
|
11977
|
+
if (lenBuf[0] !== UCRED_SIZE)
|
|
11723
11978
|
return null;
|
|
11724
|
-
|
|
11979
|
+
const view = new DataView(credBuf);
|
|
11980
|
+
return {
|
|
11981
|
+
pid: view.getInt32(0, true),
|
|
11982
|
+
uid: view.getInt32(4, true),
|
|
11983
|
+
gid: view.getInt32(8, true)
|
|
11984
|
+
};
|
|
11725
11985
|
} catch {
|
|
11726
11986
|
return null;
|
|
11727
11987
|
}
|
|
11728
11988
|
}
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11989
|
+
|
|
11990
|
+
// src/vault/broker/peercred.ts
|
|
11991
|
+
var SOCKET_PATH_AGENT_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\.sock$/;
|
|
11992
|
+
var SOCKET_PATH_AGENT_SUBDIR_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\/sock$/;
|
|
11993
|
+
var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
|
|
11994
|
+
function socketPathToIdentity(socketPath) {
|
|
11995
|
+
if (typeof socketPath !== "string" || socketPath.length === 0)
|
|
11996
|
+
return null;
|
|
11997
|
+
const m = socketPath.match(SOCKET_PATH_AGENT_RE) ?? socketPath.match(SOCKET_PATH_AGENT_SUBDIR_RE);
|
|
11998
|
+
if (!m)
|
|
11999
|
+
return null;
|
|
12000
|
+
const name = m[1];
|
|
12001
|
+
if (name === "operator")
|
|
12002
|
+
return { kind: "operator" };
|
|
12003
|
+
if (RESERVED_AGENT_NAMES.has(name))
|
|
12004
|
+
return null;
|
|
12005
|
+
return { kind: "agent", name };
|
|
12006
|
+
}
|
|
12007
|
+
function socketPathToAgent(socketPath) {
|
|
12008
|
+
const identity = socketPathToIdentity(socketPath);
|
|
12009
|
+
return identity?.kind === "agent" ? identity.name : null;
|
|
12010
|
+
}
|
|
12011
|
+
function isReservedAgentName(name) {
|
|
12012
|
+
return RESERVED_AGENT_NAMES.has(name);
|
|
12013
|
+
}
|
|
12014
|
+
function unlockSocketFor(dataSocketPath) {
|
|
12015
|
+
if (dataSocketPath.endsWith("/sock")) {
|
|
12016
|
+
return dataSocketPath.slice(0, -"/sock".length) + "/unlock";
|
|
11732
12017
|
}
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
12018
|
+
return dataSocketPath.replace(/\.sock$/, ".unlock.sock");
|
|
12019
|
+
}
|
|
12020
|
+
function parseSsRows(output) {
|
|
12021
|
+
const rows = [];
|
|
12022
|
+
const lines = output.split(`
|
|
12023
|
+
`);
|
|
12024
|
+
for (const line of lines) {
|
|
12025
|
+
if (!line.trim() || line.startsWith("Netid"))
|
|
12026
|
+
continue;
|
|
12027
|
+
const tokens = line.split(/\s+/).filter((t) => t.length > 0);
|
|
12028
|
+
if (tokens.length < 8)
|
|
12029
|
+
continue;
|
|
12030
|
+
const localAddr = tokens[4];
|
|
12031
|
+
const localInode = tokens[5];
|
|
12032
|
+
const peerAddr = tokens[6];
|
|
12033
|
+
const peerInode = tokens[7];
|
|
12034
|
+
const usersToken = tokens.slice(8).join(" ");
|
|
12035
|
+
const m = usersToken.match(/users:\(\(".*?",pid=(\d+),fd=\d+\)\)/);
|
|
12036
|
+
const pid = m ? parseInt(m[1], 10) : null;
|
|
12037
|
+
rows.push({ localAddr, localInode, peerAddr, peerInode, pid });
|
|
11738
12038
|
}
|
|
12039
|
+
return rows;
|
|
11739
12040
|
}
|
|
11740
|
-
function
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
12041
|
+
function findClientPids(rows, socketPath) {
|
|
12042
|
+
const pids = [];
|
|
12043
|
+
for (const serverRow of rows) {
|
|
12044
|
+
if (serverRow.localAddr !== socketPath)
|
|
12045
|
+
continue;
|
|
12046
|
+
for (const clientRow of rows) {
|
|
12047
|
+
if (clientRow.localAddr !== "*")
|
|
12048
|
+
continue;
|
|
12049
|
+
if (clientRow.localInode !== serverRow.peerInode)
|
|
12050
|
+
continue;
|
|
12051
|
+
if (clientRow.pid === null)
|
|
12052
|
+
continue;
|
|
12053
|
+
pids.push(clientRow.pid);
|
|
12054
|
+
break;
|
|
11751
12055
|
}
|
|
11752
|
-
rmdirSync(lockPath);
|
|
11753
|
-
return true;
|
|
11754
|
-
} catch {
|
|
11755
|
-
return false;
|
|
11756
12056
|
}
|
|
12057
|
+
return pids;
|
|
11757
12058
|
}
|
|
11758
|
-
function
|
|
11759
|
-
const
|
|
11760
|
-
const
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
12059
|
+
function findClientPidByServerInode(rows, socketPath, serverInode) {
|
|
12060
|
+
const serverInodeStr = String(serverInode);
|
|
12061
|
+
for (const serverRow of rows) {
|
|
12062
|
+
if (serverRow.localAddr !== socketPath)
|
|
12063
|
+
continue;
|
|
12064
|
+
if (serverRow.localInode !== serverInodeStr)
|
|
12065
|
+
continue;
|
|
12066
|
+
for (const clientRow of rows) {
|
|
12067
|
+
if (clientRow.localAddr !== "*")
|
|
12068
|
+
continue;
|
|
12069
|
+
if (clientRow.localInode !== serverRow.peerInode)
|
|
12070
|
+
continue;
|
|
12071
|
+
if (clientRow.pid === null)
|
|
12072
|
+
continue;
|
|
12073
|
+
return clientRow.pid;
|
|
12074
|
+
}
|
|
12075
|
+
return null;
|
|
12076
|
+
}
|
|
12077
|
+
return null;
|
|
12078
|
+
}
|
|
12079
|
+
function readUid(pid) {
|
|
12080
|
+
try {
|
|
12081
|
+
const status = readFileSync(`/proc/${pid}/status`, "utf8");
|
|
12082
|
+
const m = status.match(/^Uid:\s+(\d+)/m);
|
|
12083
|
+
if (!m)
|
|
12084
|
+
return null;
|
|
12085
|
+
return parseInt(m[1], 10);
|
|
12086
|
+
} catch {
|
|
12087
|
+
return null;
|
|
12088
|
+
}
|
|
12089
|
+
}
|
|
12090
|
+
function readExe(pid) {
|
|
12091
|
+
try {
|
|
12092
|
+
return readlinkSync(`/proc/${pid}/exe`);
|
|
12093
|
+
} catch {
|
|
12094
|
+
return null;
|
|
12095
|
+
}
|
|
12096
|
+
}
|
|
12097
|
+
function readSystemdUnit(pid) {
|
|
12098
|
+
try {
|
|
12099
|
+
const content = readFileSync(`/proc/${pid}/cgroup`, "utf8");
|
|
12100
|
+
const lines = content.split(`
|
|
12101
|
+
`);
|
|
12102
|
+
for (const line of lines) {
|
|
12103
|
+
if (!line.trim())
|
|
12104
|
+
continue;
|
|
12105
|
+
const parts = line.split(":");
|
|
12106
|
+
if (parts.length < 3)
|
|
12107
|
+
continue;
|
|
12108
|
+
const controller = parts[1];
|
|
12109
|
+
const isV2 = parts[0] === "0" && controller === "";
|
|
12110
|
+
const isV1Systemd = controller === "name=systemd";
|
|
12111
|
+
if (!isV2 && !isV1Systemd)
|
|
12112
|
+
continue;
|
|
12113
|
+
const cgroupPath = parts.slice(2).join(":");
|
|
12114
|
+
const segments = cgroupPath.split("/");
|
|
12115
|
+
const lastSegment = segments[segments.length - 1];
|
|
12116
|
+
if (!lastSegment)
|
|
12117
|
+
continue;
|
|
12118
|
+
if (/^switchroom-[a-zA-Z0-9_-]+(-cron-\d+)?\.service$/.test(lastSegment)) {
|
|
12119
|
+
return lastSegment;
|
|
12120
|
+
}
|
|
12121
|
+
}
|
|
12122
|
+
return null;
|
|
12123
|
+
} catch {
|
|
12124
|
+
return null;
|
|
12125
|
+
}
|
|
12126
|
+
}
|
|
12127
|
+
function verifySystemdUnit(unitName, runner) {
|
|
12128
|
+
let raw;
|
|
12129
|
+
try {
|
|
12130
|
+
const out = runner("systemctl", [
|
|
12131
|
+
"--user",
|
|
12132
|
+
"show",
|
|
12133
|
+
unitName,
|
|
12134
|
+
"--property=LoadState,ActiveState"
|
|
12135
|
+
], { timeout: 500, encoding: "utf8" });
|
|
12136
|
+
raw = typeof out === "string" ? out : out.toString("utf8");
|
|
12137
|
+
} catch {
|
|
12138
|
+
return false;
|
|
12139
|
+
}
|
|
12140
|
+
const props = {};
|
|
12141
|
+
for (const line of raw.split(`
|
|
12142
|
+
`)) {
|
|
12143
|
+
const m = line.match(/^([A-Za-z]+)=(.*)$/);
|
|
12144
|
+
if (m)
|
|
12145
|
+
props[m[1]] = m[2];
|
|
12146
|
+
}
|
|
12147
|
+
if (props.LoadState !== "loaded")
|
|
12148
|
+
return false;
|
|
12149
|
+
if (props.ActiveState !== "active" && props.ActiveState !== "activating") {
|
|
12150
|
+
return false;
|
|
12151
|
+
}
|
|
12152
|
+
return true;
|
|
12153
|
+
}
|
|
12154
|
+
function readFdInode(fd) {
|
|
12155
|
+
try {
|
|
12156
|
+
const stat = fstatSync(fd);
|
|
12157
|
+
return stat.ino;
|
|
12158
|
+
} catch {
|
|
12159
|
+
return null;
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
function fdFromSocket(socket) {
|
|
12163
|
+
const handle = socket._handle;
|
|
12164
|
+
if (!handle || typeof handle.fd !== "number" || handle.fd < 0)
|
|
12165
|
+
return null;
|
|
12166
|
+
return handle.fd;
|
|
12167
|
+
}
|
|
12168
|
+
function identify(socketPath, socket, execFileSyncOverride) {
|
|
12169
|
+
if (process.platform !== "linux") {
|
|
12170
|
+
return null;
|
|
12171
|
+
}
|
|
12172
|
+
const runner = execFileSyncOverride ?? execFileSync;
|
|
12173
|
+
let pid = null;
|
|
12174
|
+
if (socket !== undefined) {
|
|
12175
|
+
const fd = fdFromSocket(socket);
|
|
12176
|
+
if (fd !== null) {
|
|
12177
|
+
const cred = getPeerCred(fd);
|
|
12178
|
+
if (cred !== null)
|
|
12179
|
+
pid = cred.pid;
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
if (pid === null) {
|
|
12183
|
+
let ssOutput;
|
|
12184
|
+
try {
|
|
12185
|
+
const raw = runner("ss", ["-xpn"], {
|
|
12186
|
+
timeout: 200,
|
|
12187
|
+
encoding: "utf8"
|
|
12188
|
+
});
|
|
12189
|
+
ssOutput = typeof raw === "string" ? raw : raw.toString("utf8");
|
|
12190
|
+
} catch {
|
|
12191
|
+
return null;
|
|
12192
|
+
}
|
|
12193
|
+
const rows = parseSsRows(ssOutput);
|
|
12194
|
+
let serverInode = null;
|
|
12195
|
+
if (socket !== undefined) {
|
|
12196
|
+
const fd = fdFromSocket(socket);
|
|
12197
|
+
if (fd !== null)
|
|
12198
|
+
serverInode = readFdInode(fd);
|
|
12199
|
+
}
|
|
12200
|
+
if (serverInode !== null) {
|
|
12201
|
+
pid = findClientPidByServerInode(rows, socketPath, serverInode);
|
|
12202
|
+
} else {
|
|
12203
|
+
const clientPids = findClientPids(rows, socketPath);
|
|
12204
|
+
if (clientPids.length === 0)
|
|
12205
|
+
return null;
|
|
12206
|
+
if (clientPids.length > 1) {
|
|
12207
|
+
process.stderr.write(`[vault-broker] peercred: ${clientPids.length} connected peers found for ${socketPath}; ` + `using pid=${clientPids[0]}. ` + `Multiple simultaneous connections reduce identification accuracy. ` + `(This warning means identify() was called without a socket arg — likely a stale call site.)
|
|
12208
|
+
`);
|
|
12209
|
+
}
|
|
12210
|
+
pid = clientPids[0];
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
if (pid === null)
|
|
12214
|
+
return null;
|
|
12215
|
+
const uid = readUid(pid);
|
|
12216
|
+
if (uid === null) {
|
|
12217
|
+
return null;
|
|
12218
|
+
}
|
|
12219
|
+
const brokerUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
12220
|
+
if (brokerUid !== null && uid !== brokerUid) {
|
|
12221
|
+
process.stderr.write(`[vault-broker] peercred: UID mismatch — caller uid=${uid}, broker uid=${brokerUid}; denying
|
|
12222
|
+
`);
|
|
12223
|
+
return null;
|
|
12224
|
+
}
|
|
12225
|
+
const exe = readExe(pid);
|
|
12226
|
+
if (exe === null) {
|
|
12227
|
+
return null;
|
|
12228
|
+
}
|
|
12229
|
+
const cgroupClaim = readSystemdUnit(pid);
|
|
12230
|
+
let systemdUnit = null;
|
|
12231
|
+
if (cgroupClaim !== null) {
|
|
12232
|
+
if (verifySystemdUnit(cgroupClaim, runner)) {
|
|
12233
|
+
systemdUnit = cgroupClaim;
|
|
12234
|
+
} else {
|
|
12235
|
+
process.stderr.write(`[vault-broker] peercred: cgroup claims unit=${cgroupClaim} but systemd-user does not report it as loaded+running; treating caller as unidentified
|
|
12236
|
+
`);
|
|
12237
|
+
}
|
|
12238
|
+
}
|
|
12239
|
+
return { uid, pid, exe, systemdUnit };
|
|
12240
|
+
}
|
|
12241
|
+
|
|
12242
|
+
// src/memory/hindsight.ts
|
|
12243
|
+
var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
|
|
12244
|
+
|
|
12245
|
+
// src/agents/reconcile-default-skills.ts
|
|
12246
|
+
var warnedMissingPool = new Set;
|
|
12247
|
+
|
|
12248
|
+
// src/agents/compose.ts
|
|
12249
|
+
var AGENT_UID_MIN = 10001;
|
|
12250
|
+
var AGENT_UID_MAX = 10999;
|
|
12251
|
+
function allocateAgentUid(name) {
|
|
12252
|
+
if (isReservedAgentName(name)) {
|
|
12253
|
+
throw new Error(`agent name '${name}' is reserved by switchroom for another identity kind ` + `(see vault/broker/peercred.ts:RESERVED_AGENT_NAMES). Pick a different name.`);
|
|
12254
|
+
}
|
|
12255
|
+
const hash = createHash("sha256").update(name).digest();
|
|
12256
|
+
const u32 = hash.readUInt32BE(0);
|
|
12257
|
+
const range = AGENT_UID_MAX - AGENT_UID_MIN + 1;
|
|
12258
|
+
return AGENT_UID_MIN + u32 % range;
|
|
12259
|
+
}
|
|
12260
|
+
var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
|
|
12261
|
+
|
|
12262
|
+
// src/vault/broker/server.ts
|
|
12263
|
+
import { dirname as dirname4, resolve as resolve5, basename as basename3 } from "node:path";
|
|
12264
|
+
import * as os3 from "node:os";
|
|
12265
|
+
import * as path3 from "node:path";
|
|
12266
|
+
|
|
12267
|
+
// src/vault/vault.ts
|
|
12268
|
+
import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
|
|
12269
|
+
import {
|
|
12270
|
+
readFileSync as readFileSync3,
|
|
12271
|
+
writeFileSync,
|
|
12272
|
+
existsSync as existsSync2,
|
|
12273
|
+
renameSync,
|
|
12274
|
+
mkdirSync,
|
|
12275
|
+
unlinkSync as unlinkSync2,
|
|
12276
|
+
lstatSync,
|
|
12277
|
+
realpathSync
|
|
12278
|
+
} from "node:fs";
|
|
12279
|
+
import { dirname, basename, resolve } from "node:path";
|
|
12280
|
+
|
|
12281
|
+
// src/vault/flock.ts
|
|
12282
|
+
import {
|
|
12283
|
+
existsSync,
|
|
12284
|
+
openSync,
|
|
12285
|
+
closeSync,
|
|
12286
|
+
writeSync,
|
|
12287
|
+
fsyncSync,
|
|
12288
|
+
unlinkSync,
|
|
12289
|
+
readFileSync as readFileSync2,
|
|
12290
|
+
readdirSync,
|
|
12291
|
+
rmdirSync,
|
|
12292
|
+
statSync,
|
|
12293
|
+
constants as fsConstants
|
|
12294
|
+
} from "node:fs";
|
|
12295
|
+
var DEFAULT_LOCK_RETRY_MS = 5000;
|
|
12296
|
+
function lockPathFor(vaultPath) {
|
|
12297
|
+
return `${vaultPath}.lock`;
|
|
12298
|
+
}
|
|
12299
|
+
function readLockHolder(lockPath) {
|
|
12300
|
+
try {
|
|
12301
|
+
const raw = readFileSync2(lockPath, "utf8");
|
|
12302
|
+
const lines = raw.split(`
|
|
12303
|
+
`);
|
|
12304
|
+
const pid = Number.parseInt(lines[0] ?? "", 10);
|
|
12305
|
+
const acquiredAtMs = Number.parseInt(lines[1] ?? "", 10);
|
|
12306
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
12307
|
+
return null;
|
|
12308
|
+
if (!Number.isFinite(acquiredAtMs) || acquiredAtMs <= 0)
|
|
12309
|
+
return null;
|
|
12310
|
+
return { pid, acquiredAtMs, argv0: lines[2] ?? "" };
|
|
12311
|
+
} catch {
|
|
12312
|
+
return null;
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
function pidIsLive(pid) {
|
|
12316
|
+
if (process.platform === "linux") {
|
|
12317
|
+
return existsSync(`/proc/${pid}`);
|
|
12318
|
+
}
|
|
12319
|
+
try {
|
|
12320
|
+
process.kill(pid, 0);
|
|
12321
|
+
return true;
|
|
12322
|
+
} catch {
|
|
12323
|
+
return false;
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
12326
|
+
function parseProcStartTimeMs(statLine, procStat, now) {
|
|
12327
|
+
const tailStart = statLine.lastIndexOf(")");
|
|
12328
|
+
if (tailStart < 0)
|
|
12329
|
+
return null;
|
|
12330
|
+
const tokens = statLine.slice(tailStart + 1).trim().split(/\s+/);
|
|
12331
|
+
const starttimeTicks = Number.parseInt(tokens[19] ?? "", 10);
|
|
12332
|
+
if (!Number.isFinite(starttimeTicks))
|
|
12333
|
+
return null;
|
|
12334
|
+
const btimeMatch = procStat.match(/^btime\s+(\d+)/m);
|
|
12335
|
+
if (!btimeMatch)
|
|
12336
|
+
return null;
|
|
12337
|
+
const bootEpochSec = Number.parseInt(btimeMatch[1], 10);
|
|
12338
|
+
if (!Number.isFinite(bootEpochSec))
|
|
12339
|
+
return null;
|
|
12340
|
+
const USER_HZ = 100;
|
|
12341
|
+
const startEpochMs = bootEpochSec * 1000 + starttimeTicks / USER_HZ * 1000;
|
|
12342
|
+
const bootEpochMs = bootEpochSec * 1000;
|
|
12343
|
+
const SLOP_MS = 5000;
|
|
12344
|
+
if (startEpochMs < bootEpochMs - SLOP_MS || startEpochMs > now + SLOP_MS) {
|
|
12345
|
+
return null;
|
|
12346
|
+
}
|
|
12347
|
+
return Math.floor(startEpochMs);
|
|
12348
|
+
}
|
|
12349
|
+
function pidStartTimeMs(pid) {
|
|
12350
|
+
if (process.platform !== "linux")
|
|
12351
|
+
return null;
|
|
12352
|
+
try {
|
|
12353
|
+
const statLine = readFileSync2(`/proc/${pid}/stat`, "utf8");
|
|
12354
|
+
const procStat = readFileSync2("/proc/stat", "utf8");
|
|
12355
|
+
return parseProcStartTimeMs(statLine, procStat, Date.now());
|
|
12356
|
+
} catch {
|
|
12357
|
+
return null;
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
function pidIsOriginalHolder(holder) {
|
|
12361
|
+
const startMs = pidStartTimeMs(holder.pid);
|
|
12362
|
+
if (startMs === null)
|
|
12363
|
+
return true;
|
|
12364
|
+
return startMs <= holder.acquiredAtMs + 100;
|
|
12365
|
+
}
|
|
12366
|
+
var STALE_MTIME_FLOOR_MS = 1e4;
|
|
12367
|
+
function lockFileMtimeIsOlderThan(lockPath, budgetMs) {
|
|
12368
|
+
try {
|
|
12369
|
+
const s = statSync(lockPath);
|
|
12370
|
+
const threshold = Math.max(STALE_MTIME_FLOOR_MS, budgetMs * 2);
|
|
12371
|
+
return Date.now() - s.mtimeMs > threshold;
|
|
12372
|
+
} catch {
|
|
12373
|
+
return false;
|
|
12374
|
+
}
|
|
12375
|
+
}
|
|
12376
|
+
var SENTINEL_DIR_RECENT_WRITE_MS = 60000;
|
|
12377
|
+
function clearStaleSentinelDir(lockPath) {
|
|
12378
|
+
try {
|
|
12379
|
+
if (!existsSync(lockPath))
|
|
12380
|
+
return true;
|
|
12381
|
+
const s = statSync(lockPath);
|
|
12382
|
+
if (!s.isDirectory())
|
|
12383
|
+
return true;
|
|
12384
|
+
const now = Date.now();
|
|
12385
|
+
for (const entry of readdirSync(lockPath)) {
|
|
12386
|
+
try {
|
|
12387
|
+
const childStat = statSync(`${lockPath}/${entry}`);
|
|
12388
|
+
if (now - childStat.mtimeMs < SENTINEL_DIR_RECENT_WRITE_MS) {
|
|
12389
|
+
return false;
|
|
12390
|
+
}
|
|
12391
|
+
} catch {}
|
|
12392
|
+
}
|
|
12393
|
+
for (const entry of readdirSync(lockPath)) {
|
|
12394
|
+
try {
|
|
12395
|
+
unlinkSync(`${lockPath}/${entry}`);
|
|
12396
|
+
} catch {}
|
|
12397
|
+
}
|
|
12398
|
+
rmdirSync(lockPath);
|
|
12399
|
+
return true;
|
|
12400
|
+
} catch {
|
|
12401
|
+
return false;
|
|
12402
|
+
}
|
|
12403
|
+
}
|
|
12404
|
+
function acquireLock(vaultPath, options = {}) {
|
|
12405
|
+
const budgetMs = options.budgetMs ?? DEFAULT_LOCK_RETRY_MS;
|
|
12406
|
+
const lockPath = lockPathFor(vaultPath);
|
|
12407
|
+
const deadline = Date.now() + budgetMs;
|
|
12408
|
+
const sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
12409
|
+
while (true) {
|
|
12410
|
+
let fd = null;
|
|
12411
|
+
try {
|
|
12412
|
+
fd = openSync(lockPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
|
|
12413
|
+
} catch (err) {
|
|
12414
|
+
const code = err?.code ?? "";
|
|
12415
|
+
if (code === "EEXIST") {
|
|
12416
|
+
let isDir = false;
|
|
12417
|
+
try {
|
|
12418
|
+
isDir = statSync(lockPath).isDirectory();
|
|
12419
|
+
} catch {}
|
|
12420
|
+
if (isDir) {
|
|
12421
|
+
if (clearStaleSentinelDir(lockPath))
|
|
11776
12422
|
continue;
|
|
11777
12423
|
}
|
|
11778
12424
|
const holder = readLockHolder(lockPath);
|
|
11779
|
-
|
|
12425
|
+
const isDeadPid = holder !== null && !pidIsLive(holder.pid);
|
|
12426
|
+
const isReusedPid = holder !== null && pidIsLive(holder.pid) && !pidIsOriginalHolder(holder);
|
|
12427
|
+
const isUnparseableAndOld = holder === null && lockFileMtimeIsOlderThan(lockPath, budgetMs);
|
|
12428
|
+
if (isDeadPid || isReusedPid || isUnparseableAndOld) {
|
|
11780
12429
|
try {
|
|
11781
12430
|
unlinkSync(lockPath);
|
|
11782
12431
|
} catch {}
|
|
@@ -11858,11 +12507,17 @@ var SCRYPT_R = 8;
|
|
|
11858
12507
|
var SCRYPT_P = 1;
|
|
11859
12508
|
var SCRYPT_MAXMEM = 128 * 1024 * 1024;
|
|
11860
12509
|
function atomicWriteFileSync(path, data, mode) {
|
|
11861
|
-
|
|
11862
|
-
|
|
12510
|
+
let effectivePath = path;
|
|
12511
|
+
try {
|
|
12512
|
+
if (existsSync2(path) && lstatSync(path).isSymbolicLink()) {
|
|
12513
|
+
effectivePath = realpathSync(path);
|
|
12514
|
+
}
|
|
12515
|
+
} catch {}
|
|
12516
|
+
const dir = dirname(resolve(effectivePath));
|
|
12517
|
+
const tmp = resolve(dir, `.${basename(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
11863
12518
|
try {
|
|
11864
12519
|
writeFileSync(tmp, data, { encoding: "utf8", mode });
|
|
11865
|
-
renameSync(tmp,
|
|
12520
|
+
renameSync(tmp, effectivePath);
|
|
11866
12521
|
} catch (err) {
|
|
11867
12522
|
try {
|
|
11868
12523
|
if (existsSync2(tmp))
|
|
@@ -11929,7 +12584,7 @@ function openVault(passphrase, vaultPath) {
|
|
|
11929
12584
|
}
|
|
11930
12585
|
let vaultFile;
|
|
11931
12586
|
try {
|
|
11932
|
-
vaultFile = JSON.parse(
|
|
12587
|
+
vaultFile = JSON.parse(readFileSync3(vaultPath, "utf8"));
|
|
11933
12588
|
} catch {
|
|
11934
12589
|
throw new VaultError(`Failed to read vault file: ${vaultPath}`);
|
|
11935
12590
|
}
|
|
@@ -11966,7 +12621,7 @@ function saveVault(passphrase, vaultPath, secrets) {
|
|
|
11966
12621
|
try {
|
|
11967
12622
|
let vaultFile;
|
|
11968
12623
|
try {
|
|
11969
|
-
vaultFile = JSON.parse(
|
|
12624
|
+
vaultFile = JSON.parse(readFileSync3(vaultPath, "utf8"));
|
|
11970
12625
|
} catch {
|
|
11971
12626
|
throw new VaultError(`Failed to read vault file: ${vaultPath}`);
|
|
11972
12627
|
}
|
|
@@ -12004,11 +12659,11 @@ import {
|
|
|
12004
12659
|
chmodSync,
|
|
12005
12660
|
existsSync as existsSync3,
|
|
12006
12661
|
fsyncSync as fsyncSync3,
|
|
12007
|
-
lstatSync,
|
|
12662
|
+
lstatSync as lstatSync2,
|
|
12008
12663
|
mkdirSync as mkdirSync2,
|
|
12009
12664
|
openSync as openSync3,
|
|
12010
12665
|
closeSync as closeSync3,
|
|
12011
|
-
readFileSync as
|
|
12666
|
+
readFileSync as readFileSync4,
|
|
12012
12667
|
renameSync as renameSync2,
|
|
12013
12668
|
statSync as statSync2,
|
|
12014
12669
|
symlinkSync,
|
|
@@ -12054,451 +12709,162 @@ function runMigration(home, opts) {
|
|
|
12054
12709
|
const oldRealStat = statSync2(oldPath);
|
|
12055
12710
|
const newRealStat = statSync2(newPath);
|
|
12056
12711
|
return {
|
|
12057
|
-
kind: "divergent",
|
|
12058
|
-
details: {
|
|
12059
|
-
oldPath,
|
|
12060
|
-
newPath,
|
|
12061
|
-
oldHash,
|
|
12062
|
-
newHash,
|
|
12063
|
-
oldSize: oldRealStat.size,
|
|
12064
|
-
newSize: newRealStat.size,
|
|
12065
|
-
oldMtime: oldRealStat.mtime.toISOString(),
|
|
12066
|
-
newMtime: newRealStat.mtime.toISOString()
|
|
12067
|
-
}
|
|
12068
|
-
};
|
|
12069
|
-
}
|
|
12070
|
-
if (oldStat?.isFile() && !newExists) {
|
|
12071
|
-
if (opts.dryRun)
|
|
12072
|
-
return { kind: "migrated" };
|
|
12073
|
-
mkdirSync2(parent, { recursive: true, mode: 448 });
|
|
12074
|
-
const tempNew = `${newPath}.tmp`;
|
|
12075
|
-
copyFileSync(oldPath, tempNew);
|
|
12076
|
-
chmodSync(tempNew, 384);
|
|
12077
|
-
fsyncFile(tempNew);
|
|
12078
|
-
renameSync2(tempNew, newPath);
|
|
12079
|
-
fsyncDir(parent);
|
|
12080
|
-
atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
|
|
12081
|
-
fsyncDir(switchroomRoot);
|
|
12082
|
-
return { kind: "migrated" };
|
|
12083
|
-
}
|
|
12084
|
-
return { kind: "no-vault" };
|
|
12085
|
-
} finally {
|
|
12086
|
-
if (release !== null) {
|
|
12087
|
-
try {
|
|
12088
|
-
release();
|
|
12089
|
-
} catch {}
|
|
12090
|
-
}
|
|
12091
|
-
}
|
|
12092
|
-
}
|
|
12093
|
-
function lstatSyncOrNull(path) {
|
|
12094
|
-
try {
|
|
12095
|
-
return lstatSync(path);
|
|
12096
|
-
} catch {
|
|
12097
|
-
return null;
|
|
12098
|
-
}
|
|
12099
|
-
}
|
|
12100
|
-
function sha256File(path) {
|
|
12101
|
-
const data = readFileSync3(path);
|
|
12102
|
-
return createHash2("sha256").update(data).digest("hex");
|
|
12103
|
-
}
|
|
12104
|
-
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
12105
|
-
const tmp = join(dirname2(target), `.${basename2(target)}.symlink-tmp`);
|
|
12106
|
-
if (existsSync3(tmp)) {
|
|
12107
|
-
try {
|
|
12108
|
-
unlinkSync3(tmp);
|
|
12109
|
-
} catch {}
|
|
12110
|
-
}
|
|
12111
|
-
symlinkSync(linkTarget, tmp);
|
|
12112
|
-
renameSync2(tmp, target);
|
|
12113
|
-
}
|
|
12114
|
-
function fsyncFile(path) {
|
|
12115
|
-
const fd = openSync3(path, "r+");
|
|
12116
|
-
try {
|
|
12117
|
-
fsyncSync3(fd);
|
|
12118
|
-
} finally {
|
|
12119
|
-
closeSync3(fd);
|
|
12120
|
-
}
|
|
12121
|
-
}
|
|
12122
|
-
function fsyncDir(path) {
|
|
12123
|
-
const fd = openSync3(path, "r");
|
|
12124
|
-
try {
|
|
12125
|
-
fsyncSync3(fd);
|
|
12126
|
-
} finally {
|
|
12127
|
-
closeSync3(fd);
|
|
12128
|
-
}
|
|
12129
|
-
}
|
|
12130
|
-
|
|
12131
|
-
// src/vault/broker/server.ts
|
|
12132
|
-
init_loader();
|
|
12133
|
-
|
|
12134
|
-
// src/vault/auto-unlock.ts
|
|
12135
|
-
import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
|
|
12136
|
-
import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12137
|
-
var FORMAT_VERSION = 1;
|
|
12138
|
-
var SALT_LEN = 16;
|
|
12139
|
-
var NONCE_LEN = 12;
|
|
12140
|
-
var TAG_LEN = 16;
|
|
12141
|
-
var KEY_LEN = 32;
|
|
12142
|
-
var HKDF_INFO = "switchroom-vault-auto-unlock-v1";
|
|
12143
|
-
var MACHINE_ID_PRIMARY = "/etc/machine-id";
|
|
12144
|
-
var MACHINE_ID_FALLBACK = "/var/lib/dbus/machine-id";
|
|
12145
|
-
|
|
12146
|
-
class MachineIdUnavailableError extends Error {
|
|
12147
|
-
constructor() {
|
|
12148
|
-
super(`Cannot derive machine-bound key: neither ${MACHINE_ID_PRIMARY} nor ` + `${MACHINE_ID_FALLBACK} is readable. Auto-unlock requires a stable ` + `machine identifier. On a fresh install, run \`systemd-machine-id-setup\` ` + `or boot once to populate it.`);
|
|
12149
|
-
this.name = "MachineIdUnavailableError";
|
|
12150
|
-
}
|
|
12151
|
-
}
|
|
12152
|
-
|
|
12153
|
-
class AutoUnlockDecryptError extends Error {
|
|
12154
|
-
reason;
|
|
12155
|
-
constructor(reason) {
|
|
12156
|
-
super(reason === "tag-mismatch" ? "Auto-unlock blob failed to decrypt — likely bound to a different " + "machine-id. Re-run `switchroom vault broker enable-auto-unlock` to refresh." : reason === "format" ? "Auto-unlock blob is malformed (wrong length or version)." : "Auto-unlock blob could not be read.");
|
|
12157
|
-
this.reason = reason;
|
|
12158
|
-
this.name = "AutoUnlockDecryptError";
|
|
12159
|
-
}
|
|
12160
|
-
}
|
|
12161
|
-
function readMachineId() {
|
|
12162
|
-
for (const path of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
|
|
12163
|
-
try {
|
|
12164
|
-
const id = readFileSync5(path, "utf8").trim();
|
|
12165
|
-
if (id.length > 0)
|
|
12166
|
-
return id;
|
|
12167
|
-
} catch {}
|
|
12168
|
-
}
|
|
12169
|
-
throw new MachineIdUnavailableError;
|
|
12170
|
-
}
|
|
12171
|
-
function deriveKey2(machineId, salt) {
|
|
12172
|
-
const ikm = Buffer.from(machineId, "utf8");
|
|
12173
|
-
const prk = createHmac("sha256", salt).update(ikm).digest();
|
|
12174
|
-
const okm = createHmac("sha256", prk).update(Buffer.concat([Buffer.from(HKDF_INFO, "utf8"), Buffer.from([1])])).digest();
|
|
12175
|
-
return okm.subarray(0, KEY_LEN);
|
|
12176
|
-
}
|
|
12177
|
-
function decryptAutoUnlock(blob, machineId) {
|
|
12178
|
-
if (blob.length < 1 + SALT_LEN + NONCE_LEN + TAG_LEN) {
|
|
12179
|
-
throw new AutoUnlockDecryptError("format");
|
|
12180
|
-
}
|
|
12181
|
-
if (blob[0] !== FORMAT_VERSION) {
|
|
12182
|
-
throw new AutoUnlockDecryptError("format");
|
|
12183
|
-
}
|
|
12184
|
-
const salt = blob.subarray(1, 1 + SALT_LEN);
|
|
12185
|
-
const nonce = blob.subarray(1 + SALT_LEN, 1 + SALT_LEN + NONCE_LEN);
|
|
12186
|
-
const ctAndTag = blob.subarray(1 + SALT_LEN + NONCE_LEN);
|
|
12187
|
-
const ciphertext = ctAndTag.subarray(0, ctAndTag.length - TAG_LEN);
|
|
12188
|
-
const tag = ctAndTag.subarray(ctAndTag.length - TAG_LEN);
|
|
12189
|
-
const id = machineId ?? readMachineId();
|
|
12190
|
-
const key = deriveKey2(id, salt);
|
|
12191
|
-
const decipher = createDecipheriv2("aes-256-gcm", key, nonce);
|
|
12192
|
-
decipher.setAuthTag(tag);
|
|
12193
|
-
try {
|
|
12194
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
12195
|
-
return plaintext.toString("utf8");
|
|
12196
|
-
} catch {
|
|
12197
|
-
throw new AutoUnlockDecryptError("tag-mismatch");
|
|
12198
|
-
}
|
|
12199
|
-
}
|
|
12200
|
-
function readAutoUnlockFile(filePath) {
|
|
12201
|
-
if (!existsSync6(filePath)) {
|
|
12202
|
-
throw new AutoUnlockDecryptError("io");
|
|
12203
|
-
}
|
|
12204
|
-
let blob;
|
|
12205
|
-
try {
|
|
12206
|
-
blob = readFileSync5(filePath);
|
|
12207
|
-
} catch {
|
|
12208
|
-
throw new AutoUnlockDecryptError("io");
|
|
12209
|
-
}
|
|
12210
|
-
return decryptAutoUnlock(blob);
|
|
12211
|
-
}
|
|
12212
|
-
var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
12213
|
-
|
|
12214
|
-
// src/vault/broker/peercred.ts
|
|
12215
|
-
import { execFileSync } from "node:child_process";
|
|
12216
|
-
import { readFileSync as readFileSync6, readlinkSync, fstatSync } from "node:fs";
|
|
12217
|
-
|
|
12218
|
-
// src/vault/broker/peercred-ffi.ts
|
|
12219
|
-
function getPeerCred(fd) {
|
|
12220
|
-
if (process.platform !== "linux")
|
|
12221
|
-
return null;
|
|
12222
|
-
try {
|
|
12223
|
-
const ffi = __require("bun:ffi");
|
|
12224
|
-
const { dlopen, FFIType, ptr } = ffi;
|
|
12225
|
-
const SOL_SOCKET = 1;
|
|
12226
|
-
const SO_PEERCRED = 17;
|
|
12227
|
-
const UCRED_SIZE = 12;
|
|
12228
|
-
const cache = getPeerCred;
|
|
12229
|
-
const lib = cache._lib ?? (() => {
|
|
12230
|
-
const candidates = ["libc.so.6", "libc.so"];
|
|
12231
|
-
const symbolSpec = {
|
|
12232
|
-
getsockopt: {
|
|
12233
|
-
args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
|
|
12234
|
-
returns: FFIType.i32
|
|
12235
|
-
}
|
|
12236
|
-
};
|
|
12237
|
-
const errors3 = [];
|
|
12238
|
-
for (const name of candidates) {
|
|
12239
|
-
try {
|
|
12240
|
-
const opened = dlopen(name, symbolSpec);
|
|
12241
|
-
cache._lib = opened;
|
|
12242
|
-
return opened;
|
|
12243
|
-
} catch (e) {
|
|
12244
|
-
errors3.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
12245
|
-
}
|
|
12246
|
-
}
|
|
12247
|
-
process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors3.join("; ")}); falling back to ss-parsing.
|
|
12248
|
-
`);
|
|
12249
|
-
throw new Error("no libc candidate could be opened");
|
|
12250
|
-
})();
|
|
12251
|
-
const credBuf = new ArrayBuffer(UCRED_SIZE);
|
|
12252
|
-
const lenBuf = new Uint32Array(1);
|
|
12253
|
-
lenBuf[0] = UCRED_SIZE;
|
|
12254
|
-
const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
|
|
12255
|
-
if (rc !== 0)
|
|
12256
|
-
return null;
|
|
12257
|
-
if (lenBuf[0] !== UCRED_SIZE)
|
|
12258
|
-
return null;
|
|
12259
|
-
const view = new DataView(credBuf);
|
|
12260
|
-
return {
|
|
12261
|
-
pid: view.getInt32(0, true),
|
|
12262
|
-
uid: view.getInt32(4, true),
|
|
12263
|
-
gid: view.getInt32(8, true)
|
|
12264
|
-
};
|
|
12265
|
-
} catch {
|
|
12266
|
-
return null;
|
|
12267
|
-
}
|
|
12268
|
-
}
|
|
12269
|
-
|
|
12270
|
-
// src/vault/broker/peercred.ts
|
|
12271
|
-
var SOCKET_PATH_AGENT_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\.sock$/;
|
|
12272
|
-
var SOCKET_PATH_AGENT_SUBDIR_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\/sock$/;
|
|
12273
|
-
function socketPathToAgent(socketPath) {
|
|
12274
|
-
if (typeof socketPath !== "string" || socketPath.length === 0)
|
|
12275
|
-
return null;
|
|
12276
|
-
const m = socketPath.match(SOCKET_PATH_AGENT_RE) ?? socketPath.match(SOCKET_PATH_AGENT_SUBDIR_RE);
|
|
12277
|
-
if (!m)
|
|
12278
|
-
return null;
|
|
12279
|
-
return m[1];
|
|
12280
|
-
}
|
|
12281
|
-
function parseSsRows(output) {
|
|
12282
|
-
const rows = [];
|
|
12283
|
-
const lines = output.split(`
|
|
12284
|
-
`);
|
|
12285
|
-
for (const line of lines) {
|
|
12286
|
-
if (!line.trim() || line.startsWith("Netid"))
|
|
12287
|
-
continue;
|
|
12288
|
-
const tokens = line.split(/\s+/).filter((t) => t.length > 0);
|
|
12289
|
-
if (tokens.length < 8)
|
|
12290
|
-
continue;
|
|
12291
|
-
const localAddr = tokens[4];
|
|
12292
|
-
const localInode = tokens[5];
|
|
12293
|
-
const peerAddr = tokens[6];
|
|
12294
|
-
const peerInode = tokens[7];
|
|
12295
|
-
const usersToken = tokens.slice(8).join(" ");
|
|
12296
|
-
const m = usersToken.match(/users:\(\(".*?",pid=(\d+),fd=\d+\)\)/);
|
|
12297
|
-
const pid = m ? parseInt(m[1], 10) : null;
|
|
12298
|
-
rows.push({ localAddr, localInode, peerAddr, peerInode, pid });
|
|
12299
|
-
}
|
|
12300
|
-
return rows;
|
|
12301
|
-
}
|
|
12302
|
-
function findClientPids(rows, socketPath) {
|
|
12303
|
-
const pids = [];
|
|
12304
|
-
for (const serverRow of rows) {
|
|
12305
|
-
if (serverRow.localAddr !== socketPath)
|
|
12306
|
-
continue;
|
|
12307
|
-
for (const clientRow of rows) {
|
|
12308
|
-
if (clientRow.localAddr !== "*")
|
|
12309
|
-
continue;
|
|
12310
|
-
if (clientRow.localInode !== serverRow.peerInode)
|
|
12311
|
-
continue;
|
|
12312
|
-
if (clientRow.pid === null)
|
|
12313
|
-
continue;
|
|
12314
|
-
pids.push(clientRow.pid);
|
|
12315
|
-
break;
|
|
12712
|
+
kind: "divergent",
|
|
12713
|
+
details: {
|
|
12714
|
+
oldPath,
|
|
12715
|
+
newPath,
|
|
12716
|
+
oldHash,
|
|
12717
|
+
newHash,
|
|
12718
|
+
oldSize: oldRealStat.size,
|
|
12719
|
+
newSize: newRealStat.size,
|
|
12720
|
+
oldMtime: oldRealStat.mtime.toISOString(),
|
|
12721
|
+
newMtime: newRealStat.mtime.toISOString()
|
|
12722
|
+
}
|
|
12723
|
+
};
|
|
12316
12724
|
}
|
|
12317
|
-
|
|
12318
|
-
|
|
12319
|
-
}
|
|
12320
|
-
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12725
|
+
if (oldStat?.isFile() && !newExists) {
|
|
12726
|
+
if (opts.dryRun)
|
|
12727
|
+
return { kind: "migrated" };
|
|
12728
|
+
mkdirSync2(parent, { recursive: true, mode: 448 });
|
|
12729
|
+
const tempNew = `${newPath}.tmp`;
|
|
12730
|
+
copyFileSync(oldPath, tempNew);
|
|
12731
|
+
chmodSync(tempNew, 384);
|
|
12732
|
+
fsyncFile(tempNew);
|
|
12733
|
+
renameSync2(tempNew, newPath);
|
|
12734
|
+
fsyncDir(parent);
|
|
12735
|
+
atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
|
|
12736
|
+
fsyncDir(switchroomRoot);
|
|
12737
|
+
return { kind: "migrated" };
|
|
12738
|
+
}
|
|
12739
|
+
return { kind: "no-vault" };
|
|
12740
|
+
} finally {
|
|
12741
|
+
if (release !== null) {
|
|
12742
|
+
try {
|
|
12743
|
+
release();
|
|
12744
|
+
} catch {}
|
|
12335
12745
|
}
|
|
12336
|
-
return null;
|
|
12337
12746
|
}
|
|
12338
|
-
return null;
|
|
12339
12747
|
}
|
|
12340
|
-
function
|
|
12748
|
+
function lstatSyncOrNull(path) {
|
|
12341
12749
|
try {
|
|
12342
|
-
|
|
12343
|
-
const m = status.match(/^Uid:\s+(\d+)/m);
|
|
12344
|
-
if (!m)
|
|
12345
|
-
return null;
|
|
12346
|
-
return parseInt(m[1], 10);
|
|
12750
|
+
return lstatSync2(path);
|
|
12347
12751
|
} catch {
|
|
12348
12752
|
return null;
|
|
12349
12753
|
}
|
|
12350
12754
|
}
|
|
12351
|
-
function
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12755
|
+
function sha256File(path) {
|
|
12756
|
+
const data = readFileSync4(path);
|
|
12757
|
+
return createHash2("sha256").update(data).digest("hex");
|
|
12758
|
+
}
|
|
12759
|
+
function atomicReplaceWithSymlink(target, linkTarget) {
|
|
12760
|
+
const tmp = join(dirname2(target), `.${basename2(target)}.symlink-tmp`);
|
|
12761
|
+
if (existsSync3(tmp)) {
|
|
12762
|
+
try {
|
|
12763
|
+
unlinkSync3(tmp);
|
|
12764
|
+
} catch {}
|
|
12356
12765
|
}
|
|
12766
|
+
symlinkSync(linkTarget, tmp);
|
|
12767
|
+
renameSync2(tmp, target);
|
|
12357
12768
|
}
|
|
12358
|
-
function
|
|
12769
|
+
function fsyncFile(path) {
|
|
12770
|
+
const fd = openSync3(path, "r+");
|
|
12359
12771
|
try {
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
for (const line of lines) {
|
|
12364
|
-
if (!line.trim())
|
|
12365
|
-
continue;
|
|
12366
|
-
const parts = line.split(":");
|
|
12367
|
-
if (parts.length < 3)
|
|
12368
|
-
continue;
|
|
12369
|
-
const controller = parts[1];
|
|
12370
|
-
const isV2 = parts[0] === "0" && controller === "";
|
|
12371
|
-
const isV1Systemd = controller === "name=systemd";
|
|
12372
|
-
if (!isV2 && !isV1Systemd)
|
|
12373
|
-
continue;
|
|
12374
|
-
const cgroupPath = parts.slice(2).join(":");
|
|
12375
|
-
const segments = cgroupPath.split("/");
|
|
12376
|
-
const lastSegment = segments[segments.length - 1];
|
|
12377
|
-
if (!lastSegment)
|
|
12378
|
-
continue;
|
|
12379
|
-
if (/^switchroom-[a-zA-Z0-9_-]+(-cron-\d+)?\.service$/.test(lastSegment)) {
|
|
12380
|
-
return lastSegment;
|
|
12381
|
-
}
|
|
12382
|
-
}
|
|
12383
|
-
return null;
|
|
12384
|
-
} catch {
|
|
12385
|
-
return null;
|
|
12772
|
+
fsyncSync3(fd);
|
|
12773
|
+
} finally {
|
|
12774
|
+
closeSync3(fd);
|
|
12386
12775
|
}
|
|
12387
12776
|
}
|
|
12388
|
-
function
|
|
12389
|
-
|
|
12777
|
+
function fsyncDir(path) {
|
|
12778
|
+
const fd = openSync3(path, "r");
|
|
12390
12779
|
try {
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
unitName,
|
|
12395
|
-
"--property=LoadState,ActiveState"
|
|
12396
|
-
], { timeout: 500, encoding: "utf8" });
|
|
12397
|
-
raw = typeof out === "string" ? out : out.toString("utf8");
|
|
12398
|
-
} catch {
|
|
12399
|
-
return false;
|
|
12780
|
+
fsyncSync3(fd);
|
|
12781
|
+
} finally {
|
|
12782
|
+
closeSync3(fd);
|
|
12400
12783
|
}
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12784
|
+
}
|
|
12785
|
+
|
|
12786
|
+
// src/vault/broker/server.ts
|
|
12787
|
+
init_loader();
|
|
12788
|
+
|
|
12789
|
+
// src/vault/auto-unlock.ts
|
|
12790
|
+
import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
|
|
12791
|
+
import { chmodSync as chmodSync2, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
12792
|
+
var FORMAT_VERSION = 1;
|
|
12793
|
+
var SALT_LEN = 16;
|
|
12794
|
+
var NONCE_LEN = 12;
|
|
12795
|
+
var TAG_LEN = 16;
|
|
12796
|
+
var KEY_LEN = 32;
|
|
12797
|
+
var HKDF_INFO = "switchroom-vault-auto-unlock-v1";
|
|
12798
|
+
var MACHINE_ID_PRIMARY = "/etc/machine-id";
|
|
12799
|
+
var MACHINE_ID_FALLBACK = "/var/lib/dbus/machine-id";
|
|
12800
|
+
|
|
12801
|
+
class MachineIdUnavailableError extends Error {
|
|
12802
|
+
constructor() {
|
|
12803
|
+
super(`Cannot derive machine-bound key: neither ${MACHINE_ID_PRIMARY} nor ` + `${MACHINE_ID_FALLBACK} is readable. Auto-unlock requires a stable ` + `machine identifier. On a fresh install, run \`systemd-machine-id-setup\` ` + `or boot once to populate it.`);
|
|
12804
|
+
this.name = "MachineIdUnavailableError";
|
|
12407
12805
|
}
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12806
|
+
}
|
|
12807
|
+
|
|
12808
|
+
class AutoUnlockDecryptError extends Error {
|
|
12809
|
+
reason;
|
|
12810
|
+
constructor(reason) {
|
|
12811
|
+
super(reason === "tag-mismatch" ? "Auto-unlock blob failed to decrypt — likely bound to a different " + "machine-id. Re-run `switchroom vault broker enable-auto-unlock` to refresh." : reason === "format" ? "Auto-unlock blob is malformed (wrong length or version)." : "Auto-unlock blob could not be read.");
|
|
12812
|
+
this.reason = reason;
|
|
12813
|
+
this.name = "AutoUnlockDecryptError";
|
|
12412
12814
|
}
|
|
12413
|
-
return true;
|
|
12414
12815
|
}
|
|
12415
|
-
function
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12816
|
+
function readMachineId() {
|
|
12817
|
+
for (const path of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
|
|
12818
|
+
try {
|
|
12819
|
+
const id = readFileSync7(path, "utf8").trim();
|
|
12820
|
+
if (id.length > 0)
|
|
12821
|
+
return id;
|
|
12822
|
+
} catch {}
|
|
12421
12823
|
}
|
|
12824
|
+
throw new MachineIdUnavailableError;
|
|
12422
12825
|
}
|
|
12423
|
-
function
|
|
12424
|
-
const
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
return
|
|
12826
|
+
function deriveKey2(machineId, salt) {
|
|
12827
|
+
const ikm = Buffer.from(machineId, "utf8");
|
|
12828
|
+
const prk = createHmac("sha256", salt).update(ikm).digest();
|
|
12829
|
+
const okm = createHmac("sha256", prk).update(Buffer.concat([Buffer.from(HKDF_INFO, "utf8"), Buffer.from([1])])).digest();
|
|
12830
|
+
return okm.subarray(0, KEY_LEN);
|
|
12428
12831
|
}
|
|
12429
|
-
function
|
|
12430
|
-
if (
|
|
12431
|
-
|
|
12432
|
-
}
|
|
12433
|
-
const runner = execFileSyncOverride ?? execFileSync;
|
|
12434
|
-
let pid = null;
|
|
12435
|
-
if (socket !== undefined) {
|
|
12436
|
-
const fd = fdFromSocket(socket);
|
|
12437
|
-
if (fd !== null) {
|
|
12438
|
-
const cred = getPeerCred(fd);
|
|
12439
|
-
if (cred !== null)
|
|
12440
|
-
pid = cred.pid;
|
|
12441
|
-
}
|
|
12442
|
-
}
|
|
12443
|
-
if (pid === null) {
|
|
12444
|
-
let ssOutput;
|
|
12445
|
-
try {
|
|
12446
|
-
const raw = runner("ss", ["-xpn"], {
|
|
12447
|
-
timeout: 200,
|
|
12448
|
-
encoding: "utf8"
|
|
12449
|
-
});
|
|
12450
|
-
ssOutput = typeof raw === "string" ? raw : raw.toString("utf8");
|
|
12451
|
-
} catch {
|
|
12452
|
-
return null;
|
|
12453
|
-
}
|
|
12454
|
-
const rows = parseSsRows(ssOutput);
|
|
12455
|
-
let serverInode = null;
|
|
12456
|
-
if (socket !== undefined) {
|
|
12457
|
-
const fd = fdFromSocket(socket);
|
|
12458
|
-
if (fd !== null)
|
|
12459
|
-
serverInode = readFdInode(fd);
|
|
12460
|
-
}
|
|
12461
|
-
if (serverInode !== null) {
|
|
12462
|
-
pid = findClientPidByServerInode(rows, socketPath, serverInode);
|
|
12463
|
-
} else {
|
|
12464
|
-
const clientPids = findClientPids(rows, socketPath);
|
|
12465
|
-
if (clientPids.length === 0)
|
|
12466
|
-
return null;
|
|
12467
|
-
if (clientPids.length > 1) {
|
|
12468
|
-
process.stderr.write(`[vault-broker] peercred: ${clientPids.length} connected peers found for ${socketPath}; ` + `using pid=${clientPids[0]}. ` + `Multiple simultaneous connections reduce identification accuracy. ` + `(This warning means identify() was called without a socket arg — likely a stale call site.)
|
|
12469
|
-
`);
|
|
12470
|
-
}
|
|
12471
|
-
pid = clientPids[0];
|
|
12472
|
-
}
|
|
12832
|
+
function decryptAutoUnlock(blob, machineId) {
|
|
12833
|
+
if (blob.length < 1 + SALT_LEN + NONCE_LEN + TAG_LEN) {
|
|
12834
|
+
throw new AutoUnlockDecryptError("format");
|
|
12473
12835
|
}
|
|
12474
|
-
if (
|
|
12475
|
-
|
|
12476
|
-
const uid = readUid(pid);
|
|
12477
|
-
if (uid === null) {
|
|
12478
|
-
return null;
|
|
12836
|
+
if (blob[0] !== FORMAT_VERSION) {
|
|
12837
|
+
throw new AutoUnlockDecryptError("format");
|
|
12479
12838
|
}
|
|
12480
|
-
const
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12839
|
+
const salt = blob.subarray(1, 1 + SALT_LEN);
|
|
12840
|
+
const nonce = blob.subarray(1 + SALT_LEN, 1 + SALT_LEN + NONCE_LEN);
|
|
12841
|
+
const ctAndTag = blob.subarray(1 + SALT_LEN + NONCE_LEN);
|
|
12842
|
+
const ciphertext = ctAndTag.subarray(0, ctAndTag.length - TAG_LEN);
|
|
12843
|
+
const tag = ctAndTag.subarray(ctAndTag.length - TAG_LEN);
|
|
12844
|
+
const id = machineId ?? readMachineId();
|
|
12845
|
+
const key = deriveKey2(id, salt);
|
|
12846
|
+
const decipher = createDecipheriv2("aes-256-gcm", key, nonce);
|
|
12847
|
+
decipher.setAuthTag(tag);
|
|
12848
|
+
try {
|
|
12849
|
+
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
12850
|
+
return plaintext.toString("utf8");
|
|
12851
|
+
} catch {
|
|
12852
|
+
throw new AutoUnlockDecryptError("tag-mismatch");
|
|
12485
12853
|
}
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12854
|
+
}
|
|
12855
|
+
function readAutoUnlockFile(filePath) {
|
|
12856
|
+
if (!existsSync7(filePath)) {
|
|
12857
|
+
throw new AutoUnlockDecryptError("io");
|
|
12489
12858
|
}
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
} else {
|
|
12496
|
-
process.stderr.write(`[vault-broker] peercred: cgroup claims unit=${cgroupClaim} but systemd-user does not report it as loaded+running; treating caller as unidentified
|
|
12497
|
-
`);
|
|
12498
|
-
}
|
|
12859
|
+
let blob;
|
|
12860
|
+
try {
|
|
12861
|
+
blob = readFileSync7(filePath);
|
|
12862
|
+
} catch {
|
|
12863
|
+
throw new AutoUnlockDecryptError("io");
|
|
12499
12864
|
}
|
|
12500
|
-
return
|
|
12865
|
+
return decryptAutoUnlock(blob);
|
|
12501
12866
|
}
|
|
12867
|
+
var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
|
|
12502
12868
|
|
|
12503
12869
|
// src/vault/broker/acl.ts
|
|
12504
12870
|
function parseCronUnit(unitName) {
|
|
@@ -12583,6 +12949,10 @@ function checkAclByAgent(config, agentName, key) {
|
|
|
12583
12949
|
if (!agentConfig) {
|
|
12584
12950
|
return { allow: false, reason: `agent '${agentName}' not found in config` };
|
|
12585
12951
|
}
|
|
12952
|
+
const googleSlot = parseGoogleAccountSlotKey(key);
|
|
12953
|
+
if (googleSlot !== null) {
|
|
12954
|
+
return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
|
|
12955
|
+
}
|
|
12586
12956
|
const schedule = agentConfig.schedule ?? [];
|
|
12587
12957
|
if (schedule.length === 0) {
|
|
12588
12958
|
return {
|
|
@@ -12601,6 +12971,37 @@ function checkAclByAgent(config, agentName, key) {
|
|
|
12601
12971
|
reason: `key '${key}' not in ACL for agent '${agentName}'`
|
|
12602
12972
|
};
|
|
12603
12973
|
}
|
|
12974
|
+
function parseGoogleAccountSlotKey(key) {
|
|
12975
|
+
const match = key.match(/^google:([^:]+):([a-z_]+)$/);
|
|
12976
|
+
if (!match)
|
|
12977
|
+
return null;
|
|
12978
|
+
return { account: match[1], field: match[2] };
|
|
12979
|
+
}
|
|
12980
|
+
function checkGoogleAccountAcl(config, agentName, account, key) {
|
|
12981
|
+
const accounts = config.google_accounts ?? {};
|
|
12982
|
+
const accountKey = account.toLowerCase();
|
|
12983
|
+
const accountEntry = accounts[accountKey] ?? accounts[account];
|
|
12984
|
+
if (!accountEntry) {
|
|
12985
|
+
return {
|
|
12986
|
+
allow: false,
|
|
12987
|
+
reason: `google_accounts['${account}'] not configured (key '${key}')`
|
|
12988
|
+
};
|
|
12989
|
+
}
|
|
12990
|
+
const enabled = accountEntry.enabled_for ?? [];
|
|
12991
|
+
if (enabled.length === 0) {
|
|
12992
|
+
return {
|
|
12993
|
+
allow: false,
|
|
12994
|
+
reason: `google_accounts['${account}'].enabled_for is empty (key '${key}')`
|
|
12995
|
+
};
|
|
12996
|
+
}
|
|
12997
|
+
if (!enabled.includes(agentName)) {
|
|
12998
|
+
return {
|
|
12999
|
+
allow: false,
|
|
13000
|
+
reason: `agent '${agentName}' not in google_accounts['${account}'].enabled_for (key '${key}')`
|
|
13001
|
+
};
|
|
13002
|
+
}
|
|
13003
|
+
return { allow: true };
|
|
13004
|
+
}
|
|
12604
13005
|
|
|
12605
13006
|
// src/vault/broker/protocol.ts
|
|
12606
13007
|
init_zod();
|
|
@@ -12621,7 +13022,8 @@ var PutRequestSchema = exports_external.object({
|
|
|
12621
13022
|
exports_external.object({ kind: exports_external.literal("binary"), value: exports_external.string() })
|
|
12622
13023
|
]),
|
|
12623
13024
|
token: exports_external.string().optional(),
|
|
12624
|
-
passphrase: exports_external.string().optional()
|
|
13025
|
+
passphrase: exports_external.string().optional(),
|
|
13026
|
+
attest_via_posture: exports_external.boolean().optional()
|
|
12625
13027
|
});
|
|
12626
13028
|
var ListRequestSchema = exports_external.object({
|
|
12627
13029
|
v: exports_external.literal(1),
|
|
@@ -12635,12 +13037,16 @@ var MintGrantRequestSchema = exports_external.object({
|
|
|
12635
13037
|
keys: exports_external.array(exports_external.string().min(1)),
|
|
12636
13038
|
ttl_seconds: exports_external.number().int().positive().nullable(),
|
|
12637
13039
|
description: exports_external.string().optional(),
|
|
12638
|
-
write_keys: exports_external.array(exports_external.string().min(1)).optional()
|
|
13040
|
+
write_keys: exports_external.array(exports_external.string().min(1)).optional(),
|
|
13041
|
+
passphrase: exports_external.string().optional(),
|
|
13042
|
+
attest_via_posture: exports_external.boolean().optional()
|
|
12639
13043
|
});
|
|
12640
13044
|
var ListGrantsRequestSchema = exports_external.object({
|
|
12641
13045
|
v: exports_external.literal(1),
|
|
12642
13046
|
op: exports_external.literal("list_grants"),
|
|
12643
|
-
agent: exports_external.string().optional()
|
|
13047
|
+
agent: exports_external.string().optional(),
|
|
13048
|
+
passphrase: exports_external.string().optional(),
|
|
13049
|
+
attest_via_posture: exports_external.boolean().optional()
|
|
12644
13050
|
});
|
|
12645
13051
|
var RevokeGrantRequestSchema = exports_external.object({
|
|
12646
13052
|
v: exports_external.literal(1),
|
|
@@ -12947,7 +13353,7 @@ function createAuditLogger(opts = {}) {
|
|
|
12947
13353
|
// src/vault/grants.ts
|
|
12948
13354
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
12949
13355
|
|
|
12950
|
-
// node_modules/bcryptjs/index.js
|
|
13356
|
+
// node_modules/.bun/bcryptjs@3.0.3/node_modules/bcryptjs/index.js
|
|
12951
13357
|
import nodeCrypto from "crypto";
|
|
12952
13358
|
var randomFallback = null;
|
|
12953
13359
|
function randomBytes3(len) {
|
|
@@ -13002,13 +13408,13 @@ function genSalt(rounds, seed_length, callback) {
|
|
|
13002
13408
|
throw Error("Illegal callback: " + typeof callback);
|
|
13003
13409
|
_async(callback);
|
|
13004
13410
|
} else
|
|
13005
|
-
return new Promise(function(
|
|
13411
|
+
return new Promise(function(resolve5, reject) {
|
|
13006
13412
|
_async(function(err, res) {
|
|
13007
13413
|
if (err) {
|
|
13008
13414
|
reject(err);
|
|
13009
13415
|
return;
|
|
13010
13416
|
}
|
|
13011
|
-
|
|
13417
|
+
resolve5(res);
|
|
13012
13418
|
});
|
|
13013
13419
|
});
|
|
13014
13420
|
}
|
|
@@ -13028,13 +13434,13 @@ function hash(password, salt, callback, progressCallback) {
|
|
|
13028
13434
|
throw Error("Illegal callback: " + typeof callback);
|
|
13029
13435
|
_async(callback);
|
|
13030
13436
|
} else
|
|
13031
|
-
return new Promise(function(
|
|
13437
|
+
return new Promise(function(resolve5, reject) {
|
|
13032
13438
|
_async(function(err, res) {
|
|
13033
13439
|
if (err) {
|
|
13034
13440
|
reject(err);
|
|
13035
13441
|
return;
|
|
13036
13442
|
}
|
|
13037
|
-
|
|
13443
|
+
resolve5(res);
|
|
13038
13444
|
});
|
|
13039
13445
|
});
|
|
13040
13446
|
}
|
|
@@ -13067,13 +13473,13 @@ function compare(password, hashValue, callback, progressCallback) {
|
|
|
13067
13473
|
throw Error("Illegal callback: " + typeof callback);
|
|
13068
13474
|
_async(callback);
|
|
13069
13475
|
} else
|
|
13070
|
-
return new Promise(function(
|
|
13476
|
+
return new Promise(function(resolve5, reject) {
|
|
13071
13477
|
_async(function(err, res) {
|
|
13072
13478
|
if (err) {
|
|
13073
13479
|
reject(err);
|
|
13074
13480
|
return;
|
|
13075
13481
|
}
|
|
13076
|
-
|
|
13482
|
+
resolve5(res);
|
|
13077
13483
|
});
|
|
13078
13484
|
});
|
|
13079
13485
|
}
|
|
@@ -14799,11 +15205,8 @@ import { Database } from "bun:sqlite";
|
|
|
14799
15205
|
|
|
14800
15206
|
// src/vault/approvals/schema.ts
|
|
14801
15207
|
function migrateApprovalSchema(db) {
|
|
14802
|
-
db.run(`DROP TABLE IF EXISTS approval_audit`);
|
|
14803
|
-
db.run(`DROP TABLE IF EXISTS approval_nonces`);
|
|
14804
|
-
db.run(`DROP TABLE IF EXISTS approval_decisions`);
|
|
14805
15208
|
db.run(`
|
|
14806
|
-
CREATE TABLE approval_decisions (
|
|
15209
|
+
CREATE TABLE IF NOT EXISTS approval_decisions (
|
|
14807
15210
|
id TEXT PRIMARY KEY,
|
|
14808
15211
|
agent_unit TEXT NOT NULL,
|
|
14809
15212
|
scope TEXT NOT NULL,
|
|
@@ -14819,12 +15222,12 @@ function migrateApprovalSchema(db) {
|
|
|
14819
15222
|
)
|
|
14820
15223
|
`);
|
|
14821
15224
|
db.run(`
|
|
14822
|
-
CREATE INDEX approval_decisions_lookup
|
|
15225
|
+
CREATE INDEX IF NOT EXISTS approval_decisions_lookup
|
|
14823
15226
|
ON approval_decisions(agent_unit, scope, action)
|
|
14824
15227
|
WHERE revoked_at IS NULL
|
|
14825
15228
|
`);
|
|
14826
15229
|
db.run(`
|
|
14827
|
-
CREATE TABLE approval_nonces (
|
|
15230
|
+
CREATE TABLE IF NOT EXISTS approval_nonces (
|
|
14828
15231
|
request_id TEXT PRIMARY KEY,
|
|
14829
15232
|
decision_id TEXT,
|
|
14830
15233
|
agent_unit TEXT NOT NULL,
|
|
@@ -14839,12 +15242,12 @@ function migrateApprovalSchema(db) {
|
|
|
14839
15242
|
)
|
|
14840
15243
|
`);
|
|
14841
15244
|
db.run(`
|
|
14842
|
-
CREATE INDEX approval_nonces_pending
|
|
15245
|
+
CREATE INDEX IF NOT EXISTS approval_nonces_pending
|
|
14843
15246
|
ON approval_nonces(agent_unit, expires_at)
|
|
14844
15247
|
WHERE consumed_at IS NULL
|
|
14845
15248
|
`);
|
|
14846
15249
|
db.run(`
|
|
14847
|
-
CREATE TABLE approval_audit (
|
|
15250
|
+
CREATE TABLE IF NOT EXISTS approval_audit (
|
|
14848
15251
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14849
15252
|
ts INTEGER NOT NULL,
|
|
14850
15253
|
agent_unit TEXT NOT NULL,
|
|
@@ -14857,7 +15260,7 @@ function migrateApprovalSchema(db) {
|
|
|
14857
15260
|
)
|
|
14858
15261
|
`);
|
|
14859
15262
|
db.run(`
|
|
14860
|
-
CREATE INDEX approval_audit_by_scope
|
|
15263
|
+
CREATE INDEX IF NOT EXISTS approval_audit_by_scope
|
|
14861
15264
|
ON approval_audit(scope, ts)
|
|
14862
15265
|
`);
|
|
14863
15266
|
}
|
|
@@ -15173,7 +15576,7 @@ class VaultBroker {
|
|
|
15173
15576
|
grantsDb;
|
|
15174
15577
|
constructor(testOpts = {}) {
|
|
15175
15578
|
this.testOpts = testOpts;
|
|
15176
|
-
const usingTestOpt = testOpts._testSecrets !== undefined || testOpts._testConfig !== undefined || testOpts._testIdentify !== undefined || testOpts._testAuditLogger !== undefined || testOpts._testGrantsDb !== undefined || testOpts._testVaultPath !== undefined;
|
|
15579
|
+
const usingTestOpt = testOpts._testSecrets !== undefined || testOpts._testConfig !== undefined || testOpts._testIdentify !== undefined || testOpts._testAuditLogger !== undefined || testOpts._testGrantsDb !== undefined || testOpts._testVaultPath !== undefined || testOpts._testPassphrase !== undefined || testOpts._testAgentName !== undefined;
|
|
15177
15580
|
if (usingTestOpt && true) {
|
|
15178
15581
|
throw new Error("VaultBroker: BrokerTestOpts (_testSecrets/_testConfig/_testIdentify/_testAuditLogger/_testGrantsDb/_testVaultPath) " + "must not be set outside tests. Set NODE_ENV=test if you really mean it.");
|
|
15179
15582
|
}
|
|
@@ -15191,8 +15594,8 @@ class VaultBroker {
|
|
|
15191
15594
|
if (process.platform !== "linux" && process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX !== "1") {
|
|
15192
15595
|
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.`);
|
|
15193
15596
|
}
|
|
15194
|
-
this.socketPath =
|
|
15195
|
-
this.unlockSocketPath = this.socketPath
|
|
15597
|
+
this.socketPath = resolve5(socketPath);
|
|
15598
|
+
this.unlockSocketPath = unlockSocketFor(this.socketPath);
|
|
15196
15599
|
this.startedAt = Date.now();
|
|
15197
15600
|
if (this.testOpts._testConfig) {
|
|
15198
15601
|
this.config = this.testOpts._testConfig;
|
|
@@ -15201,13 +15604,16 @@ class VaultBroker {
|
|
|
15201
15604
|
this.config = loadConfig2(configPath);
|
|
15202
15605
|
}
|
|
15203
15606
|
if (vaultPath) {
|
|
15204
|
-
this.vaultPath =
|
|
15607
|
+
this.vaultPath = resolve5(vaultPath);
|
|
15205
15608
|
} else {
|
|
15206
15609
|
this.vaultPath = resolvePath(this.config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
15207
15610
|
}
|
|
15208
15611
|
if (this.testOpts._testSecrets !== undefined) {
|
|
15209
15612
|
this.secrets = { ...this.testOpts._testSecrets };
|
|
15210
15613
|
}
|
|
15614
|
+
if (this.testOpts._testPassphrase !== undefined) {
|
|
15615
|
+
this.passphrase = this.testOpts._testPassphrase;
|
|
15616
|
+
}
|
|
15211
15617
|
process.umask(63);
|
|
15212
15618
|
const parentDir = dirname4(this.socketPath);
|
|
15213
15619
|
mkdirSync5(parentDir, { recursive: true, mode: 448 });
|
|
@@ -15215,7 +15621,7 @@ class VaultBroker {
|
|
|
15215
15621
|
chmodSync4(parentDir, 448);
|
|
15216
15622
|
} catch {}
|
|
15217
15623
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
15218
|
-
if (
|
|
15624
|
+
if (existsSync8(p)) {
|
|
15219
15625
|
try {
|
|
15220
15626
|
unlinkSync4(p);
|
|
15221
15627
|
} catch {}
|
|
@@ -15265,7 +15671,7 @@ class VaultBroker {
|
|
|
15265
15671
|
try {
|
|
15266
15672
|
entry.server.close();
|
|
15267
15673
|
} catch {}
|
|
15268
|
-
if (
|
|
15674
|
+
if (existsSync8(sockPath)) {
|
|
15269
15675
|
try {
|
|
15270
15676
|
unlinkSync4(sockPath);
|
|
15271
15677
|
} catch {}
|
|
@@ -15273,7 +15679,7 @@ class VaultBroker {
|
|
|
15273
15679
|
}
|
|
15274
15680
|
this.agentServers.clear();
|
|
15275
15681
|
for (const p of [this.socketPath, this.unlockSocketPath]) {
|
|
15276
|
-
if (p &&
|
|
15682
|
+
if (p && existsSync8(p)) {
|
|
15277
15683
|
try {
|
|
15278
15684
|
unlinkSync4(p);
|
|
15279
15685
|
} catch {}
|
|
@@ -15281,7 +15687,7 @@ class VaultBroker {
|
|
|
15281
15687
|
}
|
|
15282
15688
|
try {
|
|
15283
15689
|
const pidPath = resolvePath(PID_FILE_DEFAULT);
|
|
15284
|
-
if (
|
|
15690
|
+
if (existsSync8(pidPath))
|
|
15285
15691
|
unlinkSync4(pidPath);
|
|
15286
15692
|
} catch {}
|
|
15287
15693
|
}
|
|
@@ -15296,7 +15702,7 @@ class VaultBroker {
|
|
|
15296
15702
|
return this.secrets;
|
|
15297
15703
|
}
|
|
15298
15704
|
bindAgentSocket(socketPath) {
|
|
15299
|
-
const abs =
|
|
15705
|
+
const abs = resolve5(socketPath);
|
|
15300
15706
|
const agentName = socketPathToAgent(abs);
|
|
15301
15707
|
if (agentName === null) {
|
|
15302
15708
|
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`));
|
|
@@ -15304,7 +15710,7 @@ class VaultBroker {
|
|
|
15304
15710
|
return new Promise((resolveP, rejectP) => {
|
|
15305
15711
|
if (abs.endsWith("/sock")) {
|
|
15306
15712
|
const dir = abs.slice(0, -"/sock".length);
|
|
15307
|
-
if (
|
|
15713
|
+
if (existsSync8(dir)) {
|
|
15308
15714
|
try {
|
|
15309
15715
|
chownSync(dir, 0, 0);
|
|
15310
15716
|
} catch {}
|
|
@@ -15313,7 +15719,7 @@ class VaultBroker {
|
|
|
15313
15719
|
} catch {}
|
|
15314
15720
|
}
|
|
15315
15721
|
}
|
|
15316
|
-
if (
|
|
15722
|
+
if (existsSync8(abs)) {
|
|
15317
15723
|
try {
|
|
15318
15724
|
unlinkSync4(abs);
|
|
15319
15725
|
} catch (err) {
|
|
@@ -15348,7 +15754,7 @@ class VaultBroker {
|
|
|
15348
15754
|
});
|
|
15349
15755
|
}
|
|
15350
15756
|
_bindDataSocket() {
|
|
15351
|
-
return new Promise((
|
|
15757
|
+
return new Promise((resolve6, reject) => {
|
|
15352
15758
|
const server = net.createServer((socket) => {
|
|
15353
15759
|
this._handleDataConnection(socket);
|
|
15354
15760
|
});
|
|
@@ -15360,12 +15766,76 @@ class VaultBroker {
|
|
|
15360
15766
|
chmodSync4(this.socketPath, 384);
|
|
15361
15767
|
} catch {}
|
|
15362
15768
|
this.server = server;
|
|
15363
|
-
|
|
15769
|
+
resolve6();
|
|
15770
|
+
});
|
|
15771
|
+
});
|
|
15772
|
+
}
|
|
15773
|
+
bindOperatorListener(socketPath, operatorUid) {
|
|
15774
|
+
const abs = resolve5(socketPath);
|
|
15775
|
+
const identity2 = socketPathToIdentity(abs);
|
|
15776
|
+
if (identity2?.kind !== "operator") {
|
|
15777
|
+
return Promise.reject(new Error(`bindOperatorListener: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/operator/sock shape — refusing to bind`));
|
|
15778
|
+
}
|
|
15779
|
+
const unlockAbs = unlockSocketFor(abs);
|
|
15780
|
+
if (abs.endsWith("/sock")) {
|
|
15781
|
+
const dir = abs.slice(0, -"/sock".length);
|
|
15782
|
+
if (existsSync8(dir)) {
|
|
15783
|
+
try {
|
|
15784
|
+
chownSync(dir, 0, 0);
|
|
15785
|
+
} catch {}
|
|
15786
|
+
try {
|
|
15787
|
+
chmodSync4(dir, 448);
|
|
15788
|
+
} catch {}
|
|
15789
|
+
}
|
|
15790
|
+
}
|
|
15791
|
+
for (const p of [abs, unlockAbs]) {
|
|
15792
|
+
if (existsSync8(p)) {
|
|
15793
|
+
try {
|
|
15794
|
+
unlinkSync4(p);
|
|
15795
|
+
} catch {}
|
|
15796
|
+
}
|
|
15797
|
+
}
|
|
15798
|
+
return new Promise((resolveP, rejectP) => {
|
|
15799
|
+
const dataServer = net.createServer((sock) => {
|
|
15800
|
+
this._handleDataConnection(sock, abs, null, true);
|
|
15801
|
+
});
|
|
15802
|
+
dataServer.on("error", (err) => rejectP(err));
|
|
15803
|
+
dataServer.listen(abs, () => {
|
|
15804
|
+
try {
|
|
15805
|
+
chmodSync4(abs, 384);
|
|
15806
|
+
} catch {}
|
|
15807
|
+
try {
|
|
15808
|
+
chownSync(abs, operatorUid, operatorUid);
|
|
15809
|
+
} catch {}
|
|
15810
|
+
const unlockServer = net.createServer((sock) => {
|
|
15811
|
+
this._handleUnlockConnection(sock);
|
|
15812
|
+
});
|
|
15813
|
+
unlockServer.on("error", (err) => rejectP(err));
|
|
15814
|
+
unlockServer.listen(unlockAbs, () => {
|
|
15815
|
+
try {
|
|
15816
|
+
chmodSync4(unlockAbs, 384);
|
|
15817
|
+
} catch {}
|
|
15818
|
+
try {
|
|
15819
|
+
chownSync(unlockAbs, operatorUid, operatorUid);
|
|
15820
|
+
} catch {}
|
|
15821
|
+
if (abs.endsWith("/sock")) {
|
|
15822
|
+
const dir = abs.slice(0, -"/sock".length);
|
|
15823
|
+
try {
|
|
15824
|
+
chownSync(dir, operatorUid, operatorUid);
|
|
15825
|
+
} catch {}
|
|
15826
|
+
try {
|
|
15827
|
+
chmodSync4(dir, 448);
|
|
15828
|
+
} catch {}
|
|
15829
|
+
}
|
|
15830
|
+
this.agentServers.set(abs, { server: dataServer, agentName: "__operator__" });
|
|
15831
|
+
this.agentServers.set(unlockAbs, { server: unlockServer, agentName: "__operator_unlock__" });
|
|
15832
|
+
resolveP();
|
|
15833
|
+
});
|
|
15364
15834
|
});
|
|
15365
15835
|
});
|
|
15366
15836
|
}
|
|
15367
15837
|
_bindUnlockSocket() {
|
|
15368
|
-
return new Promise((
|
|
15838
|
+
return new Promise((resolve6, reject) => {
|
|
15369
15839
|
const server = net.createServer((socket) => {
|
|
15370
15840
|
this._handleUnlockConnection(socket);
|
|
15371
15841
|
});
|
|
@@ -15377,11 +15847,11 @@ class VaultBroker {
|
|
|
15377
15847
|
chmodSync4(this.unlockSocketPath, 384);
|
|
15378
15848
|
} catch {}
|
|
15379
15849
|
this.unlockServer = server;
|
|
15380
|
-
|
|
15850
|
+
resolve6();
|
|
15381
15851
|
});
|
|
15382
15852
|
});
|
|
15383
15853
|
}
|
|
15384
|
-
_handleDataConnection(socket, listenerSocketPath = this.socketPath, agentName = null) {
|
|
15854
|
+
_handleDataConnection(socket, listenerSocketPath = this.socketPath, agentName = this.testOpts._testAgentName ?? null, isOperator = false) {
|
|
15385
15855
|
let peer = null;
|
|
15386
15856
|
if (process.platform === "linux") {
|
|
15387
15857
|
peer = this.testOpts._testIdentify ? this.testOpts._testIdentify(listenerSocketPath, socket) : identify(listenerSocketPath, socket);
|
|
@@ -15391,8 +15861,7 @@ class VaultBroker {
|
|
|
15391
15861
|
buffer += chunk.toString("utf8");
|
|
15392
15862
|
if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
|
|
15393
15863
|
const resp = encodeResponse(errorResponse("BAD_REQUEST", "Frame exceeds 64 KiB limit"));
|
|
15394
|
-
socket.
|
|
15395
|
-
socket.destroy();
|
|
15864
|
+
socket.end(resp);
|
|
15396
15865
|
return;
|
|
15397
15866
|
}
|
|
15398
15867
|
let newlineIdx;
|
|
@@ -15402,14 +15871,14 @@ class VaultBroker {
|
|
|
15402
15871
|
buffer = buffer.slice(newlineIdx + 1);
|
|
15403
15872
|
if (!line)
|
|
15404
15873
|
continue;
|
|
15405
|
-
this._handleRequest(socket, peer, line, agentName);
|
|
15874
|
+
this._handleRequest(socket, peer, line, agentName, isOperator);
|
|
15406
15875
|
}
|
|
15407
15876
|
});
|
|
15408
15877
|
socket.on("error", () => {
|
|
15409
15878
|
socket.destroy();
|
|
15410
15879
|
});
|
|
15411
15880
|
}
|
|
15412
|
-
async _handleRequest(socket, peer, line, agentName = null) {
|
|
15881
|
+
async _handleRequest(socket, peer, line, agentName = null, isOperator = false) {
|
|
15413
15882
|
let req;
|
|
15414
15883
|
try {
|
|
15415
15884
|
req = decodeRequest(line);
|
|
@@ -15419,7 +15888,7 @@ class VaultBroker {
|
|
|
15419
15888
|
return;
|
|
15420
15889
|
}
|
|
15421
15890
|
const auditPid = peer?.pid ?? process.pid;
|
|
15422
|
-
const auditCaller = agentName !== null ? `agent:${agentName}` : peer !== null ? callerFromPeer(peer) : `pid:${process.pid}`;
|
|
15891
|
+
const auditCaller = isOperator ? "operator" : agentName !== null ? `agent:${agentName}` : peer !== null ? callerFromPeer(peer) : `pid:${process.pid}`;
|
|
15423
15892
|
const auditCgroup = peer?.systemdUnit ?? undefined;
|
|
15424
15893
|
const auditPeerUid = peer?.uid;
|
|
15425
15894
|
const auditAgentName = agentName ?? undefined;
|
|
@@ -15488,7 +15957,7 @@ class VaultBroker {
|
|
|
15488
15957
|
socket.write(encodeResponse({ ok: true, keys: visibleKeys2 }));
|
|
15489
15958
|
return;
|
|
15490
15959
|
}
|
|
15491
|
-
if (agentName === null && process.platform === "linux" && peer === null) {
|
|
15960
|
+
if (!isOperator && agentName === null && process.platform === "linux" && peer === null) {
|
|
15492
15961
|
const reason = "Unable to identify caller (peercred unavailable); denying on Linux";
|
|
15493
15962
|
this.auditLogger.write({
|
|
15494
15963
|
ts: new Date().toISOString(),
|
|
@@ -15503,7 +15972,9 @@ class VaultBroker {
|
|
|
15503
15972
|
}
|
|
15504
15973
|
const listAgentSlug = agentName ?? (peer !== null ? agentSlugFromPeer(peer) : null);
|
|
15505
15974
|
let visibleKeys;
|
|
15506
|
-
if (
|
|
15975
|
+
if (isOperator) {
|
|
15976
|
+
visibleKeys = Object.entries(this.secrets).filter(([, entry]) => checkEntryScope(entry.scope, "operator").allow).map(([k]) => k);
|
|
15977
|
+
} else if (agentName !== null && this.config !== null) {
|
|
15507
15978
|
visibleKeys = Object.entries(this.secrets).filter(([key, entry]) => checkAclByAgent(this.config, agentName, key).allow && checkEntryScope(entry.scope, agentName).allow).map(([k]) => k);
|
|
15508
15979
|
} else if (peer !== null && this.config !== null) {
|
|
15509
15980
|
visibleKeys = Object.entries(this.secrets).filter(([key, entry]) => checkAcl(peer, this.config, key).allow && checkEntryScope(entry.scope, listAgentSlug).allow).map(([k]) => k);
|
|
@@ -15578,7 +16049,25 @@ class VaultBroker {
|
|
|
15578
16049
|
return;
|
|
15579
16050
|
}
|
|
15580
16051
|
}
|
|
15581
|
-
if (
|
|
16052
|
+
if (isOperator) {
|
|
16053
|
+
const entry2 = this.secrets[req.key];
|
|
16054
|
+
if (entry2 !== undefined) {
|
|
16055
|
+
const scopeResult2 = checkEntryScope(entry2.scope, "operator");
|
|
16056
|
+
if (!scopeResult2.allow) {
|
|
16057
|
+
writeAudit({
|
|
16058
|
+
ts: new Date().toISOString(),
|
|
16059
|
+
op: "get",
|
|
16060
|
+
key: req.key,
|
|
16061
|
+
caller: auditCaller,
|
|
16062
|
+
pid: auditPid,
|
|
16063
|
+
cgroup: auditCgroup,
|
|
16064
|
+
result: `denied:${scopeResult2.reason}`
|
|
16065
|
+
});
|
|
16066
|
+
socket.write(encodeResponse(errorResponse("DENIED", scopeResult2.reason)));
|
|
16067
|
+
return;
|
|
16068
|
+
}
|
|
16069
|
+
}
|
|
16070
|
+
} else if (agentName !== null && this.config !== null) {
|
|
15582
16071
|
const aclResult = checkAclByAgent(this.config, agentName, req.key);
|
|
15583
16072
|
if (!aclResult.allow) {
|
|
15584
16073
|
writeAudit({
|
|
@@ -15669,6 +16158,74 @@ class VaultBroker {
|
|
|
15669
16158
|
return;
|
|
15670
16159
|
}
|
|
15671
16160
|
let passphraseAttested = false;
|
|
16161
|
+
const requestedPostureAttest = req.attest_via_posture === true;
|
|
16162
|
+
if (requestedPostureAttest && req.passphrase !== undefined && req.passphrase !== "") {
|
|
16163
|
+
writeAudit({
|
|
16164
|
+
ts: new Date().toISOString(),
|
|
16165
|
+
op: "put",
|
|
16166
|
+
key: req.key,
|
|
16167
|
+
caller: auditCaller,
|
|
16168
|
+
pid: auditPid,
|
|
16169
|
+
cgroup: auditCgroup,
|
|
16170
|
+
result: "denied:bad-request-both-attestations"
|
|
16171
|
+
});
|
|
16172
|
+
socket.write(encodeResponse(errorResponse("BAD_REQUEST", "put: passphrase and attest_via_posture are mutually exclusive")));
|
|
16173
|
+
return;
|
|
16174
|
+
}
|
|
16175
|
+
if (requestedPostureAttest) {
|
|
16176
|
+
if (agentName === null) {
|
|
16177
|
+
writeAudit({
|
|
16178
|
+
ts: new Date().toISOString(),
|
|
16179
|
+
op: "put",
|
|
16180
|
+
key: req.key,
|
|
16181
|
+
caller: auditCaller,
|
|
16182
|
+
pid: auditPid,
|
|
16183
|
+
cgroup: auditCgroup,
|
|
16184
|
+
result: "denied:posture-attest-needs-per-agent-peer"
|
|
16185
|
+
});
|
|
16186
|
+
socket.write(encodeResponse(errorResponse("DENIED", "put attest_via_posture is per-agent only")));
|
|
16187
|
+
return;
|
|
16188
|
+
}
|
|
16189
|
+
const postureMode = this.config?.vault?.broker?.approvalAuth ?? "passphrase";
|
|
16190
|
+
if (postureMode !== "telegram-id") {
|
|
16191
|
+
writeAudit({
|
|
16192
|
+
ts: new Date().toISOString(),
|
|
16193
|
+
op: "put",
|
|
16194
|
+
key: req.key,
|
|
16195
|
+
caller: auditCaller,
|
|
16196
|
+
pid: auditPid,
|
|
16197
|
+
cgroup: auditCgroup,
|
|
16198
|
+
result: "denied:telegram-id-not-enabled"
|
|
16199
|
+
});
|
|
16200
|
+
socket.write(encodeResponse(errorResponse("DENIED", "put attest_via_posture requires vault.broker.approvalAuth: telegram-id in broker config")));
|
|
16201
|
+
return;
|
|
16202
|
+
}
|
|
16203
|
+
const postureAllowlist = this.config?.vault?.broker?.postureMintAgents ?? [];
|
|
16204
|
+
if (!postureAllowlist.includes(agentName)) {
|
|
16205
|
+
writeAudit({
|
|
16206
|
+
ts: new Date().toISOString(),
|
|
16207
|
+
op: "put",
|
|
16208
|
+
key: req.key,
|
|
16209
|
+
caller: auditCaller,
|
|
16210
|
+
pid: auditPid,
|
|
16211
|
+
cgroup: auditCgroup,
|
|
16212
|
+
result: "denied:posture-agent-not-allowlisted"
|
|
16213
|
+
});
|
|
16214
|
+
socket.write(encodeResponse(errorResponse("DENIED", `agent '${agentName}' is not on vault.broker.postureMintAgents — operator must opt this agent into posture-attested put`)));
|
|
16215
|
+
return;
|
|
16216
|
+
}
|
|
16217
|
+
passphraseAttested = true;
|
|
16218
|
+
writeAudit({
|
|
16219
|
+
ts: new Date().toISOString(),
|
|
16220
|
+
op: "put",
|
|
16221
|
+
key: req.key,
|
|
16222
|
+
caller: auditCaller,
|
|
16223
|
+
pid: auditPid,
|
|
16224
|
+
cgroup: auditCgroup,
|
|
16225
|
+
result: "allowed:posture-attested",
|
|
16226
|
+
method: "posture"
|
|
16227
|
+
});
|
|
16228
|
+
}
|
|
15672
16229
|
if (req.passphrase !== undefined && req.passphrase !== "") {
|
|
15673
16230
|
if (req.passphrase === this.passphrase) {
|
|
15674
16231
|
passphraseAttested = true;
|
|
@@ -15799,46 +16356,180 @@ class VaultBroker {
|
|
|
15799
16356
|
return;
|
|
15800
16357
|
}
|
|
15801
16358
|
const isGrantMgmtOp = req.op === "mint_grant" || req.op === "list_grants" || req.op === "revoke_grant";
|
|
16359
|
+
let mintPassphraseAttested = false;
|
|
15802
16360
|
if (isGrantMgmtOp) {
|
|
15803
|
-
|
|
16361
|
+
const isAdminAgent = agentName !== null && this.config?.agents?.[agentName]?.admin === true;
|
|
16362
|
+
if ((req.op === "mint_grant" || req.op === "list_grants") && req.passphrase !== undefined && req.passphrase !== "") {
|
|
16363
|
+
if (req.attest_via_posture === true) {
|
|
16364
|
+
writeAudit({
|
|
16365
|
+
ts: new Date().toISOString(),
|
|
16366
|
+
op: req.op,
|
|
16367
|
+
caller: auditCaller,
|
|
16368
|
+
pid: auditPid,
|
|
16369
|
+
cgroup: auditCgroup,
|
|
16370
|
+
result: "denied:bad-request-both-attestations"
|
|
16371
|
+
});
|
|
16372
|
+
socket.write(encodeResponse(errorResponse("BAD_REQUEST", "mint_grant: passphrase and attest_via_posture are mutually exclusive")));
|
|
16373
|
+
return;
|
|
16374
|
+
}
|
|
16375
|
+
if (req.passphrase === this.passphrase) {
|
|
16376
|
+
mintPassphraseAttested = true;
|
|
16377
|
+
} else {
|
|
16378
|
+
writeAudit({
|
|
16379
|
+
ts: new Date().toISOString(),
|
|
16380
|
+
op: req.op,
|
|
16381
|
+
caller: auditCaller,
|
|
16382
|
+
pid: auditPid,
|
|
16383
|
+
cgroup: auditCgroup,
|
|
16384
|
+
result: "denied:passphrase-mismatch",
|
|
16385
|
+
method: "passphrase"
|
|
16386
|
+
});
|
|
16387
|
+
socket.write(encodeResponse(errorResponse("DENIED", "supplied passphrase does not match the broker's unlocked passphrase")));
|
|
16388
|
+
return;
|
|
16389
|
+
}
|
|
16390
|
+
}
|
|
16391
|
+
let mintPostureAttested = false;
|
|
16392
|
+
if ((req.op === "mint_grant" || req.op === "list_grants") && req.attest_via_posture === true) {
|
|
16393
|
+
if (agentName === null) {
|
|
16394
|
+
writeAudit({
|
|
16395
|
+
ts: new Date().toISOString(),
|
|
16396
|
+
op: req.op,
|
|
16397
|
+
caller: auditCaller,
|
|
16398
|
+
pid: auditPid,
|
|
16399
|
+
cgroup: auditCgroup,
|
|
16400
|
+
result: "denied:posture-attest-needs-per-agent-peer"
|
|
16401
|
+
});
|
|
16402
|
+
socket.write(encodeResponse(errorResponse("DENIED", "mint_grant attest_via_posture is per-agent only")));
|
|
16403
|
+
return;
|
|
16404
|
+
}
|
|
16405
|
+
const postureMode = this.config?.vault?.broker?.approvalAuth ?? "passphrase";
|
|
16406
|
+
if (postureMode !== "telegram-id") {
|
|
16407
|
+
writeAudit({
|
|
16408
|
+
ts: new Date().toISOString(),
|
|
16409
|
+
op: req.op,
|
|
16410
|
+
caller: auditCaller,
|
|
16411
|
+
pid: auditPid,
|
|
16412
|
+
cgroup: auditCgroup,
|
|
16413
|
+
result: "denied:telegram-id-not-enabled"
|
|
16414
|
+
});
|
|
16415
|
+
socket.write(encodeResponse(errorResponse("DENIED", "mint_grant attest_via_posture requires vault.broker.approvalAuth: telegram-id in broker config")));
|
|
16416
|
+
return;
|
|
16417
|
+
}
|
|
16418
|
+
const postureAllowlist = this.config?.vault?.broker?.postureMintAgents ?? [];
|
|
16419
|
+
if (!postureAllowlist.includes(agentName)) {
|
|
16420
|
+
writeAudit({
|
|
16421
|
+
ts: new Date().toISOString(),
|
|
16422
|
+
op: req.op,
|
|
16423
|
+
caller: auditCaller,
|
|
16424
|
+
pid: auditPid,
|
|
16425
|
+
cgroup: auditCgroup,
|
|
16426
|
+
result: "denied:posture-agent-not-allowlisted"
|
|
16427
|
+
});
|
|
16428
|
+
socket.write(encodeResponse(errorResponse("DENIED", `agent '${agentName}' is not on vault.broker.postureMintAgents — operator must opt this agent into posture-attested mint`)));
|
|
16429
|
+
return;
|
|
16430
|
+
}
|
|
16431
|
+
const reqAgent = req.agent;
|
|
16432
|
+
if (req.op === "mint_grant" && reqAgent !== agentName) {
|
|
16433
|
+
writeAudit({
|
|
16434
|
+
ts: new Date().toISOString(),
|
|
16435
|
+
op: req.op,
|
|
16436
|
+
caller: auditCaller,
|
|
16437
|
+
pid: auditPid,
|
|
16438
|
+
cgroup: auditCgroup,
|
|
16439
|
+
result: "denied:posture-cross-agent-mint-refused"
|
|
16440
|
+
});
|
|
16441
|
+
socket.write(encodeResponse(errorResponse("DENIED", `posture-attested mint refused: request.agent=${reqAgent ?? "<unset>"} but calling peer is ${agentName}`)));
|
|
16442
|
+
return;
|
|
16443
|
+
}
|
|
16444
|
+
if (req.op === "list_grants" && reqAgent !== undefined && reqAgent !== agentName) {
|
|
16445
|
+
writeAudit({
|
|
16446
|
+
ts: new Date().toISOString(),
|
|
16447
|
+
op: req.op,
|
|
16448
|
+
caller: auditCaller,
|
|
16449
|
+
pid: auditPid,
|
|
16450
|
+
cgroup: auditCgroup,
|
|
16451
|
+
result: "denied:posture-cross-agent-list-refused"
|
|
16452
|
+
});
|
|
16453
|
+
socket.write(encodeResponse(errorResponse("DENIED", `posture-attested list refused: request.agent=${reqAgent} but calling peer is ${agentName}`)));
|
|
16454
|
+
return;
|
|
16455
|
+
}
|
|
16456
|
+
if (this.passphrase === null) {
|
|
16457
|
+
writeAudit({
|
|
16458
|
+
ts: new Date().toISOString(),
|
|
16459
|
+
op: req.op,
|
|
16460
|
+
caller: auditCaller,
|
|
16461
|
+
pid: auditPid,
|
|
16462
|
+
cgroup: auditCgroup,
|
|
16463
|
+
result: "denied:broker-locked"
|
|
16464
|
+
});
|
|
16465
|
+
socket.write(encodeResponse(errorResponse("LOCKED", "Broker is locked")));
|
|
16466
|
+
return;
|
|
16467
|
+
}
|
|
16468
|
+
mintPostureAttested = true;
|
|
15804
16469
|
writeAudit({
|
|
15805
16470
|
ts: new Date().toISOString(),
|
|
15806
16471
|
op: req.op,
|
|
15807
16472
|
caller: auditCaller,
|
|
15808
16473
|
pid: auditPid,
|
|
15809
16474
|
cgroup: auditCgroup,
|
|
15810
|
-
result: "
|
|
16475
|
+
result: "allowed:posture-attested",
|
|
16476
|
+
method: "posture"
|
|
15811
16477
|
});
|
|
15812
|
-
socket.write(encodeResponse(errorResponse("DENIED", "Grant management ops are operator-only; agent-bound listeners cannot mint, list, or revoke grants")));
|
|
15813
|
-
return;
|
|
15814
16478
|
}
|
|
15815
|
-
const
|
|
15816
|
-
if (
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15825
|
-
|
|
15826
|
-
|
|
16479
|
+
const trustedForGrantMgmt = isOperator || isAdminAgent || mintPassphraseAttested || mintPostureAttested;
|
|
16480
|
+
if (isOperator) {} else if (agentName !== null) {
|
|
16481
|
+
if (isAdminAgent) {} else if (mintPassphraseAttested) {
|
|
16482
|
+
writeAudit({
|
|
16483
|
+
ts: new Date().toISOString(),
|
|
16484
|
+
op: req.op,
|
|
16485
|
+
caller: auditCaller,
|
|
16486
|
+
pid: auditPid,
|
|
16487
|
+
cgroup: auditCgroup,
|
|
16488
|
+
result: "allowed:passphrase-attested",
|
|
16489
|
+
method: "passphrase"
|
|
16490
|
+
});
|
|
16491
|
+
} else if (mintPostureAttested) {} else {
|
|
16492
|
+
writeAudit({
|
|
16493
|
+
ts: new Date().toISOString(),
|
|
16494
|
+
op: req.op,
|
|
16495
|
+
caller: auditCaller,
|
|
16496
|
+
pid: auditPid,
|
|
16497
|
+
cgroup: auditCgroup,
|
|
16498
|
+
result: "denied:agent-cannot-manage-grants"
|
|
16499
|
+
});
|
|
16500
|
+
socket.write(encodeResponse(errorResponse("DENIED", "Grant management ops are operator-only; agent-bound listeners cannot mint, list, or revoke grants (set `admin: true` on this agent in switchroom.yaml + restart broker, OR forward operator-passphrase attestation if intended — see vault_request_access flow)")));
|
|
16501
|
+
return;
|
|
16502
|
+
}
|
|
15827
16503
|
}
|
|
15828
|
-
if (
|
|
15829
|
-
const
|
|
15830
|
-
if (
|
|
16504
|
+
if (!trustedForGrantMgmt) {
|
|
16505
|
+
const allowNonLinux = process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX === "1";
|
|
16506
|
+
if (peer === null && !allowNonLinux) {
|
|
15831
16507
|
this.auditLogger.write({
|
|
15832
16508
|
ts: new Date().toISOString(),
|
|
15833
16509
|
op: req.op,
|
|
15834
16510
|
caller: auditCaller,
|
|
15835
16511
|
pid: auditPid,
|
|
15836
16512
|
cgroup: auditCgroup,
|
|
15837
|
-
result: "denied:
|
|
16513
|
+
result: "denied:peercred-unavailable"
|
|
15838
16514
|
});
|
|
15839
|
-
socket.write(encodeResponse(errorResponse("DENIED", "
|
|
16515
|
+
socket.write(encodeResponse(errorResponse("DENIED", "peercred unavailable; cannot verify operator identity")));
|
|
15840
16516
|
return;
|
|
15841
16517
|
}
|
|
16518
|
+
if (peer !== null && peer.systemdUnit !== null) {
|
|
16519
|
+
const parsed = parseCronUnit(peer.systemdUnit);
|
|
16520
|
+
if (parsed !== null) {
|
|
16521
|
+
this.auditLogger.write({
|
|
16522
|
+
ts: new Date().toISOString(),
|
|
16523
|
+
op: req.op,
|
|
16524
|
+
caller: auditCaller,
|
|
16525
|
+
pid: auditPid,
|
|
16526
|
+
cgroup: auditCgroup,
|
|
16527
|
+
result: "denied:cron-cannot-manage-grants"
|
|
16528
|
+
});
|
|
16529
|
+
socket.write(encodeResponse(errorResponse("DENIED", "Grant management ops are operator-only; cron units cannot mint, list, or revoke grants")));
|
|
16530
|
+
return;
|
|
16531
|
+
}
|
|
16532
|
+
}
|
|
15842
16533
|
}
|
|
15843
16534
|
}
|
|
15844
16535
|
if (req.op === "mint_grant") {
|
|
@@ -15908,7 +16599,8 @@ class VaultBroker {
|
|
|
15908
16599
|
caller: auditCaller,
|
|
15909
16600
|
pid: auditPid,
|
|
15910
16601
|
cgroup: auditCgroup,
|
|
15911
|
-
result: `allowed:${grants.length}
|
|
16602
|
+
result: `allowed:${grants.length}`,
|
|
16603
|
+
...mintPassphraseAttested ? { method: "passphrase" } : {}
|
|
15912
16604
|
});
|
|
15913
16605
|
const grantMetas = grants.map(({ id, agent_slug, key_allow, write_allow, expires_at, created_at, description }) => ({
|
|
15914
16606
|
id,
|
|
@@ -15929,7 +16621,7 @@ class VaultBroker {
|
|
|
15929
16621
|
const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
|
|
15930
16622
|
if (row) {
|
|
15931
16623
|
const tokenPath = path3.join(os3.homedir(), ".switchroom", "agents", row.agent_slug, ".vault-token");
|
|
15932
|
-
if (
|
|
16624
|
+
if (existsSync8(tokenPath)) {
|
|
15933
16625
|
try {
|
|
15934
16626
|
unlinkSync4(tokenPath);
|
|
15935
16627
|
} catch {}
|
|
@@ -16083,9 +16775,8 @@ class VaultBroker {
|
|
|
16083
16775
|
pid: process.pid,
|
|
16084
16776
|
result: "denied:unable to verify caller identity"
|
|
16085
16777
|
});
|
|
16086
|
-
socket.
|
|
16778
|
+
socket.end(`ERR unable to verify caller identity
|
|
16087
16779
|
`);
|
|
16088
|
-
socket.destroy();
|
|
16089
16780
|
return;
|
|
16090
16781
|
}
|
|
16091
16782
|
}
|
|
@@ -16099,9 +16790,8 @@ class VaultBroker {
|
|
|
16099
16790
|
`);
|
|
16100
16791
|
if (newlineIdx === -1) {
|
|
16101
16792
|
if (Buffer.byteLength(buffer, "utf8") > 4096) {
|
|
16102
|
-
socket.
|
|
16793
|
+
socket.end(`ERR passphrase too long
|
|
16103
16794
|
`);
|
|
16104
|
-
socket.destroy();
|
|
16105
16795
|
buffer = "";
|
|
16106
16796
|
}
|
|
16107
16797
|
return;
|
|
@@ -16117,9 +16807,8 @@ class VaultBroker {
|
|
|
16117
16807
|
cgroup: auditCgroup,
|
|
16118
16808
|
result: "denied:passphrase cannot be empty"
|
|
16119
16809
|
});
|
|
16120
|
-
socket.
|
|
16810
|
+
socket.end(`ERR passphrase cannot be empty
|
|
16121
16811
|
`);
|
|
16122
|
-
socket.destroy();
|
|
16123
16812
|
return;
|
|
16124
16813
|
}
|
|
16125
16814
|
try {
|
|
@@ -16132,7 +16821,7 @@ class VaultBroker {
|
|
|
16132
16821
|
cgroup: auditCgroup,
|
|
16133
16822
|
result: "allowed"
|
|
16134
16823
|
});
|
|
16135
|
-
socket.
|
|
16824
|
+
socket.end(`OK
|
|
16136
16825
|
`);
|
|
16137
16826
|
} catch (err) {
|
|
16138
16827
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -16146,10 +16835,8 @@ class VaultBroker {
|
|
|
16146
16835
|
cgroup: auditCgroup,
|
|
16147
16836
|
result: "error:decryption failed"
|
|
16148
16837
|
});
|
|
16149
|
-
socket.
|
|
16838
|
+
socket.end(`ERR decryption failed
|
|
16150
16839
|
`);
|
|
16151
|
-
} finally {
|
|
16152
|
-
socket.destroy();
|
|
16153
16840
|
}
|
|
16154
16841
|
});
|
|
16155
16842
|
socket.on("error", () => {
|
|
@@ -16172,7 +16859,7 @@ class VaultBroker {
|
|
|
16172
16859
|
const envPath = process.env.SWITCHROOM_VAULT_BROKER_AUTO_UNLOCK_PATH;
|
|
16173
16860
|
const configuredPath = (envPath && envPath.length > 0 ? envPath : undefined) ?? this.config?.vault?.broker?.autoUnlockCredentialPath ?? DEFAULT_AUTO_UNLOCK_PATH;
|
|
16174
16861
|
const filePath = resolvePath(configuredPath);
|
|
16175
|
-
if (!
|
|
16862
|
+
if (!existsSync8(filePath))
|
|
16176
16863
|
return false;
|
|
16177
16864
|
let passphrase;
|
|
16178
16865
|
try {
|
|
@@ -16209,7 +16896,7 @@ class VaultBroker {
|
|
|
16209
16896
|
const credPath = `${dir}/vault-passphrase`;
|
|
16210
16897
|
let passphrase;
|
|
16211
16898
|
try {
|
|
16212
|
-
passphrase =
|
|
16899
|
+
passphrase = readFileSync8(credPath, "utf8").replace(/\n+$/, "");
|
|
16213
16900
|
} catch (err) {
|
|
16214
16901
|
const code = err.code;
|
|
16215
16902
|
if (code === "ENOENT") {
|
|
@@ -16281,19 +16968,19 @@ async function main() {
|
|
|
16281
16968
|
const vaultPath = process.env.SWITCHROOM_VAULT_PATH;
|
|
16282
16969
|
let perAgentTargets = [];
|
|
16283
16970
|
try {
|
|
16284
|
-
if (
|
|
16285
|
-
const entries =
|
|
16971
|
+
if (existsSync8(perAgentDir)) {
|
|
16972
|
+
const entries = readdirSync3(perAgentDir, { withFileTypes: true });
|
|
16286
16973
|
const flat = [];
|
|
16287
16974
|
const subdirs = [];
|
|
16288
16975
|
for (const e of entries) {
|
|
16289
16976
|
if (e.name.startsWith("."))
|
|
16290
16977
|
continue;
|
|
16291
16978
|
if ((e.isFile() || e.isSocket()) && e.name.endsWith(".sock")) {
|
|
16292
|
-
flat.push(
|
|
16979
|
+
flat.push(resolve5(perAgentDir, e.name));
|
|
16293
16980
|
continue;
|
|
16294
16981
|
}
|
|
16295
16982
|
if (e.isDirectory()) {
|
|
16296
|
-
const candidate =
|
|
16983
|
+
const candidate = resolve5(perAgentDir, e.name, "sock");
|
|
16297
16984
|
if (socketPathToAgent(candidate) !== null) {
|
|
16298
16985
|
subdirs.push(candidate);
|
|
16299
16986
|
}
|
|
@@ -16321,6 +17008,25 @@ async function main() {
|
|
|
16321
17008
|
`);
|
|
16322
17009
|
}
|
|
16323
17010
|
}
|
|
17011
|
+
const operatorUidStr = process.env.SWITCHROOM_BROKER_OPERATOR_UID;
|
|
17012
|
+
const operatorDir = "/run/switchroom/broker/operator";
|
|
17013
|
+
if (operatorUidStr !== undefined && existsSync8(operatorDir)) {
|
|
17014
|
+
const operatorUid = parseInt(operatorUidStr, 10);
|
|
17015
|
+
if (!Number.isFinite(operatorUid) || operatorUid <= 0) {
|
|
17016
|
+
process.stderr.write(`[vault-broker] SWITCHROOM_BROKER_OPERATOR_UID='${operatorUidStr}' is not a positive integer; skipping operator listener
|
|
17017
|
+
`);
|
|
17018
|
+
} else {
|
|
17019
|
+
const operatorSock = `${operatorDir}/sock`;
|
|
17020
|
+
try {
|
|
17021
|
+
await broker.bindOperatorListener(operatorSock, operatorUid);
|
|
17022
|
+
process.stdout.write(`vault-broker: operator socket listening sock=${operatorSock} uid=${operatorUid}
|
|
17023
|
+
`);
|
|
17024
|
+
} catch (err) {
|
|
17025
|
+
process.stderr.write(`[vault-broker] failed to bind operator listener at ${operatorSock}: ${err.message}
|
|
17026
|
+
`);
|
|
17027
|
+
}
|
|
17028
|
+
}
|
|
17029
|
+
}
|
|
16324
17030
|
return;
|
|
16325
17031
|
}
|
|
16326
17032
|
await broker.start(legacySocketPath, configPath, vaultPath);
|