@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.
Files changed (63) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  5. package/assets/agent-state/runtime-tool-specs.json +70 -2
  6. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  7. package/assets/instructions/ACE_UI.instructions.md +11 -0
  8. package/dist/ace-context.js +70 -11
  9. package/dist/ace-internal-tools.d.ts +3 -1
  10. package/dist/ace-internal-tools.js +10 -2
  11. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  12. package/dist/agent-runtime/role-adapters.js +49 -5
  13. package/dist/astgrep-index.d.ts +48 -0
  14. package/dist/astgrep-index.js +126 -1
  15. package/dist/cli.js +205 -15
  16. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  17. package/dist/discovery-runtime-wrappers.js +615 -0
  18. package/dist/helpers/bootstrap.js +1 -1
  19. package/dist/helpers/constants.d.ts +2 -2
  20. package/dist/helpers/constants.js +7 -0
  21. package/dist/helpers/path-utils.d.ts +8 -1
  22. package/dist/helpers/path-utils.js +27 -8
  23. package/dist/helpers/store-resolution.js +7 -3
  24. package/dist/job-scheduler.js +30 -4
  25. package/dist/json-sanitizer.d.ts +16 -0
  26. package/dist/json-sanitizer.js +26 -0
  27. package/dist/local-model-policy.d.ts +27 -0
  28. package/dist/local-model-policy.js +84 -0
  29. package/dist/local-model-runtime.d.ts +6 -0
  30. package/dist/local-model-runtime.js +21 -20
  31. package/dist/model-bridge.d.ts +6 -1
  32. package/dist/model-bridge.js +338 -21
  33. package/dist/orchestrator-supervisor.d.ts +42 -0
  34. package/dist/orchestrator-supervisor.js +110 -3
  35. package/dist/plan-proposal.d.ts +115 -0
  36. package/dist/plan-proposal.js +1073 -0
  37. package/dist/runtime-executor.d.ts +6 -1
  38. package/dist/runtime-executor.js +72 -5
  39. package/dist/runtime-tool-specs.d.ts +19 -1
  40. package/dist/runtime-tool-specs.js +67 -26
  41. package/dist/schemas.js +29 -1
  42. package/dist/server.js +51 -0
  43. package/dist/shared.d.ts +1 -0
  44. package/dist/shared.js +2 -0
  45. package/dist/store/bootstrap-store.d.ts +1 -0
  46. package/dist/store/bootstrap-store.js +8 -2
  47. package/dist/store/repositories/local-model-runtime-repository.d.ts +1 -1
  48. package/dist/store/repositories/local-model-runtime-repository.js +1 -1
  49. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  50. package/dist/tools-agent.d.ts +20 -0
  51. package/dist/tools-agent.js +538 -28
  52. package/dist/tools-discovery.js +135 -0
  53. package/dist/tools-files.js +768 -66
  54. package/dist/tools-framework.js +80 -61
  55. package/dist/tui/index.js +10 -1
  56. package/dist/tui/ollama.d.ts +8 -1
  57. package/dist/tui/ollama.js +53 -12
  58. package/dist/tui/openai-compatible.d.ts +13 -0
  59. package/dist/tui/openai-compatible.js +305 -5
  60. package/dist/tui/provider-discovery.d.ts +1 -0
  61. package/dist/tui/provider-discovery.js +35 -11
  62. package/dist/vericify-bridge.d.ts +1 -1
  63. package/package.json +1 -1
@@ -21,6 +21,7 @@ import { auditStoreAuthority, writeStoreAuthorityAuditReport, } from "./store/st
21
21
  import { PROVENANCE_CRITICAL_EVENT_TYPES, validateArtifactManifestPayload, validateProvenanceLogContent, validateTealConfigContent, } from "./schemas.js";
22
22
  import { readAceTaskContractAssessment } from "./ace-autonomy.js";
23
23
  import { listStoreKeysSync, readStoreBlobSync } from "./store/store-snapshot.js";
