switchroom 0.19.25 → 0.19.26
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 +6 -2
- package/dist/auth-broker/index.js +9 -2
- package/dist/cli/notion-write-pretool.mjs +6 -2
- package/dist/cli/switchroom.js +908 -527
- package/dist/host-control/main.js +10 -3
- package/dist/vault/approvals/kernel-server.js +10 -2
- package/dist/vault/broker/server.js +10 -2
- package/package.json +1 -1
- package/profiles/_base/cron-session.sh.hbs +6 -0
- package/profiles/_base/start.sh.hbs +40 -4
- package/telegram-plugin/dist/gateway/gateway.js +275 -109
- package/telegram-plugin/gateway/gateway.ts +53 -52
- package/telegram-plugin/gateway/periodic-sweep-guard.ts +86 -0
- package/telegram-plugin/gateway/status-pin-retarget.ts +144 -0
- package/telegram-plugin/status-no-truncate.ts +49 -0
- package/telegram-plugin/status-pin-driver.ts +28 -0
- package/telegram-plugin/status-pin.ts +33 -4
- package/telegram-plugin/tests/card-type-distinguishability.test.ts +268 -0
- package/telegram-plugin/tests/periodic-sweep-guard.test.ts +151 -0
- package/telegram-plugin/tests/pinned-card-collapse.test.ts +29 -18
- package/telegram-plugin/tests/status-pin-retarget.test.ts +216 -0
- package/telegram-plugin/tests/status-pin-shutdown-wiring.test.ts +94 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +87 -21
- package/telegram-plugin/tests/status-pin.test.ts +128 -2
- package/telegram-plugin/tests/worker-activity-feed.test.ts +10 -10
- package/telegram-plugin/tests/worker-feed-coalesce.test.ts +37 -21
- package/telegram-plugin/tests/worker-visibility-prose-silent-harness.test.ts +1 -1
- package/telegram-plugin/tier-downgrade.ts +3 -2
- package/telegram-plugin/tool-activity-summary.ts +61 -18
- package/telegram-plugin/uat/assertions.ts +21 -2
- package/telegram-plugin/uat/feed-matcher.test.ts +29 -0
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-channel.test.ts +9 -2
- package/telegram-plugin/uat/scenarios/jtbd-liveness-narration-dm.test.ts +9 -2
- package/telegram-plugin/worker-activity-feed.ts +38 -17
- package/vendor/hindsight-memory/scripts/tests/test_recall_request_timeout.py +241 -0
|
@@ -21496,13 +21496,15 @@ var init_schema = __esm(() => {
|
|
|
21496
21496
|
recall: exports_external.object({
|
|
21497
21497
|
max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
|
|
21498
21498
|
cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
|
|
21499
|
+
hook_timeout_seconds: exports_external.number().int().min(1).optional().describe("Ceiling (seconds) Claude Code gives the UserPromptSubmit recall " + "hook before killing it. Stamped into the installed plugin's " + "hooks/hooks.json, so it survives `switchroom apply` reinstalling " + "the plugin. Default 12. Raising it lets slow banks finish at the " + "cost of pre-turn dead air; `parallel_deadline_seconds` and " + "`request_timeout_seconds` are both kept under it. A value below " + "3s is raised to 3s and reported: the fan-out deadline must be at " + "least 1s AND still leave 2s of post-deadline headroom, so a " + "lower ceiling admits no usable envelope at all."),
|
|
21500
|
+
parallel_deadline_seconds: exports_external.number().int().min(1).optional().describe("Shared deadline (seconds) for the whole parallel multi-bank " + "recall fan-out. Slots unfinished when it elapses are abandoned " + "and reported as timed out. Defaults to `hook_timeout_seconds` " + "minus 2s of headroom for block formatting, cache write and " + "stdout flush, so a straggler bank can never push the hook past " + "its ceiling. Set explicitly to override that derivation; a value " + "that would leave less than 2s under the hook ceiling \u2014 including " + "one set EQUAL to it \u2014 is clamped back to `hook_timeout_seconds` " + "minus 2, and the clamp is reported. Equality is not allowed: at " + "zero headroom the hook is killed mid-write and the turn loses " + "both the memories and the recall_log row explaining why."),
|
|
21499
21501
|
query_max_tokens: exports_external.number().int().min(0).optional().describe("Cap on the number of DISTINCT BM25 terms the recall hook may " + "put on the wire. `recallMaxQueryChars` bounds characters, which " + "is not the cost driver: Hindsight OR-joins every query token " + "into one tsquery and Postgres native FTS ranks the entire " + "matched set before the top-60 heapsort, so cost tracks TERMS. " + "An 800-char composed query is ~96 distinct terms and matched " + "119,510 of 135,565 rows on the `overlord` bank (14.0s for the " + "3-arm " + "BM25 UNION), past the per-bank timeout \u2014 96.8% of that agent's " + "own-bank recalls returned nothing. Plugin default is 24 " + "(measured 48,433 rows / 2.7s on the same bank). Terms are " + "chosen recency-first (the latest turn beats prior context), " + "then by selectivity. 0 disables shaping (rollback lever)."),
|
|
21500
21502
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional().describe("Extra terms dropped from the BM25 recall query, on top of the " + "built-in English stopword list. For BANK-SPECIFIC " + "high-document-frequency words a generic stoplist cannot know " + "about: on `overlord`, `switchroom` matches 20% of the bank and " + "`agent` another 20%, purely because that is what the corpus is " + "about, and each such term drags tens of thousands of rows into " + "the ranking. Defaults to []."),
|
|
21501
|
-
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request.
|
|
21503
|
+
request_timeout_seconds: exports_external.number().int().min(1).optional().describe("Per-bank HTTP read timeout, in seconds, for one recall " + "request. Even parallelised, each bank carries its own deadline " + "so ONE hung bank returns empty instead of consuming the shared " + "deadline and starving its siblings. The plugin default is 12 " + "(raised from a hardcoded 8 in #3757, which fired on 96.8% of " + "one agent's own-bank recalls). Switchroom defaults it to the " + "effective `parallel_deadline_seconds` instead \u2014 10 at the " + "shipped ceiling \u2014 because the shared fan-out deadline is " + "already the tighter outer guard, so a per-bank value above it " + "can never bind. An explicitly configured value above the " + "effective deadline is clamped down to it, and the clamp is " + "reported."),
|
|
21502
21504
|
own_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the agent's " + "own bank when recall fans out to more than one bank. The merged " + "set is sorted globally by relevance and head-sliced, which is " + "winner-take-all across banks: when both banks return more " + "candidates than the cap, one bank's score distribution can fill " + "every slot and the agent gets a dossier about its operator with " + "none of its own session memory. A floor, not a quota: at most " + "this many slots, only if the own bank returned that many, and " + "only up to HALF the cap shared with `additional_bank_min_slots` " + "\u2014 the rest is always won on pure relevance, so composition still " + "moves with the scores. Fixes score-based crowd-out only; a " + "timed-out bank returns no candidates and reservation is a no-op " + "there. 0 disables (default). Switchroom-managed agents use 2 " + "against the fleet-deployed cap of 6."),
|
|
21503
21505
|
additional_bank_min_slots: exports_external.number().int().min(0).optional().describe("Slots inside `max_memories` reserved as a FLOOR for the " + "additional (profile / shared / sender) banks. Symmetric with " + "`own_bank_min_slots` \u2014 same floor-not-quota semantics, and the " + "two share the same half-of-cap reservation budget. When they sum " + "above that budget the own-bank floor is honoured first. 0 " + "disables (default). Switchroom-managed agents use 1 against the " + "fleet-deployed cap of 6. Observe `injected_own_bank_count` / " + "`injected_additional_bank_count` via " + "`switchroom memory recall-log`."),
|
|
21504
21506
|
types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
|
|
21505
|
-
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with
|
|
21507
|
+
additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with the `request_timeout_seconds` per-bank timeout " + "(defaults to the effective `parallel_deadline_seconds`, 10s at " + "the shipped ceiling) and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
|
|
21506
21508
|
sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
|
|
21507
21509
|
skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
|
|
21508
21510
|
topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
|
|
@@ -21861,6 +21863,8 @@ var init_schema = __esm(() => {
|
|
|
21861
21863
|
recall: exports_external.object({
|
|
21862
21864
|
max_memories: exports_external.number().int().min(0).optional(),
|
|
21863
21865
|
cache_ttl_secs: exports_external.number().int().min(0).optional(),
|
|
21866
|
+
hook_timeout_seconds: exports_external.number().int().min(1).optional(),
|
|
21867
|
+
parallel_deadline_seconds: exports_external.number().int().min(1).optional(),
|
|
21864
21868
|
query_max_tokens: exports_external.number().int().min(0).optional(),
|
|
21865
21869
|
query_stop_terms: exports_external.array(exports_external.string().min(1).regex(/^[\w./-]+$/)).optional(),
|
|
21866
21870
|
request_timeout_seconds: exports_external.number().int().min(1).optional(),
|
|
@@ -44389,6 +44393,45 @@ async function handleVoiceMessage(ctx, deps) {
|
|
|
44389
44393
|
}
|
|
44390
44394
|
|
|
44391
44395
|
// gateway/status-pin-store.ts
|
|
44396
|
+
function isPinRow(x) {
|
|
44397
|
+
if (x == null || typeof x !== "object")
|
|
44398
|
+
return false;
|
|
44399
|
+
const o = x;
|
|
44400
|
+
return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number");
|
|
44401
|
+
}
|
|
44402
|
+
function loadStatusPins(path, fs2) {
|
|
44403
|
+
if (!fs2.existsSync(path))
|
|
44404
|
+
return [];
|
|
44405
|
+
let raw = "";
|
|
44406
|
+
try {
|
|
44407
|
+
raw = fs2.readFileSync(path);
|
|
44408
|
+
} catch {
|
|
44409
|
+
return [];
|
|
44410
|
+
}
|
|
44411
|
+
let parsed;
|
|
44412
|
+
try {
|
|
44413
|
+
parsed = JSON.parse(raw);
|
|
44414
|
+
} catch {
|
|
44415
|
+
return [];
|
|
44416
|
+
}
|
|
44417
|
+
if (parsed == null || typeof parsed !== "object")
|
|
44418
|
+
return [];
|
|
44419
|
+
const env = parsed;
|
|
44420
|
+
if (env.v !== 1 && env.v !== 2 || !Array.isArray(env.pins))
|
|
44421
|
+
return [];
|
|
44422
|
+
return env.pins.filter(isPinRow);
|
|
44423
|
+
}
|
|
44424
|
+
function persistStatusPins(path, fs2, snapshot, log = (l) => process.stderr.write(l)) {
|
|
44425
|
+
const env = { v: 2, pins: [...snapshot] };
|
|
44426
|
+
const tmp = path + ".tmp";
|
|
44427
|
+
try {
|
|
44428
|
+
fs2.writeFileSync(tmp, JSON.stringify(env));
|
|
44429
|
+
fs2.renameSync(tmp, path);
|
|
44430
|
+
} catch (err) {
|
|
44431
|
+
log(`status-pin-store: persist FAILED path=${path}: ${err.message} \u2014 ` + `durability degraded to in-memory
|
|
44432
|
+
`);
|
|
44433
|
+
}
|
|
44434
|
+
}
|
|
44392
44435
|
function pinnedMessageIsOurs(tracked, chatId, pinnedMessageId) {
|
|
44393
44436
|
for (const t of tracked) {
|
|
44394
44437
|
if (t.messageId === pinnedMessageId && t.chatId === chatId)
|
|
@@ -44397,7 +44440,46 @@ function pinnedMessageIsOurs(tracked, chatId, pinnedMessageId) {
|
|
|
44397
44440
|
return false;
|
|
44398
44441
|
}
|
|
44399
44442
|
var storeLockTails = new Map;
|
|
44443
|
+
function withStoreLock(path, fn) {
|
|
44444
|
+
const prev = storeLockTails.get(path) ?? Promise.resolve();
|
|
44445
|
+
const run2 = prev.then(fn, fn);
|
|
44446
|
+
storeLockTails.set(path, run2.then(() => {
|
|
44447
|
+
return;
|
|
44448
|
+
}, () => {
|
|
44449
|
+
return;
|
|
44450
|
+
}));
|
|
44451
|
+
return run2;
|
|
44452
|
+
}
|
|
44400
44453
|
var pinReconcileTails = new Map;
|
|
44454
|
+
function applyStatusPinRow(path, fs2, pinKey, row, log) {
|
|
44455
|
+
const current = loadStatusPins(path, fs2);
|
|
44456
|
+
const others = current.filter((p) => p.pinKey !== pinKey);
|
|
44457
|
+
const next = row == null ? others : [...others, row];
|
|
44458
|
+
persistStatusPins(path, fs2, next, log);
|
|
44459
|
+
}
|
|
44460
|
+
function reconcileAndPersistStatusPin(args) {
|
|
44461
|
+
const { path, fs: fs2, pinKey, chatId, op } = args;
|
|
44462
|
+
const log = args.log ?? ((l) => process.stderr.write(l));
|
|
44463
|
+
return withStoreLock(path, async () => {
|
|
44464
|
+
if (op.kind === "pin") {
|
|
44465
|
+
applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: op.messageId, pending: true }, log);
|
|
44466
|
+
const next2 = await args.applyPin();
|
|
44467
|
+
if (next2 == null) {
|
|
44468
|
+
applyStatusPinRow(path, fs2, pinKey, null, log);
|
|
44469
|
+
return null;
|
|
44470
|
+
}
|
|
44471
|
+
applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: next2.messageId }, log);
|
|
44472
|
+
return next2;
|
|
44473
|
+
}
|
|
44474
|
+
const next = await args.applyPin();
|
|
44475
|
+
if (next == null) {
|
|
44476
|
+
applyStatusPinRow(path, fs2, pinKey, null, log);
|
|
44477
|
+
} else {
|
|
44478
|
+
applyStatusPinRow(path, fs2, pinKey, { pinKey, chatId, messageId: next.messageId }, log);
|
|
44479
|
+
}
|
|
44480
|
+
return next;
|
|
44481
|
+
});
|
|
44482
|
+
}
|
|
44401
44483
|
|
|
44402
44484
|
// gateway/pinned-message-handler.ts
|
|
44403
44485
|
async function handlePinnedMessage(ctx, deps) {
|
|
@@ -46504,6 +46586,11 @@ var STATUS_LINE_MAX = 200;
|
|
|
46504
46586
|
var STATUS_CARD_CHAR_BUDGET = RICH_MESSAGE_MAX_CHARS;
|
|
46505
46587
|
var NESTED_PREFIX = " \u21b3 ";
|
|
46506
46588
|
var WORKER_STEP_INDENT = "\u2800\u2800\u2800";
|
|
46589
|
+
var SUBORDINATE_HEADER_PREFIX = "\u2514\u2500 ";
|
|
46590
|
+
var SUBORDINATE_LINE_INDENT = WORKER_STEP_INDENT;
|
|
46591
|
+
function nestSubordinateCardLines(lines) {
|
|
46592
|
+
return lines.map((line, i) => i === 0 ? `${SUBORDINATE_HEADER_PREFIX}${line}` : `${SUBORDINATE_LINE_INDENT}${line}`);
|
|
46593
|
+
}
|
|
46507
46594
|
|
|
46508
46595
|
// hooks/tool-label-pretool.mjs
|
|
46509
46596
|
import { readFileSync as readFileSync9, mkdirSync as mkdirSync10, appendFileSync as appendFileSync2, existsSync as existsSync9 } from "node:fs";
|
|
@@ -46976,7 +47063,9 @@ function renderStatusCard(opts) {
|
|
|
46976
47063
|
}
|
|
46977
47064
|
if (out.length === 0)
|
|
46978
47065
|
return null;
|
|
46979
|
-
const joined = stackCardLines(out
|
|
47066
|
+
const joined = stackCardLines(opts.subordinate === true ? nestSubordinateCardLines(out) : out, {
|
|
47067
|
+
collapseSafe: true
|
|
47068
|
+
});
|
|
46980
47069
|
if (joined.length <= STATUS_CARD_CHAR_BUDGET)
|
|
46981
47070
|
return joined;
|
|
46982
47071
|
return fitCardToBudget(opts, headerLines);
|
|
@@ -46988,6 +47077,9 @@ function fitCardToBudget(opts, headerLines) {
|
|
|
46988
47077
|
const rawSteps = opts.steps.filter((s) => s != null);
|
|
46989
47078
|
const rawChildren = (opts.childSteps ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
|
|
46990
47079
|
const hasChildren = rawChildren.length > 0;
|
|
47080
|
+
const subordinate = opts.subordinate === true;
|
|
47081
|
+
const nestCost = subordinate ? SUBORDINATE_LINE_INDENT.length : 0;
|
|
47082
|
+
const stack = (lines2) => stackCardLines(subordinate ? nestSubordinateCardLines(lines2) : lines2, { collapseSafe: true });
|
|
46991
47083
|
const footerLines = [];
|
|
46992
47084
|
if (final && stepCount != null && stepCount > 0)
|
|
46993
47085
|
footerLines.push(`_\u2713 ${stepCount} steps_`);
|
|
@@ -46996,7 +47088,7 @@ function fitCardToBudget(opts, headerLines) {
|
|
|
46996
47088
|
footerLines.push(`${result.emoji} _${escapeMarkdown(truncate(result.text, WORKER_RESULT_MAX))}_`);
|
|
46997
47089
|
}
|
|
46998
47090
|
const fixedCost = [...headerLines, ...footerLines].join(`
|
|
46999
|
-
`).length;
|
|
47091
|
+
`).length + (headerLines.length + footerLines.length) * nestCost;
|
|
47000
47092
|
const body = hasChildren ? rawChildren : rawSteps;
|
|
47001
47093
|
const escapedBody = body.map(escapeStepLine);
|
|
47002
47094
|
const prefix = hasChildren ? NESTED_PREFIX : "";
|
|
@@ -47011,13 +47103,13 @@ function fitCardToBudget(opts, headerLines) {
|
|
|
47011
47103
|
const lastIdx = shown.length - 1;
|
|
47012
47104
|
shown.forEach((esc, i) => lines2.push(buildBullet(esc, i === lastIdx)));
|
|
47013
47105
|
lines2.push(...footerLines);
|
|
47014
|
-
const candidate =
|
|
47106
|
+
const candidate = stack(lines2);
|
|
47015
47107
|
if (candidate.length <= STATUS_CARD_CHAR_BUDGET)
|
|
47016
47108
|
return candidate;
|
|
47017
47109
|
}
|
|
47018
47110
|
const rawNewest = body.length > 0 ? stripMarkdown(body[body.length - 1]).replace(/\s+/g, " ").trim() : "";
|
|
47019
|
-
const wrapperOverhead = final ? (prefix + "_\u2713 _").length : (prefix + "**\u2192 **").length + liveSuffix.length;
|
|
47020
|
-
const headerFooterCost = fixedCost + (fixedCost > 0 ? 1 : 0) + (parentMarker != null ? parentMarker.length + 1 : 0);
|
|
47111
|
+
const wrapperOverhead = (final ? (prefix + "_\u2713 _").length : (prefix + "**\u2192 **").length + liveSuffix.length) + nestCost;
|
|
47112
|
+
const headerFooterCost = fixedCost + (fixedCost > 0 ? 1 : 0) + (parentMarker != null ? parentMarker.length + nestCost + 1 : 0);
|
|
47021
47113
|
const budget = STATUS_CARD_CHAR_BUDGET - headerFooterCost - wrapperOverhead;
|
|
47022
47114
|
let raw = rawNewest.slice(0, Math.max(0, budget));
|
|
47023
47115
|
let newest = escapeMarkdown(raw);
|
|
@@ -47032,7 +47124,7 @@ function fitCardToBudget(opts, headerLines) {
|
|
|
47032
47124
|
lines.push(parentMarker);
|
|
47033
47125
|
lines.push(newestLine);
|
|
47034
47126
|
lines.push(...footerLines);
|
|
47035
|
-
return
|
|
47127
|
+
return stack(lines);
|
|
47036
47128
|
}
|
|
47037
47129
|
function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, header) {
|
|
47038
47130
|
if (lines.length === 0 && header == null)
|
|
@@ -47090,7 +47182,7 @@ function glanceLine(rows) {
|
|
|
47090
47182
|
const tools = rows.reduce((n, r) => n + r.toolCount, 0);
|
|
47091
47183
|
const tok = rows.reduce((n, r) => n + (r.totalTokens ?? 0), 0);
|
|
47092
47184
|
const toolWord = tools === 1 ? "tool" : "tools";
|
|
47093
|
-
return `\uD83D\uDEE0 **
|
|
47185
|
+
return `\uD83D\uDEE0 **WORKERS** \u00b7 _${rows.length} running \u00b7 oldest ${formatFeedElapsed(oldestMs)}` + ` \u00b7 ${tools} ${toolWord}${tokenSegment(tok)}_`;
|
|
47094
47186
|
}
|
|
47095
47187
|
function renderCombinedWorkerFeed(rows, opts) {
|
|
47096
47188
|
if (rows.length === 0)
|
|
@@ -47129,7 +47221,10 @@ function renderCombinedWorkerFeed(rows, opts) {
|
|
|
47129
47221
|
const out = [...chrome, ...bodyOut];
|
|
47130
47222
|
if (hidden > 0)
|
|
47131
47223
|
out.push(`_+${hidden} more working\u2026_`);
|
|
47132
|
-
return {
|
|
47224
|
+
return {
|
|
47225
|
+
body: stackCardLines(nestSubordinateCardLines(out), { collapseSafe: true }),
|
|
47226
|
+
bodyLines: bodyOut.length
|
|
47227
|
+
};
|
|
47133
47228
|
};
|
|
47134
47229
|
let visible = Math.min(rows.length, maxRows);
|
|
47135
47230
|
let { body, bodyLines } = compose(visible);
|
|
@@ -47185,7 +47280,7 @@ function renderWorkerActivity(v, liveSuffix = "") {
|
|
|
47185
47280
|
}
|
|
47186
47281
|
const header = {
|
|
47187
47282
|
emoji: "\uD83D\uDEE0",
|
|
47188
|
-
label: "
|
|
47283
|
+
label: "WORKER",
|
|
47189
47284
|
description: desc,
|
|
47190
47285
|
elapsedMs: v.elapsedMs,
|
|
47191
47286
|
toolCount: v.toolCount,
|
|
@@ -47205,21 +47300,22 @@ function renderWorkerActivity(v, liveSuffix = "") {
|
|
|
47205
47300
|
final: finished,
|
|
47206
47301
|
liveSuffix: finished ? "" : liveSuffix,
|
|
47207
47302
|
result,
|
|
47208
|
-
historyWindow: workerHistoryDepth(1)
|
|
47303
|
+
historyWindow: workerHistoryDepth(1),
|
|
47304
|
+
subordinate: true
|
|
47209
47305
|
});
|
|
47210
47306
|
if (card == null) {
|
|
47211
|
-
return
|
|
47307
|
+
return `${SUBORDINATE_HEADER_PREFIX}\uD83D\uDEE0 **WORKER** \u00b7 _starting\u2026_`;
|
|
47212
47308
|
}
|
|
47213
47309
|
if (!finished && steps.length === 0) {
|
|
47214
47310
|
return `${card}${COLLAPSE_SAFE_SEPARATOR}
|
|
47215
|
-
_starting\u2026_`;
|
|
47311
|
+
${SUBORDINATE_LINE_INDENT}_starting\u2026_`;
|
|
47216
47312
|
}
|
|
47217
47313
|
return card;
|
|
47218
47314
|
}
|
|
47219
47315
|
var COOLDOWN_JITTER_MS = 500;
|
|
47220
|
-
var WORKER_CARD_SUPERSEDED_BODY =
|
|
47316
|
+
var WORKER_CARD_SUPERSEDED_BODY = `${SUBORDINATE_HEADER_PREFIX}\uD83D\uDEE0 **WORKER** \u00b7 _continued_
|
|
47221
47317
|
|
|
47222
|
-
_Live progress moved to a fresh card to stay pinned._`;
|
|
47318
|
+
` + `${SUBORDINATE_LINE_INDENT}_Live progress moved to a fresh card to stay pinned._`;
|
|
47223
47319
|
function extractRetryAfterSecs(err) {
|
|
47224
47320
|
if (err == null || typeof err !== "object")
|
|
47225
47321
|
return null;
|
|
@@ -48095,7 +48191,11 @@ function decidePinAction(prev, desired) {
|
|
|
48095
48191
|
if (prev.messageId === desired.messageId) {
|
|
48096
48192
|
return { kind: "noop", reason: "already pinned this message" };
|
|
48097
48193
|
}
|
|
48098
|
-
return {
|
|
48194
|
+
return {
|
|
48195
|
+
kind: "repin",
|
|
48196
|
+
unpinMessageId: prev.messageId,
|
|
48197
|
+
pinMessageId: desired.messageId
|
|
48198
|
+
};
|
|
48099
48199
|
}
|
|
48100
48200
|
function errorDescription(err) {
|
|
48101
48201
|
if (err != null && typeof err === "object") {
|
|
@@ -48144,6 +48244,19 @@ async function reconcilePin(args) {
|
|
|
48144
48244
|
const action = decidePinAction(args.prevState, args.desired);
|
|
48145
48245
|
if (action.kind === "noop")
|
|
48146
48246
|
return args.prevState;
|
|
48247
|
+
if (action.kind === "repin") {
|
|
48248
|
+
const afterUnpin = await reconcilePin({
|
|
48249
|
+
...args,
|
|
48250
|
+
desired: { pinned: false }
|
|
48251
|
+
});
|
|
48252
|
+
if (afterUnpin != null)
|
|
48253
|
+
return afterUnpin;
|
|
48254
|
+
return reconcilePin({
|
|
48255
|
+
...args,
|
|
48256
|
+
prevState: null,
|
|
48257
|
+
desired: { pinned: true, messageId: action.pinMessageId }
|
|
48258
|
+
});
|
|
48259
|
+
}
|
|
48147
48260
|
if (action.kind === "unpin") {
|
|
48148
48261
|
if (args.rightsCache?.isBlocked(args.chatId))
|
|
48149
48262
|
return null;
|
|
@@ -48184,19 +48297,6 @@ async function reconcilePin(args) {
|
|
|
48184
48297
|
}
|
|
48185
48298
|
|
|
48186
48299
|
// status-pin.ts
|
|
48187
|
-
function decidePinAction2(prev, desired) {
|
|
48188
|
-
if (!desired.pinned) {
|
|
48189
|
-
if (prev)
|
|
48190
|
-
return { kind: "unpin", messageId: prev.messageId };
|
|
48191
|
-
return { kind: "noop", reason: "nothing pinned, nothing wanted" };
|
|
48192
|
-
}
|
|
48193
|
-
if (prev == null)
|
|
48194
|
-
return { kind: "pin", messageId: desired.messageId };
|
|
48195
|
-
if (prev.messageId === desired.messageId) {
|
|
48196
|
-
return { kind: "noop", reason: "already pinned this message" };
|
|
48197
|
-
}
|
|
48198
|
-
return { kind: "unpin", messageId: prev.messageId };
|
|
48199
|
-
}
|
|
48200
48300
|
class PinRightsCache2 {
|
|
48201
48301
|
blocked = new Set;
|
|
48202
48302
|
isBlocked(chatId) {
|
|
@@ -83028,6 +83128,12 @@ init_peercred();
|
|
|
83028
83128
|
|
|
83029
83129
|
// ../src/agents/scaffold.ts
|
|
83030
83130
|
init_generation_stamp();
|
|
83131
|
+
|
|
83132
|
+
// ../src/setup/hindsight-recall-tunables.ts
|
|
83133
|
+
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
83134
|
+
var MIN_RECALL_HOOK_TIMEOUT_SECONDS = RECALL_DEADLINE_HEADROOM_SECONDS + 1;
|
|
83135
|
+
|
|
83136
|
+
// ../src/agents/scaffold.ts
|
|
83031
83137
|
init_schema();
|
|
83032
83138
|
|
|
83033
83139
|
// ../src/config/users.ts
|
|
@@ -84974,6 +85080,97 @@ async function runBootPinSweepSteps(deps) {
|
|
|
84974
85080
|
await step(`dm-sweep:${id}`, () => deps.sweepDm(id), log);
|
|
84975
85081
|
}
|
|
84976
85082
|
|
|
85083
|
+
// gateway/status-pin-retarget.ts
|
|
85084
|
+
async function runStatusPinReconcile(args) {
|
|
85085
|
+
const { pinKey, chatId, prev, desired, persist, runPin, registries } = args;
|
|
85086
|
+
const commit = (next) => {
|
|
85087
|
+
if (next == null) {
|
|
85088
|
+
registries.state.delete(pinKey);
|
|
85089
|
+
registries.chatIds.delete(pinKey);
|
|
85090
|
+
registries.pinnedAt.delete(pinKey);
|
|
85091
|
+
return;
|
|
85092
|
+
}
|
|
85093
|
+
registries.state.set(pinKey, next);
|
|
85094
|
+
registries.chatIds.set(pinKey, chatId);
|
|
85095
|
+
if (!registries.pinnedAt.has(pinKey))
|
|
85096
|
+
registries.pinnedAt.set(pinKey, Date.now());
|
|
85097
|
+
};
|
|
85098
|
+
const runLeg = (from, want) => {
|
|
85099
|
+
if (persist == null)
|
|
85100
|
+
return runPin(from, want);
|
|
85101
|
+
const legAction = decidePinAction(from, want);
|
|
85102
|
+
const op = legAction.kind === "pin" ? { kind: "pin", messageId: legAction.messageId } : { kind: "clear" };
|
|
85103
|
+
return reconcileAndPersistStatusPin({
|
|
85104
|
+
path: persist.path,
|
|
85105
|
+
fs: persist.fs,
|
|
85106
|
+
pinKey,
|
|
85107
|
+
chatId,
|
|
85108
|
+
op,
|
|
85109
|
+
applyPin: () => runPin(from, want)
|
|
85110
|
+
});
|
|
85111
|
+
};
|
|
85112
|
+
const action = decidePinAction(prev, desired);
|
|
85113
|
+
if (action.kind === "repin" && desired.pinned) {
|
|
85114
|
+
const afterUnpin = await runLeg(prev, { pinned: false });
|
|
85115
|
+
commit(afterUnpin);
|
|
85116
|
+
if (afterUnpin != null)
|
|
85117
|
+
return;
|
|
85118
|
+
commit(await runLeg(null, desired));
|
|
85119
|
+
return;
|
|
85120
|
+
}
|
|
85121
|
+
commit(await runLeg(prev, desired));
|
|
85122
|
+
}
|
|
85123
|
+
|
|
85124
|
+
// gateway/periodic-sweep-guard.ts
|
|
85125
|
+
function createPeriodicSweepGuard(args) {
|
|
85126
|
+
let running = false;
|
|
85127
|
+
let skippedCount = 0;
|
|
85128
|
+
const notify = (fn) => {
|
|
85129
|
+
if (fn == null)
|
|
85130
|
+
return;
|
|
85131
|
+
try {
|
|
85132
|
+
fn();
|
|
85133
|
+
} catch {}
|
|
85134
|
+
};
|
|
85135
|
+
return {
|
|
85136
|
+
async tick() {
|
|
85137
|
+
if (running) {
|
|
85138
|
+
skippedCount += 1;
|
|
85139
|
+
notify(args.onSkip);
|
|
85140
|
+
return;
|
|
85141
|
+
}
|
|
85142
|
+
running = true;
|
|
85143
|
+
try {
|
|
85144
|
+
await args.run();
|
|
85145
|
+
} catch (err) {
|
|
85146
|
+
notify(args.onError == null ? undefined : () => args.onError?.(err));
|
|
85147
|
+
} finally {
|
|
85148
|
+
running = false;
|
|
85149
|
+
}
|
|
85150
|
+
},
|
|
85151
|
+
isRunning() {
|
|
85152
|
+
return running;
|
|
85153
|
+
},
|
|
85154
|
+
skipped() {
|
|
85155
|
+
return skippedCount;
|
|
85156
|
+
}
|
|
85157
|
+
};
|
|
85158
|
+
}
|
|
85159
|
+
|
|
85160
|
+
// gateway/with-deadline.ts
|
|
85161
|
+
function withDeadline(p, ms, timeoutMessage) {
|
|
85162
|
+
p.catch(() => {});
|
|
85163
|
+
let timer3;
|
|
85164
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
85165
|
+
timer3 = setTimeout(() => reject(new Error(timeoutMessage)), ms);
|
|
85166
|
+
timer3.unref?.();
|
|
85167
|
+
});
|
|
85168
|
+
return Promise.race([p, deadline]).finally(() => {
|
|
85169
|
+
if (timer3 !== undefined)
|
|
85170
|
+
clearTimeout(timer3);
|
|
85171
|
+
});
|
|
85172
|
+
}
|
|
85173
|
+
|
|
84977
85174
|
// gateway/status-pin-api.ts
|
|
84978
85175
|
class BotNotReadyError extends Error {
|
|
84979
85176
|
constructor(what) {
|
|
@@ -87879,13 +88076,13 @@ function createCapturedResumeDispatcher(ports) {
|
|
|
87879
88076
|
|
|
87880
88077
|
// gateway/status-pin-store.ts
|
|
87881
88078
|
var BOOT_UNPIN_MAX_ATTEMPTS = 5;
|
|
87882
|
-
function
|
|
88079
|
+
function isPinRow2(x) {
|
|
87883
88080
|
if (x == null || typeof x !== "object")
|
|
87884
88081
|
return false;
|
|
87885
88082
|
const o = x;
|
|
87886
88083
|
return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number");
|
|
87887
88084
|
}
|
|
87888
|
-
function
|
|
88085
|
+
function loadStatusPins2(path3, fs3) {
|
|
87889
88086
|
if (!fs3.existsSync(path3))
|
|
87890
88087
|
return [];
|
|
87891
88088
|
let raw = "";
|
|
@@ -87905,9 +88102,9 @@ function loadStatusPins(path3, fs3) {
|
|
|
87905
88102
|
const env = parsed;
|
|
87906
88103
|
if (env.v !== 1 && env.v !== 2 || !Array.isArray(env.pins))
|
|
87907
88104
|
return [];
|
|
87908
|
-
return env.pins.filter(
|
|
88105
|
+
return env.pins.filter(isPinRow2);
|
|
87909
88106
|
}
|
|
87910
|
-
function
|
|
88107
|
+
function persistStatusPins2(path3, fs3, snapshot, log = (l) => process.stderr.write(l)) {
|
|
87911
88108
|
const env = { v: 2, pins: [...snapshot] };
|
|
87912
88109
|
const tmp = path3 + ".tmp";
|
|
87913
88110
|
try {
|
|
@@ -87921,7 +88118,7 @@ function persistStatusPins(path3, fs3, snapshot, log = (l) => process.stderr.wri
|
|
|
87921
88118
|
async function runStatusPinBootCleanup(args) {
|
|
87922
88119
|
const log = args.log ?? ((l) => process.stderr.write(l));
|
|
87923
88120
|
const now = args.now ?? Date.now();
|
|
87924
|
-
const persisted =
|
|
88121
|
+
const persisted = loadStatusPins2(args.path, args.fs);
|
|
87925
88122
|
if (persisted.length === 0)
|
|
87926
88123
|
return { cleared: 0, retained: 0, kept: 0, total: 0 };
|
|
87927
88124
|
let cleared = 0;
|
|
@@ -87950,11 +88147,11 @@ async function runStatusPinBootCleanup(args) {
|
|
|
87950
88147
|
}
|
|
87951
88148
|
}
|
|
87952
88149
|
}
|
|
87953
|
-
|
|
88150
|
+
persistStatusPins2(args.path, args.fs, next, log);
|
|
87954
88151
|
return { cleared, retained, kept, total: persisted.length };
|
|
87955
88152
|
}
|
|
87956
88153
|
var storeLockTails2 = new Map;
|
|
87957
|
-
function
|
|
88154
|
+
function withStoreLock2(path3, fn) {
|
|
87958
88155
|
const prev = storeLockTails2.get(path3) ?? Promise.resolve();
|
|
87959
88156
|
const run3 = prev.then(fn, fn);
|
|
87960
88157
|
storeLockTails2.set(path3, run3.then(() => {
|
|
@@ -87975,38 +88172,15 @@ function withPinReconcileLock(pinKey, fn) {
|
|
|
87975
88172
|
}));
|
|
87976
88173
|
return run3;
|
|
87977
88174
|
}
|
|
87978
|
-
function
|
|
87979
|
-
const current =
|
|
88175
|
+
function applyStatusPinRow2(path3, fs3, pinKey, row, log) {
|
|
88176
|
+
const current = loadStatusPins2(path3, fs3);
|
|
87980
88177
|
const others = current.filter((p) => p.pinKey !== pinKey);
|
|
87981
88178
|
const next = row == null ? others : [...others, row];
|
|
87982
|
-
|
|
88179
|
+
persistStatusPins2(path3, fs3, next, log);
|
|
87983
88180
|
}
|
|
87984
88181
|
function mutateStatusPinRow(path3, fs3, pinKey, row, log = (l) => process.stderr.write(l)) {
|
|
87985
|
-
return
|
|
87986
|
-
|
|
87987
|
-
});
|
|
87988
|
-
}
|
|
87989
|
-
function reconcileAndPersistStatusPin(args) {
|
|
87990
|
-
const { path: path3, fs: fs3, pinKey, chatId, op } = args;
|
|
87991
|
-
const log = args.log ?? ((l) => process.stderr.write(l));
|
|
87992
|
-
return withStoreLock(path3, async () => {
|
|
87993
|
-
if (op.kind === "pin") {
|
|
87994
|
-
applyStatusPinRow(path3, fs3, pinKey, { pinKey, chatId, messageId: op.messageId, pending: true }, log);
|
|
87995
|
-
const next2 = await args.applyPin();
|
|
87996
|
-
if (next2 == null) {
|
|
87997
|
-
applyStatusPinRow(path3, fs3, pinKey, null, log);
|
|
87998
|
-
return null;
|
|
87999
|
-
}
|
|
88000
|
-
applyStatusPinRow(path3, fs3, pinKey, { pinKey, chatId, messageId: next2.messageId }, log);
|
|
88001
|
-
return next2;
|
|
88002
|
-
}
|
|
88003
|
-
const next = await args.applyPin();
|
|
88004
|
-
if (next == null) {
|
|
88005
|
-
applyStatusPinRow(path3, fs3, pinKey, null, log);
|
|
88006
|
-
} else {
|
|
88007
|
-
applyStatusPinRow(path3, fs3, pinKey, { pinKey, chatId, messageId: next.messageId }, log);
|
|
88008
|
-
}
|
|
88009
|
-
return next;
|
|
88182
|
+
return withStoreLock2(path3, async () => {
|
|
88183
|
+
applyStatusPinRow2(path3, fs3, pinKey, row, log);
|
|
88010
88184
|
});
|
|
88011
88185
|
}
|
|
88012
88186
|
|
|
@@ -90059,7 +90233,7 @@ function buildObligationRepresentInbound(o, now) {
|
|
|
90059
90233
|
}
|
|
90060
90234
|
|
|
90061
90235
|
// gateway/with-deadline.ts
|
|
90062
|
-
function
|
|
90236
|
+
function withDeadline2(p, ms, timeoutMessage) {
|
|
90063
90237
|
p.catch(() => {});
|
|
90064
90238
|
let timer3;
|
|
90065
90239
|
const deadline = new Promise((_resolve, reject) => {
|
|
@@ -90076,7 +90250,7 @@ function withDeadline(p, ms, timeoutMessage) {
|
|
|
90076
90250
|
function driveEscalation(args) {
|
|
90077
90251
|
const { escId, inFlight, ledger, send, maxAttempts, deadlineMs } = args;
|
|
90078
90252
|
const log = args.log ?? ((l) => process.stderr.write(l));
|
|
90079
|
-
const wd = args.withDeadlineFn ??
|
|
90253
|
+
const wd = args.withDeadlineFn ?? withDeadline2;
|
|
90080
90254
|
if (inFlight.has(escId))
|
|
90081
90255
|
return;
|
|
90082
90256
|
const attempt = ledger.markEscalateAttempt(escId);
|
|
@@ -96617,10 +96791,10 @@ function startOutboxSweep(deps) {
|
|
|
96617
96791
|
}
|
|
96618
96792
|
|
|
96619
96793
|
// ../src/build-info.ts
|
|
96620
|
-
var VERSION2 = "0.19.
|
|
96621
|
-
var COMMIT_SHA = "
|
|
96622
|
-
var COMMIT_DATE = "2026-07-
|
|
96623
|
-
var LATEST_PR =
|
|
96794
|
+
var VERSION2 = "0.19.26";
|
|
96795
|
+
var COMMIT_SHA = "f9314b89";
|
|
96796
|
+
var COMMIT_DATE = "2026-07-27T13:46:37Z";
|
|
96797
|
+
var LATEST_PR = 3829;
|
|
96624
96798
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
96625
96799
|
|
|
96626
96800
|
// gateway/boot-version.ts
|
|
@@ -101873,7 +102047,7 @@ async function runMidSessionCardReaper() {
|
|
|
101873
102047
|
pinnedAt: statusPinPinnedAt.get(k) ?? now
|
|
101874
102048
|
}));
|
|
101875
102049
|
const storeOnlyCandidates = statusPinPersistEnabled ? storeOnlyWorkerPinCandidates({
|
|
101876
|
-
rows:
|
|
102050
|
+
rows: loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs),
|
|
101877
102051
|
inMemoryPinKeys: inMemoryKeys,
|
|
101878
102052
|
now
|
|
101879
102053
|
}) : [];
|
|
@@ -101926,8 +102100,15 @@ async function runMidSessionCardReaper() {
|
|
|
101926
102100
|
}
|
|
101927
102101
|
}
|
|
101928
102102
|
}
|
|
102103
|
+
var midSessionReaperGuard = createPeriodicSweepGuard({
|
|
102104
|
+
run: runMidSessionCardReaper,
|
|
102105
|
+
onSkip: () => process.stderr.write(`telegram gateway: mid-session reaper tick skipped \u2014 previous pass still running
|
|
102106
|
+
`),
|
|
102107
|
+
onError: (err) => process.stderr.write(`telegram gateway: mid-session reaper pass threw: ${err.message}
|
|
102108
|
+
`)
|
|
102109
|
+
});
|
|
101929
102110
|
var midSessionCardReaper = isGatewayMain ? setInterval(() => {
|
|
101930
|
-
|
|
102111
|
+
midSessionReaperGuard.tick();
|
|
101931
102112
|
}, MID_SESSION_CARD_REAPER_INTERVAL_MS) : undefined;
|
|
101932
102113
|
midSessionCardReaper?.unref();
|
|
101933
102114
|
async function reconcileStatusPin(pinKey, chatId, desired) {
|
|
@@ -101945,11 +102126,11 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
|
|
|
101945
102126
|
if (chatId.length === 0)
|
|
101946
102127
|
return;
|
|
101947
102128
|
const prev = statusPinState.get(pinKey) ?? null;
|
|
101948
|
-
const
|
|
102129
|
+
const runReconcileFrom = (from, want) => reconcilePin({
|
|
101949
102130
|
api: statusPinApi(),
|
|
101950
102131
|
chatId,
|
|
101951
|
-
prevState:
|
|
101952
|
-
desired,
|
|
102132
|
+
prevState: from,
|
|
102133
|
+
desired: want,
|
|
101953
102134
|
rightsCache: statusPinRightsCache,
|
|
101954
102135
|
onPinRightsDisabled: (chat) => {
|
|
101955
102136
|
process.stderr.write(`telegram gateway: status-pin disabled for chat ${chat}: bot lacks pin rights; grant 'Pin messages' admin right to re-enable after restart
|
|
@@ -101961,40 +102142,19 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
|
|
|
101961
102142
|
`);
|
|
101962
102143
|
}
|
|
101963
102144
|
});
|
|
101964
|
-
|
|
101965
|
-
const op = action.kind === "pin" ? { kind: "pin", messageId: action.messageId } : { kind: "clear" };
|
|
101966
|
-
if (!statusPinPersistEnabled) {
|
|
101967
|
-
const next2 = await runReconcile();
|
|
101968
|
-
if (next2 == null) {
|
|
101969
|
-
statusPinState.delete(pinKey);
|
|
101970
|
-
statusPinChatIds.delete(pinKey);
|
|
101971
|
-
statusPinPinnedAt.delete(pinKey);
|
|
101972
|
-
} else {
|
|
101973
|
-
statusPinState.set(pinKey, next2);
|
|
101974
|
-
statusPinChatIds.set(pinKey, chatId);
|
|
101975
|
-
if (!statusPinPinnedAt.has(pinKey))
|
|
101976
|
-
statusPinPinnedAt.set(pinKey, Date.now());
|
|
101977
|
-
}
|
|
101978
|
-
return;
|
|
101979
|
-
}
|
|
101980
|
-
const next = await reconcileAndPersistStatusPin({
|
|
101981
|
-
path: STATUS_PIN_STORE_PATH,
|
|
101982
|
-
fs: statusPinStoreFs,
|
|
102145
|
+
await runStatusPinReconcile({
|
|
101983
102146
|
pinKey,
|
|
101984
102147
|
chatId,
|
|
101985
|
-
|
|
101986
|
-
|
|
102148
|
+
prev,
|
|
102149
|
+
desired,
|
|
102150
|
+
persist: statusPinPersistEnabled ? { path: STATUS_PIN_STORE_PATH, fs: statusPinStoreFs } : null,
|
|
102151
|
+
runPin: runReconcileFrom,
|
|
102152
|
+
registries: {
|
|
102153
|
+
state: statusPinState,
|
|
102154
|
+
chatIds: statusPinChatIds,
|
|
102155
|
+
pinnedAt: statusPinPinnedAt
|
|
102156
|
+
}
|
|
101987
102157
|
});
|
|
101988
|
-
if (next == null) {
|
|
101989
|
-
statusPinState.delete(pinKey);
|
|
101990
|
-
statusPinChatIds.delete(pinKey);
|
|
101991
|
-
statusPinPinnedAt.delete(pinKey);
|
|
101992
|
-
} else {
|
|
101993
|
-
statusPinState.set(pinKey, next);
|
|
101994
|
-
statusPinChatIds.set(pinKey, chatId);
|
|
101995
|
-
if (!statusPinPinnedAt.has(pinKey))
|
|
101996
|
-
statusPinPinnedAt.set(pinKey, Date.now());
|
|
101997
|
-
}
|
|
101998
102158
|
}
|
|
101999
102159
|
async function unpinAllStatusPins() {
|
|
102000
102160
|
const keys = [...statusPinState.keys()];
|
|
@@ -102028,7 +102188,7 @@ var dmPinSweeper = createDmPinSweeper({
|
|
|
102028
102188
|
}
|
|
102029
102189
|
if (statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled) {
|
|
102030
102190
|
try {
|
|
102031
|
-
ids.push(...unexpiredStoreRepinIds(
|
|
102191
|
+
ids.push(...unexpiredStoreRepinIds(loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs), chatId, Date.now()));
|
|
102032
102192
|
} catch (err) {
|
|
102033
102193
|
process.stderr.write(`telegram gateway: dm-pin-sweep: store repin scan failed (chat=${chatId}): ${err.message}
|
|
102034
102194
|
`);
|
|
@@ -102042,7 +102202,7 @@ var dmPinSweeper = createDmPinSweeper({
|
|
|
102042
102202
|
function runBootPinCleanupAndDmSweep() {
|
|
102043
102203
|
return runBootPinSweepSteps({
|
|
102044
102204
|
scanDmChatIds: () => collectDmChatIdsFromStores({
|
|
102045
|
-
statusPins: statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled ?
|
|
102205
|
+
statusPins: statusPinPersistEnabled || bannerPinPersistEnabled || toolPinPersistEnabled ? loadStatusPins2(STATUS_PIN_STORE_PATH, statusPinStoreFs) : [],
|
|
102046
102206
|
activityCards: activityCardPersistEnabled ? loadActivityCards2(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs) : [],
|
|
102047
102207
|
queuedCards: queuedCardPersistEnabled ? loadQueuedCards(QUEUED_CARD_STORE_PATH, queuedCardStoreFs) : []
|
|
102048
102208
|
}),
|
|
@@ -109375,6 +109535,12 @@ async function shutdown(signal) {
|
|
|
109375
109535
|
budgetMs: SHUTDOWN_DRAIN_BUDGET_MS,
|
|
109376
109536
|
agentName: agentName3
|
|
109377
109537
|
});
|
|
109538
|
+
try {
|
|
109539
|
+
await withDeadline(unpinAllStatusPins(), 5000, "status-pin shutdown sweep timed out");
|
|
109540
|
+
} catch (err) {
|
|
109541
|
+
process.stderr.write(`telegram gateway: shutdown status-pin sweep incomplete: ${err.message}
|
|
109542
|
+
`);
|
|
109543
|
+
}
|
|
109378
109544
|
inboundCoalescer.reset();
|
|
109379
109545
|
pendingReauthFlows.clear();
|
|
109380
109546
|
for (const [, v] of pendingLoopbackFlows2)
|