@voybio/ace-swarm 0.2.0 → 0.2.2
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 +15 -15
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/STATUS.md +2 -2
- 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 +67 -0
- package/dist/handoff-registry.js +11 -7
- package/dist/helpers.js +74 -8
- 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/bootstrap-store.js +20 -9
- 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/host-file-materializer.d.ts +6 -0
- package/dist/store/materializers/host-file-materializer.js +13 -0
- 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/state-reader.d.ts +8 -1
- package/dist/store/state-reader.js +12 -1
- package/dist/store/store-artifacts.js +31 -5
- package/dist/store/store-authority-audit.d.ts +30 -0
- package/dist/store/store-authority-audit.js +448 -0
- package/dist/store/types.d.ts +2 -0
- package/dist/store/types.js +1 -0
- package/dist/todo-state.js +179 -11
- package/dist/tools-files.js +2 -1
- package/dist/tools-framework.js +60 -0
- 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 +5 -0
- package/dist/tui/dashboard.js +38 -2
- package/dist/tui/index.d.ts +5 -0
- package/dist/tui/index.js +146 -2
- 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 +1 -1
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type AcePackedStore } from "../ace-packed-store.js";
|
|
2
|
+
export interface AceSessionActivationLedger {
|
|
3
|
+
session_id: string;
|
|
4
|
+
created_at: number;
|
|
5
|
+
updated_at: number;
|
|
6
|
+
prompt_count: number;
|
|
7
|
+
shown_nudges: string[];
|
|
8
|
+
accepted_nudges: string[];
|
|
9
|
+
activated_tools: string[];
|
|
10
|
+
activated_roles: string[];
|
|
11
|
+
last_recommended_action?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AceSessionContinuityRecord {
|
|
14
|
+
session_id: string;
|
|
15
|
+
created_at: number;
|
|
16
|
+
updated_at: number;
|
|
17
|
+
workspace_root: string;
|
|
18
|
+
state_resolution_mode: "top_level" | "nested_projection" | "store_projection" | "missing";
|
|
19
|
+
last_role?: string;
|
|
20
|
+
last_preflight_state?: "ready" | "attention_required" | "blocked";
|
|
21
|
+
last_bridge_status?: "running" | "needs_input" | "approval_pending" | "retrying" | "blocked" | "failed" | "done";
|
|
22
|
+
active_tool?: string;
|
|
23
|
+
blockers: string[];
|
|
24
|
+
recent_decisions: string[];
|
|
25
|
+
recommended_next_action?: string;
|
|
26
|
+
evidence_refs: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface AceRuntimeStatusPacket {
|
|
29
|
+
session_id: string;
|
|
30
|
+
process_id: number;
|
|
31
|
+
turn_count: number;
|
|
32
|
+
role?: string;
|
|
33
|
+
bridge_status: "running" | "needs_input" | "approval_pending" | "retrying" | "blocked" | "failed" | "done";
|
|
34
|
+
preflight_state: "ready" | "attention_required" | "blocked";
|
|
35
|
+
approval_state?: "not_required" | "pending" | "approved" | "rejected";
|
|
36
|
+
retry_state?: {
|
|
37
|
+
attempt: number;
|
|
38
|
+
max_attempts?: number;
|
|
39
|
+
backoff_ms?: number;
|
|
40
|
+
reason?: string;
|
|
41
|
+
};
|
|
42
|
+
poll_state?: {
|
|
43
|
+
active: boolean;
|
|
44
|
+
interval_ms?: number;
|
|
45
|
+
waiting_on?: string;
|
|
46
|
+
};
|
|
47
|
+
current_task?: string;
|
|
48
|
+
active_tool?: string;
|
|
49
|
+
active_tool_role?: string;
|
|
50
|
+
recommended_next_action?: string;
|
|
51
|
+
blocked_reason?: string;
|
|
52
|
+
tokens_in?: number;
|
|
53
|
+
tokens_out?: number;
|
|
54
|
+
updated_at: number;
|
|
55
|
+
}
|
|
56
|
+
export interface AceArchivedChatRecord {
|
|
57
|
+
session_id: string;
|
|
58
|
+
tab_id: string;
|
|
59
|
+
label: string;
|
|
60
|
+
provider: string;
|
|
61
|
+
model: string;
|
|
62
|
+
created_at: number;
|
|
63
|
+
archived_at: number;
|
|
64
|
+
last_active_at: number;
|
|
65
|
+
transcript_excerpt?: string;
|
|
66
|
+
transcript_summary?: string;
|
|
67
|
+
ace_context: {
|
|
68
|
+
resolved_layout_mode: string;
|
|
69
|
+
role?: string;
|
|
70
|
+
bridge_status: string;
|
|
71
|
+
active_tool?: string;
|
|
72
|
+
blocked_reason?: string;
|
|
73
|
+
approval_state?: string;
|
|
74
|
+
retry_state?: Record<string, unknown>;
|
|
75
|
+
recommended_next_action?: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export declare class LocalModelRuntimeRepository {
|
|
79
|
+
private store;
|
|
80
|
+
constructor(store: AcePackedStore);
|
|
81
|
+
private activationKey;
|
|
82
|
+
private continuityKey;
|
|
83
|
+
private statusKey;
|
|
84
|
+
private archiveKey;
|
|
85
|
+
getActivationLedger(sessionId: string): Promise<AceSessionActivationLedger | undefined>;
|
|
86
|
+
upsertActivationLedger(input: Partial<AceSessionActivationLedger> & Pick<AceSessionActivationLedger, "session_id">): Promise<AceSessionActivationLedger>;
|
|
87
|
+
getContinuityRecord(sessionId: string): Promise<AceSessionContinuityRecord | undefined>;
|
|
88
|
+
upsertContinuityRecord(input: Omit<AceSessionContinuityRecord, "updated_at" | "created_at"> & Partial<Pick<AceSessionContinuityRecord, "created_at">>): Promise<AceSessionContinuityRecord>;
|
|
89
|
+
getRuntimeStatus(sessionId: string): Promise<AceRuntimeStatusPacket | undefined>;
|
|
90
|
+
upsertRuntimeStatus(input: Omit<AceRuntimeStatusPacket, "updated_at">): Promise<AceRuntimeStatusPacket>;
|
|
91
|
+
listRuntimeStatuses(): Promise<AceRuntimeStatusPacket[]>;
|
|
92
|
+
archiveChat(input: Omit<AceArchivedChatRecord, "archived_at"> & Partial<Pick<AceArchivedChatRecord, "archived_at">>): Promise<AceArchivedChatRecord>;
|
|
93
|
+
listArchivedChats(): Promise<AceArchivedChatRecord[]>;
|
|
94
|
+
private addToIndex;
|
|
95
|
+
private appendLifecycleEntry;
|
|
96
|
+
}
|
|
97
|
+
export declare function withLocalModelRuntimeRepository<T>(workspaceRoot: string, fn: (repo: LocalModelRuntimeRepository) => Promise<T>): Promise<T | undefined>;
|
|
98
|
+
//# sourceMappingURL=local-model-runtime-repository.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { openStore } from "../ace-packed-store.js";
|
|
3
|
+
import { ContentSource, EntityKind } from "../types.js";
|
|
4
|
+
import { getWorkspaceStorePath } from "../store-snapshot.js";
|
|
5
|
+
import { withStoreWriteQueue } from "../write-queue.js";
|
|
6
|
+
const ACTIVATION_INDEX_KEY = "state/ui/session_activation/index";
|
|
7
|
+
const CONTINUITY_INDEX_KEY = "state/runtime/session_continuity/index";
|
|
8
|
+
const STATUS_INDEX_KEY = "state/runtime/ui_status/index";
|
|
9
|
+
const ARCHIVE_INDEX_KEY = "state/ui/chat_archives/index";
|
|
10
|
+
function uniqueStrings(values) {
|
|
11
|
+
return Array.from(new Set((values ?? []).filter((value) => typeof value === "string" && value.length > 0)));
|
|
12
|
+
}
|
|
13
|
+
export class LocalModelRuntimeRepository {
|
|
14
|
+
store;
|
|
15
|
+
constructor(store) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
}
|
|
18
|
+
activationKey(sessionId) {
|
|
19
|
+
return `state/ui/session_activation/${sessionId}`;
|
|
20
|
+
}
|
|
21
|
+
continuityKey(sessionId) {
|
|
22
|
+
return `state/runtime/session_continuity/${sessionId}`;
|
|
23
|
+
}
|
|
24
|
+
statusKey(sessionId) {
|
|
25
|
+
return `state/runtime/ui_status/${sessionId}`;
|
|
26
|
+
}
|
|
27
|
+
archiveKey(sessionId) {
|
|
28
|
+
return `state/ui/chat_archives/${sessionId}`;
|
|
29
|
+
}
|
|
30
|
+
async getActivationLedger(sessionId) {
|
|
31
|
+
return this.store.getJSON(this.activationKey(sessionId));
|
|
32
|
+
}
|
|
33
|
+
async upsertActivationLedger(input) {
|
|
34
|
+
const current = await this.getActivationLedger(input.session_id);
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const record = {
|
|
37
|
+
session_id: input.session_id,
|
|
38
|
+
created_at: current?.created_at ?? now,
|
|
39
|
+
updated_at: now,
|
|
40
|
+
prompt_count: input.prompt_count ?? current?.prompt_count ?? 0,
|
|
41
|
+
shown_nudges: uniqueStrings(input.shown_nudges ?? current?.shown_nudges ?? []),
|
|
42
|
+
accepted_nudges: uniqueStrings(input.accepted_nudges ?? current?.accepted_nudges ?? []),
|
|
43
|
+
activated_tools: uniqueStrings(input.activated_tools ?? current?.activated_tools ?? []),
|
|
44
|
+
activated_roles: uniqueStrings(input.activated_roles ?? current?.activated_roles ?? []),
|
|
45
|
+
last_recommended_action: input.last_recommended_action ?? current?.last_recommended_action,
|
|
46
|
+
};
|
|
47
|
+
await this.store.setJSON(this.activationKey(record.session_id), record);
|
|
48
|
+
await this.appendLifecycleEntry(this.activationKey(record.session_id), {
|
|
49
|
+
op: current ? "activation_update" : "activation_create",
|
|
50
|
+
session_id: record.session_id,
|
|
51
|
+
});
|
|
52
|
+
await this.addToIndex(ACTIVATION_INDEX_KEY, record.session_id);
|
|
53
|
+
return record;
|
|
54
|
+
}
|
|
55
|
+
async getContinuityRecord(sessionId) {
|
|
56
|
+
return this.store.getJSON(this.continuityKey(sessionId));
|
|
57
|
+
}
|
|
58
|
+
async upsertContinuityRecord(input) {
|
|
59
|
+
const current = await this.getContinuityRecord(input.session_id);
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
const record = {
|
|
62
|
+
...input,
|
|
63
|
+
created_at: current?.created_at ?? input.created_at ?? now,
|
|
64
|
+
updated_at: now,
|
|
65
|
+
blockers: uniqueStrings(input.blockers ?? current?.blockers ?? []),
|
|
66
|
+
recent_decisions: uniqueStrings(input.recent_decisions ?? current?.recent_decisions ?? []),
|
|
67
|
+
evidence_refs: uniqueStrings(input.evidence_refs ?? current?.evidence_refs ?? []),
|
|
68
|
+
};
|
|
69
|
+
await this.store.setJSON(this.continuityKey(record.session_id), record);
|
|
70
|
+
await this.appendLifecycleEntry(this.continuityKey(record.session_id), {
|
|
71
|
+
op: current ? "continuity_update" : "continuity_create",
|
|
72
|
+
session_id: record.session_id,
|
|
73
|
+
bridge_status: record.last_bridge_status,
|
|
74
|
+
});
|
|
75
|
+
await this.addToIndex(CONTINUITY_INDEX_KEY, record.session_id);
|
|
76
|
+
return record;
|
|
77
|
+
}
|
|
78
|
+
async getRuntimeStatus(sessionId) {
|
|
79
|
+
return this.store.getJSON(this.statusKey(sessionId));
|
|
80
|
+
}
|
|
81
|
+
async upsertRuntimeStatus(input) {
|
|
82
|
+
const record = {
|
|
83
|
+
...input,
|
|
84
|
+
updated_at: Date.now(),
|
|
85
|
+
};
|
|
86
|
+
await this.store.setJSON(this.statusKey(record.session_id), record);
|
|
87
|
+
await this.appendLifecycleEntry(this.statusKey(record.session_id), {
|
|
88
|
+
op: "runtime_status",
|
|
89
|
+
session_id: record.session_id,
|
|
90
|
+
bridge_status: record.bridge_status,
|
|
91
|
+
preflight_state: record.preflight_state,
|
|
92
|
+
});
|
|
93
|
+
await this.addToIndex(STATUS_INDEX_KEY, record.session_id);
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
async listRuntimeStatuses() {
|
|
97
|
+
const index = await this.store.getJSON(STATUS_INDEX_KEY) ?? [];
|
|
98
|
+
const statuses = [];
|
|
99
|
+
for (const sessionId of index) {
|
|
100
|
+
const record = await this.getRuntimeStatus(sessionId);
|
|
101
|
+
if (record)
|
|
102
|
+
statuses.push(record);
|
|
103
|
+
}
|
|
104
|
+
return statuses.sort((left, right) => right.updated_at - left.updated_at);
|
|
105
|
+
}
|
|
106
|
+
async archiveChat(input) {
|
|
107
|
+
const record = {
|
|
108
|
+
...input,
|
|
109
|
+
archived_at: input.archived_at ?? Date.now(),
|
|
110
|
+
};
|
|
111
|
+
await this.store.setJSON(this.archiveKey(record.session_id), record);
|
|
112
|
+
await this.appendLifecycleEntry(this.archiveKey(record.session_id), {
|
|
113
|
+
op: "chat_archive",
|
|
114
|
+
session_id: record.session_id,
|
|
115
|
+
tab_id: record.tab_id,
|
|
116
|
+
label: record.label,
|
|
117
|
+
});
|
|
118
|
+
await this.addToIndex(ARCHIVE_INDEX_KEY, record.session_id);
|
|
119
|
+
return record;
|
|
120
|
+
}
|
|
121
|
+
async listArchivedChats() {
|
|
122
|
+
const index = await this.store.getJSON(ARCHIVE_INDEX_KEY) ?? [];
|
|
123
|
+
const archives = [];
|
|
124
|
+
for (const sessionId of index) {
|
|
125
|
+
const record = await this.store.getJSON(this.archiveKey(sessionId));
|
|
126
|
+
if (record)
|
|
127
|
+
archives.push(record);
|
|
128
|
+
}
|
|
129
|
+
return archives.sort((left, right) => right.archived_at - left.archived_at);
|
|
130
|
+
}
|
|
131
|
+
async addToIndex(indexKey, id) {
|
|
132
|
+
const index = await this.store.getJSON(indexKey) ?? [];
|
|
133
|
+
if (!index.includes(id)) {
|
|
134
|
+
index.push(id);
|
|
135
|
+
await this.store.setJSON(indexKey, index);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async appendLifecycleEntry(key, payload) {
|
|
139
|
+
await this.store.appendEntry({
|
|
140
|
+
kind: EntityKind.SessionEvent,
|
|
141
|
+
content_source: ContentSource.Runtime,
|
|
142
|
+
key,
|
|
143
|
+
payload: {
|
|
144
|
+
...payload,
|
|
145
|
+
id: randomUUID(),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function withLocalModelRuntimeRepository(workspaceRoot, fn) {
|
|
151
|
+
const storePath = getWorkspaceStorePath(workspaceRoot);
|
|
152
|
+
return withStoreWriteQueue(storePath, async () => {
|
|
153
|
+
const store = await openStore(storePath);
|
|
154
|
+
try {
|
|
155
|
+
const repo = new LocalModelRuntimeRepository(store);
|
|
156
|
+
const result = await fn(repo);
|
|
157
|
+
await store.commit();
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
await store.close();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=local-model-runtime-repository.js.map
|