switchroom 0.19.37 → 0.19.39
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/auth-broker/index.js +3 -3
- package/dist/cli/switchroom.js +638 -385
- package/dist/host-control/main.js +4 -4
- package/dist/vault/approvals/kernel-server.js +3 -3
- package/dist/vault/broker/server.js +3 -3
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +149 -21
- package/telegram-plugin/gateway/gateway.ts +12 -4
- package/telegram-plugin/gateway/handback-preturn-signal.ts +68 -3
- package/telegram-plugin/gateway/stream-render.ts +238 -17
- package/telegram-plugin/hooks/tool-label-pretool.mjs +119 -1
- package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +19 -12
- package/telegram-plugin/tests/fixtures/pretool-main-2.1.219.json +15 -0
- package/telegram-plugin/tests/fixtures/pretool-subagent-2.1.219.json +16 -0
- package/telegram-plugin/tests/handback-preturn-signal.test.ts +67 -0
- package/telegram-plugin/tests/queued-card-surface.test.ts +262 -0
- package/telegram-plugin/tests/sidechain-label-filter-pretool.test.ts +272 -0
- package/vendor/hindsight-memory/scripts/lib/client.py +7 -4
- package/vendor/hindsight-memory/scripts/lib/config.py +151 -0
- package/vendor/hindsight-memory/scripts/retain.py +19 -15
- package/vendor/hindsight-memory/scripts/tests/test_backfill.py +14 -1
- package/vendor/hindsight-memory/scripts/tests/test_observation_scopes.py +192 -3
- package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +18 -2
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +57 -4
|
@@ -21311,7 +21311,7 @@ function allocateAgentUid(name) {
|
|
|
21311
21311
|
}
|
|
21312
21312
|
|
|
21313
21313
|
// src/build-info.ts
|
|
21314
|
-
var VERSION = "0.19.
|
|
21314
|
+
var VERSION = "0.19.39";
|
|
21315
21315
|
|
|
21316
21316
|
// src/setup/hindsight-recall-tunables.ts
|
|
21317
21317
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -21440,8 +21440,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
|
21440
21440
|
var HINDSIGHT_PG_APP_ANON_MIB = 2560;
|
|
21441
21441
|
var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
|
|
21442
21442
|
var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
|
|
21443
|
-
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
21444
|
-
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB =
|
|
21443
|
+
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
|
|
21444
|
+
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
|
|
21445
21445
|
function pgMib(mib) {
|
|
21446
21446
|
return `${mib}MB`;
|
|
21447
21447
|
}
|
|
@@ -21485,7 +21485,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
|
|
|
21485
21485
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
|
|
21486
21486
|
var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
21487
21487
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
21488
|
-
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND =
|
|
21488
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
|
|
21489
21489
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
21490
21490
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
21491
21491
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
@@ -19337,8 +19337,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
|
19337
19337
|
var HINDSIGHT_PG_APP_ANON_MIB = 2560;
|
|
19338
19338
|
var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
|
|
19339
19339
|
var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
|
|
19340
|
-
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
19341
|
-
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB =
|
|
19340
|
+
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
|
|
19341
|
+
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
|
|
19342
19342
|
function pgMib(mib) {
|
|
19343
19343
|
return `${mib}MB`;
|
|
19344
19344
|
}
|
|
@@ -19382,7 +19382,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
|
|
|
19382
19382
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
|
|
19383
19383
|
var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
19384
19384
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
19385
|
-
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND =
|
|
19385
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
|
|
19386
19386
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
19387
19387
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
19388
19388
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
@@ -19750,8 +19750,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
|
19750
19750
|
var HINDSIGHT_PG_APP_ANON_MIB = 2560;
|
|
19751
19751
|
var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
|
|
19752
19752
|
var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
|
|
19753
|
-
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
19754
|
-
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB =
|
|
19753
|
+
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
|
|
19754
|
+
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
|
|
19755
19755
|
function pgMib(mib) {
|
|
19756
19756
|
return `${mib}MB`;
|
|
19757
19757
|
}
|
|
@@ -19795,7 +19795,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
|
|
|
19795
19795
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
|
|
19796
19796
|
var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
19797
19797
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
19798
|
-
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND =
|
|
19798
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
|
|
19799
19799
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
19800
19800
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
19801
19801
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.39",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -47609,6 +47609,23 @@ function computeLabel(toolName, input) {
|
|
|
47609
47609
|
}
|
|
47610
47610
|
return "Working\u2026";
|
|
47611
47611
|
}
|
|
47612
|
+
function isSubagentToolCall(event) {
|
|
47613
|
+
if (!event || typeof event !== "object")
|
|
47614
|
+
return false;
|
|
47615
|
+
const at = event.agent_type;
|
|
47616
|
+
if (typeof at === "string" && at.length > 0)
|
|
47617
|
+
return at !== "main";
|
|
47618
|
+
const aid = event.agent_id;
|
|
47619
|
+
const sid = event.session_id;
|
|
47620
|
+
return typeof aid === "string" && aid.length > 0 && typeof sid === "string" && sid.length > 0 && aid !== sid;
|
|
47621
|
+
}
|
|
47622
|
+
function hasAttributionSignal(event) {
|
|
47623
|
+
if (!event || typeof event !== "object")
|
|
47624
|
+
return false;
|
|
47625
|
+
const at = event.agent_type;
|
|
47626
|
+
const aid = event.agent_id;
|
|
47627
|
+
return typeof at === "string" && at.length > 0 || typeof aid === "string" && aid.length > 0;
|
|
47628
|
+
}
|
|
47612
47629
|
function main() {
|
|
47613
47630
|
const raw = readStdin().trim();
|
|
47614
47631
|
if (!raw)
|
|
@@ -47627,6 +47644,14 @@ function main() {
|
|
|
47627
47644
|
const toolName = event.tool_name;
|
|
47628
47645
|
if (!sessionId || !toolUseId || !toolName)
|
|
47629
47646
|
process.exit(0);
|
|
47647
|
+
if (!hasAttributionSignal(event)) {
|
|
47648
|
+
try {
|
|
47649
|
+
process.stderr.write(`[tool-label-pretool] WARNING: PreToolUse payload carries neither ` + `agent_type nor agent_id \u2014 the sidechain-attribution invariant may be ` + `broken by a Claude Code schema change; sub-agent labels may be ` + `mis-attributed to the parent session (tool_use_id=${toolUseId}).
|
|
47650
|
+
`);
|
|
47651
|
+
} catch {}
|
|
47652
|
+
}
|
|
47653
|
+
if (isSubagentToolCall(event))
|
|
47654
|
+
process.exit(0);
|
|
47630
47655
|
let label;
|
|
47631
47656
|
try {
|
|
47632
47657
|
label = computeLabel(toolName, event.tool_input);
|
|
@@ -53405,6 +53430,12 @@ function createHandbackPreturnSignal(deps) {
|
|
|
53405
53430
|
entry.reapTimer = null;
|
|
53406
53431
|
if (entry.consumed)
|
|
53407
53432
|
return;
|
|
53433
|
+
if (deps.isClaudeBusy?.() === true) {
|
|
53434
|
+
log(`handback-preturn-signal: reap deferred key=${entry.statusKey} ` + `(claude busy \u2014 handback enqueued behind an in-flight turn, not orphaned)
|
|
53435
|
+
`);
|
|
53436
|
+
entry.reapTimer = setTimer(() => reap(entry), adoptTimeoutMs);
|
|
53437
|
+
return;
|
|
53438
|
+
}
|
|
53408
53439
|
entry.consumed = true;
|
|
53409
53440
|
stopTypingUnlessTurnLive(entry, "orphan reap");
|
|
53410
53441
|
if (entry.activityMessageId != null) {
|
|
@@ -53501,6 +53532,13 @@ function createHandbackPreturnSignal(deps) {
|
|
|
53501
53532
|
dropEntry(entry);
|
|
53502
53533
|
return adoption;
|
|
53503
53534
|
},
|
|
53535
|
+
hasPendingForTurnId(turnId) {
|
|
53536
|
+
for (const e of byKey.values()) {
|
|
53537
|
+
if (e.adoptTurnId === turnId && !e.consumed)
|
|
53538
|
+
return true;
|
|
53539
|
+
}
|
|
53540
|
+
return false;
|
|
53541
|
+
},
|
|
53504
53542
|
isPreTurnRecord(turnKey2) {
|
|
53505
53543
|
return turnKey2.startsWith(PRETURN_TURNKEY_PREFIX);
|
|
53506
53544
|
},
|
|
@@ -80098,23 +80136,35 @@ function decideFallbackTeardownNotice2(input) {
|
|
|
80098
80136
|
// gateway/stream-render.ts
|
|
80099
80137
|
var PARKED_TURN_START_MAX = 16;
|
|
80100
80138
|
var PARKED_TURN_START_TTL_MS = 30 * 60000;
|
|
80139
|
+
var QUEUED_CARD_ENABLED = process.env.SWITCHROOM_QUEUED_CARD !== "0";
|
|
80140
|
+
var QUEUED_CARD_HTML = "\u23f3 Queued \u2014 waiting for the current task to finish\u2026";
|
|
80141
|
+
var QUEUED_CARD_FOLDED_HTML = "\u2705 Folded into the current task.";
|
|
80142
|
+
var QUEUED_CARD_EXPIRED_HTML = "\u26a0\ufe0f This queued message timed out before it could start.";
|
|
80101
80143
|
var parkedTurnStarts = [];
|
|
80102
|
-
function pruneParkedTurnStarts(now) {
|
|
80144
|
+
function pruneParkedTurnStarts(now, onDrop) {
|
|
80103
80145
|
for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
|
|
80104
80146
|
if (now - parkedTurnStarts[i].parkedAt > PARKED_TURN_START_TTL_MS) {
|
|
80105
80147
|
const [dropped] = parkedTurnStarts.splice(i, 1);
|
|
80106
80148
|
process.stderr.write(`telegram gateway: parked-turn-start expired chat=${dropped.chatId ?? "-"} ` + `msg=${dropped.messageId ?? "-"} age_ms=${now - dropped.parkedAt}
|
|
80107
80149
|
`);
|
|
80150
|
+
try {
|
|
80151
|
+
onDrop?.(dropped);
|
|
80152
|
+
} catch {}
|
|
80108
80153
|
}
|
|
80109
80154
|
}
|
|
80110
80155
|
}
|
|
80111
|
-
function parkTurnStart(env, now) {
|
|
80112
|
-
|
|
80156
|
+
function parkTurnStart(env, now, onEvict) {
|
|
80157
|
+
const entry = { ...env, parkedAt: now };
|
|
80158
|
+
parkedTurnStarts.push(entry);
|
|
80113
80159
|
while (parkedTurnStarts.length > PARKED_TURN_START_MAX) {
|
|
80114
80160
|
const [dropped] = parkedTurnStarts.splice(0, 1);
|
|
80115
80161
|
process.stderr.write(`telegram gateway: parked-turn-start evicted (cap ${PARKED_TURN_START_MAX}) ` + `chat=${dropped.chatId ?? "-"} msg=${dropped.messageId ?? "-"}
|
|
80116
80162
|
`);
|
|
80163
|
+
try {
|
|
80164
|
+
onEvict?.(dropped);
|
|
80165
|
+
} catch {}
|
|
80117
80166
|
}
|
|
80167
|
+
return entry;
|
|
80118
80168
|
}
|
|
80119
80169
|
function takeParkedTurnStart() {
|
|
80120
80170
|
return parkedTurnStarts.pop() ?? null;
|
|
@@ -80122,11 +80172,56 @@ function takeParkedTurnStart() {
|
|
|
80122
80172
|
function discardParkedTurnStart(rawContent) {
|
|
80123
80173
|
for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
|
|
80124
80174
|
if (parkedTurnStarts[i].rawContent === rawContent) {
|
|
80125
|
-
parkedTurnStarts.splice(i, 1);
|
|
80126
|
-
return
|
|
80175
|
+
const [dropped] = parkedTurnStarts.splice(i, 1);
|
|
80176
|
+
return dropped;
|
|
80127
80177
|
}
|
|
80128
80178
|
}
|
|
80129
|
-
return
|
|
80179
|
+
return null;
|
|
80180
|
+
}
|
|
80181
|
+
async function openQueuedCard(deps, chatId, threadId, replyToMessageId) {
|
|
80182
|
+
try {
|
|
80183
|
+
const sent = await deps.robustApiCall(() => deps.bot.api.sendRichMessage(chatId, richMessage(QUEUED_CARD_HTML), {
|
|
80184
|
+
...threadId != null ? { message_thread_id: threadId } : {},
|
|
80185
|
+
...replyToMessageId != null ? { reply_parameters: { message_id: replyToMessageId, allow_sending_without_reply: true } } : {},
|
|
80186
|
+
disable_notification: true
|
|
80187
|
+
}), {
|
|
80188
|
+
chat_id: chatId,
|
|
80189
|
+
...threadId != null ? { threadId } : {},
|
|
80190
|
+
verb: "queued-card.send"
|
|
80191
|
+
});
|
|
80192
|
+
return sent?.message_id ?? null;
|
|
80193
|
+
} catch (err) {
|
|
80194
|
+
process.stderr.write(`telegram gateway: queued card send failed: ${err.message}
|
|
80195
|
+
`);
|
|
80196
|
+
return null;
|
|
80197
|
+
}
|
|
80198
|
+
}
|
|
80199
|
+
function finalizeQueuedCard(deps, chatId, threadId, messageId, html) {
|
|
80200
|
+
deps.robustApiCall(() => deps.bot.api.editMessageText(chatId, messageId, richMessage(html), {}), {
|
|
80201
|
+
chat_id: chatId,
|
|
80202
|
+
...threadId != null ? { threadId } : {},
|
|
80203
|
+
verb: "queued-card.finalize"
|
|
80204
|
+
}).catch(() => {
|
|
80205
|
+
return;
|
|
80206
|
+
});
|
|
80207
|
+
}
|
|
80208
|
+
function deleteQueuedCard(deps, chatId, messageId) {
|
|
80209
|
+
deps.robustApiCall(() => deps.bot.api.deleteMessage(chatId, messageId), { chat_id: chatId, verb: "queued-card.delete-orphan" }).catch(() => {
|
|
80210
|
+
return;
|
|
80211
|
+
});
|
|
80212
|
+
}
|
|
80213
|
+
function envThreadIdNum(threadId) {
|
|
80214
|
+
if (threadId == null)
|
|
80215
|
+
return null;
|
|
80216
|
+
const n = Number(threadId);
|
|
80217
|
+
return Number.isFinite(n) ? n : null;
|
|
80218
|
+
}
|
|
80219
|
+
function finalizeParkedEntryCard(deps, entry, html) {
|
|
80220
|
+
if (!QUEUED_CARD_ENABLED)
|
|
80221
|
+
return;
|
|
80222
|
+
if (entry.queuedCardMessageId == null || entry.chatId == null)
|
|
80223
|
+
return;
|
|
80224
|
+
finalizeQueuedCard(deps, entry.chatId, envThreadIdNum(entry.threadId), entry.queuedCardMessageId, html);
|
|
80130
80225
|
}
|
|
80131
80226
|
function beginTurn(deps, ev) {
|
|
80132
80227
|
const {
|
|
@@ -80240,6 +80335,16 @@ function beginTurn(deps, ev) {
|
|
|
80240
80335
|
`);
|
|
80241
80336
|
}
|
|
80242
80337
|
}
|
|
80338
|
+
if (QUEUED_CARD_ENABLED && ev.queuedCardMessageId != null && next.activityMessageId == null) {
|
|
80339
|
+
next.activityMessageId = ev.queuedCardMessageId;
|
|
80340
|
+
next.activityEverOpened = true;
|
|
80341
|
+
process.stderr.write(`telegram gateway: queued card adopted turnId=${turnId} card=${ev.queuedCardMessageId}
|
|
80342
|
+
`);
|
|
80343
|
+
} else if (QUEUED_CARD_ENABLED && ev.queuedCardMessageId != null && ev.chatId != null && next.activityMessageId != null && next.activityMessageId !== ev.queuedCardMessageId) {
|
|
80344
|
+
deleteQueuedCard(deps, ev.chatId, ev.queuedCardMessageId);
|
|
80345
|
+
process.stderr.write(`telegram gateway: queued card superseded by handback adoption turnId=${turnId} ` + `queued=${ev.queuedCardMessageId} adopted=${next.activityMessageId}
|
|
80346
|
+
`);
|
|
80347
|
+
}
|
|
80243
80348
|
startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
|
|
80244
80349
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
80245
80350
|
scheduleEarlyLivenessOpen(next);
|
|
@@ -80388,7 +80493,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
80388
80493
|
ackDelivery(deliveryQueue, chatKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null), ev.messageId, ev.rawContent);
|
|
80389
80494
|
}
|
|
80390
80495
|
}
|
|
80391
|
-
pruneParkedTurnStarts(now);
|
|
80496
|
+
pruneParkedTurnStarts(now, (e) => finalizeParkedEntryCard(deps, e, QUEUED_CARD_EXPIRED_HTML));
|
|
80392
80497
|
if (!ev.chatId) {
|
|
80393
80498
|
beginTurn(deps, ev);
|
|
80394
80499
|
return;
|
|
@@ -80399,18 +80504,38 @@ function handleSessionEvent(deps, ev) {
|
|
|
80399
80504
|
beginTurn(deps, ev);
|
|
80400
80505
|
return;
|
|
80401
80506
|
}
|
|
80402
|
-
parkTurnStart({
|
|
80507
|
+
const parkedEntry = parkTurnStart({
|
|
80403
80508
|
chatId: ev.chatId,
|
|
80404
80509
|
messageId: ev.messageId,
|
|
80405
80510
|
threadId: ev.threadId,
|
|
80406
80511
|
rawContent: ev.rawContent
|
|
80407
|
-
}, now);
|
|
80512
|
+
}, now, (evicted) => finalizeParkedEntryCard(deps, evicted, QUEUED_CARD_EXPIRED_HTML));
|
|
80408
80513
|
process.stderr.write(`telegram gateway: turn-start parked (session busy) chat=${ev.chatId} ` + `thread=${enqThreadIdNum ?? "-"} msg=${ev.messageId ?? "-"} ` + `parked=${parkedTurnStarts.length}
|
|
80409
80514
|
`);
|
|
80515
|
+
const parkTurnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId);
|
|
80516
|
+
const handbackOwnsSurface = HANDBACK_PRETURN_ENABLED && parkTurnId != null && handbackPreturnSignal.hasPendingForTurnId(parkTurnId);
|
|
80517
|
+
if (handbackOwnsSurface) {
|
|
80518
|
+
process.stderr.write(`telegram gateway: queued card suppressed (handback owns surface) ` + `turnId=${parkTurnId} msg=${ev.messageId ?? "-"}
|
|
80519
|
+
`);
|
|
80520
|
+
}
|
|
80521
|
+
if (QUEUED_CARD_ENABLED && !handbackOwnsSurface) {
|
|
80522
|
+
const cardChatId = ev.chatId;
|
|
80523
|
+
const replyToRaw = ev.messageId != null ? Number(ev.messageId) : null;
|
|
80524
|
+
const replyTo = replyToRaw != null && Number.isFinite(replyToRaw) ? replyToRaw : null;
|
|
80525
|
+
openQueuedCard(deps, cardChatId, enqThreadIdNum ?? null, replyTo).then((cardId) => {
|
|
80526
|
+
if (cardId == null)
|
|
80527
|
+
return;
|
|
80528
|
+
if (parkedTurnStarts.includes(parkedEntry)) {
|
|
80529
|
+
parkedEntry.queuedCardMessageId = cardId;
|
|
80530
|
+
} else {
|
|
80531
|
+
deleteQueuedCard(deps, cardChatId, cardId);
|
|
80532
|
+
}
|
|
80533
|
+
});
|
|
80534
|
+
}
|
|
80410
80535
|
return;
|
|
80411
80536
|
}
|
|
80412
80537
|
case "dequeue": {
|
|
80413
|
-
pruneParkedTurnStarts(Date.now());
|
|
80538
|
+
pruneParkedTurnStarts(Date.now(), (e) => finalizeParkedEntryCard(deps, e, QUEUED_CARD_EXPIRED_HTML));
|
|
80414
80539
|
const parked = takeParkedTurnStart();
|
|
80415
80540
|
if (parked == null)
|
|
80416
80541
|
return;
|
|
@@ -80418,8 +80543,10 @@ function handleSessionEvent(deps, ev) {
|
|
|
80418
80543
|
return;
|
|
80419
80544
|
}
|
|
80420
80545
|
case "queue_remove": {
|
|
80421
|
-
pruneParkedTurnStarts(Date.now());
|
|
80422
|
-
discardParkedTurnStart(ev.rawContent);
|
|
80546
|
+
pruneParkedTurnStarts(Date.now(), (e) => finalizeParkedEntryCard(deps, e, QUEUED_CARD_EXPIRED_HTML));
|
|
80547
|
+
const removed = discardParkedTurnStart(ev.rawContent);
|
|
80548
|
+
if (removed != null)
|
|
80549
|
+
finalizeParkedEntryCard(deps, removed, QUEUED_CARD_FOLDED_HTML);
|
|
80423
80550
|
return;
|
|
80424
80551
|
}
|
|
80425
80552
|
case "model": {
|
|
@@ -84938,8 +85065,8 @@ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
|
84938
85065
|
var HINDSIGHT_PG_APP_ANON_MIB = 2560;
|
|
84939
85066
|
var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
|
|
84940
85067
|
var HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
|
|
84941
|
-
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
84942
|
-
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB =
|
|
85068
|
+
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 6144;
|
|
85069
|
+
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 12288;
|
|
84943
85070
|
function pgMib(mib) {
|
|
84944
85071
|
return `${mib}MB`;
|
|
84945
85072
|
}
|
|
@@ -84983,7 +85110,7 @@ var HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150;
|
|
|
84983
85110
|
var HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4;
|
|
84984
85111
|
var HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8;
|
|
84985
85112
|
var HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600;
|
|
84986
|
-
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND =
|
|
85113
|
+
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250;
|
|
84987
85114
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6;
|
|
84988
85115
|
var HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1;
|
|
84989
85116
|
var HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential";
|
|
@@ -99672,10 +99799,10 @@ function startOutboxSweep(deps) {
|
|
|
99672
99799
|
}
|
|
99673
99800
|
|
|
99674
99801
|
// ../src/build-info.ts
|
|
99675
|
-
var VERSION2 = "0.19.
|
|
99676
|
-
var COMMIT_SHA = "
|
|
99677
|
-
var COMMIT_DATE = "2026-07-
|
|
99678
|
-
var LATEST_PR =
|
|
99802
|
+
var VERSION2 = "0.19.39";
|
|
99803
|
+
var COMMIT_SHA = "4aff2bdd";
|
|
99804
|
+
var COMMIT_DATE = "2026-07-31T00:54:36Z";
|
|
99805
|
+
var LATEST_PR = 4041;
|
|
99679
99806
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
99680
99807
|
|
|
99681
99808
|
// gateway/boot-version.ts
|
|
@@ -104885,7 +105012,7 @@ async function runMidSessionCardReaper() {
|
|
|
104885
105012
|
const { finalized, vanished, total } = await runActivityCardMidSessionReaper({
|
|
104886
105013
|
path: ACTIVITY_CARD_STORE_PATH,
|
|
104887
105014
|
fs: activityCardStoreFs,
|
|
104888
|
-
isLive: (record2) => topicKeys.has(record2.turnKey),
|
|
105015
|
+
isLive: (record2) => topicKeys.has(record2.turnKey) || handbackPreturnSignal.isPreTurnRecord(record2.turnKey) && isMachineInTurn(),
|
|
104889
105016
|
ttlMs: MID_SESSION_CARD_REAPER_TTL_MS,
|
|
104890
105017
|
now,
|
|
104891
105018
|
finalizeCard: (record2) => {
|
|
@@ -107525,7 +107652,8 @@ var handbackPreturnSignal = createHandbackPreturnSignal({
|
|
|
107525
107652
|
if (live == null || live.endedAt != null)
|
|
107526
107653
|
return false;
|
|
107527
107654
|
return statusKey(live.sessionChatId, live.sessionThreadId) === key;
|
|
107528
|
-
}
|
|
107655
|
+
},
|
|
107656
|
+
isClaudeBusy: () => isMachineInTurn()
|
|
107529
107657
|
});
|
|
107530
107658
|
var memoryLegibilityStager = new MemoryLegibilityStager;
|
|
107531
107659
|
function sendMemoryLegibilityLine(event, chatId, threadId) {
|
|
@@ -8909,10 +8909,14 @@ async function runMidSessionCardReaper(): Promise<void> {
|
|
|
8909
8909
|
const { finalized, vanished, total } = await runActivityCardMidSessionReaper({
|
|
8910
8910
|
path: ACTIVITY_CARD_STORE_PATH,
|
|
8911
8911
|
fs: activityCardStoreFs,
|
|
8912
|
-
// A synthetic pre-turn
|
|
8913
|
-
//
|
|
8914
|
-
//
|
|
8915
|
-
|
|
8912
|
+
// A synthetic pre-turn key reads as ownerless here (self-reap is the fast
|
|
8913
|
+
// path; this is the crash backstop). EXCEPTION: a pre-turn handback still
|
|
8914
|
+
// enqueued behind an in-flight turn is NOT an orphan — keep it live while
|
|
8915
|
+
// claude is busy, else this 15-min backstop re-posts the false nudge the
|
|
8916
|
+
// self-reap gate removed. (Boot reaper stays ungated: boot is never mid-turn.)
|
|
8917
|
+
isLive: (record) =>
|
|
8918
|
+
topicKeys.has(record.turnKey) ||
|
|
8919
|
+
(handbackPreturnSignal.isPreTurnRecord(record.turnKey) && isMachineInTurn()),
|
|
8916
8920
|
ttlMs: MID_SESSION_CARD_REAPER_TTL_MS,
|
|
8917
8921
|
now,
|
|
8918
8922
|
finalizeCard: (record) => {
|
|
@@ -13827,6 +13831,10 @@ const handbackPreturnSignal = createHandbackPreturnSignal({
|
|
|
13827
13831
|
// dark-indicator bug). Flag-ON is already per-key; the check is a no-op there.
|
|
13828
13832
|
return statusKey(live.sessionChatId, live.sessionThreadId) === key
|
|
13829
13833
|
},
|
|
13834
|
+
// Queue-state gate for the orphan reap (see the signal's `isClaudeBusy` doc):
|
|
13835
|
+
// while a turn is in flight the handback is enqueued behind it, not orphaned.
|
|
13836
|
+
// False for both `bridge_alive_idle` and `bridge_dead` → genuine orphan reaps.
|
|
13837
|
+
isClaudeBusy: () => isMachineInTurn(),
|
|
13830
13838
|
})
|
|
13831
13839
|
|
|
13832
13840
|
/**
|
|
@@ -54,9 +54,23 @@
|
|
|
54
54
|
* On adoption the durable record is re-keyed from its synthetic pre-turn key
|
|
55
55
|
* to the real `statusKey` so that teardown matches.
|
|
56
56
|
*
|
|
57
|
-
* • NEVER-ADOPTED ORPHAN REAP
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* • NEVER-ADOPTED ORPHAN REAP — GATED ON A GLOBAL-BUSY QUEUE CHECK. A
|
|
58
|
+
* degenerate case (bridge death after release, no enqueue ever arrives)
|
|
59
|
+
* would leave a frozen card + a forever `typing…` loop. But the reap must
|
|
60
|
+
* fire ONLY for that genuine orphan, never for a handback that is correctly
|
|
61
|
+
* enqueued and simply waiting behind a long in-flight turn: claude has ONE
|
|
62
|
+
* bridge / ONE input queue, so a released handback mints its adopting turn
|
|
63
|
+
* only when claude next goes idle. A flat age-based reap cannot tell the two
|
|
64
|
+
* apart and (before this gate) posted a false "never started — needs a
|
|
65
|
+
* nudge" card every time a parent turn out-ran the timeout (the 3-cards-in-
|
|
66
|
+
* 4-min incident). The reap therefore consults `isClaudeBusy()` — the
|
|
67
|
+
* machine-level "a turn is in flight right now" signal — and DEFERS
|
|
68
|
+
* (re-arms for another `adoptTimeoutMs`) while claude is busy, finalizing
|
|
69
|
+
* only once claude is idle AND the handback still has not been adopted. That
|
|
70
|
+
* idle-with-no-adoption state is the genuine bridge-death / dropped-inbound
|
|
71
|
+
* degenerate case. Lengthening the flat timeout would merely move the false
|
|
72
|
+
* positive; the queue-state gate removes it. The pre-turn card is persisted
|
|
73
|
+
* as a COMPLETE `ActivityCardRecord`
|
|
60
74
|
* under a SYNTHETIC `turnKey` (`preturn:<statusKey>:<startedAt>`) — synthetic
|
|
61
75
|
* so a sibling REAL turn on the same topic key can neither upsert-clobber it
|
|
62
76
|
* nor keep it "live" (the mid-session reaper's `isLive` keys on live topic
|
|
@@ -171,6 +185,22 @@ export interface HandbackPreturnSignalDeps {
|
|
|
171
185
|
* canonical turn-end owns the stop. Optional; defaults to "no live turn"
|
|
172
186
|
* (the pre-#3544 behaviour). */
|
|
173
187
|
hasLiveTurn?: (statusKey: string) => boolean
|
|
188
|
+
/** True IFF claude is CURRENTLY producing a turn (ANY topic — the machine has
|
|
189
|
+
* ONE bridge / ONE input queue). Consulted by the orphan reap: a released
|
|
190
|
+
* handback is delivered into that single queue and only mints its adopting
|
|
191
|
+
* turn when claude next goes idle, so while claude is busy the handback is
|
|
192
|
+
* NOT orphaned — it is correctly enqueued BEHIND the in-flight turn (a long
|
|
193
|
+
* parent turn can run many minutes). Finalizing it as an orphan in that
|
|
194
|
+
* window posts a false "never started — needs a nudge" card. The reap
|
|
195
|
+
* therefore DEFERS (re-arms) while this is true and finalizes only once
|
|
196
|
+
* claude is idle and the handback still has not been adopted — the genuine
|
|
197
|
+
* bridge-death / dropped-inbound degenerate case the reap exists for.
|
|
198
|
+
* Distinct from `hasLiveTurn`, which is PER-KEY and only governs the
|
|
199
|
+
* typing-loop stop; this is the GLOBAL busy signal because the handback
|
|
200
|
+
* queues behind whatever turn is running, regardless of topic. Optional;
|
|
201
|
+
* defaults to "not busy" (immediate reap — the pre-fix behaviour), so the
|
|
202
|
+
* genuine-orphan path is unchanged. */
|
|
203
|
+
isClaudeBusy?: () => boolean
|
|
174
204
|
now?: () => number
|
|
175
205
|
/** Debounce before painting the pre-turn card (kills sub-second flicker). */
|
|
176
206
|
debounceMs?: number
|
|
@@ -216,6 +246,15 @@ export interface HandbackPreturnSignal {
|
|
|
216
246
|
* re-keyed to the real `statusKey` so the turn's end-of-turn
|
|
217
247
|
* `clearActivitySummary` finalizes it. Cancels the debounce/self-reap. */
|
|
218
248
|
tryAdopt: (turnId: string) => HandbackAdoption | null
|
|
249
|
+
/** True IFF a live (un-consumed) pre-turn entry is armed whose adopting turn
|
|
250
|
+
* is `turnId`. Consulted by the queued-card surface (stream-render Part B) at
|
|
251
|
+
* PARK time: when a handback pre-turn signal already owns the surface for the
|
|
252
|
+
* turn this parked message will mint, the queued card must NOT be posted —
|
|
253
|
+
* otherwise the same message ends up with two cards (the handback card the
|
|
254
|
+
* turn adopts, plus a queued card that then freezes). Turn-scoped, NOT
|
|
255
|
+
* key-scoped, so an unrelated user message parked on the same topic (a
|
|
256
|
+
* DIFFERENT adoptTurnId) is not wrongly suppressed. */
|
|
257
|
+
hasPendingForTurnId: (turnId: string) => boolean
|
|
219
258
|
/** True IFF `turnKey` is a synthetic pre-turn record key. */
|
|
220
259
|
isPreTurnRecord: (turnKey: string) => boolean
|
|
221
260
|
/** Reap hook for the gateway's mid-session / boot card reapers: stop the
|
|
@@ -349,6 +388,25 @@ export function createHandbackPreturnSignal(
|
|
|
349
388
|
function reap(entry: PreTurnEntry): void {
|
|
350
389
|
entry.reapTimer = null
|
|
351
390
|
if (entry.consumed) return
|
|
391
|
+
// PRIMARY GATE (queue state, not a flat timeout): a released handback is
|
|
392
|
+
// delivered into claude's SINGLE input queue and mints its adopting turn
|
|
393
|
+
// only when claude next goes idle. If claude is STILL in a turn, this
|
|
394
|
+
// handback is not orphaned — it is correctly enqueued behind the in-flight
|
|
395
|
+
// turn (a long parent turn can run many minutes). Finalizing now would post
|
|
396
|
+
// a false "never started — needs a nudge" card (the 3-cards-in-4-min
|
|
397
|
+
// incident). Defer: re-arm and re-check. We finalize only once claude is
|
|
398
|
+
// idle AND the handback still has not been adopted (the genuine bridge-
|
|
399
|
+
// death / dropped-inbound case). This cannot loop forever on a healthy
|
|
400
|
+
// agent: claude processes its queue FIFO, so the handback is either adopted
|
|
401
|
+
// (cancelling the reap) or claude eventually goes idle (letting it fire).
|
|
402
|
+
if (deps.isClaudeBusy?.() === true) {
|
|
403
|
+
log(
|
|
404
|
+
`handback-preturn-signal: reap deferred key=${entry.statusKey} ` +
|
|
405
|
+
`(claude busy — handback enqueued behind an in-flight turn, not orphaned)\n`,
|
|
406
|
+
)
|
|
407
|
+
entry.reapTimer = setTimer(() => reap(entry), adoptTimeoutMs)
|
|
408
|
+
return
|
|
409
|
+
}
|
|
352
410
|
entry.consumed = true
|
|
353
411
|
// Stop the forever-running typing loop and finalize the frozen card.
|
|
354
412
|
stopTypingUnlessTurnLive(entry, 'orphan reap')
|
|
@@ -467,6 +525,13 @@ export function createHandbackPreturnSignal(
|
|
|
467
525
|
return adoption
|
|
468
526
|
},
|
|
469
527
|
|
|
528
|
+
hasPendingForTurnId(turnId) {
|
|
529
|
+
for (const e of byKey.values()) {
|
|
530
|
+
if (e.adoptTurnId === turnId && !e.consumed) return true
|
|
531
|
+
}
|
|
532
|
+
return false
|
|
533
|
+
},
|
|
534
|
+
|
|
470
535
|
isPreTurnRecord(turnKey) {
|
|
471
536
|
return turnKey.startsWith(PRETURN_TURNKEY_PREFIX)
|
|
472
537
|
},
|