@voybio/ace-swarm 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -1,8 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { ACE_TASKS_ROOT_REL, resolveWorkspaceRoot, safeRead, safeWrite, wsPath } from "./helpers.js";
3
3
  import { openStore } from "./store/ace-packed-store.js";
4
- import { VericifyProjector } from "./store/materializers/vericify-projector.js";
5
- import { getWorkspaceStorePath } from "./store/store-snapshot.js";
4
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
5
+ import { TodoRepository } from "./store/repositories/todo-repository.js";
6
+ import { getWorkspaceStorePath, readStoreJsonSync, storeExistsSync } from "./store/store-snapshot.js";
7
+ import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
6
8
  import { withStoreWriteQueue } from "./store/write-queue.js";
7
9
  import { isReadError, slugify } from "./shared.js";
8
10
  const TODO_MARKDOWN_REL = `${ACE_TASKS_ROOT_REL}/todo.md`;
@@ -182,9 +184,12 @@ function parseTodoState(raw) {
182
184
  }
183
185
  function readPersistedState() {
184
186
  const raw = safeRead(TODO_STATE_REL);
185
- if (isReadError(raw))
186
- return defaultState();
187
- return parseTodoState(raw) ?? defaultState();
187
+ if (!isReadError(raw)) {
188
+ const parsed = parseTodoState(raw);
189
+ if (parsed)
190
+ return parsed;
191
+ }
192
+ return readStoreBackedState() ?? defaultState();
188
193
  }
189
194
  function mapStoreStatus(status) {
190
195
  switch (status) {
@@ -200,8 +205,61 @@ function mapStoreStatus(status) {
200
205
  return "pending";
201
206
  }
202
207
  }
203
- async function mirrorTodoStateToStore(state) {
208
+ function mapLegacyStatus(status) {
209
+ switch (status) {
210
+ case "in_progress":
211
+ return "in_progress";
212
+ case "blocked":
213
+ return "blocked";
214
+ case "done":
215
+ case "cancelled":
216
+ return "done";
217
+ case "pending":
218
+ default:
219
+ return "backlog";
220
+ }
221
+ }
222
+ function readStoreBackedState() {
204
223
  const root = workspaceRoot();
224
+ if (!storeExistsSync(root))
225
+ return undefined;
226
+ const index = readStoreJsonSync(root, "state/todo/index") ?? [];
227
+ if (index.length === 0)
228
+ return undefined;
229
+ const nodes = {};
230
+ const order = [];
231
+ let updatedAt = Date.now();
232
+ for (const id of index) {
233
+ const record = readStoreJsonSync(root, `state/todo/${id}`);
234
+ if (!record?.id || !record.title || !record.status)
235
+ continue;
236
+ const noteSection = typeof record.notes === "string" && record.notes.startsWith("section: ")
237
+ ? record.notes.slice("section: ".length)
238
+ : "";
239
+ nodes[id] = {
240
+ id: record.id,
241
+ title: record.title,
242
+ section: noteSection,
243
+ source_line: 0,
244
+ depends_on: Array.isArray(record.depends_on)
245
+ ? record.depends_on.filter((value) => typeof value === "string")
246
+ : [],
247
+ status: mapLegacyStatus(record.status),
248
+ };
249
+ order.push(id);
250
+ if (typeof record.updated_at === "number" && Number.isFinite(record.updated_at)) {
251
+ updatedAt = Math.max(updatedAt, record.updated_at);
252
+ }
253
+ }
254
+ return {
255
+ version: 1,
256
+ updated_at: new Date(updatedAt).toISOString(),
257
+ source_todo_path: TODO_MARKDOWN_REL,
258
+ order,
259
+ nodes,
260
+ };
261
+ }
262
+ async function mirrorTodoStateToStore(root, state) {
205
263
  const storePath = getWorkspaceStorePath(root);
206
264
  if (!existsSync(storePath))
207
265
  return;
@@ -230,29 +288,59 @@ async function mirrorTodoStateToStore(state) {
230
288
  }
231
289
  await store.setJSON("state/todo/index", nextIds);
232
290
  await store.commit();
233
- const projector = new VericifyProjector(store, root);
234
- await projector.projectTodos();
291
+ const projections = new ProjectionManager(store, root);
292
+ await projections.projectAfterCommit(["todo_state"]);
293
+ }
294
+ finally {
295
+ await store.close();
296
+ }
297
+ });
298
+ }
299
+ async function writeStateStoreBacked(root, state) {
300
+ const storePath = getWorkspaceStorePath(root);
301
+ if (!existsSync(storePath)) {
302
+ return writeState(state);
303
+ }
304
+ return withStoreWriteQueue(storePath, async () => {
305
+ const store = await openStore(storePath);
306
+ try {
307
+ const repo = new TodoRepository(store);
308
+ await repo.replaceAll(state.order
309
+ .map((id) => state.nodes[id])
310
+ .filter((node) => Boolean(node))
311
+ .map((node) => ({
312
+ id: node.id,
313
+ title: node.title,
314
+ status: mapStoreStatus(node.status),
315
+ priority: "medium",
316
+ depends_on: node.depends_on,
317
+ notes: node.section ? `section: ${node.section}` : undefined,
318
+ })));
235
319
  await store.commit();
320
+ const projections = new ProjectionManager(store, root);
321
+ await projections.projectAfterCommit(["todo_state"]);
322
+ return operationalArtifactVirtualPath(root, TODO_STATE_REL);
236
323
  }
237
324
  finally {
238
325
  await store.close();
239
326
  }
240
327
  });
241
328
  }
