switchroom 0.19.44 → 0.19.46

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.
@@ -41869,7 +41869,8 @@ function normalizeForDedup(text) {
41869
41869
  }
41870
41870
 
41871
41871
  // flushed-turn-supersede.ts
41872
- var DEFAULT_SUPERSEDE_TTL_MS = 60000;
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 ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
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
- if (args.now - record.ts > ttlMs) {
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
- ttlMs;
41921
+ openCapMs;
41922
+ completedGraceMs;
41919
41923
  constructor(opts = {}) {
41920
- this.ttlMs = opts.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
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
- ttlMs: this.ttlMs
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
- if (now - rec.ts > this.ttlMs)
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(turnKey2) {
53958
- return turnKey2.startsWith(PRETURN_TURNKEY_PREFIX);
54269
+ isPreTurnRecord(turnKey3) {
54270
+ return turnKey3.startsWith(PRETURN_TURNKEY_PREFIX);
53959
54271
  },
53960
- handleReaped(turnKey2) {
53961
- const statusKey = bySyntheticKey.get(turnKey2);
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(turnKey2, deps) {
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 !== turnKey2)
71609
+ if (prev.turnKey != null && prev.turnKey !== turnKey3)
71298
71610
  return;
71299
71611
  unlinkSync12(statePath);
71300
- emitLog(deps, `silent-end: cleared state file turnKey=${turnKey2}
71612
+ emitLog(deps, `silent-end: cleared state file turnKey=${turnKey3}
71301
71613
  `);
71302
71614
  } catch {}
71303
71615
  }
@@ -72469,7 +72781,7 @@ function parseProviderVerb(provider, rest) {
72469
72781
  return { kind: "provider-cancel", provider };
72470
72782
  }
72471
72783
  if (sub !== "add") {
72472
- const usage = provider === "google" ? "Usage: /auth google add <email> [--replace] [--write]" : "Usage: /auth microsoft add <email> [--replace] [--org-mode]";
72784
+ const usage = provider === "google" ? "Usage: /auth google add <email> [--replace] [--write] [--calendar]" : "Usage: /auth microsoft add <email> [--replace] [--org-mode]";
72473
72785
  return {
72474
72786
  kind: "help",
72475
72787
  reason: `Unknown \`${provider}\` subcommand: \`${codeSpanSafe(sub || "(none)")}\`. ${usage}`
@@ -72480,13 +72792,13 @@ function parseProviderVerb(provider, rest) {
72480
72792
  const positional = args.filter((a) => !a.startsWith("--"));
72481
72793
  const email = positional[0];
72482
72794
  if (!email) {
72483
- const usage = provider === "google" ? "Usage: /auth google add <email> [--replace] [--write]" : "Usage: /auth microsoft add <email> [--replace] [--org-mode]";
72795
+ const usage = provider === "google" ? "Usage: /auth google add <email> [--replace] [--write] [--calendar]" : "Usage: /auth microsoft add <email> [--replace] [--org-mode]";
72484
72796
  return { kind: "help", reason: usage };
72485
72797
  }
72486
72798
  const emailErr = validateAuthAddLabel(email);
72487
72799
  if (emailErr)
72488
72800
  return { kind: "help", reason: emailErr };
72489
- const allowed = provider === "google" ? new Set(["--replace", "--write"]) : new Set(["--replace", "--org-mode"]);
72801
+ const allowed = provider === "google" ? new Set(["--replace", "--write", "--calendar"]) : new Set(["--replace", "--org-mode"]);
72490
72802
  for (const f of flags) {
72491
72803
  if (!allowed.has(f)) {
72492
72804
  return {
@@ -72501,6 +72813,7 @@ function parseProviderVerb(provider, rest) {
72501
72813
  email,
72502
72814
  replace: flags.has("--replace"),
72503
72815
  write: provider === "google" && flags.has("--write"),
72816
+ calendar: provider === "google" && flags.has("--calendar"),
72504
72817
  orgMode: provider === "microsoft" && flags.has("--org-mode")
72505
72818
  };
72506
72819
  }
@@ -74208,16 +74521,16 @@ ${loginUrl}
74208
74521
 
74209
74522
  // gateway/auth-loopback-relay.ts
74210
74523
  import { spawn } from "node:child_process";
74211
- function defaultSpawnRelay(provider, email, opts) {
74212
- const binary = opts.binary ?? "switchroom";
74213
- const args = provider === "google" ? [
74524
+ function buildRelayArgs(provider, email, opts) {
74525
+ return provider === "google" ? [
74214
74526
  "auth",
74215
74527
  "google",
74216
74528
  "account",
74217
74529
  "add",
74218
74530
  email,
74219
74531
  ...opts.replace ? ["--replace"] : [],
74220
- ...opts.write ? ["--write"] : []
74532
+ ...opts.write ? ["--write"] : [],
74533
+ ...opts.calendar ? ["--calendar"] : []
74221
74534
  ] : [
74222
74535
  "auth",
74223
74536
  "microsoft",
@@ -74227,6 +74540,10 @@ function defaultSpawnRelay(provider, email, opts) {
74227
74540
  ...opts.replace ? ["--replace"] : [],
74228
74541
  ...opts.orgMode ? ["--org-mode"] : []
74229
74542
  ];
74543
+ }
74544
+ function defaultSpawnRelay(provider, email, opts) {
74545
+ const binary = opts.binary ?? "switchroom";
74546
+ const args = buildRelayArgs(provider, email, opts);
74230
74547
  const child = spawn(binary, args, {
74231
74548
  stdio: ["ignore", "pipe", "pipe"],
74232
74549
  env: {
@@ -75372,12 +75689,12 @@ class PendingUserNoticeGate {
75372
75689
  this.pending = this.pending.filter((p) => !(p.agent === notice.agent && p.key === notice.key));
75373
75690
  this.pending.push(notice);
75374
75691
  }
75375
- resolveTurnEnd(turnKey2, turnDeliveredReply, now = Date.now()) {
75692
+ resolveTurnEnd(turnKey3, turnDeliveredReply, now = Date.now()) {
75376
75693
  this.prune(now);
75377
75694
  const resolved = [];
75378
75695
  const remaining = [];
75379
75696
  for (const p of this.pending) {
75380
- if (p.key === turnKey2 || p.key === undefined)
75697
+ if (p.key === turnKey3 || p.key === undefined)
75381
75698
  resolved.push(p);
75382
75699
  else
75383
75700
  remaining.push(p);
@@ -78005,166 +78322,76 @@ ${input.newReplyText}`;
78005
78322
  };
78006
78323
  }
78007
78324
 
78008
- // flushed-turn-supersede.ts
78009
- var DEFAULT_SUPERSEDE_TTL_MS2 = 60000;
78010
- var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
78011
- function normalizeForMatch2(text4) {
78012
- return text4.replace(/\s+/g, " ").trim();
78013
- }
78014
- function flushedAnswerMatchesReply2(flushedText, replyText) {
78015
- const flushed = normalizeForMatch2(flushedText);
78016
- const reply = normalizeForMatch2(replyText);
78017
- if (flushed.length === 0 || reply.length === 0)
78325
+ // gateway/subagent-handback-marker.ts
78326
+ var INBOUND_SOURCE_CLASSIFICATION = {
78327
+ subagent_handback: { decoupledCompletion: true },
78328
+ cron: { decoupledCompletion: false },
78329
+ reaction: { decoupledCompletion: false },
78330
+ subagent_progress: { decoupledCompletion: false },
78331
+ resume_interrupted: { decoupledCompletion: false },
78332
+ resume_deferred: { decoupledCompletion: false },
78333
+ resume_watchdog_timeout: { decoupledCompletion: false },
78334
+ vault_grant_approved: { decoupledCompletion: false },
78335
+ vault_grant_denied: { decoupledCompletion: false },
78336
+ vault_grant_timeout: { decoupledCompletion: false },
78337
+ vault_save_completed: { decoupledCompletion: false },
78338
+ vault_save_discarded: { decoupledCompletion: false },
78339
+ vault_save_failed: { decoupledCompletion: false },
78340
+ vault_save_timeout: { decoupledCompletion: false },
78341
+ secret_provided: { decoupledCompletion: false },
78342
+ secret_declined: { decoupledCompletion: false },
78343
+ secret_provide_failed: { decoupledCompletion: false },
78344
+ secret_request_timeout: { decoupledCompletion: false },
78345
+ mental_model_propose_timeout: { decoupledCompletion: false },
78346
+ bridge_dead_restart: { decoupledCompletion: false },
78347
+ obligation_represent: { decoupledCompletion: false },
78348
+ missed_approval_retry: { decoupledCompletion: false },
78349
+ skill_proposal_apply: { decoupledCompletion: false },
78350
+ warmup: { decoupledCompletion: false },
78351
+ mental_model_proposal_applied: { decoupledCompletion: false },
78352
+ mental_model_proposal_denied: { decoupledCompletion: false },
78353
+ mental_model_proposal_failed: { decoupledCompletion: false },
78354
+ webhook: { decoupledCompletion: false },
78355
+ linear: { decoupledCompletion: false }
78356
+ };
78357
+ function stampsHandbackMarker(source) {
78358
+ if (source == null)
78018
78359
  return false;
78019
- if (flushed === reply)
78020
- return true;
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))
78360
+ const known = INBOUND_SOURCE_CLASSIFICATION[source];
78361
+ if (known == null)
78024
78362
  return true;
78025
- return false;
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;
78363
+ return known.decoupledCompletion;
78058
78364
  }
78365
+ var HANDBACK_RECENCY_WINDOW_MS = 60000;
78366
+ var MAIN_THREAD_KEY = "<main>";
78059
78367
 
78060
- class FlushedTurnSupersedeRegistry2 {
78061
- entries = new Map;
78062
- ttlMs;
78063
- constructor(opts = {}) {
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
- });
78368
+ class SubagentHandbackMarker {
78369
+ byChat = new Map;
78370
+ threadKey(threadId) {
78371
+ return threadId == null ? MAIN_THREAD_KEY : String(threadId);
78092
78372
  }
78093
- take(chatId, threadId, args) {
78094
- const lane = makeKey3(chatId, threadId);
78095
- const decision = this.peek(chatId, threadId, args);
78096
- if (decision.supersede) {
78097
- const laneMap = this.entries.get(lane);
78098
- laneMap?.delete(turnKey2(args.liveTurnId));
78099
- if (laneMap != null && laneMap.size === 0)
78100
- this.entries.delete(lane);
78373
+ record(chatId, threadId, now) {
78374
+ let inner = this.byChat.get(chatId);
78375
+ if (inner == null) {
78376
+ inner = new Map;
78377
+ this.byChat.set(chatId, inner);
78101
78378
  }
78102
- return decision;
78103
- }
78104
- forget(chatId, threadId) {
78105
- this.entries.delete(makeKey3(chatId, threadId));
78106
- }
78107
- clear() {
78108
- this.entries.clear();
78379
+ inner.set(this.threadKey(threadId), now);
78109
78380
  }
78110
- sweep(now) {
78111
- for (const [lane, laneMap] of this.entries) {
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
- }
78381
+ lastAt(chatId, threadId) {
78382
+ return this.byChat.get(chatId)?.get(this.threadKey(threadId)) ?? null;
78119
78383
  }
78120
- size(now) {
78121
- this.sweep(now);
78122
- let total = 0;
78123
- for (const laneMap of this.entries.values())
78124
- total += laneMap.size;
78125
- return total;
78384
+ lastAtInChat(chatId) {
78385
+ const inner = this.byChat.get(chatId);
78386
+ if (inner == null || inner.size === 0)
78387
+ return null;
78388
+ let max = -Infinity;
78389
+ for (const ts of inner.values())
78390
+ if (ts > max)
78391
+ max = ts;
78392
+ return max === -Infinity ? null : max;
78126
78393
  }
78127
78394
  }
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
78395
 
78169
78396
  // telegraph.ts
78170
78397
  function deriveTelegraphTitle2(text4) {
@@ -78739,6 +78966,7 @@ async function sendReply(deps, req) {
78739
78966
  resolveThreadId,
78740
78967
  getLatestInboundMessageId: getLatestInboundMessageId2,
78741
78968
  getLastSubagentHandbackAt,
78969
+ subagentReplyAuthority: subagentReplyAuthority2,
78742
78970
  recordOutbound: recordOutbound2,
78743
78971
  emissionAuthorityFor,
78744
78972
  clearActivitySummary,
@@ -78813,7 +79041,10 @@ async function sendReply(deps, req) {
78813
79041
  }
78814
79042
  }
78815
79043
  let supersedeFlushIds = [];
78816
- {
79044
+ if (req.callerIsForeignSession === true) {
79045
+ process.stderr.write(`telegram gateway: reply: foreign-session caller \u2014 supersede/latch skipped (#4172) ` + `chatId=${chat_id}
79046
+ `);
79047
+ } else {
78817
79048
  const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
78818
79049
  const {
78819
79050
  turn: ownerTurn,
@@ -78825,12 +79056,14 @@ async function sendReply(deps, req) {
78825
79056
  const gateThreadId = ownerTurn?.sessionThreadId ?? replyThreadId;
78826
79057
  const handbackAt = getLastSubagentHandbackAt(chat_id);
78827
79058
  const now = Date.now();
78828
- const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= DEFAULT_SUPERSEDE_TTL_MS2;
79059
+ const handbackCouldOwnReply = handbackAt != null && ownerEndedAt != null && handbackAt > ownerEndedAt && now - handbackAt <= HANDBACK_RECENCY_WINDOW_MS;
79060
+ const subagentCouldOwnReply = subagentReplyAuthority2.subagentCouldOwnReply();
78829
79061
  const replyIsOwnAnswer = decideContentGateBypass({
78830
79062
  tier: ownerTier,
78831
79063
  resolvedTurnId,
78832
79064
  candidates: ownerCandidates,
78833
- handbackCouldOwnReply
79065
+ handbackCouldOwnReply,
79066
+ subagentCouldOwnReply
78834
79067
  });
78835
79068
  const decision = flushedTurnSupersede.take(chat_id, gateThreadId, { liveTurnId: resolvedTurnId, replyText: text4, positiveAttribution: replyIsOwnAnswer, now });
78836
79069
  if (decision.supersede) {
@@ -78856,7 +79089,7 @@ async function sendReply(deps, req) {
78856
79089
  replyMatchesFlushedAnswer
78857
79090
  });
78858
79091
  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
79092
+ 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
79093
  `);
78861
79094
  }
78862
79095
  if (suppressByLatch) {
@@ -80110,6 +80343,37 @@ function emitRuntimeMetric2(event) {
80110
80343
  captureEvent(event.kind, { ...event, ts: wrapped.ts });
80111
80344
  }
80112
80345
 
80346
+ // gateway/subagent-reply-authority.ts
80347
+ var SUB_AGENT_KIND_PREFIX2 = "sub_agent_";
80348
+
80349
+ class SubagentReplyAuthority2 {
80350
+ live = new Set;
80351
+ noteSessionEvent(ev) {
80352
+ if (ev == null)
80353
+ return;
80354
+ const kind = ev.kind;
80355
+ if (typeof kind !== "string" || !kind.startsWith(SUB_AGENT_KIND_PREFIX2))
80356
+ return;
80357
+ const agentId = ev.agentId;
80358
+ if (typeof agentId !== "string" || agentId === "")
80359
+ return;
80360
+ if (kind === `${SUB_AGENT_KIND_PREFIX2}turn_end`)
80361
+ this.live.delete(agentId);
80362
+ else
80363
+ this.live.add(agentId);
80364
+ }
80365
+ subagentCouldOwnReply() {
80366
+ return this.live.size > 0;
80367
+ }
80368
+ reset() {
80369
+ this.live.clear();
80370
+ }
80371
+ liveCount() {
80372
+ return this.live.size;
80373
+ }
80374
+ }
80375
+ var subagentReplyAuthority2 = new SubagentReplyAuthority2;
80376
+
80113
80377
  // gateway/feed-open-gate.ts
80114
80378
  function shouldEarlyOpenLiveness(input) {
80115
80379
  if (!input.enabled)
@@ -80838,6 +81102,12 @@ function discardParkedTurnStart(rawContent) {
80838
81102
  }
80839
81103
  return null;
80840
81104
  }
81105
+ var flushCompletionTracker = new FlushCompletionTracker;
81106
+ function closeFlushCompletionWindows(registry, now) {
81107
+ const closed = flushCompletionTracker.closeAll(now);
81108
+ registry?.completeAll?.(now);
81109
+ return closed;
81110
+ }
80841
81111
  function drainParkedTurnStartsForChat(deps, chatId, threadId) {
80842
81112
  const wantThread = envThreadIdNum(threadId);
80843
81113
  const drained = [];
@@ -80970,6 +81240,7 @@ function beginTurn(deps, ev) {
80970
81240
  answerDelivered: false,
80971
81241
  flushedAnswerText: null,
80972
81242
  endedAt: null,
81243
+ realEndObservedAt: null,
80973
81244
  firstPingAt: null,
80974
81245
  firstPingWasSubstantive: false,
80975
81246
  silentAnchorMessageId: null,
@@ -81025,6 +81296,7 @@ function beginTurn(deps, ev) {
81025
81296
  `);
81026
81297
  }
81027
81298
  startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
81299
+ closeFlushCompletionWindows(deps.flushedTurnSupersede, Date.now());
81028
81300
  setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
81029
81301
  scheduleEarlyLivenessOpen(next);
81030
81302
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
@@ -81163,6 +81435,7 @@ function handleSessionEvent(deps, ev) {
81163
81435
  const durationMs = ev.kind === "turn_end" ? ev.durationMs : undefined;
81164
81436
  idleTracker.noteEvent(ev.kind, Date.now(), durationMs);
81165
81437
  }
81438
+ subagentReplyAuthority2.noteSessionEvent(ev);
81166
81439
  switch (ev.kind) {
81167
81440
  case "enqueue": {
81168
81441
  const enqThreadIdNum = ev.threadId != null ? Number(ev.threadId) : undefined;
@@ -81475,6 +81748,7 @@ function handleSessionEvent(deps, ev) {
81475
81748
  return;
81476
81749
  }
81477
81750
  }
81751
+ closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
81478
81752
  const turnEndBackstopTurn = getCurrentTurn();
81479
81753
  const turnEndBackstopKey = turnEndBackstopTurn != null ? statusKey(turnEndBackstopTurn.sessionChatId, turnEndBackstopTurn.sessionThreadId) : null;
81480
81754
  withTurnEndGateBackstop(turnEndBackstopKey, turnEndBackstopTurn, () => {
@@ -81648,6 +81922,9 @@ function handleSessionEvent(deps, ev) {
81648
81922
  const backstopCardMessageId = cardTakeover.wasEmitted && cardTakeover.turnKey != null ? getPinnedProgressCardMessageId?.(cardTakeover.turnKey) ?? null : null;
81649
81923
  const backstopCardTurnKey = cardTakeover.turnKey;
81650
81924
  const backstopTurnEndedAt = endCurrentTurnAtomic(turn, { deferRecord: true, deferObligationClose: true });
81925
+ if (ev.durationMs === -1) {
81926
+ flushCompletionTracker.open(turn);
81927
+ }
81651
81928
  preambleSuppressor.dropNow();
81652
81929
  {
81653
81930
  const tKey = statusKey(chatId, threadId);
@@ -81725,7 +82002,12 @@ function handleSessionEvent(deps, ev) {
81725
82002
  turn.landedUnconfirmed = delivery.landedUnconfirmed;
81726
82003
  outboundDedup.record(backstopChatId, backstopThreadId, capturedText, Date.now(), getCurrentTurn()?.registryKey ?? null);
81727
82004
  if (sentIds.length > 0) {
81728
- flushedTurnSupersede.record(backstopChatId, backstopThreadId, { turnId: turn.turnId, messageIds: sentIds, text: capturedText }, Date.now());
82005
+ flushedTurnSupersede.record(backstopChatId, backstopThreadId, {
82006
+ turnId: turn.turnId,
82007
+ messageIds: sentIds,
82008
+ text: capturedText,
82009
+ completedAt: turn.realEndObservedAt
82010
+ }, Date.now());
81729
82011
  }
81730
82012
  if (!delivered) {
81731
82013
  if (backstopCtrl)
@@ -82698,51 +82980,13 @@ function isTurnFlushSafetyEnabled(env = process.env) {
82698
82980
  return true;
82699
82981
  }
82700
82982
 
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
82983
  // gateway/subagent-handback-marker.ts
82740
- var MAIN_THREAD_KEY = "<main>";
82984
+ var MAIN_THREAD_KEY2 = "<main>";
82741
82985
 
82742
- class SubagentHandbackMarker {
82986
+ class SubagentHandbackMarker2 {
82743
82987
  byChat = new Map;
82744
82988
  threadKey(threadId) {
82745
- return threadId == null ? MAIN_THREAD_KEY : String(threadId);
82989
+ return threadId == null ? MAIN_THREAD_KEY2 : String(threadId);
82746
82990
  }
82747
82991
  record(chatId, threadId, now) {
82748
82992
  let inner = this.byChat.get(chatId);
@@ -91182,76 +91426,6 @@ function buildDiffPreviewCard(input) {
91182
91426
  return { text: text4, reply_markup: kb };
91183
91427
  }
91184
91428
 
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
91429
  // gateway/pending-inbound-buffer.ts
91256
91430
  var DEFAULT_PENDING_INBOUND_CAP = 32;
91257
91431
  function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
@@ -91405,6 +91579,11 @@ function cronIdentity(agent) {
91405
91579
  function isCronIdentity(name) {
91406
91580
  return typeof name === "string" && name.endsWith(CRON_IDENTITY_SUFFIX);
91407
91581
  }
91582
+ function replyCallerIsForeignSession(name) {
91583
+ if (name == null || name === "")
91584
+ return true;
91585
+ return isCronIdentity(name);
91586
+ }
91408
91587
  function isCronInjectFire(meta) {
91409
91588
  return meta?.source === "cron" || meta?.session === "cron";
91410
91589
  }
@@ -93204,6 +93383,7 @@ function createTurnEndFunnel(deps) {
93204
93383
  endCurrentTurnForKey(turn, key);
93205
93384
  const turnEndedAt = Date.now();
93206
93385
  turn.endedAt = turnEndedAt;
93386
+ turn.realEndObservedAt = turnEndedAt;
93207
93387
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "turn_end", turn, turnEndedAt)}
93208
93388
  `);
93209
93389
  if (opts?.deferRecord !== true) {
@@ -100866,10 +101046,10 @@ function startOutboxSweep(deps) {
100866
101046
  }
100867
101047
 
100868
101048
  // ../src/build-info.ts
100869
- var VERSION2 = "0.19.44";
100870
- var COMMIT_SHA = "82d3b0b2";
100871
- var COMMIT_DATE = "2026-08-01T23:02:10Z";
100872
- var LATEST_PR = 4186;
101049
+ var VERSION2 = "0.19.46";
101050
+ var COMMIT_SHA = "306c3ed1";
101051
+ var COMMIT_DATE = "2026-08-02T00:29:39Z";
101052
+ var LATEST_PR = 4194;
100873
101053
  var COMMITS_AHEAD_OF_TAG = 0;
100874
101054
 
100875
101055
  // gateway/boot-version.ts
@@ -103412,7 +103592,10 @@ var deferredDoneReactions = new DeferredDoneReactions({
103412
103592
  purge: (key) => purgeReactionTracking(key)
103413
103593
  });
103414
103594
  var outboundDedup = new OutboundDedupCache;
103415
- var flushedTurnSupersede = new FlushedTurnSupersedeRegistry;
103595
+ var flushedTurnSupersede = new FlushedTurnSupersedeRegistry({
103596
+ openCapMs: SUPERSEDE_OPEN_CAP_MS,
103597
+ completedGraceMs: SUPERSEDE_GRACE_MS
103598
+ });
103416
103599
  var backstopDeliveryLedger = new BackstopDeliveryLedger2;
103417
103600
  var BACKSTOP_DELIVERY_MAX_ATTEMPTS = (() => {
103418
103601
  const raw = Number(process.env.SWITCHROOM_BACKSTOP_DELIVERY_MAX_ATTEMPTS);
@@ -103776,7 +103959,7 @@ function cardDrainGate(turn, ea, run4) {
103776
103959
  var RECENT_TURNS_MAX = 32;
103777
103960
  var recentTurnsById = new Map;
103778
103961
  var recentTurnIdBySourceMessageId = new Map;
103779
- var subagentHandbackMarker = new SubagentHandbackMarker;
103962
+ var subagentHandbackMarker = new SubagentHandbackMarker2;
103780
103963
  var getLastSubagentHandbackAt = (chatId) => subagentHandbackMarker.lastAtInChat(chatId);
103781
103964
  function rememberRecentTurn(turn) {
103782
103965
  recentTurnsById.set(turn.turnId, turn);
@@ -103830,26 +104013,7 @@ function findLatestTurnForChat(chatId, opts) {
103830
104013
  return latestTurnForChat(recentTurnsById.values(), chatId, opts);
103831
104014
  }
103832
104015
  function resolveReplyOwnerTurn(liveTurn, chatId, args) {
103833
- const origin = findTurnByOriginId(args.origin_turn_id);
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 };
104016
+ return resolveReplyOwnerTurnWith({ findTurnByOriginId, findTurnByQuotedMessageId, findLatestTurnForChat, now: Date.now }, liveTurn, chatId, args);
103853
104017
  }
103854
104018
  function resolveAnswerThreadWithLog(chatId, explicitThreadId, originTurn, originVia, liveTurn, surface) {
103855
104019
  const recovered = LATE_REPLY_TOPIC_RECOVERY_ENABLED && explicitThreadId == null && originTurn == null && liveTurn == null ? findLatestTurnForChat(chatId, { endedOnly: false }) : null;
@@ -106905,12 +107069,20 @@ if (isGatewayMain)
106905
107069
  log: (msg) => process.stderr.write(`${msg}
106906
107070
  `)
106907
107071
  });
107072
+ if (client3.agentName != null && !isCronIdentity(client3.agentName)) {
107073
+ subagentReplyAuthority.reset();
107074
+ const closedWindows = closeFlushCompletionWindows(flushedTurnSupersede, Date.now());
107075
+ if (closedWindows > 0) {
107076
+ process.stderr.write(`telegram gateway: bridge disconnect closed ${closedWindows} open flush-completion window(s) (#4173)
107077
+ `);
107078
+ }
107079
+ }
106908
107080
  },
106909
107081
  async onToolCall(client3, msg) {
106910
107082
  process.stderr.write(`telegram gateway: ipc: tool_call tool=${msg.tool} agent=${client3.agentName ?? "-"} clientId=${client3.id ?? "-"} callId=${msg.id}
106911
107083
  `);
106912
107084
  try {
106913
- const result = await executeToolCall(msg.tool, msg.args);
107085
+ const result = await executeToolCall(msg.tool, msg.args, client3.agentName);
106914
107086
  return { type: "tool_call_result", id: msg.id, success: true, result };
106915
107087
  } catch (err) {
106916
107088
  return {
@@ -107588,13 +107760,13 @@ var ALLOWED_TOOLS = new Set([
107588
107760
  "linear_create_issue",
107589
107761
  "linear_agent_setup"
107590
107762
  ]);
107591
- async function executeToolCall(tool, args) {
107763
+ async function executeToolCall(tool, args, callerAgentName = null) {
107592
107764
  if (!ALLOWED_TOOLS.has(tool)) {
107593
107765
  throw new Error(`tool not allowed: ${tool}`);
107594
107766
  }
107595
107767
  switch (tool) {
107596
107768
  case "reply":
107597
- return executeReply(args);
107769
+ return executeReply(args, callerAgentName);
107598
107770
  case "progress_update":
107599
107771
  return executeProgressUpdate(args);
107600
107772
  case "react":
@@ -107875,8 +108047,12 @@ async function synthesizeVoiceOut(plan) {
107875
108047
  return null;
107876
108048
  }
107877
108049
  }
107878
- async function executeReply(args) {
107879
- return sendReply(gatewaySendReplyDeps(), { args, turn: currentTurn });
108050
+ async function executeReply(args, callerAgentName = null) {
108051
+ return sendReply(gatewaySendReplyDeps(), {
108052
+ args,
108053
+ turn: currentTurn,
108054
+ callerIsForeignSession: replyCallerIsForeignSession(callerAgentName)
108055
+ });
107880
108056
  }
107881
108057
  function gatewaySendReplyDeps() {
107882
108058
  return {
@@ -107915,6 +108091,7 @@ function gatewaySendReplyDeps() {
107915
108091
  resolveThreadId,
107916
108092
  getLatestInboundMessageId,
107917
108093
  getLastSubagentHandbackAt,
108094
+ subagentReplyAuthority,
107918
108095
  recordOutbound,
107919
108096
  emissionAuthorityFor,
107920
108097
  clearActivitySummary,
@@ -112355,7 +112532,7 @@ Set \`admin: true\` on this agent in switchroom.yaml to unlock.`, { html: true }
112355
112532
  return;
112356
112533
  }
112357
112534
  try {
112358
- const { consentUrl, state: state7, port, child } = await startLoopbackFlow(parsed.provider, parsed.email, { replace: parsed.replace, write: parsed.write, orgMode: parsed.orgMode });
112535
+ const { consentUrl, state: state7, port, child } = await startLoopbackFlow(parsed.provider, parsed.email, { replace: parsed.replace, write: parsed.write, calendar: parsed.calendar, orgMode: parsed.orgMode });
112359
112536
  const newFlow = {
112360
112537
  provider: parsed.provider,
112361
112538
  email: parsed.email,