switchroom 0.19.43 → 0.19.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-scheduler/index.js +4 -1
- package/dist/auth-broker/index.js +254 -119
- package/dist/cli/notion-write-pretool.mjs +4 -1
- package/dist/cli/switchroom.js +3281 -2416
- package/dist/host-control/main.js +270 -130
- package/dist/vault/approvals/kernel-server.js +201 -66
- package/dist/vault/broker/server.js +263 -128
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +11 -4
- package/telegram-plugin/dist/gateway/gateway.js +463 -328
- package/vendor/hindsight-memory/scripts/lib/config.py +15 -0
- package/vendor/hindsight-memory/scripts/tests/test_retain_provenance_tag.py +135 -0
- package/vendor/hindsight-memory/settings.json +1 -1
|
@@ -10994,7 +10994,7 @@ var init_observation_scopes = __esm(() => {
|
|
|
10994
10994
|
});
|
|
10995
10995
|
|
|
10996
10996
|
// src/config/schema.ts
|
|
10997
|
-
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, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
10997
|
+
var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, 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, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
|
|
10998
10998
|
var init_schema = __esm(() => {
|
|
10999
10999
|
init_zod();
|
|
11000
11000
|
init_observation_scopes();
|
|
@@ -11111,6 +11111,7 @@ var init_schema = __esm(() => {
|
|
|
11111
11111
|
}).optional();
|
|
11112
11112
|
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 — 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).");
|
|
11113
11113
|
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 — keeping the stable " + "semantic ones on the source fact — 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).");
|
|
11114
|
+
AntiConfabulationDirectiveSchema = exports_external.union([exports_external.boolean(), exports_external.string().min(1)]).optional().describe("The seeded anti-confabulation directive: reflect has no relevance " + "floor (`min_scores` is a /recall-only parameter), so a bank holding " + "nothing relevant still yields a confident answer, and a question that " + "presupposes a decision reliably produces one. Directives ARE applied " + "during reflect, so this is the lever. Unset or true (the default) " + "seeds and maintains switchroom's own text; false disables seeding and " + "leaves any existing directive untouched; a string is operator-authored " + "text that wins outright. Switchroom only ever upgrades directive text " + "byte-equal to a default it shipped, so editing the directive in the " + "bank is also a permanent override. " + "Cascade: override (per-agent wins over default).");
|
|
11114
11115
|
AgentMemorySchema = exports_external.object({
|
|
11115
11116
|
collection: exports_external.string().describe("Hindsight collection name for this agent"),
|
|
11116
11117
|
auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
|
|
@@ -11146,6 +11147,7 @@ var init_schema = __esm(() => {
|
|
|
11146
11147
|
empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
|
|
11147
11148
|
}).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — 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`)."),
|
|
11148
11149
|
directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") 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 — 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)."),
|
|
11150
|
+
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
11149
11151
|
observation_scopes: ObservationScopesSchema,
|
|
11150
11152
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11151
11153
|
recall: exports_external.object({
|
|
@@ -11416,7 +11418,7 @@ var init_schema = __esm(() => {
|
|
|
11416
11418
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
|
|
11417
11419
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
|
|
11418
11420
|
}).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
|
|
11419
|
-
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11421
|
+
env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT — the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
11420
11422
|
});
|
|
11421
11423
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
11422
11424
|
microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -11550,6 +11552,7 @@ var init_schema = __esm(() => {
|
|
|
11550
11552
|
isolation: exports_external.enum(["default", "strict"]).optional(),
|
|
11551
11553
|
profile: exports_external.string().optional(),
|
|
11552
11554
|
directive_capture_nudge: exports_external.boolean().optional(),
|
|
11555
|
+
anti_confabulation_directive: AntiConfabulationDirectiveSchema,
|
|
11553
11556
|
observation_scopes: ObservationScopesSchema,
|
|
11554
11557
|
observation_scope_strategy: ObservationScopeStrategySchema,
|
|
11555
11558
|
recall: exports_external.object({
|
|
@@ -18379,13 +18382,13 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18379
18382
|
|
|
18380
18383
|
// src/vault/flock.ts
|
|
18381
18384
|
import {
|
|
18382
|
-
existsSync as
|
|
18385
|
+
existsSync as existsSync7,
|
|
18383
18386
|
openSync as openSync2,
|
|
18384
18387
|
closeSync as closeSync2,
|
|
18385
18388
|
writeSync as writeSync2,
|
|
18386
18389
|
fsyncSync as fsyncSync2,
|
|
18387
18390
|
unlinkSync as unlinkSync2,
|
|
18388
|
-
readFileSync as
|
|
18391
|
+
readFileSync as readFileSync6,
|
|
18389
18392
|
readdirSync as readdirSync3,
|
|
18390
18393
|
rmdirSync,
|
|
18391
18394
|
statSync as statSync3,
|
|
@@ -18418,8 +18421,8 @@ function pidStartTimeMs(pid) {
|
|
|
18418
18421
|
if (process.platform !== "linux")
|
|
18419
18422
|
return null;
|
|
18420
18423
|
try {
|
|
18421
|
-
const statLine =
|
|
18422
|
-
const procStat =
|
|
18424
|
+
const statLine = readFileSync6(`/proc/${pid}/stat`, "utf8");
|
|
18425
|
+
const procStat = readFileSync6("/proc/stat", "utf8");
|
|
18423
18426
|
return parseProcStartTimeMs(statLine, procStat, Date.now());
|
|
18424
18427
|
} catch {
|
|
18425
18428
|
return null;
|
|
@@ -18815,13 +18818,13 @@ async function rpcRaw(req, opts) {
|
|
|
18815
18818
|
async function rpc(req, opts) {
|
|
18816
18819
|
const socketPath = resolveBrokerSocketPath(opts);
|
|
18817
18820
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
18818
|
-
return new Promise((
|
|
18821
|
+
return new Promise((resolve6) => {
|
|
18819
18822
|
let settled = false;
|
|
18820
18823
|
const settle = (val) => {
|
|
18821
18824
|
if (settled)
|
|
18822
18825
|
return;
|
|
18823
18826
|
settled = true;
|
|
18824
|
-
|
|
18827
|
+
resolve6(val);
|
|
18825
18828
|
};
|
|
18826
18829
|
const client = new net.Socket;
|
|
18827
18830
|
const timer = setTimeout(() => {
|
|
@@ -20931,8 +20934,8 @@ var require_commander = __commonJS((exports2) => {
|
|
|
20931
20934
|
// src/host-control/main.ts
|
|
20932
20935
|
init_loader();
|
|
20933
20936
|
import { homedir as homedir8 } from "node:os";
|
|
20934
|
-
import { existsSync as
|
|
20935
|
-
import { join as
|
|
20937
|
+
import { existsSync as existsSync17, readdirSync as readdirSync7 } from "node:fs";
|
|
20938
|
+
import { join as join13, resolve as resolve10 } from "node:path";
|
|
20936
20939
|
|
|
20937
20940
|
// src/host-control/config-degraded.ts
|
|
20938
20941
|
init_loader();
|
|
@@ -21323,9 +21326,66 @@ async function waitForConfigRecovery(opts) {
|
|
|
21323
21326
|
init_schema();
|
|
21324
21327
|
|
|
21325
21328
|
// src/agents/scaffold.ts
|
|
21326
|
-
import { dirname, isAbsolute, join as join5, relative, resolve as resolve5 } from "node:path";
|
|
21327
21329
|
init_atomic();
|
|
21328
21330
|
|
|
21331
|
+
// src/util/shipped-assets.ts
|
|
21332
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, realpathSync } from "node:fs";
|
|
21333
|
+
import { dirname, resolve as resolve4 } from "node:path";
|
|
21334
|
+
var FHS_SHARE_ROOTS = [
|
|
21335
|
+
"/usr/local/share/switchroom",
|
|
21336
|
+
"/usr/share/switchroom"
|
|
21337
|
+
];
|
|
21338
|
+
var PROFILES_ASSET = {
|
|
21339
|
+
asset: "profiles",
|
|
21340
|
+
envVar: "SWITCHROOM_PROFILES_ROOT"
|
|
21341
|
+
};
|
|
21342
|
+
function orderedCandidates(spec, probe) {
|
|
21343
|
+
const out = [];
|
|
21344
|
+
const push = (path, source) => {
|
|
21345
|
+
if (!out.some((c) => c.path === path))
|
|
21346
|
+
out.push({ path, source });
|
|
21347
|
+
};
|
|
21348
|
+
if (probe.bundleDir) {
|
|
21349
|
+
push(resolve4(probe.bundleDir, "../..", spec.asset), "npm");
|
|
21350
|
+
push(resolve4(probe.bundleDir, spec.asset), "image");
|
|
21351
|
+
}
|
|
21352
|
+
if (probe.execPath) {
|
|
21353
|
+
push(resolve4(dirname(probe.execPath), "../share/switchroom", spec.asset), "sea-sibling");
|
|
21354
|
+
}
|
|
21355
|
+
for (const root of FHS_SHARE_ROOTS) {
|
|
21356
|
+
push(resolve4(root, spec.asset), "fhs");
|
|
21357
|
+
}
|
|
21358
|
+
return out;
|
|
21359
|
+
}
|
|
21360
|
+
function resolveShippedAsset(spec, probe) {
|
|
21361
|
+
const env = probe.env ?? process.env;
|
|
21362
|
+
const override = env[spec.envVar]?.trim();
|
|
21363
|
+
if (override) {
|
|
21364
|
+
const path = canonicalise(resolve4(override), probe);
|
|
21365
|
+
return { path, candidates: [path], source: "env" };
|
|
21366
|
+
}
|
|
21367
|
+
const exists = probe.exists ?? existsSync5;
|
|
21368
|
+
const candidates = orderedCandidates(spec, probe);
|
|
21369
|
+
for (const c of candidates) {
|
|
21370
|
+
if (exists(c.path)) {
|
|
21371
|
+
return {
|
|
21372
|
+
path: canonicalise(c.path, probe),
|
|
21373
|
+
candidates: candidates.map((x) => x.path),
|
|
21374
|
+
source: c.source
|
|
21375
|
+
};
|
|
21376
|
+
}
|
|
21377
|
+
}
|
|
21378
|
+
return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
|
|
21379
|
+
}
|
|
21380
|
+
function canonicalise(path, probe) {
|
|
21381
|
+
const realpath = probe.realpath ?? realpathSync;
|
|
21382
|
+
try {
|
|
21383
|
+
return realpath(path);
|
|
21384
|
+
} catch {
|
|
21385
|
+
return path;
|
|
21386
|
+
}
|
|
21387
|
+
}
|
|
21388
|
+
|
|
21329
21389
|
// src/agents/agent-uid.ts
|
|
21330
21390
|
init_peercred();
|
|
21331
21391
|
import { createHash } from "node:crypto";
|
|
@@ -21342,7 +21402,7 @@ function allocateAgentUid(name) {
|
|
|
21342
21402
|
}
|
|
21343
21403
|
|
|
21344
21404
|
// src/build-info.ts
|
|
21345
|
-
var VERSION = "0.19.
|
|
21405
|
+
var VERSION = "0.19.44";
|
|
21346
21406
|
|
|
21347
21407
|
// src/setup/hindsight-recall-tunables.ts
|
|
21348
21408
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -21387,37 +21447,29 @@ var AUDIT_ROOT = join2(homedir2(), ".switchroom", "audit");
|
|
|
21387
21447
|
|
|
21388
21448
|
// src/agents/profiles.ts
|
|
21389
21449
|
var import_handlebars = __toESM(require_lib(), 1);
|
|
21390
|
-
import { readFileSync as
|
|
21391
|
-
import { resolve as
|
|
21392
|
-
function
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
}
|
|
21397
|
-
const candidates = [
|
|
21398
|
-
resolve4(import.meta.dirname, "../../profiles"),
|
|
21399
|
-
resolve4(import.meta.dirname, "profiles")
|
|
21400
|
-
];
|
|
21401
|
-
for (const candidate of candidates) {
|
|
21402
|
-
if (existsSync5(candidate)) {
|
|
21403
|
-
return candidate;
|
|
21404
|
-
}
|
|
21405
|
-
}
|
|
21406
|
-
return candidates[0];
|
|
21450
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2, copyFileSync, mkdirSync as mkdirSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
21451
|
+
import { resolve as resolve5, join as join3, sep as pathSep } from "node:path";
|
|
21452
|
+
function resolveProfilesRootDetailed() {
|
|
21453
|
+
return resolveShippedAsset(PROFILES_ASSET, {
|
|
21454
|
+
bundleDir: import.meta.dirname,
|
|
21455
|
+
execPath: process.execPath
|
|
21456
|
+
});
|
|
21407
21457
|
}
|
|
21408
|
-
var
|
|
21458
|
+
var PROFILES_RESOLUTION = resolveProfilesRootDetailed();
|
|
21459
|
+
var PROFILES_ROOT = PROFILES_RESOLUTION.path ?? PROFILES_RESOLUTION.candidates[0];
|
|
21460
|
+
var PROFILES_ROOT_SEARCH = PROFILES_RESOLUTION.candidates;
|
|
21409
21461
|
import_handlebars.default.registerHelper("json", (value) => {
|
|
21410
21462
|
return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
|
|
21411
21463
|
});
|
|
21412
21464
|
import_handlebars.default.registerHelper("isNumber", (value) => {
|
|
21413
21465
|
return typeof value === "number" && Number.isFinite(value);
|
|
21414
21466
|
});
|
|
21415
|
-
var SHARED_FRAGMENTS_DIR =
|
|
21467
|
+
var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
|
|
21416
21468
|
var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
|
|
21417
21469
|
for (const name of SHARED_FRAGMENTS) {
|
|
21418
21470
|
const fragPath = join3(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
|
|
21419
|
-
if (
|
|
21420
|
-
import_handlebars.default.registerPartial(name,
|
|
21471
|
+
if (existsSync6(fragPath)) {
|
|
21472
|
+
import_handlebars.default.registerPartial(name, readFileSync5(fragPath, "utf-8"));
|
|
21421
21473
|
}
|
|
21422
21474
|
}
|
|
21423
21475
|
|
|
@@ -21620,7 +21672,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
|
21620
21672
|
"HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
|
|
21621
21673
|
"HINDSIGHT_MCP_RECALL_BUDGET_MODE",
|
|
21622
21674
|
"HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
|
|
21623
|
-
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
|
|
21675
|
+
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
21676
|
+
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
|
|
21624
21677
|
]);
|
|
21625
21678
|
var HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
21626
21679
|
"consolidation",
|
|
@@ -21715,6 +21768,15 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
21715
21768
|
` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
|
|
21716
21769
|
` + ` system's self-reports are not memories.
|
|
21717
21770
|
` + `- Restatements of the user's current request or the task in progress.
|
|
21771
|
+
` + `- The assistant's own answers, summaries, recaps, or reflect output. Before
|
|
21772
|
+
` + ` extracting, ask: does anything in this transcript support this claim OTHER
|
|
21773
|
+
` + ` than the assistant having asserted it? If not, drop it — a model's own
|
|
21774
|
+
` + ` synthesis re-extracted as a fact is how a guess becomes permanent.
|
|
21775
|
+
` + ` Concretely, never produce a fact whose only support is the assistant
|
|
21776
|
+
` + ` stating a date, a version, an attribution, a total, or a decision that the
|
|
21777
|
+
` + ` user never confirmed and no tool output shows. An unverified claim recalls
|
|
21778
|
+
` + ` later as though it had been established, which is worse than not
|
|
21779
|
+
` + ` remembering it at all.
|
|
21718
21780
|
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
21719
21781
|
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
21720
21782
|
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
@@ -21870,6 +21932,64 @@ var SUPERSEDED_RETAIN_MISSIONS = [
|
|
|
21870
21932
|
` + `
|
|
21871
21933
|
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
21872
21934
|
` + `nothing durable remains, return an empty facts list.
|
|
21935
|
+
`,
|
|
21936
|
+
`Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
|
|
21937
|
+
` + `
|
|
21938
|
+
` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
|
|
21939
|
+
` + `- PREFERENCE — what the user likes, wants, or always does; a standing rule or correction.
|
|
21940
|
+
` + `- DECISION — a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision — drop it unless it establishes a standing rule or permanently changes a system.
|
|
21941
|
+
` + `- FINDING — a root cause, a measurement, or verified behaviour of a system. Include the number.
|
|
21942
|
+
` + `- OUTCOME — a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
|
|
21943
|
+
` + `- RELATIONSHIP — who a person is, what a project or tool is, and how they connect.
|
|
21944
|
+
` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
|
|
21945
|
+
` + `
|
|
21946
|
+
` + `A preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.
|
|
21947
|
+
` + `
|
|
21948
|
+
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
21949
|
+
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
21950
|
+
` + `the location where some output was written? If yes, drop it — it is transcript
|
|
21951
|
+
` + `exhaust, not memory.
|
|
21952
|
+
` + `
|
|
21953
|
+
` + `NEVER extract:
|
|
21954
|
+
` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
|
|
21955
|
+
` + ` text resembles any of these: "File created successfully at /path/to/file",
|
|
21956
|
+
` + ` "A background command with ID bctz4yskm is running, and its output will be
|
|
21957
|
+
` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
|
|
21958
|
+
` + ` and is running in the background", "User executed a Bash command to sleep for
|
|
21959
|
+
` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
|
|
21960
|
+
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
21961
|
+
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
21962
|
+
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
21963
|
+
` + `- The act of delegating, dispatching, spawning, launching, steering or merging
|
|
21964
|
+
` + ` work — including when it succeeded. "X was dispatched and completed" is the
|
|
21965
|
+
` + ` session describing itself. Record only what the work LEARNED or CHANGED.
|
|
21966
|
+
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
21967
|
+
` + ` running) — retain the outcome only once the task completes or a decision is made.
|
|
21968
|
+
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
21969
|
+
` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
|
|
21970
|
+
` + ` reset assistant state").
|
|
21971
|
+
` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
|
|
21972
|
+
` + ` system's self-reports are not memories.
|
|
21973
|
+
` + `- Restatements of the user's current request or the task in progress.
|
|
21974
|
+
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
21975
|
+
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
21976
|
+
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
21977
|
+
` + ` fact whose text resembles any of these: "Switchroom fleet is running image
|
|
21978
|
+
` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
|
|
21979
|
+
` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
|
|
21980
|
+
` + ` currently green". If the claim is worth keeping, put the date INSIDE the
|
|
21981
|
+
` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
|
|
21982
|
+
` + ` cannot date it, drop it. An undated one is recalled forever as though it
|
|
21983
|
+
` + ` were still true, which is worse than not remembering it at all.
|
|
21984
|
+
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
21985
|
+
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
21986
|
+
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
21987
|
+
` + `
|
|
21988
|
+
` + `Write each fact so it stands alone: name the thing, the number, and the date. A
|
|
21989
|
+
` + `sentence that only makes sense while reading this transcript is not durable.
|
|
21990
|
+
` + `
|
|
21991
|
+
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
21992
|
+
` + `nothing durable remains, return an empty facts list.
|
|
21873
21993
|
`
|
|
21874
21994
|
];
|
|
21875
21995
|
var DEFAULT_OBSERVATIONS_MISSION = `Synthesise durable, standing knowledge about the people, projects, and systems this agent works with: preferences and standing rules, roles and relationships, skills and recurring patterns, technical and operational decisions with their rationale, and the state of long-running work once it lands.
|
|
@@ -21947,6 +22067,22 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
21947
22067
|
var warnedMissingPool = new Set;
|
|
21948
22068
|
var warnedMissingDefault = new Set;
|
|
21949
22069
|
|
|
22070
|
+
// src/memory/hindsight-retain-provenance.ts
|
|
22071
|
+
var RETAIN_PROVENANCE_TAG = "source:transcript";
|
|
22072
|
+
var RETAIN_TAGS_DEFAULT = Object.freeze([
|
|
22073
|
+
"{session_id}",
|
|
22074
|
+
RETAIN_PROVENANCE_TAG
|
|
22075
|
+
]);
|
|
22076
|
+
|
|
22077
|
+
// src/memory/hindsight-seed-directives.ts
|
|
22078
|
+
var ANTI_CONFABULATION_DIRECTIVE = `When the retrieved memories do not support an answer, say so instead of composing one.
|
|
22079
|
+
` + `
|
|
22080
|
+
` + `- If nothing retrieved scores meaningfully above zero, or what came back is off-topic, the honest answer is that the bank does not know. Say that plainly, and say what would settle it.
|
|
22081
|
+
` + `- Never assert a date, a version, a number, an attribution, a decision, or an outcome that no retrieved memory states. A question that presupposes something is not evidence for it: asked when a decision was made, when the bank records no such decision, answer that none is recorded — do not supply one.
|
|
22082
|
+
` + `- Keep what the bank RECORDED separate from what you INFER. State inferences as inferences, and say which memory a recorded fact came from.
|
|
22083
|
+
` + `- Treat a memory that was extracted from a conversation transcript as an unverified claim, not an established fact — it may be an assistant's own earlier guess. If it is the only support for an answer, say that it is.
|
|
22084
|
+
` + "- Partial knowledge with its gaps named is more useful than a fluent answer that fills them in.";
|
|
22085
|
+
|
|
21950
22086
|
// src/telegram/state.ts
|
|
21951
22087
|
init_paths();
|
|
21952
22088
|
|
|
@@ -21984,7 +22120,6 @@ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
|
21984
22120
|
init_paths();
|
|
21985
22121
|
|
|
21986
22122
|
// src/agents/scaffold.ts
|
|
21987
|
-
var REPO_ROOT = resolve5(import.meta.dirname, "../..");
|
|
21988
22123
|
var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
|
|
21989
22124
|
var pendingBankOps = new Set;
|
|
21990
22125
|
var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
|
|
@@ -22016,8 +22151,8 @@ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
|
|
|
22016
22151
|
import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
|
|
22017
22152
|
import {
|
|
22018
22153
|
readdirSync as readdirSync6,
|
|
22019
|
-
existsSync as
|
|
22020
|
-
readFileSync as
|
|
22154
|
+
existsSync as existsSync16,
|
|
22155
|
+
readFileSync as readFileSync12,
|
|
22021
22156
|
writeFileSync as writeFileSync6,
|
|
22022
22157
|
renameSync as renameSync5,
|
|
22023
22158
|
mkdirSync as mkdirSync5,
|
|
@@ -22029,7 +22164,7 @@ import {
|
|
|
22029
22164
|
copyFileSync as copyFileSync3,
|
|
22030
22165
|
unlinkSync as unlinkSync5
|
|
22031
22166
|
} from "node:fs";
|
|
22032
|
-
import { join as
|
|
22167
|
+
import { join as join12, dirname as dirname9, resolve as resolve9 } from "node:path";
|
|
22033
22168
|
import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
|
|
22034
22169
|
|
|
22035
22170
|
// src/host-control/protocol.ts
|
|
@@ -26721,9 +26856,9 @@ class PostHog extends PostHogBackendClient {
|
|
|
26721
26856
|
// src/analytics/posthog.ts
|
|
26722
26857
|
init_paths();
|
|
26723
26858
|
import {
|
|
26724
|
-
existsSync as
|
|
26859
|
+
existsSync as existsSync9,
|
|
26725
26860
|
mkdirSync as mkdirSync3,
|
|
26726
|
-
readFileSync as
|
|
26861
|
+
readFileSync as readFileSync8,
|
|
26727
26862
|
writeFileSync as writeFileSync3
|
|
26728
26863
|
} from "node:fs";
|
|
26729
26864
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -26742,8 +26877,8 @@ function getDistinctId() {
|
|
|
26742
26877
|
return cachedDistinctId;
|
|
26743
26878
|
const path = resolveStatePath("analytics-id");
|
|
26744
26879
|
try {
|
|
26745
|
-
if (
|
|
26746
|
-
const existing =
|
|
26880
|
+
if (existsSync9(path)) {
|
|
26881
|
+
const existing = readFileSync8(path, "utf-8").trim();
|
|
26747
26882
|
if (existing) {
|
|
26748
26883
|
cachedDistinctId = existing;
|
|
26749
26884
|
return existing;
|
|
@@ -27952,8 +28087,8 @@ function releaseBlockIsCommented(doc) {
|
|
|
27952
28087
|
|
|
27953
28088
|
// src/cli/rollout-pin-journal.ts
|
|
27954
28089
|
import {
|
|
27955
|
-
existsSync as
|
|
27956
|
-
readFileSync as
|
|
28090
|
+
existsSync as existsSync12,
|
|
28091
|
+
readFileSync as readFileSync9,
|
|
27957
28092
|
writeFileSync as writeFileSync4,
|
|
27958
28093
|
renameSync as renameSync4,
|
|
27959
28094
|
unlinkSync as unlinkSync4,
|
|
@@ -27962,7 +28097,7 @@ import {
|
|
|
27962
28097
|
} from "node:fs";
|
|
27963
28098
|
import { homedir as homedir5 } from "node:os";
|
|
27964
28099
|
import { createHash as createHash4 } from "node:crypto";
|
|
27965
|
-
import { join as
|
|
28100
|
+
import { join as join7, basename as basename3, resolve as resolve7, dirname as dirname6 } from "node:path";
|
|
27966
28101
|
init_flock();
|
|
27967
28102
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
27968
28103
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -27975,15 +28110,15 @@ function pinJournalDir(configPath) {
|
|
|
27975
28110
|
if (basename3(dir) === STATE_DIR_NAME)
|
|
27976
28111
|
return dir;
|
|
27977
28112
|
}
|
|
27978
|
-
return
|
|
28113
|
+
return join7(homedir5(), STATE_DIR_NAME);
|
|
27979
28114
|
}
|
|
27980
28115
|
function pinJournalPath(configPath) {
|
|
27981
28116
|
const abs = resolve7(configPath);
|
|
27982
28117
|
const key = createHash4("sha256").update(abs).digest("hex").slice(0, 12);
|
|
27983
|
-
return
|
|
28118
|
+
return join7(pinJournalDir(abs), `.rollout-pin-journal.${basename3(abs)}.${key}.json`);
|
|
27984
28119
|
}
|
|
27985
28120
|
function hasPinJournal(configPath) {
|
|
27986
|
-
return
|
|
28121
|
+
return existsSync12(pinJournalPath(configPath));
|
|
27987
28122
|
}
|
|
27988
28123
|
function isPidAlive(pid) {
|
|
27989
28124
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -28017,9 +28152,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
28017
28152
|
const p = pinJournalPath(configPath);
|
|
28018
28153
|
let raw;
|
|
28019
28154
|
try {
|
|
28020
|
-
raw =
|
|
28155
|
+
raw = readFileSync9(p, "utf8");
|
|
28021
28156
|
} catch (e) {
|
|
28022
|
-
if (
|
|
28157
|
+
if (existsSync12(p)) {
|
|
28023
28158
|
warn(`⚠️ rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted — verify it host-side before the next reconcile.
|
|
28024
28159
|
`);
|
|
28025
28160
|
}
|
|
@@ -28064,7 +28199,7 @@ function commitPinPersist(configPath) {
|
|
|
28064
28199
|
unlinkSync4(p);
|
|
28065
28200
|
return null;
|
|
28066
28201
|
} catch (e) {
|
|
28067
|
-
if (!
|
|
28202
|
+
if (!existsSync12(p))
|
|
28068
28203
|
return null;
|
|
28069
28204
|
return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side — while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
|
|
28070
28205
|
}
|
|
@@ -28086,7 +28221,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
28086
28221
|
}
|
|
28087
28222
|
}
|
|
28088
28223
|
try {
|
|
28089
|
-
const current =
|
|
28224
|
+
const current = readFileSync9(configPath, "utf8");
|
|
28090
28225
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
28091
28226
|
let mode = 384;
|
|
28092
28227
|
try {
|
|
@@ -28137,14 +28272,14 @@ function recoverPinJournal(configPath, opts = {}) {
|
|
|
28137
28272
|
// src/cli/operator-uid.ts
|
|
28138
28273
|
import {
|
|
28139
28274
|
chownSync,
|
|
28140
|
-
existsSync as
|
|
28275
|
+
existsSync as existsSync13,
|
|
28141
28276
|
lstatSync as lstatSync2,
|
|
28142
28277
|
readdirSync as readdirSync5,
|
|
28143
|
-
realpathSync as
|
|
28278
|
+
realpathSync as realpathSync3,
|
|
28144
28279
|
statSync as statSync7
|
|
28145
28280
|
} from "node:fs";
|
|
28146
28281
|
import { homedir as homedir6 } from "node:os";
|
|
28147
|
-
import { join as
|
|
28282
|
+
import { join as join8 } from "node:path";
|
|
28148
28283
|
var CONTAINER_OPERATOR_HOME = "/host-home";
|
|
28149
28284
|
var SWITCHROOM_HOME_MARKER = "switchroom.yaml";
|
|
28150
28285
|
function operatorHomeCandidates(env, home2) {
|
|
@@ -28153,7 +28288,7 @@ function operatorHomeCandidates(env, home2) {
|
|
|
28153
28288
|
for (const r of roots) {
|
|
28154
28289
|
if (!r || r.length === 0)
|
|
28155
28290
|
continue;
|
|
28156
|
-
const dir =
|
|
28291
|
+
const dir = join8(r, ".switchroom");
|
|
28157
28292
|
if (!out.includes(dir))
|
|
28158
28293
|
out.push(dir);
|
|
28159
28294
|
}
|
|
@@ -28162,7 +28297,7 @@ function operatorHomeCandidates(env, home2) {
|
|
|
28162
28297
|
function resolveOperatorUidFromOwnership(deps = {}) {
|
|
28163
28298
|
const env = deps.env ?? process.env;
|
|
28164
28299
|
const home2 = deps.home ?? homedir6();
|
|
28165
|
-
const exists = deps.exists ?? ((p) =>
|
|
28300
|
+
const exists = deps.exists ?? ((p) => existsSync13(p));
|
|
28166
28301
|
const statUid = deps.statUid ?? ((p) => {
|
|
28167
28302
|
try {
|
|
28168
28303
|
return statSync7(p).uid;
|
|
@@ -28171,7 +28306,7 @@ function resolveOperatorUidFromOwnership(deps = {}) {
|
|
|
28171
28306
|
}
|
|
28172
28307
|
});
|
|
28173
28308
|
for (const dir of operatorHomeCandidates(env, home2)) {
|
|
28174
|
-
if (!exists(
|
|
28309
|
+
if (!exists(join8(dir, SWITCHROOM_HOME_MARKER)))
|
|
28175
28310
|
continue;
|
|
28176
28311
|
const uid = statUid(dir);
|
|
28177
28312
|
if (uid !== undefined && Number.isFinite(uid) && uid > 0)
|
|
@@ -28204,15 +28339,15 @@ function resolveOperatorUid(ownershipDeps = {}) {
|
|
|
28204
28339
|
init_atomic();
|
|
28205
28340
|
|
|
28206
28341
|
// src/cli/resolve-version.ts
|
|
28207
|
-
import { existsSync as
|
|
28208
|
-
import { dirname as dirname7, join as
|
|
28342
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
|
|
28343
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
28209
28344
|
function readPackageVersion() {
|
|
28210
28345
|
let dir = import.meta.dirname;
|
|
28211
28346
|
for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
|
|
28212
|
-
const p =
|
|
28213
|
-
if (
|
|
28347
|
+
const p = join9(dir, "package.json");
|
|
28348
|
+
if (existsSync14(p)) {
|
|
28214
28349
|
try {
|
|
28215
|
-
const pkg = JSON.parse(
|
|
28350
|
+
const pkg = JSON.parse(readFileSync10(p, "utf-8"));
|
|
28216
28351
|
if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
|
|
28217
28352
|
return pkg.version;
|
|
28218
28353
|
}
|
|
@@ -28613,7 +28748,7 @@ var import__2 = __toESM(require_commander(), 1);
|
|
|
28613
28748
|
|
|
28614
28749
|
// src/cli/update.ts
|
|
28615
28750
|
init_loader();
|
|
28616
|
-
import { join as
|
|
28751
|
+
import { join as join10, dirname as dirname8 } from "node:path";
|
|
28617
28752
|
import { homedir as homedir7 } from "node:os";
|
|
28618
28753
|
|
|
28619
28754
|
// src/agents/lifecycle.ts
|
|
@@ -28716,7 +28851,12 @@ function getAllAgentStatuses(config) {
|
|
|
28716
28851
|
|
|
28717
28852
|
// src/cli/update.ts
|
|
28718
28853
|
init_atomic();
|
|
28719
|
-
var DEFAULT_COMPOSE_PATH =
|
|
28854
|
+
var DEFAULT_COMPOSE_PATH = join10(homedir7(), ".switchroom", "compose", "docker-compose.yml");
|
|
28855
|
+
var PACKAGED_INSTALL_KINDS = new Set([
|
|
28856
|
+
"static-binary",
|
|
28857
|
+
"npm-global",
|
|
28858
|
+
"container"
|
|
28859
|
+
]);
|
|
28720
28860
|
var UPDATE_RESULT_SENTINEL = "SWITCHROOM_UPDATE_RESULT:";
|
|
28721
28861
|
function parseUpdateResultLine(stdout) {
|
|
28722
28862
|
const idx = stdout.lastIndexOf(UPDATE_RESULT_SENTINEL);
|
|
@@ -28802,9 +28942,9 @@ function resolvePriorPinFromFleet(components, target, scope) {
|
|
|
28802
28942
|
// src/host-control/config-edit-validator.ts
|
|
28803
28943
|
init_schema();
|
|
28804
28944
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
28805
|
-
import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as
|
|
28945
|
+
import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
|
|
28806
28946
|
import { tmpdir } from "node:os";
|
|
28807
|
-
import { join as
|
|
28947
|
+
import { join as join11, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
|
|
28808
28948
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
28809
28949
|
import { isDeepStrictEqual } from "node:util";
|
|
28810
28950
|
var MAX_PATCH_BYTES = 1024 * 1024;
|
|
@@ -28817,12 +28957,12 @@ function isTargetPathHeader(headerPath, targetBasename) {
|
|
|
28817
28957
|
let p = headerPath;
|
|
28818
28958
|
if (p.startsWith("a/") || p.startsWith("b/"))
|
|
28819
28959
|
p = p.slice(2);
|
|
28820
|
-
if (
|
|
28960
|
+
if (isAbsolute2(p))
|
|
28821
28961
|
return false;
|
|
28822
28962
|
if (p.includes(".."))
|
|
28823
28963
|
return false;
|
|
28824
28964
|
const norm = normalize(p);
|
|
28825
|
-
if (norm.includes("..") ||
|
|
28965
|
+
if (norm.includes("..") || isAbsolute2(norm))
|
|
28826
28966
|
return false;
|
|
28827
28967
|
return norm === targetBasename || basename4(norm) === targetBasename;
|
|
28828
28968
|
}
|
|
@@ -28944,21 +29084,21 @@ function validateShape(unifiedDiff, targetPath) {
|
|
|
28944
29084
|
return null;
|
|
28945
29085
|
}
|
|
28946
29086
|
function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
28947
|
-
if (!
|
|
29087
|
+
if (!existsSync15(configPath)) {
|
|
28948
29088
|
return {
|
|
28949
29089
|
ok: false,
|
|
28950
29090
|
code: "E_PATCH_APPLY_FAILED",
|
|
28951
29091
|
detail: `target config not found at ${configPath}`
|
|
28952
29092
|
};
|
|
28953
29093
|
}
|
|
28954
|
-
const liveContent =
|
|
28955
|
-
const scratchDir = mkdtempSync(
|
|
29094
|
+
const liveContent = readFileSync11(configPath, "utf8");
|
|
29095
|
+
const scratchDir = mkdtempSync(join11(tmpdir(), "config-propose-edit-"));
|
|
28956
29096
|
try {
|
|
28957
29097
|
const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
|
|
28958
|
-
const scratchFile =
|
|
29098
|
+
const scratchFile = join11(scratchDir, targetBasename);
|
|
28959
29099
|
writeFileSync5(scratchFile, liveContent);
|
|
28960
29100
|
const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
|
|
28961
|
-
const patchFile =
|
|
29101
|
+
const patchFile = join11(scratchDir, "proposal.patch");
|
|
28962
29102
|
writeFileSync5(patchFile, normalizedDiff);
|
|
28963
29103
|
const bin = gitBin ?? "git";
|
|
28964
29104
|
const baseArgs = [
|
|
@@ -28988,7 +29128,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
|
28988
29128
|
detail: `git apply failed: ${(real.stderr || "").trim().slice(0, 500)}`
|
|
28989
29129
|
};
|
|
28990
29130
|
}
|
|
28991
|
-
const after =
|
|
29131
|
+
const after = readFileSync11(scratchFile, "utf8");
|
|
28992
29132
|
return { ok: true, after };
|
|
28993
29133
|
} finally {
|
|
28994
29134
|
rmSync2(scratchDir, { recursive: true, force: true });
|
|
@@ -29186,7 +29326,7 @@ function validateConfigEdit(opts) {
|
|
|
29186
29326
|
return schemaErr;
|
|
29187
29327
|
let beforeData = {};
|
|
29188
29328
|
try {
|
|
29189
|
-
const beforeRaw =
|
|
29329
|
+
const beforeRaw = existsSync15(opts.configPath) ? readFileSync11(opts.configPath, "utf8") : "";
|
|
29190
29330
|
const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
|
|
29191
29331
|
beforeData = beforeDoc.toJS();
|
|
29192
29332
|
} catch {
|
|
@@ -29770,11 +29910,11 @@ function resolveDigests(imageRefs) {
|
|
|
29770
29910
|
return out;
|
|
29771
29911
|
}
|
|
29772
29912
|
function readCachedInstallType(bindRoot) {
|
|
29773
|
-
const cacheDir =
|
|
29774
|
-
const cachePath =
|
|
29775
|
-
if (
|
|
29913
|
+
const cacheDir = join12(bindRoot, ".switchroom");
|
|
29914
|
+
const cachePath = join12(cacheDir, "install-type.json");
|
|
29915
|
+
if (existsSync16(cachePath)) {
|
|
29776
29916
|
try {
|
|
29777
|
-
const raw =
|
|
29917
|
+
const raw = readFileSync12(cachePath, "utf-8");
|
|
29778
29918
|
const parsed = JSON.parse(raw);
|
|
29779
29919
|
if (parsed && typeof parsed.install_type === "string" && typeof parsed.detected_at === "string") {
|
|
29780
29920
|
return parsed;
|
|
@@ -29826,7 +29966,7 @@ function writeFileInPlacePreservingInode(targetPath, content) {
|
|
|
29826
29966
|
} finally {
|
|
29827
29967
|
closeSync5(fd);
|
|
29828
29968
|
}
|
|
29829
|
-
const readBack =
|
|
29969
|
+
const readBack = readFileSync12(targetPath);
|
|
29830
29970
|
if (readBack.length !== buf.length) {
|
|
29831
29971
|
throw new Error(`in-place write short: wrote ${buf.length} bytes but read back ${readBack.length}`);
|
|
29832
29972
|
}
|
|
@@ -29861,11 +30001,11 @@ class HostdServer {
|
|
|
29861
30001
|
fleetMutationInFlight = null;
|
|
29862
30002
|
lastAutoRolloutFailedPin = null;
|
|
29863
30003
|
autoRolloutLatchPath() {
|
|
29864
|
-
return
|
|
30004
|
+
return join12(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
|
|
29865
30005
|
}
|
|
29866
30006
|
readAutoRolloutLatch() {
|
|
29867
30007
|
try {
|
|
29868
|
-
const raw =
|
|
30008
|
+
const raw = readFileSync12(this.autoRolloutLatchPath(), "utf-8");
|
|
29869
30009
|
const parsed = JSON.parse(raw);
|
|
29870
30010
|
if (parsed && parsed.v === 1 && typeof parsed.pin === "string" && parsed.pin.length > 0 && (parsed.outcome === "attempting" || parsed.outcome === "failed")) {
|
|
29871
30011
|
return parsed;
|
|
@@ -29904,7 +30044,7 @@ class HostdServer {
|
|
|
29904
30044
|
return resolveHostdConfigPath(this.opts.configPath);
|
|
29905
30045
|
}
|
|
29906
30046
|
fleetComposePath() {
|
|
29907
|
-
return
|
|
30047
|
+
return join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
29908
30048
|
}
|
|
29909
30049
|
recoverRolloutPinJournal(context) {
|
|
29910
30050
|
let note = null;
|
|
@@ -29943,7 +30083,7 @@ class HostdServer {
|
|
|
29943
30083
|
return note;
|
|
29944
30084
|
}
|
|
29945
30085
|
async start() {
|
|
29946
|
-
const hostdDir =
|
|
30086
|
+
const hostdDir = join12(this.opts.homeDir, ".switchroom", "hostd");
|
|
29947
30087
|
await mkdir2(hostdDir, { recursive: true });
|
|
29948
30088
|
await chmod(hostdDir, 493).catch(() => {
|
|
29949
30089
|
return;
|
|
@@ -29964,13 +30104,13 @@ class HostdServer {
|
|
|
29964
30104
|
}
|
|
29965
30105
|
try {
|
|
29966
30106
|
for (const name of agentNames) {
|
|
29967
|
-
const dir =
|
|
29968
|
-
const sockPath =
|
|
30107
|
+
const dir = join12(hostdDir, name);
|
|
30108
|
+
const sockPath = join12(dir, "sock");
|
|
29969
30109
|
await mkdir2(dir, { recursive: true });
|
|
29970
30110
|
await chmod(dir, 493).catch(() => {
|
|
29971
30111
|
return;
|
|
29972
30112
|
});
|
|
29973
|
-
if (
|
|
30113
|
+
if (existsSync16(sockPath))
|
|
29974
30114
|
await unlink(sockPath).catch(() => {
|
|
29975
30115
|
return;
|
|
29976
30116
|
});
|
|
@@ -30001,13 +30141,13 @@ class HostdServer {
|
|
|
30001
30141
|
process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
|
|
30002
30142
|
`);
|
|
30003
30143
|
} else {
|
|
30004
|
-
const dir =
|
|
30005
|
-
const sockPath =
|
|
30144
|
+
const dir = join12(hostdDir, "operator");
|
|
30145
|
+
const sockPath = join12(dir, "sock");
|
|
30006
30146
|
await mkdir2(dir, { recursive: true });
|
|
30007
30147
|
await chmod(dir, 493).catch(() => {
|
|
30008
30148
|
return;
|
|
30009
30149
|
});
|
|
30010
|
-
if (
|
|
30150
|
+
if (existsSync16(sockPath))
|
|
30011
30151
|
await unlink(sockPath).catch(() => {
|
|
30012
30152
|
return;
|
|
30013
30153
|
});
|
|
@@ -30043,11 +30183,11 @@ class HostdServer {
|
|
|
30043
30183
|
}
|
|
30044
30184
|
async reconcileOrphanedFleetMutations() {
|
|
30045
30185
|
const path3 = this.auditLogPath();
|
|
30046
|
-
if (!
|
|
30186
|
+
if (!existsSync16(path3))
|
|
30047
30187
|
return;
|
|
30048
30188
|
let raw;
|
|
30049
30189
|
try {
|
|
30050
|
-
raw =
|
|
30190
|
+
raw = readFileSync12(path3, "utf-8");
|
|
30051
30191
|
} catch {
|
|
30052
30192
|
return;
|
|
30053
30193
|
}
|
|
@@ -30446,11 +30586,11 @@ class HostdServer {
|
|
|
30446
30586
|
missingApplyAssets() {
|
|
30447
30587
|
const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
|
|
30448
30588
|
return [
|
|
30449
|
-
|
|
30450
|
-
|
|
30451
|
-
|
|
30452
|
-
|
|
30453
|
-
].filter((p) => !
|
|
30589
|
+
join12(root, "profiles"),
|
|
30590
|
+
join12(root, "profiles", "default"),
|
|
30591
|
+
join12(root, "vendor", "hindsight-memory"),
|
|
30592
|
+
join12(root, "skills")
|
|
30593
|
+
].filter((p) => !existsSync16(p));
|
|
30454
30594
|
}
|
|
30455
30595
|
applyAssetPreflight(request_id, started) {
|
|
30456
30596
|
const missing = this.missingApplyAssets();
|
|
@@ -30708,19 +30848,19 @@ class HostdServer {
|
|
|
30708
30848
|
return entry;
|
|
30709
30849
|
}
|
|
30710
30850
|
hostdDirPath() {
|
|
30711
|
-
return
|
|
30851
|
+
return join12(this.opts.homeDir, ".switchroom", "hostd");
|
|
30712
30852
|
}
|
|
30713
30853
|
hostdDirHostPath() {
|
|
30714
30854
|
const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
|
|
30715
|
-
return
|
|
30855
|
+
return join12(hostHome, ".switchroom", "hostd");
|
|
30716
30856
|
}
|
|
30717
30857
|
async beginSelfBump(req, caller, started) {
|
|
30718
30858
|
const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
|
|
30719
|
-
const composePath =
|
|
30720
|
-
if (!
|
|
30859
|
+
const composePath = join12(this.hostdDirPath(), "docker-compose.yml");
|
|
30860
|
+
if (!existsSync16(composePath)) {
|
|
30721
30861
|
return deniedResponse(req.request_id, `rollout: hostd's CLI (v${ownVersion}) is older than the target ` + `${req.args.pin} and needs a self-bump first, but its compose file ` + `is missing at ${composePath}. Run \`switchroom hostd install ` + `--tag ${req.args.pin}\` on the host, then re-run the roll. ` + `Nothing was changed.`, Date.now() - started);
|
|
30722
30862
|
}
|
|
30723
|
-
const before =
|
|
30863
|
+
const before = readFileSync12(composePath, "utf8");
|
|
30724
30864
|
const bumped = bumpHostdComposeImageTag(before, req.args.pin);
|
|
30725
30865
|
if (!bumped) {
|
|
30726
30866
|
return deniedResponse(req.request_id, `rollout: hostd needs a self-bump to ${req.args.pin} but no ` + `switchroom-hostd image line was found in ${composePath} ` + `(hand-edited compose?). Run \`switchroom hostd install --tag ` + `${req.args.pin}\` on the host, then re-run the roll. Nothing ` + `was changed.`, Date.now() - started);
|
|
@@ -30741,10 +30881,10 @@ class HostdServer {
|
|
|
30741
30881
|
try {
|
|
30742
30882
|
const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
|
|
30743
30883
|
for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
|
|
30744
|
-
unlinkSync5(
|
|
30884
|
+
unlinkSync5(join12(this.hostdDirPath(), f));
|
|
30745
30885
|
}
|
|
30746
30886
|
} catch {}
|
|
30747
|
-
const markerPath =
|
|
30887
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30748
30888
|
const marker = {
|
|
30749
30889
|
v: 1,
|
|
30750
30890
|
request_id: req.request_id,
|
|
@@ -30828,7 +30968,7 @@ class HostdServer {
|
|
|
30828
30968
|
rollbackBump();
|
|
30829
30969
|
entry.result = "error";
|
|
30830
30970
|
entry.finished_at = Date.now();
|
|
30831
|
-
entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${
|
|
30971
|
+
entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
|
|
30832
30972
|
if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
|
|
30833
30973
|
this.fleetMutationInFlight = null;
|
|
30834
30974
|
}
|
|
@@ -30855,10 +30995,10 @@ class HostdServer {
|
|
|
30855
30995
|
try {
|
|
30856
30996
|
if (!Number.isInteger(messageId) || messageId <= 0)
|
|
30857
30997
|
return;
|
|
30858
|
-
const markerPath =
|
|
30859
|
-
if (!
|
|
30998
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30999
|
+
if (!existsSync16(markerPath))
|
|
30860
31000
|
return;
|
|
30861
|
-
const marker = parsePendingRolloutMarker(
|
|
31001
|
+
const marker = parsePendingRolloutMarker(readFileSync12(markerPath, "utf8"));
|
|
30862
31002
|
if (!marker)
|
|
30863
31003
|
return;
|
|
30864
31004
|
if (marker.request_id !== requestId)
|
|
@@ -30873,12 +31013,12 @@ class HostdServer {
|
|
|
30873
31013
|
} catch {}
|
|
30874
31014
|
}
|
|
30875
31015
|
async resumePendingSelfBumpRollout() {
|
|
30876
|
-
const markerPath =
|
|
30877
|
-
if (!
|
|
31016
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
31017
|
+
if (!existsSync16(markerPath))
|
|
30878
31018
|
return;
|
|
30879
31019
|
let raw;
|
|
30880
31020
|
try {
|
|
30881
|
-
raw =
|
|
31021
|
+
raw = readFileSync12(markerPath, "utf8");
|
|
30882
31022
|
} finally {
|
|
30883
31023
|
try {
|
|
30884
31024
|
unlinkSync5(markerPath);
|
|
@@ -30929,7 +31069,7 @@ class HostdServer {
|
|
|
30929
31069
|
return;
|
|
30930
31070
|
}
|
|
30931
31071
|
if (needsSelfBump(ownVersion, marker.pin)) {
|
|
30932
|
-
await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${
|
|
31072
|
+
await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
|
|
30933
31073
|
return;
|
|
30934
31074
|
}
|
|
30935
31075
|
process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
|
|
@@ -31037,7 +31177,7 @@ class HostdServer {
|
|
|
31037
31177
|
}
|
|
31038
31178
|
let beforeContent;
|
|
31039
31179
|
try {
|
|
31040
|
-
beforeContent =
|
|
31180
|
+
beforeContent = readFileSync12(configPath, "utf-8");
|
|
31041
31181
|
} catch {
|
|
31042
31182
|
beforeContent = "";
|
|
31043
31183
|
}
|
|
@@ -31157,7 +31297,7 @@ class HostdServer {
|
|
|
31157
31297
|
try {
|
|
31158
31298
|
let snapshot;
|
|
31159
31299
|
try {
|
|
31160
|
-
snapshot =
|
|
31300
|
+
snapshot = readFileSync12(configPath, "utf-8");
|
|
31161
31301
|
} catch (e) {
|
|
31162
31302
|
await approval.finalize({
|
|
31163
31303
|
outcome: "reconcile_failed_rolled_back",
|
|
@@ -31455,8 +31595,8 @@ ${output.recovery.stderr}` : "";
|
|
|
31455
31595
|
if (this.opts.imageRefsForDigests)
|
|
31456
31596
|
return this.opts.imageRefsForDigests();
|
|
31457
31597
|
try {
|
|
31458
|
-
const composePath =
|
|
31459
|
-
if (!
|
|
31598
|
+
const composePath = join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
31599
|
+
if (!existsSync16(composePath))
|
|
31460
31600
|
return [];
|
|
31461
31601
|
const r = spawnSync4("docker", [
|
|
31462
31602
|
"compose",
|
|
@@ -31714,10 +31854,10 @@ ${pinNote}`.trim();
|
|
|
31714
31854
|
}
|
|
31715
31855
|
rolloutRowExistsInLog(targetRequestId) {
|
|
31716
31856
|
const path3 = this.auditLogPath();
|
|
31717
|
-
if (!
|
|
31857
|
+
if (!existsSync16(path3))
|
|
31718
31858
|
return false;
|
|
31719
31859
|
try {
|
|
31720
|
-
const raw =
|
|
31860
|
+
const raw = readFileSync12(path3, "utf-8");
|
|
31721
31861
|
return latestRolloutRowForRequest(raw, targetRequestId) !== null;
|
|
31722
31862
|
} catch {
|
|
31723
31863
|
return false;
|
|
@@ -31725,11 +31865,11 @@ ${pinNote}`.trim();
|
|
|
31725
31865
|
}
|
|
31726
31866
|
rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
|
|
31727
31867
|
const path3 = this.auditLogPath();
|
|
31728
|
-
if (!
|
|
31868
|
+
if (!existsSync16(path3))
|
|
31729
31869
|
return null;
|
|
31730
31870
|
let raw;
|
|
31731
31871
|
try {
|
|
31732
|
-
raw =
|
|
31872
|
+
raw = readFileSync12(path3, "utf-8");
|
|
31733
31873
|
} catch {
|
|
31734
31874
|
return null;
|
|
31735
31875
|
}
|
|
@@ -31811,7 +31951,7 @@ ${pinNote}`.trim();
|
|
|
31811
31951
|
}
|
|
31812
31952
|
}
|
|
31813
31953
|
auditLogPath() {
|
|
31814
|
-
return this.opts.auditLogPath ??
|
|
31954
|
+
return this.opts.auditLogPath ?? join12(this.opts.homeDir, ".switchroom", "host-control-audit.log");
|
|
31815
31955
|
}
|
|
31816
31956
|
maybeRotateAuditLog(path3) {
|
|
31817
31957
|
const { maxBytes, maxFiles } = resolveRotationConfig({
|
|
@@ -33295,7 +33435,7 @@ function makeRestart(switchroomBin) {
|
|
|
33295
33435
|
}
|
|
33296
33436
|
|
|
33297
33437
|
// src/host-control/update-notifier.ts
|
|
33298
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
33438
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
33299
33439
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
33300
33440
|
import { dirname as dirname10 } from "node:path";
|
|
33301
33441
|
var FINALIZE_DETAIL_MAX = 450;
|
|
@@ -33412,7 +33552,7 @@ class UpdateNotifier {
|
|
|
33412
33552
|
}
|
|
33413
33553
|
readState() {
|
|
33414
33554
|
try {
|
|
33415
|
-
const raw =
|
|
33555
|
+
const raw = readFileSync13(this.opts.statePath, "utf8");
|
|
33416
33556
|
const parsed = JSON.parse(raw);
|
|
33417
33557
|
if (parsed === null || typeof parsed !== "object")
|
|
33418
33558
|
return null;
|
|
@@ -33553,13 +33693,13 @@ async function loadConfigResilient() {
|
|
|
33553
33693
|
} catch {
|
|
33554
33694
|
configPath = undefined;
|
|
33555
33695
|
}
|
|
33556
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
33696
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
|
|
33557
33697
|
const listGatewayCandidates = () => {
|
|
33558
33698
|
const out = [];
|
|
33559
33699
|
try {
|
|
33560
33700
|
for (const name of readdirSync7(agentsDir).sort()) {
|
|
33561
33701
|
const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
|
|
33562
|
-
if (
|
|
33702
|
+
if (existsSync17(sock))
|
|
33563
33703
|
out.push({ agent: name, sock });
|
|
33564
33704
|
}
|
|
33565
33705
|
} catch {}
|
|
@@ -33606,10 +33746,10 @@ async function main() {
|
|
|
33606
33746
|
`);
|
|
33607
33747
|
process.exit(2);
|
|
33608
33748
|
}
|
|
33609
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
33749
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
|
|
33610
33750
|
const resolveGatewaySocket = (agentName) => {
|
|
33611
33751
|
const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
|
|
33612
|
-
return
|
|
33752
|
+
return existsSync17(sock) ? sock : null;
|
|
33613
33753
|
};
|
|
33614
33754
|
const approvalGateway = new SocketApprovalGateway({
|
|
33615
33755
|
resolveGatewaySocket,
|
|
@@ -33663,7 +33803,7 @@ async function main() {
|
|
|
33663
33803
|
const autoRel = config.host_control?.auto_release_check;
|
|
33664
33804
|
const watcherMode = resolveReleaseWatcherMode(config);
|
|
33665
33805
|
if (watcherMode.mode !== "off") {
|
|
33666
|
-
const eventsLog =
|
|
33806
|
+
const eventsLog = join13(homedir8(), ".switchroom", "release-watcher-events.jsonl");
|
|
33667
33807
|
const log = (m) => {
|
|
33668
33808
|
process.stderr.write(`hostd: ${m}
|
|
33669
33809
|
`);
|
|
@@ -33722,7 +33862,7 @@ async function main() {
|
|
|
33722
33862
|
`);
|
|
33723
33863
|
} else
|
|
33724
33864
|
notifier = new UpdateNotifier({
|
|
33725
|
-
statePath:
|
|
33865
|
+
statePath: join13(homedir8(), ".switchroom", "release-notify-state.json"),
|
|
33726
33866
|
repostSuppressMs: cardTimeoutMs,
|
|
33727
33867
|
isFleetMutationInFlight: () => server.isFleetMutationLocked(),
|
|
33728
33868
|
startApply: (requestId) => {
|