242
- function scheduleTodoMirror(state) {
329
+ function scheduleTodoMirror(root, state) {
243
330
  const snapshot = JSON.parse(JSON.stringify(state));
244
331
  todoMirrorChain = todoMirrorChain
245
332
  .catch(() => { })
246
- .then(() => mirrorTodoStateToStore(snapshot))
333
+ .then(() => mirrorTodoStateToStore(root, snapshot))
247
334
  .catch(() => { });
248
335
  }
249
336
  export async function waitForTodoStoreMirror() {
250
337
  await todoMirrorChain.catch(() => { });
251
338
  }
252
339
  function writeState(state) {
340
+ const root = workspaceRoot();
253
341
  state.updated_at = new Date().toISOString();
254
342
  const path = safeWrite(TODO_STATE_REL, JSON.stringify(state, null, 2));
255
- scheduleTodoMirror(state);
343
+ scheduleTodoMirror(root, state);
256
344
  return path;
257
345
  }
258
346
  function syncStateWithTodo(todoRaw, previous) {
@@ -314,11 +402,26 @@ export function syncTodoState(todoContent) {
314
402
  return { state, path };
315
403
  }
316
404
  export async function syncTodoStateSafe(todoContent) {
405
+ const root = workspaceRoot();
406
+ if (storeExistsSync(root)) {
407
+ const todoRaw = typeof todoContent === "string" ? todoContent : safeRead(TODO_MARKDOWN_REL);
408
+ if (isReadError(todoRaw)) {
409
+ const state = defaultState();
410
+ const path = await writeStateStoreBacked(root, state);
411
+ return { state, path };
412
+ }
413
+ const state = syncStateWithTodo(todoRaw, readPersistedState());
414
+ const path = await writeStateStoreBacked(root, state);
415
+ return { state, path };
416
+ }
317
417
  const result = syncTodoState(todoContent);
318
418
  await waitForTodoStoreMirror();
319
419
  return result;
320
420
  }
321
421
  export function readTodoState() {
422
+ if (storeExistsSync(workspaceRoot())) {
423
+ return readPersistedState();
424
+ }
322
425
  const todoRaw = safeRead(TODO_MARKDOWN_REL);
323
426
  if (isReadError(todoRaw))
324
427
  return readPersistedState();
@@ -392,6 +495,71 @@ export function transitionTodoNode(nodeId, targetStatus) {
392
495
  };
393
496
  }
394
497
  export async function transitionTodoNodeSafe(nodeId, targetStatus) {
498
+ const root = workspaceRoot();
499
+ if (storeExistsSync(root)) {
500
+ const todoRaw = safeRead(TODO_MARKDOWN_REL);
501
+ if (isReadError(todoRaw)) {
502
+ return {
503
+ ok: false,
504
+ path: getTodoStatePath(),
505
+ todo_path: getTodoPath(),
506
+ error: `${TODO_MARKDOWN_REL} not found; cannot apply transition.`,
507
+ };
508
+ }
509
+ const synced = syncStateWithTodo(todoRaw, readPersistedState());
510
+ const node = synced.nodes[nodeId];
511
+ if (!node) {
512
+ return {
513
+ ok: false,
514
+ path: getTodoStatePath(),
515
+ todo_path: getTodoPath(),
516
+ error: `Unknown node_id: ${nodeId}`,
517
+ };
518
+ }
519
+ const from = node.status;
520
+ if (from !== targetStatus) {
521
+ const allowed = TRANSITIONS[from];
522
+ if (!allowed.includes(targetStatus)) {
523
+ return {
524
+ ok: false,
525
+ path: getTodoStatePath(),
526
+ todo_path: getTodoPath(),
527
+ node_id: nodeId,
528
+ from,
529
+ to: targetStatus,
530
+ allowed,
531
+ error: `Invalid transition ${from} -> ${targetStatus}`,
532
+ };
533
+ }
534
+ }
535
+ if (targetStatus === "in_progress" ||
536
+ targetStatus === "gated" ||
537
+ targetStatus === "done") {
538
+ const unresolved = node.depends_on.filter((depId) => synced.nodes[depId]?.status !== "done");
539
+ if (unresolved.length > 0) {
540
+ return {
541
+ ok: false,
542
+ path: getTodoStatePath(),
543
+ todo_path: getTodoPath(),
544
+ node_id: nodeId,
545
+ from,
546
+ to: targetStatus,
547
+ unresolved_dependencies: unresolved,
548
+ error: `Dependencies not satisfied for ${nodeId}`,
549
+ };
550
+ }
551
+ }
552
+ node.status = targetStatus;
553
+ const statePath = await writeStateStoreBacked(root, synced);
554
+ return {
555
+ ok: true,
556
+ path: statePath,
557
+ todo_path: getTodoPath(),
558
+ node_id: nodeId,
559
+ from,
560
+ to: targetStatus,
561
+ };
562
+ }
395
563
  const result = transitionTodoNode(nodeId, targetStatus);
396
564
  await waitForTodoStoreMirror();
397
565
  return result;
@@ -470,12 +470,13 @@ export function registerFileTools(server) {
470
470
  validationNotes.push(`lint warnings: ${lint.warnings.join("; ")}`);
471
471
  }
472
472
  }
473
- const abs = safeWrite(path, content);
474
473
  let todoStateSuffix = "";
475
474
  if (normalizedPath === `${ACE_TASKS_ROOT_REL}/todo.md`) {
476
475
  const synced = await syncTodoStateSafe(content);
477
476
  todoStateSuffix = `\nTODO state synced: ${synced.path}`;
478
477
  }
478
+ // Keep the file as a materialized projection after the canonical store update.
479
+ const abs = safeWrite(path, content);
479
480
  let kanbanSuffix = "";
480
481
  const shouldRefreshKanban = shouldAutoRefreshKanbanForPath(normalizedPath);
481
482
  if (shouldRefreshKanban) {
@@ -17,6 +17,7 @@ import { isGitRepo, gitStatus } from "./git-ops.js";
17
17
  import { existsSync, readdirSync, readFileSync } from "node:fs";
18
18
  import { resolve } from "node:path";
19
19
  import { auditPublicSurface } from "./public-surface.js";
20
+ import { auditStoreAuthority, writeStoreAuthorityAuditReport, } from "./store/store-authority-audit.js";
20
21
  import { PROVENANCE_CRITICAL_EVENT_TYPES, validateArtifactManifestPayload, validateProvenanceLogContent, validateTealConfigContent, } from "./schemas.js";
21
22
  import { readAceTaskContractAssessment } from "./ace-autonomy.js";
22
23
  function getArtifactManifestEntries(payload) {
@@ -1003,6 +1004,28 @@ export function registerFrameworkTools(server) {
1003
1004
  });
1004
1005
  }
1005
1006
  }
1007
+ const storeAuthority = auditStoreAuthority();
1008
+ checks.push({
1009
+ name: "store-authority:bypasses",
1010
+ ok: storeAuthority.bypassing_sites.length === 0,
1011
+ detail: storeAuthority.bypassing_sites.length === 0
1012
+ ? "no runtime writers bypass ACEPACK"
1013
+ : `${storeAuthority.bypassing_sites.length} runtime writers still bypass ACEPACK`,
1014
+ });
1015
+ checks.push({
1016
+ name: "store-authority:runtime-fallbacks",
1017
+ ok: storeAuthority.runtime_files_without_store_fallback_keys.length === 0,
1018
+ detail: storeAuthority.runtime_files_without_store_fallback_keys.length === 0
1019
+ ? "all runtime writers have store fallback keys"
1020
+ : `${storeAuthority.runtime_files_without_store_fallback_keys.length} runtime writers lack store fallback keys`,
1021
+ });
1022
+ checks.push({
1023
+ name: "store-authority:regenerable-projections",
1024
+ ok: storeAuthority.non_regenerable_projections.length === 0,
1025
+ detail: storeAuthority.non_regenerable_projections.length === 0
1026
+ ? "all projections are regenerable from store state"
1027
+ : `${storeAuthority.non_regenerable_projections.length} projections are not regenerable from store state`,
1028
+ });
1006
1029
  // Check: TEAL config has exactly one active YAML block
1007
1030
  const tealRaw = safeRead("agent-state/TEAL_CONFIG.md");
1008
1031
  if (!tealRaw.startsWith("[FILE NOT FOUND]")) {
@@ -1259,6 +1282,43 @@ export function registerFrameworkTools(server) {
1259
1282
  ],
1260
1283
  };
1261
1284
  });
