switchroom 0.19.31 → 0.19.33
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 +112 -6
- package/dist/cli/switchroom.js +177 -34
- package/dist/host-control/main.js +289 -27
- package/dist/vault/approvals/kernel-server.js +112 -6
- package/dist/vault/broker/server.js +112 -6
- package/package.json +1 -1
- package/telegram-plugin/dist/bridge/bridge.js +3 -0
- package/telegram-plugin/dist/gateway/gateway.js +434 -187
- package/telegram-plugin/dist/server.js +3 -0
- package/telegram-plugin/gateway/stale-pin-sweep-store.ts +14 -8
- package/telegram-plugin/gateway/stale-pin-sweep.test.ts +106 -1
- package/telegram-plugin/gateway/stale-pin-sweep.ts +156 -47
- package/telegram-plugin/gateway/status-pin-store.ts +44 -11
- package/telegram-plugin/gateway/stream-render.ts +578 -321
- package/telegram-plugin/session-tail.ts +13 -0
- package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
- package/telegram-plugin/tests/stream-render-golden.test.ts +20 -5
- package/telegram-plugin/tests/turn-mint-defers-until-dequeue.test.ts +196 -0
- package/telegram-plugin/tests/turn-mint-harness.ts +155 -0
- package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +124 -0
- package/vendor/hindsight-memory/scripts/subagent_retain.py +30 -7
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +100 -2
- package/vendor/hindsight-memory/settings.json +1 -1
|
@@ -44732,7 +44732,7 @@ async function handleVoiceMessage(ctx, deps) {
|
|
|
44732
44732
|
}
|
|
44733
44733
|
|
|
44734
44734
|
// gateway/status-pin-store.ts
|
|
44735
|
-
var
|
|
44735
|
+
var SNAPSHOT_VERSION = 4;
|
|
44736
44736
|
function isPinRow(x) {
|
|
44737
44737
|
if (x == null || typeof x !== "object")
|
|
44738
44738
|
return false;
|
|
@@ -44757,12 +44757,12 @@ function loadStatusPins(path, fs2) {
|
|
|
44757
44757
|
if (parsed == null || typeof parsed !== "object")
|
|
44758
44758
|
return [];
|
|
44759
44759
|
const env = parsed;
|
|
44760
|
-
if (
|
|
44760
|
+
if (!Number.isInteger(env.v) || env.v < 1 || !Array.isArray(env.pins))
|
|
44761
44761
|
return [];
|
|
44762
44762
|
return env.pins.filter(isPinRow);
|
|
44763
44763
|
}
|
|
44764
44764
|
function persistStatusPins(path, fs2, snapshot, log = (l) => process.stderr.write(l)) {
|
|
44765
|
-
const env = { v:
|
|
44765
|
+
const env = { v: SNAPSHOT_VERSION, pins: [...snapshot] };
|
|
44766
44766
|
const tmp = path + ".tmp";
|
|
44767
44767
|
try {
|
|
44768
44768
|
fs2.writeFileSync(tmp, JSON.stringify(env));
|
|
@@ -71213,6 +71213,9 @@ function projectTranscriptLine(line) {
|
|
|
71213
71213
|
if (op === "dequeue") {
|
|
71214
71214
|
return [{ kind: "dequeue" }];
|
|
71215
71215
|
}
|
|
71216
|
+
if (op === "remove") {
|
|
71217
|
+
return [{ kind: "queue_remove", rawContent: obj.content ?? "" }];
|
|
71218
|
+
}
|
|
71216
71219
|
return [];
|
|
71217
71220
|
}
|
|
71218
71221
|
if (type === "assistant") {
|
|
@@ -79814,6 +79817,195 @@ function decideFallbackTeardownNotice2(input) {
|
|
|
79814
79817
|
}
|
|
79815
79818
|
|
|
79816
79819
|
// gateway/stream-render.ts
|
|
79820
|
+
var PARKED_TURN_START_MAX = 16;
|
|
79821
|
+
var PARKED_TURN_START_TTL_MS = 30 * 60000;
|
|
79822
|
+
var parkedTurnStarts = [];
|
|
79823
|
+
function pruneParkedTurnStarts(now) {
|
|
79824
|
+
for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
|
|
79825
|
+
if (now - parkedTurnStarts[i].parkedAt > PARKED_TURN_START_TTL_MS) {
|
|
79826
|
+
const [dropped] = parkedTurnStarts.splice(i, 1);
|
|
79827
|
+
process.stderr.write(`telegram gateway: parked-turn-start expired chat=${dropped.chatId ?? "-"} ` + `msg=${dropped.messageId ?? "-"} age_ms=${now - dropped.parkedAt}
|
|
79828
|
+
`);
|
|
79829
|
+
}
|
|
79830
|
+
}
|
|
79831
|
+
}
|
|
79832
|
+
function parkTurnStart(env, now) {
|
|
79833
|
+
parkedTurnStarts.push({ ...env, parkedAt: now });
|
|
79834
|
+
while (parkedTurnStarts.length > PARKED_TURN_START_MAX) {
|
|
79835
|
+
const [dropped] = parkedTurnStarts.splice(0, 1);
|
|
79836
|
+
process.stderr.write(`telegram gateway: parked-turn-start evicted (cap ${PARKED_TURN_START_MAX}) ` + `chat=${dropped.chatId ?? "-"} msg=${dropped.messageId ?? "-"}
|
|
79837
|
+
`);
|
|
79838
|
+
}
|
|
79839
|
+
}
|
|
79840
|
+
function takeParkedTurnStart() {
|
|
79841
|
+
return parkedTurnStarts.pop() ?? null;
|
|
79842
|
+
}
|
|
79843
|
+
function discardParkedTurnStart(rawContent) {
|
|
79844
|
+
for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
|
|
79845
|
+
if (parkedTurnStarts[i].rawContent === rawContent) {
|
|
79846
|
+
parkedTurnStarts.splice(i, 1);
|
|
79847
|
+
return true;
|
|
79848
|
+
}
|
|
79849
|
+
}
|
|
79850
|
+
return false;
|
|
79851
|
+
}
|
|
79852
|
+
function beginTurn(deps, ev) {
|
|
79853
|
+
const {
|
|
79854
|
+
HANDBACK_PRETURN_ENABLED,
|
|
79855
|
+
STATE_DIR,
|
|
79856
|
+
clearActivitySummary,
|
|
79857
|
+
extractUserPromptPreview,
|
|
79858
|
+
getCurrentTurn,
|
|
79859
|
+
getPendingPtyPartial,
|
|
79860
|
+
handbackPreturnSignal,
|
|
79861
|
+
handlePtyPartial,
|
|
79862
|
+
isDmChatId,
|
|
79863
|
+
makeNarrativeGate,
|
|
79864
|
+
pendingCrossTurnGate,
|
|
79865
|
+
preambleSuppressor,
|
|
79866
|
+
promoteQueuedStatus,
|
|
79867
|
+
rememberRecentTurn,
|
|
79868
|
+
scheduleEarlyLivenessOpen,
|
|
79869
|
+
setCurrentTurn,
|
|
79870
|
+
setPendingPtyPartial,
|
|
79871
|
+
startTurnTypingLoop,
|
|
79872
|
+
statusKey,
|
|
79873
|
+
turnsDb,
|
|
79874
|
+
typingWrapper
|
|
79875
|
+
} = deps;
|
|
79876
|
+
typingWrapper.drainAll();
|
|
79877
|
+
if (ev.chatId) {
|
|
79878
|
+
const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
79879
|
+
clearPending2(statusKey(ev.chatId, enqThreadId), "handback");
|
|
79880
|
+
}
|
|
79881
|
+
if (ev.chatId) {
|
|
79882
|
+
const prior = getCurrentTurn();
|
|
79883
|
+
if (prior?.answerStream != null) {
|
|
79884
|
+
prior.answerStream.forceNewMessage();
|
|
79885
|
+
prior.answerStream.stop();
|
|
79886
|
+
prior.answerStream = null;
|
|
79887
|
+
}
|
|
79888
|
+
if (prior?.orphanedReplyTimeoutId != null) {
|
|
79889
|
+
clearTimeout(prior.orphanedReplyTimeoutId);
|
|
79890
|
+
prior.orphanedReplyTimeoutId = null;
|
|
79891
|
+
}
|
|
79892
|
+
prior?.narrativeGate?.teardown();
|
|
79893
|
+
if (prior != null && prior.endedAt == null) {
|
|
79894
|
+
clearActivitySummary(prior);
|
|
79895
|
+
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "superseded", prior, Date.now())}
|
|
79896
|
+
`);
|
|
79897
|
+
}
|
|
79898
|
+
const startedAt = Date.now();
|
|
79899
|
+
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
79900
|
+
const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
|
|
79901
|
+
const xTurnGateKey = turnId;
|
|
79902
|
+
const consumedCrossTurnGate = pendingCrossTurnGate.get(xTurnGateKey);
|
|
79903
|
+
if (consumedCrossTurnGate != null)
|
|
79904
|
+
pendingCrossTurnGate.delete(xTurnGateKey);
|
|
79905
|
+
const next = {
|
|
79906
|
+
sessionChatId: ev.chatId,
|
|
79907
|
+
sessionThreadId: enqThreadIdNum,
|
|
79908
|
+
sourceMessageId: parseSourceMessageId(ev.messageId),
|
|
79909
|
+
startedAt,
|
|
79910
|
+
gatewayReceiveAt: startedAt,
|
|
79911
|
+
role: deriveTurnRole(ev.rawContent),
|
|
79912
|
+
...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
|
|
79913
|
+
replyCalled: false,
|
|
79914
|
+
finalAnswerDelivered: false,
|
|
79915
|
+
finalAnswerSubstantive: false,
|
|
79916
|
+
finalAnswerEverDelivered: false,
|
|
79917
|
+
answerDelivered: false,
|
|
79918
|
+
flushedAnswerText: null,
|
|
79919
|
+
endedAt: null,
|
|
79920
|
+
firstPingAt: null,
|
|
79921
|
+
firstPingWasSubstantive: false,
|
|
79922
|
+
silentAnchorMessageId: null,
|
|
79923
|
+
silentAnchorText: "",
|
|
79924
|
+
capturedText: [],
|
|
79925
|
+
capturedBlockMeta: [],
|
|
79926
|
+
orphanedReplyTimeoutId: null,
|
|
79927
|
+
answerReadyFlushTimeoutId: null,
|
|
79928
|
+
liveness: new LivenessTracker(startedAt),
|
|
79929
|
+
turnId,
|
|
79930
|
+
registryKey: null,
|
|
79931
|
+
noReplyDrainTimer: null,
|
|
79932
|
+
lastAssistantMsgId: null,
|
|
79933
|
+
lastAssistantDone: false,
|
|
79934
|
+
toolCallCount: 0,
|
|
79935
|
+
labeledToolCount: 0,
|
|
79936
|
+
totalTokens: 0,
|
|
79937
|
+
seenUsageMessageIds: new Set,
|
|
79938
|
+
activityMessageId: null,
|
|
79939
|
+
activityInFlight: null,
|
|
79940
|
+
activityPendingRender: null,
|
|
79941
|
+
activityLastSentRender: null,
|
|
79942
|
+
activityEverOpened: false,
|
|
79943
|
+
activityDrainFailures: 0,
|
|
79944
|
+
mirrorLines: [],
|
|
79945
|
+
narrativeGate: undefined,
|
|
79946
|
+
lastReplyText: "",
|
|
79947
|
+
foregroundSubAgents: new Map,
|
|
79948
|
+
answerStream: null,
|
|
79949
|
+
isDm: isDmChatId(ev.chatId),
|
|
79950
|
+
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
79951
|
+
};
|
|
79952
|
+
next.narrativeGate = makeNarrativeGate(next);
|
|
79953
|
+
if (HANDBACK_PRETURN_ENABLED) {
|
|
79954
|
+
const handbackAdoption = handbackPreturnSignal.tryAdopt(turnId);
|
|
79955
|
+
if (handbackAdoption != null) {
|
|
79956
|
+
if (handbackAdoption.activityMessageId != null) {
|
|
79957
|
+
next.activityMessageId = handbackAdoption.activityMessageId;
|
|
79958
|
+
next.activityEverOpened = true;
|
|
79959
|
+
}
|
|
79960
|
+
process.stderr.write(`telegram gateway: handback pre-turn adopted turnId=${turnId} ` + `key=${handbackAdoption.statusKey} card=${handbackAdoption.activityMessageId ?? "none"}
|
|
79961
|
+
`);
|
|
79962
|
+
}
|
|
79963
|
+
}
|
|
79964
|
+
startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
|
|
79965
|
+
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
79966
|
+
scheduleEarlyLivenessOpen(next);
|
|
79967
|
+
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
79968
|
+
`);
|
|
79969
|
+
rememberRecentTurn(next);
|
|
79970
|
+
promoteQueuedStatus(ev.chatId, enqThreadIdNum);
|
|
79971
|
+
shadowEmit({
|
|
79972
|
+
kind: "turnStart",
|
|
79973
|
+
key: statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : undefined),
|
|
79974
|
+
at: startedAt
|
|
79975
|
+
});
|
|
79976
|
+
preambleSuppressor.reset();
|
|
79977
|
+
clearSilentEndState2(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
|
|
79978
|
+
if (turnsDb != null) {
|
|
79979
|
+
const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
|
|
79980
|
+
const turnKey3 = chatKeyWithSuffix(ev.chatId, evThreadIdNum, String(startedAt));
|
|
79981
|
+
next.registryKey = turnKey3;
|
|
79982
|
+
const userPromptPreview = extractUserPromptPreview(ev.rawContent);
|
|
79983
|
+
try {
|
|
79984
|
+
recordTurnStart(turnsDb, {
|
|
79985
|
+
turnKey: turnKey3,
|
|
79986
|
+
chatId: String(ev.chatId),
|
|
79987
|
+
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
79988
|
+
lastUserMsgId: ev.messageId != null ? String(ev.messageId) : null,
|
|
79989
|
+
userPromptPreview
|
|
79990
|
+
});
|
|
79991
|
+
} catch (err) {
|
|
79992
|
+
process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${turnKey3}: ${err.message}
|
|
79993
|
+
`);
|
|
79994
|
+
}
|
|
79995
|
+
writeTurnActiveMarker(STATE_DIR, {
|
|
79996
|
+
turnKey: turnKey3,
|
|
79997
|
+
chatId: String(ev.chatId),
|
|
79998
|
+
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
79999
|
+
startedAt
|
|
80000
|
+
});
|
|
80001
|
+
}
|
|
80002
|
+
const pending = getPendingPtyPartial();
|
|
80003
|
+
if (pending != null) {
|
|
80004
|
+
setPendingPtyPartial(null);
|
|
80005
|
+
handlePtyPartial(pending);
|
|
80006
|
+
}
|
|
80007
|
+
}
|
|
80008
|
+
}
|
|
79817
80009
|
function handleSessionEvent(deps, ev) {
|
|
79818
80010
|
const {
|
|
79819
80011
|
ANSWER_LANE,
|
|
@@ -79910,140 +80102,47 @@ function handleSessionEvent(deps, ev) {
|
|
|
79910
80102
|
}
|
|
79911
80103
|
switch (ev.kind) {
|
|
79912
80104
|
case "enqueue": {
|
|
79913
|
-
|
|
79914
|
-
|
|
79915
|
-
const enqThreadId = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
79916
|
-
clearPending2(statusKey(ev.chatId, enqThreadId), "handback");
|
|
79917
|
-
}
|
|
80105
|
+
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
80106
|
+
const now = Date.now();
|
|
79918
80107
|
if (ev.chatId) {
|
|
79919
|
-
const prior = getCurrentTurn();
|
|
79920
|
-
if (prior?.answerStream != null) {
|
|
79921
|
-
prior.answerStream.forceNewMessage();
|
|
79922
|
-
prior.answerStream.stop();
|
|
79923
|
-
prior.answerStream = null;
|
|
79924
|
-
}
|
|
79925
|
-
if (prior?.orphanedReplyTimeoutId != null) {
|
|
79926
|
-
clearTimeout(prior.orphanedReplyTimeoutId);
|
|
79927
|
-
prior.orphanedReplyTimeoutId = null;
|
|
79928
|
-
}
|
|
79929
|
-
prior?.narrativeGate?.teardown();
|
|
79930
|
-
const startedAt = Date.now();
|
|
79931
|
-
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
79932
|
-
const turnId = deriveTurnId(ev.chatId, enqThreadIdNum ?? null, ev.messageId) ?? `${chatKey(ev.chatId, enqThreadIdNum ?? null)}#synthetic-${startedAt}`;
|
|
79933
|
-
const xTurnGateKey = turnId;
|
|
79934
|
-
const consumedCrossTurnGate = pendingCrossTurnGate.get(xTurnGateKey);
|
|
79935
|
-
if (consumedCrossTurnGate != null)
|
|
79936
|
-
pendingCrossTurnGate.delete(xTurnGateKey);
|
|
79937
|
-
const next = {
|
|
79938
|
-
sessionChatId: ev.chatId,
|
|
79939
|
-
sessionThreadId: enqThreadIdNum,
|
|
79940
|
-
sourceMessageId: parseSourceMessageId(ev.messageId),
|
|
79941
|
-
startedAt,
|
|
79942
|
-
gatewayReceiveAt: startedAt,
|
|
79943
|
-
role: deriveTurnRole(ev.rawContent),
|
|
79944
|
-
...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
|
|
79945
|
-
replyCalled: false,
|
|
79946
|
-
finalAnswerDelivered: false,
|
|
79947
|
-
finalAnswerSubstantive: false,
|
|
79948
|
-
finalAnswerEverDelivered: false,
|
|
79949
|
-
answerDelivered: false,
|
|
79950
|
-
flushedAnswerText: null,
|
|
79951
|
-
endedAt: null,
|
|
79952
|
-
firstPingAt: null,
|
|
79953
|
-
firstPingWasSubstantive: false,
|
|
79954
|
-
silentAnchorMessageId: null,
|
|
79955
|
-
silentAnchorText: "",
|
|
79956
|
-
capturedText: [],
|
|
79957
|
-
capturedBlockMeta: [],
|
|
79958
|
-
orphanedReplyTimeoutId: null,
|
|
79959
|
-
answerReadyFlushTimeoutId: null,
|
|
79960
|
-
liveness: new LivenessTracker(startedAt),
|
|
79961
|
-
turnId,
|
|
79962
|
-
registryKey: null,
|
|
79963
|
-
noReplyDrainTimer: null,
|
|
79964
|
-
lastAssistantMsgId: null,
|
|
79965
|
-
lastAssistantDone: false,
|
|
79966
|
-
toolCallCount: 0,
|
|
79967
|
-
labeledToolCount: 0,
|
|
79968
|
-
totalTokens: 0,
|
|
79969
|
-
seenUsageMessageIds: new Set,
|
|
79970
|
-
activityMessageId: null,
|
|
79971
|
-
activityInFlight: null,
|
|
79972
|
-
activityPendingRender: null,
|
|
79973
|
-
activityLastSentRender: null,
|
|
79974
|
-
activityEverOpened: false,
|
|
79975
|
-
activityDrainFailures: 0,
|
|
79976
|
-
mirrorLines: [],
|
|
79977
|
-
narrativeGate: undefined,
|
|
79978
|
-
lastReplyText: "",
|
|
79979
|
-
foregroundSubAgents: new Map,
|
|
79980
|
-
answerStream: null,
|
|
79981
|
-
isDm: isDmChatId(ev.chatId),
|
|
79982
|
-
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
79983
|
-
};
|
|
79984
|
-
next.narrativeGate = makeNarrativeGate(next);
|
|
79985
|
-
if (HANDBACK_PRETURN_ENABLED) {
|
|
79986
|
-
const handbackAdoption = handbackPreturnSignal.tryAdopt(turnId);
|
|
79987
|
-
if (handbackAdoption != null) {
|
|
79988
|
-
if (handbackAdoption.activityMessageId != null) {
|
|
79989
|
-
next.activityMessageId = handbackAdoption.activityMessageId;
|
|
79990
|
-
next.activityEverOpened = true;
|
|
79991
|
-
}
|
|
79992
|
-
process.stderr.write(`telegram gateway: handback pre-turn adopted turnId=${turnId} ` + `key=${handbackAdoption.statusKey} card=${handbackAdoption.activityMessageId ?? "none"}
|
|
79993
|
-
`);
|
|
79994
|
-
}
|
|
79995
|
-
}
|
|
79996
|
-
startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
|
|
79997
|
-
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
79998
|
-
scheduleEarlyLivenessOpen(next);
|
|
79999
|
-
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
80000
|
-
`);
|
|
80001
|
-
rememberRecentTurn(next);
|
|
80002
|
-
promoteQueuedStatus(ev.chatId, enqThreadIdNum);
|
|
80003
80108
|
if (DELIVERY_CONFIRM_ENABLED) {
|
|
80004
80109
|
ackDelivery(deliveryQueue, chatKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null), ev.messageId, ev.rawContent);
|
|
80005
80110
|
}
|
|
80006
|
-
shadowEmit({
|
|
80007
|
-
kind: "turnStart",
|
|
80008
|
-
key: statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : undefined),
|
|
80009
|
-
at: startedAt
|
|
80010
|
-
});
|
|
80011
|
-
preambleSuppressor.reset();
|
|
80012
|
-
clearSilentEndState2(statusKey(ev.chatId, ev.threadId != null ? Number(ev.threadId) : null));
|
|
80013
|
-
if (turnsDb != null) {
|
|
80014
|
-
const evThreadIdNum = ev.threadId != null ? Number(ev.threadId) : null;
|
|
80015
|
-
const turnKey3 = chatKeyWithSuffix(ev.chatId, evThreadIdNum, String(startedAt));
|
|
80016
|
-
next.registryKey = turnKey3;
|
|
80017
|
-
const userPromptPreview = extractUserPromptPreview(ev.rawContent);
|
|
80018
|
-
try {
|
|
80019
|
-
recordTurnStart(turnsDb, {
|
|
80020
|
-
turnKey: turnKey3,
|
|
80021
|
-
chatId: String(ev.chatId),
|
|
80022
|
-
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
80023
|
-
lastUserMsgId: ev.messageId != null ? String(ev.messageId) : null,
|
|
80024
|
-
userPromptPreview
|
|
80025
|
-
});
|
|
80026
|
-
} catch (err) {
|
|
80027
|
-
process.stderr.write(`telegram gateway: recordTurnStart failed turnKey=${turnKey3}: ${err.message}
|
|
80028
|
-
`);
|
|
80029
|
-
}
|
|
80030
|
-
writeTurnActiveMarker(STATE_DIR, {
|
|
80031
|
-
turnKey: turnKey3,
|
|
80032
|
-
chatId: String(ev.chatId),
|
|
80033
|
-
threadId: ev.threadId != null ? String(ev.threadId) : null,
|
|
80034
|
-
startedAt
|
|
80035
|
-
});
|
|
80036
|
-
}
|
|
80037
|
-
const pending = getPendingPtyPartial();
|
|
80038
|
-
if (pending != null) {
|
|
80039
|
-
setPendingPtyPartial(null);
|
|
80040
|
-
handlePtyPartial(pending);
|
|
80041
|
-
}
|
|
80042
80111
|
}
|
|
80112
|
+
pruneParkedTurnStarts(now);
|
|
80113
|
+
if (!ev.chatId) {
|
|
80114
|
+
beginTurn(deps, ev);
|
|
80115
|
+
return;
|
|
80116
|
+
}
|
|
80117
|
+
const live = getCurrentTurn();
|
|
80118
|
+
const sessionBusy = live != null && live.endedAt == null;
|
|
80119
|
+
if (!sessionBusy && parkedTurnStarts.length === 0) {
|
|
80120
|
+
beginTurn(deps, ev);
|
|
80121
|
+
return;
|
|
80122
|
+
}
|
|
80123
|
+
parkTurnStart({
|
|
80124
|
+
chatId: ev.chatId,
|
|
80125
|
+
messageId: ev.messageId,
|
|
80126
|
+
threadId: ev.threadId,
|
|
80127
|
+
rawContent: ev.rawContent
|
|
80128
|
+
}, now);
|
|
80129
|
+
process.stderr.write(`telegram gateway: turn-start parked (session busy) chat=${ev.chatId} ` + `thread=${enqThreadIdNum ?? "-"} msg=${ev.messageId ?? "-"} ` + `parked=${parkedTurnStarts.length}
|
|
80130
|
+
`);
|
|
80131
|
+
return;
|
|
80132
|
+
}
|
|
80133
|
+
case "dequeue": {
|
|
80134
|
+
pruneParkedTurnStarts(Date.now());
|
|
80135
|
+
const parked = takeParkedTurnStart();
|
|
80136
|
+
if (parked == null)
|
|
80137
|
+
return;
|
|
80138
|
+
beginTurn(deps, parked);
|
|
80043
80139
|
return;
|
|
80044
80140
|
}
|
|
80045
|
-
case "
|
|
80141
|
+
case "queue_remove": {
|
|
80142
|
+
pruneParkedTurnStarts(Date.now());
|
|
80143
|
+
discardParkedTurnStart(ev.rawContent);
|
|
80046
80144
|
return;
|
|
80145
|
+
}
|
|
80047
80146
|
case "model": {
|
|
80048
80147
|
const turn = getCurrentTurn();
|
|
80049
80148
|
if (turn != null) {
|
|
@@ -84475,12 +84574,12 @@ init_paths();
|
|
|
84475
84574
|
var _warnedReads2 = new Set;
|
|
84476
84575
|
|
|
84477
84576
|
// ../src/setup/hindsight-pg-defaults.ts
|
|
84478
|
-
var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION =
|
|
84577
|
+
var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
|
|
84479
84578
|
var HINDSIGHT_PG_APP_ANON_MIB = 2560;
|
|
84480
84579
|
var HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048;
|
|
84481
84580
|
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;
|
|
84482
|
-
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB =
|
|
84483
|
-
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB =
|
|
84581
|
+
var HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072;
|
|
84582
|
+
var HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168;
|
|
84484
84583
|
function pgMib(mib) {
|
|
84485
84584
|
return `${mib}MB`;
|
|
84486
84585
|
}
|
|
@@ -84655,7 +84754,17 @@ var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
|
84655
84754
|
var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
|
|
84656
84755
|
|
|
84657
84756
|
// ../src/memory/hindsight.ts
|
|
84658
|
-
var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and useful weeks from now
|
|
84757
|
+
var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
|
|
84758
|
+
` + `
|
|
84759
|
+
` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
|
|
84760
|
+
` + `- PREFERENCE \u2014 what the user likes, wants, or always does; a standing rule or correction.
|
|
84761
|
+
` + `- DECISION \u2014 a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision \u2014 drop it unless it establishes a standing rule or permanently changes a system.
|
|
84762
|
+
` + `- FINDING \u2014 a root cause, a measurement, or verified behaviour of a system. Include the number.
|
|
84763
|
+
` + `- OUTCOME \u2014 a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
|
|
84764
|
+
` + `- RELATIONSHIP \u2014 who a person is, what a project or tool is, and how they connect.
|
|
84765
|
+
` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
|
|
84766
|
+
` + `
|
|
84767
|
+
` + `A preference revealed by a request is durable \u2014 record the preference (what the user likes, wants, or always does), not the request itself.
|
|
84659
84768
|
` + `
|
|
84660
84769
|
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
84661
84770
|
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
@@ -84672,6 +84781,9 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
84672
84781
|
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
84673
84782
|
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
84674
84783
|
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
84784
|
+
` + `- The act of delegating, dispatching, spawning, launching, steering or merging
|
|
84785
|
+
` + ` work \u2014 including when it succeeded. "X was dispatched and completed" is the
|
|
84786
|
+
` + ` session describing itself. Record only what the work LEARNED or CHANGED.
|
|
84675
84787
|
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
84676
84788
|
` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
|
|
84677
84789
|
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
@@ -84694,8 +84806,12 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
|
|
|
84694
84806
|
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84695
84807
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84696
84808
|
` + `
|
|
84809
|
+
` + `Write each fact so it stands alone: name the thing, the number, and the date. A
|
|
84810
|
+
` + `sentence that only makes sense while reading this transcript is not durable.
|
|
84811
|
+
` + `
|
|
84697
84812
|
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84698
|
-
` +
|
|
84813
|
+
` + `nothing durable remains, return an empty facts list.
|
|
84814
|
+
`;
|
|
84699
84815
|
var SUPERSEDED_RETAIN_MISSIONS = [
|
|
84700
84816
|
"Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
|
|
84701
84817
|
"Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.",
|
|
@@ -84742,7 +84858,96 @@ var SUPERSEDED_RETAIN_MISSIONS = [
|
|
|
84742
84858
|
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84743
84859
|
` + `
|
|
84744
84860
|
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84745
|
-
` + "nothing durable remains, return an empty facts list."
|
|
84861
|
+
` + "nothing durable remains, return an empty facts list.",
|
|
84862
|
+
`Extract durable facts that will still be true and useful weeks from now: user preferences and standing rules, ongoing projects and recurring commitments, technical and architectural decisions with their rationale, and people/tool relationships. A preference revealed by a request is durable \u2014 record the preference (what the user likes, wants, or always does), not the request itself.
|
|
84863
|
+
` + `
|
|
84864
|
+
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
84865
|
+
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
84866
|
+
` + `the location where some output was written? If yes, drop it \u2014 it is transcript
|
|
84867
|
+
` + `exhaust, not memory.
|
|
84868
|
+
` + `
|
|
84869
|
+
` + `NEVER extract:
|
|
84870
|
+
` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
|
|
84871
|
+
` + ` text resembles any of these: "File created successfully at /path/to/file",
|
|
84872
|
+
` + ` "A background command with ID bctz4yskm is running, and its output will be
|
|
84873
|
+
` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
|
|
84874
|
+
` + ` and is running in the background", "User executed a Bash command to sleep for
|
|
84875
|
+
` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
|
|
84876
|
+
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
84877
|
+
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
84878
|
+
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
84879
|
+
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
84880
|
+
` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
|
|
84881
|
+
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
84882
|
+
` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
|
|
84883
|
+
` + ` reset assistant state").
|
|
84884
|
+
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84885
|
+
` + ` system's self-reports are not memories.
|
|
84886
|
+
` + `- Restatements of the user's current request or the task in progress.
|
|
84887
|
+
` + `- Volatile state written as a timeless assertion. A version, count, size,
|
|
84888
|
+
` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
|
|
84889
|
+
` + ` claim is true only at the instant it was said. Concretely, never produce a
|
|
84890
|
+
` + ` fact whose text resembles any of these: "Switchroom fleet is running image
|
|
84891
|
+
` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
|
|
84892
|
+
` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
|
|
84893
|
+
` + ` currently green". If the claim is worth keeping, put the date INSIDE the
|
|
84894
|
+
` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
|
|
84895
|
+
` + ` cannot date it, drop it. An undated one is recalled forever as though it
|
|
84896
|
+
` + ` were still true, which is worse than not remembering it at all.
|
|
84897
|
+
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84898
|
+
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84899
|
+
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84900
|
+
` + `
|
|
84901
|
+
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84902
|
+
` + "nothing durable remains, return an empty facts list.",
|
|
84903
|
+
`Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
|
|
84904
|
+
` + `
|
|
84905
|
+
` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
|
|
84906
|
+
` + `- PREFERENCE \u2014 what the user likes, wants, or always does; a standing rule or correction.
|
|
84907
|
+
` + `- DECISION \u2014 a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision \u2014 drop it unless it establishes a standing rule or permanently changes a system.
|
|
84908
|
+
` + `- FINDING \u2014 a root cause, a measurement, or verified behaviour of a system. Include the number.
|
|
84909
|
+
` + `- OUTCOME \u2014 a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
|
|
84910
|
+
` + `- RELATIONSHIP \u2014 who a person is, what a project or tool is, and how they connect.
|
|
84911
|
+
` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
|
|
84912
|
+
` + `
|
|
84913
|
+
` + `A preference revealed by a request is durable \u2014 record the preference (what the user likes, wants, or always does), not the request itself.
|
|
84914
|
+
` + `
|
|
84915
|
+
` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
|
|
84916
|
+
` + `candidate a file path, a command/process/agent/session id, a temp directory, or
|
|
84917
|
+
` + `the location where some output was written? If yes, drop it \u2014 it is transcript
|
|
84918
|
+
` + `exhaust, not memory.
|
|
84919
|
+
` + `
|
|
84920
|
+
` + `NEVER extract:
|
|
84921
|
+
` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
|
|
84922
|
+
` + ` text resembles any of these: "File created successfully at /path/to/file",
|
|
84923
|
+
` + ` "A background command with ID bctz4yskm is running, and its output will be
|
|
84924
|
+
` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
|
|
84925
|
+
` + ` and is running in the background", "User executed a Bash command to sleep for
|
|
84926
|
+
` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
|
|
84927
|
+
` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
|
|
84928
|
+
` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
|
|
84929
|
+
` + ` assistant used X to query Y", "ran a search", "sent the message").
|
|
84930
|
+
` + `- The act of delegating, dispatching, spawning, launching, steering or merging
|
|
84931
|
+
` + ` work \u2014 including when it succeeded. "X was dispatched and completed" is the
|
|
84932
|
+
` + ` session describing itself. Record only what the work LEARNED or CHANGED.
|
|
84933
|
+
` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
|
|
84934
|
+
` + ` running) \u2014 retain the outcome only once the task completes or a decision is made.
|
|
84935
|
+
` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
|
|
84936
|
+
` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
|
|
84937
|
+
` + ` reset assistant state").
|
|
84938
|
+
` + `- Hindsight's own errors, retries, backlogs, or internal state \u2014 the memory
|
|
84939
|
+
` + ` system's self-reports are not memories.
|
|
84940
|
+
` + `- Restatements of the user's current request or the task in progress.
|
|
84941
|
+
` + `- Transient state (unread counts, build status, what is running right now) unless
|
|
84942
|
+
` + ` the fact is explicitly dated, in which case record it as a dated observation.
|
|
84943
|
+
` + `- Greetings, acknowledgements, and routine operational chatter.
|
|
84944
|
+
` + `
|
|
84945
|
+
` + `Write each fact so it stands alone: name the thing, the number, and the date. A
|
|
84946
|
+
` + `sentence that only makes sense while reading this transcript is not durable.
|
|
84947
|
+
` + `
|
|
84948
|
+
` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
|
|
84949
|
+
` + `nothing durable remains, return an empty facts list.
|
|
84950
|
+
`
|
|
84746
84951
|
];
|
|
84747
84952
|
var DEFAULT_OBSERVATIONS_MISSION = `Synthesise durable, standing knowledge about the people, projects, and systems this agent works with: preferences and standing rules, roles and relationships, skills and recurring patterns, technical and operational decisions with their rationale, and the state of long-running work once it lands.
|
|
84748
84953
|
` + `
|
|
@@ -86424,7 +86629,7 @@ function shouldSweepChatAtBoot(chatId) {
|
|
|
86424
86629
|
|
|
86425
86630
|
// gateway/stale-pin-sweep-store.ts
|
|
86426
86631
|
var SWEEP_MAX_ATTEMPTS = 8;
|
|
86427
|
-
var
|
|
86632
|
+
var SWEEP_ENVELOPE_VERSION = 1;
|
|
86428
86633
|
function sweepTargetKey(chatId, threadId) {
|
|
86429
86634
|
return `${chatId}:${threadId ?? "-"}`;
|
|
86430
86635
|
}
|
|
@@ -86452,13 +86657,13 @@ function loadSweepCursors(path2, fs2) {
|
|
|
86452
86657
|
if (parsed == null || typeof parsed !== "object")
|
|
86453
86658
|
return [];
|
|
86454
86659
|
const env = parsed;
|
|
86455
|
-
if (
|
|
86660
|
+
if (!Number.isInteger(env.v) || env.v < 1 || !Array.isArray(env.cursors)) {
|
|
86456
86661
|
return [];
|
|
86457
86662
|
}
|
|
86458
86663
|
return env.cursors.filter(isCursorRow);
|
|
86459
86664
|
}
|
|
86460
86665
|
function persistSweepCursors(path2, fs2, cursors, log = (l) => process.stderr.write(l)) {
|
|
86461
|
-
const env = { v:
|
|
86666
|
+
const env = { v: SWEEP_ENVELOPE_VERSION, cursors: [...cursors] };
|
|
86462
86667
|
try {
|
|
86463
86668
|
fs2.writeFileSync(path2, JSON.stringify(env));
|
|
86464
86669
|
} catch (err) {
|
|
@@ -86879,11 +87084,13 @@ function createStalePinSweeper(deps) {
|
|
|
86879
87084
|
const repinUnpinLoop = async (target, kind, cursor) => {
|
|
86880
87085
|
const gate = gateFor(kind);
|
|
86881
87086
|
let popped = 0;
|
|
87087
|
+
let issued = 0;
|
|
86882
87088
|
let consecutiveFloods = 0;
|
|
86883
87089
|
let lastRetryAfter = null;
|
|
86884
87090
|
let noProgressAtSameRetryAfter = 0;
|
|
86885
87091
|
let lastObservedTop = null;
|
|
86886
87092
|
let unverifiedPop = false;
|
|
87093
|
+
const out = (r) => ({ ...r, issued });
|
|
86887
87094
|
const creditPop = () => {
|
|
86888
87095
|
popped++;
|
|
86889
87096
|
cursor.popped++;
|
|
@@ -86892,29 +87099,29 @@ function createStalePinSweeper(deps) {
|
|
|
86892
87099
|
};
|
|
86893
87100
|
for (let i = 0;i < gate.maxPopsPerChatPerSweep; i++) {
|
|
86894
87101
|
if (circuitOpen) {
|
|
86895
|
-
return { status: "aborted-circuit-breaker", popped, detail: "breaker open" };
|
|
87102
|
+
return out({ status: "aborted-circuit-breaker", popped, detail: "breaker open" });
|
|
86896
87103
|
}
|
|
86897
87104
|
const observed = await readTopVerified(target.chatId);
|
|
86898
87105
|
if (observed == null) {
|
|
86899
|
-
return { status: "incomplete", popped, detail: "pin-stack read unverifiable" };
|
|
87106
|
+
return out({ status: "incomplete", popped, detail: "pin-stack read unverifiable" });
|
|
86900
87107
|
}
|
|
86901
87108
|
if (unverifiedPop) {
|
|
86902
87109
|
if (observed.top != null && observed.top === lastObservedTop) {
|
|
86903
|
-
return {
|
|
87110
|
+
return out({
|
|
86904
87111
|
status: "incomplete",
|
|
86905
87112
|
popped,
|
|
86906
87113
|
detail: `no progress: message ${observed.top} still on top after a repin+unpin`
|
|
86907
|
-
};
|
|
87114
|
+
});
|
|
86908
87115
|
}
|
|
86909
87116
|
creditPop();
|
|
86910
87117
|
unverifiedPop = false;
|
|
86911
87118
|
}
|
|
86912
87119
|
if (observed.top == null) {
|
|
86913
|
-
return { status: "drained", popped };
|
|
87120
|
+
return out({ status: "drained", popped });
|
|
86914
87121
|
}
|
|
86915
87122
|
lastObservedTop = observed.top;
|
|
86916
87123
|
if (!await awaitWriteSlot(kind)) {
|
|
86917
|
-
return { status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" };
|
|
87124
|
+
return out({ status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" });
|
|
86918
87125
|
}
|
|
86919
87126
|
try {
|
|
86920
87127
|
await deps.pinSilent(target.chatId, observed.top);
|
|
@@ -86922,10 +87129,10 @@ function createStalePinSweeper(deps) {
|
|
|
86922
87129
|
const d = classify(err);
|
|
86923
87130
|
if (d.kind === "breaker") {
|
|
86924
87131
|
circuitOpen = true;
|
|
86925
|
-
return { status: "aborted-circuit-breaker", popped, detail: d.detail };
|
|
87132
|
+
return out({ status: "aborted-circuit-breaker", popped, detail: d.detail });
|
|
86926
87133
|
}
|
|
86927
87134
|
if (d.kind === "rights")
|
|
86928
|
-
return { status: "skipped-no-rights", popped };
|
|
87135
|
+
return out({ status: "skipped-no-rights", popped });
|
|
86929
87136
|
if (d.kind === "flood") {
|
|
86930
87137
|
consecutiveFloods++;
|
|
86931
87138
|
if (lastRetryAfter === d.seconds)
|
|
@@ -86934,19 +87141,19 @@ function createStalePinSweeper(deps) {
|
|
|
86934
87141
|
noProgressAtSameRetryAfter = 1;
|
|
86935
87142
|
lastRetryAfter = d.seconds;
|
|
86936
87143
|
if (consecutiveFloods >= MAX_CONSECUTIVE_FLOOD_WAITS || noProgressAtSameRetryAfter >= MAX_IDENTICAL_RETRY_AFTER_NO_PROGRESS) {
|
|
86937
|
-
return {
|
|
87144
|
+
return out({
|
|
86938
87145
|
status: "deferred-flood",
|
|
86939
87146
|
popped,
|
|
86940
87147
|
detail: `flood: ${consecutiveFloods} consecutive 429s, retry_after=${d.seconds}s`
|
|
86941
|
-
};
|
|
87148
|
+
});
|
|
86942
87149
|
}
|
|
86943
87150
|
await deps.sleep(d.seconds * 1000 * Math.pow(FLOOD_BACKOFF_FACTOR, consecutiveFloods - 1));
|
|
86944
87151
|
continue;
|
|
86945
87152
|
}
|
|
86946
|
-
return { status: "error", popped, detail: d.detail };
|
|
87153
|
+
return out({ status: "error", popped, detail: d.detail });
|
|
86947
87154
|
}
|
|
86948
87155
|
if (!await awaitWriteSlot(kind)) {
|
|
86949
|
-
return { status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" };
|
|
87156
|
+
return out({ status: "deferred-budget", popped, detail: "per-minute pin-op budget spent" });
|
|
86950
87157
|
}
|
|
86951
87158
|
try {
|
|
86952
87159
|
await deps.unpin(target.chatId, observed.top);
|
|
@@ -86954,12 +87161,12 @@ function createStalePinSweeper(deps) {
|
|
|
86954
87161
|
const d = classify(err);
|
|
86955
87162
|
if (d.kind === "breaker") {
|
|
86956
87163
|
circuitOpen = true;
|
|
86957
|
-
return { status: "aborted-circuit-breaker", popped, detail: d.detail };
|
|
87164
|
+
return out({ status: "aborted-circuit-breaker", popped, detail: d.detail });
|
|
86958
87165
|
}
|
|
86959
87166
|
if (d.kind === "rights")
|
|
86960
|
-
return { status: "skipped-no-rights", popped };
|
|
87167
|
+
return out({ status: "skipped-no-rights", popped });
|
|
86961
87168
|
if (isNothingToUnpinError(err))
|
|
86962
|
-
return { status: "drained", popped };
|
|
87169
|
+
return out({ status: "drained", popped });
|
|
86963
87170
|
if (d.kind === "flood") {
|
|
86964
87171
|
consecutiveFloods++;
|
|
86965
87172
|
if (lastRetryAfter === d.seconds)
|
|
@@ -86968,30 +87175,31 @@ function createStalePinSweeper(deps) {
|
|
|
86968
87175
|
noProgressAtSameRetryAfter = 1;
|
|
86969
87176
|
lastRetryAfter = d.seconds;
|
|
86970
87177
|
if (consecutiveFloods >= MAX_CONSECUTIVE_FLOOD_WAITS || noProgressAtSameRetryAfter >= MAX_IDENTICAL_RETRY_AFTER_NO_PROGRESS) {
|
|
86971
|
-
return {
|
|
87178
|
+
return out({
|
|
86972
87179
|
status: "deferred-flood",
|
|
86973
87180
|
popped,
|
|
86974
87181
|
detail: `flood: ${consecutiveFloods} consecutive 429s, retry_after=${d.seconds}s`
|
|
86975
|
-
};
|
|
87182
|
+
});
|
|
86976
87183
|
}
|
|
86977
87184
|
await deps.sleep(d.seconds * 1000 * Math.pow(FLOOD_BACKOFF_FACTOR, consecutiveFloods - 1));
|
|
86978
87185
|
continue;
|
|
86979
87186
|
}
|
|
86980
|
-
return { status: "error", popped, detail: d.detail };
|
|
87187
|
+
return out({ status: "error", popped, detail: d.detail });
|
|
86981
87188
|
}
|
|
86982
87189
|
consecutiveFloods = 0;
|
|
87190
|
+
issued++;
|
|
86983
87191
|
unverifiedPop = true;
|
|
86984
87192
|
}
|
|
86985
87193
|
const finalRead = await readTopVerified(target.chatId);
|
|
86986
87194
|
if (unverifiedPop && finalRead != null && finalRead.top !== lastObservedTop)
|
|
86987
87195
|
creditPop();
|
|
86988
87196
|
if (finalRead != null && finalRead.top == null)
|
|
86989
|
-
return { status: "drained", popped };
|
|
86990
|
-
return {
|
|
87197
|
+
return out({ status: "drained", popped });
|
|
87198
|
+
return out({
|
|
86991
87199
|
status: "deferred-budget",
|
|
86992
87200
|
popped,
|
|
86993
87201
|
detail: `hit the ${gate.maxPopsPerChatPerSweep}-pop cap for this chat; yielding to next boot`
|
|
86994
|
-
};
|
|
87202
|
+
});
|
|
86995
87203
|
};
|
|
86996
87204
|
const targetedUnpinDrain = async (target, kind, cursor, ids) => {
|
|
86997
87205
|
const alreadyDone = new Set(cursor.doneIds ?? []);
|
|
@@ -87088,10 +87296,21 @@ function createStalePinSweeper(deps) {
|
|
|
87088
87296
|
const forumTopicDrain = async (target, cursor) => {
|
|
87089
87297
|
const threadId = target.threadId;
|
|
87090
87298
|
if (threadId == null) {
|
|
87091
|
-
return {
|
|
87299
|
+
return {
|
|
87300
|
+
status: "skipped-nothing-recorded",
|
|
87301
|
+
popped: 0,
|
|
87302
|
+
issued: 0,
|
|
87303
|
+
detail: "no message_thread_id"
|
|
87304
|
+
};
|
|
87092
87305
|
}
|
|
87306
|
+
const before = await readTopVerified(target.chatId);
|
|
87093
87307
|
if (!await awaitWriteSlot("forum-topic")) {
|
|
87094
|
-
return {
|
|
87308
|
+
return {
|
|
87309
|
+
status: "deferred-budget",
|
|
87310
|
+
popped: 0,
|
|
87311
|
+
issued: 0,
|
|
87312
|
+
detail: "per-minute pin-op budget spent"
|
|
87313
|
+
};
|
|
87095
87314
|
}
|
|
87096
87315
|
try {
|
|
87097
87316
|
await deps.unpinAllForumTopicMessages(target.chatId, threadId);
|
|
@@ -87099,39 +87318,59 @@ function createStalePinSweeper(deps) {
|
|
|
87099
87318
|
const d = classify(err);
|
|
87100
87319
|
if (d.kind === "breaker") {
|
|
87101
87320
|
circuitOpen = true;
|
|
87102
|
-
return { status: "aborted-circuit-breaker", popped: 0, detail: d.detail };
|
|
87321
|
+
return { status: "aborted-circuit-breaker", popped: 0, issued: 0, detail: d.detail };
|
|
87103
87322
|
}
|
|
87104
87323
|
if (d.kind === "rights")
|
|
87105
|
-
return { status: "skipped-no-rights", popped: 0 };
|
|
87324
|
+
return { status: "skipped-no-rights", popped: 0, issued: 0 };
|
|
87106
87325
|
if (d.kind === "flood") {
|
|
87107
87326
|
return {
|
|
87108
87327
|
status: "deferred-flood",
|
|
87109
87328
|
popped: 0,
|
|
87329
|
+
issued: 0,
|
|
87110
87330
|
detail: `unpinAllForumTopicMessages pegged at retry_after=${d.seconds}s; not looping`
|
|
87111
87331
|
};
|
|
87112
87332
|
}
|
|
87113
|
-
return { status: "error", popped: 0, detail: d.detail };
|
|
87333
|
+
return { status: "error", popped: 0, issued: 0, detail: d.detail };
|
|
87114
87334
|
}
|
|
87115
|
-
cursor.popped++;
|
|
87116
|
-
cursor.updatedAt = deps.now();
|
|
87117
|
-
commit(cursor);
|
|
87118
87335
|
const observed = await readTopVerified(target.chatId);
|
|
87119
|
-
|
|
87120
|
-
|
|
87121
|
-
|
|
87336
|
+
const topMoved = before != null && observed != null && observed.top !== before.top ? 1 : 0;
|
|
87337
|
+
if (topMoved > 0) {
|
|
87338
|
+
cursor.popped += topMoved;
|
|
87339
|
+
cursor.updatedAt = deps.now();
|
|
87340
|
+
commit(cursor);
|
|
87341
|
+
}
|
|
87342
|
+
if (observed != null && observed.top == null) {
|
|
87343
|
+
return { status: "drained", popped: topMoved, issued: 1 };
|
|
87344
|
+
}
|
|
87345
|
+
return {
|
|
87346
|
+
status: "drained",
|
|
87347
|
+
popped: topMoved,
|
|
87348
|
+
issued: 1,
|
|
87349
|
+
detail: "topic drained; chat-wide stack may hold other topics"
|
|
87350
|
+
};
|
|
87122
87351
|
};
|
|
87123
87352
|
const sweepOnce = async (target) => {
|
|
87124
87353
|
if (!deps.eligible())
|
|
87125
|
-
return { status: "skipped-not-eligible", popped: 0 };
|
|
87354
|
+
return { status: "skipped-not-eligible", popped: 0, issued: 0 };
|
|
87126
87355
|
if (circuitOpen) {
|
|
87127
|
-
return {
|
|
87356
|
+
return {
|
|
87357
|
+
status: "aborted-circuit-breaker",
|
|
87358
|
+
popped: 0,
|
|
87359
|
+
issued: 0,
|
|
87360
|
+
detail: "breaker already open"
|
|
87361
|
+
};
|
|
87128
87362
|
}
|
|
87129
87363
|
const kind = classifyChatForSweep(target);
|
|
87130
87364
|
const cursor = loadCursor(target, kind);
|
|
87131
87365
|
if (cursor.done)
|
|
87132
|
-
return { status: "already-drained", popped: 0 };
|
|
87366
|
+
return { status: "already-drained", popped: 0, issued: 0 };
|
|
87133
87367
|
if (cursor.attempts >= SWEEP_MAX_ATTEMPTS) {
|
|
87134
|
-
return {
|
|
87368
|
+
return {
|
|
87369
|
+
status: "forfeited",
|
|
87370
|
+
popped: 0,
|
|
87371
|
+
issued: 0,
|
|
87372
|
+
detail: `${cursor.attempts} attempts exhausted`
|
|
87373
|
+
};
|
|
87135
87374
|
}
|
|
87136
87375
|
if (kind !== "dm") {
|
|
87137
87376
|
let allowed = false;
|
|
@@ -87145,7 +87384,7 @@ function createStalePinSweeper(deps) {
|
|
|
87145
87384
|
cursor.lastStatus = "skipped-no-rights";
|
|
87146
87385
|
cursor.updatedAt = deps.now();
|
|
87147
87386
|
commit(cursor);
|
|
87148
|
-
return { status: "skipped-no-rights", popped: 0 };
|
|
87387
|
+
return { status: "skipped-no-rights", popped: 0, issued: 0 };
|
|
87149
87388
|
}
|
|
87150
87389
|
}
|
|
87151
87390
|
cursor.attempts++;
|
|
@@ -87174,7 +87413,7 @@ function createStalePinSweeper(deps) {
|
|
|
87174
87413
|
} : await targetedUnpinDrain(target, kind, cursor, ids);
|
|
87175
87414
|
}
|
|
87176
87415
|
} catch (err) {
|
|
87177
|
-
result = { status: "error", popped: 0, detail: err.message };
|
|
87416
|
+
result = { status: "error", popped: 0, issued: 0, detail: err.message };
|
|
87178
87417
|
}
|
|
87179
87418
|
if (result.status === "drained" || result.status === "skipped-nothing-recorded") {
|
|
87180
87419
|
cursor.done = true;
|
|
@@ -87207,10 +87446,12 @@ function createStalePinSweeper(deps) {
|
|
|
87207
87446
|
const running = inFlight.get(key);
|
|
87208
87447
|
if (running != null)
|
|
87209
87448
|
return running;
|
|
87210
|
-
if (!deps.eligible())
|
|
87211
|
-
return Promise.resolve({ status: "skipped-not-eligible", popped: 0 });
|
|
87212
|
-
|
|
87213
|
-
|
|
87449
|
+
if (!deps.eligible()) {
|
|
87450
|
+
return Promise.resolve({ status: "skipped-not-eligible", popped: 0, issued: 0 });
|
|
87451
|
+
}
|
|
87452
|
+
if (attempted.has(key)) {
|
|
87453
|
+
return Promise.resolve({ status: "already-attempted", popped: 0, issued: 0 });
|
|
87454
|
+
}
|
|
87214
87455
|
attempted.add(key);
|
|
87215
87456
|
const p = sweepOnce(target).finally(() => inFlight.delete(key));
|
|
87216
87457
|
inFlight.set(key, p);
|
|
@@ -90115,7 +90356,7 @@ function createCapturedResumeDispatcher(ports) {
|
|
|
90115
90356
|
|
|
90116
90357
|
// gateway/status-pin-store.ts
|
|
90117
90358
|
var BOOT_UNPIN_MAX_ATTEMPTS = 5;
|
|
90118
|
-
var
|
|
90359
|
+
var SNAPSHOT_VERSION2 = 4;
|
|
90119
90360
|
function isPinRow2(x) {
|
|
90120
90361
|
if (x == null || typeof x !== "object")
|
|
90121
90362
|
return false;
|
|
@@ -90140,12 +90381,12 @@ function loadStatusPins2(path3, fs3) {
|
|
|
90140
90381
|
if (parsed == null || typeof parsed !== "object")
|
|
90141
90382
|
return [];
|
|
90142
90383
|
const env = parsed;
|
|
90143
|
-
if (
|
|
90384
|
+
if (!Number.isInteger(env.v) || env.v < 1 || !Array.isArray(env.pins))
|
|
90144
90385
|
return [];
|
|
90145
90386
|
return env.pins.filter(isPinRow2);
|
|
90146
90387
|
}
|
|
90147
90388
|
function persistStatusPins2(path3, fs3, snapshot, log = (l) => process.stderr.write(l)) {
|
|
90148
|
-
const env = { v:
|
|
90389
|
+
const env = { v: SNAPSHOT_VERSION2, pins: [...snapshot] };
|
|
90149
90390
|
const tmp = path3 + ".tmp";
|
|
90150
90391
|
try {
|
|
90151
90392
|
fs3.writeFileSync(tmp, JSON.stringify(env));
|
|
@@ -96446,6 +96687,12 @@ function parseAuditLine(line) {
|
|
|
96446
96687
|
entry.m = o.m;
|
|
96447
96688
|
if (typeof o.prior_pin === "string")
|
|
96448
96689
|
entry.prior_pin = o.prior_pin;
|
|
96690
|
+
if (Array.isArray(o.prior_pin_observed) && o.prior_pin_observed.every((v) => typeof v === "string")) {
|
|
96691
|
+
entry.prior_pin_observed = o.prior_pin_observed;
|
|
96692
|
+
}
|
|
96693
|
+
if (typeof o.prior_pin_source === "string") {
|
|
96694
|
+
entry.prior_pin_source = o.prior_pin_source;
|
|
96695
|
+
}
|
|
96449
96696
|
return entry;
|
|
96450
96697
|
}
|
|
96451
96698
|
function filterEntries(entries, filters) {
|
|
@@ -99070,10 +99317,10 @@ function startOutboxSweep(deps) {
|
|
|
99070
99317
|
}
|
|
99071
99318
|
|
|
99072
99319
|
// ../src/build-info.ts
|
|
99073
|
-
var VERSION2 = "0.19.
|
|
99074
|
-
var COMMIT_SHA = "
|
|
99075
|
-
var COMMIT_DATE = "2026-07-
|
|
99076
|
-
var LATEST_PR =
|
|
99320
|
+
var VERSION2 = "0.19.33";
|
|
99321
|
+
var COMMIT_SHA = "4ccbde6b";
|
|
99322
|
+
var COMMIT_DATE = "2026-07-29T02:39:39Z";
|
|
99323
|
+
var LATEST_PR = 3973;
|
|
99077
99324
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
99078
99325
|
|
|
99079
99326
|
// gateway/boot-version.ts
|