24
+ import { parseJsonLikeText } from "./json-sanitizer.js";
24
25
  function executionRoleForDomain(domain) {
25
26
  switch (domain) {
26
27
  case "venture":
@@ -75,6 +76,14 @@ function parseGateManifest(raw, sourceRef) {
75
76
  return undefined;
76
77
  }
77
78
  }
79
+ function parseJsonForValidation(raw) {
80
+ return parseJsonLikeText(raw);
81
+ }
82
+ function jsonValidationDetail(parsed, validDetail = "valid JSON") {
83
+ return parsed.sanitized.removed_control_bytes > 0
84
+ ? `control bytes removed before parse (${parsed.sanitized.removed_control_bytes}); source requires repair`
85
+ : validDetail;
86
+ }
78
87
  function readGateManifests(gatesDir) {
79
88
  const files = readdirSync(gatesDir).filter((f) => f.endsWith(".json"));
80
89
  const allGates = [];
@@ -737,7 +746,10 @@ export function registerFrameworkTools(server) {
737
746
  "",
738
747
  "## LLM Runtime Profile",
739
748
  `- provider: ${llm_provider}`,
740
- `- model: ${resolvedLlmModel ?? defaultModelForProvider(llm_provider)}`,
749
+ `- model: ${resolvedLlmModel ??
750
+ (llm_provider === "llama.cpp"
751
+ ? "(set via ace connect or ace doctor --scan)"
752
+ : defaultModelForProvider(llm_provider))}`,
741
753
  `- base_url: ${resolvedLlmBaseUrl ??
742
754
  (llm_provider === "ollama" || llm_provider === "llama.cpp"
743
755
  ? "discover via ace doctor --scan or set explicitly"
@@ -1010,58 +1022,60 @@ export function registerFrameworkTools(server) {
1010
1022
  // Check: handoff registry parseable
1011
1023
  const hrRaw = safeRead("agent-state/handoff-registry.json");
1012
1024
  const hrOk = !hrRaw.startsWith("[FILE NOT FOUND]");
1025
+ let hrParsed;
1013
1026
  if (hrOk) {
1014
- try {
1015
- JSON.parse(hrRaw);
1027
+ const parsed = parseJsonForValidation(hrRaw);
1028
+ if (parsed.ok) {
1029
+ hrParsed = parsed.value;
1016
1030
  checks.push({
1017
1031
  name: "handoff-registry:parse",
1018
- ok: true,
1019
- detail: "valid JSON",
1032
+ ok: parsed.sanitized.removed_control_bytes === 0,
1033
+ detail: jsonValidationDetail(parsed),
1020
1034
  });
1021
1035
  }
1022
- catch {
1036
+ else {
1023
1037
  checks.push({
1024
1038
  name: "handoff-registry:parse",
1025
1039
  ok: false,
1026
- detail: "CORRUPT JSON",
1040
+ detail: `agent-state/handoff-registry.json: ${parsed.error}`,
1027
1041
  });
1028
1042
  }
1029
1043
  }
1030
1044
  // Check: run-ledger parseable
1031
1045
  const rlRaw = safeRead("agent-state/run-ledger.json");
1032
1046
  if (!rlRaw.startsWith("[FILE NOT FOUND]")) {
1033
- try {
1034
- JSON.parse(rlRaw);
1047
+ const parsed = parseJsonForValidation(rlRaw);
1048
+ if (parsed.ok) {
1035
1049
  checks.push({
1036
1050
  name: "run-ledger:parse",
1037
- ok: true,
1038
- detail: "valid JSON",
1051
+ ok: parsed.sanitized.removed_control_bytes === 0,
1052
+ detail: jsonValidationDetail(parsed),
1039
1053
  });
1040
1054
  }
1041
- catch {
1055
+ else {
1042
1056
  checks.push({
1043
1057
  name: "run-ledger:parse",
1044
1058
  ok: false,
1045
- detail: "CORRUPT JSON",
1059
+ detail: `agent-state/run-ledger.json: ${parsed.error}`,
1046
1060
  });
1047
1061
  }
1048
1062
  }
1049
1063
  // Check: todo-state parseable
1050
1064
  const tsRaw = safeRead("agent-state/todo-state.json");
1051
1065
  if (!tsRaw.startsWith("[FILE NOT FOUND]")) {
1052
- try {
1053
- JSON.parse(tsRaw);
1066
+ const parsed = parseJsonForValidation(tsRaw);
1067
+ if (parsed.ok) {
1054
1068
  checks.push({
1055
1069
  name: "todo-state:parse",
1056
- ok: true,
1057
- detail: "valid JSON",
1070
+ ok: parsed.sanitized.removed_control_bytes === 0,
1071
+ detail: jsonValidationDetail(parsed),
1058
1072
  });
1059
1073
  }
1060
- catch {
1074
+ else {
1061
1075
  checks.push({
1062
1076
  name: "todo-state:parse",
1063
1077
  ok: false,
1064
- detail: "CORRUPT JSON",
1078
+ detail: `agent-state/todo-state.json: ${parsed.error}`,
1065
1079
  });
1066
1080
  }
1067
1081
  }
@@ -1081,19 +1095,19 @@ export function registerFrameworkTools(server) {
1081
1095
  });
1082
1096
  continue;
1083
1097
  }
1084
- try {
1085
- JSON.parse(raw);
1098
+ const parsed = parseJsonForValidation(raw);
1099
+ if (parsed.ok) {
1086
1100
  checks.push({
1087
1101
  name: check.name,
1088
- ok: true,
1089
- detail: "valid JSON",
1102
+ ok: parsed.sanitized.removed_control_bytes === 0,
1103
+ detail: jsonValidationDetail(parsed),
1090
1104
  });
1091
1105
  }
1092
- catch {
1106
+ else {
1093
1107
  checks.push({
1094
1108
  name: check.name,
1095
1109
  ok: false,
1096
- detail: "CORRUPT JSON",
1110
+ detail: `${check.rel}: ${parsed.error}`,
1097
1111
  });
1098
1112
  }
1099
1113
  }
@@ -1147,25 +1161,28 @@ export function registerFrameworkTools(server) {
1147
1161
  const artifactManifestRaw = safeRead("agent-state/ARTIFACT_MANIFEST.json");
1148
1162
  let artifactManifestEntries = [];
1149
1163
  if (!artifactManifestRaw.startsWith("[FILE NOT FOUND]")) {
1150
- try {
1151
- const parsed = JSON.parse(artifactManifestRaw);
1152
- const validation = validateArtifactManifestPayload(parsed);
1164
+ const parsed = parseJsonForValidation(artifactManifestRaw);
1165
+ if (parsed.ok) {
1166
+ const validation = validateArtifactManifestPayload(parsed.value);
1167
+ const controlBytesOk = parsed.sanitized.removed_control_bytes === 0;
1153
1168
  checks.push({
1154
1169
  name: "artifact-manifest:schema",
1155
- ok: validation.ok,
1156
- detail: validation.ok
1157
- ? `valid (${validation.schema})`
1158
- : validation.errors.join("; "),
1170
+ ok: validation.ok && controlBytesOk,
1171
+ detail: !controlBytesOk
1172
+ ? jsonValidationDetail(parsed)
1173
+ : validation.ok
1174
+ ? `valid (${validation.schema})`
1175
+ : validation.errors.join("; "),
1159
1176
  });
1160
1177
  if (validation.ok) {
1161
- artifactManifestEntries = getArtifactManifestEntries(parsed);
1178
+ artifactManifestEntries = getArtifactManifestEntries(parsed.value);
1162
1179
  }
1163
1180
  }
1164
- catch {
1181
+ else {
1165
1182
  checks.push({
1166
1183
  name: "artifact-manifest:parse",
1167
1184
  ok: false,
1168
- detail: "CORRUPT JSON",
1185
+ detail: `agent-state/ARTIFACT_MANIFEST.json: ${parsed.error}`,
1169
1186
  });
1170
1187
  }
1171
1188
  }
@@ -1203,8 +1220,16 @@ export function registerFrameworkTools(server) {
1203
1220
  // Check: module registry role coverage vs event emitters
1204
1221
  const registryRaw = safeRead("agent-state/MODULES/registry.json");
1205
1222
  if (!registryRaw.startsWith("[FILE NOT FOUND]")) {
1206
- try {
1207
- const registry = JSON.parse(registryRaw);
1223
+ const parsed = parseJsonForValidation(registryRaw);
1224
+ if (parsed.ok) {
1225
+ if (parsed.sanitized.removed_control_bytes > 0) {
1226
+ checks.push({
1227
+ name: "registry:parse",
1228
+ ok: false,
1229
+ detail: jsonValidationDetail(parsed),
1230
+ });
1231
+ }
1232
+ const registry = parsed.value;
1208
1233
  const registeredRoles = new Set(registry.roles ?? []);
1209
1234
  const expectedEmitters = [
1210
1235
  "capability-safety",
@@ -1223,11 +1248,11 @@ export function registerFrameworkTools(server) {
1223
1248
  });
1224
1249
  }
1225
1250
  }
1226
- catch {
1251
+ else {
1227
1252
  checks.push({
1228
1253
  name: "registry:parse",
1229
1254
  ok: false,
1230
- detail: "registry.json is corrupt",
1255
+ detail: `agent-state/MODULES/registry.json: ${parsed.error}`,
1231
1256
  });
1232
1257
  }
1233
1258
  }
@@ -1248,30 +1273,24 @@ export function registerFrameworkTools(server) {
1248
1273
  });
1249
1274
  }
1250
1275
  // Check: handoff consistency (open handoffs have source files)
1251
- if (hrOk) {
1252
- try {
1253
- const registry = JSON.parse(hrRaw);
1254
- const handoffs = Object.values(registry.handoffs ?? {});
1255
- let openWithoutFile = 0;
1256
- for (const h of handoffs) {
1257
- if ((h.status === "open" || h.status === "accepted") &&
1258
- h.source_file) {
1259
- const fileContent = safeRead(h.source_file);
1260
- if (fileContent.startsWith("[FILE NOT FOUND]"))
1261
- openWithoutFile++;
1262
- }
1276
+ if (hrOk && hrParsed) {
1277
+ const handoffs = Object.values(hrParsed.handoffs ?? {});
1278
+ let openWithoutFile = 0;
1279
+ for (const h of handoffs) {
1280
+ if ((h.status === "open" || h.status === "accepted") &&
1281
+ h.source_file) {
1282
+ const fileContent = safeRead(h.source_file);
1283
+ if (fileContent.startsWith("[FILE NOT FOUND]"))
1284
+ openWithoutFile++;
1263
1285
  }
1264
- checks.push({
1265
- name: "handoff:source-file-integrity",
1266
- ok: openWithoutFile === 0,
1267
- detail: openWithoutFile === 0
1268
- ? "all open handoffs have source files"
1269
- : `${openWithoutFile} open handoffs reference missing source files`,
1270
- });
1271
- }
1272
- catch {
1273
- /* already caught above */
1274
1286
  }
1287
+ checks.push({
1288
+ name: "handoff:source-file-integrity",
1289
+ ok: openWithoutFile === 0,
1290
+ detail: openWithoutFile === 0
1291
+ ? "all open handoffs have source files"
1292
+ : `${openWithoutFile} open handoffs reference missing source files`,
1293
+ });
1275
1294
  }
1276
1295
  // Check: event provenance gaps in recent events.
1277
1296
  // Scans for provenance-critical event types that are missing
package/dist/tui/index.js CHANGED
@@ -56,7 +56,13 @@ export class AceTui {
56
56
  const workspaceRoot = options.workspaceRoot ?? WORKSPACE_ROOT;
57
57
  this.workspaceRoot = workspaceRoot;
58
58
  this.provider = this.normalizeProvider(options.provider ?? inferProviderFromModel(options.model) ?? "ollama") ?? "ollama";
59
- this.model = (options.model ?? defaultModelForProvider(this.provider)).trim();
59
+ const initialModel = options.model ??
60
+ (this.provider === "ollama"
61
+ ? defaultModelForProvider(this.provider)
62
+ : this.provider === "llama.cpp"
63
+ ? ""
64
+ : defaultModelForProvider(this.provider));
65
+ this.model = initialModel.trim();
60
66
  // Initialize modules
61
67
  const colorLevel = detectColorLevel();
62
68
  for (const [provider, baseUrl] of Object.entries(options.providerBaseUrls ?? {})) {
@@ -757,6 +763,9 @@ export class AceTui {
757
763
  else if (this.provider === "ollama") {
758
764
  this.model = DEFAULT_OLLAMA_MODEL;
759
765
  }
766
+ else if (this.provider === "llama.cpp") {
767
+ this.model = "";
768
+ }
760
769
  else {
761
770
  this.model = defaultModelForProvider(this.provider);
762
771
  }
@@ -111,6 +111,13 @@ export declare class OllamaClient {
111
111
  export declare class OllamaError extends Error {
112
112
  statusCode: number;
113
113
  responseBody: string;
114
- constructor(message: string, statusCode: number, responseBody: string);
114
+ kind?: string;
115
+ meta?: Record<string, unknown>;
116
+ suggested_remediation?: string;
117
+ constructor(message: string, statusCode: number, responseBody: string, options?: {
118
+ kind?: string;
119
+ model?: string;
120
+ suggested_remediation?: string;
121
+ });
115
122
  }
116
123
  //# sourceMappingURL=ollama.d.ts.map
@@ -5,6 +5,24 @@
5
5
  * Zero dependencies — uses Node.js built-in fetch/http.
6
6
  * Supports model listing, pulling, chat streaming, and health checks.
7
7
  */
8
+ function sleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ function requestedModelFromOptions(options) {
12
+ if (typeof options?.body !== "string")
13
+ return undefined;
14
+ try {
15
+ const parsed = JSON.parse(options.body);
16
+ return typeof parsed.model === "string"
17
+ ? parsed.model
18
+ : typeof parsed.name === "string"
19
+ ? parsed.name
20
+ : undefined;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
8
26
  // ── Client ───────────────────────────────────────────────────────────
9
27
  export class OllamaClient {
10
28
  baseUrl;
@@ -124,19 +142,36 @@ export class OllamaClient {
124
142
  throw new OllamaError("Ollama base URL is not configured. Set one explicitly or run `ace doctor --scan`.", 0, "");
125
143
  }
126
144
  const url = `${this.baseUrl}${path}`;
127
- const res = await fetch(url, {
128
- ...options,
129
- headers: {
130
- "Content-Type": "application/json",
131
- Accept: "application/json",
132
- ...(options?.headers ?? {}),
133
- },
134
- });
135
- if (!res.ok) {
145
+ const maxAttempts = 3;
146
+ let lastError;
147
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
148
+ const res = await fetch(url, {
149
+ ...options,
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ Accept: "application/json",
153
+ ...(options?.headers ?? {}),
154
+ },
155
+ });
156
+ if (res.ok)
157
+ return res;
136
158
  const text = await res.text().catch(() => "");
137
- throw new OllamaError(`Ollama API error: ${res.status} ${res.statusText}`, res.status, text);
159
+ const requestedModel = requestedModelFromOptions(options);
160
+ if (res.status >= 500 && /unable to load model/i.test(text)) {
161
+ throw new OllamaError(`Ollama was unable to load model${requestedModel ? ` '${requestedModel}'` : ""}. ` +
162
+ `Run \`ollama pull ${requestedModel ?? "<model>"}\` or \`ace doctor --repair-ollama\`.`, res.status, text, {
163
+ kind: "ollama_model_load_error",
164
+ model: requestedModel,
165
+ suggested_remediation: `run ollama pull ${requestedModel ?? "<model>"} or ace doctor --repair-ollama`,
166
+ });
167
+ }
168
+ lastError = new OllamaError(`Ollama API error: ${res.status} ${res.statusText}`, res.status, text);
169
+ if (res.status < 500 || attempt === maxAttempts) {
170
+ throw lastError;
171
+ }
172
+ await sleep(100 * attempt);
138
173
  }
