kibi-runtime 2.0.2 → 2.1.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 +758 -137
- package/dist/index.d.ts +2 -1
- package/dist/index.js +25014 -20890
- package/dist/skills/kibi-usage/resources/operation-access.md +1 -0
- package/package.json +5 -4
package/dist/engine-daemon.js
CHANGED
|
@@ -4,36 +4,161 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// ../cli/src/engine-daemon.ts
|
|
6
6
|
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
7
|
-
import
|
|
7
|
+
import path8 from "node:path";
|
|
8
8
|
import { pathToFileURL } from "node:url";
|
|
9
9
|
|
|
10
10
|
// ../cli/src/engine.ts
|
|
11
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
11
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
12
12
|
import {
|
|
13
13
|
chmodSync,
|
|
14
|
-
closeSync,
|
|
15
|
-
existsSync as
|
|
14
|
+
closeSync as closeSync2,
|
|
15
|
+
existsSync as existsSync6,
|
|
16
16
|
fsyncSync,
|
|
17
|
+
lstatSync as lstatSync3,
|
|
17
18
|
mkdirSync as mkdirSync2,
|
|
18
|
-
openSync,
|
|
19
|
-
readFileSync as
|
|
19
|
+
openSync as openSync2,
|
|
20
|
+
readFileSync as readFileSync4,
|
|
20
21
|
readdirSync as readdirSync2,
|
|
21
|
-
realpathSync,
|
|
22
|
+
realpathSync as realpathSync3,
|
|
22
23
|
renameSync,
|
|
23
24
|
rmSync as rmSync2,
|
|
24
|
-
statSync as
|
|
25
|
+
statSync as statSync4,
|
|
25
26
|
unlinkSync,
|
|
26
27
|
writeFileSync as writeFileSync2
|
|
27
28
|
} from "node:fs";
|
|
28
29
|
import * as net from "node:net";
|
|
29
30
|
import * as os from "node:os";
|
|
30
|
-
import * as
|
|
31
|
+
import * as path7 from "node:path";
|
|
32
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
33
|
+
|
|
34
|
+
// ../cli/src/performance-trace.ts
|
|
35
|
+
import { constants } from "node:fs";
|
|
36
|
+
import {
|
|
37
|
+
closeSync,
|
|
38
|
+
existsSync,
|
|
39
|
+
lstatSync,
|
|
40
|
+
openSync,
|
|
41
|
+
realpathSync,
|
|
42
|
+
statSync,
|
|
43
|
+
writeSync
|
|
44
|
+
} from "node:fs";
|
|
45
|
+
import path from "node:path";
|
|
46
|
+
var KIBI_PERF_TRACE_DIR_ENV = "KIBI_PERF_TRACE_DIR";
|
|
47
|
+
var KIBI_PERF_TIMINGS_ENV = "KIBI_PERF_TIMINGS";
|
|
48
|
+
var KIBI_PERF_TRACE_MAX_EVENTS = 5000;
|
|
49
|
+
var KIBI_PERF_TRACE_MAX_BYTES = 512 * 1024;
|
|
50
|
+
var states = new Map;
|
|
51
|
+
var MAX_DURATION_MS = 60000;
|
|
52
|
+
var TRACE_LIMIT_RESERVE_BYTES = 96;
|
|
53
|
+
function isWithin(parent, candidate) {
|
|
54
|
+
const relative = path.relative(parent, candidate);
|
|
55
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
56
|
+
}
|
|
57
|
+
function privateTraceFile() {
|
|
58
|
+
if (process.env[KIBI_PERF_TIMINGS_ENV] !== "1")
|
|
59
|
+
return null;
|
|
60
|
+
const configuredDirectory = process.env[KIBI_PERF_TRACE_DIR_ENV];
|
|
61
|
+
if (!configuredDirectory || !path.isAbsolute(configuredDirectory))
|
|
62
|
+
return null;
|
|
63
|
+
try {
|
|
64
|
+
const directoryInfo = lstatSync(configuredDirectory);
|
|
65
|
+
if (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink()) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const directory = realpathSync(configuredDirectory);
|
|
69
|
+
if (path.resolve(configuredDirectory) !== directory)
|
|
70
|
+
return null;
|
|
71
|
+
const directoryStats = statSync(directory);
|
|
72
|
+
if (typeof process.getuid !== "function" || directoryStats.uid !== process.getuid()) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if ((directoryStats.mode & 63) !== 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const currentDirectory = realpathSync(process.cwd());
|
|
79
|
+
if (isWithin(currentDirectory, directory))
|
|
80
|
+
return null;
|
|
81
|
+
const file = path.join(directory, `kibi-performance-${process.pid}.jsonl`);
|
|
82
|
+
let state = states.get(file);
|
|
83
|
+
if (!state) {
|
|
84
|
+
if (existsSync(file))
|
|
85
|
+
return null;
|
|
86
|
+
state = { bytes: 0, events: 0, disabled: false };
|
|
87
|
+
states.set(file, state);
|
|
88
|
+
}
|
|
89
|
+
if (state.disabled)
|
|
90
|
+
return null;
|
|
91
|
+
return { file, state };
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function writeAll(fd, bytes) {
|
|
97
|
+
let offset = 0;
|
|
98
|
+
while (offset < bytes.length) {
|
|
99
|
+
const written = writeSync(fd, bytes, offset, bytes.length - offset);
|
|
100
|
+
if (written <= 0)
|
|
101
|
+
throw new Error("Performance trace write made no progress");
|
|
102
|
+
offset += written;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function appendLine(file, state, line, exclusive) {
|
|
106
|
+
const fileInfo = existsSync(file) ? lstatSync(file) : null;
|
|
107
|
+
if (exclusive && fileInfo !== null || !exclusive && (!fileInfo?.isFile() || fileInfo.isSymbolicLink() || fileInfo.size !== state.bytes)) {
|
|
108
|
+
throw new Error("Performance trace file changed unexpectedly");
|
|
109
|
+
}
|
|
110
|
+
const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_APPEND | (constants.O_NOFOLLOW ?? 0) | (exclusive ? constants.O_EXCL : 0);
|
|
111
|
+
const fd = openSync(file, flags, 384);
|
|
112
|
+
try {
|
|
113
|
+
const bytes = Buffer.from(line, "utf8");
|
|
114
|
+
writeAll(fd, bytes);
|
|
115
|
+
state.bytes += bytes.length;
|
|
116
|
+
} finally {
|
|
117
|
+
closeSync(fd);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function appendLimitMarker(file, state, limit) {
|
|
121
|
+
const marker = `${JSON.stringify({ kind: "trace-limit", pid: process.pid, limit })}
|
|
122
|
+
`;
|
|
123
|
+
const byteLength = Buffer.byteLength(marker, "utf8");
|
|
124
|
+
if (state.bytes + byteLength <= KIBI_PERF_TRACE_MAX_BYTES) {
|
|
125
|
+
appendLine(file, state, marker, state.events === 0);
|
|
126
|
+
}
|
|
127
|
+
state.disabled = true;
|
|
128
|
+
}
|
|
129
|
+
function writePerformanceTraceEvent(event) {
|
|
130
|
+
if (!Number.isFinite(event.durationMs) || event.durationMs < 0 || event.durationMs > MAX_DURATION_MS) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const trace = privateTraceFile();
|
|
134
|
+
if (!trace)
|
|
135
|
+
return;
|
|
136
|
+
const durationMs = Math.round(event.durationMs * 1000) / 1000;
|
|
137
|
+
const line = `${JSON.stringify({
|
|
138
|
+
kind: event.kind,
|
|
139
|
+
durationMs,
|
|
140
|
+
pid: process.pid
|
|
141
|
+
})}
|
|
142
|
+
`;
|
|
143
|
+
const byteLength = Buffer.byteLength(line, "utf8");
|
|
144
|
+
try {
|
|
145
|
+
if (trace.state.events >= KIBI_PERF_TRACE_MAX_EVENTS || trace.state.bytes + byteLength > KIBI_PERF_TRACE_MAX_BYTES - TRACE_LIMIT_RESERVE_BYTES) {
|
|
146
|
+
appendLimitMarker(trace.file, trace.state, trace.state.events >= KIBI_PERF_TRACE_MAX_EVENTS ? "events" : "bytes");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
appendLine(trace.file, trace.state, line, trace.state.events === 0);
|
|
150
|
+
trace.state.events += 1;
|
|
151
|
+
} catch {
|
|
152
|
+
trace.state.disabled = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
31
155
|
|
|
32
156
|
// ../cli/src/prolog.ts
|
|
33
157
|
import { spawn } from "node:child_process";
|
|
34
|
-
import { existsSync } from "node:fs";
|
|
158
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
35
159
|
import { createRequire as createRequire2 } from "node:module";
|
|
36
|
-
import
|
|
160
|
+
import path3 from "node:path";
|
|
161
|
+
import { performance } from "node:perf_hooks";
|
|
37
162
|
import { fileURLToPath } from "node:url";
|
|
38
163
|
|
|
39
164
|
// ../cli/src/env.ts
|
|
@@ -383,8 +508,336 @@ function unquoteAtom(value) {
|
|
|
383
508
|
return trimmed;
|
|
384
509
|
}
|
|
385
510
|
|
|
511
|
+
// ../cli/src/prolog/swipl-resolver.ts
|
|
512
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
513
|
+
import {
|
|
514
|
+
constants as constants2,
|
|
515
|
+
accessSync,
|
|
516
|
+
existsSync as existsSync2,
|
|
517
|
+
readFileSync,
|
|
518
|
+
statSync as statSync2
|
|
519
|
+
} from "node:fs";
|
|
520
|
+
import path2 from "node:path";
|
|
521
|
+
import {
|
|
522
|
+
BUNDLED_BINARY_PATH,
|
|
523
|
+
MANIFEST_FILE,
|
|
524
|
+
PLATFORM_PACKAGES,
|
|
525
|
+
locatePlatformPackage,
|
|
526
|
+
platformKey,
|
|
527
|
+
sha256File,
|
|
528
|
+
validateBuildManifest
|
|
529
|
+
} from "kibi-swipl";
|
|
530
|
+
var MINIMUM_SWIPL_MAJOR = 9;
|
|
531
|
+
|
|
532
|
+
class SwiplResolutionError extends Error {
|
|
533
|
+
code;
|
|
534
|
+
platform;
|
|
535
|
+
packageName;
|
|
536
|
+
constructor(code, message, platform, packageName) {
|
|
537
|
+
super(message);
|
|
538
|
+
this.name = "SwiplResolutionError";
|
|
539
|
+
this.code = code;
|
|
540
|
+
this.platform = platform;
|
|
541
|
+
this.packageName = packageName;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function detectGlibc() {
|
|
545
|
+
if (process.platform !== "linux")
|
|
546
|
+
return;
|
|
547
|
+
try {
|
|
548
|
+
const report = process.report?.getReport?.();
|
|
549
|
+
if (report !== undefined) {
|
|
550
|
+
return typeof report.header?.glibcVersionRuntime === "string";
|
|
551
|
+
}
|
|
552
|
+
} catch {}
|
|
553
|
+
return !["/lib/ld-musl-x86_64.so.1", "/lib/ld-musl-aarch64.so.1"].some((loader) => existsSync2(loader));
|
|
554
|
+
}
|
|
555
|
+
function defaultRunVersion(bin) {
|
|
556
|
+
return execFileSync(bin, ["--version"], {
|
|
557
|
+
encoding: "utf8",
|
|
558
|
+
timeout: 1e4,
|
|
559
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
function defaultDeps() {
|
|
563
|
+
return {
|
|
564
|
+
platform: process.platform,
|
|
565
|
+
arch: process.arch,
|
|
566
|
+
glibc: detectGlibc(),
|
|
567
|
+
locatePackage: (name) => locatePlatformPackage(name),
|
|
568
|
+
runVersion: defaultRunVersion
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function parseSwiplVersion(output) {
|
|
572
|
+
const match = /version\s+(\d+)\.(\d+)(?:\.(\d+))?/i.exec(output);
|
|
573
|
+
if (match === null)
|
|
574
|
+
return;
|
|
575
|
+
const [, major, minor, patch] = match;
|
|
576
|
+
return {
|
|
577
|
+
version: `${major}.${minor}${patch === undefined ? "" : `.${patch}`}`,
|
|
578
|
+
major: Number.parseInt(major ?? "0", 10)
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function installCommand(platform) {
|
|
582
|
+
if (platform === "linux") {
|
|
583
|
+
return "sudo apt-add-repository ppa:swi-prolog/stable && sudo apt-get update && sudo apt-get install swi-prolog";
|
|
584
|
+
}
|
|
585
|
+
if (platform === "darwin")
|
|
586
|
+
return "brew install swi-prolog";
|
|
587
|
+
return "download the installer from https://www.swi-prolog.org/download/stable and add swipl to PATH";
|
|
588
|
+
}
|
|
589
|
+
function hostLabel(deps) {
|
|
590
|
+
const libc = deps.platform === "linux" && deps.glibc !== undefined ? ` (${deps.glibc ? "glibc" : "musl"})` : "";
|
|
591
|
+
return `${deps.platform}-${deps.arch}${libc}`;
|
|
592
|
+
}
|
|
593
|
+
function isExecutableFile(candidate) {
|
|
594
|
+
try {
|
|
595
|
+
if (!statSync2(candidate).isFile())
|
|
596
|
+
return false;
|
|
597
|
+
accessSync(candidate, constants2.X_OK);
|
|
598
|
+
return true;
|
|
599
|
+
} catch {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function inspectBundle(deps) {
|
|
604
|
+
const key = platformKey({
|
|
605
|
+
platform: deps.platform,
|
|
606
|
+
arch: deps.arch,
|
|
607
|
+
glibc: deps.glibc
|
|
608
|
+
});
|
|
609
|
+
if (key === undefined)
|
|
610
|
+
return { kind: "unsupported" };
|
|
611
|
+
const packageName = PLATFORM_PACKAGES[key].package;
|
|
612
|
+
const packageDir = deps.locatePackage(packageName);
|
|
613
|
+
if (packageDir === undefined)
|
|
614
|
+
return { kind: "missing", key, packageName };
|
|
615
|
+
const manifestPath = path2.join(packageDir, MANIFEST_FILE);
|
|
616
|
+
const binary = path2.join(packageDir, BUNDLED_BINARY_PATH);
|
|
617
|
+
if (!existsSync2(manifestPath)) {
|
|
618
|
+
if (!existsSync2(binary))
|
|
619
|
+
return { kind: "empty", key, packageName };
|
|
620
|
+
return {
|
|
621
|
+
kind: "corrupt",
|
|
622
|
+
key,
|
|
623
|
+
packageName,
|
|
624
|
+
reason: `${MANIFEST_FILE} is missing next to ${BUNDLED_BINARY_PATH}`
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
const corrupt = (reason) => ({
|
|
628
|
+
kind: "corrupt",
|
|
629
|
+
key,
|
|
630
|
+
packageName,
|
|
631
|
+
reason
|
|
632
|
+
});
|
|
633
|
+
let manifest;
|
|
634
|
+
let packageJson;
|
|
635
|
+
try {
|
|
636
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
637
|
+
} catch (error) {
|
|
638
|
+
return corrupt(`${MANIFEST_FILE} is not valid JSON (${error instanceof Error ? error.message : String(error)})`);
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
packageJson = JSON.parse(readFileSync(path2.join(packageDir, "package.json"), "utf8"));
|
|
642
|
+
} catch (error) {
|
|
643
|
+
return corrupt(`package.json is unreadable (${error instanceof Error ? error.message : String(error)})`);
|
|
644
|
+
}
|
|
645
|
+
const packageVersion = packageJson.kibi?.swiplVersion;
|
|
646
|
+
const checked = validateBuildManifest(manifest, {
|
|
647
|
+
target: key,
|
|
648
|
+
swiplVersion: typeof packageVersion === "string" ? packageVersion : undefined
|
|
649
|
+
});
|
|
650
|
+
if (!checked.ok)
|
|
651
|
+
return corrupt(checked.reason);
|
|
652
|
+
if (!isExecutableFile(binary)) {
|
|
653
|
+
return corrupt(`${BUNDLED_BINARY_PATH} is missing or not executable`);
|
|
654
|
+
}
|
|
655
|
+
let actual;
|
|
656
|
+
try {
|
|
657
|
+
actual = sha256File(binary);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
return corrupt(`${BUNDLED_BINARY_PATH} is unreadable (${error instanceof Error ? error.message : String(error)})`);
|
|
660
|
+
}
|
|
661
|
+
if (actual !== checked.binarySha256) {
|
|
662
|
+
return corrupt(`${BUNDLED_BINARY_PATH} SHA-256 ${actual} does not match the manifest (${checked.binarySha256})`);
|
|
663
|
+
}
|
|
664
|
+
const home = path2.join(packageDir, checked.home);
|
|
665
|
+
let homeIsDirectory = false;
|
|
666
|
+
try {
|
|
667
|
+
homeIsDirectory = statSync2(home).isDirectory();
|
|
668
|
+
} catch {}
|
|
669
|
+
if (!homeIsDirectory)
|
|
670
|
+
return corrupt(`${checked.home} is missing`);
|
|
671
|
+
return {
|
|
672
|
+
kind: "ok",
|
|
673
|
+
key,
|
|
674
|
+
packageName,
|
|
675
|
+
resolved: {
|
|
676
|
+
bin: binary,
|
|
677
|
+
home,
|
|
678
|
+
version: checked.swiplVersion,
|
|
679
|
+
source: "bundled"
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function probeVersion(bin, deps) {
|
|
684
|
+
let output;
|
|
685
|
+
try {
|
|
686
|
+
output = deps.runVersion(bin);
|
|
687
|
+
} catch (error) {
|
|
688
|
+
return {
|
|
689
|
+
kind: "unusable",
|
|
690
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
const parsed = parseSwiplVersion(output);
|
|
694
|
+
if (parsed === undefined) {
|
|
695
|
+
return {
|
|
696
|
+
kind: "unusable",
|
|
697
|
+
detail: `unrecognized --version output ${JSON.stringify(output.trim().slice(0, 80))}`
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (parsed.major < MINIMUM_SWIPL_MAJOR) {
|
|
701
|
+
return { kind: "too-old", version: parsed.version };
|
|
702
|
+
}
|
|
703
|
+
return { kind: "ok", version: parsed.version };
|
|
704
|
+
}
|
|
705
|
+
function findOnPath(env, deps) {
|
|
706
|
+
for (const directory of (env.PATH ?? "").split(path2.delimiter)) {
|
|
707
|
+
if (directory === "" || !path2.isAbsolute(directory))
|
|
708
|
+
continue;
|
|
709
|
+
const candidate = path2.join(directory, "swipl");
|
|
710
|
+
if (!isExecutableFile(candidate))
|
|
711
|
+
continue;
|
|
712
|
+
const probe = probeVersion(candidate, deps);
|
|
713
|
+
if (probe.kind === "ok") {
|
|
714
|
+
return {
|
|
715
|
+
kind: "ok",
|
|
716
|
+
resolved: { bin: candidate, version: probe.version, source: "path" }
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
return probe.kind === "too-old" ? { kind: "too-old", bin: candidate, version: probe.version } : { kind: "unusable", bin: candidate, detail: probe.detail };
|
|
720
|
+
}
|
|
721
|
+
return { kind: "absent" };
|
|
722
|
+
}
|
|
723
|
+
function resolveFromEnv(override, deps) {
|
|
724
|
+
const platform = hostLabel(deps);
|
|
725
|
+
const key = platformKey({
|
|
726
|
+
platform: deps.platform,
|
|
727
|
+
arch: deps.arch,
|
|
728
|
+
glibc: deps.glibc
|
|
729
|
+
});
|
|
730
|
+
const packageName = key === undefined ? undefined : PLATFORM_PACKAGES[key].package;
|
|
731
|
+
const invalid = (detail) => new SwiplResolutionError("swipl_env_invalid", `KIBI_SWIPL=${override} ${detail}. Set KIBI_SWIPL to the absolute path of a SWI-Prolog ${MINIMUM_SWIPL_MAJOR}.0+ executable, to "system" to use swipl from PATH, or unset it to use the bundled runtime.`, platform, packageName);
|
|
732
|
+
if (!path2.isAbsolute(override))
|
|
733
|
+
return fail(invalid("is not an absolute path"));
|
|
734
|
+
if (!isExecutableFile(override)) {
|
|
735
|
+
return fail(invalid("is not an executable file"));
|
|
736
|
+
}
|
|
737
|
+
const probe = probeVersion(override, deps);
|
|
738
|
+
if (probe.kind === "unusable") {
|
|
739
|
+
return fail(invalid(`could not report its version (${probe.detail})`));
|
|
740
|
+
}
|
|
741
|
+
if (probe.kind === "too-old") {
|
|
742
|
+
return fail(invalid(`is SWI-Prolog ${probe.version}, but ${MINIMUM_SWIPL_MAJOR}.0 or newer is required`));
|
|
743
|
+
}
|
|
744
|
+
return { bin: override, version: probe.version, source: "env" };
|
|
745
|
+
}
|
|
746
|
+
function fail(error) {
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
function notFoundError(deps, bundle, lookup) {
|
|
750
|
+
const platform = hostLabel(deps);
|
|
751
|
+
const command = installCommand(deps.platform);
|
|
752
|
+
const lines = [
|
|
753
|
+
`Kibi could not find a usable SWI-Prolog (${MINIMUM_SWIPL_MAJOR}.0 or newer is required).`,
|
|
754
|
+
`Detected platform: ${platform}.`
|
|
755
|
+
];
|
|
756
|
+
let packageName;
|
|
757
|
+
if (bundle === "skipped") {
|
|
758
|
+
lines.push("Bundled SWI-Prolog: skipped because KIBI_SWIPL=system.");
|
|
759
|
+
const key = platformKey({
|
|
760
|
+
platform: deps.platform,
|
|
761
|
+
arch: deps.arch,
|
|
762
|
+
glibc: deps.glibc
|
|
763
|
+
});
|
|
764
|
+
if (key !== undefined)
|
|
765
|
+
packageName = PLATFORM_PACKAGES[key].package;
|
|
766
|
+
} else if (bundle.kind === "unsupported") {
|
|
767
|
+
lines.push(`Bundled SWI-Prolog: no bundled build exists for ${platform} (supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}).`);
|
|
768
|
+
} else {
|
|
769
|
+
packageName = bundle.packageName;
|
|
770
|
+
if (bundle.kind === "empty") {
|
|
771
|
+
lines.push(`Bundled SWI-Prolog: package ${bundle.packageName} is installed but carries no runtime (a source checkout that was not populated from a release build).`);
|
|
772
|
+
} else {
|
|
773
|
+
lines.push(`Bundled SWI-Prolog: package ${bundle.packageName} would cover this platform but is not installed. It is skipped by installs that omit optional dependencies (npm --omit=optional, --no-optional) and by pnpm supportedArchitectures settings that exclude this platform.`);
|
|
774
|
+
lines.push(` Add it with: npm install --save-dev ${bundle.packageName}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (bundle === "skipped" && packageName !== undefined) {
|
|
778
|
+
lines.push(` To use it, unset KIBI_SWIPL and install ${packageName}.`);
|
|
779
|
+
}
|
|
780
|
+
switch (lookup.kind) {
|
|
781
|
+
case "absent":
|
|
782
|
+
lines.push("System SWI-Prolog: swipl was not found on PATH.");
|
|
783
|
+
break;
|
|
784
|
+
case "too-old":
|
|
785
|
+
lines.push(`System SWI-Prolog: swipl on PATH (${lookup.bin}) is version ${lookup.version}, older than ${MINIMUM_SWIPL_MAJOR}.0.`);
|
|
786
|
+
break;
|
|
787
|
+
case "unusable":
|
|
788
|
+
lines.push(`System SWI-Prolog: swipl on PATH (${lookup.bin}) could not report its version (${lookup.detail}).`);
|
|
789
|
+
break;
|
|
790
|
+
case "ok":
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
lines.push(` Install it with: ${command}`);
|
|
794
|
+
lines.push(`Or set KIBI_SWIPL to the absolute path of a SWI-Prolog ${MINIMUM_SWIPL_MAJOR}.0+ executable.`);
|
|
795
|
+
return new SwiplResolutionError("swipl_not_found", lines.join(`
|
|
796
|
+
`), platform, packageName);
|
|
797
|
+
}
|
|
798
|
+
function resolveSwiplWith(env, deps) {
|
|
799
|
+
const override = env.KIBI_SWIPL?.trim() ?? "";
|
|
800
|
+
if (override !== "" && override !== "system") {
|
|
801
|
+
return resolveFromEnv(override, deps);
|
|
802
|
+
}
|
|
803
|
+
let bundle = "skipped";
|
|
804
|
+
if (override === "") {
|
|
805
|
+
bundle = inspectBundle(deps);
|
|
806
|
+
if (bundle.kind === "ok")
|
|
807
|
+
return bundle.resolved;
|
|
808
|
+
if (bundle.kind === "corrupt") {
|
|
809
|
+
throw new SwiplResolutionError("swipl_bundle_corrupt", [
|
|
810
|
+
`The bundled SWI-Prolog package ${bundle.packageName} is damaged: ${bundle.reason}.`,
|
|
811
|
+
`Detected platform: ${hostLabel(deps)}.`,
|
|
812
|
+
`Reinstall it (for example: npm install --save-dev ${bundle.packageName}), or set KIBI_SWIPL=system to use swipl from PATH instead.`
|
|
813
|
+
].join(`
|
|
814
|
+
`), hostLabel(deps), bundle.packageName);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const lookup = findOnPath(env, deps);
|
|
818
|
+
if (lookup.kind === "ok")
|
|
819
|
+
return lookup.resolved;
|
|
820
|
+
throw notFoundError(deps, bundle, lookup);
|
|
821
|
+
}
|
|
822
|
+
var cache = new Map;
|
|
823
|
+
function resolveSwipl(env = process.env) {
|
|
824
|
+
const key = `${env.KIBI_SWIPL ?? ""}\x00${env.PATH ?? ""}`;
|
|
825
|
+
const cached = cache.get(key);
|
|
826
|
+
if (cached !== undefined)
|
|
827
|
+
return cached;
|
|
828
|
+
const resolved = resolveSwiplWith(env, defaultDeps());
|
|
829
|
+
cache.set(key, resolved);
|
|
830
|
+
return resolved;
|
|
831
|
+
}
|
|
832
|
+
function swiplIdentity(resolved) {
|
|
833
|
+
return `${resolved.bin}@${resolved.version}`;
|
|
834
|
+
}
|
|
835
|
+
function swiplChildEnv(resolved) {
|
|
836
|
+
return resolved.home === undefined ? {} : { SWI_HOME_DIR: resolved.home };
|
|
837
|
+
}
|
|
838
|
+
|
|
386
839
|
// ../cli/src/prolog.ts
|
|
387
|
-
var importMetaDir =
|
|
840
|
+
var importMetaDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
388
841
|
var DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
389
842
|
var PROLOG_OUTPUT_OVERFLOW_ERROR = "Query exceeded bounded Prolog output capacity (ENOBUFS); narrow the operation or reduce stored entity size";
|
|
390
843
|
var INTERACTIVE_QUERY_FRAME_END = "__KIBI_QUERY_FRAME_END__";
|
|
@@ -398,9 +851,9 @@ function packageVersion(packagePath) {
|
|
|
398
851
|
}
|
|
399
852
|
}
|
|
400
853
|
var KIBI_PACKAGE_VERSIONS = [
|
|
401
|
-
`kibi-cli@${packageVersion(
|
|
402
|
-
`kibi-core@${packageVersion(
|
|
403
|
-
`kibi-mcp@${packageVersion(
|
|
854
|
+
`kibi-cli@${packageVersion(path3.join(importMetaDir, "..", "package.json"))}`,
|
|
855
|
+
`kibi-core@${packageVersion(path3.join(importMetaDir, "..", "..", "core", "package.json"))}`,
|
|
856
|
+
`kibi-mcp@${packageVersion(path3.join(importMetaDir, "..", "..", "mcp", "package.json"))}`
|
|
404
857
|
].join(",");
|
|
405
858
|
function resolveKbPlPath() {
|
|
406
859
|
const overrideKbPath = getKbPlPathOverride();
|
|
@@ -412,17 +865,17 @@ function resolveKbPlPath() {
|
|
|
412
865
|
return require2.resolve("kibi-core/src/kb.pl");
|
|
413
866
|
} catch {
|
|
414
867
|
const coreMain = require2.resolve("kibi-core");
|
|
415
|
-
const coreDir =
|
|
416
|
-
return
|
|
868
|
+
const coreDir = path3.dirname(coreMain);
|
|
869
|
+
return path3.join(coreDir, "src", "kb.pl");
|
|
417
870
|
}
|
|
418
871
|
} catch {}
|
|
419
872
|
let currentDir = importMetaDir;
|
|
420
|
-
while (currentDir !==
|
|
421
|
-
const candidate =
|
|
422
|
-
if (
|
|
873
|
+
while (currentDir !== path3.dirname(currentDir)) {
|
|
874
|
+
const candidate = path3.join(currentDir, "packages", "core", "src", "kb.pl");
|
|
875
|
+
if (existsSync3(candidate)) {
|
|
423
876
|
return candidate;
|
|
424
877
|
}
|
|
425
|
-
currentDir =
|
|
878
|
+
currentDir = path3.dirname(currentDir);
|
|
426
879
|
}
|
|
427
880
|
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
881
|
}
|
|
@@ -450,7 +903,8 @@ function bindProcessExitHandler(current, terminate, on = (event, listener) => pr
|
|
|
450
903
|
|
|
451
904
|
class PrologProcess {
|
|
452
905
|
process = null;
|
|
453
|
-
|
|
906
|
+
explicitSwiplPath;
|
|
907
|
+
swiplResolver;
|
|
454
908
|
timeout;
|
|
455
909
|
outputBuffer = "";
|
|
456
910
|
outputBufferBytes = 0;
|
|
@@ -468,7 +922,8 @@ class PrologProcess {
|
|
|
468
922
|
interactiveStarted = false;
|
|
469
923
|
terminationReason = null;
|
|
470
924
|
constructor(options = {}) {
|
|
471
|
-
this.
|
|
925
|
+
this.explicitSwiplPath = options.swiplPath || undefined;
|
|
926
|
+
this.swiplResolver = options.swiplResolver ?? (() => resolveSwipl());
|
|
472
927
|
this.timeout = options.timeout || 30000;
|
|
473
928
|
this.maxOutputBytes = options.maxOutputBytes !== undefined && Number.isInteger(options.maxOutputBytes) && options.maxOutputBytes > 0 ? options.maxOutputBytes : DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES;
|
|
474
929
|
this.useOneShotMode = options.oneShot ?? false;
|
|
@@ -482,15 +937,24 @@ class PrologProcess {
|
|
|
482
937
|
needsRestart() {
|
|
483
938
|
return this.interactiveStarted && !this.isProcessUsable();
|
|
484
939
|
}
|
|
940
|
+
launchTarget() {
|
|
941
|
+
const explicit = this.explicitSwiplPath;
|
|
942
|
+
if (explicit !== undefined)
|
|
943
|
+
return { bin: explicit, env: {} };
|
|
944
|
+
const resolved = this.swiplResolver();
|
|
945
|
+
return { bin: resolved.bin, env: swiplChildEnv(resolved) };
|
|
946
|
+
}
|
|
485
947
|
async start() {
|
|
486
|
-
|
|
487
|
-
|
|
948
|
+
const explicit = this.explicitSwiplPath;
|
|
949
|
+
if (explicit !== undefined && !existsSync3(explicit) && explicit !== "swipl") {
|
|
950
|
+
throw new Error(`SWI-Prolog not found at ${explicit}. Please install SWI-Prolog or check your PATH.`);
|
|
488
951
|
}
|
|
952
|
+
const launch = this.launchTarget();
|
|
489
953
|
const kbPath = resolveKbPlPath();
|
|
490
954
|
this.interactiveStarted = true;
|
|
491
955
|
this.terminationReason = null;
|
|
492
956
|
this.invalidateCache();
|
|
493
|
-
this.process = spawn(
|
|
957
|
+
this.process = spawn(launch.bin, [
|
|
494
958
|
"-g",
|
|
495
959
|
`use_module('${kbPath}'), use_module(library(semweb/rdf_db)), set_prolog_flag(answer_write_options, [max_depth(0), quoted(true)])`,
|
|
496
960
|
"--quiet"
|
|
@@ -498,6 +962,7 @@ class PrologProcess {
|
|
|
498
962
|
detached: process.platform !== "win32",
|
|
499
963
|
env: {
|
|
500
964
|
...process.env,
|
|
965
|
+
...launch.env,
|
|
501
966
|
KIBI_RUNTIME_NAME: process.versions.bun !== undefined ? "bun" : "node",
|
|
502
967
|
KIBI_RUNTIME_VERSION: process.versions.bun ?? process.versions.node ?? "unknown",
|
|
503
968
|
KIBI_PACKAGE_VERSIONS: process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS
|
|
@@ -546,12 +1011,22 @@ class PrologProcess {
|
|
|
546
1011
|
this.clearQueryBuffers();
|
|
547
1012
|
}
|
|
548
1013
|
async query(goal) {
|
|
1014
|
+
if (!this.useOneShotMode && this.needsRestart()) {
|
|
1015
|
+
throw this.lostProcessError();
|
|
1016
|
+
}
|
|
549
1017
|
const isSingleGoal = typeof goal === "string";
|
|
550
1018
|
const goalKey = isSingleGoal ? goal : null;
|
|
551
1019
|
const cacheable = goalKey !== null && this.isCacheableGoal(goalKey);
|
|
552
1020
|
if (cacheable) {
|
|
1021
|
+
const cacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
|
|
553
1022
|
const cachedResult = this.cache.get(goalKey);
|
|
554
1023
|
if (cachedResult) {
|
|
1024
|
+
if (cacheLookupStartedAt !== null) {
|
|
1025
|
+
writePerformanceTraceEvent({
|
|
1026
|
+
kind: "prolog-process-cache-hit",
|
|
1027
|
+
durationMs: performance.now() - cacheLookupStartedAt
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
555
1030
|
return cachedResult;
|
|
556
1031
|
}
|
|
557
1032
|
}
|
|
@@ -696,12 +1171,22 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
696
1171
|
releaseQuery = resolve;
|
|
697
1172
|
});
|
|
698
1173
|
await previousQuery;
|
|
1174
|
+
const tracingEnabled = process.env.KIBI_PERF_TIMINGS === "1";
|
|
1175
|
+
let roundTripStartedAt = null;
|
|
699
1176
|
try {
|
|
700
1177
|
if (!this.isProcessUsable()) {
|
|
701
1178
|
throw this.lostProcessError();
|
|
702
1179
|
}
|
|
1180
|
+
if (tracingEnabled)
|
|
1181
|
+
roundTripStartedAt = performance.now();
|
|
703
1182
|
return await runInteractiveQuery();
|
|
704
1183
|
} finally {
|
|
1184
|
+
if (roundTripStartedAt !== null) {
|
|
1185
|
+
writePerformanceTraceEvent({
|
|
1186
|
+
kind: "prolog-round-trip",
|
|
1187
|
+
durationMs: performance.now() - roundTripStartedAt
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
705
1190
|
releaseQuery();
|
|
706
1191
|
}
|
|
707
1192
|
}
|
|
@@ -783,10 +1268,12 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
783
1268
|
const packageVersions = process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS;
|
|
784
1269
|
let child;
|
|
785
1270
|
try {
|
|
786
|
-
|
|
1271
|
+
const launch = this.launchTarget();
|
|
1272
|
+
child = spawn(launch.bin, ["-q", "-g", prologGoal, "-t", "halt"], {
|
|
787
1273
|
detached: process.platform !== "win32",
|
|
788
1274
|
env: {
|
|
789
1275
|
...process.env,
|
|
1276
|
+
...launch.env,
|
|
790
1277
|
KIBI_GOAL: combinedGoal,
|
|
791
1278
|
KIBI_RUNTIME_NAME: runtimeName,
|
|
792
1279
|
KIBI_RUNTIME_VERSION: runtimeVersion,
|
|
@@ -1042,6 +1529,18 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1042
1529
|
return { message: this.translateError(errorText) };
|
|
1043
1530
|
}
|
|
1044
1531
|
translateError(errorText) {
|
|
1532
|
+
const existence = errorText.match(/^__KIBI_ERROR__:error\(existence_error\((key|procedure),([^,()\r\n]+)(?:,|\),)/m);
|
|
1533
|
+
const identifier = existence?.[2]?.trim().replace(/^'([^']+)'$/, "$1");
|
|
1534
|
+
if (existence?.[1] === "key" && identifier !== undefined) {
|
|
1535
|
+
if (/^[A-Za-z][A-Za-z0-9_-]{0,95}$/.test(identifier)) {
|
|
1536
|
+
return `Predicate or file not found (missing dictionary key: ${identifier})`;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (existence?.[1] === "procedure" && identifier !== undefined) {
|
|
1540
|
+
if (/^(?:[A-Za-z][A-Za-z0-9_]{0,95}:)?[A-Za-z][A-Za-z0-9_]{0,95}\/[0-9]{1,3}$/.test(identifier)) {
|
|
1541
|
+
return `Predicate or file not found (missing procedure: ${identifier})`;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1045
1544
|
const cleanError = errorText.replace(/^__KIBI_(?:STAGE|RUNTIME|ERROR)__:[^\r\n]*\r?\n?/gm, "");
|
|
1046
1545
|
if (cleanError.includes("stale_snapshot")) {
|
|
1047
1546
|
return "KB snapshot is stale; reattach or refresh the runtime before retrying (stale_snapshot)";
|
|
@@ -1072,6 +1571,8 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1072
1571
|
if (cleanError.includes("existence_error") || cleanError.includes("Unknown procedure")) {
|
|
1073
1572
|
return "Predicate or file not found";
|
|
1074
1573
|
}
|
|
1574
|
+
if (existence !== null)
|
|
1575
|
+
return "Predicate or file not found";
|
|
1075
1576
|
if (cleanError.includes("permission_error")) {
|
|
1076
1577
|
return "Access denied or KB locked";
|
|
1077
1578
|
}
|
|
@@ -1131,8 +1632,8 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1131
1632
|
}
|
|
1132
1633
|
|
|
1133
1634
|
// ../cli/src/prolog/store-lock.ts
|
|
1134
|
-
import { existsSync as
|
|
1135
|
-
import { readFileSync } from "node:fs";
|
|
1635
|
+
import { existsSync as existsSync4, rmSync } from "node:fs";
|
|
1636
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1136
1637
|
import { dirname, join } from "node:path";
|
|
1137
1638
|
var cachedBootId;
|
|
1138
1639
|
function currentBootId() {
|
|
@@ -1143,7 +1644,7 @@ function currentBootId() {
|
|
|
1143
1644
|
return cachedBootId;
|
|
1144
1645
|
}
|
|
1145
1646
|
try {
|
|
1146
|
-
const raw =
|
|
1647
|
+
const raw = readFileSync2("/proc/sys/kernel/random/boot_id", "utf8");
|
|
1147
1648
|
cachedBootId = raw.trim();
|
|
1148
1649
|
} catch {
|
|
1149
1650
|
cachedBootId = null;
|
|
@@ -1182,7 +1683,7 @@ function breakStoreLock(rdfDirectory) {
|
|
|
1182
1683
|
join(dirname(rdfDirectory), ".kibi-lock-owner.json")
|
|
1183
1684
|
];
|
|
1184
1685
|
for (const candidate of candidates) {
|
|
1185
|
-
if (
|
|
1686
|
+
if (existsSync4(candidate)) {
|
|
1186
1687
|
rmSync(candidate, { force: true });
|
|
1187
1688
|
}
|
|
1188
1689
|
}
|
|
@@ -1236,31 +1737,31 @@ import {
|
|
|
1236
1737
|
execFileSync as rawExecFileSync,
|
|
1237
1738
|
execSync as rawExecSync
|
|
1238
1739
|
} from "node:child_process";
|
|
1239
|
-
import * as
|
|
1740
|
+
import * as path5 from "node:path";
|
|
1240
1741
|
|
|
1241
1742
|
// ../cli/src/utils/branch-store-locator.ts
|
|
1242
1743
|
import { createHash } from "node:crypto";
|
|
1243
1744
|
import {
|
|
1244
|
-
existsSync as
|
|
1745
|
+
existsSync as existsSync5,
|
|
1245
1746
|
mkdirSync,
|
|
1246
|
-
readFileSync as
|
|
1747
|
+
readFileSync as readFileSync3,
|
|
1247
1748
|
readdirSync,
|
|
1248
|
-
statSync,
|
|
1749
|
+
statSync as statSync3,
|
|
1249
1750
|
writeFileSync
|
|
1250
1751
|
} from "node:fs";
|
|
1251
|
-
import
|
|
1752
|
+
import path4 from "node:path";
|
|
1252
1753
|
var BRANCH_STORE_MANIFEST_VERSION = 1;
|
|
1253
1754
|
function branchStoreKey(branch) {
|
|
1254
1755
|
return createHash("sha256").update(branch, "utf8").digest("hex");
|
|
1255
1756
|
}
|
|
1256
1757
|
function branchStoresRoot(workspaceRoot) {
|
|
1257
|
-
return
|
|
1758
|
+
return path4.join(path4.resolve(workspaceRoot), ".kb", "branches");
|
|
1258
1759
|
}
|
|
1259
1760
|
function branchStorePath(workspaceRoot, branch) {
|
|
1260
|
-
return
|
|
1761
|
+
return path4.join(branchStoresRoot(workspaceRoot), branchStoreKey(branch));
|
|
1261
1762
|
}
|
|
1262
1763
|
function branchStoreManifestPath(storePath) {
|
|
1263
|
-
return
|
|
1764
|
+
return path4.join(storePath, "branch.json");
|
|
1264
1765
|
}
|
|
1265
1766
|
function expectedBranchStoreManifest(branch) {
|
|
1266
1767
|
return {
|
|
@@ -1271,10 +1772,10 @@ function expectedBranchStoreManifest(branch) {
|
|
|
1271
1772
|
}
|
|
1272
1773
|
function readBranchStoreManifest(storePath) {
|
|
1273
1774
|
const manifestPath = branchStoreManifestPath(storePath);
|
|
1274
|
-
if (!
|
|
1775
|
+
if (!existsSync5(manifestPath))
|
|
1275
1776
|
return null;
|
|
1276
1777
|
try {
|
|
1277
|
-
const parsed = JSON.parse(
|
|
1778
|
+
const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
1278
1779
|
if (parsed.version !== BRANCH_STORE_MANIFEST_VERSION || typeof parsed.branch !== "string" || typeof parsed.key !== "string") {
|
|
1279
1780
|
return null;
|
|
1280
1781
|
}
|
|
@@ -1290,7 +1791,7 @@ function readBranchStoreManifest(storePath) {
|
|
|
1290
1791
|
function branchStoreManifestMatches(storePath, branch) {
|
|
1291
1792
|
const manifest = readBranchStoreManifest(storePath);
|
|
1292
1793
|
const expected = expectedBranchStoreManifest(branch);
|
|
1293
|
-
return manifest !== null && manifest.version === expected.version && manifest.branch === expected.branch && manifest.key === expected.key &&
|
|
1794
|
+
return manifest !== null && manifest.version === expected.version && manifest.branch === expected.branch && manifest.key === expected.key && path4.basename(storePath) === expected.key;
|
|
1294
1795
|
}
|
|
1295
1796
|
function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
1296
1797
|
const storePath = branchStorePath(workspaceRoot, branch);
|
|
@@ -1301,7 +1802,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
|
1301
1802
|
throw new Error(`Branch store identity mismatch at ${storePath}; refusing to attach it to '${branch}'.`);
|
|
1302
1803
|
}
|
|
1303
1804
|
if (existing === null) {
|
|
1304
|
-
if (
|
|
1805
|
+
if (existsSync5(manifestPath)) {
|
|
1305
1806
|
throw new Error(`Branch store manifest is invalid at ${manifestPath}; refusing to overwrite it.`);
|
|
1306
1807
|
}
|
|
1307
1808
|
const entries = readdirSync(storePath);
|
|
@@ -1315,7 +1816,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
|
1315
1816
|
}
|
|
1316
1817
|
|
|
1317
1818
|
// ../cli/src/utils/branch-resolver.ts
|
|
1318
|
-
var
|
|
1819
|
+
var defaultDeps2 = {
|
|
1319
1820
|
execSync: rawExecSync,
|
|
1320
1821
|
execFileSync: rawExecFileSync
|
|
1321
1822
|
};
|
|
@@ -1335,7 +1836,7 @@ function isValidBranchName(name) {
|
|
|
1335
1836
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
1336
1837
|
return codePoint <= 32 || codePoint === 127;
|
|
1337
1838
|
});
|
|
1338
|
-
if (name.includes("..") ||
|
|
1839
|
+
if (name.includes("..") || path5.isAbsolute(name) || name.startsWith("/")) {
|
|
1339
1840
|
return false;
|
|
1340
1841
|
}
|
|
1341
1842
|
if (name.includes("//") || name.endsWith("/") || name.endsWith(".") || name.includes("\\") || name.startsWith("-") || name.includes("@{") || name.includes("..") || hasControlCharacter || /[~^:?*\[\]]/.test(name)) {
|
|
@@ -1346,7 +1847,7 @@ function isValidBranchName(name) {
|
|
|
1346
1847
|
return false;
|
|
1347
1848
|
}
|
|
1348
1849
|
try {
|
|
1349
|
-
|
|
1850
|
+
defaultDeps2.execFileSync("git", ["check-ref-format", "--branch", name], {
|
|
1350
1851
|
stdio: ["ignore", "ignore", "ignore"],
|
|
1351
1852
|
timeout: 5000
|
|
1352
1853
|
});
|
|
@@ -1356,10 +1857,43 @@ function isValidBranchName(name) {
|
|
|
1356
1857
|
}
|
|
1357
1858
|
}
|
|
1358
1859
|
|
|
1860
|
+
// ../cli/src/utils/canonical-path.ts
|
|
1861
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "node:fs";
|
|
1862
|
+
import path6 from "node:path";
|
|
1863
|
+
function canonicalFilesystemPath(candidate) {
|
|
1864
|
+
let existing = path6.resolve(candidate);
|
|
1865
|
+
const missing = [];
|
|
1866
|
+
for (;; ) {
|
|
1867
|
+
try {
|
|
1868
|
+
return path6.resolve(realpathSync2.native(existing), ...missing);
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
if (error.code !== "ENOENT")
|
|
1871
|
+
throw error;
|
|
1872
|
+
let entry;
|
|
1873
|
+
try {
|
|
1874
|
+
entry = lstatSync2(existing);
|
|
1875
|
+
} catch (statError) {
|
|
1876
|
+
if (statError.code !== "ENOENT")
|
|
1877
|
+
throw statError;
|
|
1878
|
+
}
|
|
1879
|
+
if (entry !== undefined)
|
|
1880
|
+
throw error;
|
|
1881
|
+
const parent = path6.dirname(existing);
|
|
1882
|
+
if (parent === existing)
|
|
1883
|
+
throw error;
|
|
1884
|
+
missing.unshift(path6.basename(existing));
|
|
1885
|
+
existing = parent;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1359
1890
|
// ../cli/src/engine.ts
|
|
1360
1891
|
var ENGINE_PROTOCOL_VERSION = 1;
|
|
1361
1892
|
var ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1362
1893
|
var ENGINE_PACKAGE_VERSIONS = process.env.KIBI_PACKAGE_VERSIONS ?? "unknown";
|
|
1894
|
+
function engineSwiplIdentity() {
|
|
1895
|
+
return swiplIdentity(resolveSwipl());
|
|
1896
|
+
}
|
|
1363
1897
|
var ENGINE_QUERY_CACHE_MAX_ENTRIES = 128;
|
|
1364
1898
|
var ENGINE_QUERY_CACHE_MAX_RESULT_BYTES = 8 * 1024 * 1024;
|
|
1365
1899
|
var ENGINE_FRESHNESS_CACHE_MS = 100;
|
|
@@ -1370,18 +1904,18 @@ function requestEngineSignalShutdown(shutdown) {
|
|
|
1370
1904
|
};
|
|
1371
1905
|
}
|
|
1372
1906
|
function readJournalGeneration(branchPath) {
|
|
1373
|
-
const currentPath =
|
|
1374
|
-
if (!
|
|
1907
|
+
const currentPath = path7.join(branchPath, "CURRENT");
|
|
1908
|
+
if (!existsSync6(currentPath))
|
|
1375
1909
|
return "";
|
|
1376
|
-
return
|
|
1910
|
+
return readFileSync4(currentPath, "utf8").trim();
|
|
1377
1911
|
}
|
|
1378
1912
|
function readEngineAttachmentIdentity(branchPath) {
|
|
1379
|
-
if (!
|
|
1913
|
+
if (!existsSync6(branchPath))
|
|
1380
1914
|
return null;
|
|
1381
|
-
const stats =
|
|
1382
|
-
let resolved =
|
|
1915
|
+
const stats = statSync4(branchPath);
|
|
1916
|
+
let resolved = path7.resolve(branchPath);
|
|
1383
1917
|
try {
|
|
1384
|
-
resolved =
|
|
1918
|
+
resolved = realpathSync3(branchPath);
|
|
1385
1919
|
} catch {}
|
|
1386
1920
|
return {
|
|
1387
1921
|
path: resolved,
|
|
@@ -1513,42 +2047,85 @@ function parseFrames(buffer, onFrame) {
|
|
|
1513
2047
|
return buffer.subarray(cursor);
|
|
1514
2048
|
}
|
|
1515
2049
|
function runtimeDirectory() {
|
|
1516
|
-
const configured = process.env.KIBI_RUNTIME_DIR ?? process.env.XDG_RUNTIME_DIR ??
|
|
1517
|
-
|
|
2050
|
+
const configured = process.env.KIBI_RUNTIME_DIR ?? process.env.XDG_RUNTIME_DIR ?? path7.join(os.tmpdir(), "kibi-runtime");
|
|
2051
|
+
if (process.platform === "win32") {
|
|
2052
|
+
for (const candidate of [
|
|
2053
|
+
configured,
|
|
2054
|
+
path7.join(os.tmpdir(), "kibi-runtime")
|
|
2055
|
+
]) {
|
|
2056
|
+
try {
|
|
2057
|
+
mkdirSync2(candidate, { recursive: true, mode: 448 });
|
|
2058
|
+
const probe = path7.join(candidate, `.kibi-write-${process.pid}`);
|
|
2059
|
+
writeFileSync2(probe, "", { mode: 384 });
|
|
2060
|
+
unlinkSync(probe);
|
|
2061
|
+
return candidate;
|
|
2062
|
+
} catch {}
|
|
2063
|
+
}
|
|
2064
|
+
throw new Error("Unable to create a writable Kibi engine runtime directory");
|
|
2065
|
+
}
|
|
2066
|
+
const euid = process.geteuid?.();
|
|
2067
|
+
if (euid === undefined)
|
|
2068
|
+
throw new Error("Kibi engine runtime euid unavailable");
|
|
2069
|
+
let configuredIdentity;
|
|
2070
|
+
try {
|
|
2071
|
+
configuredIdentity = canonicalFilesystemPath(configured);
|
|
2072
|
+
} catch {
|
|
2073
|
+
configuredIdentity = path7.resolve(configured);
|
|
2074
|
+
}
|
|
2075
|
+
const configuredKey = createHash2("sha256").update(configuredIdentity).digest("hex").slice(0, 32);
|
|
2076
|
+
const candidates = [
|
|
2077
|
+
configured,
|
|
2078
|
+
path7.join("/tmp", `kb-${euid}-${configuredKey}`)
|
|
2079
|
+
];
|
|
2080
|
+
const maximumBytes = process.platform === "linux" ? 107 : 103;
|
|
2081
|
+
const socketName = `kibi-${"0".repeat(32)}.sock`;
|
|
1518
2082
|
for (const candidate of candidates) {
|
|
1519
2083
|
try {
|
|
2084
|
+
try {
|
|
2085
|
+
if (lstatSync3(candidate).isSymbolicLink())
|
|
2086
|
+
continue;
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
if (error.code !== "ENOENT")
|
|
2089
|
+
throw error;
|
|
2090
|
+
}
|
|
1520
2091
|
mkdirSync2(candidate, { recursive: true, mode: 448 });
|
|
1521
|
-
const
|
|
1522
|
-
|
|
2092
|
+
const directory = lstatSync3(candidate);
|
|
2093
|
+
if (!directory.isDirectory() || directory.uid !== euid || (directory.mode & 63) !== 0)
|
|
2094
|
+
continue;
|
|
2095
|
+
const canonical = canonicalFilesystemPath(candidate);
|
|
2096
|
+
if (Buffer.byteLength(path7.join(canonical, socketName), "utf8") > maximumBytes)
|
|
2097
|
+
continue;
|
|
2098
|
+
const probe = path7.join(canonical, `.kibi-write-${randomUUID()}`);
|
|
2099
|
+
writeFileSync2(probe, "", { mode: 384, flag: "wx" });
|
|
1523
2100
|
unlinkSync(probe);
|
|
1524
|
-
return
|
|
2101
|
+
return canonical;
|
|
1525
2102
|
} catch {}
|
|
1526
2103
|
}
|
|
1527
2104
|
throw new Error("Unable to create a writable Kibi engine runtime directory");
|
|
1528
2105
|
}
|
|
1529
2106
|
function fsyncPath(filePath) {
|
|
1530
|
-
if (!
|
|
2107
|
+
if (!existsSync6(filePath))
|
|
1531
2108
|
return;
|
|
1532
2109
|
let descriptor = null;
|
|
1533
2110
|
try {
|
|
1534
|
-
descriptor =
|
|
2111
|
+
descriptor = openSync2(filePath, "r");
|
|
1535
2112
|
fsyncSync(descriptor);
|
|
1536
2113
|
} finally {
|
|
1537
2114
|
if (descriptor !== null)
|
|
1538
|
-
|
|
2115
|
+
closeSync2(descriptor);
|
|
1539
2116
|
}
|
|
1540
2117
|
}
|
|
1541
2118
|
function fsyncJournaledBranchStore(branchPath) {
|
|
1542
|
-
const rdfRoot =
|
|
2119
|
+
const rdfRoot = path7.join(branchPath, "rdf");
|
|
1543
2120
|
const directories = [];
|
|
1544
|
-
const pending =
|
|
2121
|
+
const pending = existsSync6(rdfRoot) ? [rdfRoot] : [];
|
|
1545
2122
|
while (pending.length > 0) {
|
|
1546
2123
|
const directory = pending.pop();
|
|
1547
2124
|
if (directory === undefined)
|
|
1548
2125
|
break;
|
|
1549
2126
|
directories.push(directory);
|
|
1550
2127
|
for (const entry of readdirSync2(directory, { withFileTypes: true })) {
|
|
1551
|
-
const entryPath =
|
|
2128
|
+
const entryPath = path7.join(directory, entry.name);
|
|
1552
2129
|
if (entry.isDirectory())
|
|
1553
2130
|
pending.push(entryPath);
|
|
1554
2131
|
else if (entry.isFile() && entry.name !== "lock")
|
|
@@ -1556,7 +2133,7 @@ function fsyncJournaledBranchStore(branchPath) {
|
|
|
1556
2133
|
}
|
|
1557
2134
|
}
|
|
1558
2135
|
for (const metadata of ["CURRENT", "storage.json", "kb.rdf"]) {
|
|
1559
|
-
fsyncPath(
|
|
2136
|
+
fsyncPath(path7.join(branchPath, metadata));
|
|
1560
2137
|
}
|
|
1561
2138
|
if (process.platform !== "win32") {
|
|
1562
2139
|
for (const directory of directories.reverse())
|
|
@@ -1565,33 +2142,27 @@ function fsyncJournaledBranchStore(branchPath) {
|
|
|
1565
2142
|
}
|
|
1566
2143
|
}
|
|
1567
2144
|
function engineSocketPath(workspaceRoot, branch) {
|
|
1568
|
-
const canonicalRoot = (
|
|
1569
|
-
try {
|
|
1570
|
-
return realpathSync(workspaceRoot);
|
|
1571
|
-
} catch {
|
|
1572
|
-
return path4.resolve(workspaceRoot);
|
|
1573
|
-
}
|
|
1574
|
-
})();
|
|
2145
|
+
const canonicalRoot = canonicalFilesystemPath(workspaceRoot);
|
|
1575
2146
|
const key = createHash2("sha256").update(`${canonicalRoot}\x00${branch}`).digest("hex").slice(0, 32);
|
|
1576
2147
|
if (process.platform === "win32") {
|
|
1577
2148
|
return `\\\\.\\pipe\\kibi-${key}`;
|
|
1578
2149
|
}
|
|
1579
|
-
return
|
|
2150
|
+
return path7.join(runtimeDirectory(), `kibi-${key}.sock`);
|
|
1580
2151
|
}
|
|
1581
2152
|
function enginePidPath(workspaceRoot, branch) {
|
|
1582
2153
|
const socket = engineSocketPath(workspaceRoot, branch);
|
|
1583
2154
|
if (process.platform === "win32") {
|
|
1584
|
-
return
|
|
2155
|
+
return path7.join(runtimeDirectory(), `kibi-${createHash2("sha256").update(`${path7.resolve(workspaceRoot)}\x00${branch}`).digest("hex")}.pid`);
|
|
1585
2156
|
}
|
|
1586
2157
|
return `${socket}.pid`;
|
|
1587
2158
|
}
|
|
1588
2159
|
function recoverStaleJournalLock(branchPath) {
|
|
1589
|
-
const lockPath =
|
|
1590
|
-
if (!
|
|
2160
|
+
const lockPath = path7.join(branchPath, "rdf", "lock");
|
|
2161
|
+
if (!existsSync6(lockPath))
|
|
1591
2162
|
return;
|
|
1592
2163
|
let contents = "";
|
|
1593
2164
|
try {
|
|
1594
|
-
contents =
|
|
2165
|
+
contents = readFileSync4(lockPath, "utf8");
|
|
1595
2166
|
} catch {
|
|
1596
2167
|
return;
|
|
1597
2168
|
}
|
|
@@ -1639,27 +2210,27 @@ function connectSocket(socketPath, timeoutMs) {
|
|
|
1639
2210
|
}
|
|
1640
2211
|
async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
1641
2212
|
mkdirSync2(branchPath, { recursive: true });
|
|
1642
|
-
const markerPath =
|
|
1643
|
-
const marker =
|
|
2213
|
+
const markerPath = path7.join(branchPath, "storage.json");
|
|
2214
|
+
const marker = existsSync6(markerPath) ? readFileSync4(markerPath, "utf8") : "";
|
|
1644
2215
|
if (marker.includes("kibi.rdf-journal.v1")) {
|
|
1645
|
-
const rdfDirectory =
|
|
1646
|
-
const currentPath =
|
|
1647
|
-
const sentinelPath =
|
|
1648
|
-
if (!
|
|
2216
|
+
const rdfDirectory = path7.join(branchPath, "rdf");
|
|
2217
|
+
const currentPath = path7.join(branchPath, "CURRENT");
|
|
2218
|
+
const sentinelPath = path7.join(branchPath, "kb.rdf");
|
|
2219
|
+
if (!existsSync6(rdfDirectory) || !existsSync6(currentPath)) {
|
|
1649
2220
|
recoverInterruptedGeneration(branchPath);
|
|
1650
|
-
if (
|
|
2221
|
+
if (existsSync6(rdfDirectory) && existsSync6(currentPath)) {
|
|
1651
2222
|
recoverStaleJournalLock(branchPath);
|
|
1652
2223
|
archiveLegacyFiles(branchPath);
|
|
1653
|
-
if (!
|
|
2224
|
+
if (!existsSync6(sentinelPath)) {
|
|
1654
2225
|
writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1655
2226
|
`, { mode: 384 });
|
|
1656
2227
|
}
|
|
1657
2228
|
return;
|
|
1658
2229
|
}
|
|
1659
|
-
const legacyRdf =
|
|
1660
|
-
const legacyAudit =
|
|
1661
|
-
const legacyContents =
|
|
1662
|
-
if (
|
|
2230
|
+
const legacyRdf = path7.join(branchPath, "kb.rdf");
|
|
2231
|
+
const legacyAudit = path7.join(branchPath, "audit.log");
|
|
2232
|
+
const legacyContents = existsSync6(legacyRdf) ? readFileSync4(legacyRdf, "utf8") : "";
|
|
2233
|
+
if (existsSync6(legacyAudit) || existsSync6(legacyRdf) && !legacyContents.includes("kibi.rdf-journal.v1")) {
|
|
1663
2234
|
rmSync2(rdfDirectory, { recursive: true, force: true });
|
|
1664
2235
|
rmSync2(currentPath, { force: true });
|
|
1665
2236
|
rmSync2(markerPath, { force: true });
|
|
@@ -1669,36 +2240,36 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
1669
2240
|
} else {
|
|
1670
2241
|
recoverStaleJournalLock(branchPath);
|
|
1671
2242
|
archiveLegacyFiles(branchPath);
|
|
1672
|
-
if (!
|
|
2243
|
+
if (!existsSync6(sentinelPath)) {
|
|
1673
2244
|
writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1674
2245
|
`, { mode: 384 });
|
|
1675
2246
|
}
|
|
1676
2247
|
return;
|
|
1677
2248
|
}
|
|
1678
2249
|
}
|
|
1679
|
-
const legacyRdf =
|
|
1680
|
-
const legacyAudit =
|
|
1681
|
-
if (!
|
|
1682
|
-
mkdirSync2(
|
|
2250
|
+
const legacyRdf = path7.join(branchPath, "kb.rdf");
|
|
2251
|
+
const legacyAudit = path7.join(branchPath, "audit.log");
|
|
2252
|
+
if (!existsSync6(legacyRdf) && !existsSync6(legacyAudit)) {
|
|
2253
|
+
mkdirSync2(path7.join(branchPath, "rdf"), { recursive: true });
|
|
1683
2254
|
writeFileSync2(markerPath, `{"format":"kibi.rdf-journal.v1","schemaVersion":1}
|
|
1684
2255
|
`, { mode: 384 });
|
|
1685
|
-
writeFileSync2(
|
|
2256
|
+
writeFileSync2(path7.join(branchPath, "CURRENT"), `generation-1:0
|
|
1686
2257
|
`, {
|
|
1687
2258
|
mode: 384
|
|
1688
2259
|
});
|
|
1689
|
-
writeFileSync2(
|
|
2260
|
+
writeFileSync2(path7.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1690
2261
|
`, { mode: 384 });
|
|
1691
2262
|
return;
|
|
1692
2263
|
}
|
|
1693
|
-
if (
|
|
1694
|
-
const legacyContents =
|
|
2264
|
+
if (existsSync6(legacyRdf)) {
|
|
2265
|
+
const legacyContents = readFileSync4(legacyRdf, "utf8");
|
|
1695
2266
|
if (!/<rdf:RDF(?:\s|>)/.test(legacyContents)) {
|
|
1696
2267
|
throw new Error(`Legacy Kibi store is corrupt at ${legacyRdf}: expected RDF/XML root`);
|
|
1697
2268
|
}
|
|
1698
2269
|
}
|
|
1699
|
-
const parent =
|
|
1700
|
-
const migrationKey = createHash2("sha256").update(
|
|
1701
|
-
const staging =
|
|
2270
|
+
const parent = path7.dirname(branchPath);
|
|
2271
|
+
const migrationKey = createHash2("sha256").update(path7.resolve(branchPath)).digest("hex").slice(0, 16);
|
|
2272
|
+
const staging = path7.join(parent, `.${migrationKey}.migration.${process.pid}.${Date.now()}`);
|
|
1702
2273
|
mkdirSync2(staging, { recursive: true, mode: 448 });
|
|
1703
2274
|
const testOneShot = false;
|
|
1704
2275
|
const migrator = new PrologProcess({
|
|
@@ -1726,35 +2297,35 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
1726
2297
|
await migrator.terminate();
|
|
1727
2298
|
}
|
|
1728
2299
|
for (const file of ["rdf", "storage.json", "CURRENT"]) {
|
|
1729
|
-
const source =
|
|
1730
|
-
if (!
|
|
2300
|
+
const source = path7.join(staging, file);
|
|
2301
|
+
if (!existsSync6(source))
|
|
1731
2302
|
continue;
|
|
1732
|
-
const target =
|
|
1733
|
-
if (
|
|
2303
|
+
const target = path7.join(branchPath, file);
|
|
2304
|
+
if (existsSync6(target))
|
|
1734
2305
|
rmSync2(target, { recursive: true, force: true });
|
|
1735
2306
|
renameSync(source, target);
|
|
1736
2307
|
}
|
|
1737
2308
|
archiveLegacyFiles(branchPath);
|
|
1738
|
-
writeFileSync2(
|
|
2309
|
+
writeFileSync2(path7.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1739
2310
|
`, { mode: 384 });
|
|
1740
2311
|
fsyncJournaledBranchStore(branchPath);
|
|
1741
2312
|
rmSync2(staging, { recursive: true, force: true });
|
|
1742
2313
|
}
|
|
1743
2314
|
function archiveLegacyFiles(branchPath) {
|
|
1744
|
-
const legacyDir =
|
|
2315
|
+
const legacyDir = path7.join(branchPath, "legacy");
|
|
1745
2316
|
mkdirSync2(legacyDir, { recursive: true, mode: 448 });
|
|
1746
2317
|
for (const file of ["kb.rdf", "audit.log"]) {
|
|
1747
|
-
const source =
|
|
1748
|
-
if (!
|
|
2318
|
+
const source = path7.join(branchPath, file);
|
|
2319
|
+
if (!existsSync6(source))
|
|
1749
2320
|
continue;
|
|
1750
2321
|
if (file === "kb.rdf") {
|
|
1751
|
-
const contents =
|
|
2322
|
+
const contents = readFileSync4(source, "utf8");
|
|
1752
2323
|
if (contents.includes("kibi.rdf-journal.v1"))
|
|
1753
2324
|
continue;
|
|
1754
2325
|
}
|
|
1755
|
-
const destination =
|
|
1756
|
-
if (
|
|
1757
|
-
renameSync(source,
|
|
2326
|
+
const destination = path7.join(legacyDir, file);
|
|
2327
|
+
if (existsSync6(destination)) {
|
|
2328
|
+
renameSync(source, path7.join(legacyDir, `${file}.recovery.${Date.now()}`));
|
|
1758
2329
|
} else {
|
|
1759
2330
|
renameSync(source, destination);
|
|
1760
2331
|
}
|
|
@@ -1770,36 +2341,36 @@ function recoverInterruptedGeneration(branchPath) {
|
|
|
1770
2341
|
const newest = (prefix) => {
|
|
1771
2342
|
const candidates = entries.filter((entry) => entry.startsWith(prefix)).sort().reverse();
|
|
1772
2343
|
const candidate = candidates[0];
|
|
1773
|
-
return candidate === undefined ? null :
|
|
2344
|
+
return candidate === undefined ? null : path7.join(branchPath, candidate);
|
|
1774
2345
|
};
|
|
1775
2346
|
const oldRdf = newest("rdf.old.");
|
|
1776
2347
|
const oldCurrent = newest("CURRENT.old.");
|
|
1777
|
-
const rdfPath =
|
|
1778
|
-
const currentPath =
|
|
1779
|
-
if ((!
|
|
2348
|
+
const rdfPath = path7.join(branchPath, "rdf");
|
|
2349
|
+
const currentPath = path7.join(branchPath, "CURRENT");
|
|
2350
|
+
if ((!existsSync6(rdfPath) || !existsSync6(currentPath)) && oldRdf && oldCurrent) {
|
|
1780
2351
|
try {
|
|
1781
|
-
if (
|
|
2352
|
+
if (existsSync6(rdfPath))
|
|
1782
2353
|
rmSync2(rdfPath, { recursive: true, force: true });
|
|
1783
|
-
if (
|
|
2354
|
+
if (existsSync6(currentPath))
|
|
1784
2355
|
rmSync2(currentPath, { force: true });
|
|
1785
2356
|
renameSync(oldRdf, rdfPath);
|
|
1786
2357
|
renameSync(oldCurrent, currentPath);
|
|
1787
2358
|
return;
|
|
1788
2359
|
} catch {}
|
|
1789
2360
|
}
|
|
1790
|
-
if (!
|
|
2361
|
+
if (!existsSync6(rdfPath) && oldRdf) {
|
|
1791
2362
|
try {
|
|
1792
2363
|
renameSync(oldRdf, rdfPath);
|
|
1793
2364
|
} catch {}
|
|
1794
2365
|
}
|
|
1795
|
-
if (!
|
|
2366
|
+
if (!existsSync6(currentPath) && oldCurrent) {
|
|
1796
2367
|
try {
|
|
1797
2368
|
renameSync(oldCurrent, currentPath);
|
|
1798
2369
|
} catch {}
|
|
1799
2370
|
}
|
|
1800
2371
|
}
|
|
1801
2372
|
function isEngineLifecycleRequest(request) {
|
|
1802
|
-
if (request.method === "stop" || request.method === "cancel")
|
|
2373
|
+
if (request.method === "stop" || request.method === "cancel" || request.method === "handshake")
|
|
1803
2374
|
return true;
|
|
1804
2375
|
const kind = request.command?.kind;
|
|
1805
2376
|
return request.method === "command" && (kind === "stop" || kind === "cancel" || kind === "lifecycle");
|
|
@@ -1817,9 +2388,9 @@ class EngineSession {
|
|
|
1817
2388
|
constructor(prolog, branchPath) {
|
|
1818
2389
|
this.prolog = prolog;
|
|
1819
2390
|
this.branchPath = branchPath;
|
|
1820
|
-
const coreModuleDir =
|
|
2391
|
+
const coreModuleDir = path7.dirname(resolveKbPlPath());
|
|
1821
2392
|
for (const [fileName, label] of ENGINE_PRELOADED_MODULES) {
|
|
1822
|
-
const modulePath =
|
|
2393
|
+
const modulePath = path7.join(coreModuleDir, fileName);
|
|
1823
2394
|
this.modules.push({
|
|
1824
2395
|
goal: `use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`,
|
|
1825
2396
|
label
|
|
@@ -1866,10 +2437,14 @@ class EngineSession {
|
|
|
1866
2437
|
return true;
|
|
1867
2438
|
}
|
|
1868
2439
|
}
|
|
1869
|
-
async function runEngineDaemon(
|
|
1870
|
-
if (!isValidBranchName(
|
|
1871
|
-
throw new Error(`Invalid Kibi engine branch name: ${
|
|
2440
|
+
async function runEngineDaemon(requestedOptions) {
|
|
2441
|
+
if (!isValidBranchName(requestedOptions.branch)) {
|
|
2442
|
+
throw new Error(`Invalid Kibi engine branch name: ${requestedOptions.branch}`);
|
|
1872
2443
|
}
|
|
2444
|
+
const options = {
|
|
2445
|
+
...requestedOptions,
|
|
2446
|
+
workspaceRoot: canonicalFilesystemPath(requestedOptions.workspaceRoot)
|
|
2447
|
+
};
|
|
1873
2448
|
const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
|
|
1874
2449
|
await ensureJournaledBranchStoreAsync(branchPath);
|
|
1875
2450
|
const prolog = new PrologProcess({
|
|
@@ -1880,8 +2455,9 @@ async function runEngineDaemon(options) {
|
|
|
1880
2455
|
const session = new EngineSession(prolog, branchPath);
|
|
1881
2456
|
await session.boot();
|
|
1882
2457
|
let attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
1883
|
-
|
|
1884
|
-
|
|
2458
|
+
const serverPrologIdentity = engineSwiplIdentity();
|
|
2459
|
+
mkdirSync2(path7.dirname(options.socketPath), { recursive: true, mode: 448 });
|
|
2460
|
+
if (existsSync6(options.socketPath)) {
|
|
1885
2461
|
const isLivePeerSignal = (error) => {
|
|
1886
2462
|
if (error && typeof error === "object") {
|
|
1887
2463
|
const code = "code" in error ? String(error.code) : "";
|
|
@@ -1933,6 +2509,10 @@ async function runEngineDaemon(options) {
|
|
|
1933
2509
|
let freshnessCache = null;
|
|
1934
2510
|
const clients = new Set;
|
|
1935
2511
|
let shuttingDown = false;
|
|
2512
|
+
let finishShutdown;
|
|
2513
|
+
const shutdownFinished = new Promise((resolve) => {
|
|
2514
|
+
finishShutdown = resolve;
|
|
2515
|
+
});
|
|
1936
2516
|
const exactBranchStatus = (result) => {
|
|
1937
2517
|
if (!result.success || typeof result.bindings.JsonString !== "string") {
|
|
1938
2518
|
return result;
|
|
@@ -1961,7 +2541,7 @@ async function runEngineDaemon(options) {
|
|
|
1961
2541
|
const workspaceWatchdog = setInterval(() => {
|
|
1962
2542
|
if (shuttingDown)
|
|
1963
2543
|
return;
|
|
1964
|
-
if (!
|
|
2544
|
+
if (!existsSync6(options.workspaceRoot)) {
|
|
1965
2545
|
workspaceMissingStreak += 1;
|
|
1966
2546
|
if (workspaceMissingStreak < 2)
|
|
1967
2547
|
return;
|
|
@@ -1973,7 +2553,7 @@ async function runEngineDaemon(options) {
|
|
|
1973
2553
|
}, engineWorkspaceWatchdogMs());
|
|
1974
2554
|
workspaceWatchdog.unref();
|
|
1975
2555
|
const scheduleIdleExit = () => {
|
|
1976
|
-
if (activeClients > 0)
|
|
2556
|
+
if (shuttingDown || activeClients > 0)
|
|
1977
2557
|
return;
|
|
1978
2558
|
if (!idleCompactionQueued) {
|
|
1979
2559
|
idleCompactionQueued = true;
|
|
@@ -2002,9 +2582,12 @@ async function runEngineDaemon(options) {
|
|
|
2002
2582
|
if (expectedPackages !== undefined && request.packageVersions !== expectedPackages) {
|
|
2003
2583
|
throw new Error(`Kibi engine package-version mismatch: client=${request.packageVersions ?? "missing"}, server=${expectedPackages}`);
|
|
2004
2584
|
}
|
|
2005
|
-
if (
|
|
2585
|
+
if (canonicalFilesystemPath(request.workspaceRoot ?? "") !== options.workspaceRoot || request.branch !== options.branch) {
|
|
2006
2586
|
throw new Error("Kibi engine workspace identity mismatch");
|
|
2007
2587
|
}
|
|
2588
|
+
if (request.method !== "handshake" && request.method !== "stop" && request.prologIdentity !== serverPrologIdentity) {
|
|
2589
|
+
throw new Error(`Kibi engine SWI-Prolog mismatch: client=${request.prologIdentity ?? "missing"}, server=${serverPrologIdentity}`);
|
|
2590
|
+
}
|
|
2008
2591
|
if (!isEngineLifecycleRequest(request) && await session.ensureLive()) {
|
|
2009
2592
|
queryCache.clear();
|
|
2010
2593
|
freshnessCache = null;
|
|
@@ -2145,7 +2728,14 @@ async function runEngineDaemon(options) {
|
|
|
2145
2728
|
}
|
|
2146
2729
|
}
|
|
2147
2730
|
case "kbStatus": {
|
|
2731
|
+
const freshnessCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance2.now() : null;
|
|
2148
2732
|
if (freshnessCache !== null && Date.now() - freshnessCache.capturedAt <= ENGINE_FRESHNESS_CACHE_MS) {
|
|
2733
|
+
if (freshnessCacheLookupStartedAt !== null) {
|
|
2734
|
+
writePerformanceTraceEvent({
|
|
2735
|
+
kind: "engine-freshness-cache-hit",
|
|
2736
|
+
durationMs: performance2.now() - freshnessCacheLookupStartedAt
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2149
2739
|
return freshnessCache.result;
|
|
2150
2740
|
}
|
|
2151
2741
|
const result = exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
|
|
@@ -2165,9 +2755,17 @@ async function runEngineDaemon(options) {
|
|
|
2165
2755
|
freshnessCache = null;
|
|
2166
2756
|
}
|
|
2167
2757
|
if (cacheableEngineGoal(request.goal)) {
|
|
2758
|
+
const queryCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance2.now() : null;
|
|
2168
2759
|
const cached = queryCache.get(request.goal);
|
|
2169
|
-
if (cached !== undefined)
|
|
2760
|
+
if (cached !== undefined) {
|
|
2761
|
+
if (queryCacheLookupStartedAt !== null) {
|
|
2762
|
+
writePerformanceTraceEvent({
|
|
2763
|
+
kind: "engine-query-cache-hit",
|
|
2764
|
+
durationMs: performance2.now() - queryCacheLookupStartedAt
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2170
2767
|
return cached;
|
|
2768
|
+
}
|
|
2171
2769
|
}
|
|
2172
2770
|
const result = await prolog.query(request.goal);
|
|
2173
2771
|
session.recordModuleLoad(request.goal, result);
|
|
@@ -2204,6 +2802,8 @@ async function runEngineDaemon(options) {
|
|
|
2204
2802
|
if (typeof request.targetDirectory !== "string")
|
|
2205
2803
|
throw new Error("export.targetDirectory is required");
|
|
2206
2804
|
return prolog.query(`kb_storage_export('${quoteProlog(request.targetDirectory)}')`);
|
|
2805
|
+
case "handshake":
|
|
2806
|
+
return { prologIdentity: serverPrologIdentity };
|
|
2207
2807
|
case "stop":
|
|
2208
2808
|
setImmediate(() => void shutdown());
|
|
2209
2809
|
return { stopped: true };
|
|
@@ -2223,6 +2823,10 @@ async function runEngineDaemon(options) {
|
|
|
2223
2823
|
if (idleTimer)
|
|
2224
2824
|
clearTimeout(idleTimer);
|
|
2225
2825
|
clearInterval(workspaceWatchdog);
|
|
2826
|
+
server.close();
|
|
2827
|
+
await queue.catch(() => {
|
|
2828
|
+
return;
|
|
2829
|
+
});
|
|
2226
2830
|
const saved = session.needsRecycle() ? null : await prolog.query("kb_save").catch(() => null);
|
|
2227
2831
|
if (saved?.success) {
|
|
2228
2832
|
try {
|
|
@@ -2234,22 +2838,29 @@ async function runEngineDaemon(options) {
|
|
|
2234
2838
|
});
|
|
2235
2839
|
for (const client of clients)
|
|
2236
2840
|
client.destroy();
|
|
2237
|
-
server.close();
|
|
2238
2841
|
server.unref();
|
|
2239
2842
|
try {
|
|
2240
|
-
if (
|
|
2843
|
+
if (existsSync6(options.socketPath))
|
|
2241
2844
|
rmSync2(options.socketPath, { force: true });
|
|
2242
|
-
if (
|
|
2845
|
+
if (existsSync6(enginePidPath(options.workspaceRoot, options.branch)))
|
|
2243
2846
|
rmSync2(enginePidPath(options.workspaceRoot, options.branch), {
|
|
2244
2847
|
force: true
|
|
2245
2848
|
});
|
|
2246
2849
|
} catch {}
|
|
2247
2850
|
setTimeout(() => process.exit(0), 50).unref();
|
|
2851
|
+
finishShutdown();
|
|
2248
2852
|
};
|
|
2249
2853
|
const requestSignalShutdown = requestEngineSignalShutdown(shutdown);
|
|
2250
2854
|
process.once("SIGTERM", requestSignalShutdown);
|
|
2251
2855
|
process.once("SIGINT", requestSignalShutdown);
|
|
2252
2856
|
server.on("connection", (socket) => {
|
|
2857
|
+
socket.on("error", () => {
|
|
2858
|
+
return;
|
|
2859
|
+
});
|
|
2860
|
+
if (shuttingDown) {
|
|
2861
|
+
socket.destroy();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2253
2864
|
clients.add(socket);
|
|
2254
2865
|
activeClients += 1;
|
|
2255
2866
|
const cancelledRequests = new Set;
|
|
@@ -2259,6 +2870,15 @@ async function runEngineDaemon(options) {
|
|
|
2259
2870
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
2260
2871
|
buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
|
|
2261
2872
|
const request = value;
|
|
2873
|
+
if (shuttingDown) {
|
|
2874
|
+
writeSocketFrame(socket, {
|
|
2875
|
+
id: request.id,
|
|
2876
|
+
ok: false,
|
|
2877
|
+
error: "Kibi engine is shutting down; request was not executed",
|
|
2878
|
+
serverPackageVersions: ENGINE_PACKAGE_VERSIONS
|
|
2879
|
+
});
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2262
2882
|
if (request.method === "cancel" && typeof request.cancelOf === "number") {
|
|
2263
2883
|
cancelledRequests.add(request.cancelOf);
|
|
2264
2884
|
}
|
|
@@ -2317,6 +2937,7 @@ async function runEngineDaemon(options) {
|
|
|
2317
2937
|
await new Promise((resolve) => {
|
|
2318
2938
|
server.once("close", resolve);
|
|
2319
2939
|
});
|
|
2940
|
+
await shutdownFinished;
|
|
2320
2941
|
}
|
|
2321
2942
|
|
|
2322
2943
|
// ../cli/src/engine-daemon.ts
|
|
@@ -2328,7 +2949,7 @@ function isEngineDaemonEntrypoint(argv = process.argv, moduleUrl = import.meta.u
|
|
|
2328
2949
|
if (typeof entry !== "string" || entry.length === 0)
|
|
2329
2950
|
return false;
|
|
2330
2951
|
try {
|
|
2331
|
-
return moduleUrl === pathToFileURL(
|
|
2952
|
+
return moduleUrl === pathToFileURL(path8.resolve(entry)).href;
|
|
2332
2953
|
} catch {
|
|
2333
2954
|
return false;
|
|
2334
2955
|
}
|