kibi-runtime 2.0.1 → 2.0.2
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/engine-daemon.js +136 -32
- package/dist/index.d.ts +2 -1
- package/dist/index.js +40648 -38780
- package/dist/skills/kibi-usage/SKILL.md +7 -3
- package/dist/skills/kibi-usage/resources/fact-lanes.md +4 -0
- package/dist/skills/kibi-usage/resources/relationship-directions.md +35 -17
- package/dist/skills/kibi-usage/resources/source-authoring.md +36 -0
- package/dist/skills/kibi-usage/resources/workflows.md +52 -1
- package/package.json +3 -3
package/dist/engine-daemon.js
CHANGED
|
@@ -385,7 +385,7 @@ function unquoteAtom(value) {
|
|
|
385
385
|
|
|
386
386
|
// ../cli/src/prolog.ts
|
|
387
387
|
var importMetaDir = path.dirname(fileURLToPath(import.meta.url));
|
|
388
|
-
var
|
|
388
|
+
var DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
389
389
|
var PROLOG_OUTPUT_OVERFLOW_ERROR = "Query exceeded bounded Prolog output capacity (ENOBUFS); narrow the operation or reduce stored entity size";
|
|
390
390
|
var INTERACTIVE_QUERY_FRAME_END = "__KIBI_QUERY_FRAME_END__";
|
|
391
391
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -426,6 +426,16 @@ function resolveKbPlPath() {
|
|
|
426
426
|
}
|
|
427
427
|
throw new Error("Unable to resolve kb.pl. Expected kibi-core to be installed (node_modules) " + "or to be running inside the monorepo checkout.");
|
|
428
428
|
}
|
|
429
|
+
|
|
430
|
+
class PrologProcessTerminatedError extends Error {
|
|
431
|
+
code = "prolog_process_terminated";
|
|
432
|
+
reason;
|
|
433
|
+
constructor(reason) {
|
|
434
|
+
super(`Kibi Prolog process is no longer running (${reason}); the interactive session and its attached KB were lost. The owning engine must restart it before serving more queries — run 'kibi engine stop' and retry if this persists.`);
|
|
435
|
+
this.name = "PrologProcessTerminatedError";
|
|
436
|
+
this.reason = reason;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
429
439
|
function registerProcessExitOnce(current, handler, on = (event, listener) => process.on(event, listener)) {
|
|
430
440
|
if (current)
|
|
431
441
|
return current;
|
|
@@ -454,19 +464,32 @@ class PrologProcess {
|
|
|
454
464
|
useOneShotMode;
|
|
455
465
|
attachedKbPath = null;
|
|
456
466
|
onProcessExit = null;
|
|
467
|
+
maxOutputBytes;
|
|
468
|
+
interactiveStarted = false;
|
|
469
|
+
terminationReason = null;
|
|
457
470
|
constructor(options = {}) {
|
|
458
471
|
this.swiplPath = options.swiplPath || "swipl";
|
|
459
472
|
this.timeout = options.timeout || 30000;
|
|
473
|
+
this.maxOutputBytes = options.maxOutputBytes !== undefined && Number.isInteger(options.maxOutputBytes) && options.maxOutputBytes > 0 ? options.maxOutputBytes : DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES;
|
|
460
474
|
this.useOneShotMode = options.oneShot ?? false;
|
|
461
475
|
}
|
|
462
476
|
attachProcessExitHandler(on = (event, listener) => process.on(event, listener)) {
|
|
463
477
|
this.onProcessExit = bindProcessExitHandler(this.onProcessExit, () => this.terminate(), on);
|
|
464
478
|
}
|
|
479
|
+
get oneShotMode() {
|
|
480
|
+
return this.useOneShotMode || !this.interactiveStarted;
|
|
481
|
+
}
|
|
482
|
+
needsRestart() {
|
|
483
|
+
return this.interactiveStarted && !this.isProcessUsable();
|
|
484
|
+
}
|
|
465
485
|
async start() {
|
|
466
486
|
if (!existsSync(this.swiplPath) && this.swiplPath !== "swipl") {
|
|
467
487
|
throw new Error(`SWI-Prolog not found at ${this.swiplPath}. Please install SWI-Prolog or check your PATH.`);
|
|
468
488
|
}
|
|
469
489
|
const kbPath = resolveKbPlPath();
|
|
490
|
+
this.interactiveStarted = true;
|
|
491
|
+
this.terminationReason = null;
|
|
492
|
+
this.invalidateCache();
|
|
470
493
|
this.process = spawn(this.swiplPath, [
|
|
471
494
|
"-g",
|
|
472
495
|
`use_module('${kbPath}'), use_module(library(semweb/rdf_db)), set_prolog_flag(answer_write_options, [max_depth(0), quoted(true)])`,
|
|
@@ -490,6 +513,12 @@ class PrologProcess {
|
|
|
490
513
|
this.process.stderr.on("data", (chunk) => {
|
|
491
514
|
this.appendErrorChunk(chunk);
|
|
492
515
|
});
|
|
516
|
+
const child = this.process;
|
|
517
|
+
child.stdin?.on("error", (error) => {
|
|
518
|
+
if (this.process === child) {
|
|
519
|
+
this.terminationReason ??= `stdin ${error.code ?? "error"}`;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
493
522
|
this.process.stdin.write(`true.
|
|
494
523
|
`);
|
|
495
524
|
this.attachProcessExitHandler();
|
|
@@ -543,10 +572,10 @@ class PrologProcess {
|
|
|
543
572
|
return this.query(batchGoal);
|
|
544
573
|
}
|
|
545
574
|
if (!this.isProcessUsable()) {
|
|
546
|
-
if (this.process === null) {
|
|
575
|
+
if (this.process === null && !this.interactiveStarted) {
|
|
547
576
|
return this.execOneShot(goal, this.attachedKbPath);
|
|
548
577
|
}
|
|
549
|
-
throw
|
|
578
|
+
throw this.lostProcessError();
|
|
550
579
|
}
|
|
551
580
|
const runInteractiveQuery = async () => {
|
|
552
581
|
this.clearQueryBuffers();
|
|
@@ -576,6 +605,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
576
605
|
console.error(`[prolog debug] runtime=${runtime ?? "unknown"} packages=${process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS}`);
|
|
577
606
|
}
|
|
578
607
|
settled = true;
|
|
608
|
+
this.terminationReason = `query timeout: ${goalLabel}`;
|
|
579
609
|
this.terminate().finally(() => {
|
|
580
610
|
reject(new Error(msg));
|
|
581
611
|
});
|
|
@@ -587,6 +617,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
587
617
|
if (this.outputOverflowed) {
|
|
588
618
|
clearTimeout(timeoutId);
|
|
589
619
|
settled = true;
|
|
620
|
+
this.terminationReason = `output overflow (ENOBUFS): ${goalLabel}`;
|
|
590
621
|
this.terminate().finally(() => {
|
|
591
622
|
resolve({
|
|
592
623
|
success: false,
|
|
@@ -599,6 +630,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
599
630
|
if (!this.isProcessUsable()) {
|
|
600
631
|
clearTimeout(timeoutId);
|
|
601
632
|
settled = true;
|
|
633
|
+
this.terminationReason ??= `process exited during query: ${goalLabel}`;
|
|
602
634
|
resolve({
|
|
603
635
|
success: false,
|
|
604
636
|
bindings: {},
|
|
@@ -666,7 +698,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
666
698
|
await previousQuery;
|
|
667
699
|
try {
|
|
668
700
|
if (!this.isProcessUsable()) {
|
|
669
|
-
throw
|
|
701
|
+
throw this.lostProcessError();
|
|
670
702
|
}
|
|
671
703
|
return await runInteractiveQuery();
|
|
672
704
|
} finally {
|
|
@@ -784,7 +816,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
784
816
|
if (outputOverflowed)
|
|
785
817
|
return;
|
|
786
818
|
const bytes = target === "stdout" ? outputBytes : errorBytes;
|
|
787
|
-
const remaining =
|
|
819
|
+
const remaining = this.maxOutputBytes - bytes;
|
|
788
820
|
if (chunk.byteLength > remaining) {
|
|
789
821
|
const clipped = chunk.subarray(0, Math.max(0, remaining)).toString();
|
|
790
822
|
if (target === "stdout")
|
|
@@ -897,10 +929,10 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
897
929
|
appendOutputChunk(chunk) {
|
|
898
930
|
if (this.outputOverflowed)
|
|
899
931
|
return;
|
|
900
|
-
const remaining =
|
|
932
|
+
const remaining = this.maxOutputBytes - this.outputBufferBytes;
|
|
901
933
|
if (chunk.byteLength > remaining) {
|
|
902
934
|
this.outputBuffer += chunk.subarray(0, remaining).toString();
|
|
903
|
-
this.outputBufferBytes =
|
|
935
|
+
this.outputBufferBytes = this.maxOutputBytes;
|
|
904
936
|
this.outputOverflowed = true;
|
|
905
937
|
return;
|
|
906
938
|
}
|
|
@@ -910,10 +942,10 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
910
942
|
appendErrorChunk(chunk) {
|
|
911
943
|
if (this.outputOverflowed)
|
|
912
944
|
return;
|
|
913
|
-
const remaining =
|
|
945
|
+
const remaining = this.maxOutputBytes - this.errorBufferBytes;
|
|
914
946
|
if (chunk.byteLength > remaining) {
|
|
915
947
|
this.errorBuffer += chunk.subarray(0, remaining).toString();
|
|
916
|
-
this.errorBufferBytes =
|
|
948
|
+
this.errorBufferBytes = this.maxOutputBytes;
|
|
917
949
|
this.outputOverflowed = true;
|
|
918
950
|
return;
|
|
919
951
|
}
|
|
@@ -1059,7 +1091,16 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1059
1091
|
getPid() {
|
|
1060
1092
|
return this.process?.pid || 0;
|
|
1061
1093
|
}
|
|
1094
|
+
lostProcessError() {
|
|
1095
|
+
if (!this.interactiveStarted) {
|
|
1096
|
+
return new Error("Prolog process not started");
|
|
1097
|
+
}
|
|
1098
|
+
return new PrologProcessTerminatedError(this.terminationReason ?? "process exited");
|
|
1099
|
+
}
|
|
1062
1100
|
async terminate() {
|
|
1101
|
+
if (this.interactiveStarted) {
|
|
1102
|
+
this.terminationReason ??= "terminated";
|
|
1103
|
+
}
|
|
1063
1104
|
if (this.terminationPromise) {
|
|
1064
1105
|
await this.terminationPromise;
|
|
1065
1106
|
return;
|
|
@@ -1448,11 +1489,12 @@ function frame(value) {
|
|
|
1448
1489
|
header.writeUInt32BE(payload.byteLength, 0);
|
|
1449
1490
|
return Buffer.concat([header, payload]);
|
|
1450
1491
|
}
|
|
1492
|
+
function ignoreWriteFailure() {}
|
|
1451
1493
|
function writeSocketFrame(socket, value) {
|
|
1452
1494
|
if (socket.destroyed || !socket.writable)
|
|
1453
1495
|
return;
|
|
1454
1496
|
try {
|
|
1455
|
-
socket.write(frame(value));
|
|
1497
|
+
socket.write(frame(value), ignoreWriteFailure);
|
|
1456
1498
|
} catch {}
|
|
1457
1499
|
}
|
|
1458
1500
|
function parseFrames(buffer, onFrame) {
|
|
@@ -1756,32 +1798,88 @@ function recoverInterruptedGeneration(branchPath) {
|
|
|
1756
1798
|
} catch {}
|
|
1757
1799
|
}
|
|
1758
1800
|
}
|
|
1801
|
+
function isEngineLifecycleRequest(request) {
|
|
1802
|
+
if (request.method === "stop" || request.method === "cancel")
|
|
1803
|
+
return true;
|
|
1804
|
+
const kind = request.command?.kind;
|
|
1805
|
+
return request.method === "command" && (kind === "stop" || kind === "cancel" || kind === "lifecycle");
|
|
1806
|
+
}
|
|
1807
|
+
var ENGINE_PRELOADED_MODULES = [
|
|
1808
|
+
["status.pl", "status"],
|
|
1809
|
+
["discovery.pl", "discovery"]
|
|
1810
|
+
];
|
|
1811
|
+
var USE_MODULE_GOAL = /^use_module\('(?:[^']|'')*'\)$/u;
|
|
1812
|
+
|
|
1813
|
+
class EngineSession {
|
|
1814
|
+
prolog;
|
|
1815
|
+
branchPath;
|
|
1816
|
+
modules = [];
|
|
1817
|
+
constructor(prolog, branchPath) {
|
|
1818
|
+
this.prolog = prolog;
|
|
1819
|
+
this.branchPath = branchPath;
|
|
1820
|
+
const coreModuleDir = path4.dirname(resolveKbPlPath());
|
|
1821
|
+
for (const [fileName, label] of ENGINE_PRELOADED_MODULES) {
|
|
1822
|
+
const modulePath = path4.join(coreModuleDir, fileName);
|
|
1823
|
+
this.modules.push({
|
|
1824
|
+
goal: `use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`,
|
|
1825
|
+
label
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
async boot() {
|
|
1830
|
+
await this.prolog.start();
|
|
1831
|
+
const attached = await this.prolog.query(`kb_attach('${quoteProlog(this.branchPath)}')`);
|
|
1832
|
+
if (!attached.success) {
|
|
1833
|
+
const recovered = await retryAttachAfterBreakingStaleLock(this.prolog, this.branchPath, attached);
|
|
1834
|
+
if (!recovered.success)
|
|
1835
|
+
throw new Error(recovered.error ?? "Failed to attach branch KB");
|
|
1836
|
+
}
|
|
1837
|
+
for (const { goal, label } of this.modules) {
|
|
1838
|
+
const loaded = await this.prolog.query(goal);
|
|
1839
|
+
if (!loaded.success) {
|
|
1840
|
+
throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
recordModuleLoad(goal, result) {
|
|
1845
|
+
const normalized = goal.trim().replace(/\.+\s*$/u, "");
|
|
1846
|
+
if (result.success && USE_MODULE_GOAL.test(normalized) && !this.modules.some((module) => module.goal === normalized)) {
|
|
1847
|
+
this.modules.push({ goal: normalized, label: "client-loaded" });
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
needsRecycle() {
|
|
1851
|
+
return this.prolog.needsRestart();
|
|
1852
|
+
}
|
|
1853
|
+
async ensureLive() {
|
|
1854
|
+
if (!this.prolog.needsRestart())
|
|
1855
|
+
return false;
|
|
1856
|
+
console.error("[KIBI] engine Prolog session was lost (output overflow, timeout, or crash); restarting and reattaching the branch store");
|
|
1857
|
+
try {
|
|
1858
|
+
await this.boot();
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
await this.prolog.terminate().catch(() => {
|
|
1861
|
+
return;
|
|
1862
|
+
});
|
|
1863
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1864
|
+
throw new Error(`Kibi engine failed to recycle its Prolog session: ${message}`);
|
|
1865
|
+
}
|
|
1866
|
+
return true;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1759
1869
|
async function runEngineDaemon(options) {
|
|
1760
1870
|
if (!isValidBranchName(options.branch)) {
|
|
1761
1871
|
throw new Error(`Invalid Kibi engine branch name: ${options.branch}`);
|
|
1762
1872
|
}
|
|
1763
1873
|
const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
|
|
1764
1874
|
await ensureJournaledBranchStoreAsync(branchPath);
|
|
1765
|
-
const prolog = new PrologProcess({
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
const attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
1774
|
-
const coreModuleDir = path4.dirname(resolveKbPlPath());
|
|
1775
|
-
for (const [fileName, label] of [
|
|
1776
|
-
["status.pl", "status"],
|
|
1777
|
-
["discovery.pl", "discovery"]
|
|
1778
|
-
]) {
|
|
1779
|
-
const modulePath = path4.join(coreModuleDir, fileName);
|
|
1780
|
-
const loaded = await prolog.query(`use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`);
|
|
1781
|
-
if (!loaded.success) {
|
|
1782
|
-
throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1875
|
+
const prolog = new PrologProcess({
|
|
1876
|
+
timeout: 120000,
|
|
1877
|
+
oneShot: false,
|
|
1878
|
+
...options.maxOutputBytes === undefined ? {} : { maxOutputBytes: options.maxOutputBytes }
|
|
1879
|
+
});
|
|
1880
|
+
const session = new EngineSession(prolog, branchPath);
|
|
1881
|
+
await session.boot();
|
|
1882
|
+
let attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
1785
1883
|
mkdirSync2(path4.dirname(options.socketPath), { recursive: true, mode: 448 });
|
|
1786
1884
|
if (existsSync4(options.socketPath)) {
|
|
1787
1885
|
const isLivePeerSignal = (error) => {
|
|
@@ -1880,7 +1978,7 @@ async function runEngineDaemon(options) {
|
|
|
1880
1978
|
if (!idleCompactionQueued) {
|
|
1881
1979
|
idleCompactionQueued = true;
|
|
1882
1980
|
queue = queue.then(async () => {
|
|
1883
|
-
if (activeClients === 0) {
|
|
1981
|
+
if (activeClients === 0 && !session.needsRecycle()) {
|
|
1884
1982
|
await prolog.query("kb_storage_compact_if_needed");
|
|
1885
1983
|
}
|
|
1886
1984
|
}).catch(() => {
|
|
@@ -1907,6 +2005,11 @@ async function runEngineDaemon(options) {
|
|
|
1907
2005
|
if (path4.resolve(request.workspaceRoot ?? "") !== path4.resolve(options.workspaceRoot) || request.branch !== options.branch) {
|
|
1908
2006
|
throw new Error("Kibi engine workspace identity mismatch");
|
|
1909
2007
|
}
|
|
2008
|
+
if (!isEngineLifecycleRequest(request) && await session.ensureLive()) {
|
|
2009
|
+
queryCache.clear();
|
|
2010
|
+
freshnessCache = null;
|
|
2011
|
+
attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
2012
|
+
}
|
|
1910
2013
|
switch (request.method) {
|
|
1911
2014
|
case "entities": {
|
|
1912
2015
|
const limit = request.limit ?? 100;
|
|
@@ -2067,6 +2170,7 @@ async function runEngineDaemon(options) {
|
|
|
2067
2170
|
return cached;
|
|
2068
2171
|
}
|
|
2069
2172
|
const result = await prolog.query(request.goal);
|
|
2173
|
+
session.recordModuleLoad(request.goal, result);
|
|
2070
2174
|
if (result.success && mutatingEngineGoal(request.goal)) {
|
|
2071
2175
|
fsyncJournaledBranchStore(branchPath);
|
|
2072
2176
|
}
|
|
@@ -2119,7 +2223,7 @@ async function runEngineDaemon(options) {
|
|
|
2119
2223
|
if (idleTimer)
|
|
2120
2224
|
clearTimeout(idleTimer);
|
|
2121
2225
|
clearInterval(workspaceWatchdog);
|
|
2122
|
-
const saved = await prolog.query("kb_save").catch(() => null);
|
|
2226
|
+
const saved = session.needsRecycle() ? null : await prolog.query("kb_save").catch(() => null);
|
|
2123
2227
|
if (saved?.success) {
|
|
2124
2228
|
try {
|
|
2125
2229
|
fsyncJournaledBranchStore(branchPath);
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,8 @@ export { RULE_NAMES, RULES, getCanonicalRules, getDefaultRules, getEffectiveRule
|
|
|
30
30
|
export type { RuleDefinition, RuleEnforcementClass, StrictWriteSet, Violation, } from "kibi-cli/public/check-types";
|
|
31
31
|
export type { ChangedFileImpactResult, QualityDiagnostic, } from "kibi-cli/public/impact-diagnostics";
|
|
32
32
|
export { branchStorePath, branchStoreManifestPath, branchStoresRoot, branchStoreKey, resolveActiveBranch, resolveBranchAttachment, isValidBranchName, copyCleanSnapshot, ensureBranchStoreManifest, getBranchDiagnostic, } from "kibi-cli/public/branch-resolver";
|
|
33
|
-
export { KIBI_PROTOCOL_VERSION, operationData, resultVersion, toKibiResult, } from "kibi-cli/operations/result-envelope";
|
|
33
|
+
export { KIBI_PROTOCOL_VERSION, appendPayloadCountField, normalizeResultPayload, operationData, resultVersion, toKibiResult, } from "kibi-cli/operations/result-envelope";
|
|
34
|
+
export type { NormalizedResultPayload } from "kibi-cli/operations/result-envelope";
|
|
34
35
|
export * from "kibi-cli/public/branch-resolver";
|
|
35
36
|
export { EngineClient, engineSocketPath } from "kibi-cli/engine";
|
|
36
37
|
export { PrologProcess, resolveKbPlPath } from "kibi-cli/prolog";
|