kibi-runtime 2.13.2 → 2.14.0
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 +121 -47
- package/dist/index.js +4044 -3205
- package/dist/skills/kibi-usage/SKILL.md +20 -3
- package/dist/skills/kibi-usage/resources/proof.md +23 -0
- package/package.json +2 -2
package/dist/engine-daemon.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// ../cli/src/engine-daemon.ts
|
|
6
|
-
import { writeFileSync as
|
|
6
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
7
7
|
import path9 from "node:path";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
rmSync as rmSync2,
|
|
25
25
|
statSync as statSync4,
|
|
26
26
|
unlinkSync,
|
|
27
|
-
writeFileSync as
|
|
27
|
+
writeFileSync as writeFileSync3
|
|
28
28
|
} from "node:fs";
|
|
29
29
|
import * as net from "node:net";
|
|
30
30
|
import * as os from "node:os";
|
|
@@ -102,7 +102,7 @@ function manifestVersion(manifestPath, expectedName) {
|
|
|
102
102
|
}
|
|
103
103
|
function cliVersion() {
|
|
104
104
|
if (true)
|
|
105
|
-
return "2.
|
|
105
|
+
return "2.19.0";
|
|
106
106
|
return manifestVersion(path.join(moduleDirectory, "..", "package.json"), "kibi-cli") ?? "unknown";
|
|
107
107
|
}
|
|
108
108
|
function coreVersion() {
|
|
@@ -1576,7 +1576,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1576
1576
|
child.kill(signal);
|
|
1577
1577
|
} catch {}
|
|
1578
1578
|
}
|
|
1579
|
-
async terminateProcessTree(child) {
|
|
1579
|
+
async terminateProcessTree(child, graceMs = 0) {
|
|
1580
1580
|
let closed = false;
|
|
1581
1581
|
const exited = new Promise((resolve) => {
|
|
1582
1582
|
const markExited = () => {
|
|
@@ -1586,6 +1586,19 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1586
1586
|
child.once("close", markExited);
|
|
1587
1587
|
child.once("error", markExited);
|
|
1588
1588
|
});
|
|
1589
|
+
if (graceMs > 0 && child.exitCode === null && child.signalCode === null) {
|
|
1590
|
+
let graceTimer;
|
|
1591
|
+
await Promise.race([
|
|
1592
|
+
exited,
|
|
1593
|
+
new Promise((resolve) => {
|
|
1594
|
+
graceTimer = setTimeout(resolve, graceMs);
|
|
1595
|
+
})
|
|
1596
|
+
]);
|
|
1597
|
+
if (graceTimer)
|
|
1598
|
+
clearTimeout(graceTimer);
|
|
1599
|
+
if (closed)
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1589
1602
|
if (child.exitCode === null)
|
|
1590
1603
|
this.signalProcessTree(child, "SIGTERM");
|
|
1591
1604
|
await Promise.race([
|
|
@@ -1707,7 +1720,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1707
1720
|
}
|
|
1708
1721
|
return new PrologProcessTerminatedError(this.terminationReason ?? "process exited");
|
|
1709
1722
|
}
|
|
1710
|
-
async terminate() {
|
|
1723
|
+
async terminate(options = {}) {
|
|
1711
1724
|
if (this.interactiveStarted) {
|
|
1712
1725
|
this.terminationReason ??= "terminated";
|
|
1713
1726
|
}
|
|
@@ -1727,7 +1740,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1727
1740
|
this.terminationPromise = (async () => {
|
|
1728
1741
|
if (current) {
|
|
1729
1742
|
current.stdin?.end();
|
|
1730
|
-
await this.terminateProcessTree(current);
|
|
1743
|
+
await this.terminateProcessTree(current, options.graceMs ?? 0);
|
|
1731
1744
|
}
|
|
1732
1745
|
await Promise.all(oneShots.map((child) => this.terminateProcessTree(child)));
|
|
1733
1746
|
})();
|
|
@@ -1741,7 +1754,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1741
1754
|
}
|
|
1742
1755
|
|
|
1743
1756
|
// ../cli/src/prolog/store-lock.ts
|
|
1744
|
-
import { existsSync as existsSync4, rmSync } from "node:fs";
|
|
1757
|
+
import { existsSync as existsSync4, rmSync, writeFileSync } from "node:fs";
|
|
1745
1758
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1746
1759
|
import { dirname, join } from "node:path";
|
|
1747
1760
|
var cachedBootId;
|
|
@@ -1785,6 +1798,33 @@ function classifyStoreLockHolder(owner) {
|
|
|
1785
1798
|
}
|
|
1786
1799
|
return "alive";
|
|
1787
1800
|
}
|
|
1801
|
+
function readStoreLockOwner(storePath) {
|
|
1802
|
+
const candidates = [
|
|
1803
|
+
join(storePath, ".kibi-lock-owner.json"),
|
|
1804
|
+
join(storePath, "rdf", ".kibi-lock-owner.json")
|
|
1805
|
+
];
|
|
1806
|
+
for (const journalPath of candidates) {
|
|
1807
|
+
if (!existsSync4(journalPath))
|
|
1808
|
+
continue;
|
|
1809
|
+
let parsed;
|
|
1810
|
+
try {
|
|
1811
|
+
parsed = JSON.parse(readFileSync3(journalPath, "utf8"));
|
|
1812
|
+
} catch {
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
if (parsed === null || typeof parsed !== "object")
|
|
1816
|
+
continue;
|
|
1817
|
+
const record = parsed;
|
|
1818
|
+
const owner = {
|
|
1819
|
+
...typeof record.pid === "number" ? { pid: record.pid } : {},
|
|
1820
|
+
...typeof record.workspaceRoot === "string" ? { workspaceRoot: record.workspaceRoot } : {},
|
|
1821
|
+
...typeof record.bootId === "string" ? { bootId: record.bootId } : {},
|
|
1822
|
+
...typeof record.startedAt === "string" ? { startedAt: record.startedAt } : {}
|
|
1823
|
+
};
|
|
1824
|
+
return { owner, journalPath };
|
|
1825
|
+
}
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1788
1828
|
function breakStoreLock(rdfDirectory) {
|
|
1789
1829
|
const candidates = [
|
|
1790
1830
|
join(rdfDirectory, "lock"),
|
|
@@ -1797,6 +1837,40 @@ function breakStoreLock(rdfDirectory) {
|
|
|
1797
1837
|
}
|
|
1798
1838
|
}
|
|
1799
1839
|
}
|
|
1840
|
+
var STORE_LOCK_TAKEOVER_FILE = ".kibi-lock-takeover.json";
|
|
1841
|
+
var STORE_LOCK_TAKEOVER_VISIBLE_MS = 24 * 60 * 60 * 1000;
|
|
1842
|
+
function recordStoreLockTakeover(storePath, brokenPid, reason, at = new Date) {
|
|
1843
|
+
try {
|
|
1844
|
+
writeFileSync(join(storePath, STORE_LOCK_TAKEOVER_FILE), `${JSON.stringify({ brokenPid, reason, at: at.toISOString() })}
|
|
1845
|
+
`, { mode: 384 });
|
|
1846
|
+
} catch {}
|
|
1847
|
+
}
|
|
1848
|
+
function readRdfLockPid(lockPath) {
|
|
1849
|
+
try {
|
|
1850
|
+
const match = /pid\((\d+)\)/.exec(readFileSync3(lockPath, "utf8"));
|
|
1851
|
+
const pid = Number.parseInt(match?.[1] ?? "", 10);
|
|
1852
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
1853
|
+
} catch {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
function noteDeadHolderBeforeAttach(kbPath) {
|
|
1858
|
+
const lockPath = join(kbPath, "rdf", "lock");
|
|
1859
|
+
if (!existsSync4(lockPath))
|
|
1860
|
+
return () => {
|
|
1861
|
+
return;
|
|
1862
|
+
};
|
|
1863
|
+
const lockPid = readRdfLockPid(lockPath);
|
|
1864
|
+
const journal = readStoreLockOwner(kbPath)?.owner ?? null;
|
|
1865
|
+
const owner = journal !== null && (lockPid === null || journal.pid === lockPid) ? journal : lockPid !== null ? { pid: lockPid } : null;
|
|
1866
|
+
const decision = decideStoreLockTakeover(owner);
|
|
1867
|
+
if (decision.action !== "break")
|
|
1868
|
+
return () => {
|
|
1869
|
+
return;
|
|
1870
|
+
};
|
|
1871
|
+
const brokenPid = owner?.pid ?? null;
|
|
1872
|
+
return () => recordStoreLockTakeover(kbPath, brokenPid, decision.reason);
|
|
1873
|
+
}
|
|
1800
1874
|
function decideStoreLockTakeover(owner) {
|
|
1801
1875
|
const state = classifyStoreLockHolder(owner);
|
|
1802
1876
|
switch (state) {
|
|
@@ -1833,6 +1907,7 @@ Store lock holder: ${holderSummary}. Close that session, or run 'kibi engine sto
|
|
|
1833
1907
|
};
|
|
1834
1908
|
}
|
|
1835
1909
|
const lockDirectory = storeLocked.lockDirectory !== "" ? storeLocked.lockDirectory : join(kbPath, "rdf");
|
|
1910
|
+
recordStoreLockTakeover(kbPath, storeLocked.owner?.pid ?? null, decision.reason);
|
|
1836
1911
|
breakStoreLock(lockDirectory);
|
|
1837
1912
|
const retried = await prolog.query(`kb_attach('${escapeAtom(kbPath)}')`);
|
|
1838
1913
|
if (retried.success) {
|
|
@@ -1856,7 +1931,7 @@ import {
|
|
|
1856
1931
|
readFileSync as readFileSync4,
|
|
1857
1932
|
readdirSync,
|
|
1858
1933
|
statSync as statSync3,
|
|
1859
|
-
writeFileSync
|
|
1934
|
+
writeFileSync as writeFileSync2
|
|
1860
1935
|
} from "node:fs";
|
|
1861
1936
|
import path5 from "node:path";
|
|
1862
1937
|
var BRANCH_STORE_MANIFEST_VERSION = 1;
|
|
@@ -1918,7 +1993,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
|
1918
1993
|
if (entries.length > 0) {
|
|
1919
1994
|
throw new Error(`Branch store at ${storePath} has compiled data but no valid identity manifest; refusing to adopt it.`);
|
|
1920
1995
|
}
|
|
1921
|
-
|
|
1996
|
+
writeFileSync2(manifestPath, `${JSON.stringify(expectedBranchStoreManifest(branch), null, 2)}
|
|
1922
1997
|
`, { mode: 384 });
|
|
1923
1998
|
}
|
|
1924
1999
|
return storePath;
|
|
@@ -2005,6 +2080,8 @@ var READ_LIMITED_METHODS = new Set([
|
|
|
2005
2080
|
]);
|
|
2006
2081
|
var ENGINE_PROTOCOL_VERSION = 1;
|
|
2007
2082
|
var ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
2083
|
+
var ENGINE_IDLE_CHECK_INTERVAL_MS = 30 * 1000;
|
|
2084
|
+
var ENGINE_PROLOG_EXIT_GRACE_MS = 1000;
|
|
2008
2085
|
function enginePackageVersions() {
|
|
2009
2086
|
return kibiPackageVersions();
|
|
2010
2087
|
}
|
|
@@ -2174,7 +2251,7 @@ function runtimeDirectory() {
|
|
|
2174
2251
|
try {
|
|
2175
2252
|
mkdirSync2(candidate, { recursive: true, mode: 448 });
|
|
2176
2253
|
const probe = path8.join(candidate, `.kibi-write-${process.pid}`);
|
|
2177
|
-
|
|
2254
|
+
writeFileSync3(probe, "", { mode: 384 });
|
|
2178
2255
|
unlinkSync(probe);
|
|
2179
2256
|
return candidate;
|
|
2180
2257
|
} catch {}
|
|
@@ -2214,7 +2291,7 @@ function runtimeDirectory() {
|
|
|
2214
2291
|
if (Buffer.byteLength(path8.join(canonical, socketName), "utf8") > maximumBytes)
|
|
2215
2292
|
continue;
|
|
2216
2293
|
const probe = path8.join(canonical, `.kibi-write-${randomUUID()}`);
|
|
2217
|
-
|
|
2294
|
+
writeFileSync3(probe, "", { mode: 384, flag: "wx" });
|
|
2218
2295
|
unlinkSync(probe);
|
|
2219
2296
|
return canonical;
|
|
2220
2297
|
} catch {}
|
|
@@ -2278,26 +2355,13 @@ function recoverStaleJournalLock(branchPath) {
|
|
|
2278
2355
|
const lockPath = path8.join(branchPath, "rdf", "lock");
|
|
2279
2356
|
if (!existsSync6(lockPath))
|
|
2280
2357
|
return;
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
contents = readFileSync5(lockPath, "utf8");
|
|
2284
|
-
} catch {
|
|
2285
|
-
return;
|
|
2286
|
-
}
|
|
2287
|
-
const match = /pid\((\d+)\)/.exec(contents);
|
|
2288
|
-
if (!match)
|
|
2289
|
-
return;
|
|
2290
|
-
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
2291
|
-
if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid)
|
|
2358
|
+
const pid = readRdfLockPid(lockPath);
|
|
2359
|
+
if (pid === null || pid === process.pid || isProcessAlive(pid))
|
|
2292
2360
|
return;
|
|
2361
|
+
recordStoreLockTakeover(branchPath, pid, decideStoreLockTakeover({ pid }).reason);
|
|
2293
2362
|
try {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
} catch {
|
|
2297
|
-
try {
|
|
2298
|
-
rmSync2(lockPath, { force: true });
|
|
2299
|
-
} catch {}
|
|
2300
|
-
}
|
|
2363
|
+
rmSync2(lockPath, { force: true });
|
|
2364
|
+
} catch {}
|
|
2301
2365
|
}
|
|
2302
2366
|
function connectSocket(socketPath, timeoutMs) {
|
|
2303
2367
|
return new Promise((resolve, reject) => {
|
|
@@ -2340,7 +2404,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
2340
2404
|
recoverStaleJournalLock(branchPath);
|
|
2341
2405
|
archiveLegacyFiles(branchPath);
|
|
2342
2406
|
if (!existsSync6(sentinelPath)) {
|
|
2343
|
-
|
|
2407
|
+
writeFileSync3(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
2344
2408
|
`, { mode: 384 });
|
|
2345
2409
|
}
|
|
2346
2410
|
return;
|
|
@@ -2359,7 +2423,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
2359
2423
|
recoverStaleJournalLock(branchPath);
|
|
2360
2424
|
archiveLegacyFiles(branchPath);
|
|
2361
2425
|
if (!existsSync6(sentinelPath)) {
|
|
2362
|
-
|
|
2426
|
+
writeFileSync3(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
2363
2427
|
`, { mode: 384 });
|
|
2364
2428
|
}
|
|
2365
2429
|
return;
|
|
@@ -2369,13 +2433,13 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
2369
2433
|
const legacyAudit = path8.join(branchPath, "audit.log");
|
|
2370
2434
|
if (!existsSync6(legacyRdf) && !existsSync6(legacyAudit)) {
|
|
2371
2435
|
mkdirSync2(path8.join(branchPath, "rdf"), { recursive: true });
|
|
2372
|
-
|
|
2436
|
+
writeFileSync3(markerPath, `{"format":"kibi.rdf-journal.v1","schemaVersion":1}
|
|
2373
2437
|
`, { mode: 384 });
|
|
2374
|
-
|
|
2438
|
+
writeFileSync3(path8.join(branchPath, "CURRENT"), `generation-1:0
|
|
2375
2439
|
`, {
|
|
2376
2440
|
mode: 384
|
|
2377
2441
|
});
|
|
2378
|
-
|
|
2442
|
+
writeFileSync3(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
2379
2443
|
`, { mode: 384 });
|
|
2380
2444
|
return;
|
|
2381
2445
|
}
|
|
@@ -2424,7 +2488,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
2424
2488
|
renameSync(source, target);
|
|
2425
2489
|
}
|
|
2426
2490
|
archiveLegacyFiles(branchPath);
|
|
2427
|
-
|
|
2491
|
+
writeFileSync3(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
2428
2492
|
`, { mode: 384 });
|
|
2429
2493
|
fsyncJournaledBranchStore(branchPath);
|
|
2430
2494
|
rmSync2(staging, { recursive: true, force: true });
|
|
@@ -2517,8 +2581,11 @@ class EngineSession {
|
|
|
2517
2581
|
}
|
|
2518
2582
|
async boot() {
|
|
2519
2583
|
await this.prolog.start();
|
|
2584
|
+
const recordTakeover = noteDeadHolderBeforeAttach(this.branchPath);
|
|
2520
2585
|
const attached = await this.prolog.query(`kb_attach('${quoteProlog(this.branchPath)}')`);
|
|
2521
|
-
if (
|
|
2586
|
+
if (attached.success)
|
|
2587
|
+
recordTakeover();
|
|
2588
|
+
else {
|
|
2522
2589
|
const recovered = await retryAttachAfterBreakingStaleLock(this.prolog, this.branchPath, attached);
|
|
2523
2590
|
if (!recovered.success)
|
|
2524
2591
|
throw new Error(recovered.error ?? "Failed to attach branch KB");
|
|
@@ -2617,11 +2684,12 @@ async function runEngineDaemon(requestedOptions) {
|
|
|
2617
2684
|
rmSync2(options.socketPath, { force: true });
|
|
2618
2685
|
} catch {}
|
|
2619
2686
|
}
|
|
2620
|
-
|
|
2687
|
+
writeFileSync3(enginePidPath(options.workspaceRoot, options.branch), `${process.pid}
|
|
2621
2688
|
`, { mode: 384 });
|
|
2622
2689
|
const server = net.createServer();
|
|
2623
2690
|
let activeClients = 0;
|
|
2624
|
-
let
|
|
2691
|
+
let lastActivityAt = Date.now();
|
|
2692
|
+
let idleWatch = null;
|
|
2625
2693
|
let queue = Promise.resolve();
|
|
2626
2694
|
let idleCompactionQueued = false;
|
|
2627
2695
|
const queryCache = new Map;
|
|
@@ -2698,12 +2766,15 @@ async function runEngineDaemon(requestedOptions) {
|
|
|
2698
2766
|
idleCompactionQueued = false;
|
|
2699
2767
|
});
|
|
2700
2768
|
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2769
|
+
lastActivityAt = Date.now();
|
|
2770
|
+
if (idleWatch)
|
|
2771
|
+
return;
|
|
2772
|
+
const idleMs = engineIdleTimeoutMs();
|
|
2773
|
+
idleWatch = setInterval(() => {
|
|
2774
|
+
if (!shuttingDown && activeClients === 0 && Date.now() - lastActivityAt >= idleMs)
|
|
2705
2775
|
shutdown();
|
|
2706
|
-
},
|
|
2776
|
+
}, Math.min(idleMs, ENGINE_IDLE_CHECK_INTERVAL_MS));
|
|
2777
|
+
idleWatch.unref();
|
|
2707
2778
|
};
|
|
2708
2779
|
const handle = async (request, cancelledRequests) => {
|
|
2709
2780
|
if (request.protocolVersion !== ENGINE_PROTOCOL_VERSION) {
|
|
@@ -2958,20 +3029,22 @@ async function runEngineDaemon(requestedOptions) {
|
|
|
2958
3029
|
shuttingDown = true;
|
|
2959
3030
|
process.off("SIGTERM", requestSignalShutdown);
|
|
2960
3031
|
process.off("SIGINT", requestSignalShutdown);
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
3032
|
+
if (idleWatch)
|
|
3033
|
+
clearInterval(idleWatch);
|
|
2963
3034
|
clearInterval(workspaceWatchdog);
|
|
2964
3035
|
server.close();
|
|
2965
3036
|
await queue.catch(() => {
|
|
2966
3037
|
return;
|
|
2967
3038
|
});
|
|
2968
3039
|
const saved = session.needsRecycle() ? null : await prolog.query("kb_save").catch(() => null);
|
|
3040
|
+
let detached = false;
|
|
2969
3041
|
if (saved?.success) {
|
|
2970
3042
|
try {
|
|
2971
3043
|
fsyncJournaledBranchStore(branchPath);
|
|
2972
3044
|
} catch {}
|
|
3045
|
+
detached = await prolog.query("kb_detach").then((result) => result.success).catch(() => false);
|
|
2973
3046
|
}
|
|
2974
|
-
await prolog.terminate().catch(() => {
|
|
3047
|
+
await prolog.terminate({ graceMs: detached ? 0 : ENGINE_PROLOG_EXIT_GRACE_MS }).catch(() => {
|
|
2975
3048
|
return;
|
|
2976
3049
|
});
|
|
2977
3050
|
for (const client of clients)
|
|
@@ -3008,6 +3081,7 @@ async function runEngineDaemon(requestedOptions) {
|
|
|
3008
3081
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
3009
3082
|
buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
|
|
3010
3083
|
const request = value;
|
|
3084
|
+
lastActivityAt = Date.now();
|
|
3011
3085
|
if (shuttingDown) {
|
|
3012
3086
|
writeSocketFrame(socket, {
|
|
3013
3087
|
id: request.id,
|
|
@@ -3114,7 +3188,7 @@ async function runEngineDaemonCli(args = process.argv.slice(2)) {
|
|
|
3114
3188
|
const socketPath = socketIndex >= 0 ? args[socketIndex + 1] : undefined;
|
|
3115
3189
|
if (socketPath) {
|
|
3116
3190
|
try {
|
|
3117
|
-
|
|
3191
|
+
writeFileSync4(`${socketPath}.error`, `${message}
|
|
3118
3192
|
`, { mode: 384 });
|
|
3119
3193
|
} catch {}
|
|
3120
3194
|
}
|