@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.
Files changed (76) hide show
  1. package/README.md +69 -29
  2. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  3. package/assets/agent-state/STATUS.md +2 -2
  4. package/assets/scripts/ace-hook-dispatch.mjs +1 -1
  5. package/dist/ace-autonomy.js +38 -1
  6. package/dist/ace-context.js +8 -0
  7. package/dist/ace-server-instructions.js +55 -19
  8. package/dist/ace-state-resolver.d.ts +18 -0
  9. package/dist/ace-state-resolver.js +106 -0
  10. package/dist/cli.js +74 -7
  11. package/dist/handoff-registry.js +11 -7
  12. package/dist/helpers.js +75 -9
  13. package/dist/job-scheduler.js +94 -44
  14. package/dist/run-ledger.js +3 -4
  15. package/dist/server.d.ts +1 -1
  16. package/dist/server.js +1 -1
  17. package/dist/shared.d.ts +1 -1
  18. package/dist/status-events.js +12 -14
  19. package/dist/store/ace-packed-store.d.ts +65 -26
  20. package/dist/store/ace-packed-store.js +448 -261
  21. package/dist/store/bootstrap-store.d.ts +1 -1
  22. package/dist/store/bootstrap-store.js +24 -13
  23. package/dist/store/catalog-builder.js +3 -3
  24. package/dist/store/importer.d.ts +2 -2
  25. package/dist/store/importer.js +2 -2
  26. package/dist/store/materializers/context-snapshot-materializer.d.ts +10 -0
  27. package/dist/store/materializers/context-snapshot-materializer.js +51 -0
  28. package/dist/store/materializers/hook-context-materializer.d.ts +1 -1
  29. package/dist/store/materializers/hook-context-materializer.js +1 -1
  30. package/dist/store/materializers/host-file-materializer.d.ts +6 -0
  31. package/dist/store/materializers/host-file-materializer.js +14 -1
  32. package/dist/store/materializers/projection-manager.d.ts +14 -0
  33. package/dist/store/materializers/projection-manager.js +73 -0
  34. package/dist/store/materializers/scheduler-projection-materializer.d.ts +16 -0
  35. package/dist/store/materializers/scheduler-projection-materializer.js +48 -0
  36. package/dist/store/repositories/context-snapshot-repository.d.ts +46 -0
  37. package/dist/store/repositories/context-snapshot-repository.js +105 -0
  38. package/dist/store/repositories/local-model-runtime-repository.d.ts +98 -0
  39. package/dist/store/repositories/local-model-runtime-repository.js +165 -0
  40. package/dist/store/repositories/scheduler-repository.d.ts +21 -39
  41. package/dist/store/repositories/scheduler-repository.js +123 -93
  42. package/dist/store/repositories/todo-repository.d.ts +4 -0
  43. package/dist/store/repositories/todo-repository.js +50 -0
  44. package/dist/store/skills-install.d.ts +1 -1
  45. package/dist/store/skills-install.js +3 -3
  46. package/dist/store/state-reader.d.ts +8 -1
  47. package/dist/store/state-reader.js +19 -13
  48. package/dist/store/store-artifacts.js +105 -41
  49. package/dist/store/store-authority-audit.d.ts +30 -0
  50. package/dist/store/store-authority-audit.js +448 -0
  51. package/dist/store/store-snapshot.js +3 -3
  52. package/dist/store/types.d.ts +6 -2
  53. package/dist/store/types.js +5 -2
  54. package/dist/todo-state.js +179 -11
  55. package/dist/tools-files.js +2 -1
  56. package/dist/tools-framework.js +62 -2
  57. package/dist/tools-memory.js +69 -34
  58. package/dist/tools-todo.js +1 -1
  59. package/dist/tui/agent-worker.d.ts +1 -1
  60. package/dist/tui/agent-worker.js +5 -3
  61. package/dist/tui/chat.d.ts +19 -0
  62. package/dist/tui/chat.js +275 -9
  63. package/dist/tui/commands.d.ts +2 -0
  64. package/dist/tui/commands.js +62 -0
  65. package/dist/tui/dashboard.d.ts +6 -1
  66. package/dist/tui/dashboard.js +44 -3
  67. package/dist/tui/index.d.ts +5 -0
  68. package/dist/tui/index.js +154 -0
  69. package/dist/tui/input.js +5 -0
  70. package/dist/tui/layout.d.ts +24 -0
  71. package/dist/tui/layout.js +76 -2
  72. package/dist/tui/local-model-contract.d.ts +50 -0
  73. package/dist/tui/local-model-contract.js +272 -0
  74. package/dist/vericify-bridge.js +3 -4
  75. package/dist/vericify-context.js +18 -6
  76. package/package.json +4 -6
