@voybio/ace-swarm 0.2.5 → 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 +19 -1
- package/README.md +21 -13
- package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
- package/assets/agent-state/EVIDENCE_LOG.md +1 -1
- package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
- package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
- package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
- package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
- package/assets/agent-state/STATUS.md +2 -2
- 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/assets/scripts/ace-hook-dispatch.mjs +70 -6
- package/assets/scripts/render-mcp-configs.sh +19 -5
- package/dist/ace-context.js +91 -11
- package/dist/ace-internal-tools.d.ts +3 -1
- package/dist/ace-internal-tools.js +10 -2
- package/dist/ace-server-instructions.js +3 -3
- package/dist/ace-state-resolver.js +5 -3
- 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 +57 -1
- package/dist/astgrep-index.js +140 -4
- package/dist/cli.js +232 -35
- package/dist/discovery-runtime-wrappers.d.ts +108 -0
- package/dist/discovery-runtime-wrappers.js +615 -0
- package/dist/handoff-registry.js +5 -5
- package/dist/helpers/artifacts.d.ts +19 -0
- package/dist/helpers/artifacts.js +152 -0
- package/dist/helpers/bootstrap.d.ts +24 -0
- package/dist/helpers/bootstrap.js +894 -0
- package/dist/helpers/constants.d.ts +53 -0
- package/dist/helpers/constants.js +295 -0
- package/dist/helpers/drift.d.ts +13 -0
- package/dist/helpers/drift.js +45 -0
- package/dist/helpers/path-utils.d.ts +24 -0
- package/dist/helpers/path-utils.js +123 -0
- package/dist/helpers/store-resolution.d.ts +19 -0
- package/dist/helpers/store-resolution.js +305 -0
- package/dist/helpers/workspace-root.d.ts +3 -0
- package/dist/helpers/workspace-root.js +80 -0
- package/dist/helpers.d.ts +8 -125
- package/dist/helpers.js +8 -1768
- package/dist/job-scheduler.js +33 -7
- 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 +33 -21
- package/dist/model-bridge.d.ts +13 -1
- package/dist/model-bridge.js +410 -23
- package/dist/orchestrator-supervisor.d.ts +56 -0
- package/dist/orchestrator-supervisor.js +179 -1
- package/dist/plan-proposal.d.ts +115 -0
- package/dist/plan-proposal.js +1073 -0
- package/dist/run-ledger.js +3 -3
- package/dist/runtime-command.d.ts +8 -0
- package/dist/runtime-command.js +38 -6
- package/dist/runtime-executor.d.ts +20 -1
- package/dist/runtime-executor.js +737 -172
- package/dist/runtime-profile.d.ts +32 -0
- package/dist/runtime-profile.js +89 -13
- package/dist/runtime-tool-specs.d.ts +39 -0
- package/dist/runtime-tool-specs.js +144 -28
- package/dist/safe-edit.d.ts +7 -0
- package/dist/safe-edit.js +163 -37
- package/dist/schemas.js +48 -1
- package/dist/server.js +51 -0
- package/dist/shared.d.ts +3 -2
- package/dist/shared.js +2 -0
- package/dist/status-events.js +9 -6
- package/dist/store/ace-packed-store.d.ts +3 -2
- package/dist/store/ace-packed-store.js +188 -110
- package/dist/store/bootstrap-store.d.ts +2 -1
- package/dist/store/bootstrap-store.js +102 -83
- package/dist/store/cache-workspace.js +11 -5
- package/dist/store/materializers/context-snapshot-materializer.js +6 -2
- package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
- package/dist/store/materializers/hook-context-materializer.js +11 -21
- package/dist/store/materializers/host-file-materializer.js +6 -0
- package/dist/store/materializers/projection-manager.d.ts +0 -1
- package/dist/store/materializers/projection-manager.js +5 -13
- package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
- package/dist/store/materializers/vericify-projector.d.ts +7 -7
- package/dist/store/materializers/vericify-projector.js +11 -11
- package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
- package/dist/store/repositories/local-model-runtime-repository.js +242 -6
- package/dist/store/repositories/vericify-repository.d.ts +1 -1
- package/dist/store/skills-install.d.ts +4 -0
- package/dist/store/skills-install.js +21 -12
- package/dist/store/state-reader.d.ts +2 -0
- package/dist/store/state-reader.js +20 -0
- package/dist/store/store-artifacts.d.ts +7 -0
- package/dist/store/store-artifacts.js +27 -1
- package/dist/store/store-authority-audit.d.ts +18 -1
- package/dist/store/store-authority-audit.js +115 -5
- package/dist/store/store-snapshot.d.ts +3 -0
- package/dist/store/store-snapshot.js +22 -2
- package/dist/store/workspace-store-paths.d.ts +39 -0
- package/dist/store/workspace-store-paths.js +94 -0
- package/dist/store/write-coordinator.d.ts +65 -0
- package/dist/store/write-coordinator.js +386 -0
- package/dist/todo-state.js +5 -5
- package/dist/tools-agent.d.ts +20 -0
- package/dist/tools-agent.js +789 -25
- package/dist/tools-discovery.js +136 -1
- package/dist/tools-files.d.ts +7 -0
- package/dist/tools-files.js +1002 -11
- package/dist/tools-framework.js +105 -66
- package/dist/tools-handoff.js +2 -2
- package/dist/tools-lifecycle.js +4 -4
- package/dist/tools-memory.js +6 -6
- package/dist/tools-todo.js +2 -2
- package/dist/tracker-adapters.d.ts +1 -1
- package/dist/tracker-adapters.js +13 -18
- package/dist/tracker-sync.js +5 -3
- package/dist/tui/agent-runner.js +3 -1
- package/dist/tui/chat.js +103 -7
- package/dist/tui/dashboard.d.ts +1 -0
- package/dist/tui/dashboard.js +43 -0
- package/dist/tui/index.js +10 -1
- package/dist/tui/layout.d.ts +20 -0
- package/dist/tui/layout.js +31 -1
- package/dist/tui/local-model-contract.d.ts +6 -2
- package/dist/tui/local-model-contract.js +16 -3
- 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 +6 -1
- package/dist/vericify-bridge.js +27 -3
- package/dist/workspace-manager.d.ts +30 -3
- package/dist/workspace-manager.js +257 -27
- package/package.json +1 -2
- package/dist/internal-tool-runtime.d.ts +0 -21
- package/dist/internal-tool-runtime.js +0 -136
- package/dist/store/workspace-snapshot.d.ts +0 -26
- package/dist/store/workspace-snapshot.js +0 -107
package/dist/tools-agent.js
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
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, resolveWritableTaskPath,
|
|
5
|
+
import { ALL_AGENTS, COMPOSABLE_AGENTS, SWARM_AGENTS, SWARM_SUBAGENT_MAP, classifyPathSource, getAgentInstructionPath, getAgentManifestPath, getKernelArtifactPath, isSwarmRole, listAvailableSkills, readAgentInstructions, readAgentManifest, readKernelArtifact, readSkillInstructions, readTaskArtifact, resolveWorkspaceRoot, resolveWritableTaskPath, safeWriteAsync, } 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
8
|
import { executeRuntimeTool, listRuntimeToolSpecs, loadRuntimeToolRegistry, validateRuntimeToolRegistryContent, } from "./runtime-tool-specs.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createWorkspaceSessionAsync, listWorkspaceSessions, removeWorkspaceSession, resolveRuntimeWorkspaceRoot, validateManagedWorkspacePath, } from "./workspace-manager.js";
|
|
10
10
|
import { getTrackerAdapter, listTrackerAdapterKinds, loadTrackerSnapshot, validateTrackerSnapshotContent, } from "./tracker-adapters.js";
|
|
11
11
|
import { refreshTrackerSnapshot } from "./tracker-sync.js";
|
|
12
12
|
import { appendVericifyProcessPost, loadVericifyBridgeSnapshot, loadVericifyProcessPostLog, refreshVericifyBridgeSnapshot, validateVericifyBridgeSnapshotContent, validateVericifyProcessPostLogContent, } from "./vericify-bridge.js";
|
|
13
13
|
import { getRoleTitle, ROLE_ENUM, KERNEL_KEY_ENUM, ROLE_TITLES } from "./shared.js";
|
|
14
|
-
import { createDefaultModelBridgeClients, resolveLocalModelRuntime, runLocalModelTask, } from "./local-model-runtime.js";
|
|
14
|
+
import { createDefaultModelBridgeClients, resolveLocalModelRuntime, resolveTier, runLocalModelTask, } from "./local-model-runtime.js";
|
|
15
|
+
import { withLocalModelRuntimeRepository } from "./store/repositories/local-model-runtime-repository.js";
|
|
15
16
|
import { executeAceInternalTool } from "./ace-internal-tools.js";
|
|
16
17
|
import { ModelBridge } from "./model-bridge.js";
|
|
17
18
|
import { getVericifyContextPacket, getVericifyDelta } from "./vericify-context.js";
|
|
18
|
-
import {
|
|
19
|
+
import { proposePlan as proposePlanImpl, proposePlanDeterministic, validatePlan as validatePlanImpl, loadAcceptanceTraceContract as loadAcceptanceTraceContractImpl, persistAcceptanceTraceMapWithContract, } from "./plan-proposal.js";
|
|
20
|
+
import { amendTaskPlan, createTaskPlan, superviseTaskPlan, } from "./orchestrator-supervisor.js";
|
|
19
21
|
function parseOptionalJsonObject(raw) {
|
|
20
22
|
if (!raw)
|
|
21
23
|
return {};
|
|
@@ -52,6 +54,11 @@ function extractToolTextContent(result) {
|
|
|
52
54
|
.filter(Boolean)
|
|
53
55
|
.join("\n");
|
|
54
56
|
}
|
|
57
|
+
function didExecuteGatesPass(result) {
|
|
58
|
+
if (Boolean(result?.isError))
|
|
59
|
+
return false;
|
|
60
|
+
return !extractToolTextContent(result).trimStart().startsWith("❌");
|
|
61
|
+
}
|
|
55
62
|
function mapRoleToTaskType(role) {
|
|
56
63
|
switch (role) {
|
|
57
64
|
case "vos":
|
|
@@ -71,9 +78,268 @@ function extractHandoffId(text) {
|
|
|
71
78
|
const lineMatch = text.match(/handoff_id:\s*([A-Z0-9-]+)/i);
|
|
72
79
|
return lineMatch?.[1];
|
|
73
80
|
}
|
|
81
|
+
export function formatStepTaskForBridge(step) {
|
|
82
|
+
const upstreamOutputs = step.upstream_outputs ?? [];
|
|
83
|
+
if (upstreamOutputs.length === 0) {
|
|
84
|
+
return step.task;
|
|
85
|
+
}
|
|
86
|
+
const renderedOutputs = upstreamOutputs
|
|
87
|
+
.map((output) => `- ${output.step_id}: ${output.result_summary || "[no summary]"}${output.evidence_refs.length > 0 ? ` (evidence: ${output.evidence_refs.join(", ")})` : ""}`)
|
|
88
|
+
.join("\n");
|
|
89
|
+
return [
|
|
90
|
+
step.task,
|
|
91
|
+
"",
|
|
92
|
+
"Upstream outputs:",
|
|
93
|
+
renderedOutputs,
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
// ── Re-export plan-proposal API ────────────────────────────────────────────
|
|
97
|
+
export { loadAcceptanceTraceContractImpl as loadAcceptanceTraceContract };
|
|
98
|
+
export { proposePlanImpl as proposePlan, validatePlanImpl as validatePlan };
|
|
99
|
+
// ── proposalToSteps: maps PlanProposal steps to local PlannedStepInput ────
|
|
100
|
+
function proposalToSteps(proposal) {
|
|
101
|
+
return proposal.steps.map((s) => ({
|
|
102
|
+
role: s.role,
|
|
103
|
+
task: s.task,
|
|
104
|
+
depends_on: s.depends_on && s.depends_on.length > 0 ? s.depends_on : undefined,
|
|
105
|
+
tool_scope: s.tool_scope && s.tool_scope.length > 0 ? s.tool_scope : undefined,
|
|
106
|
+
expected_output_class: s.expected_output_class,
|
|
107
|
+
expected_artifacts: s.expected_artifacts,
|
|
108
|
+
allowed_tools: s.allowed_tools,
|
|
109
|
+
forbidden_patterns: s.forbidden_patterns,
|
|
110
|
+
required_evidence_refs: s.required_evidence_refs,
|
|
111
|
+
structural_edit_plan_required: s.structural_edit_plan_required,
|
|
112
|
+
structural_edit_waiver: s.structural_edit_waiver,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
function stripPlanIdFromVerdict(verdict) {
|
|
116
|
+
return {
|
|
117
|
+
ok: verdict.ok,
|
|
118
|
+
score: verdict.score,
|
|
119
|
+
blocking_findings: [...verdict.blocking_findings],
|
|
120
|
+
soft_findings: [...verdict.soft_findings],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function proposalToNormalization(proposal, verdict) {
|
|
124
|
+
const acMap = new Map();
|
|
125
|
+
const stopConditionsMap = new Map();
|
|
126
|
+
proposal.steps.forEach((ps, idx) => {
|
|
127
|
+
acMap.set(stepLabel(idx), ps.acceptance_criteria ?? []);
|
|
128
|
+
stopConditionsMap.set(stepLabel(idx), ps.stop_condition ?? []);
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
planSource: proposal.plan_source,
|
|
132
|
+
steps: proposalToSteps(proposal),
|
|
133
|
+
insertedResearch: false,
|
|
134
|
+
shipFanoutEnabled: false,
|
|
135
|
+
intentSummary: proposal.intent_summary,
|
|
136
|
+
successCriteria: proposal.success_criteria,
|
|
137
|
+
validationVerdict: stripPlanIdFromVerdict(verdict),
|
|
138
|
+
acceptanceCriteriaByStep: acMap,
|
|
139
|
+
stopConditionsByStep: stopConditionsMap,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function stepLabel(index) {
|
|
143
|
+
return `step-${index + 1}`;
|
|
144
|
+
}
|
|
145
|
+
function isShipBoundaryTask(task) {
|
|
146
|
+
return /\b(ship|release|promot(?:e|ion)|land|merge)\b/i.test(task);
|
|
147
|
+
}
|
|
148
|
+
function isImplementationRole(role) {
|
|
149
|
+
return role === "coders" || role === "builder";
|
|
150
|
+
}
|
|
74
151
|
async function buildOrchestratorSteps(task, sessionId) {
|
|
75
|
-
|
|
76
|
-
|
|
152
|
+
// (a) propose a plan via the planner model
|
|
153
|
+
const proposal = await proposePlanImpl(task, sessionId);
|
|
154
|
+
// (b) validate the proposal
|
|
155
|
+
const verdict = await validatePlanImpl({ proposal, sessionId });
|
|
156
|
+
// (c) if ok, return validated steps with planner enrichment
|
|
157
|
+
if (verdict.ok && proposal.steps.length > 0) {
|
|
158
|
+
return proposalToNormalization(proposal, verdict);
|
|
159
|
+
}
|
|
160
|
+
// Deterministic fallback: still validate the floor before returning it.
|
|
161
|
+
const fallbackProposal = proposePlanDeterministic(task);
|
|
162
|
+
const fallbackVerdict = await validatePlanImpl({ proposal: fallbackProposal, sessionId });
|
|
163
|
+
return proposalToNormalization(fallbackProposal, fallbackVerdict);
|
|
164
|
+
}
|
|
165
|
+
function normalizeExplicitPlanSteps(steps, task) {
|
|
166
|
+
const originalIdByLabel = new Map();
|
|
167
|
+
const normalized = steps.map((step, index) => {
|
|
168
|
+
const id = `explicit-${index + 1}`;
|
|
169
|
+
originalIdByLabel.set(stepLabel(index), id);
|
|
170
|
+
return {
|
|
171
|
+
...step,
|
|
172
|
+
id,
|
|
173
|
+
depends_on_ids: [...(step.depends_on ?? [])],
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
for (const step of normalized) {
|
|
177
|
+
step.depends_on_ids = step.depends_on_ids.map((dependency) => originalIdByLabel.get(dependency) ?? dependency);
|
|
178
|
+
}
|
|
179
|
+
let insertedResearch = false;
|
|
180
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
181
|
+
const step = normalized[index];
|
|
182
|
+
if (step.role !== "spec")
|
|
183
|
+
continue;
|
|
184
|
+
const hasResearchBefore = normalized.slice(0, index).some((candidate) => candidate.role === "research");
|
|
185
|
+
if (hasResearchBefore)
|
|
186
|
+
continue;
|
|
187
|
+
const researchId = `auto-research-${index + 1}`;
|
|
188
|
+
normalized.splice(index, 0, {
|
|
189
|
+
id: researchId,
|
|
190
|
+
role: "research",
|
|
191
|
+
task: `Validate source-backed claims and gather evidence before spec work: ${step.task}`,
|
|
192
|
+
tool_scope: ["recall_context", "read_workspace_file", "build_continuity_packet"],
|
|
193
|
+
depends_on_ids: [],
|
|
194
|
+
});
|
|
195
|
+
step.depends_on_ids = Array.from(new Set([researchId, ...step.depends_on_ids]));
|
|
196
|
+
insertedResearch = true;
|
|
197
|
+
index += 1;
|
|
198
|
+
}
|
|
199
|
+
const shipFanoutEnabled = isShipBoundaryTask(task) || normalized.some((step) => isShipBoundaryTask(step.task));
|
|
200
|
+
if (shipFanoutEnabled) {
|
|
201
|
+
const hasReviewRole = normalized.some((step) => step.role === "skeptic");
|
|
202
|
+
const hasSecurityRole = normalized.some((step) => step.role === "security");
|
|
203
|
+
const hasQaRole = normalized.some((step) => step.role === "qa");
|
|
204
|
+
const hasReleaseRole = normalized.some((step) => step.role === "release");
|
|
205
|
+
const lastImplementation = [...normalized].reverse().find((step) => isImplementationRole(step.role)) ?? normalized.at(-1);
|
|
206
|
+
const implementationDependency = lastImplementation ? [lastImplementation.id] : [];
|
|
207
|
+
const fanoutStepIds = [];
|
|
208
|
+
if (!hasReviewRole) {
|
|
209
|
+
const reviewId = `auto-review-${normalized.length + 1}`;
|
|
210
|
+
normalized.push({
|
|
211
|
+
id: reviewId,
|
|
212
|
+
role: "skeptic",
|
|
213
|
+
task: `Review ship readiness for: ${task}`,
|
|
214
|
+
tool_scope: ["execute_gates", "validate_framework"],
|
|
215
|
+
depends_on_ids: implementationDependency,
|
|
216
|
+
});
|
|
217
|
+
fanoutStepIds.push(reviewId);
|
|
218
|
+
}
|
|
219
|
+
if (!hasSecurityRole) {
|
|
220
|
+
const securityId = `auto-security-${normalized.length + 1}`;
|
|
221
|
+
normalized.push({
|
|
222
|
+
id: securityId,
|
|
223
|
+
role: "security",
|
|
224
|
+
task: `Assess security readiness for: ${task}`,
|
|
225
|
+
tool_scope: ["execute_gates", "validate_framework"],
|
|
226
|
+
depends_on_ids: implementationDependency,
|
|
227
|
+
});
|
|
228
|
+
fanoutStepIds.push(securityId);
|
|
229
|
+
}
|
|
230
|
+
if (!hasQaRole) {
|
|
231
|
+
const qaId = `auto-qa-${normalized.length + 1}`;
|
|
232
|
+
normalized.push({
|
|
233
|
+
id: qaId,
|
|
234
|
+
role: "qa",
|
|
235
|
+
task: `Validate ship readiness for: ${task}`,
|
|
236
|
+
tool_scope: ["run_tests", "execute_gates", "git_diff"],
|
|
237
|
+
depends_on_ids: implementationDependency,
|
|
238
|
+
});
|
|
239
|
+
fanoutStepIds.push(qaId);
|
|
240
|
+
}
|
|
241
|
+
if (!hasReleaseRole && fanoutStepIds.length > 0) {
|
|
242
|
+
normalized.push({
|
|
243
|
+
id: `auto-release-${normalized.length + 1}`,
|
|
244
|
+
role: "release",
|
|
245
|
+
task: `Finalize ship decision for: ${task}`,
|
|
246
|
+
tool_scope: ["git_status", "execute_gates", "validate_framework"],
|
|
247
|
+
depends_on_ids: fanoutStepIds,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const finalIdByInternal = new Map();
|
|
252
|
+
normalized.forEach((step, index) => {
|
|
253
|
+
finalIdByInternal.set(step.id, stepLabel(index));
|
|
254
|
+
});
|
|
255
|
+
return {
|
|
256
|
+
planSource: "explicit_steps",
|
|
257
|
+
steps: normalized.map((step) => ({
|
|
258
|
+
role: step.role,
|
|
259
|
+
task: step.task,
|
|
260
|
+
depends_on: step.depends_on_ids.length > 0
|
|
261
|
+
? step.depends_on_ids.map((dependency) => finalIdByInternal.get(dependency) ?? dependency)
|
|
262
|
+
: undefined,
|
|
263
|
+
parallel_group: step.parallel_group,
|
|
264
|
+
tool_scope: step.tool_scope,
|
|
265
|
+
expected_output_class: step.expected_output_class,
|
|
266
|
+
expected_artifacts: step.expected_artifacts,
|
|
267
|
+
allowed_tools: step.allowed_tools,
|
|
268
|
+
forbidden_patterns: step.forbidden_patterns,
|
|
269
|
+
required_evidence_refs: step.required_evidence_refs,
|
|
270
|
+
structural_edit_plan_required: step.structural_edit_plan_required,
|
|
271
|
+
structural_edit_waiver: step.structural_edit_waiver,
|
|
272
|
+
})),
|
|
273
|
+
insertedResearch,
|
|
274
|
+
shipFanoutEnabled,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
async function normalizeOrchestratorPlanSteps(task, steps, sessionId) {
|
|
278
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
279
|
+
const normalization = await buildOrchestratorSteps(task, sessionId);
|
|
280
|
+
return { planSource: normalization.planSource, normalization };
|
|
281
|
+
}
|
|
282
|
+
const normalization = normalizeExplicitPlanSteps(steps, task);
|
|
283
|
+
// Run the same artifact/output-class gate that planner-derived steps go through.
|
|
284
|
+
const syntheticProposal = {
|
|
285
|
+
plan_id: `explicit-${Date.now()}`,
|
|
286
|
+
status: "planning",
|
|
287
|
+
intent_summary: task,
|
|
288
|
+
success_criteria: [],
|
|
289
|
+
steps: normalization.steps.map((s) => ({
|
|
290
|
+
role: s.role,
|
|
291
|
+
task: s.task,
|
|
292
|
+
depends_on: s.depends_on,
|
|
293
|
+
tool_scope: s.tool_scope,
|
|
294
|
+
acceptance_criteria: [],
|
|
295
|
+
expected_output_class: s.expected_output_class,
|
|
296
|
+
expected_artifacts: s.expected_artifacts,
|
|
297
|
+
allowed_tools: s.allowed_tools,
|
|
298
|
+
forbidden_patterns: s.forbidden_patterns,
|
|
299
|
+
required_evidence_refs: s.required_evidence_refs,
|
|
300
|
+
structural_edit_plan_required: s.structural_edit_plan_required,
|
|
301
|
+
structural_edit_waiver: s.structural_edit_waiver,
|
|
302
|
+
})),
|
|
303
|
+
plan_source: "explicit_steps",
|
|
304
|
+
};
|
|
305
|
+
const verdict = await validatePlanImpl({ proposal: syntheticProposal, sessionId });
|
|
306
|
+
normalization.validationVerdict = stripPlanIdFromVerdict(verdict);
|
|
307
|
+
return { planSource: "explicit_steps", normalization };
|
|
308
|
+
}
|
|
309
|
+
async function persistAcceptanceTraceMap(input) {
|
|
310
|
+
return persistAcceptanceTraceMapWithContract({
|
|
311
|
+
plan_id: input.plan.plan_id,
|
|
312
|
+
task: input.task,
|
|
313
|
+
plan_source: input.planSource,
|
|
314
|
+
intent_summary: input.normalization?.intentSummary,
|
|
315
|
+
success_criteria: input.normalization?.successCriteria,
|
|
316
|
+
validation_verdict: input.normalization?.validationVerdict,
|
|
317
|
+
policies: {
|
|
318
|
+
inserted_research_before_spec: input.insertedResearch,
|
|
319
|
+
ship_fanout_enabled: input.shipFanoutEnabled,
|
|
320
|
+
},
|
|
321
|
+
steps: input.plan.steps.map((step, idx) => ({
|
|
322
|
+
step_id: step.step_id,
|
|
323
|
+
role: step.role,
|
|
324
|
+
task: step.task,
|
|
325
|
+
depends_on: step.depends_on ?? [],
|
|
326
|
+
tool_scope: step.tool_scope ?? [],
|
|
327
|
+
verification_role: step.role === "coders" || step.role === "builder"
|
|
328
|
+
? "qa"
|
|
329
|
+
: step.role === "spec"
|
|
330
|
+
? "research"
|
|
331
|
+
: null,
|
|
332
|
+
acceptance_criteria: input.normalization?.acceptanceCriteriaByStep?.get(`step-${idx + 1}`),
|
|
333
|
+
stop_condition: input.normalization?.stopConditionsByStep?.get(`step-${idx + 1}`),
|
|
334
|
+
expected_output_class: step.expected_output_class,
|
|
335
|
+
expected_artifacts: step.expected_artifacts,
|
|
336
|
+
allowed_tools: step.allowed_tools,
|
|
337
|
+
forbidden_patterns: step.forbidden_patterns,
|
|
338
|
+
required_evidence_refs: step.required_evidence_refs,
|
|
339
|
+
structural_edit_plan_required: step.structural_edit_plan_required,
|
|
340
|
+
structural_edit_waiver: step.structural_edit_waiver,
|
|
341
|
+
})),
|
|
342
|
+
});
|
|
77
343
|
}
|
|
78
344
|
function appendUniqueNote(target, note) {
|
|
79
345
|
if (!target.includes(note)) {
|
|
@@ -116,6 +382,265 @@ function buildDefaultOrchestratorAmendment(input) {
|
|
|
116
382
|
add_after_step_id: input.step.step_id,
|
|
117
383
|
};
|
|
118
384
|
}
|
|
385
|
+
function isAcceptanceTraceContract(value) {
|
|
386
|
+
if (!value || typeof value !== "object")
|
|
387
|
+
return false;
|
|
388
|
+
const candidate = value;
|
|
389
|
+
return Array.isArray(candidate.steps);
|
|
390
|
+
}
|
|
391
|
+
const INTENT_STOPWORDS = new Set([
|
|
392
|
+
"the",
|
|
393
|
+
"and",
|
|
394
|
+
"for",
|
|
395
|
+
"with",
|
|
396
|
+
"this",
|
|
397
|
+
"that",
|
|
398
|
+
"step",
|
|
399
|
+
"step1",
|
|
400
|
+
"step2",
|
|
401
|
+
"step3",
|
|
402
|
+
"step4",
|
|
403
|
+
"step5",
|
|
404
|
+
"step6",
|
|
405
|
+
"step7",
|
|
406
|
+
"step8",
|
|
407
|
+
"step9",
|
|
408
|
+
"step10",
|
|
409
|
+
"complete",
|
|
410
|
+
"completed",
|
|
411
|
+
"validate",
|
|
412
|
+
"validation",
|
|
413
|
+
"implement",
|
|
414
|
+
"implementation",
|
|
415
|
+
"done",
|
|
416
|
+
"ready",
|
|
417
|
+
]);
|
|
418
|
+
function normalizeIntentText(value) {
|
|
419
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
420
|
+
}
|
|
421
|
+
function intentTokens(value) {
|
|
422
|
+
return normalizeIntentText(value)
|
|
423
|
+
.split(/\s+/)
|
|
424
|
+
.filter((token) => token.length > 2 && !INTENT_STOPWORDS.has(token));
|
|
425
|
+
}
|
|
426
|
+
function intentCriterionSatisfied(haystack, criterion) {
|
|
427
|
+
const normalizedHaystack = normalizeIntentText(haystack);
|
|
428
|
+
const tokens = intentTokens(criterion);
|
|
429
|
+
if (tokens.length === 0)
|
|
430
|
+
return false;
|
|
431
|
+
const hits = tokens.filter((token) => normalizedHaystack.includes(token)).length;
|
|
432
|
+
return hits >= Math.min(2, tokens.length) || hits / tokens.length >= 0.5;
|
|
433
|
+
}
|
|
434
|
+
function resultContractText(result) {
|
|
435
|
+
return [
|
|
436
|
+
result.summary,
|
|
437
|
+
JSON.stringify(result.tool_calls ?? []),
|
|
438
|
+
JSON.stringify(result.child_results ?? []),
|
|
439
|
+
].join("\n");
|
|
440
|
+
}
|
|
441
|
+
const ARTIFACT_STUB_PATTERN = /\b(?:todo-only|placeholder|stub(?:bed|by)?|tbd|boilerplate(?:-only)?|scaffold(?:ing)?\s+(?:stub|only))\b/i;
|
|
442
|
+
function isUiPlainTextContract(step, contractStep) {
|
|
443
|
+
const expected = contractStep.expected_output_class ?? inferContractClassFromStep(step);
|
|
444
|
+
return step.role === "ui" && expected === "plain_text_plan";
|
|
445
|
+
}
|
|
446
|
+
function validateUiTopicAnchor(input) {
|
|
447
|
+
if (!isUiPlainTextContract(input.step, input.contract_step)) {
|
|
448
|
+
return { ok: true };
|
|
449
|
+
}
|
|
450
|
+
const acceptanceCriteria = (input.contract_step.acceptance_criteria ?? []).filter((criterion) => typeof criterion === "string" && criterion.trim().length > 0);
|
|
451
|
+
if (acceptanceCriteria.length === 0) {
|
|
452
|
+
return { ok: true };
|
|
453
|
+
}
|
|
454
|
+
const outputText = resultContractText(input.result);
|
|
455
|
+
if (intentTokens(outputText).length < 4) {
|
|
456
|
+
return { ok: true };
|
|
457
|
+
}
|
|
458
|
+
const taskAnchored = intentCriterionSatisfied(outputText, input.step.task);
|
|
459
|
+
const matchedCriteria = acceptanceCriteria.filter((criterion) => intentCriterionSatisfied(outputText, criterion));
|
|
460
|
+
if (!taskAnchored && matchedCriteria.length === 0) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
reason_code: "role_drift_ui_off_topic",
|
|
464
|
+
reason: `UI step ${input.step.step_id} drifted off topic instead of staying anchored to the requested plan.`,
|
|
465
|
+
uncovered_clauses: acceptanceCriteria,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
return { ok: true };
|
|
469
|
+
}
|
|
470
|
+
function inferContractClassFromStep(step) {
|
|
471
|
+
if (step.role === "qa" || step.role === "release")
|
|
472
|
+
return "qa_verdict";
|
|
473
|
+
if ((step.tool_scope ?? []).some((tool) => tool.includes("astgrep") || tool.includes("structural_edit"))) {
|
|
474
|
+
return "structural_edit_plan";
|
|
475
|
+
}
|
|
476
|
+
if ((step.tool_scope ?? []).some((tool) => tool.includes("write") || tool.includes("safe_edit"))) {
|
|
477
|
+
return "code_artifact";
|
|
478
|
+
}
|
|
479
|
+
if ((step.tool_scope ?? []).length > 0)
|
|
480
|
+
return "tool_envelope";
|
|
481
|
+
return "plain_text_plan";
|
|
482
|
+
}
|
|
483
|
+
function requiresContract(step) {
|
|
484
|
+
if ((step.tool_scope ?? []).length > 0)
|
|
485
|
+
return true;
|
|
486
|
+
if (step.role === "coders" || step.role === "builder" || step.role === "qa")
|
|
487
|
+
return true;
|
|
488
|
+
return (step.tool_scope ?? []).some((tool) => /write|edit|safe_edit|astgrep|structural/i.test(tool));
|
|
489
|
+
}
|
|
490
|
+
function validateContractClass(input) {
|
|
491
|
+
const expected = input.contract_step.expected_output_class ?? inferContractClassFromStep(input.step);
|
|
492
|
+
const toolCalls = input.result.tool_calls ?? [];
|
|
493
|
+
const evidenceRefs = input.result.evidence_refs ?? [];
|
|
494
|
+
if (input.contract_step.allowed_tools?.length) {
|
|
495
|
+
const allowed = new Set(input.contract_step.allowed_tools);
|
|
496
|
+
const disallowed = toolCalls.find((call) => !allowed.has(call.tool));
|
|
497
|
+
if (disallowed) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
reason_code: "bridge_output_malformed_json",
|
|
501
|
+
reason: `Tool ${disallowed.tool} is not allowed for ${input.step.step_id}.`,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (expected === "qa_verdict" && toolCalls.some((call) => /write|edit|rewrite|safe_edit/i.test(call.tool))) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
reason_code: "qa_rewrote_artifact",
|
|
509
|
+
reason: `QA step ${input.step.step_id} attempted a mutation tool.`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if ((expected === "tool_envelope" || expected === "structural_edit_plan") && toolCalls.length === 0) {
|
|
513
|
+
return {
|
|
514
|
+
ok: false,
|
|
515
|
+
reason_code: "bridge_output_malformed_json",
|
|
516
|
+
reason: `Step ${input.step.step_id} expected a tool envelope but produced none.`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (expected === "code_artifact" || expected === "structural_edit_plan") {
|
|
520
|
+
const expectedArtifacts = (input.contract_step.expected_artifacts ?? []).filter((artifact) => artifact.required !== false);
|
|
521
|
+
const missingArtifacts = expectedArtifacts
|
|
522
|
+
.map((artifact) => artifact.path)
|
|
523
|
+
.filter((artifactPath) => !evidenceRefs.some((ref) => ref.includes(artifactPath)));
|
|
524
|
+
if (missingArtifacts.length > 0) {
|
|
525
|
+
return {
|
|
526
|
+
ok: false,
|
|
527
|
+
reason_code: "artifact_mismatch",
|
|
528
|
+
reason: `Step ${input.step.step_id} did not prove expected artifact evidence.`,
|
|
529
|
+
uncovered_clauses: missingArtifacts,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (expected === "code_artifact") {
|
|
534
|
+
const mutationSignal = toolCalls.some((call) => /write|edit|patch|create|apply|safe_edit/i.test(call.tool)) || evidenceRefs.length > 0;
|
|
535
|
+
if (mutationSignal && ARTIFACT_STUB_PATTERN.test(resultContractText(input.result))) {
|
|
536
|
+
return {
|
|
537
|
+
ok: false,
|
|
538
|
+
reason_code: "coder_artifact_stub",
|
|
539
|
+
reason: `Step ${input.step.step_id} reported a stub or placeholder artifact instead of a real implementation.`,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const forbiddenPatterns = input.contract_step.forbidden_patterns ?? [];
|
|
544
|
+
const outputText = resultContractText(input.result);
|
|
545
|
+
const forbiddenHit = forbiddenPatterns.find((pattern) => outputText.includes(pattern));
|
|
546
|
+
if (forbiddenHit) {
|
|
547
|
+
return {
|
|
548
|
+
ok: false,
|
|
549
|
+
reason_code: "forbidden_pattern",
|
|
550
|
+
reason: `Step ${input.step.step_id} output matched a forbidden pattern.`,
|
|
551
|
+
uncovered_clauses: [forbiddenHit],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const missingEvidence = (input.contract_step.required_evidence_refs ?? [])
|
|
555
|
+
.filter((required) => !evidenceRefs.some((ref) => ref.includes(required)));
|
|
556
|
+
if (missingEvidence.length > 0) {
|
|
557
|
+
return {
|
|
558
|
+
ok: false,
|
|
559
|
+
reason_code: "required_evidence_missing",
|
|
560
|
+
reason: `Step ${input.step.step_id} is missing required evidence refs.`,
|
|
561
|
+
uncovered_clauses: missingEvidence,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return { ok: true };
|
|
565
|
+
}
|
|
566
|
+
export function verifyIntentAgainstContract(input) {
|
|
567
|
+
const contract = isAcceptanceTraceContract(input.intent_contract)
|
|
568
|
+
? input.intent_contract
|
|
569
|
+
: loadAcceptanceTraceContractImpl(input.plan.plan_id);
|
|
570
|
+
if (!contract) {
|
|
571
|
+
if (requiresContract(input.step)) {
|
|
572
|
+
return {
|
|
573
|
+
outcome: "revisit_step",
|
|
574
|
+
reason: `No acceptance trace contract available for ${input.plan.plan_id}.`,
|
|
575
|
+
reason_code: "contract_missing",
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
outcome: "ok",
|
|
580
|
+
reason: `No acceptance trace contract available for ${input.plan.plan_id}.`,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
const contractStep = contract.steps.find((candidate) => candidate.step_id === input.step.step_id);
|
|
584
|
+
if (!contractStep) {
|
|
585
|
+
return {
|
|
586
|
+
outcome: requiresContract(input.step) ? "revisit_step" : "ok",
|
|
587
|
+
reason: `No acceptance trace step recorded for ${input.step.step_id}.`,
|
|
588
|
+
reason_code: requiresContract(input.step) ? "contract_missing" : undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const classValidation = validateContractClass({
|
|
592
|
+
step: input.step,
|
|
593
|
+
result: input.result,
|
|
594
|
+
contract_step: contractStep,
|
|
595
|
+
});
|
|
596
|
+
if (!classValidation.ok) {
|
|
597
|
+
return {
|
|
598
|
+
outcome: "revisit_step",
|
|
599
|
+
reason: classValidation.reason ?? `Step ${input.step.step_id} failed contract-class validation.`,
|
|
600
|
+
reason_code: classValidation.reason_code,
|
|
601
|
+
uncovered_clauses: classValidation.uncovered_clauses,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
const acceptanceCriteria = (contractStep?.acceptance_criteria ?? []).filter((criterion) => typeof criterion === "string" && criterion.trim().length > 0);
|
|
605
|
+
if (acceptanceCriteria.length === 0) {
|
|
606
|
+
return {
|
|
607
|
+
outcome: "ok",
|
|
608
|
+
reason: `No acceptance criteria recorded for ${input.step.step_id}.`,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
const uiTopicValidation = validateUiTopicAnchor({
|
|
612
|
+
step: input.step,
|
|
613
|
+
result: input.result,
|
|
614
|
+
contract_step: contractStep,
|
|
615
|
+
});
|
|
616
|
+
if (!uiTopicValidation.ok) {
|
|
617
|
+
return {
|
|
618
|
+
outcome: "revisit_step",
|
|
619
|
+
reason: uiTopicValidation.reason ?? `Step ${input.step.step_id} drifted off topic.`,
|
|
620
|
+
reason_code: uiTopicValidation.reason_code,
|
|
621
|
+
uncovered_clauses: uiTopicValidation.uncovered_clauses,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const haystack = [
|
|
625
|
+
input.step.task,
|
|
626
|
+
input.result.summary,
|
|
627
|
+
JSON.stringify(input.result.tool_calls ?? []),
|
|
628
|
+
JSON.stringify(input.result.child_results ?? []),
|
|
629
|
+
].join("\n");
|
|
630
|
+
const uncovered = acceptanceCriteria.filter((criterion) => !intentCriterionSatisfied(haystack, criterion));
|
|
631
|
+
if (uncovered.length === 0) {
|
|
632
|
+
return {
|
|
633
|
+
outcome: "ok",
|
|
634
|
+
reason: `Step ${input.step.step_id} satisfied persisted acceptance criteria.`,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
outcome: "revisit_step",
|
|
639
|
+
reason: `Step ${input.step.step_id} did not satisfy acceptance criteria yet.`,
|
|
640
|
+
reason_code: "contract_invalid",
|
|
641
|
+
uncovered_clauses: uncovered,
|
|
642
|
+
};
|
|
643
|
+
}
|
|
119
644
|
async function tryVericifyPacket(factory, onWarning) {
|
|
120
645
|
try {
|
|
121
646
|
return await factory();
|
|
@@ -282,6 +807,45 @@ export function registerAgentTools(server) {
|
|
|
282
807
|
],
|
|
283
808
|
};
|
|
284
809
|
});
|
|
810
|
+
server.tool("get_transition_log", "Read the transition log for a session. Answers 'why stopped?', 'what changed?', and 'what evidence caused it?' from the ACE transition record store.", {
|
|
811
|
+
session_id: z.string().describe("Session ID to read transitions for"),
|
|
812
|
+
limit: z.number().int().positive().optional().default(20).describe("Max transitions to return (default: 20)"),
|
|
813
|
+
}, async ({ session_id, limit }) => {
|
|
814
|
+
try {
|
|
815
|
+
const transitions = await withLocalModelRuntimeRepository(resolveWorkspaceRoot(), (repo) => repo.getTransitionLog(session_id, limit ?? 20));
|
|
816
|
+
return {
|
|
817
|
+
content: [{ type: "text", text: JSON.stringify(transitions, null, 2) }],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
catch (error) {
|
|
821
|
+
return {
|
|
822
|
+
content: [{ type: "text", text: `Error reading transition log: ${error instanceof Error ? error.message : String(error)}` }],
|
|
823
|
+
isError: true,
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
server.tool("get_capability_snapshot", "Read the capability snapshot for a specific turn. Shows which tools were available, their cost class, and any unavailable tools with reasons.", {
|
|
828
|
+
session_id: z.string().describe("Session ID"),
|
|
829
|
+
turn_number: z.number().int().positive().describe("Turn number"),
|
|
830
|
+
}, async ({ session_id, turn_number }) => {
|
|
831
|
+
try {
|
|
832
|
+
const snapshot = await withLocalModelRuntimeRepository(resolveWorkspaceRoot(), (repo) => repo.getCapabilitySnapshot(session_id, turn_number));
|
|
833
|
+
if (!snapshot) {
|
|
834
|
+
return {
|
|
835
|
+
content: [{ type: "text", text: `No capability snapshot found for session ${session_id} turn ${turn_number}` }],
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
content: [{ type: "text", text: JSON.stringify(snapshot, null, 2) }],
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
return {
|
|
844
|
+
content: [{ type: "text", text: `Error reading capability snapshot: ${error instanceof Error ? error.message : String(error)}` }],
|
|
845
|
+
isError: true,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
});
|
|
285
849
|
server.tool("validate_runtime_profile", "Validate ACE runtime profile markdown content or the current ACE_WORKFLOW.md file", {
|
|
286
850
|
content: z
|
|
287
851
|
.string()
|
|
@@ -493,7 +1057,19 @@ export function registerAgentTools(server) {
|
|
|
493
1057
|
.boolean()
|
|
494
1058
|
.optional()
|
|
495
1059
|
.describe("If false, keep the managed workspace session after completion"),
|
|
496
|
-
|
|
1060
|
+
emit_to: z
|
|
1061
|
+
.array(z.enum(["tui", "tracker", "handoff", "vericify"]))
|
|
1062
|
+
.optional()
|
|
1063
|
+
.describe("Output emission targets for this session. Defaults to ['tui', 'vericify']."),
|
|
1064
|
+
silent_unless_blocked: z
|
|
1065
|
+
.boolean()
|
|
1066
|
+
.optional()
|
|
1067
|
+
.describe("If true, turns that produce no tool calls are classified as no_op_success and do not emit."),
|
|
1068
|
+
require_approval_before_emit: z
|
|
1069
|
+
.boolean()
|
|
1070
|
+
.optional()
|
|
1071
|
+
.describe("If true, require operator approval before emitting meaningful_completion output."),
|
|
1072
|
+
}, async ({ task, context_json, workspace_name, workspace_path, objective_id, tracker_item_id, max_turns, turn_timeout_ms, auto_cleanup, emit_to, silent_unless_blocked, require_approval_before_emit, }) => {
|
|
497
1073
|
const result = await startUnattendedSession({
|
|
498
1074
|
task,
|
|
499
1075
|
context: parseOptionalJsonObject(context_json),
|
|
@@ -504,6 +1080,9 @@ export function registerAgentTools(server) {
|
|
|
504
1080
|
max_turns,
|
|
505
1081
|
turn_timeout_ms,
|
|
506
1082
|
auto_cleanup,
|
|
1083
|
+
emit_to,
|
|
1084
|
+
silent_unless_blocked,
|
|
1085
|
+
require_approval_before_emit,
|
|
507
1086
|
});
|
|
508
1087
|
return {
|
|
509
1088
|
content: [
|
|
@@ -557,6 +1136,56 @@ export function registerAgentTools(server) {
|
|
|
557
1136
|
],
|
|
558
1137
|
};
|
|
559
1138
|
});
|
|
1139
|
+
server.tool("propose_plan", "Use the ACE Planner role to decompose a task into a multi-step PlanProposal with intent_summary, success_criteria, per-step acceptance_criteria, and explicit stop conditions. On model failure, falls back to the deterministic goal compiler scaffold.", {
|
|
1140
|
+
task: z.string().describe("The task to decompose into a plan"),
|
|
1141
|
+
session_id: z.string().optional().describe("Optional session ID for transition record linkage"),
|
|
1142
|
+
}, async ({ task, session_id }) => {
|
|
1143
|
+
const proposal = await proposePlanImpl(task, session_id);
|
|
1144
|
+
return {
|
|
1145
|
+
content: [
|
|
1146
|
+
{
|
|
1147
|
+
type: "text",
|
|
1148
|
+
text: JSON.stringify(proposal, null, 2),
|
|
1149
|
+
},
|
|
1150
|
+
],
|
|
1151
|
+
};
|
|
1152
|
+
});
|
|
1153
|
+
server.tool("validate_plan", "Run shape checks against a PlanProposal (coverage, verification chain, tool-scope realism, acceptance criteria presence, stop conditions visibility). Returns { ok, score, blocking_findings, soft_findings }. Persists a transition record and Vericify process post.", {
|
|
1154
|
+
plan_id: z.string().optional().describe("ID of a previously proposed plan to validate"),
|
|
1155
|
+
proposal: z
|
|
1156
|
+
.object({
|
|
1157
|
+
plan_id: z.string(),
|
|
1158
|
+
status: z.literal("planning"),
|
|
1159
|
+
intent_summary: z.string(),
|
|
1160
|
+
success_criteria: z.array(z.string()),
|
|
1161
|
+
steps: z.array(z.object({
|
|
1162
|
+
role: z.string(),
|
|
1163
|
+
task: z.string(),
|
|
1164
|
+
depends_on: z.array(z.string()).optional(),
|
|
1165
|
+
tool_scope: z.array(z.string()).optional(),
|
|
1166
|
+
acceptance_criteria: z.array(z.string()),
|
|
1167
|
+
stop_condition: z.array(z.string()).optional(),
|
|
1168
|
+
})),
|
|
1169
|
+
plan_source: z.string(),
|
|
1170
|
+
})
|
|
1171
|
+
.optional()
|
|
1172
|
+
.describe("Inline proposal to validate; mutually exclusive with plan_id"),
|
|
1173
|
+
session_id: z.string().optional().describe("Optional session ID for transition record linkage"),
|
|
1174
|
+
}, async ({ plan_id, proposal, session_id }) => {
|
|
1175
|
+
const result = await validatePlanImpl({
|
|
1176
|
+
plan_id,
|
|
1177
|
+
proposal: proposal,
|
|
1178
|
+
sessionId: session_id,
|
|
1179
|
+
});
|
|
1180
|
+
return {
|
|
1181
|
+
content: [
|
|
1182
|
+
{
|
|
1183
|
+
type: "text",
|
|
1184
|
+
text: JSON.stringify(result, null, 2),
|
|
1185
|
+
},
|
|
1186
|
+
],
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
560
1189
|
server.tool("run_local_model", "Offload a governed ACE subtask to the provider-backed ACE bridge and return the result", {
|
|
561
1190
|
task: z.string().describe("Task to execute with the ACE model bridge"),
|
|
562
1191
|
role: ROLE_ENUM.optional().describe("Optional ACE role; defaults to orchestrator"),
|
|
@@ -578,6 +1207,10 @@ export function registerAgentTools(server) {
|
|
|
578
1207
|
.string()
|
|
579
1208
|
.optional()
|
|
580
1209
|
.describe("Optional model override; otherwise discovered from workspace/runtime context"),
|
|
1210
|
+
model_class: z
|
|
1211
|
+
.enum(["frontier", "mid", "small_local"])
|
|
1212
|
+
.optional()
|
|
1213
|
+
.describe("Optional capability class override; provider name alone is not used as capability authority"),
|
|
581
1214
|
base_url: z
|
|
582
1215
|
.string()
|
|
583
1216
|
.optional()
|
|
@@ -594,13 +1227,14 @@ export function registerAgentTools(server) {
|
|
|
594
1227
|
.string()
|
|
595
1228
|
.optional()
|
|
596
1229
|
.describe("Optional workspace root override; defaults to the active workspace"),
|
|
597
|
-
}, async ({ task, role, max_turns, tier, provider, model, base_url, ollama_url, tool_scope, workspace_root, }) => {
|
|
1230
|
+
}, async ({ task, role, max_turns, tier, provider, model, model_class, base_url, ollama_url, tool_scope, workspace_root, }) => {
|
|
598
1231
|
const delegated = await runLocalModelTask({
|
|
599
1232
|
task,
|
|
600
1233
|
role,
|
|
601
1234
|
workspaceRoot: workspace_root,
|
|
602
1235
|
provider,
|
|
603
1236
|
model,
|
|
1237
|
+
modelClass: model_class,
|
|
604
1238
|
baseUrl: base_url,
|
|
605
1239
|
ollamaUrl: ollama_url,
|
|
606
1240
|
maxTurns: max_turns,
|
|
@@ -616,6 +1250,9 @@ export function registerAgentTools(server) {
|
|
|
616
1250
|
`- role: ${delegated.role}`,
|
|
617
1251
|
`- provider: ${delegated.runtime.provider}`,
|
|
618
1252
|
`- model: ${delegated.runtime.model}`,
|
|
1253
|
+
`- model_class: ${delegated.policy.model_class}`,
|
|
1254
|
+
`- tier: ${delegated.policy.tier}`,
|
|
1255
|
+
`- mutation_lane: ${delegated.policy.mutation_lane}`,
|
|
619
1256
|
`- workspace: ${delegated.runtime.workspaceRoot}`,
|
|
620
1257
|
`- status: ${delegated.result.status}`,
|
|
621
1258
|
`- turns: ${delegated.result.turns}`,
|
|
@@ -646,7 +1283,7 @@ export function registerAgentTools(server) {
|
|
|
646
1283
|
],
|
|
647
1284
|
};
|
|
648
1285
|
});
|
|
649
|
-
server.tool("run_orchestrator", "Execute a supervised plan via model bridge child runs; when steps are omitted, the plan
|
|
1286
|
+
server.tool("run_orchestrator", "Execute a supervised plan via model bridge child runs; when steps are omitted, the plan is compiled through the deterministic goal scaffold first", {
|
|
650
1287
|
task: z.string().describe("The task to decompose and execute"),
|
|
651
1288
|
steps: z
|
|
652
1289
|
.array(z.object({
|
|
@@ -664,9 +1301,44 @@ export function registerAgentTools(server) {
|
|
|
664
1301
|
.array(z.string())
|
|
665
1302
|
.optional()
|
|
666
1303
|
.describe("Optional ACE tool allowlist for the step"),
|
|
1304
|
+
expected_output_class: z
|
|
1305
|
+
.enum(["plain_text_plan", "tool_envelope", "code_artifact", "structural_edit_plan", "qa_verdict"])
|
|
1306
|
+
.optional()
|
|
1307
|
+
.describe("Optional expected output contract class for intent verification"),
|
|
1308
|
+
expected_artifacts: z
|
|
1309
|
+
.array(z.object({
|
|
1310
|
+
path: z.string(),
|
|
1311
|
+
required: z.boolean().optional(),
|
|
1312
|
+
evidence_ref_kind: z.enum(["artifact", "diff", "hash", "test", "gate"]).optional(),
|
|
1313
|
+
}))
|
|
1314
|
+
.optional()
|
|
1315
|
+
.describe("Optional artifact evidence expected from this step"),
|
|
1316
|
+
allowed_tools: z
|
|
1317
|
+
.array(z.string())
|
|
1318
|
+
.optional()
|
|
1319
|
+
.describe("Optional stricter tool allowlist for contract verification"),
|
|
1320
|
+
forbidden_patterns: z
|
|
1321
|
+
.array(z.string())
|
|
1322
|
+
.optional()
|
|
1323
|
+
.describe("Optional forbidden output substrings for contract verification"),
|
|
1324
|
+
required_evidence_refs: z
|
|
1325
|
+
.array(z.string())
|
|
1326
|
+
.optional()
|
|
1327
|
+
.describe("Optional evidence ref substrings required from this step"),
|
|
1328
|
+
structural_edit_plan_required: z
|
|
1329
|
+
.boolean()
|
|
1330
|
+
.optional()
|
|
1331
|
+
.describe("Require this code-mutating step to route through a structural edit plan"),
|
|
1332
|
+
structural_edit_waiver: z
|
|
1333
|
+
.object({
|
|
1334
|
+
reason: z.string(),
|
|
1335
|
+
evidence_ref: z.string(),
|
|
1336
|
+
})
|
|
1337
|
+
.optional()
|
|
1338
|
+
.describe("Evidence-backed waiver when a code-mutating step cannot use structural edits"),
|
|
667
1339
|
}))
|
|
668
1340
|
.optional()
|
|
669
|
-
.describe("Pre-defined steps; if omitted, the orchestrator
|
|
1341
|
+
.describe("Pre-defined steps; if omitted, the orchestrator compiles a deterministic goal scaffold first"),
|
|
670
1342
|
execution_mode: z
|
|
671
1343
|
.enum(["sequential", "scheduled"])
|
|
672
1344
|
.optional()
|
|
@@ -716,27 +1388,53 @@ export function registerAgentTools(server) {
|
|
|
716
1388
|
const effectiveWorkspaceRoot = runtime?.workspaceRoot ??
|
|
717
1389
|
(workspace_root ? resolveRuntimeWorkspaceRoot(workspace_root) : resolveWorkspaceRoot());
|
|
718
1390
|
const sessionId = typeof extra?.sessionId === "string" ? extra.sessionId : undefined;
|
|
719
|
-
const planSource =
|
|
720
|
-
const planSteps = Array.isArray(steps) && steps.length > 0
|
|
721
|
-
? steps
|
|
722
|
-
: await buildOrchestratorSteps(task, sessionId);
|
|
1391
|
+
const { planSource, normalization } = await normalizeOrchestratorPlanSteps(task, steps, sessionId);
|
|
723
1392
|
const plan = createTaskPlan({
|
|
724
1393
|
task,
|
|
725
|
-
steps:
|
|
1394
|
+
steps: normalization.steps,
|
|
726
1395
|
execution_mode: execution_mode ?? "sequential",
|
|
727
1396
|
});
|
|
1397
|
+
let traceArtifactPath = await persistAcceptanceTraceMap({
|
|
1398
|
+
plan,
|
|
1399
|
+
task,
|
|
1400
|
+
planSource,
|
|
1401
|
+
insertedResearch: normalization.insertedResearch,
|
|
1402
|
+
shipFanoutEnabled: normalization.shipFanoutEnabled,
|
|
1403
|
+
normalization,
|
|
1404
|
+
});
|
|
1405
|
+
const intentContract = loadAcceptanceTraceContractImpl(plan.plan_id);
|
|
728
1406
|
const bridge = runtime
|
|
729
1407
|
? new ModelBridge(createDefaultModelBridgeClients(runtime))
|
|
730
1408
|
: undefined;
|
|
731
1409
|
const fallbackHandoffPrefix = `LOCAL-${plan.plan_id}-`;
|
|
732
1410
|
const vericifyWarnings = [];
|
|
1411
|
+
const appendSessionPlanTransition = async (stepId, from, to, reason, reasonCode) => {
|
|
1412
|
+
if (!sessionId)
|
|
1413
|
+
return;
|
|
1414
|
+
await withLocalModelRuntimeRepository(effectiveWorkspaceRoot, async (repo) => {
|
|
1415
|
+
const step = plan.steps.find((candidate) => candidate.step_id === stepId);
|
|
1416
|
+
if (!step)
|
|
1417
|
+
return;
|
|
1418
|
+
await repo.appendTransitionRecord({
|
|
1419
|
+
session_id: sessionId,
|
|
1420
|
+
subject_kind: "plan_step",
|
|
1421
|
+
subject_id: `${plan.plan_id}/${step.step_id}`,
|
|
1422
|
+
from,
|
|
1423
|
+
to,
|
|
1424
|
+
reason,
|
|
1425
|
+
reason_code: reasonCode,
|
|
1426
|
+
evidence_refs: step.tool_scope ?? [],
|
|
1427
|
+
});
|
|
1428
|
+
}).catch(() => undefined);
|
|
1429
|
+
};
|
|
733
1430
|
const supervised = await superviseTaskPlan(plan, {
|
|
734
1431
|
async spawnStep(step) {
|
|
735
1432
|
if (bridge && runtime) {
|
|
736
1433
|
return bridge.spawn({
|
|
737
|
-
task: step
|
|
1434
|
+
task: formatStepTaskForBridge(step),
|
|
738
1435
|
role: step.role,
|
|
739
1436
|
workspace: runtime.workspaceRoot,
|
|
1437
|
+
tier: resolveTier(undefined, runtime.provider, runtime.model, step.role),
|
|
740
1438
|
maxTurns: max_turns_per_step ?? 6,
|
|
741
1439
|
provider: runtime.provider,
|
|
742
1440
|
model: runtime.model,
|
|
@@ -775,12 +1473,29 @@ export function registerAgentTools(server) {
|
|
|
775
1473
|
note,
|
|
776
1474
|
}, sessionId);
|
|
777
1475
|
},
|
|
778
|
-
amendPlan({ plan: activePlan, step, result }) {
|
|
779
|
-
|
|
1476
|
+
async amendPlan({ plan: activePlan, step, result }) {
|
|
1477
|
+
await appendSessionPlanTransition(step.step_id, "running", step.status, `Plan step ${step.step_id} ${step.status}: ${result.summary}`, step.status === "done"
|
|
1478
|
+
? "step_completed"
|
|
1479
|
+
: step.status === "blocked"
|
|
1480
|
+
? "step_blocked"
|
|
1481
|
+
: "step_failed");
|
|
1482
|
+
const amendment = buildDefaultOrchestratorAmendment({
|
|
780
1483
|
plan: activePlan,
|
|
781
1484
|
step,
|
|
782
1485
|
result,
|
|
783
1486
|
});
|
|
1487
|
+
if (!amendment)
|
|
1488
|
+
return undefined;
|
|
1489
|
+
const amendedPlan = amendTaskPlan(activePlan, amendment);
|
|
1490
|
+
traceArtifactPath = await persistAcceptanceTraceMap({
|
|
1491
|
+
plan: amendedPlan,
|
|
1492
|
+
task,
|
|
1493
|
+
planSource,
|
|
1494
|
+
insertedResearch: normalization.insertedResearch,
|
|
1495
|
+
shipFanoutEnabled: normalization.shipFanoutEnabled,
|
|
1496
|
+
normalization,
|
|
1497
|
+
});
|
|
1498
|
+
return amendedPlan;
|
|
784
1499
|
},
|
|
785
1500
|
async getVericifyContext() {
|
|
786
1501
|
return tryVericifyPacket(() => getVericifyContextPacket({
|
|
@@ -793,6 +1508,34 @@ export function registerAgentTools(server) {
|
|
|
793
1508
|
workspaceRoot: effectiveWorkspaceRoot,
|
|
794
1509
|
}), (message) => appendUniqueNote(vericifyWarnings, `Vericify delta unavailable for ${plan.plan_id}: ${message}`));
|
|
795
1510
|
},
|
|
1511
|
+
async verifyIntent({ plan: activePlan, step, result, intent_contract }) {
|
|
1512
|
+
const verification = verifyIntentAgainstContract({
|
|
1513
|
+
plan: activePlan,
|
|
1514
|
+
step,
|
|
1515
|
+
result,
|
|
1516
|
+
intent_contract: intent_contract ?? loadAcceptanceTraceContractImpl(activePlan.plan_id) ?? intentContract,
|
|
1517
|
+
});
|
|
1518
|
+
// Transition recording is deferred to recordIntentVerificationFailure so
|
|
1519
|
+
// the supervisor can supply the correct from/to based on retry state.
|
|
1520
|
+
return verification;
|
|
1521
|
+
},
|
|
1522
|
+
async replanForClauses({ uncovered_clauses }) {
|
|
1523
|
+
if (!uncovered_clauses.length) {
|
|
1524
|
+
return undefined;
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
append_steps: [
|
|
1528
|
+
{
|
|
1529
|
+
role: "research",
|
|
1530
|
+
task: `Resolve uncovered acceptance clauses: ${uncovered_clauses.join("; ")}`,
|
|
1531
|
+
tool_scope: ["recall_context", "read_workspace_file", "build_continuity_packet"],
|
|
1532
|
+
},
|
|
1533
|
+
],
|
|
1534
|
+
};
|
|
1535
|
+
},
|
|
1536
|
+
async recordIntentVerificationFailure({ step, verification, from, to }) {
|
|
1537
|
+
await appendSessionPlanTransition(step.step_id, from, to, verification.reason, verification.reason_code);
|
|
1538
|
+
},
|
|
796
1539
|
async openCircuitBreaker(reason) {
|
|
797
1540
|
await executeAceInternalTool("open_circuit_breaker", {
|
|
798
1541
|
reason,
|
|
@@ -807,7 +1550,7 @@ export function registerAgentTools(server) {
|
|
|
807
1550
|
async executeGates() {
|
|
808
1551
|
const result = await executeAceInternalTool("execute_gates", {}, sessionId);
|
|
809
1552
|
return {
|
|
810
|
-
ok:
|
|
1553
|
+
ok: didExecuteGatesPass(result),
|
|
811
1554
|
summary: extractToolTextContent(result),
|
|
812
1555
|
};
|
|
813
1556
|
},
|
|
@@ -822,6 +1565,9 @@ export function registerAgentTools(server) {
|
|
|
822
1565
|
}, sessionId);
|
|
823
1566
|
},
|
|
824
1567
|
async emitStatusEvent(event) {
|
|
1568
|
+
if (event.step_id) {
|
|
1569
|
+
await appendSessionPlanTransition(event.step_id, "planned", "running", `Plan step ${event.step_id} started: ${event.summary}`, "step_started");
|
|
1570
|
+
}
|
|
825
1571
|
await executeAceInternalTool("emit_status_event", {
|
|
826
1572
|
source_module: "capability-ops",
|
|
827
1573
|
event_type: "ORCHESTRATOR_STEP",
|
|
@@ -831,6 +1577,14 @@ export function registerAgentTools(server) {
|
|
|
831
1577
|
}, sessionId);
|
|
832
1578
|
},
|
|
833
1579
|
});
|
|
1580
|
+
traceArtifactPath = await persistAcceptanceTraceMap({
|
|
1581
|
+
plan: supervised.plan,
|
|
1582
|
+
task,
|
|
1583
|
+
planSource,
|
|
1584
|
+
insertedResearch: normalization.insertedResearch,
|
|
1585
|
+
shipFanoutEnabled: normalization.shipFanoutEnabled,
|
|
1586
|
+
normalization,
|
|
1587
|
+
});
|
|
834
1588
|
const step_summaries = supervised.plan.steps.map((step) => ({
|
|
835
1589
|
step_id: step.step_id,
|
|
836
1590
|
role: step.role,
|
|
@@ -851,15 +1605,25 @@ export function registerAgentTools(server) {
|
|
|
851
1605
|
runtime_warnings: runtimeWarnings,
|
|
852
1606
|
workspace_root: effectiveWorkspaceRoot,
|
|
853
1607
|
plan_source: planSource,
|
|
854
|
-
planning_note: planSource === "orchestrator_default_step"
|
|
855
|
-
? "Auto-planning
|
|
856
|
-
:
|
|
1608
|
+
planning_note: planSource === "orchestrator_default_step" || planSource === "deterministic_fallback" || planSource === "planner_model"
|
|
1609
|
+
? "Auto-planning now starts with a deterministic goal compiler scaffold; planner_model means the scaffold was refined."
|
|
1610
|
+
: normalization.insertedResearch
|
|
1611
|
+
? "Research was inserted ahead of spec work to require source-backed evidence before specification."
|
|
1612
|
+
: normalization.shipFanoutEnabled
|
|
1613
|
+
? "Ship fan-out enforcement added review, security, QA, and release coordination steps."
|
|
1614
|
+
: null,
|
|
1615
|
+
trace_artifact_path: traceArtifactPath,
|
|
1616
|
+
engskills_imports: {
|
|
1617
|
+
inserted_research_before_spec: normalization.insertedResearch,
|
|
1618
|
+
ship_fanout_enabled: normalization.shipFanoutEnabled,
|
|
1619
|
+
},
|
|
857
1620
|
plan: supervised.plan,
|
|
858
1621
|
step_summaries,
|
|
859
1622
|
handoff_ids: supervised.handoff_ids,
|
|
860
1623
|
job_ids: supervised.job_ids,
|
|
861
1624
|
circuit_opened: supervised.circuit_opened,
|
|
862
1625
|
final_gate: supervised.final_gate ?? null,
|
|
1626
|
+
plan_validation_verdict: normalization.validationVerdict ?? null,
|
|
863
1627
|
vericify_warnings: vericifyWarnings,
|
|
864
1628
|
}, null, 2),
|
|
865
1629
|
},
|
|
@@ -1153,7 +1917,7 @@ export function registerAgentTools(server) {
|
|
|
1153
1917
|
.optional()
|
|
1154
1918
|
.describe("Optional hook timeout override in milliseconds"),
|
|
1155
1919
|
}, async ({ workspace_name, workspace_path, source, objective_id, tracker_item_id, root, hooks_timeout_ms, }) => {
|
|
1156
|
-
const result =
|
|
1920
|
+
const result = await createWorkspaceSessionAsync({
|
|
1157
1921
|
workspace_name,
|
|
1158
1922
|
workspace_path,
|
|
1159
1923
|
source,
|
|
@@ -1204,7 +1968,7 @@ export function registerAgentTools(server) {
|
|
|
1204
1968
|
.optional()
|
|
1205
1969
|
.describe("Optional hook timeout override in milliseconds"),
|
|
1206
1970
|
}, async ({ session_id, workspace_path, root, hooks_timeout_ms }) => {
|
|
1207
|
-
const result = removeWorkspaceSession({
|
|
1971
|
+
const result = await removeWorkspaceSession({
|
|
1208
1972
|
session_id,
|
|
1209
1973
|
workspace_path,
|
|
1210
1974
|
root,
|
|
@@ -1540,7 +2304,7 @@ export function registerAgentTools(server) {
|
|
|
1540
2304
|
"",
|
|
1541
2305
|
].join("\n");
|
|
1542
2306
|
const existing = readTaskArtifact("lessons");
|
|
1543
|
-
const path =
|
|
2307
|
+
const path = await safeWriteAsync(resolveWritableTaskPath("lessons"), `${existing}${entry}`);
|
|
1544
2308
|
return {
|
|
1545
2309
|
content: [{ type: "text", text: `✅ Lesson recorded in ${path}` }],
|
|
1546
2310
|
};
|