@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
@@ -71,6 +71,7 @@ export const ALL_AGENTS = [
71
71
  "observability",
72
72
  "eval",
73
73
  "release",
74
+ "planner",
74
75
  ];
75
76
  export const SWARM_AGENTS = ["orchestrator", "vos", "ui", "coders"];
76
77
  export const COMPOSABLE_AGENTS = [
@@ -87,6 +88,7 @@ export const COMPOSABLE_AGENTS = [
87
88
  "observability",
88
89
  "eval",
89
90
  "release",
91
+ "planner",
90
92
  ];
91
93
  export const SWARM_SUBAGENT_MAP = {
92
94
  orchestrator: COMPOSABLE_AGENTS,
@@ -145,6 +147,7 @@ export const AGENT_FILES = {
145
147
  ],
146
148
  eval: [".agents/ACE/agent-eval/instructions.md", ".agents/ACE/agent-eval/AGENTS.md"],
147
149
  release: [".agents/ACE/agent-release/instructions.md", ".agents/ACE/agent-release/AGENTS.md"],
150
+ planner: [".agents/ACE/agent-planner/instructions.md", ".agents/ACE/agent-planner/AGENTS.md"],
148
151
  };
149
152
  export const AGENT_MANIFEST_FILES = {
150
153
  orchestrator: [
@@ -167,6 +170,7 @@ export const AGENT_MANIFEST_FILES = {
167
170
  observability: [".agents/ACE/agent-observability/AGENTS.md"],
168
171
  eval: [".agents/ACE/agent-eval/AGENTS.md"],
169
172
  release: [".agents/ACE/agent-release/AGENTS.md"],
173
+ planner: [".agents/ACE/agent-planner/AGENTS.md"],
170
174
  };
171
175
  export const TASK_FILES = {
172
176
  todo: [`${ACE_TASKS_ROOT_REL}/todo.md`],
@@ -208,6 +212,7 @@ export const DEFAULT_AGENT_INSTRUCTION_FILES = {
208
212
  observability: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-observability", "instructions.md"),
209
213
  eval: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-eval", "instructions.md"),
210
214
  release: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-release", "instructions.md"),
215
+ planner: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-planner", "instructions.md"),
211
216
  };
212
217
  export const DEFAULT_AGENT_MANIFEST_FILES = {
213
218
  orchestrator: resolve(DEFAULTS_ROOT, ".agents", "ACE", "orchestrator", "AGENTS.md"),
@@ -227,6 +232,7 @@ export const DEFAULT_AGENT_MANIFEST_FILES = {
227
232
  observability: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-observability", "AGENTS.md"),
228
233
  eval: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-eval", "AGENTS.md"),
229
234
  release: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-release", "AGENTS.md"),
235
+ planner: resolve(DEFAULTS_ROOT, ".agents", "ACE", "agent-planner", "AGENTS.md"),
230
236
  };
231
237
  export const DEFAULT_TASK_FILES = {
232
238
  todo: resolve(DEFAULTS_TASKS_ROOT, "todo.md"),
@@ -269,6 +275,7 @@ export const STORE_AGENT_FILES = {
269
275
  },
270
276
  eval: { agent: "eval", instructions: ["instructions.md", "AGENTS.md"], manifests: ["AGENTS.md"] },
271
277
  release: { agent: "release", instructions: ["instructions.md", "AGENTS.md"], manifests: ["AGENTS.md"] },
278
+ planner: { agent: "planner", instructions: ["instructions.md", "AGENTS.md"], manifests: ["AGENTS.md"] },
272
279
  };
273
280
  export const STORE_TASK_FILES = {
274
281
  todo: "knowledge/tasks/todo.md",
@@ -8,7 +8,14 @@ export declare function normalizePathForValidation(path: string): string;
8
8
  export declare function isSwarmRole(role: string): role is (typeof SWARM_AGENTS)[number];
9
9
  export declare function atomicWrite(filePath: string, content: string): string;
10
10
  export declare function fileExists(filePath: string): boolean;
11
- export declare function resolveWorkspaceWritePath(filePath: string): string;
11
+ export interface WorkspacePathError extends Error {
12
+ reason_code: "path_escape" | "missing_workspace_path";
13
+ payload: {
14
+ reason_code: "path_escape" | "missing_workspace_path";
15
+ message: string;
16
+ };
17
+ }
18
+ export declare function resolveWorkspaceWritePath(filePath: string, workspaceRoot?: string): string;
12
19
  export declare function resolveWorkspaceReadPath(filePath: string): string;
13
20
  export declare function resolveWorkspaceArtifactPath(filePath: string, mode?: "read" | "write"): string;
14
21
  export declare function toAbsoluteWorkspaceCandidates(candidates: string[]): string[];
@@ -54,18 +54,37 @@ export function fileExists(filePath) {
54
54
  const abs = isAbsolute(filePath) ? filePath : resolveWorkspaceReadPath(filePath);
55
55
  return existsSync(abs);
56
56
  }
57
- export function resolveWorkspaceWritePath(filePath) {
58
- const root = currentWorkspaceRoot();
57
+ function workspacePathError(reasonCode, message) {
58
+ const error = new Error(message);
59
+ error.reason_code = reasonCode;
60
+ error.payload = {
61
+ reason_code: reasonCode,
62
+ message,
63
+ };
64
+ return error;
65
+ }
66
+ function looksLikeWindowsAbsolutePath(filePath) {
67
+ return /^[a-zA-Z]:[\\/]/.test(filePath) || /^\\\\/.test(filePath);
68
+ }
69
+ export function resolveWorkspaceWritePath(filePath, workspaceRoot = currentWorkspaceRoot()) {
70
+ const root = resolve(workspaceRoot);
59
71
  if (dirname(root) === root) {
60
72
  throw new Error("Workspace root resolved to filesystem root. Set ACE_WORKSPACE_ROOT or run ACE from the target workspace.");
61
73
  }
62
- const abs = isAbsolute(filePath)
63
- ? filePath
64
- : resolve(root, mapAceWorkspaceRelativePath(filePath));
65
- if (!isInside(root, abs)) {
66
- throw new Error(`Path escapes workspace root: ${filePath}`);
74
+ const trimmed = filePath.trim();
75
+ if (!trimmed) {
76
+ throw workspacePathError("path_escape", "Workspace write path is empty.");
67
77
  }
68
- return abs;
78
+ if (trimmed.startsWith("~") || isAbsolute(trimmed) || looksLikeWindowsAbsolutePath(trimmed)) {
79
+ throw workspacePathError("path_escape", `Workspace write path must be relative to workspace root: ${filePath}`);
80
+ }
81
+ const normalizedPath = mapAceWorkspaceRelativePath(normalizeRelPath(trimmed));
82
+ const target = resolve(root, normalizedPath);
83
+ const rel = relative(root, target);
84
+ if (rel.startsWith("..") || isAbsolute(rel) || !isInside(root, target)) {
85
+ throw workspacePathError("path_escape", `Path escapes workspace root: ${filePath}`);
86
+ }
87
+ return target;
69
88
  }
70
89
  export function resolveWorkspaceReadPath(filePath) {
71
90
  const root = currentWorkspaceRoot();
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
2
2
  import { mkdir, rename, writeFile } from "node:fs/promises";
3
- import { dirname } from "node:path";
3
+ import { dirname, isAbsolute } from "node:path";
4
4
  import { isInside, normalizeRelPath } from "../shared.js";
5
5
  import { getWorkspaceStorePath, listStoreKeysSync, parseVirtualStorePath, readStoreBlobSync, readVirtualStorePathSync, toVirtualStorePath, } from "../store/store-snapshot.js";
6
6
  import { isOperationalArtifactPath, operationalArtifactKey, writeOperationalArtifactQueued, writeStoreBlobQueued, writeStoreBlobSync, } from "../store/store-artifacts.js";
@@ -234,7 +234,9 @@ export function safeWrite(filePath, content) {
234
234
  return storeVirtualPath;
235
235
  }
236
236
  }
237
- const abs = resolveWorkspaceWritePath(filePath);
237
+ const abs = isAbsolute(filePath) && isInside(root, filePath)
238
+ ? filePath
239
+ : resolveWorkspaceWritePath(filePath, root);
238
240
  mkdirSync(dirname(abs), { recursive: true });
239
241
  const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
240
242
  writeFileSync(tmpPath, content, "utf-8");
@@ -271,7 +273,9 @@ export async function safeWriteAsync(filePath, content) {
271
273
  return storeVirtualPath;
272
274
  }
273
275
  }
274
- const abs = resolveWorkspaceWritePath(filePath);
276
+ const abs = isAbsolute(filePath) && isInside(root, filePath)
277
+ ? filePath
278
+ : resolveWorkspaceWritePath(filePath, root);
275
279
  await mkdir(dirname(abs), { recursive: true });
276
280
  const tmpPath = `${abs}.${process.pid}.${Date.now()}.tmp`;
277
281
  await writeFile(tmpPath, content, "utf-8");
@@ -114,6 +114,30 @@ function defaultJobLockFile() {
114
114
  locks: [],
115
115
  };
116
116
  }
117
+ function coerceJobQueueFile(value) {
118
+ if (!value || typeof value !== "object" || Array.isArray(value))
119
+ return undefined;
120
+ const candidate = value;
121
+ if (candidate.version !== 1)
122
+ return undefined;
123
+ return {
124
+ version: 1,
125
+ updated_at: typeof candidate.updated_at === "string" ? candidate.updated_at : nowIso(),
126
+ jobs: Array.isArray(candidate.jobs) ? candidate.jobs : [],
127
+ };
128
+ }
129
+ function coerceJobLockTableFile(value) {
130
+ if (!value || typeof value !== "object" || Array.isArray(value))
131
+ return undefined;
132
+ const candidate = value;
133
+ if (candidate.version !== 1)
134
+ return undefined;
135
+ return {
136
+ version: 1,
137
+ updated_at: typeof candidate.updated_at === "string" ? candidate.updated_at : nowIso(),
138
+ locks: Array.isArray(candidate.locks) ? candidate.locks : [],
139
+ };
140
+ }
117
141
  function defaultLease(owner = "scheduler") {
118
142
  const now = new Date();
119
143
  const expires = new Date(now.getTime() + 30_000);
@@ -278,8 +302,9 @@ export function readJobQueue() {
278
302
  const root = resolveWorkspaceRoot();
279
303
  if (storeExistsSync(root)) {
280
304
  const stored = readStoreJsonSync(root, "state/scheduler/queue");
281
- if (stored)
282
- return stored;
305
+ const normalized = coerceJobQueueFile(stored);
306
+ if (normalized)
307
+ return normalized;
283
308
  }
284
309
  const raw = safeRead(JOB_QUEUE_REL);
285
310
  if (isReadError(raw))
@@ -290,8 +315,9 @@ export function readJobLockTable() {
290
315
  const root = resolveWorkspaceRoot();
291
316
  if (storeExistsSync(root)) {
292
317
  const stored = readStoreJsonSync(root, "state/scheduler/locks");
293
- if (stored)
294
- return stored;
318
+ const normalized = coerceJobLockTableFile(stored);
319
+ if (normalized)
320
+ return normalized;
295
321
  }
296
322
  const raw = safeRead(JOB_LOCK_TABLE_REL);
297
323
  if (isReadError(raw))
@@ -0,0 +1,16 @@
1
+ export interface JsonTextSanitizationResult {
2
+ text: string;
3
+ removed_control_bytes: number;
4
+ had_bom: boolean;
5
+ }
6
+ export declare function sanitizeJsonLikeText(raw: string): JsonTextSanitizationResult;
7
+ export declare function parseJsonLikeText(raw: string): {
8
+ ok: true;
9
+ value: unknown;
10
+ sanitized: JsonTextSanitizationResult;
11
+ } | {
12
+ ok: false;
13
+ error: string;
14
+ sanitized: JsonTextSanitizationResult;
15
+ };
16
+ //# sourceMappingURL=json-sanitizer.d.ts.map
@@ -0,0 +1,26 @@
1
+ export function sanitizeJsonLikeText(raw) {
2
+ const hadBom = raw.startsWith("\uFEFF");
3
+ let removed = 0;
4
+ const text = raw
5
+ .replace(/^\uFEFF/, "")
6
+ .replace(/\r\n?/g, "\n")
7
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, () => {
8
+ removed += 1;
9
+ return "";
10
+ });
11
+ return { text, removed_control_bytes: removed, had_bom: hadBom };
12
+ }
13
+ export function parseJsonLikeText(raw) {
14
+ const sanitized = sanitizeJsonLikeText(raw);
15
+ try {
16
+ return { ok: true, value: JSON.parse(sanitized.text), sanitized };
17
+ }
18
+ catch (error) {
19
+ return {
20
+ ok: false,
21
+ error: error instanceof Error ? error.message : String(error),
22
+ sanitized,
23
+ };
24
+ }
25
+ }
26
+ //# sourceMappingURL=json-sanitizer.js.map
@@ -0,0 +1,27 @@
1
+ import type { AceContextTier } from "./ace-context.js";
2
+ import { type RuntimeModelClass } from "./runtime-profile.js";
3
+ export interface LocalModelPolicyInput {
4
+ provider: string;
5
+ model: string;
6
+ role: string;
7
+ task?: string;
8
+ requested_tier?: "auto" | AceContextTier;
9
+ requested_model_class?: RuntimeModelClass;
10
+ }
11
+ export interface LocalModelExecutionPolicy {
12
+ model_class: RuntimeModelClass;
13
+ tier: AceContextTier;
14
+ mutation_lane: "structural_edit" | "raw_write_allowed";
15
+ structural_tools_required: boolean;
16
+ read_file_lines_max_lines: number | null;
17
+ }
18
+ export declare function resolveLocalModelClass(input: {
19
+ provider: string;
20
+ model: string;
21
+ requested_model_class?: RuntimeModelClass;
22
+ }): RuntimeModelClass;
23
+ export declare function tierForModelClass(role: string, modelClass: RuntimeModelClass, requestedTier?: "auto" | AceContextTier): AceContextTier;
24
+ export declare function defaultTurnsForPolicy(role: string, policy: LocalModelExecutionPolicy): number;
25
+ export declare function defaultToolScopeForPolicy(role: string, policy: LocalModelExecutionPolicy): string[] | undefined;
26
+ export declare function resolveLocalModelExecutionPolicy(input: LocalModelPolicyInput): LocalModelExecutionPolicy;
27
+ //# sourceMappingURL=local-model-policy.d.ts.map
@@ -0,0 +1,84 @@
1
+ import { DEFAULT_SURGICAL_READ_BUDGETS, } from "./runtime-profile.js";
2
+ const FRONTIER_MODEL_PATTERN = /\b(70b|72b|90b|405b|claude|gpt-|codex|gemini|o[1345])\b/i;
3
+ const SMALL_LOCAL_MODEL_PATTERN = /\b(1\.5b|2b|3b|4b|5b|6b|7b|8b|mini|small|phi|tiny|qwen2\.5-coder|llama-3\.2-3b)\b/i;
4
+ const LOCAL_PROVIDER_PATTERN = /\b(ollama|llama\.cpp|llamacpp|openai-compatible|local)\b/i;
5
+ const HOSTED_PROVIDER_PATTERN = /\b(codex|claude|gemini|copilot)\b/i;
6
+ function normalize(value) {
7
+ return (value ?? "").trim().toLowerCase();
8
+ }
9
+ function isMutationTask(task, role) {
10
+ if (role === "coders" || role === "builder")
11
+ return true;
12
+ return /\b(write|create|mutate|edit|patch|modify|persist|save|generate|implement)\b/i.test(task ?? "");
13
+ }
14
+ export function resolveLocalModelClass(input) {
15
+ if (input.requested_model_class)
16
+ return input.requested_model_class;
17
+ const provider = normalize(input.provider);
18
+ const model = normalize(input.model);
19
+ const joined = `${provider} ${model}`;
20
+ if (FRONTIER_MODEL_PATTERN.test(joined) || HOSTED_PROVIDER_PATTERN.test(provider) || provider === "openai") {
21
+ return "frontier";
22
+ }
23
+ if (SMALL_LOCAL_MODEL_PATTERN.test(model)) {
24
+ return "small_local";
25
+ }
26
+ if (LOCAL_PROVIDER_PATTERN.test(provider)) {
27
+ return "mid";
28
+ }
29
+ return "mid";
30
+ }
31
+ export function tierForModelClass(role, modelClass, requestedTier) {
32
+ if (requestedTier && requestedTier !== "auto")
33
+ return requestedTier;
34
+ if (role === "orchestrator")
35
+ return "compressed";
36
+ if (modelClass === "frontier")
37
+ return "full";
38
+ if (modelClass === "small_local")
39
+ return "brief";
40
+ return "compressed";
41
+ }
42
+ export function defaultTurnsForPolicy(role, policy) {
43
+ if (role === "orchestrator")
44
+ return 6;
45
+ return policy.model_class === "small_local" ? 4 : 4;
46
+ }
47
+ export function defaultToolScopeForPolicy(role, policy) {
48
+ if (!policy.structural_tools_required || !(role === "coders" || role === "builder")) {
49
+ return undefined;
50
+ }
51
+ return [
52
+ "outline_file",
53
+ "astgrep_query",
54
+ "astgrep_locate",
55
+ "read_file_lines",
56
+ "compile_structural_edit",
57
+ "preview_structural_edit",
58
+ "astgrep_rewrite",
59
+ "safe_edit_file",
60
+ "write_workspace_file",
61
+ "run_tests",
62
+ "git_diff",
63
+ "git_status",
64
+ "execute_gates",
65
+ ];
66
+ }
67
+ export function resolveLocalModelExecutionPolicy(input) {
68
+ const role = normalize(input.role);
69
+ const modelClass = resolveLocalModelClass({
70
+ provider: input.provider,
71
+ model: input.model,
72
+ requested_model_class: input.requested_model_class,
73
+ });
74
+ const mutationIntent = isMutationTask(input.task, role);
75
+ const structuralToolsRequired = modelClass === "small_local" && mutationIntent;
76
+ return {
77
+ model_class: modelClass,
78
+ tier: tierForModelClass(role, modelClass, input.requested_tier),
79
+ mutation_lane: structuralToolsRequired ? "structural_edit" : "raw_write_allowed",
80
+ structural_tools_required: structuralToolsRequired,
81
+ read_file_lines_max_lines: DEFAULT_SURGICAL_READ_BUDGETS[modelClass],
82
+ };
83
+ }
84
+ //# sourceMappingURL=local-model-policy.js.map
@@ -1,5 +1,7 @@
1
1
  import { type BridgeResult, type ModelBridgeClients } from "./model-bridge.js";
2
2
  import type { AceContextTier } from "./ace-context.js";
3
+ import { type LocalModelExecutionPolicy } from "./local-model-policy.js";
4
+ import type { RuntimeModelClass } from "./runtime-profile.js";
3
5
  export interface LocalModelRuntimeConfig {
4
6
  workspaceRoot: string;
5
7
  provider: string;
@@ -19,6 +21,7 @@ export interface RunLocalModelTaskOptions {
19
21
  ollamaUrl?: string;
20
22
  maxTurns?: number;
21
23
  tier?: AceContextTier | "auto";
24
+ modelClass?: RuntimeModelClass;
22
25
  toolScope?: string[];
23
26
  clients?: ModelBridgeClients;
24
27
  }
@@ -26,8 +29,10 @@ export interface RunLocalModelTaskResult {
26
29
  runtime: LocalModelRuntimeConfig;
27
30
  role: string;
28
31
  routingSummary?: string;
32
+ policy: LocalModelExecutionPolicy;
29
33
  result: BridgeResult;
30
34
  }
35
+ export declare function resolveTier(requested: RunLocalModelTaskOptions["tier"], provider: string, model: string, role: string): AceContextTier;
31
36
  export declare function createDefaultModelBridgeClients(runtime: Pick<LocalModelRuntimeConfig, "providerBaseUrls">): ModelBridgeClients;
32
37
  export declare function resolveLocalModelRuntime(input: {
33
38
  workspaceRoot?: string;
@@ -37,4 +42,5 @@ export declare function resolveLocalModelRuntime(input: {
37
42
  ollamaUrl?: string;
38
43
  }): LocalModelRuntimeConfig;
39
44
  export declare function runLocalModelTask(options: RunLocalModelTaskOptions): Promise<RunLocalModelTaskResult>;
45
+ export { resolveLocalModelExecutionPolicy, resolveLocalModelClass } from "./local-model-policy.js";
40
46
  //# sourceMappingURL=local-model-runtime.d.ts.map
@@ -6,6 +6,7 @@ import { ModelBridge } from "./model-bridge.js";
6
6
  import { OllamaClient } from "./tui/ollama.js";
7
7
  import { OpenAICompatibleClient, diagnoseChatRuntimeConfig, } from "./tui/openai-compatible.js";
8
8
  import { discoverProviderContext } from "./tui/provider-discovery.js";
9
+ import { defaultToolScopeForPolicy, defaultTurnsForPolicy, resolveLocalModelExecutionPolicy, } from "./local-model-policy.js";
9
10
  function extractTextContent(result) {
10
11
  if (!result || typeof result !== "object")
11
12
  return "";
@@ -50,22 +51,13 @@ async function resolveRole(task, sessionId, requestedRole) {
50
51
  routingSummary,
51
52
  };
52
53
  }
53
- function resolveTier(requested, provider, model, role) {
54
- if (requested && requested !== "auto")
55
- return requested;
56
- const normalizedProvider = provider.trim().toLowerCase();
57
- const normalizedModel = model.trim().toLowerCase();
58
- if (role === "orchestrator")
59
- return "compressed";
60
- if (normalizedProvider !== "ollama")
61
- return "full";
62
- if (/(70b|72b|90b|405b|claude|gpt-|codex|gemini|o[1345])/.test(normalizedModel)) {
63
- return "full";
64
- }
65
- if (/(7b|8b|mini|small|phi|tiny)/.test(normalizedModel)) {
66
- return "brief";
67
- }
68
- return "compressed";
54
+ export function resolveTier(requested, provider, model, role) {
55
+ return resolveLocalModelExecutionPolicy({
56
+ provider,
57
+ model,
58
+ role,
59
+ requested_tier: requested,
60
+ }).tier;
69
61
  }
70
62
  export function createDefaultModelBridgeClients(runtime) {
71
63
  const providerConfigs = {};
@@ -119,22 +111,31 @@ export async function runLocalModelTask(options) {
119
111
  });
120
112
  const bridge = new ModelBridge(options.clients ?? createDefaultModelBridgeClients(runtime));
121
113
  const { role, routingSummary } = await resolveRole(options.task, undefined, options.role);
122
- const tier = resolveTier(options.tier, runtime.provider, runtime.model, role);
114
+ const policy = resolveLocalModelExecutionPolicy({
115
+ provider: runtime.provider,
116
+ model: runtime.model,
117
+ role,
118
+ task: options.task,
119
+ requested_tier: options.tier,
120
+ requested_model_class: options.modelClass,
121
+ });
123
122
  const result = await bridge.run({
124
123
  task: options.task,
125
124
  role,
126
125
  workspace: runtime.workspaceRoot,
127
- tier,
128
- maxTurns: options.maxTurns ?? (role === "orchestrator" ? 6 : 4),
126
+ tier: policy.tier,
127
+ maxTurns: options.maxTurns ?? defaultTurnsForPolicy(role, policy),
129
128
  provider: runtime.provider,
130
129
  model: runtime.model,
131
- toolScope: options.toolScope,
130
+ toolScope: options.toolScope ?? defaultToolScopeForPolicy(role, policy),
132
131
  });
133
132
  return {
134
133
  runtime,
135
134
  role,
136
135
  routingSummary,
136
+ policy,
137
137
  result,
138
138
  };
139
139
  }
140
+ export { resolveLocalModelExecutionPolicy, resolveLocalModelClass } from "./local-model-policy.js";
140
141
  //# sourceMappingURL=local-model-runtime.js.map
@@ -19,12 +19,13 @@ export interface BridgeProgressEvent {
19
19
  export interface BridgeResult {
20
20
  bridge_id: string;
21
21
  role: string;
22
- status: "completed" | "needs_input" | "failed" | "max_turns";
22
+ status: "completed" | "blocked" | "needs_input" | "failed" | "max_turns";
23
23
  summary: string;
24
24
  turns: number;
25
25
  tool_calls: BridgeToolResult[];
26
26
  child_results: BridgeResult[];
27
27
  evidence_refs?: string[];
28
+ reason_code?: string;
28
29
  }
29
30
  export interface ModelBridgeClients {
30
31
  ollama: Pick<OllamaClient, "chat" | "abort">;
@@ -40,6 +41,10 @@ export interface ModelBridgeRunOptions {
40
41
  provider: string;
41
42
  model: string;
42
43
  toolScope?: string[];
44
+ expectedArtifacts?: {
45
+ path: string;
46
+ required?: boolean;
47
+ }[];
43
48
  parentBridge?: string;
44
49
  onToolCall?: (tool: string, args: Record<string, unknown>) => void;
45
50
  onToolResult?: (tool: string, result: BridgeToolResult) => void;