@voybio/ace-swarm 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  5. package/assets/agent-state/runtime-tool-specs.json +70 -2
  6. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  7. package/assets/instructions/ACE_UI.instructions.md +11 -0
  8. package/dist/ace-context.js +70 -11
  9. package/dist/ace-internal-tools.d.ts +3 -1
  10. package/dist/ace-internal-tools.js +10 -2
  11. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  12. package/dist/agent-runtime/role-adapters.js +49 -5
  13. package/dist/astgrep-index.d.ts +48 -0
  14. package/dist/astgrep-index.js +126 -1
  15. package/dist/cli.js +205 -15
  16. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  17. package/dist/discovery-runtime-wrappers.js +615 -0
  18. package/dist/helpers/bootstrap.js +1 -1
  19. package/dist/helpers/constants.d.ts +2 -2
  20. package/dist/helpers/constants.js +7 -0
  21. package/dist/helpers/path-utils.d.ts +8 -1
  22. package/dist/helpers/path-utils.js +27 -8
  23. package/dist/helpers/store-resolution.js +7 -3
  24. package/dist/job-scheduler.js +30 -4
  25. package/dist/json-sanitizer.d.ts +16 -0
  26. package/dist/json-sanitizer.js +26 -0
  27. package/dist/local-model-policy.d.ts +27 -0
  28. package/dist/local-model-policy.js +84 -0
  29. package/dist/local-model-runtime.d.ts +6 -0
  30. package/dist/local-model-runtime.js +21 -20
  31. package/dist/model-bridge.d.ts +6 -1
  32. package/dist/model-bridge.js +338 -21
  33. package/dist/orchestrator-supervisor.d.ts +42 -0
  34. package/dist/orchestrator-supervisor.js +110 -3
  35. package/dist/plan-proposal.d.ts +115 -0
  36. package/dist/plan-proposal.js +1073 -0
  37. package/dist/runtime-executor.d.ts +6 -1
  38. package/dist/runtime-executor.js +72 -5
  39. package/dist/runtime-tool-specs.d.ts +19 -1
  40. package/dist/runtime-tool-specs.js +67 -26
  41. package/dist/schemas.js +29 -1
  42. package/dist/server.js +51 -0
  43. package/dist/shared.d.ts +1 -0
  44. package/dist/shared.js +2 -0
  45. package/dist/store/bootstrap-store.d.ts +1 -0
  46. package/dist/store/bootstrap-store.js +8 -2
  47. package/dist/store/repositories/local-model-runtime-repository.d.ts +1 -1
  48. package/dist/store/repositories/local-model-runtime-repository.js +1 -1
  49. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  50. package/dist/tools-agent.d.ts +20 -0
  51. package/dist/tools-agent.js +538 -28
  52. package/dist/tools-discovery.js +135 -0
  53. package/dist/tools-files.js +768 -66
  54. package/dist/tools-framework.js +80 -61
  55. package/dist/tui/index.js +10 -1
  56. package/dist/tui/ollama.d.ts +8 -1
  57. package/dist/tui/ollama.js +53 -12
  58. package/dist/tui/openai-compatible.d.ts +13 -0
  59. package/dist/tui/openai-compatible.js +305 -5
  60. package/dist/tui/provider-discovery.d.ts +1 -0
  61. package/dist/tui/provider-discovery.js +35 -11
  62. package/dist/vericify-bridge.d.ts +1 -1
  63. package/package.json +1 -1
@@ -66,6 +66,8 @@ export interface UnattendedSessionRecord {
66
66
  workspace_cleanup_status: "pending" | "removed" | "archived" | "failed";
67
67
  output_policy?: TurnOutputPolicy;
68
68
  turns: UnattendedTurnRecord[];
69
+ /** Set after propose_plan + validate_plan succeed in the before_run preflight. */
70
+ validated_plan_id?: string;
69
71
  }
