@voybio/ace-swarm 0.2.5 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +21 -13
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  5. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  6. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  7. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  10. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  11. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  12. package/assets/agent-state/STATUS.md +2 -2
  13. package/assets/agent-state/runtime-tool-specs.json +70 -2
  14. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  15. package/assets/instructions/ACE_UI.instructions.md +11 -0
  16. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  17. package/assets/scripts/render-mcp-configs.sh +19 -5
  18. package/dist/ace-context.js +91 -11
  19. package/dist/ace-internal-tools.d.ts +3 -1
  20. package/dist/ace-internal-tools.js +10 -2
  21. package/dist/ace-server-instructions.js +3 -3
  22. package/dist/ace-state-resolver.js +5 -3
  23. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  24. package/dist/agent-runtime/role-adapters.js +49 -5
  25. package/dist/astgrep-index.d.ts +57 -1
  26. package/dist/astgrep-index.js +140 -4
  27. package/dist/cli.js +232 -35
  28. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  29. package/dist/discovery-runtime-wrappers.js +615 -0
  30. package/dist/handoff-registry.js +5 -5
  31. package/dist/helpers/artifacts.d.ts +19 -0
  32. package/dist/helpers/artifacts.js +152 -0
  33. package/dist/helpers/bootstrap.d.ts +24 -0
  34. package/dist/helpers/bootstrap.js +894 -0
  35. package/dist/helpers/constants.d.ts +53 -0
  36. package/dist/helpers/constants.js +295 -0
  37. package/dist/helpers/drift.d.ts +13 -0
  38. package/dist/helpers/drift.js +45 -0
  39. package/dist/helpers/path-utils.d.ts +24 -0
  40. package/dist/helpers/path-utils.js +123 -0
  41. package/dist/helpers/store-resolution.d.ts +19 -0
  42. package/dist/helpers/store-resolution.js +305 -0
  43. package/dist/helpers/workspace-root.d.ts +3 -0
  44. package/dist/helpers/workspace-root.js +80 -0
  45. package/dist/helpers.d.ts +8 -125
  46. package/dist/helpers.js +8 -1768
  47. package/dist/job-scheduler.js +33 -7
  48. package/dist/json-sanitizer.d.ts +16 -0
  49. package/dist/json-sanitizer.js +26 -0
  50. package/dist/local-model-policy.d.ts +27 -0
  51. package/dist/local-model-policy.js +84 -0
  52. package/dist/local-model-runtime.d.ts +6 -0
  53. package/dist/local-model-runtime.js +33 -21
  54. package/dist/model-bridge.d.ts +13 -1
  55. package/dist/model-bridge.js +410 -23
  56. package/dist/orchestrator-supervisor.d.ts +56 -0
  57. package/dist/orchestrator-supervisor.js +179 -1
  58. package/dist/plan-proposal.d.ts +115 -0
  59. package/dist/plan-proposal.js +1073 -0
  60. package/dist/run-ledger.js +3 -3
  61. package/dist/runtime-command.d.ts +8 -0
  62. package/dist/runtime-command.js +38 -6
  63. package/dist/runtime-executor.d.ts +20 -1
  64. package/dist/runtime-executor.js +737 -172
  65. package/dist/runtime-profile.d.ts +32 -0
  66. package/dist/runtime-profile.js +89 -13
  67. package/dist/runtime-tool-specs.d.ts +39 -0
  68. package/dist/runtime-tool-specs.js +144 -28
  69. package/dist/safe-edit.d.ts +7 -0
  70. package/dist/safe-edit.js +163 -37
  71. package/dist/schemas.js +48 -1
  72. package/dist/server.js +51 -0
  73. package/dist/shared.d.ts +3 -2
  74. package/dist/shared.js +2 -0
  75. package/dist/status-events.js +9 -6
  76. package/dist/store/ace-packed-store.d.ts +3 -2
  77. package/dist/store/ace-packed-store.js +188 -110
  78. package/dist/store/bootstrap-store.d.ts +2 -1
  79. package/dist/store/bootstrap-store.js +102 -83
  80. package/dist/store/cache-workspace.js +11 -5
  81. package/dist/store/materializers/context-snapshot-materializer.js +6 -2
  82. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  83. package/dist/store/materializers/hook-context-materializer.js +11 -21
  84. package/dist/store/materializers/host-file-materializer.js +6 -0
  85. package/dist/store/materializers/projection-manager.d.ts +0 -1
  86. package/dist/store/materializers/projection-manager.js +5 -13
  87. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  88. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  89. package/dist/store/materializers/vericify-projector.js +11 -11
  90. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  91. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  92. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  93. package/dist/store/skills-install.d.ts +4 -0
  94. package/dist/store/skills-install.js +21 -12
  95. package/dist/store/state-reader.d.ts +2 -0
  96. package/dist/store/state-reader.js +20 -0
  97. package/dist/store/store-artifacts.d.ts +7 -0
  98. package/dist/store/store-artifacts.js +27 -1
  99. package/dist/store/store-authority-audit.d.ts +18 -1
  100. package/dist/store/store-authority-audit.js +115 -5
  101. package/dist/store/store-snapshot.d.ts +3 -0
  102. package/dist/store/store-snapshot.js +22 -2
  103. package/dist/store/workspace-store-paths.d.ts +39 -0
  104. package/dist/store/workspace-store-paths.js +94 -0
  105. package/dist/store/write-coordinator.d.ts +65 -0
  106. package/dist/store/write-coordinator.js +386 -0
  107. package/dist/todo-state.js +5 -5
  108. package/dist/tools-agent.d.ts +20 -0
  109. package/dist/tools-agent.js +789 -25
  110. package/dist/tools-discovery.js +136 -1
  111. package/dist/tools-files.d.ts +7 -0
  112. package/dist/tools-files.js +1002 -11
  113. package/dist/tools-framework.js +105 -66
  114. package/dist/tools-handoff.js +2 -2
  115. package/dist/tools-lifecycle.js +4 -4
  116. package/dist/tools-memory.js +6 -6
  117. package/dist/tools-todo.js +2 -2
  118. package/dist/tracker-adapters.d.ts +1 -1
  119. package/dist/tracker-adapters.js +13 -18
  120. package/dist/tracker-sync.js +5 -3
  121. package/dist/tui/agent-runner.js +3 -1
  122. package/dist/tui/chat.js +103 -7
  123. package/dist/tui/dashboard.d.ts +1 -0
  124. package/dist/tui/dashboard.js +43 -0
  125. package/dist/tui/index.js +10 -1
  126. package/dist/tui/layout.d.ts +20 -0
  127. package/dist/tui/layout.js +31 -1
  128. package/dist/tui/local-model-contract.d.ts +6 -2
  129. package/dist/tui/local-model-contract.js +16 -3
  130. package/dist/tui/ollama.d.ts +8 -1
  131. package/dist/tui/ollama.js +53 -12
  132. package/dist/tui/openai-compatible.d.ts +13 -0
  133. package/dist/tui/openai-compatible.js +305 -5
  134. package/dist/tui/provider-discovery.d.ts +1 -0
  135. package/dist/tui/provider-discovery.js +35 -11
  136. package/dist/vericify-bridge.d.ts +6 -1
  137. package/dist/vericify-bridge.js +27 -3
  138. package/dist/workspace-manager.d.ts +30 -3
  139. package/dist/workspace-manager.js +257 -27
  140. package/package.json +1 -2
  141. package/dist/internal-tool-runtime.d.ts +0 -21
  142. package/dist/internal-tool-runtime.js +0 -136
  143. package/dist/store/workspace-snapshot.d.ts +0 -26
  144. package/dist/store/workspace-snapshot.js +0 -107
