@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
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { isReadError, normalizePathForValidation, resolveWorkspaceArtifactPath, resolveWorkspaceRoot, safeRead, safeWriteAsync, wsPath, } from "./helpers.js";
|
|
6
|
+
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
7
|
+
import { runShellCommand } from "./runtime-command.js";
|
|
8
|
+
import { executeRuntimeTool, loadRuntimeToolRegistry, persistRuntimeToolInvocationArtifacts, } from "./runtime-tool-specs.js";
|
|
9
|
+
import { refreshAstgrepIndex } from "./astgrep-index.js";
|
|
10
|
+
const BLOCKED_HOSTNAMES = new Set(["localhost", "localhost.localdomain"]);
|
|
11
|
+
const SAFE_EXTERNAL_ENV_KEYS = new Set(["PATH", "HOME", "TMPDIR", "TEMP", "TMP", "USER", "LANG", "LC_ALL"]);
|
|
12
|
+
const DEFAULT_WEB_RESEARCH_TOOL = "web_research_packet";
|
|
13
|
+
const DEFAULT_CODEMUNCH_TOOL = "codemunch_snapshot";
|
|
14
|
+
const DEFAULT_CODEMUNCH_COMMAND = "npx -y codemunch";
|
|
15
|
+
const WEB_RESEARCH_MAX_CITATIONS = 12;
|
|
16
|
+
const WEB_RESEARCH_MAX_SEARCH_RESULTS = 20;
|
|
17
|
+
const WEB_RESEARCH_MAX_FETCH_DOCS = 10;
|
|
18
|
+
const WEB_RESEARCH_MAX_SNIPPET_CHARS = 280;
|
|
19
|
+
const WEB_RESEARCH_MAX_FETCH_CHARS = 6000;
|
|
20
|
+
const defaultDeps = {
|
|
21
|
+
executeRuntimeTool,
|
|
22
|
+
loadRuntimeToolRegistry,
|
|
23
|
+
persistRuntimeToolInvocationArtifacts,
|
|
24
|
+
runShellCommand,
|
|
25
|
+
refreshAstgrepIndex,
|
|
26
|
+
now: () => new Date(),
|
|
27
|
+
randomId: () => randomUUID(),
|
|
28
|
+
};
|
|
29
|
+
function sha256(content) {
|
|
30
|
+
return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
|
|
31
|
+
}
|
|
32
|
+
function isRecord(value) {
|
|
33
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
function pickString(record, keys) {
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const value = record[key];
|
|
38
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
39
|
+
return value.trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function pickNumber(record, keys) {
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
const value = record[key];
|
|
47
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function truncate(value, maxChars) {
|
|
53
|
+
if (!value)
|
|
54
|
+
return undefined;
|
|
55
|
+
const trimmed = value.trim();
|
|
56
|
+
if (trimmed.length <= maxChars)
|
|
57
|
+
return trimmed;
|
|
58
|
+
return `${trimmed.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
59
|
+
}
|
|
60
|
+
function slugify(value, fallback = "artifact") {
|
|
61
|
+
const slug = value
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.replace(/https?:\/\//g, "")
|
|
64
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
65
|
+
.replace(/^-+|-+$/g, "")
|
|
66
|
+
.slice(0, 48);
|
|
67
|
+
return slug || fallback;
|
|
68
|
+
}
|
|
69
|
+
function artifactStamp(now, id, seed) {
|
|
70
|
+
return `${now.toISOString().replace(/[:.]/g, "-")}-${slugify(seed)}-${id.slice(0, 8)}`;
|
|
71
|
+
}
|
|
72
|
+
function runtimeToolConfigured(name, deps) {
|
|
73
|
+
const registry = deps.loadRuntimeToolRegistry();
|
|
74
|
+
return Boolean(registry.ok && registry.registry?.tools.some((tool) => tool.name === name));
|
|
75
|
+
}
|
|
76
|
+
function extractArrays(output, keys) {
|
|
77
|
+
const rows = [];
|
|
78
|
+
for (const key of keys) {
|
|
79
|
+
const value = output[key];
|
|
80
|
+
if (!Array.isArray(value))
|
|
81
|
+
continue;
|
|
82
|
+
for (const entry of value) {
|
|
83
|
+
if (isRecord(entry))
|
|
84
|
+
rows.push(entry);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
function normalizeCitations(output) {
|
|
90
|
+
const rows = extractArrays(output, ["citations", "results", "search_results", "sources"]);
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
const citations = [];
|
|
93
|
+
for (const row of rows) {
|
|
94
|
+
const url = pickString(row, ["url", "link", "source_url"]);
|
|
95
|
+
if (!url || seen.has(url))
|
|
96
|
+
continue;
|
|
97
|
+
seen.add(url);
|
|
98
|
+
citations.push({
|
|
99
|
+
title: pickString(row, ["title", "name", "label"]) ?? url,
|
|
100
|
+
url,
|
|
101
|
+
snippet: truncate(pickString(row, ["snippet", "summary", "excerpt", "description"]), WEB_RESEARCH_MAX_SNIPPET_CHARS),
|
|
102
|
+
});
|
|
103
|
+
if (citations.length >= WEB_RESEARCH_MAX_CITATIONS)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return citations;
|
|
107
|
+
}
|
|
108
|
+
function normalizeFetchDocs(output) {
|
|
109
|
+
const rows = extractArrays(output, ["fetches", "fetched", "pages", "documents", "fetched_pages"]);
|
|
110
|
+
return rows.slice(0, WEB_RESEARCH_MAX_FETCH_DOCS).map((row) => ({
|
|
111
|
+
url: pickString(row, ["url", "source_url", "link"]),
|
|
112
|
+
title: pickString(row, ["title", "name"]),
|
|
113
|
+
content: truncate(pickString(row, ["content", "text", "excerpt", "markdown", "body", "html"]), WEB_RESEARCH_MAX_FETCH_CHARS),
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
function readWorkspaceContent(path) {
|
|
117
|
+
const raw = safeRead(path);
|
|
118
|
+
if (isReadError(raw)) {
|
|
119
|
+
throw new Error(raw);
|
|
120
|
+
}
|
|
121
|
+
return raw;
|
|
122
|
+
}
|
|
123
|
+
function resolveOutputCxml(output) {
|
|
124
|
+
const inline = pickString(output, ["cxml", "snapshot_cxml"]);
|
|
125
|
+
if (inline)
|
|
126
|
+
return { content: inline };
|
|
127
|
+
const candidatePath = pickString(output, ["cxml_path", "snapshot_path", "output_path", "path"]);
|
|
128
|
+
if (!candidatePath) {
|
|
129
|
+
throw new Error("Codemunch wrapper expected 'cxml' or 'cxml_path' in the tool output.");
|
|
130
|
+
}
|
|
131
|
+
const resolved = resolveWorkspaceArtifactPath(candidatePath, "read");
|
|
132
|
+
if (!existsSync(resolved)) {
|
|
133
|
+
throw new Error(`Codemunch output path not found: ${candidatePath}`);
|
|
134
|
+
}
|
|
135
|
+
return { content: readFileSync(resolved, "utf-8"), source_path: resolved };
|
|
136
|
+
}
|
|
137
|
+
function shellQuote(value) {
|
|
138
|
+
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
139
|
+
}
|
|
140
|
+
function isPrivateIp(hostname) {
|
|
141
|
+
const kind = isIP(hostname);
|
|
142
|
+
if (kind === 0)
|
|
143
|
+
return false;
|
|
144
|
+
if (kind === 6) {
|
|
145
|
+
const lower = hostname.toLowerCase();
|
|
146
|
+
return lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
|
|
147
|
+
}
|
|
148
|
+
const parts = hostname.split(".").map((part) => Number(part));
|
|
149
|
+
const [a, b] = parts;
|
|
150
|
+
return (a === 10 ||
|
|
151
|
+
a === 127 ||
|
|
152
|
+
(a === 169 && b === 254) ||
|
|
153
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
154
|
+
(a === 192 && b === 168) ||
|
|
155
|
+
a === 0);
|
|
156
|
+
}
|
|
157
|
+
function assertPublicHttpUrl(raw, label) {
|
|
158
|
+
let parsed;
|
|
159
|
+
try {
|
|
160
|
+
parsed = new URL(raw);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
throw new Error(`${label} must be a valid URL.`);
|
|
164
|
+
}
|
|
165
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
166
|
+
throw new Error(`${label} must use http or https.`);
|
|
167
|
+
}
|
|
168
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
169
|
+
if (BLOCKED_HOSTNAMES.has(hostname) || isPrivateIp(hostname)) {
|
|
170
|
+
throw new Error(`${label} targets a private or local address.`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function assertGitHubUrl(raw) {
|
|
174
|
+
assertPublicHttpUrl(raw, "repo_url");
|
|
175
|
+
const parsed = new URL(raw);
|
|
176
|
+
if (parsed.hostname.toLowerCase() !== "github.com") {
|
|
177
|
+
throw new Error("repo_url must be a GitHub URL for codemunch snapshots.");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function safeExternalEnv(extra = {}) {
|
|
181
|
+
const env = {};
|
|
182
|
+
for (const key of SAFE_EXTERNAL_ENV_KEYS) {
|
|
183
|
+
const value = process.env[key];
|
|
184
|
+
if (typeof value === "string")
|
|
185
|
+
env[key] = value;
|
|
186
|
+
}
|
|
187
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
188
|
+
env[key] = value;
|
|
189
|
+
}
|
|
190
|
+
return env;
|
|
191
|
+
}
|
|
192
|
+
async function appendWrapperLedgerEntry(tool, message, artifacts, metadata) {
|
|
193
|
+
await appendRunLedgerEntrySafe({
|
|
194
|
+
tool,
|
|
195
|
+
category: "info",
|
|
196
|
+
message,
|
|
197
|
+
artifacts: artifacts.filter((artifact) => Boolean(artifact)).map((artifact) => normalizePathForValidation(artifact)),
|
|
198
|
+
metadata,
|
|
199
|
+
}).catch(() => undefined);
|
|
200
|
+
}
|
|
201
|
+
export async function createWebResearchPacket(input, deps = {}) {
|
|
202
|
+
const resolved = { ...defaultDeps, ...deps };
|
|
203
|
+
try {
|
|
204
|
+
for (const url of input.fetch_urls ?? []) {
|
|
205
|
+
assertPublicHttpUrl(url, "fetch_urls");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
summary: "Web research request failed SSRF guard validation.",
|
|
212
|
+
tool_name: DEFAULT_WEB_RESEARCH_TOOL,
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!runtimeToolConfigured(DEFAULT_WEB_RESEARCH_TOOL, resolved)) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
summary: `Runtime tool '${DEFAULT_WEB_RESEARCH_TOOL}' is not configured.`,
|
|
220
|
+
tool_name: DEFAULT_WEB_RESEARCH_TOOL,
|
|
221
|
+
error: `Unknown runtime tool spec: ${DEFAULT_WEB_RESEARCH_TOOL}`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const runtimeResult = await resolved.executeRuntimeTool(DEFAULT_WEB_RESEARCH_TOOL, {
|
|
225
|
+
query: input.query,
|
|
226
|
+
sources_required: input.sources_required,
|
|
227
|
+
fetch_urls: input.fetch_urls,
|
|
228
|
+
}, {
|
|
229
|
+
session_id: input.session_id,
|
|
230
|
+
turn_number: input.turn_number,
|
|
231
|
+
workspace_path: input.workspace_path,
|
|
232
|
+
});
|
|
233
|
+
if (!runtimeResult.ok || !isRecord(runtimeResult.output)) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
summary: runtimeResult.summary,
|
|
237
|
+
tool_name: DEFAULT_WEB_RESEARCH_TOOL,
|
|
238
|
+
request_path: runtimeResult.request_path,
|
|
239
|
+
response_path: runtimeResult.response_path,
|
|
240
|
+
error: runtimeResult.error ? JSON.stringify(runtimeResult.error) : "Invalid runtime tool output.",
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const now = resolved.now();
|
|
244
|
+
const id = artifactStamp(now, resolved.randomId(), input.query);
|
|
245
|
+
const researchDir = `agent-state/research/${id}`;
|
|
246
|
+
const citations = normalizeCitations(runtimeResult.output);
|
|
247
|
+
const searchResultsPath = await safeWriteAsync(`${researchDir}/search-results.json`, JSON.stringify({
|
|
248
|
+
query: input.query,
|
|
249
|
+
generated_at: now.toISOString(),
|
|
250
|
+
sources_required: input.sources_required,
|
|
251
|
+
results: citations.slice(0, WEB_RESEARCH_MAX_SEARCH_RESULTS),
|
|
252
|
+
}, null, 2));
|
|
253
|
+
const fetchedPaths = [];
|
|
254
|
+
const fetchedDocs = normalizeFetchDocs(runtimeResult.output);
|
|
255
|
+
for (const [index, doc] of fetchedDocs.entries()) {
|
|
256
|
+
if (!doc.content)
|
|
257
|
+
continue;
|
|
258
|
+
const fetchPath = await safeWriteAsync(`${researchDir}/fetch-${String(index + 1).padStart(2, "0")}-${slugify(doc.title ?? doc.url ?? "page")}.txt`, doc.content);
|
|
259
|
+
fetchedPaths.push(fetchPath);
|
|
260
|
+
const match = doc.url ? citations.find((citation) => citation.url === doc.url) : undefined;
|
|
261
|
+
if (match)
|
|
262
|
+
match.fetched_path = fetchPath;
|
|
263
|
+
}
|
|
264
|
+
const packetPath = await safeWriteAsync(`${researchDir}/packet.json`, JSON.stringify({
|
|
265
|
+
schema: "ace-web-research-packet@1",
|
|
266
|
+
generated_at: now.toISOString(),
|
|
267
|
+
query: input.query,
|
|
268
|
+
sources_required: input.sources_required,
|
|
269
|
+
fetch_urls: input.fetch_urls ?? [],
|
|
270
|
+
summary: pickString(runtimeResult.output, ["summary", "answer", "overview"]) ?? runtimeResult.summary,
|
|
271
|
+
citations,
|
|
272
|
+
fetched_documents: fetchedDocs.map((doc, index) => ({
|
|
273
|
+
...doc,
|
|
274
|
+
artifact_path: fetchedPaths[index],
|
|
275
|
+
})),
|
|
276
|
+
runtime_tool: {
|
|
277
|
+
name: DEFAULT_WEB_RESEARCH_TOOL,
|
|
278
|
+
summary: runtimeResult.summary,
|
|
279
|
+
request_path: runtimeResult.request_path,
|
|
280
|
+
response_path: runtimeResult.response_path,
|
|
281
|
+
},
|
|
282
|
+
}, null, 2));
|
|
283
|
+
const summary = pickString(runtimeResult.output, ["summary", "answer", "overview"]) ??
|
|
284
|
+
`Stored ${citations.length} citations and ${fetchedPaths.length} fetched pages for '${input.query}'.`;
|
|
285
|
+
await appendWrapperLedgerEntry("web_research_packet", summary, [packetPath, searchResultsPath, ...fetchedPaths, runtimeResult.request_path, runtimeResult.response_path], {
|
|
286
|
+
query: input.query,
|
|
287
|
+
citations: citations.length,
|
|
288
|
+
fetched_documents: fetchedPaths.length,
|
|
289
|
+
runtime_tool: DEFAULT_WEB_RESEARCH_TOOL,
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
summary,
|
|
294
|
+
tool_name: DEFAULT_WEB_RESEARCH_TOOL,
|
|
295
|
+
packet_path: packetPath,
|
|
296
|
+
search_results_path: searchResultsPath,
|
|
297
|
+
fetched_paths: fetchedPaths,
|
|
298
|
+
request_path: runtimeResult.request_path,
|
|
299
|
+
response_path: runtimeResult.response_path,
|
|
300
|
+
citations: citations.slice(0, 5),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
export async function createCodemunchSnapshot(input, deps = {}) {
|
|
304
|
+
const resolved = { ...defaultDeps, ...deps };
|
|
305
|
+
try {
|
|
306
|
+
assertGitHubUrl(input.repo_url);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
summary: "Codemunch snapshot request failed repository guard validation.",
|
|
312
|
+
mode: "runtime_tool",
|
|
313
|
+
error: error instanceof Error ? error.message : String(error),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const now = resolved.now();
|
|
317
|
+
const id = artifactStamp(now, resolved.randomId(), input.repo_url);
|
|
318
|
+
const researchDir = `agent-state/research/codemunch/${id}`;
|
|
319
|
+
const snapshotPath = resolveWorkspaceArtifactPath(`${researchDir}/snapshot.cxml`, "write");
|
|
320
|
+
mkdirSync(dirname(snapshotPath), { recursive: true });
|
|
321
|
+
let mode = "runtime_tool";
|
|
322
|
+
let requestPath;
|
|
323
|
+
let responsePath;
|
|
324
|
+
let runtimeSummary = "";
|
|
325
|
+
let sourceContent = "";
|
|
326
|
+
let sourcePath;
|
|
327
|
+
let provenanceSource = {};
|
|
328
|
+
if (runtimeToolConfigured(DEFAULT_CODEMUNCH_TOOL, resolved)) {
|
|
329
|
+
const runtimeResult = await resolved.executeRuntimeTool(DEFAULT_CODEMUNCH_TOOL, {
|
|
330
|
+
repo_url: input.repo_url,
|
|
331
|
+
branch: input.branch,
|
|
332
|
+
max_bytes: input.max_bytes,
|
|
333
|
+
}, {
|
|
334
|
+
session_id: input.session_id,
|
|
335
|
+
turn_number: input.turn_number,
|
|
336
|
+
workspace_path: input.workspace_path,
|
|
337
|
+
});
|
|
338
|
+
requestPath = runtimeResult.request_path;
|
|
339
|
+
responsePath = runtimeResult.response_path;
|
|
340
|
+
runtimeSummary = runtimeResult.summary;
|
|
341
|
+
if (!runtimeResult.ok || !isRecord(runtimeResult.output)) {
|
|
342
|
+
return {
|
|
343
|
+
ok: false,
|
|
344
|
+
summary: runtimeResult.summary,
|
|
345
|
+
mode,
|
|
346
|
+
request_path: requestPath,
|
|
347
|
+
response_path: responsePath,
|
|
348
|
+
error: runtimeResult.error ? JSON.stringify(runtimeResult.error) : "Invalid runtime tool output.",
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const resolvedOutput = resolveOutputCxml(runtimeResult.output);
|
|
352
|
+
sourceContent = resolvedOutput.content;
|
|
353
|
+
sourcePath = resolvedOutput.source_path;
|
|
354
|
+
provenanceSource = {
|
|
355
|
+
tool_name: DEFAULT_CODEMUNCH_TOOL,
|
|
356
|
+
cache_path: pickString(runtimeResult.output, ["cache_path", "cache_dir"]),
|
|
357
|
+
generated_at: pickString(runtimeResult.output, ["generated_at", "created_at"]),
|
|
358
|
+
source_path: sourcePath,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
mode = "external_cli";
|
|
363
|
+
const commandBase = process.env.ACE_CODEMUNCH_COMMAND?.trim() || DEFAULT_CODEMUNCH_COMMAND;
|
|
364
|
+
const command = [
|
|
365
|
+
commandBase,
|
|
366
|
+
shellQuote(input.repo_url),
|
|
367
|
+
"--out",
|
|
368
|
+
shellQuote(snapshotPath),
|
|
369
|
+
typeof input.max_bytes === "number" ? `--max-bytes ${Math.trunc(input.max_bytes)}` : "",
|
|
370
|
+
input.branch ? `--branch ${shellQuote(input.branch)}` : "",
|
|
371
|
+
]
|
|
372
|
+
.filter(Boolean)
|
|
373
|
+
.join(" ");
|
|
374
|
+
const requestPayload = JSON.stringify({
|
|
375
|
+
tool: { name: DEFAULT_CODEMUNCH_TOOL, mode: "external_cli", command_base: commandBase },
|
|
376
|
+
input: {
|
|
377
|
+
repo_url: input.repo_url,
|
|
378
|
+
branch: input.branch,
|
|
379
|
+
max_bytes: input.max_bytes,
|
|
380
|
+
},
|
|
381
|
+
workspace_path: input.workspace_path ?? resolveWorkspaceRoot(),
|
|
382
|
+
timestamp: now.toISOString(),
|
|
383
|
+
}, null, 2);
|
|
384
|
+
const shellResult = await resolved.runShellCommand(command, {
|
|
385
|
+
cwd: resolveWorkspaceRoot(),
|
|
386
|
+
env: safeExternalEnv({
|
|
387
|
+
ACE_CODEMUNCH_REPO_URL: input.repo_url,
|
|
388
|
+
ACE_CODEMUNCH_BRANCH: input.branch ?? "",
|
|
389
|
+
ACE_CODEMUNCH_MAX_BYTES: typeof input.max_bytes === "number" ? String(Math.trunc(input.max_bytes)) : "",
|
|
390
|
+
ACE_CODEMUNCH_OUT_PATH: snapshotPath,
|
|
391
|
+
}),
|
|
392
|
+
timeout_ms: 120_000,
|
|
393
|
+
});
|
|
394
|
+
const responsePayload = JSON.stringify({
|
|
395
|
+
ok: shellResult.exit_code === 0 && existsSync(snapshotPath),
|
|
396
|
+
summary: shellResult.exit_code === 0 && existsSync(snapshotPath)
|
|
397
|
+
? `Codemunch snapshot generated for ${input.repo_url}`
|
|
398
|
+
: `Codemunch CLI failed for ${input.repo_url}`,
|
|
399
|
+
output: shellResult.exit_code === 0 && existsSync(snapshotPath)
|
|
400
|
+
? {
|
|
401
|
+
cxml_path: snapshotPath,
|
|
402
|
+
repo_url: input.repo_url,
|
|
403
|
+
branch: input.branch,
|
|
404
|
+
max_bytes: input.max_bytes,
|
|
405
|
+
command,
|
|
406
|
+
}
|
|
407
|
+
: undefined,
|
|
408
|
+
error: shellResult.exit_code === 0 && existsSync(snapshotPath)
|
|
409
|
+
? undefined
|
|
410
|
+
: {
|
|
411
|
+
exit_code: shellResult.exit_code,
|
|
412
|
+
stdout: shellResult.stdout,
|
|
413
|
+
stderr: shellResult.stderr,
|
|
414
|
+
},
|
|
415
|
+
}, null, 2);
|
|
416
|
+
const persisted = await resolved.persistRuntimeToolInvocationArtifacts(resolved.randomId(), requestPayload, responsePayload);
|
|
417
|
+
requestPath = persisted.requestPath;
|
|
418
|
+
responsePath = persisted.responsePath;
|
|
419
|
+
runtimeSummary =
|
|
420
|
+
shellResult.exit_code === 0 && existsSync(snapshotPath)
|
|
421
|
+
? `Codemunch snapshot generated for ${input.repo_url}`
|
|
422
|
+
: `Codemunch CLI failed for ${input.repo_url}`;
|
|
423
|
+
if (shellResult.exit_code !== 0 || !existsSync(snapshotPath)) {
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
summary: runtimeSummary,
|
|
427
|
+
mode,
|
|
428
|
+
request_path: requestPath,
|
|
429
|
+
response_path: responsePath,
|
|
430
|
+
error: shellResult.stderr || shellResult.stdout || "Codemunch CLI did not produce snapshot output.",
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
sourceContent = readFileSync(snapshotPath, "utf-8");
|
|
434
|
+
sourcePath = snapshotPath;
|
|
435
|
+
provenanceSource = {
|
|
436
|
+
command,
|
|
437
|
+
exit_code: shellResult.exit_code,
|
|
438
|
+
duration_ms: shellResult.duration_ms,
|
|
439
|
+
source_path: snapshotPath,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const persistedSnapshotPath = await safeWriteAsync(`${researchDir}/snapshot.cxml`, sourceContent);
|
|
443
|
+
const checksum = sha256(sourceContent);
|
|
444
|
+
const provenancePath = await safeWriteAsync(`${researchDir}/provenance.json`, JSON.stringify({
|
|
445
|
+
schema: "ace-codemunch-snapshot@1",
|
|
446
|
+
generated_at: now.toISOString(),
|
|
447
|
+
repo_url: input.repo_url,
|
|
448
|
+
branch: input.branch ?? null,
|
|
449
|
+
max_bytes: typeof input.max_bytes === "number" ? Math.trunc(input.max_bytes) : null,
|
|
450
|
+
snapshot_path: persistedSnapshotPath,
|
|
451
|
+
checksum,
|
|
452
|
+
request_path: requestPath,
|
|
453
|
+
response_path: responsePath,
|
|
454
|
+
mode,
|
|
455
|
+
source: provenanceSource,
|
|
456
|
+
}, null, 2));
|
|
457
|
+
await appendWrapperLedgerEntry("codemunch_snapshot", runtimeSummary, [persistedSnapshotPath, provenancePath, requestPath, responsePath], {
|
|
458
|
+
repo_url: input.repo_url,
|
|
459
|
+
branch: input.branch ?? null,
|
|
460
|
+
checksum,
|
|
461
|
+
mode,
|
|
462
|
+
source_path: sourcePath ? normalizePathForValidation(sourcePath) : null,
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
summary: runtimeSummary,
|
|
467
|
+
mode,
|
|
468
|
+
snapshot_path: persistedSnapshotPath,
|
|
469
|
+
checksum,
|
|
470
|
+
provenance_path: provenancePath,
|
|
471
|
+
request_path: requestPath,
|
|
472
|
+
response_path: responsePath,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
export async function createMcpIngestionProbe(input, deps = {}) {
|
|
476
|
+
const resolved = { ...defaultDeps, ...deps };
|
|
477
|
+
const callId = resolved.randomId();
|
|
478
|
+
const now = resolved.now();
|
|
479
|
+
const requestPayload = JSON.stringify({
|
|
480
|
+
schema: "ace-mcp-ingestion-probe@1",
|
|
481
|
+
generated_at: now.toISOString(),
|
|
482
|
+
name: input.name,
|
|
483
|
+
transport: input.transport,
|
|
484
|
+
command: input.transport === "stdio" ? input.command : undefined,
|
|
485
|
+
args: input.transport === "stdio" ? input.args ?? [] : undefined,
|
|
486
|
+
url: input.transport === "http" ? input.url : undefined,
|
|
487
|
+
env_keys: Object.keys(input.env ?? {}).sort(),
|
|
488
|
+
}, null, 2);
|
|
489
|
+
let error;
|
|
490
|
+
let reasonCode;
|
|
491
|
+
if (!input.name.trim()) {
|
|
492
|
+
error = "MCP server name is required.";
|
|
493
|
+
reasonCode = "invalid_name";
|
|
494
|
+
}
|
|
495
|
+
else if (input.transport === "stdio" && !input.command?.trim()) {
|
|
496
|
+
error = "stdio MCP ingestion requires command.";
|
|
497
|
+
reasonCode = "command_missing";
|
|
498
|
+
}
|
|
499
|
+
else if (input.transport === "http") {
|
|
500
|
+
try {
|
|
501
|
+
if (!input.url)
|
|
502
|
+
throw new Error("http MCP ingestion requires url.");
|
|
503
|
+
assertPublicHttpUrl(input.url, "mcp_server.url");
|
|
504
|
+
}
|
|
505
|
+
catch (caught) {
|
|
506
|
+
error = caught instanceof Error ? caught.message : String(caught);
|
|
507
|
+
reasonCode = "ssrf_blocked";
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!error) {
|
|
511
|
+
const forbiddenEnv = Object.keys(input.env ?? {}).filter((key) => /(SECRET|TOKEN|KEY|PASSWORD|PASS|CREDENTIAL|AUTH|COOKIE|SESSION)/i.test(key));
|
|
512
|
+
if (forbiddenEnv.length > 0) {
|
|
513
|
+
error = `MCP server env contains secret-like keys: ${forbiddenEnv.join(", ")}`;
|
|
514
|
+
reasonCode = "secret_env_blocked";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const tools = (input.declared_tools ?? []).map((tool) => ({
|
|
518
|
+
name: tool.name,
|
|
519
|
+
description: tool.description,
|
|
520
|
+
input_schema: tool.input_schema,
|
|
521
|
+
})).filter((tool) => tool.name?.trim());
|
|
522
|
+
const responsePayload = JSON.stringify({
|
|
523
|
+
ok: !error,
|
|
524
|
+
summary: error
|
|
525
|
+
? `MCP ingestion probe blocked for ${input.name}: ${error}`
|
|
526
|
+
: `MCP ingestion probe configured ${tools.length} declared tool(s) for ${input.name}.`,
|
|
527
|
+
output: !error
|
|
528
|
+
? {
|
|
529
|
+
health: "configured",
|
|
530
|
+
tools,
|
|
531
|
+
transport: input.transport,
|
|
532
|
+
}
|
|
533
|
+
: undefined,
|
|
534
|
+
error: error ? { reason_code: reasonCode, message: error } : undefined,
|
|
535
|
+
}, null, 2);
|
|
536
|
+
const persisted = await resolved.persistRuntimeToolInvocationArtifacts(callId, requestPayload, responsePayload);
|
|
537
|
+
return {
|
|
538
|
+
ok: !error,
|
|
539
|
+
summary: error
|
|
540
|
+
? `MCP ingestion probe blocked for ${input.name}: ${error}`
|
|
541
|
+
: `MCP ingestion probe configured ${tools.length} declared tool(s) for ${input.name}.`,
|
|
542
|
+
server_name: input.name,
|
|
543
|
+
transport: input.transport,
|
|
544
|
+
health: error ? "blocked" : "configured",
|
|
545
|
+
tools,
|
|
546
|
+
request_path: persisted.requestPath,
|
|
547
|
+
response_path: persisted.responsePath,
|
|
548
|
+
error,
|
|
549
|
+
reason_code: reasonCode,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
export async function createCodemunchIndex(input, deps = {}) {
|
|
553
|
+
const resolved = { ...defaultDeps, ...deps };
|
|
554
|
+
const now = resolved.now();
|
|
555
|
+
const id = artifactStamp(now, resolved.randomId(), input.cxml_path);
|
|
556
|
+
const researchDir = `agent-state/research/codemunch/${id}`;
|
|
557
|
+
const sourceContent = readWorkspaceContent(input.cxml_path);
|
|
558
|
+
const checksum = sha256(sourceContent);
|
|
559
|
+
const repPath = wsPath("rep_astgrep.cxml");
|
|
560
|
+
const priorRep = existsSync(repPath) ? readFileSync(repPath, "utf-8") : undefined;
|
|
561
|
+
mkdirSync(resolveWorkspaceArtifactPath(researchDir, "write"), { recursive: true });
|
|
562
|
+
try {
|
|
563
|
+
const routedRepPath = await safeWriteAsync("rep_astgrep.cxml", sourceContent);
|
|
564
|
+
const discoveryResult = await resolved.refreshAstgrepIndex({
|
|
565
|
+
scope: researchDir,
|
|
566
|
+
append_evidence: input.append_evidence ?? false,
|
|
567
|
+
emit_event: input.emit_event ?? false,
|
|
568
|
+
include_rep_corpus: true,
|
|
569
|
+
});
|
|
570
|
+
const indexJsonContent = readWorkspaceContent(discoveryResult.output_json_path);
|
|
571
|
+
const indexMdContent = readWorkspaceContent(discoveryResult.output_md_path);
|
|
572
|
+
const summaryJsonPath = await safeWriteAsync(`${researchDir}/structural-summary.json`, indexJsonContent);
|
|
573
|
+
const summaryMdPath = await safeWriteAsync(`${researchDir}/structural-summary.md`, indexMdContent);
|
|
574
|
+
const parsedSummary = JSON.parse(indexJsonContent);
|
|
575
|
+
const routingHints = Array.isArray(parsedSummary.routing_hints)
|
|
576
|
+
? parsedSummary.routing_hints.filter((value) => typeof value === "string").slice(0, 5)
|
|
577
|
+
: [];
|
|
578
|
+
const provenancePath = await safeWriteAsync(`${researchDir}/index-provenance.json`, JSON.stringify({
|
|
579
|
+
schema: "ace-codemunch-index@1",
|
|
580
|
+
generated_at: now.toISOString(),
|
|
581
|
+
source_cxml_path: input.cxml_path,
|
|
582
|
+
checksum,
|
|
583
|
+
routed_rep_corpus_path: routedRepPath,
|
|
584
|
+
discovery_output_json_path: discoveryResult.output_json_path,
|
|
585
|
+
discovery_output_md_path: discoveryResult.output_md_path,
|
|
586
|
+
copied_summary_json_path: summaryJsonPath,
|
|
587
|
+
copied_summary_md_path: summaryMdPath,
|
|
588
|
+
}, null, 2));
|
|
589
|
+
await appendWrapperLedgerEntry("codemunch_index", `Indexed codemunch snapshot ${normalizePathForValidation(input.cxml_path)}`, [summaryJsonPath, summaryMdPath, provenancePath, routedRepPath], {
|
|
590
|
+
checksum,
|
|
591
|
+
rep_sources: discoveryResult.stats.rep_sources,
|
|
592
|
+
hotspot_files: discoveryResult.stats.hotspot_files,
|
|
593
|
+
});
|
|
594
|
+
return {
|
|
595
|
+
ok: true,
|
|
596
|
+
summary: `Indexed codemunch snapshot with ${discoveryResult.stats.rep_sources} rep sources.`,
|
|
597
|
+
source_cxml_path: input.cxml_path,
|
|
598
|
+
checksum,
|
|
599
|
+
rep_corpus_path: routedRepPath,
|
|
600
|
+
structural_summary_json_path: summaryJsonPath,
|
|
601
|
+
structural_summary_md_path: summaryMdPath,
|
|
602
|
+
provenance_path: provenancePath,
|
|
603
|
+
routing_hints: routingHints,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
if (typeof priorRep === "string") {
|
|
608
|
+
await safeWriteAsync("rep_astgrep.cxml", priorRep);
|
|
609
|
+
}
|
|
610
|
+
else if (existsSync(repPath)) {
|
|
611
|
+
rmSync(repPath, { force: true });
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
//# sourceMappingURL=discovery-runtime-wrappers.js.map
|
|
@@ -550,7 +550,7 @@ export function bootstrapAceWorkspace(options = {}) {
|
|
|
550
550
|
const llmProvider = normalizedProvider;
|
|
551
551
|
const llmModel = options.llmModel?.trim() ||
|
|
552
552
|
options.ollamaModel?.trim() ||
|
|
553
|
-
defaultModelForProvider(llmProvider);
|
|
553
|
+
(llmProvider === "ollama" ? defaultModelForProvider(llmProvider) : undefined);
|
|
554
554
|
const llmBaseUrl = normalizeLocalBaseUrl(options.llmBaseUrl ?? options.ollamaBaseUrl);
|
|
555
555
|
if (llmProvider && !ALL_LLM_PROVIDERS.includes(llmProvider)) {
|
|
556
556
|
throw new Error(`Unsupported llmProvider: ${llmProvider}`);
|
|
@@ -26,11 +26,11 @@ export declare const ALL_MCP_CLIENTS: readonly ["codex", "vscode", "copilot", "c
|
|
|
26
26
|
export type McpClient = (typeof ALL_MCP_CLIENTS)[number];
|
|
27
27
|
export declare const ALL_LLM_PROVIDERS: readonly ["ollama", "llama.cpp", "codex", "claude", "gemini", "copilot"];
|
|
28
28
|
export type LlmProvider = (typeof ALL_LLM_PROVIDERS)[number];
|
|
29
|
-
export declare const ALL_AGENTS: readonly ["orchestrator", "vos", "ui", "coders", "astgrep", "skeptic", "ops", "research", "spec", "builder", "qa", "docs", "memory", "security", "observability", "eval", "release"];
|
|
29
|
+
export declare const ALL_AGENTS: readonly ["orchestrator", "vos", "ui", "coders", "astgrep", "skeptic", "ops", "research", "spec", "builder", "qa", "docs", "memory", "security", "observability", "eval", "release", "planner"];
|
|
30
30
|
export type AgentRole = (typeof ALL_AGENTS)[number];
|
|
31
31
|
export declare const SWARM_AGENTS: readonly ["orchestrator", "vos", "ui", "coders"];
|
|
32
32
|
export type SwarmAgentRole = (typeof SWARM_AGENTS)[number];
|
|
33
|
-
export declare const COMPOSABLE_AGENTS: readonly ["astgrep", "skeptic", "ops", "research", "spec", "builder", "qa", "docs", "memory", "security", "observability", "eval", "release"];
|
|
33
|
+
export declare const COMPOSABLE_AGENTS: readonly ["astgrep", "skeptic", "ops", "research", "spec", "builder", "qa", "docs", "memory", "security", "observability", "eval", "release", "planner"];
|
|
34
34
|
export type ComposableAgentRole = (typeof COMPOSABLE_AGENTS)[number];
|
|
35
35
|
export declare const SWARM_SUBAGENT_MAP: Record<SwarmAgentRole, readonly ComposableAgentRole[]>;
|
|
36
36
|
export type TaskKey = "todo" | "role_tasks" | "cli_work_split" | "lessons" | "readme" | "handoff_template" | "handoff_example" | "handoff_example_vos_ui" | "handoff_example_ui_coders";
|