metheus-governance-mcp-cli 0.2.248 → 0.2.250
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/cli.mjs +371 -247
- package/lib/runner-delivery.mjs +17 -0
- package/lib/runner-helpers.mjs +90 -0
- package/lib/runner-orchestration.mjs +60 -8
- package/lib/runner-runtime.mjs +53 -1
- package/lib/selftest-runner-scenarios.mjs +157 -0
- package/lib/selftest-telegram-e2e.mjs +45 -11
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -140,6 +140,7 @@ import {
|
|
|
140
140
|
buildRunnerRouteStateFromComment,
|
|
141
141
|
buildProcessableArchiveLogicalKey,
|
|
142
142
|
findEarlierProcessableArchiveDuplicate,
|
|
143
|
+
findRecentTelegramMessageEnvelope,
|
|
143
144
|
isInboundArchiveKind,
|
|
144
145
|
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
145
146
|
normalizeArchiveCommentRecord,
|
|
@@ -1989,6 +1990,7 @@ function mergeRunnerStateRecords(preferred, fallback) {
|
|
|
1989
1990
|
last_followup_source_message_envelope: pickObjectField("last_followup_source_message_envelope"),
|
|
1990
1991
|
last_followup_last_reply_message_envelope: pickObjectField("last_followup_last_reply_message_envelope"),
|
|
1991
1992
|
last_followup_attempted_delivery_envelope: pickObjectField("last_followup_attempted_delivery_envelope"),
|
|
1993
|
+
recent_local_inbound_envelopes: pickObjectField("recent_local_inbound_envelopes"),
|
|
1992
1994
|
last_contract_validation_targets: pickArrayField("last_contract_validation_targets", normalizeTelegramMentionUsername),
|
|
1993
1995
|
last_normalized_execution_contract_targets: pickArrayField("last_normalized_execution_contract_targets", normalizeTelegramMentionUsername),
|
|
1994
1996
|
last_normalized_execution_next_responders: pickArrayField("last_normalized_execution_next_responders", normalizeTelegramMentionUsername),
|
|
@@ -2075,9 +2077,10 @@ function migrateBotRunnerStateRoutes(routes, runnerConfig) {
|
|
|
2075
2077
|
};
|
|
2076
2078
|
}
|
|
2077
2079
|
|
|
2078
|
-
function loadBotRunnerState() {
|
|
2079
|
-
const filePath = botRunnerStateFilePath();
|
|
2080
|
-
|
|
2080
|
+
function loadBotRunnerState() {
|
|
2081
|
+
const filePath = botRunnerStateFilePath();
|
|
2082
|
+
waitForBotRunnerStateLockRelease(filePath);
|
|
2083
|
+
try {
|
|
2081
2084
|
if (!fs.existsSync(filePath)) {
|
|
2082
2085
|
return {
|
|
2083
2086
|
filePath,
|
|
@@ -2127,8 +2130,97 @@ function loadBotRunnerState() {
|
|
|
2127
2130
|
remainingAnonymousKeys: [],
|
|
2128
2131
|
};
|
|
2129
2132
|
}
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function sleepSyncMs(delayMs) {
|
|
2136
|
+
const ms = Number(delayMs) || 0;
|
|
2137
|
+
if (!(ms > 0)) {
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
const sleepBuffer = new SharedArrayBuffer(4);
|
|
2141
|
+
const sleepArray = new Int32Array(sleepBuffer);
|
|
2142
|
+
Atomics.wait(sleepArray, 0, 0, ms);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function botRunnerStateLockFilePath(filePath) {
|
|
2146
|
+
const normalizedPath = String(filePath || "").trim();
|
|
2147
|
+
return normalizedPath ? `${normalizedPath}.lock` : "";
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
function tryReleaseStaleBotRunnerStateLock(lockPath, staleMs = 60000) {
|
|
2151
|
+
const normalizedPath = String(lockPath || "").trim();
|
|
2152
|
+
if (!normalizedPath) {
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
try {
|
|
2156
|
+
const stats = fs.statSync(normalizedPath);
|
|
2157
|
+
const ageMs = Date.now() - Number(stats.mtimeMs || 0);
|
|
2158
|
+
if (Number.isFinite(ageMs) && ageMs >= staleMs) {
|
|
2159
|
+
fs.rmSync(normalizedPath, { force: true });
|
|
2160
|
+
return true;
|
|
2161
|
+
}
|
|
2162
|
+
} catch {}
|
|
2163
|
+
return false;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function waitForBotRunnerStateLockRelease(filePath, timeoutMs = 5000, staleMs = 60000) {
|
|
2167
|
+
const lockPath = botRunnerStateLockFilePath(filePath);
|
|
2168
|
+
if (!lockPath) {
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
const deadlineMs = Date.now() + Math.max(0, Number(timeoutMs) || 0);
|
|
2172
|
+
while (fs.existsSync(lockPath)) {
|
|
2173
|
+
tryReleaseStaleBotRunnerStateLock(lockPath, staleMs);
|
|
2174
|
+
if (!fs.existsSync(lockPath)) {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
if (Date.now() >= deadlineMs) {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
sleepSyncMs(25);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
function withBotRunnerStateFileLock(filePath, callback, timeoutMs = 5000, staleMs = 60000) {
|
|
2185
|
+
const lockPath = botRunnerStateLockFilePath(filePath);
|
|
2186
|
+
if (!lockPath) {
|
|
2187
|
+
return callback();
|
|
2188
|
+
}
|
|
2189
|
+
const deadlineMs = Date.now() + Math.max(0, Number(timeoutMs) || 0);
|
|
2190
|
+
let lockFD = null;
|
|
2191
|
+
while (lockFD === null) {
|
|
2192
|
+
try {
|
|
2193
|
+
lockFD = fs.openSync(lockPath, "wx");
|
|
2194
|
+
fs.writeFileSync(lockFD, `${process.pid} ${new Date().toISOString()}\n`, "utf8");
|
|
2195
|
+
break;
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
const errorCode = String(error?.code || "").trim().toUpperCase();
|
|
2198
|
+
if (!["EEXIST", "EPERM", "EBUSY"].includes(errorCode)) {
|
|
2199
|
+
throw error;
|
|
2200
|
+
}
|
|
2201
|
+
tryReleaseStaleBotRunnerStateLock(lockPath, staleMs);
|
|
2202
|
+
if (Date.now() >= deadlineMs) {
|
|
2203
|
+
throw error;
|
|
2204
|
+
}
|
|
2205
|
+
sleepSyncMs(25);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
try {
|
|
2209
|
+
return callback();
|
|
2210
|
+
} finally {
|
|
2211
|
+
try {
|
|
2212
|
+
if (lockFD !== null) {
|
|
2213
|
+
fs.closeSync(lockFD);
|
|
2214
|
+
}
|
|
2215
|
+
} catch {}
|
|
2216
|
+
try {
|
|
2217
|
+
if (fs.existsSync(lockPath)) {
|
|
2218
|
+
fs.rmSync(lockPath, { force: true });
|
|
2219
|
+
}
|
|
2220
|
+
} catch {}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2132
2224
|
function writeTextFileAtomic(filePath, text) {
|
|
2133
2225
|
const normalizedPath = String(filePath || "").trim();
|
|
2134
2226
|
if (!normalizedPath) {
|
|
@@ -2141,14 +2233,12 @@ function writeTextFileAtomic(filePath, text) {
|
|
|
2141
2233
|
`.${path.basename(normalizedPath)}.${process.pid}.${Date.now()}.tmp`,
|
|
2142
2234
|
);
|
|
2143
2235
|
fs.writeFileSync(tempPath, text, "utf8");
|
|
2144
|
-
const renameRetryDelaysMs = [0, 20, 50, 100, 200];
|
|
2145
|
-
|
|
2146
|
-
const sleepArray = new Int32Array(sleepBuffer);
|
|
2147
|
-
let lastRenameError = null;
|
|
2236
|
+
const renameRetryDelaysMs = [0, 20, 50, 100, 200];
|
|
2237
|
+
let lastRenameError = null;
|
|
2148
2238
|
try {
|
|
2149
2239
|
for (const delayMs of renameRetryDelaysMs) {
|
|
2150
2240
|
if (delayMs > 0) {
|
|
2151
|
-
|
|
2241
|
+
sleepSyncMs(delayMs);
|
|
2152
2242
|
}
|
|
2153
2243
|
try {
|
|
2154
2244
|
fs.renameSync(tempPath, normalizedPath);
|
|
@@ -2190,186 +2280,188 @@ function writeTextFileAtomic(filePath, text) {
|
|
|
2190
2280
|
}
|
|
2191
2281
|
}
|
|
2192
2282
|
|
|
2193
|
-
function saveBotRunnerState(nextState) {
|
|
2194
|
-
const filePath = botRunnerStateFilePath();
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
const
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
const
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2283
|
+
function saveBotRunnerState(nextState) {
|
|
2284
|
+
const filePath = botRunnerStateFilePath();
|
|
2285
|
+
return withBotRunnerStateFileLock(filePath, () => {
|
|
2286
|
+
let current = {};
|
|
2287
|
+
try {
|
|
2288
|
+
current = safeObject(tryJsonParse(fs.readFileSync(filePath, "utf8")));
|
|
2289
|
+
} catch {}
|
|
2290
|
+
const stateEntryTimestampMs = (...values) => {
|
|
2291
|
+
for (const value of values) {
|
|
2292
|
+
const ms = Date.parse(String(value || "").trim());
|
|
2293
|
+
if (Number.isFinite(ms)) {
|
|
2294
|
+
return ms;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return 0;
|
|
2298
|
+
};
|
|
2299
|
+
const mergeRunnerStateRoutes = (currentRoutesRaw, nextRoutesRaw) => {
|
|
2300
|
+
const currentRoutes = safeObject(currentRoutesRaw);
|
|
2301
|
+
const nextRoutes = safeObject(nextRoutesRaw);
|
|
2302
|
+
const merged = {
|
|
2303
|
+
...currentRoutes,
|
|
2304
|
+
};
|
|
2305
|
+
const mergeConversationSessions = (currentSessionsRaw, nextSessionsRaw) => {
|
|
2306
|
+
const currentSessions = safeObject(currentSessionsRaw);
|
|
2307
|
+
const nextSessions = safeObject(nextSessionsRaw);
|
|
2308
|
+
const mergedSessions = {
|
|
2309
|
+
...currentSessions,
|
|
2310
|
+
};
|
|
2311
|
+
for (const [conversationID, nextSessionRaw] of Object.entries(nextSessions)) {
|
|
2312
|
+
const currentSession = safeObject(currentSessions[conversationID]);
|
|
2313
|
+
const nextSession = safeObject(nextSessionRaw);
|
|
2314
|
+
if (!Object.keys(currentSession).length) {
|
|
2315
|
+
mergedSessions[conversationID] = nextSession;
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
const currentMs = stateEntryTimestampMs(
|
|
2319
|
+
currentSession.updated_at,
|
|
2320
|
+
currentSession.last_activity_at,
|
|
2321
|
+
currentSession.closed_at,
|
|
2322
|
+
currentSession.started_at,
|
|
2323
|
+
currentSession.expires_at,
|
|
2324
|
+
);
|
|
2325
|
+
const nextMs = stateEntryTimestampMs(
|
|
2326
|
+
nextSession.updated_at,
|
|
2327
|
+
nextSession.last_activity_at,
|
|
2328
|
+
nextSession.closed_at,
|
|
2329
|
+
nextSession.started_at,
|
|
2330
|
+
nextSession.expires_at,
|
|
2331
|
+
);
|
|
2332
|
+
mergedSessions[conversationID] = nextMs >= currentMs
|
|
2333
|
+
? {
|
|
2334
|
+
...currentSession,
|
|
2335
|
+
...nextSession,
|
|
2336
|
+
}
|
|
2337
|
+
: {
|
|
2338
|
+
...nextSession,
|
|
2339
|
+
...currentSession,
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
return mergedSessions;
|
|
2343
|
+
};
|
|
2344
|
+
for (const [routeKey, nextRouteRaw] of Object.entries(nextRoutes)) {
|
|
2345
|
+
const currentRoute = safeObject(currentRoutes[routeKey]);
|
|
2346
|
+
const nextRoute = safeObject(nextRouteRaw);
|
|
2347
|
+
if (!Object.keys(currentRoute).length) {
|
|
2348
|
+
merged[routeKey] = nextRoute;
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2351
|
+
const preferredRoute = prefersRunnerStateRecord(nextRoute, currentRoute) ? nextRoute : currentRoute;
|
|
2352
|
+
const fallbackRoute = preferredRoute === nextRoute ? currentRoute : nextRoute;
|
|
2353
|
+
merged[routeKey] = cleanupRunnerStateRecord({
|
|
2354
|
+
...mergeRunnerStateRecords(preferredRoute, fallbackRoute),
|
|
2355
|
+
conversation_sessions: mergeConversationSessions(
|
|
2356
|
+
currentRoute.conversation_sessions,
|
|
2357
|
+
nextRoute.conversation_sessions,
|
|
2358
|
+
),
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
return merged;
|
|
2362
|
+
};
|
|
2363
|
+
const mergeRunnerStateRequests = (currentRequestsRaw, nextRequestsRaw) => {
|
|
2364
|
+
const currentRequests = normalizeBotRunnerRequests(currentRequestsRaw);
|
|
2365
|
+
const nextRequests = normalizeBotRunnerRequests(nextRequestsRaw);
|
|
2366
|
+
const merged = {
|
|
2367
|
+
...currentRequests,
|
|
2368
|
+
};
|
|
2369
|
+
for (const [requestKey, nextRequestRaw] of Object.entries(nextRequests)) {
|
|
2370
|
+
const currentRequest = safeObject(currentRequests[requestKey]);
|
|
2371
|
+
const nextRequest = safeObject(nextRequestRaw);
|
|
2372
|
+
const currentMs = stateEntryTimestampMs(
|
|
2373
|
+
currentRequest.updated_at,
|
|
2374
|
+
currentRequest.completed_at,
|
|
2375
|
+
currentRequest.closed_at,
|
|
2376
|
+
currentRequest.claimed_at,
|
|
2377
|
+
);
|
|
2378
|
+
const nextMs = stateEntryTimestampMs(
|
|
2379
|
+
nextRequest.updated_at,
|
|
2380
|
+
nextRequest.completed_at,
|
|
2381
|
+
nextRequest.closed_at,
|
|
2382
|
+
nextRequest.claimed_at,
|
|
2383
|
+
);
|
|
2384
|
+
if (!Object.keys(currentRequest).length) {
|
|
2385
|
+
merged[requestKey] = nextRequest;
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
merged[requestKey] = nextMs >= currentMs
|
|
2389
|
+
? {
|
|
2390
|
+
...currentRequest,
|
|
2391
|
+
...nextRequest,
|
|
2392
|
+
}
|
|
2393
|
+
: currentRequest;
|
|
2394
|
+
}
|
|
2395
|
+
return normalizeBotRunnerRequests(merged);
|
|
2396
|
+
};
|
|
2397
|
+
const mergeRunnerStateExcludedComments = (currentExcludedRaw, nextExcludedRaw) => {
|
|
2398
|
+
const currentExcluded = normalizeBotRunnerExcludedComments(currentExcludedRaw);
|
|
2399
|
+
const nextExcluded = normalizeBotRunnerExcludedComments(nextExcludedRaw);
|
|
2400
|
+
const merged = {
|
|
2401
|
+
...currentExcluded,
|
|
2402
|
+
};
|
|
2403
|
+
for (const [commentID, nextEntryRaw] of Object.entries(nextExcluded)) {
|
|
2404
|
+
const currentEntry = safeObject(currentExcluded[commentID]);
|
|
2405
|
+
const nextEntry = safeObject(nextEntryRaw);
|
|
2406
|
+
const currentMs = stateEntryTimestampMs(currentEntry.excluded_at);
|
|
2407
|
+
const nextMs = stateEntryTimestampMs(nextEntry.excluded_at);
|
|
2408
|
+
if (!Object.keys(currentEntry).length || nextMs >= currentMs) {
|
|
2409
|
+
merged[commentID] = {
|
|
2410
|
+
...currentEntry,
|
|
2411
|
+
...nextEntry,
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return normalizeBotRunnerExcludedComments(merged);
|
|
2416
|
+
};
|
|
2417
|
+
const mergeRunnerStateConsumedComments = (currentConsumedRaw, nextConsumedRaw) => {
|
|
2418
|
+
const currentConsumed = normalizeBotRunnerConsumedComments(currentConsumedRaw);
|
|
2419
|
+
const nextConsumed = normalizeBotRunnerConsumedComments(nextConsumedRaw);
|
|
2420
|
+
const merged = {
|
|
2421
|
+
...currentConsumed,
|
|
2422
|
+
};
|
|
2423
|
+
for (const [commentID, nextEntryRaw] of Object.entries(nextConsumed)) {
|
|
2424
|
+
const currentEntry = safeObject(currentConsumed[commentID]);
|
|
2425
|
+
const nextEntry = safeObject(nextEntryRaw);
|
|
2426
|
+
const currentMs = stateEntryTimestampMs(currentEntry.consumed_at);
|
|
2427
|
+
const nextMs = stateEntryTimestampMs(nextEntry.consumed_at);
|
|
2428
|
+
if (!Object.keys(currentEntry).length || nextMs >= currentMs) {
|
|
2429
|
+
merged[commentID] = {
|
|
2430
|
+
...currentEntry,
|
|
2431
|
+
...nextEntry,
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return normalizeBotRunnerConsumedComments(merged);
|
|
2436
|
+
};
|
|
2437
|
+
const payload = {
|
|
2438
|
+
version: 1,
|
|
2439
|
+
updated_at: new Date().toISOString(),
|
|
2440
|
+
routes: mergeRunnerStateRoutes(
|
|
2441
|
+
current.routes,
|
|
2442
|
+
nextState?.routes ?? current.routes,
|
|
2443
|
+
),
|
|
2444
|
+
shared_inboxes: {
|
|
2445
|
+
...safeObject(current.shared_inboxes ?? current.sharedInboxes),
|
|
2446
|
+
...safeObject(nextState?.sharedInboxes ?? nextState?.shared_inboxes),
|
|
2447
|
+
},
|
|
2448
|
+
excluded_comments: mergeRunnerStateExcludedComments(
|
|
2449
|
+
current.excluded_comments ?? current.excludedComments,
|
|
2450
|
+
nextState?.excludedComments ?? nextState?.excluded_comments ?? current.excluded_comments ?? current.excludedComments,
|
|
2451
|
+
),
|
|
2452
|
+
requests: mergeRunnerStateRequests(
|
|
2453
|
+
current.requests,
|
|
2454
|
+
nextState?.requests ?? current.requests,
|
|
2455
|
+
),
|
|
2456
|
+
consumed_comments: mergeRunnerStateConsumedComments(
|
|
2457
|
+
current.consumed_comments ?? current.consumedComments,
|
|
2458
|
+
nextState?.consumedComments ?? nextState?.consumed_comments ?? current.consumed_comments ?? current.consumedComments,
|
|
2459
|
+
),
|
|
2460
|
+
};
|
|
2461
|
+
writeTextFileAtomic(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
2462
|
+
return filePath;
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2373
2465
|
|
|
2374
2466
|
function normalizeBotRunnerExcludedComments(rawExcluded, nowMs = Date.now()) {
|
|
2375
2467
|
const normalized = {};
|
|
@@ -2552,6 +2644,42 @@ function normalizeRunnerReplyChainContext(rawContext) {
|
|
|
2552
2644
|
return normalized;
|
|
2553
2645
|
}
|
|
2554
2646
|
|
|
2647
|
+
function findRunnerRouteLocalInboundEnvelope(routeStateRaw, parsedArchiveRaw) {
|
|
2648
|
+
const routeState = safeObject(routeStateRaw);
|
|
2649
|
+
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
2650
|
+
const chatID = String(parsedArchive.chatID || parsedArchive.chatId || "").trim();
|
|
2651
|
+
const messageID = intFromRawAllowZero(parsedArchive.messageID, 0);
|
|
2652
|
+
if (!chatID || !(messageID > 0)) {
|
|
2653
|
+
return {};
|
|
2654
|
+
}
|
|
2655
|
+
return findRecentTelegramMessageEnvelope(routeState.recent_local_inbound_envelopes, {
|
|
2656
|
+
chatID,
|
|
2657
|
+
messageID,
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
function buildRunnerSourceMessageEnvelope({
|
|
2662
|
+
routeState = {},
|
|
2663
|
+
routeKey = "",
|
|
2664
|
+
normalizedRoute = null,
|
|
2665
|
+
parsedArchive = null,
|
|
2666
|
+
}) {
|
|
2667
|
+
const localEnvelope = findRunnerRouteLocalInboundEnvelope(routeState, parsedArchive);
|
|
2668
|
+
if (String(localEnvelope.source_origin || "").trim().toLowerCase() === "local_telegram_inbound") {
|
|
2669
|
+
return localEnvelope;
|
|
2670
|
+
}
|
|
2671
|
+
const fallbackBotSelector = normalizeTelegramMentionUsername(
|
|
2672
|
+
normalizedRoute?.botName
|
|
2673
|
+
|| normalizedRoute?.serverBotName
|
|
2674
|
+
|| "",
|
|
2675
|
+
);
|
|
2676
|
+
return buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsedArchive, {
|
|
2677
|
+
source_origin: "archive_reconstructed",
|
|
2678
|
+
source_route_key: String(routeKey || "").trim(),
|
|
2679
|
+
source_bot_username: fallbackBotSelector,
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2555
2683
|
function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
2556
2684
|
const normalized = {};
|
|
2557
2685
|
for (const [requestKeyRaw, entryRaw] of Object.entries(safeObject(rawRequests))) {
|
|
@@ -2586,6 +2714,7 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2586
2714
|
body: entry.source_message_body || entry.sourceMessageBody,
|
|
2587
2715
|
sender: "human",
|
|
2588
2716
|
sender_is_bot: false,
|
|
2717
|
+
source_origin: "archive_reconstructed",
|
|
2589
2718
|
}
|
|
2590
2719
|
: {}),
|
|
2591
2720
|
),
|
|
@@ -2728,30 +2857,12 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2728
2857
|
last_reply_message_envelope: normalizeRunnerTelegramMessageEnvelope(
|
|
2729
2858
|
entry.last_reply_message_envelope
|
|
2730
2859
|
|| entry.lastReplyMessageEnvelope
|
|
2731
|
-
||
|
|
2732
|
-
? buildTelegramBotReplyEnvelope({
|
|
2733
|
-
chatID: entry.chat_id || entry.chatID,
|
|
2734
|
-
messageID: entry.last_reply_message_id || entry.lastReplyMessageID,
|
|
2735
|
-
messageThreadID: entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID,
|
|
2736
|
-
replyToMessageID: entry.last_reply_to_message_id || entry.lastReplyToMessageID,
|
|
2737
|
-
senderUsername: entry.conversation_summary_bot || ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)[0] || "",
|
|
2738
|
-
body: entry.followup_ai_reply_preview || entry.followupAiReplyPreview || entry.ai_reply_preview || entry.aiReplyPreview,
|
|
2739
|
-
})
|
|
2740
|
-
: {}),
|
|
2860
|
+
|| {},
|
|
2741
2861
|
),
|
|
2742
2862
|
attempted_delivery_envelope: normalizeRunnerTelegramMessageEnvelope(
|
|
2743
2863
|
entry.attempted_delivery_envelope
|
|
2744
2864
|
|| entry.attemptedDeliveryEnvelope
|
|
2745
|
-
||
|
|
2746
|
-
|| intFromRawAllowZero(entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID, 0) > 0)
|
|
2747
|
-
? buildTelegramBotReplyEnvelope({
|
|
2748
|
-
chatID: entry.chat_id || entry.chatID,
|
|
2749
|
-
messageThreadID: entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID,
|
|
2750
|
-
replyToMessageID: entry.last_reply_to_message_id || entry.lastReplyToMessageID,
|
|
2751
|
-
senderUsername: entry.conversation_summary_bot || ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)[0] || "",
|
|
2752
|
-
body: entry.followup_ai_reply_preview || entry.followupAiReplyPreview || entry.ai_reply_preview || entry.aiReplyPreview,
|
|
2753
|
-
})
|
|
2754
|
-
: {}),
|
|
2865
|
+
|| {},
|
|
2755
2866
|
),
|
|
2756
2867
|
updated_at: updatedAt || new Date(nowMs).toISOString(),
|
|
2757
2868
|
};
|
|
@@ -3836,20 +3947,10 @@ function buildRunnerReplyChainSnapshotFromRequestReply(requestRaw) {
|
|
|
3836
3947
|
const explicitEnvelope = normalizeRunnerTelegramMessageEnvelope(
|
|
3837
3948
|
request.last_reply_message_envelope || request.lastReplyMessageEnvelope,
|
|
3838
3949
|
);
|
|
3839
|
-
const fallbackEnvelope = intFromRawAllowZero(request.last_reply_message_id, 0) > 0
|
|
3840
|
-
? buildTelegramBotReplyEnvelope({
|
|
3841
|
-
chatID: request.chat_id,
|
|
3842
|
-
messageID: request.last_reply_message_id,
|
|
3843
|
-
messageThreadID: request.last_reply_message_thread_id,
|
|
3844
|
-
replyToMessageID: request.last_reply_to_message_id || request.last_source_message_id || request.source_message_id,
|
|
3845
|
-
senderUsername: request.conversation_summary_bot || ensureArray(request.selected_bot_usernames)[0] || "",
|
|
3846
|
-
body: preview,
|
|
3847
|
-
})
|
|
3848
|
-
: null;
|
|
3849
3950
|
return buildRunnerReplyChainSnapshotFromMessageEnvelope(
|
|
3850
3951
|
explicitEnvelope && intFromRawAllowZero(explicitEnvelope.message_id, 0) > 0
|
|
3851
3952
|
? explicitEnvelope
|
|
3852
|
-
:
|
|
3953
|
+
: null,
|
|
3853
3954
|
{
|
|
3854
3955
|
speaker_type: "bot",
|
|
3855
3956
|
speaker_label: firstNonEmptyString([
|
|
@@ -4346,9 +4447,10 @@ async function claimRunnerRequestForHumanComment({
|
|
|
4346
4447
|
reason: "non_human_comment_cannot_create_request",
|
|
4347
4448
|
};
|
|
4348
4449
|
}
|
|
4349
|
-
const currentState = loadBotRunnerState();
|
|
4350
|
-
const
|
|
4351
|
-
|
|
4450
|
+
const currentState = loadBotRunnerState();
|
|
4451
|
+
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
4452
|
+
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
4453
|
+
state: currentState,
|
|
4352
4454
|
normalizedRoute,
|
|
4353
4455
|
selectedRecord,
|
|
4354
4456
|
runtime,
|
|
@@ -4468,14 +4570,19 @@ async function claimRunnerRequestForHumanComment({
|
|
|
4468
4570
|
};
|
|
4469
4571
|
}
|
|
4470
4572
|
const nowISO = new Date().toISOString();
|
|
4471
|
-
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
4573
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
4472
4574
|
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
4473
4575
|
provider: String(normalizedRoute?.provider || "").trim(),
|
|
4474
4576
|
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
4475
4577
|
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
4476
4578
|
source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
|
|
4477
4579
|
source_message_body: String(parsed.body || "").trim(),
|
|
4478
|
-
source_message_envelope:
|
|
4580
|
+
source_message_envelope: buildRunnerSourceMessageEnvelope({
|
|
4581
|
+
routeState: currentRouteState,
|
|
4582
|
+
routeKey,
|
|
4583
|
+
normalizedRoute,
|
|
4584
|
+
parsedArchive: parsed,
|
|
4585
|
+
}),
|
|
4479
4586
|
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
4480
4587
|
root_comment_kind: commentKind,
|
|
4481
4588
|
conversation_id: resolvedConversationID,
|
|
@@ -5217,15 +5324,17 @@ function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, requestR
|
|
|
5217
5324
|
setFollowupStringPatch("followup_archive_status", ["last_followup_archive_status"]);
|
|
5218
5325
|
setFollowupStringPatch("followup_transport_error", ["last_followup_transport_error"]);
|
|
5219
5326
|
setFollowupStringPatch("followup_archive_error", ["last_followup_archive_error"]);
|
|
5327
|
+
const routeFollowupDeliveryStatus = String(routeState.last_followup_delivery_status || "").trim().toLowerCase();
|
|
5328
|
+
const routeFollowupDelivered = ["delivered", "dry_run"].includes(routeFollowupDeliveryStatus);
|
|
5220
5329
|
if (!Object.keys(safeObject(request.source_message_envelope)).length) {
|
|
5221
5330
|
const recoveredSourceEnvelope = normalizeRunnerTelegramMessageEnvelope(routeState.last_followup_source_message_envelope);
|
|
5222
5331
|
if (Object.keys(recoveredSourceEnvelope).length) {
|
|
5223
5332
|
patch.source_message_envelope = recoveredSourceEnvelope;
|
|
5224
5333
|
}
|
|
5225
5334
|
}
|
|
5226
|
-
if (!Object.keys(safeObject(request.last_reply_message_envelope)).length) {
|
|
5335
|
+
if (!Object.keys(safeObject(request.last_reply_message_envelope)).length && routeFollowupDelivered) {
|
|
5227
5336
|
const recoveredReplyEnvelope = normalizeRunnerTelegramMessageEnvelope(routeState.last_followup_last_reply_message_envelope);
|
|
5228
|
-
if (
|
|
5337
|
+
if (intFromRawAllowZero(recoveredReplyEnvelope.message_id, 0) > 0) {
|
|
5229
5338
|
patch.last_reply_message_envelope = recoveredReplyEnvelope;
|
|
5230
5339
|
}
|
|
5231
5340
|
}
|
|
@@ -5592,6 +5701,9 @@ function markRunnerRequestLifecycle({
|
|
|
5592
5701
|
senderUsername: normalizedCurrentBotSelector,
|
|
5593
5702
|
body: aiReplyPreview,
|
|
5594
5703
|
});
|
|
5704
|
+
const normalizedDeliveryStatus = String(deliveryStatus || "").trim().toLowerCase();
|
|
5705
|
+
const persistSuccessfulReplyEnvelope = ["delivered", "dry_run"].includes(normalizedDeliveryStatus)
|
|
5706
|
+
&& intFromRawAllowZero(lastReplyMessageEnvelope.message_id, 0) > 0;
|
|
5595
5707
|
const attemptedDeliveryEnvelope = buildTelegramBotReplyEnvelope({
|
|
5596
5708
|
sourceEnvelope: sourceMessageEnvelope,
|
|
5597
5709
|
chatID: existing.chat_id,
|
|
@@ -5934,11 +6046,11 @@ function markRunnerRequestLifecycle({
|
|
|
5934
6046
|
? transportError || existing.followup_transport_error || ""
|
|
5935
6047
|
: existing.followup_transport_error || "",
|
|
5936
6048
|
).trim(),
|
|
5937
|
-
followup_archive_error: String(
|
|
5938
|
-
isFollowupComment
|
|
5939
|
-
? archiveError || existing.followup_archive_error || ""
|
|
5940
|
-
: existing.followup_archive_error || "",
|
|
5941
|
-
).trim(),
|
|
6049
|
+
followup_archive_error: String(
|
|
6050
|
+
isFollowupComment
|
|
6051
|
+
? archiveError || existing.followup_archive_error || ""
|
|
6052
|
+
: existing.followup_archive_error || "",
|
|
6053
|
+
).trim(),
|
|
5942
6054
|
normalized_intent: nextNormalizedIntent,
|
|
5943
6055
|
status: nextStatus,
|
|
5944
6056
|
started_at: firstNonEmptyString([existing.started_at, nowISO]),
|
|
@@ -5964,7 +6076,7 @@ function markRunnerRequestLifecycle({
|
|
|
5964
6076
|
last_reply_message_id: intFromRawAllowZero(lastReplyMessageID, 0) || existing.last_reply_message_id,
|
|
5965
6077
|
last_reply_message_thread_id: intFromRawAllowZero(lastReplyMessageThreadID, 0) || existing.last_reply_message_thread_id,
|
|
5966
6078
|
last_reply_to_message_id: intFromRawAllowZero(replyToMessageID, 0) || existing.last_reply_to_message_id,
|
|
5967
|
-
last_reply_message_envelope:
|
|
6079
|
+
last_reply_message_envelope: persistSuccessfulReplyEnvelope
|
|
5968
6080
|
? lastReplyMessageEnvelope
|
|
5969
6081
|
: safeObject(existing.last_reply_message_envelope),
|
|
5970
6082
|
attempted_delivery_envelope: shouldRefreshAttemptedDeliveryEnvelope
|
|
@@ -7485,11 +7597,11 @@ function parseArchivedChatComment(rawBody) {
|
|
|
7485
7597
|
.filter(Boolean),
|
|
7486
7598
|
}
|
|
7487
7599
|
: null;
|
|
7488
|
-
return {
|
|
7489
|
-
kind,
|
|
7490
|
-
header,
|
|
7491
|
-
metadata,
|
|
7492
|
-
body,
|
|
7600
|
+
return {
|
|
7601
|
+
kind,
|
|
7602
|
+
header,
|
|
7603
|
+
metadata,
|
|
7604
|
+
body,
|
|
7493
7605
|
chatID: String(metadata.chat_id || "").trim(),
|
|
7494
7606
|
chatType: String(metadata.chat_type || "").trim().toLowerCase(),
|
|
7495
7607
|
messageID: intFromRawAllowZero(metadata.message_id, 0),
|
|
@@ -7502,8 +7614,11 @@ function parseArchivedChatComment(rawBody) {
|
|
|
7502
7614
|
.split(",")
|
|
7503
7615
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
7504
7616
|
.filter(Boolean),
|
|
7505
|
-
occurredAt: String(metadata.occurred_at || "").trim(),
|
|
7506
|
-
|
|
7617
|
+
occurredAt: String(metadata.occurred_at || "").trim(),
|
|
7618
|
+
sourceOrigin: String(metadata.source_origin || "").trim().toLowerCase(),
|
|
7619
|
+
sourceRouteKey: String(metadata.source_route_key || "").trim(),
|
|
7620
|
+
sourceBotUsername: normalizeTelegramMentionUsername(metadata.source_bot_username || ""),
|
|
7621
|
+
replyToMessageID: intFromRawAllowZero(metadata.reply_to_message_id, 0),
|
|
7507
7622
|
replyToSender: String(metadata.reply_to_sender || "").trim(),
|
|
7508
7623
|
replyToUsername: String(metadata.reply_to_telegram_username || "").trim(),
|
|
7509
7624
|
replyToSenderIsBot: boolFromRaw(metadata.reply_to_sender_is_bot, false),
|
|
@@ -7819,18 +7934,27 @@ function buildArchivedInboundMessageKey(chatID, messageID) {
|
|
|
7819
7934
|
return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
7820
7935
|
}
|
|
7821
7936
|
|
|
7822
|
-
function formatTelegramInboundArchiveComment(normalized) {
|
|
7823
|
-
const headerLines = [
|
|
7937
|
+
function formatTelegramInboundArchiveComment(normalized) {
|
|
7938
|
+
const headerLines = [
|
|
7824
7939
|
`[Telegram ${normalized.eventName === "telegram.message.updated" ? "edited" : "message"}]`,
|
|
7825
7940
|
`chat_id: ${normalized.chatID || "<missing>"}`,
|
|
7826
7941
|
`chat_type: ${normalized.chatType || "unknown"}`,
|
|
7827
7942
|
`message_id: ${normalized.messageID || "<missing>"}`,
|
|
7828
7943
|
`occurred_at: ${normalized.occurredAt || new Date().toISOString()}`,
|
|
7829
7944
|
`sender_id: ${normalized.fromID || "<missing>"}`,
|
|
7830
|
-
`sender: ${normalized.fromName || normalized.fromUsername || normalized.fromID || "unknown"}`,
|
|
7831
|
-
`sender_is_bot: ${normalized.fromIsBot ? "true" : "false"}`,
|
|
7832
|
-
];
|
|
7833
|
-
if (normalized.
|
|
7945
|
+
`sender: ${normalized.fromName || normalized.fromUsername || normalized.fromID || "unknown"}`,
|
|
7946
|
+
`sender_is_bot: ${normalized.fromIsBot ? "true" : "false"}`,
|
|
7947
|
+
];
|
|
7948
|
+
if (String(normalized.sourceOrigin || "").trim()) {
|
|
7949
|
+
headerLines.push(`source_origin: ${String(normalized.sourceOrigin || "").trim()}`);
|
|
7950
|
+
}
|
|
7951
|
+
if (String(normalized.sourceRouteKey || "").trim()) {
|
|
7952
|
+
headerLines.push(`source_route_key: ${String(normalized.sourceRouteKey || "").trim()}`);
|
|
7953
|
+
}
|
|
7954
|
+
if (String(normalized.sourceBotUsername || "").trim()) {
|
|
7955
|
+
headerLines.push(`source_bot_username: @${String(normalized.sourceBotUsername || "").trim().replace(/^@+/, "")}`);
|
|
7956
|
+
}
|
|
7957
|
+
if (normalized.fromUsername) {
|
|
7834
7958
|
headerLines.push(`telegram_username: @${normalized.fromUsername.replace(/^@+/, "")}`);
|
|
7835
7959
|
}
|
|
7836
7960
|
if (normalized.mentionUsernames.length > 0) {
|