polygram 0.38.1 → 0.38.3
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/.claude-plugin/plugin.json +1 -1
- package/lib/approvals/store.js +45 -7
- package/lib/codex/runtime-controller.js +107 -1
- package/lib/db/event-detail-schema.js +523 -0
- package/lib/db/pairings.js +9 -1
- package/lib/db/secret-sweep.js +6 -3
- package/lib/db/sessions.js +2 -2
- package/lib/db.js +353 -49
- package/lib/handlers/abort.js +20 -3
- package/lib/handlers/approvals.js +2 -2
- package/lib/handlers/config-callback.js +7 -7
- package/lib/handlers/dispatcher.js +24 -9
- package/lib/handlers/questions.js +546 -45
- package/lib/handlers/slash-commands.js +54 -13
- package/lib/handlers/voice.js +1 -1
- package/lib/ipc/handlers.js +4 -0
- package/lib/ops/clean-restart-qualification.js +1 -0
- package/lib/ops/clean-shutdown.js +5 -0
- package/lib/ops/deploy-restart-handler.js +111 -0
- package/lib/ops/foreground-canary-target.js +454 -0
- package/lib/questions/questions.js +51 -0
- package/lib/questions/store.js +61 -5
- package/lib/rewind/execute.js +12 -2
- package/lib/rewind/rewind.js +1 -1
- package/lib/sdk/build-options.js +2 -2
- package/lib/sdk/callbacks.js +14 -9
- package/lib/secret-detect.js +280 -12
- package/lib/telegram/api.js +10 -2
- package/lib/telegram/rich-edit.js +9 -5
- package/lib/telegram/rich-send.js +6 -6
- package/migrations/020-drop-secret-fingerprint.sql +13 -0
- package/package.json +2 -2
- package/polygram.js +117 -68
package/polygram.js
CHANGED
|
@@ -31,13 +31,15 @@ const {
|
|
|
31
31
|
const { buildPrompt, resolvePromptBackend } = require('./lib/prompt');
|
|
32
32
|
const { countInFlight } = require('./lib/queue-utils');
|
|
33
33
|
const { createIpcHandlers } = require('./lib/ipc/handlers');
|
|
34
|
-
const {
|
|
35
|
-
requireRestartRequestId,
|
|
36
|
-
} = require('./lib/ipc/restart-request-id');
|
|
37
34
|
const {
|
|
38
35
|
buildQualificationEvent,
|
|
39
|
-
normalizeDeployQualificationExpectation,
|
|
40
36
|
} = require('./lib/ops/clean-restart-qualification');
|
|
37
|
+
const {
|
|
38
|
+
createForegroundCanaryAuthorizer,
|
|
39
|
+
} = require('./lib/ops/foreground-canary-target');
|
|
40
|
+
const {
|
|
41
|
+
createDeployRestartHandler,
|
|
42
|
+
} = require('./lib/ops/deploy-restart-handler');
|
|
41
43
|
const { classifyOrphanSweep } = require('./lib/ops/tmux-preflight');
|
|
42
44
|
const {
|
|
43
45
|
parseSystemdInvocationId,
|
|
@@ -119,7 +121,7 @@ const { createRedeliver } = require('./lib/handlers/redeliver');
|
|
|
119
121
|
const { classifyReplay, executeReplayPlan } = require('./lib/handlers/replay-disposition');
|
|
120
122
|
const { createDropRedeliverer } = require('./lib/handlers/drop-redeliver');
|
|
121
123
|
const { createSessionFeedback } = require('./lib/feedback/session-feedback');
|
|
122
|
-
const { createSlashCommands } = require('./lib/handlers/slash-commands');
|
|
124
|
+
const { createSlashCommands, normalizeCompactCommand } = require('./lib/handlers/slash-commands');
|
|
123
125
|
const {
|
|
124
126
|
buildCodexReconciliationView,
|
|
125
127
|
createHandleCodexReconciliationCallback,
|
|
@@ -1075,6 +1077,8 @@ let attemptAutoResume = null;
|
|
|
1075
1077
|
let errorReplyText = null;
|
|
1076
1078
|
let queueWarnThreshold = null;
|
|
1077
1079
|
let inFlightHandlers = null;
|
|
1080
|
+
let getActiveHandlerCount = null;
|
|
1081
|
+
let getActiveHandlerTargets = null;
|
|
1078
1082
|
let awaitHandlerSettlement = null;
|
|
1079
1083
|
let trackHandlerTask = null;
|
|
1080
1084
|
let recoverCodexRequest = null;
|
|
@@ -1394,7 +1398,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1394
1398
|
// /pair-code /pairings /unpair /pair. Returns true when handled;
|
|
1395
1399
|
// caller short-circuits.
|
|
1396
1400
|
if (await dispatchSlashCommand({
|
|
1397
|
-
text, sessionKey, chatId, threadIdStr, chatConfig,
|
|
1401
|
+
text, msgId: msg.message_id, sessionKey, chatId, threadIdStr, chatConfig,
|
|
1398
1402
|
cmdUser, cmdUserId, label, sendReply,
|
|
1399
1403
|
})) return;
|
|
1400
1404
|
|
|
@@ -1507,7 +1511,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1507
1511
|
console.error(`[${label}] failed to notify user of skipped attachments: ${err.message}`);
|
|
1508
1512
|
logEvent('attachment-skip-notice-failed', {
|
|
1509
1513
|
chat_id: chatId, msg_id: msg.message_id,
|
|
1510
|
-
|
|
1514
|
+
error_class: err.name || 'Error',
|
|
1511
1515
|
rejected_count: rejected.length,
|
|
1512
1516
|
});
|
|
1513
1517
|
}
|
|
@@ -1789,7 +1793,8 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
1789
1793
|
// Network errors can embed the request URL, which carries the
|
|
1790
1794
|
// bot token — redact before persisting, matching rich-edit.js
|
|
1791
1795
|
// and api.js's handling of the same error class.
|
|
1792
|
-
|
|
1796
|
+
api_error_code: err.code || err.name || 'error',
|
|
1797
|
+
error_len: err.message?.length ?? 0,
|
|
1793
1798
|
bot: BOT_NAME,
|
|
1794
1799
|
});
|
|
1795
1800
|
throw err;
|
|
@@ -2449,7 +2454,9 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2449
2454
|
logEvent('wedged-session-detected', {
|
|
2450
2455
|
chat_id: chatId, session_key: sessionKey,
|
|
2451
2456
|
kind: wedge.kind,
|
|
2452
|
-
|
|
2457
|
+
// The wrapper's classification is the forensic signal; the wrapped
|
|
2458
|
+
// payload is provider content and stays out of the row.
|
|
2459
|
+
text_len: result.text.length,
|
|
2453
2460
|
});
|
|
2454
2461
|
// Promote the wrapped error to result.error so the existing
|
|
2455
2462
|
// auto-recover + thrown-error machinery handles it uniformly.
|
|
@@ -2470,6 +2477,13 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2470
2477
|
// to type /new.
|
|
2471
2478
|
const cls = classifyError(result.error);
|
|
2472
2479
|
if (cls.autoRecover === 'reset_session') {
|
|
2480
|
+
// The process that owns any open question is going away with the
|
|
2481
|
+
// session. Retirement closes the row and drops what is held exactly
|
|
2482
|
+
// for it — that part runs synchronously, so starting it here is enough
|
|
2483
|
+
// to have happened before the reset. Only the card edit it leaves
|
|
2484
|
+
// behind is awaited, and the reset must not wait on Telegram.
|
|
2485
|
+
questionHandlers?.retireSession(sessionKey)
|
|
2486
|
+
?.catch((err) => console.error(`[${label}] question retire failed: ${err.message}`));
|
|
2473
2487
|
pm.resetSession(sessionKey, { reason: cls.kind })
|
|
2474
2488
|
.catch((err) => console.error(`[${label}] auto-reset failed: ${err.message}`));
|
|
2475
2489
|
logEvent('auto-recover', {
|
|
@@ -2644,7 +2658,7 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2644
2658
|
reactor.setState('ERROR');
|
|
2645
2659
|
logEvent('telegram-empty-response-fallback-failed', {
|
|
2646
2660
|
chat_id: chatId, msg_id: msg.message_id, bot: BOT_NAME,
|
|
2647
|
-
|
|
2661
|
+
error_class: err.name || 'Error',
|
|
2648
2662
|
});
|
|
2649
2663
|
throw new Error(`empty-response fallback send failed: ${err.message}`);
|
|
2650
2664
|
}
|
|
@@ -2733,7 +2747,8 @@ async function handleMessage(sessionKey, chatId, msg, bot) {
|
|
|
2733
2747
|
logEvent('canned-reply-suppressed', {
|
|
2734
2748
|
chat_id: chatId,
|
|
2735
2749
|
msg_id: msg.message_id,
|
|
2736
|
-
|
|
2750
|
+
// The suppressed reply is provider content; its size is the signal.
|
|
2751
|
+
original_len: sanitized.original?.length ?? 0,
|
|
2737
2752
|
backend: result?.backend || null,
|
|
2738
2753
|
});
|
|
2739
2754
|
parsed.text = sanitized.text;
|
|
@@ -3531,7 +3546,7 @@ function createBot(token) {
|
|
|
3531
3546
|
bot: BOT_NAME,
|
|
3532
3547
|
update_id: updateId,
|
|
3533
3548
|
msg_id: msgId,
|
|
3534
|
-
|
|
3549
|
+
error_class: err.name || 'Error',
|
|
3535
3550
|
});
|
|
3536
3551
|
});
|
|
3537
3552
|
|
|
@@ -3871,7 +3886,7 @@ async function main() {
|
|
|
3871
3886
|
}
|
|
3872
3887
|
} catch (err) {
|
|
3873
3888
|
console.error(`[secret-sweep] FAILED (${trigger}): ${err.message}`);
|
|
3874
|
-
try { db.logEvent('secret-sweep-failed', { trigger,
|
|
3889
|
+
try { db.logEvent('secret-sweep-failed', { trigger, error_class: err.name || 'Error' }); } catch {}
|
|
3875
3890
|
}
|
|
3876
3891
|
};
|
|
3877
3892
|
if (secretSweepCfg.enabled) {
|
|
@@ -4042,6 +4057,20 @@ async function main() {
|
|
|
4042
4057
|
answerQuestion: (sk, tc, result) => pm.answerQuestion(sk, tc, result),
|
|
4043
4058
|
logger: console,
|
|
4044
4059
|
});
|
|
4060
|
+
// A question whose answer was held in memory cannot be completed by this
|
|
4061
|
+
// process: the exact value went with the one that died. Cancel those rows
|
|
4062
|
+
// here — before replay, redelivery, provider recovery, polling or any other
|
|
4063
|
+
// path can act on them — so none is ever answered from a marker.
|
|
4064
|
+
// A failure here is fatal on purpose. Rows this could not cancel would meet
|
|
4065
|
+
// replay and inbound traffic with an answer no live process can complete, so
|
|
4066
|
+
// startup stops instead of admitting messages behind an open fence.
|
|
4067
|
+
try {
|
|
4068
|
+
await questionHandlers.reconcileMarkedQuestionsAtBoot(questionStore.listOpen(BOT_NAME));
|
|
4069
|
+
} catch (e) {
|
|
4070
|
+
console.error(`[${BOT_NAME}] question boot reconciliation failed; refusing to admit traffic: ${e.message}`);
|
|
4071
|
+
throw e;
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4045
4074
|
// Resolve expired questions with {timedout} so claude never hangs on an ignored ask.
|
|
4046
4075
|
setInterval(() => {
|
|
4047
4076
|
try {
|
|
@@ -4055,7 +4084,10 @@ async function main() {
|
|
|
4055
4084
|
// the copy-only transcript-fork executor (P2/P3: fork → repoint the session → kill → delete
|
|
4056
4085
|
// orphaned bot messages). channels/cli only; the fork mechanism was proven in P0.6.
|
|
4057
4086
|
// See docs/0.13-rewind-design.md.
|
|
4058
|
-
const executeRewind = createRewindExecutor({
|
|
4087
|
+
const executeRewind = createRewindExecutor({
|
|
4088
|
+
db, pm, tg, bot, botName: BOT_NAME, logEvent, logger: console,
|
|
4089
|
+
retireQuestionSession: (sessionKey) => questionHandlers?.retireSession(sessionKey),
|
|
4090
|
+
});
|
|
4059
4091
|
rewindHandler = createRewindHandler({
|
|
4060
4092
|
pm, tg, bot, botName: BOT_NAME, logEvent, logger: console, executeRewind,
|
|
4061
4093
|
});
|
|
@@ -4403,6 +4435,7 @@ async function main() {
|
|
|
4403
4435
|
handleAbortIfRequested = createHandleAbort({
|
|
4404
4436
|
pm, bot, tg, logEvent, isAbortRequest,
|
|
4405
4437
|
markSessionAborted, clearAutosteeredReactions, getSessionKey,
|
|
4438
|
+
retireQuestionSession: (sessionKey) => questionHandlers?.retireSession(sessionKey),
|
|
4406
4439
|
botName: BOT_NAME, logger: console,
|
|
4407
4440
|
});
|
|
4408
4441
|
autosteer = createAutosteerHandlers({
|
|
@@ -4423,6 +4456,8 @@ async function main() {
|
|
|
4423
4456
|
errorReplyText,
|
|
4424
4457
|
queueWarnThreshold,
|
|
4425
4458
|
inFlightHandlers,
|
|
4459
|
+
getActiveHandlerCount,
|
|
4460
|
+
getActiveHandlerTargets,
|
|
4426
4461
|
awaitSettlement: awaitHandlerSettlement,
|
|
4427
4462
|
trackTask: trackHandlerTask,
|
|
4428
4463
|
} = createDispatcher({
|
|
@@ -4508,6 +4543,7 @@ async function main() {
|
|
|
4508
4543
|
modelVersionsDesc: MODEL_VERSIONS_DESC, saveConfig,
|
|
4509
4544
|
resolveRuntimeView: resolveSessionRuntimeView,
|
|
4510
4545
|
botName: BOT_NAME, logEvent, logger: console,
|
|
4546
|
+
retireQuestionSession: (sessionKey) => questionHandlers?.retireSession(sessionKey),
|
|
4511
4547
|
});
|
|
4512
4548
|
console.log('[polygram] using SDK ProcessManager');
|
|
4513
4549
|
|
|
@@ -4569,19 +4605,19 @@ async function main() {
|
|
|
4569
4605
|
// provider; ordinary stops keep the existing answer-and-await behavior.
|
|
4570
4606
|
try {
|
|
4571
4607
|
const openQuestions = questionStore.listOpen?.(BOT_NAME) || [];
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4608
|
+
const shutdownNotice = 'Bot is restarting — this question was cancelled. It may be re-asked in a moment.';
|
|
4609
|
+
// Each row is disposed on its own: one that throws must not take the
|
|
4610
|
+
// rows after it, nor the drop of everything held in memory, with it.
|
|
4611
|
+
for (const row of openQuestions) {
|
|
4612
|
+
try {
|
|
4613
|
+
if (continuationAuthorized) {
|
|
4614
|
+
questionHandlers.beginShutdownDisposition(row, { message: shutdownNotice });
|
|
4615
|
+
} else {
|
|
4616
|
+
// eslint-disable-next-line no-await-in-loop
|
|
4617
|
+
await questionHandlers.expireQuestion(row, { status: 'cancelled', message: shutdownNotice });
|
|
4618
|
+
}
|
|
4619
|
+
} catch (err) {
|
|
4620
|
+
console.error(`[shutdown] question ${row.id} disposition failed: ${err.message}`);
|
|
4585
4621
|
}
|
|
4586
4622
|
}
|
|
4587
4623
|
if (openQuestions.length) {
|
|
@@ -4589,6 +4625,11 @@ async function main() {
|
|
|
4589
4625
|
}
|
|
4590
4626
|
} catch (err) {
|
|
4591
4627
|
console.error(`[shutdown] question expiry failed: ${err.message}`);
|
|
4628
|
+
} finally {
|
|
4629
|
+
// Nothing exact survives the process on purpose, whatever any single row
|
|
4630
|
+
// did on the way out.
|
|
4631
|
+
try { questionHandlers.discardAll(); }
|
|
4632
|
+
catch (err) { console.error(`[shutdown] question context drop failed: ${err.message}`); }
|
|
4592
4633
|
}
|
|
4593
4634
|
|
|
4594
4635
|
// 1.6 Album siblings still buffered (arrived <flushMs before shutdown,
|
|
@@ -4681,6 +4722,7 @@ async function main() {
|
|
|
4681
4722
|
awaitIngressSettlement,
|
|
4682
4723
|
awaitHandlerSettlement,
|
|
4683
4724
|
settlementTimeoutMs: SHUTDOWN_DRAIN_MS,
|
|
4725
|
+
continuationAuthorized,
|
|
4684
4726
|
qualificationExpectation,
|
|
4685
4727
|
});
|
|
4686
4728
|
if (qualificationExpectation !== undefined) {
|
|
@@ -4793,7 +4835,7 @@ async function main() {
|
|
|
4793
4835
|
logEvent('shutdown-persistence-fallback', {
|
|
4794
4836
|
bot: BOT_NAME,
|
|
4795
4837
|
replay_marked: fallback.replayMarked,
|
|
4796
|
-
|
|
4838
|
+
error_class: err.name || 'Error',
|
|
4797
4839
|
});
|
|
4798
4840
|
} catch (fallbackErr) {
|
|
4799
4841
|
console.error(
|
|
@@ -4849,6 +4891,27 @@ async function main() {
|
|
|
4849
4891
|
// scripts, hook); also exported to spawned Claude processes via env.
|
|
4850
4892
|
const ipcSecret = ipcServer.writeSecret(BOT_NAME);
|
|
4851
4893
|
process.env.POLYGRAM_IPC_SECRET = ipcSecret;
|
|
4894
|
+
const foregroundCanaryAuthorizer = createForegroundCanaryAuthorizer({
|
|
4895
|
+
botName: BOT_NAME,
|
|
4896
|
+
daemonIdentity,
|
|
4897
|
+
config,
|
|
4898
|
+
tokenSecret: ipcSecret,
|
|
4899
|
+
getActiveHandlerCount: () => getActiveHandlerCount(),
|
|
4900
|
+
getActiveHandlerTargets: () => getActiveHandlerTargets(),
|
|
4901
|
+
getForegroundCanaryTarget: (input) => (
|
|
4902
|
+
db.getForegroundCanaryTarget(input)
|
|
4903
|
+
),
|
|
4904
|
+
});
|
|
4905
|
+
const requestDeployRestart = createDeployRestartHandler({
|
|
4906
|
+
getIsShuttingDown: () => isShuttingDown,
|
|
4907
|
+
getPid: () => process.pid,
|
|
4908
|
+
foregroundCanaryAuthorizer,
|
|
4909
|
+
persistForegroundCanaryAuthorization: (detail) => (
|
|
4910
|
+
db.logEvent('foreground-canary-target-authorized', detail)
|
|
4911
|
+
),
|
|
4912
|
+
shutdown,
|
|
4913
|
+
logger: console,
|
|
4914
|
+
});
|
|
4852
4915
|
ipcCloser = await ipcServer.start({
|
|
4853
4916
|
path: ipcServer.socketPathFor(BOT_NAME),
|
|
4854
4917
|
secret: ipcSecret,
|
|
@@ -4857,28 +4920,13 @@ async function main() {
|
|
|
4857
4920
|
daemonIdentity,
|
|
4858
4921
|
getInFlightHandlers: () => inFlightHandlers,
|
|
4859
4922
|
handleSendOverIpc: (req) => handleSendOverIpc(req),
|
|
4923
|
+
requestForegroundCanaryTarget: (req) => (
|
|
4924
|
+
foregroundCanaryAuthorizer.probe(req)
|
|
4925
|
+
),
|
|
4860
4926
|
inspectCleanRestartQualification: () => (
|
|
4861
4927
|
pm.inspectCleanRestartQualification(undefined)
|
|
4862
4928
|
),
|
|
4863
|
-
requestDeployRestart
|
|
4864
|
-
if (isShuttingDown) {
|
|
4865
|
-
const error = new Error('shutdown already in progress');
|
|
4866
|
-
error.code = 'SHUTDOWN_IN_PROGRESS';
|
|
4867
|
-
throw error;
|
|
4868
|
-
}
|
|
4869
|
-
const restartRequestId = requireRestartRequestId(req.id);
|
|
4870
|
-
const qualificationExpectation = normalizeDeployQualificationExpectation(req);
|
|
4871
|
-
const oldPid = process.pid;
|
|
4872
|
-
shutdown({
|
|
4873
|
-
continuationAuthorized: true,
|
|
4874
|
-
trigger: 'deploy-ipc',
|
|
4875
|
-
restartRequestId,
|
|
4876
|
-
qualificationExpectation,
|
|
4877
|
-
}).catch((error) => {
|
|
4878
|
-
console.error(`[shutdown] deploy restart failed: ${error.message}`);
|
|
4879
|
-
});
|
|
4880
|
-
return { accepted: true, old_pid: oldPid };
|
|
4881
|
-
},
|
|
4929
|
+
requestDeployRestart,
|
|
4882
4930
|
}),
|
|
4883
4931
|
logger: console,
|
|
4884
4932
|
});
|
|
@@ -5274,18 +5322,17 @@ async function main() {
|
|
|
5274
5322
|
console.error(`[replay] boot replay failed: ${err.message}`);
|
|
5275
5323
|
}
|
|
5276
5324
|
|
|
5277
|
-
//
|
|
5278
|
-
//
|
|
5279
|
-
//
|
|
5280
|
-
//
|
|
5281
|
-
//
|
|
5282
|
-
// freshly-spawned (resumed) Query." Same pattern as boot-replay
|
|
5283
|
-
// for inbound messages — recovery should be invisible.
|
|
5325
|
+
// Handle compact-command events that were never paired with a
|
|
5326
|
+
// compact-boundary (a deploy or crash interrupted them before the SDK
|
|
5327
|
+
// processed them). Recovery re-pushes the same /compact line into a
|
|
5328
|
+
// freshly-spawned (resumed) session, invisibly — the same pattern as
|
|
5329
|
+
// boot-replay for inbound messages.
|
|
5284
5330
|
//
|
|
5285
5331
|
// Requirements for a silent retry:
|
|
5286
|
-
// 1. The
|
|
5287
|
-
//
|
|
5288
|
-
//
|
|
5332
|
+
// 1. The command line is recoverable — from the inbound message row the
|
|
5333
|
+
// event points at, or from an older event that still stored it — and
|
|
5334
|
+
// it normalizes to a real /compact command. Otherwise the user gets
|
|
5335
|
+
// the "please retry" message instead.
|
|
5289
5336
|
// 2. The chat is still configured (config.chats[chat_id] exists).
|
|
5290
5337
|
// 3. The session has a saved claude_session_id we can resume.
|
|
5291
5338
|
//
|
|
@@ -5313,9 +5360,13 @@ async function main() {
|
|
|
5313
5360
|
const threadId = o.thread_id ? Number(o.thread_id) : null;
|
|
5314
5361
|
const savedSessionId = getClaudeSessionId(db, o.session_key);
|
|
5315
5362
|
|
|
5316
|
-
// Silent retry path: only when
|
|
5317
|
-
//
|
|
5318
|
-
|
|
5363
|
+
// Silent retry path: only when the stored line is provably a compact
|
|
5364
|
+
// command AND there is a session_id to resume into. The line comes back
|
|
5365
|
+
// from storage, so it is normalized and validated before it is fired at
|
|
5366
|
+
// a live session — a mention-suffixed command is accepted in its
|
|
5367
|
+
// normalized form, anything else is refused and surfaced instead.
|
|
5368
|
+
const replayCommand = normalizeCompactCommand(o.text);
|
|
5369
|
+
if (replayCommand && savedSessionId) {
|
|
5319
5370
|
try {
|
|
5320
5371
|
const entry = await pm.getOrSpawn(
|
|
5321
5372
|
o.session_key,
|
|
@@ -5327,7 +5378,7 @@ async function main() {
|
|
|
5327
5378
|
if (!entry || typeof entry.fireUserMessage !== 'function') {
|
|
5328
5379
|
throw new Error('Process.fireUserMessage not available');
|
|
5329
5380
|
}
|
|
5330
|
-
const ok = entry.fireUserMessage(
|
|
5381
|
+
const ok = entry.fireUserMessage(replayCommand);
|
|
5331
5382
|
if (!ok) {
|
|
5332
5383
|
throw new Error('fireUserMessage refused (closed or empty content)');
|
|
5333
5384
|
}
|
|
@@ -5336,8 +5387,7 @@ async function main() {
|
|
|
5336
5387
|
thread_id: o.thread_id,
|
|
5337
5388
|
session_key: o.session_key,
|
|
5338
5389
|
original_ts: o.ts,
|
|
5339
|
-
text_len:
|
|
5340
|
-
user: o.user,
|
|
5390
|
+
text_len: replayCommand.length,
|
|
5341
5391
|
user_id: o.user_id,
|
|
5342
5392
|
});
|
|
5343
5393
|
replayed += 1;
|
|
@@ -5348,9 +5398,9 @@ async function main() {
|
|
|
5348
5398
|
}
|
|
5349
5399
|
}
|
|
5350
5400
|
|
|
5351
|
-
// Fallback: surface the
|
|
5352
|
-
//
|
|
5353
|
-
// the silent
|
|
5401
|
+
// Fallback: surface the "please retry" message. Happens when the hint
|
|
5402
|
+
// could not be recovered, when the stored line is not a compact
|
|
5403
|
+
// command, or when the silent retry failed.
|
|
5354
5404
|
try {
|
|
5355
5405
|
await tg(bot, 'sendMessage', {
|
|
5356
5406
|
chat_id: o.chat_id,
|
|
@@ -5362,9 +5412,8 @@ async function main() {
|
|
|
5362
5412
|
thread_id: o.thread_id,
|
|
5363
5413
|
session_key: o.session_key,
|
|
5364
5414
|
original_ts: o.ts,
|
|
5365
|
-
user: o.user,
|
|
5366
5415
|
user_id: o.user_id,
|
|
5367
|
-
reason:
|
|
5416
|
+
reason: replayCommand ? 'spawn-failed' : (o.text ? 'not-a-compact-command' : 'no-recoverable-text'),
|
|
5368
5417
|
});
|
|
5369
5418
|
surfacedFallback += 1;
|
|
5370
5419
|
} catch (err) {
|