switchroom 0.18.30 → 0.18.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +4 -2
- package/dist/auth-broker/index.js +4 -2
- package/dist/cli/notion-write-pretool.mjs +4 -2
- package/dist/cli/switchroom.js +708 -255
- package/dist/host-control/main.js +5 -3
- package/dist/vault/approvals/kernel-server.js +4 -2
- package/dist/vault/broker/server.js +4 -2
- package/package.json +3 -2
- package/profiles/_base/start.sh.hbs +142 -7
- package/telegram-plugin/dist/gateway/gateway.js +25870 -25007
- package/telegram-plugin/gateway/backstop-delivery.ts +223 -23
- package/telegram-plugin/gateway/captured-answer-resume.ts +259 -0
- package/telegram-plugin/gateway/disconnect-flush.ts +6 -44
- package/telegram-plugin/gateway/gateway-import-clean.test.ts +188 -0
- package/telegram-plugin/gateway/gateway.ts +5479 -7069
- package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +7 -15
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +35 -68
- package/telegram-plugin/gateway/obligation-ledger.ts +42 -0
- package/telegram-plugin/gateway/obligation-store.ts +37 -1
- package/telegram-plugin/gateway/outbound-send-path.ts +2012 -0
- package/telegram-plugin/gateway/turn-flush-suppression.ts +82 -0
- package/telegram-plugin/pending-user-notice.ts +59 -13
- package/telegram-plugin/subagent-watcher.ts +111 -28
- package/telegram-plugin/tests/backstop-delivery.test.ts +167 -0
- package/telegram-plugin/tests/backstop-readback-probe.test.ts +144 -0
- package/telegram-plugin/tests/buffer-gate-broadened.test.ts +16 -6
- package/telegram-plugin/tests/button-tap-turn-gated.test.ts +3 -3
- package/telegram-plugin/tests/captured-answer-resume.test.ts +358 -0
- package/telegram-plugin/tests/emission-authority-facade.test.ts +29 -19
- package/telegram-plugin/tests/emission-authority-ping-gate.test.ts +4 -1
- package/telegram-plugin/tests/emission-determinism-wiring.test.ts +18 -7
- package/telegram-plugin/tests/gateway-boot-side-effect-gating.test.ts +249 -0
- package/telegram-plugin/tests/gateway-bot-construction-deferral.test.ts +251 -0
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +5 -128
- package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +303 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -3
- package/telegram-plugin/tests/inbound-delivery-cutover-flip.test.ts +54 -150
- package/telegram-plugin/tests/inbound-delivery-cutover-gate.test.ts +10 -14
- package/telegram-plugin/tests/inbound-delivery-dispatch-equivalence.test.ts +6 -7
- package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +0 -16
- package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +4 -4
- package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +69 -14
- package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +9 -3
- package/telegram-plugin/tests/obligation-ledger.test.ts +40 -0
- package/telegram-plugin/tests/obligation-store.test.ts +43 -0
- package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +2 -1
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +9 -6
- package/telegram-plugin/tests/photo-reroute-wiring.test.ts +5 -2
- package/telegram-plugin/tests/reply-terminal-reaction.test.ts +6 -2
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +3 -2
- package/telegram-plugin/tests/send-reply-golden.test.ts +571 -0
- package/telegram-plugin/tests/subagent-watcher-resume-reregister.test.ts +291 -0
- package/telegram-plugin/tests/subagent-watcher-resurrection.test.ts +32 -0
- package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +8 -12
- package/telegram-plugin/tests/turn-flush-safety.test.ts +8 -6
- package/telegram-plugin/tests/turn-flush-suppression-wiring.test.ts +112 -0
- package/telegram-plugin/tests/turn-flush-suppression.test.ts +90 -0
- package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +2 -2
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +126 -0
- package/telegram-plugin/tool-activity-summary.ts +27 -3
- package/telegram-plugin/worker-activity-feed.ts +24 -0
- package/telegram-plugin/gateway/busy-key-reaper.ts +0 -113
- package/telegram-plugin/gateway/gate-parity-probe.ts +0 -102
- package/telegram-plugin/tests/busy-key-reaper.test.ts +0 -192
- package/telegram-plugin/tests/fixtures/cutover-killswitch-probe.ts +0 -75
- package/telegram-plugin/tests/gate-parity-probe.test.ts +0 -171
- package/telegram-plugin/tests/parallel-turns-deadlock-fix.test.ts +0 -217
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.18.
|
|
2123
|
+
var VERSION = "0.18.32", COMMIT_SHA = "de41b271";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -14247,7 +14247,8 @@ var init_schema = __esm(() => {
|
|
|
14247
14247
|
llm_model: exports_external.string().optional().describe("LiteLLM model name for Hindsight's LLM ops (retain/reflect/" + "consolidation) when the top-level `litellm.enabled` carve-out is " + "on. Defaults to a cheap OpenRouter model (routed via LiteLLM's " + "model-mapped path, not the Anthropic OAuth pass-through) to keep " + "background memory-op cost off the Claude subscription quota. Set " + "to a `claude-*` model name to route it back through the OAuth " + "pass-through instead. Has no effect when litellm is disabled."),
|
|
14248
14248
|
api_key: exports_external.string().optional().describe("API key or vault reference for embedding provider"),
|
|
14249
14249
|
docker_service: exports_external.boolean().default(true).describe("Whether to include Hindsight in docker-compose"),
|
|
14250
|
-
url: exports_external.string().url("Hindsight URL must be a valid URL (no shell-special characters)").optional().describe("Hindsight MCP endpoint URL (e.g., http://localhost:18888/mcp/). Defaults to http://localhost:8888/mcp/.")
|
|
14250
|
+
url: exports_external.string().url("Hindsight URL must be a valid URL (no shell-special characters)").optional().describe("Hindsight MCP endpoint URL (e.g., http://localhost:18888/mcp/). Defaults to http://localhost:8888/mcp/."),
|
|
14251
|
+
mcp_transport: exports_external.enum(["shim", "http"]).optional().describe("How agents reach Hindsight's MCP surface. Default (unset or " + "'shim'): the lazy-connect stdio proxy `switchroom " + "hindsight-mcp-shim`, which always registers at session start " + "even when the hindsight container is down and reconnects " + "per-call. 'http' is the escape hatch that restores the legacy " + "direct Streamable-HTTP entry (session-fatal if the backend is " + "down when the agent session boots).")
|
|
14251
14252
|
}).optional()
|
|
14252
14253
|
});
|
|
14253
14254
|
VaultConfigSchema = exports_external.object({
|
|
@@ -14290,7 +14291,8 @@ var init_schema = __esm(() => {
|
|
|
14290
14291
|
auto_release_check: AutoReleaseCheckSchema.default({}).describe("Pull-based release-triggered fleet restart (#1743). hostd polls " + "the remote release tag on a fixed interval and applies + " + "restarts the fleet (graceful) when a new release is detected. " + "Opt-in: default enabled=false.")
|
|
14291
14292
|
});
|
|
14292
14293
|
WebServiceConfigSchema = exports_external.object({
|
|
14293
|
-
managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false \u2014 existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
|
|
14294
|
+
managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false \u2014 existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers."),
|
|
14295
|
+
port: exports_external.number().int().min(1).max(65535).default(8080).describe("Host loopback port the web server listens on (127.0.0.1:<port>). " + "Default: 8080. Set this when another service owns 8080 on the " + "host (e.g. UniFi OS Server) \u2014 `switchroom webd install` and the " + "`switchroom update` refresh-web step both honor it, so a custom " + "port survives updates instead of being silently reverted to " + "8080 by the regenerated compose file. Remember to point the " + "host-side consumers (cloudflared tunnel, `tailscale serve`) at " + "the same port.")
|
|
14294
14296
|
});
|
|
14295
14297
|
FleetHealthConfigSchema = exports_external.object({
|
|
14296
14298
|
owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
@@ -15959,11 +15961,24 @@ var init_hindsight = __esm(() => {
|
|
|
15959
15961
|
// src/memory/hindsight.ts
|
|
15960
15962
|
function generateHindsightMcpConfig(collection, memoryConfig) {
|
|
15961
15963
|
const url = memoryConfig.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
15964
|
+
if (memoryConfig.config?.mcp_transport === "http") {
|
|
15965
|
+
return {
|
|
15966
|
+
type: "http",
|
|
15967
|
+
url,
|
|
15968
|
+
headers: {
|
|
15969
|
+
"X-Bank-Id": collection
|
|
15970
|
+
}
|
|
15971
|
+
};
|
|
15972
|
+
}
|
|
15962
15973
|
return {
|
|
15963
|
-
type: "
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15974
|
+
type: "stdio",
|
|
15975
|
+
command: HINDSIGHT_SHIM_CLI_PATH,
|
|
15976
|
+
args: ["hindsight-mcp-shim"],
|
|
15977
|
+
env: {
|
|
15978
|
+
HINDSIGHT_MCP_URL: url,
|
|
15979
|
+
HINDSIGHT_BANK_ID: collection,
|
|
15980
|
+
HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
|
|
15981
|
+
HOME: HINDSIGHT_SHIM_AGENT_HOME
|
|
15967
15982
|
}
|
|
15968
15983
|
};
|
|
15969
15984
|
}
|
|
@@ -16490,7 +16505,7 @@ async function addMemoryTag(apiUrl, bankId, memoryId, tag, opts) {
|
|
|
16490
16505
|
return { ok: false, reason: String(err) };
|
|
16491
16506
|
}
|
|
16492
16507
|
}
|
|
16493
|
-
var DEFAULT_RETAIN_MISSION, PROFILE_MEMORY_DEFAULTS, USER_PROFILE_SOURCE_QUERY = "What are the key facts, preferences, context, and communication style about the user I talk to? Summarize what matters for making the agent feel like it knows them.", DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
|
|
16508
|
+
var HINDSIGHT_SHIM_CLI_PATH = "/usr/local/bin/switchroom", HINDSIGHT_SHIM_AGENT_HOME = "/state/agent/home", DEFAULT_RETAIN_MISSION, PROFILE_MEMORY_DEFAULTS, USER_PROFILE_SOURCE_QUERY = "What are the key facts, preferences, context, and communication style about the user I talk to? Summarize what matters for making the agent feel like it knows them.", DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
|
|
16494
16509
|
var init_hindsight2 = __esm(() => {
|
|
16495
16510
|
init_users();
|
|
16496
16511
|
init_hindsight();
|
|
@@ -26179,7 +26194,10 @@ function webkiteDenyForAgent(agentConfig) {
|
|
|
26179
26194
|
function resolveMainModel(model) {
|
|
26180
26195
|
if (model === undefined || model === "default")
|
|
26181
26196
|
return SWITCHROOM_DEFAULT_MAIN_MODEL;
|
|
26182
|
-
return model;
|
|
26197
|
+
return normalizeModelAlias(model);
|
|
26198
|
+
}
|
|
26199
|
+
function normalizeModelAlias(model) {
|
|
26200
|
+
return model === "claude-fable-5" ? "fable" : model;
|
|
26183
26201
|
}
|
|
26184
26202
|
function dedupe2(items) {
|
|
26185
26203
|
const seen = new Set;
|
|
@@ -47786,8 +47804,8 @@ var init_fleet_defaults = __esm(() => {
|
|
|
47786
47804
|
});
|
|
47787
47805
|
|
|
47788
47806
|
// src/agents/connection-health.ts
|
|
47789
|
-
import { mkdirSync as
|
|
47790
|
-
import { join as
|
|
47807
|
+
import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
|
|
47808
|
+
import { join as join82 } from "node:path";
|
|
47791
47809
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
47792
47810
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
47793
47811
|
if (reqs.length === 0)
|
|
@@ -47844,10 +47862,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
47844
47862
|
return issues;
|
|
47845
47863
|
}
|
|
47846
47864
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
47847
|
-
const dir =
|
|
47848
|
-
const path8 =
|
|
47849
|
-
(deps?.mkdir ?? ((p, o) =>
|
|
47850
|
-
(deps?.writeFile ?? ((p, d) =>
|
|
47865
|
+
const dir = join82(agentDir, ".claude");
|
|
47866
|
+
const path8 = join82(dir, CONNECTION_HEALTH_FILENAME);
|
|
47867
|
+
(deps?.mkdir ?? ((p, o) => mkdirSync46(p, o)))(dir, { recursive: true });
|
|
47868
|
+
(deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path8, JSON.stringify(health, null, 2) + `
|
|
47851
47869
|
`);
|
|
47852
47870
|
}
|
|
47853
47871
|
async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
|
|
@@ -47868,10 +47886,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
47868
47886
|
var init_connection_health = () => {};
|
|
47869
47887
|
|
|
47870
47888
|
// src/cli/update-prompt-hook.ts
|
|
47871
|
-
import { existsSync as existsSync83, readFileSync as
|
|
47872
|
-
import { join as
|
|
47889
|
+
import { existsSync as existsSync83, readFileSync as readFileSync72, writeFileSync as writeFileSync29, chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "node:fs";
|
|
47890
|
+
import { join as join83 } from "node:path";
|
|
47873
47891
|
function containerHookCommand() {
|
|
47874
|
-
return
|
|
47892
|
+
return join83(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
47875
47893
|
}
|
|
47876
47894
|
function updatePromptHookScript() {
|
|
47877
47895
|
return `#!/bin/bash
|
|
@@ -47937,14 +47955,14 @@ exit 0
|
|
|
47937
47955
|
`;
|
|
47938
47956
|
}
|
|
47939
47957
|
function installUpdatePromptHook(agentDir) {
|
|
47940
|
-
const hooksDir =
|
|
47941
|
-
|
|
47942
|
-
const scriptPath =
|
|
47958
|
+
const hooksDir = join83(agentDir, ".claude", "hooks");
|
|
47959
|
+
mkdirSync47(hooksDir, { recursive: true });
|
|
47960
|
+
const scriptPath = join83(hooksDir, HOOK_FILENAME);
|
|
47943
47961
|
const desired = updatePromptHookScript();
|
|
47944
47962
|
let installed = false;
|
|
47945
|
-
const existing = existsSync83(scriptPath) ?
|
|
47963
|
+
const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
|
|
47946
47964
|
if (existing !== desired) {
|
|
47947
|
-
|
|
47965
|
+
writeFileSync29(scriptPath, desired, { mode: 493 });
|
|
47948
47966
|
chmodSync12(scriptPath, 493);
|
|
47949
47967
|
installed = true;
|
|
47950
47968
|
} else {
|
|
@@ -47952,11 +47970,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47952
47970
|
chmodSync12(scriptPath, 493);
|
|
47953
47971
|
} catch {}
|
|
47954
47972
|
}
|
|
47955
|
-
const settingsPath =
|
|
47973
|
+
const settingsPath = join83(agentDir, ".claude", "settings.json");
|
|
47956
47974
|
if (!existsSync83(settingsPath)) {
|
|
47957
47975
|
return { scriptPath, settingsPath, installed };
|
|
47958
47976
|
}
|
|
47959
|
-
const raw =
|
|
47977
|
+
const raw = readFileSync72(settingsPath, "utf-8");
|
|
47960
47978
|
let parsed;
|
|
47961
47979
|
try {
|
|
47962
47980
|
parsed = JSON.parse(raw);
|
|
@@ -47992,7 +48010,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47992
48010
|
if (mutated) {
|
|
47993
48011
|
hooks.UserPromptSubmit = list2;
|
|
47994
48012
|
parsed.hooks = hooks;
|
|
47995
|
-
|
|
48013
|
+
writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
47996
48014
|
`, { mode: 384 });
|
|
47997
48015
|
installed = true;
|
|
47998
48016
|
} else if (!alreadyCorrect) {
|
|
@@ -48001,7 +48019,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48001
48019
|
});
|
|
48002
48020
|
hooks.UserPromptSubmit = list2;
|
|
48003
48021
|
parsed.hooks = hooks;
|
|
48004
|
-
|
|
48022
|
+
writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48005
48023
|
`, { mode: 384 });
|
|
48006
48024
|
installed = true;
|
|
48007
48025
|
}
|
|
@@ -48381,7 +48399,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
48381
48399
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
48382
48400
|
});
|
|
48383
48401
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
48384
|
-
import { chmodSync as chmodSync13, chownSync as chownSync7, existsSync as existsSync85, mkdirSync as
|
|
48402
|
+
import { chmodSync as chmodSync13, chownSync as chownSync7, existsSync as existsSync85, mkdirSync as mkdirSync48, readFileSync as readFileSync73, rmSync as rmSync17, writeFileSync as writeFileSync30 } from "node:fs";
|
|
48385
48403
|
import { dirname as dirname29 } from "node:path";
|
|
48386
48404
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
48387
48405
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -48417,7 +48435,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48417
48435
|
if (engine !== "local") {
|
|
48418
48436
|
try {
|
|
48419
48437
|
if (existsSync85(envPath)) {
|
|
48420
|
-
const body =
|
|
48438
|
+
const body = readFileSync73(envPath, "utf-8");
|
|
48421
48439
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
48422
48440
|
rmSync17(envPath);
|
|
48423
48441
|
}
|
|
@@ -48436,11 +48454,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48436
48454
|
if (!token)
|
|
48437
48455
|
return;
|
|
48438
48456
|
try {
|
|
48439
|
-
|
|
48457
|
+
mkdirSync48(dirname29(envPath), { recursive: true });
|
|
48440
48458
|
let body = "";
|
|
48441
48459
|
try {
|
|
48442
48460
|
if (existsSync85(envPath))
|
|
48443
|
-
body =
|
|
48461
|
+
body = readFileSync73(envPath, "utf-8");
|
|
48444
48462
|
} catch {}
|
|
48445
48463
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
48446
48464
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -48448,7 +48466,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48448
48466
|
`) ? body + `
|
|
48449
48467
|
` : body) + line + `
|
|
48450
48468
|
`;
|
|
48451
|
-
|
|
48469
|
+
writeFileSync30(envPath, next, {
|
|
48452
48470
|
encoding: "utf-8",
|
|
48453
48471
|
mode: 384
|
|
48454
48472
|
});
|
|
@@ -48499,11 +48517,11 @@ __export(exports_apply, {
|
|
|
48499
48517
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
48500
48518
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
48501
48519
|
});
|
|
48502
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as
|
|
48520
|
+
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync29, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
|
|
48503
48521
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
48504
48522
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
48505
48523
|
import readline from "node:readline";
|
|
48506
|
-
import { dirname as dirname30, join as
|
|
48524
|
+
import { dirname as dirname30, join as join85, resolve as resolve52 } from "node:path";
|
|
48507
48525
|
import { homedir as homedir49 } from "node:os";
|
|
48508
48526
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
48509
48527
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -48515,7 +48533,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
48515
48533
|
return envPass;
|
|
48516
48534
|
try {
|
|
48517
48535
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
48518
|
-
const blobPath =
|
|
48536
|
+
const blobPath = join85(home2, ".switchroom", "vault-auto-unlock");
|
|
48519
48537
|
const pass = readAutoUnlockFile2(blobPath);
|
|
48520
48538
|
return pass && pass.length > 0 ? pass : null;
|
|
48521
48539
|
} catch {
|
|
@@ -48605,7 +48623,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48605
48623
|
let configText = null;
|
|
48606
48624
|
if (switchroomConfigPath && existsSync86(switchroomConfigPath)) {
|
|
48607
48625
|
try {
|
|
48608
|
-
configText =
|
|
48626
|
+
configText = readFileSync74(switchroomConfigPath, "utf-8");
|
|
48609
48627
|
} catch (err) {
|
|
48610
48628
|
ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
|
|
48611
48629
|
`));
|
|
@@ -48828,7 +48846,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
48828
48846
|
if (isCustomPath && ctx.customVaultPath) {
|
|
48829
48847
|
return dirname30(ctx.customVaultPath);
|
|
48830
48848
|
}
|
|
48831
|
-
return
|
|
48849
|
+
return join85(homeDir, ".switchroom", "vault");
|
|
48832
48850
|
}
|
|
48833
48851
|
function inspectVaultBindMountDir(vaultDir) {
|
|
48834
48852
|
if (!existsSync86(vaultDir))
|
|
@@ -48859,60 +48877,60 @@ function hasVaultRefs(value) {
|
|
|
48859
48877
|
async function ensureHostMountSources(config) {
|
|
48860
48878
|
const home2 = resolveHostHomeForCompose();
|
|
48861
48879
|
const dirs = [
|
|
48862
|
-
|
|
48863
|
-
|
|
48864
|
-
|
|
48865
|
-
|
|
48866
|
-
|
|
48880
|
+
join85(home2, ".switchroom", "approvals"),
|
|
48881
|
+
join85(home2, ".switchroom", "scheduler"),
|
|
48882
|
+
join85(home2, ".switchroom", "logs"),
|
|
48883
|
+
join85(home2, ".switchroom", "compose"),
|
|
48884
|
+
join85(home2, ".switchroom", "broker-operator")
|
|
48867
48885
|
];
|
|
48868
48886
|
for (const name of Object.keys(config.agents)) {
|
|
48869
|
-
dirs.push(
|
|
48870
|
-
dirs.push(
|
|
48871
|
-
dirs.push(
|
|
48872
|
-
dirs.push(
|
|
48873
|
-
if (existsSync86(
|
|
48874
|
-
dirs.push(
|
|
48887
|
+
dirs.push(join85(home2, ".switchroom", "agents", name));
|
|
48888
|
+
dirs.push(join85(home2, ".switchroom", "logs", name));
|
|
48889
|
+
dirs.push(join85(home2, ".claude", "projects", name));
|
|
48890
|
+
dirs.push(join85(home2, ".switchroom", "audit", name));
|
|
48891
|
+
if (existsSync86(join85(home2, ".switchroom-config"))) {
|
|
48892
|
+
dirs.push(join85(home2, ".switchroom-config", "agents", name, "personal-skills"));
|
|
48875
48893
|
}
|
|
48876
48894
|
}
|
|
48877
48895
|
for (const dir of dirs) {
|
|
48878
48896
|
await mkdir2(dir, { recursive: true });
|
|
48879
48897
|
}
|
|
48880
|
-
const autoUnlockPath =
|
|
48898
|
+
const autoUnlockPath = join85(home2, ".switchroom", "vault-auto-unlock");
|
|
48881
48899
|
if (!existsSync86(autoUnlockPath)) {
|
|
48882
|
-
|
|
48900
|
+
writeFileSync31(autoUnlockPath, "", { mode: 384 });
|
|
48883
48901
|
}
|
|
48884
|
-
const auditLogPath =
|
|
48902
|
+
const auditLogPath = join85(home2, ".switchroom", "vault-audit.log");
|
|
48885
48903
|
if (!existsSync86(auditLogPath)) {
|
|
48886
|
-
|
|
48904
|
+
writeFileSync31(auditLogPath, "", { mode: 420 });
|
|
48887
48905
|
}
|
|
48888
48906
|
const grantsDbDir = getGrantsDbDir(home2);
|
|
48889
|
-
|
|
48907
|
+
mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
|
|
48890
48908
|
migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
|
|
48891
|
-
const hostdAuditLogPath =
|
|
48909
|
+
const hostdAuditLogPath = join85(home2, ".switchroom", "host-control-audit.log");
|
|
48892
48910
|
if (!existsSync86(hostdAuditLogPath)) {
|
|
48893
|
-
|
|
48911
|
+
writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
|
|
48894
48912
|
}
|
|
48895
48913
|
for (const name of Object.keys(config.agents)) {
|
|
48896
|
-
const tokenPath =
|
|
48914
|
+
const tokenPath = join85(home2, ".switchroom", "agents", name, ".vault-token");
|
|
48897
48915
|
if (!existsSync86(tokenPath)) {
|
|
48898
|
-
|
|
48916
|
+
writeFileSync31(tokenPath, "", { mode: 384 });
|
|
48899
48917
|
}
|
|
48900
48918
|
try {
|
|
48901
48919
|
const uid = allocateAgentUid(name);
|
|
48902
48920
|
chownSync8(tokenPath, uid, uid);
|
|
48903
48921
|
} catch {}
|
|
48904
48922
|
}
|
|
48905
|
-
const fleetDir =
|
|
48923
|
+
const fleetDir = join85(home2, ".switchroom", "fleet");
|
|
48906
48924
|
await mkdir2(fleetDir, { recursive: true });
|
|
48907
|
-
const invariantsPath =
|
|
48925
|
+
const invariantsPath = join85(fleetDir, "switchroom-invariants.md");
|
|
48908
48926
|
const invariantsCanonical = renderFleetInvariants();
|
|
48909
|
-
const invariantsCurrent = existsSync86(invariantsPath) ?
|
|
48927
|
+
const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
|
|
48910
48928
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
48911
|
-
|
|
48929
|
+
writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
48912
48930
|
}
|
|
48913
|
-
const fleetClaudePath =
|
|
48931
|
+
const fleetClaudePath = join85(fleetDir, "CLAUDE.md");
|
|
48914
48932
|
if (!existsSync86(fleetClaudePath)) {
|
|
48915
|
-
|
|
48933
|
+
writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
48916
48934
|
mode: 420
|
|
48917
48935
|
});
|
|
48918
48936
|
}
|
|
@@ -48996,17 +49014,17 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
48996
49014
|
}
|
|
48997
49015
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
48998
49016
|
const ctx = detectInstallType();
|
|
48999
|
-
const dir =
|
|
49000
|
-
const out =
|
|
49017
|
+
const dir = join85(homeDir, ".switchroom");
|
|
49018
|
+
const out = join85(dir, "install-type.json");
|
|
49001
49019
|
const tmp = `${out}.tmp`;
|
|
49002
|
-
|
|
49020
|
+
mkdirSync49(dir, { recursive: true });
|
|
49003
49021
|
const payload = {
|
|
49004
49022
|
install_type: ctx.install_type,
|
|
49005
49023
|
detected_at: new Date().toISOString(),
|
|
49006
49024
|
source_paths: ctx.source_paths
|
|
49007
49025
|
};
|
|
49008
|
-
|
|
49009
|
-
|
|
49026
|
+
writeFileSync31(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
49027
|
+
renameSync20(tmp, out);
|
|
49010
49028
|
return out;
|
|
49011
49029
|
}
|
|
49012
49030
|
async function runApply(config, options, deps = {}, switchroomConfigPath) {
|
|
@@ -49064,17 +49082,17 @@ Applying switchroom config...
|
|
|
49064
49082
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49065
49083
|
`));
|
|
49066
49084
|
try {
|
|
49067
|
-
installUpdatePromptHook(
|
|
49085
|
+
installUpdatePromptHook(join85(agentsDir, name));
|
|
49068
49086
|
} catch (hookErr) {
|
|
49069
49087
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49070
49088
|
`));
|
|
49071
49089
|
}
|
|
49072
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49090
|
+
await refreshAgentConnectionHealth(config, name, join85(agentsDir, name), {
|
|
49073
49091
|
vaultAclReader: connHealthVaultAclReader
|
|
49074
49092
|
});
|
|
49075
49093
|
try {
|
|
49076
49094
|
const uid = allocateAgentUid(name);
|
|
49077
|
-
alignAgentUid(name,
|
|
49095
|
+
alignAgentUid(name, join85(agentsDir, name), uid, {
|
|
49078
49096
|
confirm: !options.nonInteractive,
|
|
49079
49097
|
writeOut
|
|
49080
49098
|
});
|
|
@@ -49119,7 +49137,7 @@ Applying switchroom config...
|
|
|
49119
49137
|
for (const name of agentNames) {
|
|
49120
49138
|
try {
|
|
49121
49139
|
const uid = allocateAgentUid(name);
|
|
49122
|
-
alignAgentUid(name,
|
|
49140
|
+
alignAgentUid(name, join85(agentsDir, name), uid, {
|
|
49123
49141
|
confirm: !options.nonInteractive,
|
|
49124
49142
|
writeOut
|
|
49125
49143
|
});
|
|
@@ -49463,7 +49481,7 @@ function copyExampleConfig2(name) {
|
|
|
49463
49481
|
}
|
|
49464
49482
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
49465
49483
|
if (embedded !== undefined) {
|
|
49466
|
-
|
|
49484
|
+
writeFileSync31(dest, embedded, { encoding: "utf8" });
|
|
49467
49485
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
49468
49486
|
return;
|
|
49469
49487
|
}
|
|
@@ -49479,7 +49497,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
49479
49497
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
49480
49498
|
const unwritable = [];
|
|
49481
49499
|
for (const name of targets) {
|
|
49482
|
-
const startSh =
|
|
49500
|
+
const startSh = join85(agentsDir, name, "start.sh");
|
|
49483
49501
|
if (!existsSync86(startSh))
|
|
49484
49502
|
continue;
|
|
49485
49503
|
try {
|
|
@@ -49684,7 +49702,7 @@ var init_apply = __esm(() => {
|
|
|
49684
49702
|
switchroom: switchroom_default,
|
|
49685
49703
|
minimal: minimal_default
|
|
49686
49704
|
};
|
|
49687
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
49705
|
+
DEFAULT_COMPOSE_PATH2 = join85(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
49688
49706
|
IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
|
|
49689
49707
|
` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
|
|
49690
49708
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -63133,7 +63151,7 @@ __export(exports_server2, {
|
|
|
63133
63151
|
TOOLS: () => TOOLS2
|
|
63134
63152
|
});
|
|
63135
63153
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
63136
|
-
import { existsSync as existsSync95, readFileSync as
|
|
63154
|
+
import { existsSync as existsSync95, readFileSync as readFileSync83 } from "node:fs";
|
|
63137
63155
|
function selfSocketPath() {
|
|
63138
63156
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
63139
63157
|
}
|
|
@@ -63422,7 +63440,7 @@ function getLastUpdateApplyStatus() {
|
|
|
63422
63440
|
}
|
|
63423
63441
|
let raw;
|
|
63424
63442
|
try {
|
|
63425
|
-
raw =
|
|
63443
|
+
raw = readFileSync83(path9, "utf-8");
|
|
63426
63444
|
} catch (err2) {
|
|
63427
63445
|
return errorText2(`get_status: failed to read audit log at ${path9}: ${err2.message}`);
|
|
63428
63446
|
}
|
|
@@ -64112,11 +64130,11 @@ __export(exports_scan, {
|
|
|
64112
64130
|
ledgerPathForBase: () => ledgerPathForBase
|
|
64113
64131
|
});
|
|
64114
64132
|
import {
|
|
64115
|
-
readFileSync as
|
|
64133
|
+
readFileSync as readFileSync85,
|
|
64116
64134
|
readdirSync as readdirSync37,
|
|
64117
64135
|
existsSync as existsSync98,
|
|
64118
|
-
mkdirSync as
|
|
64119
|
-
writeFileSync as
|
|
64136
|
+
mkdirSync as mkdirSync56,
|
|
64137
|
+
writeFileSync as writeFileSync37
|
|
64120
64138
|
} from "node:fs";
|
|
64121
64139
|
import { resolve as resolve57, dirname as dirname33 } from "node:path";
|
|
64122
64140
|
import { homedir as homedir58 } from "node:os";
|
|
@@ -64155,7 +64173,7 @@ function runScan(opts = {}) {
|
|
|
64155
64173
|
let sawArtifact = false;
|
|
64156
64174
|
try {
|
|
64157
64175
|
if (existsSync98(turnsPath)) {
|
|
64158
|
-
turnsText =
|
|
64176
|
+
turnsText = readFileSync85(turnsPath, "utf-8");
|
|
64159
64177
|
sawArtifact = true;
|
|
64160
64178
|
}
|
|
64161
64179
|
} catch (e) {
|
|
@@ -64163,7 +64181,7 @@ function runScan(opts = {}) {
|
|
|
64163
64181
|
}
|
|
64164
64182
|
try {
|
|
64165
64183
|
if (existsSync98(gwPath)) {
|
|
64166
|
-
gwText =
|
|
64184
|
+
gwText = readFileSync85(gwPath, "utf-8");
|
|
64167
64185
|
sawArtifact = true;
|
|
64168
64186
|
}
|
|
64169
64187
|
} catch (e) {
|
|
@@ -64205,7 +64223,7 @@ function readLedgerIfPresent(base) {
|
|
|
64205
64223
|
try {
|
|
64206
64224
|
if (!existsSync98(path9))
|
|
64207
64225
|
return null;
|
|
64208
|
-
return JSON.parse(
|
|
64226
|
+
return JSON.parse(readFileSync85(path9, "utf-8"));
|
|
64209
64227
|
} catch {
|
|
64210
64228
|
return null;
|
|
64211
64229
|
}
|
|
@@ -64215,8 +64233,8 @@ function ledgerPathForBase(base) {
|
|
|
64215
64233
|
}
|
|
64216
64234
|
function writeLedger(base, ledger) {
|
|
64217
64235
|
const path9 = ledgerPathForBase(base);
|
|
64218
|
-
|
|
64219
|
-
|
|
64236
|
+
mkdirSync56(dirname33(path9), { recursive: true });
|
|
64237
|
+
writeFileSync37(path9, JSON.stringify(ledger, null, 2) + `
|
|
64220
64238
|
`, "utf-8");
|
|
64221
64239
|
return path9;
|
|
64222
64240
|
}
|
|
@@ -66965,7 +66983,7 @@ function registerAgentCommand(program3) {
|
|
|
66965
66983
|
name,
|
|
66966
66984
|
status: status?.active ?? "unknown",
|
|
66967
66985
|
uptime: formatUptime2(status?.uptime ?? null),
|
|
66968
|
-
model: resolved.model
|
|
66986
|
+
model: resolveMainModel(resolved.model),
|
|
66969
66987
|
thinking_effort: resolved.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
|
|
66970
66988
|
extends: agentConfig.extends ?? "default",
|
|
66971
66989
|
topic_name: agentConfig.topic_name,
|
|
@@ -66999,6 +67017,16 @@ function registerAgentCommand(program3) {
|
|
|
66999
67017
|
printTable(headers, rows, widths);
|
|
67000
67018
|
console.log();
|
|
67001
67019
|
}));
|
|
67020
|
+
agent.command("effective-model <name>").description("Print the cascade-resolved model the agent's launcher boots with").action(withConfigError(async (name) => {
|
|
67021
|
+
const config = getConfig(program3);
|
|
67022
|
+
const agentConfig = config.agents[name];
|
|
67023
|
+
if (!agentConfig) {
|
|
67024
|
+
console.error(source_default.red(`Agent "${name}" is not defined in switchroom.yaml`));
|
|
67025
|
+
process.exit(1);
|
|
67026
|
+
}
|
|
67027
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
67028
|
+
console.log(resolveMainModel(resolved.model));
|
|
67029
|
+
}));
|
|
67002
67030
|
agent.command("status <name>").description("Show health status for a single agent (PID, polling, Hindsight, last messages)").option("--json", "Output as JSON").action(withConfigError(async (name, opts) => {
|
|
67003
67031
|
const config = getConfig(program3);
|
|
67004
67032
|
const agentsDir = resolveAgentsDir(config);
|
|
@@ -89646,9 +89674,425 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
89646
89674
|
});
|
|
89647
89675
|
}
|
|
89648
89676
|
|
|
89677
|
+
// src/cli/hindsight-mcp-shim.ts
|
|
89678
|
+
init_hindsight();
|
|
89679
|
+
import { mkdirSync as mkdirSync45, readFileSync as readFileSync70, renameSync as renameSync19, writeFileSync as writeFileSync27 } from "node:fs";
|
|
89680
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
89681
|
+
import { join as join81 } from "node:path";
|
|
89682
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
89683
|
+
var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
|
|
89684
|
+
"2025-06-18",
|
|
89685
|
+
"2025-03-26",
|
|
89686
|
+
"2024-11-05"
|
|
89687
|
+
];
|
|
89688
|
+
var TOOLS_LIST_TIMEOUT_MS = 3000;
|
|
89689
|
+
var TOOLS_CALL_TIMEOUT_MS = 120000;
|
|
89690
|
+
var UPSTREAM_CONNECT_TIMEOUT_MS = 3000;
|
|
89691
|
+
var TOOLS_CACHE_FILENAME = "hindsight-tools-list.json";
|
|
89692
|
+
var FALLBACK_TOOL_TABLE = {
|
|
89693
|
+
cancel_operation: [["operation_id"], ["bank_id", "operation_id"]],
|
|
89694
|
+
clear_memories: [[], ["bank_id", "type"]],
|
|
89695
|
+
create_bank: [["bank_id"], ["bank_id", "mission", "name"]],
|
|
89696
|
+
create_directive: [["content", "name"], ["bank_id", "content", "is_active", "name", "priority", "tags"]],
|
|
89697
|
+
create_mental_model: [["name", "source_query"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]],
|
|
89698
|
+
delete_bank: [[], ["bank_id"]],
|
|
89699
|
+
delete_directive: [["directive_id"], ["bank_id", "directive_id"]],
|
|
89700
|
+
delete_document: [["document_id"], ["bank_id", "document_id"]],
|
|
89701
|
+
delete_mental_model: [["mental_model_id"], ["bank_id", "mental_model_id"]],
|
|
89702
|
+
get_bank: [[], ["bank_id"]],
|
|
89703
|
+
get_bank_stats: [[], ["bank_id"]],
|
|
89704
|
+
get_document: [["document_id"], ["bank_id", "document_id"]],
|
|
89705
|
+
get_memory: [["memory_id"], ["bank_id", "memory_id"]],
|
|
89706
|
+
get_mental_model: [["mental_model_id"], ["bank_id", "detail", "mental_model_id"]],
|
|
89707
|
+
get_operation: [["operation_id"], ["bank_id", "operation_id"]],
|
|
89708
|
+
list_banks: [[], []],
|
|
89709
|
+
list_directives: [[], ["active_only", "bank_id", "tags"]],
|
|
89710
|
+
list_documents: [[], ["bank_id", "limit", "q"]],
|
|
89711
|
+
list_memories: [[], ["bank_id", "limit", "offset", "q", "type"]],
|
|
89712
|
+
list_mental_models: [[], ["bank_id", "detail", "tags"]],
|
|
89713
|
+
list_operations: [[], ["bank_id", "limit", "status"]],
|
|
89714
|
+
list_tags: [[], ["bank_id", "limit", "q"]],
|
|
89715
|
+
recall: [["query"], ["bank_id", "budget", "max_tokens", "query", "query_timestamp", "tag_groups", "tags", "tags_match", "types"]],
|
|
89716
|
+
reflect: [["query"], ["bank_id", "budget", "context", "max_tokens", "query", "response_schema", "tags", "tags_match"]],
|
|
89717
|
+
refresh_mental_model: [["mental_model_id"], ["bank_id", "mental_model_id"]],
|
|
89718
|
+
retain: [["content"], ["bank_id", "content", "context", "document_id", "metadata", "strategy", "tags", "timestamp", "update_mode"]],
|
|
89719
|
+
sync_retain: [["content"], ["bank_id", "content", "context", "document_id", "metadata", "strategy", "tags", "timestamp"]],
|
|
89720
|
+
update_bank: [[], ["bank_id", "config_updates", "mission", "name"]],
|
|
89721
|
+
update_mental_model: [["mental_model_id"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]]
|
|
89722
|
+
};
|
|
89723
|
+
function buildFallbackToolsList() {
|
|
89724
|
+
const tools = Object.entries(FALLBACK_TOOL_TABLE).map(([name, [required, props]]) => ({
|
|
89725
|
+
name,
|
|
89726
|
+
description: "Hindsight memory tool (served from the shim's static fallback " + "manifest because the backend has not been reachable yet; the " + "schema is permissive and will be replaced by the live one).",
|
|
89727
|
+
inputSchema: {
|
|
89728
|
+
type: "object",
|
|
89729
|
+
properties: Object.fromEntries(props.map((p) => [p, {}])),
|
|
89730
|
+
required
|
|
89731
|
+
}
|
|
89732
|
+
}));
|
|
89733
|
+
return { tools };
|
|
89734
|
+
}
|
|
89735
|
+
|
|
89736
|
+
class UpstreamClient {
|
|
89737
|
+
opts;
|
|
89738
|
+
sessionId = null;
|
|
89739
|
+
protocolVersion = null;
|
|
89740
|
+
nextId = 1;
|
|
89741
|
+
constructor(opts) {
|
|
89742
|
+
this.opts = opts;
|
|
89743
|
+
}
|
|
89744
|
+
get fetchImpl() {
|
|
89745
|
+
return this.opts.fetchImpl ?? fetch;
|
|
89746
|
+
}
|
|
89747
|
+
reset() {
|
|
89748
|
+
this.sessionId = null;
|
|
89749
|
+
this.protocolVersion = null;
|
|
89750
|
+
}
|
|
89751
|
+
get connected() {
|
|
89752
|
+
return this.sessionId !== null || this.protocolVersion !== null;
|
|
89753
|
+
}
|
|
89754
|
+
headers() {
|
|
89755
|
+
const h = {
|
|
89756
|
+
"content-type": "application/json",
|
|
89757
|
+
accept: "application/json, text/event-stream"
|
|
89758
|
+
};
|
|
89759
|
+
if (this.opts.bankId)
|
|
89760
|
+
h["X-Bank-Id"] = this.opts.bankId;
|
|
89761
|
+
if (this.sessionId)
|
|
89762
|
+
h["mcp-session-id"] = this.sessionId;
|
|
89763
|
+
if (this.protocolVersion)
|
|
89764
|
+
h["mcp-protocol-version"] = this.protocolVersion;
|
|
89765
|
+
return h;
|
|
89766
|
+
}
|
|
89767
|
+
async post(body, timeoutMs) {
|
|
89768
|
+
const ctl = new AbortController;
|
|
89769
|
+
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
89770
|
+
try {
|
|
89771
|
+
return await this.fetchImpl(this.opts.url, {
|
|
89772
|
+
method: "POST",
|
|
89773
|
+
headers: this.headers(),
|
|
89774
|
+
body: JSON.stringify(body),
|
|
89775
|
+
signal: ctl.signal
|
|
89776
|
+
});
|
|
89777
|
+
} finally {
|
|
89778
|
+
clearTimeout(timer);
|
|
89779
|
+
}
|
|
89780
|
+
}
|
|
89781
|
+
async parseResponse(res, id) {
|
|
89782
|
+
const ctype = res.headers.get("content-type") ?? "";
|
|
89783
|
+
const text = await res.text();
|
|
89784
|
+
if (ctype.includes("text/event-stream")) {
|
|
89785
|
+
for (const chunk2 of text.split(/\n\n/)) {
|
|
89786
|
+
const data = chunk2.split(/\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join(`
|
|
89787
|
+
`);
|
|
89788
|
+
if (!data)
|
|
89789
|
+
continue;
|
|
89790
|
+
try {
|
|
89791
|
+
const msg = JSON.parse(data);
|
|
89792
|
+
if (msg.id === id)
|
|
89793
|
+
return msg;
|
|
89794
|
+
} catch {}
|
|
89795
|
+
}
|
|
89796
|
+
throw new Error("no matching JSON-RPC response in SSE stream");
|
|
89797
|
+
}
|
|
89798
|
+
return JSON.parse(text);
|
|
89799
|
+
}
|
|
89800
|
+
async ensureSession() {
|
|
89801
|
+
if (this.protocolVersion)
|
|
89802
|
+
return;
|
|
89803
|
+
const timeoutMs = this.opts.connectTimeoutMs ?? UPSTREAM_CONNECT_TIMEOUT_MS;
|
|
89804
|
+
const id = this.nextId++;
|
|
89805
|
+
const res = await this.post({
|
|
89806
|
+
jsonrpc: "2.0",
|
|
89807
|
+
id,
|
|
89808
|
+
method: "initialize",
|
|
89809
|
+
params: {
|
|
89810
|
+
protocolVersion: SHIM_SUPPORTED_PROTOCOL_VERSIONS[0],
|
|
89811
|
+
capabilities: {},
|
|
89812
|
+
clientInfo: { name: "switchroom-hindsight-shim", version: "1.0.0" }
|
|
89813
|
+
}
|
|
89814
|
+
}, timeoutMs);
|
|
89815
|
+
if (!res.ok) {
|
|
89816
|
+
throw new Error(`upstream initialize failed: HTTP ${res.status}`);
|
|
89817
|
+
}
|
|
89818
|
+
this.sessionId = res.headers.get("mcp-session-id");
|
|
89819
|
+
const msg = await this.parseResponse(res, id);
|
|
89820
|
+
if (msg.error) {
|
|
89821
|
+
throw new Error(`upstream initialize error: ${msg.error.message}`);
|
|
89822
|
+
}
|
|
89823
|
+
const negotiated = msg.result?.protocolVersion;
|
|
89824
|
+
this.protocolVersion = negotiated ?? SHIM_SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
89825
|
+
const notifRes = await this.post({ jsonrpc: "2.0", method: "notifications/initialized" }, timeoutMs);
|
|
89826
|
+
if (!notifRes.ok && notifRes.status !== 405) {
|
|
89827
|
+
throw new Error(`upstream initialized notification failed: HTTP ${notifRes.status}`);
|
|
89828
|
+
}
|
|
89829
|
+
await notifRes.text().catch(() => {
|
|
89830
|
+
return;
|
|
89831
|
+
});
|
|
89832
|
+
}
|
|
89833
|
+
async request(method, params, timeoutMs, retry = true) {
|
|
89834
|
+
try {
|
|
89835
|
+
await this.ensureSession();
|
|
89836
|
+
} catch (err) {
|
|
89837
|
+
this.reset();
|
|
89838
|
+
if (retry)
|
|
89839
|
+
return this.request(method, params, timeoutMs, false);
|
|
89840
|
+
throw err;
|
|
89841
|
+
}
|
|
89842
|
+
const id = this.nextId++;
|
|
89843
|
+
let res;
|
|
89844
|
+
try {
|
|
89845
|
+
res = await this.post({ jsonrpc: "2.0", id, method, params }, timeoutMs);
|
|
89846
|
+
} catch (err) {
|
|
89847
|
+
this.reset();
|
|
89848
|
+
if (retry && isPreDeliveryError(err)) {
|
|
89849
|
+
return this.request(method, params, timeoutMs, false);
|
|
89850
|
+
}
|
|
89851
|
+
throw err;
|
|
89852
|
+
}
|
|
89853
|
+
if (res.status === 404) {
|
|
89854
|
+
this.reset();
|
|
89855
|
+
await res.text().catch(() => {
|
|
89856
|
+
return;
|
|
89857
|
+
});
|
|
89858
|
+
if (retry)
|
|
89859
|
+
return this.request(method, params, timeoutMs, false);
|
|
89860
|
+
throw new Error("upstream session expired (404)");
|
|
89861
|
+
}
|
|
89862
|
+
try {
|
|
89863
|
+
if (!res.ok)
|
|
89864
|
+
throw new Error(`upstream HTTP ${res.status}`);
|
|
89865
|
+
return await this.parseResponse(res, id);
|
|
89866
|
+
} catch (err) {
|
|
89867
|
+
this.reset();
|
|
89868
|
+
throw err;
|
|
89869
|
+
}
|
|
89870
|
+
}
|
|
89871
|
+
}
|
|
89872
|
+
function isPreDeliveryError(err) {
|
|
89873
|
+
const PRE_DELIVERY_CODES = new Set([
|
|
89874
|
+
"ECONNREFUSED",
|
|
89875
|
+
"ENOTFOUND",
|
|
89876
|
+
"EAI_AGAIN",
|
|
89877
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
89878
|
+
]);
|
|
89879
|
+
let cur = err;
|
|
89880
|
+
for (let depth = 0;cur && depth < 5; depth++) {
|
|
89881
|
+
const code = cur.code;
|
|
89882
|
+
if (typeof code === "string" && PRE_DELIVERY_CODES.has(code))
|
|
89883
|
+
return true;
|
|
89884
|
+
const errors2 = cur.errors;
|
|
89885
|
+
if (Array.isArray(errors2) && errors2.length > 0) {
|
|
89886
|
+
return errors2.every((e) => isPreDeliveryError(e));
|
|
89887
|
+
}
|
|
89888
|
+
cur = cur.cause;
|
|
89889
|
+
}
|
|
89890
|
+
return false;
|
|
89891
|
+
}
|
|
89892
|
+
|
|
89893
|
+
class HindsightShim {
|
|
89894
|
+
opts;
|
|
89895
|
+
upstream;
|
|
89896
|
+
log;
|
|
89897
|
+
constructor(opts) {
|
|
89898
|
+
this.opts = opts;
|
|
89899
|
+
this.upstream = new UpstreamClient(opts);
|
|
89900
|
+
this.log = opts.logger ?? ((l) => process.stderr.write(l + `
|
|
89901
|
+
`));
|
|
89902
|
+
}
|
|
89903
|
+
get cachePath() {
|
|
89904
|
+
return join81(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
|
|
89905
|
+
}
|
|
89906
|
+
writeCache(result) {
|
|
89907
|
+
try {
|
|
89908
|
+
mkdirSync45(this.opts.cacheDir, { recursive: true });
|
|
89909
|
+
const tmp = join81(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
89910
|
+
writeFileSync27(tmp, JSON.stringify(result, null, 2) + `
|
|
89911
|
+
`);
|
|
89912
|
+
renameSync19(tmp, this.cachePath);
|
|
89913
|
+
} catch (err) {
|
|
89914
|
+
this.log(`[hindsight-shim] cache write failed: ${String(err)}`);
|
|
89915
|
+
}
|
|
89916
|
+
}
|
|
89917
|
+
readCache() {
|
|
89918
|
+
try {
|
|
89919
|
+
const parsed = JSON.parse(readFileSync70(this.cachePath, "utf-8"));
|
|
89920
|
+
if (Array.isArray(parsed.tools))
|
|
89921
|
+
return parsed;
|
|
89922
|
+
return null;
|
|
89923
|
+
} catch {
|
|
89924
|
+
return null;
|
|
89925
|
+
}
|
|
89926
|
+
}
|
|
89927
|
+
async handle(msg) {
|
|
89928
|
+
const { id, method } = msg;
|
|
89929
|
+
if (method === undefined)
|
|
89930
|
+
return null;
|
|
89931
|
+
if (id === undefined || id === null) {
|
|
89932
|
+
if (method !== "notifications/initialized" && this.upstream.connected) {
|
|
89933
|
+
this.upstream.request(method, msg.params, this.opts.connectTimeoutMs ?? UPSTREAM_CONNECT_TIMEOUT_MS, false).catch(() => {
|
|
89934
|
+
return;
|
|
89935
|
+
});
|
|
89936
|
+
}
|
|
89937
|
+
return null;
|
|
89938
|
+
}
|
|
89939
|
+
switch (method) {
|
|
89940
|
+
case "initialize":
|
|
89941
|
+
return { jsonrpc: "2.0", id, result: this.initializeResult(msg.params) };
|
|
89942
|
+
case "tools/list":
|
|
89943
|
+
return { jsonrpc: "2.0", id, result: await this.toolsList(msg.params) };
|
|
89944
|
+
case "tools/call":
|
|
89945
|
+
return { jsonrpc: "2.0", id, result: await this.toolsCall(msg.params) };
|
|
89946
|
+
case "ping":
|
|
89947
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
89948
|
+
default:
|
|
89949
|
+
return this.forward(msg);
|
|
89950
|
+
}
|
|
89951
|
+
}
|
|
89952
|
+
initializeResult(params) {
|
|
89953
|
+
const requested = params?.protocolVersion;
|
|
89954
|
+
const version2 = requested && SHIM_SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : SHIM_SUPPORTED_PROTOCOL_VERSIONS[0];
|
|
89955
|
+
return {
|
|
89956
|
+
protocolVersion: version2,
|
|
89957
|
+
capabilities: { tools: { listChanged: true } },
|
|
89958
|
+
serverInfo: { name: "switchroom-hindsight-shim", version: "1.0.0" }
|
|
89959
|
+
};
|
|
89960
|
+
}
|
|
89961
|
+
staleManifestServed = false;
|
|
89962
|
+
notificationSink = () => {
|
|
89963
|
+
return;
|
|
89964
|
+
};
|
|
89965
|
+
notifyIfRecovered() {
|
|
89966
|
+
if (!this.staleManifestServed)
|
|
89967
|
+
return;
|
|
89968
|
+
this.staleManifestServed = false;
|
|
89969
|
+
this.notificationSink({
|
|
89970
|
+
jsonrpc: "2.0",
|
|
89971
|
+
method: "notifications/tools/list_changed"
|
|
89972
|
+
});
|
|
89973
|
+
}
|
|
89974
|
+
async toolsList(params) {
|
|
89975
|
+
try {
|
|
89976
|
+
const res = await this.upstream.request("tools/list", params ?? {}, this.opts.toolsListTimeoutMs ?? TOOLS_LIST_TIMEOUT_MS);
|
|
89977
|
+
if (res.error)
|
|
89978
|
+
throw new Error(res.error.message);
|
|
89979
|
+
const result = res.result;
|
|
89980
|
+
if (Array.isArray(result?.tools)) {
|
|
89981
|
+
this.writeCache(result);
|
|
89982
|
+
this.staleManifestServed = false;
|
|
89983
|
+
return result;
|
|
89984
|
+
}
|
|
89985
|
+
throw new Error("upstream tools/list returned no tools array");
|
|
89986
|
+
} catch (err) {
|
|
89987
|
+
this.log(`[hindsight-shim] live tools/list failed (${String(err)}); serving ` + "cached/fallback manifest");
|
|
89988
|
+
this.staleManifestServed = true;
|
|
89989
|
+
return this.readCache() ?? buildFallbackToolsList();
|
|
89990
|
+
}
|
|
89991
|
+
}
|
|
89992
|
+
async toolsCall(params) {
|
|
89993
|
+
try {
|
|
89994
|
+
const res = await this.upstream.request("tools/call", params, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
|
|
89995
|
+
this.notifyIfRecovered();
|
|
89996
|
+
if (res.error) {
|
|
89997
|
+
return {
|
|
89998
|
+
content: [
|
|
89999
|
+
{ type: "text", text: `Hindsight returned an error: ${res.error.message}` }
|
|
90000
|
+
],
|
|
90001
|
+
isError: true
|
|
90002
|
+
};
|
|
90003
|
+
}
|
|
90004
|
+
return res.result;
|
|
90005
|
+
} catch (err) {
|
|
90006
|
+
const name = params?.name ?? "unknown";
|
|
90007
|
+
this.log(`[hindsight-shim] tools/call ${name} failed after retry: ${String(err)}`);
|
|
90008
|
+
return {
|
|
90009
|
+
content: [
|
|
90010
|
+
{
|
|
90011
|
+
type: "text",
|
|
90012
|
+
text: "Hindsight memory is temporarily unavailable (backend at " + `${this.opts.url} is not reachable: ${String(err)}). ` + "This is transient - the shim reconnects automatically, so " + "simply retry this tool call shortly. Do not treat memory " + "as permanently lost."
|
|
90013
|
+
}
|
|
90014
|
+
],
|
|
90015
|
+
isError: true
|
|
90016
|
+
};
|
|
90017
|
+
}
|
|
90018
|
+
}
|
|
90019
|
+
async forward(msg) {
|
|
90020
|
+
try {
|
|
90021
|
+
const res = await this.upstream.request(msg.method, msg.params, this.opts.toolsListTimeoutMs ?? TOOLS_LIST_TIMEOUT_MS);
|
|
90022
|
+
return { jsonrpc: "2.0", id: msg.id, result: res.result, ...res.error ? { error: res.error } : {} };
|
|
90023
|
+
} catch (err) {
|
|
90024
|
+
return {
|
|
90025
|
+
jsonrpc: "2.0",
|
|
90026
|
+
id: msg.id,
|
|
90027
|
+
error: {
|
|
90028
|
+
code: -32001,
|
|
90029
|
+
message: `hindsight backend unavailable: ${String(err)}`
|
|
90030
|
+
}
|
|
90031
|
+
};
|
|
90032
|
+
}
|
|
90033
|
+
}
|
|
90034
|
+
async run(input, output) {
|
|
90035
|
+
this.notificationSink = (msg) => output.write(JSON.stringify(msg) + `
|
|
90036
|
+
`);
|
|
90037
|
+
const rl = createInterface6({ input, crlfDelay: Infinity });
|
|
90038
|
+
const inflight = new Set;
|
|
90039
|
+
rl.on("line", (line) => {
|
|
90040
|
+
const trimmed = line.trim();
|
|
90041
|
+
if (!trimmed)
|
|
90042
|
+
return;
|
|
90043
|
+
let msg;
|
|
90044
|
+
try {
|
|
90045
|
+
msg = JSON.parse(trimmed);
|
|
90046
|
+
} catch {
|
|
90047
|
+
this.log(`[hindsight-shim] dropping non-JSON stdin line`);
|
|
90048
|
+
return;
|
|
90049
|
+
}
|
|
90050
|
+
const task = (async () => {
|
|
90051
|
+
try {
|
|
90052
|
+
const res = await this.handle(msg);
|
|
90053
|
+
if (res)
|
|
90054
|
+
output.write(JSON.stringify(res) + `
|
|
90055
|
+
`);
|
|
90056
|
+
} catch (err) {
|
|
90057
|
+
this.log(`[hindsight-shim] handler threw: ${String(err)}`);
|
|
90058
|
+
if (msg.id !== undefined && msg.id !== null) {
|
|
90059
|
+
output.write(JSON.stringify({
|
|
90060
|
+
jsonrpc: "2.0",
|
|
90061
|
+
id: msg.id,
|
|
90062
|
+
error: { code: -32603, message: `shim internal error: ${String(err)}` }
|
|
90063
|
+
}) + `
|
|
90064
|
+
`);
|
|
90065
|
+
}
|
|
90066
|
+
}
|
|
90067
|
+
})();
|
|
90068
|
+
inflight.add(task);
|
|
90069
|
+
task.finally(() => inflight.delete(task));
|
|
90070
|
+
});
|
|
90071
|
+
await new Promise((resolve51) => rl.on("close", resolve51));
|
|
90072
|
+
while (inflight.size > 0) {
|
|
90073
|
+
await Promise.allSettled([...inflight]);
|
|
90074
|
+
}
|
|
90075
|
+
}
|
|
90076
|
+
}
|
|
90077
|
+
function resolveShimOptionsFromEnv(env2) {
|
|
90078
|
+
const home2 = env2.HOME && env2.HOME !== "/" ? env2.HOME : tmpdir5();
|
|
90079
|
+
return {
|
|
90080
|
+
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
90081
|
+
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
90082
|
+
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join81(home2, ".hindsight-shim")
|
|
90083
|
+
};
|
|
90084
|
+
}
|
|
90085
|
+
function registerHindsightMcpShimCommand(program3) {
|
|
90086
|
+
program3.command("hindsight-mcp-shim", { hidden: true }).description("Internal: lazy-connect stdio MCP proxy for the Hindsight memory " + "backend. Spawned as the `hindsight` MCP command inside agents.").action(async () => {
|
|
90087
|
+
const shim = new HindsightShim(resolveShimOptionsFromEnv(process.env));
|
|
90088
|
+
await shim.run(process.stdin, process.stdout);
|
|
90089
|
+
process.exit(0);
|
|
90090
|
+
});
|
|
90091
|
+
}
|
|
90092
|
+
|
|
89649
90093
|
// src/cli/deliver-file.ts
|
|
89650
90094
|
init_client2();
|
|
89651
|
-
import { readFileSync as
|
|
90095
|
+
import { readFileSync as readFileSync71, statSync as statSync46 } from "node:fs";
|
|
89652
90096
|
import { basename as basename11 } from "node:path";
|
|
89653
90097
|
|
|
89654
90098
|
// src/delivery/onedrive.ts
|
|
@@ -89988,7 +90432,7 @@ async function defaultResolveProvider() {
|
|
|
89988
90432
|
async function runDeliverFile(localPath, deps = {}) {
|
|
89989
90433
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
89990
90434
|
const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
|
|
89991
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
90435
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync71(p)));
|
|
89992
90436
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
89993
90437
|
let size;
|
|
89994
90438
|
try {
|
|
@@ -90309,8 +90753,8 @@ function runRedactStdin() {
|
|
|
90309
90753
|
}
|
|
90310
90754
|
|
|
90311
90755
|
// src/cli/status-ask.ts
|
|
90312
|
-
import { readFileSync as
|
|
90313
|
-
import { join as
|
|
90756
|
+
import { readFileSync as readFileSync75, existsSync as existsSync87, readdirSync as readdirSync30 } from "node:fs";
|
|
90757
|
+
import { join as join86 } from "node:path";
|
|
90314
90758
|
import { homedir as homedir50 } from "node:os";
|
|
90315
90759
|
|
|
90316
90760
|
// src/status-ask/report.ts
|
|
@@ -90585,7 +91029,7 @@ function runReport(opts) {
|
|
|
90585
91029
|
for (const src of sources) {
|
|
90586
91030
|
let content;
|
|
90587
91031
|
try {
|
|
90588
|
-
content =
|
|
91032
|
+
content = readFileSync75(src.path, "utf-8");
|
|
90589
91033
|
} catch (err) {
|
|
90590
91034
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
90591
91035
|
`);
|
|
@@ -90646,7 +91090,7 @@ function resolveSources(explicitPath) {
|
|
|
90646
91090
|
const config = loadConfig();
|
|
90647
91091
|
agentsDir = resolveAgentsDir(config);
|
|
90648
91092
|
} catch {
|
|
90649
|
-
agentsDir =
|
|
91093
|
+
agentsDir = join86(homedir50(), ".switchroom", "agents");
|
|
90650
91094
|
}
|
|
90651
91095
|
if (!existsSync87(agentsDir))
|
|
90652
91096
|
return [];
|
|
@@ -90658,7 +91102,7 @@ function resolveSources(explicitPath) {
|
|
|
90658
91102
|
return [];
|
|
90659
91103
|
}
|
|
90660
91104
|
for (const name of entries) {
|
|
90661
|
-
const path9 =
|
|
91105
|
+
const path9 = join86(agentsDir, name, "runtime-metrics.jsonl");
|
|
90662
91106
|
if (existsSync87(path9)) {
|
|
90663
91107
|
sources.push({ path: path9, agent: name });
|
|
90664
91108
|
}
|
|
@@ -90691,43 +91135,43 @@ import {
|
|
|
90691
91135
|
closeSync as closeSync14,
|
|
90692
91136
|
existsSync as existsSync88,
|
|
90693
91137
|
fsyncSync as fsyncSync8,
|
|
90694
|
-
mkdirSync as
|
|
91138
|
+
mkdirSync as mkdirSync50,
|
|
90695
91139
|
openSync as openSync14,
|
|
90696
91140
|
readdirSync as readdirSync31,
|
|
90697
|
-
readFileSync as
|
|
90698
|
-
renameSync as
|
|
91141
|
+
readFileSync as readFileSync76,
|
|
91142
|
+
renameSync as renameSync21,
|
|
90699
91143
|
statSync as statSync48,
|
|
90700
91144
|
unlinkSync as unlinkSync18,
|
|
90701
91145
|
writeSync as writeSync10
|
|
90702
91146
|
} from "node:fs";
|
|
90703
|
-
import { join as
|
|
91147
|
+
import { join as join87, resolve as resolve53 } from "node:path";
|
|
90704
91148
|
var STAGING_SUBDIR = ".staging";
|
|
90705
91149
|
function overlayPathsFor(agent, opts = {}) {
|
|
90706
91150
|
const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
90707
|
-
const scheduleDir =
|
|
90708
|
-
const scheduleStagingDir =
|
|
90709
|
-
const skillsDir =
|
|
90710
|
-
const skillsStagingDir =
|
|
91151
|
+
const scheduleDir = join87(base, "schedule.d");
|
|
91152
|
+
const scheduleStagingDir = join87(scheduleDir, STAGING_SUBDIR);
|
|
91153
|
+
const skillsDir = join87(base, "skills.d");
|
|
91154
|
+
const skillsStagingDir = join87(skillsDir, STAGING_SUBDIR);
|
|
90711
91155
|
return {
|
|
90712
91156
|
agentRoot: base,
|
|
90713
91157
|
scheduleDir,
|
|
90714
91158
|
scheduleStagingDir,
|
|
90715
91159
|
skillsDir,
|
|
90716
91160
|
skillsStagingDir,
|
|
90717
|
-
lockPath:
|
|
91161
|
+
lockPath: join87(base, ".lock"),
|
|
90718
91162
|
stagingDir: scheduleStagingDir
|
|
90719
91163
|
};
|
|
90720
91164
|
}
|
|
90721
91165
|
function ensureDirs(paths) {
|
|
90722
|
-
|
|
90723
|
-
|
|
91166
|
+
mkdirSync50(paths.scheduleDir, { recursive: true });
|
|
91167
|
+
mkdirSync50(paths.scheduleStagingDir, { recursive: true });
|
|
90724
91168
|
}
|
|
90725
91169
|
function ensureSkillsDirs(paths) {
|
|
90726
|
-
|
|
90727
|
-
|
|
91170
|
+
mkdirSync50(paths.skillsDir, { recursive: true });
|
|
91171
|
+
mkdirSync50(paths.skillsStagingDir, { recursive: true });
|
|
90728
91172
|
}
|
|
90729
91173
|
function withAgentLock(paths, fn) {
|
|
90730
|
-
|
|
91174
|
+
mkdirSync50(paths.agentRoot, { recursive: true });
|
|
90731
91175
|
const start = Date.now();
|
|
90732
91176
|
const TIMEOUT_MS = 5000;
|
|
90733
91177
|
let fd = null;
|
|
@@ -90768,8 +91212,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90768
91212
|
const paths = overlayPathsFor(agent, opts);
|
|
90769
91213
|
return withAgentLock(paths, () => {
|
|
90770
91214
|
ensureDirs(paths);
|
|
90771
|
-
const stagingPath =
|
|
90772
|
-
const finalPath =
|
|
91215
|
+
const stagingPath = join87(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
91216
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90773
91217
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90774
91218
|
try {
|
|
90775
91219
|
writeSync10(fd, yamlText);
|
|
@@ -90777,7 +91221,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90777
91221
|
} finally {
|
|
90778
91222
|
closeSync14(fd);
|
|
90779
91223
|
}
|
|
90780
|
-
|
|
91224
|
+
renameSync21(stagingPath, finalPath);
|
|
90781
91225
|
return finalPath;
|
|
90782
91226
|
});
|
|
90783
91227
|
}
|
|
@@ -90785,8 +91229,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90785
91229
|
const paths = overlayPathsFor(agent, opts);
|
|
90786
91230
|
return withAgentLock(paths, () => {
|
|
90787
91231
|
ensureSkillsDirs(paths);
|
|
90788
|
-
const stagingPath =
|
|
90789
|
-
const finalPath =
|
|
91232
|
+
const stagingPath = join87(paths.skillsStagingDir, `${slug}.yaml`);
|
|
91233
|
+
const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
|
|
90790
91234
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90791
91235
|
try {
|
|
90792
91236
|
writeSync10(fd, yamlText);
|
|
@@ -90794,14 +91238,14 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90794
91238
|
} finally {
|
|
90795
91239
|
closeSync14(fd);
|
|
90796
91240
|
}
|
|
90797
|
-
|
|
91241
|
+
renameSync21(stagingPath, finalPath);
|
|
90798
91242
|
return finalPath;
|
|
90799
91243
|
});
|
|
90800
91244
|
}
|
|
90801
91245
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90802
91246
|
const paths = overlayPathsFor(agent, opts);
|
|
90803
91247
|
return withAgentLock(paths, () => {
|
|
90804
|
-
const finalPath =
|
|
91248
|
+
const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
|
|
90805
91249
|
if (!existsSync88(finalPath))
|
|
90806
91250
|
return false;
|
|
90807
91251
|
unlinkSync18(finalPath);
|
|
@@ -90816,9 +91260,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90816
91260
|
for (const name of readdirSync31(paths.skillsDir)) {
|
|
90817
91261
|
if (!/\.ya?ml$/i.test(name))
|
|
90818
91262
|
continue;
|
|
90819
|
-
const full =
|
|
91263
|
+
const full = join87(paths.skillsDir, name);
|
|
90820
91264
|
try {
|
|
90821
|
-
const raw =
|
|
91265
|
+
const raw = readFileSync76(full, "utf-8");
|
|
90822
91266
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90823
91267
|
out.push({ slug, path: full, raw });
|
|
90824
91268
|
} catch {}
|
|
@@ -90828,7 +91272,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90828
91272
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90829
91273
|
const paths = overlayPathsFor(agent, opts);
|
|
90830
91274
|
return withAgentLock(paths, () => {
|
|
90831
|
-
const finalPath =
|
|
91275
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90832
91276
|
if (!existsSync88(finalPath))
|
|
90833
91277
|
return false;
|
|
90834
91278
|
unlinkSync18(finalPath);
|
|
@@ -90843,9 +91287,9 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
90843
91287
|
for (const name of readdirSync31(paths.scheduleDir)) {
|
|
90844
91288
|
if (!/\.ya?ml$/i.test(name))
|
|
90845
91289
|
continue;
|
|
90846
|
-
const full =
|
|
91290
|
+
const full = join87(paths.scheduleDir, name);
|
|
90847
91291
|
try {
|
|
90848
|
-
const raw =
|
|
91292
|
+
const raw = readFileSync76(full, "utf-8");
|
|
90849
91293
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90850
91294
|
out.push({ slug, path: full, raw });
|
|
90851
91295
|
} catch {}
|
|
@@ -91067,25 +91511,25 @@ import {
|
|
|
91067
91511
|
closeSync as closeSync15,
|
|
91068
91512
|
existsSync as existsSync89,
|
|
91069
91513
|
fsyncSync as fsyncSync9,
|
|
91070
|
-
mkdirSync as
|
|
91514
|
+
mkdirSync as mkdirSync51,
|
|
91071
91515
|
openSync as openSync15,
|
|
91072
91516
|
readdirSync as readdirSync32,
|
|
91073
|
-
readFileSync as
|
|
91074
|
-
renameSync as
|
|
91517
|
+
readFileSync as readFileSync77,
|
|
91518
|
+
renameSync as renameSync22,
|
|
91075
91519
|
unlinkSync as unlinkSync19,
|
|
91076
|
-
writeFileSync as
|
|
91520
|
+
writeFileSync as writeFileSync32,
|
|
91077
91521
|
writeSync as writeSync11
|
|
91078
91522
|
} from "node:fs";
|
|
91079
|
-
import { join as
|
|
91523
|
+
import { join as join88 } from "node:path";
|
|
91080
91524
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
91081
91525
|
var STAGE_ID_PREFIX = "cap_";
|
|
91082
91526
|
function pendingDir(agent, opts = {}) {
|
|
91083
91527
|
const paths = overlayPathsFor(agent, opts);
|
|
91084
|
-
return
|
|
91528
|
+
return join88(paths.scheduleDir, ".pending");
|
|
91085
91529
|
}
|
|
91086
91530
|
function ensurePendingDir(agent, opts = {}) {
|
|
91087
91531
|
const dir = pendingDir(agent, opts);
|
|
91088
|
-
|
|
91532
|
+
mkdirSync51(dir, { recursive: true });
|
|
91089
91533
|
return dir;
|
|
91090
91534
|
}
|
|
91091
91535
|
function newStageId() {
|
|
@@ -91094,8 +91538,8 @@ function newStageId() {
|
|
|
91094
91538
|
function stagePendingScheduleEntry(opts) {
|
|
91095
91539
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
91096
91540
|
const stageId = opts.stageId ?? newStageId();
|
|
91097
|
-
const yamlPath =
|
|
91098
|
-
const metaPath =
|
|
91541
|
+
const yamlPath = join88(dir, `${stageId}.yaml`);
|
|
91542
|
+
const metaPath = join88(dir, `${stageId}.meta.json`);
|
|
91099
91543
|
const meta = {
|
|
91100
91544
|
v: 1,
|
|
91101
91545
|
stage_id: stageId,
|
|
@@ -91114,9 +91558,9 @@ function stagePendingScheduleEntry(opts) {
|
|
|
91114
91558
|
} finally {
|
|
91115
91559
|
closeSync15(fd);
|
|
91116
91560
|
}
|
|
91117
|
-
|
|
91561
|
+
renameSync22(yamlTmp, yamlPath);
|
|
91118
91562
|
}
|
|
91119
|
-
|
|
91563
|
+
writeFileSync32(metaPath, JSON.stringify(meta, null, 2) + `
|
|
91120
91564
|
`, { mode: 384 });
|
|
91121
91565
|
return { stageId, yamlPath, metaPath };
|
|
91122
91566
|
}
|
|
@@ -91129,12 +91573,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
91129
91573
|
if (!name.endsWith(".meta.json"))
|
|
91130
91574
|
continue;
|
|
91131
91575
|
const stageId = name.slice(0, -".meta.json".length);
|
|
91132
|
-
const metaPath =
|
|
91133
|
-
const yamlPath =
|
|
91576
|
+
const metaPath = join88(dir, name);
|
|
91577
|
+
const yamlPath = join88(dir, `${stageId}.yaml`);
|
|
91134
91578
|
if (!existsSync89(yamlPath))
|
|
91135
91579
|
continue;
|
|
91136
91580
|
try {
|
|
91137
|
-
const meta = JSON.parse(
|
|
91581
|
+
const meta = JSON.parse(readFileSync77(metaPath, "utf-8"));
|
|
91138
91582
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
91139
91583
|
continue;
|
|
91140
91584
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -91149,11 +91593,11 @@ function commitPendingScheduleEntry(opts) {
|
|
|
91149
91593
|
return { committed: false, reason: "not_found" };
|
|
91150
91594
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
91151
91595
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
91152
|
-
const finalPath =
|
|
91596
|
+
const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
|
|
91153
91597
|
if (existsSync89(finalPath)) {
|
|
91154
91598
|
return { committed: false, reason: "slug_collision" };
|
|
91155
91599
|
}
|
|
91156
|
-
|
|
91600
|
+
renameSync22(match.yamlPath, finalPath);
|
|
91157
91601
|
unlinkSync19(match.metaPath);
|
|
91158
91602
|
return { committed: true, path: finalPath, slug };
|
|
91159
91603
|
}
|
|
@@ -91172,7 +91616,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
91172
91616
|
}
|
|
91173
91617
|
|
|
91174
91618
|
// src/cli/agent-config-write.ts
|
|
91175
|
-
import { existsSync as existsSync90, readFileSync as
|
|
91619
|
+
import { existsSync as existsSync90, readFileSync as readFileSync78 } from "node:fs";
|
|
91176
91620
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
91177
91621
|
|
|
91178
91622
|
// src/scheduler/schedule-report.ts
|
|
@@ -91563,7 +92007,7 @@ function scheduleRemove(opts) {
|
|
|
91563
92007
|
let priorContent = null;
|
|
91564
92008
|
try {
|
|
91565
92009
|
if (existsSync90(match.path))
|
|
91566
|
-
priorContent =
|
|
92010
|
+
priorContent = readFileSync78(match.path, "utf-8");
|
|
91567
92011
|
} catch {}
|
|
91568
92012
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
91569
92013
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -91766,7 +92210,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91766
92210
|
}
|
|
91767
92211
|
let blob;
|
|
91768
92212
|
if (opts.jsonl) {
|
|
91769
|
-
blob = existsSync90(opts.jsonl) ?
|
|
92213
|
+
blob = existsSync90(opts.jsonl) ? readFileSync78(opts.jsonl, "utf-8") : "";
|
|
91770
92214
|
} else {
|
|
91771
92215
|
try {
|
|
91772
92216
|
blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91802,7 +92246,7 @@ import { existsSync as existsSync91 } from "node:fs";
|
|
|
91802
92246
|
init_reconcile_default_skills();
|
|
91803
92247
|
init_agent_config();
|
|
91804
92248
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91805
|
-
import { join as
|
|
92249
|
+
import { join as join89 } from "node:path";
|
|
91806
92250
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91807
92251
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91808
92252
|
function exitCodeFor2(code) {
|
|
@@ -91877,7 +92321,7 @@ function skillInstall(opts) {
|
|
|
91877
92321
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91878
92322
|
}
|
|
91879
92323
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91880
|
-
const skillPath =
|
|
92324
|
+
const skillPath = join89(poolDir, skillName);
|
|
91881
92325
|
if (!existsSync91(skillPath)) {
|
|
91882
92326
|
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.`);
|
|
91883
92327
|
}
|
|
@@ -92044,19 +92488,19 @@ import {
|
|
|
92044
92488
|
closeSync as closeSync16,
|
|
92045
92489
|
existsSync as existsSync92,
|
|
92046
92490
|
lstatSync as lstatSync11,
|
|
92047
|
-
mkdirSync as
|
|
92491
|
+
mkdirSync as mkdirSync52,
|
|
92048
92492
|
mkdtempSync as mkdtempSync5,
|
|
92049
92493
|
openSync as openSync16,
|
|
92050
|
-
readFileSync as
|
|
92494
|
+
readFileSync as readFileSync79,
|
|
92051
92495
|
readdirSync as readdirSync33,
|
|
92052
92496
|
realpathSync as realpathSync7,
|
|
92053
|
-
renameSync as
|
|
92497
|
+
renameSync as renameSync23,
|
|
92054
92498
|
rmSync as rmSync18,
|
|
92055
92499
|
statSync as statSync49,
|
|
92056
|
-
writeFileSync as
|
|
92500
|
+
writeFileSync as writeFileSync33
|
|
92057
92501
|
} from "node:fs";
|
|
92058
|
-
import { tmpdir as
|
|
92059
|
-
import { dirname as dirname31, join as
|
|
92502
|
+
import { tmpdir as tmpdir6, homedir as homedir51 } from "node:os";
|
|
92503
|
+
import { dirname as dirname31, join as join90, relative as relative2, resolve as resolve54 } from "node:path";
|
|
92060
92504
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92061
92505
|
|
|
92062
92506
|
// src/cli/skill-common.ts
|
|
@@ -92290,7 +92734,7 @@ function scanForClaudeP2(content) {
|
|
|
92290
92734
|
function resolveSkillsPoolDir2(override) {
|
|
92291
92735
|
const raw = override ?? "~/.switchroom/skills";
|
|
92292
92736
|
if (raw.startsWith("~/")) {
|
|
92293
|
-
return
|
|
92737
|
+
return join90(homedir51(), raw.slice(2));
|
|
92294
92738
|
}
|
|
92295
92739
|
if (raw === "~")
|
|
92296
92740
|
return homedir51();
|
|
@@ -92329,7 +92773,7 @@ function loadFromDir(dir) {
|
|
|
92329
92773
|
const walk2 = (sub) => {
|
|
92330
92774
|
const entries = readdirSync33(sub, { withFileTypes: true });
|
|
92331
92775
|
for (const ent of entries) {
|
|
92332
|
-
const full =
|
|
92776
|
+
const full = join90(sub, ent.name);
|
|
92333
92777
|
const rel = relative2(abs, full);
|
|
92334
92778
|
if (ent.isSymbolicLink()) {
|
|
92335
92779
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -92339,7 +92783,7 @@ function loadFromDir(dir) {
|
|
|
92339
92783
|
continue;
|
|
92340
92784
|
}
|
|
92341
92785
|
if (ent.isFile()) {
|
|
92342
|
-
const buf =
|
|
92786
|
+
const buf = readFileSync79(full);
|
|
92343
92787
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
92344
92788
|
}
|
|
92345
92789
|
}
|
|
@@ -92364,7 +92808,7 @@ function loadFromTarball(tarPath) {
|
|
|
92364
92808
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
92365
92809
|
}
|
|
92366
92810
|
}
|
|
92367
|
-
const staging = mkdtempSync5(
|
|
92811
|
+
const staging = mkdtempSync5(join90(tmpdir6(), "skill-apply-extract-"));
|
|
92368
92812
|
try {
|
|
92369
92813
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92370
92814
|
const r = spawnSync16("tar", [
|
|
@@ -92388,7 +92832,7 @@ function loadFromTarball(tarPath) {
|
|
|
92388
92832
|
}
|
|
92389
92833
|
}
|
|
92390
92834
|
function loadSingleFile(filePath) {
|
|
92391
|
-
const content =
|
|
92835
|
+
const content = readFileSync79(filePath, "utf-8");
|
|
92392
92836
|
return { "SKILL.md": content };
|
|
92393
92837
|
}
|
|
92394
92838
|
function loadFromStdin() {
|
|
@@ -92450,10 +92894,10 @@ function validatePayload(name, files) {
|
|
|
92450
92894
|
errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
92451
92895
|
}
|
|
92452
92896
|
} else if (PY_SCRIPT_RE2.test(path9)) {
|
|
92453
|
-
const tmp = mkdtempSync5(
|
|
92454
|
-
const tmpPy =
|
|
92897
|
+
const tmp = mkdtempSync5(join90(tmpdir6(), "skill-apply-py-"));
|
|
92898
|
+
const tmpPy = join90(tmp, "check.py");
|
|
92455
92899
|
try {
|
|
92456
|
-
|
|
92900
|
+
writeFileSync33(tmpPy, content);
|
|
92457
92901
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
92458
92902
|
encoding: "utf-8"
|
|
92459
92903
|
});
|
|
@@ -92474,12 +92918,12 @@ function diffSummary(currentDir, files) {
|
|
|
92474
92918
|
if (existsSync92(currentDir)) {
|
|
92475
92919
|
const walk2 = (sub) => {
|
|
92476
92920
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
92477
|
-
const full =
|
|
92921
|
+
const full = join90(sub, ent.name);
|
|
92478
92922
|
const rel = relative2(currentDir, full);
|
|
92479
92923
|
if (ent.isDirectory()) {
|
|
92480
92924
|
walk2(full);
|
|
92481
92925
|
} else if (ent.isFile()) {
|
|
92482
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
92926
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync79(full, "utf-8");
|
|
92483
92927
|
}
|
|
92484
92928
|
}
|
|
92485
92929
|
};
|
|
@@ -92508,9 +92952,9 @@ function diffSummary(currentDir, files) {
|
|
|
92508
92952
|
}
|
|
92509
92953
|
function writePayload(poolDir, name, files) {
|
|
92510
92954
|
if (!existsSync92(poolDir)) {
|
|
92511
|
-
|
|
92955
|
+
mkdirSync52(poolDir, { recursive: true, mode: 493 });
|
|
92512
92956
|
}
|
|
92513
|
-
const target =
|
|
92957
|
+
const target = join90(poolDir, name);
|
|
92514
92958
|
let targetIsSymlink = false;
|
|
92515
92959
|
try {
|
|
92516
92960
|
const st = lstatSync11(target);
|
|
@@ -92521,15 +92965,15 @@ function writePayload(poolDir, name, files) {
|
|
|
92521
92965
|
if (targetIsSymlink) {
|
|
92522
92966
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
92523
92967
|
}
|
|
92524
|
-
const staging = mkdtempSync5(
|
|
92968
|
+
const staging = mkdtempSync5(join90(poolDir, `.skill-apply-stage-${name}-`));
|
|
92525
92969
|
let oldRename = null;
|
|
92526
92970
|
try {
|
|
92527
92971
|
for (const [path9, content] of Object.entries(files)) {
|
|
92528
|
-
const full =
|
|
92529
|
-
|
|
92972
|
+
const full = join90(staging, path9);
|
|
92973
|
+
mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
|
|
92530
92974
|
const fd = openSync16(full, "wx");
|
|
92531
92975
|
try {
|
|
92532
|
-
|
|
92976
|
+
writeFileSync33(fd, content);
|
|
92533
92977
|
} finally {
|
|
92534
92978
|
closeSync16(fd);
|
|
92535
92979
|
}
|
|
@@ -92545,9 +92989,9 @@ function writePayload(poolDir, name, files) {
|
|
|
92545
92989
|
} catch {}
|
|
92546
92990
|
if (targetExists) {
|
|
92547
92991
|
oldRename = `${target}.skill-apply-old-${Date.now()}`;
|
|
92548
|
-
|
|
92992
|
+
renameSync23(target, oldRename);
|
|
92549
92993
|
}
|
|
92550
|
-
|
|
92994
|
+
renameSync23(staging, target);
|
|
92551
92995
|
if (oldRename) {
|
|
92552
92996
|
rmSync18(oldRename, { recursive: true, force: true });
|
|
92553
92997
|
oldRename = null;
|
|
@@ -92561,7 +93005,7 @@ function writePayload(poolDir, name, files) {
|
|
|
92561
93005
|
if (existsSync92(target)) {
|
|
92562
93006
|
rmSync18(target, { recursive: true, force: true });
|
|
92563
93007
|
}
|
|
92564
|
-
|
|
93008
|
+
renameSync23(oldRename, target);
|
|
92565
93009
|
} catch {}
|
|
92566
93010
|
}
|
|
92567
93011
|
throw err2;
|
|
@@ -92603,7 +93047,7 @@ function registerSkillCommand(program3) {
|
|
|
92603
93047
|
}
|
|
92604
93048
|
const config = loadConfig();
|
|
92605
93049
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
92606
|
-
const currentDir =
|
|
93050
|
+
const currentDir = join90(poolDir, name);
|
|
92607
93051
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
92608
93052
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
92609
93053
|
console.log(diffSummary(currentDir, files));
|
|
@@ -92637,19 +93081,19 @@ import {
|
|
|
92637
93081
|
closeSync as closeSync17,
|
|
92638
93082
|
existsSync as existsSync93,
|
|
92639
93083
|
lstatSync as lstatSync12,
|
|
92640
|
-
mkdirSync as
|
|
93084
|
+
mkdirSync as mkdirSync53,
|
|
92641
93085
|
mkdtempSync as mkdtempSync6,
|
|
92642
93086
|
openSync as openSync17,
|
|
92643
|
-
readFileSync as
|
|
93087
|
+
readFileSync as readFileSync80,
|
|
92644
93088
|
readdirSync as readdirSync34,
|
|
92645
|
-
renameSync as
|
|
93089
|
+
renameSync as renameSync24,
|
|
92646
93090
|
rmSync as rmSync19,
|
|
92647
93091
|
statSync as statSync50,
|
|
92648
93092
|
utimesSync,
|
|
92649
|
-
writeFileSync as
|
|
93093
|
+
writeFileSync as writeFileSync34
|
|
92650
93094
|
} from "node:fs";
|
|
92651
|
-
import { dirname as dirname32, join as
|
|
92652
|
-
import { homedir as homedir52, tmpdir as
|
|
93095
|
+
import { dirname as dirname32, join as join91, relative as relative3, resolve as resolve55 } from "node:path";
|
|
93096
|
+
import { homedir as homedir52, tmpdir as tmpdir7 } from "node:os";
|
|
92653
93097
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
92654
93098
|
init_helpers();
|
|
92655
93099
|
init_agent_config();
|
|
@@ -92660,10 +93104,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
92660
93104
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
92661
93105
|
function resolveConfigSkillsDir(agent) {
|
|
92662
93106
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
92663
|
-
const candidate = override ? resolve55(override) :
|
|
93107
|
+
const candidate = override ? resolve55(override) : join91(homedir52(), ".switchroom-config");
|
|
92664
93108
|
if (!existsSync93(candidate))
|
|
92665
93109
|
return null;
|
|
92666
|
-
return
|
|
93110
|
+
return join91(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
92667
93111
|
}
|
|
92668
93112
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
92669
93113
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -92681,7 +93125,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
92681
93125
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
92682
93126
|
continue;
|
|
92683
93127
|
try {
|
|
92684
|
-
rmSync19(
|
|
93128
|
+
rmSync19(join91(configSkillsRoot, ent), { recursive: true, force: true });
|
|
92685
93129
|
} catch {}
|
|
92686
93130
|
}
|
|
92687
93131
|
} catch {}
|
|
@@ -92690,7 +93134,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92690
93134
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
92691
93135
|
if (!configSkillsRoot)
|
|
92692
93136
|
return;
|
|
92693
|
-
const dest =
|
|
93137
|
+
const dest = join91(configSkillsRoot, name);
|
|
92694
93138
|
try {
|
|
92695
93139
|
if (liveSkillDir !== null) {
|
|
92696
93140
|
try {
|
|
@@ -92705,34 +93149,34 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92705
93149
|
if (liveSkillDir === null) {
|
|
92706
93150
|
sweepMirrorPriors(configSkillsRoot);
|
|
92707
93151
|
if (existsSync93(dest)) {
|
|
92708
|
-
const trash =
|
|
92709
|
-
|
|
93152
|
+
const trash = join91(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
93153
|
+
renameSync24(dest, trash);
|
|
92710
93154
|
}
|
|
92711
93155
|
return;
|
|
92712
93156
|
}
|
|
92713
|
-
|
|
93157
|
+
mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
|
|
92714
93158
|
sweepMirrorPriors(configSkillsRoot);
|
|
92715
|
-
const staging = mkdtempSync6(
|
|
93159
|
+
const staging = mkdtempSync6(join91(configSkillsRoot, `.${name}-staging-`));
|
|
92716
93160
|
const walk2 = (src, dst) => {
|
|
92717
|
-
|
|
93161
|
+
mkdirSync53(dst, { recursive: true, mode: 493 });
|
|
92718
93162
|
for (const ent of readdirSync34(src, { withFileTypes: true })) {
|
|
92719
|
-
const s =
|
|
92720
|
-
const d =
|
|
93163
|
+
const s = join91(src, ent.name);
|
|
93164
|
+
const d = join91(dst, ent.name);
|
|
92721
93165
|
if (ent.isSymbolicLink())
|
|
92722
93166
|
continue;
|
|
92723
93167
|
if (ent.isDirectory())
|
|
92724
93168
|
walk2(s, d);
|
|
92725
93169
|
else if (ent.isFile()) {
|
|
92726
|
-
|
|
93170
|
+
writeFileSync34(d, readFileSync80(s));
|
|
92727
93171
|
}
|
|
92728
93172
|
}
|
|
92729
93173
|
};
|
|
92730
93174
|
walk2(liveSkillDir, staging);
|
|
92731
93175
|
if (existsSync93(dest)) {
|
|
92732
|
-
const prior =
|
|
92733
|
-
|
|
93176
|
+
const prior = join91(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
93177
|
+
renameSync24(dest, prior);
|
|
92734
93178
|
}
|
|
92735
|
-
|
|
93179
|
+
renameSync24(staging, dest);
|
|
92736
93180
|
} catch (err2) {
|
|
92737
93181
|
process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
|
|
92738
93182
|
`));
|
|
@@ -92759,16 +93203,16 @@ function resolveAgent(opts) {
|
|
|
92759
93203
|
function resolveAgentsRoot(opts) {
|
|
92760
93204
|
if (opts.root)
|
|
92761
93205
|
return resolve55(opts.root);
|
|
92762
|
-
return
|
|
93206
|
+
return join91(homedir52(), ".switchroom", "agents");
|
|
92763
93207
|
}
|
|
92764
93208
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
92765
|
-
return
|
|
93209
|
+
return join91(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
92766
93210
|
}
|
|
92767
93211
|
function trashDir(agentsRoot, agent) {
|
|
92768
|
-
return
|
|
93212
|
+
return join91(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
92769
93213
|
}
|
|
92770
93214
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92771
|
-
const skillsDir =
|
|
93215
|
+
const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
|
|
92772
93216
|
if (!existsSync93(skillsDir))
|
|
92773
93217
|
return 0;
|
|
92774
93218
|
let n = 0;
|
|
@@ -92806,7 +93250,7 @@ function loadFromDir2(dir) {
|
|
|
92806
93250
|
const files = {};
|
|
92807
93251
|
const walk2 = (sub) => {
|
|
92808
93252
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92809
|
-
const full =
|
|
93253
|
+
const full = join91(sub, ent.name);
|
|
92810
93254
|
if (ent.isSymbolicLink()) {
|
|
92811
93255
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92812
93256
|
}
|
|
@@ -92816,7 +93260,7 @@ function loadFromDir2(dir) {
|
|
|
92816
93260
|
}
|
|
92817
93261
|
if (ent.isFile()) {
|
|
92818
93262
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
92819
|
-
files[rel] =
|
|
93263
|
+
files[rel] = readFileSync80(full, "utf-8");
|
|
92820
93264
|
}
|
|
92821
93265
|
}
|
|
92822
93266
|
};
|
|
@@ -92859,10 +93303,10 @@ function behavioralValidate(files) {
|
|
|
92859
93303
|
errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92860
93304
|
}
|
|
92861
93305
|
} else if (PY_SCRIPT_RE.test(path9)) {
|
|
92862
|
-
const tmp = mkdtempSync6(
|
|
92863
|
-
const tmpPy =
|
|
93306
|
+
const tmp = mkdtempSync6(join91(tmpdir7(), "skill-personal-py-"));
|
|
93307
|
+
const tmpPy = join91(tmp, "check.py");
|
|
92864
93308
|
try {
|
|
92865
|
-
|
|
93309
|
+
writeFileSync34(tmpPy, content);
|
|
92866
93310
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
92867
93311
|
encoding: "utf-8"
|
|
92868
93312
|
});
|
|
@@ -92884,7 +93328,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
92884
93328
|
for (const ent of readdirSync34(trash, { withFileTypes: true })) {
|
|
92885
93329
|
if (!ent.isDirectory())
|
|
92886
93330
|
continue;
|
|
92887
|
-
const entPath =
|
|
93331
|
+
const entPath = join91(trash, ent.name);
|
|
92888
93332
|
try {
|
|
92889
93333
|
const st = statSync50(entPath);
|
|
92890
93334
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -92904,16 +93348,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92904
93348
|
if (targetIsSymlink) {
|
|
92905
93349
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92906
93350
|
}
|
|
92907
|
-
|
|
92908
|
-
const staging = mkdtempSync6(
|
|
93351
|
+
mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
|
|
93352
|
+
const staging = mkdtempSync6(join91(dirname32(targetDir), `.skill-personal-stage-`));
|
|
92909
93353
|
let oldRename = null;
|
|
92910
93354
|
try {
|
|
92911
93355
|
for (const [path9, content] of Object.entries(files)) {
|
|
92912
|
-
const full =
|
|
92913
|
-
|
|
93356
|
+
const full = join91(staging, path9);
|
|
93357
|
+
mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
|
|
92914
93358
|
const fd = openSync17(full, "wx");
|
|
92915
93359
|
try {
|
|
92916
|
-
|
|
93360
|
+
writeFileSync34(fd, content);
|
|
92917
93361
|
} finally {
|
|
92918
93362
|
closeSync17(fd);
|
|
92919
93363
|
}
|
|
@@ -92929,9 +93373,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92929
93373
|
} catch {}
|
|
92930
93374
|
if (targetExists) {
|
|
92931
93375
|
oldRename = `${targetDir}.personal-old-${Date.now()}`;
|
|
92932
|
-
|
|
93376
|
+
renameSync24(targetDir, oldRename);
|
|
92933
93377
|
}
|
|
92934
|
-
|
|
93378
|
+
renameSync24(staging, targetDir);
|
|
92935
93379
|
if (oldRename) {
|
|
92936
93380
|
rmSync19(oldRename, { recursive: true, force: true });
|
|
92937
93381
|
oldRename = null;
|
|
@@ -92945,7 +93389,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92945
93389
|
if (existsSync93(targetDir)) {
|
|
92946
93390
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92947
93391
|
}
|
|
92948
|
-
|
|
93392
|
+
renameSync24(oldRename, targetDir);
|
|
92949
93393
|
} catch {}
|
|
92950
93394
|
}
|
|
92951
93395
|
throw err2;
|
|
@@ -93016,7 +93460,7 @@ function loadFiles(opts) {
|
|
|
93016
93460
|
return loadFromDir2(p);
|
|
93017
93461
|
}
|
|
93018
93462
|
if (p.endsWith(".md")) {
|
|
93019
|
-
return { "SKILL.md":
|
|
93463
|
+
return { "SKILL.md": readFileSync80(p, "utf-8") };
|
|
93020
93464
|
}
|
|
93021
93465
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
93022
93466
|
}
|
|
@@ -93056,10 +93500,10 @@ function editPersonalAction(name, opts) {
|
|
|
93056
93500
|
}
|
|
93057
93501
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
93058
93502
|
function defaultSharedRoot() {
|
|
93059
|
-
return
|
|
93503
|
+
return join91(homedir52(), ".switchroom", "skills");
|
|
93060
93504
|
}
|
|
93061
93505
|
function defaultBundledRoot() {
|
|
93062
|
-
return
|
|
93506
|
+
return join91(homedir52(), ".switchroom", "skills", "_bundled");
|
|
93063
93507
|
}
|
|
93064
93508
|
function resolveCloneSource(source, opts) {
|
|
93065
93509
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -93069,7 +93513,7 @@ function resolveCloneSource(source, opts) {
|
|
|
93069
93513
|
const tier = m[1];
|
|
93070
93514
|
const slug = m[2];
|
|
93071
93515
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
93072
|
-
const dir =
|
|
93516
|
+
const dir = join91(root, slug);
|
|
93073
93517
|
if (!existsSync93(dir)) {
|
|
93074
93518
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
93075
93519
|
}
|
|
@@ -93085,7 +93529,7 @@ function readSourceFiles(dir) {
|
|
|
93085
93529
|
const skipped = [];
|
|
93086
93530
|
const walk2 = (sub) => {
|
|
93087
93531
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
93088
|
-
const full =
|
|
93532
|
+
const full = join91(sub, ent.name);
|
|
93089
93533
|
if (ent.isSymbolicLink()) {
|
|
93090
93534
|
continue;
|
|
93091
93535
|
}
|
|
@@ -93105,7 +93549,7 @@ function readSourceFiles(dir) {
|
|
|
93105
93549
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
93106
93550
|
}
|
|
93107
93551
|
} catch {}
|
|
93108
|
-
files[rel] =
|
|
93552
|
+
files[rel] = readFileSync80(full, "utf-8");
|
|
93109
93553
|
}
|
|
93110
93554
|
}
|
|
93111
93555
|
};
|
|
@@ -93194,10 +93638,10 @@ function removePersonalAction(name, opts) {
|
|
|
93194
93638
|
throw err2;
|
|
93195
93639
|
}
|
|
93196
93640
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
93197
|
-
|
|
93641
|
+
mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
|
|
93198
93642
|
const ts = Date.now();
|
|
93199
|
-
const trashTarget =
|
|
93200
|
-
|
|
93643
|
+
const trashTarget = join91(trashRoot2, `${name}-${ts}`);
|
|
93644
|
+
renameSync24(target, trashTarget);
|
|
93201
93645
|
const now = new Date(ts);
|
|
93202
93646
|
utimesSync(trashTarget, now, now);
|
|
93203
93647
|
mirrorToConfigRepo(agent, name, null);
|
|
@@ -93215,7 +93659,7 @@ function listPersonalAction(opts) {
|
|
|
93215
93659
|
const agent = resolveAgent(opts);
|
|
93216
93660
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
93217
93661
|
sweepTrash(agentsRoot, agent);
|
|
93218
|
-
const skillsDir =
|
|
93662
|
+
const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
|
|
93219
93663
|
const personal = [];
|
|
93220
93664
|
if (existsSync93(skillsDir)) {
|
|
93221
93665
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
@@ -93224,7 +93668,7 @@ function listPersonalAction(opts) {
|
|
|
93224
93668
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
93225
93669
|
continue;
|
|
93226
93670
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
93227
|
-
const skillPath =
|
|
93671
|
+
const skillPath = join91(skillsDir, ent.name);
|
|
93228
93672
|
let fileCount = 0;
|
|
93229
93673
|
let totalBytes = 0;
|
|
93230
93674
|
const walk2 = (sub) => {
|
|
@@ -93232,10 +93676,10 @@ function listPersonalAction(opts) {
|
|
|
93232
93676
|
if (e.isFile()) {
|
|
93233
93677
|
fileCount += 1;
|
|
93234
93678
|
try {
|
|
93235
|
-
totalBytes += statSync50(
|
|
93679
|
+
totalBytes += statSync50(join91(sub, e.name)).size;
|
|
93236
93680
|
} catch {}
|
|
93237
93681
|
} else if (e.isDirectory()) {
|
|
93238
|
-
walk2(
|
|
93682
|
+
walk2(join91(sub, e.name));
|
|
93239
93683
|
}
|
|
93240
93684
|
}
|
|
93241
93685
|
};
|
|
@@ -93274,11 +93718,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
93274
93718
|
// src/cli/self-improve-propose-skill.ts
|
|
93275
93719
|
import { createConnection as createConnection4 } from "node:net";
|
|
93276
93720
|
import { homedir as homedir53 } from "node:os";
|
|
93277
|
-
import { join as
|
|
93278
|
-
import { readFileSync as
|
|
93721
|
+
import { join as join92 } from "node:path";
|
|
93722
|
+
import { readFileSync as readFileSync81 } from "node:fs";
|
|
93279
93723
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
93280
93724
|
function gatewaySocketPath() {
|
|
93281
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ?
|
|
93725
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join92(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join92(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
93282
93726
|
}
|
|
93283
93727
|
function fail5(msg, code = 1) {
|
|
93284
93728
|
console.error(msg);
|
|
@@ -93313,7 +93757,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93313
93757
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
93314
93758
|
let draft;
|
|
93315
93759
|
try {
|
|
93316
|
-
draft = JSON.parse(
|
|
93760
|
+
draft = JSON.parse(readFileSync81(opts.draft, "utf-8"));
|
|
93317
93761
|
} catch (e) {
|
|
93318
93762
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
93319
93763
|
}
|
|
@@ -93350,9 +93794,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93350
93794
|
init_esm();
|
|
93351
93795
|
init_helpers();
|
|
93352
93796
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
93353
|
-
import { existsSync as existsSync94, readdirSync as readdirSync35, readFileSync as
|
|
93797
|
+
import { existsSync as existsSync94, readdirSync as readdirSync35, readFileSync as readFileSync82, statSync as statSync51 } from "node:fs";
|
|
93354
93798
|
import { homedir as homedir54 } from "node:os";
|
|
93355
|
-
import { join as
|
|
93799
|
+
import { join as join93, resolve as resolve56 } from "node:path";
|
|
93356
93800
|
var PERSONAL_PREFIX2 = "personal-";
|
|
93357
93801
|
var BUNDLED_SUBDIR = "_bundled";
|
|
93358
93802
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -93366,12 +93810,12 @@ function defaultBundledRoot2() {
|
|
|
93366
93810
|
return resolve56(homedir54(), ".switchroom/skills/_bundled");
|
|
93367
93811
|
}
|
|
93368
93812
|
function readSkillFrontmatter(skillDir) {
|
|
93369
|
-
const mdPath =
|
|
93813
|
+
const mdPath = join93(skillDir, "SKILL.md");
|
|
93370
93814
|
if (!existsSync94(mdPath))
|
|
93371
93815
|
return null;
|
|
93372
93816
|
let content;
|
|
93373
93817
|
try {
|
|
93374
|
-
content =
|
|
93818
|
+
content = readFileSync82(mdPath, "utf-8");
|
|
93375
93819
|
} catch {
|
|
93376
93820
|
return null;
|
|
93377
93821
|
}
|
|
@@ -93399,7 +93843,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
93399
93843
|
return { fm: parsed };
|
|
93400
93844
|
}
|
|
93401
93845
|
function statSkillMd(skillDir) {
|
|
93402
|
-
const mdPath =
|
|
93846
|
+
const mdPath = join93(skillDir, "SKILL.md");
|
|
93403
93847
|
try {
|
|
93404
93848
|
const st = statSync51(mdPath);
|
|
93405
93849
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -93410,7 +93854,7 @@ function statSkillMd(skillDir) {
|
|
|
93410
93854
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
93411
93855
|
if (!AGENT_NAME_RE3.test(agent))
|
|
93412
93856
|
return [];
|
|
93413
|
-
const skillsDir =
|
|
93857
|
+
const skillsDir = join93(agentsRoot, agent, ".claude/skills");
|
|
93414
93858
|
if (!existsSync94(skillsDir))
|
|
93415
93859
|
return [];
|
|
93416
93860
|
const out = [];
|
|
@@ -93423,7 +93867,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93423
93867
|
for (const ent of entries) {
|
|
93424
93868
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
93425
93869
|
continue;
|
|
93426
|
-
const dirPath =
|
|
93870
|
+
const dirPath = join93(skillsDir, ent);
|
|
93427
93871
|
try {
|
|
93428
93872
|
if (!statSync51(dirPath).isDirectory())
|
|
93429
93873
|
continue;
|
|
@@ -93463,7 +93907,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93463
93907
|
continue;
|
|
93464
93908
|
if (ent.startsWith("."))
|
|
93465
93909
|
continue;
|
|
93466
|
-
const dirPath =
|
|
93910
|
+
const dirPath = join93(sharedRoot, ent);
|
|
93467
93911
|
try {
|
|
93468
93912
|
if (!statSync51(dirPath).isDirectory())
|
|
93469
93913
|
continue;
|
|
@@ -93499,7 +93943,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
93499
93943
|
for (const ent of entries) {
|
|
93500
93944
|
if (ent.startsWith("."))
|
|
93501
93945
|
continue;
|
|
93502
|
-
const dirPath =
|
|
93946
|
+
const dirPath = join93(bundledRoot, ent);
|
|
93503
93947
|
try {
|
|
93504
93948
|
if (!statSync51(dirPath).isDirectory())
|
|
93505
93949
|
continue;
|
|
@@ -93646,17 +94090,17 @@ init_helpers();
|
|
|
93646
94090
|
init_operator_uid();
|
|
93647
94091
|
import {
|
|
93648
94092
|
existsSync as existsSync96,
|
|
93649
|
-
mkdirSync as
|
|
94093
|
+
mkdirSync as mkdirSync54,
|
|
93650
94094
|
readdirSync as readdirSync36,
|
|
93651
|
-
readFileSync as
|
|
93652
|
-
writeFileSync as
|
|
94095
|
+
readFileSync as readFileSync84,
|
|
94096
|
+
writeFileSync as writeFileSync35,
|
|
93653
94097
|
statSync as statSync52,
|
|
93654
94098
|
lstatSync as lstatSync13,
|
|
93655
94099
|
realpathSync as realpathSync8,
|
|
93656
94100
|
copyFileSync as copyFileSync13
|
|
93657
94101
|
} from "node:fs";
|
|
93658
94102
|
import { homedir as homedir55 } from "node:os";
|
|
93659
|
-
import { join as
|
|
94103
|
+
import { join as join94 } from "node:path";
|
|
93660
94104
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
93661
94105
|
|
|
93662
94106
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -94005,7 +94449,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
94005
94449
|
return resolved;
|
|
94006
94450
|
}
|
|
94007
94451
|
function resolveHostdSkillsTarget(hostHome) {
|
|
94008
|
-
const skillsPath =
|
|
94452
|
+
const skillsPath = join94(hostHome, ".switchroom", "skills");
|
|
94009
94453
|
let st;
|
|
94010
94454
|
try {
|
|
94011
94455
|
st = lstatSync13(skillsPath);
|
|
@@ -94029,10 +94473,10 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
94029
94473
|
return target;
|
|
94030
94474
|
}
|
|
94031
94475
|
function hostdDir() {
|
|
94032
|
-
return
|
|
94476
|
+
return join94(homedir55(), ".switchroom", "hostd");
|
|
94033
94477
|
}
|
|
94034
94478
|
function hostdComposePath() {
|
|
94035
|
-
return
|
|
94479
|
+
return join94(hostdDir(), "docker-compose.yml");
|
|
94036
94480
|
}
|
|
94037
94481
|
function backupExistingCompose() {
|
|
94038
94482
|
const p = hostdComposePath();
|
|
@@ -94069,7 +94513,7 @@ async function doInstall(opts, program3) {
|
|
|
94069
94513
|
}
|
|
94070
94514
|
const dir = hostdDir();
|
|
94071
94515
|
const composePath = hostdComposePath();
|
|
94072
|
-
|
|
94516
|
+
mkdirSync54(dir, { recursive: true });
|
|
94073
94517
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
94074
94518
|
const guard = checkDowngrade({
|
|
94075
94519
|
container: "switchroom-hostd",
|
|
@@ -94102,7 +94546,7 @@ async function doInstall(opts, program3) {
|
|
|
94102
94546
|
const bak = backupExistingCompose();
|
|
94103
94547
|
if (bak)
|
|
94104
94548
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94105
|
-
|
|
94549
|
+
writeFileSync35(composePath, yaml, "utf8");
|
|
94106
94550
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94107
94551
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
94108
94552
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -94161,7 +94605,7 @@ function doStatus() {
|
|
|
94161
94605
|
for (const name of readdirSync36(dir)) {
|
|
94162
94606
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
94163
94607
|
continue;
|
|
94164
|
-
const sockPath =
|
|
94608
|
+
const sockPath = join94(dir, name, "sock");
|
|
94165
94609
|
if (existsSync96(sockPath)) {
|
|
94166
94610
|
const st = statSync52(sockPath);
|
|
94167
94611
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -94210,7 +94654,7 @@ function registerHostdCommand(program3) {
|
|
|
94210
94654
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
94211
94655
|
return;
|
|
94212
94656
|
}
|
|
94213
|
-
const raw =
|
|
94657
|
+
const raw = readFileSync84(logPath, "utf-8");
|
|
94214
94658
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
94215
94659
|
const filters = {
|
|
94216
94660
|
agent: opts.agent,
|
|
@@ -94253,9 +94697,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
94253
94697
|
init_source();
|
|
94254
94698
|
init_helpers();
|
|
94255
94699
|
init_operator_uid();
|
|
94256
|
-
import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as
|
|
94700
|
+
import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync55, writeFileSync as writeFileSync36, copyFileSync as copyFileSync14 } from "node:fs";
|
|
94257
94701
|
import { homedir as homedir56 } from "node:os";
|
|
94258
|
-
import { join as
|
|
94702
|
+
import { join as join95 } from "node:path";
|
|
94259
94703
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
94260
94704
|
function resolveWebImageTag(explicitTag, release) {
|
|
94261
94705
|
if (explicitTag)
|
|
@@ -94274,7 +94718,7 @@ function resolveWebHostHome(env2 = process.env, home2 = homedir56()) {
|
|
|
94274
94718
|
}
|
|
94275
94719
|
var WEB_COMPOSE_PROJECT = "switchroom-web";
|
|
94276
94720
|
function renderWebComposeFile(opts) {
|
|
94277
|
-
const { hostHome, imageTag, operatorUid } = opts;
|
|
94721
|
+
const { hostHome, imageTag, operatorUid, port } = opts;
|
|
94278
94722
|
return `# AUTO-GENERATED by \`switchroom webd install\` \u2014 do not hand-edit.
|
|
94279
94723
|
# Edits land at \`switchroom webd install\` time; backed up to
|
|
94280
94724
|
# docker-compose.yml.bak-<ts> on overwrite.
|
|
@@ -94289,13 +94733,18 @@ services:
|
|
|
94289
94733
|
image: ghcr.io/switchroom/switchroom-web:${imageTag}
|
|
94290
94734
|
container_name: switchroom-web
|
|
94291
94735
|
restart: always
|
|
94292
|
-
# The server MUST own host loopback 127.0.0.1
|
|
94736
|
+
# The server MUST own host loopback 127.0.0.1:${port} \u2014 the cloudflared
|
|
94293
94737
|
# tunnel (GitHub webhooks) and \`tailscale serve\` (dashboard) both
|
|
94294
94738
|
# reach it there, and the dashboard CSRF gate trusts the
|
|
94295
94739
|
# Tailscale-User-Login header only when the request arrives on
|
|
94296
94740
|
# loopback (PR #1380). Host networking makes the container's
|
|
94297
94741
|
# 127.0.0.1 the host loopback, so both keep working unchanged.
|
|
94298
94742
|
network_mode: host
|
|
94743
|
+
# Listen port comes from \`web_service.port\` in switchroom.yaml
|
|
94744
|
+
# (default 8080) \u2014 overriding the image CMD here is what lets a
|
|
94745
|
+
# custom port survive \`switchroom update\`'s compose regeneration.
|
|
94746
|
+
# Point cloudflared / tailscale serve at the same port if changed.
|
|
94747
|
+
command: ["switchroom", "web", "--port", "${port}", "--bind", "127.0.0.1"]
|
|
94299
94748
|
# The webhook.sock forward is peercred-gated by each agent's gateway
|
|
94300
94749
|
# to {agent uid, SWITCHROOM_WEBHOOK_RECEIVER_UID = operator uid}.
|
|
94301
94750
|
# Run as any other uid \u2192 every forward is 503'd. Operator uid also
|
|
@@ -94350,10 +94799,10 @@ services:
|
|
|
94350
94799
|
`;
|
|
94351
94800
|
}
|
|
94352
94801
|
function webdDir() {
|
|
94353
|
-
return
|
|
94802
|
+
return join95(homedir56(), ".switchroom", "web");
|
|
94354
94803
|
}
|
|
94355
94804
|
function webdComposePath() {
|
|
94356
|
-
return
|
|
94805
|
+
return join95(webdDir(), "docker-compose.yml");
|
|
94357
94806
|
}
|
|
94358
94807
|
function backupExistingCompose2() {
|
|
94359
94808
|
const p = webdComposePath();
|
|
@@ -94382,9 +94831,10 @@ async function doInstall2(opts, program3) {
|
|
|
94382
94831
|
}
|
|
94383
94832
|
const dir = webdDir();
|
|
94384
94833
|
const composePath = webdComposePath();
|
|
94385
|
-
|
|
94834
|
+
mkdirSync55(dir, { recursive: true });
|
|
94386
94835
|
const cfg = getConfig(program3);
|
|
94387
94836
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
94837
|
+
const port = cfg.web_service?.port ?? 8080;
|
|
94388
94838
|
const guard = checkDowngrade({
|
|
94389
94839
|
container: "switchroom-web",
|
|
94390
94840
|
targetTag: imageTag,
|
|
@@ -94404,7 +94854,8 @@ async function doInstall2(opts, program3) {
|
|
|
94404
94854
|
const yaml = renderWebComposeFile({
|
|
94405
94855
|
hostHome,
|
|
94406
94856
|
imageTag,
|
|
94407
|
-
operatorUid
|
|
94857
|
+
operatorUid,
|
|
94858
|
+
port
|
|
94408
94859
|
});
|
|
94409
94860
|
if (opts.dryRun) {
|
|
94410
94861
|
console.log(source_default.dim(`# Would write: ${composePath}`));
|
|
@@ -94415,7 +94866,7 @@ async function doInstall2(opts, program3) {
|
|
|
94415
94866
|
const bak = backupExistingCompose2();
|
|
94416
94867
|
if (bak)
|
|
94417
94868
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94418
|
-
|
|
94869
|
+
writeFileSync36(composePath, yaml, "utf8");
|
|
94419
94870
|
try {
|
|
94420
94871
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
94421
94872
|
chownSync9(dir, operatorUid, operatorUid);
|
|
@@ -94425,7 +94876,7 @@ async function doInstall2(opts, program3) {
|
|
|
94425
94876
|
}
|
|
94426
94877
|
} catch {}
|
|
94427
94878
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94428
|
-
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
|
|
94879
|
+
console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host, listening on 127.0.0.1:${port}`));
|
|
94429
94880
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
94430
94881
|
const pull = runDocker2(["compose", "-p", WEB_COMPOSE_PROJECT, "-f", composePath, "pull"]);
|
|
94431
94882
|
if (!pull.ok) {
|
|
@@ -94448,7 +94899,8 @@ ${up.stderr}`));
|
|
|
94448
94899
|
console.log(source_default.dim(` Logs: docker logs switchroom-web --tail 50`));
|
|
94449
94900
|
console.log(source_default.dim(` Verify: switchroom webd status`));
|
|
94450
94901
|
console.log(source_default.yellow(` NOTE: the switchroom-web.service systemd unit (if still installed) also
|
|
94451
|
-
` + ` binds 127.0.0.1:8080. Stop it before this container can claim the port
|
|
94902
|
+
` + ` binds 127.0.0.1:8080. Stop it before this container can claim the port
|
|
94903
|
+
` + ` (the container listens on 127.0.0.1:${port}, from web_service.port):
|
|
94452
94904
|
` + ` systemctl --user stop switchroom-web.service
|
|
94453
94905
|
` + ` systemctl --user disable switchroom-web.service`));
|
|
94454
94906
|
}
|
|
@@ -94510,9 +94962,9 @@ function registerWebdCommand(program3) {
|
|
|
94510
94962
|
// src/cli/host-repair.ts
|
|
94511
94963
|
init_source();
|
|
94512
94964
|
import { homedir as homedir57 } from "node:os";
|
|
94513
|
-
import { join as
|
|
94965
|
+
import { join as join96 } from "node:path";
|
|
94514
94966
|
var ARTIFACT_ALLOWLIST = {
|
|
94515
|
-
dockerComposePluginDir: (home2) =>
|
|
94967
|
+
dockerComposePluginDir: (home2) => join96(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
94516
94968
|
stateSentinel: "/state"
|
|
94517
94969
|
};
|
|
94518
94970
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -95003,6 +95455,7 @@ registerDriveCommand(program3);
|
|
|
95003
95455
|
registerDriveMcpLauncherCommand(program3);
|
|
95004
95456
|
registerM365McpLauncherCommand(program3);
|
|
95005
95457
|
registerNotionMcpLauncherCommand(program3);
|
|
95458
|
+
registerHindsightMcpShimCommand(program3);
|
|
95006
95459
|
registerDeliverFileCommand(program3);
|
|
95007
95460
|
registerNotionCommand(program3);
|
|
95008
95461
|
registerApplyCommand(program3);
|