@agenr/agenr-plugin 1.9.3 → 2.0.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/dist/{chunk-I6A6DPNF.js → chunk-XD3446YW.js} +2 -2
- package/dist/{chunk-EMRMV2QR.js → chunk-Y2BC7RCE.js} +1347 -110
- package/dist/chunk-ZYADFKX3.js +115 -0
- package/dist/index.js +191 -56
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/chunk-ETQPUJGS.js +0 -0
- package/dist/{chunk-GUDCFFRV.js → chunk-MEHOGUZE.js} +175 -175
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildLexicalPlan,
|
|
3
|
+
combinedRelevance,
|
|
4
|
+
computeLexicalScore,
|
|
3
5
|
cosineSimilarity,
|
|
4
6
|
describeClaimKeyNormalizationFailure,
|
|
5
7
|
normalizeClaimKey,
|
|
6
8
|
recall,
|
|
7
9
|
resolveClaimSlotPolicy,
|
|
8
10
|
tokenize
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MEHOGUZE.js";
|
|
10
12
|
|
|
11
13
|
// src/adapters/db/client.ts
|
|
12
14
|
import fs from "fs/promises";
|
|
@@ -31,6 +33,9 @@ var CLAIM_KEY_SOURCES = [
|
|
|
31
33
|
];
|
|
32
34
|
var CLAIM_SUPPORT_MODES = ["explicit", "normalized", "inferred"];
|
|
33
35
|
var EPISODE_ACTIVITY_LEVELS = ["substantial", "minimal", "none"];
|
|
36
|
+
var PROCEDURE_STEP_KINDS = ["run_command", "read_reference", "inspect_state", "edit_file", "ask_user", "invoke_tool", "verify"];
|
|
37
|
+
var PROCEDURE_CONDITION_KINDS = ["harness_is", "tool_available", "file_exists", "path_exists", "env_flag", "repo_state", "user_confirmed"];
|
|
38
|
+
var PROCEDURE_SOURCE_KINDS = ["skill", "doc", "entry", "episode", "repo_file", "manual"];
|
|
34
39
|
|
|
35
40
|
// src/core/claim-key-lifecycle.ts
|
|
36
41
|
var PRECOMPUTED_LIFECYCLE_FIELDS = ["claim_key_status", "claim_key_source", "claim_key_confidence", "claim_key_rationale"];
|
|
@@ -1136,8 +1141,817 @@ function toNullableInteger(value) {
|
|
|
1136
1141
|
return value ?? null;
|
|
1137
1142
|
}
|
|
1138
1143
|
|
|
1144
|
+
// src/adapters/db/procedure-queries.ts
|
|
1145
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1146
|
+
|
|
1147
|
+
// src/core/procedures/validation.ts
|
|
1148
|
+
import { parseDocument } from "yaml";
|
|
1149
|
+
function parseProcedureYaml(sourceText, filePath) {
|
|
1150
|
+
const document = parseDocument(sourceText, {
|
|
1151
|
+
merge: false,
|
|
1152
|
+
prettyErrors: true,
|
|
1153
|
+
uniqueKeys: true
|
|
1154
|
+
});
|
|
1155
|
+
if (document.errors.length > 0) {
|
|
1156
|
+
const [firstError] = document.errors;
|
|
1157
|
+
throw new Error(`Invalid procedure ${filePath}: ${firstError?.message ?? "YAML parsing failed."}`);
|
|
1158
|
+
}
|
|
1159
|
+
return document.toJS({ maxAliasCount: 0 });
|
|
1160
|
+
}
|
|
1161
|
+
function readProcedureRecord(value, label, filePath) {
|
|
1162
|
+
if (!isRecord(value)) {
|
|
1163
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be an object.`);
|
|
1164
|
+
}
|
|
1165
|
+
return value;
|
|
1166
|
+
}
|
|
1167
|
+
function rejectUnexpectedProcedureFields(record, allowedKeys, label, filePath) {
|
|
1168
|
+
const unexpected = Object.keys(record).filter((key) => !allowedKeys.has(key));
|
|
1169
|
+
if (unexpected.length === 0) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} contains unsupported field "${unexpected[0]}".`);
|
|
1173
|
+
}
|
|
1174
|
+
function readRequiredProcedureString(value, label, filePath) {
|
|
1175
|
+
if (typeof value !== "string") {
|
|
1176
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be a non-empty string.`);
|
|
1177
|
+
}
|
|
1178
|
+
const normalized = normalizeWhitespace(value);
|
|
1179
|
+
if (normalized.length === 0) {
|
|
1180
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be a non-empty string.`);
|
|
1181
|
+
}
|
|
1182
|
+
return normalized;
|
|
1183
|
+
}
|
|
1184
|
+
function readOptionalProcedureString(value, label, filePath) {
|
|
1185
|
+
if (value === void 0) {
|
|
1186
|
+
return void 0;
|
|
1187
|
+
}
|
|
1188
|
+
return readRequiredProcedureString(value, label, filePath);
|
|
1189
|
+
}
|
|
1190
|
+
function readProcedureStringArray(value, label, filePath, options = {}) {
|
|
1191
|
+
if (value === void 0) {
|
|
1192
|
+
if (options.required) {
|
|
1193
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
|
|
1194
|
+
}
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
if (!Array.isArray(value)) {
|
|
1198
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
|
|
1199
|
+
}
|
|
1200
|
+
const normalized = value.map((item, index) => readRequiredProcedureString(item, `${label}[${index}]`, filePath));
|
|
1201
|
+
if (options.minItems !== void 0 && normalized.length < options.minItems) {
|
|
1202
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must contain at least ${options.minItems} item(s).`);
|
|
1203
|
+
}
|
|
1204
|
+
return normalized;
|
|
1205
|
+
}
|
|
1206
|
+
function readProcedureRecordArray(value, label, filePath, options = {}) {
|
|
1207
|
+
if (value === void 0) {
|
|
1208
|
+
if (options.required) {
|
|
1209
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
|
|
1210
|
+
}
|
|
1211
|
+
return [];
|
|
1212
|
+
}
|
|
1213
|
+
if (!Array.isArray(value)) {
|
|
1214
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be an array.`);
|
|
1215
|
+
}
|
|
1216
|
+
const records = value.map((item, index) => readProcedureRecord(item, `${label}[${index}]`, filePath));
|
|
1217
|
+
if (options.minItems !== void 0 && records.length < options.minItems) {
|
|
1218
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must contain at least ${options.minItems} item(s).`);
|
|
1219
|
+
}
|
|
1220
|
+
return records;
|
|
1221
|
+
}
|
|
1222
|
+
function readProcedureStepKind(value, label, filePath, supportedKinds) {
|
|
1223
|
+
const normalized = readRequiredProcedureString(value, label, filePath);
|
|
1224
|
+
if (!supportedKinds.includes(normalized)) {
|
|
1225
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported step kind "${normalized}".`);
|
|
1226
|
+
}
|
|
1227
|
+
return normalized;
|
|
1228
|
+
}
|
|
1229
|
+
function readProcedureConditionKind(value, label, filePath, supportedKinds) {
|
|
1230
|
+
const normalized = readRequiredProcedureString(value, label, filePath);
|
|
1231
|
+
if (!supportedKinds.includes(normalized)) {
|
|
1232
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported condition kind "${normalized}".`);
|
|
1233
|
+
}
|
|
1234
|
+
return normalized;
|
|
1235
|
+
}
|
|
1236
|
+
function readProcedureSourceKind(value, label, filePath, supportedKinds) {
|
|
1237
|
+
const normalized = readRequiredProcedureString(value, label, filePath);
|
|
1238
|
+
if (!supportedKinds.includes(normalized)) {
|
|
1239
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} has unsupported source kind "${normalized}".`);
|
|
1240
|
+
}
|
|
1241
|
+
return normalized;
|
|
1242
|
+
}
|
|
1243
|
+
function normalizeProcedureToolArgumentValue(value, label, filePath) {
|
|
1244
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
1245
|
+
return value;
|
|
1246
|
+
}
|
|
1247
|
+
if (typeof value === "number") {
|
|
1248
|
+
if (!Number.isFinite(value)) {
|
|
1249
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must not contain non-finite numbers.`);
|
|
1250
|
+
}
|
|
1251
|
+
return value;
|
|
1252
|
+
}
|
|
1253
|
+
if (Array.isArray(value)) {
|
|
1254
|
+
return value.map((item, index) => normalizeProcedureToolArgumentValue(item, `${label}[${index}]`, filePath));
|
|
1255
|
+
}
|
|
1256
|
+
if (!isRecord(value)) {
|
|
1257
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must be JSON-like data.`);
|
|
1258
|
+
}
|
|
1259
|
+
const normalizedEntries = Object.entries(value).map(([key, entryValue]) => {
|
|
1260
|
+
const normalizedKey = key.trim();
|
|
1261
|
+
if (normalizedKey.length === 0) {
|
|
1262
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must not contain blank object keys.`);
|
|
1263
|
+
}
|
|
1264
|
+
return [normalizedKey, normalizeProcedureToolArgumentValue(entryValue, `${label}.${normalizedKey}`, filePath)];
|
|
1265
|
+
}).sort(([left], [right]) => left.localeCompare(right));
|
|
1266
|
+
return Object.fromEntries(normalizedEntries);
|
|
1267
|
+
}
|
|
1268
|
+
function isRecord(value) {
|
|
1269
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1270
|
+
}
|
|
1271
|
+
function normalizeWhitespace(value) {
|
|
1272
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/core/procedures/normalization.ts
|
|
1276
|
+
var PROCEDURE_KEYS = /* @__PURE__ */ new Set([
|
|
1277
|
+
"procedure_key",
|
|
1278
|
+
"title",
|
|
1279
|
+
"goal",
|
|
1280
|
+
"when_to_use",
|
|
1281
|
+
"when_not_to_use",
|
|
1282
|
+
"prerequisites",
|
|
1283
|
+
"steps",
|
|
1284
|
+
"verification",
|
|
1285
|
+
"failure_modes",
|
|
1286
|
+
"sources"
|
|
1287
|
+
]);
|
|
1288
|
+
var STEP_BASE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "instruction", "conditions", "stop_if"]);
|
|
1289
|
+
var READ_REFERENCE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "ref"]);
|
|
1290
|
+
var RUN_COMMAND_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "command"]);
|
|
1291
|
+
var INSPECT_STATE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "target", "query"]);
|
|
1292
|
+
var EDIT_FILE_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "path", "edit"]);
|
|
1293
|
+
var ASK_USER_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "prompt"]);
|
|
1294
|
+
var INVOKE_TOOL_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "tool", "arguments"]);
|
|
1295
|
+
var VERIFY_STEP_KEYS = /* @__PURE__ */ new Set([...STEP_BASE_KEYS, "checks"]);
|
|
1296
|
+
var SOURCE_KEYS = /* @__PURE__ */ new Set(["kind", "path", "locator", "label"]);
|
|
1297
|
+
var HARNESS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
|
|
1298
|
+
var TOOL_AVAILABLE_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
|
|
1299
|
+
var FILE_EXISTS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "path"]);
|
|
1300
|
+
var PATH_EXISTS_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "path"]);
|
|
1301
|
+
var ENV_FLAG_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "name", "value"]);
|
|
1302
|
+
var REPO_STATE_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
|
|
1303
|
+
var USER_CONFIRMED_CONDITION_KEYS = /* @__PURE__ */ new Set(["kind", "value"]);
|
|
1304
|
+
function parseAndNormalizeProcedureYaml(sourceText, filePath) {
|
|
1305
|
+
return normalizeProcedureDefinition(parseProcedureYaml(sourceText, filePath), filePath);
|
|
1306
|
+
}
|
|
1307
|
+
function normalizeProcedureDefinition(value, filePath) {
|
|
1308
|
+
const record = readProcedureRecord(value, "Procedure root", filePath);
|
|
1309
|
+
rejectUnexpectedProcedureFields(record, PROCEDURE_KEYS, "Procedure root", filePath);
|
|
1310
|
+
return {
|
|
1311
|
+
procedure_key: normalizeProcedureKey(record.procedure_key, filePath),
|
|
1312
|
+
title: readRequiredProcedureString(record.title, "title", filePath),
|
|
1313
|
+
goal: readRequiredProcedureString(record.goal, "goal", filePath),
|
|
1314
|
+
when_to_use: readProcedureStringArray(record.when_to_use, "when_to_use", filePath),
|
|
1315
|
+
when_not_to_use: readProcedureStringArray(record.when_not_to_use, "when_not_to_use", filePath),
|
|
1316
|
+
prerequisites: readProcedureStringArray(record.prerequisites, "prerequisites", filePath),
|
|
1317
|
+
steps: normalizeProcedureSteps(record.steps, filePath),
|
|
1318
|
+
verification: readProcedureStringArray(record.verification, "verification", filePath, { required: true, minItems: 1 }),
|
|
1319
|
+
failure_modes: readProcedureStringArray(record.failure_modes, "failure_modes", filePath, { required: true, minItems: 1 }),
|
|
1320
|
+
sources: normalizeProcedureSources(record.sources, "sources", filePath, { required: true, minItems: 1 })
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function normalizeProcedureKey(value, filePath) {
|
|
1324
|
+
const normalized = readRequiredProcedureString(value, "procedure_key", filePath).toLowerCase();
|
|
1325
|
+
if (!normalized.includes("/") || !/^[a-z0-9][a-z0-9._/-]*$/u.test(normalized)) {
|
|
1326
|
+
throw new Error(`Invalid procedure ${filePath}: procedure_key must be a lowercase slash-delimited identifier like "agenr/release".`);
|
|
1327
|
+
}
|
|
1328
|
+
return normalized;
|
|
1329
|
+
}
|
|
1330
|
+
function normalizeProcedureSteps(value, filePath) {
|
|
1331
|
+
const records = readProcedureRecordArray(value, "steps", filePath, { required: true, minItems: 1 });
|
|
1332
|
+
const steps = records.map((record, index) => normalizeProcedureStep(record, `steps[${index}]`, filePath));
|
|
1333
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
1334
|
+
for (const step of steps) {
|
|
1335
|
+
if (stepIds.has(step.id)) {
|
|
1336
|
+
throw new Error(`Invalid procedure ${filePath}: steps must not contain duplicate id "${step.id}".`);
|
|
1337
|
+
}
|
|
1338
|
+
stepIds.add(step.id);
|
|
1339
|
+
}
|
|
1340
|
+
return steps;
|
|
1341
|
+
}
|
|
1342
|
+
function normalizeProcedureStep(record, label, filePath) {
|
|
1343
|
+
const kind = readProcedureStepKind(record.kind, `${label}.kind`, filePath, PROCEDURE_STEP_KINDS);
|
|
1344
|
+
const base = normalizeProcedureStepBase(record, label, filePath);
|
|
1345
|
+
switch (kind) {
|
|
1346
|
+
case "run_command":
|
|
1347
|
+
rejectUnexpectedProcedureFields(record, RUN_COMMAND_STEP_KEYS, label, filePath);
|
|
1348
|
+
return {
|
|
1349
|
+
...base,
|
|
1350
|
+
kind,
|
|
1351
|
+
command: readRequiredProcedureString(record.command, `${label}.command`, filePath)
|
|
1352
|
+
};
|
|
1353
|
+
case "read_reference":
|
|
1354
|
+
rejectUnexpectedProcedureFields(record, READ_REFERENCE_STEP_KEYS, label, filePath);
|
|
1355
|
+
return {
|
|
1356
|
+
...base,
|
|
1357
|
+
kind,
|
|
1358
|
+
ref: normalizeProcedureSource(readProcedureRecord(record.ref, `${label}.ref`, filePath), `${label}.ref`, filePath)
|
|
1359
|
+
};
|
|
1360
|
+
case "inspect_state": {
|
|
1361
|
+
rejectUnexpectedProcedureFields(record, INSPECT_STATE_STEP_KEYS, label, filePath);
|
|
1362
|
+
const target = readOptionalProcedureString(record.target, `${label}.target`, filePath);
|
|
1363
|
+
const query = readOptionalProcedureString(record.query, `${label}.query`, filePath);
|
|
1364
|
+
if (!target && !query) {
|
|
1365
|
+
throw new Error(`Invalid procedure ${filePath}: ${label} must define target, query, or both.`);
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
...base,
|
|
1369
|
+
kind,
|
|
1370
|
+
...target ? { target } : {},
|
|
1371
|
+
...query ? { query } : {}
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
case "edit_file":
|
|
1375
|
+
rejectUnexpectedProcedureFields(record, EDIT_FILE_STEP_KEYS, label, filePath);
|
|
1376
|
+
return {
|
|
1377
|
+
...base,
|
|
1378
|
+
kind,
|
|
1379
|
+
path: readRequiredProcedureString(record.path, `${label}.path`, filePath),
|
|
1380
|
+
edit: readRequiredProcedureString(record.edit, `${label}.edit`, filePath)
|
|
1381
|
+
};
|
|
1382
|
+
case "ask_user":
|
|
1383
|
+
rejectUnexpectedProcedureFields(record, ASK_USER_STEP_KEYS, label, filePath);
|
|
1384
|
+
return {
|
|
1385
|
+
...base,
|
|
1386
|
+
kind,
|
|
1387
|
+
prompt: readRequiredProcedureString(record.prompt, `${label}.prompt`, filePath)
|
|
1388
|
+
};
|
|
1389
|
+
case "invoke_tool":
|
|
1390
|
+
rejectUnexpectedProcedureFields(record, INVOKE_TOOL_STEP_KEYS, label, filePath);
|
|
1391
|
+
return {
|
|
1392
|
+
...base,
|
|
1393
|
+
kind,
|
|
1394
|
+
tool: readRequiredProcedureString(record.tool, `${label}.tool`, filePath),
|
|
1395
|
+
...record.arguments !== void 0 ? {
|
|
1396
|
+
arguments: normalizeProcedureArguments(record.arguments, `${label}.arguments`, filePath)
|
|
1397
|
+
} : {}
|
|
1398
|
+
};
|
|
1399
|
+
case "verify":
|
|
1400
|
+
rejectUnexpectedProcedureFields(record, VERIFY_STEP_KEYS, label, filePath);
|
|
1401
|
+
return {
|
|
1402
|
+
...base,
|
|
1403
|
+
kind,
|
|
1404
|
+
checks: readProcedureStringArray(record.checks, `${label}.checks`, filePath, { required: true, minItems: 1 })
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
function normalizeProcedureStepBase(record, label, filePath) {
|
|
1409
|
+
return {
|
|
1410
|
+
id: readRequiredProcedureString(record.id, `${label}.id`, filePath),
|
|
1411
|
+
instruction: readRequiredProcedureString(record.instruction, `${label}.instruction`, filePath),
|
|
1412
|
+
...record.conditions !== void 0 ? { conditions: normalizeProcedureConditions(record.conditions, `${label}.conditions`, filePath) } : {},
|
|
1413
|
+
...record.stop_if !== void 0 ? { stop_if: normalizeProcedureConditions(record.stop_if, `${label}.stop_if`, filePath) } : {}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
function normalizeProcedureArguments(value, label, filePath) {
|
|
1417
|
+
const record = readProcedureRecord(value, label, filePath);
|
|
1418
|
+
return normalizeProcedureToolArgumentValue(record, label, filePath);
|
|
1419
|
+
}
|
|
1420
|
+
function normalizeProcedureConditions(value, label, filePath) {
|
|
1421
|
+
return readProcedureRecordArray(value, label, filePath, { required: true, minItems: 1 }).map(
|
|
1422
|
+
(record, index) => normalizeProcedureCondition(record, `${label}[${index}]`, filePath)
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
function normalizeProcedureCondition(record, label, filePath) {
|
|
1426
|
+
const kind = readProcedureConditionKind(record.kind, `${label}.kind`, filePath, PROCEDURE_CONDITION_KINDS);
|
|
1427
|
+
switch (kind) {
|
|
1428
|
+
case "harness_is":
|
|
1429
|
+
rejectUnexpectedProcedureFields(record, HARNESS_CONDITION_KEYS, label, filePath);
|
|
1430
|
+
return {
|
|
1431
|
+
kind,
|
|
1432
|
+
value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
|
|
1433
|
+
};
|
|
1434
|
+
case "tool_available":
|
|
1435
|
+
rejectUnexpectedProcedureFields(record, TOOL_AVAILABLE_CONDITION_KEYS, label, filePath);
|
|
1436
|
+
return {
|
|
1437
|
+
kind,
|
|
1438
|
+
value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
|
|
1439
|
+
};
|
|
1440
|
+
case "file_exists":
|
|
1441
|
+
rejectUnexpectedProcedureFields(record, FILE_EXISTS_CONDITION_KEYS, label, filePath);
|
|
1442
|
+
return {
|
|
1443
|
+
kind,
|
|
1444
|
+
path: readRequiredProcedureString(record.path, `${label}.path`, filePath)
|
|
1445
|
+
};
|
|
1446
|
+
case "path_exists":
|
|
1447
|
+
rejectUnexpectedProcedureFields(record, PATH_EXISTS_CONDITION_KEYS, label, filePath);
|
|
1448
|
+
return {
|
|
1449
|
+
kind,
|
|
1450
|
+
path: readRequiredProcedureString(record.path, `${label}.path`, filePath)
|
|
1451
|
+
};
|
|
1452
|
+
case "env_flag":
|
|
1453
|
+
rejectUnexpectedProcedureFields(record, ENV_FLAG_CONDITION_KEYS, label, filePath);
|
|
1454
|
+
return {
|
|
1455
|
+
kind,
|
|
1456
|
+
name: readRequiredProcedureString(record.name, `${label}.name`, filePath),
|
|
1457
|
+
...record.value !== void 0 ? { value: readRequiredProcedureString(record.value, `${label}.value`, filePath) } : {}
|
|
1458
|
+
};
|
|
1459
|
+
case "repo_state":
|
|
1460
|
+
rejectUnexpectedProcedureFields(record, REPO_STATE_CONDITION_KEYS, label, filePath);
|
|
1461
|
+
return {
|
|
1462
|
+
kind,
|
|
1463
|
+
value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
|
|
1464
|
+
};
|
|
1465
|
+
case "user_confirmed":
|
|
1466
|
+
rejectUnexpectedProcedureFields(record, USER_CONFIRMED_CONDITION_KEYS, label, filePath);
|
|
1467
|
+
return {
|
|
1468
|
+
kind,
|
|
1469
|
+
value: readRequiredProcedureString(record.value, `${label}.value`, filePath)
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
function normalizeProcedureSources(value, label, filePath, options = {}) {
|
|
1474
|
+
return readProcedureRecordArray(value, label, filePath, options).map((record, index) => normalizeProcedureSource(record, `${label}[${index}]`, filePath));
|
|
1475
|
+
}
|
|
1476
|
+
function normalizeProcedureSource(record, label, filePath) {
|
|
1477
|
+
rejectUnexpectedProcedureFields(record, SOURCE_KEYS, label, filePath);
|
|
1478
|
+
const kind = readProcedureSourceKind(record.kind, `${label}.kind`, filePath, PROCEDURE_SOURCE_KINDS);
|
|
1479
|
+
const path3 = readOptionalProcedureString(record.path, `${label}.path`, filePath);
|
|
1480
|
+
const locator = readOptionalProcedureString(record.locator, `${label}.locator`, filePath);
|
|
1481
|
+
const sourceLabel = readOptionalProcedureString(record.label, `${label}.label`, filePath);
|
|
1482
|
+
switch (kind) {
|
|
1483
|
+
case "skill":
|
|
1484
|
+
case "doc":
|
|
1485
|
+
case "repo_file":
|
|
1486
|
+
if (!path3) {
|
|
1487
|
+
throw new Error(`Invalid procedure ${filePath}: ${label}.${kind} sources require a path.`);
|
|
1488
|
+
}
|
|
1489
|
+
break;
|
|
1490
|
+
case "entry":
|
|
1491
|
+
case "episode":
|
|
1492
|
+
if (!locator) {
|
|
1493
|
+
throw new Error(`Invalid procedure ${filePath}: ${label}.${kind} sources require a locator.`);
|
|
1494
|
+
}
|
|
1495
|
+
break;
|
|
1496
|
+
case "manual":
|
|
1497
|
+
if (!sourceLabel && !locator) {
|
|
1498
|
+
throw new Error(`Invalid procedure ${filePath}: ${label}.manual sources require a label or locator.`);
|
|
1499
|
+
}
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
kind,
|
|
1504
|
+
...path3 ? { path: path3 } : {},
|
|
1505
|
+
...locator ? { locator } : {},
|
|
1506
|
+
...sourceLabel ? { label: sourceLabel } : {}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/adapters/db/procedure-row-mapping.ts
|
|
1511
|
+
var ACTIVE_PROCEDURE_CLAUSE = "retired = 0 AND superseded_by IS NULL";
|
|
1512
|
+
var PROCEDURE_SELECT_COLUMNS = `
|
|
1513
|
+
id,
|
|
1514
|
+
procedure_key,
|
|
1515
|
+
title,
|
|
1516
|
+
goal,
|
|
1517
|
+
body_json,
|
|
1518
|
+
recall_text,
|
|
1519
|
+
source_file,
|
|
1520
|
+
source_hash,
|
|
1521
|
+
revision_hash,
|
|
1522
|
+
embedding,
|
|
1523
|
+
retired,
|
|
1524
|
+
retired_at,
|
|
1525
|
+
retired_reason,
|
|
1526
|
+
superseded_by,
|
|
1527
|
+
created_at,
|
|
1528
|
+
updated_at
|
|
1529
|
+
`;
|
|
1530
|
+
function buildActiveProcedureClause(alias) {
|
|
1531
|
+
if (!alias) {
|
|
1532
|
+
return ACTIVE_PROCEDURE_CLAUSE;
|
|
1533
|
+
}
|
|
1534
|
+
return `${alias}.retired = 0 AND ${alias}.superseded_by IS NULL`;
|
|
1535
|
+
}
|
|
1536
|
+
function serializeProcedureBody(procedure) {
|
|
1537
|
+
return JSON.stringify(procedure);
|
|
1538
|
+
}
|
|
1539
|
+
function mapProcedureRow(row) {
|
|
1540
|
+
const definition = parseProcedureBody(readRequiredString(row, "body_json"));
|
|
1541
|
+
return {
|
|
1542
|
+
id: readRequiredString(row, "id"),
|
|
1543
|
+
procedure_key: readRequiredString(row, "procedure_key"),
|
|
1544
|
+
title: readRequiredString(row, "title"),
|
|
1545
|
+
goal: readRequiredString(row, "goal"),
|
|
1546
|
+
when_to_use: definition.when_to_use,
|
|
1547
|
+
when_not_to_use: definition.when_not_to_use,
|
|
1548
|
+
prerequisites: definition.prerequisites,
|
|
1549
|
+
steps: definition.steps,
|
|
1550
|
+
verification: definition.verification,
|
|
1551
|
+
failure_modes: definition.failure_modes,
|
|
1552
|
+
sources: definition.sources,
|
|
1553
|
+
recall_text: readRequiredString(row, "recall_text"),
|
|
1554
|
+
revision_hash: readRequiredString(row, "revision_hash"),
|
|
1555
|
+
source_hash: readRequiredString(row, "source_hash"),
|
|
1556
|
+
source_file: readOptionalString(row, "source_file"),
|
|
1557
|
+
embedding: readEmbedding(row, "embedding"),
|
|
1558
|
+
retired: readBoolean(row, "retired"),
|
|
1559
|
+
retired_at: readOptionalString(row, "retired_at"),
|
|
1560
|
+
retired_reason: readOptionalString(row, "retired_reason"),
|
|
1561
|
+
superseded_by: readOptionalString(row, "superseded_by"),
|
|
1562
|
+
created_at: readRequiredString(row, "created_at"),
|
|
1563
|
+
updated_at: readRequiredString(row, "updated_at")
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
function parseProcedureBody(bodyJson) {
|
|
1567
|
+
let parsed;
|
|
1568
|
+
try {
|
|
1569
|
+
parsed = JSON.parse(bodyJson);
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1572
|
+
throw new Error(`Invalid procedures.body_json payload: ${message}`, {
|
|
1573
|
+
cause: error
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
return normalizeProcedureDefinition(parsed, "procedures.body_json");
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// src/adapters/db/procedure-queries.ts
|
|
1580
|
+
var LOOKUP_CHUNK_SIZE = 100;
|
|
1581
|
+
var FTS_TIERS = ["exact", "all_tokens", "any_tokens"];
|
|
1582
|
+
async function upsertProcedure(executor, procedure) {
|
|
1583
|
+
const payload = normalizeStoredProcedure(procedure);
|
|
1584
|
+
const id = payload.id.trim().length > 0 ? payload.id.trim() : randomUUID2();
|
|
1585
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1586
|
+
const createdAt = normalizeTimestamp(payload.created_at) ?? now;
|
|
1587
|
+
const updatedAt = normalizeTimestamp(payload.updated_at) ?? now;
|
|
1588
|
+
const vectorJson = serializeEmbeddingForVector(payload.embedding ?? []);
|
|
1589
|
+
await executor.execute({
|
|
1590
|
+
sql: `
|
|
1591
|
+
INSERT INTO procedures (
|
|
1592
|
+
id,
|
|
1593
|
+
procedure_key,
|
|
1594
|
+
title,
|
|
1595
|
+
goal,
|
|
1596
|
+
body_json,
|
|
1597
|
+
recall_text,
|
|
1598
|
+
source_file,
|
|
1599
|
+
source_hash,
|
|
1600
|
+
revision_hash,
|
|
1601
|
+
embedding,
|
|
1602
|
+
retired,
|
|
1603
|
+
retired_at,
|
|
1604
|
+
retired_reason,
|
|
1605
|
+
superseded_by,
|
|
1606
|
+
created_at,
|
|
1607
|
+
updated_at
|
|
1608
|
+
)
|
|
1609
|
+
VALUES (
|
|
1610
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
1611
|
+
CASE WHEN ? IS NULL THEN NULL ELSE vector32(?) END,
|
|
1612
|
+
?, ?, ?, ?, ?, ?
|
|
1613
|
+
)
|
|
1614
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1615
|
+
procedure_key = excluded.procedure_key,
|
|
1616
|
+
title = excluded.title,
|
|
1617
|
+
goal = excluded.goal,
|
|
1618
|
+
body_json = excluded.body_json,
|
|
1619
|
+
recall_text = excluded.recall_text,
|
|
1620
|
+
source_file = excluded.source_file,
|
|
1621
|
+
source_hash = excluded.source_hash,
|
|
1622
|
+
revision_hash = excluded.revision_hash,
|
|
1623
|
+
embedding = excluded.embedding,
|
|
1624
|
+
retired = excluded.retired,
|
|
1625
|
+
retired_at = excluded.retired_at,
|
|
1626
|
+
retired_reason = excluded.retired_reason,
|
|
1627
|
+
superseded_by = excluded.superseded_by,
|
|
1628
|
+
updated_at = excluded.updated_at
|
|
1629
|
+
`,
|
|
1630
|
+
args: [
|
|
1631
|
+
id,
|
|
1632
|
+
payload.procedure_key,
|
|
1633
|
+
payload.title,
|
|
1634
|
+
payload.goal,
|
|
1635
|
+
serializeProcedureBody(payload.body),
|
|
1636
|
+
payload.recall_text,
|
|
1637
|
+
toNullableString2(payload.source_file),
|
|
1638
|
+
payload.source_hash,
|
|
1639
|
+
payload.revision_hash,
|
|
1640
|
+
vectorJson,
|
|
1641
|
+
vectorJson,
|
|
1642
|
+
payload.retired ? 1 : 0,
|
|
1643
|
+
toNullableString2(payload.retired_at),
|
|
1644
|
+
toNullableString2(payload.retired_reason),
|
|
1645
|
+
toNullableString2(payload.superseded_by),
|
|
1646
|
+
createdAt,
|
|
1647
|
+
updatedAt
|
|
1648
|
+
]
|
|
1649
|
+
});
|
|
1650
|
+
return getProcedureById(executor, id);
|
|
1651
|
+
}
|
|
1652
|
+
async function getProcedure(executor, id) {
|
|
1653
|
+
const [procedure] = await hydrateProcedures(executor, [id]);
|
|
1654
|
+
return procedure ?? null;
|
|
1655
|
+
}
|
|
1656
|
+
async function hydrateProcedures(executor, ids) {
|
|
1657
|
+
const normalizedIds = dedupeStrings(ids);
|
|
1658
|
+
if (normalizedIds.length === 0) {
|
|
1659
|
+
return [];
|
|
1660
|
+
}
|
|
1661
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1662
|
+
for (const chunk of chunkValues(normalizedIds, LOOKUP_CHUNK_SIZE)) {
|
|
1663
|
+
const placeholders = chunk.map(() => "?").join(", ");
|
|
1664
|
+
const result = await executor.execute({
|
|
1665
|
+
sql: `
|
|
1666
|
+
SELECT
|
|
1667
|
+
${PROCEDURE_SELECT_COLUMNS}
|
|
1668
|
+
FROM procedures
|
|
1669
|
+
WHERE id IN (${placeholders})
|
|
1670
|
+
AND ${ACTIVE_PROCEDURE_CLAUSE}
|
|
1671
|
+
`,
|
|
1672
|
+
args: chunk
|
|
1673
|
+
});
|
|
1674
|
+
for (const row of result.rows) {
|
|
1675
|
+
const procedure = mapProcedureRow(row);
|
|
1676
|
+
byId.set(procedure.id, procedure);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return ids.map((id) => byId.get(id.trim())).filter((procedure) => procedure !== void 0);
|
|
1680
|
+
}
|
|
1681
|
+
async function findActiveProcedureByKey(executor, procedureKey) {
|
|
1682
|
+
const normalizedKey = normalizeOptionalString3(procedureKey);
|
|
1683
|
+
if (!normalizedKey) {
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
const result = await executor.execute({
|
|
1687
|
+
sql: `
|
|
1688
|
+
SELECT ${PROCEDURE_SELECT_COLUMNS}
|
|
1689
|
+
FROM procedures
|
|
1690
|
+
WHERE procedure_key = ?
|
|
1691
|
+
AND ${ACTIVE_PROCEDURE_CLAUSE}
|
|
1692
|
+
LIMIT 1
|
|
1693
|
+
`,
|
|
1694
|
+
args: [normalizedKey]
|
|
1695
|
+
});
|
|
1696
|
+
const row = result.rows[0];
|
|
1697
|
+
return row ? mapProcedureRow(row) : null;
|
|
1698
|
+
}
|
|
1699
|
+
async function procedureVectorSearch(executor, params) {
|
|
1700
|
+
if (params.limit <= 0 || params.embedding.length === 0) {
|
|
1701
|
+
return [];
|
|
1702
|
+
}
|
|
1703
|
+
const serializedEmbedding = serializeEmbeddingForVector(params.embedding);
|
|
1704
|
+
if (!serializedEmbedding) {
|
|
1705
|
+
return [];
|
|
1706
|
+
}
|
|
1707
|
+
let result;
|
|
1708
|
+
try {
|
|
1709
|
+
result = await executor.execute({
|
|
1710
|
+
sql: `
|
|
1711
|
+
SELECT ${prefixColumns2(PROCEDURE_SELECT_COLUMNS, "p")}
|
|
1712
|
+
FROM vector_top_k('idx_procedures_embedding', vector32(?), ?) AS v
|
|
1713
|
+
JOIN procedures AS p ON p.rowid = v.id
|
|
1714
|
+
WHERE ${buildActiveProcedureClause("p")}
|
|
1715
|
+
LIMIT ?
|
|
1716
|
+
`,
|
|
1717
|
+
args: [serializedEmbedding, params.limit, params.limit]
|
|
1718
|
+
});
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
throw wrapProcedureVectorError(error);
|
|
1721
|
+
}
|
|
1722
|
+
return result.rows.map((row) => {
|
|
1723
|
+
const procedure = mapProcedureRow(row);
|
|
1724
|
+
return {
|
|
1725
|
+
procedure,
|
|
1726
|
+
vectorSim: cosineSimilarity(params.embedding, procedure.embedding ?? [])
|
|
1727
|
+
};
|
|
1728
|
+
}).filter((candidate) => candidate.vectorSim > 0).sort((left, right) => right.vectorSim - left.vectorSim).slice(0, params.limit);
|
|
1729
|
+
}
|
|
1730
|
+
async function procedureFtsSearch(executor, params) {
|
|
1731
|
+
const query = normalizeOptionalString3(params.text);
|
|
1732
|
+
if (!query || params.limit <= 0) {
|
|
1733
|
+
return [];
|
|
1734
|
+
}
|
|
1735
|
+
const plan = buildLexicalPlan(query);
|
|
1736
|
+
if (plan.length === 0) {
|
|
1737
|
+
return [];
|
|
1738
|
+
}
|
|
1739
|
+
const matches = /* @__PURE__ */ new Map();
|
|
1740
|
+
for (const tier of plan) {
|
|
1741
|
+
let result;
|
|
1742
|
+
try {
|
|
1743
|
+
result = await executor.execute({
|
|
1744
|
+
sql: `
|
|
1745
|
+
SELECT
|
|
1746
|
+
${prefixColumns2(PROCEDURE_SELECT_COLUMNS, "p")},
|
|
1747
|
+
bm25(procedures_fts, 1.0, 2.0) AS rank
|
|
1748
|
+
FROM procedures_fts
|
|
1749
|
+
JOIN procedures AS p ON p.rowid = procedures_fts.rowid
|
|
1750
|
+
WHERE procedures_fts MATCH ?
|
|
1751
|
+
AND ${buildActiveProcedureClause("p")}
|
|
1752
|
+
ORDER BY bm25(procedures_fts, 1.0, 2.0)
|
|
1753
|
+
LIMIT ?
|
|
1754
|
+
`,
|
|
1755
|
+
args: [compileLexicalTier(tier), params.limit]
|
|
1756
|
+
});
|
|
1757
|
+
} catch {
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
for (const row of result.rows) {
|
|
1761
|
+
const procedure = mapProcedureRow(row);
|
|
1762
|
+
if (matches.has(procedure.id)) {
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
matches.set(procedure.id, {
|
|
1766
|
+
procedure,
|
|
1767
|
+
rank: readNumber(row, "rank", Number.POSITIVE_INFINITY),
|
|
1768
|
+
tier: tier.tier
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return Array.from(matches.values()).sort(compareProcedureFtsMatches).slice(0, params.limit).map(({ procedure, rank }) => ({
|
|
1773
|
+
procedure,
|
|
1774
|
+
rank
|
|
1775
|
+
}));
|
|
1776
|
+
}
|
|
1777
|
+
async function listProceduresWithoutEmbeddings(executor, limit) {
|
|
1778
|
+
const normalizedLimit = normalizePositiveInteger2(limit);
|
|
1779
|
+
const result = await executor.execute({
|
|
1780
|
+
sql: `
|
|
1781
|
+
SELECT ${PROCEDURE_SELECT_COLUMNS}
|
|
1782
|
+
FROM procedures
|
|
1783
|
+
WHERE ${ACTIVE_PROCEDURE_CLAUSE}
|
|
1784
|
+
AND embedding IS NULL
|
|
1785
|
+
ORDER BY created_at DESC, id ASC
|
|
1786
|
+
${normalizedLimit ? "LIMIT ?" : ""}
|
|
1787
|
+
`,
|
|
1788
|
+
args: normalizedLimit ? [normalizedLimit] : []
|
|
1789
|
+
});
|
|
1790
|
+
return result.rows.map((row) => mapProcedureRow(row));
|
|
1791
|
+
}
|
|
1792
|
+
async function updateProcedureEmbedding(executor, id, embedding) {
|
|
1793
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1794
|
+
const vectorJson = serializeEmbeddingForVector(embedding);
|
|
1795
|
+
await executor.execute({
|
|
1796
|
+
sql: `
|
|
1797
|
+
UPDATE procedures
|
|
1798
|
+
SET embedding = CASE WHEN ? IS NULL THEN NULL ELSE vector32(?) END,
|
|
1799
|
+
updated_at = ?
|
|
1800
|
+
WHERE id = ?
|
|
1801
|
+
`,
|
|
1802
|
+
args: [vectorJson, vectorJson, now, id]
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
async function retireProcedure(executor, id, reason) {
|
|
1806
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1807
|
+
const result = await executor.execute({
|
|
1808
|
+
sql: `
|
|
1809
|
+
UPDATE procedures
|
|
1810
|
+
SET retired = 1,
|
|
1811
|
+
retired_at = ?,
|
|
1812
|
+
retired_reason = ?,
|
|
1813
|
+
updated_at = ?
|
|
1814
|
+
WHERE id = ?
|
|
1815
|
+
AND ${ACTIVE_PROCEDURE_CLAUSE}
|
|
1816
|
+
`,
|
|
1817
|
+
args: [now, toNullableString2(normalizeOptionalString3(reason)), now, id]
|
|
1818
|
+
});
|
|
1819
|
+
return result.rowsAffected > 0;
|
|
1820
|
+
}
|
|
1821
|
+
async function supersedeProcedure(executor, oldId, newId, reason) {
|
|
1822
|
+
const normalizedOldId = normalizeOptionalString3(oldId);
|
|
1823
|
+
const normalizedNewId = normalizeOptionalString3(newId);
|
|
1824
|
+
if (!normalizedOldId || !normalizedNewId || normalizedOldId === normalizedNewId) {
|
|
1825
|
+
return false;
|
|
1826
|
+
}
|
|
1827
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1828
|
+
const result = await executor.execute({
|
|
1829
|
+
sql: `
|
|
1830
|
+
UPDATE procedures
|
|
1831
|
+
SET superseded_by = ?,
|
|
1832
|
+
retired_reason = COALESCE(?, retired_reason),
|
|
1833
|
+
updated_at = ?
|
|
1834
|
+
WHERE id = ?
|
|
1835
|
+
AND ${ACTIVE_PROCEDURE_CLAUSE}
|
|
1836
|
+
`,
|
|
1837
|
+
args: [normalizedNewId, toNullableString2(normalizeOptionalString3(reason)), now, normalizedOldId]
|
|
1838
|
+
});
|
|
1839
|
+
return result.rowsAffected > 0;
|
|
1840
|
+
}
|
|
1841
|
+
async function getProcedureById(executor, id) {
|
|
1842
|
+
const result = await executor.execute({
|
|
1843
|
+
sql: `
|
|
1844
|
+
SELECT ${PROCEDURE_SELECT_COLUMNS}
|
|
1845
|
+
FROM procedures
|
|
1846
|
+
WHERE id = ?
|
|
1847
|
+
LIMIT 1
|
|
1848
|
+
`,
|
|
1849
|
+
args: [id]
|
|
1850
|
+
});
|
|
1851
|
+
const row = result.rows[0];
|
|
1852
|
+
if (!row) {
|
|
1853
|
+
throw new Error(`Procedure ${id} was not found after persistence.`);
|
|
1854
|
+
}
|
|
1855
|
+
return mapProcedureRow(row);
|
|
1856
|
+
}
|
|
1857
|
+
function normalizeStoredProcedure(procedure) {
|
|
1858
|
+
const body = normalizeProcedureDefinition(
|
|
1859
|
+
{
|
|
1860
|
+
procedure_key: procedure.procedure_key,
|
|
1861
|
+
title: procedure.title,
|
|
1862
|
+
goal: procedure.goal,
|
|
1863
|
+
when_to_use: procedure.when_to_use,
|
|
1864
|
+
when_not_to_use: procedure.when_not_to_use,
|
|
1865
|
+
prerequisites: procedure.prerequisites,
|
|
1866
|
+
steps: procedure.steps,
|
|
1867
|
+
verification: procedure.verification,
|
|
1868
|
+
failure_modes: procedure.failure_modes,
|
|
1869
|
+
sources: procedure.sources
|
|
1870
|
+
},
|
|
1871
|
+
"procedure write payload"
|
|
1872
|
+
);
|
|
1873
|
+
return {
|
|
1874
|
+
...procedure,
|
|
1875
|
+
...body,
|
|
1876
|
+
body,
|
|
1877
|
+
recall_text: normalizeRequiredText(procedure.recall_text, "recall_text"),
|
|
1878
|
+
revision_hash: normalizeRequiredText(procedure.revision_hash, "revision_hash"),
|
|
1879
|
+
source_hash: normalizeRequiredText(procedure.source_hash, "source_hash"),
|
|
1880
|
+
source_file: normalizeOptionalString3(procedure.source_file),
|
|
1881
|
+
retired_at: normalizeOptionalString3(procedure.retired_at),
|
|
1882
|
+
retired_reason: normalizeOptionalString3(procedure.retired_reason),
|
|
1883
|
+
superseded_by: normalizeOptionalString3(procedure.superseded_by),
|
|
1884
|
+
embedding: normalizeEmbedding2(procedure.embedding)
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
function normalizeRequiredText(value, label) {
|
|
1888
|
+
const normalized = normalizeOptionalString3(value);
|
|
1889
|
+
if (!normalized) {
|
|
1890
|
+
throw new Error(`Procedure ${label} must be a non-empty string.`);
|
|
1891
|
+
}
|
|
1892
|
+
return normalized;
|
|
1893
|
+
}
|
|
1894
|
+
function normalizeOptionalString3(value) {
|
|
1895
|
+
const trimmed = value?.trim();
|
|
1896
|
+
return trimmed ? trimmed : void 0;
|
|
1897
|
+
}
|
|
1898
|
+
function normalizeTimestamp(value) {
|
|
1899
|
+
return normalizeOptionalString3(value);
|
|
1900
|
+
}
|
|
1901
|
+
function normalizeEmbedding2(embedding) {
|
|
1902
|
+
if (!embedding || embedding.length === 0) {
|
|
1903
|
+
return void 0;
|
|
1904
|
+
}
|
|
1905
|
+
return embedding.map((value) => Number.isFinite(value) ? value : 0);
|
|
1906
|
+
}
|
|
1907
|
+
function normalizePositiveInteger2(value) {
|
|
1908
|
+
if (value === void 0 || !Number.isFinite(value) || value <= 0) {
|
|
1909
|
+
return void 0;
|
|
1910
|
+
}
|
|
1911
|
+
return Math.trunc(value);
|
|
1912
|
+
}
|
|
1913
|
+
function chunkValues(values, size) {
|
|
1914
|
+
const chunks = [];
|
|
1915
|
+
for (let index = 0; index < values.length; index += size) {
|
|
1916
|
+
chunks.push(values.slice(index, index + size));
|
|
1917
|
+
}
|
|
1918
|
+
return chunks;
|
|
1919
|
+
}
|
|
1920
|
+
function dedupeStrings(values) {
|
|
1921
|
+
return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
|
|
1922
|
+
}
|
|
1923
|
+
function prefixColumns2(columns, alias) {
|
|
1924
|
+
return columns.split(",").map((column) => column.trim()).filter((column) => column.length > 0).map((column) => `${alias}.${column}`).join(", ");
|
|
1925
|
+
}
|
|
1926
|
+
function compareProcedureFtsMatches(left, right) {
|
|
1927
|
+
const tierDelta = ftsTierPriority(left.tier) - ftsTierPriority(right.tier);
|
|
1928
|
+
if (tierDelta !== 0) {
|
|
1929
|
+
return tierDelta;
|
|
1930
|
+
}
|
|
1931
|
+
if (left.rank !== right.rank) {
|
|
1932
|
+
return left.rank - right.rank;
|
|
1933
|
+
}
|
|
1934
|
+
return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
|
|
1935
|
+
}
|
|
1936
|
+
function ftsTierPriority(tier) {
|
|
1937
|
+
return FTS_TIERS.indexOf(tier);
|
|
1938
|
+
}
|
|
1939
|
+
function compileLexicalTier(tier) {
|
|
1940
|
+
if (tier.tier === "exact") {
|
|
1941
|
+
return `"${tier.text.replaceAll('"', '""')}"`;
|
|
1942
|
+
}
|
|
1943
|
+
return tier.tier === "all_tokens" ? tier.tokens.join(" ") : tier.tokens.join(" OR ");
|
|
1944
|
+
}
|
|
1945
|
+
function wrapProcedureVectorError(error) {
|
|
1946
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1947
|
+
return new Error(`Procedure vector search is unavailable: ${message}`);
|
|
1948
|
+
}
|
|
1949
|
+
function toNullableString2(value) {
|
|
1950
|
+
return value ?? null;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1139
1953
|
// src/adapters/db/queries.ts
|
|
1140
|
-
import { randomUUID as
|
|
1954
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1141
1955
|
|
|
1142
1956
|
// src/core/temporal-validity.ts
|
|
1143
1957
|
function validateTemporalValidityRange(validFrom, validTo) {
|
|
@@ -1189,13 +2003,13 @@ function normalizeOptionalTimestamp(value) {
|
|
|
1189
2003
|
}
|
|
1190
2004
|
|
|
1191
2005
|
// src/adapters/db/queries.ts
|
|
1192
|
-
var
|
|
2006
|
+
var LOOKUP_CHUNK_SIZE2 = 100;
|
|
1193
2007
|
var DEFAULT_QUALITY_SCORE2 = 0.5;
|
|
1194
2008
|
async function insertEntry(executor, entry, embedding, contentHash) {
|
|
1195
2009
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1196
|
-
const id = entry.id.trim().length > 0 ? entry.id.trim() :
|
|
1197
|
-
const createdAt =
|
|
1198
|
-
const updatedAt =
|
|
2010
|
+
const id = entry.id.trim().length > 0 ? entry.id.trim() : randomUUID3();
|
|
2011
|
+
const createdAt = normalizeTimestamp2(entry.created_at) ?? now;
|
|
2012
|
+
const updatedAt = normalizeTimestamp2(entry.updated_at) ?? now;
|
|
1199
2013
|
const vectorJson = serializeEmbeddingForVector(embedding);
|
|
1200
2014
|
await executor.execute({
|
|
1201
2015
|
sql: `
|
|
@@ -1254,37 +2068,37 @@ async function insertEntry(executor, entry, embedding, contentHash) {
|
|
|
1254
2068
|
normalizeInteger(entry.importance, 0),
|
|
1255
2069
|
entry.expiry,
|
|
1256
2070
|
serializeTags(entry.tags),
|
|
1257
|
-
|
|
1258
|
-
|
|
2071
|
+
normalizeOptionalString4(entry.source_file),
|
|
2072
|
+
normalizeOptionalString4(entry.source_context),
|
|
1259
2073
|
vectorJson,
|
|
1260
2074
|
vectorJson,
|
|
1261
2075
|
contentHash.trim(),
|
|
1262
|
-
|
|
2076
|
+
normalizeOptionalString4(entry.norm_content_hash),
|
|
1263
2077
|
null,
|
|
1264
2078
|
normalizeNumber(entry.quality_score, DEFAULT_QUALITY_SCORE2),
|
|
1265
2079
|
normalizeInteger(entry.recall_count, 0),
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
2080
|
+
normalizeOptionalString4(entry.last_recalled_at),
|
|
2081
|
+
normalizeOptionalString4(entry.superseded_by),
|
|
2082
|
+
normalizeOptionalString4(entry.valid_from),
|
|
2083
|
+
normalizeOptionalString4(entry.valid_to),
|
|
2084
|
+
normalizeOptionalString4(entry.claim_key),
|
|
2085
|
+
normalizeOptionalString4(entry.claim_key_raw),
|
|
2086
|
+
normalizeOptionalString4(entry.claim_key_status),
|
|
2087
|
+
normalizeOptionalString4(entry.claim_key_source),
|
|
1274
2088
|
normalizeOptionalNumber(entry.claim_key_confidence),
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
2089
|
+
normalizeOptionalString4(entry.claim_key_rationale),
|
|
2090
|
+
normalizeOptionalString4(entry.claim_support_source_kind),
|
|
2091
|
+
normalizeOptionalString4(entry.claim_support_locator),
|
|
2092
|
+
normalizeOptionalString4(entry.claim_support_observed_at),
|
|
2093
|
+
normalizeOptionalString4(entry.claim_support_mode),
|
|
2094
|
+
normalizeOptionalString4(entry.supersession_kind),
|
|
2095
|
+
normalizeOptionalString4(entry.supersession_reason),
|
|
2096
|
+
normalizeOptionalString4(entry.cluster_id),
|
|
2097
|
+
normalizeOptionalString4(entry.user_id),
|
|
2098
|
+
normalizeOptionalString4(entry.project),
|
|
1285
2099
|
entry.retired ? 1 : 0,
|
|
1286
|
-
|
|
1287
|
-
|
|
2100
|
+
normalizeOptionalString4(entry.retired_at),
|
|
2101
|
+
normalizeOptionalString4(entry.retired_reason),
|
|
1288
2102
|
createdAt,
|
|
1289
2103
|
updatedAt
|
|
1290
2104
|
]
|
|
@@ -1292,12 +2106,12 @@ async function insertEntry(executor, entry, embedding, contentHash) {
|
|
|
1292
2106
|
return id;
|
|
1293
2107
|
}
|
|
1294
2108
|
async function getEntries(executor, ids) {
|
|
1295
|
-
const normalizedIds =
|
|
2109
|
+
const normalizedIds = dedupeStrings2(ids);
|
|
1296
2110
|
if (normalizedIds.length === 0) {
|
|
1297
2111
|
return [];
|
|
1298
2112
|
}
|
|
1299
2113
|
const byId = /* @__PURE__ */ new Map();
|
|
1300
|
-
for (const chunk of
|
|
2114
|
+
for (const chunk of chunkValues2(normalizedIds, LOOKUP_CHUNK_SIZE2)) {
|
|
1301
2115
|
const placeholders = chunk.map(() => "?").join(", ");
|
|
1302
2116
|
const result = await executor.execute({
|
|
1303
2117
|
sql: `
|
|
@@ -1321,12 +2135,12 @@ async function getEntry(executor, id) {
|
|
|
1321
2135
|
return entry ?? null;
|
|
1322
2136
|
}
|
|
1323
2137
|
async function findExistingHashes(executor, hashes) {
|
|
1324
|
-
const normalizedHashes =
|
|
2138
|
+
const normalizedHashes = dedupeStrings2(hashes);
|
|
1325
2139
|
if (normalizedHashes.length === 0) {
|
|
1326
2140
|
return /* @__PURE__ */ new Set();
|
|
1327
2141
|
}
|
|
1328
2142
|
const matches = /* @__PURE__ */ new Set();
|
|
1329
|
-
for (const chunk of
|
|
2143
|
+
for (const chunk of chunkValues2(normalizedHashes, LOOKUP_CHUNK_SIZE2)) {
|
|
1330
2144
|
const placeholders = chunk.map(() => "?").join(", ");
|
|
1331
2145
|
const result = await executor.execute({
|
|
1332
2146
|
sql: `
|
|
@@ -1344,12 +2158,12 @@ async function findExistingHashes(executor, hashes) {
|
|
|
1344
2158
|
return matches;
|
|
1345
2159
|
}
|
|
1346
2160
|
async function findExistingNormHashes(executor, hashes) {
|
|
1347
|
-
const normalizedHashes =
|
|
2161
|
+
const normalizedHashes = dedupeStrings2(hashes);
|
|
1348
2162
|
if (normalizedHashes.length === 0) {
|
|
1349
2163
|
return /* @__PURE__ */ new Set();
|
|
1350
2164
|
}
|
|
1351
2165
|
const matches = /* @__PURE__ */ new Set();
|
|
1352
|
-
for (const chunk of
|
|
2166
|
+
for (const chunk of chunkValues2(normalizedHashes, LOOKUP_CHUNK_SIZE2)) {
|
|
1353
2167
|
const placeholders = chunk.map(() => "?").join(", ");
|
|
1354
2168
|
const result = await executor.execute({
|
|
1355
2169
|
sql: `
|
|
@@ -1378,7 +2192,7 @@ async function retireEntry(executor, id, reason) {
|
|
|
1378
2192
|
WHERE id = ?
|
|
1379
2193
|
AND ${ACTIVE_ENTRY_CLAUSE}
|
|
1380
2194
|
`,
|
|
1381
|
-
args: [now,
|
|
2195
|
+
args: [now, normalizeOptionalString4(reason), now, id]
|
|
1382
2196
|
});
|
|
1383
2197
|
return result.rowsAffected > 0;
|
|
1384
2198
|
}
|
|
@@ -1412,7 +2226,7 @@ async function supersedeEntry(executor, oldId, newId, kind, reason) {
|
|
|
1412
2226
|
WHERE id = ?
|
|
1413
2227
|
AND ${ACTIVE_ENTRY_CLAUSE}
|
|
1414
2228
|
`,
|
|
1415
|
-
args: [normalizedNewId,
|
|
2229
|
+
args: [normalizedNewId, normalizeOptionalString4(kind) ?? "update", normalizeOptionalString4(reason), now, normalizedOldId]
|
|
1416
2230
|
});
|
|
1417
2231
|
return result.rowsAffected > 0;
|
|
1418
2232
|
}
|
|
@@ -1552,25 +2366,25 @@ async function updateEntry(executor, id, fields, options) {
|
|
|
1552
2366
|
assignments.push("claim_support_observed_at = ?");
|
|
1553
2367
|
assignments.push("claim_support_mode = ?");
|
|
1554
2368
|
args.push(
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
2369
|
+
normalizeOptionalString4(lifecycleUpdate.claim_key),
|
|
2370
|
+
normalizeOptionalString4(lifecycleUpdate.claim_key_raw),
|
|
2371
|
+
normalizeOptionalString4(lifecycleUpdate.claim_key_status),
|
|
2372
|
+
normalizeOptionalString4(lifecycleUpdate.claim_key_source),
|
|
1559
2373
|
normalizeOptionalNumber(lifecycleUpdate.claim_key_confidence),
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
2374
|
+
normalizeOptionalString4(lifecycleUpdate.claim_key_rationale),
|
|
2375
|
+
normalizeOptionalString4(lifecycleUpdate.claim_support_source_kind),
|
|
2376
|
+
normalizeOptionalString4(lifecycleUpdate.claim_support_locator),
|
|
2377
|
+
normalizeTimestamp2(lifecycleUpdate.claim_support_observed_at),
|
|
2378
|
+
normalizeOptionalString4(lifecycleUpdate.claim_support_mode)
|
|
1565
2379
|
);
|
|
1566
2380
|
}
|
|
1567
2381
|
if (fields.valid_from !== void 0) {
|
|
1568
2382
|
assignments.push("valid_from = ?");
|
|
1569
|
-
args.push(
|
|
2383
|
+
args.push(normalizeOptionalString4(fields.valid_from));
|
|
1570
2384
|
}
|
|
1571
2385
|
if (fields.valid_to !== void 0) {
|
|
1572
2386
|
assignments.push("valid_to = ?");
|
|
1573
|
-
args.push(
|
|
2387
|
+
args.push(normalizeOptionalString4(fields.valid_to));
|
|
1574
2388
|
}
|
|
1575
2389
|
if (assignments.length === 0) {
|
|
1576
2390
|
return false;
|
|
@@ -1636,7 +2450,7 @@ async function recordRecallEvent(executor, entryId, query, sessionKey) {
|
|
|
1636
2450
|
)
|
|
1637
2451
|
VALUES (?, ?, ?, ?, ?)
|
|
1638
2452
|
`,
|
|
1639
|
-
args: [
|
|
2453
|
+
args: [randomUUID3(), entryId, query, normalizeOptionalString4(sessionKey), now]
|
|
1640
2454
|
});
|
|
1641
2455
|
}
|
|
1642
2456
|
async function getIngestLogEntry(executor, filePath) {
|
|
@@ -1676,25 +2490,25 @@ async function insertIngestLogEntry(executor, filePath, fileHash, entryCount) {
|
|
|
1676
2490
|
args: [filePath, fileHash, (/* @__PURE__ */ new Date()).toISOString(), normalizeInteger(entryCount, 0)]
|
|
1677
2491
|
});
|
|
1678
2492
|
}
|
|
1679
|
-
function
|
|
2493
|
+
function dedupeStrings2(values) {
|
|
1680
2494
|
return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
|
|
1681
2495
|
}
|
|
1682
|
-
function
|
|
2496
|
+
function chunkValues2(values, size) {
|
|
1683
2497
|
const chunks = [];
|
|
1684
2498
|
for (let index = 0; index < values.length; index += size) {
|
|
1685
2499
|
chunks.push(values.slice(index, index + size));
|
|
1686
2500
|
}
|
|
1687
2501
|
return chunks;
|
|
1688
2502
|
}
|
|
1689
|
-
function
|
|
2503
|
+
function normalizeOptionalString4(value) {
|
|
1690
2504
|
if (value === void 0) {
|
|
1691
2505
|
return null;
|
|
1692
2506
|
}
|
|
1693
2507
|
const trimmed = value.trim();
|
|
1694
2508
|
return trimmed.length > 0 ? trimmed : null;
|
|
1695
2509
|
}
|
|
1696
|
-
function
|
|
1697
|
-
return
|
|
2510
|
+
function normalizeTimestamp2(value) {
|
|
2511
|
+
return normalizeOptionalString4(value);
|
|
1698
2512
|
}
|
|
1699
2513
|
function normalizeOptionalNumber(value) {
|
|
1700
2514
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
@@ -1721,9 +2535,10 @@ function normalizeInteger(value, fallback) {
|
|
|
1721
2535
|
}
|
|
1722
2536
|
|
|
1723
2537
|
// src/adapters/db/schema.ts
|
|
1724
|
-
var SCHEMA_VERSION = "
|
|
2538
|
+
var SCHEMA_VERSION = "10";
|
|
1725
2539
|
var VECTOR_INDEX_NAME = "idx_entries_embedding";
|
|
1726
2540
|
var EPISODE_VECTOR_INDEX_NAME = "idx_episodes_embedding";
|
|
2541
|
+
var PROCEDURE_VECTOR_INDEX_NAME = "idx_procedures_embedding";
|
|
1727
2542
|
var BULK_WRITE_STATE_META_KEY = "bulk_write_state";
|
|
1728
2543
|
var LAST_BULK_INGEST_META_KEY = "last_bulk_ingest_at";
|
|
1729
2544
|
var CREATE_ENTRIES_TABLE_SQL = `
|
|
@@ -1839,6 +2654,59 @@ var CREATE_EPISODES_TABLE_SQL = `
|
|
|
1839
2654
|
updated_at TEXT NOT NULL
|
|
1840
2655
|
)
|
|
1841
2656
|
`;
|
|
2657
|
+
var CREATE_PROCEDURES_TABLE_SQL = `
|
|
2658
|
+
CREATE TABLE IF NOT EXISTS procedures (
|
|
2659
|
+
id TEXT PRIMARY KEY,
|
|
2660
|
+
procedure_key TEXT NOT NULL,
|
|
2661
|
+
title TEXT NOT NULL,
|
|
2662
|
+
goal TEXT NOT NULL,
|
|
2663
|
+
body_json TEXT NOT NULL,
|
|
2664
|
+
recall_text TEXT NOT NULL,
|
|
2665
|
+
source_file TEXT,
|
|
2666
|
+
source_hash TEXT NOT NULL,
|
|
2667
|
+
revision_hash TEXT NOT NULL,
|
|
2668
|
+
embedding F32_BLOB(1024),
|
|
2669
|
+
retired INTEGER NOT NULL DEFAULT 0,
|
|
2670
|
+
retired_at TEXT,
|
|
2671
|
+
retired_reason TEXT,
|
|
2672
|
+
superseded_by TEXT REFERENCES procedures(id),
|
|
2673
|
+
created_at TEXT NOT NULL,
|
|
2674
|
+
updated_at TEXT NOT NULL
|
|
2675
|
+
)
|
|
2676
|
+
`;
|
|
2677
|
+
var CREATE_PROCEDURES_FTS_TABLE_SQL = `
|
|
2678
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS procedures_fts USING fts5(
|
|
2679
|
+
title,
|
|
2680
|
+
recall_text,
|
|
2681
|
+
content=procedures,
|
|
2682
|
+
content_rowid=rowid
|
|
2683
|
+
)
|
|
2684
|
+
`;
|
|
2685
|
+
var CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL = `
|
|
2686
|
+
CREATE TRIGGER IF NOT EXISTS procedures_ai AFTER INSERT ON procedures
|
|
2687
|
+
WHEN new.retired = 0 AND new.superseded_by IS NULL BEGIN
|
|
2688
|
+
INSERT INTO procedures_fts(rowid, title, recall_text)
|
|
2689
|
+
VALUES (new.rowid, new.title, new.recall_text);
|
|
2690
|
+
END
|
|
2691
|
+
`;
|
|
2692
|
+
var CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL = `
|
|
2693
|
+
CREATE TRIGGER IF NOT EXISTS procedures_ad AFTER DELETE ON procedures
|
|
2694
|
+
WHEN old.retired = 0 AND old.superseded_by IS NULL BEGIN
|
|
2695
|
+
INSERT INTO procedures_fts(procedures_fts, rowid, title, recall_text)
|
|
2696
|
+
VALUES ('delete', old.rowid, old.title, old.recall_text);
|
|
2697
|
+
END
|
|
2698
|
+
`;
|
|
2699
|
+
var CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL = `
|
|
2700
|
+
CREATE TRIGGER IF NOT EXISTS procedures_au AFTER UPDATE ON procedures BEGIN
|
|
2701
|
+
INSERT INTO procedures_fts(procedures_fts, rowid, title, recall_text)
|
|
2702
|
+
SELECT 'delete', old.rowid, old.title, old.recall_text
|
|
2703
|
+
WHERE old.retired = 0 AND old.superseded_by IS NULL;
|
|
2704
|
+
|
|
2705
|
+
INSERT INTO procedures_fts(rowid, title, recall_text)
|
|
2706
|
+
SELECT new.rowid, new.title, new.recall_text
|
|
2707
|
+
WHERE new.retired = 0 AND new.superseded_by IS NULL;
|
|
2708
|
+
END
|
|
2709
|
+
`;
|
|
1842
2710
|
var CREATE_RECALL_EVENTS_TABLE_SQL = `
|
|
1843
2711
|
CREATE TABLE IF NOT EXISTS recall_events (
|
|
1844
2712
|
id TEXT PRIMARY KEY,
|
|
@@ -2006,6 +2874,32 @@ var CREATE_EPISODES_SOURCE_SOURCE_ID_UNIQUE_INDEX_SQL = `
|
|
|
2006
2874
|
ON episodes(source, source_id)
|
|
2007
2875
|
WHERE source_id IS NOT NULL
|
|
2008
2876
|
`;
|
|
2877
|
+
var CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL = `
|
|
2878
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_procedure_key
|
|
2879
|
+
ON procedures(procedure_key)
|
|
2880
|
+
`;
|
|
2881
|
+
var CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL = `
|
|
2882
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_revision_hash
|
|
2883
|
+
ON procedures(revision_hash)
|
|
2884
|
+
`;
|
|
2885
|
+
var CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL = `
|
|
2886
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_source_hash
|
|
2887
|
+
ON procedures(source_hash)
|
|
2888
|
+
`;
|
|
2889
|
+
var CREATE_PROCEDURES_RETIRED_INDEX_SQL = `
|
|
2890
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_retired
|
|
2891
|
+
ON procedures(retired)
|
|
2892
|
+
`;
|
|
2893
|
+
var CREATE_PROCEDURES_CREATED_AT_INDEX_SQL = `
|
|
2894
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_created_at
|
|
2895
|
+
ON procedures(created_at)
|
|
2896
|
+
`;
|
|
2897
|
+
var CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL = `
|
|
2898
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_procedures_active_procedure_key
|
|
2899
|
+
ON procedures(procedure_key)
|
|
2900
|
+
WHERE retired = 0
|
|
2901
|
+
AND superseded_by IS NULL
|
|
2902
|
+
`;
|
|
2009
2903
|
var CREATE_RECALL_EVENTS_ENTRY_ID_INDEX_SQL = `
|
|
2010
2904
|
CREATE INDEX IF NOT EXISTS idx_recall_events_entry_id
|
|
2011
2905
|
ON recall_events(entry_id)
|
|
@@ -2040,6 +2934,19 @@ var CREATE_EPISODES_EMBEDDING_INDEX_SQL = `
|
|
|
2040
2934
|
AND retired = 0
|
|
2041
2935
|
AND superseded_by IS NULL
|
|
2042
2936
|
`;
|
|
2937
|
+
var CREATE_PROCEDURES_EMBEDDING_INDEX_SQL = `
|
|
2938
|
+
CREATE INDEX IF NOT EXISTS idx_procedures_embedding ON procedures (
|
|
2939
|
+
libsql_vector_idx(
|
|
2940
|
+
embedding,
|
|
2941
|
+
'metric=cosine',
|
|
2942
|
+
'compress_neighbors=float8',
|
|
2943
|
+
'max_neighbors=50'
|
|
2944
|
+
)
|
|
2945
|
+
)
|
|
2946
|
+
WHERE embedding IS NOT NULL
|
|
2947
|
+
AND retired = 0
|
|
2948
|
+
AND superseded_by IS NULL
|
|
2949
|
+
`;
|
|
2043
2950
|
var SCHEMA_STATEMENTS = [
|
|
2044
2951
|
CREATE_ENTRIES_TABLE_SQL,
|
|
2045
2952
|
CREATE_ENTRIES_FTS_TABLE_SQL,
|
|
@@ -2048,6 +2955,11 @@ var SCHEMA_STATEMENTS = [
|
|
|
2048
2955
|
CREATE_ENTRIES_FTS_UPDATE_TRIGGER_SQL,
|
|
2049
2956
|
CREATE_INGEST_LOG_TABLE_SQL,
|
|
2050
2957
|
CREATE_EPISODES_TABLE_SQL,
|
|
2958
|
+
CREATE_PROCEDURES_TABLE_SQL,
|
|
2959
|
+
CREATE_PROCEDURES_FTS_TABLE_SQL,
|
|
2960
|
+
CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL,
|
|
2961
|
+
CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL,
|
|
2962
|
+
CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL,
|
|
2051
2963
|
CREATE_RECALL_EVENTS_TABLE_SQL,
|
|
2052
2964
|
CREATE_SURGEON_RUNS_TABLE_SQL,
|
|
2053
2965
|
CREATE_SURGEON_RUN_ACTIONS_TABLE_SQL,
|
|
@@ -2076,6 +2988,12 @@ var SCHEMA_STATEMENTS = [
|
|
|
2076
2988
|
CREATE_EPISODES_SOURCE_ID_INDEX_SQL,
|
|
2077
2989
|
CREATE_EPISODES_RETIRED_INDEX_SQL,
|
|
2078
2990
|
CREATE_EPISODES_SOURCE_SOURCE_ID_UNIQUE_INDEX_SQL,
|
|
2991
|
+
CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL,
|
|
2992
|
+
CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL,
|
|
2993
|
+
CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL,
|
|
2994
|
+
CREATE_PROCEDURES_RETIRED_INDEX_SQL,
|
|
2995
|
+
CREATE_PROCEDURES_CREATED_AT_INDEX_SQL,
|
|
2996
|
+
CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL,
|
|
2079
2997
|
CREATE_RECALL_EVENTS_ENTRY_ID_INDEX_SQL,
|
|
2080
2998
|
CREATE_RECALL_EVENTS_RECALLED_AT_INDEX_SQL
|
|
2081
2999
|
];
|
|
@@ -2099,7 +3017,12 @@ async function initSchema(db) {
|
|
|
2099
3017
|
await migrateV8ToV9(db);
|
|
2100
3018
|
currentVersion = "9";
|
|
2101
3019
|
}
|
|
3020
|
+
if (currentVersion === "9") {
|
|
3021
|
+
await migrateV9ToV10(db);
|
|
3022
|
+
currentVersion = "10";
|
|
3023
|
+
}
|
|
2102
3024
|
const hadEntriesFts = await tableExists(db, "entries_fts");
|
|
3025
|
+
const hadProceduresFts = await tableExists(db, "procedures_fts");
|
|
2103
3026
|
for (const statement of SCHEMA_STATEMENTS) {
|
|
2104
3027
|
await db.execute(statement);
|
|
2105
3028
|
}
|
|
@@ -2115,13 +3038,13 @@ async function initSchema(db) {
|
|
|
2115
3038
|
await finalizeBulkWrites(db);
|
|
2116
3039
|
return;
|
|
2117
3040
|
}
|
|
2118
|
-
if (currentVersion !== SCHEMA_VERSION || !hadEntriesFts) {
|
|
3041
|
+
if (currentVersion !== SCHEMA_VERSION || !hadEntriesFts || !hadProceduresFts) {
|
|
2119
3042
|
await rebuildFts(db);
|
|
2120
3043
|
}
|
|
2121
3044
|
await ensureVectorIndexes(db);
|
|
2122
3045
|
}
|
|
2123
3046
|
async function assertSupportedSchemaState(db, currentVersion) {
|
|
2124
|
-
if (currentVersion && currentVersion !== "5" && currentVersion !== "6" && currentVersion !== "7" && currentVersion !== "8" && currentVersion !== SCHEMA_VERSION) {
|
|
3047
|
+
if (currentVersion && currentVersion !== "5" && currentVersion !== "6" && currentVersion !== "7" && currentVersion !== "8" && currentVersion !== "9" && currentVersion !== SCHEMA_VERSION) {
|
|
2125
3048
|
throw new Error(
|
|
2126
3049
|
`Unsupported agenr database schema version "${currentVersion}". This build only supports schema version ${SCHEMA_VERSION}. Create a fresh database with \`agenr db reset\` or manually migrate the data into a new database.`
|
|
2127
3050
|
);
|
|
@@ -2207,8 +3130,22 @@ async function migrateV8ToV9(db) {
|
|
|
2207
3130
|
}
|
|
2208
3131
|
await db.execute(CREATE_SURGEON_RUN_PROPOSALS_REVIEW_STATUS_INDEX_SQL);
|
|
2209
3132
|
}
|
|
3133
|
+
async function migrateV9ToV10(db) {
|
|
3134
|
+
await db.execute(CREATE_PROCEDURES_TABLE_SQL);
|
|
3135
|
+
await db.execute(CREATE_PROCEDURES_FTS_TABLE_SQL);
|
|
3136
|
+
await db.execute(CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL);
|
|
3137
|
+
await db.execute(CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL);
|
|
3138
|
+
await db.execute(CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL);
|
|
3139
|
+
await db.execute(CREATE_PROCEDURES_PROCEDURE_KEY_INDEX_SQL);
|
|
3140
|
+
await db.execute(CREATE_PROCEDURES_REVISION_HASH_INDEX_SQL);
|
|
3141
|
+
await db.execute(CREATE_PROCEDURES_SOURCE_HASH_INDEX_SQL);
|
|
3142
|
+
await db.execute(CREATE_PROCEDURES_RETIRED_INDEX_SQL);
|
|
3143
|
+
await db.execute(CREATE_PROCEDURES_CREATED_AT_INDEX_SQL);
|
|
3144
|
+
await db.execute(CREATE_PROCEDURES_ACTIVE_KEY_UNIQUE_INDEX_SQL);
|
|
3145
|
+
}
|
|
2210
3146
|
async function rebuildFts(db) {
|
|
2211
3147
|
await db.execute("INSERT INTO entries_fts(entries_fts) VALUES ('rebuild')");
|
|
3148
|
+
await db.execute("INSERT INTO procedures_fts(procedures_fts) VALUES ('rebuild')");
|
|
2212
3149
|
}
|
|
2213
3150
|
async function prepareBulkWrites(db) {
|
|
2214
3151
|
await runImmediateTransaction(db, async () => {
|
|
@@ -2223,6 +3160,9 @@ async function prepareBulkWrites(db) {
|
|
|
2223
3160
|
await db.execute("DROP TRIGGER IF EXISTS entries_ai");
|
|
2224
3161
|
await db.execute("DROP TRIGGER IF EXISTS entries_ad");
|
|
2225
3162
|
await db.execute("DROP TRIGGER IF EXISTS entries_au");
|
|
3163
|
+
await db.execute("DROP TRIGGER IF EXISTS procedures_ai");
|
|
3164
|
+
await db.execute("DROP TRIGGER IF EXISTS procedures_ad");
|
|
3165
|
+
await db.execute("DROP TRIGGER IF EXISTS procedures_au");
|
|
2226
3166
|
await dropVectorIndexes(db);
|
|
2227
3167
|
});
|
|
2228
3168
|
}
|
|
@@ -2231,6 +3171,9 @@ async function finalizeBulkWrites(db) {
|
|
|
2231
3171
|
await db.execute(CREATE_ENTRIES_FTS_INSERT_TRIGGER_SQL);
|
|
2232
3172
|
await db.execute(CREATE_ENTRIES_FTS_DELETE_TRIGGER_SQL);
|
|
2233
3173
|
await db.execute(CREATE_ENTRIES_FTS_UPDATE_TRIGGER_SQL);
|
|
3174
|
+
await db.execute(CREATE_PROCEDURES_FTS_INSERT_TRIGGER_SQL);
|
|
3175
|
+
await db.execute(CREATE_PROCEDURES_FTS_DELETE_TRIGGER_SQL);
|
|
3176
|
+
await db.execute(CREATE_PROCEDURES_FTS_UPDATE_TRIGGER_SQL);
|
|
2234
3177
|
await rebuildFts(db);
|
|
2235
3178
|
await ensureVectorIndexes(db);
|
|
2236
3179
|
await db.execute({
|
|
@@ -2316,6 +3259,7 @@ async function ensureVectorIndexes(db) {
|
|
|
2316
3259
|
try {
|
|
2317
3260
|
await db.execute(CREATE_ENTRIES_EMBEDDING_INDEX_SQL);
|
|
2318
3261
|
await db.execute(CREATE_EPISODES_EMBEDDING_INDEX_SQL);
|
|
3262
|
+
await db.execute(CREATE_PROCEDURES_EMBEDDING_INDEX_SQL);
|
|
2319
3263
|
} catch (error) {
|
|
2320
3264
|
if (!isVectorUnavailableError(error)) {
|
|
2321
3265
|
throw error;
|
|
@@ -2326,6 +3270,7 @@ async function dropVectorIndexes(db) {
|
|
|
2326
3270
|
try {
|
|
2327
3271
|
await db.execute(`DROP INDEX IF EXISTS ${VECTOR_INDEX_NAME}`);
|
|
2328
3272
|
await db.execute(`DROP INDEX IF EXISTS ${EPISODE_VECTOR_INDEX_NAME}`);
|
|
3273
|
+
await db.execute(`DROP INDEX IF EXISTS ${PROCEDURE_VECTOR_INDEX_NAME}`);
|
|
2329
3274
|
} catch (error) {
|
|
2330
3275
|
if (!isVectorUnavailableError(error)) {
|
|
2331
3276
|
throw error;
|
|
@@ -2416,6 +3361,46 @@ var LibsqlDatabase = class _LibsqlDatabase {
|
|
|
2416
3361
|
async updateEpisodeEmbedding(id, embedding) {
|
|
2417
3362
|
await updateEpisodeEmbedding(this.executor, id, embedding);
|
|
2418
3363
|
}
|
|
3364
|
+
/** Inserts or updates one procedure revision row. */
|
|
3365
|
+
async upsertProcedure(procedure) {
|
|
3366
|
+
return upsertProcedure(this.executor, procedure);
|
|
3367
|
+
}
|
|
3368
|
+
/** Loads one active procedure by primary key. */
|
|
3369
|
+
async getProcedure(id) {
|
|
3370
|
+
return getProcedure(this.executor, id);
|
|
3371
|
+
}
|
|
3372
|
+
/** Hydrates active procedures by ID while preserving caller order. */
|
|
3373
|
+
async hydrateProcedures(ids) {
|
|
3374
|
+
return hydrateProcedures(this.executor, ids);
|
|
3375
|
+
}
|
|
3376
|
+
/** Loads one active procedure revision by stable procedure key. */
|
|
3377
|
+
async findActiveProcedureByKey(procedureKey) {
|
|
3378
|
+
return findActiveProcedureByKey(this.executor, procedureKey);
|
|
3379
|
+
}
|
|
3380
|
+
/** Finds active procedures by vector similarity. */
|
|
3381
|
+
async procedureVectorSearch(params) {
|
|
3382
|
+
return procedureVectorSearch(this.executor, params);
|
|
3383
|
+
}
|
|
3384
|
+
/** Finds active procedures by lexical FTS search. */
|
|
3385
|
+
async procedureFtsSearch(params) {
|
|
3386
|
+
return procedureFtsSearch(this.executor, params);
|
|
3387
|
+
}
|
|
3388
|
+
/** Lists active procedures that still need embeddings. */
|
|
3389
|
+
async listProceduresWithoutEmbeddings(limit) {
|
|
3390
|
+
return listProceduresWithoutEmbeddings(this.executor, limit);
|
|
3391
|
+
}
|
|
3392
|
+
/** Updates only the embedding payload for one procedure row. */
|
|
3393
|
+
async updateProcedureEmbedding(id, embedding) {
|
|
3394
|
+
await updateProcedureEmbedding(this.executor, id, embedding);
|
|
3395
|
+
}
|
|
3396
|
+
/** Marks one active procedure revision as retired. */
|
|
3397
|
+
async retireProcedure(id, reason) {
|
|
3398
|
+
return retireProcedure(this.executor, id, reason);
|
|
3399
|
+
}
|
|
3400
|
+
/** Marks one active procedure revision as superseded by a newer revision. */
|
|
3401
|
+
async supersedeProcedure(oldId, newId, reason) {
|
|
3402
|
+
return supersedeProcedure(this.executor, oldId, newId, reason);
|
|
3403
|
+
}
|
|
2419
3404
|
/** Finds which normalized content hashes already exist in storage. */
|
|
2420
3405
|
async findExistingNormHashes(hashes) {
|
|
2421
3406
|
return findExistingNormHashes(this.executor, hashes);
|
|
@@ -2530,7 +3515,7 @@ import path2 from "path";
|
|
|
2530
3515
|
import { fileURLToPath } from "url";
|
|
2531
3516
|
|
|
2532
3517
|
// src/adapters/shared/validation.ts
|
|
2533
|
-
function
|
|
3518
|
+
function isRecord2(value) {
|
|
2534
3519
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2535
3520
|
}
|
|
2536
3521
|
function pushIssue(issues, path3, message) {
|
|
@@ -2765,7 +3750,7 @@ function normalizeAgenrConfig(value, options) {
|
|
|
2765
3750
|
}
|
|
2766
3751
|
};
|
|
2767
3752
|
}
|
|
2768
|
-
if (!
|
|
3753
|
+
if (!isRecord2(value)) {
|
|
2769
3754
|
return {
|
|
2770
3755
|
ok: false,
|
|
2771
3756
|
issues: [{ path: "config", message: "Expected a JSON object." }]
|
|
@@ -2884,7 +3869,7 @@ function parseCredentials(value, path3, issues) {
|
|
|
2884
3869
|
if (value === void 0) {
|
|
2885
3870
|
return void 0;
|
|
2886
3871
|
}
|
|
2887
|
-
if (!
|
|
3872
|
+
if (!isRecord2(value)) {
|
|
2888
3873
|
pushIssue(issues, path3, "Expected an object.");
|
|
2889
3874
|
return void 0;
|
|
2890
3875
|
}
|
|
@@ -2907,7 +3892,7 @@ function parseModelConfig(value, path3, issues) {
|
|
|
2907
3892
|
if (value === void 0) {
|
|
2908
3893
|
return void 0;
|
|
2909
3894
|
}
|
|
2910
|
-
if (!
|
|
3895
|
+
if (!isRecord2(value)) {
|
|
2911
3896
|
pushIssue(issues, path3, "Expected an object.");
|
|
2912
3897
|
return void 0;
|
|
2913
3898
|
}
|
|
@@ -2933,7 +3918,7 @@ function parseClaimExtractionConfig(value, path3, issues) {
|
|
|
2933
3918
|
resolved: defaults
|
|
2934
3919
|
};
|
|
2935
3920
|
}
|
|
2936
|
-
if (!
|
|
3921
|
+
if (!isRecord2(value)) {
|
|
2937
3922
|
pushIssue(issues, path3, "Expected an object.");
|
|
2938
3923
|
return {
|
|
2939
3924
|
resolved: defaults
|
|
@@ -2978,7 +3963,7 @@ function parseSurgeonConfig(value, path3, issues) {
|
|
|
2978
3963
|
resolved: defaults
|
|
2979
3964
|
};
|
|
2980
3965
|
}
|
|
2981
|
-
if (!
|
|
3966
|
+
if (!isRecord2(value)) {
|
|
2982
3967
|
pushIssue(issues, path3, "Expected an object.");
|
|
2983
3968
|
return {
|
|
2984
3969
|
resolved: defaults
|
|
@@ -3030,7 +4015,7 @@ function parseRetirementPassConfig(value, path3, issues) {
|
|
|
3030
4015
|
resolved: defaults
|
|
3031
4016
|
};
|
|
3032
4017
|
}
|
|
3033
|
-
if (!
|
|
4018
|
+
if (!isRecord2(value)) {
|
|
3034
4019
|
pushIssue(issues, path3, "Expected an object.");
|
|
3035
4020
|
return {
|
|
3036
4021
|
resolved: defaults
|
|
@@ -3047,7 +4032,7 @@ function parseRetirementPassConfig(value, path3, issues) {
|
|
|
3047
4032
|
resolved: defaults
|
|
3048
4033
|
};
|
|
3049
4034
|
}
|
|
3050
|
-
if (!
|
|
4035
|
+
if (!isRecord2(retirement)) {
|
|
3051
4036
|
pushIssue(issues, `${path3}.retirement`, "Expected an object.");
|
|
3052
4037
|
return {
|
|
3053
4038
|
resolved: defaults
|
|
@@ -3221,11 +4206,11 @@ function resolveConfigDir() {
|
|
|
3221
4206
|
return process.env.AGENR_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
|
|
3222
4207
|
}
|
|
3223
4208
|
function resolveConfigPath(options = {}) {
|
|
3224
|
-
const envConfigPath =
|
|
4209
|
+
const envConfigPath = normalizeOptionalString5(process.env.AGENR_CONFIG_PATH);
|
|
3225
4210
|
if (envConfigPath) {
|
|
3226
4211
|
return envConfigPath;
|
|
3227
4212
|
}
|
|
3228
|
-
const explicitConfigPath =
|
|
4213
|
+
const explicitConfigPath = normalizeOptionalString5(options.configPath);
|
|
3229
4214
|
if (explicitConfigPath) {
|
|
3230
4215
|
return explicitConfigPath;
|
|
3231
4216
|
}
|
|
@@ -3236,7 +4221,7 @@ function resolveConfigPath(options = {}) {
|
|
|
3236
4221
|
return path2.join(resolveConfigDir(), "config.json");
|
|
3237
4222
|
}
|
|
3238
4223
|
function resolveDbPath(config) {
|
|
3239
|
-
return
|
|
4224
|
+
return normalizeOptionalString5(process.env.AGENR_DB_PATH) ?? normalizeOptionalString5(config?.dbPath) ?? resolvePersistedDefaultDbPath();
|
|
3240
4225
|
}
|
|
3241
4226
|
function resolveClaimExtractionConfig(config) {
|
|
3242
4227
|
if (config && "enabled" in (config.claimExtraction ?? {})) {
|
|
@@ -3327,7 +4312,7 @@ function writeConfig(config, options = {}) {
|
|
|
3327
4312
|
}
|
|
3328
4313
|
}
|
|
3329
4314
|
function resolveAdjacentConfigPath(dbPath) {
|
|
3330
|
-
const normalizedDbPath =
|
|
4315
|
+
const normalizedDbPath = normalizeOptionalString5(dbPath);
|
|
3331
4316
|
if (!normalizedDbPath || normalizedDbPath === ":memory:") {
|
|
3332
4317
|
return void 0;
|
|
3333
4318
|
}
|
|
@@ -3340,7 +4325,7 @@ function resolveAdjacentConfigPath(dbPath) {
|
|
|
3340
4325
|
}
|
|
3341
4326
|
return path2.join(path2.dirname(normalizedDbPath), "config.json");
|
|
3342
4327
|
}
|
|
3343
|
-
function
|
|
4328
|
+
function normalizeOptionalString5(value) {
|
|
3344
4329
|
const normalized = value?.trim();
|
|
3345
4330
|
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
3346
4331
|
}
|
|
@@ -3358,7 +4343,7 @@ function resolvePersistedDefaultDbPath() {
|
|
|
3358
4343
|
return path2.join(resolveConfigDir(), DEFAULT_DB_NAME);
|
|
3359
4344
|
}
|
|
3360
4345
|
function resolveReadDefaultDbPath(options) {
|
|
3361
|
-
return
|
|
4346
|
+
return normalizeOptionalString5(process.env.AGENR_DB_PATH) ?? normalizeOptionalString5(options.dbPath) ?? resolvePersistedDefaultDbPath();
|
|
3362
4347
|
}
|
|
3363
4348
|
function formatConfigValidationError(configPath, issues) {
|
|
3364
4349
|
const details = issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ");
|
|
@@ -3405,7 +4390,7 @@ async function embedTexts(texts, apiKey, model) {
|
|
|
3405
4390
|
if (texts.length === 0) {
|
|
3406
4391
|
return [];
|
|
3407
4392
|
}
|
|
3408
|
-
const chunks =
|
|
4393
|
+
const chunks = chunkValues3(texts, EMBEDDING_BATCH_SIZE);
|
|
3409
4394
|
const chunkResults = new Array(chunks.length);
|
|
3410
4395
|
let nextChunkIndex = 0;
|
|
3411
4396
|
await Promise.all(
|
|
@@ -3525,7 +4510,7 @@ function getErrorSnippet(rawBody) {
|
|
|
3525
4510
|
const maxLength = 200;
|
|
3526
4511
|
return trimmed.length <= maxLength ? trimmed : `${trimmed.slice(0, maxLength)}...`;
|
|
3527
4512
|
}
|
|
3528
|
-
function
|
|
4513
|
+
function chunkValues3(values, chunkSize) {
|
|
3529
4514
|
const chunks = [];
|
|
3530
4515
|
for (let index = 0; index < values.length; index += chunkSize) {
|
|
3531
4516
|
chunks.push(values.slice(index, index + chunkSize));
|
|
@@ -3558,7 +4543,7 @@ var RECALL_CANDIDATE_SELECT_COLUMNS = `
|
|
|
3558
4543
|
e.retired,
|
|
3559
4544
|
e.created_at
|
|
3560
4545
|
`;
|
|
3561
|
-
var
|
|
4546
|
+
var FTS_TIERS2 = ["exact", "all_tokens", "any_tokens"];
|
|
3562
4547
|
var PREDECESSOR_EXPANSION_LIMIT_PER_SEED = 8;
|
|
3563
4548
|
var PREDECESSOR_EXPANSION_MAX_RESULTS = 40;
|
|
3564
4549
|
function createRecallAdapter(executor, embeddingPort) {
|
|
@@ -3625,7 +4610,7 @@ var LibsqlRecallAdapter = class {
|
|
|
3625
4610
|
const filters = buildEntryFilterClause(params.filters, "e");
|
|
3626
4611
|
const matches = /* @__PURE__ */ new Map();
|
|
3627
4612
|
for (const tier of plan) {
|
|
3628
|
-
const query =
|
|
4613
|
+
const query = compileLexicalTier2(tier);
|
|
3629
4614
|
let result;
|
|
3630
4615
|
try {
|
|
3631
4616
|
result = await this.executor.execute({
|
|
@@ -3814,16 +4799,16 @@ function normalizeStrings(values) {
|
|
|
3814
4799
|
return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
|
|
3815
4800
|
}
|
|
3816
4801
|
function compareFtsCandidates(left, right) {
|
|
3817
|
-
const tierDelta =
|
|
4802
|
+
const tierDelta = ftsTierPriority2(left.tier) - ftsTierPriority2(right.tier);
|
|
3818
4803
|
if (tierDelta !== 0) {
|
|
3819
4804
|
return tierDelta;
|
|
3820
4805
|
}
|
|
3821
4806
|
return left.rank - right.rank;
|
|
3822
4807
|
}
|
|
3823
|
-
function
|
|
3824
|
-
return
|
|
4808
|
+
function ftsTierPriority2(tier) {
|
|
4809
|
+
return FTS_TIERS2.indexOf(tier);
|
|
3825
4810
|
}
|
|
3826
|
-
function
|
|
4811
|
+
function compileLexicalTier2(tier) {
|
|
3827
4812
|
if (tier.tier === "exact") {
|
|
3828
4813
|
return `"${tier.text.replaceAll('"', '""')}"`;
|
|
3829
4814
|
}
|
|
@@ -3893,7 +4878,7 @@ function flattenClaimCentricRecallFamilies(families) {
|
|
|
3893
4878
|
}
|
|
3894
4879
|
function projectClaimCentricRecallEntry(recall2, options = {}) {
|
|
3895
4880
|
const entry = recall2.entry;
|
|
3896
|
-
const claimKey =
|
|
4881
|
+
const claimKey = normalizeOptionalString6(entry.claim_key);
|
|
3897
4882
|
const familyKey = claimKey ?? `entry:${entry.id}`;
|
|
3898
4883
|
const slotPolicy = resolveClaimSlotPolicy(claimKey, options.slotPolicyConfig).policy;
|
|
3899
4884
|
const asOfResolution = buildAsOfResolution(recall2, options.asOf);
|
|
@@ -3919,38 +4904,38 @@ function resolveMemoryState(recall2, asOfResolution) {
|
|
|
3919
4904
|
if (asOfResolution.relation === "active") {
|
|
3920
4905
|
return "current";
|
|
3921
4906
|
}
|
|
3922
|
-
return
|
|
4907
|
+
return normalizeOptionalString6(entry.superseded_by) ? "superseded" : "historical";
|
|
3923
4908
|
}
|
|
3924
4909
|
if (asOfResolution.relation === "observed_after" || asOfResolution.relation === "created_after") {
|
|
3925
4910
|
return "historical";
|
|
3926
4911
|
}
|
|
3927
|
-
if (
|
|
4912
|
+
if (normalizeOptionalString6(entry.superseded_by)) {
|
|
3928
4913
|
return "superseded";
|
|
3929
4914
|
}
|
|
3930
|
-
if (entry.retired ||
|
|
4915
|
+
if (entry.retired || normalizeOptionalString6(entry.valid_to)) {
|
|
3931
4916
|
return "historical";
|
|
3932
4917
|
}
|
|
3933
4918
|
return "current";
|
|
3934
4919
|
}
|
|
3935
|
-
if (
|
|
4920
|
+
if (normalizeOptionalString6(entry.superseded_by)) {
|
|
3936
4921
|
return "superseded";
|
|
3937
4922
|
}
|
|
3938
|
-
if (entry.retired ||
|
|
4923
|
+
if (entry.retired || normalizeOptionalString6(entry.valid_to)) {
|
|
3939
4924
|
return "historical";
|
|
3940
4925
|
}
|
|
3941
4926
|
return "current";
|
|
3942
4927
|
}
|
|
3943
4928
|
function resolveClaimStatus(recall2) {
|
|
3944
4929
|
const entry = recall2.entry;
|
|
3945
|
-
if (!
|
|
4930
|
+
if (!normalizeOptionalString6(entry.claim_key)) {
|
|
3946
4931
|
return "no_key";
|
|
3947
4932
|
}
|
|
3948
4933
|
return entry.claim_key_status ?? "legacy";
|
|
3949
4934
|
}
|
|
3950
4935
|
function buildFreshness(recall2, memoryState, asOfResolution) {
|
|
3951
4936
|
const entry = recall2.entry;
|
|
3952
|
-
const validFrom =
|
|
3953
|
-
const validTo =
|
|
4937
|
+
const validFrom = normalizeOptionalString6(entry.valid_from);
|
|
4938
|
+
const validTo = normalizeOptionalString6(entry.valid_to);
|
|
3954
4939
|
const createdAt = entry.created_at;
|
|
3955
4940
|
const labelParts = [`created ${createdAt}`];
|
|
3956
4941
|
if (validFrom || validTo) {
|
|
@@ -3978,12 +4963,12 @@ function buildFreshness(recall2, memoryState, asOfResolution) {
|
|
|
3978
4963
|
function buildProvenance(recall2) {
|
|
3979
4964
|
const entry = recall2.entry;
|
|
3980
4965
|
return {
|
|
3981
|
-
...
|
|
3982
|
-
...
|
|
3983
|
-
...
|
|
3984
|
-
...
|
|
3985
|
-
...
|
|
3986
|
-
...
|
|
4966
|
+
...normalizeOptionalString6(entry.superseded_by) ? { supersededById: entry.superseded_by } : {},
|
|
4967
|
+
...normalizeOptionalString6(entry.supersession_kind) ? { supersessionKind: entry.supersession_kind } : {},
|
|
4968
|
+
...normalizeOptionalString6(entry.supersession_reason) ? { supersessionReason: entry.supersession_reason } : {},
|
|
4969
|
+
...normalizeOptionalString6(entry.claim_support_source_kind) ? { supportSourceKind: entry.claim_support_source_kind } : {},
|
|
4970
|
+
...normalizeOptionalString6(entry.claim_support_locator) ? { supportLocator: entry.claim_support_locator } : {},
|
|
4971
|
+
...normalizeOptionalString6(entry.claim_support_observed_at) ? { supportObservedAt: entry.claim_support_observed_at } : {},
|
|
3987
4972
|
...entry.claim_support_mode ? { supportMode: entry.claim_support_mode } : {}
|
|
3988
4973
|
};
|
|
3989
4974
|
}
|
|
@@ -4019,12 +5004,12 @@ function buildWhySurfaced(recall2, asOfResolution) {
|
|
|
4019
5004
|
function formatScore(value) {
|
|
4020
5005
|
return value.toFixed(2);
|
|
4021
5006
|
}
|
|
4022
|
-
function
|
|
5007
|
+
function normalizeOptionalString6(value) {
|
|
4023
5008
|
const normalized = value?.trim();
|
|
4024
5009
|
return normalized && normalized.length > 0 ? normalized : void 0;
|
|
4025
5010
|
}
|
|
4026
5011
|
function buildAsOfResolution(recall2, asOf) {
|
|
4027
|
-
const normalizedAsOf =
|
|
5012
|
+
const normalizedAsOf = normalizeOptionalString6(asOf);
|
|
4028
5013
|
if (!normalizedAsOf) {
|
|
4029
5014
|
return void 0;
|
|
4030
5015
|
}
|
|
@@ -4088,7 +5073,7 @@ function formatAsOfRelation(relation) {
|
|
|
4088
5073
|
}
|
|
4089
5074
|
}
|
|
4090
5075
|
function parseTimestamp(value) {
|
|
4091
|
-
const normalized =
|
|
5076
|
+
const normalized = normalizeOptionalString6(value);
|
|
4092
5077
|
if (!normalized) {
|
|
4093
5078
|
return void 0;
|
|
4094
5079
|
}
|
|
@@ -4585,7 +5570,7 @@ async function searchEpisodes(query, database, now = /* @__PURE__ */ new Date())
|
|
|
4585
5570
|
if (limit === 0) {
|
|
4586
5571
|
return [];
|
|
4587
5572
|
}
|
|
4588
|
-
const normalizedEmbedding =
|
|
5573
|
+
const normalizedEmbedding = normalizeEmbedding3(query.embedding);
|
|
4589
5574
|
const bounds = query.timeWindow ? resolveTemporalWindowBounds(query.timeWindow, now) : null;
|
|
4590
5575
|
const hasTemporal = bounds !== null;
|
|
4591
5576
|
const hasSemantic = normalizedEmbedding.length > 0;
|
|
@@ -4618,7 +5603,7 @@ function normalizeLimit(value) {
|
|
|
4618
5603
|
function computeCandidateLimit(limit) {
|
|
4619
5604
|
return Math.min(Math.max(limit * CANDIDATE_MULTIPLIER, MIN_CANDIDATE_LIMIT), MAX_CANDIDATE_LIMIT);
|
|
4620
5605
|
}
|
|
4621
|
-
function
|
|
5606
|
+
function normalizeEmbedding3(embedding) {
|
|
4622
5607
|
if (!embedding || embedding.length === 0) {
|
|
4623
5608
|
return [];
|
|
4624
5609
|
}
|
|
@@ -4668,6 +5653,156 @@ function compareAscending2(left, right) {
|
|
|
4668
5653
|
return left.localeCompare(right);
|
|
4669
5654
|
}
|
|
4670
5655
|
|
|
5656
|
+
// src/app/procedures/recall/service.ts
|
|
5657
|
+
var DEFAULT_LIMIT2 = 5;
|
|
5658
|
+
var DEFAULT_CANONICAL_THRESHOLD = 0.55;
|
|
5659
|
+
var DEFAULT_CANONICAL_MARGIN = 0.08;
|
|
5660
|
+
var LEXICAL_CANDIDATE_MULTIPLIER = 3;
|
|
5661
|
+
var VECTOR_CANDIDATE_MULTIPLIER = 4;
|
|
5662
|
+
var VECTOR_ONLY_CANONICAL_FLOOR = 0.6;
|
|
5663
|
+
var LEXICAL_ONLY_NOTICE = "Semantic procedure search unavailable - using lexical-only procedure ranking.";
|
|
5664
|
+
var LEXICAL_ONLY_FALLBACK_NOTICE = "Semantic procedure search failed during procedure recall - using lexical-only procedure ranking.";
|
|
5665
|
+
async function runProcedureRecall(input, deps) {
|
|
5666
|
+
const text = input.text.trim();
|
|
5667
|
+
const limit = normalizeLimit2(input.limit);
|
|
5668
|
+
if (text.length === 0 || limit === 0) {
|
|
5669
|
+
return {
|
|
5670
|
+
candidates: [],
|
|
5671
|
+
notices: []
|
|
5672
|
+
};
|
|
5673
|
+
}
|
|
5674
|
+
const lexicalLimit = limit * LEXICAL_CANDIDATE_MULTIPLIER;
|
|
5675
|
+
const vectorLimit = limit * VECTOR_CANDIDATE_MULTIPLIER;
|
|
5676
|
+
const notices = [];
|
|
5677
|
+
const lexicalMatches = await deps.db.procedureFtsSearch({
|
|
5678
|
+
text,
|
|
5679
|
+
limit: lexicalLimit
|
|
5680
|
+
});
|
|
5681
|
+
const queryEmbedding = await maybeEmbedQuery(text, deps.embedQuery, notices);
|
|
5682
|
+
const vectorMatches = queryEmbedding.length > 0 ? await deps.db.procedureVectorSearch({
|
|
5683
|
+
embedding: queryEmbedding,
|
|
5684
|
+
limit: vectorLimit
|
|
5685
|
+
}).catch(() => {
|
|
5686
|
+
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5687
|
+
return [];
|
|
5688
|
+
}) : [];
|
|
5689
|
+
const ranked = rankProcedureCandidates(text, lexicalMatches, vectorMatches).slice(0, limit);
|
|
5690
|
+
const canonicalProcedure = selectCanonicalProcedure(ranked, input.threshold);
|
|
5691
|
+
return {
|
|
5692
|
+
...canonicalProcedure ? { canonicalProcedure } : {},
|
|
5693
|
+
candidates: ranked,
|
|
5694
|
+
notices: dedupePreservingOrder(notices)
|
|
5695
|
+
};
|
|
5696
|
+
}
|
|
5697
|
+
async function maybeEmbedQuery(text, embedQuery, notices) {
|
|
5698
|
+
if (!embedQuery) {
|
|
5699
|
+
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5700
|
+
return [];
|
|
5701
|
+
}
|
|
5702
|
+
try {
|
|
5703
|
+
const embedding = await embedQuery(text);
|
|
5704
|
+
if (embedding.length === 0) {
|
|
5705
|
+
notices.push(LEXICAL_ONLY_NOTICE);
|
|
5706
|
+
return [];
|
|
5707
|
+
}
|
|
5708
|
+
return embedding;
|
|
5709
|
+
} catch {
|
|
5710
|
+
notices.push(LEXICAL_ONLY_FALLBACK_NOTICE);
|
|
5711
|
+
return [];
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
function rankProcedureCandidates(query, lexicalMatches, vectorMatches) {
|
|
5715
|
+
const merged = /* @__PURE__ */ new Map();
|
|
5716
|
+
for (const match of lexicalMatches) {
|
|
5717
|
+
const lexical = computeProcedureLexicalScore(query, match.procedure);
|
|
5718
|
+
merged.set(match.procedure.id, {
|
|
5719
|
+
procedure: match.procedure,
|
|
5720
|
+
lexical,
|
|
5721
|
+
vector: 0
|
|
5722
|
+
});
|
|
5723
|
+
}
|
|
5724
|
+
for (const match of vectorMatches) {
|
|
5725
|
+
const existing = merged.get(match.procedure.id);
|
|
5726
|
+
merged.set(match.procedure.id, {
|
|
5727
|
+
procedure: match.procedure,
|
|
5728
|
+
lexical: existing?.lexical ?? computeProcedureLexicalScore(query, match.procedure),
|
|
5729
|
+
vector: Math.max(existing?.vector ?? 0, match.vectorSim)
|
|
5730
|
+
});
|
|
5731
|
+
}
|
|
5732
|
+
return Array.from(merged.values()).map((candidate) => {
|
|
5733
|
+
const relevance = combinedRelevance(candidate.vector, candidate.lexical);
|
|
5734
|
+
return {
|
|
5735
|
+
procedure: candidate.procedure,
|
|
5736
|
+
score: relevance,
|
|
5737
|
+
scores: {
|
|
5738
|
+
relevance,
|
|
5739
|
+
lexical: candidate.lexical,
|
|
5740
|
+
vector: candidate.vector
|
|
5741
|
+
}
|
|
5742
|
+
};
|
|
5743
|
+
}).filter((candidate) => candidate.score > 0).sort(compareProcedureCandidates);
|
|
5744
|
+
}
|
|
5745
|
+
function computeProcedureLexicalScore(query, procedure) {
|
|
5746
|
+
return computeLexicalScore(query, procedure.title, procedure.recall_text);
|
|
5747
|
+
}
|
|
5748
|
+
function selectCanonicalProcedure(ranked, threshold) {
|
|
5749
|
+
const leader = ranked[0];
|
|
5750
|
+
if (!leader) {
|
|
5751
|
+
return void 0;
|
|
5752
|
+
}
|
|
5753
|
+
const minimumScore = normalizeThreshold(threshold);
|
|
5754
|
+
if (leader.score < minimumScore) {
|
|
5755
|
+
return void 0;
|
|
5756
|
+
}
|
|
5757
|
+
if (leader.scores.lexical === 0 && leader.scores.vector < VECTOR_ONLY_CANONICAL_FLOOR) {
|
|
5758
|
+
return void 0;
|
|
5759
|
+
}
|
|
5760
|
+
const runnerUp = ranked[1];
|
|
5761
|
+
if (runnerUp && leader.score - runnerUp.score < DEFAULT_CANONICAL_MARGIN) {
|
|
5762
|
+
return void 0;
|
|
5763
|
+
}
|
|
5764
|
+
return leader.procedure;
|
|
5765
|
+
}
|
|
5766
|
+
function compareProcedureCandidates(left, right) {
|
|
5767
|
+
if (left.score !== right.score) {
|
|
5768
|
+
return right.score - left.score;
|
|
5769
|
+
}
|
|
5770
|
+
if (left.scores.lexical !== right.scores.lexical) {
|
|
5771
|
+
return right.scores.lexical - left.scores.lexical;
|
|
5772
|
+
}
|
|
5773
|
+
if (left.scores.vector !== right.scores.vector) {
|
|
5774
|
+
return right.scores.vector - left.scores.vector;
|
|
5775
|
+
}
|
|
5776
|
+
if (left.procedure.updated_at !== right.procedure.updated_at) {
|
|
5777
|
+
return right.procedure.updated_at.localeCompare(left.procedure.updated_at);
|
|
5778
|
+
}
|
|
5779
|
+
return left.procedure.procedure_key.localeCompare(right.procedure.procedure_key);
|
|
5780
|
+
}
|
|
5781
|
+
function normalizeLimit2(value) {
|
|
5782
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
5783
|
+
return DEFAULT_LIMIT2;
|
|
5784
|
+
}
|
|
5785
|
+
return Math.max(0, Math.trunc(value));
|
|
5786
|
+
}
|
|
5787
|
+
function normalizeThreshold(value) {
|
|
5788
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
5789
|
+
return DEFAULT_CANONICAL_THRESHOLD;
|
|
5790
|
+
}
|
|
5791
|
+
return Math.min(1, Math.max(0, value));
|
|
5792
|
+
}
|
|
5793
|
+
function dedupePreservingOrder(values) {
|
|
5794
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5795
|
+
const deduped = [];
|
|
5796
|
+
for (const value of values) {
|
|
5797
|
+
if (seen.has(value)) {
|
|
5798
|
+
continue;
|
|
5799
|
+
}
|
|
5800
|
+
seen.add(value);
|
|
5801
|
+
deduped.push(value);
|
|
5802
|
+
}
|
|
5803
|
+
return deduped;
|
|
5804
|
+
}
|
|
5805
|
+
|
|
4671
5806
|
// src/app/recall/transitions.ts
|
|
4672
5807
|
function buildClaimTransitionExplanations(params) {
|
|
4673
5808
|
if (params.families.length === 0 || params.detectedIntent === "temporal_narrative") {
|
|
@@ -4754,7 +5889,7 @@ function selectEpisodeContext(family, episodes) {
|
|
|
4754
5889
|
var EPISODE_FRESHNESS_NOTICE = "Episodes cover consolidated prior sessions only; the most recent completed session may not appear yet.";
|
|
4755
5890
|
var EPISODE_SEMANTIC_FALLBACK_NOTICE = "Semantic episode search unavailable - showing temporal results only.";
|
|
4756
5891
|
var EPISODE_SEMANTIC_UNAVAILABLE_NOTICE = "Semantic episode search unavailable - no semantic episode results could be returned.";
|
|
4757
|
-
var ENTRY_FILTER_NOTICE = "
|
|
5892
|
+
var ENTRY_FILTER_NOTICE = "Type and tag filters were applied to entries only.";
|
|
4758
5893
|
var HISTORICAL_STATE_PATTERNS = [
|
|
4759
5894
|
"what was the previous",
|
|
4760
5895
|
"what was the earlier",
|
|
@@ -4775,6 +5910,35 @@ var HISTORICAL_STATE_REGEX_PATTERNS = [
|
|
|
4775
5910
|
/\bwhat\b.*\bplan\b.*\bearlier\b/u,
|
|
4776
5911
|
/\bwhat\b.*\bplan\b.*\bbefore\b/u
|
|
4777
5912
|
];
|
|
5913
|
+
var PROCEDURAL_PATTERNS = [
|
|
5914
|
+
"how do i",
|
|
5915
|
+
"how should i",
|
|
5916
|
+
"how can i",
|
|
5917
|
+
"what steps",
|
|
5918
|
+
"which steps",
|
|
5919
|
+
"walk me through",
|
|
5920
|
+
"guide me through",
|
|
5921
|
+
"step by step",
|
|
5922
|
+
"step-by-step",
|
|
5923
|
+
"checklist for",
|
|
5924
|
+
"process for",
|
|
5925
|
+
"procedure for",
|
|
5926
|
+
"method for",
|
|
5927
|
+
"instructions for",
|
|
5928
|
+
"best way to",
|
|
5929
|
+
"recommended way to"
|
|
5930
|
+
];
|
|
5931
|
+
var PROCEDURAL_REGEX_PATTERNS = [
|
|
5932
|
+
/\bhow (?:do|should|can) (?:i|we)\b/u,
|
|
5933
|
+
/\bhow to\b/u,
|
|
5934
|
+
/\bwhat (?:are the )?steps\b/u,
|
|
5935
|
+
/\bwhich steps\b/u,
|
|
5936
|
+
/\bwalk me through\b/u,
|
|
5937
|
+
/\bguide me through\b/u,
|
|
5938
|
+
/\bstep(?: |-)?by(?: |-)?step\b/u,
|
|
5939
|
+
/\b(?:checklist|playbook|runbook|procedure|process|instructions?|workflow|method)\b.*\b(?:for|to)\b/u,
|
|
5940
|
+
/\bwhat(?:'s| is) the (?:best|recommended|right) way to\b/u
|
|
5941
|
+
];
|
|
4778
5942
|
async function runUnifiedRecall(input, deps) {
|
|
4779
5943
|
const now = deps.now ?? /* @__PURE__ */ new Date();
|
|
4780
5944
|
const requested = normalizeMode(input.mode);
|
|
@@ -4782,9 +5946,13 @@ async function runUnifiedRecall(input, deps) {
|
|
|
4782
5946
|
const hasEntryFilters = hasEntryScopedFilters(input);
|
|
4783
5947
|
const topicAnchor = hasTopicAnchor(input.text, hasEntryFilters);
|
|
4784
5948
|
const historicalStatePattern = detectHistoricalStatePattern(input.text);
|
|
5949
|
+
const proceduralPattern = detectProceduralPattern(input.text);
|
|
4785
5950
|
if (historicalStatePattern) {
|
|
4786
5951
|
deps.debugLog?.(`[agenr] unified recall matched historical-state pattern=${JSON.stringify(historicalStatePattern)} query=${JSON.stringify(input.text)}`);
|
|
4787
5952
|
}
|
|
5953
|
+
if (proceduralPattern) {
|
|
5954
|
+
deps.debugLog?.(`[agenr] unified recall matched procedural pattern=${JSON.stringify(proceduralPattern)} query=${JSON.stringify(input.text)}`);
|
|
5955
|
+
}
|
|
4788
5956
|
const routing = routeRecall({
|
|
4789
5957
|
requested,
|
|
4790
5958
|
text: input.text,
|
|
@@ -4811,6 +5979,20 @@ async function runUnifiedRecall(input, deps) {
|
|
|
4811
5979
|
if (routing.queried.includes("episodes") && hasEntryScopedFilters(input)) {
|
|
4812
5980
|
notices.push(ENTRY_FILTER_NOTICE);
|
|
4813
5981
|
}
|
|
5982
|
+
const procedureResults = routing.queried.includes("procedures") ? await runProcedureRecall(
|
|
5983
|
+
{
|
|
5984
|
+
text: input.text,
|
|
5985
|
+
...input.limit !== void 0 ? { limit: input.limit } : {},
|
|
5986
|
+
...input.threshold !== void 0 ? { threshold: input.threshold } : {}
|
|
5987
|
+
},
|
|
5988
|
+
{
|
|
5989
|
+
db: deps.procedures,
|
|
5990
|
+
...deps.embedQuery ? { embedQuery: deps.embedQuery } : {}
|
|
5991
|
+
}
|
|
5992
|
+
) : {
|
|
5993
|
+
candidates: [],
|
|
5994
|
+
notices: []
|
|
5995
|
+
};
|
|
4814
5996
|
const entries = await maybeRunEntryRecall({
|
|
4815
5997
|
input,
|
|
4816
5998
|
deps,
|
|
@@ -4846,13 +6028,16 @@ async function runUnifiedRecall(input, deps) {
|
|
|
4846
6028
|
}
|
|
4847
6029
|
} : {},
|
|
4848
6030
|
...input.asOf ? { asOf: input.asOf.trim() } : {},
|
|
6031
|
+
...procedureResults.canonicalProcedure ? { procedure: procedureResults.canonicalProcedure } : {},
|
|
6032
|
+
procedureCandidates: procedureResults.candidates,
|
|
6033
|
+
procedureNotices: dedupePreservingOrder2(procedureResults.notices),
|
|
4849
6034
|
episodes,
|
|
4850
6035
|
entries: rawEntries,
|
|
4851
6036
|
projectedEntries,
|
|
4852
6037
|
entryFamilies,
|
|
4853
6038
|
claimTransitions,
|
|
4854
|
-
notices:
|
|
4855
|
-
count: episodes.length + rawEntries.length
|
|
6039
|
+
notices: dedupePreservingOrder2(notices),
|
|
6040
|
+
count: procedureResults.candidates.length + episodes.length + rawEntries.length
|
|
4856
6041
|
};
|
|
4857
6042
|
}
|
|
4858
6043
|
function routeRecall(params) {
|
|
@@ -4860,6 +6045,7 @@ function routeRecall(params) {
|
|
|
4860
6045
|
const factual = /^(when did|when was|what decision|what preference|what(?:'s| is) the default|which version|what threshold)\b/.test(lower);
|
|
4861
6046
|
const narrative = /\b(what happened|what were we doing|what was going on|summarize|catch me up)\b/.test(lower);
|
|
4862
6047
|
const historicalState = detectHistoricalStatePattern(params.text) !== void 0;
|
|
6048
|
+
const procedural = detectProceduralPattern(params.text) !== void 0;
|
|
4863
6049
|
const topicAnchor = hasTopicAnchor(params.text, params.hasEntryFilters);
|
|
4864
6050
|
if (params.requested === "entries") {
|
|
4865
6051
|
return {
|
|
@@ -4877,20 +6063,60 @@ function routeRecall(params) {
|
|
|
4877
6063
|
reason: params.parsedTimeWindow ? "Explicit mode=episodes override with a resolved time window." : "Explicit mode=episodes override without a resolved time window."
|
|
4878
6064
|
};
|
|
4879
6065
|
}
|
|
6066
|
+
if (params.requested === "procedures") {
|
|
6067
|
+
return {
|
|
6068
|
+
requested: params.requested,
|
|
6069
|
+
detectedIntent: "procedural",
|
|
6070
|
+
queried: ["procedures"],
|
|
6071
|
+
reason: "Explicit mode=procedures override."
|
|
6072
|
+
};
|
|
6073
|
+
}
|
|
4880
6074
|
if (historicalState) {
|
|
4881
6075
|
return {
|
|
4882
6076
|
requested: params.requested,
|
|
4883
6077
|
detectedIntent: "historical_state",
|
|
4884
|
-
queried: ["entries", "episodes"],
|
|
4885
|
-
reason: params.parsedTimeWindow ? "The query asks about a previous state or transition and includes a supported time expression, so both entries and episodes were queried." : "The query asks about a previous state or transition, so both entries and episodes were queried."
|
|
6078
|
+
queried: procedural ? ["procedures", "entries", "episodes"] : ["entries", "episodes"],
|
|
6079
|
+
reason: params.parsedTimeWindow ? procedural ? "The query asks for steps around a previous state or transition and includes a supported time expression, so procedures, entries, and episodes were queried." : "The query asks about a previous state or transition and includes a supported time expression, so both entries and episodes were queried." : procedural ? "The query asks for steps around a previous state or transition, so procedures, entries, and episodes were queried." : "The query asks about a previous state or transition, so both entries and episodes were queried."
|
|
4886
6080
|
};
|
|
4887
6081
|
}
|
|
4888
6082
|
if (factual && params.parsedTimeWindow) {
|
|
4889
6083
|
return {
|
|
4890
6084
|
requested: params.requested,
|
|
4891
6085
|
detectedIntent: "mixed",
|
|
4892
|
-
queried: ["entries", "episodes"],
|
|
4893
|
-
reason: "The query combines a factual phrase with a supported time expression, so both entries and episodes were queried."
|
|
6086
|
+
queried: procedural ? ["procedures", "entries", "episodes"] : ["entries", "episodes"],
|
|
6087
|
+
reason: procedural ? "The query combines a procedural ask with factual and time-based signals, so procedures, entries, and episodes were queried." : "The query combines a factual phrase with a supported time expression, so both entries and episodes were queried."
|
|
6088
|
+
};
|
|
6089
|
+
}
|
|
6090
|
+
if (procedural && params.parsedTimeWindow && topicAnchor) {
|
|
6091
|
+
return {
|
|
6092
|
+
requested: params.requested,
|
|
6093
|
+
detectedIntent: "mixed",
|
|
6094
|
+
queried: ["procedures", "episodes", "entries"],
|
|
6095
|
+
reason: "The query asks for steps, includes a supported time expression, and names a topic anchor, so procedures were queried first with supporting episodes and entries."
|
|
6096
|
+
};
|
|
6097
|
+
}
|
|
6098
|
+
if (procedural && params.parsedTimeWindow) {
|
|
6099
|
+
return {
|
|
6100
|
+
requested: params.requested,
|
|
6101
|
+
detectedIntent: "mixed",
|
|
6102
|
+
queried: ["procedures", "episodes"],
|
|
6103
|
+
reason: "The query asks for steps and includes a supported time expression, so procedures were queried first with supporting episodes."
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
if (procedural && topicAnchor) {
|
|
6107
|
+
return {
|
|
6108
|
+
requested: params.requested,
|
|
6109
|
+
detectedIntent: "mixed",
|
|
6110
|
+
queried: ["procedures", "entries"],
|
|
6111
|
+
reason: "The query asks for steps and includes a topic anchor, so procedures were queried first with supporting entries."
|
|
6112
|
+
};
|
|
6113
|
+
}
|
|
6114
|
+
if (procedural) {
|
|
6115
|
+
return {
|
|
6116
|
+
requested: params.requested,
|
|
6117
|
+
detectedIntent: "procedural",
|
|
6118
|
+
queried: ["procedures"],
|
|
6119
|
+
reason: "The query asks how to do something or requests a step-by-step method, so procedure recall was used first."
|
|
4894
6120
|
};
|
|
4895
6121
|
}
|
|
4896
6122
|
if (factual) {
|
|
@@ -5027,11 +6253,20 @@ function detectHistoricalStatePattern(text) {
|
|
|
5027
6253
|
const regexPattern = HISTORICAL_STATE_REGEX_PATTERNS.find((pattern) => pattern.test(lower));
|
|
5028
6254
|
return regexPattern?.source;
|
|
5029
6255
|
}
|
|
6256
|
+
function detectProceduralPattern(text) {
|
|
6257
|
+
const lower = text.trim().toLowerCase();
|
|
6258
|
+
const explicitPattern = PROCEDURAL_PATTERNS.find((pattern) => lower.includes(pattern));
|
|
6259
|
+
if (explicitPattern) {
|
|
6260
|
+
return explicitPattern;
|
|
6261
|
+
}
|
|
6262
|
+
const regexPattern = PROCEDURAL_REGEX_PATTERNS.find((pattern) => pattern.test(lower));
|
|
6263
|
+
return regexPattern?.source;
|
|
6264
|
+
}
|
|
5030
6265
|
function normalizeMode(value) {
|
|
5031
|
-
return value === "entries" || value === "episodes" ? value : "auto";
|
|
6266
|
+
return value === "entries" || value === "episodes" || value === "procedures" ? value : "auto";
|
|
5032
6267
|
}
|
|
5033
6268
|
function hasEntryScopedFilters(input) {
|
|
5034
|
-
return Boolean(
|
|
6269
|
+
return Boolean(hasNonEmptyArray(input.types) || hasNonEmptyArray(input.tags));
|
|
5035
6270
|
}
|
|
5036
6271
|
function hasNonEmptyArray(value) {
|
|
5037
6272
|
return Array.isArray(value) && value.length > 0;
|
|
@@ -5051,7 +6286,7 @@ async function maybeEmbedEpisodeQuery(text, embedQuery) {
|
|
|
5051
6286
|
return void 0;
|
|
5052
6287
|
}
|
|
5053
6288
|
}
|
|
5054
|
-
function
|
|
6289
|
+
function dedupePreservingOrder2(values) {
|
|
5055
6290
|
const seen = /* @__PURE__ */ new Set();
|
|
5056
6291
|
const deduped = [];
|
|
5057
6292
|
for (const value of values) {
|
|
@@ -5101,6 +6336,8 @@ export {
|
|
|
5101
6336
|
readNumber,
|
|
5102
6337
|
readBoolean,
|
|
5103
6338
|
mapEntryRow,
|
|
6339
|
+
parseAndNormalizeProcedureYaml,
|
|
6340
|
+
normalizeProcedureDefinition,
|
|
5104
6341
|
validateTemporalValidityRange,
|
|
5105
6342
|
insertEntry,
|
|
5106
6343
|
getEntry,
|
|
@@ -5110,7 +6347,7 @@ export {
|
|
|
5110
6347
|
VECTOR_INDEX_NAME,
|
|
5111
6348
|
getLastBulkIngestAt,
|
|
5112
6349
|
createDatabase,
|
|
5113
|
-
isRecord,
|
|
6350
|
+
isRecord2 as isRecord,
|
|
5114
6351
|
pushIssue,
|
|
5115
6352
|
pushUnexpectedFields,
|
|
5116
6353
|
parseRequiredTrimmedString,
|