139
- return res;
174
+ throw lastError ?? new OllamaError("Ollama API request failed", 0, "");
140
175
  }
141
176
  /** Stream newline-delimited JSON from a ReadableStream */
142
177
  async *streamJsonLines(body) {
@@ -182,11 +217,17 @@ export class OllamaClient {
182
217
  export class OllamaError extends Error {
183
218
  statusCode;
184
219
  responseBody;
185
- constructor(message, statusCode, responseBody) {
220
+ kind;
221
+ meta;
222
+ suggested_remediation;
223
+ constructor(message, statusCode, responseBody, options) {
186
224
  super(message);
187
225
  this.statusCode = statusCode;
188
226
  this.responseBody = responseBody;
189
227
  this.name = "OllamaError";
228
+ this.kind = options?.kind;
229
+ this.meta = options?.model ? { model: options.model } : undefined;
230
+ this.suggested_remediation = options?.suggested_remediation;
190
231
  }
191
232
  }
192
233
  //# sourceMappingURL=ollama.js.map
@@ -15,6 +15,7 @@ export interface OpenAICompatibleChatRequest {
15
15
  messages: OpenAICompatibleChatMessage[];
16
16
  temperature?: number;
17
17
  topP?: number;
18
+ onProviderEvent?: (event: OpenAICompatibleProviderEvent) => void;
18
19
  }
19
20
  export interface OpenAICompatibleChatChunk {
20
21
  text: string;
@@ -22,6 +23,18 @@ export interface OpenAICompatibleChatChunk {
22
23
  promptTokens?: number;
23
24
  completionTokens?: number;
24
25
  }
26
+ export type OpenAICompatibleAdapterStage = "streaming_chat" | "non_streaming_chat" | "completions";
27
+ export interface OpenAICompatibleProviderEvent {
28
+ provider: string;
29
+ stage: OpenAICompatibleAdapterStage;
30
+ event: "attempt" | "success" | "fallback" | "parse_error";
31
+ reason?: string;
32
+ statusCode?: number;
33
+ next_stage?: OpenAICompatibleAdapterStage;
34
+ fallback_count?: number;
35
+ sample_hex?: string;
36
+ sample_text?: string;
37
+ }
25
38
  export interface ProviderConfigOverride {
26
39
  baseUrl?: string;
27
40
  apiKey?: string;