@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.
- 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
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.
|
|
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(
|
|
568
|
-
return writeOperationalArtifactSync(
|
|
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`;
|
package/dist/job-scheduler.js
CHANGED
|
@@ -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
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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:
|
|
958
|
-
lock_path:
|
|
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
|
-
|
|
1026
|
-
|
|
1066
|
+
await persistSchedulerState({
|
|
1067
|
+
queue,
|
|
1068
|
+
locks: table,
|
|
1069
|
+
lease: leaseResult.lease,
|
|
1070
|
+
});
|
|
1027
1071
|
return {
|
|
1028
1072
|
ok: true,
|
|
1029
|
-
queue_path:
|
|
1030
|
-
lock_path:
|
|
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
|
-
|
|
1073
|
-
|
|
1116
|
+
await persistSchedulerState({
|
|
1117
|
+
queue,
|
|
1118
|
+
locks: table,
|
|
1119
|
+
lease: leaseResult.lease,
|
|
1120
|
+
});
|
|
1074
1121
|
return {
|
|
1075
1122
|
ok: true,
|
|
1076
|
-
queue_path:
|
|
1077
|
-
lock_path:
|
|
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
|
-
|
|
1174
|
-
|
|
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:
|
|
1207
|
-
lock_path:
|
|
1256
|
+
queue_path: getJobQueuePath(),
|
|
1257
|
+
lock_path: getJobLockTablePath(),
|
|
1208
1258
|
queue,
|
|
1209
1259
|
locks: table,
|
|
1210
1260
|
status: job.status,
|
package/dist/run-ledger.js
CHANGED
|
@@ -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 {
|
|
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
|
|
238
|
-
await
|
|
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 = "
|
|
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 = "
|
|
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";
|
package/dist/status-events.js
CHANGED
|
@@ -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 {
|
|
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
|
|
119
|
-
await
|
|
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
|
|
161
|
-
await
|
|
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(
|
|
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
|
-
|
|
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
|