kibi-runtime 2.0.1 → 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 +891 -166
- package/dist/index.d.ts +4 -2
- package/dist/index.js +45261 -39269
- 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/operation-access.md +1 -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 +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,9 +508,337 @@ 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 =
|
|
388
|
-
var
|
|
840
|
+
var importMetaDir = path3.dirname(fileURLToPath(import.meta.url));
|
|
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__";
|
|
391
844
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -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,20 +865,30 @@ 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
|
}
|
|
882
|
+
|
|
883
|
+
class PrologProcessTerminatedError extends Error {
|
|
884
|
+
code = "prolog_process_terminated";
|
|
885
|
+
reason;
|
|
886
|
+
constructor(reason) {
|
|
887
|
+
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.`);
|
|
888
|
+
this.name = "PrologProcessTerminatedError";
|
|
889
|
+
this.reason = reason;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
429
892
|
function registerProcessExitOnce(current, handler, on = (event, listener) => process.on(event, listener)) {
|
|
430
893
|
if (current)
|
|
431
894
|
return current;
|
|
@@ -440,7 +903,8 @@ function bindProcessExitHandler(current, terminate, on = (event, listener) => pr
|
|
|
440
903
|
|
|
441
904
|
class PrologProcess {
|
|
442
905
|
process = null;
|
|
443
|
-
|
|
906
|
+
explicitSwiplPath;
|
|
907
|
+
swiplResolver;
|
|
444
908
|
timeout;
|
|
445
909
|
outputBuffer = "";
|
|
446
910
|
outputBufferBytes = 0;
|
|
@@ -454,20 +918,43 @@ class PrologProcess {
|
|
|
454
918
|
useOneShotMode;
|
|
455
919
|
attachedKbPath = null;
|
|
456
920
|
onProcessExit = null;
|
|
921
|
+
maxOutputBytes;
|
|
922
|
+
interactiveStarted = false;
|
|
923
|
+
terminationReason = null;
|
|
457
924
|
constructor(options = {}) {
|
|
458
|
-
this.
|
|
925
|
+
this.explicitSwiplPath = options.swiplPath || undefined;
|
|
926
|
+
this.swiplResolver = options.swiplResolver ?? (() => resolveSwipl());
|
|
459
927
|
this.timeout = options.timeout || 30000;
|
|
928
|
+
this.maxOutputBytes = options.maxOutputBytes !== undefined && Number.isInteger(options.maxOutputBytes) && options.maxOutputBytes > 0 ? options.maxOutputBytes : DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES;
|
|
460
929
|
this.useOneShotMode = options.oneShot ?? false;
|
|
461
930
|
}
|
|
462
931
|
attachProcessExitHandler(on = (event, listener) => process.on(event, listener)) {
|
|
463
932
|
this.onProcessExit = bindProcessExitHandler(this.onProcessExit, () => this.terminate(), on);
|
|
464
933
|
}
|
|
934
|
+
get oneShotMode() {
|
|
935
|
+
return this.useOneShotMode || !this.interactiveStarted;
|
|
936
|
+
}
|
|
937
|
+
needsRestart() {
|
|
938
|
+
return this.interactiveStarted && !this.isProcessUsable();
|
|
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
|
+
}
|
|
465
947
|
async start() {
|
|
466
|
-
|
|
467
|
-
|
|
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.`);
|
|
468
951
|
}
|
|
952
|
+
const launch = this.launchTarget();
|
|
469
953
|
const kbPath = resolveKbPlPath();
|
|
470
|
-
this.
|
|
954
|
+
this.interactiveStarted = true;
|
|
955
|
+
this.terminationReason = null;
|
|
956
|
+
this.invalidateCache();
|
|
957
|
+
this.process = spawn(launch.bin, [
|
|
471
958
|
"-g",
|
|
472
959
|
`use_module('${kbPath}'), use_module(library(semweb/rdf_db)), set_prolog_flag(answer_write_options, [max_depth(0), quoted(true)])`,
|
|
473
960
|
"--quiet"
|
|
@@ -475,6 +962,7 @@ class PrologProcess {
|
|
|
475
962
|
detached: process.platform !== "win32",
|
|
476
963
|
env: {
|
|
477
964
|
...process.env,
|
|
965
|
+
...launch.env,
|
|
478
966
|
KIBI_RUNTIME_NAME: process.versions.bun !== undefined ? "bun" : "node",
|
|
479
967
|
KIBI_RUNTIME_VERSION: process.versions.bun ?? process.versions.node ?? "unknown",
|
|
480
968
|
KIBI_PACKAGE_VERSIONS: process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS
|
|
@@ -490,6 +978,12 @@ class PrologProcess {
|
|
|
490
978
|
this.process.stderr.on("data", (chunk) => {
|
|
491
979
|
this.appendErrorChunk(chunk);
|
|
492
980
|
});
|
|
981
|
+
const child = this.process;
|
|
982
|
+
child.stdin?.on("error", (error) => {
|
|
983
|
+
if (this.process === child) {
|
|
984
|
+
this.terminationReason ??= `stdin ${error.code ?? "error"}`;
|
|
985
|
+
}
|
|
986
|
+
});
|
|
493
987
|
this.process.stdin.write(`true.
|
|
494
988
|
`);
|
|
495
989
|
this.attachProcessExitHandler();
|
|
@@ -517,12 +1011,22 @@ class PrologProcess {
|
|
|
517
1011
|
this.clearQueryBuffers();
|
|
518
1012
|
}
|
|
519
1013
|
async query(goal) {
|
|
1014
|
+
if (!this.useOneShotMode && this.needsRestart()) {
|
|
1015
|
+
throw this.lostProcessError();
|
|
1016
|
+
}
|
|
520
1017
|
const isSingleGoal = typeof goal === "string";
|
|
521
1018
|
const goalKey = isSingleGoal ? goal : null;
|
|
522
1019
|
const cacheable = goalKey !== null && this.isCacheableGoal(goalKey);
|
|
523
1020
|
if (cacheable) {
|
|
1021
|
+
const cacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
|
|
524
1022
|
const cachedResult = this.cache.get(goalKey);
|
|
525
1023
|
if (cachedResult) {
|
|
1024
|
+
if (cacheLookupStartedAt !== null) {
|
|
1025
|
+
writePerformanceTraceEvent({
|
|
1026
|
+
kind: "prolog-process-cache-hit",
|
|
1027
|
+
durationMs: performance.now() - cacheLookupStartedAt
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
526
1030
|
return cachedResult;
|
|
527
1031
|
}
|
|
528
1032
|
}
|
|
@@ -543,10 +1047,10 @@ class PrologProcess {
|
|
|
543
1047
|
return this.query(batchGoal);
|
|
544
1048
|
}
|
|
545
1049
|
if (!this.isProcessUsable()) {
|
|
546
|
-
if (this.process === null) {
|
|
1050
|
+
if (this.process === null && !this.interactiveStarted) {
|
|
547
1051
|
return this.execOneShot(goal, this.attachedKbPath);
|
|
548
1052
|
}
|
|
549
|
-
throw
|
|
1053
|
+
throw this.lostProcessError();
|
|
550
1054
|
}
|
|
551
1055
|
const runInteractiveQuery = async () => {
|
|
552
1056
|
this.clearQueryBuffers();
|
|
@@ -576,6 +1080,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
576
1080
|
console.error(`[prolog debug] runtime=${runtime ?? "unknown"} packages=${process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS}`);
|
|
577
1081
|
}
|
|
578
1082
|
settled = true;
|
|
1083
|
+
this.terminationReason = `query timeout: ${goalLabel}`;
|
|
579
1084
|
this.terminate().finally(() => {
|
|
580
1085
|
reject(new Error(msg));
|
|
581
1086
|
});
|
|
@@ -587,6 +1092,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
587
1092
|
if (this.outputOverflowed) {
|
|
588
1093
|
clearTimeout(timeoutId);
|
|
589
1094
|
settled = true;
|
|
1095
|
+
this.terminationReason = `output overflow (ENOBUFS): ${goalLabel}`;
|
|
590
1096
|
this.terminate().finally(() => {
|
|
591
1097
|
resolve({
|
|
592
1098
|
success: false,
|
|
@@ -599,6 +1105,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
599
1105
|
if (!this.isProcessUsable()) {
|
|
600
1106
|
clearTimeout(timeoutId);
|
|
601
1107
|
settled = true;
|
|
1108
|
+
this.terminationReason ??= `process exited during query: ${goalLabel}`;
|
|
602
1109
|
resolve({
|
|
603
1110
|
success: false,
|
|
604
1111
|
bindings: {},
|
|
@@ -664,12 +1171,22 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
664
1171
|
releaseQuery = resolve;
|
|
665
1172
|
});
|
|
666
1173
|
await previousQuery;
|
|
1174
|
+
const tracingEnabled = process.env.KIBI_PERF_TIMINGS === "1";
|
|
1175
|
+
let roundTripStartedAt = null;
|
|
667
1176
|
try {
|
|
668
1177
|
if (!this.isProcessUsable()) {
|
|
669
|
-
throw
|
|
1178
|
+
throw this.lostProcessError();
|
|
670
1179
|
}
|
|
1180
|
+
if (tracingEnabled)
|
|
1181
|
+
roundTripStartedAt = performance.now();
|
|
671
1182
|
return await runInteractiveQuery();
|
|
672
1183
|
} finally {
|
|
1184
|
+
if (roundTripStartedAt !== null) {
|
|
1185
|
+
writePerformanceTraceEvent({
|
|
1186
|
+
kind: "prolog-round-trip",
|
|
1187
|
+
durationMs: performance.now() - roundTripStartedAt
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
673
1190
|
releaseQuery();
|
|
674
1191
|
}
|
|
675
1192
|
}
|
|
@@ -751,10 +1268,12 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
751
1268
|
const packageVersions = process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS;
|
|
752
1269
|
let child;
|
|
753
1270
|
try {
|
|
754
|
-
|
|
1271
|
+
const launch = this.launchTarget();
|
|
1272
|
+
child = spawn(launch.bin, ["-q", "-g", prologGoal, "-t", "halt"], {
|
|
755
1273
|
detached: process.platform !== "win32",
|
|
756
1274
|
env: {
|
|
757
1275
|
...process.env,
|
|
1276
|
+
...launch.env,
|
|
758
1277
|
KIBI_GOAL: combinedGoal,
|
|
759
1278
|
KIBI_RUNTIME_NAME: runtimeName,
|
|
760
1279
|
KIBI_RUNTIME_VERSION: runtimeVersion,
|
|
@@ -784,7 +1303,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
784
1303
|
if (outputOverflowed)
|
|
785
1304
|
return;
|
|
786
1305
|
const bytes = target === "stdout" ? outputBytes : errorBytes;
|
|
787
|
-
const remaining =
|
|
1306
|
+
const remaining = this.maxOutputBytes - bytes;
|
|
788
1307
|
if (chunk.byteLength > remaining) {
|
|
789
1308
|
const clipped = chunk.subarray(0, Math.max(0, remaining)).toString();
|
|
790
1309
|
if (target === "stdout")
|
|
@@ -897,10 +1416,10 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
897
1416
|
appendOutputChunk(chunk) {
|
|
898
1417
|
if (this.outputOverflowed)
|
|
899
1418
|
return;
|
|
900
|
-
const remaining =
|
|
1419
|
+
const remaining = this.maxOutputBytes - this.outputBufferBytes;
|
|
901
1420
|
if (chunk.byteLength > remaining) {
|
|
902
1421
|
this.outputBuffer += chunk.subarray(0, remaining).toString();
|
|
903
|
-
this.outputBufferBytes =
|
|
1422
|
+
this.outputBufferBytes = this.maxOutputBytes;
|
|
904
1423
|
this.outputOverflowed = true;
|
|
905
1424
|
return;
|
|
906
1425
|
}
|
|
@@ -910,10 +1429,10 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
910
1429
|
appendErrorChunk(chunk) {
|
|
911
1430
|
if (this.outputOverflowed)
|
|
912
1431
|
return;
|
|
913
|
-
const remaining =
|
|
1432
|
+
const remaining = this.maxOutputBytes - this.errorBufferBytes;
|
|
914
1433
|
if (chunk.byteLength > remaining) {
|
|
915
1434
|
this.errorBuffer += chunk.subarray(0, remaining).toString();
|
|
916
|
-
this.errorBufferBytes =
|
|
1435
|
+
this.errorBufferBytes = this.maxOutputBytes;
|
|
917
1436
|
this.outputOverflowed = true;
|
|
918
1437
|
return;
|
|
919
1438
|
}
|
|
@@ -1010,6 +1529,18 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1010
1529
|
return { message: this.translateError(errorText) };
|
|
1011
1530
|
}
|
|
1012
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
|
+
}
|
|
1013
1544
|
const cleanError = errorText.replace(/^__KIBI_(?:STAGE|RUNTIME|ERROR)__:[^\r\n]*\r?\n?/gm, "");
|
|
1014
1545
|
if (cleanError.includes("stale_snapshot")) {
|
|
1015
1546
|
return "KB snapshot is stale; reattach or refresh the runtime before retrying (stale_snapshot)";
|
|
@@ -1040,6 +1571,8 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1040
1571
|
if (cleanError.includes("existence_error") || cleanError.includes("Unknown procedure")) {
|
|
1041
1572
|
return "Predicate or file not found";
|
|
1042
1573
|
}
|
|
1574
|
+
if (existence !== null)
|
|
1575
|
+
return "Predicate or file not found";
|
|
1043
1576
|
if (cleanError.includes("permission_error")) {
|
|
1044
1577
|
return "Access denied or KB locked";
|
|
1045
1578
|
}
|
|
@@ -1059,7 +1592,16 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1059
1592
|
getPid() {
|
|
1060
1593
|
return this.process?.pid || 0;
|
|
1061
1594
|
}
|
|
1595
|
+
lostProcessError() {
|
|
1596
|
+
if (!this.interactiveStarted) {
|
|
1597
|
+
return new Error("Prolog process not started");
|
|
1598
|
+
}
|
|
1599
|
+
return new PrologProcessTerminatedError(this.terminationReason ?? "process exited");
|
|
1600
|
+
}
|
|
1062
1601
|
async terminate() {
|
|
1602
|
+
if (this.interactiveStarted) {
|
|
1603
|
+
this.terminationReason ??= "terminated";
|
|
1604
|
+
}
|
|
1063
1605
|
if (this.terminationPromise) {
|
|
1064
1606
|
await this.terminationPromise;
|
|
1065
1607
|
return;
|
|
@@ -1090,8 +1632,8 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
|
|
|
1090
1632
|
}
|
|
1091
1633
|
|
|
1092
1634
|
// ../cli/src/prolog/store-lock.ts
|
|
1093
|
-
import { existsSync as
|
|
1094
|
-
import { readFileSync } from "node:fs";
|
|
1635
|
+
import { existsSync as existsSync4, rmSync } from "node:fs";
|
|
1636
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1095
1637
|
import { dirname, join } from "node:path";
|
|
1096
1638
|
var cachedBootId;
|
|
1097
1639
|
function currentBootId() {
|
|
@@ -1102,7 +1644,7 @@ function currentBootId() {
|
|
|
1102
1644
|
return cachedBootId;
|
|
1103
1645
|
}
|
|
1104
1646
|
try {
|
|
1105
|
-
const raw =
|
|
1647
|
+
const raw = readFileSync2("/proc/sys/kernel/random/boot_id", "utf8");
|
|
1106
1648
|
cachedBootId = raw.trim();
|
|
1107
1649
|
} catch {
|
|
1108
1650
|
cachedBootId = null;
|
|
@@ -1141,7 +1683,7 @@ function breakStoreLock(rdfDirectory) {
|
|
|
1141
1683
|
join(dirname(rdfDirectory), ".kibi-lock-owner.json")
|
|
1142
1684
|
];
|
|
1143
1685
|
for (const candidate of candidates) {
|
|
1144
|
-
if (
|
|
1686
|
+
if (existsSync4(candidate)) {
|
|
1145
1687
|
rmSync(candidate, { force: true });
|
|
1146
1688
|
}
|
|
1147
1689
|
}
|
|
@@ -1195,31 +1737,31 @@ import {
|
|
|
1195
1737
|
execFileSync as rawExecFileSync,
|
|
1196
1738
|
execSync as rawExecSync
|
|
1197
1739
|
} from "node:child_process";
|
|
1198
|
-
import * as
|
|
1740
|
+
import * as path5 from "node:path";
|
|
1199
1741
|
|
|
1200
1742
|
// ../cli/src/utils/branch-store-locator.ts
|
|
1201
1743
|
import { createHash } from "node:crypto";
|
|
1202
1744
|
import {
|
|
1203
|
-
existsSync as
|
|
1745
|
+
existsSync as existsSync5,
|
|
1204
1746
|
mkdirSync,
|
|
1205
|
-
readFileSync as
|
|
1747
|
+
readFileSync as readFileSync3,
|
|
1206
1748
|
readdirSync,
|
|
1207
|
-
statSync,
|
|
1749
|
+
statSync as statSync3,
|
|
1208
1750
|
writeFileSync
|
|
1209
1751
|
} from "node:fs";
|
|
1210
|
-
import
|
|
1752
|
+
import path4 from "node:path";
|
|
1211
1753
|
var BRANCH_STORE_MANIFEST_VERSION = 1;
|
|
1212
1754
|
function branchStoreKey(branch) {
|
|
1213
1755
|
return createHash("sha256").update(branch, "utf8").digest("hex");
|
|
1214
1756
|
}
|
|
1215
1757
|
function branchStoresRoot(workspaceRoot) {
|
|
1216
|
-
return
|
|
1758
|
+
return path4.join(path4.resolve(workspaceRoot), ".kb", "branches");
|
|
1217
1759
|
}
|
|
1218
1760
|
function branchStorePath(workspaceRoot, branch) {
|
|
1219
|
-
return
|
|
1761
|
+
return path4.join(branchStoresRoot(workspaceRoot), branchStoreKey(branch));
|
|
1220
1762
|
}
|
|
1221
1763
|
function branchStoreManifestPath(storePath) {
|
|
1222
|
-
return
|
|
1764
|
+
return path4.join(storePath, "branch.json");
|
|
1223
1765
|
}
|
|
1224
1766
|
function expectedBranchStoreManifest(branch) {
|
|
1225
1767
|
return {
|
|
@@ -1230,10 +1772,10 @@ function expectedBranchStoreManifest(branch) {
|
|
|
1230
1772
|
}
|
|
1231
1773
|
function readBranchStoreManifest(storePath) {
|
|
1232
1774
|
const manifestPath = branchStoreManifestPath(storePath);
|
|
1233
|
-
if (!
|
|
1775
|
+
if (!existsSync5(manifestPath))
|
|
1234
1776
|
return null;
|
|
1235
1777
|
try {
|
|
1236
|
-
const parsed = JSON.parse(
|
|
1778
|
+
const parsed = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
1237
1779
|
if (parsed.version !== BRANCH_STORE_MANIFEST_VERSION || typeof parsed.branch !== "string" || typeof parsed.key !== "string") {
|
|
1238
1780
|
return null;
|
|
1239
1781
|
}
|
|
@@ -1249,7 +1791,7 @@ function readBranchStoreManifest(storePath) {
|
|
|
1249
1791
|
function branchStoreManifestMatches(storePath, branch) {
|
|
1250
1792
|
const manifest = readBranchStoreManifest(storePath);
|
|
1251
1793
|
const expected = expectedBranchStoreManifest(branch);
|
|
1252
|
-
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;
|
|
1253
1795
|
}
|
|
1254
1796
|
function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
1255
1797
|
const storePath = branchStorePath(workspaceRoot, branch);
|
|
@@ -1260,7 +1802,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
|
1260
1802
|
throw new Error(`Branch store identity mismatch at ${storePath}; refusing to attach it to '${branch}'.`);
|
|
1261
1803
|
}
|
|
1262
1804
|
if (existing === null) {
|
|
1263
|
-
if (
|
|
1805
|
+
if (existsSync5(manifestPath)) {
|
|
1264
1806
|
throw new Error(`Branch store manifest is invalid at ${manifestPath}; refusing to overwrite it.`);
|
|
1265
1807
|
}
|
|
1266
1808
|
const entries = readdirSync(storePath);
|
|
@@ -1274,7 +1816,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
|
|
|
1274
1816
|
}
|
|
1275
1817
|
|
|
1276
1818
|
// ../cli/src/utils/branch-resolver.ts
|
|
1277
|
-
var
|
|
1819
|
+
var defaultDeps2 = {
|
|
1278
1820
|
execSync: rawExecSync,
|
|
1279
1821
|
execFileSync: rawExecFileSync
|
|
1280
1822
|
};
|
|
@@ -1294,7 +1836,7 @@ function isValidBranchName(name) {
|
|
|
1294
1836
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
1295
1837
|
return codePoint <= 32 || codePoint === 127;
|
|
1296
1838
|
});
|
|
1297
|
-
if (name.includes("..") ||
|
|
1839
|
+
if (name.includes("..") || path5.isAbsolute(name) || name.startsWith("/")) {
|
|
1298
1840
|
return false;
|
|
1299
1841
|
}
|
|
1300
1842
|
if (name.includes("//") || name.endsWith("/") || name.endsWith(".") || name.includes("\\") || name.startsWith("-") || name.includes("@{") || name.includes("..") || hasControlCharacter || /[~^:?*\[\]]/.test(name)) {
|
|
@@ -1305,7 +1847,7 @@ function isValidBranchName(name) {
|
|
|
1305
1847
|
return false;
|
|
1306
1848
|
}
|
|
1307
1849
|
try {
|
|
1308
|
-
|
|
1850
|
+
defaultDeps2.execFileSync("git", ["check-ref-format", "--branch", name], {
|
|
1309
1851
|
stdio: ["ignore", "ignore", "ignore"],
|
|
1310
1852
|
timeout: 5000
|
|
1311
1853
|
});
|
|
@@ -1315,10 +1857,43 @@ function isValidBranchName(name) {
|
|
|
1315
1857
|
}
|
|
1316
1858
|
}
|
|
1317
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
|
+
|
|
1318
1890
|
// ../cli/src/engine.ts
|
|
1319
1891
|
var ENGINE_PROTOCOL_VERSION = 1;
|
|
1320
1892
|
var ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
1321
1893
|
var ENGINE_PACKAGE_VERSIONS = process.env.KIBI_PACKAGE_VERSIONS ?? "unknown";
|
|
1894
|
+
function engineSwiplIdentity() {
|
|
1895
|
+
return swiplIdentity(resolveSwipl());
|
|
1896
|
+
}
|
|
1322
1897
|
var ENGINE_QUERY_CACHE_MAX_ENTRIES = 128;
|
|
1323
1898
|
var ENGINE_QUERY_CACHE_MAX_RESULT_BYTES = 8 * 1024 * 1024;
|
|
1324
1899
|
var ENGINE_FRESHNESS_CACHE_MS = 100;
|
|
@@ -1329,18 +1904,18 @@ function requestEngineSignalShutdown(shutdown) {
|
|
|
1329
1904
|
};
|
|
1330
1905
|
}
|
|
1331
1906
|
function readJournalGeneration(branchPath) {
|
|
1332
|
-
const currentPath =
|
|
1333
|
-
if (!
|
|
1907
|
+
const currentPath = path7.join(branchPath, "CURRENT");
|
|
1908
|
+
if (!existsSync6(currentPath))
|
|
1334
1909
|
return "";
|
|
1335
|
-
return
|
|
1910
|
+
return readFileSync4(currentPath, "utf8").trim();
|
|
1336
1911
|
}
|
|
1337
1912
|
function readEngineAttachmentIdentity(branchPath) {
|
|
1338
|
-
if (!
|
|
1913
|
+
if (!existsSync6(branchPath))
|
|
1339
1914
|
return null;
|
|
1340
|
-
const stats =
|
|
1341
|
-
let resolved =
|
|
1915
|
+
const stats = statSync4(branchPath);
|
|
1916
|
+
let resolved = path7.resolve(branchPath);
|
|
1342
1917
|
try {
|
|
1343
|
-
resolved =
|
|
1918
|
+
resolved = realpathSync3(branchPath);
|
|
1344
1919
|
} catch {}
|
|
1345
1920
|
return {
|
|
1346
1921
|
path: resolved,
|
|
@@ -1448,11 +2023,12 @@ function frame(value) {
|
|
|
1448
2023
|
header.writeUInt32BE(payload.byteLength, 0);
|
|
1449
2024
|
return Buffer.concat([header, payload]);
|
|
1450
2025
|
}
|
|
2026
|
+
function ignoreWriteFailure() {}
|
|
1451
2027
|
function writeSocketFrame(socket, value) {
|
|
1452
2028
|
if (socket.destroyed || !socket.writable)
|
|
1453
2029
|
return;
|
|
1454
2030
|
try {
|
|
1455
|
-
socket.write(frame(value));
|
|
2031
|
+
socket.write(frame(value), ignoreWriteFailure);
|
|
1456
2032
|
} catch {}
|
|
1457
2033
|
}
|
|
1458
2034
|
function parseFrames(buffer, onFrame) {
|
|
@@ -1471,42 +2047,85 @@ function parseFrames(buffer, onFrame) {
|
|
|
1471
2047
|
return buffer.subarray(cursor);
|
|
1472
2048
|
}
|
|
1473
2049
|
function runtimeDirectory() {
|
|
1474
|
-
const configured = process.env.KIBI_RUNTIME_DIR ?? process.env.XDG_RUNTIME_DIR ??
|
|
1475
|
-
|
|
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`;
|
|
1476
2082
|
for (const candidate of candidates) {
|
|
1477
2083
|
try {
|
|
2084
|
+
try {
|
|
2085
|
+
if (lstatSync3(candidate).isSymbolicLink())
|
|
2086
|
+
continue;
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
if (error.code !== "ENOENT")
|
|
2089
|
+
throw error;
|
|
2090
|
+
}
|
|
1478
2091
|
mkdirSync2(candidate, { recursive: true, mode: 448 });
|
|
1479
|
-
const
|
|
1480
|
-
|
|
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" });
|
|
1481
2100
|
unlinkSync(probe);
|
|
1482
|
-
return
|
|
2101
|
+
return canonical;
|
|
1483
2102
|
} catch {}
|
|
1484
2103
|
}
|
|
1485
2104
|
throw new Error("Unable to create a writable Kibi engine runtime directory");
|
|
1486
2105
|
}
|
|
1487
2106
|
function fsyncPath(filePath) {
|
|
1488
|
-
if (!
|
|
2107
|
+
if (!existsSync6(filePath))
|
|
1489
2108
|
return;
|
|
1490
2109
|
let descriptor = null;
|
|
1491
2110
|
try {
|
|
1492
|
-
descriptor =
|
|
2111
|
+
descriptor = openSync2(filePath, "r");
|
|
1493
2112
|
fsyncSync(descriptor);
|
|
1494
2113
|
} finally {
|
|
1495
2114
|
if (descriptor !== null)
|
|
1496
|
-
|
|
2115
|
+
closeSync2(descriptor);
|
|
1497
2116
|
}
|
|
1498
2117
|
}
|
|
1499
2118
|
function fsyncJournaledBranchStore(branchPath) {
|
|
1500
|
-
const rdfRoot =
|
|
2119
|
+
const rdfRoot = path7.join(branchPath, "rdf");
|
|
1501
2120
|
const directories = [];
|
|
1502
|
-
const pending =
|
|
2121
|
+
const pending = existsSync6(rdfRoot) ? [rdfRoot] : [];
|
|
1503
2122
|
while (pending.length > 0) {
|
|
1504
2123
|
const directory = pending.pop();
|
|
1505
2124
|
if (directory === undefined)
|
|
1506
2125
|
break;
|
|
1507
2126
|
directories.push(directory);
|
|
1508
2127
|
for (const entry of readdirSync2(directory, { withFileTypes: true })) {
|
|
1509
|
-
const entryPath =
|
|
2128
|
+
const entryPath = path7.join(directory, entry.name);
|
|
1510
2129
|
if (entry.isDirectory())
|
|
1511
2130
|
pending.push(entryPath);
|
|
1512
2131
|
else if (entry.isFile() && entry.name !== "lock")
|
|
@@ -1514,7 +2133,7 @@ function fsyncJournaledBranchStore(branchPath) {
|
|
|
1514
2133
|
}
|
|
1515
2134
|
}
|
|
1516
2135
|
for (const metadata of ["CURRENT", "storage.json", "kb.rdf"]) {
|
|
1517
|
-
fsyncPath(
|
|
2136
|
+
fsyncPath(path7.join(branchPath, metadata));
|
|
1518
2137
|
}
|
|
1519
2138
|
if (process.platform !== "win32") {
|
|
1520
2139
|
for (const directory of directories.reverse())
|
|
@@ -1523,33 +2142,27 @@ function fsyncJournaledBranchStore(branchPath) {
|
|
|
1523
2142
|
}
|
|
1524
2143
|
}
|
|
1525
2144
|
function engineSocketPath(workspaceRoot, branch) {
|
|
1526
|
-
const canonicalRoot = (
|
|
1527
|
-
try {
|
|
1528
|
-
return realpathSync(workspaceRoot);
|
|
1529
|
-
} catch {
|
|
1530
|
-
return path4.resolve(workspaceRoot);
|
|
1531
|
-
}
|
|
1532
|
-
})();
|
|
2145
|
+
const canonicalRoot = canonicalFilesystemPath(workspaceRoot);
|
|
1533
2146
|
const key = createHash2("sha256").update(`${canonicalRoot}\x00${branch}`).digest("hex").slice(0, 32);
|
|
1534
2147
|
if (process.platform === "win32") {
|
|
1535
2148
|
return `\\\\.\\pipe\\kibi-${key}`;
|
|
1536
2149
|
}
|
|
1537
|
-
return
|
|
2150
|
+
return path7.join(runtimeDirectory(), `kibi-${key}.sock`);
|
|
1538
2151
|
}
|
|
1539
2152
|
function enginePidPath(workspaceRoot, branch) {
|
|
1540
2153
|
const socket = engineSocketPath(workspaceRoot, branch);
|
|
1541
2154
|
if (process.platform === "win32") {
|
|
1542
|
-
return
|
|
2155
|
+
return path7.join(runtimeDirectory(), `kibi-${createHash2("sha256").update(`${path7.resolve(workspaceRoot)}\x00${branch}`).digest("hex")}.pid`);
|
|
1543
2156
|
}
|
|
1544
2157
|
return `${socket}.pid`;
|
|
1545
2158
|
}
|
|
1546
2159
|
function recoverStaleJournalLock(branchPath) {
|
|
1547
|
-
const lockPath =
|
|
1548
|
-
if (!
|
|
2160
|
+
const lockPath = path7.join(branchPath, "rdf", "lock");
|
|
2161
|
+
if (!existsSync6(lockPath))
|
|
1549
2162
|
return;
|
|
1550
2163
|
let contents = "";
|
|
1551
2164
|
try {
|
|
1552
|
-
contents =
|
|
2165
|
+
contents = readFileSync4(lockPath, "utf8");
|
|
1553
2166
|
} catch {
|
|
1554
2167
|
return;
|
|
1555
2168
|
}
|
|
@@ -1597,27 +2210,27 @@ function connectSocket(socketPath, timeoutMs) {
|
|
|
1597
2210
|
}
|
|
1598
2211
|
async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
1599
2212
|
mkdirSync2(branchPath, { recursive: true });
|
|
1600
|
-
const markerPath =
|
|
1601
|
-
const marker =
|
|
2213
|
+
const markerPath = path7.join(branchPath, "storage.json");
|
|
2214
|
+
const marker = existsSync6(markerPath) ? readFileSync4(markerPath, "utf8") : "";
|
|
1602
2215
|
if (marker.includes("kibi.rdf-journal.v1")) {
|
|
1603
|
-
const rdfDirectory =
|
|
1604
|
-
const currentPath =
|
|
1605
|
-
const sentinelPath =
|
|
1606
|
-
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)) {
|
|
1607
2220
|
recoverInterruptedGeneration(branchPath);
|
|
1608
|
-
if (
|
|
2221
|
+
if (existsSync6(rdfDirectory) && existsSync6(currentPath)) {
|
|
1609
2222
|
recoverStaleJournalLock(branchPath);
|
|
1610
2223
|
archiveLegacyFiles(branchPath);
|
|
1611
|
-
if (!
|
|
2224
|
+
if (!existsSync6(sentinelPath)) {
|
|
1612
2225
|
writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1613
2226
|
`, { mode: 384 });
|
|
1614
2227
|
}
|
|
1615
2228
|
return;
|
|
1616
2229
|
}
|
|
1617
|
-
const legacyRdf =
|
|
1618
|
-
const legacyAudit =
|
|
1619
|
-
const legacyContents =
|
|
1620
|
-
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")) {
|
|
1621
2234
|
rmSync2(rdfDirectory, { recursive: true, force: true });
|
|
1622
2235
|
rmSync2(currentPath, { force: true });
|
|
1623
2236
|
rmSync2(markerPath, { force: true });
|
|
@@ -1627,36 +2240,36 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
1627
2240
|
} else {
|
|
1628
2241
|
recoverStaleJournalLock(branchPath);
|
|
1629
2242
|
archiveLegacyFiles(branchPath);
|
|
1630
|
-
if (!
|
|
2243
|
+
if (!existsSync6(sentinelPath)) {
|
|
1631
2244
|
writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1632
2245
|
`, { mode: 384 });
|
|
1633
2246
|
}
|
|
1634
2247
|
return;
|
|
1635
2248
|
}
|
|
1636
2249
|
}
|
|
1637
|
-
const legacyRdf =
|
|
1638
|
-
const legacyAudit =
|
|
1639
|
-
if (!
|
|
1640
|
-
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 });
|
|
1641
2254
|
writeFileSync2(markerPath, `{"format":"kibi.rdf-journal.v1","schemaVersion":1}
|
|
1642
2255
|
`, { mode: 384 });
|
|
1643
|
-
writeFileSync2(
|
|
2256
|
+
writeFileSync2(path7.join(branchPath, "CURRENT"), `generation-1:0
|
|
1644
2257
|
`, {
|
|
1645
2258
|
mode: 384
|
|
1646
2259
|
});
|
|
1647
|
-
writeFileSync2(
|
|
2260
|
+
writeFileSync2(path7.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1648
2261
|
`, { mode: 384 });
|
|
1649
2262
|
return;
|
|
1650
2263
|
}
|
|
1651
|
-
if (
|
|
1652
|
-
const legacyContents =
|
|
2264
|
+
if (existsSync6(legacyRdf)) {
|
|
2265
|
+
const legacyContents = readFileSync4(legacyRdf, "utf8");
|
|
1653
2266
|
if (!/<rdf:RDF(?:\s|>)/.test(legacyContents)) {
|
|
1654
2267
|
throw new Error(`Legacy Kibi store is corrupt at ${legacyRdf}: expected RDF/XML root`);
|
|
1655
2268
|
}
|
|
1656
2269
|
}
|
|
1657
|
-
const parent =
|
|
1658
|
-
const migrationKey = createHash2("sha256").update(
|
|
1659
|
-
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()}`);
|
|
1660
2273
|
mkdirSync2(staging, { recursive: true, mode: 448 });
|
|
1661
2274
|
const testOneShot = false;
|
|
1662
2275
|
const migrator = new PrologProcess({
|
|
@@ -1684,35 +2297,35 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
|
|
|
1684
2297
|
await migrator.terminate();
|
|
1685
2298
|
}
|
|
1686
2299
|
for (const file of ["rdf", "storage.json", "CURRENT"]) {
|
|
1687
|
-
const source =
|
|
1688
|
-
if (!
|
|
2300
|
+
const source = path7.join(staging, file);
|
|
2301
|
+
if (!existsSync6(source))
|
|
1689
2302
|
continue;
|
|
1690
|
-
const target =
|
|
1691
|
-
if (
|
|
2303
|
+
const target = path7.join(branchPath, file);
|
|
2304
|
+
if (existsSync6(target))
|
|
1692
2305
|
rmSync2(target, { recursive: true, force: true });
|
|
1693
2306
|
renameSync(source, target);
|
|
1694
2307
|
}
|
|
1695
2308
|
archiveLegacyFiles(branchPath);
|
|
1696
|
-
writeFileSync2(
|
|
2309
|
+
writeFileSync2(path7.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
|
|
1697
2310
|
`, { mode: 384 });
|
|
1698
2311
|
fsyncJournaledBranchStore(branchPath);
|
|
1699
2312
|
rmSync2(staging, { recursive: true, force: true });
|
|
1700
2313
|
}
|
|
1701
2314
|
function archiveLegacyFiles(branchPath) {
|
|
1702
|
-
const legacyDir =
|
|
2315
|
+
const legacyDir = path7.join(branchPath, "legacy");
|
|
1703
2316
|
mkdirSync2(legacyDir, { recursive: true, mode: 448 });
|
|
1704
2317
|
for (const file of ["kb.rdf", "audit.log"]) {
|
|
1705
|
-
const source =
|
|
1706
|
-
if (!
|
|
2318
|
+
const source = path7.join(branchPath, file);
|
|
2319
|
+
if (!existsSync6(source))
|
|
1707
2320
|
continue;
|
|
1708
2321
|
if (file === "kb.rdf") {
|
|
1709
|
-
const contents =
|
|
2322
|
+
const contents = readFileSync4(source, "utf8");
|
|
1710
2323
|
if (contents.includes("kibi.rdf-journal.v1"))
|
|
1711
2324
|
continue;
|
|
1712
2325
|
}
|
|
1713
|
-
const destination =
|
|
1714
|
-
if (
|
|
1715
|
-
renameSync(source,
|
|
2326
|
+
const destination = path7.join(legacyDir, file);
|
|
2327
|
+
if (existsSync6(destination)) {
|
|
2328
|
+
renameSync(source, path7.join(legacyDir, `${file}.recovery.${Date.now()}`));
|
|
1716
2329
|
} else {
|
|
1717
2330
|
renameSync(source, destination);
|
|
1718
2331
|
}
|
|
@@ -1728,62 +2341,123 @@ function recoverInterruptedGeneration(branchPath) {
|
|
|
1728
2341
|
const newest = (prefix) => {
|
|
1729
2342
|
const candidates = entries.filter((entry) => entry.startsWith(prefix)).sort().reverse();
|
|
1730
2343
|
const candidate = candidates[0];
|
|
1731
|
-
return candidate === undefined ? null :
|
|
2344
|
+
return candidate === undefined ? null : path7.join(branchPath, candidate);
|
|
1732
2345
|
};
|
|
1733
2346
|
const oldRdf = newest("rdf.old.");
|
|
1734
2347
|
const oldCurrent = newest("CURRENT.old.");
|
|
1735
|
-
const rdfPath =
|
|
1736
|
-
const currentPath =
|
|
1737
|
-
if ((!
|
|
2348
|
+
const rdfPath = path7.join(branchPath, "rdf");
|
|
2349
|
+
const currentPath = path7.join(branchPath, "CURRENT");
|
|
2350
|
+
if ((!existsSync6(rdfPath) || !existsSync6(currentPath)) && oldRdf && oldCurrent) {
|
|
1738
2351
|
try {
|
|
1739
|
-
if (
|
|
2352
|
+
if (existsSync6(rdfPath))
|
|
1740
2353
|
rmSync2(rdfPath, { recursive: true, force: true });
|
|
1741
|
-
if (
|
|
2354
|
+
if (existsSync6(currentPath))
|
|
1742
2355
|
rmSync2(currentPath, { force: true });
|
|
1743
2356
|
renameSync(oldRdf, rdfPath);
|
|
1744
2357
|
renameSync(oldCurrent, currentPath);
|
|
1745
2358
|
return;
|
|
1746
2359
|
} catch {}
|
|
1747
2360
|
}
|
|
1748
|
-
if (!
|
|
2361
|
+
if (!existsSync6(rdfPath) && oldRdf) {
|
|
1749
2362
|
try {
|
|
1750
2363
|
renameSync(oldRdf, rdfPath);
|
|
1751
2364
|
} catch {}
|
|
1752
2365
|
}
|
|
1753
|
-
if (!
|
|
2366
|
+
if (!existsSync6(currentPath) && oldCurrent) {
|
|
1754
2367
|
try {
|
|
1755
2368
|
renameSync(oldCurrent, currentPath);
|
|
1756
2369
|
} catch {}
|
|
1757
2370
|
}
|
|
1758
2371
|
}
|
|
1759
|
-
|
|
1760
|
-
if (
|
|
1761
|
-
|
|
2372
|
+
function isEngineLifecycleRequest(request) {
|
|
2373
|
+
if (request.method === "stop" || request.method === "cancel" || request.method === "handshake")
|
|
2374
|
+
return true;
|
|
2375
|
+
const kind = request.command?.kind;
|
|
2376
|
+
return request.method === "command" && (kind === "stop" || kind === "cancel" || kind === "lifecycle");
|
|
2377
|
+
}
|
|
2378
|
+
var ENGINE_PRELOADED_MODULES = [
|
|
2379
|
+
["status.pl", "status"],
|
|
2380
|
+
["discovery.pl", "discovery"]
|
|
2381
|
+
];
|
|
2382
|
+
var USE_MODULE_GOAL = /^use_module\('(?:[^']|'')*'\)$/u;
|
|
2383
|
+
|
|
2384
|
+
class EngineSession {
|
|
2385
|
+
prolog;
|
|
2386
|
+
branchPath;
|
|
2387
|
+
modules = [];
|
|
2388
|
+
constructor(prolog, branchPath) {
|
|
2389
|
+
this.prolog = prolog;
|
|
2390
|
+
this.branchPath = branchPath;
|
|
2391
|
+
const coreModuleDir = path7.dirname(resolveKbPlPath());
|
|
2392
|
+
for (const [fileName, label] of ENGINE_PRELOADED_MODULES) {
|
|
2393
|
+
const modulePath = path7.join(coreModuleDir, fileName);
|
|
2394
|
+
this.modules.push({
|
|
2395
|
+
goal: `use_module('${quoteProlog(modulePath.replaceAll("\\", "/"))}')`,
|
|
2396
|
+
label
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
async boot() {
|
|
2401
|
+
await this.prolog.start();
|
|
2402
|
+
const attached = await this.prolog.query(`kb_attach('${quoteProlog(this.branchPath)}')`);
|
|
2403
|
+
if (!attached.success) {
|
|
2404
|
+
const recovered = await retryAttachAfterBreakingStaleLock(this.prolog, this.branchPath, attached);
|
|
2405
|
+
if (!recovered.success)
|
|
2406
|
+
throw new Error(recovered.error ?? "Failed to attach branch KB");
|
|
2407
|
+
}
|
|
2408
|
+
for (const { goal, label } of this.modules) {
|
|
2409
|
+
const loaded = await this.prolog.query(goal);
|
|
2410
|
+
if (!loaded.success) {
|
|
2411
|
+
throw new Error(loaded.error ?? `Failed to load Kibi ${label} module`);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
recordModuleLoad(goal, result) {
|
|
2416
|
+
const normalized = goal.trim().replace(/\.+\s*$/u, "");
|
|
2417
|
+
if (result.success && USE_MODULE_GOAL.test(normalized) && !this.modules.some((module) => module.goal === normalized)) {
|
|
2418
|
+
this.modules.push({ goal: normalized, label: "client-loaded" });
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
needsRecycle() {
|
|
2422
|
+
return this.prolog.needsRestart();
|
|
2423
|
+
}
|
|
2424
|
+
async ensureLive() {
|
|
2425
|
+
if (!this.prolog.needsRestart())
|
|
2426
|
+
return false;
|
|
2427
|
+
console.error("[KIBI] engine Prolog session was lost (output overflow, timeout, or crash); restarting and reattaching the branch store");
|
|
2428
|
+
try {
|
|
2429
|
+
await this.boot();
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
await this.prolog.terminate().catch(() => {
|
|
2432
|
+
return;
|
|
2433
|
+
});
|
|
2434
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2435
|
+
throw new Error(`Kibi engine failed to recycle its Prolog session: ${message}`);
|
|
2436
|
+
}
|
|
2437
|
+
return true;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
async function runEngineDaemon(requestedOptions) {
|
|
2441
|
+
if (!isValidBranchName(requestedOptions.branch)) {
|
|
2442
|
+
throw new Error(`Invalid Kibi engine branch name: ${requestedOptions.branch}`);
|
|
1762
2443
|
}
|
|
2444
|
+
const options = {
|
|
2445
|
+
...requestedOptions,
|
|
2446
|
+
workspaceRoot: canonicalFilesystemPath(requestedOptions.workspaceRoot)
|
|
2447
|
+
};
|
|
1763
2448
|
const branchPath = ensureBranchStoreManifest(options.workspaceRoot, options.branch);
|
|
1764
2449
|
await ensureJournaledBranchStoreAsync(branchPath);
|
|
1765
|
-
const prolog = new PrologProcess({
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1775
|
-
|
|
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
|
-
}
|
|
1785
|
-
mkdirSync2(path4.dirname(options.socketPath), { recursive: true, mode: 448 });
|
|
1786
|
-
if (existsSync4(options.socketPath)) {
|
|
2450
|
+
const prolog = new PrologProcess({
|
|
2451
|
+
timeout: 120000,
|
|
2452
|
+
oneShot: false,
|
|
2453
|
+
...options.maxOutputBytes === undefined ? {} : { maxOutputBytes: options.maxOutputBytes }
|
|
2454
|
+
});
|
|
2455
|
+
const session = new EngineSession(prolog, branchPath);
|
|
2456
|
+
await session.boot();
|
|
2457
|
+
let attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
2458
|
+
const serverPrologIdentity = engineSwiplIdentity();
|
|
2459
|
+
mkdirSync2(path7.dirname(options.socketPath), { recursive: true, mode: 448 });
|
|
2460
|
+
if (existsSync6(options.socketPath)) {
|
|
1787
2461
|
const isLivePeerSignal = (error) => {
|
|
1788
2462
|
if (error && typeof error === "object") {
|
|
1789
2463
|
const code = "code" in error ? String(error.code) : "";
|
|
@@ -1835,6 +2509,10 @@ async function runEngineDaemon(options) {
|
|
|
1835
2509
|
let freshnessCache = null;
|
|
1836
2510
|
const clients = new Set;
|
|
1837
2511
|
let shuttingDown = false;
|
|
2512
|
+
let finishShutdown;
|
|
2513
|
+
const shutdownFinished = new Promise((resolve) => {
|
|
2514
|
+
finishShutdown = resolve;
|
|
2515
|
+
});
|
|
1838
2516
|
const exactBranchStatus = (result) => {
|
|
1839
2517
|
if (!result.success || typeof result.bindings.JsonString !== "string") {
|
|
1840
2518
|
return result;
|
|
@@ -1863,7 +2541,7 @@ async function runEngineDaemon(options) {
|
|
|
1863
2541
|
const workspaceWatchdog = setInterval(() => {
|
|
1864
2542
|
if (shuttingDown)
|
|
1865
2543
|
return;
|
|
1866
|
-
if (!
|
|
2544
|
+
if (!existsSync6(options.workspaceRoot)) {
|
|
1867
2545
|
workspaceMissingStreak += 1;
|
|
1868
2546
|
if (workspaceMissingStreak < 2)
|
|
1869
2547
|
return;
|
|
@@ -1875,12 +2553,12 @@ async function runEngineDaemon(options) {
|
|
|
1875
2553
|
}, engineWorkspaceWatchdogMs());
|
|
1876
2554
|
workspaceWatchdog.unref();
|
|
1877
2555
|
const scheduleIdleExit = () => {
|
|
1878
|
-
if (activeClients > 0)
|
|
2556
|
+
if (shuttingDown || activeClients > 0)
|
|
1879
2557
|
return;
|
|
1880
2558
|
if (!idleCompactionQueued) {
|
|
1881
2559
|
idleCompactionQueued = true;
|
|
1882
2560
|
queue = queue.then(async () => {
|
|
1883
|
-
if (activeClients === 0) {
|
|
2561
|
+
if (activeClients === 0 && !session.needsRecycle()) {
|
|
1884
2562
|
await prolog.query("kb_storage_compact_if_needed");
|
|
1885
2563
|
}
|
|
1886
2564
|
}).catch(() => {
|
|
@@ -1904,9 +2582,17 @@ async function runEngineDaemon(options) {
|
|
|
1904
2582
|
if (expectedPackages !== undefined && request.packageVersions !== expectedPackages) {
|
|
1905
2583
|
throw new Error(`Kibi engine package-version mismatch: client=${request.packageVersions ?? "missing"}, server=${expectedPackages}`);
|
|
1906
2584
|
}
|
|
1907
|
-
if (
|
|
2585
|
+
if (canonicalFilesystemPath(request.workspaceRoot ?? "") !== options.workspaceRoot || request.branch !== options.branch) {
|
|
1908
2586
|
throw new Error("Kibi engine workspace identity mismatch");
|
|
1909
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
|
+
}
|
|
2591
|
+
if (!isEngineLifecycleRequest(request) && await session.ensureLive()) {
|
|
2592
|
+
queryCache.clear();
|
|
2593
|
+
freshnessCache = null;
|
|
2594
|
+
attachedIdentity = readEngineAttachmentIdentity(branchPath);
|
|
2595
|
+
}
|
|
1910
2596
|
switch (request.method) {
|
|
1911
2597
|
case "entities": {
|
|
1912
2598
|
const limit = request.limit ?? 100;
|
|
@@ -2042,7 +2728,14 @@ async function runEngineDaemon(options) {
|
|
|
2042
2728
|
}
|
|
2043
2729
|
}
|
|
2044
2730
|
case "kbStatus": {
|
|
2731
|
+
const freshnessCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance2.now() : null;
|
|
2045
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
|
+
}
|
|
2046
2739
|
return freshnessCache.result;
|
|
2047
2740
|
}
|
|
2048
2741
|
const result = exactBranchStatus(await prolog.query("status:kb_status_json(JsonString)"));
|
|
@@ -2062,11 +2755,20 @@ async function runEngineDaemon(options) {
|
|
|
2062
2755
|
freshnessCache = null;
|
|
2063
2756
|
}
|
|
2064
2757
|
if (cacheableEngineGoal(request.goal)) {
|
|
2758
|
+
const queryCacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance2.now() : null;
|
|
2065
2759
|
const cached = queryCache.get(request.goal);
|
|
2066
|
-
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
|
+
}
|
|
2067
2767
|
return cached;
|
|
2768
|
+
}
|
|
2068
2769
|
}
|
|
2069
2770
|
const result = await prolog.query(request.goal);
|
|
2771
|
+
session.recordModuleLoad(request.goal, result);
|
|
2070
2772
|
if (result.success && mutatingEngineGoal(request.goal)) {
|
|
2071
2773
|
fsyncJournaledBranchStore(branchPath);
|
|
2072
2774
|
}
|
|
@@ -2100,6 +2802,8 @@ async function runEngineDaemon(options) {
|
|
|
2100
2802
|
if (typeof request.targetDirectory !== "string")
|
|
2101
2803
|
throw new Error("export.targetDirectory is required");
|
|
2102
2804
|
return prolog.query(`kb_storage_export('${quoteProlog(request.targetDirectory)}')`);
|
|
2805
|
+
case "handshake":
|
|
2806
|
+
return { prologIdentity: serverPrologIdentity };
|
|
2103
2807
|
case "stop":
|
|
2104
2808
|
setImmediate(() => void shutdown());
|
|
2105
2809
|
return { stopped: true };
|
|
@@ -2119,7 +2823,11 @@ async function runEngineDaemon(options) {
|
|
|
2119
2823
|
if (idleTimer)
|
|
2120
2824
|
clearTimeout(idleTimer);
|
|
2121
2825
|
clearInterval(workspaceWatchdog);
|
|
2122
|
-
|
|
2826
|
+
server.close();
|
|
2827
|
+
await queue.catch(() => {
|
|
2828
|
+
return;
|
|
2829
|
+
});
|
|
2830
|
+
const saved = session.needsRecycle() ? null : await prolog.query("kb_save").catch(() => null);
|
|
2123
2831
|
if (saved?.success) {
|
|
2124
2832
|
try {
|
|
2125
2833
|
fsyncJournaledBranchStore(branchPath);
|
|
@@ -2130,22 +2838,29 @@ async function runEngineDaemon(options) {
|
|
|
2130
2838
|
});
|
|
2131
2839
|
for (const client of clients)
|
|
2132
2840
|
client.destroy();
|
|
2133
|
-
server.close();
|
|
2134
2841
|
server.unref();
|
|
2135
2842
|
try {
|
|
2136
|
-
if (
|
|
2843
|
+
if (existsSync6(options.socketPath))
|
|
2137
2844
|
rmSync2(options.socketPath, { force: true });
|
|
2138
|
-
if (
|
|
2845
|
+
if (existsSync6(enginePidPath(options.workspaceRoot, options.branch)))
|
|
2139
2846
|
rmSync2(enginePidPath(options.workspaceRoot, options.branch), {
|
|
2140
2847
|
force: true
|
|
2141
2848
|
});
|
|
2142
2849
|
} catch {}
|
|
2143
2850
|
setTimeout(() => process.exit(0), 50).unref();
|
|
2851
|
+
finishShutdown();
|
|
2144
2852
|
};
|
|
2145
2853
|
const requestSignalShutdown = requestEngineSignalShutdown(shutdown);
|
|
2146
2854
|
process.once("SIGTERM", requestSignalShutdown);
|
|
2147
2855
|
process.once("SIGINT", requestSignalShutdown);
|
|
2148
2856
|
server.on("connection", (socket) => {
|
|
2857
|
+
socket.on("error", () => {
|
|
2858
|
+
return;
|
|
2859
|
+
});
|
|
2860
|
+
if (shuttingDown) {
|
|
2861
|
+
socket.destroy();
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2149
2864
|
clients.add(socket);
|
|
2150
2865
|
activeClients += 1;
|
|
2151
2866
|
const cancelledRequests = new Set;
|
|
@@ -2155,6 +2870,15 @@ async function runEngineDaemon(options) {
|
|
|
2155
2870
|
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
2156
2871
|
buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
|
|
2157
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
|
+
}
|
|
2158
2882
|
if (request.method === "cancel" && typeof request.cancelOf === "number") {
|
|
2159
2883
|
cancelledRequests.add(request.cancelOf);
|
|
2160
2884
|
}
|
|
@@ -2213,6 +2937,7 @@ async function runEngineDaemon(options) {
|
|
|
2213
2937
|
await new Promise((resolve) => {
|
|
2214
2938
|
server.once("close", resolve);
|
|
2215
2939
|
});
|
|
2940
|
+
await shutdownFinished;
|
|
2216
2941
|
}
|
|
2217
2942
|
|
|
2218
2943
|
// ../cli/src/engine-daemon.ts
|
|
@@ -2224,7 +2949,7 @@ function isEngineDaemonEntrypoint(argv = process.argv, moduleUrl = import.meta.u
|
|
|
2224
2949
|
if (typeof entry !== "string" || entry.length === 0)
|
|
2225
2950
|
return false;
|
|
2226
2951
|
try {
|
|
2227
|
-
return moduleUrl === pathToFileURL(
|
|
2952
|
+
return moduleUrl === pathToFileURL(path8.resolve(entry)).href;
|
|
2228
2953
|
} catch {
|
|
2229
2954
|
return false;
|
|
2230
2955
|
}
|