switchroom 0.20.6 → 0.20.8
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 +106 -12
- package/dist/auth-broker/index.js +71 -9
- package/dist/cli/notion-write-pretool.mjs +68 -7
- package/dist/cli/switchroom.js +387 -30
- package/dist/host-control/main.js +72 -10
- package/dist/vault/approvals/kernel-server.js +71 -9
- package/dist/vault/broker/server.js +71 -9
- package/package.json +1 -1
- package/telegram-plugin/bridge/ipc-client.ts +17 -1
- package/telegram-plugin/dist/bridge/bridge.js +5 -2
- package/telegram-plugin/dist/gateway/gateway.js +342 -120
- package/telegram-plugin/dist/server.js +5 -2
- package/telegram-plugin/gateway/boot-reason.ts +61 -0
- package/telegram-plugin/gateway/cron-session.ts +66 -0
- package/telegram-plugin/gateway/gateway.ts +50 -54
- package/telegram-plugin/gateway/narrative-lane.ts +21 -1
- package/telegram-plugin/gateway/obligation-ledger.ts +9 -0
- package/telegram-plugin/gateway/obligation-wiring.ts +19 -4
- package/telegram-plugin/gateway/pending-inbound-buffer.ts +43 -3
- package/telegram-plugin/gateway/represent-delivery-guard.ts +188 -0
- package/telegram-plugin/gateway/stream-render.ts +24 -2
- package/telegram-plugin/tests/boot-card-reason.test.ts +88 -0
- package/telegram-plugin/tests/cron-bridge-drain-spool-ack.test.ts +150 -0
- package/telegram-plugin/tests/ipc-client-reconnect-rejection.test.ts +70 -0
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +86 -0
- package/telegram-plugin/tests/pending-inbound-buffer.test.ts +145 -9
- package/telegram-plugin/tests/queued-card-surface.test.ts +66 -0
- package/telegram-plugin/tests/represent-guard.test.ts +295 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +83 -0
- package/telegram-plugin/turn-flush-safety.ts +79 -0
|
@@ -21997,7 +21997,7 @@ var init_schema = __esm(() => {
|
|
|
21997
21997
|
reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
|
|
21998
21998
|
consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
|
|
21999
21999
|
}).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."),
|
|
22000
|
-
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 \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_TEMPORAL_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing), 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
22000
|
+
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 \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_TEMPORAL_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis, made live by switchroom's " + "temporal-language image patch (which ended a 200+-locale auto-detection " + "pass that blocked the shared asyncio loop on every recall); " + "comma-separated, unset means the image's baked `en`, set e.g. `en,es` to " + "restore i18n parsing; and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS \u2014 the max chars of query text " + "dateparser.search_dates() is handed during temporal analysis (0 = " + "unlimited), made live by switchroom's temporal-offload image patch " + "(which moved the synchronous extraction off the shared asyncio loop onto " + "a single-worker thread and bounds its input, ending multi-second loop " + "stalls on multi-KB consolidation queries); unset means the image's baked " + "2000, set 0 to restore an unbounded full scan), 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
|
|
22001
22001
|
});
|
|
22002
22002
|
MicrosoftWorkspaceConfigSchema = exports_external.object({
|
|
22003
22003
|
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 \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
|
|
@@ -22593,6 +22593,20 @@ var init_overlay_schema = __esm(() => {
|
|
|
22593
22593
|
// ../src/config/overlay-loader.ts
|
|
22594
22594
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
|
|
22595
22595
|
import { basename as basename4, resolve as resolve4 } from "node:path";
|
|
22596
|
+
function recordReadFailure(agentCfg, failure) {
|
|
22597
|
+
const node = agentCfg;
|
|
22598
|
+
const existing = node[OVERLAY_READ_FAILURES];
|
|
22599
|
+
if (Array.isArray(existing)) {
|
|
22600
|
+
existing.push(failure);
|
|
22601
|
+
return;
|
|
22602
|
+
}
|
|
22603
|
+
Object.defineProperty(agentCfg, OVERLAY_READ_FAILURES, {
|
|
22604
|
+
value: [failure],
|
|
22605
|
+
enumerable: false,
|
|
22606
|
+
configurable: true,
|
|
22607
|
+
writable: false
|
|
22608
|
+
});
|
|
22609
|
+
}
|
|
22596
22610
|
function deriveOverlayTitle(raw, fileName) {
|
|
22597
22611
|
const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
|
|
22598
22612
|
if (titleFromComment)
|
|
@@ -22602,17 +22616,39 @@ function deriveOverlayTitle(raw, fileName) {
|
|
|
22602
22616
|
return;
|
|
22603
22617
|
return base.length > 0 ? base : undefined;
|
|
22604
22618
|
}
|
|
22619
|
+
function readOverlayFile(agentName, file, agentCfg, warnings) {
|
|
22620
|
+
try {
|
|
22621
|
+
return readFileSync5(file, "utf-8");
|
|
22622
|
+
} catch (err) {
|
|
22623
|
+
const code = err.code;
|
|
22624
|
+
if (code === "ENOENT")
|
|
22625
|
+
return;
|
|
22626
|
+
const w = {
|
|
22627
|
+
agent: agentName,
|
|
22628
|
+
file,
|
|
22629
|
+
reason: `read error: ${err.message}`,
|
|
22630
|
+
code: code ?? "EUNKNOWN"
|
|
22631
|
+
};
|
|
22632
|
+
recordReadFailure(agentCfg, { file, code: w.code });
|
|
22633
|
+
warnings.push(w);
|
|
22634
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${file}': ${w.reason}`);
|
|
22635
|
+
return;
|
|
22636
|
+
}
|
|
22637
|
+
}
|
|
22605
22638
|
function overlayDirFor(agentName, subdir) {
|
|
22606
22639
|
const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
|
|
22607
22640
|
return resolve4(base);
|
|
22608
22641
|
}
|
|
22609
|
-
function listYamlFiles(dir) {
|
|
22642
|
+
function listYamlFiles(dir, onUnreadableDir) {
|
|
22610
22643
|
if (!existsSync5(dir))
|
|
22611
22644
|
return [];
|
|
22612
22645
|
let entries;
|
|
22613
22646
|
try {
|
|
22614
22647
|
entries = readdirSync3(dir);
|
|
22615
|
-
} catch {
|
|
22648
|
+
} catch (err) {
|
|
22649
|
+
const code = err.code;
|
|
22650
|
+
if (code !== "ENOENT")
|
|
22651
|
+
onUnreadableDir?.(code ?? "EUNKNOWN");
|
|
22616
22652
|
return [];
|
|
22617
22653
|
}
|
|
22618
22654
|
const out = [];
|
|
@@ -22650,12 +22686,24 @@ function applyAgentOverlays(config) {
|
|
|
22650
22686
|
for (const [agentName, agentCfg] of Object.entries(agents)) {
|
|
22651
22687
|
try {
|
|
22652
22688
|
const scheduleDir = overlayDirFor(agentName, "schedule.d");
|
|
22653
|
-
const files = listYamlFiles(scheduleDir)
|
|
22689
|
+
const files = listYamlFiles(scheduleDir, (code) => {
|
|
22690
|
+
const w = {
|
|
22691
|
+
agent: agentName,
|
|
22692
|
+
file: scheduleDir,
|
|
22693
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
22694
|
+
code
|
|
22695
|
+
};
|
|
22696
|
+
recordReadFailure(agentCfg, { file: scheduleDir, code });
|
|
22697
|
+
warnings.push(w);
|
|
22698
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${scheduleDir}': ${w.reason}`);
|
|
22699
|
+
});
|
|
22654
22700
|
if (files.length > 0) {
|
|
22655
22701
|
const merged = [...agentCfg.schedule ?? []];
|
|
22656
22702
|
for (const file of files) {
|
|
22703
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22704
|
+
if (raw === undefined)
|
|
22705
|
+
continue;
|
|
22657
22706
|
try {
|
|
22658
|
-
const raw = readFileSync5(file, "utf-8");
|
|
22659
22707
|
const parsed = import_yaml.parse(raw);
|
|
22660
22708
|
const doc = OverlayDocSchema.parse(parsed);
|
|
22661
22709
|
const title = deriveOverlayTitle(raw, basename4(file));
|
|
@@ -22690,13 +22738,25 @@ function applyAgentOverlays(config) {
|
|
|
22690
22738
|
}
|
|
22691
22739
|
try {
|
|
22692
22740
|
const skillsDir = overlayDirFor(agentName, "skills.d");
|
|
22693
|
-
const skillFiles = listYamlFiles(skillsDir)
|
|
22741
|
+
const skillFiles = listYamlFiles(skillsDir, (code) => {
|
|
22742
|
+
const w = {
|
|
22743
|
+
agent: agentName,
|
|
22744
|
+
file: skillsDir,
|
|
22745
|
+
reason: `read error: cannot list overlay directory (${code})`,
|
|
22746
|
+
code
|
|
22747
|
+
};
|
|
22748
|
+
recordReadFailure(agentCfg, { file: skillsDir, code });
|
|
22749
|
+
warnings.push(w);
|
|
22750
|
+
console.warn(`[switchroom] overlay-loader: agent='${agentName}' file='${skillsDir}': ${w.reason}`);
|
|
22751
|
+
});
|
|
22694
22752
|
if (skillFiles.length === 0) {} else {
|
|
22695
22753
|
const merged = [...agentCfg.skills ?? []];
|
|
22696
22754
|
const seen = new Set(merged);
|
|
22697
22755
|
for (const file of skillFiles) {
|
|
22756
|
+
const raw = readOverlayFile(agentName, file, agentCfg, warnings);
|
|
22757
|
+
if (raw === undefined)
|
|
22758
|
+
continue;
|
|
22698
22759
|
try {
|
|
22699
|
-
const raw = readFileSync5(file, "utf-8");
|
|
22700
22760
|
const parsed = import_yaml.parse(raw);
|
|
22701
22761
|
const doc = OverlayDocSchema.parse(parsed);
|
|
22702
22762
|
for (const skillName of doc.skills ?? []) {
|
|
@@ -22724,7 +22784,7 @@ function applyAgentOverlays(config) {
|
|
|
22724
22784
|
}
|
|
22725
22785
|
return { config, warnings };
|
|
22726
22786
|
}
|
|
22727
|
-
var import_yaml, OVERLAY_SOURCE, OVERLAY_TITLE;
|
|
22787
|
+
var import_yaml, OVERLAY_SOURCE, OVERLAY_TITLE, OVERLAY_READ_FAILURES;
|
|
22728
22788
|
var init_overlay_loader = __esm(() => {
|
|
22729
22789
|
init_zod();
|
|
22730
22790
|
init_overlay_schema();
|
|
@@ -22732,6 +22792,7 @@ var init_overlay_loader = __esm(() => {
|
|
|
22732
22792
|
import_yaml = __toESM(require_dist(), 1);
|
|
22733
22793
|
OVERLAY_SOURCE = Symbol.for("switchroom.config.overlay-source");
|
|
22734
22794
|
OVERLAY_TITLE = Symbol.for("switchroom.config.overlay-title");
|
|
22795
|
+
OVERLAY_READ_FAILURES = Symbol.for("switchroom.config.overlay-read-failures");
|
|
22735
22796
|
});
|
|
22736
22797
|
|
|
22737
22798
|
// ../src/config/merge.ts
|
|
@@ -80949,6 +81010,22 @@ function endsWithSilentMarker(text4) {
|
|
|
80949
81010
|
return false;
|
|
80950
81011
|
return isSilentFlushMarker(lines[lines.length - 1]);
|
|
80951
81012
|
}
|
|
81013
|
+
function isSilentSentinelCardOutcome(input) {
|
|
81014
|
+
if (input.finalAnswerEverDelivered)
|
|
81015
|
+
return false;
|
|
81016
|
+
if (isSilentFlushMarker(input.lastReplyText) || isCompositeSilentNoise(input.lastReplyText)) {
|
|
81017
|
+
return true;
|
|
81018
|
+
}
|
|
81019
|
+
if (!input.replyCalled) {
|
|
81020
|
+
const joined = input.capturedText.join(`
|
|
81021
|
+
|
|
81022
|
+
`).trim();
|
|
81023
|
+
if (joined.length > 0 && (isSilentFlushMarker(joined) || isCompositeSilentNoise(joined) || endsWithSilentMarker(joined))) {
|
|
81024
|
+
return true;
|
|
81025
|
+
}
|
|
81026
|
+
}
|
|
81027
|
+
return false;
|
|
81028
|
+
}
|
|
80952
81029
|
function decideTurnFlush(input) {
|
|
80953
81030
|
const flushEnabled = input.flushEnabled !== false;
|
|
80954
81031
|
if (!flushEnabled)
|
|
@@ -82242,6 +82319,9 @@ function closeFlushCompletionWindows(registry, now) {
|
|
|
82242
82319
|
registry?.completeAll?.(now);
|
|
82243
82320
|
return closed;
|
|
82244
82321
|
}
|
|
82322
|
+
function parkedTurnStartCount() {
|
|
82323
|
+
return parkedTurnStarts.length;
|
|
82324
|
+
}
|
|
82245
82325
|
function drainParkedTurnStartsForChat(deps, chatId, threadId) {
|
|
82246
82326
|
const wantThread = envThreadIdNum(threadId);
|
|
82247
82327
|
const drained = [];
|
|
@@ -82611,8 +82691,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
82611
82691
|
}
|
|
82612
82692
|
if (QUEUED_CARD_ENABLED && !handbackOwnsSurface) {
|
|
82613
82693
|
const cardChatId = ev.chatId;
|
|
82614
|
-
const
|
|
82615
|
-
const replyTo = replyToRaw != null && Number.isFinite(replyToRaw) ? replyToRaw : null;
|
|
82694
|
+
const replyTo = parseSourceMessageId(ev.messageId);
|
|
82616
82695
|
openQueuedCard(deps, cardChatId, enqThreadIdNum ?? null, replyTo).then((cardId) => {
|
|
82617
82696
|
if (cardId == null)
|
|
82618
82697
|
return;
|
|
@@ -83896,7 +83975,13 @@ function createNarrativeLane(deps) {
|
|
|
83896
83975
|
clearActivityCardRecord(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs, statusKey(chat, thread), id);
|
|
83897
83976
|
}
|
|
83898
83977
|
await reconcileStatusPin(`fg:${statusKey(chat, thread)}`, chat, { pinned: false });
|
|
83899
|
-
|
|
83978
|
+
const silentSentinelTurn = isSilentSentinelCardOutcome({
|
|
83979
|
+
replyCalled: turn.replyCalled,
|
|
83980
|
+
lastReplyText: turn.lastReplyText,
|
|
83981
|
+
capturedText: turn.capturedText,
|
|
83982
|
+
finalAnswerEverDelivered: turn.finalAnswerEverDelivered
|
|
83983
|
+
});
|
|
83984
|
+
if (CLEAR_STATUS_ON_COMPLETION || silentSentinelTurn) {
|
|
83900
83985
|
try {
|
|
83901
83986
|
await robustApiCall(() => bot.api.deleteMessage(chat, id), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.delete" });
|
|
83902
83987
|
} catch (err) {
|
|
@@ -87376,7 +87461,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
|
|
|
87376
87461
|
"HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
|
|
87377
87462
|
"HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT",
|
|
87378
87463
|
"HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S",
|
|
87379
|
-
"HINDSIGHT_API_TEMPORAL_LANGUAGES"
|
|
87464
|
+
"HINDSIGHT_API_TEMPORAL_LANGUAGES",
|
|
87465
|
+
"HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS"
|
|
87380
87466
|
]);
|
|
87381
87467
|
var HINDSIGHT_WORKER_SLOT_TYPES = [
|
|
87382
87468
|
"consolidation",
|
|
@@ -92780,7 +92866,24 @@ function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
|
|
|
92780
92866
|
const pending = buffer.drain(agent);
|
|
92781
92867
|
let redelivered = 0;
|
|
92782
92868
|
let rebuffered = 0;
|
|
92869
|
+
let retracted = 0;
|
|
92783
92870
|
for (const { merged, originals } of planBufferedRedelivery(pending)) {
|
|
92871
|
+
let proceed = true;
|
|
92872
|
+
if (buffer.beforeRedeliver != null) {
|
|
92873
|
+
try {
|
|
92874
|
+
proceed = buffer.beforeRedeliver(merged);
|
|
92875
|
+
} catch (e) {
|
|
92876
|
+
proceed = true;
|
|
92877
|
+
process.stderr.write(`redeliver beforeRedeliver threw \u2014 failing open (deliver): ${String(e)}
|
|
92878
|
+
`);
|
|
92879
|
+
}
|
|
92880
|
+
}
|
|
92881
|
+
if (!proceed) {
|
|
92882
|
+
for (const o of originals)
|
|
92883
|
+
spool?.ack(o);
|
|
92884
|
+
retracted += originals.length;
|
|
92885
|
+
continue;
|
|
92886
|
+
}
|
|
92784
92887
|
let delivered = false;
|
|
92785
92888
|
try {
|
|
92786
92889
|
delivered = send(merged);
|
|
@@ -92798,7 +92901,7 @@ function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
|
|
|
92798
92901
|
rebuffered += originals.length;
|
|
92799
92902
|
}
|
|
92800
92903
|
}
|
|
92801
|
-
return { drained: pending.length, redelivered, rebuffered };
|
|
92904
|
+
return { drained: pending.length, redelivered, rebuffered, retracted };
|
|
92802
92905
|
}
|
|
92803
92906
|
function isMergeableUserInbound(msg) {
|
|
92804
92907
|
return msg.type === "inbound" && (msg.meta == null || msg.meta.source == null && msg.meta.button_callback == null);
|
|
@@ -92915,10 +93018,167 @@ function createPendingInboundBuffer(opts = {}) {
|
|
|
92915
93018
|
for (const q of queues.values())
|
|
92916
93019
|
n += q.length;
|
|
92917
93020
|
return n;
|
|
93021
|
+
},
|
|
93022
|
+
beforeRedeliver: opts.beforeRedeliver
|
|
93023
|
+
};
|
|
93024
|
+
}
|
|
93025
|
+
|
|
93026
|
+
// gateway/represent-guard.ts
|
|
93027
|
+
function shouldSuppressRepresent(o, deps) {
|
|
93028
|
+
if (!deps.historyEnabled)
|
|
93029
|
+
return false;
|
|
93030
|
+
if (o.lastRepresentedAt == null) {
|
|
93031
|
+
if (o.openedAt == null)
|
|
93032
|
+
return false;
|
|
93033
|
+
return deps.hasOutboundDeliveredSince(o.chatId, o.openedAt, o.threadId);
|
|
93034
|
+
}
|
|
93035
|
+
return deps.hasOutboundDeliveredSince(o.chatId, o.lastRepresentedAt, o.threadId);
|
|
93036
|
+
}
|
|
93037
|
+
|
|
93038
|
+
// gateway/represent-delivery-guard.ts
|
|
93039
|
+
function makeRepresentRedeliveryGuard(deps) {
|
|
93040
|
+
return (msg) => {
|
|
93041
|
+
if (!deps.enabled)
|
|
93042
|
+
return true;
|
|
93043
|
+
if (msg.meta?.source !== "obligation_represent")
|
|
93044
|
+
return true;
|
|
93045
|
+
const originTurnId = msg.meta?.origin_turn_id;
|
|
93046
|
+
if (originTurnId == null)
|
|
93047
|
+
return true;
|
|
93048
|
+
const o = deps.ledger.get(originTurnId);
|
|
93049
|
+
if (o == null) {
|
|
93050
|
+
deps.log(`telegram gateway: represent retracted at delivery \u2014 obligation already ` + `closed since decision (no re-fire) origin=${originTurnId}
|
|
93051
|
+
`);
|
|
93052
|
+
return false;
|
|
93053
|
+
}
|
|
93054
|
+
const suppress = shouldSuppressRepresent(o, {
|
|
93055
|
+
historyEnabled: deps.historyEnabled,
|
|
93056
|
+
hasOutboundDeliveredSince: (chatId, sinceMs, threadId) => deps.hasOutboundDeliveredSince(chatId, sinceMs, threadId, deps.minReplyChars)
|
|
93057
|
+
});
|
|
93058
|
+
if (suppress) {
|
|
93059
|
+
deps.ledger.close(originTurnId);
|
|
93060
|
+
deps.log(`telegram gateway: represent retracted at delivery \u2014 reply landed since ` + `decision (no re-fire) origin=${originTurnId}
|
|
93061
|
+
`);
|
|
93062
|
+
return false;
|
|
92918
93063
|
}
|
|
93064
|
+
return true;
|
|
93065
|
+
};
|
|
93066
|
+
}
|
|
93067
|
+
var DEFAULT_DRAIN_DEFER_STALE_GAP_MS = 15000;
|
|
93068
|
+
function makeSessionBusyDrainDeferral(boundMs, staleGapMs = DEFAULT_DRAIN_DEFER_STALE_GAP_MS) {
|
|
93069
|
+
let deferringSince = null;
|
|
93070
|
+
let lastCallAt = null;
|
|
93071
|
+
return (busy, now) => {
|
|
93072
|
+
const gapSinceLastCall = lastCallAt == null ? null : now - lastCallAt;
|
|
93073
|
+
lastCallAt = now;
|
|
93074
|
+
if (!busy || boundMs <= 0) {
|
|
93075
|
+
deferringSince = null;
|
|
93076
|
+
return false;
|
|
93077
|
+
}
|
|
93078
|
+
if (deferringSince == null || staleGapMs > 0 && gapSinceLastCall != null && gapSinceLastCall > staleGapMs) {
|
|
93079
|
+
deferringSince = now;
|
|
93080
|
+
}
|
|
93081
|
+
return now - deferringSince < boundMs;
|
|
92919
93082
|
};
|
|
92920
93083
|
}
|
|
92921
93084
|
|
|
93085
|
+
// gateway/pending-inbound-buffer.ts
|
|
93086
|
+
function redeliverBufferedInbound2(buffer, agent, send, spool, onDelivered) {
|
|
93087
|
+
const pending = buffer.drain(agent);
|
|
93088
|
+
let redelivered = 0;
|
|
93089
|
+
let rebuffered = 0;
|
|
93090
|
+
let retracted = 0;
|
|
93091
|
+
for (const { merged, originals } of planBufferedRedelivery2(pending)) {
|
|
93092
|
+
let proceed = true;
|
|
93093
|
+
if (buffer.beforeRedeliver != null) {
|
|
93094
|
+
try {
|
|
93095
|
+
proceed = buffer.beforeRedeliver(merged);
|
|
93096
|
+
} catch (e) {
|
|
93097
|
+
proceed = true;
|
|
93098
|
+
process.stderr.write(`redeliver beforeRedeliver threw \u2014 failing open (deliver): ${String(e)}
|
|
93099
|
+
`);
|
|
93100
|
+
}
|
|
93101
|
+
}
|
|
93102
|
+
if (!proceed) {
|
|
93103
|
+
for (const o of originals)
|
|
93104
|
+
spool?.ack(o);
|
|
93105
|
+
retracted += originals.length;
|
|
93106
|
+
continue;
|
|
93107
|
+
}
|
|
93108
|
+
let delivered = false;
|
|
93109
|
+
try {
|
|
93110
|
+
delivered = send(merged);
|
|
93111
|
+
} catch {
|
|
93112
|
+
delivered = false;
|
|
93113
|
+
}
|
|
93114
|
+
if (delivered) {
|
|
93115
|
+
for (const o of originals)
|
|
93116
|
+
spool?.ack(o);
|
|
93117
|
+
redelivered += originals.length;
|
|
93118
|
+
onDelivered?.(merged, originals);
|
|
93119
|
+
} else {
|
|
93120
|
+
for (const o of originals)
|
|
93121
|
+
buffer.push(agent, o);
|
|
93122
|
+
rebuffered += originals.length;
|
|
93123
|
+
}
|
|
93124
|
+
}
|
|
93125
|
+
return { drained: pending.length, redelivered, rebuffered, retracted };
|
|
93126
|
+
}
|
|
93127
|
+
function isMergeableUserInbound2(msg) {
|
|
93128
|
+
return msg.type === "inbound" && (msg.meta == null || msg.meta.source == null && msg.meta.button_callback == null);
|
|
93129
|
+
}
|
|
93130
|
+
function inboundHasMedia2(msg) {
|
|
93131
|
+
return msg.imagePath != null || msg.attachment != null;
|
|
93132
|
+
}
|
|
93133
|
+
function planBufferedRedelivery2(pending) {
|
|
93134
|
+
const out = [];
|
|
93135
|
+
let run3 = [];
|
|
93136
|
+
let runHasMedia = false;
|
|
93137
|
+
const sameTarget = (a, b) => a.chatId === b.chatId && (a.threadId ?? null) === (b.threadId ?? null) && a.userId === b.userId;
|
|
93138
|
+
const flush = () => {
|
|
93139
|
+
if (run3.length === 0)
|
|
93140
|
+
return;
|
|
93141
|
+
out.push({ merged: run3.length === 1 ? run3[0] : mergeRun2(run3), originals: run3 });
|
|
93142
|
+
run3 = [];
|
|
93143
|
+
runHasMedia = false;
|
|
93144
|
+
};
|
|
93145
|
+
for (const msg of pending) {
|
|
93146
|
+
const msgHasMedia = inboundHasMedia2(msg);
|
|
93147
|
+
const canJoin = run3.length > 0 && isMergeableUserInbound2(msg) && isMergeableUserInbound2(run3[run3.length - 1]) && sameTarget(run3[run3.length - 1], msg) && !(runHasMedia && msgHasMedia);
|
|
93148
|
+
if (!canJoin)
|
|
93149
|
+
flush();
|
|
93150
|
+
run3.push(msg);
|
|
93151
|
+
runHasMedia = runHasMedia || msgHasMedia;
|
|
93152
|
+
}
|
|
93153
|
+
flush();
|
|
93154
|
+
return out;
|
|
93155
|
+
}
|
|
93156
|
+
var ATTACHMENT_META_RE2 = /^(image_path|attachment_)/;
|
|
93157
|
+
function mergeRun2(run3) {
|
|
93158
|
+
const last = run3[run3.length - 1];
|
|
93159
|
+
const mediaEntry = run3.find(inboundHasMedia2);
|
|
93160
|
+
const merged = {
|
|
93161
|
+
...last,
|
|
93162
|
+
text: run3.map((m) => m.text).join(`
|
|
93163
|
+
`)
|
|
93164
|
+
};
|
|
93165
|
+
delete merged.imagePath;
|
|
93166
|
+
delete merged.attachment;
|
|
93167
|
+
if (mediaEntry != null && mediaEntry !== last) {
|
|
93168
|
+
const splicedMeta = { ...merged.meta };
|
|
93169
|
+
for (const [k, v] of Object.entries(mediaEntry.meta)) {
|
|
93170
|
+
if (ATTACHMENT_META_RE2.test(k))
|
|
93171
|
+
splicedMeta[k] = v;
|
|
93172
|
+
}
|
|
93173
|
+
merged.meta = splicedMeta;
|
|
93174
|
+
}
|
|
93175
|
+
if (mediaEntry?.imagePath != null)
|
|
93176
|
+
merged.imagePath = mediaEntry.imagePath;
|
|
93177
|
+
if (mediaEntry?.attachment != null)
|
|
93178
|
+
merged.attachment = mediaEntry.attachment;
|
|
93179
|
+
return merged;
|
|
93180
|
+
}
|
|
93181
|
+
|
|
92922
93182
|
// gateway/cron-session.ts
|
|
92923
93183
|
var CRON_IDENTITY_SUFFIX = "-cron";
|
|
92924
93184
|
function cronIdentity(agent) {
|
|
@@ -92948,6 +93208,23 @@ function deliverInjectWithFallback(agentName3, meta, send) {
|
|
|
92948
93208
|
}
|
|
92949
93209
|
return { target, delivered: false, fellBackToMain: false };
|
|
92950
93210
|
}
|
|
93211
|
+
function drainCronBridgeOnRegister(client3, buffer, spool, log) {
|
|
93212
|
+
client3.send({ type: "status", status: "agent_connected" });
|
|
93213
|
+
const send = (msg) => {
|
|
93214
|
+
try {
|
|
93215
|
+
client3.send(msg);
|
|
93216
|
+
return true;
|
|
93217
|
+
} catch {
|
|
93218
|
+
return false;
|
|
93219
|
+
}
|
|
93220
|
+
};
|
|
93221
|
+
const result = redeliverBufferedInbound2(buffer, client3.agentName ?? "", send, spool);
|
|
93222
|
+
if (result.drained > 0 && log != null) {
|
|
93223
|
+
log(`telegram gateway: cron-bridge drain agent=${client3.agentName} ` + `drained=${result.drained} redelivered=${result.redelivered} ` + `rebuffered=${result.rebuffered}
|
|
93224
|
+
`);
|
|
93225
|
+
}
|
|
93226
|
+
return result;
|
|
93227
|
+
}
|
|
92951
93228
|
|
|
92952
93229
|
// gateway/obligation-ledger.ts
|
|
92953
93230
|
class ObligationLedger {
|
|
@@ -92987,6 +93264,9 @@ class ObligationLedger {
|
|
|
92987
93264
|
isOpen(originTurnId) {
|
|
92988
93265
|
return this.open.has(originTurnId);
|
|
92989
93266
|
}
|
|
93267
|
+
get(originTurnId) {
|
|
93268
|
+
return this.open.get(originTurnId);
|
|
93269
|
+
}
|
|
92990
93270
|
hasOpen() {
|
|
92991
93271
|
return this.open.size > 0;
|
|
92992
93272
|
}
|
|
@@ -94507,86 +94787,6 @@ function formatEventDetail2(event) {
|
|
|
94507
94787
|
}
|
|
94508
94788
|
}
|
|
94509
94789
|
|
|
94510
|
-
// gateway/pending-inbound-buffer.ts
|
|
94511
|
-
function redeliverBufferedInbound2(buffer, agent, send, spool, onDelivered) {
|
|
94512
|
-
const pending = buffer.drain(agent);
|
|
94513
|
-
let redelivered = 0;
|
|
94514
|
-
let rebuffered = 0;
|
|
94515
|
-
for (const { merged, originals } of planBufferedRedelivery2(pending)) {
|
|
94516
|
-
let delivered = false;
|
|
94517
|
-
try {
|
|
94518
|
-
delivered = send(merged);
|
|
94519
|
-
} catch {
|
|
94520
|
-
delivered = false;
|
|
94521
|
-
}
|
|
94522
|
-
if (delivered) {
|
|
94523
|
-
for (const o of originals)
|
|
94524
|
-
spool?.ack(o);
|
|
94525
|
-
redelivered += originals.length;
|
|
94526
|
-
onDelivered?.(merged, originals);
|
|
94527
|
-
} else {
|
|
94528
|
-
for (const o of originals)
|
|
94529
|
-
buffer.push(agent, o);
|
|
94530
|
-
rebuffered += originals.length;
|
|
94531
|
-
}
|
|
94532
|
-
}
|
|
94533
|
-
return { drained: pending.length, redelivered, rebuffered };
|
|
94534
|
-
}
|
|
94535
|
-
function isMergeableUserInbound2(msg) {
|
|
94536
|
-
return msg.type === "inbound" && (msg.meta == null || msg.meta.source == null && msg.meta.button_callback == null);
|
|
94537
|
-
}
|
|
94538
|
-
function inboundHasMedia2(msg) {
|
|
94539
|
-
return msg.imagePath != null || msg.attachment != null;
|
|
94540
|
-
}
|
|
94541
|
-
function planBufferedRedelivery2(pending) {
|
|
94542
|
-
const out = [];
|
|
94543
|
-
let run3 = [];
|
|
94544
|
-
let runHasMedia = false;
|
|
94545
|
-
const sameTarget = (a, b) => a.chatId === b.chatId && (a.threadId ?? null) === (b.threadId ?? null) && a.userId === b.userId;
|
|
94546
|
-
const flush = () => {
|
|
94547
|
-
if (run3.length === 0)
|
|
94548
|
-
return;
|
|
94549
|
-
out.push({ merged: run3.length === 1 ? run3[0] : mergeRun2(run3), originals: run3 });
|
|
94550
|
-
run3 = [];
|
|
94551
|
-
runHasMedia = false;
|
|
94552
|
-
};
|
|
94553
|
-
for (const msg of pending) {
|
|
94554
|
-
const msgHasMedia = inboundHasMedia2(msg);
|
|
94555
|
-
const canJoin = run3.length > 0 && isMergeableUserInbound2(msg) && isMergeableUserInbound2(run3[run3.length - 1]) && sameTarget(run3[run3.length - 1], msg) && !(runHasMedia && msgHasMedia);
|
|
94556
|
-
if (!canJoin)
|
|
94557
|
-
flush();
|
|
94558
|
-
run3.push(msg);
|
|
94559
|
-
runHasMedia = runHasMedia || msgHasMedia;
|
|
94560
|
-
}
|
|
94561
|
-
flush();
|
|
94562
|
-
return out;
|
|
94563
|
-
}
|
|
94564
|
-
var ATTACHMENT_META_RE2 = /^(image_path|attachment_)/;
|
|
94565
|
-
function mergeRun2(run3) {
|
|
94566
|
-
const last = run3[run3.length - 1];
|
|
94567
|
-
const mediaEntry = run3.find(inboundHasMedia2);
|
|
94568
|
-
const merged = {
|
|
94569
|
-
...last,
|
|
94570
|
-
text: run3.map((m) => m.text).join(`
|
|
94571
|
-
`)
|
|
94572
|
-
};
|
|
94573
|
-
delete merged.imagePath;
|
|
94574
|
-
delete merged.attachment;
|
|
94575
|
-
if (mediaEntry != null && mediaEntry !== last) {
|
|
94576
|
-
const splicedMeta = { ...merged.meta };
|
|
94577
|
-
for (const [k, v] of Object.entries(mediaEntry.meta)) {
|
|
94578
|
-
if (ATTACHMENT_META_RE2.test(k))
|
|
94579
|
-
splicedMeta[k] = v;
|
|
94580
|
-
}
|
|
94581
|
-
merged.meta = splicedMeta;
|
|
94582
|
-
}
|
|
94583
|
-
if (mediaEntry?.imagePath != null)
|
|
94584
|
-
merged.imagePath = mediaEntry.imagePath;
|
|
94585
|
-
if (mediaEntry?.attachment != null)
|
|
94586
|
-
merged.attachment = mediaEntry.attachment;
|
|
94587
|
-
return merged;
|
|
94588
|
-
}
|
|
94589
|
-
|
|
94590
94790
|
// gateway/obligation-turn-end.ts
|
|
94591
94791
|
function decideObligationTurnEnd(finalAnswerDelivered, replyCalled) {
|
|
94592
94792
|
return finalAnswerDelivered || replyCalled ? "close" : "note-ended";
|
|
@@ -95425,6 +95625,9 @@ class ObligationLedger2 {
|
|
|
95425
95625
|
isOpen(originTurnId) {
|
|
95426
95626
|
return this.open.has(originTurnId);
|
|
95427
95627
|
}
|
|
95628
|
+
get(originTurnId) {
|
|
95629
|
+
return this.open.get(originTurnId);
|
|
95630
|
+
}
|
|
95428
95631
|
hasOpen() {
|
|
95429
95632
|
return this.open.size > 0;
|
|
95430
95633
|
}
|
|
@@ -95587,23 +95790,24 @@ function driveEscalation(args) {
|
|
|
95587
95790
|
});
|
|
95588
95791
|
}
|
|
95589
95792
|
|
|
95590
|
-
// gateway/represent-guard.ts
|
|
95591
|
-
function shouldSuppressRepresent(o, deps) {
|
|
95592
|
-
if (!deps.historyEnabled)
|
|
95593
|
-
return false;
|
|
95594
|
-
if (o.lastRepresentedAt == null) {
|
|
95595
|
-
if (o.openedAt == null)
|
|
95596
|
-
return false;
|
|
95597
|
-
return deps.hasOutboundDeliveredSince(o.chatId, o.openedAt, o.threadId);
|
|
95598
|
-
}
|
|
95599
|
-
return deps.hasOutboundDeliveredSince(o.chatId, o.lastRepresentedAt, o.threadId);
|
|
95600
|
-
}
|
|
95601
|
-
|
|
95602
95793
|
// gateway/escalation-bridge-gate.ts
|
|
95603
95794
|
function shouldDeferEscalationForBridge(deps) {
|
|
95604
95795
|
return !deps.bridgeAlive;
|
|
95605
95796
|
}
|
|
95606
95797
|
|
|
95798
|
+
// gateway/stream-render.ts
|
|
95799
|
+
init_history();
|
|
95800
|
+
init_rich_send();
|
|
95801
|
+
var PARKED_TURN_START_TTL_MS2 = 30 * 60000;
|
|
95802
|
+
var QUEUED_CARD_ENABLED2 = process.env.SWITCHROOM_QUEUED_CARD !== "0";
|
|
95803
|
+
var BUZZ_ENABLED2 = process.env.BUZZ_ENABLED === "1" || process.env.BUZZ_ENABLED === "true";
|
|
95804
|
+
var BUZZ_ORIGIN_STAMP_ACTIVE2 = BUZZ_ENABLED2 && isBuzzTurnRoutingEnabled();
|
|
95805
|
+
var parkedTurnStarts2 = [];
|
|
95806
|
+
var flushCompletionTracker2 = new FlushCompletionTracker;
|
|
95807
|
+
function parkedTurnStartCount2() {
|
|
95808
|
+
return parkedTurnStarts2.length;
|
|
95809
|
+
}
|
|
95810
|
+
|
|
95607
95811
|
// gateway/obligation-wiring.ts
|
|
95608
95812
|
function createObligationWiring(deps) {
|
|
95609
95813
|
const {
|
|
@@ -95682,7 +95886,9 @@ function createObligationWiring(deps) {
|
|
|
95682
95886
|
return;
|
|
95683
95887
|
const agent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
95684
95888
|
const now = Date.now();
|
|
95685
|
-
const
|
|
95889
|
+
const liveTurn = getCurrentTurn();
|
|
95890
|
+
const sessionBusy = liveTurn != null && liveTurn.endedAt == null || parkedTurnStartCount2() > 0;
|
|
95891
|
+
const backgroundWorkActive = OBLIGATION_BACKGROUND_WORK_GRACE_MS > 0 && (agentHasInFlightBackgroundWork(now) || sessionBusy);
|
|
95686
95892
|
const decision = obligationLedger.decideAtIdle(OBLIGATION_ESCALATE_GRACE_MS > 0 || backgroundWorkActive || OBLIGATION_REPRESENT_GRACE_MS > 0 ? {
|
|
95687
95893
|
now,
|
|
95688
95894
|
graceMs: OBLIGATION_ESCALATE_GRACE_MS,
|
|
@@ -95694,7 +95900,7 @@ function createObligationWiring(deps) {
|
|
|
95694
95900
|
if (decision.action === "none" || o == null) {
|
|
95695
95901
|
if (backgroundWorkActive && obligationLedger.hasOpen() && now - lastBgWorkDeferLogMs > 60000) {
|
|
95696
95902
|
lastBgWorkDeferLogMs = now;
|
|
95697
|
-
process.stderr.write(`telegram gateway: obligation sweep deferred \u2014 in-flight autonomous sub-agent work ` + `(${obligationLedger.size()} open, bounded ${Math.round(OBLIGATION_BACKGROUND_WORK_GRACE_MS / 60000)}m from receipt)
|
|
95903
|
+
process.stderr.write(`telegram gateway: obligation sweep deferred \u2014 in-flight autonomous sub-agent work ` + `or busy CLI session behind a poke-cleared turn ` + `(${obligationLedger.size()} open, bounded ${Math.round(OBLIGATION_BACKGROUND_WORK_GRACE_MS / 60000)}m from receipt)
|
|
95698
95904
|
`);
|
|
95699
95905
|
}
|
|
95700
95906
|
return;
|
|
@@ -99630,6 +99836,14 @@ function determineRestartReason(opts) {
|
|
|
99630
99836
|
return "crash";
|
|
99631
99837
|
return "fresh";
|
|
99632
99838
|
}
|
|
99839
|
+
var BOOT_REASON_REUSE_WINDOW_MS = 5 * 60000;
|
|
99840
|
+
function determineBridgeReconnectReason(opts) {
|
|
99841
|
+
const { gatewayStartedAtMs, bootReason, bootReasonReuseWindowMs = BOOT_REASON_REUSE_WINDOW_MS } = opts;
|
|
99842
|
+
if (opts.marker == null && opts.cleanMarker == null && bootReason != null && opts.now - gatewayStartedAtMs < bootReasonReuseWindowMs) {
|
|
99843
|
+
return bootReason;
|
|
99844
|
+
}
|
|
99845
|
+
return determineRestartReason(opts);
|
|
99846
|
+
}
|
|
99633
99847
|
|
|
99634
99848
|
// gateway/update-announce.ts
|
|
99635
99849
|
import { existsSync as existsSync50, mkdirSync as mkdirSync44, openSync as openSync12, closeSync as closeSync12, readFileSync as readFileSync53 } from "node:fs";
|
|
@@ -102479,10 +102693,10 @@ function startOutboxSweep(deps) {
|
|
|
102479
102693
|
}
|
|
102480
102694
|
|
|
102481
102695
|
// ../src/build-info.ts
|
|
102482
|
-
var VERSION2 = "0.20.
|
|
102483
|
-
var COMMIT_SHA = "
|
|
102484
|
-
var COMMIT_DATE = "2026-08-
|
|
102485
|
-
var LATEST_PR =
|
|
102696
|
+
var VERSION2 = "0.20.8";
|
|
102697
|
+
var COMMIT_SHA = "a6efc10f";
|
|
102698
|
+
var COMMIT_DATE = "2026-08-04T21:38:16Z";
|
|
102699
|
+
var LATEST_PR = 4374;
|
|
102486
102700
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
102487
102701
|
|
|
102488
102702
|
// gateway/boot-version.ts
|
|
@@ -108240,6 +108454,7 @@ var GATEWAY_STARTED_AT_MS = Date.now();
|
|
|
108240
108454
|
var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
|
|
108241
108455
|
var activeBootCard = null;
|
|
108242
108456
|
var bootCardPending = false;
|
|
108457
|
+
var bootReasonAtStartup = null;
|
|
108243
108458
|
var ISSUES_CARD_ENABLED = process.env.SWITCHROOM_ISSUES_CARD !== "false";
|
|
108244
108459
|
var activeIssuesCard = null;
|
|
108245
108460
|
var activeIssuesWatcher = null;
|
|
@@ -108501,7 +108716,8 @@ var pendingInboundBuffer = createPendingInboundBuffer({
|
|
|
108501
108716
|
const threadOpts = evThread != null ? { message_thread_id: evThread } : {};
|
|
108502
108717
|
swallowingApiCall(() => bot.api.sendMessage(chat, "\u23F3 Messages are arriving faster than I can process them. Your messages are saved and will be handled once I finish the current turn \u2014 if any can't be picked up, I'll ask you to resend it.", { ...threadOpts }), { chat_id: chat, verb: "inbound-buffer-eviction" });
|
|
108503
108718
|
},
|
|
108504
|
-
onHandbackEnqueue: (chatId, threadId, ts) => subagentHandbackMarker.record(chatId, threadId, ts)
|
|
108719
|
+
onHandbackEnqueue: (chatId, threadId, ts) => subagentHandbackMarker.record(chatId, threadId, ts),
|
|
108720
|
+
beforeRedeliver: makeRepresentRedeliveryGuard({ enabled: OBLIGATION_LEDGER_ENABLED, historyEnabled: HISTORY_ENABLED, ledger: obligationLedger, hasOutboundDeliveredSince, minReplyChars: OBLIGATION_REPRESENT_GUARD_MIN_REPLY_CHARS, log: (l) => process.stderr.write(l) })
|
|
108505
108721
|
});
|
|
108506
108722
|
function agentHasInFlightBackgroundWork(now) {
|
|
108507
108723
|
if (countRunningWorkers() > 0)
|
|
@@ -108732,13 +108948,7 @@ if (isGatewayMain)
|
|
|
108732
108948
|
process.stderr.write(`telegram gateway: bridge registered \u2014 agent=${client3.agentName}
|
|
108733
108949
|
`);
|
|
108734
108950
|
if (isCronIdentity(client3.agentName)) {
|
|
108735
|
-
client3
|
|
108736
|
-
const pending2 = pendingInboundBuffer.drain(client3.agentName ?? "");
|
|
108737
|
-
for (const m of pending2) {
|
|
108738
|
-
try {
|
|
108739
|
-
client3.send(m);
|
|
108740
|
-
} catch {}
|
|
108741
|
-
}
|
|
108951
|
+
drainCronBridgeOnRegister(client3, pendingInboundBuffer, inboundSpool ?? undefined, (l) => process.stderr.write(l));
|
|
108742
108952
|
return;
|
|
108743
108953
|
}
|
|
108744
108954
|
const bridgeUpEffects = client3.agentName != null ? shadowEmit2({ kind: "bridgeUp", at: Date.now() }) : [];
|
|
@@ -108785,7 +108995,14 @@ if (isGatewayMain)
|
|
|
108785
108995
|
`);
|
|
108786
108996
|
clearRestartMarker();
|
|
108787
108997
|
}
|
|
108788
|
-
const reason =
|
|
108998
|
+
const reason = determineBridgeReconnectReason({
|
|
108999
|
+
marker,
|
|
109000
|
+
cleanMarker,
|
|
109001
|
+
sessionMarker: storedSession,
|
|
109002
|
+
now: nowMs3,
|
|
109003
|
+
gatewayStartedAtMs: GATEWAY_STARTED_AT_MS,
|
|
109004
|
+
bootReason: bootReasonAtStartup
|
|
109005
|
+
});
|
|
108789
109006
|
const target = resolveBootChatId(marker, markerAgeMs);
|
|
108790
109007
|
if (target) {
|
|
108791
109008
|
const { chatId, threadId, ackMsgId } = target;
|
|
@@ -109532,12 +109749,16 @@ if (isGatewayMain)
|
|
|
109532
109749
|
}
|
|
109533
109750
|
})();
|
|
109534
109751
|
var IDLE_DRAIN_INTERVAL_MS = 5000;
|
|
109752
|
+
var idleDrainBusyDefer = makeSessionBusyDrainDeferral(OBLIGATION_BACKGROUND_WORK_GRACE_MS, IDLE_DRAIN_INTERVAL_MS * 3);
|
|
109535
109753
|
if (isGatewayMain && !STATIC) {
|
|
109536
109754
|
setInterval(() => {
|
|
109537
109755
|
const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
|
|
109538
109756
|
const r = idleDrainTick(pendingInboundBuffer, selfAgent, () => {
|
|
109539
109757
|
if (turnInFlightForGate())
|
|
109540
109758
|
return false;
|
|
109759
|
+
const sessionBusy = currentTurn != null && currentTurn.endedAt == null || parkedTurnStartCount() > 0;
|
|
109760
|
+
if (idleDrainBusyDefer(sessionBusy, Date.now()))
|
|
109761
|
+
return false;
|
|
109541
109762
|
const c = ipcServer.getClient(selfAgent);
|
|
109542
109763
|
return c != null && c.isAlive();
|
|
109543
109764
|
}, (m) => ipcServer.sendToAgent(selfAgent, m), inboundSpool, trackRedeliveredInbound);
|
|
@@ -116047,6 +116268,7 @@ async function startGateway() {
|
|
|
116047
116268
|
} else {
|
|
116048
116269
|
const markerAgeMs = marker ? nowMs3 - marker.ts : undefined;
|
|
116049
116270
|
const reason = determineRestartReason({ marker, cleanMarker, sessionMarker: storedSession, now: nowMs3 });
|
|
116271
|
+
bootReasonAtStartup = reason;
|
|
116050
116272
|
const target = resolveBootChatId(marker, markerAgeMs);
|
|
116051
116273
|
if (reason === "crash") {
|
|
116052
116274
|
const cleanMarkerStale = cleanMarker ? !shouldSuppressRecoveryBanner(cleanMarker, nowMs3, DEFAULT_MAX_AGE_MS) : false;
|