@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/dist/tui/chat.js CHANGED
@@ -13,6 +13,7 @@ import { ModelBridge } from "../model-bridge.js";
13
13
  import { resolveAceStateLayout } from "../ace-state-resolver.js";
14
14
  import { applyEvidenceGuardrail, buildAcePreflightPacket, buildBridgeTaskInput, buildContinuityRecord, buildStartupNudge, mapBridgeResultToRuntimeStatus, nextActivationLedger, } from "./local-model-contract.js";
15
15
  import { withLocalModelRuntimeRepository, } from "../store/repositories/local-model-runtime-repository.js";
16
+ import { parseExecutionEngine, runLocalModelTask } from "../local-model-runtime.js";
16
17
  export class ChatSession extends EventEmitter {
17
18
  clients;
18
19
  telemetry;
@@ -32,7 +33,10 @@ export class ChatSession extends EventEmitter {
32
33
  aceRole;
33
34
  aceTier;
34
35
  maxTurns;
36
+ executionEngine;
35
37
  aceBridge;
38
+ hermesExecutor;
39
+ hermesLaunchProfile;
36
40
  activeAceBridge = false;
37
41
  providerBaseUrls;
38
42
  sessionId = randomUUID();
@@ -56,7 +60,10 @@ export class ChatSession extends EventEmitter {
56
60
  this.aceRole = options.aceRole?.trim() || "orchestrator";
57
61
  this.aceTier = options.aceTier;
58
62
  this.maxTurns = options.maxTurns ?? 6;
63
+ this.executionEngine = parseExecutionEngine(options.engine) ?? "direct";
59
64
  this.aceBridge = options.bridge ?? new ModelBridge(clients);
65
+ this.hermesExecutor = options.hermesExecutor;
66
+ this.hermesLaunchProfile = options.hermesLaunchProfile;
60
67
  // Add system prompt if provided
61
68
  if (this.systemPrompt) {
62
69
  this.messages.push({ role: "system", content: this.systemPrompt });
@@ -349,6 +356,9 @@ export class ChatSession extends EventEmitter {
349
356
  recommended_next_action: input.recommendedNextAction,
350
357
  blocked_reason: input.blockedReason,
351
358
  updated_at: Date.now(),
359
+ execution_engine: this.executionEngine,
360
+ underlying_provider: this.provider,
361
+ underlying_model: this.model,
352
362
  };
353
363
  }
354
364
  setRuntimeStatus(status) {
@@ -536,6 +546,70 @@ export class ChatSession extends EventEmitter {
536
546
  this.activationLedger = activationLedger;
537
547
  return;
538
548
  }
549
+ if (this.executionEngine === "hermes_local") {
550
+ const delegated = await runLocalModelTask({
551
+ task: buildBridgeTaskInput(recentConversation, preflight),
552
+ role: executionRole,
553
+ workspaceRoot: this.workspaceRoot,
554
+ provider: streamProvider,
555
+ model: streamModel,
556
+ baseUrl: this.providerBaseUrls[streamProvider],
557
+ ollamaUrl: this.providerBaseUrls.ollama,
558
+ engine: "hermes_local",
559
+ maxTurns: this.maxTurns,
560
+ tier: this.aceTier ?? (executionRole === "orchestrator" ? "compressed" : "brief"),
561
+ hermesExecutor: this.hermesExecutor,
562
+ hermesLaunchProfile: this.hermesLaunchProfile,
563
+ });
564
+ const assistantText = delegated.result.summary.trim() || "Hermes-local turn completed.";
565
+ this.messages.push({ role: "assistant", content: assistantText });
566
+ this.displayMessages.push({
567
+ role: "assistant",
568
+ content: assistantText,
569
+ timestamp: Date.now(),
570
+ tokens: estimateTokenCount(assistantText),
571
+ });
572
+ runtimeStatus = {
573
+ ...runtimeStatus,
574
+ bridge_status: delegated.result.status === "completed"
575
+ ? "done"
576
+ : delegated.result.status === "failed"
577
+ ? "failed"
578
+ : delegated.result.status === "max_turns"
579
+ ? "needs_input"
580
+ : delegated.result.status,
581
+ blocked_reason: delegated.result.status === "failed" ? assistantText : undefined,
582
+ hermes_bridge_protocol_version: delegated.hermes?.bridge_protocol_version,
583
+ hermes_session_id: delegated.hermes?.hermes_session_id,
584
+ shadow_mcp_session_id: delegated.hermes?.shadow_mcp_session_id,
585
+ updated_at: Date.now(),
586
+ };
587
+ this.setRuntimeStatus(runtimeStatus);
588
+ const continuity = {
589
+ ...buildContinuityRecord({
590
+ preflight,
591
+ role: executionRole,
592
+ bridgeStatus: runtimeStatus.bridge_status,
593
+ evidenceRefs: delegated.result.evidence_refs ?? [],
594
+ }),
595
+ execution_engine: "hermes_local",
596
+ underlying_provider: streamProvider,
597
+ underlying_model: streamModel,
598
+ hermes_bridge_protocol_version: delegated.hermes?.bridge_protocol_version,
599
+ hermes_session_id: delegated.hermes?.hermes_session_id,
600
+ shadow_mcp_session_id: delegated.hermes?.shadow_mcp_session_id,
601
+ };
602
+ this.persistRuntimeSessionArtifacts({ activationLedger, runtimeStatus, continuity });
603
+ this.activationLedger = activationLedger;
604
+ this.telemetry.recordRequest({
605
+ startTime,
606
+ endTime: Date.now(),
607
+ promptTokens: estimateTokenCount(text),
608
+ completionTokens: estimateTokenCount(assistantText),
609
+ model: streamModel,
610
+ });
611
+ return;
612
+ }
539
613
  let bridgeOutput = "";
540
614
  const toolNames = [];
541
615
  let retryAttempt = 0;
@@ -5,6 +5,7 @@
5
5
  * This is the process entry when `ace tui` is invoked.
6
6
  */
7
7
  import { type TuiController } from "./commands.js";
8
+ import type { HermesLaunchProfile } from "../hermes/launch-profile.js";
8
9
  export declare class AceTui implements TuiController {
9
10
  private input;
10
11
  private layout;
@@ -18,6 +19,7 @@ export declare class AceTui implements TuiController {
18
19
  private chatSessions;
19
20
  private activeChatSession;
20
21
  private provider;
22
+ private executionEngine;
21
23
  private model;
22
24
  private ollamaAvailable;
23
25
  private providers;
@@ -32,6 +34,7 @@ export declare class AceTui implements TuiController {
32
34
  private providerBaseUrls;
33
35
  private latestRuntimeStatus;
34
36
  private pendingCloseTabId;
37
+ private hermesLaunchProfile?;
35
38
  constructor(options?: {
36
39
  provider?: string;
37
40
  model?: string;
@@ -40,6 +43,8 @@ export declare class AceTui implements TuiController {
40
43
  baseUrl?: string;
41
44
  ollamaUrl?: string;
42
45
  providerBaseUrls?: Record<string, string>;
46
+ engine?: string;
47
+ hermesLaunchProfile?: HermesLaunchProfile;
43
48
  workspaceRoot?: string;
44
49
  systemPrompt?: string;
45
50
  });
@@ -114,6 +119,8 @@ export declare function runTui(options?: {
114
119
  baseUrl?: string;
115
120
  ollamaUrl?: string;
116
121
  providerBaseUrls?: Record<string, string>;
122
+ engine?: string;
123
+ hermesLaunchProfile?: HermesLaunchProfile;
117
124
  workspaceRoot?: string;
118
125
  }): Promise<void>;
119
126
  //# sourceMappingURL=index.d.ts.map
package/dist/tui/index.js CHANGED
@@ -16,8 +16,9 @@ import { ChatSession } from "./chat.js";
16
16
  import { OpenAICompatibleClient, diagnoseChatRuntimeConfig, } from "./openai-compatible.js";
17
17
  import { detectColorLevel, write, cursor, screen, fg, style } from "./renderer.js";
18
18
  import { ALL_AGENTS, WORKSPACE_ROOT } from "../helpers.js";
19
+ import { parseExecutionEngine } from "../local-model-runtime.js";
19
20
  import { backfillHandoffsIntoScheduler } from "../tools-handoff.js";
20
- import { DEFAULT_OLLAMA_MODEL, defaultModelForProvider, inferProviderFromModel, normalizeLocalBaseUrl, } from "./provider-discovery.js";
21
+ import { DEFAULT_OLLAMA_MODEL, defaultModelForProvider, inferProviderFromModel, normalizeLocalBaseUrl, scanLocalModelRuntimes, } from "./provider-discovery.js";
21
22
  import { resolveAceStateLayout } from "../ace-state-resolver.js";
22
23
  import { withLocalModelRuntimeRepository, } from "../store/repositories/local-model-runtime-repository.js";
23
24
  const DASHBOARD_CONTROLS = ["provider", "model", "chat", "logs", "refresh"];
@@ -38,6 +39,7 @@ export class AceTui {
38
39
  activeChatSession = null;
39
40
  // State
40
41
  provider;
42
+ executionEngine;
41
43
  model;
42
44
  ollamaAvailable = false;
43
45
  providers = [];
@@ -52,10 +54,13 @@ export class AceTui {
52
54
  providerBaseUrls = new Map();
53
55
  latestRuntimeStatus = null;
54
56
  pendingCloseTabId = null;
57
+ hermesLaunchProfile;
55
58
  constructor(options = {}) {
56
59
  const workspaceRoot = options.workspaceRoot ?? WORKSPACE_ROOT;
57
60
  this.workspaceRoot = workspaceRoot;
61
+ this.hermesLaunchProfile = options.hermesLaunchProfile;
58
62
  this.provider = this.normalizeProvider(options.provider ?? inferProviderFromModel(options.model) ?? "ollama") ?? "ollama";
63
+ this.executionEngine = parseExecutionEngine(options.engine) ?? "direct";
59
64
  const initialModel = options.model ??
60
65
  (this.provider === "ollama"
61
66
  ? defaultModelForProvider(this.provider)
@@ -92,6 +97,7 @@ export class AceTui {
92
97
  this.config.set("ollama_url", this.providerBaseUrls.get("ollama"));
93
98
  }
94
99
  this.config.set("provider", this.provider);
100
+ this.config.set("engine", this.executionEngine);
95
101
  this.config.set("model", this.model);
96
102
  this.config.set("temperature", "0.7");
97
103
  this.config.set("top_p", "0.9");
@@ -148,6 +154,31 @@ export class AceTui {
148
154
  this.showMessage(`Provider '${this.provider}' loaded from session/config. Switch with /provider or dashboard controls.`, "info");
149
155
  this.checkProviderRuntimeConfig(this.provider, this.model, "startup");
150
156
  }
157
+ // Additionally probe local runtimes (llama.cpp / Ollama) and merge discoveries into TUI so users can explicitly pick either.
158
+ try {
159
+ const scan = await scanLocalModelRuntimes({ workspaceRoot: this.workspaceRoot, timeoutMs: 800 });
160
+ for (const c of scan.candidates) {
161
+ this.ensureProvider(c.provider);
162
+ this.setProviderBaseUrl(c.provider, c.baseUrl);
163
+ if (c.models && c.models.length > 0)
164
+ this.setProviderModels(c.provider, c.models, false);
165
+ }
166
+ // If the current model appears served by a discovered runtime and the provider wasn't explicitly forced, prefer the local runtime.
167
+ if (!process.env.ACE_TUI_PROVIDER) {
168
+ for (const c of scan.candidates) {
169
+ if (c.models.includes(this.model) && this.provider !== c.provider) {
170
+ this.provider = c.provider;
171
+ this.config.set("provider", this.provider);
172
+ this.telemetry.setModel(this.model);
173
+ this.showMessage(`Discovered local runtime '${c.provider}' serving model '${this.model}'. Provider set to '${this.provider}'.`, "info");
174
+ break;
175
+ }
176
+ }
177
+ }
178
+ }
179
+ catch {
180
+ // Ignore scan failures — discovery is best-effort.
181
+ }
151
182
  // Initial render
152
183
  this.fullRender();
153
184
  // Start 1-second tick for status bar updates
@@ -498,6 +529,7 @@ export class AceTui {
498
529
  const state = {
499
530
  taskState: this.getTaskState(),
500
531
  provider: this.provider,
532
+ engine: this.executionEngine,
501
533
  model: this.model,
502
534
  tokensIn: snap.tokensIn + agentTokens.input,
503
535
  tokensOut: snap.tokensOut + agentTokens.output,
@@ -928,6 +960,7 @@ export class AceTui {
928
960
  // Create a chat session for this tab
929
961
  const session = new ChatSession({ ollama: this.ollama, openai: this.openai }, this.telemetry, {
930
962
  provider: this.provider,
963
+ engine: this.executionEngine,
931
964
  model: this.model,
932
965
  providerBaseUrls: Object.fromEntries(this.providerBaseUrls.entries()),
933
966
  systemPrompt: this.config.get("system_prompt"),
@@ -935,6 +968,7 @@ export class AceTui {
935
968
  topP: parseFloat(this.config.get("top_p") ?? "0.9"),
936
969
  numCtx: parseInt(this.config.get("num_ctx") ?? "8192", 10),
937
970
  workspaceRoot: this.workspaceRoot,
971
+ hermesLaunchProfile: this.hermesLaunchProfile,
938
972
  });
939
973
  // Wire chat session events
940
974
  session.on("updated", () => {
@@ -20,6 +20,7 @@ export interface LayoutZones {
20
20
  export interface StatusBarState {
21
21
  taskState: "idle" | "running" | "blocked" | "overdue";
22
22
  provider: string;
23
+ engine?: string;
23
24
  model: string;
24
25
  tokensIn: number;
25
26
  tokensOut: number;
@@ -160,6 +160,9 @@ export class LayoutManager {
160
160
  ? `${fg.gray}tab: ${fg.white}${state.activeTabLabel}${style.reset}`
161
161
  : undefined;
162
162
  const providerSeg = `${fg.cyan}provider: ${fg.brightCyan}${state.provider}${style.reset}`;
163
+ const engineSeg = state.engine
164
+ ? `${fg.cyan}engine: ${fg.brightCyan}${state.engine}${style.reset}`
165
+ : undefined;
163
166
  const modelSeg = `${fg.cyan}model: ${fg.brightCyan}${state.model}${style.reset}`;
164
167
  const runtimeSummary = formatRuntimeStatusSummary(state);
165
168
  const runtimeSeg = runtimeSummary
@@ -175,7 +178,7 @@ export class LayoutManager {
175
178
  : state.bridgeStatus
176
179
  ? `${fg.gray}${state.bridgeStatus}${style.reset}`
177
180
  : undefined;
178
- const segments = [stateIndicator, tabSeg, providerSeg, modelSeg, runtimeSeg, contextSeg, tokenSeg, timeSeg, clockSeg]
181
+ const segments = [stateIndicator, tabSeg, providerSeg, engineSeg, modelSeg, runtimeSeg, contextSeg, tokenSeg, timeSeg, clockSeg]
179
182
  .filter((segment) => Boolean(segment));
180
183
  const sep = ` ${fg.gray}│${style.reset} `;
181
184
  const line = ` ${segments.join(sep)} `;
@@ -72,22 +72,19 @@ export function inferProviderFromModel(model) {
72
72
  const value = model.trim().toLowerCase();
73
73
  if (!value)
74
74
  return undefined;
75
- if (value.startsWith("llama.cpp/") || value.startsWith("llamacpp/")) {
75
+ // explicit prefixes
76
+ if (value.startsWith("llama.cpp/") || value.startsWith("llamacpp/"))
76
77
  return "llama.cpp";
77
- }
78
- if (value.startsWith("ollama/") ||
79
- value.includes("llama") ||
80
- value.includes("qwen") ||
81
- value.includes("mistral") ||
82
- value.includes("deepseek") ||
83
- value.includes("phi") ||
84
- value.includes(":") ||
85
- value.includes("mixtral")) {
78
+ if (value.startsWith("ollama/"))
86
79
  return "ollama";
87
- }
88
- if (value.startsWith("copilot/")) {
80
+ if (value.startsWith("copilot/"))
89
81
  return "copilot";
90
- }
82
+ // file-like or gguf artifacts -> prefer llama.cpp
83
+ if (value.endsWith(".gguf") || value.includes(".gguf"))
84
+ return "llama.cpp";
85
+ if (value.endsWith(".bin"))
86
+ return "llama.cpp";
87
+ // common hosted provider hints
91
88
  if (value.includes("claude"))
92
89
  return "claude";
93
90
  if (value.includes("gemini"))
@@ -100,6 +97,11 @@ export function inferProviderFromModel(model) {
100
97
  value.startsWith("o5")) {
101
98
  return "codex";
102
99
  }
100
+ // model families often served via Ollama
101
+ if (value.includes("qwen") || value.includes("mistral") || value.includes("mixtral") || value.includes("deepseek") || value.includes("phi")) {
102
+ return "ollama";
103
+ }
104
+ // Ambiguous cases (e.g., hf.co/owner/name:ref) should not default to Ollama; allow runtime discovery to decide.
103
105
  return undefined;
104
106
  }
105
107
  export function isLocalLlmProvider(providerInput) {
@@ -14,6 +14,7 @@ export interface VericifyProcessPost {
14
14
  agent_id: string;
15
15
  kind: VericifyProcessPostKind;
16
16
  summary: string;
17
+ blocker_category?: string;
17
18
  tool_refs: string[];
18
19
  evidence_refs: string[];
19
20
  checkpoint_ref?: string;
@@ -109,6 +110,7 @@ export declare function appendVericifyProcessPost(input: {
109
110
  agent_id: string;
110
111
  kind: VericifyProcessPostKind;
111
112
  summary: string;
113
+ blocker_category?: string;
112
114
  tool_refs?: string[];
113
115
  evidence_refs?: string[];
114
116
  checkpoint_ref?: string;
@@ -124,6 +126,7 @@ export declare function appendVericifyProcessPostSafe(input: {
124
126
  agent_id: string;
125
127
  kind: VericifyProcessPostKind;
126
128
  summary: string;
129
+ blocker_category?: string;
127
130
  tool_refs?: string[];
128
131
  evidence_refs?: string[];
129
132
  checkpoint_ref?: string;
@@ -50,6 +50,7 @@ function normalizePost(input) {
50
50
  agent_id: input.agent_id.trim(),
51
51
  kind: input.kind,
52
52
  summary: input.summary.trim(),
53
+ blocker_category: input.blocker_category?.trim() || undefined,
53
54
  tool_refs: [...new Set(input.tool_refs.map((entry) => entry.trim()).filter(Boolean))],
54
55
  evidence_refs: [
55
56
  ...new Set(input.evidence_refs.map((entry) => entry.trim()).filter(Boolean)),
@@ -354,6 +355,7 @@ export async function appendVericifyProcessPost(input) {
354
355
  agent_id: agentId,
355
356
  kind: input.kind,
356
357
  summary,
358
+ blocker_category: input.blocker_category,
357
359
  tool_refs: input.tool_refs ?? [],
358
360
  evidence_refs: input.evidence_refs ?? [],
359
361
  checkpoint_ref: input.checkpoint_ref,
@@ -375,6 +377,7 @@ export async function appendVericifyProcessPost(input) {
375
377
  metadata: {
376
378
  branch_id: post.branch_id,
377
379
  lane_id: post.lane_id,
380
+ blocker_category: post.blocker_category,
378
381
  checkpoint_ref: post.checkpoint_ref,
379
382
  tool_refs: post.tool_refs,
380
383
  evidence_refs: post.evidence_refs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voybio/ace-swarm",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "ACE Framework MCP server and CLI — single-file ACEPACK state, local-model serving, agent orchestration, and host compliance enforcement.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,6 +21,7 @@
21
21
  "files": [
22
22
  "dist/**/*.js",
23
23
  "dist/**/*.d.ts",
24
+ "scripts/hermes_bridge_worker.py",
24
25
  "assets",
25
26
  "README.md",
26
27
  "CHANGELOG.md"
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env python3
2
+ """ACE-owned Hermes bridge worker.
3
+
4
+ The worker reads one JSON request from stdin and writes newline-delimited
5
+ bridge events to stdout. All Hermes stdout chatter is redirected to stderr so
6
+ ACE never has to guess whether an unframed line is assistant text or authority.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import contextlib
12
+ import json
13
+ import os
14
+ import sys
15
+ import traceback
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+
20
+ ORIGINAL_STDOUT = sys.stdout
21
+
22
+
23
+ def emit(event: dict[str, Any]) -> None:
24
+ ORIGINAL_STDOUT.write(json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n")
25
+ ORIGINAL_STDOUT.flush()
26
+
27
+
28
+ def load_request() -> dict[str, Any]:
29
+ raw = sys.stdin.read()
30
+ if not raw.strip():
31
+ raise ValueError("empty bridge request")
32
+ parsed = json.loads(raw)
33
+ if not isinstance(parsed, dict):
34
+ raise ValueError("bridge request must be a JSON object")
35
+ return parsed
36
+
37
+
38
+ def provider_for_hermes(provider: str) -> str:
39
+ if provider in {"llama.cpp", "ollama"}:
40
+ return "openai"
41
+ return provider
42
+
43
+
44
+ def base_url_for_hermes(provider: str, base_url: Any) -> str:
45
+ raw = str(base_url or "").rstrip("/")
46
+ if provider == "ollama" and raw and not raw.endswith("/v1"):
47
+ return f"{raw}/v1"
48
+ return raw
49
+
50
+
51
+ def main() -> int:
52
+ try:
53
+ request = load_request()
54
+ hermes_root = Path(request["hermes_root"]).resolve()
55
+ sys.path.insert(0, str(hermes_root))
56
+
57
+ session_id = str(request["session_id"])
58
+ turn_id = str(request.get("turn_id") or session_id)
59
+ emit({"type": "session_open", "session_id": session_id, "turn_id": turn_id})
60
+
61
+ with contextlib.redirect_stdout(sys.stderr):
62
+ from run_agent import AIAgent # type: ignore
63
+
64
+ def status_callback(kind: str, message: str) -> None:
65
+ emit({"type": "status" if kind == "lifecycle" else "status", "session_id": session_id, "turn_id": turn_id, "kind": kind, "message": str(message)})
66
+
67
+ def delta_callback(text: str) -> None:
68
+ emit({"type": "delta", "session_id": session_id, "turn_id": turn_id, "text": str(text)})
69
+
70
+ def reasoning_callback(text: str) -> None:
71
+ emit({"type": "reasoning", "session_id": session_id, "turn_id": turn_id, "text": str(text)})
72
+
73
+ def interim_callback(text: str, **kwargs: Any) -> None:
74
+ emit({"type": "assistant_interim", "session_id": session_id, "turn_id": turn_id, "text": str(text), **kwargs})
75
+
76
+ def thinking_callback(text: str) -> None:
77
+ emit({"type": "reasoning", "session_id": session_id, "turn_id": turn_id, "text": str(text)})
78
+
79
+ def tool_start_callback(tool_name: str, *args: Any, **kwargs: Any) -> None:
80
+ emit({"type": "tool_start", "session_id": session_id, "turn_id": turn_id, "tool": str(tool_name), "input": kwargs.get("args") or (args[0] if args else {})})
81
+
82
+ def tool_progress_callback(tool_name: str, message: str = "", *args: Any, **kwargs: Any) -> None:
83
+ emit({"type": "tool_progress", "session_id": session_id, "turn_id": turn_id, "tool": str(tool_name), "message": str(message), **kwargs})
84
+
85
+ def tool_complete_callback(tool_name: str, result: Any = None, *args: Any, **kwargs: Any) -> None:
86
+ emit({"type": "tool_complete", "session_id": session_id, "turn_id": turn_id, "tool": str(tool_name), "result": result, **kwargs})
87
+
88
+ agent = AIAgent(
89
+ base_url=base_url_for_hermes(str(request.get("provider") or ""), request.get("base_url")),
90
+ api_key=str(request.get("api_key") or "no-key-required"),
91
+ provider=provider_for_hermes(str(request.get("provider") or "")),
92
+ model=str(request.get("model") or ""),
93
+ max_iterations=int(request.get("max_turns") or 6),
94
+ enabled_toolsets=["mcp-ace_shadow"],
95
+ disabled_toolsets=["terminal", "filesystem", "web", "vision", "creative", "reasoning"],
96
+ quiet_mode=True,
97
+ verbose_logging=False,
98
+ ephemeral_system_prompt=str(request.get("system_prompt") or ""),
99
+ session_id=session_id,
100
+ platform="ace",
101
+ skip_context_files=True,
102
+ skip_memory=True,
103
+ status_callback=status_callback,
104
+ stream_delta_callback=delta_callback,
105
+ reasoning_callback=reasoning_callback,
106
+ thinking_callback=thinking_callback,
107
+ interim_assistant_callback=interim_callback,
108
+ tool_start_callback=tool_start_callback,
109
+ tool_progress_callback=tool_progress_callback,
110
+ tool_complete_callback=tool_complete_callback,
111
+ )
112
+ emit({"type": "turn_run", "session_id": session_id, "turn_id": turn_id})
113
+ result = agent.run_conversation(str(request.get("task") or ""), task_id=turn_id)
114
+
115
+ final = result.get("final_response") if isinstance(result, dict) else str(result)
116
+ emit({
117
+ "type": "final",
118
+ "session_id": session_id,
119
+ "turn_id": turn_id,
120
+ "text": final or "",
121
+ "completed": bool(result.get("completed", True)) if isinstance(result, dict) else True,
122
+ "turns": int(result.get("api_calls", 1) or 1) if isinstance(result, dict) else 1,
123
+ })
124
+ emit({"type": "session_close", "session_id": session_id, "turn_id": turn_id})
125
+ return 0
126
+ except Exception as exc:
127
+ emit({
128
+ "type": "error",
129
+ "message": str(exc),
130
+ "traceback": traceback.format_exc(limit=8),
131
+ })
132
+ return 1
133
+
134
+
135
+ if __name__ == "__main__":
136
+ raise SystemExit(main())