@@ -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
@@ -3,48 +3,30 @@
3
3
  * Replaces: job-queue.json, job-locks.json, scheduler-lease.json
4
4
  */
5
5
  import { AcePackedStore } from "../ace-packed-store.js";
6
- export interface Job {
7
- id: string;
8
- role: string;
9
- task: string;
10
- priority: "high" | "medium" | "low";
11
- status: "queued" | "running" | "done" | "failed" | "cancelled";
12
- depends_on?: string[];
13
- created_at: number;
14
- started_at?: number;
15
- completed_at?: number;
16
- result?: unknown;
17
- metadata?: Record<string, unknown>;
18
- }
19
- export interface ResourceLock {
20
- resource: string;
21
- holder: string;
22
- acquired_at: number;
23
- expires_at?: number;
24
- }
25
- export interface SchedulerLease {
26
- holder: string;
27
- acquired_at: number;
28
- expires_at: number;
29
- run_id?: string;
30
- }
6
+ import type { JobLockRecord, JobLockTableFile, JobQueueFile, JobRecord, SchedulerLeaseFile } from "../../job-scheduler.js";
31
7
  export declare class SchedulerRepository {
32
8
  private store;
33
9
  constructor(store: AcePackedStore);
34
- enqueue(job: Omit<Job, "created_at" | "status">): Promise<Job>;
10
+ readQueue(): Promise<JobQueueFile>;
11
+ readLockTable(): Promise<JobLockTableFile>;
12
+ readLease(): Promise<SchedulerLeaseFile | undefined>;
13
+ writeQueue(queue: JobQueueFile): Promise<JobQueueFile>;
14
+ writeLockTable(table: JobLockTableFile): Promise<JobLockTableFile>;
15
+ writeLease(lease: SchedulerLeaseFile | undefined): Promise<SchedulerLeaseFile | undefined>;
16
+ replaceState(input: {
17
+ queue: JobQueueFile;
18
+ locks: JobLockTableFile;
19
+ lease?: SchedulerLeaseFile;
20
+ }): Promise<{
21
+ queue: JobQueueFile;
22
+ locks: JobLockTableFile;
23
+ lease?: SchedulerLeaseFile;
24
+ }>;
35
25
  listJobs(filter?: {
36
- status?: Job["status"];
37
- role?: string;
38
- }): Promise<Job[]>;
39
- updateJob(id: string, patch: Partial<Job>): Promise<Job>;
40
- completeJob(id: string, result?: unknown): Promise<Job>;
41
- failJob(id: string, reason?: string): Promise<Job>;
42
- dispatchReady(): Promise<Job[]>;
43
- acquireLock(resource: string, holder: string, ttlMs?: number): Promise<ResourceLock>;
44
- releaseLock(resource: string, holder: string): Promise<boolean>;
45
- getLocks(): Promise<ResourceLock[]>;
46
- acquireLease(holder: string, ttlMs?: number, runId?: string): Promise<SchedulerLease>;
47
- releaseLease(holder: string): Promise<boolean>;
48
- getLease(): Promise<SchedulerLease | undefined>;
26
+ status?: JobRecord["status"];
27
+ }): Promise<JobRecord[]>;
28
+ listLocks(filter?: {
29
+ status?: JobLockRecord["status"];
30
+ }): Promise<JobLockRecord[]>;
49
31
  }
50
32
  //# sourceMappingURL=scheduler-repository.d.ts.map
@@ -6,118 +6,148 @@ import { ContentSource, EntityKind } from "../types.js";
6
6
  const QUEUE_KEY = "state/scheduler/queue";
7
7
  const LOCKS_KEY = "state/scheduler/locks";
8
8
  const LEASE_KEY = "state/scheduler/lease";
