switchroom 0.19.42 → 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 +284 -124
- package/dist/cli/notion-write-pretool.mjs +4 -1
- package/dist/cli/switchroom.js +4094 -2711
- package/dist/host-control/main.js +472 -155
- package/dist/vault/approvals/kernel-server.js +231 -71
- package/dist/vault/broker/server.js +293 -133
- package/package.json +3 -3
- package/profiles/_base/start.sh.hbs +93 -8
- package/telegram-plugin/bridge/bridge.ts +1 -1
- package/telegram-plugin/card-layout.ts +63 -1
- package/telegram-plugin/dist/bridge/bridge.js +4 -1
- package/telegram-plugin/dist/gateway/gateway.js +1682 -936
- package/telegram-plugin/dist/server.js +5 -2
- package/telegram-plugin/flood-429-ledger.ts +5 -3
- package/telegram-plugin/flood-circuit-breaker.ts +11 -3
- package/telegram-plugin/format.ts +223 -13
- package/telegram-plugin/gateway/boot-card.ts +7 -2
- package/telegram-plugin/gateway/flood-reply-queue.ts +5 -0
- package/telegram-plugin/gateway/gateway.ts +49 -51
- package/telegram-plugin/gateway/handback-orphan-recovery.ts +69 -0
- package/telegram-plugin/gateway/handback-preturn-signal.ts +170 -32
- package/telegram-plugin/gateway/ipc-protocol.ts +31 -3
- package/telegram-plugin/gateway/ipc-server.ts +13 -4
- package/telegram-plugin/gateway/obligation-store.ts +55 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +127 -11
- package/telegram-plugin/gateway/outbox-sweep.ts +371 -61
- package/telegram-plugin/gateway/rollout-status-edit.ts +175 -0
- package/telegram-plugin/gateway/stream-render.ts +5 -0
- package/telegram-plugin/gateway/update-announce.ts +11 -4
- package/telegram-plugin/hooks/audience-classify.d.mts +50 -0
- package/telegram-plugin/hooks/audience-classify.mjs +364 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +86 -3
- package/telegram-plugin/hooks/silent-end-scan.mjs +80 -4
- package/telegram-plugin/outbox.ts +103 -2
- package/telegram-plugin/render/rich-render.ts +10 -2
- package/telegram-plugin/reply-quote.ts +150 -0
- package/telegram-plugin/retry-api-call.ts +254 -11
- package/telegram-plugin/shared/bot-runtime.ts +69 -34
- package/telegram-plugin/shared/gw-trace-gate.ts +14 -2
- package/telegram-plugin/silent-end.ts +34 -2
- package/telegram-plugin/status-no-truncate.ts +31 -16
- package/telegram-plugin/stream-controller.ts +44 -9
- package/telegram-plugin/stream-reply-handler.ts +5 -3
- package/telegram-plugin/tests/boot-card-render.test.ts +2 -1
- package/telegram-plugin/tests/card-budget-never-overflows.test.ts +165 -0
- package/telegram-plugin/tests/card-hard-truncate-entity-safe.test.ts +153 -0
- package/telegram-plugin/tests/card-variants.golden.txt +9 -9
- package/telegram-plugin/tests/flood-reply-queue.test.ts +89 -4
- package/telegram-plugin/tests/format-consistency.test.ts +160 -14
- package/telegram-plugin/tests/handback-orphan-recovery.test.ts +131 -0
- package/telegram-plugin/tests/handback-preturn-signal.test.ts +212 -17
- package/telegram-plugin/tests/nested-indent-idiom.test.ts +162 -0
- package/telegram-plugin/tests/nested-prefix-indent.test.ts +115 -0
- package/telegram-plugin/tests/no-robust-api-call-factory.test.ts +62 -0
- package/telegram-plugin/tests/obligation-store.test.ts +51 -0
- package/telegram-plugin/tests/outbox-audience-3865.test.ts +579 -0
- package/telegram-plugin/tests/outbox-provenance-4141.test.ts +1126 -0
- package/telegram-plugin/tests/outbox-sweep-single-flight.test.ts +138 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +12 -10
- package/telegram-plugin/tests/plain-fallback-4096-cap.test.ts +509 -0
- package/telegram-plugin/tests/reply-quote-wire.test.ts +433 -0
- package/telegram-plugin/tests/retry-grammy-http-error.test.ts +404 -0
- package/telegram-plugin/tests/rollout-narration-edit-socket.test.ts +170 -0
- package/telegram-plugin/tests/rollout-status-edit-retry-policy.test.ts +366 -0
- package/telegram-plugin/tests/rollout-status-edit.test.ts +140 -0
- package/telegram-plugin/tests/rollout-status-wiring.test.ts +40 -7
- package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +4 -0
- package/telegram-plugin/tests/tg-post-retry-attribution.test.ts +196 -0
- package/telegram-plugin/tests/tool-activity-summary.test.ts +23 -17
- package/telegram-plugin/tool-activity-summary.ts +58 -30
- package/telegram-plugin/uat/scenarios/jtbd-foreground-subagent-activity-dm.test.ts +3 -2
- 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,
|
|
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
|
|
|
@@ -21518,7 +21570,9 @@ var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
|
21518
21570
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
21519
21571
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
|
|
21520
21572
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
21521
|
-
var
|
|
21573
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1;
|
|
21574
|
+
var HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3;
|
|
21575
|
+
var HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false";
|
|
21522
21576
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
21523
21577
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30;
|
|
21524
21578
|
var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
@@ -21564,8 +21618,8 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
|
21564
21618
|
String(HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S)
|
|
21565
21619
|
],
|
|
21566
21620
|
[
|
|
21567
|
-
"
|
|
21568
|
-
String(
|
|
21621
|
+
"HINDSIGHT_API_WORKER_CONSOLIDATION_RESERVED_SLOTS",
|
|
21622
|
+
String(HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS)
|
|
21569
21623
|
],
|
|
21570
21624
|
[
|
|
21571
21625
|
"HINDSIGHT_API_WORKER_CONSOLIDATION_SLOT_LIMIT",
|
|
@@ -21582,6 +21636,10 @@ var HINDSIGHT_PERF_DEFAULTS_UNGATED = [
|
|
|
21582
21636
|
[
|
|
21583
21637
|
"HINDSIGHT_API_RECENCY_DECAY_HALFLIFE_DAYS",
|
|
21584
21638
|
String(HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS)
|
|
21639
|
+
],
|
|
21640
|
+
[
|
|
21641
|
+
"HINDSIGHT_API_GRAPH_SEED_MIN_SIMILARITY",
|
|
21642
|
+
String(HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY)
|
|
21585
21643
|
]
|
|
21586
21644
|
];
|
|
21587
21645
|
var HINDSIGHT_PERF_DEFAULTS_GPU = [
|
|
@@ -21602,22 +21660,42 @@ var HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM = [
|
|
|
21602
21660
|
String(HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT)
|
|
21603
21661
|
],
|
|
21604
21662
|
["HINDSIGHT_API_LLM_STRICT_SCHEMA", HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA],
|
|
21605
|
-
["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)]
|
|
21663
|
+
["HINDSIGHT_API_LLM_MAX_RETRIES", String(HINDSIGHT_DEFAULT_LLM_MAX_RETRIES)],
|
|
21664
|
+
["HINDSIGHT_API_LLM_SUPPORTS_MAX_ITEMS", HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS]
|
|
21606
21665
|
];
|
|
21607
21666
|
var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
21608
21667
|
"HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY",
|
|
21609
21668
|
"HINDSIGHT_CE_DECISIVE_RELATIVE_GAP",
|
|
21610
21669
|
"HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS",
|
|
21611
21670
|
"HINDSIGHT_API_WORKER_MAX_SLOTS",
|
|
21612
|
-
"
|
|
21671
|
+
"HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS",
|
|
21672
|
+
"HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
|
|
21673
|
+
"HINDSIGHT_MCP_RECALL_BUDGET_MODE",
|
|
21674
|
+
"HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
|
|
21675
|
+
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
21676
|
+
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
|
|
21613
21677
|
]);
|
|
21678
|
+
var HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
21679
|
+
"consolidation",
|
|
21680
|
+
"file_convert_retain",
|
|
21681
|
+
"graph_maintenance",
|
|
21682
|
+
"import_documents",
|
|
21683
|
+
"refresh_mental_model",
|
|
21684
|
+
"retain"
|
|
21685
|
+
];
|
|
21686
|
+
var HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES = new Map(HINDSIGHT_WORKER_SLOT_TYPES.map((type) => [
|
|
21687
|
+
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
|
|
21688
|
+
`HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
|
|
21689
|
+
]));
|
|
21614
21690
|
var HINDSIGHT_PERF_ENV_KEYS = new Set([
|
|
21615
21691
|
...[
|
|
21616
21692
|
...HINDSIGHT_PERF_DEFAULTS_UNGATED,
|
|
21617
21693
|
...HINDSIGHT_PERF_DEFAULTS_GPU,
|
|
21618
21694
|
...HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM
|
|
21619
21695
|
].map(([k]) => k),
|
|
21620
|
-
...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS
|
|
21696
|
+
...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
|
|
21697
|
+
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
|
|
21698
|
+
...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
|
|
21621
21699
|
]);
|
|
21622
21700
|
|
|
21623
21701
|
// src/setup/hindsight-context-budget.ts
|
|
@@ -21690,6 +21768,15 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
21690
21768
|
` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
|
|
21691
21769
|
` + ` system's self-reports are not memories.
|
|
21692
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.
|
|
21693
21780
|
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
21694
21781
|
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
21695
21782
|
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
@@ -21845,6 +21932,64 @@ var SUPERSEDED_RETAIN_MISSIONS = [
|
|
|
21845
21932
|
` + `
|
|
21846
21933
|
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
21847
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.
|
|
21848
21993
|
`
|
|
21849
21994
|
];
|
|
21850
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.
|
|
@@ -21922,6 +22067,22 @@ var PROFILE_MEMORY_DEFAULTS = {
|
|
|
21922
22067
|
var warnedMissingPool = new Set;
|
|
21923
22068
|
var warnedMissingDefault = new Set;
|
|
21924
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
|
+
|
|
21925
22086
|
// src/telegram/state.ts
|
|
21926
22087
|
init_paths();
|
|
21927
22088
|
|
|
@@ -21959,7 +22120,6 @@ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
|
|
|
21959
22120
|
init_paths();
|
|
21960
22121
|
|
|
21961
22122
|
// src/agents/scaffold.ts
|
|
21962
|
-
var REPO_ROOT = resolve5(import.meta.dirname, "../..");
|
|
21963
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.";
|
|
21964
22124
|
var pendingBankOps = new Set;
|
|
21965
22125
|
var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
|
|
@@ -21991,8 +22151,8 @@ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
|
|
|
21991
22151
|
import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
|
|
21992
22152
|
import {
|
|
21993
22153
|
readdirSync as readdirSync6,
|
|
21994
|
-
existsSync as
|
|
21995
|
-
readFileSync as
|
|
22154
|
+
existsSync as existsSync16,
|
|
22155
|
+
readFileSync as readFileSync12,
|
|
21996
22156
|
writeFileSync as writeFileSync6,
|
|
21997
22157
|
renameSync as renameSync5,
|
|
21998
22158
|
mkdirSync as mkdirSync5,
|
|
@@ -22004,7 +22164,7 @@ import {
|
|
|
22004
22164
|
copyFileSync as copyFileSync3,
|
|
22005
22165
|
unlinkSync as unlinkSync5
|
|
22006
22166
|
} from "node:fs";
|
|
22007
|
-
import { join as
|
|
22167
|
+
import { join as join12, dirname as dirname9, resolve as resolve9 } from "node:path";
|
|
22008
22168
|
import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
|
|
22009
22169
|
|
|
22010
22170
|
// src/host-control/protocol.ts
|
|
@@ -26696,9 +26856,9 @@ class PostHog extends PostHogBackendClient {
|
|
|
26696
26856
|
// src/analytics/posthog.ts
|
|
26697
26857
|
init_paths();
|
|
26698
26858
|
import {
|
|
26699
|
-
existsSync as
|
|
26859
|
+
existsSync as existsSync9,
|
|
26700
26860
|
mkdirSync as mkdirSync3,
|
|
26701
|
-
readFileSync as
|
|
26861
|
+
readFileSync as readFileSync8,
|
|
26702
26862
|
writeFileSync as writeFileSync3
|
|
26703
26863
|
} from "node:fs";
|
|
26704
26864
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -26717,8 +26877,8 @@ function getDistinctId() {
|
|
|
26717
26877
|
return cachedDistinctId;
|
|
26718
26878
|
const path = resolveStatePath("analytics-id");
|
|
26719
26879
|
try {
|
|
26720
|
-
if (
|
|
26721
|
-
const existing =
|
|
26880
|
+
if (existsSync9(path)) {
|
|
26881
|
+
const existing = readFileSync8(path, "utf-8").trim();
|
|
26722
26882
|
if (existing) {
|
|
26723
26883
|
cachedDistinctId = existing;
|
|
26724
26884
|
return existing;
|
|
@@ -27927,8 +28087,8 @@ function releaseBlockIsCommented(doc) {
|
|
|
27927
28087
|
|
|
27928
28088
|
// src/cli/rollout-pin-journal.ts
|
|
27929
28089
|
import {
|
|
27930
|
-
existsSync as
|
|
27931
|
-
readFileSync as
|
|
28090
|
+
existsSync as existsSync12,
|
|
28091
|
+
readFileSync as readFileSync9,
|
|
27932
28092
|
writeFileSync as writeFileSync4,
|
|
27933
28093
|
renameSync as renameSync4,
|
|
27934
28094
|
unlinkSync as unlinkSync4,
|
|
@@ -27937,7 +28097,7 @@ import {
|
|
|
27937
28097
|
} from "node:fs";
|
|
27938
28098
|
import { homedir as homedir5 } from "node:os";
|
|
27939
28099
|
import { createHash as createHash4 } from "node:crypto";
|
|
27940
|
-
import { join as
|
|
28100
|
+
import { join as join7, basename as basename3, resolve as resolve7, dirname as dirname6 } from "node:path";
|
|
27941
28101
|
init_flock();
|
|
27942
28102
|
var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
|
|
27943
28103
|
var STATE_DIR_NAME = ".switchroom";
|
|
@@ -27950,15 +28110,15 @@ function pinJournalDir(configPath) {
|
|
|
27950
28110
|
if (basename3(dir) === STATE_DIR_NAME)
|
|
27951
28111
|
return dir;
|
|
27952
28112
|
}
|
|
27953
|
-
return
|
|
28113
|
+
return join7(homedir5(), STATE_DIR_NAME);
|
|
27954
28114
|
}
|
|
27955
28115
|
function pinJournalPath(configPath) {
|
|
27956
28116
|
const abs = resolve7(configPath);
|
|
27957
28117
|
const key = createHash4("sha256").update(abs).digest("hex").slice(0, 12);
|
|
27958
|
-
return
|
|
28118
|
+
return join7(pinJournalDir(abs), `.rollout-pin-journal.${basename3(abs)}.${key}.json`);
|
|
27959
28119
|
}
|
|
27960
28120
|
function hasPinJournal(configPath) {
|
|
27961
|
-
return
|
|
28121
|
+
return existsSync12(pinJournalPath(configPath));
|
|
27962
28122
|
}
|
|
27963
28123
|
function isPidAlive(pid) {
|
|
27964
28124
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
@@ -27992,9 +28152,9 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
|
|
|
27992
28152
|
const p = pinJournalPath(configPath);
|
|
27993
28153
|
let raw;
|
|
27994
28154
|
try {
|
|
27995
|
-
raw =
|
|
28155
|
+
raw = readFileSync9(p, "utf8");
|
|
27996
28156
|
} catch (e) {
|
|
27997
|
-
if (
|
|
28157
|
+
if (existsSync12(p)) {
|
|
27998
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.
|
|
27999
28159
|
`);
|
|
28000
28160
|
}
|
|
@@ -28039,7 +28199,7 @@ function commitPinPersist(configPath) {
|
|
|
28039
28199
|
unlinkSync4(p);
|
|
28040
28200
|
return null;
|
|
28041
28201
|
} catch (e) {
|
|
28042
|
-
if (!
|
|
28202
|
+
if (!existsSync12(p))
|
|
28043
28203
|
return null;
|
|
28044
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\`.`;
|
|
28045
28205
|
}
|
|
@@ -28061,7 +28221,7 @@ function rollbackPinPersist(configPath, opts = {}) {
|
|
|
28061
28221
|
}
|
|
28062
28222
|
}
|
|
28063
28223
|
try {
|
|
28064
|
-
const current =
|
|
28224
|
+
const current = readFileSync9(configPath, "utf8");
|
|
28065
28225
|
const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
|
|
28066
28226
|
let mode = 384;
|
|
28067
28227
|
try {
|
|
@@ -28112,14 +28272,14 @@ function recoverPinJournal(configPath, opts = {}) {
|
|
|
28112
28272
|
// src/cli/operator-uid.ts
|
|
28113
28273
|
import {
|
|
28114
28274
|
chownSync,
|
|
28115
|
-
existsSync as
|
|
28275
|
+
existsSync as existsSync13,
|
|
28116
28276
|
lstatSync as lstatSync2,
|
|
28117
28277
|
readdirSync as readdirSync5,
|
|
28118
|
-
realpathSync as
|
|
28278
|
+
realpathSync as realpathSync3,
|
|
28119
28279
|
statSync as statSync7
|
|
28120
28280
|
} from "node:fs";
|
|
28121
28281
|
import { homedir as homedir6 } from "node:os";
|
|
28122
|
-
import { join as
|
|
28282
|
+
import { join as join8 } from "node:path";
|
|
28123
28283
|
var CONTAINER_OPERATOR_HOME = "/host-home";
|
|
28124
28284
|
var SWITCHROOM_HOME_MARKER = "switchroom.yaml";
|
|
28125
28285
|
function operatorHomeCandidates(env, home2) {
|
|
@@ -28128,7 +28288,7 @@ function operatorHomeCandidates(env, home2) {
|
|
|
28128
28288
|
for (const r of roots) {
|
|
28129
28289
|
if (!r || r.length === 0)
|
|
28130
28290
|
continue;
|
|
28131
|
-
const dir =
|
|
28291
|
+
const dir = join8(r, ".switchroom");
|
|
28132
28292
|
if (!out.includes(dir))
|
|
28133
28293
|
out.push(dir);
|
|
28134
28294
|
}
|
|
@@ -28137,7 +28297,7 @@ function operatorHomeCandidates(env, home2) {
|
|
|
28137
28297
|
function resolveOperatorUidFromOwnership(deps = {}) {
|
|
28138
28298
|
const env = deps.env ?? process.env;
|
|
28139
28299
|
const home2 = deps.home ?? homedir6();
|
|
28140
|
-
const exists = deps.exists ?? ((p) =>
|
|
28300
|
+
const exists = deps.exists ?? ((p) => existsSync13(p));
|
|
28141
28301
|
const statUid = deps.statUid ?? ((p) => {
|
|
28142
28302
|
try {
|
|
28143
28303
|
return statSync7(p).uid;
|
|
@@ -28146,7 +28306,7 @@ function resolveOperatorUidFromOwnership(deps = {}) {
|
|
|
28146
28306
|
}
|
|
28147
28307
|
});
|
|
28148
28308
|
for (const dir of operatorHomeCandidates(env, home2)) {
|
|
28149
|
-
if (!exists(
|
|
28309
|
+
if (!exists(join8(dir, SWITCHROOM_HOME_MARKER)))
|
|
28150
28310
|
continue;
|
|
28151
28311
|
const uid = statUid(dir);
|
|
28152
28312
|
if (uid !== undefined && Number.isFinite(uid) && uid > 0)
|
|
@@ -28179,15 +28339,15 @@ function resolveOperatorUid(ownershipDeps = {}) {
|
|
|
28179
28339
|
init_atomic();
|
|
28180
28340
|
|
|
28181
28341
|
// src/cli/resolve-version.ts
|
|
28182
|
-
import { existsSync as
|
|
28183
|
-
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";
|
|
28184
28344
|
function readPackageVersion() {
|
|
28185
28345
|
let dir = import.meta.dirname;
|
|
28186
28346
|
for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
|
|
28187
|
-
const p =
|
|
28188
|
-
if (
|
|
28347
|
+
const p = join9(dir, "package.json");
|
|
28348
|
+
if (existsSync14(p)) {
|
|
28189
28349
|
try {
|
|
28190
|
-
const pkg = JSON.parse(
|
|
28350
|
+
const pkg = JSON.parse(readFileSync10(p, "utf-8"));
|
|
28191
28351
|
if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
|
|
28192
28352
|
return pkg.version;
|
|
28193
28353
|
}
|
|
@@ -28588,7 +28748,7 @@ var import__2 = __toESM(require_commander(), 1);
|
|
|
28588
28748
|
|
|
28589
28749
|
// src/cli/update.ts
|
|
28590
28750
|
init_loader();
|
|
28591
|
-
import { join as
|
|
28751
|
+
import { join as join10, dirname as dirname8 } from "node:path";
|
|
28592
28752
|
import { homedir as homedir7 } from "node:os";
|
|
28593
28753
|
|
|
28594
28754
|
// src/agents/lifecycle.ts
|
|
@@ -28691,7 +28851,12 @@ function getAllAgentStatuses(config) {
|
|
|
28691
28851
|
|
|
28692
28852
|
// src/cli/update.ts
|
|
28693
28853
|
init_atomic();
|
|
28694
|
-
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
|
+
]);
|
|
28695
28860
|
var UPDATE_RESULT_SENTINEL = "SWITCHROOM_UPDATE_RESULT:";
|
|
28696
28861
|
function parseUpdateResultLine(stdout) {
|
|
28697
28862
|
const idx = stdout.lastIndexOf(UPDATE_RESULT_SENTINEL);
|
|
@@ -28777,9 +28942,9 @@ function resolvePriorPinFromFleet(components, target, scope) {
|
|
|
28777
28942
|
// src/host-control/config-edit-validator.ts
|
|
28778
28943
|
init_schema();
|
|
28779
28944
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
28780
|
-
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";
|
|
28781
28946
|
import { tmpdir } from "node:os";
|
|
28782
|
-
import { join as
|
|
28947
|
+
import { join as join11, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
|
|
28783
28948
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
28784
28949
|
import { isDeepStrictEqual } from "node:util";
|
|
28785
28950
|
var MAX_PATCH_BYTES = 1024 * 1024;
|
|
@@ -28792,12 +28957,12 @@ function isTargetPathHeader(headerPath, targetBasename) {
|
|
|
28792
28957
|
let p = headerPath;
|
|
28793
28958
|
if (p.startsWith("a/") || p.startsWith("b/"))
|
|
28794
28959
|
p = p.slice(2);
|
|
28795
|
-
if (
|
|
28960
|
+
if (isAbsolute2(p))
|
|
28796
28961
|
return false;
|
|
28797
28962
|
if (p.includes(".."))
|
|
28798
28963
|
return false;
|
|
28799
28964
|
const norm = normalize(p);
|
|
28800
|
-
if (norm.includes("..") ||
|
|
28965
|
+
if (norm.includes("..") || isAbsolute2(norm))
|
|
28801
28966
|
return false;
|
|
28802
28967
|
return norm === targetBasename || basename4(norm) === targetBasename;
|
|
28803
28968
|
}
|
|
@@ -28919,21 +29084,21 @@ function validateShape(unifiedDiff, targetPath) {
|
|
|
28919
29084
|
return null;
|
|
28920
29085
|
}
|
|
28921
29086
|
function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
28922
|
-
if (!
|
|
29087
|
+
if (!existsSync15(configPath)) {
|
|
28923
29088
|
return {
|
|
28924
29089
|
ok: false,
|
|
28925
29090
|
code: "E_PATCH_APPLY_FAILED",
|
|
28926
29091
|
detail: `target config not found at ${configPath}`
|
|
28927
29092
|
};
|
|
28928
29093
|
}
|
|
28929
|
-
const liveContent =
|
|
28930
|
-
const scratchDir = mkdtempSync(
|
|
29094
|
+
const liveContent = readFileSync11(configPath, "utf8");
|
|
29095
|
+
const scratchDir = mkdtempSync(join11(tmpdir(), "config-propose-edit-"));
|
|
28931
29096
|
try {
|
|
28932
29097
|
const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
|
|
28933
|
-
const scratchFile =
|
|
29098
|
+
const scratchFile = join11(scratchDir, targetBasename);
|
|
28934
29099
|
writeFileSync5(scratchFile, liveContent);
|
|
28935
29100
|
const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
|
|
28936
|
-
const patchFile =
|
|
29101
|
+
const patchFile = join11(scratchDir, "proposal.patch");
|
|
28937
29102
|
writeFileSync5(patchFile, normalizedDiff);
|
|
28938
29103
|
const bin = gitBin ?? "git";
|
|
28939
29104
|
const baseArgs = [
|
|
@@ -28963,7 +29128,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
|
|
|
28963
29128
|
detail: `git apply failed: ${(real.stderr || "").trim().slice(0, 500)}`
|
|
28964
29129
|
};
|
|
28965
29130
|
}
|
|
28966
|
-
const after =
|
|
29131
|
+
const after = readFileSync11(scratchFile, "utf8");
|
|
28967
29132
|
return { ok: true, after };
|
|
28968
29133
|
} finally {
|
|
28969
29134
|
rmSync2(scratchDir, { recursive: true, force: true });
|
|
@@ -29161,7 +29326,7 @@ function validateConfigEdit(opts) {
|
|
|
29161
29326
|
return schemaErr;
|
|
29162
29327
|
let beforeData = {};
|
|
29163
29328
|
try {
|
|
29164
|
-
const beforeRaw =
|
|
29329
|
+
const beforeRaw = existsSync15(opts.configPath) ? readFileSync11(opts.configPath, "utf8") : "";
|
|
29165
29330
|
const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
|
|
29166
29331
|
beforeData = beforeDoc.toJS();
|
|
29167
29332
|
} catch {
|
|
@@ -29543,6 +29708,17 @@ function deferredLines(target) {
|
|
|
29543
29708
|
`- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``
|
|
29544
29709
|
];
|
|
29545
29710
|
}
|
|
29711
|
+
function warningLines(warnings) {
|
|
29712
|
+
if (!warnings || warnings.length === 0)
|
|
29713
|
+
return [];
|
|
29714
|
+
const MAX_SHOWN = 8;
|
|
29715
|
+
const shown = warnings.slice(0, MAX_SHOWN);
|
|
29716
|
+
const lines = ["**Warnings:**", ...shown.map((w) => `- ⚠️ ${w}`)];
|
|
29717
|
+
if (warnings.length > MAX_SHOWN) {
|
|
29718
|
+
lines.push(`- …and ${warnings.length - MAX_SHOWN} more (see \`get_status\`).`);
|
|
29719
|
+
}
|
|
29720
|
+
return lines;
|
|
29721
|
+
}
|
|
29546
29722
|
var MAX_RENDER_CHARS = 3800;
|
|
29547
29723
|
function rolledList(rolled, compact) {
|
|
29548
29724
|
if (rolled.length === 0)
|
|
@@ -29556,6 +29732,7 @@ function renderWith(s, compact) {
|
|
|
29556
29732
|
const rolledCount = rolled.length;
|
|
29557
29733
|
const checklist = checklistLines(s, compact);
|
|
29558
29734
|
const singletons = singletonLines(s);
|
|
29735
|
+
const warnings = warningLines(s.warnings);
|
|
29559
29736
|
const elapsedMs = s.elapsedMs ?? (s.startedAtMs !== undefined && s.nowMs !== undefined ? s.nowMs - s.startedAtMs : undefined);
|
|
29560
29737
|
if (s.terminal === "completed") {
|
|
29561
29738
|
const parts2 = [headerLine(s, "✅")];
|
|
@@ -29568,6 +29745,8 @@ function renderWith(s, compact) {
|
|
|
29568
29745
|
parts2.push("", ...checklist);
|
|
29569
29746
|
if (singletons.length > 0)
|
|
29570
29747
|
parts2.push("", ...singletons);
|
|
29748
|
+
if (warnings.length > 0)
|
|
29749
|
+
parts2.push("", ...warnings);
|
|
29571
29750
|
if (s.deferred !== false)
|
|
29572
29751
|
parts2.push("", ...deferredLines(s.target));
|
|
29573
29752
|
return parts2.join(`
|
|
@@ -29587,6 +29766,8 @@ function renderWith(s, compact) {
|
|
|
29587
29766
|
parts2.push("", ...checklist);
|
|
29588
29767
|
if (singletons.length > 0)
|
|
29589
29768
|
parts2.push("", ...singletons);
|
|
29769
|
+
if (warnings.length > 0)
|
|
29770
|
+
parts2.push("", ...warnings);
|
|
29590
29771
|
return parts2.join(`
|
|
29591
29772
|
`);
|
|
29592
29773
|
}
|
|
@@ -29602,6 +29783,8 @@ function renderWith(s, compact) {
|
|
|
29602
29783
|
parts2.push("", ...checklist);
|
|
29603
29784
|
if (singletons.length > 0)
|
|
29604
29785
|
parts2.push("", ...singletons);
|
|
29786
|
+
if (warnings.length > 0)
|
|
29787
|
+
parts2.push("", ...warnings);
|
|
29605
29788
|
return parts2.join(`
|
|
29606
29789
|
`);
|
|
29607
29790
|
}
|
|
@@ -29727,11 +29910,11 @@ function resolveDigests(imageRefs) {
|
|
|
29727
29910
|
return out;
|
|
29728
29911
|
}
|
|
29729
29912
|
function readCachedInstallType(bindRoot) {
|
|
29730
|
-
const cacheDir =
|
|
29731
|
-
const cachePath =
|
|
29732
|
-
if (
|
|
29913
|
+
const cacheDir = join12(bindRoot, ".switchroom");
|
|
29914
|
+
const cachePath = join12(cacheDir, "install-type.json");
|
|
29915
|
+
if (existsSync16(cachePath)) {
|
|
29733
29916
|
try {
|
|
29734
|
-
const raw =
|
|
29917
|
+
const raw = readFileSync12(cachePath, "utf-8");
|
|
29735
29918
|
const parsed = JSON.parse(raw);
|
|
29736
29919
|
if (parsed && typeof parsed.install_type === "string" && typeof parsed.detected_at === "string") {
|
|
29737
29920
|
return parsed;
|
|
@@ -29783,7 +29966,7 @@ function writeFileInPlacePreservingInode(targetPath, content) {
|
|
|
29783
29966
|
} finally {
|
|
29784
29967
|
closeSync5(fd);
|
|
29785
29968
|
}
|
|
29786
|
-
const readBack =
|
|
29969
|
+
const readBack = readFileSync12(targetPath);
|
|
29787
29970
|
if (readBack.length !== buf.length) {
|
|
29788
29971
|
throw new Error(`in-place write short: wrote ${buf.length} bytes but read back ${readBack.length}`);
|
|
29789
29972
|
}
|
|
@@ -29818,11 +30001,11 @@ class HostdServer {
|
|
|
29818
30001
|
fleetMutationInFlight = null;
|
|
29819
30002
|
lastAutoRolloutFailedPin = null;
|
|
29820
30003
|
autoRolloutLatchPath() {
|
|
29821
|
-
return
|
|
30004
|
+
return join12(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
|
|
29822
30005
|
}
|
|
29823
30006
|
readAutoRolloutLatch() {
|
|
29824
30007
|
try {
|
|
29825
|
-
const raw =
|
|
30008
|
+
const raw = readFileSync12(this.autoRolloutLatchPath(), "utf-8");
|
|
29826
30009
|
const parsed = JSON.parse(raw);
|
|
29827
30010
|
if (parsed && parsed.v === 1 && typeof parsed.pin === "string" && parsed.pin.length > 0 && (parsed.outcome === "attempting" || parsed.outcome === "failed")) {
|
|
29828
30011
|
return parsed;
|
|
@@ -29861,7 +30044,7 @@ class HostdServer {
|
|
|
29861
30044
|
return resolveHostdConfigPath(this.opts.configPath);
|
|
29862
30045
|
}
|
|
29863
30046
|
fleetComposePath() {
|
|
29864
|
-
return
|
|
30047
|
+
return join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
29865
30048
|
}
|
|
29866
30049
|
recoverRolloutPinJournal(context) {
|
|
29867
30050
|
let note = null;
|
|
@@ -29900,7 +30083,7 @@ class HostdServer {
|
|
|
29900
30083
|
return note;
|
|
29901
30084
|
}
|
|
29902
30085
|
async start() {
|
|
29903
|
-
const hostdDir =
|
|
30086
|
+
const hostdDir = join12(this.opts.homeDir, ".switchroom", "hostd");
|
|
29904
30087
|
await mkdir2(hostdDir, { recursive: true });
|
|
29905
30088
|
await chmod(hostdDir, 493).catch(() => {
|
|
29906
30089
|
return;
|
|
@@ -29921,13 +30104,13 @@ class HostdServer {
|
|
|
29921
30104
|
}
|
|
29922
30105
|
try {
|
|
29923
30106
|
for (const name of agentNames) {
|
|
29924
|
-
const dir =
|
|
29925
|
-
const sockPath =
|
|
30107
|
+
const dir = join12(hostdDir, name);
|
|
30108
|
+
const sockPath = join12(dir, "sock");
|
|
29926
30109
|
await mkdir2(dir, { recursive: true });
|
|
29927
30110
|
await chmod(dir, 493).catch(() => {
|
|
29928
30111
|
return;
|
|
29929
30112
|
});
|
|
29930
|
-
if (
|
|
30113
|
+
if (existsSync16(sockPath))
|
|
29931
30114
|
await unlink(sockPath).catch(() => {
|
|
29932
30115
|
return;
|
|
29933
30116
|
});
|
|
@@ -29958,13 +30141,13 @@ class HostdServer {
|
|
|
29958
30141
|
process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
|
|
29959
30142
|
`);
|
|
29960
30143
|
} else {
|
|
29961
|
-
const dir =
|
|
29962
|
-
const sockPath =
|
|
30144
|
+
const dir = join12(hostdDir, "operator");
|
|
30145
|
+
const sockPath = join12(dir, "sock");
|
|
29963
30146
|
await mkdir2(dir, { recursive: true });
|
|
29964
30147
|
await chmod(dir, 493).catch(() => {
|
|
29965
30148
|
return;
|
|
29966
30149
|
});
|
|
29967
|
-
if (
|
|
30150
|
+
if (existsSync16(sockPath))
|
|
29968
30151
|
await unlink(sockPath).catch(() => {
|
|
29969
30152
|
return;
|
|
29970
30153
|
});
|
|
@@ -30000,11 +30183,11 @@ class HostdServer {
|
|
|
30000
30183
|
}
|
|
30001
30184
|
async reconcileOrphanedFleetMutations() {
|
|
30002
30185
|
const path3 = this.auditLogPath();
|
|
30003
|
-
if (!
|
|
30186
|
+
if (!existsSync16(path3))
|
|
30004
30187
|
return;
|
|
30005
30188
|
let raw;
|
|
30006
30189
|
try {
|
|
30007
|
-
raw =
|
|
30190
|
+
raw = readFileSync12(path3, "utf-8");
|
|
30008
30191
|
} catch {
|
|
30009
30192
|
return;
|
|
30010
30193
|
}
|
|
@@ -30403,11 +30586,11 @@ class HostdServer {
|
|
|
30403
30586
|
missingApplyAssets() {
|
|
30404
30587
|
const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
|
|
30405
30588
|
return [
|
|
30406
|
-
|
|
30407
|
-
|
|
30408
|
-
|
|
30409
|
-
|
|
30410
|
-
].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));
|
|
30411
30594
|
}
|
|
30412
30595
|
applyAssetPreflight(request_id, started) {
|
|
30413
30596
|
const missing = this.missingApplyAssets();
|
|
@@ -30665,19 +30848,19 @@ class HostdServer {
|
|
|
30665
30848
|
return entry;
|
|
30666
30849
|
}
|
|
30667
30850
|
hostdDirPath() {
|
|
30668
|
-
return
|
|
30851
|
+
return join12(this.opts.homeDir, ".switchroom", "hostd");
|
|
30669
30852
|
}
|
|
30670
30853
|
hostdDirHostPath() {
|
|
30671
30854
|
const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
|
|
30672
|
-
return
|
|
30855
|
+
return join12(hostHome, ".switchroom", "hostd");
|
|
30673
30856
|
}
|
|
30674
30857
|
async beginSelfBump(req, caller, started) {
|
|
30675
30858
|
const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
|
|
30676
|
-
const composePath =
|
|
30677
|
-
if (!
|
|
30859
|
+
const composePath = join12(this.hostdDirPath(), "docker-compose.yml");
|
|
30860
|
+
if (!existsSync16(composePath)) {
|
|
30678
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);
|
|
30679
30862
|
}
|
|
30680
|
-
const before =
|
|
30863
|
+
const before = readFileSync12(composePath, "utf8");
|
|
30681
30864
|
const bumped = bumpHostdComposeImageTag(before, req.args.pin);
|
|
30682
30865
|
if (!bumped) {
|
|
30683
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);
|
|
@@ -30698,10 +30881,10 @@ class HostdServer {
|
|
|
30698
30881
|
try {
|
|
30699
30882
|
const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
|
|
30700
30883
|
for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
|
|
30701
|
-
unlinkSync5(
|
|
30884
|
+
unlinkSync5(join12(this.hostdDirPath(), f));
|
|
30702
30885
|
}
|
|
30703
30886
|
} catch {}
|
|
30704
|
-
const markerPath =
|
|
30887
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30705
30888
|
const marker = {
|
|
30706
30889
|
v: 1,
|
|
30707
30890
|
request_id: req.request_id,
|
|
@@ -30785,7 +30968,7 @@ class HostdServer {
|
|
|
30785
30968
|
rollbackBump();
|
|
30786
30969
|
entry.result = "error";
|
|
30787
30970
|
entry.finished_at = Date.now();
|
|
30788
|
-
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.`;
|
|
30789
30972
|
if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
|
|
30790
30973
|
this.fleetMutationInFlight = null;
|
|
30791
30974
|
}
|
|
@@ -30812,10 +30995,10 @@ class HostdServer {
|
|
|
30812
30995
|
try {
|
|
30813
30996
|
if (!Number.isInteger(messageId) || messageId <= 0)
|
|
30814
30997
|
return;
|
|
30815
|
-
const markerPath =
|
|
30816
|
-
if (!
|
|
30998
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
30999
|
+
if (!existsSync16(markerPath))
|
|
30817
31000
|
return;
|
|
30818
|
-
const marker = parsePendingRolloutMarker(
|
|
31001
|
+
const marker = parsePendingRolloutMarker(readFileSync12(markerPath, "utf8"));
|
|
30819
31002
|
if (!marker)
|
|
30820
31003
|
return;
|
|
30821
31004
|
if (marker.request_id !== requestId)
|
|
@@ -30830,12 +31013,12 @@ class HostdServer {
|
|
|
30830
31013
|
} catch {}
|
|
30831
31014
|
}
|
|
30832
31015
|
async resumePendingSelfBumpRollout() {
|
|
30833
|
-
const markerPath =
|
|
30834
|
-
if (!
|
|
31016
|
+
const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
|
|
31017
|
+
if (!existsSync16(markerPath))
|
|
30835
31018
|
return;
|
|
30836
31019
|
let raw;
|
|
30837
31020
|
try {
|
|
30838
|
-
raw =
|
|
31021
|
+
raw = readFileSync12(markerPath, "utf8");
|
|
30839
31022
|
} finally {
|
|
30840
31023
|
try {
|
|
30841
31024
|
unlinkSync5(markerPath);
|
|
@@ -30886,7 +31069,7 @@ class HostdServer {
|
|
|
30886
31069
|
return;
|
|
30887
31070
|
}
|
|
30888
31071
|
if (needsSelfBump(ownVersion, marker.pin)) {
|
|
30889
|
-
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.`);
|
|
30890
31073
|
return;
|
|
30891
31074
|
}
|
|
30892
31075
|
process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
|
|
@@ -30994,7 +31177,7 @@ class HostdServer {
|
|
|
30994
31177
|
}
|
|
30995
31178
|
let beforeContent;
|
|
30996
31179
|
try {
|
|
30997
|
-
beforeContent =
|
|
31180
|
+
beforeContent = readFileSync12(configPath, "utf-8");
|
|
30998
31181
|
} catch {
|
|
30999
31182
|
beforeContent = "";
|
|
31000
31183
|
}
|
|
@@ -31114,7 +31297,7 @@ class HostdServer {
|
|
|
31114
31297
|
try {
|
|
31115
31298
|
let snapshot;
|
|
31116
31299
|
try {
|
|
31117
|
-
snapshot =
|
|
31300
|
+
snapshot = readFileSync12(configPath, "utf-8");
|
|
31118
31301
|
} catch (e) {
|
|
31119
31302
|
await approval.finalize({
|
|
31120
31303
|
outcome: "reconcile_failed_rolled_back",
|
|
@@ -31412,8 +31595,8 @@ ${output.recovery.stderr}` : "";
|
|
|
31412
31595
|
if (this.opts.imageRefsForDigests)
|
|
31413
31596
|
return this.opts.imageRefsForDigests();
|
|
31414
31597
|
try {
|
|
31415
|
-
const composePath =
|
|
31416
|
-
if (!
|
|
31598
|
+
const composePath = join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
|
|
31599
|
+
if (!existsSync16(composePath))
|
|
31417
31600
|
return [];
|
|
31418
31601
|
const r = spawnSync4("docker", [
|
|
31419
31602
|
"compose",
|
|
@@ -31487,6 +31670,9 @@ ${output.recovery.stderr}` : "";
|
|
|
31487
31670
|
if (parsed.drifted && parsed.drifted.length > 0) {
|
|
31488
31671
|
entry.drifted = parsed.drifted;
|
|
31489
31672
|
}
|
|
31673
|
+
if (parsed.warnings && parsed.warnings.length > 0) {
|
|
31674
|
+
entry.warnings = parsed.warnings;
|
|
31675
|
+
}
|
|
31490
31676
|
if (parsed.got !== undefined)
|
|
31491
31677
|
entry.got = parsed.got;
|
|
31492
31678
|
if (parsed.timedOut)
|
|
@@ -31629,7 +31815,8 @@ ${pinNote}`.trim();
|
|
|
31629
31815
|
...entry.failed_step ? { failedStep: entry.failed_step } : {},
|
|
31630
31816
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
31631
31817
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31632
|
-
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {}
|
|
31818
|
+
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
31819
|
+
...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {}
|
|
31633
31820
|
});
|
|
31634
31821
|
this.opts.rolloutRelay.postTerminal({
|
|
31635
31822
|
requestId: entry.request_id,
|
|
@@ -31667,10 +31854,10 @@ ${pinNote}`.trim();
|
|
|
31667
31854
|
}
|
|
31668
31855
|
rolloutRowExistsInLog(targetRequestId) {
|
|
31669
31856
|
const path3 = this.auditLogPath();
|
|
31670
|
-
if (!
|
|
31857
|
+
if (!existsSync16(path3))
|
|
31671
31858
|
return false;
|
|
31672
31859
|
try {
|
|
31673
|
-
const raw =
|
|
31860
|
+
const raw = readFileSync12(path3, "utf-8");
|
|
31674
31861
|
return latestRolloutRowForRequest(raw, targetRequestId) !== null;
|
|
31675
31862
|
} catch {
|
|
31676
31863
|
return false;
|
|
@@ -31678,11 +31865,11 @@ ${pinNote}`.trim();
|
|
|
31678
31865
|
}
|
|
31679
31866
|
rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
|
|
31680
31867
|
const path3 = this.auditLogPath();
|
|
31681
|
-
if (!
|
|
31868
|
+
if (!existsSync16(path3))
|
|
31682
31869
|
return null;
|
|
31683
31870
|
let raw;
|
|
31684
31871
|
try {
|
|
31685
|
-
raw =
|
|
31872
|
+
raw = readFileSync12(path3, "utf-8");
|
|
31686
31873
|
} catch {
|
|
31687
31874
|
return null;
|
|
31688
31875
|
}
|
|
@@ -31712,7 +31899,7 @@ ${pinNote}`.trim();
|
|
|
31712
31899
|
};
|
|
31713
31900
|
}
|
|
31714
31901
|
statusEntryToResponse(request_id, entry) {
|
|
31715
|
-
const rolloutPayload = entry.op === "rollout" && (entry.rolled !== undefined || entry.failed_step !== undefined || entry.failed_agent !== undefined || entry.current_phase !== undefined) ? JSON.stringify({
|
|
31902
|
+
const rolloutPayload = entry.op === "rollout" && (entry.rolled !== undefined || entry.failed_step !== undefined || entry.failed_agent !== undefined || entry.warnings !== undefined && entry.warnings.length > 0 || entry.current_phase !== undefined) ? JSON.stringify({
|
|
31716
31903
|
rolled: entry.rolled ?? [],
|
|
31717
31904
|
...entry.current_phase ? { phase: entry.current_phase } : {},
|
|
31718
31905
|
...entry.phase_n !== undefined ? { n: entry.phase_n } : {},
|
|
@@ -31722,6 +31909,7 @@ ${pinNote}`.trim();
|
|
|
31722
31909
|
...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
|
|
31723
31910
|
...entry.got !== undefined ? { got: entry.got } : {},
|
|
31724
31911
|
...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
|
|
31912
|
+
...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {},
|
|
31725
31913
|
...entry.pin ? { pin: entry.pin } : {},
|
|
31726
31914
|
...entry.prior_pin ? { prior_pin: entry.prior_pin } : {}
|
|
31727
31915
|
}) : undefined;
|
|
@@ -31763,7 +31951,7 @@ ${pinNote}`.trim();
|
|
|
31763
31951
|
}
|
|
31764
31952
|
}
|
|
31765
31953
|
auditLogPath() {
|
|
31766
|
-
return this.opts.auditLogPath ??
|
|
31954
|
+
return this.opts.auditLogPath ?? join12(this.opts.homeDir, ".switchroom", "host-control-audit.log");
|
|
31767
31955
|
}
|
|
31768
31956
|
maybeRotateAuditLog(path3) {
|
|
31769
31957
|
const { maxBytes, maxFiles } = resolveRotationConfig({
|
|
@@ -32285,6 +32473,7 @@ class SocketRolloutRelay {
|
|
|
32285
32473
|
// src/host-control/rollout-narration-relay.ts
|
|
32286
32474
|
import { connect as connect4 } from "node:net";
|
|
32287
32475
|
var DEFAULT_POST_REPLY_TIMEOUT_MS = 5000;
|
|
32476
|
+
var DEFAULT_EDIT_REPLY_TIMEOUT_MS = 5000;
|
|
32288
32477
|
|
|
32289
32478
|
class SocketRolloutNarrationRelay {
|
|
32290
32479
|
opts;
|
|
@@ -32382,18 +32571,40 @@ class SocketRolloutNarrationRelay {
|
|
|
32382
32571
|
sockPath = this.opts.resolveGatewaySocket(args.agentName);
|
|
32383
32572
|
} catch (e) {
|
|
32384
32573
|
log(`narration edit resolveGatewaySocket threw: ${e.message}`);
|
|
32385
|
-
return
|
|
32574
|
+
return Promise.resolve({
|
|
32575
|
+
ok: false,
|
|
32576
|
+
gone: false,
|
|
32577
|
+
reason: `resolveGatewaySocket threw: ${e.message}`
|
|
32578
|
+
});
|
|
32386
32579
|
}
|
|
32387
|
-
if (sockPath === null)
|
|
32388
|
-
return
|
|
32389
|
-
|
|
32390
|
-
|
|
32391
|
-
|
|
32392
|
-
|
|
32580
|
+
if (sockPath === null) {
|
|
32581
|
+
return Promise.resolve({
|
|
32582
|
+
ok: false,
|
|
32583
|
+
gone: false,
|
|
32584
|
+
reason: "no reachable gateway"
|
|
32585
|
+
});
|
|
32586
|
+
}
|
|
32587
|
+
return new Promise((resolve10) => {
|
|
32588
|
+
let settled = false;
|
|
32589
|
+
let client2;
|
|
32590
|
+
const finish = (outcome) => {
|
|
32591
|
+
if (settled)
|
|
32592
|
+
return;
|
|
32593
|
+
settled = true;
|
|
32393
32594
|
try {
|
|
32394
32595
|
client2.destroy();
|
|
32395
32596
|
} catch {}
|
|
32597
|
+
resolve10(outcome);
|
|
32396
32598
|
};
|
|
32599
|
+
try {
|
|
32600
|
+
client2 = connect4({ path: sockPath });
|
|
32601
|
+
} catch (e) {
|
|
32602
|
+
log(`narration edit connect threw: ${e.message}`);
|
|
32603
|
+
resolve10({ ok: false, gone: false, reason: e.message });
|
|
32604
|
+
return;
|
|
32605
|
+
}
|
|
32606
|
+
client2.setTimeout(this.opts.editReplyTimeoutMs ?? DEFAULT_EDIT_REPLY_TIMEOUT_MS);
|
|
32607
|
+
let buffer = "";
|
|
32397
32608
|
client2.on("connect", () => {
|
|
32398
32609
|
try {
|
|
32399
32610
|
client2.write(JSON.stringify({
|
|
@@ -32404,21 +32615,46 @@ class SocketRolloutNarrationRelay {
|
|
|
32404
32615
|
text: args.text
|
|
32405
32616
|
}) + `
|
|
32406
32617
|
`);
|
|
32407
|
-
client2.end();
|
|
32408
32618
|
} catch (e) {
|
|
32409
32619
|
log(`narration edit write failed: ${e.message}`);
|
|
32410
|
-
|
|
32620
|
+
finish({ ok: false, gone: false, reason: e.message });
|
|
32411
32621
|
}
|
|
32412
32622
|
});
|
|
32413
|
-
client2.on("
|
|
32623
|
+
client2.on("data", (chunk2) => {
|
|
32624
|
+
buffer += chunk2.toString("utf8");
|
|
32625
|
+
const lines = buffer.split(`
|
|
32626
|
+
`);
|
|
32627
|
+
buffer = lines.pop() ?? "";
|
|
32628
|
+
for (const line of lines) {
|
|
32629
|
+
if (!line.trim())
|
|
32630
|
+
continue;
|
|
32631
|
+
let obj;
|
|
32632
|
+
try {
|
|
32633
|
+
obj = JSON.parse(line);
|
|
32634
|
+
} catch {
|
|
32635
|
+
continue;
|
|
32636
|
+
}
|
|
32637
|
+
if (obj.type === "rollout_status_edited" && obj.requestId === args.requestId) {
|
|
32638
|
+
if (obj.ok === true) {
|
|
32639
|
+
finish({ ok: true });
|
|
32640
|
+
} else {
|
|
32641
|
+
finish({
|
|
32642
|
+
ok: false,
|
|
32643
|
+
gone: obj.gone === true,
|
|
32644
|
+
...typeof obj.reason === "string" ? { reason: obj.reason } : {}
|
|
32645
|
+
});
|
|
32646
|
+
}
|
|
32647
|
+
return;
|
|
32648
|
+
}
|
|
32649
|
+
}
|
|
32650
|
+
});
|
|
32651
|
+
client2.on("timeout", () => finish({ ok: false, gone: false, reason: "edit reply timed out" }));
|
|
32414
32652
|
client2.on("error", (e) => {
|
|
32415
32653
|
log(`narration edit socket error: ${e.message}`);
|
|
32416
|
-
|
|
32654
|
+
finish({ ok: false, gone: false, reason: e.message });
|
|
32417
32655
|
});
|
|
32418
|
-
client2.on("close", () => {});
|
|
32419
|
-
}
|
|
32420
|
-
log(`narration edit connect threw: ${e.message}`);
|
|
32421
|
-
}
|
|
32656
|
+
client2.on("close", () => finish({ ok: false, gone: false, reason: "no edit reply" }));
|
|
32657
|
+
});
|
|
32422
32658
|
}
|
|
32423
32659
|
}
|
|
32424
32660
|
|
|
@@ -32731,9 +32967,62 @@ class LogTailRolloutNarrator {
|
|
|
32731
32967
|
singletons: initialSingletons(),
|
|
32732
32968
|
frozen: false,
|
|
32733
32969
|
timer: null,
|
|
32734
|
-
pendingEditAfterPost: false
|
|
32970
|
+
pendingEditAfterPost: false,
|
|
32971
|
+
goneRepostUsed: false,
|
|
32972
|
+
goneRepostForMessageId: null
|
|
32735
32973
|
};
|
|
32736
32974
|
}
|
|
32975
|
+
escalateCard(esc) {
|
|
32976
|
+
try {
|
|
32977
|
+
if (this.opts.escalate) {
|
|
32978
|
+
this.opts.escalate(esc);
|
|
32979
|
+
return;
|
|
32980
|
+
}
|
|
32981
|
+
this.opts.log?.(`rollout card escalation request=${esc.requestId} agent=${esc.agentName} ` + `staleMessageId=${esc.staleMessageId} reason=${esc.reason}` + `${esc.detail !== undefined ? ` detail=${esc.detail}` : ""} ` + `— card could not be restored, no operator card issued (get_status remains the record)`);
|
|
32982
|
+
} catch {}
|
|
32983
|
+
}
|
|
32984
|
+
onEditOutcome(requestId, editedMessageId, outcome) {
|
|
32985
|
+
if (outcome.ok)
|
|
32986
|
+
return;
|
|
32987
|
+
const st = this.states.get(requestId);
|
|
32988
|
+
if (!st)
|
|
32989
|
+
return;
|
|
32990
|
+
if (!outcome.gone) {
|
|
32991
|
+
this.opts.log?.(`rollout narration edit failed (transient, requestId=${requestId}): ${outcome.reason ?? "unknown"}`);
|
|
32992
|
+
return;
|
|
32993
|
+
}
|
|
32994
|
+
if (st.messageId !== editedMessageId)
|
|
32995
|
+
return;
|
|
32996
|
+
if (st.goneRepostUsed) {
|
|
32997
|
+
this.escalateCard({
|
|
32998
|
+
requestId,
|
|
32999
|
+
agentName: st.agentName,
|
|
33000
|
+
staleMessageId: editedMessageId,
|
|
33001
|
+
reason: "gone-again",
|
|
33002
|
+
detail: outcome.reason
|
|
33003
|
+
});
|
|
33004
|
+
return;
|
|
33005
|
+
}
|
|
33006
|
+
st.goneRepostUsed = true;
|
|
33007
|
+
st.messageId = null;
|
|
33008
|
+
st.postFailed = false;
|
|
33009
|
+
st.goneRepostForMessageId = editedMessageId;
|
|
33010
|
+
if (st.timer) {
|
|
33011
|
+
clearTimeout(st.timer);
|
|
33012
|
+
st.timer = null;
|
|
33013
|
+
}
|
|
33014
|
+
this.opts.log?.(`rollout narration card ${editedMessageId} is gone (requestId=${requestId}); re-posting once`);
|
|
33015
|
+
if (!this.tryPost(requestId, true)) {
|
|
33016
|
+
st.goneRepostForMessageId = null;
|
|
33017
|
+
this.escalateCard({
|
|
33018
|
+
requestId,
|
|
33019
|
+
agentName: st.agentName,
|
|
33020
|
+
staleMessageId: editedMessageId,
|
|
33021
|
+
reason: "repost-unavailable",
|
|
33022
|
+
detail: "post-attempt cap reached"
|
|
33023
|
+
});
|
|
33024
|
+
}
|
|
33025
|
+
}
|
|
32737
33026
|
ensureState(entry) {
|
|
32738
33027
|
let st = this.states.get(entry.request_id);
|
|
32739
33028
|
if (!st) {
|
|
@@ -32797,10 +33086,22 @@ class LogTailRolloutNarrator {
|
|
|
32797
33086
|
if (mid === null) {
|
|
32798
33087
|
st.postFailed = true;
|
|
32799
33088
|
this.opts.log?.(`rollout narration post returned no message_id (requestId=${requestId}); backing off re-posts`);
|
|
33089
|
+
if (st.goneRepostForMessageId !== null) {
|
|
33090
|
+
const stale = st.goneRepostForMessageId;
|
|
33091
|
+
st.goneRepostForMessageId = null;
|
|
33092
|
+
this.escalateCard({
|
|
33093
|
+
requestId,
|
|
33094
|
+
agentName: st.agentName,
|
|
33095
|
+
staleMessageId: stale,
|
|
33096
|
+
reason: "repost-failed",
|
|
33097
|
+
detail: "post returned no message_id"
|
|
33098
|
+
});
|
|
33099
|
+
}
|
|
32800
33100
|
return;
|
|
32801
33101
|
}
|
|
32802
33102
|
st.messageId = mid;
|
|
32803
33103
|
st.postFailed = false;
|
|
33104
|
+
st.goneRepostForMessageId = null;
|
|
32804
33105
|
try {
|
|
32805
33106
|
this.opts.onMessageId?.(requestId, mid);
|
|
32806
33107
|
} catch (e) {
|
|
@@ -32814,6 +33115,17 @@ class LogTailRolloutNarrator {
|
|
|
32814
33115
|
st.posting = false;
|
|
32815
33116
|
st.postFailed = true;
|
|
32816
33117
|
this.opts.log?.(`rollout narration post failed (non-fatal): ${e.message}`);
|
|
33118
|
+
if (st.goneRepostForMessageId !== null) {
|
|
33119
|
+
const stale = st.goneRepostForMessageId;
|
|
33120
|
+
st.goneRepostForMessageId = null;
|
|
33121
|
+
this.escalateCard({
|
|
33122
|
+
requestId,
|
|
33123
|
+
agentName: st.agentName,
|
|
33124
|
+
staleMessageId: stale,
|
|
33125
|
+
reason: "repost-failed",
|
|
33126
|
+
detail: e.message
|
|
33127
|
+
});
|
|
33128
|
+
}
|
|
32817
33129
|
});
|
|
32818
33130
|
return true;
|
|
32819
33131
|
}
|
|
@@ -32849,11 +33161,14 @@ class LogTailRolloutNarrator {
|
|
|
32849
33161
|
return;
|
|
32850
33162
|
}
|
|
32851
33163
|
const text = renderRolloutStatus({ ...st.render, nowMs: this.now() });
|
|
33164
|
+
const editedMessageId = st.messageId;
|
|
32852
33165
|
this.relay.edit({
|
|
32853
33166
|
requestId,
|
|
32854
33167
|
agentName: st.agentName,
|
|
32855
|
-
messageId:
|
|
33168
|
+
messageId: editedMessageId,
|
|
32856
33169
|
text
|
|
33170
|
+
}).then((outcome) => this.onEditOutcome(requestId, editedMessageId, outcome)).catch((e) => {
|
|
33171
|
+
this.opts.log?.(`rollout narration edit relay rejected (non-fatal): ${e.message}`);
|
|
32857
33172
|
});
|
|
32858
33173
|
}
|
|
32859
33174
|
}
|
|
@@ -33120,7 +33435,7 @@ function makeRestart(switchroomBin) {
|
|
|
33120
33435
|
}
|
|
33121
33436
|
|
|
33122
33437
|
// src/host-control/update-notifier.ts
|
|
33123
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
33438
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
33124
33439
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
33125
33440
|
import { dirname as dirname10 } from "node:path";
|
|
33126
33441
|
var FINALIZE_DETAIL_MAX = 450;
|
|
@@ -33237,7 +33552,7 @@ class UpdateNotifier {
|
|
|
33237
33552
|
}
|
|
33238
33553
|
readState() {
|
|
33239
33554
|
try {
|
|
33240
|
-
const raw =
|
|
33555
|
+
const raw = readFileSync13(this.opts.statePath, "utf8");
|
|
33241
33556
|
const parsed = JSON.parse(raw);
|
|
33242
33557
|
if (parsed === null || typeof parsed !== "object")
|
|
33243
33558
|
return null;
|
|
@@ -33378,13 +33693,13 @@ async function loadConfigResilient() {
|
|
|
33378
33693
|
} catch {
|
|
33379
33694
|
configPath = undefined;
|
|
33380
33695
|
}
|
|
33381
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
33696
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
|
|
33382
33697
|
const listGatewayCandidates = () => {
|
|
33383
33698
|
const out = [];
|
|
33384
33699
|
try {
|
|
33385
33700
|
for (const name of readdirSync7(agentsDir).sort()) {
|
|
33386
33701
|
const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
|
|
33387
|
-
if (
|
|
33702
|
+
if (existsSync17(sock))
|
|
33388
33703
|
out.push({ agent: name, sock });
|
|
33389
33704
|
}
|
|
33390
33705
|
} catch {}
|
|
@@ -33431,10 +33746,10 @@ async function main() {
|
|
|
33431
33746
|
`);
|
|
33432
33747
|
process.exit(2);
|
|
33433
33748
|
}
|
|
33434
|
-
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ??
|
|
33749
|
+
const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
|
|
33435
33750
|
const resolveGatewaySocket = (agentName) => {
|
|
33436
33751
|
const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
|
|
33437
|
-
return
|
|
33752
|
+
return existsSync17(sock) ? sock : null;
|
|
33438
33753
|
};
|
|
33439
33754
|
const approvalGateway = new SocketApprovalGateway({
|
|
33440
33755
|
resolveGatewaySocket,
|
|
@@ -33453,7 +33768,9 @@ async function main() {
|
|
|
33453
33768
|
}), {
|
|
33454
33769
|
log: (m) => process.stderr.write(`hostd: rollout-narration — ${m}
|
|
33455
33770
|
`),
|
|
33456
|
-
onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid)
|
|
33771
|
+
onMessageId: (rid, mid) => server?.persistNarrationMessageId(rid, mid),
|
|
33772
|
+
escalate: (esc) => process.stderr.write(`hostd: rollout card escalation request=${esc.requestId} agent=${esc.agentName} ` + `staleMessageId=${esc.staleMessageId} reason=${esc.reason}` + `${esc.detail !== undefined ? ` detail=${esc.detail}` : ""} ` + `— card could not be restored, no operator card issued
|
|
33773
|
+
`)
|
|
33457
33774
|
});
|
|
33458
33775
|
const server = new HostdServer({
|
|
33459
33776
|
homeDir: homedir8(),
|
|
@@ -33486,7 +33803,7 @@ async function main() {
|
|
|
33486
33803
|
const autoRel = config.host_control?.auto_release_check;
|
|
33487
33804
|
const watcherMode = resolveReleaseWatcherMode(config);
|
|
33488
33805
|
if (watcherMode.mode !== "off") {
|
|
33489
|
-
const eventsLog =
|
|
33806
|
+
const eventsLog = join13(homedir8(), ".switchroom", "release-watcher-events.jsonl");
|
|
33490
33807
|
const log = (m) => {
|
|
33491
33808
|
process.stderr.write(`hostd: ${m}
|
|
33492
33809
|
`);
|
|
@@ -33545,7 +33862,7 @@ async function main() {
|
|
|
33545
33862
|
`);
|
|
33546
33863
|
} else
|
|
33547
33864
|
notifier = new UpdateNotifier({
|
|
33548
|
-
statePath:
|
|
33865
|
+
statePath: join13(homedir8(), ".switchroom", "release-notify-state.json"),
|
|
33549
33866
|
repostSuppressMs: cardTimeoutMs,
|
|
33550
33867
|
isFleetMutationInFlight: () => server.isFleetMutationLocked(),
|
|
33551
33868
|
startApply: (requestId) => {
|