@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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
- package/assets/agent-state/runtime-tool-specs.json +70 -2
- package/assets/instructions/ACE_Coder.instructions.md +13 -0
- package/assets/instructions/ACE_UI.instructions.md +11 -0
- package/dist/ace-context.js +70 -11
- package/dist/ace-internal-tools.d.ts +3 -1
- package/dist/ace-internal-tools.js +10 -2
- package/dist/agent-runtime/role-adapters.d.ts +18 -1
- package/dist/agent-runtime/role-adapters.js +49 -5
- package/dist/astgrep-index.d.ts +48 -0
- package/dist/astgrep-index.js +126 -1
- package/dist/cli.js +205 -15
- package/dist/discovery-runtime-wrappers.d.ts +108 -0
- package/dist/discovery-runtime-wrappers.js +615 -0
- package/dist/helpers/bootstrap.js +1 -1
- package/dist/helpers/constants.d.ts +2 -2
- package/dist/helpers/constants.js +7 -0
- package/dist/helpers/path-utils.d.ts +8 -1
- package/dist/helpers/path-utils.js +27 -8
- package/dist/helpers/store-resolution.js +7 -3
- package/dist/job-scheduler.js +30 -4
- package/dist/json-sanitizer.d.ts +16 -0
- package/dist/json-sanitizer.js +26 -0
- package/dist/local-model-policy.d.ts +27 -0
- package/dist/local-model-policy.js +84 -0
- package/dist/local-model-runtime.d.ts +6 -0
- package/dist/local-model-runtime.js +21 -20
- package/dist/model-bridge.d.ts +6 -1
- package/dist/model-bridge.js +338 -21
- package/dist/orchestrator-supervisor.d.ts +42 -0
- package/dist/orchestrator-supervisor.js +110 -3
- package/dist/plan-proposal.d.ts +115 -0
- package/dist/plan-proposal.js +1073 -0
- package/dist/runtime-executor.d.ts +6 -1
- package/dist/runtime-executor.js +72 -5
- package/dist/runtime-tool-specs.d.ts +19 -1
- package/dist/runtime-tool-specs.js +67 -26
- package/dist/schemas.js +29 -1
- package/dist/server.js +51 -0
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +2 -0
- package/dist/store/bootstrap-store.d.ts +1 -0
- package/dist/store/bootstrap-store.js +8 -2
- package/dist/store/repositories/local-model-runtime-repository.d.ts +1 -1
- package/dist/store/repositories/local-model-runtime-repository.js +1 -1
- package/dist/store/repositories/vericify-repository.d.ts +1 -1
- package/dist/tools-agent.d.ts +20 -0
- package/dist/tools-agent.js +538 -28
- package/dist/tools-discovery.js +135 -0
- package/dist/tools-files.js +768 -66
- package/dist/tools-framework.js +80 -61
- package/dist/tui/index.js +10 -1
- package/dist/tui/ollama.d.ts +8 -1
- package/dist/tui/ollama.js +53 -12
- package/dist/tui/openai-compatible.d.ts +13 -0
- package/dist/tui/openai-compatible.js +305 -5
- package/dist/tui/provider-discovery.d.ts +1 -0
- package/dist/tui/provider-discovery.js +35 -11
- package/dist/vericify-bridge.d.ts +1 -1
- 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,
|
|
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
|
package/dist/runtime-executor.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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", "
|
|
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
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
167
|
+
const parsed = parseJsonLikeText(raw);
|
|
168
|
+
if (!parsed.ok) {
|
|
169
|
+
throw new Error(`Runtime tool registry contains invalid JSON: ${parsed.error}`);
|
|
136
170
|
}
|
|
137
|
-
|
|
138
|
-
throw new Error(`Runtime tool registry contains
|
|
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
|
-
|
|
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: [
|
|
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
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
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"])
|
|
@@ -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
|
|
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>;
|
package/dist/tools-agent.d.ts
CHANGED
|
@@ -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
|