@voybio/ace-swarm 2.4.1 → 2.4.2
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 +501 -56
- package/dist/cli.js +282 -2
- package/dist/helpers/constants.d.ts +2 -0
- package/dist/helpers/constants.js +1 -0
- package/dist/hermes/bridge-protocol.d.ts +41 -0
- package/dist/hermes/bridge-protocol.js +70 -0
- package/dist/hermes/launch-profile.d.ts +19 -0
- package/dist/hermes/launch-profile.js +81 -0
- package/dist/hermes/session-manager.d.ts +42 -0
- package/dist/hermes/session-manager.js +187 -0
- package/dist/local-model-runtime.d.ts +11 -0
- package/dist/local-model-runtime.js +58 -2
- package/dist/schemas.js +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +22 -4
- package/dist/store/materializers/vericify-projector.js +3 -0
- package/dist/store/repositories/local-model-runtime-repository.d.ts +12 -0
- package/dist/store/repositories/local-model-runtime-repository.js +3 -0
- package/dist/tools-agent.js +6 -1
- package/dist/tools.d.ts +4 -1
- package/dist/tools.js +35 -13
- package/dist/tui/chat.d.ts +8 -0
- package/dist/tui/chat.js +74 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.js +35 -1
- package/dist/tui/layout.d.ts +1 -0
- package/dist/tui/layout.js +4 -1
- package/dist/tui/provider-discovery.js +15 -13
- package/dist/vericify-bridge.d.ts +3 -0
- package/dist/vericify-bridge.js +3 -0
- package/package.json +2 -1
- package/scripts/hermes_bridge_worker.py +136 -0
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ACE_TASKS_ROOT_REL, ALL_MCP_CLIENTS, ALL_LLM_PROVIDERS, DEFAULTS_ROOT, PACKAGE_ROOT, WORKSPACE_ROOT, fileExists, getAllMcpServerConfigSnippets, getMcpClientInstallHint, getMcpServerConfigSnippet, wsPath, } from "./helpers.js";
|
|
3
3
|
import { refreshAstgrepIndex } from "./astgrep-index.js";
|
|
4
4
|
import { scanWorkspaceDelta } from "./index-store.js";
|
|
5
|
-
import { startStdioServer } from "./server.js";
|
|
5
|
+
import { startHermesShadowStdioServer, startStdioServer } from "./server.js";
|
|
6
6
|
import { appendRunLedgerEntrySafe } from "./run-ledger.js";
|
|
7
7
|
import { appendStatusEventSafe, waitForPendingStatusEventMirrors } from "./status-events.js";
|
|
8
8
|
import { bootstrapStoreWorkspace } from "./store/bootstrap-store.js";
|
|
@@ -12,16 +12,20 @@ import { DiscoveryRepository } from "./store/repositories/discovery-repository.j
|
|
|
12
12
|
import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
|
|
13
13
|
import { getWorkspaceStorePath, readStoreBlobSync, readStoreJsonSync, } from "./store/store-snapshot.js";
|
|
14
14
|
import { ensureCanonicalWorkspaceStore } from "./store/workspace-store-paths.js";
|
|
15
|
-
import { readFileSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { resolve } from "node:path";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
16
18
|
import { runTui } from "./tui/index.js";
|
|
17
19
|
import { buildOpenAiCompatibleBaseUrl, buildProviderDoctorCommands, defaultModelForProvider, discoverProviderContext, isLocalLlmProvider, normalizeLocalBaseUrl, normalizeLlamaCppHfModelName, normalizeProvider, scanLocalModelRuntimes, } from "./tui/provider-discovery.js";
|
|
18
20
|
import { diagnoseChatRuntimeConfig, OpenAICompatibleClient, } from "./tui/openai-compatible.js";
|
|
19
21
|
import { runShellCommand } from "./runtime-command.js";
|
|
22
|
+
import { resolveHermesLaunchProfile } from "./hermes/launch-profile.js";
|
|
20
23
|
function printHelp() {
|
|
21
24
|
console.log(`ACE Swarm CLI
|
|
22
25
|
|
|
23
26
|
Usage:
|
|
24
27
|
ace mcp Start MCP server over stdio
|
|
28
|
+
ace mcp-shadow --tools <csv> Start filtered Hermes-local MCP shadow server over stdio
|
|
25
29
|
ace serve Alias for mcp
|
|
26
30
|
ace tui [options] Launch interactive TUI dashboard
|
|
27
31
|
ace init [options] Bootstrap the ACE store into current workspace
|
|
@@ -39,6 +43,16 @@ Options for tui:
|
|
|
39
43
|
--model <name> Model name override (defaults from profile/settings discovery)
|
|
40
44
|
--base-url <url> Local runtime base URL override
|
|
41
45
|
--ollama-url <url> Legacy alias for --base-url
|
|
46
|
+
--engine <name> direct|hermes_local execution engine
|
|
47
|
+
--hermes Alias for --engine hermes_local
|
|
48
|
+
--hermes-root <path> User-supplied Hermes checkout/import root
|
|
49
|
+
--hermes-python <path> User-supplied Python executable with Hermes deps
|
|
50
|
+
--hermes-uv-project <path> Optional uv project root for operator-managed Hermes
|
|
51
|
+
--hermes-command-json <json> Advanced command array override for Hermes Python
|
|
52
|
+
|
|
53
|
+
Hermes is optional and user-supplied. \`ace tui\` and \`ace doctor\` only resolve
|
|
54
|
+
Hermes when you pass \`--hermes\` / \`--engine hermes_local\` or set the matching
|
|
55
|
+
Hermes environment variables.
|
|
42
56
|
|
|
43
57
|
Options for init:
|
|
44
58
|
--project <name> Project name stored in agent-state/ace-state.ace metadata
|
|
@@ -63,6 +77,11 @@ Options for doctor:
|
|
|
63
77
|
--base-url <url> Runtime base URL override
|
|
64
78
|
--ollama-url <url> Legacy alias for --base-url
|
|
65
79
|
--scan Probe common local Ollama + llama.cpp endpoints when URL is unset
|
|
80
|
+
--hermes Also run optional Hermes-local readiness checks
|
|
81
|
+
--hermes-root <path> User-supplied Hermes checkout/import root (or ACE_HERMES_ROOT)
|
|
82
|
+
--hermes-python <path> User-supplied Python executable (or HERMES_PYTHON)
|
|
83
|
+
--hermes-uv-project <path> Optional uv project root for operator-managed Hermes
|
|
84
|
+
--hermes-command-json <json> Advanced command array override for Hermes Python
|
|
66
85
|
--repair-ollama Opt-in: run ollama pull <model> when doctor finds a missing Ollama model
|
|
67
86
|
|
|
68
87
|
Options for cache:
|
|
@@ -73,6 +92,11 @@ Options for mcp-config:
|
|
|
73
92
|
--client <name> codex|vscode|copilot|claude|cursor|antigravity
|
|
74
93
|
--all Print all client snippets for optional global install
|
|
75
94
|
|
|
95
|
+
Hermes environment:
|
|
96
|
+
ACE_HERMES_ROOT User-supplied Hermes checkout/import root
|
|
97
|
+
HERMES_PYTHON User-supplied Python with Hermes dependencies
|
|
98
|
+
ACE_HERMES_UV_PROJECT Optional operator-managed uv project root
|
|
99
|
+
|
|
76
100
|
preconfig writes .mcp-config/ at the workspace root with ready-to-use config files for every
|
|
77
101
|
supported MCP client plus a root .mcp.json for GitHub Copilot CLI. Run once after ace init.
|
|
78
102
|
Each file includes the install hint for its client.
|
|
@@ -87,6 +111,180 @@ function readFlagValue(args, flag) {
|
|
|
87
111
|
return undefined;
|
|
88
112
|
return args[index + 1];
|
|
89
113
|
}
|
|
114
|
+
async function runCommandArray(command, args, options) {
|
|
115
|
+
const [bin, ...prefixArgs] = command;
|
|
116
|
+
if (!bin)
|
|
117
|
+
return { ok: false, stdout: "", stderr: "", detail: "empty command array" };
|
|
118
|
+
return await new Promise((resolveCommand) => {
|
|
119
|
+
const child = spawn(bin, [...prefixArgs, ...args], {
|
|
120
|
+
cwd: options.cwd,
|
|
121
|
+
env: options.env,
|
|
122
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
123
|
+
});
|
|
124
|
+
let stdout = "";
|
|
125
|
+
let stderr = "";
|
|
126
|
+
const timeout = setTimeout(() => {
|
|
127
|
+
child.kill("SIGTERM");
|
|
128
|
+
}, options.timeoutMs ?? 5000);
|
|
129
|
+
child.stdout?.setEncoding("utf8");
|
|
130
|
+
child.stderr?.setEncoding("utf8");
|
|
131
|
+
child.stdout?.on("data", (chunk) => {
|
|
132
|
+
stdout += String(chunk);
|
|
133
|
+
});
|
|
134
|
+
child.stderr?.on("data", (chunk) => {
|
|
135
|
+
stderr += String(chunk);
|
|
136
|
+
});
|
|
137
|
+
child.on("error", (error) => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
resolveCommand({ ok: false, stdout, stderr, detail: error.message });
|
|
140
|
+
});
|
|
141
|
+
child.on("close", (code) => {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
resolveCommand({
|
|
144
|
+
ok: code === 0,
|
|
145
|
+
stdout,
|
|
146
|
+
stderr,
|
|
147
|
+
detail: code === 0 ? "exit 0" : `exit ${code}; ${stderr.slice(0, 300)}`,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function mcpFrame(payload) {
|
|
153
|
+
const body = JSON.stringify(payload);
|
|
154
|
+
return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
155
|
+
}
|
|
156
|
+
async function probeHermesShadowMcp(cliPath, toolAllowlist) {
|
|
157
|
+
return await new Promise((resolveProbe, rejectProbe) => {
|
|
158
|
+
const child = spawn(process.execPath, [cliPath, "mcp-shadow", "--tools", toolAllowlist.join(",")], {
|
|
159
|
+
cwd: PACKAGE_ROOT,
|
|
160
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
+
});
|
|
162
|
+
let stdout = "";
|
|
163
|
+
let stderr = "";
|
|
164
|
+
const timeout = setTimeout(() => {
|
|
165
|
+
child.kill("SIGTERM");
|
|
166
|
+
rejectProbe(new Error(`shadow MCP probe timed out${stderr ? `: ${stderr.slice(0, 200)}` : ""}`));
|
|
167
|
+
}, 2500);
|
|
168
|
+
child.stdout.on("data", (chunk) => {
|
|
169
|
+
stdout += chunk.toString("utf8");
|
|
170
|
+
if (stdout.includes('"tools"') && stdout.includes(toolAllowlist[0] ?? "")) {
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
child.kill("SIGTERM");
|
|
173
|
+
resolveProbe(`shadow MCP initialize/tools-list probe succeeded; tools=${toolAllowlist.join(",")}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
child.stderr.on("data", (chunk) => {
|
|
177
|
+
stderr += chunk.toString("utf8");
|
|
178
|
+
});
|
|
179
|
+
child.on("error", (error) => {
|
|
180
|
+
clearTimeout(timeout);
|
|
181
|
+
rejectProbe(error);
|
|
182
|
+
});
|
|
183
|
+
child.on("close", () => {
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
if (stdout.includes('"tools"') && stdout.includes(toolAllowlist[0] ?? "")) {
|
|
186
|
+
resolveProbe(`shadow MCP initialize/tools-list probe succeeded; tools=${toolAllowlist.join(",")}`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
rejectProbe(new Error(`shadow MCP probe did not return tools/list${stderr ? `: ${stderr.slice(0, 200)}` : ""}`));
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
child.stdin.write(mcpFrame({
|
|
193
|
+
jsonrpc: "2.0",
|
|
194
|
+
id: 1,
|
|
195
|
+
method: "initialize",
|
|
196
|
+
params: {
|
|
197
|
+
protocolVersion: "2024-11-05",
|
|
198
|
+
capabilities: {},
|
|
199
|
+
clientInfo: { name: "ace-doctor", version: "0.0.0" },
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
child.stdin.write(mcpFrame({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }));
|
|
203
|
+
child.stdin.write(mcpFrame({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async function appendHermesLocalReadinessChecks(checks, profile) {
|
|
207
|
+
const hermesRoot = profile.hermes_root;
|
|
208
|
+
const workerPath = profile.worker_path;
|
|
209
|
+
const cliPath = resolve(PACKAGE_ROOT, "dist", "cli.js");
|
|
210
|
+
const toolAllowlist = ["get_all_agents_summary"];
|
|
211
|
+
checks.push({
|
|
212
|
+
name: "Hermes launch profile",
|
|
213
|
+
ok: true,
|
|
214
|
+
detail: `source=${profile.source}; mode=${profile.mode}; command=${JSON.stringify(profile.command)}; hermes_root=${profile.hermes_root ?? "not configured"}`,
|
|
215
|
+
});
|
|
216
|
+
checks.push({
|
|
217
|
+
name: "Hermes worker packaged",
|
|
218
|
+
ok: existsSync(workerPath),
|
|
219
|
+
detail: existsSync(workerPath)
|
|
220
|
+
? workerPath
|
|
221
|
+
: `Missing ${workerPath}; run npm run build.`,
|
|
222
|
+
});
|
|
223
|
+
checks.push({
|
|
224
|
+
name: "Hermes checkout import root",
|
|
225
|
+
ok: Boolean(hermesRoot &&
|
|
226
|
+
existsSync(resolve(hermesRoot, "run_agent.py")) &&
|
|
227
|
+
existsSync(resolve(hermesRoot, "tools", "mcp_tool.py"))),
|
|
228
|
+
detail: hermesRoot && existsSync(resolve(hermesRoot, "run_agent.py"))
|
|
229
|
+
? hermesRoot
|
|
230
|
+
: "ACE does not install Hermes. Set ACE_HERMES_ROOT or pass --hermes-root to a Hermes checkout.",
|
|
231
|
+
});
|
|
232
|
+
const pythonResolves = await runCommandArray(profile.command, ["-c", "import sys; print(sys.executable)"], {
|
|
233
|
+
cwd: PACKAGE_ROOT,
|
|
234
|
+
timeoutMs: 5000,
|
|
235
|
+
});
|
|
236
|
+
checks.push({
|
|
237
|
+
name: "Hermes Python command",
|
|
238
|
+
ok: pythonResolves.ok,
|
|
239
|
+
detail: pythonResolves.ok ? `${JSON.stringify(profile.command)} -> ${pythonResolves.stdout.trim()}` : pythonResolves.detail,
|
|
240
|
+
});
|
|
241
|
+
const importEnv = {
|
|
242
|
+
...process.env,
|
|
243
|
+
PYTHONPATH: [hermesRoot, process.env.PYTHONPATH].filter(Boolean).join(":"),
|
|
244
|
+
};
|
|
245
|
+
const importResult = hermesRoot
|
|
246
|
+
? await runCommandArray(profile.command, ["-c", "from run_agent import AIAgent; print(AIAgent.__name__)"], {
|
|
247
|
+
cwd: hermesRoot,
|
|
248
|
+
env: importEnv,
|
|
249
|
+
timeoutMs: 5000,
|
|
250
|
+
})
|
|
251
|
+
: { ok: false, detail: "Hermes root not configured", stdout: "", stderr: "" };
|
|
252
|
+
checks.push({
|
|
253
|
+
name: "Hermes Python imports",
|
|
254
|
+
ok: importResult.ok,
|
|
255
|
+
detail: importResult.ok ? "run_agent.AIAgent import ok" : importResult.detail,
|
|
256
|
+
});
|
|
257
|
+
checks.push({
|
|
258
|
+
name: "ACE shadow MCP command",
|
|
259
|
+
ok: existsSync(cliPath),
|
|
260
|
+
detail: existsSync(cliPath)
|
|
261
|
+
? JSON.stringify([process.execPath, cliPath, "mcp-shadow", "--tools", toolAllowlist.join(",")])
|
|
262
|
+
: `Missing ${cliPath}; run npm run build.`,
|
|
263
|
+
});
|
|
264
|
+
if (existsSync(cliPath)) {
|
|
265
|
+
try {
|
|
266
|
+
checks.push({
|
|
267
|
+
name: "ACE shadow MCP handshake",
|
|
268
|
+
ok: true,
|
|
269
|
+
detail: await probeHermesShadowMcp(cliPath, toolAllowlist),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
checks.push({
|
|
274
|
+
name: "ACE shadow MCP handshake",
|
|
275
|
+
ok: false,
|
|
276
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
checks.push({
|
|
282
|
+
name: "ACE shadow MCP handshake",
|
|
283
|
+
ok: false,
|
|
284
|
+
detail: `Cannot probe shadow MCP until ${cliPath} exists; run npm run build.`,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
90
288
|
function readLlmProfile() {
|
|
91
289
|
const storeProfile = readStoreJsonSync(WORKSPACE_ROOT, "state/runtime/llm_profile") ??
|
|
92
290
|
readStoreJsonSync(WORKSPACE_ROOT, "state/runtime/llm-profile");
|
|
@@ -654,6 +852,25 @@ async function runDoctor(args) {
|
|
|
654
852
|
}
|
|
655
853
|
}
|
|
656
854
|
}
|
|
855
|
+
if (isLocalLlmProvider(provider) && args.includes("--hermes")) {
|
|
856
|
+
try {
|
|
857
|
+
const hermesProfile = resolveHermesLaunchProfile({
|
|
858
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
859
|
+
cliHermesRoot: readFlagValue(args, "--hermes-root"),
|
|
860
|
+
cliHermesPython: readFlagValue(args, "--hermes-python"),
|
|
861
|
+
cliHermesUvProject: readFlagValue(args, "--hermes-uv-project"),
|
|
862
|
+
cliHermesCommandJson: readFlagValue(args, "--hermes-command-json"),
|
|
863
|
+
});
|
|
864
|
+
await appendHermesLocalReadinessChecks(checks, hermesProfile);
|
|
865
|
+
}
|
|
866
|
+
catch (error) {
|
|
867
|
+
checks.push({
|
|
868
|
+
name: "Hermes launch profile",
|
|
869
|
+
ok: false,
|
|
870
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
657
874
|
const failed = checks.filter((check) => !check.ok);
|
|
658
875
|
console.log("ACE Doctor Report");
|
|
659
876
|
console.log(`Workspace: ${WORKSPACE_ROOT}`);
|
|
@@ -740,11 +957,32 @@ async function main() {
|
|
|
740
957
|
await startStdioServer();
|
|
741
958
|
return;
|
|
742
959
|
}
|
|
960
|
+
if (command === "mcp-shadow") {
|
|
961
|
+
const shadowArgs = args.slice(1);
|
|
962
|
+
const tools = (readFlagValue(shadowArgs, "--tools") ?? "")
|
|
963
|
+
.split(",")
|
|
964
|
+
.map((tool) => tool.trim())
|
|
965
|
+
.filter(Boolean);
|
|
966
|
+
await startHermesShadowStdioServer(tools);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
743
969
|
if (command === "tui") {
|
|
744
970
|
const tuiArgs = args.slice(1);
|
|
745
971
|
const cliProvider = readFlagValue(tuiArgs, "--provider")?.trim() ||
|
|
746
972
|
readFlagValue(tuiArgs, "--llm")?.trim();
|
|
747
973
|
const cliModel = readFlagValue(tuiArgs, "--model")?.trim();
|
|
974
|
+
const cliEngine = readFlagValue(tuiArgs, "--engine")?.trim() ||
|
|
975
|
+
(tuiArgs.includes("--hermes") ? "hermes_local" : undefined);
|
|
976
|
+
const normalizedCliEngine = cliEngine?.toLowerCase().replace(/-/g, "_");
|
|
977
|
+
const hermesLaunchProfile = normalizedCliEngine === "hermes_local"
|
|
978
|
+
? resolveHermesLaunchProfile({
|
|
979
|
+
workspaceRoot: WORKSPACE_ROOT,
|
|
980
|
+
cliHermesRoot: readFlagValue(tuiArgs, "--hermes-root"),
|
|
981
|
+
cliHermesPython: readFlagValue(tuiArgs, "--hermes-python"),
|
|
982
|
+
cliHermesUvProject: readFlagValue(tuiArgs, "--hermes-uv-project"),
|
|
983
|
+
cliHermesCommandJson: readFlagValue(tuiArgs, "--hermes-command-json"),
|
|
984
|
+
})
|
|
985
|
+
: undefined;
|
|
748
986
|
const cliBaseUrl = readBaseUrlFlag(tuiArgs);
|
|
749
987
|
const discovered = discoverProviderContext({
|
|
750
988
|
workspaceRoot: WORKSPACE_ROOT,
|
|
@@ -774,8 +1012,50 @@ async function main() {
|
|
|
774
1012
|
},
|
|
775
1013
|
}
|
|
776
1014
|
: discovered;
|
|
1015
|
+
// Merge any persisted discovery records from the workspace store so recent `ace doctor`
|
|
1016
|
+
// results are reflected immediately in the TUI startup options.
|
|
1017
|
+
try {
|
|
1018
|
+
const store = await openStore(getWorkspaceStorePath(WORKSPACE_ROOT), { readOnly: true });
|
|
1019
|
+
try {
|
|
1020
|
+
const discoveryRepo = new DiscoveryRepository(store);
|
|
1021
|
+
const stored = await discoveryRepo.listAll();
|
|
1022
|
+
const mergedProviders = new Set(resolvedTui.providers ?? []);
|
|
1023
|
+
const mergedModelsByProvider = { ...(resolvedTui.modelsByProvider ?? {}) };
|
|
1024
|
+
const mergedProviderBaseUrls = { ...(resolvedTui.providerBaseUrls ?? {}) };
|
|
1025
|
+
for (const p of stored) {
|
|
1026
|
+
if (!p || !p.provider)
|
|
1027
|
+
continue;
|
|
1028
|
+
const prov = String(p.provider).trim();
|
|
1029
|
+
if (!prov)
|
|
1030
|
+
continue;
|
|
1031
|
+
mergedProviders.add(prov);
|
|
1032
|
+
if (Array.isArray(p.models) && p.models.length > 0) {
|
|
1033
|
+
const existing = new Set(mergedModelsByProvider[prov] ?? []);
|
|
1034
|
+
for (const m of p.models)
|
|
1035
|
+
if (typeof m === "string" && m.trim())
|
|
1036
|
+
existing.add(m.trim());
|
|
1037
|
+
mergedModelsByProvider[prov] = [...existing].sort((a, b) => a.localeCompare(b));
|
|
1038
|
+
}
|
|
1039
|
+
if (p.endpoint && typeof p.endpoint === "string") {
|
|
1040
|
+
mergedProviderBaseUrls[prov] = p.endpoint;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
resolvedTui.providers = [...mergedProviders].sort((a, b) => a.localeCompare(b));
|
|
1044
|
+
resolvedTui.modelsByProvider = { ...(resolvedTui.modelsByProvider ?? {}), ...mergedModelsByProvider };
|
|
1045
|
+
resolvedTui.providerBaseUrls = { ...(resolvedTui.providerBaseUrls ?? {}), ...mergedProviderBaseUrls };
|
|
1046
|
+
}
|
|
1047
|
+
finally {
|
|
1048
|
+
await store.close();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
catch (err) {
|
|
1052
|
+
// Non-fatal — continue with discovered defaults if store read fails.
|
|
1053
|
+
console.warn(`Could not merge discovery records into TUI startup: ${err instanceof Error ? err.message : String(err)}`);
|
|
1054
|
+
}
|
|
777
1055
|
await runTui({
|
|
778
1056
|
provider: resolvedTui.provider,
|
|
1057
|
+
engine: cliEngine,
|
|
1058
|
+
hermesLaunchProfile,
|
|
779
1059
|
model: resolvedTui.model,
|
|
780
1060
|
providers: resolvedTui.providers,
|
|
781
1061
|
modelsByProvider: resolvedTui.modelsByProvider,
|
|
@@ -26,6 +26,8 @@ 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_EXECUTION_ENGINES: readonly ["direct", "hermes_local"];
|
|
30
|
+
export type ExecutionEngine = (typeof ALL_EXECUTION_ENGINES)[number];
|
|
29
31
|
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
32
|
export type AgentRole = (typeof ALL_AGENTS)[number];
|
|
31
33
|
export declare const SWARM_AGENTS: readonly ["orchestrator", "vos", "ui", "coders"];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const HERMES_BRIDGE_PROTOCOL_VERSION = "1.0.0";
|
|
3
|
+
declare const bridgeEventSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<{
|
|
5
|
+
error: "error";
|
|
6
|
+
status: "status";
|
|
7
|
+
tool_start: "tool_start";
|
|
8
|
+
session_open: "session_open";
|
|
9
|
+
turn_run: "turn_run";
|
|
10
|
+
interrupt: "interrupt";
|
|
11
|
+
session_close: "session_close";
|
|
12
|
+
delta: "delta";
|
|
13
|
+
reasoning: "reasoning";
|
|
14
|
+
assistant_interim: "assistant_interim";
|
|
15
|
+
tool_progress: "tool_progress";
|
|
16
|
+
tool_complete: "tool_complete";
|
|
17
|
+
final: "final";
|
|
18
|
+
}>;
|
|
19
|
+
session_id: z.ZodOptional<z.ZodString>;
|
|
20
|
+
turn_id: z.ZodOptional<z.ZodString>;
|
|
21
|
+
sequence: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
}, z.core.$loose>;
|
|
23
|
+
export type HermesBridgeEvent = z.infer<typeof bridgeEventSchema>;
|
|
24
|
+
export interface DecodedHermesBridgeFrame {
|
|
25
|
+
ok: true;
|
|
26
|
+
event: HermesBridgeEvent;
|
|
27
|
+
}
|
|
28
|
+
export interface RejectedHermesBridgeFrame {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: string;
|
|
31
|
+
raw: string;
|
|
32
|
+
}
|
|
33
|
+
export type HermesBridgeFrame = DecodedHermesBridgeFrame | RejectedHermesBridgeFrame;
|
|
34
|
+
export declare class HermesBridgeFrameDecoder {
|
|
35
|
+
private decoder;
|
|
36
|
+
private buffer;
|
|
37
|
+
push(chunk: Uint8Array, end?: boolean): HermesBridgeFrame[];
|
|
38
|
+
}
|
|
39
|
+
export declare function decodeHermesBridgeLine(line: string): HermesBridgeFrame;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=bridge-protocol.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { TextDecoder } from "node:util";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const HERMES_BRIDGE_PROTOCOL_VERSION = "1.0.0";
|
|
4
|
+
const bridgeEventBaseSchema = z.object({
|
|
5
|
+
type: z.enum([
|
|
6
|
+
"session_open",
|
|
7
|
+
"turn_run",
|
|
8
|
+
"interrupt",
|
|
9
|
+
"session_close",
|
|
10
|
+
"status",
|
|
11
|
+
"delta",
|
|
12
|
+
"reasoning",
|
|
13
|
+
"assistant_interim",
|
|
14
|
+
"tool_start",
|
|
15
|
+
"tool_progress",
|
|
16
|
+
"tool_complete",
|
|
17
|
+
"final",
|
|
18
|
+
"error",
|
|
19
|
+
]),
|
|
20
|
+
session_id: z.string().optional(),
|
|
21
|
+
turn_id: z.string().optional(),
|
|
22
|
+
sequence: z.number().int().nonnegative().optional(),
|
|
23
|
+
});
|
|
24
|
+
const bridgeEventSchema = bridgeEventBaseSchema.passthrough();
|
|
25
|
+
function sanitizeBridgeLine(line) {
|
|
26
|
+
if (line.includes("\u0000"))
|
|
27
|
+
return undefined;
|
|
28
|
+
const stripped = line.replace(/[\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
29
|
+
const trimmed = stripped.trim();
|
|
30
|
+
return trimmed ? trimmed : undefined;
|
|
31
|
+
}
|
|
32
|
+
export class HermesBridgeFrameDecoder {
|
|
33
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
34
|
+
buffer = "";
|
|
35
|
+
push(chunk, end = false) {
|
|
36
|
+
this.buffer += this.decoder.decode(chunk, { stream: !end });
|
|
37
|
+
const lines = this.buffer.split(/\r?\n/);
|
|
38
|
+
this.buffer = end ? "" : (lines.pop() ?? "");
|
|
39
|
+
const frames = [];
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
if (!line.trim())
|
|
42
|
+
continue;
|
|
43
|
+
frames.push(decodeHermesBridgeLine(line));
|
|
44
|
+
}
|
|
45
|
+
if (end && this.buffer.trim()) {
|
|
46
|
+
frames.push(decodeHermesBridgeLine(this.buffer));
|
|
47
|
+
this.buffer = "";
|
|
48
|
+
}
|
|
49
|
+
return frames;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function decodeHermesBridgeLine(line) {
|
|
53
|
+
const sanitized = sanitizeBridgeLine(line);
|
|
54
|
+
if (!sanitized) {
|
|
55
|
+
return { ok: false, reason: "bridge_frame_control_bytes", raw: line };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(sanitized);
|
|
59
|
+
const event = bridgeEventSchema.parse(parsed);
|
|
60
|
+
return { ok: true, event };
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
reason: error instanceof Error ? error.message : "bridge_frame_invalid_json",
|
|
66
|
+
raw: sanitized,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=bridge-protocol.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface HermesLaunchProfile {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
mode: "ephemeral_subprocess" | "external_command";
|
|
4
|
+
hermes_root?: string;
|
|
5
|
+
command: readonly string[];
|
|
6
|
+
worker_path: string;
|
|
7
|
+
source: "cli" | "env" | "store" | "default";
|
|
8
|
+
diagnostics: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ResolveHermesLaunchProfileInput {
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
cliHermesRoot?: string;
|
|
13
|
+
cliHermesPython?: string;
|
|
14
|
+
cliHermesUvProject?: string;
|
|
15
|
+
cliHermesCommandJson?: string;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
}
|
|
18
|
+
export declare function resolveHermesLaunchProfile(input: ResolveHermesLaunchProfileInput): HermesLaunchProfile;
|
|
19
|
+
//# sourceMappingURL=launch-profile.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { readStoreJsonSync } from "../store/store-snapshot.js";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const DIST_ROOT = resolve(__dirname, "..");
|
|
8
|
+
const PACKAGE_ROOT = resolve(DIST_ROOT, "..");
|
|
9
|
+
function parseCommandJson(raw, source) {
|
|
10
|
+
if (!raw?.trim())
|
|
11
|
+
return undefined;
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw new Error(`${source} must be a JSON array of command tokens.`);
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(parsed) || parsed.length === 0 || parsed.some((item) => typeof item !== "string" || !item.trim())) {
|
|
20
|
+
throw new Error(`${source} must be a non-empty JSON array of non-empty strings.`);
|
|
21
|
+
}
|
|
22
|
+
return parsed.map((item) => item.trim());
|
|
23
|
+
}
|
|
24
|
+
function defaultHermesRoot() {
|
|
25
|
+
const candidate = resolve(PACKAGE_ROOT, "..", ".tmp", "hermes-agent");
|
|
26
|
+
return existsSync(candidate) ? candidate : undefined;
|
|
27
|
+
}
|
|
28
|
+
export function resolveHermesLaunchProfile(input) {
|
|
29
|
+
const env = input.env ?? process.env;
|
|
30
|
+
const diagnostics = [];
|
|
31
|
+
const workerPath = resolve(PACKAGE_ROOT, "scripts", "hermes_bridge_worker.py");
|
|
32
|
+
const stored = readStoreJsonSync(input.workspaceRoot, "state/runtime/hermes_launch_profile") ??
|
|
33
|
+
undefined;
|
|
34
|
+
const hermesRoot = input.cliHermesRoot ?? env.ACE_HERMES_ROOT ?? stored?.hermes_root ?? defaultHermesRoot();
|
|
35
|
+
const commandJson = input.cliHermesCommandJson ?? env.ACE_HERMES_COMMAND_JSON ?? stored?.hermes_command_json;
|
|
36
|
+
const uvProject = input.cliHermesUvProject ?? env.ACE_HERMES_UV_PROJECT ?? stored?.hermes_uv_project;
|
|
37
|
+
const python = input.cliHermesPython ?? env.HERMES_PYTHON ?? stored?.hermes_python;
|
|
38
|
+
let command;
|
|
39
|
+
let source = "default";
|
|
40
|
+
if (commandJson) {
|
|
41
|
+
command = parseCommandJson(commandJson, input.cliHermesCommandJson ? "--hermes-command-json" : "ACE_HERMES_COMMAND_JSON");
|
|
42
|
+
source = input.cliHermesCommandJson ? "cli" : "env";
|
|
43
|
+
if (!input.cliHermesCommandJson && !env.ACE_HERMES_COMMAND_JSON) {
|
|
44
|
+
source = "store";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (uvProject) {
|
|
48
|
+
command = ["uv", "run", "--project", uvProject, "python"];
|
|
49
|
+
source = input.cliHermesUvProject ? "cli" : "env";
|
|
50
|
+
if (!input.cliHermesUvProject && !env.ACE_HERMES_UV_PROJECT) {
|
|
51
|
+
source = "store";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (python) {
|
|
55
|
+
command = [python];
|
|
56
|
+
source = input.cliHermesPython ? "cli" : "env";
|
|
57
|
+
if (!input.cliHermesPython && !env.HERMES_PYTHON) {
|
|
58
|
+
source = "store";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
command = ["python3"];
|
|
63
|
+
diagnostics.push("HERMES_PYTHON not set; using python3 fallback.");
|
|
64
|
+
}
|
|
65
|
+
if (!hermesRoot) {
|
|
66
|
+
diagnostics.push("ACE_HERMES_ROOT not set and no default Hermes checkout was found.");
|
|
67
|
+
}
|
|
68
|
+
if (!existsSync(workerPath)) {
|
|
69
|
+
diagnostics.push(`Hermes bridge worker is missing at ${workerPath}.`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
enabled: true,
|
|
73
|
+
mode: commandJson ? "external_command" : "ephemeral_subprocess",
|
|
74
|
+
hermes_root: hermesRoot,
|
|
75
|
+
command,
|
|
76
|
+
worker_path: workerPath,
|
|
77
|
+
source,
|
|
78
|
+
diagnostics,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=launch-profile.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BridgeResult } from "../model-bridge.js";
|
|
2
|
+
import type { LocalModelRuntimeConfig } from "../local-model-runtime.js";
|
|
3
|
+
import type { LocalModelExecutionPolicy } from "../local-model-policy.js";
|
|
4
|
+
import { type HermesBridgeEvent } from "./bridge-protocol.js";
|
|
5
|
+
import { type HermesLaunchProfile } from "./launch-profile.js";
|
|
6
|
+
export interface HermesLocalTurnOptions {
|
|
7
|
+
task: string;
|
|
8
|
+
role: string;
|
|
9
|
+
runtime: LocalModelRuntimeConfig;
|
|
10
|
+
policy: LocalModelExecutionPolicy;
|
|
11
|
+
toolScope?: string[];
|
|
12
|
+
maxTurns: number;
|
|
13
|
+
}
|
|
14
|
+
export interface HermesLocalTurnResult {
|
|
15
|
+
result: BridgeResult;
|
|
16
|
+
metadata: {
|
|
17
|
+
bridge_protocol_version: string;
|
|
18
|
+
hermes_session_id: string;
|
|
19
|
+
shadow_mcp_session_id: string;
|
|
20
|
+
execution_engine: "hermes_local";
|
|
21
|
+
provider: string;
|
|
22
|
+
model: string;
|
|
23
|
+
events: HermesBridgeEvent[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface HermesLocalExecutor {
|
|
27
|
+
runTurn(options: HermesLocalTurnOptions): Promise<HermesLocalTurnResult>;
|
|
28
|
+
}
|
|
29
|
+
export interface HermesSubprocessExecutorOptions {
|
|
30
|
+
hermesRoot?: string;
|
|
31
|
+
workerPath?: string;
|
|
32
|
+
pythonCommand?: string;
|
|
33
|
+
command?: readonly string[];
|
|
34
|
+
launchProfile?: HermesLaunchProfile;
|
|
35
|
+
keepTemp?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare class HermesSubprocessExecutor implements HermesLocalExecutor {
|
|
38
|
+
private options;
|
|
39
|
+
constructor(options?: HermesSubprocessExecutorOptions);
|
|
40
|
+
runTurn(options: HermesLocalTurnOptions): Promise<HermesLocalTurnResult>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=session-manager.d.ts.map
|