switchroom 0.19.44 → 0.19.45
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/cli/switchroom.js +940 -1006
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +486 -314
- package/telegram-plugin/flushed-turn-supersede.ts +190 -16
- package/telegram-plugin/gateway/cron-session.ts +31 -0
- package/telegram-plugin/gateway/gateway.ts +66 -65
- package/telegram-plugin/gateway/outbound-send-path.ts +60 -8
- package/telegram-plugin/gateway/reply-owner-wiring.ts +128 -0
- package/telegram-plugin/gateway/stream-render.ts +91 -1
- package/telegram-plugin/gateway/subagent-handback-marker.ts +49 -9
- package/telegram-plugin/gateway/subagent-reply-authority.ts +147 -0
- package/telegram-plugin/gateway/turn-end.ts +9 -1
- package/telegram-plugin/reply-owner-resolve.ts +102 -26
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +110 -8
- package/telegram-plugin/tests/narrative-lane-golden.test.ts +2 -0
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +297 -27
- package/telegram-plugin/tests/reply-quote-wire.test.ts +2 -0
- package/telegram-plugin/tests/send-reply-golden.test.ts +567 -5
- package/telegram-plugin/tests/stream-render-golden.test.ts +204 -1
|
@@ -41869,7 +41869,8 @@ function normalizeForDedup(text) {
|
|
|
41869
41869
|
}
|
|
41870
41870
|
|
|
41871
41871
|
// flushed-turn-supersede.ts
|
|
41872
|
-
var
|
|
41872
|
+
var SUPERSEDE_OPEN_WINDOW_CAP_MS = 30 * 60000;
|
|
41873
|
+
var SUPERSEDE_COMPLETED_GRACE_MS = 60000;
|
|
41873
41874
|
var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS = 32;
|
|
41874
41875
|
function normalizeForMatch(text) {
|
|
41875
41876
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -41888,10 +41889,12 @@ function flushedAnswerMatchesReply(flushedText, replyText) {
|
|
|
41888
41889
|
return false;
|
|
41889
41890
|
}
|
|
41890
41891
|
function decideSupersede(record, args) {
|
|
41891
|
-
const
|
|
41892
|
+
const openCapMs = args.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS;
|
|
41893
|
+
const completedGraceMs = args.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS;
|
|
41892
41894
|
if (record == null)
|
|
41893
41895
|
return { supersede: false, deleteMessageIds: [], reason: "no-record" };
|
|
41894
|
-
|
|
41896
|
+
const expired = record.completedAt == null ? args.now - record.ts > openCapMs : args.now - record.completedAt > completedGraceMs;
|
|
41897
|
+
if (expired) {
|
|
41895
41898
|
return { supersede: false, deleteMessageIds: [], reason: "expired" };
|
|
41896
41899
|
}
|
|
41897
41900
|
const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
|
|
@@ -41915,9 +41918,11 @@ function turnKey(turnId) {
|
|
|
41915
41918
|
|
|
41916
41919
|
class FlushedTurnSupersedeRegistry {
|
|
41917
41920
|
entries = new Map;
|
|
41918
|
-
|
|
41921
|
+
openCapMs;
|
|
41922
|
+
completedGraceMs;
|
|
41919
41923
|
constructor(opts = {}) {
|
|
41920
|
-
this.
|
|
41924
|
+
this.openCapMs = opts.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS;
|
|
41925
|
+
this.completedGraceMs = opts.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS;
|
|
41921
41926
|
}
|
|
41922
41927
|
record(chatId, threadId, rec, now) {
|
|
41923
41928
|
if (rec.messageIds.length === 0)
|
|
@@ -41933,9 +41938,18 @@ class FlushedTurnSupersedeRegistry {
|
|
|
41933
41938
|
turnId: rec.turnId,
|
|
41934
41939
|
messageIds: [...rec.messageIds],
|
|
41935
41940
|
text: rec.text,
|
|
41936
|
-
ts: now
|
|
41941
|
+
ts: now,
|
|
41942
|
+
completedAt: rec.completedAt ?? null
|
|
41937
41943
|
});
|
|
41938
41944
|
}
|
|
41945
|
+
completeAll(now) {
|
|
41946
|
+
for (const laneMap of this.entries.values()) {
|
|
41947
|
+
for (const rec of laneMap.values()) {
|
|
41948
|
+
if (rec.completedAt == null)
|
|
41949
|
+
rec.completedAt = now;
|
|
41950
|
+
}
|
|
41951
|
+
}
|
|
41952
|
+
}
|
|
41939
41953
|
peek(chatId, threadId, args) {
|
|
41940
41954
|
const rec = this.entries.get(makeKey2(chatId, threadId))?.get(turnKey(args.liveTurnId));
|
|
41941
41955
|
return decideSupersede(rec, {
|
|
@@ -41943,7 +41957,8 @@ class FlushedTurnSupersedeRegistry {
|
|
|
41943
41957
|
replyText: args.replyText,
|
|
41944
41958
|
positiveAttribution: args.positiveAttribution,
|
|
41945
41959
|
now: args.now,
|
|
41946
|
-
|
|
41960
|
+
openCapMs: this.openCapMs,
|
|
41961
|
+
completedGraceMs: this.completedGraceMs
|
|
41947
41962
|
});
|
|
41948
41963
|
}
|
|
41949
41964
|
take(chatId, threadId, args) {
|
|
@@ -41966,7 +41981,8 @@ class FlushedTurnSupersedeRegistry {
|
|
|
41966
41981
|
sweep(now) {
|
|
41967
41982
|
for (const [lane, laneMap] of this.entries) {
|
|
41968
41983
|
for (const [tk, rec] of laneMap) {
|
|
41969
|
-
|
|
41984
|
+
const expired = rec.completedAt == null ? now - rec.ts > this.openCapMs : now - rec.completedAt > this.completedGraceMs;
|
|
41985
|
+
if (expired)
|
|
41970
41986
|
laneMap.delete(tk);
|
|
41971
41987
|
}
|
|
41972
41988
|
if (laneMap.size === 0)
|
|
@@ -41985,6 +42001,302 @@ function makeKey2(chatId, threadId) {
|
|
|
41985
42001
|
return threadId == null ? chatId : `${chatId}|${threadId}`;
|
|
41986
42002
|
}
|
|
41987
42003
|
|
|
42004
|
+
// flushed-turn-supersede.ts
|
|
42005
|
+
var SUPERSEDE_OPEN_WINDOW_CAP_MS2 = 30 * 60000;
|
|
42006
|
+
var SUPERSEDE_COMPLETED_GRACE_MS2 = 60000;
|
|
42007
|
+
var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
|
|
42008
|
+
function normalizeForMatch2(text) {
|
|
42009
|
+
return text.replace(/\s+/g, " ").trim();
|
|
42010
|
+
}
|
|
42011
|
+
function flushedAnswerMatchesReply2(flushedText, replyText) {
|
|
42012
|
+
const flushed = normalizeForMatch2(flushedText);
|
|
42013
|
+
const reply = normalizeForMatch2(replyText);
|
|
42014
|
+
if (flushed.length === 0 || reply.length === 0)
|
|
42015
|
+
return false;
|
|
42016
|
+
if (flushed === reply)
|
|
42017
|
+
return true;
|
|
42018
|
+
if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
|
|
42019
|
+
return true;
|
|
42020
|
+
if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
|
|
42021
|
+
return true;
|
|
42022
|
+
return false;
|
|
42023
|
+
}
|
|
42024
|
+
function decideSupersede2(record, args) {
|
|
42025
|
+
const openCapMs = args.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS2;
|
|
42026
|
+
const completedGraceMs = args.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
|
|
42027
|
+
if (record == null)
|
|
42028
|
+
return { supersede: false, deleteMessageIds: [], reason: "no-record" };
|
|
42029
|
+
const expired = record.completedAt == null ? args.now - record.ts > openCapMs : args.now - record.completedAt > completedGraceMs;
|
|
42030
|
+
if (expired) {
|
|
42031
|
+
return { supersede: false, deleteMessageIds: [], reason: "expired" };
|
|
42032
|
+
}
|
|
42033
|
+
const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
|
|
42034
|
+
if (!sameTurn) {
|
|
42035
|
+
return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
|
|
42036
|
+
}
|
|
42037
|
+
if (!args.positiveAttribution && args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
|
|
42038
|
+
return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
|
|
42039
|
+
}
|
|
42040
|
+
return {
|
|
42041
|
+
supersede: true,
|
|
42042
|
+
deleteMessageIds: [...record.messageIds],
|
|
42043
|
+
reason: "supersede",
|
|
42044
|
+
recordText: record.text
|
|
42045
|
+
};
|
|
42046
|
+
}
|
|
42047
|
+
function decideSupersedeCorrection(input) {
|
|
42048
|
+
const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
|
|
42049
|
+
if (eligible) {
|
|
42050
|
+
return { mode: "edit-in-place", editMessageId: input.flushMessageIds[0], deleteMessageIds: [] };
|
|
42051
|
+
}
|
|
42052
|
+
return { mode: "delete-resend", deleteMessageIds: [...input.flushMessageIds] };
|
|
42053
|
+
}
|
|
42054
|
+
var NULL_TURN_KEY2 = "<<null-turn>>";
|
|
42055
|
+
function turnKey2(turnId) {
|
|
42056
|
+
return turnId == null ? NULL_TURN_KEY2 : turnId;
|
|
42057
|
+
}
|
|
42058
|
+
|
|
42059
|
+
class FlushedTurnSupersedeRegistry2 {
|
|
42060
|
+
entries = new Map;
|
|
42061
|
+
openCapMs;
|
|
42062
|
+
completedGraceMs;
|
|
42063
|
+
constructor(opts = {}) {
|
|
42064
|
+
this.openCapMs = opts.openCapMs ?? SUPERSEDE_OPEN_WINDOW_CAP_MS2;
|
|
42065
|
+
this.completedGraceMs = opts.completedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
|
|
42066
|
+
}
|
|
42067
|
+
record(chatId, threadId, rec, now) {
|
|
42068
|
+
if (rec.messageIds.length === 0)
|
|
42069
|
+
return;
|
|
42070
|
+
this.sweep(now);
|
|
42071
|
+
const lane = makeKey3(chatId, threadId);
|
|
42072
|
+
let laneMap = this.entries.get(lane);
|
|
42073
|
+
if (laneMap == null) {
|
|
42074
|
+
laneMap = new Map;
|
|
42075
|
+
this.entries.set(lane, laneMap);
|
|
42076
|
+
}
|
|
42077
|
+
laneMap.set(turnKey2(rec.turnId), {
|
|
42078
|
+
turnId: rec.turnId,
|
|
42079
|
+
messageIds: [...rec.messageIds],
|
|
42080
|
+
text: rec.text,
|
|
42081
|
+
ts: now,
|
|
42082
|
+
completedAt: rec.completedAt ?? null
|
|
42083
|
+
});
|
|
42084
|
+
}
|
|
42085
|
+
completeAll(now) {
|
|
42086
|
+
for (const laneMap of this.entries.values()) {
|
|
42087
|
+
for (const rec of laneMap.values()) {
|
|
42088
|
+
if (rec.completedAt == null)
|
|
42089
|
+
rec.completedAt = now;
|
|
42090
|
+
}
|
|
42091
|
+
}
|
|
42092
|
+
}
|
|
42093
|
+
peek(chatId, threadId, args) {
|
|
42094
|
+
const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
|
|
42095
|
+
return decideSupersede2(rec, {
|
|
42096
|
+
liveTurnId: args.liveTurnId,
|
|
42097
|
+
replyText: args.replyText,
|
|
42098
|
+
positiveAttribution: args.positiveAttribution,
|
|
42099
|
+
now: args.now,
|
|
42100
|
+
openCapMs: this.openCapMs,
|
|
42101
|
+
completedGraceMs: this.completedGraceMs
|
|
42102
|
+
});
|
|
42103
|
+
}
|
|
42104
|
+
take(chatId, threadId, args) {
|
|
42105
|
+
const lane = makeKey3(chatId, threadId);
|
|
42106
|
+
const decision = this.peek(chatId, threadId, args);
|
|
42107
|
+
if (decision.supersede) {
|
|
42108
|
+
const laneMap = this.entries.get(lane);
|
|
42109
|
+
laneMap?.delete(turnKey2(args.liveTurnId));
|
|
42110
|
+
if (laneMap != null && laneMap.size === 0)
|
|
42111
|
+
this.entries.delete(lane);
|
|
42112
|
+
}
|
|
42113
|
+
return decision;
|
|
42114
|
+
}
|
|
42115
|
+
forget(chatId, threadId) {
|
|
42116
|
+
this.entries.delete(makeKey3(chatId, threadId));
|
|
42117
|
+
}
|
|
42118
|
+
clear() {
|
|
42119
|
+
this.entries.clear();
|
|
42120
|
+
}
|
|
42121
|
+
sweep(now) {
|
|
42122
|
+
for (const [lane, laneMap] of this.entries) {
|
|
42123
|
+
for (const [tk, rec] of laneMap) {
|
|
42124
|
+
const expired = rec.completedAt == null ? now - rec.ts > this.openCapMs : now - rec.completedAt > this.completedGraceMs;
|
|
42125
|
+
if (expired)
|
|
42126
|
+
laneMap.delete(tk);
|
|
42127
|
+
}
|
|
42128
|
+
if (laneMap.size === 0)
|
|
42129
|
+
this.entries.delete(lane);
|
|
42130
|
+
}
|
|
42131
|
+
}
|
|
42132
|
+
size(now) {
|
|
42133
|
+
this.sweep(now);
|
|
42134
|
+
let total = 0;
|
|
42135
|
+
for (const laneMap of this.entries.values())
|
|
42136
|
+
total += laneMap.size;
|
|
42137
|
+
return total;
|
|
42138
|
+
}
|
|
42139
|
+
}
|
|
42140
|
+
function makeKey3(chatId, threadId) {
|
|
42141
|
+
return threadId == null ? chatId : `${chatId}|${threadId}`;
|
|
42142
|
+
}
|
|
42143
|
+
|
|
42144
|
+
class FlushCompletionTracker {
|
|
42145
|
+
pending = [];
|
|
42146
|
+
open(turn) {
|
|
42147
|
+
turn.realEndObservedAt = null;
|
|
42148
|
+
if (!this.pending.includes(turn))
|
|
42149
|
+
this.pending.push(turn);
|
|
42150
|
+
}
|
|
42151
|
+
closeAll(now) {
|
|
42152
|
+
let closed = 0;
|
|
42153
|
+
for (const turn of this.pending) {
|
|
42154
|
+
if (turn.realEndObservedAt == null) {
|
|
42155
|
+
turn.realEndObservedAt = now;
|
|
42156
|
+
closed++;
|
|
42157
|
+
}
|
|
42158
|
+
}
|
|
42159
|
+
this.pending = [];
|
|
42160
|
+
return closed;
|
|
42161
|
+
}
|
|
42162
|
+
pendingCount() {
|
|
42163
|
+
return this.pending.length;
|
|
42164
|
+
}
|
|
42165
|
+
}
|
|
42166
|
+
|
|
42167
|
+
// reply-owner-resolve.ts
|
|
42168
|
+
function latestEndedAccepted(candidates) {
|
|
42169
|
+
if (candidates.latestEndedTurnId == null)
|
|
42170
|
+
return false;
|
|
42171
|
+
const age = candidates.latestEndedAgeMs;
|
|
42172
|
+
const ttl = candidates.latestEndedTtlMs;
|
|
42173
|
+
if (age == null || ttl == null)
|
|
42174
|
+
return false;
|
|
42175
|
+
const realEndAge = candidates.latestEndedRealEndAgeMs;
|
|
42176
|
+
if (realEndAge === undefined || realEndAge === null) {
|
|
42177
|
+
return age <= ttl;
|
|
42178
|
+
}
|
|
42179
|
+
const grace = candidates.latestEndedCompletedGraceMs ?? SUPERSEDE_COMPLETED_GRACE_MS2;
|
|
42180
|
+
return realEndAge <= grace;
|
|
42181
|
+
}
|
|
42182
|
+
function resolveReplyOwnerTier(candidates) {
|
|
42183
|
+
if (candidates.liveTurnId != null)
|
|
42184
|
+
return "live";
|
|
42185
|
+
if (candidates.originTurnId != null)
|
|
42186
|
+
return "origin";
|
|
42187
|
+
if (candidates.quotedTurnId != null)
|
|
42188
|
+
return "quoted";
|
|
42189
|
+
if (latestEndedAccepted(candidates))
|
|
42190
|
+
return "latest-ended";
|
|
42191
|
+
return "none";
|
|
42192
|
+
}
|
|
42193
|
+
function resolveReplyOwnerTurnId(candidates) {
|
|
42194
|
+
switch (resolveReplyOwnerTier(candidates)) {
|
|
42195
|
+
case "live":
|
|
42196
|
+
return candidates.liveTurnId;
|
|
42197
|
+
case "origin":
|
|
42198
|
+
return candidates.originTurnId;
|
|
42199
|
+
case "quoted":
|
|
42200
|
+
return candidates.quotedTurnId;
|
|
42201
|
+
case "latest-ended":
|
|
42202
|
+
return candidates.latestEndedTurnId;
|
|
42203
|
+
case "none":
|
|
42204
|
+
return null;
|
|
42205
|
+
}
|
|
42206
|
+
}
|
|
42207
|
+
function decideContentGateBypass(input) {
|
|
42208
|
+
if (input.tier === "live")
|
|
42209
|
+
return true;
|
|
42210
|
+
if (input.tier === "none")
|
|
42211
|
+
return false;
|
|
42212
|
+
if (input.handbackCouldOwnReply)
|
|
42213
|
+
return false;
|
|
42214
|
+
if (input.subagentCouldOwnReply !== false)
|
|
42215
|
+
return false;
|
|
42216
|
+
if (input.candidates == null)
|
|
42217
|
+
return false;
|
|
42218
|
+
if (!latestEndedAccepted(input.candidates))
|
|
42219
|
+
return false;
|
|
42220
|
+
return input.resolvedTurnId != null && input.resolvedTurnId === input.candidates.latestEndedTurnId;
|
|
42221
|
+
}
|
|
42222
|
+
function decideAnswerLatchSuppression(input) {
|
|
42223
|
+
if (input.superseded)
|
|
42224
|
+
return false;
|
|
42225
|
+
if (!input.replySubstantive)
|
|
42226
|
+
return false;
|
|
42227
|
+
if (!input.isLateReply)
|
|
42228
|
+
return false;
|
|
42229
|
+
if (input.replyMatchesFlushedAnswer === false)
|
|
42230
|
+
return false;
|
|
42231
|
+
return input.ownerAnswerDelivered === "flush";
|
|
42232
|
+
}
|
|
42233
|
+
|
|
42234
|
+
// gateway/reply-owner-wiring.ts
|
|
42235
|
+
var SUPERSEDE_OPEN_CAP_MS = (() => {
|
|
42236
|
+
const raw = Number(process.env.SWITCHROOM_SUPERSEDE_OPEN_CAP_MS);
|
|
42237
|
+
return Number.isFinite(raw) && raw > 0 ? raw : SUPERSEDE_OPEN_WINDOW_CAP_MS2;
|
|
42238
|
+
})();
|
|
42239
|
+
var SUPERSEDE_GRACE_MS = (() => {
|
|
42240
|
+
const raw = Number(process.env.SWITCHROOM_SUPERSEDE_GRACE_MS);
|
|
42241
|
+
return Number.isFinite(raw) && raw > 0 ? raw : SUPERSEDE_COMPLETED_GRACE_MS2;
|
|
42242
|
+
})();
|
|
42243
|
+
function resolveReplyOwnerTurnWith(lookups, liveTurn, chatId, args) {
|
|
42244
|
+
const origin = lookups.findTurnByOriginId(args.origin_turn_id);
|
|
42245
|
+
const quoted = lookups.findTurnByQuotedMessageId(chatId, args.reply_to);
|
|
42246
|
+
const latestEnded = lookups.findLatestTurnForChat(chatId, { endedOnly: true });
|
|
42247
|
+
const byId = new Map;
|
|
42248
|
+
for (const t of [latestEnded, quoted, origin, liveTurn]) {
|
|
42249
|
+
if (t != null)
|
|
42250
|
+
byId.set(t.turnId, t);
|
|
42251
|
+
}
|
|
42252
|
+
const latestEndedAgeMs = latestEnded?.endedAt != null ? lookups.now() - latestEnded.endedAt : null;
|
|
42253
|
+
const latestEndedRealEndAgeMs = latestEnded == null ? null : latestEnded.realEndObservedAt != null ? lookups.now() - latestEnded.realEndObservedAt : null;
|
|
42254
|
+
const candidates = {
|
|
42255
|
+
liveTurnId: liveTurn?.turnId ?? null,
|
|
42256
|
+
originTurnId: origin?.turnId ?? null,
|
|
42257
|
+
quotedTurnId: quoted?.turnId ?? null,
|
|
42258
|
+
latestEndedTurnId: latestEnded?.turnId ?? null,
|
|
42259
|
+
latestEndedAgeMs,
|
|
42260
|
+
latestEndedTtlMs: SUPERSEDE_OPEN_CAP_MS,
|
|
42261
|
+
latestEndedRealEndAgeMs,
|
|
42262
|
+
latestEndedCompletedGraceMs: SUPERSEDE_GRACE_MS
|
|
42263
|
+
};
|
|
42264
|
+
const tier = resolveReplyOwnerTier(candidates);
|
|
42265
|
+
const winnerId = resolveReplyOwnerTurnId(candidates);
|
|
42266
|
+
return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
|
|
42267
|
+
}
|
|
42268
|
+
|
|
42269
|
+
// gateway/subagent-reply-authority.ts
|
|
42270
|
+
var SUB_AGENT_KIND_PREFIX = "sub_agent_";
|
|
42271
|
+
|
|
42272
|
+
class SubagentReplyAuthority {
|
|
42273
|
+
live = new Set;
|
|
42274
|
+
noteSessionEvent(ev) {
|
|
42275
|
+
if (ev == null)
|
|
42276
|
+
return;
|
|
42277
|
+
const kind = ev.kind;
|
|
42278
|
+
if (typeof kind !== "string" || !kind.startsWith(SUB_AGENT_KIND_PREFIX))
|
|
42279
|
+
return;
|
|
42280
|
+
const agentId = ev.agentId;
|
|
42281
|
+
if (typeof agentId !== "string" || agentId === "")
|
|
42282
|
+
return;
|
|
42283
|
+
if (kind === `${SUB_AGENT_KIND_PREFIX}turn_end`)
|
|
42284
|
+
this.live.delete(agentId);
|
|
42285
|
+
else
|
|
42286
|
+
this.live.add(agentId);
|
|
42287
|
+
}
|
|
42288
|
+
subagentCouldOwnReply() {
|
|
42289
|
+
return this.live.size > 0;
|
|
42290
|
+
}
|
|
42291
|
+
reset() {
|
|
42292
|
+
this.live.clear();
|
|
42293
|
+
}
|
|
42294
|
+
liveCount() {
|
|
42295
|
+
return this.live.size;
|
|
42296
|
+
}
|
|
42297
|
+
}
|
|
42298
|
+
var subagentReplyAuthority = new SubagentReplyAuthority;
|
|
42299
|
+
|
|
41988
42300
|
// gateway/inbound-coalesce.ts
|
|
41989
42301
|
function createInboundCoalescer(opts) {
|
|
41990
42302
|
const buffer = new Map;
|
|
@@ -53954,11 +54266,11 @@ function createHandbackPreturnSignal(deps) {
|
|
|
53954
54266
|
}
|
|
53955
54267
|
return false;
|
|
53956
54268
|
},
|
|
53957
|
-
isPreTurnRecord(
|
|
53958
|
-
return
|
|
54269
|
+
isPreTurnRecord(turnKey3) {
|
|
54270
|
+
return turnKey3.startsWith(PRETURN_TURNKEY_PREFIX);
|
|
53959
54271
|
},
|
|
53960
|
-
handleReaped(
|
|
53961
|
-
const statusKey = bySyntheticKey.get(
|
|
54272
|
+
handleReaped(turnKey3) {
|
|
54273
|
+
const statusKey = bySyntheticKey.get(turnKey3);
|
|
53962
54274
|
if (statusKey == null)
|
|
53963
54275
|
return;
|
|
53964
54276
|
const entry = byKey.get(statusKey);
|
|
@@ -71288,16 +71600,16 @@ function writeSilentEndState(args, deps) {
|
|
|
71288
71600
|
`);
|
|
71289
71601
|
}
|
|
71290
71602
|
}
|
|
71291
|
-
function clearSilentEndState(
|
|
71603
|
+
function clearSilentEndState(turnKey3, deps) {
|
|
71292
71604
|
const statePath = resolveStatePath2(deps);
|
|
71293
71605
|
if (!existsSync16(statePath))
|
|
71294
71606
|
return;
|
|
71295
71607
|
try {
|
|
71296
71608
|
const prev = JSON.parse(readFileSync18(statePath, "utf8"));
|
|
71297
|
-
if (prev.turnKey != null && prev.turnKey !==
|
|
71609
|
+
if (prev.turnKey != null && prev.turnKey !== turnKey3)
|
|
71298
71610
|
return;
|
|
71299
71611
|
unlinkSync12(statePath);
|
|
71300
|
-
emitLog(deps, `silent-end: cleared state file turnKey=${
|
|
71612
|
+
emitLog(deps, `silent-end: cleared state file turnKey=${turnKey3}
|
|
71301
71613
|
`);
|
|
71302
71614
|
} catch {}
|
|
71303
71615
|
}
|
|
@@ -75372,12 +75684,12 @@ class PendingUserNoticeGate {
|
|
|
75372
75684
|
this.pending = this.pending.filter((p) => !(p.agent === notice.agent && p.key === notice.key));
|
|
75373
75685
|
this.pending.push(notice);
|
|
75374
75686
|
}
|
|
75375
|
-
resolveTurnEnd(
|
|
75687
|
+
resolveTurnEnd(turnKey3, turnDeliveredReply, now = Date.now()) {
|
|
75376
75688
|
this.prune(now);
|
|
75377
75689
|
const resolved = [];
|
|
75378
75690
|
const remaining = [];
|
|
75379
75691
|
for (const p of this.pending) {
|
|
75380
|
-
if (p.key ===
|
|
75692
|
+
if (p.key === turnKey3 || p.key === undefined)
|
|
75381
75693
|
resolved.push(p);
|
|
75382
75694
|
else
|
|
75383
75695
|
remaining.push(p);
|
|
@@ -78005,166 +78317,76 @@ ${input.newReplyText}`;
|
|
|
78005
78317
|
};
|
|
78006
78318
|
}
|
|
78007
78319
|
|
|
78008
|
-
//
|
|
78009
|
-
var
|
|
78010
|
-
|
|
78011
|
-
|
|
78012
|
-
|
|
78013
|
-
}
|
|
78014
|
-
|
|
78015
|
-
|
|
78016
|
-
|
|
78017
|
-
|
|
78320
|
+
// gateway/subagent-handback-marker.ts
|
|
78321
|
+
var INBOUND_SOURCE_CLASSIFICATION = {
|
|
78322
|
+
subagent_handback: { decoupledCompletion: true },
|
|
78323
|
+
cron: { decoupledCompletion: false },
|
|
78324
|
+
reaction: { decoupledCompletion: false },
|
|
78325
|
+
subagent_progress: { decoupledCompletion: false },
|
|
78326
|
+
resume_interrupted: { decoupledCompletion: false },
|
|
78327
|
+
resume_deferred: { decoupledCompletion: false },
|
|
78328
|
+
resume_watchdog_timeout: { decoupledCompletion: false },
|
|
78329
|
+
vault_grant_approved: { decoupledCompletion: false },
|
|
78330
|
+
vault_grant_denied: { decoupledCompletion: false },
|
|
78331
|
+
vault_grant_timeout: { decoupledCompletion: false },
|
|
78332
|
+
vault_save_completed: { decoupledCompletion: false },
|
|
78333
|
+
vault_save_discarded: { decoupledCompletion: false },
|
|
78334
|
+
vault_save_failed: { decoupledCompletion: false },
|
|
78335
|
+
vault_save_timeout: { decoupledCompletion: false },
|
|
78336
|
+
secret_provided: { decoupledCompletion: false },
|
|
78337
|
+
secret_declined: { decoupledCompletion: false },
|
|
78338
|
+
secret_provide_failed: { decoupledCompletion: false },
|
|
78339
|
+
secret_request_timeout: { decoupledCompletion: false },
|
|
78340
|
+
mental_model_propose_timeout: { decoupledCompletion: false },
|
|
78341
|
+
bridge_dead_restart: { decoupledCompletion: false },
|
|
78342
|
+
obligation_represent: { decoupledCompletion: false },
|
|
78343
|
+
missed_approval_retry: { decoupledCompletion: false },
|
|
78344
|
+
skill_proposal_apply: { decoupledCompletion: false },
|
|
78345
|
+
warmup: { decoupledCompletion: false },
|
|
78346
|
+
mental_model_proposal_applied: { decoupledCompletion: false },
|
|
78347
|
+
mental_model_proposal_denied: { decoupledCompletion: false },
|
|
78348
|
+
mental_model_proposal_failed: { decoupledCompletion: false },
|
|
78349
|
+
webhook: { decoupledCompletion: false },
|
|
78350
|
+
linear: { decoupledCompletion: false }
|
|
78351
|
+
};
|
|
78352
|
+
function stampsHandbackMarker(source) {
|
|
78353
|
+
if (source == null)
|
|
78018
78354
|
return false;
|
|
78019
|
-
|
|
78020
|
-
|
|
78021
|
-
if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
|
|
78022
|
-
return true;
|
|
78023
|
-
if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
|
|
78355
|
+
const known = INBOUND_SOURCE_CLASSIFICATION[source];
|
|
78356
|
+
if (known == null)
|
|
78024
78357
|
return true;
|
|
78025
|
-
return
|
|
78026
|
-
}
|
|
78027
|
-
function decideSupersede2(record, args) {
|
|
78028
|
-
const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
|
|
78029
|
-
if (record == null)
|
|
78030
|
-
return { supersede: false, deleteMessageIds: [], reason: "no-record" };
|
|
78031
|
-
if (args.now - record.ts > ttlMs) {
|
|
78032
|
-
return { supersede: false, deleteMessageIds: [], reason: "expired" };
|
|
78033
|
-
}
|
|
78034
|
-
const sameTurn = record.turnId != null ? record.turnId === args.liveTurnId : args.liveTurnId == null;
|
|
78035
|
-
if (!sameTurn) {
|
|
78036
|
-
return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
|
|
78037
|
-
}
|
|
78038
|
-
if (!args.positiveAttribution && args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
|
|
78039
|
-
return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
|
|
78040
|
-
}
|
|
78041
|
-
return {
|
|
78042
|
-
supersede: true,
|
|
78043
|
-
deleteMessageIds: [...record.messageIds],
|
|
78044
|
-
reason: "supersede",
|
|
78045
|
-
recordText: record.text
|
|
78046
|
-
};
|
|
78047
|
-
}
|
|
78048
|
-
function decideSupersedeCorrection(input) {
|
|
78049
|
-
const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
|
|
78050
|
-
if (eligible) {
|
|
78051
|
-
return { mode: "edit-in-place", editMessageId: input.flushMessageIds[0], deleteMessageIds: [] };
|
|
78052
|
-
}
|
|
78053
|
-
return { mode: "delete-resend", deleteMessageIds: [...input.flushMessageIds] };
|
|
78054
|
-
}
|
|
78055
|
-
var NULL_TURN_KEY2 = "<<null-turn>>";
|
|
78056
|
-
function turnKey2(turnId) {
|
|
78057
|
-
return turnId == null ? NULL_TURN_KEY2 : turnId;
|
|
78358
|
+
return known.decoupledCompletion;
|
|
78058
78359
|
}
|
|
78360
|
+
var HANDBACK_RECENCY_WINDOW_MS = 60000;
|
|
78361
|
+
var MAIN_THREAD_KEY = "<main>";
|
|
78059
78362
|
|
|
78060
|
-
class
|
|
78061
|
-
|
|
78062
|
-
|
|
78063
|
-
|
|
78064
|
-
this.ttlMs = opts.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
|
|
78065
|
-
}
|
|
78066
|
-
record(chatId, threadId, rec, now) {
|
|
78067
|
-
if (rec.messageIds.length === 0)
|
|
78068
|
-
return;
|
|
78069
|
-
this.sweep(now);
|
|
78070
|
-
const lane = makeKey3(chatId, threadId);
|
|
78071
|
-
let laneMap = this.entries.get(lane);
|
|
78072
|
-
if (laneMap == null) {
|
|
78073
|
-
laneMap = new Map;
|
|
78074
|
-
this.entries.set(lane, laneMap);
|
|
78075
|
-
}
|
|
78076
|
-
laneMap.set(turnKey2(rec.turnId), {
|
|
78077
|
-
turnId: rec.turnId,
|
|
78078
|
-
messageIds: [...rec.messageIds],
|
|
78079
|
-
text: rec.text,
|
|
78080
|
-
ts: now
|
|
78081
|
-
});
|
|
78082
|
-
}
|
|
78083
|
-
peek(chatId, threadId, args) {
|
|
78084
|
-
const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
|
|
78085
|
-
return decideSupersede2(rec, {
|
|
78086
|
-
liveTurnId: args.liveTurnId,
|
|
78087
|
-
replyText: args.replyText,
|
|
78088
|
-
positiveAttribution: args.positiveAttribution,
|
|
78089
|
-
now: args.now,
|
|
78090
|
-
ttlMs: this.ttlMs
|
|
78091
|
-
});
|
|
78363
|
+
class SubagentHandbackMarker {
|
|
78364
|
+
byChat = new Map;
|
|
78365
|
+
threadKey(threadId) {
|
|
78366
|
+
return threadId == null ? MAIN_THREAD_KEY : String(threadId);
|
|
78092
78367
|
}
|
|
78093
|
-
|
|
78094
|
-
|
|
78095
|
-
|
|
78096
|
-
|
|
78097
|
-
|
|
78098
|
-
laneMap?.delete(turnKey2(args.liveTurnId));
|
|
78099
|
-
if (laneMap != null && laneMap.size === 0)
|
|
78100
|
-
this.entries.delete(lane);
|
|
78368
|
+
record(chatId, threadId, now) {
|
|
78369
|
+
let inner = this.byChat.get(chatId);
|
|
78370
|
+
if (inner == null) {
|
|
78371
|
+
inner = new Map;
|
|
78372
|
+
this.byChat.set(chatId, inner);
|
|
78101
78373
|
}
|
|
78102
|
-
|
|
78103
|
-
}
|
|
78104
|
-
forget(chatId, threadId) {
|
|
78105
|
-
this.entries.delete(makeKey3(chatId, threadId));
|
|
78106
|
-
}
|
|
78107
|
-
clear() {
|
|
78108
|
-
this.entries.clear();
|
|
78374
|
+
inner.set(this.threadKey(threadId), now);
|
|
78109
78375
|
}
|
|
78110
|
-
|
|
78111
|
-
|
|
78112
|
-
for (const [tk, rec] of laneMap) {
|
|
78113
|
-
if (now - rec.ts > this.ttlMs)
|
|
78114
|
-
laneMap.delete(tk);
|
|
78115
|
-
}
|
|
78116
|
-
if (laneMap.size === 0)
|
|
78117
|
-
this.entries.delete(lane);
|
|
78118
|
-
}
|
|
78376
|
+
lastAt(chatId, threadId) {
|
|
78377
|
+
return this.byChat.get(chatId)?.get(this.threadKey(threadId)) ?? null;
|
|
78119
78378
|
}
|
|
78120
|
-
|
|
78121
|
-
this.
|
|
78122
|
-
|
|
78123
|
-
|
|
78124
|
-
|
|
78125
|
-
|
|
78379
|
+
lastAtInChat(chatId) {
|
|
78380
|
+
const inner = this.byChat.get(chatId);
|
|
78381
|
+
if (inner == null || inner.size === 0)
|
|
78382
|
+
return null;
|
|
78383
|
+
let max = -Infinity;
|
|
78384
|
+
for (const ts of inner.values())
|
|
78385
|
+
if (ts > max)
|
|
78386
|
+
max = ts;
|
|
78387
|
+
return max === -Infinity ? null : max;
|
|
78126
78388
|
}
|
|
78127
78389
|
}
|
|
78128
|
-
function makeKey3(chatId, threadId) {
|
|
78129
|
-
return threadId == null ? chatId : `${chatId}|${threadId}`;
|
|
78130
|
-
}
|
|
78131
|
-
|
|
78132
|
-
// reply-owner-resolve.ts
|
|
78133
|
-
function latestEndedAccepted(candidates) {
|
|
78134
|
-
if (candidates.latestEndedTurnId == null)
|
|
78135
|
-
return false;
|
|
78136
|
-
const age = candidates.latestEndedAgeMs;
|
|
78137
|
-
const ttl = candidates.latestEndedTtlMs;
|
|
78138
|
-
if (age === null)
|
|
78139
|
-
return false;
|
|
78140
|
-
if (age === undefined || ttl == null)
|
|
78141
|
-
return true;
|
|
78142
|
-
return age <= ttl;
|
|
78143
|
-
}
|
|
78144
|
-
function decideContentGateBypass(input) {
|
|
78145
|
-
if (input.tier === "live")
|
|
78146
|
-
return true;
|
|
78147
|
-
if (input.tier === "none")
|
|
78148
|
-
return false;
|
|
78149
|
-
if (input.handbackCouldOwnReply)
|
|
78150
|
-
return false;
|
|
78151
|
-
if (input.candidates == null)
|
|
78152
|
-
return false;
|
|
78153
|
-
if (!latestEndedAccepted(input.candidates))
|
|
78154
|
-
return false;
|
|
78155
|
-
return input.resolvedTurnId != null && input.resolvedTurnId === input.candidates.latestEndedTurnId;
|
|
78156
|
-
}
|
|
78157
|
-
function decideAnswerLatchSuppression(input) {
|
|
78158
|
-
if (input.superseded)
|
|
78159
|
-
return false;
|
|
78160
|
-
if (!input.replySubstantive)
|
|
78161
|
-
return false;
|
|
78162
|
-
if (!input.isLateReply)
|
|
78163
|
-
return false;
|
|
78164
|
-
if (input.replyMatchesFlushedAnswer === false)
|
|
78165
|
-
return false;
|
|
78166
|
-
return input.ownerAnswerDelivered === "flush";
|
|
78167
|
-
}
|
|
78168
78390
|
|
|
78169
78391
|
// telegraph.ts
|
|
78170
78392
|
function deriveTelegraphTitle2(text4) {
|
|
@@ -78739,6 +78961,7 @@ async function sendReply(deps, req) {
|
|
|
78739
78961
|
resolveThreadId,
|
|
78740
78962
|
getLatestInboundMessageId: getLatestInboundMessageId2,
|
|
78741
78963
|
getLastSubagentHandbackAt,
|
|
78964
|
+
subagentReplyAuthority: subagentReplyAuthority2,
|
|
78742
78965
|
recordOutbound: recordOutbound2,
|
|
78743
78966
|
emissionAuthorityFor,
|
|
78744
78967
|
clearActivitySummary,
|
|
@@ -78813,7 +79036,10 @@ async function sendReply(deps, req) {
|
|
|
78813
79036
|
}
|
|
78814
79037
|
}
|
|
78815
79038
|
let supersedeFlushIds = [];
|
|
78816
|
-
{
|
|
79039
|
+
if (req.callerIsForeignSession === true) {
|
|
79040
|
+
process.stderr.write(`telegram gateway: reply: foreign-session caller \u2014 supersede/latch skipped (#4172) ` + `chatId=${chat_id}
|
|
79041
|
+
`);
|
|
79042
|
+
} else {
|
|
78817
79043
|
const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
78818
79044
|
const {
|
|
78819
79045
|
turn: ownerTurn,
|
|
@@ -78825,12 +79051,14 @@ async function sendReply(deps, req) {
|
|
|
78825
79051
|
const gateThreadId = ownerTurn?.sessionThreadId ?? replyThreadId;
|
|
78826
79052
|
const handbackAt = getLastSubagentHandbackAt(chat_id);
|
|
78827
79053
|
const now = Date.now();
|
|
78828
|
-
const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <=
|
|
79054
|
+
const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= HANDBACK_RECENCY_WINDOW_MS;
|
|
79055
|
+
const subagentCouldOwnReply = subagentReplyAuthority2.subagentCouldOwnReply();
|
|
78829
79056
|
const replyIsOwnAnswer = decideContentGateBypass({
|
|
78830
79057
|
tier: ownerTier,
|
|
78831
79058
|
resolvedTurnId,
|
|
78832
79059
|
candidates: ownerCandidates,
|
|
78833
|
-
handbackCouldOwnReply
|
|
79060
|
+
handbackCouldOwnReply,
|
|
79061
|
+
subagentCouldOwnReply
|
|
78834
79062
|
});
|
|
78835
79063
|
const decision = flushedTurnSupersede.take(chat_id, gateThreadId, { liveTurnId: resolvedTurnId, replyText: text4, positiveAttribution: replyIsOwnAnswer, now });
|
|
78836
79064
|
if (decision.supersede) {
|
|
@@ -78856,7 +79084,7 @@ async function sendReply(deps, req) {
|
|
|
78856
79084
|
replyMatchesFlushedAnswer
|
|
78857
79085
|
});
|
|
78858
79086
|
if (decision.reason === "new-content") {
|
|
78859
|
-
process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)} ` + `tier=${ownerTier} latestEnded=${JSON.stringify(ownerCandidates.latestEndedTurnId)} ` + `handbackInWindow=${handbackCouldOwnReply}; sending fresh
|
|
79087
|
+
process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)} ` + `tier=${ownerTier} latestEnded=${JSON.stringify(ownerCandidates.latestEndedTurnId)} ` + `handbackInWindow=${handbackCouldOwnReply} subagentLive=${subagentCouldOwnReply}; sending fresh
|
|
78860
79088
|
`);
|
|
78861
79089
|
}
|
|
78862
79090
|
if (suppressByLatch) {
|
|
@@ -80110,6 +80338,37 @@ function emitRuntimeMetric2(event) {
|
|
|
80110
80338
|
captureEvent(event.kind, { ...event, ts: wrapped.ts });
|
|
80111
80339
|
}
|
|
80112
80340
|
|
|
80341
|
+
// gateway/subagent-reply-authority.ts
|
|
80342
|
+
var SUB_AGENT_KIND_PREFIX2 = "sub_agent_";
|
|
80343
|
+
|
|
80344
|
+
class SubagentReplyAuthority2 {
|
|
80345
|
+
live = new Set;
|
|
80346
|
+
noteSessionEvent(ev) {
|
|
80347
|
+
if (ev == null)
|
|
80348
|
+
return;
|
|
80349
|
+
const kind = ev.kind;
|
|
80350
|
+
if (typeof kind !== "string" || !kind.startsWith(SUB_AGENT_KIND_PREFIX2))
|
|
80351
|
+
return;
|
|
80352
|
+
const agentId = ev.agentId;
|
|
80353
|
+
if (typeof agentId !== "string" || agentId === "")
|
|
80354
|
+
return;
|
|
80355
|
+
if (kind === `${SUB_AGENT_KIND_PREFIX2}turn_end`)
|
|
80356
|
+
this.live.delete(agentId);
|
|
80357
|
+
else
|
|
80358
|
+
this.live.add(agentId);
|
|
80359
|
+
}
|
|
80360
|
+
subagentCouldOwnReply() {
|
|
80361
|
+
return this.live.size > 0;
|
|
80362
|
+
}
|
|
80363
|
+
reset() {
|
|
80364
|
+
this.live.clear();
|
|
80365
|
+
}
|
|
80366
|
+
liveCount() {
|
|
80367
|
+
return this.live.size;
|
|
80368
|
+
}
|
|
80369
|
+
}
|
|
80370
|
+
var subagentReplyAuthority2 = new SubagentReplyAuthority2;
|
|
80371
|
+
|
|
80113
80372
|
// gateway/feed-open-gate.ts
|
|
80114
80373
|
function shouldEarlyOpenLiveness(input) {
|
|
80115
80374
|
if (!input.enabled)
|
|
@@ -80838,6 +81097,12 @@ function discardParkedTurnStart(rawContent) {
|
|
|
80838
81097
|
}
|
|
80839
81098
|
return null;
|
|
80840
81099
|
}
|
|
81100
|
+
var flushCompletionTracker = new FlushCompletionTracker;
|
|
81101
|
+
function closeFlushCompletionWindows(registry, now) {
|
|
81102
|
+
const closed = flushCompletionTracker.closeAll(now);
|
|
81103
|
+
registry?.completeAll?.(now);
|
|
81104
|
+
return closed;
|
|
81105
|
+
}
|
|
80841
81106
|
function drainParkedTurnStartsForChat(deps, chatId, threadId) {
|
|
80842
81107
|
const wantThread = envThreadIdNum(threadId);
|
|
80843
81108
|
const drained = [];
|
|
@@ -80970,6 +81235,7 @@ function beginTurn(deps, ev) {
|
|
|
80970
81235
|
answerDelivered: false,
|
|
80971
81236
|
flushedAnswerText: null,
|
|
80972
81237
|
endedAt: null,
|
|
81238
|
+
realEndObservedAt: null,
|
|
80973
81239
|
firstPingAt: null,
|
|
80974
81240
|
firstPingWasSubstantive: false,
|
|
80975
81241
|
silentAnchorMessageId: null,
|
|
@@ -81025,6 +81291,7 @@ function beginTurn(deps, ev) {
|
|
|
81025
81291
|
`);
|
|
81026
81292
|
}
|
|
81027
81293
|
startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
|
|
81294
|
+
closeFlushCompletionWindows(deps.flushedTurnSupersede, Date.now());
|
|
81028
81295
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
81029
81296
|
scheduleEarlyLivenessOpen(next);
|
|
81030
81297
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
@@ -81163,6 +81430,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
81163
81430
|
const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
|
|
81164
81431
|
idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
|
|
81165
81432
|
}
|
|
81433
|
+
subagentReplyAuthority2.noteSessionEvent(ev);
|
|
81166
81434
|
switch (ev.kind) {
|
|
81167
81435
|
case "enqueue": {
|
|
81168
81436
|
const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
|
|
@@ -81475,6 +81743,7 @@ function handleSessionEvent(deps, ev) {
|
|
|
81475
81743
|
return;
|
|
81476
81744
|
}
|
|
81477
81745
|
}
|
|
81746
|
+
closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
|
|
81478
81747
|
const turnEndBackstopTurn = getCurrentTurn();
|
|
81479
81748
|
const turnEndBackstopKey = turnEndBackstopTurn != null ? statusKey(turnEndBackstopTurn.sessionChatId, turnEndBackstopTurn.sessionThreadId) : null;
|
|
81480
81749
|
withTurnEndGateBackstop(turnEndBackstopKey, turnEndBackstopTurn, () => {
|
|
@@ -81648,6 +81917,9 @@ function handleSessionEvent(deps, ev) {
|
|
|
81648
81917
|
const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
|
|
81649
81918
|
const backstopCardTurnKey = cardTakeover.turnKey;
|
|
81650
81919
|
const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true, deferObligationClose: true });
|
|
81920
|
+
if (ev.durationMs === -1) {
|
|
81921
|
+
flushCompletionTracker.open(turn);
|
|
81922
|
+
}
|
|
81651
81923
|
preambleSuppressor.dropNow();
|
|
81652
81924
|
{
|
|
81653
81925
|
const tKey = statusKey(chatId, threadId);
|
|
@@ -81725,7 +81997,12 @@ function handleSessionEvent(deps, ev) {
|
|
|
81725
81997
|
turn.landedUnconfirmed = delivery.landedUnconfirmed;
|
|
81726
81998
|
outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), getCurrentTurn()?.registryKey ?? null);
|
|
81727
81999
|
if (sentIds.length > 0) {
|
|
81728
|
-
flushedTurnSupersede.record(backstopChatId, backstopThreadId, {
|
|
82000
|
+
flushedTurnSupersede.record(backstopChatId, backstopThreadId, {
|
|
82001
|
+
turnId: turn.turnId,
|
|
82002
|
+
messageIds: sentIds,
|
|
82003
|
+
text: capturedText,
|
|
82004
|
+
completedAt: turn.realEndObservedAt
|
|
82005
|
+
}, Date.now());
|
|
81729
82006
|
}
|
|
81730
82007
|
if (!delivered) {
|
|
81731
82008
|
if (backstopCtrl)
|
|
@@ -82698,51 +82975,13 @@ function isTurnFlushSafetyEnabled(env = process.env) {
|
|
|
82698
82975
|
return true;
|
|
82699
82976
|
}
|
|
82700
82977
|
|
|
82701
|
-
// reply-owner-resolve.ts
|
|
82702
|
-
function latestEndedAccepted2(candidates) {
|
|
82703
|
-
if (candidates.latestEndedTurnId == null)
|
|
82704
|
-
return false;
|
|
82705
|
-
const age = candidates.latestEndedAgeMs;
|
|
82706
|
-
const ttl = candidates.latestEndedTtlMs;
|
|
82707
|
-
if (age === null)
|
|
82708
|
-
return false;
|
|
82709
|
-
if (age === undefined || ttl == null)
|
|
82710
|
-
return true;
|
|
82711
|
-
return age <= ttl;
|
|
82712
|
-
}
|
|
82713
|
-
function resolveReplyOwnerTier(candidates) {
|
|
82714
|
-
if (candidates.liveTurnId != null)
|
|
82715
|
-
return "live";
|
|
82716
|
-
if (candidates.originTurnId != null)
|
|
82717
|
-
return "origin";
|
|
82718
|
-
if (candidates.quotedTurnId != null)
|
|
82719
|
-
return "quoted";
|
|
82720
|
-
if (latestEndedAccepted2(candidates))
|
|
82721
|
-
return "latest-ended";
|
|
82722
|
-
return "none";
|
|
82723
|
-
}
|
|
82724
|
-
function resolveReplyOwnerTurnId(candidates) {
|
|
82725
|
-
switch (resolveReplyOwnerTier(candidates)) {
|
|
82726
|
-
case "live":
|
|
82727
|
-
return candidates.liveTurnId;
|
|
82728
|
-
case "origin":
|
|
82729
|
-
return candidates.originTurnId;
|
|
82730
|
-
case "quoted":
|
|
82731
|
-
return candidates.quotedTurnId;
|
|
82732
|
-
case "latest-ended":
|
|
82733
|
-
return candidates.latestEndedTurnId;
|
|
82734
|
-
case "none":
|
|
82735
|
-
return null;
|
|
82736
|
-
}
|
|
82737
|
-
}
|
|
82738
|
-
|
|
82739
82978
|
// gateway/subagent-handback-marker.ts
|
|
82740
|
-
var
|
|
82979
|
+
var MAIN_THREAD_KEY2 = "<main>";
|
|
82741
82980
|
|
|
82742
|
-
class
|
|
82981
|
+
class SubagentHandbackMarker2 {
|
|
82743
82982
|
byChat = new Map;
|
|
82744
82983
|
threadKey(threadId) {
|
|
82745
|
-
return threadId == null ?
|
|
82984
|
+
return threadId == null ? MAIN_THREAD_KEY2 : String(threadId);
|
|
82746
82985
|
}
|
|
82747
82986
|
record(chatId, threadId, now) {
|
|
82748
82987
|
let inner = this.byChat.get(chatId);
|
|
@@ -91182,76 +91421,6 @@ function buildDiffPreviewCard(input) {
|
|
|
91182
91421
|
return { text: text4, reply_markup: kb };
|
|
91183
91422
|
}
|
|
91184
91423
|
|
|
91185
|
-
// gateway/subagent-handback-marker.ts
|
|
91186
|
-
var INBOUND_SOURCE_CLASSIFICATION = {
|
|
91187
|
-
subagent_handback: { decoupledCompletion: true },
|
|
91188
|
-
cron: { decoupledCompletion: false },
|
|
91189
|
-
reaction: { decoupledCompletion: false },
|
|
91190
|
-
subagent_progress: { decoupledCompletion: false },
|
|
91191
|
-
resume_interrupted: { decoupledCompletion: false },
|
|
91192
|
-
resume_deferred: { decoupledCompletion: false },
|
|
91193
|
-
resume_watchdog_timeout: { decoupledCompletion: false },
|
|
91194
|
-
vault_grant_approved: { decoupledCompletion: false },
|
|
91195
|
-
vault_grant_denied: { decoupledCompletion: false },
|
|
91196
|
-
vault_grant_timeout: { decoupledCompletion: false },
|
|
91197
|
-
vault_save_completed: { decoupledCompletion: false },
|
|
91198
|
-
vault_save_discarded: { decoupledCompletion: false },
|
|
91199
|
-
vault_save_failed: { decoupledCompletion: false },
|
|
91200
|
-
vault_save_timeout: { decoupledCompletion: false },
|
|
91201
|
-
secret_provided: { decoupledCompletion: false },
|
|
91202
|
-
secret_declined: { decoupledCompletion: false },
|
|
91203
|
-
secret_provide_failed: { decoupledCompletion: false },
|
|
91204
|
-
secret_request_timeout: { decoupledCompletion: false },
|
|
91205
|
-
mental_model_propose_timeout: { decoupledCompletion: false },
|
|
91206
|
-
bridge_dead_restart: { decoupledCompletion: false },
|
|
91207
|
-
obligation_represent: { decoupledCompletion: false },
|
|
91208
|
-
missed_approval_retry: { decoupledCompletion: false },
|
|
91209
|
-
skill_proposal_apply: { decoupledCompletion: false },
|
|
91210
|
-
warmup: { decoupledCompletion: false },
|
|
91211
|
-
mental_model_proposal_applied: { decoupledCompletion: false },
|
|
91212
|
-
mental_model_proposal_denied: { decoupledCompletion: false },
|
|
91213
|
-
mental_model_proposal_failed: { decoupledCompletion: false },
|
|
91214
|
-
webhook: { decoupledCompletion: false },
|
|
91215
|
-
linear: { decoupledCompletion: false }
|
|
91216
|
-
};
|
|
91217
|
-
function stampsHandbackMarker(source) {
|
|
91218
|
-
if (source == null)
|
|
91219
|
-
return false;
|
|
91220
|
-
const known = INBOUND_SOURCE_CLASSIFICATION[source];
|
|
91221
|
-
if (known == null)
|
|
91222
|
-
return true;
|
|
91223
|
-
return known.decoupledCompletion;
|
|
91224
|
-
}
|
|
91225
|
-
var MAIN_THREAD_KEY2 = "<main>";
|
|
91226
|
-
|
|
91227
|
-
class SubagentHandbackMarker2 {
|
|
91228
|
-
byChat = new Map;
|
|
91229
|
-
threadKey(threadId) {
|
|
91230
|
-
return threadId == null ? MAIN_THREAD_KEY2 : String(threadId);
|
|
91231
|
-
}
|
|
91232
|
-
record(chatId, threadId, now) {
|
|
91233
|
-
let inner = this.byChat.get(chatId);
|
|
91234
|
-
if (inner == null) {
|
|
91235
|
-
inner = new Map;
|
|
91236
|
-
this.byChat.set(chatId, inner);
|
|
91237
|
-
}
|
|
91238
|
-
inner.set(this.threadKey(threadId), now);
|
|
91239
|
-
}
|
|
91240
|
-
lastAt(chatId, threadId) {
|
|
91241
|
-
return this.byChat.get(chatId)?.get(this.threadKey(threadId)) ?? null;
|
|
91242
|
-
}
|
|
91243
|
-
lastAtInChat(chatId) {
|
|
91244
|
-
const inner = this.byChat.get(chatId);
|
|
91245
|
-
if (inner == null || inner.size === 0)
|
|
91246
|
-
return null;
|
|
91247
|
-
let max = -Infinity;
|
|
91248
|
-
for (const ts of inner.values())
|
|
91249
|
-
if (ts > max)
|
|
91250
|
-
max = ts;
|
|
91251
|
-
return max === -Infinity ? null : max;
|
|
91252
|
-
}
|
|
91253
|
-
}
|
|
91254
|
-
|
|
91255
91424
|
// gateway/pending-inbound-buffer.ts
|
|
91256
91425
|
var DEFAULT_PENDING_INBOUND_CAP = 32;
|
|
91257
91426
|
function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
|
|
@@ -91405,6 +91574,11 @@ function cronIdentity(agent) {
|
|
|
91405
91574
|
function isCronIdentity(name) {
|
|
91406
91575
|
return typeof name === "string" && name.endsWith(CRON_IDENTITY_SUFFIX);
|
|
91407
91576
|
}
|
|
91577
|
+
function replyCallerIsForeignSession(name) {
|
|
91578
|
+
if (name == null || name === "")
|
|
91579
|
+
return true;
|
|
91580
|
+
return isCronIdentity(name);
|
|
91581
|
+
}
|
|
91408
91582
|
function isCronInjectFire(meta) {
|
|
91409
91583
|
return meta?.source === "cron" || meta?.session === "cron";
|
|
91410
91584
|
}
|
|
@@ -93204,6 +93378,7 @@ function createTurnEndFunnel(deps) {
|
|
|
93204
93378
|
endCurrentTurnForKey(turn, key);
|
|
93205
93379
|
const turnEndedAt = Date.now();
|
|
93206
93380
|
turn.endedAt = turnEndedAt;
|
|
93381
|
+
turn.realEndObservedAt = turnEndedAt;
|
|
93207
93382
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
|
|
93208
93383
|
`);
|
|
93209
93384
|
if (opts?.deferRecord !== true) {
|
|
@@ -100866,10 +101041,10 @@ function startOutboxSweep(deps) {
|
|
|
100866
101041
|
}
|
|
100867
101042
|
|
|
100868
101043
|
// ../src/build-info.ts
|
|
100869
|
-
var VERSION2 = "0.19.
|
|
100870
|
-
var COMMIT_SHA = "
|
|
100871
|
-
var COMMIT_DATE = "2026-08-01T23:
|
|
100872
|
-
var LATEST_PR =
|
|
101044
|
+
var VERSION2 = "0.19.45";
|
|
101045
|
+
var COMMIT_SHA = "2ad33d5d";
|
|
101046
|
+
var COMMIT_DATE = "2026-08-01T23:47:32Z";
|
|
101047
|
+
var LATEST_PR = 4192;
|
|
100873
101048
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
100874
101049
|
|
|
100875
101050
|
// gateway/boot-version.ts
|
|
@@ -103412,7 +103587,10 @@ var deferredDoneReactions = new DeferredDoneReactions({
|
|
|
103412
103587
|
purge: (key) => purgeReactionTracking(key)
|
|
103413
103588
|
});
|
|
103414
103589
|
var outboundDedup = new OutboundDedupCache;
|
|
103415
|
-
var flushedTurnSupersede = new FlushedTurnSupersedeRegistry
|
|
103590
|
+
var flushedTurnSupersede = new FlushedTurnSupersedeRegistry({
|
|
103591
|
+
openCapMs: SUPERSEDE_OPEN_CAP_MS,
|
|
103592
|
+
completedGraceMs: SUPERSEDE_GRACE_MS
|
|
103593
|
+
});
|
|
103416
103594
|
var backstopDeliveryLedger = new BackstopDeliveryLedger2;
|
|
103417
103595
|
var BACKSTOP_DELIVERY_MAX_ATTEMPTS = (() => {
|
|
103418
103596
|
const raw = Number(process.env.SWITCHROOM_BACKSTOP_DELIVERY_MAX_ATTEMPTS);
|
|
@@ -103776,7 +103954,7 @@ function cardDrainGate(turn, ea, run4) {
|
|
|
103776
103954
|
var RECENT_TURNS_MAX = 32;
|
|
103777
103955
|
var recentTurnsById = new Map;
|
|
103778
103956
|
var recentTurnIdBySourceMessageId = new Map;
|
|
103779
|
-
var subagentHandbackMarker = new
|
|
103957
|
+
var subagentHandbackMarker = new SubagentHandbackMarker2;
|
|
103780
103958
|
var getLastSubagentHandbackAt = (chatId) => subagentHandbackMarker.lastAtInChat(chatId);
|
|
103781
103959
|
function rememberRecentTurn(turn) {
|
|
103782
103960
|
recentTurnsById.set(turn.turnId, turn);
|
|
@@ -103830,26 +104008,7 @@ function findLatestTurnForChat(chatId, opts) {
|
|
|
103830
104008
|
return latestTurnForChat(recentTurnsById.values(), chatId, opts);
|
|
103831
104009
|
}
|
|
103832
104010
|
function resolveReplyOwnerTurn(liveTurn, chatId, args) {
|
|
103833
|
-
|
|
103834
|
-
const quoted = findTurnByQuotedMessageId(chatId, args.reply_to);
|
|
103835
|
-
const latestEnded = findLatestTurnForChat(chatId, { endedOnly: true });
|
|
103836
|
-
const byId = new Map;
|
|
103837
|
-
for (const t of [latestEnded, quoted, origin, liveTurn]) {
|
|
103838
|
-
if (t != null)
|
|
103839
|
-
byId.set(t.turnId, t);
|
|
103840
|
-
}
|
|
103841
|
-
const latestEndedAgeMs = latestEnded?.endedAt != null ? Date.now() - latestEnded.endedAt : null;
|
|
103842
|
-
const candidates = {
|
|
103843
|
-
liveTurnId: liveTurn?.turnId ?? null,
|
|
103844
|
-
originTurnId: origin?.turnId ?? null,
|
|
103845
|
-
quotedTurnId: quoted?.turnId ?? null,
|
|
103846
|
-
latestEndedTurnId: latestEnded?.turnId ?? null,
|
|
103847
|
-
latestEndedAgeMs,
|
|
103848
|
-
latestEndedTtlMs: DEFAULT_SUPERSEDE_TTL_MS
|
|
103849
|
-
};
|
|
103850
|
-
const tier = resolveReplyOwnerTier(candidates);
|
|
103851
|
-
const winnerId = resolveReplyOwnerTurnId(candidates);
|
|
103852
|
-
return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
|
|
104011
|
+
return resolveReplyOwnerTurnWith({ findTurnByOriginId, findTurnByQuotedMessageId, findLatestTurnForChat, now: Date.now }, liveTurn, chatId, args);
|
|
103853
104012
|
}
|
|
103854
104013
|
function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
|
|
103855
104014
|
const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
|
|
@@ -106905,12 +107064,20 @@ if (isGatewayMain)
|
|
|
106905
107064
|
log: (msg) => process.stderr.write(`${msg}
|
|
106906
107065
|
`)
|
|
106907
107066
|
});
|
|
107067
|
+
if (client3.agentName != null && !isCronIdentity(client3.agentName)) {
|
|
107068
|
+
subagentReplyAuthority.reset();
|
|
107069
|
+
const closedWindows = closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
|
|
107070
|
+
if (closedWindows > 0) {
|
|
107071
|
+
process.stderr.write(`telegram gateway: bridge disconnect closed ${closedWindows} open flush-completion window(s) (#4173)
|
|
107072
|
+
`);
|
|
107073
|
+
}
|
|
107074
|
+
}
|
|
106908
107075
|
},
|
|
106909
107076
|
async onToolCall(client3, msg) {
|
|
106910
107077
|
process.stderr.write(`telegram gateway: ipc: tool_call tool=${msg.tool} agent=${client3.agentName ?? "-"} clientId=${client3.id ?? "-"} callId=${msg.id}
|
|
106911
107078
|
`);
|
|
106912
107079
|
try {
|
|
106913
|
-
const result = await executeToolCall(msg.tool, msg.args);
|
|
107080
|
+
const result = await executeToolCall(msg.tool, msg.args, client3.agentName);
|
|
106914
107081
|
return { type: "tool_call_result", id: msg.id, success: true, result };
|
|
106915
107082
|
} catch (err) {
|
|
106916
107083
|
return {
|
|
@@ -107588,13 +107755,13 @@ var ALLOWED_TOOLS = new Set([
|
|
|
107588
107755
|
"linear_create_issue",
|
|
107589
107756
|
"linear_agent_setup"
|
|
107590
107757
|
]);
|
|
107591
|
-
async function executeToolCall(tool, args) {
|
|
107758
|
+
async function executeToolCall(tool, args, callerAgentName = null) {
|
|
107592
107759
|
if (!ALLOWED_TOOLS.has(tool)) {
|
|
107593
107760
|
throw new Error(`tool not allowed: ${tool}`);
|
|
107594
107761
|
}
|
|
107595
107762
|
switch (tool) {
|
|
107596
107763
|
case "reply":
|
|
107597
|
-
return executeReply(args);
|
|
107764
|
+
return executeReply(args, callerAgentName);
|
|
107598
107765
|
case "progress_update":
|
|
107599
107766
|
return executeProgressUpdate(args);
|
|
107600
107767
|
case "react":
|
|
@@ -107875,8 +108042,12 @@ async function synthesizeVoiceOut(plan) {
|
|
|
107875
108042
|
return null;
|
|
107876
108043
|
}
|
|
107877
108044
|
}
|
|
107878
|
-
async function executeReply(args) {
|
|
107879
|
-
return sendReply(gatewaySendReplyDeps(), {
|
|
108045
|
+
async function executeReply(args, callerAgentName = null) {
|
|
108046
|
+
return sendReply(gatewaySendReplyDeps(), {
|
|
108047
|
+
args,
|
|
108048
|
+
turn: currentTurn,
|
|
108049
|
+
callerIsForeignSession: replyCallerIsForeignSession(callerAgentName)
|
|
108050
|
+
});
|
|
107880
108051
|
}
|
|
107881
108052
|
function gatewaySendReplyDeps() {
|
|
107882
108053
|
return {
|
|
@@ -107915,6 +108086,7 @@ function gatewaySendReplyDeps() {
|
|
|
107915
108086
|
resolveThreadId,
|
|
107916
108087
|
getLatestInboundMessageId,
|
|
107917
108088
|
getLastSubagentHandbackAt,
|
|
108089
|
+
subagentReplyAuthority,
|
|
107918
108090
|
recordOutbound,
|
|
107919
108091
|
emissionAuthorityFor,
|
|
107920
108092
|
clearActivitySummary,
|