@@ -5,7 +5,104 @@
5
5
  * - codex: defaults to OpenAI API endpoint/key env vars
6
6
  * - others: require provider-specific base URL + API key env vars
7
7
  */
8
+ import { appendFileSync } from "node:fs";
8
9
  import { buildOpenAiCompatibleBaseUrl } from "./provider-discovery.js";
10
+ function sanitizeTransportText(input) {
11
+ return input
12
+ .replace(/^\uFEFF/, "")
13
+ .replace(/\r\n?/g, "\n")
14
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "");
15
+ }
16
+ function sampleBytes(bytes, decoded) {
17
+ const buffer = Buffer.from(bytes);
18
+ return {
19
+ sample_hex: buffer.toString("hex").slice(0, 512),
20
+ sample_text: sanitizeTransportText(decoded).slice(0, 512),
21
+ };
22
+ }
23
+ function captureRawBytes(label, bytes) {
24
+ const target = process.env.ACE_RAW_RESPONSE_LOG?.trim();
25
+ if (!target)
26
+ return;
27
+ const decoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
28
+ const sample = sampleBytes(bytes, decoded);
29
+ try {
30
+ appendFileSync(target, `${JSON.stringify({
31
+ at: new Date().toISOString(),
32
+ label,
33
+ byte_length: bytes.byteLength,
34
+ ...sample,
35
+ })}\n`, "utf8");
36
+ }
37
+ catch {
38
+ // Raw capture is opt-in diagnostics; never fail the provider call because logging failed.
39
+ }
40
+ }
41
+ async function readResponseText(response, label) {
42
+ const bytes = new Uint8Array(await response.arrayBuffer());
43
+ captureRawBytes(label, bytes);
44
+ const decoded = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
45
+ const text = sanitizeTransportText(decoded);
46
+ const sample = sampleBytes(bytes, decoded);
47
+ return {
48
+ text,
49
+ byteLength: bytes.byteLength,
50
+ ...sample,
51
+ };
52
+ }
53
+ function sanitizeChatMessages(messages) {
54
+ return (Array.isArray(messages) ? messages : [])
55
+ .map((msg) => {
56
+ const role = msg?.role === "system" || msg?.role === "assistant" || msg?.role === "user"
57
+ ? msg.role
58
+ : "user";
59
+ const content = typeof msg?.content === "string"
60
+ ? sanitizeTransportText(msg.content)
61
+ : sanitizeTransportText(String(msg?.content ?? ""));
62
+ return { role, content };
63
+ })
64
+ .filter((msg) => msg.role === "assistant" || msg.content.trim().length > 0);
65
+ }
66
+ function extractChoiceText(payload) {
67
+ if (!payload || typeof payload !== "object")
68
+ return "";
69
+ const choices = Array.isArray(payload.choices)
70
+ ? (payload.choices ?? [])
71
+ : [];
72
+ const firstChoice = choices[0];
73
+ if (!firstChoice || typeof firstChoice !== "object")
74
+ return "";
75
+ const choice = firstChoice;
76
+ if (typeof choice.message?.content === "string")
77
+ return sanitizeTransportText(choice.message.content);
78
+ if (typeof choice.text === "string")
79
+ return sanitizeTransportText(choice.text);
80
+ if (typeof choice.delta?.content === "string")
81
+ return sanitizeTransportText(choice.delta.content);
82
+ return "";
83
+ }
84
+ function extractProviderBodyText(bodyText) {
85
+ const trimmed = sanitizeTransportText(bodyText).trim();
86
+ if (!trimmed)
87
+ return { text: "", parsedJson: false };
88
+ try {
89
+ const payload = JSON.parse(trimmed);
90
+ const extracted = extractChoiceText(payload);
91
+ return {
92
+ text: extracted || trimmed,
93
+ parsedJson: true,
94
+ };
95
+ }
96
+ catch {
97
+ return {
98
+ text: trimmed,
99
+ parsedJson: false,
100
+ };
101
+ }
102
+ }
103
+ function formatErrorMessage(error) {
104
+ return error instanceof Error ? error.message : String(error);
105
+ }
9
106
  export class OpenAICompatibleError extends Error {
10
107
  statusCode;
11
108
  responseBody;
@@ -46,14 +143,216 @@ export class OpenAICompatibleClient {
46
143
  const resolvedModel = provider === "copilot"
47
144
  ? normalizeCopilotModelName(request.model)
48
145
  : request.model;
146
+ if (provider === "llama.cpp") {
147
+ const emitProviderEvent = (event) => {
148
+ request.onProviderEvent?.({ provider, ...event });
149
+ };
150
+ let fallbackCount = 0;
151
+ const emitFallback = (stage, nextStage, reason, extra = {}) => {
152
+ fallbackCount += 1;
153
+ emitProviderEvent({
154
+ event: "fallback",
155
+ stage,
156
+ next_stage: nextStage,
157
+ reason,
158
+ fallback_count: fallbackCount,
159
+ statusCode: extra.statusCode,
160
+ sample_hex: extra.sample_hex,
161
+ sample_text: extra.sample_text,
162
+ });
163
+ };
164
+ const messagesToSend = sanitizeChatMessages(request.messages);
165
+ const messagesToPrompt = messagesToSend.map((m) => `${m.role}: ${m.content}`).join("\n");
166
+ // Attempt 1: streaming chat (SSE)
167
+ try {
168
+ emitProviderEvent({ event: "attempt", stage: "streaming_chat" });
169
+ const streamBody = {
170
+ model: resolvedModel,
171
+ messages: messagesToSend,
172
+ stream: true,
173
+ temperature: request.temperature,
174
+ top_p: request.topP,
175
+ };
176
+ const streamRes = await fetch(`${config.baseUrl}/chat/completions`, {
177
+ method: "POST",
178
+ signal: controller.signal,
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ Accept: "text/event-stream",
182
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
183
+ ...(config.headers ?? {}),
184
+ },
185
+ body: JSON.stringify(streamBody),
186
+ });
187
+ if (streamRes.ok && streamRes.body) {
188
+ let streamContentSeen = false;
189
+ let streamParsedFrames = 0;
190
+ let streamParseErrors = 0;
191
+ for await (const data of this.streamSseData(streamRes.body)) {
192
+ if (data === "[DONE]") {
193
+ continue;
194
+ }
195
+ let parsed;
196
+ try {
197
+ parsed = JSON.parse(data);
198
+ }
199
+ catch {
200
+ streamParseErrors += 1;
201
+ emitProviderEvent({
202
+ event: "parse_error",
203
+ stage: "streaming_chat",
204
+ reason: "sse_data_json_parse_error",
205
+ sample_text: data.slice(0, 512),
206
+ });
207
+ continue;
208
+ }
209
+ streamParsedFrames += 1;
210
+ const usage = (parsed.usage ?? {});
211
+ if (typeof usage.prompt_tokens === "number")
212
+ promptTokens = usage.prompt_tokens;
213
+ if (typeof usage.completion_tokens === "number")
214
+ completionTokens = usage.completion_tokens;
215
+ const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
216
+ const firstChoice = (choices[0] ?? {});
217
+ const delta = firstChoice.delta;
218
+ const text = typeof delta?.content === "string" ? sanitizeTransportText(delta.content) : "";
219
+ if (text.length > 0) {
220
+ streamContentSeen = true;
221
+ yield { text, done: false };
222
+ }
223
+ }
224
+ if (streamContentSeen) {
225
+ if (!doneSent) {
226
+ doneSent = true;
227
+ yield {
228
+ text: "",
229
+ done: true,
230
+ promptTokens,
231
+ completionTokens,
232
+ };
233
+ }
234
+ emitProviderEvent({ event: "success", stage: "streaming_chat" });
235
+ return;
236
+ }
237
+ emitFallback("streaming_chat", "non_streaming_chat", streamParseErrors > 0
238
+ ? "stream_parse_error_no_content"
239
+ : streamParsedFrames > 0
240
+ ? "stream_no_text_content"
241
+ : "stream_no_parseable_frames");
242
+ }
243
+ else {
244
+ const sample = await readResponseText(streamRes, `${provider}:streaming_chat:error`);
245
+ emitFallback("streaming_chat", "non_streaming_chat", "stream_http_error", {
246
+ statusCode: streamRes.status,
247
+ sample_hex: sample.sample_hex,
248
+ sample_text: sample.sample_text,
249
+ });
250
+ }
251
+ }
252
+ catch (err) {
253
+ if (err instanceof Error && /abort/i.test(err.name))
254
+ throw err;
255
+ emitFallback("streaming_chat", "non_streaming_chat", formatErrorMessage(err));
256
+ }
257
+ // Attempt 2: non-streaming chat (JSON or direct text)
258
+ try {
259
+ emitProviderEvent({ event: "attempt", stage: "non_streaming_chat" });
260
+ const chatBody = {
261
+ model: resolvedModel,
262
+ messages: messagesToSend,
263
+ stream: false,
264
+ temperature: request.temperature,
265
+ top_p: request.topP,
266
+ };
267
+ const res = await fetch(`${config.baseUrl}/chat/completions`, {
268
+ method: "POST",
269
+ signal: controller.signal,
270
+ headers: {
271
+ "Content-Type": "application/json",
272
+ Accept: "application/json, text/plain;q=0.9, */*;q=0.1",
273
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
274
+ ...(config.headers ?? {}),
275
+ },
276
+ body: JSON.stringify(chatBody),
277
+ });
278
+ const sample = await readResponseText(res, `${provider}:non_streaming_chat`);
279
+ if (res.ok) {
280
+ const extracted = extractProviderBodyText(sample.text);
281
+ if (extracted.text.length > 0) {
282
+ yield { text: extracted.text, done: false };
283
+ yield {
284
+ text: "",
285
+ done: true,
286
+ promptTokens,
287
+ completionTokens,
288
+ };
289
+ doneSent = true;
290
+ emitProviderEvent({ event: "success", stage: "non_streaming_chat" });
291
+ return;
292
+ }
293
+ emitFallback("non_streaming_chat", "completions", "empty_non_streaming_response", {
294
+ statusCode: res.status,
295
+ sample_hex: sample.sample_hex,
296
+ sample_text: sample.sample_text,
297
+ });
298
+ }
299
+ else {
300
+ emitFallback("non_streaming_chat", "completions", "non_streaming_http_error", {
301
+ statusCode: res.status,
302
+ sample_hex: sample.sample_hex,
303
+ sample_text: sample.sample_text,
304
+ });
305
+ }
306
+ }
307
+ catch (err) {
308
+ if (err instanceof Error && /abort/i.test(err.name))
309
+ throw err;
310
+ emitFallback("non_streaming_chat", "completions", formatErrorMessage(err));
311
+ }
312
+ // Attempt 3: completions endpoint with `prompt` (legacy shape, JSON or direct text)
313
+ emitProviderEvent({ event: "attempt", stage: "completions" });
314
+ const prompt = messagesToPrompt || "";
315
+ const compBody = {
316
+ model: resolvedModel,
317
+ prompt,
318
+ temperature: request.temperature,
319
+ top_p: request.topP,
320
+ };
321
+ const compRes = await fetch(`${config.baseUrl}/completions`, {
322
+ method: "POST",
323
+ signal: controller.signal,
324
+ headers: {
325
+ "Content-Type": "application/json",
326
+ Accept: "application/json",
327
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
328
+ ...(config.headers ?? {}),
329
+ },
330
+ body: JSON.stringify(compBody),
331
+ });
332
+ const compSample = await readResponseText(compRes, `${provider}:completions`);
333
+ if (!compRes.ok) {
334
+ throw new OpenAICompatibleError(`Provider '${provider}' API error: ${compRes.status} ${compRes.statusText}`, compRes.status, compSample.text);
335
+ }
336
+ const extracted = extractProviderBodyText(compSample.text);
337
+ const text = extracted.text;
338
+ if (text.length > 0)
339
+ yield { text, done: false };
340
+ yield { text: "", done: true, promptTokens, completionTokens };
341
+ emitProviderEvent({ event: "success", stage: "completions" });
342
+ return;
343
+ }
344
+ // default streaming path for other providers
49
345
  const body = {
50
346
  model: resolvedModel,
51
- messages: request.messages,
347
+ messages: sanitizeChatMessages(request.messages),
52
348
  stream: true,
53
349
  temperature: request.temperature,
54
350
  top_p: request.topP,
55
- stream_options: { include_usage: true },
56
351
  };
352
+ // llama.cpp rejects custom stream_options; only include for non-llama providers
353
+ if (provider !== 'llama.cpp') {
354
+ body.stream_options = { include_usage: true };
355
+ }
57
356
  const res = await fetch(`${config.baseUrl}/chat/completions`, {
58
357
  method: "POST",
59
358
  signal: controller.signal,
@@ -202,7 +501,8 @@ export class OpenAICompatibleClient {
202
501
  const { done, value } = await reader.read();
203
502
  if (done)
204
503
  break;
205
- buffer += decoder.decode(value, { stream: true });
504
+ captureRawBytes("openai-compatible:sse_chunk", value);
505
+ buffer += sanitizeTransportText(decoder.decode(value, { stream: true }));
206
506
  const lines = buffer.split("\n");
207
507
  buffer = lines.pop() ?? "";
208
508
  for (const line of lines) {
@@ -211,14 +511,14 @@ export class OpenAICompatibleClient {
211
511
  continue;
212
512
  if (!trimmed.startsWith("data:"))
213
513
  continue;
214
- const data = trimmed.slice(5).trim();
514
+ const data = sanitizeTransportText(trimmed.slice(5).trim());
215
515
  if (!data)
216
516
  continue;
217
517
  yield data;
218
518
  }
219
519
  }
220
520
  if (buffer.trim().startsWith("data:")) {
221
- const data = buffer.trim().slice(5).trim();
521
+ const data = sanitizeTransportText(buffer.trim().slice(5).trim());
222
522
  if (data)
223
523
  yield data;
224
524
  }
@@ -57,6 +57,7 @@ export declare function buildOpenAiCompatibleBaseUrl(baseUrl: string): string;
57
57
  export declare function inferProviderFromModel(model: string | undefined): string | undefined;
58
58
  export declare function isLocalLlmProvider(providerInput: string | undefined): providerInput is LocalLlmProvider;
59
59
  export declare function defaultModelForProvider(providerInput: string | undefined): string;
60
+ export declare function normalizeLlamaCppHfModelName(modelInput: string | undefined): string | undefined;
60
61
  export declare function providerEnvPrefix(providerInput: string | undefined): string;
61
62
  export declare function buildProviderDoctorCommands(providerInput: string | undefined, modelInput: string | undefined, baseUrlInput?: string): string[];
62
63
  export declare function parseJsoncLoose(raw: string): unknown;
@@ -114,6 +114,22 @@ export function defaultModelForProvider(providerInput) {
114
114
  return DEFAULT_LLAMA_CPP_MODEL;
115
115
  return DEFAULT_HOSTED_MODELS[provider] ?? DEFAULT_HOSTED_MODELS.codex;
116
116
  }
117
+ export function normalizeLlamaCppHfModelName(modelInput) {
118
+ const value = modelInput?.trim();
119
+ if (!value)
120
+ return undefined;
121
+ if (value.endsWith(".gguf"))
122
+ return value;
123
+ const normalized = value.replace(/^(?:https?:\/\/)?(?:hf\.co|huggingface\.co)\//i, "");
124
+ const match = normalized.match(/^([^/]+)\/([^:]+?)(?::([^:]+))?$/);
125
+ if (!match)
126
+ return value;
127
+ const repoName = match[2].replace(/\.gguf$/i, "").replace(/-GGUF$/i, "");
128
+ const quant = match[3]?.trim();
129
+ if (!quant)
130
+ return value;
131
+ return `${repoName}-${quant}.gguf`;
132
+ }
117
133
  export function providerEnvPrefix(providerInput) {
118
134
  const provider = normalizeProvider(providerInput);
119
135
  if (!provider)
@@ -124,46 +140,51 @@ export function providerEnvPrefix(providerInput) {
124
140
  }
125
141
  export function buildProviderDoctorCommands(providerInput, modelInput, baseUrlInput) {
126
142
  const provider = normalizeProvider(providerInput) ?? "ollama";
127
- const model = modelInput?.trim() || defaultModelForProvider(provider);
143
+ const model = modelInput?.trim();
128
144
  const baseUrl = normalizeLocalBaseUrl(baseUrlInput);
129
145
  if (provider === "ollama") {
146
+ const resolvedModel = model || defaultModelForProvider(provider);
130
147
  return [
131
148
  "ollama serve",
132
- `ollama pull ${model}`,
149
+ `ollama pull ${resolvedModel}`,
133
150
  ...(baseUrl ? [`curl -s ${baseUrl}/api/tags`] : []),
134
151
  baseUrl
135
- ? `ace doctor --llm ${provider} --model ${model} --base-url ${baseUrl}`
136
- : `ace doctor --llm ${provider} --model ${model} --scan`,
152
+ ? `ace doctor --llm ${provider} --model ${resolvedModel} --base-url ${baseUrl}`
153
+ : `ace doctor --llm ${provider} --model ${resolvedModel} --scan`,
137
154
  ];
138
155
  }
139
156
  if (provider === "llama.cpp") {
157
+ const resolvedModel = model || "<hf-model-or-gguf>";
140
158
  return [
141
159
  "# Start llama-server separately, for example:",
142
- "# llama-server -m /path/to/model.gguf --port 8080",
160
+ `# llama-server -hf ${resolvedModel} --port 8080`,
143
161
  ...(baseUrl ? [`curl -s ${buildOpenAiCompatibleBaseUrl(baseUrl)}/models`] : []),
144
162
  baseUrl
145
- ? `ace doctor --llm ${provider} --model ${model} --base-url ${baseUrl}`
146
- : `ace doctor --llm ${provider} --model ${model} --scan`,
163
+ ? `ace doctor --llm ${provider} --model ${resolvedModel} --base-url ${baseUrl}`
164
+ : `ace doctor --llm ${provider} --model ${resolvedModel} --scan`,
147
165
  ];
148
166
  }
149
167
  if (provider === "codex") {
168
+ const resolvedModel = model || defaultModelForProvider(provider);
150
169
  return [
151
170
  "export OPENAI_API_KEY=<token>",
152
171
  ...(baseUrl ? [`export OPENAI_BASE_URL=${baseUrl}`] : []),
153
- `ace doctor --llm ${provider} --model ${model}${baseUrl ? ` --base-url ${baseUrl}` : ""}`,
172
+ `ace doctor --llm ${provider} --model ${resolvedModel}${baseUrl ? ` --base-url ${baseUrl}` : ""}`,
154
173
  ];
155
174
  }
156
175
  if (provider === "copilot") {
176
+ const resolvedModel = model || defaultModelForProvider(provider);
157
177
  return [
158
178
  "gh auth login # or export GITHUB_TOKEN=<token>",
159
- `ace doctor --llm ${provider} --model ${model}`,
179
+ `ace doctor --llm ${provider} --model ${resolvedModel}`,
160
180
  ];
161
181
  }
162
182
  const prefix = providerEnvPrefix(provider);
183
+ const resolvedModel = model || defaultModelForProvider(provider);
163
184
  return [
164
185
  `export ${prefix}_BASE_URL=${baseUrl ?? "<openai-compatible-base-url>"}`,
165
186
  `export ${prefix}_API_KEY=<token>`,
166
- `ace doctor --llm ${provider} --model ${model}${baseUrl ? ` --base-url ${baseUrl}` : ""}`,
187
+ `ace doctor --llm ${provider} --model ${resolvedModel}${baseUrl ? ` --base-url ${baseUrl}` : ""}`,
167
188
  ];
168
189
  }
169
190
  function looksLikeModel(value) {
@@ -458,7 +479,10 @@ export function discoverProviderContext(options) {
458
479
  }
459
480
  }
460
481
  if (!model) {
461
- model = defaultModelForProvider(provider);
482
+ model = provider === "llama.cpp" ? "" : defaultModelForProvider(provider);
483
+ if (provider === "llama.cpp") {
484
+ notes.push("runtime_default=llama.cpp (model not set; run ace connect or ace doctor --scan)");
485
+ }
462
486
  }
463
487
  addModel(modelsByProvider, provider, model);
464
488
  const allProviders = sortProviders([
@@ -5,7 +5,7 @@ export declare const VERICIFY_PROCESS_POST_LOG_SCHEMA_NAME = "vericify-process-p
5
5
  export declare const VERICIFY_BRIDGE_SNAPSHOT_REL_PATH = "agent-state/vericify/ace-bridge.json";
6
6
  export declare const VERICIFY_BRIDGE_SNAPSHOT_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/VERICIFY_BRIDGE_SNAPSHOT.schema.json";
7
7
  export declare const VERICIFY_BRIDGE_SNAPSHOT_SCHEMA_NAME = "vericify-bridge-snapshot@1.0.0";
8
- type VericifyProcessPostKind = "intent" | "progress" | "blocker" | "handoff_note" | "stale_ack" | "completion";
8
+ type VericifyProcessPostKind = "intent" | "progress" | "blocker" | "handoff_note" | "stale_ack" | "completion" | "plan_proposal" | "plan_quality_assessment";
9
9
  export interface VericifyProcessPost {
10
10
  process_post_id: string;
11
11
  run_id: string;
@@ -71,6 +71,11 @@ export interface VericifyBridgeSnapshot {
71
71
  process_posts: VericifyBridgeSourceState;
72
72
  };
73
73
  active_run_refs: VericifyBridgeActiveRunRef[];
74
+ ace_runtime_enrichment?: {
75
+ live_session_id?: string;
76
+ last_turn_outcome?: string;
77
+ last_turn_outcome_reason?: string;
78
+ };
74
79
  }
75
80
  export interface VericifyBridgeSnapshotResult {
76
81
  ok: boolean;
@@ -9,7 +9,7 @@ import { ProjectionManager } from "./store/materializers/projection-manager.js";
9
9
  import { VericifyRepository } from "./store/repositories/vericify-repository.js";
10
10
  import { getWorkspaceStorePath, storeExistsSync } from "./store/store-snapshot.js";
11
11
  import { isOperationalArtifactPath, operationalArtifactVirtualPath } from "./store/store-artifacts.js";
12
- import { withStoreWriteQueue } from "./store/write-queue.js";
12
+ import { withStoreWriteCoordinator } from "./store/write-coordinator.js";
13
13
  export const VERICIFY_PROCESS_POST_LOG_REL_PATH = "agent-state/vericify/process-posts.json";
14
14
  export const VERICIFY_PROCESS_POST_LOG_SCHEMA_REL_PATH = "agent-state/MODULES/schemas/VERICIFY_PROCESS_POST_LOG.schema.json";
15
15
  export const VERICIFY_PROCESS_POST_LOG_SCHEMA_NAME = "vericify-process-post-log@1.0.0";
@@ -363,7 +363,7 @@ export async function appendVericifyProcessPost(input) {
363
363
  const path = await withFileLock(VERICIFY_PROCESS_POST_LOG_REL_PATH, async () => {
364
364
  const root = workspaceRoot();
365
365
  const storePath = getWorkspaceStorePath(root);
366
- return withStoreWriteQueue(storePath, async () => {
366
+ return withStoreWriteCoordinator(storePath, async () => {
367
367
  const store = await openStore(storePath);
368
368
  try {
369
369
  const repo = new VericifyRepository(store);
@@ -390,7 +390,7 @@ export async function appendVericifyProcessPost(input) {
390
390
  await store.close();
391
391
  }
392
392
  return processPostLogPath();
393
- });
393
+ }, { operation_label: "appendVericifyProcessPost" });
394
394
  });
395
395
  return {
396
396
  path,
@@ -544,6 +544,30 @@ export function buildVericifyBridgeSnapshot() {
544
544
  }),
545
545
  },
546
546
  active_run_refs: buildActiveRunRefs(),
547
+ ace_runtime_enrichment: (() => {
548
+ const rawSessions = safeRead(runtimeExecutorPath);
549
+ if (!rawSessions)
550
+ return undefined;
551
+ try {
552
+ const parsed = JSON.parse(rawSessions);
553
+ const runningSession = Array.isArray(parsed.sessions)
554
+ ? parsed.sessions.find((s) => s.status === "running")
555
+ : undefined;
556
+ if (!runningSession)
557
+ return undefined;
558
+ const lastTurn = Array.isArray(runningSession.turns) && runningSession.turns.length > 0
559
+ ? runningSession.turns[runningSession.turns.length - 1]
560
+ : undefined;
561
+ return {
562
+ live_session_id: runningSession.session_id,
563
+ last_turn_outcome: lastTurn?.turn_outcome,
564
+ last_turn_outcome_reason: lastTurn?.outcome_reason,
565
+ };
566
+ }
567
+ catch {
568
+ return undefined;
569
+ }
570
+ })(),
547
571
  };
548
572
  }
549
573
  export function refreshVericifyBridgeSnapshot() {
@@ -1,6 +1,15 @@
1
1
  export declare const WORKSPACE_SESSION_REGISTRY_REL_PATH = "agent-state/runtime-workspaces.json";
2
2
  export type WorkspaceSessionStatus = "active" | "archived" | "removed" | "failed";
3
3
  export type WorkspaceSessionSource = "manual" | "executor" | "test";
4
+ export type WorkspaceSurfaceKind = "tui_interactive" | "unattended_runtime" | "scheduled_job" | "bridge_resumed" | "supervised_step";
5
+ export type ManagedWorkspaceTrigger = "unattended_runtime" | "supervised_step_file_mutation" | "scheduled_job_downstream_artifacts" | "multi_turn_bridge";
6
+ export interface WorkspaceTriggerContext {
7
+ surface_kind?: WorkspaceSurfaceKind;
8
+ performs_file_mutations?: boolean;
9
+ produces_artifacts_for_downstream?: boolean;
10
+ multi_turn_bridge?: boolean;
11
+ }
12
+ export declare function requiresManagedWorkspace(ctx: WorkspaceTriggerContext): boolean;
4
13
  export type WorkspaceHookKind = "after_create" | "before_run" | "after_run" | "before_remove";
5
14
  export type WorkspaceHookStatus = "not_run" | "ok" | "failed";
6
15
  export interface WorkspaceHookState {
@@ -19,12 +28,16 @@ export interface WorkspaceSessionRecord {
19
28
  root_path: string;
20
29
  status: WorkspaceSessionStatus;
21
30
  source: WorkspaceSessionSource;
31
+ trigger?: ManagedWorkspaceTrigger;
32
+ trigger_summary?: string;
22
33
  objective_id?: string;
23
34
  tracker_item_id?: string;
24
35
  created_at: string;
25
36
  updated_at: string;
26
37
  last_error?: string;
27
38
  hooks: WorkspaceHookSummary;
39
+ hook_health?: "ok" | "degraded" | "failed";
40
+ hook_summary?: string;
28
41
  }
29
42
  export interface WorkspaceSessionRegistry {
30
43
  version: 1;
@@ -58,9 +71,11 @@ export interface CreateWorkspaceSessionInput {
58
71
  workspace_path?: string;
59
72
  root?: string;
60
73
  source: WorkspaceSessionSource;
74
+ trigger_context?: WorkspaceTriggerContext;
61
75
  objective_id?: string;
62
76
  tracker_item_id?: string;
63
77
  hooks_timeout_ms?: number;
78
+ runtime_profile_path?: string;
64
79
  }
65
80
  export interface CreateWorkspaceSessionResult {
66
81
  ok: boolean;
@@ -74,6 +89,7 @@ export interface RemoveWorkspaceSessionInput {
74
89
  workspace_path?: string;
75
90
  root?: string;
76
91
  hooks_timeout_ms?: number;
92
+ runtime_profile_path?: string;
77
93
  }
78
94
  export interface RemoveWorkspaceSessionResult {
79
95
  ok: boolean;
@@ -87,6 +103,7 @@ export interface RunWorkspaceSessionHookInput {
87
103
  workspace_path?: string;
88
104
  kind: WorkspaceHookKind;
89
105
  hooks_timeout_ms?: number;
106
+ runtime_profile_path?: string;
90
107
  }
91
108
  export interface RunWorkspaceSessionHookResult {
92
109
  ok: boolean;
@@ -96,12 +113,22 @@ export interface RunWorkspaceSessionHookResult {
96
113
  error?: string;
97
114
  }
98
115
  export declare function resolveRuntimeWorkspaceRoot(rootOverride?: string): string;
116
+ export declare function projectWorkspaceSessionRuntimeSummary(session: WorkspaceSessionRecord): {
117
+ active_workspace_path: string;
118
+ active_workspace_session_id: string;
119
+ workspace_session_status: WorkspaceSessionStatus;
120
+ workspace_hook_health?: "ok" | "degraded" | "failed";
121
+ workspace_hook_summary?: string;
122
+ managed_workspace_trigger?: ManagedWorkspaceTrigger;
123
+ managed_workspace_trigger_summary?: string;
124
+ };
99
125
  export declare function getWorkspaceSessionRegistryPath(): string;
100
126
  export declare function listWorkspaceSessions(): WorkspaceSessionRegistry;
101
127
  export declare function validateManagedWorkspacePath(root: string, target: string, options?: WorkspacePathValidationOptions): WorkspacePathValidationResult;
102
- export declare function runWorkspaceHook(kind: WorkspaceHookKind, session: WorkspaceSessionRecord, timeoutMs?: number): HookResult;
128
+ export declare function runWorkspaceHook(kind: WorkspaceHookKind, session: WorkspaceSessionRecord, timeoutMs?: number, runtimeProfilePath?: string): HookResult;
103
129
  export declare function createWorkspaceSession(input: CreateWorkspaceSessionInput): CreateWorkspaceSessionResult;
104
- export declare function removeWorkspaceSession(input: RemoveWorkspaceSessionInput): RemoveWorkspaceSessionResult;
105
- export declare function runWorkspaceSessionHook(input: RunWorkspaceSessionHookInput): RunWorkspaceSessionHookResult;
130
+ export declare function createWorkspaceSessionAsync(input: CreateWorkspaceSessionInput): Promise<CreateWorkspaceSessionResult>;
131
+ export declare function removeWorkspaceSession(input: RemoveWorkspaceSessionInput): Promise<RemoveWorkspaceSessionResult>;
132
+ export declare function runWorkspaceSessionHook(input: RunWorkspaceSessionHookInput): Promise<RunWorkspaceSessionHookResult>;
106
133
  export {};
107
134
  //# sourceMappingURL=workspace-manager.d.ts.map