70
72
  export interface UnattendedSessionRegistry {
71
73
  version: 1;
@@ -108,11 +110,14 @@ export interface WaitForUnattendedSessionResult {
108
110
  session?: UnattendedSessionRecord;
109
111
  error?: string;
110
112
  }
113
+ export interface WaitForUnattendedSessionOptions {
114
+ flush_sidecars?: boolean;
115
+ }
111
116
  export declare function validateRuntimeExecutorSessionRegistryContent(raw: string): ValidationResult;
112
117
  export declare function listUnattendedSessions(): UnattendedSessionRegistry;
113
118
  export declare function getUnattendedSession(sessionId: string): UnattendedSessionRecord | undefined;
114
119
  export declare function getRuntimeExecutorSessionRegistryPath(): string;
115
120
  export declare function startUnattendedSession(input: StartUnattendedSessionInput): Promise<StartUnattendedSessionResult>;
116
- export declare function waitForUnattendedSession(sessionId: string, timeoutMs?: number): Promise<WaitForUnattendedSessionResult>;
121
+ export declare function waitForUnattendedSession(sessionId: string, timeoutMsOrOptions?: number | WaitForUnattendedSessionOptions, options?: WaitForUnattendedSessionOptions): Promise<WaitForUnattendedSessionResult>;
117
122
  export declare function stopUnattendedSession(sessionId: string): Promise<StopUnattendedSessionResult>;
118
123
  //# sourceMappingURL=runtime-executor.d.ts.map
@@ -7,6 +7,7 @@ import { resolveWorkspaceArtifactPath as resolveWorkspaceArtifactPathHelper, res
7
7
  import { appendRunLedgerEntrySafe } from "./run-ledger.js";
8
8
  import { runShellCommand } from "./runtime-command.js";
9
9
  import { loadRuntimeProfile, resolveEffectiveSurgicalReadBudget, renderRuntimePromptFromSnapshot, getLivenessDefaults, } from "./runtime-profile.js";
10
+ import { resolveLocalModelExecutionPolicy } from "./local-model-policy.js";
10
11
  import { renderAceContinuityPromptBlock, renderAceRecallPromptBlock, renderAceSnapshotPromptBlock, } from "./ace-context.js";
11
12
  import { executeRuntimeTool, buildFilteredToolCatalog, SMALL_LOCAL_PRIORITY_TOOL_NAMES, } from "./runtime-tool-specs.js";
12
13
  import { validateRuntimeExecutorSessionRegistryPayload, } from "./schemas.js";
@@ -19,6 +20,7 @@ import { getWorkspaceStorePath, storeExistsSync, } from "./store/store-snapshot.
19
20
  import { operationalArtifactVirtualPath } from "./store/store-artifacts.js";
20
21
  import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
21
22
  import { LocalModelRuntimeRepository, withLocalModelRuntimeRepository, } from "./store/repositories/local-model-runtime-repository.js";
23
+ import { proposePlanDeterministic, validatePlan } from "./plan-proposal.js";
22
24
  export const RUNTIME_EXECUTOR_SESSION_REGISTRY_REL_PATH = "agent-state/runtime-executor-sessions.json";
23
25
  export const RUNTIME_EXECUTOR_SESSION_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json";
24
26
  export const RUNTIME_EXECUTOR_SESSION_SCHEMA_NAME = "runtime-executor-session-registry@1.0.0";
@@ -93,6 +95,20 @@ async function waitForSessionSidecars(sessionId) {
93
95
  return;
94
96
  await state.chain.catch(() => undefined);
95
97
  }
98
+ async function delaySessionCompletedSidecarIfRequested(sessionId) {
99
+ const rawDelayMs = process.env.ACE_RUNTIME_EXECUTOR_TEST_SIDECAR_DELAY_MS;
100
+ if (!rawDelayMs)
101
+ return;
102
+ const delayMs = Number(rawDelayMs);
103
+ if (!Number.isFinite(delayMs) || delayMs <= 0)
104
+ return;
105
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
106
+ const markerDir = process.env.ACE_RUNTIME_EXECUTOR_TEST_SIDECAR_MARKER_DIR;
107
+ if (markerDir && markerDir.trim().length > 0) {
108
+ mkdirSync(markerDir, { recursive: true });
109
+ writeFileSync(join(markerDir, `${sessionId}.done`), new Date().toISOString(), "utf-8");
110
+ }
111
+ }
96
112
  function setSessionPhase(sessionId, phase, checkpoint) {
97
113
  const active = activeSessions.get(sessionId);
98
114
  if (active) {
@@ -567,6 +583,31 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
567
583
  }
568
584
  return;
569
585
  }
586
+ // ── Plan validation preflight (Task 6) ───────────────────────────────
587
+ {
588
+ const currentValidatedPlanId = registryStart.validated_plan_id;
589
+ if (!currentValidatedPlanId) {
590
+ const task = registryStart.current_task || registryStart.task;
591
+ try {
592
+ const proposal = proposePlanDeterministic(task);
593
+ const verdict = await validatePlan({ proposal, sessionId });
594
+ if (verdict.ok) {
595
+ // Persist validated_plan_id opportunistically. This is a readiness hint,
596
+ // not a hard gate on unattended execution.
597
+ await mutateRegistry((registry) => {
598
+ const session = findSession(registry, sessionId);
599
+ if (session)
600
+ session.validated_plan_id = verdict.plan_id;
601
+ }).catch(() => undefined);
602
+ }
603
+ }
604
+ catch {
605
+ // Preflight validation is best-effort. Unattended runtime should still run
606
+ // even when the planner/model path or trace lookup is unavailable.
607
+ }
608
+ }
609
+ }
610
+ // ── End plan validation preflight ────────────────────────────────────
570
611
  setSessionPhase(sessionId, "workspace_before_run");
571
612
  const beforeRun = await runWorkspaceSessionHook({
572
613
  session_id: registryStart.workspace_session_id,
@@ -592,7 +633,14 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
592
633
  const runtimeStatus = storeExistsSync(workspaceRoot())
593
634
  ? await withLocalModelRuntimeRepository(workspaceRoot(), async (repo) => repo.getRuntimeStatus(sessionId))
594
635
  : undefined;
595
- const effectiveModelClass = runtimeStatus?.model_class ?? "frontier";
636
+ const executionPolicy = resolveLocalModelExecutionPolicy({
637
+ provider: "",
638
+ model: "",
639
+ role: "orchestrator",
640
+ task: currentTask,
641
+ requested_model_class: runtimeStatus?.model_class,
642
+ });
643
+ const effectiveModelClass = executionPolicy.model_class;
596
644
  // Stall restart state — session-level, reset on successful turn.
597
645
  // Seeded runtime status is authoritative when present.
598
646
  let consecutiveStallRestarts = runtimeStatus?.stall_restart_count ?? 0;
@@ -681,7 +729,7 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
681
729
  const taskWithStallHint = isStallRestart
682
730
  ? `${stallRestartContext}\n\nOriginal task: ${currentTask}`
683
731
  : currentTask;
684
- const prompt = renderRuntimePromptFromSnapshot(runtimeProfileSnapshot, {
732
+ let prompt = renderRuntimePromptFromSnapshot(runtimeProfileSnapshot, {
685
733
  task: taskWithStallHint,
686
734
  session_id: sessionId,
687
735
  turn_number: turnNumber,
@@ -695,6 +743,16 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
695
743
  ace_continuity_packet_md: renderAceContinuityPromptBlock(turnContinuity),
696
744
  ace_context_snapshot_md: renderAceSnapshotPromptBlock(turnRecall),
697
745
  });
746
+ if (effectiveModelClass === "small_local") {
747
+ prompt += [
748
+ "",
749
+ "## ACE-Owned Preflight Packet",
750
+ `- status: ${preflight.ok ? "passed" : "blocked"}`,
751
+ `- summary: ${preflight.summary}`,
752
+ `- evidence_refs: agent-state/TASK.md, agent-state/STATUS.md, agent-state/QUALITY_GATES.md`,
753
+ "- model_visible_tools: route_task or run_orchestrator only when delegation is needed; ACE owns recall_context, validate_framework, and get_framework_status.",
754
+ ].join("\n");
755
+ }
698
756
  const requestPayload = {
699
757
  session_id: sessionId,
700
758
  turn_number: turnNumber,
@@ -1157,6 +1215,7 @@ async function runSessionLoop(sessionId, context, autoCleanup, runtimeProfileSna
1157
1215
  // Critical: finalize session status immediately so waitForUnattendedSession resolves correctly.
1158
1216
  const completed = await finalizeSession(sessionId, "completed", normalizedSummary);
1159
1217
  scheduleRuntimeSidecar(sessionId, "session-completed-events", async () => {
1218
+ await delaySessionCompletedSidecarIfRequested(sessionId);
1160
1219
  await emitExecutorEvent("RUNTIME_EXECUTOR_SESSION_COMPLETED", "done", capturedNormalizedSummary, {
1161
1220
  session_id: sessionId,
1162
1221
  turn_number: capturedTurnNumber,
@@ -1398,8 +1457,12 @@ export async function startUnattendedSession(input) {
1398
1457
  workspace: workspace.session,
1399
1458
  };
1400
1459
  }
1401
- export async function waitForUnattendedSession(sessionId, timeoutMs = 30_000) {
1460
+ export async function waitForUnattendedSession(sessionId, timeoutMsOrOptions = 30_000, options) {
1402
1461
  const registryPathValue = registryPath();
1462
+ const timeoutMs = typeof timeoutMsOrOptions === "number" ? timeoutMsOrOptions : 30_000;
1463
+ const flushSidecars = typeof timeoutMsOrOptions === "number"
1464
+ ? options?.flush_sidecars !== false
1465
+ : timeoutMsOrOptions.flush_sidecars !== false;
1403
1466
  const active = activeSessions.get(sessionId);
1404
1467
  if (!active?.completion) {
1405
1468
  const session = getUnattendedSession(sessionId);
@@ -1412,7 +1475,9 @@ export async function waitForUnattendedSession(sessionId, timeoutMs = 30_000) {
1412
1475
  };
1413
1476
  }
1414
1477
  if (terminalStatus(session.status)) {
1415
- await waitForSessionSidecars(sessionId);
1478
+ if (flushSidecars) {
1479
+ await waitForSessionSidecars(sessionId);
1480
+ }
1416
1481
  const freshSession = getUnattendedSession(sessionId);
1417
1482
  return {
1418
1483
  ok: terminalStatus(freshSession?.status ?? session.status),
@@ -1447,7 +1512,9 @@ export async function waitForUnattendedSession(sessionId, timeoutMs = 30_000) {
1447
1512
  error: `Timed out waiting for unattended session ${sessionId}${phaseInfo}`,
1448
1513
  };
1449
1514
  }
1450
- await waitForSessionSidecars(sessionId);
1515
+ if (flushSidecars) {
1516
+ await waitForSessionSidecars(sessionId);
1517
+ }
1451
1518
  const completedSession = getUnattendedSession(sessionId);
1452
1519
  return {
1453
1520
  ok: !!completedSession && terminalStatus(completedSession.status),
@@ -22,6 +22,14 @@ export interface RuntimeToolExecutorConfig {
22
22
  timeout_ms?: number;
23
23
  env?: Record<string, string>;
24
24
  }
25
+ export interface RuntimeToolMcpServerConfig {
26
+ transport: "stdio" | "http";
27
+ command?: string;
28
+ args?: string[];
29
+ url?: string;
30
+ env?: Record<string, string>;
31
+ tool_allowlist?: string[];
32
+ }
25
33
  export interface RuntimeToolSpec {
26
34
  name: string;
27
35
  description: string;
@@ -29,6 +37,7 @@ export interface RuntimeToolSpec {
29
37
  success_schema?: RuntimeToolSchema;
30
38
  failure_schema?: RuntimeToolSchema;
31
39
  executor: RuntimeToolExecutorConfig;
40
+ mcp_server?: RuntimeToolMcpServerConfig;
32
41
  }
33
42
  export interface RuntimeToolSpecRegistry {
34
43
  version: 1;
@@ -60,6 +69,15 @@ export interface RuntimeToolExecutionResult {
60
69
  error?: unknown;
61
70
  validation_errors?: string[];
62
71
  }
72
+ export declare function buildRuntimeToolEnvironment(input: {
73
+ base_env?: NodeJS.ProcessEnv;
74
+ spec_env?: Record<string, string>;
75
+ injected_env: Record<string, string>;
76
+ }): NodeJS.ProcessEnv;
77
+ export declare function persistRuntimeToolInvocationArtifacts(callId: string, requestContent: string, responseContent: string): Promise<{
78
+ requestPath: string;
79
+ responsePath: string;
80
+ }>;
63
81
  export declare function validateRuntimeToolRegistryContent(raw: string): ValidationResult;
64
82
  export declare function loadRuntimeToolRegistry(explicitPath?: string): RuntimeToolSpecRegistryResult;
65
83
  export declare function readRuntimeToolRegistry(): RuntimeToolSpecRegistry;
@@ -67,7 +85,7 @@ export declare function listRuntimeToolSpecs(): RuntimeToolSpec[];
67
85
  export declare function getRuntimeToolRegistryPath(): string;
68
86
  export declare function executeRuntimeTool(name: string, input: unknown, context?: RuntimeToolExecutionContext): Promise<RuntimeToolExecutionResult>;
69
87
  export type ModelClass = RuntimeModelClass;
70
- export declare const SMALL_LOCAL_PRIORITY_TOOL_NAMES: readonly ["outline_file", "astgrep_query", "list_workspace", "read_file_lines", "recall_context", "validate_framework", "run_orchestrator"];
88
+ export declare const SMALL_LOCAL_PRIORITY_TOOL_NAMES: readonly ["outline_file", "astgrep_query", "astgrep_locate", "read_file_lines", "compile_structural_edit", "preview_structural_edit", "list_workspace", "recall_context", "validate_framework", "run_orchestrator"];
71
89
  export interface FilteredToolCatalogEntry {
72
90
  name: string;
73
91
  description: string;
@@ -13,6 +13,7 @@ import { openStore } from "./store/ace-packed-store.js";
13
13
  import { getWorkspaceStorePath, readStoreBlobSync, storeExistsSync, toVirtualStorePath, } from "./store/store-snapshot.js";
14
14
  import { operationalArtifactKey, } from "./store/store-artifacts.js";
15
15
  import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
16
+ import { parseJsonLikeText } from "./json-sanitizer.js";
16
17
  export const RUNTIME_TOOL_SPEC_REGISTRY_REL_PATH = "agent-state/runtime-tool-specs.json";
17
18
  export const RUNTIME_TOOL_SPEC_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json";
18
19
  export const RUNTIME_TOOL_SPEC_SCHEMA_NAME = "runtime-tool-spec-registry@1.0.0";
@@ -96,13 +97,46 @@ function buildTempToolPaths(callId) {
96
97
  responsePath: join(tempDir, "response.json"),
97
98
  };
98
99
  }
100
+ const SAFE_RUNTIME_ENV_KEYS = new Set([
101
+ "PATH",
102
+ "HOME",
103
+ "TMPDIR",
104
+ "TEMP",
105
+ "TMP",
106
+ "USER",
107
+ "LANG",
108
+ "LC_ALL",
109
+ "SHELL",
110
+ "NODE_PATH",
111
+ ]);
112
+ function isSecretEnvKey(key) {
113
+ return /(SECRET|TOKEN|KEY|PASSWORD|PASS|CREDENTIAL|AUTH|COOKIE|SESSION)/i.test(key);
114
+ }
115
+ export function buildRuntimeToolEnvironment(input) {
116
+ const base = input.base_env ?? process.env;
117
+ const env = {};
118
+ for (const key of SAFE_RUNTIME_ENV_KEYS) {
119
+ const value = base[key];
120
+ if (typeof value === "string")
121
+ env[key] = value;
122
+ }
123
+ for (const [key, value] of Object.entries(input.spec_env ?? {})) {
124
+ if (isSecretEnvKey(key))
125
+ continue;
126
+ env[key] = value;
127
+ }
128
+ for (const [key, value] of Object.entries(input.injected_env)) {
129
+ env[key] = value;
130
+ }
131
+ return env;
132
+ }
99
133
  function runtimeToolArtifactKey(callId, kind) {
100
134
  return `state/runtime/tools/invocations/${callId}/${kind}.json`;
101
135
  }
102
136
  function runtimeToolArtifactVirtualPath(callId, kind) {
103
137
  return toVirtualStorePath(getWorkspaceStorePath(resolveWorkspaceRoot()), runtimeToolArtifactKey(callId, kind));
104
138
  }
105
- async function persistToolInvocationArtifacts(callId, requestContent, responseContent) {
139
+ export async function persistRuntimeToolInvocationArtifacts(callId, requestContent, responseContent) {
106
140
  const workspaceRoot = resolveWorkspaceRoot();
107
141
  if (!storeExistsSync(workspaceRoot)) {
108
142
  const requestPath = resolveWorkspaceArtifactPath(`.ace-runtime/tools/${callId}.request.json`);
@@ -130,18 +164,18 @@ async function persistToolInvocationArtifacts(callId, requestContent, responseCo
130
164
  };
131
165
  }
132
166
  function parseRuntimeToolRegistry(raw) {
133
- let parsed;
134
- try {
135
- parsed = JSON.parse(raw);
167
+ const parsed = parseJsonLikeText(raw);
168
+ if (!parsed.ok) {
169
+ throw new Error(`Runtime tool registry contains invalid JSON: ${parsed.error}`);
136
170
  }
137
- catch (error) {
138
- throw new Error(`Runtime tool registry contains invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
171
+ if (parsed.sanitized.removed_control_bytes > 0) {
172
+ throw new Error(`Runtime tool registry contains control-byte corruption: control_bytes_removed=${parsed.sanitized.removed_control_bytes}`);
139
173
  }
140
- const validation = validateRuntimeToolSpecRegistryPayload(parsed);
174
+ const validation = validateRuntimeToolSpecRegistryPayload(parsed.value);
141
175
  if (!validation.ok) {
142
176
  throw new Error(`Runtime tool registry failed validation (${validation.schema}): ${validation.errors.join("; ")}`);
143
177
  }
144
- return parsed;
178
+ return parsed.value;
145
179
  }
146
180
  function ensureToolSpec(spec, name) {
147
181
  if (spec)
@@ -258,17 +292,20 @@ export function loadRuntimeToolRegistry(explicitPath) {
258
292
  };
259
293
  }
260
294
  catch (error) {
295
+ const errorMessage = error instanceof Error ? error.message : String(error);
296
+ const controlBytesRemoved = Number(errorMessage.match(/control_bytes_removed=(\d+)/)?.[1] ?? 0);
261
297
  void emitRuntimeToolEvent("RUNTIME_TOOL_REGISTRY_INVALID", `Runtime tool registry validation failed for ${target.path}`, {
262
298
  path: target.path,
263
299
  source: target.source,
264
- errors: [error instanceof Error ? error.message : String(error)],
300
+ ...(controlBytesRemoved > 0 ? { control_bytes_removed: controlBytesRemoved } : {}),
301
+ errors: [errorMessage],
265
302
  });
266
303
  return {
267
304
  ok: false,
268
305
  schema: RUNTIME_TOOL_SPEC_SCHEMA_NAME,
269
306
  path: target.path,
270
307
  source: target.source,
271
- errors: [error instanceof Error ? error.message : String(error)],
308
+ errors: [errorMessage],
272
309
  };
273
310
  }
274
311
  }
@@ -317,7 +354,7 @@ export async function executeRuntimeTool(name, input, context = {}) {
317
354
  },
318
355
  validation_errors: inputErrors,
319
356
  }, null, 2);
320
- ({ requestPath, responsePath } = await persistToolInvocationArtifacts(callId, validationRequest, validationResponse));
357
+ ({ requestPath, responsePath } = await persistRuntimeToolInvocationArtifacts(callId, validationRequest, validationResponse));
321
358
  rmSync(tempPaths.tempDir, { recursive: true, force: true });
322
359
  const result = {
323
360
  ok: false,
@@ -368,16 +405,17 @@ export async function executeRuntimeTool(name, input, context = {}) {
368
405
  : workspacePath;
369
406
  const shellResult = await runShellCommand(spec.executor.command, {
370
407
  cwd: cwdBase,
371
- env: {
372
- ...process.env,
373
- ...(spec.executor.env ?? {}),
374
- ACE_RUNTIME_TOOL_NAME: spec.name,
375
- ACE_RUNTIME_TOOL_REQUEST_FILE: tempPaths.requestPath,
376
- ACE_RUNTIME_TOOL_RESPONSE_FILE: tempPaths.responsePath,
377
- ACE_RUNTIME_SESSION_ID: context.session_id ?? "",
378
- ACE_RUNTIME_TURN: context.turn_number ? String(context.turn_number) : "",
379
- ACE_RUNTIME_WORKSPACE_PATH: workspacePath,
380
- },
408
+ env: buildRuntimeToolEnvironment({
409
+ spec_env: spec.executor.env,
410
+ injected_env: {
411
+ ACE_RUNTIME_TOOL_NAME: spec.name,
412
+ ACE_RUNTIME_TOOL_REQUEST_FILE: tempPaths.requestPath,
413
+ ACE_RUNTIME_TOOL_RESPONSE_FILE: tempPaths.responsePath,
414
+ ACE_RUNTIME_SESSION_ID: context.session_id ?? "",
415
+ ACE_RUNTIME_TURN: context.turn_number ? String(context.turn_number) : "",
416
+ ACE_RUNTIME_WORKSPACE_PATH: workspacePath,
417
+ },
418
+ }),
381
419
  timeout_ms: spec.executor.timeout_ms ?? 60_000,
382
420
  });
383
421
  let responseRaw;
@@ -386,7 +424,7 @@ export async function executeRuntimeTool(name, input, context = {}) {
386
424
  }
387
425
  if (!responseRaw) {
388
426
  const summary = `Runtime tool '${name}' did not produce a response payload`;
389
- ({ requestPath, responsePath } = await persistToolInvocationArtifacts(callId, requestRaw, JSON.stringify({
427
+ ({ requestPath, responsePath } = await persistRuntimeToolInvocationArtifacts(callId, requestRaw, JSON.stringify({
390
428
  ok: false,
391
429
  summary,
392
430
  error: {
@@ -436,7 +474,7 @@ export async function executeRuntimeTool(name, input, context = {}) {
436
474
  }
437
475
  catch (error) {
438
476
  const summary = `Runtime tool '${name}' returned invalid JSON`;
439
- ({ requestPath, responsePath } = await persistToolInvocationArtifacts(callId, requestRaw, responseRaw));
477
+ ({ requestPath, responsePath } = await persistRuntimeToolInvocationArtifacts(callId, requestRaw, responseRaw));
440
478
  await emitRuntimeToolEvent("RUNTIME_TOOL_FAILED", summary, {
441
479
  tool_name: name,
442
480
  error: error instanceof Error ? error.message : String(error),
@@ -458,7 +496,7 @@ export async function executeRuntimeTool(name, input, context = {}) {
458
496
  const envelope = runtimeToolResponseSchema.safeParse(parsed);
459
497
  if (!envelope.success) {
460
498
  const summary = `Runtime tool '${name}' returned an invalid response envelope`;
461
- ({ requestPath, responsePath } = await persistToolInvocationArtifacts(callId, requestRaw, responseRaw));
499
+ ({ requestPath, responsePath } = await persistRuntimeToolInvocationArtifacts(callId, requestRaw, responseRaw));
462
500
  rmSync(tempPaths.tempDir, { recursive: true, force: true });
463
501
  return {
464
502
  ok: false,
@@ -483,7 +521,7 @@ export async function executeRuntimeTool(name, input, context = {}) {
483
521
  const success = envelope.data.ok && validationErrors.length === 0;
484
522
  const summary = envelope.data.summary ??
485
523
  (success ? `Runtime tool '${name}' executed successfully` : `Runtime tool '${name}' failed`);
486
- ({ requestPath, responsePath } = await persistToolInvocationArtifacts(callId, requestRaw, responseRaw));
524
+ ({ requestPath, responsePath } = await persistRuntimeToolInvocationArtifacts(callId, requestRaw, responseRaw));
487
525
  rmSync(tempPaths.tempDir, { recursive: true, force: true });
488
526
  const result = {
489
527
  ok: success,
@@ -527,8 +565,11 @@ export async function executeRuntimeTool(name, input, context = {}) {
527
565
  export const SMALL_LOCAL_PRIORITY_TOOL_NAMES = [
528
566
  "outline_file",
529
567
  "astgrep_query",
530
- "list_workspace",
568
+ "astgrep_locate",
531
569
  "read_file_lines",
570
+ "compile_structural_edit",
571
+ "preview_structural_edit",
572
+ "list_workspace",
532
573
  "recall_context",
533
574
  "validate_framework",
534
575
  "run_orchestrator",
package/dist/schemas.js CHANGED
@@ -294,6 +294,32 @@ const runtimeToolExecutorSchema = z
294
294
  env: z.record(z.string(), z.string()).optional(),
295
295
  })
296
296
  .strict();
297
+ const runtimeToolMcpServerSchema = z
298
+ .object({
299
+ transport: z.enum(["stdio", "http"]),
300
+ command: z.string().optional(),
301
+ args: z.array(z.string()).optional(),
302
+ url: z.string().url().optional(),
303
+ env: z.record(z.string(), z.string()).optional(),
304
+ tool_allowlist: z.array(z.string()).optional(),
305
+ })
306
+ .strict()
307
+ .superRefine((value, ctx) => {
308
+ if (value.transport === "stdio" && !value.command?.trim()) {
309
+ ctx.addIssue({
310
+ code: z.ZodIssueCode.custom,
311
+ path: ["command"],
312
+ message: "stdio MCP servers require command",
313
+ });
314
+ }
315
+ if (value.transport === "http" && !value.url?.trim()) {
316
+ ctx.addIssue({
317
+ code: z.ZodIssueCode.custom,
318
+ path: ["url"],
319
+ message: "http MCP servers require url",
320
+ });
321
+ }
322
+ });
297
323
  const runtimeToolSpecSchema = z
298
324
  .object({
299
325
  name: NON_EMPTY,
@@ -302,6 +328,7 @@ const runtimeToolSpecSchema = z
302
328
  success_schema: runtimeToolSchemaNodeSchema.optional(),
303
329
  failure_schema: runtimeToolSchemaNodeSchema.optional(),
304
330
  executor: runtimeToolExecutorSchema,
331
+ mcp_server: runtimeToolMcpServerSchema.optional(),
305
332
  })
306
333
  .strict();
307
334
  const runtimeToolSpecRegistrySchema = z
@@ -388,6 +415,7 @@ const runtimeExecutorSessionRecordSchema = z
388
415
  last_error: z.string().optional(),
389
416
  cleanup_error: z.string().optional(),
390
417
  workspace_cleanup_status: z.enum(["pending", "removed", "archived", "failed"]),
418
+ validated_plan_id: z.string().optional(),
391
419
  output_policy: runtimeOutputPolicySchema.optional(),
392
420
  turns: z.array(unattendedTurnRecordSchema),
393
421
  })
@@ -406,7 +434,7 @@ const vericifyProcessPostSchema = z
406
434
  branch_id: z.string().optional(),
407
435
  lane_id: z.string().optional(),
408
436
  agent_id: NON_EMPTY,
409
- kind: z.enum(["intent", "progress", "blocker", "handoff_note", "stale_ack", "completion"]),
437
+ kind: z.enum(["intent", "progress", "blocker", "handoff_note", "stale_ack", "completion", "plan_proposal", "plan_quality_assessment"]),
410
438
  summary: NON_EMPTY,
411
439
  tool_refs: z.array(z.string()),
412
440
  evidence_refs: z.array(z.string()),
package/dist/server.js CHANGED
@@ -1,7 +1,11 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
3
5
  import { buildServerInstructions, installAceToolGovernance, } from "./ace-server-instructions.js";
4
6
  import { resolveWorkspaceRoot } from "./helpers.js";
7
+ import { openStore } from "./store/ace-packed-store.js";
8
+ import { getWorkspaceStorePath } from "./store/store-snapshot.js";
5
9
  import { registerPrompts } from "./prompts.js";
6
10
  import { registerResources } from "./resources.js";
7
11
  import { registerTools } from "./tools.js";
@@ -24,7 +28,54 @@ export function createAceServer(options = {}) {
24
28
  registerTools(server);
25
29
  return server;
26
30
  }
31
+ function appendEmergencyMcpStatusEvent(workspaceRoot, message) {
32
+ const event = {
33
+ schema_version: "1.0.0",
34
+ event_id: `evt-${Date.now()}-${Math.random().toString(36).slice(2)}`,
35
+ trace_id: `trace-${Date.now()}`,
36
+ timestamp: new Date().toISOString(),
37
+ source_module: "capability-ops",
38
+ event_type: "MCP_BINARY_PARSE_ERROR",
39
+ status: "blocked",
40
+ objective_id: "mcp-stdio-startup",
41
+ payload: {
42
+ reason_code: "mcp_binary_parse_error",
43
+ summary: message,
44
+ },
45
+ };
46
+ const target = join(workspaceRoot, "agent-state", "STATUS_EVENTS.ndjson");
47
+ mkdirSync(dirname(target), { recursive: true });
48
+ writeFileSync(target, `${JSON.stringify(event)}\n`, { flag: "a" });
49
+ }
50
+ async function verifyMcpStoreStartup(workspaceRoot, logStartup) {
51
+ const canonicalStore = getWorkspaceStorePath(workspaceRoot);
52
+ const legacyStore = join(workspaceRoot, ".agents", "ACE", "ace-state.ace");
53
+ const candidates = [canonicalStore, legacyStore].filter((storePath, index, all) => existsSync(storePath) && all.indexOf(storePath) === index);
54
+ if (candidates.length === 0)
55
+ return true;
56
+ const errors = [];
57
+ for (const storePath of candidates) {
58
+ try {
59
+ const store = await openStore(storePath, { readOnly: true });
60
+ await store.close();
61
+ return true;
62
+ }
63
+ catch (error) {
64
+ errors.push(`${storePath}: ${error instanceof Error ? error.message : String(error)}`);
65
+ }
66
+ }
67
+ const message = `ACE MCP startup could not open ace-state.ace as ACEPACK; refusing stdio startup without crashing. ` +
68
+ `reason_code=mcp_binary_parse_error. ${errors.join(" | ")}`;
69
+ appendEmergencyMcpStatusEvent(workspaceRoot, message);
70
+ if (logStartup)
71
+ console.error(message);
72
+ return false;
73
+ }
27
74
  export async function startStdioServer(logStartup = true) {
75
+ const workspaceRoot = resolveWorkspaceRoot();
76
+ if (!(await verifyMcpStoreStartup(workspaceRoot, logStartup))) {
77
+ return;
78
+ }
28
79
  try {
29
80
  const backfill = await backfillHandoffsIntoScheduler();
30
81
  if (logStartup && backfill.scanned > 0) {
package/dist/shared.d.ts CHANGED
@@ -43,6 +43,7 @@ export declare const ROLE_ENUM: z.ZodEnum<{
43
43
  observability: "observability";
44
44
  eval: "eval";
45
45
  release: "release";
46
+ planner: "planner";
46
47
  }>;
47
48
  export declare const HANDOFF_VALIDATION_MODE: z.ZodOptional<z.ZodEnum<{
48
49
  "agent-state": "agent-state";
package/dist/shared.js CHANGED
@@ -57,6 +57,7 @@ export const ROLE_TITLES = {
57
57
  observability: "Observability",
58
58
  eval: "Eval",
59
59
  release: "Release",
60
+ planner: "Planner",
60
61
  };
61
62
  export function getRoleTitle(role) {
62
63
  return ROLE_TITLES[role] ?? role.toUpperCase();
@@ -89,6 +90,7 @@ export const ROLE_ENUM = z.enum([
89
90
  "observability",
90
91
  "eval",
91
92
  "release",
93
+ "planner",
92
94
  ]);
93
95
  export const HANDOFF_VALIDATION_MODE = z
94
96
  .enum(["auto", "swarm", "agent-state"])
@@ -26,6 +26,7 @@ export interface BootstrapOptions {
26
26
  model?: string;
27
27
  baseUrl?: string;
28
28
  ollamaUrl?: string;
29
+ launcherCommand?: string;
29
30
  }
30
31
  export interface BootstrapResult {
31
32
  storePath: string;
@@ -43,13 +43,19 @@ async function applyLlmRuntimeConfig(store, opts) {
43
43
  if (!opts.llm)
44
44
  return;
45
45
  const provider = normalizeProvider(opts.llm) ?? opts.llm;
46
- const configuredModel = opts.model ?? defaultModelForProvider(provider);
46
+ const configuredModel = opts.model?.trim() ||
47
+ (provider === "ollama" ? defaultModelForProvider(provider) : undefined);
47
48
  const configuredBaseUrl = normalizeLocalBaseUrl(opts.baseUrl ?? opts.ollamaUrl);
48
49
  const profilePayload = {
49
50
  provider,
50
- model: configuredModel,
51
51
  generated_at: new Date().toISOString(),
52
52
  };
53
+ if (configuredModel) {
54
+ profilePayload.model = configuredModel;
55
+ }
56
+ if (opts.launcherCommand) {
57
+ profilePayload.launch_command = opts.launcherCommand;
58
+ }
53
59
  if (configuredBaseUrl) {
54
60
  profilePayload.base_url = configuredBaseUrl;
55
61
  if (provider === "ollama") {
@@ -101,7 +101,7 @@ export interface AceArchivedChatRecord {
101
101
  export interface TransitionRecord {
102
102
  transition_id: string;
103
103
  session_id?: string;
104
- subject_kind: "runtime_status" | "continuity" | "plan_step" | "nudge" | "capability" | "workspace_session" | "turn" | "activation";
104
+ subject_kind: "runtime_status" | "continuity" | "plan_step" | "plan_proposal" | "nudge" | "capability" | "workspace_session" | "turn" | "activation";
105
105
  subject_id: string;
106
106
  from?: string;
107
107
  to: string;
@@ -255,7 +255,7 @@ export class LocalModelRuntimeRepository {
255
255
  deduped.set(record.transition_id, record);
256
256
  }
257
257
  const kinds = [
258
- "runtime_status", "continuity", "turn", "plan_step", "capability", "workspace_session", "nudge", "activation",
258
+ "runtime_status", "continuity", "turn", "plan_step", "plan_proposal", "capability", "workspace_session", "nudge", "activation",
259
259
  ];
260
260
  for (const kind of kinds) {
261
261
  const records = await this.store.getJSON(this.transitionLogKey(kind, sessionId)) ?? [];
@@ -9,7 +9,7 @@ export interface VericifyPost {
9
9
  id: string;
10
10
  run_id: string;
11
11
  agent_id: string;
12
- kind: "intent" | "progress" | "completion" | "blocker" | "handoff_note" | "stale_ack";
12
+ kind: "intent" | "progress" | "completion" | "blocker" | "handoff_note" | "stale_ack" | "plan_proposal" | "plan_quality_assessment";
13
13
  summary: string;
14
14
  ts: number;
15
15
  metadata?: Record<string, unknown>;
@@ -2,6 +2,26 @@
2
2
  * Agent, skill, kernel, and task-pack tool registrations.
3
3
  */
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { type BridgeResult } from "./model-bridge.js";
6
+ import { proposePlan as proposePlanImpl, validatePlan as validatePlanImpl, loadAcceptanceTraceContract as loadAcceptanceTraceContractImpl } from "./plan-proposal.js";
7
+ import { type TaskPlan, type TaskStep } from "./orchestrator-supervisor.js";
5
8
  export type { TaskPlan, TaskStep } from "./orchestrator-supervisor.js";
9
+ export declare function formatStepTaskForBridge(step: Pick<TaskStep, "task" | "upstream_outputs">): string;
10
+ type IntentVerificationOutcome = "ok" | "revisit_step" | "replan_required";
11
+ type IntentDriftReasonCode = "contract_missing" | "contract_invalid" | "bridge_output_malformed_json" | "role_drift_ui_off_topic" | "coder_artifact_stub" | "qa_rewrote_artifact" | "artifact_mismatch" | "forbidden_pattern" | "required_evidence_missing";
12
+ interface IntentVerificationResult {
13
+ outcome: IntentVerificationOutcome;
14
+ reason: string;
15
+ reason_code?: IntentDriftReasonCode;
16
+ uncovered_clauses?: string[];
17
+ }
18
+ export { loadAcceptanceTraceContractImpl as loadAcceptanceTraceContract };
19
+ export { proposePlanImpl as proposePlan, validatePlanImpl as validatePlan };
20
+ export declare function verifyIntentAgainstContract(input: {
21
+ plan: TaskPlan;
22
+ step: TaskStep;
23
+ result: BridgeResult;
24
+ intent_contract: unknown;
25
+ }): IntentVerificationResult;
6
26
  export declare function registerAgentTools(server: McpServer): void;
7
27
  //# sourceMappingURL=tools-agent.d.ts.map