switchroom 0.21.16 → 0.21.18
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 +5 -0
- package/dist/auth-broker/index.js +5 -0
- package/dist/cli/notion-write-pretool.mjs +5 -0
- package/dist/cli/switchroom.js +1492 -1136
- package/dist/host-control/main.js +6 -1
- package/dist/vault/approvals/kernel-server.js +5 -0
- package/dist/vault/broker/server.js +5 -0
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +11 -0
- package/telegram-plugin/dist/gateway/gateway.js +9 -4
- package/telegram-plugin/scripts/bun-test-ci.sh +6 -0
- package/telegram-plugin/uat/flip/allowlist.test.ts +229 -0
- package/telegram-plugin/uat/flip/allowlist.ts +349 -0
- package/telegram-plugin/uat/flip/gate.test.ts +153 -0
- package/telegram-plugin/uat/flip/gate.ts +232 -0
- package/telegram-plugin/uat/flip/probe-scoring.test.ts +210 -0
- package/telegram-plugin/uat/flip/probe-scoring.ts +200 -0
- package/telegram-plugin/uat/flip/probe-suite.test.ts +95 -0
- package/telegram-plugin/uat/flip/probe-suite.ts +155 -0
- package/telegram-plugin/uat/flip/probes/kdogg.probes.json +36 -0
- package/telegram-plugin/uat/flip/probes/test-harness.probes.json +15 -0
- package/telegram-plugin/uat/flip/recall-log.test.ts +131 -0
- package/telegram-plugin/uat/flip/recall-log.ts +178 -0
- package/telegram-plugin/uat/flip/report.ts +95 -0
- package/telegram-plugin/uat/flip/tier1-equivalence.test.ts +470 -0
- package/telegram-plugin/uat/flip/tier1-equivalence.ts +697 -0
- package/telegram-plugin/uat/flip/tier2-probe-runner.ts +327 -0
- package/telegram-plugin/uat/runners/scorer.ts +1 -1
- package/vendor/hindsight-memory/hooks/hooks.json +9 -0
- package/vendor/hindsight-memory/scripts/directive_verify.py +43 -1
- package/vendor/hindsight-memory/scripts/lib/client.py +35 -0
- package/vendor/hindsight-memory/scripts/lib/config.py +47 -0
- package/vendor/hindsight-memory/scripts/lib/orientation.py +248 -0
- package/vendor/hindsight-memory/scripts/lib/recall_buffer.py +29 -0
- package/vendor/hindsight-memory/scripts/orientation.py +195 -0
- package/vendor/hindsight-memory/scripts/prefetch.py +10 -0
- package/vendor/hindsight-memory/scripts/recall.py +144 -11
- package/vendor/hindsight-memory/scripts/setup_hooks.py +10 -1
- package/vendor/hindsight-memory/scripts/tests/fixtures/rules-block.golden.md +9 -0
- package/vendor/hindsight-memory/scripts/tests/test_orientation_hook.py +283 -0
- package/vendor/hindsight-memory/scripts/tests/test_orientation_logic.py +176 -0
- package/vendor/hindsight-memory/scripts/tests/test_prefetch_invalidation.py +329 -0
- package/vendor/hindsight-memory/scripts/tests/test_recall_directive_suppression.py +328 -0
package/dist/cli/switchroom.js
CHANGED
|
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
|
|
|
2120
2120
|
});
|
|
2121
2121
|
|
|
2122
2122
|
// src/build-info.ts
|
|
2123
|
-
var VERSION = "0.21.
|
|
2123
|
+
var VERSION = "0.21.18", COMMIT_SHA = "ae063418", COMMIT_DATE = "2026-08-18T03:14:53Z";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13745,6 +13745,10 @@ var init_schema = __esm(() => {
|
|
|
13745
13745
|
}).optional().describe("Operator-declared, per-specialist Hindsight mental models (RFC " + "Phase 5). Named, opt-in curated reflections this agent's bank should " + "carry \u2014 e.g. a coach's 'training-plan-state' or a lawyer's " + "'open-matters'. Ensured idempotently at scaffold/reconcile: NOTHING " + "is created unless declared here (zero declarations = zero models, " + "matching post-#2447 behaviour), and no fixed identity model is " + "reintroduced \u2014 'who the user is' stays owned by dedicated profile " + "banks (users.*.profile_bank), never a per-agent model. Per-agent " + "ONLY: intentionally not accepted at the defaults/profile tier, so a " + "model can never be fleet-seeded \u2014 each specialist opts in on its own " + "(the invariant-clean inverse of the retired blind auto-seeding)."),
|
|
13746
13746
|
observations_mission: exports_external.string().optional().describe("Steers what the observation-consolidation LLM synthesises from raw " + "facts (the higher-order 'what patterns matter' lens). Cascade: override."),
|
|
13747
13747
|
rules_block: exports_external.boolean().default(false).describe("Memory v2 M3 go-live flag (M1 only DEFINES this, default false; " + "M3 flips it per agent). When true, the sanctioned rules/index " + "blocks render live in this agent's CLAUDE.md (marker-delimited, " + "below the `# --- Yours ---` line) AND the permission deny for " + "direct Edit/Write of the agent's own CLAUDE.md is seeded \u2014 the " + "flag couples deny + tools together so a live deny never orphans " + "the invited free-text edit path (the `memory_edit_yours` verb is " + "the only sanctioned writer once flipped). Unset/false \u21d2 byte-" + "identical to pre-M1 behaviour: no blocks, no deny, dark build. " + "Cascade: override (per-agent wins over default; never fleet-" + "seeded \u2014 each agent's flip is a deliberate M3 rollout step)."),
|
|
13748
|
+
orientation: exports_external.boolean().optional().describe("Memory v2 M5 go-live flag (Surface B: orientation-at-boot), " + "effective default false \u2014 dark build (unset \u21d2 OFF, applied by the " + "scaffold resolver, NOT a Zod .default() \u2014 a hard default here would " + "shadow the per-agent value in the cascade the way it does every other " + "memory knob). When true, the orientation SessionStart " + "hook injects this agent's cron-refreshed `orientation` mental model " + "into context at boot AND re-seats it after every compaction (E-88), " + "deterministically and with zero tool call. Unset/false \u21d2 byte-" + "identical to pre-M5 behaviour: the hook no-ops before any bank " + "resolve or network call. Per-agent ONLY (never fleet-seeded \u2014 a " + "flip is a deliberate canary/rollout step gated on the agent's " + "m2-residue measurement + operator-created model, carve-M5 \u00a77). " + "Cascade: override (per-agent wins over default)."),
|
|
13749
|
+
orientation_reinject_turns: exports_external.number().int().min(0).optional().describe("Memory v2 M5 optional per-turn re-inject cadence, effective default " + "0 = off (unset \u21d2 0, applied by the scaffold resolver, not a Zod " + ".default() \u2014 same cascade-shadowing reason as `orientation`). At 0 the " + "briefing is injected only at SessionStart + compaction (the cheap " + "path). N>0 " + "would additionally re-inject the orientation briefing every N turns; " + "priced at ~55M tok/30d at every-turn, which is WHY it defaults off " + "(carve-M5 \u00a72/\u00a74). Only late-session context loss should ever set it, " + "per-agent, measured. A stripped/absent value fails to 0 (fail-safe " + "AND fail-cheap). Per-agent ONLY. Cascade: override."),
|
|
13750
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional().describe("Memory v2 M5 per-agent refresh cadence tier (hours), effective " + "default 48 (unset \u21d2 48, applied by the scaffold resolver, not a Zod " + ".default() \u2014 a hard default would shadow a `defaults.memory." + "orientation_cadence_hours` fleet value in the cascade). " + "The fast-churning agents (klanker, overlord) set 24; everyone else " + "inherits 48 (carve-M5 \u00a76, Ken's locked tiered cadence). Drives BOTH " + "the out-of-session refresh scheduler AND the staleness guard's " + "per-tier thresholds (stale prefix at 1.5\u00d7 cadence, cold degrade at " + "3\u00d7) \u2014 so a fixed 36h can never mislabel a 48h-tier agent as stale a " + "day early. Modelled as an explicit int (not a hard-coded name list) " + "so the tiering is data-driven and auditable. A cost knob, not an " + "enablement knob: a stripped/absent value failing to 48 is safe " + "(the cheaper tier). Cascade: override (per-agent wins over default); " + "unlike `orientation`/`_reinject_turns` it IS accepted at the " + "defaults/profile tier."),
|
|
13751
|
+
inject_directives: exports_external.boolean().optional().describe("Memory v2 M3 Surface-A directive-injection switch (default TRUE \u2014 " + "on-by-default via the plugin settings.json stamp + config.py " + "DEFAULTS). When left unset the agent keeps injecting the " + "`<active_directives>` block on every turn, byte-identical to pre-M3. " + "Set FALSE to SUPPRESS that injection for this agent \u2014 its standing " + "rules are then served from the live rules block (memory.rules_block) " + "instead of re-injected directives, which is the change that collapses " + "the always-on directive-token spend (E-41). The suppression is " + "fail-safe and re-checked every turn in recall.py: it fires ONLY when " + "a non-empty rules block is physically present in the agent's " + "CLAUDE.md \u2014 a false flag with an empty/absent block keeps injecting " + "AND emits a degraded-canary notice, so a zero-standing-rules turn is " + "unreachable. Requires memory.rules_block=true first (the block must " + "be live before injection is turned off \u2014 enforce the ordering with " + "`switchroom memory flip`, never a bare flag flip). Coordinates with " + "M4's memory.recall prefetch (memoryPrefetchEnabled): the same guard " + "gates all three recall.py emit sites so a flipped agent leaks no " + "directive on a cache/prefetch hit. Cascade: override, per-agent ONLY " + "(never fleet-seeded from the defaults/profile tier \u2014 each flip is a " + "deliberate staggered M3 rollout step, same darkness safeguard as " + "rules_block). Default-true is the fail-safe direction under the " + "Zod-strip footgun: a stripped/misread key keeps the agent injecting."),
|
|
13748
13752
|
disposition: exports_external.object({
|
|
13749
13753
|
skepticism: exports_external.number().int().min(1).max(5).optional().describe("How much the bank doubts unverified claims (1-5; engine default 3)."),
|
|
13750
13754
|
literalism: exports_external.number().int().min(1).max(5).optional().describe("How literally the bank reads statements vs inferring intent (1-5; engine default 3)."),
|
|
@@ -14190,6 +14194,7 @@ var init_schema = __esm(() => {
|
|
|
14190
14194
|
profile: exports_external.string().optional(),
|
|
14191
14195
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
14192
14196
|
profile_capture_nudge: exports_external.boolean().optional(),
|
|
14197
|
+
orientation_cadence_hours: exports_external.number().int().min(1).optional(),
|
|
14193
14198
|
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
14194
14199
|
observation_scopes: ObservationScopesSchema,
|
|
14195
14200
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
@@ -19562,6 +19567,9 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
|
|
|
19562
19567
|
}
|
|
19563
19568
|
};
|
|
19564
19569
|
}
|
|
19570
|
+
const grantedBanks = [
|
|
19571
|
+
...new Set((opts.knowledgeExtraBanks ?? []).filter((b) => b && b !== collection))
|
|
19572
|
+
];
|
|
19565
19573
|
return {
|
|
19566
19574
|
type: "stdio",
|
|
19567
19575
|
command: HINDSIGHT_SHIM_CLI_PATH,
|
|
@@ -19569,6 +19577,7 @@ function generateHindsightMcpConfig(collection, memoryConfig, opts = {}) {
|
|
|
19569
19577
|
env: {
|
|
19570
19578
|
HINDSIGHT_MCP_URL: url,
|
|
19571
19579
|
HINDSIGHT_BANK_ID: collection,
|
|
19580
|
+
...grantedBanks.length > 0 ? { HINDSIGHT_KNOWLEDGE_EXTRA_BANKS: grantedBanks.join(",") } : {},
|
|
19572
19581
|
HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
|
|
19573
19582
|
HOME: HINDSIGHT_SHIM_AGENT_HOME,
|
|
19574
19583
|
...opts.reflectBudget ? { HINDSIGHT_SHIM_REFLECT_BUDGET: opts.reflectBudget } : {},
|
|
@@ -28007,9 +28016,11 @@ function getHindsightSettingsEntry(agentName, config) {
|
|
|
28007
28016
|
}
|
|
28008
28017
|
const collection = getCollectionForAgent(agentName, config);
|
|
28009
28018
|
const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[agentName] ?? {});
|
|
28019
|
+
const { additionalBanks } = resolveUsers(config, agentName);
|
|
28010
28020
|
const mcpConfig = generateHindsightMcpConfig(collection, memoryConfig, {
|
|
28011
28021
|
reflectBudget: resolved.memory?.reflect_budget,
|
|
28012
|
-
reflectMaxTokens: resolved.memory?.reflect_max_tokens
|
|
28022
|
+
reflectMaxTokens: resolved.memory?.reflect_max_tokens,
|
|
28023
|
+
knowledgeExtraBanks: additionalBanks
|
|
28013
28024
|
});
|
|
28014
28025
|
return { key: "hindsight", value: { ...mcpConfig, alwaysLoad: false } };
|
|
28015
28026
|
}
|
|
@@ -28102,6 +28113,7 @@ function getBuiltinDefaultSkillEntries() {
|
|
|
28102
28113
|
var GOOGLE_WORKSPACE_MCP_PINNED_SHA = "db6212992738d4380a94fb6940140bcebf5d861d", MICROSOFT_WORKSPACE_MCP_PINNED_VERSION = "0.113.0", MICROSOFT_WORKSPACE_MCP_PACKAGE = "@softeria/ms-365-mcp-server", NOTION_MCP_PINNED_VERSION = "1.8.1", NOTION_MCP_PACKAGE = "@notionhq/notion-mcp-server", NOTION_TOKEN_ENV = "NOTION_TOKEN";
|
|
28103
28114
|
var init_scaffold_integration = __esm(() => {
|
|
28104
28115
|
init_merge();
|
|
28116
|
+
init_users();
|
|
28105
28117
|
init_hindsight2();
|
|
28106
28118
|
});
|
|
28107
28119
|
|
|
@@ -30493,6 +30505,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
|
|
|
30493
30505
|
const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
|
|
30494
30506
|
const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName, resolvedAgentConfig);
|
|
30495
30507
|
const observationScopeSettings = resolveHindsightObservationScopeSettings(switchroomConfig, agentName, resolvedAgentConfig);
|
|
30508
|
+
const orientationSettings = resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig);
|
|
30496
30509
|
const recallConfig = resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig);
|
|
30497
30510
|
const recallTunables = resolveHindsightRecallTunables(recallConfig);
|
|
30498
30511
|
const recallCaps = resolveHindsightRecallCaps(recallConfig);
|
|
@@ -30500,7 +30513,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
|
|
|
30500
30513
|
try {
|
|
30501
30514
|
const vendorSettingsPath = join15(sourcePath, "settings.json");
|
|
30502
30515
|
if (existsSync20(vendorSettingsPath)) {
|
|
30503
|
-
const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
|
|
30516
|
+
const expectedSettings = renderHindsightSettingsOverrides(readFileSync17(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
|
|
30504
30517
|
if (expectedSettings != null)
|
|
30505
30518
|
contentOverrides["settings.json"] = expectedSettings;
|
|
30506
30519
|
}
|
|
@@ -30518,7 +30531,7 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig, resolvedA
|
|
|
30518
30531
|
}
|
|
30519
30532
|
copyDirRecursive2(sourcePath, destPath);
|
|
30520
30533
|
}
|
|
30521
|
-
applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
|
|
30534
|
+
applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
|
|
30522
30535
|
applyHindsightHooksOverrides(destPath, recallTunables);
|
|
30523
30536
|
const bankId = agentMemory?.collection ?? agentName;
|
|
30524
30537
|
const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
@@ -30547,6 +30560,15 @@ function resolveHindsightObservationScopeSettings(switchroomConfig, agentName, r
|
|
|
30547
30560
|
observationScopeStrategy: memory?.observation_scope_strategy
|
|
30548
30561
|
};
|
|
30549
30562
|
}
|
|
30563
|
+
function resolveHindsightOrientationSettings(switchroomConfig, agentName, resolvedAgentConfig) {
|
|
30564
|
+
const memory = resolvedAgentConfig ? resolvedAgentConfig.memory : switchroomConfig ? resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {})?.memory : undefined;
|
|
30565
|
+
return {
|
|
30566
|
+
enabled: memory?.orientation === true,
|
|
30567
|
+
model: "orientation",
|
|
30568
|
+
cadenceHours: typeof memory?.orientation_cadence_hours === "number" ? memory.orientation_cadence_hours : 48,
|
|
30569
|
+
reinjectTurns: typeof memory?.orientation_reinject_turns === "number" ? memory.orientation_reinject_turns : 0
|
|
30570
|
+
};
|
|
30571
|
+
}
|
|
30550
30572
|
function resolveHindsightRecallConfig(switchroomConfig, agentName, resolvedAgentConfig) {
|
|
30551
30573
|
if (resolvedAgentConfig) {
|
|
30552
30574
|
return resolvedAgentConfig.memory?.recall;
|
|
@@ -30581,7 +30603,7 @@ function defaultRecallOwnBankMinSlots(maxMemories) {
|
|
|
30581
30603
|
function defaultRecallAdditionalBankMinSlots(maxMemories) {
|
|
30582
30604
|
return recallSlotFloorFromCap(maxMemories, RECALL_ADDITIONAL_BANK_SLOT_DIVISOR);
|
|
30583
30605
|
}
|
|
30584
|
-
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
|
|
30606
|
+
function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
|
|
30585
30607
|
const settingsPath = join15(pluginDestPath, "settings.json");
|
|
30586
30608
|
if (!existsSync20(settingsPath))
|
|
30587
30609
|
return;
|
|
@@ -30591,12 +30613,12 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retain
|
|
|
30591
30613
|
} catch {
|
|
30592
30614
|
return;
|
|
30593
30615
|
}
|
|
30594
|
-
const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings);
|
|
30616
|
+
const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings);
|
|
30595
30617
|
if (next == null)
|
|
30596
30618
|
return;
|
|
30597
30619
|
writeFileSyncIfChanged(settingsPath, next);
|
|
30598
30620
|
}
|
|
30599
|
-
function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings) {
|
|
30621
|
+
function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, recallTunables, recallCaps, observationScopeSettings, orientationSettings) {
|
|
30600
30622
|
let settings;
|
|
30601
30623
|
try {
|
|
30602
30624
|
settings = JSON.parse(raw);
|
|
@@ -30618,6 +30640,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
|
|
|
30618
30640
|
settings.recallSkipTrivial = true;
|
|
30619
30641
|
settings.directiveCaptureNudge = true;
|
|
30620
30642
|
settings.profileCaptureNudge = true;
|
|
30643
|
+
settings.injectDirectives = true;
|
|
30621
30644
|
settings.recallTagWeights = { ...HINDSIGHT_RECALL_TAG_WEIGHT_SEED };
|
|
30622
30645
|
if (additionalBanks.length > 0) {
|
|
30623
30646
|
settings.recallAdditionalBanks = [...additionalBanks];
|
|
@@ -30630,6 +30653,10 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig, re
|
|
|
30630
30653
|
if (typeof observationScopeStrategy === "string" && observationScopeStrategy.length > 0) {
|
|
30631
30654
|
settings.observationScopeStrategy = observationScopeStrategy;
|
|
30632
30655
|
}
|
|
30656
|
+
settings.memoryOrientationEnabled = orientationSettings.enabled;
|
|
30657
|
+
settings.memoryOrientationModel = orientationSettings.model;
|
|
30658
|
+
settings.memoryOrientationCadenceHours = orientationSettings.cadenceHours;
|
|
30659
|
+
settings.memoryOrientationReinjectTurns = orientationSettings.reinjectTurns;
|
|
30633
30660
|
return JSON.stringify(settings, null, 2) + `
|
|
30634
30661
|
`;
|
|
30635
30662
|
}
|
|
@@ -30672,6 +30699,7 @@ function buildWorkspaceContext(args) {
|
|
|
30672
30699
|
hindsightRecallSkipTrivial,
|
|
30673
30700
|
hindsightDirectiveCaptureNudge,
|
|
30674
30701
|
hindsightProfileCaptureNudge,
|
|
30702
|
+
hindsightInjectDirectives,
|
|
30675
30703
|
hindsightObservationScopes,
|
|
30676
30704
|
hindsightObservationScopeStrategy,
|
|
30677
30705
|
hindsightTopicAliasesJson,
|
|
@@ -30728,6 +30756,7 @@ function buildWorkspaceContext(args) {
|
|
|
30728
30756
|
hindsightRecallSkipTrivial,
|
|
30729
30757
|
hindsightDirectiveCaptureNudge,
|
|
30730
30758
|
hindsightProfileCaptureNudge,
|
|
30759
|
+
hindsightInjectDirectives,
|
|
30731
30760
|
hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
|
|
30732
30761
|
hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
|
|
30733
30762
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
@@ -31004,6 +31033,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
31004
31033
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
31005
31034
|
const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
|
|
31006
31035
|
const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
|
|
31036
|
+
const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
|
|
31007
31037
|
const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
|
|
31008
31038
|
const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
|
|
31009
31039
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
@@ -31042,6 +31072,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
31042
31072
|
hindsightRecallSkipTrivial,
|
|
31043
31073
|
hindsightDirectiveCaptureNudge,
|
|
31044
31074
|
hindsightProfileCaptureNudge,
|
|
31075
|
+
hindsightInjectDirectives,
|
|
31045
31076
|
hindsightObservationScopes,
|
|
31046
31077
|
hindsightObservationScopeStrategy,
|
|
31047
31078
|
hindsightTopicAliasesJson,
|
|
@@ -32031,6 +32062,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
32031
32062
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
32032
32063
|
const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
|
|
32033
32064
|
const hindsightProfileCaptureNudge = agentConfig.memory?.profile_capture_nudge === undefined ? undefined : String(agentConfig.memory.profile_capture_nudge);
|
|
32065
|
+
const hindsightInjectDirectives = agentConfig.memory?.inject_directives === undefined ? undefined : String(agentConfig.memory.inject_directives);
|
|
32034
32066
|
const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
|
|
32035
32067
|
const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
|
|
32036
32068
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
@@ -32081,6 +32113,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
32081
32113
|
hindsightRecallSkipTrivial,
|
|
32082
32114
|
hindsightDirectiveCaptureNudge,
|
|
32083
32115
|
hindsightProfileCaptureNudge,
|
|
32116
|
+
hindsightInjectDirectives,
|
|
32084
32117
|
hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
|
|
32085
32118
|
hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
|
|
32086
32119
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
@@ -32519,6 +32552,7 @@ ${body}
|
|
|
32519
32552
|
hindsightRecallSkipTrivial,
|
|
32520
32553
|
hindsightDirectiveCaptureNudge,
|
|
32521
32554
|
hindsightProfileCaptureNudge,
|
|
32555
|
+
hindsightInjectDirectives,
|
|
32522
32556
|
hindsightObservationScopes,
|
|
32523
32557
|
hindsightObservationScopeStrategy,
|
|
32524
32558
|
hindsightTopicAliasesJson,
|
|
@@ -66507,6 +66541,51 @@ function readClaudeMdSafe(agentsDir, agentName, readFileFn) {
|
|
|
66507
66541
|
return null;
|
|
66508
66542
|
}
|
|
66509
66543
|
}
|
|
66544
|
+
function runDirectiveFlipChecks(config, deps = {}) {
|
|
66545
|
+
const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
|
|
66546
|
+
let agentsDir = deps.agentsDir;
|
|
66547
|
+
if (agentsDir === undefined) {
|
|
66548
|
+
try {
|
|
66549
|
+
agentsDir = resolveAgentsDir(config);
|
|
66550
|
+
} catch {
|
|
66551
|
+
agentsDir = undefined;
|
|
66552
|
+
}
|
|
66553
|
+
}
|
|
66554
|
+
const results = [];
|
|
66555
|
+
for (const [agentName, agentConfig] of Object.entries(config.agents)) {
|
|
66556
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
66557
|
+
if (resolved.memory?.inject_directives !== false)
|
|
66558
|
+
continue;
|
|
66559
|
+
const name = `agent ${agentName} directive flip (inject_directives=false)`;
|
|
66560
|
+
if (resolved.memory?.rules_block !== true) {
|
|
66561
|
+
results.push({
|
|
66562
|
+
name,
|
|
66563
|
+
status: "fail",
|
|
66564
|
+
detail: "inject_directives is false but memory.rules_block is not true \u2014 the " + "rules block that must carry the migrated directives is not even " + "enabled. recall.py fails safe (keeps injecting), but fix the config: " + "enable rules_block and migrate, or re-enable inject_directives.",
|
|
66565
|
+
fix: `Set memory.rules_block: true for ${agentName} (and migrate its directives) before flipping.`
|
|
66566
|
+
});
|
|
66567
|
+
continue;
|
|
66568
|
+
}
|
|
66569
|
+
const claudeMd = readClaudeMdSafe(agentsDir, agentName, readFileFn);
|
|
66570
|
+
const parsed = claudeMd ? parseRulesBlock(claudeMd) : null;
|
|
66571
|
+
const ruleCount = parsed?.rules.length ?? 0;
|
|
66572
|
+
if (ruleCount === 0) {
|
|
66573
|
+
results.push({
|
|
66574
|
+
name,
|
|
66575
|
+
status: "fail",
|
|
66576
|
+
detail: claudeMd === null ? "inject_directives is false but the agent's CLAUDE.md could not be " + "read to confirm a rules block carries the migrated directives." : "inject_directives is false but the CLAUDE.md rules block is empty " + "(no `- **R-\u2026` rule lines). Every directive would be suppressed with " + "nothing carrying its guarantee. recall.py fails safe at runtime, but " + "fix the config: migrate the directives into the rules block, or " + "re-enable inject_directives.",
|
|
66577
|
+
fix: `Migrate ${agentName}'s directives into the rules block (memory rule add) or re-enable inject_directives.`
|
|
66578
|
+
});
|
|
66579
|
+
} else {
|
|
66580
|
+
results.push({
|
|
66581
|
+
name,
|
|
66582
|
+
status: "ok",
|
|
66583
|
+
detail: `flipped, and the rules block carries ${ruleCount} rule(s) \u2014 directive injection safely suppressed.`
|
|
66584
|
+
});
|
|
66585
|
+
}
|
|
66586
|
+
}
|
|
66587
|
+
return results;
|
|
66588
|
+
}
|
|
66510
66589
|
async function runRulesBlockChecks(config, engineUrl, deps = {}) {
|
|
66511
66590
|
const readFileFn = deps.readFileFn ?? ((p) => readFileSync65(p, "utf-8"));
|
|
66512
66591
|
let agentsDir = deps.agentsDir;
|
|
@@ -68797,6 +68876,7 @@ async function checkHindsight(config) {
|
|
|
68797
68876
|
results.push(...await checkBankIngestHealth(config, url, { includeConsolidationBacklog: true }));
|
|
68798
68877
|
results.push(...await checkBankObservationsMissions(config, url));
|
|
68799
68878
|
results.push(...await runRulesBlockChecks(config, url));
|
|
68879
|
+
results.push(...runDirectiveFlipChecks(config));
|
|
68800
68880
|
results.push(...await checkBankConfigApiReachable(config, url));
|
|
68801
68881
|
results.push(await checkObservationScopeSaturation(config, url));
|
|
68802
68882
|
const memoryAgentsDir = resolveAgentsDir(config);
|
|
@@ -71656,11 +71736,11 @@ var init_apply = __esm(() => {
|
|
|
71656
71736
|
});
|
|
71657
71737
|
|
|
71658
71738
|
// src/host-control/audit-reader.ts
|
|
71659
|
-
import { closeSync as closeSync15, existsSync as
|
|
71739
|
+
import { closeSync as closeSync15, existsSync as existsSync84, openSync as openSync15, readSync as readSync5, statSync as statSync50 } from "node:fs";
|
|
71660
71740
|
import { homedir as homedir43 } from "node:os";
|
|
71661
|
-
import { join as
|
|
71741
|
+
import { join as join91 } from "node:path";
|
|
71662
71742
|
function defaultAuditLogPath2(home2 = homedir43()) {
|
|
71663
|
-
return
|
|
71743
|
+
return join91(home2, ".switchroom", "host-control-audit.log");
|
|
71664
71744
|
}
|
|
71665
71745
|
function auditRotation() {
|
|
71666
71746
|
return resolveRotationConfig({
|
|
@@ -71677,14 +71757,14 @@ function auditReadFullWindowBytes() {
|
|
|
71677
71757
|
function auditReadMaxGenerations() {
|
|
71678
71758
|
return auditRotation().maxFiles;
|
|
71679
71759
|
}
|
|
71680
|
-
function tailBytes(
|
|
71760
|
+
function tailBytes(path9, n, strict = false) {
|
|
71681
71761
|
if (n <= 0)
|
|
71682
71762
|
return "";
|
|
71683
71763
|
let fd;
|
|
71684
71764
|
let size;
|
|
71685
71765
|
try {
|
|
71686
|
-
size =
|
|
71687
|
-
fd = openSync15(
|
|
71766
|
+
size = statSync50(path9).size;
|
|
71767
|
+
fd = openSync15(path9, "r");
|
|
71688
71768
|
} catch (err) {
|
|
71689
71769
|
if (strict && err.code !== "ENOENT")
|
|
71690
71770
|
throw err;
|
|
@@ -71729,7 +71809,7 @@ function readAuditRaw(logPath, opts = {}) {
|
|
|
71729
71809
|
budget -= Buffer.byteLength(active, "utf-8");
|
|
71730
71810
|
for (let n = 1;n <= maxFiles && budget > 0; n++) {
|
|
71731
71811
|
const gen = `${logPath}.${n}`;
|
|
71732
|
-
if (!
|
|
71812
|
+
if (!existsSync84(gen))
|
|
71733
71813
|
break;
|
|
71734
71814
|
const chunk2 = tailBytes(gen, budget, strict);
|
|
71735
71815
|
if (chunk2.length === 0)
|
|
@@ -71907,16 +71987,16 @@ var init_audit_reader = __esm(() => {
|
|
|
71907
71987
|
});
|
|
71908
71988
|
|
|
71909
71989
|
// src/web/fleet-health-read.ts
|
|
71910
|
-
import { readFileSync as
|
|
71911
|
-
import { resolve as
|
|
71990
|
+
import { readFileSync as readFileSync79 } from "node:fs";
|
|
71991
|
+
import { resolve as resolve46 } from "node:path";
|
|
71912
71992
|
import { homedir as homedir46 } from "node:os";
|
|
71913
71993
|
function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir46()) {
|
|
71914
|
-
return
|
|
71994
|
+
return resolve46(home2, ".switchroom", "fleet-health", "ledger.json");
|
|
71915
71995
|
}
|
|
71916
|
-
function readFleetHealth(
|
|
71996
|
+
function readFleetHealth(path9) {
|
|
71917
71997
|
let ledger;
|
|
71918
71998
|
try {
|
|
71919
|
-
ledger = JSON.parse(
|
|
71999
|
+
ledger = JSON.parse(readFileSync79(path9, "utf-8"));
|
|
71920
72000
|
} catch {
|
|
71921
72001
|
return {
|
|
71922
72002
|
owner_agent: null,
|
|
@@ -71942,8 +72022,8 @@ function readFleetHealth(path7) {
|
|
|
71942
72022
|
...ranked.length === 0 ? { reason: EMPTY_REASON } : {}
|
|
71943
72023
|
};
|
|
71944
72024
|
}
|
|
71945
|
-
function handleGetFleetHealth(
|
|
71946
|
-
return readFleetHealth(
|
|
72025
|
+
function handleGetFleetHealth(path9 = fleetHealthLedgerPath()) {
|
|
72026
|
+
return readFleetHealth(path9);
|
|
71947
72027
|
}
|
|
71948
72028
|
var EMPTY_REASON;
|
|
71949
72029
|
var init_fleet_health_read = __esm(() => {
|
|
@@ -110178,8 +110258,8 @@ init_loader();
|
|
|
110178
110258
|
init_hindsight();
|
|
110179
110259
|
var import_yaml16 = __toESM(require_dist(), 1);
|
|
110180
110260
|
import { spawn as spawn5 } from "node:child_process";
|
|
110181
|
-
import { existsSync as
|
|
110182
|
-
import { join as
|
|
110261
|
+
import { existsSync as existsSync83, readFileSync as readFileSync75, statSync as statSync49 } from "node:fs";
|
|
110262
|
+
import { join as join89 } from "node:path";
|
|
110183
110263
|
|
|
110184
110264
|
// src/cli/memory-rules.ts
|
|
110185
110265
|
init_source();
|
|
@@ -110446,11 +110526,933 @@ function registerMemoryDirectiveCommand(memory, program3) {
|
|
|
110446
110526
|
}));
|
|
110447
110527
|
}
|
|
110448
110528
|
|
|
110529
|
+
// src/cli/memory-flip.ts
|
|
110530
|
+
init_source();
|
|
110531
|
+
init_helpers();
|
|
110532
|
+
init_hindsight2();
|
|
110533
|
+
init_hindsight();
|
|
110534
|
+
init_hindsight_directive_admin();
|
|
110535
|
+
|
|
110536
|
+
// src/memory/directive-triage.ts
|
|
110537
|
+
init_hindsight_directive_admin();
|
|
110538
|
+
var SUPERSEDED_BY_TAG_RE = /^superseded-by:(.+)$/;
|
|
110539
|
+
function classifyDirective(directive, override) {
|
|
110540
|
+
const isActive = directive.is_active !== false;
|
|
110541
|
+
const overrideSignal = override?.signal.trim();
|
|
110542
|
+
if (hasRulesBlockMarker(directive.tags)) {
|
|
110543
|
+
return {
|
|
110544
|
+
category: "rules-block",
|
|
110545
|
+
signal: "carries the rules-block marker tag \u2014 staged for M3, never retired",
|
|
110546
|
+
action: "stage-for-m3"
|
|
110547
|
+
};
|
|
110548
|
+
}
|
|
110549
|
+
if (override && override.category === "rules-block") {
|
|
110550
|
+
return {
|
|
110551
|
+
category: "rules-block",
|
|
110552
|
+
signal: overrideSignal || "rules-block override with no signal text \u2014 staged for M3, never retired",
|
|
110553
|
+
action: "stage-for-m3"
|
|
110554
|
+
};
|
|
110555
|
+
}
|
|
110556
|
+
const supersessionTag = (directive.tags ?? []).map((t) => SUPERSEDED_BY_TAG_RE.exec(t)).find((m) => m !== null);
|
|
110557
|
+
if (supersessionTag) {
|
|
110558
|
+
const winner = supersessionTag[1];
|
|
110559
|
+
return {
|
|
110560
|
+
category: "retire",
|
|
110561
|
+
signal: isActive ? `superseded by '${winner}' (superseded-by tag)` : `superseded by '${winner}' (superseded-by tag) \u2014 already inactive, nothing to retire`,
|
|
110562
|
+
action: isActive ? "retire" : "keep",
|
|
110563
|
+
supersededBy: winner
|
|
110564
|
+
};
|
|
110565
|
+
}
|
|
110566
|
+
if (override && overrideSignal) {
|
|
110567
|
+
if (override.category === "reflect-directive") {
|
|
110568
|
+
return { category: "reflect-directive", signal: overrideSignal, action: "keep" };
|
|
110569
|
+
}
|
|
110570
|
+
return {
|
|
110571
|
+
category: override.category,
|
|
110572
|
+
signal: isActive ? overrideSignal : `${overrideSignal} \u2014 already inactive, nothing to retire`,
|
|
110573
|
+
action: isActive ? "retire" : "keep"
|
|
110574
|
+
};
|
|
110575
|
+
}
|
|
110576
|
+
return {
|
|
110577
|
+
category: "reflect-directive",
|
|
110578
|
+
signal: "no deterministic signal \u2014 defaults to KEEP",
|
|
110579
|
+
action: "keep"
|
|
110580
|
+
};
|
|
110581
|
+
}
|
|
110582
|
+
function buildDirectiveTriageRows(directives, overrides) {
|
|
110583
|
+
return directives.map((d) => {
|
|
110584
|
+
const classified = classifyDirective(d, overrides?.get(d.id));
|
|
110585
|
+
return {
|
|
110586
|
+
id: d.id,
|
|
110587
|
+
name: d.name,
|
|
110588
|
+
priority: d.priority ?? 0,
|
|
110589
|
+
isActive: d.is_active !== false,
|
|
110590
|
+
...classified
|
|
110591
|
+
};
|
|
110592
|
+
});
|
|
110593
|
+
}
|
|
110594
|
+
|
|
110595
|
+
// src/cli/debug.ts
|
|
110596
|
+
init_helpers();
|
|
110597
|
+
init_loader();
|
|
110598
|
+
import { existsSync as existsSync82, readFileSync as readFileSync74, readdirSync as readdirSync30, statSync as statSync48 } from "node:fs";
|
|
110599
|
+
import { resolve as resolve43, join as join88 } from "node:path";
|
|
110600
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
110601
|
+
|
|
110602
|
+
// src/agents/workspace.ts
|
|
110603
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
110604
|
+
import path8 from "node:path";
|
|
110605
|
+
|
|
110606
|
+
// src/agents/bootstrap-budget.ts
|
|
110607
|
+
import path7 from "node:path";
|
|
110608
|
+
var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
|
|
110609
|
+
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
|
|
110610
|
+
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
|
|
110611
|
+
|
|
110612
|
+
class BootstrapBudgetExceededError extends Error {
|
|
110613
|
+
analysis;
|
|
110614
|
+
constructor(analysis, message) {
|
|
110615
|
+
super(message);
|
|
110616
|
+
this.analysis = analysis;
|
|
110617
|
+
this.name = "BootstrapBudgetExceededError";
|
|
110618
|
+
}
|
|
110619
|
+
}
|
|
110620
|
+
function normalizePositiveLimit(value) {
|
|
110621
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
110622
|
+
return 1;
|
|
110623
|
+
}
|
|
110624
|
+
return Math.floor(value);
|
|
110625
|
+
}
|
|
110626
|
+
function formatWarningCause(cause) {
|
|
110627
|
+
return cause === "per-file-limit" ? "max/file" : "max/total";
|
|
110628
|
+
}
|
|
110629
|
+
function normalizeSeenSignatures(signatures) {
|
|
110630
|
+
if (!Array.isArray(signatures) || signatures.length === 0) {
|
|
110631
|
+
return [];
|
|
110632
|
+
}
|
|
110633
|
+
const seen = new Set;
|
|
110634
|
+
const result = [];
|
|
110635
|
+
for (const signature of signatures) {
|
|
110636
|
+
const value = typeof signature === "string" ? signature.trim() : "";
|
|
110637
|
+
if (!value || seen.has(value)) {
|
|
110638
|
+
continue;
|
|
110639
|
+
}
|
|
110640
|
+
seen.add(value);
|
|
110641
|
+
result.push(value);
|
|
110642
|
+
}
|
|
110643
|
+
return result;
|
|
110644
|
+
}
|
|
110645
|
+
function appendSeenSignature(signatures, signature) {
|
|
110646
|
+
if (!signature.trim()) {
|
|
110647
|
+
return signatures;
|
|
110648
|
+
}
|
|
110649
|
+
if (signatures.includes(signature)) {
|
|
110650
|
+
return signatures;
|
|
110651
|
+
}
|
|
110652
|
+
const next = [...signatures, signature];
|
|
110653
|
+
if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
|
|
110654
|
+
return next;
|
|
110655
|
+
}
|
|
110656
|
+
return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
|
|
110657
|
+
}
|
|
110658
|
+
function buildBootstrapInjectionStats(params) {
|
|
110659
|
+
const injectedByPath = new Map;
|
|
110660
|
+
const injectedByBaseName = new Map;
|
|
110661
|
+
for (const file of params.injectedFiles) {
|
|
110662
|
+
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
110663
|
+
if (!pathValue) {
|
|
110664
|
+
continue;
|
|
110665
|
+
}
|
|
110666
|
+
if (!injectedByPath.has(pathValue)) {
|
|
110667
|
+
injectedByPath.set(pathValue, file.content);
|
|
110668
|
+
}
|
|
110669
|
+
const normalizedPath = pathValue.replace(/\\/g, "/");
|
|
110670
|
+
const baseName = path7.posix.basename(normalizedPath);
|
|
110671
|
+
if (!injectedByBaseName.has(baseName)) {
|
|
110672
|
+
injectedByBaseName.set(baseName, file.content);
|
|
110673
|
+
}
|
|
110674
|
+
}
|
|
110675
|
+
return params.bootstrapFiles.map((file) => {
|
|
110676
|
+
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
110677
|
+
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
|
110678
|
+
const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
|
|
110679
|
+
const injectedChars = injected ? injected.length : 0;
|
|
110680
|
+
const truncated = !file.missing && injectedChars < rawChars;
|
|
110681
|
+
return {
|
|
110682
|
+
name: file.name,
|
|
110683
|
+
path: pathValue || file.name,
|
|
110684
|
+
missing: file.missing,
|
|
110685
|
+
rawChars,
|
|
110686
|
+
injectedChars,
|
|
110687
|
+
truncated
|
|
110688
|
+
};
|
|
110689
|
+
});
|
|
110690
|
+
}
|
|
110691
|
+
function analyzeBootstrapBudget(params) {
|
|
110692
|
+
const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
|
|
110693
|
+
const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
|
|
110694
|
+
const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
|
|
110695
|
+
const nonMissing = params.files.filter((file) => !file.missing);
|
|
110696
|
+
const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
|
|
110697
|
+
const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
|
|
110698
|
+
const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
|
|
110699
|
+
const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
|
|
110700
|
+
const files = params.files.map((file) => {
|
|
110701
|
+
if (file.missing) {
|
|
110702
|
+
return { ...file, nearLimit: false, causes: [] };
|
|
110703
|
+
}
|
|
110704
|
+
const perFileOverLimit = file.rawChars > bootstrapMaxChars;
|
|
110705
|
+
const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
|
|
110706
|
+
const causes = [];
|
|
110707
|
+
if (file.truncated) {
|
|
110708
|
+
if (perFileOverLimit) {
|
|
110709
|
+
causes.push("per-file-limit");
|
|
110710
|
+
}
|
|
110711
|
+
if (totalOverLimit) {
|
|
110712
|
+
causes.push("total-limit");
|
|
110713
|
+
}
|
|
110714
|
+
}
|
|
110715
|
+
return { ...file, nearLimit, causes };
|
|
110716
|
+
});
|
|
110717
|
+
const truncatedFiles = files.filter((file) => file.truncated);
|
|
110718
|
+
const nearLimitFiles = files.filter((file) => file.nearLimit);
|
|
110719
|
+
return {
|
|
110720
|
+
files,
|
|
110721
|
+
truncatedFiles,
|
|
110722
|
+
nearLimitFiles,
|
|
110723
|
+
totalNearLimit,
|
|
110724
|
+
hasTruncation: truncatedFiles.length > 0,
|
|
110725
|
+
totals: {
|
|
110726
|
+
rawChars,
|
|
110727
|
+
injectedChars,
|
|
110728
|
+
truncatedChars: Math.max(0, rawChars - injectedChars),
|
|
110729
|
+
bootstrapMaxChars,
|
|
110730
|
+
bootstrapTotalMaxChars,
|
|
110731
|
+
nearLimitRatio
|
|
110732
|
+
}
|
|
110733
|
+
};
|
|
110734
|
+
}
|
|
110735
|
+
function buildBootstrapTruncationSignature(analysis) {
|
|
110736
|
+
if (!analysis.hasTruncation) {
|
|
110737
|
+
return;
|
|
110738
|
+
}
|
|
110739
|
+
const files = analysis.truncatedFiles.map((file) => ({
|
|
110740
|
+
path: file.path || file.name,
|
|
110741
|
+
rawChars: file.rawChars,
|
|
110742
|
+
injectedChars: file.injectedChars,
|
|
110743
|
+
causes: [...file.causes].sort()
|
|
110744
|
+
})).sort((a, b) => {
|
|
110745
|
+
const pathCmp = a.path.localeCompare(b.path);
|
|
110746
|
+
if (pathCmp !== 0) {
|
|
110747
|
+
return pathCmp;
|
|
110748
|
+
}
|
|
110749
|
+
if (a.rawChars !== b.rawChars) {
|
|
110750
|
+
return a.rawChars - b.rawChars;
|
|
110751
|
+
}
|
|
110752
|
+
if (a.injectedChars !== b.injectedChars) {
|
|
110753
|
+
return a.injectedChars - b.injectedChars;
|
|
110754
|
+
}
|
|
110755
|
+
return a.causes.join("+").localeCompare(b.causes.join("+"));
|
|
110756
|
+
});
|
|
110757
|
+
return JSON.stringify({
|
|
110758
|
+
bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
|
|
110759
|
+
bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
|
|
110760
|
+
files
|
|
110761
|
+
});
|
|
110762
|
+
}
|
|
110763
|
+
function formatBootstrapTruncationWarningLines(params) {
|
|
110764
|
+
if (!params.analysis.hasTruncation) {
|
|
110765
|
+
return [];
|
|
110766
|
+
}
|
|
110767
|
+
const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
|
|
110768
|
+
const lines = [];
|
|
110769
|
+
const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
|
|
110770
|
+
acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
|
|
110771
|
+
return acc;
|
|
110772
|
+
}, new Map);
|
|
110773
|
+
const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
|
|
110774
|
+
for (const file of topFiles) {
|
|
110775
|
+
const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
|
|
110776
|
+
const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
|
|
110777
|
+
const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
|
|
110778
|
+
lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
|
|
110779
|
+
}
|
|
110780
|
+
if (params.analysis.truncatedFiles.length > topFiles.length) {
|
|
110781
|
+
lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
|
|
110782
|
+
}
|
|
110783
|
+
lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
|
|
110784
|
+
return lines;
|
|
110785
|
+
}
|
|
110786
|
+
function buildBootstrapPromptWarning(params) {
|
|
110787
|
+
const signature = buildBootstrapTruncationSignature(params.analysis);
|
|
110788
|
+
let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
|
|
110789
|
+
if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
|
|
110790
|
+
seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
|
|
110791
|
+
}
|
|
110792
|
+
const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
|
|
110793
|
+
const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
|
|
110794
|
+
const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
|
|
110795
|
+
return {
|
|
110796
|
+
signature,
|
|
110797
|
+
warningShown,
|
|
110798
|
+
lines: warningShown ? formatBootstrapTruncationWarningLines({
|
|
110799
|
+
analysis: params.analysis,
|
|
110800
|
+
maxFiles: params.maxFiles
|
|
110801
|
+
}) : [],
|
|
110802
|
+
warningSignaturesSeen
|
|
110803
|
+
};
|
|
110804
|
+
}
|
|
110805
|
+
|
|
110806
|
+
// src/agents/bootstrap-types.ts
|
|
110807
|
+
var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
110808
|
+
var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
|
|
110809
|
+
var DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
110810
|
+
var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
110811
|
+
var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
|
|
110812
|
+
var DEFAULT_USER_FILENAME = "USER.md";
|
|
110813
|
+
var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
|
|
110814
|
+
var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
|
|
110815
|
+
|
|
110816
|
+
// src/agents/workspace.ts
|
|
110817
|
+
var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
|
|
110818
|
+
var DEFAULT_MEMORY_SUBDIR = "memory";
|
|
110819
|
+
var STABLE_BOOTSTRAP_FILENAMES = [
|
|
110820
|
+
DEFAULT_AGENTS_FILENAME,
|
|
110821
|
+
DEFAULT_SOUL_DEFAULT_FILENAME,
|
|
110822
|
+
DEFAULT_SOUL_FILENAME,
|
|
110823
|
+
DEFAULT_IDENTITY_FILENAME,
|
|
110824
|
+
DEFAULT_USER_FILENAME,
|
|
110825
|
+
DEFAULT_TOOLS_FILENAME,
|
|
110826
|
+
DEFAULT_BOOTSTRAP_FILENAME
|
|
110827
|
+
];
|
|
110828
|
+
var DYNAMIC_BOOTSTRAP_FILENAMES = [
|
|
110829
|
+
DEFAULT_MEMORY_FILENAME
|
|
110830
|
+
];
|
|
110831
|
+
var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
|
|
110832
|
+
var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
|
|
110833
|
+
var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
|
|
110834
|
+
function resolveAgentWorkspaceDir(agentDir) {
|
|
110835
|
+
return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
|
|
110836
|
+
}
|
|
110837
|
+
async function readOptionalFile(filePath) {
|
|
110838
|
+
try {
|
|
110839
|
+
const content = await readFile2(filePath, "utf8");
|
|
110840
|
+
return content;
|
|
110841
|
+
} catch (err) {
|
|
110842
|
+
if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
|
|
110843
|
+
return;
|
|
110844
|
+
}
|
|
110845
|
+
throw err;
|
|
110846
|
+
}
|
|
110847
|
+
}
|
|
110848
|
+
function isErrnoException(value) {
|
|
110849
|
+
return value instanceof Error && typeof value.code === "string";
|
|
110850
|
+
}
|
|
110851
|
+
async function loadNamedFile(workspaceDir, name, relativePath) {
|
|
110852
|
+
const filePath = path8.join(workspaceDir, relativePath ?? name);
|
|
110853
|
+
const content = await readOptionalFile(filePath);
|
|
110854
|
+
if (content === undefined) {
|
|
110855
|
+
return { name, path: filePath, missing: true };
|
|
110856
|
+
}
|
|
110857
|
+
return { name, path: filePath, content, missing: false };
|
|
110858
|
+
}
|
|
110859
|
+
async function loadStableBootstrapFiles(workspaceDir, options) {
|
|
110860
|
+
const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110861
|
+
const extras = options?.extraStableFiles ?? [];
|
|
110862
|
+
if (extras.length === 0) {
|
|
110863
|
+
return defaultFiles;
|
|
110864
|
+
}
|
|
110865
|
+
const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110866
|
+
return [...defaultFiles, ...extraFiles];
|
|
110867
|
+
}
|
|
110868
|
+
async function loadDynamicBootstrapFiles(workspaceDir, options) {
|
|
110869
|
+
const now2 = options?.now ?? new Date;
|
|
110870
|
+
const includeYesterday = options?.includeYesterday ?? true;
|
|
110871
|
+
const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
110872
|
+
const todayRelative = dailyMemoryRelativePath(now2);
|
|
110873
|
+
const todayName = DEFAULT_MEMORY_FILENAME;
|
|
110874
|
+
const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
|
|
110875
|
+
today.path = path8.join(workspaceDir, todayRelative);
|
|
110876
|
+
files.push(today);
|
|
110877
|
+
if (includeYesterday) {
|
|
110878
|
+
const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
|
|
110879
|
+
const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
|
|
110880
|
+
yesterday.path = path8.join(workspaceDir, yesterdayRelative);
|
|
110881
|
+
files.push(yesterday);
|
|
110882
|
+
}
|
|
110883
|
+
return files;
|
|
110884
|
+
}
|
|
110885
|
+
function pad22(n) {
|
|
110886
|
+
return n < 10 ? `0${n}` : String(n);
|
|
110887
|
+
}
|
|
110888
|
+
function dailyMemoryRelativePath(date) {
|
|
110889
|
+
const y = date.getFullYear();
|
|
110890
|
+
const m = pad22(date.getMonth() + 1);
|
|
110891
|
+
const d = pad22(date.getDate());
|
|
110892
|
+
return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
|
|
110893
|
+
}
|
|
110894
|
+
function addDays(date, days) {
|
|
110895
|
+
const out = new Date(date.getTime());
|
|
110896
|
+
out.setDate(out.getDate() + days);
|
|
110897
|
+
return out;
|
|
110898
|
+
}
|
|
110899
|
+
function truncateContent(name, content, maxChars) {
|
|
110900
|
+
if (content.length <= maxChars) {
|
|
110901
|
+
return content;
|
|
110902
|
+
}
|
|
110903
|
+
if (maxChars < 200) {
|
|
110904
|
+
return content.slice(0, Math.max(0, maxChars));
|
|
110905
|
+
}
|
|
110906
|
+
const headRoomInitial = Math.floor(maxChars * 0.7);
|
|
110907
|
+
const sampleMarker = `
|
|
110908
|
+
\u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
|
|
110909
|
+
`;
|
|
110910
|
+
const markerReserve = Math.max(64, sampleMarker.length + 8);
|
|
110911
|
+
const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
|
|
110912
|
+
const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
|
|
110913
|
+
const head = content.slice(0, Math.max(0, headRoom));
|
|
110914
|
+
const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
|
|
110915
|
+
const result = `${head}
|
|
110916
|
+
\u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
|
|
110917
|
+
${tail}`;
|
|
110918
|
+
return result.length <= maxChars ? result : result.slice(0, maxChars);
|
|
110919
|
+
}
|
|
110920
|
+
function projectBootstrapFiles(params) {
|
|
110921
|
+
const {
|
|
110922
|
+
files,
|
|
110923
|
+
heading,
|
|
110924
|
+
budget,
|
|
110925
|
+
seenSignatures,
|
|
110926
|
+
warningMode = "error",
|
|
110927
|
+
warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
|
|
110928
|
+
} = params;
|
|
110929
|
+
const perFileCap = budget.bootstrapMaxChars;
|
|
110930
|
+
let remainingTotal = budget.bootstrapTotalMaxChars;
|
|
110931
|
+
const injectedFiles = [];
|
|
110932
|
+
const parts = [];
|
|
110933
|
+
for (const file of files) {
|
|
110934
|
+
if (file.missing || typeof file.content !== "string") {
|
|
110935
|
+
continue;
|
|
110936
|
+
}
|
|
110937
|
+
const raw = file.content.trimEnd();
|
|
110938
|
+
if (raw.length === 0) {
|
|
110939
|
+
continue;
|
|
110940
|
+
}
|
|
110941
|
+
const perFileAllowed = Math.min(perFileCap, remainingTotal);
|
|
110942
|
+
if (perFileAllowed <= 0) {
|
|
110943
|
+
break;
|
|
110944
|
+
}
|
|
110945
|
+
const injected = truncateContent(file.name, raw, perFileAllowed);
|
|
110946
|
+
const relativePath = file.path;
|
|
110947
|
+
injectedFiles.push({ path: relativePath, content: injected });
|
|
110948
|
+
parts.push(`## ${relativePath}`);
|
|
110949
|
+
parts.push(injected);
|
|
110950
|
+
remainingTotal -= injected.length;
|
|
110951
|
+
if (remainingTotal <= 0) {
|
|
110952
|
+
break;
|
|
110953
|
+
}
|
|
110954
|
+
}
|
|
110955
|
+
const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
|
|
110956
|
+
|
|
110957
|
+
`) : "";
|
|
110958
|
+
const stats = buildBootstrapInjectionStats({
|
|
110959
|
+
bootstrapFiles: files,
|
|
110960
|
+
injectedFiles
|
|
110961
|
+
});
|
|
110962
|
+
const analysis = analyzeBootstrapBudget({
|
|
110963
|
+
files: stats,
|
|
110964
|
+
bootstrapMaxChars: budget.bootstrapMaxChars,
|
|
110965
|
+
bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
|
|
110966
|
+
nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
|
|
110967
|
+
});
|
|
110968
|
+
const warning = buildBootstrapPromptWarning({
|
|
110969
|
+
analysis,
|
|
110970
|
+
mode: warningMode,
|
|
110971
|
+
seenSignatures,
|
|
110972
|
+
maxFiles: warningMaxFiles
|
|
110973
|
+
});
|
|
110974
|
+
if (warningMode === "error" && analysis.hasTruncation) {
|
|
110975
|
+
const errorLines = [
|
|
110976
|
+
"Bootstrap budget exceeded. The following files exceed limits:",
|
|
110977
|
+
""
|
|
110978
|
+
];
|
|
110979
|
+
for (const file of analysis.truncatedFiles) {
|
|
110980
|
+
const excessChars = file.rawChars - file.injectedChars;
|
|
110981
|
+
const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
|
|
110982
|
+
errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
|
|
110983
|
+
errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
|
|
110984
|
+
}
|
|
110985
|
+
throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
|
|
110986
|
+
`));
|
|
110987
|
+
}
|
|
110988
|
+
return { files, injectedFiles, concatenated, analysis, warning };
|
|
110989
|
+
}
|
|
110990
|
+
async function buildStableBootstrapPrompt(params) {
|
|
110991
|
+
const files = await loadStableBootstrapFiles(params.workspaceDir, {
|
|
110992
|
+
extraStableFiles: params.extraStableFiles
|
|
110993
|
+
});
|
|
110994
|
+
const budget = {
|
|
110995
|
+
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
110996
|
+
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
|
|
110997
|
+
nearLimitRatio: params.budget?.nearLimitRatio
|
|
110998
|
+
};
|
|
110999
|
+
return projectBootstrapFiles({
|
|
111000
|
+
files,
|
|
111001
|
+
heading: "Project Context (stable workspace files)",
|
|
111002
|
+
budget,
|
|
111003
|
+
seenSignatures: params.seenSignatures,
|
|
111004
|
+
warningMode: params.budget?.warningMode,
|
|
111005
|
+
warningMaxFiles: params.budget?.warningMaxFiles
|
|
111006
|
+
});
|
|
111007
|
+
}
|
|
111008
|
+
async function buildDynamicBootstrapPrompt(params) {
|
|
111009
|
+
const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
|
|
111010
|
+
now: params.now,
|
|
111011
|
+
includeYesterday: params.includeYesterday
|
|
111012
|
+
});
|
|
111013
|
+
const budget = {
|
|
111014
|
+
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
111015
|
+
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
|
|
111016
|
+
nearLimitRatio: params.budget?.nearLimitRatio
|
|
111017
|
+
};
|
|
111018
|
+
return projectBootstrapFiles({
|
|
111019
|
+
files,
|
|
111020
|
+
heading: "Project Context (dynamic workspace files)",
|
|
111021
|
+
budget,
|
|
111022
|
+
seenSignatures: params.seenSignatures,
|
|
111023
|
+
warningMode: params.budget?.warningMode,
|
|
111024
|
+
warningMaxFiles: params.budget?.warningMaxFiles
|
|
111025
|
+
});
|
|
111026
|
+
}
|
|
111027
|
+
|
|
111028
|
+
// src/cli/debug.ts
|
|
111029
|
+
init_merge();
|
|
111030
|
+
init_hindsight2();
|
|
111031
|
+
function formatBytes(bytes) {
|
|
111032
|
+
return `${bytes.toLocaleString()} bytes`;
|
|
111033
|
+
}
|
|
111034
|
+
function estimateTokens(bytes) {
|
|
111035
|
+
return Math.round(bytes / 3.7);
|
|
111036
|
+
}
|
|
111037
|
+
function readMcpServerNames(agentDir) {
|
|
111038
|
+
const mcpPath = join88(agentDir, ".mcp.json");
|
|
111039
|
+
if (!existsSync82(mcpPath))
|
|
111040
|
+
return [];
|
|
111041
|
+
try {
|
|
111042
|
+
const parsed = JSON.parse(readFileSync74(mcpPath, "utf-8"));
|
|
111043
|
+
return Object.keys(parsed.mcpServers ?? {});
|
|
111044
|
+
} catch {
|
|
111045
|
+
return null;
|
|
111046
|
+
}
|
|
111047
|
+
}
|
|
111048
|
+
function sha2562(content) {
|
|
111049
|
+
return createHash15("sha256").update(content).digest("hex").slice(0, 16);
|
|
111050
|
+
}
|
|
111051
|
+
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
111052
|
+
const projectsDir = join88(claudeConfigDir, "projects");
|
|
111053
|
+
if (!existsSync82(projectsDir))
|
|
111054
|
+
return;
|
|
111055
|
+
try {
|
|
111056
|
+
const entries = readdirSync30(projectsDir, { withFileTypes: true });
|
|
111057
|
+
let latest;
|
|
111058
|
+
for (const entry of entries) {
|
|
111059
|
+
if (!entry.isDirectory())
|
|
111060
|
+
continue;
|
|
111061
|
+
const projectPath = join88(projectsDir, entry.name);
|
|
111062
|
+
const transcriptPath = join88(projectPath, "transcript.jsonl");
|
|
111063
|
+
if (!existsSync82(transcriptPath))
|
|
111064
|
+
continue;
|
|
111065
|
+
const stat2 = statSync48(transcriptPath);
|
|
111066
|
+
if (!latest || stat2.mtimeMs > latest.mtime) {
|
|
111067
|
+
latest = { path: transcriptPath, mtime: stat2.mtimeMs };
|
|
111068
|
+
}
|
|
111069
|
+
}
|
|
111070
|
+
return latest?.path;
|
|
111071
|
+
} catch {
|
|
111072
|
+
return;
|
|
111073
|
+
}
|
|
111074
|
+
}
|
|
111075
|
+
function extractLatestUserMessage(transcriptPath) {
|
|
111076
|
+
try {
|
|
111077
|
+
const content = readFileSync74(transcriptPath, "utf-8");
|
|
111078
|
+
const lines = content.trim().split(`
|
|
111079
|
+
`).filter(Boolean);
|
|
111080
|
+
for (let i2 = lines.length - 1;i2 >= 0; i2--) {
|
|
111081
|
+
const line = lines[i2];
|
|
111082
|
+
try {
|
|
111083
|
+
const event = JSON.parse(line);
|
|
111084
|
+
if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
|
|
111085
|
+
const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
|
|
111086
|
+
return { text: event.content, timestamp };
|
|
111087
|
+
}
|
|
111088
|
+
} catch {
|
|
111089
|
+
continue;
|
|
111090
|
+
}
|
|
111091
|
+
}
|
|
111092
|
+
} catch {
|
|
111093
|
+
return;
|
|
111094
|
+
}
|
|
111095
|
+
}
|
|
111096
|
+
function buildProgressUpdateGuidance() {
|
|
111097
|
+
return `## Progress updates (human-style check-ins)
|
|
111098
|
+
|
|
111099
|
+
You're talking to a human colleague on Telegram. Alongside the emoji status
|
|
111100
|
+
ladder, send a short \`progress_update\` at inflection points, the moments a
|
|
111101
|
+
senior colleague would ping the person who asked them to do something:
|
|
111102
|
+
|
|
111103
|
+
- **Plan formed:** "Got it. Going to do X first, then Y, then Z."
|
|
111104
|
+
- **Pivot or blocker:** "First approach didn't work because <reason>. Trying
|
|
111105
|
+
<alternative> instead."
|
|
111106
|
+
- **Chunk finished:** "Done with X. Starting Y now."
|
|
111107
|
+
|
|
111108
|
+
Keep them short (one or two sentences). Don't narrate every step, the pinned
|
|
111109
|
+
progress card shows that for free. Don't send an update on a trivial one-shot
|
|
111110
|
+
task. Send them when a colleague would genuinely want to know what's happening.
|
|
111111
|
+
|
|
111112
|
+
Final answers still go through \`reply\` as usual,
|
|
111113
|
+
\`progress_update\` is only for mid-turn check-ins.`;
|
|
111114
|
+
}
|
|
111115
|
+
function registerDebugCommand(program3) {
|
|
111116
|
+
const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
|
|
111117
|
+
cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
|
|
111118
|
+
const config = getConfig(program3);
|
|
111119
|
+
const agentConfig = config.agents[agentName];
|
|
111120
|
+
if (!agentConfig) {
|
|
111121
|
+
console.error(`Agent '${agentName}' not found in switchroom.yaml`);
|
|
111122
|
+
process.exit(1);
|
|
111123
|
+
}
|
|
111124
|
+
const agentsDir = resolveAgentsDir(config);
|
|
111125
|
+
const agentDir = resolve43(agentsDir, agentName);
|
|
111126
|
+
if (!existsSync82(agentDir)) {
|
|
111127
|
+
console.error(`Agent directory not found: ${agentDir}`);
|
|
111128
|
+
process.exit(1);
|
|
111129
|
+
}
|
|
111130
|
+
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
111131
|
+
const claudeConfigDir = join88(agentDir, ".claude");
|
|
111132
|
+
const claudeMdPath2 = join88(agentDir, "CLAUDE.md");
|
|
111133
|
+
const soulMdPath = join88(agentDir, "SOUL.md");
|
|
111134
|
+
const workspaceSoulMdPath = join88(workspaceDir, "SOUL.md");
|
|
111135
|
+
const handoffPath = join88(agentDir, ".handoff.md");
|
|
111136
|
+
const lastN = parseInt(opts.last, 10);
|
|
111137
|
+
if (isNaN(lastN) || lastN < 1) {
|
|
111138
|
+
console.error("--last must be a positive integer");
|
|
111139
|
+
process.exit(1);
|
|
111140
|
+
}
|
|
111141
|
+
if (lastN > 1) {
|
|
111142
|
+
console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
|
|
111143
|
+
process.exit(1);
|
|
111144
|
+
}
|
|
111145
|
+
console.log(`=== Debug Turn Dump: ${agentName} ===
|
|
111146
|
+
`);
|
|
111147
|
+
console.log(`=== Append System Prompt (stable) ===
|
|
111148
|
+
`);
|
|
111149
|
+
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
111150
|
+
const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
|
|
111151
|
+
const stableResult = await buildStableBootstrapPrompt({
|
|
111152
|
+
workspaceDir,
|
|
111153
|
+
budget: { warningMode: "off" }
|
|
111154
|
+
});
|
|
111155
|
+
if (useHotReloadStable) {
|
|
111156
|
+
console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
|
|
111157
|
+
console.log();
|
|
111158
|
+
} else if (stableResult.concatenated.trim().length > 0) {
|
|
111159
|
+
console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
|
|
111160
|
+
console.log(stableResult.concatenated);
|
|
111161
|
+
console.log();
|
|
111162
|
+
} else {
|
|
111163
|
+
console.log("-- Workspace Stable Render (0 bytes, not set up) --");
|
|
111164
|
+
console.log();
|
|
111165
|
+
}
|
|
111166
|
+
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
|
|
111167
|
+
const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
|
|
111168
|
+
if (progressGuidance.length > 0) {
|
|
111169
|
+
console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
|
|
111170
|
+
console.log(progressGuidance);
|
|
111171
|
+
console.log();
|
|
111172
|
+
}
|
|
111173
|
+
const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
|
|
111174
|
+
if (baseSystemPromptAppend.trim().length > 0) {
|
|
111175
|
+
console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
|
|
111176
|
+
console.log(baseSystemPromptAppend);
|
|
111177
|
+
console.log();
|
|
111178
|
+
}
|
|
111179
|
+
console.log(`=== Append System Prompt (per-session) ===
|
|
111180
|
+
`);
|
|
111181
|
+
const handoffContent = existsSync82(handoffPath) ? readFileSync74(handoffPath, "utf-8") : "";
|
|
111182
|
+
if (handoffContent.trim().length > 0) {
|
|
111183
|
+
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
111184
|
+
console.log(handoffContent);
|
|
111185
|
+
console.log();
|
|
111186
|
+
} else {
|
|
111187
|
+
console.log("-- Handoff Briefing (0 bytes, no prior session) --");
|
|
111188
|
+
console.log();
|
|
111189
|
+
}
|
|
111190
|
+
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
111191
|
+
`);
|
|
111192
|
+
const claudeMdContent = existsSync82(claudeMdPath2) ? readFileSync74(claudeMdPath2, "utf-8") : "";
|
|
111193
|
+
if (claudeMdContent.trim().length > 0) {
|
|
111194
|
+
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
111195
|
+
console.log(claudeMdContent);
|
|
111196
|
+
console.log();
|
|
111197
|
+
} else {
|
|
111198
|
+
console.log("(0 bytes, not present)");
|
|
111199
|
+
console.log();
|
|
111200
|
+
}
|
|
111201
|
+
console.log(`=== Persona (SOUL.md) ===
|
|
111202
|
+
`);
|
|
111203
|
+
const soulMdContent = existsSync82(soulMdPath) ? readFileSync74(soulMdPath, "utf-8") : existsSync82(workspaceSoulMdPath) ? readFileSync74(workspaceSoulMdPath, "utf-8") : "";
|
|
111204
|
+
if (soulMdContent.trim().length > 0) {
|
|
111205
|
+
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
111206
|
+
console.log(soulMdContent);
|
|
111207
|
+
console.log();
|
|
111208
|
+
} else {
|
|
111209
|
+
console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
|
|
111210
|
+
console.log();
|
|
111211
|
+
}
|
|
111212
|
+
console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
|
|
111213
|
+
`);
|
|
111214
|
+
if (useHotReloadStable) {
|
|
111215
|
+
if (stableResult.concatenated.trim().length > 0) {
|
|
111216
|
+
console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
|
|
111217
|
+
console.log(stableResult.concatenated);
|
|
111218
|
+
console.log();
|
|
111219
|
+
} else {
|
|
111220
|
+
console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
|
|
111221
|
+
console.log();
|
|
111222
|
+
}
|
|
111223
|
+
}
|
|
111224
|
+
const dynamicResult = await buildDynamicBootstrapPrompt({
|
|
111225
|
+
workspaceDir,
|
|
111226
|
+
budget: { warningMode: "off" }
|
|
111227
|
+
});
|
|
111228
|
+
if (dynamicResult.concatenated.trim().length > 0) {
|
|
111229
|
+
console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
|
|
111230
|
+
console.log(dynamicResult.concatenated);
|
|
111231
|
+
console.log();
|
|
111232
|
+
} else {
|
|
111233
|
+
console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
|
|
111234
|
+
console.log();
|
|
111235
|
+
}
|
|
111236
|
+
const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
|
|
111237
|
+
if (hindsightEnabled) {
|
|
111238
|
+
console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
|
|
111239
|
+
console.log();
|
|
111240
|
+
} else {
|
|
111241
|
+
console.log("-- Hindsight Recall: disabled --");
|
|
111242
|
+
console.log();
|
|
111243
|
+
}
|
|
111244
|
+
console.log(`=== User Message (latest turn) ===
|
|
111245
|
+
`);
|
|
111246
|
+
const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
|
|
111247
|
+
const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
|
|
111248
|
+
if (userMessage) {
|
|
111249
|
+
console.log(`(Turn timestamp: ${userMessage.timestamp})`);
|
|
111250
|
+
console.log(userMessage.text);
|
|
111251
|
+
console.log();
|
|
111252
|
+
} else {
|
|
111253
|
+
console.log("(unavailable: no transcript found or transcript empty)");
|
|
111254
|
+
console.log();
|
|
111255
|
+
}
|
|
111256
|
+
console.log(`=== Totals ===
|
|
111257
|
+
`);
|
|
111258
|
+
const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
|
|
111259
|
+
const perSessionBytes = handoffContent.length;
|
|
111260
|
+
const claudeMdBytes = claudeMdContent.length;
|
|
111261
|
+
const soulMdBytes = soulMdContent.length;
|
|
111262
|
+
const perTurnBytes = dynamicResult.concatenated.length;
|
|
111263
|
+
const userBytes = userMessage?.text.length ?? 0;
|
|
111264
|
+
const fleetDir = join88(agentsDir, "..", "fleet");
|
|
111265
|
+
const fleetInvPath = join88(fleetDir, "switchroom-invariants.md");
|
|
111266
|
+
const fleetClaudePath = join88(fleetDir, "CLAUDE.md");
|
|
111267
|
+
const fleetInvBytes = existsSync82(fleetInvPath) ? readFileSync74(fleetInvPath, "utf-8").length : 0;
|
|
111268
|
+
const fleetClaudeBytes = existsSync82(fleetClaudePath) ? readFileSync74(fleetClaudePath, "utf-8").length : 0;
|
|
111269
|
+
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
111270
|
+
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
111271
|
+
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
111272
|
+
console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
|
|
111273
|
+
console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
|
|
111274
|
+
console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
|
|
111275
|
+
console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
|
|
111276
|
+
console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
|
|
111277
|
+
console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
|
|
111278
|
+
const mcpServers = readMcpServerNames(agentDir);
|
|
111279
|
+
const mcpCount = mcpServers?.length ?? null;
|
|
111280
|
+
const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
|
|
111281
|
+
const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
|
|
111282
|
+
const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
|
|
111283
|
+
console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
|
|
111284
|
+
if (mcpServers && mcpServers.length > 0) {
|
|
111285
|
+
console.log(` [${mcpServers.join(", ")}]`);
|
|
111286
|
+
}
|
|
111287
|
+
const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
|
|
111288
|
+
console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
|
|
111289
|
+
const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
|
|
111290
|
+
const stableHash = sha2562(stableCacheInput);
|
|
111291
|
+
console.log(`Cache stable hash: sha256:${stableHash}`);
|
|
111292
|
+
console.log();
|
|
111293
|
+
}));
|
|
111294
|
+
}
|
|
111295
|
+
|
|
111296
|
+
// src/memory/directive-residue.ts
|
|
111297
|
+
var RESIDUE_CATEGORIES = new Set([
|
|
111298
|
+
"rules-block",
|
|
111299
|
+
"reflect-directive"
|
|
111300
|
+
]);
|
|
111301
|
+
function renderInjectedLine(index, directive) {
|
|
111302
|
+
const priority = directive.priority ?? 0;
|
|
111303
|
+
const name = (directive.name ?? "").trim() || "(unnamed)";
|
|
111304
|
+
const content = (directive.content ?? "").trim();
|
|
111305
|
+
return `${index}. [P${priority}] ${name}: ${content}`;
|
|
111306
|
+
}
|
|
111307
|
+
function measureDirectiveResidue(agent, rows, directivesById) {
|
|
111308
|
+
const residueRows = rows.filter((r) => r.isActive && RESIDUE_CATEGORIES.has(r.category));
|
|
111309
|
+
const lines = residueRows.map((row, i2) => {
|
|
111310
|
+
const directive = directivesById.get(row.id);
|
|
111311
|
+
if (!directive) {
|
|
111312
|
+
throw new Error(`measureDirectiveResidue: row '${row.name}' (id ${row.id}) has no ` + "matching directive in directivesById \u2014 pass the SAME list the " + "rows were built from.");
|
|
111313
|
+
}
|
|
111314
|
+
return renderInjectedLine(i2 + 1, directive);
|
|
111315
|
+
});
|
|
111316
|
+
const residueBytes = lines.reduce((sum, line) => sum + Buffer.byteLength(line, "utf8") + 1, 0);
|
|
111317
|
+
return {
|
|
111318
|
+
agent,
|
|
111319
|
+
residueDirectiveCount: residueRows.length,
|
|
111320
|
+
totalDirectiveCount: rows.length,
|
|
111321
|
+
residueBytes,
|
|
111322
|
+
residueTokensEstimate: estimateTokens(residueBytes)
|
|
111323
|
+
};
|
|
111324
|
+
}
|
|
111325
|
+
|
|
111326
|
+
// src/memory/directive-flip.ts
|
|
111327
|
+
init_rules_block();
|
|
111328
|
+
function evaluateFlipReadiness(measurement, opts) {
|
|
111329
|
+
const reasons = [];
|
|
111330
|
+
if (!opts.rulesBlockEnabled) {
|
|
111331
|
+
reasons.push("memory.rules_block (M1) is not enabled \u2014 enable the rules-block " + "toolchain and migrate the directives into the block BEFORE flipping " + "inject_directives off (two-flag ordering; flipping first strips every " + "guardrail).");
|
|
111332
|
+
}
|
|
111333
|
+
if (measurement.residueBytes > RULES_BLOCK_BUDGET_BYTES) {
|
|
111334
|
+
reasons.push(`directive residue is ${measurement.residueBytes}B, over the ` + `${RULES_BLOCK_BUDGET_BYTES}B rules-block budget (` + `${measurement.residueDirectiveCount} active residue directive(s)). ` + "Triage the directive set down \u2014 retire stale rules or shorten bodies \u2014 " + "until the residue fits, then re-check. The budget, not MAX_DIRECTIVES, " + "is the binding gate.");
|
|
111335
|
+
}
|
|
111336
|
+
return {
|
|
111337
|
+
agent: measurement.agent,
|
|
111338
|
+
ready: reasons.length === 0,
|
|
111339
|
+
reasons,
|
|
111340
|
+
residueBytes: measurement.residueBytes,
|
|
111341
|
+
budgetBytes: RULES_BLOCK_BUDGET_BYTES,
|
|
111342
|
+
residueDirectiveCount: measurement.residueDirectiveCount
|
|
111343
|
+
};
|
|
111344
|
+
}
|
|
111345
|
+
async function measureLiveResidue(admin, agent) {
|
|
111346
|
+
const directives = await admin.list();
|
|
111347
|
+
const rows = buildDirectiveTriageRows(directives);
|
|
111348
|
+
const byId = new Map(directives.map((d) => [d.id, d]));
|
|
111349
|
+
return measureDirectiveResidue(agent, rows, byId);
|
|
111350
|
+
}
|
|
111351
|
+
function flipConfigStanza(agent) {
|
|
111352
|
+
return [
|
|
111353
|
+
`# ${agent} \u2014 Memory v2 M3 Surface-A flip (suppress <active_directives>)`,
|
|
111354
|
+
"memory:",
|
|
111355
|
+
" rules_block: true # M1 must already be on (checked)",
|
|
111356
|
+
" inject_directives: false # M3: stop re-injecting migrated directives"
|
|
111357
|
+
].join(`
|
|
111358
|
+
`);
|
|
111359
|
+
}
|
|
111360
|
+
|
|
111361
|
+
// src/cli/memory-flip.ts
|
|
111362
|
+
function resolveFlipAdmin(program3, agent) {
|
|
111363
|
+
const config = getConfig(program3);
|
|
111364
|
+
if (!config.agents[agent]) {
|
|
111365
|
+
console.error(source_default.red(`Agent "${agent}" is not defined in switchroom.yaml`));
|
|
111366
|
+
process.exit(1);
|
|
111367
|
+
}
|
|
111368
|
+
if (!isHindsightEnabled(config)) {
|
|
111369
|
+
console.error(source_default.red("Hindsight memory is not enabled in this switchroom.yaml."));
|
|
111370
|
+
process.exit(1);
|
|
111371
|
+
}
|
|
111372
|
+
const agentConfig = config.agents[agent];
|
|
111373
|
+
const mcpBaseUrl = config.memory?.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
111374
|
+
const apiBaseUrl = mcpBaseUrl.replace(/\/mcp\/?$/, "").replace(/\/+$/, "");
|
|
111375
|
+
const bankId = agentConfig?.memory?.collection ?? agent;
|
|
111376
|
+
return new DirectiveAdmin({ apiBaseUrl, bankId });
|
|
111377
|
+
}
|
|
111378
|
+
function readRulesBlockEnabled(program3, agent) {
|
|
111379
|
+
const config = getConfig(program3);
|
|
111380
|
+
return config.agents[agent]?.memory?.rules_block === true;
|
|
111381
|
+
}
|
|
111382
|
+
function buildFlipPreflightJson(readiness, rulesBlockEnabled) {
|
|
111383
|
+
return {
|
|
111384
|
+
ok: true,
|
|
111385
|
+
agent: readiness.agent,
|
|
111386
|
+
ready: readiness.ready,
|
|
111387
|
+
rules_block: rulesBlockEnabled,
|
|
111388
|
+
residue_bytes: readiness.residueBytes,
|
|
111389
|
+
budget_bytes: readiness.budgetBytes,
|
|
111390
|
+
fits_budget: readiness.residueBytes <= readiness.budgetBytes,
|
|
111391
|
+
residue_directive_count: readiness.residueDirectiveCount,
|
|
111392
|
+
reasons: readiness.reasons
|
|
111393
|
+
};
|
|
111394
|
+
}
|
|
111395
|
+
function renderFlipPreflight(readiness, rulesBlockEnabled) {
|
|
111396
|
+
const fits = readiness.residueBytes <= readiness.budgetBytes;
|
|
111397
|
+
const lines = [];
|
|
111398
|
+
const verdict = readiness.ready ? source_default.green("READY") : source_default.red("NOT-READY");
|
|
111399
|
+
lines.push(`${verdict} \u2014 ${readiness.agent} M3 flip preflight`);
|
|
111400
|
+
lines.push("");
|
|
111401
|
+
lines.push(` residue: ${readiness.residueBytes}B / ${readiness.budgetBytes}B budget ` + `(${readiness.residueDirectiveCount} active residue directive(s)) ` + (fits ? source_default.green("fits") : source_default.red("OVER budget")));
|
|
111402
|
+
lines.push(` rules_block: ${rulesBlockEnabled ? source_default.green("on") : source_default.red("off")}`);
|
|
111403
|
+
if (readiness.ready) {
|
|
111404
|
+
lines.push("");
|
|
111405
|
+
lines.push(" Apply this per-agent stanza to switchroom.yaml, then restart:");
|
|
111406
|
+
lines.push("");
|
|
111407
|
+
for (const l of flipConfigStanza(readiness.agent).split(`
|
|
111408
|
+
`)) {
|
|
111409
|
+
lines.push(` ${l}`);
|
|
111410
|
+
}
|
|
111411
|
+
} else {
|
|
111412
|
+
lines.push("");
|
|
111413
|
+
lines.push(source_default.red(" Blocked:"));
|
|
111414
|
+
for (const reason of readiness.reasons) {
|
|
111415
|
+
lines.push(` - ${reason}`);
|
|
111416
|
+
}
|
|
111417
|
+
}
|
|
111418
|
+
return lines.join(`
|
|
111419
|
+
`);
|
|
111420
|
+
}
|
|
111421
|
+
function registerMemoryFlipCommand(memory, program3, deps = {}) {
|
|
111422
|
+
const makeAdmin = deps.makeAdmin ?? resolveFlipAdmin;
|
|
111423
|
+
memory.command("flip-preflight <agent>").description("Deterministic M3 (Surface-A) flip-readiness gate for <agent>: measure " + "the live post-triage directive residue, check it fits the 6144B " + "rules-block budget AND that memory.rules_block is already on, and " + "print READY + the exact switchroom.yaml stanza, or NOT-READY + why. " + "Never mutates config \u2014 the flip itself is an operator-gated edit.").option("--json", "Machine-readable output").action(withConfigError(async (agent, opts) => {
|
|
111424
|
+
const admin = makeAdmin(program3, agent);
|
|
111425
|
+
const rulesBlockEnabled = readRulesBlockEnabled(program3, agent);
|
|
111426
|
+
let ready;
|
|
111427
|
+
try {
|
|
111428
|
+
const measurement = await measureLiveResidue(admin, agent);
|
|
111429
|
+
const readiness = evaluateFlipReadiness(measurement, { rulesBlockEnabled });
|
|
111430
|
+
ready = readiness.ready;
|
|
111431
|
+
if (opts.json) {
|
|
111432
|
+
console.log(JSON.stringify(buildFlipPreflightJson(readiness, rulesBlockEnabled)));
|
|
111433
|
+
} else {
|
|
111434
|
+
console.log(renderFlipPreflight(readiness, rulesBlockEnabled));
|
|
111435
|
+
}
|
|
111436
|
+
} catch (e) {
|
|
111437
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
111438
|
+
if (opts.json) {
|
|
111439
|
+
console.log(JSON.stringify({ ok: false, error: msg }));
|
|
111440
|
+
} else {
|
|
111441
|
+
console.error(source_default.red(`\u2717 flip preflight for ${agent} failed: ${msg}`));
|
|
111442
|
+
}
|
|
111443
|
+
process.exit(1);
|
|
111444
|
+
return;
|
|
111445
|
+
}
|
|
111446
|
+
if (!ready)
|
|
111447
|
+
process.exit(2);
|
|
111448
|
+
}));
|
|
111449
|
+
}
|
|
111450
|
+
|
|
110449
111451
|
// src/cli/memory.ts
|
|
110450
111452
|
function persistMemoryConfigUrl(configPath, provider, url) {
|
|
110451
|
-
if (!
|
|
111453
|
+
if (!existsSync83(configPath))
|
|
110452
111454
|
return false;
|
|
110453
|
-
const raw =
|
|
111455
|
+
const raw = readFileSync75(configPath, "utf-8");
|
|
110454
111456
|
const doc = import_yaml16.default.parseDocument(raw);
|
|
110455
111457
|
if (!doc.has("memory")) {
|
|
110456
111458
|
doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
|
|
@@ -110468,7 +111470,7 @@ function persistMemoryConfigUrl(configPath, provider, url) {
|
|
|
110468
111470
|
}
|
|
110469
111471
|
let mode = 420;
|
|
110470
111472
|
try {
|
|
110471
|
-
mode =
|
|
111473
|
+
mode = statSync49(configPath).mode & 511;
|
|
110472
111474
|
} catch {}
|
|
110473
111475
|
writeConfigFileSync(configPath, doc.toString(), mode);
|
|
110474
111476
|
return true;
|
|
@@ -110517,12 +111519,12 @@ async function resolveLiteLLMForHindsight(config) {
|
|
|
110517
111519
|
return resolveHindsightLiteLlm(config);
|
|
110518
111520
|
}
|
|
110519
111521
|
function readRecallLog(agentDir, limit) {
|
|
110520
|
-
const
|
|
110521
|
-
if (!
|
|
111522
|
+
const path9 = join89(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
111523
|
+
if (!existsSync83(path9))
|
|
110522
111524
|
return [];
|
|
110523
111525
|
let raw;
|
|
110524
111526
|
try {
|
|
110525
|
-
raw =
|
|
111527
|
+
raw = readFileSync75(path9, "utf-8");
|
|
110526
111528
|
} catch {
|
|
110527
111529
|
return [];
|
|
110528
111530
|
}
|
|
@@ -111286,9 +112288,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
|
|
|
111286
112288
|
captured += chunk2.toString();
|
|
111287
112289
|
process.stdout.write(chunk2);
|
|
111288
112290
|
});
|
|
111289
|
-
const childExit = await new Promise((
|
|
111290
|
-
child.on("error", () =>
|
|
111291
|
-
child.on("close", (c) =>
|
|
112291
|
+
const childExit = await new Promise((resolve44) => {
|
|
112292
|
+
child.on("error", () => resolve44(127));
|
|
112293
|
+
child.on("close", (c) => resolve44(c ?? 1));
|
|
111292
112294
|
});
|
|
111293
112295
|
const outcome = classifyRepairOutcome({
|
|
111294
112296
|
childExit,
|
|
@@ -111313,7 +112315,7 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
|
|
|
111313
112315
|
process.exit(1);
|
|
111314
112316
|
})() : Object.keys(config.agents);
|
|
111315
112317
|
for (const name of targets) {
|
|
111316
|
-
const agentDir =
|
|
112318
|
+
const agentDir = join89(agentsDir, name);
|
|
111317
112319
|
const entries = readRecallLog(agentDir, limit);
|
|
111318
112320
|
if (opts.json) {
|
|
111319
112321
|
for (const e of entries) {
|
|
@@ -111506,11 +112508,12 @@ Observation-scope cleanup \u2014 DRY RUN (no writes)
|
|
|
111506
112508
|
}));
|
|
111507
112509
|
registerMemoryRuleCommand(memory, program3);
|
|
111508
112510
|
registerMemoryDirectiveCommand(memory, program3);
|
|
112511
|
+
registerMemoryFlipCommand(memory, program3);
|
|
111509
112512
|
}
|
|
111510
112513
|
|
|
111511
112514
|
// src/cli/web.ts
|
|
111512
112515
|
init_source();
|
|
111513
|
-
import { join as
|
|
112516
|
+
import { join as join101 } from "node:path";
|
|
111514
112517
|
import { homedir as homedir50 } from "node:os";
|
|
111515
112518
|
|
|
111516
112519
|
// src/web/server.ts
|
|
@@ -111518,8 +112521,8 @@ init_merge();
|
|
|
111518
112521
|
init_loader();
|
|
111519
112522
|
init_client();
|
|
111520
112523
|
import {
|
|
111521
|
-
readFileSync as
|
|
111522
|
-
existsSync as
|
|
112524
|
+
readFileSync as readFileSync83,
|
|
112525
|
+
existsSync as existsSync91,
|
|
111523
112526
|
realpathSync as realpathSync7,
|
|
111524
112527
|
mkdirSync as mkdirSync50,
|
|
111525
112528
|
openSync as openSync17,
|
|
@@ -111527,7 +112530,7 @@ import {
|
|
|
111527
112530
|
writeSync as writeSync8,
|
|
111528
112531
|
constants as fsConstants6
|
|
111529
112532
|
} from "node:fs";
|
|
111530
|
-
import { resolve as
|
|
112533
|
+
import { resolve as resolve48, extname, join as join100, relative as relative5, dirname as dirname36 } from "node:path";
|
|
111531
112534
|
import { homedir as homedir49 } from "node:os";
|
|
111532
112535
|
import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes15 } from "node:crypto";
|
|
111533
112536
|
|
|
@@ -111536,8 +112539,8 @@ init_lifecycle();
|
|
|
111536
112539
|
init_manager();
|
|
111537
112540
|
init_hindsight2();
|
|
111538
112541
|
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
111539
|
-
import { existsSync as
|
|
111540
|
-
import { resolve as
|
|
112542
|
+
import { existsSync as existsSync87, readFileSync as readFileSync78, statSync as statSync52 } from "node:fs";
|
|
112543
|
+
import { resolve as resolve45, join as join96 } from "node:path";
|
|
111541
112544
|
|
|
111542
112545
|
// src/web/memory-remediation.ts
|
|
111543
112546
|
init_hindsight2();
|
|
@@ -111605,15 +112608,15 @@ async function buildUserProfile(mcpUrl, bank, opts) {
|
|
|
111605
112608
|
init_hindsight();
|
|
111606
112609
|
|
|
111607
112610
|
// src/web/blocked-approvals-read.ts
|
|
111608
|
-
import { readFileSync as
|
|
111609
|
-
import { resolve as
|
|
112611
|
+
import { readFileSync as readFileSync76, readdirSync as readdirSync31 } from "node:fs";
|
|
112612
|
+
import { resolve as resolve44, join as join90 } from "node:path";
|
|
111610
112613
|
import { homedir as homedir42 } from "node:os";
|
|
111611
112614
|
function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir42()) {
|
|
111612
|
-
return
|
|
112615
|
+
return resolve44(home2, ".switchroom", "blocked-approvals");
|
|
111613
112616
|
}
|
|
111614
112617
|
var FALLBACK_RECORD_NAME = "blocked-approval.json";
|
|
111615
112618
|
function agentStateRoot(sharedDir) {
|
|
111616
|
-
return
|
|
112619
|
+
return resolve44(sharedDir, "..", "agents");
|
|
111617
112620
|
}
|
|
111618
112621
|
function finiteOrNull(v) {
|
|
111619
112622
|
const n = Number(v);
|
|
@@ -111652,7 +112655,7 @@ function readBlockedApprovalsWithErrors(dir, agentsRoot = agentStateRoot(dir)) {
|
|
|
111652
112655
|
function readSharedRecords(dir) {
|
|
111653
112656
|
let entries;
|
|
111654
112657
|
try {
|
|
111655
|
-
entries =
|
|
112658
|
+
entries = readdirSync31(dir);
|
|
111656
112659
|
} catch (err) {
|
|
111657
112660
|
if (err?.code === "ENOENT") {
|
|
111658
112661
|
return { blocked: [], unreadable: 0 };
|
|
@@ -111666,7 +112669,7 @@ function readSharedRecords(dir) {
|
|
|
111666
112669
|
continue;
|
|
111667
112670
|
let text;
|
|
111668
112671
|
try {
|
|
111669
|
-
text =
|
|
112672
|
+
text = readFileSync76(join90(dir, name), "utf-8");
|
|
111670
112673
|
} catch {
|
|
111671
112674
|
unreadable++;
|
|
111672
112675
|
continue;
|
|
@@ -111685,7 +112688,7 @@ function readSharedRecords(dir) {
|
|
|
111685
112688
|
function readFallbackRecords(agentsRoot, alreadySeen) {
|
|
111686
112689
|
let agents;
|
|
111687
112690
|
try {
|
|
111688
|
-
agents =
|
|
112691
|
+
agents = readdirSync31(agentsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
111689
112692
|
} catch (err) {
|
|
111690
112693
|
if (err?.code === "ENOENT") {
|
|
111691
112694
|
return { blocked: [], unreadable: 0 };
|
|
@@ -111699,7 +112702,7 @@ function readFallbackRecords(agentsRoot, alreadySeen) {
|
|
|
111699
112702
|
continue;
|
|
111700
112703
|
let text;
|
|
111701
112704
|
try {
|
|
111702
|
-
text =
|
|
112705
|
+
text = readFileSync76(join90(agentsRoot, agent, FALLBACK_RECORD_NAME), "utf-8");
|
|
111703
112706
|
} catch (err) {
|
|
111704
112707
|
if (err?.code !== "ENOENT")
|
|
111705
112708
|
unreadable++;
|
|
@@ -111746,7 +112749,7 @@ init_audit_reader();
|
|
|
111746
112749
|
// src/scheduler/dispatch.ts
|
|
111747
112750
|
init_merge();
|
|
111748
112751
|
init_overlay_loader();
|
|
111749
|
-
import { createHash as
|
|
112752
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
111750
112753
|
function collectScheduleEntries(config) {
|
|
111751
112754
|
const out = [];
|
|
111752
112755
|
const agentNames = Object.keys(config.agents).sort();
|
|
@@ -111766,7 +112769,7 @@ function collectScheduleEntries(config) {
|
|
|
111766
112769
|
cron: entry.cron,
|
|
111767
112770
|
...typeof title === "string" && title.length > 0 ? { name: title } : {},
|
|
111768
112771
|
...entry.prompt !== undefined ? { prompt: entry.prompt } : {},
|
|
111769
|
-
promptKey:
|
|
112772
|
+
promptKey: createHash16("sha256").update(auditMaterial).digest("hex").slice(0, 12),
|
|
111770
112773
|
...entry.topic !== undefined ? { topic: entry.topic } : {},
|
|
111771
112774
|
...entry.kind !== undefined ? { kind: entry.kind } : {},
|
|
111772
112775
|
...entry.model !== undefined ? { model: entry.model } : {},
|
|
@@ -111815,7 +112818,7 @@ import { homedir as homedir45 } from "node:os";
|
|
|
111815
112818
|
// src/web/config-edit-plan.ts
|
|
111816
112819
|
init_schema();
|
|
111817
112820
|
var import_yaml17 = __toESM(require_dist(), 1);
|
|
111818
|
-
import { readFileSync as
|
|
112821
|
+
import { readFileSync as readFileSync77 } from "node:fs";
|
|
111819
112822
|
|
|
111820
112823
|
class ConfigPlanError extends Error {
|
|
111821
112824
|
}
|
|
@@ -111825,7 +112828,7 @@ function composeTransforms(...transforms) {
|
|
|
111825
112828
|
function planConfigEdit(configPath, transform) {
|
|
111826
112829
|
let before;
|
|
111827
112830
|
try {
|
|
111828
|
-
before =
|
|
112831
|
+
before = readFileSync77(configPath, "utf-8");
|
|
111829
112832
|
} catch (err) {
|
|
111830
112833
|
throw new ConfigPlanError(`could not read config: ${err.message}`);
|
|
111831
112834
|
}
|
|
@@ -111860,19 +112863,19 @@ import {
|
|
|
111860
112863
|
} from "node:fs";
|
|
111861
112864
|
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
111862
112865
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
111863
|
-
import { join as
|
|
112866
|
+
import { join as join92 } from "node:path";
|
|
111864
112867
|
|
|
111865
112868
|
class ConfigDiffError extends Error {
|
|
111866
112869
|
}
|
|
111867
112870
|
function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
|
|
111868
112871
|
if (before === after)
|
|
111869
112872
|
return "";
|
|
111870
|
-
const dir = mkdtemp(
|
|
112873
|
+
const dir = mkdtemp(join92(tmpdir5(), "switchroom-config-diff-"));
|
|
111871
112874
|
try {
|
|
111872
|
-
mkdirSync47(
|
|
111873
|
-
mkdirSync47(
|
|
111874
|
-
writeFileSync32(
|
|
111875
|
-
writeFileSync32(
|
|
112875
|
+
mkdirSync47(join92(dir, "cur"), { recursive: true });
|
|
112876
|
+
mkdirSync47(join92(dir, "new"), { recursive: true });
|
|
112877
|
+
writeFileSync32(join92(dir, "cur", name), before);
|
|
112878
|
+
writeFileSync32(join92(dir, "new", name), after);
|
|
111876
112879
|
const r = spawnSync16(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
|
|
111877
112880
|
if (r.status === 0)
|
|
111878
112881
|
return "";
|
|
@@ -111904,9 +112907,9 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
|
|
|
111904
112907
|
|
|
111905
112908
|
// src/web/hostd-config-propose.ts
|
|
111906
112909
|
init_client5();
|
|
111907
|
-
import { existsSync as
|
|
112910
|
+
import { existsSync as existsSync85 } from "node:fs";
|
|
111908
112911
|
import { homedir as homedir44 } from "node:os";
|
|
111909
|
-
import { join as
|
|
112912
|
+
import { join as join93 } from "node:path";
|
|
111910
112913
|
var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
|
|
111911
112914
|
function resolveHostdOperatorSocket(env2 = process.env) {
|
|
111912
112915
|
const override = env2.SWITCHROOM_HOSTD_OPERATOR_SOCKET;
|
|
@@ -111914,10 +112917,10 @@ function resolveHostdOperatorSocket(env2 = process.env) {
|
|
|
111914
112917
|
return override;
|
|
111915
112918
|
const candidates = [
|
|
111916
112919
|
"/host-home/.switchroom/hostd/operator/sock",
|
|
111917
|
-
|
|
112920
|
+
join93(homedir44(), ".switchroom", "hostd", "operator", "sock")
|
|
111918
112921
|
];
|
|
111919
112922
|
for (const c of candidates) {
|
|
111920
|
-
if (
|
|
112923
|
+
if (existsSync85(c))
|
|
111921
112924
|
return c;
|
|
111922
112925
|
}
|
|
111923
112926
|
return null;
|
|
@@ -112125,7 +113128,7 @@ init_client2();
|
|
|
112125
113128
|
|
|
112126
113129
|
// telegram-plugin/registry/turns-schema.ts
|
|
112127
113130
|
import { chmodSync as chmodSync15 } from "fs";
|
|
112128
|
-
import { join as
|
|
113131
|
+
import { join as join95 } from "path";
|
|
112129
113132
|
|
|
112130
113133
|
// src/util/state-owner.ts
|
|
112131
113134
|
init_atomic();
|
|
@@ -112133,28 +113136,28 @@ import {
|
|
|
112133
113136
|
appendFileSync as appendFileSync4,
|
|
112134
113137
|
closeSync as closeSync16,
|
|
112135
113138
|
constants as constants2,
|
|
112136
|
-
existsSync as
|
|
113139
|
+
existsSync as existsSync86,
|
|
112137
113140
|
fchownSync as fchownSync2,
|
|
112138
113141
|
fstatSync as fstatSync5,
|
|
112139
113142
|
mkdirSync as mkdirSync48,
|
|
112140
113143
|
openSync as openSync16,
|
|
112141
|
-
readdirSync as
|
|
112142
|
-
statSync as
|
|
113144
|
+
readdirSync as readdirSync32,
|
|
113145
|
+
statSync as statSync51,
|
|
112143
113146
|
writeFileSync as writeFileSync33
|
|
112144
113147
|
} from "node:fs";
|
|
112145
|
-
import { dirname as dirname35, join as
|
|
113148
|
+
import { dirname as dirname35, join as join94, relative as relative4, sep as sep6 } from "node:path";
|
|
112146
113149
|
var NOFOLLOW = constants2.O_NOFOLLOW ?? 0;
|
|
112147
113150
|
var ownerCache = new Map;
|
|
112148
113151
|
var loggedFailure = false;
|
|
112149
113152
|
function defaultLog(line) {
|
|
112150
113153
|
process.stderr.write(line);
|
|
112151
113154
|
}
|
|
112152
|
-
function noteFailure(op,
|
|
113155
|
+
function noteFailure(op, path9, err, log) {
|
|
112153
113156
|
if (loggedFailure)
|
|
112154
113157
|
return;
|
|
112155
113158
|
loggedFailure = true;
|
|
112156
113159
|
const code = err.code ?? "";
|
|
112157
|
-
log(`switchroom state-owner: ${op} could not adopt directory ownership for ${
|
|
113160
|
+
log(`switchroom state-owner: ${op} could not adopt directory ownership for ${path9}` + `${code ? ` (${code})` : ""}: ${err.message}. ` + `Continuing; state files may stay owned by the writing uid. ` + `Further ownership warnings this process are suppressed.
|
|
112158
113161
|
`);
|
|
112159
113162
|
}
|
|
112160
113163
|
function isMultiplyLinked(st) {
|
|
@@ -112168,7 +113171,7 @@ function resolveStateOwner(dir) {
|
|
|
112168
113171
|
return cached;
|
|
112169
113172
|
let owner = null;
|
|
112170
113173
|
try {
|
|
112171
|
-
const st =
|
|
113174
|
+
const st = statSync51(dir);
|
|
112172
113175
|
owner = st.uid === 0 && st.gid === 0 ? null : { uid: st.uid, gid: st.gid };
|
|
112173
113176
|
} catch {
|
|
112174
113177
|
owner = null;
|
|
@@ -112176,13 +113179,13 @@ function resolveStateOwner(dir) {
|
|
|
112176
113179
|
ownerCache.set(dir, owner);
|
|
112177
113180
|
return owner;
|
|
112178
113181
|
}
|
|
112179
|
-
function adoptStateOwnership(
|
|
112180
|
-
const target = owner === undefined ? resolveStateOwner(dirname35(
|
|
113182
|
+
function adoptStateOwnership(path9, owner, log = defaultLog) {
|
|
113183
|
+
const target = owner === undefined ? resolveStateOwner(dirname35(path9)) : owner;
|
|
112181
113184
|
if (!target)
|
|
112182
113185
|
return;
|
|
112183
113186
|
let fd = null;
|
|
112184
113187
|
try {
|
|
112185
|
-
fd = openSync16(
|
|
113188
|
+
fd = openSync16(path9, constants2.O_RDONLY | NOFOLLOW);
|
|
112186
113189
|
const st = fstatSync5(fd);
|
|
112187
113190
|
if (st.uid === target.uid && st.gid === target.gid)
|
|
112188
113191
|
return;
|
|
@@ -112191,7 +113194,7 @@ function adoptStateOwnership(path7, owner, log = defaultLog) {
|
|
|
112191
113194
|
fchownSync2(fd, target.uid, target.gid);
|
|
112192
113195
|
} catch (err) {
|
|
112193
113196
|
if (err.code !== "ENOENT") {
|
|
112194
|
-
noteFailure("adopt",
|
|
113197
|
+
noteFailure("adopt", path9, err, log);
|
|
112195
113198
|
}
|
|
112196
113199
|
} finally {
|
|
112197
113200
|
if (fd !== null) {
|
|
@@ -112209,8 +113212,8 @@ function adoptSqliteOwnership(dbPath, log = defaultLog) {
|
|
|
112209
113212
|
adoptStateOwnership(dbPath + suffix, owner, log);
|
|
112210
113213
|
}
|
|
112211
113214
|
}
|
|
112212
|
-
function mkdirStateSync(
|
|
112213
|
-
const first = mkdirSync48(
|
|
113215
|
+
function mkdirStateSync(path9, options = { recursive: true }, log = defaultLog) {
|
|
113216
|
+
const first = mkdirSync48(path9, { ...options, recursive: true });
|
|
112214
113217
|
if (first === undefined)
|
|
112215
113218
|
return;
|
|
112216
113219
|
const owner = resolveStateOwner(dirname35(first));
|
|
@@ -112218,13 +113221,13 @@ function mkdirStateSync(path7, options = { recursive: true }, log = defaultLog)
|
|
|
112218
113221
|
return;
|
|
112219
113222
|
let current = first;
|
|
112220
113223
|
adoptStateOwnership(current, owner, log);
|
|
112221
|
-
const tail = relative4(first,
|
|
113224
|
+
const tail = relative4(first, path9);
|
|
112222
113225
|
if (tail === "" || tail.startsWith(".."))
|
|
112223
113226
|
return;
|
|
112224
113227
|
for (const part of tail.split(sep6)) {
|
|
112225
113228
|
if (part === "")
|
|
112226
113229
|
continue;
|
|
112227
|
-
current =
|
|
113230
|
+
current = join94(current, part);
|
|
112228
113231
|
adoptStateOwnership(current, owner, log);
|
|
112229
113232
|
}
|
|
112230
113233
|
}
|
|
@@ -112301,20 +113304,20 @@ function applySchema(db) {
|
|
|
112301
113304
|
}
|
|
112302
113305
|
function openTurnsDb(agentDir) {
|
|
112303
113306
|
const Database2 = loadDatabaseClass();
|
|
112304
|
-
const dir =
|
|
113307
|
+
const dir = join95(agentDir, "telegram");
|
|
112305
113308
|
mkdirStateSync(dir, { recursive: true, mode: 448 });
|
|
112306
|
-
const
|
|
112307
|
-
const db = new Database2(
|
|
113309
|
+
const path9 = join95(dir, "registry.db");
|
|
113310
|
+
const db = new Database2(path9, { create: true });
|
|
112308
113311
|
applySchema(db);
|
|
112309
113312
|
try {
|
|
112310
|
-
chmodSync15(
|
|
113313
|
+
chmodSync15(path9, 420);
|
|
112311
113314
|
for (const suffix of ["-shm", "-wal"]) {
|
|
112312
113315
|
try {
|
|
112313
|
-
chmodSync15(
|
|
113316
|
+
chmodSync15(path9 + suffix, 420);
|
|
112314
113317
|
} catch {}
|
|
112315
113318
|
}
|
|
112316
113319
|
} catch {}
|
|
112317
|
-
adoptSqliteOwnership(
|
|
113320
|
+
adoptSqliteOwnership(path9);
|
|
112318
113321
|
return db;
|
|
112319
113322
|
}
|
|
112320
113323
|
function mapRow(row) {
|
|
@@ -112516,7 +113519,7 @@ var DASHBOARD_CACHE_TTL = {
|
|
|
112516
113519
|
};
|
|
112517
113520
|
function readContextOccupancy(agentsDir, name) {
|
|
112518
113521
|
try {
|
|
112519
|
-
const raw =
|
|
113522
|
+
const raw = readFileSync78(resolve45(agentsDir, name, "context-occupancy.json"), "utf-8");
|
|
112520
113523
|
const s = JSON.parse(raw);
|
|
112521
113524
|
if (typeof s?.occupancy !== "number")
|
|
112522
113525
|
return null;
|
|
@@ -112527,7 +113530,7 @@ function readContextOccupancy(agentsDir, name) {
|
|
|
112527
113530
|
}
|
|
112528
113531
|
function readLastTurnAt(agentsDir, name) {
|
|
112529
113532
|
try {
|
|
112530
|
-
const db = openTurnsDb(
|
|
113533
|
+
const db = openTurnsDb(resolve45(agentsDir, name));
|
|
112531
113534
|
try {
|
|
112532
113535
|
const turns = listTurnsForAgent(db, { limit: 1 });
|
|
112533
113536
|
return turns.length > 0 ? turns[0].started_at : null;
|
|
@@ -112540,8 +113543,8 @@ function readLastTurnAt(agentsDir, name) {
|
|
|
112540
113543
|
}
|
|
112541
113544
|
function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now2 = Date.now()) {
|
|
112542
113545
|
try {
|
|
112543
|
-
const f =
|
|
112544
|
-
return now2 -
|
|
113546
|
+
const f = resolve45(agentsDir, name, "telegram", ".bridge-alive");
|
|
113547
|
+
return now2 - statSync52(f).mtimeMs <= maxAgeMs;
|
|
112545
113548
|
} catch {
|
|
112546
113549
|
return false;
|
|
112547
113550
|
}
|
|
@@ -112639,7 +113642,7 @@ function handleGetTurns(config, sessionId, limit) {
|
|
|
112639
113642
|
const agentName = parsed.agentName;
|
|
112640
113643
|
const hasThread = "threadId" in parsed;
|
|
112641
113644
|
const agentsDir = resolveAgentsDir(config);
|
|
112642
|
-
const agentDir =
|
|
113645
|
+
const agentDir = resolve45(agentsDir, agentName);
|
|
112643
113646
|
const chatId = config.agents?.[agentName]?.channels?.telegram?.chat_id ?? undefined;
|
|
112644
113647
|
const db = openTurnsDb(agentDir);
|
|
112645
113648
|
try {
|
|
@@ -112662,7 +113665,7 @@ function handleListThreadIds(config, agentName) {
|
|
|
112662
113665
|
if (!chatId)
|
|
112663
113666
|
return [];
|
|
112664
113667
|
const agentsDir = resolveAgentsDir(config);
|
|
112665
|
-
const db = openTurnsDb(
|
|
113668
|
+
const db = openTurnsDb(resolve45(agentsDir, agentName));
|
|
112666
113669
|
try {
|
|
112667
113670
|
return listDistinctThreadIds(db, chatId);
|
|
112668
113671
|
} finally {
|
|
@@ -112675,7 +113678,7 @@ function handleListThreadIds(config, agentName) {
|
|
|
112675
113678
|
function handleGetSubagents(config, agentName, status) {
|
|
112676
113679
|
try {
|
|
112677
113680
|
const agentsDir = resolveAgentsDir(config);
|
|
112678
|
-
const agentDir =
|
|
113681
|
+
const agentDir = resolve45(agentsDir, agentName);
|
|
112679
113682
|
const db = openTurnsDb(agentDir);
|
|
112680
113683
|
try {
|
|
112681
113684
|
applySubagentsSchema(db);
|
|
@@ -112932,7 +113935,7 @@ async function handleGetSystemHealth(config, home2) {
|
|
|
112932
113935
|
};
|
|
112933
113936
|
try {
|
|
112934
113937
|
const logPath = defaultAuditLogPath2(home2);
|
|
112935
|
-
if (
|
|
113938
|
+
if (existsSync87(logPath)) {
|
|
112936
113939
|
hostd.auditLogPresent = true;
|
|
112937
113940
|
const raw = readAuditRaw(logPath);
|
|
112938
113941
|
hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
|
|
@@ -113241,7 +114244,7 @@ async function handleGetSchedule(config, deps = {}) {
|
|
|
113241
114244
|
const recentByAgent = {};
|
|
113242
114245
|
const agents = new Set(entries.map((e) => e.agent));
|
|
113243
114246
|
for (const agent of agents) {
|
|
113244
|
-
const rows = readRecentFires(
|
|
114247
|
+
const rows = readRecentFires(resolve45(agentsDir, agent, "scheduler.jsonl"));
|
|
113245
114248
|
if (rows.length > 0)
|
|
113246
114249
|
recentByAgent[agent] = rows.slice(-10);
|
|
113247
114250
|
}
|
|
@@ -113273,11 +114276,11 @@ async function handleGetApprovals() {
|
|
|
113273
114276
|
return { reachable: true, decisions: sorted };
|
|
113274
114277
|
}
|
|
113275
114278
|
function brokerOperatorSocketPath(home2 = process.env.HOME || homedir45()) {
|
|
113276
|
-
return
|
|
114279
|
+
return join96(home2, ".switchroom", "broker-operator", "sock");
|
|
113277
114280
|
}
|
|
113278
114281
|
function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir45()) {
|
|
113279
114282
|
const p = brokerOperatorSocketPath(home2);
|
|
113280
|
-
return
|
|
114283
|
+
return existsSync87(p) ? p : null;
|
|
113281
114284
|
}
|
|
113282
114285
|
async function handleGetGrants(deps = {}) {
|
|
113283
114286
|
const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
|
|
@@ -113668,8 +114671,8 @@ async function handleGetSummary(deps, now2 = Date.now()) {
|
|
|
113668
114671
|
init_fleet_health_read();
|
|
113669
114672
|
|
|
113670
114673
|
// src/web/webhook-handler.ts
|
|
113671
|
-
import { appendFileSync as appendFileSync5, existsSync as
|
|
113672
|
-
import { join as
|
|
114674
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync89, mkdirSync as mkdirSync49, readFileSync as readFileSync81, writeFileSync as writeFileSync34 } from "fs";
|
|
114675
|
+
import { join as join98 } from "path";
|
|
113673
114676
|
import { homedir as homedir48 } from "os";
|
|
113674
114677
|
|
|
113675
114678
|
// src/web/webhook-verify.ts
|
|
@@ -113837,7 +114840,7 @@ init_log_rotation();
|
|
|
113837
114840
|
import net4 from "node:net";
|
|
113838
114841
|
function forwardToGateway(socketPath, req, opts = {}) {
|
|
113839
114842
|
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
113840
|
-
return new Promise((
|
|
114843
|
+
return new Promise((resolve47) => {
|
|
113841
114844
|
let settled = false;
|
|
113842
114845
|
let buf = "";
|
|
113843
114846
|
const done = (value) => {
|
|
@@ -113847,7 +114850,7 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
113847
114850
|
try {
|
|
113848
114851
|
conn.destroy();
|
|
113849
114852
|
} catch {}
|
|
113850
|
-
|
|
114853
|
+
resolve47(value);
|
|
113851
114854
|
};
|
|
113852
114855
|
const conn = net4.createConnection(socketPath);
|
|
113853
114856
|
conn.setEncoding("utf8");
|
|
@@ -113878,20 +114881,20 @@ function forwardToGateway(socketPath, req, opts = {}) {
|
|
|
113878
114881
|
}
|
|
113879
114882
|
|
|
113880
114883
|
// src/web/webhook-edge.ts
|
|
113881
|
-
import { existsSync as
|
|
113882
|
-
import { join as
|
|
114884
|
+
import { existsSync as existsSync88, readFileSync as readFileSync80 } from "fs";
|
|
114885
|
+
import { join as join97 } from "path";
|
|
113883
114886
|
import { homedir as homedir47 } from "os";
|
|
113884
114887
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
113885
114888
|
var EDGE_HEADER = "x-switchroom-edge";
|
|
113886
114889
|
function edgeSecretPath() {
|
|
113887
|
-
return
|
|
114890
|
+
return join97(homedir47(), ".switchroom", "webhook-edge-secret");
|
|
113888
114891
|
}
|
|
113889
|
-
function loadEdgeSecret(
|
|
113890
|
-
const p =
|
|
114892
|
+
function loadEdgeSecret(path9) {
|
|
114893
|
+
const p = path9 ?? edgeSecretPath();
|
|
113891
114894
|
try {
|
|
113892
|
-
if (!
|
|
114895
|
+
if (!existsSync88(p))
|
|
113893
114896
|
return null;
|
|
113894
|
-
const raw =
|
|
114897
|
+
const raw = readFileSync80(p, "utf-8").trim();
|
|
113895
114898
|
return raw.length > 0 ? raw : null;
|
|
113896
114899
|
} catch {
|
|
113897
114900
|
return null;
|
|
@@ -113940,17 +114943,17 @@ function jsonReply(status, body, extraHeaders) {
|
|
|
113940
114943
|
}
|
|
113941
114944
|
var DEDUP_MAX = 1000;
|
|
113942
114945
|
var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
113943
|
-
function loadDedupFile(
|
|
114946
|
+
function loadDedupFile(path9) {
|
|
113944
114947
|
try {
|
|
113945
|
-
if (!
|
|
114948
|
+
if (!existsSync89(path9))
|
|
113946
114949
|
return {};
|
|
113947
|
-
const raw = JSON.parse(
|
|
114950
|
+
const raw = JSON.parse(readFileSync81(path9, "utf-8"));
|
|
113948
114951
|
return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
|
|
113949
114952
|
} catch {
|
|
113950
114953
|
return {};
|
|
113951
114954
|
}
|
|
113952
114955
|
}
|
|
113953
|
-
function saveDedupFile(
|
|
114956
|
+
function saveDedupFile(path9, deliveries, now2) {
|
|
113954
114957
|
const pruned = {};
|
|
113955
114958
|
for (const [id, ts] of Object.entries(deliveries)) {
|
|
113956
114959
|
if (now2 - ts < DEDUP_TTL_MS)
|
|
@@ -113958,7 +114961,7 @@ function saveDedupFile(path7, deliveries, now2) {
|
|
|
113958
114961
|
}
|
|
113959
114962
|
const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
|
|
113960
114963
|
const final = Object.fromEntries(sorted);
|
|
113961
|
-
writeFileSync34(
|
|
114964
|
+
writeFileSync34(path9, JSON.stringify({ deliveries: final }), {
|
|
113962
114965
|
mode: 384
|
|
113963
114966
|
});
|
|
113964
114967
|
}
|
|
@@ -113966,8 +114969,8 @@ var agentDedupCache = new Map;
|
|
|
113966
114969
|
function createFileDedupStore(resolveAgentDir2) {
|
|
113967
114970
|
return {
|
|
113968
114971
|
check(agent, deliveryId, now2) {
|
|
113969
|
-
const telegramDir =
|
|
113970
|
-
const filePath =
|
|
114972
|
+
const telegramDir = join98(resolveAgentDir2(agent), "telegram");
|
|
114973
|
+
const filePath = join98(telegramDir, "webhook-dedup.json");
|
|
113971
114974
|
if (!agentDedupCache.has(agent)) {
|
|
113972
114975
|
agentDedupCache.set(agent, loadDedupFile(filePath));
|
|
113973
114976
|
}
|
|
@@ -114019,7 +115022,7 @@ function shouldWriteThrottleIssue(agent, source, now2, windowMap) {
|
|
|
114019
115022
|
return true;
|
|
114020
115023
|
}
|
|
114021
115024
|
function writeThrottleIssue(agent, source, now2, telegramDir, log) {
|
|
114022
|
-
const issuesPath =
|
|
115025
|
+
const issuesPath = join98(telegramDir, "issues.jsonl");
|
|
114023
115026
|
try {
|
|
114024
115027
|
mkdirSync49(telegramDir, { recursive: true });
|
|
114025
115028
|
const record = {
|
|
@@ -114044,7 +115047,7 @@ function writeThrottleIssue(agent, source, now2, telegramDir, log) {
|
|
|
114044
115047
|
async function handleWebhookIngest(args, deps = {}) {
|
|
114045
115048
|
const log = deps.log ?? ((s) => process.stderr.write(s));
|
|
114046
115049
|
const now2 = (deps.now ?? Date.now)();
|
|
114047
|
-
const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) =>
|
|
115050
|
+
const resolveAgentDir2 = deps.resolveAgentDir ?? ((a) => join98(homedir48(), ".switchroom", "agents", a));
|
|
114048
115051
|
const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
|
|
114049
115052
|
const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir2);
|
|
114050
115053
|
if (!args.agentExists) {
|
|
@@ -114111,7 +115114,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114111
115114
|
if (retryAfter !== null) {
|
|
114112
115115
|
if (!args.viaGateway) {
|
|
114113
115116
|
const agentDir2 = resolveAgentDir2(args.agent);
|
|
114114
|
-
const telegramDir2 =
|
|
115117
|
+
const telegramDir2 = join98(agentDir2, "telegram");
|
|
114115
115118
|
if (shouldWriteThrottleIssue(args.agent, source, now2)) {
|
|
114116
115119
|
writeThrottleIssue(args.agent, source, now2, telegramDir2, log);
|
|
114117
115120
|
}
|
|
@@ -114131,7 +115134,7 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114131
115134
|
const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
|
|
114132
115135
|
const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
|
|
114133
115136
|
if (args.viaGateway) {
|
|
114134
|
-
const socketPath =
|
|
115137
|
+
const socketPath = join98(resolveAgentDir2(args.agent), "telegram", "webhook.sock");
|
|
114135
115138
|
const forward = deps.forwardFn ?? forwardToGateway;
|
|
114136
115139
|
const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
|
|
114137
115140
|
let resp;
|
|
@@ -114170,8 +115173,8 @@ async function handleWebhookIngest(args, deps = {}) {
|
|
|
114170
115173
|
return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
|
|
114171
115174
|
}
|
|
114172
115175
|
const agentDir = resolveAgentDir2(args.agent);
|
|
114173
|
-
const telegramDir =
|
|
114174
|
-
const logPath =
|
|
115176
|
+
const telegramDir = join98(agentDir, "telegram");
|
|
115177
|
+
const logPath = join98(telegramDir, "webhook-events.jsonl");
|
|
114175
115178
|
try {
|
|
114176
115179
|
mkdirSync49(telegramDir, { recursive: true });
|
|
114177
115180
|
rotateWebhookLogIfNeeded(logPath);
|
|
@@ -114200,9 +115203,9 @@ init_shipped_assets();
|
|
|
114200
115203
|
// src/web/hermes-adapter.ts
|
|
114201
115204
|
init_loader();
|
|
114202
115205
|
import { createConnection as createConnection3 } from "node:net";
|
|
114203
|
-
import { createHash as
|
|
114204
|
-
import { join as
|
|
114205
|
-
import { existsSync as
|
|
115206
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
115207
|
+
import { join as join99, resolve as resolve47 } from "node:path";
|
|
115208
|
+
import { existsSync as existsSync90, readFileSync as readFileSync82 } from "node:fs";
|
|
114206
115209
|
|
|
114207
115210
|
// src/agent-scheduler/channel-target.ts
|
|
114208
115211
|
init_merge();
|
|
@@ -114246,8 +115249,8 @@ function agentLiveness(config, agentName) {
|
|
|
114246
115249
|
const agentsDir = resolveAgentsDir(config);
|
|
114247
115250
|
if (agentBridgeAlive(agentsDir, agentName))
|
|
114248
115251
|
return "active";
|
|
114249
|
-
const alive =
|
|
114250
|
-
if (
|
|
115252
|
+
const alive = join99(agentsDir, agentName, "telegram", ".bridge-alive");
|
|
115253
|
+
if (existsSync90(alive))
|
|
114251
115254
|
return "idle";
|
|
114252
115255
|
return "offline";
|
|
114253
115256
|
}
|
|
@@ -114355,11 +115358,11 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
114355
115358
|
const threadId = topicId !== undefined ? topicId === null ? undefined : parseInt(topicId, 10) || undefined : channel.threadId;
|
|
114356
115359
|
return { chatId: channel.chatId, threadId };
|
|
114357
115360
|
}
|
|
114358
|
-
const accessPath =
|
|
114359
|
-
if (!
|
|
115361
|
+
const accessPath = resolve47(agentsDir, agentName, "telegram", "access.json");
|
|
115362
|
+
if (!existsSync90(accessPath))
|
|
114360
115363
|
return null;
|
|
114361
115364
|
try {
|
|
114362
|
-
const raw =
|
|
115365
|
+
const raw = readFileSync82(accessPath, "utf-8");
|
|
114363
115366
|
const access = JSON.parse(raw);
|
|
114364
115367
|
const chatId = access.allowFrom?.[0];
|
|
114365
115368
|
if (!chatId)
|
|
@@ -114370,8 +115373,8 @@ function resolveAgentChat(config, sessionId, agentsDir) {
|
|
|
114370
115373
|
}
|
|
114371
115374
|
}
|
|
114372
115375
|
async function injectInbound(agentsDir, agentName, chatId, threadId, text, promptKey) {
|
|
114373
|
-
const socketPath =
|
|
114374
|
-
if (!
|
|
115376
|
+
const socketPath = resolve47(agentsDir, agentName, "telegram", "gateway.sock");
|
|
115377
|
+
if (!existsSync90(socketPath)) {
|
|
114375
115378
|
return { ok: false, error: `agent ${agentName} gateway socket not found \u2014 is it running?` };
|
|
114376
115379
|
}
|
|
114377
115380
|
const ts = Date.now();
|
|
@@ -114418,8 +115421,8 @@ async function injectInbound(agentsDir, agentName, chatId, threadId, text, promp
|
|
|
114418
115421
|
});
|
|
114419
115422
|
}
|
|
114420
115423
|
function readHistoryDb(agentsDir, agentName, limit) {
|
|
114421
|
-
const dbPath =
|
|
114422
|
-
if (!
|
|
115424
|
+
const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
|
|
115425
|
+
if (!existsSync90(dbPath))
|
|
114423
115426
|
return null;
|
|
114424
115427
|
try {
|
|
114425
115428
|
let SqliteDatabase;
|
|
@@ -114448,7 +115451,7 @@ function readHistoryDb(agentsDir, agentName, limit) {
|
|
|
114448
115451
|
}
|
|
114449
115452
|
}
|
|
114450
115453
|
function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs = 2000) {
|
|
114451
|
-
const dbPath =
|
|
115454
|
+
const dbPath = resolve47(agentsDir, agentName, "telegram", "history.db");
|
|
114452
115455
|
let SqliteDatabase = null;
|
|
114453
115456
|
try {
|
|
114454
115457
|
const meta = import.meta;
|
|
@@ -114458,7 +115461,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
|
|
|
114458
115461
|
}
|
|
114459
115462
|
} catch {}
|
|
114460
115463
|
let cursor = 0;
|
|
114461
|
-
if (SqliteDatabase &&
|
|
115464
|
+
if (SqliteDatabase && existsSync90(dbPath)) {
|
|
114462
115465
|
try {
|
|
114463
115466
|
const db = new SqliteDatabase(dbPath, { readonly: true });
|
|
114464
115467
|
try {
|
|
@@ -114470,7 +115473,7 @@ function startHistoryPoll(ctx, agentsDir, agentName, sessionId, pollIntervalMs =
|
|
|
114470
115473
|
} catch {}
|
|
114471
115474
|
}
|
|
114472
115475
|
const timer = setInterval(() => {
|
|
114473
|
-
if (!SqliteDatabase || !
|
|
115476
|
+
if (!SqliteDatabase || !existsSync90(dbPath))
|
|
114474
115477
|
return;
|
|
114475
115478
|
try {
|
|
114476
115479
|
const db = new SqliteDatabase(dbPath, { readonly: true });
|
|
@@ -115133,7 +116136,7 @@ async function onHermesMessage(ctx, raw) {
|
|
|
115133
116136
|
sendResponse(ctx, rpcErr(id, -32603, `Could not resolve chat for ${sessionId} \u2014 ensure the agent has telegram.forum_chat_id or channels.telegram.chat_id configured.`));
|
|
115134
116137
|
break;
|
|
115135
116138
|
}
|
|
115136
|
-
const promptKey =
|
|
116139
|
+
const promptKey = createHash17("sha256").update(`${sessionId}:${Date.now()}:${content}`).digest("hex").slice(0, 12);
|
|
115137
116140
|
sendEvent(ctx, "message.start", sessionId, { prompt_key: promptKey });
|
|
115138
116141
|
pendingPromptKeys(ctx).set(sessionId, promptKey);
|
|
115139
116142
|
const result = await injectInbound(agentsDir, submitAgent, chat.chatId, chat.threadId, content, promptKey);
|
|
@@ -115329,9 +116332,9 @@ function resolveWebToken() {
|
|
|
115329
116332
|
if (fromEnv && fromEnv.length > 0)
|
|
115330
116333
|
return fromEnv;
|
|
115331
116334
|
const home2 = process.env.HOME ?? homedir49();
|
|
115332
|
-
const tokenPath =
|
|
115333
|
-
if (
|
|
115334
|
-
const existing =
|
|
116335
|
+
const tokenPath = join100(home2, ".switchroom", "web-token");
|
|
116336
|
+
if (existsSync91(tokenPath)) {
|
|
116337
|
+
const existing = readFileSync83(tokenPath, "utf8").trim();
|
|
115335
116338
|
if (existing.length > 0)
|
|
115336
116339
|
return existing;
|
|
115337
116340
|
}
|
|
@@ -115348,7 +116351,7 @@ function resolveWebToken() {
|
|
|
115348
116351
|
return token;
|
|
115349
116352
|
} catch (err) {
|
|
115350
116353
|
if (err.code === "EEXIST") {
|
|
115351
|
-
const existing =
|
|
116354
|
+
const existing = readFileSync83(tokenPath, "utf8").trim();
|
|
115352
116355
|
if (existing.length > 0)
|
|
115353
116356
|
return existing;
|
|
115354
116357
|
}
|
|
@@ -115421,14 +116424,14 @@ function checkWsAuth(req, token, server) {
|
|
|
115421
116424
|
return presented !== null && constantTimeEqual(presented, token);
|
|
115422
116425
|
}
|
|
115423
116426
|
function loadWebhookSecrets() {
|
|
115424
|
-
const
|
|
115425
|
-
if (!
|
|
116427
|
+
const path9 = join100(homedir49(), ".switchroom", "webhook-secrets.json");
|
|
116428
|
+
if (!existsSync91(path9))
|
|
115426
116429
|
return {};
|
|
115427
116430
|
try {
|
|
115428
|
-
const parsed = JSON.parse(
|
|
116431
|
+
const parsed = JSON.parse(readFileSync83(path9, "utf-8"));
|
|
115429
116432
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
115430
116433
|
} catch (err) {
|
|
115431
|
-
process.stderr.write(`webhook-ingest: failed to parse ${
|
|
116434
|
+
process.stderr.write(`webhook-ingest: failed to parse ${path9}: ${err.message} \u2014 webhooks will return 401 until fixed
|
|
115432
116435
|
`);
|
|
115433
116436
|
return {};
|
|
115434
116437
|
}
|
|
@@ -115615,11 +116618,11 @@ function resolveUiDir(probe2 = {
|
|
|
115615
116618
|
bundleDir: import.meta.dirname,
|
|
115616
116619
|
execPath: process.execPath
|
|
115617
116620
|
}) {
|
|
115618
|
-
return resolveShippedAsset(WEB_UI_ASSET, probe2).path ??
|
|
116621
|
+
return resolveShippedAsset(WEB_UI_ASSET, probe2).path ?? resolve48(probe2.bundleDir, WEB_UI_ASSET.asset);
|
|
115619
116622
|
}
|
|
115620
116623
|
function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
115621
116624
|
const uiDirRaw = resolveUiDir();
|
|
115622
|
-
const uiDir =
|
|
116625
|
+
const uiDir = existsSync91(uiDirRaw) ? realpathSync7(uiDirRaw) : uiDirRaw;
|
|
115623
116626
|
const token = resolveWebToken();
|
|
115624
116627
|
const freshConfig = () => {
|
|
115625
116628
|
if (!configPath)
|
|
@@ -115919,8 +116922,8 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
115919
116922
|
})();
|
|
115920
116923
|
}
|
|
115921
116924
|
const filePath = resolveDashboardFilePath(pathname);
|
|
115922
|
-
const fullPath =
|
|
115923
|
-
if (!
|
|
116925
|
+
const fullPath = join100(uiDir, filePath);
|
|
116926
|
+
if (!existsSync91(fullPath)) {
|
|
115924
116927
|
return new Response("Not Found", { status: 404 });
|
|
115925
116928
|
}
|
|
115926
116929
|
let realFullPath;
|
|
@@ -115930,12 +116933,12 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
|
|
|
115930
116933
|
return new Response("Not Found", { status: 404 });
|
|
115931
116934
|
}
|
|
115932
116935
|
const rel = relative5(uiDir, realFullPath);
|
|
115933
|
-
if (rel.startsWith("..") ||
|
|
116936
|
+
if (rel.startsWith("..") || resolve48(uiDir, rel) !== realFullPath) {
|
|
115934
116937
|
return new Response("Forbidden", { status: 403 });
|
|
115935
116938
|
}
|
|
115936
116939
|
const ext = extname(realFullPath);
|
|
115937
116940
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
115938
|
-
const content =
|
|
116941
|
+
const content = readFileSync83(realFullPath);
|
|
115939
116942
|
const headers = { "Content-Type": contentType };
|
|
115940
116943
|
const cacheControl = dashboardCacheControl(ext);
|
|
115941
116944
|
if (cacheControl)
|
|
@@ -116058,13 +117061,13 @@ init_helpers();
|
|
|
116058
117061
|
init_loader();
|
|
116059
117062
|
|
|
116060
117063
|
// src/web/startup-guard.ts
|
|
116061
|
-
import { existsSync as
|
|
117064
|
+
import { existsSync as existsSync92, readFileSync as readFileSync84, writeFileSync as writeFileSync35, mkdirSync as mkdirSync51, statSync as statSync53, unlinkSync as unlinkSync18 } from "node:fs";
|
|
116062
117065
|
import { dirname as dirname37 } from "node:path";
|
|
116063
117066
|
function detectConfigMountFault(configPath, deps = {}) {
|
|
116064
|
-
const
|
|
117067
|
+
const stat2 = deps.stat ?? ((p) => statSync53(p));
|
|
116065
117068
|
let st;
|
|
116066
117069
|
try {
|
|
116067
|
-
st =
|
|
117070
|
+
st = stat2(configPath);
|
|
116068
117071
|
} catch {
|
|
116069
117072
|
return null;
|
|
116070
117073
|
}
|
|
@@ -116094,9 +117097,9 @@ function nextCrashBackoff(prior, now2, options = {}) {
|
|
|
116094
117097
|
}
|
|
116095
117098
|
function readCrashState(statePath) {
|
|
116096
117099
|
try {
|
|
116097
|
-
if (!
|
|
117100
|
+
if (!existsSync92(statePath))
|
|
116098
117101
|
return null;
|
|
116099
|
-
const raw = JSON.parse(
|
|
117102
|
+
const raw = JSON.parse(readFileSync84(statePath, "utf-8"));
|
|
116100
117103
|
if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
|
|
116101
117104
|
return null;
|
|
116102
117105
|
return { count: raw.count, lastTs: raw.lastTs };
|
|
@@ -116112,7 +117115,7 @@ function writeCrashState(statePath, state) {
|
|
|
116112
117115
|
}
|
|
116113
117116
|
function clearCrashState(statePath) {
|
|
116114
117117
|
try {
|
|
116115
|
-
if (
|
|
117118
|
+
if (existsSync92(statePath))
|
|
116116
117119
|
unlinkSync18(statePath);
|
|
116117
117120
|
} catch {}
|
|
116118
117121
|
}
|
|
@@ -116135,7 +117138,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
|
|
|
116135
117138
|
throw err;
|
|
116136
117139
|
}
|
|
116137
117140
|
function webCrashStatePath() {
|
|
116138
|
-
return
|
|
117141
|
+
return join101(homedir50(), ".switchroom", "web", ".crashloop-state.json");
|
|
116139
117142
|
}
|
|
116140
117143
|
function registerWebCommand(program3) {
|
|
116141
117144
|
program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
|
|
@@ -116202,9 +117205,9 @@ init_embedded_examples();
|
|
|
116202
117205
|
init_atomic();
|
|
116203
117206
|
init_loader();
|
|
116204
117207
|
init_scaffold();
|
|
116205
|
-
import { existsSync as
|
|
117208
|
+
import { existsSync as existsSync94, readFileSync as readFileSync85, mkdirSync as mkdirSync52, statSync as statSync54, writeFileSync as writeFileSync36 } from "node:fs";
|
|
116206
117209
|
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
116207
|
-
import { resolve as
|
|
117210
|
+
import { resolve as resolve49, dirname as dirname38 } from "node:path";
|
|
116208
117211
|
init_state();
|
|
116209
117212
|
init_vault();
|
|
116210
117213
|
init_manager();
|
|
@@ -116619,7 +117622,7 @@ ${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
|
|
|
116619
117622
|
init_source();
|
|
116620
117623
|
init_loader();
|
|
116621
117624
|
init_hindsight();
|
|
116622
|
-
import { existsSync as
|
|
117625
|
+
import { existsSync as existsSync93 } from "node:fs";
|
|
116623
117626
|
var HINDSIGHT_READY_RETRIES = 5;
|
|
116624
117627
|
var HINDSIGHT_READY_INTERVAL_MS = 1000;
|
|
116625
117628
|
async function resolveLiteLLMForHindsight2(config, deps = {}) {
|
|
@@ -116656,7 +117659,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
|
|
|
116656
117659
|
try {
|
|
116657
117660
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
116658
117661
|
const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
116659
|
-
if (passphrase &&
|
|
117662
|
+
if (passphrase && existsSync93(vaultPath)) {
|
|
116660
117663
|
const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
116661
117664
|
const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
|
|
116662
117665
|
if (existing) {
|
|
@@ -116926,7 +117929,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
|
|
|
116926
117929
|
function writeSwitchroomYaml(configPath, text) {
|
|
116927
117930
|
let mode = 420;
|
|
116928
117931
|
try {
|
|
116929
|
-
mode =
|
|
117932
|
+
mode = statSync54(configPath).mode & 511;
|
|
116930
117933
|
} catch {}
|
|
116931
117934
|
writeConfigFileSync(configPath, text, mode);
|
|
116932
117935
|
}
|
|
@@ -117025,7 +118028,7 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
117025
118028
|
existingConfig = null;
|
|
117026
118029
|
}
|
|
117027
118030
|
}
|
|
117028
|
-
if (existingConfig &&
|
|
118031
|
+
if (existingConfig && existsSync94(existingConfig)) {
|
|
117029
118032
|
if (!nonInteractive) {
|
|
117030
118033
|
const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
|
|
117031
118034
|
if (!useExisting) {
|
|
@@ -117037,9 +118040,9 @@ async function stepConfigFile(configPath, nonInteractive) {
|
|
|
117037
118040
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
117038
118041
|
const hasExplicitTimezone = config.switchroom?.timezone !== undefined || config.defaults?.timezone !== undefined;
|
|
117039
118042
|
if (!hasExplicitTimezone) {
|
|
117040
|
-
await writeDetectedTimezone(
|
|
118043
|
+
await writeDetectedTimezone(resolve49(existingConfig), nonInteractive);
|
|
117041
118044
|
}
|
|
117042
|
-
return { config, configPath:
|
|
118045
|
+
return { config, configPath: resolve49(existingConfig) };
|
|
117043
118046
|
}
|
|
117044
118047
|
return await copyExampleConfig2(nonInteractive);
|
|
117045
118048
|
}
|
|
@@ -117066,12 +118069,12 @@ async function copyExampleConfig2(nonInteractive) {
|
|
|
117066
118069
|
await writeDetectedTimezone(destFile, nonInteractive);
|
|
117067
118070
|
const config = loadConfig(destFile);
|
|
117068
118071
|
console.log(source_default.green(` ${STEP_DONE} Config loaded`) + source_default.gray(` (${Object.keys(config.agents).length} agents)`));
|
|
117069
|
-
return { config, configPath:
|
|
118072
|
+
return { config, configPath: resolve49(destFile) };
|
|
117070
118073
|
}
|
|
117071
118074
|
async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
|
|
117072
118075
|
const detected = detect();
|
|
117073
118076
|
if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
|
|
117074
|
-
const before2 =
|
|
118077
|
+
const before2 = readFileSync85(destFile, "utf-8");
|
|
117075
118078
|
const after2 = setSwitchroomTimezone(before2, detected);
|
|
117076
118079
|
if (after2 !== before2)
|
|
117077
118080
|
writeSwitchroomYaml(destFile, after2);
|
|
@@ -117092,7 +118095,7 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
|
|
|
117092
118095
|
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."));
|
|
117093
118096
|
return;
|
|
117094
118097
|
}
|
|
117095
|
-
const before =
|
|
118098
|
+
const before = readFileSync85(destFile, "utf-8");
|
|
117096
118099
|
const after = setSwitchroomTimezone(before, zone);
|
|
117097
118100
|
if (after !== before)
|
|
117098
118101
|
writeSwitchroomYaml(destFile, after);
|
|
@@ -117171,7 +118174,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
117171
118174
|
try {
|
|
117172
118175
|
const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
|
|
117173
118176
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117174
|
-
if (
|
|
118177
|
+
if (existsSync94(vaultPath)) {
|
|
117175
118178
|
const secrets = openVault2(passphrase, vaultPath);
|
|
117176
118179
|
const key = rawToken.replace("vault:", "");
|
|
117177
118180
|
const entry = secrets[key];
|
|
@@ -117199,7 +118202,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
|
|
|
117199
118202
|
async function storeTokenInVault(config, vaultRef, token) {
|
|
117200
118203
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117201
118204
|
const key = vaultRef.replace("vault:", "");
|
|
117202
|
-
if (!
|
|
118205
|
+
if (!existsSync94(vaultPath)) {
|
|
117203
118206
|
console.log(source_default.gray(" Creating encrypted vault..."));
|
|
117204
118207
|
let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
|
|
117205
118208
|
if (!passphrase) {
|
|
@@ -117482,13 +118485,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
117482
118485
|
return;
|
|
117483
118486
|
}
|
|
117484
118487
|
const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
|
|
117485
|
-
if (!
|
|
118488
|
+
if (!existsSync94(vaultPath)) {
|
|
117486
118489
|
console.log(source_default.gray(" Skipping (vault not created yet)."));
|
|
117487
118490
|
return;
|
|
117488
118491
|
}
|
|
117489
118492
|
const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
|
|
117490
118493
|
const credPath = resolvePath(credPathRaw);
|
|
117491
|
-
if (config.vault?.broker?.autoUnlock === true &&
|
|
118494
|
+
if (config.vault?.broker?.autoUnlock === true && existsSync94(credPath)) {
|
|
117492
118495
|
console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
|
|
117493
118496
|
return;
|
|
117494
118497
|
}
|
|
@@ -117566,10 +118569,10 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
|
|
|
117566
118569
|
}
|
|
117567
118570
|
}
|
|
117568
118571
|
function persistApprovalAuthTelegramId(configPath) {
|
|
117569
|
-
if (!
|
|
118572
|
+
if (!existsSync94(configPath)) {
|
|
117570
118573
|
throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
|
|
117571
118574
|
}
|
|
117572
|
-
const content =
|
|
118575
|
+
const content = readFileSync85(configPath, "utf-8");
|
|
117573
118576
|
const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
|
|
117574
118577
|
if (result.kind === "rewritten") {
|
|
117575
118578
|
writeSwitchroomYaml(configPath, result.content);
|
|
@@ -117577,10 +118580,10 @@ function persistApprovalAuthTelegramId(configPath) {
|
|
|
117577
118580
|
return result.kind;
|
|
117578
118581
|
}
|
|
117579
118582
|
function persistDangerousMode(config, configPath) {
|
|
117580
|
-
if (!
|
|
118583
|
+
if (!existsSync94(configPath)) {
|
|
117581
118584
|
throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
|
|
117582
118585
|
}
|
|
117583
|
-
let content =
|
|
118586
|
+
let content = readFileSync85(configPath, "utf-8");
|
|
117584
118587
|
const agentNames = Object.keys(config.agents);
|
|
117585
118588
|
for (const name of agentNames) {
|
|
117586
118589
|
const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
|
|
@@ -117620,7 +118623,7 @@ async function stepOnboardingGuidance(config, nonInteractive) {
|
|
|
117620
118623
|
const agentNames = Object.keys(config.agents);
|
|
117621
118624
|
let allAuthenticated = true;
|
|
117622
118625
|
for (const name of agentNames) {
|
|
117623
|
-
const agentDir =
|
|
118626
|
+
const agentDir = resolve49(agentsDir, name);
|
|
117624
118627
|
const status = getAuthStatus(name, agentDir);
|
|
117625
118628
|
if (status.authenticated) {
|
|
117626
118629
|
console.log(` ${source_default.green("OK")} ${source_default.bold(name)}` + source_default.gray(` - authenticated (expires: ${status.timeUntilExpiry ?? "unknown"})`));
|
|
@@ -117716,7 +118719,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
|
|
|
117716
118719
|
const findings = [];
|
|
117717
118720
|
let applyPending = false;
|
|
117718
118721
|
if (!nonInteractive && agentNames.length > 0) {
|
|
117719
|
-
const composeExists = (deps.composeFileExists ?? (() =>
|
|
118722
|
+
const composeExists = (deps.composeFileExists ?? (() => existsSync94(composeFilePath())))();
|
|
117720
118723
|
if (!composeExists) {
|
|
117721
118724
|
applyPending = true;
|
|
117722
118725
|
log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
|
|
@@ -117813,9 +118816,9 @@ init_source();
|
|
|
117813
118816
|
init_loader();
|
|
117814
118817
|
init_lifecycle();
|
|
117815
118818
|
init_compose_env();
|
|
117816
|
-
import { existsSync as
|
|
118819
|
+
import { existsSync as existsSync98, mkdirSync as mkdirSync56, readFileSync as readFileSync89, realpathSync as realpathSync8, statSync as statSync57, chownSync as chownSync13 } from "node:fs";
|
|
117817
118820
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
117818
|
-
import { join as
|
|
118821
|
+
import { join as join102, dirname as dirname42 } from "node:path";
|
|
117819
118822
|
import { homedir as homedir51 } from "node:os";
|
|
117820
118823
|
|
|
117821
118824
|
// src/cli/release-yaml.ts
|
|
@@ -117876,7 +118879,7 @@ init_operator_uid();
|
|
|
117876
118879
|
init_atomic();
|
|
117877
118880
|
|
|
117878
118881
|
// src/cli/preflight-mounts.ts
|
|
117879
|
-
import { statSync as
|
|
118882
|
+
import { statSync as statSync55 } from "node:fs";
|
|
117880
118883
|
function parseHostBindSources(composeText) {
|
|
117881
118884
|
const out = [];
|
|
117882
118885
|
const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
|
|
@@ -117903,7 +118906,7 @@ function parseHostBindSources(composeText) {
|
|
|
117903
118906
|
return out;
|
|
117904
118907
|
}
|
|
117905
118908
|
function validateBindSources(composeText, deps = {}) {
|
|
117906
|
-
const
|
|
118909
|
+
const stat2 = deps.stat ?? ((p) => statSync55(p));
|
|
117907
118910
|
const sources = parseHostBindSources(composeText);
|
|
117908
118911
|
const issues = [];
|
|
117909
118912
|
const seen = new Set;
|
|
@@ -117915,7 +118918,7 @@ function validateBindSources(composeText, deps = {}) {
|
|
|
117915
118918
|
checked++;
|
|
117916
118919
|
let st = null;
|
|
117917
118920
|
try {
|
|
117918
|
-
st =
|
|
118921
|
+
st = stat2(source);
|
|
117919
118922
|
} catch {
|
|
117920
118923
|
st = null;
|
|
117921
118924
|
}
|
|
@@ -117949,18 +118952,18 @@ init_self_update();
|
|
|
117949
118952
|
init_shipped_assets();
|
|
117950
118953
|
|
|
117951
118954
|
// src/cli/self-update-io.ts
|
|
117952
|
-
import { createHash as
|
|
118955
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
117953
118956
|
import {
|
|
117954
118957
|
chmodSync as chmodSync16,
|
|
117955
118958
|
closeSync as closeSync18,
|
|
117956
118959
|
copyFileSync as copyFileSync12,
|
|
117957
118960
|
createWriteStream,
|
|
117958
|
-
existsSync as
|
|
118961
|
+
existsSync as existsSync95,
|
|
117959
118962
|
lstatSync as lstatSync13,
|
|
117960
118963
|
mkdirSync as mkdirSync53,
|
|
117961
118964
|
openSync as openSync18,
|
|
117962
|
-
readdirSync as
|
|
117963
|
-
readFileSync as
|
|
118965
|
+
readdirSync as readdirSync33,
|
|
118966
|
+
readFileSync as readFileSync86,
|
|
117964
118967
|
readSync as readSync6,
|
|
117965
118968
|
renameSync as renameSync23,
|
|
117966
118969
|
rmSync as rmSync18,
|
|
@@ -117999,9 +119002,9 @@ function defaultSelfUpdateIO() {
|
|
|
117999
119002
|
throw new Error(`empty response body for ${url}`);
|
|
118000
119003
|
await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
|
|
118001
119004
|
},
|
|
118002
|
-
sha256File(
|
|
118003
|
-
const hash2 =
|
|
118004
|
-
const fd = openSync18(
|
|
119005
|
+
sha256File(path9) {
|
|
119006
|
+
const hash2 = createHash18("sha256");
|
|
119007
|
+
const fd = openSync18(path9, "r");
|
|
118005
119008
|
try {
|
|
118006
119009
|
const buf = Buffer.allocUnsafe(1 << 20);
|
|
118007
119010
|
for (;; ) {
|
|
@@ -118015,8 +119018,8 @@ function defaultSelfUpdateIO() {
|
|
|
118015
119018
|
}
|
|
118016
119019
|
return hash2.digest("hex");
|
|
118017
119020
|
},
|
|
118018
|
-
probeBinary(
|
|
118019
|
-
const r = spawnSync18(
|
|
119021
|
+
probeBinary(path9) {
|
|
119022
|
+
const r = spawnSync18(path9, ["--version"], {
|
|
118020
119023
|
encoding: "utf-8",
|
|
118021
119024
|
timeout: 30000,
|
|
118022
119025
|
env: { ...process.env, SWITCHROOM_SELF_UPDATED: "" }
|
|
@@ -118056,24 +119059,24 @@ function defaultSelfUpdateIO() {
|
|
|
118056
119059
|
copyFile(src, dest) {
|
|
118057
119060
|
copyFileSync12(src, dest);
|
|
118058
119061
|
},
|
|
118059
|
-
chmodExec(
|
|
118060
|
-
chmodSync16(
|
|
119062
|
+
chmodExec(path9) {
|
|
119063
|
+
chmodSync16(path9, 493);
|
|
118061
119064
|
},
|
|
118062
119065
|
rename(src, dest) {
|
|
118063
119066
|
renameSync23(src, dest);
|
|
118064
119067
|
},
|
|
118065
|
-
remove(
|
|
118066
|
-
rmSync18(
|
|
119068
|
+
remove(path9) {
|
|
119069
|
+
rmSync18(path9, { force: true });
|
|
118067
119070
|
},
|
|
118068
|
-
exists(
|
|
118069
|
-
return
|
|
119071
|
+
exists(path9) {
|
|
119072
|
+
return existsSync95(path9);
|
|
118070
119073
|
},
|
|
118071
|
-
dirname(
|
|
118072
|
-
return dirname39(
|
|
119074
|
+
dirname(path9) {
|
|
119075
|
+
return dirname39(path9);
|
|
118073
119076
|
},
|
|
118074
|
-
readText(
|
|
119077
|
+
readText(path9) {
|
|
118075
119078
|
try {
|
|
118076
|
-
return
|
|
119079
|
+
return readFileSync86(path9, "utf-8");
|
|
118077
119080
|
} catch {
|
|
118078
119081
|
return null;
|
|
118079
119082
|
}
|
|
@@ -118093,19 +119096,19 @@ function defaultSelfUpdateIO() {
|
|
|
118093
119096
|
symlink(target, linkPath) {
|
|
118094
119097
|
symlinkSync5(target, linkPath);
|
|
118095
119098
|
},
|
|
118096
|
-
isSymlink(
|
|
119099
|
+
isSymlink(path9) {
|
|
118097
119100
|
try {
|
|
118098
|
-
return lstatSync13(
|
|
119101
|
+
return lstatSync13(path9).isSymbolicLink();
|
|
118099
119102
|
} catch {
|
|
118100
119103
|
return false;
|
|
118101
119104
|
}
|
|
118102
119105
|
},
|
|
118103
|
-
removeTree(
|
|
118104
|
-
rmSync18(
|
|
119106
|
+
removeTree(path9) {
|
|
119107
|
+
rmSync18(path9, { recursive: true, force: true });
|
|
118105
119108
|
},
|
|
118106
119109
|
listDir(dir) {
|
|
118107
119110
|
try {
|
|
118108
|
-
return
|
|
119111
|
+
return readdirSync33(dir);
|
|
118109
119112
|
} catch {
|
|
118110
119113
|
return [];
|
|
118111
119114
|
}
|
|
@@ -118120,7 +119123,7 @@ init_install_cron();
|
|
|
118120
119123
|
|
|
118121
119124
|
// src/memory/mm-refresh-cron.ts
|
|
118122
119125
|
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
118123
|
-
import { chownSync as chownSync12, existsSync as
|
|
119126
|
+
import { chownSync as chownSync12, existsSync as existsSync96, mkdirSync as mkdirSync54, readFileSync as readFileSync87, renameSync as renameSync24, statSync as statSync56, writeFileSync as writeFileSync37 } from "node:fs";
|
|
118124
119127
|
import { dirname as dirname40 } from "node:path";
|
|
118125
119128
|
var CRON_PATH3 = "/etc/cron.d/mental-model-refresh";
|
|
118126
119129
|
var CRON_SCHEDULE2 = "27 4 * * *";
|
|
@@ -118137,20 +119140,20 @@ function renderCron3(opts) {
|
|
|
118137
119140
|
`;
|
|
118138
119141
|
}
|
|
118139
119142
|
function installCron3(opts) {
|
|
118140
|
-
const
|
|
119143
|
+
const path9 = opts.path ?? CRON_PATH3;
|
|
118141
119144
|
const content = renderCron3(opts);
|
|
118142
|
-
if (
|
|
119145
|
+
if (existsSync96(path9)) {
|
|
118143
119146
|
try {
|
|
118144
|
-
if (
|
|
118145
|
-
return { status: "unchanged", path:
|
|
119147
|
+
if (readFileSync87(path9, "utf8") === content) {
|
|
119148
|
+
return { status: "unchanged", path: path9, content };
|
|
118146
119149
|
}
|
|
118147
119150
|
} catch {}
|
|
118148
119151
|
}
|
|
118149
|
-
mkdirSync54(dirname40(
|
|
118150
|
-
const tmp = `${
|
|
119152
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119153
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118151
119154
|
writeFileSync37(tmp, content, { mode: 420 });
|
|
118152
|
-
renameSync24(tmp,
|
|
118153
|
-
return { status: "installed", path:
|
|
119155
|
+
renameSync24(tmp, path9);
|
|
119156
|
+
return { status: "installed", path: path9, content };
|
|
118154
119157
|
}
|
|
118155
119158
|
var defaultIdResolver3 = (user) => {
|
|
118156
119159
|
const uid = Number(execFileSync30("id", ["-u", user], { encoding: "utf8" }).trim());
|
|
@@ -118161,27 +119164,27 @@ var defaultIdResolver3 = (user) => {
|
|
|
118161
119164
|
return { uid, gid };
|
|
118162
119165
|
};
|
|
118163
119166
|
function ensureLogFile3(opts) {
|
|
118164
|
-
const
|
|
118165
|
-
if (
|
|
118166
|
-
return { status: "unchanged", path:
|
|
119167
|
+
const path9 = opts.path ?? CRON_LOG_PATH3;
|
|
119168
|
+
if (existsSync96(path9))
|
|
119169
|
+
return { status: "unchanged", path: path9 };
|
|
118167
119170
|
const { uid, gid } = (opts.resolveIds ?? defaultIdResolver3)(opts.user);
|
|
118168
|
-
mkdirSync54(dirname40(
|
|
118169
|
-
writeFileSync37(
|
|
119171
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119172
|
+
writeFileSync37(path9, "", { mode: 420, flag: "a" });
|
|
118170
119173
|
const isRoot = process.getuid?.() === 0;
|
|
118171
119174
|
let alreadyOwned = false;
|
|
118172
119175
|
try {
|
|
118173
|
-
const st =
|
|
119176
|
+
const st = statSync56(path9);
|
|
118174
119177
|
alreadyOwned = st.uid === uid && st.gid === gid;
|
|
118175
119178
|
} catch {}
|
|
118176
119179
|
if (isRoot && !alreadyOwned) {
|
|
118177
119180
|
try {
|
|
118178
|
-
chownSync12(
|
|
119181
|
+
chownSync12(path9, uid, gid);
|
|
118179
119182
|
} catch (e) {
|
|
118180
119183
|
if (e.code !== "EPERM")
|
|
118181
119184
|
throw e;
|
|
118182
119185
|
}
|
|
118183
119186
|
}
|
|
118184
|
-
return { status: "created", path:
|
|
119187
|
+
return { status: "created", path: path9 };
|
|
118185
119188
|
}
|
|
118186
119189
|
function renderLogrotate3(opts) {
|
|
118187
119190
|
return `${opts.logPath} {
|
|
@@ -118197,20 +119200,20 @@ function renderLogrotate3(opts) {
|
|
|
118197
119200
|
`;
|
|
118198
119201
|
}
|
|
118199
119202
|
function installLogrotate3(opts) {
|
|
118200
|
-
const
|
|
119203
|
+
const path9 = opts.path ?? LOGROTATE_PATH3;
|
|
118201
119204
|
const content = renderLogrotate3({ logPath: opts.logPath ?? CRON_LOG_PATH3, user: opts.user });
|
|
118202
|
-
if (
|
|
119205
|
+
if (existsSync96(path9)) {
|
|
118203
119206
|
try {
|
|
118204
|
-
if (
|
|
118205
|
-
return { status: "unchanged", path:
|
|
119207
|
+
if (readFileSync87(path9, "utf8") === content) {
|
|
119208
|
+
return { status: "unchanged", path: path9, content };
|
|
118206
119209
|
}
|
|
118207
119210
|
} catch {}
|
|
118208
119211
|
}
|
|
118209
|
-
mkdirSync54(dirname40(
|
|
118210
|
-
const tmp = `${
|
|
119212
|
+
mkdirSync54(dirname40(path9), { recursive: true });
|
|
119213
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118211
119214
|
writeFileSync37(tmp, content, { mode: 420 });
|
|
118212
|
-
renameSync24(tmp,
|
|
118213
|
-
return { status: "installed", path:
|
|
119215
|
+
renameSync24(tmp, path9);
|
|
119216
|
+
return { status: "installed", path: path9, content };
|
|
118214
119217
|
}
|
|
118215
119218
|
function parseCronUser3(content) {
|
|
118216
119219
|
for (const line of content.split(`
|
|
@@ -118227,20 +119230,20 @@ function parseCronUser3(content) {
|
|
|
118227
119230
|
return null;
|
|
118228
119231
|
}
|
|
118229
119232
|
function reconcileCron3(opts) {
|
|
118230
|
-
const
|
|
118231
|
-
if (!
|
|
118232
|
-
return { status: "absent", path:
|
|
119233
|
+
const path9 = opts.path ?? CRON_PATH3;
|
|
119234
|
+
if (!existsSync96(path9))
|
|
119235
|
+
return { status: "absent", path: path9 };
|
|
118233
119236
|
let user = opts.fallbackUser;
|
|
118234
119237
|
try {
|
|
118235
|
-
user = parseCronUser3(
|
|
119238
|
+
user = parseCronUser3(readFileSync87(path9, "utf8")) ?? opts.fallbackUser;
|
|
118236
119239
|
} catch {}
|
|
118237
119240
|
if (!user) {
|
|
118238
|
-
throw new Error(`cannot reconcile ${
|
|
119241
|
+
throw new Error(`cannot reconcile ${path9}: no cron-user parseable from the existing fragment and no fallback user available`);
|
|
118239
119242
|
}
|
|
118240
|
-
const r = installCron3({ user, binary: opts.binary, path:
|
|
119243
|
+
const r = installCron3({ user, binary: opts.binary, path: path9 });
|
|
118241
119244
|
return {
|
|
118242
119245
|
status: r.status === "installed" ? "reconciled" : "unchanged",
|
|
118243
|
-
path:
|
|
119246
|
+
path: path9,
|
|
118244
119247
|
content: r.content
|
|
118245
119248
|
};
|
|
118246
119249
|
}
|
|
@@ -118250,7 +119253,7 @@ init_install_cron2();
|
|
|
118250
119253
|
|
|
118251
119254
|
// src/host-cli-self-heal/install-timer.ts
|
|
118252
119255
|
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
118253
|
-
import { existsSync as
|
|
119256
|
+
import { existsSync as existsSync97, mkdirSync as mkdirSync55, readFileSync as readFileSync88, renameSync as renameSync25, writeFileSync as writeFileSync38 } from "node:fs";
|
|
118254
119257
|
import { dirname as dirname41 } from "node:path";
|
|
118255
119258
|
var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
|
|
118256
119259
|
var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
|
|
@@ -118296,22 +119299,22 @@ function renderTimer() {
|
|
|
118296
119299
|
` + `WantedBy=timers.target
|
|
118297
119300
|
`;
|
|
118298
119301
|
}
|
|
118299
|
-
function writeUnitIdempotent(
|
|
118300
|
-
if (
|
|
119302
|
+
function writeUnitIdempotent(path9, content) {
|
|
119303
|
+
if (existsSync97(path9)) {
|
|
118301
119304
|
try {
|
|
118302
|
-
if (
|
|
119305
|
+
if (readFileSync88(path9, "utf8") === content)
|
|
118303
119306
|
return "unchanged";
|
|
118304
119307
|
} catch {}
|
|
118305
119308
|
}
|
|
118306
|
-
mkdirSync55(dirname41(
|
|
118307
|
-
const tmp = `${
|
|
119309
|
+
mkdirSync55(dirname41(path9), { recursive: true });
|
|
119310
|
+
const tmp = `${path9}.${process.pid}.tmp`;
|
|
118308
119311
|
writeFileSync38(tmp, content, { mode: 420 });
|
|
118309
|
-
renameSync25(tmp,
|
|
119312
|
+
renameSync25(tmp, path9);
|
|
118310
119313
|
return "written";
|
|
118311
119314
|
}
|
|
118312
119315
|
function homeFromPasswd2(user) {
|
|
118313
119316
|
try {
|
|
118314
|
-
for (const line of
|
|
119317
|
+
for (const line of readFileSync88("/etc/passwd", "utf8").split(`
|
|
118315
119318
|
`)) {
|
|
118316
119319
|
const f = line.split(":");
|
|
118317
119320
|
if (f[0] === user && f[5])
|
|
@@ -118328,7 +119331,7 @@ function resolveOperatorUser(env2) {
|
|
|
118328
119331
|
}
|
|
118329
119332
|
function installSelfHealTimer(deps = {}) {
|
|
118330
119333
|
const env2 = deps.env ?? process.env;
|
|
118331
|
-
const systemdBooted = deps.systemdBooted ?? (() =>
|
|
119334
|
+
const systemdBooted = deps.systemdBooted ?? (() => existsSync97(SYSTEMD_MARKER));
|
|
118332
119335
|
const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
|
|
118333
119336
|
const homeForUser = deps.homeForUser ?? homeFromPasswd2;
|
|
118334
119337
|
const servicePath = deps.servicePath ?? SERVICE_PATH;
|
|
@@ -118389,22 +119392,22 @@ function installSelfHealTimer(deps = {}) {
|
|
|
118389
119392
|
// src/cli/update.ts
|
|
118390
119393
|
function defaultPersistPin(configPath) {
|
|
118391
119394
|
return (pin) => {
|
|
118392
|
-
const
|
|
118393
|
-
const before =
|
|
119395
|
+
const path9 = configPath ?? findConfigFile();
|
|
119396
|
+
const before = readFileSync89(path9, "utf8");
|
|
118394
119397
|
const after = setReleasePinInConfig(before, pin);
|
|
118395
119398
|
if (after === before)
|
|
118396
119399
|
return;
|
|
118397
|
-
writeConfigFileSync(
|
|
119400
|
+
writeConfigFileSync(path9, after, statSync57(path9).mode & 511);
|
|
118398
119401
|
try {
|
|
118399
119402
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
118400
119403
|
const uid = resolveOperatorUid();
|
|
118401
119404
|
if (uid !== undefined)
|
|
118402
|
-
chownSync13(
|
|
119405
|
+
chownSync13(path9, uid, uid);
|
|
118403
119406
|
}
|
|
118404
119407
|
} catch {}
|
|
118405
119408
|
};
|
|
118406
119409
|
}
|
|
118407
|
-
var DEFAULT_COMPOSE_PATH2 =
|
|
119410
|
+
var DEFAULT_COMPOSE_PATH2 = join102(homedir51(), ".switchroom", "compose", "docker-compose.yml");
|
|
118408
119411
|
var PACKAGED_INSTALL_KINDS = new Set([
|
|
118409
119412
|
"static-binary",
|
|
118410
119413
|
"npm-global",
|
|
@@ -118413,9 +119416,9 @@ var PACKAGED_INSTALL_KINDS = new Set([
|
|
|
118413
119416
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
118414
119417
|
let dir = dirname42(scriptPath);
|
|
118415
119418
|
for (let i2 = 0;i2 < 12; i2++) {
|
|
118416
|
-
if (
|
|
119419
|
+
if (existsSync98(join102(dir, ".git"))) {
|
|
118417
119420
|
try {
|
|
118418
|
-
const pkg = JSON.parse(
|
|
119421
|
+
const pkg = JSON.parse(readFileSync89(join102(dir, "package.json"), "utf-8"));
|
|
118419
119422
|
if (pkg.name === "switchroom")
|
|
118420
119423
|
return true;
|
|
118421
119424
|
} catch {}
|
|
@@ -118482,7 +119485,7 @@ function planUpdate(opts) {
|
|
|
118482
119485
|
bundleDir: import.meta.dirname,
|
|
118483
119486
|
execPath: process.execPath,
|
|
118484
119487
|
scriptPath,
|
|
118485
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
119488
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
|
|
118486
119489
|
});
|
|
118487
119490
|
if (detection.kind !== "static-binary") {
|
|
118488
119491
|
say(source_default.gray(` host CLI not self-updated: ${detection.reason}
|
|
@@ -118565,7 +119568,7 @@ function planUpdate(opts) {
|
|
|
118565
119568
|
steps.push({
|
|
118566
119569
|
name: "pull-images",
|
|
118567
119570
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
118568
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
119571
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync98(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
118569
119572
|
run: () => {
|
|
118570
119573
|
const r = runner("docker", [
|
|
118571
119574
|
"compose",
|
|
@@ -118822,13 +119825,13 @@ function planUpdate(opts) {
|
|
|
118822
119825
|
bundleDir: import.meta.dirname,
|
|
118823
119826
|
execPath: process.execPath
|
|
118824
119827
|
});
|
|
118825
|
-
const dest =
|
|
119828
|
+
const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
|
|
118826
119829
|
if (resolution.path === null) {
|
|
118827
119830
|
const detection = detectInstallKind(opts.installProbe ?? {
|
|
118828
119831
|
bundleDir: import.meta.dirname,
|
|
118829
119832
|
execPath: process.execPath,
|
|
118830
119833
|
scriptPath,
|
|
118831
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
119834
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync98("/.dockerenv")
|
|
118832
119835
|
});
|
|
118833
119836
|
const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
|
|
118834
119837
|
if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
|
|
@@ -118867,11 +119870,11 @@ function planUpdate(opts) {
|
|
|
118867
119870
|
run: () => {
|
|
118868
119871
|
if (opts.syncBundledSkillsFn)
|
|
118869
119872
|
return;
|
|
118870
|
-
const dest =
|
|
118871
|
-
if (!
|
|
119873
|
+
const dest = join102(homedir51(), ".switchroom", "skills", "_bundled");
|
|
119874
|
+
if (!existsSync98(dest)) {
|
|
118872
119875
|
return;
|
|
118873
119876
|
}
|
|
118874
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
119877
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync98(join102(dest, key)));
|
|
118875
119878
|
if (missing.length > 0) {
|
|
118876
119879
|
throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
|
|
118877
119880
|
}
|
|
@@ -118906,7 +119909,7 @@ function planUpdate(opts) {
|
|
|
118906
119909
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
118907
119910
|
run: () => {
|
|
118908
119911
|
try {
|
|
118909
|
-
const composeText =
|
|
119912
|
+
const composeText = readFileSync89(composePath, "utf8");
|
|
118910
119913
|
const pf = validateBindSources(composeText);
|
|
118911
119914
|
if (!pf.ok)
|
|
118912
119915
|
throw new Error(formatPreflightError(pf));
|
|
@@ -118981,14 +119984,14 @@ function defaultStatusProbe(composePath) {
|
|
|
118981
119984
|
} catch {}
|
|
118982
119985
|
if (scriptPath) {
|
|
118983
119986
|
try {
|
|
118984
|
-
cliBuiltAt = new Date(
|
|
119987
|
+
cliBuiltAt = new Date(statSync57(scriptPath).mtimeMs).toISOString();
|
|
118985
119988
|
} catch {}
|
|
118986
119989
|
let dir = dirname42(scriptPath);
|
|
118987
119990
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
118988
|
-
const pkgPath =
|
|
118989
|
-
if (
|
|
119991
|
+
const pkgPath = join102(dir, "package.json");
|
|
119992
|
+
if (existsSync98(pkgPath)) {
|
|
118990
119993
|
try {
|
|
118991
|
-
const pkg = JSON.parse(
|
|
119994
|
+
const pkg = JSON.parse(readFileSync89(pkgPath, "utf-8"));
|
|
118992
119995
|
if (typeof pkg.version === "string")
|
|
118993
119996
|
cliVersion = pkg.version;
|
|
118994
119997
|
} catch (err) {
|
|
@@ -119009,7 +120012,7 @@ function defaultStatusProbe(composePath) {
|
|
|
119009
120012
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
119010
120013
|
}
|
|
119011
120014
|
const services = [];
|
|
119012
|
-
if (!
|
|
120015
|
+
if (!existsSync98(composePath)) {
|
|
119013
120016
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
119014
120017
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
119015
120018
|
}
|
|
@@ -119272,22 +120275,22 @@ function registerUpdateCommand(program3) {
|
|
|
119272
120275
|
// src/cli/rollout.ts
|
|
119273
120276
|
init_helpers();
|
|
119274
120277
|
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
119275
|
-
import { readFileSync as
|
|
120278
|
+
import { readFileSync as readFileSync91, chownSync as chownSync14, statSync as statSync60 } from "node:fs";
|
|
119276
120279
|
import { homedir as homedir53 } from "node:os";
|
|
119277
120280
|
|
|
119278
120281
|
// src/cli/rollout-pin-journal.ts
|
|
119279
120282
|
import {
|
|
119280
|
-
existsSync as
|
|
119281
|
-
readFileSync as
|
|
120283
|
+
existsSync as existsSync99,
|
|
120284
|
+
readFileSync as readFileSync90,
|
|
119282
120285
|
writeFileSync as writeFileSync39,
|
|
119283
120286
|
renameSync as renameSync26,
|
|
119284
120287
|
unlinkSync as unlinkSync19,
|
|
119285
|
-
statSync as
|
|
120288
|
+
statSync as statSync58,
|
|
119286
120289
|
mkdirSync as mkdirSync57
|
|
119287
120290
|
} from "node:fs";
|
|
119288
120291
|
import { homedir as homedir52 } from "node:os";
|
|
119289
|
-
import { createHash as
|
|
119290
|
-
import { join as
|
|
120292
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
120293
|
+
import { join as join103, basename as basename9, resolve as resolve51, dirname as dirname43 } from "node:path";
|
|
119291
120294
|
init_flock();
|
|
119292
120295
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
119293
120296
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -119296,16 +120299,16 @@ function pinJournalDir(configPath) {
|
|
|
119296
120299
|
if (override && override.trim().length > 0)
|
|
119297
120300
|
return override.trim();
|
|
119298
120301
|
if (configPath) {
|
|
119299
|
-
const dir = dirname43(
|
|
120302
|
+
const dir = dirname43(resolve51(configPath));
|
|
119300
120303
|
if (basename9(dir) === STATE_DIR_NAME)
|
|
119301
120304
|
return dir;
|
|
119302
120305
|
}
|
|
119303
|
-
return
|
|
120306
|
+
return join103(homedir52(), STATE_DIR_NAME);
|
|
119304
120307
|
}
|
|
119305
120308
|
function pinJournalPath(configPath) {
|
|
119306
|
-
const abs =
|
|
119307
|
-
const key =
|
|
119308
|
-
return
|
|
120309
|
+
const abs = resolve51(configPath);
|
|
120310
|
+
const key = createHash19("sha256").update(abs).digest("hex").slice(0, 12);
|
|
120311
|
+
return join103(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
|
|
119309
120312
|
}
|
|
119310
120313
|
function isPidAlive(pid) {
|
|
119311
120314
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -119339,9 +120342,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
119339
120342
|
const p = pinJournalPath(configPath);
|
|
119340
120343
|
let raw;
|
|
119341
120344
|
try {
|
|
119342
|
-
raw =
|
|
120345
|
+
raw = readFileSync90(p, "utf8");
|
|
119343
120346
|
} catch (e) {
|
|
119344
|
-
if (
|
|
120347
|
+
if (existsSync99(p)) {
|
|
119345
120348
|
warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
|
|
119346
120349
|
`);
|
|
119347
120350
|
}
|
|
@@ -119392,7 +120395,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
119392
120395
|
warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
|
|
119393
120396
|
`);
|
|
119394
120397
|
}
|
|
119395
|
-
const priorPin = getReleasePinFromConfig(
|
|
120398
|
+
const priorPin = getReleasePinFromConfig(readFileSync90(configPath, "utf8"));
|
|
119396
120399
|
const journal = {
|
|
119397
120400
|
v: 1,
|
|
119398
120401
|
configPath,
|
|
@@ -119413,7 +120416,7 @@ function commitPinPersist(configPath) {
|
|
|
119413
120416
|
unlinkSync19(p);
|
|
119414
120417
|
return null;
|
|
119415
120418
|
} catch (e) {
|
|
119416
|
-
if (!
|
|
120419
|
+
if (!existsSync99(p))
|
|
119417
120420
|
return null;
|
|
119418
120421
|
return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side \u2014 while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
|
|
119419
120422
|
}
|
|
@@ -119435,11 +120438,11 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
119435
120438
|
}
|
|
119436
120439
|
}
|
|
119437
120440
|
try {
|
|
119438
|
-
const current =
|
|
120441
|
+
const current = readFileSync90(configPath, "utf8");
|
|
119439
120442
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
119440
120443
|
let mode = 384;
|
|
119441
120444
|
try {
|
|
119442
|
-
mode =
|
|
120445
|
+
mode = statSync58(configPath).mode & 511;
|
|
119443
120446
|
} catch {}
|
|
119444
120447
|
if (opts.writeConfig) {
|
|
119445
120448
|
opts.writeConfig(configPath, next, mode);
|
|
@@ -119489,8 +120492,8 @@ init_host_cli_stamp();
|
|
|
119489
120492
|
|
|
119490
120493
|
// src/cli/host-cli-upgrade.ts
|
|
119491
120494
|
init_self_update();
|
|
119492
|
-
import { lchownSync, lstatSync as lstatSync14, readdirSync as
|
|
119493
|
-
import { basename as basename10, dirname as dirname44, join as
|
|
120495
|
+
import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync34, statSync as statSync59 } from "node:fs";
|
|
120496
|
+
import { basename as basename10, dirname as dirname44, join as join104 } from "node:path";
|
|
119494
120497
|
init_shipped_assets();
|
|
119495
120498
|
var HOST_CLI_UPGRADE_SENTINEL = "SWITCHROOM_HOST_CLI_UPGRADE:";
|
|
119496
120499
|
function encodeHostCliUpgradeResult(r) {
|
|
@@ -119526,7 +120529,7 @@ function defaultIo() {
|
|
|
119526
120529
|
return {
|
|
119527
120530
|
isFile: (p) => {
|
|
119528
120531
|
try {
|
|
119529
|
-
return
|
|
120532
|
+
return statSync59(p).isFile();
|
|
119530
120533
|
} catch {
|
|
119531
120534
|
return false;
|
|
119532
120535
|
}
|
|
@@ -119556,7 +120559,7 @@ function defaultIo() {
|
|
|
119556
120559
|
chown: (p, uid, gid) => lchownSync(p, uid, gid),
|
|
119557
120560
|
list: (d) => {
|
|
119558
120561
|
try {
|
|
119559
|
-
return
|
|
120562
|
+
return readdirSync34(d);
|
|
119560
120563
|
} catch {
|
|
119561
120564
|
return [];
|
|
119562
120565
|
}
|
|
@@ -119587,7 +120590,7 @@ function handBackOwnership(paths, owner, io) {
|
|
|
119587
120590
|
if (kind !== "dir")
|
|
119588
120591
|
continue;
|
|
119589
120592
|
for (const child of io.list(p))
|
|
119590
|
-
stack.push(
|
|
120593
|
+
stack.push(join104(p, child));
|
|
119591
120594
|
}
|
|
119592
120595
|
return failures;
|
|
119593
120596
|
}
|
|
@@ -119626,7 +120629,7 @@ async function runHostCliUpgrade(opts, io = defaultIo(), log = () => {}) {
|
|
|
119626
120629
|
const installRoot = payloadInstallRoot(binary);
|
|
119627
120630
|
const failures = handBackOwnership([
|
|
119628
120631
|
binary,
|
|
119629
|
-
|
|
120632
|
+
join104(dirname44(binary), ".switchroom-versions"),
|
|
119630
120633
|
dirname44(installRoot),
|
|
119631
120634
|
installRoot,
|
|
119632
120635
|
payloadVersionDir(installRoot, pin)
|
|
@@ -119722,21 +120725,21 @@ function planHostCliHeal(opts) {
|
|
|
119722
120725
|
reason: `the host CLI is a ${stamp.installKind} install \u2014 only a static-binary ` + `install can be replaced by swapping one file, so an operator must run ` + `the upgrade host-side`
|
|
119723
120726
|
};
|
|
119724
120727
|
}
|
|
119725
|
-
const
|
|
119726
|
-
if (!
|
|
119727
|
-
return { action: "skip", reason: `recorded host CLI path "${
|
|
120728
|
+
const path9 = stamp.path;
|
|
120729
|
+
if (!path9.startsWith("/") || path9.includes("/..")) {
|
|
120730
|
+
return { action: "skip", reason: `recorded host CLI path "${path9}" is not a plain absolute path` };
|
|
119728
120731
|
}
|
|
119729
|
-
const installDir =
|
|
119730
|
-
const name =
|
|
120732
|
+
const installDir = path9.slice(0, path9.lastIndexOf("/"));
|
|
120733
|
+
const name = path9.slice(path9.lastIndexOf("/") + 1);
|
|
119731
120734
|
if (name !== "switchroom") {
|
|
119732
|
-
return { action: "skip", reason: `recorded host CLI path "${
|
|
120735
|
+
return { action: "skip", reason: `recorded host CLI path "${path9}" is not named \`switchroom\`` };
|
|
119733
120736
|
}
|
|
119734
120737
|
const prefixHostPath = installDir.slice(0, installDir.lastIndexOf("/"));
|
|
119735
120738
|
const dirName = installDir.slice(installDir.lastIndexOf("/") + 1);
|
|
119736
120739
|
if (prefixHostPath.length === 0 || dirName.length === 0) {
|
|
119737
120740
|
return {
|
|
119738
120741
|
action: "skip",
|
|
119739
|
-
reason: `recorded host CLI path "${
|
|
120742
|
+
reason: `recorded host CLI path "${path9}" has no install prefix to bind \u2014 ` + `refusing to mount the host root`
|
|
119740
120743
|
};
|
|
119741
120744
|
}
|
|
119742
120745
|
return {
|
|
@@ -120421,13 +121424,13 @@ function createRolloutDeps(params) {
|
|
|
120421
121424
|
}
|
|
120422
121425
|
return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
120423
121426
|
};
|
|
120424
|
-
const writeConfigPreservingOwnership = (
|
|
120425
|
-
writeConfigFileSync(
|
|
121427
|
+
const writeConfigPreservingOwnership = (path9, text, mode) => {
|
|
121428
|
+
writeConfigFileSync(path9, text, mode);
|
|
120426
121429
|
try {
|
|
120427
121430
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
120428
121431
|
const uid = resolveOperatorUid();
|
|
120429
121432
|
if (uid !== undefined) {
|
|
120430
|
-
chownSync14(
|
|
121433
|
+
chownSync14(path9, uid, uid);
|
|
120431
121434
|
} else if (hostdCtx) {
|
|
120432
121435
|
warn("\u26a0\ufe0f rollout (hostd path): could not resolve operator uid; " + "skipping config chown-back to avoid leaving switchroom.yaml " + "root-owned. Verify ownership host-side if other yaml verbs " + `EACCES.
|
|
120433
121436
|
`);
|
|
@@ -120486,12 +121489,12 @@ function createRolloutDeps(params) {
|
|
|
120486
121489
|
return stamp ? { hostCli: stamp } : {};
|
|
120487
121490
|
})(),
|
|
120488
121491
|
persistPin: (pin) => {
|
|
120489
|
-
const before =
|
|
121492
|
+
const before = readFileSync91(configPath, "utf8");
|
|
120490
121493
|
const after = setReleasePinInConfig(before, pin);
|
|
120491
121494
|
if (after === before)
|
|
120492
121495
|
return false;
|
|
120493
121496
|
beginPinPersist(configPath, pin);
|
|
120494
|
-
writeConfigPreservingOwnership(configPath, after,
|
|
121497
|
+
writeConfigPreservingOwnership(configPath, after, statSync60(configPath).mode & 511);
|
|
120495
121498
|
return true;
|
|
120496
121499
|
},
|
|
120497
121500
|
commitPin: () => {
|
|
@@ -120767,7 +121770,7 @@ init_source();
|
|
|
120767
121770
|
init_helpers();
|
|
120768
121771
|
init_loader();
|
|
120769
121772
|
init_lifecycle();
|
|
120770
|
-
import { resolve as
|
|
121773
|
+
import { resolve as resolve52 } from "node:path";
|
|
120771
121774
|
|
|
120772
121775
|
// src/cli/version.ts
|
|
120773
121776
|
init_source();
|
|
@@ -120775,8 +121778,8 @@ init_helpers();
|
|
|
120775
121778
|
init_lifecycle();
|
|
120776
121779
|
init_resolve_version();
|
|
120777
121780
|
import { execSync as execSync3 } from "node:child_process";
|
|
120778
|
-
import { existsSync as
|
|
120779
|
-
import { dirname as dirname45, join as
|
|
121781
|
+
import { existsSync as existsSync100, readFileSync as readFileSync92 } from "node:fs";
|
|
121782
|
+
import { dirname as dirname45, join as join105 } from "node:path";
|
|
120780
121783
|
function getClaudeCodeVersion() {
|
|
120781
121784
|
try {
|
|
120782
121785
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -120826,11 +121829,11 @@ function formatUptime3(timestamp) {
|
|
|
120826
121829
|
function locateSwitchroomInstallDir() {
|
|
120827
121830
|
let dir = import.meta.dirname;
|
|
120828
121831
|
for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
|
|
120829
|
-
const pkgPath =
|
|
120830
|
-
if (
|
|
121832
|
+
const pkgPath = join105(dir, "package.json");
|
|
121833
|
+
if (existsSync100(pkgPath)) {
|
|
120831
121834
|
try {
|
|
120832
|
-
const pkg = JSON.parse(
|
|
120833
|
-
if (pkg.name === "switchroom" &&
|
|
121835
|
+
const pkg = JSON.parse(readFileSync92(pkgPath, "utf-8"));
|
|
121836
|
+
if (pkg.name === "switchroom" && existsSync100(join105(dir, ".git"))) {
|
|
120834
121837
|
return dir;
|
|
120835
121838
|
}
|
|
120836
121839
|
} catch {}
|
|
@@ -120923,7 +121926,7 @@ function registerRestartCommand(program3) {
|
|
|
120923
121926
|
}
|
|
120924
121927
|
const didRestart = res.restarted || !graceful;
|
|
120925
121928
|
if (didRestart) {
|
|
120926
|
-
const agentDir =
|
|
121929
|
+
const agentDir = resolve52(agentsDir, name);
|
|
120927
121930
|
const converged = waitForAuthConverge(name, agentDir);
|
|
120928
121931
|
if (!converged) {
|
|
120929
121932
|
console.log(source_default.yellow(` ${name}: agent is up but auth status didn't converge in 30s \u2014 check logs`));
|
|
@@ -121006,37 +122009,37 @@ Dependency manifest`));
|
|
|
121006
122009
|
// src/cli/handoff.ts
|
|
121007
122010
|
init_helpers();
|
|
121008
122011
|
init_loader();
|
|
121009
|
-
import { resolve as
|
|
122012
|
+
import { resolve as resolve53 } from "node:path";
|
|
121010
122013
|
init_merge();
|
|
121011
122014
|
|
|
121012
122015
|
// src/agents/session-retention.ts
|
|
121013
122016
|
import {
|
|
121014
|
-
existsSync as
|
|
121015
|
-
readdirSync as
|
|
121016
|
-
statSync as
|
|
122017
|
+
existsSync as existsSync101,
|
|
122018
|
+
readdirSync as readdirSync35,
|
|
122019
|
+
statSync as statSync61,
|
|
121017
122020
|
unlinkSync as unlinkSync20
|
|
121018
122021
|
} from "node:fs";
|
|
121019
|
-
import { join as
|
|
122022
|
+
import { join as join106 } from "node:path";
|
|
121020
122023
|
var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
|
|
121021
122024
|
var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
121022
122025
|
var MIN_KEEP = 2;
|
|
121023
122026
|
function collectSessionJsonl(claudeConfigDir) {
|
|
121024
|
-
const projects =
|
|
121025
|
-
if (!
|
|
122027
|
+
const projects = join106(claudeConfigDir, "projects");
|
|
122028
|
+
if (!existsSync101(projects))
|
|
121026
122029
|
return [];
|
|
121027
122030
|
const found = [];
|
|
121028
122031
|
const walk2 = (dir) => {
|
|
121029
122032
|
let entries;
|
|
121030
122033
|
try {
|
|
121031
|
-
entries =
|
|
122034
|
+
entries = readdirSync35(dir);
|
|
121032
122035
|
} catch {
|
|
121033
122036
|
return;
|
|
121034
122037
|
}
|
|
121035
122038
|
for (const name of entries) {
|
|
121036
|
-
const full =
|
|
122039
|
+
const full = join106(dir, name);
|
|
121037
122040
|
let st;
|
|
121038
122041
|
try {
|
|
121039
|
-
st =
|
|
122042
|
+
st = statSync61(full);
|
|
121040
122043
|
} catch {
|
|
121041
122044
|
continue;
|
|
121042
122045
|
}
|
|
@@ -121107,7 +122110,7 @@ function registerHandoffCommand(program3) {
|
|
|
121107
122110
|
}
|
|
121108
122111
|
agentConfig = resolveAgentConfig(config.defaults, config.profiles, rawAgent);
|
|
121109
122112
|
const agentsDir = resolveAgentsDir(config);
|
|
121110
|
-
agentDir =
|
|
122113
|
+
agentDir = resolve53(agentsDir, agentName);
|
|
121111
122114
|
} catch (err) {
|
|
121112
122115
|
if (!(err instanceof ConfigError))
|
|
121113
122116
|
throw err;
|
|
@@ -121122,7 +122125,7 @@ function registerHandoffCommand(program3) {
|
|
|
121122
122125
|
`);
|
|
121123
122126
|
return;
|
|
121124
122127
|
}
|
|
121125
|
-
const claudeConfigDir =
|
|
122128
|
+
const claudeConfigDir = resolve53(agentDir, ".claude");
|
|
121126
122129
|
const jsonl = findLatestSessionJsonl(claudeConfigDir);
|
|
121127
122130
|
if (!jsonl) {
|
|
121128
122131
|
process.stderr.write(`handoff: no session JSONL under ${claudeConfigDir}/projects; skipping
|
|
@@ -121154,18 +122157,18 @@ function registerHandoffCommand(program3) {
|
|
|
121154
122157
|
// src/issues/store.ts
|
|
121155
122158
|
import {
|
|
121156
122159
|
closeSync as closeSync19,
|
|
121157
|
-
existsSync as
|
|
122160
|
+
existsSync as existsSync102,
|
|
121158
122161
|
mkdirSync as mkdirSync58,
|
|
121159
122162
|
openSync as openSync19,
|
|
121160
|
-
readdirSync as
|
|
121161
|
-
readFileSync as
|
|
122163
|
+
readdirSync as readdirSync36,
|
|
122164
|
+
readFileSync as readFileSync93,
|
|
121162
122165
|
renameSync as renameSync27,
|
|
121163
|
-
statSync as
|
|
122166
|
+
statSync as statSync62,
|
|
121164
122167
|
unlinkSync as unlinkSync21,
|
|
121165
122168
|
writeFileSync as writeFileSync40,
|
|
121166
122169
|
writeSync as writeSync9
|
|
121167
122170
|
} from "node:fs";
|
|
121168
|
-
import { join as
|
|
122171
|
+
import { join as join107 } from "node:path";
|
|
121169
122172
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
121170
122173
|
import { execSync as execSync4 } from "node:child_process";
|
|
121171
122174
|
|
|
@@ -121192,12 +122195,12 @@ function computeFingerprint(source, code) {
|
|
|
121192
122195
|
var ISSUES_FILE = "issues.jsonl";
|
|
121193
122196
|
var ISSUES_LOCK = "issues.lock";
|
|
121194
122197
|
function readAll(stateDir) {
|
|
121195
|
-
const
|
|
121196
|
-
if (!
|
|
122198
|
+
const path9 = join107(stateDir, ISSUES_FILE);
|
|
122199
|
+
if (!existsSync102(path9))
|
|
121197
122200
|
return [];
|
|
121198
122201
|
let raw;
|
|
121199
122202
|
try {
|
|
121200
|
-
raw =
|
|
122203
|
+
raw = readFileSync93(path9, "utf-8");
|
|
121201
122204
|
} catch {
|
|
121202
122205
|
return [];
|
|
121203
122206
|
}
|
|
@@ -121269,8 +122272,8 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
121269
122272
|
return result;
|
|
121270
122273
|
});
|
|
121271
122274
|
}
|
|
121272
|
-
function
|
|
121273
|
-
if (!
|
|
122275
|
+
function resolve54(stateDir, fingerprint, nowFn = Date.now) {
|
|
122276
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121274
122277
|
return 0;
|
|
121275
122278
|
return withLock(stateDir, () => {
|
|
121276
122279
|
const all = readAll(stateDir);
|
|
@@ -121288,7 +122291,7 @@ function resolve53(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
121288
122291
|
});
|
|
121289
122292
|
}
|
|
121290
122293
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
121291
|
-
if (!
|
|
122294
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121292
122295
|
return 0;
|
|
121293
122296
|
return withLock(stateDir, () => {
|
|
121294
122297
|
const all = readAll(stateDir);
|
|
@@ -121306,7 +122309,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
121306
122309
|
});
|
|
121307
122310
|
}
|
|
121308
122311
|
function prune(stateDir, opts = {}) {
|
|
121309
|
-
if (!
|
|
122312
|
+
if (!existsSync102(join107(stateDir, ISSUES_FILE)))
|
|
121310
122313
|
return 0;
|
|
121311
122314
|
return withLock(stateDir, () => {
|
|
121312
122315
|
const all = readAll(stateDir);
|
|
@@ -121339,21 +122342,21 @@ function ensureDir2(stateDir) {
|
|
|
121339
122342
|
mkdirSync58(stateDir, { recursive: true });
|
|
121340
122343
|
}
|
|
121341
122344
|
function writeAll(stateDir, events) {
|
|
121342
|
-
const
|
|
122345
|
+
const path9 = join107(stateDir, ISSUES_FILE);
|
|
121343
122346
|
sweepOrphanTmpFiles(stateDir);
|
|
121344
|
-
const tmp = `${
|
|
122347
|
+
const tmp = `${path9}.tmp-${process.pid}-${randomBytes16(4).toString("hex")}`;
|
|
121345
122348
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
121346
122349
|
`) + `
|
|
121347
122350
|
`;
|
|
121348
122351
|
writeFileSync40(tmp, body, "utf-8");
|
|
121349
|
-
renameSync27(tmp,
|
|
122352
|
+
renameSync27(tmp, path9);
|
|
121350
122353
|
}
|
|
121351
122354
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
121352
122355
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
121353
122356
|
function sweepOrphanTmpFiles(stateDir) {
|
|
121354
122357
|
let entries;
|
|
121355
122358
|
try {
|
|
121356
|
-
entries =
|
|
122359
|
+
entries = readdirSync36(stateDir);
|
|
121357
122360
|
} catch {
|
|
121358
122361
|
return;
|
|
121359
122362
|
}
|
|
@@ -121361,10 +122364,10 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
121361
122364
|
for (const entry of entries) {
|
|
121362
122365
|
if (!entry.startsWith(TMP_PREFIX))
|
|
121363
122366
|
continue;
|
|
121364
|
-
const tmpPath =
|
|
122367
|
+
const tmpPath = join107(stateDir, entry);
|
|
121365
122368
|
try {
|
|
121366
|
-
const
|
|
121367
|
-
if (
|
|
122369
|
+
const stat2 = statSync62(tmpPath);
|
|
122370
|
+
if (stat2.mtimeMs < cutoff) {
|
|
121368
122371
|
unlinkSync21(tmpPath);
|
|
121369
122372
|
}
|
|
121370
122373
|
} catch {}
|
|
@@ -121373,7 +122376,7 @@ function sweepOrphanTmpFiles(stateDir) {
|
|
|
121373
122376
|
var LOCK_RETRY_MS = 25;
|
|
121374
122377
|
var LOCK_TIMEOUT_MS = 1e4;
|
|
121375
122378
|
function withLock(stateDir, fn) {
|
|
121376
|
-
const lockPath =
|
|
122379
|
+
const lockPath = join107(stateDir, ISSUES_LOCK);
|
|
121377
122380
|
const startedAt = Date.now();
|
|
121378
122381
|
let fd = null;
|
|
121379
122382
|
while (fd === null) {
|
|
@@ -121408,7 +122411,7 @@ function withLock(stateDir, fn) {
|
|
|
121408
122411
|
function tryStealStaleLock(lockPath) {
|
|
121409
122412
|
let pidStr;
|
|
121410
122413
|
try {
|
|
121411
|
-
pidStr =
|
|
122414
|
+
pidStr = readFileSync93(lockPath, "utf-8").trim();
|
|
121412
122415
|
} catch {
|
|
121413
122416
|
return true;
|
|
121414
122417
|
}
|
|
@@ -121557,11 +122560,11 @@ function registerIssuesCommand(program3) {
|
|
|
121557
122560
|
const stateDir = resolveStateDir2(opts);
|
|
121558
122561
|
let flipped;
|
|
121559
122562
|
if (opts.source && opts.code) {
|
|
121560
|
-
flipped =
|
|
122563
|
+
flipped = resolve54(stateDir, computeFingerprint(opts.source, opts.code));
|
|
121561
122564
|
} else if (opts.source && !fingerprint) {
|
|
121562
122565
|
flipped = resolveAllBySource(stateDir, opts.source);
|
|
121563
122566
|
} else if (fingerprint) {
|
|
121564
|
-
flipped =
|
|
122567
|
+
flipped = resolve54(stateDir, fingerprint);
|
|
121565
122568
|
} else {
|
|
121566
122569
|
process.stderr.write(`issues resolve: need either <fingerprint>, --source, or --source + --code
|
|
121567
122570
|
`);
|
|
@@ -121656,20 +122659,20 @@ function relTime(deltaMs) {
|
|
|
121656
122659
|
|
|
121657
122660
|
// src/cli/deps.ts
|
|
121658
122661
|
init_source();
|
|
121659
|
-
import { existsSync as
|
|
122662
|
+
import { existsSync as existsSync105 } from "node:fs";
|
|
121660
122663
|
import { homedir as homedir56 } from "node:os";
|
|
121661
|
-
import { join as
|
|
122664
|
+
import { join as join110, resolve as resolve55 } from "node:path";
|
|
121662
122665
|
|
|
121663
122666
|
// src/deps/python.ts
|
|
121664
|
-
import { createHash as
|
|
122667
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
121665
122668
|
import {
|
|
121666
|
-
existsSync as
|
|
122669
|
+
existsSync as existsSync103,
|
|
121667
122670
|
mkdirSync as mkdirSync59,
|
|
121668
|
-
readFileSync as
|
|
122671
|
+
readFileSync as readFileSync94,
|
|
121669
122672
|
rmSync as rmSync19,
|
|
121670
122673
|
writeFileSync as writeFileSync41
|
|
121671
122674
|
} from "node:fs";
|
|
121672
|
-
import { dirname as dirname46, join as
|
|
122675
|
+
import { dirname as dirname46, join as join108 } from "node:path";
|
|
121673
122676
|
import { homedir as homedir54 } from "node:os";
|
|
121674
122677
|
import { execFileSync as execFileSync31 } from "node:child_process";
|
|
121675
122678
|
|
|
@@ -121682,26 +122685,26 @@ class PythonEnvError extends Error {
|
|
|
121682
122685
|
}
|
|
121683
122686
|
}
|
|
121684
122687
|
function defaultPythonCacheRoot() {
|
|
121685
|
-
return
|
|
122688
|
+
return join108(homedir54(), ".switchroom", "deps", "python");
|
|
121686
122689
|
}
|
|
121687
|
-
function hashFile(
|
|
121688
|
-
return
|
|
122690
|
+
function hashFile(path9) {
|
|
122691
|
+
return createHash20("sha256").update(readFileSync94(path9)).digest("hex");
|
|
121689
122692
|
}
|
|
121690
122693
|
function ensurePythonEnv(opts) {
|
|
121691
122694
|
const { skillName, requirementsPath, force = false } = opts;
|
|
121692
122695
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
121693
122696
|
const hostPython = opts.pythonBin ?? "python3";
|
|
121694
|
-
if (!
|
|
122697
|
+
if (!existsSync103(requirementsPath)) {
|
|
121695
122698
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
121696
122699
|
}
|
|
121697
|
-
const venvDir =
|
|
121698
|
-
const stampPath =
|
|
121699
|
-
const binDir =
|
|
121700
|
-
const pythonBin =
|
|
121701
|
-
const pipBin =
|
|
122700
|
+
const venvDir = join108(cacheRoot, skillName);
|
|
122701
|
+
const stampPath = join108(venvDir, ".requirements.sha256");
|
|
122702
|
+
const binDir = join108(venvDir, "bin");
|
|
122703
|
+
const pythonBin = join108(binDir, "python");
|
|
122704
|
+
const pipBin = join108(binDir, "pip");
|
|
121702
122705
|
const targetHash = hashFile(requirementsPath);
|
|
121703
|
-
if (!force &&
|
|
121704
|
-
const existingHash =
|
|
122706
|
+
if (!force && existsSync103(stampPath) && existsSync103(pythonBin)) {
|
|
122707
|
+
const existingHash = readFileSync94(stampPath, "utf8").trim();
|
|
121705
122708
|
if (existingHash === targetHash) {
|
|
121706
122709
|
return {
|
|
121707
122710
|
skillName,
|
|
@@ -121713,7 +122716,7 @@ function ensurePythonEnv(opts) {
|
|
|
121713
122716
|
};
|
|
121714
122717
|
}
|
|
121715
122718
|
}
|
|
121716
|
-
if (
|
|
122719
|
+
if (existsSync103(venvDir)) {
|
|
121717
122720
|
rmSync19(venvDir, { recursive: true, force: true });
|
|
121718
122721
|
}
|
|
121719
122722
|
mkdirSync59(dirname46(venvDir), { recursive: true });
|
|
@@ -121748,16 +122751,16 @@ function ensurePythonEnv(opts) {
|
|
|
121748
122751
|
}
|
|
121749
122752
|
|
|
121750
122753
|
// src/deps/node.ts
|
|
121751
|
-
import { createHash as
|
|
122754
|
+
import { createHash as createHash21 } from "node:crypto";
|
|
121752
122755
|
import {
|
|
121753
122756
|
copyFileSync as copyFileSync13,
|
|
121754
|
-
existsSync as
|
|
122757
|
+
existsSync as existsSync104,
|
|
121755
122758
|
mkdirSync as mkdirSync60,
|
|
121756
|
-
readFileSync as
|
|
122759
|
+
readFileSync as readFileSync95,
|
|
121757
122760
|
rmSync as rmSync20,
|
|
121758
122761
|
writeFileSync as writeFileSync42
|
|
121759
122762
|
} from "node:fs";
|
|
121760
|
-
import { dirname as dirname47, join as
|
|
122763
|
+
import { dirname as dirname47, join as join109 } from "node:path";
|
|
121761
122764
|
import { homedir as homedir55 } from "node:os";
|
|
121762
122765
|
import { execFileSync as execFileSync32 } from "node:child_process";
|
|
121763
122766
|
|
|
@@ -121781,23 +122784,23 @@ var LOCKFILES_FOR = {
|
|
|
121781
122784
|
npm: ["package-lock.json"]
|
|
121782
122785
|
};
|
|
121783
122786
|
function defaultNodeCacheRoot() {
|
|
121784
|
-
return
|
|
122787
|
+
return join109(homedir55(), ".switchroom", "deps", "node");
|
|
121785
122788
|
}
|
|
121786
122789
|
function hashDepInputs(packageJsonPath) {
|
|
121787
122790
|
const sourceDir = dirname47(packageJsonPath);
|
|
121788
|
-
const hasher =
|
|
122791
|
+
const hasher = createHash21("sha256");
|
|
121789
122792
|
hasher.update(`package.json
|
|
121790
122793
|
`);
|
|
121791
|
-
hasher.update(
|
|
122794
|
+
hasher.update(readFileSync95(packageJsonPath));
|
|
121792
122795
|
for (const lockName of ALL_LOCKFILES) {
|
|
121793
|
-
const lockPath =
|
|
121794
|
-
if (
|
|
122796
|
+
const lockPath = join109(sourceDir, lockName);
|
|
122797
|
+
if (existsSync104(lockPath)) {
|
|
121795
122798
|
hasher.update(`
|
|
121796
122799
|
`);
|
|
121797
122800
|
hasher.update(lockName);
|
|
121798
122801
|
hasher.update(`
|
|
121799
122802
|
`);
|
|
121800
|
-
hasher.update(
|
|
122803
|
+
hasher.update(readFileSync95(lockPath));
|
|
121801
122804
|
}
|
|
121802
122805
|
}
|
|
121803
122806
|
return hasher.digest("hex");
|
|
@@ -121806,17 +122809,17 @@ function ensureNodeEnv(opts) {
|
|
|
121806
122809
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
121807
122810
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
121808
122811
|
const installer = opts.installer ?? "bun";
|
|
121809
|
-
if (!
|
|
122812
|
+
if (!existsSync104(packageJsonPath)) {
|
|
121810
122813
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
121811
122814
|
}
|
|
121812
122815
|
const sourceDir = dirname47(packageJsonPath);
|
|
121813
|
-
const envDir =
|
|
121814
|
-
const stampPath =
|
|
121815
|
-
const nodeModulesDir =
|
|
121816
|
-
const binDir =
|
|
122816
|
+
const envDir = join109(cacheRoot, skillName);
|
|
122817
|
+
const stampPath = join109(envDir, ".package.sha256");
|
|
122818
|
+
const nodeModulesDir = join109(envDir, "node_modules");
|
|
122819
|
+
const binDir = join109(nodeModulesDir, ".bin");
|
|
121817
122820
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
121818
|
-
if (!force &&
|
|
121819
|
-
const existingHash =
|
|
122821
|
+
if (!force && existsSync104(stampPath) && existsSync104(nodeModulesDir)) {
|
|
122822
|
+
const existingHash = readFileSync95(stampPath, "utf8").trim();
|
|
121820
122823
|
if (existingHash === targetHash) {
|
|
121821
122824
|
return {
|
|
121822
122825
|
skillName,
|
|
@@ -121827,16 +122830,16 @@ function ensureNodeEnv(opts) {
|
|
|
121827
122830
|
};
|
|
121828
122831
|
}
|
|
121829
122832
|
}
|
|
121830
|
-
if (
|
|
122833
|
+
if (existsSync104(envDir)) {
|
|
121831
122834
|
rmSync20(envDir, { recursive: true, force: true });
|
|
121832
122835
|
}
|
|
121833
122836
|
mkdirSync60(envDir, { recursive: true });
|
|
121834
|
-
copyFileSync13(packageJsonPath,
|
|
122837
|
+
copyFileSync13(packageJsonPath, join109(envDir, "package.json"));
|
|
121835
122838
|
let copiedLockfile = false;
|
|
121836
122839
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
121837
|
-
const lockPath =
|
|
121838
|
-
if (
|
|
121839
|
-
copyFileSync13(lockPath,
|
|
122840
|
+
const lockPath = join109(sourceDir, lockName);
|
|
122841
|
+
if (existsSync104(lockPath)) {
|
|
122842
|
+
copyFileSync13(lockPath, join109(envDir, lockName));
|
|
121840
122843
|
copiedLockfile = true;
|
|
121841
122844
|
}
|
|
121842
122845
|
}
|
|
@@ -121865,28 +122868,28 @@ function ensureNodeEnv(opts) {
|
|
|
121865
122868
|
|
|
121866
122869
|
// src/cli/deps.ts
|
|
121867
122870
|
function builtinSkillsRoot() {
|
|
121868
|
-
return
|
|
122871
|
+
return resolve55(homedir56(), ".switchroom/skills/_bundled");
|
|
121869
122872
|
}
|
|
121870
122873
|
function registerDepsCommand(program3) {
|
|
121871
122874
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
121872
122875
|
deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
|
|
121873
122876
|
const skillsRoot = builtinSkillsRoot();
|
|
121874
|
-
if (!
|
|
122877
|
+
if (!existsSync105(skillsRoot)) {
|
|
121875
122878
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
121876
122879
|
process.exit(1);
|
|
121877
122880
|
}
|
|
121878
|
-
const skillDir =
|
|
121879
|
-
if (!
|
|
122881
|
+
const skillDir = join110(skillsRoot, skill);
|
|
122882
|
+
if (!existsSync105(skillDir)) {
|
|
121880
122883
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
121881
122884
|
process.exit(1);
|
|
121882
122885
|
}
|
|
121883
|
-
const requirementsPath =
|
|
121884
|
-
const packageJsonPath =
|
|
121885
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
121886
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
122886
|
+
const requirementsPath = join110(skillDir, "requirements.txt");
|
|
122887
|
+
const packageJsonPath = join110(skillDir, "package.json");
|
|
122888
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync105(requirementsPath));
|
|
122889
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync105(packageJsonPath));
|
|
121887
122890
|
let did = 0;
|
|
121888
122891
|
if (wantPython) {
|
|
121889
|
-
if (!
|
|
122892
|
+
if (!existsSync105(requirementsPath)) {
|
|
121890
122893
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
121891
122894
|
process.exit(1);
|
|
121892
122895
|
}
|
|
@@ -121910,7 +122913,7 @@ function registerDepsCommand(program3) {
|
|
|
121910
122913
|
}
|
|
121911
122914
|
}
|
|
121912
122915
|
if (wantNode) {
|
|
121913
|
-
if (!
|
|
122916
|
+
if (!existsSync105(packageJsonPath)) {
|
|
121914
122917
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
121915
122918
|
process.exit(1);
|
|
121916
122919
|
}
|
|
@@ -121943,436 +122946,10 @@ function registerDepsCommand(program3) {
|
|
|
121943
122946
|
// src/cli/workspace.ts
|
|
121944
122947
|
init_helpers();
|
|
121945
122948
|
init_loader();
|
|
121946
|
-
import { existsSync as
|
|
121947
|
-
import { resolve as
|
|
122949
|
+
import { existsSync as existsSync106 } from "node:fs";
|
|
122950
|
+
import { resolve as resolve56, sep as sep7 } from "node:path";
|
|
121948
122951
|
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
121949
122952
|
|
|
121950
|
-
// src/agents/workspace.ts
|
|
121951
|
-
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
121952
|
-
import path8 from "node:path";
|
|
121953
|
-
|
|
121954
|
-
// src/agents/bootstrap-budget.ts
|
|
121955
|
-
import path7 from "node:path";
|
|
121956
|
-
var DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO = 0.85;
|
|
121957
|
-
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES = 3;
|
|
121958
|
-
var DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX = 32;
|
|
121959
|
-
|
|
121960
|
-
class BootstrapBudgetExceededError extends Error {
|
|
121961
|
-
analysis;
|
|
121962
|
-
constructor(analysis, message) {
|
|
121963
|
-
super(message);
|
|
121964
|
-
this.analysis = analysis;
|
|
121965
|
-
this.name = "BootstrapBudgetExceededError";
|
|
121966
|
-
}
|
|
121967
|
-
}
|
|
121968
|
-
function normalizePositiveLimit(value) {
|
|
121969
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
121970
|
-
return 1;
|
|
121971
|
-
}
|
|
121972
|
-
return Math.floor(value);
|
|
121973
|
-
}
|
|
121974
|
-
function formatWarningCause(cause) {
|
|
121975
|
-
return cause === "per-file-limit" ? "max/file" : "max/total";
|
|
121976
|
-
}
|
|
121977
|
-
function normalizeSeenSignatures(signatures) {
|
|
121978
|
-
if (!Array.isArray(signatures) || signatures.length === 0) {
|
|
121979
|
-
return [];
|
|
121980
|
-
}
|
|
121981
|
-
const seen = new Set;
|
|
121982
|
-
const result = [];
|
|
121983
|
-
for (const signature of signatures) {
|
|
121984
|
-
const value = typeof signature === "string" ? signature.trim() : "";
|
|
121985
|
-
if (!value || seen.has(value)) {
|
|
121986
|
-
continue;
|
|
121987
|
-
}
|
|
121988
|
-
seen.add(value);
|
|
121989
|
-
result.push(value);
|
|
121990
|
-
}
|
|
121991
|
-
return result;
|
|
121992
|
-
}
|
|
121993
|
-
function appendSeenSignature(signatures, signature) {
|
|
121994
|
-
if (!signature.trim()) {
|
|
121995
|
-
return signatures;
|
|
121996
|
-
}
|
|
121997
|
-
if (signatures.includes(signature)) {
|
|
121998
|
-
return signatures;
|
|
121999
|
-
}
|
|
122000
|
-
const next = [...signatures, signature];
|
|
122001
|
-
if (next.length <= DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX) {
|
|
122002
|
-
return next;
|
|
122003
|
-
}
|
|
122004
|
-
return next.slice(-DEFAULT_BOOTSTRAP_PROMPT_WARNING_SIGNATURE_HISTORY_MAX);
|
|
122005
|
-
}
|
|
122006
|
-
function buildBootstrapInjectionStats(params) {
|
|
122007
|
-
const injectedByPath = new Map;
|
|
122008
|
-
const injectedByBaseName = new Map;
|
|
122009
|
-
for (const file of params.injectedFiles) {
|
|
122010
|
-
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
122011
|
-
if (!pathValue) {
|
|
122012
|
-
continue;
|
|
122013
|
-
}
|
|
122014
|
-
if (!injectedByPath.has(pathValue)) {
|
|
122015
|
-
injectedByPath.set(pathValue, file.content);
|
|
122016
|
-
}
|
|
122017
|
-
const normalizedPath = pathValue.replace(/\\/g, "/");
|
|
122018
|
-
const baseName = path7.posix.basename(normalizedPath);
|
|
122019
|
-
if (!injectedByBaseName.has(baseName)) {
|
|
122020
|
-
injectedByBaseName.set(baseName, file.content);
|
|
122021
|
-
}
|
|
122022
|
-
}
|
|
122023
|
-
return params.bootstrapFiles.map((file) => {
|
|
122024
|
-
const pathValue = typeof file.path === "string" ? file.path.trim() : "";
|
|
122025
|
-
const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length;
|
|
122026
|
-
const injected = (pathValue ? injectedByPath.get(pathValue) : undefined) ?? injectedByPath.get(file.name) ?? injectedByBaseName.get(file.name);
|
|
122027
|
-
const injectedChars = injected ? injected.length : 0;
|
|
122028
|
-
const truncated = !file.missing && injectedChars < rawChars;
|
|
122029
|
-
return {
|
|
122030
|
-
name: file.name,
|
|
122031
|
-
path: pathValue || file.name,
|
|
122032
|
-
missing: file.missing,
|
|
122033
|
-
rawChars,
|
|
122034
|
-
injectedChars,
|
|
122035
|
-
truncated
|
|
122036
|
-
};
|
|
122037
|
-
});
|
|
122038
|
-
}
|
|
122039
|
-
function analyzeBootstrapBudget(params) {
|
|
122040
|
-
const bootstrapMaxChars = normalizePositiveLimit(params.bootstrapMaxChars);
|
|
122041
|
-
const bootstrapTotalMaxChars = normalizePositiveLimit(params.bootstrapTotalMaxChars);
|
|
122042
|
-
const nearLimitRatio = typeof params.nearLimitRatio === "number" && Number.isFinite(params.nearLimitRatio) && params.nearLimitRatio > 0 && params.nearLimitRatio < 1 ? params.nearLimitRatio : DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO;
|
|
122043
|
-
const nonMissing = params.files.filter((file) => !file.missing);
|
|
122044
|
-
const rawChars = nonMissing.reduce((sum, file) => sum + file.rawChars, 0);
|
|
122045
|
-
const injectedChars = nonMissing.reduce((sum, file) => sum + file.injectedChars, 0);
|
|
122046
|
-
const totalNearLimit = injectedChars >= Math.ceil(bootstrapTotalMaxChars * nearLimitRatio);
|
|
122047
|
-
const totalOverLimit = injectedChars >= bootstrapTotalMaxChars;
|
|
122048
|
-
const files = params.files.map((file) => {
|
|
122049
|
-
if (file.missing) {
|
|
122050
|
-
return { ...file, nearLimit: false, causes: [] };
|
|
122051
|
-
}
|
|
122052
|
-
const perFileOverLimit = file.rawChars > bootstrapMaxChars;
|
|
122053
|
-
const nearLimit = file.rawChars >= Math.ceil(bootstrapMaxChars * nearLimitRatio);
|
|
122054
|
-
const causes = [];
|
|
122055
|
-
if (file.truncated) {
|
|
122056
|
-
if (perFileOverLimit) {
|
|
122057
|
-
causes.push("per-file-limit");
|
|
122058
|
-
}
|
|
122059
|
-
if (totalOverLimit) {
|
|
122060
|
-
causes.push("total-limit");
|
|
122061
|
-
}
|
|
122062
|
-
}
|
|
122063
|
-
return { ...file, nearLimit, causes };
|
|
122064
|
-
});
|
|
122065
|
-
const truncatedFiles = files.filter((file) => file.truncated);
|
|
122066
|
-
const nearLimitFiles = files.filter((file) => file.nearLimit);
|
|
122067
|
-
return {
|
|
122068
|
-
files,
|
|
122069
|
-
truncatedFiles,
|
|
122070
|
-
nearLimitFiles,
|
|
122071
|
-
totalNearLimit,
|
|
122072
|
-
hasTruncation: truncatedFiles.length > 0,
|
|
122073
|
-
totals: {
|
|
122074
|
-
rawChars,
|
|
122075
|
-
injectedChars,
|
|
122076
|
-
truncatedChars: Math.max(0, rawChars - injectedChars),
|
|
122077
|
-
bootstrapMaxChars,
|
|
122078
|
-
bootstrapTotalMaxChars,
|
|
122079
|
-
nearLimitRatio
|
|
122080
|
-
}
|
|
122081
|
-
};
|
|
122082
|
-
}
|
|
122083
|
-
function buildBootstrapTruncationSignature(analysis) {
|
|
122084
|
-
if (!analysis.hasTruncation) {
|
|
122085
|
-
return;
|
|
122086
|
-
}
|
|
122087
|
-
const files = analysis.truncatedFiles.map((file) => ({
|
|
122088
|
-
path: file.path || file.name,
|
|
122089
|
-
rawChars: file.rawChars,
|
|
122090
|
-
injectedChars: file.injectedChars,
|
|
122091
|
-
causes: [...file.causes].sort()
|
|
122092
|
-
})).sort((a, b) => {
|
|
122093
|
-
const pathCmp = a.path.localeCompare(b.path);
|
|
122094
|
-
if (pathCmp !== 0) {
|
|
122095
|
-
return pathCmp;
|
|
122096
|
-
}
|
|
122097
|
-
if (a.rawChars !== b.rawChars) {
|
|
122098
|
-
return a.rawChars - b.rawChars;
|
|
122099
|
-
}
|
|
122100
|
-
if (a.injectedChars !== b.injectedChars) {
|
|
122101
|
-
return a.injectedChars - b.injectedChars;
|
|
122102
|
-
}
|
|
122103
|
-
return a.causes.join("+").localeCompare(b.causes.join("+"));
|
|
122104
|
-
});
|
|
122105
|
-
return JSON.stringify({
|
|
122106
|
-
bootstrapMaxChars: analysis.totals.bootstrapMaxChars,
|
|
122107
|
-
bootstrapTotalMaxChars: analysis.totals.bootstrapTotalMaxChars,
|
|
122108
|
-
files
|
|
122109
|
-
});
|
|
122110
|
-
}
|
|
122111
|
-
function formatBootstrapTruncationWarningLines(params) {
|
|
122112
|
-
if (!params.analysis.hasTruncation) {
|
|
122113
|
-
return [];
|
|
122114
|
-
}
|
|
122115
|
-
const maxFiles = typeof params.maxFiles === "number" && Number.isFinite(params.maxFiles) && params.maxFiles > 0 ? Math.floor(params.maxFiles) : DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES;
|
|
122116
|
-
const lines = [];
|
|
122117
|
-
const duplicateNameCounts = params.analysis.truncatedFiles.reduce((acc, file) => {
|
|
122118
|
-
acc.set(file.name, (acc.get(file.name) ?? 0) + 1);
|
|
122119
|
-
return acc;
|
|
122120
|
-
}, new Map);
|
|
122121
|
-
const topFiles = params.analysis.truncatedFiles.slice(0, maxFiles);
|
|
122122
|
-
for (const file of topFiles) {
|
|
122123
|
-
const pct = file.rawChars > 0 ? Math.round((file.rawChars - file.injectedChars) / file.rawChars * 100) : 0;
|
|
122124
|
-
const causeText = file.causes.length > 0 ? file.causes.map((cause) => formatWarningCause(cause)).join(", ") : "";
|
|
122125
|
-
const nameLabel = (duplicateNameCounts.get(file.name) ?? 0) > 1 && file.path.trim().length > 0 ? `${file.name} (${file.path})` : file.name;
|
|
122126
|
-
lines.push(`${nameLabel}: ${file.rawChars} raw -> ${file.injectedChars} injected (~${Math.max(0, pct)}% removed${causeText ? `; ${causeText}` : ""}).`);
|
|
122127
|
-
}
|
|
122128
|
-
if (params.analysis.truncatedFiles.length > topFiles.length) {
|
|
122129
|
-
lines.push(`+${params.analysis.truncatedFiles.length - topFiles.length} more truncated file(s).`);
|
|
122130
|
-
}
|
|
122131
|
-
lines.push("If unintentional, raise switchroom workspace bootstrapMaxChars and/or bootstrapTotalMaxChars config.");
|
|
122132
|
-
return lines;
|
|
122133
|
-
}
|
|
122134
|
-
function buildBootstrapPromptWarning(params) {
|
|
122135
|
-
const signature = buildBootstrapTruncationSignature(params.analysis);
|
|
122136
|
-
let seenSignatures = normalizeSeenSignatures(params.seenSignatures);
|
|
122137
|
-
if (params.previousSignature && !seenSignatures.includes(params.previousSignature)) {
|
|
122138
|
-
seenSignatures = appendSeenSignature(seenSignatures, params.previousSignature);
|
|
122139
|
-
}
|
|
122140
|
-
const hasSeenSignature = Boolean(signature && seenSignatures.includes(signature));
|
|
122141
|
-
const warningShown = params.mode !== "off" && Boolean(signature) && (params.mode === "always" || !hasSeenSignature);
|
|
122142
|
-
const warningSignaturesSeen = signature && params.mode !== "off" ? appendSeenSignature(seenSignatures, signature) : seenSignatures;
|
|
122143
|
-
return {
|
|
122144
|
-
signature,
|
|
122145
|
-
warningShown,
|
|
122146
|
-
lines: warningShown ? formatBootstrapTruncationWarningLines({
|
|
122147
|
-
analysis: params.analysis,
|
|
122148
|
-
maxFiles: params.maxFiles
|
|
122149
|
-
}) : [],
|
|
122150
|
-
warningSignaturesSeen
|
|
122151
|
-
};
|
|
122152
|
-
}
|
|
122153
|
-
|
|
122154
|
-
// src/agents/bootstrap-types.ts
|
|
122155
|
-
var DEFAULT_AGENTS_FILENAME = "AGENTS.md";
|
|
122156
|
-
var DEFAULT_SOUL_DEFAULT_FILENAME = "SOUL.default.md";
|
|
122157
|
-
var DEFAULT_SOUL_FILENAME = "SOUL.md";
|
|
122158
|
-
var DEFAULT_TOOLS_FILENAME = "TOOLS.md";
|
|
122159
|
-
var DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
|
|
122160
|
-
var DEFAULT_USER_FILENAME = "USER.md";
|
|
122161
|
-
var DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
|
|
122162
|
-
var DEFAULT_MEMORY_FILENAME = "MEMORY.md";
|
|
122163
|
-
|
|
122164
|
-
// src/agents/workspace.ts
|
|
122165
|
-
var DEFAULT_WORKSPACE_DIR_NAME = "workspace";
|
|
122166
|
-
var DEFAULT_MEMORY_SUBDIR = "memory";
|
|
122167
|
-
var STABLE_BOOTSTRAP_FILENAMES = [
|
|
122168
|
-
DEFAULT_AGENTS_FILENAME,
|
|
122169
|
-
DEFAULT_SOUL_DEFAULT_FILENAME,
|
|
122170
|
-
DEFAULT_SOUL_FILENAME,
|
|
122171
|
-
DEFAULT_IDENTITY_FILENAME,
|
|
122172
|
-
DEFAULT_USER_FILENAME,
|
|
122173
|
-
DEFAULT_TOOLS_FILENAME,
|
|
122174
|
-
DEFAULT_BOOTSTRAP_FILENAME
|
|
122175
|
-
];
|
|
122176
|
-
var DYNAMIC_BOOTSTRAP_FILENAMES = [
|
|
122177
|
-
DEFAULT_MEMORY_FILENAME
|
|
122178
|
-
];
|
|
122179
|
-
var DEFAULT_BOOTSTRAP_MAX_CHARS = 12000;
|
|
122180
|
-
var DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS = 64000;
|
|
122181
|
-
var DEFAULT_DYNAMIC_TOTAL_MAX_CHARS = 24000;
|
|
122182
|
-
function resolveAgentWorkspaceDir(agentDir) {
|
|
122183
|
-
return path8.join(agentDir, DEFAULT_WORKSPACE_DIR_NAME);
|
|
122184
|
-
}
|
|
122185
|
-
async function readOptionalFile(filePath) {
|
|
122186
|
-
try {
|
|
122187
|
-
const content = await readFile2(filePath, "utf8");
|
|
122188
|
-
return content;
|
|
122189
|
-
} catch (err) {
|
|
122190
|
-
if (isErrnoException(err) && (err.code === "ENOENT" || err.code === "EISDIR")) {
|
|
122191
|
-
return;
|
|
122192
|
-
}
|
|
122193
|
-
throw err;
|
|
122194
|
-
}
|
|
122195
|
-
}
|
|
122196
|
-
function isErrnoException(value) {
|
|
122197
|
-
return value instanceof Error && typeof value.code === "string";
|
|
122198
|
-
}
|
|
122199
|
-
async function loadNamedFile(workspaceDir, name, relativePath) {
|
|
122200
|
-
const filePath = path8.join(workspaceDir, relativePath ?? name);
|
|
122201
|
-
const content = await readOptionalFile(filePath);
|
|
122202
|
-
if (content === undefined) {
|
|
122203
|
-
return { name, path: filePath, missing: true };
|
|
122204
|
-
}
|
|
122205
|
-
return { name, path: filePath, content, missing: false };
|
|
122206
|
-
}
|
|
122207
|
-
async function loadStableBootstrapFiles(workspaceDir, options) {
|
|
122208
|
-
const defaultFiles = await Promise.all(STABLE_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122209
|
-
const extras = options?.extraStableFiles ?? [];
|
|
122210
|
-
if (extras.length === 0) {
|
|
122211
|
-
return defaultFiles;
|
|
122212
|
-
}
|
|
122213
|
-
const extraFiles = await Promise.all(extras.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122214
|
-
return [...defaultFiles, ...extraFiles];
|
|
122215
|
-
}
|
|
122216
|
-
async function loadDynamicBootstrapFiles(workspaceDir, options) {
|
|
122217
|
-
const now2 = options?.now ?? new Date;
|
|
122218
|
-
const includeYesterday = options?.includeYesterday ?? true;
|
|
122219
|
-
const files = await Promise.all(DYNAMIC_BOOTSTRAP_FILENAMES.map((name) => loadNamedFile(workspaceDir, name)));
|
|
122220
|
-
const todayRelative = dailyMemoryRelativePath(now2);
|
|
122221
|
-
const todayName = DEFAULT_MEMORY_FILENAME;
|
|
122222
|
-
const today = await loadNamedFile(workspaceDir, todayName, todayRelative);
|
|
122223
|
-
today.path = path8.join(workspaceDir, todayRelative);
|
|
122224
|
-
files.push(today);
|
|
122225
|
-
if (includeYesterday) {
|
|
122226
|
-
const yesterdayRelative = dailyMemoryRelativePath(addDays(now2, -1));
|
|
122227
|
-
const yesterday = await loadNamedFile(workspaceDir, todayName, yesterdayRelative);
|
|
122228
|
-
yesterday.path = path8.join(workspaceDir, yesterdayRelative);
|
|
122229
|
-
files.push(yesterday);
|
|
122230
|
-
}
|
|
122231
|
-
return files;
|
|
122232
|
-
}
|
|
122233
|
-
function pad22(n) {
|
|
122234
|
-
return n < 10 ? `0${n}` : String(n);
|
|
122235
|
-
}
|
|
122236
|
-
function dailyMemoryRelativePath(date) {
|
|
122237
|
-
const y = date.getFullYear();
|
|
122238
|
-
const m = pad22(date.getMonth() + 1);
|
|
122239
|
-
const d = pad22(date.getDate());
|
|
122240
|
-
return path8.join(DEFAULT_MEMORY_SUBDIR, `${y}-${m}-${d}.md`);
|
|
122241
|
-
}
|
|
122242
|
-
function addDays(date, days) {
|
|
122243
|
-
const out = new Date(date.getTime());
|
|
122244
|
-
out.setDate(out.getDate() + days);
|
|
122245
|
-
return out;
|
|
122246
|
-
}
|
|
122247
|
-
function truncateContent(name, content, maxChars) {
|
|
122248
|
-
if (content.length <= maxChars) {
|
|
122249
|
-
return content;
|
|
122250
|
-
}
|
|
122251
|
-
if (maxChars < 200) {
|
|
122252
|
-
return content.slice(0, Math.max(0, maxChars));
|
|
122253
|
-
}
|
|
122254
|
-
const headRoomInitial = Math.floor(maxChars * 0.7);
|
|
122255
|
-
const sampleMarker = `
|
|
122256
|
-
\u2026(truncated ${name}: kept ${headRoomInitial}+${Math.max(0, maxChars - headRoomInitial - 96)} chars of ${content.length})\u2026
|
|
122257
|
-
`;
|
|
122258
|
-
const markerReserve = Math.max(64, sampleMarker.length + 8);
|
|
122259
|
-
const headRoom = Math.min(headRoomInitial, Math.max(0, maxChars - markerReserve - 8));
|
|
122260
|
-
const tailRoom = Math.max(0, maxChars - headRoom - markerReserve);
|
|
122261
|
-
const head = content.slice(0, Math.max(0, headRoom));
|
|
122262
|
-
const tail = tailRoom > 0 ? content.slice(content.length - tailRoom) : "";
|
|
122263
|
-
const result = `${head}
|
|
122264
|
-
\u2026(truncated ${name}: kept ${headRoom}+${tailRoom} chars of ${content.length})\u2026
|
|
122265
|
-
${tail}`;
|
|
122266
|
-
return result.length <= maxChars ? result : result.slice(0, maxChars);
|
|
122267
|
-
}
|
|
122268
|
-
function projectBootstrapFiles(params) {
|
|
122269
|
-
const {
|
|
122270
|
-
files,
|
|
122271
|
-
heading,
|
|
122272
|
-
budget,
|
|
122273
|
-
seenSignatures,
|
|
122274
|
-
warningMode = "error",
|
|
122275
|
-
warningMaxFiles = DEFAULT_BOOTSTRAP_PROMPT_WARNING_MAX_FILES
|
|
122276
|
-
} = params;
|
|
122277
|
-
const perFileCap = budget.bootstrapMaxChars;
|
|
122278
|
-
let remainingTotal = budget.bootstrapTotalMaxChars;
|
|
122279
|
-
const injectedFiles = [];
|
|
122280
|
-
const parts = [];
|
|
122281
|
-
for (const file of files) {
|
|
122282
|
-
if (file.missing || typeof file.content !== "string") {
|
|
122283
|
-
continue;
|
|
122284
|
-
}
|
|
122285
|
-
const raw = file.content.trimEnd();
|
|
122286
|
-
if (raw.length === 0) {
|
|
122287
|
-
continue;
|
|
122288
|
-
}
|
|
122289
|
-
const perFileAllowed = Math.min(perFileCap, remainingTotal);
|
|
122290
|
-
if (perFileAllowed <= 0) {
|
|
122291
|
-
break;
|
|
122292
|
-
}
|
|
122293
|
-
const injected = truncateContent(file.name, raw, perFileAllowed);
|
|
122294
|
-
const relativePath = file.path;
|
|
122295
|
-
injectedFiles.push({ path: relativePath, content: injected });
|
|
122296
|
-
parts.push(`## ${relativePath}`);
|
|
122297
|
-
parts.push(injected);
|
|
122298
|
-
remainingTotal -= injected.length;
|
|
122299
|
-
if (remainingTotal <= 0) {
|
|
122300
|
-
break;
|
|
122301
|
-
}
|
|
122302
|
-
}
|
|
122303
|
-
const concatenated = parts.length > 0 ? [heading.trim().length > 0 ? `# ${heading}` : null, ...parts].filter((p) => p !== null).join(`
|
|
122304
|
-
|
|
122305
|
-
`) : "";
|
|
122306
|
-
const stats = buildBootstrapInjectionStats({
|
|
122307
|
-
bootstrapFiles: files,
|
|
122308
|
-
injectedFiles
|
|
122309
|
-
});
|
|
122310
|
-
const analysis = analyzeBootstrapBudget({
|
|
122311
|
-
files: stats,
|
|
122312
|
-
bootstrapMaxChars: budget.bootstrapMaxChars,
|
|
122313
|
-
bootstrapTotalMaxChars: budget.bootstrapTotalMaxChars,
|
|
122314
|
-
nearLimitRatio: budget.nearLimitRatio ?? DEFAULT_BOOTSTRAP_NEAR_LIMIT_RATIO
|
|
122315
|
-
});
|
|
122316
|
-
const warning = buildBootstrapPromptWarning({
|
|
122317
|
-
analysis,
|
|
122318
|
-
mode: warningMode,
|
|
122319
|
-
seenSignatures,
|
|
122320
|
-
maxFiles: warningMaxFiles
|
|
122321
|
-
});
|
|
122322
|
-
if (warningMode === "error" && analysis.hasTruncation) {
|
|
122323
|
-
const errorLines = [
|
|
122324
|
-
"Bootstrap budget exceeded. The following files exceed limits:",
|
|
122325
|
-
""
|
|
122326
|
-
];
|
|
122327
|
-
for (const file of analysis.truncatedFiles) {
|
|
122328
|
-
const excessChars = file.rawChars - file.injectedChars;
|
|
122329
|
-
const causes = file.causes.map((c) => c === "per-file-limit" ? `per-file limit (${analysis.totals.bootstrapMaxChars.toLocaleString()} chars)` : `total limit (${analysis.totals.bootstrapTotalMaxChars.toLocaleString()} chars)`);
|
|
122330
|
-
errorLines.push(` ${file.name}: ${file.rawChars.toLocaleString()} bytes (exceeds ${causes.join(", ")})`);
|
|
122331
|
-
errorLines.push(` Trim ${excessChars.toLocaleString()} bytes, or pass --warning-mode warn to proceed with truncation.`);
|
|
122332
|
-
}
|
|
122333
|
-
throw new BootstrapBudgetExceededError(analysis, errorLines.join(`
|
|
122334
|
-
`));
|
|
122335
|
-
}
|
|
122336
|
-
return { files, injectedFiles, concatenated, analysis, warning };
|
|
122337
|
-
}
|
|
122338
|
-
async function buildStableBootstrapPrompt(params) {
|
|
122339
|
-
const files = await loadStableBootstrapFiles(params.workspaceDir, {
|
|
122340
|
-
extraStableFiles: params.extraStableFiles
|
|
122341
|
-
});
|
|
122342
|
-
const budget = {
|
|
122343
|
-
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
122344
|
-
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_BOOTSTRAP_TOTAL_MAX_CHARS,
|
|
122345
|
-
nearLimitRatio: params.budget?.nearLimitRatio
|
|
122346
|
-
};
|
|
122347
|
-
return projectBootstrapFiles({
|
|
122348
|
-
files,
|
|
122349
|
-
heading: "Project Context (stable workspace files)",
|
|
122350
|
-
budget,
|
|
122351
|
-
seenSignatures: params.seenSignatures,
|
|
122352
|
-
warningMode: params.budget?.warningMode,
|
|
122353
|
-
warningMaxFiles: params.budget?.warningMaxFiles
|
|
122354
|
-
});
|
|
122355
|
-
}
|
|
122356
|
-
async function buildDynamicBootstrapPrompt(params) {
|
|
122357
|
-
const files = await loadDynamicBootstrapFiles(params.workspaceDir, {
|
|
122358
|
-
now: params.now,
|
|
122359
|
-
includeYesterday: params.includeYesterday
|
|
122360
|
-
});
|
|
122361
|
-
const budget = {
|
|
122362
|
-
bootstrapMaxChars: params.budget?.bootstrapMaxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS,
|
|
122363
|
-
bootstrapTotalMaxChars: params.budget?.bootstrapTotalMaxChars ?? DEFAULT_DYNAMIC_TOTAL_MAX_CHARS,
|
|
122364
|
-
nearLimitRatio: params.budget?.nearLimitRatio
|
|
122365
|
-
};
|
|
122366
|
-
return projectBootstrapFiles({
|
|
122367
|
-
files,
|
|
122368
|
-
heading: "Project Context (dynamic workspace files)",
|
|
122369
|
-
budget,
|
|
122370
|
-
seenSignatures: params.seenSignatures,
|
|
122371
|
-
warningMode: params.budget?.warningMode,
|
|
122372
|
-
warningMaxFiles: params.budget?.warningMaxFiles
|
|
122373
|
-
});
|
|
122374
|
-
}
|
|
122375
|
-
|
|
122376
122953
|
// src/agents/memory-search.ts
|
|
122377
122954
|
import { readFile as readFile3, readdir, realpath, stat as stat2 } from "node:fs/promises";
|
|
122378
122955
|
import path9 from "node:path";
|
|
@@ -122650,8 +123227,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122650
123227
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122651
123228
|
if (!dir)
|
|
122652
123229
|
return;
|
|
122653
|
-
const resolvedWorkspace =
|
|
122654
|
-
const target =
|
|
123230
|
+
const resolvedWorkspace = resolve56(dir);
|
|
123231
|
+
const target = resolve56(resolvedWorkspace, file ?? "AGENTS.md");
|
|
122655
123232
|
if (!isInsideWorkspace(resolvedWorkspace, target)) {
|
|
122656
123233
|
process.stderr.write(`workspace edit: refusing path traversal outside workspace dir (${target})
|
|
122657
123234
|
`);
|
|
@@ -122719,8 +123296,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122719
123296
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122720
123297
|
if (!dir)
|
|
122721
123298
|
return;
|
|
122722
|
-
const gitDir =
|
|
122723
|
-
if (!
|
|
123299
|
+
const gitDir = resolve56(dir, ".git");
|
|
123300
|
+
if (!existsSync106(gitDir)) {
|
|
122724
123301
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
122725
123302
|
`);
|
|
122726
123303
|
return;
|
|
@@ -122773,8 +123350,8 @@ function registerWorkspaceCommand(program3) {
|
|
|
122773
123350
|
const dir = resolveAgentWorkspaceDirOrExit(program3, agentName);
|
|
122774
123351
|
if (!dir)
|
|
122775
123352
|
return;
|
|
122776
|
-
const gitDir =
|
|
122777
|
-
if (!
|
|
123353
|
+
const gitDir = resolve56(dir, ".git");
|
|
123354
|
+
if (!existsSync106(gitDir)) {
|
|
122778
123355
|
process.stdout.write(`Workspace is not a git repository.
|
|
122779
123356
|
`);
|
|
122780
123357
|
return;
|
|
@@ -122797,9 +123374,9 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
122797
123374
|
return;
|
|
122798
123375
|
}
|
|
122799
123376
|
const agentsDir = resolveAgentsDir(config);
|
|
122800
|
-
const agentDir =
|
|
123377
|
+
const agentDir = resolve56(agentsDir, agentName);
|
|
122801
123378
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
122802
|
-
if (!
|
|
123379
|
+
if (!existsSync106(dir)) {
|
|
122803
123380
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
122804
123381
|
`);
|
|
122805
123382
|
return;
|
|
@@ -122835,8 +123412,8 @@ function safeParseInt(value, fallback) {
|
|
|
122835
123412
|
init_helpers();
|
|
122836
123413
|
init_loader();
|
|
122837
123414
|
init_merge();
|
|
122838
|
-
import { copyFileSync as copyFileSync14, existsSync as
|
|
122839
|
-
import { join as
|
|
123415
|
+
import { copyFileSync as copyFileSync14, existsSync as existsSync107, readFileSync as readFileSync96, writeFileSync as writeFileSync43 } from "node:fs";
|
|
123416
|
+
import { join as join111, resolve as resolve57 } from "node:path";
|
|
122840
123417
|
init_scaffold();
|
|
122841
123418
|
init_profiles();
|
|
122842
123419
|
init_schema();
|
|
@@ -122851,9 +123428,9 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
122851
123428
|
const profileName = merged.extends ?? DEFAULT_PROFILE;
|
|
122852
123429
|
const profilePath = getProfilePath(profileName);
|
|
122853
123430
|
const agentsDir = resolveAgentsDir(config);
|
|
122854
|
-
const agentDir =
|
|
123431
|
+
const agentDir = resolve57(agentsDir, agentName);
|
|
122855
123432
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
122856
|
-
if (!
|
|
123433
|
+
if (!existsSync107(workspaceDir)) {
|
|
122857
123434
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
122858
123435
|
process.exit(1);
|
|
122859
123436
|
}
|
|
@@ -122862,7 +123439,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
122862
123439
|
profileName,
|
|
122863
123440
|
profilePath,
|
|
122864
123441
|
workspaceDir,
|
|
122865
|
-
soulPath:
|
|
123442
|
+
soulPath: join111(workspaceDir, "SOUL.md"),
|
|
122866
123443
|
soul: merged.soul
|
|
122867
123444
|
};
|
|
122868
123445
|
}
|
|
@@ -122879,11 +123456,11 @@ function registerSoulCommand(program3) {
|
|
|
122879
123456
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
122880
123457
|
if (!t)
|
|
122881
123458
|
return;
|
|
122882
|
-
if (!
|
|
123459
|
+
if (!existsSync107(t.soulPath)) {
|
|
122883
123460
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
122884
123461
|
process.exit(1);
|
|
122885
123462
|
}
|
|
122886
|
-
process.stdout.write(
|
|
123463
|
+
process.stdout.write(readFileSync96(t.soulPath, "utf-8"));
|
|
122887
123464
|
}));
|
|
122888
123465
|
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) => {
|
|
122889
123466
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -122894,7 +123471,7 @@ function registerSoulCommand(program3) {
|
|
|
122894
123471
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
122895
123472
|
process.exit(1);
|
|
122896
123473
|
}
|
|
122897
|
-
const exists =
|
|
123474
|
+
const exists = existsSync107(t.soulPath);
|
|
122898
123475
|
if (exists && !opts.yes) {
|
|
122899
123476
|
if (!isInteractive()) {
|
|
122900
123477
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -122909,7 +123486,7 @@ function registerSoulCommand(program3) {
|
|
|
122909
123486
|
let backupPath;
|
|
122910
123487
|
if (exists) {
|
|
122911
123488
|
backupPath = `${t.soulPath}.bak`;
|
|
122912
|
-
if (
|
|
123489
|
+
if (existsSync107(backupPath)) {
|
|
122913
123490
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
122914
123491
|
}
|
|
122915
123492
|
copyFileSync14(t.soulPath, backupPath);
|
|
@@ -122925,279 +123502,6 @@ function registerSoulCommand(program3) {
|
|
|
122925
123502
|
}));
|
|
122926
123503
|
}
|
|
122927
123504
|
|
|
122928
|
-
// src/cli/debug.ts
|
|
122929
|
-
init_helpers();
|
|
122930
|
-
init_loader();
|
|
122931
|
-
import { existsSync as existsSync107, readFileSync as readFileSync96, readdirSync as readdirSync36, statSync as statSync62 } from "node:fs";
|
|
122932
|
-
import { resolve as resolve57, join as join111 } from "node:path";
|
|
122933
|
-
import { createHash as createHash21 } from "node:crypto";
|
|
122934
|
-
init_merge();
|
|
122935
|
-
init_hindsight2();
|
|
122936
|
-
function formatBytes(bytes) {
|
|
122937
|
-
return `${bytes.toLocaleString()} bytes`;
|
|
122938
|
-
}
|
|
122939
|
-
function estimateTokens(bytes) {
|
|
122940
|
-
return Math.round(bytes / 3.7);
|
|
122941
|
-
}
|
|
122942
|
-
function readMcpServerNames(agentDir) {
|
|
122943
|
-
const mcpPath = join111(agentDir, ".mcp.json");
|
|
122944
|
-
if (!existsSync107(mcpPath))
|
|
122945
|
-
return [];
|
|
122946
|
-
try {
|
|
122947
|
-
const parsed = JSON.parse(readFileSync96(mcpPath, "utf-8"));
|
|
122948
|
-
return Object.keys(parsed.mcpServers ?? {});
|
|
122949
|
-
} catch {
|
|
122950
|
-
return null;
|
|
122951
|
-
}
|
|
122952
|
-
}
|
|
122953
|
-
function sha2562(content) {
|
|
122954
|
-
return createHash21("sha256").update(content).digest("hex").slice(0, 16);
|
|
122955
|
-
}
|
|
122956
|
-
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
122957
|
-
const projectsDir = join111(claudeConfigDir, "projects");
|
|
122958
|
-
if (!existsSync107(projectsDir))
|
|
122959
|
-
return;
|
|
122960
|
-
try {
|
|
122961
|
-
const entries = readdirSync36(projectsDir, { withFileTypes: true });
|
|
122962
|
-
let latest;
|
|
122963
|
-
for (const entry of entries) {
|
|
122964
|
-
if (!entry.isDirectory())
|
|
122965
|
-
continue;
|
|
122966
|
-
const projectPath = join111(projectsDir, entry.name);
|
|
122967
|
-
const transcriptPath = join111(projectPath, "transcript.jsonl");
|
|
122968
|
-
if (!existsSync107(transcriptPath))
|
|
122969
|
-
continue;
|
|
122970
|
-
const stat3 = statSync62(transcriptPath);
|
|
122971
|
-
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
122972
|
-
latest = { path: transcriptPath, mtime: stat3.mtimeMs };
|
|
122973
|
-
}
|
|
122974
|
-
}
|
|
122975
|
-
return latest?.path;
|
|
122976
|
-
} catch {
|
|
122977
|
-
return;
|
|
122978
|
-
}
|
|
122979
|
-
}
|
|
122980
|
-
function extractLatestUserMessage(transcriptPath) {
|
|
122981
|
-
try {
|
|
122982
|
-
const content = readFileSync96(transcriptPath, "utf-8");
|
|
122983
|
-
const lines = content.trim().split(`
|
|
122984
|
-
`).filter(Boolean);
|
|
122985
|
-
for (let i2 = lines.length - 1;i2 >= 0; i2--) {
|
|
122986
|
-
const line = lines[i2];
|
|
122987
|
-
try {
|
|
122988
|
-
const event = JSON.parse(line);
|
|
122989
|
-
if (event.type === "message" && event.role === "user" && typeof event.content === "string") {
|
|
122990
|
-
const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleString() : "unknown";
|
|
122991
|
-
return { text: event.content, timestamp };
|
|
122992
|
-
}
|
|
122993
|
-
} catch {
|
|
122994
|
-
continue;
|
|
122995
|
-
}
|
|
122996
|
-
}
|
|
122997
|
-
} catch {
|
|
122998
|
-
return;
|
|
122999
|
-
}
|
|
123000
|
-
}
|
|
123001
|
-
function buildProgressUpdateGuidance() {
|
|
123002
|
-
return `## Progress updates (human-style check-ins)
|
|
123003
|
-
|
|
123004
|
-
You're talking to a human colleague on Telegram. Alongside the emoji status
|
|
123005
|
-
ladder, send a short \`progress_update\` at inflection points, the moments a
|
|
123006
|
-
senior colleague would ping the person who asked them to do something:
|
|
123007
|
-
|
|
123008
|
-
- **Plan formed:** "Got it. Going to do X first, then Y, then Z."
|
|
123009
|
-
- **Pivot or blocker:** "First approach didn't work because <reason>. Trying
|
|
123010
|
-
<alternative> instead."
|
|
123011
|
-
- **Chunk finished:** "Done with X. Starting Y now."
|
|
123012
|
-
|
|
123013
|
-
Keep them short (one or two sentences). Don't narrate every step, the pinned
|
|
123014
|
-
progress card shows that for free. Don't send an update on a trivial one-shot
|
|
123015
|
-
task. Send them when a colleague would genuinely want to know what's happening.
|
|
123016
|
-
|
|
123017
|
-
Final answers still go through \`reply\` as usual,
|
|
123018
|
-
\`progress_update\` is only for mid-turn check-ins.`;
|
|
123019
|
-
}
|
|
123020
|
-
function registerDebugCommand(program3) {
|
|
123021
|
-
const cmd = program3.command("debug", { hidden: true }).description("Observability tools for inspecting agent prompt layering [advanced]");
|
|
123022
|
-
cmd.command("turn <agent>").description("Dump the exact prompt layering the model saw on the most recent turn").option("--last <n>", "Show N-th most recent turn instead of latest", "1").action(withConfigError(async (agentName, opts) => {
|
|
123023
|
-
const config = getConfig(program3);
|
|
123024
|
-
const agentConfig = config.agents[agentName];
|
|
123025
|
-
if (!agentConfig) {
|
|
123026
|
-
console.error(`Agent '${agentName}' not found in switchroom.yaml`);
|
|
123027
|
-
process.exit(1);
|
|
123028
|
-
}
|
|
123029
|
-
const agentsDir = resolveAgentsDir(config);
|
|
123030
|
-
const agentDir = resolve57(agentsDir, agentName);
|
|
123031
|
-
if (!existsSync107(agentDir)) {
|
|
123032
|
-
console.error(`Agent directory not found: ${agentDir}`);
|
|
123033
|
-
process.exit(1);
|
|
123034
|
-
}
|
|
123035
|
-
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
123036
|
-
const claudeConfigDir = join111(agentDir, ".claude");
|
|
123037
|
-
const claudeMdPath2 = join111(agentDir, "CLAUDE.md");
|
|
123038
|
-
const soulMdPath = join111(agentDir, "SOUL.md");
|
|
123039
|
-
const workspaceSoulMdPath = join111(workspaceDir, "SOUL.md");
|
|
123040
|
-
const handoffPath = join111(agentDir, ".handoff.md");
|
|
123041
|
-
const lastN = parseInt(opts.last, 10);
|
|
123042
|
-
if (isNaN(lastN) || lastN < 1) {
|
|
123043
|
-
console.error("--last must be a positive integer");
|
|
123044
|
-
process.exit(1);
|
|
123045
|
-
}
|
|
123046
|
-
if (lastN > 1) {
|
|
123047
|
-
console.error("Note: --last N where N > 1 not yet implemented (only latest turn supported)");
|
|
123048
|
-
process.exit(1);
|
|
123049
|
-
}
|
|
123050
|
-
console.log(`=== Debug Turn Dump: ${agentName} ===
|
|
123051
|
-
`);
|
|
123052
|
-
console.log(`=== Append System Prompt (stable) ===
|
|
123053
|
-
`);
|
|
123054
|
-
const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
|
|
123055
|
-
const useHotReloadStable = resolved.channels?.telegram?.hotReloadStable === true;
|
|
123056
|
-
const stableResult = await buildStableBootstrapPrompt({
|
|
123057
|
-
workspaceDir,
|
|
123058
|
-
budget: { warningMode: "off" }
|
|
123059
|
-
});
|
|
123060
|
-
if (useHotReloadStable) {
|
|
123061
|
-
console.log(`-- Workspace Stable Render (not used \u2014 stable content is in per-turn hook) --`);
|
|
123062
|
-
console.log();
|
|
123063
|
-
} else if (stableResult.concatenated.trim().length > 0) {
|
|
123064
|
-
console.log(`-- Workspace Stable Render (${formatBytes(stableResult.concatenated.length)}) --`);
|
|
123065
|
-
console.log(stableResult.concatenated);
|
|
123066
|
-
console.log();
|
|
123067
|
-
} else {
|
|
123068
|
-
console.log("-- Workspace Stable Render (0 bytes, not set up) --");
|
|
123069
|
-
console.log();
|
|
123070
|
-
}
|
|
123071
|
-
const useSwitchroomPlugin = usesSwitchroomTelegramPlugin(resolved);
|
|
123072
|
-
const progressGuidance = useSwitchroomPlugin ? buildProgressUpdateGuidance() : "";
|
|
123073
|
-
if (progressGuidance.length > 0) {
|
|
123074
|
-
console.log(`-- Progress Updates Guidance (${formatBytes(progressGuidance.length)}) --`);
|
|
123075
|
-
console.log(progressGuidance);
|
|
123076
|
-
console.log();
|
|
123077
|
-
}
|
|
123078
|
-
const baseSystemPromptAppend = resolved.system_prompt_append ?? "";
|
|
123079
|
-
if (baseSystemPromptAppend.trim().length > 0) {
|
|
123080
|
-
console.log(`-- User System Prompt Append (${formatBytes(baseSystemPromptAppend.length)}) --`);
|
|
123081
|
-
console.log(baseSystemPromptAppend);
|
|
123082
|
-
console.log();
|
|
123083
|
-
}
|
|
123084
|
-
console.log(`=== Append System Prompt (per-session) ===
|
|
123085
|
-
`);
|
|
123086
|
-
const handoffContent = existsSync107(handoffPath) ? readFileSync96(handoffPath, "utf-8") : "";
|
|
123087
|
-
if (handoffContent.trim().length > 0) {
|
|
123088
|
-
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
123089
|
-
console.log(handoffContent);
|
|
123090
|
-
console.log();
|
|
123091
|
-
} else {
|
|
123092
|
-
console.log("-- Handoff Briefing (0 bytes, no prior session) --");
|
|
123093
|
-
console.log();
|
|
123094
|
-
}
|
|
123095
|
-
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
123096
|
-
`);
|
|
123097
|
-
const claudeMdContent = existsSync107(claudeMdPath2) ? readFileSync96(claudeMdPath2, "utf-8") : "";
|
|
123098
|
-
if (claudeMdContent.trim().length > 0) {
|
|
123099
|
-
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
123100
|
-
console.log(claudeMdContent);
|
|
123101
|
-
console.log();
|
|
123102
|
-
} else {
|
|
123103
|
-
console.log("(0 bytes, not present)");
|
|
123104
|
-
console.log();
|
|
123105
|
-
}
|
|
123106
|
-
console.log(`=== Persona (SOUL.md) ===
|
|
123107
|
-
`);
|
|
123108
|
-
const soulMdContent = existsSync107(soulMdPath) ? readFileSync96(soulMdPath, "utf-8") : existsSync107(workspaceSoulMdPath) ? readFileSync96(workspaceSoulMdPath, "utf-8") : "";
|
|
123109
|
-
if (soulMdContent.trim().length > 0) {
|
|
123110
|
-
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
123111
|
-
console.log(soulMdContent);
|
|
123112
|
-
console.log();
|
|
123113
|
-
} else {
|
|
123114
|
-
console.log("(0 bytes, stale placeholder \u2014 Phase 2 item: single source of truth for persona)");
|
|
123115
|
-
console.log();
|
|
123116
|
-
}
|
|
123117
|
-
console.log(`=== Per-Turn Injections (UserPromptSubmit) ===
|
|
123118
|
-
`);
|
|
123119
|
-
if (useHotReloadStable) {
|
|
123120
|
-
if (stableResult.concatenated.trim().length > 0) {
|
|
123121
|
-
console.log(`-- Workspace Stable (hot-reload hook): ${formatBytes(stableResult.concatenated.length)} --`);
|
|
123122
|
-
console.log(stableResult.concatenated);
|
|
123123
|
-
console.log();
|
|
123124
|
-
} else {
|
|
123125
|
-
console.log("-- Workspace Stable (hot-reload hook): 0 bytes, not set up --");
|
|
123126
|
-
console.log();
|
|
123127
|
-
}
|
|
123128
|
-
}
|
|
123129
|
-
const dynamicResult = await buildDynamicBootstrapPrompt({
|
|
123130
|
-
workspaceDir,
|
|
123131
|
-
budget: { warningMode: "off" }
|
|
123132
|
-
});
|
|
123133
|
-
if (dynamicResult.concatenated.trim().length > 0) {
|
|
123134
|
-
console.log(`-- Workspace Dynamic: fired, ${formatBytes(dynamicResult.concatenated.length)} --`);
|
|
123135
|
-
console.log(dynamicResult.concatenated);
|
|
123136
|
-
console.log();
|
|
123137
|
-
} else {
|
|
123138
|
-
console.log("-- Workspace Dynamic: no content (MEMORY.md and daily notes empty or missing) --");
|
|
123139
|
-
console.log();
|
|
123140
|
-
}
|
|
123141
|
-
const hindsightEnabled = isHindsightEnabled(config) && agentConfig.memory?.auto_recall !== false;
|
|
123142
|
-
if (hindsightEnabled) {
|
|
123143
|
-
console.log("-- Hindsight Recall: enabled (exact content unavailable, check hindsight logs) --");
|
|
123144
|
-
console.log();
|
|
123145
|
-
} else {
|
|
123146
|
-
console.log("-- Hindsight Recall: disabled --");
|
|
123147
|
-
console.log();
|
|
123148
|
-
}
|
|
123149
|
-
console.log(`=== User Message (latest turn) ===
|
|
123150
|
-
`);
|
|
123151
|
-
const transcriptPath = findLatestTranscriptJsonl(claudeConfigDir);
|
|
123152
|
-
const userMessage = transcriptPath ? extractLatestUserMessage(transcriptPath) : undefined;
|
|
123153
|
-
if (userMessage) {
|
|
123154
|
-
console.log(`(Turn timestamp: ${userMessage.timestamp})`);
|
|
123155
|
-
console.log(userMessage.text);
|
|
123156
|
-
console.log();
|
|
123157
|
-
} else {
|
|
123158
|
-
console.log("(unavailable: no transcript found or transcript empty)");
|
|
123159
|
-
console.log();
|
|
123160
|
-
}
|
|
123161
|
-
console.log(`=== Totals ===
|
|
123162
|
-
`);
|
|
123163
|
-
const stableBytes = stableResult.concatenated.length + progressGuidance.length + baseSystemPromptAppend.length;
|
|
123164
|
-
const perSessionBytes = handoffContent.length;
|
|
123165
|
-
const claudeMdBytes = claudeMdContent.length;
|
|
123166
|
-
const soulMdBytes = soulMdContent.length;
|
|
123167
|
-
const perTurnBytes = dynamicResult.concatenated.length;
|
|
123168
|
-
const userBytes = userMessage?.text.length ?? 0;
|
|
123169
|
-
const fleetDir = join111(agentsDir, "..", "fleet");
|
|
123170
|
-
const fleetInvPath = join111(fleetDir, "switchroom-invariants.md");
|
|
123171
|
-
const fleetClaudePath = join111(fleetDir, "CLAUDE.md");
|
|
123172
|
-
const fleetInvBytes = existsSync107(fleetInvPath) ? readFileSync96(fleetInvPath, "utf-8").length : 0;
|
|
123173
|
-
const fleetClaudeBytes = existsSync107(fleetClaudePath) ? readFileSync96(fleetClaudePath, "utf-8").length : 0;
|
|
123174
|
-
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
123175
|
-
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
123176
|
-
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
123177
|
-
console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
|
|
123178
|
-
console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
|
|
123179
|
-
console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
|
|
123180
|
-
console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
|
|
123181
|
-
console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
|
|
123182
|
-
console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
|
|
123183
|
-
const mcpServers = readMcpServerNames(agentDir);
|
|
123184
|
-
const mcpCount = mcpServers?.length ?? null;
|
|
123185
|
-
const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
|
|
123186
|
-
const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
|
|
123187
|
-
const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
|
|
123188
|
-
console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
|
|
123189
|
-
if (mcpServers && mcpServers.length > 0) {
|
|
123190
|
-
console.log(` [${mcpServers.join(", ")}]`);
|
|
123191
|
-
}
|
|
123192
|
-
const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
|
|
123193
|
-
console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
|
|
123194
|
-
const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
|
|
123195
|
-
const stableHash = sha2562(stableCacheInput);
|
|
123196
|
-
console.log(`Cache stable hash: sha256:${stableHash}`);
|
|
123197
|
-
console.log();
|
|
123198
|
-
}));
|
|
123199
|
-
}
|
|
123200
|
-
|
|
123201
123505
|
// src/cli/worktree.ts
|
|
123202
123506
|
init_source();
|
|
123203
123507
|
init_claim();
|
|
@@ -126158,6 +126462,19 @@ function clampKnowledgeSearchLimit(limit) {
|
|
|
126158
126462
|
}
|
|
126159
126463
|
return Math.min(KNOWLEDGE_SEARCH_LIMIT_MAX, Math.max(KNOWLEDGE_SEARCH_LIMIT_MIN, Math.trunc(limit)));
|
|
126160
126464
|
}
|
|
126465
|
+
|
|
126466
|
+
class KnowledgeBankNotGrantedError extends Error {
|
|
126467
|
+
requested;
|
|
126468
|
+
ownBankId;
|
|
126469
|
+
grantedBanks;
|
|
126470
|
+
constructor(requested, ownBankId, grantedBanks) {
|
|
126471
|
+
super(grantedBanks.length > 0 ? `bank_id '${requested}' is not a bank you can read. You may read your ` + `own bank ('${ownBankId}') or an operator-granted bank ` + `(${grantedBanks.map((b) => `'${b}'`).join(", ")}); '${requested}' ` + `is none of these. Omit bank_id to read your own memory bank.` : `bank_id '${requested}' is not your own bank ('${ownBankId}'), and ` + `this agent has not been granted any other bank to read. Omit ` + `bank_id to read your own memory bank.`);
|
|
126472
|
+
this.requested = requested;
|
|
126473
|
+
this.ownBankId = ownBankId;
|
|
126474
|
+
this.grantedBanks = grantedBanks;
|
|
126475
|
+
this.name = "KnowledgeBankNotGrantedError";
|
|
126476
|
+
}
|
|
126477
|
+
}
|
|
126161
126478
|
var KNOWLEDGE_ADMIN_TIMEOUT_MS = 15000;
|
|
126162
126479
|
var KNOWLEDGE_PAGE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
126163
126480
|
|
|
@@ -126169,11 +126486,22 @@ class KnowledgeAdmin {
|
|
|
126169
126486
|
get fetchImpl() {
|
|
126170
126487
|
return this.opts.fetchImpl ?? fetch;
|
|
126171
126488
|
}
|
|
126172
|
-
|
|
126489
|
+
resolveBank(requested) {
|
|
126490
|
+
const own = this.opts.bankId;
|
|
126491
|
+
if (requested === undefined || requested === "" || requested === own) {
|
|
126492
|
+
return own;
|
|
126493
|
+
}
|
|
126494
|
+
if ((this.opts.extraBanks ?? []).includes(requested))
|
|
126495
|
+
return requested;
|
|
126496
|
+
throw new KnowledgeBankNotGrantedError(requested, own, [
|
|
126497
|
+
...this.opts.extraBanks ?? []
|
|
126498
|
+
]);
|
|
126499
|
+
}
|
|
126500
|
+
knowledgeBasePath(bankId) {
|
|
126173
126501
|
const base = this.opts.apiBaseUrl.replace(/\/+$/, "");
|
|
126174
|
-
return `${base}/v1/default/banks/${encodeURIComponent(
|
|
126502
|
+
return `${base}/v1/default/banks/${encodeURIComponent(bankId)}/knowledge-base`;
|
|
126175
126503
|
}
|
|
126176
|
-
async get(url) {
|
|
126504
|
+
async get(url, bankId) {
|
|
126177
126505
|
const ctl = new AbortController;
|
|
126178
126506
|
const timer = setTimeout(() => ctl.abort(), this.opts.timeoutMs ?? KNOWLEDGE_ADMIN_TIMEOUT_MS);
|
|
126179
126507
|
try {
|
|
@@ -126181,7 +126509,7 @@ class KnowledgeAdmin {
|
|
|
126181
126509
|
method: "GET",
|
|
126182
126510
|
headers: {
|
|
126183
126511
|
accept: "application/json",
|
|
126184
|
-
...
|
|
126512
|
+
...bankId ? { "X-Bank-Id": bankId } : {}
|
|
126185
126513
|
},
|
|
126186
126514
|
signal: ctl.signal
|
|
126187
126515
|
});
|
|
@@ -126190,13 +126518,14 @@ class KnowledgeAdmin {
|
|
|
126190
126518
|
}
|
|
126191
126519
|
}
|
|
126192
126520
|
async search(args) {
|
|
126521
|
+
const bank = this.resolveBank(args.bankId);
|
|
126193
126522
|
const params = new URLSearchParams({
|
|
126194
126523
|
q: args.query,
|
|
126195
126524
|
limit: String(clampKnowledgeSearchLimit(args.limit))
|
|
126196
126525
|
});
|
|
126197
|
-
const res = await this.get(`${this.knowledgeBasePath()}/search?${params}
|
|
126526
|
+
const res = await this.get(`${this.knowledgeBasePath(bank)}/search?${params}`, bank);
|
|
126198
126527
|
if (!res.ok) {
|
|
126199
|
-
throw new Error(`searching knowledge pages in bank '${
|
|
126528
|
+
throw new Error(`searching knowledge pages in bank '${bank}' failed: HTTP ${res.status}`);
|
|
126200
126529
|
}
|
|
126201
126530
|
const parsed = await res.json();
|
|
126202
126531
|
const results = Array.isArray(parsed.results) ? parsed.results : [];
|
|
@@ -126209,23 +126538,25 @@ class KnowledgeAdmin {
|
|
|
126209
126538
|
if (!KNOWLEDGE_PAGE_ID_PATTERN.test(args.page_id)) {
|
|
126210
126539
|
throw new Error(`'${args.page_id}' is not a knowledge page id. Ids look like ` + `'kp-<hex>' (letters, digits, '-' and '_' only) and come from ` + `search_knowledge_pages or get_knowledge_tree.`);
|
|
126211
126540
|
}
|
|
126212
|
-
const
|
|
126541
|
+
const bank = this.resolveBank(args.bankId);
|
|
126542
|
+
const res = await this.get(`${this.knowledgeBasePath(bank)}/pages/${encodeURIComponent(args.page_id)}`, bank);
|
|
126213
126543
|
if (res.status === 404) {
|
|
126214
|
-
throw new Error(`no knowledge page '${args.page_id}' in bank '${
|
|
126544
|
+
throw new Error(`no knowledge page '${args.page_id}' in bank '${bank}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
|
|
126215
126545
|
}
|
|
126216
126546
|
if (!res.ok) {
|
|
126217
|
-
throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${
|
|
126547
|
+
throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${bank}' failed: HTTP ${res.status}`);
|
|
126218
126548
|
}
|
|
126219
126549
|
const page = await res.json();
|
|
126220
126550
|
if (typeof page.markdown !== "string") {
|
|
126221
|
-
throw new Error(`knowledge page '${args.page_id}' in bank '${
|
|
126551
|
+
throw new Error(`knowledge page '${args.page_id}' in bank '${bank}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
|
|
126222
126552
|
}
|
|
126223
126553
|
return page;
|
|
126224
126554
|
}
|
|
126225
|
-
async tree() {
|
|
126226
|
-
const
|
|
126555
|
+
async tree(args = {}) {
|
|
126556
|
+
const bank = this.resolveBank(args.bankId);
|
|
126557
|
+
const res = await this.get(`${this.knowledgeBasePath(bank)}/tree`, bank);
|
|
126227
126558
|
if (!res.ok) {
|
|
126228
|
-
throw new Error(`listing the knowledge tree in bank '${
|
|
126559
|
+
throw new Error(`listing the knowledge tree in bank '${bank}' failed: HTTP ${res.status}`);
|
|
126229
126560
|
}
|
|
126230
126561
|
const parsed = await res.json();
|
|
126231
126562
|
return { roots: Array.isArray(parsed.roots) ? parsed.roots : [] };
|
|
@@ -126278,6 +126609,10 @@ var FALLBACK_TOOL_TABLE = {
|
|
|
126278
126609
|
update_memory: [["memory_id"], ["bank_id", "context", "entities", "fact_type", "memory_id", "occurred_end", "occurred_start", "text"]],
|
|
126279
126610
|
update_mental_model: [["mental_model_id"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]]
|
|
126280
126611
|
};
|
|
126612
|
+
var KNOWLEDGE_BANK_SELECTOR_PROP = {
|
|
126613
|
+
type: "string",
|
|
126614
|
+
description: "Optional. Which memory bank to read. Omit to read YOUR OWN bank (the " + "default). You may name another bank ONLY if your operator has granted " + "it to you (e.g. a shared repo knowledge bank); naming any other bank is " + "rejected."
|
|
126615
|
+
};
|
|
126281
126616
|
var SYNTHESIZED_TOOL_TABLE = {
|
|
126282
126617
|
deactivate_directive: {
|
|
126283
126618
|
description: "Retire one of YOUR OWN standing directives by setting is_active=false, " + "so it stops being injected as a hard rule. Reversible with " + "reactivate_directive; the directive and its text are preserved. Pass " + "superseded_by when another directive replaces this one \u2014 both get " + "provenance tags recording the supersession. Operates only on your own " + "memory bank and changes nothing but the active flag and those tags.",
|
|
@@ -126304,7 +126639,7 @@ var SYNTHESIZED_TOOL_TABLE = {
|
|
|
126304
126639
|
}
|
|
126305
126640
|
},
|
|
126306
126641
|
search_knowledge_pages: {
|
|
126307
|
-
description: "Search
|
|
126642
|
+
description: "Search knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what a bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only. Defaults to your own " + "bank; pass bank_id to search an operator-granted bank. An empty " + "knowledge base returns no results, not an error.",
|
|
126308
126643
|
required: ["query"],
|
|
126309
126644
|
props: {
|
|
126310
126645
|
query: {
|
|
@@ -126316,23 +126651,27 @@ var SYNTHESIZED_TOOL_TABLE = {
|
|
|
126316
126651
|
minimum: KNOWLEDGE_SEARCH_LIMIT_MIN,
|
|
126317
126652
|
maximum: KNOWLEDGE_SEARCH_LIMIT_MAX,
|
|
126318
126653
|
description: `Maximum hits to return (${KNOWLEDGE_SEARCH_LIMIT_MIN}-` + `${KNOWLEDGE_SEARCH_LIMIT_MAX}, default ${KNOWLEDGE_SEARCH_LIMIT_DEFAULT}).`
|
|
126319
|
-
}
|
|
126654
|
+
},
|
|
126655
|
+
bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
|
|
126320
126656
|
}
|
|
126321
126657
|
},
|
|
126322
126658
|
get_knowledge_page: {
|
|
126323
|
-
description: "Read one
|
|
126659
|
+
description: "Read one knowledge page in full, by the page id returned by " + "search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh. " + "Defaults to your own bank; pass bank_id (matching the tree/search call " + "the id came from) to read from an operator-granted bank.",
|
|
126324
126660
|
required: ["page_id"],
|
|
126325
126661
|
props: {
|
|
126326
126662
|
page_id: {
|
|
126327
126663
|
type: "string",
|
|
126328
126664
|
description: "Id of the page to read, from search_knowledge_pages or " + "get_knowledge_tree."
|
|
126329
|
-
}
|
|
126665
|
+
},
|
|
126666
|
+
bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
|
|
126330
126667
|
}
|
|
126331
126668
|
},
|
|
126332
126669
|
get_knowledge_tree: {
|
|
126333
|
-
description: "List
|
|
126670
|
+
description: "List a knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "a bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only. Defaults to your own bank; pass bank_id to list an " + "operator-granted bank.",
|
|
126334
126671
|
required: [],
|
|
126335
|
-
props: {
|
|
126672
|
+
props: {
|
|
126673
|
+
bank_id: KNOWLEDGE_BANK_SELECTOR_PROP
|
|
126674
|
+
}
|
|
126336
126675
|
}
|
|
126337
126676
|
};
|
|
126338
126677
|
var SYNTHESIZED_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_TABLE);
|
|
@@ -126936,6 +127275,7 @@ class HindsightShim {
|
|
|
126936
127275
|
this.knowledgeAdminCache = new KnowledgeAdmin({
|
|
126937
127276
|
apiBaseUrl: this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""),
|
|
126938
127277
|
bankId: this.opts.bankId,
|
|
127278
|
+
...this.opts.extraBanks ? { extraBanks: this.opts.extraBanks } : {},
|
|
126939
127279
|
...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {}
|
|
126940
127280
|
});
|
|
126941
127281
|
}
|
|
@@ -126979,6 +127319,15 @@ class HindsightShim {
|
|
|
126979
127319
|
if (!this.opts.bankId) {
|
|
126980
127320
|
return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
|
|
126981
127321
|
}
|
|
127322
|
+
if (typeof clean3.bank_id === "string") {
|
|
127323
|
+
const requested = clean3.bank_id;
|
|
127324
|
+
const granted = requested === this.opts.bankId || (this.opts.extraBanks ?? []).includes(requested);
|
|
127325
|
+
if (!granted) {
|
|
127326
|
+
return fail4(new KnowledgeBankNotGrantedError(requested, this.opts.bankId, [
|
|
127327
|
+
...this.opts.extraBanks ?? []
|
|
127328
|
+
]).message);
|
|
127329
|
+
}
|
|
127330
|
+
}
|
|
126982
127331
|
const preflight = await this.contractPin.preflight(name);
|
|
126983
127332
|
if (!preflight.ok)
|
|
126984
127333
|
return fail4(preflight.text);
|
|
@@ -126986,6 +127335,9 @@ class HindsightShim {
|
|
|
126986
127335
|
const text = await this.runSynthesized(name, clean3);
|
|
126987
127336
|
return { content: [{ type: "text", text }], isError: false };
|
|
126988
127337
|
} catch (err) {
|
|
127338
|
+
if (err instanceof KnowledgeBankNotGrantedError) {
|
|
127339
|
+
return fail4(err.message);
|
|
127340
|
+
}
|
|
126989
127341
|
if (err instanceof DirectivePairInconsistentError) {
|
|
126990
127342
|
this.log(`[hindsight-shim] ${err.message}`);
|
|
126991
127343
|
}
|
|
@@ -127004,23 +127356,25 @@ class HindsightShim {
|
|
|
127004
127356
|
case "search_knowledge_pages": {
|
|
127005
127357
|
const res = await this.knowledgeAdmin.search({
|
|
127006
127358
|
query: args.query,
|
|
127007
|
-
...typeof args.limit === "number" ? { limit: args.limit } : {}
|
|
127359
|
+
...typeof args.limit === "number" ? { limit: args.limit } : {},
|
|
127360
|
+
...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
|
|
127008
127361
|
});
|
|
127009
127362
|
if (res.results.length === 0) {
|
|
127010
|
-
return "No knowledge pages matched that query in your memory bank. " + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
|
|
127363
|
+
return "No knowledge pages matched that query in " + (typeof args.bank_id === "string" ? `bank '${args.bank_id}'. ` : "your memory bank. ") + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
|
|
127011
127364
|
}
|
|
127012
127365
|
return capKnowledgeResponse(JSON.stringify({ total: res.total, results: res.results }, null, 2));
|
|
127013
127366
|
}
|
|
127014
127367
|
case "get_knowledge_page": {
|
|
127015
127368
|
const page = await this.knowledgeAdmin.getPage({
|
|
127016
|
-
page_id: args.page_id
|
|
127369
|
+
page_id: args.page_id,
|
|
127370
|
+
...typeof args.bank_id === "string" ? { bankId: args.bank_id } : {}
|
|
127017
127371
|
});
|
|
127018
127372
|
return capKnowledgeResponse(page.markdown);
|
|
127019
127373
|
}
|
|
127020
127374
|
case "get_knowledge_tree": {
|
|
127021
|
-
const tree = await this.knowledgeAdmin.tree();
|
|
127375
|
+
const tree = await this.knowledgeAdmin.tree(typeof args.bank_id === "string" ? { bankId: args.bank_id } : {});
|
|
127022
127376
|
if (tree.roots.length === 0) {
|
|
127023
|
-
return "Your knowledge base has no pages yet. Pages are synthesized
|
|
127377
|
+
return typeof args.bank_id === "string" ? `Bank '${args.bank_id}' has no knowledge pages yet.` : "Your knowledge base has no pages yet. Pages are synthesized " + "from mental models \u2014 propose one to start it.";
|
|
127024
127378
|
}
|
|
127025
127379
|
return renderKnowledgeTree(tree.roots);
|
|
127026
127380
|
}
|
|
@@ -127134,9 +127488,11 @@ class HindsightShim {
|
|
|
127134
127488
|
}
|
|
127135
127489
|
function resolveShimOptionsFromEnv(env2) {
|
|
127136
127490
|
const home2 = env2.HOME && env2.HOME !== "/" ? env2.HOME : tmpdir6();
|
|
127491
|
+
const extraBanks = (env2.HINDSIGHT_KNOWLEDGE_EXTRA_BANKS ?? "").split(",").map((b) => b.trim()).filter((b) => b.length > 0 && b !== (env2.HINDSIGHT_BANK_ID || ""));
|
|
127137
127492
|
return {
|
|
127138
127493
|
url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
|
|
127139
127494
|
bankId: env2.HINDSIGHT_BANK_ID || "",
|
|
127495
|
+
...extraBanks.length > 0 ? { extraBanks } : {},
|
|
127140
127496
|
cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join117(home2, ".hindsight-shim")
|
|
127141
127497
|
};
|
|
127142
127498
|
}
|