switchroom 0.19.38 → 0.19.40
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 +10 -1
- package/dist/auth-broker/index.js +15 -6
- package/dist/cli/notion-write-pretool.mjs +10 -1
- package/dist/cli/switchroom.js +1141 -566
- package/dist/host-control/main.js +220 -8
- package/dist/vault/approvals/kernel-server.js +15 -6
- package/dist/vault/broker/server.js +15 -6
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +10 -0
- package/telegram-plugin/dist/bridge/bridge.js +8 -0
- package/telegram-plugin/dist/gateway/gateway.js +450 -206
- package/telegram-plugin/dist/server.js +8 -0
- package/telegram-plugin/gateway/backstop-delivery.ts +48 -0
- package/telegram-plugin/gateway/compaction-marker.ts +84 -0
- package/telegram-plugin/gateway/gateway.ts +3 -3
- package/telegram-plugin/gateway/handback-preturn-signal.ts +16 -0
- package/telegram-plugin/gateway/liveness-wiring.ts +15 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +20 -0
- package/telegram-plugin/gateway/outbox-sweep.ts +116 -18
- package/telegram-plugin/gateway/silence-poke-session-event.ts +13 -0
- package/telegram-plugin/gateway/stream-render.ts +277 -18
- package/telegram-plugin/hooks/compaction-marker-precompact.mjs +70 -0
- package/telegram-plugin/hooks/hooks.json +11 -0
- package/telegram-plugin/hooks/tool-label-pretool.mjs +119 -1
- package/telegram-plugin/session-tail.ts +20 -0
- package/telegram-plugin/silence-poke.ts +28 -0
- package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +19 -12
- package/telegram-plugin/tests/fixtures/pretool-main-2.1.219.json +15 -0
- package/telegram-plugin/tests/fixtures/pretool-subagent-2.1.219.json +16 -0
- package/telegram-plugin/tests/outbox-delivery.test.ts +38 -1
- package/telegram-plugin/tests/outbox-flush-ack-claim-race.test.ts +213 -0
- package/telegram-plugin/tests/outbox-reply-then-recap-e2e.test.ts +1 -1
- package/telegram-plugin/tests/outbox-sweep-flood-breaker.test.ts +4 -4
- package/telegram-plugin/tests/outbox-sweep-listen-button.test.ts +71 -8
- package/telegram-plugin/tests/queued-card-surface.test.ts +262 -0
- package/telegram-plugin/tests/send-reply-golden.test.ts +47 -0
- package/telegram-plugin/tests/sidechain-label-filter-pretool.test.ts +272 -0
- package/telegram-plugin/tests/silence-poke-compaction.test.ts +222 -0
- package/vendor/hindsight-memory/scripts/lib/client.py +7 -4
- package/vendor/hindsight-memory/scripts/lib/config.py +151 -0
- package/vendor/hindsight-memory/scripts/retain.py +19 -15
- package/vendor/hindsight-memory/scripts/tests/test_backfill.py +14 -1
- package/vendor/hindsight-memory/scripts/tests/test_observation_scopes.py +192 -3
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +18 -2
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +57 -4
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.19.
|
|
2123
|
+
var VERSION = "0.19.40", COMMIT_SHA = "89286b22";
|
|
2124
2124
|
|
|
2125
2125
|
// src/cli/resolve-version.ts
|
|
2126
2126
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -13593,7 +13593,7 @@ function resolveObservationScope(configured, env2 = process.env) {
|
|
|
13593
13593
|
return classifyObservationScope(configured);
|
|
13594
13594
|
return classifyObservationScope(env2[OBSERVATION_SCOPES_ENV]);
|
|
13595
13595
|
}
|
|
13596
|
-
var OBSERVATION_SCOPES, OBSERVATION_SCOPES_ENV = "HINDSIGHT_OBSERVATION_SCOPES";
|
|
13596
|
+
var OBSERVATION_SCOPES, OBSERVATION_SCOPES_ENV = "HINDSIGHT_OBSERVATION_SCOPES", OBSERVATION_SCOPE_STRATEGIES;
|
|
13597
13597
|
var init_observation_scopes = __esm(() => {
|
|
13598
13598
|
OBSERVATION_SCOPES = [
|
|
13599
13599
|
"per_tag",
|
|
@@ -13601,13 +13601,19 @@ var init_observation_scopes = __esm(() => {
|
|
|
13601
13601
|
"all_combinations",
|
|
13602
13602
|
"shared"
|
|
13603
13603
|
];
|
|
13604
|
+
OBSERVATION_SCOPE_STRATEGIES = [
|
|
13605
|
+
"curated",
|
|
13606
|
+
"shared",
|
|
13607
|
+
"combined",
|
|
13608
|
+
"off"
|
|
13609
|
+
];
|
|
13604
13610
|
});
|
|
13605
13611
|
|
|
13606
13612
|
// src/config/schema.ts
|
|
13607
13613
|
function isValidTimezone(value) {
|
|
13608
13614
|
return TIMEZONE_REGEX.test(value);
|
|
13609
13615
|
}
|
|
13610
|
-
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13616
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
13611
13617
|
var init_schema = __esm(() => {
|
|
13612
13618
|
init_zod();
|
|
13613
13619
|
init_observation_scopes();
|
|
@@ -13722,7 +13728,8 @@ var init_schema = __esm(() => {
|
|
|
13722
13728
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
13723
13729
|
deny: exports_external.array(exports_external.string()).default([]).describe("Denied tools (overrides allow)")
|
|
13724
13730
|
}).optional();
|
|
13725
|
-
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("
|
|
13731
|
+
ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope \u2014 what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
13732
|
+
ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope \u2014 keeping the stable " + "semantic ones on the source fact \u2014 so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
|
|
13726
13733
|
AgentMemorySchema = exports_external.object({
|
|
13727
13734
|
collection: exports_external.string().describe("Hindsight collection name for this agent"),
|
|
13728
13735
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
@@ -13758,6 +13765,7 @@ var init_schema = __esm(() => {
|
|
|
13758
13765
|
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis \u2014 a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
|
|
13759
13766
|
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true \u2014 Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never \u2026", "from now on \u2026", ' + `"stop doing \u2026", a stated preference, "that's wrong, it's \u2026") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex \u2014 the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
|
|
13760
13767
|
observation_scopes: ObservationScopesSchema,
|
|
13768
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
13761
13769
|
recall: exports_external.object({
|
|
13762
13770
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
13763
13771
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
@@ -14158,6 +14166,7 @@ var init_schema = __esm(() => {
|
|
|
14158
14166
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
14159
14167
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
14160
14168
|
observation_scopes: ObservationScopesSchema,
|
|
14169
|
+
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
14161
14170
|
recall: exports_external.object({
|
|
14162
14171
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
14163
14172
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
@@ -15830,7 +15839,7 @@ function hindsightPgEnv(overrides = new Map) {
|
|
|
15830
15839
|
}
|
|
15831
15840
|
return out;
|
|
15832
15841
|
}
|
|
15833
|
-
var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
15842
|
+
var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144, HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288, HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE", HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS", HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC", HINDSIGHT_PG_DEFAULT_FSYNC = "on", HINDSIGHT_PG_DEFAULTS, HINDSIGHT_PG_ENV_KEYS;
|
|
15834
15843
|
var init_hindsight_pg_defaults = __esm(() => {
|
|
15835
15844
|
HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
15836
15845
|
HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
|
|
@@ -15908,7 +15917,7 @@ function hindsightPerfEnv(caps, overrides = new Map) {
|
|
|
15908
15917
|
function findUnmanagedHindsightEnvKeys(configEnv) {
|
|
15909
15918
|
return Object.keys(configEnv ?? {}).filter((key) => !HINDSIGHT_PERF_ENV_KEYS.has(key) && !HINDSIGHT_PG_ENV_KEYS.has(key)).sort();
|
|
15910
15919
|
}
|
|
15911
|
-
var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND =
|
|
15920
|
+
var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ENV_KEYS;
|
|
15912
15921
|
var init_hindsight_perf_defaults = __esm(() => {
|
|
15913
15922
|
init_hindsight_pg_defaults();
|
|
15914
15923
|
HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
|
|
@@ -29102,6 +29111,7 @@ function buildWorkspaceContext(args) {
|
|
|
29102
29111
|
hindsightRecallSkipTrivial,
|
|
29103
29112
|
hindsightDirectiveCaptureNudge,
|
|
29104
29113
|
hindsightObservationScopes,
|
|
29114
|
+
hindsightObservationScopeStrategy,
|
|
29105
29115
|
hindsightTopicAliasesJson,
|
|
29106
29116
|
hindsightSenderBanksJson,
|
|
29107
29117
|
hindsightTopicFilterMode
|
|
@@ -29156,6 +29166,7 @@ function buildWorkspaceContext(args) {
|
|
|
29156
29166
|
hindsightRecallSkipTrivial,
|
|
29157
29167
|
hindsightDirectiveCaptureNudge,
|
|
29158
29168
|
hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
|
|
29169
|
+
hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
|
|
29159
29170
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
29160
29171
|
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
29161
29172
|
hindsightTopicFilterMode,
|
|
@@ -29421,6 +29432,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
29421
29432
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
29422
29433
|
const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
|
|
29423
29434
|
const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
|
|
29435
|
+
const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
|
|
29424
29436
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
29425
29437
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
29426
29438
|
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
@@ -29457,6 +29469,7 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
|
|
|
29457
29469
|
hindsightRecallSkipTrivial,
|
|
29458
29470
|
hindsightDirectiveCaptureNudge,
|
|
29459
29471
|
hindsightObservationScopes,
|
|
29472
|
+
hindsightObservationScopeStrategy,
|
|
29460
29473
|
hindsightTopicAliasesJson,
|
|
29461
29474
|
hindsightSenderBanksJson,
|
|
29462
29475
|
hindsightTopicFilterMode
|
|
@@ -30402,6 +30415,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
30402
30415
|
const hindsightRecallSkipTrivial = agentConfig.memory?.recall?.skip_trivial === undefined ? undefined : String(agentConfig.memory.recall.skip_trivial);
|
|
30403
30416
|
const hindsightDirectiveCaptureNudge = agentConfig.memory?.directive_capture_nudge === undefined ? undefined : String(agentConfig.memory.directive_capture_nudge);
|
|
30404
30417
|
const hindsightObservationScopes = agentConfig.memory?.observation_scopes;
|
|
30418
|
+
const hindsightObservationScopeStrategy = agentConfig.memory?.observation_scope_strategy;
|
|
30405
30419
|
const topicAliases = agentConfig.channels?.telegram?.topic_aliases;
|
|
30406
30420
|
const hindsightTopicAliasesJson = topicAliases && Object.keys(topicAliases).length > 0 ? JSON.stringify(topicAliases) : undefined;
|
|
30407
30421
|
const senderBanks = switchroomConfig ? resolveUsers(switchroomConfig, name).senderBanks : agentConfig.memory?.recall?.sender_banks ?? {};
|
|
@@ -30451,6 +30465,7 @@ function reconcileAgentInner(name, agentConfigRaw, agentsDir, telegramConfig, sw
|
|
|
30451
30465
|
hindsightRecallSkipTrivial,
|
|
30452
30466
|
hindsightDirectiveCaptureNudge,
|
|
30453
30467
|
hindsightObservationScopesQ: hindsightObservationScopes ? shellSingleQuote(hindsightObservationScopes) : undefined,
|
|
30468
|
+
hindsightObservationScopeStrategyQ: hindsightObservationScopeStrategy ? shellSingleQuote(hindsightObservationScopeStrategy) : undefined,
|
|
30454
30469
|
hindsightTopicAliasesJsonQ: hindsightTopicAliasesJson ? shellSingleQuote(hindsightTopicAliasesJson) : undefined,
|
|
30455
30470
|
hindsightSenderBanksJsonQ: hindsightSenderBanksJson ? shellSingleQuote(hindsightSenderBanksJson) : undefined,
|
|
30456
30471
|
hindsightTopicFilterMode,
|
|
@@ -30874,6 +30889,7 @@ ${body}
|
|
|
30874
30889
|
hindsightRecallSkipTrivial,
|
|
30875
30890
|
hindsightDirectiveCaptureNudge,
|
|
30876
30891
|
hindsightObservationScopes,
|
|
30892
|
+
hindsightObservationScopeStrategy,
|
|
30877
30893
|
hindsightTopicAliasesJson,
|
|
30878
30894
|
hindsightSenderBanksJson,
|
|
30879
30895
|
hindsightTopicFilterMode
|
|
@@ -39115,168 +39131,6 @@ var init_telegram_yaml = __esm(() => {
|
|
|
39115
39131
|
import_yaml12 = __toESM(require_dist(), 1);
|
|
39116
39132
|
});
|
|
39117
39133
|
|
|
39118
|
-
// src/memory/hindsight-tools.ts
|
|
39119
|
-
var HINDSIGHT_MIN_API_VERSION = "0.8.5", HINDSIGHT_REPAIR_MIN_API_VERSION = "0.8.5", EXPECTED_HINDSIGHT_TOOLS;
|
|
39120
|
-
var init_hindsight_tools = __esm(() => {
|
|
39121
|
-
EXPECTED_HINDSIGHT_TOOLS = {
|
|
39122
|
-
recall: { required: ["query"] },
|
|
39123
|
-
reflect: { required: ["query"] },
|
|
39124
|
-
retain: { required: ["content"] },
|
|
39125
|
-
sync_retain: { required: ["content"] },
|
|
39126
|
-
delete_document: { required: ["document_id"] },
|
|
39127
|
-
create_directive: { required: ["content", "name"] },
|
|
39128
|
-
list_directives: { required: [] },
|
|
39129
|
-
delete_directive: { required: ["directive_id"] },
|
|
39130
|
-
create_bank: { required: ["bank_id"] },
|
|
39131
|
-
update_bank: { required: [] },
|
|
39132
|
-
list_banks: { required: [] },
|
|
39133
|
-
create_mental_model: { required: ["name", "source_query"] },
|
|
39134
|
-
list_mental_models: { required: [] },
|
|
39135
|
-
update_mental_model: { required: ["mental_model_id"] },
|
|
39136
|
-
refresh_mental_model: { required: ["mental_model_id"] },
|
|
39137
|
-
list_memories: { required: [] },
|
|
39138
|
-
get_memory: { required: ["memory_id"] },
|
|
39139
|
-
update_memory: { required: ["memory_id"] },
|
|
39140
|
-
invalidate_memory: { required: ["memory_id"] }
|
|
39141
|
-
};
|
|
39142
|
-
});
|
|
39143
|
-
|
|
39144
|
-
// src/memory/hindsight-repair.ts
|
|
39145
|
-
function buildRepairBankArgv(opts) {
|
|
39146
|
-
const hasBank = typeof opts.bank === "string" && opts.bank.length > 0;
|
|
39147
|
-
if (hasBank === Boolean(opts.all)) {
|
|
39148
|
-
throw new Error("pass exactly one of --bank <id> or --all");
|
|
39149
|
-
}
|
|
39150
|
-
const cmdArgs = ["repair-bank"];
|
|
39151
|
-
if (hasBank)
|
|
39152
|
-
cmdArgs.push("--bank", opts.bank);
|
|
39153
|
-
if (opts.all)
|
|
39154
|
-
cmdArgs.push("--all");
|
|
39155
|
-
if (opts.schema)
|
|
39156
|
-
cmdArgs.push("--schema", opts.schema);
|
|
39157
|
-
if (opts.dryRun)
|
|
39158
|
-
cmdArgs.push("--dry-run");
|
|
39159
|
-
const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
|
|
39160
|
-
return [
|
|
39161
|
-
"exec",
|
|
39162
|
-
opts.container ?? HINDSIGHT_CONTAINER_NAME,
|
|
39163
|
-
"sh",
|
|
39164
|
-
"-c",
|
|
39165
|
-
script,
|
|
39166
|
-
"--",
|
|
39167
|
-
...cmdArgs
|
|
39168
|
-
];
|
|
39169
|
-
}
|
|
39170
|
-
function parseRepairSummary(stdout) {
|
|
39171
|
-
const m = stdout.match(/Done:\s*(\d+)\s+schema\(s\),\s*(\d+)\s+bank\(s\) scanned,\s*(\d+)\s+already present,\s*(\d+)\s+created,\s*(\d+)\s+to-create \(dry-run\),\s*(\d+)\s+failed/);
|
|
39172
|
-
if (!m)
|
|
39173
|
-
return null;
|
|
39174
|
-
return {
|
|
39175
|
-
schemas: Number(m[1]),
|
|
39176
|
-
banksScanned: Number(m[2]),
|
|
39177
|
-
alreadyPresent: Number(m[3]),
|
|
39178
|
-
created: Number(m[4]),
|
|
39179
|
-
toCreate: Number(m[5]),
|
|
39180
|
-
failed: Number(m[6])
|
|
39181
|
-
};
|
|
39182
|
-
}
|
|
39183
|
-
function compareApiVersion(a, b) {
|
|
39184
|
-
const part = (v, i) => {
|
|
39185
|
-
const raw = v.split(".")[i];
|
|
39186
|
-
if (raw === undefined)
|
|
39187
|
-
return 0;
|
|
39188
|
-
const n = parseInt(raw, 10);
|
|
39189
|
-
return Number.isFinite(n) ? n : 0;
|
|
39190
|
-
};
|
|
39191
|
-
const len = Math.max(a.split(".").length, b.split(".").length);
|
|
39192
|
-
for (let i = 0;i < len; i++) {
|
|
39193
|
-
const d = part(a, i) - part(b, i);
|
|
39194
|
-
if (d !== 0)
|
|
39195
|
-
return d;
|
|
39196
|
-
}
|
|
39197
|
-
return 0;
|
|
39198
|
-
}
|
|
39199
|
-
async function fetchHindsightApiVersion(mcpUrl, opts = {}) {
|
|
39200
|
-
const doFetch = opts.fetchImpl ?? fetch;
|
|
39201
|
-
const origin = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
39202
|
-
const controller = new AbortController;
|
|
39203
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 4000);
|
|
39204
|
-
try {
|
|
39205
|
-
const res = await doFetch(`${origin}/version`, { signal: controller.signal });
|
|
39206
|
-
if (!res.ok)
|
|
39207
|
-
return null;
|
|
39208
|
-
const body = await res.json();
|
|
39209
|
-
return typeof body?.api_version === "string" ? body.api_version : null;
|
|
39210
|
-
} catch {
|
|
39211
|
-
return null;
|
|
39212
|
-
} finally {
|
|
39213
|
-
clearTimeout(timer);
|
|
39214
|
-
}
|
|
39215
|
-
}
|
|
39216
|
-
function classifyRepairPreflight(liveVersion) {
|
|
39217
|
-
if (liveVersion === null)
|
|
39218
|
-
return { ok: true };
|
|
39219
|
-
if (compareApiVersion(liveVersion, HINDSIGHT_REPAIR_MIN_API_VERSION) < 0) {
|
|
39220
|
-
return {
|
|
39221
|
-
ok: false,
|
|
39222
|
-
reason: `hindsight ${liveVersion} has no \`repair-bank\` command \u2014 it lands in ` + `${HINDSIGHT_REPAIR_MIN_API_VERSION} (upstream #2645). Without it there is no ` + `supported way to rebuild a bank's vector-index coverage. Update the ` + `pinned image in docker/Dockerfile.hindsight and recreate the container ` + `with \`switchroom memory --update\`, then re-run this command.`
|
|
39223
|
-
};
|
|
39224
|
-
}
|
|
39225
|
-
return { ok: true };
|
|
39226
|
-
}
|
|
39227
|
-
function classifyRepairOutcome(run) {
|
|
39228
|
-
if (run.childExit !== 0) {
|
|
39229
|
-
return {
|
|
39230
|
-
exit: REPAIR_FAILED_EXIT,
|
|
39231
|
-
level: "fail",
|
|
39232
|
-
headline: `repair-bank exited ${run.childExit}.`,
|
|
39233
|
-
detail: run.childExit === 2 ? "Exit 2 from the admin CLI is a usage error \u2014 check --bank / --schema spelling. " + "Nothing was repaired and coverage is unknown." : "Failed indexes are dropped, so a re-run is safe and is the documented retry."
|
|
39234
|
-
};
|
|
39235
|
-
}
|
|
39236
|
-
if (run.summary === null) {
|
|
39237
|
-
return {
|
|
39238
|
-
exit: REPAIR_UNVERIFIED_EXIT,
|
|
39239
|
-
level: "fail",
|
|
39240
|
-
headline: "repair-bank exited 0 but printed no summary \u2014 coverage was NOT confirmed.",
|
|
39241
|
-
detail: "Read its output above. This is not a pass: a backend with a single global " + "index, an aborted scan, or an output-format change upstream all look like " + "this, and none of them means coverage is complete."
|
|
39242
|
-
};
|
|
39243
|
-
}
|
|
39244
|
-
if (run.summary.banksScanned === 0) {
|
|
39245
|
-
return {
|
|
39246
|
-
exit: REPAIR_UNVERIFIED_EXIT,
|
|
39247
|
-
level: "fail",
|
|
39248
|
-
headline: "repair-bank scanned 0 bank(s) \u2014 nothing was checked.",
|
|
39249
|
-
detail: "A scan that examined no bank proves nothing about coverage. Usual cause is a " + "--bank id or --schema name that matches nothing; `switchroom memory banks` " + "lists the real ids."
|
|
39250
|
-
};
|
|
39251
|
-
}
|
|
39252
|
-
if (run.summary.failed > 0) {
|
|
39253
|
-
return {
|
|
39254
|
-
exit: REPAIR_FAILED_EXIT,
|
|
39255
|
-
level: "fail",
|
|
39256
|
-
headline: `${run.summary.failed} index(es) failed to build.`,
|
|
39257
|
-
detail: "Those banks still under-return on recall. Failed indexes are dropped, so " + "re-running is safe and is the documented retry; if it repeats, the hindsight " + "container logs carry the postgres error."
|
|
39258
|
-
};
|
|
39259
|
-
}
|
|
39260
|
-
if (run.dryRun && run.summary.toCreate > 0) {
|
|
39261
|
-
return {
|
|
39262
|
-
exit: REPAIR_COVERAGE_MISSING_EXIT,
|
|
39263
|
-
level: "fail",
|
|
39264
|
-
headline: `${run.summary.toCreate} vector index(es) missing across ${run.summary.banksScanned} bank(s).`,
|
|
39265
|
-
detail: "Recall on those banks silently under-returns. Re-run without --dry-run to fix."
|
|
39266
|
-
};
|
|
39267
|
-
}
|
|
39268
|
-
return {
|
|
39269
|
-
exit: 0,
|
|
39270
|
-
level: "ok",
|
|
39271
|
-
headline: "repair-bank finished.",
|
|
39272
|
-
detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
|
|
39273
|
-
};
|
|
39274
|
-
}
|
|
39275
|
-
var HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
|
|
39276
|
-
var init_hindsight_repair = __esm(() => {
|
|
39277
|
-
init_hindsight_tools();
|
|
39278
|
-
});
|
|
39279
|
-
|
|
39280
39134
|
// src/memory/bank-health.ts
|
|
39281
39135
|
function hindsightRestBase(mcpUrl) {
|
|
39282
39136
|
return mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
@@ -39446,6 +39300,168 @@ function recentUnextracted(docs, withinDays = 30, now = new Date, minTextLength
|
|
|
39446
39300
|
}
|
|
39447
39301
|
var DOCUMENTS_PAGE_LIMIT = 500;
|
|
39448
39302
|
|
|
39303
|
+
// src/memory/hindsight-tools.ts
|
|
39304
|
+
var HINDSIGHT_MIN_API_VERSION = "0.8.5", HINDSIGHT_REPAIR_MIN_API_VERSION = "0.8.5", EXPECTED_HINDSIGHT_TOOLS;
|
|
39305
|
+
var init_hindsight_tools = __esm(() => {
|
|
39306
|
+
EXPECTED_HINDSIGHT_TOOLS = {
|
|
39307
|
+
recall: { required: ["query"] },
|
|
39308
|
+
reflect: { required: ["query"] },
|
|
39309
|
+
retain: { required: ["content"] },
|
|
39310
|
+
sync_retain: { required: ["content"] },
|
|
39311
|
+
delete_document: { required: ["document_id"] },
|
|
39312
|
+
create_directive: { required: ["content", "name"] },
|
|
39313
|
+
list_directives: { required: [] },
|
|
39314
|
+
delete_directive: { required: ["directive_id"] },
|
|
39315
|
+
create_bank: { required: ["bank_id"] },
|
|
39316
|
+
update_bank: { required: [] },
|
|
39317
|
+
list_banks: { required: [] },
|
|
39318
|
+
create_mental_model: { required: ["name", "source_query"] },
|
|
39319
|
+
list_mental_models: { required: [] },
|
|
39320
|
+
update_mental_model: { required: ["mental_model_id"] },
|
|
39321
|
+
refresh_mental_model: { required: ["mental_model_id"] },
|
|
39322
|
+
list_memories: { required: [] },
|
|
39323
|
+
get_memory: { required: ["memory_id"] },
|
|
39324
|
+
update_memory: { required: ["memory_id"] },
|
|
39325
|
+
invalidate_memory: { required: ["memory_id"] }
|
|
39326
|
+
};
|
|
39327
|
+
});
|
|
39328
|
+
|
|
39329
|
+
// src/memory/hindsight-repair.ts
|
|
39330
|
+
function buildRepairBankArgv(opts) {
|
|
39331
|
+
const hasBank = typeof opts.bank === "string" && opts.bank.length > 0;
|
|
39332
|
+
if (hasBank === Boolean(opts.all)) {
|
|
39333
|
+
throw new Error("pass exactly one of --bank <id> or --all");
|
|
39334
|
+
}
|
|
39335
|
+
const cmdArgs = ["repair-bank"];
|
|
39336
|
+
if (hasBank)
|
|
39337
|
+
cmdArgs.push("--bank", opts.bank);
|
|
39338
|
+
if (opts.all)
|
|
39339
|
+
cmdArgs.push("--all");
|
|
39340
|
+
if (opts.schema)
|
|
39341
|
+
cmdArgs.push("--schema", opts.schema);
|
|
39342
|
+
if (opts.dryRun)
|
|
39343
|
+
cmdArgs.push("--dry-run");
|
|
39344
|
+
const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
|
|
39345
|
+
return [
|
|
39346
|
+
"exec",
|
|
39347
|
+
opts.container ?? HINDSIGHT_CONTAINER_NAME,
|
|
39348
|
+
"sh",
|
|
39349
|
+
"-c",
|
|
39350
|
+
script,
|
|
39351
|
+
"--",
|
|
39352
|
+
...cmdArgs
|
|
39353
|
+
];
|
|
39354
|
+
}
|
|
39355
|
+
function parseRepairSummary(stdout) {
|
|
39356
|
+
const m = stdout.match(/Done:\s*(\d+)\s+schema\(s\),\s*(\d+)\s+bank\(s\) scanned,\s*(\d+)\s+already present,\s*(\d+)\s+created,\s*(\d+)\s+to-create \(dry-run\),\s*(\d+)\s+failed/);
|
|
39357
|
+
if (!m)
|
|
39358
|
+
return null;
|
|
39359
|
+
return {
|
|
39360
|
+
schemas: Number(m[1]),
|
|
39361
|
+
banksScanned: Number(m[2]),
|
|
39362
|
+
alreadyPresent: Number(m[3]),
|
|
39363
|
+
created: Number(m[4]),
|
|
39364
|
+
toCreate: Number(m[5]),
|
|
39365
|
+
failed: Number(m[6])
|
|
39366
|
+
};
|
|
39367
|
+
}
|
|
39368
|
+
function compareApiVersion(a, b) {
|
|
39369
|
+
const part = (v, i) => {
|
|
39370
|
+
const raw = v.split(".")[i];
|
|
39371
|
+
if (raw === undefined)
|
|
39372
|
+
return 0;
|
|
39373
|
+
const n = parseInt(raw, 10);
|
|
39374
|
+
return Number.isFinite(n) ? n : 0;
|
|
39375
|
+
};
|
|
39376
|
+
const len = Math.max(a.split(".").length, b.split(".").length);
|
|
39377
|
+
for (let i = 0;i < len; i++) {
|
|
39378
|
+
const d = part(a, i) - part(b, i);
|
|
39379
|
+
if (d !== 0)
|
|
39380
|
+
return d;
|
|
39381
|
+
}
|
|
39382
|
+
return 0;
|
|
39383
|
+
}
|
|
39384
|
+
async function fetchHindsightApiVersion(mcpUrl, opts = {}) {
|
|
39385
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
39386
|
+
const origin = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
39387
|
+
const controller = new AbortController;
|
|
39388
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 4000);
|
|
39389
|
+
try {
|
|
39390
|
+
const res = await doFetch(`${origin}/version`, { signal: controller.signal });
|
|
39391
|
+
if (!res.ok)
|
|
39392
|
+
return null;
|
|
39393
|
+
const body = await res.json();
|
|
39394
|
+
return typeof body?.api_version === "string" ? body.api_version : null;
|
|
39395
|
+
} catch {
|
|
39396
|
+
return null;
|
|
39397
|
+
} finally {
|
|
39398
|
+
clearTimeout(timer);
|
|
39399
|
+
}
|
|
39400
|
+
}
|
|
39401
|
+
function classifyRepairPreflight(liveVersion) {
|
|
39402
|
+
if (liveVersion === null)
|
|
39403
|
+
return { ok: true };
|
|
39404
|
+
if (compareApiVersion(liveVersion, HINDSIGHT_REPAIR_MIN_API_VERSION) < 0) {
|
|
39405
|
+
return {
|
|
39406
|
+
ok: false,
|
|
39407
|
+
reason: `hindsight ${liveVersion} has no \`repair-bank\` command \u2014 it lands in ` + `${HINDSIGHT_REPAIR_MIN_API_VERSION} (upstream #2645). Without it there is no ` + `supported way to rebuild a bank's vector-index coverage. Update the ` + `pinned image in docker/Dockerfile.hindsight and recreate the container ` + `with \`switchroom memory --update\`, then re-run this command.`
|
|
39408
|
+
};
|
|
39409
|
+
}
|
|
39410
|
+
return { ok: true };
|
|
39411
|
+
}
|
|
39412
|
+
function classifyRepairOutcome(run) {
|
|
39413
|
+
if (run.childExit !== 0) {
|
|
39414
|
+
return {
|
|
39415
|
+
exit: REPAIR_FAILED_EXIT,
|
|
39416
|
+
level: "fail",
|
|
39417
|
+
headline: `repair-bank exited ${run.childExit}.`,
|
|
39418
|
+
detail: run.childExit === 2 ? "Exit 2 from the admin CLI is a usage error \u2014 check --bank / --schema spelling. " + "Nothing was repaired and coverage is unknown." : "Failed indexes are dropped, so a re-run is safe and is the documented retry."
|
|
39419
|
+
};
|
|
39420
|
+
}
|
|
39421
|
+
if (run.summary === null) {
|
|
39422
|
+
return {
|
|
39423
|
+
exit: REPAIR_UNVERIFIED_EXIT,
|
|
39424
|
+
level: "fail",
|
|
39425
|
+
headline: "repair-bank exited 0 but printed no summary \u2014 coverage was NOT confirmed.",
|
|
39426
|
+
detail: "Read its output above. This is not a pass: a backend with a single global " + "index, an aborted scan, or an output-format change upstream all look like " + "this, and none of them means coverage is complete."
|
|
39427
|
+
};
|
|
39428
|
+
}
|
|
39429
|
+
if (run.summary.banksScanned === 0) {
|
|
39430
|
+
return {
|
|
39431
|
+
exit: REPAIR_UNVERIFIED_EXIT,
|
|
39432
|
+
level: "fail",
|
|
39433
|
+
headline: "repair-bank scanned 0 bank(s) \u2014 nothing was checked.",
|
|
39434
|
+
detail: "A scan that examined no bank proves nothing about coverage. Usual cause is a " + "--bank id or --schema name that matches nothing; `switchroom memory banks` " + "lists the real ids."
|
|
39435
|
+
};
|
|
39436
|
+
}
|
|
39437
|
+
if (run.summary.failed > 0) {
|
|
39438
|
+
return {
|
|
39439
|
+
exit: REPAIR_FAILED_EXIT,
|
|
39440
|
+
level: "fail",
|
|
39441
|
+
headline: `${run.summary.failed} index(es) failed to build.`,
|
|
39442
|
+
detail: "Those banks still under-return on recall. Failed indexes are dropped, so " + "re-running is safe and is the documented retry; if it repeats, the hindsight " + "container logs carry the postgres error."
|
|
39443
|
+
};
|
|
39444
|
+
}
|
|
39445
|
+
if (run.dryRun && run.summary.toCreate > 0) {
|
|
39446
|
+
return {
|
|
39447
|
+
exit: REPAIR_COVERAGE_MISSING_EXIT,
|
|
39448
|
+
level: "fail",
|
|
39449
|
+
headline: `${run.summary.toCreate} vector index(es) missing across ${run.summary.banksScanned} bank(s).`,
|
|
39450
|
+
detail: "Recall on those banks silently under-returns. Re-run without --dry-run to fix."
|
|
39451
|
+
};
|
|
39452
|
+
}
|
|
39453
|
+
return {
|
|
39454
|
+
exit: 0,
|
|
39455
|
+
level: "ok",
|
|
39456
|
+
headline: "repair-bank finished.",
|
|
39457
|
+
detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
|
|
39458
|
+
};
|
|
39459
|
+
}
|
|
39460
|
+
var HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
|
|
39461
|
+
var init_hindsight_repair = __esm(() => {
|
|
39462
|
+
init_hindsight_tools();
|
|
39463
|
+
});
|
|
39464
|
+
|
|
39449
39465
|
// src/host-control/audit-rotation-config.ts
|
|
39450
39466
|
var DEFAULT_HOSTD_AUDIT_MAX_BYTES, DEFAULT_HOSTD_AUDIT_MAX_FILES = 3;
|
|
39451
39467
|
var init_audit_rotation_config = __esm(() => {
|
|
@@ -45781,6 +45797,8 @@ function classifyObservationScopeSaturation(reports) {
|
|
|
45781
45797
|
}
|
|
45782
45798
|
const unreadable = reports.filter((r) => !r.ok);
|
|
45783
45799
|
const readable = reports.filter((r) => r.ok);
|
|
45800
|
+
const growing = readable.filter((r) => r.observationsEnabled && r.untaggedCount >= OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT).sort((a, b) => b.untaggedCount - a.untaggedCount);
|
|
45801
|
+
const growthNote = growing.length > 0 ? `${growing.length} bank(s) with an untagged global scope over ` + `${OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT.toLocaleString()} observations ` + `(uncapped curated sink, growth watch): ` + growing.slice(0, OBSERVATION_SCOPE_MAX_LISTED).map((r) => `${r.bankId} ${r.untaggedCount.toLocaleString()}`).join(", ") + (growing.length > OBSERVATION_SCOPE_MAX_LISTED ? `; +${growing.length - OBSERVATION_SCOPE_MAX_LISTED} more` : "") : "";
|
|
45784
45802
|
if (readable.length === 0) {
|
|
45785
45803
|
return {
|
|
45786
45804
|
name,
|
|
@@ -45803,7 +45821,11 @@ function classifyObservationScopeSaturation(reports) {
|
|
|
45803
45821
|
notes.push(`${disabled.length} bank(s) have observations disabled: ` + disabled.map((r) => r.bankId).join(", "));
|
|
45804
45822
|
}
|
|
45805
45823
|
const suffix = notes.length > 0 ? ` \u00b7 ${notes.join(" \u00b7 ")}` : "";
|
|
45824
|
+
const growthSuffix = growthNote ? ` \u00b7 ${growthNote}` : "";
|
|
45806
45825
|
if (judged.length === 0) {
|
|
45826
|
+
if (growing.length > 0) {
|
|
45827
|
+
return { name, status: "warn", detail: `${growthNote}${suffix}` };
|
|
45828
|
+
}
|
|
45807
45829
|
return { name, status: "skip", detail: `no bank could be judged${suffix}` };
|
|
45808
45830
|
}
|
|
45809
45831
|
const closed = judged.filter((r) => r.cap === 0);
|
|
@@ -45835,17 +45857,14 @@ function classifyObservationScopeSaturation(reports) {
|
|
|
45835
45857
|
return {
|
|
45836
45858
|
name,
|
|
45837
45859
|
status: "fail",
|
|
45838
|
-
detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + suffix,
|
|
45860
|
+
detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + growthSuffix + suffix,
|
|
45839
45861
|
fix
|
|
45840
45862
|
};
|
|
45841
45863
|
}
|
|
45842
|
-
if (warning.length > 0) {
|
|
45843
|
-
|
|
45844
|
-
|
|
45845
|
-
|
|
45846
|
-
detail: `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) + suffix,
|
|
45847
|
-
fix
|
|
45848
|
-
};
|
|
45864
|
+
if (warning.length > 0 || growing.length > 0) {
|
|
45865
|
+
const scopeClause = warning.length > 0 ? `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) : "";
|
|
45866
|
+
const detail = warning.length > 0 ? scopeClause + growthSuffix + suffix : `${growthNote}${suffix}`;
|
|
45867
|
+
return { name, status: "warn", detail, ...warning.length > 0 ? { fix } : {} };
|
|
45849
45868
|
}
|
|
45850
45869
|
const totalScopes = capped.reduce((n, r) => n + r.scopeCount, 0);
|
|
45851
45870
|
const detailBase = capped.length === 0 ? `no bank has a positive observation cap` : `${totalScopes} scope(s) across ${capped.length} bank(s) below ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap`;
|
|
@@ -45862,7 +45881,7 @@ function classifyObservationScopeSaturation(reports) {
|
|
|
45862
45881
|
detail: detailBase + (extra.length > 0 ? ` \u00b7 ${extra.join(" \u00b7 ")}` : "") + suffix
|
|
45863
45882
|
};
|
|
45864
45883
|
}
|
|
45865
|
-
async function
|
|
45884
|
+
async function getJson3(url, opts) {
|
|
45866
45885
|
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
45867
45886
|
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
45868
45887
|
const controller = new AbortController;
|
|
@@ -45891,10 +45910,11 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
|
|
|
45891
45910
|
observationsEnabled: false,
|
|
45892
45911
|
hasScopeLimitRules: false,
|
|
45893
45912
|
scopeCount: 0,
|
|
45913
|
+
untaggedCount: 0,
|
|
45894
45914
|
saturated: [],
|
|
45895
45915
|
pendingConsolidation: null
|
|
45896
45916
|
});
|
|
45897
|
-
const cfg = await
|
|
45917
|
+
const cfg = await getJson3(`${base}/v1/default/banks/${bank}/config`, opts);
|
|
45898
45918
|
if (!cfg.ok)
|
|
45899
45919
|
return fail4(cfg.reason);
|
|
45900
45920
|
const config = cfg.data?.config;
|
|
@@ -45907,7 +45927,7 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
|
|
|
45907
45927
|
const rules = config["observation_scope_limits"];
|
|
45908
45928
|
const hasScopeLimitRules = Array.isArray(rules) && rules.length > 0;
|
|
45909
45929
|
const observationsEnabled = config["enable_observations"] !== false;
|
|
45910
|
-
const scopesResp = await
|
|
45930
|
+
const scopesResp = await getJson3(`${base}/v1/default/banks/${bank}/observations/scopes`, opts);
|
|
45911
45931
|
if (!scopesResp.ok)
|
|
45912
45932
|
return fail4(scopesResp.reason);
|
|
45913
45933
|
const rawScopes = scopesResp.data?.scopes;
|
|
@@ -45924,9 +45944,10 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
|
|
|
45924
45944
|
scopes.push({ tags, count });
|
|
45925
45945
|
}
|
|
45926
45946
|
const saturated = computeScopeSaturation(scopes, rawCap).filter((s) => s.status !== "ok");
|
|
45947
|
+
const untaggedCount = scopes.filter((s) => s.tags.length === 0).reduce((n, s) => n + s.count, 0);
|
|
45927
45948
|
let pendingConsolidation = null;
|
|
45928
45949
|
if (saturated.length > 0 && !hasScopeLimitRules && observationsEnabled) {
|
|
45929
|
-
const stats = await
|
|
45950
|
+
const stats = await getJson3(`${base}/v1/default/banks/${bank}/stats`, opts);
|
|
45930
45951
|
if (stats.ok && typeof stats.data?.pending_consolidation === "number") {
|
|
45931
45952
|
pendingConsolidation = stats.data.pending_consolidation;
|
|
45932
45953
|
}
|
|
@@ -45938,6 +45959,7 @@ async function inspectBankScopes(apiUrl, bankId, opts) {
|
|
|
45938
45959
|
observationsEnabled,
|
|
45939
45960
|
hasScopeLimitRules,
|
|
45940
45961
|
scopeCount: scopes.length,
|
|
45962
|
+
untaggedCount,
|
|
45941
45963
|
saturated,
|
|
45942
45964
|
pendingConsolidation
|
|
45943
45965
|
};
|
|
@@ -45953,7 +45975,7 @@ async function checkObservationScopeSaturation(config, apiUrl, opts) {
|
|
|
45953
45975
|
const reports = await Promise.all([...banks].map((bankId) => inspectBankScopes(apiUrl, bankId, opts)));
|
|
45954
45976
|
return classifyObservationScopeSaturation(reports);
|
|
45955
45977
|
}
|
|
45956
|
-
var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6;
|
|
45978
|
+
var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6, OBSERVATION_SCOPE_UNTAGGED_WARN_COUNT = 1e4;
|
|
45957
45979
|
var init_doctor_observation_scopes = __esm(() => {
|
|
45958
45980
|
init_merge();
|
|
45959
45981
|
init_hindsight2();
|
|
@@ -45987,6 +46009,38 @@ function installCron(opts) {
|
|
|
45987
46009
|
renameSync15(tmp, path6);
|
|
45988
46010
|
return { status: "installed", path: path6, content };
|
|
45989
46011
|
}
|
|
46012
|
+
function parseCronUser(content) {
|
|
46013
|
+
for (const line of content.split(`
|
|
46014
|
+
`)) {
|
|
46015
|
+
const trimmed = line.trim();
|
|
46016
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
46017
|
+
continue;
|
|
46018
|
+
if (!trimmed.includes("hindsight-watch"))
|
|
46019
|
+
continue;
|
|
46020
|
+
const fields = trimmed.split(/\s+/);
|
|
46021
|
+
if (fields.length >= 7)
|
|
46022
|
+
return fields[5];
|
|
46023
|
+
}
|
|
46024
|
+
return null;
|
|
46025
|
+
}
|
|
46026
|
+
function reconcileCron(opts) {
|
|
46027
|
+
const path6 = opts.path ?? CRON_PATH;
|
|
46028
|
+
if (!existsSync63(path6))
|
|
46029
|
+
return { status: "absent", path: path6 };
|
|
46030
|
+
let user = opts.fallbackUser;
|
|
46031
|
+
try {
|
|
46032
|
+
user = parseCronUser(readFileSync60(path6, "utf8")) ?? opts.fallbackUser;
|
|
46033
|
+
} catch {}
|
|
46034
|
+
if (!user) {
|
|
46035
|
+
throw new Error(`cannot reconcile ${path6}: no cron-user parseable from the existing fragment and no fallback user available`);
|
|
46036
|
+
}
|
|
46037
|
+
const r = installCron({ user, binary: opts.binary, path: path6 });
|
|
46038
|
+
return {
|
|
46039
|
+
status: r.status === "installed" ? "reconciled" : "unchanged",
|
|
46040
|
+
path: path6,
|
|
46041
|
+
content: r.content
|
|
46042
|
+
};
|
|
46043
|
+
}
|
|
45990
46044
|
var CRON_PATH = "/etc/cron.d/hindsight-watch", CRON_SCHEDULE = "*/15 * * * *", CRON_LOG_PATH = "/var/log/hindsight-watch.log", CRON_LOCK_PATH = "/run/lock/hindsight-watch.lock";
|
|
45991
46045
|
var init_install_cron = () => {};
|
|
45992
46046
|
|
|
@@ -51689,6 +51743,9 @@ import { spawnSync as spawnSync12 } from "node:child_process";
|
|
|
51689
51743
|
function fixFor(c, target) {
|
|
51690
51744
|
return remediationFor(classifyComponent(c), target);
|
|
51691
51745
|
}
|
|
51746
|
+
function hostCliBehindStatus(c) {
|
|
51747
|
+
return c.kind === "cli" ? "fail" : "warn";
|
|
51748
|
+
}
|
|
51692
51749
|
function runComponentVersionChecks(config, opts = {}) {
|
|
51693
51750
|
const exec = opts.exec ?? defaultExec2;
|
|
51694
51751
|
const components = collectComponents(opts.cliVersion ?? SWITCHROOM_VERSION, exec);
|
|
@@ -51712,10 +51769,11 @@ function runComponentVersionChecks(config, opts = {}) {
|
|
|
51712
51769
|
});
|
|
51713
51770
|
}
|
|
51714
51771
|
for (const c of report.behind) {
|
|
51772
|
+
const status = hostCliBehindStatus(c);
|
|
51715
51773
|
results.push({
|
|
51716
51774
|
name: `component behind: ${c.name}`,
|
|
51717
|
-
status
|
|
51718
|
-
detail: `on ${c.version}, expected ${report.target} (${src})`,
|
|
51775
|
+
status,
|
|
51776
|
+
detail: status === "fail" ? `on ${c.version}, expected ${report.target} (${src}) \u2014 the host CLI must never trail the fleet; it self-heals in-band via \`switchroom update\`` : `on ${c.version}, expected ${report.target} (${src})`,
|
|
51719
51777
|
fix: fixFor(c, report.target)
|
|
51720
51778
|
});
|
|
51721
51779
|
}
|
|
@@ -68980,9 +69038,9 @@ __export(exports_server, {
|
|
|
68980
69038
|
dispatchTool: () => dispatchTool,
|
|
68981
69039
|
TOOLS: () => TOOLS
|
|
68982
69040
|
});
|
|
68983
|
-
import { spawnSync as
|
|
69041
|
+
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
68984
69042
|
function execCli(args, stdin) {
|
|
68985
|
-
const r =
|
|
69043
|
+
const r = spawnSync23(CLI_BIN, args, {
|
|
68986
69044
|
encoding: "utf-8",
|
|
68987
69045
|
env: process.env,
|
|
68988
69046
|
timeout: 15000,
|
|
@@ -69491,7 +69549,7 @@ __export(exports_server2, {
|
|
|
69491
69549
|
TOOLS: () => TOOLS2
|
|
69492
69550
|
});
|
|
69493
69551
|
import { randomBytes as randomBytes16 } from "node:crypto";
|
|
69494
|
-
import { existsSync as
|
|
69552
|
+
import { existsSync as existsSync106 } from "node:fs";
|
|
69495
69553
|
function selfSocketPath() {
|
|
69496
69554
|
return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
|
|
69497
69555
|
}
|
|
@@ -69518,7 +69576,7 @@ async function dispatchTool2(name, args) {
|
|
|
69518
69576
|
return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
|
|
69519
69577
|
}
|
|
69520
69578
|
const sockPath = selfSocketPath();
|
|
69521
|
-
if (!
|
|
69579
|
+
if (!existsSync106(sockPath)) {
|
|
69522
69580
|
return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
|
|
69523
69581
|
}
|
|
69524
69582
|
let req;
|
|
@@ -69769,13 +69827,13 @@ function resolveAuditLogPath() {
|
|
|
69769
69827
|
if (process.env.HOSTD_AUDIT_LOG_PATH)
|
|
69770
69828
|
return process.env.HOSTD_AUDIT_LOG_PATH;
|
|
69771
69829
|
const bindMounted = "/host-home/.switchroom/host-control-audit.log";
|
|
69772
|
-
if (
|
|
69830
|
+
if (existsSync106(bindMounted))
|
|
69773
69831
|
return bindMounted;
|
|
69774
69832
|
return defaultAuditLogPath2();
|
|
69775
69833
|
}
|
|
69776
69834
|
function getLastUpdateApplyStatus() {
|
|
69777
69835
|
const path10 = resolveAuditLogPath();
|
|
69778
|
-
if (!
|
|
69836
|
+
if (!existsSync106(path10)) {
|
|
69779
69837
|
return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
|
|
69780
69838
|
}
|
|
69781
69839
|
let raw;
|
|
@@ -70494,11 +70552,11 @@ var init_test_agents = __esm(() => {
|
|
|
70494
70552
|
});
|
|
70495
70553
|
|
|
70496
70554
|
// src/litellm/header-passthrough-guard.ts
|
|
70497
|
-
import { existsSync as
|
|
70555
|
+
import { existsSync as existsSync109, readdirSync as readdirSync41 } from "node:fs";
|
|
70498
70556
|
function discoverLiveLitellmConfigPath(opts) {
|
|
70499
70557
|
const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
|
|
70500
70558
|
const readdir2 = opts?.readdirFn ?? ((p) => readdirSync41(p));
|
|
70501
|
-
const exists = opts?.existsFn ??
|
|
70559
|
+
const exists = opts?.existsFn ?? existsSync109;
|
|
70502
70560
|
let entries;
|
|
70503
70561
|
try {
|
|
70504
70562
|
entries = readdir2(servicesDir);
|
|
@@ -70588,7 +70646,7 @@ var init_header_passthrough_guard = __esm(() => {
|
|
|
70588
70646
|
});
|
|
70589
70647
|
|
|
70590
70648
|
// src/fleet-health/litellm-config-sensor.ts
|
|
70591
|
-
import { readFileSync as
|
|
70649
|
+
import { readFileSync as readFileSync98, existsSync as existsSync110 } from "node:fs";
|
|
70592
70650
|
function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
|
|
70593
70651
|
if (explicit)
|
|
70594
70652
|
return explicit;
|
|
@@ -70599,8 +70657,8 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
|
|
|
70599
70657
|
}
|
|
70600
70658
|
function scanLitellmConfig(opts = {}) {
|
|
70601
70659
|
const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
|
|
70602
|
-
const exists = opts.existsFn ??
|
|
70603
|
-
const read = opts.readFn ?? ((p) =>
|
|
70660
|
+
const exists = opts.existsFn ?? existsSync110;
|
|
70661
|
+
const read = opts.readFn ?? ((p) => readFileSync98(p, "utf-8"));
|
|
70604
70662
|
const log = opts.log ?? (() => {});
|
|
70605
70663
|
const nowIso = opts.nowIso ?? new Date().toISOString();
|
|
70606
70664
|
if (path10 === null) {
|
|
@@ -70675,20 +70733,20 @@ __export(exports_scan, {
|
|
|
70675
70733
|
ledgerPathForBase: () => ledgerPathForBase
|
|
70676
70734
|
});
|
|
70677
70735
|
import {
|
|
70678
|
-
readFileSync as
|
|
70736
|
+
readFileSync as readFileSync99,
|
|
70679
70737
|
readdirSync as readdirSync42,
|
|
70680
|
-
existsSync as
|
|
70681
|
-
mkdirSync as
|
|
70682
|
-
writeFileSync as
|
|
70738
|
+
existsSync as existsSync111,
|
|
70739
|
+
mkdirSync as mkdirSync64,
|
|
70740
|
+
writeFileSync as writeFileSync47
|
|
70683
70741
|
} from "node:fs";
|
|
70684
|
-
import { resolve as resolve61, dirname as
|
|
70742
|
+
import { resolve as resolve61, dirname as dirname43 } from "node:path";
|
|
70685
70743
|
import { homedir as homedir61 } from "node:os";
|
|
70686
70744
|
function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir61()) {
|
|
70687
70745
|
return resolve61(home2, ".switchroom");
|
|
70688
70746
|
}
|
|
70689
70747
|
function listAgents(base) {
|
|
70690
70748
|
const dir = resolve61(base, "agents");
|
|
70691
|
-
if (!
|
|
70749
|
+
if (!existsSync111(dir))
|
|
70692
70750
|
return [];
|
|
70693
70751
|
try {
|
|
70694
70752
|
return readdirSync42(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
@@ -70717,16 +70775,16 @@ function runScan(opts = {}) {
|
|
|
70717
70775
|
let gwText = "";
|
|
70718
70776
|
let sawArtifact = false;
|
|
70719
70777
|
try {
|
|
70720
|
-
if (
|
|
70721
|
-
turnsText =
|
|
70778
|
+
if (existsSync111(turnsPath)) {
|
|
70779
|
+
turnsText = readFileSync99(turnsPath, "utf-8");
|
|
70722
70780
|
sawArtifact = true;
|
|
70723
70781
|
}
|
|
70724
70782
|
} catch (e) {
|
|
70725
70783
|
log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
|
|
70726
70784
|
}
|
|
70727
70785
|
try {
|
|
70728
|
-
if (
|
|
70729
|
-
gwText =
|
|
70786
|
+
if (existsSync111(gwPath)) {
|
|
70787
|
+
gwText = readFileSync99(gwPath, "utf-8");
|
|
70730
70788
|
sawArtifact = true;
|
|
70731
70789
|
}
|
|
70732
70790
|
} catch (e) {
|
|
@@ -70768,20 +70826,20 @@ function runScan(opts = {}) {
|
|
|
70768
70826
|
function readLedgerIfPresent(base) {
|
|
70769
70827
|
const path10 = ledgerPathForBase(base);
|
|
70770
70828
|
try {
|
|
70771
|
-
if (!
|
|
70829
|
+
if (!existsSync111(path10))
|
|
70772
70830
|
return null;
|
|
70773
|
-
return JSON.parse(
|
|
70831
|
+
return JSON.parse(readFileSync99(path10, "utf-8"));
|
|
70774
70832
|
} catch {
|
|
70775
70833
|
return null;
|
|
70776
70834
|
}
|
|
70777
70835
|
}
|
|
70778
70836
|
function ledgerPathForBase(base) {
|
|
70779
|
-
return fleetHealthLedgerPath(
|
|
70837
|
+
return fleetHealthLedgerPath(dirname43(base));
|
|
70780
70838
|
}
|
|
70781
70839
|
function writeLedger(base, ledger) {
|
|
70782
70840
|
const path10 = ledgerPathForBase(base);
|
|
70783
|
-
|
|
70784
|
-
|
|
70841
|
+
mkdirSync64(dirname43(path10), { recursive: true });
|
|
70842
|
+
writeFileSync47(path10, JSON.stringify(ledger, null, 2) + `
|
|
70785
70843
|
`, "utf-8");
|
|
70786
70844
|
return path10;
|
|
70787
70845
|
}
|
|
@@ -85438,6 +85496,174 @@ function reflectAcrossAgents(config) {
|
|
|
85438
85496
|
return { eligible, excluded, commands };
|
|
85439
85497
|
}
|
|
85440
85498
|
|
|
85499
|
+
// src/memory/observation-scope-cleanup.ts
|
|
85500
|
+
var CLEANUP_VOLATILE_PATTERNS = [
|
|
85501
|
+
/^parent_session:/,
|
|
85502
|
+
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(-sub-.+)?$/
|
|
85503
|
+
];
|
|
85504
|
+
var CLEANUP_STALE_PATTERNS = [/^agent-[0-9a-f]{8,}$/];
|
|
85505
|
+
function matchesAny(tag, patterns) {
|
|
85506
|
+
return patterns.some((p) => p.test(tag));
|
|
85507
|
+
}
|
|
85508
|
+
function scopeKey(tags) {
|
|
85509
|
+
return tags.length === 0 ? "shared" : [...tags].sort().join(",");
|
|
85510
|
+
}
|
|
85511
|
+
function planScopeCleanup(bankId, scopes, options = {}) {
|
|
85512
|
+
const volatilePatterns = options.volatilePatterns ?? CLEANUP_VOLATILE_PATTERNS;
|
|
85513
|
+
const stalePatterns = options.stalePatterns ?? CLEANUP_STALE_PATTERNS;
|
|
85514
|
+
const retireStale = options.retireStale ?? false;
|
|
85515
|
+
const totalScopes = scopes.length;
|
|
85516
|
+
const totalObservations = scopes.reduce((n, s) => n + s.count, 0);
|
|
85517
|
+
const reclassified = [];
|
|
85518
|
+
const breakdown = {
|
|
85519
|
+
allVolatileScopes: 0,
|
|
85520
|
+
allVolatileObservations: 0,
|
|
85521
|
+
mixedScopes: 0,
|
|
85522
|
+
mixedObservations: 0,
|
|
85523
|
+
staleScopes: 0,
|
|
85524
|
+
staleObservations: 0
|
|
85525
|
+
};
|
|
85526
|
+
const targets = new Map;
|
|
85527
|
+
for (const s of scopes) {
|
|
85528
|
+
const volatileTags = s.tags.filter((t) => matchesAny(t, volatilePatterns));
|
|
85529
|
+
const staleTags = s.tags.filter((t) => matchesAny(t, stalePatterns));
|
|
85530
|
+
const stripped = s.tags.filter((t) => {
|
|
85531
|
+
if (matchesAny(t, volatilePatterns))
|
|
85532
|
+
return false;
|
|
85533
|
+
if (retireStale && matchesAny(t, stalePatterns))
|
|
85534
|
+
return false;
|
|
85535
|
+
return true;
|
|
85536
|
+
});
|
|
85537
|
+
const toKey = scopeKey(stripped);
|
|
85538
|
+
const changed = scopeKey(s.tags) !== toKey;
|
|
85539
|
+
const hasStable = s.tags.some((t) => !matchesAny(t, volatilePatterns) && !matchesAny(t, stalePatterns));
|
|
85540
|
+
if (staleTags.length > 0) {
|
|
85541
|
+
breakdown.staleScopes += 1;
|
|
85542
|
+
breakdown.staleObservations += s.count;
|
|
85543
|
+
}
|
|
85544
|
+
if (volatileTags.length > 0 && !hasStable && staleTags.length === 0) {
|
|
85545
|
+
breakdown.allVolatileScopes += 1;
|
|
85546
|
+
breakdown.allVolatileObservations += s.count;
|
|
85547
|
+
} else if (volatileTags.length > 0 && hasStable) {
|
|
85548
|
+
breakdown.mixedScopes += 1;
|
|
85549
|
+
breakdown.mixedObservations += s.count;
|
|
85550
|
+
}
|
|
85551
|
+
const entry = targets.get(toKey) ?? { sources: 0, observations: 0, changed: false };
|
|
85552
|
+
entry.sources += 1;
|
|
85553
|
+
entry.observations += s.count;
|
|
85554
|
+
entry.changed = entry.changed || changed;
|
|
85555
|
+
targets.set(toKey, entry);
|
|
85556
|
+
if (changed) {
|
|
85557
|
+
reclassified.push({
|
|
85558
|
+
from: s.tags,
|
|
85559
|
+
to: toKey,
|
|
85560
|
+
count: s.count,
|
|
85561
|
+
strippedVolatile: volatileTags,
|
|
85562
|
+
strippedStale: retireStale ? staleTags : []
|
|
85563
|
+
});
|
|
85564
|
+
}
|
|
85565
|
+
}
|
|
85566
|
+
reclassified.sort((a, b) => b.count - a.count);
|
|
85567
|
+
const merges = [...targets.entries()].filter(([, v]) => v.sources > 1 && v.changed).map(([target, v]) => ({ target, sourceScopes: v.sources, observations: v.observations })).sort((a, b) => b.observations - a.observations);
|
|
85568
|
+
const affectedObservations = reclassified.reduce((n, r) => n + r.count, 0);
|
|
85569
|
+
const resultingScopeCount = targets.size;
|
|
85570
|
+
return {
|
|
85571
|
+
bankId,
|
|
85572
|
+
ok: true,
|
|
85573
|
+
totalScopes,
|
|
85574
|
+
totalObservations,
|
|
85575
|
+
reclassified,
|
|
85576
|
+
merges,
|
|
85577
|
+
affectedScopes: reclassified.length,
|
|
85578
|
+
affectedObservations,
|
|
85579
|
+
resultingScopeCount,
|
|
85580
|
+
scopeReduction: totalScopes - resultingScopeCount,
|
|
85581
|
+
breakdown
|
|
85582
|
+
};
|
|
85583
|
+
}
|
|
85584
|
+
async function getJson2(url, opts) {
|
|
85585
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
85586
|
+
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
85587
|
+
const controller = new AbortController;
|
|
85588
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
85589
|
+
try {
|
|
85590
|
+
const resp = await fetchImpl(url, { signal: controller.signal });
|
|
85591
|
+
clearTimeout(timeout);
|
|
85592
|
+
if (!resp.ok)
|
|
85593
|
+
return { ok: false, reason: `HTTP ${resp.status}` };
|
|
85594
|
+
return { ok: true, data: await resp.json() };
|
|
85595
|
+
} catch (err) {
|
|
85596
|
+
clearTimeout(timeout);
|
|
85597
|
+
if (err.name === "AbortError")
|
|
85598
|
+
return { ok: false, reason: "Timeout" };
|
|
85599
|
+
return { ok: false, reason: String(err.message ?? err) };
|
|
85600
|
+
}
|
|
85601
|
+
}
|
|
85602
|
+
async function planBankCleanup(apiUrl, bankId, options = {}, fetchOpts) {
|
|
85603
|
+
const base = hindsightRestBase(apiUrl);
|
|
85604
|
+
const bank = encodeURIComponent(bankId);
|
|
85605
|
+
const resp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, fetchOpts);
|
|
85606
|
+
if (!resp.ok) {
|
|
85607
|
+
return {
|
|
85608
|
+
bankId,
|
|
85609
|
+
ok: false,
|
|
85610
|
+
reason: resp.reason,
|
|
85611
|
+
totalScopes: 0,
|
|
85612
|
+
totalObservations: 0,
|
|
85613
|
+
reclassified: [],
|
|
85614
|
+
merges: [],
|
|
85615
|
+
affectedScopes: 0,
|
|
85616
|
+
affectedObservations: 0,
|
|
85617
|
+
resultingScopeCount: 0,
|
|
85618
|
+
scopeReduction: 0,
|
|
85619
|
+
breakdown: {
|
|
85620
|
+
allVolatileScopes: 0,
|
|
85621
|
+
allVolatileObservations: 0,
|
|
85622
|
+
mixedScopes: 0,
|
|
85623
|
+
mixedObservations: 0,
|
|
85624
|
+
staleScopes: 0,
|
|
85625
|
+
staleObservations: 0
|
|
85626
|
+
}
|
|
85627
|
+
};
|
|
85628
|
+
}
|
|
85629
|
+
const raw = resp.data?.scopes;
|
|
85630
|
+
const scopes = [];
|
|
85631
|
+
if (Array.isArray(raw)) {
|
|
85632
|
+
for (const entry of raw) {
|
|
85633
|
+
const tags = entry?.tags;
|
|
85634
|
+
const count = entry?.count;
|
|
85635
|
+
if (!Array.isArray(tags) || typeof count !== "number")
|
|
85636
|
+
continue;
|
|
85637
|
+
if (!tags.every((t) => typeof t === "string"))
|
|
85638
|
+
continue;
|
|
85639
|
+
scopes.push({ tags, count });
|
|
85640
|
+
}
|
|
85641
|
+
}
|
|
85642
|
+
return planScopeCleanup(bankId, scopes, options);
|
|
85643
|
+
}
|
|
85644
|
+
function formatCleanupPlan(plan, maxMerges = 10) {
|
|
85645
|
+
if (!plan.ok) {
|
|
85646
|
+
return `bank ${plan.bankId}: UNREADABLE (${plan.reason ?? "unknown"})`;
|
|
85647
|
+
}
|
|
85648
|
+
const b = plan.breakdown;
|
|
85649
|
+
const lines = [];
|
|
85650
|
+
lines.push(`bank ${plan.bankId}`);
|
|
85651
|
+
lines.push(` today: ${plan.totalScopes} scopes, ${plan.totalObservations.toLocaleString()} observations`);
|
|
85652
|
+
lines.push(` legacy fragmentation:` + ` all-volatile ${b.allVolatileScopes} scopes / ${b.allVolatileObservations.toLocaleString()} obs;` + ` mixed ${b.mixedScopes} scopes / ${b.mixedObservations.toLocaleString()} obs;` + ` stale agent-<hex> ${b.staleScopes} scopes / ${b.staleObservations.toLocaleString()} obs`);
|
|
85653
|
+
lines.push(` WOULD change: ${plan.affectedScopes} scopes` + ` (${plan.affectedObservations.toLocaleString()} obs re-homed),` + ` ${plan.totalScopes} \u2192 ${plan.resultingScopeCount} scopes` + ` (\u2212${plan.scopeReduction})`);
|
|
85654
|
+
if (plan.merges.length > 0) {
|
|
85655
|
+
lines.push(` top merges:`);
|
|
85656
|
+
for (const m of plan.merges.slice(0, maxMerges)) {
|
|
85657
|
+
lines.push(` ${m.sourceScopes} scopes \u2192 ${m.target} (${m.observations.toLocaleString()} obs)`);
|
|
85658
|
+
}
|
|
85659
|
+
if (plan.merges.length > maxMerges) {
|
|
85660
|
+
lines.push(` +${plan.merges.length - maxMerges} more merge targets`);
|
|
85661
|
+
}
|
|
85662
|
+
}
|
|
85663
|
+
return lines.join(`
|
|
85664
|
+
`);
|
|
85665
|
+
}
|
|
85666
|
+
|
|
85441
85667
|
// src/cli/memory.ts
|
|
85442
85668
|
init_helpers();
|
|
85443
85669
|
init_hindsight();
|
|
@@ -86164,6 +86390,55 @@ Demoting memory ${source_default.cyan(memoryId)}`));
|
|
|
86164
86390
|
` + " If the ID itself is suspect: `switchroom memory recall-log " + agent + " --json`."));
|
|
86165
86391
|
process.exit(1);
|
|
86166
86392
|
}));
|
|
86393
|
+
memory.command("cleanup-scopes [agent]").description("Dry-run audit of legacy pre-#4035 observation-scope fragmentation " + "(per-session UUID islands, stale agent-<hex> scopes). Reads only; " + "prints the blast radius of a curated re-home. Pass an agent, or --all.").option("--all", "Audit every agent bank and profile bank").option("--retire-stale", "Also fold stale agent-<hex> producer scopes into shared (opt-in; not part of the #4035 heal)").option("--execute", "(refused) live mutation is not supported \u2014 see the printed reason").option("--json", "Emit the plans as JSON").action(withConfigError(async (agent, opts) => {
|
|
86394
|
+
const config = getConfig(program3);
|
|
86395
|
+
const apiUrl = config.memory?.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
|
|
86396
|
+
if (opts.execute) {
|
|
86397
|
+
console.error(source_default.red("\u2717 --execute is not supported: legacy-scope healing is not mechanizable"));
|
|
86398
|
+
console.error(source_default.gray(` Hindsight exposes no per-memory tag-write (update_memory / PATCH memories/{id}
|
|
86399
|
+
` + " silently drop a `tags` argument, #3772) and no per-scope delete (DELETE\n" + ` .../observations clears the WHOLE bank). So an observation's scope cannot be
|
|
86400
|
+
` + ` edited in place, and this tool stays a read-only audit. A live heal needs an
|
|
86401
|
+
` + ` upstream tag-write path or a deliberate, operator-approved enumerate-and-
|
|
86402
|
+
` + " invalidate pass \u2014 neither is run from here."));
|
|
86403
|
+
process.exit(1);
|
|
86404
|
+
}
|
|
86405
|
+
const banks = new Set;
|
|
86406
|
+
if (agent) {
|
|
86407
|
+
if (!config.agents[agent]) {
|
|
86408
|
+
console.error(source_default.red(`Agent "${agent}" is not defined in switchroom.yaml`));
|
|
86409
|
+
process.exit(1);
|
|
86410
|
+
}
|
|
86411
|
+
banks.add(getCollectionForAgent(agent, config));
|
|
86412
|
+
} else if (opts.all) {
|
|
86413
|
+
for (const name of Object.keys(config.agents)) {
|
|
86414
|
+
banks.add(getCollectionForAgent(name, config));
|
|
86415
|
+
}
|
|
86416
|
+
for (const bank of collectProfileBanks(config))
|
|
86417
|
+
banks.add(bank);
|
|
86418
|
+
} else {
|
|
86419
|
+
console.error(source_default.red("Pass an agent name, or --all to audit every bank."));
|
|
86420
|
+
process.exit(1);
|
|
86421
|
+
}
|
|
86422
|
+
const plans = await Promise.all([...banks].map((bankId) => planBankCleanup(apiUrl, bankId, { retireStale: opts.retireStale })));
|
|
86423
|
+
plans.sort((a, b) => b.affectedObservations - a.affectedObservations);
|
|
86424
|
+
if (opts.json) {
|
|
86425
|
+
console.log(JSON.stringify(plans, null, 2));
|
|
86426
|
+
return;
|
|
86427
|
+
}
|
|
86428
|
+
console.log(source_default.bold(`
|
|
86429
|
+
Observation-scope cleanup \u2014 DRY RUN (no writes)
|
|
86430
|
+
`));
|
|
86431
|
+
console.log(source_default.gray(` api=${apiUrl} retireStale=${Boolean(opts.retireStale)}
|
|
86432
|
+
`));
|
|
86433
|
+
for (const plan of plans) {
|
|
86434
|
+
console.log(formatCleanupPlan(plan));
|
|
86435
|
+
console.log();
|
|
86436
|
+
}
|
|
86437
|
+
const totalObs = plans.reduce((n, p) => n + p.affectedObservations, 0);
|
|
86438
|
+
const totalScopes = plans.reduce((n, p) => n + p.scopeReduction, 0);
|
|
86439
|
+
console.log(source_default.cyan(`Fleet total (would change): ${totalObs.toLocaleString()} observations re-homed, ` + `${totalScopes.toLocaleString()} scopes removed.`));
|
|
86440
|
+
console.log(source_default.gray("Nothing was written. Live execution is not supported from this tool " + "(--execute prints why)."));
|
|
86441
|
+
}));
|
|
86167
86442
|
const restBase = (url) => (url ?? HINDSIGHT_DEFAULT_MCP_URL).replace(/\/mcp\/?$/, "").replace(/\/$/, "");
|
|
86168
86443
|
const VALID_BANK = /^[a-zA-Z0-9_.-]+$/;
|
|
86169
86444
|
const profile = memory.command("profile").description("Author + inspect operator profile banks (shared/per-user memory; wire via memory.recall.additional_banks)");
|
|
@@ -92108,9 +92383,9 @@ init_source();
|
|
|
92108
92383
|
init_loader();
|
|
92109
92384
|
init_lifecycle();
|
|
92110
92385
|
init_compose_env();
|
|
92111
|
-
import { existsSync as
|
|
92112
|
-
import { spawnSync as
|
|
92113
|
-
import { join as join84, dirname as
|
|
92386
|
+
import { existsSync as existsSync81, mkdirSync as mkdirSync46, readFileSync as readFileSync75, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
|
|
92387
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
92388
|
+
import { join as join84, dirname as dirname34, resolve as resolve45 } from "node:path";
|
|
92114
92389
|
import { homedir as homedir44 } from "node:os";
|
|
92115
92390
|
|
|
92116
92391
|
// src/cli/release-yaml.ts
|
|
@@ -92639,10 +92914,151 @@ function defaultSelfUpdateIO() {
|
|
|
92639
92914
|
// src/cli/update.ts
|
|
92640
92915
|
init_self_invoke();
|
|
92641
92916
|
init_component_versions();
|
|
92917
|
+
init_install_cron();
|
|
92918
|
+
|
|
92919
|
+
// src/host-cli-self-heal/install-timer.ts
|
|
92920
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
92921
|
+
import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync74, renameSync as renameSync20, writeFileSync as writeFileSync31 } from "node:fs";
|
|
92922
|
+
import { dirname as dirname33 } from "node:path";
|
|
92923
|
+
var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
|
|
92924
|
+
var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
|
|
92925
|
+
var TIMER_UNIT = "switchroom-self-heal.timer";
|
|
92926
|
+
var SYSTEMD_MARKER = "/run/systemd/system";
|
|
92927
|
+
var DOC_URL = "https://github.com/switchroom/switchroom/blob/main/docs/operators/host-cli-self-heal.md";
|
|
92928
|
+
function renderService(opts) {
|
|
92929
|
+
const homeLine = opts.home ? `Environment=HOME=${opts.home}
|
|
92930
|
+
` : "";
|
|
92931
|
+
return `# switchroom host CLI self-heal \u2014 keeps /usr/local/bin/switchroom from silently
|
|
92932
|
+
` + `# trailing the fleet. Managed by \`switchroom update\`; edits are overwritten.
|
|
92933
|
+
` + `[Unit]
|
|
92934
|
+
` + `Description=Switchroom host CLI self-heal (re-run \`switchroom update\` so the host binary can't silently trail the fleet)
|
|
92935
|
+
` + `Documentation=${DOC_URL}
|
|
92936
|
+
` + `After=network-online.target docker.service
|
|
92937
|
+
` + `Wants=network-online.target
|
|
92938
|
+
` + `
|
|
92939
|
+
` + `[Service]
|
|
92940
|
+
` + `Type=oneshot
|
|
92941
|
+
` + `# Run as the operator whose ~/.switchroom holds the fleet config \u2014 NOT root.
|
|
92942
|
+
` + `User=${opts.user}
|
|
92943
|
+
` + homeLine + `# --skip-images keeps each tick cheap: self-update-cli (the host binary swap)
|
|
92944
|
+
` + `# still runs; GHCR pulls and container recreation do not.
|
|
92945
|
+
` + `ExecStart=${opts.binary} update --skip-images
|
|
92946
|
+
` + `# Self-heal must never wedge the host: bound the run and run at low priority.
|
|
92947
|
+
` + `TimeoutStartSec=600
|
|
92948
|
+
` + `Nice=10
|
|
92949
|
+
`;
|
|
92950
|
+
}
|
|
92951
|
+
function renderTimer() {
|
|
92952
|
+
return `# switchroom host CLI self-heal timer. Managed by \`switchroom update\`.
|
|
92953
|
+
` + `[Unit]
|
|
92954
|
+
` + `Description=Run the Switchroom host CLI self-heal every 30 minutes
|
|
92955
|
+
` + `Documentation=${DOC_URL}
|
|
92956
|
+
` + `
|
|
92957
|
+
` + `[Timer]
|
|
92958
|
+
` + `OnBootSec=5min
|
|
92959
|
+
` + `OnUnitActiveSec=30min
|
|
92960
|
+
` + `RandomizedDelaySec=2min
|
|
92961
|
+
` + `Persistent=true
|
|
92962
|
+
` + `
|
|
92963
|
+
` + `[Install]
|
|
92964
|
+
` + `WantedBy=timers.target
|
|
92965
|
+
`;
|
|
92966
|
+
}
|
|
92967
|
+
function writeUnitIdempotent(path7, content) {
|
|
92968
|
+
if (existsSync80(path7)) {
|
|
92969
|
+
try {
|
|
92970
|
+
if (readFileSync74(path7, "utf8") === content)
|
|
92971
|
+
return "unchanged";
|
|
92972
|
+
} catch {}
|
|
92973
|
+
}
|
|
92974
|
+
mkdirSync45(dirname33(path7), { recursive: true });
|
|
92975
|
+
const tmp = `${path7}.${process.pid}.tmp`;
|
|
92976
|
+
writeFileSync31(tmp, content, { mode: 420 });
|
|
92977
|
+
renameSync20(tmp, path7);
|
|
92978
|
+
return "written";
|
|
92979
|
+
}
|
|
92980
|
+
function homeFromPasswd(user) {
|
|
92981
|
+
try {
|
|
92982
|
+
for (const line of readFileSync74("/etc/passwd", "utf8").split(`
|
|
92983
|
+
`)) {
|
|
92984
|
+
const f = line.split(":");
|
|
92985
|
+
if (f[0] === user && f[5])
|
|
92986
|
+
return f[5];
|
|
92987
|
+
}
|
|
92988
|
+
} catch {}
|
|
92989
|
+
return;
|
|
92990
|
+
}
|
|
92991
|
+
function resolveOperatorUser(env2) {
|
|
92992
|
+
const user = env2.SUDO_USER ?? env2.USER ?? env2.LOGNAME;
|
|
92993
|
+
if (!user || user === "root")
|
|
92994
|
+
return null;
|
|
92995
|
+
return user;
|
|
92996
|
+
}
|
|
92997
|
+
function installSelfHealTimer(deps = {}) {
|
|
92998
|
+
const env2 = deps.env ?? process.env;
|
|
92999
|
+
const systemdBooted = deps.systemdBooted ?? (() => existsSync80(SYSTEMD_MARKER));
|
|
93000
|
+
const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
|
|
93001
|
+
const homeForUser = deps.homeForUser ?? homeFromPasswd;
|
|
93002
|
+
const servicePath = deps.servicePath ?? SERVICE_PATH;
|
|
93003
|
+
const timerPath = deps.timerPath ?? TIMER_PATH;
|
|
93004
|
+
const binary = env2.SWITCHROOM_BINARY ?? "/usr/local/bin/switchroom";
|
|
93005
|
+
const user = resolveOperatorUser(env2);
|
|
93006
|
+
const renderUser = user ?? "OPERATOR";
|
|
93007
|
+
const home2 = user ? homeForUser(user) : undefined;
|
|
93008
|
+
const serviceContent = renderService({ binary, user: renderUser, ...home2 ? { home: home2 } : {} });
|
|
93009
|
+
const timerContent = renderTimer();
|
|
93010
|
+
const manualCommands = [
|
|
93011
|
+
"sudo systemctl daemon-reload",
|
|
93012
|
+
`sudo systemctl enable --now ${TIMER_UNIT}`
|
|
93013
|
+
];
|
|
93014
|
+
const base = { servicePath, timerPath, serviceContent, timerContent, manualCommands };
|
|
93015
|
+
if (!binary.startsWith("/")) {
|
|
93016
|
+
return {
|
|
93017
|
+
status: "skipped",
|
|
93018
|
+
reason: `SWITCHROOM_BINARY must be an absolute path (got ${binary})`,
|
|
93019
|
+
...base
|
|
93020
|
+
};
|
|
93021
|
+
}
|
|
93022
|
+
if (!systemdBooted()) {
|
|
93023
|
+
return {
|
|
93024
|
+
status: "skipped",
|
|
93025
|
+
reason: "host is not systemd-booted (/run/systemd/system absent)",
|
|
93026
|
+
...base
|
|
93027
|
+
};
|
|
93028
|
+
}
|
|
93029
|
+
if (geteuid() !== 0) {
|
|
93030
|
+
return { status: "skipped", reason: "not privileged (euid != 0)", ...base };
|
|
93031
|
+
}
|
|
93032
|
+
if (!user) {
|
|
93033
|
+
return {
|
|
93034
|
+
status: "skipped",
|
|
93035
|
+
reason: "no non-root operator user resolvable (SUDO_USER / USER / LOGNAME all unset or root)",
|
|
93036
|
+
...base
|
|
93037
|
+
};
|
|
93038
|
+
}
|
|
93039
|
+
const svc = writeUnitIdempotent(servicePath, serviceContent);
|
|
93040
|
+
const tim = writeUnitIdempotent(timerPath, timerContent);
|
|
93041
|
+
const changed = svc === "written" || tim === "written";
|
|
93042
|
+
const runner = deps.runner ?? ((cmd, args) => {
|
|
93043
|
+
const r = spawnSync16(cmd, args, { stdio: "inherit" });
|
|
93044
|
+
return { status: r.status ?? 1 };
|
|
93045
|
+
});
|
|
93046
|
+
const reload = runner("systemctl", ["daemon-reload"]);
|
|
93047
|
+
if (reload.status !== 0) {
|
|
93048
|
+
throw new Error(`systemctl daemon-reload failed (exit ${reload.status})`);
|
|
93049
|
+
}
|
|
93050
|
+
const enable = runner("systemctl", ["enable", "--now", TIMER_UNIT]);
|
|
93051
|
+
if (enable.status !== 0) {
|
|
93052
|
+
throw new Error(`systemctl enable --now ${TIMER_UNIT} failed (exit ${enable.status})`);
|
|
93053
|
+
}
|
|
93054
|
+
return { status: changed ? "installed" : "unchanged", ...base };
|
|
93055
|
+
}
|
|
93056
|
+
|
|
93057
|
+
// src/cli/update.ts
|
|
92642
93058
|
function defaultPersistPin(configPath) {
|
|
92643
93059
|
return (pin) => {
|
|
92644
93060
|
const path7 = configPath ?? findConfigFile();
|
|
92645
|
-
const before =
|
|
93061
|
+
const before = readFileSync75(path7, "utf8");
|
|
92646
93062
|
const after = setReleasePinInConfig(before, pin);
|
|
92647
93063
|
if (after === before)
|
|
92648
93064
|
return;
|
|
@@ -92658,16 +93074,16 @@ function defaultPersistPin(configPath) {
|
|
|
92658
93074
|
}
|
|
92659
93075
|
var DEFAULT_COMPOSE_PATH2 = join84(homedir44(), ".switchroom", "compose", "docker-compose.yml");
|
|
92660
93076
|
function runningFromSwitchroomCheckout(scriptPath) {
|
|
92661
|
-
let dir =
|
|
93077
|
+
let dir = dirname34(scriptPath);
|
|
92662
93078
|
for (let i = 0;i < 12; i++) {
|
|
92663
|
-
if (
|
|
93079
|
+
if (existsSync81(join84(dir, ".git"))) {
|
|
92664
93080
|
try {
|
|
92665
|
-
const pkg = JSON.parse(
|
|
93081
|
+
const pkg = JSON.parse(readFileSync75(join84(dir, "package.json"), "utf-8"));
|
|
92666
93082
|
if (pkg.name === "switchroom")
|
|
92667
93083
|
return true;
|
|
92668
93084
|
} catch {}
|
|
92669
93085
|
}
|
|
92670
|
-
const parent =
|
|
93086
|
+
const parent = dirname34(dir);
|
|
92671
93087
|
if (parent === dir)
|
|
92672
93088
|
break;
|
|
92673
93089
|
dir = parent;
|
|
@@ -92728,7 +93144,7 @@ function planUpdate(opts) {
|
|
|
92728
93144
|
bundleDir: import.meta.dirname,
|
|
92729
93145
|
execPath: process.execPath,
|
|
92730
93146
|
scriptPath,
|
|
92731
|
-
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" ||
|
|
93147
|
+
inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync81("/.dockerenv")
|
|
92732
93148
|
});
|
|
92733
93149
|
if (detection.kind !== "static-binary") {
|
|
92734
93150
|
say(source_default.gray(` host CLI not self-updated: ${detection.reason}
|
|
@@ -92801,7 +93217,7 @@ function planUpdate(opts) {
|
|
|
92801
93217
|
steps.push({
|
|
92802
93218
|
name: "pull-images",
|
|
92803
93219
|
description: "Pull broker / kernel / agent images from GHCR",
|
|
92804
|
-
skipReason: opts.skipImages ? "--skip-images flag set" : !
|
|
93220
|
+
skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync81(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
|
|
92805
93221
|
run: () => {
|
|
92806
93222
|
const r = runner("docker", [
|
|
92807
93223
|
"compose",
|
|
@@ -92846,6 +93262,69 @@ function planUpdate(opts) {
|
|
|
92846
93262
|
throw new Error("switchroom apply failed");
|
|
92847
93263
|
}
|
|
92848
93264
|
});
|
|
93265
|
+
steps.push({
|
|
93266
|
+
name: "reconcile-hindsight-watch-cron",
|
|
93267
|
+
description: "Reconcile /etc/cron.d/hindsight-watch (binary path / schedule / flags) on already-armed hosts \u2014 idempotent; no-op when unarmed or already current",
|
|
93268
|
+
skipReason: hostdContext ? "deferred (hostd-context): /etc/cron.d is on the host, not in the hostd container \u2014 finish host-side with `switchroom hindsight-watch --install-cron`" : undefined,
|
|
93269
|
+
isHostdDeferred: hostdContext,
|
|
93270
|
+
run: () => {
|
|
93271
|
+
const say = opts.stdout ?? ((t) => process.stdout.write(t));
|
|
93272
|
+
const binary = process.env.SWITCHROOM_BINARY ?? "/usr/local/bin/switchroom";
|
|
93273
|
+
const fallbackUser = process.env.SUDO_USER ?? process.env.USER ?? process.env.LOGNAME;
|
|
93274
|
+
let res;
|
|
93275
|
+
try {
|
|
93276
|
+
res = reconcileCron({
|
|
93277
|
+
binary,
|
|
93278
|
+
fallbackUser,
|
|
93279
|
+
...opts.watchCronPath ? { path: opts.watchCronPath } : {}
|
|
93280
|
+
});
|
|
93281
|
+
} catch (e) {
|
|
93282
|
+
say(source_default.yellow(` hindsight-watch cron not reconciled: ${e.message}
|
|
93283
|
+
`));
|
|
93284
|
+
return;
|
|
93285
|
+
}
|
|
93286
|
+
if (res.status === "absent") {
|
|
93287
|
+
say(source_default.gray(" hindsight-watch cron not armed \u2014 skipping reconcile " + "(arm with `switchroom hindsight-watch --install-cron`; doctor FAILs while unarmed)\n"));
|
|
93288
|
+
} else if (res.status === "reconciled") {
|
|
93289
|
+
say(source_default.green(` hindsight-watch cron reconciled at ${res.path}
|
|
93290
|
+
`));
|
|
93291
|
+
} else {
|
|
93292
|
+
say(source_default.gray(` hindsight-watch cron already current at ${res.path}
|
|
93293
|
+
`));
|
|
93294
|
+
}
|
|
93295
|
+
}
|
|
93296
|
+
});
|
|
93297
|
+
steps.push({
|
|
93298
|
+
name: "install-self-heal-timer",
|
|
93299
|
+
description: "Install + enable the switchroom-self-heal systemd timer so a shipped release converges the host binary automatically \u2014 graceful no-op without systemd/privilege",
|
|
93300
|
+
skipReason: hostdContext ? "deferred (hostd-context): /etc/systemd/system is on the host, not in the hostd container \u2014 finish host-side by re-running `switchroom update` on the host" : undefined,
|
|
93301
|
+
isHostdDeferred: hostdContext,
|
|
93302
|
+
run: () => {
|
|
93303
|
+
const say = opts.stdout ?? ((t) => process.stdout.write(t));
|
|
93304
|
+
let res;
|
|
93305
|
+
try {
|
|
93306
|
+
res = installSelfHealTimer(opts.selfHealTimerDeps ?? {});
|
|
93307
|
+
} catch (e) {
|
|
93308
|
+
say(source_default.yellow(` self-heal timer not installed: ${e.message}
|
|
93309
|
+
`));
|
|
93310
|
+
return;
|
|
93311
|
+
}
|
|
93312
|
+
if (res.status === "installed") {
|
|
93313
|
+
say(source_default.green(` self-heal timer installed + enabled (${res.servicePath}, ${res.timerPath})
|
|
93314
|
+
`));
|
|
93315
|
+
} else if (res.status === "unchanged") {
|
|
93316
|
+
say(source_default.gray(` self-heal timer already current + enabled at ${res.timerPath}
|
|
93317
|
+
`));
|
|
93318
|
+
} else {
|
|
93319
|
+
say(source_default.gray(` self-heal timer not installed automatically: ${res.reason}
|
|
93320
|
+
`) + source_default.gray(` Install it by hand:
|
|
93321
|
+
`) + source_default.gray(` # ${res.servicePath}
|
|
93322
|
+
`) + res.serviceContent.replace(/^/gm, source_default.gray(" ")) + source_default.gray(` # ${res.timerPath}
|
|
93323
|
+
`) + res.timerContent.replace(/^/gm, source_default.gray(" ")) + res.manualCommands.map((c) => source_default.gray(` ${c}
|
|
93324
|
+
`)).join(""));
|
|
93325
|
+
}
|
|
93326
|
+
}
|
|
93327
|
+
});
|
|
92849
93328
|
let hostControlEnabled;
|
|
92850
93329
|
if (typeof opts.hostControlEnabled === "boolean") {
|
|
92851
93330
|
hostControlEnabled = opts.hostControlEnabled;
|
|
@@ -92922,13 +93401,13 @@ function planUpdate(opts) {
|
|
|
92922
93401
|
}
|
|
92923
93402
|
const source = resolve45(import.meta.dirname, "../../skills");
|
|
92924
93403
|
const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
|
|
92925
|
-
if (!
|
|
93404
|
+
if (!existsSync81(source)) {
|
|
92926
93405
|
process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
|
|
92927
93406
|
`);
|
|
92928
93407
|
return;
|
|
92929
93408
|
}
|
|
92930
93409
|
try {
|
|
92931
|
-
|
|
93410
|
+
mkdirSync46(dirname34(dest), { recursive: true });
|
|
92932
93411
|
const r = syncBundledSkills({
|
|
92933
93412
|
source,
|
|
92934
93413
|
dest,
|
|
@@ -92954,10 +93433,10 @@ function planUpdate(opts) {
|
|
|
92954
93433
|
if (opts.syncBundledSkillsFn)
|
|
92955
93434
|
return;
|
|
92956
93435
|
const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
|
|
92957
|
-
if (!
|
|
93436
|
+
if (!existsSync81(dest)) {
|
|
92958
93437
|
return;
|
|
92959
93438
|
}
|
|
92960
|
-
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !
|
|
93439
|
+
const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync81(join84(dest, key)));
|
|
92961
93440
|
if (missing.length > 0) {
|
|
92962
93441
|
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.`);
|
|
92963
93442
|
}
|
|
@@ -92992,7 +93471,7 @@ function planUpdate(opts) {
|
|
|
92992
93471
|
description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
|
|
92993
93472
|
run: () => {
|
|
92994
93473
|
try {
|
|
92995
|
-
const composeText =
|
|
93474
|
+
const composeText = readFileSync75(composePath, "utf8");
|
|
92996
93475
|
const pf = validateBindSources(composeText);
|
|
92997
93476
|
if (!pf.ok)
|
|
92998
93477
|
throw new Error(formatPreflightError(pf));
|
|
@@ -93026,7 +93505,7 @@ function planUpdate(opts) {
|
|
|
93026
93505
|
return steps;
|
|
93027
93506
|
}
|
|
93028
93507
|
function defaultRunner2(cmd, args) {
|
|
93029
|
-
const r =
|
|
93508
|
+
const r = spawnSync17(cmd, args, { stdio: "inherit" });
|
|
93030
93509
|
return { status: r.status ?? 1 };
|
|
93031
93510
|
}
|
|
93032
93511
|
function writeMarkerInPreferredLocation(agent, reason, runner) {
|
|
@@ -93069,12 +93548,12 @@ function defaultStatusProbe(composePath) {
|
|
|
93069
93548
|
try {
|
|
93070
93549
|
cliBuiltAt = new Date(statSync45(scriptPath).mtimeMs).toISOString();
|
|
93071
93550
|
} catch {}
|
|
93072
|
-
let dir =
|
|
93551
|
+
let dir = dirname34(scriptPath);
|
|
93073
93552
|
for (let i = 0;i < 8; i++) {
|
|
93074
93553
|
const pkgPath = join84(dir, "package.json");
|
|
93075
|
-
if (
|
|
93554
|
+
if (existsSync81(pkgPath)) {
|
|
93076
93555
|
try {
|
|
93077
|
-
const pkg = JSON.parse(
|
|
93556
|
+
const pkg = JSON.parse(readFileSync75(pkgPath, "utf-8"));
|
|
93078
93557
|
if (typeof pkg.version === "string")
|
|
93079
93558
|
cliVersion = pkg.version;
|
|
93080
93559
|
} catch (err) {
|
|
@@ -93082,7 +93561,7 @@ function defaultStatusProbe(composePath) {
|
|
|
93082
93561
|
}
|
|
93083
93562
|
break;
|
|
93084
93563
|
}
|
|
93085
|
-
const parent =
|
|
93564
|
+
const parent = dirname34(dir);
|
|
93086
93565
|
if (parent === dir)
|
|
93087
93566
|
break;
|
|
93088
93567
|
dir = parent;
|
|
@@ -93095,13 +93574,13 @@ function defaultStatusProbe(composePath) {
|
|
|
93095
93574
|
warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
|
|
93096
93575
|
}
|
|
93097
93576
|
const services = [];
|
|
93098
|
-
if (!
|
|
93577
|
+
if (!existsSync81(composePath)) {
|
|
93099
93578
|
warnings.push(`compose file not found at ${composePath}; service status unknown`);
|
|
93100
93579
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
93101
93580
|
}
|
|
93102
93581
|
let serviceList = [];
|
|
93103
93582
|
try {
|
|
93104
|
-
const r =
|
|
93583
|
+
const r = spawnSync17("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
|
|
93105
93584
|
if (r.status !== 0) {
|
|
93106
93585
|
warnings.push(`docker compose config --services failed: ${r.stderr?.trim() ?? r.error?.message ?? "unknown"}`);
|
|
93107
93586
|
return { cliVersion, cliBuiltAt, services, warnings };
|
|
@@ -93118,7 +93597,7 @@ function defaultStatusProbe(composePath) {
|
|
|
93118
93597
|
let containerCreatedAt = null;
|
|
93119
93598
|
let status = "<unknown>";
|
|
93120
93599
|
try {
|
|
93121
|
-
const r =
|
|
93600
|
+
const r = spawnSync17("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
|
|
93122
93601
|
if (r.status === 0) {
|
|
93123
93602
|
const [img, created, st] = r.stdout.trim().split("|");
|
|
93124
93603
|
image = img ?? null;
|
|
@@ -93134,7 +93613,7 @@ function defaultStatusProbe(composePath) {
|
|
|
93134
93613
|
let imagePulledAt = null;
|
|
93135
93614
|
if (image) {
|
|
93136
93615
|
try {
|
|
93137
|
-
const r =
|
|
93616
|
+
const r = spawnSync17("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
|
|
93138
93617
|
if (r.status === 0) {
|
|
93139
93618
|
const [id, created, lastTag] = r.stdout.trim().split("|");
|
|
93140
93619
|
imageDigestShort = id?.replace(/^sha256:/, "").slice(0, 12) ?? null;
|
|
@@ -93209,7 +93688,7 @@ function buildReexecArgs(opts) {
|
|
|
93209
93688
|
return args;
|
|
93210
93689
|
}
|
|
93211
93690
|
function defaultReexec(binaryPath, args) {
|
|
93212
|
-
const r =
|
|
93691
|
+
const r = spawnSync17(binaryPath, args, {
|
|
93213
93692
|
stdio: "inherit",
|
|
93214
93693
|
env: { ...process.env, [SELF_UPDATE_ENV_SENTINEL]: "1" }
|
|
93215
93694
|
});
|
|
@@ -93218,7 +93697,7 @@ function defaultReexec(binaryPath, args) {
|
|
|
93218
93697
|
function renderDriftPreamble(opts) {
|
|
93219
93698
|
try {
|
|
93220
93699
|
const exec = opts.componentExec ?? ((cmd, args) => {
|
|
93221
|
-
const r =
|
|
93700
|
+
const r = spawnSync17(cmd, args, { encoding: "utf-8", timeout: 1e4 });
|
|
93222
93701
|
return { status: r.status ?? 1, stdout: r.stdout ?? "" };
|
|
93223
93702
|
});
|
|
93224
93703
|
let pin;
|
|
@@ -93342,23 +93821,23 @@ function registerUpdateCommand(program3) {
|
|
|
93342
93821
|
|
|
93343
93822
|
// src/cli/rollout.ts
|
|
93344
93823
|
init_helpers();
|
|
93345
|
-
import { spawnSync as
|
|
93346
|
-
import { readFileSync as
|
|
93824
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
93825
|
+
import { readFileSync as readFileSync77, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
|
|
93347
93826
|
import { homedir as homedir46 } from "node:os";
|
|
93348
93827
|
|
|
93349
93828
|
// src/cli/rollout-pin-journal.ts
|
|
93350
93829
|
import {
|
|
93351
|
-
existsSync as
|
|
93352
|
-
readFileSync as
|
|
93353
|
-
writeFileSync as
|
|
93354
|
-
renameSync as
|
|
93830
|
+
existsSync as existsSync82,
|
|
93831
|
+
readFileSync as readFileSync76,
|
|
93832
|
+
writeFileSync as writeFileSync32,
|
|
93833
|
+
renameSync as renameSync21,
|
|
93355
93834
|
unlinkSync as unlinkSync15,
|
|
93356
93835
|
statSync as statSync46,
|
|
93357
|
-
mkdirSync as
|
|
93836
|
+
mkdirSync as mkdirSync47
|
|
93358
93837
|
} from "node:fs";
|
|
93359
93838
|
import { homedir as homedir45 } from "node:os";
|
|
93360
93839
|
import { createHash as createHash17 } from "node:crypto";
|
|
93361
|
-
import { join as join85, basename as basename9, resolve as resolve46, dirname as
|
|
93840
|
+
import { join as join85, basename as basename9, resolve as resolve46, dirname as dirname35 } from "node:path";
|
|
93362
93841
|
init_flock();
|
|
93363
93842
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
93364
93843
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -93367,7 +93846,7 @@ function pinJournalDir(configPath) {
|
|
|
93367
93846
|
if (override && override.trim().length > 0)
|
|
93368
93847
|
return override.trim();
|
|
93369
93848
|
if (configPath) {
|
|
93370
|
-
const dir =
|
|
93849
|
+
const dir = dirname35(resolve46(configPath));
|
|
93371
93850
|
if (basename9(dir) === STATE_DIR_NAME)
|
|
93372
93851
|
return dir;
|
|
93373
93852
|
}
|
|
@@ -93410,9 +93889,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
93410
93889
|
const p = pinJournalPath(configPath);
|
|
93411
93890
|
let raw;
|
|
93412
93891
|
try {
|
|
93413
|
-
raw =
|
|
93892
|
+
raw = readFileSync76(p, "utf8");
|
|
93414
93893
|
} catch (e) {
|
|
93415
|
-
if (
|
|
93894
|
+
if (existsSync82(p)) {
|
|
93416
93895
|
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.
|
|
93417
93896
|
`);
|
|
93418
93897
|
}
|
|
@@ -93463,7 +93942,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
93463
93942
|
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.
|
|
93464
93943
|
`);
|
|
93465
93944
|
}
|
|
93466
|
-
const priorPin = getReleasePinFromConfig(
|
|
93945
|
+
const priorPin = getReleasePinFromConfig(readFileSync76(configPath, "utf8"));
|
|
93467
93946
|
const journal = {
|
|
93468
93947
|
v: 1,
|
|
93469
93948
|
configPath,
|
|
@@ -93472,10 +93951,10 @@ function beginPinPersist(configPath, pin, opts = {}) {
|
|
|
93472
93951
|
pid: process.pid,
|
|
93473
93952
|
at: new Date().toISOString()
|
|
93474
93953
|
};
|
|
93475
|
-
|
|
93954
|
+
mkdirSync47(dirname35(p), { recursive: true });
|
|
93476
93955
|
const tmp = `${p}.${process.pid}.tmp`;
|
|
93477
|
-
|
|
93478
|
-
|
|
93956
|
+
writeFileSync32(tmp, JSON.stringify(journal), { encoding: "utf8", mode: 384 });
|
|
93957
|
+
renameSync21(tmp, p);
|
|
93479
93958
|
return journal;
|
|
93480
93959
|
}
|
|
93481
93960
|
function commitPinPersist(configPath) {
|
|
@@ -93484,7 +93963,7 @@ function commitPinPersist(configPath) {
|
|
|
93484
93963
|
unlinkSync15(p);
|
|
93485
93964
|
return null;
|
|
93486
93965
|
} catch (e) {
|
|
93487
|
-
if (!
|
|
93966
|
+
if (!existsSync82(p))
|
|
93488
93967
|
return null;
|
|
93489
93968
|
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\`.`;
|
|
93490
93969
|
}
|
|
@@ -93506,7 +93985,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
93506
93985
|
}
|
|
93507
93986
|
}
|
|
93508
93987
|
try {
|
|
93509
|
-
const current =
|
|
93988
|
+
const current = readFileSync76(configPath, "utf8");
|
|
93510
93989
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
93511
93990
|
let mode = 384;
|
|
93512
93991
|
try {
|
|
@@ -93515,7 +93994,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
93515
93994
|
if (opts.writeConfig) {
|
|
93516
93995
|
opts.writeConfig(configPath, next, mode);
|
|
93517
93996
|
} else {
|
|
93518
|
-
|
|
93997
|
+
writeFileSync32(configPath, next, { encoding: "utf8", mode });
|
|
93519
93998
|
}
|
|
93520
93999
|
} catch (e) {
|
|
93521
94000
|
return {
|
|
@@ -93561,12 +94040,13 @@ init_atomic();
|
|
|
93561
94040
|
init_resolve_version();
|
|
93562
94041
|
init_audit_reader();
|
|
93563
94042
|
init_hindsight();
|
|
94043
|
+
init_hindsight2();
|
|
93564
94044
|
|
|
93565
94045
|
// src/cli/deploy-version-guard.ts
|
|
93566
|
-
import { spawnSync as
|
|
94046
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
93567
94047
|
var DOCKER_INSPECT_TIMEOUT_MS = 60 * 1000;
|
|
93568
94048
|
var defaultRunner3 = (args) => {
|
|
93569
|
-
const r =
|
|
94049
|
+
const r = spawnSync18("docker", args, {
|
|
93570
94050
|
encoding: "utf8",
|
|
93571
94051
|
timeout: DOCKER_INSPECT_TIMEOUT_MS,
|
|
93572
94052
|
killSignal: "SIGKILL"
|
|
@@ -93608,22 +94088,31 @@ function checkDowngrade(opts) {
|
|
|
93608
94088
|
init_agent_owned_tree();
|
|
93609
94089
|
init_component_versions();
|
|
93610
94090
|
var VERIFY_COMPONENTS_STEP = "verify-components";
|
|
94091
|
+
var ENSURE_BANKS_STEP = "ensure-banks";
|
|
93611
94092
|
function normalizeVersion(v) {
|
|
93612
94093
|
return v.trim().replace(/^v/, "");
|
|
93613
94094
|
}
|
|
93614
94095
|
function isVersionAssertable(target) {
|
|
93615
94096
|
return /^v?\d+\.\d+\.\d+$/.test(target.trim());
|
|
93616
94097
|
}
|
|
93617
|
-
|
|
94098
|
+
var ROLLOUT_PRIORITY_AGENTS = [
|
|
94099
|
+
"overlord",
|
|
94100
|
+
"carrie",
|
|
94101
|
+
"finn"
|
|
94102
|
+
];
|
|
94103
|
+
function orderAgentsForRoll(agents) {
|
|
93618
94104
|
const canary = agents.filter((a) => a === "test-harness");
|
|
93619
|
-
const
|
|
93620
|
-
|
|
94105
|
+
const priority = ROLLOUT_PRIORITY_AGENTS.filter((p) => p !== "test-harness" && agents.includes(p));
|
|
94106
|
+
const prioritySet = new Set(priority);
|
|
94107
|
+
const rest = agents.filter((a) => a !== "test-harness" && !prioritySet.has(a));
|
|
94108
|
+
return [...canary, ...priority, ...rest];
|
|
93621
94109
|
}
|
|
93622
94110
|
function planRollout(agents, opts = {}) {
|
|
93623
94111
|
const steps = [];
|
|
93624
|
-
const ordered =
|
|
94112
|
+
const ordered = orderAgentsForRoll(agents);
|
|
93625
94113
|
if (opts.hostdContext) {
|
|
93626
94114
|
steps.push({ kind: "apply" });
|
|
94115
|
+
steps.push({ kind: "refresh-hindsight" });
|
|
93627
94116
|
const [canary, ...rest] = ordered;
|
|
93628
94117
|
if (canary !== undefined) {
|
|
93629
94118
|
steps.push({ kind: "restart-agent", agent: canary });
|
|
@@ -93639,23 +94128,26 @@ function planRollout(agents, opts = {}) {
|
|
|
93639
94128
|
if (!opts.skipWeb) {
|
|
93640
94129
|
steps.push({ kind: "refresh-web" });
|
|
93641
94130
|
}
|
|
93642
|
-
steps.push({ kind: "refresh-hindsight" });
|
|
93643
94131
|
steps.push({ kind: "sweep" });
|
|
94132
|
+
steps.push({ kind: "ensure-banks" });
|
|
93644
94133
|
steps.push({ kind: "verify-components" });
|
|
93645
94134
|
return steps;
|
|
93646
94135
|
}
|
|
93647
94136
|
if (opts.pinToPersist)
|
|
93648
94137
|
steps.push({ kind: "persist-pin", pin: opts.pinToPersist });
|
|
93649
94138
|
steps.push({ kind: "apply" });
|
|
94139
|
+
if (!opts.skipWeb) {
|
|
94140
|
+
steps.push({ kind: "refresh-hindsight" });
|
|
94141
|
+
}
|
|
93650
94142
|
for (const agent of ordered) {
|
|
93651
94143
|
steps.push({ kind: "restart-agent", agent });
|
|
93652
94144
|
}
|
|
93653
94145
|
if (!opts.skipWeb) {
|
|
93654
94146
|
steps.push({ kind: "refresh-web" });
|
|
93655
94147
|
steps.push({ kind: "refresh-hostd" });
|
|
93656
|
-
steps.push({ kind: "refresh-hindsight" });
|
|
93657
94148
|
}
|
|
93658
94149
|
steps.push({ kind: "sweep" });
|
|
94150
|
+
steps.push({ kind: "ensure-banks" });
|
|
93659
94151
|
steps.push({ kind: "verify-components" });
|
|
93660
94152
|
return steps;
|
|
93661
94153
|
}
|
|
@@ -93686,6 +94178,9 @@ function formatRolloutPlan(steps, target) {
|
|
|
93686
94178
|
case "sweep":
|
|
93687
94179
|
lines.push(` ${n}. sweep \u2014 print per-agent version table`);
|
|
93688
94180
|
break;
|
|
94181
|
+
case "ensure-banks":
|
|
94182
|
+
lines.push(` ${n}. ensure-banks \u2014 create any missing agent Hindsight bank ` + `(idempotent backstop; structured failure if one stays missing)`);
|
|
94183
|
+
break;
|
|
93689
94184
|
case "verify-components":
|
|
93690
94185
|
lines.push(` ${n}. verify-components \u2014 assert EVERY in-scope component is on ${target} ` + `(same inventory \`switchroom update --check\` uses); non-zero exit if any is behind`);
|
|
93691
94186
|
break;
|
|
@@ -93747,7 +94242,7 @@ function evaluateRolloutDrift(components, target, steps, hostdContext) {
|
|
|
93747
94242
|
const { gated, exempt } = partitionDrift(report.behind, { owners, rolledAgents });
|
|
93748
94243
|
return { gated, exempt, target: report.target };
|
|
93749
94244
|
}
|
|
93750
|
-
function executeRollout(steps, target, deps, execOpts = {}) {
|
|
94245
|
+
async function executeRollout(steps, target, deps, execOpts = {}) {
|
|
93751
94246
|
const targetNorm = normalizeVersion(target);
|
|
93752
94247
|
const rolled = [];
|
|
93753
94248
|
const warnings = [];
|
|
@@ -93756,6 +94251,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93756
94251
|
let agentIndex = 0;
|
|
93757
94252
|
let pinPersisted = false;
|
|
93758
94253
|
let driftVerdict = null;
|
|
94254
|
+
const bankEnsureFailed = [];
|
|
93759
94255
|
const fail4 = (partial) => {
|
|
93760
94256
|
let pinReverted = false;
|
|
93761
94257
|
if (pinPersisted && deps.revertPin) {
|
|
@@ -93860,6 +94356,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93860
94356
|
const webTag = deps.webImageTag?.();
|
|
93861
94357
|
if (webTag !== undefined && webTag !== null && isVersionAssertable(webTag) && normalizeVersion(webTag) !== targetNorm) {
|
|
93862
94358
|
warnings.push(`web refresh completed but switchroom-web is running ${webTag}, ` + `NOT the roll target ${target} (most likely its downgrade guard ` + `skipped the install). Finish it host-side: \`switchroom webd ` + `install --tag ${target} --allow-downgrade\`.`);
|
|
94359
|
+
break;
|
|
94360
|
+
}
|
|
94361
|
+
if (webTag !== undefined && webTag !== null && isVersionAssertable(webTag) && normalizeVersion(webTag) === targetNorm) {
|
|
94362
|
+
emit({ phase: "web-refresh-done", target });
|
|
93863
94363
|
}
|
|
93864
94364
|
break;
|
|
93865
94365
|
}
|
|
@@ -93875,9 +94375,11 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93875
94375
|
const hindsightExists = deps.hindsightExists ?? isHindsightContainerExists;
|
|
93876
94376
|
if (!hindsightExists()) {
|
|
93877
94377
|
deps.log(`ROLL_STEP refresh-hindsight \u2014 no hindsight container on this host; skipping`);
|
|
94378
|
+
emit({ phase: "hindsight-skipped", target });
|
|
93878
94379
|
break;
|
|
93879
94380
|
}
|
|
93880
94381
|
deps.log(`ROLL_STEP refresh-hindsight \u2014 memory setup --recreate --tag ${target} (pull ${target} + recreate)`);
|
|
94382
|
+
emit({ phase: "hindsight-refresh", target });
|
|
93881
94383
|
const r = deps.run(["memory", "setup", "--recreate", "--tag", target]);
|
|
93882
94384
|
if (r.status !== 0) {
|
|
93883
94385
|
deps.log(` \u2717 hindsight recreate FAILED \u2014 the memory backend is DOWN (the ` + `recreate stopped + removed the old container before the failed ` + `start). Run \`switchroom memory setup\` to bring it back. \u2014 STOPPING`);
|
|
@@ -93886,6 +94388,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93886
94388
|
...r.timedOut ? { timedOut: true } : {}
|
|
93887
94389
|
});
|
|
93888
94390
|
}
|
|
94391
|
+
emit({ phase: "hindsight-refresh-done", target });
|
|
93889
94392
|
break;
|
|
93890
94393
|
}
|
|
93891
94394
|
case "sweep": {
|
|
@@ -93896,6 +94399,38 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93896
94399
|
}
|
|
93897
94400
|
break;
|
|
93898
94401
|
}
|
|
94402
|
+
case "ensure-banks": {
|
|
94403
|
+
const hindsightExists = deps.hindsightExists ?? isHindsightContainerExists;
|
|
94404
|
+
if (!hindsightExists()) {
|
|
94405
|
+
deps.log(`ROLL_STEP ensure-banks \u2014 no hindsight container on this host; skipping`);
|
|
94406
|
+
break;
|
|
94407
|
+
}
|
|
94408
|
+
if (!deps.ensureBank) {
|
|
94409
|
+
warnings.push(`ensure-banks ran with NO bank-ensure hook wired, so brand-new ` + `agents' Hindsight banks were NOT verified. If an agent's first ` + `\`retain\` fails with a foreign-key error, its bank was never ` + `created \u2014 recreate it by restarting that agent.`);
|
|
94410
|
+
break;
|
|
94411
|
+
}
|
|
94412
|
+
deps.log(`ROLL_STEP ensure-banks \u2014 creating any missing agent bank (idempotent) for ${rolled.length} agent(s)`);
|
|
94413
|
+
for (const a of rolled) {
|
|
94414
|
+
let res;
|
|
94415
|
+
try {
|
|
94416
|
+
res = await deps.ensureBank(a);
|
|
94417
|
+
} catch (e) {
|
|
94418
|
+
res = { ok: false, reason: e.message };
|
|
94419
|
+
}
|
|
94420
|
+
if (res.ok) {
|
|
94421
|
+
deps.log(` ${a}: bank present`);
|
|
94422
|
+
continue;
|
|
94423
|
+
}
|
|
94424
|
+
if (/unreachable|timeout/i.test(res.reason)) {
|
|
94425
|
+
deps.log(` ${a}: bank check ${res.reason.toLowerCase()} (degraded to warning)`);
|
|
94426
|
+
warnings.push(`ensure-banks could not reach Hindsight to verify ${a}'s bank ` + `(${res.reason}). If ${a} is brand-new and its first \`retain\` ` + `fails with a foreign-key error, restart it to recreate the bank.`);
|
|
94427
|
+
continue;
|
|
94428
|
+
}
|
|
94429
|
+
deps.log(` \u2717 ${a}: bank could NOT be created \u2014 ${res.reason}`);
|
|
94430
|
+
bankEnsureFailed.push(a);
|
|
94431
|
+
}
|
|
94432
|
+
break;
|
|
94433
|
+
}
|
|
93899
94434
|
case "verify-components": {
|
|
93900
94435
|
deps.log(`ROLL_STEP verify-components \u2014 asserting every in-scope component is on ${target}`);
|
|
93901
94436
|
if (!deps.collectComponents) {
|
|
@@ -93934,6 +94469,19 @@ function executeRollout(steps, target, deps, execOpts = {}) {
|
|
|
93934
94469
|
warnings.push(`roll succeeded but committing the durable release.pin threw: ` + `${e.message}. The pin IS written; a stale rollout ` + `journal may cause a later boot to revert it \u2014 verify host-side.`);
|
|
93935
94470
|
}
|
|
93936
94471
|
}
|
|
94472
|
+
if (bankEnsureFailed.length > 0) {
|
|
94473
|
+
for (const a of bankEnsureFailed) {
|
|
94474
|
+
warnings.push(`${a}'s Hindsight bank could NOT be created during the roll \u2014 its ` + `first \`retain\` will fail with a foreign-key error until the bank ` + `exists. Restart ${a} to re-run its bank creation now that Hindsight ` + `is back up.`);
|
|
94475
|
+
}
|
|
94476
|
+
deps.log(` \u2717 ensure-banks FAILED \u2014 ${bankEnsureFailed.length} agent bank(s) ` + `could not be created: ${bankEnsureFailed.join(", ")}`);
|
|
94477
|
+
return {
|
|
94478
|
+
ok: false,
|
|
94479
|
+
rolled,
|
|
94480
|
+
warnings,
|
|
94481
|
+
failedStep: ENSURE_BANKS_STEP,
|
|
94482
|
+
drifted: bankEnsureFailed
|
|
94483
|
+
};
|
|
94484
|
+
}
|
|
93937
94485
|
if (driftVerdict && driftVerdict.gated.length > 0) {
|
|
93938
94486
|
const normalizedTarget = `v${normalizeVersion(target)}`;
|
|
93939
94487
|
for (const c of driftVerdict.gated) {
|
|
@@ -93990,7 +94538,7 @@ function isSpawnTimeout(r, killSignal) {
|
|
|
93990
94538
|
var ROLLOUT_KILL_SIGNAL = "SIGKILL";
|
|
93991
94539
|
function createRolloutDeps(params) {
|
|
93992
94540
|
const { configPath, scriptPath, hostdCtx } = params;
|
|
93993
|
-
const spawn6 = params.spawn ??
|
|
94541
|
+
const spawn6 = params.spawn ?? spawnSync19;
|
|
93994
94542
|
const warn = params.warn ?? ((line) => process.stderr.write(line));
|
|
93995
94543
|
const self = resolveSelfInvocation({
|
|
93996
94544
|
execPath: params.execPath ?? process.execPath,
|
|
@@ -94075,7 +94623,7 @@ function createRolloutDeps(params) {
|
|
|
94075
94623
|
return { status: r.ok ? 0 : 1, stdout: r.stdout };
|
|
94076
94624
|
}),
|
|
94077
94625
|
persistPin: (pin) => {
|
|
94078
|
-
const before =
|
|
94626
|
+
const before = readFileSync77(configPath, "utf8");
|
|
94079
94627
|
const after = setReleasePinInConfig(before, pin);
|
|
94080
94628
|
if (after === before)
|
|
94081
94629
|
return false;
|
|
@@ -94100,7 +94648,12 @@ function createRolloutDeps(params) {
|
|
|
94100
94648
|
`);
|
|
94101
94649
|
}
|
|
94102
94650
|
return outcome.reverted;
|
|
94103
|
-
}
|
|
94651
|
+
},
|
|
94652
|
+
...params.hindsightApiUrl && params.resolveBankId ? {
|
|
94653
|
+
ensureBank: (agent) => createBank(params.hindsightApiUrl, params.resolveBankId(agent), {
|
|
94654
|
+
timeoutMs: 5000
|
|
94655
|
+
})
|
|
94656
|
+
} : {}
|
|
94104
94657
|
};
|
|
94105
94658
|
}
|
|
94106
94659
|
function registerRolloutCommand(program3) {
|
|
@@ -94193,10 +94746,23 @@ function registerRolloutCommand(program3) {
|
|
|
94193
94746
|
}
|
|
94194
94747
|
const configPath = getConfigPath(program3);
|
|
94195
94748
|
const scriptPath = process.argv[1] ?? "";
|
|
94196
|
-
|
|
94749
|
+
let hindsightApiUrl;
|
|
94750
|
+
let resolveBankId;
|
|
94751
|
+
if (isHindsightEnabled(config)) {
|
|
94752
|
+
const rawUrl = config.memory?.config?.url;
|
|
94753
|
+
hindsightApiUrl = rawUrl ? rawUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "") : HINDSIGHT_DEFAULT_API_BASE_URL;
|
|
94754
|
+
resolveBankId = (agent) => config.agents?.[agent]?.memory?.collection ?? agent;
|
|
94755
|
+
}
|
|
94756
|
+
const deps = createRolloutDeps({
|
|
94757
|
+
configPath,
|
|
94758
|
+
scriptPath,
|
|
94759
|
+
hostdCtx,
|
|
94760
|
+
hindsightApiUrl,
|
|
94761
|
+
resolveBankId
|
|
94762
|
+
});
|
|
94197
94763
|
process.stdout.write(`${opts.allowDowngrade ? "Rolling back" : "Rolling"} ${requested.length} agent(s) to ${target}\u2026
|
|
94198
94764
|
`);
|
|
94199
|
-
const result = executeRollout(steps, target, deps, {
|
|
94765
|
+
const result = await executeRollout(steps, target, deps, {
|
|
94200
94766
|
hostdContext: hostdCtx,
|
|
94201
94767
|
allowDowngrade: opts.allowDowngrade
|
|
94202
94768
|
});
|
|
@@ -94217,6 +94783,15 @@ function registerRolloutCommand(program3) {
|
|
|
94217
94783
|
\u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} component(s) ` + `still behind ${target}: ${(result.drifted ?? []).join(", ") || "unknown"}.
|
|
94218
94784
|
` + ` ${result.rolled.length} agent(s) DID reach ${target} and the pin is ` + `committed \u2014 re-running the agent roll will not fix this.
|
|
94219
94785
|
` + ` Run the per-component command named in the warning(s) above, then ` + `\`switchroom update --check\` to confirm the host is converged.
|
|
94786
|
+
`);
|
|
94787
|
+
process.exitCode = 1;
|
|
94788
|
+
return;
|
|
94789
|
+
}
|
|
94790
|
+
if (!result.ok && result.failedStep === ENSURE_BANKS_STEP) {
|
|
94791
|
+
process.stderr.write(`
|
|
94792
|
+
\u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} agent(s) ` + `reached ${target} but their Hindsight bank could NOT be created: ` + `${(result.drifted ?? []).join(", ") || "unknown"}.
|
|
94793
|
+
` + ` The pin is committed and the agents ARE on the target \u2014 re-running ` + `the agent roll will not fix this.
|
|
94794
|
+
` + ` Restart the named agent(s) to re-run bank creation now that ` + `Hindsight is back up (their first \`retain\` fails with a foreign-key ` + `error until the bank exists).
|
|
94220
94795
|
`);
|
|
94221
94796
|
process.exitCode = 1;
|
|
94222
94797
|
return;
|
|
@@ -94263,8 +94838,8 @@ init_helpers();
|
|
|
94263
94838
|
init_lifecycle();
|
|
94264
94839
|
init_resolve_version();
|
|
94265
94840
|
import { execSync as execSync3 } from "node:child_process";
|
|
94266
|
-
import { existsSync as
|
|
94267
|
-
import { dirname as
|
|
94841
|
+
import { existsSync as existsSync83, readFileSync as readFileSync78 } from "node:fs";
|
|
94842
|
+
import { dirname as dirname36, join as join86 } from "node:path";
|
|
94268
94843
|
function getClaudeCodeVersion() {
|
|
94269
94844
|
try {
|
|
94270
94845
|
const out = execSync3("claude --version 2>/dev/null", {
|
|
@@ -94315,15 +94890,15 @@ function locateSwitchroomInstallDir() {
|
|
|
94315
94890
|
let dir = import.meta.dirname;
|
|
94316
94891
|
for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
|
|
94317
94892
|
const pkgPath = join86(dir, "package.json");
|
|
94318
|
-
if (
|
|
94893
|
+
if (existsSync83(pkgPath)) {
|
|
94319
94894
|
try {
|
|
94320
|
-
const pkg = JSON.parse(
|
|
94321
|
-
if (pkg.name === "switchroom" &&
|
|
94895
|
+
const pkg = JSON.parse(readFileSync78(pkgPath, "utf-8"));
|
|
94896
|
+
if (pkg.name === "switchroom" && existsSync83(join86(dir, ".git"))) {
|
|
94322
94897
|
return dir;
|
|
94323
94898
|
}
|
|
94324
94899
|
} catch {}
|
|
94325
94900
|
}
|
|
94326
|
-
dir =
|
|
94901
|
+
dir = dirname36(dir);
|
|
94327
94902
|
}
|
|
94328
94903
|
return null;
|
|
94329
94904
|
}
|
|
@@ -94497,7 +95072,7 @@ init_merge();
|
|
|
94497
95072
|
|
|
94498
95073
|
// src/agents/session-retention.ts
|
|
94499
95074
|
import {
|
|
94500
|
-
existsSync as
|
|
95075
|
+
existsSync as existsSync84,
|
|
94501
95076
|
readdirSync as readdirSync28,
|
|
94502
95077
|
statSync as statSync48,
|
|
94503
95078
|
unlinkSync as unlinkSync16
|
|
@@ -94508,7 +95083,7 @@ var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
|
|
|
94508
95083
|
var MIN_KEEP = 2;
|
|
94509
95084
|
function collectSessionJsonl(claudeConfigDir) {
|
|
94510
95085
|
const projects = join87(claudeConfigDir, "projects");
|
|
94511
|
-
if (!
|
|
95086
|
+
if (!existsSync84(projects))
|
|
94512
95087
|
return [];
|
|
94513
95088
|
const found = [];
|
|
94514
95089
|
const walk2 = (dir) => {
|
|
@@ -94644,15 +95219,15 @@ function registerHandoffCommand(program3) {
|
|
|
94644
95219
|
// src/issues/store.ts
|
|
94645
95220
|
import {
|
|
94646
95221
|
closeSync as closeSync16,
|
|
94647
|
-
existsSync as
|
|
94648
|
-
mkdirSync as
|
|
95222
|
+
existsSync as existsSync85,
|
|
95223
|
+
mkdirSync as mkdirSync48,
|
|
94649
95224
|
openSync as openSync16,
|
|
94650
95225
|
readdirSync as readdirSync29,
|
|
94651
|
-
readFileSync as
|
|
94652
|
-
renameSync as
|
|
95226
|
+
readFileSync as readFileSync79,
|
|
95227
|
+
renameSync as renameSync22,
|
|
94653
95228
|
statSync as statSync49,
|
|
94654
95229
|
unlinkSync as unlinkSync17,
|
|
94655
|
-
writeFileSync as
|
|
95230
|
+
writeFileSync as writeFileSync33,
|
|
94656
95231
|
writeSync as writeSync9
|
|
94657
95232
|
} from "node:fs";
|
|
94658
95233
|
import { join as join88 } from "node:path";
|
|
@@ -94683,11 +95258,11 @@ var ISSUES_FILE = "issues.jsonl";
|
|
|
94683
95258
|
var ISSUES_LOCK = "issues.lock";
|
|
94684
95259
|
function readAll(stateDir) {
|
|
94685
95260
|
const path7 = join88(stateDir, ISSUES_FILE);
|
|
94686
|
-
if (!
|
|
95261
|
+
if (!existsSync85(path7))
|
|
94687
95262
|
return [];
|
|
94688
95263
|
let raw;
|
|
94689
95264
|
try {
|
|
94690
|
-
raw =
|
|
95265
|
+
raw = readFileSync79(path7, "utf-8");
|
|
94691
95266
|
} catch {
|
|
94692
95267
|
return [];
|
|
94693
95268
|
}
|
|
@@ -94760,7 +95335,7 @@ function record(stateDir, input, nowFn = Date.now) {
|
|
|
94760
95335
|
});
|
|
94761
95336
|
}
|
|
94762
95337
|
function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
94763
|
-
if (!
|
|
95338
|
+
if (!existsSync85(join88(stateDir, ISSUES_FILE)))
|
|
94764
95339
|
return 0;
|
|
94765
95340
|
return withLock(stateDir, () => {
|
|
94766
95341
|
const all = readAll(stateDir);
|
|
@@ -94778,7 +95353,7 @@ function resolve49(stateDir, fingerprint, nowFn = Date.now) {
|
|
|
94778
95353
|
});
|
|
94779
95354
|
}
|
|
94780
95355
|
function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
94781
|
-
if (!
|
|
95356
|
+
if (!existsSync85(join88(stateDir, ISSUES_FILE)))
|
|
94782
95357
|
return 0;
|
|
94783
95358
|
return withLock(stateDir, () => {
|
|
94784
95359
|
const all = readAll(stateDir);
|
|
@@ -94796,7 +95371,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
|
|
|
94796
95371
|
});
|
|
94797
95372
|
}
|
|
94798
95373
|
function prune(stateDir, opts = {}) {
|
|
94799
|
-
if (!
|
|
95374
|
+
if (!existsSync85(join88(stateDir, ISSUES_FILE)))
|
|
94800
95375
|
return 0;
|
|
94801
95376
|
return withLock(stateDir, () => {
|
|
94802
95377
|
const all = readAll(stateDir);
|
|
@@ -94826,7 +95401,7 @@ function prune(stateDir, opts = {}) {
|
|
|
94826
95401
|
});
|
|
94827
95402
|
}
|
|
94828
95403
|
function ensureDir(stateDir) {
|
|
94829
|
-
|
|
95404
|
+
mkdirSync48(stateDir, { recursive: true });
|
|
94830
95405
|
}
|
|
94831
95406
|
function writeAll(stateDir, events) {
|
|
94832
95407
|
const path7 = join88(stateDir, ISSUES_FILE);
|
|
@@ -94835,8 +95410,8 @@ function writeAll(stateDir, events) {
|
|
|
94835
95410
|
const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
|
|
94836
95411
|
`) + `
|
|
94837
95412
|
`;
|
|
94838
|
-
|
|
94839
|
-
|
|
95413
|
+
writeFileSync33(tmp, body, "utf-8");
|
|
95414
|
+
renameSync22(tmp, path7);
|
|
94840
95415
|
}
|
|
94841
95416
|
var ORPHAN_TMP_TTL_MS = 60000;
|
|
94842
95417
|
var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
|
|
@@ -94898,7 +95473,7 @@ function withLock(stateDir, fn) {
|
|
|
94898
95473
|
function tryStealStaleLock(lockPath) {
|
|
94899
95474
|
let pidStr;
|
|
94900
95475
|
try {
|
|
94901
|
-
pidStr =
|
|
95476
|
+
pidStr = readFileSync79(lockPath, "utf-8").trim();
|
|
94902
95477
|
} catch {
|
|
94903
95478
|
return true;
|
|
94904
95479
|
}
|
|
@@ -95146,20 +95721,20 @@ function relTime(deltaMs) {
|
|
|
95146
95721
|
|
|
95147
95722
|
// src/cli/deps.ts
|
|
95148
95723
|
init_source();
|
|
95149
|
-
import { existsSync as
|
|
95724
|
+
import { existsSync as existsSync88 } from "node:fs";
|
|
95150
95725
|
import { homedir as homedir49 } from "node:os";
|
|
95151
95726
|
import { join as join91, resolve as resolve50 } from "node:path";
|
|
95152
95727
|
|
|
95153
95728
|
// src/deps/python.ts
|
|
95154
95729
|
import { createHash as createHash18 } from "node:crypto";
|
|
95155
95730
|
import {
|
|
95156
|
-
existsSync as
|
|
95157
|
-
mkdirSync as
|
|
95158
|
-
readFileSync as
|
|
95731
|
+
existsSync as existsSync86,
|
|
95732
|
+
mkdirSync as mkdirSync49,
|
|
95733
|
+
readFileSync as readFileSync80,
|
|
95159
95734
|
rmSync as rmSync15,
|
|
95160
|
-
writeFileSync as
|
|
95735
|
+
writeFileSync as writeFileSync34
|
|
95161
95736
|
} from "node:fs";
|
|
95162
|
-
import { dirname as
|
|
95737
|
+
import { dirname as dirname37, join as join89 } from "node:path";
|
|
95163
95738
|
import { homedir as homedir47 } from "node:os";
|
|
95164
95739
|
import { execFileSync as execFileSync24 } from "node:child_process";
|
|
95165
95740
|
|
|
@@ -95175,13 +95750,13 @@ function defaultPythonCacheRoot() {
|
|
|
95175
95750
|
return join89(homedir47(), ".switchroom", "deps", "python");
|
|
95176
95751
|
}
|
|
95177
95752
|
function hashFile(path7) {
|
|
95178
|
-
return createHash18("sha256").update(
|
|
95753
|
+
return createHash18("sha256").update(readFileSync80(path7)).digest("hex");
|
|
95179
95754
|
}
|
|
95180
95755
|
function ensurePythonEnv(opts) {
|
|
95181
95756
|
const { skillName, requirementsPath, force = false } = opts;
|
|
95182
95757
|
const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
|
|
95183
95758
|
const hostPython = opts.pythonBin ?? "python3";
|
|
95184
|
-
if (!
|
|
95759
|
+
if (!existsSync86(requirementsPath)) {
|
|
95185
95760
|
throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
|
|
95186
95761
|
}
|
|
95187
95762
|
const venvDir = join89(cacheRoot, skillName);
|
|
@@ -95190,8 +95765,8 @@ function ensurePythonEnv(opts) {
|
|
|
95190
95765
|
const pythonBin = join89(binDir, "python");
|
|
95191
95766
|
const pipBin = join89(binDir, "pip");
|
|
95192
95767
|
const targetHash = hashFile(requirementsPath);
|
|
95193
|
-
if (!force &&
|
|
95194
|
-
const existingHash =
|
|
95768
|
+
if (!force && existsSync86(stampPath) && existsSync86(pythonBin)) {
|
|
95769
|
+
const existingHash = readFileSync80(stampPath, "utf8").trim();
|
|
95195
95770
|
if (existingHash === targetHash) {
|
|
95196
95771
|
return {
|
|
95197
95772
|
skillName,
|
|
@@ -95203,10 +95778,10 @@ function ensurePythonEnv(opts) {
|
|
|
95203
95778
|
};
|
|
95204
95779
|
}
|
|
95205
95780
|
}
|
|
95206
|
-
if (
|
|
95781
|
+
if (existsSync86(venvDir)) {
|
|
95207
95782
|
rmSync15(venvDir, { recursive: true, force: true });
|
|
95208
95783
|
}
|
|
95209
|
-
|
|
95784
|
+
mkdirSync49(dirname37(venvDir), { recursive: true });
|
|
95210
95785
|
try {
|
|
95211
95786
|
execFileSync24(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
|
|
95212
95787
|
} catch (err) {
|
|
@@ -95225,7 +95800,7 @@ function ensurePythonEnv(opts) {
|
|
|
95225
95800
|
const e = err;
|
|
95226
95801
|
throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
|
|
95227
95802
|
}
|
|
95228
|
-
|
|
95803
|
+
writeFileSync34(stampPath, targetHash + `
|
|
95229
95804
|
`);
|
|
95230
95805
|
return {
|
|
95231
95806
|
skillName,
|
|
@@ -95241,13 +95816,13 @@ function ensurePythonEnv(opts) {
|
|
|
95241
95816
|
import { createHash as createHash19 } from "node:crypto";
|
|
95242
95817
|
import {
|
|
95243
95818
|
copyFileSync as copyFileSync12,
|
|
95244
|
-
existsSync as
|
|
95245
|
-
mkdirSync as
|
|
95246
|
-
readFileSync as
|
|
95819
|
+
existsSync as existsSync87,
|
|
95820
|
+
mkdirSync as mkdirSync50,
|
|
95821
|
+
readFileSync as readFileSync81,
|
|
95247
95822
|
rmSync as rmSync16,
|
|
95248
|
-
writeFileSync as
|
|
95823
|
+
writeFileSync as writeFileSync35
|
|
95249
95824
|
} from "node:fs";
|
|
95250
|
-
import { dirname as
|
|
95825
|
+
import { dirname as dirname38, join as join90 } from "node:path";
|
|
95251
95826
|
import { homedir as homedir48 } from "node:os";
|
|
95252
95827
|
import { execFileSync as execFileSync25 } from "node:child_process";
|
|
95253
95828
|
|
|
@@ -95274,20 +95849,20 @@ function defaultNodeCacheRoot() {
|
|
|
95274
95849
|
return join90(homedir48(), ".switchroom", "deps", "node");
|
|
95275
95850
|
}
|
|
95276
95851
|
function hashDepInputs(packageJsonPath) {
|
|
95277
|
-
const sourceDir =
|
|
95852
|
+
const sourceDir = dirname38(packageJsonPath);
|
|
95278
95853
|
const hasher = createHash19("sha256");
|
|
95279
95854
|
hasher.update(`package.json
|
|
95280
95855
|
`);
|
|
95281
|
-
hasher.update(
|
|
95856
|
+
hasher.update(readFileSync81(packageJsonPath));
|
|
95282
95857
|
for (const lockName of ALL_LOCKFILES) {
|
|
95283
95858
|
const lockPath = join90(sourceDir, lockName);
|
|
95284
|
-
if (
|
|
95859
|
+
if (existsSync87(lockPath)) {
|
|
95285
95860
|
hasher.update(`
|
|
95286
95861
|
`);
|
|
95287
95862
|
hasher.update(lockName);
|
|
95288
95863
|
hasher.update(`
|
|
95289
95864
|
`);
|
|
95290
|
-
hasher.update(
|
|
95865
|
+
hasher.update(readFileSync81(lockPath));
|
|
95291
95866
|
}
|
|
95292
95867
|
}
|
|
95293
95868
|
return hasher.digest("hex");
|
|
@@ -95296,17 +95871,17 @@ function ensureNodeEnv(opts) {
|
|
|
95296
95871
|
const { skillName, packageJsonPath, force = false } = opts;
|
|
95297
95872
|
const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
|
|
95298
95873
|
const installer = opts.installer ?? "bun";
|
|
95299
|
-
if (!
|
|
95874
|
+
if (!existsSync87(packageJsonPath)) {
|
|
95300
95875
|
throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
|
|
95301
95876
|
}
|
|
95302
|
-
const sourceDir =
|
|
95877
|
+
const sourceDir = dirname38(packageJsonPath);
|
|
95303
95878
|
const envDir = join90(cacheRoot, skillName);
|
|
95304
95879
|
const stampPath = join90(envDir, ".package.sha256");
|
|
95305
95880
|
const nodeModulesDir = join90(envDir, "node_modules");
|
|
95306
95881
|
const binDir = join90(nodeModulesDir, ".bin");
|
|
95307
95882
|
const targetHash = hashDepInputs(packageJsonPath);
|
|
95308
|
-
if (!force &&
|
|
95309
|
-
const existingHash =
|
|
95883
|
+
if (!force && existsSync87(stampPath) && existsSync87(nodeModulesDir)) {
|
|
95884
|
+
const existingHash = readFileSync81(stampPath, "utf8").trim();
|
|
95310
95885
|
if (existingHash === targetHash) {
|
|
95311
95886
|
return {
|
|
95312
95887
|
skillName,
|
|
@@ -95317,15 +95892,15 @@ function ensureNodeEnv(opts) {
|
|
|
95317
95892
|
};
|
|
95318
95893
|
}
|
|
95319
95894
|
}
|
|
95320
|
-
if (
|
|
95895
|
+
if (existsSync87(envDir)) {
|
|
95321
95896
|
rmSync16(envDir, { recursive: true, force: true });
|
|
95322
95897
|
}
|
|
95323
|
-
|
|
95898
|
+
mkdirSync50(envDir, { recursive: true });
|
|
95324
95899
|
copyFileSync12(packageJsonPath, join90(envDir, "package.json"));
|
|
95325
95900
|
let copiedLockfile = false;
|
|
95326
95901
|
for (const lockName of LOCKFILES_FOR[installer]) {
|
|
95327
95902
|
const lockPath = join90(sourceDir, lockName);
|
|
95328
|
-
if (
|
|
95903
|
+
if (existsSync87(lockPath)) {
|
|
95329
95904
|
copyFileSync12(lockPath, join90(envDir, lockName));
|
|
95330
95905
|
copiedLockfile = true;
|
|
95331
95906
|
}
|
|
@@ -95342,7 +95917,7 @@ function ensureNodeEnv(opts) {
|
|
|
95342
95917
|
const e = err;
|
|
95343
95918
|
throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
|
|
95344
95919
|
}
|
|
95345
|
-
|
|
95920
|
+
writeFileSync35(stampPath, targetHash + `
|
|
95346
95921
|
`);
|
|
95347
95922
|
return {
|
|
95348
95923
|
skillName,
|
|
@@ -95361,22 +95936,22 @@ function registerDepsCommand(program3) {
|
|
|
95361
95936
|
const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
|
|
95362
95937
|
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) => {
|
|
95363
95938
|
const skillsRoot = builtinSkillsRoot();
|
|
95364
|
-
if (!
|
|
95939
|
+
if (!existsSync88(skillsRoot)) {
|
|
95365
95940
|
console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
|
|
95366
95941
|
process.exit(1);
|
|
95367
95942
|
}
|
|
95368
95943
|
const skillDir = join91(skillsRoot, skill);
|
|
95369
|
-
if (!
|
|
95944
|
+
if (!existsSync88(skillDir)) {
|
|
95370
95945
|
console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
|
|
95371
95946
|
process.exit(1);
|
|
95372
95947
|
}
|
|
95373
95948
|
const requirementsPath = join91(skillDir, "requirements.txt");
|
|
95374
95949
|
const packageJsonPath = join91(skillDir, "package.json");
|
|
95375
|
-
const wantPython = opts.python ?? (!opts.python && !opts.node &&
|
|
95376
|
-
const wantNode = opts.node ?? (!opts.python && !opts.node &&
|
|
95950
|
+
const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync88(requirementsPath));
|
|
95951
|
+
const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync88(packageJsonPath));
|
|
95377
95952
|
let did = 0;
|
|
95378
95953
|
if (wantPython) {
|
|
95379
|
-
if (!
|
|
95954
|
+
if (!existsSync88(requirementsPath)) {
|
|
95380
95955
|
console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
|
|
95381
95956
|
process.exit(1);
|
|
95382
95957
|
}
|
|
@@ -95400,7 +95975,7 @@ function registerDepsCommand(program3) {
|
|
|
95400
95975
|
}
|
|
95401
95976
|
}
|
|
95402
95977
|
if (wantNode) {
|
|
95403
|
-
if (!
|
|
95978
|
+
if (!existsSync88(packageJsonPath)) {
|
|
95404
95979
|
console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
|
|
95405
95980
|
process.exit(1);
|
|
95406
95981
|
}
|
|
@@ -95433,9 +96008,9 @@ function registerDepsCommand(program3) {
|
|
|
95433
96008
|
// src/cli/workspace.ts
|
|
95434
96009
|
init_helpers();
|
|
95435
96010
|
init_loader();
|
|
95436
|
-
import { existsSync as
|
|
96011
|
+
import { existsSync as existsSync89 } from "node:fs";
|
|
95437
96012
|
import { resolve as resolve51, sep as sep4 } from "node:path";
|
|
95438
|
-
import { spawnSync as
|
|
96013
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
95439
96014
|
|
|
95440
96015
|
// src/agents/workspace.ts
|
|
95441
96016
|
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
@@ -96148,7 +96723,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
96148
96723
|
process.exit(1);
|
|
96149
96724
|
}
|
|
96150
96725
|
const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
|
|
96151
|
-
const child =
|
|
96726
|
+
const child = spawnSync20(editor, [target], { stdio: "inherit" });
|
|
96152
96727
|
if (child.status !== 0 && child.status !== null) {
|
|
96153
96728
|
process.exit(child.status);
|
|
96154
96729
|
}
|
|
@@ -96210,12 +96785,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
96210
96785
|
if (!dir)
|
|
96211
96786
|
return;
|
|
96212
96787
|
const gitDir = resolve51(dir, ".git");
|
|
96213
|
-
if (!
|
|
96788
|
+
if (!existsSync89(gitDir)) {
|
|
96214
96789
|
process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
|
|
96215
96790
|
`);
|
|
96216
96791
|
return;
|
|
96217
96792
|
}
|
|
96218
|
-
const statusResult =
|
|
96793
|
+
const statusResult = spawnSync20("git", ["status", "--short"], {
|
|
96219
96794
|
cwd: dir,
|
|
96220
96795
|
encoding: "utf-8"
|
|
96221
96796
|
});
|
|
@@ -96230,7 +96805,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
96230
96805
|
return;
|
|
96231
96806
|
}
|
|
96232
96807
|
const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
|
|
96233
|
-
const addResult =
|
|
96808
|
+
const addResult = spawnSync20("git", ["add", "-A"], {
|
|
96234
96809
|
cwd: dir,
|
|
96235
96810
|
encoding: "utf-8"
|
|
96236
96811
|
});
|
|
@@ -96239,7 +96814,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
96239
96814
|
`);
|
|
96240
96815
|
process.exit(1);
|
|
96241
96816
|
}
|
|
96242
|
-
const commitResult =
|
|
96817
|
+
const commitResult = spawnSync20("git", ["commit", "-m", message], {
|
|
96243
96818
|
cwd: dir,
|
|
96244
96819
|
encoding: "utf-8"
|
|
96245
96820
|
});
|
|
@@ -96248,7 +96823,7 @@ function registerWorkspaceCommand(program3) {
|
|
|
96248
96823
|
`);
|
|
96249
96824
|
process.exit(1);
|
|
96250
96825
|
}
|
|
96251
|
-
const shaResult =
|
|
96826
|
+
const shaResult = spawnSync20("git", ["rev-parse", "--short", "HEAD"], {
|
|
96252
96827
|
cwd: dir,
|
|
96253
96828
|
encoding: "utf-8"
|
|
96254
96829
|
});
|
|
@@ -96264,12 +96839,12 @@ function registerWorkspaceCommand(program3) {
|
|
|
96264
96839
|
if (!dir)
|
|
96265
96840
|
return;
|
|
96266
96841
|
const gitDir = resolve51(dir, ".git");
|
|
96267
|
-
if (!
|
|
96842
|
+
if (!existsSync89(gitDir)) {
|
|
96268
96843
|
process.stdout.write(`Workspace is not a git repository.
|
|
96269
96844
|
`);
|
|
96270
96845
|
return;
|
|
96271
96846
|
}
|
|
96272
|
-
const child =
|
|
96847
|
+
const child = spawnSync20("git", ["status", "--short"], {
|
|
96273
96848
|
cwd: dir,
|
|
96274
96849
|
stdio: "inherit"
|
|
96275
96850
|
});
|
|
@@ -96289,7 +96864,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
|
|
|
96289
96864
|
const agentsDir = resolveAgentsDir(config);
|
|
96290
96865
|
const agentDir = resolve51(agentsDir, agentName);
|
|
96291
96866
|
const dir = resolveAgentWorkspaceDir(agentDir);
|
|
96292
|
-
if (!
|
|
96867
|
+
if (!existsSync89(dir)) {
|
|
96293
96868
|
process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
|
|
96294
96869
|
`);
|
|
96295
96870
|
return;
|
|
@@ -96325,7 +96900,7 @@ function safeParseInt(value, fallback) {
|
|
|
96325
96900
|
init_helpers();
|
|
96326
96901
|
init_loader();
|
|
96327
96902
|
init_merge();
|
|
96328
|
-
import { copyFileSync as copyFileSync13, existsSync as
|
|
96903
|
+
import { copyFileSync as copyFileSync13, existsSync as existsSync90, readFileSync as readFileSync82, writeFileSync as writeFileSync36 } from "node:fs";
|
|
96329
96904
|
import { join as join92, resolve as resolve52 } from "node:path";
|
|
96330
96905
|
init_scaffold();
|
|
96331
96906
|
init_profiles();
|
|
@@ -96343,7 +96918,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
|
|
|
96343
96918
|
const agentsDir = resolveAgentsDir(config);
|
|
96344
96919
|
const agentDir = resolve52(agentsDir, agentName);
|
|
96345
96920
|
const workspaceDir = resolveAgentWorkspaceDir(agentDir);
|
|
96346
|
-
if (!
|
|
96921
|
+
if (!existsSync90(workspaceDir)) {
|
|
96347
96922
|
console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
|
|
96348
96923
|
process.exit(1);
|
|
96349
96924
|
}
|
|
@@ -96369,11 +96944,11 @@ function registerSoulCommand(program3) {
|
|
|
96369
96944
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
96370
96945
|
if (!t)
|
|
96371
96946
|
return;
|
|
96372
|
-
if (!
|
|
96947
|
+
if (!existsSync90(t.soulPath)) {
|
|
96373
96948
|
console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
|
|
96374
96949
|
process.exit(1);
|
|
96375
96950
|
}
|
|
96376
|
-
process.stdout.write(
|
|
96951
|
+
process.stdout.write(readFileSync82(t.soulPath, "utf-8"));
|
|
96377
96952
|
}));
|
|
96378
96953
|
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) => {
|
|
96379
96954
|
const t = resolveSoulTargetOrExit(program3, agentName);
|
|
@@ -96384,7 +96959,7 @@ function registerSoulCommand(program3) {
|
|
|
96384
96959
|
console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
|
|
96385
96960
|
process.exit(1);
|
|
96386
96961
|
}
|
|
96387
|
-
const exists =
|
|
96962
|
+
const exists = existsSync90(t.soulPath);
|
|
96388
96963
|
if (exists && !opts.yes) {
|
|
96389
96964
|
if (!isInteractive()) {
|
|
96390
96965
|
console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
|
|
@@ -96399,12 +96974,12 @@ function registerSoulCommand(program3) {
|
|
|
96399
96974
|
let backupPath;
|
|
96400
96975
|
if (exists) {
|
|
96401
96976
|
backupPath = `${t.soulPath}.bak`;
|
|
96402
|
-
if (
|
|
96977
|
+
if (existsSync90(backupPath)) {
|
|
96403
96978
|
backupPath = `${t.soulPath}.bak.${Date.now()}`;
|
|
96404
96979
|
}
|
|
96405
96980
|
copyFileSync13(t.soulPath, backupPath);
|
|
96406
96981
|
}
|
|
96407
|
-
|
|
96982
|
+
writeFileSync36(t.soulPath, content, "utf-8");
|
|
96408
96983
|
if (backupPath) {
|
|
96409
96984
|
console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
|
|
96410
96985
|
` + ` Previous version saved to ${backupPath}`);
|
|
@@ -96418,7 +96993,7 @@ function registerSoulCommand(program3) {
|
|
|
96418
96993
|
// src/cli/debug.ts
|
|
96419
96994
|
init_helpers();
|
|
96420
96995
|
init_loader();
|
|
96421
|
-
import { existsSync as
|
|
96996
|
+
import { existsSync as existsSync91, readFileSync as readFileSync83, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
|
|
96422
96997
|
import { resolve as resolve53, join as join93 } from "node:path";
|
|
96423
96998
|
import { createHash as createHash20 } from "node:crypto";
|
|
96424
96999
|
init_merge();
|
|
@@ -96431,10 +97006,10 @@ function estimateTokens(bytes) {
|
|
|
96431
97006
|
}
|
|
96432
97007
|
function readMcpServerNames(agentDir) {
|
|
96433
97008
|
const mcpPath = join93(agentDir, ".mcp.json");
|
|
96434
|
-
if (!
|
|
97009
|
+
if (!existsSync91(mcpPath))
|
|
96435
97010
|
return [];
|
|
96436
97011
|
try {
|
|
96437
|
-
const parsed = JSON.parse(
|
|
97012
|
+
const parsed = JSON.parse(readFileSync83(mcpPath, "utf-8"));
|
|
96438
97013
|
return Object.keys(parsed.mcpServers ?? {});
|
|
96439
97014
|
} catch {
|
|
96440
97015
|
return null;
|
|
@@ -96445,7 +97020,7 @@ function sha256(content) {
|
|
|
96445
97020
|
}
|
|
96446
97021
|
function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
96447
97022
|
const projectsDir = join93(claudeConfigDir, "projects");
|
|
96448
|
-
if (!
|
|
97023
|
+
if (!existsSync91(projectsDir))
|
|
96449
97024
|
return;
|
|
96450
97025
|
try {
|
|
96451
97026
|
const entries = readdirSync30(projectsDir, { withFileTypes: true });
|
|
@@ -96455,7 +97030,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
96455
97030
|
continue;
|
|
96456
97031
|
const projectPath = join93(projectsDir, entry.name);
|
|
96457
97032
|
const transcriptPath = join93(projectPath, "transcript.jsonl");
|
|
96458
|
-
if (!
|
|
97033
|
+
if (!existsSync91(transcriptPath))
|
|
96459
97034
|
continue;
|
|
96460
97035
|
const stat3 = statSync50(transcriptPath);
|
|
96461
97036
|
if (!latest || stat3.mtimeMs > latest.mtime) {
|
|
@@ -96469,7 +97044,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
|
|
|
96469
97044
|
}
|
|
96470
97045
|
function extractLatestUserMessage(transcriptPath) {
|
|
96471
97046
|
try {
|
|
96472
|
-
const content =
|
|
97047
|
+
const content = readFileSync83(transcriptPath, "utf-8");
|
|
96473
97048
|
const lines = content.trim().split(`
|
|
96474
97049
|
`).filter(Boolean);
|
|
96475
97050
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
@@ -96518,7 +97093,7 @@ function registerDebugCommand(program3) {
|
|
|
96518
97093
|
}
|
|
96519
97094
|
const agentsDir = resolveAgentsDir(config);
|
|
96520
97095
|
const agentDir = resolve53(agentsDir, agentName);
|
|
96521
|
-
if (!
|
|
97096
|
+
if (!existsSync91(agentDir)) {
|
|
96522
97097
|
console.error(`Agent directory not found: ${agentDir}`);
|
|
96523
97098
|
process.exit(1);
|
|
96524
97099
|
}
|
|
@@ -96573,7 +97148,7 @@ function registerDebugCommand(program3) {
|
|
|
96573
97148
|
}
|
|
96574
97149
|
console.log(`=== Append System Prompt (per-session) ===
|
|
96575
97150
|
`);
|
|
96576
|
-
const handoffContent =
|
|
97151
|
+
const handoffContent = existsSync91(handoffPath) ? readFileSync83(handoffPath, "utf-8") : "";
|
|
96577
97152
|
if (handoffContent.trim().length > 0) {
|
|
96578
97153
|
console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
|
|
96579
97154
|
console.log(handoffContent);
|
|
@@ -96584,7 +97159,7 @@ function registerDebugCommand(program3) {
|
|
|
96584
97159
|
}
|
|
96585
97160
|
console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
|
|
96586
97161
|
`);
|
|
96587
|
-
const claudeMdContent =
|
|
97162
|
+
const claudeMdContent = existsSync91(claudeMdPath) ? readFileSync83(claudeMdPath, "utf-8") : "";
|
|
96588
97163
|
if (claudeMdContent.trim().length > 0) {
|
|
96589
97164
|
console.log(`(${formatBytes(claudeMdContent.length)})`);
|
|
96590
97165
|
console.log(claudeMdContent);
|
|
@@ -96595,7 +97170,7 @@ function registerDebugCommand(program3) {
|
|
|
96595
97170
|
}
|
|
96596
97171
|
console.log(`=== Persona (SOUL.md) ===
|
|
96597
97172
|
`);
|
|
96598
|
-
const soulMdContent =
|
|
97173
|
+
const soulMdContent = existsSync91(soulMdPath) ? readFileSync83(soulMdPath, "utf-8") : existsSync91(workspaceSoulMdPath) ? readFileSync83(workspaceSoulMdPath, "utf-8") : "";
|
|
96599
97174
|
if (soulMdContent.trim().length > 0) {
|
|
96600
97175
|
console.log(`(${formatBytes(soulMdContent.length)})`);
|
|
96601
97176
|
console.log(soulMdContent);
|
|
@@ -96659,8 +97234,8 @@ function registerDebugCommand(program3) {
|
|
|
96659
97234
|
const fleetDir = join93(agentsDir, "..", "fleet");
|
|
96660
97235
|
const fleetInvPath = join93(fleetDir, "switchroom-invariants.md");
|
|
96661
97236
|
const fleetClaudePath = join93(fleetDir, "CLAUDE.md");
|
|
96662
|
-
const fleetInvBytes =
|
|
96663
|
-
const fleetClaudeBytes =
|
|
97237
|
+
const fleetInvBytes = existsSync91(fleetInvPath) ? readFileSync83(fleetInvPath, "utf-8").length : 0;
|
|
97238
|
+
const fleetClaudeBytes = existsSync91(fleetClaudePath) ? readFileSync83(fleetClaudePath, "utf-8").length : 0;
|
|
96664
97239
|
const fleetBytes = fleetInvBytes + fleetClaudeBytes;
|
|
96665
97240
|
const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
|
|
96666
97241
|
console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
|
|
@@ -96693,20 +97268,20 @@ init_source();
|
|
|
96693
97268
|
|
|
96694
97269
|
// src/worktree/claim.ts
|
|
96695
97270
|
import { execFileSync as execFileSync26 } from "node:child_process";
|
|
96696
|
-
import { closeSync as closeSync17, mkdirSync as
|
|
97271
|
+
import { closeSync as closeSync17, mkdirSync as mkdirSync52, openSync as openSync17, existsSync as existsSync93, unlinkSync as unlinkSync19 } from "node:fs";
|
|
96697
97272
|
import { join as join95, resolve as resolve55 } from "node:path";
|
|
96698
97273
|
import { homedir as homedir51 } from "node:os";
|
|
96699
97274
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
96700
97275
|
|
|
96701
97276
|
// src/worktree/registry.ts
|
|
96702
97277
|
import {
|
|
96703
|
-
mkdirSync as
|
|
96704
|
-
writeFileSync as
|
|
96705
|
-
readFileSync as
|
|
97278
|
+
mkdirSync as mkdirSync51,
|
|
97279
|
+
writeFileSync as writeFileSync37,
|
|
97280
|
+
readFileSync as readFileSync84,
|
|
96706
97281
|
readdirSync as readdirSync31,
|
|
96707
97282
|
unlinkSync as unlinkSync18,
|
|
96708
|
-
existsSync as
|
|
96709
|
-
renameSync as
|
|
97283
|
+
existsSync as existsSync92,
|
|
97284
|
+
renameSync as renameSync23
|
|
96710
97285
|
} from "node:fs";
|
|
96711
97286
|
import { join as join94, resolve as resolve54 } from "node:path";
|
|
96712
97287
|
import { homedir as homedir50 } from "node:os";
|
|
@@ -96717,20 +97292,20 @@ function recordPath(id) {
|
|
|
96717
97292
|
return join94(registryDir(), `${id}.json`);
|
|
96718
97293
|
}
|
|
96719
97294
|
function ensureDir2() {
|
|
96720
|
-
|
|
97295
|
+
mkdirSync51(registryDir(), { recursive: true });
|
|
96721
97296
|
}
|
|
96722
97297
|
function writeRecord(record2) {
|
|
96723
97298
|
ensureDir2();
|
|
96724
97299
|
const target = recordPath(record2.id);
|
|
96725
97300
|
const tmp = `${target}.tmp${process.pid}`;
|
|
96726
|
-
|
|
97301
|
+
writeFileSync37(tmp, JSON.stringify(record2, null, 2) + `
|
|
96727
97302
|
`, { mode: 384 });
|
|
96728
|
-
|
|
97303
|
+
renameSync23(tmp, target);
|
|
96729
97304
|
}
|
|
96730
97305
|
function readRecord(id) {
|
|
96731
97306
|
const path10 = recordPath(id);
|
|
96732
97307
|
try {
|
|
96733
|
-
const raw =
|
|
97308
|
+
const raw = readFileSync84(path10, "utf8");
|
|
96734
97309
|
return JSON.parse(raw);
|
|
96735
97310
|
} catch {
|
|
96736
97311
|
return null;
|
|
@@ -96763,7 +97338,7 @@ function countByRepo(repoPath) {
|
|
|
96763
97338
|
// src/worktree/claim.ts
|
|
96764
97339
|
function acquireRepoLock(repoPath) {
|
|
96765
97340
|
const lockDir = registryDir();
|
|
96766
|
-
|
|
97341
|
+
mkdirSync52(lockDir, { recursive: true });
|
|
96767
97342
|
const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
|
|
96768
97343
|
const lockPath = join95(lockDir, `.lock-${lockName}`);
|
|
96769
97344
|
const deadline = Date.now() + 5000;
|
|
@@ -96819,7 +97394,7 @@ function expandHome(p) {
|
|
|
96819
97394
|
}
|
|
96820
97395
|
async function claimWorktree(input, codeRepos) {
|
|
96821
97396
|
const repoPath = resolveRepoPath(input.repo, codeRepos);
|
|
96822
|
-
if (!
|
|
97397
|
+
if (!existsSync93(repoPath)) {
|
|
96823
97398
|
throw new Error(`Repository path does not exist: ${repoPath}`);
|
|
96824
97399
|
}
|
|
96825
97400
|
let concurrencyCap = DEFAULT_CONCURRENCY;
|
|
@@ -96841,7 +97416,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
96841
97416
|
const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
|
|
96842
97417
|
branch = `task/${taskSuffix}-${id}`;
|
|
96843
97418
|
const baseDir = worktreesBaseDir();
|
|
96844
|
-
|
|
97419
|
+
mkdirSync52(baseDir, { recursive: true });
|
|
96845
97420
|
worktreePath = join95(baseDir, `${id}-${taskSuffix}`);
|
|
96846
97421
|
const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
|
|
96847
97422
|
const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
|
|
@@ -96875,7 +97450,7 @@ async function claimWorktree(input, codeRepos) {
|
|
|
96875
97450
|
|
|
96876
97451
|
// src/worktree/release.ts
|
|
96877
97452
|
import { execFileSync as execFileSync27 } from "node:child_process";
|
|
96878
|
-
import { existsSync as
|
|
97453
|
+
import { existsSync as existsSync94 } from "node:fs";
|
|
96879
97454
|
function releaseWorktree(input) {
|
|
96880
97455
|
const { id } = input;
|
|
96881
97456
|
const record2 = readRecord(id);
|
|
@@ -96883,7 +97458,7 @@ function releaseWorktree(input) {
|
|
|
96883
97458
|
return { released: true };
|
|
96884
97459
|
}
|
|
96885
97460
|
let gitSuccess = true;
|
|
96886
|
-
if (
|
|
97461
|
+
if (existsSync94(record2.path)) {
|
|
96887
97462
|
try {
|
|
96888
97463
|
execFileSync27("git", ["worktree", "remove", "--force", record2.path], {
|
|
96889
97464
|
cwd: record2.repo,
|
|
@@ -96922,7 +97497,7 @@ function listWorktrees() {
|
|
|
96922
97497
|
|
|
96923
97498
|
// src/worktree/reaper.ts
|
|
96924
97499
|
import { execFileSync as execFileSync28 } from "node:child_process";
|
|
96925
|
-
import { existsSync as
|
|
97500
|
+
import { existsSync as existsSync95 } from "node:fs";
|
|
96926
97501
|
var STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
|
96927
97502
|
function reapSkipReasonText(action) {
|
|
96928
97503
|
switch (action) {
|
|
@@ -96983,7 +97558,7 @@ function planReaper(nowMs, deps = {}) {
|
|
|
96983
97558
|
const plan = [];
|
|
96984
97559
|
for (const record2 of listRecords()) {
|
|
96985
97560
|
const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
|
|
96986
|
-
const worktreeExists =
|
|
97561
|
+
const worktreeExists = existsSync95(record2.path);
|
|
96987
97562
|
if (!worktreeExists) {
|
|
96988
97563
|
plan.push({
|
|
96989
97564
|
record: record2,
|
|
@@ -97064,12 +97639,12 @@ function runReaper(nowMs, deps = {}) {
|
|
|
97064
97639
|
// src/worktree/gc.ts
|
|
97065
97640
|
import { execFileSync as execFileSync29 } from "node:child_process";
|
|
97066
97641
|
import {
|
|
97067
|
-
existsSync as
|
|
97068
|
-
readFileSync as
|
|
97642
|
+
existsSync as existsSync96,
|
|
97643
|
+
readFileSync as readFileSync85,
|
|
97069
97644
|
readdirSync as readdirSync32,
|
|
97070
97645
|
statSync as statSync51,
|
|
97071
|
-
renameSync as
|
|
97072
|
-
mkdirSync as
|
|
97646
|
+
renameSync as renameSync24,
|
|
97647
|
+
mkdirSync as mkdirSync53,
|
|
97073
97648
|
rmSync as rmSync17
|
|
97074
97649
|
} from "node:fs";
|
|
97075
97650
|
import { homedir as homedir52 } from "node:os";
|
|
@@ -97191,9 +97766,9 @@ function trashRoot() {
|
|
|
97191
97766
|
return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join96(homedir52(), ".switchroom", "worktree-gc-trash"));
|
|
97192
97767
|
}
|
|
97193
97768
|
function planGc(roots, deps = {}) {
|
|
97194
|
-
const exists = deps.existsSync ??
|
|
97769
|
+
const exists = deps.existsSync ?? existsSync96;
|
|
97195
97770
|
const readDir = deps.readDir ?? ((p) => readdirSync32(p));
|
|
97196
|
-
const readFile4 = deps.readFile ?? ((p) =>
|
|
97771
|
+
const readFile4 = deps.readFile ?? ((p) => readFileSync85(p, "utf8"));
|
|
97197
97772
|
const stat3 = deps.stat ?? ((p) => statSync51(p));
|
|
97198
97773
|
const exec = deps.exec ?? defaultExec3;
|
|
97199
97774
|
const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
|
|
@@ -97321,10 +97896,10 @@ function planGc(roots, deps = {}) {
|
|
|
97321
97896
|
}
|
|
97322
97897
|
function applyGc(plan, deps = {}) {
|
|
97323
97898
|
const exec = deps.exec ?? defaultExec3;
|
|
97324
|
-
const mkdirp = deps.mkdirp ?? ((p) => void
|
|
97899
|
+
const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync53(p, { recursive: true }));
|
|
97325
97900
|
const move = deps.move ?? ((src, dest) => {
|
|
97326
97901
|
try {
|
|
97327
|
-
|
|
97902
|
+
renameSync24(src, dest);
|
|
97328
97903
|
} catch {
|
|
97329
97904
|
exec("mv", [src, dest]);
|
|
97330
97905
|
}
|
|
@@ -97370,7 +97945,7 @@ function selectPurgeTargets(entries, olderThanDays) {
|
|
|
97370
97945
|
return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
|
|
97371
97946
|
}
|
|
97372
97947
|
function listTrashEntries(nowMs, deps = {}) {
|
|
97373
|
-
const exists = deps.existsSync ??
|
|
97948
|
+
const exists = deps.existsSync ?? existsSync96;
|
|
97374
97949
|
const readDir = deps.readDir ?? ((p) => readdirSync32(p));
|
|
97375
97950
|
const root = trashRoot();
|
|
97376
97951
|
if (!exists(root))
|
|
@@ -97632,10 +98207,10 @@ init_drive();
|
|
|
97632
98207
|
init_scaffold_integration();
|
|
97633
98208
|
import {
|
|
97634
98209
|
chmodSync as chmodSync16,
|
|
97635
|
-
mkdirSync as
|
|
98210
|
+
mkdirSync as mkdirSync54,
|
|
97636
98211
|
readdirSync as readdirSync33,
|
|
97637
98212
|
rmSync as rmSync18,
|
|
97638
|
-
writeFileSync as
|
|
98213
|
+
writeFileSync as writeFileSync38
|
|
97639
98214
|
} from "node:fs";
|
|
97640
98215
|
import { join as join97 } from "node:path";
|
|
97641
98216
|
function encodeCredentialsFilename(email) {
|
|
@@ -97830,14 +98405,14 @@ function resolveCredentialsDir(env2) {
|
|
|
97830
98405
|
return join97(stateBase, "google-workspace-mcp", "credentials");
|
|
97831
98406
|
}
|
|
97832
98407
|
function writeSeedFile(dir, email, seed) {
|
|
97833
|
-
|
|
98408
|
+
mkdirSync54(dir, { recursive: true, mode: 448 });
|
|
97834
98409
|
chmodSync16(dir, 448);
|
|
97835
98410
|
for (const name of readdirSync33(dir)) {
|
|
97836
98411
|
rmSync18(join97(dir, name), { force: true, recursive: true });
|
|
97837
98412
|
}
|
|
97838
98413
|
const filename = encodeCredentialsFilename(email);
|
|
97839
98414
|
const filePath = join97(dir, filename);
|
|
97840
|
-
|
|
98415
|
+
writeFileSync38(filePath, JSON.stringify(seed), { mode: 384 });
|
|
97841
98416
|
chmodSync16(filePath, 384);
|
|
97842
98417
|
return filePath;
|
|
97843
98418
|
}
|
|
@@ -97995,8 +98570,8 @@ function registerDriveMcpLauncherCommand(program3) {
|
|
|
97995
98570
|
// src/cli/m365-mcp-launcher.ts
|
|
97996
98571
|
init_scaffold_integration();
|
|
97997
98572
|
import { spawn as spawn6 } from "node:child_process";
|
|
97998
|
-
import { writeFileSync as
|
|
97999
|
-
import { dirname as
|
|
98573
|
+
import { writeFileSync as writeFileSync39, mkdirSync as mkdirSync55 } from "node:fs";
|
|
98574
|
+
import { dirname as dirname39, join as join98 } from "node:path";
|
|
98000
98575
|
var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
|
|
98001
98576
|
var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
|
|
98002
98577
|
var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -98032,8 +98607,8 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
|
|
|
98032
98607
|
function writeRefreshHeartbeat(agentName, data, account) {
|
|
98033
98608
|
const path10 = heartbeatPath(agentName, account);
|
|
98034
98609
|
try {
|
|
98035
|
-
|
|
98036
|
-
|
|
98610
|
+
mkdirSync55(dirname39(path10), { recursive: true });
|
|
98611
|
+
writeFileSync39(path10, JSON.stringify(data, null, 2), { mode: 420 });
|
|
98037
98612
|
} catch {}
|
|
98038
98613
|
}
|
|
98039
98614
|
function heartbeatPath(agentName, account) {
|
|
@@ -98273,8 +98848,8 @@ function registerM365McpLauncherCommand(program3) {
|
|
|
98273
98848
|
// src/cli/notion-mcp-launcher.ts
|
|
98274
98849
|
init_scaffold_integration();
|
|
98275
98850
|
import { spawn as spawn7 } from "node:child_process";
|
|
98276
|
-
import { existsSync as
|
|
98277
|
-
import { dirname as
|
|
98851
|
+
import { existsSync as existsSync97, mkdirSync as mkdirSync56, writeFileSync as writeFileSync40 } from "node:fs";
|
|
98852
|
+
import { dirname as dirname40 } from "node:path";
|
|
98278
98853
|
var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
|
|
98279
98854
|
var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
|
|
98280
98855
|
var DEFAULT_VAULT_KEY = "notion/integration-token";
|
|
@@ -98284,10 +98859,10 @@ function buildNotionMcpArgs(opts) {
|
|
|
98284
98859
|
}
|
|
98285
98860
|
function defaultWriteHeartbeat(path10, contents) {
|
|
98286
98861
|
try {
|
|
98287
|
-
const dir =
|
|
98288
|
-
if (!
|
|
98289
|
-
|
|
98290
|
-
|
|
98862
|
+
const dir = dirname40(path10);
|
|
98863
|
+
if (!existsSync97(dir))
|
|
98864
|
+
mkdirSync56(dir, { recursive: true });
|
|
98865
|
+
writeFileSync40(path10, contents);
|
|
98291
98866
|
} catch {}
|
|
98292
98867
|
}
|
|
98293
98868
|
async function runNotionMcpLauncher(opts, runtime) {
|
|
@@ -98396,7 +98971,7 @@ function registerNotionMcpLauncherCommand(program3) {
|
|
|
98396
98971
|
}
|
|
98397
98972
|
|
|
98398
98973
|
// src/cli/hindsight-mcp-shim.ts
|
|
98399
|
-
import { mkdirSync as
|
|
98974
|
+
import { mkdirSync as mkdirSync57, readFileSync as readFileSync86, renameSync as renameSync25, writeFileSync as writeFileSync41 } from "node:fs";
|
|
98400
98975
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
98401
98976
|
import { join as join99 } from "node:path";
|
|
98402
98977
|
import { createInterface as createInterface6 } from "node:readline";
|
|
@@ -98848,18 +99423,18 @@ class HindsightShim {
|
|
|
98848
99423
|
}
|
|
98849
99424
|
writeCache(result) {
|
|
98850
99425
|
try {
|
|
98851
|
-
|
|
99426
|
+
mkdirSync57(this.opts.cacheDir, { recursive: true });
|
|
98852
99427
|
const tmp = join99(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
|
|
98853
|
-
|
|
99428
|
+
writeFileSync41(tmp, JSON.stringify(result, null, 2) + `
|
|
98854
99429
|
`);
|
|
98855
|
-
|
|
99430
|
+
renameSync25(tmp, this.cachePath);
|
|
98856
99431
|
} catch (err) {
|
|
98857
99432
|
this.log(`[hindsight-shim] cache write failed: ${String(err)}`);
|
|
98858
99433
|
}
|
|
98859
99434
|
}
|
|
98860
99435
|
readCache() {
|
|
98861
99436
|
try {
|
|
98862
|
-
const parsed = JSON.parse(
|
|
99437
|
+
const parsed = JSON.parse(readFileSync86(this.cachePath, "utf-8"));
|
|
98863
99438
|
if (Array.isArray(parsed.tools))
|
|
98864
99439
|
return parsed;
|
|
98865
99440
|
return null;
|
|
@@ -99092,7 +99667,7 @@ function registerHindsightMcpShimCommand(program3) {
|
|
|
99092
99667
|
|
|
99093
99668
|
// src/cli/deliver-file.ts
|
|
99094
99669
|
init_client2();
|
|
99095
|
-
import { readFileSync as
|
|
99670
|
+
import { readFileSync as readFileSync87, statSync as statSync52 } from "node:fs";
|
|
99096
99671
|
import { basename as basename13 } from "node:path";
|
|
99097
99672
|
|
|
99098
99673
|
// src/delivery/onedrive.ts
|
|
@@ -99432,7 +100007,7 @@ async function defaultResolveProvider() {
|
|
|
99432
100007
|
async function runDeliverFile(localPath, deps = {}) {
|
|
99433
100008
|
const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
|
|
99434
100009
|
const sizeOf = deps.fileSize ?? ((p) => statSync52(p).size);
|
|
99435
|
-
const read = deps.readFile ?? ((p) => new Uint8Array(
|
|
100010
|
+
const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync87(p)));
|
|
99436
100011
|
const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
|
|
99437
100012
|
let size;
|
|
99438
100013
|
try {
|
|
@@ -99753,7 +100328,7 @@ function runRedactStdin() {
|
|
|
99753
100328
|
}
|
|
99754
100329
|
|
|
99755
100330
|
// src/cli/status-ask.ts
|
|
99756
|
-
import { readFileSync as
|
|
100331
|
+
import { readFileSync as readFileSync88, existsSync as existsSync98, readdirSync as readdirSync34 } from "node:fs";
|
|
99757
100332
|
import { join as join100 } from "node:path";
|
|
99758
100333
|
import { homedir as homedir53 } from "node:os";
|
|
99759
100334
|
|
|
@@ -100029,7 +100604,7 @@ function runReport(opts) {
|
|
|
100029
100604
|
for (const src of sources) {
|
|
100030
100605
|
let content;
|
|
100031
100606
|
try {
|
|
100032
|
-
content =
|
|
100607
|
+
content = readFileSync88(src.path, "utf-8");
|
|
100033
100608
|
} catch (err) {
|
|
100034
100609
|
process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
|
|
100035
100610
|
`);
|
|
@@ -100076,7 +100651,7 @@ function runReport(opts) {
|
|
|
100076
100651
|
function resolveSources(explicitPath) {
|
|
100077
100652
|
if (explicitPath != null && explicitPath.trim() !== "") {
|
|
100078
100653
|
const trimmed = explicitPath.trim();
|
|
100079
|
-
if (!
|
|
100654
|
+
if (!existsSync98(trimmed)) {
|
|
100080
100655
|
process.stderr.write(`status-ask report: ${trimmed}: file not found
|
|
100081
100656
|
`);
|
|
100082
100657
|
process.exit(1);
|
|
@@ -100092,7 +100667,7 @@ function resolveSources(explicitPath) {
|
|
|
100092
100667
|
} catch {
|
|
100093
100668
|
agentsDir = join100(homedir53(), ".switchroom", "agents");
|
|
100094
100669
|
}
|
|
100095
|
-
if (!
|
|
100670
|
+
if (!existsSync98(agentsDir))
|
|
100096
100671
|
return [];
|
|
100097
100672
|
const sources = [];
|
|
100098
100673
|
let entries;
|
|
@@ -100103,7 +100678,7 @@ function resolveSources(explicitPath) {
|
|
|
100103
100678
|
}
|
|
100104
100679
|
for (const name of entries) {
|
|
100105
100680
|
const path10 = join100(agentsDir, name, "runtime-metrics.jsonl");
|
|
100106
|
-
if (
|
|
100681
|
+
if (existsSync98(path10)) {
|
|
100107
100682
|
sources.push({ path: path10, agent: name });
|
|
100108
100683
|
}
|
|
100109
100684
|
}
|
|
@@ -100133,13 +100708,13 @@ var import_yaml21 = __toESM(require_dist(), 1);
|
|
|
100133
100708
|
init_paths();
|
|
100134
100709
|
import {
|
|
100135
100710
|
closeSync as closeSync18,
|
|
100136
|
-
existsSync as
|
|
100711
|
+
existsSync as existsSync99,
|
|
100137
100712
|
fsyncSync as fsyncSync8,
|
|
100138
|
-
mkdirSync as
|
|
100713
|
+
mkdirSync as mkdirSync58,
|
|
100139
100714
|
openSync as openSync18,
|
|
100140
100715
|
readdirSync as readdirSync35,
|
|
100141
|
-
readFileSync as
|
|
100142
|
-
renameSync as
|
|
100716
|
+
readFileSync as readFileSync89,
|
|
100717
|
+
renameSync as renameSync26,
|
|
100143
100718
|
statSync as statSync53,
|
|
100144
100719
|
unlinkSync as unlinkSync20,
|
|
100145
100720
|
writeSync as writeSync10
|
|
@@ -100163,15 +100738,15 @@ function overlayPathsFor(agent, opts = {}) {
|
|
|
100163
100738
|
};
|
|
100164
100739
|
}
|
|
100165
100740
|
function ensureDirs(paths) {
|
|
100166
|
-
|
|
100167
|
-
|
|
100741
|
+
mkdirSync58(paths.scheduleDir, { recursive: true });
|
|
100742
|
+
mkdirSync58(paths.scheduleStagingDir, { recursive: true });
|
|
100168
100743
|
}
|
|
100169
100744
|
function ensureSkillsDirs(paths) {
|
|
100170
|
-
|
|
100171
|
-
|
|
100745
|
+
mkdirSync58(paths.skillsDir, { recursive: true });
|
|
100746
|
+
mkdirSync58(paths.skillsStagingDir, { recursive: true });
|
|
100172
100747
|
}
|
|
100173
100748
|
function withAgentLock(paths, fn) {
|
|
100174
|
-
|
|
100749
|
+
mkdirSync58(paths.agentRoot, { recursive: true });
|
|
100175
100750
|
const start = Date.now();
|
|
100176
100751
|
const TIMEOUT_MS = 5000;
|
|
100177
100752
|
let fd = null;
|
|
@@ -100221,7 +100796,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
100221
100796
|
} finally {
|
|
100222
100797
|
closeSync18(fd);
|
|
100223
100798
|
}
|
|
100224
|
-
|
|
100799
|
+
renameSync26(stagingPath, finalPath);
|
|
100225
100800
|
return finalPath;
|
|
100226
100801
|
});
|
|
100227
100802
|
}
|
|
@@ -100238,7 +100813,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
|
|
|
100238
100813
|
} finally {
|
|
100239
100814
|
closeSync18(fd);
|
|
100240
100815
|
}
|
|
100241
|
-
|
|
100816
|
+
renameSync26(stagingPath, finalPath);
|
|
100242
100817
|
return finalPath;
|
|
100243
100818
|
});
|
|
100244
100819
|
}
|
|
@@ -100246,7 +100821,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
100246
100821
|
const paths = overlayPathsFor(agent, opts);
|
|
100247
100822
|
return withAgentLock(paths, () => {
|
|
100248
100823
|
const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
|
|
100249
|
-
if (!
|
|
100824
|
+
if (!existsSync99(finalPath))
|
|
100250
100825
|
return false;
|
|
100251
100826
|
unlinkSync20(finalPath);
|
|
100252
100827
|
return true;
|
|
@@ -100254,7 +100829,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
|
|
|
100254
100829
|
}
|
|
100255
100830
|
function listSkillsOverlayEntries(agent, opts = {}) {
|
|
100256
100831
|
const paths = overlayPathsFor(agent, opts);
|
|
100257
|
-
if (!
|
|
100832
|
+
if (!existsSync99(paths.skillsDir))
|
|
100258
100833
|
return [];
|
|
100259
100834
|
const out = [];
|
|
100260
100835
|
for (const name of readdirSync35(paths.skillsDir)) {
|
|
@@ -100262,7 +100837,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
|
|
|
100262
100837
|
continue;
|
|
100263
100838
|
const full = join101(paths.skillsDir, name);
|
|
100264
100839
|
try {
|
|
100265
|
-
const raw =
|
|
100840
|
+
const raw = readFileSync89(full, "utf-8");
|
|
100266
100841
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
100267
100842
|
out.push({ slug, path: full, raw });
|
|
100268
100843
|
} catch {}
|
|
@@ -100273,7 +100848,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
100273
100848
|
const paths = overlayPathsFor(agent, opts);
|
|
100274
100849
|
return withAgentLock(paths, () => {
|
|
100275
100850
|
const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
|
|
100276
|
-
if (!
|
|
100851
|
+
if (!existsSync99(finalPath))
|
|
100277
100852
|
return false;
|
|
100278
100853
|
unlinkSync20(finalPath);
|
|
100279
100854
|
return true;
|
|
@@ -100281,7 +100856,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
|
|
|
100281
100856
|
}
|
|
100282
100857
|
function listOverlayEntries(agent, opts = {}) {
|
|
100283
100858
|
const paths = overlayPathsFor(agent, opts);
|
|
100284
|
-
if (!
|
|
100859
|
+
if (!existsSync99(paths.scheduleDir))
|
|
100285
100860
|
return [];
|
|
100286
100861
|
const out = [];
|
|
100287
100862
|
for (const name of readdirSync35(paths.scheduleDir)) {
|
|
@@ -100289,7 +100864,7 @@ function listOverlayEntries(agent, opts = {}) {
|
|
|
100289
100864
|
continue;
|
|
100290
100865
|
const full = join101(paths.scheduleDir, name);
|
|
100291
100866
|
try {
|
|
100292
|
-
const raw =
|
|
100867
|
+
const raw = readFileSync89(full, "utf-8");
|
|
100293
100868
|
const slug = name.replace(/\.ya?ml$/i, "");
|
|
100294
100869
|
out.push({ slug, path: full, raw });
|
|
100295
100870
|
} catch {}
|
|
@@ -100509,15 +101084,15 @@ function reconcileAgentCronOnly(agent) {
|
|
|
100509
101084
|
// src/cli/agent-config-pending.ts
|
|
100510
101085
|
import {
|
|
100511
101086
|
closeSync as closeSync19,
|
|
100512
|
-
existsSync as
|
|
101087
|
+
existsSync as existsSync100,
|
|
100513
101088
|
fsyncSync as fsyncSync9,
|
|
100514
|
-
mkdirSync as
|
|
101089
|
+
mkdirSync as mkdirSync59,
|
|
100515
101090
|
openSync as openSync19,
|
|
100516
101091
|
readdirSync as readdirSync36,
|
|
100517
|
-
readFileSync as
|
|
100518
|
-
renameSync as
|
|
101092
|
+
readFileSync as readFileSync90,
|
|
101093
|
+
renameSync as renameSync27,
|
|
100519
101094
|
unlinkSync as unlinkSync21,
|
|
100520
|
-
writeFileSync as
|
|
101095
|
+
writeFileSync as writeFileSync42,
|
|
100521
101096
|
writeSync as writeSync11
|
|
100522
101097
|
} from "node:fs";
|
|
100523
101098
|
import { join as join102 } from "node:path";
|
|
@@ -100529,7 +101104,7 @@ function pendingDir(agent, opts = {}) {
|
|
|
100529
101104
|
}
|
|
100530
101105
|
function ensurePendingDir(agent, opts = {}) {
|
|
100531
101106
|
const dir = pendingDir(agent, opts);
|
|
100532
|
-
|
|
101107
|
+
mkdirSync59(dir, { recursive: true });
|
|
100533
101108
|
return dir;
|
|
100534
101109
|
}
|
|
100535
101110
|
function newStageId() {
|
|
@@ -100558,15 +101133,15 @@ function stagePendingScheduleEntry(opts) {
|
|
|
100558
101133
|
} finally {
|
|
100559
101134
|
closeSync19(fd);
|
|
100560
101135
|
}
|
|
100561
|
-
|
|
101136
|
+
renameSync27(yamlTmp, yamlPath);
|
|
100562
101137
|
}
|
|
100563
|
-
|
|
101138
|
+
writeFileSync42(metaPath, JSON.stringify(meta, null, 2) + `
|
|
100564
101139
|
`, { mode: 384 });
|
|
100565
101140
|
return { stageId, yamlPath, metaPath };
|
|
100566
101141
|
}
|
|
100567
101142
|
function listPendingScheduleEntries(agent, opts = {}) {
|
|
100568
101143
|
const dir = pendingDir(agent, opts);
|
|
100569
|
-
if (!
|
|
101144
|
+
if (!existsSync100(dir))
|
|
100570
101145
|
return [];
|
|
100571
101146
|
const out = [];
|
|
100572
101147
|
for (const name of readdirSync36(dir).sort()) {
|
|
@@ -100575,10 +101150,10 @@ function listPendingScheduleEntries(agent, opts = {}) {
|
|
|
100575
101150
|
const stageId = name.slice(0, -".meta.json".length);
|
|
100576
101151
|
const metaPath = join102(dir, name);
|
|
100577
101152
|
const yamlPath = join102(dir, `${stageId}.yaml`);
|
|
100578
|
-
if (!
|
|
101153
|
+
if (!existsSync100(yamlPath))
|
|
100579
101154
|
continue;
|
|
100580
101155
|
try {
|
|
100581
|
-
const meta = JSON.parse(
|
|
101156
|
+
const meta = JSON.parse(readFileSync90(metaPath, "utf-8"));
|
|
100582
101157
|
if (meta?.v !== 1 || typeof meta.stage_id !== "string")
|
|
100583
101158
|
continue;
|
|
100584
101159
|
out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
|
|
@@ -100594,10 +101169,10 @@ function commitPendingScheduleEntry(opts) {
|
|
|
100594
101169
|
const slug = match.meta.entry.name ?? match.stageId;
|
|
100595
101170
|
const paths = overlayPathsFor(opts.agent, { root: opts.root });
|
|
100596
101171
|
const finalPath = join102(paths.scheduleDir, `${slug}.yaml`);
|
|
100597
|
-
if (
|
|
101172
|
+
if (existsSync100(finalPath)) {
|
|
100598
101173
|
return { committed: false, reason: "slug_collision" };
|
|
100599
101174
|
}
|
|
100600
|
-
|
|
101175
|
+
renameSync27(match.yamlPath, finalPath);
|
|
100601
101176
|
unlinkSync21(match.metaPath);
|
|
100602
101177
|
return { committed: true, path: finalPath, slug };
|
|
100603
101178
|
}
|
|
@@ -100616,7 +101191,7 @@ function denyPendingScheduleEntry(opts) {
|
|
|
100616
101191
|
}
|
|
100617
101192
|
|
|
100618
101193
|
// src/cli/agent-config-write.ts
|
|
100619
|
-
import { existsSync as
|
|
101194
|
+
import { existsSync as existsSync101, readFileSync as readFileSync91 } from "node:fs";
|
|
100620
101195
|
import { execFileSync as execFileSync30 } from "node:child_process";
|
|
100621
101196
|
|
|
100622
101197
|
// src/scheduler/schedule-report.ts
|
|
@@ -101006,8 +101581,8 @@ function scheduleRemove(opts) {
|
|
|
101006
101581
|
}
|
|
101007
101582
|
let priorContent = null;
|
|
101008
101583
|
try {
|
|
101009
|
-
if (
|
|
101010
|
-
priorContent =
|
|
101584
|
+
if (existsSync101(match.path))
|
|
101585
|
+
priorContent = readFileSync91(match.path, "utf-8");
|
|
101011
101586
|
} catch {}
|
|
101012
101587
|
deleteOverlayEntry(agent, match.slug, { root: opts.root });
|
|
101013
101588
|
const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
|
|
@@ -101210,7 +101785,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
101210
101785
|
}
|
|
101211
101786
|
let blob;
|
|
101212
101787
|
if (opts.jsonl) {
|
|
101213
|
-
blob =
|
|
101788
|
+
blob = existsSync101(opts.jsonl) ? readFileSync91(opts.jsonl, "utf-8") : "";
|
|
101214
101789
|
} else {
|
|
101215
101790
|
try {
|
|
101216
101791
|
blob = execFileSync30("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
|
|
@@ -101242,7 +101817,7 @@ function registerAgentConfigWriteCommands(program3) {
|
|
|
101242
101817
|
|
|
101243
101818
|
// src/cli/agent-config-skill-write.ts
|
|
101244
101819
|
var import_yaml22 = __toESM(require_dist(), 1);
|
|
101245
|
-
import { existsSync as
|
|
101820
|
+
import { existsSync as existsSync102 } from "node:fs";
|
|
101246
101821
|
init_reconcile_default_skills();
|
|
101247
101822
|
init_agent_config();
|
|
101248
101823
|
var import_yaml23 = __toESM(require_dist(), 1);
|
|
@@ -101322,7 +101897,7 @@ function skillInstall(opts) {
|
|
|
101322
101897
|
}
|
|
101323
101898
|
const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
|
|
101324
101899
|
const skillPath = join103(poolDir, skillName);
|
|
101325
|
-
if (!
|
|
101900
|
+
if (!existsSync102(skillPath)) {
|
|
101326
101901
|
return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
|
|
101327
101902
|
}
|
|
101328
101903
|
const yamlText = import_yaml22.stringify({ skills: [skillName] });
|
|
@@ -101486,22 +102061,22 @@ function registerAgentConfigSkillWriteCommands(program3) {
|
|
|
101486
102061
|
// src/cli/skill.ts
|
|
101487
102062
|
import {
|
|
101488
102063
|
closeSync as closeSync20,
|
|
101489
|
-
existsSync as
|
|
102064
|
+
existsSync as existsSync103,
|
|
101490
102065
|
lstatSync as lstatSync12,
|
|
101491
|
-
mkdirSync as
|
|
102066
|
+
mkdirSync as mkdirSync60,
|
|
101492
102067
|
mkdtempSync as mkdtempSync5,
|
|
101493
102068
|
openSync as openSync20,
|
|
101494
|
-
readFileSync as
|
|
102069
|
+
readFileSync as readFileSync92,
|
|
101495
102070
|
readdirSync as readdirSync37,
|
|
101496
102071
|
realpathSync as realpathSync7,
|
|
101497
|
-
renameSync as
|
|
102072
|
+
renameSync as renameSync28,
|
|
101498
102073
|
rmSync as rmSync19,
|
|
101499
102074
|
statSync as statSync54,
|
|
101500
|
-
writeFileSync as
|
|
102075
|
+
writeFileSync as writeFileSync43
|
|
101501
102076
|
} from "node:fs";
|
|
101502
102077
|
import { tmpdir as tmpdir6, homedir as homedir54 } from "node:os";
|
|
101503
|
-
import { dirname as
|
|
101504
|
-
import { spawnSync as
|
|
102078
|
+
import { dirname as dirname41, join as join104, relative as relative4, resolve as resolve58 } from "node:path";
|
|
102079
|
+
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
101505
102080
|
|
|
101506
102081
|
// src/cli/skill-common.ts
|
|
101507
102082
|
var import_yaml24 = __toESM(require_dist(), 1);
|
|
@@ -101784,7 +102359,7 @@ function loadFromDir(dir) {
|
|
|
101784
102359
|
continue;
|
|
101785
102360
|
}
|
|
101786
102361
|
if (ent.isFile()) {
|
|
101787
|
-
const buf =
|
|
102362
|
+
const buf = readFileSync92(full);
|
|
101788
102363
|
files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
|
|
101789
102364
|
}
|
|
101790
102365
|
}
|
|
@@ -101795,7 +102370,7 @@ function loadFromDir(dir) {
|
|
|
101795
102370
|
function loadFromTarball(tarPath) {
|
|
101796
102371
|
const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
|
|
101797
102372
|
const listFlags = isGz ? ["-tzf"] : ["-tf"];
|
|
101798
|
-
const list2 =
|
|
102373
|
+
const list2 = spawnSync21("tar", [...listFlags, tarPath], {
|
|
101799
102374
|
encoding: "utf-8",
|
|
101800
102375
|
stdio: ["ignore", "pipe", "pipe"]
|
|
101801
102376
|
});
|
|
@@ -101812,7 +102387,7 @@ function loadFromTarball(tarPath) {
|
|
|
101812
102387
|
const staging = mkdtempSync5(join104(tmpdir6(), "skill-apply-extract-"));
|
|
101813
102388
|
try {
|
|
101814
102389
|
const flags = isGz ? ["-xzf"] : ["-xf"];
|
|
101815
|
-
const r =
|
|
102390
|
+
const r = spawnSync21("tar", [
|
|
101816
102391
|
...flags,
|
|
101817
102392
|
tarPath,
|
|
101818
102393
|
"-C",
|
|
@@ -101833,7 +102408,7 @@ function loadFromTarball(tarPath) {
|
|
|
101833
102408
|
}
|
|
101834
102409
|
}
|
|
101835
102410
|
function loadSingleFile(filePath) {
|
|
101836
|
-
const content =
|
|
102411
|
+
const content = readFileSync92(filePath, "utf-8");
|
|
101837
102412
|
return { "SKILL.md": content };
|
|
101838
102413
|
}
|
|
101839
102414
|
function loadFromStdin() {
|
|
@@ -101887,7 +102462,7 @@ function validatePayload(name, files) {
|
|
|
101887
102462
|
if (errors2.length === 0) {
|
|
101888
102463
|
for (const [path10, content] of Object.entries(files)) {
|
|
101889
102464
|
if (SH_SCRIPT_RE2.test(path10)) {
|
|
101890
|
-
const r =
|
|
102465
|
+
const r = spawnSync21("bash", ["-n"], {
|
|
101891
102466
|
input: content,
|
|
101892
102467
|
encoding: "utf-8"
|
|
101893
102468
|
});
|
|
@@ -101898,8 +102473,8 @@ function validatePayload(name, files) {
|
|
|
101898
102473
|
const tmp = mkdtempSync5(join104(tmpdir6(), "skill-apply-py-"));
|
|
101899
102474
|
const tmpPy = join104(tmp, "check.py");
|
|
101900
102475
|
try {
|
|
101901
|
-
|
|
101902
|
-
const r =
|
|
102476
|
+
writeFileSync43(tmpPy, content);
|
|
102477
|
+
const r = spawnSync21("python3", ["-m", "py_compile", tmpPy], {
|
|
101903
102478
|
encoding: "utf-8"
|
|
101904
102479
|
});
|
|
101905
102480
|
if (r.status !== 0) {
|
|
@@ -101916,7 +102491,7 @@ function validatePayload(name, files) {
|
|
|
101916
102491
|
function diffSummary(currentDir, files) {
|
|
101917
102492
|
const lines = [];
|
|
101918
102493
|
const currentFiles = {};
|
|
101919
|
-
if (
|
|
102494
|
+
if (existsSync103(currentDir)) {
|
|
101920
102495
|
const walk2 = (sub) => {
|
|
101921
102496
|
for (const ent of readdirSync37(sub, { withFileTypes: true })) {
|
|
101922
102497
|
const full = join104(sub, ent.name);
|
|
@@ -101924,7 +102499,7 @@ function diffSummary(currentDir, files) {
|
|
|
101924
102499
|
if (ent.isDirectory()) {
|
|
101925
102500
|
walk2(full);
|
|
101926
102501
|
} else if (ent.isFile()) {
|
|
101927
|
-
currentFiles[rel.replace(/\\/g, "/")] =
|
|
102502
|
+
currentFiles[rel.replace(/\\/g, "/")] = readFileSync92(full, "utf-8");
|
|
101928
102503
|
}
|
|
101929
102504
|
}
|
|
101930
102505
|
};
|
|
@@ -101952,8 +102527,8 @@ function diffSummary(currentDir, files) {
|
|
|
101952
102527
|
`);
|
|
101953
102528
|
}
|
|
101954
102529
|
function writePayload(poolDir, name, files) {
|
|
101955
|
-
if (!
|
|
101956
|
-
|
|
102530
|
+
if (!existsSync103(poolDir)) {
|
|
102531
|
+
mkdirSync60(poolDir, { recursive: true, mode: 493 });
|
|
101957
102532
|
}
|
|
101958
102533
|
const target = join104(poolDir, name);
|
|
101959
102534
|
let targetIsSymlink = false;
|
|
@@ -101971,10 +102546,10 @@ function writePayload(poolDir, name, files) {
|
|
|
101971
102546
|
try {
|
|
101972
102547
|
for (const [path10, content] of Object.entries(files)) {
|
|
101973
102548
|
const full = join104(staging, path10);
|
|
101974
|
-
|
|
102549
|
+
mkdirSync60(dirname41(full), { recursive: true, mode: 493 });
|
|
101975
102550
|
const fd = openSync20(full, "wx");
|
|
101976
102551
|
try {
|
|
101977
|
-
|
|
102552
|
+
writeFileSync43(fd, content);
|
|
101978
102553
|
} finally {
|
|
101979
102554
|
closeSync20(fd);
|
|
101980
102555
|
}
|
|
@@ -101990,9 +102565,9 @@ function writePayload(poolDir, name, files) {
|
|
|
101990
102565
|
} catch {}
|
|
101991
102566
|
if (targetExists) {
|
|
101992
102567
|
oldRename = `${target}.skill-apply-old-${Date.now()}`;
|
|
101993
|
-
|
|
102568
|
+
renameSync28(target, oldRename);
|
|
101994
102569
|
}
|
|
101995
|
-
|
|
102570
|
+
renameSync28(staging, target);
|
|
101996
102571
|
if (oldRename) {
|
|
101997
102572
|
rmSync19(oldRename, { recursive: true, force: true });
|
|
101998
102573
|
oldRename = null;
|
|
@@ -102001,12 +102576,12 @@ function writePayload(poolDir, name, files) {
|
|
|
102001
102576
|
try {
|
|
102002
102577
|
rmSync19(staging, { recursive: true, force: true });
|
|
102003
102578
|
} catch {}
|
|
102004
|
-
if (oldRename &&
|
|
102579
|
+
if (oldRename && existsSync103(oldRename)) {
|
|
102005
102580
|
try {
|
|
102006
|
-
if (
|
|
102581
|
+
if (existsSync103(target)) {
|
|
102007
102582
|
rmSync19(target, { recursive: true, force: true });
|
|
102008
102583
|
}
|
|
102009
|
-
|
|
102584
|
+
renameSync28(oldRename, target);
|
|
102010
102585
|
} catch {}
|
|
102011
102586
|
}
|
|
102012
102587
|
throw err2;
|
|
@@ -102024,7 +102599,7 @@ function registerSkillCommand(program3) {
|
|
|
102024
102599
|
files = loadFromStdin();
|
|
102025
102600
|
} else {
|
|
102026
102601
|
const fromPath = resolve58(opts.from);
|
|
102027
|
-
if (!
|
|
102602
|
+
if (!existsSync103(fromPath)) {
|
|
102028
102603
|
fail4(`--from path does not exist: ${opts.from}`);
|
|
102029
102604
|
}
|
|
102030
102605
|
const st = statSync54(fromPath);
|
|
@@ -102062,7 +102637,7 @@ function registerSkillCommand(program3) {
|
|
|
102062
102637
|
\u2713 Wrote ${name} to ${currentDir}`));
|
|
102063
102638
|
const self = selfInvokeArgv(["apply", "--non-interactive"]);
|
|
102064
102639
|
console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
|
|
102065
|
-
const r =
|
|
102640
|
+
const r = spawnSync21(self.command, self.argv, { stdio: "inherit" });
|
|
102066
102641
|
if (r.status !== 0) {
|
|
102067
102642
|
console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
|
|
102068
102643
|
}
|
|
@@ -102080,22 +102655,22 @@ function sumBytes(files) {
|
|
|
102080
102655
|
init_esm();
|
|
102081
102656
|
import {
|
|
102082
102657
|
closeSync as closeSync21,
|
|
102083
|
-
existsSync as
|
|
102658
|
+
existsSync as existsSync104,
|
|
102084
102659
|
lstatSync as lstatSync13,
|
|
102085
|
-
mkdirSync as
|
|
102660
|
+
mkdirSync as mkdirSync61,
|
|
102086
102661
|
mkdtempSync as mkdtempSync6,
|
|
102087
102662
|
openSync as openSync21,
|
|
102088
|
-
readFileSync as
|
|
102663
|
+
readFileSync as readFileSync93,
|
|
102089
102664
|
readdirSync as readdirSync38,
|
|
102090
|
-
renameSync as
|
|
102665
|
+
renameSync as renameSync29,
|
|
102091
102666
|
rmSync as rmSync20,
|
|
102092
102667
|
statSync as statSync55,
|
|
102093
102668
|
utimesSync,
|
|
102094
|
-
writeFileSync as
|
|
102669
|
+
writeFileSync as writeFileSync44
|
|
102095
102670
|
} from "node:fs";
|
|
102096
|
-
import { dirname as
|
|
102671
|
+
import { dirname as dirname42, join as join105, relative as relative5, resolve as resolve59 } from "node:path";
|
|
102097
102672
|
import { homedir as homedir55, tmpdir as tmpdir7 } from "node:os";
|
|
102098
|
-
import { spawnSync as
|
|
102673
|
+
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
102099
102674
|
init_helpers();
|
|
102100
102675
|
init_agent_config();
|
|
102101
102676
|
init_source();
|
|
@@ -102106,14 +102681,14 @@ var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
|
102106
102681
|
function resolveConfigSkillsDir(agent) {
|
|
102107
102682
|
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
102108
102683
|
const candidate = override ? resolve59(override) : join105(homedir55(), ".switchroom-config");
|
|
102109
|
-
if (!
|
|
102684
|
+
if (!existsSync104(candidate))
|
|
102110
102685
|
return null;
|
|
102111
102686
|
return join105(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
102112
102687
|
}
|
|
102113
102688
|
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
102114
102689
|
function sweepMirrorPriors(configSkillsRoot) {
|
|
102115
102690
|
try {
|
|
102116
|
-
if (!
|
|
102691
|
+
if (!existsSync104(configSkillsRoot))
|
|
102117
102692
|
return;
|
|
102118
102693
|
const now = Date.now();
|
|
102119
102694
|
for (const ent of readdirSync38(configSkillsRoot)) {
|
|
@@ -102149,17 +102724,17 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
102149
102724
|
}
|
|
102150
102725
|
if (liveSkillDir === null) {
|
|
102151
102726
|
sweepMirrorPriors(configSkillsRoot);
|
|
102152
|
-
if (
|
|
102727
|
+
if (existsSync104(dest)) {
|
|
102153
102728
|
const trash = join105(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
102154
|
-
|
|
102729
|
+
renameSync29(dest, trash);
|
|
102155
102730
|
}
|
|
102156
102731
|
return;
|
|
102157
102732
|
}
|
|
102158
|
-
|
|
102733
|
+
mkdirSync61(configSkillsRoot, { recursive: true, mode: 493 });
|
|
102159
102734
|
sweepMirrorPriors(configSkillsRoot);
|
|
102160
102735
|
const staging = mkdtempSync6(join105(configSkillsRoot, `.${name}-staging-`));
|
|
102161
102736
|
const walk2 = (src, dst) => {
|
|
102162
|
-
|
|
102737
|
+
mkdirSync61(dst, { recursive: true, mode: 493 });
|
|
102163
102738
|
for (const ent of readdirSync38(src, { withFileTypes: true })) {
|
|
102164
102739
|
const s = join105(src, ent.name);
|
|
102165
102740
|
const d = join105(dst, ent.name);
|
|
@@ -102168,16 +102743,16 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
|
102168
102743
|
if (ent.isDirectory())
|
|
102169
102744
|
walk2(s, d);
|
|
102170
102745
|
else if (ent.isFile()) {
|
|
102171
|
-
|
|
102746
|
+
writeFileSync44(d, readFileSync93(s));
|
|
102172
102747
|
}
|
|
102173
102748
|
}
|
|
102174
102749
|
};
|
|
102175
102750
|
walk2(liveSkillDir, staging);
|
|
102176
|
-
if (
|
|
102751
|
+
if (existsSync104(dest)) {
|
|
102177
102752
|
const prior = join105(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
102178
|
-
|
|
102753
|
+
renameSync29(dest, prior);
|
|
102179
102754
|
}
|
|
102180
|
-
|
|
102755
|
+
renameSync29(staging, dest);
|
|
102181
102756
|
} catch (err2) {
|
|
102182
102757
|
process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
|
|
102183
102758
|
`));
|
|
@@ -102214,7 +102789,7 @@ function trashDir(agentsRoot, agent) {
|
|
|
102214
102789
|
}
|
|
102215
102790
|
function countPersonalSkills(agentsRoot, agent) {
|
|
102216
102791
|
const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
|
|
102217
|
-
if (!
|
|
102792
|
+
if (!existsSync104(skillsDir))
|
|
102218
102793
|
return 0;
|
|
102219
102794
|
let n = 0;
|
|
102220
102795
|
for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
|
|
@@ -102261,7 +102836,7 @@ function loadFromDir2(dir) {
|
|
|
102261
102836
|
}
|
|
102262
102837
|
if (ent.isFile()) {
|
|
102263
102838
|
const rel = relative5(abs, full).replace(/\\/g, "/");
|
|
102264
|
-
files[rel] =
|
|
102839
|
+
files[rel] = readFileSync93(full, "utf-8");
|
|
102265
102840
|
}
|
|
102266
102841
|
}
|
|
102267
102842
|
};
|
|
@@ -102299,7 +102874,7 @@ function behavioralValidate(files) {
|
|
|
102299
102874
|
const errors2 = [];
|
|
102300
102875
|
for (const [path10, content] of Object.entries(files)) {
|
|
102301
102876
|
if (SH_SCRIPT_RE.test(path10)) {
|
|
102302
|
-
const r =
|
|
102877
|
+
const r = spawnSync22("bash", ["-n"], { input: content, encoding: "utf-8" });
|
|
102303
102878
|
if (r.status !== 0) {
|
|
102304
102879
|
errors2.push(`${path10} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
|
|
102305
102880
|
}
|
|
@@ -102307,8 +102882,8 @@ function behavioralValidate(files) {
|
|
|
102307
102882
|
const tmp = mkdtempSync6(join105(tmpdir7(), "skill-personal-py-"));
|
|
102308
102883
|
const tmpPy = join105(tmp, "check.py");
|
|
102309
102884
|
try {
|
|
102310
|
-
|
|
102311
|
-
const r =
|
|
102885
|
+
writeFileSync44(tmpPy, content);
|
|
102886
|
+
const r = spawnSync22("python3", ["-m", "py_compile", tmpPy], {
|
|
102312
102887
|
encoding: "utf-8"
|
|
102313
102888
|
});
|
|
102314
102889
|
if (r.status !== 0) {
|
|
@@ -102323,7 +102898,7 @@ function behavioralValidate(files) {
|
|
|
102323
102898
|
}
|
|
102324
102899
|
function sweepTrash(agentsRoot, agent) {
|
|
102325
102900
|
const trash = trashDir(agentsRoot, agent);
|
|
102326
|
-
if (!
|
|
102901
|
+
if (!existsSync104(trash))
|
|
102327
102902
|
return;
|
|
102328
102903
|
const now = Date.now();
|
|
102329
102904
|
for (const ent of readdirSync38(trash, { withFileTypes: true })) {
|
|
@@ -102349,16 +102924,16 @@ function writePersonalSkill(targetDir, files) {
|
|
|
102349
102924
|
if (targetIsSymlink) {
|
|
102350
102925
|
fail5(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
|
|
102351
102926
|
}
|
|
102352
|
-
|
|
102353
|
-
const staging = mkdtempSync6(join105(
|
|
102927
|
+
mkdirSync61(dirname42(targetDir), { recursive: true, mode: 493 });
|
|
102928
|
+
const staging = mkdtempSync6(join105(dirname42(targetDir), `.skill-personal-stage-`));
|
|
102354
102929
|
let oldRename = null;
|
|
102355
102930
|
try {
|
|
102356
102931
|
for (const [path10, content] of Object.entries(files)) {
|
|
102357
102932
|
const full = join105(staging, path10);
|
|
102358
|
-
|
|
102933
|
+
mkdirSync61(dirname42(full), { recursive: true, mode: 493 });
|
|
102359
102934
|
const fd = openSync21(full, "wx");
|
|
102360
102935
|
try {
|
|
102361
|
-
|
|
102936
|
+
writeFileSync44(fd, content);
|
|
102362
102937
|
} finally {
|
|
102363
102938
|
closeSync21(fd);
|
|
102364
102939
|
}
|
|
@@ -102374,9 +102949,9 @@ function writePersonalSkill(targetDir, files) {
|
|
|
102374
102949
|
} catch {}
|
|
102375
102950
|
if (targetExists) {
|
|
102376
102951
|
oldRename = `${targetDir}.personal-old-${Date.now()}`;
|
|
102377
|
-
|
|
102952
|
+
renameSync29(targetDir, oldRename);
|
|
102378
102953
|
}
|
|
102379
|
-
|
|
102954
|
+
renameSync29(staging, targetDir);
|
|
102380
102955
|
if (oldRename) {
|
|
102381
102956
|
rmSync20(oldRename, { recursive: true, force: true });
|
|
102382
102957
|
oldRename = null;
|
|
@@ -102385,12 +102960,12 @@ function writePersonalSkill(targetDir, files) {
|
|
|
102385
102960
|
try {
|
|
102386
102961
|
rmSync20(staging, { recursive: true, force: true });
|
|
102387
102962
|
} catch {}
|
|
102388
|
-
if (oldRename &&
|
|
102963
|
+
if (oldRename && existsSync104(oldRename)) {
|
|
102389
102964
|
try {
|
|
102390
|
-
if (
|
|
102965
|
+
if (existsSync104(targetDir)) {
|
|
102391
102966
|
rmSync20(targetDir, { recursive: true, force: true });
|
|
102392
102967
|
}
|
|
102393
|
-
|
|
102968
|
+
renameSync29(oldRename, targetDir);
|
|
102394
102969
|
} catch {}
|
|
102395
102970
|
}
|
|
102396
102971
|
throw err2;
|
|
@@ -102453,7 +103028,7 @@ function loadFiles(opts) {
|
|
|
102453
103028
|
return loadFromStdin2();
|
|
102454
103029
|
}
|
|
102455
103030
|
const p = resolve59(opts.from);
|
|
102456
|
-
if (!
|
|
103031
|
+
if (!existsSync104(p)) {
|
|
102457
103032
|
fail5(`--from path does not exist: ${opts.from}`);
|
|
102458
103033
|
}
|
|
102459
103034
|
const st = statSync55(p);
|
|
@@ -102461,7 +103036,7 @@ function loadFiles(opts) {
|
|
|
102461
103036
|
return loadFromDir2(p);
|
|
102462
103037
|
}
|
|
102463
103038
|
if (p.endsWith(".md")) {
|
|
102464
|
-
return { "SKILL.md":
|
|
103039
|
+
return { "SKILL.md": readFileSync93(p, "utf-8") };
|
|
102465
103040
|
}
|
|
102466
103041
|
fail5(`--from must be a directory or a .md file. Got: ${opts.from}`);
|
|
102467
103042
|
}
|
|
@@ -102515,7 +103090,7 @@ function resolveCloneSource(source, opts) {
|
|
|
102515
103090
|
const slug = m[2];
|
|
102516
103091
|
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
102517
103092
|
const dir = join105(root, slug);
|
|
102518
|
-
if (!
|
|
103093
|
+
if (!existsSync104(dir)) {
|
|
102519
103094
|
fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
102520
103095
|
}
|
|
102521
103096
|
const st = lstatSync13(dir);
|
|
@@ -102550,7 +103125,7 @@ function readSourceFiles(dir) {
|
|
|
102550
103125
|
fail5(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
102551
103126
|
}
|
|
102552
103127
|
} catch {}
|
|
102553
|
-
files[rel] =
|
|
103128
|
+
files[rel] = readFileSync93(full, "utf-8");
|
|
102554
103129
|
}
|
|
102555
103130
|
}
|
|
102556
103131
|
};
|
|
@@ -102639,10 +103214,10 @@ function removePersonalAction(name, opts) {
|
|
|
102639
103214
|
throw err2;
|
|
102640
103215
|
}
|
|
102641
103216
|
const trashRoot2 = trashDir(agentsRoot, agent);
|
|
102642
|
-
|
|
103217
|
+
mkdirSync61(trashRoot2, { recursive: true, mode: 493 });
|
|
102643
103218
|
const ts = Date.now();
|
|
102644
103219
|
const trashTarget = join105(trashRoot2, `${name}-${ts}`);
|
|
102645
|
-
|
|
103220
|
+
renameSync29(target, trashTarget);
|
|
102646
103221
|
const now = new Date(ts);
|
|
102647
103222
|
utimesSync(trashTarget, now, now);
|
|
102648
103223
|
mirrorToConfigRepo(agent, name, null);
|
|
@@ -102662,7 +103237,7 @@ function listPersonalAction(opts) {
|
|
|
102662
103237
|
sweepTrash(agentsRoot, agent);
|
|
102663
103238
|
const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
|
|
102664
103239
|
const personal = [];
|
|
102665
|
-
if (
|
|
103240
|
+
if (existsSync104(skillsDir)) {
|
|
102666
103241
|
for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
|
|
102667
103242
|
if (!ent.isDirectory())
|
|
102668
103243
|
continue;
|
|
@@ -102720,7 +103295,7 @@ function registerSkillPersonalCommands(program3) {
|
|
|
102720
103295
|
import { createConnection as createConnection4 } from "node:net";
|
|
102721
103296
|
import { homedir as homedir56 } from "node:os";
|
|
102722
103297
|
import { join as join106 } from "node:path";
|
|
102723
|
-
import { readFileSync as
|
|
103298
|
+
import { readFileSync as readFileSync94 } from "node:fs";
|
|
102724
103299
|
var IPC_CONNECT_TIMEOUT_MS = 5000;
|
|
102725
103300
|
function gatewaySocketPath() {
|
|
102726
103301
|
return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join106(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join106(homedir56(), ".claude", "channels", "telegram", "gateway.sock"));
|
|
@@ -102758,7 +103333,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
102758
103333
|
fail6("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
|
|
102759
103334
|
let draft;
|
|
102760
103335
|
try {
|
|
102761
|
-
draft = JSON.parse(
|
|
103336
|
+
draft = JSON.parse(readFileSync94(opts.draft, "utf-8"));
|
|
102762
103337
|
} catch (e) {
|
|
102763
103338
|
fail6(`failed to read/parse --draft: ${e.message}`);
|
|
102764
103339
|
}
|
|
@@ -102795,7 +103370,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
|
|
|
102795
103370
|
init_esm();
|
|
102796
103371
|
init_helpers();
|
|
102797
103372
|
var import_yaml25 = __toESM(require_dist(), 1);
|
|
102798
|
-
import { existsSync as
|
|
103373
|
+
import { existsSync as existsSync105, readdirSync as readdirSync39, readFileSync as readFileSync95, statSync as statSync56 } from "node:fs";
|
|
102799
103374
|
import { homedir as homedir57 } from "node:os";
|
|
102800
103375
|
import { join as join107, resolve as resolve60 } from "node:path";
|
|
102801
103376
|
var PERSONAL_PREFIX2 = "personal-";
|
|
@@ -102812,11 +103387,11 @@ function defaultBundledRoot2() {
|
|
|
102812
103387
|
}
|
|
102813
103388
|
function readSkillFrontmatter(skillDir) {
|
|
102814
103389
|
const mdPath = join107(skillDir, "SKILL.md");
|
|
102815
|
-
if (!
|
|
103390
|
+
if (!existsSync105(mdPath))
|
|
102816
103391
|
return null;
|
|
102817
103392
|
let content;
|
|
102818
103393
|
try {
|
|
102819
|
-
content =
|
|
103394
|
+
content = readFileSync95(mdPath, "utf-8");
|
|
102820
103395
|
} catch {
|
|
102821
103396
|
return null;
|
|
102822
103397
|
}
|
|
@@ -102856,7 +103431,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
102856
103431
|
if (!AGENT_NAME_RE3.test(agent))
|
|
102857
103432
|
return [];
|
|
102858
103433
|
const skillsDir = join107(agentsRoot, agent, ".claude/skills");
|
|
102859
|
-
if (!
|
|
103434
|
+
if (!existsSync105(skillsDir))
|
|
102860
103435
|
return [];
|
|
102861
103436
|
const out = [];
|
|
102862
103437
|
let entries;
|
|
@@ -102894,7 +103469,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
102894
103469
|
return out;
|
|
102895
103470
|
}
|
|
102896
103471
|
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
102897
|
-
if (!
|
|
103472
|
+
if (!existsSync105(sharedRoot))
|
|
102898
103473
|
return [];
|
|
102899
103474
|
const out = [];
|
|
102900
103475
|
let entries;
|
|
@@ -102932,7 +103507,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
|
102932
103507
|
return out;
|
|
102933
103508
|
}
|
|
102934
103509
|
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
102935
|
-
if (!
|
|
103510
|
+
if (!existsSync105(bundledRoot))
|
|
102936
103511
|
return [];
|
|
102937
103512
|
const out = [];
|
|
102938
103513
|
let entries;
|
|
@@ -103090,10 +103665,10 @@ init_source();
|
|
|
103090
103665
|
init_helpers();
|
|
103091
103666
|
init_operator_uid();
|
|
103092
103667
|
import {
|
|
103093
|
-
existsSync as
|
|
103094
|
-
mkdirSync as
|
|
103668
|
+
existsSync as existsSync107,
|
|
103669
|
+
mkdirSync as mkdirSync62,
|
|
103095
103670
|
readdirSync as readdirSync40,
|
|
103096
|
-
writeFileSync as
|
|
103671
|
+
writeFileSync as writeFileSync45,
|
|
103097
103672
|
statSync as statSync57,
|
|
103098
103673
|
lstatSync as lstatSync14,
|
|
103099
103674
|
realpathSync as realpathSync8,
|
|
@@ -103101,13 +103676,13 @@ import {
|
|
|
103101
103676
|
} from "node:fs";
|
|
103102
103677
|
import { homedir as homedir58 } from "node:os";
|
|
103103
103678
|
import { join as join108 } from "node:path";
|
|
103104
|
-
import { spawnSync as
|
|
103679
|
+
import { spawnSync as spawnSync25 } from "node:child_process";
|
|
103105
103680
|
|
|
103106
103681
|
// src/cli/singleton-stale-cleanup.ts
|
|
103107
|
-
import { spawnSync as
|
|
103682
|
+
import { spawnSync as spawnSync24 } from "node:child_process";
|
|
103108
103683
|
function makeDockerRunner() {
|
|
103109
103684
|
return (args) => {
|
|
103110
|
-
const r =
|
|
103685
|
+
const r = spawnSync24("docker", args, { encoding: "utf8" });
|
|
103111
103686
|
return {
|
|
103112
103687
|
ok: r.status === 0,
|
|
103113
103688
|
stdout: r.stdout ?? "",
|
|
@@ -103466,7 +104041,7 @@ function resolveHostdSkillsTarget(hostHome) {
|
|
|
103466
104041
|
console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
|
|
103467
104042
|
return;
|
|
103468
104043
|
}
|
|
103469
|
-
if (!
|
|
104044
|
+
if (!existsSync107(target)) {
|
|
103470
104045
|
console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
|
|
103471
104046
|
return;
|
|
103472
104047
|
}
|
|
@@ -103480,7 +104055,7 @@ function hostdComposePath() {
|
|
|
103480
104055
|
}
|
|
103481
104056
|
function backupExistingCompose() {
|
|
103482
104057
|
const p = hostdComposePath();
|
|
103483
|
-
if (!
|
|
104058
|
+
if (!existsSync107(p))
|
|
103484
104059
|
return null;
|
|
103485
104060
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
103486
104061
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -103488,7 +104063,7 @@ function backupExistingCompose() {
|
|
|
103488
104063
|
return bak;
|
|
103489
104064
|
}
|
|
103490
104065
|
function runDocker(args) {
|
|
103491
|
-
const r =
|
|
104066
|
+
const r = spawnSync25("docker", args, { encoding: "utf8" });
|
|
103492
104067
|
return {
|
|
103493
104068
|
ok: r.status === 0,
|
|
103494
104069
|
stdout: r.stdout ?? "",
|
|
@@ -103513,7 +104088,7 @@ async function doInstall(opts, program3) {
|
|
|
103513
104088
|
}
|
|
103514
104089
|
const dir = hostdDir();
|
|
103515
104090
|
const composePath = hostdComposePath();
|
|
103516
|
-
|
|
104091
|
+
mkdirSync62(dir, { recursive: true });
|
|
103517
104092
|
const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
|
|
103518
104093
|
const guard = checkDowngrade({
|
|
103519
104094
|
container: "switchroom-hostd",
|
|
@@ -103546,7 +104121,7 @@ async function doInstall(opts, program3) {
|
|
|
103546
104121
|
const bak = backupExistingCompose();
|
|
103547
104122
|
if (bak)
|
|
103548
104123
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
103549
|
-
|
|
104124
|
+
writeFileSync45(composePath, yaml, "utf8");
|
|
103550
104125
|
console.log(source_default.green(` \u2713 Wrote ${composePath}`));
|
|
103551
104126
|
const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
|
|
103552
104127
|
console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
|
|
@@ -103578,7 +104153,7 @@ function doStatus() {
|
|
|
103578
104153
|
const composeYml = hostdComposePath();
|
|
103579
104154
|
console.log(source_default.bold("switchroom-hostd"));
|
|
103580
104155
|
console.log("");
|
|
103581
|
-
if (!
|
|
104156
|
+
if (!existsSync107(composeYml)) {
|
|
103582
104157
|
console.log(source_default.yellow(" compose: not installed"));
|
|
103583
104158
|
console.log(source_default.dim(" run `switchroom hostd install` to set up."));
|
|
103584
104159
|
return;
|
|
@@ -103599,14 +104174,14 @@ function doStatus() {
|
|
|
103599
104174
|
} else {
|
|
103600
104175
|
console.log(source_default.green(` container: ${ps.stdout.trim()}`));
|
|
103601
104176
|
}
|
|
103602
|
-
if (
|
|
104177
|
+
if (existsSync107(dir)) {
|
|
103603
104178
|
const entries = [];
|
|
103604
104179
|
try {
|
|
103605
104180
|
for (const name of readdirSync40(dir)) {
|
|
103606
104181
|
if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
|
|
103607
104182
|
continue;
|
|
103608
104183
|
const sockPath = join108(dir, name, "sock");
|
|
103609
|
-
if (
|
|
104184
|
+
if (existsSync107(sockPath)) {
|
|
103610
104185
|
const st = statSync57(sockPath);
|
|
103611
104186
|
if ((st.mode & 61440) === 49152) {
|
|
103612
104187
|
entries.push(`${name} \u2192 ${sockPath}`);
|
|
@@ -103625,7 +104200,7 @@ function doStatus() {
|
|
|
103625
104200
|
}
|
|
103626
104201
|
function doUninstall() {
|
|
103627
104202
|
const composeYml = hostdComposePath();
|
|
103628
|
-
if (!
|
|
104203
|
+
if (!existsSync107(composeYml)) {
|
|
103629
104204
|
console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
|
|
103630
104205
|
return;
|
|
103631
104206
|
}
|
|
@@ -103649,7 +104224,7 @@ function registerHostdCommand(program3) {
|
|
|
103649
104224
|
hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
|
|
103650
104225
|
hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
|
|
103651
104226
|
const logPath = opts.path ?? defaultAuditLogPath2();
|
|
103652
|
-
if (!
|
|
104227
|
+
if (!existsSync107(logPath)) {
|
|
103653
104228
|
console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
|
|
103654
104229
|
The log is created when hostd handles its first privileged-verb request.`));
|
|
103655
104230
|
return;
|
|
@@ -103698,10 +104273,10 @@ init_source();
|
|
|
103698
104273
|
init_helpers();
|
|
103699
104274
|
init_operator_uid();
|
|
103700
104275
|
init_compose();
|
|
103701
|
-
import { chownSync as chownSync10, existsSync as
|
|
104276
|
+
import { chownSync as chownSync10, existsSync as existsSync108, mkdirSync as mkdirSync63, writeFileSync as writeFileSync46, copyFileSync as copyFileSync15 } from "node:fs";
|
|
103702
104277
|
import { homedir as homedir59 } from "node:os";
|
|
103703
104278
|
import { join as join109 } from "node:path";
|
|
103704
|
-
import { spawnSync as
|
|
104279
|
+
import { spawnSync as spawnSync26 } from "node:child_process";
|
|
103705
104280
|
function resolveWebImageTag(explicitTag, release) {
|
|
103706
104281
|
if (explicitTag)
|
|
103707
104282
|
return explicitTag;
|
|
@@ -103809,7 +104384,7 @@ function webdComposePath() {
|
|
|
103809
104384
|
}
|
|
103810
104385
|
function backupExistingCompose2() {
|
|
103811
104386
|
const p = webdComposePath();
|
|
103812
|
-
if (!
|
|
104387
|
+
if (!existsSync108(p))
|
|
103813
104388
|
return null;
|
|
103814
104389
|
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
103815
104390
|
const bak = `${p}.bak-${ts}`;
|
|
@@ -103817,7 +104392,7 @@ function backupExistingCompose2() {
|
|
|
103817
104392
|
return bak;
|
|
103818
104393
|
}
|
|
103819
104394
|
function runDocker2(args) {
|
|
103820
|
-
const r =
|
|
104395
|
+
const r = spawnSync26("docker", args, { encoding: "utf8" });
|
|
103821
104396
|
return {
|
|
103822
104397
|
ok: r.status === 0,
|
|
103823
104398
|
stdout: r.stdout ?? "",
|
|
@@ -103840,7 +104415,7 @@ async function doInstall2(opts, program3) {
|
|
|
103840
104415
|
}
|
|
103841
104416
|
const dir = webdDir();
|
|
103842
104417
|
const composePath = webdComposePath();
|
|
103843
|
-
|
|
104418
|
+
mkdirSync63(dir, { recursive: true });
|
|
103844
104419
|
const cfg = getConfig(program3);
|
|
103845
104420
|
const imageTag = resolveWebImageTag(opts.tag, cfg.release);
|
|
103846
104421
|
const port = cfg.web_service?.port ?? 8080;
|
|
@@ -103875,7 +104450,7 @@ async function doInstall2(opts, program3) {
|
|
|
103875
104450
|
const bak = backupExistingCompose2();
|
|
103876
104451
|
if (bak)
|
|
103877
104452
|
console.log(source_default.dim(` Backed up existing compose to ${bak}`));
|
|
103878
|
-
|
|
104453
|
+
writeFileSync46(composePath, yaml, "utf8");
|
|
103879
104454
|
try {
|
|
103880
104455
|
if (typeof process.geteuid === "function" && process.geteuid() === 0) {
|
|
103881
104456
|
chownSync10(dir, operatorUid, operatorUid);
|
|
@@ -103917,7 +104492,7 @@ function doStatus2() {
|
|
|
103917
104492
|
const composeYml = webdComposePath();
|
|
103918
104493
|
console.log(source_default.bold("switchroom-web"));
|
|
103919
104494
|
console.log("");
|
|
103920
|
-
if (!
|
|
104495
|
+
if (!existsSync108(composeYml)) {
|
|
103921
104496
|
console.log(source_default.yellow(" compose: not installed"));
|
|
103922
104497
|
console.log(source_default.dim(" run `switchroom webd install` to set up."));
|
|
103923
104498
|
return;
|
|
@@ -103941,7 +104516,7 @@ function doStatus2() {
|
|
|
103941
104516
|
}
|
|
103942
104517
|
function doUninstall2() {
|
|
103943
104518
|
const composeYml = webdComposePath();
|
|
103944
|
-
if (!
|
|
104519
|
+
if (!existsSync108(composeYml)) {
|
|
103945
104520
|
console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
|
|
103946
104521
|
return;
|
|
103947
104522
|
}
|
|
@@ -104216,13 +104791,13 @@ init_helpers();
|
|
|
104216
104791
|
init_scan();
|
|
104217
104792
|
|
|
104218
104793
|
// src/fleet-health/gh-sync.ts
|
|
104219
|
-
import { spawnSync as
|
|
104794
|
+
import { spawnSync as spawnSync27 } from "node:child_process";
|
|
104220
104795
|
var LABEL = "fleet-health";
|
|
104221
104796
|
function defaultGhDeps(log) {
|
|
104222
104797
|
return {
|
|
104223
104798
|
log,
|
|
104224
104799
|
run: (args) => {
|
|
104225
|
-
const r =
|
|
104800
|
+
const r = spawnSync27("gh", args, { encoding: "utf-8" });
|
|
104226
104801
|
return {
|
|
104227
104802
|
ok: r.status === 0,
|
|
104228
104803
|
stdout: (r.stdout ?? "").trim(),
|
|
@@ -104427,7 +105002,7 @@ function printDeepDiveBrief(targets) {
|
|
|
104427
105002
|
|
|
104428
105003
|
// src/cli/hindsight-watch.ts
|
|
104429
105004
|
init_source();
|
|
104430
|
-
import { existsSync as
|
|
105005
|
+
import { existsSync as existsSync113, readdirSync as readdirSync45 } from "node:fs";
|
|
104431
105006
|
import { homedir as homedir63 } from "node:os";
|
|
104432
105007
|
import { join as join112, resolve as resolve63 } from "node:path";
|
|
104433
105008
|
|
|
@@ -104497,8 +105072,8 @@ function postOperatorNoticeViaGateways(candidates, text, log, connectTimeoutMs =
|
|
|
104497
105072
|
init_install_cron();
|
|
104498
105073
|
|
|
104499
105074
|
// src/hindsight-watch/probe.ts
|
|
104500
|
-
import { spawnSync as
|
|
104501
|
-
import { readFileSync as
|
|
105075
|
+
import { spawnSync as spawnSync28 } from "node:child_process";
|
|
105076
|
+
import { readFileSync as readFileSync100, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
|
|
104502
105077
|
import { homedir as homedir62 } from "node:os";
|
|
104503
105078
|
import { resolve as resolve62 } from "node:path";
|
|
104504
105079
|
|
|
@@ -104626,7 +105201,7 @@ function readLlmSignals(series) {
|
|
|
104626
105201
|
}
|
|
104627
105202
|
|
|
104628
105203
|
// src/hindsight-watch/recall-log.ts
|
|
104629
|
-
import { closeSync as closeSync22, existsSync as
|
|
105204
|
+
import { closeSync as closeSync22, existsSync as existsSync112, openSync as openSync22, readdirSync as readdirSync43, readSync as readSync6, statSync as statSync58 } from "node:fs";
|
|
104630
105205
|
import { join as join111 } from "node:path";
|
|
104631
105206
|
var RECALL_WINDOW_ROWS = 200;
|
|
104632
105207
|
var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
|
|
@@ -104635,7 +105210,7 @@ function recallLogPath2(agentDir) {
|
|
|
104635
105210
|
return join111(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
|
|
104636
105211
|
}
|
|
104637
105212
|
function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
|
|
104638
|
-
if (!
|
|
105213
|
+
if (!existsSync112(path10))
|
|
104639
105214
|
return [];
|
|
104640
105215
|
let text;
|
|
104641
105216
|
let truncatedHead = false;
|
|
@@ -104847,7 +105422,7 @@ async function probeMetrics(url = DEFAULT_METRICS_URL, fetchImpl = fetch) {
|
|
|
104847
105422
|
}
|
|
104848
105423
|
}
|
|
104849
105424
|
var defaultRunner4 = (cmd, args) => {
|
|
104850
|
-
const r =
|
|
105425
|
+
const r = spawnSync28(cmd, args, { stdio: "pipe", timeout: 1e4, encoding: "utf8" });
|
|
104851
105426
|
if (r.error)
|
|
104852
105427
|
return { status: null, stdout: "", stderr: r.error.message };
|
|
104853
105428
|
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
@@ -104875,7 +105450,7 @@ function readDropCount(hindsightDir) {
|
|
|
104875
105450
|
try {
|
|
104876
105451
|
if (statSync59(path10).size > DROPS_LEDGER_MAX_BYTES)
|
|
104877
105452
|
return 0;
|
|
104878
|
-
parsed = JSON.parse(
|
|
105453
|
+
parsed = JSON.parse(readFileSync100(path10, "utf8"));
|
|
104879
105454
|
} catch {
|
|
104880
105455
|
return 0;
|
|
104881
105456
|
}
|
|
@@ -105923,7 +106498,7 @@ function collectRelayCandidates(agentsDir) {
|
|
|
105923
106498
|
const candidates = [];
|
|
105924
106499
|
for (const name of readdirSync45(agentsDir).sort()) {
|
|
105925
106500
|
const sock = resolve63(agentsDir, name, "telegram", "gateway.sock");
|
|
105926
|
-
if (
|
|
106501
|
+
if (existsSync113(sock))
|
|
105927
106502
|
candidates.push({ agent: name, sock });
|
|
105928
106503
|
}
|
|
105929
106504
|
return orderRelayCandidates(candidates);
|
|
@@ -105945,13 +106520,13 @@ async function notifyOperator(agentsDir, text, log) {
|
|
|
105945
106520
|
|
|
105946
106521
|
// src/cli/openrouter-watch.ts
|
|
105947
106522
|
init_source();
|
|
105948
|
-
import { existsSync as
|
|
106523
|
+
import { existsSync as existsSync115, readdirSync as readdirSync46 } from "node:fs";
|
|
105949
106524
|
import { homedir as homedir65 } from "node:os";
|
|
105950
106525
|
import { join as join113, resolve as resolve65 } from "node:path";
|
|
105951
106526
|
|
|
105952
106527
|
// src/openrouter/install-cron.ts
|
|
105953
|
-
import { existsSync as
|
|
105954
|
-
import { dirname as
|
|
106528
|
+
import { existsSync as existsSync114, mkdirSync as mkdirSync65, readFileSync as readFileSync101, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
|
|
106529
|
+
import { dirname as dirname44 } from "node:path";
|
|
105955
106530
|
var CRON_PATH2 = "/etc/cron.d/openrouter-watch";
|
|
105956
106531
|
var CRON_SCHEDULE2 = "17 * * * *";
|
|
105957
106532
|
var CRON_LOG_PATH2 = "/var/log/openrouter-watch.log";
|
|
@@ -105968,30 +106543,30 @@ function renderCron2(opts) {
|
|
|
105968
106543
|
function installCron2(opts) {
|
|
105969
106544
|
const path10 = opts.path ?? CRON_PATH2;
|
|
105970
106545
|
const content = renderCron2(opts);
|
|
105971
|
-
if (
|
|
106546
|
+
if (existsSync114(path10)) {
|
|
105972
106547
|
try {
|
|
105973
|
-
if (
|
|
106548
|
+
if (readFileSync101(path10, "utf8") === content) {
|
|
105974
106549
|
return { status: "unchanged", path: path10, content };
|
|
105975
106550
|
}
|
|
105976
106551
|
} catch {}
|
|
105977
106552
|
}
|
|
105978
|
-
|
|
106553
|
+
mkdirSync65(dirname44(path10), { recursive: true });
|
|
105979
106554
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
105980
|
-
|
|
105981
|
-
|
|
106555
|
+
writeFileSync48(tmp, content, { mode: 420 });
|
|
106556
|
+
renameSync30(tmp, path10);
|
|
105982
106557
|
return { status: "installed", path: path10, content };
|
|
105983
106558
|
}
|
|
105984
106559
|
// src/openrouter/state.ts
|
|
105985
|
-
import { mkdirSync as
|
|
106560
|
+
import { mkdirSync as mkdirSync66, readFileSync as readFileSync102, renameSync as renameSync31, writeFileSync as writeFileSync49 } from "node:fs";
|
|
105986
106561
|
import { homedir as homedir64 } from "node:os";
|
|
105987
|
-
import { dirname as
|
|
106562
|
+
import { dirname as dirname45, resolve as resolve64 } from "node:path";
|
|
105988
106563
|
function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir64()) {
|
|
105989
106564
|
return resolve64(home2, ".switchroom", "openrouter-watch", "state.json");
|
|
105990
106565
|
}
|
|
105991
106566
|
function loadState2(path10) {
|
|
105992
106567
|
let parsed;
|
|
105993
106568
|
try {
|
|
105994
|
-
parsed = JSON.parse(
|
|
106569
|
+
parsed = JSON.parse(readFileSync102(path10, "utf8"));
|
|
105995
106570
|
} catch {
|
|
105996
106571
|
return {};
|
|
105997
106572
|
}
|
|
@@ -106011,12 +106586,12 @@ function loadState2(path10) {
|
|
|
106011
106586
|
return out;
|
|
106012
106587
|
}
|
|
106013
106588
|
function saveState2(path10, state) {
|
|
106014
|
-
|
|
106589
|
+
mkdirSync66(dirname45(path10), { recursive: true, mode: 448 });
|
|
106015
106590
|
const tmp = `${path10}.${process.pid}.tmp`;
|
|
106016
106591
|
const payload = { v: 1, ...state };
|
|
106017
|
-
|
|
106592
|
+
writeFileSync49(tmp, JSON.stringify(payload, null, 2) + `
|
|
106018
106593
|
`, { mode: 384 });
|
|
106019
|
-
|
|
106594
|
+
renameSync31(tmp, path10);
|
|
106020
106595
|
}
|
|
106021
106596
|
|
|
106022
106597
|
// src/openrouter/watch.ts
|
|
@@ -106201,7 +106776,7 @@ async function notifyOperator2(agentsDir, text, log) {
|
|
|
106201
106776
|
try {
|
|
106202
106777
|
for (const name of readdirSync46(agentsDir).sort()) {
|
|
106203
106778
|
const sock = resolve65(agentsDir, name, "telegram", "gateway.sock");
|
|
106204
|
-
if (
|
|
106779
|
+
if (existsSync115(sock))
|
|
106205
106780
|
candidates.push({ agent: name, sock });
|
|
106206
106781
|
}
|
|
106207
106782
|
} catch (e) {
|