switchroom 0.18.26 → 0.18.28
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/README.md +6 -2
- package/dist/cli/ms-365-write-pretool.mjs +4953 -14
- package/dist/cli/switchroom.js +1 -1
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/profiles/_base/start.sh.hbs +16 -0
- package/telegram-plugin/dist/gateway/gateway.js +571 -43
- package/telegram-plugin/flushed-turn-supersede.ts +58 -0
- package/telegram-plugin/gateway/derive-turn-id.ts +32 -0
- package/telegram-plugin/gateway/gateway.ts +358 -53
- package/telegram-plugin/gateway/handback-preturn-signal.ts +442 -0
- package/telegram-plugin/gateway/model-command.ts +68 -0
- package/telegram-plugin/gateway/ms365-write-approval.test.ts +101 -0
- package/telegram-plugin/gateway/ms365-write-approval.ts +65 -3
- package/telegram-plugin/gateway/subagent-handback-inbound-builder.ts +12 -0
- package/telegram-plugin/gateway/turn-active-marker.ts +35 -0
- package/telegram-plugin/gateway/worker-pin-reaper.ts +54 -0
- package/telegram-plugin/send-gate.test.ts +138 -0
- package/telegram-plugin/send-gate.ts +104 -1
- package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +14 -4
- package/telegram-plugin/tests/effort-command.test.ts +47 -0
- package/telegram-plugin/tests/flushed-turn-supersede.test.ts +60 -0
- package/telegram-plugin/tests/handback-preturn-adoption-roundtrip.test.ts +211 -0
- package/telegram-plugin/tests/handback-preturn-signal.test.ts +346 -0
- package/telegram-plugin/tests/model-command.test.ts +112 -0
- package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +14 -2
- package/telegram-plugin/tests/outbound-send-chunks.test.ts +57 -0
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +18 -11
- package/telegram-plugin/tests/reply-owner-resolve.test.ts +90 -0
- package/telegram-plugin/tests/subagent-handback-inbound-builder.test.ts +5 -0
- package/telegram-plugin/tests/turn-active-marker.test.ts +29 -0
- package/telegram-plugin/tests/worker-activity-feed.test.ts +121 -0
- package/telegram-plugin/tests/worker-feed-migration-eviction.test.ts +140 -0
- package/telegram-plugin/tests/worker-pin-reaper.test.ts +78 -0
- package/telegram-plugin/worker-activity-feed.ts +169 -6
|
@@ -39670,6 +39670,13 @@ function decideSupersede(record, args) {
|
|
|
39670
39670
|
}
|
|
39671
39671
|
return { supersede: true, deleteMessageIds: [...record.messageIds], reason: "supersede" };
|
|
39672
39672
|
}
|
|
39673
|
+
function decideSupersedeCorrection(input) {
|
|
39674
|
+
const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
|
|
39675
|
+
if (eligible) {
|
|
39676
|
+
return { mode: "edit-in-place", editMessageId: input.flushMessageIds[0], deleteMessageIds: [] };
|
|
39677
|
+
}
|
|
39678
|
+
return { mode: "delete-resend", deleteMessageIds: [...input.flushMessageIds] };
|
|
39679
|
+
}
|
|
39673
39680
|
var NULL_TURN_KEY = "<<null-turn>>";
|
|
39674
39681
|
function turnKey(turnId) {
|
|
39675
39682
|
return turnId == null ? NULL_TURN_KEY : turnId;
|
|
@@ -40962,6 +40969,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
40962
40969
|
const nowFn = opts.now ?? Date.now;
|
|
40963
40970
|
const floodWaitRemainingMs = opts.floodWaitRemainingMs ?? (() => 0);
|
|
40964
40971
|
const minEditInterval = opts.minEditIntervalMs ?? 2500;
|
|
40972
|
+
const elapsedRefreshMs = Math.max(minEditInterval, Math.floor(opts.elapsedRefreshMs ?? 15000));
|
|
40965
40973
|
const firstPaintMin = opts.firstPaintMinMs ?? 8000;
|
|
40966
40974
|
const heartbeatTickMs = opts.heartbeatTickMs ?? 6000;
|
|
40967
40975
|
const maxRows = Math.max(1, Math.floor(opts.maxRows ?? 8));
|
|
@@ -41076,11 +41084,46 @@ function createWorkerActivityFeed(opts) {
|
|
|
41076
41084
|
});
|
|
41077
41085
|
return renderCombinedWorkerFeed(rows, { maxRows });
|
|
41078
41086
|
}
|
|
41087
|
+
function groupSubstanceKey(g, terminalRecap) {
|
|
41088
|
+
const FS = "\x00";
|
|
41089
|
+
const RS = "\x1E";
|
|
41090
|
+
if (terminalRecap != null) {
|
|
41091
|
+
return [
|
|
41092
|
+
"T",
|
|
41093
|
+
terminalRecap.state,
|
|
41094
|
+
terminalRecap.description,
|
|
41095
|
+
terminalRecap.toolCount,
|
|
41096
|
+
terminalRecap.totalTokens ?? "",
|
|
41097
|
+
terminalRecap.latestSummary,
|
|
41098
|
+
...terminalRecap.narrativeLines ?? []
|
|
41099
|
+
].join(FS);
|
|
41100
|
+
}
|
|
41101
|
+
const running = runningRows(g);
|
|
41102
|
+
if (running.length === 0)
|
|
41103
|
+
return "EMPTY";
|
|
41104
|
+
return running.map((r) => {
|
|
41105
|
+
const v = r.lastView;
|
|
41106
|
+
return [
|
|
41107
|
+
r.agentId,
|
|
41108
|
+
v.state,
|
|
41109
|
+
v.description,
|
|
41110
|
+
v.toolCount,
|
|
41111
|
+
v.totalTokens ?? "",
|
|
41112
|
+
...r.narrative
|
|
41113
|
+
].join(FS);
|
|
41114
|
+
}).join(RS);
|
|
41115
|
+
}
|
|
41079
41116
|
function removeWorker(g, agentId) {
|
|
41080
41117
|
g.workers.delete(agentId);
|
|
41081
41118
|
agentIndex.delete(agentId);
|
|
41082
41119
|
maybeDeleteGroup(g);
|
|
41083
41120
|
}
|
|
41121
|
+
function evictRowFromGroup(g, agentId) {
|
|
41122
|
+
g.workers.delete(agentId);
|
|
41123
|
+
if (agentIndex.get(agentId) === g.feedKey)
|
|
41124
|
+
agentIndex.delete(agentId);
|
|
41125
|
+
maybeDeleteGroup(g);
|
|
41126
|
+
}
|
|
41084
41127
|
function maybeDeleteGroup(g) {
|
|
41085
41128
|
if (g.workers.size === 0 && g.pendingFinalize.size === 0)
|
|
41086
41129
|
groups.delete(g.feedKey);
|
|
@@ -41125,6 +41168,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41125
41168
|
return;
|
|
41126
41169
|
}
|
|
41127
41170
|
const body = renderGroupBody(g, now, opts2.terminalRecap ?? null, opts2.heartbeat ?? false);
|
|
41171
|
+
const substanceKey = groupSubstanceKey(g, opts2.terminalRecap ?? null);
|
|
41128
41172
|
if (body == null) {
|
|
41129
41173
|
if (isTerminal)
|
|
41130
41174
|
clearStaged();
|
|
@@ -41148,6 +41192,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41148
41192
|
g.messageId = sent.message_id;
|
|
41149
41193
|
g.messageCreatedAtMs = now;
|
|
41150
41194
|
g.lastBody = body;
|
|
41195
|
+
g.lastSubstanceKey = substanceKey;
|
|
41151
41196
|
g.lastEditAt = now;
|
|
41152
41197
|
g.terminalPainted = false;
|
|
41153
41198
|
syncPin(g);
|
|
@@ -41163,8 +41208,13 @@ function createWorkerActivityFeed(opts) {
|
|
|
41163
41208
|
clearStaged();
|
|
41164
41209
|
return;
|
|
41165
41210
|
}
|
|
41166
|
-
if (!opts2.force &&
|
|
41167
|
-
|
|
41211
|
+
if (!opts2.force && !isTerminal) {
|
|
41212
|
+
if (now - g.lastEditAt < minEditInterval)
|
|
41213
|
+
return;
|
|
41214
|
+
const substanceChanged = substanceKey !== g.lastSubstanceKey;
|
|
41215
|
+
if (!substanceChanged && now - g.lastEditAt < elapsedRefreshMs)
|
|
41216
|
+
return;
|
|
41217
|
+
}
|
|
41168
41218
|
try {
|
|
41169
41219
|
const res = await opts.bot.editMessageText(g.chatId, g.messageId, body, sendOptsFor(g));
|
|
41170
41220
|
if (isSendGateShed(res)) {
|
|
@@ -41172,6 +41222,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41172
41222
|
return;
|
|
41173
41223
|
}
|
|
41174
41224
|
g.lastBody = body;
|
|
41225
|
+
g.lastSubstanceKey = substanceKey;
|
|
41175
41226
|
g.lastEditAt = now;
|
|
41176
41227
|
if (isTerminal) {
|
|
41177
41228
|
log(`worker-feed: finish feed=${g.feedKey} chat=${g.chatId} thread=${g.threadId ?? "-"} ` + `msgId=${g.messageId} agent=${finishingAgentId ?? "-"} ` + `state=${opts2.terminalRecap?.state ?? "done"} bytes=${body.length}`);
|
|
@@ -41189,6 +41240,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41189
41240
|
}
|
|
41190
41241
|
if (outcome === "not_modified") {
|
|
41191
41242
|
g.lastBody = body;
|
|
41243
|
+
g.lastSubstanceKey = substanceKey;
|
|
41192
41244
|
g.lastEditAt = now;
|
|
41193
41245
|
if (isTerminal)
|
|
41194
41246
|
clearStaged();
|
|
@@ -41198,6 +41250,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41198
41250
|
g.messageId = null;
|
|
41199
41251
|
g.messageCreatedAtMs = 0;
|
|
41200
41252
|
g.lastBody = null;
|
|
41253
|
+
g.lastSubstanceKey = null;
|
|
41201
41254
|
if (isTerminal)
|
|
41202
41255
|
clearStaged();
|
|
41203
41256
|
else
|
|
@@ -41262,7 +41315,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41262
41315
|
if (row.finished)
|
|
41263
41316
|
staleFinished.push({ g, agentId: row.agentId, reason });
|
|
41264
41317
|
else
|
|
41265
|
-
staleAgentIds.push({ agentId: row.agentId, reason });
|
|
41318
|
+
staleAgentIds.push({ g, agentId: row.agentId, reason });
|
|
41266
41319
|
}
|
|
41267
41320
|
}
|
|
41268
41321
|
for (const { g, agentId, reason } of staleFinished) {
|
|
@@ -41276,15 +41329,22 @@ function createWorkerActivityFeed(opts) {
|
|
|
41276
41329
|
removeWorker(g, agentId);
|
|
41277
41330
|
syncPin(g);
|
|
41278
41331
|
}
|
|
41279
|
-
for (const { agentId, reason } of staleAgentIds) {
|
|
41280
|
-
const row =
|
|
41332
|
+
for (const { g, agentId, reason } of staleAgentIds) {
|
|
41333
|
+
const row = g.workers.get(agentId);
|
|
41281
41334
|
if (reason === "absolute") {
|
|
41282
41335
|
const age = Math.floor((now - (row?.createdAtMs ?? now)) / 1000);
|
|
41283
41336
|
log(`worker-feed: ABSOLUTE cap reap agent=${agentId} \u2014 row age ${age}s (>= ${Math.floor(absoluteRowLifetimeCapMs / 1000)}s); force-terminating immortal row (survives lastUpdateAt reset)`);
|
|
41284
41337
|
} else {
|
|
41285
41338
|
log(`worker-feed: TTL reap agent=${agentId} \u2014 no update in ${Math.floor((now - (row?.lastUpdateAt ?? now)) / 1000)}s (>= ${Math.floor(staleWorkerTtlMs / 1000)}s); force-terminating leaked row`);
|
|
41286
41339
|
}
|
|
41287
|
-
|
|
41340
|
+
if (groupOfAgent(agentId) === g) {
|
|
41341
|
+
terminateWorker(agentId);
|
|
41342
|
+
} else {
|
|
41343
|
+
log(`worker-feed: force-evicting leaked row agent=${agentId} feed=${g.feedKey} \u2014 agentIndex desynced (points elsewhere); removing directly`);
|
|
41344
|
+
markFinalized(agentId);
|
|
41345
|
+
evictRowFromGroup(g, agentId);
|
|
41346
|
+
syncPin(g);
|
|
41347
|
+
}
|
|
41288
41348
|
}
|
|
41289
41349
|
for (const g of [...groups.values()]) {
|
|
41290
41350
|
if (g.pendingFinalize.size > 0 && now >= g.cooldownUntil) {
|
|
@@ -41307,6 +41367,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41307
41367
|
g.messageId = null;
|
|
41308
41368
|
g.messageCreatedAtMs = 0;
|
|
41309
41369
|
g.lastBody = null;
|
|
41370
|
+
g.lastSubstanceKey = null;
|
|
41310
41371
|
syncPin(g);
|
|
41311
41372
|
opts.bot.editMessageText(g.chatId, retiredId, WORKER_CARD_SUPERSEDED_BODY, sendOptsFor(g)).catch(() => {});
|
|
41312
41373
|
}
|
|
@@ -41330,6 +41391,14 @@ function createWorkerActivityFeed(opts) {
|
|
|
41330
41391
|
}
|
|
41331
41392
|
}
|
|
41332
41393
|
heartbeatTimer = setIntervalFn(heartbeatTick, heartbeatTickMs);
|
|
41394
|
+
opts.exposeTestControls?.({
|
|
41395
|
+
repointAgentIndex: (agentId, feedKey) => {
|
|
41396
|
+
if (feedKey == null)
|
|
41397
|
+
agentIndex.delete(agentId);
|
|
41398
|
+
else
|
|
41399
|
+
agentIndex.set(agentId, feedKey);
|
|
41400
|
+
}
|
|
41401
|
+
});
|
|
41333
41402
|
return {
|
|
41334
41403
|
has(agentId) {
|
|
41335
41404
|
const g = groupOfAgent(agentId);
|
|
@@ -41357,6 +41426,14 @@ function createWorkerActivityFeed(opts) {
|
|
|
41357
41426
|
if (existingRow?.finished === true)
|
|
41358
41427
|
return Promise.resolve();
|
|
41359
41428
|
const feedKey = feedKeyOf(chatId, threadId);
|
|
41429
|
+
const priorFeedKey = agentIndex.get(agentId);
|
|
41430
|
+
if (priorFeedKey != null && priorFeedKey !== feedKey) {
|
|
41431
|
+
const priorGroup = groups.get(priorFeedKey);
|
|
41432
|
+
if (priorGroup != null) {
|
|
41433
|
+
evictRowFromGroup(priorGroup, agentId);
|
|
41434
|
+
syncPin(priorGroup);
|
|
41435
|
+
}
|
|
41436
|
+
}
|
|
41360
41437
|
let g = groups.get(feedKey);
|
|
41361
41438
|
if (g == null) {
|
|
41362
41439
|
g = {
|
|
@@ -41366,6 +41443,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41366
41443
|
messageId: null,
|
|
41367
41444
|
messageCreatedAtMs: 0,
|
|
41368
41445
|
lastBody: null,
|
|
41446
|
+
lastSubstanceKey: null,
|
|
41369
41447
|
lastEditAt: 0,
|
|
41370
41448
|
cooldownUntil: 0,
|
|
41371
41449
|
chain: Promise.resolve(),
|
|
@@ -41379,6 +41457,7 @@ function createWorkerActivityFeed(opts) {
|
|
|
41379
41457
|
g.messageId = null;
|
|
41380
41458
|
g.messageCreatedAtMs = 0;
|
|
41381
41459
|
g.lastBody = null;
|
|
41460
|
+
g.lastSubstanceKey = null;
|
|
41382
41461
|
g.pendingFinalize.clear();
|
|
41383
41462
|
g.terminalPainted = false;
|
|
41384
41463
|
syncPin(g);
|
|
@@ -46193,6 +46272,207 @@ function createTurnTypingLoop(deps) {
|
|
|
46193
46272
|
};
|
|
46194
46273
|
}
|
|
46195
46274
|
|
|
46275
|
+
// gateway/handback-preturn-signal.ts
|
|
46276
|
+
var PRETURN_TURNKEY_PREFIX = "preturn:";
|
|
46277
|
+
function isHandbackInbound(msg) {
|
|
46278
|
+
return msg.type === "inbound" && msg.meta?.source === "subagent_handback";
|
|
46279
|
+
}
|
|
46280
|
+
function createHandbackPreturnSignal(deps) {
|
|
46281
|
+
const now = deps.now ?? (() => Date.now());
|
|
46282
|
+
const debounceMs = deps.debounceMs ?? 700;
|
|
46283
|
+
const adoptTimeoutMs = deps.adoptTimeoutMs ?? 30000;
|
|
46284
|
+
const setTimer = deps.setTimer ?? ((fn, ms) => {
|
|
46285
|
+
const t = setTimeout(fn, ms);
|
|
46286
|
+
t.unref?.();
|
|
46287
|
+
return t;
|
|
46288
|
+
});
|
|
46289
|
+
const clearTimer = deps.clearTimer ?? ((h) => clearTimeout(h));
|
|
46290
|
+
const log = deps.log ?? ((l) => process.stderr.write(l));
|
|
46291
|
+
const byKey = new Map;
|
|
46292
|
+
const bySyntheticKey = new Map;
|
|
46293
|
+
function clearTimers(entry) {
|
|
46294
|
+
if (entry.debounceTimer != null) {
|
|
46295
|
+
clearTimer(entry.debounceTimer);
|
|
46296
|
+
entry.debounceTimer = null;
|
|
46297
|
+
}
|
|
46298
|
+
if (entry.reapTimer != null) {
|
|
46299
|
+
clearTimer(entry.reapTimer);
|
|
46300
|
+
entry.reapTimer = null;
|
|
46301
|
+
}
|
|
46302
|
+
}
|
|
46303
|
+
function dropEntry(entry) {
|
|
46304
|
+
clearTimers(entry);
|
|
46305
|
+
byKey.delete(entry.statusKey);
|
|
46306
|
+
bySyntheticKey.delete(entry.syntheticTurnKey);
|
|
46307
|
+
}
|
|
46308
|
+
function emit(entry) {
|
|
46309
|
+
entry.debounceTimer = null;
|
|
46310
|
+
if (entry.consumed)
|
|
46311
|
+
return;
|
|
46312
|
+
if (deps.isTurnSettled?.(entry.statusKey)) {
|
|
46313
|
+
dropEntry(entry);
|
|
46314
|
+
return;
|
|
46315
|
+
}
|
|
46316
|
+
deps.startTypingLoop(entry.chatId, entry.threadId);
|
|
46317
|
+
entry.emitted = true;
|
|
46318
|
+
entry.reapTimer = setTimer(() => reap(entry), adoptTimeoutMs);
|
|
46319
|
+
Promise.resolve().then(() => deps.openCard(entry.chatId, entry.threadId)).then((messageId) => {
|
|
46320
|
+
if (messageId == null)
|
|
46321
|
+
return;
|
|
46322
|
+
if (entry.consumed) {
|
|
46323
|
+
deps.finalizeCard({
|
|
46324
|
+
turnKey: entry.syntheticTurnKey,
|
|
46325
|
+
chatId: entry.chatId,
|
|
46326
|
+
threadId: entry.threadId,
|
|
46327
|
+
activityMessageId: messageId,
|
|
46328
|
+
startedAt: entry.startedAt,
|
|
46329
|
+
pinned: entry.pinned
|
|
46330
|
+
});
|
|
46331
|
+
return;
|
|
46332
|
+
}
|
|
46333
|
+
entry.activityMessageId = messageId;
|
|
46334
|
+
const record = {
|
|
46335
|
+
turnKey: entry.syntheticTurnKey,
|
|
46336
|
+
chatId: entry.chatId,
|
|
46337
|
+
threadId: entry.threadId,
|
|
46338
|
+
activityMessageId: messageId,
|
|
46339
|
+
startedAt: entry.startedAt,
|
|
46340
|
+
pinned: entry.pinned
|
|
46341
|
+
};
|
|
46342
|
+
deps.writeCardRecord(record);
|
|
46343
|
+
}).catch((err) => {
|
|
46344
|
+
log(`handback-preturn-signal: openCard failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
|
|
46345
|
+
`);
|
|
46346
|
+
});
|
|
46347
|
+
}
|
|
46348
|
+
function reap(entry) {
|
|
46349
|
+
entry.reapTimer = null;
|
|
46350
|
+
if (entry.consumed)
|
|
46351
|
+
return;
|
|
46352
|
+
entry.consumed = true;
|
|
46353
|
+
deps.stopTypingLoop(entry.chatId, entry.threadId);
|
|
46354
|
+
if (entry.activityMessageId != null) {
|
|
46355
|
+
const record = {
|
|
46356
|
+
turnKey: entry.syntheticTurnKey,
|
|
46357
|
+
chatId: entry.chatId,
|
|
46358
|
+
threadId: entry.threadId,
|
|
46359
|
+
activityMessageId: entry.activityMessageId,
|
|
46360
|
+
startedAt: entry.startedAt,
|
|
46361
|
+
pinned: entry.pinned
|
|
46362
|
+
};
|
|
46363
|
+
deps.clearCardRecord(entry.syntheticTurnKey, entry.activityMessageId);
|
|
46364
|
+
Promise.resolve(deps.finalizeCard(record)).catch((err) => {
|
|
46365
|
+
log(`handback-preturn-signal: orphan finalize failed key=${entry.statusKey}: ` + `${err instanceof Error ? err.message : String(err)}
|
|
46366
|
+
`);
|
|
46367
|
+
});
|
|
46368
|
+
}
|
|
46369
|
+
dropEntry(entry);
|
|
46370
|
+
}
|
|
46371
|
+
return {
|
|
46372
|
+
noteHandbackRelease(inbound) {
|
|
46373
|
+
if (!isHandbackInbound(inbound))
|
|
46374
|
+
return;
|
|
46375
|
+
const chatId = inbound.chatId;
|
|
46376
|
+
if (chatId == null || chatId === "")
|
|
46377
|
+
return;
|
|
46378
|
+
const threadId = inbound.threadId ?? null;
|
|
46379
|
+
const adoptTurnId = deps.deriveTurnId(chatId, threadId, inbound.messageId);
|
|
46380
|
+
if (adoptTurnId == null)
|
|
46381
|
+
return;
|
|
46382
|
+
const statusKey = deps.chatKey(chatId, threadId);
|
|
46383
|
+
if (byKey.has(statusKey))
|
|
46384
|
+
return;
|
|
46385
|
+
const startedAt = now();
|
|
46386
|
+
const syntheticTurnKey = `${PRETURN_TURNKEY_PREFIX}${statusKey}:${startedAt}`;
|
|
46387
|
+
const entry = {
|
|
46388
|
+
statusKey,
|
|
46389
|
+
chatId,
|
|
46390
|
+
threadId,
|
|
46391
|
+
adoptTurnId,
|
|
46392
|
+
syntheticTurnKey,
|
|
46393
|
+
startedAt,
|
|
46394
|
+
pinned: false,
|
|
46395
|
+
debounceTimer: null,
|
|
46396
|
+
reapTimer: null,
|
|
46397
|
+
activityMessageId: null,
|
|
46398
|
+
emitted: false,
|
|
46399
|
+
consumed: false
|
|
46400
|
+
};
|
|
46401
|
+
byKey.set(statusKey, entry);
|
|
46402
|
+
bySyntheticKey.set(syntheticTurnKey, statusKey);
|
|
46403
|
+
entry.debounceTimer = setTimer(() => emit(entry), debounceMs);
|
|
46404
|
+
},
|
|
46405
|
+
tryAdopt(turnId) {
|
|
46406
|
+
let entry;
|
|
46407
|
+
for (const e of byKey.values()) {
|
|
46408
|
+
if (e.adoptTurnId === turnId && !e.consumed) {
|
|
46409
|
+
entry = e;
|
|
46410
|
+
break;
|
|
46411
|
+
}
|
|
46412
|
+
}
|
|
46413
|
+
if (entry == null)
|
|
46414
|
+
return null;
|
|
46415
|
+
entry.consumed = true;
|
|
46416
|
+
clearTimers(entry);
|
|
46417
|
+
const adoption = {
|
|
46418
|
+
statusKey: entry.statusKey,
|
|
46419
|
+
chatId: entry.chatId,
|
|
46420
|
+
threadId: entry.threadId,
|
|
46421
|
+
activityMessageId: entry.activityMessageId,
|
|
46422
|
+
startedAt: entry.startedAt,
|
|
46423
|
+
pinned: entry.pinned
|
|
46424
|
+
};
|
|
46425
|
+
if (entry.activityMessageId != null) {
|
|
46426
|
+
deps.clearCardRecord(entry.syntheticTurnKey, entry.activityMessageId);
|
|
46427
|
+
deps.writeCardRecord({
|
|
46428
|
+
turnKey: entry.statusKey,
|
|
46429
|
+
chatId: entry.chatId,
|
|
46430
|
+
threadId: entry.threadId,
|
|
46431
|
+
activityMessageId: entry.activityMessageId,
|
|
46432
|
+
startedAt: entry.startedAt,
|
|
46433
|
+
pinned: entry.pinned
|
|
46434
|
+
});
|
|
46435
|
+
}
|
|
46436
|
+
dropEntry(entry);
|
|
46437
|
+
return adoption;
|
|
46438
|
+
},
|
|
46439
|
+
isPreTurnRecord(turnKey2) {
|
|
46440
|
+
return turnKey2.startsWith(PRETURN_TURNKEY_PREFIX);
|
|
46441
|
+
},
|
|
46442
|
+
handleReaped(turnKey2) {
|
|
46443
|
+
const statusKey = bySyntheticKey.get(turnKey2);
|
|
46444
|
+
if (statusKey == null)
|
|
46445
|
+
return;
|
|
46446
|
+
const entry = byKey.get(statusKey);
|
|
46447
|
+
if (entry == null)
|
|
46448
|
+
return;
|
|
46449
|
+
entry.consumed = true;
|
|
46450
|
+
deps.stopTypingLoop(entry.chatId, entry.threadId);
|
|
46451
|
+
dropEntry(entry);
|
|
46452
|
+
},
|
|
46453
|
+
pendingCount() {
|
|
46454
|
+
let n = 0;
|
|
46455
|
+
for (const e of byKey.values())
|
|
46456
|
+
if (!e.consumed)
|
|
46457
|
+
n++;
|
|
46458
|
+
return n;
|
|
46459
|
+
},
|
|
46460
|
+
stopAll() {
|
|
46461
|
+
for (const e of [...byKey.values()])
|
|
46462
|
+
clearTimers(e);
|
|
46463
|
+
byKey.clear();
|
|
46464
|
+
bySyntheticKey.clear();
|
|
46465
|
+
}
|
|
46466
|
+
};
|
|
46467
|
+
}
|
|
46468
|
+
|
|
46469
|
+
// gateway/derive-turn-id.ts
|
|
46470
|
+
function deriveTurnId(chatId, threadId, messageId) {
|
|
46471
|
+
if (messageId == null || messageId === "" || String(messageId) === "0")
|
|
46472
|
+
return null;
|
|
46473
|
+
return `${chatKey(chatId, threadId ?? null)}#${messageId}`;
|
|
46474
|
+
}
|
|
46475
|
+
|
|
46196
46476
|
// typing-emitter.ts
|
|
46197
46477
|
var TYPING_REFRESH_MS = 4000;
|
|
46198
46478
|
var TYPING_FLOOR_MS = 3500;
|
|
@@ -56388,7 +56668,9 @@ var SEND_GATE_DEFAULTS = {
|
|
|
56388
56668
|
perChatBurst: 3,
|
|
56389
56669
|
perGroupPerMin: 18,
|
|
56390
56670
|
perGroupBurst: 2,
|
|
56391
|
-
editFloorMs: 1500
|
|
56671
|
+
editFloorMs: 1500,
|
|
56672
|
+
perMessageEditWindowMs: 300000,
|
|
56673
|
+
perMessageEditMaxPerWindow: 150
|
|
56392
56674
|
};
|
|
56393
56675
|
function createSendGate(config) {
|
|
56394
56676
|
const enabled2 = config.enabled;
|
|
@@ -56400,6 +56682,8 @@ function createSendGate(config) {
|
|
|
56400
56682
|
const perGroupPerMin = config.perGroupPerMin ?? SEND_GATE_DEFAULTS.perGroupPerMin;
|
|
56401
56683
|
const perGroupBurst = config.perGroupBurst ?? SEND_GATE_DEFAULTS.perGroupBurst;
|
|
56402
56684
|
const editFloorMs = config.editFloorMs ?? SEND_GATE_DEFAULTS.editFloorMs;
|
|
56685
|
+
const perMessageEditWindowMs = Math.max(1, Math.floor(config.perMessageEditWindowMs ?? SEND_GATE_DEFAULTS.perMessageEditWindowMs));
|
|
56686
|
+
const perMessageEditMaxPerWindow = Math.max(0, Math.floor(config.perMessageEditMaxPerWindow ?? SEND_GATE_DEFAULTS.perMessageEditMaxPerWindow));
|
|
56403
56687
|
const messageStateTtlMs = config.messageStateTtlMs ?? 60000;
|
|
56404
56688
|
const maxMessageStates = config.maxMessageStates ?? 5000;
|
|
56405
56689
|
const usefulTtlMs = config.usefulTtlMs ?? 120000;
|
|
@@ -56415,7 +56699,8 @@ function createSendGate(config) {
|
|
|
56415
56699
|
dropped: 0,
|
|
56416
56700
|
shed: 0,
|
|
56417
56701
|
expired: 0,
|
|
56418
|
-
failedFast: 0
|
|
56702
|
+
failedFast: 0,
|
|
56703
|
+
budgetDeferred: 0
|
|
56419
56704
|
};
|
|
56420
56705
|
const bootStart = clock.now();
|
|
56421
56706
|
const globalRamp = config.bootRamp ? {
|
|
@@ -56454,7 +56739,8 @@ function createSendGate(config) {
|
|
|
56454
56739
|
lastHash: undefined,
|
|
56455
56740
|
pending: null,
|
|
56456
56741
|
running: false,
|
|
56457
|
-
suppressedUntilMs: 0
|
|
56742
|
+
suppressedUntilMs: 0,
|
|
56743
|
+
editWindowTs: []
|
|
56458
56744
|
};
|
|
56459
56745
|
perMessage.set(key, state);
|
|
56460
56746
|
}
|
|
@@ -56641,7 +56927,20 @@ function createSendGate(config) {
|
|
|
56641
56927
|
try {
|
|
56642
56928
|
while (state.pending) {
|
|
56643
56929
|
const now = clock.now();
|
|
56644
|
-
|
|
56930
|
+
let readyAt = Math.max(state.lastSentMs + editFloorMs, state.suppressedUntilMs);
|
|
56931
|
+
if (perMessageEditMaxPerWindow > 0 && state.pending.priorityClass === "cosmetic") {
|
|
56932
|
+
const windowStart = now - perMessageEditWindowMs;
|
|
56933
|
+
while (state.editWindowTs.length > 0 && state.editWindowTs[0] <= windowStart) {
|
|
56934
|
+
state.editWindowTs.shift();
|
|
56935
|
+
}
|
|
56936
|
+
if (state.editWindowTs.length >= perMessageEditMaxPerWindow) {
|
|
56937
|
+
const budgetReadyAt = state.editWindowTs[0] + perMessageEditWindowMs;
|
|
56938
|
+
if (budgetReadyAt > readyAt) {
|
|
56939
|
+
readyAt = budgetReadyAt;
|
|
56940
|
+
counters.budgetDeferred++;
|
|
56941
|
+
}
|
|
56942
|
+
}
|
|
56943
|
+
}
|
|
56645
56944
|
const waitMs = readyAt - now;
|
|
56646
56945
|
if (waitMs > 0) {
|
|
56647
56946
|
await clock.sleep(waitMs);
|
|
@@ -56667,6 +56966,12 @@ function createSendGate(config) {
|
|
|
56667
56966
|
await admit(bucketsFor(opts));
|
|
56668
56967
|
}
|
|
56669
56968
|
state.lastSentMs = clock.now();
|
|
56969
|
+
if (perMessageEditMaxPerWindow > 0 && p.priorityClass === "cosmetic") {
|
|
56970
|
+
state.editWindowTs.push(state.lastSentMs);
|
|
56971
|
+
const overflow = state.editWindowTs.length - (perMessageEditMaxPerWindow + 1);
|
|
56972
|
+
if (overflow > 0)
|
|
56973
|
+
state.editWindowTs.splice(0, overflow);
|
|
56974
|
+
}
|
|
56670
56975
|
try {
|
|
56671
56976
|
const res = await p.fn();
|
|
56672
56977
|
state.lastHash = p.hash;
|
|
@@ -56854,6 +57159,12 @@ function sendGateConfigFromEnv(env = process.env) {
|
|
|
56854
57159
|
const editFloorMs = parseNonNegativeInt(env.SWITCHROOM_TG_SEND_GATE_EDIT_FLOOR_MS);
|
|
56855
57160
|
if (editFloorMs !== undefined)
|
|
56856
57161
|
out.editFloorMs = editFloorMs;
|
|
57162
|
+
const perMsgWindowMs = parsePositiveInt(env.SWITCHROOM_TG_SEND_GATE_PER_MSG_EDIT_WINDOW_MS);
|
|
57163
|
+
if (perMsgWindowMs !== undefined)
|
|
57164
|
+
out.perMessageEditWindowMs = perMsgWindowMs;
|
|
57165
|
+
const perMsgMax = parseNonNegativeInt(env.SWITCHROOM_TG_SEND_GATE_PER_MSG_EDIT_MAX);
|
|
57166
|
+
if (perMsgMax !== undefined)
|
|
57167
|
+
out.perMessageEditMaxPerWindow = perMsgMax;
|
|
56857
57168
|
const conservativeGlobal = parseBoolFlag(env.SWITCHROOM_TG_SEND_GATE_CONSERVATIVE_GLOBAL);
|
|
56858
57169
|
if (conservativeGlobal !== undefined)
|
|
56859
57170
|
out.conservativeGlobalFloodScope = conservativeGlobal;
|
|
@@ -70901,6 +71212,15 @@ function parseModelCommand(text4) {
|
|
|
70901
71212
|
function isModelCommandBusy(ctx) {
|
|
70902
71213
|
return ctx.currentTurnActive || ctx.turnInFlight;
|
|
70903
71214
|
}
|
|
71215
|
+
function resolveStaleAwareBusy(input) {
|
|
71216
|
+
const turnStale = input.currentTurnActive && input.turnAgeMs !== null && input.turnAgeMs > input.hardTtlMs;
|
|
71217
|
+
const approvalLive = input.oldestPendingApprovalAgeMs !== null && input.oldestPendingApprovalAgeMs <= input.hardTtlMs;
|
|
71218
|
+
return {
|
|
71219
|
+
currentTurnActive: input.currentTurnActive && !turnStale,
|
|
71220
|
+
turnInFlight: input.machineInTurn || approvalLive,
|
|
71221
|
+
clearStaleTurn: turnStale
|
|
71222
|
+
};
|
|
71223
|
+
}
|
|
70904
71224
|
function planModelCommand(parsed, ctx) {
|
|
70905
71225
|
if (parsed.kind === "show" && ctx.menuEnabled)
|
|
70906
71226
|
return { kind: "menu" };
|
|
@@ -75523,10 +75843,34 @@ function validateMs365Preview(input) {
|
|
|
75523
75843
|
out.sizeBytesBefore = o.sizeBytesBefore;
|
|
75524
75844
|
if (typeof o.sizeBytesAfter === "number")
|
|
75525
75845
|
out.sizeBytesAfter = o.sizeBytesAfter;
|
|
75846
|
+
if (typeof o.eventWhen === "string")
|
|
75847
|
+
out.eventWhen = o.eventWhen;
|
|
75848
|
+
const changes = sanitizeChanges(o.changes);
|
|
75849
|
+
if (changes)
|
|
75850
|
+
out.changes = changes;
|
|
75526
75851
|
if (typeof o.agentRationale === "string")
|
|
75527
75852
|
out.agentRationale = o.agentRationale;
|
|
75528
75853
|
return out;
|
|
75529
75854
|
}
|
|
75855
|
+
function sanitizeChanges(input) {
|
|
75856
|
+
if (!Array.isArray(input))
|
|
75857
|
+
return;
|
|
75858
|
+
const out = [];
|
|
75859
|
+
for (const raw of input) {
|
|
75860
|
+
if (!raw || typeof raw !== "object")
|
|
75861
|
+
continue;
|
|
75862
|
+
const c = raw;
|
|
75863
|
+
if (typeof c.field !== "string" || c.field.length === 0)
|
|
75864
|
+
continue;
|
|
75865
|
+
const entry = { field: c.field };
|
|
75866
|
+
if (typeof c.before === "string")
|
|
75867
|
+
entry.before = c.before;
|
|
75868
|
+
if (typeof c.after === "string")
|
|
75869
|
+
entry.after = c.after;
|
|
75870
|
+
out.push(entry);
|
|
75871
|
+
}
|
|
75872
|
+
return out.length > 0 ? out : undefined;
|
|
75873
|
+
}
|
|
75530
75874
|
var DEFAULT_TTL_MS2 = 5 * 60 * 1000;
|
|
75531
75875
|
var MAX_TTL_MS2 = 30 * 60 * 1000;
|
|
75532
75876
|
var MIN_TTL_MS2 = 30 * 1000;
|
|
@@ -75551,6 +75895,9 @@ function buildMs365CardText(p) {
|
|
|
75551
75895
|
lines.push(`ID: ${truncate3(p.itemId, 96)}`);
|
|
75552
75896
|
}
|
|
75553
75897
|
lines.push(`Account: ${truncate3(p.accountEmail, 96)}`);
|
|
75898
|
+
if (p.eventWhen) {
|
|
75899
|
+
lines.push(`When: ${truncate3(p.eventWhen, 96)}`);
|
|
75900
|
+
}
|
|
75554
75901
|
if (typeof p.sizeBytesBefore === "number" || typeof p.sizeBytesAfter === "number") {
|
|
75555
75902
|
const before = p.sizeBytesBefore ?? 0;
|
|
75556
75903
|
const after = p.sizeBytesAfter ?? 0;
|
|
@@ -75561,19 +75908,29 @@ function buildMs365CardText(p) {
|
|
|
75561
75908
|
if (p.deepLink) {
|
|
75562
75909
|
lines.push(`Link: ${truncate3(p.deepLink, 256)}`);
|
|
75563
75910
|
}
|
|
75911
|
+
if (p.changes && p.changes.length > 0) {
|
|
75912
|
+
lines.push("");
|
|
75913
|
+
lines.push("Changes:");
|
|
75914
|
+
for (const c of p.changes.slice(0, 8)) {
|
|
75915
|
+
const before = c.before !== undefined ? truncate3(c.before, 96) : "(none)";
|
|
75916
|
+
const after = c.after !== undefined ? truncate3(c.after, 96) : "(cleared)";
|
|
75917
|
+
lines.push(`\u2022 ${c.field}: ${before} \u2192 ${after}`);
|
|
75918
|
+
}
|
|
75919
|
+
}
|
|
75564
75920
|
if (p.agentRationale) {
|
|
75565
75921
|
lines.push("");
|
|
75566
75922
|
lines.push(`\uD83D\uDCAC ${truncate3(p.agentRationale, 512)}`);
|
|
75567
75923
|
}
|
|
75568
75924
|
lines.push("");
|
|
75569
|
-
lines.push("\u26a0\ufe0f Weak attestation (RFC \u00a78 v1): operator should click through to verify the actual change before approving. Structural diff coming v1.5.");
|
|
75925
|
+
lines.push(p.changes && p.changes.length > 0 ? "\u26a0\ufe0f Attestation (RFC \u00a78 v1.5): the diff above is derived from live Graph state + the mutation payload. Verify before approving." : "\u26a0\ufe0f Weak attestation (RFC \u00a78 v1): operator should click through to verify the actual change before approving. Structural diff coming v1.5.");
|
|
75570
75926
|
return hardenCardBreaks(lines.join(`
|
|
75571
75927
|
`));
|
|
75572
75928
|
}
|
|
75573
75929
|
function truncate3(s, n) {
|
|
75574
|
-
|
|
75575
|
-
|
|
75576
|
-
|
|
75930
|
+
const oneLine = s.replace(/[\r\n\t]+/g, " ");
|
|
75931
|
+
if (oneLine.length <= n)
|
|
75932
|
+
return oneLine;
|
|
75933
|
+
return oneLine.slice(0, n - 1) + "\u2026";
|
|
75577
75934
|
}
|
|
75578
75935
|
function humanBytes(bytes) {
|
|
75579
75936
|
const abs = Math.abs(bytes);
|
|
@@ -76431,6 +76788,28 @@ function workerAgentIdOfPinKey(pinKey) {
|
|
|
76431
76788
|
const agentId = pinKey.slice(WORKER_PIN_KEY_PREFIX.length);
|
|
76432
76789
|
return agentId.length > 0 ? agentId : null;
|
|
76433
76790
|
}
|
|
76791
|
+
function storeOnlyWorkerPinCandidates(args) {
|
|
76792
|
+
const out = [];
|
|
76793
|
+
for (const r of args.rows) {
|
|
76794
|
+
if (workerAgentIdOfPinKey(r.pinKey) == null)
|
|
76795
|
+
continue;
|
|
76796
|
+
if (r.pending)
|
|
76797
|
+
continue;
|
|
76798
|
+
if (r.expiresAt != null)
|
|
76799
|
+
continue;
|
|
76800
|
+
if (r.chatId.length === 0)
|
|
76801
|
+
continue;
|
|
76802
|
+
if (args.inMemoryPinKeys.has(r.pinKey))
|
|
76803
|
+
continue;
|
|
76804
|
+
out.push({
|
|
76805
|
+
pinKey: r.pinKey,
|
|
76806
|
+
chatId: r.chatId,
|
|
76807
|
+
pinnedAt: args.now,
|
|
76808
|
+
messageId: r.messageId
|
|
76809
|
+
});
|
|
76810
|
+
}
|
|
76811
|
+
return out;
|
|
76812
|
+
}
|
|
76434
76813
|
function decideWorkerPinReaps(args) {
|
|
76435
76814
|
const reaps = [];
|
|
76436
76815
|
for (const pin of args.pins) {
|
|
@@ -78164,6 +78543,7 @@ ${result}
|
|
|
78164
78543
|
meta: {
|
|
78165
78544
|
source: "subagent_handback",
|
|
78166
78545
|
outcome: opts.ctx.outcome,
|
|
78546
|
+
message_id: String(ts),
|
|
78167
78547
|
...opts.ctx.threadId != null ? { message_thread_id: String(opts.ctx.threadId) } : {},
|
|
78168
78548
|
...opts.ctx.jsonlAgentId ? { subagent_jsonl_id: opts.ctx.jsonlAgentId } : {}
|
|
78169
78549
|
}
|
|
@@ -80002,6 +80382,7 @@ import {
|
|
|
80002
80382
|
} from "node:fs";
|
|
80003
80383
|
import { join as join39 } from "node:path";
|
|
80004
80384
|
var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
|
|
80385
|
+
var TURN_ACTIVE_HARD_TTL_MS = 10 * 60000;
|
|
80005
80386
|
function touchTurnActiveMarker(stateDir) {
|
|
80006
80387
|
const path2 = join39(stateDir, TURN_ACTIVE_MARKER_FILE);
|
|
80007
80388
|
if (!existsSync34(path2))
|
|
@@ -83728,6 +84109,8 @@ import {
|
|
|
83728
84109
|
} from "node:fs";
|
|
83729
84110
|
import { join as join52 } from "node:path";
|
|
83730
84111
|
var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
|
|
84112
|
+
var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
|
|
84113
|
+
var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
|
|
83731
84114
|
function writeTurnActiveMarker(stateDir, marker) {
|
|
83732
84115
|
try {
|
|
83733
84116
|
mkdirSync37(stateDir, { recursive: true });
|
|
@@ -83794,12 +84177,15 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
83794
84177
|
return null;
|
|
83795
84178
|
}
|
|
83796
84179
|
}
|
|
84180
|
+
function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
|
|
84181
|
+
return markerAgeMs ?? now - turnStartedAt;
|
|
84182
|
+
}
|
|
83797
84183
|
|
|
83798
84184
|
// ../src/build-info.ts
|
|
83799
|
-
var VERSION = "0.18.
|
|
83800
|
-
var COMMIT_SHA = "
|
|
83801
|
-
var COMMIT_DATE = "2026-07-
|
|
83802
|
-
var LATEST_PR =
|
|
84185
|
+
var VERSION = "0.18.28";
|
|
84186
|
+
var COMMIT_SHA = "ef0be9a2";
|
|
84187
|
+
var COMMIT_DATE = "2026-07-16T14:42:35+10:00";
|
|
84188
|
+
var LATEST_PR = 3274;
|
|
83803
84189
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
83804
84190
|
|
|
83805
84191
|
// gateway/boot-version.ts
|
|
@@ -86479,9 +86865,43 @@ var FEED_LIVENESS_OPEN_MS = (() => {
|
|
|
86479
86865
|
var POST_ANSWER_LIVENESS_STALE_MS = parsePostAnswerLivenessMs(process.env.SWITCHROOM_POST_ANSWER_LIVENESS_STALE_MS) || 30000;
|
|
86480
86866
|
function turnInFlightForGate() {
|
|
86481
86867
|
const hasPendingApproval = pendingPermissions.size > 0;
|
|
86868
|
+
return turnInFlightMachineOnly() || hasPendingApproval;
|
|
86869
|
+
}
|
|
86870
|
+
function turnInFlightMachineOnly() {
|
|
86482
86871
|
if (!isDeliveryCutoverEnabled())
|
|
86483
|
-
return claudeBusyKeys.size > 0
|
|
86484
|
-
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size)
|
|
86872
|
+
return claudeBusyKeys.size > 0;
|
|
86873
|
+
return probeGateParity(isMachineInTurn(), claudeBusyKeys.size);
|
|
86874
|
+
}
|
|
86875
|
+
function liveTurnAgeMs(now) {
|
|
86876
|
+
if (currentTurn === null)
|
|
86877
|
+
return null;
|
|
86878
|
+
return effectiveTurnAgeMs(readTurnActiveMarkerAgeMs(STATE_DIR, now), currentTurn.startedAt, now);
|
|
86879
|
+
}
|
|
86880
|
+
function oldestPendingApprovalAgeMs(now) {
|
|
86881
|
+
let oldest = null;
|
|
86882
|
+
for (const p of pendingPermissions.values()) {
|
|
86883
|
+
const age = now - p.startedAt;
|
|
86884
|
+
if (oldest === null || age > oldest)
|
|
86885
|
+
oldest = age;
|
|
86886
|
+
}
|
|
86887
|
+
return oldest;
|
|
86888
|
+
}
|
|
86889
|
+
function resolveModelEffortBusy(now = Date.now()) {
|
|
86890
|
+
const turnAgeMs = liveTurnAgeMs(now);
|
|
86891
|
+
const resolved = resolveStaleAwareBusy({
|
|
86892
|
+
currentTurnActive: currentTurn !== null,
|
|
86893
|
+
turnAgeMs,
|
|
86894
|
+
machineInTurn: turnInFlightMachineOnly(),
|
|
86895
|
+
oldestPendingApprovalAgeMs: oldestPendingApprovalAgeMs(now),
|
|
86896
|
+
hardTtlMs: TURN_ACTIVE_HARD_TTL_MS2
|
|
86897
|
+
});
|
|
86898
|
+
if (resolved.clearStaleTurn && currentTurn !== null) {
|
|
86899
|
+
const ageSec = Math.round((turnAgeMs ?? 0) / 1000);
|
|
86900
|
+
process.stderr.write(`telegram gateway: [phantomturn] cleared stale currentTurn atom age=${ageSec}s ttl=${Math.round(TURN_ACTIVE_HARD_TTL_MS2 / 1000)}s for /model|/effort busy-check agent=${getMyAgentName()}
|
|
86901
|
+
`);
|
|
86902
|
+
clearAllCurrentTurns();
|
|
86903
|
+
}
|
|
86904
|
+
return { currentTurnActive: resolved.currentTurnActive, turnInFlight: resolved.turnInFlight };
|
|
86485
86905
|
}
|
|
86486
86906
|
function deliverResumeSyntheticOrBuffer(agent, inbound) {
|
|
86487
86907
|
const decision = decideInboundDelivery({
|
|
@@ -86649,11 +87069,6 @@ function findTurnByQuotedMessageId(chatId, replyTo) {
|
|
|
86649
87069
|
return null;
|
|
86650
87070
|
return turn;
|
|
86651
87071
|
}
|
|
86652
|
-
function deriveTurnId(chatId, threadId, messageId) {
|
|
86653
|
-
if (messageId == null || messageId === "" || String(messageId) === "0")
|
|
86654
|
-
return null;
|
|
86655
|
-
return `${chatKey2(chatId, threadId ?? null)}#${messageId}`;
|
|
86656
|
-
}
|
|
86657
87072
|
function findTurnByOriginId(originTurnId) {
|
|
86658
87073
|
if (originTurnId == null || originTurnId === "")
|
|
86659
87074
|
return null;
|
|
@@ -88479,8 +88894,8 @@ var pendingStateReaper = setInterval(() => {
|
|
|
88479
88894
|
try {
|
|
88480
88895
|
sweepStaleTurnActiveMarker(STATE_DIR, {
|
|
88481
88896
|
turnInFlight: currentTurn?.registryKey != null,
|
|
88482
|
-
idleSweepMs:
|
|
88483
|
-
hardTtlMs:
|
|
88897
|
+
idleSweepMs: TURN_ACTIVE_IDLE_SWEEP_MS,
|
|
88898
|
+
hardTtlMs: TURN_ACTIVE_HARD_TTL_MS2,
|
|
88484
88899
|
now,
|
|
88485
88900
|
onRemove: ({ ageMs, reason, payload }) => {
|
|
88486
88901
|
const agent = getMyAgentName();
|
|
@@ -88927,11 +89342,16 @@ async function runMidSessionCardReaper() {
|
|
|
88927
89342
|
isLive: (record2) => topicKeys.has(record2.turnKey),
|
|
88928
89343
|
ttlMs: MID_SESSION_CARD_REAPER_TTL_MS,
|
|
88929
89344
|
now,
|
|
88930
|
-
finalizeCard: (record2) =>
|
|
88931
|
-
|
|
88932
|
-
|
|
88933
|
-
|
|
88934
|
-
|
|
89345
|
+
finalizeCard: (record2) => {
|
|
89346
|
+
if (handbackPreturnSignal.isPreTurnRecord(record2.turnKey)) {
|
|
89347
|
+
handbackPreturnSignal.handleReaped(record2.turnKey);
|
|
89348
|
+
}
|
|
89349
|
+
return robustApiCall(() => lockedBot.api.editMessageText(record2.chatId, record2.activityMessageId, richMessage2(restartOrphanCardFinalizeText(record2.startedAt)), {}), {
|
|
89350
|
+
chat_id: record2.chatId,
|
|
89351
|
+
...record2.threadId != null ? { threadId: record2.threadId } : {},
|
|
89352
|
+
verb: "activity-card.mid-session-reap-finalize"
|
|
89353
|
+
});
|
|
89354
|
+
},
|
|
88935
89355
|
unpinCard: async (record2) => {
|
|
88936
89356
|
const pinKey = `fg:${record2.turnKey}`;
|
|
88937
89357
|
if (statusPinState.has(pinKey)) {
|
|
@@ -88956,11 +89376,19 @@ async function runMidSessionCardReaper() {
|
|
|
88956
89376
|
}
|
|
88957
89377
|
if (WORKER_PIN_REAPER_ENABLED && PIN_STATUS_WHILE_WORKING) {
|
|
88958
89378
|
try {
|
|
88959
|
-
const
|
|
89379
|
+
const inMemoryKeys = new Set([...statusPinState.keys()].filter((k) => k.startsWith("wk:")));
|
|
89380
|
+
const inMemoryCandidates = [...inMemoryKeys].map((k) => ({
|
|
88960
89381
|
pinKey: k,
|
|
88961
89382
|
chatId: statusPinChatIds.get(k) ?? "",
|
|
88962
89383
|
pinnedAt: statusPinPinnedAt.get(k) ?? now
|
|
88963
89384
|
}));
|
|
89385
|
+
const storeOnlyCandidates = statusPinPersistEnabled ? storeOnlyWorkerPinCandidates({
|
|
89386
|
+
rows: loadStatusPins(STATUS_PIN_STORE_PATH, statusPinStoreFs),
|
|
89387
|
+
inMemoryPinKeys: inMemoryKeys,
|
|
89388
|
+
now
|
|
89389
|
+
}) : [];
|
|
89390
|
+
const storeOnlyKeys = new Set(storeOnlyCandidates.map((c) => c.pinKey));
|
|
89391
|
+
const candidates = [...inMemoryCandidates, ...storeOnlyCandidates];
|
|
88964
89392
|
const reaps = decideWorkerPinReaps({
|
|
88965
89393
|
pins: candidates,
|
|
88966
89394
|
statusOf: (agentId) => {
|
|
@@ -88987,9 +89415,20 @@ async function runMidSessionCardReaper() {
|
|
|
88987
89415
|
now
|
|
88988
89416
|
});
|
|
88989
89417
|
for (const reap of reaps) {
|
|
88990
|
-
|
|
89418
|
+
const storeOnly = storeOnlyKeys.has(reap.pinKey);
|
|
89419
|
+
process.stderr.write(`telegram gateway: worker-pin reaper unpinning ${reap.pinKey} (chat=${reap.chatId} reason=${reap.reason}${storeOnly ? " source=store-orphan" : ""})
|
|
88991
89420
|
`);
|
|
88992
|
-
|
|
89421
|
+
if (storeOnly && reap.messageId != null) {
|
|
89422
|
+
try {
|
|
89423
|
+
await statusPinApi().unpinChatMessage(reap.chatId, reap.messageId);
|
|
89424
|
+
} catch (err) {
|
|
89425
|
+
process.stderr.write(`telegram gateway: worker-pin reaper store-orphan unpin failed (${reap.pinKey} chat=${reap.chatId} msg=${reap.messageId}): ${err.message}
|
|
89426
|
+
`);
|
|
89427
|
+
}
|
|
89428
|
+
await mutateStatusPinRow(STATUS_PIN_STORE_PATH, statusPinStoreFs, reap.pinKey, null);
|
|
89429
|
+
} else {
|
|
89430
|
+
await reconcileStatusPin(reap.pinKey, reap.chatId, { pinned: false });
|
|
89431
|
+
}
|
|
88993
89432
|
}
|
|
88994
89433
|
} catch (err) {
|
|
88995
89434
|
process.stderr.write(`telegram gateway: worker-pin reaper error: ${err.message}
|
|
@@ -89411,6 +89850,8 @@ var _deliveryMachineTick = setInterval(() => {
|
|
|
89411
89850
|
}, DELIVERY_MACHINE_TICK_MS);
|
|
89412
89851
|
_deliveryMachineTick.unref?.();
|
|
89413
89852
|
function trackRedeliveredInbound(merged) {
|
|
89853
|
+
if (HANDBACK_PRETURN_ENABLED)
|
|
89854
|
+
handbackPreturnSignal.noteHandbackRelease(merged);
|
|
89414
89855
|
if (!DELIVERY_CONFIRM_ENABLED)
|
|
89415
89856
|
return;
|
|
89416
89857
|
const isTrackableResume = isTrackableResumeSynthetic(merged.meta);
|
|
@@ -91090,6 +91531,7 @@ async function executeReply(args) {
|
|
|
91090
91531
|
return { content: [{ type: "text", text: "sent (deduped \u2014 same content sent via earlier path)" }] };
|
|
91091
91532
|
}
|
|
91092
91533
|
}
|
|
91534
|
+
let supersedeFlushIds = [];
|
|
91093
91535
|
{
|
|
91094
91536
|
const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
91095
91537
|
const ownerTurn = resolveReplyOwnerTurn(turn, chat_id, args);
|
|
@@ -91098,9 +91540,9 @@ async function executeReply(args) {
|
|
|
91098
91540
|
if (decision.supersede) {
|
|
91099
91541
|
process.stderr.write(`telegram gateway: reply: superseding flushed turn message(s) chatId=${chat_id} ids=${JSON.stringify(decision.deleteMessageIds)}
|
|
91100
91542
|
`);
|
|
91101
|
-
|
|
91102
|
-
|
|
91103
|
-
|
|
91543
|
+
supersedeFlushIds = decision.deleteMessageIds;
|
|
91544
|
+
if (ownerTurn != null)
|
|
91545
|
+
ownerTurn.answerDelivered = true;
|
|
91104
91546
|
} else {
|
|
91105
91547
|
const replySubstantive = isSubstantiveFinalReply({
|
|
91106
91548
|
text: rawText,
|
|
@@ -91328,6 +91770,25 @@ ${url}`;
|
|
|
91328
91770
|
});
|
|
91329
91771
|
}
|
|
91330
91772
|
}
|
|
91773
|
+
if (supersedeFlushIds.length > 0) {
|
|
91774
|
+
const correction = decideSupersedeCorrection({
|
|
91775
|
+
flushMessageIds: supersedeFlushIds,
|
|
91776
|
+
chunkCount: chunks.length,
|
|
91777
|
+
hasFiles: files.length > 0,
|
|
91778
|
+
suppressText,
|
|
91779
|
+
hasOpenPreview: previewMessageId != null
|
|
91780
|
+
});
|
|
91781
|
+
if (correction.mode === "edit-in-place") {
|
|
91782
|
+
previewMessageId = correction.editMessageId;
|
|
91783
|
+
reply_to = undefined;
|
|
91784
|
+
process.stderr.write(`telegram gateway: reply: superseding flushed message via edit-in-place chatId=${chat_id} id=${correction.editMessageId}
|
|
91785
|
+
`);
|
|
91786
|
+
} else {
|
|
91787
|
+
for (const id of correction.deleteMessageIds) {
|
|
91788
|
+
await swallowingApiCall(() => lockedBot.api.deleteMessage(chat_id, id), { chat_id, verb: "reply.supersedeFlushed" });
|
|
91789
|
+
}
|
|
91790
|
+
}
|
|
91791
|
+
}
|
|
91331
91792
|
if (previewMessageId != null && reply_to != null && replyMode !== "off") {
|
|
91332
91793
|
await deleteStalePreview(previewMessageId);
|
|
91333
91794
|
previewMessageId = null;
|
|
@@ -91336,7 +91797,7 @@ ${url}`;
|
|
|
91336
91797
|
let silentAnchorEditDone = false;
|
|
91337
91798
|
{
|
|
91338
91799
|
const turn2 = currentTurn;
|
|
91339
|
-
if (turn2 != null && chunks.length === 1) {
|
|
91800
|
+
if (turn2 != null && chunks.length === 1 && supersedeFlushIds.length === 0) {
|
|
91340
91801
|
const decision = decideSilentReplyAnchor({
|
|
91341
91802
|
effectivelySilent: disableNotification,
|
|
91342
91803
|
anchorMessageId: turn2.silentAnchorMessageId,
|
|
@@ -93151,6 +93612,61 @@ function clearActivitySummary(turn, finalHtmlOverride) {
|
|
|
93151
93612
|
}
|
|
93152
93613
|
});
|
|
93153
93614
|
}
|
|
93615
|
+
var HANDBACK_PRETURN_ENABLED = !STATIC && process.env.SWITCHROOM_HANDBACK_PRETURN !== "0";
|
|
93616
|
+
var HANDBACK_PRETURN_HTML = "\uD83E\uDD1D Reading the worker\u2019s results\u2026";
|
|
93617
|
+
var HANDBACK_PRETURN_ORPHAN_HTML = "\uD83E\uDD1D A background worker finished, but the handback never started \u2014 it may need a nudge.";
|
|
93618
|
+
async function openHandbackPreTurnCard(chatId, threadId) {
|
|
93619
|
+
if (STATIC)
|
|
93620
|
+
return null;
|
|
93621
|
+
try {
|
|
93622
|
+
const sent = await robustApiCall(() => bot.api.sendRichMessage(chatId, richMessage2(HANDBACK_PRETURN_HTML), {
|
|
93623
|
+
...threadId != null ? { message_thread_id: threadId } : {},
|
|
93624
|
+
disable_notification: true
|
|
93625
|
+
}), {
|
|
93626
|
+
chat_id: chatId,
|
|
93627
|
+
...threadId != null ? { threadId } : {},
|
|
93628
|
+
verb: "handback-preturn.send"
|
|
93629
|
+
});
|
|
93630
|
+
return sent?.message_id ?? null;
|
|
93631
|
+
} catch (err) {
|
|
93632
|
+
process.stderr.write(`telegram gateway: handback pre-turn card send failed: ${err.message}
|
|
93633
|
+
`);
|
|
93634
|
+
return null;
|
|
93635
|
+
}
|
|
93636
|
+
}
|
|
93637
|
+
function finalizeHandbackPreTurnCard(record2) {
|
|
93638
|
+
return robustApiCall(() => bot.api.editMessageText(record2.chatId, record2.activityMessageId, richMessage2(HANDBACK_PRETURN_ORPHAN_HTML), {}), {
|
|
93639
|
+
chat_id: record2.chatId,
|
|
93640
|
+
...record2.threadId != null ? { threadId: record2.threadId } : {},
|
|
93641
|
+
verb: "handback-preturn.orphan-finalize"
|
|
93642
|
+
}).then(() => {
|
|
93643
|
+
return;
|
|
93644
|
+
}).catch(() => {
|
|
93645
|
+
return;
|
|
93646
|
+
});
|
|
93647
|
+
}
|
|
93648
|
+
var handbackPreturnSignal = createHandbackPreturnSignal({
|
|
93649
|
+
chatKey: (chatId, threadId) => chatKey2(chatId, threadId),
|
|
93650
|
+
deriveTurnId: (chatId, threadId, messageId) => deriveTurnId(chatId, threadId, messageId),
|
|
93651
|
+
startTypingLoop: (chatId, threadId) => startTurnTypingLoop(chatId, threadId),
|
|
93652
|
+
stopTypingLoop: (chatId, threadId) => stopTurnTypingLoop(chatId, threadId),
|
|
93653
|
+
openCard: openHandbackPreTurnCard,
|
|
93654
|
+
finalizeCard: finalizeHandbackPreTurnCard,
|
|
93655
|
+
writeCardRecord: (record2) => {
|
|
93656
|
+
if (!activityCardPersistEnabled)
|
|
93657
|
+
return;
|
|
93658
|
+
writeActivityCardRecord(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs, record2);
|
|
93659
|
+
},
|
|
93660
|
+
clearCardRecord: (turnKey2, activityMessageId) => {
|
|
93661
|
+
if (!activityCardPersistEnabled)
|
|
93662
|
+
return;
|
|
93663
|
+
clearActivityCardRecord(ACTIVITY_CARD_STORE_PATH, activityCardStoreFs, turnKey2, activityMessageId);
|
|
93664
|
+
},
|
|
93665
|
+
isTurnSettled: (key) => {
|
|
93666
|
+
const live = currentTurnMap.get(key);
|
|
93667
|
+
return live != null && (live.finalAnswerDelivered || live.endedAt != null);
|
|
93668
|
+
}
|
|
93669
|
+
});
|
|
93154
93670
|
var memoryLegibilityStager = new MemoryLegibilityStager;
|
|
93155
93671
|
function sendMemoryLegibilityLine(event, chatId, threadId) {
|
|
93156
93672
|
const line = renderMemoryLegibilityLine(event);
|
|
@@ -93304,6 +93820,16 @@ function handleSessionEvent(ev) {
|
|
|
93304
93820
|
emissionAuthority: new EmissionAuthority(statusKey(ev.chatId, enqThreadIdNum))
|
|
93305
93821
|
};
|
|
93306
93822
|
next.narrativeGate = makeNarrativeGate(next);
|
|
93823
|
+
if (HANDBACK_PRETURN_ENABLED) {
|
|
93824
|
+
const handbackAdoption = handbackPreturnSignal.tryAdopt(turnId);
|
|
93825
|
+
if (handbackAdoption != null) {
|
|
93826
|
+
if (handbackAdoption.activityMessageId != null) {
|
|
93827
|
+
next.activityMessageId = handbackAdoption.activityMessageId;
|
|
93828
|
+
next.activityEverOpened = true;
|
|
93829
|
+
}
|
|
93830
|
+
startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
|
|
93831
|
+
}
|
|
93832
|
+
}
|
|
93307
93833
|
setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
|
|
93308
93834
|
scheduleEarlyLivenessOpen(next);
|
|
93309
93835
|
process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
|
|
@@ -96350,7 +96876,8 @@ bot.command("model", async (ctx) => {
|
|
|
96350
96876
|
const parsed = parseModelCommand(text5) ?? { kind: "show" };
|
|
96351
96877
|
const chatId = String(ctx.chat.id);
|
|
96352
96878
|
const threadId = resolveThreadId(chatId, ctx.message?.message_thread_id);
|
|
96353
|
-
const
|
|
96879
|
+
const modelBusy = resolveModelEffortBusy();
|
|
96880
|
+
const busyNow = modelBusy.currentTurnActive || modelBusy.turnInFlight;
|
|
96354
96881
|
process.stderr.write(modelCommandReceiptLine(getMyAgentName(), parsed, busyNow) + `
|
|
96355
96882
|
`);
|
|
96356
96883
|
if (HISTORY_ENABLED && ctx.message?.message_id != null) {
|
|
@@ -96371,8 +96898,8 @@ bot.command("model", async (ctx) => {
|
|
|
96371
96898
|
}
|
|
96372
96899
|
const deps = buildModelDeps({ chatId, threadId });
|
|
96373
96900
|
const disposition = planModelCommand(parsed, {
|
|
96374
|
-
currentTurnActive:
|
|
96375
|
-
turnInFlight:
|
|
96901
|
+
currentTurnActive: modelBusy.currentTurnActive,
|
|
96902
|
+
turnInFlight: modelBusy.turnInFlight,
|
|
96376
96903
|
menuEnabled: process.env.SWITCHROOM_MODEL_MENU !== "0"
|
|
96377
96904
|
});
|
|
96378
96905
|
if (disposition.kind === "menu") {
|
|
@@ -96443,7 +96970,8 @@ bot.command("effort", async (ctx) => {
|
|
|
96443
96970
|
await switchroomReply(ctx, menu.text, { html: true, reply_markup: effortMenuReplyMarkup(menu) });
|
|
96444
96971
|
return;
|
|
96445
96972
|
}
|
|
96446
|
-
|
|
96973
|
+
const effortBusy = resolveModelEffortBusy();
|
|
96974
|
+
if ((parsed.kind === "set" || parsed.kind === "default") && effortBusy.currentTurnActive) {
|
|
96447
96975
|
const requestedLevel = parsed.kind === "set" ? parsed.level : "default";
|
|
96448
96976
|
const chatId = String(ctx.chat.id);
|
|
96449
96977
|
const threadId = resolveThreadId(chatId, ctx.message?.message_thread_id);
|