@kognai/orchestrator-core 0.1.1 → 0.1.3
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/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lib/did-resolver-registry.d.ts +15 -0
- package/dist/lib/did-resolver-registry.js +9 -0
- package/dist/lib/engine-agents.d.ts +71 -0
- package/dist/lib/engine-agents.js +835 -0
- package/dist/lib/engine-coding-agent.d.ts +17 -0
- package/dist/lib/engine-coding-agent.js +890 -0
- package/dist/lib/engine-helpers.d.ts +10 -0
- package/dist/lib/engine-helpers.js +319 -0
- package/dist/lib/engine-loaders.d.ts +5 -0
- package/dist/lib/engine-loaders.js +241 -0
- package/dist/lib/engine-orchestrator.d.ts +46 -0
- package/dist/lib/engine-orchestrator.js +1491 -0
- package/dist/lib/engine-primitives.d.ts +141 -0
- package/dist/lib/engine-primitives.js +748 -0
- package/dist/lib/orchestrate-engine.d.ts +14 -1
- package/dist/lib/orchestrate-engine.js +26 -4333
- package/dist/lib/plumber/extractor.d.ts +18 -0
- package/dist/lib/plumber/extractor.js +213 -0
- package/dist/lib/plumber/fixer.d.ts +75 -0
- package/dist/lib/plumber/fixer.js +165 -0
- package/dist/lib/plumber/index.d.ts +3 -0
- package/dist/lib/plumber/index.js +3 -0
- package/dist/lib/plumber/patcher.d.ts +44 -0
- package/dist/lib/plumber/patcher.js +210 -0
- package/dist/lib/preamble-provider-registry.d.ts +12 -0
- package/dist/lib/preamble-provider-registry.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyPatches = applyPatches;
|
|
4
|
+
exports.writeFileAtomic = writeFileAtomic;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
/**
|
|
9
|
+
* Count non-overlapping occurrences of `needle` in `haystack`.
|
|
10
|
+
* Uses indexOf to avoid regex pitfalls (no special-char escaping needed).
|
|
11
|
+
*/
|
|
12
|
+
function countOccurrences(haystack, needle) {
|
|
13
|
+
if (needle.length === 0)
|
|
14
|
+
return 0;
|
|
15
|
+
let count = 0;
|
|
16
|
+
let from = 0;
|
|
17
|
+
// Non-overlapping scan: advance past the full match each hit.
|
|
18
|
+
while (from <= haystack.length) {
|
|
19
|
+
const idx = haystack.indexOf(needle, from);
|
|
20
|
+
if (idx === -1)
|
|
21
|
+
break;
|
|
22
|
+
count += 1;
|
|
23
|
+
from = idx + needle.length;
|
|
24
|
+
}
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Replace the first (and, by precondition, only) occurrence of `needle`
|
|
29
|
+
* with `replacement` in `haystack`. Returns the resulting string.
|
|
30
|
+
* If the needle is not found, returns the original string unchanged.
|
|
31
|
+
*/
|
|
32
|
+
function replaceOnce(haystack, needle, replacement) {
|
|
33
|
+
const idx = haystack.indexOf(needle);
|
|
34
|
+
if (idx === -1)
|
|
35
|
+
return haystack;
|
|
36
|
+
return (haystack.slice(0, idx) + replacement + haystack.slice(idx + needle.length));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Truncate a string for inclusion in human-readable failure messages.
|
|
40
|
+
* Keeps logs scannable when anchors are large.
|
|
41
|
+
*/
|
|
42
|
+
function previewAnchor(text, max = 80) {
|
|
43
|
+
const collapsed = text.replace(/\r?\n/g, "\\n");
|
|
44
|
+
if (collapsed.length <= max)
|
|
45
|
+
return collapsed;
|
|
46
|
+
return collapsed.slice(0, max) + "…";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Apply a list of anchored string-replacement patches to `source`.
|
|
50
|
+
*
|
|
51
|
+
* Contract:
|
|
52
|
+
* - Pure: does NOT touch the filesystem or any other I/O.
|
|
53
|
+
* - For each patch, `old` MUST occur EXACTLY ONCE in `source`. If it occurs
|
|
54
|
+
* zero times or more than once, that patch is recorded as a failure.
|
|
55
|
+
* - All-or-nothing: if ANY patch fails, the function aborts and applies
|
|
56
|
+
* NOTHING. `ok` is false, `content` is undefined, `applied` is 0, and
|
|
57
|
+
* `failures` contains one entry per failing patch.
|
|
58
|
+
* - On full success, every patch's `old` is replaced by its `new` in the
|
|
59
|
+
* order given, and the final `content` is returned with `ok: true`.
|
|
60
|
+
*
|
|
61
|
+
* Validation is performed against the ORIGINAL source (pre-mutation) so that
|
|
62
|
+
* patch ordering cannot accidentally mask a duplicate anchor. We re-validate
|
|
63
|
+
* uniqueness against the in-progress buffer during application only as a
|
|
64
|
+
* defensive check; if that ever trips, the entire operation is aborted.
|
|
65
|
+
*/
|
|
66
|
+
function applyPatches(source, patches) {
|
|
67
|
+
if (typeof source !== "string") {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
applied: 0,
|
|
71
|
+
failures: ["source must be a string"],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (!Array.isArray(patches)) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
applied: 0,
|
|
78
|
+
failures: ["patches must be an array"],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Empty patch list is a no-op success: nothing to do, source unchanged.
|
|
82
|
+
if (patches.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
content: source,
|
|
86
|
+
applied: 0,
|
|
87
|
+
failures: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const failures = [];
|
|
91
|
+
// ---- Phase 1: validate every patch against the ORIGINAL source. ----
|
|
92
|
+
// We do not mutate anything yet. This guarantees all-or-nothing semantics
|
|
93
|
+
// and avoids the trap where applying patch[0] could change the occurrence
|
|
94
|
+
// count of patch[1]'s anchor mid-flight.
|
|
95
|
+
for (let i = 0; i < patches.length; i += 1) {
|
|
96
|
+
const patch = patches[i];
|
|
97
|
+
if (!patch || typeof patch !== "object") {
|
|
98
|
+
failures.push(`patch[${i}]: must be an object with { old, new }`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (typeof patch.old !== "string") {
|
|
102
|
+
failures.push(`patch[${i}]: \`old\` must be a string`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (typeof patch.new !== "string") {
|
|
106
|
+
failures.push(`patch[${i}]: \`new\` must be a string`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (patch.old.length === 0) {
|
|
110
|
+
failures.push(`patch[${i}]: \`old\` must be non-empty`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const occurrences = countOccurrences(source, patch.old);
|
|
114
|
+
if (occurrences === 0) {
|
|
115
|
+
failures.push(`patch[${i}]: anchor not found (\`${previewAnchor(patch.old)}\`)`);
|
|
116
|
+
}
|
|
117
|
+
else if (occurrences > 1) {
|
|
118
|
+
failures.push(`patch[${i}]: anchor matches ${occurrences} times, must be exactly 1 (\`${previewAnchor(patch.old)}\`)`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (failures.length > 0) {
|
|
122
|
+
// All-or-nothing: abort with NO mutation.
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
applied: 0,
|
|
126
|
+
failures,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// ---- Phase 2: apply patches in order against a working buffer. ----
|
|
130
|
+
// Defensive: each step re-checks that its anchor is still uniquely present
|
|
131
|
+
// in the in-progress buffer. If a previous patch's `new` text happens to
|
|
132
|
+
// collide with a later patch's `old`, we refuse rather than guessing.
|
|
133
|
+
let working = source;
|
|
134
|
+
let applied = 0;
|
|
135
|
+
for (let i = 0; i < patches.length; i += 1) {
|
|
136
|
+
const patch = patches[i]; // validated above
|
|
137
|
+
const occurrences = countOccurrences(working, patch.old);
|
|
138
|
+
if (occurrences !== 1) {
|
|
139
|
+
// Anchor collision introduced by an earlier replacement. Abort entirely.
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
applied: 0,
|
|
143
|
+
failures: [
|
|
144
|
+
`patch[${i}]: anchor became ambiguous after earlier patches (now matches ${occurrences} times)`,
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
working = replaceOnce(working, patch.old, patch.new);
|
|
149
|
+
applied += 1;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
content: working,
|
|
154
|
+
applied,
|
|
155
|
+
failures: [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Atomically write `content` to `path`.
|
|
160
|
+
*
|
|
161
|
+
* Strategy:
|
|
162
|
+
* 1. Ensure the parent directory exists (mkdir -p).
|
|
163
|
+
* 2. Write to a sibling temp file (same directory, so rename is atomic
|
|
164
|
+
* on POSIX filesystems — cross-device renames are not atomic).
|
|
165
|
+
* 3. Rename temp file over the target path.
|
|
166
|
+
* 4. On any failure after the temp write, attempt to clean up the temp
|
|
167
|
+
* file; surface the original error to the caller.
|
|
168
|
+
*
|
|
169
|
+
* This guarantees readers never observe a partially-written file: they
|
|
170
|
+
* either see the old bytes or the new bytes, never a torn mix.
|
|
171
|
+
*/
|
|
172
|
+
function writeFileAtomic(path, content) {
|
|
173
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
174
|
+
throw new TypeError("writeFileAtomic: path must be a non-empty string");
|
|
175
|
+
}
|
|
176
|
+
if (typeof content !== "string") {
|
|
177
|
+
throw new TypeError("writeFileAtomic: content must be a string");
|
|
178
|
+
}
|
|
179
|
+
const absolute = (0, node_path_1.resolve)(path);
|
|
180
|
+
const dir = (0, node_path_1.dirname)(absolute);
|
|
181
|
+
const base = (0, node_path_1.basename)(absolute);
|
|
182
|
+
// mkdir -p the parent directory if missing.
|
|
183
|
+
if (!(0, node_fs_1.existsSync)(dir)) {
|
|
184
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
185
|
+
}
|
|
186
|
+
// Unique temp filename in the SAME directory so rename(2) is atomic.
|
|
187
|
+
const suffix = (0, node_crypto_1.randomBytes)(8).toString("hex");
|
|
188
|
+
const tempPath = `${dir}/.${base}.${process.pid}.${suffix}.tmp`;
|
|
189
|
+
try {
|
|
190
|
+
// fsync-equivalent durability is not guaranteed without an explicit
|
|
191
|
+
// flush, but rename atomicity protects readers from torn writes which
|
|
192
|
+
// is the contract Plumber relies on.
|
|
193
|
+
(0, node_fs_1.writeFileSync)(tempPath, content, { encoding: "utf8", mode: 0o644 });
|
|
194
|
+
(0, node_fs_1.renameSync)(tempPath, absolute);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
// Best-effort cleanup; swallow cleanup errors so we surface the real one.
|
|
198
|
+
try {
|
|
199
|
+
// Lazy import to avoid pulling unlinkSync into the hot path above.
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
201
|
+
const { unlinkSync, existsSync: exists } = require("node:fs");
|
|
202
|
+
if (exists(tempPath))
|
|
203
|
+
unlinkSync(tempPath);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// intentionally ignored
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitutional-preamble provider seam — TICKET-226 Phase A1.
|
|
3
|
+
*
|
|
4
|
+
* The "you are a Kognai citizen" constitutional preamble is Kognai polity, not
|
|
5
|
+
* engine-generic. This seam lets a consuming template supply its own preamble (or
|
|
6
|
+
* none). OPTIONAL override: if no provider is set the engine keeps its existing
|
|
7
|
+
* inline behavior — NON-BREAKING. Phase A2 wires Kognai's provider; Phase A3
|
|
8
|
+
* removes the engine's inline default so core ships no Kognai-specific preamble.
|
|
9
|
+
*/
|
|
10
|
+
export type PreambleProvider = () => string;
|
|
11
|
+
export declare function setPreambleProvider(fn: PreambleProvider): void;
|
|
12
|
+
export declare function getPreambleProvider(): PreambleProvider | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setPreambleProvider = setPreambleProvider;
|
|
4
|
+
exports.getPreambleProvider = getPreambleProvider;
|
|
5
|
+
let _provider = null;
|
|
6
|
+
function setPreambleProvider(fn) { _provider = fn; }
|
|
7
|
+
function getPreambleProvider() { return _provider; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kognai/orchestrator-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Kognai sovereign orchestrator — core engine (template-agnostic). Shared by all products (Kognai/coding, Voxight/market-intel, Invoica/fin-compliance); each supplies only its template. Replaces per-repo forks of orchestrate-agents-v2 / sprint-runner / lib.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SkinGem",
|