1285
+ server.tool("audit_store_authority", "Report ACEPACK writer bypasses, missing fallback keys, and non-regenerable projections", {
1286
+ write_artifact: z
1287
+ .boolean()
1288
+ .optional()
1289
+ .describe("Write agent-state/STORE_AUTHORITY_AUDIT.md (default: true)"),
1290
+ artifact_path: z
1291
+ .string()
1292
+ .optional()
1293
+ .describe("Optional workspace-relative path for the audit report"),
1294
+ }, async ({ write_artifact, artifact_path }) => {
1295
+ const result = auditStoreAuthority();
1296
+ const reportPath = artifact_path ?? result.report_path;
1297
+ if (write_artifact !== false) {
1298
+ writeStoreAuthorityAuditReport({
1299
+ ...result,
1300
+ report_path: reportPath,
1301
+ }, reportPath);
1302
+ }
1303
+ return {
1304
+ content: [
1305
+ {
1306
+ type: "text",
1307
+ text: [
1308
+ `✅ Store authority audit completed (${result.total_safe_write_sites} safeWrite users)`,
1309
+ `Report: ${reportPath}`,
1310
+ `Bypassing ACEPACK: ${result.bypassing_sites.length}`,
1311
+ `Runtime files without fallback keys: ${result.runtime_files_without_store_fallback_keys.length}`,
1312
+ `Non-regenerable projections: ${result.non_regenerable_projections.length}`,
1313
+ "",
1314
+ `- canonical knowledge artifacts: ${result.classifications["canonical knowledge artifact"].length}`,
1315
+ `- canonical runtime state writers: ${result.classifications["canonical runtime state"].length}`,
1316
+ `- projection-only writers: ${result.classifications["projection only"].length}`,
1317
+ ].join("\n"),
1318
+ },
1319
+ ],
1320
+ };
1321
+ });
1262
1322
  // ── Execute Gates (manifest-driven gate runner) ───────────────────
