switchroom 0.17.5 → 0.17.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +39 -5
- package/dist/auth-broker/index.js +386 -208
- package/dist/cli/notion-write-pretool.mjs +36 -3
- package/dist/cli/switchroom.js +1185 -585
- package/dist/host-control/main.js +149 -15
- package/dist/vault/approvals/kernel-server.js +141 -56
- package/dist/vault/broker/server.js +143 -58
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +50 -6
- package/profiles/default/CLAUDE.md +116 -0
- package/skills/mental-model-curator/SKILL.md +162 -0
- package/telegram-plugin/auth-snapshot-format.ts +50 -2
- package/telegram-plugin/bridge/bridge.ts +80 -1
- package/telegram-plugin/bridge/ipc-client.ts +19 -0
- package/telegram-plugin/bridge/permission-ledger.ts +61 -0
- package/telegram-plugin/consolidation-legibility.ts +279 -0
- package/telegram-plugin/dist/bridge/bridge.js +85 -1
- package/telegram-plugin/dist/gateway/gateway.js +2597 -645
- package/telegram-plugin/dist/server.js +86 -2
- package/telegram-plugin/feed-heartbeat-climb.ts +206 -0
- package/telegram-plugin/gateway/activity-card-store.ts +293 -0
- package/telegram-plugin/gateway/auth-command.ts +1 -1
- package/telegram-plugin/gateway/gateway.ts +1414 -119
- package/telegram-plugin/gateway/inbound-spool.ts +22 -0
- package/telegram-plugin/gateway/mental-model-propose-card.ts +69 -0
- package/telegram-plugin/gateway/mental-model-propose-diff.ts +171 -0
- package/telegram-plugin/gateway/mental-model-propose-inbound-builders.ts +147 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +201 -0
- package/telegram-plugin/gateway/missed-approvals-card.ts +161 -0
- package/telegram-plugin/gateway/missed-approvals-store.ts +167 -0
- package/telegram-plugin/gateway/permission-rearm.ts +115 -0
- package/telegram-plugin/gateway/scoped-grant-store.ts +89 -0
- package/telegram-plugin/memory-legibility.ts +217 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/scoped-approval.ts +59 -0
- package/telegram-plugin/silent-end.ts +78 -0
- package/telegram-plugin/subagent-watcher.ts +60 -6
- package/telegram-plugin/tests/activity-card-store.test.ts +436 -0
- package/telegram-plugin/tests/activity-card-wiring.test.ts +88 -0
- package/telegram-plugin/tests/auth-snapshot-format.test.ts +75 -0
- package/telegram-plugin/tests/consolidation-legibility.test.ts +224 -0
- package/telegram-plugin/tests/emission-authority-facade.test.ts +25 -10
- package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +33 -9
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/inbound-spool.test.ts +105 -0
- package/telegram-plugin/tests/memory-legibility.test.ts +216 -0
- package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +67 -0
- package/telegram-plugin/tests/mental-model-propose-card.test.ts +56 -0
- package/telegram-plugin/tests/mental-model-propose-diff.test.ts +201 -0
- package/telegram-plugin/tests/mental-model-propose-inbound-builders.test.ts +68 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +157 -0
- package/telegram-plugin/tests/missed-approvals-card.test.ts +145 -0
- package/telegram-plugin/tests/missed-approvals-store.test.ts +147 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +89 -0
- package/telegram-plugin/tests/permission-ledger.test.ts +166 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +1 -1
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +175 -0
- package/telegram-plugin/tests/permission-rearm.test.ts +126 -0
- package/telegram-plugin/tests/scoped-grant-persist.test.ts +223 -0
- package/telegram-plugin/tests/silent-end-transport.test.ts +290 -0
- package/telegram-plugin/tests/silent-turn-climb-transport.test.ts +337 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +139 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +103 -0
- package/telegram-plugin/uat/assertions.ts +88 -4
- package/telegram-plugin/uat/feed-matcher.test.ts +69 -0
- package/telegram-plugin/uat/scenarios/fuzz-liveness-climb-dm.test.ts +155 -0
- package/telegram-plugin/uat/scenarios/jtbd-directive-capture-nudge-dm.test.ts +185 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-climb-channel.test.ts +192 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-climb-dm.test.ts +220 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +137 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +148 -0
- package/telegram-plugin/uat/scenarios/jtbd-memory-legibility-channel.test.ts +66 -0
- package/telegram-plugin/uat/scenarios/jtbd-memory-legibility-dm.test.ts +61 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-channel.test.ts +136 -0
- package/telegram-plugin/uat/scenarios/silent-end-recovery-dm.test.ts +24 -2
- package/telegram-plugin/worktree-watch-cwds.ts +60 -0
- package/vendor/hindsight-memory/hooks/hooks.json +9 -0
- package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +445 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +37 -0
- package/vendor/hindsight-memory/scripts/lib/directives.py +88 -0
- package/vendor/hindsight-memory/scripts/lib/switchroom_envelope.py +77 -0
- package/vendor/hindsight-memory/scripts/recall.py +153 -4
- package/vendor/hindsight-memory/scripts/retain.py +17 -0
- package/vendor/hindsight-memory/scripts/setup_hooks.py +9 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_capture_nudge.py +185 -0
- package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +516 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +49 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_window.py +66 -1
- package/vendor/hindsight-memory/scripts/tests/test_switchroom_envelope.py +69 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
- package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
- package/vendor/hindsight-memory/tests/test_recall_exit_codes.py +49 -2
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
2
4
|
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
3
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4
33
|
var __returnValue = (v) => v;
|
|
5
34
|
function __exportSetter(name, newValue) {
|
|
@@ -14,6 +43,7 @@ var __export = (target, all) => {
|
|
|
14
43
|
set: __exportSetter.bind(all, name)
|
|
15
44
|
});
|
|
16
45
|
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
48
|
|
|
19
49
|
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
|
|
@@ -6931,60 +6961,99 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6931
6961
|
exports.stringify = stringify;
|
|
6932
6962
|
});
|
|
6933
6963
|
|
|
6964
|
+
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
|
|
6965
|
+
var require_dist = __commonJS((exports) => {
|
|
6966
|
+
var composer = require_composer();
|
|
6967
|
+
var Document = require_Document();
|
|
6968
|
+
var Schema = require_Schema();
|
|
6969
|
+
var errors = require_errors();
|
|
6970
|
+
var Alias = require_Alias();
|
|
6971
|
+
var identity = require_identity();
|
|
6972
|
+
var Pair = require_Pair();
|
|
6973
|
+
var Scalar = require_Scalar();
|
|
6974
|
+
var YAMLMap = require_YAMLMap();
|
|
6975
|
+
var YAMLSeq = require_YAMLSeq();
|
|
6976
|
+
var cst = require_cst();
|
|
6977
|
+
var lexer = require_lexer();
|
|
6978
|
+
var lineCounter = require_line_counter();
|
|
6979
|
+
var parser = require_parser();
|
|
6980
|
+
var publicApi = require_public_api();
|
|
6981
|
+
var visit = require_visit();
|
|
6982
|
+
exports.Composer = composer.Composer;
|
|
6983
|
+
exports.Document = Document.Document;
|
|
6984
|
+
exports.Schema = Schema.Schema;
|
|
6985
|
+
exports.YAMLError = errors.YAMLError;
|
|
6986
|
+
exports.YAMLParseError = errors.YAMLParseError;
|
|
6987
|
+
exports.YAMLWarning = errors.YAMLWarning;
|
|
6988
|
+
exports.Alias = Alias.Alias;
|
|
6989
|
+
exports.isAlias = identity.isAlias;
|
|
6990
|
+
exports.isCollection = identity.isCollection;
|
|
6991
|
+
exports.isDocument = identity.isDocument;
|
|
6992
|
+
exports.isMap = identity.isMap;
|
|
6993
|
+
exports.isNode = identity.isNode;
|
|
6994
|
+
exports.isPair = identity.isPair;
|
|
6995
|
+
exports.isScalar = identity.isScalar;
|
|
6996
|
+
exports.isSeq = identity.isSeq;
|
|
6997
|
+
exports.Pair = Pair.Pair;
|
|
6998
|
+
exports.Scalar = Scalar.Scalar;
|
|
6999
|
+
exports.YAMLMap = YAMLMap.YAMLMap;
|
|
7000
|
+
exports.YAMLSeq = YAMLSeq.YAMLSeq;
|
|
7001
|
+
exports.CST = cst;
|
|
7002
|
+
exports.Lexer = lexer.Lexer;
|
|
7003
|
+
exports.LineCounter = lineCounter.LineCounter;
|
|
7004
|
+
exports.Parser = parser.Parser;
|
|
7005
|
+
exports.parse = publicApi.parse;
|
|
7006
|
+
exports.parseAllDocuments = publicApi.parseAllDocuments;
|
|
7007
|
+
exports.parseDocument = publicApi.parseDocument;
|
|
7008
|
+
exports.stringify = publicApi.stringify;
|
|
7009
|
+
exports.visit = visit.visit;
|
|
7010
|
+
exports.visitAsync = visit.visitAsync;
|
|
7011
|
+
});
|
|
7012
|
+
|
|
7013
|
+
// src/util/atomic.ts
|
|
7014
|
+
import { randomBytes } from "node:crypto";
|
|
7015
|
+
import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
|
|
7016
|
+
function atomicWriteFileSync(destPath, contents, mode = 384) {
|
|
7017
|
+
const tmp = `${destPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
7018
|
+
const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
|
|
7019
|
+
let fd = null;
|
|
7020
|
+
try {
|
|
7021
|
+
fd = openSync(tmp, TMP_OPEN_FLAGS, mode);
|
|
7022
|
+
writeSync(fd, buf, 0, buf.length, 0);
|
|
7023
|
+
fsyncSync(fd);
|
|
7024
|
+
closeSync(fd);
|
|
7025
|
+
fd = null;
|
|
7026
|
+
renameSync(tmp, destPath);
|
|
7027
|
+
} catch (err) {
|
|
7028
|
+
if (fd !== null) {
|
|
7029
|
+
try {
|
|
7030
|
+
closeSync(fd);
|
|
7031
|
+
} catch {}
|
|
7032
|
+
}
|
|
7033
|
+
try {
|
|
7034
|
+
rmSync(tmp, { force: true });
|
|
7035
|
+
} catch {}
|
|
7036
|
+
throw err;
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
function atomicWriteJsonSync(destPath, value, mode = 384) {
|
|
7040
|
+
atomicWriteFileSync(destPath, JSON.stringify(value, null, 2) + `
|
|
7041
|
+
`, mode);
|
|
7042
|
+
}
|
|
7043
|
+
var TMP_OPEN_FLAGS;
|
|
7044
|
+
var init_atomic = __esm(() => {
|
|
7045
|
+
TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
|
|
7046
|
+
});
|
|
7047
|
+
|
|
6934
7048
|
// src/auth/broker/index.ts
|
|
6935
7049
|
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
6936
7050
|
|
|
6937
7051
|
// src/config/loader.ts
|
|
7052
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
6938
7053
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "node:fs";
|
|
6939
7054
|
import { homedir } from "node:os";
|
|
6940
7055
|
import { resolve as resolve3 } from "node:path";
|
|
6941
7056
|
|
|
6942
|
-
// node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
|
|
6943
|
-
var composer = require_composer();
|
|
6944
|
-
var Document = require_Document();
|
|
6945
|
-
var Schema = require_Schema();
|
|
6946
|
-
var errors = require_errors();
|
|
6947
|
-
var Alias = require_Alias();
|
|
6948
|
-
var identity = require_identity();
|
|
6949
|
-
var Pair = require_Pair();
|
|
6950
|
-
var Scalar = require_Scalar();
|
|
6951
|
-
var YAMLMap = require_YAMLMap();
|
|
6952
|
-
var YAMLSeq = require_YAMLSeq();
|
|
6953
|
-
var cst = require_cst();
|
|
6954
|
-
var lexer = require_lexer();
|
|
6955
|
-
var lineCounter = require_line_counter();
|
|
6956
|
-
var parser = require_parser();
|
|
6957
|
-
var publicApi = require_public_api();
|
|
6958
|
-
var visit = require_visit();
|
|
6959
|
-
var $Composer = composer.Composer;
|
|
6960
|
-
var $Document = Document.Document;
|
|
6961
|
-
var $Schema = Schema.Schema;
|
|
6962
|
-
var $YAMLError = errors.YAMLError;
|
|
6963
|
-
var $YAMLParseError = errors.YAMLParseError;
|
|
6964
|
-
var $YAMLWarning = errors.YAMLWarning;
|
|
6965
|
-
var $Alias = Alias.Alias;
|
|
6966
|
-
var $isAlias = identity.isAlias;
|
|
6967
|
-
var $isCollection = identity.isCollection;
|
|
6968
|
-
var $isDocument = identity.isDocument;
|
|
6969
|
-
var $isMap = identity.isMap;
|
|
6970
|
-
var $isNode = identity.isNode;
|
|
6971
|
-
var $isPair = identity.isPair;
|
|
6972
|
-
var $isScalar = identity.isScalar;
|
|
6973
|
-
var $isSeq = identity.isSeq;
|
|
6974
|
-
var $Pair = Pair.Pair;
|
|
6975
|
-
var $Scalar = Scalar.Scalar;
|
|
6976
|
-
var $YAMLMap = YAMLMap.YAMLMap;
|
|
6977
|
-
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
6978
|
-
var $Lexer = lexer.Lexer;
|
|
6979
|
-
var $LineCounter = lineCounter.LineCounter;
|
|
6980
|
-
var $Parser = parser.Parser;
|
|
6981
|
-
var $parse = publicApi.parse;
|
|
6982
|
-
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
6983
|
-
var $parseDocument = publicApi.parseDocument;
|
|
6984
|
-
var $stringify = publicApi.stringify;
|
|
6985
|
-
var $visit = visit.visit;
|
|
6986
|
-
var $visitAsync = visit.visitAsync;
|
|
6987
|
-
|
|
6988
7057
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
6989
7058
|
var exports_external = {};
|
|
6990
7059
|
__export(exports_external, {
|
|
@@ -11070,8 +11139,35 @@ var AgentMemorySchema = exports_external.object({
|
|
|
11070
11139
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
11071
11140
|
file: exports_external.boolean().default(true).describe("Maintain a curated workspace MEMORY.md file (seeded once, " + "auto-loaded every turn). Set false for hindsight-only memory: " + "the file is not seeded or re-created, so once migrated into " + "Hindsight and deleted it stays gone. Recall + directives carry " + "the memory instead. Cascade: override (per-agent wins over default)."),
|
|
11072
11141
|
isolation: exports_external.enum(["default", "strict"]).default("default").describe("strict = never shared cross-agent, default = eligible for reflect"),
|
|
11073
|
-
bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize results"),
|
|
11074
|
-
|
|
11142
|
+
bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
|
|
11143
|
+
reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
|
|
11144
|
+
retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
|
|
11145
|
+
mental_models: exports_external.array(exports_external.object({
|
|
11146
|
+
name: exports_external.string().min(1).describe("Stable model name (identity key for idempotent ensure). Two " + "declarations with the same name in one agent are rejected."),
|
|
11147
|
+
source_query: exports_external.string().min(1).max(2000).describe("The reflection query the model answers, semantically " + "refreshed from the bank's content. Capped at 2000 chars — a " + "standing reflection query, not a document; the ceiling also " + "bounds what an agent-proposed model can smuggle past the " + "operator approval card."),
|
|
11148
|
+
refresh_after_consolidation: exports_external.boolean().optional().describe("Refresh this model after each consolidation. Defaults OFF — " + "refresh adds bounded background model-spend + timeout risk " + "(RFC Phase 5), so it is opt-in per model."),
|
|
11149
|
+
max_tokens: exports_external.number().int().positive().max(8192).optional().describe("Cap on the synthesized model's token size. Upper-bounded at " + "8192 — a mental model is a standing summary, not a corpus; the " + "ceiling also caps what an agent-proposed model can request.")
|
|
11150
|
+
})).superRefine((models, ctx) => {
|
|
11151
|
+
const seen = new Set;
|
|
11152
|
+
for (let i = 0;i < models.length; i++) {
|
|
11153
|
+
const key = models[i].name;
|
|
11154
|
+
if (seen.has(key)) {
|
|
11155
|
+
ctx.addIssue({
|
|
11156
|
+
code: exports_external.ZodIssueCode.custom,
|
|
11157
|
+
path: [i, "name"],
|
|
11158
|
+
message: `duplicate mental_models name "${key}" — model names must be ` + `unique within an agent (they are the idempotent-ensure key)`
|
|
11159
|
+
});
|
|
11160
|
+
}
|
|
11161
|
+
seen.add(key);
|
|
11162
|
+
}
|
|
11163
|
+
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry — e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced — 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded — each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
11164
|
+
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
11165
|
+
disposition: exports_external.object({
|
|
11166
|
+
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
11167
|
+
literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
|
|
11168
|
+
empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
|
|
11169
|
+
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
|
|
11170
|
+
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
|
|
11075
11171
|
recall: exports_external.object({
|
|
11076
11172
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
11077
11173
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
@@ -11336,6 +11432,7 @@ var profileFields = {
|
|
|
11336
11432
|
auto_recall: exports_external.boolean().optional(),
|
|
11337
11433
|
file: exports_external.boolean().optional(),
|
|
11338
11434
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
11435
|
+
directive_capture_nudge: exports_external.boolean().optional(),
|
|
11339
11436
|
recall: exports_external.object({
|
|
11340
11437
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
11341
11438
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
@@ -11591,7 +11688,7 @@ var SwitchroomConfigSchema = exports_external.object({
|
|
|
11591
11688
|
name: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, {
|
|
11592
11689
|
message: "Consumer name must be a path-safe slug (letters, digits, underscore, hyphen)"
|
|
11593
11690
|
}).describe("Socket-path identity; binds at /run/switchroom/auth-broker/<name>/sock"),
|
|
11594
|
-
account: exports_external.string().min(1).describe("
|
|
11691
|
+
account: exports_external.string().min(1).optional().describe("Optional pinned account label for this consumer. When set, " + "`get-credentials` serves this account (with automatic failover " + "while it is quota-exhausted) and `mark-exhausted` from this " + "consumer attributes to it — use a pin for quota isolation. " + "When omitted, the consumer follows the fleet `auth.active` " + "exactly like an agent: same account swaps, same failover."),
|
|
11595
11692
|
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)."),
|
|
11596
11693
|
mirror_dir: exports_external.string().optional().describe("Optional host-side directory path. When set, the broker actively " + "writes the consumer's effective-account `.credentials.json` mirror " + "here — in addition to serving creds on demand via `get-credentials`. " + "Use this to eliminate the pull-latency gap: without a mirror the " + "consumer only gets failover creds at its next scheduled re-fetch " + "(up to 30 min). With a mirror the broker pushes failover creds " + "immediately when it detects exhaustion (consumer-quota-sensor tick, " + "or a mark-exhausted RPC on the pinned account). The directory must " + "be accessible to the broker container (bind-mounted from the host) " + "and to the consumer container; the broker writes " + "`<mirror_dir>/.credentials.json` atomically. Chown is attempted to " + "`uid` (default 0) — swallowed when CAP_CHOWN is absent.")
|
|
11597
11694
|
})).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."),
|
|
@@ -11687,6 +11784,7 @@ function resolveDualPath(pathStr) {
|
|
|
11687
11784
|
}
|
|
11688
11785
|
|
|
11689
11786
|
// src/config/overlay-loader.ts
|
|
11787
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
11690
11788
|
import { existsSync as existsSync2, readFileSync, readdirSync, statSync } from "node:fs";
|
|
11691
11789
|
import { basename, resolve as resolve2 } from "node:path";
|
|
11692
11790
|
|
|
@@ -11762,7 +11860,7 @@ function applyAgentOverlays(config) {
|
|
|
11762
11860
|
for (const file of files) {
|
|
11763
11861
|
try {
|
|
11764
11862
|
const raw = readFileSync(file, "utf-8");
|
|
11765
|
-
const parsed =
|
|
11863
|
+
const parsed = import_yaml.parse(raw);
|
|
11766
11864
|
const doc = OverlayDocSchema.parse(parsed);
|
|
11767
11865
|
const title = deriveOverlayTitle(raw, basename(file));
|
|
11768
11866
|
for (const entry of doc.schedule ?? []) {
|
|
@@ -11803,7 +11901,7 @@ function applyAgentOverlays(config) {
|
|
|
11803
11901
|
for (const file of skillFiles) {
|
|
11804
11902
|
try {
|
|
11805
11903
|
const raw = readFileSync(file, "utf-8");
|
|
11806
|
-
const parsed =
|
|
11904
|
+
const parsed = import_yaml.parse(raw);
|
|
11807
11905
|
const doc = OverlayDocSchema.parse(parsed);
|
|
11808
11906
|
for (const skillName of doc.skills ?? []) {
|
|
11809
11907
|
if (seen.has(skillName))
|
|
@@ -11979,6 +12077,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
11979
12077
|
continue;
|
|
11980
12078
|
if (k === "recall" && base.recall && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
11981
12079
|
combined[k] = { ...base.recall, ...v };
|
|
12080
|
+
} else if (k === "disposition" && base.disposition && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
12081
|
+
combined[k] = {
|
|
12082
|
+
...base.disposition,
|
|
12083
|
+
...v
|
|
12084
|
+
};
|
|
11982
12085
|
} else {
|
|
11983
12086
|
combined[k] = v;
|
|
11984
12087
|
}
|
|
@@ -12320,7 +12423,7 @@ function loadConfig(configPath) {
|
|
|
12320
12423
|
}
|
|
12321
12424
|
let parsed;
|
|
12322
12425
|
try {
|
|
12323
|
-
parsed =
|
|
12426
|
+
parsed = import_yaml2.parse(raw);
|
|
12324
12427
|
} catch (err) {
|
|
12325
12428
|
throw new ConfigError(`Invalid YAML in ${filePath}`, [
|
|
12326
12429
|
` ${err.message}`
|
|
@@ -12440,8 +12543,31 @@ function isReservedAgentName(name) {
|
|
|
12440
12543
|
return RESERVED_AGENT_NAMES.has(name);
|
|
12441
12544
|
}
|
|
12442
12545
|
|
|
12546
|
+
// src/setup/hindsight.ts
|
|
12547
|
+
var HINDSIGHT_DEFAULT_API_PORT = 18888;
|
|
12548
|
+
var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
|
|
12549
|
+
var HINDSIGHT_DEFAULT_API_BASE_URL = HINDSIGHT_DEFAULT_MCP_URL.replace(/\/mcp\/?$/, "");
|
|
12550
|
+
var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
12551
|
+
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
12552
|
+
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
12553
|
+
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
12554
|
+
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
12555
|
+
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
12556
|
+
|
|
12443
12557
|
// src/memory/hindsight.ts
|
|
12444
12558
|
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.";
|
|
12559
|
+
var PROFILE_MEMORY_DEFAULTS = {
|
|
12560
|
+
"health-coach": {
|
|
12561
|
+
disposition: { skepticism: 2, literalism: 2, empathy: 5 },
|
|
12562
|
+
observations_mission: "Synthesise the person's wellbeing patterns, motivations, and emotional " + "context — how habits, setbacks, and encouragement connect over time."
|
|
12563
|
+
},
|
|
12564
|
+
"executive-assistant": {
|
|
12565
|
+
disposition: { skepticism: 4, literalism: 4, empathy: 3 }
|
|
12566
|
+
},
|
|
12567
|
+
coding: {
|
|
12568
|
+
disposition: { skepticism: 4, literalism: 5, empathy: 2 }
|
|
12569
|
+
}
|
|
12570
|
+
};
|
|
12445
12571
|
|
|
12446
12572
|
// src/agents/reconcile-default-skills.ts
|
|
12447
12573
|
var warnedMissingPool = new Set;
|
|
@@ -12657,37 +12783,8 @@ function resolveConsumerProbeIntervalMs(env) {
|
|
|
12657
12783
|
return DEFAULT_CONSUMER_PROBE_INTERVAL_MS;
|
|
12658
12784
|
}
|
|
12659
12785
|
|
|
12660
|
-
// src/
|
|
12661
|
-
|
|
12662
|
-
import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
|
|
12663
|
-
var TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
|
|
12664
|
-
function atomicWriteFileSync(destPath, contents, mode = 384) {
|
|
12665
|
-
const tmp = `${destPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
12666
|
-
const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
|
|
12667
|
-
let fd = null;
|
|
12668
|
-
try {
|
|
12669
|
-
fd = openSync(tmp, TMP_OPEN_FLAGS, mode);
|
|
12670
|
-
writeSync(fd, buf, 0, buf.length, 0);
|
|
12671
|
-
fsyncSync(fd);
|
|
12672
|
-
closeSync(fd);
|
|
12673
|
-
fd = null;
|
|
12674
|
-
renameSync(tmp, destPath);
|
|
12675
|
-
} catch (err) {
|
|
12676
|
-
if (fd !== null) {
|
|
12677
|
-
try {
|
|
12678
|
-
closeSync(fd);
|
|
12679
|
-
} catch {}
|
|
12680
|
-
}
|
|
12681
|
-
try {
|
|
12682
|
-
rmSync(tmp, { force: true });
|
|
12683
|
-
} catch {}
|
|
12684
|
-
throw err;
|
|
12685
|
-
}
|
|
12686
|
-
}
|
|
12687
|
-
function atomicWriteJsonSync(destPath, value, mode = 384) {
|
|
12688
|
-
atomicWriteFileSync(destPath, JSON.stringify(value, null, 2) + `
|
|
12689
|
-
`, mode);
|
|
12690
|
-
}
|
|
12786
|
+
// src/auth/broker/server.ts
|
|
12787
|
+
init_atomic();
|
|
12691
12788
|
|
|
12692
12789
|
// src/auth/account-store.ts
|
|
12693
12790
|
import {
|
|
@@ -13341,6 +13438,7 @@ function resolveMicrosoftClientId(configClientId) {
|
|
|
13341
13438
|
}
|
|
13342
13439
|
|
|
13343
13440
|
// src/auth/broker/google-storage.ts
|
|
13441
|
+
init_atomic();
|
|
13344
13442
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
|
|
13345
13443
|
import { dirname, join as join2, resolve as resolve5 } from "node:path";
|
|
13346
13444
|
function normalizeGoogleAccountForStorage(account) {
|
|
@@ -13409,6 +13507,7 @@ function listGoogleAccounts(stateDir) {
|
|
|
13409
13507
|
}
|
|
13410
13508
|
|
|
13411
13509
|
// src/auth/broker/microsoft-storage.ts
|
|
13510
|
+
init_atomic();
|
|
13412
13511
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, rmSync as rmSync4, statSync as statSync4 } from "node:fs";
|
|
13413
13512
|
import { dirname as dirname2, join as join3, resolve as resolve6 } from "node:path";
|
|
13414
13513
|
function normalizeMicrosoftAccountForStorage(account) {
|
|
@@ -13527,19 +13626,19 @@ function classify(socketPath, config) {
|
|
|
13527
13626
|
return null;
|
|
13528
13627
|
}
|
|
13529
13628
|
function validateConsumerNames(config) {
|
|
13530
|
-
const
|
|
13629
|
+
const errors2 = [];
|
|
13531
13630
|
for (const c of config.consumers) {
|
|
13532
13631
|
if (RESERVED_NAMES.has(c)) {
|
|
13533
|
-
|
|
13632
|
+
errors2.push(`consumer name '${c}' is reserved`);
|
|
13534
13633
|
}
|
|
13535
13634
|
if (config.agents.includes(c)) {
|
|
13536
|
-
|
|
13635
|
+
errors2.push(`consumer name '${c}' collides with an agent name`);
|
|
13537
13636
|
}
|
|
13538
13637
|
if (config.adminAgents.includes(c)) {
|
|
13539
|
-
|
|
13638
|
+
errors2.push(`consumer name '${c}' is an admin agent (consumers cannot be admins)`);
|
|
13540
13639
|
}
|
|
13541
13640
|
}
|
|
13542
|
-
return
|
|
13641
|
+
return errors2;
|
|
13543
13642
|
}
|
|
13544
13643
|
|
|
13545
13644
|
// src/auth/broker/protocol.ts
|
|
@@ -13910,10 +14009,12 @@ class AuthBroker {
|
|
|
13910
14009
|
refreshInFlight = new Set;
|
|
13911
14010
|
consumerLastSeen = {};
|
|
13912
14011
|
capChownWarned = false;
|
|
14012
|
+
yamlActive;
|
|
13913
14013
|
closed = false;
|
|
13914
14014
|
constructor(config, opts = {}) {
|
|
13915
14015
|
this.opts = opts;
|
|
13916
14016
|
this.config = config;
|
|
14017
|
+
this.yamlActive = config.auth?.active;
|
|
13917
14018
|
this.home = opts.home;
|
|
13918
14019
|
this.now = opts.now ?? nowMs;
|
|
13919
14020
|
this.operatorUid = opts.operatorUid;
|
|
@@ -14040,6 +14141,8 @@ class AuthBroker {
|
|
|
14040
14141
|
this.assertConfigConsistent(config);
|
|
14041
14142
|
const prev = this.config;
|
|
14042
14143
|
this.config = config;
|
|
14144
|
+
this.yamlActive = config.auth?.active;
|
|
14145
|
+
this.applyActiveOverride();
|
|
14043
14146
|
const wanted = new Set;
|
|
14044
14147
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
14045
14148
|
wanted.add(this.agentSocketPath(name));
|
|
@@ -14115,7 +14218,7 @@ class AuthBroker {
|
|
|
14115
14218
|
const sockPath = this.operatorSocketPath();
|
|
14116
14219
|
await this.bindListener(sockPath, operatorUid, 384, { kind: "operator" });
|
|
14117
14220
|
}
|
|
14118
|
-
async bindListener(sockPath, targetUid, sockMode,
|
|
14221
|
+
async bindListener(sockPath, targetUid, sockMode, identity) {
|
|
14119
14222
|
const dir = dirname3(sockPath);
|
|
14120
14223
|
if (existsSync7(dir)) {
|
|
14121
14224
|
try {
|
|
@@ -14134,7 +14237,7 @@ class AuthBroker {
|
|
|
14134
14237
|
}
|
|
14135
14238
|
await new Promise((resolveP, rejectP) => {
|
|
14136
14239
|
const server = net.createServer((sock) => {
|
|
14137
|
-
this.handleConnection(sock, sockPath,
|
|
14240
|
+
this.handleConnection(sock, sockPath, identity);
|
|
14138
14241
|
});
|
|
14139
14242
|
server.on("error", (err) => rejectP(err));
|
|
14140
14243
|
server.listen(sockPath, () => {
|
|
@@ -14147,7 +14250,7 @@ class AuthBroker {
|
|
|
14147
14250
|
try {
|
|
14148
14251
|
chownSync2(dir, targetUid, targetUid);
|
|
14149
14252
|
} catch {}
|
|
14150
|
-
this.listeners.set(sockPath, { server, identity
|
|
14253
|
+
this.listeners.set(sockPath, { server, identity, socketPath: sockPath });
|
|
14151
14254
|
resolveP();
|
|
14152
14255
|
});
|
|
14153
14256
|
});
|
|
@@ -14191,7 +14294,7 @@ class AuthBroker {
|
|
|
14191
14294
|
socket.write(encodeError(reqId, "INVALID_ARGS", err.message));
|
|
14192
14295
|
return;
|
|
14193
14296
|
}
|
|
14194
|
-
const
|
|
14297
|
+
const identity = classify(sockPath, configToShape(this.config)) ?? boundIdentity;
|
|
14195
14298
|
try {
|
|
14196
14299
|
switch (req.op) {
|
|
14197
14300
|
case "get-credentials": {
|
|
@@ -14201,22 +14304,22 @@ class AuthBroker {
|
|
|
14201
14304
|
break;
|
|
14202
14305
|
}
|
|
14203
14306
|
if (provider === "anthropic") {
|
|
14204
|
-
await this.opGetCredentials(socket, reqId,
|
|
14307
|
+
await this.opGetCredentials(socket, reqId, identity);
|
|
14205
14308
|
break;
|
|
14206
14309
|
}
|
|
14207
14310
|
if (provider === "google") {
|
|
14208
|
-
await this.opGoogleGetCredentials(socket, reqId,
|
|
14311
|
+
await this.opGoogleGetCredentials(socket, reqId, identity);
|
|
14209
14312
|
break;
|
|
14210
14313
|
}
|
|
14211
14314
|
if (provider === "microsoft") {
|
|
14212
|
-
await this.opMicrosoftGetCredentials(socket, reqId,
|
|
14315
|
+
await this.opMicrosoftGetCredentials(socket, reqId, identity);
|
|
14213
14316
|
break;
|
|
14214
14317
|
}
|
|
14215
14318
|
socket.write(encodeError(reqId, "INTERNAL", `unhandled provider '${provider}' in get-credentials dispatch`));
|
|
14216
14319
|
break;
|
|
14217
14320
|
}
|
|
14218
14321
|
case "list-state":
|
|
14219
|
-
await this.opListState(socket, reqId,
|
|
14322
|
+
await this.opListState(socket, reqId, identity);
|
|
14220
14323
|
break;
|
|
14221
14324
|
case "set-active": {
|
|
14222
14325
|
const provider = req.provider ?? "anthropic";
|
|
@@ -14224,11 +14327,11 @@ class AuthBroker {
|
|
|
14224
14327
|
socket.write(encodeError(reqId, "INVALID_ARGS", `set-active is Anthropic-only — Google's account-active model is per-agent via google_accounts.enabled_for[]`));
|
|
14225
14328
|
break;
|
|
14226
14329
|
}
|
|
14227
|
-
await this.opSetActive(socket, reqId,
|
|
14330
|
+
await this.opSetActive(socket, reqId, identity, req.account);
|
|
14228
14331
|
break;
|
|
14229
14332
|
}
|
|
14230
14333
|
case "mark-exhausted":
|
|
14231
|
-
await this.opMarkExhausted(socket, reqId,
|
|
14334
|
+
await this.opMarkExhausted(socket, reqId, identity, req.until);
|
|
14232
14335
|
break;
|
|
14233
14336
|
case "refresh-account": {
|
|
14234
14337
|
const provider = req.provider ?? "anthropic";
|
|
@@ -14237,7 +14340,7 @@ class AuthBroker {
|
|
|
14237
14340
|
break;
|
|
14238
14341
|
}
|
|
14239
14342
|
if (provider === "anthropic") {
|
|
14240
|
-
await this.opRefreshAccount(socket, reqId,
|
|
14343
|
+
await this.opRefreshAccount(socket, reqId, identity, req.account);
|
|
14241
14344
|
break;
|
|
14242
14345
|
}
|
|
14243
14346
|
socket.write(encodeError(reqId, "INVALID_ARGS", `refresh-account dispatch through provider '${provider}' storage path lands in Phase 3b.2c (vault-broker-mediated, not direct-to-disk like Anthropic)`));
|
|
@@ -14256,17 +14359,17 @@ class AuthBroker {
|
|
|
14256
14359
|
}
|
|
14257
14360
|
if (provider === "anthropic") {
|
|
14258
14361
|
const anthropicCreds = req.credentials;
|
|
14259
|
-
await this.opAddAccount(socket, reqId,
|
|
14362
|
+
await this.opAddAccount(socket, reqId, identity, req.label, anthropicCreds, req.replace ?? false);
|
|
14260
14363
|
break;
|
|
14261
14364
|
}
|
|
14262
14365
|
if (provider === "google") {
|
|
14263
14366
|
const googleCreds = req.credentials;
|
|
14264
|
-
await this.opGoogleAddAccount(socket, reqId,
|
|
14367
|
+
await this.opGoogleAddAccount(socket, reqId, identity, req.label, googleCreds, req.replace ?? false);
|
|
14265
14368
|
break;
|
|
14266
14369
|
}
|
|
14267
14370
|
if (provider === "microsoft") {
|
|
14268
14371
|
const microsoftCreds = req.credentials;
|
|
14269
|
-
await this.opMicrosoftAddAccount(socket, reqId,
|
|
14372
|
+
await this.opMicrosoftAddAccount(socket, reqId, identity, req.label, microsoftCreds, req.replace ?? false);
|
|
14270
14373
|
break;
|
|
14271
14374
|
}
|
|
14272
14375
|
socket.write(encodeError(reqId, "INTERNAL", `unhandled provider '${provider}' in add-account dispatch`));
|
|
@@ -14279,69 +14382,71 @@ class AuthBroker {
|
|
|
14279
14382
|
break;
|
|
14280
14383
|
}
|
|
14281
14384
|
if (provider === "anthropic") {
|
|
14282
|
-
await this.opRmAccount(socket, reqId,
|
|
14385
|
+
await this.opRmAccount(socket, reqId, identity, req.label);
|
|
14283
14386
|
break;
|
|
14284
14387
|
}
|
|
14285
14388
|
if (provider === "google") {
|
|
14286
|
-
await this.opGoogleRmAccount(socket, reqId,
|
|
14389
|
+
await this.opGoogleRmAccount(socket, reqId, identity, req.label);
|
|
14287
14390
|
break;
|
|
14288
14391
|
}
|
|
14289
14392
|
if (provider === "microsoft") {
|
|
14290
|
-
await this.opMicrosoftRmAccount(socket, reqId,
|
|
14393
|
+
await this.opMicrosoftRmAccount(socket, reqId, identity, req.label);
|
|
14291
14394
|
break;
|
|
14292
14395
|
}
|
|
14293
14396
|
socket.write(encodeError(reqId, "INTERNAL", `unhandled provider '${provider}' in rm-account dispatch`));
|
|
14294
14397
|
break;
|
|
14295
14398
|
}
|
|
14296
14399
|
case "set-override":
|
|
14297
|
-
await this.opSetOverride(socket, reqId,
|
|
14400
|
+
await this.opSetOverride(socket, reqId, identity, req.agent, req.account);
|
|
14298
14401
|
break;
|
|
14299
14402
|
case "list-google-accounts":
|
|
14300
|
-
await this.opListGoogleAccounts(socket, reqId,
|
|
14403
|
+
await this.opListGoogleAccounts(socket, reqId, identity);
|
|
14301
14404
|
break;
|
|
14302
14405
|
case "list-microsoft-accounts":
|
|
14303
|
-
await this.opListMicrosoftAccounts(socket, reqId,
|
|
14406
|
+
await this.opListMicrosoftAccounts(socket, reqId, identity);
|
|
14304
14407
|
break;
|
|
14305
14408
|
case "probe-quota":
|
|
14306
|
-
await this.opProbeQuota(socket, reqId,
|
|
14409
|
+
await this.opProbeQuota(socket, reqId, identity, req.accounts, req.timeoutMs, req.forceLive);
|
|
14307
14410
|
break;
|
|
14308
14411
|
case "claim-notification":
|
|
14309
|
-
this.opClaimNotification(socket, reqId,
|
|
14412
|
+
this.opClaimNotification(socket, reqId, identity, req.key, req.windowMs);
|
|
14310
14413
|
break;
|
|
14311
14414
|
}
|
|
14312
14415
|
} catch (err) {
|
|
14313
14416
|
socket.write(encodeError(reqId, "INTERNAL", err.message));
|
|
14314
14417
|
}
|
|
14315
14418
|
}
|
|
14316
|
-
isAdmin(
|
|
14317
|
-
if (
|
|
14419
|
+
isAdmin(identity) {
|
|
14420
|
+
if (identity.kind === "operator")
|
|
14318
14421
|
return true;
|
|
14319
|
-
if (
|
|
14320
|
-
return
|
|
14422
|
+
if (identity.kind === "agent")
|
|
14423
|
+
return identity.admin;
|
|
14321
14424
|
return false;
|
|
14322
14425
|
}
|
|
14323
14426
|
respondForbidden(socket, id, why) {
|
|
14324
14427
|
socket.write(encodeError(id, "FORBIDDEN", why));
|
|
14325
14428
|
}
|
|
14326
|
-
callerAccount(
|
|
14429
|
+
callerAccount(identity) {
|
|
14327
14430
|
const auth = this.config.auth;
|
|
14328
14431
|
if (!auth)
|
|
14329
14432
|
return null;
|
|
14330
|
-
if (
|
|
14433
|
+
if (identity.kind === "operator")
|
|
14331
14434
|
return auth.active ?? null;
|
|
14332
|
-
if (
|
|
14333
|
-
const c = (auth.consumers ?? []).find((x) => x.name ===
|
|
14334
|
-
|
|
14435
|
+
if (identity.kind === "consumer") {
|
|
14436
|
+
const c = (auth.consumers ?? []).find((x) => x.name === identity.name);
|
|
14437
|
+
if (!c)
|
|
14438
|
+
return null;
|
|
14439
|
+
return c.account ?? auth.active ?? null;
|
|
14335
14440
|
}
|
|
14336
|
-
const agent = (this.config.agents ?? {})[
|
|
14441
|
+
const agent = (this.config.agents ?? {})[identity.name];
|
|
14337
14442
|
const override = agent?.auth?.override;
|
|
14338
14443
|
if (override)
|
|
14339
14444
|
return override;
|
|
14340
14445
|
return auth.active ?? null;
|
|
14341
14446
|
}
|
|
14342
|
-
servingAccount(
|
|
14343
|
-
const account = this.callerAccount(
|
|
14344
|
-
if (
|
|
14447
|
+
servingAccount(identity) {
|
|
14448
|
+
const account = this.callerAccount(identity);
|
|
14449
|
+
if (identity.kind === "operator")
|
|
14345
14450
|
return account;
|
|
14346
14451
|
return this.accountWithFailover(account);
|
|
14347
14452
|
}
|
|
@@ -14433,24 +14538,24 @@ class AuthBroker {
|
|
|
14433
14538
|
}
|
|
14434
14539
|
return account;
|
|
14435
14540
|
}
|
|
14436
|
-
async opGetCredentials(socket, id,
|
|
14437
|
-
const account = this.servingAccount(
|
|
14541
|
+
async opGetCredentials(socket, id, identity) {
|
|
14542
|
+
const account = this.servingAccount(identity);
|
|
14438
14543
|
if (!account) {
|
|
14439
|
-
this.audit({ op: "get-credentials", identity
|
|
14544
|
+
this.audit({ op: "get-credentials", identity, accountKind: "claude", ok: false, error: "no-active-account" });
|
|
14440
14545
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", "no active account configured"));
|
|
14441
14546
|
return;
|
|
14442
14547
|
}
|
|
14443
14548
|
const creds = readAccountCredentials(account, this.home);
|
|
14444
14549
|
if (!creds) {
|
|
14445
|
-
this.audit({ op: "get-credentials", identity
|
|
14550
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "claude", ok: false, error: "missing-credentials" });
|
|
14446
14551
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no credentials for account '${account}'`));
|
|
14447
14552
|
return;
|
|
14448
14553
|
}
|
|
14449
14554
|
const expiresAt = creds.claudeAiOauth?.expiresAt;
|
|
14450
|
-
this.audit({ op: "get-credentials", identity
|
|
14555
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "claude", ok: true });
|
|
14451
14556
|
socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
|
|
14452
14557
|
}
|
|
14453
|
-
async opListState(socket, id,
|
|
14558
|
+
async opListState(socket, id, identity) {
|
|
14454
14559
|
const auth = this.config.auth ?? {};
|
|
14455
14560
|
const accounts = listAccounts(this.home).map((label) => {
|
|
14456
14561
|
const creds = readAccountCredentials(label, this.home);
|
|
@@ -14475,11 +14580,11 @@ class AuthBroker {
|
|
|
14475
14580
|
});
|
|
14476
14581
|
const consumers = (auth.consumers ?? []).map((c) => ({
|
|
14477
14582
|
name: c.name,
|
|
14478
|
-
account: c.account,
|
|
14583
|
+
account: c.account ?? auth.active ?? "",
|
|
14479
14584
|
last_seen_at: this.consumerLastSeen[c.name] ?? null
|
|
14480
14585
|
}));
|
|
14481
|
-
const active_overage_serving = this.isActiveOverageServing(this.callerAccount(
|
|
14482
|
-
this.audit({ op: "list-state", identity
|
|
14586
|
+
const active_overage_serving = this.isActiveOverageServing(this.callerAccount(identity));
|
|
14587
|
+
this.audit({ op: "list-state", identity, ok: true });
|
|
14483
14588
|
socket.write(encodeSuccess(id, {
|
|
14484
14589
|
active: auth.active ?? "",
|
|
14485
14590
|
fallback_order: auth.fallback_order ?? [],
|
|
@@ -14489,7 +14594,7 @@ class AuthBroker {
|
|
|
14489
14594
|
active_overage_serving
|
|
14490
14595
|
}));
|
|
14491
14596
|
}
|
|
14492
|
-
async opListGoogleAccounts(socket, id,
|
|
14597
|
+
async opListGoogleAccounts(socket, id, identity) {
|
|
14493
14598
|
const accounts = listGoogleAccounts(this.stateDir).map((account) => {
|
|
14494
14599
|
const creds = readGoogleAccountCredentials(this.stateDir, account);
|
|
14495
14600
|
if (!creds)
|
|
@@ -14501,10 +14606,10 @@ class AuthBroker {
|
|
|
14501
14606
|
clientId: creds.googleOauth.clientId
|
|
14502
14607
|
};
|
|
14503
14608
|
}).filter((entry) => entry !== null).sort((a, b) => a.account.localeCompare(b.account));
|
|
14504
|
-
this.audit({ op: "list-google-accounts", identity
|
|
14609
|
+
this.audit({ op: "list-google-accounts", identity, accountKind: "google", ok: true });
|
|
14505
14610
|
socket.write(encodeSuccess(id, { accounts }));
|
|
14506
14611
|
}
|
|
14507
|
-
async opProbeQuota(socket, id,
|
|
14612
|
+
async opProbeQuota(socket, id, identity, accounts, timeoutMs, forceLive) {
|
|
14508
14613
|
const ttlMs = forceLive ? 0 : quotaProbeTtlMs();
|
|
14509
14614
|
const results = await Promise.all(accounts.map(async (label) => {
|
|
14510
14615
|
const cached = this.lastQuotaCache[label];
|
|
@@ -14521,13 +14626,13 @@ class AuthBroker {
|
|
|
14521
14626
|
ok: false,
|
|
14522
14627
|
reason: "no credentials for account in broker store"
|
|
14523
14628
|
};
|
|
14524
|
-
this.audit({ op: "probe-quota", identity
|
|
14629
|
+
this.audit({ op: "probe-quota", identity, account: label, accountKind: "claude", ok: false, error: "missing-credentials" });
|
|
14525
14630
|
return { label, result: result2 };
|
|
14526
14631
|
}
|
|
14527
14632
|
const result = await this.probeQuotaSingleFlight(label, token, timeoutMs);
|
|
14528
14633
|
this.audit({
|
|
14529
14634
|
op: "probe-quota",
|
|
14530
|
-
identity
|
|
14635
|
+
identity,
|
|
14531
14636
|
account: label,
|
|
14532
14637
|
accountKind: "claude",
|
|
14533
14638
|
ok: result.ok,
|
|
@@ -14594,10 +14699,20 @@ class AuthBroker {
|
|
|
14594
14699
|
continue;
|
|
14595
14700
|
}
|
|
14596
14701
|
this.cacheQuotaSnapshot(label, result);
|
|
14702
|
+
const active = this.config.auth?.active;
|
|
14703
|
+
if (label !== active)
|
|
14704
|
+
continue;
|
|
14705
|
+
const decision = quotaIndicatesExhaustion(result, this.isOverageAllowed(label));
|
|
14706
|
+
if (!decision.exhausted)
|
|
14707
|
+
continue;
|
|
14708
|
+
process.stdout.write(`auth-broker: fleet-quota-probe shows ACTIVE ${label} exhausted — proactive failover
|
|
14709
|
+
`);
|
|
14710
|
+
this.audit({ op: "mark-exhausted", identity: { kind: "operator" }, account: label, accountKind: "claude", ok: true });
|
|
14711
|
+
await this.markExhaustedAndRoll(label, decision.until ?? undefined, { kind: "operator" });
|
|
14597
14712
|
}
|
|
14598
14713
|
}
|
|
14599
14714
|
async consumerQuotaProbeTick() {
|
|
14600
|
-
const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account)));
|
|
14715
|
+
const accounts = Array.from(new Set((this.config.auth?.consumers ?? []).map((c) => c.account).filter((a) => typeof a === "string" && a.length > 0)));
|
|
14601
14716
|
for (const label of accounts) {
|
|
14602
14717
|
const creds = readAccountCredentials(label, this.home);
|
|
14603
14718
|
const token = creds?.claudeAiOauth?.accessToken;
|
|
@@ -14638,14 +14753,14 @@ class AuthBroker {
|
|
|
14638
14753
|
this.fanoutToAffectedConsumers(label);
|
|
14639
14754
|
}
|
|
14640
14755
|
}
|
|
14641
|
-
async opSetActive(socket, id,
|
|
14642
|
-
if (!this.isAdmin(
|
|
14643
|
-
this.audit({ op: "set-active", identity
|
|
14756
|
+
async opSetActive(socket, id, identity, account) {
|
|
14757
|
+
if (!this.isAdmin(identity)) {
|
|
14758
|
+
this.audit({ op: "set-active", identity, account, accountKind: "claude", ok: false, error: "FORBIDDEN" });
|
|
14644
14759
|
this.respondForbidden(socket, id, "set-active requires admin");
|
|
14645
14760
|
return;
|
|
14646
14761
|
}
|
|
14647
14762
|
if (!accountExists(account, this.home)) {
|
|
14648
|
-
this.audit({ op: "set-active", identity
|
|
14763
|
+
this.audit({ op: "set-active", identity, account, accountKind: "claude", ok: false, error: "ACCOUNT_NOT_FOUND" });
|
|
14649
14764
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `account '${account}' not found`));
|
|
14650
14765
|
return;
|
|
14651
14766
|
}
|
|
@@ -14654,18 +14769,24 @@ class AuthBroker {
|
|
|
14654
14769
|
auth: { ...this.config.auth ?? {}, active: account }
|
|
14655
14770
|
};
|
|
14656
14771
|
this.config = cfg;
|
|
14772
|
+
this.persistActiveOverride(account);
|
|
14657
14773
|
const fanned = this.fanoutToAffectedAgents(account);
|
|
14658
14774
|
this.fanoutAllConsumers();
|
|
14659
|
-
this.audit({ op: "set-active", identity
|
|
14775
|
+
this.audit({ op: "set-active", identity, account, accountKind: "claude", ok: true });
|
|
14660
14776
|
socket.write(encodeSuccess(id, { active: account, fanned }));
|
|
14661
14777
|
}
|
|
14662
|
-
async opMarkExhausted(socket, id,
|
|
14663
|
-
const account = this.callerAccount(
|
|
14778
|
+
async opMarkExhausted(socket, id, identity, until) {
|
|
14779
|
+
const account = this.callerAccount(identity);
|
|
14664
14780
|
if (!account) {
|
|
14665
|
-
this.audit({ op: "mark-exhausted", identity
|
|
14781
|
+
this.audit({ op: "mark-exhausted", identity, accountKind: "claude", ok: false, error: "no-active-account" });
|
|
14666
14782
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", "no active account configured"));
|
|
14667
14783
|
return;
|
|
14668
14784
|
}
|
|
14785
|
+
const { rolled, rolledTo } = await this.markExhaustedAndRoll(account, until, identity);
|
|
14786
|
+
this.audit({ op: "mark-exhausted", identity, account, accountKind: "claude", ok: true });
|
|
14787
|
+
socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
|
|
14788
|
+
}
|
|
14789
|
+
async markExhaustedAndRoll(account, until, identity) {
|
|
14669
14790
|
const now = this.now();
|
|
14670
14791
|
const exhaustedUntil = clampMarkExpiry({
|
|
14671
14792
|
proposedUntil: until ?? now + MARK_EXHAUSTED_DEFAULT_MS,
|
|
@@ -14678,10 +14799,28 @@ class AuthBroker {
|
|
|
14678
14799
|
const rolledTo = await this.nextHealthyAccountLive(account, this.config.auth?.fallback_order ?? []);
|
|
14679
14800
|
const rolled = this.fanoutFailoverTo(account, rolledTo);
|
|
14680
14801
|
this.fanoutToAffectedConsumers(account);
|
|
14681
|
-
|
|
14682
|
-
|
|
14802
|
+
if (rolledTo && this.config.auth?.active === account && this.exhaustionLiveCorroborated(account)) {
|
|
14803
|
+
this.config = {
|
|
14804
|
+
...this.config,
|
|
14805
|
+
auth: { ...this.config.auth ?? {}, active: rolledTo }
|
|
14806
|
+
};
|
|
14807
|
+
this.persistActiveOverride(rolledTo);
|
|
14808
|
+
this.fanoutAllConsumers();
|
|
14809
|
+
this.audit({ op: "auto-promote-active", identity, account: rolledTo, accountKind: "claude", ok: true });
|
|
14810
|
+
process.stdout.write(`auth-broker: auto-promoted auth.active ${account} → ${rolledTo} ` + `(${account} exhausted until ${new Date(exhaustedUntil).toISOString()}) — persisted
|
|
14811
|
+
`);
|
|
14812
|
+
}
|
|
14813
|
+
return { rolled, rolledTo };
|
|
14814
|
+
}
|
|
14815
|
+
exhaustionLiveCorroborated(account) {
|
|
14816
|
+
const snapshot = this.lastQuotaCache[account];
|
|
14817
|
+
if (!snapshot || !snapshotFresh(snapshot, this.now(), MARK_EXHAUSTED_DEFAULT_MS))
|
|
14818
|
+
return false;
|
|
14819
|
+
if (!snapshotWalled(snapshot))
|
|
14820
|
+
return false;
|
|
14821
|
+
return !overageLiftsWall(snapshot, this.isOverageAllowed(account));
|
|
14683
14822
|
}
|
|
14684
|
-
opClaimNotification(socket, id,
|
|
14823
|
+
opClaimNotification(socket, id, identity, key, windowMs) {
|
|
14685
14824
|
const now = this.now();
|
|
14686
14825
|
const prev = this.notificationClaims[key];
|
|
14687
14826
|
const granted = prev === undefined || now - prev >= windowMs;
|
|
@@ -14692,35 +14831,35 @@ class AuthBroker {
|
|
|
14692
14831
|
delete this.notificationClaims[k];
|
|
14693
14832
|
}
|
|
14694
14833
|
this.persistNotificationClaims();
|
|
14695
|
-
this.audit({ op: "claim-notification", identity
|
|
14834
|
+
this.audit({ op: "claim-notification", identity, account: key, ok: true });
|
|
14696
14835
|
}
|
|
14697
14836
|
socket.write(encodeSuccess(id, { granted }));
|
|
14698
14837
|
}
|
|
14699
|
-
async opRefreshAccount(socket, id,
|
|
14700
|
-
if (!this.isAdmin(
|
|
14701
|
-
this.audit({ op: "refresh-account", identity
|
|
14838
|
+
async opRefreshAccount(socket, id, identity, account) {
|
|
14839
|
+
if (!this.isAdmin(identity)) {
|
|
14840
|
+
this.audit({ op: "refresh-account", identity, account, accountKind: "claude", ok: false, error: "FORBIDDEN" });
|
|
14702
14841
|
this.respondForbidden(socket, id, "refresh-account requires admin");
|
|
14703
14842
|
return;
|
|
14704
14843
|
}
|
|
14705
14844
|
if (!accountExists(account, this.home)) {
|
|
14706
|
-
this.audit({ op: "refresh-account", identity
|
|
14845
|
+
this.audit({ op: "refresh-account", identity, account, accountKind: "claude", ok: false, error: "ACCOUNT_NOT_FOUND" });
|
|
14707
14846
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `account '${account}' not found`));
|
|
14708
14847
|
return;
|
|
14709
14848
|
}
|
|
14710
14849
|
const result = await this.refreshOneAccount(account, true);
|
|
14711
14850
|
if (result.kind === "failed") {
|
|
14712
|
-
this.audit({ op: "refresh-account", identity
|
|
14851
|
+
this.audit({ op: "refresh-account", identity, account, accountKind: "claude", ok: false, error: result.error });
|
|
14713
14852
|
socket.write(encodeError(id, "REFRESH_FAILED", result.error));
|
|
14714
14853
|
return;
|
|
14715
14854
|
}
|
|
14716
14855
|
const creds = readAccountCredentials(account, this.home);
|
|
14717
14856
|
const expiresAt = creds?.claudeAiOauth?.expiresAt;
|
|
14718
|
-
this.audit({ op: "refresh-account", identity
|
|
14857
|
+
this.audit({ op: "refresh-account", identity, account, accountKind: "claude", ok: true });
|
|
14719
14858
|
socket.write(encodeSuccess(id, { account, expiresAt }));
|
|
14720
14859
|
}
|
|
14721
|
-
async opAddAccount(socket, id,
|
|
14722
|
-
if (!this.isAdmin(
|
|
14723
|
-
this.audit({ op: "add-account", identity
|
|
14860
|
+
async opAddAccount(socket, id, identity, label, credentials, replace) {
|
|
14861
|
+
if (!this.isAdmin(identity)) {
|
|
14862
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "claude", ok: false, error: "FORBIDDEN" });
|
|
14724
14863
|
this.respondForbidden(socket, id, "add-account requires admin");
|
|
14725
14864
|
return;
|
|
14726
14865
|
}
|
|
@@ -14731,7 +14870,7 @@ class AuthBroker {
|
|
|
14731
14870
|
return;
|
|
14732
14871
|
}
|
|
14733
14872
|
if (accountExists(label, this.home) && !replace) {
|
|
14734
|
-
this.audit({ op: "add-account", identity
|
|
14873
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "claude", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
|
|
14735
14874
|
socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `account '${label}' already exists; pass replace:true to overwrite`));
|
|
14736
14875
|
return;
|
|
14737
14876
|
}
|
|
@@ -14749,12 +14888,12 @@ class AuthBroker {
|
|
|
14749
14888
|
this.persistShaIndex();
|
|
14750
14889
|
this.fanoutToAffectedAgents(label);
|
|
14751
14890
|
const expiresAt = credentials.claudeAiOauth?.expiresAt;
|
|
14752
|
-
this.audit({ op: "add-account", identity
|
|
14891
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "claude", ok: true, replace });
|
|
14753
14892
|
socket.write(encodeSuccess(id, { label, expiresAt }));
|
|
14754
14893
|
}
|
|
14755
|
-
async opRmAccount(socket, id,
|
|
14756
|
-
if (!this.isAdmin(
|
|
14757
|
-
this.audit({ op: "rm-account", identity
|
|
14894
|
+
async opRmAccount(socket, id, identity, label) {
|
|
14895
|
+
if (!this.isAdmin(identity)) {
|
|
14896
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "claude", ok: false, error: "FORBIDDEN" });
|
|
14758
14897
|
this.respondForbidden(socket, id, "rm-account requires admin");
|
|
14759
14898
|
return;
|
|
14760
14899
|
}
|
|
@@ -14787,42 +14926,42 @@ class AuthBroker {
|
|
|
14787
14926
|
this.persistQuota();
|
|
14788
14927
|
this.persistThresholdViolations();
|
|
14789
14928
|
this.persistLastQuotaCache();
|
|
14790
|
-
this.audit({ op: "rm-account", identity
|
|
14929
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "claude", ok: true });
|
|
14791
14930
|
socket.write(encodeSuccess(id, { label }));
|
|
14792
14931
|
}
|
|
14793
|
-
async opGoogleGetCredentials(socket, id,
|
|
14794
|
-
if (
|
|
14795
|
-
socket.write(encodeError(id, "INVALID_ARGS", `Google get-credentials is per-agent only (caller kind '${
|
|
14932
|
+
async opGoogleGetCredentials(socket, id, identity) {
|
|
14933
|
+
if (identity.kind !== "agent") {
|
|
14934
|
+
socket.write(encodeError(id, "INVALID_ARGS", `Google get-credentials is per-agent only (caller kind '${identity.kind}' not supported); use the agent's per-agent socket bind`));
|
|
14796
14935
|
return;
|
|
14797
14936
|
}
|
|
14798
|
-
const agentName =
|
|
14937
|
+
const agentName = identity.name;
|
|
14799
14938
|
const agent = (this.config.agents ?? {})[agentName];
|
|
14800
14939
|
const account = agent?.google_workspace?.account;
|
|
14801
14940
|
if (!account) {
|
|
14802
|
-
this.audit({ op: "get-credentials", identity
|
|
14941
|
+
this.audit({ op: "get-credentials", identity, accountKind: "google", ok: false, error: "no-google-account-configured" });
|
|
14803
14942
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `agent '${agentName}' has no google_workspace.account configured in switchroom.yaml`));
|
|
14804
14943
|
return;
|
|
14805
14944
|
}
|
|
14806
14945
|
const ga = this.config.google_accounts;
|
|
14807
14946
|
const enabledFor = ga?.[account]?.enabled_for ?? [];
|
|
14808
14947
|
if (!enabledFor.includes(agentName)) {
|
|
14809
|
-
this.audit({ op: "get-credentials", identity
|
|
14948
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "google", ok: false, error: "acl-deny" });
|
|
14810
14949
|
socket.write(encodeError(id, "FORBIDDEN", `agent '${agentName}' not in google_accounts['${account}'].enabled_for[] — operator must run \`switchroom auth google enable ${account} ${agentName}\``));
|
|
14811
14950
|
return;
|
|
14812
14951
|
}
|
|
14813
14952
|
const creds = readGoogleAccountCredentials(this.stateDir, account);
|
|
14814
14953
|
if (!creds) {
|
|
14815
|
-
this.audit({ op: "get-credentials", identity
|
|
14954
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "google", ok: false, error: "missing-credentials" });
|
|
14816
14955
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no Google credentials for account '${account}' — operator must run \`switchroom auth google account add ${account}\``));
|
|
14817
14956
|
return;
|
|
14818
14957
|
}
|
|
14819
14958
|
const expiresAt = creds.googleOauth?.expiresAt;
|
|
14820
|
-
this.audit({ op: "get-credentials", identity
|
|
14959
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "google", ok: true });
|
|
14821
14960
|
socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
|
|
14822
14961
|
}
|
|
14823
|
-
async opGoogleAddAccount(socket, id,
|
|
14824
|
-
if (!this.isAdmin(
|
|
14825
|
-
this.audit({ op: "add-account", identity
|
|
14962
|
+
async opGoogleAddAccount(socket, id, identity, label, credentials, replace) {
|
|
14963
|
+
if (!this.isAdmin(identity)) {
|
|
14964
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "google", ok: false, error: "FORBIDDEN" });
|
|
14826
14965
|
this.respondForbidden(socket, id, "add-account requires admin");
|
|
14827
14966
|
return;
|
|
14828
14967
|
}
|
|
@@ -14833,7 +14972,7 @@ class AuthBroker {
|
|
|
14833
14972
|
return;
|
|
14834
14973
|
}
|
|
14835
14974
|
if (googleAccountExists(this.stateDir, label) && !replace) {
|
|
14836
|
-
this.audit({ op: "add-account", identity
|
|
14975
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "google", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
|
|
14837
14976
|
socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `google account '${label}' already exists; pass replace:true to overwrite`));
|
|
14838
14977
|
return;
|
|
14839
14978
|
}
|
|
@@ -14844,12 +14983,12 @@ class AuthBroker {
|
|
|
14844
14983
|
return;
|
|
14845
14984
|
}
|
|
14846
14985
|
const expiresAt = credentials.googleOauth?.expiresAt;
|
|
14847
|
-
this.audit({ op: "add-account", identity
|
|
14986
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "google", ok: true, replace });
|
|
14848
14987
|
socket.write(encodeSuccess(id, { label, expiresAt }));
|
|
14849
14988
|
}
|
|
14850
|
-
async opGoogleRmAccount(socket, id,
|
|
14851
|
-
if (!this.isAdmin(
|
|
14852
|
-
this.audit({ op: "rm-account", identity
|
|
14989
|
+
async opGoogleRmAccount(socket, id, identity, label) {
|
|
14990
|
+
if (!this.isAdmin(identity)) {
|
|
14991
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "google", ok: false, error: "FORBIDDEN" });
|
|
14853
14992
|
this.respondForbidden(socket, id, "rm-account requires admin");
|
|
14854
14993
|
return;
|
|
14855
14994
|
}
|
|
@@ -14875,42 +15014,42 @@ class AuthBroker {
|
|
|
14875
15014
|
socket.write(encodeError(id, "INTERNAL", err.message));
|
|
14876
15015
|
return;
|
|
14877
15016
|
}
|
|
14878
|
-
this.audit({ op: "rm-account", identity
|
|
15017
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "google", ok: true });
|
|
14879
15018
|
socket.write(encodeSuccess(id, { label }));
|
|
14880
15019
|
}
|
|
14881
|
-
async opMicrosoftGetCredentials(socket, id,
|
|
14882
|
-
if (
|
|
14883
|
-
socket.write(encodeError(id, "INVALID_ARGS", `Microsoft get-credentials is per-agent only (caller kind '${
|
|
15020
|
+
async opMicrosoftGetCredentials(socket, id, identity) {
|
|
15021
|
+
if (identity.kind !== "agent") {
|
|
15022
|
+
socket.write(encodeError(id, "INVALID_ARGS", `Microsoft get-credentials is per-agent only (caller kind '${identity.kind}' not supported); use the agent's per-agent socket bind`));
|
|
14884
15023
|
return;
|
|
14885
15024
|
}
|
|
14886
|
-
const agentName =
|
|
15025
|
+
const agentName = identity.name;
|
|
14887
15026
|
const agent = (this.config.agents ?? {})[agentName];
|
|
14888
15027
|
const account = agent?.microsoft_workspace?.account;
|
|
14889
15028
|
if (!account) {
|
|
14890
|
-
this.audit({ op: "get-credentials", identity
|
|
15029
|
+
this.audit({ op: "get-credentials", identity, accountKind: "microsoft", ok: false, error: "no-microsoft-account-configured" });
|
|
14891
15030
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `agent '${agentName}' has no microsoft_workspace.account configured in switchroom.yaml`));
|
|
14892
15031
|
return;
|
|
14893
15032
|
}
|
|
14894
15033
|
const ma = this.config.microsoft_accounts;
|
|
14895
15034
|
const enabledFor = ma?.[account]?.enabled_for ?? [];
|
|
14896
15035
|
if (!enabledFor.includes(agentName)) {
|
|
14897
|
-
this.audit({ op: "get-credentials", identity
|
|
15036
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "microsoft", ok: false, error: "acl-deny" });
|
|
14898
15037
|
socket.write(encodeError(id, "FORBIDDEN", `agent '${agentName}' not in microsoft_accounts['${account}'].enabled_for[] — operator must run \`switchroom auth microsoft enable ${account} ${agentName}\``));
|
|
14899
15038
|
return;
|
|
14900
15039
|
}
|
|
14901
15040
|
const creds = readMicrosoftAccountCredentials(this.stateDir, account);
|
|
14902
15041
|
if (!creds) {
|
|
14903
|
-
this.audit({ op: "get-credentials", identity
|
|
15042
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "microsoft", ok: false, error: "missing-credentials" });
|
|
14904
15043
|
socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no Microsoft credentials for account '${account}' — operator must run \`switchroom auth microsoft account add ${account}\``));
|
|
14905
15044
|
return;
|
|
14906
15045
|
}
|
|
14907
15046
|
const expiresAt = creds.microsoftOauth?.expiresAt;
|
|
14908
|
-
this.audit({ op: "get-credentials", identity
|
|
15047
|
+
this.audit({ op: "get-credentials", identity, account, accountKind: "microsoft", ok: true });
|
|
14909
15048
|
socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
|
|
14910
15049
|
}
|
|
14911
|
-
async opMicrosoftAddAccount(socket, id,
|
|
14912
|
-
if (!this.isAdmin(
|
|
14913
|
-
this.audit({ op: "add-account", identity
|
|
15050
|
+
async opMicrosoftAddAccount(socket, id, identity, label, credentials, replace) {
|
|
15051
|
+
if (!this.isAdmin(identity)) {
|
|
15052
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "microsoft", ok: false, error: "FORBIDDEN" });
|
|
14914
15053
|
this.respondForbidden(socket, id, "add-account requires admin");
|
|
14915
15054
|
return;
|
|
14916
15055
|
}
|
|
@@ -14921,7 +15060,7 @@ class AuthBroker {
|
|
|
14921
15060
|
return;
|
|
14922
15061
|
}
|
|
14923
15062
|
if (microsoftAccountExists(this.stateDir, label) && !replace) {
|
|
14924
|
-
this.audit({ op: "add-account", identity
|
|
15063
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "microsoft", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
|
|
14925
15064
|
socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `microsoft account '${label}' already exists; pass replace:true to overwrite`));
|
|
14926
15065
|
return;
|
|
14927
15066
|
}
|
|
@@ -14932,12 +15071,12 @@ class AuthBroker {
|
|
|
14932
15071
|
return;
|
|
14933
15072
|
}
|
|
14934
15073
|
const expiresAt = credentials.microsoftOauth?.expiresAt;
|
|
14935
|
-
this.audit({ op: "add-account", identity
|
|
15074
|
+
this.audit({ op: "add-account", identity, account: label, accountKind: "microsoft", ok: true, replace });
|
|
14936
15075
|
socket.write(encodeSuccess(id, { label, expiresAt }));
|
|
14937
15076
|
}
|
|
14938
|
-
async opMicrosoftRmAccount(socket, id,
|
|
14939
|
-
if (!this.isAdmin(
|
|
14940
|
-
this.audit({ op: "rm-account", identity
|
|
15077
|
+
async opMicrosoftRmAccount(socket, id, identity, label) {
|
|
15078
|
+
if (!this.isAdmin(identity)) {
|
|
15079
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "microsoft", ok: false, error: "FORBIDDEN" });
|
|
14941
15080
|
this.respondForbidden(socket, id, "rm-account requires admin");
|
|
14942
15081
|
return;
|
|
14943
15082
|
}
|
|
@@ -14963,10 +15102,10 @@ class AuthBroker {
|
|
|
14963
15102
|
socket.write(encodeError(id, "INTERNAL", err.message));
|
|
14964
15103
|
return;
|
|
14965
15104
|
}
|
|
14966
|
-
this.audit({ op: "rm-account", identity
|
|
15105
|
+
this.audit({ op: "rm-account", identity, account: label, accountKind: "microsoft", ok: true });
|
|
14967
15106
|
socket.write(encodeSuccess(id, { label }));
|
|
14968
15107
|
}
|
|
14969
|
-
async opListMicrosoftAccounts(socket, id,
|
|
15108
|
+
async opListMicrosoftAccounts(socket, id, identity) {
|
|
14970
15109
|
const accounts = listMicrosoftAccounts(this.stateDir).map((account) => {
|
|
14971
15110
|
const creds = readMicrosoftAccountCredentials(this.stateDir, account);
|
|
14972
15111
|
if (!creds)
|
|
@@ -14979,12 +15118,12 @@ class AuthBroker {
|
|
|
14979
15118
|
accountType: creds.microsoftOauth.accountType
|
|
14980
15119
|
};
|
|
14981
15120
|
}).filter((entry) => entry !== null).sort((a, b) => a.account.localeCompare(b.account));
|
|
14982
|
-
this.audit({ op: "list-microsoft-accounts", identity
|
|
15121
|
+
this.audit({ op: "list-microsoft-accounts", identity, accountKind: "microsoft", ok: true });
|
|
14983
15122
|
socket.write(encodeSuccess(id, { accounts }));
|
|
14984
15123
|
}
|
|
14985
|
-
async opSetOverride(socket, id,
|
|
14986
|
-
if (!this.isAdmin(
|
|
14987
|
-
this.audit({ op: "set-override", identity
|
|
15124
|
+
async opSetOverride(socket, id, identity, agentName, account) {
|
|
15125
|
+
if (!this.isAdmin(identity)) {
|
|
15126
|
+
this.audit({ op: "set-override", identity, account: account ?? undefined, accountKind: "claude", ok: false, error: "FORBIDDEN" });
|
|
14988
15127
|
this.respondForbidden(socket, id, "set-override requires admin");
|
|
14989
15128
|
return;
|
|
14990
15129
|
}
|
|
@@ -15006,7 +15145,7 @@ class AuthBroker {
|
|
|
15006
15145
|
agents[agentName] = { ...cur, auth };
|
|
15007
15146
|
this.config = { ...this.config, agents };
|
|
15008
15147
|
this.fanoutForAgent(agentName);
|
|
15009
|
-
this.audit({ op: "set-override", identity
|
|
15148
|
+
this.audit({ op: "set-override", identity, account: account ?? undefined, accountKind: "claude", ok: true });
|
|
15010
15149
|
socket.write(encodeSuccess(id, { agent: agentName, account }));
|
|
15011
15150
|
}
|
|
15012
15151
|
async refreshTick() {
|
|
@@ -15187,12 +15326,15 @@ class AuthBroker {
|
|
|
15187
15326
|
for (const consumer of this.config.auth?.consumers ?? []) {
|
|
15188
15327
|
if (!consumer.mirror_dir)
|
|
15189
15328
|
continue;
|
|
15190
|
-
const
|
|
15329
|
+
const bound = consumer.account ?? this.config.auth?.active;
|
|
15330
|
+
const isPinned = bound === label;
|
|
15191
15331
|
const effective = this.servingAccountForConsumer(consumer.name);
|
|
15192
15332
|
const isEffective = effective === label;
|
|
15193
15333
|
if (!isPinned && !isEffective)
|
|
15194
15334
|
continue;
|
|
15195
|
-
const toMirror = effective ??
|
|
15335
|
+
const toMirror = effective ?? bound;
|
|
15336
|
+
if (toMirror == null)
|
|
15337
|
+
continue;
|
|
15196
15338
|
if (this.mirrorAccountToConsumer(toMirror, consumer)) {
|
|
15197
15339
|
fanned.push(consumer.name);
|
|
15198
15340
|
}
|
|
@@ -15216,7 +15358,7 @@ class AuthBroker {
|
|
|
15216
15358
|
const c = (this.config.auth?.consumers ?? []).find((x) => x.name === name);
|
|
15217
15359
|
if (!c)
|
|
15218
15360
|
return null;
|
|
15219
|
-
return this.accountWithFailover(c.account);
|
|
15361
|
+
return this.accountWithFailover(c.account ?? this.config.auth?.active);
|
|
15220
15362
|
}
|
|
15221
15363
|
mirrorAccountToConsumer(label, consumer) {
|
|
15222
15364
|
const mirrorDir = consumer.mirror_dir;
|
|
@@ -15362,6 +15504,42 @@ class AuthBroker {
|
|
|
15362
15504
|
this.thresholdViolations = this.readJson("threshold-violations.json") ?? {};
|
|
15363
15505
|
this.notificationClaims = this.readJson("notification-claims.json") ?? {};
|
|
15364
15506
|
this.lastQuotaCache = this.readJson("last-quota.json") ?? {};
|
|
15507
|
+
this.applyActiveOverride();
|
|
15508
|
+
}
|
|
15509
|
+
applyActiveOverride() {
|
|
15510
|
+
const ov = this.readJson("active-override.json");
|
|
15511
|
+
if (!ov || typeof ov.active !== "string" || ov.active.length === 0)
|
|
15512
|
+
return;
|
|
15513
|
+
const yamlActive = this.yamlActive ?? null;
|
|
15514
|
+
if ((ov.yaml_active_at_write ?? null) !== yamlActive) {
|
|
15515
|
+
process.stdout.write(`auth-broker: active-override dropped — yaml auth.active changed since the swap ` + `(${ov.yaml_active_at_write ?? "unset"} → ${yamlActive ?? "unset"}); yaml wins
|
|
15516
|
+
`);
|
|
15517
|
+
try {
|
|
15518
|
+
unlinkSync(join4(this.stateDir, "active-override.json"));
|
|
15519
|
+
} catch {}
|
|
15520
|
+
return;
|
|
15521
|
+
}
|
|
15522
|
+
if (!accountExists(ov.active, this.home)) {
|
|
15523
|
+
process.stdout.write(`auth-broker: active-override ignored — account '${ov.active}' not found on disk
|
|
15524
|
+
`);
|
|
15525
|
+
return;
|
|
15526
|
+
}
|
|
15527
|
+
if (ov.active === this.config.auth?.active)
|
|
15528
|
+
return;
|
|
15529
|
+
this.config = {
|
|
15530
|
+
...this.config,
|
|
15531
|
+
auth: { ...this.config.auth ?? {}, active: ov.active }
|
|
15532
|
+
};
|
|
15533
|
+
process.stdout.write(`auth-broker: active-override applied — auth.active ${yamlActive ?? "unset"} → ${ov.active} ` + `(persisted swap survives restarts)
|
|
15534
|
+
`);
|
|
15535
|
+
}
|
|
15536
|
+
persistActiveOverride(active) {
|
|
15537
|
+
const entry = {
|
|
15538
|
+
active,
|
|
15539
|
+
yaml_active_at_write: this.yamlActive ?? null,
|
|
15540
|
+
updated_at: this.now()
|
|
15541
|
+
};
|
|
15542
|
+
atomicWriteJsonSync(join4(this.stateDir, "active-override.json"), entry, 384);
|
|
15365
15543
|
}
|
|
15366
15544
|
readJson(name) {
|
|
15367
15545
|
const p = join4(this.stateDir, name);
|