viveworker 0.8.9 → 0.9.0
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 +14 -3
- package/package.json +1 -1
- package/plugins/viveworker-control-plane/skills/viveworker-control-plane/SKILL.md +7 -1
- package/scripts/lib/immutable-json-patch.mjs +142 -0
- package/scripts/lib/ipc-frame-worker.mjs +30 -0
- package/scripts/lib/ipc-message-projector.mjs +221 -0
- package/scripts/lib/length-prefixed-frame-decoder.mjs +128 -0
- package/scripts/lib/remote-pairing/bridge-relay-client.mjs +24 -0
- package/scripts/lib/remote-pairing/orchestrator.mjs +10 -0
- package/scripts/lib/work-items.mjs +179 -0
- package/scripts/mcp-server.mjs +240 -16
- package/scripts/moltbook-api.mjs +27 -0
- package/scripts/moltbook-cli.mjs +12 -4
- package/scripts/moltbook-scout-auto.sh +105 -37
- package/scripts/viveworker-bridge.mjs +483 -234
- package/scripts/viveworker-claude-hook.mjs +5 -1
- package/scripts/viveworker.mjs +5 -0
- package/skills/viveworker-control-plane/SKILL.md +7 -0
- package/templates/CLAUDE.viveworker.md +12 -1
- package/web/app.css +106 -0
- package/web/app.js +183 -28
- package/web/build-id.js +1 -1
- package/web/i18n.js +44 -10
- package/web/remote-pairing/api-router.js +356 -11
- package/web/remote-pairing/transport.js +7 -1
|
@@ -12,16 +12,20 @@ import process from "node:process";
|
|
|
12
12
|
import { createInterface } from "node:readline";
|
|
13
13
|
import { inspect } from "node:util";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { Worker } from "node:worker_threads";
|
|
15
16
|
import zlib from "node:zlib";
|
|
16
17
|
import webPush from "web-push";
|
|
17
18
|
import * as hazbaseAuth from "@hazbase/auth";
|
|
18
19
|
import { DEFAULT_LOCALE, SUPPORTED_LOCALES, localeDisplayName, normalizeLocale, resolveLocalePreference, t } from "../web/i18n.js";
|
|
19
20
|
import { APP_BUILD_ID as WEB_APP_BUILD_ID } from "../web/build-id.js";
|
|
20
21
|
import { generatePairingCredentials, shouldRotatePairing, upsertEnvText } from "./lib/pairing.mjs";
|
|
22
|
+
import { applyJsonPatches } from "./lib/immutable-json-patch.mjs";
|
|
23
|
+
import { LengthPrefixedFrameDecoder } from "./lib/length-prefixed-frame-decoder.mjs";
|
|
21
24
|
import { renderMarkdownHtml } from "./lib/markdown-render.mjs";
|
|
25
|
+
import { buildWorkItemResponse } from "./lib/work-items.mjs";
|
|
22
26
|
import { buildAgentCard, handleA2ARequest, resolveA2ATaskDecision, completeA2ATask, failA2ATask } from "./a2a-handler.mjs";
|
|
23
27
|
import { registerWithRelay, startRelayPolling, stopRelayPolling, postRelayResult, getRelayStatus, updatePublicTasksFlag } from "./a2a-relay-client.mjs";
|
|
24
|
-
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM, recordPuzzleAttempt, listInboxItems, updateInboxStatus, getMoltbookReplyQuotaState } from "./moltbook-api.mjs";
|
|
28
|
+
import { createMoltbookClient, readScoutState, writeScoutState, rollScoutDayIfNeeded, markPostSeen, recordComposeAttempt, writeDraft, readDraft, deleteDraft, listPendingDrafts, solveVerificationPuzzle, solvePuzzleWithLLM, recordPuzzleAttempt, listInboxItems, updateInboxStatus, getMoltbookReplyQuotaState, looksLikeProviderErrorText } from "./moltbook-api.mjs";
|
|
25
29
|
import { startRemotePairingRelay, DEFAULT_RELAY_URL } from "./lib/remote-pairing/orchestrator.mjs";
|
|
26
30
|
import { restartRemotePairingRelay, persistRemotePairingEnv, getRemotePairingStatus } from "./lib/remote-pairing/control.mjs";
|
|
27
31
|
import { appendRemotePairingAuditEvent, readRemotePairingAuditEvents } from "./lib/remote-pairing/audit.mjs";
|
|
@@ -58,6 +62,9 @@ const historyKinds = new Set(["completion", "assistant_final", "plan_ready", "ap
|
|
|
58
62
|
const timelineMessageKinds = new Set(["user_message", "assistant_commentary", "assistant_final"]);
|
|
59
63
|
const timelineKinds = new Set([...timelineMessageKinds, "activity_status", "ambient_suggestions", "approval", "plan", "choice", "plan_ready", "file_event", "command_event", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task", "a2a_task_result"]);
|
|
60
64
|
const SQLITE_COMPLETION_BATCH_SIZE = 200;
|
|
65
|
+
const SQLITE_QUERY_BUSY_TIMEOUT_MS = 1000;
|
|
66
|
+
const SQLITE_QUERY_MAX_ATTEMPTS = 3;
|
|
67
|
+
const SQLITE_QUERY_RETRY_BASE_DELAY_MS = 75;
|
|
61
68
|
const DEFAULT_DEVICE_TRUST_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
62
69
|
const MAX_PAIRED_DEVICES = 200;
|
|
63
70
|
const PAIRING_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1000;
|
|
@@ -73,6 +80,15 @@ const HAZBASE_METADATA_TIMEOUT_MS = 1500;
|
|
|
73
80
|
const NPM_VERSION_CHECK_TIMEOUT_MS = 2500;
|
|
74
81
|
const NPM_VERSION_CHECK_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
75
82
|
const TIMELINE_ACTIVITY_TTL_MS = 2 * 60 * 1000;
|
|
83
|
+
const STARTUP_MAINTENANCE_DELAY_MS = 15_000;
|
|
84
|
+
const STARTUP_MAINTENANCE_RETRY_MS = 5_000;
|
|
85
|
+
const INITIAL_SCAN_WARMUP_MS = 1_500;
|
|
86
|
+
const SCAN_COOPERATIVE_YIELD_EVERY = 16;
|
|
87
|
+
const APPLY_PATCH_LOOKBACK_BYTES = 8 * 1024 * 1024;
|
|
88
|
+
const APPLY_PATCH_LOOKUP_MISS_LIMIT = 256;
|
|
89
|
+
const IPC_LARGE_FRAME_LOG_BYTES = 8 * 1024 * 1024;
|
|
90
|
+
const ROLLOUT_LABEL_METADATA_MAX_LINES = 256;
|
|
91
|
+
const ROLLOUT_LABEL_METADATA_MAX_BYTES = 512 * 1024;
|
|
76
92
|
const PAYMENT_NETWORKS = {
|
|
77
93
|
base: { family: "evm", chainId: 8453, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base", releaseStatus: "comingSoon" },
|
|
78
94
|
"base-sepolia": { family: "evm", chainId: 84532, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base Sepolia" },
|
|
@@ -254,6 +270,7 @@ const runtime = {
|
|
|
254
270
|
sourceFile: "",
|
|
255
271
|
},
|
|
256
272
|
rolloutThreadLabels: new Map(),
|
|
273
|
+
rolloutLabelIndexInFlight: false,
|
|
257
274
|
rolloutThreadCwds: new Map(),
|
|
258
275
|
threadStates: new Map(),
|
|
259
276
|
threadOwnerClientIds: new Map(),
|
|
@@ -294,6 +311,7 @@ const runtime = {
|
|
|
294
311
|
timelineLiveScanReasons: new Set(),
|
|
295
312
|
timelineLiveClaudeFiles: new Set(),
|
|
296
313
|
timelineLiveRolloutFiles: new Set(),
|
|
314
|
+
initialScanComplete: false,
|
|
297
315
|
pairingAttemptsByRemoteAddress: new Map(),
|
|
298
316
|
ipcClient: null,
|
|
299
317
|
remotePairingHandle: null,
|
|
@@ -337,6 +355,11 @@ try {
|
|
|
337
355
|
await deleteDraft(draft.token).catch(() => {});
|
|
338
356
|
continue;
|
|
339
357
|
}
|
|
358
|
+
if (looksLikeProviderErrorText(draft.draftText)) {
|
|
359
|
+
await deleteDraft(draft.token).catch(() => {});
|
|
360
|
+
console.warn(`[moltbook-draft-provider-error] Dropped persisted draft ${draft.token}`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
340
363
|
runtime.moltbookDraftsByToken.set(draft.token, { ...draft, decisionWaiters: [] });
|
|
341
364
|
}
|
|
342
365
|
if (runtime.moltbookDraftsByToken.size) {
|
|
@@ -351,6 +374,12 @@ setInterval(async () => {
|
|
|
351
374
|
const now = Date.now();
|
|
352
375
|
for (const [token, draft] of runtime.moltbookDraftsByToken) {
|
|
353
376
|
if (draft.decision) continue;
|
|
377
|
+
if (looksLikeProviderErrorText(draft.draftText)) {
|
|
378
|
+
runtime.moltbookDraftsByToken.delete(token);
|
|
379
|
+
await deleteDraft(token).catch(() => {});
|
|
380
|
+
console.warn(`[moltbook-draft-provider-error] Dropped pending draft ${token}`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
354
383
|
const age = now - (draft.createdAtMs || 0);
|
|
355
384
|
if (age > (draft.ttlMs || 86400000)) {
|
|
356
385
|
runtime.moltbookDraftsByToken.delete(token);
|
|
@@ -423,9 +452,6 @@ state.recentHistoryItems = initialHistoryItems;
|
|
|
423
452
|
state.recentTimelineEntries = initialTimelineEntries;
|
|
424
453
|
const backfilledAmbientSuggestionsStateChanged = backfillAmbientSuggestionsState({ config, runtime, state });
|
|
425
454
|
const migratedRecentCodeEventsStateChanged = migrateRecentCodeEventsState({ config, runtime, state });
|
|
426
|
-
const restoredTimelineImagePathsStateChanged = await backfillPersistedTimelineImagePaths({ config, runtime, state });
|
|
427
|
-
const backfilledMoltbookInboxChanged = await backfillMoltbookInboxHistory({ config, runtime, state });
|
|
428
|
-
const recoveredMissingProviderStateChanged = await recoverMissingProviderStateFromBackup({ config, runtime, state });
|
|
429
455
|
runtime.historyFileState.offset = Number(state.historyFileOffset) || 0;
|
|
430
456
|
runtime.historyFileState.sourceFile = cleanText(state.historyFileSourceFile ?? "");
|
|
431
457
|
|
|
@@ -1803,11 +1829,6 @@ function unwrapShellCommand(commandText) {
|
|
|
1803
1829
|
.trim();
|
|
1804
1830
|
}
|
|
1805
1831
|
|
|
1806
|
-
function extractCommandLineFromFunctionOutput(outputText) {
|
|
1807
|
-
const match = String(outputText || "").match(/^Command:\s+(.+)$/mu);
|
|
1808
|
-
return unwrapShellCommand(match?.[1] || "");
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
1832
|
function parseToolArgumentsJson(value) {
|
|
1812
1833
|
if (isPlainObject(value)) {
|
|
1813
1834
|
return value;
|
|
@@ -1864,16 +1885,21 @@ function redactTimelineCommandText(commandText) {
|
|
|
1864
1885
|
.replace(/\b([A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)[A-Z0-9_]*=)(["']?)[^\s"']+\2/giu, "$1[redacted]");
|
|
1865
1886
|
}
|
|
1866
1887
|
|
|
1888
|
+
function escapeMarkdownCodeFenceBody(text) {
|
|
1889
|
+
return cleanText(text || "").replace(/```/gu, "\\`\\`\\`");
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1867
1892
|
function timelineCommandMessage(locale, { commandText = "", toolName = "", fileRefs = [] } = {}) {
|
|
1893
|
+
const safeCommandText = escapeMarkdownCodeFenceBody(redactTimelineCommandText(commandText));
|
|
1868
1894
|
const commandBlock = commandText
|
|
1869
|
-
? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${
|
|
1895
|
+
? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${safeCommandText}\n\`\`\``
|
|
1870
1896
|
: "";
|
|
1871
1897
|
const toolBlock = !commandBlock && toolName
|
|
1872
|
-
? `${t(locale, "server.message.toolLabel")}\n\`\`\`text\n${
|
|
1898
|
+
? `${t(locale, "server.message.toolLabel")}\n\`\`\`text\n${escapeMarkdownCodeFenceBody(toolName)}\n\`\`\``
|
|
1873
1899
|
: "";
|
|
1874
1900
|
const files = normalizeTimelineFileRefs(fileRefs);
|
|
1875
1901
|
const filesBlock = files.length
|
|
1876
|
-
? `${t(locale, "server.message.filesLabel")}\n\`\`\`text\n${files.join("\n")}\n\`\`\``
|
|
1902
|
+
? `${t(locale, "server.message.filesLabel")}\n\`\`\`text\n${files.map(escapeMarkdownCodeFenceBody).join("\n")}\n\`\`\``
|
|
1877
1903
|
: "";
|
|
1878
1904
|
return [commandBlock || toolBlock, filesBlock].filter(Boolean).join("\n\n");
|
|
1879
1905
|
}
|
|
@@ -2003,7 +2029,7 @@ function sedCommandPathArgs(tokens) {
|
|
|
2003
2029
|
function autoPilotApprovalMessage(locale, commandText) {
|
|
2004
2030
|
const prefix = t(locale, "server.message.autoPilotTrustedReadApproved");
|
|
2005
2031
|
const commandBlock = cleanText(commandText || "")
|
|
2006
|
-
? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${
|
|
2032
|
+
? `${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${escapeMarkdownCodeFenceBody(commandText)}\n\`\`\``
|
|
2007
2033
|
: "";
|
|
2008
2034
|
return [prefix, commandBlock].filter(Boolean).join("\n\n");
|
|
2009
2035
|
}
|
|
@@ -2162,6 +2188,7 @@ function rememberApplyPatchInput(fileState, payload, createdAtMs = Date.now()) {
|
|
|
2162
2188
|
cwd: cleanText(fileState.cwd || ""),
|
|
2163
2189
|
createdAtMs: Number(createdAtMs) || Date.now(),
|
|
2164
2190
|
});
|
|
2191
|
+
fileState.applyPatchLookupMisses?.delete(callId);
|
|
2165
2192
|
while (fileState.applyPatchInputsByCallId.size > 64) {
|
|
2166
2193
|
const oldestKey = fileState.applyPatchInputsByCallId.keys().next().value;
|
|
2167
2194
|
if (!oldestKey) {
|
|
@@ -2214,6 +2241,9 @@ async function findStoredApplyPatchInput({ fileState, callId, rolloutFilePath })
|
|
|
2214
2241
|
if (inMemory?.inputText) {
|
|
2215
2242
|
return inMemory;
|
|
2216
2243
|
}
|
|
2244
|
+
if (fileState?.applyPatchLookupMisses instanceof Set && fileState.applyPatchLookupMisses.has(normalizedCallId)) {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2217
2247
|
|
|
2218
2248
|
const normalizedRolloutFilePath = cleanText(rolloutFilePath || "");
|
|
2219
2249
|
if (!normalizedRolloutFilePath) {
|
|
@@ -2222,14 +2252,40 @@ async function findStoredApplyPatchInput({ fileState, callId, rolloutFilePath })
|
|
|
2222
2252
|
|
|
2223
2253
|
let content = "";
|
|
2224
2254
|
try {
|
|
2225
|
-
|
|
2255
|
+
const stat = await fs.stat(normalizedRolloutFilePath);
|
|
2256
|
+
const length = Math.min(stat.size, APPLY_PATCH_LOOKBACK_BYTES);
|
|
2257
|
+
const offset = Math.max(0, stat.size - length);
|
|
2258
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
2259
|
+
const handle = await fs.open(normalizedRolloutFilePath, "r");
|
|
2260
|
+
try {
|
|
2261
|
+
const { bytesRead } = await handle.read(buffer, 0, length, offset);
|
|
2262
|
+
content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
2263
|
+
} finally {
|
|
2264
|
+
await handle.close();
|
|
2265
|
+
}
|
|
2266
|
+
if (offset > 0) {
|
|
2267
|
+
const firstNewline = content.indexOf("\n");
|
|
2268
|
+
content = firstNewline >= 0 ? content.slice(firstNewline + 1) : "";
|
|
2269
|
+
}
|
|
2226
2270
|
} catch {
|
|
2227
2271
|
return null;
|
|
2228
2272
|
}
|
|
2229
2273
|
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2274
|
+
let cursor = content.length;
|
|
2275
|
+
let scannedLines = 0;
|
|
2276
|
+
while (cursor > 0) {
|
|
2277
|
+
let end = cursor;
|
|
2278
|
+
if (content.charCodeAt(end - 1) === 10) {
|
|
2279
|
+
end -= 1;
|
|
2280
|
+
}
|
|
2281
|
+
const previousNewline = content.lastIndexOf("\n", end - 1);
|
|
2282
|
+
const start = previousNewline + 1;
|
|
2283
|
+
const rawLine = content.slice(start, end);
|
|
2284
|
+
cursor = previousNewline >= 0 ? previousNewline : 0;
|
|
2285
|
+
scannedLines += 1;
|
|
2286
|
+
if (scannedLines % 128 === 0) {
|
|
2287
|
+
await yieldToEventLoop();
|
|
2288
|
+
}
|
|
2233
2289
|
if (!rawLine.trim()) {
|
|
2234
2290
|
continue;
|
|
2235
2291
|
}
|
|
@@ -2256,6 +2312,7 @@ async function findStoredApplyPatchInput({ fileState, callId, rolloutFilePath })
|
|
|
2256
2312
|
|
|
2257
2313
|
const inputText = normalizeTimelineDiffText(payload?.input ?? "");
|
|
2258
2314
|
if (!inputText) {
|
|
2315
|
+
rememberApplyPatchLookupMiss(fileState, normalizedCallId);
|
|
2259
2316
|
return null;
|
|
2260
2317
|
}
|
|
2261
2318
|
|
|
@@ -2268,12 +2325,29 @@ async function findStoredApplyPatchInput({ fileState, callId, rolloutFilePath })
|
|
|
2268
2325
|
fileState.applyPatchInputsByCallId = new Map();
|
|
2269
2326
|
}
|
|
2270
2327
|
fileState.applyPatchInputsByCallId.set(normalizedCallId, restored);
|
|
2328
|
+
fileState.applyPatchLookupMisses?.delete(normalizedCallId);
|
|
2271
2329
|
return restored;
|
|
2272
2330
|
}
|
|
2273
2331
|
|
|
2332
|
+
rememberApplyPatchLookupMiss(fileState, normalizedCallId);
|
|
2274
2333
|
return null;
|
|
2275
2334
|
}
|
|
2276
2335
|
|
|
2336
|
+
function rememberApplyPatchLookupMiss(fileState, callId) {
|
|
2337
|
+
if (!fileState || !callId) {
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
if (!(fileState.applyPatchLookupMisses instanceof Set)) {
|
|
2341
|
+
fileState.applyPatchLookupMisses = new Set();
|
|
2342
|
+
}
|
|
2343
|
+
fileState.applyPatchLookupMisses.add(callId);
|
|
2344
|
+
while (fileState.applyPatchLookupMisses.size > APPLY_PATCH_LOOKUP_MISS_LIMIT) {
|
|
2345
|
+
const oldestKey = fileState.applyPatchLookupMisses.values().next().value;
|
|
2346
|
+
if (!oldestKey) break;
|
|
2347
|
+
fileState.applyPatchLookupMisses.delete(oldestKey);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2277
2351
|
function diffPathForSide(fileRef, side) {
|
|
2278
2352
|
const normalizedFileRef = cleanText(fileRef || "");
|
|
2279
2353
|
if (!normalizedFileRef) {
|
|
@@ -3399,7 +3473,20 @@ function normalizeHistoryItems(rawItems, maxItems) {
|
|
|
3399
3473
|
|
|
3400
3474
|
const normalized = rawItems
|
|
3401
3475
|
.map(normalizeHistoryItem)
|
|
3402
|
-
.filter(Boolean)
|
|
3476
|
+
.filter(Boolean);
|
|
3477
|
+
return trimNormalizedHistoryItems(normalized, maxItems);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
function mergeNormalizedHistoryItems(rawNewItems, existingItems, maxItems) {
|
|
3481
|
+
const additions = (Array.isArray(rawNewItems) ? rawNewItems : [])
|
|
3482
|
+
.map(normalizeHistoryItem)
|
|
3483
|
+
.filter(Boolean);
|
|
3484
|
+
const existing = Array.isArray(existingItems) ? existingItems : [];
|
|
3485
|
+
return trimNormalizedHistoryItems([...additions, ...existing], maxItems);
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
function trimNormalizedHistoryItems(items, maxItems) {
|
|
3489
|
+
const normalized = [...items]
|
|
3403
3490
|
.sort((left, right) => Number(right.createdAtMs ?? 0) - Number(left.createdAtMs ?? 0));
|
|
3404
3491
|
const deduped = [];
|
|
3405
3492
|
const seen = new Set();
|
|
@@ -4792,11 +4879,11 @@ async function scanOnce({ config, runtime, state }) {
|
|
|
4792
4879
|
if (knownFilesChanged) {
|
|
4793
4880
|
runtime.rolloutThreadCwds = new Map();
|
|
4794
4881
|
}
|
|
4795
|
-
|
|
4796
|
-
dirty = refreshResolvedThreadLabels({ config, runtime, state }) || dirty;
|
|
4882
|
+
refreshRolloutThreadLabelIndexInBackground({ config, runtime, state });
|
|
4797
4883
|
}
|
|
4798
4884
|
|
|
4799
|
-
for (
|
|
4885
|
+
for (let index = 0; index < runtime.knownFiles.length; index += 1) {
|
|
4886
|
+
const filePath = runtime.knownFiles[index];
|
|
4800
4887
|
const changed = await processRolloutFile({
|
|
4801
4888
|
filePath,
|
|
4802
4889
|
config,
|
|
@@ -4805,6 +4892,9 @@ async function scanOnce({ config, runtime, state }) {
|
|
|
4805
4892
|
now,
|
|
4806
4893
|
});
|
|
4807
4894
|
dirty = dirty || changed;
|
|
4895
|
+
if ((index + 1) % 4 === 0) {
|
|
4896
|
+
await yieldToEventLoop();
|
|
4897
|
+
}
|
|
4808
4898
|
}
|
|
4809
4899
|
|
|
4810
4900
|
if (config.notifyCompletions || config.webUiEnabled) {
|
|
@@ -4828,10 +4918,14 @@ async function scanOnce({ config, runtime, state }) {
|
|
|
4828
4918
|
claudeSessionTitlesChanged = await refreshClaudeSessionTitles(runtime);
|
|
4829
4919
|
runtime.lastClaudeSessionTitleScanAt = now;
|
|
4830
4920
|
}
|
|
4831
|
-
for (
|
|
4921
|
+
for (let index = 0; index < runtime.claudeKnownFiles.length; index += 1) {
|
|
4922
|
+
const filePath = runtime.claudeKnownFiles[index];
|
|
4832
4923
|
const changed = await processClaudeTranscriptFile({ filePath, config, runtime, state, now });
|
|
4833
4924
|
claudeTranscriptChanged = claudeTranscriptChanged || changed;
|
|
4834
4925
|
dirty = dirty || changed;
|
|
4926
|
+
if ((index + 1) % 4 === 0) {
|
|
4927
|
+
await yieldToEventLoop();
|
|
4928
|
+
}
|
|
4835
4929
|
}
|
|
4836
4930
|
if (claudeTranscriptChanged || claudeSessionTitlesChanged) {
|
|
4837
4931
|
dirty = refreshResolvedThreadLabels({ config, runtime, state }) || dirty;
|
|
@@ -5168,7 +5262,11 @@ async function processRolloutFile({ filePath, config, runtime, state, now }) {
|
|
|
5168
5262
|
state.fileOffsets[filePath] = fileState.offset;
|
|
5169
5263
|
|
|
5170
5264
|
let dirty = true;
|
|
5171
|
-
for (
|
|
5265
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
5266
|
+
if (lineIndex > 0 && lineIndex % SCAN_COOPERATIVE_YIELD_EVERY === 0) {
|
|
5267
|
+
await yieldToEventLoop();
|
|
5268
|
+
}
|
|
5269
|
+
const rawLine = lines[lineIndex];
|
|
5172
5270
|
if (!rawLine.trim()) {
|
|
5173
5271
|
continue;
|
|
5174
5272
|
}
|
|
@@ -5581,7 +5679,11 @@ async function processClaudeTranscriptFile({ filePath, config, runtime, state, n
|
|
|
5581
5679
|
fileState.offset = newOffset;
|
|
5582
5680
|
let dirty = false;
|
|
5583
5681
|
|
|
5584
|
-
for (
|
|
5682
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
5683
|
+
if (lineIndex > 0 && lineIndex % SCAN_COOPERATIVE_YIELD_EVERY === 0) {
|
|
5684
|
+
await yieldToEventLoop();
|
|
5685
|
+
}
|
|
5686
|
+
const line = lines[lineIndex];
|
|
5585
5687
|
const trimmed = line.trim();
|
|
5586
5688
|
if (!trimmed) continue;
|
|
5587
5689
|
|
|
@@ -6172,7 +6274,11 @@ async function processHistoryTimelineFile({ config, runtime, state, now }) {
|
|
|
6172
6274
|
state.historyFileSourceFile = historyFile;
|
|
6173
6275
|
dirty = true;
|
|
6174
6276
|
|
|
6175
|
-
for (
|
|
6277
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
6278
|
+
if (lineIndex > 0 && lineIndex % SCAN_COOPERATIVE_YIELD_EVERY === 0) {
|
|
6279
|
+
await yieldToEventLoop();
|
|
6280
|
+
}
|
|
6281
|
+
const rawLine = lines[lineIndex];
|
|
6176
6282
|
if (!rawLine.trim()) {
|
|
6177
6283
|
continue;
|
|
6178
6284
|
}
|
|
@@ -6226,6 +6332,9 @@ async function backfillRecentTimelineEntryImages({ config, runtime, state }) {
|
|
|
6226
6332
|
const nextEntries = runtime.recentTimelineEntries.map((entry) => ({ ...entry }));
|
|
6227
6333
|
|
|
6228
6334
|
for (let index = 0; index < nextEntries.length; index += 1) {
|
|
6335
|
+
if (index > 0 && index % SCAN_COOPERATIVE_YIELD_EVERY === 0) {
|
|
6336
|
+
await yieldToEventLoop();
|
|
6337
|
+
}
|
|
6229
6338
|
const entry = nextEntries[index];
|
|
6230
6339
|
if (
|
|
6231
6340
|
cleanText(entry?.kind || "") !== "user_message" ||
|
|
@@ -6262,7 +6371,11 @@ async function backfillRecentTimelineEntryImages({ config, runtime, state }) {
|
|
|
6262
6371
|
async function backfillPersistedTimelineImagePaths({ config, runtime, state }) {
|
|
6263
6372
|
let changed = false;
|
|
6264
6373
|
const nextEntries = [];
|
|
6265
|
-
for (
|
|
6374
|
+
for (let index = 0; index < runtime.recentTimelineEntries.length; index += 1) {
|
|
6375
|
+
if (index > 0 && index % SCAN_COOPERATIVE_YIELD_EVERY === 0) {
|
|
6376
|
+
await yieldToEventLoop();
|
|
6377
|
+
}
|
|
6378
|
+
const entry = runtime.recentTimelineEntries[index];
|
|
6266
6379
|
const nextImagePaths = await normalizePersistedTimelineImagePaths({
|
|
6267
6380
|
config,
|
|
6268
6381
|
state,
|
|
@@ -6299,7 +6412,8 @@ async function backfillPersistedTimelineImagePaths({ config, runtime, state }) {
|
|
|
6299
6412
|
// watcher push for the same commentId replaces this entry cleanly rather than
|
|
6300
6413
|
// creating a duplicate.
|
|
6301
6414
|
async function backfillMoltbookInboxHistory({ config, runtime, state }) {
|
|
6302
|
-
|
|
6415
|
+
const additions = [];
|
|
6416
|
+
const knownStableIds = new Set(runtime.recentHistoryItems.map((entry) => cleanText(entry?.stableId || "")));
|
|
6303
6417
|
try {
|
|
6304
6418
|
const inboxItems = await listInboxItems();
|
|
6305
6419
|
for (const inbox of inboxItems) {
|
|
@@ -6309,7 +6423,7 @@ async function backfillMoltbookInboxHistory({ config, runtime, state }) {
|
|
|
6309
6423
|
if (!commentId) continue;
|
|
6310
6424
|
const sourceId = `comment:${commentId}`;
|
|
6311
6425
|
const stableId = `moltbook_reply:${sourceId}`;
|
|
6312
|
-
if (
|
|
6426
|
+
if (knownStableIds.has(stableId)) continue;
|
|
6313
6427
|
const token = historyToken(`moltbook:${sourceId}`);
|
|
6314
6428
|
const createdAtMs = Number(Date.parse(inbox.updatedAt || inbox.createdAt || "")) || Date.now();
|
|
6315
6429
|
const postTitle = cleanText(inbox.postTitle || "");
|
|
@@ -6319,36 +6433,73 @@ async function backfillMoltbookInboxHistory({ config, runtime, state }) {
|
|
|
6319
6433
|
const summary = (status === "replied" ? (replyText || contextText) : contextText).slice(0, 160);
|
|
6320
6434
|
const titlePrefix = status === "replied" ? "replied" : "skipped";
|
|
6321
6435
|
const title = postTitle ? `${titlePrefix} → ${postTitle}` : (status === "replied" ? "Moltbook reply" : "Moltbook skipped");
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
messageText,
|
|
6335
|
-
createdAtMs,
|
|
6336
|
-
readOnly: true,
|
|
6337
|
-
provider: "moltbook",
|
|
6338
|
-
},
|
|
6436
|
+
additions.push({
|
|
6437
|
+
stableId,
|
|
6438
|
+
token,
|
|
6439
|
+
kind: "moltbook_reply",
|
|
6440
|
+
threadId: "moltbook",
|
|
6441
|
+
threadLabel: postTitle || "Moltbook",
|
|
6442
|
+
title,
|
|
6443
|
+
summary,
|
|
6444
|
+
messageText,
|
|
6445
|
+
createdAtMs,
|
|
6446
|
+
readOnly: true,
|
|
6447
|
+
provider: "moltbook",
|
|
6339
6448
|
});
|
|
6340
|
-
|
|
6449
|
+
knownStableIds.add(stableId);
|
|
6341
6450
|
}
|
|
6342
6451
|
} catch (error) {
|
|
6343
6452
|
console.error(`[moltbook-inbox-backfill] ${error.message}`);
|
|
6344
6453
|
return false;
|
|
6345
6454
|
}
|
|
6455
|
+
if (additions.length === 0) {
|
|
6456
|
+
return false;
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
const nextItems = mergeNormalizedHistoryItems(
|
|
6460
|
+
additions,
|
|
6461
|
+
runtime.recentHistoryItems,
|
|
6462
|
+
config.maxHistoryItems
|
|
6463
|
+
);
|
|
6464
|
+
const changed = timelineProjectionChanged(nextItems, runtime.recentHistoryItems, [
|
|
6465
|
+
"stableId",
|
|
6466
|
+
"title",
|
|
6467
|
+
"createdAtMs",
|
|
6468
|
+
]);
|
|
6346
6469
|
if (changed) {
|
|
6470
|
+
runtime.recentHistoryItems = nextItems;
|
|
6471
|
+
state.recentHistoryItems = nextItems;
|
|
6347
6472
|
console.log(`[moltbook-inbox-backfill] Synthesized history entries for resolved inbox items`);
|
|
6348
6473
|
}
|
|
6349
6474
|
return changed;
|
|
6350
6475
|
}
|
|
6351
6476
|
|
|
6477
|
+
async function runDeferredStartupBackfills({ config, runtime, state }) {
|
|
6478
|
+
const startedAtMs = Date.now();
|
|
6479
|
+
let changed = false;
|
|
6480
|
+
const steps = [
|
|
6481
|
+
["timeline-images", () => backfillPersistedTimelineImagePaths({ config, runtime, state })],
|
|
6482
|
+
["moltbook-inbox", () => backfillMoltbookInboxHistory({ config, runtime, state })],
|
|
6483
|
+
["provider-recovery", () => recoverMissingProviderStateFromBackup({ config, runtime, state })],
|
|
6484
|
+
];
|
|
6485
|
+
|
|
6486
|
+
for (const [label, run] of steps) {
|
|
6487
|
+
try {
|
|
6488
|
+
changed = (await run()) || changed;
|
|
6489
|
+
} catch (error) {
|
|
6490
|
+
console.error(`[startup] ${label} backfill failed: ${error?.message || error}`);
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6494
|
+
if (changed && !runtime.stopping) {
|
|
6495
|
+
scheduleSaveState(config, state);
|
|
6496
|
+
}
|
|
6497
|
+
const durationMs = Date.now() - startedAtMs;
|
|
6498
|
+
if (durationMs >= 1_000) {
|
|
6499
|
+
console.log(`[startup] deferred backfills ready durationMs=${durationMs}`);
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6352
6503
|
async function backfillRecentTimelineEntryDiffs({ config, runtime, state }) {
|
|
6353
6504
|
const nextEntries = runtime.recentTimelineEntries.map((entry) => ({ ...entry }));
|
|
6354
6505
|
let changed = false;
|
|
@@ -7080,11 +7231,6 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
7080
7231
|
});
|
|
7081
7232
|
return [];
|
|
7082
7233
|
}
|
|
7083
|
-
const commandText = extractCommandLineFromFunctionOutput(payload.output ?? "");
|
|
7084
|
-
if (!commandText) {
|
|
7085
|
-
return [];
|
|
7086
|
-
}
|
|
7087
|
-
const classified = classifyTimelineCommand(commandText);
|
|
7088
7234
|
upsertTimelineActivity({
|
|
7089
7235
|
config,
|
|
7090
7236
|
runtime,
|
|
@@ -7096,19 +7242,7 @@ async function buildRolloutFileTimelineEntries({ config, record, fileState, runt
|
|
|
7096
7242
|
source: "codex-tool-output",
|
|
7097
7243
|
atMs: createdAtMs,
|
|
7098
7244
|
});
|
|
7099
|
-
return [
|
|
7100
|
-
buildToolTimelineEntry({
|
|
7101
|
-
provider: "codex",
|
|
7102
|
-
threadId,
|
|
7103
|
-
threadLabel,
|
|
7104
|
-
callId,
|
|
7105
|
-
createdAtMs,
|
|
7106
|
-
fileEventType: classified.fileEventType,
|
|
7107
|
-
commandText: classified.commandText || commandText,
|
|
7108
|
-
fileRefs: classified.fileRefs,
|
|
7109
|
-
cwd: fileState.cwd || "",
|
|
7110
|
-
}),
|
|
7111
|
-
].filter(Boolean);
|
|
7245
|
+
return [];
|
|
7112
7246
|
}
|
|
7113
7247
|
|
|
7114
7248
|
if (payloadType === "custom_tool_call_output") {
|
|
@@ -7428,9 +7562,34 @@ async function querySqliteCompletionRows({ logsDbFile, cursorId, minTsSec = 0 })
|
|
|
7428
7562
|
return runSqliteJsonQuery(logsDbFile, sql);
|
|
7429
7563
|
}
|
|
7430
7564
|
|
|
7565
|
+
function isRetryableSqliteQueryError(error) {
|
|
7566
|
+
const message = cleanText(error?.message ?? error).toLowerCase();
|
|
7567
|
+
return message.includes("database is locked") || message.includes("database is busy") || /sqlite_(?:busy|locked)/u.test(message);
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7570
|
+
function sqliteQueryRetryDelayMs(attempt) {
|
|
7571
|
+
return SQLITE_QUERY_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, Number(attempt) || 0);
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7431
7574
|
async function runSqliteJsonQuery(dbFile, sql) {
|
|
7575
|
+
let lastError = null;
|
|
7576
|
+
for (let attempt = 0; attempt < SQLITE_QUERY_MAX_ATTEMPTS; attempt += 1) {
|
|
7577
|
+
try {
|
|
7578
|
+
return await runSqliteJsonQueryOnce(dbFile, sql);
|
|
7579
|
+
} catch (error) {
|
|
7580
|
+
lastError = error;
|
|
7581
|
+
if (!isRetryableSqliteQueryError(error) || attempt + 1 >= SQLITE_QUERY_MAX_ATTEMPTS) {
|
|
7582
|
+
throw error;
|
|
7583
|
+
}
|
|
7584
|
+
await sleep(sqliteQueryRetryDelayMs(attempt));
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
throw lastError || new Error("sqlite-query-failed");
|
|
7588
|
+
}
|
|
7589
|
+
|
|
7590
|
+
function runSqliteJsonQueryOnce(dbFile, sql) {
|
|
7432
7591
|
return new Promise((resolve, reject) => {
|
|
7433
|
-
const child = spawn("sqlite3", ["-json", dbFile, sql], {
|
|
7592
|
+
const child = spawn("sqlite3", ["-readonly", "-cmd", `.timeout ${SQLITE_QUERY_BUSY_TIMEOUT_MS}`, "-json", dbFile, sql], {
|
|
7434
7593
|
stdio: ["ignore", "pipe", "pipe"],
|
|
7435
7594
|
});
|
|
7436
7595
|
|
|
@@ -9951,13 +10110,28 @@ function buildDefaultCollaborationMode(threadState) {
|
|
|
9951
10110
|
return buildRequestedCollaborationMode(threadState, "default");
|
|
9952
10111
|
}
|
|
9953
10112
|
|
|
10113
|
+
function transferableFrameBytes(frame) {
|
|
10114
|
+
if (
|
|
10115
|
+
frame.byteOffset === 0 &&
|
|
10116
|
+
frame.byteLength === frame.buffer.byteLength &&
|
|
10117
|
+
frame.buffer instanceof ArrayBuffer
|
|
10118
|
+
) {
|
|
10119
|
+
return new Uint8Array(frame.buffer);
|
|
10120
|
+
}
|
|
10121
|
+
const owned = new Uint8Array(frame.byteLength);
|
|
10122
|
+
owned.set(frame);
|
|
10123
|
+
return owned;
|
|
10124
|
+
}
|
|
10125
|
+
|
|
9954
10126
|
class NativeIpcClient {
|
|
9955
10127
|
constructor({ config, runtime, onThreadStateChanged, onUserInputRequested }) {
|
|
9956
10128
|
this.config = config;
|
|
9957
10129
|
this.runtime = runtime;
|
|
9958
10130
|
this.onThreadStateChanged = onThreadStateChanged;
|
|
9959
10131
|
this.onUserInputRequested = onUserInputRequested;
|
|
9960
|
-
this.
|
|
10132
|
+
this.frameDecoder = new LengthPrefixedFrameDecoder();
|
|
10133
|
+
this.frameWorker = null;
|
|
10134
|
+
this.messageQueue = Promise.resolve();
|
|
9961
10135
|
this.socket = null;
|
|
9962
10136
|
this.clientId = null;
|
|
9963
10137
|
this.pendingResponses = new Map();
|
|
@@ -9966,6 +10140,7 @@ class NativeIpcClient {
|
|
|
9966
10140
|
}
|
|
9967
10141
|
|
|
9968
10142
|
start() {
|
|
10143
|
+
this.ensureFrameWorker();
|
|
9969
10144
|
this.connect();
|
|
9970
10145
|
}
|
|
9971
10146
|
|
|
@@ -9987,6 +10162,36 @@ class NativeIpcClient {
|
|
|
9987
10162
|
this.socket.destroy();
|
|
9988
10163
|
this.socket = null;
|
|
9989
10164
|
}
|
|
10165
|
+
if (this.frameWorker) {
|
|
10166
|
+
void this.frameWorker.terminate();
|
|
10167
|
+
this.frameWorker = null;
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
|
|
10171
|
+
ensureFrameWorker() {
|
|
10172
|
+
if (this.frameWorker) {
|
|
10173
|
+
return this.frameWorker;
|
|
10174
|
+
}
|
|
10175
|
+
const worker = new Worker(new URL("./lib/ipc-frame-worker.mjs", import.meta.url), {
|
|
10176
|
+
type: "module",
|
|
10177
|
+
});
|
|
10178
|
+
this.frameWorker = worker;
|
|
10179
|
+
worker.on("message", (payload) => {
|
|
10180
|
+
this.handleFrameWorkerMessage(payload);
|
|
10181
|
+
});
|
|
10182
|
+
worker.on("error", (error) => {
|
|
10183
|
+
console.error(`[ipc-worker-error] ${error.message}`);
|
|
10184
|
+
});
|
|
10185
|
+
worker.on("exit", (code) => {
|
|
10186
|
+
if (this.frameWorker === worker) {
|
|
10187
|
+
this.frameWorker = null;
|
|
10188
|
+
}
|
|
10189
|
+
if (!this.stopped && code !== 0) {
|
|
10190
|
+
console.error(`[ipc-worker-error] exited with code ${code}`);
|
|
10191
|
+
this.socket?.destroy();
|
|
10192
|
+
}
|
|
10193
|
+
});
|
|
10194
|
+
return worker;
|
|
9990
10195
|
}
|
|
9991
10196
|
|
|
9992
10197
|
async sendApprovalDecision(approval, decision) {
|
|
@@ -10128,11 +10333,12 @@ class NativeIpcClient {
|
|
|
10128
10333
|
return;
|
|
10129
10334
|
}
|
|
10130
10335
|
|
|
10336
|
+
this.ensureFrameWorker();
|
|
10131
10337
|
const socket = net.createConnection(this.config.ipcSocketPath);
|
|
10132
10338
|
this.socket = socket;
|
|
10133
10339
|
|
|
10134
10340
|
socket.on("connect", () => {
|
|
10135
|
-
this.
|
|
10341
|
+
this.frameDecoder.reset();
|
|
10136
10342
|
this.write({
|
|
10137
10343
|
type: "request",
|
|
10138
10344
|
requestId: crypto.randomUUID(),
|
|
@@ -10142,9 +10348,7 @@ class NativeIpcClient {
|
|
|
10142
10348
|
});
|
|
10143
10349
|
|
|
10144
10350
|
socket.on("data", (chunk) => {
|
|
10145
|
-
this.handleData(chunk)
|
|
10146
|
-
console.error(`[ipc-error] ${error.message}`);
|
|
10147
|
-
});
|
|
10351
|
+
this.handleData(chunk);
|
|
10148
10352
|
});
|
|
10149
10353
|
|
|
10150
10354
|
socket.on("error", (error) => {
|
|
@@ -10178,27 +10382,51 @@ class NativeIpcClient {
|
|
|
10178
10382
|
}
|
|
10179
10383
|
}
|
|
10180
10384
|
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
const frame = this.buffer.subarray(4, 4 + frameBytes).toString("utf8");
|
|
10191
|
-
this.buffer = this.buffer.subarray(4 + frameBytes);
|
|
10385
|
+
handleData(chunk) {
|
|
10386
|
+
let frames;
|
|
10387
|
+
try {
|
|
10388
|
+
frames = this.frameDecoder.push(chunk);
|
|
10389
|
+
} catch (error) {
|
|
10390
|
+
console.error(`[ipc-error] ${error.message}`);
|
|
10391
|
+
this.socket?.destroy();
|
|
10392
|
+
return this.messageQueue;
|
|
10393
|
+
}
|
|
10192
10394
|
|
|
10193
|
-
|
|
10395
|
+
const worker = this.ensureFrameWorker();
|
|
10396
|
+
for (const frame of frames) {
|
|
10194
10397
|
try {
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10398
|
+
const transferable = transferableFrameBytes(frame);
|
|
10399
|
+
worker.postMessage(
|
|
10400
|
+
{ type: "frame", frame: transferable },
|
|
10401
|
+
[transferable.buffer]
|
|
10402
|
+
);
|
|
10403
|
+
} catch (error) {
|
|
10404
|
+
console.error(`[ipc-worker-error] ${error.message}`);
|
|
10405
|
+
this.socket?.destroy();
|
|
10406
|
+
break;
|
|
10198
10407
|
}
|
|
10408
|
+
}
|
|
10409
|
+
return this.messageQueue;
|
|
10410
|
+
}
|
|
10199
10411
|
|
|
10200
|
-
|
|
10412
|
+
handleFrameWorkerMessage(payload) {
|
|
10413
|
+
if (payload?.type === "error") {
|
|
10414
|
+
console.error(`[ipc-worker-error] ${payload.error || "IPC frame parse failed"}`);
|
|
10415
|
+
return;
|
|
10416
|
+
}
|
|
10417
|
+
if (payload?.type !== "message" || !payload.message) {
|
|
10418
|
+
return;
|
|
10201
10419
|
}
|
|
10420
|
+
if (Number(payload.frameBytes) >= IPC_LARGE_FRAME_LOG_BYTES) {
|
|
10421
|
+
console.log(
|
|
10422
|
+
`[ipc-frame] bytes=${Number(payload.frameBytes) || 0} parseMs=${Number(payload.parseMs) || 0} totalMs=${Number(payload.totalMs) || 0}`
|
|
10423
|
+
);
|
|
10424
|
+
}
|
|
10425
|
+
this.messageQueue = this.messageQueue
|
|
10426
|
+
.then(() => this.handleMessage(payload.message))
|
|
10427
|
+
.catch((error) => {
|
|
10428
|
+
console.error(`[ipc-error] ${error.message}`);
|
|
10429
|
+
});
|
|
10202
10430
|
}
|
|
10203
10431
|
|
|
10204
10432
|
async handleMessage(message) {
|
|
@@ -10267,7 +10495,8 @@ class NativeIpcClient {
|
|
|
10267
10495
|
}
|
|
10268
10496
|
|
|
10269
10497
|
const previousState = this.runtime.threadStates.get(normalized.conversationId) ?? { requests: [] };
|
|
10270
|
-
|
|
10498
|
+
const previousRequests = normalizeNativeRequests(previousState.requests);
|
|
10499
|
+
let nextState = previousState;
|
|
10271
10500
|
|
|
10272
10501
|
if (normalized.state) {
|
|
10273
10502
|
nextState = mergeConversationState(nextState, normalized.state);
|
|
@@ -10281,7 +10510,6 @@ class NativeIpcClient {
|
|
|
10281
10510
|
nextState.requests = [];
|
|
10282
10511
|
}
|
|
10283
10512
|
|
|
10284
|
-
const previousRequests = normalizeNativeRequests(previousState.requests);
|
|
10285
10513
|
const nextRequests = normalizeNativeRequests(nextState.requests);
|
|
10286
10514
|
|
|
10287
10515
|
if (previousRequests.length !== nextRequests.length) {
|
|
@@ -10449,143 +10677,6 @@ function mergeConversationState(previousState, nextState) {
|
|
|
10449
10677
|
return merged;
|
|
10450
10678
|
}
|
|
10451
10679
|
|
|
10452
|
-
function applyJsonPatches(document, patches) {
|
|
10453
|
-
let next = cloneJson(document) ?? {};
|
|
10454
|
-
|
|
10455
|
-
for (const patch of patches) {
|
|
10456
|
-
next = applyJsonPatch(next, patch);
|
|
10457
|
-
}
|
|
10458
|
-
|
|
10459
|
-
return next;
|
|
10460
|
-
}
|
|
10461
|
-
|
|
10462
|
-
function applyJsonPatch(document, patch) {
|
|
10463
|
-
const operation = patch.op;
|
|
10464
|
-
const pathSegments = decodeJsonPointer(patch.path);
|
|
10465
|
-
|
|
10466
|
-
if (pathSegments.length === 0) {
|
|
10467
|
-
if (operation === "remove") {
|
|
10468
|
-
return {};
|
|
10469
|
-
}
|
|
10470
|
-
if (operation === "add" || operation === "replace") {
|
|
10471
|
-
return cloneJson(patch.value);
|
|
10472
|
-
}
|
|
10473
|
-
return document;
|
|
10474
|
-
}
|
|
10475
|
-
|
|
10476
|
-
const target = cloneJson(document) ?? {};
|
|
10477
|
-
if (operation === "remove") {
|
|
10478
|
-
removeAtPointer(target, pathSegments);
|
|
10479
|
-
return target;
|
|
10480
|
-
}
|
|
10481
|
-
|
|
10482
|
-
if (operation === "add" || operation === "replace") {
|
|
10483
|
-
setAtPointer(target, pathSegments, cloneJson(patch.value), operation === "add");
|
|
10484
|
-
return target;
|
|
10485
|
-
}
|
|
10486
|
-
|
|
10487
|
-
return target;
|
|
10488
|
-
}
|
|
10489
|
-
|
|
10490
|
-
function decodeJsonPointer(pointer) {
|
|
10491
|
-
if (Array.isArray(pointer)) {
|
|
10492
|
-
return pointer.map((segment) => String(segment));
|
|
10493
|
-
}
|
|
10494
|
-
|
|
10495
|
-
if (!pointer || pointer === "/") {
|
|
10496
|
-
return [];
|
|
10497
|
-
}
|
|
10498
|
-
|
|
10499
|
-
return String(pointer)
|
|
10500
|
-
.split("/")
|
|
10501
|
-
.slice(1)
|
|
10502
|
-
.map((segment) => segment.replace(/~1/gu, "/").replace(/~0/gu, "~"));
|
|
10503
|
-
}
|
|
10504
|
-
|
|
10505
|
-
function setAtPointer(target, segments, value, isAdd) {
|
|
10506
|
-
let node = target;
|
|
10507
|
-
|
|
10508
|
-
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
10509
|
-
const segment = segments[index];
|
|
10510
|
-
const nextSegment = segments[index + 1];
|
|
10511
|
-
|
|
10512
|
-
if (Array.isArray(node)) {
|
|
10513
|
-
const arrayIndex = toArrayIndex(segment, node.length, true);
|
|
10514
|
-
if (node[arrayIndex] == null || typeof node[arrayIndex] !== "object") {
|
|
10515
|
-
node[arrayIndex] = isArrayPointerSegment(nextSegment) ? [] : {};
|
|
10516
|
-
}
|
|
10517
|
-
node = node[arrayIndex];
|
|
10518
|
-
continue;
|
|
10519
|
-
}
|
|
10520
|
-
|
|
10521
|
-
if (node[segment] == null || typeof node[segment] !== "object") {
|
|
10522
|
-
node[segment] = isArrayPointerSegment(nextSegment) ? [] : {};
|
|
10523
|
-
}
|
|
10524
|
-
node = node[segment];
|
|
10525
|
-
}
|
|
10526
|
-
|
|
10527
|
-
const finalSegment = segments.at(-1);
|
|
10528
|
-
if (Array.isArray(node)) {
|
|
10529
|
-
const index = toArrayIndex(finalSegment, node.length, isAdd);
|
|
10530
|
-
if (isAdd && (finalSegment === "-" || index === node.length)) {
|
|
10531
|
-
node.push(value);
|
|
10532
|
-
return;
|
|
10533
|
-
}
|
|
10534
|
-
if (isAdd) {
|
|
10535
|
-
node.splice(index, 0, value);
|
|
10536
|
-
return;
|
|
10537
|
-
}
|
|
10538
|
-
node[index] = value;
|
|
10539
|
-
return;
|
|
10540
|
-
}
|
|
10541
|
-
|
|
10542
|
-
node[finalSegment] = value;
|
|
10543
|
-
}
|
|
10544
|
-
|
|
10545
|
-
function removeAtPointer(target, segments) {
|
|
10546
|
-
let node = target;
|
|
10547
|
-
|
|
10548
|
-
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
10549
|
-
const segment = segments[index];
|
|
10550
|
-
if (Array.isArray(node)) {
|
|
10551
|
-
const arrayIndex = toArrayIndex(segment, node.length, false);
|
|
10552
|
-
node = node[arrayIndex];
|
|
10553
|
-
} else {
|
|
10554
|
-
node = node?.[segment];
|
|
10555
|
-
}
|
|
10556
|
-
|
|
10557
|
-
if (node == null) {
|
|
10558
|
-
return;
|
|
10559
|
-
}
|
|
10560
|
-
}
|
|
10561
|
-
|
|
10562
|
-
const finalSegment = segments.at(-1);
|
|
10563
|
-
if (Array.isArray(node)) {
|
|
10564
|
-
const arrayIndex = toArrayIndex(finalSegment, node.length, false);
|
|
10565
|
-
if (arrayIndex >= 0 && arrayIndex < node.length) {
|
|
10566
|
-
node.splice(arrayIndex, 1);
|
|
10567
|
-
}
|
|
10568
|
-
return;
|
|
10569
|
-
}
|
|
10570
|
-
|
|
10571
|
-
delete node?.[finalSegment];
|
|
10572
|
-
}
|
|
10573
|
-
|
|
10574
|
-
function toArrayIndex(segment, length, allowEnd) {
|
|
10575
|
-
if (segment === "-" && allowEnd) {
|
|
10576
|
-
return length;
|
|
10577
|
-
}
|
|
10578
|
-
const parsed = Number.parseInt(segment, 10);
|
|
10579
|
-
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
10580
|
-
return parsed;
|
|
10581
|
-
}
|
|
10582
|
-
return allowEnd ? length : 0;
|
|
10583
|
-
}
|
|
10584
|
-
|
|
10585
|
-
function isArrayPointerSegment(segment) {
|
|
10586
|
-
return segment === "-" || /^\d+$/u.test(String(segment || ""));
|
|
10587
|
-
}
|
|
10588
|
-
|
|
10589
10680
|
function normalizeNativeRequests(requests) {
|
|
10590
10681
|
if (!Array.isArray(requests)) {
|
|
10591
10682
|
return [];
|
|
@@ -11033,7 +11124,7 @@ function formatCommandApprovalMessage(params, locale = config?.defaultLocale ||
|
|
|
11033
11124
|
parts.push(t(locale, "server.message.commandApprovalNeeded"));
|
|
11034
11125
|
}
|
|
11035
11126
|
if (command) {
|
|
11036
|
-
parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${command}\n\`\`\``);
|
|
11127
|
+
parts.push(`${t(locale, "server.message.commandLabel")}\n\`\`\`sh\n${escapeMarkdownCodeFenceBody(command)}\n\`\`\``);
|
|
11037
11128
|
}
|
|
11038
11129
|
return parts.join("\n\n") || t(locale, "server.message.commandApprovalNeeded");
|
|
11039
11130
|
}
|
|
@@ -12986,6 +13077,7 @@ function buildPendingInboxItems(runtime, state, config, locale) {
|
|
|
12986
13077
|
primaryLabel: t(locale, "server.action.review"),
|
|
12987
13078
|
createdAtMs: Number(approval.createdAtMs) || now,
|
|
12988
13079
|
provider: normalizeProvider(approval.provider),
|
|
13080
|
+
approvalKind: cleanText(approval.kind || ""),
|
|
12989
13081
|
});
|
|
12990
13082
|
}
|
|
12991
13083
|
|
|
@@ -13030,6 +13122,7 @@ function buildPendingInboxItems(runtime, state, config, locale) {
|
|
|
13030
13122
|
primaryLabel: t(locale, userInputRequest.supported ? "server.action.select" : "server.action.detail"),
|
|
13031
13123
|
createdAtMs: Number(userInputRequest.createdAtMs) || now,
|
|
13032
13124
|
provider: "codex",
|
|
13125
|
+
supported: userInputRequest.supported !== false,
|
|
13033
13126
|
});
|
|
13034
13127
|
}
|
|
13035
13128
|
|
|
@@ -13091,10 +13184,52 @@ function buildPendingInboxItems(runtime, state, config, locale) {
|
|
|
13091
13184
|
return items.sort((left, right) => Number(right.createdAtMs ?? 0) - Number(left.createdAtMs ?? 0));
|
|
13092
13185
|
}
|
|
13093
13186
|
|
|
13187
|
+
function buildInProgressInboxItems(runtime, locale) {
|
|
13188
|
+
const items = buildActiveTimelineActivityEntries(runtime, locale).map((entry) => ({
|
|
13189
|
+
kind: "activity_status",
|
|
13190
|
+
token: entry.token,
|
|
13191
|
+
threadId: cleanText(entry.threadId || ""),
|
|
13192
|
+
threadLabel: cleanText(entry.threadLabel || ""),
|
|
13193
|
+
title: cleanText(entry.threadLabel || entry.title || ""),
|
|
13194
|
+
summary: [cleanText(entry.title || ""), cleanText(entry.summary || "")].filter(Boolean).join(" · "),
|
|
13195
|
+
primaryLabel: t(locale, "server.action.detail"),
|
|
13196
|
+
createdAtMs: Number(entry.createdAtMs) || Date.now(),
|
|
13197
|
+
provider: normalizeProvider(entry.provider),
|
|
13198
|
+
activityPhase: cleanText(entry.activityPhase || "working"),
|
|
13199
|
+
commandText: cleanText(entry.commandText || ""),
|
|
13200
|
+
fileRefs: normalizeTimelineFileRefs(entry.fileRefs ?? []),
|
|
13201
|
+
}));
|
|
13202
|
+
|
|
13203
|
+
for (const task of runtime.a2aTasksByToken.values()) {
|
|
13204
|
+
const status = cleanText(task?.status || "");
|
|
13205
|
+
if (status !== "working" && status !== "input-required") {
|
|
13206
|
+
continue;
|
|
13207
|
+
}
|
|
13208
|
+
const instruction = cleanText(task.instruction || "");
|
|
13209
|
+
items.push({
|
|
13210
|
+
kind: "a2a_task",
|
|
13211
|
+
token: task.token,
|
|
13212
|
+
threadId: "a2a",
|
|
13213
|
+
threadLabel: instruction.slice(0, 80),
|
|
13214
|
+
title: instruction.slice(0, 80) || "A2A Task",
|
|
13215
|
+
summary: cleanText(task.statusMessage || instruction).slice(0, 160),
|
|
13216
|
+
primaryLabel: t(locale, "server.action.detail"),
|
|
13217
|
+
createdAtMs: Number(task.updatedAtMs || task.createdAtMs) || Date.now(),
|
|
13218
|
+
provider: "a2a",
|
|
13219
|
+
taskStatus: status,
|
|
13220
|
+
});
|
|
13221
|
+
}
|
|
13222
|
+
|
|
13223
|
+
return items.sort((left, right) => Number(right.createdAtMs ?? 0) - Number(left.createdAtMs ?? 0));
|
|
13224
|
+
}
|
|
13225
|
+
|
|
13094
13226
|
function buildCompletedInboxItems(runtime, state, config, locale) {
|
|
13095
13227
|
const items = normalizeHistoryItems(state.recentHistoryItems ?? runtime.recentHistoryItems, config.maxHistoryItems);
|
|
13096
13228
|
runtime.recentHistoryItems = items;
|
|
13097
|
-
|
|
13229
|
+
// `completion` remains the notification/history format for the Codex
|
|
13230
|
+
// SQLite completion scanner, so it must stay visible alongside the newer
|
|
13231
|
+
// assistant_final timeline format.
|
|
13232
|
+
const completedKinds = new Set(["completion", "assistant_final", "approval", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task_result"]);
|
|
13098
13233
|
|
|
13099
13234
|
// Infrequent kinds (A2A, thread_share) get evicted from the fixed-size
|
|
13100
13235
|
// history FIFO by the high volume of approval/assistant_final entries.
|
|
@@ -13137,6 +13272,8 @@ function buildCompletedInboxItems(runtime, state, config, locale) {
|
|
|
13137
13272
|
createdAtMs: item.createdAtMs,
|
|
13138
13273
|
provider: normalizeProvider(item.provider),
|
|
13139
13274
|
...(item.draftType != null ? { draftType: item.draftType } : {}),
|
|
13275
|
+
...(item.taskStatus != null ? { taskStatus: item.taskStatus } : {}),
|
|
13276
|
+
...(item.outcome != null ? { outcome: item.outcome } : {}),
|
|
13140
13277
|
}));
|
|
13141
13278
|
}
|
|
13142
13279
|
|
|
@@ -13306,9 +13443,27 @@ async function buildDiffThreadGroups(runtime, state, config) {
|
|
|
13306
13443
|
// is served from `/api/inbox/diff` and fetched separately by the PWA so
|
|
13307
13444
|
// it doesn't block first paint of the inbox/completed lists.
|
|
13308
13445
|
function buildInboxFastResponse(runtime, state, config, locale) {
|
|
13309
|
-
|
|
13446
|
+
const normalized = buildWorkItemResponse({
|
|
13310
13447
|
pending: buildPendingInboxItems(runtime, state, config, locale),
|
|
13448
|
+
inProgress: buildInProgressInboxItems(runtime, locale),
|
|
13311
13449
|
completed: buildCompletedInboxItems(runtime, state, config, locale),
|
|
13450
|
+
});
|
|
13451
|
+
const { pending, inProgress, completed, ...work } = normalized;
|
|
13452
|
+
const attention = [];
|
|
13453
|
+
if (pending.some((item) => item.provider === "codex") && !runtime.ipcClient?.clientId) {
|
|
13454
|
+
attention.push({ source: "codex", code: "connection-required" });
|
|
13455
|
+
}
|
|
13456
|
+
return {
|
|
13457
|
+
pending,
|
|
13458
|
+
inProgress,
|
|
13459
|
+
completed,
|
|
13460
|
+
work: {
|
|
13461
|
+
...work,
|
|
13462
|
+
health: {
|
|
13463
|
+
checkedAtMs: Date.now(),
|
|
13464
|
+
attention,
|
|
13465
|
+
},
|
|
13466
|
+
},
|
|
13312
13467
|
};
|
|
13313
13468
|
}
|
|
13314
13469
|
|
|
@@ -14174,6 +14329,28 @@ function buildTimelineMessageDetail(entry, locale, runtime = null, config = null
|
|
|
14174
14329
|
};
|
|
14175
14330
|
}
|
|
14176
14331
|
|
|
14332
|
+
function buildTimelineActivityDetail(entry, locale) {
|
|
14333
|
+
const detailText = [
|
|
14334
|
+
cleanText(entry?.title || ""),
|
|
14335
|
+
cleanText(entry?.summary || ""),
|
|
14336
|
+
].filter(Boolean).join("\n\n");
|
|
14337
|
+
return {
|
|
14338
|
+
kind: "activity_status",
|
|
14339
|
+
token: cleanText(entry?.token || ""),
|
|
14340
|
+
threadId: cleanText(entry?.threadId || ""),
|
|
14341
|
+
title: cleanText(entry?.threadLabel || entry?.title || "") || kindTitle(locale, "activity_status"),
|
|
14342
|
+
threadLabel: cleanText(entry?.threadLabel || ""),
|
|
14343
|
+
createdAtMs: Number(entry?.createdAtMs) || 0,
|
|
14344
|
+
messageHtml: renderMessageHtml(detailText, `<p>${escapeHtml(t(locale, "server.activity.working"))}</p>`),
|
|
14345
|
+
fileRefs: normalizeTimelineFileRefs(entry?.fileRefs ?? []),
|
|
14346
|
+
commandText: cleanText(entry?.commandText || ""),
|
|
14347
|
+
activityPhase: cleanText(entry?.activityPhase || "working"),
|
|
14348
|
+
provider: normalizeProvider(entry?.provider),
|
|
14349
|
+
readOnly: true,
|
|
14350
|
+
actions: [],
|
|
14351
|
+
};
|
|
14352
|
+
}
|
|
14353
|
+
|
|
14177
14354
|
function buildAmbientSuggestionsDetail(entry, locale) {
|
|
14178
14355
|
const suggestions = normalizeAmbientSuggestions(entry?.suggestions ?? []);
|
|
14179
14356
|
return {
|
|
@@ -15548,6 +15725,11 @@ async function buildApiItemDetail({ config, runtime, state, kind, token, locale
|
|
|
15548
15725
|
const group = (await getDiffThreadGroupsCached(runtime, state, config)).find((entry) => entry.token === token);
|
|
15549
15726
|
return group ? buildDiffThreadDetail(group, locale) : null;
|
|
15550
15727
|
}
|
|
15728
|
+
if (kind === "activity_status") {
|
|
15729
|
+
const entry = buildActiveTimelineActivityEntries(runtime, locale)
|
|
15730
|
+
.find((candidate) => cleanText(candidate?.token || "") === cleanText(token || ""));
|
|
15731
|
+
return entry ? buildTimelineActivityDetail(entry, locale) : null;
|
|
15732
|
+
}
|
|
15551
15733
|
if (kind === "file_event") {
|
|
15552
15734
|
const entry = timelineEntryByToken(runtime, token, kind);
|
|
15553
15735
|
return entry ? buildTimelineFileEventDetail(entry, locale) : null;
|
|
@@ -16183,6 +16365,7 @@ function logRemotePairingBootTrace(body, session, req) {
|
|
|
16183
16365
|
const traceId = cleanText(body?.traceId || "").slice(0, 80) || "unknown";
|
|
16184
16366
|
const reason = cleanText(body?.reason || "").slice(0, 80) || "unknown";
|
|
16185
16367
|
const appBuildId = cleanText(body?.appBuildId || "").slice(0, 80) || "unknown";
|
|
16368
|
+
const origin = cleanText(body?.origin || "").slice(0, 180) || "unknown";
|
|
16186
16369
|
const totalMs = Math.max(0, Math.min(600_000, Math.round(Number(body?.totalMs) || 0)));
|
|
16187
16370
|
const remoteRouteSeen = body?.remoteRouteSeen === true;
|
|
16188
16371
|
const eventList = Array.isArray(body?.events) ? body.events : [];
|
|
@@ -16207,7 +16390,7 @@ function logRemotePairingBootTrace(body, session, req) {
|
|
|
16207
16390
|
console.log(
|
|
16208
16391
|
`[remote-pairing-boot] trace=${traceId} device=${deviceId} reason=${reason} ` +
|
|
16209
16392
|
`total=${totalMs}ms remote=${remoteRouteSeen ? 1 : 0} build=${appBuildId} ` +
|
|
16210
|
-
`events=${events.length} ua=${JSON.stringify(ua)}`
|
|
16393
|
+
`origin=${JSON.stringify(origin)} events=${events.length} ua=${JSON.stringify(ua)}`
|
|
16211
16394
|
);
|
|
16212
16395
|
if (phases) {
|
|
16213
16396
|
console.log(`[remote-pairing-boot-phases] trace=${traceId} ${phases}`);
|
|
@@ -19235,6 +19418,9 @@ if (url.pathname === "/api/hazbase/logout" && req.method === "POST") {
|
|
|
19235
19418
|
if (!sourceId || !draftText) {
|
|
19236
19419
|
return writeJson(res, 400, { error: "missing-sourceId-or-draftText" });
|
|
19237
19420
|
}
|
|
19421
|
+
if (looksLikeProviderErrorText(draftText)) {
|
|
19422
|
+
return writeJson(res, 422, { error: "moltbook-draft-provider-error" });
|
|
19423
|
+
}
|
|
19238
19424
|
if (draftType === "reply" && !postId) {
|
|
19239
19425
|
return writeJson(res, 400, { error: "missing-postId-for-reply" });
|
|
19240
19426
|
}
|
|
@@ -23071,7 +23257,15 @@ async function extractRolloutThreadMetadata(filePath) {
|
|
|
23071
23257
|
crlfDelay: Infinity,
|
|
23072
23258
|
});
|
|
23073
23259
|
|
|
23260
|
+
let linesRead = 0;
|
|
23261
|
+
let bytesRead = 0;
|
|
23074
23262
|
for await (const line of rl) {
|
|
23263
|
+
linesRead += 1;
|
|
23264
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
23265
|
+
if (linesRead > ROLLOUT_LABEL_METADATA_MAX_LINES || bytesRead + lineBytes > ROLLOUT_LABEL_METADATA_MAX_BYTES) {
|
|
23266
|
+
break;
|
|
23267
|
+
}
|
|
23268
|
+
bytesRead += lineBytes;
|
|
23075
23269
|
if (!line.trim()) {
|
|
23076
23270
|
continue;
|
|
23077
23271
|
}
|
|
@@ -23137,11 +23331,16 @@ async function extractRolloutThreadMetadata(filePath) {
|
|
|
23137
23331
|
|
|
23138
23332
|
async function buildRolloutThreadLabelIndex(knownFiles, sessionIndex) {
|
|
23139
23333
|
const entries = new Map();
|
|
23140
|
-
|
|
23334
|
+
const files = Array.isArray(knownFiles) ? knownFiles : [];
|
|
23335
|
+
for (let index = 0; index < files.length; index += 1) {
|
|
23336
|
+
const filePath = files[index];
|
|
23141
23337
|
const metadata = await extractRolloutThreadMetadata(filePath);
|
|
23142
23338
|
if (metadata?.threadId) {
|
|
23143
23339
|
entries.set(metadata.threadId, metadata);
|
|
23144
23340
|
}
|
|
23341
|
+
if ((index + 1) % 8 === 0) {
|
|
23342
|
+
await yieldToEventLoop();
|
|
23343
|
+
}
|
|
23145
23344
|
}
|
|
23146
23345
|
|
|
23147
23346
|
const resolved = new Map();
|
|
@@ -23201,6 +23400,48 @@ async function buildRolloutThreadLabelIndex(knownFiles, sessionIndex) {
|
|
|
23201
23400
|
return resolved;
|
|
23202
23401
|
}
|
|
23203
23402
|
|
|
23403
|
+
function refreshRolloutThreadLabelIndexInBackground({ config, runtime, state }) {
|
|
23404
|
+
if (runtime.rolloutLabelIndexInFlight || runtime.stopping) {
|
|
23405
|
+
return;
|
|
23406
|
+
}
|
|
23407
|
+
runtime.rolloutLabelIndexInFlight = true;
|
|
23408
|
+
const startedAtMs = Date.now();
|
|
23409
|
+
void buildRolloutThreadLabelIndex(runtime.knownFiles, runtime.sessionIndex)
|
|
23410
|
+
.then((labels) => {
|
|
23411
|
+
runtime.rolloutThreadLabels = labels;
|
|
23412
|
+
if (!runtime.stopping && refreshResolvedThreadLabels({ config, runtime, state })) {
|
|
23413
|
+
scheduleSaveState(config, state);
|
|
23414
|
+
}
|
|
23415
|
+
const durationMs = Date.now() - startedAtMs;
|
|
23416
|
+
if (durationMs >= 1_000) {
|
|
23417
|
+
console.log(`[startup] rollout labels ready count=${labels.size} durationMs=${durationMs}`);
|
|
23418
|
+
}
|
|
23419
|
+
})
|
|
23420
|
+
.catch((error) => {
|
|
23421
|
+
console.error(`[startup] rollout label index failed: ${error?.message || error}`);
|
|
23422
|
+
})
|
|
23423
|
+
.finally(() => {
|
|
23424
|
+
runtime.rolloutLabelIndexInFlight = false;
|
|
23425
|
+
});
|
|
23426
|
+
}
|
|
23427
|
+
|
|
23428
|
+
function scheduleStartupMaintenance({ config, runtime, state }) {
|
|
23429
|
+
const run = () => {
|
|
23430
|
+
if (runtime.stopping) {
|
|
23431
|
+
return;
|
|
23432
|
+
}
|
|
23433
|
+
if (!runtime.initialScanComplete) {
|
|
23434
|
+
const retryTimer = setTimeout(run, STARTUP_MAINTENANCE_RETRY_MS);
|
|
23435
|
+
retryTimer.unref?.();
|
|
23436
|
+
return;
|
|
23437
|
+
}
|
|
23438
|
+
refreshRolloutThreadLabelIndexInBackground({ config, runtime, state });
|
|
23439
|
+
void runDeferredStartupBackfills({ config, runtime, state });
|
|
23440
|
+
};
|
|
23441
|
+
const timer = setTimeout(run, STARTUP_MAINTENANCE_DELAY_MS);
|
|
23442
|
+
timer.unref?.();
|
|
23443
|
+
}
|
|
23444
|
+
|
|
23204
23445
|
async function findLatestCodexLogsDbFile(codexHome) {
|
|
23205
23446
|
let entries;
|
|
23206
23447
|
try {
|
|
@@ -23824,6 +24065,10 @@ function sleep(ms) {
|
|
|
23824
24065
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23825
24066
|
}
|
|
23826
24067
|
|
|
24068
|
+
function yieldToEventLoop() {
|
|
24069
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
24070
|
+
}
|
|
24071
|
+
|
|
23827
24072
|
function resolvePath(value) {
|
|
23828
24073
|
if (!value) {
|
|
23829
24074
|
return value;
|
|
@@ -23929,7 +24174,6 @@ async function main() {
|
|
|
23929
24174
|
runtime.knownFiles = await listRolloutFiles(config.sessionsDir);
|
|
23930
24175
|
runtime.logsDbFile = config.codexLogsDbFile || (await findLatestCodexLogsDbFile(config.codexHome)) || "";
|
|
23931
24176
|
runtime.lastDirectoryScanAt = Date.now();
|
|
23932
|
-
runtime.rolloutThreadLabels = await buildRolloutThreadLabelIndex(runtime.knownFiles, runtime.sessionIndex);
|
|
23933
24177
|
|
|
23934
24178
|
console.log(
|
|
23935
24179
|
[
|
|
@@ -24002,12 +24246,9 @@ async function main() {
|
|
|
24002
24246
|
normalizedTimelineStateChanged ||
|
|
24003
24247
|
migratedPairedDevicesStateChanged ||
|
|
24004
24248
|
restoredPendingPlanStateChanged ||
|
|
24005
|
-
restoredTimelineImagePathsStateChanged ||
|
|
24006
24249
|
backfilledAmbientSuggestionsStateChanged ||
|
|
24007
24250
|
migratedRecentCodeEventsStateChanged ||
|
|
24008
24251
|
restoredPendingUserInputStateChanged ||
|
|
24009
|
-
backfilledMoltbookInboxChanged ||
|
|
24010
|
-
recoveredMissingProviderStateChanged ||
|
|
24011
24252
|
refreshResolvedThreadLabels({ config, runtime, state })
|
|
24012
24253
|
) {
|
|
24013
24254
|
await saveState(config.stateFile, state);
|
|
@@ -24091,6 +24332,8 @@ async function main() {
|
|
|
24091
24332
|
}
|
|
24092
24333
|
}
|
|
24093
24334
|
|
|
24335
|
+
scheduleStartupMaintenance({ config, runtime, state });
|
|
24336
|
+
|
|
24094
24337
|
timelineLiveHandle = startTimelineLiveSync({ config, runtime, state });
|
|
24095
24338
|
|
|
24096
24339
|
// --- A2A Relay ---
|
|
@@ -24144,6 +24387,10 @@ async function main() {
|
|
|
24144
24387
|
let lastA2aEnvCheckAt = 0;
|
|
24145
24388
|
const A2A_ENV_CHECK_INTERVAL_MS = 30_000; // check every 30 seconds
|
|
24146
24389
|
|
|
24390
|
+
if (approvalServer) {
|
|
24391
|
+
await sleep(INITIAL_SCAN_WARMUP_MS);
|
|
24392
|
+
}
|
|
24393
|
+
|
|
24147
24394
|
while (!runtime.stopping) {
|
|
24148
24395
|
try {
|
|
24149
24396
|
const dirty = await scanOnce({ config, runtime, state });
|
|
@@ -24161,6 +24408,8 @@ async function main() {
|
|
|
24161
24408
|
}
|
|
24162
24409
|
} catch (error) {
|
|
24163
24410
|
console.error(`[scan-error] ${error.message}`);
|
|
24411
|
+
} finally {
|
|
24412
|
+
runtime.initialScanComplete = true;
|
|
24164
24413
|
}
|
|
24165
24414
|
|
|
24166
24415
|
// Hot-reload: detect a2a.env changes (new config or updated card)
|