@codemem/opencode-plugin 0.44.0-beta.1 → 0.44.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/plugins/codemem.js +462 -104
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const TRUTHY_VALUES = ["1", "true", "yes"];
|
|
27
27
|
const DISABLED_VALUES = ["0", "false", "off"];
|
|
28
|
-
const PINNED_BACKEND_VERSION = "0.44.
|
|
28
|
+
const PINNED_BACKEND_VERSION = "0.44.1";
|
|
29
29
|
const COMPAT_CHECK_DELAY_MS = 1500;
|
|
30
30
|
const COMPAT_CHECK_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
31
31
|
const MAX_UPDATE_STATUS_BYTES = 16 * 1024;
|
|
@@ -35,7 +35,6 @@ const RELEASE_VERSION =
|
|
|
35
35
|
/^(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-]+)*)?$/;
|
|
36
36
|
const CODEMEM_CONTEXT_PART_ID_PREFIX = "codemem-context-";
|
|
37
37
|
const MAX_MESSAGE_INJECTION_CACHE_SESSIONS = 20;
|
|
38
|
-
const MAX_MESSAGE_INJECTION_CACHE_MESSAGES = 100;
|
|
39
38
|
const COMPACTION_INJECTION_SKIP_TTL_MS = 30 * 1000;
|
|
40
39
|
const MAX_WORKING_SET_PATH_CHARS = 400;
|
|
41
40
|
const VIEWER_HEALTH_CHECK_INTERVAL_MS = 60_000;
|
|
@@ -43,6 +42,9 @@ const VIEWER_HEALTH_TIMEOUT_MS = 5_000;
|
|
|
43
42
|
const VIEWER_HEALTH_RESTART_THRESHOLD = 3;
|
|
44
43
|
const VIEWER_HEALTH_RESTART_COOLDOWN_MS = 5 * 60_000;
|
|
45
44
|
const RAW_EVENTS_STATUS_TIMEOUT_MS = 5_000;
|
|
45
|
+
const DEFAULT_INJECT_TOKEN_BUDGET = 800;
|
|
46
|
+
const DEFAULT_RETAINED_TOKEN_BUDGET = Infinity;
|
|
47
|
+
const CODEMEM_CONTEXT_PREFIX = "[codemem context]\n";
|
|
46
48
|
const DEFAULT_EMBEDDING_MODEL = "Xenova/bge-small-en-v1.5";
|
|
47
49
|
const DEFAULT_EMBEDDING_REVISION = "ea104dacec62c0de699686887e3f920caeb4f3e3";
|
|
48
50
|
const PLUGIN_REGISTRATIONS_KEY = Symbol.for("codemem.opencode-plugin.registrations");
|
|
@@ -789,7 +791,7 @@ const parsePackOutput = (result) => {
|
|
|
789
791
|
? Number(metrics.total_items)
|
|
790
792
|
: null;
|
|
791
793
|
const candidatePackText = succeeded && itemCount !== 0
|
|
792
|
-
? String(payload?.pack_text || "")
|
|
794
|
+
? String(payload?.pack_text || "")
|
|
793
795
|
: "";
|
|
794
796
|
return {
|
|
795
797
|
conflictPackText: ledgerConflict ? candidatePackText : "",
|
|
@@ -836,6 +838,7 @@ const isValidLedgerFailureHttpPayload = (payload) =>
|
|
|
836
838
|
&& (
|
|
837
839
|
payload.errorCode === "retrieval_ledger_write_failed"
|
|
838
840
|
|| payload.errorCode === "retrieval_ledger_delivery_write_failed"
|
|
841
|
+
|| payload.errorCode === "automatic_recall_write_failed"
|
|
839
842
|
)
|
|
840
843
|
&& typeof payload.reason === "string";
|
|
841
844
|
|
|
@@ -927,9 +930,10 @@ const isViewerPolicyOrAuthFailurePayload = (payload) => {
|
|
|
927
930
|
].includes(code);
|
|
928
931
|
};
|
|
929
932
|
|
|
930
|
-
const viewerFailureClassification = (cause, disposition) => ({
|
|
933
|
+
const viewerFailureClassification = (cause, disposition, kind = null) => ({
|
|
931
934
|
cause,
|
|
932
935
|
disposition,
|
|
936
|
+
kind,
|
|
933
937
|
retryable: disposition !== "terminal",
|
|
934
938
|
});
|
|
935
939
|
|
|
@@ -966,6 +970,7 @@ const classifyViewerHttpFailure = ({
|
|
|
966
970
|
kind: "viewer_contract_unsupported",
|
|
967
971
|
compatibleProfile,
|
|
968
972
|
}),
|
|
973
|
+
"viewer_contract_unsupported",
|
|
969
974
|
);
|
|
970
975
|
}
|
|
971
976
|
if (operation === "prompt-pack-ledger" && isValidLedgerFailureHttpPayload(body)) {
|
|
@@ -1018,6 +1023,7 @@ const buildPackHttpBody = ({
|
|
|
1018
1023
|
dbPath,
|
|
1019
1024
|
identityTarget,
|
|
1020
1025
|
attempt,
|
|
1026
|
+
automaticContext,
|
|
1021
1027
|
}) => ({
|
|
1022
1028
|
context: query,
|
|
1023
1029
|
limit: injectLimit !== null && Number.isFinite(injectLimit) && injectLimit > 0
|
|
@@ -1038,6 +1044,7 @@ const buildPackHttpBody = ({
|
|
|
1038
1044
|
.map((value) => String(value || "").trim())
|
|
1039
1045
|
.filter((value) => value && value.length <= MAX_WORKING_SET_PATH_CHARS),
|
|
1040
1046
|
attempt,
|
|
1047
|
+
...(automaticContext !== undefined ? { automatic_context: automaticContext } : {}),
|
|
1041
1048
|
});
|
|
1042
1049
|
|
|
1043
1050
|
const canonicalJson = (value) => {
|
|
@@ -1226,16 +1233,11 @@ const getSessionMessageInjectionCache = (messageInjectionCache, sessionID) => {
|
|
|
1226
1233
|
return sessionCache;
|
|
1227
1234
|
};
|
|
1228
1235
|
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
if (!oldestKey) break;
|
|
1234
|
-
sessionCache.delete(oldestKey);
|
|
1236
|
+
const normalizeInjectedMessageParts = (messages, sessionCache, { pruneAbsent = true } = {}) => {
|
|
1237
|
+
const presentIds = new Set(messages.map(resolveEntryMessageId).filter(Boolean));
|
|
1238
|
+
for (const id of sessionCache?.keys() || []) {
|
|
1239
|
+
if (pruneAbsent && !presentIds.has(id)) sessionCache.delete(id);
|
|
1235
1240
|
}
|
|
1236
|
-
};
|
|
1237
|
-
|
|
1238
|
-
const normalizeInjectedMessageParts = (messages, sessionCache) => {
|
|
1239
1241
|
for (let index = 0; index < messages.length; index += 1) {
|
|
1240
1242
|
const entry = messages[index];
|
|
1241
1243
|
if (!isUserMessageEntry(entry) || !Array.isArray(entry.parts)) {
|
|
@@ -1243,24 +1245,19 @@ const normalizeInjectedMessageParts = (messages, sessionCache) => {
|
|
|
1243
1245
|
}
|
|
1244
1246
|
|
|
1245
1247
|
const messageId = resolveEntryMessageId(entry);
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
sessionCache.set(messageId, { text: existingText, attemptId: null, reconstructed: true });
|
|
1260
|
-
trimSessionMessageInjectionCache(sessionCache);
|
|
1261
|
-
}
|
|
1262
|
-
if (retainedParts.length !== entry.parts.length) {
|
|
1263
|
-
entry.parts.splice(0, entry.parts.length, ...retainedParts);
|
|
1248
|
+
const parts = entry.parts.filter(isCodememContextPart);
|
|
1249
|
+
if (parts.length && sessionCache && messageId) {
|
|
1250
|
+
const cached = sessionCache.get(messageId);
|
|
1251
|
+
sessionCache.set(messageId, {
|
|
1252
|
+
...cached,
|
|
1253
|
+
text: parts[0].text,
|
|
1254
|
+
parts: parts.map((part) => {
|
|
1255
|
+
const recall = retainedPartRecall(part, cached?.parts);
|
|
1256
|
+
return recall
|
|
1257
|
+
? { ...part, metadata: { ...part.metadata, codemem: recall } }
|
|
1258
|
+
: { ...part };
|
|
1259
|
+
}),
|
|
1260
|
+
});
|
|
1264
1261
|
}
|
|
1265
1262
|
}
|
|
1266
1263
|
};
|
|
@@ -1275,6 +1272,7 @@ const buildInjectedContextPart = (entry, index, text, options = {}) => {
|
|
|
1275
1272
|
type: "text",
|
|
1276
1273
|
text,
|
|
1277
1274
|
synthetic: true,
|
|
1275
|
+
...(options.recall ? { metadata: { codemem: options.recall } } : {}),
|
|
1278
1276
|
};
|
|
1279
1277
|
};
|
|
1280
1278
|
|
|
@@ -1315,7 +1313,10 @@ const appendCachedInjectedContextParts = async (
|
|
|
1315
1313
|
cacheReuseReady = typeof reuse === "object" ? reuse?.ready : null;
|
|
1316
1314
|
}
|
|
1317
1315
|
try {
|
|
1318
|
-
entry.parts.
|
|
1316
|
+
if (!entry.parts.some(isCodememContextPart)) {
|
|
1317
|
+
const parts = cached?.parts || [buildInjectedContextPart(entry, index, text, { messageId, sessionID })];
|
|
1318
|
+
entry.parts.push(...parts.map((part) => ({ ...part })));
|
|
1319
|
+
}
|
|
1319
1320
|
} catch (error) {
|
|
1320
1321
|
if (messageId === options.latestMessageId && attemptId && options.confirmDelivery) {
|
|
1321
1322
|
if (cacheReuseReady) {
|
|
@@ -1342,6 +1343,11 @@ const appendCachedInjectedContextParts = async (
|
|
|
1342
1343
|
return appended;
|
|
1343
1344
|
};
|
|
1344
1345
|
|
|
1346
|
+
const scopeMessagesToSession = (messages, sessionID) => messages.filter((entry) => {
|
|
1347
|
+
const entrySession = resolveEntrySessionID(entry);
|
|
1348
|
+
return !entrySession || !sessionID || entrySession === sessionID;
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1345
1351
|
const applyInjectedContextToMessages = async ({
|
|
1346
1352
|
injectEnabled,
|
|
1347
1353
|
input,
|
|
@@ -1355,40 +1361,82 @@ const applyInjectedContextToMessages = async ({
|
|
|
1355
1361
|
confirmDelivery,
|
|
1356
1362
|
recordCacheReuse,
|
|
1357
1363
|
recordSkipped,
|
|
1364
|
+
injectTokenBudget = DEFAULT_INJECT_TOKEN_BUDGET,
|
|
1365
|
+
retainedTokenBudget = DEFAULT_RETAINED_TOKEN_BUDGET,
|
|
1366
|
+
workingSet = [],
|
|
1367
|
+
recordMeasurement,
|
|
1358
1368
|
}) => {
|
|
1359
1369
|
const hasMessages = Array.isArray(output?.messages);
|
|
1360
|
-
const
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1370
|
+
const hookSessionID = input?.sessionID || null;
|
|
1371
|
+
const hookMessages = hasMessages ? scopeMessagesToSession(output.messages, hookSessionID) : [];
|
|
1372
|
+
const latestUser = findLatestUserMessage(hookMessages);
|
|
1373
|
+
const sessionID = hookSessionID || (latestUser ? resolveEntrySessionID(latestUser.entry) : null);
|
|
1374
|
+
// Both transforms use the hook session when an entry omits session identity.
|
|
1375
|
+
const messages = scopeMessagesToSession(hookMessages, sessionID);
|
|
1376
|
+
const report = (reason, newTokens = 0, duplicates = 0) => {
|
|
1377
|
+
const measurement = {
|
|
1378
|
+
new_tokens: newTokens,
|
|
1379
|
+
retained_tokens: countRetainedInjectionTokens(messages),
|
|
1380
|
+
duplicates_omitted: duplicates,
|
|
1381
|
+
reason,
|
|
1382
|
+
};
|
|
1383
|
+
try { void Promise.resolve(recordMeasurement?.(measurement)).catch(() => {}); } catch { /* best effort */ }
|
|
1384
|
+
};
|
|
1364
1385
|
if (!injectEnabled) {
|
|
1365
1386
|
recordSkipped?.("injection_disabled", sessionID);
|
|
1387
|
+
report("injection_disabled");
|
|
1366
1388
|
return false;
|
|
1367
1389
|
}
|
|
1368
1390
|
if (!hasMessages) {
|
|
1391
|
+
report("missing_history");
|
|
1369
1392
|
return false;
|
|
1370
1393
|
}
|
|
1371
|
-
|
|
1372
1394
|
if (consumeCompactionInjectionSkip(compactionInjectionSkips, sessionID)) {
|
|
1373
|
-
|
|
1395
|
+
const cache = getSessionMessageInjectionCache(messageInjectionCache, sessionID);
|
|
1396
|
+
normalizeInjectedMessageParts(messages, cache, { pruneAbsent: false });
|
|
1374
1397
|
recordSkipped?.("compaction_skipped", sessionID);
|
|
1398
|
+
report("compaction_skipped");
|
|
1375
1399
|
return false;
|
|
1376
1400
|
}
|
|
1377
1401
|
|
|
1378
1402
|
if (!latestUser) {
|
|
1403
|
+
report("missing_user");
|
|
1379
1404
|
return false;
|
|
1380
1405
|
}
|
|
1381
1406
|
|
|
1382
1407
|
const sessionCache = getSessionMessageInjectionCache(messageInjectionCache, sessionID);
|
|
1383
|
-
normalizeInjectedMessageParts(
|
|
1408
|
+
normalizeInjectedMessageParts(messages, sessionCache);
|
|
1384
1409
|
|
|
1385
1410
|
const latestMessageId = resolveEntryMessageId(latestUser.entry);
|
|
1386
1411
|
const canReplay = Boolean(sessionCache && latestMessageId);
|
|
1387
1412
|
const latestCached = canReplay ? sessionCache.get(latestMessageId) : null;
|
|
1388
|
-
const latestWasCached = Boolean(latestCached)
|
|
1413
|
+
const latestWasCached = Boolean(latestCached) || latestUser.entry.parts?.some(isCodememContextPart);
|
|
1414
|
+
if (sessionCache) {
|
|
1415
|
+
try {
|
|
1416
|
+
await appendCachedInjectedContextParts(messages, sessionCache, sessionID, {
|
|
1417
|
+
latestMessageId, confirmDelivery, recordCacheReuse,
|
|
1418
|
+
});
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
report("delivery_failed");
|
|
1421
|
+
throw error;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
const retainedTokens = countRetainedInjectionTokens(messages);
|
|
1425
|
+
const fullBudget = Math.min(injectTokenBudget, Math.max(0, retainedTokenBudget - retainedTokens));
|
|
1389
1426
|
let latestWasBuilt = false;
|
|
1427
|
+
let newlyDelivered = 0;
|
|
1428
|
+
let duplicates = 0;
|
|
1429
|
+
let reason = latestWasCached ? "replay" : "no_context";
|
|
1390
1430
|
if (!latestWasCached) {
|
|
1391
|
-
|
|
1431
|
+
if (reserveContextPrefixBudget(fullBudget) === null) {
|
|
1432
|
+
if (reserveContextPrefixBudget(injectTokenBudget) !== null) {
|
|
1433
|
+
recordSkipped?.("allowance_exhausted", sessionID);
|
|
1434
|
+
}
|
|
1435
|
+
report("allowance_exhausted");
|
|
1436
|
+
return retainedTokens > 0;
|
|
1437
|
+
}
|
|
1438
|
+
const firstUser = findFirstUserMessage(messages);
|
|
1439
|
+
const workingContext = workingContextDigest(messages, workingSet);
|
|
1392
1440
|
const query = resolveInjectQuery({
|
|
1393
1441
|
firstPrompt: firstUser ? extractMessageText(firstUser.entry) : null,
|
|
1394
1442
|
lastPromptText: extractMessageText(latestUser.entry),
|
|
@@ -1396,36 +1444,62 @@ const applyInjectedContextToMessages = async ({
|
|
|
1396
1444
|
const injected = await buildInjectedContext(query, {
|
|
1397
1445
|
sessionID,
|
|
1398
1446
|
requestKey: latestMessageId || fallbackEntryMessageId(latestUser.entry, latestUser.index),
|
|
1447
|
+
fallbackTurn: sessionID && latestMessageId ? null : latestUser.index,
|
|
1399
1448
|
surface: "message",
|
|
1449
|
+
tokenBudget: fullBudget,
|
|
1450
|
+
retainedItems: retainedMemoryFingerprints(messages, sessionCache),
|
|
1451
|
+
retainedMetadata: retainedMetadataGaps(messages, sessionCache),
|
|
1452
|
+
workingContext,
|
|
1453
|
+
continuationOnly: isContinuationOnly(extractMessageText(latestUser.entry), messages, workingContext, sessionCache),
|
|
1400
1454
|
});
|
|
1401
|
-
|
|
1455
|
+
// Another transform may have delivered while retrieval was awaiting I/O.
|
|
1456
|
+
if (sessionCache) {
|
|
1457
|
+
await appendCachedInjectedContextParts(messages, sessionCache, sessionID, {
|
|
1458
|
+
latestMessageId, confirmDelivery, recordCacheReuse,
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
if (latestUser.entry.parts?.some(isCodememContextPart)) {
|
|
1462
|
+
duplicates = injected?.duplicates || 0;
|
|
1463
|
+
if (injected?.attemptId) {
|
|
1464
|
+
confirmDelivery?.(injected.attemptId, "unknown", injected.evaluation);
|
|
1465
|
+
}
|
|
1466
|
+
report("replay", 0, duplicates);
|
|
1467
|
+
return true;
|
|
1468
|
+
}
|
|
1469
|
+
const deliveryBudget = Math.min(fullBudget, Math.max(
|
|
1470
|
+
0, retainedTokenBudget - countRetainedInjectionTokens(messages),
|
|
1471
|
+
));
|
|
1472
|
+
duplicates = injected?.duplicates || 0;
|
|
1473
|
+
reason = injected?.skipReason || "no_context";
|
|
1474
|
+
if (injected?.text && estimateTokens(injected.text) <= deliveryBudget) {
|
|
1402
1475
|
latestWasBuilt = true;
|
|
1403
|
-
if (
|
|
1404
|
-
sessionCache.set(latestMessageId, {
|
|
1405
|
-
text: injected.text,
|
|
1406
|
-
attemptId: injected.attemptId || null,
|
|
1407
|
-
requestId: injected.requestId || null,
|
|
1408
|
-
queryHash: injected.queryHash || null,
|
|
1409
|
-
promptNumber: injected.promptNumber || 0,
|
|
1410
|
-
reuseCount: 0,
|
|
1411
|
-
});
|
|
1412
|
-
trimSessionMessageInjectionCache(sessionCache);
|
|
1413
|
-
} else if (Array.isArray(latestUser.entry.parts)) {
|
|
1476
|
+
if (Array.isArray(latestUser.entry.parts)) {
|
|
1414
1477
|
try {
|
|
1415
1478
|
latestUser.entry.parts.push(
|
|
1416
1479
|
buildInjectedContextPart(latestUser.entry, latestUser.index, injected.text, {
|
|
1417
1480
|
messageId: latestMessageId || undefined,
|
|
1418
1481
|
sessionID: sessionID || undefined,
|
|
1482
|
+
recall: injected.recall,
|
|
1419
1483
|
})
|
|
1420
1484
|
);
|
|
1421
1485
|
} catch (error) {
|
|
1422
1486
|
if (injected.attemptId) {
|
|
1423
|
-
confirmDelivery?.(injected.attemptId, "failed");
|
|
1487
|
+
confirmDelivery?.(injected.attemptId, "failed", injected.evaluation);
|
|
1424
1488
|
}
|
|
1489
|
+
report("delivery_failed", 0, duplicates);
|
|
1425
1490
|
throw error;
|
|
1426
1491
|
}
|
|
1492
|
+
newlyDelivered = estimateTokens(injected.text);
|
|
1493
|
+
reason = "delivered";
|
|
1427
1494
|
if (injected.attemptId) {
|
|
1428
|
-
confirmDelivery?.(injected.attemptId);
|
|
1495
|
+
confirmDelivery?.(injected.attemptId, "handed_off", injected.evaluation);
|
|
1496
|
+
}
|
|
1497
|
+
if (canReplay) {
|
|
1498
|
+
sessionCache.set(latestMessageId, {
|
|
1499
|
+
...injected,
|
|
1500
|
+
parts: latestUser.entry.parts.filter(isCodememContextPart).map((part) => ({ ...part })),
|
|
1501
|
+
reuseCount: 0,
|
|
1502
|
+
});
|
|
1429
1503
|
}
|
|
1430
1504
|
}
|
|
1431
1505
|
|
|
@@ -1438,10 +1512,15 @@ const applyInjectedContextToMessages = async ({
|
|
|
1438
1512
|
// best-effort only
|
|
1439
1513
|
}
|
|
1440
1514
|
}
|
|
1441
|
-
} else if (
|
|
1442
|
-
|
|
1515
|
+
} else if (injected?.skipReason) {
|
|
1516
|
+
// The retrieval already has an attempt. Withheld output is not a second retrieval.
|
|
1517
|
+
if (injected.attemptId) confirmDelivery?.(injected.attemptId, "unknown", injected.evaluation);
|
|
1518
|
+
} else if (injected?.text) {
|
|
1519
|
+
reason = "budget_rejected";
|
|
1520
|
+
if (injected.attemptId) confirmDelivery?.(injected.attemptId, "failed", injected.evaluation);
|
|
1443
1521
|
}
|
|
1444
1522
|
}
|
|
1523
|
+
report(reason, newlyDelivered, duplicates);
|
|
1445
1524
|
|
|
1446
1525
|
if (!canReplay) {
|
|
1447
1526
|
return Boolean(
|
|
@@ -1450,13 +1529,136 @@ const applyInjectedContextToMessages = async ({
|
|
|
1450
1529
|
);
|
|
1451
1530
|
}
|
|
1452
1531
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1532
|
+
return latestWasBuilt || retainedTokens > 0;
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
const countRetainedInjectionTokens = (messages) => messages.reduce(
|
|
1536
|
+
(sum, entry) => sum + (entry.parts || []).filter(isCodememContextPart)
|
|
1537
|
+
.reduce((tokens, part) => tokens + estimateTokens(part.text), 0),
|
|
1538
|
+
0,
|
|
1539
|
+
);
|
|
1540
|
+
|
|
1541
|
+
const injectionDigest = (text) => createHash("sha256").update(text).digest("hex");
|
|
1542
|
+
|
|
1543
|
+
const retainedPartRecall = (part, cachedParts = []) => {
|
|
1544
|
+
const valid = (candidate) => candidate?.metadata?.codemem?.v === 1
|
|
1545
|
+
&& candidate.metadata.codemem.digest === injectionDigest(String(part.text || ""));
|
|
1546
|
+
if (valid(part)) return part.metadata.codemem;
|
|
1547
|
+
const cached = cachedParts.find((candidate) => candidate.id === part.id && candidate.text === part.text && valid(candidate));
|
|
1548
|
+
return cached?.metadata?.codemem;
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
const workingContextDigest = (messages, workingSet) => {
|
|
1552
|
+
const scalar = (value) => typeof value === "string" ? value.slice(0, 256) : null;
|
|
1553
|
+
const hash = createHash("sha256");
|
|
1554
|
+
for (const path of [...workingSet].filter((value) => typeof value === "string").sort()) {
|
|
1555
|
+
hash.update(JSON.stringify(["file", path.slice(0, MAX_WORKING_SET_PATH_CHARS)]));
|
|
1556
|
+
}
|
|
1557
|
+
for (const entry of messages) {
|
|
1558
|
+
for (const part of entry.parts || []) {
|
|
1559
|
+
if (part.type === "text") continue;
|
|
1560
|
+
// Never traverse args, output, attachments, or arbitrary tool-state payloads.
|
|
1561
|
+
hash.update(JSON.stringify([
|
|
1562
|
+
scalar(entry.info?.id), scalar(part.type), scalar(part.id),
|
|
1563
|
+
scalar(part.callID), scalar(part.state?.status),
|
|
1564
|
+
]));
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return hash.digest("hex");
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
const isContinuationOnly = (prompt, messages, workingContext, sessionCache) => {
|
|
1571
|
+
if (!/^(continue|proceed|go on|keep going)[.!]?$/i.test(prompt.trim())) return false;
|
|
1572
|
+
const latest = findLatestUserMessage(messages);
|
|
1573
|
+
for (let index = (latest?.index ?? 0) - 1; index >= 0; index -= 1) {
|
|
1574
|
+
const entry = messages[index];
|
|
1575
|
+
if (!isUserMessageEntry(entry)) continue;
|
|
1576
|
+
return (entry.parts || []).some((part) => {
|
|
1577
|
+
const recall = retainedPartRecall(part, sessionCache?.get(resolveEntryMessageId(entry))?.parts);
|
|
1578
|
+
return isCodememContextPart(part) && recall?.v === 1
|
|
1579
|
+
&& recall.digest === injectionDigest(String(part.text || ""))
|
|
1580
|
+
&& recall.workingContext === workingContext;
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
return false;
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
const retainedMemoryFingerprints = (messages, sessionCache) => {
|
|
1587
|
+
const retained = new Set();
|
|
1588
|
+
for (const entry of messages) {
|
|
1589
|
+
for (const part of (entry.parts || []).filter(isCodememContextPart)) {
|
|
1590
|
+
const recall = retainedPartRecall(part, sessionCache?.get(resolveEntryMessageId(entry))?.parts);
|
|
1591
|
+
if (recall?.v !== 1 || recall.digest !== injectionDigest(String(part.text || ""))) continue;
|
|
1592
|
+
for (const item of Array.isArray(recall.items) ? recall.items : []) {
|
|
1593
|
+
if (isRecord(item) && Number.isSafeInteger(item.id) && item.id > 0 && /^[a-f0-9]{64}$/.test(item.fingerprint)) {
|
|
1594
|
+
retained.add(`${item.id}:${item.fingerprint}`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return retained;
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1602
|
+
const filterRetainedPack = (stdout, packText, retained = new Set()) => {
|
|
1603
|
+
const fallback = { text: packText, items: [], duplicates: 0, packMetadata: "missing" };
|
|
1604
|
+
let payload;
|
|
1605
|
+
try { payload = JSON.parse(stdout); } catch { return fallback; }
|
|
1606
|
+
const items = payload?.rendered_items;
|
|
1607
|
+
if (items != null) fallback.packMetadata = "invalid";
|
|
1608
|
+
// Old transports and malformed metadata remain eligible rather than guessing from prose.
|
|
1609
|
+
if (payload?.pack_text !== packText || !Array.isArray(items) || !items.length) return fallback;
|
|
1610
|
+
if (payload.metrics?.total_items !== items.length) return fallback;
|
|
1611
|
+
const spans = [];
|
|
1612
|
+
const seen = new Set();
|
|
1613
|
+
for (const item of items) {
|
|
1614
|
+
if (!isRecord(item) || !Number.isSafeInteger(item.id) || item.id <= 0 || seen.has(item.id)
|
|
1615
|
+
|| !/^[a-f0-9]{64}$/.test(item.fingerprint) || !Array.isArray(item.spans) || !item.spans.length) return fallback;
|
|
1616
|
+
seen.add(item.id);
|
|
1617
|
+
for (const span of item.spans) {
|
|
1618
|
+
if (!isRecord(span) || !Number.isSafeInteger(span.start) || !Number.isSafeInteger(span.end)
|
|
1619
|
+
|| span.start < 0 || span.end <= span.start || span.end > packText.length) return fallback;
|
|
1620
|
+
spans.push({ ...span, omit: retained.has(`${item.id}:${item.fingerprint}`) });
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (payload.items != null) {
|
|
1624
|
+
if (!Array.isArray(payload.items) || payload.items.length !== seen.size) return fallback;
|
|
1625
|
+
const selectedIds = new Set(payload.items.map((item) => item?.id));
|
|
1626
|
+
if (selectedIds.size !== seen.size || [...selectedIds].some((id) => !seen.has(id))) return fallback;
|
|
1627
|
+
}
|
|
1628
|
+
for (const ids of [payload.item_ids, payload.metrics?.pack_item_ids]) {
|
|
1629
|
+
if (ids == null) continue;
|
|
1630
|
+
if (!Array.isArray(ids) || new Set(ids).size !== ids.length
|
|
1631
|
+
|| ids.some((id) => !Number.isSafeInteger(id) || id <= 0)
|
|
1632
|
+
|| [...seen].some((id) => !ids.includes(id))) return fallback;
|
|
1633
|
+
}
|
|
1634
|
+
spans.sort((a, b) => a.start - b.start);
|
|
1635
|
+
if (spans.some((span, index) => index > 0 && span.start < spans[index - 1].end)) return fallback;
|
|
1636
|
+
const kept = items.filter((item) => !retained.has(`${item.id}:${item.fingerprint}`));
|
|
1637
|
+
let text = packText;
|
|
1638
|
+
for (const span of spans.reverse()) {
|
|
1639
|
+
if (span.omit) text = text.slice(0, span.start) + text.slice(span.end);
|
|
1640
|
+
}
|
|
1641
|
+
return {
|
|
1642
|
+
text: kept.length ? text : "",
|
|
1643
|
+
items: kept.map(({ id, fingerprint }) => ({ id, fingerprint })),
|
|
1644
|
+
duplicates: items.length - kept.length,
|
|
1645
|
+
packMetadata: "valid",
|
|
1646
|
+
};
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
const retainedMetadataGaps = (messages, sessionCache) => {
|
|
1650
|
+
let missingRetainedMetadata = false;
|
|
1651
|
+
let invalidRetainedMetadata = false;
|
|
1652
|
+
for (const entry of messages) {
|
|
1653
|
+
for (const part of (entry.parts || []).filter(isCodememContextPart)) {
|
|
1654
|
+
const recall = retainedPartRecall(part, sessionCache?.get(resolveEntryMessageId(entry))?.parts);
|
|
1655
|
+
if (recall?.v === 1 && Array.isArray(recall.items) && recall.items.length > 0
|
|
1656
|
+
&& recall.items.every((item) => isRecord(item) && Number.isSafeInteger(item.id) && item.id > 0 && /^[a-f0-9]{64}$/.test(item.fingerprint))) continue;
|
|
1657
|
+
if (part.metadata?.codemem != null || recall != null) invalidRetainedMetadata = true;
|
|
1658
|
+
else missingRetainedMetadata = true;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return { missingRetainedMetadata, invalidRetainedMetadata };
|
|
1460
1662
|
};
|
|
1461
1663
|
|
|
1462
1664
|
const mapOpencodeEventTypeToAdapterType = (eventType) => {
|
|
@@ -1767,6 +1969,25 @@ const parsePositiveInt = (value, fallback) => {
|
|
|
1767
1969
|
return parsed;
|
|
1768
1970
|
};
|
|
1769
1971
|
|
|
1972
|
+
const estimateTokens = (text) => Math.ceil(String(text || "").length / 4);
|
|
1973
|
+
|
|
1974
|
+
const resolveInjectTokenBudget = (value) =>
|
|
1975
|
+
parsePositiveInt(value, DEFAULT_INJECT_TOKEN_BUDGET);
|
|
1976
|
+
|
|
1977
|
+
const resolveRetainedTokenBudget = (value) => {
|
|
1978
|
+
const text = String(value ?? "").trim();
|
|
1979
|
+
const parsed = Number(text);
|
|
1980
|
+
return /^\d+$/.test(text) && Number.isSafeInteger(parsed) && parsed > 0
|
|
1981
|
+
? parsed : DEFAULT_RETAINED_TOKEN_BUDGET;
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1984
|
+
const reserveContextPrefixBudget = (tokenBudget) => {
|
|
1985
|
+
const packBudget = tokenBudget - estimateTokens(CODEMEM_CONTEXT_PREFIX);
|
|
1986
|
+
return packBudget > 0 ? packBudget : null;
|
|
1987
|
+
};
|
|
1988
|
+
|
|
1989
|
+
const wrapInjectedContext = (packText) => `${CODEMEM_CONTEXT_PREFIX}${packText}`;
|
|
1990
|
+
|
|
1770
1991
|
export const buildInjectionToastMessage = (metrics) => {
|
|
1771
1992
|
const items = asFiniteNonNegativeInt(metrics?.items) ?? asFiniteNonNegativeInt(metrics?.total_items);
|
|
1772
1993
|
const packTokens = asFiniteNonNegativeInt(metrics?.pack_tokens);
|
|
@@ -1982,15 +2203,30 @@ export const CodememPlugin = async ({
|
|
|
1982
2203
|
process.env.CODEMEM_INJECT_CONTEXT || "1"
|
|
1983
2204
|
);
|
|
1984
2205
|
const injectSurface = resolveInjectSurface(process.env.CODEMEM_INJECT_SURFACE);
|
|
1985
|
-
//
|
|
2206
|
+
// The item limit remains optional so the CLI can use its configured default.
|
|
1986
2207
|
const injectLimitEnv = process.env.CODEMEM_INJECT_LIMIT;
|
|
1987
2208
|
const injectLimit = injectLimitEnv ? parseNumber(injectLimitEnv, null) : null;
|
|
1988
|
-
const
|
|
1989
|
-
|
|
2209
|
+
const injectTokenBudget = resolveInjectTokenBudget(
|
|
2210
|
+
process.env.CODEMEM_INJECT_TOKEN_BUDGET
|
|
2211
|
+
);
|
|
2212
|
+
const injectPackTokenBudget = reserveContextPrefixBudget(injectTokenBudget);
|
|
2213
|
+
const retainedTokenBudget = resolveRetainedTokenBudget(process.env.CODEMEM_INJECT_RETAINED_TOKEN_BUDGET);
|
|
2214
|
+
if (injectEnabled && injectPackTokenBudget === null) {
|
|
2215
|
+
await log(
|
|
2216
|
+
"warn",
|
|
2217
|
+
"codemem context injection skipped: token budget is too small",
|
|
2218
|
+
{
|
|
2219
|
+
inject_token_budget: injectTokenBudget,
|
|
2220
|
+
required_prefix_tokens: estimateTokens(CODEMEM_CONTEXT_PREFIX),
|
|
2221
|
+
next_action: "increase CODEMEM_INJECT_TOKEN_BUDGET or unset it to use the default",
|
|
2222
|
+
}
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
1990
2225
|
const injectionToastShown = new Set();
|
|
1991
2226
|
const messageInjectionCache = new Map();
|
|
1992
2227
|
const compactionInjectionSkips = new Map();
|
|
1993
2228
|
const disabledInjectionRecorded = new Set();
|
|
2229
|
+
const latestPolicySkips = new Map();
|
|
1994
2230
|
const attemptStartedAt = new Map();
|
|
1995
2231
|
const promptPackRetryCounts = new Map();
|
|
1996
2232
|
const successfulPromptPackArtifacts = new Map();
|
|
@@ -2905,7 +3141,7 @@ export const CodememPlugin = async ({
|
|
|
2905
3141
|
request_id: identity.requestId,
|
|
2906
3142
|
});
|
|
2907
3143
|
|
|
2908
|
-
const runPromptPackLedger = async (payload) => {
|
|
3144
|
+
const runPromptPackLedger = async (payload, { viewerOnly = false } = {}) => {
|
|
2909
3145
|
const viewerPayload = {
|
|
2910
3146
|
...payload,
|
|
2911
3147
|
db_path: promptPackDbPath,
|
|
@@ -2926,12 +3162,12 @@ export const CodememPlugin = async ({
|
|
|
2926
3162
|
};
|
|
2927
3163
|
}
|
|
2928
3164
|
|
|
2929
|
-
const { cause, retryable } = httpResult.classification;
|
|
3165
|
+
const { cause, kind, retryable } = httpResult.classification;
|
|
2930
3166
|
await logLine(
|
|
2931
3167
|
`inject.ledger.http_error cause=${JSON.stringify(redactLog(cause, 200))} retryable=${retryable}`
|
|
2932
3168
|
);
|
|
2933
|
-
if (!retryable) {
|
|
2934
|
-
return { exitCode: 1, stdout: "", stderr: cause, transport: "viewer" };
|
|
3169
|
+
if (!retryable || viewerOnly) {
|
|
3170
|
+
return { exitCode: 1, stdout: "", stderr: cause, transport: "viewer", failureKind: kind };
|
|
2935
3171
|
}
|
|
2936
3172
|
|
|
2937
3173
|
try {
|
|
@@ -2953,9 +3189,18 @@ export const CodememPlugin = async ({
|
|
|
2953
3189
|
queryHash: hashPromptPackQuery(""),
|
|
2954
3190
|
});
|
|
2955
3191
|
|
|
2956
|
-
const recordSkippedPromptPack = (failureCode, sessionID = null, surface = injectSurface) => {
|
|
3192
|
+
const recordSkippedPromptPack = (failureCode, sessionID = null, surface = injectSurface, requestKey = null) => {
|
|
2957
3193
|
const sessionKey = String(sessionID || "unknown");
|
|
2958
3194
|
const memoKey = `${surface}:${sessionKey}`;
|
|
3195
|
+
if (requestKey) {
|
|
3196
|
+
const signature = `${requestKey}:${failureCode}`;
|
|
3197
|
+
if (latestPolicySkips.get(memoKey) === signature) return null;
|
|
3198
|
+
latestPolicySkips.delete(memoKey);
|
|
3199
|
+
latestPolicySkips.set(memoKey, signature);
|
|
3200
|
+
while (latestPolicySkips.size > MAX_MESSAGE_INJECTION_CACHE_SESSIONS) {
|
|
3201
|
+
latestPolicySkips.delete(latestPolicySkips.keys().next().value);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
2959
3204
|
if (failureCode === "injection_disabled" && disabledInjectionRecorded.has(memoKey)) {
|
|
2960
3205
|
return null;
|
|
2961
3206
|
}
|
|
@@ -2964,7 +3209,7 @@ export const CodememPlugin = async ({
|
|
|
2964
3209
|
failureCode,
|
|
2965
3210
|
sessionID,
|
|
2966
3211
|
surface,
|
|
2967
|
-
failureCode === "injection_disabled" ? "once" : `event-${++skippedAttemptCounter}
|
|
3212
|
+
requestKey || (failureCode === "injection_disabled" ? "once" : `event-${++skippedAttemptCounter}`),
|
|
2968
3213
|
);
|
|
2969
3214
|
void runPromptPackLedger({
|
|
2970
3215
|
action: "record",
|
|
@@ -2993,12 +3238,28 @@ export const CodememPlugin = async ({
|
|
|
2993
3238
|
return { attemptId: identity.attemptId, ready };
|
|
2994
3239
|
};
|
|
2995
3240
|
|
|
2996
|
-
const confirmPromptPackDelivery = (attemptId, deliveryStatus = "handed_off") => {
|
|
3241
|
+
const confirmPromptPackDelivery = (attemptId, deliveryStatus = "handed_off", evaluation) => {
|
|
3242
|
+
const delivery = {
|
|
3243
|
+
action: "delivery", attempt_id: attemptId, delivery_status: deliveryStatus,
|
|
3244
|
+
};
|
|
2997
3245
|
void runPromptPackLedger({
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3246
|
+
...delivery,
|
|
3247
|
+
...evaluation,
|
|
3248
|
+
}).then((result) => {
|
|
3249
|
+
// Retry additive fields only after the transport identifies that exact compatibility case.
|
|
3250
|
+
if (
|
|
3251
|
+
evaluation
|
|
3252
|
+
&& result?.transport === "viewer"
|
|
3253
|
+
&& result.failureKind === "viewer_contract_unsupported"
|
|
3254
|
+
) {
|
|
3255
|
+
return runPromptPackLedger(delivery, { viewerOnly: true });
|
|
3256
|
+
}
|
|
3257
|
+
// Older CLI fallbacks can reject optional fields. Preserve the original receipt;
|
|
3258
|
+
// never retry a Viewer policy/auth rejection through a different transport.
|
|
3259
|
+
if (evaluation && result?.transport === "cli" && result.exitCode !== 0) {
|
|
3260
|
+
return runPromptPackLedger(delivery);
|
|
3261
|
+
}
|
|
3262
|
+
}).catch(() => {});
|
|
3002
3263
|
};
|
|
3003
3264
|
|
|
3004
3265
|
const showToast = async (message, variant = "warning") => {
|
|
@@ -3322,11 +3583,44 @@ export const CodememPlugin = async ({
|
|
|
3322
3583
|
}
|
|
3323
3584
|
};
|
|
3324
3585
|
|
|
3586
|
+
let fallbackEvaluationSessionId = nextEventId();
|
|
3325
3587
|
const buildInjectedContext = async (query, context = {}) => {
|
|
3588
|
+
const requestPackBudget = reserveContextPrefixBudget(context.tokenBudget ?? injectTokenBudget);
|
|
3326
3589
|
const queryHash = hashPromptPackQuery(query);
|
|
3327
|
-
|
|
3590
|
+
// Requester eligibility comes only from the transform's own session identity.
|
|
3591
|
+
// activeSessionID tracks the latest event stream, which can belong to another
|
|
3592
|
+
// overlapping session; inferring from it would authorize the wrong summaries.
|
|
3593
|
+
const requesterHostSessionID = context.sessionID || null;
|
|
3594
|
+
const sessionID = requesterHostSessionID || "unknown";
|
|
3328
3595
|
const surface = context.surface || injectSurface;
|
|
3329
|
-
|
|
3596
|
+
let requestKey = context.requestKey || "unknown";
|
|
3597
|
+
if (context.fallbackTurn != null) {
|
|
3598
|
+
requestKey = JSON.stringify([requestKey, fallbackEvaluationSessionId, promptCounter, context.fallbackTurn]);
|
|
3599
|
+
}
|
|
3600
|
+
const evaluationFields = (
|
|
3601
|
+
beforeText,
|
|
3602
|
+
afterText,
|
|
3603
|
+
candidateItems,
|
|
3604
|
+
duplicatesOmitted,
|
|
3605
|
+
packMetadata,
|
|
3606
|
+
artifactFingerprint,
|
|
3607
|
+
) => ({
|
|
3608
|
+
evaluation_key: injectionDigest(JSON.stringify([
|
|
3609
|
+
sessionID,
|
|
3610
|
+
requestKey,
|
|
3611
|
+
surface,
|
|
3612
|
+
queryHash,
|
|
3613
|
+
artifactFingerprint,
|
|
3614
|
+
])),
|
|
3615
|
+
automatic_recall: {
|
|
3616
|
+
v: 1, candidateItems, duplicatesOmitted,
|
|
3617
|
+
beforeTokens: beforeText ? estimateTokens(wrapInjectedContext(beforeText)) : 0,
|
|
3618
|
+
afterTokens: afterText ? estimateTokens(wrapInjectedContext(afterText)) : 0,
|
|
3619
|
+
missingRetainedMetadata: context.retainedMetadata?.missingRetainedMetadata === true,
|
|
3620
|
+
invalidRetainedMetadata: context.retainedMetadata?.invalidRetainedMetadata === true,
|
|
3621
|
+
packMetadata,
|
|
3622
|
+
},
|
|
3623
|
+
});
|
|
3330
3624
|
const attemptKey = JSON.stringify([
|
|
3331
3625
|
sessionID,
|
|
3332
3626
|
requestKey,
|
|
@@ -3347,14 +3641,23 @@ export const CodememPlugin = async ({
|
|
|
3347
3641
|
let identity = resolveIdentity();
|
|
3348
3642
|
let metadata = attemptMetadata(
|
|
3349
3643
|
identity,
|
|
3350
|
-
|
|
3644
|
+
requesterHostSessionID,
|
|
3351
3645
|
);
|
|
3646
|
+
if (requestPackBudget === null) {
|
|
3647
|
+
return {
|
|
3648
|
+
text: "",
|
|
3649
|
+
attemptId: identity.attemptId,
|
|
3650
|
+
requestId: identity.requestId,
|
|
3651
|
+
queryHash,
|
|
3652
|
+
promptNumber: promptCounter,
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3352
3655
|
const runPack = async () => {
|
|
3353
3656
|
let packArgs = buildPackArgs({
|
|
3354
3657
|
query,
|
|
3355
3658
|
filesModified: sessionContext.filesModified,
|
|
3356
3659
|
injectLimit,
|
|
3357
|
-
injectTokenBudget,
|
|
3660
|
+
injectTokenBudget: requestPackBudget,
|
|
3358
3661
|
internalLedger: true,
|
|
3359
3662
|
});
|
|
3360
3663
|
const httpResult = await postViewerJson({
|
|
@@ -3364,12 +3667,15 @@ export const CodememPlugin = async ({
|
|
|
3364
3667
|
query,
|
|
3365
3668
|
filesModified: sessionContext.filesModified,
|
|
3366
3669
|
injectLimit,
|
|
3367
|
-
injectTokenBudget,
|
|
3670
|
+
injectTokenBudget: requestPackBudget,
|
|
3368
3671
|
projectName: normalizeProjectLabel(process.env.CODEMEM_PROJECT),
|
|
3369
3672
|
cwd,
|
|
3370
3673
|
dbPath: promptPackDbPath,
|
|
3371
3674
|
identityTarget: promptPackIdentityTarget,
|
|
3372
3675
|
attempt: metadata,
|
|
3676
|
+
automaticContext: requesterHostSessionID
|
|
3677
|
+
? { source: "opencode", host_session_id: requesterHostSessionID }
|
|
3678
|
+
: null,
|
|
3373
3679
|
}),
|
|
3374
3680
|
validate: isValidPackHttpPayload,
|
|
3375
3681
|
});
|
|
@@ -3396,16 +3702,17 @@ export const CodememPlugin = async ({
|
|
|
3396
3702
|
};
|
|
3397
3703
|
}
|
|
3398
3704
|
|
|
3399
|
-
|
|
3705
|
+
const result = await runCli(packArgs, { stdinText: JSON.stringify(metadata) });
|
|
3400
3706
|
if (rejectsInternalLedgerFlag(result)) {
|
|
3401
|
-
|
|
3402
|
-
|
|
3707
|
+
// An older CLI cannot enforce requester-session continuity. Do not retry
|
|
3708
|
+
// through its generic pack path, which may inject an unrelated summary.
|
|
3709
|
+
await logLine("inject.pack.cli_legacy_unsafe_fallback_blocked");
|
|
3403
3710
|
}
|
|
3404
3711
|
return { packArgs, result: { ...result, transport: "cli" } };
|
|
3405
3712
|
};
|
|
3406
3713
|
let { packArgs, result } = await runPack();
|
|
3407
3714
|
let { packText, conflictPackText, metrics, itemCount, ledgerConflict } = parsePackOutput(result);
|
|
3408
|
-
let artifactFingerprint = packText
|
|
3715
|
+
let artifactFingerprint = packText || itemCount === 0
|
|
3409
3716
|
? promptPackArtifactFingerprint(result.stdout, packText)
|
|
3410
3717
|
: "";
|
|
3411
3718
|
let injectedIdentity = identity;
|
|
@@ -3432,14 +3739,11 @@ export const CodememPlugin = async ({
|
|
|
3432
3739
|
advancePromptPackRetryIdentity(attemptKey);
|
|
3433
3740
|
retryCount = promptPackRetryCounts.get(attemptKey) || 0;
|
|
3434
3741
|
identity = resolveIdentity();
|
|
3435
|
-
metadata = attemptMetadata(
|
|
3436
|
-
identity,
|
|
3437
|
-
context.sessionID || activeSessionID || null,
|
|
3438
|
-
);
|
|
3742
|
+
metadata = attemptMetadata(identity, requesterHostSessionID);
|
|
3439
3743
|
({ packArgs, result } = await runPack());
|
|
3440
3744
|
// Repair-conflict bytes are never preferred over the original preserved pack.
|
|
3441
3745
|
({ packText, metrics, itemCount, ledgerConflict } = parsePackOutput(result));
|
|
3442
|
-
artifactFingerprint = packText
|
|
3746
|
+
artifactFingerprint = packText || itemCount === 0
|
|
3443
3747
|
? promptPackArtifactFingerprint(result.stdout, packText)
|
|
3444
3748
|
: "";
|
|
3445
3749
|
const repairFailed = ledgerConflict
|
|
@@ -3518,13 +3822,10 @@ export const CodememPlugin = async ({
|
|
|
3518
3822
|
advancePromptPackRetryIdentity(attemptKey);
|
|
3519
3823
|
retryCount = promptPackRetryCounts.get(attemptKey) || 0;
|
|
3520
3824
|
identity = resolveIdentity();
|
|
3521
|
-
metadata = attemptMetadata(
|
|
3522
|
-
identity,
|
|
3523
|
-
context.sessionID || activeSessionID || null,
|
|
3524
|
-
);
|
|
3825
|
+
metadata = attemptMetadata(identity, requesterHostSessionID);
|
|
3525
3826
|
({ packArgs, result } = await runPack());
|
|
3526
3827
|
({ packText, metrics, itemCount, ledgerConflict } = parsePackOutput(result));
|
|
3527
|
-
artifactFingerprint = packText
|
|
3828
|
+
artifactFingerprint = packText || itemCount === 0
|
|
3528
3829
|
? promptPackArtifactFingerprint(result.stdout, packText)
|
|
3529
3830
|
: "";
|
|
3530
3831
|
|
|
@@ -3624,6 +3925,10 @@ export const CodememPlugin = async ({
|
|
|
3624
3925
|
}
|
|
3625
3926
|
if (!packText) {
|
|
3626
3927
|
if (itemCount === 0) {
|
|
3928
|
+
if (surface === "message") {
|
|
3929
|
+
void runPromptPackLedger({ action: "recall", attempt_id: identity.attemptId,
|
|
3930
|
+
...evaluationFields("", "", 0, 0, "valid", artifactFingerprint) }).catch(() => {});
|
|
3931
|
+
}
|
|
3627
3932
|
advancePromptPackRetryIdentity(attemptKey);
|
|
3628
3933
|
}
|
|
3629
3934
|
if (debug) {
|
|
@@ -3656,9 +3961,34 @@ export const CodememPlugin = async ({
|
|
|
3656
3961
|
artifactFingerprint || promptPackArtifactFingerprint(result.stdout, packText)
|
|
3657
3962
|
);
|
|
3658
3963
|
}
|
|
3964
|
+
const filtered = context.surface === "message"
|
|
3965
|
+
? filterRetainedPack(result.stdout, packText, context.retainedItems)
|
|
3966
|
+
: { text: packText, items: [], duplicates: 0 };
|
|
3967
|
+
const text = filtered.text ? wrapInjectedContext(filtered.text) : "";
|
|
3968
|
+
const evaluation = surface === "message"
|
|
3969
|
+
? evaluationFields(
|
|
3970
|
+
packText,
|
|
3971
|
+
filtered.text,
|
|
3972
|
+
itemCount,
|
|
3973
|
+
filtered.duplicates,
|
|
3974
|
+
filtered.packMetadata,
|
|
3975
|
+
artifactFingerprint,
|
|
3976
|
+
)
|
|
3977
|
+
: undefined;
|
|
3978
|
+
const recall = filtered.items.length
|
|
3979
|
+
? { v: 1, digest: injectionDigest(text), items: filtered.items, workingContext: context.workingContext }
|
|
3980
|
+
: undefined;
|
|
3981
|
+
let skipReason = null;
|
|
3982
|
+
if (!text && filtered.duplicates > 0) {
|
|
3983
|
+
skipReason = context.continuationOnly ? "continuation_only" : "unchanged_memories";
|
|
3984
|
+
}
|
|
3659
3985
|
if (metrics) {
|
|
3660
3986
|
return {
|
|
3661
|
-
text
|
|
3987
|
+
text,
|
|
3988
|
+
recall,
|
|
3989
|
+
duplicates: filtered.duplicates,
|
|
3990
|
+
evaluation,
|
|
3991
|
+
skipReason,
|
|
3662
3992
|
metrics,
|
|
3663
3993
|
attemptId: injectedIdentity?.attemptId || null,
|
|
3664
3994
|
requestId: injectedIdentity?.requestId || null,
|
|
@@ -3667,7 +3997,11 @@ export const CodememPlugin = async ({
|
|
|
3667
3997
|
};
|
|
3668
3998
|
}
|
|
3669
3999
|
return {
|
|
3670
|
-
text
|
|
4000
|
+
text,
|
|
4001
|
+
recall,
|
|
4002
|
+
duplicates: filtered.duplicates,
|
|
4003
|
+
evaluation,
|
|
4004
|
+
skipReason,
|
|
3671
4005
|
attemptId: injectedIdentity?.attemptId || null,
|
|
3672
4006
|
requestId: injectedIdentity?.requestId || null,
|
|
3673
4007
|
queryHash,
|
|
@@ -3892,10 +4226,12 @@ export const CodememPlugin = async ({
|
|
|
3892
4226
|
if (injectSurface === "system") {
|
|
3893
4227
|
return;
|
|
3894
4228
|
}
|
|
3895
|
-
const
|
|
3896
|
-
?
|
|
3897
|
-
:
|
|
3898
|
-
const
|
|
4229
|
+
const hookMessages = Array.isArray(output?.messages)
|
|
4230
|
+
? scopeMessagesToSession(output.messages, input?.sessionID || null)
|
|
4231
|
+
: [];
|
|
4232
|
+
const latestUser = findLatestUserMessage(hookMessages);
|
|
4233
|
+
const sessionID = input?.sessionID
|
|
4234
|
+
|| (latestUser ? resolveEntrySessionID(latestUser.entry) : null);
|
|
3899
4235
|
const latestPromptText = latestUser ? extractMessageText(latestUser.entry) : "";
|
|
3900
4236
|
if (debug) {
|
|
3901
4237
|
const query = resolveInjectQuery({ lastPromptText: latestPromptText });
|
|
@@ -3928,10 +4264,16 @@ export const CodememPlugin = async ({
|
|
|
3928
4264
|
resolveInjectQuery,
|
|
3929
4265
|
buildInjectedContext,
|
|
3930
4266
|
messageInjectionCache,
|
|
4267
|
+
injectTokenBudget,
|
|
4268
|
+
retainedTokenBudget,
|
|
4269
|
+
workingSet: [...sessionContext.filesModified, ...sessionContext.filesRead],
|
|
4270
|
+
recordMeasurement: (measurement) => logLine(`inject.recall ${JSON.stringify(measurement)}`),
|
|
3931
4271
|
compactionInjectionSkips,
|
|
3932
4272
|
confirmDelivery: confirmPromptPackDelivery,
|
|
3933
4273
|
recordCacheReuse: recordCachedPromptPack,
|
|
3934
|
-
recordSkipped: recordSkippedPromptPack
|
|
4274
|
+
recordSkipped: (reason, sessionID) => recordSkippedPromptPack(
|
|
4275
|
+
reason, sessionID, "message", latestUser ? resolveEntryMessageId(latestUser.entry) : null,
|
|
4276
|
+
),
|
|
3935
4277
|
});
|
|
3936
4278
|
} catch (err) {
|
|
3937
4279
|
await logLine(
|
|
@@ -4183,6 +4525,7 @@ export const CodememPlugin = async ({
|
|
|
4183
4525
|
activeSessionID = sessionID || null;
|
|
4184
4526
|
sessionStartedAt = new Date().toISOString();
|
|
4185
4527
|
promptCounter = 0;
|
|
4528
|
+
fallbackEvaluationSessionId = nextEventId();
|
|
4186
4529
|
skippedAttemptCounter = 0;
|
|
4187
4530
|
disabledInjectionRecorded.delete("message:unknown");
|
|
4188
4531
|
disabledInjectionRecorded.delete("system:unknown");
|
|
@@ -4204,6 +4547,8 @@ export const CodememPlugin = async ({
|
|
|
4204
4547
|
compactionInjectionSkips.delete(sessionID);
|
|
4205
4548
|
disabledInjectionRecorded.delete(`message:${sessionID}`);
|
|
4206
4549
|
disabledInjectionRecorded.delete(`system:${sessionID}`);
|
|
4550
|
+
latestPolicySkips.delete(`message:${sessionID}`);
|
|
4551
|
+
latestPolicySkips.delete(`system:${sessionID}`);
|
|
4207
4552
|
}
|
|
4208
4553
|
await stopViewer();
|
|
4209
4554
|
}
|
|
@@ -4356,5 +4701,18 @@ export const __testUtils = {
|
|
|
4356
4701
|
buildRawEventEnvelope,
|
|
4357
4702
|
trimEventQueue,
|
|
4358
4703
|
parsePositiveInt,
|
|
4704
|
+
resolveInjectTokenBudget,
|
|
4705
|
+
reserveContextPrefixBudget,
|
|
4706
|
+
wrapInjectedContext,
|
|
4707
|
+
estimateTokens,
|
|
4708
|
+
countRetainedInjectionTokens,
|
|
4709
|
+
retainedMemoryFingerprints,
|
|
4710
|
+
retainedMetadataGaps,
|
|
4711
|
+
filterRetainedPack,
|
|
4712
|
+
isContinuationOnly,
|
|
4713
|
+
resolveRetainedTokenBudget,
|
|
4714
|
+
workingContextDigest,
|
|
4715
|
+
DEFAULT_INJECT_TOKEN_BUDGET,
|
|
4716
|
+
CODEMEM_CONTEXT_PREFIX,
|
|
4359
4717
|
createViewerHealthMonitor,
|
|
4360
4718
|
};
|