switchroom 0.16.38 → 0.16.47
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 +8 -2
- package/dist/auth-broker/index.js +7 -1
- package/dist/cli/notion-write-pretool.mjs +7 -1
- package/dist/cli/switchroom.js +1259 -375
- package/dist/cli/ui/index.html +877 -214
- package/dist/host-control/main.js +116 -84
- package/dist/vault/approvals/kernel-server.js +8 -2
- package/dist/vault/broker/server.js +8 -2
- package/examples/minimal.yaml +1 -1
- package/examples/switchroom.yaml +1 -1
- package/package.json +2 -2
- package/profiles/_shared/reply-discipline.md.hbs +9 -0
- package/skills/switchroom-status/SKILL.md +1 -1
- package/telegram-plugin/bridge/bridge.ts +2 -1
- package/telegram-plugin/card-format.ts +7 -1
- package/telegram-plugin/dist/bridge/bridge.js +20 -2
- package/telegram-plugin/dist/gateway/gateway.js +2197 -964
- package/telegram-plugin/dist/server.js +20 -2
- package/telegram-plugin/format.ts +305 -31
- package/telegram-plugin/gateway/gateway.ts +310 -70
- package/telegram-plugin/gateway/model-command.ts +173 -19
- package/telegram-plugin/hooks/tool-label-pretool.d.mts +12 -0
- package/telegram-plugin/hooks/tool-label-pretool.mjs +54 -16
- package/telegram-plugin/package.json +1 -1
- package/telegram-plugin/session-tail.ts +47 -1
- package/telegram-plugin/stream-reply-handler.ts +19 -1
- package/telegram-plugin/tests/always-allow-grant.test.ts +34 -2
- package/telegram-plugin/tests/card-format.test.ts +28 -0
- package/telegram-plugin/tests/claude-code-event-contract.test.ts +151 -0
- package/telegram-plugin/tests/format-consistency.test.ts +223 -0
- package/telegram-plugin/tests/formatting-parse-regression.test.ts +272 -0
- package/telegram-plugin/tests/formatting-torture-set.ts +218 -0
- package/telegram-plugin/tests/model-command.test.ts +213 -47
- package/telegram-plugin/tests/paragraph-normalizer.test.ts +203 -21
- package/telegram-plugin/tests/rich-markdown-oracle.ts +469 -0
- package/telegram-plugin/tests/session-tail.test.ts +91 -0
- package/telegram-plugin/tests/status-vocabulary-unification.test.ts +125 -0
- package/telegram-plugin/tests/telegram-format.test.ts +33 -8
- package/telegram-plugin/tests/text-voice-scrub.test.ts +142 -22
- package/telegram-plugin/tests/tool-activity-summary.test.ts +6 -1
- package/telegram-plugin/tests/tts-normalize.test.ts +242 -0
- package/telegram-plugin/tests/vault-request-access-tool.test.ts +24 -0
- package/telegram-plugin/tests/vault-request-access-unlock-resume.test.ts +46 -0
- package/telegram-plugin/tests/voice-ondemand.test.ts +99 -2
- package/telegram-plugin/tests/voice-presynth.test.ts +437 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +49 -0
- package/telegram-plugin/text-voice-scrub.ts +68 -18
- package/telegram-plugin/tool-activity-summary.ts +20 -108
- package/telegram-plugin/tts-normalize.ts +377 -0
- package/telegram-plugin/uat/driver.ts +472 -22
- package/telegram-plugin/uat/scenarios/jtbd-model-litellm-sr-dm.test.ts +34 -14
- package/telegram-plugin/uat/scenarios/jtbd-multipart-render-dm.test.ts +169 -0
- package/telegram-plugin/uat/scenarios/jtbd-narration-intent-dm.test.ts +134 -0
- package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +254 -0
- package/telegram-plugin/uat/scenarios/jtbd-status-phase-transitions-dm.test.ts +109 -0
- package/telegram-plugin/uat/uat-driver.test.ts +297 -0
- package/telegram-plugin/voice-ondemand.ts +161 -10
- package/telegram-plugin/voice-presynth.ts +242 -0
- package/telegram-plugin/worker-activity-feed.ts +9 -1
package/dist/cli/switchroom.js
CHANGED
|
@@ -13542,7 +13542,7 @@ var init_zod = __esm(() => {
|
|
|
13542
13542
|
function isValidTimezone(value) {
|
|
13543
13543
|
return TIMEZONE_REGEX.test(value);
|
|
13544
13544
|
}
|
|
13545
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13545
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13546
13546
|
var init_schema = __esm(() => {
|
|
13547
13547
|
init_zod();
|
|
13548
13548
|
CodeRepoEntrySchema = exports_external.object({
|
|
@@ -14005,7 +14005,7 @@ var init_schema = __esm(() => {
|
|
|
14005
14005
|
secrets: exports_external.array(exports_external.string().regex(/^[a-zA-Z0-9_\-/]+$/, "Secret key names must contain only alphanumeric characters, underscores, hyphens, and forward slashes")).optional(),
|
|
14006
14006
|
reactions: ReactionsSchema,
|
|
14007
14007
|
reaction_dispatch: ReactionDispatchSchema,
|
|
14008
|
-
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-
|
|
14008
|
+
model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only (no spaces or shell specials)").optional().describe("Claude model override (e.g., 'claude-sonnet-5')"),
|
|
14009
14009
|
thinking_effort: exports_external.enum(["low", "medium", "high", "xhigh", "max"]).optional().describe("Adaptive-thinking effort level passed as --effort to the claude CLI. " + "Per-agent override wins over defaults.thinking_effort. " + "lower = faster/cheaper, higher = more reasoning. Omit to use Claude's default."),
|
|
14010
14010
|
permission_mode: exports_external.enum(["acceptEdits", "auto", "bypassPermissions", "default", "dontAsk", "plan"]).optional().describe("Permission mode passed as --permission-mode to the claude CLI. " + "Per-agent override wins over defaults.permission_mode. " + "Warning: bypassPermissions and dontAsk skip all safety checks \u2014 use only in trusted sandboxes."),
|
|
14011
14011
|
fallback_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Fallback model name must be alphanumeric with ._-/[]: only").optional().describe("Fallback model passed as --fallback-model to the claude CLI. " + "Per-agent override wins over defaults.fallback_model. " + "Used when the primary model is overloaded. Note: only functional in --print (non-interactive) mode per Claude CLI docs; silently no-ops in interactive sessions."),
|
|
@@ -14136,6 +14136,11 @@ var init_schema = __esm(() => {
|
|
|
14136
14136
|
WebServiceConfigSchema = exports_external.object({
|
|
14137
14137
|
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.")
|
|
14138
14138
|
});
|
|
14139
|
+
FleetHealthConfigSchema = exports_external.object({
|
|
14140
|
+
owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
|
|
14141
|
+
message: "owner_agent must match the standard agent-name pattern"
|
|
14142
|
+
}).optional().describe("The admin agent that runs the Fleet Health sensor + deep-dive crons " + "(RFC fleet-health.md). Default unset \u2192 the feature is inert: no crons " + "are scheduled and the admin page renders its empty state. The named " + "agent must be admin: true. A dedicated owner (not any admin) keeps " + "the fleet-health work attributable, its memory scoped, and its token " + "spend accountable. The detection runs ONLY as operator-set schedules " + "on this agent \u2014 never a self-authored loop (on-leash, " + "no-self-escalation).")
|
|
14143
|
+
});
|
|
14139
14144
|
HostdConfigSchema = exports_external.object({
|
|
14140
14145
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit \u00a73). Default false \u2014 the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
14141
14146
|
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit \u00a75). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card \u2014 so a looping agent is throttled rather than spamming the chat.")
|
|
@@ -14184,6 +14189,7 @@ var init_schema = __esm(() => {
|
|
|
14184
14189
|
quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
|
|
14185
14190
|
host_control: HostControlConfigSchema.default({}).describe("Host-control daemon configuration. Defaults to enabled=true since " + "RFC C Phase 2 (reference/rfcs/host-control-daemon.md). Omit the block " + "to accept defaults; set `enabled: false` only on legacy systemd-" + "mode installs (removal tracked as RFC C Phase 3)."),
|
|
14186
14191
|
hostd: HostdConfigSchema.default({}).describe("hostd verb-level knobs (RFC admin-agent-config-edit). Distinct " + "from `host_control:` which governs whether the daemon runs at " + "all. Scopes the opt-in flag and rate cap for the " + "`config_propose_edit` verb (disabled by default)."),
|
|
14192
|
+
fleet_health: FleetHealthConfigSchema.default({}).describe("Fleet Health \u2014 job-spec-anchored, operator-facing issue tracker (RFC " + "fleet-health.md, serves fleet-stays-healthy). Assigns the owner agent " + "that runs the nightly model-free sensor + weekly deep-dive. Default " + "unset owner_agent \u2192 inert; the admin page renders an empty state."),
|
|
14187
14193
|
web_service: WebServiceConfigSchema.default({}).describe("Web-service container (dashboard + GitHub-webhook receiver) config. " + "Defaults to managed=false so existing systemd-mode installs are " + "untouched. Set managed: true after cutting over to the " + "`switchroom-web` container \u2014 then `switchroom update` keeps it " + "refreshed. See `switchroom webd install`."),
|
|
14188
14194
|
google_accounts: exports_external.record(exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
|
|
14189
14195
|
message: "Account key must be a Google account email like 'alice@example.com' (colons not allowed)"
|
|
@@ -15682,7 +15688,7 @@ function scheduleNeedsCronSession(entries, opts) {
|
|
|
15682
15688
|
return r.session === "cron";
|
|
15683
15689
|
});
|
|
15684
15690
|
}
|
|
15685
|
-
var DEFAULT_CRON_MODEL = "claude-sonnet-
|
|
15691
|
+
var DEFAULT_CRON_MODEL = "claude-sonnet-5", CHEAP_MODEL_RE, OPUS_MODEL_RE;
|
|
15686
15692
|
var init_cron_routing = __esm(() => {
|
|
15687
15693
|
CHEAP_MODEL_RE = /(^|[^a-z])(sonnet|haiku)([^a-z]|$)/i;
|
|
15688
15694
|
OPUS_MODEL_RE = /opus/i;
|
|
@@ -22263,6 +22269,14 @@ function renderExecutionDisciplineFragment(context = {}, profilesRoot = PROFILES
|
|
|
22263
22269
|
const template = import_handlebars.default.compile(source, { noEscape: true });
|
|
22264
22270
|
return template(context).trimEnd();
|
|
22265
22271
|
}
|
|
22272
|
+
function renderReplyDisciplineFragment(context = {}, profilesRoot = PROFILES_ROOT) {
|
|
22273
|
+
const fragPath = join4(resolve4(profilesRoot, "_shared"), "reply-discipline.md.hbs");
|
|
22274
|
+
if (!existsSync6(fragPath))
|
|
22275
|
+
return "";
|
|
22276
|
+
const source = readFileSync6(fragPath, "utf-8");
|
|
22277
|
+
const template = import_handlebars.default.compile(source, { noEscape: true });
|
|
22278
|
+
return template(context).trimEnd();
|
|
22279
|
+
}
|
|
22266
22280
|
function renderProfileClaudeTemplate(profileName, profilesRoot = PROFILES_ROOT) {
|
|
22267
22281
|
const profileDir = resolve4(profilesRoot, profileName);
|
|
22268
22282
|
const hbsPath = join4(profileDir, "CLAUDE.md.hbs");
|
|
@@ -22296,7 +22310,7 @@ var init_profiles = __esm(() => {
|
|
|
22296
22310
|
return typeof value === "number" && Number.isFinite(value);
|
|
22297
22311
|
});
|
|
22298
22312
|
SHARED_FRAGMENTS_DIR = resolve4(PROFILES_ROOT, "_shared");
|
|
22299
|
-
SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline"];
|
|
22313
|
+
SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline"];
|
|
22300
22314
|
for (const name of SHARED_FRAGMENTS) {
|
|
22301
22315
|
const fragPath = join4(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
|
|
22302
22316
|
if (existsSync6(fragPath)) {
|
|
@@ -24883,6 +24897,26 @@ import { homedir as homedir5 } from "node:os";
|
|
|
24883
24897
|
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
24884
24898
|
import { join as join10, resolve as resolve11 } from "node:path";
|
|
24885
24899
|
import { createHash as createHash2 } from "node:crypto";
|
|
24900
|
+
function prependReplyDiscipline(rendered, context) {
|
|
24901
|
+
const replyDiscipline = renderReplyDisciplineFragment(context);
|
|
24902
|
+
if (!replyDiscipline)
|
|
24903
|
+
return rendered;
|
|
24904
|
+
const newlineIdx = rendered.indexOf(`
|
|
24905
|
+
`);
|
|
24906
|
+
if (newlineIdx === -1) {
|
|
24907
|
+
return rendered.trimEnd() + `
|
|
24908
|
+
|
|
24909
|
+
` + replyDiscipline + `
|
|
24910
|
+
`;
|
|
24911
|
+
}
|
|
24912
|
+
const title = rendered.slice(0, newlineIdx);
|
|
24913
|
+
const body = rendered.slice(newlineIdx + 1).replace(/^\n+/, "");
|
|
24914
|
+
return title + `
|
|
24915
|
+
|
|
24916
|
+
` + replyDiscipline + `
|
|
24917
|
+
|
|
24918
|
+
` + body;
|
|
24919
|
+
}
|
|
24886
24920
|
function renderFleetInvariants() {
|
|
24887
24921
|
return [
|
|
24888
24922
|
"<!--",
|
|
@@ -25452,6 +25486,67 @@ function copyDirRecursive2(src, dest) {
|
|
|
25452
25486
|
}
|
|
25453
25487
|
}
|
|
25454
25488
|
}
|
|
25489
|
+
function writeFileSyncIfChanged(filePath, content, mode) {
|
|
25490
|
+
if (existsSync15(filePath)) {
|
|
25491
|
+
try {
|
|
25492
|
+
if (readFileSync13(filePath, "utf-8") === content)
|
|
25493
|
+
return false;
|
|
25494
|
+
} catch {}
|
|
25495
|
+
}
|
|
25496
|
+
writeFileSync5(filePath, content, mode !== undefined ? { encoding: "utf-8", mode } : "utf-8");
|
|
25497
|
+
return true;
|
|
25498
|
+
}
|
|
25499
|
+
function dirContentEquals(src, dest, topLevelOverrides) {
|
|
25500
|
+
if (!existsSync15(src) || !existsSync15(dest))
|
|
25501
|
+
return false;
|
|
25502
|
+
let srcEntries;
|
|
25503
|
+
let destEntries;
|
|
25504
|
+
try {
|
|
25505
|
+
srcEntries = readdirSync6(src).sort();
|
|
25506
|
+
destEntries = readdirSync6(dest).sort();
|
|
25507
|
+
} catch {
|
|
25508
|
+
return false;
|
|
25509
|
+
}
|
|
25510
|
+
if (srcEntries.length !== destEntries.length)
|
|
25511
|
+
return false;
|
|
25512
|
+
for (let i = 0;i < srcEntries.length; i++) {
|
|
25513
|
+
if (srcEntries[i] !== destEntries[i])
|
|
25514
|
+
return false;
|
|
25515
|
+
}
|
|
25516
|
+
for (const entry of srcEntries) {
|
|
25517
|
+
const s = join10(src, entry);
|
|
25518
|
+
const d = join10(dest, entry);
|
|
25519
|
+
let sStat;
|
|
25520
|
+
let dStat;
|
|
25521
|
+
try {
|
|
25522
|
+
sStat = statSync7(s);
|
|
25523
|
+
dStat = statSync7(d);
|
|
25524
|
+
} catch {
|
|
25525
|
+
return false;
|
|
25526
|
+
}
|
|
25527
|
+
if (sStat.isDirectory() !== dStat.isDirectory())
|
|
25528
|
+
return false;
|
|
25529
|
+
if (sStat.isDirectory()) {
|
|
25530
|
+
if (!dirContentEquals(s, d))
|
|
25531
|
+
return false;
|
|
25532
|
+
} else {
|
|
25533
|
+
if ((sStat.mode & 64) !== (dStat.mode & 64))
|
|
25534
|
+
return false;
|
|
25535
|
+
try {
|
|
25536
|
+
const expected = topLevelOverrides?.[entry];
|
|
25537
|
+
if (expected !== undefined) {
|
|
25538
|
+
if (readFileSync13(d, "utf-8") !== expected)
|
|
25539
|
+
return false;
|
|
25540
|
+
} else if (!readFileSync13(s).equals(readFileSync13(d))) {
|
|
25541
|
+
return false;
|
|
25542
|
+
}
|
|
25543
|
+
} catch {
|
|
25544
|
+
return false;
|
|
25545
|
+
}
|
|
25546
|
+
}
|
|
25547
|
+
}
|
|
25548
|
+
return true;
|
|
25549
|
+
}
|
|
25455
25550
|
function seedWorkspaceBootstrapFiles(params) {
|
|
25456
25551
|
const profileWorkspaceDir = join10(params.profilePath, "workspace");
|
|
25457
25552
|
if (!existsSync15(profileWorkspaceDir)) {
|
|
@@ -25624,11 +25719,21 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
|
|
|
25624
25719
|
return null;
|
|
25625
25720
|
}
|
|
25626
25721
|
const destPath = join10(agentDir, ".claude", "plugins", "hindsight-memory");
|
|
25627
|
-
if (existsSync15(destPath)) {
|
|
25628
|
-
rmSync3(destPath, { recursive: true, force: true });
|
|
25629
|
-
}
|
|
25630
|
-
copyDirRecursive2(sourcePath, destPath);
|
|
25631
25722
|
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
25723
|
+
let expectedSettings = null;
|
|
25724
|
+
try {
|
|
25725
|
+
const vendorSettingsPath = join10(sourcePath, "settings.json");
|
|
25726
|
+
if (existsSync15(vendorSettingsPath)) {
|
|
25727
|
+
expectedSettings = renderHindsightSettingsOverrides(readFileSync13(vendorSettingsPath, "utf-8"), additionalBanks);
|
|
25728
|
+
}
|
|
25729
|
+
} catch {}
|
|
25730
|
+
const upToDate = dirContentEquals(sourcePath, destPath, expectedSettings != null ? { "settings.json": expectedSettings } : undefined);
|
|
25731
|
+
if (!upToDate) {
|
|
25732
|
+
if (existsSync15(destPath)) {
|
|
25733
|
+
rmSync3(destPath, { recursive: true, force: true });
|
|
25734
|
+
}
|
|
25735
|
+
copyDirRecursive2(sourcePath, destPath);
|
|
25736
|
+
}
|
|
25632
25737
|
applyHindsightSettingsOverrides(destPath, additionalBanks);
|
|
25633
25738
|
const bankId = agentMemory?.collection ?? agentName;
|
|
25634
25739
|
const mcpUrl = memory.config?.url ?? "http://127.0.0.1:8888/mcp/";
|
|
@@ -25645,11 +25750,17 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
|
|
|
25645
25750
|
} catch {
|
|
25646
25751
|
return;
|
|
25647
25752
|
}
|
|
25753
|
+
const next = renderHindsightSettingsOverrides(raw, additionalBanks);
|
|
25754
|
+
if (next == null)
|
|
25755
|
+
return;
|
|
25756
|
+
writeFileSyncIfChanged(settingsPath, next);
|
|
25757
|
+
}
|
|
25758
|
+
function renderHindsightSettingsOverrides(raw, additionalBanks) {
|
|
25648
25759
|
let settings;
|
|
25649
25760
|
try {
|
|
25650
25761
|
settings = JSON.parse(raw);
|
|
25651
25762
|
} catch {
|
|
25652
|
-
return;
|
|
25763
|
+
return null;
|
|
25653
25764
|
}
|
|
25654
25765
|
settings.retainEveryNTurns = 1;
|
|
25655
25766
|
settings.recallMaxMemories = 8;
|
|
@@ -25659,8 +25770,8 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
|
|
|
25659
25770
|
if (additionalBanks.length > 0) {
|
|
25660
25771
|
settings.recallAdditionalBanks = [...additionalBanks];
|
|
25661
25772
|
}
|
|
25662
|
-
|
|
25663
|
-
|
|
25773
|
+
return JSON.stringify(settings, null, 2) + `
|
|
25774
|
+
`;
|
|
25664
25775
|
}
|
|
25665
25776
|
function buildWorkspaceContext(args) {
|
|
25666
25777
|
const {
|
|
@@ -26011,8 +26122,8 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
26011
26122
|
if (agentConfig.settings_raw && Object.keys(agentConfig.settings_raw).length > 0) {
|
|
26012
26123
|
mergedSettings._switchroomManagedRawKeys = Object.keys(agentConfig.settings_raw);
|
|
26013
26124
|
}
|
|
26014
|
-
|
|
26015
|
-
|
|
26125
|
+
writeFileSyncIfChanged(settingsPath, JSON.stringify(mergedSettings, null, 2) + `
|
|
26126
|
+
`);
|
|
26016
26127
|
}
|
|
26017
26128
|
}
|
|
26018
26129
|
let mcpServerKeysToTrust = null;
|
|
@@ -26092,6 +26203,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
26092
26203
|
rerenderWithFingerprint(join10(agentDir, dest), () => {
|
|
26093
26204
|
let rendered = renderTemplate(srcPath, context);
|
|
26094
26205
|
if (dest === "CLAUDE.md") {
|
|
26206
|
+
rendered = prependReplyDiscipline(rendered, context);
|
|
26095
26207
|
const vaultProtocol = renderVaultProtocolFragment(context);
|
|
26096
26208
|
if (vaultProtocol) {
|
|
26097
26209
|
rendered = rendered.trimEnd() + `
|
|
@@ -26261,7 +26373,7 @@ ${fmLines}
|
|
|
26261
26373
|
|
|
26262
26374
|
${body}
|
|
26263
26375
|
`;
|
|
26264
|
-
|
|
26376
|
+
writeFileSyncIfChanged(mdPath, content);
|
|
26265
26377
|
}
|
|
26266
26378
|
}
|
|
26267
26379
|
{
|
|
@@ -26576,6 +26688,8 @@ function buildSettingsHooksBlock(p) {
|
|
|
26576
26688
|
` + ` - Question with a short answer \u2192 just reply with the answer.
|
|
26577
26689
|
` + " - Complex tool-driven work \u2192 go straight to the tools (the " + "compose-area preview is the ambient liveness signal), then reply " + 'once with the answer or a genuine mid-work pivot ("halfway ' + 'through \u2014 found an unexpected issue, want me to continue?"). Not ' + `"still working".
|
|
26578
26690
|
|
|
26691
|
+
` + "NARRATE YOUR INTENT (your reasoning is NOT shown to the user). The " + "user cannot see your thinking \u2014 only your tool activity and your " + "replies. So before you fire a burst of tools or work silently for a " + "while, first write ONE short plain-language line of intent as " + "ordinary working text (NOT a reply tool call): name what you are " + 'about to do and why \u2014 "Now checking the gateway logs to find why ' + 'the turn stalled". The framework mirrors that line into the live ' + "compose-area preview the user watches; because it is plain text and " + "not a reply, it is never a chat message and never pings. One intent " + "line before a silent stretch, in plain words, never raw tool names " + '("calling Bash", "Read(x)") and never a debug dump. And when you run ' + "a Bash command, always give it a plain-English `description` naming " + "the goal \u2014 that description, not the raw command, is what the user " + "sees. This intent narration is the ONE thing to keep saying; it is " + "NOT the placeholder ack banned above (that is a chat reply, which " + `you still skip).
|
|
26692
|
+
|
|
26579
26693
|
` + 'Do NOT send a trailing confirmation after your answer \u2014 no "Done.", ' + '"Sent.", "Hope that helps." as a separate message once you have ' + "already replied. Your answer is the last thing the user should " + `see; a follow-up "Done." is dead-air clutter (and the user's ` + `device already pinged on the answer). Stop after the answer.
|
|
26580
26694
|
|
|
26581
26695
|
` + "GROUND BEFORE YOU ASSERT. Any fact in your reply that can change " + "(a number, a status, a price, a date, who-uses-what, anything " + '"current" or "latest") must come from a source you actually checked ' + "THIS turn: your data tool, a file, the web. Memory and what you " + '"already know" are leads to verify, not sources. If you have not ' + "checked it this turn, do not state it as fact: go get it now, or tell " + "the user you will confirm and then do it. A confident wrong number is " + `worse than "let me check".
|
|
@@ -26906,6 +27020,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
|
|
|
26906
27020
|
linearAgentEnabled: agentConfig.channels?.telegram?.linear_agent?.enabled === true
|
|
26907
27021
|
};
|
|
26908
27022
|
let rendered = renderTemplate(claudeMdSrc, claudeContext);
|
|
27023
|
+
rendered = prependReplyDiscipline(rendered, claudeContext);
|
|
26909
27024
|
const vaultProtocol = renderVaultProtocolFragment(claudeContext);
|
|
26910
27025
|
if (vaultProtocol) {
|
|
26911
27026
|
rendered = rendered.trimEnd() + `
|
|
@@ -27530,9 +27645,22 @@ messaging a capable colleague \u2014 not a tool emitting output. Five beats:
|
|
|
27530
27645
|
compose the full answer. This holds even for a pure-thinking
|
|
27531
27646
|
answer: if it will run to a paragraph, ack first. It is the line
|
|
27532
27647
|
between a colleague and a black box.
|
|
27533
|
-
2. **Then go quiet and work
|
|
27534
|
-
every tool call
|
|
27535
|
-
|
|
27648
|
+
2. **Then go quiet and work \u2014 but leave a trail of intent.** Heads-down
|
|
27649
|
+
is correct: do NOT narrate every tool call, and a typing indicator
|
|
27650
|
+
runs automatically (you do not maintain it). One thing you SHOULD do,
|
|
27651
|
+
because your private reasoning is never shown to the user: before a
|
|
27652
|
+
burst of tool calls or a long silent stretch, drop ONE short
|
|
27653
|
+
plain-language line of intent in your own working text \u2014 *what* you
|
|
27654
|
+
are about to do and *why* ("Now checking the gateway logs to find why
|
|
27655
|
+
the turn stalled"). Write it as ordinary text, NOT a \`reply\` call, so
|
|
27656
|
+
it never becomes a chat message and never pings \u2014 the framework
|
|
27657
|
+
mirrors that line into the live preview the user watches while you
|
|
27658
|
+
work. One line per stretch, plain words, never raw tool names
|
|
27659
|
+
("calling Bash") or debug dumps. It is the difference between the user
|
|
27660
|
+
seeing "checking the logs to find the stall" and seeing a black box.
|
|
27661
|
+
When a step is a \`Bash\` command, always give it a plain-English
|
|
27662
|
+
\`description\` naming the goal \u2014 that description, not the raw command,
|
|
27663
|
+
is what the user sees.
|
|
27536
27664
|
3. **Surface meaningful progress** at genuine inflection points \u2014 a
|
|
27537
27665
|
hard step finished, a blocker, a pivot, dispatching a sub-agent, a
|
|
27538
27666
|
notably slow wait, a finding worth knowing now. One short \`reply\`,
|
|
@@ -27703,53 +27831,34 @@ operator and stop \u2014 don't theorise about auth or tokens; it just means
|
|
|
27703
27831
|
a stored value reached a tool argument.`, TELEGRAM_FORMATTING_FLOOR_CARD = `## Formatting for Telegram
|
|
27704
27832
|
|
|
27705
27833
|
You're writing for a phone screen in Telegram. Every reply renders as rich Markdown
|
|
27706
|
-
(Bot API 10.1).
|
|
27707
|
-
|
|
27708
|
-
|
|
27709
|
-
|
|
27710
|
-
|
|
27711
|
-
|
|
27712
|
-
|
|
27713
|
-
|
|
27714
|
-
-
|
|
27715
|
-
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
27719
|
-
|
|
27720
|
-
|
|
27721
|
-
|
|
27722
|
-
|
|
27723
|
-
|
|
27724
|
-
|
|
27725
|
-
|
|
27726
|
-
|
|
27727
|
-
|
|
27728
|
-
|
|
27729
|
-
|
|
27730
|
-
- Tables \u2014 2-D data only (rows \u00d7 columns): per-account usage, status fields. Render as
|
|
27731
|
-
real tables (up to 20 columns). Overkill for a flat list.
|
|
27732
|
-
- Headings (\`#\`) \u2014 only in a long, multi-section answer. Clutter on a short reply.
|
|
27733
|
-
- Blockquotes (\`>\`) \u2014 quoted text or indented continuation. Telegram drops leading
|
|
27734
|
-
spaces, so use \`>\`, never literal indentation.
|
|
27735
|
-
- \`==highlight==\` \u2014 a single word or value the reader's eye must land on. Stronger
|
|
27736
|
-
than bold; use at most once per message.
|
|
27737
|
-
- Dividers (\`---\`) \u2014 between genuinely separate sections. Heavy; use sparingly.
|
|
27738
|
-
|
|
27739
|
-
Tables, headings, dividers, task lists, lists, blockquotes and \`==highlight==\` all
|
|
27740
|
-
render as rich blocks (Bot API 10.1) \u2014 reach for whichever cuts the reader's scanning
|
|
27741
|
-
effort. The ONE exception: sub/superscript (\`H~2~O\`, \`x^2^\`) falls back to literal
|
|
27742
|
-
text, so don't use it.
|
|
27743
|
-
|
|
27744
|
-
### The why
|
|
27745
|
-
Structure exists for the reader, not the writer. A two-item bullet list is worse than a
|
|
27746
|
-
sentence. A heading on a three-line reply is noise. Reach for structure only when it
|
|
27747
|
-
**reduces the reader's effort** \u2014 parallel options, scannable data, ordered steps \u2014 and
|
|
27748
|
-
stay in prose when the thought is connected. When in doubt: shorter and plainer wins.
|
|
27834
|
+
(Bot API 10.1). One shared spec, three tiers:
|
|
27835
|
+
|
|
27836
|
+
- **Short answers (a line or two): plain prose, no formatting.** "on it, pulling the
|
|
27837
|
+
logs now" is already perfect. No bold, no bullets, no headings.
|
|
27838
|
+
- **Default: light structure.** Bold ONLY the one key fact or answer, never more. Use
|
|
27839
|
+
a list only for 3+ genuinely parallel items the reader will scan or compare; two
|
|
27840
|
+
items or a flowing thought stay prose. \`code spans\` for identifiers: filenames,
|
|
27841
|
+
commands, config keys, error codes (tap-to-copy).
|
|
27842
|
+
- **Long answers may add tables / headings / blockquotes, but only when they genuinely
|
|
27843
|
+
aid scanning**: a table for real 2-D data (rows x columns), headings only in a
|
|
27844
|
+
multi-section answer, \`>\` for quoted text. If the structure doesn't cut the
|
|
27845
|
+
reader's effort, drop it.
|
|
27846
|
+
|
|
27847
|
+
The framework normalizes mechanics in code on every outbound message: block spacing
|
|
27848
|
+
(one blank line between distinct blocks), em/en dashes, and \`\u2022\` bullet markers are
|
|
27849
|
+
rewritten deterministically at send time. Don't fight it or hand-tune spacing; write
|
|
27850
|
+
the content, the gateway makes the typography consistent. Over-bolded messages (most
|
|
27851
|
+
of the text bold, or whole paragraphs/lists bolded) have their bold stripped at send
|
|
27852
|
+
time, so bold sparingly.
|
|
27853
|
+
|
|
27854
|
+
Hard cap is 32768 characters. Long before that, ask whether a wall of text is the
|
|
27855
|
+
right answer at all. Structure exists for the reader, not the writer: a two-item
|
|
27856
|
+
bullet list is worse than a sentence, a heading on a three-line reply is noise. When
|
|
27857
|
+
in doubt, shorter and plainer wins.
|
|
27749
27858
|
|
|
27750
27859
|
Every turn that answers a user message ends with a user-visible \`reply\` (or
|
|
27751
27860
|
\`stream_reply\` done=true) \u2014 Telegram is all the user sees; your terminal output
|
|
27752
|
-
never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-
|
|
27861
|
+
never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", INTEGRATION_MCP_RESOLVERS;
|
|
27753
27862
|
var init_scaffold = __esm(() => {
|
|
27754
27863
|
init_source();
|
|
27755
27864
|
init_cron_unit_name();
|
|
@@ -39474,6 +39583,50 @@ var init_client5 = __esm(() => {
|
|
|
39474
39583
|
init_protocol3();
|
|
39475
39584
|
});
|
|
39476
39585
|
|
|
39586
|
+
// src/web/fleet-health-read.ts
|
|
39587
|
+
import { readFileSync as readFileSync46 } from "node:fs";
|
|
39588
|
+
import { resolve as resolve29 } from "node:path";
|
|
39589
|
+
import { homedir as homedir24 } from "node:os";
|
|
39590
|
+
function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir24()) {
|
|
39591
|
+
return resolve29(home2, ".switchroom", "fleet-health", "ledger.json");
|
|
39592
|
+
}
|
|
39593
|
+
function readFleetHealth(path4) {
|
|
39594
|
+
let ledger;
|
|
39595
|
+
try {
|
|
39596
|
+
ledger = JSON.parse(readFileSync46(path4, "utf-8"));
|
|
39597
|
+
} catch {
|
|
39598
|
+
return {
|
|
39599
|
+
owner_agent: null,
|
|
39600
|
+
generated_at: null,
|
|
39601
|
+
summary: null,
|
|
39602
|
+
records: [],
|
|
39603
|
+
empty: true,
|
|
39604
|
+
reason: EMPTY_REASON
|
|
39605
|
+
};
|
|
39606
|
+
}
|
|
39607
|
+
const rawRecords = Array.isArray(ledger.records) ? ledger.records : [];
|
|
39608
|
+
const records = rawRecords.map((r) => {
|
|
39609
|
+
const n = Number(r?.priority_score);
|
|
39610
|
+
return { ...r, priority_score: Number.isFinite(n) ? n : 0 };
|
|
39611
|
+
});
|
|
39612
|
+
const ranked = [...records].sort((a, b) => b.priority_score - a.priority_score);
|
|
39613
|
+
return {
|
|
39614
|
+
owner_agent: ledger.owner_agent ?? null,
|
|
39615
|
+
generated_at: ledger.generated_at ?? null,
|
|
39616
|
+
summary: ledger.summary ?? null,
|
|
39617
|
+
records: ranked,
|
|
39618
|
+
empty: ranked.length === 0,
|
|
39619
|
+
...ranked.length === 0 ? { reason: EMPTY_REASON } : {}
|
|
39620
|
+
};
|
|
39621
|
+
}
|
|
39622
|
+
function handleGetFleetHealth(path4 = fleetHealthLedgerPath()) {
|
|
39623
|
+
return readFleetHealth(path4);
|
|
39624
|
+
}
|
|
39625
|
+
var EMPTY_REASON;
|
|
39626
|
+
var init_fleet_health_read = __esm(() => {
|
|
39627
|
+
EMPTY_REASON = "Fleet Health is not wired up yet \u2014 no ledger at " + "~/.switchroom/fleet-health/ledger.json. Assign an owner agent " + "(`fleet_health.owner_agent` in switchroom.yaml) and schedule its " + "nightly sensor cron. See reference/rfcs/fleet-health.md.";
|
|
39628
|
+
});
|
|
39629
|
+
|
|
39477
39630
|
// src/cli/doctor-status.ts
|
|
39478
39631
|
function statusGlyph(status) {
|
|
39479
39632
|
switch (status) {
|
|
@@ -39518,7 +39671,7 @@ var init_thinking_effort_risk = __esm(() => {
|
|
|
39518
39671
|
// src/manifest.ts
|
|
39519
39672
|
import {
|
|
39520
39673
|
existsSync as existsSync56,
|
|
39521
|
-
readFileSync as
|
|
39674
|
+
readFileSync as readFileSync52,
|
|
39522
39675
|
readdirSync as readdirSync19
|
|
39523
39676
|
} from "node:fs";
|
|
39524
39677
|
import { dirname as dirname16, join as join50 } from "node:path";
|
|
@@ -39540,7 +39693,7 @@ function loadManifest(manifestPath) {
|
|
|
39540
39693
|
}
|
|
39541
39694
|
let raw;
|
|
39542
39695
|
try {
|
|
39543
|
-
raw =
|
|
39696
|
+
raw = readFileSync52(path4, "utf-8");
|
|
39544
39697
|
} catch (err) {
|
|
39545
39698
|
throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
|
|
39546
39699
|
}
|
|
@@ -39605,7 +39758,7 @@ function probePlaywrightMcpVersion() {
|
|
|
39605
39758
|
const pkgPath = join50(npxCache, entry, "node_modules/@playwright/mcp/package.json");
|
|
39606
39759
|
if (existsSync56(pkgPath)) {
|
|
39607
39760
|
try {
|
|
39608
|
-
const pkg = JSON.parse(
|
|
39761
|
+
const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
|
|
39609
39762
|
if (pkg.version)
|
|
39610
39763
|
return pkg.version;
|
|
39611
39764
|
} catch {}
|
|
@@ -39840,7 +39993,7 @@ var init_doctor_memory = __esm(() => {
|
|
|
39840
39993
|
});
|
|
39841
39994
|
|
|
39842
39995
|
// src/cli/doctor-docker.ts
|
|
39843
|
-
import { readFileSync as
|
|
39996
|
+
import { readFileSync as readFileSync53 } from "node:fs";
|
|
39844
39997
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
39845
39998
|
function imageTagOf(ref) {
|
|
39846
39999
|
if (!ref)
|
|
@@ -39856,7 +40009,7 @@ function isDockerMode(opts) {
|
|
|
39856
40009
|
return true;
|
|
39857
40010
|
if (opts?.composePath) {
|
|
39858
40011
|
try {
|
|
39859
|
-
|
|
40012
|
+
readFileSync53(opts.composePath, "utf8");
|
|
39860
40013
|
return true;
|
|
39861
40014
|
} catch {}
|
|
39862
40015
|
}
|
|
@@ -40138,10 +40291,10 @@ var init_doctor_docker = __esm(() => {
|
|
|
40138
40291
|
});
|
|
40139
40292
|
|
|
40140
40293
|
// src/cli/doctor-auth-broker.ts
|
|
40141
|
-
import { existsSync as existsSync57, readFileSync as
|
|
40294
|
+
import { existsSync as existsSync57, readFileSync as readFileSync54 } from "node:fs";
|
|
40142
40295
|
import { createHash as createHash11 } from "node:crypto";
|
|
40143
40296
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
40144
|
-
import { homedir as
|
|
40297
|
+
import { homedir as homedir28 } from "node:os";
|
|
40145
40298
|
import { join as join51 } from "node:path";
|
|
40146
40299
|
function defaultDockerInspect(container, format) {
|
|
40147
40300
|
try {
|
|
@@ -40252,7 +40405,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
40252
40405
|
}
|
|
40253
40406
|
let index;
|
|
40254
40407
|
try {
|
|
40255
|
-
index = JSON.parse(
|
|
40408
|
+
index = JSON.parse(readFileSync54(indexPath, "utf-8"));
|
|
40256
40409
|
} catch (err) {
|
|
40257
40410
|
return {
|
|
40258
40411
|
name: "auth-broker: drift",
|
|
@@ -40261,7 +40414,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
40261
40414
|
fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
|
|
40262
40415
|
};
|
|
40263
40416
|
}
|
|
40264
|
-
const home2 = deps.home ??
|
|
40417
|
+
const home2 = deps.home ?? homedir28();
|
|
40265
40418
|
const divergent = [];
|
|
40266
40419
|
const missingOnDisk = [];
|
|
40267
40420
|
for (const [label, expected] of Object.entries(index)) {
|
|
@@ -40272,7 +40425,7 @@ function checkAuthBrokerDrift(deps = {}) {
|
|
|
40272
40425
|
}
|
|
40273
40426
|
let got;
|
|
40274
40427
|
try {
|
|
40275
|
-
got = sha256Hex(
|
|
40428
|
+
got = sha256Hex(readFileSync54(credsPath, "utf-8"));
|
|
40276
40429
|
} catch (err) {
|
|
40277
40430
|
divergent.push(`${label} (read failed: ${err.message})`);
|
|
40278
40431
|
continue;
|
|
@@ -40313,7 +40466,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
|
|
|
40313
40466
|
}
|
|
40314
40467
|
let violations;
|
|
40315
40468
|
try {
|
|
40316
|
-
violations = JSON.parse(
|
|
40469
|
+
violations = JSON.parse(readFileSync54(path4, "utf-8"));
|
|
40317
40470
|
} catch (err) {
|
|
40318
40471
|
return {
|
|
40319
40472
|
name: "auth-broker: threshold violations",
|
|
@@ -40347,7 +40500,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
|
|
|
40347
40500
|
fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
|
|
40348
40501
|
};
|
|
40349
40502
|
}
|
|
40350
|
-
const home2 = deps.home ??
|
|
40503
|
+
const home2 = deps.home ?? homedir28();
|
|
40351
40504
|
const dir = accountDir(active, home2);
|
|
40352
40505
|
if (!existsSync57(dir)) {
|
|
40353
40506
|
return {
|
|
@@ -40499,7 +40652,7 @@ import {
|
|
|
40499
40652
|
realpathSync as realpathSync5,
|
|
40500
40653
|
statSync as statSync23
|
|
40501
40654
|
} from "node:fs";
|
|
40502
|
-
import { userInfo, homedir as
|
|
40655
|
+
import { userInfo, homedir as homedir29 } from "node:os";
|
|
40503
40656
|
import { join as join52 } from "node:path";
|
|
40504
40657
|
function resolveVaultPath2(config) {
|
|
40505
40658
|
return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
|
|
@@ -40638,7 +40791,7 @@ async function runSecretAccessChecks(config, deps = {}) {
|
|
|
40638
40791
|
};
|
|
40639
40792
|
const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
40640
40793
|
if (!passphrase) {
|
|
40641
|
-
const sock = deps.brokerOperatorSocket ?? join52(
|
|
40794
|
+
const sock = deps.brokerOperatorSocket ?? join52(homedir29(), ".switchroom", "broker-operator", "sock");
|
|
40642
40795
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
40643
40796
|
for (const name of Object.keys(config.agents ?? {})) {
|
|
40644
40797
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
|
|
@@ -40723,8 +40876,8 @@ import {
|
|
|
40723
40876
|
existsSync as realExistsSync,
|
|
40724
40877
|
readFileSync as realReadFileSync
|
|
40725
40878
|
} from "node:fs";
|
|
40726
|
-
import { join as join53, resolve as
|
|
40727
|
-
import { homedir as
|
|
40879
|
+
import { join as join53, resolve as resolve33 } from "node:path";
|
|
40880
|
+
import { homedir as homedir30 } from "node:os";
|
|
40728
40881
|
function resolveDeps(config, deps) {
|
|
40729
40882
|
let agentsDir = deps.agentsDir;
|
|
40730
40883
|
if (agentsDir === undefined) {
|
|
@@ -40838,7 +40991,7 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
40838
40991
|
];
|
|
40839
40992
|
}
|
|
40840
40993
|
for (const name of driveAgents) {
|
|
40841
|
-
const agentDir =
|
|
40994
|
+
const agentDir = resolve33(d.agentsDir, name);
|
|
40842
40995
|
if (!d.existsSync(agentDir)) {
|
|
40843
40996
|
results.push({
|
|
40844
40997
|
name: `drive: ${name} scaffold`,
|
|
@@ -40876,7 +41029,7 @@ function checkScaffoldWiring(config, driveAgents, d) {
|
|
|
40876
41029
|
let trustOk = false;
|
|
40877
41030
|
let trustDetail = "no .claude/.claude.json";
|
|
40878
41031
|
if (trustRead.kind === "ok") {
|
|
40879
|
-
const proj = trustRead.data?.projects?.[
|
|
41032
|
+
const proj = trustRead.data?.projects?.[resolve33(agentDir)];
|
|
40880
41033
|
const enabled = proj?.enabledMcpjsonServers;
|
|
40881
41034
|
if (Array.isArray(enabled) && enabled.includes("gdrive")) {
|
|
40882
41035
|
trustOk = true;
|
|
@@ -40961,7 +41114,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
|
|
|
40961
41114
|
}
|
|
40962
41115
|
];
|
|
40963
41116
|
}
|
|
40964
|
-
const sock = deps.brokerOperatorSocket ?? join53(
|
|
41117
|
+
const sock = deps.brokerOperatorSocket ?? join53(homedir30(), ".switchroom", "broker-operator", "sock");
|
|
40965
41118
|
const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
|
|
40966
41119
|
const results = [];
|
|
40967
41120
|
for (const agent of driveAgents) {
|
|
@@ -41016,7 +41169,7 @@ import {
|
|
|
41016
41169
|
closeSync as realCloseSync
|
|
41017
41170
|
} from "node:fs";
|
|
41018
41171
|
import { join as join54 } from "node:path";
|
|
41019
|
-
import { homedir as
|
|
41172
|
+
import { homedir as homedir31 } from "node:os";
|
|
41020
41173
|
function defaultReadHead(p, n) {
|
|
41021
41174
|
let fd;
|
|
41022
41175
|
try {
|
|
@@ -41044,7 +41197,7 @@ function resolveDeps2(config, deps) {
|
|
|
41044
41197
|
}
|
|
41045
41198
|
}
|
|
41046
41199
|
return {
|
|
41047
|
-
homeDir: deps.homeDir ??
|
|
41200
|
+
homeDir: deps.homeDir ?? homedir31(),
|
|
41048
41201
|
agentsDir,
|
|
41049
41202
|
existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
|
|
41050
41203
|
readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
|
|
@@ -41201,7 +41354,7 @@ var init_doctor_webkite = __esm(() => {
|
|
|
41201
41354
|
|
|
41202
41355
|
// src/cli/doctor-cron-session.ts
|
|
41203
41356
|
import { statSync as realStatSync } from "node:fs";
|
|
41204
|
-
import { resolve as
|
|
41357
|
+
import { resolve as resolve34 } from "node:path";
|
|
41205
41358
|
function agentRunsCronSession(config, agent) {
|
|
41206
41359
|
const raw = config.agents[agent];
|
|
41207
41360
|
if (!raw)
|
|
@@ -41217,7 +41370,7 @@ function runCronSessionChecks(config, deps = defaultDeps2) {
|
|
|
41217
41370
|
if (!agentRunsCronSession(config, agent))
|
|
41218
41371
|
continue;
|
|
41219
41372
|
const name = `cron-session: ${agent}`;
|
|
41220
|
-
const alivePath =
|
|
41373
|
+
const alivePath = resolve34(agentsDir, agent, "telegram", ".bridge-alive-cron");
|
|
41221
41374
|
let mtimeMs;
|
|
41222
41375
|
try {
|
|
41223
41376
|
mtimeMs = deps.statMtimeMs(alivePath);
|
|
@@ -41266,7 +41419,7 @@ var init_doctor_cron_session = __esm(() => {
|
|
|
41266
41419
|
});
|
|
41267
41420
|
|
|
41268
41421
|
// src/cli/doctor-scaffold-wiring.ts
|
|
41269
|
-
import { join as join55, resolve as
|
|
41422
|
+
import { join as join55, resolve as resolve35 } from "node:path";
|
|
41270
41423
|
function readJson2(d, path4) {
|
|
41271
41424
|
if (!d.existsSync(path4))
|
|
41272
41425
|
return { kind: "absent" };
|
|
@@ -41300,7 +41453,7 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
41300
41453
|
];
|
|
41301
41454
|
}
|
|
41302
41455
|
for (const name of agents) {
|
|
41303
|
-
const agentDir =
|
|
41456
|
+
const agentDir = resolve35(agentsDir, name);
|
|
41304
41457
|
if (!deps.existsSync(agentDir)) {
|
|
41305
41458
|
results.push({
|
|
41306
41459
|
name: `${label}: ${name} scaffold`,
|
|
@@ -41340,7 +41493,7 @@ function checkIntegrationScaffoldWiring(args) {
|
|
|
41340
41493
|
let trustDetail = "no .claude/.claude.json";
|
|
41341
41494
|
if (trustRead.kind === "ok") {
|
|
41342
41495
|
const projects = trustRead.data?.projects ?? {};
|
|
41343
|
-
const proj = projects[
|
|
41496
|
+
const proj = projects[resolve35(agentDir)];
|
|
41344
41497
|
const enabled = proj?.enabledMcpjsonServers;
|
|
41345
41498
|
if (Array.isArray(enabled) && enabled.includes(mcpKey)) {
|
|
41346
41499
|
trustOk = true;
|
|
@@ -41375,9 +41528,9 @@ import {
|
|
|
41375
41528
|
readFileSync as realReadFileSync3
|
|
41376
41529
|
} from "node:fs";
|
|
41377
41530
|
import { join as join56 } from "node:path";
|
|
41378
|
-
import { homedir as
|
|
41531
|
+
import { homedir as homedir32 } from "node:os";
|
|
41379
41532
|
function resolveDeps3(deps) {
|
|
41380
|
-
const home2 = deps.homeDir?.() ??
|
|
41533
|
+
const home2 = deps.homeDir?.() ?? homedir32();
|
|
41381
41534
|
return {
|
|
41382
41535
|
existsSync: deps.existsSync ?? realExistsSync3,
|
|
41383
41536
|
readFileSync: deps.readFileSync ?? realReadFileSync3,
|
|
@@ -41562,9 +41715,9 @@ import {
|
|
|
41562
41715
|
statSync as realStatSync2
|
|
41563
41716
|
} from "node:fs";
|
|
41564
41717
|
import { join as join57 } from "node:path";
|
|
41565
|
-
import { homedir as
|
|
41718
|
+
import { homedir as homedir33 } from "node:os";
|
|
41566
41719
|
function resolveDeps4(deps) {
|
|
41567
|
-
const home2 = deps.homeDir?.() ??
|
|
41720
|
+
const home2 = deps.homeDir?.() ?? homedir33();
|
|
41568
41721
|
return {
|
|
41569
41722
|
existsSync: deps.existsSync ?? realExistsSync4,
|
|
41570
41723
|
readFileSync: deps.readFileSync ?? realReadFileSync4,
|
|
@@ -41915,10 +42068,10 @@ import {
|
|
|
41915
42068
|
readdirSync as realReaddirSync2,
|
|
41916
42069
|
statSync as realStatSync3
|
|
41917
42070
|
} from "node:fs";
|
|
41918
|
-
import { homedir as
|
|
42071
|
+
import { homedir as homedir34 } from "node:os";
|
|
41919
42072
|
import { join as join58 } from "node:path";
|
|
41920
42073
|
function runCredentialsMigrationChecks(config, deps = {}) {
|
|
41921
|
-
const credDir = deps.credentialsDir ?? join58(
|
|
42074
|
+
const credDir = deps.credentialsDir ?? join58(homedir34(), ".switchroom", "credentials");
|
|
41922
42075
|
const existsSync59 = deps.existsSync ?? ((p) => realExistsSync5(p));
|
|
41923
42076
|
const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
|
|
41924
42077
|
const isDirectory = deps.isDirectory ?? ((p) => {
|
|
@@ -42069,7 +42222,7 @@ var init_doctor_inlined_secrets = __esm(() => {
|
|
|
42069
42222
|
|
|
42070
42223
|
// src/cli/doctor-audit-integrity.ts
|
|
42071
42224
|
import { readFileSync as fsReadFileSync2 } from "node:fs";
|
|
42072
|
-
import { homedir as
|
|
42225
|
+
import { homedir as homedir35 } from "node:os";
|
|
42073
42226
|
import { join as join59 } from "node:path";
|
|
42074
42227
|
function rootWrittenLogs(home2) {
|
|
42075
42228
|
return [
|
|
@@ -42081,7 +42234,7 @@ function rootWrittenLogs(home2) {
|
|
|
42081
42234
|
];
|
|
42082
42235
|
}
|
|
42083
42236
|
function runAuditIntegrityChecks(deps = {}) {
|
|
42084
|
-
const home2 = deps.homeDir ??
|
|
42237
|
+
const home2 = deps.homeDir ?? homedir35();
|
|
42085
42238
|
const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
|
|
42086
42239
|
const results = [];
|
|
42087
42240
|
for (const { label, path: path4 } of rootWrittenLogs(home2)) {
|
|
@@ -42139,13 +42292,13 @@ var init_doctor_audit_integrity = __esm(() => {
|
|
|
42139
42292
|
|
|
42140
42293
|
// src/cli/doctor-agent-smoke.ts
|
|
42141
42294
|
import { existsSync as existsSync59 } from "node:fs";
|
|
42142
|
-
import { homedir as
|
|
42295
|
+
import { homedir as homedir36 } from "node:os";
|
|
42143
42296
|
import { join as join60 } from "node:path";
|
|
42144
42297
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
42145
42298
|
async function runAgentSmokeChecks(config, deps = {}) {
|
|
42146
42299
|
if (deps.fast)
|
|
42147
42300
|
return [];
|
|
42148
|
-
const home2 = deps.homeDir ??
|
|
42301
|
+
const home2 = deps.homeDir ?? homedir36();
|
|
42149
42302
|
const sock = deps.operatorSockPath ?? join60(home2, ".switchroom", "hostd", "operator", "sock");
|
|
42150
42303
|
if (!deps.hostdRequestImpl && !existsSync59(sock)) {
|
|
42151
42304
|
return [
|
|
@@ -42226,7 +42379,7 @@ var init_doctor_agent_smoke = __esm(() => {
|
|
|
42226
42379
|
// src/cli/doctor-vault-broker-durability.ts
|
|
42227
42380
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
42228
42381
|
import { existsSync as existsSync60, statSync as statSync24 } from "node:fs";
|
|
42229
|
-
import { homedir as
|
|
42382
|
+
import { homedir as homedir37 } from "node:os";
|
|
42230
42383
|
import { join as join61 } from "node:path";
|
|
42231
42384
|
function probeBindMountInode(hostPath, brokerContainerPath, opts) {
|
|
42232
42385
|
const statHost = opts?.statHost ?? defaultStatHost;
|
|
@@ -42370,7 +42523,7 @@ function defaultBrokerStatusProbe() {
|
|
|
42370
42523
|
}
|
|
42371
42524
|
}
|
|
42372
42525
|
function runVaultBrokerDurabilityChecks(_config, opts) {
|
|
42373
|
-
const home2 =
|
|
42526
|
+
const home2 = homedir37();
|
|
42374
42527
|
const probe2 = opts?.inodeProbe ?? probeBindMountInode;
|
|
42375
42528
|
return [
|
|
42376
42529
|
probeBrokerUnlocked(opts?.statusProbe),
|
|
@@ -42629,11 +42782,11 @@ import {
|
|
|
42629
42782
|
existsSync as existsSync61,
|
|
42630
42783
|
lstatSync as lstatSync7,
|
|
42631
42784
|
mkdirSync as mkdirSync32,
|
|
42632
|
-
readFileSync as
|
|
42785
|
+
readFileSync as readFileSync55,
|
|
42633
42786
|
readdirSync as readdirSync21,
|
|
42634
42787
|
statSync as statSync25
|
|
42635
42788
|
} from "node:fs";
|
|
42636
|
-
import { dirname as dirname17, join as join62, resolve as
|
|
42789
|
+
import { dirname as dirname17, join as join62, resolve as resolve36 } from "node:path";
|
|
42637
42790
|
import { createPublicKey, createPrivateKey } from "node:crypto";
|
|
42638
42791
|
function findInNvm(bin) {
|
|
42639
42792
|
const nvmRoot = join62(process.env.HOME ?? "", ".nvm", "versions", "node");
|
|
@@ -43441,7 +43594,7 @@ function classifyReadError(err) {
|
|
|
43441
43594
|
}
|
|
43442
43595
|
function tryReadHostFile(path4) {
|
|
43443
43596
|
try {
|
|
43444
|
-
return { kind: "ok", content:
|
|
43597
|
+
return { kind: "ok", content: readFileSync55(path4, "utf-8") };
|
|
43445
43598
|
} catch (err) {
|
|
43446
43599
|
const kind = classifyReadError(err);
|
|
43447
43600
|
const error = err?.message ?? String(err);
|
|
@@ -43457,7 +43610,7 @@ function parseEnvFile(path4) {
|
|
|
43457
43610
|
return {};
|
|
43458
43611
|
let content;
|
|
43459
43612
|
try {
|
|
43460
|
-
content =
|
|
43613
|
+
content = readFileSync55(path4, "utf-8");
|
|
43461
43614
|
} catch {
|
|
43462
43615
|
return {};
|
|
43463
43616
|
}
|
|
@@ -43574,7 +43727,7 @@ function checkStartShStale(agentName, startShPath) {
|
|
|
43574
43727
|
}
|
|
43575
43728
|
let content;
|
|
43576
43729
|
try {
|
|
43577
|
-
content =
|
|
43730
|
+
content = readFileSync55(startShPath, "utf-8");
|
|
43578
43731
|
} catch (err) {
|
|
43579
43732
|
return {
|
|
43580
43733
|
name: label,
|
|
@@ -43687,7 +43840,7 @@ function isSwitchroomCheckout(dir) {
|
|
|
43687
43840
|
const pkgPath = join62(dir, "package.json");
|
|
43688
43841
|
if (!existsSync61(pkgPath))
|
|
43689
43842
|
return false;
|
|
43690
|
-
const pkg = JSON.parse(
|
|
43843
|
+
const pkg = JSON.parse(readFileSync55(pkgPath, "utf-8"));
|
|
43691
43844
|
return pkg.name === "switchroom";
|
|
43692
43845
|
} catch {
|
|
43693
43846
|
return false;
|
|
@@ -43699,7 +43852,7 @@ function checkAgents(config, configPath) {
|
|
|
43699
43852
|
const statuses = getAllAgentStatuses(config);
|
|
43700
43853
|
const authStatuses = getAllAuthStatuses(config);
|
|
43701
43854
|
for (const [name, agentConfig] of Object.entries(config.agents)) {
|
|
43702
|
-
const agentDir =
|
|
43855
|
+
const agentDir = resolve36(agentsDir, name);
|
|
43703
43856
|
if (!existsSync61(agentDir)) {
|
|
43704
43857
|
results.push({
|
|
43705
43858
|
name: `${name}: scaffold`,
|
|
@@ -43808,7 +43961,7 @@ function checkAgents(config, configPath) {
|
|
|
43808
43961
|
});
|
|
43809
43962
|
} else {
|
|
43810
43963
|
try {
|
|
43811
|
-
const mcp = JSON.parse(
|
|
43964
|
+
const mcp = JSON.parse(readFileSync55(mcpJsonPath, "utf-8"));
|
|
43812
43965
|
const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
|
|
43813
43966
|
const memoryEnabled = isHindsightEnabled(config);
|
|
43814
43967
|
const hasHindsight = !!mcp.mcpServers?.hindsight;
|
|
@@ -43882,7 +44035,7 @@ function mffAgentName(config) {
|
|
|
43882
44035
|
function mffEnvPath(config) {
|
|
43883
44036
|
const home2 = process.env.HOME ?? "/root";
|
|
43884
44037
|
const agent = mffAgentName(config);
|
|
43885
|
-
return agent ?
|
|
44038
|
+
return agent ? resolve36(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve36(home2, ".switchroom/credentials/my-family-finance/.env");
|
|
43886
44039
|
}
|
|
43887
44040
|
function mffEnvState(envPath) {
|
|
43888
44041
|
if (!existsSync61(envPath))
|
|
@@ -44298,16 +44451,16 @@ async function checkManifestDrift(probers) {
|
|
|
44298
44451
|
return results;
|
|
44299
44452
|
}
|
|
44300
44453
|
function runDockerSection(config) {
|
|
44301
|
-
const composePath =
|
|
44454
|
+
const composePath = resolve36(process.env.HOME ?? "", ".switchroom", "compose", "docker-compose.yml");
|
|
44302
44455
|
const active = isDockerMode({ composePath });
|
|
44303
44456
|
let composeYaml;
|
|
44304
44457
|
let dockerfileAgent;
|
|
44305
44458
|
try {
|
|
44306
|
-
composeYaml =
|
|
44459
|
+
composeYaml = readFileSync55(composePath, "utf8");
|
|
44307
44460
|
} catch {}
|
|
44308
|
-
const dockerfilePath =
|
|
44461
|
+
const dockerfilePath = resolve36(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
|
|
44309
44462
|
try {
|
|
44310
|
-
dockerfileAgent =
|
|
44463
|
+
dockerfileAgent = readFileSync55(dockerfilePath, "utf8");
|
|
44311
44464
|
} catch {}
|
|
44312
44465
|
return runDockerChecks({
|
|
44313
44466
|
config,
|
|
@@ -47300,7 +47453,7 @@ vault:
|
|
|
47300
47453
|
# Every agent inherits this. Per-agent fields win on conflict.
|
|
47301
47454
|
# tools/skills/schedule are unioned; scalars are overridden.
|
|
47302
47455
|
defaults:
|
|
47303
|
-
model: claude-sonnet-
|
|
47456
|
+
model: claude-sonnet-5
|
|
47304
47457
|
# The switchroom-telegram fork is the default. Uncomment to fall back to
|
|
47305
47458
|
# the upstream marketplace plugin: channels.telegram.plugin: official
|
|
47306
47459
|
channels:
|
|
@@ -47583,7 +47736,7 @@ memory:
|
|
|
47583
47736
|
shared_collection: shared
|
|
47584
47737
|
|
|
47585
47738
|
defaults:
|
|
47586
|
-
model: claude-sonnet-
|
|
47739
|
+
model: claude-sonnet-5
|
|
47587
47740
|
tools:
|
|
47588
47741
|
allow: [all]
|
|
47589
47742
|
subagents:
|
|
@@ -47708,7 +47861,7 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
|
|
|
47708
47861
|
var init_connection_health = () => {};
|
|
47709
47862
|
|
|
47710
47863
|
// src/cli/update-prompt-hook.ts
|
|
47711
|
-
import { existsSync as existsSync77, readFileSync as
|
|
47864
|
+
import { existsSync as existsSync77, readFileSync as readFileSync67, writeFileSync as writeFileSync38, chmodSync as chmodSync10, mkdirSync as mkdirSync44 } from "node:fs";
|
|
47712
47865
|
import { join as join77 } from "node:path";
|
|
47713
47866
|
function containerHookCommand() {
|
|
47714
47867
|
return join77(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
|
|
@@ -47782,7 +47935,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47782
47935
|
const scriptPath = join77(hooksDir, HOOK_FILENAME);
|
|
47783
47936
|
const desired = updatePromptHookScript();
|
|
47784
47937
|
let installed = false;
|
|
47785
|
-
const existing = existsSync77(scriptPath) ?
|
|
47938
|
+
const existing = existsSync77(scriptPath) ? readFileSync67(scriptPath, "utf-8") : "";
|
|
47786
47939
|
if (existing !== desired) {
|
|
47787
47940
|
writeFileSync38(scriptPath, desired, { mode: 493 });
|
|
47788
47941
|
chmodSync10(scriptPath, 493);
|
|
@@ -47796,7 +47949,7 @@ function installUpdatePromptHook(agentDir) {
|
|
|
47796
47949
|
if (!existsSync77(settingsPath)) {
|
|
47797
47950
|
return { scriptPath, settingsPath, installed };
|
|
47798
47951
|
}
|
|
47799
|
-
const raw =
|
|
47952
|
+
const raw = readFileSync67(settingsPath, "utf-8");
|
|
47800
47953
|
let parsed;
|
|
47801
47954
|
try {
|
|
47802
47955
|
parsed = JSON.parse(raw);
|
|
@@ -48020,7 +48173,7 @@ __export(exports_voice_sidecar_token, {
|
|
|
48020
48173
|
VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
|
|
48021
48174
|
});
|
|
48022
48175
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
48023
|
-
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync79, mkdirSync as mkdirSync45, readFileSync as
|
|
48176
|
+
import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync79, mkdirSync as mkdirSync45, readFileSync as readFileSync68, rmSync as rmSync17, writeFileSync as writeFileSync39 } from "node:fs";
|
|
48024
48177
|
import { dirname as dirname26 } from "node:path";
|
|
48025
48178
|
async function defaultResolveOrSeedToken(home2, writeErr) {
|
|
48026
48179
|
const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
|
|
@@ -48056,7 +48209,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48056
48209
|
if (engine !== "local") {
|
|
48057
48210
|
try {
|
|
48058
48211
|
if (existsSync79(envPath)) {
|
|
48059
|
-
const body =
|
|
48212
|
+
const body = readFileSync68(envPath, "utf-8");
|
|
48060
48213
|
if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
|
|
48061
48214
|
rmSync17(envPath);
|
|
48062
48215
|
}
|
|
@@ -48079,7 +48232,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
|
|
|
48079
48232
|
let body = "";
|
|
48080
48233
|
try {
|
|
48081
48234
|
if (existsSync79(envPath))
|
|
48082
|
-
body =
|
|
48235
|
+
body = readFileSync68(envPath, "utf-8");
|
|
48083
48236
|
} catch {}
|
|
48084
48237
|
const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
|
|
48085
48238
|
const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
|
|
@@ -48132,12 +48285,12 @@ __export(exports_apply, {
|
|
|
48132
48285
|
DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
|
|
48133
48286
|
COMPOSE_PROJECT: () => COMPOSE_PROJECT2
|
|
48134
48287
|
});
|
|
48135
|
-
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync80, mkdirSync as mkdirSync46, readFileSync as
|
|
48288
|
+
import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync80, mkdirSync as mkdirSync46, readFileSync as readFileSync69, readdirSync as readdirSync27, renameSync as renameSync15, writeFileSync as writeFileSync40 } from "node:fs";
|
|
48136
48289
|
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
48137
48290
|
import { spawnSync as childSpawnSync } from "node:child_process";
|
|
48138
48291
|
import readline from "node:readline";
|
|
48139
|
-
import { dirname as dirname27, join as join79, resolve as
|
|
48140
|
-
import { homedir as
|
|
48292
|
+
import { dirname as dirname27, join as join79, resolve as resolve49 } from "node:path";
|
|
48293
|
+
import { homedir as homedir47 } from "node:os";
|
|
48141
48294
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
48142
48295
|
function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
|
|
48143
48296
|
return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
|
|
@@ -48175,7 +48328,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48175
48328
|
Promise.resolve().then(() => (init_provision(), exports_provision)),
|
|
48176
48329
|
Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
|
|
48177
48330
|
]);
|
|
48178
|
-
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ??
|
|
48331
|
+
const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir47());
|
|
48179
48332
|
if (passphrase === null) {
|
|
48180
48333
|
const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
48181
48334
|
const alreadyProvisioned = [];
|
|
@@ -48217,7 +48370,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
|
|
|
48217
48370
|
let configText = null;
|
|
48218
48371
|
if (switchroomConfigPath && existsSync80(switchroomConfigPath)) {
|
|
48219
48372
|
try {
|
|
48220
|
-
configText =
|
|
48373
|
+
configText = readFileSync69(switchroomConfigPath, "utf-8");
|
|
48221
48374
|
} catch (err) {
|
|
48222
48375
|
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.
|
|
48223
48376
|
`));
|
|
@@ -48459,7 +48612,7 @@ async function ensureHostMountSources(config) {
|
|
|
48459
48612
|
await mkdir2(fleetDir, { recursive: true });
|
|
48460
48613
|
const invariantsPath = join79(fleetDir, "switchroom-invariants.md");
|
|
48461
48614
|
const invariantsCanonical = renderFleetInvariants();
|
|
48462
|
-
const invariantsCurrent = existsSync80(invariantsPath) ?
|
|
48615
|
+
const invariantsCurrent = existsSync80(invariantsPath) ? readFileSync69(invariantsPath, "utf-8") : null;
|
|
48463
48616
|
if (invariantsCurrent !== invariantsCanonical) {
|
|
48464
48617
|
writeFileSync40(invariantsPath, invariantsCanonical, { mode: 420 });
|
|
48465
48618
|
}
|
|
@@ -48546,7 +48699,7 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
|
|
|
48546
48699
|
`));
|
|
48547
48700
|
}
|
|
48548
48701
|
}
|
|
48549
|
-
function writeInstallTypeCache(homeDir =
|
|
48702
|
+
function writeInstallTypeCache(homeDir = homedir47()) {
|
|
48550
48703
|
const ctx = detectInstallType();
|
|
48551
48704
|
const dir = join79(homeDir, ".switchroom");
|
|
48552
48705
|
const out = join79(dir, "install-type.json");
|
|
@@ -48663,7 +48816,7 @@ Applying switchroom config...
|
|
|
48663
48816
|
writeOut,
|
|
48664
48817
|
writeErr,
|
|
48665
48818
|
failures,
|
|
48666
|
-
home:
|
|
48819
|
+
home: homedir47()
|
|
48667
48820
|
});
|
|
48668
48821
|
}
|
|
48669
48822
|
await ensureHostMountSources(config);
|
|
@@ -48685,7 +48838,7 @@ Applying switchroom config...
|
|
|
48685
48838
|
}
|
|
48686
48839
|
const vaultPathConfigured = config.vault?.path;
|
|
48687
48840
|
const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
|
|
48688
|
-
const migrationResult = migrateVaultLayout(
|
|
48841
|
+
const migrationResult = migrateVaultLayout(homedir47(), {
|
|
48689
48842
|
customVaultPath
|
|
48690
48843
|
});
|
|
48691
48844
|
switch (migrationResult.kind) {
|
|
@@ -48711,7 +48864,7 @@ Applying switchroom config...
|
|
|
48711
48864
|
writeErr(formatDivergentRecoveryMessage(migrationResult.details));
|
|
48712
48865
|
process.exit(4);
|
|
48713
48866
|
}
|
|
48714
|
-
const postMigrationInspect = inspectVaultLayout(
|
|
48867
|
+
const postMigrationInspect = inspectVaultLayout(homedir47());
|
|
48715
48868
|
const acceptable = [
|
|
48716
48869
|
"no-vault",
|
|
48717
48870
|
"already-migrated",
|
|
@@ -48726,7 +48879,7 @@ Expected one of: ${acceptable.join(", ")}
|
|
|
48726
48879
|
`));
|
|
48727
48880
|
process.exit(5);
|
|
48728
48881
|
}
|
|
48729
|
-
const vaultDir = resolveVaultBindMountDir(
|
|
48882
|
+
const vaultDir = resolveVaultBindMountDir(homedir47(), {
|
|
48730
48883
|
migrationKind: migrationResult.kind,
|
|
48731
48884
|
customVaultPath
|
|
48732
48885
|
});
|
|
@@ -48757,7 +48910,7 @@ vault.enc.lock (PID-file flock from saveVault), and
|
|
|
48757
48910
|
});
|
|
48758
48911
|
if (!skipScaffold) {
|
|
48759
48912
|
const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
|
|
48760
|
-
await provisionVoiceSidecarToken2(composePath,
|
|
48913
|
+
await provisionVoiceSidecarToken2(composePath, homedir47(), {
|
|
48761
48914
|
writeOut,
|
|
48762
48915
|
writeErr,
|
|
48763
48916
|
operatorUid
|
|
@@ -48773,7 +48926,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
|
|
|
48773
48926
|
writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
|
|
48774
48927
|
`));
|
|
48775
48928
|
if (process.geteuid?.() === 0 && operatorUid !== undefined) {
|
|
48776
|
-
const restored = restoreOperatorOwnership(
|
|
48929
|
+
const restored = restoreOperatorOwnership(homedir47(), operatorUid);
|
|
48777
48930
|
if (restored.length > 0) {
|
|
48778
48931
|
writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
|
|
48779
48932
|
`));
|
|
@@ -48820,7 +48973,7 @@ function copyExampleConfig2(name) {
|
|
|
48820
48973
|
if (!/^[a-z0-9_-]+$/.test(name)) {
|
|
48821
48974
|
throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
|
|
48822
48975
|
}
|
|
48823
|
-
const dest =
|
|
48976
|
+
const dest = resolve49(process.cwd(), "switchroom.yaml");
|
|
48824
48977
|
if (existsSync80(dest)) {
|
|
48825
48978
|
console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
|
|
48826
48979
|
return;
|
|
@@ -48831,7 +48984,7 @@ function copyExampleConfig2(name) {
|
|
|
48831
48984
|
console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
|
|
48832
48985
|
return;
|
|
48833
48986
|
}
|
|
48834
|
-
const exampleFile =
|
|
48987
|
+
const exampleFile = resolve49(import.meta.dirname, `../../examples/${name}.yaml`);
|
|
48835
48988
|
if (!existsSync80(exampleFile)) {
|
|
48836
48989
|
throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
|
|
48837
48990
|
}
|
|
@@ -49027,7 +49180,7 @@ var init_apply = __esm(() => {
|
|
|
49027
49180
|
switchroom: switchroom_default,
|
|
49028
49181
|
minimal: minimal_default
|
|
49029
49182
|
};
|
|
49030
|
-
DEFAULT_COMPOSE_PATH2 = join79(
|
|
49183
|
+
DEFAULT_COMPOSE_PATH2 = join79(homedir47(), ".switchroom", "compose", "docker-compose.yml");
|
|
49031
49184
|
SELF_ELEVATE_PRESERVED_ENV = [
|
|
49032
49185
|
"HOME",
|
|
49033
49186
|
"SWITCHROOM_CONFIG",
|
|
@@ -54502,7 +54655,7 @@ class Protocol {
|
|
|
54502
54655
|
return;
|
|
54503
54656
|
}
|
|
54504
54657
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
54505
|
-
await new Promise((
|
|
54658
|
+
await new Promise((resolve54) => setTimeout(resolve54, pollInterval));
|
|
54506
54659
|
options?.signal?.throwIfAborted();
|
|
54507
54660
|
}
|
|
54508
54661
|
} catch (error2) {
|
|
@@ -54514,7 +54667,7 @@ class Protocol {
|
|
|
54514
54667
|
}
|
|
54515
54668
|
request(request, resultSchema, options) {
|
|
54516
54669
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
54517
|
-
return new Promise((
|
|
54670
|
+
return new Promise((resolve54, reject) => {
|
|
54518
54671
|
const earlyReject = (error2) => {
|
|
54519
54672
|
reject(error2);
|
|
54520
54673
|
};
|
|
@@ -54592,7 +54745,7 @@ class Protocol {
|
|
|
54592
54745
|
if (!parseResult.success) {
|
|
54593
54746
|
reject(parseResult.error);
|
|
54594
54747
|
} else {
|
|
54595
|
-
|
|
54748
|
+
resolve54(parseResult.data);
|
|
54596
54749
|
}
|
|
54597
54750
|
} catch (error2) {
|
|
54598
54751
|
reject(error2);
|
|
@@ -54783,12 +54936,12 @@ class Protocol {
|
|
|
54783
54936
|
interval = task.pollInterval;
|
|
54784
54937
|
}
|
|
54785
54938
|
} catch {}
|
|
54786
|
-
return new Promise((
|
|
54939
|
+
return new Promise((resolve54, reject) => {
|
|
54787
54940
|
if (signal.aborted) {
|
|
54788
54941
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
54789
54942
|
return;
|
|
54790
54943
|
}
|
|
54791
|
-
const timeoutId = setTimeout(
|
|
54944
|
+
const timeoutId = setTimeout(resolve54, interval);
|
|
54792
54945
|
signal.addEventListener("abort", () => {
|
|
54793
54946
|
clearTimeout(timeoutId);
|
|
54794
54947
|
reject(new McpError(ErrorCode2.InvalidRequest, "Request cancelled"));
|
|
@@ -57773,7 +57926,7 @@ var require_compile = __commonJS((exports2) => {
|
|
|
57773
57926
|
const schOrFunc = root.refs[ref];
|
|
57774
57927
|
if (schOrFunc)
|
|
57775
57928
|
return schOrFunc;
|
|
57776
|
-
let _sch =
|
|
57929
|
+
let _sch = resolve54.call(this, root, ref);
|
|
57777
57930
|
if (_sch === undefined) {
|
|
57778
57931
|
const schema = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
|
|
57779
57932
|
const { schemaId } = this.opts;
|
|
@@ -57800,7 +57953,7 @@ var require_compile = __commonJS((exports2) => {
|
|
|
57800
57953
|
function sameSchemaEnv(s1, s2) {
|
|
57801
57954
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
57802
57955
|
}
|
|
57803
|
-
function
|
|
57956
|
+
function resolve54(root, ref) {
|
|
57804
57957
|
let sch;
|
|
57805
57958
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
57806
57959
|
ref = sch;
|
|
@@ -58330,7 +58483,7 @@ var require_fast_uri = __commonJS((exports2, module) => {
|
|
|
58330
58483
|
}
|
|
58331
58484
|
return uri;
|
|
58332
58485
|
}
|
|
58333
|
-
function
|
|
58486
|
+
function resolve54(baseURI, relativeURI, options) {
|
|
58334
58487
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
58335
58488
|
const resolved = resolveComponent(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
58336
58489
|
schemelessOptions.skipEscape = true;
|
|
@@ -58558,7 +58711,7 @@ var require_fast_uri = __commonJS((exports2, module) => {
|
|
|
58558
58711
|
var fastUri = {
|
|
58559
58712
|
SCHEMES,
|
|
58560
58713
|
normalize,
|
|
58561
|
-
resolve:
|
|
58714
|
+
resolve: resolve54,
|
|
58562
58715
|
resolveComponent,
|
|
58563
58716
|
equal,
|
|
58564
58717
|
serialize,
|
|
@@ -61941,12 +62094,12 @@ class StdioServerTransport {
|
|
|
61941
62094
|
this.onclose?.();
|
|
61942
62095
|
}
|
|
61943
62096
|
send(message) {
|
|
61944
|
-
return new Promise((
|
|
62097
|
+
return new Promise((resolve54) => {
|
|
61945
62098
|
const json = serializeMessage(message);
|
|
61946
62099
|
if (this._stdout.write(json)) {
|
|
61947
|
-
|
|
62100
|
+
resolve54();
|
|
61948
62101
|
} else {
|
|
61949
|
-
this._stdout.once("drain",
|
|
62102
|
+
this._stdout.once("drain", resolve54);
|
|
61950
62103
|
}
|
|
61951
62104
|
});
|
|
61952
62105
|
}
|
|
@@ -62472,7 +62625,7 @@ __export(exports_server2, {
|
|
|
62472
62625
|
TOOLS: () => TOOLS2
|
|
62473
62626
|
});
|
|
62474
62627
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
62475
|
-
import { existsSync as existsSync89, readFileSync as
|
|
62628
|
+
import { existsSync as existsSync89, readFileSync as readFileSync78 } from "node:fs";
|
|
62476
62629
|
function selfSocketPath() {
|
|
62477
62630
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
62478
62631
|
}
|
|
@@ -62689,7 +62842,7 @@ function getLastUpdateApplyStatus() {
|
|
|
62689
62842
|
}
|
|
62690
62843
|
let raw;
|
|
62691
62844
|
try {
|
|
62692
|
-
raw =
|
|
62845
|
+
raw = readFileSync78(path8, "utf-8");
|
|
62693
62846
|
} catch (err2) {
|
|
62694
62847
|
return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
|
|
62695
62848
|
}
|
|
@@ -62968,6 +63121,475 @@ var init_server4 = __esm(() => {
|
|
|
62968
63121
|
];
|
|
62969
63122
|
});
|
|
62970
63123
|
|
|
63124
|
+
// src/fleet-health/detect.ts
|
|
63125
|
+
function parseTurns(text) {
|
|
63126
|
+
const out = [];
|
|
63127
|
+
for (const raw of text.split(`
|
|
63128
|
+
`)) {
|
|
63129
|
+
const line = raw.trim();
|
|
63130
|
+
if (!line)
|
|
63131
|
+
continue;
|
|
63132
|
+
try {
|
|
63133
|
+
out.push(JSON.parse(line));
|
|
63134
|
+
} catch {}
|
|
63135
|
+
}
|
|
63136
|
+
return out;
|
|
63137
|
+
}
|
|
63138
|
+
function isoFromTs(ts) {
|
|
63139
|
+
if (typeof ts !== "number" || !Number.isFinite(ts))
|
|
63140
|
+
return null;
|
|
63141
|
+
return new Date(ts * 1000).toISOString();
|
|
63142
|
+
}
|
|
63143
|
+
function detectTurnFindings(agent, turns) {
|
|
63144
|
+
const findings = [];
|
|
63145
|
+
for (const t of turns) {
|
|
63146
|
+
const tid = t.turn_id ?? "?";
|
|
63147
|
+
const st = t.status;
|
|
63148
|
+
const tl = typeof t.tools === "number" ? t.tools : 0;
|
|
63149
|
+
const dur = typeof t.duration_ms === "number" ? t.duration_ms : 0;
|
|
63150
|
+
const synthetic = tid.includes("synthetic-");
|
|
63151
|
+
const ts = isoFromTs(t.ts);
|
|
63152
|
+
if (st !== "complete" && st !== "no_reply") {
|
|
63153
|
+
findings.push({
|
|
63154
|
+
signal: "killed-incomplete-turn",
|
|
63155
|
+
agent,
|
|
63156
|
+
turn_id: tid,
|
|
63157
|
+
log_pointer: `turns.jsonl:${tid} status=${st}`,
|
|
63158
|
+
ts
|
|
63159
|
+
});
|
|
63160
|
+
}
|
|
63161
|
+
if (dur > HANG_MS && tl <= HANG_MAXTOOLS) {
|
|
63162
|
+
findings.push({
|
|
63163
|
+
signal: "hang-long-stalled",
|
|
63164
|
+
agent,
|
|
63165
|
+
turn_id: tid,
|
|
63166
|
+
log_pointer: `turns.jsonl:${tid} ${Math.floor(dur / 1000)}s tools=${tl}`,
|
|
63167
|
+
ts
|
|
63168
|
+
});
|
|
63169
|
+
}
|
|
63170
|
+
if (st === "complete" && tl === 0 && !synthetic) {
|
|
63171
|
+
findings.push({
|
|
63172
|
+
signal: "silent-no-op-candidate",
|
|
63173
|
+
agent,
|
|
63174
|
+
turn_id: tid,
|
|
63175
|
+
log_pointer: `turns.jsonl:${tid} tools=0`,
|
|
63176
|
+
ts
|
|
63177
|
+
});
|
|
63178
|
+
}
|
|
63179
|
+
}
|
|
63180
|
+
return findings;
|
|
63181
|
+
}
|
|
63182
|
+
function detectGatewayFindings(agent, logText, logName = `logs/${agent}/gateway-supervisor.log`) {
|
|
63183
|
+
const findings = [];
|
|
63184
|
+
const gw_hits = {
|
|
63185
|
+
"duplicate-delivery-represent": 0,
|
|
63186
|
+
"represent-escalation": 0,
|
|
63187
|
+
"reply-delivery-failure": 0
|
|
63188
|
+
};
|
|
63189
|
+
const lines = logText.split(`
|
|
63190
|
+
`);
|
|
63191
|
+
for (const [name, re] of Object.entries(GATEWAY_SIGNATURES)) {
|
|
63192
|
+
for (let i = 0;i < lines.length; i++) {
|
|
63193
|
+
const line = lines[i];
|
|
63194
|
+
if (!line)
|
|
63195
|
+
continue;
|
|
63196
|
+
if (re.test(line)) {
|
|
63197
|
+
gw_hits[name] += 1;
|
|
63198
|
+
findings.push({
|
|
63199
|
+
signal: name,
|
|
63200
|
+
agent,
|
|
63201
|
+
turn_id: extractTurnId(line) ?? `${agent}:gw#${i + 1}`,
|
|
63202
|
+
log_pointer: `${logName}:${i + 1}`,
|
|
63203
|
+
ts: extractTs(line)
|
|
63204
|
+
});
|
|
63205
|
+
}
|
|
63206
|
+
}
|
|
63207
|
+
}
|
|
63208
|
+
return { findings, gw_hits };
|
|
63209
|
+
}
|
|
63210
|
+
function extractTurnId(line) {
|
|
63211
|
+
const m = line.match(/\d+:_#\d+/);
|
|
63212
|
+
return m ? m[0] : null;
|
|
63213
|
+
}
|
|
63214
|
+
function extractTs(line) {
|
|
63215
|
+
const m = line.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/);
|
|
63216
|
+
return m ? m[0] : null;
|
|
63217
|
+
}
|
|
63218
|
+
function scanAgent(agent, turnsText, gatewayText) {
|
|
63219
|
+
const turns = parseTurns(turnsText);
|
|
63220
|
+
const status_mix = {};
|
|
63221
|
+
for (const t of turns) {
|
|
63222
|
+
const st = t.status ?? "unknown";
|
|
63223
|
+
status_mix[st] = (status_mix[st] ?? 0) + 1;
|
|
63224
|
+
}
|
|
63225
|
+
const turnFindings = detectTurnFindings(agent, turns);
|
|
63226
|
+
const { findings: gwFindings, gw_hits } = detectGatewayFindings(agent, gatewayText);
|
|
63227
|
+
const findings = [...turnFindings, ...gwFindings];
|
|
63228
|
+
const escalate = turnFindings.some((f) => f.signal === "killed-incomplete-turn" || f.signal === "hang-long-stalled" || f.signal === "silent-no-op-candidate") || gw_hits["duplicate-delivery-represent"] > 0 || gw_hits["reply-delivery-failure"] > 0;
|
|
63229
|
+
return { agent, turns: turns.length, status_mix, findings, gw_hits, escalate };
|
|
63230
|
+
}
|
|
63231
|
+
var HANG_MS = 360000, HANG_MAXTOOLS = 2, GATEWAY_SIGNATURES;
|
|
63232
|
+
var init_detect = __esm(() => {
|
|
63233
|
+
GATEWAY_SIGNATURES = {
|
|
63234
|
+
"duplicate-delivery-represent": /represent duplicate-send/,
|
|
63235
|
+
"represent-escalation": /obligation escalation/,
|
|
63236
|
+
"reply-delivery-failure": /tg-post method=sendRichMessage[^\n]*status=err/
|
|
63237
|
+
};
|
|
63238
|
+
});
|
|
63239
|
+
|
|
63240
|
+
// src/fleet-health/mapping.ts
|
|
63241
|
+
function mapSignal(signal) {
|
|
63242
|
+
return SIGNAL_MAP[signal];
|
|
63243
|
+
}
|
|
63244
|
+
function dedupKeyFor(finding) {
|
|
63245
|
+
const m = mapSignal(finding.signal);
|
|
63246
|
+
return `${m.job_spec}:${m.signature}`;
|
|
63247
|
+
}
|
|
63248
|
+
function frequencyFactor(count) {
|
|
63249
|
+
return Math.log10(1 + Math.max(0, count));
|
|
63250
|
+
}
|
|
63251
|
+
function recencyFactor(newestIso, now = new Date, windowDays = 30) {
|
|
63252
|
+
if (!newestIso)
|
|
63253
|
+
return 0;
|
|
63254
|
+
const newest = Date.parse(newestIso);
|
|
63255
|
+
if (!Number.isFinite(newest))
|
|
63256
|
+
return 0;
|
|
63257
|
+
const ageMs = now.getTime() - newest;
|
|
63258
|
+
const dayMs = 86400000;
|
|
63259
|
+
if (ageMs <= dayMs)
|
|
63260
|
+
return 1;
|
|
63261
|
+
const windowMs = windowDays * dayMs;
|
|
63262
|
+
if (ageMs >= windowMs)
|
|
63263
|
+
return 0.1;
|
|
63264
|
+
const frac = (ageMs - dayMs) / (windowMs - dayMs);
|
|
63265
|
+
return 1 - frac * 0.9;
|
|
63266
|
+
}
|
|
63267
|
+
function reachFactor(reachCount) {
|
|
63268
|
+
return Math.max(1, reachCount);
|
|
63269
|
+
}
|
|
63270
|
+
function issuePriority(severity, frequency, reachCount, newestIso, now = new Date, windowDays = 30) {
|
|
63271
|
+
return severity * frequencyFactor(frequency) * reachFactor(reachCount) * recencyFactor(newestIso, now, windowDays);
|
|
63272
|
+
}
|
|
63273
|
+
var SIGNAL_MAP, ALL_JOB_SPECS;
|
|
63274
|
+
var init_mapping = __esm(() => {
|
|
63275
|
+
SIGNAL_MAP = {
|
|
63276
|
+
"silent-no-op-candidate": {
|
|
63277
|
+
failure_mode: "silent-no-op",
|
|
63278
|
+
severity: 3,
|
|
63279
|
+
job_spec: "know-what-my-agent-is-doing",
|
|
63280
|
+
signature: "silent-no-op:completed-zero-tools"
|
|
63281
|
+
},
|
|
63282
|
+
"duplicate-delivery-represent": {
|
|
63283
|
+
failure_mode: "duplicate",
|
|
63284
|
+
severity: 2,
|
|
63285
|
+
job_spec: "talk-to-agents-from-anywhere",
|
|
63286
|
+
signature: "represent-duplicate-send:reply-tool-not-called"
|
|
63287
|
+
},
|
|
63288
|
+
"reply-delivery-failure": {
|
|
63289
|
+
failure_mode: "success-theater",
|
|
63290
|
+
severity: 3,
|
|
63291
|
+
job_spec: "talk-to-agents-from-anywhere",
|
|
63292
|
+
signature: "reply-delivery-failure:sendRichMessage-err"
|
|
63293
|
+
},
|
|
63294
|
+
"hang-long-stalled": {
|
|
63295
|
+
failure_mode: "partial",
|
|
63296
|
+
severity: 2,
|
|
63297
|
+
job_spec: "steer-or-queue-mid-flight",
|
|
63298
|
+
signature: "hang:long-stalled-turn"
|
|
63299
|
+
},
|
|
63300
|
+
"killed-incomplete-turn": {
|
|
63301
|
+
failure_mode: "missed-trigger",
|
|
63302
|
+
severity: 3,
|
|
63303
|
+
job_spec: "steer-or-queue-mid-flight",
|
|
63304
|
+
signature: "killed:incomplete-turn"
|
|
63305
|
+
},
|
|
63306
|
+
"represent-escalation": {
|
|
63307
|
+
failure_mode: "drift",
|
|
63308
|
+
severity: 1,
|
|
63309
|
+
job_spec: "feel-like-a-colleague",
|
|
63310
|
+
signature: "represent:obligation-escalation"
|
|
63311
|
+
}
|
|
63312
|
+
};
|
|
63313
|
+
ALL_JOB_SPECS = [
|
|
63314
|
+
"act-in-my-tools-with-an-identity",
|
|
63315
|
+
"approve-what-my-agent-can-touch",
|
|
63316
|
+
"crons-use-the-model-only-when-it-earns-it",
|
|
63317
|
+
"deliver-files-i-can-open",
|
|
63318
|
+
"extend-without-forking",
|
|
63319
|
+
"feel-like-a-colleague",
|
|
63320
|
+
"fleet-stays-healthy",
|
|
63321
|
+
"get-better-the-longer-they-run",
|
|
63322
|
+
"get-from-zero-to-a-working-fleet",
|
|
63323
|
+
"give-each-agent-its-own-workspace",
|
|
63324
|
+
"idempotent-update-and-restart",
|
|
63325
|
+
"keep-my-subscription-honest",
|
|
63326
|
+
"know-what-my-agent-is-doing",
|
|
63327
|
+
"operate-the-fleet-from-telegram",
|
|
63328
|
+
"remember-across-sessions",
|
|
63329
|
+
"restart-and-know-what-im-running",
|
|
63330
|
+
"run-a-fleet-of-specialists",
|
|
63331
|
+
"see-my-whole-fleet-from-one-screen",
|
|
63332
|
+
"share-auth-across-the-fleet",
|
|
63333
|
+
"steer-or-queue-mid-flight",
|
|
63334
|
+
"survive-reboots-and-real-life",
|
|
63335
|
+
"talk-to-agents-from-anywhere",
|
|
63336
|
+
"track-plan-quota-live"
|
|
63337
|
+
];
|
|
63338
|
+
});
|
|
63339
|
+
|
|
63340
|
+
// src/fleet-health/ledger.ts
|
|
63341
|
+
function indexPriorIssues(prior) {
|
|
63342
|
+
const idx = new Map;
|
|
63343
|
+
if (!prior?.records)
|
|
63344
|
+
return idx;
|
|
63345
|
+
for (const rec of prior.records) {
|
|
63346
|
+
for (const iss of rec.issues ?? []) {
|
|
63347
|
+
idx.set(iss.dedup_key, {
|
|
63348
|
+
gh_issue: iss.gh_issue,
|
|
63349
|
+
frequency: iss.frequency,
|
|
63350
|
+
status: iss.status,
|
|
63351
|
+
job_spec: rec.job_spec,
|
|
63352
|
+
failure_mode: iss.failure_mode,
|
|
63353
|
+
severity: iss.severity
|
|
63354
|
+
});
|
|
63355
|
+
}
|
|
63356
|
+
}
|
|
63357
|
+
return idx;
|
|
63358
|
+
}
|
|
63359
|
+
function newer(a, b) {
|
|
63360
|
+
if (!a)
|
|
63361
|
+
return b;
|
|
63362
|
+
if (!b)
|
|
63363
|
+
return a;
|
|
63364
|
+
return Date.parse(a) >= Date.parse(b) ? a : b;
|
|
63365
|
+
}
|
|
63366
|
+
function buildLedger(findings, opts = {}) {
|
|
63367
|
+
const now = opts.now ?? new Date;
|
|
63368
|
+
const windowDays = opts.windowDays ?? 30;
|
|
63369
|
+
const priorIdx = indexPriorIssues(opts.prior ?? null);
|
|
63370
|
+
const nowIso = now.toISOString();
|
|
63371
|
+
const aggs = new Map;
|
|
63372
|
+
for (const f of findings) {
|
|
63373
|
+
const m = mapSignal(f.signal);
|
|
63374
|
+
const key = dedupKeyFor(f);
|
|
63375
|
+
let agg = aggs.get(key);
|
|
63376
|
+
if (!agg) {
|
|
63377
|
+
agg = {
|
|
63378
|
+
dedup_key: key,
|
|
63379
|
+
job_spec: m.job_spec,
|
|
63380
|
+
failure_mode: m.failure_mode,
|
|
63381
|
+
severity: m.severity,
|
|
63382
|
+
occurrences: [],
|
|
63383
|
+
reach: new Set,
|
|
63384
|
+
newest: null,
|
|
63385
|
+
count: 0
|
|
63386
|
+
};
|
|
63387
|
+
aggs.set(key, agg);
|
|
63388
|
+
}
|
|
63389
|
+
agg.count += 1;
|
|
63390
|
+
agg.reach.add(f.agent);
|
|
63391
|
+
agg.newest = newer(agg.newest, f.ts);
|
|
63392
|
+
if (agg.occurrences.length < 50) {
|
|
63393
|
+
agg.occurrences.push({
|
|
63394
|
+
agent: f.agent,
|
|
63395
|
+
turn_id: f.turn_id,
|
|
63396
|
+
log_pointer: f.log_pointer
|
|
63397
|
+
});
|
|
63398
|
+
}
|
|
63399
|
+
}
|
|
63400
|
+
const byJob = new Map;
|
|
63401
|
+
for (const agg of aggs.values()) {
|
|
63402
|
+
const count = agg.count;
|
|
63403
|
+
const prior = priorIdx.get(agg.dedup_key);
|
|
63404
|
+
let status = "open";
|
|
63405
|
+
if (count <= RESOLVED_THRESHOLD && prior && prior.frequency > RESOLVED_THRESHOLD) {
|
|
63406
|
+
status = "resolved-pending-verify";
|
|
63407
|
+
} else if (prior?.status === "resolved-pending-verify" && count <= RESOLVED_THRESHOLD) {
|
|
63408
|
+
status = "closed";
|
|
63409
|
+
}
|
|
63410
|
+
const issue2 = {
|
|
63411
|
+
dedup_key: agg.dedup_key,
|
|
63412
|
+
failure_mode: agg.failure_mode,
|
|
63413
|
+
severity: agg.severity,
|
|
63414
|
+
frequency: count,
|
|
63415
|
+
reach: [...agg.reach].sort(),
|
|
63416
|
+
recency: agg.newest,
|
|
63417
|
+
occurrences: agg.occurrences,
|
|
63418
|
+
...prior?.gh_issue !== undefined ? { gh_issue: prior.gh_issue } : {},
|
|
63419
|
+
status
|
|
63420
|
+
};
|
|
63421
|
+
const list2 = byJob.get(agg.job_spec) ?? [];
|
|
63422
|
+
list2.push(issue2);
|
|
63423
|
+
byJob.set(agg.job_spec, list2);
|
|
63424
|
+
}
|
|
63425
|
+
for (const [dedup_key, prior] of priorIdx) {
|
|
63426
|
+
if (aggs.has(dedup_key))
|
|
63427
|
+
continue;
|
|
63428
|
+
if (prior.status !== "open" && prior.status !== "resolved-pending-verify") {
|
|
63429
|
+
continue;
|
|
63430
|
+
}
|
|
63431
|
+
const issue2 = {
|
|
63432
|
+
dedup_key,
|
|
63433
|
+
failure_mode: prior.failure_mode,
|
|
63434
|
+
severity: prior.severity,
|
|
63435
|
+
frequency: 0,
|
|
63436
|
+
reach: [],
|
|
63437
|
+
recency: null,
|
|
63438
|
+
occurrences: [],
|
|
63439
|
+
...prior.gh_issue !== undefined ? { gh_issue: prior.gh_issue } : {},
|
|
63440
|
+
status: "closed"
|
|
63441
|
+
};
|
|
63442
|
+
const list2 = byJob.get(prior.job_spec) ?? [];
|
|
63443
|
+
list2.push(issue2);
|
|
63444
|
+
byJob.set(prior.job_spec, list2);
|
|
63445
|
+
}
|
|
63446
|
+
const records = ALL_JOB_SPECS.map((job_spec) => {
|
|
63447
|
+
const issues = byJob.get(job_spec) ?? [];
|
|
63448
|
+
const openIssues = issues.filter((i) => i.status === "open");
|
|
63449
|
+
const priority_score = issues.reduce((max, i) => {
|
|
63450
|
+
const s = issuePriority(i.severity, i.frequency, i.reach.length, i.recency, now, windowDays);
|
|
63451
|
+
return Math.max(max, s);
|
|
63452
|
+
}, 0);
|
|
63453
|
+
const gh_issues = issues.filter((i) => i.status !== "closed").map((i) => i.gh_issue).filter((n) => typeof n === "number");
|
|
63454
|
+
return {
|
|
63455
|
+
job_spec,
|
|
63456
|
+
open_issue_count: openIssues.length,
|
|
63457
|
+
last_scanned: nowIso,
|
|
63458
|
+
priority_score: Number(priority_score.toFixed(4)),
|
|
63459
|
+
gh_issues,
|
|
63460
|
+
last_deep_dive: findLastDeepDive(opts.prior ?? null, job_spec),
|
|
63461
|
+
issues
|
|
63462
|
+
};
|
|
63463
|
+
});
|
|
63464
|
+
return {
|
|
63465
|
+
...opts.ownerAgent ? { owner_agent: opts.ownerAgent } : {},
|
|
63466
|
+
generated_at: nowIso,
|
|
63467
|
+
...opts.summary ? { summary: opts.summary } : {},
|
|
63468
|
+
records
|
|
63469
|
+
};
|
|
63470
|
+
}
|
|
63471
|
+
function findLastDeepDive(prior, job_spec) {
|
|
63472
|
+
if (!prior?.records)
|
|
63473
|
+
return null;
|
|
63474
|
+
const rec = prior.records.find((r) => r.job_spec === job_spec);
|
|
63475
|
+
return rec?.last_deep_dive ?? null;
|
|
63476
|
+
}
|
|
63477
|
+
var RESOLVED_THRESHOLD = 3;
|
|
63478
|
+
var init_ledger = __esm(() => {
|
|
63479
|
+
init_mapping();
|
|
63480
|
+
});
|
|
63481
|
+
|
|
63482
|
+
// src/fleet-health/scan.ts
|
|
63483
|
+
var exports_scan = {};
|
|
63484
|
+
__export(exports_scan, {
|
|
63485
|
+
writeLedger: () => writeLedger,
|
|
63486
|
+
runScan: () => runScan,
|
|
63487
|
+
resolveSwitchroomBase: () => resolveSwitchroomBase,
|
|
63488
|
+
readLedgerIfPresent: () => readLedgerIfPresent,
|
|
63489
|
+
listAgents: () => listAgents,
|
|
63490
|
+
ledgerPathForBase: () => ledgerPathForBase
|
|
63491
|
+
});
|
|
63492
|
+
import {
|
|
63493
|
+
readFileSync as readFileSync80,
|
|
63494
|
+
readdirSync as readdirSync35,
|
|
63495
|
+
existsSync as existsSync92,
|
|
63496
|
+
mkdirSync as mkdirSync53,
|
|
63497
|
+
writeFileSync as writeFileSync46
|
|
63498
|
+
} from "node:fs";
|
|
63499
|
+
import { resolve as resolve54, dirname as dirname30 } from "node:path";
|
|
63500
|
+
import { homedir as homedir56 } from "node:os";
|
|
63501
|
+
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir56()) {
|
|
63502
|
+
return resolve54(home2, ".switchroom");
|
|
63503
|
+
}
|
|
63504
|
+
function listAgents(base) {
|
|
63505
|
+
const dir = resolve54(base, "agents");
|
|
63506
|
+
if (!existsSync92(dir))
|
|
63507
|
+
return [];
|
|
63508
|
+
try {
|
|
63509
|
+
return readdirSync35(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
63510
|
+
} catch {
|
|
63511
|
+
return [];
|
|
63512
|
+
}
|
|
63513
|
+
}
|
|
63514
|
+
function runScan(opts = {}) {
|
|
63515
|
+
const base = opts.base ?? resolveSwitchroomBase();
|
|
63516
|
+
const log = opts.log ?? (() => {});
|
|
63517
|
+
const agents = listAgents(base);
|
|
63518
|
+
const findings = [];
|
|
63519
|
+
const perAgent = [];
|
|
63520
|
+
const skipped = [];
|
|
63521
|
+
let scanned = 0;
|
|
63522
|
+
for (const agent of agents) {
|
|
63523
|
+
const turnsPath = resolve54(base, "agents", agent, "turns.jsonl");
|
|
63524
|
+
const gwPath = resolve54(base, "logs", agent, "gateway-supervisor.log");
|
|
63525
|
+
let turnsText = "";
|
|
63526
|
+
let gwText = "";
|
|
63527
|
+
let sawArtifact = false;
|
|
63528
|
+
try {
|
|
63529
|
+
if (existsSync92(turnsPath)) {
|
|
63530
|
+
turnsText = readFileSync80(turnsPath, "utf-8");
|
|
63531
|
+
sawArtifact = true;
|
|
63532
|
+
}
|
|
63533
|
+
} catch (e) {
|
|
63534
|
+
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
63535
|
+
}
|
|
63536
|
+
try {
|
|
63537
|
+
if (existsSync92(gwPath)) {
|
|
63538
|
+
gwText = readFileSync80(gwPath, "utf-8");
|
|
63539
|
+
sawArtifact = true;
|
|
63540
|
+
}
|
|
63541
|
+
} catch (e) {
|
|
63542
|
+
log(`fleet-health: WARN ${agent} gateway log unreadable: ${String(e)}`);
|
|
63543
|
+
}
|
|
63544
|
+
if (!sawArtifact) {
|
|
63545
|
+
skipped.push(agent);
|
|
63546
|
+
continue;
|
|
63547
|
+
}
|
|
63548
|
+
try {
|
|
63549
|
+
const res = scanAgent(agent, turnsText, gwText);
|
|
63550
|
+
perAgent.push(res);
|
|
63551
|
+
findings.push(...res.findings);
|
|
63552
|
+
scanned++;
|
|
63553
|
+
} catch (e) {
|
|
63554
|
+
log(`fleet-health: WARN scan failed for ${agent}: ${String(e)}`);
|
|
63555
|
+
skipped.push(agent);
|
|
63556
|
+
}
|
|
63557
|
+
}
|
|
63558
|
+
const prior = readLedgerIfPresent(base);
|
|
63559
|
+
const ledger = buildLedger(findings, {
|
|
63560
|
+
ownerAgent: opts.ownerAgent,
|
|
63561
|
+
now: opts.now,
|
|
63562
|
+
windowDays: opts.windowDays,
|
|
63563
|
+
prior
|
|
63564
|
+
});
|
|
63565
|
+
return { ledger, perAgent, agentsScanned: scanned, agentsSkipped: skipped };
|
|
63566
|
+
}
|
|
63567
|
+
function readLedgerIfPresent(base) {
|
|
63568
|
+
const path8 = ledgerPathForBase(base);
|
|
63569
|
+
try {
|
|
63570
|
+
if (!existsSync92(path8))
|
|
63571
|
+
return null;
|
|
63572
|
+
return JSON.parse(readFileSync80(path8, "utf-8"));
|
|
63573
|
+
} catch {
|
|
63574
|
+
return null;
|
|
63575
|
+
}
|
|
63576
|
+
}
|
|
63577
|
+
function ledgerPathForBase(base) {
|
|
63578
|
+
return fleetHealthLedgerPath(dirname30(base));
|
|
63579
|
+
}
|
|
63580
|
+
function writeLedger(base, ledger) {
|
|
63581
|
+
const path8 = ledgerPathForBase(base);
|
|
63582
|
+
mkdirSync53(dirname30(path8), { recursive: true });
|
|
63583
|
+
writeFileSync46(path8, JSON.stringify(ledger, null, 2) + `
|
|
63584
|
+
`, "utf-8");
|
|
63585
|
+
return path8;
|
|
63586
|
+
}
|
|
63587
|
+
var init_scan = __esm(() => {
|
|
63588
|
+
init_fleet_health_read();
|
|
63589
|
+
init_detect();
|
|
63590
|
+
init_ledger();
|
|
63591
|
+
});
|
|
63592
|
+
|
|
62971
63593
|
// src/cli/index.ts
|
|
62972
63594
|
init_esm();
|
|
62973
63595
|
|
|
@@ -62976,8 +63598,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
62976
63598
|
import { dirname, join } from "node:path";
|
|
62977
63599
|
|
|
62978
63600
|
// src/build-info.ts
|
|
62979
|
-
var VERSION = "0.
|
|
62980
|
-
var COMMIT_SHA = "
|
|
63601
|
+
var VERSION = "0.15.48";
|
|
63602
|
+
var COMMIT_SHA = "eee77212";
|
|
62981
63603
|
|
|
62982
63604
|
// src/cli/resolve-version.ts
|
|
62983
63605
|
function readPackageVersion() {
|
|
@@ -76615,8 +77237,16 @@ var HINDSIGHT_DEFAULT_UI_PORT = 9999;
|
|
|
76615
77237
|
var HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000;
|
|
76616
77238
|
var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
76617
77239
|
var HINDSIGHT_DEFAULT_UID = 11000;
|
|
76618
|
-
var
|
|
76619
|
-
var
|
|
77240
|
+
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
77241
|
+
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
77242
|
+
function hindsightImageRef(tag) {
|
|
77243
|
+
const t = tag?.trim();
|
|
77244
|
+
if (!t)
|
|
77245
|
+
return HINDSIGHT_IMAGE;
|
|
77246
|
+
const v = t.replace(/^v/, "");
|
|
77247
|
+
return `${HINDSIGHT_IMAGE_REPO}:v${v}`;
|
|
77248
|
+
}
|
|
77249
|
+
var HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5";
|
|
76620
77250
|
var HINDSIGHT_DEFAULT_MCP_STATELESS = true;
|
|
76621
77251
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
76622
77252
|
var HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true";
|
|
@@ -76693,7 +77323,7 @@ function isHindsightContainerExists() {
|
|
|
76693
77323
|
return false;
|
|
76694
77324
|
}
|
|
76695
77325
|
}
|
|
76696
|
-
function startHindsight(ports, litellm) {
|
|
77326
|
+
function startHindsight(ports, litellm, imageTag) {
|
|
76697
77327
|
const apiPort = ports?.apiPort ?? HINDSIGHT_DEFAULT_API_PORT;
|
|
76698
77328
|
const uiPort = ports?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
|
|
76699
77329
|
const envArgs = [
|
|
@@ -76756,7 +77386,7 @@ x-litellm-tags: service:hindsight`);
|
|
|
76756
77386
|
} else {
|
|
76757
77387
|
args.push("-p", `127.0.0.1:${apiPort}:8888`, "-p", `127.0.0.1:${uiPort}:9999`);
|
|
76758
77388
|
}
|
|
76759
|
-
args.push("-v", "switchroom-hindsight-data:/home/hindsight/.pg0", "-v", "switchroom-hindsight-backups:/backups", "-v", `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`, "--tmpfs", `/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`, ...envArgs,
|
|
77389
|
+
args.push("-v", "switchroom-hindsight-data:/home/hindsight/.pg0", "-v", "switchroom-hindsight-backups:/backups", "-v", `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`, "--tmpfs", `/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`, ...envArgs, hindsightImageRef(imageTag));
|
|
76760
77390
|
execFileSync16("docker", args, { stdio: "pipe" });
|
|
76761
77391
|
}
|
|
76762
77392
|
function stopHindsight() {
|
|
@@ -76767,8 +77397,8 @@ function stopHindsight() {
|
|
|
76767
77397
|
execFileSync16("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
|
|
76768
77398
|
} catch {}
|
|
76769
77399
|
}
|
|
76770
|
-
function pullHindsightImage() {
|
|
76771
|
-
execFileSync16("docker", ["pull",
|
|
77400
|
+
function pullHindsightImage(imageTag) {
|
|
77401
|
+
execFileSync16("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
|
|
76772
77402
|
}
|
|
76773
77403
|
function getRunningHindsightPorts() {
|
|
76774
77404
|
const readPort = (containerPort) => {
|
|
@@ -77055,7 +77685,7 @@ Cross-agent reflection plan
|
|
|
77055
77685
|
}
|
|
77056
77686
|
console.log();
|
|
77057
77687
|
}));
|
|
77058
|
-
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current.").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
|
|
77688
|
+
memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current.").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
|
|
77059
77689
|
if (opts.status) {
|
|
77060
77690
|
if (!isDockerAvailable()) {
|
|
77061
77691
|
console.log(source_default.red(" Docker is not available."));
|
|
@@ -77105,9 +77735,9 @@ Cross-agent reflection plan
|
|
|
77105
77735
|
return;
|
|
77106
77736
|
}
|
|
77107
77737
|
if (recreate) {
|
|
77108
|
-
console.log(source_default.gray(
|
|
77738
|
+
console.log(source_default.gray(` Pulling ${opts.tag ? `Hindsight image ${opts.tag}` : "latest Hindsight image"}...`));
|
|
77109
77739
|
try {
|
|
77110
|
-
pullHindsightImage();
|
|
77740
|
+
pullHindsightImage(opts.tag);
|
|
77111
77741
|
} catch (err) {
|
|
77112
77742
|
console.error(source_default.red(`
|
|
77113
77743
|
Failed to pull Hindsight image: ${err.message}
|
|
@@ -77142,7 +77772,7 @@ Cross-agent reflection plan
|
|
|
77142
77772
|
console.log(source_default.gray(" Starting Hindsight Docker container..."));
|
|
77143
77773
|
try {
|
|
77144
77774
|
const litellmCfg = await resolveLiteLLMForHindsight(getConfig(program3));
|
|
77145
|
-
startHindsight(ports, litellmCfg);
|
|
77775
|
+
startHindsight(ports, litellmCfg, opts.tag);
|
|
77146
77776
|
if (litellmCfg) {
|
|
77147
77777
|
console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
|
|
77148
77778
|
}
|
|
@@ -77349,7 +77979,7 @@ init_merge();
|
|
|
77349
77979
|
init_loader();
|
|
77350
77980
|
init_client();
|
|
77351
77981
|
import {
|
|
77352
|
-
readFileSync as
|
|
77982
|
+
readFileSync as readFileSync50,
|
|
77353
77983
|
existsSync as existsSync54,
|
|
77354
77984
|
realpathSync as realpathSync4,
|
|
77355
77985
|
mkdirSync as mkdirSync30,
|
|
@@ -77358,8 +77988,8 @@ import {
|
|
|
77358
77988
|
writeSync as writeSync6,
|
|
77359
77989
|
constants as fsConstants3
|
|
77360
77990
|
} from "node:fs";
|
|
77361
|
-
import { resolve as
|
|
77362
|
-
import { homedir as
|
|
77991
|
+
import { resolve as resolve31, extname, join as join49, relative, dirname as dirname14 } from "node:path";
|
|
77992
|
+
import { homedir as homedir27 } from "node:os";
|
|
77363
77993
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
|
|
77364
77994
|
|
|
77365
77995
|
// src/web/api.ts
|
|
@@ -79192,10 +79822,13 @@ async function handleGetSummary(deps, now = Date.now()) {
|
|
|
79192
79822
|
};
|
|
79193
79823
|
}
|
|
79194
79824
|
|
|
79825
|
+
// src/web/server.ts
|
|
79826
|
+
init_fleet_health_read();
|
|
79827
|
+
|
|
79195
79828
|
// src/web/webhook-handler.ts
|
|
79196
|
-
import { appendFileSync as appendFileSync4, existsSync as existsSync52, mkdirSync as mkdirSync29, readFileSync as
|
|
79829
|
+
import { appendFileSync as appendFileSync4, existsSync as existsSync52, mkdirSync as mkdirSync29, readFileSync as readFileSync48, writeFileSync as writeFileSync27 } from "fs";
|
|
79197
79830
|
import { join as join47 } from "path";
|
|
79198
|
-
import { homedir as
|
|
79831
|
+
import { homedir as homedir26 } from "os";
|
|
79199
79832
|
|
|
79200
79833
|
// src/web/webhook-verify.ts
|
|
79201
79834
|
import { createHmac as createHmac2, timingSafeEqual } from "crypto";
|
|
@@ -79359,7 +79992,7 @@ function escapeHtml3(s) {
|
|
|
79359
79992
|
import net4 from "node:net";
|
|
79360
79993
|
function forwardToGateway(socketPath, req, opts = {}) {
|
|
79361
79994
|
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
79362
|
-
return new Promise((
|
|
79995
|
+
return new Promise((resolve30) => {
|
|
79363
79996
|
let settled = false;
|
|
79364
79997
|
let buf = "";
|
|
79365
79998
|
const done = (value) => {
|
|
@@ -79369,7 +80002,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
79369
80002
|
try {
|
|
79370
80003
|
conn.destroy();
|
|
79371
80004
|
} catch {}
|
|
79372
|
-
|
|
80005
|
+
resolve30(value);
|
|
79373
80006
|
};
|
|
79374
80007
|
const conn = net4.createConnection(socketPath);
|
|
79375
80008
|
conn.setEncoding("utf8");
|
|
@@ -79400,20 +80033,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
79400
80033
|
}
|
|
79401
80034
|
|
|
79402
80035
|
// src/web/webhook-edge.ts
|
|
79403
|
-
import { existsSync as existsSync51, readFileSync as
|
|
80036
|
+
import { existsSync as existsSync51, readFileSync as readFileSync47 } from "fs";
|
|
79404
80037
|
import { join as join46 } from "path";
|
|
79405
|
-
import { homedir as
|
|
80038
|
+
import { homedir as homedir25 } from "os";
|
|
79406
80039
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
79407
80040
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
79408
80041
|
function edgeSecretPath() {
|
|
79409
|
-
return join46(
|
|
80042
|
+
return join46(homedir25(), ".switchroom", "webhook-edge-secret");
|
|
79410
80043
|
}
|
|
79411
80044
|
function loadEdgeSecret(path4) {
|
|
79412
80045
|
const p = path4 ?? edgeSecretPath();
|
|
79413
80046
|
try {
|
|
79414
80047
|
if (!existsSync51(p))
|
|
79415
80048
|
return null;
|
|
79416
|
-
const raw =
|
|
80049
|
+
const raw = readFileSync47(p, "utf-8").trim();
|
|
79417
80050
|
return raw.length > 0 ? raw : null;
|
|
79418
80051
|
} catch {
|
|
79419
80052
|
return null;
|
|
@@ -79447,7 +80080,7 @@ function loadDedupFile(path4) {
|
|
|
79447
80080
|
try {
|
|
79448
80081
|
if (!existsSync52(path4))
|
|
79449
80082
|
return {};
|
|
79450
|
-
const raw = JSON.parse(
|
|
80083
|
+
const raw = JSON.parse(readFileSync48(path4, "utf-8"));
|
|
79451
80084
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
79452
80085
|
} catch {
|
|
79453
80086
|
return {};
|
|
@@ -79547,7 +80180,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
|
|
|
79547
80180
|
async function handleWebhookIngest(args, deps = {}) {
|
|
79548
80181
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
79549
80182
|
const now = (deps.now ?? Date.now)();
|
|
79550
|
-
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(
|
|
80183
|
+
const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join47(homedir26(), ".switchroom", "agents", a));
|
|
79551
80184
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
79552
80185
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
|
|
79553
80186
|
if (!args.agentExists) {
|
|
@@ -79700,8 +80333,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
79700
80333
|
init_loader();
|
|
79701
80334
|
import { createConnection as createConnection3 } from "node:net";
|
|
79702
80335
|
import { createHash as createHash10 } from "node:crypto";
|
|
79703
|
-
import { join as join48, resolve as
|
|
79704
|
-
import { existsSync as existsSync53, readFileSync as
|
|
80336
|
+
import { join as join48, resolve as resolve30 } from "node:path";
|
|
80337
|
+
import { existsSync as existsSync53, readFileSync as readFileSync49 } from "node:fs";
|
|
79705
80338
|
|
|
79706
80339
|
// src/agent-scheduler/channel-target.ts
|
|
79707
80340
|
init_merge();
|
|
@@ -79818,11 +80451,11 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
79818
80451
|
const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
|
|
79819
80452
|
return { chatId: channel.chatId, threadId };
|
|
79820
80453
|
}
|
|
79821
|
-
const accessPath =
|
|
80454
|
+
const accessPath = resolve30(agentsDir, agentName, "telegram", "access.json");
|
|
79822
80455
|
if (!existsSync53(accessPath))
|
|
79823
80456
|
return null;
|
|
79824
80457
|
try {
|
|
79825
|
-
const raw =
|
|
80458
|
+
const raw = readFileSync49(accessPath, "utf-8");
|
|
79826
80459
|
const access = JSON.parse(raw);
|
|
79827
80460
|
const chatId = access.allowFrom?.[0];
|
|
79828
80461
|
if (!chatId)
|
|
@@ -79833,7 +80466,7 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
79833
80466
|
}
|
|
79834
80467
|
}
|
|
79835
80468
|
async function injectInbound(agentsDir, agentName, chatId, threadId, text, promptKey) {
|
|
79836
|
-
const socketPath =
|
|
80469
|
+
const socketPath = resolve30(agentsDir, agentName, "telegram", "gateway.sock");
|
|
79837
80470
|
if (!existsSync53(socketPath)) {
|
|
79838
80471
|
return { ok: false, error: `agent ${agentName} gateway socket not found \u2014 is it running?` };
|
|
79839
80472
|
}
|
|
@@ -79881,7 +80514,7 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
|
|
|
79881
80514
|
});
|
|
79882
80515
|
}
|
|
79883
80516
|
function readHistoryDb(agentsDir, agentName, limit) {
|
|
79884
|
-
const dbPath =
|
|
80517
|
+
const dbPath = resolve30(agentsDir, agentName, "telegram", "history.db");
|
|
79885
80518
|
if (!existsSync53(dbPath))
|
|
79886
80519
|
return null;
|
|
79887
80520
|
try {
|
|
@@ -79910,7 +80543,7 @@ function readHistoryDb(agentsDir, agentName, limit) {
|
|
|
79910
80543
|
}
|
|
79911
80544
|
}
|
|
79912
80545
|
function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs = 2000) {
|
|
79913
|
-
const dbPath =
|
|
80546
|
+
const dbPath = resolve30(agentsDir, agentName, "telegram", "history.db");
|
|
79914
80547
|
let SqliteDatabase = null;
|
|
79915
80548
|
try {
|
|
79916
80549
|
const meta = import.meta;
|
|
@@ -80028,15 +80661,14 @@ function turnsToMessages(turns) {
|
|
|
80028
80661
|
}
|
|
80029
80662
|
return messages;
|
|
80030
80663
|
}
|
|
80031
|
-
var SWITCHROOM_MODELS = [
|
|
80032
|
-
|
|
80033
|
-
|
|
80034
|
-
"
|
|
80035
|
-
|
|
80036
|
-
|
|
80037
|
-
function switchroomModelOptions() {
|
|
80664
|
+
var SWITCHROOM_MODELS = ["fable", "opus", "sonnet", "haiku"];
|
|
80665
|
+
function configuredModel(config, agentName) {
|
|
80666
|
+
const agentModel = agentName ? config.agents?.[agentName]?.model : undefined;
|
|
80667
|
+
return agentModel ?? config.defaults?.model ?? "sonnet";
|
|
80668
|
+
}
|
|
80669
|
+
function switchroomModelOptions(config, agentName) {
|
|
80038
80670
|
return {
|
|
80039
|
-
model:
|
|
80671
|
+
model: configuredModel(config, agentName),
|
|
80040
80672
|
provider: "switchroom",
|
|
80041
80673
|
providers: [
|
|
80042
80674
|
{
|
|
@@ -80164,7 +80796,7 @@ async function handleHermesRest(method, pathname, config, search = "") {
|
|
|
80164
80796
|
return {
|
|
80165
80797
|
status: 200,
|
|
80166
80798
|
body: {
|
|
80167
|
-
model:
|
|
80799
|
+
model: configuredModel(config),
|
|
80168
80800
|
provider: "switchroom",
|
|
80169
80801
|
capabilities: {}
|
|
80170
80802
|
}
|
|
@@ -80235,7 +80867,7 @@ async function handleHermesRest(method, pathname, config, search = "") {
|
|
|
80235
80867
|
return { status: 200, body: { providers: [] } };
|
|
80236
80868
|
}
|
|
80237
80869
|
if (method === "GET" && pathname.startsWith("/api/model/options")) {
|
|
80238
|
-
return { status: 200, body: switchroomModelOptions() };
|
|
80870
|
+
return { status: 200, body: switchroomModelOptions(config) };
|
|
80239
80871
|
}
|
|
80240
80872
|
if (method === "POST" && pathname === "/api/model/set") {
|
|
80241
80873
|
return { status: 200, body: { ok: true } };
|
|
@@ -80467,11 +81099,15 @@ async function onHermesMessage(ctx, raw) {
|
|
|
80467
81099
|
break;
|
|
80468
81100
|
}
|
|
80469
81101
|
case "model.options": {
|
|
80470
|
-
|
|
81102
|
+
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
81103
|
+
const agentName = sessionId ? parseHermesSessionId(sessionId).agentName : undefined;
|
|
81104
|
+
sendResponse(ctx, rpcOk(id, switchroomModelOptions(config, agentName)));
|
|
80471
81105
|
break;
|
|
80472
81106
|
}
|
|
80473
81107
|
case "model.info": {
|
|
80474
|
-
|
|
81108
|
+
const sessionId = String(params.session_id ?? ctx.activeSessionId ?? "");
|
|
81109
|
+
const agentName = sessionId ? parseHermesSessionId(sessionId).agentName : undefined;
|
|
81110
|
+
sendResponse(ctx, rpcOk(id, { model: configuredModel(config, agentName), provider: "switchroom" }));
|
|
80475
81111
|
break;
|
|
80476
81112
|
}
|
|
80477
81113
|
case "config.set": {
|
|
@@ -80577,7 +81213,8 @@ var SPA_TAB_ROUTES = new Set([
|
|
|
80577
81213
|
"memory",
|
|
80578
81214
|
"connections",
|
|
80579
81215
|
"schedule",
|
|
80580
|
-
"approvals"
|
|
81216
|
+
"approvals",
|
|
81217
|
+
"fleet-health"
|
|
80581
81218
|
]);
|
|
80582
81219
|
function resolveDashboardFilePath(pathname) {
|
|
80583
81220
|
const routeName = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
@@ -80607,10 +81244,10 @@ function resolveWebToken() {
|
|
|
80607
81244
|
const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
|
|
80608
81245
|
if (fromEnv && fromEnv.length > 0)
|
|
80609
81246
|
return fromEnv;
|
|
80610
|
-
const home2 = process.env.HOME ??
|
|
81247
|
+
const home2 = process.env.HOME ?? homedir27();
|
|
80611
81248
|
const tokenPath = join49(home2, ".switchroom", "web-token");
|
|
80612
81249
|
if (existsSync54(tokenPath)) {
|
|
80613
|
-
const existing =
|
|
81250
|
+
const existing = readFileSync50(tokenPath, "utf8").trim();
|
|
80614
81251
|
if (existing.length > 0)
|
|
80615
81252
|
return existing;
|
|
80616
81253
|
}
|
|
@@ -80627,7 +81264,7 @@ function resolveWebToken() {
|
|
|
80627
81264
|
return token;
|
|
80628
81265
|
} catch (err) {
|
|
80629
81266
|
if (err.code === "EEXIST") {
|
|
80630
|
-
const existing =
|
|
81267
|
+
const existing = readFileSync50(tokenPath, "utf8").trim();
|
|
80631
81268
|
if (existing.length > 0)
|
|
80632
81269
|
return existing;
|
|
80633
81270
|
}
|
|
@@ -80700,11 +81337,11 @@ function checkWsAuth(req, token, server) {
|
|
|
80700
81337
|
return presented !== null && constantTimeEqual(presented, token);
|
|
80701
81338
|
}
|
|
80702
81339
|
function loadWebhookSecrets() {
|
|
80703
|
-
const path4 = join49(
|
|
81340
|
+
const path4 = join49(homedir27(), ".switchroom", "webhook-secrets.json");
|
|
80704
81341
|
if (!existsSync54(path4))
|
|
80705
81342
|
return {};
|
|
80706
81343
|
try {
|
|
80707
|
-
const parsed = JSON.parse(
|
|
81344
|
+
const parsed = JSON.parse(readFileSync50(path4, "utf-8"));
|
|
80708
81345
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
80709
81346
|
} catch (err) {
|
|
80710
81347
|
process.stderr.write(`webhook-ingest: failed to parse ${path4}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
@@ -80839,6 +81476,9 @@ function parseRoute(pathname, method) {
|
|
|
80839
81476
|
if (method === "GET" && pathname === "/api/approvals") {
|
|
80840
81477
|
return { handler: "getApprovals", params: {} };
|
|
80841
81478
|
}
|
|
81479
|
+
if (method === "GET" && pathname === "/api/fleet-health") {
|
|
81480
|
+
return { handler: "getFleetHealth", params: {} };
|
|
81481
|
+
}
|
|
80842
81482
|
if (method === "GET" && pathname === "/api/grants") {
|
|
80843
81483
|
return { handler: "getGrants", params: {} };
|
|
80844
81484
|
}
|
|
@@ -80882,7 +81522,7 @@ function parseRoute(pathname, method) {
|
|
|
80882
81522
|
return null;
|
|
80883
81523
|
}
|
|
80884
81524
|
function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
80885
|
-
const uiDirRaw =
|
|
81525
|
+
const uiDirRaw = resolve31(import.meta.dirname, "ui");
|
|
80886
81526
|
const uiDir = existsSync54(uiDirRaw) ? realpathSync4(uiDirRaw) : uiDirRaw;
|
|
80887
81527
|
const token = resolveWebToken();
|
|
80888
81528
|
const freshConfig = () => {
|
|
@@ -81034,6 +81674,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
81034
81674
|
return (async () => jsonResponse(withStamp(await cachedSchedule())))();
|
|
81035
81675
|
case "getApprovals":
|
|
81036
81676
|
return (async () => jsonResponse(withStamp(await cachedApprovals())))();
|
|
81677
|
+
case "getFleetHealth":
|
|
81678
|
+
return jsonResponse(handleGetFleetHealth());
|
|
81037
81679
|
case "getGrants":
|
|
81038
81680
|
return (async () => jsonResponse(withStamp(await cachedGrants())))();
|
|
81039
81681
|
case "getAccounts":
|
|
@@ -81188,12 +81830,12 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
81188
81830
|
return new Response("Not Found", { status: 404 });
|
|
81189
81831
|
}
|
|
81190
81832
|
const rel = relative(uiDir, realFullPath);
|
|
81191
|
-
if (rel.startsWith("..") ||
|
|
81833
|
+
if (rel.startsWith("..") || resolve31(uiDir, rel) !== realFullPath) {
|
|
81192
81834
|
return new Response("Forbidden", { status: 403 });
|
|
81193
81835
|
}
|
|
81194
81836
|
const ext = extname(realFullPath);
|
|
81195
81837
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
81196
|
-
const content =
|
|
81838
|
+
const content = readFileSync50(realFullPath);
|
|
81197
81839
|
const headers = { "Content-Type": contentType };
|
|
81198
81840
|
const cacheControl = dashboardCacheControl(ext);
|
|
81199
81841
|
if (cacheControl)
|
|
@@ -81368,8 +82010,8 @@ Starting Switchroom dashboard...
|
|
|
81368
82010
|
init_source();
|
|
81369
82011
|
init_loader();
|
|
81370
82012
|
init_scaffold();
|
|
81371
|
-
import { existsSync as existsSync55, copyFileSync as copyFileSync8, readFileSync as
|
|
81372
|
-
import { resolve as
|
|
82013
|
+
import { existsSync as existsSync55, copyFileSync as copyFileSync8, readFileSync as readFileSync51, writeFileSync as writeFileSync28, mkdirSync as mkdirSync31 } from "node:fs";
|
|
82014
|
+
import { resolve as resolve32, dirname as dirname15 } from "node:path";
|
|
81373
82015
|
init_state();
|
|
81374
82016
|
init_vault();
|
|
81375
82017
|
init_manager();
|
|
@@ -81650,14 +82292,14 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
81650
82292
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
81651
82293
|
const hasExplicitTimezone = config.switchroom?.timezone !== undefined || config.defaults?.timezone !== undefined;
|
|
81652
82294
|
if (!hasExplicitTimezone) {
|
|
81653
|
-
await writeDetectedTimezone(
|
|
82295
|
+
await writeDetectedTimezone(resolve32(existingConfig), nonInteractive);
|
|
81654
82296
|
}
|
|
81655
|
-
return { config, configPath:
|
|
82297
|
+
return { config, configPath: resolve32(existingConfig) };
|
|
81656
82298
|
}
|
|
81657
82299
|
return await copyExampleConfig(nonInteractive);
|
|
81658
82300
|
}
|
|
81659
82301
|
async function copyExampleConfig(nonInteractive) {
|
|
81660
|
-
const examplesDir =
|
|
82302
|
+
const examplesDir = resolve32(import.meta.dirname, "../../examples");
|
|
81661
82303
|
let choice;
|
|
81662
82304
|
if (nonInteractive) {
|
|
81663
82305
|
choice = "switchroom";
|
|
@@ -81668,7 +82310,7 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
81668
82310
|
]);
|
|
81669
82311
|
choice = choice.split(" ")[0];
|
|
81670
82312
|
}
|
|
81671
|
-
const srcFile =
|
|
82313
|
+
const srcFile = resolve32(examplesDir, `${choice}.yaml`);
|
|
81672
82314
|
const destFile = resolvePath("~/.switchroom/switchroom.yaml");
|
|
81673
82315
|
if (!existsSync55(srcFile)) {
|
|
81674
82316
|
throw new ConfigError(`Example config not found: ${choice}.yaml`);
|
|
@@ -81680,12 +82322,12 @@ async function copyExampleConfig(nonInteractive) {
|
|
|
81680
82322
|
await writeDetectedTimezone(destFile, nonInteractive);
|
|
81681
82323
|
const config = loadConfig(destFile);
|
|
81682
82324
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
81683
|
-
return { config, configPath:
|
|
82325
|
+
return { config, configPath: resolve32(destFile) };
|
|
81684
82326
|
}
|
|
81685
82327
|
async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
|
|
81686
82328
|
const detected = detect();
|
|
81687
82329
|
if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
|
|
81688
|
-
const before2 =
|
|
82330
|
+
const before2 = readFileSync51(destFile, "utf-8");
|
|
81689
82331
|
const after2 = setSwitchroomTimezone(before2, detected);
|
|
81690
82332
|
if (after2 !== before2)
|
|
81691
82333
|
writeFileSync28(destFile, after2);
|
|
@@ -81706,7 +82348,7 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
|
|
|
81706
82348
|
console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
|
|
81707
82349
|
return;
|
|
81708
82350
|
}
|
|
81709
|
-
const before =
|
|
82351
|
+
const before = readFileSync51(destFile, "utf-8");
|
|
81710
82352
|
const after = setSwitchroomTimezone(before, zone);
|
|
81711
82353
|
if (after !== before)
|
|
81712
82354
|
writeFileSync28(destFile, after);
|
|
@@ -82258,9 +82900,9 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
82258
82900
|
const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
|
|
82259
82901
|
if (choice === TELEGRAM_ID_CHOICE) {
|
|
82260
82902
|
try {
|
|
82261
|
-
const yamlPath = existsSync55(
|
|
82903
|
+
const yamlPath = existsSync55(resolve32(process.cwd(), "switchroom.yaml")) ? resolve32(process.cwd(), "switchroom.yaml") : resolve32(process.cwd(), "switchroom.yml");
|
|
82262
82904
|
if (existsSync55(yamlPath)) {
|
|
82263
|
-
const content =
|
|
82905
|
+
const content = readFileSync51(yamlPath, "utf-8");
|
|
82264
82906
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
82265
82907
|
if (result.kind === "rewritten") {
|
|
82266
82908
|
writeFileSync28(yamlPath, result.content, "utf-8");
|
|
@@ -82290,12 +82932,12 @@ async function stepDangerousMode(config, nonInteractive) {
|
|
|
82290
82932
|
}
|
|
82291
82933
|
if (enableDangerous) {
|
|
82292
82934
|
const configPaths = [
|
|
82293
|
-
|
|
82294
|
-
|
|
82935
|
+
resolve32(process.cwd(), "switchroom.yaml"),
|
|
82936
|
+
resolve32(process.cwd(), "switchroom.yml")
|
|
82295
82937
|
];
|
|
82296
82938
|
for (const configPath of configPaths) {
|
|
82297
82939
|
if (existsSync55(configPath)) {
|
|
82298
|
-
let content =
|
|
82940
|
+
let content = readFileSync51(configPath, "utf-8");
|
|
82299
82941
|
const agentNames = Object.keys(config.agents);
|
|
82300
82942
|
for (const name of agentNames) {
|
|
82301
82943
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -82325,7 +82967,7 @@ async function stepOnboardingGuidance(config, nonInteractive) {
|
|
|
82325
82967
|
const agentNames = Object.keys(config.agents);
|
|
82326
82968
|
let allAuthenticated = true;
|
|
82327
82969
|
for (const name of agentNames) {
|
|
82328
|
-
const agentDir =
|
|
82970
|
+
const agentDir = resolve32(agentsDir, name);
|
|
82329
82971
|
const status = getAuthStatus(name, agentDir);
|
|
82330
82972
|
if (status.authenticated) {
|
|
82331
82973
|
console.log(` ${source_default.green("OK")} ${source_default.bold(name)}` + source_default.gray(` - authenticated (expires: ${status.timeUntilExpiry ?? "unknown"})`));
|
|
@@ -82425,10 +83067,10 @@ init_source();
|
|
|
82425
83067
|
init_loader();
|
|
82426
83068
|
init_lifecycle();
|
|
82427
83069
|
init_compose_env();
|
|
82428
|
-
import { cpSync as cpSync2, existsSync as existsSync62, mkdirSync as mkdirSync33, readFileSync as
|
|
83070
|
+
import { cpSync as cpSync2, existsSync as existsSync62, mkdirSync as mkdirSync33, readFileSync as readFileSync56, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync27, chownSync as chownSync5 } from "node:fs";
|
|
82429
83071
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
82430
|
-
import { join as join63, dirname as dirname18, resolve as
|
|
82431
|
-
import { homedir as
|
|
83072
|
+
import { join as join63, dirname as dirname18, resolve as resolve37 } from "node:path";
|
|
83073
|
+
import { homedir as homedir38 } from "node:os";
|
|
82432
83074
|
|
|
82433
83075
|
// src/cli/release-yaml.ts
|
|
82434
83076
|
var import_yaml19 = __toESM(require_dist(), 1);
|
|
@@ -82520,7 +83162,7 @@ ${lines.join(`
|
|
|
82520
83162
|
function defaultPersistPin(configPath) {
|
|
82521
83163
|
return (pin) => {
|
|
82522
83164
|
const path4 = configPath ?? findConfigFile();
|
|
82523
|
-
const before =
|
|
83165
|
+
const before = readFileSync56(path4, "utf8");
|
|
82524
83166
|
const after = setReleasePinInConfig(before, pin);
|
|
82525
83167
|
if (after === before)
|
|
82526
83168
|
return;
|
|
@@ -82534,13 +83176,13 @@ function defaultPersistPin(configPath) {
|
|
|
82534
83176
|
} catch {}
|
|
82535
83177
|
};
|
|
82536
83178
|
}
|
|
82537
|
-
var DEFAULT_COMPOSE_PATH = join63(
|
|
83179
|
+
var DEFAULT_COMPOSE_PATH = join63(homedir38(), ".switchroom", "compose", "docker-compose.yml");
|
|
82538
83180
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
82539
83181
|
let dir = dirname18(scriptPath);
|
|
82540
83182
|
for (let i = 0;i < 12; i++) {
|
|
82541
83183
|
if (existsSync62(join63(dir, ".git"))) {
|
|
82542
83184
|
try {
|
|
82543
|
-
const pkg = JSON.parse(
|
|
83185
|
+
const pkg = JSON.parse(readFileSync56(join63(dir, "package.json"), "utf-8"));
|
|
82544
83186
|
if (pkg.name === "switchroom")
|
|
82545
83187
|
return true;
|
|
82546
83188
|
} catch {}
|
|
@@ -82730,8 +83372,8 @@ function planUpdate(opts) {
|
|
|
82730
83372
|
opts.syncBundledSkillsFn();
|
|
82731
83373
|
return;
|
|
82732
83374
|
}
|
|
82733
|
-
const source =
|
|
82734
|
-
const dest = join63(
|
|
83375
|
+
const source = resolve37(import.meta.dirname, "../../skills");
|
|
83376
|
+
const dest = join63(homedir38(), ".switchroom", "skills", "_bundled");
|
|
82735
83377
|
if (!existsSync62(source)) {
|
|
82736
83378
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
82737
83379
|
`);
|
|
@@ -82777,7 +83419,7 @@ function planUpdate(opts) {
|
|
|
82777
83419
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
82778
83420
|
run: () => {
|
|
82779
83421
|
try {
|
|
82780
|
-
const composeText =
|
|
83422
|
+
const composeText = readFileSync56(composePath, "utf8");
|
|
82781
83423
|
const pf = validateBindSources(composeText);
|
|
82782
83424
|
if (!pf.ok)
|
|
82783
83425
|
throw new Error(formatPreflightError(pf));
|
|
@@ -82859,7 +83501,7 @@ function defaultStatusProbe(composePath) {
|
|
|
82859
83501
|
const pkgPath = join63(dir, "package.json");
|
|
82860
83502
|
if (existsSync62(pkgPath)) {
|
|
82861
83503
|
try {
|
|
82862
|
-
const pkg = JSON.parse(
|
|
83504
|
+
const pkg = JSON.parse(readFileSync56(pkgPath, "utf-8"));
|
|
82863
83505
|
if (typeof pkg.version === "string")
|
|
82864
83506
|
cliVersion = pkg.version;
|
|
82865
83507
|
} catch (err) {
|
|
@@ -83076,8 +83718,8 @@ function registerUpdateCommand(program3) {
|
|
|
83076
83718
|
// src/cli/rollout.ts
|
|
83077
83719
|
init_helpers();
|
|
83078
83720
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
83079
|
-
import { readFileSync as
|
|
83080
|
-
import { homedir as
|
|
83721
|
+
import { readFileSync as readFileSync57, chownSync as chownSync6, statSync as statSync28 } from "node:fs";
|
|
83722
|
+
import { homedir as homedir39 } from "node:os";
|
|
83081
83723
|
init_operator_uid();
|
|
83082
83724
|
init_atomic();
|
|
83083
83725
|
init_audit_reader();
|
|
@@ -83121,6 +83763,7 @@ function planRollout(agents, opts = {}) {
|
|
|
83121
83763
|
if (!opts.skipWeb) {
|
|
83122
83764
|
steps.push({ kind: "refresh-web" });
|
|
83123
83765
|
steps.push({ kind: "refresh-hostd" });
|
|
83766
|
+
steps.push({ kind: "refresh-hindsight" });
|
|
83124
83767
|
}
|
|
83125
83768
|
steps.push({ kind: "sweep" });
|
|
83126
83769
|
return steps;
|
|
@@ -83146,6 +83789,9 @@ function formatRolloutPlan(steps, target) {
|
|
|
83146
83789
|
case "refresh-hostd":
|
|
83147
83790
|
lines.push(` ${n}. hostd install --tag ${target} (separate compose project)`);
|
|
83148
83791
|
break;
|
|
83792
|
+
case "refresh-hindsight":
|
|
83793
|
+
lines.push(` ${n}. refresh hindsight \u2014 pull ${target} + recreate (standalone docker run, not compose)`);
|
|
83794
|
+
break;
|
|
83149
83795
|
case "sweep":
|
|
83150
83796
|
lines.push(` ${n}. sweep \u2014 print per-agent version table`);
|
|
83151
83797
|
break;
|
|
@@ -83219,11 +83865,14 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
83219
83865
|
case "apply": {
|
|
83220
83866
|
deps.log(`ROLL_STEP apply \u2014 regenerating compose for ${target}`);
|
|
83221
83867
|
emit({ phase: "apply", target });
|
|
83222
|
-
const applyArgs = execOpts.hostdContext ? ["apply", "--pin", target] : ["apply"];
|
|
83868
|
+
const applyArgs = execOpts.hostdContext ? ["apply", "--pin", target, "--compose-only", "--non-interactive"] : ["apply"];
|
|
83223
83869
|
const r = deps.run(applyArgs);
|
|
83224
83870
|
if (r.status !== 0) {
|
|
83225
83871
|
return { ok: false, rolled, failedStep: "apply", warnings };
|
|
83226
83872
|
}
|
|
83873
|
+
if (execOpts.hostdContext) {
|
|
83874
|
+
warnings.push(`apply ran --compose-only (hostd is unprivileged and cannot ` + `write per-agent state dirs). Compose + compose-level env/` + `healthcheck are up to date, but per-agent template changes ` + `(start.sh / .mcp.json / settings.json) are NOT applied \u2014 run ` + `a host-side \`sudo switchroom apply\` if this release changed them.`);
|
|
83875
|
+
}
|
|
83227
83876
|
break;
|
|
83228
83877
|
}
|
|
83229
83878
|
case "restart-agent": {
|
|
@@ -83265,6 +83914,25 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
83265
83914
|
warnings.push(`hostd refresh failed (non-fatal); agents already rolled`);
|
|
83266
83915
|
break;
|
|
83267
83916
|
}
|
|
83917
|
+
case "refresh-hindsight": {
|
|
83918
|
+
const hindsightExists = deps.hindsightExists ?? isHindsightContainerExists;
|
|
83919
|
+
if (!hindsightExists()) {
|
|
83920
|
+
deps.log(`ROLL_STEP refresh-hindsight \u2014 no hindsight container on this host; skipping`);
|
|
83921
|
+
break;
|
|
83922
|
+
}
|
|
83923
|
+
deps.log(`ROLL_STEP refresh-hindsight \u2014 memory setup --recreate --tag ${target} (pull ${target} + recreate)`);
|
|
83924
|
+
const r = deps.run(["memory", "setup", "--recreate", "--tag", target]);
|
|
83925
|
+
if (r.status !== 0) {
|
|
83926
|
+
deps.log(` \u2717 hindsight recreate FAILED \u2014 the memory backend is DOWN (the ` + `recreate stopped + removed the old container before the failed ` + `start). Run \`switchroom memory setup\` to bring it back. \u2014 STOPPING`);
|
|
83927
|
+
return {
|
|
83928
|
+
ok: false,
|
|
83929
|
+
rolled,
|
|
83930
|
+
failedStep: "refresh-hindsight",
|
|
83931
|
+
warnings
|
|
83932
|
+
};
|
|
83933
|
+
}
|
|
83934
|
+
break;
|
|
83935
|
+
}
|
|
83268
83936
|
case "sweep": {
|
|
83269
83937
|
deps.log(`ROLL_STEP sweep`);
|
|
83270
83938
|
for (const a of rolled) {
|
|
@@ -83278,10 +83946,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
83278
83946
|
return { ok: true, rolled, warnings };
|
|
83279
83947
|
}
|
|
83280
83948
|
function resolveRollbackTarget(auditLogPath) {
|
|
83281
|
-
const logPath = auditLogPath ?? defaultAuditLogPath2(
|
|
83949
|
+
const logPath = auditLogPath ?? defaultAuditLogPath2(homedir39());
|
|
83282
83950
|
let raw;
|
|
83283
83951
|
try {
|
|
83284
|
-
raw =
|
|
83952
|
+
raw = readFileSync57(logPath, "utf8");
|
|
83285
83953
|
} catch {
|
|
83286
83954
|
return null;
|
|
83287
83955
|
}
|
|
@@ -83296,7 +83964,7 @@ function resolveRollbackTarget(auditLogPath) {
|
|
|
83296
83964
|
}
|
|
83297
83965
|
function registerRolloutCommand(program3) {
|
|
83298
83966
|
const hostdCtx = isHostdContext2();
|
|
83299
|
-
program3.command("rollout").description("Deploy a pinned version to the fleet, safely (staggered restart + " + "per-agent version assert + web/hostd refresh)." + (hostdCtx ? "" : " Run with sudo.")).option("--pin <version>", "Version to roll (e.g. v0.15.18). Defaults to release.pin from config.").option("--agents <list>", "Comma-separated subset of agents to roll (default: all configured).").option("--skip-web", "Skip the web + hostd refresh step.").option("--allow-downgrade", "Permit rolling to an older semver tag (operator-approved rollback path). " + "When set, the downgrade guard is relaxed so --pin may be older than the " + "current release.pin. All other safety rails apply unchanged.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
83967
|
+
program3.command("rollout").description("Deploy a pinned version to the fleet, safely (staggered restart + " + "per-agent version assert + web/hostd refresh)." + (hostdCtx ? "" : " Run with sudo.")).option("--pin <version>", "Version to roll (e.g. v0.15.18). Defaults to release.pin from config.").option("--agents <list>", "Comma-separated subset of agents to roll (default: all configured).").option("--skip-web", "Skip the web + hostd + hindsight refresh step.").option("--allow-downgrade", "Permit rolling to an older semver tag (operator-approved rollback path). " + "When set, the downgrade guard is relaxed so --pin may be older than the " + "current release.pin. All other safety rails apply unchanged.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
83300
83968
|
const config = getConfig(program3);
|
|
83301
83969
|
let resolvedPin = opts.pin;
|
|
83302
83970
|
if (opts.allowDowngrade && !resolvedPin) {
|
|
@@ -83395,7 +84063,7 @@ function registerRolloutCommand(program3) {
|
|
|
83395
84063
|
emitPhase: (phase) => process.stdout.write(encodeRolloutPhaseLine(phase) + `
|
|
83396
84064
|
`),
|
|
83397
84065
|
persistPin: (pin) => {
|
|
83398
|
-
const before =
|
|
84066
|
+
const before = readFileSync57(configPath, "utf8");
|
|
83399
84067
|
const after = setReleasePinInConfig(before, pin);
|
|
83400
84068
|
if (after === before)
|
|
83401
84069
|
return;
|
|
@@ -83443,7 +84111,7 @@ function registerRolloutCommand(program3) {
|
|
|
83443
84111
|
});
|
|
83444
84112
|
}
|
|
83445
84113
|
function registerRollbackCommand(program3) {
|
|
83446
|
-
program3.command("rollback").description("Roll the fleet back to the previous version (operator-approved downgrade). " + "Defaults to the version captured in the last completed rollout's prior_pin. " + "Equivalent to `rollout --allow-downgrade [--pin <version>]`.").option("--to <version>", "Explicit version to roll back to (e.g. v0.15.17). " + "Omit to use the last completed rollout's prior_pin.").option("--agents <list>", "Comma-separated subset of agents to roll back (default: all configured).").option("--skip-web", "Skip the web + hostd refresh step.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
84114
|
+
program3.command("rollback").description("Roll the fleet back to the previous version (operator-approved downgrade). " + "Defaults to the version captured in the last completed rollout's prior_pin. " + "Equivalent to `rollout --allow-downgrade [--pin <version>]`.").option("--to <version>", "Explicit version to roll back to (e.g. v0.15.17). " + "Omit to use the last completed rollout's prior_pin.").option("--agents <list>", "Comma-separated subset of agents to roll back (default: all configured).").option("--skip-web", "Skip the web + hostd + hindsight refresh step.").option("--dry-run", "Print the plan and exit without changing anything.").action(async (opts) => {
|
|
83447
84115
|
const rolloutArgs = ["--allow-downgrade"];
|
|
83448
84116
|
if (opts.to)
|
|
83449
84117
|
rolloutArgs.push("--pin", opts.to);
|
|
@@ -83462,14 +84130,14 @@ init_source();
|
|
|
83462
84130
|
init_helpers();
|
|
83463
84131
|
init_loader();
|
|
83464
84132
|
init_lifecycle();
|
|
83465
|
-
import { resolve as
|
|
84133
|
+
import { resolve as resolve38 } from "node:path";
|
|
83466
84134
|
|
|
83467
84135
|
// src/cli/version.ts
|
|
83468
84136
|
init_source();
|
|
83469
84137
|
init_helpers();
|
|
83470
84138
|
init_lifecycle();
|
|
83471
84139
|
import { execSync as execSync4 } from "node:child_process";
|
|
83472
|
-
import { existsSync as existsSync63, readFileSync as
|
|
84140
|
+
import { existsSync as existsSync63, readFileSync as readFileSync58 } from "node:fs";
|
|
83473
84141
|
import { dirname as dirname19, join as join64 } from "node:path";
|
|
83474
84142
|
function getClaudeCodeVersion() {
|
|
83475
84143
|
try {
|
|
@@ -83523,7 +84191,7 @@ function locateSwitchroomInstallDir() {
|
|
|
83523
84191
|
const pkgPath = join64(dir, "package.json");
|
|
83524
84192
|
if (existsSync63(pkgPath)) {
|
|
83525
84193
|
try {
|
|
83526
|
-
const pkg = JSON.parse(
|
|
84194
|
+
const pkg = JSON.parse(readFileSync58(pkgPath, "utf-8"));
|
|
83527
84195
|
if (pkg.name === "switchroom" && existsSync63(join64(dir, ".git"))) {
|
|
83528
84196
|
return dir;
|
|
83529
84197
|
}
|
|
@@ -83617,7 +84285,7 @@ function registerRestartCommand(program3) {
|
|
|
83617
84285
|
}
|
|
83618
84286
|
const didRestart = res.restarted || !graceful;
|
|
83619
84287
|
if (didRestart) {
|
|
83620
|
-
const agentDir =
|
|
84288
|
+
const agentDir = resolve38(agentsDir, name);
|
|
83621
84289
|
const converged = waitForAuthConverge(name, agentDir);
|
|
83622
84290
|
if (!converged) {
|
|
83623
84291
|
console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
|
|
@@ -83698,7 +84366,7 @@ Dependency manifest`));
|
|
|
83698
84366
|
// src/cli/handoff.ts
|
|
83699
84367
|
init_helpers();
|
|
83700
84368
|
init_loader();
|
|
83701
|
-
import { resolve as
|
|
84369
|
+
import { resolve as resolve39 } from "node:path";
|
|
83702
84370
|
function registerHandoffCommand(program3) {
|
|
83703
84371
|
program3.command("handoff <agent>", { hidden: true }).description("Build the agent's session handoff sidecars \u2014 a transcript-tail " + "briefing (.handoff.md) and topic line (.handoff-topic). " + "[internal \u2014 used by the Stop hook]").option("--max-turns <n>", "Max turns kept in the handoff transcript tail", String(DEFAULT_MAX_TURNS)).action(withConfigError(async (agentName, opts) => {
|
|
83704
84372
|
let agentConfig;
|
|
@@ -83712,7 +84380,7 @@ function registerHandoffCommand(program3) {
|
|
|
83712
84380
|
return;
|
|
83713
84381
|
}
|
|
83714
84382
|
const agentsDir = resolveAgentsDir(config);
|
|
83715
|
-
agentDir =
|
|
84383
|
+
agentDir = resolve39(agentsDir, agentName);
|
|
83716
84384
|
} catch (err) {
|
|
83717
84385
|
if (!(err instanceof ConfigError))
|
|
83718
84386
|
throw err;
|
|
@@ -83727,7 +84395,7 @@ function registerHandoffCommand(program3) {
|
|
|
83727
84395
|
`);
|
|
83728
84396
|
return;
|
|
83729
84397
|
}
|
|
83730
|
-
const claudeConfigDir =
|
|
84398
|
+
const claudeConfigDir = resolve39(agentDir, ".claude");
|
|
83731
84399
|
const jsonl = findLatestSessionJsonl(claudeConfigDir);
|
|
83732
84400
|
if (!jsonl) {
|
|
83733
84401
|
process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
|
|
@@ -83754,7 +84422,7 @@ import {
|
|
|
83754
84422
|
mkdirSync as mkdirSync34,
|
|
83755
84423
|
openSync as openSync11,
|
|
83756
84424
|
readdirSync as readdirSync22,
|
|
83757
|
-
readFileSync as
|
|
84425
|
+
readFileSync as readFileSync59,
|
|
83758
84426
|
renameSync as renameSync12,
|
|
83759
84427
|
statSync as statSync29,
|
|
83760
84428
|
unlinkSync as unlinkSync11,
|
|
@@ -84164,7 +84832,7 @@ function readAll(stateDir) {
|
|
|
84164
84832
|
return [];
|
|
84165
84833
|
let raw;
|
|
84166
84834
|
try {
|
|
84167
|
-
raw =
|
|
84835
|
+
raw = readFileSync59(path4, "utf-8");
|
|
84168
84836
|
} catch {
|
|
84169
84837
|
return [];
|
|
84170
84838
|
}
|
|
@@ -84236,7 +84904,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
84236
84904
|
return result;
|
|
84237
84905
|
});
|
|
84238
84906
|
}
|
|
84239
|
-
function
|
|
84907
|
+
function resolve40(stateDir, fingerprint, nowFn = Date.now) {
|
|
84240
84908
|
if (!existsSync64(join65(stateDir, ISSUES_FILE)))
|
|
84241
84909
|
return 0;
|
|
84242
84910
|
return withLock(stateDir, () => {
|
|
@@ -84375,7 +85043,7 @@ function withLock(stateDir, fn) {
|
|
|
84375
85043
|
function tryStealStaleLock(lockPath) {
|
|
84376
85044
|
let pidStr;
|
|
84377
85045
|
try {
|
|
84378
|
-
pidStr =
|
|
85046
|
+
pidStr = readFileSync59(lockPath, "utf-8").trim();
|
|
84379
85047
|
} catch {
|
|
84380
85048
|
return true;
|
|
84381
85049
|
}
|
|
@@ -84524,11 +85192,11 @@ function registerIssuesCommand(program3) {
|
|
|
84524
85192
|
const stateDir = resolveStateDir2(opts);
|
|
84525
85193
|
let flipped;
|
|
84526
85194
|
if (opts.source && opts.code) {
|
|
84527
|
-
flipped =
|
|
85195
|
+
flipped = resolve40(stateDir, computeFingerprint(opts.source, opts.code));
|
|
84528
85196
|
} else if (opts.source && !fingerprint) {
|
|
84529
85197
|
flipped = resolveAllBySource(stateDir, opts.source);
|
|
84530
85198
|
} else if (fingerprint) {
|
|
84531
|
-
flipped =
|
|
85199
|
+
flipped = resolve40(stateDir, fingerprint);
|
|
84532
85200
|
} else {
|
|
84533
85201
|
process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
|
|
84534
85202
|
`);
|
|
@@ -84624,20 +85292,20 @@ function relTime(deltaMs) {
|
|
|
84624
85292
|
// src/cli/deps.ts
|
|
84625
85293
|
init_source();
|
|
84626
85294
|
import { existsSync as existsSync67 } from "node:fs";
|
|
84627
|
-
import { homedir as
|
|
84628
|
-
import { join as join68, resolve as
|
|
85295
|
+
import { homedir as homedir42 } from "node:os";
|
|
85296
|
+
import { join as join68, resolve as resolve41 } from "node:path";
|
|
84629
85297
|
|
|
84630
85298
|
// src/deps/python.ts
|
|
84631
85299
|
import { createHash as createHash12 } from "node:crypto";
|
|
84632
85300
|
import {
|
|
84633
85301
|
existsSync as existsSync65,
|
|
84634
85302
|
mkdirSync as mkdirSync35,
|
|
84635
|
-
readFileSync as
|
|
85303
|
+
readFileSync as readFileSync60,
|
|
84636
85304
|
rmSync as rmSync13,
|
|
84637
85305
|
writeFileSync as writeFileSync30
|
|
84638
85306
|
} from "node:fs";
|
|
84639
85307
|
import { dirname as dirname20, join as join66 } from "node:path";
|
|
84640
|
-
import { homedir as
|
|
85308
|
+
import { homedir as homedir40 } from "node:os";
|
|
84641
85309
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
84642
85310
|
|
|
84643
85311
|
class PythonEnvError extends Error {
|
|
@@ -84649,10 +85317,10 @@ class PythonEnvError extends Error {
|
|
|
84649
85317
|
}
|
|
84650
85318
|
}
|
|
84651
85319
|
function defaultPythonCacheRoot() {
|
|
84652
|
-
return join66(
|
|
85320
|
+
return join66(homedir40(), ".switchroom", "deps", "python");
|
|
84653
85321
|
}
|
|
84654
85322
|
function hashFile(path4) {
|
|
84655
|
-
return createHash12("sha256").update(
|
|
85323
|
+
return createHash12("sha256").update(readFileSync60(path4)).digest("hex");
|
|
84656
85324
|
}
|
|
84657
85325
|
function ensurePythonEnv(opts) {
|
|
84658
85326
|
const { skillName, requirementsPath, force = false } = opts;
|
|
@@ -84668,7 +85336,7 @@ function ensurePythonEnv(opts) {
|
|
|
84668
85336
|
const pipBin = join66(binDir, "pip");
|
|
84669
85337
|
const targetHash = hashFile(requirementsPath);
|
|
84670
85338
|
if (!force && existsSync65(stampPath) && existsSync65(pythonBin)) {
|
|
84671
|
-
const existingHash =
|
|
85339
|
+
const existingHash = readFileSync60(stampPath, "utf8").trim();
|
|
84672
85340
|
if (existingHash === targetHash) {
|
|
84673
85341
|
return {
|
|
84674
85342
|
skillName,
|
|
@@ -84720,12 +85388,12 @@ import {
|
|
|
84720
85388
|
copyFileSync as copyFileSync9,
|
|
84721
85389
|
existsSync as existsSync66,
|
|
84722
85390
|
mkdirSync as mkdirSync36,
|
|
84723
|
-
readFileSync as
|
|
85391
|
+
readFileSync as readFileSync61,
|
|
84724
85392
|
rmSync as rmSync14,
|
|
84725
85393
|
writeFileSync as writeFileSync31
|
|
84726
85394
|
} from "node:fs";
|
|
84727
85395
|
import { dirname as dirname21, join as join67 } from "node:path";
|
|
84728
|
-
import { homedir as
|
|
85396
|
+
import { homedir as homedir41 } from "node:os";
|
|
84729
85397
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
84730
85398
|
|
|
84731
85399
|
class NodeEnvError extends Error {
|
|
@@ -84748,14 +85416,14 @@ var LOCKFILES_FOR = {
|
|
|
84748
85416
|
npm: ["package-lock.json"]
|
|
84749
85417
|
};
|
|
84750
85418
|
function defaultNodeCacheRoot() {
|
|
84751
|
-
return join67(
|
|
85419
|
+
return join67(homedir41(), ".switchroom", "deps", "node");
|
|
84752
85420
|
}
|
|
84753
85421
|
function hashDepInputs(packageJsonPath) {
|
|
84754
85422
|
const sourceDir = dirname21(packageJsonPath);
|
|
84755
85423
|
const hasher = createHash13("sha256");
|
|
84756
85424
|
hasher.update(`package.json
|
|
84757
85425
|
`);
|
|
84758
|
-
hasher.update(
|
|
85426
|
+
hasher.update(readFileSync61(packageJsonPath));
|
|
84759
85427
|
for (const lockName of ALL_LOCKFILES) {
|
|
84760
85428
|
const lockPath = join67(sourceDir, lockName);
|
|
84761
85429
|
if (existsSync66(lockPath)) {
|
|
@@ -84764,7 +85432,7 @@ function hashDepInputs(packageJsonPath) {
|
|
|
84764
85432
|
hasher.update(lockName);
|
|
84765
85433
|
hasher.update(`
|
|
84766
85434
|
`);
|
|
84767
|
-
hasher.update(
|
|
85435
|
+
hasher.update(readFileSync61(lockPath));
|
|
84768
85436
|
}
|
|
84769
85437
|
}
|
|
84770
85438
|
return hasher.digest("hex");
|
|
@@ -84783,7 +85451,7 @@ function ensureNodeEnv(opts) {
|
|
|
84783
85451
|
const binDir = join67(nodeModulesDir, ".bin");
|
|
84784
85452
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
84785
85453
|
if (!force && existsSync66(stampPath) && existsSync66(nodeModulesDir)) {
|
|
84786
|
-
const existingHash =
|
|
85454
|
+
const existingHash = readFileSync61(stampPath, "utf8").trim();
|
|
84787
85455
|
if (existingHash === targetHash) {
|
|
84788
85456
|
return {
|
|
84789
85457
|
skillName,
|
|
@@ -84832,7 +85500,7 @@ function ensureNodeEnv(opts) {
|
|
|
84832
85500
|
|
|
84833
85501
|
// src/cli/deps.ts
|
|
84834
85502
|
function builtinSkillsRoot() {
|
|
84835
|
-
return
|
|
85503
|
+
return resolve41(homedir42(), ".switchroom/skills/_bundled");
|
|
84836
85504
|
}
|
|
84837
85505
|
function registerDepsCommand(program3) {
|
|
84838
85506
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
@@ -84911,7 +85579,7 @@ function registerDepsCommand(program3) {
|
|
|
84911
85579
|
init_helpers();
|
|
84912
85580
|
init_loader();
|
|
84913
85581
|
import { existsSync as existsSync68 } from "node:fs";
|
|
84914
|
-
import { resolve as
|
|
85582
|
+
import { resolve as resolve42, sep as sep3 } from "node:path";
|
|
84915
85583
|
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
84916
85584
|
|
|
84917
85585
|
// src/agents/workspace.ts
|
|
@@ -85617,8 +86285,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
85617
86285
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
85618
86286
|
if (!dir)
|
|
85619
86287
|
return;
|
|
85620
|
-
const resolvedWorkspace =
|
|
85621
|
-
const target =
|
|
86288
|
+
const resolvedWorkspace = resolve42(dir);
|
|
86289
|
+
const target = resolve42(resolvedWorkspace, file ?? "AGENTS.md");
|
|
85622
86290
|
if (!isInsideWorkspace(resolvedWorkspace, target)) {
|
|
85623
86291
|
process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
|
|
85624
86292
|
`);
|
|
@@ -85686,7 +86354,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
85686
86354
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
85687
86355
|
if (!dir)
|
|
85688
86356
|
return;
|
|
85689
|
-
const gitDir =
|
|
86357
|
+
const gitDir = resolve42(dir, ".git");
|
|
85690
86358
|
if (!existsSync68(gitDir)) {
|
|
85691
86359
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
85692
86360
|
`);
|
|
@@ -85740,7 +86408,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
85740
86408
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
85741
86409
|
if (!dir)
|
|
85742
86410
|
return;
|
|
85743
|
-
const gitDir =
|
|
86411
|
+
const gitDir = resolve42(dir, ".git");
|
|
85744
86412
|
if (!existsSync68(gitDir)) {
|
|
85745
86413
|
process.stdout.write(`Workspace is not a git repository.
|
|
85746
86414
|
`);
|
|
@@ -85764,7 +86432,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
85764
86432
|
return;
|
|
85765
86433
|
}
|
|
85766
86434
|
const agentsDir = resolveAgentsDir(config);
|
|
85767
|
-
const agentDir =
|
|
86435
|
+
const agentDir = resolve42(agentsDir, agentName);
|
|
85768
86436
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
85769
86437
|
if (!existsSync68(dir)) {
|
|
85770
86438
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
@@ -85802,8 +86470,8 @@ function safeParseInt(value, fallback) {
|
|
|
85802
86470
|
init_helpers();
|
|
85803
86471
|
init_loader();
|
|
85804
86472
|
init_merge();
|
|
85805
|
-
import { copyFileSync as copyFileSync10, existsSync as existsSync69, readFileSync as
|
|
85806
|
-
import { join as join69, resolve as
|
|
86473
|
+
import { copyFileSync as copyFileSync10, existsSync as existsSync69, readFileSync as readFileSync62, writeFileSync as writeFileSync32 } from "node:fs";
|
|
86474
|
+
import { join as join69, resolve as resolve43 } from "node:path";
|
|
85807
86475
|
init_scaffold();
|
|
85808
86476
|
init_profiles();
|
|
85809
86477
|
init_schema();
|
|
@@ -85818,7 +86486,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
85818
86486
|
const profileName = merged.extends ?? DEFAULT_PROFILE;
|
|
85819
86487
|
const profilePath = getProfilePath(profileName);
|
|
85820
86488
|
const agentsDir = resolveAgentsDir(config);
|
|
85821
|
-
const agentDir =
|
|
86489
|
+
const agentDir = resolve43(agentsDir, agentName);
|
|
85822
86490
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
85823
86491
|
if (!existsSync69(workspaceDir)) {
|
|
85824
86492
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
@@ -85850,7 +86518,7 @@ function registerSoulCommand(program3) {
|
|
|
85850
86518
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
85851
86519
|
process.exit(1);
|
|
85852
86520
|
}
|
|
85853
|
-
process.stdout.write(
|
|
86521
|
+
process.stdout.write(readFileSync62(t.soulPath, "utf-8"));
|
|
85854
86522
|
}));
|
|
85855
86523
|
cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
|
|
85856
86524
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -85895,8 +86563,8 @@ function registerSoulCommand(program3) {
|
|
|
85895
86563
|
// src/cli/debug.ts
|
|
85896
86564
|
init_helpers();
|
|
85897
86565
|
init_loader();
|
|
85898
|
-
import { existsSync as existsSync70, readFileSync as
|
|
85899
|
-
import { resolve as
|
|
86566
|
+
import { existsSync as existsSync70, readFileSync as readFileSync63, readdirSync as readdirSync23, statSync as statSync30 } from "node:fs";
|
|
86567
|
+
import { resolve as resolve44, join as join70 } from "node:path";
|
|
85900
86568
|
import { createHash as createHash14 } from "node:crypto";
|
|
85901
86569
|
init_merge();
|
|
85902
86570
|
init_hindsight();
|
|
@@ -85911,7 +86579,7 @@ function readMcpServerNames(agentDir) {
|
|
|
85911
86579
|
if (!existsSync70(mcpPath))
|
|
85912
86580
|
return [];
|
|
85913
86581
|
try {
|
|
85914
|
-
const parsed = JSON.parse(
|
|
86582
|
+
const parsed = JSON.parse(readFileSync63(mcpPath, "utf-8"));
|
|
85915
86583
|
return Object.keys(parsed.mcpServers ?? {});
|
|
85916
86584
|
} catch {
|
|
85917
86585
|
return null;
|
|
@@ -85946,7 +86614,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
85946
86614
|
}
|
|
85947
86615
|
function extractLatestUserMessage(transcriptPath) {
|
|
85948
86616
|
try {
|
|
85949
|
-
const content =
|
|
86617
|
+
const content = readFileSync63(transcriptPath, "utf-8");
|
|
85950
86618
|
const lines = content.trim().split(`
|
|
85951
86619
|
`).filter(Boolean);
|
|
85952
86620
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -85994,7 +86662,7 @@ function registerDebugCommand(program3) {
|
|
|
85994
86662
|
process.exit(1);
|
|
85995
86663
|
}
|
|
85996
86664
|
const agentsDir = resolveAgentsDir(config);
|
|
85997
|
-
const agentDir =
|
|
86665
|
+
const agentDir = resolve44(agentsDir, agentName);
|
|
85998
86666
|
if (!existsSync70(agentDir)) {
|
|
85999
86667
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
86000
86668
|
process.exit(1);
|
|
@@ -86050,7 +86718,7 @@ function registerDebugCommand(program3) {
|
|
|
86050
86718
|
}
|
|
86051
86719
|
console.log(`=== Append System Prompt (per-session) ===
|
|
86052
86720
|
`);
|
|
86053
|
-
const handoffContent = existsSync70(handoffPath) ?
|
|
86721
|
+
const handoffContent = existsSync70(handoffPath) ? readFileSync63(handoffPath, "utf-8") : "";
|
|
86054
86722
|
if (handoffContent.trim().length > 0) {
|
|
86055
86723
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
86056
86724
|
console.log(handoffContent);
|
|
@@ -86061,7 +86729,7 @@ function registerDebugCommand(program3) {
|
|
|
86061
86729
|
}
|
|
86062
86730
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
86063
86731
|
`);
|
|
86064
|
-
const claudeMdContent = existsSync70(claudeMdPath) ?
|
|
86732
|
+
const claudeMdContent = existsSync70(claudeMdPath) ? readFileSync63(claudeMdPath, "utf-8") : "";
|
|
86065
86733
|
if (claudeMdContent.trim().length > 0) {
|
|
86066
86734
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
86067
86735
|
console.log(claudeMdContent);
|
|
@@ -86072,7 +86740,7 @@ function registerDebugCommand(program3) {
|
|
|
86072
86740
|
}
|
|
86073
86741
|
console.log(`=== Persona (SOUL.md) ===
|
|
86074
86742
|
`);
|
|
86075
|
-
const soulMdContent = existsSync70(soulMdPath) ?
|
|
86743
|
+
const soulMdContent = existsSync70(soulMdPath) ? readFileSync63(soulMdPath, "utf-8") : existsSync70(workspaceSoulMdPath) ? readFileSync63(workspaceSoulMdPath, "utf-8") : "";
|
|
86076
86744
|
if (soulMdContent.trim().length > 0) {
|
|
86077
86745
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
86078
86746
|
console.log(soulMdContent);
|
|
@@ -86136,8 +86804,8 @@ function registerDebugCommand(program3) {
|
|
|
86136
86804
|
const fleetDir = join70(agentsDir, "..", "fleet");
|
|
86137
86805
|
const fleetInvPath = join70(fleetDir, "switchroom-invariants.md");
|
|
86138
86806
|
const fleetClaudePath = join70(fleetDir, "CLAUDE.md");
|
|
86139
|
-
const fleetInvBytes = existsSync70(fleetInvPath) ?
|
|
86140
|
-
const fleetClaudeBytes = existsSync70(fleetClaudePath) ?
|
|
86807
|
+
const fleetInvBytes = existsSync70(fleetInvPath) ? readFileSync63(fleetInvPath, "utf-8").length : 0;
|
|
86808
|
+
const fleetClaudeBytes = existsSync70(fleetClaudePath) ? readFileSync63(fleetClaudePath, "utf-8").length : 0;
|
|
86141
86809
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
86142
86810
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
86143
86811
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -86171,24 +86839,24 @@ init_source();
|
|
|
86171
86839
|
// src/worktree/claim.ts
|
|
86172
86840
|
import { execFileSync as execFileSync21 } from "node:child_process";
|
|
86173
86841
|
import { closeSync as closeSync12, mkdirSync as mkdirSync38, openSync as openSync12, existsSync as existsSync72, unlinkSync as unlinkSync13 } from "node:fs";
|
|
86174
|
-
import { join as join72, resolve as
|
|
86175
|
-
import { homedir as
|
|
86842
|
+
import { join as join72, resolve as resolve46 } from "node:path";
|
|
86843
|
+
import { homedir as homedir44 } from "node:os";
|
|
86176
86844
|
import { randomBytes as randomBytes13 } from "node:crypto";
|
|
86177
86845
|
|
|
86178
86846
|
// src/worktree/registry.ts
|
|
86179
86847
|
import {
|
|
86180
86848
|
mkdirSync as mkdirSync37,
|
|
86181
86849
|
writeFileSync as writeFileSync33,
|
|
86182
|
-
readFileSync as
|
|
86850
|
+
readFileSync as readFileSync64,
|
|
86183
86851
|
readdirSync as readdirSync24,
|
|
86184
86852
|
unlinkSync as unlinkSync12,
|
|
86185
86853
|
existsSync as existsSync71,
|
|
86186
86854
|
renameSync as renameSync13
|
|
86187
86855
|
} from "node:fs";
|
|
86188
|
-
import { join as join71, resolve as
|
|
86189
|
-
import { homedir as
|
|
86856
|
+
import { join as join71, resolve as resolve45 } from "node:path";
|
|
86857
|
+
import { homedir as homedir43 } from "node:os";
|
|
86190
86858
|
function registryDir() {
|
|
86191
|
-
return
|
|
86859
|
+
return resolve45(process.env.SWITCHROOM_WORKTREE_DIR ?? join71(homedir43(), ".switchroom", "worktrees"));
|
|
86192
86860
|
}
|
|
86193
86861
|
function recordPath(id) {
|
|
86194
86862
|
return join71(registryDir(), `${id}.json`);
|
|
@@ -86207,7 +86875,7 @@ function writeRecord(record2) {
|
|
|
86207
86875
|
function readRecord(id) {
|
|
86208
86876
|
const path7 = recordPath(id);
|
|
86209
86877
|
try {
|
|
86210
|
-
const raw =
|
|
86878
|
+
const raw = readFileSync64(path7, "utf8");
|
|
86211
86879
|
return JSON.parse(raw);
|
|
86212
86880
|
} catch {
|
|
86213
86881
|
return null;
|
|
@@ -86269,7 +86937,7 @@ function acquireRepoLock(repoPath) {
|
|
|
86269
86937
|
}
|
|
86270
86938
|
var DEFAULT_CONCURRENCY = 5;
|
|
86271
86939
|
function worktreesBaseDir() {
|
|
86272
|
-
return
|
|
86940
|
+
return resolve46(process.env.SWITCHROOM_WORKTREE_BASE ?? join72(homedir44(), ".switchroom", "worktree-checkouts"));
|
|
86273
86941
|
}
|
|
86274
86942
|
function shortId() {
|
|
86275
86943
|
return randomBytes13(4).toString("hex");
|
|
@@ -86291,7 +86959,7 @@ function resolveRepoPath(repo, codeRepos) {
|
|
|
86291
86959
|
}
|
|
86292
86960
|
function expandHome(p) {
|
|
86293
86961
|
if (p.startsWith("~/"))
|
|
86294
|
-
return join72(
|
|
86962
|
+
return join72(homedir44(), p.slice(2));
|
|
86295
86963
|
return p;
|
|
86296
86964
|
}
|
|
86297
86965
|
async function claimWorktree(input, codeRepos) {
|
|
@@ -86466,15 +87134,15 @@ function runReaper(nowMs) {
|
|
|
86466
87134
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
86467
87135
|
import {
|
|
86468
87136
|
existsSync as existsSync75,
|
|
86469
|
-
readFileSync as
|
|
87137
|
+
readFileSync as readFileSync65,
|
|
86470
87138
|
readdirSync as readdirSync25,
|
|
86471
87139
|
statSync as statSync31,
|
|
86472
87140
|
renameSync as renameSync14,
|
|
86473
87141
|
mkdirSync as mkdirSync39,
|
|
86474
87142
|
rmSync as rmSync15
|
|
86475
87143
|
} from "node:fs";
|
|
86476
|
-
import { homedir as
|
|
86477
|
-
import { join as join73, resolve as
|
|
87144
|
+
import { homedir as homedir45 } from "node:os";
|
|
87145
|
+
import { join as join73, resolve as resolve47 } from "node:path";
|
|
86478
87146
|
function parseGitdirPointer(dotGitFileContents) {
|
|
86479
87147
|
const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
|
|
86480
87148
|
return m ? m[1] : null;
|
|
@@ -86549,7 +87217,7 @@ function looksLikeAgentWorktree(path7, branch) {
|
|
|
86549
87217
|
return false;
|
|
86550
87218
|
}
|
|
86551
87219
|
function isEphemeralPath(path7) {
|
|
86552
|
-
const p =
|
|
87220
|
+
const p = resolve47(path7);
|
|
86553
87221
|
for (const root of ["/tmp", "/host", "/state"]) {
|
|
86554
87222
|
if (p === root || p.startsWith(root + "/"))
|
|
86555
87223
|
return true;
|
|
@@ -86589,12 +87257,12 @@ function defaultPrSignal(repo, branch, exec) {
|
|
|
86589
87257
|
}
|
|
86590
87258
|
}
|
|
86591
87259
|
function trashRoot() {
|
|
86592
|
-
return
|
|
87260
|
+
return resolve47(process.env.SWITCHROOM_WORKTREE_TRASH ?? join73(homedir45(), ".switchroom", "worktree-gc-trash"));
|
|
86593
87261
|
}
|
|
86594
87262
|
function planGc(roots, deps = {}) {
|
|
86595
87263
|
const exists = deps.existsSync ?? existsSync75;
|
|
86596
87264
|
const readDir = deps.readDir ?? ((p) => readdirSync25(p));
|
|
86597
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
87265
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync65(p, "utf8"));
|
|
86598
87266
|
const stat3 = deps.stat ?? ((p) => statSync31(p));
|
|
86599
87267
|
const exec = deps.exec ?? defaultExec;
|
|
86600
87268
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
@@ -86602,7 +87270,7 @@ function planGc(roots, deps = {}) {
|
|
|
86602
87270
|
const trash = join73(trashRoot(), stamp);
|
|
86603
87271
|
let claimed;
|
|
86604
87272
|
try {
|
|
86605
|
-
claimed = new Set(listRecords().map((r) =>
|
|
87273
|
+
claimed = new Set(listRecords().map((r) => resolve47(r.path)));
|
|
86606
87274
|
} catch {
|
|
86607
87275
|
claimed = new Set;
|
|
86608
87276
|
}
|
|
@@ -86680,8 +87348,8 @@ function planGc(roots, deps = {}) {
|
|
|
86680
87348
|
continue;
|
|
86681
87349
|
}
|
|
86682
87350
|
for (const wt of parseWorktreeList(porcelain)) {
|
|
86683
|
-
const wtPath =
|
|
86684
|
-
const isMain = wtPath ===
|
|
87351
|
+
const wtPath = resolve47(wt.path);
|
|
87352
|
+
const isMain = wtPath === resolve47(repo);
|
|
86685
87353
|
const claimedWt = claimed.has(wtPath);
|
|
86686
87354
|
const agentWt = looksLikeAgentWorktree(wt.path, wt.branch);
|
|
86687
87355
|
const ephemeral = isEphemeralPath(wt.path);
|
|
@@ -86810,7 +87478,7 @@ function purgeTrash(paths) {
|
|
|
86810
87478
|
return { deleted, errors: errors2 };
|
|
86811
87479
|
}
|
|
86812
87480
|
function defaultRoots() {
|
|
86813
|
-
return [join73(
|
|
87481
|
+
return [join73(homedir45(), "code")];
|
|
86814
87482
|
}
|
|
86815
87483
|
|
|
86816
87484
|
// src/cli/worktree.ts
|
|
@@ -87419,20 +88087,20 @@ function wireStdio(child) {
|
|
|
87419
88087
|
async function killChild(child, gracefulMs = 3000) {
|
|
87420
88088
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
87421
88089
|
return;
|
|
87422
|
-
return new Promise((
|
|
88090
|
+
return new Promise((resolve48) => {
|
|
87423
88091
|
let killTimer = null;
|
|
87424
88092
|
const onExit = () => {
|
|
87425
88093
|
if (killTimer) {
|
|
87426
88094
|
clearTimeout(killTimer);
|
|
87427
88095
|
killTimer = null;
|
|
87428
88096
|
}
|
|
87429
|
-
|
|
88097
|
+
resolve48();
|
|
87430
88098
|
};
|
|
87431
88099
|
child.once("exit", onExit);
|
|
87432
88100
|
try {
|
|
87433
88101
|
child.kill("SIGTERM");
|
|
87434
88102
|
} catch {
|
|
87435
|
-
|
|
88103
|
+
resolve48();
|
|
87436
88104
|
return;
|
|
87437
88105
|
}
|
|
87438
88106
|
killTimer = setTimeout(() => {
|
|
@@ -87546,8 +88214,8 @@ async function runMs365McpLauncher(opts, rt) {
|
|
|
87546
88214
|
};
|
|
87547
88215
|
process.on("SIGINT", onSignal);
|
|
87548
88216
|
process.on("SIGTERM", onSignal);
|
|
87549
|
-
return new Promise((
|
|
87550
|
-
resolveLauncher =
|
|
88217
|
+
return new Promise((resolve48) => {
|
|
88218
|
+
resolveLauncher = resolve48;
|
|
87551
88219
|
});
|
|
87552
88220
|
}
|
|
87553
88221
|
function registerM365McpLauncherCommand(program3) {
|
|
@@ -87649,23 +88317,23 @@ async function runNotionMcpLauncher(opts, runtime) {
|
|
|
87649
88317
|
};
|
|
87650
88318
|
process.on("SIGTERM", () => forward("SIGTERM"));
|
|
87651
88319
|
process.on("SIGINT", () => forward("SIGINT"));
|
|
87652
|
-
const exitCode = await new Promise((
|
|
88320
|
+
const exitCode = await new Promise((resolve48) => {
|
|
87653
88321
|
child.once("exit", (code, signal) => {
|
|
87654
88322
|
clearTimer(heartbeatHandle);
|
|
87655
88323
|
if (typeof code === "number") {
|
|
87656
|
-
|
|
88324
|
+
resolve48(code);
|
|
87657
88325
|
} else if (signal) {
|
|
87658
88326
|
const sigCode = { SIGTERM: 15, SIGINT: 2, SIGKILL: 9 }[signal] ?? 1;
|
|
87659
|
-
|
|
88327
|
+
resolve48(128 + sigCode);
|
|
87660
88328
|
} else {
|
|
87661
|
-
|
|
88329
|
+
resolve48(1);
|
|
87662
88330
|
}
|
|
87663
88331
|
});
|
|
87664
88332
|
child.once("error", (err) => {
|
|
87665
88333
|
clearTimer(heartbeatHandle);
|
|
87666
88334
|
process.stderr.write(`notion-mcp-launcher: child spawn error: ${err.message}
|
|
87667
88335
|
`);
|
|
87668
|
-
|
|
88336
|
+
resolve48(1);
|
|
87669
88337
|
});
|
|
87670
88338
|
});
|
|
87671
88339
|
return exitCode;
|
|
@@ -87704,7 +88372,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
87704
88372
|
|
|
87705
88373
|
// src/cli/deliver-file.ts
|
|
87706
88374
|
init_client2();
|
|
87707
|
-
import { readFileSync as
|
|
88375
|
+
import { readFileSync as readFileSync66, statSync as statSync32 } from "node:fs";
|
|
87708
88376
|
import { basename as basename10 } from "node:path";
|
|
87709
88377
|
|
|
87710
88378
|
// src/delivery/onedrive.ts
|
|
@@ -88044,7 +88712,7 @@ async function defaultResolveProvider() {
|
|
|
88044
88712
|
async function runDeliverFile(localPath, deps = {}) {
|
|
88045
88713
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
88046
88714
|
const sizeOf = deps.fileSize ?? ((p) => statSync32(p).size);
|
|
88047
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
88715
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync66(p)));
|
|
88048
88716
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
88049
88717
|
let size;
|
|
88050
88718
|
try {
|
|
@@ -88365,9 +89033,9 @@ function runRedactStdin() {
|
|
|
88365
89033
|
}
|
|
88366
89034
|
|
|
88367
89035
|
// src/cli/status-ask.ts
|
|
88368
|
-
import { readFileSync as
|
|
89036
|
+
import { readFileSync as readFileSync70, existsSync as existsSync81, readdirSync as readdirSync28 } from "node:fs";
|
|
88369
89037
|
import { join as join80 } from "node:path";
|
|
88370
|
-
import { homedir as
|
|
89038
|
+
import { homedir as homedir48 } from "node:os";
|
|
88371
89039
|
|
|
88372
89040
|
// src/status-ask/report.ts
|
|
88373
89041
|
function parseJsonl(content) {
|
|
@@ -88641,7 +89309,7 @@ function runReport(opts) {
|
|
|
88641
89309
|
for (const src of sources) {
|
|
88642
89310
|
let content;
|
|
88643
89311
|
try {
|
|
88644
|
-
content =
|
|
89312
|
+
content = readFileSync70(src.path, "utf-8");
|
|
88645
89313
|
} catch (err) {
|
|
88646
89314
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
88647
89315
|
`);
|
|
@@ -88702,7 +89370,7 @@ function resolveSources(explicitPath) {
|
|
|
88702
89370
|
const config = loadConfig();
|
|
88703
89371
|
agentsDir = resolveAgentsDir(config);
|
|
88704
89372
|
} catch {
|
|
88705
|
-
agentsDir = join80(
|
|
89373
|
+
agentsDir = join80(homedir48(), ".switchroom", "agents");
|
|
88706
89374
|
}
|
|
88707
89375
|
if (!existsSync81(agentsDir))
|
|
88708
89376
|
return [];
|
|
@@ -88750,16 +89418,16 @@ import {
|
|
|
88750
89418
|
mkdirSync as mkdirSync47,
|
|
88751
89419
|
openSync as openSync13,
|
|
88752
89420
|
readdirSync as readdirSync29,
|
|
88753
|
-
readFileSync as
|
|
89421
|
+
readFileSync as readFileSync71,
|
|
88754
89422
|
renameSync as renameSync16,
|
|
88755
89423
|
statSync as statSync33,
|
|
88756
89424
|
unlinkSync as unlinkSync14,
|
|
88757
89425
|
writeSync as writeSync8
|
|
88758
89426
|
} from "node:fs";
|
|
88759
|
-
import { join as join81, resolve as
|
|
89427
|
+
import { join as join81, resolve as resolve50 } from "node:path";
|
|
88760
89428
|
var STAGING_SUBDIR = ".staging";
|
|
88761
89429
|
function overlayPathsFor(agent, opts = {}) {
|
|
88762
|
-
const base = opts.root ?
|
|
89430
|
+
const base = opts.root ? resolve50(opts.root, agent) : resolve50(resolveDualPath(`~/.switchroom/agents/${agent}`));
|
|
88763
89431
|
const scheduleDir = join81(base, "schedule.d");
|
|
88764
89432
|
const scheduleStagingDir = join81(scheduleDir, STAGING_SUBDIR);
|
|
88765
89433
|
const skillsDir = join81(base, "skills.d");
|
|
@@ -88874,7 +89542,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
88874
89542
|
continue;
|
|
88875
89543
|
const full = join81(paths.skillsDir, name);
|
|
88876
89544
|
try {
|
|
88877
|
-
const raw =
|
|
89545
|
+
const raw = readFileSync71(full, "utf-8");
|
|
88878
89546
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
88879
89547
|
out.push({ slug, path: full, raw });
|
|
88880
89548
|
} catch {}
|
|
@@ -88901,7 +89569,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
88901
89569
|
continue;
|
|
88902
89570
|
const full = join81(paths.scheduleDir, name);
|
|
88903
89571
|
try {
|
|
88904
|
-
const raw =
|
|
89572
|
+
const raw = readFileSync71(full, "utf-8");
|
|
88905
89573
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
88906
89574
|
out.push({ slug, path: full, raw });
|
|
88907
89575
|
} catch {}
|
|
@@ -89057,7 +89725,7 @@ import {
|
|
|
89057
89725
|
mkdirSync as mkdirSync48,
|
|
89058
89726
|
openSync as openSync14,
|
|
89059
89727
|
readdirSync as readdirSync30,
|
|
89060
|
-
readFileSync as
|
|
89728
|
+
readFileSync as readFileSync72,
|
|
89061
89729
|
renameSync as renameSync17,
|
|
89062
89730
|
unlinkSync as unlinkSync15,
|
|
89063
89731
|
writeFileSync as writeFileSync41,
|
|
@@ -89121,7 +89789,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
89121
89789
|
if (!existsSync83(yamlPath))
|
|
89122
89790
|
continue;
|
|
89123
89791
|
try {
|
|
89124
|
-
const meta = JSON.parse(
|
|
89792
|
+
const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
|
|
89125
89793
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
89126
89794
|
continue;
|
|
89127
89795
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -89159,7 +89827,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
89159
89827
|
}
|
|
89160
89828
|
|
|
89161
89829
|
// src/cli/agent-config-write.ts
|
|
89162
|
-
import { existsSync as existsSync84, readFileSync as
|
|
89830
|
+
import { existsSync as existsSync84, readFileSync as readFileSync73 } from "node:fs";
|
|
89163
89831
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
89164
89832
|
|
|
89165
89833
|
// src/scheduler/schedule-report.ts
|
|
@@ -89572,7 +90240,7 @@ function scheduleRemove(opts) {
|
|
|
89572
90240
|
let priorContent = null;
|
|
89573
90241
|
try {
|
|
89574
90242
|
if (existsSync84(match.path))
|
|
89575
|
-
priorContent =
|
|
90243
|
+
priorContent = readFileSync73(match.path, "utf-8");
|
|
89576
90244
|
} catch {}
|
|
89577
90245
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
89578
90246
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -89775,7 +90443,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
89775
90443
|
}
|
|
89776
90444
|
let blob;
|
|
89777
90445
|
if (opts.jsonl) {
|
|
89778
|
-
blob = existsSync84(opts.jsonl) ?
|
|
90446
|
+
blob = existsSync84(opts.jsonl) ? readFileSync73(opts.jsonl, "utf-8") : "";
|
|
89779
90447
|
} else {
|
|
89780
90448
|
try {
|
|
89781
90449
|
blob = execFileSync26("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -90056,7 +90724,7 @@ import {
|
|
|
90056
90724
|
mkdirSync as mkdirSync49,
|
|
90057
90725
|
mkdtempSync as mkdtempSync5,
|
|
90058
90726
|
openSync as openSync15,
|
|
90059
|
-
readFileSync as
|
|
90727
|
+
readFileSync as readFileSync74,
|
|
90060
90728
|
readdirSync as readdirSync31,
|
|
90061
90729
|
realpathSync as realpathSync7,
|
|
90062
90730
|
renameSync as renameSync18,
|
|
@@ -90064,8 +90732,8 @@ import {
|
|
|
90064
90732
|
statSync as statSync34,
|
|
90065
90733
|
writeFileSync as writeFileSync42
|
|
90066
90734
|
} from "node:fs";
|
|
90067
|
-
import { tmpdir as tmpdir5, homedir as
|
|
90068
|
-
import { dirname as dirname28, join as join84, relative as relative2, resolve as
|
|
90735
|
+
import { tmpdir as tmpdir5, homedir as homedir49 } from "node:os";
|
|
90736
|
+
import { dirname as dirname28, join as join84, relative as relative2, resolve as resolve51 } from "node:path";
|
|
90069
90737
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
90070
90738
|
|
|
90071
90739
|
// src/cli/skill-common.ts
|
|
@@ -90299,11 +90967,11 @@ function scanForClaudeP2(content) {
|
|
|
90299
90967
|
function resolveSkillsPoolDir2(override) {
|
|
90300
90968
|
const raw = override ?? "~/.switchroom/skills";
|
|
90301
90969
|
if (raw.startsWith("~/")) {
|
|
90302
|
-
return join84(
|
|
90970
|
+
return join84(homedir49(), raw.slice(2));
|
|
90303
90971
|
}
|
|
90304
90972
|
if (raw === "~")
|
|
90305
|
-
return
|
|
90306
|
-
return
|
|
90973
|
+
return homedir49();
|
|
90974
|
+
return resolve51(raw);
|
|
90307
90975
|
}
|
|
90308
90976
|
function readStdinSync() {
|
|
90309
90977
|
const chunks = [];
|
|
@@ -90348,7 +91016,7 @@ function loadFromDir(dir) {
|
|
|
90348
91016
|
continue;
|
|
90349
91017
|
}
|
|
90350
91018
|
if (ent.isFile()) {
|
|
90351
|
-
const buf =
|
|
91019
|
+
const buf = readFileSync74(full);
|
|
90352
91020
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
90353
91021
|
}
|
|
90354
91022
|
}
|
|
@@ -90397,7 +91065,7 @@ function loadFromTarball(tarPath) {
|
|
|
90397
91065
|
}
|
|
90398
91066
|
}
|
|
90399
91067
|
function loadSingleFile(filePath) {
|
|
90400
|
-
const content =
|
|
91068
|
+
const content = readFileSync74(filePath, "utf-8");
|
|
90401
91069
|
return { "SKILL.md": content };
|
|
90402
91070
|
}
|
|
90403
91071
|
function loadFromStdin() {
|
|
@@ -90488,7 +91156,7 @@ function diffSummary(currentDir, files) {
|
|
|
90488
91156
|
if (ent.isDirectory()) {
|
|
90489
91157
|
walk2(full);
|
|
90490
91158
|
} else if (ent.isFile()) {
|
|
90491
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
91159
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync74(full, "utf-8");
|
|
90492
91160
|
}
|
|
90493
91161
|
}
|
|
90494
91162
|
};
|
|
@@ -90587,7 +91255,7 @@ function registerSkillCommand(program3) {
|
|
|
90587
91255
|
if (opts.from === undefined) {
|
|
90588
91256
|
files = loadFromStdin();
|
|
90589
91257
|
} else {
|
|
90590
|
-
const fromPath =
|
|
91258
|
+
const fromPath = resolve51(opts.from);
|
|
90591
91259
|
if (!existsSync86(fromPath)) {
|
|
90592
91260
|
fail3(`--from path does not exist: ${opts.from}`);
|
|
90593
91261
|
}
|
|
@@ -90649,7 +91317,7 @@ import {
|
|
|
90649
91317
|
mkdirSync as mkdirSync50,
|
|
90650
91318
|
mkdtempSync as mkdtempSync6,
|
|
90651
91319
|
openSync as openSync16,
|
|
90652
|
-
readFileSync as
|
|
91320
|
+
readFileSync as readFileSync75,
|
|
90653
91321
|
readdirSync as readdirSync32,
|
|
90654
91322
|
renameSync as renameSync19,
|
|
90655
91323
|
rmSync as rmSync19,
|
|
@@ -90657,8 +91325,8 @@ import {
|
|
|
90657
91325
|
utimesSync,
|
|
90658
91326
|
writeFileSync as writeFileSync43
|
|
90659
91327
|
} from "node:fs";
|
|
90660
|
-
import { dirname as dirname29, join as join85, relative as relative3, resolve as
|
|
90661
|
-
import { homedir as
|
|
91328
|
+
import { dirname as dirname29, join as join85, relative as relative3, resolve as resolve52 } from "node:path";
|
|
91329
|
+
import { homedir as homedir50, tmpdir as tmpdir6 } from "node:os";
|
|
90662
91330
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
90663
91331
|
init_helpers();
|
|
90664
91332
|
init_agent_config();
|
|
@@ -90669,7 +91337,7 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
|
90669
91337
|
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
90670
91338
|
function resolveConfigSkillsDir(agent) {
|
|
90671
91339
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
90672
|
-
const candidate = override ?
|
|
91340
|
+
const candidate = override ? resolve52(override) : join85(homedir50(), ".switchroom-config");
|
|
90673
91341
|
if (!existsSync87(candidate))
|
|
90674
91342
|
return null;
|
|
90675
91343
|
return join85(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
@@ -90732,7 +91400,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
90732
91400
|
if (ent.isDirectory())
|
|
90733
91401
|
walk2(s, d);
|
|
90734
91402
|
else if (ent.isFile()) {
|
|
90735
|
-
writeFileSync43(d,
|
|
91403
|
+
writeFileSync43(d, readFileSync75(s));
|
|
90736
91404
|
}
|
|
90737
91405
|
}
|
|
90738
91406
|
};
|
|
@@ -90764,8 +91432,8 @@ function resolveAgent(opts) {
|
|
|
90764
91432
|
}
|
|
90765
91433
|
function resolveAgentsRoot(opts) {
|
|
90766
91434
|
if (opts.root)
|
|
90767
|
-
return
|
|
90768
|
-
return join85(
|
|
91435
|
+
return resolve52(opts.root);
|
|
91436
|
+
return join85(homedir50(), ".switchroom", "agents");
|
|
90769
91437
|
}
|
|
90770
91438
|
function personalSkillDir(agentsRoot, agent, name) {
|
|
90771
91439
|
return join85(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
|
|
@@ -90805,7 +91473,7 @@ function readStdinSync2() {
|
|
|
90805
91473
|
return Buffer.concat(chunks).toString("utf-8");
|
|
90806
91474
|
}
|
|
90807
91475
|
function loadFromDir2(dir) {
|
|
90808
|
-
const abs =
|
|
91476
|
+
const abs = resolve52(dir);
|
|
90809
91477
|
if (!statSync35(abs).isDirectory()) {
|
|
90810
91478
|
fail4(`--from path is not a directory: ${dir}`);
|
|
90811
91479
|
}
|
|
@@ -90822,7 +91490,7 @@ function loadFromDir2(dir) {
|
|
|
90822
91490
|
}
|
|
90823
91491
|
if (ent.isFile()) {
|
|
90824
91492
|
const rel = relative3(abs, full).replace(/\\/g, "/");
|
|
90825
|
-
files[rel] =
|
|
91493
|
+
files[rel] = readFileSync75(full, "utf-8");
|
|
90826
91494
|
}
|
|
90827
91495
|
}
|
|
90828
91496
|
};
|
|
@@ -91013,7 +91681,7 @@ function loadFiles(opts) {
|
|
|
91013
91681
|
if (opts.from === undefined) {
|
|
91014
91682
|
return loadFromStdin2();
|
|
91015
91683
|
}
|
|
91016
|
-
const p =
|
|
91684
|
+
const p = resolve52(opts.from);
|
|
91017
91685
|
if (!existsSync87(p)) {
|
|
91018
91686
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
91019
91687
|
}
|
|
@@ -91022,7 +91690,7 @@ function loadFiles(opts) {
|
|
|
91022
91690
|
return loadFromDir2(p);
|
|
91023
91691
|
}
|
|
91024
91692
|
if (p.endsWith(".md")) {
|
|
91025
|
-
return { "SKILL.md":
|
|
91693
|
+
return { "SKILL.md": readFileSync75(p, "utf-8") };
|
|
91026
91694
|
}
|
|
91027
91695
|
fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
91028
91696
|
}
|
|
@@ -91062,10 +91730,10 @@ function editPersonalAction(name, opts) {
|
|
|
91062
91730
|
}
|
|
91063
91731
|
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
91064
91732
|
function defaultSharedRoot() {
|
|
91065
|
-
return join85(
|
|
91733
|
+
return join85(homedir50(), ".switchroom", "skills");
|
|
91066
91734
|
}
|
|
91067
91735
|
function defaultBundledRoot() {
|
|
91068
|
-
return join85(
|
|
91736
|
+
return join85(homedir50(), ".switchroom", "skills", "_bundled");
|
|
91069
91737
|
}
|
|
91070
91738
|
function resolveCloneSource(source, opts) {
|
|
91071
91739
|
const m = CLONE_SOURCE_RE.exec(source);
|
|
@@ -91111,7 +91779,7 @@ function readSourceFiles(dir) {
|
|
|
91111
91779
|
fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
91112
91780
|
}
|
|
91113
91781
|
} catch {}
|
|
91114
|
-
files[rel] =
|
|
91782
|
+
files[rel] = readFileSync75(full, "utf-8");
|
|
91115
91783
|
}
|
|
91116
91784
|
}
|
|
91117
91785
|
};
|
|
@@ -91279,19 +91947,19 @@ function registerSkillPersonalCommands(program3) {
|
|
|
91279
91947
|
|
|
91280
91948
|
// src/cli/self-improve-propose-skill.ts
|
|
91281
91949
|
import { createConnection as createConnection4 } from "node:net";
|
|
91282
|
-
import { homedir as
|
|
91950
|
+
import { homedir as homedir51 } from "node:os";
|
|
91283
91951
|
import { join as join86 } from "node:path";
|
|
91284
|
-
import { readFileSync as
|
|
91952
|
+
import { readFileSync as readFileSync76 } from "node:fs";
|
|
91285
91953
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
91286
91954
|
function gatewaySocketPath() {
|
|
91287
|
-
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join86(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join86(
|
|
91955
|
+
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join86(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join86(homedir51(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
91288
91956
|
}
|
|
91289
91957
|
function fail5(msg, code = 1) {
|
|
91290
91958
|
console.error(msg);
|
|
91291
91959
|
process.exit(code);
|
|
91292
91960
|
}
|
|
91293
91961
|
function sendProposal(payload) {
|
|
91294
|
-
return new Promise((
|
|
91962
|
+
return new Promise((resolve53, reject) => {
|
|
91295
91963
|
const sock = createConnection4({ path: gatewaySocketPath() });
|
|
91296
91964
|
const t = setTimeout(() => {
|
|
91297
91965
|
sock.destroy();
|
|
@@ -91302,7 +91970,7 @@ function sendProposal(payload) {
|
|
|
91302
91970
|
sock.write(JSON.stringify(payload) + `
|
|
91303
91971
|
`, () => {
|
|
91304
91972
|
sock.end();
|
|
91305
|
-
|
|
91973
|
+
resolve53();
|
|
91306
91974
|
});
|
|
91307
91975
|
});
|
|
91308
91976
|
sock.on("error", (err2) => {
|
|
@@ -91319,7 +91987,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
91319
91987
|
fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
91320
91988
|
let draft;
|
|
91321
91989
|
try {
|
|
91322
|
-
draft = JSON.parse(
|
|
91990
|
+
draft = JSON.parse(readFileSync76(opts.draft, "utf-8"));
|
|
91323
91991
|
} catch (e) {
|
|
91324
91992
|
fail5(`failed to read/parse --draft: ${e.message}`);
|
|
91325
91993
|
}
|
|
@@ -91356,20 +92024,20 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
91356
92024
|
init_esm();
|
|
91357
92025
|
init_helpers();
|
|
91358
92026
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
91359
|
-
import { existsSync as existsSync88, readdirSync as readdirSync33, readFileSync as
|
|
91360
|
-
import { homedir as
|
|
91361
|
-
import { join as join87, resolve as
|
|
92027
|
+
import { existsSync as existsSync88, readdirSync as readdirSync33, readFileSync as readFileSync77, statSync as statSync36 } from "node:fs";
|
|
92028
|
+
import { homedir as homedir52 } from "node:os";
|
|
92029
|
+
import { join as join87, resolve as resolve53 } from "node:path";
|
|
91362
92030
|
var PERSONAL_PREFIX2 = "personal-";
|
|
91363
92031
|
var BUNDLED_SUBDIR = "_bundled";
|
|
91364
92032
|
var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
91365
92033
|
function defaultAgentsRoot() {
|
|
91366
|
-
return
|
|
92034
|
+
return resolve53(homedir52(), ".switchroom/agents");
|
|
91367
92035
|
}
|
|
91368
92036
|
function defaultSharedRoot2() {
|
|
91369
|
-
return
|
|
92037
|
+
return resolve53(homedir52(), ".switchroom/skills");
|
|
91370
92038
|
}
|
|
91371
92039
|
function defaultBundledRoot2() {
|
|
91372
|
-
return
|
|
92040
|
+
return resolve53(homedir52(), ".switchroom/skills/_bundled");
|
|
91373
92041
|
}
|
|
91374
92042
|
function readSkillFrontmatter(skillDir) {
|
|
91375
92043
|
const mdPath = join87(skillDir, "SKILL.md");
|
|
@@ -91377,7 +92045,7 @@ function readSkillFrontmatter(skillDir) {
|
|
|
91377
92045
|
return null;
|
|
91378
92046
|
let content;
|
|
91379
92047
|
try {
|
|
91380
|
-
content =
|
|
92048
|
+
content = readFileSync77(mdPath, "utf-8");
|
|
91381
92049
|
} catch {
|
|
91382
92050
|
return null;
|
|
91383
92051
|
}
|
|
@@ -91654,14 +92322,14 @@ import {
|
|
|
91654
92322
|
existsSync as existsSync90,
|
|
91655
92323
|
mkdirSync as mkdirSync51,
|
|
91656
92324
|
readdirSync as readdirSync34,
|
|
91657
|
-
readFileSync as
|
|
92325
|
+
readFileSync as readFileSync79,
|
|
91658
92326
|
writeFileSync as writeFileSync44,
|
|
91659
92327
|
statSync as statSync37,
|
|
91660
92328
|
lstatSync as lstatSync11,
|
|
91661
92329
|
realpathSync as realpathSync8,
|
|
91662
92330
|
copyFileSync as copyFileSync12
|
|
91663
92331
|
} from "node:fs";
|
|
91664
|
-
import { homedir as
|
|
92332
|
+
import { homedir as homedir53 } from "node:os";
|
|
91665
92333
|
import { join as join88 } from "node:path";
|
|
91666
92334
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
91667
92335
|
|
|
@@ -91884,7 +92552,7 @@ networks:
|
|
|
91884
92552
|
# operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
|
|
91885
92553
|
`;
|
|
91886
92554
|
}
|
|
91887
|
-
function resolveHostdHostHome(env2 = process.env, home2 =
|
|
92555
|
+
function resolveHostdHostHome(env2 = process.env, home2 = homedir53()) {
|
|
91888
92556
|
const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
|
|
91889
92557
|
const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
|
|
91890
92558
|
if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
|
|
@@ -91919,7 +92587,7 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
91919
92587
|
return target;
|
|
91920
92588
|
}
|
|
91921
92589
|
function hostdDir() {
|
|
91922
|
-
return join88(
|
|
92590
|
+
return join88(homedir53(), ".switchroom", "hostd");
|
|
91923
92591
|
}
|
|
91924
92592
|
function hostdComposePath() {
|
|
91925
92593
|
return join88(hostdDir(), "docker-compose.yml");
|
|
@@ -92100,7 +92768,7 @@ function registerHostdCommand(program3) {
|
|
|
92100
92768
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
92101
92769
|
return;
|
|
92102
92770
|
}
|
|
92103
|
-
const raw =
|
|
92771
|
+
const raw = readFileSync79(logPath, "utf-8");
|
|
92104
92772
|
const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
|
|
92105
92773
|
const filters = {
|
|
92106
92774
|
agent: opts.agent,
|
|
@@ -92144,7 +92812,7 @@ init_source();
|
|
|
92144
92812
|
init_helpers();
|
|
92145
92813
|
init_operator_uid();
|
|
92146
92814
|
import { existsSync as existsSync91, mkdirSync as mkdirSync52, writeFileSync as writeFileSync45, copyFileSync as copyFileSync13 } from "node:fs";
|
|
92147
|
-
import { homedir as
|
|
92815
|
+
import { homedir as homedir54 } from "node:os";
|
|
92148
92816
|
import { join as join89 } from "node:path";
|
|
92149
92817
|
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
92150
92818
|
function resolveWebImageTag(explicitTag, release) {
|
|
@@ -92230,7 +92898,7 @@ services:
|
|
|
92230
92898
|
`;
|
|
92231
92899
|
}
|
|
92232
92900
|
function webdDir() {
|
|
92233
|
-
return join89(
|
|
92901
|
+
return join89(homedir54(), ".switchroom", "web");
|
|
92234
92902
|
}
|
|
92235
92903
|
function webdComposePath() {
|
|
92236
92904
|
return join89(webdDir(), "docker-compose.yml");
|
|
@@ -92275,7 +92943,7 @@ async function doInstall2(opts, program3) {
|
|
|
92275
92943
|
return;
|
|
92276
92944
|
}
|
|
92277
92945
|
const yaml = renderWebComposeFile({
|
|
92278
|
-
hostHome:
|
|
92946
|
+
hostHome: homedir54(),
|
|
92279
92947
|
imageTag,
|
|
92280
92948
|
operatorUid
|
|
92281
92949
|
});
|
|
@@ -92374,7 +93042,7 @@ function registerWebdCommand(program3) {
|
|
|
92374
93042
|
|
|
92375
93043
|
// src/cli/host-repair.ts
|
|
92376
93044
|
init_source();
|
|
92377
|
-
import { homedir as
|
|
93045
|
+
import { homedir as homedir55 } from "node:os";
|
|
92378
93046
|
import { join as join90 } from "node:path";
|
|
92379
93047
|
var ARTIFACT_ALLOWLIST = {
|
|
92380
93048
|
dockerComposePluginDir: (home2) => join90(home2, ".docker", "cli-plugins", "docker-compose"),
|
|
@@ -92426,7 +93094,7 @@ function isStateBogusAutoDir(probe2) {
|
|
|
92426
93094
|
return true;
|
|
92427
93095
|
}
|
|
92428
93096
|
function planMountRepairs(probe2, opts = {}) {
|
|
92429
|
-
const home2 = opts.hostHome ??
|
|
93097
|
+
const home2 = opts.hostHome ?? homedir55();
|
|
92430
93098
|
const items = [];
|
|
92431
93099
|
const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
|
|
92432
93100
|
const dockerComposeSt = probe2.lstat(dockerComposePath);
|
|
@@ -92614,6 +93282,221 @@ Some artifacts were skipped (see above). Remediate them manually.`));
|
|
|
92614
93282
|
});
|
|
92615
93283
|
}
|
|
92616
93284
|
|
|
93285
|
+
// src/cli/fleet-health.ts
|
|
93286
|
+
init_source();
|
|
93287
|
+
init_helpers();
|
|
93288
|
+
init_scan();
|
|
93289
|
+
|
|
93290
|
+
// src/fleet-health/gh-sync.ts
|
|
93291
|
+
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
93292
|
+
var LABEL = "fleet-health";
|
|
93293
|
+
function defaultGhDeps(log) {
|
|
93294
|
+
return {
|
|
93295
|
+
log,
|
|
93296
|
+
run: (args) => {
|
|
93297
|
+
const r = spawnSync22("gh", args, { encoding: "utf-8" });
|
|
93298
|
+
return {
|
|
93299
|
+
ok: r.status === 0,
|
|
93300
|
+
stdout: (r.stdout ?? "").trim(),
|
|
93301
|
+
stderr: (r.stderr ?? "").trim()
|
|
93302
|
+
};
|
|
93303
|
+
}
|
|
93304
|
+
};
|
|
93305
|
+
}
|
|
93306
|
+
function ghAvailable(deps) {
|
|
93307
|
+
const r = deps.run(["auth", "status"]);
|
|
93308
|
+
return r.ok;
|
|
93309
|
+
}
|
|
93310
|
+
function severityLabel(sev) {
|
|
93311
|
+
const s = Math.max(1, Math.min(3, Math.round(sev)));
|
|
93312
|
+
return `severity:${s}`;
|
|
93313
|
+
}
|
|
93314
|
+
function issueTitle(iss, job_spec) {
|
|
93315
|
+
return `[fleet-health] ${job_spec}: ${iss.failure_mode} (${iss.dedup_key})`;
|
|
93316
|
+
}
|
|
93317
|
+
function issueBody(iss, job_spec) {
|
|
93318
|
+
const lines = [];
|
|
93319
|
+
lines.push(`**Job spec:** \`${job_spec}\` (\`reference/jobs/${job_spec}.md\`)`);
|
|
93320
|
+
lines.push(`**Failure mode:** ${iss.failure_mode} \u2014 severity ${iss.severity}`);
|
|
93321
|
+
lines.push(`**Dedup key:** \`${iss.dedup_key}\``);
|
|
93322
|
+
lines.push(`**Frequency (scan window):** ${iss.frequency}`);
|
|
93323
|
+
lines.push(`**Reach:** ${iss.reach.join(", ") || "\u2014"}`);
|
|
93324
|
+
lines.push(`**Newest occurrence:** ${iss.recency ?? "\u2014"}`);
|
|
93325
|
+
lines.push("");
|
|
93326
|
+
lines.push("**Occurrences (hard-artifact evidence):**");
|
|
93327
|
+
for (const o of iss.occurrences.slice(0, 25)) {
|
|
93328
|
+
lines.push(`- \`${o.agent}\` turn \`${o.turn_id}\` \u2014 ${o.log_pointer ?? ""}`);
|
|
93329
|
+
}
|
|
93330
|
+
lines.push("");
|
|
93331
|
+
lines.push("_Opened + maintained by the Fleet Health sensor " + "(`switchroom fleet-health scan --sync-issues`). Closes automatically on " + "a verified count-drop. See `reference/rfcs/fleet-health.md`._");
|
|
93332
|
+
return lines.join(`
|
|
93333
|
+
`);
|
|
93334
|
+
}
|
|
93335
|
+
function syncIssue(deps, repo, job_spec, iss) {
|
|
93336
|
+
const labels = [LABEL, severityLabel(iss.severity), `job:${job_spec}`];
|
|
93337
|
+
const title = issueTitle(iss, job_spec);
|
|
93338
|
+
const body = issueBody(iss, job_spec);
|
|
93339
|
+
const shouldClose = iss.status === "closed" || iss.status === "resolved-pending-verify";
|
|
93340
|
+
if (iss.gh_issue === undefined) {
|
|
93341
|
+
if (shouldClose)
|
|
93342
|
+
return;
|
|
93343
|
+
const r = deps.run([
|
|
93344
|
+
"issue",
|
|
93345
|
+
"create",
|
|
93346
|
+
"-R",
|
|
93347
|
+
repo,
|
|
93348
|
+
"--title",
|
|
93349
|
+
title,
|
|
93350
|
+
"--body",
|
|
93351
|
+
body,
|
|
93352
|
+
"--label",
|
|
93353
|
+
labels.join(",")
|
|
93354
|
+
]);
|
|
93355
|
+
if (r.ok) {
|
|
93356
|
+
const m = r.stdout.match(/\/issues\/(\d+)/);
|
|
93357
|
+
if (m) {
|
|
93358
|
+
iss.gh_issue = Number(m[1]);
|
|
93359
|
+
deps.log(`fleet-health: opened GH #${iss.gh_issue} for ${iss.dedup_key}`);
|
|
93360
|
+
}
|
|
93361
|
+
} else {
|
|
93362
|
+
deps.log(`fleet-health: gh issue create failed for ${iss.dedup_key}: ${r.stderr}`);
|
|
93363
|
+
}
|
|
93364
|
+
return;
|
|
93365
|
+
}
|
|
93366
|
+
const num = String(iss.gh_issue);
|
|
93367
|
+
const upd = deps.run(["issue", "edit", num, "-R", repo, "--body", body]);
|
|
93368
|
+
if (!upd.ok) {
|
|
93369
|
+
deps.log(`fleet-health: gh issue edit #${num} failed: ${upd.stderr}`);
|
|
93370
|
+
}
|
|
93371
|
+
if (iss.status === "closed") {
|
|
93372
|
+
const c = deps.run([
|
|
93373
|
+
"issue",
|
|
93374
|
+
"close",
|
|
93375
|
+
num,
|
|
93376
|
+
"-R",
|
|
93377
|
+
repo,
|
|
93378
|
+
"--comment",
|
|
93379
|
+
`Verified count-drop (frequency ${iss.frequency} \u2264 resolved threshold). Closed by the Fleet Health sensor.`
|
|
93380
|
+
]);
|
|
93381
|
+
if (c.ok)
|
|
93382
|
+
deps.log(`fleet-health: closed GH #${num} (count-drop) for ${iss.dedup_key}`);
|
|
93383
|
+
else
|
|
93384
|
+
deps.log(`fleet-health: gh issue close #${num} failed: ${c.stderr}`);
|
|
93385
|
+
}
|
|
93386
|
+
}
|
|
93387
|
+
function syncLedgerIssues(ledger, repo, deps) {
|
|
93388
|
+
if (!ghAvailable(deps)) {
|
|
93389
|
+
deps.log("fleet-health: gh unavailable or not authenticated \u2014 skipping GitHub issue sync (ledger still written).");
|
|
93390
|
+
return { synced: 0, skipped: true };
|
|
93391
|
+
}
|
|
93392
|
+
let synced = 0;
|
|
93393
|
+
for (const rec of ledger.records) {
|
|
93394
|
+
for (const iss of rec.issues) {
|
|
93395
|
+
syncIssue(deps, repo, rec.job_spec, iss);
|
|
93396
|
+
synced++;
|
|
93397
|
+
}
|
|
93398
|
+
rec.gh_issues = rec.issues.map((i) => i.gh_issue).filter((n) => typeof n === "number");
|
|
93399
|
+
}
|
|
93400
|
+
return { synced, skipped: false };
|
|
93401
|
+
}
|
|
93402
|
+
|
|
93403
|
+
// src/cli/fleet-health.ts
|
|
93404
|
+
init_mapping();
|
|
93405
|
+
function registerFleetHealthCommand(program3) {
|
|
93406
|
+
const cmd = program3.command("fleet-health").description("Fleet Health live detection pipeline: model-free sensor, priority " + "ledger, and GitHub-issue lifecycle (RFC fleet-health.md).");
|
|
93407
|
+
cmd.command("scan").description("Nightly model-free sensor: scan every agent's turns.jsonl + gateway " + "log, rank recurring failures by priority, write the ledger.").option("--dry-run", "print the ledger, do not write it", false).option("--json", "emit machine-readable JSON", false).option("--sync-issues", "open/update/close GitHub issues (needs an authed gh; off by default)", false).option("--window-days <n>", "scan window in days", "30").option("--repo <owner/name>", "GitHub repo for issue sync (default switchroom/switchroom)", "switchroom/switchroom").action(withConfigError(async (opts) => {
|
|
93408
|
+
const cfg = getConfig(program3);
|
|
93409
|
+
const ownerAgent = cfg.fleet_health?.owner_agent;
|
|
93410
|
+
const windowDays = Number(opts.windowDays) || 30;
|
|
93411
|
+
const log = (m) => console.error(source_default.gray(m));
|
|
93412
|
+
const result = runScan({ ownerAgent, windowDays, log });
|
|
93413
|
+
const ledger = result.ledger;
|
|
93414
|
+
log(`fleet-health: scanned ${result.agentsScanned} agents` + (result.agentsSkipped.length ? `, skipped ${result.agentsSkipped.length} (${result.agentsSkipped.join(", ")})` : ""));
|
|
93415
|
+
if (opts.syncIssues && !opts.dryRun) {
|
|
93416
|
+
const deps = defaultGhDeps(log);
|
|
93417
|
+
const sync = syncLedgerIssues(ledger, String(opts.repo), deps);
|
|
93418
|
+
if (!sync.skipped) {
|
|
93419
|
+
log(`fleet-health: synced ${sync.synced} issues to ${String(opts.repo)}`);
|
|
93420
|
+
}
|
|
93421
|
+
} else if (opts.syncIssues && opts.dryRun) {
|
|
93422
|
+
log("fleet-health: --dry-run set, skipping GitHub issue sync.");
|
|
93423
|
+
}
|
|
93424
|
+
if (opts.json) {
|
|
93425
|
+
process.stdout.write(JSON.stringify(ledger, null, 2) + `
|
|
93426
|
+
`);
|
|
93427
|
+
} else {
|
|
93428
|
+
printLedgerTable(ledger);
|
|
93429
|
+
}
|
|
93430
|
+
if (opts.dryRun) {
|
|
93431
|
+
log("fleet-health: --dry-run \u2014 ledger NOT written.");
|
|
93432
|
+
return;
|
|
93433
|
+
}
|
|
93434
|
+
const base = resolveSwitchroomBase();
|
|
93435
|
+
const path8 = writeLedger(base, ledger);
|
|
93436
|
+
log(source_default.green(`fleet-health: wrote ledger \u2192 ${path8}`));
|
|
93437
|
+
}));
|
|
93438
|
+
cmd.command("deep-dive-targets").description("Print the top-N ledger records (by priority) as a structured brief " + "for the owner agent's weekly Opus deep-dive cron.").option("--top <n>", "number of targets", "2").option("--json", "emit machine-readable JSON", false).action(withConfigError(async (opts) => {
|
|
93439
|
+
const base = resolveSwitchroomBase();
|
|
93440
|
+
const { readLedgerIfPresent: readLedgerIfPresent2 } = await Promise.resolve().then(() => (init_scan(), exports_scan));
|
|
93441
|
+
const ledger = readLedgerIfPresent2(base);
|
|
93442
|
+
const top = Number(opts.top) || 2;
|
|
93443
|
+
if (!ledger) {
|
|
93444
|
+
console.error(source_default.yellow("fleet-health: no ledger yet \u2014 run `fleet-health scan` first."));
|
|
93445
|
+
return;
|
|
93446
|
+
}
|
|
93447
|
+
const targets = [...ledger.records].filter((r) => r.open_issue_count > 0).sort((a, b) => b.priority_score - a.priority_score).slice(0, top);
|
|
93448
|
+
if (opts.json) {
|
|
93449
|
+
process.stdout.write(JSON.stringify({ targets }, null, 2) + `
|
|
93450
|
+
`);
|
|
93451
|
+
return;
|
|
93452
|
+
}
|
|
93453
|
+
printDeepDiveBrief(targets);
|
|
93454
|
+
}));
|
|
93455
|
+
cmd.command("mapping").description("Print the signal\u2192job-spec mapping + severity table.").action(() => {
|
|
93456
|
+
console.log(source_default.bold(`Signal \u2192 job-spec mapping (model-free):
|
|
93457
|
+
`));
|
|
93458
|
+
for (const [signal, m] of Object.entries(SIGNAL_MAP)) {
|
|
93459
|
+
console.log(` ${source_default.cyan(signal.padEnd(28))} \u2192 ${m.job_spec.padEnd(30)} ` + `[${m.failure_mode}, sev ${m.severity}]`);
|
|
93460
|
+
}
|
|
93461
|
+
});
|
|
93462
|
+
}
|
|
93463
|
+
function printLedgerTable(ledger) {
|
|
93464
|
+
const ranked = [...ledger.records].sort((a, b) => b.priority_score - a.priority_score);
|
|
93465
|
+
console.log(source_default.bold(`
|
|
93466
|
+
Fleet Health ledger (worst-first):`));
|
|
93467
|
+
console.log(source_default.gray(`owner=${ledger.owner_agent ?? "(unset)"} generated_at=${ledger.generated_at}`));
|
|
93468
|
+
for (const r of ranked) {
|
|
93469
|
+
if (r.open_issue_count === 0 && r.priority_score === 0)
|
|
93470
|
+
continue;
|
|
93471
|
+
console.log(` ${source_default.yellow(r.priority_score.toFixed(2).padStart(8))} ` + `${r.job_spec.padEnd(34)} open=${r.open_issue_count} ` + `issues=${r.issues.length}`);
|
|
93472
|
+
for (const iss of r.issues) {
|
|
93473
|
+
console.log(source_default.gray(` - ${iss.dedup_key} freq=${iss.frequency} ` + `reach=[${iss.reach.join(",")}] sev=${iss.severity} ${iss.status}`));
|
|
93474
|
+
}
|
|
93475
|
+
}
|
|
93476
|
+
}
|
|
93477
|
+
function printDeepDiveBrief(targets) {
|
|
93478
|
+
console.log(source_default.bold(`Fleet Health \u2014 weekly deep-dive targets
|
|
93479
|
+
`));
|
|
93480
|
+
if (targets.length === 0) {
|
|
93481
|
+
console.log(" (no open issues \u2014 nothing to deep-dive this week)");
|
|
93482
|
+
return;
|
|
93483
|
+
}
|
|
93484
|
+
for (const r of targets) {
|
|
93485
|
+
console.log(source_default.cyan(`## ${r.job_spec} (score ${r.priority_score.toFixed(2)})`));
|
|
93486
|
+
console.log(` spec: reference/jobs/${r.job_spec}.md`);
|
|
93487
|
+
console.log(` open issues: ${r.open_issue_count}`);
|
|
93488
|
+
for (const iss of r.issues) {
|
|
93489
|
+
console.log(` - ${iss.dedup_key} [${iss.failure_mode}, sev ${iss.severity}]`);
|
|
93490
|
+
console.log(` freq=${iss.frequency} reach=[${iss.reach.join(",")}] ` + `newest=${iss.recency ?? "\u2014"} gh=${iss.gh_issue ?? "none"}`);
|
|
93491
|
+
for (const o of iss.occurrences.slice(0, 5)) {
|
|
93492
|
+
console.log(` \u2022 ${o.agent} ${o.turn_id} \u2014 ${o.log_pointer ?? ""}`);
|
|
93493
|
+
}
|
|
93494
|
+
}
|
|
93495
|
+
console.log("");
|
|
93496
|
+
}
|
|
93497
|
+
console.log(source_default.gray("Deep-dive: read the flagged turns' transcripts (join by turn_id), " + "root-cause, and write the finding into the linked GitHub issue."));
|
|
93498
|
+
}
|
|
93499
|
+
|
|
92617
93500
|
// src/cli/index.ts
|
|
92618
93501
|
init_posthog();
|
|
92619
93502
|
installGlobalErrorHandlers();
|
|
@@ -92670,6 +93553,7 @@ registerHostdMcpCommand(program3);
|
|
|
92670
93553
|
registerHostdCommand(program3);
|
|
92671
93554
|
registerWebdCommand(program3);
|
|
92672
93555
|
registerHostCommand(program3);
|
|
93556
|
+
registerFleetHealthCommand(program3);
|
|
92673
93557
|
|
|
92674
93558
|
// bin/switchroom.ts
|
|
92675
93559
|
program3.parse();
|