switchroom 0.15.25 → 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 +1293 -414
- 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 +179 -18
- package/telegram-plugin/gateway/linear-activity.ts +102 -14
- package/telegram-plugin/tests/linear-agent-activity.test.ts +75 -0
- package/telegram-plugin/tests/linear-create-issue.test.ts +42 -0
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();
|
|
@@ -67478,6 +67495,23 @@ function addWebhookSource(yamlText, agentName, source) {
|
|
|
67478
67495
|
}
|
|
67479
67496
|
return String(doc);
|
|
67480
67497
|
}
|
|
67498
|
+
function addAgentSecret(yamlText, agentName, key) {
|
|
67499
|
+
const doc = import_yaml11.parseDocument(yamlText);
|
|
67500
|
+
ensureAgent(doc, agentName);
|
|
67501
|
+
const existing = doc.getIn(["agents", agentName, "secrets"]);
|
|
67502
|
+
if (import_yaml11.isSeq(existing)) {
|
|
67503
|
+
const seq = existing;
|
|
67504
|
+
for (const item of seq.items) {
|
|
67505
|
+
const v = item.value ?? item;
|
|
67506
|
+
if (v === key)
|
|
67507
|
+
return yamlText;
|
|
67508
|
+
}
|
|
67509
|
+
seq.add(key);
|
|
67510
|
+
} else {
|
|
67511
|
+
doc.setIn(["agents", agentName, "secrets"], [key]);
|
|
67512
|
+
}
|
|
67513
|
+
return String(doc);
|
|
67514
|
+
}
|
|
67481
67515
|
function removeWebhookSource(yamlText, agentName, source) {
|
|
67482
67516
|
const doc = import_yaml11.parseDocument(yamlText);
|
|
67483
67517
|
if (!hasAgent(doc, agentName))
|
|
@@ -67860,6 +67894,22 @@ async function vaultPut(program3, key, value) {
|
|
|
67860
67894
|
setStringSecret(passphrase, vaultPath, key, value);
|
|
67861
67895
|
console.log(source_default.green(`\u2713 Stored secret in vault as '${key}'`));
|
|
67862
67896
|
}
|
|
67897
|
+
async function vaultPutQuiet(program3, key, value) {
|
|
67898
|
+
const configPath = program3.optsWithGlobals().config ?? undefined;
|
|
67899
|
+
const vaultPath = resolveVaultPath(configPath);
|
|
67900
|
+
const passphrase = await getVaultPassphrase();
|
|
67901
|
+
if (!existsSync43(vaultPath))
|
|
67902
|
+
createVault(passphrase, vaultPath);
|
|
67903
|
+
setStringSecret(passphrase, vaultPath, key, value);
|
|
67904
|
+
}
|
|
67905
|
+
async function vaultGet(program3, key) {
|
|
67906
|
+
const configPath = program3.optsWithGlobals().config ?? undefined;
|
|
67907
|
+
const vaultPath = resolveVaultPath(configPath);
|
|
67908
|
+
if (!existsSync43(vaultPath))
|
|
67909
|
+
return null;
|
|
67910
|
+
const passphrase = await getVaultPassphrase();
|
|
67911
|
+
return getStringSecret(passphrase, vaultPath, key);
|
|
67912
|
+
}
|
|
67863
67913
|
function resolveVaultPath(configPath) {
|
|
67864
67914
|
try {
|
|
67865
67915
|
const config = loadConfig(configPath);
|
|
@@ -67988,9 +68038,118 @@ function promptHidden2(prompt) {
|
|
|
67988
68038
|
init_source();
|
|
67989
68039
|
init_helpers();
|
|
67990
68040
|
import { readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "node:fs";
|
|
68041
|
+
|
|
68042
|
+
// src/linear/oauth-refresh.ts
|
|
68043
|
+
var LINEAR_TOKEN_ENDPOINT = "https://api.linear.app/oauth/token";
|
|
68044
|
+
var DEFAULT_REFRESH_SKEW_SEC = 2 * 3600;
|
|
68045
|
+
async function refreshLinearAppToken(bundle, opts = {}) {
|
|
68046
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
68047
|
+
const nowSec = opts.nowSec ?? (() => Math.floor(Date.now() / 1000));
|
|
68048
|
+
const form = new URLSearchParams({
|
|
68049
|
+
grant_type: "refresh_token",
|
|
68050
|
+
refresh_token: bundle.refreshToken,
|
|
68051
|
+
client_id: bundle.clientId,
|
|
68052
|
+
client_secret: bundle.clientSecret
|
|
68053
|
+
});
|
|
68054
|
+
let resp;
|
|
68055
|
+
try {
|
|
68056
|
+
resp = await fetchImpl(LINEAR_TOKEN_ENDPOINT, {
|
|
68057
|
+
method: "POST",
|
|
68058
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
68059
|
+
body: form.toString()
|
|
68060
|
+
});
|
|
68061
|
+
} catch (err) {
|
|
68062
|
+
return { ok: false, reason: "network", detail: err.message };
|
|
68063
|
+
}
|
|
68064
|
+
if (!resp.ok) {
|
|
68065
|
+
const txt = await resp.text().catch(() => "");
|
|
68066
|
+
const revoked = resp.status === 400 || /invalid_grant|invalid_token/i.test(txt);
|
|
68067
|
+
return {
|
|
68068
|
+
ok: false,
|
|
68069
|
+
reason: revoked ? "revoked" : "http_error",
|
|
68070
|
+
detail: `HTTP ${resp.status}${txt ? ` ${txt.slice(0, 200)}` : ""}`
|
|
68071
|
+
};
|
|
68072
|
+
}
|
|
68073
|
+
let json;
|
|
68074
|
+
try {
|
|
68075
|
+
json = await resp.json();
|
|
68076
|
+
} catch {
|
|
68077
|
+
return { ok: false, reason: "bad_response", detail: "non-JSON token response" };
|
|
68078
|
+
}
|
|
68079
|
+
const accessToken = json.access_token;
|
|
68080
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
68081
|
+
return { ok: false, reason: "bad_response", detail: "no access_token in response" };
|
|
68082
|
+
}
|
|
68083
|
+
const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 86400;
|
|
68084
|
+
const rotated = typeof json.refresh_token === "string" && json.refresh_token.length > 0 ? json.refresh_token : bundle.refreshToken;
|
|
68085
|
+
return {
|
|
68086
|
+
ok: true,
|
|
68087
|
+
accessToken,
|
|
68088
|
+
refreshToken: rotated,
|
|
68089
|
+
expiresAt: nowSec() + expiresIn,
|
|
68090
|
+
...typeof json.scope === "string" ? { scope: json.scope } : {}
|
|
68091
|
+
};
|
|
68092
|
+
}
|
|
68093
|
+
function parseBundle(raw) {
|
|
68094
|
+
if (raw == null || raw === "")
|
|
68095
|
+
return null;
|
|
68096
|
+
let o;
|
|
68097
|
+
try {
|
|
68098
|
+
o = JSON.parse(raw);
|
|
68099
|
+
} catch {
|
|
68100
|
+
return null;
|
|
68101
|
+
}
|
|
68102
|
+
if (typeof o.client_id === "string" && typeof o.client_secret === "string" && typeof o.refresh_token === "string" && o.client_id.length > 0 && o.client_secret.length > 0 && o.refresh_token.length > 0) {
|
|
68103
|
+
return {
|
|
68104
|
+
clientId: o.client_id,
|
|
68105
|
+
clientSecret: o.client_secret,
|
|
68106
|
+
refreshToken: o.refresh_token,
|
|
68107
|
+
...typeof o.expires_at === "number" ? { expiresAt: o.expires_at } : {}
|
|
68108
|
+
};
|
|
68109
|
+
}
|
|
68110
|
+
return null;
|
|
68111
|
+
}
|
|
68112
|
+
function serializeBundle(b) {
|
|
68113
|
+
return JSON.stringify({
|
|
68114
|
+
client_id: b.clientId,
|
|
68115
|
+
client_secret: b.clientSecret,
|
|
68116
|
+
refresh_token: b.refreshToken,
|
|
68117
|
+
...b.expiresAt != null ? { expires_at: b.expiresAt } : {}
|
|
68118
|
+
});
|
|
68119
|
+
}
|
|
68120
|
+
async function performLinearRefresh(io) {
|
|
68121
|
+
const raw = await io.readBundle();
|
|
68122
|
+
const bundle = parseBundle(raw);
|
|
68123
|
+
if (!bundle) {
|
|
68124
|
+
return { ok: false, reason: "no_bundle", detail: "no/invalid refresh bundle" };
|
|
68125
|
+
}
|
|
68126
|
+
const res = await refreshLinearAppToken(bundle, {
|
|
68127
|
+
...io.fetchImpl ? { fetchImpl: io.fetchImpl } : {},
|
|
68128
|
+
...io.nowSec ? { nowSec: io.nowSec } : {}
|
|
68129
|
+
});
|
|
68130
|
+
if (!res.ok)
|
|
68131
|
+
return { ok: false, reason: res.reason, detail: res.detail };
|
|
68132
|
+
try {
|
|
68133
|
+
await io.writeBundle(serializeBundle({
|
|
68134
|
+
clientId: bundle.clientId,
|
|
68135
|
+
clientSecret: bundle.clientSecret,
|
|
68136
|
+
refreshToken: res.refreshToken,
|
|
68137
|
+
expiresAt: res.expiresAt
|
|
68138
|
+
}));
|
|
68139
|
+
await io.writeToken(res.accessToken);
|
|
68140
|
+
} catch (err) {
|
|
68141
|
+
return { ok: false, reason: "persist_failed", detail: err.message };
|
|
68142
|
+
}
|
|
68143
|
+
return { ok: true, accessToken: res.accessToken, expiresAt: res.expiresAt };
|
|
68144
|
+
}
|
|
68145
|
+
|
|
68146
|
+
// src/cli/linear-agent.ts
|
|
68147
|
+
function bundleKeyFor(agent) {
|
|
68148
|
+
return `linear/${agent}/oauth`;
|
|
68149
|
+
}
|
|
67991
68150
|
function registerLinearAgentCommand(program3) {
|
|
67992
68151
|
const linear = program3.command("linear-agent").description("Install an agent into a Linear workspace as a first-class app actor (#2298) \u2014 @-mentionable, delegate-assignable, agent sessions wake it instantly.");
|
|
67993
|
-
linear.command("setup").description("Provision <agent> as a Linear agent. Vault-stores the Linear OAuth app token (actor=app) under 'linear/<agent>/token' and enables the linear_agent block in switchroom.yaml. The OAuth browser authorize step is printed as instructions (it can't run headless); pass the already-obtained --token.").requiredOption("--agent <name>", "Agent name (must exist in switchroom.yaml)").requiredOption("--token <token>", "The Linear OAuth app token (actor=app), obtained out-of-band via the browser authorize step. Stored in the vault, never in switchroom.yaml.").option("--client-id <id>", "Linear OAuth app client id (
|
|
68152
|
+
linear.command("setup").description("Provision <agent> as a Linear agent. Vault-stores the Linear OAuth app token (actor=app) under 'linear/<agent>/token' and enables the linear_agent block in switchroom.yaml. The OAuth browser authorize step is printed as instructions (it can't run headless); pass the already-obtained --token.").requiredOption("--agent <name>", "Agent name (must exist in switchroom.yaml)").requiredOption("--token <token>", "The Linear OAuth app token (actor=app), obtained out-of-band via the browser authorize step. Stored in the vault, never in switchroom.yaml.").option("--client-id <id>", "Linear OAuth app client id. Stored (with --client-secret + --refresh-token) to enable unattended token refresh.").option("--client-secret <secret>", "Linear OAuth app client secret. Stored in the vault (with --client-id + --refresh-token) so the token can be refreshed without a browser re-auth.").option("--refresh-token <token>", "The refresh_token from the OAuth exchange. Stored so an expired access token self-heals (see 'linear-agent refresh').").option("--token-expires-in <seconds>", "expires_in from the OAuth token response (seconds). Records when the access token expires so refresh runs proactively. Defaults to 86400.").option("--redirect-uri <uri>", "OAuth redirect URI registered on the Linear app (for the authorize-URL hint).").option("--workspace-id <id>", "Optional Linear workspace (organization) id to record in config.").option("--webhook-base <url>", "Base URL of the switchroom web server (e.g. https://hooks.switchroom.ai). Used to print the webhook URL to register in Linear. Defaults to a placeholder.").option("--dry-run", "Print the YAML diff + instructions without writing or vaulting anything").action(withConfigError(async (opts) => {
|
|
67994
68153
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
|
|
67995
68154
|
fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
|
|
67996
68155
|
}
|
|
@@ -67998,10 +68157,26 @@ function registerLinearAgentCommand(program3) {
|
|
|
67998
68157
|
fail2("--token must be a non-empty Linear app token.");
|
|
67999
68158
|
}
|
|
68000
68159
|
const vaultKey = `linear/${opts.agent}/token`;
|
|
68160
|
+
const bundleKey = bundleKeyFor(opts.agent);
|
|
68161
|
+
const canRefresh = Boolean(opts.refreshToken && opts.clientId && opts.clientSecret);
|
|
68001
68162
|
if (!opts.dryRun) {
|
|
68002
68163
|
await vaultPut(program3, vaultKey, opts.token);
|
|
68164
|
+
if (canRefresh) {
|
|
68165
|
+
const expiresIn = Number.parseInt(opts.tokenExpiresIn ?? "", 10);
|
|
68166
|
+
const ttl = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 86400;
|
|
68167
|
+
const bundle = serializeBundle({
|
|
68168
|
+
clientId: opts.clientId,
|
|
68169
|
+
clientSecret: opts.clientSecret,
|
|
68170
|
+
refreshToken: opts.refreshToken,
|
|
68171
|
+
expiresAt: Math.floor(Date.now() / 1000) + ttl
|
|
68172
|
+
});
|
|
68173
|
+
await vaultPutQuiet(program3, bundleKey, bundle);
|
|
68174
|
+
}
|
|
68003
68175
|
} else {
|
|
68004
68176
|
console.log(source_default.gray(`[dry-run] would store the Linear token in the vault as '${vaultKey}'`));
|
|
68177
|
+
if (canRefresh) {
|
|
68178
|
+
console.log(source_default.gray(`[dry-run] would store the refresh bundle as '${bundleKey}' (enables auto-refresh)`));
|
|
68179
|
+
}
|
|
68005
68180
|
}
|
|
68006
68181
|
const path4 = getConfigPath(program3);
|
|
68007
68182
|
const before = readFileSync39(path4, "utf-8");
|
|
@@ -68011,6 +68186,10 @@ function registerLinearAgentCommand(program3) {
|
|
|
68011
68186
|
token: `vault:${vaultKey}`,
|
|
68012
68187
|
...opts.workspaceId ? { workspaceId: opts.workspaceId } : {}
|
|
68013
68188
|
});
|
|
68189
|
+
if (canRefresh) {
|
|
68190
|
+
after = addAgentSecret(after, opts.agent, bundleKey);
|
|
68191
|
+
after = addAgentSecret(after, opts.agent, vaultKey);
|
|
68192
|
+
}
|
|
68014
68193
|
} catch (err) {
|
|
68015
68194
|
fail2(err.message);
|
|
68016
68195
|
}
|
|
@@ -68021,10 +68200,42 @@ function registerLinearAgentCommand(program3) {
|
|
|
68021
68200
|
writeFileSync22(path4, after, "utf-8");
|
|
68022
68201
|
console.log(source_default.green(`\u2713 Enabled linear-agent for agent '${opts.agent}'`));
|
|
68023
68202
|
console.log(source_default.gray(` Vault key: ${vaultKey}`));
|
|
68203
|
+
if (canRefresh) {
|
|
68204
|
+
console.log(source_default.green(`\u2713 Auto-refresh enabled \u2014 refresh bundle stored at '${bundleKey}'`));
|
|
68205
|
+
console.log(source_default.gray(` Granted ACL: '${bundleKey}' + '${vaultKey}' added to agents.${opts.agent}.secrets[] (agent rotates them in-container on a 401).`));
|
|
68206
|
+
} else {
|
|
68207
|
+
console.log(source_default.yellow(`\u26a0 No refresh bundle stored \u2014 the access token will expire (~24h) and need a manual re-auth.`));
|
|
68208
|
+
console.log(source_default.gray(` To enable auto-refresh, re-run with --refresh-token <rt> --client-id <id> --client-secret <secret> --token-expires-in <sec>.`));
|
|
68209
|
+
}
|
|
68024
68210
|
console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
|
|
68025
68211
|
}
|
|
68026
68212
|
printLinearInstructions(opts, vaultKey);
|
|
68027
68213
|
}));
|
|
68214
|
+
linear.command("refresh").description("Refresh <agent>'s Linear app token using the stored refresh bundle (linear/<agent>/oauth). Exchanges the refresh_token for a fresh access token, writes it to linear/<agent>/token, and rotates the stored refresh_token + expiry. Use to recover an expired token or seed automation. Host-side write \u2014 the running agent picks it up on its next broker re-mirror / restart.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").action(withConfigError(async (opts) => {
|
|
68215
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
|
|
68216
|
+
fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
|
|
68217
|
+
}
|
|
68218
|
+
const bundleKey = bundleKeyFor(opts.agent);
|
|
68219
|
+
const res = await performLinearRefresh({
|
|
68220
|
+
readBundle: () => vaultGet(program3, bundleKey),
|
|
68221
|
+
writeToken: (t) => vaultPutQuiet(program3, `linear/${opts.agent}/token`, t),
|
|
68222
|
+
writeBundle: (json) => vaultPutQuiet(program3, bundleKey, json)
|
|
68223
|
+
});
|
|
68224
|
+
if (!res.ok) {
|
|
68225
|
+
if (res.reason === "no_bundle") {
|
|
68226
|
+
fail2(`No refresh bundle at '${bundleKey}'. Provision one via 'linear-agent setup --agent ${opts.agent} ` + `--token <t> --refresh-token <rt> --client-id <id> --client-secret <secret>'.`);
|
|
68227
|
+
}
|
|
68228
|
+
if (res.reason === "revoked") {
|
|
68229
|
+
fail2(`Refresh token is dead (revoked/expired) \u2014 re-authorize in a browser (actor=app) and re-run setup with the new --refresh-token. (${res.detail})`);
|
|
68230
|
+
}
|
|
68231
|
+
fail2(`Refresh failed (${res.reason}): ${res.detail}`);
|
|
68232
|
+
}
|
|
68233
|
+
if (res.ok) {
|
|
68234
|
+
const hours = Math.max(1, Math.round((res.expiresAt - Date.now() / 1000) / 3600));
|
|
68235
|
+
console.log(source_default.green(`\u2713 Refreshed Linear token for '${opts.agent}' (expires in ~${hours}h).`));
|
|
68236
|
+
console.log(source_default.gray(` Written to vault:linear/${opts.agent}/token (+ rotated bundle). Restart the agent or wait for the broker to re-mirror.`));
|
|
68237
|
+
}
|
|
68238
|
+
}));
|
|
68028
68239
|
linear.command("set-team").description("Set (or clear) the default Linear team captured issues file into for <agent>. Only needed when the workspace has multiple teams \u2014 a single-team workspace auto-resolves. Pass --clear to remove the default.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").option("--team <id>", "Linear team id new captured issues default to.").option("--clear", "Remove the configured default team (revert to auto-resolve).").action(withConfigError(async (opts) => {
|
|
68029
68240
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
|
|
68030
68241
|
fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
|
|
@@ -68680,7 +68891,6 @@ init_source();
|
|
|
68680
68891
|
init_merge();
|
|
68681
68892
|
init_loader();
|
|
68682
68893
|
init_client();
|
|
68683
|
-
init_lifecycle();
|
|
68684
68894
|
import {
|
|
68685
68895
|
readFileSync as readFileSync46,
|
|
68686
68896
|
existsSync as existsSync50,
|
|
@@ -68691,9 +68901,8 @@ import {
|
|
|
68691
68901
|
writeSync as writeSync6,
|
|
68692
68902
|
constants as fsConstants3
|
|
68693
68903
|
} from "node:fs";
|
|
68694
|
-
import { resolve as resolve28, extname, join as
|
|
68695
|
-
import { homedir as
|
|
68696
|
-
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";
|
|
68697
68906
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
|
|
68698
68907
|
|
|
68699
68908
|
// src/web/api.ts
|
|
@@ -68702,7 +68911,71 @@ init_manager();
|
|
|
68702
68911
|
init_hindsight();
|
|
68703
68912
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
68704
68913
|
import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
|
|
68705
|
-
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
|
|
68706
68979
|
init_audit_reader();
|
|
68707
68980
|
|
|
68708
68981
|
// src/scheduler/dispatch.ts
|
|
@@ -68765,6 +69038,8 @@ function readRecentFires(jsonlPath) {
|
|
|
68765
69038
|
|
|
68766
69039
|
// src/web/api.ts
|
|
68767
69040
|
init_client3();
|
|
69041
|
+
init_client();
|
|
69042
|
+
import { homedir as homedir23 } from "node:os";
|
|
68768
69043
|
|
|
68769
69044
|
// node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
|
|
68770
69045
|
import { dirname as dirname8, posix, sep as sep2 } from "path";
|
|
@@ -73500,6 +73775,121 @@ async function proposeConfigEditViaHostd(args) {
|
|
|
73500
73775
|
}
|
|
73501
73776
|
}
|
|
73502
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
|
+
|
|
73503
73893
|
// src/web/api.ts
|
|
73504
73894
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
73505
73895
|
|
|
@@ -73731,7 +74121,78 @@ function listSubagents(db, opts = {}) {
|
|
|
73731
74121
|
return rows.map(mapSubagentRow);
|
|
73732
74122
|
}
|
|
73733
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
|
+
|
|
73734
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
|
+
}
|
|
73735
74196
|
function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
|
|
73736
74197
|
try {
|
|
73737
74198
|
const f = resolve27(agentsDir, name, "telegram", ".bridge-alive");
|
|
@@ -73740,11 +74201,25 @@ function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
|
|
|
73740
74201
|
return false;
|
|
73741
74202
|
}
|
|
73742
74203
|
}
|
|
73743
|
-
|
|
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;
|
|
73744
74217
|
const statuses = getAllAgentStatuses(config);
|
|
73745
74218
|
const authStatuses = getAllAuthStatuses(config);
|
|
73746
74219
|
const agentsDir = resolveAgentsDir(config);
|
|
73747
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;
|
|
73748
74223
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
73749
74224
|
const status = statuses[name];
|
|
73750
74225
|
const auth = authStatuses[name];
|
|
@@ -73752,11 +74227,14 @@ function handleGetAgents(config) {
|
|
|
73752
74227
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
73753
74228
|
const primaryAccount = resolved.auth?.override ?? config.auth?.active;
|
|
73754
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;
|
|
73755
74233
|
agents.push({
|
|
73756
74234
|
name,
|
|
73757
74235
|
active,
|
|
73758
|
-
uptime
|
|
73759
|
-
memory
|
|
74236
|
+
uptime,
|
|
74237
|
+
memory,
|
|
73760
74238
|
extends: agentConfig.extends ?? "default",
|
|
73761
74239
|
topic_name: agentConfig.topic_name,
|
|
73762
74240
|
topic_emoji: agentConfig.topic_emoji,
|
|
@@ -73767,54 +74245,40 @@ function handleGetAgents(config) {
|
|
|
73767
74245
|
timeUntilExpiry: auth?.timeUntilExpiry,
|
|
73768
74246
|
expiresAt: auth?.expiresAt
|
|
73769
74247
|
},
|
|
73770
|
-
memoryCollection: collection
|
|
74248
|
+
memoryCollection: collection,
|
|
74249
|
+
lastTurnAt: readLastTurn(agentsDir, name)
|
|
73771
74250
|
});
|
|
73772
74251
|
}
|
|
73773
74252
|
return agents;
|
|
73774
74253
|
}
|
|
73775
|
-
function
|
|
73776
|
-
|
|
73777
|
-
startAgent(name);
|
|
73778
|
-
captureEvent("agent_started", { agent: name, source: "web_api" });
|
|
73779
|
-
return { ok: true };
|
|
73780
|
-
} catch (err) {
|
|
73781
|
-
captureException(err, { action: "start_agent", agent: name });
|
|
73782
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73783
|
-
}
|
|
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>"}\`.`;
|
|
73784
74256
|
}
|
|
73785
|
-
|
|
73786
|
-
|
|
73787
|
-
|
|
73788
|
-
|
|
73789
|
-
|
|
73790
|
-
|
|
73791
|
-
|
|
73792
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73793
|
-
}
|
|
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 };
|
|
73794
74264
|
}
|
|
73795
|
-
function handleRestartAgent(name) {
|
|
74265
|
+
async function handleRestartAgent(name, deps = {}) {
|
|
74266
|
+
const restart = deps.restart ?? restartAgentViaHostd;
|
|
73796
74267
|
try {
|
|
73797
|
-
|
|
73798
|
-
|
|
73799
|
-
|
|
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 };
|
|
73800
74274
|
} catch (err) {
|
|
73801
74275
|
captureException(err, { action: "restart_agent", agent: name });
|
|
73802
74276
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
73803
74277
|
}
|
|
73804
74278
|
}
|
|
73805
|
-
function handleGetLogs(name, lines = 50) {
|
|
73806
|
-
const
|
|
73807
|
-
|
|
73808
|
-
return { ok: false, error: res.error.message };
|
|
73809
|
-
}
|
|
73810
|
-
if (res.status !== 0) {
|
|
73811
|
-
const stderr = (res.stderr ?? "").trim();
|
|
73812
|
-
return {
|
|
73813
|
-
ok: false,
|
|
73814
|
-
error: stderr || `docker logs exited ${res.status ?? "non-zero"}`
|
|
73815
|
-
};
|
|
73816
|
-
}
|
|
73817
|
-
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);
|
|
73818
74282
|
}
|
|
73819
74283
|
function handleGetTurns(config, agentName, limit) {
|
|
73820
74284
|
try {
|
|
@@ -73858,6 +74322,31 @@ var quotaCache = new Map;
|
|
|
73858
74322
|
function quotaEntryFresh(e, now) {
|
|
73859
74323
|
return !!e && now - e.fetchedAt < QUOTA_CACHE_TTL_MS;
|
|
73860
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
|
+
}
|
|
73861
74350
|
async function handleGetAccounts(config, home2) {
|
|
73862
74351
|
const infos = getAccountInfos(Date.now(), home2);
|
|
73863
74352
|
const brokerAccounts = new Map;
|
|
@@ -73884,12 +74373,15 @@ async function handleGetAccounts(config, home2) {
|
|
|
73884
74373
|
usedBy.sort();
|
|
73885
74374
|
}
|
|
73886
74375
|
const now = Date.now();
|
|
73887
|
-
const
|
|
74376
|
+
const brokerState = brokerAccounts.get(info.label) ?? null;
|
|
74377
|
+
const view = deriveAccountQuotaView(brokerState, quotaCache.get(info.label), now);
|
|
73888
74378
|
return {
|
|
73889
74379
|
...info,
|
|
73890
|
-
quota:
|
|
73891
|
-
|
|
73892
|
-
|
|
74380
|
+
health: brokerState?.exhausted ? "quota-exhausted" : info.health,
|
|
74381
|
+
quota: brokerState,
|
|
74382
|
+
quotaUsage: view.quotaUsage,
|
|
74383
|
+
quotaUsageSource: view.quotaUsageSource,
|
|
74384
|
+
quotaStale: view.quotaStale,
|
|
73893
74385
|
usedBy
|
|
73894
74386
|
};
|
|
73895
74387
|
});
|
|
@@ -74012,6 +74504,12 @@ function inspectEnv(container, keys) {
|
|
|
74012
74504
|
} catch {}
|
|
74013
74505
|
return out;
|
|
74014
74506
|
}
|
|
74507
|
+
var HOSTD_NOISE_OPS = new Set([
|
|
74508
|
+
"agent_smoke",
|
|
74509
|
+
"get_status",
|
|
74510
|
+
"agent_status",
|
|
74511
|
+
"agent_logs"
|
|
74512
|
+
]);
|
|
74015
74513
|
async function handleGetSystemHealth(config, home2) {
|
|
74016
74514
|
const broker = { reachable: false };
|
|
74017
74515
|
try {
|
|
@@ -74063,7 +74561,7 @@ async function handleGetSystemHealth(config, home2) {
|
|
|
74063
74561
|
if (existsSync47(logPath)) {
|
|
74064
74562
|
hostd.auditLogPresent = true;
|
|
74065
74563
|
const raw = readFileSync43(logPath, "utf-8");
|
|
74066
|
-
hostd.recent = readAndFilter(raw, {}, 10);
|
|
74564
|
+
hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
|
|
74067
74565
|
}
|
|
74068
74566
|
} catch (err) {
|
|
74069
74567
|
hostd.error = err instanceof Error ? err.message : String(err);
|
|
@@ -74084,8 +74582,10 @@ async function handleGetGoogleAccounts(config) {
|
|
|
74084
74582
|
}
|
|
74085
74583
|
});
|
|
74086
74584
|
} catch (err) {
|
|
74087
|
-
if (!(err instanceof AuthBrokerUnreachableError))
|
|
74088
|
-
|
|
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
|
+
}
|
|
74089
74589
|
}
|
|
74090
74590
|
const cfgAccounts = config.google_accounts ?? {};
|
|
74091
74591
|
const keys = new Set([
|
|
@@ -74122,8 +74622,10 @@ async function handleGetMicrosoftAccounts(config) {
|
|
|
74122
74622
|
}
|
|
74123
74623
|
});
|
|
74124
74624
|
} catch (err) {
|
|
74125
|
-
if (!(err instanceof AuthBrokerUnreachableError))
|
|
74126
|
-
|
|
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
|
+
}
|
|
74127
74629
|
}
|
|
74128
74630
|
const cfgAccounts = config.microsoft_accounts ?? {};
|
|
74129
74631
|
const keys = new Set([
|
|
@@ -74328,7 +74830,16 @@ function handleSetConnectionAccess(configPath, config, args, deps = {}) {
|
|
|
74328
74830
|
});
|
|
74329
74831
|
return { ok: true, changed: true, requestId, pendingApproval: true };
|
|
74330
74832
|
}
|
|
74331
|
-
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
|
+
}
|
|
74332
74843
|
const entries = collectScheduleEntries(config);
|
|
74333
74844
|
const agentsDir = resolveAgentsDir(config);
|
|
74334
74845
|
const recentByAgent = {};
|
|
@@ -74338,7 +74849,12 @@ function handleGetSchedule(config) {
|
|
|
74338
74849
|
if (rows.length > 0)
|
|
74339
74850
|
recentByAgent[agent] = rows.slice(-10);
|
|
74340
74851
|
}
|
|
74341
|
-
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
|
+
};
|
|
74342
74858
|
}
|
|
74343
74859
|
async function handleGetApprovals() {
|
|
74344
74860
|
const opSock = resolveKernelOperatorSocket();
|
|
@@ -74360,6 +74876,48 @@ async function handleGetApprovals() {
|
|
|
74360
74876
|
const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
|
|
74361
74877
|
return { reachable: true, decisions: sorted };
|
|
74362
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
|
+
}
|
|
74363
74921
|
async function handleGetMemoryHealth(config, opts) {
|
|
74364
74922
|
const url = config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
|
|
74365
74923
|
const now = opts?.now ?? new Date;
|
|
@@ -74381,6 +74939,8 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74381
74939
|
const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
|
|
74382
74940
|
const stale = staleMentalModels(h.mentalModels, 7, now);
|
|
74383
74941
|
const corrupted = corruptedMentalModels(h.mentalModels);
|
|
74942
|
+
const newestAge = ageDays(h.newestDocumentAt, now);
|
|
74943
|
+
const pendingStuck = h.pendingOperations > 0 && newestAge !== null && newestAge > 1;
|
|
74384
74944
|
let status = "ok";
|
|
74385
74945
|
let statusDetail = "facts flowing";
|
|
74386
74946
|
if (!h.ok) {
|
|
@@ -74392,6 +74952,9 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74392
74952
|
} else if (gaps.length > 0) {
|
|
74393
74953
|
status = "fail";
|
|
74394
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`;
|
|
74395
74958
|
} else if (stale.length > 0) {
|
|
74396
74959
|
status = "warn";
|
|
74397
74960
|
statusDetail = `${stale.length} mental model(s) not refreshed in >7d`;
|
|
@@ -74410,6 +74973,7 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74410
74973
|
newestDocumentAt: h.newestDocumentAt,
|
|
74411
74974
|
recentUnextractedCount: gaps.length,
|
|
74412
74975
|
oldestUnextractedAt: gaps[0]?.createdAt ?? null,
|
|
74976
|
+
unextractedDocIds: gaps.slice(0, 20).map((d) => d.id),
|
|
74413
74977
|
mentalModels: h.mentalModels,
|
|
74414
74978
|
staleMentalModelCount: stale.length,
|
|
74415
74979
|
corruptedMentalModelNames: corrupted.map((m) => m.name),
|
|
@@ -74420,11 +74984,236 @@ async function handleGetMemoryHealth(config, opts) {
|
|
|
74420
74984
|
rows.sort((a, b) => a.bank.localeCompare(b.bank));
|
|
74421
74985
|
return { reachable, url, banks: rows };
|
|
74422
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
|
+
}
|
|
74423
75212
|
|
|
74424
75213
|
// src/web/webhook-handler.ts
|
|
74425
75214
|
import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as readFileSync45, writeFileSync as writeFileSync26 } from "fs";
|
|
74426
|
-
import { join as
|
|
74427
|
-
import { homedir as
|
|
75215
|
+
import { join as join46 } from "path";
|
|
75216
|
+
import { homedir as homedir25 } from "os";
|
|
74428
75217
|
|
|
74429
75218
|
// src/web/webhook-verify.ts
|
|
74430
75219
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -74630,12 +75419,12 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
74630
75419
|
|
|
74631
75420
|
// src/web/webhook-edge.ts
|
|
74632
75421
|
import { existsSync as existsSync48, readFileSync as readFileSync44 } from "fs";
|
|
74633
|
-
import { join as
|
|
74634
|
-
import { homedir as
|
|
75422
|
+
import { join as join45 } from "path";
|
|
75423
|
+
import { homedir as homedir24 } from "os";
|
|
74635
75424
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
74636
75425
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
74637
75426
|
function edgeSecretPath() {
|
|
74638
|
-
return
|
|
75427
|
+
return join45(homedir24(), ".switchroom", "webhook-edge-secret");
|
|
74639
75428
|
}
|
|
74640
75429
|
function loadEdgeSecret(path4) {
|
|
74641
75430
|
const p = path4 ?? edgeSecretPath();
|
|
@@ -74698,8 +75487,8 @@ var agentDedupCache = new Map;
|
|
|
74698
75487
|
function createFileDedupStore(resolveAgentDir) {
|
|
74699
75488
|
return {
|
|
74700
75489
|
check(agent, deliveryId, now) {
|
|
74701
|
-
const telegramDir =
|
|
74702
|
-
const filePath =
|
|
75490
|
+
const telegramDir = join46(resolveAgentDir(agent), "telegram");
|
|
75491
|
+
const filePath = join46(telegramDir, "webhook-dedup.json");
|
|
74703
75492
|
if (!agentDedupCache.has(agent)) {
|
|
74704
75493
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
74705
75494
|
}
|
|
@@ -74751,7 +75540,7 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
|
|
|
74751
75540
|
return true;
|
|
74752
75541
|
}
|
|
74753
75542
|
function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
74754
|
-
const issuesPath =
|
|
75543
|
+
const issuesPath = join46(telegramDir, "issues.jsonl");
|
|
74755
75544
|
try {
|
|
74756
75545
|
mkdirSync28(telegramDir, { recursive: true });
|
|
74757
75546
|
const record = {
|
|
@@ -74776,7 +75565,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
74776
75565
|
async function handleWebhookIngest(args, deps = {}) {
|
|
74777
75566
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
74778
75567
|
const now = (deps.now ?? Date.now)();
|
|
74779
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) =>
|
|
75568
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir25(), ".switchroom", "agents", a));
|
|
74780
75569
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
74781
75570
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
74782
75571
|
if (!args.agentExists) {
|
|
@@ -74843,7 +75632,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74843
75632
|
if (retryAfter !== null) {
|
|
74844
75633
|
if (!args.viaGateway) {
|
|
74845
75634
|
const agentDir2 = resolveAgentDir(args.agent);
|
|
74846
|
-
const telegramDir2 =
|
|
75635
|
+
const telegramDir2 = join46(agentDir2, "telegram");
|
|
74847
75636
|
if (shouldWriteThrottleIssue(args.agent, source, now)) {
|
|
74848
75637
|
writeThrottleIssue(args.agent, source, now, telegramDir2, log);
|
|
74849
75638
|
}
|
|
@@ -74863,7 +75652,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74863
75652
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
|
|
74864
75653
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
74865
75654
|
if (args.viaGateway) {
|
|
74866
|
-
const socketPath =
|
|
75655
|
+
const socketPath = join46(resolveAgentDir(args.agent), "telegram", "webhook.sock");
|
|
74867
75656
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
74868
75657
|
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
74869
75658
|
let resp;
|
|
@@ -74902,8 +75691,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74902
75691
|
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
74903
75692
|
}
|
|
74904
75693
|
const agentDir = resolveAgentDir(args.agent);
|
|
74905
|
-
const telegramDir =
|
|
74906
|
-
const logPath =
|
|
75694
|
+
const telegramDir = join46(agentDir, "telegram");
|
|
75695
|
+
const logPath = join46(telegramDir, "webhook-events.jsonl");
|
|
74907
75696
|
try {
|
|
74908
75697
|
mkdirSync28(telegramDir, { recursive: true });
|
|
74909
75698
|
const record = {
|
|
@@ -74926,6 +75715,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
74926
75715
|
}
|
|
74927
75716
|
|
|
74928
75717
|
// src/web/server.ts
|
|
75718
|
+
var LOG_POLL_INTERVAL_MS = 3000;
|
|
75719
|
+
var LOG_POLL_TAIL_LINES = 400;
|
|
74929
75720
|
var MIME_TYPES = {
|
|
74930
75721
|
".html": "text/html",
|
|
74931
75722
|
".css": "text/css",
|
|
@@ -74956,8 +75747,8 @@ function resolveWebToken() {
|
|
|
74956
75747
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
74957
75748
|
if (fromEnv && fromEnv.length > 0)
|
|
74958
75749
|
return fromEnv;
|
|
74959
|
-
const home2 = process.env.HOME ??
|
|
74960
|
-
const tokenPath =
|
|
75750
|
+
const home2 = process.env.HOME ?? homedir26();
|
|
75751
|
+
const tokenPath = join47(home2, ".switchroom", "web-token");
|
|
74961
75752
|
if (existsSync50(tokenPath)) {
|
|
74962
75753
|
const existing = readFileSync46(tokenPath, "utf8").trim();
|
|
74963
75754
|
if (existing.length > 0)
|
|
@@ -75042,7 +75833,7 @@ function checkWsAuth(req, token, server) {
|
|
|
75042
75833
|
return presented !== null && constantTimeEqual(presented, token);
|
|
75043
75834
|
}
|
|
75044
75835
|
function loadWebhookSecrets() {
|
|
75045
|
-
const path4 =
|
|
75836
|
+
const path4 = join47(homedir26(), ".switchroom", "webhook-secrets.json");
|
|
75046
75837
|
if (!existsSync50(path4))
|
|
75047
75838
|
return {};
|
|
75048
75839
|
try {
|
|
@@ -75118,6 +75909,9 @@ function parseRoute(pathname, method) {
|
|
|
75118
75909
|
if (method === "GET" && pathname === "/api/agents") {
|
|
75119
75910
|
return { handler: "getAgents", params: {} };
|
|
75120
75911
|
}
|
|
75912
|
+
if (method === "GET" && pathname === "/api/summary") {
|
|
75913
|
+
return { handler: "getSummary", params: {} };
|
|
75914
|
+
}
|
|
75121
75915
|
const logsMatch = pathname.match(/^\/api\/agents\/([^/]+)\/logs$/);
|
|
75122
75916
|
if (method === "GET" && logsMatch) {
|
|
75123
75917
|
return { handler: "getLogs", params: { name: logsMatch[1] } };
|
|
@@ -75148,6 +75942,15 @@ function parseRoute(pathname, method) {
|
|
|
75148
75942
|
if (method === "GET" && pathname === "/api/memory-health") {
|
|
75149
75943
|
return { handler: "getMemoryHealth", params: {} };
|
|
75150
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
|
+
}
|
|
75151
75954
|
if (method === "GET" && pathname === "/api/google-accounts") {
|
|
75152
75955
|
return { handler: "getGoogleAccounts", params: {} };
|
|
75153
75956
|
}
|
|
@@ -75163,6 +75966,9 @@ function parseRoute(pathname, method) {
|
|
|
75163
75966
|
if (method === "GET" && pathname === "/api/approvals") {
|
|
75164
75967
|
return { handler: "getApprovals", params: {} };
|
|
75165
75968
|
}
|
|
75969
|
+
if (method === "GET" && pathname === "/api/grants") {
|
|
75970
|
+
return { handler: "getGrants", params: {} };
|
|
75971
|
+
}
|
|
75166
75972
|
if (method === "GET" && pathname === "/api/accounts") {
|
|
75167
75973
|
return { handler: "getAccounts", params: {} };
|
|
75168
75974
|
}
|
|
@@ -75215,6 +76021,14 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75215
76021
|
return config;
|
|
75216
76022
|
}
|
|
75217
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 });
|
|
75218
76032
|
const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
75219
76033
|
const server = Bun.serve({
|
|
75220
76034
|
port,
|
|
@@ -75251,7 +76065,16 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75251
76065
|
return authError;
|
|
75252
76066
|
switch (route.handler) {
|
|
75253
76067
|
case "getAgents":
|
|
75254
|
-
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
|
+
})))();
|
|
75255
76078
|
case "getLogs": {
|
|
75256
76079
|
const agentName = route.params.name;
|
|
75257
76080
|
if (!config.agents[agentName]) {
|
|
@@ -75259,28 +76082,28 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75259
76082
|
}
|
|
75260
76083
|
const rawLines = Number(url.searchParams.get("lines") ?? "50");
|
|
75261
76084
|
const lines = Number.isInteger(rawLines) && rawLines >= 1 && rawLines <= 1e4 ? rawLines : 50;
|
|
75262
|
-
return jsonResponse(handleGetLogs(agentName, lines));
|
|
76085
|
+
return (async () => jsonResponse(await handleGetLogs(agentName, lines)))();
|
|
75263
76086
|
}
|
|
75264
76087
|
case "startAgent": {
|
|
75265
76088
|
const agentName = route.params.name;
|
|
75266
76089
|
if (!config.agents[agentName]) {
|
|
75267
76090
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75268
76091
|
}
|
|
75269
|
-
return jsonResponse(handleStartAgent(agentName));
|
|
76092
|
+
return (async () => jsonResponse(await handleStartAgent(agentName)))();
|
|
75270
76093
|
}
|
|
75271
76094
|
case "stopAgent": {
|
|
75272
76095
|
const agentName = route.params.name;
|
|
75273
76096
|
if (!config.agents[agentName]) {
|
|
75274
76097
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75275
76098
|
}
|
|
75276
|
-
return jsonResponse(handleStopAgent(agentName));
|
|
76099
|
+
return (async () => jsonResponse(await handleStopAgent(agentName)))();
|
|
75277
76100
|
}
|
|
75278
76101
|
case "restartAgent": {
|
|
75279
76102
|
const agentName = route.params.name;
|
|
75280
76103
|
if (!config.agents[agentName]) {
|
|
75281
76104
|
return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
|
|
75282
76105
|
}
|
|
75283
|
-
return jsonResponse(handleRestartAgent(agentName));
|
|
76106
|
+
return (async () => jsonResponse(await handleRestartAgent(agentName)))();
|
|
75284
76107
|
}
|
|
75285
76108
|
case "getTurns": {
|
|
75286
76109
|
const agentName = route.params.name;
|
|
@@ -75308,9 +76131,9 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75308
76131
|
return jsonResponse(result.subagents);
|
|
75309
76132
|
}
|
|
75310
76133
|
case "getSystemHealth":
|
|
75311
|
-
return (async () => jsonResponse(await
|
|
76134
|
+
return (async () => jsonResponse(withStamp(await cachedSystemHealth())))();
|
|
75312
76135
|
case "getMemoryHealth":
|
|
75313
|
-
return (async () => jsonResponse(await
|
|
76136
|
+
return (async () => jsonResponse(withStamp(await cachedMemoryHealth())))();
|
|
75314
76137
|
case "getGoogleAccounts":
|
|
75315
76138
|
return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
|
|
75316
76139
|
case "getMicrosoftAccounts":
|
|
@@ -75318,11 +76141,13 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75318
76141
|
case "getNotionWorkspace":
|
|
75319
76142
|
return jsonResponse(handleGetNotionWorkspace(freshConfig()));
|
|
75320
76143
|
case "getSchedule":
|
|
75321
|
-
return jsonResponse(
|
|
76144
|
+
return (async () => jsonResponse(withStamp(await cachedSchedule())))();
|
|
75322
76145
|
case "getApprovals":
|
|
75323
|
-
return (async () => jsonResponse(await
|
|
76146
|
+
return (async () => jsonResponse(withStamp(await cachedApprovals())))();
|
|
76147
|
+
case "getGrants":
|
|
76148
|
+
return (async () => jsonResponse(withStamp(await cachedGrants())))();
|
|
75324
76149
|
case "getAccounts":
|
|
75325
|
-
return (async () => jsonResponse(await
|
|
76150
|
+
return (async () => jsonResponse((await cachedAccounts()).value))();
|
|
75326
76151
|
case "useAccount": {
|
|
75327
76152
|
return (async () => {
|
|
75328
76153
|
let body;
|
|
@@ -75386,6 +76211,45 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75386
76211
|
return jsonResponse(result, result.ok ? 200 : 502);
|
|
75387
76212
|
})();
|
|
75388
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
|
+
}
|
|
75389
76253
|
case "getAgentAccounts": {
|
|
75390
76254
|
const agentName = route.params.name;
|
|
75391
76255
|
if (!config.agents[agentName]) {
|
|
@@ -75403,7 +76267,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75403
76267
|
}
|
|
75404
76268
|
}
|
|
75405
76269
|
let filePath = pathname === "/" ? "/index.html" : pathname;
|
|
75406
|
-
const fullPath =
|
|
76270
|
+
const fullPath = join47(uiDir, filePath);
|
|
75407
76271
|
if (!existsSync50(fullPath)) {
|
|
75408
76272
|
return new Response("Not Found", { status: 404 });
|
|
75409
76273
|
}
|
|
@@ -75427,64 +76291,79 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
75427
76291
|
websocket: {
|
|
75428
76292
|
open(_ws) {},
|
|
75429
76293
|
close(ws) {
|
|
75430
|
-
const
|
|
75431
|
-
if (
|
|
75432
|
-
|
|
75433
|
-
ws.
|
|
76294
|
+
const interval = ws._logInterval;
|
|
76295
|
+
if (interval) {
|
|
76296
|
+
clearInterval(interval);
|
|
76297
|
+
ws._logInterval = null;
|
|
75434
76298
|
}
|
|
75435
76299
|
},
|
|
75436
76300
|
message(ws, message) {
|
|
76301
|
+
let data;
|
|
75437
76302
|
try {
|
|
75438
|
-
|
|
75439
|
-
|
|
75440
|
-
|
|
75441
|
-
|
|
75442
|
-
|
|
75443
|
-
|
|
75444
|
-
|
|
75445
|
-
|
|
75446
|
-
|
|
75447
|
-
|
|
75448
|
-
|
|
75449
|
-
|
|
75450
|
-
|
|
75451
|
-
|
|
75452
|
-
|
|
75453
|
-
|
|
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) {
|
|
75454
76332
|
try {
|
|
75455
76333
|
ws.send(JSON.stringify({
|
|
75456
76334
|
type: "log_error",
|
|
75457
76335
|
agent: agentName,
|
|
75458
|
-
data:
|
|
75459
|
-
`
|
|
76336
|
+
data: err instanceof Error ? err.message : String(err)
|
|
75460
76337
|
}));
|
|
75461
76338
|
} catch {}
|
|
75462
|
-
|
|
75463
|
-
|
|
76339
|
+
clearLogInterval();
|
|
76340
|
+
return;
|
|
76341
|
+
}
|
|
76342
|
+
if (result.ok) {
|
|
75464
76343
|
try {
|
|
75465
76344
|
ws.send(JSON.stringify({
|
|
75466
76345
|
type: "log",
|
|
75467
76346
|
agent: agentName,
|
|
75468
|
-
data:
|
|
76347
|
+
data: result.logs,
|
|
76348
|
+
replace: true
|
|
75469
76349
|
}));
|
|
75470
76350
|
} catch {
|
|
75471
|
-
|
|
76351
|
+
clearLogInterval();
|
|
75472
76352
|
}
|
|
75473
|
-
}
|
|
75474
|
-
child.stderr.on("data", (chunk) => {
|
|
76353
|
+
} else {
|
|
75475
76354
|
try {
|
|
75476
76355
|
ws.send(JSON.stringify({
|
|
75477
76356
|
type: "log_error",
|
|
75478
76357
|
agent: agentName,
|
|
75479
|
-
data:
|
|
76358
|
+
data: result.error
|
|
75480
76359
|
}));
|
|
75481
|
-
} catch {
|
|
75482
|
-
|
|
75483
|
-
|
|
75484
|
-
|
|
75485
|
-
|
|
75486
|
-
|
|
75487
|
-
}
|
|
76360
|
+
} catch {}
|
|
76361
|
+
clearLogInterval();
|
|
76362
|
+
}
|
|
76363
|
+
};
|
|
76364
|
+
poll();
|
|
76365
|
+
ws._logInterval = setInterval(() => void poll(), LOG_POLL_INTERVAL_MS);
|
|
76366
|
+
}
|
|
75488
76367
|
}
|
|
75489
76368
|
}
|
|
75490
76369
|
});
|
|
@@ -76447,8 +77326,8 @@ init_loader();
|
|
|
76447
77326
|
init_lifecycle();
|
|
76448
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";
|
|
76449
77328
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
76450
|
-
import { join as
|
|
76451
|
-
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";
|
|
76452
77331
|
|
|
76453
77332
|
// src/cli/release-yaml.ts
|
|
76454
77333
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
@@ -76487,13 +77366,13 @@ function defaultPersistPin(configPath) {
|
|
|
76487
77366
|
} catch {}
|
|
76488
77367
|
};
|
|
76489
77368
|
}
|
|
76490
|
-
var DEFAULT_COMPOSE_PATH =
|
|
77369
|
+
var DEFAULT_COMPOSE_PATH = join61(homedir37(), ".switchroom", "compose", "docker-compose.yml");
|
|
76491
77370
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
76492
77371
|
let dir = dirname14(scriptPath);
|
|
76493
77372
|
for (let i = 0;i < 12; i++) {
|
|
76494
|
-
if (existsSync58(
|
|
77373
|
+
if (existsSync58(join61(dir, ".git"))) {
|
|
76495
77374
|
try {
|
|
76496
|
-
const pkg = JSON.parse(readFileSync52(
|
|
77375
|
+
const pkg = JSON.parse(readFileSync52(join61(dir, "package.json"), "utf-8"));
|
|
76497
77376
|
if (pkg.name === "switchroom")
|
|
76498
77377
|
return true;
|
|
76499
77378
|
} catch {}
|
|
@@ -76653,7 +77532,7 @@ function planUpdate(opts) {
|
|
|
76653
77532
|
return;
|
|
76654
77533
|
}
|
|
76655
77534
|
const source = resolve34(import.meta.dirname, "../../skills");
|
|
76656
|
-
const dest =
|
|
77535
|
+
const dest = join61(homedir37(), ".switchroom", "skills", "_bundled");
|
|
76657
77536
|
if (!existsSync58(source)) {
|
|
76658
77537
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
76659
77538
|
`);
|
|
@@ -76767,7 +77646,7 @@ function defaultStatusProbe(composePath) {
|
|
|
76767
77646
|
} catch {}
|
|
76768
77647
|
let dir = dirname14(scriptPath);
|
|
76769
77648
|
for (let i = 0;i < 8; i++) {
|
|
76770
|
-
const pkgPath =
|
|
77649
|
+
const pkgPath = join61(dir, "package.json");
|
|
76771
77650
|
if (existsSync58(pkgPath)) {
|
|
76772
77651
|
try {
|
|
76773
77652
|
const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
|
|
@@ -77209,7 +78088,7 @@ init_helpers();
|
|
|
77209
78088
|
init_lifecycle();
|
|
77210
78089
|
import { execSync as execSync4 } from "node:child_process";
|
|
77211
78090
|
import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
|
|
77212
|
-
import { dirname as dirname15, join as
|
|
78091
|
+
import { dirname as dirname15, join as join62 } from "node:path";
|
|
77213
78092
|
function getClaudeCodeVersion() {
|
|
77214
78093
|
try {
|
|
77215
78094
|
const out = execSync4("claude --version 2>/dev/null", {
|
|
@@ -77259,11 +78138,11 @@ function formatUptime3(timestamp) {
|
|
|
77259
78138
|
function locateSwitchroomInstallDir() {
|
|
77260
78139
|
let dir = import.meta.dirname;
|
|
77261
78140
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
77262
|
-
const pkgPath =
|
|
78141
|
+
const pkgPath = join62(dir, "package.json");
|
|
77263
78142
|
if (existsSync59(pkgPath)) {
|
|
77264
78143
|
try {
|
|
77265
78144
|
const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
|
|
77266
|
-
if (pkg.name === "switchroom" && existsSync59(
|
|
78145
|
+
if (pkg.name === "switchroom" && existsSync59(join62(dir, ".git"))) {
|
|
77267
78146
|
return dir;
|
|
77268
78147
|
}
|
|
77269
78148
|
} catch {}
|
|
@@ -77500,7 +78379,7 @@ import {
|
|
|
77500
78379
|
writeFileSync as writeFileSync28,
|
|
77501
78380
|
writeSync as writeSync7
|
|
77502
78381
|
} from "node:fs";
|
|
77503
|
-
import { join as
|
|
78382
|
+
import { join as join63 } from "node:path";
|
|
77504
78383
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
77505
78384
|
import { execSync as execSync5 } from "node:child_process";
|
|
77506
78385
|
|
|
@@ -77898,7 +78777,7 @@ function redactedMarker(ruleId) {
|
|
|
77898
78777
|
var ISSUES_FILE = "issues.jsonl";
|
|
77899
78778
|
var ISSUES_LOCK = "issues.lock";
|
|
77900
78779
|
function readAll(stateDir) {
|
|
77901
|
-
const path4 =
|
|
78780
|
+
const path4 = join63(stateDir, ISSUES_FILE);
|
|
77902
78781
|
if (!existsSync60(path4))
|
|
77903
78782
|
return [];
|
|
77904
78783
|
let raw;
|
|
@@ -77976,7 +78855,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
77976
78855
|
});
|
|
77977
78856
|
}
|
|
77978
78857
|
function resolve37(stateDir, fingerprint, nowFn = Date.now) {
|
|
77979
|
-
if (!existsSync60(
|
|
78858
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
77980
78859
|
return 0;
|
|
77981
78860
|
return withLock(stateDir, () => {
|
|
77982
78861
|
const all = readAll(stateDir);
|
|
@@ -77994,7 +78873,7 @@ function resolve37(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
77994
78873
|
});
|
|
77995
78874
|
}
|
|
77996
78875
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
77997
|
-
if (!existsSync60(
|
|
78876
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
77998
78877
|
return 0;
|
|
77999
78878
|
return withLock(stateDir, () => {
|
|
78000
78879
|
const all = readAll(stateDir);
|
|
@@ -78012,7 +78891,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
78012
78891
|
});
|
|
78013
78892
|
}
|
|
78014
78893
|
function prune(stateDir, opts = {}) {
|
|
78015
|
-
if (!existsSync60(
|
|
78894
|
+
if (!existsSync60(join63(stateDir, ISSUES_FILE)))
|
|
78016
78895
|
return 0;
|
|
78017
78896
|
return withLock(stateDir, () => {
|
|
78018
78897
|
const all = readAll(stateDir);
|
|
@@ -78045,7 +78924,7 @@ function ensureDir(stateDir) {
|
|
|
78045
78924
|
mkdirSync33(stateDir, { recursive: true });
|
|
78046
78925
|
}
|
|
78047
78926
|
function writeAll(stateDir, events) {
|
|
78048
|
-
const path4 =
|
|
78927
|
+
const path4 = join63(stateDir, ISSUES_FILE);
|
|
78049
78928
|
sweepOrphanTmpFiles(stateDir);
|
|
78050
78929
|
const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
|
|
78051
78930
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
@@ -78067,7 +78946,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
78067
78946
|
for (const entry of entries) {
|
|
78068
78947
|
if (!entry.startsWith(TMP_PREFIX))
|
|
78069
78948
|
continue;
|
|
78070
|
-
const tmpPath =
|
|
78949
|
+
const tmpPath = join63(stateDir, entry);
|
|
78071
78950
|
try {
|
|
78072
78951
|
const stat = statSync28(tmpPath);
|
|
78073
78952
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -78079,7 +78958,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
78079
78958
|
var LOCK_RETRY_MS = 25;
|
|
78080
78959
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
78081
78960
|
function withLock(stateDir, fn) {
|
|
78082
|
-
const lockPath =
|
|
78961
|
+
const lockPath = join63(stateDir, ISSUES_LOCK);
|
|
78083
78962
|
const startedAt = Date.now();
|
|
78084
78963
|
let fd = null;
|
|
78085
78964
|
while (fd === null) {
|
|
@@ -78363,8 +79242,8 @@ function relTime(deltaMs) {
|
|
|
78363
79242
|
// src/cli/deps.ts
|
|
78364
79243
|
init_source();
|
|
78365
79244
|
import { existsSync as existsSync63 } from "node:fs";
|
|
78366
|
-
import { homedir as
|
|
78367
|
-
import { join as
|
|
79245
|
+
import { homedir as homedir40 } from "node:os";
|
|
79246
|
+
import { join as join66, resolve as resolve38 } from "node:path";
|
|
78368
79247
|
|
|
78369
79248
|
// src/deps/python.ts
|
|
78370
79249
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -78375,8 +79254,8 @@ import {
|
|
|
78375
79254
|
rmSync as rmSync13,
|
|
78376
79255
|
writeFileSync as writeFileSync29
|
|
78377
79256
|
} from "node:fs";
|
|
78378
|
-
import { dirname as dirname16, join as
|
|
78379
|
-
import { homedir as
|
|
79257
|
+
import { dirname as dirname16, join as join64 } from "node:path";
|
|
79258
|
+
import { homedir as homedir38 } from "node:os";
|
|
78380
79259
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
78381
79260
|
|
|
78382
79261
|
class PythonEnvError extends Error {
|
|
@@ -78388,7 +79267,7 @@ class PythonEnvError extends Error {
|
|
|
78388
79267
|
}
|
|
78389
79268
|
}
|
|
78390
79269
|
function defaultPythonCacheRoot() {
|
|
78391
|
-
return
|
|
79270
|
+
return join64(homedir38(), ".switchroom", "deps", "python");
|
|
78392
79271
|
}
|
|
78393
79272
|
function hashFile(path4) {
|
|
78394
79273
|
return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
|
|
@@ -78400,11 +79279,11 @@ function ensurePythonEnv(opts) {
|
|
|
78400
79279
|
if (!existsSync61(requirementsPath)) {
|
|
78401
79280
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
78402
79281
|
}
|
|
78403
|
-
const venvDir =
|
|
78404
|
-
const stampPath =
|
|
78405
|
-
const binDir =
|
|
78406
|
-
const pythonBin =
|
|
78407
|
-
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");
|
|
78408
79287
|
const targetHash = hashFile(requirementsPath);
|
|
78409
79288
|
if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
|
|
78410
79289
|
const existingHash = readFileSync56(stampPath, "utf8").trim();
|
|
@@ -78463,8 +79342,8 @@ import {
|
|
|
78463
79342
|
rmSync as rmSync14,
|
|
78464
79343
|
writeFileSync as writeFileSync30
|
|
78465
79344
|
} from "node:fs";
|
|
78466
|
-
import { dirname as dirname17, join as
|
|
78467
|
-
import { homedir as
|
|
79345
|
+
import { dirname as dirname17, join as join65 } from "node:path";
|
|
79346
|
+
import { homedir as homedir39 } from "node:os";
|
|
78468
79347
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
78469
79348
|
|
|
78470
79349
|
class NodeEnvError extends Error {
|
|
@@ -78487,7 +79366,7 @@ var LOCKFILES_FOR = {
|
|
|
78487
79366
|
npm: ["package-lock.json"]
|
|
78488
79367
|
};
|
|
78489
79368
|
function defaultNodeCacheRoot() {
|
|
78490
|
-
return
|
|
79369
|
+
return join65(homedir39(), ".switchroom", "deps", "node");
|
|
78491
79370
|
}
|
|
78492
79371
|
function hashDepInputs(packageJsonPath) {
|
|
78493
79372
|
const sourceDir = dirname17(packageJsonPath);
|
|
@@ -78496,7 +79375,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
78496
79375
|
`);
|
|
78497
79376
|
hasher.update(readFileSync57(packageJsonPath));
|
|
78498
79377
|
for (const lockName of ALL_LOCKFILES) {
|
|
78499
|
-
const lockPath =
|
|
79378
|
+
const lockPath = join65(sourceDir, lockName);
|
|
78500
79379
|
if (existsSync62(lockPath)) {
|
|
78501
79380
|
hasher.update(`
|
|
78502
79381
|
`);
|
|
@@ -78516,10 +79395,10 @@ function ensureNodeEnv(opts) {
|
|
|
78516
79395
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
78517
79396
|
}
|
|
78518
79397
|
const sourceDir = dirname17(packageJsonPath);
|
|
78519
|
-
const envDir =
|
|
78520
|
-
const stampPath =
|
|
78521
|
-
const nodeModulesDir =
|
|
78522
|
-
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");
|
|
78523
79402
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
78524
79403
|
if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
|
|
78525
79404
|
const existingHash = readFileSync57(stampPath, "utf8").trim();
|
|
@@ -78537,12 +79416,12 @@ function ensureNodeEnv(opts) {
|
|
|
78537
79416
|
rmSync14(envDir, { recursive: true, force: true });
|
|
78538
79417
|
}
|
|
78539
79418
|
mkdirSync35(envDir, { recursive: true });
|
|
78540
|
-
copyFileSync9(packageJsonPath,
|
|
79419
|
+
copyFileSync9(packageJsonPath, join65(envDir, "package.json"));
|
|
78541
79420
|
let copiedLockfile = false;
|
|
78542
79421
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
78543
|
-
const lockPath =
|
|
79422
|
+
const lockPath = join65(sourceDir, lockName);
|
|
78544
79423
|
if (existsSync62(lockPath)) {
|
|
78545
|
-
copyFileSync9(lockPath,
|
|
79424
|
+
copyFileSync9(lockPath, join65(envDir, lockName));
|
|
78546
79425
|
copiedLockfile = true;
|
|
78547
79426
|
}
|
|
78548
79427
|
}
|
|
@@ -78571,7 +79450,7 @@ function ensureNodeEnv(opts) {
|
|
|
78571
79450
|
|
|
78572
79451
|
// src/cli/deps.ts
|
|
78573
79452
|
function builtinSkillsRoot() {
|
|
78574
|
-
return resolve38(
|
|
79453
|
+
return resolve38(homedir40(), ".switchroom/skills/_bundled");
|
|
78575
79454
|
}
|
|
78576
79455
|
function registerDepsCommand(program3) {
|
|
78577
79456
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -78581,13 +79460,13 @@ function registerDepsCommand(program3) {
|
|
|
78581
79460
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
78582
79461
|
process.exit(1);
|
|
78583
79462
|
}
|
|
78584
|
-
const skillDir =
|
|
79463
|
+
const skillDir = join66(skillsRoot, skill);
|
|
78585
79464
|
if (!existsSync63(skillDir)) {
|
|
78586
79465
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
78587
79466
|
process.exit(1);
|
|
78588
79467
|
}
|
|
78589
|
-
const requirementsPath =
|
|
78590
|
-
const packageJsonPath =
|
|
79468
|
+
const requirementsPath = join66(skillDir, "requirements.txt");
|
|
79469
|
+
const packageJsonPath = join66(skillDir, "package.json");
|
|
78591
79470
|
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync63(requirementsPath));
|
|
78592
79471
|
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync63(packageJsonPath));
|
|
78593
79472
|
let did = 0;
|
|
@@ -79542,7 +80421,7 @@ init_helpers();
|
|
|
79542
80421
|
init_loader();
|
|
79543
80422
|
init_merge();
|
|
79544
80423
|
import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync58, writeFileSync as writeFileSync31 } from "node:fs";
|
|
79545
|
-
import { join as
|
|
80424
|
+
import { join as join67, resolve as resolve40 } from "node:path";
|
|
79546
80425
|
init_schema();
|
|
79547
80426
|
function resolveSoulTargetOrExit(program3, agentName) {
|
|
79548
80427
|
const config = getConfig(program3);
|
|
@@ -79566,7 +80445,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
79566
80445
|
profileName,
|
|
79567
80446
|
profilePath,
|
|
79568
80447
|
workspaceDir,
|
|
79569
|
-
soulPath:
|
|
80448
|
+
soulPath: join67(workspaceDir, "SOUL.md"),
|
|
79570
80449
|
soul: merged.soul
|
|
79571
80450
|
};
|
|
79572
80451
|
}
|
|
@@ -79633,7 +80512,7 @@ function registerSoulCommand(program3) {
|
|
|
79633
80512
|
init_helpers();
|
|
79634
80513
|
init_loader();
|
|
79635
80514
|
import { existsSync as existsSync66, readFileSync as readFileSync59, readdirSync as readdirSync23, statSync as statSync29 } from "node:fs";
|
|
79636
|
-
import { resolve as resolve41, join as
|
|
80515
|
+
import { resolve as resolve41, join as join68 } from "node:path";
|
|
79637
80516
|
import { createHash as createHash13 } from "node:crypto";
|
|
79638
80517
|
init_merge();
|
|
79639
80518
|
init_hindsight();
|
|
@@ -79644,7 +80523,7 @@ function estimateTokens(bytes) {
|
|
|
79644
80523
|
return Math.round(bytes / 3.7);
|
|
79645
80524
|
}
|
|
79646
80525
|
function readMcpServerNames(agentDir) {
|
|
79647
|
-
const mcpPath =
|
|
80526
|
+
const mcpPath = join68(agentDir, ".mcp.json");
|
|
79648
80527
|
if (!existsSync66(mcpPath))
|
|
79649
80528
|
return [];
|
|
79650
80529
|
try {
|
|
@@ -79658,7 +80537,7 @@ function sha256(content) {
|
|
|
79658
80537
|
return createHash13("sha256").update(content).digest("hex").slice(0, 16);
|
|
79659
80538
|
}
|
|
79660
80539
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
79661
|
-
const projectsDir =
|
|
80540
|
+
const projectsDir = join68(claudeConfigDir, "projects");
|
|
79662
80541
|
if (!existsSync66(projectsDir))
|
|
79663
80542
|
return;
|
|
79664
80543
|
try {
|
|
@@ -79667,8 +80546,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
79667
80546
|
for (const entry of entries) {
|
|
79668
80547
|
if (!entry.isDirectory())
|
|
79669
80548
|
continue;
|
|
79670
|
-
const projectPath =
|
|
79671
|
-
const transcriptPath =
|
|
80549
|
+
const projectPath = join68(projectsDir, entry.name);
|
|
80550
|
+
const transcriptPath = join68(projectPath, "transcript.jsonl");
|
|
79672
80551
|
if (!existsSync66(transcriptPath))
|
|
79673
80552
|
continue;
|
|
79674
80553
|
const stat3 = statSync29(transcriptPath);
|
|
@@ -79737,11 +80616,11 @@ function registerDebugCommand(program3) {
|
|
|
79737
80616
|
process.exit(1);
|
|
79738
80617
|
}
|
|
79739
80618
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
79740
|
-
const claudeConfigDir =
|
|
79741
|
-
const claudeMdPath =
|
|
79742
|
-
const soulMdPath =
|
|
79743
|
-
const workspaceSoulMdPath =
|
|
79744
|
-
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");
|
|
79745
80624
|
const lastN = parseInt(opts.last, 10);
|
|
79746
80625
|
if (isNaN(lastN) || lastN < 1) {
|
|
79747
80626
|
console.error("--last must be a positive integer");
|
|
@@ -79870,9 +80749,9 @@ function registerDebugCommand(program3) {
|
|
|
79870
80749
|
const soulMdBytes = soulMdContent.length;
|
|
79871
80750
|
const perTurnBytes = dynamicResult.concatenated.length;
|
|
79872
80751
|
const userBytes = userMessage?.text.length ?? 0;
|
|
79873
|
-
const fleetDir =
|
|
79874
|
-
const fleetInvPath =
|
|
79875
|
-
const fleetClaudePath =
|
|
80752
|
+
const fleetDir = join68(agentsDir, "..", "fleet");
|
|
80753
|
+
const fleetInvPath = join68(fleetDir, "switchroom-invariants.md");
|
|
80754
|
+
const fleetClaudePath = join68(fleetDir, "CLAUDE.md");
|
|
79876
80755
|
const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync59(fleetInvPath, "utf-8").length : 0;
|
|
79877
80756
|
const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync59(fleetClaudePath, "utf-8").length : 0;
|
|
79878
80757
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
@@ -79908,8 +80787,8 @@ init_source();
|
|
|
79908
80787
|
// src/worktree/claim.ts
|
|
79909
80788
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
79910
80789
|
import { closeSync as closeSync12, mkdirSync as mkdirSync37, openSync as openSync12, existsSync as existsSync68, unlinkSync as unlinkSync13 } from "node:fs";
|
|
79911
|
-
import { join as
|
|
79912
|
-
import { homedir as
|
|
80790
|
+
import { join as join70, resolve as resolve43 } from "node:path";
|
|
80791
|
+
import { homedir as homedir42 } from "node:os";
|
|
79913
80792
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
79914
80793
|
|
|
79915
80794
|
// src/worktree/registry.ts
|
|
@@ -79922,13 +80801,13 @@ import {
|
|
|
79922
80801
|
existsSync as existsSync67,
|
|
79923
80802
|
renameSync as renameSync13
|
|
79924
80803
|
} from "node:fs";
|
|
79925
|
-
import { join as
|
|
79926
|
-
import { homedir as
|
|
80804
|
+
import { join as join69, resolve as resolve42 } from "node:path";
|
|
80805
|
+
import { homedir as homedir41 } from "node:os";
|
|
79927
80806
|
function registryDir() {
|
|
79928
|
-
return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ??
|
|
80807
|
+
return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join69(homedir41(), ".switchroom", "worktrees"));
|
|
79929
80808
|
}
|
|
79930
80809
|
function recordPath(id) {
|
|
79931
|
-
return
|
|
80810
|
+
return join69(registryDir(), `${id}.json`);
|
|
79932
80811
|
}
|
|
79933
80812
|
function ensureDir2() {
|
|
79934
80813
|
mkdirSync36(registryDir(), { recursive: true });
|
|
@@ -79979,7 +80858,7 @@ function acquireRepoLock(repoPath) {
|
|
|
79979
80858
|
const lockDir = registryDir();
|
|
79980
80859
|
mkdirSync37(lockDir, { recursive: true });
|
|
79981
80860
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
79982
|
-
const lockPath =
|
|
80861
|
+
const lockPath = join70(lockDir, `.lock-${lockName}`);
|
|
79983
80862
|
const deadline = Date.now() + 5000;
|
|
79984
80863
|
let fd = null;
|
|
79985
80864
|
while (fd === null) {
|
|
@@ -80006,7 +80885,7 @@ function acquireRepoLock(repoPath) {
|
|
|
80006
80885
|
}
|
|
80007
80886
|
var DEFAULT_CONCURRENCY = 5;
|
|
80008
80887
|
function worktreesBaseDir() {
|
|
80009
|
-
return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ??
|
|
80888
|
+
return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ?? join70(homedir42(), ".switchroom", "worktree-checkouts"));
|
|
80010
80889
|
}
|
|
80011
80890
|
function shortId() {
|
|
80012
80891
|
return randomBytes13(4).toString("hex");
|
|
@@ -80028,7 +80907,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
80028
80907
|
}
|
|
80029
80908
|
function expandHome(p) {
|
|
80030
80909
|
if (p.startsWith("~/"))
|
|
80031
|
-
return
|
|
80910
|
+
return join70(homedir42(), p.slice(2));
|
|
80032
80911
|
return p;
|
|
80033
80912
|
}
|
|
80034
80913
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -80056,7 +80935,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
80056
80935
|
branch = `task/${taskSuffix}-${id}`;
|
|
80057
80936
|
const baseDir = worktreesBaseDir();
|
|
80058
80937
|
mkdirSync37(baseDir, { recursive: true });
|
|
80059
|
-
worktreePath =
|
|
80938
|
+
worktreePath = join70(baseDir, `${id}-${taskSuffix}`);
|
|
80060
80939
|
const now = new Date().toISOString();
|
|
80061
80940
|
const record2 = {
|
|
80062
80941
|
id,
|
|
@@ -80311,7 +81190,7 @@ import {
|
|
|
80311
81190
|
rmSync as rmSync15,
|
|
80312
81191
|
writeFileSync as writeFileSync33
|
|
80313
81192
|
} from "node:fs";
|
|
80314
|
-
import { join as
|
|
81193
|
+
import { join as join71 } from "node:path";
|
|
80315
81194
|
function encodeCredentialsFilename(email) {
|
|
80316
81195
|
const SAFE = new Set([
|
|
80317
81196
|
..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
@@ -80501,16 +81380,16 @@ function resolveCredentialsDir(env2) {
|
|
|
80501
81380
|
if (explicit && explicit.length > 0)
|
|
80502
81381
|
return explicit;
|
|
80503
81382
|
const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
|
|
80504
|
-
return
|
|
81383
|
+
return join71(stateBase, "google-workspace-mcp", "credentials");
|
|
80505
81384
|
}
|
|
80506
81385
|
function writeSeedFile(dir, email, seed) {
|
|
80507
81386
|
mkdirSync38(dir, { recursive: true, mode: 448 });
|
|
80508
81387
|
chmodSync9(dir, 448);
|
|
80509
81388
|
for (const name of readdirSync25(dir)) {
|
|
80510
|
-
rmSync15(
|
|
81389
|
+
rmSync15(join71(dir, name), { force: true, recursive: true });
|
|
80511
81390
|
}
|
|
80512
81391
|
const filename = encodeCredentialsFilename(email);
|
|
80513
|
-
const filePath =
|
|
81392
|
+
const filePath = join71(dir, filename);
|
|
80514
81393
|
writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
|
|
80515
81394
|
chmodSync9(filePath, 384);
|
|
80516
81395
|
return filePath;
|
|
@@ -80631,9 +81510,9 @@ async function runDriveMcpLauncher(opts) {
|
|
|
80631
81510
|
}
|
|
80632
81511
|
const args = buildUvxArgs(tier);
|
|
80633
81512
|
const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
|
|
80634
|
-
const { spawn:
|
|
81513
|
+
const { spawn: spawn5 } = await import("node:child_process");
|
|
80635
81514
|
const os5 = await import("node:os");
|
|
80636
|
-
const child =
|
|
81515
|
+
const child = spawn5("uvx", args, {
|
|
80637
81516
|
stdio: ["pipe", "pipe", "inherit"],
|
|
80638
81517
|
env: env2
|
|
80639
81518
|
});
|
|
@@ -80668,9 +81547,9 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
80668
81547
|
|
|
80669
81548
|
// src/cli/m365-mcp-launcher.ts
|
|
80670
81549
|
init_scaffold_integration();
|
|
80671
|
-
import { spawn as
|
|
81550
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
80672
81551
|
import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync39 } from "node:fs";
|
|
80673
|
-
import { dirname as dirname18, join as
|
|
81552
|
+
import { dirname as dirname18, join as join72 } from "node:path";
|
|
80674
81553
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
80675
81554
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
80676
81555
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -80702,7 +81581,7 @@ function writeRefreshHeartbeat(agentName, data) {
|
|
|
80702
81581
|
function heartbeatPath(agentName) {
|
|
80703
81582
|
const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
|
|
80704
81583
|
if (override) {
|
|
80705
|
-
return
|
|
81584
|
+
return join72(override, `m365-launcher-${agentName}.heartbeat.json`);
|
|
80706
81585
|
}
|
|
80707
81586
|
return "/state/agent/m365-launcher.heartbeat.json";
|
|
80708
81587
|
}
|
|
@@ -80874,7 +81753,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
80874
81753
|
});
|
|
80875
81754
|
},
|
|
80876
81755
|
spawnSofteria: (env2) => {
|
|
80877
|
-
return
|
|
81756
|
+
return spawn5("npx", buildSofteriaArgs(opts), {
|
|
80878
81757
|
env: env2,
|
|
80879
81758
|
stdio: ["pipe", "pipe", "pipe"]
|
|
80880
81759
|
});
|
|
@@ -80886,7 +81765,7 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
80886
81765
|
|
|
80887
81766
|
// src/cli/notion-mcp-launcher.ts
|
|
80888
81767
|
init_scaffold_integration();
|
|
80889
|
-
import { spawn as
|
|
81768
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
80890
81769
|
import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as writeFileSync35 } from "node:fs";
|
|
80891
81770
|
import { dirname as dirname19 } from "node:path";
|
|
80892
81771
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
@@ -80999,7 +81878,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
80999
81878
|
return result.entry.value;
|
|
81000
81879
|
},
|
|
81001
81880
|
spawnMcp: (env2, args) => {
|
|
81002
|
-
return
|
|
81881
|
+
return spawn6("npx", args, {
|
|
81003
81882
|
env: env2,
|
|
81004
81883
|
stdio: ["pipe", "pipe", "pipe"]
|
|
81005
81884
|
});
|
|
@@ -82030,8 +82909,8 @@ agents:
|
|
|
82030
82909
|
|
|
82031
82910
|
// src/cli/apply.ts
|
|
82032
82911
|
init_resolver();
|
|
82033
|
-
import { dirname as dirname22, join as
|
|
82034
|
-
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";
|
|
82035
82914
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
82036
82915
|
init_vault();
|
|
82037
82916
|
init_loader();
|
|
@@ -82039,7 +82918,7 @@ init_loader();
|
|
|
82039
82918
|
|
|
82040
82919
|
// src/cli/update-prompt-hook.ts
|
|
82041
82920
|
import { existsSync as existsSync72, readFileSync as readFileSync62, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
|
|
82042
|
-
import { join as
|
|
82921
|
+
import { join as join73 } from "node:path";
|
|
82043
82922
|
var HOOK_FILENAME = "update-card-on-prompt.sh";
|
|
82044
82923
|
function updatePromptHookScript() {
|
|
82045
82924
|
return `#!/bin/bash
|
|
@@ -82105,9 +82984,9 @@ exit 0
|
|
|
82105
82984
|
`;
|
|
82106
82985
|
}
|
|
82107
82986
|
function installUpdatePromptHook(agentDir) {
|
|
82108
|
-
const hooksDir =
|
|
82987
|
+
const hooksDir = join73(agentDir, ".claude", "hooks");
|
|
82109
82988
|
mkdirSync41(hooksDir, { recursive: true });
|
|
82110
|
-
const scriptPath =
|
|
82989
|
+
const scriptPath = join73(hooksDir, HOOK_FILENAME);
|
|
82111
82990
|
const desired = updatePromptHookScript();
|
|
82112
82991
|
let installed = false;
|
|
82113
82992
|
const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
|
|
@@ -82120,7 +82999,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
82120
82999
|
chmodSync10(scriptPath, 493);
|
|
82121
83000
|
} catch {}
|
|
82122
83001
|
}
|
|
82123
|
-
const settingsPath =
|
|
83002
|
+
const settingsPath = join73(agentDir, ".claude", "settings.json");
|
|
82124
83003
|
if (!existsSync72(settingsPath)) {
|
|
82125
83004
|
return { scriptPath, settingsPath, installed };
|
|
82126
83005
|
}
|
|
@@ -82222,14 +83101,14 @@ var EMBEDDED_EXAMPLES = {
|
|
|
82222
83101
|
switchroom: switchroom_default,
|
|
82223
83102
|
minimal: minimal_default
|
|
82224
83103
|
};
|
|
82225
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
83104
|
+
var DEFAULT_COMPOSE_PATH2 = join75(homedir44(), ".switchroom", "compose", "docker-compose.yml");
|
|
82226
83105
|
var COMPOSE_PROJECT2 = "switchroom";
|
|
82227
83106
|
function resolveVaultBindMountDir(homeDir, ctx) {
|
|
82228
83107
|
const isCustomPath = ctx.migrationKind === "custom-path-skipped";
|
|
82229
83108
|
if (isCustomPath && ctx.customVaultPath) {
|
|
82230
83109
|
return dirname22(ctx.customVaultPath);
|
|
82231
83110
|
}
|
|
82232
|
-
return
|
|
83111
|
+
return join75(homeDir, ".switchroom", "vault");
|
|
82233
83112
|
}
|
|
82234
83113
|
function inspectVaultBindMountDir(vaultDir) {
|
|
82235
83114
|
if (!existsSync74(vaultDir))
|
|
@@ -82258,44 +83137,44 @@ function hasVaultRefs(value) {
|
|
|
82258
83137
|
return false;
|
|
82259
83138
|
}
|
|
82260
83139
|
async function ensureHostMountSources(config) {
|
|
82261
|
-
const home2 =
|
|
83140
|
+
const home2 = homedir44();
|
|
82262
83141
|
const dirs = [
|
|
82263
|
-
|
|
82264
|
-
|
|
82265
|
-
|
|
82266
|
-
|
|
82267
|
-
|
|
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")
|
|
82268
83147
|
];
|
|
82269
83148
|
for (const name of Object.keys(config.agents)) {
|
|
82270
|
-
dirs.push(
|
|
82271
|
-
dirs.push(
|
|
82272
|
-
dirs.push(
|
|
82273
|
-
dirs.push(
|
|
82274
|
-
if (existsSync74(
|
|
82275
|
-
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"));
|
|
82276
83155
|
}
|
|
82277
83156
|
}
|
|
82278
83157
|
for (const dir of dirs) {
|
|
82279
83158
|
await mkdir2(dir, { recursive: true });
|
|
82280
83159
|
}
|
|
82281
|
-
const autoUnlockPath =
|
|
83160
|
+
const autoUnlockPath = join75(home2, ".switchroom", "vault-auto-unlock");
|
|
82282
83161
|
if (!existsSync74(autoUnlockPath)) {
|
|
82283
83162
|
writeFileSync37(autoUnlockPath, "", { mode: 384 });
|
|
82284
83163
|
}
|
|
82285
|
-
const auditLogPath =
|
|
83164
|
+
const auditLogPath = join75(home2, ".switchroom", "vault-audit.log");
|
|
82286
83165
|
if (!existsSync74(auditLogPath)) {
|
|
82287
83166
|
writeFileSync37(auditLogPath, "", { mode: 420 });
|
|
82288
83167
|
}
|
|
82289
|
-
const grantsDbPath =
|
|
83168
|
+
const grantsDbPath = join75(home2, ".switchroom", "vault-grants.db");
|
|
82290
83169
|
if (!existsSync74(grantsDbPath)) {
|
|
82291
83170
|
writeFileSync37(grantsDbPath, "", { mode: 384 });
|
|
82292
83171
|
}
|
|
82293
|
-
const hostdAuditLogPath =
|
|
83172
|
+
const hostdAuditLogPath = join75(home2, ".switchroom", "host-control-audit.log");
|
|
82294
83173
|
if (!existsSync74(hostdAuditLogPath)) {
|
|
82295
83174
|
writeFileSync37(hostdAuditLogPath, "", { mode: 420 });
|
|
82296
83175
|
}
|
|
82297
83176
|
for (const name of Object.keys(config.agents)) {
|
|
82298
|
-
const tokenPath =
|
|
83177
|
+
const tokenPath = join75(home2, ".switchroom", "agents", name, ".vault-token");
|
|
82299
83178
|
if (!existsSync74(tokenPath)) {
|
|
82300
83179
|
writeFileSync37(tokenPath, "", { mode: 384 });
|
|
82301
83180
|
}
|
|
@@ -82304,15 +83183,15 @@ async function ensureHostMountSources(config) {
|
|
|
82304
83183
|
chownSync7(tokenPath, uid, uid);
|
|
82305
83184
|
} catch {}
|
|
82306
83185
|
}
|
|
82307
|
-
const fleetDir =
|
|
83186
|
+
const fleetDir = join75(home2, ".switchroom", "fleet");
|
|
82308
83187
|
await mkdir2(fleetDir, { recursive: true });
|
|
82309
|
-
const invariantsPath =
|
|
83188
|
+
const invariantsPath = join75(fleetDir, "switchroom-invariants.md");
|
|
82310
83189
|
const invariantsCanonical = renderFleetInvariants();
|
|
82311
83190
|
const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
|
|
82312
83191
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
82313
83192
|
writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
82314
83193
|
}
|
|
82315
|
-
const fleetClaudePath =
|
|
83194
|
+
const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
|
|
82316
83195
|
if (!existsSync74(fleetClaudePath)) {
|
|
82317
83196
|
writeFileSync37(fleetClaudePath, [
|
|
82318
83197
|
"# Switchroom fleet defaults",
|
|
@@ -82395,10 +83274,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
82395
83274
|
`));
|
|
82396
83275
|
}
|
|
82397
83276
|
}
|
|
82398
|
-
function writeInstallTypeCache(homeDir =
|
|
83277
|
+
function writeInstallTypeCache(homeDir = homedir44()) {
|
|
82399
83278
|
const ctx = detectInstallType();
|
|
82400
|
-
const dir =
|
|
82401
|
-
const out =
|
|
83279
|
+
const dir = join75(homeDir, ".switchroom");
|
|
83280
|
+
const out = join75(dir, "install-type.json");
|
|
82402
83281
|
const tmp = `${out}.tmp`;
|
|
82403
83282
|
mkdirSync42(dir, { recursive: true });
|
|
82404
83283
|
const payload = {
|
|
@@ -82447,14 +83326,14 @@ Applying switchroom config...
|
|
|
82447
83326
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
82448
83327
|
`));
|
|
82449
83328
|
try {
|
|
82450
|
-
installUpdatePromptHook(
|
|
83329
|
+
installUpdatePromptHook(join75(agentsDir, name));
|
|
82451
83330
|
} catch (hookErr) {
|
|
82452
83331
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
82453
83332
|
`));
|
|
82454
83333
|
}
|
|
82455
83334
|
try {
|
|
82456
83335
|
const uid = allocateAgentUid(name);
|
|
82457
|
-
alignAgentUid(name,
|
|
83336
|
+
alignAgentUid(name, join75(agentsDir, name), uid, {
|
|
82458
83337
|
confirm: !options.nonInteractive,
|
|
82459
83338
|
writeOut
|
|
82460
83339
|
});
|
|
@@ -82491,7 +83370,7 @@ Applying switchroom config...
|
|
|
82491
83370
|
for (const name of agentNames) {
|
|
82492
83371
|
try {
|
|
82493
83372
|
const uid = allocateAgentUid(name);
|
|
82494
|
-
alignAgentUid(name,
|
|
83373
|
+
alignAgentUid(name, join75(agentsDir, name), uid, {
|
|
82495
83374
|
confirm: !options.nonInteractive,
|
|
82496
83375
|
writeOut
|
|
82497
83376
|
});
|
|
@@ -82505,7 +83384,7 @@ Applying switchroom config...
|
|
|
82505
83384
|
}
|
|
82506
83385
|
const vaultPathConfigured = config.vault?.path;
|
|
82507
83386
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
82508
|
-
const migrationResult = migrateVaultLayout(
|
|
83387
|
+
const migrationResult = migrateVaultLayout(homedir44(), {
|
|
82509
83388
|
customVaultPath
|
|
82510
83389
|
});
|
|
82511
83390
|
switch (migrationResult.kind) {
|
|
@@ -82531,7 +83410,7 @@ Applying switchroom config...
|
|
|
82531
83410
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
82532
83411
|
process.exit(4);
|
|
82533
83412
|
}
|
|
82534
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
83413
|
+
const postMigrationInspect = inspectVaultLayout(homedir44());
|
|
82535
83414
|
const acceptable = [
|
|
82536
83415
|
"no-vault",
|
|
82537
83416
|
"already-migrated",
|
|
@@ -82546,7 +83425,7 @@ Applying switchroom config...
|
|
|
82546
83425
|
`));
|
|
82547
83426
|
process.exit(5);
|
|
82548
83427
|
}
|
|
82549
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
83428
|
+
const vaultDir = resolveVaultBindMountDir(homedir44(), {
|
|
82550
83429
|
migrationKind: migrationResult.kind,
|
|
82551
83430
|
customVaultPath
|
|
82552
83431
|
});
|
|
@@ -82585,7 +83464,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
82585
83464
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
82586
83465
|
`));
|
|
82587
83466
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
82588
|
-
const restored = restoreOperatorOwnership(
|
|
83467
|
+
const restored = restoreOperatorOwnership(homedir44(), operatorUid);
|
|
82589
83468
|
if (restored.length > 0) {
|
|
82590
83469
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
82591
83470
|
`));
|
|
@@ -82655,7 +83534,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
82655
83534
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
82656
83535
|
const unwritable = [];
|
|
82657
83536
|
for (const name of targets) {
|
|
82658
|
-
const startSh =
|
|
83537
|
+
const startSh = join75(agentsDir, name, "start.sh");
|
|
82659
83538
|
if (!existsSync74(startSh))
|
|
82660
83539
|
continue;
|
|
82661
83540
|
try {
|
|
@@ -82835,8 +83714,8 @@ function runRedactStdin() {
|
|
|
82835
83714
|
|
|
82836
83715
|
// src/cli/status-ask.ts
|
|
82837
83716
|
import { readFileSync as readFileSync64, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
|
|
82838
|
-
import { join as
|
|
82839
|
-
import { homedir as
|
|
83717
|
+
import { join as join76 } from "node:path";
|
|
83718
|
+
import { homedir as homedir45 } from "node:os";
|
|
82840
83719
|
|
|
82841
83720
|
// src/status-ask/report.ts
|
|
82842
83721
|
function parseJsonl(content) {
|
|
@@ -83171,7 +84050,7 @@ function resolveSources(explicitPath) {
|
|
|
83171
84050
|
const config = loadConfig();
|
|
83172
84051
|
agentsDir = resolveAgentsDir(config);
|
|
83173
84052
|
} catch {
|
|
83174
|
-
agentsDir =
|
|
84053
|
+
agentsDir = join76(homedir45(), ".switchroom", "agents");
|
|
83175
84054
|
}
|
|
83176
84055
|
if (!existsSync75(agentsDir))
|
|
83177
84056
|
return [];
|
|
@@ -83183,7 +84062,7 @@ function resolveSources(explicitPath) {
|
|
|
83183
84062
|
return [];
|
|
83184
84063
|
}
|
|
83185
84064
|
for (const name of entries) {
|
|
83186
|
-
const path8 =
|
|
84065
|
+
const path8 = join76(agentsDir, name, "runtime-metrics.jsonl");
|
|
83187
84066
|
if (existsSync75(path8)) {
|
|
83188
84067
|
sources.push({ path: path8, agent: name });
|
|
83189
84068
|
}
|
|
@@ -83221,21 +84100,21 @@ import {
|
|
|
83221
84100
|
unlinkSync as unlinkSync14,
|
|
83222
84101
|
writeSync as writeSync8
|
|
83223
84102
|
} from "node:fs";
|
|
83224
|
-
import { join as
|
|
84103
|
+
import { join as join77, resolve as resolve46 } from "node:path";
|
|
83225
84104
|
var STAGING_SUBDIR = ".staging";
|
|
83226
84105
|
function overlayPathsFor(agent, opts = {}) {
|
|
83227
84106
|
const base = opts.root ? resolve46(opts.root, agent) : resolve46(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
83228
|
-
const scheduleDir =
|
|
83229
|
-
const scheduleStagingDir =
|
|
83230
|
-
const skillsDir =
|
|
83231
|
-
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);
|
|
83232
84111
|
return {
|
|
83233
84112
|
agentRoot: base,
|
|
83234
84113
|
scheduleDir,
|
|
83235
84114
|
scheduleStagingDir,
|
|
83236
84115
|
skillsDir,
|
|
83237
84116
|
skillsStagingDir,
|
|
83238
|
-
lockPath:
|
|
84117
|
+
lockPath: join77(base, ".lock"),
|
|
83239
84118
|
stagingDir: scheduleStagingDir
|
|
83240
84119
|
};
|
|
83241
84120
|
}
|
|
@@ -83289,8 +84168,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83289
84168
|
const paths = overlayPathsFor(agent, opts);
|
|
83290
84169
|
return withAgentLock(paths, () => {
|
|
83291
84170
|
ensureDirs(paths);
|
|
83292
|
-
const stagingPath =
|
|
83293
|
-
const finalPath =
|
|
84171
|
+
const stagingPath = join77(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
84172
|
+
const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
|
|
83294
84173
|
const fd = openSync13(stagingPath, "w", 384);
|
|
83295
84174
|
try {
|
|
83296
84175
|
writeSync8(fd, yamlText);
|
|
@@ -83306,8 +84185,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83306
84185
|
const paths = overlayPathsFor(agent, opts);
|
|
83307
84186
|
return withAgentLock(paths, () => {
|
|
83308
84187
|
ensureSkillsDirs(paths);
|
|
83309
|
-
const stagingPath =
|
|
83310
|
-
const finalPath =
|
|
84188
|
+
const stagingPath = join77(paths.skillsStagingDir, `${slug}.yaml`);
|
|
84189
|
+
const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
|
|
83311
84190
|
const fd = openSync13(stagingPath, "w", 384);
|
|
83312
84191
|
try {
|
|
83313
84192
|
writeSync8(fd, yamlText);
|
|
@@ -83322,7 +84201,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
83322
84201
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
83323
84202
|
const paths = overlayPathsFor(agent, opts);
|
|
83324
84203
|
return withAgentLock(paths, () => {
|
|
83325
|
-
const finalPath =
|
|
84204
|
+
const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
|
|
83326
84205
|
if (!existsSync76(finalPath))
|
|
83327
84206
|
return false;
|
|
83328
84207
|
unlinkSync14(finalPath);
|
|
@@ -83337,7 +84216,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
83337
84216
|
for (const name of readdirSync28(paths.skillsDir)) {
|
|
83338
84217
|
if (!/\.ya?ml$/i.test(name))
|
|
83339
84218
|
continue;
|
|
83340
|
-
const full =
|
|
84219
|
+
const full = join77(paths.skillsDir, name);
|
|
83341
84220
|
try {
|
|
83342
84221
|
const raw = readFileSync65(full, "utf-8");
|
|
83343
84222
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -83349,7 +84228,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
83349
84228
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
83350
84229
|
const paths = overlayPathsFor(agent, opts);
|
|
83351
84230
|
return withAgentLock(paths, () => {
|
|
83352
|
-
const finalPath =
|
|
84231
|
+
const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
|
|
83353
84232
|
if (!existsSync76(finalPath))
|
|
83354
84233
|
return false;
|
|
83355
84234
|
unlinkSync14(finalPath);
|
|
@@ -83364,7 +84243,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
83364
84243
|
for (const name of readdirSync28(paths.scheduleDir)) {
|
|
83365
84244
|
if (!/\.ya?ml$/i.test(name))
|
|
83366
84245
|
continue;
|
|
83367
|
-
const full =
|
|
84246
|
+
const full = join77(paths.scheduleDir, name);
|
|
83368
84247
|
try {
|
|
83369
84248
|
const raw = readFileSync65(full, "utf-8");
|
|
83370
84249
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
@@ -83520,12 +84399,12 @@ import {
|
|
|
83520
84399
|
writeFileSync as writeFileSync38,
|
|
83521
84400
|
writeSync as writeSync9
|
|
83522
84401
|
} from "node:fs";
|
|
83523
|
-
import { join as
|
|
84402
|
+
import { join as join78 } from "node:path";
|
|
83524
84403
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
83525
84404
|
var STAGE_ID_PREFIX = "cap_";
|
|
83526
84405
|
function pendingDir(agent, opts = {}) {
|
|
83527
84406
|
const paths = overlayPathsFor(agent, opts);
|
|
83528
|
-
return
|
|
84407
|
+
return join78(paths.scheduleDir, ".pending");
|
|
83529
84408
|
}
|
|
83530
84409
|
function ensurePendingDir(agent, opts = {}) {
|
|
83531
84410
|
const dir = pendingDir(agent, opts);
|
|
@@ -83538,8 +84417,8 @@ function newStageId() {
|
|
|
83538
84417
|
function stagePendingScheduleEntry(opts) {
|
|
83539
84418
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
83540
84419
|
const stageId = opts.stageId ?? newStageId();
|
|
83541
|
-
const yamlPath =
|
|
83542
|
-
const metaPath =
|
|
84420
|
+
const yamlPath = join78(dir, `${stageId}.yaml`);
|
|
84421
|
+
const metaPath = join78(dir, `${stageId}.meta.json`);
|
|
83543
84422
|
const meta = {
|
|
83544
84423
|
v: 1,
|
|
83545
84424
|
stage_id: stageId,
|
|
@@ -83573,8 +84452,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
83573
84452
|
if (!name.endsWith(".meta.json"))
|
|
83574
84453
|
continue;
|
|
83575
84454
|
const stageId = name.slice(0, -".meta.json".length);
|
|
83576
|
-
const metaPath =
|
|
83577
|
-
const yamlPath =
|
|
84455
|
+
const metaPath = join78(dir, name);
|
|
84456
|
+
const yamlPath = join78(dir, `${stageId}.yaml`);
|
|
83578
84457
|
if (!existsSync77(yamlPath))
|
|
83579
84458
|
continue;
|
|
83580
84459
|
try {
|
|
@@ -83593,7 +84472,7 @@ function commitPendingScheduleEntry(opts) {
|
|
|
83593
84472
|
return { committed: false, reason: "not_found" };
|
|
83594
84473
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
83595
84474
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
83596
|
-
const finalPath =
|
|
84475
|
+
const finalPath = join78(paths.scheduleDir, `${slug}.yaml`);
|
|
83597
84476
|
if (existsSync77(finalPath)) {
|
|
83598
84477
|
return { committed: false, reason: "slug_collision" };
|
|
83599
84478
|
}
|
|
@@ -84226,7 +85105,7 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
84226
85105
|
import { existsSync as existsSync79 } from "node:fs";
|
|
84227
85106
|
init_reconcile_default_skills();
|
|
84228
85107
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
84229
|
-
import { join as
|
|
85108
|
+
import { join as join79 } from "node:path";
|
|
84230
85109
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
84231
85110
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
84232
85111
|
function exitCodeFor2(code) {
|
|
@@ -84301,7 +85180,7 @@ function skillInstall(opts) {
|
|
|
84301
85180
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
84302
85181
|
}
|
|
84303
85182
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
84304
|
-
const skillPath =
|
|
85183
|
+
const skillPath = join79(poolDir, skillName);
|
|
84305
85184
|
if (!existsSync79(skillPath)) {
|
|
84306
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.`);
|
|
84307
85186
|
}
|
|
@@ -84479,8 +85358,8 @@ import {
|
|
|
84479
85358
|
statSync as statSync32,
|
|
84480
85359
|
writeFileSync as writeFileSync39
|
|
84481
85360
|
} from "node:fs";
|
|
84482
|
-
import { tmpdir as tmpdir5, homedir as
|
|
84483
|
-
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";
|
|
84484
85363
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
84485
85364
|
|
|
84486
85365
|
// src/cli/skill-common.ts
|
|
@@ -84674,10 +85553,10 @@ function scanForClaudeP2(content) {
|
|
|
84674
85553
|
function resolveSkillsPoolDir2(override) {
|
|
84675
85554
|
const raw = override ?? "~/.switchroom/skills";
|
|
84676
85555
|
if (raw.startsWith("~/")) {
|
|
84677
|
-
return
|
|
85556
|
+
return join80(homedir46(), raw.slice(2));
|
|
84678
85557
|
}
|
|
84679
85558
|
if (raw === "~")
|
|
84680
|
-
return
|
|
85559
|
+
return homedir46();
|
|
84681
85560
|
return resolve47(raw);
|
|
84682
85561
|
}
|
|
84683
85562
|
function readStdinSync() {
|
|
@@ -84713,7 +85592,7 @@ function loadFromDir(dir) {
|
|
|
84713
85592
|
const walk2 = (sub) => {
|
|
84714
85593
|
const entries = readdirSync30(sub, { withFileTypes: true });
|
|
84715
85594
|
for (const ent of entries) {
|
|
84716
|
-
const full =
|
|
85595
|
+
const full = join80(sub, ent.name);
|
|
84717
85596
|
const rel = relative2(abs, full);
|
|
84718
85597
|
if (ent.isSymbolicLink()) {
|
|
84719
85598
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -84748,7 +85627,7 @@ function loadFromTarball(tarPath) {
|
|
|
84748
85627
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
84749
85628
|
}
|
|
84750
85629
|
}
|
|
84751
|
-
const staging = mkdtempSync5(
|
|
85630
|
+
const staging = mkdtempSync5(join80(tmpdir5(), "skill-apply-extract-"));
|
|
84752
85631
|
try {
|
|
84753
85632
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
84754
85633
|
const r = spawnSync12("tar", [
|
|
@@ -84834,8 +85713,8 @@ function validatePayload(name, files) {
|
|
|
84834
85713
|
errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
84835
85714
|
}
|
|
84836
85715
|
} else if (PY_SCRIPT_RE2.test(path8)) {
|
|
84837
|
-
const tmp = mkdtempSync5(
|
|
84838
|
-
const tmpPy =
|
|
85716
|
+
const tmp = mkdtempSync5(join80(tmpdir5(), "skill-apply-py-"));
|
|
85717
|
+
const tmpPy = join80(tmp, "check.py");
|
|
84839
85718
|
try {
|
|
84840
85719
|
writeFileSync39(tmpPy, content);
|
|
84841
85720
|
const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -84858,7 +85737,7 @@ function diffSummary(currentDir, files) {
|
|
|
84858
85737
|
if (existsSync80(currentDir)) {
|
|
84859
85738
|
const walk2 = (sub) => {
|
|
84860
85739
|
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
84861
|
-
const full =
|
|
85740
|
+
const full = join80(sub, ent.name);
|
|
84862
85741
|
const rel = relative2(currentDir, full);
|
|
84863
85742
|
if (ent.isDirectory()) {
|
|
84864
85743
|
walk2(full);
|
|
@@ -84894,7 +85773,7 @@ function writePayload(poolDir, name, files) {
|
|
|
84894
85773
|
if (!existsSync80(poolDir)) {
|
|
84895
85774
|
mkdirSync45(poolDir, { recursive: true, mode: 493 });
|
|
84896
85775
|
}
|
|
84897
|
-
const target =
|
|
85776
|
+
const target = join80(poolDir, name);
|
|
84898
85777
|
let targetIsSymlink = false;
|
|
84899
85778
|
try {
|
|
84900
85779
|
const st = lstatSync8(target);
|
|
@@ -84905,11 +85784,11 @@ function writePayload(poolDir, name, files) {
|
|
|
84905
85784
|
if (targetIsSymlink) {
|
|
84906
85785
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
84907
85786
|
}
|
|
84908
|
-
const staging = mkdtempSync5(
|
|
85787
|
+
const staging = mkdtempSync5(join80(poolDir, `.skill-apply-stage-${name}-`));
|
|
84909
85788
|
let oldRename = null;
|
|
84910
85789
|
try {
|
|
84911
85790
|
for (const [path8, content] of Object.entries(files)) {
|
|
84912
|
-
const full =
|
|
85791
|
+
const full = join80(staging, path8);
|
|
84913
85792
|
mkdirSync45(dirname23(full), { recursive: true, mode: 493 });
|
|
84914
85793
|
const fd = openSync15(full, "wx");
|
|
84915
85794
|
try {
|
|
@@ -84987,7 +85866,7 @@ function registerSkillCommand(program3) {
|
|
|
84987
85866
|
}
|
|
84988
85867
|
const config = loadConfig();
|
|
84989
85868
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
84990
|
-
const currentDir =
|
|
85869
|
+
const currentDir = join80(poolDir, name);
|
|
84991
85870
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
84992
85871
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
84993
85872
|
console.log(diffSummary(currentDir, files));
|
|
@@ -85031,8 +85910,8 @@ import {
|
|
|
85031
85910
|
utimesSync,
|
|
85032
85911
|
writeFileSync as writeFileSync40
|
|
85033
85912
|
} from "node:fs";
|
|
85034
|
-
import { dirname as dirname24, join as
|
|
85035
|
-
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";
|
|
85036
85915
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
85037
85916
|
init_helpers();
|
|
85038
85917
|
init_source();
|
|
@@ -85042,10 +85921,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
85042
85921
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
85043
85922
|
function resolveConfigSkillsDir(agent) {
|
|
85044
85923
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
85045
|
-
const candidate = override ? resolve48(override) :
|
|
85924
|
+
const candidate = override ? resolve48(override) : join81(homedir47(), ".switchroom-config");
|
|
85046
85925
|
if (!existsSync81(candidate))
|
|
85047
85926
|
return null;
|
|
85048
|
-
return
|
|
85927
|
+
return join81(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
85049
85928
|
}
|
|
85050
85929
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
85051
85930
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -85063,7 +85942,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
85063
85942
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
85064
85943
|
continue;
|
|
85065
85944
|
try {
|
|
85066
|
-
rmSync17(
|
|
85945
|
+
rmSync17(join81(configSkillsRoot, ent), { recursive: true, force: true });
|
|
85067
85946
|
} catch {}
|
|
85068
85947
|
}
|
|
85069
85948
|
} catch {}
|
|
@@ -85072,7 +85951,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85072
85951
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
85073
85952
|
if (!configSkillsRoot)
|
|
85074
85953
|
return;
|
|
85075
|
-
const dest =
|
|
85954
|
+
const dest = join81(configSkillsRoot, name);
|
|
85076
85955
|
try {
|
|
85077
85956
|
if (liveSkillDir !== null) {
|
|
85078
85957
|
try {
|
|
@@ -85087,19 +85966,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85087
85966
|
if (liveSkillDir === null) {
|
|
85088
85967
|
sweepMirrorPriors(configSkillsRoot);
|
|
85089
85968
|
if (existsSync81(dest)) {
|
|
85090
|
-
const trash =
|
|
85969
|
+
const trash = join81(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
85091
85970
|
renameSync18(dest, trash);
|
|
85092
85971
|
}
|
|
85093
85972
|
return;
|
|
85094
85973
|
}
|
|
85095
85974
|
mkdirSync46(configSkillsRoot, { recursive: true, mode: 493 });
|
|
85096
85975
|
sweepMirrorPriors(configSkillsRoot);
|
|
85097
|
-
const staging = mkdtempSync6(
|
|
85976
|
+
const staging = mkdtempSync6(join81(configSkillsRoot, `.${name}-staging-`));
|
|
85098
85977
|
const walk2 = (src, dst) => {
|
|
85099
85978
|
mkdirSync46(dst, { recursive: true, mode: 493 });
|
|
85100
85979
|
for (const ent of readdirSync31(src, { withFileTypes: true })) {
|
|
85101
|
-
const s =
|
|
85102
|
-
const d =
|
|
85980
|
+
const s = join81(src, ent.name);
|
|
85981
|
+
const d = join81(dst, ent.name);
|
|
85103
85982
|
if (ent.isSymbolicLink())
|
|
85104
85983
|
continue;
|
|
85105
85984
|
if (ent.isDirectory())
|
|
@@ -85111,7 +85990,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
85111
85990
|
};
|
|
85112
85991
|
walk2(liveSkillDir, staging);
|
|
85113
85992
|
if (existsSync81(dest)) {
|
|
85114
|
-
const prior =
|
|
85993
|
+
const prior = join81(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
85115
85994
|
renameSync18(dest, prior);
|
|
85116
85995
|
}
|
|
85117
85996
|
renameSync18(staging, dest);
|
|
@@ -85138,13 +86017,13 @@ function resolveAgent(opts) {
|
|
|
85138
86017
|
function resolveAgentsRoot(opts) {
|
|
85139
86018
|
if (opts.root)
|
|
85140
86019
|
return resolve48(opts.root);
|
|
85141
|
-
return
|
|
86020
|
+
return join81(homedir47(), ".switchroom", "agents");
|
|
85142
86021
|
}
|
|
85143
86022
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
85144
|
-
return
|
|
86023
|
+
return join81(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
85145
86024
|
}
|
|
85146
86025
|
function trashDir(agentsRoot, agent) {
|
|
85147
|
-
return
|
|
86026
|
+
return join81(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
85148
86027
|
}
|
|
85149
86028
|
function readStdinSync2() {
|
|
85150
86029
|
const chunks = [];
|
|
@@ -85174,7 +86053,7 @@ function loadFromDir2(dir) {
|
|
|
85174
86053
|
const files = {};
|
|
85175
86054
|
const walk2 = (sub) => {
|
|
85176
86055
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
85177
|
-
const full =
|
|
86056
|
+
const full = join81(sub, ent.name);
|
|
85178
86057
|
if (ent.isSymbolicLink()) {
|
|
85179
86058
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
85180
86059
|
}
|
|
@@ -85227,8 +86106,8 @@ function behavioralValidate(files) {
|
|
|
85227
86106
|
errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
85228
86107
|
}
|
|
85229
86108
|
} else if (PY_SCRIPT_RE.test(path8)) {
|
|
85230
|
-
const tmp = mkdtempSync6(
|
|
85231
|
-
const tmpPy =
|
|
86109
|
+
const tmp = mkdtempSync6(join81(tmpdir6(), "skill-personal-py-"));
|
|
86110
|
+
const tmpPy = join81(tmp, "check.py");
|
|
85232
86111
|
try {
|
|
85233
86112
|
writeFileSync40(tmpPy, content);
|
|
85234
86113
|
const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
|
|
@@ -85252,7 +86131,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
85252
86131
|
for (const ent of readdirSync31(trash, { withFileTypes: true })) {
|
|
85253
86132
|
if (!ent.isDirectory())
|
|
85254
86133
|
continue;
|
|
85255
|
-
const entPath =
|
|
86134
|
+
const entPath = join81(trash, ent.name);
|
|
85256
86135
|
try {
|
|
85257
86136
|
const st = statSync33(entPath);
|
|
85258
86137
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -85273,11 +86152,11 @@ function writePersonalSkill(targetDir, files) {
|
|
|
85273
86152
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
85274
86153
|
}
|
|
85275
86154
|
mkdirSync46(dirname24(targetDir), { recursive: true, mode: 493 });
|
|
85276
|
-
const staging = mkdtempSync6(
|
|
86155
|
+
const staging = mkdtempSync6(join81(dirname24(targetDir), `.skill-personal-stage-`));
|
|
85277
86156
|
let oldRename = null;
|
|
85278
86157
|
try {
|
|
85279
86158
|
for (const [path8, content] of Object.entries(files)) {
|
|
85280
|
-
const full =
|
|
86159
|
+
const full = join81(staging, path8);
|
|
85281
86160
|
mkdirSync46(dirname24(full), { recursive: true, mode: 493 });
|
|
85282
86161
|
const fd = openSync16(full, "wx");
|
|
85283
86162
|
try {
|
|
@@ -85410,10 +86289,10 @@ function editPersonalAction(name, opts) {
|
|
|
85410
86289
|
}
|
|
85411
86290
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
85412
86291
|
function defaultSharedRoot() {
|
|
85413
|
-
return
|
|
86292
|
+
return join81(homedir47(), ".switchroom", "skills");
|
|
85414
86293
|
}
|
|
85415
86294
|
function defaultBundledRoot() {
|
|
85416
|
-
return
|
|
86295
|
+
return join81(homedir47(), ".switchroom", "skills", "_bundled");
|
|
85417
86296
|
}
|
|
85418
86297
|
function resolveCloneSource(source, opts) {
|
|
85419
86298
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -85423,7 +86302,7 @@ function resolveCloneSource(source, opts) {
|
|
|
85423
86302
|
const tier = m[1];
|
|
85424
86303
|
const slug = m[2];
|
|
85425
86304
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
85426
|
-
const dir =
|
|
86305
|
+
const dir = join81(root, slug);
|
|
85427
86306
|
if (!existsSync81(dir)) {
|
|
85428
86307
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
85429
86308
|
}
|
|
@@ -85439,7 +86318,7 @@ function readSourceFiles(dir) {
|
|
|
85439
86318
|
const skipped = [];
|
|
85440
86319
|
const walk2 = (sub) => {
|
|
85441
86320
|
for (const ent of readdirSync31(sub, { withFileTypes: true })) {
|
|
85442
|
-
const full =
|
|
86321
|
+
const full = join81(sub, ent.name);
|
|
85443
86322
|
if (ent.isSymbolicLink()) {
|
|
85444
86323
|
continue;
|
|
85445
86324
|
}
|
|
@@ -85550,7 +86429,7 @@ function removePersonalAction(name, opts) {
|
|
|
85550
86429
|
const trashRoot = trashDir(agentsRoot, agent);
|
|
85551
86430
|
mkdirSync46(trashRoot, { recursive: true, mode: 493 });
|
|
85552
86431
|
const ts = Date.now();
|
|
85553
|
-
const trashTarget =
|
|
86432
|
+
const trashTarget = join81(trashRoot, `${name}-${ts}`);
|
|
85554
86433
|
renameSync18(target, trashTarget);
|
|
85555
86434
|
const now = new Date(ts);
|
|
85556
86435
|
utimesSync(trashTarget, now, now);
|
|
@@ -85569,7 +86448,7 @@ function listPersonalAction(opts) {
|
|
|
85569
86448
|
const agent = resolveAgent(opts);
|
|
85570
86449
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
85571
86450
|
sweepTrash(agentsRoot, agent);
|
|
85572
|
-
const skillsDir =
|
|
86451
|
+
const skillsDir = join81(agentsRoot, agent, ".claude", "skills");
|
|
85573
86452
|
const personal = [];
|
|
85574
86453
|
if (existsSync81(skillsDir)) {
|
|
85575
86454
|
for (const ent of readdirSync31(skillsDir, { withFileTypes: true })) {
|
|
@@ -85578,7 +86457,7 @@ function listPersonalAction(opts) {
|
|
|
85578
86457
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
85579
86458
|
continue;
|
|
85580
86459
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
85581
|
-
const skillPath =
|
|
86460
|
+
const skillPath = join81(skillsDir, ent.name);
|
|
85582
86461
|
let fileCount = 0;
|
|
85583
86462
|
let totalBytes = 0;
|
|
85584
86463
|
const walk2 = (sub) => {
|
|
@@ -85586,10 +86465,10 @@ function listPersonalAction(opts) {
|
|
|
85586
86465
|
if (e.isFile()) {
|
|
85587
86466
|
fileCount += 1;
|
|
85588
86467
|
try {
|
|
85589
|
-
totalBytes += statSync33(
|
|
86468
|
+
totalBytes += statSync33(join81(sub, e.name)).size;
|
|
85590
86469
|
} catch {}
|
|
85591
86470
|
} else if (e.isDirectory()) {
|
|
85592
|
-
walk2(
|
|
86471
|
+
walk2(join81(sub, e.name));
|
|
85593
86472
|
}
|
|
85594
86473
|
}
|
|
85595
86474
|
};
|
|
@@ -85629,22 +86508,22 @@ function registerSkillPersonalCommands(program3) {
|
|
|
85629
86508
|
init_helpers();
|
|
85630
86509
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
85631
86510
|
import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync70, statSync as statSync34 } from "node:fs";
|
|
85632
|
-
import { homedir as
|
|
85633
|
-
import { join as
|
|
86511
|
+
import { homedir as homedir48 } from "node:os";
|
|
86512
|
+
import { join as join82, resolve as resolve49 } from "node:path";
|
|
85634
86513
|
var PERSONAL_PREFIX2 = "personal-";
|
|
85635
86514
|
var BUNDLED_SUBDIR = "_bundled";
|
|
85636
86515
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
85637
86516
|
function defaultAgentsRoot() {
|
|
85638
|
-
return resolve49(
|
|
86517
|
+
return resolve49(homedir48(), ".switchroom/agents");
|
|
85639
86518
|
}
|
|
85640
86519
|
function defaultSharedRoot2() {
|
|
85641
|
-
return resolve49(
|
|
86520
|
+
return resolve49(homedir48(), ".switchroom/skills");
|
|
85642
86521
|
}
|
|
85643
86522
|
function defaultBundledRoot2() {
|
|
85644
|
-
return resolve49(
|
|
86523
|
+
return resolve49(homedir48(), ".switchroom/skills/_bundled");
|
|
85645
86524
|
}
|
|
85646
86525
|
function readSkillFrontmatter(skillDir) {
|
|
85647
|
-
const mdPath =
|
|
86526
|
+
const mdPath = join82(skillDir, "SKILL.md");
|
|
85648
86527
|
if (!existsSync82(mdPath))
|
|
85649
86528
|
return null;
|
|
85650
86529
|
let content;
|
|
@@ -85677,7 +86556,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
85677
86556
|
return { fm: parsed };
|
|
85678
86557
|
}
|
|
85679
86558
|
function statSkillMd(skillDir) {
|
|
85680
|
-
const mdPath =
|
|
86559
|
+
const mdPath = join82(skillDir, "SKILL.md");
|
|
85681
86560
|
try {
|
|
85682
86561
|
const st = statSync34(mdPath);
|
|
85683
86562
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -85688,7 +86567,7 @@ function statSkillMd(skillDir) {
|
|
|
85688
86567
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
85689
86568
|
if (!AGENT_NAME_RE3.test(agent))
|
|
85690
86569
|
return [];
|
|
85691
|
-
const skillsDir =
|
|
86570
|
+
const skillsDir = join82(agentsRoot, agent, ".claude/skills");
|
|
85692
86571
|
if (!existsSync82(skillsDir))
|
|
85693
86572
|
return [];
|
|
85694
86573
|
const out = [];
|
|
@@ -85701,7 +86580,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
85701
86580
|
for (const ent of entries) {
|
|
85702
86581
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
85703
86582
|
continue;
|
|
85704
|
-
const dirPath =
|
|
86583
|
+
const dirPath = join82(skillsDir, ent);
|
|
85705
86584
|
try {
|
|
85706
86585
|
if (!statSync34(dirPath).isDirectory())
|
|
85707
86586
|
continue;
|
|
@@ -85741,7 +86620,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
85741
86620
|
continue;
|
|
85742
86621
|
if (ent.startsWith("."))
|
|
85743
86622
|
continue;
|
|
85744
|
-
const dirPath =
|
|
86623
|
+
const dirPath = join82(sharedRoot, ent);
|
|
85745
86624
|
try {
|
|
85746
86625
|
if (!statSync34(dirPath).isDirectory())
|
|
85747
86626
|
continue;
|
|
@@ -85777,7 +86656,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
85777
86656
|
for (const ent of entries) {
|
|
85778
86657
|
if (ent.startsWith("."))
|
|
85779
86658
|
continue;
|
|
85780
|
-
const dirPath =
|
|
86659
|
+
const dirPath = join82(bundledRoot, ent);
|
|
85781
86660
|
try {
|
|
85782
86661
|
if (!statSync34(dirPath).isDirectory())
|
|
85783
86662
|
continue;
|
|
@@ -85922,8 +86801,8 @@ function registerHostdMcpCommand(program3) {
|
|
|
85922
86801
|
init_source();
|
|
85923
86802
|
init_helpers();
|
|
85924
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";
|
|
85925
|
-
import { homedir as
|
|
85926
|
-
import { join as
|
|
86804
|
+
import { homedir as homedir49 } from "node:os";
|
|
86805
|
+
import { join as join83 } from "node:path";
|
|
85927
86806
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
85928
86807
|
init_audit_reader();
|
|
85929
86808
|
function resolveHostdImageTag(explicitTag, release) {
|
|
@@ -86036,7 +86915,7 @@ networks:
|
|
|
86036
86915
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
86037
86916
|
`;
|
|
86038
86917
|
}
|
|
86039
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
86918
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir49()) {
|
|
86040
86919
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
86041
86920
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
86042
86921
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -86047,10 +86926,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
|
|
|
86047
86926
|
return resolved;
|
|
86048
86927
|
}
|
|
86049
86928
|
function hostdDir() {
|
|
86050
|
-
return
|
|
86929
|
+
return join83(homedir49(), ".switchroom", "hostd");
|
|
86051
86930
|
}
|
|
86052
86931
|
function hostdComposePath() {
|
|
86053
|
-
return
|
|
86932
|
+
return join83(hostdDir(), "docker-compose.yml");
|
|
86054
86933
|
}
|
|
86055
86934
|
function backupExistingCompose() {
|
|
86056
86935
|
const p = hostdComposePath();
|
|
@@ -86161,7 +87040,7 @@ function doStatus() {
|
|
|
86161
87040
|
for (const name of readdirSync33(dir)) {
|
|
86162
87041
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
86163
87042
|
continue;
|
|
86164
|
-
const sockPath =
|
|
87043
|
+
const sockPath = join83(dir, name, "sock");
|
|
86165
87044
|
if (existsSync84(sockPath)) {
|
|
86166
87045
|
const st = statSync35(sockPath);
|
|
86167
87046
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -86253,8 +87132,8 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
86253
87132
|
init_source();
|
|
86254
87133
|
init_helpers();
|
|
86255
87134
|
import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
|
|
86256
|
-
import { homedir as
|
|
86257
|
-
import { join as
|
|
87135
|
+
import { homedir as homedir50 } from "node:os";
|
|
87136
|
+
import { join as join84 } from "node:path";
|
|
86258
87137
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
86259
87138
|
function resolveWebImageTag(explicitTag, release) {
|
|
86260
87139
|
if (explicitTag)
|
|
@@ -86339,10 +87218,10 @@ services:
|
|
|
86339
87218
|
`;
|
|
86340
87219
|
}
|
|
86341
87220
|
function webdDir() {
|
|
86342
|
-
return
|
|
87221
|
+
return join84(homedir50(), ".switchroom", "web");
|
|
86343
87222
|
}
|
|
86344
87223
|
function webdComposePath() {
|
|
86345
|
-
return
|
|
87224
|
+
return join84(webdDir(), "docker-compose.yml");
|
|
86346
87225
|
}
|
|
86347
87226
|
function backupExistingCompose2() {
|
|
86348
87227
|
const p = webdComposePath();
|
|
@@ -86375,7 +87254,7 @@ async function doInstall2(opts, program3) {
|
|
|
86375
87254
|
const cfg = getConfig(program3);
|
|
86376
87255
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
86377
87256
|
const yaml = renderWebComposeFile({
|
|
86378
|
-
hostHome:
|
|
87257
|
+
hostHome: homedir50(),
|
|
86379
87258
|
imageTag,
|
|
86380
87259
|
operatorUid
|
|
86381
87260
|
});
|