@hilbras/omninode 2.0.0-alpha.11 → 2.0.0-alpha.12
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/README.md +8 -2
- package/dist/cli/index.js +177 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +56 -11
- package/dist/index.js +161 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ existing intelligence; it does not try to become another model.
|
|
|
18
18
|
|
|
19
19
|
## Status
|
|
20
20
|
|
|
21
|
-
**v2.0.0-alpha.
|
|
21
|
+
**v2.0.0-alpha.12 — v2 Phase 12: Persistence Layer v2**
|
|
22
22
|
(the reliability & interoperability line: architecture → execution →
|
|
23
23
|
protocol → adapters → pipelines → providers → OmniHilbras → memory → reports) (the v2
|
|
24
24
|
roadmap is [docs/ROADMAP_V2.md](docs/ROADMAP_V2.md); the v1 plan is complete:
|
|
@@ -31,6 +31,12 @@ for the plan and [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the design).
|
|
|
31
31
|
|
|
32
32
|
### What's new in v2 (so far)
|
|
33
33
|
|
|
34
|
+
- **Phase 12 — Persistence Layer v2**: writes are now **fsynced and atomic**
|
|
35
|
+
(temp → fsync → rename → directory fsync), corrupt store files are
|
|
36
|
+
**quarantined instead of lost**, documents carry a **schema version** with
|
|
37
|
+
v1 data migrated in place (`omninode migrate [--check]`), and files from a
|
|
38
|
+
newer OmniNode are refused rather than misread. Details:
|
|
39
|
+
[PERSISTENCE.md](docs/PERSISTENCE.md).
|
|
34
40
|
- **Phase 11 — Planner v2**: the planner now receives the full §15 input —
|
|
35
41
|
task, project context, agent reports, **aggregated findings (consensus and
|
|
36
42
|
conflicts included)**, memory and **constraints** (settable per pipeline
|
|
@@ -359,7 +365,7 @@ required for the core engine.
|
|
|
359
365
|
- [Architecture](docs/ARCHITECTURE.md) — module map, workflow, local state
|
|
360
366
|
- [Security model](docs/SECURITY.md) — secrets, process execution, env isolation, audit log
|
|
361
367
|
- [API stability policy](docs/API_STABILITY.md) and [public API inventory](docs/API.md)
|
|
362
|
-
- [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Memory](docs/MEMORY.md) · [Reports](docs/REPORTS.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
|
|
368
|
+
- [Agent Protocol v2 spec](docs/PROTOCOL.md) · [Providers](docs/PROVIDERS.md) · [Memory](docs/MEMORY.md) · [Reports](docs/REPORTS.md) · [Persistence](docs/PERSISTENCE.md) · [Deprecations](docs/DEPRECATIONS.md) · [Changelog](CHANGELOG.md) · [v2 roadmap](docs/ROADMAP_V2.md)
|
|
363
369
|
- [Example project](examples/demo/) — a runnable end-to-end demo (`./demo.sh`)
|
|
364
370
|
|
|
365
371
|
Prefer a container? `docker build -t omninode .` then
|
package/dist/cli/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { realpathSync } from "fs";
|
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// src/version.ts
|
|
9
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
9
|
+
var OMNINODE_VERSION = "2.0.0-alpha.12";
|
|
10
10
|
|
|
11
11
|
// src/errors/index.ts
|
|
12
12
|
var OmniNodeError = class extends Error {
|
|
@@ -2108,51 +2108,119 @@ function registerRoleCommands(program) {
|
|
|
2108
2108
|
}
|
|
2109
2109
|
|
|
2110
2110
|
// src/persistence/json-file-store.ts
|
|
2111
|
-
import { mkdir, readFile, rename,
|
|
2111
|
+
import { open, mkdir, readFile, rename, stat } from "fs/promises";
|
|
2112
2112
|
import { dirname } from "path";
|
|
2113
|
+
var SCHEMA_VERSION = 2;
|
|
2114
|
+
async function writeFileAtomic(target, contents) {
|
|
2115
|
+
const tmp = `${target}.tmp`;
|
|
2116
|
+
await mkdir(dirname(target), { recursive: true });
|
|
2117
|
+
const handle = await open(tmp, "w");
|
|
2118
|
+
try {
|
|
2119
|
+
await handle.writeFile(contents, "utf8");
|
|
2120
|
+
await handle.sync();
|
|
2121
|
+
} finally {
|
|
2122
|
+
await handle.close();
|
|
2123
|
+
}
|
|
2124
|
+
await rename(tmp, target);
|
|
2125
|
+
try {
|
|
2126
|
+
const dir = await open(dirname(target), "r");
|
|
2127
|
+
try {
|
|
2128
|
+
await dir.sync();
|
|
2129
|
+
} finally {
|
|
2130
|
+
await dir.close();
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
async function inspectDocument(path) {
|
|
2136
|
+
let raw;
|
|
2137
|
+
try {
|
|
2138
|
+
await stat(path);
|
|
2139
|
+
} catch {
|
|
2140
|
+
return { status: "missing" };
|
|
2141
|
+
}
|
|
2142
|
+
try {
|
|
2143
|
+
raw = await readFile(path, "utf8");
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
return { status: "corrupt", detail: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
2146
|
+
}
|
|
2147
|
+
if (raw.trim().length === 0) return { status: "empty" };
|
|
2148
|
+
let parsed;
|
|
2149
|
+
try {
|
|
2150
|
+
parsed = JSON.parse(raw);
|
|
2151
|
+
} catch (error) {
|
|
2152
|
+
return { status: "corrupt", detail: error instanceof Error ? error.message : "invalid JSON" };
|
|
2153
|
+
}
|
|
2154
|
+
if (Array.isArray(parsed)) return { status: "v1-legacy" };
|
|
2155
|
+
if (parsed === null || typeof parsed !== "object") return { status: "corrupt", detail: "not an object" };
|
|
2156
|
+
const doc = parsed;
|
|
2157
|
+
if (!Array.isArray(doc.items)) return { status: "corrupt", detail: "missing items array" };
|
|
2158
|
+
const version = typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1;
|
|
2159
|
+
if (version > SCHEMA_VERSION) return { status: "future", schemaVersion: version };
|
|
2160
|
+
if (version < SCHEMA_VERSION) return { status: "v1-legacy" };
|
|
2161
|
+
return { status: "current", schemaVersion: version, count: doc.items.length };
|
|
2162
|
+
}
|
|
2163
|
+
async function readDocument(path, onCorrupt, quarantine = true) {
|
|
2164
|
+
const state = await inspectDocument(path);
|
|
2165
|
+
if (state.status === "missing" || state.status === "empty") return [];
|
|
2166
|
+
if (state.status === "future") {
|
|
2167
|
+
const error = new Error(
|
|
2168
|
+
`${path} was written by a newer OmniNode (schema v${state.schemaVersion} > v${SCHEMA_VERSION})`
|
|
2169
|
+
);
|
|
2170
|
+
onCorrupt?.(path, error);
|
|
2171
|
+
return [];
|
|
2172
|
+
}
|
|
2173
|
+
let raw;
|
|
2174
|
+
try {
|
|
2175
|
+
raw = await readFile(path, "utf8");
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
onCorrupt?.(path, error);
|
|
2178
|
+
return [];
|
|
2179
|
+
}
|
|
2180
|
+
try {
|
|
2181
|
+
const parsed = JSON.parse(raw);
|
|
2182
|
+
if (Array.isArray(parsed)) return parsed;
|
|
2183
|
+
return parsed.items ?? [];
|
|
2184
|
+
} catch (error) {
|
|
2185
|
+
if (quarantine) {
|
|
2186
|
+
const quarantinePath = `${path}.corrupt-${Date.now()}`;
|
|
2187
|
+
try {
|
|
2188
|
+
await rename(path, quarantinePath);
|
|
2189
|
+
logger.child({ component: "persistence" }).warn(
|
|
2190
|
+
`Quarantined unreadable store file ${path} \u2192 ${quarantinePath}`
|
|
2191
|
+
);
|
|
2192
|
+
} catch {
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
onCorrupt?.(path, error);
|
|
2196
|
+
return [];
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2113
2199
|
var JsonFileStore = class {
|
|
2114
2200
|
filePath;
|
|
2115
2201
|
schemaVersion;
|
|
2116
2202
|
onCorrupt;
|
|
2203
|
+
quarantine;
|
|
2117
2204
|
log;
|
|
2118
2205
|
queue = Promise.resolve();
|
|
2119
2206
|
constructor(options) {
|
|
2120
2207
|
this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
|
|
2121
|
-
this.schemaVersion = options.schemaVersion ??
|
|
2208
|
+
this.schemaVersion = options.schemaVersion ?? SCHEMA_VERSION;
|
|
2122
2209
|
this.onCorrupt = options.onCorrupt;
|
|
2210
|
+
this.quarantine = options.quarantine ?? true;
|
|
2123
2211
|
this.log = logger.child({ component: "persistence", file: options.fileName });
|
|
2124
2212
|
}
|
|
2125
2213
|
get path() {
|
|
2126
2214
|
return this.filePath;
|
|
2127
2215
|
}
|
|
2128
|
-
/** All items, or [] when missing/corrupt. */
|
|
2216
|
+
/** All items, or [] when missing/migrated-from-v1/corrupt. */
|
|
2129
2217
|
async readAll() {
|
|
2130
|
-
|
|
2131
|
-
try {
|
|
2132
|
-
raw = await readFile(this.filePath, "utf8");
|
|
2133
|
-
} catch {
|
|
2134
|
-
return [];
|
|
2135
|
-
}
|
|
2136
|
-
try {
|
|
2137
|
-
const parsed = JSON.parse(raw);
|
|
2138
|
-
if (Array.isArray(parsed)) return parsed;
|
|
2139
|
-
if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
|
|
2140
|
-
return parsed.items;
|
|
2141
|
-
}
|
|
2142
|
-
throw new Error("persisted file has an unrecognized shape");
|
|
2143
|
-
} catch (error) {
|
|
2144
|
-
this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
|
|
2145
|
-
this.onCorrupt?.(this.filePath, error);
|
|
2146
|
-
return [];
|
|
2147
|
-
}
|
|
2218
|
+
return readDocument(this.filePath, this.onCorrupt, this.quarantine);
|
|
2148
2219
|
}
|
|
2149
2220
|
async writeAll(items) {
|
|
2150
2221
|
const document = { schemaVersion: this.schemaVersion, items };
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
await writeFile(tmp, `${JSON.stringify(document, null, 2)}
|
|
2154
|
-
`, "utf8");
|
|
2155
|
-
await rename(tmp, this.filePath);
|
|
2222
|
+
await writeFileAtomic(this.filePath, `${JSON.stringify(document, null, 2)}
|
|
2223
|
+
`);
|
|
2156
2224
|
}
|
|
2157
2225
|
/** Serializes an async operation against this file. */
|
|
2158
2226
|
synchronized(operation) {
|
|
@@ -2455,7 +2523,7 @@ function sleep(ms) {
|
|
|
2455
2523
|
}
|
|
2456
2524
|
|
|
2457
2525
|
// src/audit/index.ts
|
|
2458
|
-
import {
|
|
2526
|
+
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
2459
2527
|
import { dirname as dirname2 } from "path";
|
|
2460
2528
|
var FileAuditLog = class {
|
|
2461
2529
|
filePath;
|
|
@@ -2467,8 +2535,15 @@ var FileAuditLog = class {
|
|
|
2467
2535
|
async record(event) {
|
|
2468
2536
|
try {
|
|
2469
2537
|
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
2470
|
-
await
|
|
2538
|
+
const { open: open2 } = await import("fs/promises");
|
|
2539
|
+
const handle = await open2(this.filePath, "a");
|
|
2540
|
+
try {
|
|
2541
|
+
await handle.appendFile(`${JSON.stringify(event)}
|
|
2471
2542
|
`, "utf8");
|
|
2543
|
+
await handle.sync();
|
|
2544
|
+
} finally {
|
|
2545
|
+
await handle.close();
|
|
2546
|
+
}
|
|
2472
2547
|
} catch (error) {
|
|
2473
2548
|
this.log.warn(
|
|
2474
2549
|
`Audit write failed: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2477,7 +2552,6 @@ var FileAuditLog = class {
|
|
|
2477
2552
|
}
|
|
2478
2553
|
/** Recent events, oldest first; used by the CLI. */
|
|
2479
2554
|
async recent(limit = 20) {
|
|
2480
|
-
const { readFile: readFile2 } = await import("fs/promises");
|
|
2481
2555
|
let raw;
|
|
2482
2556
|
try {
|
|
2483
2557
|
raw = await readFile2(this.filePath, "utf8");
|
|
@@ -4809,6 +4883,78 @@ function registerAuditCommand(program) {
|
|
|
4809
4883
|
});
|
|
4810
4884
|
}
|
|
4811
4885
|
|
|
4886
|
+
// src/persistence/index.ts
|
|
4887
|
+
var STORE_FILES = [
|
|
4888
|
+
"tasks.json",
|
|
4889
|
+
"pipelines.json",
|
|
4890
|
+
"reports.json",
|
|
4891
|
+
"plans.json",
|
|
4892
|
+
"memory.json"
|
|
4893
|
+
];
|
|
4894
|
+
async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, options = {}) {
|
|
4895
|
+
const reports = [];
|
|
4896
|
+
for (const file of STORE_FILES) {
|
|
4897
|
+
const path = `${directory}/${file}`;
|
|
4898
|
+
const state = await inspectDocument(path);
|
|
4899
|
+
if (state.status === "missing") {
|
|
4900
|
+
reports.push({ file, state: state.status, migrated: false });
|
|
4901
|
+
continue;
|
|
4902
|
+
}
|
|
4903
|
+
if (state.status === "current") {
|
|
4904
|
+
reports.push({ file, state: state.status, before: state.count, after: state.count, migrated: false });
|
|
4905
|
+
continue;
|
|
4906
|
+
}
|
|
4907
|
+
if (state.status === "future") {
|
|
4908
|
+
reports.push({
|
|
4909
|
+
file,
|
|
4910
|
+
state: state.status,
|
|
4911
|
+
migrated: false,
|
|
4912
|
+
note: `written by a newer OmniNode (schema v${state.schemaVersion}); not touched`
|
|
4913
|
+
});
|
|
4914
|
+
continue;
|
|
4915
|
+
}
|
|
4916
|
+
if (state.status === "corrupt") {
|
|
4917
|
+
reports.push({ file, state: state.status, migrated: false, note: state.detail });
|
|
4918
|
+
continue;
|
|
4919
|
+
}
|
|
4920
|
+
const items = await readDocument(path, void 0, false);
|
|
4921
|
+
if (!options.checkOnly) {
|
|
4922
|
+
await writeFileAtomic(path, `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, items }, null, 2)}
|
|
4923
|
+
`);
|
|
4924
|
+
}
|
|
4925
|
+
const beforeCount = items.length;
|
|
4926
|
+
reports.push({
|
|
4927
|
+
file,
|
|
4928
|
+
state: state.status,
|
|
4929
|
+
before: beforeCount,
|
|
4930
|
+
after: items.length,
|
|
4931
|
+
migrated: !options.checkOnly,
|
|
4932
|
+
...options.checkOnly ? { note: "would migrate" } : {}
|
|
4933
|
+
});
|
|
4934
|
+
}
|
|
4935
|
+
return reports;
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
// src/cli/commands/migrate.ts
|
|
4939
|
+
function registerMigrateCommand(program) {
|
|
4940
|
+
program.command("migrate").description(
|
|
4941
|
+
`Inspect and upgrade persisted store files to schema v${SCHEMA_VERSION} (v1 data is migrated in place).`
|
|
4942
|
+
).option("--check", "Only report what would change; do not write.").action(async (options) => {
|
|
4943
|
+
const reports = await migrateProjectStores(`${process.cwd()}/.omninode`, {
|
|
4944
|
+
checkOnly: options.check ?? false
|
|
4945
|
+
});
|
|
4946
|
+
let changed = 0;
|
|
4947
|
+
for (const report of reports) {
|
|
4948
|
+
if (report.migrated || report.state === "v1-legacy") changed += 1;
|
|
4949
|
+
const detail = report.state === "corrupt" ? ` (corrupt: ${report.note})` : report.state === "future" ? ` (${report.note})` : report.state === "v1-legacy" ? ` ${report.migrated ? "migrated" : "would migrate"} ${report.before ?? 0} \u2192 ${report.after ?? 0} item(s)` : report.state === "current" ? ` ${report.after ?? 0} item(s)` : "";
|
|
4950
|
+
console.log(`${report.file.padEnd(16)} ${report.state}${detail}`);
|
|
4951
|
+
}
|
|
4952
|
+
console.log(
|
|
4953
|
+
changed === 0 ? `All stores are at schema v${SCHEMA_VERSION}.` : `${options.check ? "Would migrate" : "Migrated"} ${changed} store file(s) to schema v${SCHEMA_VERSION}.`
|
|
4954
|
+
);
|
|
4955
|
+
});
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4812
4958
|
// src/cli/index.ts
|
|
4813
4959
|
function createProgram() {
|
|
4814
4960
|
const program = new Command();
|
|
@@ -4828,6 +4974,7 @@ function createProgram() {
|
|
|
4828
4974
|
registerPlanCommands(program);
|
|
4829
4975
|
registerStatusCommand(program);
|
|
4830
4976
|
registerAuditCommand(program);
|
|
4977
|
+
registerMigrateCommand(program);
|
|
4831
4978
|
registerRunAlias(program);
|
|
4832
4979
|
return program;
|
|
4833
4980
|
}
|