1263
1323
  server.tool("execute_gates", "Run quality gates from agent-state/MODULES/gates/*.json manifests (single source of truth)", {
1264
1324
  gate_ids: z
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Memory tool registrations – context snapshots and recall.
3
3
  */
4
+ import { existsSync } from "node:fs";
4
5
  import { z } from "zod";
5
- import { safeRead, safeWrite, wsPath } from "./helpers.js";
6
+ import { resolveWorkspaceRoot, safeRead, safeWrite, wsPath } from "./helpers.js";
6
7
  import { isReadError } from "./shared.js";
7
8
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
8
- import { appendStatusEvent } from "./status-events.js";
9
+ import { appendStatusEventSafe } from "./status-events.js";
9
10
  import { readRuntimeProfile } from "./runtime-profile.js";
10
11
  import { buildAceContinuityPacket, formatAceContinuityPacketMarkdown, normalizeContinuityPolicy, } from "./ace-autonomy.js";
12
+ import { openStore } from "./store/ace-packed-store.js";
13
+ import { ProjectionManager } from "./store/materializers/projection-manager.js";
14
+ import { ContextSnapshotRepository } from "./store/repositories/context-snapshot-repository.js";
15
+ import { getWorkspaceStorePath, storeExistsSync, toVirtualStorePath, } from "./store/store-snapshot.js";
16
+ import { withStoreWriteQueue } from "./store/write-queue.js";
11
17
  const CONTEXT_STORE_REL = "agent-state/context-snapshots";
12
18
  export function registerMemoryTools(server) {
13
19
  server.tool("context_snapshot", "Capture a named context snapshot for later retrieval (decisions, progress, state)", {
@@ -31,39 +37,68 @@ export function registerMemoryTools(server) {
31
37
  .describe("Additional metadata"),
32
38
  }, async ({ name, summary, decisions, open_questions, artifacts, metadata }) => {
33
39
  const timestamp = new Date().toISOString();
34
- const snapshot = {
35
- name,
36
- timestamp,
37
- summary,
38
- decisions: decisions ?? [],
39
- open_questions: open_questions ?? [],
40
- artifacts: artifacts ?? [],
41
- metadata: metadata ?? {},
42
- };
43
- const slug = name
44
- .trim()
45
- .toLowerCase()
46
- .replace(/[^a-z0-9]+/g, "-")
47
- .slice(0, 48);
48
- const filename = `${slug}.json`;
49
- const path = wsPath(CONTEXT_STORE_REL, filename);
50
- safeWrite(path, JSON.stringify(snapshot, null, 2));
51
- // Update index
52
- const indexPath = wsPath(CONTEXT_STORE_REL, "index.json");
53
- const indexRaw = safeRead(indexPath);
54
- let index = { snapshots: [] };
55
- if (!isReadError(indexRaw)) {
56
- try {
57
- index = JSON.parse(indexRaw);
58
- }
59
- catch {
60
- /* keep default */
40
+ const root = resolveWorkspaceRoot();
41
+ let path = wsPath(CONTEXT_STORE_REL, `${name}.json`);
42
+ if (storeExistsSync(root) && existsSync(getWorkspaceStorePath(root))) {
43
+ const storePath = getWorkspaceStorePath(root);
44
+ const saved = await withStoreWriteQueue(storePath, async () => {
45
+ const store = await openStore(storePath);
46
+ try {
47
+ const repo = new ContextSnapshotRepository(store);
48
+ const result = await repo.saveSnapshot({
49
+ name,
50
+ summary,
51
+ decisions,
52
+ open_questions,
53
+ artifacts,
54
+ metadata,
55
+ timestamp,
56
+ });
57
+ await store.commit();
58
+ const projections = new ProjectionManager(store, root);
59
+ await projections.projectAfterCommit(["context_snapshots"]);
60
+ return result;
61
+ }
62
+ finally {
63
+ await store.close();
64
+ }
65
+ });
66
+ path = toVirtualStorePath(storePath, `state/memory/context_snapshots/${saved.file}`);
67
+ }
68
+ else {
69
+ const snapshot = {
70
+ name,
71
+ timestamp,
72
+ summary,
73
+ decisions: decisions ?? [],
74
+ open_questions: open_questions ?? [],
75
+ artifacts: artifacts ?? [],
76
+ metadata: metadata ?? {},
77
+ };
78
+ const slug = name
79
+ .trim()
80
+ .toLowerCase()
81
+ .replace(/[^a-z0-9]+/g, "-")
82
+ .slice(0, 48);
83
+ const filename = `${slug}.json`;
84
+ path = wsPath(CONTEXT_STORE_REL, filename);
85
+ safeWrite(path, JSON.stringify(snapshot, null, 2));
86
+ const indexPath = wsPath(CONTEXT_STORE_REL, "index.json");
87
+ const indexRaw = safeRead(indexPath);
88
+ let index = { snapshots: [] };
89
+ if (!isReadError(indexRaw)) {
90
+ try {
91
+ index = JSON.parse(indexRaw);
92
+ }
93
+ catch {
94
+ /* keep default */
95
+ }
61
96
  }
97
+ index.snapshots = index.snapshots.filter((s) => s.name !== name);
98
+ index.snapshots.push({ name, file: filename, timestamp, summary });
99
+ index.snapshots.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
100
+ safeWrite(indexPath, JSON.stringify(index, null, 2));
62
101
  }
63
- index.snapshots = index.snapshots.filter((s) => s.name !== name);
64
- index.snapshots.push({ name, file: filename, timestamp, summary });
65
- index.snapshots.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
66
- safeWrite(indexPath, JSON.stringify(index, null, 2));
67
102
  const ledger = await appendRunLedgerEntrySafe({
68
103
  tool: "context_snapshot",
69
104
  category: "info",
@@ -74,7 +109,7 @@ export function registerMemoryTools(server) {
74
109
  decisions_count: (decisions ?? []).length,
75
110
  },
76
111
  });
77
- appendStatusEvent({
112
+ await appendStatusEventSafe({
78
113
  source_module: "capability-memory",
79
114
  event_type: "CONTEXT_SNAPSHOT",
80
115
  status: "done",
@@ -32,8 +32,8 @@ export function registerTodoTools(server) {
32
32
  note: z.string().optional().describe("Optional transition context note"),
33
33
  }, async ({ new_content, node_id, status, note }) => {
34
34
  if (typeof new_content === "string") {
35
- const path = safeWrite(resolveWritableTaskPath("todo"), new_content);
36
35
  const synced = await syncTodoStateSafe(new_content);
36
+ const path = safeWrite(resolveWritableTaskPath("todo"), new_content);
37
37
  const runLedger = await appendRunLedgerEntrySafe({
38
38
  tool: "update_todo",
39
39
  category: "major_update",
@@ -6,5 +6,5 @@
6
6
  * present, while retaining deterministic adapter fallback for unreachable or
7
7
  * unconfigured runtimes.
8
8
  */
9
- export {};
9
+ export declare function canUseAceBridgeWorkspace(workspaceRoot: string): boolean;
10
10
  //# sourceMappingURL=agent-worker.d.ts.map
@@ -7,11 +7,10 @@
7
7
  * unconfigured runtimes.
8
8
  */
9
9
  import { parseManualHandoffCommand, parseManualToolCommand, planRoleExecution, } from "../agent-runtime/role-adapters.js";
10
- import { existsSync } from "node:fs";
11
- import { resolve } from "node:path";
12
10
  import { ModelBridge } from "../model-bridge.js";
13
11
  import { OllamaClient } from "./ollama.js";
14
12
  import { OpenAICompatibleClient, diagnoseChatRuntimeConfig, } from "./openai-compatible.js";
13
+ import { resolveAceStateLayout } from "../ace-state-resolver.js";
15
14
  const config = new Map();
16
15
  let role = process.env.ACE_TUI_AGENT_ROLE ?? "unknown";
17
16
  let initialized = false;
@@ -22,6 +21,9 @@ const bridge = new ModelBridge({
22
21
  ollama: ollamaClient,
23
22
  openai: openaiClient,
24
23
  });
24
+ export function canUseAceBridgeWorkspace(workspaceRoot) {
25
+ return resolveAceStateLayout(workspaceRoot).isAcePresent;
26
+ }
25
27
  function send(message) {
26
28
  if (typeof process.send === "function") {
27
29
  process.send(message);
@@ -101,7 +103,7 @@ async function tryModelBridge(text) {
101
103
  const workspace = config.get("workspace_root")?.trim();
102
104
  if (!provider || !model || !workspace)
103
105
  return false;
104
- if (!existsSync(resolve(workspace, ".agents", "ACE", "agent-state", "TASK.md"))) {
106
+ if (!canUseAceBridgeWorkspace(workspace)) {
105
107
  return false;
106
108
  }
107
109
  if (provider.toLowerCase() === "ollama" && !baseUrl) {
@@ -11,6 +11,7 @@ import { EventEmitter } from "node:events";
11
11
  import type { ChatMessage } from "./layout.js";
12
12
  import { ModelBridge } from "../model-bridge.js";
13
13
  import type { AceContextTier } from "../ace-context.js";
14
+ import { type AceRuntimeStatusPacket } from "../store/repositories/local-model-runtime-repository.js";
14
15
  export interface ChatSessionOptions {
15
16
  provider: string;
16
17
  model: string;
@@ -53,9 +54,21 @@ export declare class ChatSession extends EventEmitter {
53
54
  private aceBridge;
54
55
  private activeAceBridge;
55
56
  private providerBaseUrls;
57
+ private sessionId;
58
+ private createdAt;
59
+ private lastActiveAt;
60
+ private currentRuntimeStatus;
61
+ private activationLedger;
56
62
  constructor(clients: ChatSessionClients, telemetry: TelemetryTracker, options: ChatSessionOptions);
57
63
  /** Get display messages for rendering */
58
64
  getDisplayMessages(): ChatMessage[];
65
+ getSessionId(): string;
66
+ getCreatedAt(): number;
67
+ getLastActiveAt(): number;
68
+ getCurrentRuntimeStatus(): AceRuntimeStatusPacket | undefined;
69
+ hasMeaningfulTranscript(): boolean;
70
+ getTranscriptExcerpt(maxMessages?: number): string | undefined;
71
+ getTranscriptSummary(): string | undefined;
59
72
  /** Is currently streaming a response? */
60
73
  isStreaming(): boolean;
61
74
  /** Get current model */
@@ -78,6 +91,12 @@ export declare class ChatSession extends EventEmitter {
78
91
  private shouldUseAceBridge;
79
92
  private buildBridgeTask;
80
93
  private pushSystemMessage;
94
+ private noteActivity;
95
+ private buildRuntimeStatusPacket;
96
+ private setRuntimeStatus;
97
+ setApprovalState(state: NonNullable<AceRuntimeStatusPacket["approval_state"]>): boolean;
98
+ private persistRuntimeStatus;
99
+ private persistRuntimeSessionArtifacts;
81
100
  private runAceBridge;
82
101
  }
83
102
  export {};