@voybio/ace-swarm 2.4.4 → 2.4.6
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/CHANGELOG.md +69 -0
- package/README.md +72 -532
- package/assets/eval-battery/battery.json +85 -0
- package/assets/eval-battery/fixtures/edit-target.js +3 -0
- package/assets/scripts/render-mcp-configs.sh +12 -0
- package/dist/ace-context.d.ts +18 -0
- package/dist/ace-context.js +161 -16
- package/dist/ace-server-instructions.js +36 -9
- package/dist/bridge-trace.d.ts +17 -0
- package/dist/bridge-trace.js +23 -0
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +619 -10
- package/dist/discovery-runtime-wrappers.d.ts +16 -1
- package/dist/discovery-runtime-wrappers.js +65 -9
- package/dist/eval-harness.d.ts +110 -0
- package/dist/eval-harness.js +254 -0
- package/dist/experience-reflector.d.ts +13 -0
- package/dist/experience-reflector.js +62 -0
- package/dist/goal-engine.d.ts +155 -0
- package/dist/goal-engine.js +795 -0
- package/dist/helpers/store-resolution.js +40 -39
- package/dist/hermes/session-manager.d.ts +10 -0
- package/dist/hermes/session-manager.js +130 -27
- package/dist/job-scheduler.d.ts +58 -0
- package/dist/job-scheduler.js +202 -2
- package/dist/job-worker.d.ts +87 -0
- package/dist/job-worker.js +182 -0
- package/dist/local-model-policy.d.ts +8 -2
- package/dist/local-model-policy.js +31 -17
- package/dist/local-model-runtime.d.ts +6 -0
- package/dist/local-model-runtime.js +130 -2
- package/dist/measured-classing.d.ts +66 -0
- package/dist/measured-classing.js +165 -0
- package/dist/model-bridge.d.ts +45 -0
- package/dist/model-bridge.js +502 -44
- package/dist/orchestrator-supervisor.js +1 -1
- package/dist/plan-proposal.d.ts +7 -0
- package/dist/plan-proposal.js +74 -4
- package/dist/playbook-adaptation.d.ts +37 -0
- package/dist/playbook-adaptation.js +159 -0
- package/dist/playbook-curator.d.ts +16 -0
- package/dist/playbook-curator.js +106 -0
- package/dist/playbook-llm-reflector.d.ts +26 -0
- package/dist/playbook-llm-reflector.js +157 -0
- package/dist/public-surface.d.ts +2 -0
- package/dist/public-surface.js +26 -14
- package/dist/safe-edit.d.ts +20 -0
- package/dist/safe-edit.js +355 -78
- package/dist/server.d.ts +11 -2
- package/dist/server.js +55 -5
- package/dist/shared.d.ts +12 -0
- package/dist/shared.js +13 -1
- package/dist/source-mutation-coordinator.d.ts +54 -0
- package/dist/source-mutation-coordinator.js +259 -0
- package/dist/status-events.d.ts +4 -1
- package/dist/status-events.js +41 -6
- package/dist/store/ace-packed-store.d.ts +28 -9
- package/dist/store/ace-packed-store.js +324 -57
- package/dist/store/bgzf.d.ts +4 -0
- package/dist/store/bgzf.js +94 -0
- package/dist/store/bootstrap-store.d.ts +4 -1
- package/dist/store/bootstrap-store.js +57 -3
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +67 -1
- package/dist/store/crc32.d.ts +2 -0
- package/dist/store/crc32.js +16 -0
- package/dist/store/fsck.d.ts +16 -0
- package/dist/store/fsck.js +240 -0
- package/dist/store/materializers/context-snapshot-materializer.d.ts +1 -0
- package/dist/store/materializers/context-snapshot-materializer.js +4 -1
- package/dist/store/materializers/projection-manager.d.ts +7 -0
- package/dist/store/materializers/projection-manager.js +37 -0
- package/dist/store/materializers/scheduler-projection-materializer.js +10 -3
- package/dist/store/repositories/goal-repository.d.ts +112 -0
- package/dist/store/repositories/goal-repository.js +157 -0
- package/dist/store/repositories/playbook-repository.d.ts +107 -0
- package/dist/store/repositories/playbook-repository.js +314 -0
- package/dist/store/store-artifacts.js +44 -2
- package/dist/store/store-authority-audit.js +1 -0
- package/dist/store/store-cli.d.ts +30 -0
- package/dist/store/store-cli.js +242 -0
- package/dist/store/store-snapshot.d.ts +12 -0
- package/dist/store/store-snapshot.js +74 -11
- package/dist/store/types.d.ts +1 -0
- package/dist/store/write-coordinator.d.ts +7 -0
- package/dist/store/write-coordinator.js +11 -0
- package/dist/todo-state.d.ts +21 -0
- package/dist/todo-state.js +150 -53
- package/dist/tools-agent.js +86 -22
- package/dist/tools-discovery.js +5 -3
- package/dist/tools-files.js +39 -18
- package/dist/tools-framework.js +31 -3
- package/dist/tools-goal.d.ts +11 -0
- package/dist/tools-goal.js +108 -0
- package/dist/tools-handoff.js +7 -11
- package/dist/tools-lifecycle.js +2 -0
- package/dist/tools-scheduler.js +44 -1
- package/dist/tools-skills.js +3 -2
- package/dist/tools-todo.js +64 -3
- package/dist/tools.js +2 -0
- package/dist/tui/chat.js +4 -0
- package/dist/tui/index.js +4 -1
- package/dist/tui/ollama.d.ts +2 -0
- package/dist/tui/openai-compatible.d.ts +14 -0
- package/dist/tui/openai-compatible.js +15 -2
- package/dist/tui/provider-discovery.d.ts +1 -0
- package/dist/tui/provider-discovery.js +2 -2
- package/docs/cli-reference.md +125 -0
- package/docs/development.md +46 -0
- package/docs/evaluation-safety.md +27 -0
- package/docs/experience-playbook.md +35 -0
- package/docs/hermes.md +129 -0
- package/docs/library-api.md +31 -0
- package/docs/mcp-tool-reference.md +290 -0
- package/docs/provider-security.md +85 -0
- package/docs/state-model.md +113 -0
- package/docs/troubleshooting.md +95 -0
- package/docs/tui-operator-guide.md +75 -0
- package/package.json +3 -1
- package/scripts/dev/run_hermes_crispr_live.mjs +212 -0
package/dist/todo-state.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { ACE_TASKS_ROOT_REL, resolveWorkspaceRoot, safeRead, safeWrite, wsPath } from "./helpers.js";
|
|
2
|
+
import { ACE_TASKS_ROOT_REL, resolveWorkspaceRoot, resolveWritableTaskPath, safeRead, safeWrite, safeWriteAsync, withFileLock, wsPath, } from "./helpers.js";
|
|
3
3
|
import { openStore } from "./store/ace-packed-store.js";
|
|
4
4
|
import { ProjectionManager } from "./store/materializers/projection-manager.js";
|
|
5
5
|
import { TodoRepository } from "./store/repositories/todo-repository.js";
|
|
@@ -385,6 +385,49 @@ function setTodoCheckbox(todoRaw, line, checked) {
|
|
|
385
385
|
lines[line - 1] = target.replace(/^- \[( |x|X)\]/, `- [${checked ? "x" : " "}]`);
|
|
386
386
|
return lines.join("\n");
|
|
387
387
|
}
|
|
388
|
+
function insertTodoItem(todoRaw, section, title, id, dependsOn) {
|
|
389
|
+
const metaParts = [`id=${id}`];
|
|
390
|
+
if (dependsOn.length > 0)
|
|
391
|
+
metaParts.push(`depends_on=${dependsOn.join(",")}`);
|
|
392
|
+
const newLine = `- [ ] ${title} <!-- ace:${metaParts.join("; ")} -->`;
|
|
393
|
+
const lines = todoRaw.length > 0 ? todoRaw.split(/\r?\n/) : [];
|
|
394
|
+
let sectionStart = -1;
|
|
395
|
+
let sectionEnd = lines.length;
|
|
396
|
+
for (let i = 0; i < lines.length; i++) {
|
|
397
|
+
const header = lines[i]?.match(/^##\s+(.+)$/);
|
|
398
|
+
if (!header?.[1])
|
|
399
|
+
continue;
|
|
400
|
+
if (header[1].trim().toLowerCase() === section.toLowerCase()) {
|
|
401
|
+
sectionStart = i;
|
|
402
|
+
let j = i + 1;
|
|
403
|
+
while (j < lines.length && !/^##\s+/.test(lines[j] ?? ""))
|
|
404
|
+
j++;
|
|
405
|
+
sectionEnd = j;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (sectionStart === -1) {
|
|
410
|
+
const trimmed = todoRaw.replace(/\s+$/, "");
|
|
411
|
+
const prefix = trimmed.length > 0 ? `${trimmed}\n\n` : "";
|
|
412
|
+
return `${prefix}## ${section}\n${newLine}\n`;
|
|
413
|
+
}
|
|
414
|
+
let insertAt = sectionEnd;
|
|
415
|
+
while (insertAt > sectionStart + 1 && (lines[insertAt - 1] ?? "").trim() === "")
|
|
416
|
+
insertAt--;
|
|
417
|
+
lines.splice(insertAt, 0, newLine);
|
|
418
|
+
return lines.join("\n");
|
|
419
|
+
}
|
|
420
|
+
function uniqueTodoId(base, existingIds) {
|
|
421
|
+
if (!existingIds.has(base))
|
|
422
|
+
return base;
|
|
423
|
+
let n = 2;
|
|
424
|
+
let candidate = `${base}-${n}`;
|
|
425
|
+
while (existingIds.has(candidate)) {
|
|
426
|
+
n += 1;
|
|
427
|
+
candidate = `${base}-${n}`;
|
|
428
|
+
}
|
|
429
|
+
return candidate;
|
|
430
|
+
}
|
|
388
431
|
function getTodoPath() {
|
|
389
432
|
return wsPath(TODO_MARKDOWN_REL);
|
|
390
433
|
}
|
|
@@ -401,7 +444,10 @@ export function syncTodoState(todoContent) {
|
|
|
401
444
|
const path = writeState(state);
|
|
402
445
|
return { state, path };
|
|
403
446
|
}
|
|
404
|
-
|
|
447
|
+
// Lock-free core: callers that already hold the TODO_MARKDOWN_REL lock (e.g.
|
|
448
|
+
// appendTodoItemSafe) must call this directly instead of syncTodoStateSafe,
|
|
449
|
+
// since withFileLock is not reentrant and re-acquiring the same key deadlocks.
|
|
450
|
+
async function syncTodoStateLocked(todoContent) {
|
|
405
451
|
const root = workspaceRoot();
|
|
406
452
|
if (storeExistsSync(root)) {
|
|
407
453
|
const todoRaw = typeof todoContent === "string" ? todoContent : safeRead(TODO_MARKDOWN_REL);
|
|
@@ -418,6 +464,9 @@ export async function syncTodoStateSafe(todoContent) {
|
|
|
418
464
|
await waitForTodoStoreMirror();
|
|
419
465
|
return result;
|
|
420
466
|
}
|
|
467
|
+
export async function syncTodoStateSafe(todoContent) {
|
|
468
|
+
return withFileLock(TODO_MARKDOWN_REL, () => syncTodoStateLocked(todoContent));
|
|
469
|
+
}
|
|
421
470
|
export function readTodoState() {
|
|
422
471
|
if (storeExistsSync(workspaceRoot())) {
|
|
423
472
|
return readPersistedState();
|
|
@@ -495,73 +544,121 @@ export function transitionTodoNode(nodeId, targetStatus) {
|
|
|
495
544
|
};
|
|
496
545
|
}
|
|
497
546
|
export async function transitionTodoNodeSafe(nodeId, targetStatus) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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)) {
|
|
547
|
+
return withFileLock(TODO_MARKDOWN_REL, async () => {
|
|
548
|
+
const root = workspaceRoot();
|
|
549
|
+
if (storeExistsSync(root)) {
|
|
550
|
+
const todoRaw = safeRead(TODO_MARKDOWN_REL);
|
|
551
|
+
if (isReadError(todoRaw)) {
|
|
523
552
|
return {
|
|
524
553
|
ok: false,
|
|
525
554
|
path: getTodoStatePath(),
|
|
526
555
|
todo_path: getTodoPath(),
|
|
527
|
-
|
|
528
|
-
from,
|
|
529
|
-
to: targetStatus,
|
|
530
|
-
allowed,
|
|
531
|
-
error: `Invalid transition ${from} -> ${targetStatus}`,
|
|
556
|
+
error: `${TODO_MARKDOWN_REL} not found; cannot apply transition.`,
|
|
532
557
|
};
|
|
533
558
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
targetStatus === "done") {
|
|
538
|
-
const unresolved = node.depends_on.filter((depId) => synced.nodes[depId]?.status !== "done");
|
|
539
|
-
if (unresolved.length > 0) {
|
|
559
|
+
const synced = syncStateWithTodo(todoRaw, readPersistedState());
|
|
560
|
+
const node = synced.nodes[nodeId];
|
|
561
|
+
if (!node) {
|
|
540
562
|
return {
|
|
541
563
|
ok: false,
|
|
542
564
|
path: getTodoStatePath(),
|
|
543
565
|
todo_path: getTodoPath(),
|
|
544
|
-
node_id: nodeId
|
|
545
|
-
from,
|
|
546
|
-
to: targetStatus,
|
|
547
|
-
unresolved_dependencies: unresolved,
|
|
548
|
-
error: `Dependencies not satisfied for ${nodeId}`,
|
|
566
|
+
error: `Unknown node_id: ${nodeId}`,
|
|
549
567
|
};
|
|
550
568
|
}
|
|
569
|
+
const from = node.status;
|
|
570
|
+
if (from !== targetStatus) {
|
|
571
|
+
const allowed = TRANSITIONS[from];
|
|
572
|
+
if (!allowed.includes(targetStatus)) {
|
|
573
|
+
return {
|
|
574
|
+
ok: false,
|
|
575
|
+
path: getTodoStatePath(),
|
|
576
|
+
todo_path: getTodoPath(),
|
|
577
|
+
node_id: nodeId,
|
|
578
|
+
from,
|
|
579
|
+
to: targetStatus,
|
|
580
|
+
allowed,
|
|
581
|
+
error: `Invalid transition ${from} -> ${targetStatus}`,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (targetStatus === "in_progress" ||
|
|
586
|
+
targetStatus === "gated" ||
|
|
587
|
+
targetStatus === "done") {
|
|
588
|
+
const unresolved = node.depends_on.filter((depId) => synced.nodes[depId]?.status !== "done");
|
|
589
|
+
if (unresolved.length > 0) {
|
|
590
|
+
return {
|
|
591
|
+
ok: false,
|
|
592
|
+
path: getTodoStatePath(),
|
|
593
|
+
todo_path: getTodoPath(),
|
|
594
|
+
node_id: nodeId,
|
|
595
|
+
from,
|
|
596
|
+
to: targetStatus,
|
|
597
|
+
unresolved_dependencies: unresolved,
|
|
598
|
+
error: `Dependencies not satisfied for ${nodeId}`,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
node.status = targetStatus;
|
|
603
|
+
const statePath = await writeStateStoreBacked(root, synced);
|
|
604
|
+
return {
|
|
605
|
+
ok: true,
|
|
606
|
+
path: statePath,
|
|
607
|
+
todo_path: getTodoPath(),
|
|
608
|
+
node_id: nodeId,
|
|
609
|
+
from,
|
|
610
|
+
to: targetStatus,
|
|
611
|
+
};
|
|
551
612
|
}
|
|
552
|
-
|
|
553
|
-
|
|
613
|
+
const result = transitionTodoNode(nodeId, targetStatus);
|
|
614
|
+
await waitForTodoStoreMirror();
|
|
615
|
+
return result;
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Atomically append a new TODO item under a section, without requiring the
|
|
620
|
+
* caller to read the whole board and resubmit it via update_todo(new_content).
|
|
621
|
+
* That read-modify-write dance is what let concurrent agents silently clobber
|
|
622
|
+
* each other's appended items: the read and write are separate MCP calls, so
|
|
623
|
+
* a lock around the write alone cannot close the race. Here the read, insert,
|
|
624
|
+
* and write all happen inside one TODO_MARKDOWN_REL lock acquisition.
|
|
625
|
+
*/
|
|
626
|
+
export async function appendTodoItemSafe(title, options) {
|
|
627
|
+
const trimmedTitle = title.trim();
|
|
628
|
+
if (!trimmedTitle) {
|
|
554
629
|
return {
|
|
555
|
-
ok:
|
|
556
|
-
path:
|
|
630
|
+
ok: false,
|
|
631
|
+
path: getTodoStatePath(),
|
|
557
632
|
todo_path: getTodoPath(),
|
|
558
|
-
|
|
559
|
-
from,
|
|
560
|
-
to: targetStatus,
|
|
633
|
+
error: "Todo item title must not be empty.",
|
|
561
634
|
};
|
|
562
635
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
636
|
+
return withFileLock(TODO_MARKDOWN_REL, async () => {
|
|
637
|
+
const existingRaw = safeRead(TODO_MARKDOWN_REL);
|
|
638
|
+
const baseRaw = isReadError(existingRaw) ? "# Tasks\n" : existingRaw;
|
|
639
|
+
const section = options?.section?.trim() || "Active Work";
|
|
640
|
+
const dependsOn = (options?.depends_on ?? []).map((dep) => dep.trim()).filter(Boolean);
|
|
641
|
+
const existingIds = new Set(parseTodoMarkdown(baseRaw).map((entry) => entry.id));
|
|
642
|
+
const requestedId = options?.id?.trim();
|
|
643
|
+
if (requestedId && existingIds.has(requestedId)) {
|
|
644
|
+
return {
|
|
645
|
+
ok: false,
|
|
646
|
+
path: getTodoStatePath(),
|
|
647
|
+
todo_path: getTodoPath(),
|
|
648
|
+
error: `Todo id already exists: ${requestedId}`,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const id = requestedId || uniqueTodoId(`todo-${slugify(trimmedTitle)}`, existingIds);
|
|
652
|
+
const updatedRaw = insertTodoItem(baseRaw, section, trimmedTitle, id, dependsOn);
|
|
653
|
+
const synced = await syncTodoStateLocked(updatedRaw);
|
|
654
|
+
const mdPath = await safeWriteAsync(resolveWritableTaskPath("todo"), updatedRaw);
|
|
655
|
+
return {
|
|
656
|
+
ok: true,
|
|
657
|
+
path: synced.path,
|
|
658
|
+
todo_path: mdPath,
|
|
659
|
+
node_id: id,
|
|
660
|
+
section,
|
|
661
|
+
};
|
|
662
|
+
});
|
|
566
663
|
}
|
|
567
664
|
//# sourceMappingURL=todo-state.js.map
|
package/dist/tools-agent.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Agent, skill, kernel, and task-pack tool registrations.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { ALL_AGENTS, COMPOSABLE_AGENTS, SWARM_AGENTS, SWARM_SUBAGENT_MAP, classifyPathSource, getAgentInstructionPath, getAgentManifestPath, getKernelArtifactPath, isSwarmRole, listAvailableSkills, readAgentInstructions, readAgentManifest, readKernelArtifact, readSkillInstructions, readTaskArtifact, resolveWorkspaceRoot,
|
|
5
|
+
import { ALL_AGENTS, COMPOSABLE_AGENTS, SWARM_AGENTS, SWARM_SUBAGENT_MAP, classifyPathSource, getAgentInstructionPath, getAgentManifestPath, getKernelArtifactPath, isSwarmRole, listAvailableSkills, readAgentInstructions, readAgentManifest, readKernelArtifact, readSkillInstructions, readTaskArtifact, resolveWorkspaceRoot, } from "./helpers.js";
|
|
6
6
|
import { loadRuntimeProfile, readRuntimePromptTemplate, readRuntimeProfileState, validateRuntimeProfileContent, } from "./runtime-profile.js";
|
|
7
7
|
import { getUnattendedSession, listUnattendedSessions, startUnattendedSession, stopUnattendedSession, validateRuntimeExecutorSessionRegistryContent, waitForUnattendedSession, } from "./runtime-executor.js";
|
|
8
|
+
import { materializePlaybookMarkdown, PlaybookRepository, renderPlaybookMarkdown, } from "./store/repositories/playbook-repository.js";
|
|
8
9
|
import { executeRuntimeTool, listRuntimeToolSpecs, loadRuntimeToolRegistry, validateRuntimeToolRegistryContent, } from "./runtime-tool-specs.js";
|
|
9
10
|
import { createWorkspaceSessionAsync, listWorkspaceSessions, removeWorkspaceSession, resolveRuntimeWorkspaceRoot, validateManagedWorkspacePath, } from "./workspace-manager.js";
|
|
10
11
|
import { getTrackerAdapter, listTrackerAdapterKinds, loadTrackerSnapshot, validateTrackerSnapshotContent, } from "./tracker-adapters.js";
|
|
@@ -350,8 +351,9 @@ function createToolOnlyBridgeResult(step, reason) {
|
|
|
350
351
|
return {
|
|
351
352
|
bridge_id: `tool-only-${step.step_id}-${Date.now()}`,
|
|
352
353
|
role: step.role,
|
|
353
|
-
status: "
|
|
354
|
-
|
|
354
|
+
status: "blocked",
|
|
355
|
+
reason_code: "no_model_runtime",
|
|
356
|
+
summary: `Step blocked — no model runtime available for ${step.step_id} (${step.role}): ${step.task}. ${reason} Run \`ace connect\` or \`ace doctor --scan\`, or pass provider/model to resolve.`,
|
|
355
357
|
turns: 0,
|
|
356
358
|
tool_calls: [],
|
|
357
359
|
child_results: [],
|
|
@@ -1369,7 +1371,11 @@ export function registerAgentTools(server) {
|
|
|
1369
1371
|
.string()
|
|
1370
1372
|
.optional()
|
|
1371
1373
|
.describe("Optional workspace root override; defaults to the active workspace"),
|
|
1372
|
-
|
|
1374
|
+
mode: z
|
|
1375
|
+
.enum(["execute", "plan_only"])
|
|
1376
|
+
.optional()
|
|
1377
|
+
.describe("'plan_only' compiles and validates the plan without executing any steps; 'execute' is the default"),
|
|
1378
|
+
}, async ({ task, steps, execution_mode, max_turns_per_step, provider, model, base_url, ollama_url, workspace_root, mode }, extra) => {
|
|
1373
1379
|
let runtime;
|
|
1374
1380
|
const runtimeWarnings = [];
|
|
1375
1381
|
try {
|
|
@@ -1406,6 +1412,32 @@ export function registerAgentTools(server) {
|
|
|
1406
1412
|
const bridge = runtime
|
|
1407
1413
|
? new ModelBridge(createDefaultModelBridgeClients(runtime))
|
|
1408
1414
|
: undefined;
|
|
1415
|
+
// plan_only mode: return the compiled plan without executing anything
|
|
1416
|
+
if (mode === "plan_only" || (!runtime && runtimeWarnings.length > 0)) {
|
|
1417
|
+
const planOnly = mode === "plan_only";
|
|
1418
|
+
const reasonCode = planOnly ? "plan_only_requested" : "no_model_runtime";
|
|
1419
|
+
return {
|
|
1420
|
+
content: [
|
|
1421
|
+
{
|
|
1422
|
+
type: "text",
|
|
1423
|
+
text: JSON.stringify({
|
|
1424
|
+
ok: planOnly,
|
|
1425
|
+
reason_code: reasonCode,
|
|
1426
|
+
execution_backend: "none",
|
|
1427
|
+
mode: planOnly ? "plan_only" : "execute",
|
|
1428
|
+
plan,
|
|
1429
|
+
runtime: runtime ?? null,
|
|
1430
|
+
runtime_warnings: runtimeWarnings,
|
|
1431
|
+
remediation: !runtime
|
|
1432
|
+
? "Run `ace connect` or `ace doctor --scan`, or pass provider/model. Use mode:\"plan_only\" explicitly to compile a plan without a runtime."
|
|
1433
|
+
: undefined,
|
|
1434
|
+
acceptance_trace_map: traceArtifactPath,
|
|
1435
|
+
plan_source: planSource,
|
|
1436
|
+
}, null, 2),
|
|
1437
|
+
},
|
|
1438
|
+
],
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1409
1441
|
const fallbackHandoffPrefix = `LOCAL-${plan.plan_id}-`;
|
|
1410
1442
|
const vericifyWarnings = [];
|
|
1411
1443
|
const appendSessionPlanTransition = async (stepId, from, to, reason, reasonCode) => {
|
|
@@ -1426,6 +1458,22 @@ export function registerAgentTools(server) {
|
|
|
1426
1458
|
evidence_refs: step.tool_scope ?? [],
|
|
1427
1459
|
});
|
|
1428
1460
|
}).catch(() => undefined);
|
|
1461
|
+
// WS-C item 5: emit a PLAN_UPDATE payload on the existing status-event pipe so
|
|
1462
|
+
// consumers can follow "exactly one item in_progress" without a new transport.
|
|
1463
|
+
const inProgressStep = plan.steps.find((candidate) => candidate.status === "running");
|
|
1464
|
+
await executeAceInternalTool("emit_status_event", {
|
|
1465
|
+
source_module: "capability-ops",
|
|
1466
|
+
event_type: "PLAN_UPDATE",
|
|
1467
|
+
status: to === "done" ? "done" : to === "blocked" || to === "failed" ? "blocked" : "in_progress",
|
|
1468
|
+
summary: `Plan ${plan.plan_id} step ${stepId}: ${from} -> ${to}`,
|
|
1469
|
+
payload_json: JSON.stringify({
|
|
1470
|
+
plan_id: plan.plan_id,
|
|
1471
|
+
step_id: stepId,
|
|
1472
|
+
from,
|
|
1473
|
+
to,
|
|
1474
|
+
in_progress_step_id: inProgressStep?.step_id ?? null,
|
|
1475
|
+
}),
|
|
1476
|
+
}, sessionId).catch(() => undefined);
|
|
1429
1477
|
};
|
|
1430
1478
|
const supervised = await superviseTaskPlan(plan, {
|
|
1431
1479
|
async spawnStep(step) {
|
|
@@ -1439,6 +1487,8 @@ export function registerAgentTools(server) {
|
|
|
1439
1487
|
provider: runtime.provider,
|
|
1440
1488
|
model: runtime.model,
|
|
1441
1489
|
toolScope: step.tool_scope,
|
|
1490
|
+
expectedArtifacts: step.expected_artifacts,
|
|
1491
|
+
requiredEvidenceRefs: step.required_evidence_refs,
|
|
1442
1492
|
});
|
|
1443
1493
|
}
|
|
1444
1494
|
const reason = runtimeWarnings[0] ??
|
|
@@ -2287,31 +2337,45 @@ export function registerAgentTools(server) {
|
|
|
2287
2337
|
};
|
|
2288
2338
|
});
|
|
2289
2339
|
// ── Lessons ───────────────────────────────────────────────────────
|
|
2290
|
-
server.tool("get_lessons", "Read all accumulated lessons and guardrails", {}, async () =>
|
|
2291
|
-
|
|
2292
|
-
|
|
2340
|
+
server.tool("get_lessons", "Read all accumulated playbook lessons and guardrails", {}, async () => {
|
|
2341
|
+
const root = resolveWorkspaceRoot();
|
|
2342
|
+
const repo = new PlaybookRepository(root);
|
|
2343
|
+
const path = materializePlaybookMarkdown(root);
|
|
2344
|
+
return {
|
|
2345
|
+
content: [{ type: "text", text: `${renderPlaybookMarkdown(repo)}\nProjection: ${path}` }],
|
|
2346
|
+
};
|
|
2347
|
+
});
|
|
2293
2348
|
server.tool("add_lesson", "Record a new lesson/guardrail after a correction or failure", {
|
|
2294
2349
|
context: z.string().describe("What happened"),
|
|
2295
2350
|
failure_pattern: z.string().describe("What went wrong"),
|
|
2296
2351
|
guardrail: z.string().describe("Rule to prevent recurrence"),
|
|
2297
2352
|
verification_check: z.string().describe("How to verify compliance"),
|
|
2298
2353
|
}, async ({ context, failure_pattern, guardrail, verification_check }) => {
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
"",
|
|
2302
|
-
|
|
2303
|
-
"",
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2354
|
+
const knownReasonCodes = [
|
|
2355
|
+
"false_completion_no_evidence",
|
|
2356
|
+
"forward_pacing_incomplete",
|
|
2357
|
+
"bridge_tool_doom_loop",
|
|
2358
|
+
"bridge_text_doom_loop",
|
|
2359
|
+
"context_overflow",
|
|
2360
|
+
"tool_timeout",
|
|
2361
|
+
"role_contract_violation",
|
|
2362
|
+
];
|
|
2363
|
+
const scope = {};
|
|
2364
|
+
const lowered = failure_pattern.toLowerCase();
|
|
2365
|
+
const reason = knownReasonCodes.find((code) => lowered.includes(code));
|
|
2366
|
+
if (reason)
|
|
2367
|
+
scope.reason_code = reason;
|
|
2368
|
+
const root = resolveWorkspaceRoot();
|
|
2369
|
+
const bullet = new PlaybookRepository(root).createBullet({
|
|
2370
|
+
section: "failure_modes",
|
|
2371
|
+
scope,
|
|
2372
|
+
content: `${guardrail} Verify: ${verification_check}`,
|
|
2373
|
+
evidence_refs: [],
|
|
2374
|
+
provenance: { source: "operator" },
|
|
2375
|
+
});
|
|
2376
|
+
const path = materializePlaybookMarkdown(root);
|
|
2313
2377
|
return {
|
|
2314
|
-
content: [{ type: "text", text: `✅ Lesson recorded in ${path}` }],
|
|
2378
|
+
content: [{ type: "text", text: `✅ Lesson recorded as ${bullet.bullet_id} (${bullet.status}) in ${path}. Context: ${context}` }],
|
|
2315
2379
|
};
|
|
2316
2380
|
});
|
|
2317
2381
|
// ── CLI Work Split ────────────────────────────────────────────────
|
package/dist/tools-discovery.js
CHANGED
|
@@ -217,7 +217,7 @@ export function registerDiscoveryTools(server) {
|
|
|
217
217
|
},
|
|
218
218
|
],
|
|
219
219
|
}));
|
|
220
|
-
server.tool("mcp_ingestion_probe", "
|
|
220
|
+
server.tool("mcp_ingestion_probe", "Connect to an external MCP server with a bounded initialize handshake, list tools/resources/templates/prompts, and persist the probe artifacts", {
|
|
221
221
|
name: z.string().min(1).describe("Logical MCP server name"),
|
|
222
222
|
transport: z.enum(["stdio", "http"]).describe("MCP transport class"),
|
|
223
223
|
command: z.string().optional().describe("Command for stdio MCP servers"),
|
|
@@ -231,8 +231,9 @@ export function registerDiscoveryTools(server) {
|
|
|
231
231
|
input_schema: z.unknown().optional(),
|
|
232
232
|
}))
|
|
233
233
|
.optional()
|
|
234
|
-
.describe("
|
|
235
|
-
|
|
234
|
+
.describe("Deprecated compatibility input; live server catalogs are authoritative"),
|
|
235
|
+
timeout_ms: z.number().int().min(250).max(60000).optional().describe("Bound for connect and each catalog request (default 10000ms)"),
|
|
236
|
+
}, async ({ name, transport, command, args, url, env, declared_tools, timeout_ms }) => ({
|
|
236
237
|
content: [
|
|
237
238
|
{
|
|
238
239
|
type: "text",
|
|
@@ -244,6 +245,7 @@ export function registerDiscoveryTools(server) {
|
|
|
244
245
|
url,
|
|
245
246
|
env,
|
|
246
247
|
declared_tools,
|
|
248
|
+
timeout_ms,
|
|
247
249
|
}), null, 2),
|
|
248
250
|
},
|
|
249
251
|
],
|
package/dist/tools-files.js
CHANGED
|
@@ -6,7 +6,7 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { ACE_TASKS_ROOT_REL, normalizePathForValidation, safeRead, safeWriteAsync, safeWrite, resolveWorkspaceWritePath, resolveStoreFallbackKeysForPath, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
|
|
9
|
+
import { ACE_TASKS_ROOT_REL, normalizePathForValidation, isReadError, safeRead, safeWriteAsync, safeWrite, resolveWorkspaceWritePath, resolveStoreFallbackKeysForPath, wsPath, WORKSPACE_ROOT, } from "./helpers.js";
|
|
10
10
|
import { isInside, looksLikeSwarmHandoffPath, normalizeRelPath } from "./shared.js";
|
|
11
11
|
import { validateAgentStateHandoffPayload, validateArtifactManifestPayload, validateProvenanceLogContent, validateStatusEventsNdjsonContent, validateSwarmHandoffPayload, validateTealConfigContent, validateRuntimeExecutorSessionRegistryPayload, validateRuntimeToolSpecRegistryPayload, validateTrackerSnapshotPayload, validateVericifyBridgeSnapshotPayload, validateVericifyProcessPostLogPayload, validateWorkspaceSessionRegistryPayload, lintHandoffPayload, } from "./schemas.js";
|
|
12
12
|
import { syncTodoStateSafe } from "./todo-state.js";
|
|
@@ -17,6 +17,7 @@ import { safeEditFile, diffContents, applyPatch } from "./safe-edit.js";
|
|
|
17
17
|
import { detectAstgrepCommand, flattenMetaVariables, locateAstgrepMatches, runAstgrepQuery, } from "./astgrep-index.js";
|
|
18
18
|
import { readRuntimeProfile, resolveEffectiveSurgicalReadBudget, validateRuntimeProfileContent, } from "./runtime-profile.js";
|
|
19
19
|
import { withLocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
|
|
20
|
+
import { listStoreKeysSync } from "./store/store-snapshot.js";
|
|
20
21
|
function reasonCodeFromError(error) {
|
|
21
22
|
return typeof error === "object" && error !== null && "reason_code" in error
|
|
22
23
|
? String(error.reason_code)
|
|
@@ -129,13 +130,22 @@ async function storeLocateArtifact(input, result) {
|
|
|
129
130
|
});
|
|
130
131
|
}
|
|
131
132
|
function listStructuralEditArtifacts(prefix) {
|
|
133
|
+
const relPaths = new Set();
|
|
134
|
+
const storePrefix = `state/artifacts/${STRUCTURAL_EDIT_ARTIFACTS_REL}/${prefix}-`;
|
|
135
|
+
for (const key of listStoreKeysSync(WORKSPACE_ROOT, storePrefix)) {
|
|
136
|
+
if (key.endsWith(".json")) {
|
|
137
|
+
relPaths.add(key.slice("state/artifacts/".length));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
132
140
|
const dir = wsPath(STRUCTURAL_EDIT_ARTIFACTS_REL);
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
141
|
+
if (existsSync(dir)) {
|
|
142
|
+
for (const name of readdirSync(dir)) {
|
|
143
|
+
if (name.startsWith(`${prefix}-`) && name.endsWith(".json")) {
|
|
144
|
+
relPaths.add(normalizeRelPath(relative(WORKSPACE_ROOT, resolve(dir, name))));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [...relPaths].sort((a, b) => b.localeCompare(a));
|
|
139
149
|
}
|
|
140
150
|
function findStoredMatch(matchId) {
|
|
141
151
|
for (const relPath of listStructuralEditArtifacts("locate")) {
|
|
@@ -493,6 +503,7 @@ async function buildStructuralPreview(plan) {
|
|
|
493
503
|
: diffProcess.status === 0
|
|
494
504
|
? ""
|
|
495
505
|
: diff.diff_summary;
|
|
506
|
+
const hasUnifiedDiff = diffProcess.status === 1;
|
|
496
507
|
return {
|
|
497
508
|
artifact: await finalize({
|
|
498
509
|
ok: true,
|
|
@@ -505,7 +516,7 @@ async function buildStructuralPreview(plan) {
|
|
|
505
516
|
changed_ranges: parseChangedRanges(diffText),
|
|
506
517
|
diff_summary: diff.diff_summary,
|
|
507
518
|
diff_preview: diffText ? previewDiffText(diffText) : diff.diff_summary,
|
|
508
|
-
promotable:
|
|
519
|
+
promotable: hasUnifiedDiff,
|
|
509
520
|
staging_path: stagingDir,
|
|
510
521
|
}),
|
|
511
522
|
rewritten_content: rewrittenContent,
|
|
@@ -1134,10 +1145,15 @@ export function registerFileTools(server) {
|
|
|
1134
1145
|
.boolean()
|
|
1135
1146
|
.optional()
|
|
1136
1147
|
.describe("Keep .ace-backup copy (default: true)"),
|
|
1137
|
-
|
|
1148
|
+
expected_base_hash: z
|
|
1149
|
+
.string()
|
|
1150
|
+
.optional()
|
|
1151
|
+
.describe("Optional source precondition checked immediately before promotion; prefer full sha256:<hex>"),
|
|
1152
|
+
}, async ({ path, content, validation_command, test_command, backup, expected_base_hash }) => {
|
|
1138
1153
|
const result = safeEditFile({
|
|
1139
1154
|
path,
|
|
1140
1155
|
content,
|
|
1156
|
+
expected_base_hash,
|
|
1141
1157
|
validation_command,
|
|
1142
1158
|
test_command,
|
|
1143
1159
|
backup,
|
|
@@ -1154,6 +1170,9 @@ export function registerFileTools(server) {
|
|
|
1154
1170
|
original_hash: result.original_hash,
|
|
1155
1171
|
new_hash: result.new_hash,
|
|
1156
1172
|
restored: result.restored,
|
|
1173
|
+
reason_code: result.reason_code,
|
|
1174
|
+
expected_hash: result.expected_hash,
|
|
1175
|
+
observed_hash: result.observed_hash,
|
|
1157
1176
|
validation_passed: result.validation_passed,
|
|
1158
1177
|
test_passed: result.test_passed,
|
|
1159
1178
|
},
|
|
@@ -1215,19 +1234,19 @@ export function registerFileTools(server) {
|
|
|
1215
1234
|
};
|
|
1216
1235
|
});
|
|
1217
1236
|
// ── Diff (new P2 tool) ────────────────────────────────────────────
|
|
1218
|
-
server.tool("diff_files", "
|
|
1237
|
+
server.tool("diff_files", "Produce a sequence-aware unified diff between two contents or two workspace paths", {
|
|
1219
1238
|
original: z.string().describe("Original content or workspace-relative file path"),
|
|
1220
1239
|
modified: z.string().describe("Modified content or workspace-relative file path"),
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
.optional()
|
|
1224
|
-
.describe("If true, treat original/modified as file paths and read their contents"),
|
|
1225
|
-
}, async ({ original, modified, is_path }) => {
|
|
1240
|
+
mode: z.enum(["content", "path"]).default("content").describe("Explicitly interpret both inputs as literal content or workspace-relative paths"),
|
|
1241
|
+
}, async ({ original, modified, mode }) => {
|
|
1226
1242
|
let origContent = original;
|
|
1227
1243
|
let modContent = modified;
|
|
1228
|
-
if (
|
|
1244
|
+
if (mode === "path") {
|
|
1229
1245
|
origContent = safeRead(original);
|
|
1230
1246
|
modContent = safeRead(modified);
|
|
1247
|
+
if (isReadError(origContent) || isReadError(modContent)) {
|
|
1248
|
+
throw new Error(`Unable to read diff path: ${isReadError(origContent) ? origContent : modContent}`);
|
|
1249
|
+
}
|
|
1231
1250
|
}
|
|
1232
1251
|
const diff = diffContents(origContent, modContent);
|
|
1233
1252
|
return {
|
|
@@ -1660,6 +1679,7 @@ export function registerFileTools(server) {
|
|
|
1660
1679
|
const safeResult = safeEditFile({
|
|
1661
1680
|
path: compiledPlan.target.file,
|
|
1662
1681
|
content: preview.rewritten_content,
|
|
1682
|
+
expected_base_hash: compiledPlan.target.file_hash,
|
|
1663
1683
|
validation_command: validation_command ?? compiledPlan.validation_command,
|
|
1664
1684
|
test_command: test_command ?? compiledPlan.test_command,
|
|
1665
1685
|
});
|
|
@@ -1707,10 +1727,11 @@ export function registerFileTools(server) {
|
|
|
1707
1727
|
server.tool("apply_patch", "Apply a unified diff patch to a workspace file. Applies the patch to staged content, runs optional validation/tests, and promotes to the target only on success. Cost: moderate.", {
|
|
1708
1728
|
path: z.string().describe("Workspace-relative file path to patch"),
|
|
1709
1729
|
patch: z.string().describe("Unified diff patch (output of diff -u or similar)"),
|
|
1730
|
+
expected_base_hash: z.string().optional().describe("Optional source precondition checked immediately before promotion; prefer full sha256:<hex>"),
|
|
1710
1731
|
validation_command: z.string().optional().describe("Shell command to validate after applying (e.g. 'npx tsc --noEmit')"),
|
|
1711
1732
|
test_command: z.string().optional().describe("Shell command to run tests after applying"),
|
|
1712
|
-
}, async ({ path: relPath, patch, validation_command, test_command }) => {
|
|
1713
|
-
const result = applyPatch({ path: relPath, patch, validation_command, test_command });
|
|
1733
|
+
}, async ({ path: relPath, patch, expected_base_hash, validation_command, test_command }) => {
|
|
1734
|
+
const result = applyPatch({ path: relPath, patch, expected_base_hash, validation_command, test_command });
|
|
1714
1735
|
if (!result.ok) {
|
|
1715
1736
|
return {
|
|
1716
1737
|
content: [{ type: "text", text: `apply_patch failed: ${result.error ?? "unknown error"}\n${result.validation_output ?? ""}` }],
|
package/dist/tools-framework.js
CHANGED
|
@@ -18,6 +18,8 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
18
18
|
import { resolve } from "node:path";
|
|
19
19
|
import { auditPublicSurface } from "./public-surface.js";
|
|
20
20
|
import { auditStoreAuthority, writeStoreAuthorityAuditReport, } from "./store/store-authority-audit.js";
|
|
21
|
+
import { openStore } from "./store/ace-packed-store.js";
|
|
22
|
+
import { getWorkspaceStorePath } from "./store/store-snapshot.js";
|
|
21
23
|
import { parseGateManifest, resolveGateSelection, } from "./gate-contract.js";
|
|
22
24
|
import { PROVENANCE_CRITICAL_EVENT_TYPES, validateArtifactManifestPayload, validateProvenanceLogContent, validateTealConfigContent, } from "./schemas.js";
|
|
23
25
|
import { readAceTaskContractAssessment } from "./ace-autonomy.js";
|
|
@@ -1199,6 +1201,27 @@ export function registerFrameworkTools(server) {
|
|
|
1199
1201
|
ok: isGitRepo(),
|
|
1200
1202
|
detail: isGitRepo() ? "initialized" : "NOT a git repo",
|
|
1201
1203
|
});
|
|
1204
|
+
// Check: malformed store events were not silently dropped.
|
|
1205
|
+
const storePath = getWorkspaceStorePath(resolveWorkspaceRoot());
|
|
1206
|
+
if (existsSync(storePath)) {
|
|
1207
|
+
try {
|
|
1208
|
+
const store = await openStore(storePath, { readOnly: true });
|
|
1209
|
+
const skipped = store.skippedMalformedEventCount;
|
|
1210
|
+
await store.close();
|
|
1211
|
+
checks.push({
|
|
1212
|
+
name: "acepack:skipped-malformed-events",
|
|
1213
|
+
ok: skipped === 0,
|
|
1214
|
+
detail: skipped === 0 ? "0 skipped malformed events" : `${skipped} skipped malformed events`,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
checks.push({
|
|
1219
|
+
name: "acepack:skipped-malformed-events",
|
|
1220
|
+
ok: false,
|
|
1221
|
+
detail: `store validation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1202
1225
|
// ── Deep checks (P2 hardening) ─────────────────────────────────
|
|
1203
1226
|
// Check: module registry role coverage vs event emitters
|
|
1204
1227
|
const registryRaw = safeRead("agent-state/MODULES/registry.json");
|
|
@@ -1368,7 +1391,9 @@ export function registerFrameworkTools(server) {
|
|
|
1368
1391
|
result.ok
|
|
1369
1392
|
? `✅ Public surface audit passed (${result.summary.tools} tools, ${result.summary.resources} resources, ${result.summary.prompts} prompts)`
|
|
1370
1393
|
: `❌ Public surface audit failed (${result.failures.length} failure${result.failures.length === 1 ? "" : "s"})`,
|
|
1371
|
-
`
|
|
1394
|
+
`Artifact: ${result.artifact_path}`,
|
|
1395
|
+
`Read: ace read ${result.artifact_path}`,
|
|
1396
|
+
...(result.written_path ? [`Store path: ${result.written_path}`] : []),
|
|
1372
1397
|
`Events: ${result.summary.emitted_events}`,
|
|
1373
1398
|
...(result.failures.slice(0, 12).map((entry) => `- ${entry}`)),
|
|
1374
1399
|
...(result.warnings.slice(0, 12).map((entry) => `- warning: ${entry}`)),
|
|
@@ -1389,8 +1414,9 @@ export function registerFrameworkTools(server) {
|
|
|
1389
1414
|
}, async ({ write_artifact, artifact_path }) => {
|
|
1390
1415
|
const result = auditStoreAuthority();
|
|
1391
1416
|
const reportPath = artifact_path ?? result.report_path;
|
|
1417
|
+
let writtenPath;
|
|
1392
1418
|
if (write_artifact !== false) {
|
|
1393
|
-
writeStoreAuthorityAuditReport({
|
|
1419
|
+
writtenPath = writeStoreAuthorityAuditReport({
|
|
1394
1420
|
...result,
|
|
1395
1421
|
report_path: reportPath,
|
|
1396
1422
|
}, reportPath);
|
|
@@ -1401,7 +1427,9 @@ export function registerFrameworkTools(server) {
|
|
|
1401
1427
|
type: "text",
|
|
1402
1428
|
text: [
|
|
1403
1429
|
`✅ Store authority audit completed (${result.total_safe_write_sites} safeWrite users)`,
|
|
1404
|
-
`
|
|
1430
|
+
`Artifact: ${reportPath}`,
|
|
1431
|
+
`Read: ace read ${reportPath}`,
|
|
1432
|
+
...(writtenPath ? [`Store path: ${writtenPath}`] : []),
|
|
1405
1433
|
`Bypassing ACEPACK: ${result.bypassing_sites.length}`,
|
|
1406
1434
|
`Runtime files without fallback keys: ${result.runtime_files_without_store_fallback_keys.length}`,
|
|
1407
1435
|
`Non-regenerable projections: ${result.non_regenerable_projections.length}`,
|