@voybio/ace-swarm 0.1.0 → 0.2.1
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 +69 -29
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/STATUS.md +2 -2
- package/assets/scripts/ace-hook-dispatch.mjs +1 -1
- package/dist/ace-autonomy.js +38 -1
- package/dist/ace-context.js +8 -0
- package/dist/ace-server-instructions.js +55 -19
- package/dist/ace-state-resolver.d.ts +18 -0
- package/dist/ace-state-resolver.js +106 -0
- package/dist/cli.js +74 -7
- package/dist/handoff-registry.js +11 -7
- package/dist/helpers.js +75 -9
- package/dist/job-scheduler.js +94 -44
- package/dist/run-ledger.js +3 -4
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/shared.d.ts +1 -1
- package/dist/status-events.js +12 -14
- package/dist/store/ace-packed-store.d.ts +65 -26
- package/dist/store/ace-packed-store.js +448 -261
- package/dist/store/bootstrap-store.d.ts +1 -1
- package/dist/store/bootstrap-store.js +24 -13
- package/dist/store/catalog-builder.js +3 -3
- package/dist/store/importer.d.ts +2 -2
- package/dist/store/importer.js +2 -2
- package/dist/store/materializers/context-snapshot-materializer.d.ts +10 -0
- package/dist/store/materializers/context-snapshot-materializer.js +51 -0
- package/dist/store/materializers/hook-context-materializer.d.ts +1 -1
- package/dist/store/materializers/hook-context-materializer.js +1 -1
- package/dist/store/materializers/host-file-materializer.d.ts +6 -0
- package/dist/store/materializers/host-file-materializer.js +14 -1
- package/dist/store/materializers/projection-manager.d.ts +14 -0
- package/dist/store/materializers/projection-manager.js +73 -0
- package/dist/store/materializers/scheduler-projection-materializer.d.ts +16 -0
- package/dist/store/materializers/scheduler-projection-materializer.js +48 -0
- package/dist/store/repositories/context-snapshot-repository.d.ts +46 -0
- package/dist/store/repositories/context-snapshot-repository.js +105 -0
- package/dist/store/repositories/local-model-runtime-repository.d.ts +98 -0
- package/dist/store/repositories/local-model-runtime-repository.js +165 -0
- package/dist/store/repositories/scheduler-repository.d.ts +21 -39
- package/dist/store/repositories/scheduler-repository.js +123 -93
- package/dist/store/repositories/todo-repository.d.ts +4 -0
- package/dist/store/repositories/todo-repository.js +50 -0
- package/dist/store/skills-install.d.ts +1 -1
- package/dist/store/skills-install.js +3 -3
- package/dist/store/state-reader.d.ts +8 -1
- package/dist/store/state-reader.js +19 -13
- package/dist/store/store-artifacts.js +105 -41
- package/dist/store/store-authority-audit.d.ts +30 -0
- package/dist/store/store-authority-audit.js +448 -0
- package/dist/store/store-snapshot.js +3 -3
- package/dist/store/types.d.ts +6 -2
- package/dist/store/types.js +5 -2
- package/dist/todo-state.js +179 -11
- package/dist/tools-files.js +2 -1
- package/dist/tools-framework.js +62 -2
- package/dist/tools-memory.js +69 -34
- package/dist/tools-todo.js +1 -1
- package/dist/tui/agent-worker.d.ts +1 -1
- package/dist/tui/agent-worker.js +5 -3
- package/dist/tui/chat.d.ts +19 -0
- package/dist/tui/chat.js +275 -9
- package/dist/tui/commands.d.ts +2 -0
- package/dist/tui/commands.js +62 -0
- package/dist/tui/dashboard.d.ts +6 -1
- package/dist/tui/dashboard.js +44 -3
- package/dist/tui/index.d.ts +5 -0
- package/dist/tui/index.js +154 -0
- package/dist/tui/input.js +5 -0
- package/dist/tui/layout.d.ts +24 -0
- package/dist/tui/layout.js +76 -2
- package/dist/tui/local-model-contract.d.ts +50 -0
- package/dist/tui/local-model-contract.js +272 -0
- package/dist/vericify-bridge.js +3 -4
- package/dist/vericify-context.js +18 -6
- package/package.json +4 -6
|
@@ -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 .agents/ACE/ace-state.
|
|
8
|
+
* 2. Initialize AcePackedStore at .agents/ACE/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
|
|
@@ -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 .agents/ACE/ace-state.
|
|
8
|
+
* 2. Initialize AcePackedStore at .agents/ACE/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
|
|
@@ -22,8 +22,8 @@ import { openStore } from "./ace-packed-store.js";
|
|
|
22
22
|
import { bakeAllCoreKnowledge } from "./knowledge-bake.js";
|
|
23
23
|
import { bakeTopology } from "./topology-bake.js";
|
|
24
24
|
import { buildCatalog } from "./catalog-builder.js";
|
|
25
|
-
import { HookContextMaterializer } from "./materializers/hook-context-materializer.js";
|
|
26
25
|
import { HostFileMaterializer } from "./materializers/host-file-materializer.js";
|
|
26
|
+
import { ProjectionManager } from "./materializers/projection-manager.js";
|
|
27
27
|
import { TodoSyncer } from "./materializers/todo-syncer.js";
|
|
28
28
|
import { OPERATIONAL_ARTIFACT_REL_PATHS, operationalArtifactKey, } from "./store-artifacts.js";
|
|
29
29
|
import { STORE_VERSION } from "./types.js";
|
|
@@ -99,21 +99,31 @@ async function materializeStoreSurfaces(store, workspaceRoot, opts) {
|
|
|
99
99
|
includeClientConfigBundle: opts.includeClientConfigBundle ?? false,
|
|
100
100
|
})));
|
|
101
101
|
}
|
|
102
|
-
const hookCtx = new HookContextMaterializer(store, workspaceRoot);
|
|
103
|
-
await hookCtx.materialize();
|
|
104
|
-
materialized.push(join(workspaceRoot, ".agents", "ACE", "ace-hook-context.json"));
|
|
105
102
|
const todoSyncer = new TodoSyncer(store, workspaceRoot);
|
|
106
103
|
await todoSyncer.initStarter();
|
|
107
|
-
materialized.push(join(workspaceRoot, ".agents", "ACE", "tasks", "todo.md"));
|
|
108
104
|
await store.commit();
|
|
105
|
+
const projections = new ProjectionManager(store, workspaceRoot);
|
|
106
|
+
await projections.projectAfterCommit(["hook_context", "todo_state", "scheduler", "context_snapshots"]);
|
|
107
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "ace-hook-context.json"));
|
|
108
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "tasks", "todo.md"));
|
|
109
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "agent-state", "job-queue.json"));
|
|
110
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "agent-state", "job-locks.json"));
|
|
111
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "agent-state", "scheduler-lease.json"));
|
|
112
|
+
materialized.push(join(workspaceRoot, ".agents", "ACE", "agent-state", "context-snapshots", "index.json"));
|
|
109
113
|
return [...new Set(materialized)];
|
|
110
114
|
}
|
|
115
|
+
// Artifacts that receive live timestamp substitution on first seed.
|
|
116
|
+
const TIMESTAMPED_ARTIFACT_RELS = new Set(["agent-state/STATUS.md", "agent-state/EVIDENCE_LOG.md"]);
|
|
111
117
|
async function seedOperationalArtifacts(store) {
|
|
118
|
+
const bootstrapTs = new Date().toISOString();
|
|
112
119
|
for (const relPath of OPERATIONAL_ARTIFACT_REL_PATHS) {
|
|
113
120
|
const knowledgeKey = `knowledge/agent-state/${relPath.replace(/^agent-state\//, "")}`;
|
|
114
|
-
|
|
121
|
+
let content = relPath.endsWith("-archive.ndjson")
|
|
115
122
|
? ""
|
|
116
123
|
: (await store.getBlob(knowledgeKey)) ?? "";
|
|
124
|
+
if (TIMESTAMPED_ARTIFACT_RELS.has(relPath) && content.includes("{{BOOTSTRAP_TIMESTAMP}}")) {
|
|
125
|
+
content = content.replaceAll("{{BOOTSTRAP_TIMESTAMP}}", bootstrapTs);
|
|
126
|
+
}
|
|
117
127
|
await store.setBlob(operationalArtifactKey(relPath), content);
|
|
118
128
|
}
|
|
119
129
|
}
|
|
@@ -121,7 +131,7 @@ export async function bootstrapStoreWorkspace(opts) {
|
|
|
121
131
|
const { workspaceRoot, force = false } = opts;
|
|
122
132
|
const warnings = [];
|
|
123
133
|
const materialized = [];
|
|
124
|
-
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.
|
|
134
|
+
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.ace");
|
|
125
135
|
if (existsSync(storePath) && !force) {
|
|
126
136
|
if (!(opts.includeMcpConfig ?? false) && !(opts.includeClientConfigBundle ?? false) && !opts.llm) {
|
|
127
137
|
warnings.push(`Store already exists at ${storePath}. Use --force to reinitialize, or run 'ace migrate' to import existing state.`);
|
|
@@ -168,6 +178,8 @@ export async function bootstrapStoreWorkspace(opts) {
|
|
|
168
178
|
await store.setJSON("state/ledger/seq", 0);
|
|
169
179
|
await store.setJSON("state/scheduler/queue", []);
|
|
170
180
|
await store.setJSON("state/scheduler/locks", []);
|
|
181
|
+
await store.setJSON("state/scheduler/lease", null);
|
|
182
|
+
await store.setJSON("state/memory/context_snapshots/index.json", { snapshots: [] });
|
|
171
183
|
await store.setJSON("state/runtime/sessions/index", []);
|
|
172
184
|
await store.setJSON("state/discovery/index", []);
|
|
173
185
|
await store.setJSON("state/vericify/posts/index", []);
|
|
@@ -194,7 +206,7 @@ export async function bootstrapStoreWorkspace(opts) {
|
|
|
194
206
|
}
|
|
195
207
|
// ── ace repair ────────────────────────────────────────────────────────────────
|
|
196
208
|
export async function repairWorkspace(workspaceRoot) {
|
|
197
|
-
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.
|
|
209
|
+
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.ace");
|
|
198
210
|
if (!existsSync(storePath)) {
|
|
199
211
|
return [`No store found at ${storePath}. Run 'ace init' first.`];
|
|
200
212
|
}
|
|
@@ -208,10 +220,9 @@ export async function repairWorkspace(workspaceRoot) {
|
|
|
208
220
|
includeMcpConfig: hostPolicy.include_mcp_config ?? false,
|
|
209
221
|
includeClientConfigBundle: hostPolicy.include_client_config_bundle ?? false,
|
|
210
222
|
});
|
|
211
|
-
// Re-materialize hook context
|
|
212
|
-
const hookCtx = new HookContextMaterializer(store, workspaceRoot);
|
|
213
|
-
await hookCtx.materialize();
|
|
214
223
|
await store.commit();
|
|
224
|
+
const projections = new ProjectionManager(store, workspaceRoot);
|
|
225
|
+
await projections.projectAfterCommit(["hook_context", "todo_state", "scheduler", "context_snapshots"]);
|
|
215
226
|
}
|
|
216
227
|
catch (e) {
|
|
217
228
|
warnings.push(`repair error: ${e.message}`);
|
|
@@ -223,7 +234,7 @@ export async function repairWorkspace(workspaceRoot) {
|
|
|
223
234
|
}
|
|
224
235
|
// ── ace compact ───────────────────────────────────────────────────────────────
|
|
225
236
|
export async function compactWorkspace(workspaceRoot) {
|
|
226
|
-
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.
|
|
237
|
+
const storePath = join(workspaceRoot, ".agents", "ACE", "ace-state.ace");
|
|
227
238
|
if (!existsSync(storePath)) {
|
|
228
239
|
throw new Error(`No store found at ${storePath}. Run 'ace init' first.`);
|
|
229
240
|
}
|
|
@@ -44,10 +44,10 @@ export async function buildCatalog(store) {
|
|
|
44
44
|
return catalog;
|
|
45
45
|
}
|
|
46
46
|
function collectKeysByPrefix(store, prefix) {
|
|
47
|
-
// Access internal index synchronously — only call after store is opened
|
|
48
47
|
const keys = [];
|
|
49
|
-
// @ts-ignore — accessing private
|
|
50
|
-
|
|
48
|
+
// @ts-ignore — accessing private kvIndex for synchronous catalog building
|
|
49
|
+
const kvIndex = store["kvIndex"];
|
|
50
|
+
for (const key of kvIndex.keys()) {
|
|
51
51
|
if (key.startsWith(prefix))
|
|
52
52
|
keys.push(key);
|
|
53
53
|
}
|
package/dist/store/importer.d.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Importer — Phase 7
|
|
3
3
|
*
|
|
4
4
|
* Reads all current files from an existing ACE workspace and creates a new
|
|
5
|
-
* ace-state.
|
|
5
|
+
* ace-state.ace with all content properly categorized.
|
|
6
6
|
*
|
|
7
7
|
* Used by `ace migrate` CLI command for one-time migration from file-backed
|
|
8
|
-
* workspaces to the
|
|
8
|
+
* workspaces to the ACEPACK store.
|
|
9
9
|
*/
|
|
10
10
|
export interface ImportResult {
|
|
11
11
|
handoffs: number;
|
package/dist/store/importer.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Importer — Phase 7
|
|
3
3
|
*
|
|
4
4
|
* Reads all current files from an existing ACE workspace and creates a new
|
|
5
|
-
* ace-state.
|
|
5
|
+
* ace-state.ace with all content properly categorized.
|
|
6
6
|
*
|
|
7
7
|
* Used by `ace migrate` CLI command for one-time migration from file-backed
|
|
8
|
-
* workspaces to the
|
|
8
|
+
* workspaces to the ACEPACK store.
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, readFileSync } from "fs";
|
|
11
11
|
import { join } from "path";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AcePackedStore } from "../ace-packed-store.js";
|
|
2
|
+
export declare class ContextSnapshotMaterializer {
|
|
3
|
+
private store;
|
|
4
|
+
private workspaceRoot;
|
|
5
|
+
private repo;
|
|
6
|
+
constructor(store: AcePackedStore, workspaceRoot: string);
|
|
7
|
+
private directoryPath;
|
|
8
|
+
materializeAll(): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=context-snapshot-materializer.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ContextSnapshotRepository } from "../repositories/context-snapshot-repository.js";
|
|
4
|
+
function writeText(path, content) {
|
|
5
|
+
const dir = dirname(path);
|
|
6
|
+
if (!existsSync(dir))
|
|
7
|
+
mkdirSync(dir, { recursive: true });
|
|
8
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
9
|
+
writeFileSync(tmpPath, content, "utf-8");
|
|
10
|
+
renameSync(tmpPath, path);
|
|
11
|
+
}
|
|
12
|
+
export class ContextSnapshotMaterializer {
|
|
13
|
+
store;
|
|
14
|
+
workspaceRoot;
|
|
15
|
+
repo;
|
|
16
|
+
constructor(store, workspaceRoot) {
|
|
17
|
+
this.store = store;
|
|
18
|
+
this.workspaceRoot = workspaceRoot;
|
|
19
|
+
this.repo = new ContextSnapshotRepository(store);
|
|
20
|
+
}
|
|
21
|
+
directoryPath() {
|
|
22
|
+
return join(this.workspaceRoot, ".agents", "ACE", "agent-state", "context-snapshots");
|
|
23
|
+
}
|
|
24
|
+
async materializeAll() {
|
|
25
|
+
const dir = this.directoryPath();
|
|
26
|
+
if (!existsSync(dir))
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
const snapshots = await this.repo.listSnapshots();
|
|
29
|
+
const retained = new Set(["index.json"]);
|
|
30
|
+
for (const snapshot of snapshots) {
|
|
31
|
+
retained.add(snapshot.file);
|
|
32
|
+
writeText(join(dir, snapshot.file), `${JSON.stringify(snapshot.record, null, 2)}\n`);
|
|
33
|
+
}
|
|
34
|
+
writeText(join(dir, "index.json"), `${JSON.stringify({
|
|
35
|
+
snapshots: snapshots.map(({ name, file, timestamp, summary }) => ({
|
|
36
|
+
name,
|
|
37
|
+
file,
|
|
38
|
+
timestamp,
|
|
39
|
+
summary,
|
|
40
|
+
})),
|
|
41
|
+
}, null, 2)}\n`);
|
|
42
|
+
for (const file of readdirSync(dir)) {
|
|
43
|
+
if (!file.endsWith(".json"))
|
|
44
|
+
continue;
|
|
45
|
+
if (retained.has(file))
|
|
46
|
+
continue;
|
|
47
|
+
rmSync(join(dir, file), { force: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=context-snapshot-materializer.js.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Called at every session start, prompt submit, and tool gating change.
|
|
6
6
|
*
|
|
7
7
|
* The hook dispatcher (ace-hook-dispatch.mjs) reads ONLY this file.
|
|
8
|
-
* It never touches the .
|
|
8
|
+
* It never touches the .ace store directly.
|
|
9
9
|
*/
|
|
10
10
|
import { AcePackedStore } from "../ace-packed-store.js";
|
|
11
11
|
export declare class HookContextMaterializer {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Called at every session start, prompt submit, and tool gating change.
|
|
6
6
|
*
|
|
7
7
|
* The hook dispatcher (ace-hook-dispatch.mjs) reads ONLY this file.
|
|
8
|
-
* It never touches the .
|
|
8
|
+
* It never touches the .ace store directly.
|
|
9
9
|
*/
|
|
10
10
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
11
11
|
import { join, dirname } from "path";
|
|
@@ -32,6 +32,12 @@ export declare class HostFileMaterializer {
|
|
|
32
32
|
private buildVsCodeMcpEntry;
|
|
33
33
|
private buildAllProjectedEntries;
|
|
34
34
|
seedStorePayload(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Materialize the full MCP client config bundle to `.mcp-config/` at the workspace root.
|
|
37
|
+
* Writes one config file per supported client plus hook files and a README.
|
|
38
|
+
* Called by `ace preconfig`.
|
|
39
|
+
*/
|
|
40
|
+
materializeMcpBundle(): Promise<string[]>;
|
|
35
41
|
materializeAll(opts?: HostMaterializeOptions): Promise<string[]>;
|
|
36
42
|
}
|
|
37
43
|
//# sourceMappingURL=host-file-materializer.d.ts.map
|
|
@@ -216,7 +216,7 @@ export class HostFileMaterializer {
|
|
|
216
216
|
fallbackContent: [
|
|
217
217
|
"# ACE MCP Client Config Bundle",
|
|
218
218
|
"",
|
|
219
|
-
`Generated for ${this.packageName}. Prefer global or user-level MCP installs; this bundle is stored in ace-state.
|
|
219
|
+
`Generated for ${this.packageName}. Prefer global or user-level MCP installs; this bundle is stored in ace-state.ace for export and repair.`,
|
|
220
220
|
"",
|
|
221
221
|
"## Files",
|
|
222
222
|
...ALL_MCP_CLIENTS.map((client) => `- ${client}: ${fileNames[client]}`),
|
|
@@ -249,6 +249,19 @@ export class HostFileMaterializer {
|
|
|
249
249
|
await this.seedProjectedText(entry);
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Materialize the full MCP client config bundle to `.mcp-config/` at the workspace root.
|
|
254
|
+
* Writes one config file per supported client plus hook files and a README.
|
|
255
|
+
* Called by `ace preconfig`.
|
|
256
|
+
*/
|
|
257
|
+
async materializeMcpBundle() {
|
|
258
|
+
await this.seedStorePayload();
|
|
259
|
+
const paths = [];
|
|
260
|
+
for (const entry of [...this.buildMcpBundleEntries(), ...this.buildOptionalHookBundleEntries()]) {
|
|
261
|
+
paths.push(await this.materializeEntry(entry));
|
|
262
|
+
}
|
|
263
|
+
return paths;
|
|
264
|
+
}
|
|
252
265
|
async materializeAll(opts = {}) {
|
|
253
266
|
const paths = [];
|
|
254
267
|
const includeMcpConfig = opts.includeMcpConfig ?? false;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AcePackedStore } from "../ace-packed-store.js";
|
|
2
|
+
export type RuntimeProjectionTarget = "context_snapshots" | "handoffs" | "ledger" | "scheduler" | "status_events" | "todo_state" | "vericify_posts" | "hook_context";
|
|
3
|
+
export declare class ProjectionManager {
|
|
4
|
+
private store;
|
|
5
|
+
private workspaceRoot;
|
|
6
|
+
private vericify;
|
|
7
|
+
private todoSyncer;
|
|
8
|
+
private hookContext;
|
|
9
|
+
private contextSnapshots;
|
|
10
|
+
private scheduler;
|
|
11
|
+
constructor(store: AcePackedStore, workspaceRoot: string);
|
|
12
|
+
projectAfterCommit(targets: readonly RuntimeProjectionTarget[]): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=projection-manager.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ContextSnapshotMaterializer } from "./context-snapshot-materializer.js";
|
|
2
|
+
import { HookContextMaterializer } from "./hook-context-materializer.js";
|
|
3
|
+
import { SchedulerProjectionMaterializer } from "./scheduler-projection-materializer.js";
|
|
4
|
+
import { TodoSyncer } from "./todo-syncer.js";
|
|
5
|
+
import { VericifyProjector } from "./vericify-projector.js";
|
|
6
|
+
export class ProjectionManager {
|
|
7
|
+
store;
|
|
8
|
+
workspaceRoot;
|
|
9
|
+
vericify;
|
|
10
|
+
todoSyncer;
|
|
11
|
+
hookContext;
|
|
12
|
+
contextSnapshots;
|
|
13
|
+
scheduler;
|
|
14
|
+
constructor(store, workspaceRoot) {
|
|
15
|
+
this.store = store;
|
|
16
|
+
this.workspaceRoot = workspaceRoot;
|
|
17
|
+
this.vericify = new VericifyProjector(store, workspaceRoot);
|
|
18
|
+
this.todoSyncer = new TodoSyncer(store, workspaceRoot);
|
|
19
|
+
this.hookContext = new HookContextMaterializer(store, workspaceRoot);
|
|
20
|
+
this.contextSnapshots = new ContextSnapshotMaterializer(store, workspaceRoot);
|
|
21
|
+
this.scheduler = new SchedulerProjectionMaterializer(store, workspaceRoot);
|
|
22
|
+
}
|
|
23
|
+
async projectAfterCommit(targets) {
|
|
24
|
+
const uniqueTargets = [...new Set(targets)];
|
|
25
|
+
let stagedStoreWrites = false;
|
|
26
|
+
let renderedScheduler;
|
|
27
|
+
for (const target of uniqueTargets) {
|
|
28
|
+
if (target === "handoffs") {
|
|
29
|
+
await this.vericify.projectHandoffs();
|
|
30
|
+
stagedStoreWrites = true;
|
|
31
|
+
}
|
|
32
|
+
else if (target === "ledger") {
|
|
33
|
+
await this.vericify.projectLedger();
|
|
34
|
+
stagedStoreWrites = true;
|
|
35
|
+
}
|
|
36
|
+
else if (target === "status_events") {
|
|
37
|
+
await this.vericify.projectStatusEvents();
|
|
38
|
+
stagedStoreWrites = true;
|
|
39
|
+
}
|
|
40
|
+
else if (target === "todo_state") {
|
|
41
|
+
await this.vericify.projectTodos();
|
|
42
|
+
stagedStoreWrites = true;
|
|
43
|
+
}
|
|
44
|
+
else if (target === "vericify_posts") {
|
|
45
|
+
await this.vericify.projectVericifyPosts();
|
|
46
|
+
stagedStoreWrites = true;
|
|
47
|
+
}
|
|
48
|
+
else if (target === "scheduler") {
|
|
49
|
+
renderedScheduler = await this.scheduler.render();
|
|
50
|
+
await this.scheduler.stageStoreProjections(renderedScheduler);
|
|
51
|
+
stagedStoreWrites = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (stagedStoreWrites) {
|
|
55
|
+
await this.store.commit();
|
|
56
|
+
}
|
|
57
|
+
for (const target of uniqueTargets) {
|
|
58
|
+
if (target === "todo_state") {
|
|
59
|
+
await this.todoSyncer.writeToFile();
|
|
60
|
+
}
|
|
61
|
+
else if (target === "hook_context") {
|
|
62
|
+
await this.hookContext.materialize();
|
|
63
|
+
}
|
|
64
|
+
else if (target === "context_snapshots") {
|
|
65
|
+
await this.contextSnapshots.materializeAll();
|
|
66
|
+
}
|
|
67
|
+
else if (target === "scheduler") {
|
|
68
|
+
await this.scheduler.materializeFiles(renderedScheduler);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=projection-manager.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AcePackedStore } from "../ace-packed-store.js";
|
|
2
|
+
export declare class SchedulerProjectionMaterializer {
|
|
3
|
+
private store;
|
|
4
|
+
private workspaceRoot;
|
|
5
|
+
private repo;
|
|
6
|
+
constructor(store: AcePackedStore, workspaceRoot: string);
|
|
7
|
+
private filePath;
|
|
8
|
+
render(): Promise<{
|
|
9
|
+
queue: string;
|
|
10
|
+
locks: string;
|
|
11
|
+
lease: string;
|
|
12
|
+
}>;
|
|
13
|
+
stageStoreProjections(rendered?: Awaited<ReturnType<SchedulerProjectionMaterializer["render"]>>): Promise<void>;
|
|
14
|
+
materializeFiles(rendered?: Awaited<ReturnType<SchedulerProjectionMaterializer["render"]>>): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=scheduler-projection-materializer.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { operationalArtifactKey } from "../store-artifacts.js";
|
|
4
|
+
import { SchedulerRepository } from "../repositories/scheduler-repository.js";
|
|
5
|
+
function writeText(path, content) {
|
|
6
|
+
const dir = dirname(path);
|
|
7
|
+
if (!existsSync(dir))
|
|
8
|
+
mkdirSync(dir, { recursive: true });
|
|
9
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
10
|
+
writeFileSync(tmpPath, content, "utf-8");
|
|
11
|
+
renameSync(tmpPath, path);
|
|
12
|
+
}
|
|
13
|
+
export class SchedulerProjectionMaterializer {
|
|
14
|
+
store;
|
|
15
|
+
workspaceRoot;
|
|
16
|
+
repo;
|
|
17
|
+
constructor(store, workspaceRoot) {
|
|
18
|
+
this.store = store;
|
|
19
|
+
this.workspaceRoot = workspaceRoot;
|
|
20
|
+
this.repo = new SchedulerRepository(store);
|
|
21
|
+
}
|
|
22
|
+
filePath(file) {
|
|
23
|
+
return join(this.workspaceRoot, ".agents", "ACE", "agent-state", file);
|
|
24
|
+
}
|
|
25
|
+
async render() {
|
|
26
|
+
const queue = await this.repo.readQueue();
|
|
27
|
+
const locks = await this.repo.readLockTable();
|
|
28
|
+
const lease = await this.repo.readLease();
|
|
29
|
+
return {
|
|
30
|
+
queue: `${JSON.stringify(queue, null, 2)}\n`,
|
|
31
|
+
locks: `${JSON.stringify(locks, null, 2)}\n`,
|
|
32
|
+
lease: `${JSON.stringify(lease ?? null, null, 2)}\n`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async stageStoreProjections(rendered) {
|
|
36
|
+
const content = rendered ?? (await this.render());
|
|
37
|
+
await this.store.setBlob(operationalArtifactKey("agent-state/job-queue.json"), content.queue);
|
|
38
|
+
await this.store.setBlob(operationalArtifactKey("agent-state/job-locks.json"), content.locks);
|
|
39
|
+
await this.store.setBlob(operationalArtifactKey("agent-state/scheduler-lease.json"), content.lease);
|
|
40
|
+
}
|
|
41
|
+
async materializeFiles(rendered) {
|
|
42
|
+
const content = rendered ?? (await this.render());
|
|
43
|
+
writeText(this.filePath("job-queue.json"), content.queue);
|
|
44
|
+
writeText(this.filePath("job-locks.json"), content.locks);
|
|
45
|
+
writeText(this.filePath("scheduler-lease.json"), content.lease);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=scheduler-projection-materializer.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AcePackedStore } from "../ace-packed-store.js";
|
|
2
|
+
export interface ContextSnapshotRecord {
|
|
3
|
+
name: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
decisions: string[];
|
|
7
|
+
open_questions: string[];
|
|
8
|
+
artifacts: string[];
|
|
9
|
+
metadata: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface ContextSnapshotIndexEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
file: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
summary: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SaveContextSnapshotInput {
|
|
18
|
+
name: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
decisions?: string[];
|
|
21
|
+
open_questions?: string[];
|
|
22
|
+
artifacts?: string[];
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class ContextSnapshotRepository {
|
|
27
|
+
private store;
|
|
28
|
+
constructor(store: AcePackedStore);
|
|
29
|
+
private snapshotKey;
|
|
30
|
+
getIndex(): Promise<ContextSnapshotIndexEntry[]>;
|
|
31
|
+
getSnapshotByFile(file: string): Promise<ContextSnapshotRecord | undefined>;
|
|
32
|
+
getSnapshotByName(name: string): Promise<{
|
|
33
|
+
entry: ContextSnapshotIndexEntry;
|
|
34
|
+
record: ContextSnapshotRecord;
|
|
35
|
+
} | undefined>;
|
|
36
|
+
saveSnapshot(input: SaveContextSnapshotInput): Promise<{
|
|
37
|
+
entry: ContextSnapshotIndexEntry;
|
|
38
|
+
record: ContextSnapshotRecord;
|
|
39
|
+
file: string;
|
|
40
|
+
}>;
|
|
41
|
+
listSnapshots(): Promise<Array<ContextSnapshotIndexEntry & {
|
|
42
|
+
record: ContextSnapshotRecord;
|
|
43
|
+
}>>;
|
|
44
|
+
private allocateFilename;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=context-snapshot-repository.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ContentSource, EntityKind } from "../types.js";
|
|
2
|
+
const INDEX_KEY = "state/memory/context_snapshots/index.json";
|
|
3
|
+
const SNAPSHOT_PREFIX = "state/memory/context_snapshots/";
|
|
4
|
+
function uniqueStrings(values) {
|
|
5
|
+
return Array.from(new Set((values ?? []).filter((value) => typeof value === "string" && value.trim().length > 0)));
|
|
6
|
+
}
|
|
7
|
+
function slugify(name) {
|
|
8
|
+
const base = name
|
|
9
|
+
.trim()
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
12
|
+
.replace(/^-+|-+$/g, "")
|
|
13
|
+
.slice(0, 48);
|
|
14
|
+
return base.length > 0 ? base : "snapshot";
|
|
15
|
+
}
|
|
16
|
+
export class ContextSnapshotRepository {
|
|
17
|
+
store;
|
|
18
|
+
constructor(store) {
|
|
19
|
+
this.store = store;
|
|
20
|
+
}
|
|
21
|
+
snapshotKey(file) {
|
|
22
|
+
return `${SNAPSHOT_PREFIX}${file}`;
|
|
23
|
+
}
|
|
24
|
+
async getIndex() {
|
|
25
|
+
const index = await this.store.getJSON(INDEX_KEY);
|
|
26
|
+
if (Array.isArray(index))
|
|
27
|
+
return index;
|
|
28
|
+
return Array.isArray(index?.snapshots) ? index.snapshots : [];
|
|
29
|
+
}
|
|
30
|
+
async getSnapshotByFile(file) {
|
|
31
|
+
return this.store.getJSON(this.snapshotKey(file));
|
|
32
|
+
}
|
|
33
|
+
async getSnapshotByName(name) {
|
|
34
|
+
const index = await this.getIndex();
|
|
35
|
+
const entry = index.find((candidate) => candidate.name.toLowerCase() === name.toLowerCase());
|
|
36
|
+
if (!entry)
|
|
37
|
+
return undefined;
|
|
38
|
+
const record = await this.getSnapshotByFile(entry.file);
|
|
39
|
+
if (!record)
|
|
40
|
+
return undefined;
|
|
41
|
+
return { entry, record };
|
|
42
|
+
}
|
|
43
|
+
async saveSnapshot(input) {
|
|
44
|
+
const index = await this.getIndex();
|
|
45
|
+
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
46
|
+
const existing = index.find((candidate) => candidate.name.toLowerCase() === input.name.toLowerCase());
|
|
47
|
+
const file = existing?.file ?? this.allocateFilename(index, input.name);
|
|
48
|
+
const record = {
|
|
49
|
+
name: input.name,
|
|
50
|
+
timestamp,
|
|
51
|
+
summary: input.summary,
|
|
52
|
+
decisions: uniqueStrings(input.decisions),
|
|
53
|
+
open_questions: uniqueStrings(input.open_questions),
|
|
54
|
+
artifacts: uniqueStrings(input.artifacts),
|
|
55
|
+
metadata: input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata)
|
|
56
|
+
? input.metadata
|
|
57
|
+
: {},
|
|
58
|
+
};
|
|
59
|
+
await this.store.setJSON(this.snapshotKey(file), record);
|
|
60
|
+
await this.store.appendEntry({
|
|
61
|
+
kind: EntityKind.ContextSnapshot,
|
|
62
|
+
content_source: ContentSource.Runtime,
|
|
63
|
+
key: this.snapshotKey(file),
|
|
64
|
+
payload: {
|
|
65
|
+
name: record.name,
|
|
66
|
+
file,
|
|
67
|
+
timestamp: record.timestamp,
|
|
68
|
+
summary: record.summary,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
const nextIndex = index.filter((candidate) => candidate.name.toLowerCase() !== input.name.toLowerCase());
|
|
72
|
+
const entry = {
|
|
73
|
+
name: record.name,
|
|
74
|
+
file,
|
|
75
|
+
timestamp: record.timestamp,
|
|
76
|
+
summary: record.summary,
|
|
77
|
+
};
|
|
78
|
+
nextIndex.push(entry);
|
|
79
|
+
nextIndex.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
|
|
80
|
+
await this.store.setJSON(INDEX_KEY, { snapshots: nextIndex });
|
|
81
|
+
return { entry, record, file };
|
|
82
|
+
}
|
|
83
|
+
async listSnapshots() {
|
|
84
|
+
const index = await this.getIndex();
|
|
85
|
+
const rows = [];
|
|
86
|
+
for (const entry of index) {
|
|
87
|
+
const record = await this.getSnapshotByFile(entry.file);
|
|
88
|
+
if (!record)
|
|
89
|
+
continue;
|
|
90
|
+
rows.push({ ...entry, record });
|
|
91
|
+
}
|
|
92
|
+
return rows;
|
|
93
|
+
}
|
|
94
|
+
allocateFilename(index, name) {
|
|
95
|
+
const base = slugify(name);
|
|
96
|
+
const used = new Set(index.map((entry) => entry.file));
|
|
97
|
+
if (!used.has(`${base}.json`))
|
|
98
|
+
return `${base}.json`;
|
|
99
|
+
let suffix = 2;
|
|
100
|
+
while (used.has(`${base}-${suffix}.json`))
|
|
101
|
+
suffix += 1;
|
|
102
|
+
return `${base}-${suffix}.json`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=context-snapshot-repository.js.map
|