@lore-co/cli 0.1.16 → 0.1.18

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.
Files changed (43) hide show
  1. package/README.md +76 -14
  2. package/dist/ask.d.ts.map +1 -1
  3. package/dist/ask.js +3 -26
  4. package/dist/ask.js.map +1 -1
  5. package/dist/cli.d.ts +8 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +566 -39
  8. package/dist/cli.js.map +1 -1
  9. package/dist/context-fallback.d.ts +37 -0
  10. package/dist/context-fallback.d.ts.map +1 -0
  11. package/dist/context-fallback.js +259 -0
  12. package/dist/context-fallback.js.map +1 -0
  13. package/dist/generated-assets.d.ts +4 -4
  14. package/dist/generated-assets.d.ts.map +1 -1
  15. package/dist/generated-assets.js +4 -4
  16. package/dist/generated-assets.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/invocation-health-writer.d.ts +19 -0
  22. package/dist/invocation-health-writer.d.ts.map +1 -0
  23. package/dist/invocation-health-writer.js +131 -0
  24. package/dist/invocation-health-writer.js.map +1 -0
  25. package/dist/reliability-store.d.ts +283 -0
  26. package/dist/reliability-store.d.ts.map +1 -0
  27. package/dist/reliability-store.js +1913 -0
  28. package/dist/reliability-store.js.map +1 -0
  29. package/dist/runtime-version.d.ts +2 -0
  30. package/dist/runtime-version.d.ts.map +1 -0
  31. package/dist/runtime-version.js +5 -0
  32. package/dist/runtime-version.js.map +1 -0
  33. package/dist/runtime.d.ts.map +1 -1
  34. package/dist/runtime.js +1041 -152
  35. package/dist/runtime.js.map +1 -1
  36. package/dist/self-host.d.ts +1 -1
  37. package/dist/self-host.js +2 -2
  38. package/dist/signed-snapshot.d.ts +59 -0
  39. package/dist/signed-snapshot.d.ts.map +1 -0
  40. package/dist/signed-snapshot.js +303 -0
  41. package/dist/signed-snapshot.js.map +1 -0
  42. package/dist/update.js +3 -3
  43. package/package.json +19 -3
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { access, chmod, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
2
+ import { access, chmod, copyFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
3
3
  import { constants as fsConstants, realpathSync } from "node:fs";
4
- import { randomUUID } from "node:crypto";
4
+ import { createHash, randomUUID } from "node:crypto";
5
5
  import { delimiter, dirname, resolve } from "node:path";
6
6
  import { homedir, platform } from "node:os";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { NATIVE_CODING_AGENT_NAMES, WorkspaceIdentityResponseSchema, isNativeCodingAgent, } from "@lore-co/core";
8
+ import { CLAUDE_RELIABILITY_INTEGRATION, CODEX_RELIABILITY_INTEGRATION, CURSOR_RELIABILITY_INTEGRATION, NATIVE_CODING_AGENT_NAMES, OPENCODE_RELIABILITY_INTEGRATION, OperationalMetricsResponseSchema, POLYTOKEN_RELIABILITY_INTEGRATION, RELIABILITY_INTEGRATION_CONTRACT_VERSION, ReliabilityInvocationRecordSchema, WorkspaceIdentityResponseSchema, canonicalJson, deriveReliabilityInvocationHealth, isNativeCodingAgent, } from "@lore-co/core";
9
9
  import { runHook, } from "./runtime.js";
10
+ import { readInvocationHealthWriteInput, runInvocationHealthWrite, } from "./invocation-health-writer.js";
10
11
  import { runAskCommand } from "./ask.js";
11
12
  import { runGuardCommand } from "./guard.js";
12
13
  import { runContinueCommand, runResumeCommand } from "./handoff.js";
@@ -18,6 +19,7 @@ import { runHostCommand } from "./host.js";
18
19
  import { runDemoCommand } from "./demo.js";
19
20
  import { updateCommand } from "./update.js";
20
21
  import { runSelfHostCommand } from "./self-host.js";
22
+ import { ReliabilityStore, ReliabilityStoreError, integrationInvocationStateKey, } from "./reliability-store.js";
21
23
  import { IS_STANDALONE_BINARY, LORE_VERSION } from "./version.js";
22
24
  const LORE_OWNER_ARGUMENT = "--owner lore";
23
25
  export const LORE_OPENCODE_PLUGIN = "@lore-co/opencode";
@@ -42,6 +44,13 @@ const POLYTOKEN_HOOK_EVENTS = [
42
44
  "pre_user_prompt",
43
45
  "post_model_turn",
44
46
  ];
47
+ const AGENT_MANIFESTS = {
48
+ claude: CLAUDE_RELIABILITY_INTEGRATION,
49
+ codex: CODEX_RELIABILITY_INTEGRATION,
50
+ cursor: CURSOR_RELIABILITY_INTEGRATION,
51
+ opencode: OPENCODE_RELIABILITY_INTEGRATION,
52
+ polytoken: POLYTOKEN_RELIABILITY_INTEGRATION,
53
+ };
45
54
  const ROOT_HELP = `lore
46
55
  Connect local coding agents to Lore shared engineering memory.
47
56
 
@@ -113,7 +122,7 @@ Examples:
113
122
  lore connect --token "$LORE_WORKSPACE_TOKEN" --agent cursor
114
123
  `;
115
124
  const STATUS_HELP = `lore status
116
- Show whether Lore is configured and each native integration is installed.
125
+ Show exact installation, declared capabilities, and last invocation health.
117
126
 
118
127
  Usage:
119
128
  lore status [--json]
@@ -123,7 +132,7 @@ Examples:
123
132
  lore status --json
124
133
  `;
125
134
  const DOCTOR_HELP = `lore doctor
126
- Check local security, runtime, agent integrations, binaries, and Lore health.
135
+ Check local security, exact wiring, invocation health, binaries, and Lore health.
127
136
 
128
137
  Usage:
129
138
  lore doctor [--json]
@@ -133,8 +142,9 @@ Examples:
133
142
  lore doctor --json
134
143
  `;
135
144
  const DISCONNECT_HELP = `lore disconnect
136
- Remove only Lore-owned hooks/plugin, credentials, runtime, state, and retry queue.
137
- Unrelated agent settings, plugins, and Lore-created backups are retained.
145
+ Remove Lore-owned hooks/plugin, credentials, runtime, and transient state.
146
+ Durable captures, dead letters, verified snapshots, unrelated agent settings,
147
+ plugins, and Lore-created backups are retained.
138
148
 
139
149
  Usage:
140
150
  lore disconnect [--json]
@@ -163,6 +173,12 @@ export function getLorePaths(home, environment = process.env) {
163
173
  config: resolve(loreDirectory, "config.json"),
164
174
  runtime: resolve(loreDirectory, "bin", "lore-hook.mjs"),
165
175
  runtimeRepository: resolve(loreDirectory, "bin", "repository.js"),
176
+ runtimeReliabilityStore: resolve(loreDirectory, "bin", "reliability-store.js"),
177
+ runtimeSignedSnapshot: resolve(loreDirectory, "bin", "signed-snapshot.js"),
178
+ runtimeCanonicalJson: resolve(loreDirectory, "bin", "canonical-json.js"),
179
+ runtimeContextFallback: resolve(loreDirectory, "bin", "context-fallback.js"),
180
+ runtimeInvocationHealthWriter: resolve(loreDirectory, "bin", "invocation-health-writer.js"),
181
+ runtimeVersion: resolve(loreDirectory, "bin", "runtime-version.js"),
166
182
  runtimePackage: resolve(loreDirectory, "bin", "package.json"),
167
183
  state: resolve(loreDirectory, "state"),
168
184
  queue: resolve(loreDirectory, "queue"),
@@ -452,6 +468,120 @@ export function countLoreOpenCodePlugins(input) {
452
468
  ? input.plugin.filter(isLoreOpenCodePlugin).length
453
469
  : 0;
454
470
  }
471
+ function installationUnitId(event, index, total) {
472
+ return total === 1 ? event : `installation-${index + 1}/${event}`;
473
+ }
474
+ function installationUnitState(ownedCount, exactCount) {
475
+ if (ownedCount === 0) {
476
+ return "missing";
477
+ }
478
+ if (ownedCount > 1 || exactCount > 1) {
479
+ return "duplicate";
480
+ }
481
+ return exactCount === 1 ? "exact" : "drifted";
482
+ }
483
+ function exactConfiguration(left, right) {
484
+ return canonicalJson(left) === canonicalJson(right);
485
+ }
486
+ function commandHookInstallationUnits(input, agent, paths, index, total) {
487
+ const configuration = objectConfiguration(input, agent);
488
+ if (configuration.hooks !== undefined && !isObject(configuration.hooks)) {
489
+ throw new Error(`${agent} configuration field "hooks" must be an object`);
490
+ }
491
+ const hooks = isObject(configuration.hooks) ? configuration.hooks : {};
492
+ return HOOK_EVENTS.map((event) => {
493
+ const groups = hooks[event];
494
+ if (groups !== undefined && !Array.isArray(groups)) {
495
+ throw new Error(`${agent} hook event "${event}" must be an array`);
496
+ }
497
+ const candidates = Array.isArray(groups) ? groups : [];
498
+ const expected = {
499
+ ...(event === "PreToolUse" ? { matcher: GUARD_TOOL_MATCHER } : {}),
500
+ hooks: [eventHandler(agent, event, paths)],
501
+ };
502
+ const ownedCount = candidates.reduce((count, candidate) => {
503
+ if (!isObject(candidate) || !Array.isArray(candidate.hooks)) {
504
+ return count;
505
+ }
506
+ return count + candidate.hooks.filter(isLoreHook).length;
507
+ }, 0);
508
+ const exactCount = candidates.filter((candidate) => exactConfiguration(candidate, expected)).length;
509
+ return {
510
+ id: installationUnitId(event, index, total),
511
+ state: installationUnitState(ownedCount, exactCount),
512
+ };
513
+ });
514
+ }
515
+ function cursorInstallationUnits(input, paths, index, total) {
516
+ const configuration = objectConfiguration(input, "cursor");
517
+ if (configuration.version !== undefined && configuration.version !== 1) {
518
+ throw new Error('Cursor configuration field "version" must be 1');
519
+ }
520
+ if (configuration.hooks !== undefined && !isObject(configuration.hooks)) {
521
+ throw new Error('Cursor configuration field "hooks" must be an object');
522
+ }
523
+ const hooks = isObject(configuration.hooks) ? configuration.hooks : {};
524
+ return CURSOR_HOOK_EVENTS.map((event) => {
525
+ const values = hooks[event];
526
+ if (values !== undefined && !Array.isArray(values)) {
527
+ throw new Error(`Cursor hook event "${event}" must be an array`);
528
+ }
529
+ const candidates = Array.isArray(values) ? values : [];
530
+ const expected = cursorEventHandler(event, paths);
531
+ const ownedCount = candidates.filter(isLoreHook).length;
532
+ const exactCount = candidates.filter((candidate) => exactConfiguration(candidate, expected)).length;
533
+ return {
534
+ id: installationUnitId(event, index, total),
535
+ state: installationUnitState(ownedCount, exactCount),
536
+ };
537
+ });
538
+ }
539
+ function polytokenInstallationUnits(input, paths, index, total) {
540
+ if (!Array.isArray(input)) {
541
+ throw new Error("Polytoken hooks must contain a JSON array");
542
+ }
543
+ return POLYTOKEN_HOOK_EVENTS.map((event) => {
544
+ const expected = polytokenEventHandler(event, paths);
545
+ const owned = input.filter((candidate) => isLorePolytokenHook(candidate) &&
546
+ (!isObject(candidate) || candidate.event === event));
547
+ const exactCount = owned.filter((candidate) => exactConfiguration(candidate, expected)).length;
548
+ return {
549
+ id: installationUnitId(event, index, total),
550
+ state: installationUnitState(owned.length, exactCount),
551
+ };
552
+ });
553
+ }
554
+ function openCodeInstallationUnits(input, index, total) {
555
+ const configuration = objectConfiguration(input, "opencode");
556
+ if (configuration.plugin !== undefined &&
557
+ !Array.isArray(configuration.plugin)) {
558
+ throw new Error('OpenCode configuration field "plugin" must be an array');
559
+ }
560
+ const plugins = Array.isArray(configuration.plugin)
561
+ ? configuration.plugin
562
+ : [];
563
+ const owned = plugins.filter(isLoreOpenCodePlugin);
564
+ const exactCount = owned.filter((candidate) => candidate === LORE_OPENCODE_PLUGIN).length;
565
+ return [
566
+ {
567
+ id: installationUnitId("plugin", index, total),
568
+ state: installationUnitState(owned.length, exactCount),
569
+ },
570
+ ];
571
+ }
572
+ function inspectInstallationUnits(input, agent, paths, index, total) {
573
+ switch (agent) {
574
+ case "claude":
575
+ case "codex":
576
+ return commandHookInstallationUnits(input, agent, paths, index, total);
577
+ case "cursor":
578
+ return cursorInstallationUnits(input, paths, index, total);
579
+ case "polytoken":
580
+ return polytokenInstallationUnits(input, paths, index, total);
581
+ case "opencode":
582
+ return openCodeInstallationUnits(input, index, total);
583
+ }
584
+ }
455
585
  async function readJsonDocument(path) {
456
586
  try {
457
587
  const [raw, metadata] = await Promise.all([
@@ -511,7 +641,7 @@ async function writeMergedJson(path, document, value, now) {
511
641
  }
512
642
  function parseConnectorConfig(value) {
513
643
  if (!isObject(value) ||
514
- value.version !== 1 ||
644
+ (value.version !== 1 && value.version !== 2) ||
515
645
  typeof value.apiUrl !== "string" ||
516
646
  typeof value.token !== "string" ||
517
647
  !Array.isArray(value.agents) ||
@@ -547,10 +677,15 @@ function parseConnectorConfig(value) {
547
677
  const dashboardUrl = typeof value.dashboardUrl === "string"
548
678
  ? normalizeApiUrl(value.dashboardUrl)
549
679
  : undefined;
680
+ const parsedWorkspaceId = WorkspaceIdentityResponseSchema.shape.workspaceId.safeParse(value.workspaceId);
681
+ const workspaceId = parsedWorkspaceId.success
682
+ ? parsedWorkspaceId.data
683
+ : undefined;
550
684
  return {
551
- version: 1,
685
+ version: 2,
552
686
  apiUrl: value.apiUrl,
553
687
  ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
688
+ ...(workspaceId === undefined ? {} : { workspaceId }),
554
689
  token: value.token,
555
690
  agents,
556
691
  ...(agentConfigPaths === undefined ||
@@ -979,20 +1114,65 @@ async function installRuntime(paths) {
979
1114
  await Promise.all([
980
1115
  rm(paths.runtime, { force: true }),
981
1116
  rm(paths.runtimeRepository, { force: true }),
1117
+ rm(paths.runtimeReliabilityStore, { force: true }),
1118
+ rm(paths.runtimeSignedSnapshot, { force: true }),
1119
+ rm(paths.runtimeCanonicalJson, { force: true }),
1120
+ rm(paths.runtimeContextFallback, { force: true }),
1121
+ rm(paths.runtimeInvocationHealthWriter, { force: true }),
1122
+ rm(paths.runtimeVersion, { force: true }),
982
1123
  rm(paths.runtimePackage, { force: true }),
983
1124
  ]);
984
1125
  return;
985
1126
  }
986
1127
  const sourceDirectory = dirname(fileURLToPath(import.meta.url));
987
- const [runtime, repository] = await Promise.all([
1128
+ const canonicalJsonModule = fileURLToPath(import.meta.resolve("@lore-co/core/canonical-json"));
1129
+ const [runtime, repository, reliabilityStore, signedSnapshot, canonicalJson, contextFallback, invocationHealthWriter,] = await Promise.all([
988
1130
  readFile(resolve(sourceDirectory, "runtime.js"), "utf8"),
989
1131
  readFile(resolve(sourceDirectory, "repository.js"), "utf8"),
1132
+ readFile(resolve(sourceDirectory, "reliability-store.js"), "utf8"),
1133
+ readFile(resolve(sourceDirectory, "signed-snapshot.js"), "utf8"),
1134
+ readFile(canonicalJsonModule, "utf8"),
1135
+ readFile(resolve(sourceDirectory, "context-fallback.js"), "utf8"),
1136
+ readFile(resolve(sourceDirectory, "invocation-health-writer.js"), "utf8"),
990
1137
  ]);
1138
+ const standaloneSignedSnapshot = signedSnapshot.replaceAll("@lore-co/core/canonical-json", "./canonical-json.js");
1139
+ if (standaloneSignedSnapshot === signedSnapshot) {
1140
+ throw new Error("Signed snapshot runtime dependency could not be localized");
1141
+ }
1142
+ const runtimeVersion = `export const RUNTIME_VERSION = ${JSON.stringify(LORE_VERSION)};\n`;
1143
+ const runtimeFiles = {
1144
+ "lore-hook.mjs": runtime,
1145
+ "repository.js": repository,
1146
+ "reliability-store.js": reliabilityStore,
1147
+ "signed-snapshot.js": standaloneSignedSnapshot,
1148
+ "canonical-json.js": canonicalJson,
1149
+ "context-fallback.js": contextFallback,
1150
+ "invocation-health-writer.js": invocationHealthWriter,
1151
+ "runtime-version.js": runtimeVersion,
1152
+ };
1153
+ const runtimeManifest = {
1154
+ type: "module",
1155
+ loreRuntime: {
1156
+ version: LORE_VERSION,
1157
+ files: Object.fromEntries(Object.entries(runtimeFiles).map(([name, contents]) => [
1158
+ name,
1159
+ createHash("sha256").update(contents).digest("hex"),
1160
+ ])),
1161
+ },
1162
+ };
991
1163
  await Promise.all([
992
- atomicWrite(paths.runtime, runtime, 0o700),
993
1164
  atomicWrite(paths.runtimeRepository, repository, 0o600),
994
- atomicWrite(paths.runtimePackage, '{"type":"module"}\n', 0o600),
1165
+ atomicWrite(paths.runtimeReliabilityStore, reliabilityStore, 0o600),
1166
+ atomicWrite(paths.runtimeSignedSnapshot, standaloneSignedSnapshot, 0o600),
1167
+ atomicWrite(paths.runtimeCanonicalJson, canonicalJson, 0o600),
1168
+ atomicWrite(paths.runtimeContextFallback, contextFallback, 0o600),
1169
+ atomicWrite(paths.runtimeInvocationHealthWriter, invocationHealthWriter, 0o600),
1170
+ atomicWrite(paths.runtimeVersion, runtimeVersion, 0o600),
1171
+ atomicWrite(paths.runtimePackage, `${JSON.stringify(runtimeManifest, null, 2)}\n`, 0o600),
995
1172
  ]);
1173
+ // The hook entrypoint is the generation activation marker. Updating it last
1174
+ // prevents a new runtime from loading before all of its local modules exist.
1175
+ await atomicWrite(paths.runtime, runtime, 0o700);
996
1176
  }
997
1177
  function agentConfigPath(agent, paths) {
998
1178
  return targetFor(agent).configPath(paths);
@@ -1165,16 +1345,30 @@ async function connectCommand(args) {
1165
1345
  .map((installation) => installation.configPath);
1166
1346
  }
1167
1347
  const config = {
1168
- version: 1,
1348
+ version: 2,
1169
1349
  apiUrl,
1170
1350
  ...(dashboardUrl === undefined ? {} : { dashboardUrl }),
1351
+ workspaceId: identity.workspaceId,
1171
1352
  token: token.trim(),
1172
1353
  agents,
1173
1354
  agentConfigPaths,
1174
1355
  connectedAt: existing?.connectedAt ?? now.toISOString(),
1175
1356
  timeoutMs,
1176
1357
  };
1358
+ const reliabilityStore = new ReliabilityStore(identity.workspaceId, {
1359
+ home: paths.home,
1360
+ });
1361
+ await reliabilityStore.initialize();
1362
+ await reliabilityStore.migrateLegacyQueue(paths.queue);
1177
1363
  await atomicWrite(paths.config, `${JSON.stringify(config, null, 2)}\n`, 0o600);
1364
+ if (existing !== null &&
1365
+ existing.workspaceId === undefined &&
1366
+ existing.apiUrl === apiUrl &&
1367
+ existing.token === token.trim()) {
1368
+ const credentialStore = new ReliabilityStore(credentialReliabilityWorkspaceKey(apiUrl, token.trim()), { home: paths.home });
1369
+ await credentialStore.transferPendingTo(reliabilityStore);
1370
+ }
1371
+ await reliabilityStore.releaseAuthBlocked(now);
1178
1372
  const result = {
1179
1373
  connected: true,
1180
1374
  apiUrl: config.apiUrl,
@@ -1195,34 +1389,250 @@ async function queueCount(paths) {
1195
1389
  return 0;
1196
1390
  }
1197
1391
  }
1198
- async function getAgentStatus(agent, config, paths) {
1392
+ function reliabilityWorkspaceKey(config) {
1393
+ return (config.workspaceId ??
1394
+ credentialReliabilityWorkspaceKey(config.apiUrl, config.token));
1395
+ }
1396
+ function credentialReliabilityWorkspaceKey(apiUrl, token) {
1397
+ return `credential-${createHash("sha256")
1398
+ .update(`${apiUrl}\0${token}`)
1399
+ .digest("hex")
1400
+ .slice(0, 32)}`;
1401
+ }
1402
+ async function inspectReliabilityStore(config, paths) {
1403
+ if (config === null) {
1404
+ return { inspection: null, error: null };
1405
+ }
1406
+ try {
1407
+ const store = new ReliabilityStore(reliabilityWorkspaceKey(config), {
1408
+ home: paths.home,
1409
+ });
1410
+ return { inspection: await store.inspectExisting(), error: null };
1411
+ }
1412
+ catch (error) {
1413
+ return {
1414
+ inspection: null,
1415
+ error: error instanceof Error
1416
+ ? error.message
1417
+ : "Local reliability store is unavailable",
1418
+ };
1419
+ }
1420
+ }
1421
+ const MAX_RUNTIME_MODULE_BYTES = 2 * 1024 * 1024;
1422
+ const MAX_RUNTIME_MANIFEST_BYTES = 64 * 1024;
1423
+ async function readRuntimeFile(path, maxBytes, executable = false) {
1424
+ const handle = await open(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
1425
+ try {
1426
+ const metadata = await handle.stat();
1427
+ if (!metadata.isFile() ||
1428
+ metadata.size > maxBytes ||
1429
+ (executable && (metadata.mode & 0o111) === 0)) {
1430
+ throw new Error("Runtime file is unsafe or exceeds its size limit");
1431
+ }
1432
+ const buffer = Buffer.allocUnsafe(maxBytes + 1);
1433
+ let bytes = 0;
1434
+ while (bytes < buffer.length) {
1435
+ const result = await handle.read(buffer, bytes, buffer.length - bytes, null);
1436
+ if (result.bytesRead === 0) {
1437
+ break;
1438
+ }
1439
+ bytes += result.bytesRead;
1440
+ }
1441
+ if (bytes > maxBytes) {
1442
+ throw new Error("Runtime file exceeds its size limit");
1443
+ }
1444
+ return buffer.subarray(0, bytes).toString("utf8");
1445
+ }
1446
+ finally {
1447
+ await handle.close();
1448
+ }
1449
+ }
1450
+ async function inspectRuntimeInstallation(required, paths) {
1451
+ if (!required) {
1452
+ return { state: "not_required", version: null };
1453
+ }
1454
+ if (IS_STANDALONE_BINARY) {
1455
+ try {
1456
+ await access(process.execPath, fsConstants.R_OK | fsConstants.X_OK);
1457
+ return { state: "installed", version: LORE_VERSION };
1458
+ }
1459
+ catch {
1460
+ return { state: "missing", version: null };
1461
+ }
1462
+ }
1463
+ const runtimeFiles = {
1464
+ "lore-hook.mjs": paths.runtime,
1465
+ "repository.js": paths.runtimeRepository,
1466
+ "reliability-store.js": paths.runtimeReliabilityStore,
1467
+ "signed-snapshot.js": paths.runtimeSignedSnapshot,
1468
+ "canonical-json.js": paths.runtimeCanonicalJson,
1469
+ "context-fallback.js": paths.runtimeContextFallback,
1470
+ "invocation-health-writer.js": paths.runtimeInvocationHealthWriter,
1471
+ "runtime-version.js": paths.runtimeVersion,
1472
+ };
1473
+ let contents;
1474
+ let runtimePackage;
1475
+ try {
1476
+ [contents, runtimePackage] = await Promise.all([
1477
+ Promise.all(Object.entries(runtimeFiles).map(async ([name, path]) => {
1478
+ return [
1479
+ name,
1480
+ await readRuntimeFile(path, MAX_RUNTIME_MODULE_BYTES, name === "lore-hook.mjs"),
1481
+ ];
1482
+ })),
1483
+ readRuntimeFile(paths.runtimePackage, MAX_RUNTIME_MANIFEST_BYTES),
1484
+ ]);
1485
+ }
1486
+ catch (error) {
1487
+ const missing = typeof error === "object" &&
1488
+ error !== null &&
1489
+ "code" in error &&
1490
+ error.code === "ENOENT";
1491
+ return { state: missing ? "missing" : "stale", version: null };
1492
+ }
1493
+ let parsed;
1494
+ try {
1495
+ parsed = JSON.parse(runtimePackage);
1496
+ }
1497
+ catch {
1498
+ return { state: "stale", version: null };
1499
+ }
1500
+ const loreRuntime = isObject(parsed) && isObject(parsed.loreRuntime)
1501
+ ? parsed.loreRuntime
1502
+ : null;
1503
+ const hashes = loreRuntime !== null && isObject(loreRuntime.files)
1504
+ ? loreRuntime.files
1505
+ : null;
1506
+ const version = loreRuntime !== null && typeof loreRuntime.version === "string"
1507
+ ? loreRuntime.version
1508
+ : null;
1509
+ const hashEntries = hashes === null ? [] : Object.keys(hashes).sort();
1510
+ const expectedEntries = Object.keys(runtimeFiles).sort();
1511
+ const hashesValid = hashes !== null &&
1512
+ canonicalJson(hashEntries) === canonicalJson(expectedEntries) &&
1513
+ contents.every(([name, content]) => hashes[name] ===
1514
+ createHash("sha256").update(content).digest("hex"));
1515
+ return version === LORE_VERSION && hashesValid
1516
+ ? { state: "installed", version }
1517
+ : { state: "stale", version };
1518
+ }
1519
+ async function invocationHealth(config, paths, manifest) {
1520
+ if (config === null) {
1521
+ return {
1522
+ health: deriveReliabilityInvocationHealth(null, manifest.integrationId),
1523
+ error: null,
1524
+ };
1525
+ }
1526
+ try {
1527
+ const store = new ReliabilityStore(reliabilityWorkspaceKey(config), {
1528
+ home: paths.home,
1529
+ });
1530
+ const record = await store.readStateExisting(integrationInvocationStateKey(manifest.integrationId));
1531
+ const parsed = ReliabilityInvocationRecordSchema.safeParse(record);
1532
+ if (record !== null &&
1533
+ (!parsed.success ||
1534
+ parsed.data.integrationId !== manifest.integrationId)) {
1535
+ return {
1536
+ health: deriveReliabilityInvocationHealth(undefined, manifest.integrationId),
1537
+ error: "corrupt_record",
1538
+ };
1539
+ }
1540
+ const health = deriveReliabilityInvocationHealth(record, manifest.integrationId);
1541
+ return {
1542
+ health,
1543
+ error: manifest.integrationId === "plugin/opencode" &&
1544
+ health.runtimeVersion !== null &&
1545
+ health.runtimeVersion !== LORE_VERSION
1546
+ ? "runtime_version_mismatch"
1547
+ : null,
1548
+ };
1549
+ }
1550
+ catch (error) {
1551
+ return {
1552
+ health: deriveReliabilityInvocationHealth(undefined, manifest.integrationId),
1553
+ error: error instanceof ReliabilityStoreError
1554
+ ? error.code.toLowerCase()
1555
+ : "unavailable",
1556
+ };
1557
+ }
1558
+ }
1559
+ async function getAgentStatus(agent, config, paths, runtime) {
1199
1560
  const target = targetFor(agent);
1561
+ const manifest = AGENT_MANIFESTS[agent];
1200
1562
  const configuredPaths = config?.agentConfigPaths?.[agent];
1201
1563
  const configPaths = config?.agents.includes(agent) === true && configuredPaths !== undefined
1202
1564
  ? configuredPaths
1203
1565
  : [agentConfigPath(agent, paths)];
1204
1566
  let installed = 0;
1205
1567
  let existing = 0;
1206
- for (const path of configPaths) {
1568
+ let invalidConfiguration = false;
1569
+ const units = [];
1570
+ for (const [index, path] of configPaths.entries()) {
1207
1571
  try {
1208
1572
  const document = await readJsonDocument(path);
1209
1573
  installed += countAgentIntegrations(document.value, agent);
1210
1574
  existing += document.exists ? 1 : 0;
1575
+ units.push(...inspectInstallationUnits(document.value === undefined
1576
+ ? target.emptyValue()
1577
+ : document.value, agent, paths, index, configPaths.length));
1211
1578
  }
1212
1579
  catch {
1213
- // A malformed target config is reported as missing integration state.
1580
+ invalidConfiguration = true;
1214
1581
  }
1215
1582
  }
1583
+ const invocation = await invocationHealth(config, paths, manifest);
1584
+ const stalePluginRuntime = agent === "opencode" &&
1585
+ invocation.error === "runtime_version_mismatch";
1586
+ const agentRuntime = target.runtimeRequired
1587
+ ? runtime
1588
+ : stalePluginRuntime
1589
+ ? {
1590
+ state: "stale",
1591
+ version: invocation.health.runtimeVersion,
1592
+ }
1593
+ : {
1594
+ state: "not_required",
1595
+ version: null,
1596
+ };
1597
+ const expected = target.expected * configPaths.length;
1598
+ const exactUnits = units.filter((unit) => unit.state === "exact").length;
1599
+ const installationState = invalidConfiguration
1600
+ ? "invalid"
1601
+ : installed === 0
1602
+ ? "missing"
1603
+ : agentRuntime.state === "stale"
1604
+ ? "stale"
1605
+ : exactUnits === expected &&
1606
+ agentRuntime.state !== "missing"
1607
+ ? "complete"
1608
+ : "partial";
1609
+ const executable = await target.executable(paths);
1216
1610
  return {
1217
1611
  agent,
1612
+ integrationId: manifest.integrationId,
1218
1613
  configured: config?.agents.includes(agent) ?? false,
1219
- executable: await target.executable(paths),
1614
+ executable,
1220
1615
  configExists: existing === configPaths.length,
1221
1616
  configFile: configPaths[0] ?? agentConfigPath(agent, paths),
1222
1617
  ...(configPaths.length > 1 ? { configFiles: configPaths } : {}),
1223
1618
  integration: target.integration,
1224
1619
  installed,
1225
- expected: target.expected * configPaths.length,
1620
+ expected,
1621
+ capabilities: manifest.capabilities,
1622
+ installation: {
1623
+ state: installationState,
1624
+ configured: config?.agents.includes(agent) ?? false,
1625
+ executable,
1626
+ installedUnits: exactUnits,
1627
+ expectedUnits: expected,
1628
+ units,
1629
+ runtime: agentRuntime.state,
1630
+ runtimeVersion: agentRuntime.version,
1631
+ expectedRuntimeVersion: target.runtimeRequired || stalePluginRuntime ? LORE_VERSION : null,
1632
+ issue: invalidConfiguration ? "invalid_configuration" : null,
1633
+ },
1634
+ invocation: invocation.health,
1635
+ invocationError: invocation.error,
1226
1636
  };
1227
1637
  }
1228
1638
  async function statusData(paths) {
@@ -1235,28 +1645,25 @@ async function statusData(paths) {
1235
1645
  // Missing configuration is represented as disconnected.
1236
1646
  }
1237
1647
  const runtimeRequired = config?.agents.some((agent) => targetFor(agent).runtimeRequired) ?? false;
1238
- const runtimeInstalledCheck = runtimeRequired
1239
- ? Promise.all(IS_STANDALONE_BINARY
1240
- ? [access(process.execPath, fsConstants.R_OK | fsConstants.X_OK)]
1241
- : [
1242
- access(paths.runtime, fsConstants.R_OK | fsConstants.X_OK),
1243
- access(paths.runtimeRepository, fsConstants.R_OK),
1244
- access(paths.runtimePackage, fsConstants.R_OK),
1245
- ]).then(() => true, () => false)
1246
- : Promise.resolve(false);
1247
- const [runtimeInstalled, queuedTurns, agents] = await Promise.all([
1248
- runtimeInstalledCheck,
1648
+ const runtime = await inspectRuntimeInstallation(runtimeRequired, paths);
1649
+ const [legacyQueuedTurns, reliability, agents] = await Promise.all([
1249
1650
  queueCount(paths),
1250
- Promise.all(CONFIGURED_AGENT_NAMES.map(async (agent) => getAgentStatus(agent, config, paths))),
1651
+ inspectReliabilityStore(config, paths),
1652
+ Promise.all(CONFIGURED_AGENT_NAMES.map(async (agent) => getAgentStatus(agent, config, paths, runtime))),
1251
1653
  ]);
1252
1654
  return {
1655
+ schemaVersion: "lore-cli-status-v1",
1656
+ integrationContractVersion: RELIABILITY_INTEGRATION_CONTRACT_VERSION,
1253
1657
  connected: config !== null,
1254
1658
  apiUrl: config?.apiUrl ?? null,
1255
1659
  config: paths.config,
1256
1660
  configMode,
1257
1661
  runtimeRequired,
1258
- runtimeInstalled,
1259
- queuedTurns,
1662
+ runtimeInstalled: runtime.state === "installed",
1663
+ runtimeVersion: runtime.version,
1664
+ queuedTurns: legacyQueuedTurns + (reliability.inspection?.outbox.total ?? 0),
1665
+ reliability: reliability.inspection,
1666
+ reliabilityError: reliability.error,
1260
1667
  agents,
1261
1668
  };
1262
1669
  }
@@ -1267,9 +1674,12 @@ async function statusCommand(args) {
1267
1674
  }
1268
1675
  const data = await statusData(getLorePaths());
1269
1676
  const agentLines = data.agents
1270
- .map((agent) => `${agent.agent}: ${agent.configured ? "configured" : "not configured"}, ${agent.integration} ${agent.installed}/${agent.expected}, executable ${agent.executable ? "yes" : "no"}, config ${agent.configExists ? "yes" : "no"}`)
1677
+ .map((agent) => `${agent.agent}: ${agent.configured ? "configured" : "not configured"}, installation ${agent.installation.state}, invocation ${agent.invocation.state}${agent.invocationError === null ? "" : ` (${agent.invocationError})`}, capture ${agent.capabilities.captureDurability}, guard ${agent.capabilities.guardBoundaryCoverage}/${agent.capabilities.enforcement}`)
1271
1678
  .join("\n");
1272
- writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? "installed" : "missing") : "not required"}\nqueued_turns: ${data.queuedTurns}\n${agentLines}\n`);
1679
+ const metricRate = (value) => value === null || value === undefined
1680
+ ? "-"
1681
+ : `${(value * 100).toFixed(1)}%`;
1682
+ writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? `installed (${data.runtimeVersion})` : data.runtimeVersion === null ? "missing" : `stale (${data.runtimeVersion})`) : "not required"}\ndurable_outbox: ${data.reliability?.outbox.total ?? 0} ready=${data.reliability?.outbox.ready ?? 0} auth_blocked=${data.reliability?.outbox.authBlocked ?? 0} dead=${data.reliability?.outbox.dead ?? 0}\noldest_capture: ${data.reliability?.outbox.oldestEnqueuedAt ?? "-"}\ncontext_snapshots: ${data.reliability?.cache.context ?? 0}\noldest_context_snapshot: ${data.reliability?.cache.oldestContextWrittenAt ?? "-"}\npolicy_snapshots: ${data.reliability?.cache.policy ?? 0}\nlocal_context_fallback_rate: ${metricRate(data.reliability?.operational.retrieval.fallbackRate)}\nlocal_guard_coverage: ${metricRate(data.reliability?.operational.guard.coverageRate)}\nlocal_guard_failures: ${data.reliability?.operational.guard.failures ?? 0}\nlocal_retrieval_failures: ${data.reliability?.operational.retrieval.failed ?? 0}\n${data.reliabilityError === null ? "" : `reliability_error: ${data.reliabilityError}\n`}${agentLines}\n`);
1273
1683
  }
1274
1684
  async function disconnectCommand(args) {
1275
1685
  const parsed = parseOutputArguments(args, DISCONNECT_HELP, "disconnect");
@@ -1303,6 +1713,12 @@ async function disconnectCommand(args) {
1303
1713
  rm(paths.config, { force: true }),
1304
1714
  rm(paths.runtime, { force: true }),
1305
1715
  rm(paths.runtimeRepository, { force: true }),
1716
+ rm(paths.runtimeReliabilityStore, { force: true }),
1717
+ rm(paths.runtimeSignedSnapshot, { force: true }),
1718
+ rm(paths.runtimeCanonicalJson, { force: true }),
1719
+ rm(paths.runtimeContextFallback, { force: true }),
1720
+ rm(paths.runtimeInvocationHealthWriter, { force: true }),
1721
+ rm(paths.runtimeVersion, { force: true }),
1306
1722
  rm(paths.runtimePackage, { force: true }),
1307
1723
  rm(paths.state, { recursive: true, force: true }),
1308
1724
  rm(paths.queue, { recursive: true, force: true }),
@@ -1363,6 +1779,46 @@ async function apiChecks(config) {
1363
1779
  : detail,
1364
1780
  });
1365
1781
  }
1782
+ try {
1783
+ const response = await fetch(`${config.apiUrl}/v1/reliability/metrics`, {
1784
+ headers: {
1785
+ authorization: `Bearer ${config.token}`,
1786
+ "user-agent": `lore-cli/${LORE_VERSION}`,
1787
+ },
1788
+ signal: AbortSignal.timeout(3_000),
1789
+ });
1790
+ if (!response.ok) {
1791
+ throw new Error("Operational metrics request failed");
1792
+ }
1793
+ const metrics = OperationalMetricsResponseSchema.parse(await response.json());
1794
+ const captureBacklog = metrics.captureReplay.captureExtraction.pending +
1795
+ metrics.captureReplay.captureExtraction.running;
1796
+ const embeddingBacklog = metrics.captureReplay.memoryEmbedding.pending +
1797
+ metrics.captureReplay.memoryEmbedding.running;
1798
+ const deadLetters = metrics.captureReplay.captureExtraction.dead +
1799
+ metrics.captureReplay.memoryEmbedding.dead;
1800
+ const detectedFailures = metrics.silentFailures.count +
1801
+ metrics.guard.failures +
1802
+ deadLetters;
1803
+ checks.push({
1804
+ name: "server-operational-metrics",
1805
+ status: metrics.silentFailures.count > 0 || deadLetters > 0
1806
+ ? "error"
1807
+ : metrics.guard.failures > 0 ||
1808
+ captureBacklog > 0 ||
1809
+ embeddingBacklog > 0
1810
+ ? "warning"
1811
+ : "ok",
1812
+ detail: `${detectedFailures} detected failure(s), ${captureBacklog} capture and ${embeddingBacklog} embedding job(s) in backlog; ${metrics.guard.latency.samples} Guard latency sample(s)`,
1813
+ });
1814
+ }
1815
+ catch {
1816
+ checks.push({
1817
+ name: "server-operational-metrics",
1818
+ status: "warning",
1819
+ detail: "authenticated operational metrics are unavailable",
1820
+ });
1821
+ }
1366
1822
  return checks;
1367
1823
  }
1368
1824
  async function doctorCommand(args) {
@@ -1415,14 +1871,80 @@ async function doctorCommand(args) {
1415
1871
  });
1416
1872
  checks.push({
1417
1873
  name: `${agent.agent}-${agent.integration}`,
1418
- status: agent.installed === agent.expected ? "ok" : "error",
1419
- detail: `${agent.installed}/${agent.expected} Lore ${agent.integration} in ${agent.configFile}`,
1874
+ status: agent.installation.state === "complete" ? "ok" : "error",
1875
+ detail: `${agent.installation.state}: ${agent.installation.installedUnits}/${agent.installation.expectedUnits} exact Lore ${agent.integration} units`,
1876
+ });
1877
+ checks.push({
1878
+ name: `${agent.agent}-invocation`,
1879
+ status: agent.invocationError !== null
1880
+ ? "error"
1881
+ : agent.invocation.state === "successful"
1882
+ ? "ok"
1883
+ : agent.invocation.state === "failed" ||
1884
+ agent.invocation.state === "incomplete"
1885
+ ? "error"
1886
+ : "warning",
1887
+ detail: agent.invocationError !== null
1888
+ ? agent.invocationError === "runtime_version_mismatch"
1889
+ ? `observed Lore ${agent.invocation.runtimeVersion}; expected ${LORE_VERSION}`
1890
+ : `health record unavailable (${agent.invocationError})`
1891
+ : agent.invocation.state === "successful"
1892
+ ? `last completed ${agent.invocation.lastSuccessfulAt}`
1893
+ : agent.invocation.state === "failed"
1894
+ ? `last invocation failed (${agent.invocation.failureCode})`
1895
+ : agent.invocation.state === "incomplete"
1896
+ ? `last invocation did not complete (${agent.invocation.lastAttemptedAt})`
1897
+ : agent.invocation.state === "never_observed"
1898
+ ? "installed, but no invocation has been observed yet"
1899
+ : "invocation health is unavailable",
1900
+ });
1901
+ }
1902
+ checks.push({
1903
+ name: "reliability-store",
1904
+ status: status.reliabilityError !== null
1905
+ ? "error"
1906
+ : status.reliability === null
1907
+ ? "warning"
1908
+ : "ok",
1909
+ detail: status.reliabilityError ??
1910
+ status.reliability?.directory ??
1911
+ "not initialized",
1912
+ });
1913
+ if (status.reliability !== null) {
1914
+ checks.push({
1915
+ name: "durable-capture-outbox",
1916
+ status: status.reliability.outbox.authBlocked > 0 ||
1917
+ status.reliability.outbox.dead > 0
1918
+ ? "error"
1919
+ : status.reliability.outbox.ready > 0 ||
1920
+ status.reliability.outbox.inFlight > 0
1921
+ ? "warning"
1922
+ : "ok",
1923
+ detail: `${status.reliability.outbox.total} total; ${status.reliability.outbox.ready} ready, ${status.reliability.outbox.authBlocked} authentication-blocked, ${status.reliability.outbox.dead} dead-letter`,
1924
+ });
1925
+ checks.push({
1926
+ name: "verified-snapshot-cache",
1927
+ status: status.reliability.trustKeys > 0 ? "ok" : "warning",
1928
+ detail: `${status.reliability.cache.context} context, ${status.reliability.cache.policy} policy, ${status.reliability.trustKeys} pinned key(s)`,
1929
+ });
1930
+ const operational = status.reliability.operational;
1931
+ const detectedFailures = operational.guard.failures + operational.retrieval.failed;
1932
+ checks.push({
1933
+ name: "local-operational-metrics",
1934
+ status: detectedFailures > 0 ? "warning" : "ok",
1935
+ detail: `${detectedFailures} detected failure(s); Guard coverage ${operational.guard.coverageRate === null ? "not yet sampled" : `${(operational.guard.coverageRate * 100).toFixed(1)}%`}, context fallback ${operational.retrieval.fallbackRate === null ? "not yet sampled" : `${(operational.retrieval.fallbackRate * 100).toFixed(1)}%`}`,
1420
1936
  });
1421
1937
  }
1938
+ const incompleteInvocations = status.agents.filter((agent) => agent.configured && agent.invocation.state === "incomplete").length;
1939
+ const failedInvocations = status.agents.filter((agent) => agent.configured && agent.invocation.state === "failed").length;
1422
1940
  checks.push({
1423
- name: "retry-queue",
1424
- status: status.queuedTurns === 0 ? "ok" : "warning",
1425
- detail: `${status.queuedTurns} queued turn(s)`,
1941
+ name: "integration-invocation-completion",
1942
+ status: incompleteInvocations > 0
1943
+ ? "error"
1944
+ : failedInvocations > 0
1945
+ ? "warning"
1946
+ : "ok",
1947
+ detail: `${incompleteInvocations} incomplete and ${failedInvocations} explicitly failed latest invocation(s); target 0`,
1426
1948
  });
1427
1949
  if (config !== null) {
1428
1950
  checks.push(...(await apiChecks(config)));
@@ -1508,6 +2030,11 @@ export async function runCli(args = process.argv.slice(2)) {
1508
2030
  case "hook":
1509
2031
  await runHook(commandArgs);
1510
2032
  return;
2033
+ case "internal-health-write": {
2034
+ const encoded = commandArgs[0] ?? (await readInvocationHealthWriteInput());
2035
+ await runInvocationHealthWrite(encoded);
2036
+ return;
2037
+ }
1511
2038
  default:
1512
2039
  throw new Error(`Unknown command: ${command}\nTry: lore --help`);
1513
2040
  }