@voybio/ace-swarm 0.2.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 (64) hide show
  1. package/README.md +15 -15
  2. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  3. package/assets/agent-state/STATUS.md +2 -2
  4. package/dist/ace-autonomy.js +38 -1
  5. package/dist/ace-context.js +8 -0
  6. package/dist/ace-server-instructions.js +55 -19
  7. package/dist/ace-state-resolver.d.ts +18 -0
  8. package/dist/ace-state-resolver.js +106 -0
  9. package/dist/cli.js +67 -0
  10. package/dist/handoff-registry.js +11 -7
  11. package/dist/helpers.js +74 -8
  12. package/dist/job-scheduler.js +94 -44
  13. package/dist/run-ledger.js +3 -4
  14. package/dist/server.d.ts +1 -1
  15. package/dist/server.js +1 -1
  16. package/dist/shared.d.ts +1 -1
  17. package/dist/status-events.js +12 -14
  18. package/dist/store/bootstrap-store.js +20 -9
  19. package/dist/store/materializers/context-snapshot-materializer.d.ts +10 -0
  20. package/dist/store/materializers/context-snapshot-materializer.js +51 -0
  21. package/dist/store/materializers/host-file-materializer.d.ts +6 -0
  22. package/dist/store/materializers/host-file-materializer.js +13 -0
  23. package/dist/store/materializers/projection-manager.d.ts +14 -0
  24. package/dist/store/materializers/projection-manager.js +73 -0
  25. package/dist/store/materializers/scheduler-projection-materializer.d.ts +16 -0
  26. package/dist/store/materializers/scheduler-projection-materializer.js +48 -0
  27. package/dist/store/repositories/context-snapshot-repository.d.ts +46 -0
  28. package/dist/store/repositories/context-snapshot-repository.js +105 -0
  29. package/dist/store/repositories/local-model-runtime-repository.d.ts +98 -0
  30. package/dist/store/repositories/local-model-runtime-repository.js +165 -0
  31. package/dist/store/repositories/scheduler-repository.d.ts +21 -39
  32. package/dist/store/repositories/scheduler-repository.js +123 -93
  33. package/dist/store/repositories/todo-repository.d.ts +4 -0
  34. package/dist/store/repositories/todo-repository.js +50 -0
  35. package/dist/store/state-reader.d.ts +8 -1
  36. package/dist/store/state-reader.js +12 -1
  37. package/dist/store/store-artifacts.js +31 -5
  38. package/dist/store/store-authority-audit.d.ts +30 -0
  39. package/dist/store/store-authority-audit.js +448 -0
  40. package/dist/store/types.d.ts +2 -0
  41. package/dist/store/types.js +1 -0
  42. package/dist/todo-state.js +179 -11
  43. package/dist/tools-files.js +2 -1
  44. package/dist/tools-framework.js +60 -0
  45. package/dist/tools-memory.js +69 -34
  46. package/dist/tools-todo.js +1 -1
  47. package/dist/tui/agent-worker.d.ts +1 -1
  48. package/dist/tui/agent-worker.js +5 -3
  49. package/dist/tui/chat.d.ts +19 -0
  50. package/dist/tui/chat.js +275 -9
  51. package/dist/tui/commands.d.ts +2 -0
  52. package/dist/tui/commands.js +62 -0
  53. package/dist/tui/dashboard.d.ts +5 -0
  54. package/dist/tui/dashboard.js +38 -2
  55. package/dist/tui/index.d.ts +5 -0
  56. package/dist/tui/index.js +146 -2
  57. package/dist/tui/input.js +5 -0
  58. package/dist/tui/layout.d.ts +24 -0
  59. package/dist/tui/layout.js +76 -2
  60. package/dist/tui/local-model-contract.d.ts +50 -0
  61. package/dist/tui/local-model-contract.js +272 -0
  62. package/dist/vericify-bridge.js +3 -4
  63. package/dist/vericify-context.js +18 -6
  64. package/package.json +1 -1
package/dist/helpers.js CHANGED
@@ -3,12 +3,12 @@
3
3
  */
4
4
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
5
5
  import { homedir } from "node:os";
6
- import { dirname, isAbsolute, relative, resolve } from "node:path";
6
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { buildHostInstructionText } from "./ace-server-instructions.js";
9
9
  import { isInside, normalizeRelPath } from "./shared.js";
10
10
  import { getWorkspaceStorePath, listStoreKeysSync, parseVirtualStorePath, readStoreBlobSync, readVirtualStorePathSync, toVirtualStorePath, } from "./store/store-snapshot.js";