9
+ function nowIso() {
10
+ return new Date().toISOString();
11
+ }
12
+ function defaultQueueFile() {
13
+ return {
14
+ version: 1,
15
+ updated_at: nowIso(),
16
+ jobs: [],
17
+ };
18
+ }
19
+ function defaultLockTableFile() {
20
+ return {
21
+ version: 1,
22
+ updated_at: nowIso(),
23
+ locks: [],
24
+ };
25
+ }
26
+ function normalizeQueue(queue) {
27
+ if (!queue || queue.version !== 1 || !Array.isArray(queue.jobs)) {
28
+ return defaultQueueFile();
29
+ }
30
+ return {
31
+ version: 1,
32
+ updated_at: typeof queue.updated_at === "string" ? queue.updated_at : nowIso(),
33
+ jobs: Array.isArray(queue.jobs) ? queue.jobs : [],
34
+ };
35
+ }
36
+ function normalizeLockTable(table) {
37
+ if (!table || table.version !== 1 || !Array.isArray(table.locks)) {
38
+ return defaultLockTableFile();
39
+ }
40
+ return {
41
+ version: 1,
42
+ updated_at: typeof table.updated_at === "string" ? table.updated_at : nowIso(),
43
+ locks: Array.isArray(table.locks) ? table.locks : [],
44
+ };
45
+ }
46
+ function normalizeLease(lease) {
47
+ if (!lease || lease.version !== 1)
48
+ return undefined;
49
+ return lease;
50
+ }
9
51
  export class SchedulerRepository {
10
52
  store;
11
53
  constructor(store) {
12
54
  this.store = store;
13
55
  }
14
- // ── Jobs ──────────────────────────────────────────────────────────────────
15
- async enqueue(job) {
16
- const record = { ...job, status: "queued", created_at: Date.now() };
17
- const queue = await this.store.getJSON(QUEUE_KEY) ?? [];
18
- queue.push(record);
19
- await this.store.setJSON(QUEUE_KEY, queue);
56
+ async readQueue() {
57
+ return normalizeQueue(await this.store.getJSON(QUEUE_KEY));
58
+ }
59
+ async readLockTable() {
60
+ return normalizeLockTable(await this.store.getJSON(LOCKS_KEY));
61
+ }
62
+ async readLease() {
63
+ const stored = await this.store.getJSON(LEASE_KEY);
64
+ return normalizeLease(stored);
65
+ }
66
+ async writeQueue(queue) {
67
+ const record = {
68
+ ...normalizeQueue(queue),
69
+ updated_at: nowIso(),
70
+ };
71
+ await this.store.setJSON(QUEUE_KEY, record);
20
72
  await this.store.appendEntry({
21
73
  kind: EntityKind.SchedulerTick,
22
74
  content_source: ContentSource.Runtime,
23
75
  key: QUEUE_KEY,
24
- payload: { op: "enqueue", job_id: job.id },
76
+ payload: {
77
+ op: "queue_write",
78
+ job_count: record.jobs.length,
79
+ },
25
80
  });
26
81
  return record;
27
82
  }
28
- async listJobs(filter) {
29
- const queue = await this.store.getJSON(QUEUE_KEY) ?? [];
30
- return queue.filter((j) => {
31
- if (filter?.status && j.status !== filter.status)
32
- return false;
33
- if (filter?.role && j.role !== filter.role)
34
- return false;
35
- return true;
83
+ async writeLockTable(table) {
84
+ const record = {
85
+ ...normalizeLockTable(table),
86
+ updated_at: nowIso(),
87
+ };
88
+ await this.store.setJSON(LOCKS_KEY, record);
89
+ await this.store.appendEntry({
90
+ kind: EntityKind.SchedulerTick,
91
+ content_source: ContentSource.Runtime,
92
+ key: LOCKS_KEY,
93
+ payload: {
94
+ op: "lock_table_write",
95
+ lock_count: record.locks.length,
96
+ },
36
97
  });
98
+ return record;
37
99
  }
38
- async updateJob(id, patch) {
39
- const queue = await this.store.getJSON(QUEUE_KEY) ?? [];
40
- const idx = queue.findIndex((j) => j.id === id);
41
- if (idx === -1)
42
- throw new Error(`SchedulerRepository: job not found: ${id}`);
43
- queue[idx] = { ...queue[idx], ...patch };
44
- await this.store.setJSON(QUEUE_KEY, queue);
45
- return queue[idx];
100
+ async writeLease(lease) {
101
+ if (!lease) {
102
+ await this.store.setJSON(LEASE_KEY, null);
103
+ await this.store.appendEntry({
104
+ kind: EntityKind.SchedulerTick,
105
+ content_source: ContentSource.Runtime,
106
+ key: LEASE_KEY,
107
+ payload: {
108
+ op: "lease_clear",
109
+ },
110
+ });
111
+ return undefined;
112
+ }
113
+ const record = {
114
+ ...lease,
115
+ version: 1,
116
+ };
117
+ await this.store.setJSON(LEASE_KEY, record);
118
+ await this.store.appendEntry({
119
+ kind: EntityKind.SchedulerTick,
120
+ content_source: ContentSource.Runtime,
121
+ key: LEASE_KEY,
122
+ payload: {
123
+ op: "lease_write",
124
+ owner: record.owner,
125
+ lease_id: record.lease_id,
126
+ },
127
+ });
128
+ return record;
46
129
  }
47
- async completeJob(id, result) {
48
- return this.updateJob(id, { status: "done", completed_at: Date.now(), result });
130
+ async replaceState(input) {
131
+ const queue = await this.writeQueue(input.queue);
132
+ const locks = await this.writeLockTable(input.locks);
133
+ const lease = await this.writeLease(input.lease);
134
+ return { queue, locks, lease };
49
135
  }
50
- async failJob(id, reason) {
51
- return this.updateJob(id, {
52
- status: "failed",
53
- completed_at: Date.now(),
54
- metadata: { failure_reason: reason },
136
+ async listJobs(filter) {
137
+ const queue = await this.readQueue();
138
+ return queue.jobs.filter((job) => {
139
+ if (filter?.status && job.status !== filter.status)
140
+ return false;
141
+ return true;
55
142
  });
56
143
  }
57
- async dispatchReady() {
58
- const queue = await this.listJobs({ status: "queued" });
59
- const done = new Set((await this.listJobs({ status: "done" })).map((j) => j.id));
60
- return queue.filter((j) => {
61
- if (!j.depends_on?.length)
62
- return true;
63
- return j.depends_on.every((dep) => done.has(dep));
144
+ async listLocks(filter) {
145
+ const table = await this.readLockTable();
146
+ return table.locks.filter((lock) => {
147
+ if (filter?.status && lock.status !== filter.status)
148
+ return false;
149
+ return true;
64
150
  });
65
151
  }
66
- // ── Resource locks ────────────────────────────────────────────────────────
67
- async acquireLock(resource, holder, ttlMs) {
68
- const locks = await this.store.getJSON(LOCKS_KEY) ?? [];
69
- const existing = locks.find((l) => l.resource === resource);
70
- const now = Date.now();
71
- if (existing) {
72
- if (!existing.expires_at || existing.expires_at > now) {
73
- throw new Error(`SchedulerRepository: resource locked by ${existing.holder}: ${resource}`);
74
- }
75
- // Expired lock — remove it
76
- const next = locks.filter((l) => l.resource !== resource);
77
- next.push({ resource, holder, acquired_at: now, expires_at: ttlMs ? now + ttlMs : undefined });
78
- await this.store.setJSON(LOCKS_KEY, next);
79
- return next.find((l) => l.resource === resource);
80
- }
81
- locks.push({ resource, holder, acquired_at: now, expires_at: ttlMs ? now + ttlMs : undefined });
82
- await this.store.setJSON(LOCKS_KEY, locks);
83
- return locks.find((l) => l.resource === resource);
84
- }
85
- async releaseLock(resource, holder) {
86
- const locks = await this.store.getJSON(LOCKS_KEY) ?? [];
87
- const next = locks.filter((l) => !(l.resource === resource && l.holder === holder));
88
- if (next.length === locks.length)
89
- return false;
90
- await this.store.setJSON(LOCKS_KEY, next);
91
- return true;
92
- }
93
- async getLocks() {
94
- return (await this.store.getJSON(LOCKS_KEY)) ?? [];
95
- }
96
- // ── Scheduler lease ───────────────────────────────────────────────────────
97
- async acquireLease(holder, ttlMs = 60_000, runId) {
98
- const existing = await this.getLease();
99
- const now = Date.now();
100
- if (existing && existing.expires_at > now) {
101
- throw new Error(`SchedulerRepository: lease held by ${existing.holder}`);
102
- }
103
- const lease = {
104
- holder,
105
- acquired_at: now,
106
- expires_at: now + ttlMs,
107
- run_id: runId,
108
- };
109
- await this.store.setJSON(LEASE_KEY, lease);
110
- return lease;
111
- }
112
- async releaseLease(holder) {
113
- const lease = await this.getLease();
114
- if (!lease || lease.holder !== holder)
115
- return false;
116
- await this.store.delete(LEASE_KEY);
117
- return true;
118
- }
119
- async getLease() {
120
- return this.store.getJSON(LEASE_KEY);
121
- }
122
152
  }
123
153
  //# sourceMappingURL=scheduler-repository.js.map
@@ -14,6 +14,7 @@ export interface TodoNode {
14
14
  updated_at: number;
15
15
  notes?: string;
16
16
  }
17
+ type TodoUpsertInput = Omit<TodoNode, "created_at" | "updated_at"> & Partial<Pick<TodoNode, "created_at">>;
17
18
  export declare class TodoRepository {
18
19
  private store;
19
20
  constructor(store: AcePackedStore);
@@ -26,6 +27,9 @@ export declare class TodoRepository {
26
27
  assigned_to?: string;
27
28
  }): Promise<TodoNode[]>;
28
29
  getAll(): Promise<TodoNode[]>;
30
+ remove(id: string): Promise<boolean>;
31
+ replaceAll(todos: TodoUpsertInput[]): Promise<TodoNode[]>;
29
32
  private _updateIndex;
30
33
  }
34
+ export {};
31
35
  //# sourceMappingURL=todo-repository.d.ts.map
@@ -61,6 +61,56 @@ export class TodoRepository {
61
61
  async getAll() {
62
62
  return this.list();
63
63
  }
64
+ async remove(id) {
65
+ const existing = await this.get(id);
66
+ if (!existing)
67
+ return false;
68
+ await this.store.delete(this.key(id));
69
+ await this.store.appendEntry({
70
+ kind: EntityKind.Todo,
71
+ content_source: ContentSource.Runtime,
72
+ key: this.key(id),
73
+ payload: { op: "remove", id },
74
+ });
75
+ await this._updateIndex(existing, "remove");
76
+ return true;
77
+ }
78
+ async replaceAll(todos) {
79
+ const existing = new Map((await this.getAll()).map((todo) => [todo.id, todo]));
80
+ const nextRecords = [];
81
+ const nextIds = [];
82
+ for (const todo of todos) {
83
+ const now = Date.now();
84
+ const prior = existing.get(todo.id);
85
+ const record = {
86
+ ...prior,
87
+ ...todo,
88
+ created_at: todo.created_at ?? prior?.created_at ?? now,
89
+ updated_at: now,
90
+ };
91
+ await this.store.setJSON(this.key(todo.id), record);
92
+ await this.store.appendEntry({
93
+ kind: EntityKind.Todo,
94
+ content_source: ContentSource.Runtime,
95
+ key: this.key(todo.id),
96
+ payload: record,
97
+ });
98
+ nextIds.push(todo.id);
99
+ nextRecords.push(record);
100
+ existing.delete(todo.id);
101
+ }
102
+ for (const staleId of existing.keys()) {
103
+ await this.store.delete(this.key(staleId));
104
+ await this.store.appendEntry({
105
+ kind: EntityKind.Todo,
106
+ content_source: ContentSource.Runtime,
107
+ key: this.key(staleId),
108
+ payload: { op: "remove", id: staleId },
109
+ });
110
+ }
111
+ await this.store.setJSON(TODO_INDEX_KEY, nextIds);
112
+ return nextRecords;
113
+ }
64
114
  async _updateIndex(todo, op) {
65
115
  const index = await this.store.getJSON(TODO_INDEX_KEY) ?? [];
66
116
  if (op === "add" && !index.includes(todo.id)) {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * `npx @voybio/ace-swarm add skills [bundle]`
5
5
  *
6
- * Installs skill packs into ace-state.zarr on demand.
6
+ * Installs skill packs into ace-state.ace on demand.
7
7
  * Skills are also baked at ace init; this command is retained for repair/reinstall.
8
8
  *
9
9
  * What install does: