switchroom 0.18.31 → 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 +693 -253
- 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 +43 -5
- package/telegram-plugin/dist/gateway/gateway.js +25868 -25038
- 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 +7195 -8837
- 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/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/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();
|
|
@@ -47789,8 +47804,8 @@ var init_fleet_defaults = __esm(() => {
|
|
|
47789
47804
|
});
|
|
47790
47805
|
|
|
47791
47806
|
// src/agents/connection-health.ts
|
|
47792
|
-
import { mkdirSync as
|
|
47793
|
-
import { join as
|
|
47807
|
+
import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
|
|
47808
|
+
import { join as join82 } from "node:path";
|
|
47794
47809
|
async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
47795
47810
|
const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
|
|
47796
47811
|
if (reqs.length === 0)
|
|
@@ -47847,10 +47862,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
|
|
|
47847
47862
|
return issues;
|
|
47848
47863
|
}
|
|
47849
47864
|
function writeConnectionHealthFile(agentDir, health, deps) {
|
|
47850
|
-
const dir =
|
|
47851
|
-
const path8 =
|
|
47852
|
-
(deps?.mkdir ?? ((p, o) =>
|
|
47853
|
-
(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) + `
|
|
47854
47869
|
`);
|
|
47855
47870
|
}
|
|
47856
47871
|
async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
|
|
@@ -47871,10 +47886,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
47871
47886
|
var init_connection_health = () => {};
|
|
47872
47887
|
|
|
47873
47888
|
// src/cli/update-prompt-hook.ts
|
|
47874
|
-
import { existsSync as existsSync83, readFileSync as
|
|
47875
|
-
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";
|
|
47876
47891
|
function containerHookCommand() {
|
|
47877
|
-
return
|
|
47892
|
+
return join83(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
47878
47893
|
}
|
|
47879
47894
|
function updatePromptHookScript() {
|
|
47880
47895
|
return `#!/bin/bash
|
|
@@ -47940,14 +47955,14 @@ exit 0
|
|
|
47940
47955
|
`;
|
|
47941
47956
|
}
|
|
47942
47957
|
function installUpdatePromptHook(agentDir) {
|
|
47943
|
-
const hooksDir =
|
|
47944
|
-
|
|
47945
|
-
const scriptPath =
|
|
47958
|
+
const hooksDir = join83(agentDir, ".claude", "hooks");
|
|
47959
|
+
mkdirSync47(hooksDir, { recursive: true });
|
|
47960
|
+
const scriptPath = join83(hooksDir, HOOK_FILENAME);
|
|
47946
47961
|
const desired = updatePromptHookScript();
|
|
47947
47962
|
let installed = false;
|
|
47948
|
-
const existing = existsSync83(scriptPath) ?
|
|
47963
|
+
const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
|
|
47949
47964
|
if (existing !== desired) {
|
|
47950
|
-
|
|
47965
|
+
writeFileSync29(scriptPath, desired, { mode: 493 });
|
|
47951
47966
|
chmodSync12(scriptPath, 493);
|
|
47952
47967
|
installed = true;
|
|
47953
47968
|
} else {
|
|
@@ -47955,11 +47970,11 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47955
47970
|
chmodSync12(scriptPath, 493);
|
|
47956
47971
|
} catch {}
|
|
47957
47972
|
}
|
|
47958
|
-
const settingsPath =
|
|
47973
|
+
const settingsPath = join83(agentDir, ".claude", "settings.json");
|
|
47959
47974
|
if (!existsSync83(settingsPath)) {
|
|
47960
47975
|
return { scriptPath, settingsPath, installed };
|
|
47961
47976
|
}
|
|
47962
|
-
const raw =
|
|
47977
|
+
const raw = readFileSync72(settingsPath, "utf-8");
|
|
47963
47978
|
let parsed;
|
|
47964
47979
|
try {
|
|
47965
47980
|
parsed = JSON.parse(raw);
|
|
@@ -47995,7 +48010,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47995
48010
|
if (mutated) {
|
|
47996
48011
|
hooks.UserPromptSubmit = list2;
|
|
47997
48012
|
parsed.hooks = hooks;
|
|
47998
|
-
|
|
48013
|
+
writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
47999
48014
|
`, { mode: 384 });
|
|
48000
48015
|
installed = true;
|
|
48001
48016
|
} else if (!alreadyCorrect) {
|
|
@@ -48004,7 +48019,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
48004
48019
|
});
|
|
48005
48020
|
hooks.UserPromptSubmit = list2;
|
|
48006
48021
|
parsed.hooks = hooks;
|
|
48007
|
-
|
|
48022
|
+
writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
|
|
48008
48023
|
`, { mode: 384 });
|
|
48009
48024
|
installed = true;
|
|
48010
48025
|
}
|
|
@@ -48384,7 +48399,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
48384
48399
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
48385
48400
|
});
|
|
48386
48401
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
48387
|
-
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";
|
|
48388
48403
|
import { dirname as dirname29 } from "node:path";
|
|
48389
48404
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
48390
48405
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -48420,7 +48435,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48420
48435
|
if (engine !== "local") {
|
|
48421
48436
|
try {
|
|
48422
48437
|
if (existsSync85(envPath)) {
|
|
48423
|
-
const body =
|
|
48438
|
+
const body = readFileSync73(envPath, "utf-8");
|
|
48424
48439
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
48425
48440
|
rmSync17(envPath);
|
|
48426
48441
|
}
|
|
@@ -48439,11 +48454,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48439
48454
|
if (!token)
|
|
48440
48455
|
return;
|
|
48441
48456
|
try {
|
|
48442
|
-
|
|
48457
|
+
mkdirSync48(dirname29(envPath), { recursive: true });
|
|
48443
48458
|
let body = "";
|
|
48444
48459
|
try {
|
|
48445
48460
|
if (existsSync85(envPath))
|
|
48446
|
-
body =
|
|
48461
|
+
body = readFileSync73(envPath, "utf-8");
|
|
48447
48462
|
} catch {}
|
|
48448
48463
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
48449
48464
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -48451,7 +48466,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48451
48466
|
`) ? body + `
|
|
48452
48467
|
` : body) + line + `
|
|
48453
48468
|
`;
|
|
48454
|
-
|
|
48469
|
+
writeFileSync30(envPath, next, {
|
|
48455
48470
|
encoding: "utf-8",
|
|
48456
48471
|
mode: 384
|
|
48457
48472
|
});
|
|
@@ -48502,11 +48517,11 @@ __export(exports_apply, {
|
|
|
48502
48517
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
48503
48518
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
48504
48519
|
});
|
|
48505
|
-
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";
|
|
48506
48521
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
48507
48522
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
48508
48523
|
import readline from "node:readline";
|
|
48509
|
-
import { dirname as dirname30, join as
|
|
48524
|
+
import { dirname as dirname30, join as join85, resolve as resolve52 } from "node:path";
|
|
48510
48525
|
import { homedir as homedir49 } from "node:os";
|
|
48511
48526
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
48512
48527
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
@@ -48518,7 +48533,7 @@ async function resolveOperatorVaultPassphrase(home2) {
|
|
|
48518
48533
|
return envPass;
|
|
48519
48534
|
try {
|
|
48520
48535
|
const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
|
|
48521
|
-
const blobPath =
|
|
48536
|
+
const blobPath = join85(home2, ".switchroom", "vault-auto-unlock");
|
|
48522
48537
|
const pass = readAutoUnlockFile2(blobPath);
|
|
48523
48538
|
return pass && pass.length > 0 ? pass : null;
|
|
48524
48539
|
} catch {
|
|
@@ -48608,7 +48623,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48608
48623
|
let configText = null;
|
|
48609
48624
|
if (switchroomConfigPath && existsSync86(switchroomConfigPath)) {
|
|
48610
48625
|
try {
|
|
48611
|
-
configText =
|
|
48626
|
+
configText = readFileSync74(switchroomConfigPath, "utf-8");
|
|
48612
48627
|
} catch (err) {
|
|
48613
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.
|
|
48614
48629
|
`));
|
|
@@ -48831,7 +48846,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
|
|
|
48831
48846
|
if (isCustomPath && ctx.customVaultPath) {
|
|
48832
48847
|
return dirname30(ctx.customVaultPath);
|
|
48833
48848
|
}
|
|
48834
|
-
return
|
|
48849
|
+
return join85(homeDir, ".switchroom", "vault");
|
|
48835
48850
|
}
|
|
48836
48851
|
function inspectVaultBindMountDir(vaultDir) {
|
|
48837
48852
|
if (!existsSync86(vaultDir))
|
|
@@ -48862,60 +48877,60 @@ function hasVaultRefs(value) {
|
|
|
48862
48877
|
async function ensureHostMountSources(config) {
|
|
48863
48878
|
const home2 = resolveHostHomeForCompose();
|
|
48864
48879
|
const dirs = [
|
|
48865
|
-
|
|
48866
|
-
|
|
48867
|
-
|
|
48868
|
-
|
|
48869
|
-
|
|
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")
|
|
48870
48885
|
];
|
|
48871
48886
|
for (const name of Object.keys(config.agents)) {
|
|
48872
|
-
dirs.push(
|
|
48873
|
-
dirs.push(
|
|
48874
|
-
dirs.push(
|
|
48875
|
-
dirs.push(
|
|
48876
|
-
if (existsSync86(
|
|
48877
|
-
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"));
|
|
48878
48893
|
}
|
|
48879
48894
|
}
|
|
48880
48895
|
for (const dir of dirs) {
|
|
48881
48896
|
await mkdir2(dir, { recursive: true });
|
|
48882
48897
|
}
|
|
48883
|
-
const autoUnlockPath =
|
|
48898
|
+
const autoUnlockPath = join85(home2, ".switchroom", "vault-auto-unlock");
|
|
48884
48899
|
if (!existsSync86(autoUnlockPath)) {
|
|
48885
|
-
|
|
48900
|
+
writeFileSync31(autoUnlockPath, "", { mode: 384 });
|
|
48886
48901
|
}
|
|
48887
|
-
const auditLogPath =
|
|
48902
|
+
const auditLogPath = join85(home2, ".switchroom", "vault-audit.log");
|
|
48888
48903
|
if (!existsSync86(auditLogPath)) {
|
|
48889
|
-
|
|
48904
|
+
writeFileSync31(auditLogPath, "", { mode: 420 });
|
|
48890
48905
|
}
|
|
48891
48906
|
const grantsDbDir = getGrantsDbDir(home2);
|
|
48892
|
-
|
|
48907
|
+
mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
|
|
48893
48908
|
migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
|
|
48894
|
-
const hostdAuditLogPath =
|
|
48909
|
+
const hostdAuditLogPath = join85(home2, ".switchroom", "host-control-audit.log");
|
|
48895
48910
|
if (!existsSync86(hostdAuditLogPath)) {
|
|
48896
|
-
|
|
48911
|
+
writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
|
|
48897
48912
|
}
|
|
48898
48913
|
for (const name of Object.keys(config.agents)) {
|
|
48899
|
-
const tokenPath =
|
|
48914
|
+
const tokenPath = join85(home2, ".switchroom", "agents", name, ".vault-token");
|
|
48900
48915
|
if (!existsSync86(tokenPath)) {
|
|
48901
|
-
|
|
48916
|
+
writeFileSync31(tokenPath, "", { mode: 384 });
|
|
48902
48917
|
}
|
|
48903
48918
|
try {
|
|
48904
48919
|
const uid = allocateAgentUid(name);
|
|
48905
48920
|
chownSync8(tokenPath, uid, uid);
|
|
48906
48921
|
} catch {}
|
|
48907
48922
|
}
|
|
48908
|
-
const fleetDir =
|
|
48923
|
+
const fleetDir = join85(home2, ".switchroom", "fleet");
|
|
48909
48924
|
await mkdir2(fleetDir, { recursive: true });
|
|
48910
|
-
const invariantsPath =
|
|
48925
|
+
const invariantsPath = join85(fleetDir, "switchroom-invariants.md");
|
|
48911
48926
|
const invariantsCanonical = renderFleetInvariants();
|
|
48912
|
-
const invariantsCurrent = existsSync86(invariantsPath) ?
|
|
48927
|
+
const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
|
|
48913
48928
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
48914
|
-
|
|
48929
|
+
writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
48915
48930
|
}
|
|
48916
|
-
const fleetClaudePath =
|
|
48931
|
+
const fleetClaudePath = join85(fleetDir, "CLAUDE.md");
|
|
48917
48932
|
if (!existsSync86(fleetClaudePath)) {
|
|
48918
|
-
|
|
48933
|
+
writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
|
|
48919
48934
|
mode: 420
|
|
48920
48935
|
});
|
|
48921
48936
|
}
|
|
@@ -48999,17 +49014,17 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
48999
49014
|
}
|
|
49000
49015
|
function writeInstallTypeCache(homeDir = homedir49()) {
|
|
49001
49016
|
const ctx = detectInstallType();
|
|
49002
|
-
const dir =
|
|
49003
|
-
const out =
|
|
49017
|
+
const dir = join85(homeDir, ".switchroom");
|
|
49018
|
+
const out = join85(dir, "install-type.json");
|
|
49004
49019
|
const tmp = `${out}.tmp`;
|
|
49005
|
-
|
|
49020
|
+
mkdirSync49(dir, { recursive: true });
|
|
49006
49021
|
const payload = {
|
|
49007
49022
|
install_type: ctx.install_type,
|
|
49008
49023
|
detected_at: new Date().toISOString(),
|
|
49009
49024
|
source_paths: ctx.source_paths
|
|
49010
49025
|
};
|
|
49011
|
-
|
|
49012
|
-
|
|
49026
|
+
writeFileSync31(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
|
|
49027
|
+
renameSync20(tmp, out);
|
|
49013
49028
|
return out;
|
|
49014
49029
|
}
|
|
49015
49030
|
async function runApply(config, options, deps = {}, switchroomConfigPath) {
|
|
@@ -49067,17 +49082,17 @@ Applying switchroom config...
|
|
|
49067
49082
|
writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
|
|
49068
49083
|
`));
|
|
49069
49084
|
try {
|
|
49070
|
-
installUpdatePromptHook(
|
|
49085
|
+
installUpdatePromptHook(join85(agentsDir, name));
|
|
49071
49086
|
} catch (hookErr) {
|
|
49072
49087
|
writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
|
|
49073
49088
|
`));
|
|
49074
49089
|
}
|
|
49075
|
-
await refreshAgentConnectionHealth(config, name,
|
|
49090
|
+
await refreshAgentConnectionHealth(config, name, join85(agentsDir, name), {
|
|
49076
49091
|
vaultAclReader: connHealthVaultAclReader
|
|
49077
49092
|
});
|
|
49078
49093
|
try {
|
|
49079
49094
|
const uid = allocateAgentUid(name);
|
|
49080
|
-
alignAgentUid(name,
|
|
49095
|
+
alignAgentUid(name, join85(agentsDir, name), uid, {
|
|
49081
49096
|
confirm: !options.nonInteractive,
|
|
49082
49097
|
writeOut
|
|
49083
49098
|
});
|
|
@@ -49122,7 +49137,7 @@ Applying switchroom config...
|
|
|
49122
49137
|
for (const name of agentNames) {
|
|
49123
49138
|
try {
|
|
49124
49139
|
const uid = allocateAgentUid(name);
|
|
49125
|
-
alignAgentUid(name,
|
|
49140
|
+
alignAgentUid(name, join85(agentsDir, name), uid, {
|
|
49126
49141
|
confirm: !options.nonInteractive,
|
|
49127
49142
|
writeOut
|
|
49128
49143
|
});
|
|
@@ -49466,7 +49481,7 @@ function copyExampleConfig2(name) {
|
|
|
49466
49481
|
}
|
|
49467
49482
|
const embedded = EMBEDDED_EXAMPLES[name];
|
|
49468
49483
|
if (embedded !== undefined) {
|
|
49469
|
-
|
|
49484
|
+
writeFileSync31(dest, embedded, { encoding: "utf8" });
|
|
49470
49485
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
49471
49486
|
return;
|
|
49472
49487
|
}
|
|
@@ -49482,7 +49497,7 @@ function findUnwritableAgentDirs(config, opts) {
|
|
|
49482
49497
|
const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
|
|
49483
49498
|
const unwritable = [];
|
|
49484
49499
|
for (const name of targets) {
|
|
49485
|
-
const startSh =
|
|
49500
|
+
const startSh = join85(agentsDir, name, "start.sh");
|
|
49486
49501
|
if (!existsSync86(startSh))
|
|
49487
49502
|
continue;
|
|
49488
49503
|
try {
|
|
@@ -49687,7 +49702,7 @@ var init_apply = __esm(() => {
|
|
|
49687
49702
|
switchroom: switchroom_default,
|
|
49688
49703
|
minimal: minimal_default
|
|
49689
49704
|
};
|
|
49690
|
-
DEFAULT_COMPOSE_PATH2 =
|
|
49705
|
+
DEFAULT_COMPOSE_PATH2 = join85(homedir49(), ".switchroom", "compose", "docker-compose.yml");
|
|
49691
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.
|
|
49692
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.";
|
|
49693
49708
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
@@ -63136,7 +63151,7 @@ __export(exports_server2, {
|
|
|
63136
63151
|
TOOLS: () => TOOLS2
|
|
63137
63152
|
});
|
|
63138
63153
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
63139
|
-
import { existsSync as existsSync95, readFileSync as
|
|
63154
|
+
import { existsSync as existsSync95, readFileSync as readFileSync83 } from "node:fs";
|
|
63140
63155
|
function selfSocketPath() {
|
|
63141
63156
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
63142
63157
|
}
|
|
@@ -63425,7 +63440,7 @@ function getLastUpdateApplyStatus() {
|
|
|
63425
63440
|
}
|
|
63426
63441
|
let raw;
|
|
63427
63442
|
try {
|
|
63428
|
-
raw =
|
|
63443
|
+
raw = readFileSync83(path9, "utf-8");
|
|
63429
63444
|
} catch (err2) {
|
|
63430
63445
|
return errorText2(`get_status: failed to read audit log at ${path9}: ${err2.message}`);
|
|
63431
63446
|
}
|
|
@@ -64115,11 +64130,11 @@ __export(exports_scan, {
|
|
|
64115
64130
|
ledgerPathForBase: () => ledgerPathForBase
|
|
64116
64131
|
});
|
|
64117
64132
|
import {
|
|
64118
|
-
readFileSync as
|
|
64133
|
+
readFileSync as readFileSync85,
|
|
64119
64134
|
readdirSync as readdirSync37,
|
|
64120
64135
|
existsSync as existsSync98,
|
|
64121
|
-
mkdirSync as
|
|
64122
|
-
writeFileSync as
|
|
64136
|
+
mkdirSync as mkdirSync56,
|
|
64137
|
+
writeFileSync as writeFileSync37
|
|
64123
64138
|
} from "node:fs";
|
|
64124
64139
|
import { resolve as resolve57, dirname as dirname33 } from "node:path";
|
|
64125
64140
|
import { homedir as homedir58 } from "node:os";
|
|
@@ -64158,7 +64173,7 @@ function runScan(opts = {}) {
|
|
|
64158
64173
|
let sawArtifact = false;
|
|
64159
64174
|
try {
|
|
64160
64175
|
if (existsSync98(turnsPath)) {
|
|
64161
|
-
turnsText =
|
|
64176
|
+
turnsText = readFileSync85(turnsPath, "utf-8");
|
|
64162
64177
|
sawArtifact = true;
|
|
64163
64178
|
}
|
|
64164
64179
|
} catch (e) {
|
|
@@ -64166,7 +64181,7 @@ function runScan(opts = {}) {
|
|
|
64166
64181
|
}
|
|
64167
64182
|
try {
|
|
64168
64183
|
if (existsSync98(gwPath)) {
|
|
64169
|
-
gwText =
|
|
64184
|
+
gwText = readFileSync85(gwPath, "utf-8");
|
|
64170
64185
|
sawArtifact = true;
|
|
64171
64186
|
}
|
|
64172
64187
|
} catch (e) {
|
|
@@ -64208,7 +64223,7 @@ function readLedgerIfPresent(base) {
|
|
|
64208
64223
|
try {
|
|
64209
64224
|
if (!existsSync98(path9))
|
|
64210
64225
|
return null;
|
|
64211
|
-
return JSON.parse(
|
|
64226
|
+
return JSON.parse(readFileSync85(path9, "utf-8"));
|
|
64212
64227
|
} catch {
|
|
64213
64228
|
return null;
|
|
64214
64229
|
}
|
|
@@ -64218,8 +64233,8 @@ function ledgerPathForBase(base) {
|
|
|
64218
64233
|
}
|
|
64219
64234
|
function writeLedger(base, ledger) {
|
|
64220
64235
|
const path9 = ledgerPathForBase(base);
|
|
64221
|
-
|
|
64222
|
-
|
|
64236
|
+
mkdirSync56(dirname33(path9), { recursive: true });
|
|
64237
|
+
writeFileSync37(path9, JSON.stringify(ledger, null, 2) + `
|
|
64223
64238
|
`, "utf-8");
|
|
64224
64239
|
return path9;
|
|
64225
64240
|
}
|
|
@@ -89659,9 +89674,425 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
89659
89674
|
});
|
|
89660
89675
|
}
|
|
89661
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
|
+
|
|
89662
90093
|
// src/cli/deliver-file.ts
|
|
89663
90094
|
init_client2();
|
|
89664
|
-
import { readFileSync as
|
|
90095
|
+
import { readFileSync as readFileSync71, statSync as statSync46 } from "node:fs";
|
|
89665
90096
|
import { basename as basename11 } from "node:path";
|
|
89666
90097
|
|
|
89667
90098
|
// src/delivery/onedrive.ts
|
|
@@ -90001,7 +90432,7 @@ async function defaultResolveProvider() {
|
|
|
90001
90432
|
async function runDeliverFile(localPath, deps = {}) {
|
|
90002
90433
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
90003
90434
|
const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
|
|
90004
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
90435
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync71(p)));
|
|
90005
90436
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
90006
90437
|
let size;
|
|
90007
90438
|
try {
|
|
@@ -90322,8 +90753,8 @@ function runRedactStdin() {
|
|
|
90322
90753
|
}
|
|
90323
90754
|
|
|
90324
90755
|
// src/cli/status-ask.ts
|
|
90325
|
-
import { readFileSync as
|
|
90326
|
-
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";
|
|
90327
90758
|
import { homedir as homedir50 } from "node:os";
|
|
90328
90759
|
|
|
90329
90760
|
// src/status-ask/report.ts
|
|
@@ -90598,7 +91029,7 @@ function runReport(opts) {
|
|
|
90598
91029
|
for (const src of sources) {
|
|
90599
91030
|
let content;
|
|
90600
91031
|
try {
|
|
90601
|
-
content =
|
|
91032
|
+
content = readFileSync75(src.path, "utf-8");
|
|
90602
91033
|
} catch (err) {
|
|
90603
91034
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
90604
91035
|
`);
|
|
@@ -90659,7 +91090,7 @@ function resolveSources(explicitPath) {
|
|
|
90659
91090
|
const config = loadConfig();
|
|
90660
91091
|
agentsDir = resolveAgentsDir(config);
|
|
90661
91092
|
} catch {
|
|
90662
|
-
agentsDir =
|
|
91093
|
+
agentsDir = join86(homedir50(), ".switchroom", "agents");
|
|
90663
91094
|
}
|
|
90664
91095
|
if (!existsSync87(agentsDir))
|
|
90665
91096
|
return [];
|
|
@@ -90671,7 +91102,7 @@ function resolveSources(explicitPath) {
|
|
|
90671
91102
|
return [];
|
|
90672
91103
|
}
|
|
90673
91104
|
for (const name of entries) {
|
|
90674
|
-
const path9 =
|
|
91105
|
+
const path9 = join86(agentsDir, name, "runtime-metrics.jsonl");
|
|
90675
91106
|
if (existsSync87(path9)) {
|
|
90676
91107
|
sources.push({ path: path9, agent: name });
|
|
90677
91108
|
}
|
|
@@ -90704,43 +91135,43 @@ import {
|
|
|
90704
91135
|
closeSync as closeSync14,
|
|
90705
91136
|
existsSync as existsSync88,
|
|
90706
91137
|
fsyncSync as fsyncSync8,
|
|
90707
|
-
mkdirSync as
|
|
91138
|
+
mkdirSync as mkdirSync50,
|
|
90708
91139
|
openSync as openSync14,
|
|
90709
91140
|
readdirSync as readdirSync31,
|
|
90710
|
-
readFileSync as
|
|
90711
|
-
renameSync as
|
|
91141
|
+
readFileSync as readFileSync76,
|
|
91142
|
+
renameSync as renameSync21,
|
|
90712
91143
|
statSync as statSync48,
|
|
90713
91144
|
unlinkSync as unlinkSync18,
|
|
90714
91145
|
writeSync as writeSync10
|
|
90715
91146
|
} from "node:fs";
|
|
90716
|
-
import { join as
|
|
91147
|
+
import { join as join87, resolve as resolve53 } from "node:path";
|
|
90717
91148
|
var STAGING_SUBDIR = ".staging";
|
|
90718
91149
|
function overlayPathsFor(agent, opts = {}) {
|
|
90719
91150
|
const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
90720
|
-
const scheduleDir =
|
|
90721
|
-
const scheduleStagingDir =
|
|
90722
|
-
const skillsDir =
|
|
90723
|
-
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);
|
|
90724
91155
|
return {
|
|
90725
91156
|
agentRoot: base,
|
|
90726
91157
|
scheduleDir,
|
|
90727
91158
|
scheduleStagingDir,
|
|
90728
91159
|
skillsDir,
|
|
90729
91160
|
skillsStagingDir,
|
|
90730
|
-
lockPath:
|
|
91161
|
+
lockPath: join87(base, ".lock"),
|
|
90731
91162
|
stagingDir: scheduleStagingDir
|
|
90732
91163
|
};
|
|
90733
91164
|
}
|
|
90734
91165
|
function ensureDirs(paths) {
|
|
90735
|
-
|
|
90736
|
-
|
|
91166
|
+
mkdirSync50(paths.scheduleDir, { recursive: true });
|
|
91167
|
+
mkdirSync50(paths.scheduleStagingDir, { recursive: true });
|
|
90737
91168
|
}
|
|
90738
91169
|
function ensureSkillsDirs(paths) {
|
|
90739
|
-
|
|
90740
|
-
|
|
91170
|
+
mkdirSync50(paths.skillsDir, { recursive: true });
|
|
91171
|
+
mkdirSync50(paths.skillsStagingDir, { recursive: true });
|
|
90741
91172
|
}
|
|
90742
91173
|
function withAgentLock(paths, fn) {
|
|
90743
|
-
|
|
91174
|
+
mkdirSync50(paths.agentRoot, { recursive: true });
|
|
90744
91175
|
const start = Date.now();
|
|
90745
91176
|
const TIMEOUT_MS = 5000;
|
|
90746
91177
|
let fd = null;
|
|
@@ -90781,8 +91212,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90781
91212
|
const paths = overlayPathsFor(agent, opts);
|
|
90782
91213
|
return withAgentLock(paths, () => {
|
|
90783
91214
|
ensureDirs(paths);
|
|
90784
|
-
const stagingPath =
|
|
90785
|
-
const finalPath =
|
|
91215
|
+
const stagingPath = join87(paths.scheduleStagingDir, `${slug}.yaml`);
|
|
91216
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90786
91217
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90787
91218
|
try {
|
|
90788
91219
|
writeSync10(fd, yamlText);
|
|
@@ -90790,7 +91221,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90790
91221
|
} finally {
|
|
90791
91222
|
closeSync14(fd);
|
|
90792
91223
|
}
|
|
90793
|
-
|
|
91224
|
+
renameSync21(stagingPath, finalPath);
|
|
90794
91225
|
return finalPath;
|
|
90795
91226
|
});
|
|
90796
91227
|
}
|
|
@@ -90798,8 +91229,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90798
91229
|
const paths = overlayPathsFor(agent, opts);
|
|
90799
91230
|
return withAgentLock(paths, () => {
|
|
90800
91231
|
ensureSkillsDirs(paths);
|
|
90801
|
-
const stagingPath =
|
|
90802
|
-
const finalPath =
|
|
91232
|
+
const stagingPath = join87(paths.skillsStagingDir, `${slug}.yaml`);
|
|
91233
|
+
const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
|
|
90803
91234
|
const fd = openSync14(stagingPath, "w", 384);
|
|
90804
91235
|
try {
|
|
90805
91236
|
writeSync10(fd, yamlText);
|
|
@@ -90807,14 +91238,14 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
90807
91238
|
} finally {
|
|
90808
91239
|
closeSync14(fd);
|
|
90809
91240
|
}
|
|
90810
|
-
|
|
91241
|
+
renameSync21(stagingPath, finalPath);
|
|
90811
91242
|
return finalPath;
|
|
90812
91243
|
});
|
|
90813
91244
|
}
|
|
90814
91245
|
function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
90815
91246
|
const paths = overlayPathsFor(agent, opts);
|
|
90816
91247
|
return withAgentLock(paths, () => {
|
|
90817
|
-
const finalPath =
|
|
91248
|
+
const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
|
|
90818
91249
|
if (!existsSync88(finalPath))
|
|
90819
91250
|
return false;
|
|
90820
91251
|
unlinkSync18(finalPath);
|
|
@@ -90829,9 +91260,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90829
91260
|
for (const name of readdirSync31(paths.skillsDir)) {
|
|
90830
91261
|
if (!/\.ya?ml$/i.test(name))
|
|
90831
91262
|
continue;
|
|
90832
|
-
const full =
|
|
91263
|
+
const full = join87(paths.skillsDir, name);
|
|
90833
91264
|
try {
|
|
90834
|
-
const raw =
|
|
91265
|
+
const raw = readFileSync76(full, "utf-8");
|
|
90835
91266
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90836
91267
|
out.push({ slug, path: full, raw });
|
|
90837
91268
|
} catch {}
|
|
@@ -90841,7 +91272,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
90841
91272
|
function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
90842
91273
|
const paths = overlayPathsFor(agent, opts);
|
|
90843
91274
|
return withAgentLock(paths, () => {
|
|
90844
|
-
const finalPath =
|
|
91275
|
+
const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
|
|
90845
91276
|
if (!existsSync88(finalPath))
|
|
90846
91277
|
return false;
|
|
90847
91278
|
unlinkSync18(finalPath);
|
|
@@ -90856,9 +91287,9 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
90856
91287
|
for (const name of readdirSync31(paths.scheduleDir)) {
|
|
90857
91288
|
if (!/\.ya?ml$/i.test(name))
|
|
90858
91289
|
continue;
|
|
90859
|
-
const full =
|
|
91290
|
+
const full = join87(paths.scheduleDir, name);
|
|
90860
91291
|
try {
|
|
90861
|
-
const raw =
|
|
91292
|
+
const raw = readFileSync76(full, "utf-8");
|
|
90862
91293
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
90863
91294
|
out.push({ slug, path: full, raw });
|
|
90864
91295
|
} catch {}
|
|
@@ -91080,25 +91511,25 @@ import {
|
|
|
91080
91511
|
closeSync as closeSync15,
|
|
91081
91512
|
existsSync as existsSync89,
|
|
91082
91513
|
fsyncSync as fsyncSync9,
|
|
91083
|
-
mkdirSync as
|
|
91514
|
+
mkdirSync as mkdirSync51,
|
|
91084
91515
|
openSync as openSync15,
|
|
91085
91516
|
readdirSync as readdirSync32,
|
|
91086
|
-
readFileSync as
|
|
91087
|
-
renameSync as
|
|
91517
|
+
readFileSync as readFileSync77,
|
|
91518
|
+
renameSync as renameSync22,
|
|
91088
91519
|
unlinkSync as unlinkSync19,
|
|
91089
|
-
writeFileSync as
|
|
91520
|
+
writeFileSync as writeFileSync32,
|
|
91090
91521
|
writeSync as writeSync11
|
|
91091
91522
|
} from "node:fs";
|
|
91092
|
-
import { join as
|
|
91523
|
+
import { join as join88 } from "node:path";
|
|
91093
91524
|
import { randomBytes as randomBytes15 } from "node:crypto";
|
|
91094
91525
|
var STAGE_ID_PREFIX = "cap_";
|
|
91095
91526
|
function pendingDir(agent, opts = {}) {
|
|
91096
91527
|
const paths = overlayPathsFor(agent, opts);
|
|
91097
|
-
return
|
|
91528
|
+
return join88(paths.scheduleDir, ".pending");
|
|
91098
91529
|
}
|
|
91099
91530
|
function ensurePendingDir(agent, opts = {}) {
|
|
91100
91531
|
const dir = pendingDir(agent, opts);
|
|
91101
|
-
|
|
91532
|
+
mkdirSync51(dir, { recursive: true });
|
|
91102
91533
|
return dir;
|
|
91103
91534
|
}
|
|
91104
91535
|
function newStageId() {
|
|
@@ -91107,8 +91538,8 @@ function newStageId() {
|
|
|
91107
91538
|
function stagePendingScheduleEntry(opts) {
|
|
91108
91539
|
const dir = ensurePendingDir(opts.agent, { root: opts.root });
|
|
91109
91540
|
const stageId = opts.stageId ?? newStageId();
|
|
91110
|
-
const yamlPath =
|
|
91111
|
-
const metaPath =
|
|
91541
|
+
const yamlPath = join88(dir, `${stageId}.yaml`);
|
|
91542
|
+
const metaPath = join88(dir, `${stageId}.meta.json`);
|
|
91112
91543
|
const meta = {
|
|
91113
91544
|
v: 1,
|
|
91114
91545
|
stage_id: stageId,
|
|
@@ -91127,9 +91558,9 @@ function stagePendingScheduleEntry(opts) {
|
|
|
91127
91558
|
} finally {
|
|
91128
91559
|
closeSync15(fd);
|
|
91129
91560
|
}
|
|
91130
|
-
|
|
91561
|
+
renameSync22(yamlTmp, yamlPath);
|
|
91131
91562
|
}
|
|
91132
|
-
|
|
91563
|
+
writeFileSync32(metaPath, JSON.stringify(meta, null, 2) + `
|
|
91133
91564
|
`, { mode: 384 });
|
|
91134
91565
|
return { stageId, yamlPath, metaPath };
|
|
91135
91566
|
}
|
|
@@ -91142,12 +91573,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
91142
91573
|
if (!name.endsWith(".meta.json"))
|
|
91143
91574
|
continue;
|
|
91144
91575
|
const stageId = name.slice(0, -".meta.json".length);
|
|
91145
|
-
const metaPath =
|
|
91146
|
-
const yamlPath =
|
|
91576
|
+
const metaPath = join88(dir, name);
|
|
91577
|
+
const yamlPath = join88(dir, `${stageId}.yaml`);
|
|
91147
91578
|
if (!existsSync89(yamlPath))
|
|
91148
91579
|
continue;
|
|
91149
91580
|
try {
|
|
91150
|
-
const meta = JSON.parse(
|
|
91581
|
+
const meta = JSON.parse(readFileSync77(metaPath, "utf-8"));
|
|
91151
91582
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
91152
91583
|
continue;
|
|
91153
91584
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -91162,11 +91593,11 @@ function commitPendingScheduleEntry(opts) {
|
|
|
91162
91593
|
return { committed: false, reason: "not_found" };
|
|
91163
91594
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
91164
91595
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
91165
|
-
const finalPath =
|
|
91596
|
+
const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
|
|
91166
91597
|
if (existsSync89(finalPath)) {
|
|
91167
91598
|
return { committed: false, reason: "slug_collision" };
|
|
91168
91599
|
}
|
|
91169
|
-
|
|
91600
|
+
renameSync22(match.yamlPath, finalPath);
|
|
91170
91601
|
unlinkSync19(match.metaPath);
|
|
91171
91602
|
return { committed: true, path: finalPath, slug };
|
|
91172
91603
|
}
|
|
@@ -91185,7 +91616,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
91185
91616
|
}
|
|
91186
91617
|
|
|
91187
91618
|
// src/cli/agent-config-write.ts
|
|
91188
|
-
import { existsSync as existsSync90, readFileSync as
|
|
91619
|
+
import { existsSync as existsSync90, readFileSync as readFileSync78 } from "node:fs";
|
|
91189
91620
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
91190
91621
|
|
|
91191
91622
|
// src/scheduler/schedule-report.ts
|
|
@@ -91576,7 +92007,7 @@ function scheduleRemove(opts) {
|
|
|
91576
92007
|
let priorContent = null;
|
|
91577
92008
|
try {
|
|
91578
92009
|
if (existsSync90(match.path))
|
|
91579
|
-
priorContent =
|
|
92010
|
+
priorContent = readFileSync78(match.path, "utf-8");
|
|
91580
92011
|
} catch {}
|
|
91581
92012
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
91582
92013
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -91779,7 +92210,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
91779
92210
|
}
|
|
91780
92211
|
let blob;
|
|
91781
92212
|
if (opts.jsonl) {
|
|
91782
|
-
blob = existsSync90(opts.jsonl) ?
|
|
92213
|
+
blob = existsSync90(opts.jsonl) ? readFileSync78(opts.jsonl, "utf-8") : "";
|
|
91783
92214
|
} else {
|
|
91784
92215
|
try {
|
|
91785
92216
|
blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -91815,7 +92246,7 @@ import { existsSync as existsSync91 } from "node:fs";
|
|
|
91815
92246
|
init_reconcile_default_skills();
|
|
91816
92247
|
init_agent_config();
|
|
91817
92248
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
91818
|
-
import { join as
|
|
92249
|
+
import { join as join89 } from "node:path";
|
|
91819
92250
|
var MAX_SKILLS_PER_AGENT = 20;
|
|
91820
92251
|
var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
|
|
91821
92252
|
function exitCodeFor2(code) {
|
|
@@ -91890,7 +92321,7 @@ function skillInstall(opts) {
|
|
|
91890
92321
|
return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
|
|
91891
92322
|
}
|
|
91892
92323
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
91893
|
-
const skillPath =
|
|
92324
|
+
const skillPath = join89(poolDir, skillName);
|
|
91894
92325
|
if (!existsSync91(skillPath)) {
|
|
91895
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.`);
|
|
91896
92327
|
}
|
|
@@ -92057,19 +92488,19 @@ import {
|
|
|
92057
92488
|
closeSync as closeSync16,
|
|
92058
92489
|
existsSync as existsSync92,
|
|
92059
92490
|
lstatSync as lstatSync11,
|
|
92060
|
-
mkdirSync as
|
|
92491
|
+
mkdirSync as mkdirSync52,
|
|
92061
92492
|
mkdtempSync as mkdtempSync5,
|
|
92062
92493
|
openSync as openSync16,
|
|
92063
|
-
readFileSync as
|
|
92494
|
+
readFileSync as readFileSync79,
|
|
92064
92495
|
readdirSync as readdirSync33,
|
|
92065
92496
|
realpathSync as realpathSync7,
|
|
92066
|
-
renameSync as
|
|
92497
|
+
renameSync as renameSync23,
|
|
92067
92498
|
rmSync as rmSync18,
|
|
92068
92499
|
statSync as statSync49,
|
|
92069
|
-
writeFileSync as
|
|
92500
|
+
writeFileSync as writeFileSync33
|
|
92070
92501
|
} from "node:fs";
|
|
92071
|
-
import { tmpdir as
|
|
92072
|
-
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";
|
|
92073
92504
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92074
92505
|
|
|
92075
92506
|
// src/cli/skill-common.ts
|
|
@@ -92303,7 +92734,7 @@ function scanForClaudeP2(content) {
|
|
|
92303
92734
|
function resolveSkillsPoolDir2(override) {
|
|
92304
92735
|
const raw = override ?? "~/.switchroom/skills";
|
|
92305
92736
|
if (raw.startsWith("~/")) {
|
|
92306
|
-
return
|
|
92737
|
+
return join90(homedir51(), raw.slice(2));
|
|
92307
92738
|
}
|
|
92308
92739
|
if (raw === "~")
|
|
92309
92740
|
return homedir51();
|
|
@@ -92342,7 +92773,7 @@ function loadFromDir(dir) {
|
|
|
92342
92773
|
const walk2 = (sub) => {
|
|
92343
92774
|
const entries = readdirSync33(sub, { withFileTypes: true });
|
|
92344
92775
|
for (const ent of entries) {
|
|
92345
|
-
const full =
|
|
92776
|
+
const full = join90(sub, ent.name);
|
|
92346
92777
|
const rel = relative2(abs, full);
|
|
92347
92778
|
if (ent.isSymbolicLink()) {
|
|
92348
92779
|
fail3(`refusing to read symlink inside --from dir: ${rel}`);
|
|
@@ -92352,7 +92783,7 @@ function loadFromDir(dir) {
|
|
|
92352
92783
|
continue;
|
|
92353
92784
|
}
|
|
92354
92785
|
if (ent.isFile()) {
|
|
92355
|
-
const buf =
|
|
92786
|
+
const buf = readFileSync79(full);
|
|
92356
92787
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
92357
92788
|
}
|
|
92358
92789
|
}
|
|
@@ -92377,7 +92808,7 @@ function loadFromTarball(tarPath) {
|
|
|
92377
92808
|
fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
|
|
92378
92809
|
}
|
|
92379
92810
|
}
|
|
92380
|
-
const staging = mkdtempSync5(
|
|
92811
|
+
const staging = mkdtempSync5(join90(tmpdir6(), "skill-apply-extract-"));
|
|
92381
92812
|
try {
|
|
92382
92813
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
92383
92814
|
const r = spawnSync16("tar", [
|
|
@@ -92401,7 +92832,7 @@ function loadFromTarball(tarPath) {
|
|
|
92401
92832
|
}
|
|
92402
92833
|
}
|
|
92403
92834
|
function loadSingleFile(filePath) {
|
|
92404
|
-
const content =
|
|
92835
|
+
const content = readFileSync79(filePath, "utf-8");
|
|
92405
92836
|
return { "SKILL.md": content };
|
|
92406
92837
|
}
|
|
92407
92838
|
function loadFromStdin() {
|
|
@@ -92463,10 +92894,10 @@ function validatePayload(name, files) {
|
|
|
92463
92894
|
errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
|
|
92464
92895
|
}
|
|
92465
92896
|
} else if (PY_SCRIPT_RE2.test(path9)) {
|
|
92466
|
-
const tmp = mkdtempSync5(
|
|
92467
|
-
const tmpPy =
|
|
92897
|
+
const tmp = mkdtempSync5(join90(tmpdir6(), "skill-apply-py-"));
|
|
92898
|
+
const tmpPy = join90(tmp, "check.py");
|
|
92468
92899
|
try {
|
|
92469
|
-
|
|
92900
|
+
writeFileSync33(tmpPy, content);
|
|
92470
92901
|
const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
|
|
92471
92902
|
encoding: "utf-8"
|
|
92472
92903
|
});
|
|
@@ -92487,12 +92918,12 @@ function diffSummary(currentDir, files) {
|
|
|
92487
92918
|
if (existsSync92(currentDir)) {
|
|
92488
92919
|
const walk2 = (sub) => {
|
|
92489
92920
|
for (const ent of readdirSync33(sub, { withFileTypes: true })) {
|
|
92490
|
-
const full =
|
|
92921
|
+
const full = join90(sub, ent.name);
|
|
92491
92922
|
const rel = relative2(currentDir, full);
|
|
92492
92923
|
if (ent.isDirectory()) {
|
|
92493
92924
|
walk2(full);
|
|
92494
92925
|
} else if (ent.isFile()) {
|
|
92495
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
92926
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync79(full, "utf-8");
|
|
92496
92927
|
}
|
|
92497
92928
|
}
|
|
92498
92929
|
};
|
|
@@ -92521,9 +92952,9 @@ function diffSummary(currentDir, files) {
|
|
|
92521
92952
|
}
|
|
92522
92953
|
function writePayload(poolDir, name, files) {
|
|
92523
92954
|
if (!existsSync92(poolDir)) {
|
|
92524
|
-
|
|
92955
|
+
mkdirSync52(poolDir, { recursive: true, mode: 493 });
|
|
92525
92956
|
}
|
|
92526
|
-
const target =
|
|
92957
|
+
const target = join90(poolDir, name);
|
|
92527
92958
|
let targetIsSymlink = false;
|
|
92528
92959
|
try {
|
|
92529
92960
|
const st = lstatSync11(target);
|
|
@@ -92534,15 +92965,15 @@ function writePayload(poolDir, name, files) {
|
|
|
92534
92965
|
if (targetIsSymlink) {
|
|
92535
92966
|
fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
|
|
92536
92967
|
}
|
|
92537
|
-
const staging = mkdtempSync5(
|
|
92968
|
+
const staging = mkdtempSync5(join90(poolDir, `.skill-apply-stage-${name}-`));
|
|
92538
92969
|
let oldRename = null;
|
|
92539
92970
|
try {
|
|
92540
92971
|
for (const [path9, content] of Object.entries(files)) {
|
|
92541
|
-
const full =
|
|
92542
|
-
|
|
92972
|
+
const full = join90(staging, path9);
|
|
92973
|
+
mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
|
|
92543
92974
|
const fd = openSync16(full, "wx");
|
|
92544
92975
|
try {
|
|
92545
|
-
|
|
92976
|
+
writeFileSync33(fd, content);
|
|
92546
92977
|
} finally {
|
|
92547
92978
|
closeSync16(fd);
|
|
92548
92979
|
}
|
|
@@ -92558,9 +92989,9 @@ function writePayload(poolDir, name, files) {
|
|
|
92558
92989
|
} catch {}
|
|
92559
92990
|
if (targetExists) {
|
|
92560
92991
|
oldRename = `${target}.skill-apply-old-${Date.now()}`;
|
|
92561
|
-
|
|
92992
|
+
renameSync23(target, oldRename);
|
|
92562
92993
|
}
|
|
92563
|
-
|
|
92994
|
+
renameSync23(staging, target);
|
|
92564
92995
|
if (oldRename) {
|
|
92565
92996
|
rmSync18(oldRename, { recursive: true, force: true });
|
|
92566
92997
|
oldRename = null;
|
|
@@ -92574,7 +93005,7 @@ function writePayload(poolDir, name, files) {
|
|
|
92574
93005
|
if (existsSync92(target)) {
|
|
92575
93006
|
rmSync18(target, { recursive: true, force: true });
|
|
92576
93007
|
}
|
|
92577
|
-
|
|
93008
|
+
renameSync23(oldRename, target);
|
|
92578
93009
|
} catch {}
|
|
92579
93010
|
}
|
|
92580
93011
|
throw err2;
|
|
@@ -92616,7 +93047,7 @@ function registerSkillCommand(program3) {
|
|
|
92616
93047
|
}
|
|
92617
93048
|
const config = loadConfig();
|
|
92618
93049
|
const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
|
|
92619
|
-
const currentDir =
|
|
93050
|
+
const currentDir = join90(poolDir, name);
|
|
92620
93051
|
console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
|
|
92621
93052
|
console.log(source_default.bold("Diff vs current pool content:"));
|
|
92622
93053
|
console.log(diffSummary(currentDir, files));
|
|
@@ -92650,19 +93081,19 @@ import {
|
|
|
92650
93081
|
closeSync as closeSync17,
|
|
92651
93082
|
existsSync as existsSync93,
|
|
92652
93083
|
lstatSync as lstatSync12,
|
|
92653
|
-
mkdirSync as
|
|
93084
|
+
mkdirSync as mkdirSync53,
|
|
92654
93085
|
mkdtempSync as mkdtempSync6,
|
|
92655
93086
|
openSync as openSync17,
|
|
92656
|
-
readFileSync as
|
|
93087
|
+
readFileSync as readFileSync80,
|
|
92657
93088
|
readdirSync as readdirSync34,
|
|
92658
|
-
renameSync as
|
|
93089
|
+
renameSync as renameSync24,
|
|
92659
93090
|
rmSync as rmSync19,
|
|
92660
93091
|
statSync as statSync50,
|
|
92661
93092
|
utimesSync,
|
|
92662
|
-
writeFileSync as
|
|
93093
|
+
writeFileSync as writeFileSync34
|
|
92663
93094
|
} from "node:fs";
|
|
92664
|
-
import { dirname as dirname32, join as
|
|
92665
|
-
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";
|
|
92666
93097
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
92667
93098
|
init_helpers();
|
|
92668
93099
|
init_agent_config();
|
|
@@ -92673,10 +93104,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
92673
93104
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
92674
93105
|
function resolveConfigSkillsDir(agent) {
|
|
92675
93106
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
92676
|
-
const candidate = override ? resolve55(override) :
|
|
93107
|
+
const candidate = override ? resolve55(override) : join91(homedir52(), ".switchroom-config");
|
|
92677
93108
|
if (!existsSync93(candidate))
|
|
92678
93109
|
return null;
|
|
92679
|
-
return
|
|
93110
|
+
return join91(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
92680
93111
|
}
|
|
92681
93112
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
92682
93113
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
@@ -92694,7 +93125,7 @@ function sweepMirrorPriors(configSkillsRoot) {
|
|
|
92694
93125
|
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
92695
93126
|
continue;
|
|
92696
93127
|
try {
|
|
92697
|
-
rmSync19(
|
|
93128
|
+
rmSync19(join91(configSkillsRoot, ent), { recursive: true, force: true });
|
|
92698
93129
|
} catch {}
|
|
92699
93130
|
}
|
|
92700
93131
|
} catch {}
|
|
@@ -92703,7 +93134,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92703
93134
|
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
92704
93135
|
if (!configSkillsRoot)
|
|
92705
93136
|
return;
|
|
92706
|
-
const dest =
|
|
93137
|
+
const dest = join91(configSkillsRoot, name);
|
|
92707
93138
|
try {
|
|
92708
93139
|
if (liveSkillDir !== null) {
|
|
92709
93140
|
try {
|
|
@@ -92718,34 +93149,34 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
92718
93149
|
if (liveSkillDir === null) {
|
|
92719
93150
|
sweepMirrorPriors(configSkillsRoot);
|
|
92720
93151
|
if (existsSync93(dest)) {
|
|
92721
|
-
const trash =
|
|
92722
|
-
|
|
93152
|
+
const trash = join91(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
93153
|
+
renameSync24(dest, trash);
|
|
92723
93154
|
}
|
|
92724
93155
|
return;
|
|
92725
93156
|
}
|
|
92726
|
-
|
|
93157
|
+
mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
|
|
92727
93158
|
sweepMirrorPriors(configSkillsRoot);
|
|
92728
|
-
const staging = mkdtempSync6(
|
|
93159
|
+
const staging = mkdtempSync6(join91(configSkillsRoot, `.${name}-staging-`));
|
|
92729
93160
|
const walk2 = (src, dst) => {
|
|
92730
|
-
|
|
93161
|
+
mkdirSync53(dst, { recursive: true, mode: 493 });
|
|
92731
93162
|
for (const ent of readdirSync34(src, { withFileTypes: true })) {
|
|
92732
|
-
const s =
|
|
92733
|
-
const d =
|
|
93163
|
+
const s = join91(src, ent.name);
|
|
93164
|
+
const d = join91(dst, ent.name);
|
|
92734
93165
|
if (ent.isSymbolicLink())
|
|
92735
93166
|
continue;
|
|
92736
93167
|
if (ent.isDirectory())
|
|
92737
93168
|
walk2(s, d);
|
|
92738
93169
|
else if (ent.isFile()) {
|
|
92739
|
-
|
|
93170
|
+
writeFileSync34(d, readFileSync80(s));
|
|
92740
93171
|
}
|
|
92741
93172
|
}
|
|
92742
93173
|
};
|
|
92743
93174
|
walk2(liveSkillDir, staging);
|
|
92744
93175
|
if (existsSync93(dest)) {
|
|
92745
|
-
const prior =
|
|
92746
|
-
|
|
93176
|
+
const prior = join91(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
93177
|
+
renameSync24(dest, prior);
|
|
92747
93178
|
}
|
|
92748
|
-
|
|
93179
|
+
renameSync24(staging, dest);
|
|
92749
93180
|
} catch (err2) {
|
|
92750
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.
|
|
92751
93182
|
`));
|
|
@@ -92772,16 +93203,16 @@ function resolveAgent(opts) {
|
|
|
92772
93203
|
function resolveAgentsRoot(opts) {
|
|
92773
93204
|
if (opts.root)
|
|
92774
93205
|
return resolve55(opts.root);
|
|
92775
|
-
return
|
|
93206
|
+
return join91(homedir52(), ".switchroom", "agents");
|
|
92776
93207
|
}
|
|
92777
93208
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
92778
|
-
return
|
|
93209
|
+
return join91(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
92779
93210
|
}
|
|
92780
93211
|
function trashDir(agentsRoot, agent) {
|
|
92781
|
-
return
|
|
93212
|
+
return join91(agentsRoot, agent, ".claude", TRASH_DIRNAME);
|
|
92782
93213
|
}
|
|
92783
93214
|
function countPersonalSkills(agentsRoot, agent) {
|
|
92784
|
-
const skillsDir =
|
|
93215
|
+
const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
|
|
92785
93216
|
if (!existsSync93(skillsDir))
|
|
92786
93217
|
return 0;
|
|
92787
93218
|
let n = 0;
|
|
@@ -92819,7 +93250,7 @@ function loadFromDir2(dir) {
|
|
|
92819
93250
|
const files = {};
|
|
92820
93251
|
const walk2 = (sub) => {
|
|
92821
93252
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
92822
|
-
const full =
|
|
93253
|
+
const full = join91(sub, ent.name);
|
|
92823
93254
|
if (ent.isSymbolicLink()) {
|
|
92824
93255
|
fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
|
|
92825
93256
|
}
|
|
@@ -92829,7 +93260,7 @@ function loadFromDir2(dir) {
|
|
|
92829
93260
|
}
|
|
92830
93261
|
if (ent.isFile()) {
|
|
92831
93262
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
92832
|
-
files[rel] =
|
|
93263
|
+
files[rel] = readFileSync80(full, "utf-8");
|
|
92833
93264
|
}
|
|
92834
93265
|
}
|
|
92835
93266
|
};
|
|
@@ -92872,10 +93303,10 @@ function behavioralValidate(files) {
|
|
|
92872
93303
|
errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
92873
93304
|
}
|
|
92874
93305
|
} else if (PY_SCRIPT_RE.test(path9)) {
|
|
92875
|
-
const tmp = mkdtempSync6(
|
|
92876
|
-
const tmpPy =
|
|
93306
|
+
const tmp = mkdtempSync6(join91(tmpdir7(), "skill-personal-py-"));
|
|
93307
|
+
const tmpPy = join91(tmp, "check.py");
|
|
92877
93308
|
try {
|
|
92878
|
-
|
|
93309
|
+
writeFileSync34(tmpPy, content);
|
|
92879
93310
|
const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
|
|
92880
93311
|
encoding: "utf-8"
|
|
92881
93312
|
});
|
|
@@ -92897,7 +93328,7 @@ function sweepTrash(agentsRoot, agent) {
|
|
|
92897
93328
|
for (const ent of readdirSync34(trash, { withFileTypes: true })) {
|
|
92898
93329
|
if (!ent.isDirectory())
|
|
92899
93330
|
continue;
|
|
92900
|
-
const entPath =
|
|
93331
|
+
const entPath = join91(trash, ent.name);
|
|
92901
93332
|
try {
|
|
92902
93333
|
const st = statSync50(entPath);
|
|
92903
93334
|
if (now - st.mtimeMs > TRASH_TTL_MS) {
|
|
@@ -92917,16 +93348,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92917
93348
|
if (targetIsSymlink) {
|
|
92918
93349
|
fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
92919
93350
|
}
|
|
92920
|
-
|
|
92921
|
-
const staging = mkdtempSync6(
|
|
93351
|
+
mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
|
|
93352
|
+
const staging = mkdtempSync6(join91(dirname32(targetDir), `.skill-personal-stage-`));
|
|
92922
93353
|
let oldRename = null;
|
|
92923
93354
|
try {
|
|
92924
93355
|
for (const [path9, content] of Object.entries(files)) {
|
|
92925
|
-
const full =
|
|
92926
|
-
|
|
93356
|
+
const full = join91(staging, path9);
|
|
93357
|
+
mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
|
|
92927
93358
|
const fd = openSync17(full, "wx");
|
|
92928
93359
|
try {
|
|
92929
|
-
|
|
93360
|
+
writeFileSync34(fd, content);
|
|
92930
93361
|
} finally {
|
|
92931
93362
|
closeSync17(fd);
|
|
92932
93363
|
}
|
|
@@ -92942,9 +93373,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92942
93373
|
} catch {}
|
|
92943
93374
|
if (targetExists) {
|
|
92944
93375
|
oldRename = `${targetDir}.personal-old-${Date.now()}`;
|
|
92945
|
-
|
|
93376
|
+
renameSync24(targetDir, oldRename);
|
|
92946
93377
|
}
|
|
92947
|
-
|
|
93378
|
+
renameSync24(staging, targetDir);
|
|
92948
93379
|
if (oldRename) {
|
|
92949
93380
|
rmSync19(oldRename, { recursive: true, force: true });
|
|
92950
93381
|
oldRename = null;
|
|
@@ -92958,7 +93389,7 @@ function writePersonalSkill(targetDir, files) {
|
|
|
92958
93389
|
if (existsSync93(targetDir)) {
|
|
92959
93390
|
rmSync19(targetDir, { recursive: true, force: true });
|
|
92960
93391
|
}
|
|
92961
|
-
|
|
93392
|
+
renameSync24(oldRename, targetDir);
|
|
92962
93393
|
} catch {}
|
|
92963
93394
|
}
|
|
92964
93395
|
throw err2;
|
|
@@ -93029,7 +93460,7 @@ function loadFiles(opts) {
|
|
|
93029
93460
|
return loadFromDir2(p);
|
|
93030
93461
|
}
|
|
93031
93462
|
if (p.endsWith(".md")) {
|
|
93032
|
-
return { "SKILL.md":
|
|
93463
|
+
return { "SKILL.md": readFileSync80(p, "utf-8") };
|
|
93033
93464
|
}
|
|
93034
93465
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
93035
93466
|
}
|
|
@@ -93069,10 +93500,10 @@ function editPersonalAction(name, opts) {
|
|
|
93069
93500
|
}
|
|
93070
93501
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
93071
93502
|
function defaultSharedRoot() {
|
|
93072
|
-
return
|
|
93503
|
+
return join91(homedir52(), ".switchroom", "skills");
|
|
93073
93504
|
}
|
|
93074
93505
|
function defaultBundledRoot() {
|
|
93075
|
-
return
|
|
93506
|
+
return join91(homedir52(), ".switchroom", "skills", "_bundled");
|
|
93076
93507
|
}
|
|
93077
93508
|
function resolveCloneSource(source, opts) {
|
|
93078
93509
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -93082,7 +93513,7 @@ function resolveCloneSource(source, opts) {
|
|
|
93082
93513
|
const tier = m[1];
|
|
93083
93514
|
const slug = m[2];
|
|
93084
93515
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
93085
|
-
const dir =
|
|
93516
|
+
const dir = join91(root, slug);
|
|
93086
93517
|
if (!existsSync93(dir)) {
|
|
93087
93518
|
fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
93088
93519
|
}
|
|
@@ -93098,7 +93529,7 @@ function readSourceFiles(dir) {
|
|
|
93098
93529
|
const skipped = [];
|
|
93099
93530
|
const walk2 = (sub) => {
|
|
93100
93531
|
for (const ent of readdirSync34(sub, { withFileTypes: true })) {
|
|
93101
|
-
const full =
|
|
93532
|
+
const full = join91(sub, ent.name);
|
|
93102
93533
|
if (ent.isSymbolicLink()) {
|
|
93103
93534
|
continue;
|
|
93104
93535
|
}
|
|
@@ -93118,7 +93549,7 @@ function readSourceFiles(dir) {
|
|
|
93118
93549
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
93119
93550
|
}
|
|
93120
93551
|
} catch {}
|
|
93121
|
-
files[rel] =
|
|
93552
|
+
files[rel] = readFileSync80(full, "utf-8");
|
|
93122
93553
|
}
|
|
93123
93554
|
}
|
|
93124
93555
|
};
|
|
@@ -93207,10 +93638,10 @@ function removePersonalAction(name, opts) {
|
|
|
93207
93638
|
throw err2;
|
|
93208
93639
|
}
|
|
93209
93640
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
93210
|
-
|
|
93641
|
+
mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
|
|
93211
93642
|
const ts = Date.now();
|
|
93212
|
-
const trashTarget =
|
|
93213
|
-
|
|
93643
|
+
const trashTarget = join91(trashRoot2, `${name}-${ts}`);
|
|
93644
|
+
renameSync24(target, trashTarget);
|
|
93214
93645
|
const now = new Date(ts);
|
|
93215
93646
|
utimesSync(trashTarget, now, now);
|
|
93216
93647
|
mirrorToConfigRepo(agent, name, null);
|
|
@@ -93228,7 +93659,7 @@ function listPersonalAction(opts) {
|
|
|
93228
93659
|
const agent = resolveAgent(opts);
|
|
93229
93660
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
93230
93661
|
sweepTrash(agentsRoot, agent);
|
|
93231
|
-
const skillsDir =
|
|
93662
|
+
const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
|
|
93232
93663
|
const personal = [];
|
|
93233
93664
|
if (existsSync93(skillsDir)) {
|
|
93234
93665
|
for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
|
|
@@ -93237,7 +93668,7 @@ function listPersonalAction(opts) {
|
|
|
93237
93668
|
if (!ent.name.startsWith(PERSONAL_PREFIX))
|
|
93238
93669
|
continue;
|
|
93239
93670
|
const skillName = ent.name.slice(PERSONAL_PREFIX.length);
|
|
93240
|
-
const skillPath =
|
|
93671
|
+
const skillPath = join91(skillsDir, ent.name);
|
|
93241
93672
|
let fileCount = 0;
|
|
93242
93673
|
let totalBytes = 0;
|
|
93243
93674
|
const walk2 = (sub) => {
|
|
@@ -93245,10 +93676,10 @@ function listPersonalAction(opts) {
|
|
|
93245
93676
|
if (e.isFile()) {
|
|
93246
93677
|
fileCount += 1;
|
|
93247
93678
|
try {
|
|
93248
|
-
totalBytes += statSync50(
|
|
93679
|
+
totalBytes += statSync50(join91(sub, e.name)).size;
|
|
93249
93680
|
} catch {}
|
|
93250
93681
|
} else if (e.isDirectory()) {
|
|
93251
|
-
walk2(
|
|
93682
|
+
walk2(join91(sub, e.name));
|
|
93252
93683
|
}
|
|
93253
93684
|
}
|
|
93254
93685
|
};
|
|
@@ -93287,11 +93718,11 @@ function registerSkillPersonalCommands(program3) {
|
|
|
93287
93718
|
// src/cli/self-improve-propose-skill.ts
|
|
93288
93719
|
import { createConnection as createConnection4 } from "node:net";
|
|
93289
93720
|
import { homedir as homedir53 } from "node:os";
|
|
93290
|
-
import { join as
|
|
93291
|
-
import { readFileSync as
|
|
93721
|
+
import { join as join92 } from "node:path";
|
|
93722
|
+
import { readFileSync as readFileSync81 } from "node:fs";
|
|
93292
93723
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
93293
93724
|
function gatewaySocketPath() {
|
|
93294
|
-
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"));
|
|
93295
93726
|
}
|
|
93296
93727
|
function fail5(msg, code = 1) {
|
|
93297
93728
|
console.error(msg);
|
|
@@ -93326,7 +93757,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93326
93757
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
93327
93758
|
let draft;
|
|
93328
93759
|
try {
|
|
93329
|
-
draft = JSON.parse(
|
|
93760
|
+
draft = JSON.parse(readFileSync81(opts.draft, "utf-8"));
|
|
93330
93761
|
} catch (e) {
|
|
93331
93762
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
93332
93763
|
}
|
|
@@ -93363,9 +93794,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
93363
93794
|
init_esm();
|
|
93364
93795
|
init_helpers();
|
|
93365
93796
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
93366
|
-
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";
|
|
93367
93798
|
import { homedir as homedir54 } from "node:os";
|
|
93368
|
-
import { join as
|
|
93799
|
+
import { join as join93, resolve as resolve56 } from "node:path";
|
|
93369
93800
|
var PERSONAL_PREFIX2 = "personal-";
|
|
93370
93801
|
var BUNDLED_SUBDIR = "_bundled";
|
|
93371
93802
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
@@ -93379,12 +93810,12 @@ function defaultBundledRoot2() {
|
|
|
93379
93810
|
return resolve56(homedir54(), ".switchroom/skills/_bundled");
|
|
93380
93811
|
}
|
|
93381
93812
|
function readSkillFrontmatter(skillDir) {
|
|
93382
|
-
const mdPath =
|
|
93813
|
+
const mdPath = join93(skillDir, "SKILL.md");
|
|
93383
93814
|
if (!existsSync94(mdPath))
|
|
93384
93815
|
return null;
|
|
93385
93816
|
let content;
|
|
93386
93817
|
try {
|
|
93387
|
-
content =
|
|
93818
|
+
content = readFileSync82(mdPath, "utf-8");
|
|
93388
93819
|
} catch {
|
|
93389
93820
|
return null;
|
|
93390
93821
|
}
|
|
@@ -93412,7 +93843,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
93412
93843
|
return { fm: parsed };
|
|
93413
93844
|
}
|
|
93414
93845
|
function statSkillMd(skillDir) {
|
|
93415
|
-
const mdPath =
|
|
93846
|
+
const mdPath = join93(skillDir, "SKILL.md");
|
|
93416
93847
|
try {
|
|
93417
93848
|
const st = statSync51(mdPath);
|
|
93418
93849
|
return { size: st.size, mtime: st.mtime.toISOString() };
|
|
@@ -93423,7 +93854,7 @@ function statSkillMd(skillDir) {
|
|
|
93423
93854
|
function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
93424
93855
|
if (!AGENT_NAME_RE3.test(agent))
|
|
93425
93856
|
return [];
|
|
93426
|
-
const skillsDir =
|
|
93857
|
+
const skillsDir = join93(agentsRoot, agent, ".claude/skills");
|
|
93427
93858
|
if (!existsSync94(skillsDir))
|
|
93428
93859
|
return [];
|
|
93429
93860
|
const out = [];
|
|
@@ -93436,7 +93867,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
93436
93867
|
for (const ent of entries) {
|
|
93437
93868
|
if (!ent.startsWith(PERSONAL_PREFIX2))
|
|
93438
93869
|
continue;
|
|
93439
|
-
const dirPath =
|
|
93870
|
+
const dirPath = join93(skillsDir, ent);
|
|
93440
93871
|
try {
|
|
93441
93872
|
if (!statSync51(dirPath).isDirectory())
|
|
93442
93873
|
continue;
|
|
@@ -93476,7 +93907,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
93476
93907
|
continue;
|
|
93477
93908
|
if (ent.startsWith("."))
|
|
93478
93909
|
continue;
|
|
93479
|
-
const dirPath =
|
|
93910
|
+
const dirPath = join93(sharedRoot, ent);
|
|
93480
93911
|
try {
|
|
93481
93912
|
if (!statSync51(dirPath).isDirectory())
|
|
93482
93913
|
continue;
|
|
@@ -93512,7 +93943,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
|
93512
93943
|
for (const ent of entries) {
|
|
93513
93944
|
if (ent.startsWith("."))
|
|
93514
93945
|
continue;
|
|
93515
|
-
const dirPath =
|
|
93946
|
+
const dirPath = join93(bundledRoot, ent);
|
|
93516
93947
|
try {
|
|
93517
93948
|
if (!statSync51(dirPath).isDirectory())
|
|
93518
93949
|
continue;
|
|
@@ -93659,17 +94090,17 @@ init_helpers();
|
|
|
93659
94090
|
init_operator_uid();
|
|
93660
94091
|
import {
|
|
93661
94092
|
existsSync as existsSync96,
|
|
93662
|
-
mkdirSync as
|
|
94093
|
+
mkdirSync as mkdirSync54,
|
|
93663
94094
|
readdirSync as readdirSync36,
|
|
93664
|
-
readFileSync as
|
|
93665
|
-
writeFileSync as
|
|
94095
|
+
readFileSync as readFileSync84,
|
|
94096
|
+
writeFileSync as writeFileSync35,
|
|
93666
94097
|
statSync as statSync52,
|
|
93667
94098
|
lstatSync as lstatSync13,
|
|
93668
94099
|
realpathSync as realpathSync8,
|
|
93669
94100
|
copyFileSync as copyFileSync13
|
|
93670
94101
|
} from "node:fs";
|
|
93671
94102
|
import { homedir as homedir55 } from "node:os";
|
|
93672
|
-
import { join as
|
|
94103
|
+
import { join as join94 } from "node:path";
|
|
93673
94104
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
93674
94105
|
|
|
93675
94106
|
// src/cli/singleton-stale-cleanup.ts
|
|
@@ -94018,7 +94449,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
|
|
|
94018
94449
|
return resolved;
|
|
94019
94450
|
}
|
|
94020
94451
|
function resolveHostdSkillsTarget(hostHome) {
|
|
94021
|
-
const skillsPath =
|
|
94452
|
+
const skillsPath = join94(hostHome, ".switchroom", "skills");
|
|
94022
94453
|
let st;
|
|
94023
94454
|
try {
|
|
94024
94455
|
st = lstatSync13(skillsPath);
|
|
@@ -94042,10 +94473,10 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
94042
94473
|
return target;
|
|
94043
94474
|
}
|
|
94044
94475
|
function hostdDir() {
|
|
94045
|
-
return
|
|
94476
|
+
return join94(homedir55(), ".switchroom", "hostd");
|
|
94046
94477
|
}
|
|
94047
94478
|
function hostdComposePath() {
|
|
94048
|
-
return
|
|
94479
|
+
return join94(hostdDir(), "docker-compose.yml");
|
|
94049
94480
|
}
|
|
94050
94481
|
function backupExistingCompose() {
|
|
94051
94482
|
const p = hostdComposePath();
|
|
@@ -94082,7 +94513,7 @@ async function doInstall(opts, program3) {
|
|
|
94082
94513
|
}
|
|
94083
94514
|
const dir = hostdDir();
|
|
94084
94515
|
const composePath = hostdComposePath();
|
|
94085
|
-
|
|
94516
|
+
mkdirSync54(dir, { recursive: true });
|
|
94086
94517
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
94087
94518
|
const guard = checkDowngrade({
|
|
94088
94519
|
container: "switchroom-hostd",
|
|
@@ -94115,7 +94546,7 @@ async function doInstall(opts, program3) {
|
|
|
94115
94546
|
const bak = backupExistingCompose();
|
|
94116
94547
|
if (bak)
|
|
94117
94548
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94118
|
-
|
|
94549
|
+
writeFileSync35(composePath, yaml, "utf8");
|
|
94119
94550
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94120
94551
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
94121
94552
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -94174,7 +94605,7 @@ function doStatus() {
|
|
|
94174
94605
|
for (const name of readdirSync36(dir)) {
|
|
94175
94606
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
94176
94607
|
continue;
|
|
94177
|
-
const sockPath =
|
|
94608
|
+
const sockPath = join94(dir, name, "sock");
|
|
94178
94609
|
if (existsSync96(sockPath)) {
|
|
94179
94610
|
const st = statSync52(sockPath);
|
|
94180
94611
|
if ((st.mode & 61440) === 49152) {
|
|
@@ -94223,7 +94654,7 @@ function registerHostdCommand(program3) {
|
|
|
94223
94654
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
94224
94655
|
return;
|
|
94225
94656
|
}
|
|
94226
|
-
const raw =
|
|
94657
|
+
const raw = readFileSync84(logPath, "utf-8");
|
|
94227
94658
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
94228
94659
|
const filters = {
|
|
94229
94660
|
agent: opts.agent,
|
|
@@ -94266,9 +94697,9 @@ The log is created when hostd handles its first privileged-verb request.`));
|
|
|
94266
94697
|
init_source();
|
|
94267
94698
|
init_helpers();
|
|
94268
94699
|
init_operator_uid();
|
|
94269
|
-
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";
|
|
94270
94701
|
import { homedir as homedir56 } from "node:os";
|
|
94271
|
-
import { join as
|
|
94702
|
+
import { join as join95 } from "node:path";
|
|
94272
94703
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
94273
94704
|
function resolveWebImageTag(explicitTag, release) {
|
|
94274
94705
|
if (explicitTag)
|
|
@@ -94287,7 +94718,7 @@ function resolveWebHostHome(env2 = process.env, home2 = homedir56()) {
|
|
|
94287
94718
|
}
|
|
94288
94719
|
var WEB_COMPOSE_PROJECT = "switchroom-web";
|
|
94289
94720
|
function renderWebComposeFile(opts) {
|
|
94290
|
-
const { hostHome, imageTag, operatorUid } = opts;
|
|
94721
|
+
const { hostHome, imageTag, operatorUid, port } = opts;
|
|
94291
94722
|
return `# AUTO-GENERATED by \`switchroom webd install\` \u2014 do not hand-edit.
|
|
94292
94723
|
# Edits land at \`switchroom webd install\` time; backed up to
|
|
94293
94724
|
# docker-compose.yml.bak-<ts> on overwrite.
|
|
@@ -94302,13 +94733,18 @@ services:
|
|
|
94302
94733
|
image: ghcr.io/switchroom/switchroom-web:${imageTag}
|
|
94303
94734
|
container_name: switchroom-web
|
|
94304
94735
|
restart: always
|
|
94305
|
-
# 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
|
|
94306
94737
|
# tunnel (GitHub webhooks) and \`tailscale serve\` (dashboard) both
|
|
94307
94738
|
# reach it there, and the dashboard CSRF gate trusts the
|
|
94308
94739
|
# Tailscale-User-Login header only when the request arrives on
|
|
94309
94740
|
# loopback (PR #1380). Host networking makes the container's
|
|
94310
94741
|
# 127.0.0.1 the host loopback, so both keep working unchanged.
|
|
94311
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"]
|
|
94312
94748
|
# The webhook.sock forward is peercred-gated by each agent's gateway
|
|
94313
94749
|
# to {agent uid, SWITCHROOM_WEBHOOK_RECEIVER_UID = operator uid}.
|
|
94314
94750
|
# Run as any other uid \u2192 every forward is 503'd. Operator uid also
|
|
@@ -94363,10 +94799,10 @@ services:
|
|
|
94363
94799
|
`;
|
|
94364
94800
|
}
|
|
94365
94801
|
function webdDir() {
|
|
94366
|
-
return
|
|
94802
|
+
return join95(homedir56(), ".switchroom", "web");
|
|
94367
94803
|
}
|
|
94368
94804
|
function webdComposePath() {
|
|
94369
|
-
return
|
|
94805
|
+
return join95(webdDir(), "docker-compose.yml");
|
|
94370
94806
|
}
|
|
94371
94807
|
function backupExistingCompose2() {
|
|
94372
94808
|
const p = webdComposePath();
|
|
@@ -94395,9 +94831,10 @@ async function doInstall2(opts, program3) {
|
|
|
94395
94831
|
}
|
|
94396
94832
|
const dir = webdDir();
|
|
94397
94833
|
const composePath = webdComposePath();
|
|
94398
|
-
|
|
94834
|
+
mkdirSync55(dir, { recursive: true });
|
|
94399
94835
|
const cfg = getConfig(program3);
|
|
94400
94836
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
94837
|
+
const port = cfg.web_service?.port ?? 8080;
|
|
94401
94838
|
const guard = checkDowngrade({
|
|
94402
94839
|
container: "switchroom-web",
|
|
94403
94840
|
targetTag: imageTag,
|
|
@@ -94417,7 +94854,8 @@ async function doInstall2(opts, program3) {
|
|
|
94417
94854
|
const yaml = renderWebComposeFile({
|
|
94418
94855
|
hostHome,
|
|
94419
94856
|
imageTag,
|
|
94420
|
-
operatorUid
|
|
94857
|
+
operatorUid,
|
|
94858
|
+
port
|
|
94421
94859
|
});
|
|
94422
94860
|
if (opts.dryRun) {
|
|
94423
94861
|
console.log(source_default.dim(`# Would write: ${composePath}`));
|
|
@@ -94428,7 +94866,7 @@ async function doInstall2(opts, program3) {
|
|
|
94428
94866
|
const bak = backupExistingCompose2();
|
|
94429
94867
|
if (bak)
|
|
94430
94868
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
94431
|
-
|
|
94869
|
+
writeFileSync36(composePath, yaml, "utf8");
|
|
94432
94870
|
try {
|
|
94433
94871
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
94434
94872
|
chownSync9(dir, operatorUid, operatorUid);
|
|
@@ -94438,7 +94876,7 @@ async function doInstall2(opts, program3) {
|
|
|
94438
94876
|
}
|
|
94439
94877
|
} catch {}
|
|
94440
94878
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
94441
|
-
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}`));
|
|
94442
94880
|
console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
|
|
94443
94881
|
const pull = runDocker2(["compose", "-p", WEB_COMPOSE_PROJECT, "-f", composePath, "pull"]);
|
|
94444
94882
|
if (!pull.ok) {
|
|
@@ -94461,7 +94899,8 @@ ${up.stderr}`));
|
|
|
94461
94899
|
console.log(source_default.dim(` Logs: docker logs switchroom-web --tail 50`));
|
|
94462
94900
|
console.log(source_default.dim(` Verify: switchroom webd status`));
|
|
94463
94901
|
console.log(source_default.yellow(` NOTE: the switchroom-web.service systemd unit (if still installed) also
|
|
94464
|
-
` + ` 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):
|
|
94465
94904
|
` + ` systemctl --user stop switchroom-web.service
|
|
94466
94905
|
` + ` systemctl --user disable switchroom-web.service`));
|
|
94467
94906
|
}
|
|
@@ -94523,9 +94962,9 @@ function registerWebdCommand(program3) {
|
|
|
94523
94962
|
// src/cli/host-repair.ts
|
|
94524
94963
|
init_source();
|
|
94525
94964
|
import { homedir as homedir57 } from "node:os";
|
|
94526
|
-
import { join as
|
|
94965
|
+
import { join as join96 } from "node:path";
|
|
94527
94966
|
var ARTIFACT_ALLOWLIST = {
|
|
94528
|
-
dockerComposePluginDir: (home2) =>
|
|
94967
|
+
dockerComposePluginDir: (home2) => join96(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
94529
94968
|
stateSentinel: "/state"
|
|
94530
94969
|
};
|
|
94531
94970
|
function isStateBogusAutoDir(probe2) {
|
|
@@ -95016,6 +95455,7 @@ registerDriveCommand(program3);
|
|
|
95016
95455
|
registerDriveMcpLauncherCommand(program3);
|
|
95017
95456
|
registerM365McpLauncherCommand(program3);
|
|
95018
95457
|
registerNotionMcpLauncherCommand(program3);
|
|
95458
|
+
registerHindsightMcpShimCommand(program3);
|
|
95019
95459
|
registerDeliverFileCommand(program3);
|
|
95020
95460
|
registerNotionCommand(program3);
|
|
95021
95461
|
registerApplyCommand(program3);
|