11
- import { isOperationalArtifactPath, operationalArtifactKey, writeOperationalArtifactSync, } from "./store/store-artifacts.js";
11
+ import { isOperationalArtifactPath, operationalArtifactKey, writeStoreBlobSync, writeOperationalArtifactSync, } from "./store/store-artifacts.js";
12
12
  import { buildOpenAiCompatibleBaseUrl, DEFAULT_LLAMA_CPP_MODEL, DEFAULT_OLLAMA_MODEL, normalizeLocalBaseUrl, } from "./tui/provider-discovery.js";
13
13
  export { isInside, isReadError, normalizeRelPath, looksLikeSwarmHandoffPath } from "./shared.js";
14
14
  const __filename = fileURLToPath(import.meta.url);
@@ -24,9 +24,49 @@ function normalizeWorkspaceRootCandidate(candidate) {
24
24
  function isFilesystemRoot(path) {
25
25
  return dirname(path) === path;
26
26
  }
27
+ function normalizeWorkspaceRootLikePath(candidate) {
28
+ const resolvedCandidate = resolve(candidate);
29
+ if (resolvedCandidate.endsWith(join(".agents", "ACE", "ace-state.ace"))) {
30
+ return dirname(dirname(dirname(resolvedCandidate)));
31
+ }
32
+ if (resolvedCandidate.endsWith(join(".agents", "ACE"))) {
33
+ return dirname(dirname(resolvedCandidate));
34
+ }
35
+ if (resolvedCandidate.endsWith(join(".agents"))) {
36
+ return dirname(resolvedCandidate);
37
+ }
38
+ return resolvedCandidate;
39
+ }
40
+ function hasInitiatedAceWorkspace(root) {
41
+ return (existsSync(resolve(root, ".agents", "ACE", "ace-state.ace")) ||
42
+ existsSync(resolve(root, ".agents", "ACE")) ||
43
+ existsSync(resolve(root, "agent-state", "TASK.md")) ||
44
+ existsSync(resolve(root, "agent-state", "STATUS.md")));
45
+ }
46
+ function findNearestInitiatedWorkspaceRoot(candidate) {
47
+ let cursor = normalizeWorkspaceRootLikePath(candidate);
48
+ while (true) {
49
+ if (existsSync(cursor) && hasInitiatedAceWorkspace(cursor)) {
50
+ return cursor;
51
+ }
52
+ const parent = dirname(cursor);
53
+ if (parent === cursor)
54
+ break;
55
+ cursor = parent;
56
+ }
57
+ return undefined;
58
+ }
59
+ function pinWorkspaceRoot(root) {
60
+ const normalized = normalizeWorkspaceRootLikePath(root);
61
+ process.env.ACE_WORKSPACE_ROOT = normalized;
62
+ return normalized;
63
+ }
27
64
  export function resolveWorkspaceRoot() {
65
+ const explicit = normalizeWorkspaceRootCandidate(process.env.ACE_WORKSPACE_ROOT);
66
+ if (explicit)
67
+ return pinWorkspaceRoot(findNearestInitiatedWorkspaceRoot(explicit) ?? explicit);
28
68
  const ordered = [
29
- process.env.ACE_WORKSPACE_ROOT,
69
+ process.env.ACE_TUI_WORKSPACE_ROOT,
30
70
  process.env.INIT_CWD,
31
71
  process.env.PWD,
32
72
  process.cwd(),
@@ -34,13 +74,18 @@ export function resolveWorkspaceRoot() {
34
74
  .map(normalizeWorkspaceRootCandidate)
35
75
  .filter((candidate) => Boolean(candidate));
36
76
  const candidates = [...new Set(ordered)];
77
+ for (const candidate of candidates) {
78
+ const initiated = findNearestInitiatedWorkspaceRoot(candidate);
79
+ if (initiated)
80
+ return pinWorkspaceRoot(initiated);
81
+ }
37
82
  const firstExistingNonRoot = candidates.find((candidate) => existsSync(candidate) && !isFilesystemRoot(candidate));
38
83
  if (firstExistingNonRoot)
39
- return firstExistingNonRoot;
84
+ return pinWorkspaceRoot(firstExistingNonRoot);
40
85
  const firstExisting = candidates.find((candidate) => existsSync(candidate));
41
86
  if (firstExisting)
42
- return firstExisting;
43
- return candidates[0] ?? resolve(process.cwd());
87
+ return pinWorkspaceRoot(firstExisting);
88
+ return pinWorkspaceRoot(candidates[0] ?? resolve(process.cwd()));
44
89
  }
45
90
  export const WORKSPACE_ROOT = resolveWorkspaceRoot();
46
91
  function currentWorkspaceRoot() {
@@ -367,6 +412,9 @@ export function fileExists(filePath) {
367
412
  }
368
413
  export function resolveWorkspaceWritePath(filePath) {
369
414
  const root = currentWorkspaceRoot();
415
+ if (isFilesystemRoot(root)) {
416
+ throw new Error("Workspace root resolved to filesystem root. Set ACE_WORKSPACE_ROOT or run ACE from the target workspace.");
417
+ }
370
418
  const abs = isAbsolute(filePath)
371
419
  ? filePath
372
420
  : resolve(root, mapAceWorkspaceRelativePath(filePath));
@@ -477,10 +525,23 @@ function resolveStoreFallbackKeys(filePath) {
477
525
  const statePrefix = `${ACE_ROOT_REL}/agent-state/`;
478
526
  if (canonical.startsWith(statePrefix)) {
479
527
  const rel = canonical.slice(statePrefix.length);
528
+ const runtimeProjectionKey = {
529
+ "job-queue.json": "state/artifacts/agent-state/job-queue.json",
530
+ "job-locks.json": "state/artifacts/agent-state/job-locks.json",
531
+ "scheduler-lease.json": "state/artifacts/agent-state/scheduler-lease.json",
532
+ }[rel];
533
+ if (runtimeProjectionKey)
534
+ keys.add(runtimeProjectionKey);
480
535
  if (staticAgentState.has(rel)) {
481
536
  keys.add(`knowledge/agent-state/${rel}`);
482
537
  }
483
538
  }
539
+ const contextSnapshotPrefix = `${ACE_ROOT_REL}/agent-state/context-snapshots/`;
540
+ if (canonical.startsWith(contextSnapshotPrefix)) {
541
+ const rel = canonical.slice(contextSnapshotPrefix.length);
542
+ if (rel)
543
+ keys.add(`state/memory/context_snapshots/${rel}`);
544
+ }
484
545
  const tasksPrefix = `${ACE_TASKS_ROOT_REL}/`;
485
546
  if (canonical.startsWith(tasksPrefix)) {
486
547
  keys.add(`knowledge/tasks/${canonical.slice(tasksPrefix.length)}`);
@@ -562,12 +623,17 @@ export function safeRead(filePath) {
562
623
  export function safeWrite(filePath, content) {
563
624
  const normalized = normalizeRelPath(filePath);
564
625
  const canonical = mapAceWorkspaceRelativePath(normalized);
626
+ const root = currentWorkspaceRoot();
565
627
  if (canonical.startsWith(`${ACE_ROOT_REL}/agent-state/`)) {
566
628
  const rel = canonical.slice(`${ACE_ROOT_REL}/`.length);
567
- if (isOperationalArtifactPath(rel) && existsSync(getWorkspaceStorePath(currentWorkspaceRoot()))) {
568
- return writeOperationalArtifactSync(currentWorkspaceRoot(), rel, content);
629
+ if (isOperationalArtifactPath(rel) && existsSync(getWorkspaceStorePath(root))) {
630
+ return writeOperationalArtifactSync(root, rel, content);
569
631
  }
570
632
  }
633
+ const storeKeys = resolveStoreFallbackKeys(filePath);
634
+ if (storeKeys.length > 0 && existsSync(getWorkspaceStorePath(root))) {
635
+ writeStoreBlobSync(root, storeKeys[0], content);
636
+ }
571
637
  const abs = resolveWorkspaceWritePath(filePath);
572
638
  mkdirSync(dirname(abs), { recursive: true });
573
639
  const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
@@ -1,10 +1,15 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { readHandoffRegistry } from "./handoff-registry.js";
3
- import { safeRead, safeWrite, withFileLock, wsPath } from "./helpers.js";
3
+ import { resolveWorkspaceRoot, safeRead, safeWrite, withFileLock, wsPath } from "./helpers.js";
4
4
  import { isReadError } from "./shared.js";
5
5
  import { appendStatusEventSafe, readStatusEvents } from "./status-events.js";
6
6
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
7
7
  import { readTodoState } from "./todo-state.js";
8
+ import { openStore } from "./store/ace-packed-store.js";
9
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
10
+ import { SchedulerRepository } from "./store/repositories/scheduler-repository.js";
11
+ import { getWorkspaceStorePath, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
12
+ import { withStoreWriteQueue } from "./store/write-queue.js";
8
13
  // ── Buffered transition-event infrastructure ────────────────────────
9
14
  // Events are collected during the scheduler lock and flushed after
10
15
  // the lock is released. Emission is failure-tolerant: a broken
@@ -270,33 +275,63 @@ export function getSchedulerLeasePath() {
270
275
  return wsPath(SCHEDULER_LEASE_REL);
271
276
  }
272
277
  export function readJobQueue() {
278
+ const root = resolveWorkspaceRoot();
279
+ if (storeExistsSync(root)) {
280
+ const stored = readStoreJsonSync(root, "state/scheduler/queue");
281
+ if (stored)
282
+ return stored;
283
+ }
273
284
  const raw = safeRead(JOB_QUEUE_REL);
274
285
  if (isReadError(raw))
275
286
  return defaultQueueFile();
276
287
  return parseQueueFile(raw) ?? defaultQueueFile();
277
288
  }
278
289
  export function readJobLockTable() {
290
+ const root = resolveWorkspaceRoot();
291
+ if (storeExistsSync(root)) {
292
+ const stored = readStoreJsonSync(root, "state/scheduler/locks");
293
+ if (stored)
294
+ return stored;
295
+ }
279
296
  const raw = safeRead(JOB_LOCK_TABLE_REL);
280
297
  if (isReadError(raw))
281
298
  return defaultJobLockFile();
282
299
  return parseJobLockFile(raw) ?? defaultJobLockFile();
283
300
  }
284
301
  export function readSchedulerLease() {
302
+ const root = resolveWorkspaceRoot();
303
+ if (storeExistsSync(root)) {
304
+ const stored = readStoreJsonSync(root, "state/scheduler/lease");
305
+ if (stored)
306
+ return stored ?? undefined;
307
+ }
285
308
  const raw = safeRead(SCHEDULER_LEASE_REL);
286
309
  if (isReadError(raw))
287
310
  return undefined;
288
311
  return parseLease(raw);
289
312
  }
290
- function writeQueue(queue) {
291
- queue.updated_at = nowIso();
292
- return safeWrite(JOB_QUEUE_REL, JSON.stringify(queue, null, 2));
293
- }
294
- function writeJobLocks(table) {
295
- table.updated_at = nowIso();
296
- return safeWrite(JOB_LOCK_TABLE_REL, JSON.stringify(table, null, 2));
297
- }
298
- function writeLease(lease) {
299
- return safeWrite(SCHEDULER_LEASE_REL, JSON.stringify(lease, null, 2));
313
+ async function persistSchedulerState(input) {
314
+ const root = resolveWorkspaceRoot();
315
+ if (!storeExistsSync(root)) {
316
+ safeWrite(JOB_QUEUE_REL, JSON.stringify({ ...input.queue, updated_at: nowIso() }, null, 2));
317
+ safeWrite(JOB_LOCK_TABLE_REL, JSON.stringify({ ...input.locks, updated_at: nowIso() }, null, 2));
318
+ safeWrite(SCHEDULER_LEASE_REL, JSON.stringify(input.lease ?? null, null, 2));
319
+ return;
320
+ }
321
+ const storePath = getWorkspaceStorePath(root);
322
+ await withStoreWriteQueue(storePath, async () => {
323
+ const store = await openStore(storePath);
324
+ try {
325
+ const repo = new SchedulerRepository(store);
326
+ await repo.replaceState(input);
327
+ await store.commit();
328
+ const projections = new ProjectionManager(store, root);
329
+ await projections.projectAfterCommit(["scheduler"]);
330
+ }
331
+ finally {
332
+ await store.close();
333
+ }
334
+ });
300
335
  }
301
336
  function compareJobs(a, b) {
302
337
  const priorityCmp = PRIORITY_WEIGHT[a.priority] - PRIORITY_WEIGHT[b.priority];
@@ -480,8 +515,7 @@ function defaultLeaseTtlSeconds() {
480
515
  function hasActiveLockForJob(locks, jobId) {
481
516
  return locks.some((lock) => lock.job_id === jobId && isJobLockActive(lock));
482
517
  }
483
- function acquireLease(owner, ttlSeconds, now) {
484
- const existing = readSchedulerLease();
518
+ function acquireLease(existing, owner, ttlSeconds, now) {
485
519
  const expiry = new Date(now.getTime() + ttlSeconds * 1000);
486
520
  if (!existing) {
487
521
  const lease = {
@@ -492,7 +526,6 @@ function acquireLease(owner, ttlSeconds, now) {
492
526
  heartbeat_at: nowIso(now),
493
527
  expires_at: nowIso(expiry),
494
528
  };
495
- writeLease(lease);
496
529
  return { acquired: true, lease, recovery_mode: false };
497
530
  }
498
531
  const existingExpiry = parseDate(existing.expires_at);
@@ -506,7 +539,6 @@ function acquireLease(owner, ttlSeconds, now) {
506
539
  heartbeat_at: nowIso(now),
507
540
  expires_at: nowIso(expiry),
508
541
  };
509
- writeLease(lease);
510
542
  return { acquired: true, lease, recovery_mode: expired };
511
543
  }
512
544
  return { acquired: false, lease: existing, recovery_mode: false };
@@ -522,7 +554,7 @@ function hasActiveLeaseForOwner(lease, owner, now) {
522
554
  return expires.getTime() > now.getTime();
523
555
  }
524
556
  function ensureLeaseForOwner(owner, now, ttlSeconds = defaultLeaseTtlSeconds()) {
525
- const leaseResult = acquireLease(owner, ttlSeconds, now);
557
+ const leaseResult = acquireLease(readSchedulerLease(), owner, ttlSeconds, now);
526
558
  if (!leaseResult.acquired) {
527
559
  return leaseResult;
528
560
  }
@@ -532,7 +564,6 @@ function ensureLeaseForOwner(owner, now, ttlSeconds = defaultLeaseTtlSeconds())
532
564
  heartbeat_at: nowIso(now),
533
565
  expires_at: nowIso(new Date(now.getTime() + ttlSeconds * 1000)),
534
566
  };
535
- writeLease(refreshedLease);
536
567
  return {
537
568
  ...leaseResult,
538
569
  lease: refreshedLease,
@@ -657,7 +688,7 @@ function reconcileRecoveryState(queue, table, now, recoveryMode, buf) {
657
688
  }
658
689
  export async function enqueueJob(input) {
659
690
  const buf = newEventBuffer();
660
- const result = await withFileLock(SCHEDULER_LOCK_REL, () => {
691
+ const result = await withFileLock(SCHEDULER_LOCK_REL, async () => {
661
692
  if (!input.kind || input.kind.trim().length === 0) {
662
693
  const queue = readJobQueue();
663
694
  return {
@@ -715,7 +746,11 @@ export async function enqueueJob(input) {
715
746
  accepted_at: created,
716
747
  };
717
748
  queue.jobs.push(job);
718
- const path = writeQueue(queue);
749
+ await persistSchedulerState({
750
+ queue,
751
+ locks: readJobLockTable(),
752
+ lease: readSchedulerLease(),
753
+ });
719
754
  buf.status.push({
720
755
  event_type: "SCHEDULER_JOB_ENQUEUED",
721
756
  status: "started",
@@ -741,7 +776,7 @@ export async function enqueueJob(input) {
741
776
  });
742
777
  return {
743
778
  ok: true,
744
- path,
779
+ path: getJobQueuePath(),
745
780
  queue,
746
781
  ack: {
747
782
  job_id: job.job_id,
@@ -754,7 +789,7 @@ export async function enqueueJob(input) {
754
789
  }
755
790
  export async function acknowledgeJob(input) {
756
791
  const buf = newEventBuffer();
757
- const result = await withFileLock(SCHEDULER_LOCK_REL, () => {
792
+ const result = await withFileLock(SCHEDULER_LOCK_REL, async () => {
758
793
  const queue = readJobQueue();
759
794
  const job = queue.jobs.find((row) => row.job_id === input.job_id);
760
795
  if (!job) {
@@ -794,7 +829,11 @@ export async function acknowledgeJob(input) {
794
829
  }
795
830
  }
796
831
  }
797
- const path = writeQueue(queue);
832
+ await persistSchedulerState({
833
+ queue,
834
+ locks: readJobLockTable(),
835
+ lease: readSchedulerLease(),
836
+ });
798
837
  buf.status.push({
799
838
  event_type: "SCHEDULER_JOB_ACK",
800
839
  status: job.status === "canceled" ? "done" : "pass",
@@ -821,7 +860,7 @@ export async function acknowledgeJob(input) {
821
860
  });
822
861
  return {
823
862
  ok: true,
824
- path,
863
+ path: getJobQueuePath(),
825
864
  queue,
826
865
  from,
827
866
  to: job.status,
@@ -832,14 +871,14 @@ export async function acknowledgeJob(input) {
832
871
  }
833
872
  export async function dispatchJobs(input = {}) {
834
873
  const buf = newEventBuffer();
835
- const result = await withFileLock(SCHEDULER_LOCK_REL, () => {
874
+ const result = await withFileLock(SCHEDULER_LOCK_REL, async () => {
836
875
  const owner = deriveOwner(input.owner);
837
876
  const now = parseDate(input.now_iso) ?? new Date();
838
877
  const table = readJobLockTable();
839
878
  const leaseTtl = typeof input.lease_ttl_seconds === "number" && input.lease_ttl_seconds > 0
840
879
  ? Math.floor(input.lease_ttl_seconds)
841
880
  : defaultLeaseTtlSeconds();
842
- const leaseResult = acquireLease(owner, leaseTtl, now);
881
+ const leaseResult = acquireLease(readSchedulerLease(), owner, leaseTtl, now);
843
882
  if (!leaseResult.acquired) {
844
883
  const queue = readJobQueue();
845
884
  return {
@@ -913,9 +952,11 @@ export async function dispatchJobs(input = {}) {
913
952
  heartbeat_at: nowIso(now),
914
953
  expires_at: nowIso(new Date(now.getTime() + leaseTtl * 1000)),
915
954
  };
916
- writeLease(refreshedLease);
917
- const finalQueuePath = writeQueue(queue);
918
- const finalLockPath = writeJobLocks(table);
955
+ await persistSchedulerState({
956
+ queue,
957
+ locks: table,
958
+ lease: refreshedLease,
959
+ });
919
960
  const summary = {
920
961
  total_jobs: queue.jobs.length,
921
962
  blocked_jobs: queue.jobs.filter((job) => job.status === "blocked").length,
@@ -954,8 +995,8 @@ export async function dispatchJobs(input = {}) {
954
995
  owner,
955
996
  lease_acquired: true,
956
997
  lease: refreshedLease,
957
- queue_path: finalQueuePath,
958
- lock_path: finalLockPath,
998
+ queue_path: getJobQueuePath(),
999
+ lock_path: getJobLockTablePath(),
959
1000
  queue,
960
1001
  locks: table,
961
1002
  summary,
@@ -966,7 +1007,7 @@ export async function dispatchJobs(input = {}) {
966
1007
  }
967
1008
  export async function dispatchJobNow(input) {
968
1009
  const buf = newEventBuffer();
969
- const result = await withFileLock(SCHEDULER_LOCK_REL, () => {
1010
+ const result = await withFileLock(SCHEDULER_LOCK_REL, async () => {
970
1011
  const owner = deriveOwner(input.owner);
971
1012
  const now = parseDate(input.now_iso) ?? new Date();
972
1013
  const leaseResult = ensureLeaseForOwner(owner, now);
@@ -1022,12 +1063,15 @@ export async function dispatchJobNow(input) {
1022
1063
  job.started_at ??
1023
1064
  nowIso(now);
1024
1065
  heartbeatRunningLocks(table, leaseResult.lease.lease_id, now);
1025
- const queuePath = writeQueue(queue);
1026
- const lockPath = writeJobLocks(table);
1066
+ await persistSchedulerState({
1067
+ queue,
1068
+ locks: table,
1069
+ lease: leaseResult.lease,
1070
+ });
1027
1071
  return {
1028
1072
  ok: true,
1029
- queue_path: queuePath,
1030
- lock_path: lockPath,
1073
+ queue_path: getJobQueuePath(),
1074
+ lock_path: getJobLockTablePath(),
1031
1075
  queue,
1032
1076
  locks: table,
1033
1077
  started_lock: {
@@ -1069,12 +1113,15 @@ export async function dispatchJobNow(input) {
1069
1113
  occupancy.add(resource);
1070
1114
  }
1071
1115
  const startedLock = startJobNow(job, table, leaseResult.lease, now, buf, "dispatch_job_now");
1072
- const queuePath = writeQueue(queue);
1073
- const lockPath = writeJobLocks(table);
1116
+ await persistSchedulerState({
1117
+ queue,
1118
+ locks: table,
1119
+ lease: leaseResult.lease,
1120
+ });
1074
1121
  return {
1075
1122
  ok: true,
1076
- queue_path: queuePath,
1077
- lock_path: lockPath,
1123
+ queue_path: getJobQueuePath(),
1124
+ lock_path: getJobLockTablePath(),
1078
1125
  queue,
1079
1126
  locks: table,
1080
1127
  started_lock: startedLock,
@@ -1085,7 +1132,7 @@ export async function dispatchJobNow(input) {
1085
1132
  }
1086
1133
  export async function completeJob(input) {
1087
1134
  const buf = newEventBuffer();
1088
- const result = await withFileLock(SCHEDULER_LOCK_REL, () => {
1135
+ const result = await withFileLock(SCHEDULER_LOCK_REL, async () => {
1089
1136
  const owner = deriveOwner(input.owner);
1090
1137
  const now = parseDate(input.now_iso) ?? new Date();
1091
1138
  const leaseResult = ensureLeaseForOwner(owner, now);
@@ -1170,8 +1217,11 @@ export async function completeJob(input) {
1170
1217
  job.started_at = undefined;
1171
1218
  }
1172
1219
  }
1173
- const queuePath = writeQueue(queue);
1174
- const lockPath = writeJobLocks(table);
1220
+ await persistSchedulerState({
1221
+ queue,
1222
+ locks: table,
1223
+ lease: leaseResult.lease,
1224
+ });
1175
1225
  const finalStatus = job.status;
1176
1226
  const isSuccess = finalStatus === "done";
1177
1227
  buf.status.push({
@@ -1203,8 +1253,8 @@ export async function completeJob(input) {
1203
1253
  });
1204
1254
  return {
1205
1255
  ok: true,
1206
- queue_path: queuePath,
1207
- lock_path: lockPath,
1256
+ queue_path: getJobQueuePath(),
1257
+ lock_path: getJobLockTablePath(),
1208
1258
  queue,
1209
1259
  locks: table,
1210
1260
  status: job.status,
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { resolveWorkspaceRoot, safeRead, safeWrite, wsPath, withFileLock, } from "./helpers.js";
3
3
  import { isReadError } from "./shared.js";
4
4
  import { openStore } from "./store/ace-packed-store.js";
5
- import { VericifyProjector } from "./store/materializers/vericify-projector.js";
5
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
6
6
  import { LedgerRepository } from "./store/repositories/ledger-repository.js";
7
7
  import { withStoreWriteQueue } from "./store/write-queue.js";
8
8
  import { getWorkspaceStorePath, listStoreKeysSync, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
@@ -234,9 +234,8 @@ export async function appendRunLedgerEntrySafe(input) {
234
234
  },
235
235
  });
236
236
  await store.commit();
237
- const projector = new VericifyProjector(store, root);
238
- await projector.projectLedger();
239
- await store.commit();
237
+ const projections = new ProjectionManager(store, root);
238
+ await projections.projectAfterCommit(["ledger"]);
240
239
  return {
241
240
  entry: toLegacyRunLedgerEntryDirect(record),
242
241
  path: operationalArtifactVirtualPath(root, RUN_LEDGER_REL),
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  export declare const ACE_MCP_SERVER_NAME = "ace-swarm";
3
- export declare const ACE_MCP_SERVER_VERSION = "1.1.0";
3
+ export declare const ACE_MCP_SERVER_VERSION = "0.2.1";
4
4
  export interface CreateAceServerOptions {
5
5
  toolGovernance?: boolean;
6
6
  instructions?: string;
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import { registerResources } from "./resources.js";
7
7
  import { registerTools } from "./tools.js";
8
8
  import { backfillHandoffsIntoScheduler } from "./tools-handoff.js";
9
9
  export const ACE_MCP_SERVER_NAME = "ace-swarm";
10
- export const ACE_MCP_SERVER_VERSION = "1.1.0";
10
+ export const ACE_MCP_SERVER_VERSION = "0.2.1";
11
11
  export function createAceServer(options = {}) {
12
12
  const workspaceRoot = resolveWorkspaceRoot();
13
13
  const server = new McpServer({
package/dist/shared.d.ts CHANGED
@@ -45,9 +45,9 @@ export declare const ROLE_ENUM: z.ZodEnum<{
45
45
  release: "release";
46
46
  }>;
47
47
  export declare const HANDOFF_VALIDATION_MODE: z.ZodOptional<z.ZodEnum<{
48
+ "agent-state": "agent-state";
48
49
  auto: "auto";
49
50
  swarm: "swarm";
50
- "agent-state": "agent-state";
51
51
  }>>;
52
52
  export declare const KERNEL_KEY_ENUM: z.ZodEnum<{
53
53
  directive_kernel: "directive_kernel";
@@ -4,7 +4,7 @@ import { resolveWorkspaceRoot, resolveWorkspaceArtifactPath as resolveWorkspaceA
4
4
  import { isReadError } from "./shared.js";
5
5
  import { validateStatusEventPayload, PROVENANCE_CRITICAL_EVENT_TYPES } from "./schemas.js";
6
6
  import { openStore } from "./store/ace-packed-store.js";
7
- import { VericifyProjector } from "./store/materializers/vericify-projector.js";
7
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
8
8
  import { TrackerRepository } from "./store/repositories/tracker-repository.js";
9
9
  import { getWorkspaceStorePath, listStoreKeysSync, readStoreJsonSync, storeExistsSync, } from "./store/store-snapshot.js";
10
10
  import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
@@ -90,8 +90,7 @@ function rotateIfNeeded(content) {
90
90
  safeWriteWorkspaceFile(STATUS_EVENTS_ARCHIVE_REL, archiveFull);
91
91
  return lines.join("\n") + "\n";
92
92
  }
93
- async function mirrorStatusEventToStore(event) {
94
- const root = workspaceRoot();
93
+ async function mirrorStatusEventToStore(root, event) {
95
94
  const storePath = getWorkspaceStorePath(root);
96
95
  if (!existsSync(storePath))
97
96
  return;
@@ -115,9 +114,8 @@ async function mirrorStatusEventToStore(event) {
115
114
  },
116
115
  });
117
116
  await store.commit();
118
- const projector = new VericifyProjector(store, root);
119
- await projector.projectStatusEvents();
120
- await store.commit();
117
+ const projections = new ProjectionManager(store, root);
118
+ await projections.projectAfterCommit(["status_events"]);
121
119
  }
122
120
  finally {
123
121
  await store.close();
@@ -125,17 +123,17 @@ async function mirrorStatusEventToStore(event) {
125
123
  });
126
124
  }
127
125
  function scheduleStatusEventMirror(event) {
126
+ const root = workspaceRoot();
128
127
  const snapshot = JSON.parse(JSON.stringify(event));
129
128
  trackerMirrorChain = trackerMirrorChain
130
129
  .catch(() => { })
131
- .then(() => mirrorStatusEventToStore(snapshot))
130
+ .then(() => mirrorStatusEventToStore(root, snapshot))
132
131
  .catch(() => { });
133
132
  }
134
133
  export async function waitForPendingStatusEventMirrors() {
135
134
  await trackerMirrorChain.catch(() => { });
136
135
  }
137
- async function appendStatusEventStoreBacked(event) {
138
- const root = workspaceRoot();
136
+ async function appendStatusEventStoreBacked(root, event) {
139
137
  const storePath = getWorkspaceStorePath(root);
140
138
  return withStoreWriteQueue(storePath, async () => {
141
139
  const store = await openStore(storePath);
@@ -157,9 +155,8 @@ async function appendStatusEventStoreBacked(event) {
157
155
  },
158
156
  });
159
157
  await store.commit();
160
- const projector = new VericifyProjector(store, root);
161
- await projector.projectStatusEvents();
162
- await store.commit();
158
+ const projections = new ProjectionManager(store, root);
159
+ await projections.projectAfterCommit(["status_events"]);
163
160
  return {
164
161
  path: operationalArtifactVirtualPath(root, STATUS_EVENTS_REL_PATH),
165
162
  event,
@@ -187,11 +184,12 @@ export function appendStatusEvent(input) {
187
184
  * Use this from async tool handlers where parallel subagents may fire.
188
185
  */
189
186
  export async function appendStatusEventSafe(input) {
187
+ const root = workspaceRoot();
190
188
  return withDynamicFileLock(STATUS_EVENTS_REL_PATH, async () => {
191
189
  const event = buildStatusEvent(input);
192
190
  validateStatusEvent(event);
193
- if (storeExistsSync(workspaceRoot())) {
194
- return appendStatusEventStoreBacked(event);
191
+ if (storeExistsSync(root)) {
192
+ return appendStatusEventStoreBacked(root, event);
195
193
  }
196
194
  return appendStatusEvent(input);
197
195
  });
@@ -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
- const content = relPath.endsWith("-archive.ndjson")
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
  }
@@ -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", []);
@@ -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}`);
@@ -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