@codemem/opencode-plugin 0.45.0 → 0.46.0-alpha.1
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/.opencode/lib/compat.js +4 -1
- package/.opencode/lib/opencode-v2-adapter.js +1 -1
- package/.opencode/lib/raw-event-delivery.js +451 -0
- package/.opencode/lib/raw-event-spool.js +42 -7
- package/.opencode/lib/recall-lifecycle.js +324 -0
- package/.opencode/lib/runtime.js +115 -699
- package/package.json +1 -1
package/.opencode/lib/runtime.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync, spawn as nodeSpawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
import { spawn as nodeSpawn, execSync } from "node:child_process";
|
|
6
|
+
import { basename, dirname, join, posix, resolve, win32 } from "node:path";
|
|
7
7
|
import {
|
|
8
8
|
isVersionAtLeast,
|
|
9
9
|
parseBackendUpdatePolicy,
|
|
@@ -11,20 +11,26 @@ import {
|
|
|
11
11
|
resolveAutoUpdatePlan,
|
|
12
12
|
resolveUpgradeGuidance,
|
|
13
13
|
} from "./compat.js";
|
|
14
|
+
import { V2_ADAPTER_DIAGNOSTICS } from "./host-contract.js";
|
|
15
|
+
import {
|
|
16
|
+
classifyRawEventTransportCause,
|
|
17
|
+
createRawEventDelivery,
|
|
18
|
+
describeRawEventViewerFailure,
|
|
19
|
+
} from "./raw-event-delivery.js";
|
|
14
20
|
import {
|
|
15
21
|
DEFAULT_DRAIN_LIMIT,
|
|
16
22
|
DEFAULT_MAX_ENTRIES,
|
|
17
|
-
RAW_EVENT_SPOOL_FULL_CODE,
|
|
18
|
-
loadRawEventSpoolEntries,
|
|
19
|
-
removeRawEventSpoolEntry,
|
|
20
|
-
resolveSpoolDirectory,
|
|
21
|
-
writeRawEventSpoolEntry,
|
|
22
23
|
} from "./raw-event-spool.js";
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
createRecallLifecycle,
|
|
26
|
+
getSessionMessageInjectionCache,
|
|
27
|
+
MAX_MESSAGE_INJECTION_CACHE_ENTRIES,
|
|
28
|
+
setSessionMessageInjectionCacheEntry,
|
|
29
|
+
} from "./recall-lifecycle.js";
|
|
24
30
|
|
|
25
31
|
const TRUTHY_VALUES = ["1", "true", "yes"];
|
|
26
32
|
const DISABLED_VALUES = ["0", "false", "off"];
|
|
27
|
-
const PINNED_BACKEND_VERSION = "0.
|
|
33
|
+
const PINNED_BACKEND_VERSION = "0.46.0-alpha.1";
|
|
28
34
|
const COMPAT_CHECK_DELAY_MS = 1500;
|
|
29
35
|
const COMPAT_CHECK_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
30
36
|
const MAX_UPDATE_STATUS_BYTES = 16 * 1024;
|
|
@@ -33,8 +39,6 @@ const MAX_UPDATE_VERSION_CHARS = 128;
|
|
|
33
39
|
const RELEASE_VERSION =
|
|
34
40
|
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
35
41
|
const CODEMEM_CONTEXT_PART_ID_PREFIX = "codemem-context-";
|
|
36
|
-
const MAX_MESSAGE_INJECTION_CACHE_SESSIONS = 20;
|
|
37
|
-
const MAX_MESSAGE_INJECTION_CACHE_ENTRIES = 100;
|
|
38
42
|
const COMPACTION_INJECTION_SKIP_TTL_MS = 30 * 1000;
|
|
39
43
|
const MAX_WORKING_SET_PATH_CHARS = 400;
|
|
40
44
|
const VIEWER_HEALTH_CHECK_INTERVAL_MS = 60_000;
|
|
@@ -69,8 +73,6 @@ const claimPluginRegistration = (cwd) => {
|
|
|
69
73
|
}
|
|
70
74
|
};
|
|
71
75
|
};
|
|
72
|
-
const rawEventSpoolDrainsInFlight = new Map();
|
|
73
|
-
|
|
74
76
|
// Release an unread response body without surfacing cancellation failures.
|
|
75
77
|
const discardResponseBody = (response) => {
|
|
76
78
|
try {
|
|
@@ -249,7 +251,7 @@ const resolveInjectSurface = (value) => {
|
|
|
249
251
|
return "message";
|
|
250
252
|
};
|
|
251
253
|
|
|
252
|
-
const hasOwn = (value, key) => Object.
|
|
254
|
+
const hasOwn = (value, key) => Object.hasOwn(value || {}, key);
|
|
253
255
|
|
|
254
256
|
const deterministicUuid = (namespace, components) => {
|
|
255
257
|
const hex = createHash("sha256")
|
|
@@ -332,7 +334,7 @@ const classifyFallbackCommandResult = (result) => {
|
|
|
332
334
|
if (structured.code === "validation_error" || /invalid raw event|session id required/i.test(diagnostic)) {
|
|
333
335
|
return { retryable: false, cause: "enqueue-raw-event validation failed" };
|
|
334
336
|
}
|
|
335
|
-
if (/unknown command ['
|
|
337
|
+
if (/unknown command ['"]?enqueue-raw-event|command not found|\bENOENT\b/i.test(diagnostic)) {
|
|
336
338
|
return { retryable: false, cause: "enqueue-raw-event command unavailable" };
|
|
337
339
|
}
|
|
338
340
|
const exitCode = result?.exitCode ?? "unknown";
|
|
@@ -388,7 +390,7 @@ const clearCompatCheckCache = () => {
|
|
|
388
390
|
const parseReleaseVersion = (value) => {
|
|
389
391
|
if (typeof value !== "string" || value.length > MAX_UPDATE_VERSION_CHARS) return null;
|
|
390
392
|
const match = RELEASE_VERSION.exec(value);
|
|
391
|
-
if (!match
|
|
393
|
+
if (!match?.slice(1, 4).map(Number).every(Number.isSafeInteger)) return null;
|
|
392
394
|
const prerelease = match[4]?.split(".") || [];
|
|
393
395
|
if (prerelease.some((identifier) => /^0\d+$/.test(identifier))) return null;
|
|
394
396
|
return { core: match.slice(1, 4).map(Number), prerelease };
|
|
@@ -504,7 +506,7 @@ const createLogLine = (logPath) => async (line) => {
|
|
|
504
506
|
try {
|
|
505
507
|
await mkdir(dirname(logPath), { recursive: true });
|
|
506
508
|
await appendFile(logPath, `${new Date().toISOString()} ${line}\n`);
|
|
507
|
-
} catch
|
|
509
|
+
} catch {
|
|
508
510
|
// ignore logging failures
|
|
509
511
|
}
|
|
510
512
|
};
|
|
@@ -544,7 +546,7 @@ const createDebugLogger = ({ debug, host, logTimeoutMs, getLogLine, getErrorLogL
|
|
|
544
546
|
if (timedOut) {
|
|
545
547
|
await getLogLine()("debug log timed out");
|
|
546
548
|
}
|
|
547
|
-
} catch
|
|
549
|
+
} catch {
|
|
548
550
|
// ignore debug logging failures
|
|
549
551
|
}
|
|
550
552
|
};
|
|
@@ -711,7 +713,7 @@ const buildPackArgs = ({ query, filesModified, injectLimit, injectTokenBudget, i
|
|
|
711
713
|
};
|
|
712
714
|
|
|
713
715
|
const parsePackText = (stdout) => {
|
|
714
|
-
if (!stdout
|
|
716
|
+
if (!stdout?.trim()) {
|
|
715
717
|
return "";
|
|
716
718
|
}
|
|
717
719
|
try {
|
|
@@ -723,7 +725,7 @@ const parsePackText = (stdout) => {
|
|
|
723
725
|
};
|
|
724
726
|
|
|
725
727
|
const parsePackMetrics = (stdout) => {
|
|
726
|
-
if (!stdout
|
|
728
|
+
if (!stdout?.trim()) {
|
|
727
729
|
return null;
|
|
728
730
|
}
|
|
729
731
|
try {
|
|
@@ -867,6 +869,11 @@ const classifyRawEventViewerFailure = (payload) => {
|
|
|
867
869
|
if (isViewerDbMismatchPayload(payload)) return "database";
|
|
868
870
|
if (isViewerIdentityMismatchPayload(payload)) return "identity";
|
|
869
871
|
if (isViewerContractUnsupportedPayload(payload)) return "contract";
|
|
872
|
+
if (
|
|
873
|
+
isRecord(payload)
|
|
874
|
+
&& isRecord(payload.error)
|
|
875
|
+
&& ["raw_event_queue_full", "raw_event_queue_write_failed"].includes(payload.error.code)
|
|
876
|
+
) return "queue";
|
|
870
877
|
return "connection";
|
|
871
878
|
};
|
|
872
879
|
|
|
@@ -874,6 +881,7 @@ const RAW_EVENT_FAILURE_ACTIONS = Object.freeze({
|
|
|
874
881
|
database: "restart the viewer from the same workspace/config",
|
|
875
882
|
identity: "restart Codemem and OpenCode with the same environment",
|
|
876
883
|
contract: "update Codemem on the installed channel, then restart OpenCode",
|
|
884
|
+
queue: "repair or archive the viewer raw-event queue",
|
|
877
885
|
connection: "check or restart the viewer",
|
|
878
886
|
});
|
|
879
887
|
|
|
@@ -917,11 +925,11 @@ const classifyPromptTransportFailure = ({ kind, compatibleProfile = false }) =>
|
|
|
917
925
|
};
|
|
918
926
|
|
|
919
927
|
const isViewerPolicyOrAuthFailurePayload = (payload) => {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
928
|
+
let code = null;
|
|
929
|
+
if (isRecord(payload)) {
|
|
930
|
+
if (isRecord(payload.error)) code = payload.error.code;
|
|
931
|
+
else code = payload.error;
|
|
932
|
+
}
|
|
925
933
|
return typeof code === "string" && [
|
|
926
934
|
"authorization_failed",
|
|
927
935
|
"forbidden",
|
|
@@ -1213,68 +1221,8 @@ const consumeCompactionInjectionSkip = (compactionInjectionSkips, sessionID, now
|
|
|
1213
1221
|
// session ID and a real message ID; unidentified messages still receive current
|
|
1214
1222
|
// turn context, but are not cached because positional fallbacks can drift across
|
|
1215
1223
|
// turns or sessions. Do not "simplify" this into recomputing previous turns.
|
|
1216
|
-
const getSessionMessageInjectionCache = (messageInjectionCache, sessionID) => {
|
|
1217
|
-
if (!sessionID) {
|
|
1218
|
-
return null;
|
|
1219
|
-
}
|
|
1220
|
-
const cacheKey = sessionID;
|
|
1221
|
-
let sessionCache = messageInjectionCache.get(cacheKey);
|
|
1222
|
-
if (sessionCache) {
|
|
1223
|
-
// Refresh recency so the bounded cache behaves like a small LRU.
|
|
1224
|
-
messageInjectionCache.delete(cacheKey);
|
|
1225
|
-
} else {
|
|
1226
|
-
sessionCache = new Map();
|
|
1227
|
-
}
|
|
1228
|
-
messageInjectionCache.set(cacheKey, sessionCache);
|
|
1229
|
-
while (messageInjectionCache.size > MAX_MESSAGE_INJECTION_CACHE_SESSIONS) {
|
|
1230
|
-
const oldestKey = messageInjectionCache.keys().next().value;
|
|
1231
|
-
if (!oldestKey) break;
|
|
1232
|
-
messageInjectionCache.delete(oldestKey);
|
|
1233
|
-
}
|
|
1234
|
-
return sessionCache;
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
const setSessionMessageInjectionCacheEntry = (
|
|
1238
|
-
sessionCache,
|
|
1239
|
-
messageID,
|
|
1240
|
-
value,
|
|
1241
|
-
{ preserveMessageIDs = new Set(), evictionSnapshot = null } = {},
|
|
1242
|
-
) => {
|
|
1243
|
-
sessionCache.delete(messageID);
|
|
1244
|
-
sessionCache.set(messageID, value);
|
|
1245
|
-
while (sessionCache.size > MAX_MESSAGE_INJECTION_CACHE_ENTRIES) {
|
|
1246
|
-
// Protect host-visible blocks and entries published after this transform began.
|
|
1247
|
-
const oldestMessageID = [...sessionCache.keys()].find((id) =>
|
|
1248
|
-
!preserveMessageIDs.has(id)
|
|
1249
|
-
&& (!evictionSnapshot || (evictionSnapshot.has(id) && evictionSnapshot.get(id) === sessionCache.get(id))),
|
|
1250
|
-
);
|
|
1251
|
-
if (!oldestMessageID) break;
|
|
1252
|
-
sessionCache.delete(oldestMessageID);
|
|
1253
|
-
}
|
|
1254
|
-
};
|
|
1255
|
-
|
|
1256
1224
|
const commitImmediateDelivery = (commit) => commit();
|
|
1257
1225
|
|
|
1258
|
-
const createSessionFinalizationMutex = () => {
|
|
1259
|
-
const tails = new Map();
|
|
1260
|
-
return async (sessionID) => {
|
|
1261
|
-
if (!sessionID) return () => {};
|
|
1262
|
-
const key = String(sessionID);
|
|
1263
|
-
const previous = tails.get(key) || Promise.resolve();
|
|
1264
|
-
let releaseOwner = () => {};
|
|
1265
|
-
const owner = new Promise((resolve) => { releaseOwner = resolve; });
|
|
1266
|
-
tails.set(key, owner);
|
|
1267
|
-
await previous;
|
|
1268
|
-
let released = false;
|
|
1269
|
-
return () => {
|
|
1270
|
-
if (released) return;
|
|
1271
|
-
released = true;
|
|
1272
|
-
releaseOwner();
|
|
1273
|
-
if (tails.get(key) === owner) tails.delete(key);
|
|
1274
|
-
};
|
|
1275
|
-
};
|
|
1276
|
-
};
|
|
1277
|
-
|
|
1278
1226
|
const normalizeInjectedMessageParts = (
|
|
1279
1227
|
messages,
|
|
1280
1228
|
sessionCache,
|
|
@@ -2056,7 +2004,7 @@ const attachAdapterEvent = ({ sessionID, event }) => {
|
|
|
2056
2004
|
let adapterEvent = null;
|
|
2057
2005
|
try {
|
|
2058
2006
|
adapterEvent = buildOpencodeAdapterEvent({ sessionID, event });
|
|
2059
|
-
} catch
|
|
2007
|
+
} catch {
|
|
2060
2008
|
return event;
|
|
2061
2009
|
}
|
|
2062
2010
|
if (!adapterEvent) {
|
|
@@ -2169,18 +2117,6 @@ const detectRunner = ({ cwd, envRunner }) => {
|
|
|
2169
2117
|
return "npx";
|
|
2170
2118
|
};
|
|
2171
2119
|
|
|
2172
|
-
/**
|
|
2173
|
-
* Check if the TS CLI is available at the given path.
|
|
2174
|
-
* Used by the "node" runner to verify the built CLI exists.
|
|
2175
|
-
*/
|
|
2176
|
-
const tsCliAvailable = (cliPath) => {
|
|
2177
|
-
try {
|
|
2178
|
-
return require("fs").existsSync(cliPath);
|
|
2179
|
-
} catch {
|
|
2180
|
-
return false;
|
|
2181
|
-
}
|
|
2182
|
-
};
|
|
2183
|
-
|
|
2184
2120
|
// When an explicit npx override targets the codemem package, pair it with the
|
|
2185
2121
|
// matching @codemem/embeddings spec so the detached Viewer can resolve the
|
|
2186
2122
|
// optional runtime instead of silently falling back to lexical search. Returns
|
|
@@ -2354,16 +2290,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2354
2290
|
}
|
|
2355
2291
|
);
|
|
2356
2292
|
}
|
|
2357
|
-
const injectionToastShown = new Set();
|
|
2358
|
-
const messageInjectionCache = new Map();
|
|
2359
|
-
const emptyRecallCache = new Map();
|
|
2360
|
-
const acquireSessionFinalization = createSessionFinalizationMutex();
|
|
2361
2293
|
const compactionInjectionSkips = new Map();
|
|
2362
|
-
const disabledInjectionRecorded = new Set();
|
|
2363
|
-
const latestPolicySkips = new Map();
|
|
2364
|
-
const attemptStartedAt = new Map();
|
|
2365
|
-
const promptPackRetryCounts = new Map();
|
|
2366
|
-
const successfulPromptPackArtifacts = new Map();
|
|
2367
2294
|
let sessionStartedAt = null;
|
|
2368
2295
|
let sessionStartSessionID = null;
|
|
2369
2296
|
let activeSessionID = null;
|
|
@@ -2371,7 +2298,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2371
2298
|
let viewerStartInFlight = false;
|
|
2372
2299
|
let compatibilityAutoUpdateAttempted = false;
|
|
2373
2300
|
let promptCounter = 0;
|
|
2374
|
-
let skippedAttemptCounter = 0;
|
|
2375
2301
|
let lastPromptText = null;
|
|
2376
2302
|
const capturedPrompts = new Set();
|
|
2377
2303
|
const pendingPrompts = new Map();
|
|
@@ -2383,8 +2309,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2383
2309
|
const messageRoles = new Map();
|
|
2384
2310
|
const messageTexts = new Map();
|
|
2385
2311
|
const promptPartsByMessage = new Map();
|
|
2386
|
-
let debugLogCount = 0;
|
|
2387
|
-
|
|
2388
2312
|
const rawEventsEnabled = envNotDisabled(
|
|
2389
2313
|
process.env.CODEMEM_RAW_EVENTS || "1"
|
|
2390
2314
|
);
|
|
@@ -2392,7 +2316,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2392
2316
|
? `[${viewerHost}]`
|
|
2393
2317
|
: viewerHost;
|
|
2394
2318
|
const rawEventsUrl = `http://${viewerUrlHost}:${viewerPort}/api/raw-events`;
|
|
2395
|
-
const rawEventsStatusUrl = `http://${viewerUrlHost}:${viewerPort}/api/raw-events/status?limit=
|
|
2319
|
+
const rawEventsStatusUrl = `http://${viewerUrlHost}:${viewerPort}/api/raw-events/status?limit=0`;
|
|
2396
2320
|
const packUrl = `http://${viewerUrlHost}:${viewerPort}/api/pack`;
|
|
2397
2321
|
const promptPackProfileUrl = `http://${viewerUrlHost}:${viewerPort}/api/prompt-pack-profile`;
|
|
2398
2322
|
const promptPackLedgerUrl = `http://${viewerUrlHost}:${viewerPort}/api/prompt-pack-ledger`;
|
|
@@ -2419,27 +2343,15 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2419
2343
|
DEFAULT_MAX_ENTRIES,
|
|
2420
2344
|
);
|
|
2421
2345
|
const rawEventSpoolHome = process.env.HOME?.trim() || homedir();
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
// cannot drift in timestamp, ID, property order, or bytes.
|
|
2425
|
-
const rawEventEnvelopes = new WeakMap();
|
|
2426
|
-
const rawEventCaptureContexts = new WeakMap();
|
|
2346
|
+
let runtimeActive = true;
|
|
2347
|
+
let rawEventDelivery = null;
|
|
2427
2348
|
const rawEventPreparations = new WeakMap();
|
|
2428
2349
|
const rawEventDeliveries = new WeakMap();
|
|
2429
|
-
const rawEventStartedAts = new WeakMap();
|
|
2430
2350
|
const capacityDeferredEvents = new Set();
|
|
2431
|
-
let rawEventDeliveryCount = 0;
|
|
2432
2351
|
let rawEventDeliveryTail = Promise.resolve();
|
|
2352
|
+
let rawEventDeliveryCount = 0;
|
|
2433
2353
|
let flushDeliveryTail = Promise.resolve();
|
|
2434
|
-
let streamUnavailableUntil = 0;
|
|
2435
|
-
let spoolPersistenceFailureNoted = null;
|
|
2436
|
-
let spoolLoadFailureNoted = false;
|
|
2437
|
-
let lastStatusCheckAt = 0;
|
|
2438
|
-
let lastStatusAvailable = true;
|
|
2439
2354
|
let promptPackTransportUnavailableUntil = 0;
|
|
2440
|
-
const rawEventAbortController = new AbortController();
|
|
2441
|
-
let runtimeActive = true;
|
|
2442
|
-
|
|
2443
2355
|
const nextEventId = () => {
|
|
2444
2356
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
2445
2357
|
return crypto.randomUUID();
|
|
@@ -2447,202 +2359,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2447
2359
|
return `${Date.now()}-${Math.random()}`;
|
|
2448
2360
|
};
|
|
2449
2361
|
|
|
2450
|
-
const queueRawEventViaCli = async (body, serialized = JSON.stringify(body)) => {
|
|
2451
|
-
const runFallback = () => runCli(["enqueue-raw-event"], {
|
|
2452
|
-
stdinText: serialized,
|
|
2453
|
-
});
|
|
2454
|
-
let result = await runFallback();
|
|
2455
|
-
let classification = classifyFallbackCommandResult(result);
|
|
2456
|
-
let attemptedRetry = false;
|
|
2457
|
-
if (result?.exitCode !== 0 && classification.retryable) {
|
|
2458
|
-
attemptedRetry = true;
|
|
2459
|
-
result = await runFallback();
|
|
2460
|
-
classification = classifyFallbackCommandResult(result);
|
|
2461
|
-
}
|
|
2462
|
-
if (result?.exitCode !== 0) {
|
|
2463
|
-
const retryExhausted = attemptedRetry && classification.retryable;
|
|
2464
|
-
const error = new Error(
|
|
2465
|
-
retryExhausted ? `${classification.cause} after retry` : classification.cause
|
|
2466
|
-
);
|
|
2467
|
-
error.retryable = classification.retryable;
|
|
2468
|
-
throw error;
|
|
2469
|
-
}
|
|
2470
|
-
return true;
|
|
2471
|
-
};
|
|
2472
|
-
|
|
2473
|
-
const lastToastAtBySession = new Map();
|
|
2474
|
-
const shouldToast = (sessionID, category = "general") => {
|
|
2475
|
-
const now = Date.now();
|
|
2476
|
-
const key = `${sessionID || "unknown"}:${category}`;
|
|
2477
|
-
const last = lastToastAtBySession.get(key) || 0;
|
|
2478
|
-
if (now - last < 60000) {
|
|
2479
|
-
return false;
|
|
2480
|
-
}
|
|
2481
|
-
lastToastAtBySession.set(key, now);
|
|
2482
|
-
return true;
|
|
2483
|
-
};
|
|
2484
|
-
|
|
2485
|
-
const warnSpoolPersistenceFailure = async (sessionID, reason) => {
|
|
2486
|
-
const message = reason === "spool_full"
|
|
2487
|
-
? "codemem raw-event retry spool is full; repair Codemem, then archive retained entries"
|
|
2488
|
-
: "codemem could not save a raw event for retry; it remains queued in memory";
|
|
2489
|
-
try {
|
|
2490
|
-
await hostLog({
|
|
2491
|
-
service: "codemem",
|
|
2492
|
-
level: "error",
|
|
2493
|
-
message,
|
|
2494
|
-
extra: { category: "persistence", reason },
|
|
2495
|
-
});
|
|
2496
|
-
} catch {
|
|
2497
|
-
// Best-effort app logging only.
|
|
2498
|
-
}
|
|
2499
|
-
if (spoolPersistenceFailureNoted === reason) return;
|
|
2500
|
-
spoolPersistenceFailureNoted = reason;
|
|
2501
|
-
if (!hostNotify || !shouldToast(sessionID, `persistence:${reason}`)) return;
|
|
2502
|
-
try {
|
|
2503
|
-
await hostNotify({ message: `codemem: ${message}`, variant: "error" });
|
|
2504
|
-
} catch {
|
|
2505
|
-
// Best-effort toast only.
|
|
2506
|
-
}
|
|
2507
|
-
};
|
|
2508
|
-
|
|
2509
|
-
const persistRawEventForRetry = async ({ body, serialized, payload, sessionID }) => {
|
|
2510
|
-
try {
|
|
2511
|
-
await writeRawEventSpoolEntry({
|
|
2512
|
-
envelope: body,
|
|
2513
|
-
serialized,
|
|
2514
|
-
homeDir: rawEventSpoolHome,
|
|
2515
|
-
maxEntries: rawEventSpoolMaxEntries,
|
|
2516
|
-
});
|
|
2517
|
-
spoolPersistenceFailureNoted = null;
|
|
2518
|
-
if (payload && typeof payload === "object") {
|
|
2519
|
-
payload._raw_spooled = true;
|
|
2520
|
-
}
|
|
2521
|
-
return true;
|
|
2522
|
-
} catch (error) {
|
|
2523
|
-
const reason = error?.code === RAW_EVENT_SPOOL_FULL_CODE ? "spool_full" : "write_failed";
|
|
2524
|
-
await logLine(`raw_events.spool.${reason} category=persistence`);
|
|
2525
|
-
await warnSpoolPersistenceFailure(sessionID, reason);
|
|
2526
|
-
return false;
|
|
2527
|
-
}
|
|
2528
|
-
};
|
|
2529
|
-
|
|
2530
|
-
const removeRawEventFromSpool = async ({ eventId, payload }) => {
|
|
2531
|
-
try {
|
|
2532
|
-
await removeRawEventSpoolEntry({ eventId, homeDir: rawEventSpoolHome });
|
|
2533
|
-
if (payload && typeof payload === "object") {
|
|
2534
|
-
payload._raw_spooled = false;
|
|
2535
|
-
}
|
|
2536
|
-
} catch {
|
|
2537
|
-
await logLine("raw_events.spool.cleanup_failed category=persistence");
|
|
2538
|
-
}
|
|
2539
|
-
};
|
|
2540
|
-
|
|
2541
|
-
const notifyRawEventDelivery = async ({ category, delivered, durable, sessionID }) => {
|
|
2542
|
-
const action = RAW_EVENT_FAILURE_ACTIONS[category] || RAW_EVENT_FAILURE_ACTIONS.connection;
|
|
2543
|
-
let outcome = "was saved for retry";
|
|
2544
|
-
if (delivered) {
|
|
2545
|
-
outcome = "was queued via CLI";
|
|
2546
|
-
} else if (!durable) {
|
|
2547
|
-
outcome = "was not saved for retry; still queued in memory";
|
|
2548
|
-
}
|
|
2549
|
-
const message = `codemem raw event ${outcome}; ${action}`;
|
|
2550
|
-
let delivery = "memory";
|
|
2551
|
-
if (delivered) {
|
|
2552
|
-
delivery = "cli";
|
|
2553
|
-
} else if (durable) {
|
|
2554
|
-
delivery = "spool";
|
|
2555
|
-
}
|
|
2556
|
-
try {
|
|
2557
|
-
await hostLog({
|
|
2558
|
-
service: "codemem",
|
|
2559
|
-
level: delivered ? "warn" : "error",
|
|
2560
|
-
message,
|
|
2561
|
-
extra: {
|
|
2562
|
-
category,
|
|
2563
|
-
delivery,
|
|
2564
|
-
},
|
|
2565
|
-
});
|
|
2566
|
-
} catch {
|
|
2567
|
-
// Best-effort app logging only.
|
|
2568
|
-
}
|
|
2569
|
-
if (!hostNotify || !shouldToast(sessionID, `${category}:${delivery}`)) return;
|
|
2570
|
-
try {
|
|
2571
|
-
await hostNotify({ message: `codemem: ${message}`, variant: delivered ? "warning" : "error" });
|
|
2572
|
-
} catch {
|
|
2573
|
-
// Best-effort toast only.
|
|
2574
|
-
}
|
|
2575
|
-
};
|
|
2576
|
-
|
|
2577
|
-
const drainRawEventSpool = () => {
|
|
2578
|
-
if (!rawEventsEnabled) {
|
|
2579
|
-
return Promise.resolve();
|
|
2580
|
-
}
|
|
2581
|
-
const existingDrain = rawEventSpoolDrainsInFlight.get(rawEventSpoolDirectory);
|
|
2582
|
-
if (existingDrain) {
|
|
2583
|
-
return existingDrain;
|
|
2584
|
-
}
|
|
2585
|
-
const drainPromise = (async () => {
|
|
2586
|
-
let loaded;
|
|
2587
|
-
try {
|
|
2588
|
-
loaded = await loadRawEventSpoolEntries({
|
|
2589
|
-
homeDir: rawEventSpoolHome,
|
|
2590
|
-
limit: rawEventSpoolDrainLimit,
|
|
2591
|
-
});
|
|
2592
|
-
} catch {
|
|
2593
|
-
await logLine("raw_events.spool.load_failed category=persistence");
|
|
2594
|
-
const message = "codemem could not read saved raw events; spool entries were left untouched";
|
|
2595
|
-
try {
|
|
2596
|
-
await hostLog({
|
|
2597
|
-
service: "codemem",
|
|
2598
|
-
level: "error",
|
|
2599
|
-
message,
|
|
2600
|
-
extra: { category: "persistence", delivery: "load" },
|
|
2601
|
-
});
|
|
2602
|
-
} catch {
|
|
2603
|
-
// Best-effort app logging only.
|
|
2604
|
-
}
|
|
2605
|
-
if (!spoolLoadFailureNoted) {
|
|
2606
|
-
spoolLoadFailureNoted = true;
|
|
2607
|
-
if (hostNotify && shouldToast(null, "persistence:load")) {
|
|
2608
|
-
try {
|
|
2609
|
-
await hostNotify({ message: `codemem: ${message}`, variant: "error" });
|
|
2610
|
-
} catch {
|
|
2611
|
-
// Best-effort toast only.
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
}
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
|
-
spoolLoadFailureNoted = false;
|
|
2618
|
-
if (loaded.corruptCount > 0) {
|
|
2619
|
-
await logLine(`raw_events.spool.corrupt_retained count=${loaded.corruptCount}`);
|
|
2620
|
-
}
|
|
2621
|
-
for (const entry of loaded.entries) {
|
|
2622
|
-
try {
|
|
2623
|
-
await queueRawEventViaCli(entry.envelope, entry.serialized);
|
|
2624
|
-
await removeRawEventFromSpool({ eventId: entry.eventId });
|
|
2625
|
-
} catch (error) {
|
|
2626
|
-
await logLine("raw_events.spool.drain_deferred category=fallback");
|
|
2627
|
-
if (error?.retryable === true) {
|
|
2628
|
-
break;
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
})();
|
|
2633
|
-
const trackedDrain = drainPromise
|
|
2634
|
-
.catch(async () => {
|
|
2635
|
-
await logLine("raw_events.spool.drain_failed category=persistence");
|
|
2636
|
-
})
|
|
2637
|
-
.finally(() => {
|
|
2638
|
-
if (rawEventSpoolDrainsInFlight.get(rawEventSpoolDirectory) === trackedDrain) {
|
|
2639
|
-
rawEventSpoolDrainsInFlight.delete(rawEventSpoolDirectory);
|
|
2640
|
-
}
|
|
2641
|
-
});
|
|
2642
|
-
rawEventSpoolDrainsInFlight.set(rawEventSpoolDirectory, trackedDrain);
|
|
2643
|
-
return trackedDrain;
|
|
2644
|
-
};
|
|
2645
|
-
|
|
2646
2362
|
const buildViewerCliArgs = (action) => {
|
|
2647
2363
|
const args = ["serve", action, "--host", viewerHost, "--port", viewerPort];
|
|
2648
2364
|
if (String(viewerDbPath || "").trim()) {
|
|
@@ -2654,150 +2370,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
2654
2370
|
return args;
|
|
2655
2371
|
};
|
|
2656
2372
|
|
|
2657
|
-
const deliverRawEvent = async ({ sessionID, type, payload }) => {
|
|
2658
|
-
if (!rawEventsEnabled) {
|
|
2659
|
-
return true;
|
|
2660
|
-
}
|
|
2661
|
-
if (!sessionID || !type) {
|
|
2662
|
-
return false;
|
|
2663
|
-
}
|
|
2664
|
-
if (payload?._raw_enqueued) return true;
|
|
2665
|
-
const now = Date.now();
|
|
2666
|
-
let cachedEnvelope = payload && typeof payload === "object"
|
|
2667
|
-
? rawEventEnvelopes.get(payload)
|
|
2668
|
-
: null;
|
|
2669
|
-
if (!cachedEnvelope) {
|
|
2670
|
-
const builtEnvelope = buildRawEventEnvelope({
|
|
2671
|
-
sessionID,
|
|
2672
|
-
type,
|
|
2673
|
-
payload,
|
|
2674
|
-
cwd,
|
|
2675
|
-
project: resolveProjectName(project, cwd),
|
|
2676
|
-
startedAt: rawEventStartedAts.has(payload)
|
|
2677
|
-
? rawEventStartedAts.get(payload)
|
|
2678
|
-
: sessionStartedAt,
|
|
2679
|
-
nowMs: now,
|
|
2680
|
-
nowMono:
|
|
2681
|
-
typeof performance !== "undefined" && performance.now
|
|
2682
|
-
? performance.now()
|
|
2683
|
-
: null,
|
|
2684
|
-
nextEventId,
|
|
2685
|
-
captureContext: rawEventCaptureContexts.get(payload),
|
|
2686
|
-
});
|
|
2687
|
-
const serialized = JSON.stringify(builtEnvelope);
|
|
2688
|
-
cachedEnvelope = { body: JSON.parse(serialized), serialized };
|
|
2689
|
-
if (payload && typeof payload === "object") {
|
|
2690
|
-
rawEventEnvelopes.set(payload, cachedEnvelope);
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
const { body, serialized } = cachedEnvelope;
|
|
2694
|
-
if (!runtimeActive) {
|
|
2695
|
-
return persistRawEventForRetry({ body, serialized, payload, sessionID });
|
|
2696
|
-
}
|
|
2697
|
-
if (now < streamUnavailableUntil) {
|
|
2698
|
-
const durable = await persistRawEventForRetry({ body, serialized, payload, sessionID });
|
|
2699
|
-
try {
|
|
2700
|
-
await queueRawEventViaCli(body, serialized);
|
|
2701
|
-
await removeRawEventFromSpool({ eventId: body.event_id, payload });
|
|
2702
|
-
if (payload && typeof payload === "object") {
|
|
2703
|
-
payload._raw_enqueued = true;
|
|
2704
|
-
}
|
|
2705
|
-
await notifyRawEventDelivery({
|
|
2706
|
-
category: "connection",
|
|
2707
|
-
delivered: true,
|
|
2708
|
-
durable,
|
|
2709
|
-
sessionID,
|
|
2710
|
-
});
|
|
2711
|
-
return true;
|
|
2712
|
-
} catch {
|
|
2713
|
-
await logLine("raw_events.fallback.error category=connection");
|
|
2714
|
-
await notifyRawEventDelivery({
|
|
2715
|
-
category: "connection",
|
|
2716
|
-
delivered: false,
|
|
2717
|
-
durable,
|
|
2718
|
-
sessionID,
|
|
2719
|
-
});
|
|
2720
|
-
return false;
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
try {
|
|
2724
|
-
if (now - lastStatusCheckAt >= Math.max(1000, rawEventsStatusCheckMs)) {
|
|
2725
|
-
const statusResp = await fetchRawEventsStatus(rawEventsStatusUrl);
|
|
2726
|
-
if (!statusResp.ok) {
|
|
2727
|
-
// Release the unread body before bailing into the backoff path.
|
|
2728
|
-
discardResponseBody(statusResp);
|
|
2729
|
-
throw new Error(`raw-events status failed (${statusResp.status})`);
|
|
2730
|
-
}
|
|
2731
|
-
const statusJson = await statusResp.json();
|
|
2732
|
-
lastStatusAvailable = statusJson?.ingest?.available !== false;
|
|
2733
|
-
lastStatusCheckAt = now;
|
|
2734
|
-
}
|
|
2735
|
-
if (!lastStatusAvailable) {
|
|
2736
|
-
throw new Error("raw-events ingest unavailable");
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
const postResp = await fetch(rawEventsUrl, {
|
|
2740
|
-
method: "POST",
|
|
2741
|
-
headers: { "Content-Type": "application/json" },
|
|
2742
|
-
signal: AbortSignal.any([
|
|
2743
|
-
rawEventAbortController.signal,
|
|
2744
|
-
AbortSignal.timeout(RAW_EVENTS_STATUS_TIMEOUT_MS),
|
|
2745
|
-
]),
|
|
2746
|
-
body: JSON.stringify({
|
|
2747
|
-
...body,
|
|
2748
|
-
db_path: promptPackDbPath,
|
|
2749
|
-
identity_target: promptPackIdentityTarget,
|
|
2750
|
-
}),
|
|
2751
|
-
});
|
|
2752
|
-
if (!postResp.ok) {
|
|
2753
|
-
let responseBody = null;
|
|
2754
|
-
try {
|
|
2755
|
-
responseBody = await postResp.json();
|
|
2756
|
-
} catch {
|
|
2757
|
-
// Generic connection guidance below remains the safe fallback.
|
|
2758
|
-
}
|
|
2759
|
-
const postError = new Error(`raw-events post failed (${postResp.status})`);
|
|
2760
|
-
postError.rawEventFailureCategory = classifyRawEventViewerFailure(responseBody);
|
|
2761
|
-
throw postError;
|
|
2762
|
-
}
|
|
2763
|
-
streamUnavailableUntil = 0;
|
|
2764
|
-
lastStatusAvailable = true;
|
|
2765
|
-
await removeRawEventFromSpool({ eventId: body.event_id, payload });
|
|
2766
|
-
if (payload && typeof payload === "object") {
|
|
2767
|
-
payload._raw_enqueued = true;
|
|
2768
|
-
}
|
|
2769
|
-
return true;
|
|
2770
|
-
} catch (err) {
|
|
2771
|
-
if (!runtimeActive) {
|
|
2772
|
-
return persistRawEventForRetry({ body, serialized, payload, sessionID });
|
|
2773
|
-
}
|
|
2774
|
-
const category = err?.rawEventFailureCategory || "connection";
|
|
2775
|
-
streamUnavailableUntil = Date.now() + Math.max(1000, rawEventsBackoffMs);
|
|
2776
|
-
await logLine(`raw_events.error category=${category}`);
|
|
2777
|
-
const durable = await persistRawEventForRetry({ body, serialized, payload, sessionID });
|
|
2778
|
-
|
|
2779
|
-
let fallbackOk = false;
|
|
2780
|
-
try {
|
|
2781
|
-
await queueRawEventViaCli(body, serialized);
|
|
2782
|
-
await removeRawEventFromSpool({ eventId: body.event_id, payload });
|
|
2783
|
-
fallbackOk = true;
|
|
2784
|
-
} catch {
|
|
2785
|
-
await logLine(`raw_events.fallback.error category=${category}`);
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
if (fallbackOk) {
|
|
2789
|
-
if (payload && typeof payload === "object") {
|
|
2790
|
-
payload._raw_enqueued = true;
|
|
2791
|
-
}
|
|
2792
|
-
await notifyRawEventDelivery({ category, delivered: true, durable, sessionID });
|
|
2793
|
-
return true;
|
|
2794
|
-
}
|
|
2795
|
-
|
|
2796
|
-
await notifyRawEventDelivery({ category, delivered: false, durable, sessionID });
|
|
2797
|
-
return false;
|
|
2798
|
-
}
|
|
2799
|
-
};
|
|
2800
|
-
|
|
2801
2373
|
// Session context tracking for comprehensive memories
|
|
2802
2374
|
const createSessionContext = () => ({
|
|
2803
2375
|
firstPrompt: null,
|
|
@@ -3084,7 +2656,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3084
2656
|
};
|
|
3085
2657
|
|
|
3086
2658
|
const extractAssistantUsage = (event) => {
|
|
3087
|
-
if (
|
|
2659
|
+
if (event?.type !== "message.updated" || !event.messageInfo) {
|
|
3088
2660
|
return null;
|
|
3089
2661
|
}
|
|
3090
2662
|
const info = event.messageInfo;
|
|
@@ -3229,6 +2801,33 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3229
2801
|
const runCli = async (args, options = {}) =>
|
|
3230
2802
|
runCommand([runner, ...runnerArgs, ...args], options);
|
|
3231
2803
|
|
|
2804
|
+
rawEventDelivery = createRawEventDelivery({
|
|
2805
|
+
backoffMs: rawEventsBackoffMs,
|
|
2806
|
+
buildEnvelope: buildRawEventEnvelope,
|
|
2807
|
+
classifyViewerFailure: classifyRawEventViewerFailure,
|
|
2808
|
+
cwd,
|
|
2809
|
+
discardResponseBody,
|
|
2810
|
+
enabled: rawEventsEnabled,
|
|
2811
|
+
failureActions: RAW_EVENT_FAILURE_ACTIONS,
|
|
2812
|
+
fetchRawEventsStatus,
|
|
2813
|
+
hostLog,
|
|
2814
|
+
hostNotify,
|
|
2815
|
+
identityTarget: promptPackIdentityTarget,
|
|
2816
|
+
isActive: () => runtimeActive,
|
|
2817
|
+
logLine,
|
|
2818
|
+
nextEventId,
|
|
2819
|
+
projectName: resolveProjectName(project, cwd),
|
|
2820
|
+
promptPackDbPath,
|
|
2821
|
+
rawEventsStatusTimeoutMs: RAW_EVENTS_STATUS_TIMEOUT_MS,
|
|
2822
|
+
rawEventsStatusUrl,
|
|
2823
|
+
rawEventsUrl,
|
|
2824
|
+
sessionStartedAt: () => sessionStartedAt,
|
|
2825
|
+
statusCheckMs: rawEventsStatusCheckMs,
|
|
2826
|
+
spoolDrainLimit: rawEventSpoolDrainLimit,
|
|
2827
|
+
spoolHome: rawEventSpoolHome,
|
|
2828
|
+
spoolMaxEntries: rawEventSpoolMaxEntries,
|
|
2829
|
+
});
|
|
2830
|
+
|
|
3232
2831
|
const postViewerJson = async ({ url, operation, payload, validate }) => {
|
|
3233
2832
|
if (!viewerEnabled) {
|
|
3234
2833
|
return {
|
|
@@ -3363,26 +2962,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3363
2962
|
return { ok: true, body };
|
|
3364
2963
|
};
|
|
3365
2964
|
|
|
3366
|
-
const attemptMetadata = (identity, sessionID = null, promptNumber = promptCounter) => ({
|
|
3367
|
-
attempt_id: identity.attemptId,
|
|
3368
|
-
started_at: (() => {
|
|
3369
|
-
const existing = attemptStartedAt.get(identity.attemptId);
|
|
3370
|
-
if (existing) return existing;
|
|
3371
|
-
const created = new Date().toISOString();
|
|
3372
|
-
attemptStartedAt.set(identity.attemptId, created);
|
|
3373
|
-
while (attemptStartedAt.size > 2000) {
|
|
3374
|
-
const oldest = attemptStartedAt.keys().next().value;
|
|
3375
|
-
if (!oldest) break;
|
|
3376
|
-
attemptStartedAt.delete(oldest);
|
|
3377
|
-
}
|
|
3378
|
-
return created;
|
|
3379
|
-
})(),
|
|
3380
|
-
source: "opencode",
|
|
3381
|
-
...(sessionID ? { stream_id: String(sessionID), source_session_id: String(sessionID) } : {}),
|
|
3382
|
-
...(promptNumber > 0 ? { prompt_number: promptNumber } : {}),
|
|
3383
|
-
request_id: identity.requestId,
|
|
3384
|
-
});
|
|
3385
|
-
|
|
3386
2965
|
const runPromptPackLedger = async (payload, { viewerOnly = false } = {}) => {
|
|
3387
2966
|
const viewerPayload = {
|
|
3388
2967
|
...payload,
|
|
@@ -3422,152 +3001,28 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3422
3001
|
}
|
|
3423
3002
|
};
|
|
3424
3003
|
|
|
3425
|
-
const
|
|
3426
|
-
promptPackIdentity
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
return null;
|
|
3448
|
-
}
|
|
3449
|
-
if (failureCode === "injection_disabled") disabledInjectionRecorded.add(memoKey);
|
|
3450
|
-
const identity = skippedIdentity(
|
|
3451
|
-
failureCode,
|
|
3452
|
-
sessionID,
|
|
3453
|
-
surface,
|
|
3454
|
-
requestKey || (failureCode === "injection_disabled" ? "once" : `event-${++skippedAttemptCounter}`),
|
|
3455
|
-
);
|
|
3456
|
-
void runPromptPackLedger({
|
|
3457
|
-
action: "record",
|
|
3458
|
-
...attemptMetadata(identity, sessionID),
|
|
3459
|
-
retrieval_status: "skipped",
|
|
3460
|
-
failure_code: failureCode,
|
|
3461
|
-
failure_stage: "policy",
|
|
3462
|
-
});
|
|
3463
|
-
return identity.attemptId;
|
|
3464
|
-
};
|
|
3465
|
-
|
|
3466
|
-
const recordCachedPromptPack = (cached, { messageId, sessionID, surface = "message" } = {}) => {
|
|
3467
|
-
cached.reuseCount = (cached.reuseCount || 0) + 1;
|
|
3468
|
-
const identity = promptPackIdentity({
|
|
3469
|
-
sessionID: sessionID || "unknown",
|
|
3470
|
-
requestKey: `${messageId || "unknown"}:cache:${cached.reuseCount}`,
|
|
3471
|
-
surface,
|
|
3472
|
-
promptNumber: cached.promptNumber || promptCounter,
|
|
3473
|
-
queryHash: cached.queryHash || hashPromptPackQuery(""),
|
|
3474
|
-
});
|
|
3475
|
-
const ready = runPromptPackLedger({
|
|
3476
|
-
action: "cache_reuse",
|
|
3477
|
-
...attemptMetadata(identity, sessionID, cached.promptNumber || promptCounter),
|
|
3478
|
-
original_attempt_id: cached.attemptId,
|
|
3479
|
-
});
|
|
3480
|
-
return { attemptId: identity.attemptId, ready };
|
|
3481
|
-
};
|
|
3482
|
-
|
|
3483
|
-
const confirmPromptPackDelivery = (attemptId, deliveryStatus = "handed_off", evaluation) => {
|
|
3484
|
-
const delivery = {
|
|
3485
|
-
action: "delivery", attempt_id: attemptId, delivery_status: deliveryStatus,
|
|
3486
|
-
};
|
|
3487
|
-
void runPromptPackLedger({
|
|
3488
|
-
...delivery,
|
|
3489
|
-
...evaluation,
|
|
3490
|
-
}).then((result) => {
|
|
3491
|
-
// Retry additive fields only after the transport identifies that exact compatibility case.
|
|
3492
|
-
if (
|
|
3493
|
-
evaluation
|
|
3494
|
-
&& result?.transport === "viewer"
|
|
3495
|
-
&& result.failureKind === "viewer_contract_unsupported"
|
|
3496
|
-
) {
|
|
3497
|
-
return runPromptPackLedger(delivery, { viewerOnly: true });
|
|
3498
|
-
}
|
|
3499
|
-
// Older CLI fallbacks can reject optional fields. Preserve the original receipt;
|
|
3500
|
-
// never retry a Viewer policy/auth rejection through a different transport.
|
|
3501
|
-
if (evaluation && result?.transport === "cli" && result.exitCode !== 0) {
|
|
3502
|
-
return runPromptPackLedger(delivery);
|
|
3503
|
-
}
|
|
3504
|
-
}).catch(() => {});
|
|
3505
|
-
};
|
|
3506
|
-
|
|
3507
|
-
const createTransformDelivery = (deferred) => {
|
|
3508
|
-
const recordMeasurement = (measurement) => logLine(`inject.recall ${JSON.stringify(measurement)}`);
|
|
3509
|
-
if (!deferred) {
|
|
3510
|
-
return {
|
|
3511
|
-
complete: undefined,
|
|
3512
|
-
finish: async () => {},
|
|
3513
|
-
beginFinalization: async () => {},
|
|
3514
|
-
confirm: confirmPromptPackDelivery,
|
|
3515
|
-
recordMeasurement,
|
|
3516
|
-
};
|
|
3517
|
-
}
|
|
3518
|
-
let completion = null;
|
|
3519
|
-
let finalizationReady = null;
|
|
3520
|
-
let releaseFinalization = null;
|
|
3521
|
-
const pending = [];
|
|
3522
|
-
const commits = [];
|
|
3523
|
-
const measurements = [];
|
|
3524
|
-
const dispatch = ([attemptId, status, evaluation]) => {
|
|
3525
|
-
const deliveryStatus = completion === "failed" && (!status || status === "handed_off")
|
|
3526
|
-
? "failed"
|
|
3527
|
-
: status;
|
|
3528
|
-
confirmPromptPackDelivery(attemptId, deliveryStatus, evaluation);
|
|
3529
|
-
};
|
|
3530
|
-
const beginFinalization = async (sessionID) => {
|
|
3531
|
-
if (!finalizationReady) {
|
|
3532
|
-
finalizationReady = acquireSessionFinalization(sessionID).then((release) => {
|
|
3533
|
-
releaseFinalization = release;
|
|
3534
|
-
});
|
|
3535
|
-
}
|
|
3536
|
-
await finalizationReady;
|
|
3537
|
-
};
|
|
3538
|
-
const finish = async (status = "handed_off") => {
|
|
3539
|
-
if (completion) return;
|
|
3540
|
-
completion = status;
|
|
3541
|
-
const tasks = [];
|
|
3542
|
-
try {
|
|
3543
|
-
for (const commit of commits.splice(0)) {
|
|
3544
|
-
if (status === "handed_off") tasks.push(commit());
|
|
3545
|
-
}
|
|
3546
|
-
for (const confirmation of pending.splice(0)) dispatch(confirmation);
|
|
3547
|
-
for (const measurement of measurements.splice(0)) {
|
|
3548
|
-
const finalized = status === "handed_off" ? measurement : {
|
|
3549
|
-
...measurement, reason: "delivery_failed", new_tokens: 0, retained_tokens: 0,
|
|
3550
|
-
};
|
|
3551
|
-
tasks.push(recordMeasurement(finalized));
|
|
3552
|
-
}
|
|
3553
|
-
await Promise.all(tasks);
|
|
3554
|
-
} finally {
|
|
3555
|
-
releaseFinalization?.();
|
|
3556
|
-
releaseFinalization = null;
|
|
3557
|
-
}
|
|
3558
|
-
};
|
|
3559
|
-
return {
|
|
3560
|
-
commit: (commit) => { commits.push(commit); },
|
|
3561
|
-
recordMeasurement: (measurement) => { measurements.push(measurement); },
|
|
3562
|
-
beginFinalization,
|
|
3563
|
-
finish,
|
|
3564
|
-
complete: finish,
|
|
3565
|
-
confirm: (...confirmation) => {
|
|
3566
|
-
if (completion) dispatch(confirmation);
|
|
3567
|
-
else pending.push(confirmation);
|
|
3568
|
-
},
|
|
3569
|
-
};
|
|
3570
|
-
};
|
|
3004
|
+
const recallLifecycle = createRecallLifecycle({
|
|
3005
|
+
createIdentity: promptPackIdentity,
|
|
3006
|
+
emptyQueryHash: hashPromptPackQuery(""),
|
|
3007
|
+
runPromptPackLedger,
|
|
3008
|
+
logLine,
|
|
3009
|
+
getPromptCounter: () => promptCounter,
|
|
3010
|
+
defaultSurface: injectSurface,
|
|
3011
|
+
});
|
|
3012
|
+
const {
|
|
3013
|
+
injectionToastShown,
|
|
3014
|
+
messageInjectionCache,
|
|
3015
|
+
emptyRecallCache,
|
|
3016
|
+
attemptMetadata,
|
|
3017
|
+
recordSkippedPromptPack,
|
|
3018
|
+
recordCachedPromptPack,
|
|
3019
|
+
confirmPromptPackDelivery,
|
|
3020
|
+
createTransformDelivery,
|
|
3021
|
+
advancePromptPackRetryIdentity,
|
|
3022
|
+
getPromptPackRetryCount,
|
|
3023
|
+
getSuccessfulPromptPackArtifact,
|
|
3024
|
+
rememberSuccessfulPromptPackArtifact,
|
|
3025
|
+
} = recallLifecycle;
|
|
3571
3026
|
|
|
3572
3027
|
const showToast = async (message, variant = "warning") => {
|
|
3573
3028
|
if (backendUpdatePolicy === "off") {
|
|
@@ -3578,7 +3033,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3578
3033
|
}
|
|
3579
3034
|
try {
|
|
3580
3035
|
await hostNotify({ message, variant });
|
|
3581
|
-
} catch
|
|
3036
|
+
} catch {
|
|
3582
3037
|
// best-effort only
|
|
3583
3038
|
}
|
|
3584
3039
|
};
|
|
@@ -3615,7 +3070,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3615
3070
|
}
|
|
3616
3071
|
|
|
3617
3072
|
const versionResult = await runCli(["version"]);
|
|
3618
|
-
if (
|
|
3073
|
+
if (versionResult?.exitCode !== 0) {
|
|
3619
3074
|
await logLine(
|
|
3620
3075
|
`compat.version_check_failed exit=${versionResult?.exitCode ?? "unknown"} stderr=${
|
|
3621
3076
|
versionResult?.stderr ? redactLog(versionResult.stderr.trim()) : ""
|
|
@@ -3856,35 +3311,6 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3856
3311
|
return masked.length > limit ? `${masked.slice(0, limit)}…` : masked;
|
|
3857
3312
|
};
|
|
3858
3313
|
|
|
3859
|
-
const advancePromptPackRetryIdentity = (attemptKey) => {
|
|
3860
|
-
promptPackRetryCounts.set(
|
|
3861
|
-
attemptKey,
|
|
3862
|
-
(promptPackRetryCounts.get(attemptKey) || 0) + 1
|
|
3863
|
-
);
|
|
3864
|
-
while (promptPackRetryCounts.size > 2000) {
|
|
3865
|
-
const oldest = promptPackRetryCounts.keys().next().value;
|
|
3866
|
-
if (!oldest) break;
|
|
3867
|
-
promptPackRetryCounts.delete(oldest);
|
|
3868
|
-
}
|
|
3869
|
-
};
|
|
3870
|
-
|
|
3871
|
-
const rememberSuccessfulPromptPackArtifact = (
|
|
3872
|
-
attemptKey,
|
|
3873
|
-
retryCount,
|
|
3874
|
-
fingerprint
|
|
3875
|
-
) => {
|
|
3876
|
-
successfulPromptPackArtifacts.delete(attemptKey);
|
|
3877
|
-
successfulPromptPackArtifacts.set(attemptKey, {
|
|
3878
|
-
retryCount,
|
|
3879
|
-
fingerprint,
|
|
3880
|
-
});
|
|
3881
|
-
while (successfulPromptPackArtifacts.size > 2000) {
|
|
3882
|
-
const oldest = successfulPromptPackArtifacts.keys().next().value;
|
|
3883
|
-
if (!oldest) break;
|
|
3884
|
-
successfulPromptPackArtifacts.delete(oldest);
|
|
3885
|
-
}
|
|
3886
|
-
};
|
|
3887
|
-
|
|
3888
3314
|
let fallbackEvaluationSessionId = nextEventId();
|
|
3889
3315
|
const buildInjectedContext = async (query, context = {}) => {
|
|
3890
3316
|
const requestPackBudget = reserveContextPrefixBudget(context.tokenBudget ?? injectTokenBudget);
|
|
@@ -3930,7 +3356,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3930
3356
|
promptCounter,
|
|
3931
3357
|
queryHash,
|
|
3932
3358
|
]);
|
|
3933
|
-
let retryCount =
|
|
3359
|
+
let retryCount = getPromptPackRetryCount(attemptKey);
|
|
3934
3360
|
const resolveIdentity = () => promptPackIdentity({
|
|
3935
3361
|
sessionID,
|
|
3936
3362
|
requestKey: retryCount > 0
|
|
@@ -3955,7 +3381,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
3955
3381
|
};
|
|
3956
3382
|
}
|
|
3957
3383
|
const runPack = async () => {
|
|
3958
|
-
|
|
3384
|
+
const packArgs = buildPackArgs({
|
|
3959
3385
|
query,
|
|
3960
3386
|
filesModified: sessionContext.filesModified,
|
|
3961
3387
|
injectLimit,
|
|
@@ -4039,7 +3465,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4039
3465
|
}
|
|
4040
3466
|
: null;
|
|
4041
3467
|
advancePromptPackRetryIdentity(attemptKey);
|
|
4042
|
-
retryCount =
|
|
3468
|
+
retryCount = getPromptPackRetryCount(attemptKey);
|
|
4043
3469
|
identity = resolveIdentity();
|
|
4044
3470
|
metadata = attemptMetadata(identity, requesterHostSessionID);
|
|
4045
3471
|
({ packArgs, result } = await runPack());
|
|
@@ -4082,7 +3508,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4082
3508
|
});
|
|
4083
3509
|
}
|
|
4084
3510
|
advancePromptPackRetryIdentity(attemptKey);
|
|
4085
|
-
retryCount =
|
|
3511
|
+
retryCount = getPromptPackRetryCount(attemptKey);
|
|
4086
3512
|
({
|
|
4087
3513
|
packArgs,
|
|
4088
3514
|
result,
|
|
@@ -4102,7 +3528,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4102
3528
|
}
|
|
4103
3529
|
}
|
|
4104
3530
|
if (packText) {
|
|
4105
|
-
const previous =
|
|
3531
|
+
const previous = getSuccessfulPromptPackArtifact(attemptKey);
|
|
4106
3532
|
if (
|
|
4107
3533
|
previous?.retryCount === retryCount
|
|
4108
3534
|
&& previous.fingerprint !== artifactFingerprint
|
|
@@ -4122,7 +3548,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4122
3548
|
ledgerConflict,
|
|
4123
3549
|
};
|
|
4124
3550
|
advancePromptPackRetryIdentity(attemptKey);
|
|
4125
|
-
retryCount =
|
|
3551
|
+
retryCount = getPromptPackRetryCount(attemptKey);
|
|
4126
3552
|
identity = resolveIdentity();
|
|
4127
3553
|
metadata = attemptMetadata(identity, requesterHostSessionID);
|
|
4128
3554
|
({ packArgs, result } = await runPack());
|
|
@@ -4142,7 +3568,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4142
3568
|
// recorded that terminal outcome; do not misclassify it as a decode
|
|
4143
3569
|
// failure or fall back to stale non-empty context.
|
|
4144
3570
|
injectedIdentity = identity;
|
|
4145
|
-
} else if (
|
|
3571
|
+
} else if (result?.exitCode !== 0 || !packText) {
|
|
4146
3572
|
const malformedSuccess = result?.exitCode === 0;
|
|
4147
3573
|
const exitCode = result?.exitCode ?? "unknown";
|
|
4148
3574
|
const stderr = redactLog(result?.stderr ? result.stderr.trim() : "");
|
|
@@ -4185,7 +3611,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4185
3611
|
}
|
|
4186
3612
|
}
|
|
4187
3613
|
}
|
|
4188
|
-
if (
|
|
3614
|
+
if (result?.exitCode !== 0) {
|
|
4189
3615
|
const exitCode = result?.exitCode ?? "unknown";
|
|
4190
3616
|
const stderr = redactLog(result?.stderr ? result.stderr.trim() : "");
|
|
4191
3617
|
const stdout = redactLog(result?.stdout ? result.stdout.trim() : "");
|
|
@@ -4336,7 +3762,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4336
3762
|
// not a git repo. The catch below leaves version as "unknown".
|
|
4337
3763
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4338
3764
|
}).trim();
|
|
4339
|
-
} catch
|
|
3765
|
+
} catch {
|
|
4340
3766
|
// Ignore - version will remain 'unknown'
|
|
4341
3767
|
}
|
|
4342
3768
|
|
|
@@ -4386,7 +3812,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4386
3812
|
}
|
|
4387
3813
|
try {
|
|
4388
3814
|
return JSON.stringify(value);
|
|
4389
|
-
} catch
|
|
3815
|
+
} catch {
|
|
4390
3816
|
return String(value);
|
|
4391
3817
|
}
|
|
4392
3818
|
};
|
|
@@ -4472,7 +3898,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4472
3898
|
rawEventDeliveryCount++;
|
|
4473
3899
|
const delivery = rawEventDeliveryTail.then(async () => {
|
|
4474
3900
|
await rawEventPreparations.get(input.payload);
|
|
4475
|
-
return
|
|
3901
|
+
return rawEventDelivery.deliver(input);
|
|
4476
3902
|
});
|
|
4477
3903
|
rawEventDeliveries.set(input.payload, delivery);
|
|
4478
3904
|
rawEventDeliveryTail = delivery.catch(() => {}).finally(() => {
|
|
@@ -4501,19 +3927,19 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4501
3927
|
});
|
|
4502
3928
|
const rawEventId =
|
|
4503
3929
|
adapterAnnotatedEvent?._adapter?.event_id ||
|
|
4504
|
-
|
|
3930
|
+
adapterAnnotatedEvent?._raw_event_id ||
|
|
4505
3931
|
nextEventId();
|
|
4506
3932
|
const queuedEvent = {
|
|
4507
3933
|
...adapterAnnotatedEvent,
|
|
4508
3934
|
_raw_event_id: rawEventId,
|
|
4509
3935
|
_raw_session_id: resolvedSessionID,
|
|
4510
3936
|
};
|
|
4511
|
-
|
|
3937
|
+
rawEventDelivery.setStartedAt(queuedEvent, sessionStartedAt);
|
|
4512
3938
|
recordEvent(queuedEvent);
|
|
4513
3939
|
if (prompt && rawEventsEnabled && host.resolveCaptureContext) {
|
|
4514
3940
|
const preparation = Promise.resolve().then(() => host.resolveCaptureContext(prompt))
|
|
4515
3941
|
.then((context) => {
|
|
4516
|
-
if (context)
|
|
3942
|
+
if (context) rawEventDelivery.setCaptureContext(queuedEvent, context);
|
|
4517
3943
|
}).catch(() => {
|
|
4518
3944
|
// Optional provenance must never discard an otherwise valid prompt.
|
|
4519
3945
|
});
|
|
@@ -4635,7 +4061,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4635
4061
|
);
|
|
4636
4062
|
await logLine(`flush.ok count=${batch.events.length} dropped=0`);
|
|
4637
4063
|
}
|
|
4638
|
-
if (runtimeActive) await
|
|
4064
|
+
if (runtimeActive) await rawEventDelivery.drainSpool();
|
|
4639
4065
|
} catch {
|
|
4640
4066
|
// Flush delivery and diagnostics must not reject into OpenCode hooks.
|
|
4641
4067
|
}
|
|
@@ -4668,7 +4094,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4668
4094
|
|
|
4669
4095
|
const flushEmptyQueue = async () => {
|
|
4670
4096
|
try {
|
|
4671
|
-
await
|
|
4097
|
+
await rawEventDelivery.drainSpool();
|
|
4672
4098
|
await logLine("flush.skip empty");
|
|
4673
4099
|
} catch {
|
|
4674
4100
|
// Empty flushes follow the same non-rejecting hook contract.
|
|
@@ -4747,14 +4173,14 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
4747
4173
|
return scheduleQueuedEvents({ owner: activeSessionID });
|
|
4748
4174
|
};
|
|
4749
4175
|
|
|
4750
|
-
void
|
|
4176
|
+
void rawEventDelivery.drainSpool();
|
|
4751
4177
|
|
|
4752
4178
|
const deactivateRuntime = () => {
|
|
4753
4179
|
if (!runtimeActive) return;
|
|
4754
4180
|
runtimeActive = false;
|
|
4755
4181
|
clearTimeout(updateCheckTimer);
|
|
4756
4182
|
stopHealthCheck();
|
|
4757
|
-
|
|
4183
|
+
rawEventDelivery.abort();
|
|
4758
4184
|
releasePluginRegistration();
|
|
4759
4185
|
};
|
|
4760
4186
|
|
|
@@ -5003,9 +4429,7 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
5003
4429
|
sessionStartedAt = new Date().toISOString();
|
|
5004
4430
|
promptCounter = 0;
|
|
5005
4431
|
fallbackEvaluationSessionId = nextEventId();
|
|
5006
|
-
|
|
5007
|
-
disabledInjectionRecorded.delete("message:unknown");
|
|
5008
|
-
disabledInjectionRecorded.delete("system:unknown");
|
|
4432
|
+
recallLifecycle.startSession();
|
|
5009
4433
|
lastPromptText = null;
|
|
5010
4434
|
if (sessionID) {
|
|
5011
4435
|
await capturePendingPrompts({
|
|
@@ -5190,19 +4614,9 @@ export const createCodememRuntime = async ({ location, host }) => {
|
|
|
5190
4614
|
sessionStartSessionID = null;
|
|
5191
4615
|
}
|
|
5192
4616
|
if (sessionID) {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
lastToastAtBySession.delete(key);
|
|
5196
|
-
}
|
|
5197
|
-
}
|
|
5198
|
-
injectionToastShown.delete(sessionID);
|
|
5199
|
-
messageInjectionCache.delete(sessionID);
|
|
5200
|
-
emptyRecallCache.delete(sessionID);
|
|
4617
|
+
rawEventDelivery.clearSession(sessionID);
|
|
4618
|
+
recallLifecycle.clearSession(sessionID);
|
|
5201
4619
|
compactionInjectionSkips.delete(sessionID);
|
|
5202
|
-
disabledInjectionRecorded.delete(`message:${sessionID}`);
|
|
5203
|
-
disabledInjectionRecorded.delete(`system:${sessionID}`);
|
|
5204
|
-
latestPolicySkips.delete(`message:${sessionID}`);
|
|
5205
|
-
latestPolicySkips.delete(`system:${sessionID}`);
|
|
5206
4620
|
for (const key of failedToolCaptured) {
|
|
5207
4621
|
if (key.startsWith(`${sessionID}:`)) {
|
|
5208
4622
|
failedToolCaptured.delete(key);
|
|
@@ -5333,6 +4747,8 @@ export const __testUtils = {
|
|
|
5333
4747
|
rejectsInternalLedgerFlag,
|
|
5334
4748
|
classifyFallbackCommandResult,
|
|
5335
4749
|
classifyRawEventViewerFailure,
|
|
4750
|
+
classifyRawEventTransportCause,
|
|
4751
|
+
describeRawEventViewerFailure,
|
|
5336
4752
|
detectRunner,
|
|
5337
4753
|
PROMPT_TRANSPORT_PROTOCOL_RANGE,
|
|
5338
4754
|
normalizePromptTransportProtocolRange,
|