switchroom 0.15.26 → 0.15.27
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/cli/switchroom.js +1098 -413
- package/dist/cli/ui/index.html +682 -60
- package/dist/host-control/main.js +279 -7
- package/package.json +3 -2
- package/telegram-plugin/dist/gateway/gateway.js +21 -4
package/dist/cli/switchroom.js
CHANGED
|
@@ -28762,7 +28762,7 @@ function decodeResponse3(line) {
|
|
|
28762
28762
|
const obj = JSON.parse(line);
|
|
28763
28763
|
return ResponseSchema3.parse(obj);
|
|
28764
28764
|
}
|
|
28765
|
-
var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
|
|
28765
|
+
var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentStatusRequestSchema, AgentScheduleRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
|
|
28766
28766
|
var init_protocol3 = __esm(() => {
|
|
28767
28767
|
init_zod();
|
|
28768
28768
|
MAX_FRAME_BYTES3 = 64 * 1024;
|
|
@@ -28848,6 +28848,20 @@ var init_protocol3 = __esm(() => {
|
|
|
28848
28848
|
op: exports_external.literal("doctor"),
|
|
28849
28849
|
args: exports_external.object({}).optional()
|
|
28850
28850
|
});
|
|
28851
|
+
AgentStatusRequestSchema = exports_external.object({
|
|
28852
|
+
...RequestEnvelope,
|
|
28853
|
+
op: exports_external.literal("agent_status"),
|
|
28854
|
+
args: exports_external.object({
|
|
28855
|
+
name: AgentNameSchema2.optional()
|
|
28856
|
+
})
|
|
28857
|
+
});
|
|
28858
|
+
AgentScheduleRequestSchema = exports_external.object({
|
|
28859
|
+
...RequestEnvelope,
|
|
28860
|
+
op: exports_external.literal("agent_schedule"),
|
|
28861
|
+
args: exports_external.object({
|
|
28862
|
+
name: AgentNameSchema2.optional()
|
|
28863
|
+
})
|
|
28864
|
+
});
|
|
28851
28865
|
AgentSmokeRequestSchema = exports_external.object({
|
|
28852
28866
|
...RequestEnvelope,
|
|
28853
28867
|
op: exports_external.literal("agent_smoke"),
|
|
@@ -28878,6 +28892,8 @@ var init_protocol3 = __esm(() => {
|
|
|
28878
28892
|
AgentExecRequestSchema,
|
|
28879
28893
|
DoctorRequestSchema,
|
|
28880
28894
|
AgentSmokeRequestSchema,
|
|
28895
|
+
AgentStatusRequestSchema,
|
|
28896
|
+
AgentScheduleRequestSchema,
|
|
28881
28897
|
ConfigProposeEditRequestSchema
|
|
28882
28898
|
]);
|
|
28883
28899
|
ResultSchema = exports_external.enum(["started", "completed", "denied", "error"]);
|
|
@@ -28929,6 +28945,7 @@ var init_protocol3 = __esm(() => {
|
|
|
28929
28945
|
audit_id: exports_external.string().min(1).optional(),
|
|
28930
28946
|
stdout_tail: exports_external.string().optional(),
|
|
28931
28947
|
stderr_tail: exports_external.string().optional(),
|
|
28948
|
+
payload: exports_external.string().optional(),
|
|
28932
28949
|
error: exports_external.string().optional(),
|
|
28933
28950
|
error_envelope: ErrorEnvelopeSchema.optional()
|
|
28934
28951
|
};
|
|
@@ -29314,12 +29331,12 @@ import {
|
|
|
29314
29331
|
readFileSync as readFileSync48,
|
|
29315
29332
|
readdirSync as readdirSync19
|
|
29316
29333
|
} from "node:fs";
|
|
29317
|
-
import { dirname as dirname12, join as
|
|
29334
|
+
import { dirname as dirname12, join as join48 } from "node:path";
|
|
29318
29335
|
import { execSync as execSync2 } from "node:child_process";
|
|
29319
29336
|
function locateManifestPath() {
|
|
29320
29337
|
let dir = import.meta.dirname;
|
|
29321
29338
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
29322
|
-
const candidate =
|
|
29339
|
+
const candidate = join48(dir, "dependencies.json");
|
|
29323
29340
|
if (existsSync52(candidate))
|
|
29324
29341
|
return candidate;
|
|
29325
29342
|
dir = dirname12(dir);
|
|
@@ -29389,13 +29406,13 @@ function probeClaudeVersion() {
|
|
|
29389
29406
|
}
|
|
29390
29407
|
function probePlaywrightMcpVersion() {
|
|
29391
29408
|
const home2 = process.env.HOME ?? "";
|
|
29392
|
-
const npxCache =
|
|
29409
|
+
const npxCache = join48(home2, ".npm/_npx");
|
|
29393
29410
|
if (!existsSync52(npxCache))
|
|
29394
29411
|
return null;
|
|
29395
29412
|
try {
|
|
29396
29413
|
const entries = readdirSync19(npxCache);
|
|
29397
29414
|
for (const entry of entries) {
|
|
29398
|
-
const pkgPath =
|
|
29415
|
+
const pkgPath = join48(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
29399
29416
|
if (existsSync52(pkgPath)) {
|
|
29400
29417
|
try {
|
|
29401
29418
|
const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
|
|
@@ -29840,8 +29857,8 @@ var init_doctor_docker = __esm(() => {
|
|
|
29840
29857
|
import { existsSync as existsSync53, readFileSync as readFileSync50 } from "node:fs";
|
|
29841
29858
|
import { createHash as createHash10 } from "node:crypto";
|
|
29842
29859
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
29843
|
-
import { homedir as
|
|
29844
|
-
import { join as
|
|
29860
|
+
import { homedir as homedir27 } from "node:os";
|
|
29861
|
+
import { join as join49 } from "node:path";
|
|
29845
29862
|
function defaultDockerInspect(container, format) {
|
|
29846
29863
|
try {
|
|
29847
29864
|
const r = spawnSync6("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
|
|
@@ -29941,7 +29958,7 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
|
|
|
29941
29958
|
}
|
|
29942
29959
|
function checkAuthBrokerDrift(deps = {}) {
|
|
29943
29960
|
const stateDir = resolveStateDir(deps);
|
|
29944
|
-
const indexPath =
|
|
29961
|
+
const indexPath = join49(stateDir, "sha-index.json");
|
|
29945
29962
|
if (!existsSync53(indexPath)) {
|
|
29946
29963
|
return {
|
|
29947
29964
|
name: "auth-broker: drift",
|
|
@@ -29960,7 +29977,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
29960
29977
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
29961
29978
|
};
|
|
29962
29979
|
}
|
|
29963
|
-
const home2 = deps.home ??
|
|
29980
|
+
const home2 = deps.home ?? homedir27();
|
|
29964
29981
|
const divergent = [];
|
|
29965
29982
|
const missingOnDisk = [];
|
|
29966
29983
|
for (const [label, expected] of Object.entries(index)) {
|
|
@@ -30002,7 +30019,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
30002
30019
|
}
|
|
30003
30020
|
function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
30004
30021
|
const stateDir = resolveStateDir(deps);
|
|
30005
|
-
const path4 =
|
|
30022
|
+
const path4 = join49(stateDir, "threshold-violations.json");
|
|
30006
30023
|
if (!existsSync53(path4)) {
|
|
30007
30024
|
return {
|
|
30008
30025
|
name: "auth-broker: threshold violations",
|
|
@@ -30046,7 +30063,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
30046
30063
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
30047
30064
|
};
|
|
30048
30065
|
}
|
|
30049
|
-
const home2 = deps.home ??
|
|
30066
|
+
const home2 = deps.home ?? homedir27();
|
|
30050
30067
|
const dir = accountDir(active, home2);
|
|
30051
30068
|
if (!existsSync53(dir)) {
|
|
30052
30069
|
return {
|
|
@@ -30198,8 +30215,8 @@ import {
|
|
|
30198
30215
|
realpathSync as realpathSync5,
|
|
30199
30216
|
statSync as statSync23
|
|
30200
30217
|
} from "node:fs";
|
|
30201
|
-
import { userInfo, homedir as
|
|
30202
|
-
import { join as
|
|
30218
|
+
import { userInfo, homedir as homedir28 } from "node:os";
|
|
30219
|
+
import { join as join50 } from "node:path";
|
|
30203
30220
|
function resolveVaultPath2(config) {
|
|
30204
30221
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
30205
30222
|
}
|
|
@@ -30337,7 +30354,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
30337
30354
|
};
|
|
30338
30355
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
30339
30356
|
if (!passphrase) {
|
|
30340
|
-
const sock = deps.brokerOperatorSocket ??
|
|
30357
|
+
const sock = deps.brokerOperatorSocket ?? join50(homedir28(), ".switchroom", "broker-operator", "sock");
|
|
30341
30358
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
30342
30359
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
30343
30360
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -30422,8 +30439,8 @@ import {
|
|
|
30422
30439
|
existsSync as realExistsSync,
|
|
30423
30440
|
readFileSync as realReadFileSync
|
|
30424
30441
|
} from "node:fs";
|
|
30425
|
-
import { join as
|
|
30426
|
-
import { homedir as
|
|
30442
|
+
import { join as join51, resolve as resolve30 } from "node:path";
|
|
30443
|
+
import { homedir as homedir29 } from "node:os";
|
|
30427
30444
|
function resolveDeps(config, deps) {
|
|
30428
30445
|
let agentsDir = deps.agentsDir;
|
|
30429
30446
|
if (agentsDir === undefined) {
|
|
@@ -30546,8 +30563,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
30546
30563
|
});
|
|
30547
30564
|
continue;
|
|
30548
30565
|
}
|
|
30549
|
-
const mcpPath =
|
|
30550
|
-
const claudeJsonPath =
|
|
30566
|
+
const mcpPath = join51(agentDir, ".mcp.json");
|
|
30567
|
+
const claudeJsonPath = join51(agentDir, ".claude", ".claude.json");
|
|
30551
30568
|
const mcpRead = readJson(d, mcpPath);
|
|
30552
30569
|
const trustRead = readJson(d, claudeJsonPath);
|
|
30553
30570
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -30660,7 +30677,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
30660
30677
|
}
|
|
30661
30678
|
];
|
|
30662
30679
|
}
|
|
30663
|
-
const sock = deps.brokerOperatorSocket ??
|
|
30680
|
+
const sock = deps.brokerOperatorSocket ?? join51(homedir29(), ".switchroom", "broker-operator", "sock");
|
|
30664
30681
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
30665
30682
|
const results = [];
|
|
30666
30683
|
for (const agent of driveAgents) {
|
|
@@ -30714,8 +30731,8 @@ import {
|
|
|
30714
30731
|
readSync as realReadSync,
|
|
30715
30732
|
closeSync as realCloseSync
|
|
30716
30733
|
} from "node:fs";
|
|
30717
|
-
import { join as
|
|
30718
|
-
import { homedir as
|
|
30734
|
+
import { join as join52 } from "node:path";
|
|
30735
|
+
import { homedir as homedir30 } from "node:os";
|
|
30719
30736
|
function defaultReadHead(p, n) {
|
|
30720
30737
|
let fd;
|
|
30721
30738
|
try {
|
|
@@ -30743,7 +30760,7 @@ function resolveDeps2(config, deps) {
|
|
|
30743
30760
|
}
|
|
30744
30761
|
}
|
|
30745
30762
|
return {
|
|
30746
|
-
homeDir: deps.homeDir ??
|
|
30763
|
+
homeDir: deps.homeDir ?? homedir30(),
|
|
30747
30764
|
agentsDir,
|
|
30748
30765
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
30749
30766
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -30772,7 +30789,7 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
30772
30789
|
return [];
|
|
30773
30790
|
const d = resolveDeps2(config, deps);
|
|
30774
30791
|
const results = [];
|
|
30775
|
-
const binPath =
|
|
30792
|
+
const binPath = join52(d.homeDir, ".switchroom", "bin", "webkite");
|
|
30776
30793
|
if (!d.existsSync(binPath)) {
|
|
30777
30794
|
results.push({
|
|
30778
30795
|
name: "webkite: binary",
|
|
@@ -30802,12 +30819,12 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
30802
30819
|
});
|
|
30803
30820
|
}
|
|
30804
30821
|
}
|
|
30805
|
-
const cloakDir =
|
|
30822
|
+
const cloakDir = join52(d.homeDir, ".cloakbrowser");
|
|
30806
30823
|
let chromeFound = false;
|
|
30807
30824
|
if (d.existsSync(cloakDir)) {
|
|
30808
30825
|
try {
|
|
30809
30826
|
for (const entry of d.readdirSync(cloakDir)) {
|
|
30810
|
-
if (entry.startsWith("chromium-") && d.existsSync(
|
|
30827
|
+
if (entry.startsWith("chromium-") && d.existsSync(join52(cloakDir, entry, "chrome"))) {
|
|
30811
30828
|
chromeFound = true;
|
|
30812
30829
|
break;
|
|
30813
30830
|
}
|
|
@@ -30833,9 +30850,9 @@ function runWebkiteChecks(config, deps = {}) {
|
|
|
30833
30850
|
return results;
|
|
30834
30851
|
}
|
|
30835
30852
|
for (const agent of enabledAgents) {
|
|
30836
|
-
const agentDir =
|
|
30837
|
-
const settingsPath =
|
|
30838
|
-
const mcpPath =
|
|
30853
|
+
const agentDir = join52(d.agentsDir, agent);
|
|
30854
|
+
const settingsPath = join52(agentDir, ".claude", "settings.json");
|
|
30855
|
+
const mcpPath = join52(agentDir, ".mcp.json");
|
|
30839
30856
|
if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
|
|
30840
30857
|
continue;
|
|
30841
30858
|
}
|
|
@@ -30965,7 +30982,7 @@ var init_doctor_cron_session = __esm(() => {
|
|
|
30965
30982
|
});
|
|
30966
30983
|
|
|
30967
30984
|
// src/cli/doctor-scaffold-wiring.ts
|
|
30968
|
-
import { join as
|
|
30985
|
+
import { join as join53, resolve as resolve32 } from "node:path";
|
|
30969
30986
|
function readJson2(d, path4) {
|
|
30970
30987
|
if (!d.existsSync(path4))
|
|
30971
30988
|
return { kind: "absent" };
|
|
@@ -31008,8 +31025,8 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
31008
31025
|
});
|
|
31009
31026
|
continue;
|
|
31010
31027
|
}
|
|
31011
|
-
const mcpPath =
|
|
31012
|
-
const claudeJsonPath =
|
|
31028
|
+
const mcpPath = join53(agentDir, ".mcp.json");
|
|
31029
|
+
const claudeJsonPath = join53(agentDir, ".claude", ".claude.json");
|
|
31013
31030
|
const mcpRead = readJson2(deps, mcpPath);
|
|
31014
31031
|
const trustRead = readJson2(deps, claudeJsonPath);
|
|
31015
31032
|
if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
|
|
@@ -31073,14 +31090,14 @@ import {
|
|
|
31073
31090
|
existsSync as realExistsSync3,
|
|
31074
31091
|
readFileSync as realReadFileSync3
|
|
31075
31092
|
} from "node:fs";
|
|
31076
|
-
import { join as
|
|
31077
|
-
import { homedir as
|
|
31093
|
+
import { join as join54 } from "node:path";
|
|
31094
|
+
import { homedir as homedir31 } from "node:os";
|
|
31078
31095
|
function resolveDeps3(deps) {
|
|
31079
|
-
const home2 = deps.homeDir?.() ??
|
|
31096
|
+
const home2 = deps.homeDir?.() ?? homedir31();
|
|
31080
31097
|
return {
|
|
31081
31098
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
31082
31099
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
31083
|
-
agentsDir:
|
|
31100
|
+
agentsDir: join54(home2, ".switchroom", "agents"),
|
|
31084
31101
|
now: deps.now ?? Date.now
|
|
31085
31102
|
};
|
|
31086
31103
|
}
|
|
@@ -31163,7 +31180,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
|
|
|
31163
31180
|
];
|
|
31164
31181
|
}
|
|
31165
31182
|
function readHeartbeat(d, agentName) {
|
|
31166
|
-
const path4 =
|
|
31183
|
+
const path4 = join54(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
|
|
31167
31184
|
if (!d.existsSync(path4)) {
|
|
31168
31185
|
return { error: "heartbeat file missing \u2014 launcher has not yet started" };
|
|
31169
31186
|
}
|
|
@@ -31260,15 +31277,15 @@ import {
|
|
|
31260
31277
|
readFileSync as realReadFileSync4,
|
|
31261
31278
|
statSync as realStatSync2
|
|
31262
31279
|
} from "node:fs";
|
|
31263
|
-
import { join as
|
|
31264
|
-
import { homedir as
|
|
31280
|
+
import { join as join55 } from "node:path";
|
|
31281
|
+
import { homedir as homedir32 } from "node:os";
|
|
31265
31282
|
function resolveDeps4(deps) {
|
|
31266
|
-
const home2 = deps.homeDir?.() ??
|
|
31283
|
+
const home2 = deps.homeDir?.() ?? homedir32();
|
|
31267
31284
|
return {
|
|
31268
31285
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
31269
31286
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
31270
31287
|
statSync: deps.statSync ?? realStatSync2,
|
|
31271
|
-
agentsDir:
|
|
31288
|
+
agentsDir: join55(home2, ".switchroom", "agents"),
|
|
31272
31289
|
now: deps.now ?? Date.now,
|
|
31273
31290
|
vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
|
|
31274
31291
|
};
|
|
@@ -31393,7 +31410,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
|
|
|
31393
31410
|
return [];
|
|
31394
31411
|
const results = [];
|
|
31395
31412
|
for (const name of notionAgents) {
|
|
31396
|
-
const heartbeatPath =
|
|
31413
|
+
const heartbeatPath = join55(d.agentsDir, name, "notion-launcher.heartbeat.json");
|
|
31397
31414
|
if (!d.existsSync(heartbeatPath)) {
|
|
31398
31415
|
results.push({
|
|
31399
31416
|
name: `notion:launcher-heartbeat:${name}`,
|
|
@@ -31468,10 +31485,10 @@ import {
|
|
|
31468
31485
|
readdirSync as realReaddirSync2,
|
|
31469
31486
|
statSync as realStatSync3
|
|
31470
31487
|
} from "node:fs";
|
|
31471
|
-
import { homedir as
|
|
31472
|
-
import { join as
|
|
31488
|
+
import { homedir as homedir33 } from "node:os";
|
|
31489
|
+
import { join as join56 } from "node:path";
|
|
31473
31490
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
31474
|
-
const credDir = deps.credentialsDir ??
|
|
31491
|
+
const credDir = deps.credentialsDir ?? join56(homedir33(), ".switchroom", "credentials");
|
|
31475
31492
|
const existsSync55 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
31476
31493
|
const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
31477
31494
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
@@ -31499,7 +31516,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
|
|
|
31499
31516
|
const flat = [];
|
|
31500
31517
|
const perAgentDirs = [];
|
|
31501
31518
|
for (const e of entries) {
|
|
31502
|
-
const full =
|
|
31519
|
+
const full = join56(credDir, e);
|
|
31503
31520
|
if (isDirectory(full) && agentNames.has(e)) {
|
|
31504
31521
|
perAgentDirs.push(e);
|
|
31505
31522
|
} else {
|
|
@@ -31622,19 +31639,19 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
31622
31639
|
|
|
31623
31640
|
// src/cli/doctor-audit-integrity.ts
|
|
31624
31641
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
31625
|
-
import { homedir as
|
|
31626
|
-
import { join as
|
|
31642
|
+
import { homedir as homedir34 } from "node:os";
|
|
31643
|
+
import { join as join57 } from "node:path";
|
|
31627
31644
|
function rootWrittenLogs(home2) {
|
|
31628
31645
|
return [
|
|
31629
|
-
{ label: "vault-broker", path:
|
|
31646
|
+
{ label: "vault-broker", path: join57(home2, ".switchroom", "vault-audit.log") },
|
|
31630
31647
|
{
|
|
31631
31648
|
label: "hostd",
|
|
31632
|
-
path:
|
|
31649
|
+
path: join57(home2, ".switchroom", "host-control-audit.log")
|
|
31633
31650
|
}
|
|
31634
31651
|
];
|
|
31635
31652
|
}
|
|
31636
31653
|
function runAuditIntegrityChecks(deps = {}) {
|
|
31637
|
-
const home2 = deps.homeDir ??
|
|
31654
|
+
const home2 = deps.homeDir ?? homedir34();
|
|
31638
31655
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
31639
31656
|
const results = [];
|
|
31640
31657
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -31692,14 +31709,14 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
31692
31709
|
|
|
31693
31710
|
// src/cli/doctor-agent-smoke.ts
|
|
31694
31711
|
import { existsSync as existsSync55 } from "node:fs";
|
|
31695
|
-
import { homedir as
|
|
31696
|
-
import { join as
|
|
31712
|
+
import { homedir as homedir35 } from "node:os";
|
|
31713
|
+
import { join as join58 } from "node:path";
|
|
31697
31714
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
31698
31715
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
31699
31716
|
if (deps.fast)
|
|
31700
31717
|
return [];
|
|
31701
|
-
const home2 = deps.homeDir ??
|
|
31702
|
-
const sock = deps.operatorSockPath ??
|
|
31718
|
+
const home2 = deps.homeDir ?? homedir35();
|
|
31719
|
+
const sock = deps.operatorSockPath ?? join58(home2, ".switchroom", "hostd", "operator", "sock");
|
|
31703
31720
|
if (!deps.hostdRequestImpl && !existsSync55(sock)) {
|
|
31704
31721
|
return [
|
|
31705
31722
|
{
|
|
@@ -31779,8 +31796,8 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
31779
31796
|
// src/cli/doctor-vault-broker-durability.ts
|
|
31780
31797
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
31781
31798
|
import { existsSync as existsSync56, statSync as statSync24 } from "node:fs";
|
|
31782
|
-
import { homedir as
|
|
31783
|
-
import { join as
|
|
31799
|
+
import { homedir as homedir36 } from "node:os";
|
|
31800
|
+
import { join as join59 } from "node:path";
|
|
31784
31801
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
31785
31802
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
31786
31803
|
const statBroker = opts?.statBroker ?? defaultStatBroker;
|
|
@@ -31923,22 +31940,22 @@ function defaultBrokerStatusProbe() {
|
|
|
31923
31940
|
}
|
|
31924
31941
|
}
|
|
31925
31942
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
31926
|
-
const home2 =
|
|
31943
|
+
const home2 = homedir36();
|
|
31927
31944
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
31928
31945
|
return [
|
|
31929
31946
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
31930
31947
|
probeAutoUnlockBlob(home2),
|
|
31931
31948
|
probeMachineIdMount(),
|
|
31932
|
-
formatBindMountResult("vault-broker: vault.enc bind mount",
|
|
31933
|
-
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)",
|
|
31934
|
-
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)",
|
|
31949
|
+
formatBindMountResult("vault-broker: vault.enc bind mount", join59(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join59(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
|
|
31950
|
+
formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join59(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join59(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
|
|
31951
|
+
formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join59(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join59(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
|
|
31935
31952
|
probeKernelDbDurability(home2, {
|
|
31936
31953
|
statBroker: opts?.kernelStatBroker
|
|
31937
31954
|
})
|
|
31938
31955
|
];
|
|
31939
31956
|
}
|
|
31940
31957
|
function probeKernelDbDurability(home2, opts) {
|
|
31941
|
-
const hostDir =
|
|
31958
|
+
const hostDir = join59(home2, ".switchroom", "approvals");
|
|
31942
31959
|
const containerDir = "/state/approvals";
|
|
31943
31960
|
const name = "approval-kernel: approvals bind mount (allow_always durability)";
|
|
31944
31961
|
const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
|
|
@@ -32006,7 +32023,7 @@ function defaultKernelStatBroker(p) {
|
|
|
32006
32023
|
return { kind: "ok-with-stat", ino: inoStr, size };
|
|
32007
32024
|
}
|
|
32008
32025
|
function probeAutoUnlockBlob(home2) {
|
|
32009
|
-
const blobPath =
|
|
32026
|
+
const blobPath = join59(home2, ".switchroom", "vault-auto-unlock");
|
|
32010
32027
|
if (!existsSync56(blobPath)) {
|
|
32011
32028
|
return {
|
|
32012
32029
|
name: "vault-broker: auto-unlock blob",
|
|
@@ -32118,16 +32135,16 @@ import {
|
|
|
32118
32135
|
readdirSync as readdirSync21,
|
|
32119
32136
|
statSync as statSync25
|
|
32120
32137
|
} from "node:fs";
|
|
32121
|
-
import { dirname as dirname13, join as
|
|
32138
|
+
import { dirname as dirname13, join as join60, resolve as resolve33 } from "node:path";
|
|
32122
32139
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
32123
32140
|
function findInNvm(bin) {
|
|
32124
|
-
const nvmRoot =
|
|
32141
|
+
const nvmRoot = join60(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
32125
32142
|
if (!existsSync57(nvmRoot))
|
|
32126
32143
|
return null;
|
|
32127
32144
|
try {
|
|
32128
32145
|
const versions = readdirSync21(nvmRoot).sort().reverse();
|
|
32129
32146
|
for (const v of versions) {
|
|
32130
|
-
const candidate =
|
|
32147
|
+
const candidate = join60(nvmRoot, v, "bin", bin);
|
|
32131
32148
|
try {
|
|
32132
32149
|
const s = statSync25(candidate);
|
|
32133
32150
|
if (s.isFile() || s.isSymbolicLink()) {
|
|
@@ -32292,7 +32309,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
32292
32309
|
if (envBrowsersPath && envBrowsersPath.length > 0) {
|
|
32293
32310
|
cacheLocations.push(envBrowsersPath);
|
|
32294
32311
|
}
|
|
32295
|
-
cacheLocations.push(
|
|
32312
|
+
cacheLocations.push(join60(homeDir, ".cache", "ms-playwright"));
|
|
32296
32313
|
for (const cacheDir of cacheLocations) {
|
|
32297
32314
|
if (!existsSync57(cacheDir))
|
|
32298
32315
|
continue;
|
|
@@ -32300,10 +32317,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
|
|
|
32300
32317
|
const entries = readdirSync21(cacheDir).filter((e) => e.startsWith("chromium"));
|
|
32301
32318
|
for (const entry of entries) {
|
|
32302
32319
|
const candidates2 = [
|
|
32303
|
-
|
|
32304
|
-
|
|
32305
|
-
|
|
32306
|
-
|
|
32320
|
+
join60(cacheDir, entry, "chrome-linux64", "chrome"),
|
|
32321
|
+
join60(cacheDir, entry, "chrome-linux", "chrome"),
|
|
32322
|
+
join60(cacheDir, entry, "chrome-linux64", "headless_shell"),
|
|
32323
|
+
join60(cacheDir, entry, "chrome-linux", "headless_shell")
|
|
32307
32324
|
];
|
|
32308
32325
|
for (const path4 of candidates2) {
|
|
32309
32326
|
if (existsSync57(path4))
|
|
@@ -32426,7 +32443,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
|
|
|
32426
32443
|
function checkLegacyState() {
|
|
32427
32444
|
const results = [];
|
|
32428
32445
|
const h = process.env.HOME ?? "/root";
|
|
32429
|
-
const clerkDir =
|
|
32446
|
+
const clerkDir = join60(h, LEGACY_STATE_DIR);
|
|
32430
32447
|
const clerkPresent = existsSync57(clerkDir);
|
|
32431
32448
|
results.push({
|
|
32432
32449
|
name: "legacy ~/.clerk state",
|
|
@@ -32436,7 +32453,7 @@ function checkLegacyState() {
|
|
|
32436
32453
|
fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
|
|
32437
32454
|
} : {}
|
|
32438
32455
|
});
|
|
32439
|
-
const legacySock =
|
|
32456
|
+
const legacySock = join60(h, ".switchroom", "vault-broker.sock");
|
|
32440
32457
|
let sockStat = null;
|
|
32441
32458
|
try {
|
|
32442
32459
|
sockStat = lstatSync6(legacySock);
|
|
@@ -32814,7 +32831,7 @@ async function checkHindsight(config) {
|
|
|
32814
32831
|
}
|
|
32815
32832
|
function checkPendingRetainsQueue(dir) {
|
|
32816
32833
|
const home2 = process.env.HOME ?? "";
|
|
32817
|
-
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ??
|
|
32834
|
+
const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join60(home2, ".hindsight", "pending-retains");
|
|
32818
32835
|
if (!existsSync57(pendingDir)) {
|
|
32819
32836
|
return {
|
|
32820
32837
|
name: "pending-retains queue",
|
|
@@ -32945,7 +32962,7 @@ async function checkTelegram(config) {
|
|
|
32945
32962
|
const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
|
|
32946
32963
|
if (plugin !== "switchroom")
|
|
32947
32964
|
continue;
|
|
32948
|
-
const envPath =
|
|
32965
|
+
const envPath = join60(agentsDir, name, "telegram", ".env");
|
|
32949
32966
|
const read = tryReadHostFile(envPath);
|
|
32950
32967
|
if (read.kind === "eacces") {
|
|
32951
32968
|
results.push({
|
|
@@ -33028,7 +33045,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
33028
33045
|
}
|
|
33029
33046
|
function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
33030
33047
|
const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
|
|
33031
|
-
const path4 =
|
|
33048
|
+
const path4 = join60(agentDir, "home", ".switchroom");
|
|
33032
33049
|
let stats;
|
|
33033
33050
|
try {
|
|
33034
33051
|
stats = lstatSync6(path4);
|
|
@@ -33065,7 +33082,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
|
|
|
33065
33082
|
}
|
|
33066
33083
|
function checkRepoHygiene(repoRoot) {
|
|
33067
33084
|
const results = [];
|
|
33068
|
-
const exportDir =
|
|
33085
|
+
const exportDir = join60(repoRoot, "clerk-export");
|
|
33069
33086
|
if (existsSync57(exportDir)) {
|
|
33070
33087
|
results.push({
|
|
33071
33088
|
name: "repo hygiene: clerk-export/ on disk (#1072)",
|
|
@@ -33074,7 +33091,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
33074
33091
|
fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
|
|
33075
33092
|
});
|
|
33076
33093
|
}
|
|
33077
|
-
const knownTarball =
|
|
33094
|
+
const knownTarball = join60(repoRoot, "clerk-export-with-secrets.tar.gz");
|
|
33078
33095
|
if (existsSync57(knownTarball)) {
|
|
33079
33096
|
results.push({
|
|
33080
33097
|
name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
|
|
@@ -33092,7 +33109,7 @@ function checkRepoHygiene(repoRoot) {
|
|
|
33092
33109
|
results.push({
|
|
33093
33110
|
name: `repo hygiene: ${name} on disk (#1072)`,
|
|
33094
33111
|
status: "warn",
|
|
33095
|
-
detail: `${
|
|
33112
|
+
detail: `${join60(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
|
|
33096
33113
|
fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
|
|
33097
33114
|
});
|
|
33098
33115
|
}
|
|
@@ -33115,9 +33132,9 @@ function checkRepoHygiene(repoRoot) {
|
|
|
33115
33132
|
}
|
|
33116
33133
|
function isSwitchroomCheckout(dir) {
|
|
33117
33134
|
try {
|
|
33118
|
-
if (!existsSync57(
|
|
33135
|
+
if (!existsSync57(join60(dir, ".git")))
|
|
33119
33136
|
return false;
|
|
33120
|
-
const pkgPath =
|
|
33137
|
+
const pkgPath = join60(dir, "package.json");
|
|
33121
33138
|
if (!existsSync57(pkgPath))
|
|
33122
33139
|
return false;
|
|
33123
33140
|
const pkg = JSON.parse(readFileSync51(pkgPath, "utf-8"));
|
|
@@ -33154,7 +33171,7 @@ function checkAgents(config, configPath) {
|
|
|
33154
33171
|
fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
|
|
33155
33172
|
});
|
|
33156
33173
|
}
|
|
33157
|
-
results.push(checkStartShStale(name,
|
|
33174
|
+
results.push(checkStartShStale(name, join60(agentDir, "start.sh")));
|
|
33158
33175
|
results.push(checkLeakedHomeSwitchroom(name, agentDir));
|
|
33159
33176
|
const status = statuses[name];
|
|
33160
33177
|
const active = status?.active ?? "unknown";
|
|
@@ -33231,7 +33248,7 @@ function checkAgents(config, configPath) {
|
|
|
33231
33248
|
}
|
|
33232
33249
|
}
|
|
33233
33250
|
if (agentConfig.channels?.telegram?.plugin === "switchroom") {
|
|
33234
|
-
const mcpJsonPath =
|
|
33251
|
+
const mcpJsonPath = join60(agentDir, ".mcp.json");
|
|
33235
33252
|
if (!existsSync57(mcpJsonPath)) {
|
|
33236
33253
|
results.push({
|
|
33237
33254
|
name: `${name}: .mcp.json`,
|
|
@@ -33533,7 +33550,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
|
|
|
33533
33550
|
};
|
|
33534
33551
|
}
|
|
33535
33552
|
const credDir = dirname13(envPath);
|
|
33536
|
-
const authScript =
|
|
33553
|
+
const authScript = join60(credDir, "claude-auth.py");
|
|
33537
33554
|
if (!existsSync57(authScript)) {
|
|
33538
33555
|
return {
|
|
33539
33556
|
name: "mff: auth flow",
|
|
@@ -50477,8 +50494,8 @@ var {
|
|
|
50477
50494
|
} = import__.default;
|
|
50478
50495
|
|
|
50479
50496
|
// src/build-info.ts
|
|
50480
|
-
var VERSION = "0.15.
|
|
50481
|
-
var COMMIT_SHA = "
|
|
50497
|
+
var VERSION = "0.15.27";
|
|
50498
|
+
var COMMIT_SHA = "97160057";
|
|
50482
50499
|
|
|
50483
50500
|
// src/cli/agent.ts
|
|
50484
50501
|
init_source();
|
|
@@ -68874,7 +68891,6 @@ init_source();
|
|
|
68874
68891
|
init_merge();
|
|
68875
68892
|
init_loader();
|
|
68876
68893
|
init_client();
|
|
68877
|
-
init_lifecycle();
|
|
68878
68894
|
import {
|
|
68879
68895
|
readFileSync as readFileSync46,
|
|
68880
68896
|
existsSync as existsSync50,
|
|
@@ -68885,9 +68901,8 @@ import {
|
|
|
68885
68901
|
writeSync as writeSync6,
|
|
68886
68902
|
constants as fsConstants3
|
|
68887
68903
|
} from "node:fs";
|
|
68888
|
-
import { resolve as resolve28, extname, join as
|
|
68889
|
-
import { homedir as
|
|
68890
|
-
import { spawn as spawn5 } from "node:child_process";
|
|
68904
|
+
import { resolve as resolve28, extname, join as join47, relative, dirname as dirname10 } from "node:path";
|
|
68905
|
+
import { homedir as homedir26 } from "node:os";
|
|
68891
68906
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
|
|
68892
68907
|
|
|
68893
68908
|
// src/web/api.ts
|
|
@@ -68896,7 +68911,71 @@ init_manager();
|
|
|
68896
68911
|
init_hindsight();
|
|
68897
68912
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
68898
68913
|
import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
|
|
68899
|
-
import { resolve as resolve27 } from "node:path";
|
|
68914
|
+
import { resolve as resolve27, join as join44 } from "node:path";
|
|
68915
|
+
|
|
68916
|
+
// src/web/memory-remediation.ts
|
|
68917
|
+
init_hindsight();
|
|
68918
|
+
function restBaseFromMcpUrl(mcpUrl) {
|
|
68919
|
+
return hindsightRestBase(mcpUrl);
|
|
68920
|
+
}
|
|
68921
|
+
async function postTrigger(url, opts) {
|
|
68922
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
68923
|
+
const timeoutMs = opts?.timeoutMs ?? 5000;
|
|
68924
|
+
const controller = new AbortController;
|
|
68925
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
68926
|
+
try {
|
|
68927
|
+
const resp = await fetchImpl(url, {
|
|
68928
|
+
method: "POST",
|
|
68929
|
+
headers: { "Content-Type": "application/json" },
|
|
68930
|
+
signal: controller.signal
|
|
68931
|
+
});
|
|
68932
|
+
clearTimeout(timeout);
|
|
68933
|
+
if (!resp.ok)
|
|
68934
|
+
return { ok: false, reason: `HTTP ${resp.status}` };
|
|
68935
|
+
return { ok: true };
|
|
68936
|
+
} catch (err) {
|
|
68937
|
+
clearTimeout(timeout);
|
|
68938
|
+
if (err.name === "AbortError")
|
|
68939
|
+
return { ok: false, reason: "Timeout" };
|
|
68940
|
+
return { ok: false, reason: String(err.message ?? err) };
|
|
68941
|
+
}
|
|
68942
|
+
}
|
|
68943
|
+
async function reprocessDocuments(restBase, bank, docIds, opts) {
|
|
68944
|
+
let triggered = 0;
|
|
68945
|
+
let failed = 0;
|
|
68946
|
+
let error;
|
|
68947
|
+
const encodedBank = encodeURIComponent(bank);
|
|
68948
|
+
for (const docId of docIds) {
|
|
68949
|
+
const url = `${restBase}/v1/default/banks/${encodedBank}/documents/${encodeURIComponent(docId)}/reprocess`;
|
|
68950
|
+
const r = await postTrigger(url, opts);
|
|
68951
|
+
if (r.ok) {
|
|
68952
|
+
triggered += 1;
|
|
68953
|
+
} else {
|
|
68954
|
+
failed += 1;
|
|
68955
|
+
if (!error)
|
|
68956
|
+
error = r.reason;
|
|
68957
|
+
}
|
|
68958
|
+
}
|
|
68959
|
+
return error ? { triggered, failed, error } : { triggered, failed };
|
|
68960
|
+
}
|
|
68961
|
+
async function refreshMentalModel(restBase, bank, modelId, opts) {
|
|
68962
|
+
const url = `${restBase}/v1/default/banks/${encodeURIComponent(bank)}/mental-models/${encodeURIComponent(modelId)}/refresh`;
|
|
68963
|
+
const r = await postTrigger(url, opts);
|
|
68964
|
+
return r.ok ? { ok: true } : { ok: false, error: r.reason };
|
|
68965
|
+
}
|
|
68966
|
+
async function buildUserProfile(mcpUrl, bank, opts) {
|
|
68967
|
+
try {
|
|
68968
|
+
const r = await ensureUserProfileMentalModel(mcpUrl, bank, {
|
|
68969
|
+
fetchImpl: opts?.fetchImpl,
|
|
68970
|
+
timeoutMs: opts?.timeoutMs
|
|
68971
|
+
});
|
|
68972
|
+
return r.ok ? { ok: true } : { ok: false, error: r.reason };
|
|
68973
|
+
} catch (err) {
|
|
68974
|
+
return { ok: false, error: String(err.message ?? err) };
|
|
68975
|
+
}
|
|
68976
|
+
}
|
|
68977
|
+
|
|
68978
|
+
// src/web/api.ts
|
|
68900
68979
|
init_audit_reader();
|
|
68901
68980
|
|
|
68902
68981
|
// src/scheduler/dispatch.ts
|
|
@@ -68959,6 +69038,8 @@ function readRecentFires(jsonlPath) {
|
|
|
68959
69038
|
|
|
68960
69039
|
// src/web/api.ts
|
|
68961
69040
|
init_client3();
|
|
69041
|
+
init_client();
|
|
69042
|
+
import { homedir as homedir23 } from "node:os";
|
|
68962
69043
|
|
|
68963
69044
|
// node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
|
|
68964
69045
|
import { dirname as dirname8, posix, sep as sep2 } from "path";
|
|
@@ -73694,6 +73775,121 @@ async function proposeConfigEditViaHostd(args) {
|
|
|
73694
73775
|
}
|
|
73695
73776
|
}
|
|
73696
73777
|
|
|
73778
|
+
// src/web/hostd-read-client.ts
|
|
73779
|
+
init_client4();
|
|
73780
|
+
var READ_TIMEOUT_MS = 4000;
|
|
73781
|
+
function readRequestId(verb) {
|
|
73782
|
+
return `web-${verb}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
73783
|
+
}
|
|
73784
|
+
async function fetchFleetStatusViaHostd(opts = {}) {
|
|
73785
|
+
const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
|
|
73786
|
+
if (!socketPath)
|
|
73787
|
+
return null;
|
|
73788
|
+
const send = opts.send ?? hostdRequest;
|
|
73789
|
+
const req = {
|
|
73790
|
+
v: 1,
|
|
73791
|
+
op: "agent_status",
|
|
73792
|
+
request_id: readRequestId("agent_status"),
|
|
73793
|
+
args: {}
|
|
73794
|
+
};
|
|
73795
|
+
try {
|
|
73796
|
+
const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
|
|
73797
|
+
if (resp.result !== "completed" || !resp.payload)
|
|
73798
|
+
return null;
|
|
73799
|
+
const parsed = JSON.parse(resp.payload);
|
|
73800
|
+
if (!parsed || typeof parsed !== "object" || !parsed.statuses)
|
|
73801
|
+
return null;
|
|
73802
|
+
return parsed.statuses;
|
|
73803
|
+
} catch {
|
|
73804
|
+
return null;
|
|
73805
|
+
}
|
|
73806
|
+
}
|
|
73807
|
+
async function fetchScheduleViaHostd(opts = {}) {
|
|
73808
|
+
const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
|
|
73809
|
+
if (!socketPath)
|
|
73810
|
+
return null;
|
|
73811
|
+
const send = opts.send ?? hostdRequest;
|
|
73812
|
+
const req = {
|
|
73813
|
+
v: 1,
|
|
73814
|
+
op: "agent_schedule",
|
|
73815
|
+
request_id: readRequestId("agent_schedule"),
|
|
73816
|
+
args: {}
|
|
73817
|
+
};
|
|
73818
|
+
try {
|
|
73819
|
+
const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
|
|
73820
|
+
if (resp.result !== "completed" || !resp.payload)
|
|
73821
|
+
return null;
|
|
73822
|
+
const parsed = JSON.parse(resp.payload);
|
|
73823
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.entries)) {
|
|
73824
|
+
return null;
|
|
73825
|
+
}
|
|
73826
|
+
return {
|
|
73827
|
+
entries: parsed.entries,
|
|
73828
|
+
recentByAgent: parsed.recentByAgent ?? {},
|
|
73829
|
+
...parsed.truncated ? { truncated: true } : {}
|
|
73830
|
+
};
|
|
73831
|
+
} catch {
|
|
73832
|
+
return null;
|
|
73833
|
+
}
|
|
73834
|
+
}
|
|
73835
|
+
async function fetchAgentLogsViaHostd(name, lines, opts = {}) {
|
|
73836
|
+
const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
|
|
73837
|
+
if (!socketPath) {
|
|
73838
|
+
return { ok: false, error: HOSTD_UNREACHABLE_LOGS_MESSAGE };
|
|
73839
|
+
}
|
|
73840
|
+
const send = opts.send ?? hostdRequest;
|
|
73841
|
+
const tail = Math.max(1, Math.min(lines, 2000));
|
|
73842
|
+
const req = {
|
|
73843
|
+
v: 1,
|
|
73844
|
+
op: "agent_logs",
|
|
73845
|
+
request_id: readRequestId("agent_logs"),
|
|
73846
|
+
args: { name, tail }
|
|
73847
|
+
};
|
|
73848
|
+
try {
|
|
73849
|
+
const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
|
|
73850
|
+
if (resp.result === "completed") {
|
|
73851
|
+
return { ok: true, logs: resp.stdout_tail ?? "" };
|
|
73852
|
+
}
|
|
73853
|
+
return {
|
|
73854
|
+
ok: false,
|
|
73855
|
+
error: resp.error ?? (resp.stderr_tail && resp.stderr_tail.trim()) ?? `hostd agent_logs returned '${resp.result}'`
|
|
73856
|
+
};
|
|
73857
|
+
} catch {
|
|
73858
|
+
return { ok: false, error: HOSTD_UNREACHABLE_LOGS_MESSAGE };
|
|
73859
|
+
}
|
|
73860
|
+
}
|
|
73861
|
+
var HOSTD_UNREACHABLE_LOGS_MESSAGE = "Logs need hostd (the dashboard container has no docker access by design). " + "Ensure host_control is enabled, or run `switchroom agent logs <name>` on the host.";
|
|
73862
|
+
var HOSTD_UNREACHABLE_CONTROL_MESSAGE = "Agent control needs hostd (the dashboard container has no docker access by design). " + "Ensure host_control is enabled, or run `switchroom agent restart <name>` on the host.";
|
|
73863
|
+
async function restartAgentViaHostd(name, opts = {}) {
|
|
73864
|
+
const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
|
|
73865
|
+
if (!socketPath) {
|
|
73866
|
+
return { ok: false, error: HOSTD_UNREACHABLE_CONTROL_MESSAGE };
|
|
73867
|
+
}
|
|
73868
|
+
const send = opts.send ?? hostdRequest;
|
|
73869
|
+
const req = {
|
|
73870
|
+
v: 1,
|
|
73871
|
+
op: "agent_restart",
|
|
73872
|
+
request_id: readRequestId("agent_restart"),
|
|
73873
|
+
args: {
|
|
73874
|
+
name,
|
|
73875
|
+
reason: opts.reason ?? "restart requested from dashboard",
|
|
73876
|
+
force: opts.force ?? true
|
|
73877
|
+
}
|
|
73878
|
+
};
|
|
73879
|
+
try {
|
|
73880
|
+
const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
|
|
73881
|
+
if (resp.result === "completed" || resp.result === "started") {
|
|
73882
|
+
return { ok: true };
|
|
73883
|
+
}
|
|
73884
|
+
return {
|
|
73885
|
+
ok: false,
|
|
73886
|
+
error: resp.error ?? `hostd agent_restart returned '${resp.result}'`
|
|
73887
|
+
};
|
|
73888
|
+
} catch {
|
|
73889
|
+
return { ok: false, error: HOSTD_UNREACHABLE_CONTROL_MESSAGE };
|
|
73890
|
+
}
|
|
73891
|
+
}
|
|
73892
|
+
|
|
73697
73893
|
// src/web/api.ts
|
|
73698
73894
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
73699
73895
|
|
|
@@ -73925,7 +74121,78 @@ function listSubagents(db, opts = {}) {
|
|
|
73925
74121
|
return rows.map(mapSubagentRow);
|
|
73926
74122
|
}
|
|
73927
74123
|
|
|
74124
|
+
// src/web/cache.ts
|
|
74125
|
+
class SwrCache {
|
|
74126
|
+
now;
|
|
74127
|
+
entries = new Map;
|
|
74128
|
+
constructor(now = Date.now) {
|
|
74129
|
+
this.now = now;
|
|
74130
|
+
}
|
|
74131
|
+
async get(key, ttlMs, producer) {
|
|
74132
|
+
const existing = this.entries.get(key);
|
|
74133
|
+
if (!existing) {
|
|
74134
|
+
const value = await producer();
|
|
74135
|
+
const fetchedAt = this.now();
|
|
74136
|
+
this.entries.set(key, { value, fetchedAt, refreshing: false });
|
|
74137
|
+
return { value, dataAsOf: fetchedAt, stale: false };
|
|
74138
|
+
}
|
|
74139
|
+
const age = this.now() - existing.fetchedAt;
|
|
74140
|
+
if (age < ttlMs) {
|
|
74141
|
+
return {
|
|
74142
|
+
value: existing.value,
|
|
74143
|
+
dataAsOf: existing.fetchedAt,
|
|
74144
|
+
stale: false
|
|
74145
|
+
};
|
|
74146
|
+
}
|
|
74147
|
+
if (!existing.refreshing) {
|
|
74148
|
+
existing.refreshing = true;
|
|
74149
|
+
producer().then((value) => {
|
|
74150
|
+
this.entries.set(key, {
|
|
74151
|
+
value,
|
|
74152
|
+
fetchedAt: this.now(),
|
|
74153
|
+
refreshing: false
|
|
74154
|
+
});
|
|
74155
|
+
}, () => {
|
|
74156
|
+
const cur = this.entries.get(key);
|
|
74157
|
+
if (cur)
|
|
74158
|
+
cur.refreshing = false;
|
|
74159
|
+
});
|
|
74160
|
+
}
|
|
74161
|
+
return {
|
|
74162
|
+
value: existing.value,
|
|
74163
|
+
dataAsOf: existing.fetchedAt,
|
|
74164
|
+
stale: true
|
|
74165
|
+
};
|
|
74166
|
+
}
|
|
74167
|
+
clear() {
|
|
74168
|
+
this.entries.clear();
|
|
74169
|
+
}
|
|
74170
|
+
}
|
|
74171
|
+
|
|
73928
74172
|
// src/web/api.ts
|
|
74173
|
+
var dashboardCache = new SwrCache;
|
|
74174
|
+
var DASHBOARD_CACHE_TTL = {
|
|
74175
|
+
"system-health": 15000,
|
|
74176
|
+
"memory-health": 60000,
|
|
74177
|
+
schedule: 30000,
|
|
74178
|
+
agents: 5000,
|
|
74179
|
+
accounts: 1e4,
|
|
74180
|
+
approvals: 15000,
|
|
74181
|
+
grants: 15000
|
|
74182
|
+
};
|
|
74183
|
+
function readLastTurnAt(agentsDir, name) {
|
|
74184
|
+
try {
|
|
74185
|
+
const db = openTurnsDb(resolve27(agentsDir, name));
|
|
74186
|
+
try {
|
|
74187
|
+
const turns = listTurnsForAgent(db, { limit: 1 });
|
|
74188
|
+
return turns.length > 0 ? turns[0].started_at : null;
|
|
74189
|
+
} finally {
|
|
74190
|
+
db.close();
|
|
74191
|
+
}
|
|
74192
|
+
} catch {
|
|
74193
|
+
return null;
|
|
74194
|
+
}
|
|
74195
|
+
}
|
|
73929
74196
|
function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
|
|
73930
74197
|
try {
|
|
73931
74198
|
const f = resolve27(agentsDir, name, "telegram", ".bridge-alive");
|
|
@@ -73934,11 +74201,25 @@ function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
|
|
|
73934
74201
|
return false;
|
|
73935
74202
|
}
|
|
73936
74203
|
}
|
|
73937
|
-
|
|
74204
|
+
var AGENT_STATUS_CACHE_TTL_MS = 5000;
|
|
74205
|
+
var statusCache = new Map;
|
|
74206
|
+
async function cachedFleetStatus(now, fetchImpl = fetchFleetStatusViaHostd) {
|
|
74207
|
+
const cached = statusCache.get("fleet");
|
|
74208
|
+
if (cached && now - cached.fetchedAt < AGENT_STATUS_CACHE_TTL_MS) {
|
|
74209
|
+
return cached.statuses;
|
|
74210
|
+
}
|
|
74211
|
+
const statuses = await fetchImpl();
|
|
74212
|
+
statusCache.set("fleet", { statuses, fetchedAt: now });
|
|
74213
|
+
return statuses;
|
|
74214
|
+
}
|
|
74215
|
+
async function handleGetAgents(config, deps = {}) {
|
|
74216
|
+
const readLastTurn = deps.readLastTurn ?? readLastTurnAt;
|
|
73938
74217
|
const statuses = getAllAgentStatuses(config);
|
|
73939
74218
|
const authStatuses = getAllAuthStatuses(config);
|
|
73940
74219
|
const agentsDir = resolveAgentsDir(config);
|
|
73941
74220
|
const agents = [];
|
|
74221
|
+
const needsBackfill = Object.values(statuses).some((s) => s && (s.uptime === null || s.memory === null));
|
|
74222
|
+
const hostdStatuses = needsBackfill ? await cachedFleetStatus(deps.now ?? Date.now(), deps.fetchFleetStatus) : null;
|
|
73942
74223
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
73943
74224
|
const status = statuses[name];
|
|
73944
74225
|
const auth = authStatuses[name];
|
|
@@ -73946,11 +74227,14 @@ function handleGetAgents(config) {
|
|
|
73946
74227
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
73947
74228
|
const primaryAccount = resolved.auth?.override ?? config.auth?.active;
|
|
73948
74229
|
const active = status?.active === "active" ? "active" : agentBridgeAlive(agentsDir, name) ? "active" : status?.active ?? "unknown";
|
|
74230
|
+
const hostd = hostdStatuses?.[name];
|
|
74231
|
+
const uptime = status?.uptime ?? hostd?.uptime ?? null;
|
|
74232
|
+
const memory = status?.memory ?? hostd?.memory ?? null;
|
|
73949
74233
|
agents.push({
|
|
73950
74234
|
name,
|
|
73951
74235
|
active,
|
|
73952
|
-
uptime
|
|
73953
|
-
memory
|
|
74236
|
+
uptime,
|
|
74237
|
+
memory,
|
|
73954
74238
|
extends: agentConfig.extends ?? "default",
|
|
73955
74239
|
topic_name: agentConfig.topic_name,
|
|
73956
74240
|
topic_emoji: agentConfig.topic_emoji,
|
|
@@ -73961,54 +74245,40 @@ function handleGetAgents(config) {
|
|
|
73961
74245
|
timeUntilExpiry: auth?.timeUntilExpiry,
|
|
73962
74246
|
expiresAt: auth?.expiresAt
|
|
73963
74247
|
},
|
|
73964
|
-
memoryCollection: collection
|
|
74248
|
+
memoryCollection: collection,
|
|
74249
|
+
lastTurnAt: readLastTurn(agentsDir, name)
|
|
73965
74250
|
});
|
|
73966
74251
|
}
|
|
73967
74252
|
return agents;
|
|
73968
74253
|
}
|
|
73969
|
-
function
|
|
73970
|
-
|
|
73971
|
-
startAgent(name);
|
|
73972
|
-
captureEvent("agent_started", { agent: name, source: "web_api" });
|
|
73973
|
-
return { ok: true };
|
|
73974
|
-
} catch (err) {
|
|
73975
|
-
captureException(err, { action: "start_agent", agent: name });
|
|
73976
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73977
|
-
}
|
|
74254
|
+
function startStopNeedsOperatorMessage(verb) {
|
|
74255
|
+
return `${verb === "start" ? "Start" : "Stop"} from the dashboard needs operator approval (a follow-up). ` + `For now run on the host: \`switchroom agent ${verb} ${"<name>"}\`.`;
|
|
73978
74256
|
}
|
|
73979
|
-
|
|
73980
|
-
|
|
73981
|
-
|
|
73982
|
-
|
|
73983
|
-
|
|
73984
|
-
|
|
73985
|
-
|
|
73986
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73987
|
-
}
|
|
74257
|
+
var START_NEEDS_OPERATOR_MESSAGE = startStopNeedsOperatorMessage("start");
|
|
74258
|
+
var STOP_NEEDS_OPERATOR_MESSAGE = startStopNeedsOperatorMessage("stop");
|
|
74259
|
+
async function handleStartAgent(_name) {
|
|
74260
|
+
return { ok: false, error: START_NEEDS_OPERATOR_MESSAGE };
|
|
74261
|
+
}
|
|
74262
|
+
async function handleStopAgent(_name) {
|
|
74263
|
+
return { ok: false, error: STOP_NEEDS_OPERATOR_MESSAGE };
|
|
73988
74264
|
}
|
|
73989
|
-
function handleRestartAgent(name) {
|
|
74265
|
+
async function handleRestartAgent(name, deps = {}) {
|
|
74266
|
+
const restart = deps.restart ?? restartAgentViaHostd;
|
|
73990
74267
|
try {
|
|
73991
|
-
|
|
73992
|
-
|
|
73993
|
-
|
|
74268
|
+
const result = await restart(name);
|
|
74269
|
+
if (result.ok) {
|
|
74270
|
+
captureEvent("agent_restarted", { agent: name, source: "web_api" });
|
|
74271
|
+
return { ok: true };
|
|
74272
|
+
}
|
|
74273
|
+
return { ok: false, error: result.error };
|
|
73994
74274
|
} catch (err) {
|
|
73995
74275
|
captureException(err, { action: "restart_agent", agent: name });
|
|
73996
74276
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73997
74277
|
}
|
|
73998
74278
|
}
|
|
73999
|
-
function handleGetLogs(name, lines = 50) {
|
|
74000
|
-
const
|
|
74001
|
-
|
|
74002
|
-
return { ok: false, error: res.error.message };
|
|
74003
|
-
}
|
|
74004
|
-
if (res.status !== 0) {
|
|
74005
|
-
const stderr = (res.stderr ?? "").trim();
|
|
74006
|
-
return {
|
|
74007
|
-
ok: false,
|
|
74008
|
-
error: stderr || `docker logs exited ${res.status ?? "non-zero"}`
|
|
74009
|
-
};
|
|
74010
|
-
}
|
|
74011
|
-
return { ok: true, logs: `${res.stdout ?? ""}${res.stderr ?? ""}` };
|
|
74279
|
+
async function handleGetLogs(name, lines = 50, deps = {}) {
|
|
74280
|
+
const fetchLogs = deps.fetchLogs ?? fetchAgentLogsViaHostd;
|
|
74281
|
+
return await fetchLogs(name, lines);
|
|
74012
74282
|
}
|
|
74013
74283
|
function handleGetTurns(config, agentName, limit) {
|
|
74014
74284
|
try {
|
|
@@ -74052,6 +74322,31 @@ var quotaCache = new Map;
|
|
|
74052
74322
|
function quotaEntryFresh(e, now) {
|
|
74053
74323
|
return !!e && now - e.fetchedAt < QUOTA_CACHE_TTL_MS;
|
|
74054
74324
|
}
|
|
74325
|
+
function isoToMs(iso) {
|
|
74326
|
+
if (!iso)
|
|
74327
|
+
return null;
|
|
74328
|
+
const ms = Date.parse(iso);
|
|
74329
|
+
return Number.isNaN(ms) ? null : ms;
|
|
74330
|
+
}
|
|
74331
|
+
function deriveAccountQuotaView(brokerState, probe, now) {
|
|
74332
|
+
const probeFresh = !!probe && now - probe.fetchedAt < QUOTA_CACHE_TTL_MS;
|
|
74333
|
+
let quotaUsage = probeFresh ? probe.usage ?? null : null;
|
|
74334
|
+
let quotaUsageSource = quotaUsage ? "probe" : null;
|
|
74335
|
+
let quotaStale = !probeFresh;
|
|
74336
|
+
if (!quotaUsage && brokerState?.last_quota) {
|
|
74337
|
+
const lq = brokerState.last_quota;
|
|
74338
|
+
quotaUsage = {
|
|
74339
|
+
fiveHourPct: lq.fiveHourUtilizationPct,
|
|
74340
|
+
sevenDayPct: lq.sevenDayUtilizationPct,
|
|
74341
|
+
fiveHourResetAt: isoToMs(lq.fiveHourResetAt),
|
|
74342
|
+
sevenDayResetAt: isoToMs(lq.sevenDayResetAt),
|
|
74343
|
+
capturedAt: lq.capturedAt
|
|
74344
|
+
};
|
|
74345
|
+
quotaUsageSource = "broker-cache";
|
|
74346
|
+
quotaStale = now - lq.capturedAt >= QUOTA_CACHE_TTL_MS;
|
|
74347
|
+
}
|
|
74348
|
+
return { quotaUsage, quotaUsageSource, quotaStale };
|
|
74349
|
+
}
|
|
74055
74350
|
async function handleGetAccounts(config, home2) {
|
|
74056
74351
|
const infos = getAccountInfos(Date.now(), home2);
|
|
74057
74352
|
const brokerAccounts = new Map;
|
|
@@ -74078,12 +74373,15 @@ async function handleGetAccounts(config, home2) {
|
|
|
74078
74373
|
usedBy.sort();
|
|
74079
74374
|
}
|
|
74080
74375
|
const now = Date.now();
|
|
74081
|
-
const
|
|
74376
|
+
const brokerState = brokerAccounts.get(info.label) ?? null;
|
|
74377
|
+
const view = deriveAccountQuotaView(brokerState, quotaCache.get(info.label), now);
|
|
74082
74378
|
return {
|
|
74083
74379
|
...info,
|
|
74084
|
-
quota:
|
|
74085
|
-
|
|
74086
|
-
|
|
74380
|
+
health: brokerState?.exhausted ? "quota-exhausted" : info.health,
|
|
74381
|
+
quota: brokerState,
|
|
74382
|
+
quotaUsage: view.quotaUsage,
|
|
74383
|
+
quotaUsageSource: view.quotaUsageSource,
|
|
74384
|
+
quotaStale: view.quotaStale,
|
|
74087
74385
|
usedBy
|
|
74088
74386
|
};
|
|
74089
74387
|
});
|
|
@@ -74206,6 +74504,12 @@ function inspectEnv(container, keys) {
|
|
|
74206
74504
|
} catch {}
|
|
74207
74505
|
return out;
|
|
74208
74506
|
}
|
|
74507
|
+
var HOSTD_NOISE_OPS = new Set([
|
|
74508
|
+
"agent_smoke",
|
|
74509
|
+
"get_status",
|
|
74510
|
+
"agent_status",
|
|
74511
|
+
"agent_logs"
|
|
74512
|
+
]);
|
|
74209
74513
|
async function handleGetSystemHealth(config, home2) {
|
|
74210
74514
|
const broker = { reachable: false };
|
|
74211
74515
|
try {
|
|
@@ -74257,7 +74561,7 @@ async function handleGetSystemHealth(config, home2) {
|
|
|
74257
74561
|
if (existsSync47(logPath)) {
|
|
74258
74562
|
hostd.auditLogPresent = true;
|
|
74259
74563
|
const raw = readFileSync43(logPath, "utf-8");
|
|
74260
|
-
hostd.recent = readAndFilter(raw, {}, 10);
|
|
74564
|
+
hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
|
|
74261
74565
|
}
|
|
74262
74566
|
} catch (err) {
|
|
74263
74567
|
hostd.error = err instanceof Error ? err.message : String(err);
|
|
@@ -74278,8 +74582,10 @@ async function handleGetGoogleAccounts(config) {
|
|
|
74278
74582
|
}
|
|
74279
74583
|
});
|
|
74280
74584
|
} catch (err) {
|
|
74281
|
-
if (!(err instanceof AuthBrokerUnreachableError))
|
|
74282
|
-
|
|
74585
|
+
if (!(err instanceof AuthBrokerUnreachableError)) {
|
|
74586
|
+
process.stderr.write(`dashboard: live broker error reading workspace accounts ` + `(${err instanceof AuthBrokerError ? err.code : "unknown"}); ` + `showing config-only ACL: ${err instanceof Error ? err.message : String(err)}
|
|
74587
|
+
`);
|
|
74588
|
+
}
|
|
74283
74589
|
}
|
|
74284
74590
|
const cfgAccounts = config.google_accounts ?? {};
|
|
74285
74591
|
const keys = new Set([
|
|
@@ -74316,8 +74622,10 @@ async function handleGetMicrosoftAccounts(config) {
|
|
|
74316
74622
|
}
|
|
74317
74623
|
});
|
|
74318
74624
|
} catch (err) {
|
|
74319
|
-
if (!(err instanceof AuthBrokerUnreachableError))
|
|
74320
|
-
|
|
74625
|
+
if (!(err instanceof AuthBrokerUnreachableError)) {
|
|
74626
|
+
process.stderr.write(`dashboard: live broker error reading workspace accounts ` + `(${err instanceof AuthBrokerError ? err.code : "unknown"}); ` + `showing config-only ACL: ${err instanceof Error ? err.message : String(err)}
|
|
74627
|
+
`);
|
|
74628
|
+
}
|
|
74321
74629
|
}
|
|
74322
74630
|
const cfgAccounts = config.microsoft_accounts ?? {};
|
|
74323
74631
|
const keys = new Set([
|
|
@@ -74522,7 +74830,16 @@ function handleSetConnectionAccess(configPath, config, args, deps = {}) {
|
|
|
74522
74830
|
});
|
|
74523
74831
|
return { ok: true, changed: true, requestId, pendingApproval: true };
|
|
74524
74832
|
}
|
|
74525
|
-
function handleGetSchedule(config) {
|
|
74833
|
+
async function handleGetSchedule(config, deps = {}) {
|
|
74834
|
+
const fetchSchedule = deps.fetchSchedule ?? fetchScheduleViaHostd;
|
|
74835
|
+
const viaHostd = await fetchSchedule();
|
|
74836
|
+
if (viaHostd) {
|
|
74837
|
+
return {
|
|
74838
|
+
entries: viaHostd.entries,
|
|
74839
|
+
recentByAgent: viaHostd.recentByAgent,
|
|
74840
|
+
...viaHostd.truncated ? { truncated: true } : {}
|
|
74841
|
+
};
|
|
74842
|
+
}
|
|
74526
74843
|
const entries = collectScheduleEntries(config);
|
|
74527
74844
|
const agentsDir = resolveAgentsDir(config);
|
|
74528
74845
|
const recentByAgent = {};
|
|
@@ -74532,7 +74849,12 @@ function handleGetSchedule(config) {
|
|
|
74532
74849
|
if (rows.length > 0)
|
|
74533
74850
|
recentByAgent[agent] = rows.slice(-10);
|
|
74534
74851
|
}
|
|
74535
|
-
return {
|
|
74852
|
+
return {
|
|
74853
|
+
entries,
|
|
74854
|
+
recentByAgent,
|
|
74855
|
+
degraded: true,
|
|
74856
|
+
reason: "schedule view needs hostd \u2014 showing base-config only " + "(agent-authored schedule.d crons are not included). " + "Ensure host_control is enabled."
|
|
74857
|
+
};
|
|
74536
74858
|
}
|
|
74537
74859
|
async function handleGetApprovals() {
|
|
74538
74860
|
const opSock = resolveKernelOperatorSocket();
|
|
@@ -74554,6 +74876,48 @@ async function handleGetApprovals() {
|
|
|
74554
74876
|
const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
|
|
74555
74877
|
return { reachable: true, decisions: sorted };
|
|
74556
74878
|
}
|
|
74879
|
+
function brokerOperatorSocketPath(home2 = process.env.HOME || homedir23()) {
|
|
74880
|
+
return join44(home2, ".switchroom", "broker-operator", "sock");
|
|
74881
|
+
}
|
|
74882
|
+
function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir23()) {
|
|
74883
|
+
const p = brokerOperatorSocketPath(home2);
|
|
74884
|
+
return existsSync47(p) ? p : null;
|
|
74885
|
+
}
|
|
74886
|
+
async function handleGetGrants(deps = {}) {
|
|
74887
|
+
const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
|
|
74888
|
+
if (socket == null) {
|
|
74889
|
+
return {
|
|
74890
|
+
reachable: false,
|
|
74891
|
+
grants: [],
|
|
74892
|
+
error: "vault-broker operator socket not present \u2014 host-side grant " + "listing needs a broker-operator bind (re-apply to (re)create it)."
|
|
74893
|
+
};
|
|
74894
|
+
}
|
|
74895
|
+
const list = deps.list ?? listGrantsViaBroker;
|
|
74896
|
+
let result;
|
|
74897
|
+
try {
|
|
74898
|
+
result = await list(undefined, { socket });
|
|
74899
|
+
} catch (err) {
|
|
74900
|
+
return {
|
|
74901
|
+
reachable: false,
|
|
74902
|
+
grants: [],
|
|
74903
|
+
error: err instanceof Error ? err.message : String(err)
|
|
74904
|
+
};
|
|
74905
|
+
}
|
|
74906
|
+
if (result.kind !== "ok") {
|
|
74907
|
+
return { reachable: false, grants: [], error: result.msg };
|
|
74908
|
+
}
|
|
74909
|
+
const rows = result.grants.map((g) => ({
|
|
74910
|
+
id: g.id,
|
|
74911
|
+
agent: g.agent_slug,
|
|
74912
|
+
keys: g.key_allow,
|
|
74913
|
+
writeKeys: g.write_allow,
|
|
74914
|
+
expiresAt: g.expires_at == null ? null : g.expires_at * 1000,
|
|
74915
|
+
createdAt: g.created_at * 1000,
|
|
74916
|
+
description: g.description
|
|
74917
|
+
}));
|
|
74918
|
+
rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
74919
|
+
return { reachable: true, grants: rows };
|
|
74920
|
+
}
|
|
74557
74921
|
async function handleGetMemoryHealth(config, opts) {
|
|
74558
74922
|
const url = config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
|
|
74559
74923
|
const now = opts?.now ?? new Date;
|
|
@@ -74575,6 +74939,8 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74575
74939
|
const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
|
|
74576
74940
|
const stale = staleMentalModels(h.mentalModels, 7, now);
|
|
74577
74941
|
const corrupted = corruptedMentalModels(h.mentalModels);
|
|
74942
|
+
const newestAge = ageDays(h.newestDocumentAt, now);
|
|
74943
|
+
const pendingStuck = h.pendingOperations > 0 && newestAge !== null && newestAge > 1;
|
|
74578
74944
|
let status = "ok";
|
|
74579
74945
|
let statusDetail = "facts flowing";
|
|
74580
74946
|
if (!h.ok) {
|
|
@@ -74586,6 +74952,9 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74586
74952
|
} else if (gaps.length > 0) {
|
|
74587
74953
|
status = "fail";
|
|
74588
74954
|
statusDetail = `${gaps.length} recent conversation(s) stored with zero extracted facts \u2014 invisible to recall`;
|
|
74955
|
+
} else if (pendingStuck) {
|
|
74956
|
+
status = "warn";
|
|
74957
|
+
statusDetail = `${h.pendingOperations} memory operation(s) pending with no new documents for ${Math.round(newestAge)}d \u2014 pipeline may be stuck`;
|
|
74589
74958
|
} else if (stale.length > 0) {
|
|
74590
74959
|
status = "warn";
|
|
74591
74960
|
statusDetail = `${stale.length} mental model(s) not refreshed in >7d`;
|
|
@@ -74604,6 +74973,7 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74604
74973
|
newestDocumentAt: h.newestDocumentAt,
|
|
74605
74974
|
recentUnextractedCount: gaps.length,
|
|
74606
74975
|
oldestUnextractedAt: gaps[0]?.createdAt ?? null,
|
|
74976
|
+
unextractedDocIds: gaps.slice(0, 20).map((d) => d.id),
|
|
74607
74977
|
mentalModels: h.mentalModels,
|
|
74608
74978
|
staleMentalModelCount: stale.length,
|
|
74609
74979
|
corruptedMentalModelNames: corrupted.map((m) => m.name),
|
|
@@ -74614,11 +74984,236 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74614
74984
|
rows.sort((a, b) => a.bank.localeCompare(b.bank));
|
|
74615
74985
|
return { reachable, url, banks: rows };
|
|
74616
74986
|
}
|
|
74987
|
+
function resolveMemoryUrl(config) {
|
|
74988
|
+
return config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
|
|
74989
|
+
}
|
|
74990
|
+
function isKnownBank(config, bank) {
|
|
74991
|
+
for (const agentName of Object.keys(config.agents)) {
|
|
74992
|
+
if (getCollectionForAgent(agentName, config) === bank)
|
|
74993
|
+
return true;
|
|
74994
|
+
}
|
|
74995
|
+
return false;
|
|
74996
|
+
}
|
|
74997
|
+
async function handleMemoryReprocess(config, body, deps) {
|
|
74998
|
+
const bank = typeof body.bank === "string" ? body.bank : "";
|
|
74999
|
+
if (!bank)
|
|
75000
|
+
return { ok: false, error: "Body must include `bank` (string)." };
|
|
75001
|
+
if (!isKnownBank(config, bank))
|
|
75002
|
+
return { ok: false, error: `Unknown bank: ${bank}` };
|
|
75003
|
+
const url = resolveMemoryUrl(config);
|
|
75004
|
+
const inspect = deps?.inspect ?? inspectBankHealth;
|
|
75005
|
+
const trigger = deps?.trigger ?? reprocessDocuments;
|
|
75006
|
+
const now = deps?.now ?? new Date;
|
|
75007
|
+
try {
|
|
75008
|
+
const h = await inspect(url, bank, { fetchImpl: deps?.fetchImpl });
|
|
75009
|
+
if (!h.ok)
|
|
75010
|
+
return { ok: false, error: `inspection failed: ${h.reason ?? "unknown"}` };
|
|
75011
|
+
const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
|
|
75012
|
+
const docIds = gaps.slice(0, 20).map((d) => d.id);
|
|
75013
|
+
if (docIds.length === 0)
|
|
75014
|
+
return { ok: true, triggered: 0, failed: 0 };
|
|
75015
|
+
const result = await trigger(restBaseFromMcpUrl(url), bank, docIds, {
|
|
75016
|
+
fetchImpl: deps?.fetchImpl
|
|
75017
|
+
});
|
|
75018
|
+
return {
|
|
75019
|
+
ok: result.failed === 0,
|
|
75020
|
+
triggered: result.triggered,
|
|
75021
|
+
failed: result.failed,
|
|
75022
|
+
...result.error ? { error: result.error } : {}
|
|
75023
|
+
};
|
|
75024
|
+
} catch (err) {
|
|
75025
|
+
return { ok: false, error: String(err.message ?? err) };
|
|
75026
|
+
}
|
|
75027
|
+
}
|
|
75028
|
+
async function handleMemoryRefreshModel(config, body, deps) {
|
|
75029
|
+
const bank = typeof body.bank === "string" ? body.bank : "";
|
|
75030
|
+
const modelId = typeof body.modelId === "string" ? body.modelId : "";
|
|
75031
|
+
if (!bank)
|
|
75032
|
+
return { ok: false, error: "Body must include `bank` (string)." };
|
|
75033
|
+
if (!modelId)
|
|
75034
|
+
return { ok: false, error: "Body must include `modelId` (string)." };
|
|
75035
|
+
if (!isKnownBank(config, bank))
|
|
75036
|
+
return { ok: false, error: `Unknown bank: ${bank}` };
|
|
75037
|
+
const url = resolveMemoryUrl(config);
|
|
75038
|
+
const inspect = deps?.inspect ?? inspectBankHealth;
|
|
75039
|
+
const trigger = deps?.trigger ?? refreshMentalModel;
|
|
75040
|
+
try {
|
|
75041
|
+
const h = await inspect(url, bank, { fetchImpl: deps?.fetchImpl });
|
|
75042
|
+
if (!h.ok)
|
|
75043
|
+
return { ok: false, error: `inspection failed: ${h.reason ?? "unknown"}` };
|
|
75044
|
+
if (!h.mentalModels.some((m) => m.id === modelId)) {
|
|
75045
|
+
return { ok: false, error: `Unknown mental model for bank ${bank}` };
|
|
75046
|
+
}
|
|
75047
|
+
return await trigger(restBaseFromMcpUrl(url), bank, modelId, {
|
|
75048
|
+
fetchImpl: deps?.fetchImpl
|
|
75049
|
+
});
|
|
75050
|
+
} catch (err) {
|
|
75051
|
+
return { ok: false, error: String(err.message ?? err) };
|
|
75052
|
+
}
|
|
75053
|
+
}
|
|
75054
|
+
async function handleMemoryBuildProfile(config, body, deps) {
|
|
75055
|
+
const bank = typeof body.bank === "string" ? body.bank : "";
|
|
75056
|
+
if (!bank)
|
|
75057
|
+
return { ok: false, error: "Body must include `bank` (string)." };
|
|
75058
|
+
if (!isKnownBank(config, bank))
|
|
75059
|
+
return { ok: false, error: `Unknown bank: ${bank}` };
|
|
75060
|
+
const url = resolveMemoryUrl(config);
|
|
75061
|
+
const trigger = deps?.trigger ?? buildUserProfile;
|
|
75062
|
+
try {
|
|
75063
|
+
return await trigger(url, bank, { fetchImpl: deps?.fetchImpl });
|
|
75064
|
+
} catch (err) {
|
|
75065
|
+
return { ok: false, error: String(err.message ?? err) };
|
|
75066
|
+
}
|
|
75067
|
+
}
|
|
75068
|
+
var ATTENTION_SEVERITY_RANK = {
|
|
75069
|
+
critical: 0,
|
|
75070
|
+
warn: 1,
|
|
75071
|
+
info: 2
|
|
75072
|
+
};
|
|
75073
|
+
var ATTENTION_MAX_ITEMS = 12;
|
|
75074
|
+
function deriveAttention(parts, now) {
|
|
75075
|
+
const items = [];
|
|
75076
|
+
const mem = parts.memoryHealth;
|
|
75077
|
+
if (mem) {
|
|
75078
|
+
if (!mem.reachable) {
|
|
75079
|
+
items.push({
|
|
75080
|
+
severity: "critical",
|
|
75081
|
+
title: "Hindsight unreachable",
|
|
75082
|
+
detail: "Memory recall and extraction are down \u2014 agents are amnesiac.",
|
|
75083
|
+
tab: "memory"
|
|
75084
|
+
});
|
|
75085
|
+
}
|
|
75086
|
+
for (const bank of mem.banks ?? []) {
|
|
75087
|
+
for (const name of bank.corruptedMentalModelNames ?? []) {
|
|
75088
|
+
items.push({
|
|
75089
|
+
severity: "critical",
|
|
75090
|
+
title: `Mental model corrupted: ${name}`,
|
|
75091
|
+
detail: `Bank ${bank.bank} \u2014 an LLM-failure message is injected into every turn until refreshed.`,
|
|
75092
|
+
tab: "memory"
|
|
75093
|
+
});
|
|
75094
|
+
}
|
|
75095
|
+
if (bank.recentUnextractedCount > 0) {
|
|
75096
|
+
items.push({
|
|
75097
|
+
severity: "critical",
|
|
75098
|
+
title: `${bank.recentUnextractedCount} unextracted conversation(s)`,
|
|
75099
|
+
detail: `Bank ${bank.bank} \u2014 stored with zero extracted facts, invisible to recall.`,
|
|
75100
|
+
tab: "memory"
|
|
75101
|
+
});
|
|
75102
|
+
}
|
|
75103
|
+
if (bank.staleMentalModelCount > 0) {
|
|
75104
|
+
items.push({
|
|
75105
|
+
severity: "warn",
|
|
75106
|
+
title: `${bank.staleMentalModelCount} stale mental model(s)`,
|
|
75107
|
+
detail: `Bank ${bank.bank} \u2014 not refreshed in >7d.`,
|
|
75108
|
+
tab: "memory"
|
|
75109
|
+
});
|
|
75110
|
+
}
|
|
75111
|
+
}
|
|
75112
|
+
}
|
|
75113
|
+
for (const acct of parts.accounts ?? []) {
|
|
75114
|
+
if (acct?.quota?.exhausted) {
|
|
75115
|
+
items.push({
|
|
75116
|
+
severity: "warn",
|
|
75117
|
+
title: `Account ${acct.label} quota exhausted`,
|
|
75118
|
+
detail: acct.quota.exhausted_until ? `Resets around ${new Date(acct.quota.exhausted_until).toISOString()}.` : undefined,
|
|
75119
|
+
tab: "accounts"
|
|
75120
|
+
});
|
|
75121
|
+
}
|
|
75122
|
+
}
|
|
75123
|
+
for (const [agent, fires] of Object.entries(parts.schedule?.recentByAgent ?? {})) {
|
|
75124
|
+
if ((fires ?? []).some((f) => f && f.exitCode !== 0)) {
|
|
75125
|
+
items.push({
|
|
75126
|
+
severity: "warn",
|
|
75127
|
+
title: `Cron ${agent} last fire failed`,
|
|
75128
|
+
detail: "A recent scheduled fire returned a non-zero exit code.",
|
|
75129
|
+
tab: "schedule"
|
|
75130
|
+
});
|
|
75131
|
+
}
|
|
75132
|
+
}
|
|
75133
|
+
const sys = parts.systemHealth;
|
|
75134
|
+
if (sys) {
|
|
75135
|
+
if (sys.broker?.reachable === false) {
|
|
75136
|
+
items.push({
|
|
75137
|
+
severity: "critical",
|
|
75138
|
+
title: "Auth-broker unreachable",
|
|
75139
|
+
detail: sys.broker.error,
|
|
75140
|
+
tab: "system"
|
|
75141
|
+
});
|
|
75142
|
+
}
|
|
75143
|
+
if (sys.hindsight?.running === false) {
|
|
75144
|
+
items.push({
|
|
75145
|
+
severity: "critical",
|
|
75146
|
+
title: "Hindsight not running",
|
|
75147
|
+
detail: "The memory backend container isn't serving.",
|
|
75148
|
+
tab: "system"
|
|
75149
|
+
});
|
|
75150
|
+
}
|
|
75151
|
+
if (sys.hostd?.error) {
|
|
75152
|
+
items.push({
|
|
75153
|
+
severity: "critical",
|
|
75154
|
+
title: "Hostd error",
|
|
75155
|
+
detail: sys.hostd.error,
|
|
75156
|
+
tab: "system"
|
|
75157
|
+
});
|
|
75158
|
+
}
|
|
75159
|
+
}
|
|
75160
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
75161
|
+
for (const agent of parts.agents ?? []) {
|
|
75162
|
+
const exp = agent?.auth?.expiresAt;
|
|
75163
|
+
if (typeof exp === "number" && exp - now > 0 && exp - now < DAY_MS) {
|
|
75164
|
+
items.push({
|
|
75165
|
+
severity: "warn",
|
|
75166
|
+
title: `${agent.name} token expires soon`,
|
|
75167
|
+
detail: "OAuth token expires within 24h \u2014 re-auth before it lapses.",
|
|
75168
|
+
tab: "agents"
|
|
75169
|
+
});
|
|
75170
|
+
}
|
|
75171
|
+
}
|
|
75172
|
+
items.sort((a, b) => ATTENTION_SEVERITY_RANK[a.severity] - ATTENTION_SEVERITY_RANK[b.severity]);
|
|
75173
|
+
return items.slice(0, ATTENTION_MAX_ITEMS);
|
|
75174
|
+
}
|
|
75175
|
+
async function handleGetSummary(deps, now = Date.now()) {
|
|
75176
|
+
const safe = async (p) => {
|
|
75177
|
+
try {
|
|
75178
|
+
const r = await p();
|
|
75179
|
+
return { value: r.value, dataAsOf: r.dataAsOf };
|
|
75180
|
+
} catch {
|
|
75181
|
+
return { value: null, dataAsOf: null };
|
|
75182
|
+
}
|
|
75183
|
+
};
|
|
75184
|
+
const [agents, systemHealth, approvals, schedule, accounts, memoryHealth] = await Promise.all([
|
|
75185
|
+
safe(deps.agents),
|
|
75186
|
+
safe(deps.systemHealth),
|
|
75187
|
+
safe(deps.approvals),
|
|
75188
|
+
safe(deps.schedule),
|
|
75189
|
+
safe(deps.accounts),
|
|
75190
|
+
safe(deps.memoryHealth)
|
|
75191
|
+
]);
|
|
75192
|
+
const stamps = [agents, systemHealth, approvals, schedule, accounts, memoryHealth].map((p) => p.dataAsOf).filter((d) => typeof d === "number");
|
|
75193
|
+
const dataAsOf = stamps.length > 0 ? Math.min(...stamps) : 0;
|
|
75194
|
+
const attention = deriveAttention({
|
|
75195
|
+
memoryHealth: memoryHealth.value,
|
|
75196
|
+
accounts: accounts.value,
|
|
75197
|
+
schedule: schedule.value,
|
|
75198
|
+
systemHealth: systemHealth.value,
|
|
75199
|
+
agents: agents.value
|
|
75200
|
+
}, now);
|
|
75201
|
+
return {
|
|
75202
|
+
agents: agents.value,
|
|
75203
|
+
systemHealth: systemHealth.value,
|
|
75204
|
+
approvals: approvals.value,
|
|
75205
|
+
schedule: schedule.value,
|
|
75206
|
+
accounts: accounts.value,
|
|
75207
|
+
memoryHealth: memoryHealth.value,
|
|
75208
|
+
attention,
|
|
75209
|
+
dataAsOf
|
|
75210
|
+
};
|
|
75211
|
+
}
|
|
74617
75212
|
|
|
74618
75213
|
// src/web/webhook-handler.ts
|
|
74619
75214
|
import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as readFileSync45, writeFileSync as writeFileSync26 } from "fs";
|
|
74620
|
-
import { join as
|
|
74621
|
-
import { homedir as
|
|
75215
|
+
import { join as join46 } from "path";
|
|
75216
|
+
import { homedir as homedir25 } from "os";
|
|
74622
75217
|
|
|
74623
75218
|
// src/web/webhook-verify.ts
|
|
74624
75219
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -74824,12 +75419,12 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
74824
75419
|
|
|
74825
75420
|
// src/web/webhook-edge.ts
|
|
74826
75421
|
import { existsSync as existsSync48, readFileSync as readFileSync44 } from "fs";
|
|
74827
|
-
import { join as
|
|
74828
|
-
import { homedir as
|
|
75422
|
+
import { join as join45 } from "path";
|
|
75423
|
+
import { homedir as homedir24 } from "os";
|
|
74829
75424
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
74830
75425
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
74831
75426
|
function edgeSecretPath() {
|
|
74832
|
-
return
|
|
75427
|
+
return join45(homedir24(), ".switchroom", "webhook-edge-secret");
|
|
74833
75428
|
}
|
|
74834
75429
|
function loadEdgeSecret(path4) {
|
|
74835
75430
|
const p = path4 ?? edgeSecretPath();
|
|
@@ -74892,8 +75487,8 @@ var agentDedupCache = new Map;
|
|
|
74892
75487
|
function createFileDedupStore(resolveAgentDir) {
|
|
74893
75488
|
return {
|
|
74894
75489
|
check(agent, deliveryId, now) {
|
|
74895
|
-
const telegramDir =
|
|
74896
|
-
const filePath =
|
|
75490
|
+
const telegramDir = join46(resolveAgentDir(agent), "telegram");
|
|
75491
|
+
const filePath = join46(telegramDir, "webhook-dedup.json");
|
|
74897
75492
|
if (!agentDedupCache.has(agent)) {
|
|
74898
75493
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
74899
75494
|
}
|
|
@@ -74945,7 +75540,7 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
|
|
|
74945
75540
|
return true;
|
|
74946
75541
|
}
|
|
74947
75542
|
function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
74948
|
-
const issuesPath =
|
|
75543
|
+
const issuesPath = join46(telegramDir, "issues.jsonl");
|
|
74949
75544
|
try {
|
|
74950
75545
|
mkdirSync28(telegramDir, { recursive: true });
|
|
74951
75546
|
const record = {
|
|
@@ -74970,7 +75565,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
74970
75565
|
async function handleWebhookIngest(args, deps = {}) {
|
|
74971
75566
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
74972
75567
|
const now = (deps.now ?? Date.now)();
|
|
74973
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) =>
|
|
75568
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir25(), ".switchroom", "agents", a));
|
|
74974
75569
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
74975
75570
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
74976
75571
|
if (!args.agentExists) {
|
|
@@ -75037,7 +75632,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
75037
75632
|
if (retryAfter !== null) {
|
|
75038
75633
|
if (!args.viaGateway) {
|
|
75039
75634
|
const agentDir2 = resolveAgentDir(args.agent);
|
|
75040
|
-
const telegramDir2 =
|
|
75635
|
+
const telegramDir2 = join46(agentDir2, "telegram");
|
|
75041
75636
|
if (shouldWriteThrottleIssue(args.agent, source, now)) {
|
|
75042
75637
|
writeThrottleIssue(args.agent, source, now, telegramDir2, log);
|
|
75043
75638
|
}
|
|
@@ -75057,7 +75652,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
75057
75652
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
|
|
75058
75653
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
75059
75654
|
if (args.viaGateway) {
|
|
75060
|
-
const socketPath =
|
|
75655
|
+
const socketPath = join46(resolveAgentDir(args.agent), "telegram", "webhook.sock");
|
|
75061
75656
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
75062
75657
|
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
75063
75658
|
let resp;
|
|
@@ -75096,8 +75691,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
75096
75691
|
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
75097
75692
|
}
|
|
75098
75693
|
const agentDir = resolveAgentDir(args.agent);
|
|
75099
|
-
const telegramDir =
|
|
75100
|
-
const logPath =
|
|
75694
|
+
const telegramDir = join46(agentDir, "telegram");
|
|
75695
|
+
const logPath = join46(telegramDir, "webhook-events.jsonl");
|
|
75101
75696
|
try {
|
|
75102
75697
|
mkdirSync28(telegramDir, { recursive: true });
|
|
75103
75698
|
const record = {
|
|
@@ -75120,6 +75715,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
75120
75715
|
}
|
|
75121
75716
|
|
|
75122
75717
|
// src/web/server.ts
|
|
75718
|
+
var LOG_POLL_INTERVAL_MS = 3000;
|
|
75719
|
+
var LOG_POLL_TAIL_LINES = 400;
|
|
75123
75720
|
var MIME_TYPES = {
|
|
75124
75721
|
".html": "text/html",
|
|
75125
75722
|
".css": "text/css",
|
|
@@ -75150,8 +75747,8 @@ function resolveWebToken() {
|
|
|
75150
75747
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
75151
75748
|
if (fromEnv && fromEnv.length > 0)
|
|
75152
75749
|
return fromEnv;
|
|
75153
|
-
const home2 = process.env.HOME ??
|
|
75154
|
-
const tokenPath =
|
|
75750
|
+
const home2 = process.env.HOME ?? homedir26();
|
|
75751
|
+
const tokenPath = join47(home2, ".switchroom", "web-token");
|
|
75155
75752
|
if (existsSync50(tokenPath)) {
|
|
75156
75753
|
const existing = readFileSync46(tokenPath, "utf8").trim();
|
|
75157
75754
|
if (existing.length > 0)
|
|
@@ -75236,7 +75833,7 @@ function checkWsAuth(req, token, server) {
|
|
|
75236
75833
|
return presented !== null && constantTimeEqual(presented, token);
|
|
75237
75834
|
}
|
|
75238
75835
|
function loadWebhookSecrets() {
|
|
75239
|
-
const path4 =
|
|
75836
|
+
const path4 = join47(homedir26(), ".switchroom", "webhook-secrets.json");
|
|
75240
75837
|
if (!existsSync50(path4))
|
|
75241
75838
|
return {};
|
|
75242
75839
|
try {
|
|
@@ -75312,6 +75909,9 @@ function parseRoute(pathname, method) {
|
|
|
75312
75909
|
if (method === "GET" && pathname === "/api/agents") {
|
|
75313
75910
|
return { handler: "getAgents", params: {} };
|
|
75314
75911
|
}
|
|
75912
|
+
if (method === "GET" && pathname === "/api/summary") {
|
|
75913
|
+
return { handler: "getSummary", params: {} };
|
|
75914
|
+
}
|
|
75315
75915
|
const logsMatch = pathname.match(/^\/api\/agents\/([^/]+)\/logs$/);
|
|
75316
75916
|
if (method === "GET" && logsMatch) {
|
|
75317
75917
|
return { handler: "getLogs", params: { name: logsMatch[1] } };
|
|
@@ -75342,6 +75942,15 @@ function parseRoute(pathname, method) {
|
|
|
75342
75942
|
if (method === "GET" && pathname === "/api/memory-health") {
|
|
75343
75943
|
return { handler: "getMemoryHealth", params: {} };
|
|
75344
75944
|
}
|
|
75945
|
+
if (method === "POST" && pathname === "/api/memory/reprocess") {
|
|
75946
|
+
return { handler: "memoryReprocess", params: {} };
|
|
75947
|
+
}
|
|
75948
|
+
if (method === "POST" && pathname === "/api/memory/refresh-model") {
|
|
75949
|
+
return { handler: "memoryRefreshModel", params: {} };
|
|
75950
|
+
}
|
|
75951
|
+
if (method === "POST" && pathname === "/api/memory/build-profile") {
|
|
75952
|
+
return { handler: "memoryBuildProfile", params: {} };
|
|
75953
|
+
}
|
|
75345
75954
|
if (method === "GET" && pathname === "/api/google-accounts") {
|
|
75346
75955
|
return { handler: "getGoogleAccounts", params: {} };
|
|
75347
75956
|
}
|
|
@@ -75357,6 +75966,9 @@ function parseRoute(pathname, method) {
|
|
|
75357
75966
|
if (method === "GET" && pathname === "/api/approvals") {
|
|
75358
75967
|
return { handler: "getApprovals", params: {} };
|
|
75359
75968
|
}
|
|
75969
|
+
if (method === "GET" && pathname === "/api/grants") {
|
|
75970
|
+
return { handler: "getGrants", params: {} };
|
|
75971
|
+
}
|
|
75360
75972
|
if (method === "GET" && pathname === "/api/accounts") {
|
|
75361
75973
|
return { handler: "getAccounts", params: {} };
|
|
75362
75974
|
}
|
|
@@ -75409,6 +76021,14 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75409
76021
|
return config;
|
|
75410
76022
|
}
|
|
75411
76023
|
};
|
|
76024
|
+
const cachedAgents = () => dashboardCache.get("agents", DASHBOARD_CACHE_TTL.agents, () => handleGetAgents(freshConfig()));
|
|
76025
|
+
const cachedSystemHealth = () => dashboardCache.get("system-health", DASHBOARD_CACHE_TTL["system-health"], () => handleGetSystemHealth(freshConfig()));
|
|
76026
|
+
const cachedMemoryHealth = () => dashboardCache.get("memory-health", DASHBOARD_CACHE_TTL["memory-health"], () => handleGetMemoryHealth(freshConfig()));
|
|
76027
|
+
const cachedSchedule = () => dashboardCache.get("schedule", DASHBOARD_CACHE_TTL.schedule, () => handleGetSchedule(freshConfig()));
|
|
76028
|
+
const cachedApprovals = () => dashboardCache.get("approvals", DASHBOARD_CACHE_TTL.approvals, () => handleGetApprovals());
|
|
76029
|
+
const cachedGrants = () => dashboardCache.get("grants", DASHBOARD_CACHE_TTL.grants, () => handleGetGrants());
|
|
76030
|
+
const cachedAccounts = () => dashboardCache.get("accounts", DASHBOARD_CACHE_TTL.accounts, () => handleGetAccounts(freshConfig()));
|
|
76031
|
+
const withStamp = (part) => ({ ...part.value, dataAsOf: part.dataAsOf, stale: part.stale });
|
|
75412
76032
|
const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
75413
76033
|
const server = Bun.serve({
|
|
75414
76034
|
port,
|
|
@@ -75445,7 +76065,16 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75445
76065
|
return authError;
|
|
75446
76066
|
switch (route.handler) {
|
|
75447
76067
|
case "getAgents":
|
|
75448
|
-
return jsonResponse(
|
|
76068
|
+
return (async () => jsonResponse((await cachedAgents()).value))();
|
|
76069
|
+
case "getSummary":
|
|
76070
|
+
return (async () => jsonResponse(await handleGetSummary({
|
|
76071
|
+
agents: cachedAgents,
|
|
76072
|
+
systemHealth: cachedSystemHealth,
|
|
76073
|
+
approvals: cachedApprovals,
|
|
76074
|
+
schedule: cachedSchedule,
|
|
76075
|
+
accounts: cachedAccounts,
|
|
76076
|
+
memoryHealth: cachedMemoryHealth
|
|
76077
|
+
})))();
|
|
75449
76078
|
case "getLogs": {
|
|
75450
76079
|
const agentName = route.params.name;
|
|
75451
76080
|
if (!config.agents[agentName]) {
|
|
@@ -75453,28 +76082,28 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75453
76082
|
}
|
|
75454
76083
|
const rawLines = Number(url.searchParams.get("lines") ?? "50");
|
|
75455
76084
|
const lines = Number.isInteger(rawLines) && rawLines >= 1 && rawLines <= 1e4 ? rawLines : 50;
|
|
75456
|
-
return jsonResponse(handleGetLogs(agentName, lines));
|
|
76085
|
+
return (async () => jsonResponse(await handleGetLogs(agentName, lines)))();
|
|
75457
76086
|
}
|
|
75458
76087
|
case "startAgent": {
|
|
75459
76088
|
const agentName = route.params.name;
|
|
75460
76089
|
if (!config.agents[agentName]) {
|
|
75461
76090
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75462
76091
|
}
|
|
75463
|
-
return jsonResponse(handleStartAgent(agentName));
|
|
76092
|
+
return (async () => jsonResponse(await handleStartAgent(agentName)))();
|
|
75464
76093
|
}
|
|
75465
76094
|
case "stopAgent": {
|
|
75466
76095
|
const agentName = route.params.name;
|
|
75467
76096
|
if (!config.agents[agentName]) {
|
|
75468
76097
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75469
76098
|
}
|
|
75470
|
-
return jsonResponse(handleStopAgent(agentName));
|
|
76099
|
+
return (async () => jsonResponse(await handleStopAgent(agentName)))();
|
|
75471
76100
|
}
|
|
75472
76101
|
case "restartAgent": {
|
|
75473
76102
|
const agentName = route.params.name;
|
|
75474
76103
|
if (!config.agents[agentName]) {
|
|
75475
76104
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75476
76105
|
}
|
|
75477
|
-
return jsonResponse(handleRestartAgent(agentName));
|
|
76106
|
+
return (async () => jsonResponse(await handleRestartAgent(agentName)))();
|
|
75478
76107
|
}
|
|
75479
76108
|
case "getTurns": {
|
|
75480
76109
|
const agentName = route.params.name;
|
|
@@ -75502,9 +76131,9 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75502
76131
|
return jsonResponse(result.subagents);
|
|
75503
76132
|
}
|
|
75504
76133
|
case "getSystemHealth":
|
|
75505
|
-
return (async () => jsonResponse(await
|
|
76134
|
+
return (async () => jsonResponse(withStamp(await cachedSystemHealth())))();
|
|
75506
76135
|
case "getMemoryHealth":
|
|
75507
|
-
return (async () => jsonResponse(await
|
|
76136
|
+
return (async () => jsonResponse(withStamp(await cachedMemoryHealth())))();
|
|
75508
76137
|
case "getGoogleAccounts":
|
|
75509
76138
|
return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
|
|
75510
76139
|
case "getMicrosoftAccounts":
|
|
@@ -75512,11 +76141,13 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75512
76141
|
case "getNotionWorkspace":
|
|
75513
76142
|
return jsonResponse(handleGetNotionWorkspace(freshConfig()));
|
|
75514
76143
|
case "getSchedule":
|
|
75515
|
-
return jsonResponse(
|
|
76144
|
+
return (async () => jsonResponse(withStamp(await cachedSchedule())))();
|
|
75516
76145
|
case "getApprovals":
|
|
75517
|
-
return (async () => jsonResponse(await
|
|
76146
|
+
return (async () => jsonResponse(withStamp(await cachedApprovals())))();
|
|
76147
|
+
case "getGrants":
|
|
76148
|
+
return (async () => jsonResponse(withStamp(await cachedGrants())))();
|
|
75518
76149
|
case "getAccounts":
|
|
75519
|
-
return (async () => jsonResponse(await
|
|
76150
|
+
return (async () => jsonResponse((await cachedAccounts()).value))();
|
|
75520
76151
|
case "useAccount": {
|
|
75521
76152
|
return (async () => {
|
|
75522
76153
|
let body;
|
|
@@ -75580,6 +76211,45 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75580
76211
|
return jsonResponse(result, result.ok ? 200 : 502);
|
|
75581
76212
|
})();
|
|
75582
76213
|
}
|
|
76214
|
+
case "memoryReprocess": {
|
|
76215
|
+
return (async () => {
|
|
76216
|
+
let body = {};
|
|
76217
|
+
try {
|
|
76218
|
+
const raw = await req.text();
|
|
76219
|
+
body = raw ? JSON.parse(raw) : {};
|
|
76220
|
+
} catch {
|
|
76221
|
+
return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
|
|
76222
|
+
}
|
|
76223
|
+
const result = await handleMemoryReprocess(freshConfig(), body);
|
|
76224
|
+
return jsonResponse(result, result.ok ? 200 : 400);
|
|
76225
|
+
})();
|
|
76226
|
+
}
|
|
76227
|
+
case "memoryRefreshModel": {
|
|
76228
|
+
return (async () => {
|
|
76229
|
+
let body = {};
|
|
76230
|
+
try {
|
|
76231
|
+
const raw = await req.text();
|
|
76232
|
+
body = raw ? JSON.parse(raw) : {};
|
|
76233
|
+
} catch {
|
|
76234
|
+
return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
|
|
76235
|
+
}
|
|
76236
|
+
const result = await handleMemoryRefreshModel(freshConfig(), body);
|
|
76237
|
+
return jsonResponse(result, result.ok ? 200 : 400);
|
|
76238
|
+
})();
|
|
76239
|
+
}
|
|
76240
|
+
case "memoryBuildProfile": {
|
|
76241
|
+
return (async () => {
|
|
76242
|
+
let body = {};
|
|
76243
|
+
try {
|
|
76244
|
+
const raw = await req.text();
|
|
76245
|
+
body = raw ? JSON.parse(raw) : {};
|
|
76246
|
+
} catch {
|
|
76247
|
+
return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
|
|
76248
|
+
}
|
|
76249
|
+
const result = await handleMemoryBuildProfile(freshConfig(), body);
|
|
76250
|
+
return jsonResponse(result, result.ok ? 200 : 400);
|
|
76251
|
+
})();
|
|
76252
|
+
}
|
|
75583
76253
|
case "getAgentAccounts": {
|
|
75584
76254
|
const agentName = route.params.name;
|
|
75585
76255
|
if (!config.agents[agentName]) {
|
|
@@ -75597,7 +76267,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75597
76267
|
}
|
|
75598
76268
|
}
|
|
75599
76269
|
let filePath = pathname === "/" ? "/index.html" : pathname;
|
|
75600
|
-
const fullPath =
|
|
76270
|
+
const fullPath = join47(uiDir, filePath);
|
|
75601
76271
|
if (!existsSync50(fullPath)) {
|
|
75602
76272
|
return new Response("Not Found", { status: 404 });
|
|
75603
76273
|
}
|
|
@@ -75621,64 +76291,79 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75621
76291
|
websocket: {
|
|
75622
76292
|
open(_ws) {},
|
|
75623
76293
|
close(ws) {
|
|
75624
|
-
const
|
|
75625
|
-
if (
|
|
75626
|
-
|
|
75627
|
-
ws.
|
|
76294
|
+
const interval = ws._logInterval;
|
|
76295
|
+
if (interval) {
|
|
76296
|
+
clearInterval(interval);
|
|
76297
|
+
ws._logInterval = null;
|
|
75628
76298
|
}
|
|
75629
76299
|
},
|
|
75630
76300
|
message(ws, message) {
|
|
76301
|
+
let data;
|
|
75631
76302
|
try {
|
|
75632
|
-
|
|
75633
|
-
|
|
75634
|
-
|
|
75635
|
-
|
|
75636
|
-
|
|
75637
|
-
|
|
75638
|
-
|
|
75639
|
-
|
|
75640
|
-
|
|
75641
|
-
|
|
75642
|
-
|
|
75643
|
-
|
|
75644
|
-
|
|
75645
|
-
|
|
75646
|
-
|
|
75647
|
-
|
|
76303
|
+
data = JSON.parse(String(message));
|
|
76304
|
+
} catch {
|
|
76305
|
+
return;
|
|
76306
|
+
}
|
|
76307
|
+
const clearLogInterval = () => {
|
|
76308
|
+
const existing = ws._logInterval;
|
|
76309
|
+
if (existing) {
|
|
76310
|
+
clearInterval(existing);
|
|
76311
|
+
ws._logInterval = null;
|
|
76312
|
+
}
|
|
76313
|
+
};
|
|
76314
|
+
if (data && data.type === "unsubscribe") {
|
|
76315
|
+
clearLogInterval();
|
|
76316
|
+
return;
|
|
76317
|
+
}
|
|
76318
|
+
if (data && data.type === "subscribe" && data.agent) {
|
|
76319
|
+
const agentName = String(data.agent).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
76320
|
+
if (!agentName || !config.agents[agentName]) {
|
|
76321
|
+
try {
|
|
76322
|
+
ws.send(JSON.stringify({ type: "error", error: "Unknown agent" }));
|
|
76323
|
+
} catch {}
|
|
76324
|
+
return;
|
|
76325
|
+
}
|
|
76326
|
+
clearLogInterval();
|
|
76327
|
+
const poll = async () => {
|
|
76328
|
+
let result;
|
|
76329
|
+
try {
|
|
76330
|
+
result = await fetchAgentLogsViaHostd(agentName, LOG_POLL_TAIL_LINES);
|
|
76331
|
+
} catch (err) {
|
|
75648
76332
|
try {
|
|
75649
76333
|
ws.send(JSON.stringify({
|
|
75650
76334
|
type: "log_error",
|
|
75651
76335
|
agent: agentName,
|
|
75652
|
-
data:
|
|
75653
|
-
`
|
|
76336
|
+
data: err instanceof Error ? err.message : String(err)
|
|
75654
76337
|
}));
|
|
75655
76338
|
} catch {}
|
|
75656
|
-
|
|
75657
|
-
|
|
76339
|
+
clearLogInterval();
|
|
76340
|
+
return;
|
|
76341
|
+
}
|
|
76342
|
+
if (result.ok) {
|
|
75658
76343
|
try {
|
|
75659
76344
|
ws.send(JSON.stringify({
|
|
75660
76345
|
type: "log",
|
|
75661
76346
|
agent: agentName,
|
|
75662
|
-
data:
|
|
76347
|
+
data: result.logs,
|
|
76348
|
+
replace: true
|
|
75663
76349
|
}));
|
|
75664
76350
|
} catch {
|
|
75665
|
-
|
|
76351
|
+
clearLogInterval();
|
|
75666
76352
|
}
|
|
75667
|
-
}
|
|
75668
|
-
child.stderr.on("data", (chunk) => {
|
|
76353
|
+
} else {
|
|
75669
76354
|
try {
|
|
75670
76355
|
ws.send(JSON.stringify({
|
|
75671
76356
|
type: "log_error",
|
|
75672
76357
|
agent: agentName,
|
|
75673
|
-
data:
|
|
76358
|
+
data: result.error
|
|
75674
76359
|
}));
|
|
75675
|
-
} catch {
|
|
75676
|
-
|
|
75677
|
-
|
|
75678
|
-
|
|
75679
|
-
|
|
75680
|
-
|
|
75681
|
-
}
|
|
76360
|
+
} catch {}
|
|
76361
|
+
clearLogInterval();
|
|
76362
|
+
}
|
|
76363
|
+
};
|
|
76364
|
+
poll();
|
|
76365
|
+
ws._logInterval = setInterval(() => void poll(), LOG_POLL_INTERVAL_MS);
|
|
76366
|
+
}
|
|
75682
76367
|
}
|
|
75683
76368
|
}
|
|
75684
76369
|
});
|
|
@@ -76641,8 +77326,8 @@ init_loader();
|
|
|
76641
77326
|
init_lifecycle();
|
|
76642
77327
|
import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26, chownSync as chownSync5 } from "node:fs";
|
|
76643
77328
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
76644
|
-
import { join as
|
|
76645
|
-
import { homedir as
|
|
77329
|
+
import { join as join61, dirname as dirname14, resolve as resolve34 } from "node:path";
|
|
77330
|
+
import { homedir as homedir37 } from "node:os";
|
|
76646
77331
|
|
|
76647
77332
|
// src/cli/release-yaml.ts
|
|
76648
77333
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
@@ -76681,13 +77366,13 @@ function defaultPersistPin(configPath) {
|
|
|
76681
77366
|
} catch {}
|
|
76682
77367
|
};
|
|
76683
77368
|
}
|
|
76684
|
-
var DEFAULT_COMPOSE_PATH =
|
|
77369
|
+
var DEFAULT_COMPOSE_PATH = join61(homedir37(), ".switchroom", "compose", "docker-compose.yml");
|
|
76685
77370
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
76686
77371
|
let dir = dirname14(scriptPath);
|
|
76687
77372
|
for (let i = 0;i < 12; i++) {
|
|
76688
|
-
if (existsSync58(
|
|
77373
|
+
if (existsSync58(join61(dir, ".git"))) {
|
|
76689
77374
|
try {
|
|
76690
|
-
const pkg = JSON.parse(readFileSync52(
|
|
77375
|
+
const pkg = JSON.parse(readFileSync52(join61(dir, "package.json"), "utf-8"));
|
|
76691
77376
|
if (pkg.name === "switchroom")
|
|
76692
77377
|
return true;
|
|
76693
77378
|
} catch {}
|
|
@@ -76847,7 +77532,7 @@ function planUpdate(opts) {
|
|
|
76847
77532
|
return;
|
|
76848
77533
|
}
|
|
76849
77534
|
const source = resolve34(import.meta.dirname, "../../skills");
|
|
76850
|
-
const dest =
|
|
77535
|
+
const dest = join61(homedir37(), ".switchroom", "skills", "_bundled");
|
|
76851
77536
|
if (!existsSync58(source)) {
|
|
76852
77537
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
76853
77538
|
`);
|
|
@@ -76961,7 +77646,7 @@ function defaultStatusProbe(composePath) {
|
|
|
76961
77646
|
} catch {}
|
|
76962
77647
|
let dir = dirname14(scriptPath);
|
|
76963
77648
|
for (let i = 0;i < 8; i++) {
|
|
76964
|
-
const pkgPath =
|
|
77649
|
+
const pkgPath = join61(dir, "package.json");
|
|
76965
77650
|
if (existsSync58(pkgPath)) {
|
|
76966
77651
|
try {
|
|
76967
77652
|
const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
|
|
@@ -77403,7 +78088,7 @@ init_helpers();
|
|
|
77403
78088
|
init_lifecycle();
|
|
77404
78089
|
import { execSync as execSync4 } from "node:child_process";
|
|
77405
78090
|
import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
|
|
77406
|
-
import { dirname as dirname15, join as
|
|
78091
|
+
import { dirname as dirname15, join as join62 } from "node:path";
|
|
77407
78092
|
function getClaudeCodeVersion() {
|
|
77408
78093
|
try {
|
|
77409
78094
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -77453,11 +78138,11 @@ function formatUptime3(timestamp) {
|
|
|
77453
78138
|
function locateSwitchroomInstallDir() {
|
|
77454
78139
|
let dir = import.meta.dirname;
|
|
77455
78140
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
77456
|
-
const pkgPath =
|
|
78141
|
+
const pkgPath = join62(dir, "package.json");
|
|
77457
78142
|
if (existsSync59(pkgPath)) {
|
|
77458
78143
|
try {
|
|
77459
78144
|
const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
|
|
77460
|
-
if (pkg.name === "switchroom" && existsSync59(
|
|
78145
|
+
if (pkg.name === "switchroom" && existsSync59(join62(dir, ".git"))) {
|
|
77461
78146
|
return dir;
|
|
77462
78147
|
}
|
|
77463
78148
|
} catch {}
|
|
@@ -77694,7 +78379,7 @@ import {
|
|
|
77694
78379
|
writeFileSync as writeFileSync28,
|
|
77695
78380
|
writeSync as writeSync7
|
|
77696
78381
|
} from "node:fs";
|
|
77697
|
-
import { join as
|
|
78382
|
+
import { join as join63 } from "node:path";
|
|
77698
78383
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
77699
78384
|
import { execSync as execSync5 } from "node:child_process";
|
|
77700
78385
|
|
|
@@ -78092,7 +78777,7 @@ function redactedMarker(ruleId) {
|
|
|
78092
78777
|
var ISSUES_FILE = "issues.jsonl";
|
|
78093
78778
|
var ISSUES_LOCK = "issues.lock";
|
|
78094
78779
|
function readAll(stateDir) {
|
|
78095
|
-
const path4 =
|
|
78780
|
+
const path4 = join63(stateDir, ISSUES_FILE);
|
|
78096
78781
|
if (!existsSync60(path4))
|
|
78097
78782
|
return [];
|
|
78098
78783
|
let raw;
|
|
@@ -78170,7 +78855,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
78170
78855
|
});
|
|
78171
78856
|
}
|
|
78172
78857
|
function resolve37(stateDir, fingerprint, nowFn = Date.now) {
|
|
78173
|
-
if (!existsSync60(
|
|
78858
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
78174
78859
|
return 0;
|
|
78175
78860
|
return withLock(stateDir, () => {
|
|
78176
78861
|
const all = readAll(stateDir);
|
|
@@ -78188,7 +78873,7 @@ function resolve37(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
78188
78873
|
});
|
|
78189
78874
|
}
|
|
78190
78875
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
78191
|
-
if (!existsSync60(
|
|
78876
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
78192
78877
|
return 0;
|
|
78193
78878
|
return withLock(stateDir, () => {
|
|
78194
78879
|
const all = readAll(stateDir);
|
|
@@ -78206,7 +78891,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
78206
78891
|
});
|
|
78207
78892
|
}
|
|
78208
78893
|
function prune(stateDir, opts = {}) {
|
|
78209
|
-
if (!existsSync60(
|
|
78894
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
78210
78895
|
return 0;
|
|
78211
78896
|
return withLock(stateDir, () => {
|
|
78212
78897
|
const all = readAll(stateDir);
|
|
@@ -78239,7 +78924,7 @@ function ensureDir(stateDir) {
|
|
|
78239
78924
|
mkdirSync33(stateDir, { recursive: true });
|
|
78240
78925
|
}
|
|
78241
78926
|
function writeAll(stateDir, events) {
|
|
78242
|
-
const path4 =
|
|
78927
|
+
const path4 = join63(stateDir, ISSUES_FILE);
|
|
78243
78928
|
sweepOrphanTmpFiles(stateDir);
|
|
78244
78929
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
78245
78930
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -78261,7 +78946,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
78261
78946
|
for (const entry of entries) {
|
|
78262
78947
|
if (!entry.startsWith(TMP_PREFIX))
|
|
78263
78948
|
continue;
|
|
78264
|
-
const tmpPath =
|
|
78949
|
+
const tmpPath = join63(stateDir, entry);
|
|
78265
78950
|
try {
|
|
78266
78951
|
const stat = statSync28(tmpPath);
|
|
78267
78952
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -78273,7 +78958,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
78273
78958
|
var LOCK_RETRY_MS = 25;
|
|
78274
78959
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
78275
78960
|
function withLock(stateDir, fn) {
|
|
78276
|
-
const lockPath =
|
|
78961
|
+
const lockPath = join63(stateDir, ISSUES_LOCK);
|
|
78277
78962
|
const startedAt = Date.now();
|
|
78278
78963
|
let fd = null;
|
|
78279
78964
|
while (fd === null) {
|
|
@@ -78557,8 +79242,8 @@ function relTime(deltaMs) {
|
|
|
78557
79242
|
// src/cli/deps.ts
|
|
78558
79243
|
init_source();
|
|
78559
79244
|
import { existsSync as existsSync63 } from "node:fs";
|
|
78560
|
-
import { homedir as
|
|
78561
|
-
import { join as
|
|
79245
|
+
import { homedir as homedir40 } from "node:os";
|
|
79246
|
+
import { join as join66, resolve as resolve38 } from "node:path";
|
|
78562
79247
|
|
|
78563
79248
|
// src/deps/python.ts
|
|
78564
79249
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -78569,8 +79254,8 @@ import {
|
|
|
78569
79254
|
rmSync as rmSync13,
|
|
78570
79255
|
writeFileSync as writeFileSync29
|
|
78571
79256
|
} from "node:fs";
|
|
78572
|
-
import { dirname as dirname16, join as
|
|
78573
|
-
import { homedir as
|
|
79257
|
+
import { dirname as dirname16, join as join64 } from "node:path";
|
|
79258
|
+
import { homedir as homedir38 } from "node:os";
|
|
78574
79259
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
78575
79260
|
|
|
78576
79261
|
class PythonEnvError extends Error {
|
|
@@ -78582,7 +79267,7 @@ class PythonEnvError extends Error {
|
|
|
78582
79267
|
}
|
|
78583
79268
|
}
|
|
78584
79269
|
function defaultPythonCacheRoot() {
|
|
78585
|
-
return
|
|
79270
|
+
return join64(homedir38(), ".switchroom", "deps", "python");
|
|
78586
79271
|
}
|
|
78587
79272
|
function hashFile(path4) {
|
|
78588
79273
|
return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
|
|
@@ -78594,11 +79279,11 @@ function ensurePythonEnv(opts) {
|
|
|
78594
79279
|
if (!existsSync61(requirementsPath)) {
|
|
78595
79280
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
78596
79281
|
}
|
|
78597
|
-
const venvDir =
|
|
78598
|
-
const stampPath =
|
|
78599
|
-
const binDir =
|
|
78600
|
-
const pythonBin =
|
|
78601
|
-
const pipBin =
|
|
79282
|
+
const venvDir = join64(cacheRoot, skillName);
|
|
79283
|
+
const stampPath = join64(venvDir, ".requirements.sha256");
|
|
79284
|
+
const binDir = join64(venvDir, "bin");
|
|
79285
|
+
const pythonBin = join64(binDir, "python");
|
|
79286
|
+
const pipBin = join64(binDir, "pip");
|
|
78602
79287
|
const targetHash = hashFile(requirementsPath);
|
|
78603
79288
|
if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
|
|
78604
79289
|
const existingHash = readFileSync56(stampPath, "utf8").trim();
|
|
@@ -78657,8 +79342,8 @@ import {
|
|
|
78657
79342
|
rmSync as rmSync14,
|
|
78658
79343
|
writeFileSync as writeFileSync30
|
|
78659
79344
|
} from "node:fs";
|
|
78660
|
-
import { dirname as dirname17, join as
|
|
78661
|
-
import { homedir as
|
|
79345
|
+
import { dirname as dirname17, join as join65 } from "node:path";
|
|
79346
|
+
import { homedir as homedir39 } from "node:os";
|
|
78662
79347
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
78663
79348
|
|
|
78664
79349
|
class NodeEnvError extends Error {
|
|
@@ -78681,7 +79366,7 @@ var LOCKFILES_FOR = {
|
|
|
78681
79366
|
npm: ["package-lock.json"]
|
|
78682
79367
|
};
|
|
78683
79368
|
function defaultNodeCacheRoot() {
|
|
78684
|
-
return
|
|
79369
|
+
return join65(homedir39(), ".switchroom", "deps", "node");
|
|
78685
79370
|
}
|
|
78686
79371
|
function hashDepInputs(packageJsonPath) {
|
|
78687
79372
|
const sourceDir = dirname17(packageJsonPath);
|
|
@@ -78690,7 +79375,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
78690
79375
|
`);
|
|
78691
79376
|
hasher.update(readFileSync57(packageJsonPath));
|
|
78692
79377
|
for (const lockName of ALL_LOCKFILES) {
|
|
78693
|
-
const lockPath =
|
|
79378
|
+
const lockPath = join65(sourceDir, lockName);
|
|
78694
79379
|
if (existsSync62(lockPath)) {
|
|
78695
79380
|
hasher.update(`
|
|
78696
79381
|
`);
|
|
@@ -78710,10 +79395,10 @@ function ensureNodeEnv(opts) {
|
|
|
78710
79395
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
78711
79396
|
}
|
|
78712
79397
|
const sourceDir = dirname17(packageJsonPath);
|
|
78713
|
-
const envDir =
|
|
78714
|
-
const stampPath =
|
|
78715
|
-
const nodeModulesDir =
|
|
78716
|
-
const binDir =
|
|
79398
|
+
const envDir = join65(cacheRoot, skillName);
|
|
79399
|
+
const stampPath = join65(envDir, ".package.sha256");
|
|
79400
|
+
const nodeModulesDir = join65(envDir, "node_modules");
|
|
79401
|
+
const binDir = join65(nodeModulesDir, ".bin");
|
|
78717
79402
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
78718
79403
|
if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
|
|
78719
79404
|
const existingHash = readFileSync57(stampPath, "utf8").trim();
|
|
@@ -78731,12 +79416,12 @@ function ensureNodeEnv(opts) {
|
|
|
78731
79416
|
rmSync14(envDir, { recursive: true, force: true });
|
|
78732
79417
|
}
|
|
78733
79418
|
mkdirSync35(envDir, { recursive: true });
|
|
78734
|
-
copyFileSync9(packageJsonPath,
|
|
79419
|
+
copyFileSync9(packageJsonPath, join65(envDir, "package.json"));
|
|
78735
79420
|
let copiedLockfile = false;
|
|
78736
79421
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
78737
|
-
const lockPath =
|
|
79422
|
+
const lockPath = join65(sourceDir, lockName);
|
|
78738
79423
|
if (existsSync62(lockPath)) {
|
|
78739
|
-
copyFileSync9(lockPath,
|
|
79424
|
+
copyFileSync9(lockPath, join65(envDir, lockName));
|
|
78740
79425
|
copiedLockfile = true;
|
|
78741
79426
|
}
|
|
78742
79427
|
}
|
|
@@ -78765,7 +79450,7 @@ function ensureNodeEnv(opts) {
|
|
|
78765
79450
|
|
|
78766
79451
|
// src/cli/deps.ts
|
|
78767
79452
|
function builtinSkillsRoot() {
|
|
78768
|
-
return resolve38(
|
|
79453
|
+
return resolve38(homedir40(), ".switchroom/skills/_bundled");
|
|
78769
79454
|
}
|
|
78770
79455
|
function registerDepsCommand(program3) {
|
|
78771
79456
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -78775,13 +79460,13 @@ function registerDepsCommand(program3) {
|
|
|
78775
79460
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
78776
79461
|
process.exit(1);
|
|
78777
79462
|
}
|
|
78778
|
-
const skillDir =
|
|
79463
|
+
const skillDir = join66(skillsRoot, skill);
|
|
78779
79464
|
if (!existsSync63(skillDir)) {
|
|
78780
79465
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
78781
79466
|
process.exit(1);
|
|
78782
79467
|
}
|
|
78783
|
-
const requirementsPath =
|
|
78784
|
-
const packageJsonPath =
|
|
79468
|
+
const requirementsPath = join66(skillDir, "requirements.txt");
|
|
79469
|
+
const packageJsonPath = join66(skillDir, "package.json");
|
|
78785
79470
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync63(requirementsPath));
|
|
78786
79471
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync63(packageJsonPath));
|
|
78787
79472
|
let did = 0;
|
|
@@ -79736,7 +80421,7 @@ init_helpers();
|
|
|
79736
80421
|
init_loader();
|
|
79737
80422
|
init_merge();
|
|
79738
80423
|
import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync58, writeFileSync as writeFileSync31 } from "node:fs";
|
|
79739
|
-
import { join as
|
|
80424
|
+
import { join as join67, resolve as resolve40 } from "node:path";
|
|
79740
80425
|
init_schema();
|
|
79741
80426
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
79742
80427
|
const config = getConfig(program3);
|
|
@@ -79760,7 +80445,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
79760
80445
|
profileName,
|
|
79761
80446
|
profilePath,
|
|
79762
80447
|
workspaceDir,
|
|
79763
|
-
soulPath:
|
|
80448
|
+
soulPath: join67(workspaceDir, "SOUL.md"),
|
|
79764
80449
|
soul: merged.soul
|
|
79765
80450
|
};
|
|
79766
80451
|
}
|
|
@@ -79827,7 +80512,7 @@ function registerSoulCommand(program3) {
|
|
|
79827
80512
|
init_helpers();
|
|
79828
80513
|
init_loader();
|
|
79829
80514
|
import { existsSync as existsSync66, readFileSync as readFileSync59, readdirSync as readdirSync23, statSync as statSync29 } from "node:fs";
|
|
79830
|
-
import { resolve as resolve41, join as
|
|
80515
|
+
import { resolve as resolve41, join as join68 } from "node:path";
|
|
79831
80516
|
import { createHash as createHash13 } from "node:crypto";
|
|
79832
80517
|
init_merge();
|
|
79833
80518
|
init_hindsight();
|
|
@@ -79838,7 +80523,7 @@ function estimateTokens(bytes) {
|
|
|
79838
80523
|
return Math.round(bytes / 3.7);
|
|
79839
80524
|
}
|
|
79840
80525
|
function readMcpServerNames(agentDir) {
|
|
79841
|
-
const mcpPath =
|
|
80526
|
+
const mcpPath = join68(agentDir, ".mcp.json");
|
|
79842
80527
|
if (!existsSync66(mcpPath))
|
|
79843
80528
|
return [];
|
|
79844
80529
|
try {
|
|
@@ -79852,7 +80537,7 @@ function sha256(content) {
|
|
|
79852
80537
|
return createHash13("sha256").update(content).digest("hex").slice(0, 16);
|
|
79853
80538
|
}
|
|
79854
80539
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
79855
|
-
const projectsDir =
|
|
80540
|
+
const projectsDir = join68(claudeConfigDir, "projects");
|
|
79856
80541
|
if (!existsSync66(projectsDir))
|
|
79857
80542
|
return;
|
|
79858
80543
|
try {
|
|
@@ -79861,8 +80546,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
79861
80546
|
for (const entry of entries) {
|
|
79862
80547
|
if (!entry.isDirectory())
|
|
79863
80548
|
continue;
|
|
79864
|
-
const projectPath =
|
|
79865
|
-
const transcriptPath =
|
|
80549
|
+
const projectPath = join68(projectsDir, entry.name);
|
|
80550
|
+
const transcriptPath = join68(projectPath, "transcript.jsonl");
|
|
79866
80551
|
if (!existsSync66(transcriptPath))
|
|
79867
80552
|
continue;
|
|
79868
80553
|
const stat3 = statSync29(transcriptPath);
|
|
@@ -79931,11 +80616,11 @@ function registerDebugCommand(program3) {
|
|
|
79931
80616
|
process.exit(1);
|
|
79932
80617
|
}
|
|
79933
80618
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
79934
|
-
const claudeConfigDir =
|
|
79935
|
-
const claudeMdPath =
|
|
79936
|
-
const soulMdPath =
|
|
79937
|
-
const workspaceSoulMdPath =
|
|
79938
|
-
const handoffPath =
|
|
80619
|
+
const claudeConfigDir = join68(agentDir, ".claude");
|
|
80620
|
+
const claudeMdPath = join68(agentDir, "CLAUDE.md");
|
|
80621
|
+
const soulMdPath = join68(agentDir, "SOUL.md");
|
|
80622
|
+
const workspaceSoulMdPath = join68(workspaceDir, "SOUL.md");
|
|
80623
|
+
const handoffPath = join68(agentDir, ".handoff.md");
|
|
79939
80624
|
const lastN = parseInt(opts.last, 10);
|
|
79940
80625
|
if (isNaN(lastN) || lastN < 1) {
|
|
79941
80626
|
console.error("--last must be a positive integer");
|
|
@@ -80064,9 +80749,9 @@ function registerDebugCommand(program3) {
|
|
|
80064
80749
|
const soulMdBytes = soulMdContent.length;
|
|
80065
80750
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
80066
80751
|
const userBytes = userMessage?.text.length ?? 0;
|
|
80067
|
-
const fleetDir =
|
|
80068
|
-
const fleetInvPath =
|
|
80069
|
-
const fleetClaudePath =
|
|
80752
|
+
const fleetDir = join68(agentsDir, "..", "fleet");
|
|
80753
|
+
const fleetInvPath = join68(fleetDir, "switchroom-invariants.md");
|
|
80754
|
+
const fleetClaudePath = join68(fleetDir, "CLAUDE.md");
|
|
80070
80755
|
const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync59(fleetInvPath, "utf-8").length : 0;
|
|
80071
80756
|
const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync59(fleetClaudePath, "utf-8").length : 0;
|
|
80072
80757
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
@@ -80102,8 +80787,8 @@ init_source();
|
|
|
80102
80787
|
// src/worktree/claim.ts
|
|
80103
80788
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
80104
80789
|
import { closeSync as closeSync12, mkdirSync as mkdirSync37, openSync as openSync12, existsSync as existsSync68, unlinkSync as unlinkSync13 } from "node:fs";
|
|
80105
|
-
import { join as
|
|
80106
|
-
import { homedir as
|
|
80790
|
+
import { join as join70, resolve as resolve43 } from "node:path";
|
|
80791
|
+
import { homedir as homedir42 } from "node:os";
|
|
80107
80792
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
80108
80793
|
|
|
80109
80794
|
// src/worktree/registry.ts
|
|
@@ -80116,13 +80801,13 @@ import {
|
|
|
80116
80801
|
existsSync as existsSync67,
|
|
80117
80802
|
renameSync as renameSync13
|
|
80118
80803
|
} from "node:fs";
|
|
80119
|
-
import { join as
|
|
80120
|
-
import { homedir as
|
|
80804
|
+
import { join as join69, resolve as resolve42 } from "node:path";
|
|
80805
|
+
import { homedir as homedir41 } from "node:os";
|
|
80121
80806
|
function registryDir() {
|
|
80122
|
-
return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
80807
|
+
return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join69(homedir41(), ".switchroom", "worktrees"));
|
|
80123
80808
|
}
|
|
80124
80809
|
function recordPath(id) {
|
|
80125
|
-
return
|
|
80810
|
+
return join69(registryDir(), `${id}.json`);
|
|
80126
80811
|
}
|
|
80127
80812
|
function ensureDir2() {
|
|
80128
80813
|
mkdirSync36(registryDir(), { recursive: true });
|
|
@@ -80173,7 +80858,7 @@ function acquireRepoLock(repoPath) {
|
|
|
80173
80858
|
const lockDir = registryDir();
|
|
80174
80859
|
mkdirSync37(lockDir, { recursive: true });
|
|
80175
80860
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
80176
|
-
const lockPath =
|
|
80861
|
+
const lockPath = join70(lockDir, `.lock-${lockName}`);
|
|
80177
80862
|
const deadline = Date.now() + 5000;
|
|
80178
80863
|
let fd = null;
|
|
80179
80864
|
while (fd === null) {
|
|
@@ -80200,7 +80885,7 @@ function acquireRepoLock(repoPath) {
|
|
|
80200
80885
|
}
|
|
80201
80886
|
var DEFAULT_CONCURRENCY = 5;
|
|
80202
80887
|
function worktreesBaseDir() {
|
|
80203
|
-
return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
80888
|
+
return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ?? join70(homedir42(), ".switchroom", "worktree-checkouts"));
|
|
80204
80889
|
}
|
|
80205
80890
|
function shortId() {
|
|
80206
80891
|
return randomBytes13(4).toString("hex");
|
|
@@ -80222,7 +80907,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
80222
80907
|
}
|
|
80223
80908
|
function expandHome(p) {
|
|
80224
80909
|
if (p.startsWith("~/"))
|
|
80225
|
-
return
|
|
80910
|
+
return join70(homedir42(), p.slice(2));
|
|
80226
80911
|
return p;
|
|
80227
80912
|
}
|
|
80228
80913
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -80250,7 +80935,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
80250
80935
|
branch = `task/${taskSuffix}-${id}`;
|
|
80251
80936
|
const baseDir = worktreesBaseDir();
|
|
80252
80937
|
mkdirSync37(baseDir, { recursive: true });
|
|
80253
|
-
worktreePath =
|
|
80938
|
+
worktreePath = join70(baseDir, `${id}-${taskSuffix}`);
|
|
80254
80939
|
const now = new Date().toISOString();
|
|
80255
80940
|
const record2 = {
|
|
80256
80941
|
id,
|
|
@@ -80505,7 +81190,7 @@ import {
|
|
|
80505
81190
|
rmSync as rmSync15,
|
|
80506
81191
|
writeFileSync as writeFileSync33
|
|
80507
81192
|
} from "node:fs";
|
|
80508
|
-
import { join as
|
|
81193
|
+
import { join as join71 } from "node:path";
|
|
80509
81194
|
function encodeCredentialsFilename(email) {
|
|
80510
81195
|
const SAFE = new Set([
|
|
80511
81196
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -80695,16 +81380,16 @@ function resolveCredentialsDir(env2) {
|
|
|
80695
81380
|
if (explicit && explicit.length > 0)
|
|
80696
81381
|
return explicit;
|
|
80697
81382
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
80698
|
-
return
|
|
81383
|
+
return join71(stateBase, "google-workspace-mcp", "credentials");
|
|
80699
81384
|
}
|
|
80700
81385
|
function writeSeedFile(dir, email, seed) {
|
|
80701
81386
|
mkdirSync38(dir, { recursive: true, mode: 448 });
|
|
80702
81387
|
chmodSync9(dir, 448);
|
|
80703
81388
|
for (const name of readdirSync25(dir)) {
|
|
80704
|
-
rmSync15(
|
|
81389
|
+
rmSync15(join71(dir, name), { force: true, recursive: true });
|
|
80705
81390
|
}
|
|
80706
81391
|
const filename = encodeCredentialsFilename(email);
|
|
80707
|
-
const filePath =
|
|
81392
|
+
const filePath = join71(dir, filename);
|
|
80708
81393
|
writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
|
|
80709
81394
|
chmodSync9(filePath, 384);
|
|
80710
81395
|
return filePath;
|
|
@@ -80825,9 +81510,9 @@ async function runDriveMcpLauncher(opts) {
|
|
|
80825
81510
|
}
|
|
80826
81511
|
const args = buildUvxArgs(tier);
|
|
80827
81512
|
const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
|
|
80828
|
-
const { spawn:
|
|
81513
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
80829
81514
|
const os5 = await import("node:os");
|
|
80830
|
-
const child =
|
|
81515
|
+
const child = spawn5("uvx", args, {
|
|
80831
81516
|
stdio: ["pipe", "pipe", "inherit"],
|
|
80832
81517
|
env: env2
|
|
80833
81518
|
});
|
|
@@ -80862,9 +81547,9 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
80862
81547
|
|
|
80863
81548
|
// src/cli/m365-mcp-launcher.ts
|
|
80864
81549
|
init_scaffold_integration();
|
|
80865
|
-
import { spawn as
|
|
81550
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
80866
81551
|
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync39 } from "node:fs";
|
|
80867
|
-
import { dirname as dirname18, join as
|
|
81552
|
+
import { dirname as dirname18, join as join72 } from "node:path";
|
|
80868
81553
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
80869
81554
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
80870
81555
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -80896,7 +81581,7 @@ function writeRefreshHeartbeat(agentName, data) {
|
|
|
80896
81581
|
function heartbeatPath(agentName) {
|
|
80897
81582
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
80898
81583
|
if (override) {
|
|
80899
|
-
return
|
|
81584
|
+
return join72(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
80900
81585
|
}
|
|
80901
81586
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
80902
81587
|
}
|
|
@@ -81068,7 +81753,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
81068
81753
|
});
|
|
81069
81754
|
},
|
|
81070
81755
|
spawnSofteria: (env2) => {
|
|
81071
|
-
return
|
|
81756
|
+
return spawn5("npx", buildSofteriaArgs(opts), {
|
|
81072
81757
|
env: env2,
|
|
81073
81758
|
stdio: ["pipe", "pipe", "pipe"]
|
|
81074
81759
|
});
|
|
@@ -81080,7 +81765,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
81080
81765
|
|
|
81081
81766
|
// src/cli/notion-mcp-launcher.ts
|
|
81082
81767
|
init_scaffold_integration();
|
|
81083
|
-
import { spawn as
|
|
81768
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
81084
81769
|
import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as writeFileSync35 } from "node:fs";
|
|
81085
81770
|
import { dirname as dirname19 } from "node:path";
|
|
81086
81771
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
@@ -81193,7 +81878,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
81193
81878
|
return result.entry.value;
|
|
81194
81879
|
},
|
|
81195
81880
|
spawnMcp: (env2, args) => {
|
|
81196
|
-
return
|
|
81881
|
+
return spawn6("npx", args, {
|
|
81197
81882
|
env: env2,
|
|
81198
81883
|
stdio: ["pipe", "pipe", "pipe"]
|
|
81199
81884
|
});
|
|
@@ -82224,8 +82909,8 @@ agents:
|
|
|
82224
82909
|
|
|
82225
82910
|
// src/cli/apply.ts
|
|
82226
82911
|
init_resolver();
|
|
82227
|
-
import { dirname as dirname22, join as
|
|
82228
|
-
import { homedir as
|
|
82912
|
+
import { dirname as dirname22, join as join75, resolve as resolve45 } from "node:path";
|
|
82913
|
+
import { homedir as homedir44 } from "node:os";
|
|
82229
82914
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
82230
82915
|
init_vault();
|
|
82231
82916
|
init_loader();
|
|
@@ -82233,7 +82918,7 @@ init_loader();
|
|
|
82233
82918
|
|
|
82234
82919
|
// src/cli/update-prompt-hook.ts
|
|
82235
82920
|
import { existsSync as existsSync72, readFileSync as readFileSync62, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
|
|
82236
|
-
import { join as
|
|
82921
|
+
import { join as join73 } from "node:path";
|
|
82237
82922
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
82238
82923
|
function updatePromptHookScript() {
|
|
82239
82924
|
return `#!/bin/bash
|
|
@@ -82299,9 +82984,9 @@ exit 0
|
|
|
82299
82984
|
`;
|
|
82300
82985
|
}
|
|
82301
82986
|
function installUpdatePromptHook(agentDir) {
|
|
82302
|
-
const hooksDir =
|
|
82987
|
+
const hooksDir = join73(agentDir, ".claude", "hooks");
|
|
82303
82988
|
mkdirSync41(hooksDir, { recursive: true });
|
|
82304
|
-
const scriptPath =
|
|
82989
|
+
const scriptPath = join73(hooksDir, HOOK_FILENAME);
|
|
82305
82990
|
const desired = updatePromptHookScript();
|
|
82306
82991
|
let installed = false;
|
|
82307
82992
|
const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
|
|
@@ -82314,7 +82999,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
82314
82999
|
chmodSync10(scriptPath, 493);
|
|
82315
83000
|
} catch {}
|
|
82316
83001
|
}
|
|
82317
|
-
const settingsPath =
|
|
83002
|
+
const settingsPath = join73(agentDir, ".claude", "settings.json");
|
|
82318
83003
|
if (!existsSync72(settingsPath)) {
|
|
82319
83004
|
return { scriptPath, settingsPath, installed };
|
|
82320
83005
|
}
|
|
@@ -82416,14 +83101,14 @@ var EMBEDDED_EXAMPLES = {
|
|
|
82416
83101
|
switchroom: switchroom_default,
|
|
82417
83102
|
minimal: minimal_default
|
|
82418
83103
|
};
|
|
82419
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
83104
|
+
var DEFAULT_COMPOSE_PATH2 = join75(homedir44(), ".switchroom", "compose", "docker-compose.yml");
|
|
82420
83105
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
82421
83106
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
82422
83107
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
82423
83108
|
if (isCustomPath && ctx.customVaultPath) {
|
|
82424
83109
|
return dirname22(ctx.customVaultPath);
|
|
82425
83110
|
}
|
|
82426
|
-
return
|
|
83111
|
+
return join75(homeDir, ".switchroom", "vault");
|
|
82427
83112
|
}
|
|
82428
83113
|
function inspectVaultBindMountDir(vaultDir) {
|
|
82429
83114
|
if (!existsSync74(vaultDir))
|
|
@@ -82452,44 +83137,44 @@ function hasVaultRefs(value) {
|
|
|
82452
83137
|
return false;
|
|
82453
83138
|
}
|
|
82454
83139
|
async function ensureHostMountSources(config) {
|
|
82455
|
-
const home2 =
|
|
83140
|
+
const home2 = homedir44();
|
|
82456
83141
|
const dirs = [
|
|
82457
|
-
|
|
82458
|
-
|
|
82459
|
-
|
|
82460
|
-
|
|
82461
|
-
|
|
83142
|
+
join75(home2, ".switchroom", "approvals"),
|
|
83143
|
+
join75(home2, ".switchroom", "scheduler"),
|
|
83144
|
+
join75(home2, ".switchroom", "logs"),
|
|
83145
|
+
join75(home2, ".switchroom", "compose"),
|
|
83146
|
+
join75(home2, ".switchroom", "broker-operator")
|
|
82462
83147
|
];
|
|
82463
83148
|
for (const name of Object.keys(config.agents)) {
|
|
82464
|
-
dirs.push(
|
|
82465
|
-
dirs.push(
|
|
82466
|
-
dirs.push(
|
|
82467
|
-
dirs.push(
|
|
82468
|
-
if (existsSync74(
|
|
82469
|
-
dirs.push(
|
|
83149
|
+
dirs.push(join75(home2, ".switchroom", "agents", name));
|
|
83150
|
+
dirs.push(join75(home2, ".switchroom", "logs", name));
|
|
83151
|
+
dirs.push(join75(home2, ".claude", "projects", name));
|
|
83152
|
+
dirs.push(join75(home2, ".switchroom", "audit", name));
|
|
83153
|
+
if (existsSync74(join75(home2, ".switchroom-config"))) {
|
|
83154
|
+
dirs.push(join75(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
82470
83155
|
}
|
|
82471
83156
|
}
|
|
82472
83157
|
for (const dir of dirs) {
|
|
82473
83158
|
await mkdir2(dir, { recursive: true });
|
|
82474
83159
|
}
|
|
82475
|
-
const autoUnlockPath =
|
|
83160
|
+
const autoUnlockPath = join75(home2, ".switchroom", "vault-auto-unlock");
|
|
82476
83161
|
if (!existsSync74(autoUnlockPath)) {
|
|
82477
83162
|
writeFileSync37(autoUnlockPath, "", { mode: 384 });
|
|
82478
83163
|
}
|
|
82479
|
-
const auditLogPath =
|
|
83164
|
+
const auditLogPath = join75(home2, ".switchroom", "vault-audit.log");
|
|
82480
83165
|
if (!existsSync74(auditLogPath)) {
|
|
82481
83166
|
writeFileSync37(auditLogPath, "", { mode: 420 });
|
|
82482
83167
|
}
|
|
82483
|
-
const grantsDbPath =
|
|
83168
|
+
const grantsDbPath = join75(home2, ".switchroom", "vault-grants.db");
|
|
82484
83169
|
if (!existsSync74(grantsDbPath)) {
|
|
82485
83170
|
writeFileSync37(grantsDbPath, "", { mode: 384 });
|
|
82486
83171
|
}
|
|
82487
|
-
const hostdAuditLogPath =
|
|
83172
|
+
const hostdAuditLogPath = join75(home2, ".switchroom", "host-control-audit.log");
|
|
82488
83173
|
if (!existsSync74(hostdAuditLogPath)) {
|
|
82489
83174
|
writeFileSync37(hostdAuditLogPath, "", { mode: 420 });
|
|
82490
83175
|
}
|
|
82491
83176
|
for (const name of Object.keys(config.agents)) {
|
|
82492
|
-
const tokenPath =
|
|
83177
|
+
const tokenPath = join75(home2, ".switchroom", "agents", name, ".vault-token");
|
|
82493
83178
|
if (!existsSync74(tokenPath)) {
|
|
82494
83179
|
writeFileSync37(tokenPath, "", { mode: 384 });
|
|
82495
83180
|
}
|
|
@@ -82498,15 +83183,15 @@ async function ensureHostMountSources(config) {
|
|
|
82498
83183
|
chownSync7(tokenPath, uid, uid);
|
|
82499
83184
|
} catch {}
|
|
82500
83185
|
}
|
|
82501
|
-
const fleetDir =
|
|
83186
|
+
const fleetDir = join75(home2, ".switchroom", "fleet");
|
|
82502
83187
|
await mkdir2(fleetDir, { recursive: true });
|
|
82503
|
-
const invariantsPath =
|
|
83188
|
+
const invariantsPath = join75(fleetDir, "switchroom-invariants.md");
|
|
82504
83189
|
const invariantsCanonical = renderFleetInvariants();
|
|
82505
83190
|
const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
|
|
82506
83191
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
82507
83192
|
writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
82508
83193
|
}
|
|
82509
|
-
const fleetClaudePath =
|
|
83194
|
+
const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
|
|
82510
83195
|
if (!existsSync74(fleetClaudePath)) {
|
|
82511
83196
|
writeFileSync37(fleetClaudePath, [
|
|
82512
83197
|
"# Switchroom fleet defaults",
|
|
@@ -82589,10 +83274,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
82589
83274
|
`));
|
|
82590
83275
|
}
|
|
82591
83276
|
}
|
|
82592
|
-
function writeInstallTypeCache(homeDir =
|
|
83277
|
+
function writeInstallTypeCache(homeDir = homedir44()) {
|
|
82593
83278
|
const ctx = detectInstallType();
|
|
82594
|
-
const dir =
|
|
82595
|
-
const out =
|
|
83279
|
+
const dir = join75(homeDir, ".switchroom");
|
|
83280
|
+
const out = join75(dir, "install-type.json");
|
|
82596
83281
|
const tmp = `${out}.tmp`;
|
|
82597
83282
|
mkdirSync42(dir, { recursive: true });
|
|
82598
83283
|
const payload = {
|
|
@@ -82641,14 +83326,14 @@ Applying switchroom config...
|
|
|
82641
83326
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
82642
83327
|
`));
|
|
82643
83328
|
try {
|
|
82644
|
-
installUpdatePromptHook(
|
|
83329
|
+
installUpdatePromptHook(join75(agentsDir, name));
|
|
82645
83330
|
} catch (hookErr) {
|
|
82646
83331
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
82647
83332
|
`));
|
|
82648
83333
|
}
|
|
82649
83334
|
try {
|
|
82650
83335
|
const uid = allocateAgentUid(name);
|
|
82651
|
-
alignAgentUid(name,
|
|
83336
|
+
alignAgentUid(name, join75(agentsDir, name), uid, {
|
|
82652
83337
|
confirm: !options.nonInteractive,
|
|
82653
83338
|
writeOut
|
|
82654
83339
|
});
|
|
@@ -82685,7 +83370,7 @@ Applying switchroom config...
|
|
|
82685
83370
|
for (const name of agentNames) {
|
|
82686
83371
|
try {
|
|
82687
83372
|
const uid = allocateAgentUid(name);
|
|
82688
|
-
alignAgentUid(name,
|
|
83373
|
+
alignAgentUid(name, join75(agentsDir, name), uid, {
|
|
82689
83374
|
confirm: !options.nonInteractive,
|
|
82690
83375
|
writeOut
|
|
82691
83376
|
});
|
|
@@ -82699,7 +83384,7 @@ Applying switchroom config...
|
|
|
82699
83384
|
}
|
|
82700
83385
|
const vaultPathConfigured = config.vault?.path;
|
|
82701
83386
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
82702
|
-
const migrationResult = migrateVaultLayout(
|
|
83387
|
+
const migrationResult = migrateVaultLayout(homedir44(), {
|
|
82703
83388
|
customVaultPath
|
|
82704
83389
|
});
|
|
82705
83390
|
switch (migrationResult.kind) {
|
|
@@ -82725,7 +83410,7 @@ Applying switchroom config...
|
|
|
82725
83410
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
82726
83411
|
process.exit(4);
|
|
82727
83412
|
}
|
|
82728
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
83413
|
+
const postMigrationInspect = inspectVaultLayout(homedir44());
|
|
82729
83414
|
const acceptable = [
|
|
82730
83415
|
"no-vault",
|
|
82731
83416
|
"already-migrated",
|
|
@@ -82740,7 +83425,7 @@ Applying switchroom config...
|
|
|
82740
83425
|
`));
|
|
82741
83426
|
process.exit(5);
|
|
82742
83427
|
}
|
|
82743
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
83428
|
+
const vaultDir = resolveVaultBindMountDir(homedir44(), {
|
|
82744
83429
|
migrationKind: migrationResult.kind,
|
|
82745
83430
|
customVaultPath
|
|
82746
83431
|
});
|
|
@@ -82779,7 +83464,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
82779
83464
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
82780
83465
|
`));
|
|
82781
83466
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
82782
|
-
const restored = restoreOperatorOwnership(
|
|
83467
|
+
const restored = restoreOperatorOwnership(homedir44(), operatorUid);
|
|
82783
83468
|
if (restored.length > 0) {
|
|
82784
83469
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
82785
83470
|
`));
|
|
@@ -82849,7 +83534,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
82849
83534
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
82850
83535
|
const unwritable = [];
|
|
82851
83536
|
for (const name of targets) {
|
|
82852
|
-
const startSh =
|
|
83537
|
+
const startSh = join75(agentsDir, name, "start.sh");
|
|
82853
83538
|
if (!existsSync74(startSh))
|
|
82854
83539
|
continue;
|
|
82855
83540
|
try {
|
|
@@ -83029,8 +83714,8 @@ function runRedactStdin() {
|
|
|
83029
83714
|
|
|
83030
83715
|
// src/cli/status-ask.ts
|
|
83031
83716
|
import { readFileSync as readFileSync64, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
|
|
83032
|
-
import { join as
|
|
83033
|
-
import { homedir as
|
|
83717
|
+
import { join as join76 } from "node:path";
|
|
83718
|
+
import { homedir as homedir45 } from "node:os";
|
|
83034
83719
|
|
|
83035
83720
|
// src/status-ask/report.ts
|
|
83036
83721
|
function parseJsonl(content) {
|
|
@@ -83365,7 +84050,7 @@ function resolveSources(explicitPath) {
|
|
|
83365
84050
|
const config = loadConfig();
|
|
83366
84051
|
agentsDir = resolveAgentsDir(config);
|
|
83367
84052
|
} catch {
|
|
83368
|
-
agentsDir =
|
|
84053
|
+
agentsDir = join76(homedir45(), ".switchroom", "agents");
|
|
83369
84054
|
}
|
|
83370
84055
|
if (!existsSync75(agentsDir))
|
|
83371
84056
|
return [];
|
|
@@ -83377,7 +84062,7 @@ function resolveSources(explicitPath) {
|
|
|
83377
84062
|
return [];
|
|
83378
84063
|
}
|
|
83379
84064
|
for (const name of entries) {
|
|
83380
|
-
const path8 =
|
|
84065
|
+
const path8 = join76(agentsDir, name, "runtime-metrics.jsonl");
|
|
83381
84066
|
if (existsSync75(path8)) {
|
|
83382
84067
|
sources.push({ path: path8, agent: name });
|
|
83383
84068
|
}
|
|
@@ -83415,21 +84100,21 @@ import {
|
|
|
83415
84100
|
unlinkSync as unlinkSync14,
|
|
83416
84101
|
writeSync as writeSync8
|
|
83417
84102
|
} from "node:fs";
|
|
83418
|
-
import { join as
|
|
84103
|
+
import { join as join77, resolve as resolve46 } from "node:path";
|
|
83419
84104
|
var STAGING_SUBDIR = ".staging";
|
|
83420
84105
|
function overlayPathsFor(agent, opts = {}) {
|
|
83421
84106
|
const base = opts.root ? resolve46(opts.root, agent) : resolve46(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
83422
|
-
const scheduleDir =
|
|
83423
|
-
const scheduleStagingDir =
|
|
83424
|
-
const skillsDir =
|
|
83425
|
-
const skillsStagingDir =
|
|
84107
|
+
const scheduleDir = join77(base, "schedule.d");
|
|
84108
|
+
const scheduleStagingDir = join77(scheduleDir, STAGING_SUBDIR);
|
|
84109
|
+
const skillsDir = join77(base, "skills.d");
|
|
84110
|
+
const skillsStagingDir = join77(skillsDir, STAGING_SUBDIR);
|
|
83426
84111
|
return {
|
|
83427
84112
|
agentRoot: base,
|
|
83428
84113
|
scheduleDir,
|
|
83429
84114
|
scheduleStagingDir,
|
|
83430
84115
|
skillsDir,
|
|
83431
84116
|
skillsStagingDir,
|
|
83432
|
-
lockPath:
|
|
84117
|
+
lockPath: join77(base, ".lock"),
|
|
83433
84118
|
stagingDir: scheduleStagingDir
|
|
83434
84119
|
};
|
|
83435
84120
|
}
|
|
@@ -83483,8 +84168,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83483
84168
|
const paths = overlayPathsFor(agent, opts);
|
|
83484
84169
|
return withAgentLock(paths, () => {
|
|
83485
84170
|
ensureDirs(paths);
|
|
83486
|
-
const stagingPath =
|
|
83487
|
-
const finalPath =
|
|
84171
|
+
const stagingPath = join77(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
84172
|
+
const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
|
|
83488
84173
|
const fd = openSync13(stagingPath, "w", 384);
|
|
83489
84174
|
try {
|
|
83490
84175
|
writeSync8(fd, yamlText);
|
|
@@ -83500,8 +84185,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83500
84185
|
const paths = overlayPathsFor(agent, opts);
|
|
83501
84186
|
return withAgentLock(paths, () => {
|
|
83502
84187
|
ensureSkillsDirs(paths);
|
|
83503
|
-
const stagingPath =
|
|
83504
|
-
const finalPath =
|
|
84188
|
+
const stagingPath = join77(paths.skillsStagingDir, `${slug}.yaml`);
|
|
84189
|
+
const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
|
|
83505
84190
|
const fd = openSync13(stagingPath, "w", 384);
|
|
83506
84191
|
try {
|
|
83507
84192
|
writeSync8(fd, yamlText);
|
|
@@ -83516,7 +84201,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83516
84201
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
83517
84202
|
const paths = overlayPathsFor(agent, opts);
|
|
83518
84203
|
return withAgentLock(paths, () => {
|
|
83519
|
-
const finalPath =
|
|
84204
|
+
const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
|
|
83520
84205
|
if (!existsSync76(finalPath))
|
|
83521
84206
|
return false;
|
|
83522
84207
|
unlinkSync14(finalPath);
|
|
@@ -83531,7 +84216,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
83531
84216
|
for (const name of readdirSync28(paths.skillsDir)) {
|
|
83532
84217
|
if (!/\.ya?ml$/i.test(name))
|
|
83533
84218
|
continue;
|
|
83534
|
-
const full =
|
|
84219
|
+
const full = join77(paths.skillsDir, name);
|
|
83535
84220
|
try {
|
|
83536
84221
|
const raw = readFileSync65(full, "utf-8");
|
|
83537
84222
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -83543,7 +84228,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
83543
84228
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
83544
84229
|
const paths = overlayPathsFor(agent, opts);
|
|
83545
84230
|
return withAgentLock(paths, () => {
|
|
83546
|
-
const finalPath =
|
|
84231
|
+
const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
|
|
83547
84232
|
if (!existsSync76(finalPath))
|
|
83548
84233
|
return false;
|
|
83549
84234
|
unlinkSync14(finalPath);
|
|
@@ -83558,7 +84243,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
83558
84243
|
for (const name of readdirSync28(paths.scheduleDir)) {
|
|
83559
84244
|
if (!/\.ya?ml$/i.test(name))
|
|
83560
84245
|
continue;
|
|
83561
|
-
const full =
|
|
84246
|
+
const full = join77(paths.scheduleDir, name);
|
|
83562
84247
|
try {
|
|
83563
84248
|
const raw = readFileSync65(full, "utf-8");
|
|
83564
84249
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -83714,12 +84399,12 @@ import {
|
|
|
83714
84399
|
writeFileSync as writeFileSync38,
|
|
83715
84400
|
writeSync as writeSync9
|
|
83716
84401
|
} from "node:fs";
|
|
83717
|
-
import { join as
|
|
84402
|
+
import { join as join78 } from "node:path";
|
|
83718
84403
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
83719
84404
|
var STAGE_ID_PREFIX = "cap_";
|
|
83720
84405
|
function pendingDir(agent, opts = {}) {
|
|
83721
84406
|
const paths = overlayPathsFor(agent, opts);
|
|
83722
|
-
return
|
|
84407
|
+
return join78(paths.scheduleDir, ".pending");
|
|
83723
84408
|
}
|
|
83724
84409
|
function ensurePendingDir(agent, opts = {}) {
|
|
83725
84410
|
const dir = pendingDir(agent, opts);
|
|
@@ -83732,8 +84417,8 @@ function newStageId() {
|
|
|
83732
84417
|
function stagePendingScheduleEntry(opts) {
|
|
83733
84418
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
83734
84419
|
const stageId = opts.stageId ?? newStageId();
|
|
83735
|
-
const yamlPath =
|
|
83736
|
-
const metaPath =
|
|
84420
|
+
const yamlPath = join78(dir, `${stageId}.yaml`);
|
|
84421
|
+
const metaPath = join78(dir, `${stageId}.meta.json`);
|
|
83737
84422
|
const meta = {
|
|
83738
84423
|
v: 1,
|
|
83739
84424
|
stage_id: stageId,
|
|
@@ -83767,8 +84452,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
83767
84452
|
if (!name.endsWith(".meta.json"))
|
|
83768
84453
|
continue;
|
|
83769
84454
|
const stageId = name.slice(0, -".meta.json".length);
|
|
83770
|
-
const metaPath =
|
|
83771
|
-
const yamlPath =
|
|
84455
|
+
const metaPath = join78(dir, name);
|
|
84456
|
+
const yamlPath = join78(dir, `${stageId}.yaml`);
|
|
83772
84457
|
if (!existsSync77(yamlPath))
|
|
83773
84458
|
continue;
|
|
83774
84459
|
try {
|
|
@@ -83787,7 +84472,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
83787
84472
|
return { committed: false, reason: "not_found" };
|
|
83788
84473
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
83789
84474
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
83790
|
-
const finalPath =
|
|
84475
|
+
const finalPath = join78(paths.scheduleDir, `${slug}.yaml`);
|
|
83791
84476
|
if (existsSync77(finalPath)) {
|
|
83792
84477
|
return { committed: false, reason: "slug_collision" };
|
|
83793
84478
|
}
|
|
@@ -84420,7 +85105,7 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
84420
85105
|
import { existsSync as existsSync79 } from "node:fs";
|
|
84421
85106
|
init_reconcile_default_skills();
|
|
84422
85107
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
84423
|
-
import { join as
|
|
85108
|
+
import { join as join79 } from "node:path";
|
|
84424
85109
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
84425
85110
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
84426
85111
|
function exitCodeFor2(code) {
|
|
@@ -84495,7 +85180,7 @@ function skillInstall(opts) {
|
|
|
84495
85180
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
84496
85181
|
}
|
|
84497
85182
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
84498
|
-
const skillPath =
|
|
85183
|
+
const skillPath = join79(poolDir, skillName);
|
|
84499
85184
|
if (!existsSync79(skillPath)) {
|
|
84500
85185
|
return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
|
|
84501
85186
|
}
|
|
@@ -84673,8 +85358,8 @@ import {
|
|
|
84673
85358
|
statSync as statSync32,
|
|
84674
85359
|
writeFileSync as writeFileSync39
|
|
84675
85360
|
} from "node:fs";
|
|
84676
|
-
import { tmpdir as tmpdir5, homedir as
|
|
84677
|
-
import { dirname as dirname23, join as
|
|
85361
|
+
import { tmpdir as tmpdir5, homedir as homedir46 } from "node:os";
|
|
85362
|
+
import { dirname as dirname23, join as join80, relative as relative2, resolve as resolve47 } from "node:path";
|
|
84678
85363
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84679
85364
|
|
|
84680
85365
|
// src/cli/skill-common.ts
|
|
@@ -84868,10 +85553,10 @@ function scanForClaudeP2(content) {
|
|
|
84868
85553
|
function resolveSkillsPoolDir2(override) {
|
|
84869
85554
|
const raw = override ?? "~/.switchroom/skills";
|
|
84870
85555
|
if (raw.startsWith("~/")) {
|
|
84871
|
-
return
|
|
85556
|
+
return join80(homedir46(), raw.slice(2));
|
|
84872
85557
|
}
|
|
84873
85558
|
if (raw === "~")
|
|
84874
|
-
return
|
|
85559
|
+
return homedir46();
|
|
84875
85560
|
return resolve47(raw);
|
|
84876
85561
|
}
|
|
84877
85562
|
function readStdinSync() {
|
|
@@ -84907,7 +85592,7 @@ function loadFromDir(dir) {
|
|
|
84907
85592
|
const walk2 = (sub) => {
|
|
84908
85593
|
const entries = readdirSync30(sub, { withFileTypes: true });
|
|
84909
85594
|
for (const ent of entries) {
|
|
84910
|
-
const full =
|
|
85595
|
+
const full = join80(sub, ent.name);
|
|
84911
85596
|
const rel = relative2(abs, full);
|
|
84912
85597
|
if (ent.isSymbolicLink()) {
|
|
84913
85598
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -84942,7 +85627,7 @@ function loadFromTarball(tarPath) {
|
|
|
84942
85627
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
84943
85628
|
}
|
|
84944
85629
|
}
|
|
84945
|
-
const staging = mkdtempSync5(
|
|
85630
|
+
const staging = mkdtempSync5(join80(tmpdir5(), "skill-apply-extract-"));
|
|
84946
85631
|
try {
|
|
84947
85632
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
84948
85633
|
const r = spawnSync12("tar", [
|
|
@@ -85028,8 +85713,8 @@ function validatePayload(name, files) {
|
|
|
85028
85713
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
85029
85714
|
}
|
|
85030
85715
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
85031
|
-
const tmp = mkdtempSync5(
|
|
85032
|
-
const tmpPy =
|
|
85716
|
+
const tmp = mkdtempSync5(join80(tmpdir5(), "skill-apply-py-"));
|
|
85717
|
+
const tmpPy = join80(tmp, "check.py");
|
|
85033
85718
|
try {
|
|
85034
85719
|
writeFileSync39(tmpPy, content);
|
|
85035
85720
|
const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -85052,7 +85737,7 @@ function diffSummary(currentDir, files) {
|
|
|
85052
85737
|
if (existsSync80(currentDir)) {
|
|
85053
85738
|
const walk2 = (sub) => {
|
|
85054
85739
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
85055
|
-
const full =
|
|
85740
|
+
const full = join80(sub, ent.name);
|
|
85056
85741
|
const rel = relative2(currentDir, full);
|
|
85057
85742
|
if (ent.isDirectory()) {
|
|
85058
85743
|
walk2(full);
|
|
@@ -85088,7 +85773,7 @@ function writePayload(poolDir, name, files) {
|
|
|
85088
85773
|
if (!existsSync80(poolDir)) {
|
|
85089
85774
|
mkdirSync45(poolDir, { recursive: true, mode: 493 });
|
|
85090
85775
|
}
|
|
85091
|
-
const target =
|
|
85776
|
+
const target = join80(poolDir, name);
|
|
85092
85777
|
let targetIsSymlink = false;
|
|
85093
85778
|
try {
|
|
85094
85779
|
const st = lstatSync8(target);
|
|
@@ -85099,11 +85784,11 @@ function writePayload(poolDir, name, files) {
|
|
|
85099
85784
|
if (targetIsSymlink) {
|
|
85100
85785
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
85101
85786
|
}
|
|
85102
|
-
const staging = mkdtempSync5(
|
|
85787
|
+
const staging = mkdtempSync5(join80(poolDir, `.skill-apply-stage-${name}-`));
|
|
85103
85788
|
let oldRename = null;
|
|
85104
85789
|
try {
|
|
85105
85790
|
for (const [path8, content] of Object.entries(files)) {
|
|
85106
|
-
const full =
|
|
85791
|
+
const full = join80(staging, path8);
|
|
85107
85792
|
mkdirSync45(dirname23(full), { recursive: true, mode: 493 });
|
|
85108
85793
|
const fd = openSync15(full, "wx");
|
|
85109
85794
|
try {
|
|
@@ -85181,7 +85866,7 @@ function registerSkillCommand(program3) {
|
|
|
85181
85866
|
}
|
|
85182
85867
|
const config = loadConfig();
|
|
85183
85868
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
85184
|
-
const currentDir =
|
|
85869
|
+
const currentDir = join80(poolDir, name);
|
|
85185
85870
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
85186
85871
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
85187
85872
|
console.log(diffSummary(currentDir, files));
|
|
@@ -85225,8 +85910,8 @@ import {
|
|
|
85225
85910
|
utimesSync,
|
|
85226
85911
|
writeFileSync as writeFileSync40
|
|
85227
85912
|
} from "node:fs";
|
|
85228
|
-
import { dirname as dirname24, join as
|
|
85229
|
-
import { homedir as
|
|
85913
|
+
import { dirname as dirname24, join as join81, relative as relative3, resolve as resolve48 } from "node:path";
|
|
85914
|
+
import { homedir as homedir47, tmpdir as tmpdir6 } from "node:os";
|
|
85230
85915
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
85231
85916
|
init_helpers();
|
|
85232
85917
|
init_source();
|
|
@@ -85236,10 +85921,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
85236
85921
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
85237
85922
|
function resolveConfigSkillsDir(agent) {
|
|
85238
85923
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
85239
|
-
const candidate = override ? resolve48(override) :
|
|
85924
|
+
const candidate = override ? resolve48(override) : join81(homedir47(), ".switchroom-config");
|
|
85240
85925
|
if (!existsSync81(candidate))
|
|
85241
85926
|
return null;
|
|
85242
|
-
return
|
|
85927
|
+
return join81(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
85243
85928
|
}
|
|
85244
85929
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
85245
85930
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -85257,7 +85942,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
85257
85942
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
85258
85943
|
continue;
|
|
85259
85944
|
try {
|
|
85260
|
-
rmSync17(
|
|
85945
|
+
rmSync17(join81(configSkillsRoot, ent), { recursive: true, force: true });
|
|
85261
85946
|
} catch {}
|
|
85262
85947
|
}
|
|
85263
85948
|
} catch {}
|
|
@@ -85266,7 +85951,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85266
85951
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
85267
85952
|
if (!configSkillsRoot)
|
|
85268
85953
|
return;
|
|
85269
|
-
const dest =
|
|
85954
|
+
const dest = join81(configSkillsRoot, name);
|
|
85270
85955
|
try {
|
|
85271
85956
|
if (liveSkillDir !== null) {
|
|
85272
85957
|
try {
|
|
@@ -85281,19 +85966,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85281
85966
|
if (liveSkillDir === null) {
|
|
85282
85967
|
sweepMirrorPriors(configSkillsRoot);
|
|
85283
85968
|
if (existsSync81(dest)) {
|
|
85284
|
-
const trash =
|
|
85969
|
+
const trash = join81(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
85285
85970
|
renameSync18(dest, trash);
|
|
85286
85971
|
}
|
|
85287
85972
|
return;
|
|
85288
85973
|
}
|
|
85289
85974
|
mkdirSync46(configSkillsRoot, { recursive: true, mode: 493 });
|
|
85290
85975
|
sweepMirrorPriors(configSkillsRoot);
|
|
85291
|
-
const staging = mkdtempSync6(
|
|
85976
|
+
const staging = mkdtempSync6(join81(configSkillsRoot, `.${name}-staging-`));
|
|
85292
85977
|
const walk2 = (src, dst) => {
|
|
85293
85978
|
mkdirSync46(dst, { recursive: true, mode: 493 });
|
|
85294
85979
|
for (const ent of readdirSync31(src, { withFileTypes: true })) {
|
|
85295
|
-
const s =
|
|
85296
|
-
const d =
|
|
85980
|
+
const s = join81(src, ent.name);
|
|
85981
|
+
const d = join81(dst, ent.name);
|
|
85297
85982
|
if (ent.isSymbolicLink())
|
|
85298
85983
|
continue;
|
|
85299
85984
|
if (ent.isDirectory())
|
|
@@ -85305,7 +85990,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85305
85990
|
};
|
|
85306
85991
|
walk2(liveSkillDir, staging);
|
|
85307
85992
|
if (existsSync81(dest)) {
|
|
85308
|
-
const prior =
|
|
85993
|
+
const prior = join81(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
85309
85994
|
renameSync18(dest, prior);
|
|
85310
85995
|
}
|
|
85311
85996
|
renameSync18(staging, dest);
|
|
@@ -85332,13 +86017,13 @@ function resolveAgent(opts) {
|
|
|
85332
86017
|
function resolveAgentsRoot(opts) {
|
|
85333
86018
|
if (opts.root)
|
|
85334
86019
|
return resolve48(opts.root);
|
|
85335
|
-
return
|
|
86020
|
+
return join81(homedir47(), ".switchroom", "agents");
|
|
85336
86021
|
}
|
|
85337
86022
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
85338
|
-
return
|
|
86023
|
+
return join81(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
85339
86024
|
}
|
|
85340
86025
|
function trashDir(agentsRoot, agent) {
|
|
85341
|
-
return
|
|
86026
|
+
return join81(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
85342
86027
|
}
|
|
85343
86028
|
function readStdinSync2() {
|
|
85344
86029
|
const chunks = [];
|
|
@@ -85368,7 +86053,7 @@ function loadFromDir2(dir) {
|
|
|
85368
86053
|
const files = {};
|
|
85369
86054
|
const walk2 = (sub) => {
|
|
85370
86055
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
85371
|
-
const full =
|
|
86056
|
+
const full = join81(sub, ent.name);
|
|
85372
86057
|
if (ent.isSymbolicLink()) {
|
|
85373
86058
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
85374
86059
|
}
|
|
@@ -85421,8 +86106,8 @@ function behavioralValidate(files) {
|
|
|
85421
86106
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
85422
86107
|
}
|
|
85423
86108
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
85424
|
-
const tmp = mkdtempSync6(
|
|
85425
|
-
const tmpPy =
|
|
86109
|
+
const tmp = mkdtempSync6(join81(tmpdir6(), "skill-personal-py-"));
|
|
86110
|
+
const tmpPy = join81(tmp, "check.py");
|
|
85426
86111
|
try {
|
|
85427
86112
|
writeFileSync40(tmpPy, content);
|
|
85428
86113
|
const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -85446,7 +86131,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
85446
86131
|
for (const ent of readdirSync31(trash, { withFileTypes: true })) {
|
|
85447
86132
|
if (!ent.isDirectory())
|
|
85448
86133
|
continue;
|
|
85449
|
-
const entPath =
|
|
86134
|
+
const entPath = join81(trash, ent.name);
|
|
85450
86135
|
try {
|
|
85451
86136
|
const st = statSync33(entPath);
|
|
85452
86137
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -85467,11 +86152,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
85467
86152
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
85468
86153
|
}
|
|
85469
86154
|
mkdirSync46(dirname24(targetDir), { recursive: true, mode: 493 });
|
|
85470
|
-
const staging = mkdtempSync6(
|
|
86155
|
+
const staging = mkdtempSync6(join81(dirname24(targetDir), `.skill-personal-stage-`));
|
|
85471
86156
|
let oldRename = null;
|
|
85472
86157
|
try {
|
|
85473
86158
|
for (const [path8, content] of Object.entries(files)) {
|
|
85474
|
-
const full =
|
|
86159
|
+
const full = join81(staging, path8);
|
|
85475
86160
|
mkdirSync46(dirname24(full), { recursive: true, mode: 493 });
|
|
85476
86161
|
const fd = openSync16(full, "wx");
|
|
85477
86162
|
try {
|
|
@@ -85604,10 +86289,10 @@ function editPersonalAction(name, opts) {
|
|
|
85604
86289
|
}
|
|
85605
86290
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
85606
86291
|
function defaultSharedRoot() {
|
|
85607
|
-
return
|
|
86292
|
+
return join81(homedir47(), ".switchroom", "skills");
|
|
85608
86293
|
}
|
|
85609
86294
|
function defaultBundledRoot() {
|
|
85610
|
-
return
|
|
86295
|
+
return join81(homedir47(), ".switchroom", "skills", "_bundled");
|
|
85611
86296
|
}
|
|
85612
86297
|
function resolveCloneSource(source, opts) {
|
|
85613
86298
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -85617,7 +86302,7 @@ function resolveCloneSource(source, opts) {
|
|
|
85617
86302
|
const tier = m[1];
|
|
85618
86303
|
const slug = m[2];
|
|
85619
86304
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
85620
|
-
const dir =
|
|
86305
|
+
const dir = join81(root, slug);
|
|
85621
86306
|
if (!existsSync81(dir)) {
|
|
85622
86307
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
85623
86308
|
}
|
|
@@ -85633,7 +86318,7 @@ function readSourceFiles(dir) {
|
|
|
85633
86318
|
const skipped = [];
|
|
85634
86319
|
const walk2 = (sub) => {
|
|
85635
86320
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
85636
|
-
const full =
|
|
86321
|
+
const full = join81(sub, ent.name);
|
|
85637
86322
|
if (ent.isSymbolicLink()) {
|
|
85638
86323
|
continue;
|
|
85639
86324
|
}
|
|
@@ -85744,7 +86429,7 @@ function removePersonalAction(name, opts) {
|
|
|
85744
86429
|
const trashRoot = trashDir(agentsRoot, agent);
|
|
85745
86430
|
mkdirSync46(trashRoot, { recursive: true, mode: 493 });
|
|
85746
86431
|
const ts = Date.now();
|
|
85747
|
-
const trashTarget =
|
|
86432
|
+
const trashTarget = join81(trashRoot, `${name}-${ts}`);
|
|
85748
86433
|
renameSync18(target, trashTarget);
|
|
85749
86434
|
const now = new Date(ts);
|
|
85750
86435
|
utimesSync(trashTarget, now, now);
|
|
@@ -85763,7 +86448,7 @@ function listPersonalAction(opts) {
|
|
|
85763
86448
|
const agent = resolveAgent(opts);
|
|
85764
86449
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
85765
86450
|
sweepTrash(agentsRoot, agent);
|
|
85766
|
-
const skillsDir =
|
|
86451
|
+
const skillsDir = join81(agentsRoot, agent, ".claude", "skills");
|
|
85767
86452
|
const personal = [];
|
|
85768
86453
|
if (existsSync81(skillsDir)) {
|
|
85769
86454
|
for (const ent of readdirSync31(skillsDir, { withFileTypes: true })) {
|
|
@@ -85772,7 +86457,7 @@ function listPersonalAction(opts) {
|
|
|
85772
86457
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
85773
86458
|
continue;
|
|
85774
86459
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
85775
|
-
const skillPath =
|
|
86460
|
+
const skillPath = join81(skillsDir, ent.name);
|
|
85776
86461
|
let fileCount = 0;
|
|
85777
86462
|
let totalBytes = 0;
|
|
85778
86463
|
const walk2 = (sub) => {
|
|
@@ -85780,10 +86465,10 @@ function listPersonalAction(opts) {
|
|
|
85780
86465
|
if (e.isFile()) {
|
|
85781
86466
|
fileCount += 1;
|
|
85782
86467
|
try {
|
|
85783
|
-
totalBytes += statSync33(
|
|
86468
|
+
totalBytes += statSync33(join81(sub, e.name)).size;
|
|
85784
86469
|
} catch {}
|
|
85785
86470
|
} else if (e.isDirectory()) {
|
|
85786
|
-
walk2(
|
|
86471
|
+
walk2(join81(sub, e.name));
|
|
85787
86472
|
}
|
|
85788
86473
|
}
|
|
85789
86474
|
};
|
|
@@ -85823,22 +86508,22 @@ function registerSkillPersonalCommands(program3) {
|
|
|
85823
86508
|
init_helpers();
|
|
85824
86509
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
85825
86510
|
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync70, statSync as statSync34 } from "node:fs";
|
|
85826
|
-
import { homedir as
|
|
85827
|
-
import { join as
|
|
86511
|
+
import { homedir as homedir48 } from "node:os";
|
|
86512
|
+
import { join as join82, resolve as resolve49 } from "node:path";
|
|
85828
86513
|
var PERSONAL_PREFIX2 = "personal-";
|
|
85829
86514
|
var BUNDLED_SUBDIR = "_bundled";
|
|
85830
86515
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
85831
86516
|
function defaultAgentsRoot() {
|
|
85832
|
-
return resolve49(
|
|
86517
|
+
return resolve49(homedir48(), ".switchroom/agents");
|
|
85833
86518
|
}
|
|
85834
86519
|
function defaultSharedRoot2() {
|
|
85835
|
-
return resolve49(
|
|
86520
|
+
return resolve49(homedir48(), ".switchroom/skills");
|
|
85836
86521
|
}
|
|
85837
86522
|
function defaultBundledRoot2() {
|
|
85838
|
-
return resolve49(
|
|
86523
|
+
return resolve49(homedir48(), ".switchroom/skills/_bundled");
|
|
85839
86524
|
}
|
|
85840
86525
|
function readSkillFrontmatter(skillDir) {
|
|
85841
|
-
const mdPath =
|
|
86526
|
+
const mdPath = join82(skillDir, "SKILL.md");
|
|
85842
86527
|
if (!existsSync82(mdPath))
|
|
85843
86528
|
return null;
|
|
85844
86529
|
let content;
|
|
@@ -85871,7 +86556,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
85871
86556
|
return { fm: parsed };
|
|
85872
86557
|
}
|
|
85873
86558
|
function statSkillMd(skillDir) {
|
|
85874
|
-
const mdPath =
|
|
86559
|
+
const mdPath = join82(skillDir, "SKILL.md");
|
|
85875
86560
|
try {
|
|
85876
86561
|
const st = statSync34(mdPath);
|
|
85877
86562
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -85882,7 +86567,7 @@ function statSkillMd(skillDir) {
|
|
|
85882
86567
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
85883
86568
|
if (!AGENT_NAME_RE3.test(agent))
|
|
85884
86569
|
return [];
|
|
85885
|
-
const skillsDir =
|
|
86570
|
+
const skillsDir = join82(agentsRoot, agent, ".claude/skills");
|
|
85886
86571
|
if (!existsSync82(skillsDir))
|
|
85887
86572
|
return [];
|
|
85888
86573
|
const out = [];
|
|
@@ -85895,7 +86580,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
85895
86580
|
for (const ent of entries) {
|
|
85896
86581
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
85897
86582
|
continue;
|
|
85898
|
-
const dirPath =
|
|
86583
|
+
const dirPath = join82(skillsDir, ent);
|
|
85899
86584
|
try {
|
|
85900
86585
|
if (!statSync34(dirPath).isDirectory())
|
|
85901
86586
|
continue;
|
|
@@ -85935,7 +86620,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
85935
86620
|
continue;
|
|
85936
86621
|
if (ent.startsWith("."))
|
|
85937
86622
|
continue;
|
|
85938
|
-
const dirPath =
|
|
86623
|
+
const dirPath = join82(sharedRoot, ent);
|
|
85939
86624
|
try {
|
|
85940
86625
|
if (!statSync34(dirPath).isDirectory())
|
|
85941
86626
|
continue;
|
|
@@ -85971,7 +86656,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
85971
86656
|
for (const ent of entries) {
|
|
85972
86657
|
if (ent.startsWith("."))
|
|
85973
86658
|
continue;
|
|
85974
|
-
const dirPath =
|
|
86659
|
+
const dirPath = join82(bundledRoot, ent);
|
|
85975
86660
|
try {
|
|
85976
86661
|
if (!statSync34(dirPath).isDirectory())
|
|
85977
86662
|
continue;
|
|
@@ -86116,8 +86801,8 @@ function registerHostdMcpCommand(program3) {
|
|
|
86116
86801
|
init_source();
|
|
86117
86802
|
init_helpers();
|
|
86118
86803
|
import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync72, writeFileSync as writeFileSync41, statSync as statSync35, copyFileSync as copyFileSync12 } from "node:fs";
|
|
86119
|
-
import { homedir as
|
|
86120
|
-
import { join as
|
|
86804
|
+
import { homedir as homedir49 } from "node:os";
|
|
86805
|
+
import { join as join83 } from "node:path";
|
|
86121
86806
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
86122
86807
|
init_audit_reader();
|
|
86123
86808
|
function resolveHostdImageTag(explicitTag, release) {
|
|
@@ -86230,7 +86915,7 @@ networks:
|
|
|
86230
86915
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
86231
86916
|
`;
|
|
86232
86917
|
}
|
|
86233
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
86918
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir49()) {
|
|
86234
86919
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
86235
86920
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
86236
86921
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -86241,10 +86926,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
|
|
|
86241
86926
|
return resolved;
|
|
86242
86927
|
}
|
|
86243
86928
|
function hostdDir() {
|
|
86244
|
-
return
|
|
86929
|
+
return join83(homedir49(), ".switchroom", "hostd");
|
|
86245
86930
|
}
|
|
86246
86931
|
function hostdComposePath() {
|
|
86247
|
-
return
|
|
86932
|
+
return join83(hostdDir(), "docker-compose.yml");
|
|
86248
86933
|
}
|
|
86249
86934
|
function backupExistingCompose() {
|
|
86250
86935
|
const p = hostdComposePath();
|
|
@@ -86355,7 +87040,7 @@ function doStatus() {
|
|
|
86355
87040
|
for (const name of readdirSync33(dir)) {
|
|
86356
87041
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
86357
87042
|
continue;
|
|
86358
|
-
const sockPath =
|
|
87043
|
+
const sockPath = join83(dir, name, "sock");
|
|
86359
87044
|
if (existsSync84(sockPath)) {
|
|
86360
87045
|
const st = statSync35(sockPath);
|
|
86361
87046
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -86447,8 +87132,8 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
86447
87132
|
init_source();
|
|
86448
87133
|
init_helpers();
|
|
86449
87134
|
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
|
|
86450
|
-
import { homedir as
|
|
86451
|
-
import { join as
|
|
87135
|
+
import { homedir as homedir50 } from "node:os";
|
|
87136
|
+
import { join as join84 } from "node:path";
|
|
86452
87137
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
86453
87138
|
function resolveWebImageTag(explicitTag, release) {
|
|
86454
87139
|
if (explicitTag)
|
|
@@ -86533,10 +87218,10 @@ services:
|
|
|
86533
87218
|
`;
|
|
86534
87219
|
}
|
|
86535
87220
|
function webdDir() {
|
|
86536
|
-
return
|
|
87221
|
+
return join84(homedir50(), ".switchroom", "web");
|
|
86537
87222
|
}
|
|
86538
87223
|
function webdComposePath() {
|
|
86539
|
-
return
|
|
87224
|
+
return join84(webdDir(), "docker-compose.yml");
|
|
86540
87225
|
}
|
|
86541
87226
|
function backupExistingCompose2() {
|
|
86542
87227
|
const p = webdComposePath();
|
|
@@ -86569,7 +87254,7 @@ async function doInstall2(opts, program3) {
|
|
|
86569
87254
|
const cfg = getConfig(program3);
|
|
86570
87255
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
86571
87256
|
const yaml = renderWebComposeFile({
|
|
86572
|
-
hostHome:
|
|
87257
|
+
hostHome: homedir50(),
|
|
86573
87258
|
imageTag,
|
|
86574
87259
|
operatorUid
|
|
86575
87260
|
});
|