@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
package/dist/astgrep-index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
4
5
|
import { appendStatusEventSafe } from "./status-events.js";
|
|
@@ -14,6 +15,130 @@ export function runAstgrepQuery(pattern, lang, roots, _contextLines) {
|
|
|
14
15
|
context_lines: [],
|
|
15
16
|
}));
|
|
16
17
|
}
|
|
18
|
+
function hashTextContent(content) {
|
|
19
|
+
return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
|
|
20
|
+
}
|
|
21
|
+
function textPreview(text, maxChars = 160) {
|
|
22
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
23
|
+
if (normalized.length <= maxChars)
|
|
24
|
+
return normalized;
|
|
25
|
+
return `${normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
26
|
+
}
|
|
27
|
+
function nodeKindForMatch(match) {
|
|
28
|
+
if (typeof match.nodeKind === "string" && match.nodeKind.length > 0)
|
|
29
|
+
return match.nodeKind;
|
|
30
|
+
if (typeof match.kind === "string" && match.kind.length > 0)
|
|
31
|
+
return match.kind;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function captureMapForMatch(match) {
|
|
35
|
+
if (!match.metaVariables)
|
|
36
|
+
return undefined;
|
|
37
|
+
const captures = Object.entries(match.metaVariables).reduce((acc, [key]) => {
|
|
38
|
+
const text = extractMetaVarText(match, key);
|
|
39
|
+
if (typeof text === "string" && text.length > 0)
|
|
40
|
+
acc[key] = text;
|
|
41
|
+
return acc;
|
|
42
|
+
}, {});
|
|
43
|
+
return Object.keys(captures).length > 0 ? captures : undefined;
|
|
44
|
+
}
|
|
45
|
+
function computeMatchId(input) {
|
|
46
|
+
const encoded = [
|
|
47
|
+
input.file,
|
|
48
|
+
input.range.start?.line ?? 0,
|
|
49
|
+
input.range.start?.column ?? 0,
|
|
50
|
+
input.range.end?.line ?? 0,
|
|
51
|
+
input.range.end?.column ?? 0,
|
|
52
|
+
input.file_hash,
|
|
53
|
+
input.pattern,
|
|
54
|
+
input.lang,
|
|
55
|
+
input.matched_text,
|
|
56
|
+
].join("|");
|
|
57
|
+
return `match_${createHash("sha256").update(encoded, "utf8").digest("hex").slice(0, 16)}`;
|
|
58
|
+
}
|
|
59
|
+
function matchUsesSymbolHint(match, symbolHint) {
|
|
60
|
+
if ((match.text ?? "").includes(symbolHint))
|
|
61
|
+
return true;
|
|
62
|
+
if (!match.metaVariables)
|
|
63
|
+
return false;
|
|
64
|
+
return Object.keys(match.metaVariables).some((key) => {
|
|
65
|
+
const text = extractMetaVarText(match, key);
|
|
66
|
+
return typeof text === "string" && text.includes(symbolHint);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export function locateAstgrepMatches(input) {
|
|
70
|
+
const scope = resolveScope(input.scope ?? "src");
|
|
71
|
+
const astgrepCmd = detectAstgrepCommand();
|
|
72
|
+
if (!astgrepCmd) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
pattern: input.pattern,
|
|
76
|
+
lang: input.lang,
|
|
77
|
+
scope: scope.rel,
|
|
78
|
+
symbol_hint: input.symbol_hint,
|
|
79
|
+
astgrep_command: null,
|
|
80
|
+
total_matches: 0,
|
|
81
|
+
matches: [],
|
|
82
|
+
error: "ast-grep command not available",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const rawMatches = runAstgrep(astgrepCmd, input.pattern, input.lang, [scope.abs]);
|
|
86
|
+
const effectiveMatches = input.symbol_hint && input.symbol_hint.trim().length > 0
|
|
87
|
+
? (() => {
|
|
88
|
+
const hinted = rawMatches.filter((match) => matchUsesSymbolHint(match, input.symbol_hint));
|
|
89
|
+
return hinted.length > 0 ? hinted : rawMatches;
|
|
90
|
+
})()
|
|
91
|
+
: rawMatches;
|
|
92
|
+
const limit = input.max_results ?? 50;
|
|
93
|
+
const matches = effectiveMatches.slice(0, limit).flatMap((match) => {
|
|
94
|
+
const absFile = typeof match.file === "string" && match.file.length > 0
|
|
95
|
+
? (isAbsolute(match.file) ? match.file : resolve(WORKSPACE_ROOT, match.file))
|
|
96
|
+
: undefined;
|
|
97
|
+
if (!absFile || !isInside(WORKSPACE_ROOT, absFile) || !match.range)
|
|
98
|
+
return [];
|
|
99
|
+
const matchedText = match.text ?? "";
|
|
100
|
+
const fileContent = safeReadText(absFile);
|
|
101
|
+
const fileHash = hashTextContent(fileContent);
|
|
102
|
+
const file = normalizeHitPath(relative(WORKSPACE_ROOT, absFile));
|
|
103
|
+
const range = {
|
|
104
|
+
start: {
|
|
105
|
+
line: match.range.start?.line ?? 0,
|
|
106
|
+
column: match.range.start?.column ?? 0,
|
|
107
|
+
},
|
|
108
|
+
end: {
|
|
109
|
+
line: match.range.end?.line ?? 0,
|
|
110
|
+
column: match.range.end?.column ?? 0,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
return [{
|
|
114
|
+
match_id: computeMatchId({
|
|
115
|
+
file,
|
|
116
|
+
range,
|
|
117
|
+
matched_text: matchedText,
|
|
118
|
+
file_hash: fileHash,
|
|
119
|
+
pattern: input.pattern,
|
|
120
|
+
lang: input.lang,
|
|
121
|
+
}),
|
|
122
|
+
file,
|
|
123
|
+
range,
|
|
124
|
+
text_preview: textPreview(matchedText),
|
|
125
|
+
matched_text: matchedText,
|
|
126
|
+
captures: captureMapForMatch(match),
|
|
127
|
+
node_kind: nodeKindForMatch(match),
|
|
128
|
+
file_hash: fileHash,
|
|
129
|
+
}];
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
pattern: input.pattern,
|
|
134
|
+
lang: input.lang,
|
|
135
|
+
scope: scope.rel,
|
|
136
|
+
symbol_hint: input.symbol_hint,
|
|
137
|
+
astgrep_command: astgrepCmd,
|
|
138
|
+
total_matches: effectiveMatches.length,
|
|
139
|
+
matches,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
17
142
|
const CODE_EXTENSIONS = new Set(["ts", "tsx", "js", "mjs", "cjs", "py", "rs", "go"]);
|
|
18
143
|
const EXCLUDE_DIRS = new Set([
|
|
19
144
|
".git",
|
|
@@ -39,7 +164,7 @@ function resolveScope(scope) {
|
|
|
39
164
|
}
|
|
40
165
|
return { abs, rel: rel || "." };
|
|
41
166
|
}
|
|
42
|
-
function detectAstgrepCommand() {
|
|
167
|
+
export function detectAstgrepCommand() {
|
|
43
168
|
for (const cmd of ["ast-grep", "sg"]) {
|
|
44
169
|
const probe = spawnSync(cmd, ["--version"], { encoding: "utf8" });
|
|
45
170
|
if (probe.status === 0)
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { refreshAstgrepIndex } from "./astgrep-index.js";
|
|
|
4
4
|
import { scanWorkspaceDelta } from "./index-store.js";
|
|
5
5
|
import { startStdioServer } from "./server.js";
|
|
6
6
|
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
7
|
-
import { waitForPendingStatusEventMirrors } from "./status-events.js";
|
|
7
|
+
import { appendStatusEventSafe, waitForPendingStatusEventMirrors } from "./status-events.js";
|
|
8
8
|
import { bootstrapStoreWorkspace } from "./store/bootstrap-store.js";
|
|
9
9
|
import { HostFileMaterializer } from "./store/materializers/host-file-materializer.js";
|
|
10
10
|
import { openStore } from "./store/ace-packed-store.js";
|
|
@@ -14,8 +14,9 @@ import { getWorkspaceStorePath, readStoreBlobSync, readStoreJsonSync, } from "./
|
|
|
14
14
|
import { ensureCanonicalWorkspaceStore } from "./store/workspace-store-paths.js";
|
|
15
15
|
import { readFileSync } from "node:fs";
|
|
16
16
|
import { runTui } from "./tui/index.js";
|
|
17
|
-
import { buildOpenAiCompatibleBaseUrl, buildProviderDoctorCommands, defaultModelForProvider, discoverProviderContext, isLocalLlmProvider, normalizeLocalBaseUrl, normalizeProvider, scanLocalModelRuntimes, } from "./tui/provider-discovery.js";
|
|
17
|
+
import { buildOpenAiCompatibleBaseUrl, buildProviderDoctorCommands, defaultModelForProvider, discoverProviderContext, isLocalLlmProvider, normalizeLocalBaseUrl, normalizeLlamaCppHfModelName, normalizeProvider, scanLocalModelRuntimes, } from "./tui/provider-discovery.js";
|
|
18
18
|
import { diagnoseChatRuntimeConfig, OpenAICompatibleClient, } from "./tui/openai-compatible.js";
|
|
19
|
+
import { runShellCommand } from "./runtime-command.js";
|
|
19
20
|
function printHelp() {
|
|
20
21
|
console.log(`ACE Swarm CLI
|
|
21
22
|
|
|
@@ -24,6 +25,7 @@ Usage:
|
|
|
24
25
|
ace serve Alias for mcp
|
|
25
26
|
ace tui [options] Launch interactive TUI dashboard
|
|
26
27
|
ace init [options] Bootstrap the ACE store into current workspace
|
|
28
|
+
ace connect [provider] Register a live local runtime or hosted provider in the store
|
|
27
29
|
ace turnkey [options] Project minimal workspace bootstrap stubs from the ACE store
|
|
28
30
|
ace doctor [options] Validate ACE runtime + MCP readiness
|
|
29
31
|
ace cache [options] Cache ACE artifacts into ace-state.ace and optionally clean projections
|
|
@@ -48,12 +50,20 @@ Options for init:
|
|
|
48
50
|
--base-url <url> Runtime base URL override (local or OpenAI-compatible)
|
|
49
51
|
--ollama-url <url> Legacy alias for --base-url
|
|
50
52
|
|
|
53
|
+
Options for connect:
|
|
54
|
+
llama-server|llama-cli Optional launcher name for llama.cpp HF runtimes
|
|
55
|
+
--hf <repo> Hugging Face model repo or local model identifier
|
|
56
|
+
--model <name> Alias for --hf when you already know the model name
|
|
57
|
+
--base-url <url> OpenAI-compatible endpoint for the live runtime
|
|
58
|
+
--provider <name> Explicit provider override when not using a launcher alias
|
|
59
|
+
|
|
51
60
|
Options for doctor:
|
|
52
61
|
--llm <provider> ollama|llama.cpp|codex|claude|gemini|copilot|... (default: auto from agent-state/ace-state.ace)
|
|
53
62
|
--model <name> Model name override
|
|
54
63
|
--base-url <url> Runtime base URL override
|
|
55
64
|
--ollama-url <url> Legacy alias for --base-url
|
|
56
65
|
--scan Probe common local Ollama + llama.cpp endpoints when URL is unset
|
|
66
|
+
--repair-ollama Opt-in: run ollama pull <model> when doctor finds a missing Ollama model
|
|
57
67
|
|
|
58
68
|
Options for cache:
|
|
59
69
|
--dry-run Preview what would be cached and cleaned (no writes/deletes)
|
|
@@ -98,6 +108,29 @@ function readLlmProfile() {
|
|
|
98
108
|
function readBaseUrlFlag(args) {
|
|
99
109
|
return normalizeLocalBaseUrl(readFlagValue(args, "--base-url") ?? readFlagValue(args, "--ollama-url"));
|
|
100
110
|
}
|
|
111
|
+
function readConnectModelFlag(args) {
|
|
112
|
+
return (readFlagValue(args, "--hf") ??
|
|
113
|
+
readFlagValue(args, "-hf") ??
|
|
114
|
+
readFlagValue(args, "--model"))?.trim();
|
|
115
|
+
}
|
|
116
|
+
function parseConnectArgs(args) {
|
|
117
|
+
const firstToken = args[0];
|
|
118
|
+
const hasLauncher = Boolean(firstToken && !firstToken.startsWith("-"));
|
|
119
|
+
const launcher = hasLauncher ? firstToken?.trim().toLowerCase() : undefined;
|
|
120
|
+
const providerOverride = normalizeProvider(readFlagValue(args, "--provider")?.trim());
|
|
121
|
+
const provider = providerOverride ??
|
|
122
|
+
(launcher === "llama-server" || launcher === "llama-cli" ? "llama.cpp" : providerOverride);
|
|
123
|
+
const rawModel = readConnectModelFlag(args);
|
|
124
|
+
const model = provider === "llama.cpp" ? normalizeLlamaCppHfModelName(rawModel) ?? rawModel : rawModel;
|
|
125
|
+
const baseUrl = readBaseUrlFlag(args);
|
|
126
|
+
const launcherCommand = hasLauncher ? args.join(" ").trim() : undefined;
|
|
127
|
+
return {
|
|
128
|
+
provider: provider,
|
|
129
|
+
model,
|
|
130
|
+
baseUrl,
|
|
131
|
+
launcherCommand,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
101
134
|
function parseLlmOptions(args) {
|
|
102
135
|
const profile = readLlmProfile();
|
|
103
136
|
const selected = normalizeProvider(readFlagValue(args, "--llm")?.trim() ?? profile?.provider?.trim());
|
|
@@ -107,8 +140,11 @@ function parseLlmOptions(args) {
|
|
|
107
140
|
throw new Error(`Unsupported LLM provider: ${selected}`);
|
|
108
141
|
}
|
|
109
142
|
const provider = selected;
|
|
110
|
-
const
|
|
111
|
-
const
|
|
143
|
+
const explicitModel = readFlagValue(args, "--model")?.trim();
|
|
144
|
+
const profileModel = profile?.model?.trim();
|
|
145
|
+
const llmModel = explicitModel ||
|
|
146
|
+
profileModel ||
|
|
147
|
+
(provider === "ollama" ? defaultModelForProvider(provider) : undefined);
|
|
112
148
|
const llmBaseUrl = readBaseUrlFlag(args) ?? normalizeLocalBaseUrl(profile?.base_url);
|
|
113
149
|
return {
|
|
114
150
|
llmProvider: provider,
|
|
@@ -121,9 +157,11 @@ async function writeLlmProfile(profile) {
|
|
|
121
157
|
await withStoreWriteCoordinator(storePath, async () => {
|
|
122
158
|
const payload = {
|
|
123
159
|
provider: profile.provider,
|
|
124
|
-
model: profile.model,
|
|
125
160
|
generated_at: new Date().toISOString(),
|
|
126
161
|
};
|
|
162
|
+
if (profile.model) {
|
|
163
|
+
payload.model = profile.model;
|
|
164
|
+
}
|
|
127
165
|
if (profile.baseUrl) {
|
|
128
166
|
payload.base_url = profile.baseUrl;
|
|
129
167
|
if (profile.provider === "ollama") {
|
|
@@ -131,6 +169,9 @@ async function writeLlmProfile(profile) {
|
|
|
131
169
|
payload.default_api_key = "ollama";
|
|
132
170
|
}
|
|
133
171
|
}
|
|
172
|
+
if (profile.launcherCommand) {
|
|
173
|
+
payload.launch_command = profile.launcherCommand;
|
|
174
|
+
}
|
|
134
175
|
const doctorCommands = buildProviderDoctorCommands(profile.provider, profile.model, profile.baseUrl);
|
|
135
176
|
const store = await openStore(storePath);
|
|
136
177
|
try {
|
|
@@ -153,6 +194,40 @@ async function writeLlmProfile(profile) {
|
|
|
153
194
|
}, { operation_label: "writeLlmProfile" });
|
|
154
195
|
return `${storePath}#state/runtime/llm_profile`;
|
|
155
196
|
}
|
|
197
|
+
async function runConnect(args) {
|
|
198
|
+
const parsed = parseConnectArgs(args);
|
|
199
|
+
if (!parsed.provider) {
|
|
200
|
+
throw new Error("ace connect expects a provider or launcher command. Try `ace connect llama-server -hf <model> --base-url http://127.0.0.1:8080`.");
|
|
201
|
+
}
|
|
202
|
+
if (!ALL_LLM_PROVIDERS.includes(parsed.provider)) {
|
|
203
|
+
throw new Error(`Unsupported LLM provider: ${parsed.provider}`);
|
|
204
|
+
}
|
|
205
|
+
if (isLocalLlmProvider(parsed.provider) && !parsed.model) {
|
|
206
|
+
throw new Error("ace connect for local runtimes requires an explicit model. Pass `--hf <repo>` or `--model <name>`.");
|
|
207
|
+
}
|
|
208
|
+
const storeResult = await bootstrapStoreWorkspace({
|
|
209
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
210
|
+
llm: parsed.provider,
|
|
211
|
+
model: parsed.model,
|
|
212
|
+
baseUrl: parsed.baseUrl,
|
|
213
|
+
includeMcpConfig: false,
|
|
214
|
+
includeClientConfigBundle: false,
|
|
215
|
+
launcherCommand: parsed.launcherCommand,
|
|
216
|
+
});
|
|
217
|
+
console.log("ACE connect complete");
|
|
218
|
+
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
219
|
+
console.log(`Store: ${storeResult.storePath}`);
|
|
220
|
+
console.log(`Provider: ${parsed.provider}`);
|
|
221
|
+
console.log(`Model: ${parsed.model ?? "(not set)"}`);
|
|
222
|
+
console.log(`Base URL: ${parsed.baseUrl ?? "(not set)"}`);
|
|
223
|
+
if (parsed.launcherCommand) {
|
|
224
|
+
console.log(`Launcher: ${parsed.launcherCommand}`);
|
|
225
|
+
}
|
|
226
|
+
console.log(`Profile path: ${storeResult.storePath}#state/runtime/llm_profile`);
|
|
227
|
+
for (const warning of storeResult.warnings) {
|
|
228
|
+
console.warn(` [store] ${warning}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
156
231
|
async function recordDiscoveryProfile(input) {
|
|
157
232
|
const storePath = getWorkspaceStorePath(WORKSPACE_ROOT);
|
|
158
233
|
await withStoreWriteCoordinator(storePath, async () => {
|
|
@@ -296,6 +371,13 @@ async function listLocalRuntimeModels(provider, baseUrl) {
|
|
|
296
371
|
headers: { Accept: "application/json" },
|
|
297
372
|
});
|
|
298
373
|
if (!response.ok) {
|
|
374
|
+
const text = await response.text().catch(() => "");
|
|
375
|
+
if (response.status >= 500 && /unable to load model/i.test(text)) {
|
|
376
|
+
const error = new Error(`${response.status} ${response.statusText}: ollama_model_load_error. ` +
|
|
377
|
+
`Suggested remediation: run ollama pull <model> or ace doctor --repair-ollama.`);
|
|
378
|
+
error.reason_code = "ollama_model_load_error";
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
299
381
|
throw new Error(`${response.status} ${response.statusText}`);
|
|
300
382
|
}
|
|
301
383
|
const payload = (await response.json().catch(() => ({})));
|
|
@@ -321,6 +403,7 @@ async function listLocalRuntimeModels(provider, baseUrl) {
|
|
|
321
403
|
}
|
|
322
404
|
async function runDoctor(args) {
|
|
323
405
|
const llm = parseLlmOptions(args);
|
|
406
|
+
const repairOllama = args.includes("--repair-ollama");
|
|
324
407
|
const checks = [];
|
|
325
408
|
const mcpConfigPaths = [wsPath(".vscode", "mcp.json")];
|
|
326
409
|
const hasWorkspaceMcpConfig = mcpConfigPaths.some((path) => fileExists(path));
|
|
@@ -347,7 +430,9 @@ async function runDoctor(args) {
|
|
|
347
430
|
let provider = llm.llmProvider;
|
|
348
431
|
let model = llm.llmModel;
|
|
349
432
|
let baseUrl = llm.llmBaseUrl;
|
|
350
|
-
const shouldScan = args.includes("--scan") ||
|
|
433
|
+
const shouldScan = args.includes("--scan") ||
|
|
434
|
+
(!provider && !baseUrl) ||
|
|
435
|
+
(isLocalLlmProvider(provider) && (!baseUrl || !model));
|
|
351
436
|
if (shouldScan) {
|
|
352
437
|
const scanned = await scanLocalModelRuntimes({
|
|
353
438
|
workspaceRoot: WORKSPACE_ROOT,
|
|
@@ -359,7 +444,7 @@ async function runDoctor(args) {
|
|
|
359
444
|
if (chosen) {
|
|
360
445
|
provider = chosen.provider ?? provider;
|
|
361
446
|
baseUrl = baseUrl ?? chosen.baseUrl;
|
|
362
|
-
model = model || chosen.models[0]
|
|
447
|
+
model = model || chosen.models[0];
|
|
363
448
|
checks.push({
|
|
364
449
|
name: "Runtime endpoint discovered",
|
|
365
450
|
ok: true,
|
|
@@ -367,7 +452,7 @@ async function runDoctor(args) {
|
|
|
367
452
|
});
|
|
368
453
|
const writtenProfilePath = await writeLlmProfile({
|
|
369
454
|
provider: provider ?? chosen.provider,
|
|
370
|
-
model
|
|
455
|
+
model,
|
|
371
456
|
baseUrl,
|
|
372
457
|
});
|
|
373
458
|
checks.push({
|
|
@@ -388,6 +473,30 @@ async function runDoctor(args) {
|
|
|
388
473
|
throw new Error(`No runtime provider configured. Use --llm <provider>, bootstrap one into agent-state/ace-state.ace#state/runtime/llm_profile, or run \`ace doctor --scan\` for a local runtime.`);
|
|
389
474
|
}
|
|
390
475
|
if (!model) {
|
|
476
|
+
if (isLocalLlmProvider(provider)) {
|
|
477
|
+
checks.push({
|
|
478
|
+
name: "Runtime model configured",
|
|
479
|
+
ok: false,
|
|
480
|
+
detail: "No local runtime model is configured yet. Run `ace connect` with a user-supplied HF model or run `ace doctor --scan` after the runtime is up.",
|
|
481
|
+
});
|
|
482
|
+
const failed = checks.filter((check) => !check.ok);
|
|
483
|
+
console.log("ACE Doctor Report");
|
|
484
|
+
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
485
|
+
console.log(`Provider: ${provider}`);
|
|
486
|
+
console.log("Model: (not set)");
|
|
487
|
+
if (baseUrl) {
|
|
488
|
+
console.log(`Base URL: ${baseUrl}`);
|
|
489
|
+
}
|
|
490
|
+
console.log("");
|
|
491
|
+
for (const check of checks) {
|
|
492
|
+
const status = check.ok ? "PASS" : "FAIL";
|
|
493
|
+
console.log(`- [${status}] ${check.name}: ${check.detail}`);
|
|
494
|
+
}
|
|
495
|
+
if (failed.length > 0) {
|
|
496
|
+
process.exitCode = 1;
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
391
500
|
model = defaultModelForProvider(provider);
|
|
392
501
|
}
|
|
393
502
|
if (isLocalLlmProvider(provider) && !baseUrl) {
|
|
@@ -425,6 +534,23 @@ async function runDoctor(args) {
|
|
|
425
534
|
});
|
|
426
535
|
}
|
|
427
536
|
catch (error) {
|
|
537
|
+
const reasonCode = typeof error === "object" && error !== null && "reason_code" in error
|
|
538
|
+
? String(error.reason_code)
|
|
539
|
+
: undefined;
|
|
540
|
+
if (reasonCode === "ollama_model_load_error") {
|
|
541
|
+
await appendStatusEventSafe({
|
|
542
|
+
source_module: "capability-ops",
|
|
543
|
+
event_type: "OLLAMA_MODEL_LOAD_ERROR",
|
|
544
|
+
status: "blocked",
|
|
545
|
+
summary: `Ollama model-load error for ${model}: ${error instanceof Error ? error.message : String(error)}`,
|
|
546
|
+
objective_id: "ollama-doctor",
|
|
547
|
+
payload: {
|
|
548
|
+
reason_code: "ollama_model_load_error",
|
|
549
|
+
model,
|
|
550
|
+
base_url: baseUrl,
|
|
551
|
+
},
|
|
552
|
+
}).catch(() => undefined);
|
|
553
|
+
}
|
|
428
554
|
checks.push({
|
|
429
555
|
name: "Runtime endpoint reachable",
|
|
430
556
|
ok: false,
|
|
@@ -450,6 +576,44 @@ async function runDoctor(args) {
|
|
|
450
576
|
? `${model} found`
|
|
451
577
|
: `${model} not reported by llama.cpp (available: ${modelNames.join(", ") || "none"})`,
|
|
452
578
|
});
|
|
579
|
+
if (provider === "ollama" && !hasModel && repairOllama) {
|
|
580
|
+
const repair = await runShellCommand(`ollama pull ${model}`, {
|
|
581
|
+
cwd: WORKSPACE_ROOT,
|
|
582
|
+
timeout_ms: 10 * 60_000,
|
|
583
|
+
});
|
|
584
|
+
await appendRunLedgerEntrySafe({
|
|
585
|
+
tool: "doctor",
|
|
586
|
+
category: repair.exit_code === 0 ? "info" : "regression",
|
|
587
|
+
message: `Opt-in Ollama repair attempted for ${model}`,
|
|
588
|
+
artifacts: [],
|
|
589
|
+
metadata: {
|
|
590
|
+
reason_code: "ollama_model_load_error",
|
|
591
|
+
model,
|
|
592
|
+
exit_code: repair.exit_code,
|
|
593
|
+
timed_out: repair.timed_out,
|
|
594
|
+
},
|
|
595
|
+
}).catch(() => undefined);
|
|
596
|
+
await appendStatusEventSafe({
|
|
597
|
+
source_module: "capability-ops",
|
|
598
|
+
event_type: "OLLAMA_REPAIR_ATTEMPTED",
|
|
599
|
+
status: repair.exit_code === 0 ? "done" : "fail",
|
|
600
|
+
summary: `Opt-in Ollama repair attempted for ${model}`,
|
|
601
|
+
objective_id: "ollama-doctor",
|
|
602
|
+
payload: {
|
|
603
|
+
reason_code: "ollama_model_load_error",
|
|
604
|
+
model,
|
|
605
|
+
exit_code: repair.exit_code,
|
|
606
|
+
timed_out: repair.timed_out,
|
|
607
|
+
},
|
|
608
|
+
}).catch(() => undefined);
|
|
609
|
+
checks.push({
|
|
610
|
+
name: "Ollama model repair attempted",
|
|
611
|
+
ok: repair.exit_code === 0,
|
|
612
|
+
detail: repair.exit_code === 0
|
|
613
|
+
? `ollama pull ${model} completed`
|
|
614
|
+
: `ollama pull ${model} failed with exit ${repair.exit_code}: ${repair.stderr || repair.stdout || "no output"}`,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
453
617
|
}
|
|
454
618
|
else {
|
|
455
619
|
const diagnosis = diagnoseChatRuntimeConfig(provider, model, baseUrl ? { baseUrl } : undefined);
|
|
@@ -588,14 +752,36 @@ async function main() {
|
|
|
588
752
|
cliModel,
|
|
589
753
|
cliBaseUrl,
|
|
590
754
|
});
|
|
755
|
+
const needsLocalScan = isLocalLlmProvider(discovered.provider) &&
|
|
756
|
+
(!discovered.baseUrl || !discovered.model || discovered.model === defaultModelForProvider(discovered.provider));
|
|
757
|
+
const runtime = needsLocalScan
|
|
758
|
+
? await scanLocalModelRuntimes({
|
|
759
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
760
|
+
preferredProvider: discovered.provider,
|
|
761
|
+
explicitBaseUrl: discovered.baseUrl,
|
|
762
|
+
})
|
|
763
|
+
: undefined;
|
|
764
|
+
const scanned = runtime?.candidates.find((candidate) => candidate.provider === discovered.provider) ??
|
|
765
|
+
runtime?.candidates[0];
|
|
766
|
+
const resolvedTui = scanned?.models[0]
|
|
767
|
+
? {
|
|
768
|
+
...discovered,
|
|
769
|
+
model: scanned.models[0],
|
|
770
|
+
baseUrl: discovered.baseUrl ?? scanned.baseUrl,
|
|
771
|
+
providerBaseUrls: {
|
|
772
|
+
...discovered.providerBaseUrls,
|
|
773
|
+
[scanned.provider]: discovered.baseUrl ?? scanned.baseUrl,
|
|
774
|
+
},
|
|
775
|
+
}
|
|
776
|
+
: discovered;
|
|
591
777
|
await runTui({
|
|
592
|
-
provider:
|
|
593
|
-
model:
|
|
594
|
-
providers:
|
|
595
|
-
modelsByProvider:
|
|
596
|
-
baseUrl:
|
|
597
|
-
ollamaUrl:
|
|
598
|
-
providerBaseUrls:
|
|
778
|
+
provider: resolvedTui.provider,
|
|
779
|
+
model: resolvedTui.model,
|
|
780
|
+
providers: resolvedTui.providers,
|
|
781
|
+
modelsByProvider: resolvedTui.modelsByProvider,
|
|
782
|
+
baseUrl: resolvedTui.baseUrl,
|
|
783
|
+
ollamaUrl: resolvedTui.ollamaUrl,
|
|
784
|
+
providerBaseUrls: resolvedTui.providerBaseUrls,
|
|
599
785
|
workspaceRoot: WORKSPACE_ROOT,
|
|
600
786
|
});
|
|
601
787
|
return;
|
|
@@ -604,6 +790,10 @@ async function main() {
|
|
|
604
790
|
await runInit(args.slice(1), "init");
|
|
605
791
|
return;
|
|
606
792
|
}
|
|
793
|
+
if (command === "connect") {
|
|
794
|
+
await runConnect(args.slice(1));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
607
797
|
if (command === "turnkey") {
|
|
608
798
|
await runInit(args.slice(1), "turnkey");
|
|
609
799
|
return;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type RunShellCommandOptions, type ShellCommandResult } from "./runtime-command.js";
|
|
2
|
+
import { persistRuntimeToolInvocationArtifacts, type RuntimeToolExecutionContext, type RuntimeToolExecutionResult, type RuntimeToolSpecRegistryResult } from "./runtime-tool-specs.js";
|
|
3
|
+
import { type RefreshAstgrepIndexResult } from "./astgrep-index.js";
|
|
4
|
+
export interface WebResearchPacketInput extends RuntimeToolExecutionContext {
|
|
5
|
+
query: string;
|
|
6
|
+
sources_required: number;
|
|
7
|
+
fetch_urls?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface WebResearchCitation {
|
|
10
|
+
title: string;
|
|
11
|
+
url: string;
|
|
12
|
+
snippet?: string;
|
|
13
|
+
fetched_path?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebResearchPacketResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
summary: string;
|
|
18
|
+
tool_name: string;
|
|
19
|
+
packet_path?: string;
|
|
20
|
+
search_results_path?: string;
|
|
21
|
+
fetched_paths?: string[];
|
|
22
|
+
request_path?: string;
|
|
23
|
+
response_path?: string;
|
|
24
|
+
citations?: WebResearchCitation[];
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CodemunchSnapshotInput extends RuntimeToolExecutionContext {
|
|
28
|
+
repo_url: string;
|
|
29
|
+
branch?: string;
|
|
30
|
+
max_bytes?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface CodemunchSnapshotResult {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
summary: string;
|
|
35
|
+
mode: "runtime_tool" | "external_cli";
|
|
36
|
+
snapshot_path?: string;
|
|
37
|
+
checksum?: string;
|
|
38
|
+
provenance_path?: string;
|
|
39
|
+
request_path?: string;
|
|
40
|
+
response_path?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface CodemunchIndexInput {
|
|
44
|
+
cxml_path: string;
|
|
45
|
+
append_evidence?: boolean;
|
|
46
|
+
emit_event?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface CodemunchIndexResult {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
summary: string;
|
|
51
|
+
source_cxml_path?: string;
|
|
52
|
+
checksum?: string;
|
|
53
|
+
rep_corpus_path?: string;
|
|
54
|
+
structural_summary_json_path?: string;
|
|
55
|
+
structural_summary_md_path?: string;
|
|
56
|
+
provenance_path?: string;
|
|
57
|
+
routing_hints?: string[];
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface McpIngestionProbeInput {
|
|
61
|
+
name: string;
|
|
62
|
+
transport: "stdio" | "http";
|
|
63
|
+
command?: string;
|
|
64
|
+
args?: string[];
|
|
65
|
+
url?: string;
|
|
66
|
+
env?: Record<string, string>;
|
|
67
|
+
declared_tools?: Array<{
|
|
68
|
+
name: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
input_schema?: unknown;
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
export interface McpIngestionProbeResult {
|
|
74
|
+
ok: boolean;
|
|
75
|
+
summary: string;
|
|
76
|
+
server_name: string;
|
|
77
|
+
transport: "stdio" | "http";
|
|
78
|
+
health: "configured" | "blocked";
|
|
79
|
+
tools: Array<{
|
|
80
|
+
name: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
input_schema?: unknown;
|
|
83
|
+
}>;
|
|
84
|
+
request_path?: string;
|
|
85
|
+
response_path?: string;
|
|
86
|
+
error?: string;
|
|
87
|
+
reason_code?: string;
|
|
88
|
+
}
|
|
89
|
+
interface WrapperDeps {
|
|
90
|
+
executeRuntimeTool: (name: string, input: unknown, context?: RuntimeToolExecutionContext) => Promise<RuntimeToolExecutionResult>;
|
|
91
|
+
loadRuntimeToolRegistry: (explicitPath?: string) => RuntimeToolSpecRegistryResult;
|
|
92
|
+
persistRuntimeToolInvocationArtifacts: typeof persistRuntimeToolInvocationArtifacts;
|
|
93
|
+
runShellCommand: (command: string, options: RunShellCommandOptions) => Promise<ShellCommandResult>;
|
|
94
|
+
refreshAstgrepIndex: (input?: {
|
|
95
|
+
scope?: string;
|
|
96
|
+
append_evidence?: boolean;
|
|
97
|
+
emit_event?: boolean;
|
|
98
|
+
include_rep_corpus?: boolean;
|
|
99
|
+
}) => Promise<RefreshAstgrepIndexResult>;
|
|
100
|
+
now: () => Date;
|
|
101
|
+
randomId: () => string;
|
|
102
|
+
}
|
|
103
|
+
export declare function createWebResearchPacket(input: WebResearchPacketInput, deps?: Partial<WrapperDeps>): Promise<WebResearchPacketResult>;
|
|
104
|
+
export declare function createCodemunchSnapshot(input: CodemunchSnapshotInput, deps?: Partial<WrapperDeps>): Promise<CodemunchSnapshotResult>;
|
|
105
|
+
export declare function createMcpIngestionProbe(input: McpIngestionProbeInput, deps?: Partial<WrapperDeps>): Promise<McpIngestionProbeResult>;
|
|
106
|
+
export declare function createCodemunchIndex(input: CodemunchIndexInput, deps?: Partial<WrapperDeps>): Promise<CodemunchIndexResult>;
|
|
107
|
+
export {};
|
|
108
|
+
//# sourceMappingURL=discovery-runtime-wrappers.d.ts.map
|