@voybio/ace-swarm 0.2.4 → 2.4.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/CHANGELOG.md +11 -1
- package/README.md +20 -13
- package/assets/.agents/skills/eval-harness/SKILL.md +14 -0
- package/assets/.agents/skills/handoff-lint/SKILL.md +14 -0
- package/assets/.agents/skills/incident-commander/SKILL.md +14 -0
- package/assets/.agents/skills/memory-curator/SKILL.md +14 -0
- package/assets/.agents/skills/release-sentry/SKILL.md +14 -0
- package/assets/.agents/skills/risk-quant/SKILL.md +14 -0
- package/assets/.agents/skills/schema-forge/SKILL.md +14 -0
- package/assets/.agents/skills/state-auditor/SKILL.md +14 -0
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/MODULES/gates/gate-correctness.json +1 -1
- package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
- package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
- package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
- package/assets/agent-state/STATUS.md +2 -2
- package/assets/scripts/ace-hook-dispatch.mjs +70 -6
- package/assets/scripts/render-mcp-configs.sh +19 -5
- package/dist/ace-context.js +22 -1
- package/dist/ace-server-instructions.js +3 -3
- package/dist/ace-state-resolver.js +5 -3
- package/dist/astgrep-index.d.ts +9 -1
- package/dist/astgrep-index.js +14 -3
- package/dist/cli.js +52 -20
- package/dist/handoff-registry.js +5 -5
- package/dist/helpers/artifacts.d.ts +19 -0
- package/dist/helpers/artifacts.js +152 -0
- package/dist/helpers/bootstrap.d.ts +24 -0
- package/dist/helpers/bootstrap.js +894 -0
- package/dist/helpers/constants.d.ts +53 -0
- package/dist/helpers/constants.js +288 -0
- package/dist/helpers/drift.d.ts +13 -0
- package/dist/helpers/drift.js +45 -0
- package/dist/helpers/path-utils.d.ts +17 -0
- package/dist/helpers/path-utils.js +104 -0
- package/dist/helpers/store-resolution.d.ts +19 -0
- package/dist/helpers/store-resolution.js +301 -0
- package/dist/helpers/workspace-root.d.ts +3 -0
- package/dist/helpers/workspace-root.js +80 -0
- package/dist/helpers.d.ts +8 -123
- package/dist/helpers.js +8 -1747
- package/dist/job-scheduler.js +3 -3
- package/dist/local-model-runtime.js +12 -1
- package/dist/model-bridge.d.ts +7 -0
- package/dist/model-bridge.js +75 -5
- package/dist/orchestrator-supervisor.d.ts +14 -0
- package/dist/orchestrator-supervisor.js +72 -1
- package/dist/run-ledger.js +3 -3
- package/dist/runtime-command.d.ts +8 -0
- package/dist/runtime-command.js +38 -6
- package/dist/runtime-executor.d.ts +14 -0
- package/dist/runtime-executor.js +669 -171
- package/dist/runtime-profile.d.ts +32 -0
- package/dist/runtime-profile.js +89 -13
- package/dist/runtime-tool-specs.d.ts +21 -0
- package/dist/runtime-tool-specs.js +78 -3
- package/dist/safe-edit.d.ts +7 -0
- package/dist/safe-edit.js +163 -37
- package/dist/schemas.js +19 -0
- package/dist/shared.d.ts +2 -2
- package/dist/status-events.js +9 -6
- package/dist/store/ace-packed-store.d.ts +3 -2
- package/dist/store/ace-packed-store.js +188 -110
- package/dist/store/bootstrap-store.d.ts +1 -1
- package/dist/store/bootstrap-store.js +94 -81
- package/dist/store/cache-workspace.d.ts +22 -0
- package/dist/store/cache-workspace.js +149 -0
- package/dist/store/materializers/context-snapshot-materializer.js +6 -7
- package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
- package/dist/store/materializers/hook-context-materializer.js +11 -21
- package/dist/store/materializers/host-file-materializer.js +6 -0
- package/dist/store/materializers/projection-manager.d.ts +0 -1
- package/dist/store/materializers/projection-manager.js +5 -13
- package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
- package/dist/store/materializers/vericify-projector.d.ts +7 -7
- package/dist/store/materializers/vericify-projector.js +11 -11
- package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
- package/dist/store/repositories/local-model-runtime-repository.js +242 -6
- package/dist/store/skills-install.d.ts +4 -0
- package/dist/store/skills-install.js +21 -12
- package/dist/store/state-reader.d.ts +2 -0
- package/dist/store/state-reader.js +20 -0
- package/dist/store/store-artifacts.d.ts +7 -0
- package/dist/store/store-artifacts.js +27 -1
- package/dist/store/store-authority-audit.d.ts +18 -1
- package/dist/store/store-authority-audit.js +115 -5
- package/dist/store/store-snapshot.d.ts +3 -0
- package/dist/store/store-snapshot.js +22 -2
- package/dist/store/workspace-store-paths.d.ts +39 -0
- package/dist/store/workspace-store-paths.js +94 -0
- package/dist/store/write-coordinator.d.ts +65 -0
- package/dist/store/write-coordinator.js +386 -0
- package/dist/todo-state.js +5 -5
- package/dist/tools-agent.js +319 -34
- package/dist/tools-discovery.js +1 -1
- package/dist/tools-files.d.ts +7 -0
- package/dist/tools-files.js +299 -10
- package/dist/tools-framework.js +107 -27
- package/dist/tools-handoff.js +2 -2
- package/dist/tools-lifecycle.js +4 -4
- package/dist/tools-memory.js +6 -6
- package/dist/tools-todo.js +2 -2
- package/dist/tracker-adapters.d.ts +1 -1
- package/dist/tracker-adapters.js +13 -18
- package/dist/tracker-sync.js +5 -3
- package/dist/tui/agent-runner.js +3 -1
- package/dist/tui/chat.js +103 -7
- package/dist/tui/dashboard.d.ts +1 -0
- package/dist/tui/dashboard.js +43 -0
- package/dist/tui/layout.d.ts +20 -0
- package/dist/tui/layout.js +31 -1
- package/dist/tui/local-model-contract.d.ts +6 -2
- package/dist/tui/local-model-contract.js +16 -3
- package/dist/vericify-bridge.d.ts +5 -0
- package/dist/vericify-bridge.js +27 -3
- package/dist/workspace-manager.d.ts +30 -3
- package/dist/workspace-manager.js +257 -27
- package/package.json +1 -2
- package/dist/internal-tool-runtime.d.ts +0 -21
- package/dist/internal-tool-runtime.js +0 -136
- package/dist/store/workspace-snapshot.d.ts +0 -26
- package/dist/store/workspace-snapshot.js +0 -107
package/dist/schemas.js
CHANGED
|
@@ -186,6 +186,8 @@ const workspaceSessionRecordSchema = z
|
|
|
186
186
|
last_error: z.string().optional(),
|
|
187
187
|
created_at: z.string().datetime({ offset: true }),
|
|
188
188
|
updated_at: z.string().datetime({ offset: true }),
|
|
189
|
+
hook_health: z.enum(["ok", "degraded", "failed"]).optional(),
|
|
190
|
+
hook_summary: z.string().optional(),
|
|
189
191
|
hooks: z
|
|
190
192
|
.object({
|
|
191
193
|
after_create: workspaceHookStateSchema,
|
|
@@ -352,6 +354,15 @@ const unattendedTurnRecordSchema = z
|
|
|
352
354
|
stdout: z.string(),
|
|
353
355
|
stderr: z.string(),
|
|
354
356
|
tool_calls: z.array(unattendedToolCallRecordSchema),
|
|
357
|
+
turn_outcome: z.enum(["no_op_success", "meaningful_completion", "escalation_blocker"]).optional(),
|
|
358
|
+
outcome_reason: z.string().optional(),
|
|
359
|
+
})
|
|
360
|
+
.strict();
|
|
361
|
+
const runtimeOutputPolicySchema = z
|
|
362
|
+
.object({
|
|
363
|
+
emit_to: z.array(z.enum(["tui", "tracker", "handoff", "vericify"])),
|
|
364
|
+
silent_unless_blocked: z.boolean(),
|
|
365
|
+
require_approval_before_emit: z.boolean(),
|
|
355
366
|
})
|
|
356
367
|
.strict();
|
|
357
368
|
const runtimeExecutorSessionRecordSchema = z
|
|
@@ -377,6 +388,7 @@ const runtimeExecutorSessionRecordSchema = z
|
|
|
377
388
|
last_error: z.string().optional(),
|
|
378
389
|
cleanup_error: z.string().optional(),
|
|
379
390
|
workspace_cleanup_status: z.enum(["pending", "removed", "archived", "failed"]),
|
|
391
|
+
output_policy: runtimeOutputPolicySchema.optional(),
|
|
380
392
|
turns: z.array(unattendedTurnRecordSchema),
|
|
381
393
|
})
|
|
382
394
|
.strict();
|
|
@@ -453,6 +465,13 @@ const vericifyBridgeSnapshotSchema = z
|
|
|
453
465
|
})
|
|
454
466
|
.strict(),
|
|
455
467
|
active_run_refs: z.array(vericifyBridgeActiveRunRefSchema),
|
|
468
|
+
ace_runtime_enrichment: z
|
|
469
|
+
.object({
|
|
470
|
+
live_session_id: z.string().optional(),
|
|
471
|
+
last_turn_outcome: z.string().optional(),
|
|
472
|
+
last_turn_outcome_reason: z.string().optional(),
|
|
473
|
+
})
|
|
474
|
+
.optional(),
|
|
456
475
|
})
|
|
457
476
|
.strict();
|
|
458
477
|
/**
|
package/dist/shared.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ export declare function looksLikeSwarmHandoffPath(path: string): boolean;
|
|
|
19
19
|
export declare const ROLE_TITLES: Record<string, string>;
|
|
20
20
|
export declare function getRoleTitle(role: string): string;
|
|
21
21
|
export declare const MCP_CLIENT_ENUM: z.ZodEnum<{
|
|
22
|
-
claude: "claude";
|
|
23
|
-
cursor: "cursor";
|
|
24
22
|
codex: "codex";
|
|
25
23
|
vscode: "vscode";
|
|
24
|
+
claude: "claude";
|
|
25
|
+
cursor: "cursor";
|
|
26
26
|
antigravity: "antigravity";
|
|
27
27
|
}>;
|
|
28
28
|
export declare const ROLE_ENUM: z.ZodEnum<{
|
package/dist/status-events.js
CHANGED
|
@@ -8,7 +8,7 @@ import { ProjectionManager } from "./store/materializers/projection-manager.js";
|
|
|
8
8
|
import { TrackerRepository } from "./store/repositories/tracker-repository.js";
|
|
9
9
|
import { getWorkspaceStorePath, listStoreKeysSync, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
|
|
10
10
|
import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
|
|
11
|
-
import {
|
|
11
|
+
import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
|
|
12
12
|
export const STATUS_EVENTS_REL_PATH = "agent-state/STATUS_EVENTS.ndjson";
|
|
13
13
|
const STATUS_EVENTS_ARCHIVE_REL = "agent-state/STATUS_EVENTS-archive.ndjson";
|
|
14
14
|
const MAX_EVENT_LINES = 2000;
|
|
@@ -94,7 +94,7 @@ async function mirrorStatusEventToStore(root, event) {
|
|
|
94
94
|
const storePath = getWorkspaceStorePath(root);
|
|
95
95
|
if (!existsSync(storePath))
|
|
96
96
|
return;
|
|
97
|
-
await
|
|
97
|
+
await withStoreWriteCoordinator(storePath, async () => {
|
|
98
98
|
const store = await openStore(storePath);
|
|
99
99
|
try {
|
|
100
100
|
const tracker = new TrackerRepository(store);
|
|
@@ -120,7 +120,7 @@ async function mirrorStatusEventToStore(root, event) {
|
|
|
120
120
|
finally {
|
|
121
121
|
await store.close();
|
|
122
122
|
}
|
|
123
|
-
});
|
|
123
|
+
}, { operation_label: "mirrorStatusEventToStore" });
|
|
124
124
|
}
|
|
125
125
|
function scheduleStatusEventMirror(event) {
|
|
126
126
|
const root = workspaceRoot();
|
|
@@ -135,7 +135,7 @@ export async function waitForPendingStatusEventMirrors() {
|
|
|
135
135
|
}
|
|
136
136
|
async function appendStatusEventStoreBacked(root, event) {
|
|
137
137
|
const storePath = getWorkspaceStorePath(root);
|
|
138
|
-
return
|
|
138
|
+
return withStoreWriteCoordinator(storePath, async () => {
|
|
139
139
|
const store = await openStore(storePath);
|
|
140
140
|
try {
|
|
141
141
|
const tracker = new TrackerRepository(store);
|
|
@@ -165,9 +165,10 @@ async function appendStatusEventStoreBacked(root, event) {
|
|
|
165
165
|
finally {
|
|
166
166
|
await store.close();
|
|
167
167
|
}
|
|
168
|
-
});
|
|
168
|
+
}, { operation_label: "appendStatusEventStoreBacked" });
|
|
169
169
|
}
|
|
170
170
|
export function appendStatusEvent(input) {
|
|
171
|
+
const root = workspaceRoot();
|
|
171
172
|
const event = buildStatusEvent(input);
|
|
172
173
|
validateStatusEvent(event);
|
|
173
174
|
// Atomic append under file lock to prevent lost writes under parallelism
|
|
@@ -176,7 +177,9 @@ export function appendStatusEvent(input) {
|
|
|
176
177
|
const combined = existing.length > 0 ? `${existing}\n${line}\n` : `${line}\n`;
|
|
177
178
|
const next = rotateIfNeeded(combined);
|
|
178
179
|
const path = safeWriteWorkspaceFile(STATUS_EVENTS_REL_PATH, next);
|
|
179
|
-
|
|
180
|
+
if (storeExistsSync(root)) {
|
|
181
|
+
scheduleStatusEventMirror(event);
|
|
182
|
+
}
|
|
180
183
|
return { path, event };
|
|
181
184
|
}
|
|
182
185
|
/**
|
|
@@ -59,6 +59,9 @@ export declare class AcePackedStore implements IAcePackedStore {
|
|
|
59
59
|
private fh;
|
|
60
60
|
private kvIndex;
|
|
61
61
|
private kvChunkEnd;
|
|
62
|
+
private pendingKv;
|
|
63
|
+
private pendingDeletes;
|
|
64
|
+
private dirty;
|
|
62
65
|
private committed;
|
|
63
66
|
private pending;
|
|
64
67
|
private evtBaseId;
|
|
@@ -67,8 +70,6 @@ export declare class AcePackedStore implements IAcePackedStore {
|
|
|
67
70
|
}): Promise<void>;
|
|
68
71
|
private _initNew;
|
|
69
72
|
private _loadExisting;
|
|
70
|
-
/** Read a KV blob directly from a loaded file buffer (used during migration). */
|
|
71
|
-
private _readKvBlobDirect;
|
|
72
73
|
commit(): Promise<void>;
|
|
73
74
|
/**
|
|
74
75
|
* Compacts the KV chunk region — removes dead space left by overwritten keys.
|
|
@@ -56,6 +56,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "
|
|
|
56
56
|
import { open as fsOpen } from "node:fs/promises";
|
|
57
57
|
import { dirname } from "node:path";
|
|
58
58
|
import { MAGIC, STORE_VERSION, HEADER_SIZE, HEADER_SIZE_V1, ContentSource, EntityKind, } from "./types.js";
|
|
59
|
+
import { invalidateStoreSnapshotCache } from "./store-snapshot.js";
|
|
59
60
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
60
61
|
// Header field byte offsets (big-endian throughout)
|
|
61
62
|
const H_KV_IDX_OFF = 16; // uint64: KV index offset
|
|
@@ -204,6 +205,69 @@ function deserializeEventSection(buf) {
|
|
|
204
205
|
blob: DECODE.decode(buf.slice(poolStart + payOffs[i], poolStart + payOffs[i] + payLens[i])),
|
|
205
206
|
}));
|
|
206
207
|
}
|
|
208
|
+
function readKvBlobFromBytes(file, index, key) {
|
|
209
|
+
const entry = index.get(key);
|
|
210
|
+
if (!entry)
|
|
211
|
+
return undefined;
|
|
212
|
+
const start = entry.offset + 4;
|
|
213
|
+
const end = start + entry.length;
|
|
214
|
+
if (start < 0 || end > file.length) {
|
|
215
|
+
throw new Error(`AcePackedStore: KV entry for '${key}' exceeds file bounds`);
|
|
216
|
+
}
|
|
217
|
+
return file.slice(start, end);
|
|
218
|
+
}
|
|
219
|
+
function loadCommittedStateFromBytes(file) {
|
|
220
|
+
if (file.length < HEADER_SIZE_V1)
|
|
221
|
+
throw new Error("AcePackedStore: file too short");
|
|
222
|
+
const hdr = readHeaderV2(file.slice(0, Math.max(HEADER_SIZE, file.length)));
|
|
223
|
+
const isV1 = hdr.version < 2;
|
|
224
|
+
const idxRaw = file.slice(hdr.kvIndexOffset, hdr.kvIndexOffset + hdr.kvIndexLength);
|
|
225
|
+
const idxObj = JSON.parse(DECODE.decode(idxRaw));
|
|
226
|
+
const kvIndex = new Map(Object.entries(idxObj));
|
|
227
|
+
if (isV1) {
|
|
228
|
+
const logKeys = [...kvIndex.keys()]
|
|
229
|
+
.filter((key) => key.startsWith("core/log/") && key !== "core/log/__seq")
|
|
230
|
+
.sort();
|
|
231
|
+
const committed = [];
|
|
232
|
+
for (const key of logKeys) {
|
|
233
|
+
const entry = readKvBlobFromBytes(file, kvIndex, key);
|
|
234
|
+
if (!entry)
|
|
235
|
+
continue;
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(DECODE.decode(entry));
|
|
238
|
+
committed.push({
|
|
239
|
+
ts: parsed.ts,
|
|
240
|
+
kind: parsed.kind,
|
|
241
|
+
source: parsed.content_source,
|
|
242
|
+
flags: parsed.flags ?? 0,
|
|
243
|
+
blob: JSON.stringify({ key: parsed.key, payload: parsed.payload, parent_id: parsed.parent_id }),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Skip malformed legacy events during migration.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const key of [...kvIndex.keys()]) {
|
|
251
|
+
if (key.startsWith("core/log/") || key === "meta/log_entry_seq") {
|
|
252
|
+
kvIndex.delete(key);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
kvIndex,
|
|
257
|
+
kvChunkEnd: hdr.kvChunkEnd,
|
|
258
|
+
committed,
|
|
259
|
+
evtBaseId: 0,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
kvIndex,
|
|
264
|
+
kvChunkEnd: hdr.kvChunkEnd,
|
|
265
|
+
committed: hdr.evtLength > 0
|
|
266
|
+
? deserializeEventSection(file.slice(hdr.evtOffset, hdr.evtOffset + hdr.evtLength))
|
|
267
|
+
: [],
|
|
268
|
+
evtBaseId: hdr.evtBaseId,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
207
271
|
// ── File I/O helpers ──────────────────────────────────────────────────────────
|
|
208
272
|
async function readExact(fh, buf, position) {
|
|
209
273
|
let off = 0;
|
|
@@ -214,15 +278,6 @@ async function readExact(fh, buf, position) {
|
|
|
214
278
|
off += bytesRead;
|
|
215
279
|
}
|
|
216
280
|
}
|
|
217
|
-
async function writeExact(fh, data, position) {
|
|
218
|
-
let off = 0;
|
|
219
|
-
while (off < data.length) {
|
|
220
|
-
const { bytesWritten } = await fh.write(data, off, data.length - off, position + off);
|
|
221
|
-
if (bytesWritten <= 0)
|
|
222
|
-
throw new Error("AcePackedStore: write failed");
|
|
223
|
-
off += bytesWritten;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
281
|
// ── AcePackedStore ────────────────────────────────────────────────────────────
|
|
227
282
|
export class AcePackedStore {
|
|
228
283
|
storePath = "";
|
|
@@ -231,6 +286,9 @@ export class AcePackedStore {
|
|
|
231
286
|
// KV region
|
|
232
287
|
kvIndex = new Map();
|
|
233
288
|
kvChunkEnd = HEADER_SIZE; // end of KV chunk region in file
|
|
289
|
+
pendingKv = new Map();
|
|
290
|
+
pendingDeletes = new Set();
|
|
291
|
+
dirty = false;
|
|
234
292
|
// Event log (in-memory)
|
|
235
293
|
committed = []; // loaded from file
|
|
236
294
|
pending = []; // written since last commit
|
|
@@ -276,101 +334,90 @@ export class AcePackedStore {
|
|
|
276
334
|
this.committed = [];
|
|
277
335
|
this.pending = [];
|
|
278
336
|
this.evtBaseId = 0;
|
|
337
|
+
this.pendingKv.clear();
|
|
338
|
+
this.pendingDeletes.clear();
|
|
339
|
+
this.dirty = false;
|
|
279
340
|
}
|
|
280
341
|
async _loadExisting(path) {
|
|
281
342
|
const file = readFileSync(path);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const idxRaw = file.slice(hdr.kvIndexOffset, hdr.kvIndexOffset + hdr.kvIndexLength);
|
|
288
|
-
const idxObj = JSON.parse(DECODE.decode(idxRaw));
|
|
289
|
-
this.kvIndex = new Map(Object.entries(idxObj));
|
|
290
|
-
this.kvChunkEnd = hdr.kvChunkEnd;
|
|
291
|
-
if (isV1) {
|
|
292
|
-
// v1 → v2 migration: pull events from core/log/ KV entries
|
|
293
|
-
const logKeys = [...this.kvIndex.keys()]
|
|
294
|
-
.filter(k => k.startsWith("core/log/") && k !== "core/log/__seq")
|
|
295
|
-
.sort();
|
|
296
|
-
this.committed = [];
|
|
297
|
-
for (const lk of logKeys) {
|
|
298
|
-
const entry = await this._readKvBlobDirect(file, lk);
|
|
299
|
-
if (!entry)
|
|
300
|
-
continue;
|
|
301
|
-
try {
|
|
302
|
-
const e = JSON.parse(entry);
|
|
303
|
-
this.committed.push({
|
|
304
|
-
ts: e.ts,
|
|
305
|
-
kind: e.kind,
|
|
306
|
-
source: e.content_source,
|
|
307
|
-
flags: e.flags ?? 0,
|
|
308
|
-
blob: JSON.stringify({ key: e.key, payload: e.payload, parent_id: e.parent_id }),
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
catch { /* skip malformed */ }
|
|
312
|
-
}
|
|
313
|
-
// Remove core/log/ and meta/log_entry_seq from KV (they go to columnar section)
|
|
314
|
-
for (const k of [...this.kvIndex.keys()]) {
|
|
315
|
-
if (k.startsWith("core/log/") || k === "meta/log_entry_seq")
|
|
316
|
-
this.kvIndex.delete(k);
|
|
317
|
-
}
|
|
318
|
-
this.evtBaseId = 0;
|
|
319
|
-
// force commit to write v2 format (pending is non-empty from migration)
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
// v2: load event section
|
|
323
|
-
if (hdr.evtLength > 0) {
|
|
324
|
-
const evtBuf = file.slice(hdr.evtOffset, hdr.evtOffset + hdr.evtLength);
|
|
325
|
-
this.committed = deserializeEventSection(evtBuf);
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
this.committed = [];
|
|
329
|
-
}
|
|
330
|
-
this.evtBaseId = hdr.evtBaseId;
|
|
331
|
-
}
|
|
343
|
+
const loaded = loadCommittedStateFromBytes(file);
|
|
344
|
+
this.kvIndex = loaded.kvIndex;
|
|
345
|
+
this.kvChunkEnd = loaded.kvChunkEnd;
|
|
346
|
+
this.committed = loaded.committed;
|
|
347
|
+
this.evtBaseId = loaded.evtBaseId;
|
|
332
348
|
this.pending = [];
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const entry = this.kvIndex.get(key);
|
|
337
|
-
if (!entry)
|
|
338
|
-
return undefined;
|
|
339
|
-
const start = entry.offset + 4;
|
|
340
|
-
const end = start + entry.length;
|
|
341
|
-
if (end > file.length)
|
|
342
|
-
return undefined;
|
|
343
|
-
return DECODE.decode(file.slice(start, end));
|
|
349
|
+
this.pendingKv.clear();
|
|
350
|
+
this.pendingDeletes.clear();
|
|
351
|
+
this.dirty = false;
|
|
344
352
|
}
|
|
345
353
|
async commit() {
|
|
346
|
-
if (this.readOnly || !this.fh)
|
|
354
|
+
if (this.readOnly || !this.fh || !this.storePath || !this.dirty)
|
|
347
355
|
return;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
const currentFile = readFileSync(this.storePath);
|
|
357
|
+
const latest = loadCommittedStateFromBytes(new Uint8Array(currentFile.buffer, currentFile.byteOffset, currentFile.byteLength));
|
|
358
|
+
const nextCommitted = [...latest.committed, ...this.pending];
|
|
359
|
+
const evtBytes = serializeEventSection(nextCommitted);
|
|
360
|
+
const nextKvIndex = new Map();
|
|
361
|
+
const liveChunks = [];
|
|
362
|
+
let writeOff = HEADER_SIZE;
|
|
363
|
+
for (const [key] of latest.kvIndex) {
|
|
364
|
+
if (this.pendingDeletes.has(key) || this.pendingKv.has(key))
|
|
365
|
+
continue;
|
|
366
|
+
const value = readKvBlobFromBytes(currentFile, latest.kvIndex, key);
|
|
367
|
+
if (!value)
|
|
368
|
+
continue;
|
|
369
|
+
liveChunks.push({ value });
|
|
370
|
+
nextKvIndex.set(key, { offset: writeOff, length: value.length });
|
|
371
|
+
writeOff += 4 + value.length;
|
|
372
|
+
}
|
|
373
|
+
for (const [key, value] of this.pendingKv) {
|
|
374
|
+
const blob = value.slice();
|
|
375
|
+
liveChunks.push({ value: blob });
|
|
376
|
+
nextKvIndex.set(key, { offset: writeOff, length: blob.length });
|
|
377
|
+
writeOff += 4 + blob.length;
|
|
378
|
+
}
|
|
379
|
+
const idxJson = JSON.stringify(Object.fromEntries(nextKvIndex));
|
|
355
380
|
const idxBytes = TEXT.encode(idxJson);
|
|
356
|
-
const evtOff =
|
|
381
|
+
const evtOff = writeOff;
|
|
357
382
|
const kvIdxOff = evtOff + evtBytes.length;
|
|
358
|
-
|
|
359
|
-
await writeExact(this.fh, idxBytes, kvIdxOff);
|
|
360
|
-
await this.fh.truncate(kvIdxOff + idxBytes.length);
|
|
361
|
-
// Update header
|
|
383
|
+
const totalSize = kvIdxOff + idxBytes.length;
|
|
362
384
|
const header = new Uint8Array(HEADER_SIZE);
|
|
363
385
|
writeHeaderV2(header, {
|
|
364
386
|
kvIndexOffset: kvIdxOff,
|
|
365
387
|
kvIndexLength: idxBytes.length,
|
|
366
|
-
kvChunkEnd:
|
|
388
|
+
kvChunkEnd: writeOff,
|
|
367
389
|
evtOffset: evtOff,
|
|
368
390
|
evtLength: evtBytes.length,
|
|
369
|
-
evtCount:
|
|
370
|
-
evtBaseId:
|
|
391
|
+
evtCount: nextCommitted.length,
|
|
392
|
+
evtBaseId: latest.evtBaseId,
|
|
371
393
|
});
|
|
372
|
-
await writeExact(this.fh, header, 0);
|
|
373
394
|
await this.fh.datasync();
|
|
395
|
+
const out = new Uint8Array(totalSize);
|
|
396
|
+
out.set(header, 0);
|
|
397
|
+
let cursor = HEADER_SIZE;
|
|
398
|
+
for (const chunk of liveChunks) {
|
|
399
|
+
out.set(u32be(chunk.value.length), cursor);
|
|
400
|
+
out.set(chunk.value, cursor + 4);
|
|
401
|
+
cursor += 4 + chunk.value.length;
|
|
402
|
+
}
|
|
403
|
+
out.set(evtBytes, evtOff);
|
|
404
|
+
out.set(idxBytes, kvIdxOff);
|
|
405
|
+
const tmpPath = `${this.storePath}.${process.pid}.${Date.now()}.commit.tmp`;
|
|
406
|
+
writeFileSync(tmpPath, out);
|
|
407
|
+
readHeaderV2(readFileSync(tmpPath).slice(0, HEADER_SIZE));
|
|
408
|
+
await this.fh.close();
|
|
409
|
+
this.fh = null;
|
|
410
|
+
renameSync(tmpPath, this.storePath);
|
|
411
|
+
invalidateStoreSnapshotCache(this.storePath);
|
|
412
|
+
this.fh = await fsOpen(this.storePath, "r+");
|
|
413
|
+
this.kvIndex = nextKvIndex;
|
|
414
|
+
this.kvChunkEnd = writeOff;
|
|
415
|
+
this.committed = nextCommitted;
|
|
416
|
+
this.pending = [];
|
|
417
|
+
this.pendingKv.clear();
|
|
418
|
+
this.pendingDeletes.clear();
|
|
419
|
+
this.evtBaseId = latest.evtBaseId;
|
|
420
|
+
this.dirty = false;
|
|
374
421
|
}
|
|
375
422
|
/**
|
|
376
423
|
* Compacts the KV chunk region — removes dead space left by overwritten keys.
|
|
@@ -381,23 +428,29 @@ export class AcePackedStore {
|
|
|
381
428
|
async compact() {
|
|
382
429
|
if (!this.storePath || this.readOnly)
|
|
383
430
|
return;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
431
|
+
if (this.dirty) {
|
|
432
|
+
await this.commit();
|
|
433
|
+
}
|
|
387
434
|
// Rebuild KV chunk region with only live keys (removes dead space)
|
|
388
435
|
const srcFile = readFileSync(this.storePath);
|
|
436
|
+
const latest = loadCommittedStateFromBytes(new Uint8Array(srcFile.buffer, srcFile.byteOffset, srcFile.byteLength));
|
|
389
437
|
const liveKv = new Map();
|
|
390
438
|
const chunks = [];
|
|
391
439
|
let writeOff = HEADER_SIZE;
|
|
392
|
-
for (const [key
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
440
|
+
for (const [key] of latest.kvIndex) {
|
|
441
|
+
const chunk = readKvBlobFromBytes(srcFile, latest.kvIndex, key);
|
|
442
|
+
if (!chunk)
|
|
443
|
+
continue;
|
|
444
|
+
chunks.push(chunk);
|
|
445
|
+
liveKv.set(key, { offset: writeOff, length: chunk.length });
|
|
446
|
+
writeOff += 4 + chunk.length;
|
|
396
447
|
}
|
|
397
448
|
this.kvIndex = liveKv;
|
|
398
449
|
this.kvChunkEnd = writeOff;
|
|
450
|
+
this.committed = latest.committed;
|
|
451
|
+
this.evtBaseId = latest.evtBaseId;
|
|
399
452
|
// Serialize events (preserved in full) + new KV index
|
|
400
|
-
const evtBytes = serializeEventSection(
|
|
453
|
+
const evtBytes = serializeEventSection(latest.committed);
|
|
401
454
|
const idxBytes = TEXT.encode(JSON.stringify(Object.fromEntries(liveKv)));
|
|
402
455
|
const evtOff = writeOff;
|
|
403
456
|
const kvIdxOff = evtOff + evtBytes.length;
|
|
@@ -410,8 +463,8 @@ export class AcePackedStore {
|
|
|
410
463
|
kvChunkEnd: writeOff,
|
|
411
464
|
evtOffset: evtOff,
|
|
412
465
|
evtLength: evtBytes.length,
|
|
413
|
-
evtCount:
|
|
414
|
-
evtBaseId:
|
|
466
|
+
evtCount: latest.committed.length,
|
|
467
|
+
evtBaseId: latest.evtBaseId,
|
|
415
468
|
});
|
|
416
469
|
out.set(header, 0);
|
|
417
470
|
let pos = HEADER_SIZE;
|
|
@@ -431,10 +484,17 @@ export class AcePackedStore {
|
|
|
431
484
|
this.fh = null;
|
|
432
485
|
}
|
|
433
486
|
renameSync(tmpPath, this.storePath);
|
|
487
|
+
invalidateStoreSnapshotCache(this.storePath);
|
|
434
488
|
this.fh = await fsOpen(this.storePath, "r+");
|
|
489
|
+
this.pending = [];
|
|
490
|
+
this.pendingKv.clear();
|
|
491
|
+
this.pendingDeletes.clear();
|
|
492
|
+
this.dirty = false;
|
|
435
493
|
}
|
|
436
494
|
async close() {
|
|
437
|
-
|
|
495
|
+
if (this.dirty) {
|
|
496
|
+
await this.commit();
|
|
497
|
+
}
|
|
438
498
|
if (this.fh) {
|
|
439
499
|
await this.fh.close();
|
|
440
500
|
this.fh = null;
|
|
@@ -442,6 +502,12 @@ export class AcePackedStore {
|
|
|
442
502
|
}
|
|
443
503
|
// ── Core KV ─────────────────────────────────────────────────────────────────
|
|
444
504
|
async get(key) {
|
|
505
|
+
if (this.pendingKv.has(key)) {
|
|
506
|
+
return this.pendingKv.get(key)?.slice();
|
|
507
|
+
}
|
|
508
|
+
if (this.pendingDeletes.has(key)) {
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
445
511
|
const entry = this.kvIndex.get(key);
|
|
446
512
|
if (!entry || !this.fh)
|
|
447
513
|
return undefined;
|
|
@@ -454,19 +520,26 @@ export class AcePackedStore {
|
|
|
454
520
|
throw new Error("AcePackedStore: read-only");
|
|
455
521
|
if (!this.fh)
|
|
456
522
|
throw new Error("AcePackedStore: not open");
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.kvIndex.set(key, { offset: this.kvChunkEnd, length: value.length });
|
|
461
|
-
this.kvChunkEnd += 4 + value.length;
|
|
523
|
+
this.pendingDeletes.delete(key);
|
|
524
|
+
this.pendingKv.set(key, value.slice());
|
|
525
|
+
this.dirty = true;
|
|
462
526
|
}
|
|
463
527
|
async delete(key) {
|
|
464
|
-
const existed = this.kvIndex.has(key);
|
|
465
|
-
this.
|
|
528
|
+
const existed = this.pendingKv.has(key) || this.kvIndex.has(key);
|
|
529
|
+
this.pendingKv.delete(key);
|
|
530
|
+
if (this.kvIndex.has(key)) {
|
|
531
|
+
this.pendingDeletes.add(key);
|
|
532
|
+
}
|
|
533
|
+
this.dirty = this.dirty || existed;
|
|
466
534
|
return existed;
|
|
467
535
|
}
|
|
468
536
|
async *list() {
|
|
469
|
-
|
|
537
|
+
const keys = new Set(this.kvIndex.keys());
|
|
538
|
+
for (const key of this.pendingDeletes)
|
|
539
|
+
keys.delete(key);
|
|
540
|
+
for (const key of this.pendingKv.keys())
|
|
541
|
+
keys.add(key);
|
|
542
|
+
for (const key of keys)
|
|
470
543
|
yield key;
|
|
471
544
|
}
|
|
472
545
|
// ── JSON / Blob convenience ──────────────────────────────────────────────────
|
|
@@ -503,6 +576,7 @@ export class AcePackedStore {
|
|
|
503
576
|
blob: JSON.stringify({ key: entry.key, payload: entry.payload, parent_id: entry.parent_id }),
|
|
504
577
|
};
|
|
505
578
|
this.pending.push(rec);
|
|
579
|
+
this.dirty = true;
|
|
506
580
|
return { id, ts, kind: entry.kind, content_source: entry.content_source, key: entry.key, payload: entry.payload, flags: entry.flags };
|
|
507
581
|
}
|
|
508
582
|
async getEntries(filter) {
|
|
@@ -569,7 +643,7 @@ export class AcePackedStore {
|
|
|
569
643
|
}
|
|
570
644
|
async listAgents() {
|
|
571
645
|
const agents = new Set();
|
|
572
|
-
for (const k of this.
|
|
646
|
+
for await (const k of this.list()) {
|
|
573
647
|
if (k.startsWith("knowledge/agents/")) {
|
|
574
648
|
const p = k.split("/");
|
|
575
649
|
if (p[2])
|
|
@@ -589,7 +663,7 @@ export class AcePackedStore {
|
|
|
589
663
|
}
|
|
590
664
|
async listSkills() {
|
|
591
665
|
const skills = new Set();
|
|
592
|
-
for (const k of this.
|
|
666
|
+
for await (const k of this.list()) {
|
|
593
667
|
if (k.startsWith("knowledge/skills/")) {
|
|
594
668
|
const p = k.split("/");
|
|
595
669
|
if (p[2])
|
|
@@ -601,10 +675,14 @@ export class AcePackedStore {
|
|
|
601
675
|
// ── Introspection ─────────────────────────────────────────────────────────────
|
|
602
676
|
/** Dead space ratio in the KV chunk region. Used to decide if compaction is needed. */
|
|
603
677
|
get deadSpaceRatio() {
|
|
604
|
-
const
|
|
678
|
+
const pendingBytes = [...this.pendingKv.values()].reduce((sum, value) => sum + 4 + value.length, 0);
|
|
679
|
+
const totalKv = this.kvChunkEnd - HEADER_SIZE + pendingBytes;
|
|
605
680
|
if (totalKv <= 0)
|
|
606
681
|
return 0;
|
|
607
|
-
const liveKv = [...this.kvIndex.
|
|
682
|
+
const liveKv = [...this.kvIndex.entries()]
|
|
683
|
+
.filter(([key]) => !this.pendingDeletes.has(key) && !this.pendingKv.has(key))
|
|
684
|
+
.reduce((sum, [, entry]) => sum + 4 + entry.length, 0) +
|
|
685
|
+
pendingBytes;
|
|
608
686
|
return Math.max(0, 1 - liveKv / totalKv);
|
|
609
687
|
}
|
|
610
688
|
/** Total events in log (committed + pending). */
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Replaces the old copyTree()-based bootstrap with:
|
|
7
7
|
* 1. Create minimal directories
|
|
8
|
-
* 2. Initialize AcePackedStore at
|
|
8
|
+
* 2. Initialize AcePackedStore at agent-state/ace-state.ace
|
|
9
9
|
* 3. Bake core knowledge (agents, modules, kernel)
|
|
10
10
|
* 4. Write topology records
|
|
11
11
|
* 5. Write initial runtime state seeds
|