@actant/core 0.2.0 → 0.2.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/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ parseSkillMdContent
3
+ } from "./chunk-DYGZP4MW.js";
4
+
1
5
  // src/builder/cursor-builder.ts
2
6
  import { writeFile, mkdir, stat } from "fs/promises";
3
7
  import { join } from "path";
@@ -332,9 +336,9 @@ import { createLogger as createLogger3 } from "@actant/shared";
332
336
  // src/builder/handlers/skills-handler.ts
333
337
  var skillsHandler = {
334
338
  contextKey: "skills",
335
- resolve(refs, manager) {
339
+ resolve(refs, manager2) {
336
340
  if (!refs || !Array.isArray(refs) || refs.length === 0) return [];
337
- return manager?.resolve(refs) ?? refs.map((name) => ({ name, content: `- ${name}` }));
341
+ return manager2?.resolve(refs) ?? refs.map((name) => ({ name, content: `- ${name}` }));
338
342
  },
339
343
  async materialize(workspaceDir, definitions, _backendType, builder) {
340
344
  await builder.materializeSkills(workspaceDir, definitions);
@@ -344,9 +348,9 @@ var skillsHandler = {
344
348
  // src/builder/handlers/prompts-handler.ts
345
349
  var promptsHandler = {
346
350
  contextKey: "prompts",
347
- resolve(refs, manager) {
351
+ resolve(refs, manager2) {
348
352
  if (!refs || !Array.isArray(refs) || refs.length === 0) return [];
349
- return manager?.resolve(refs) ?? refs.map((name) => ({ name, content: `- ${name}` }));
353
+ return manager2?.resolve(refs) ?? refs.map((name) => ({ name, content: `- ${name}` }));
350
354
  },
351
355
  async materialize(workspaceDir, definitions, _backendType, builder) {
352
356
  await builder.materializePrompts(workspaceDir, definitions);
@@ -359,7 +363,7 @@ function isMcpServerRef(obj) {
359
363
  }
360
364
  var mcpServersHandler = {
361
365
  contextKey: "mcpServers",
362
- resolve(refs, manager) {
366
+ resolve(refs, manager2) {
363
367
  if (!refs || !Array.isArray(refs) || refs.length === 0) return [];
364
368
  const arr = refs;
365
369
  if (arr.every(isMcpServerRef)) {
@@ -370,7 +374,7 @@ var mcpServersHandler = {
370
374
  env: ref.env
371
375
  }));
372
376
  }
373
- return manager?.resolve(arr) ?? [];
377
+ return manager2?.resolve(arr) ?? [];
374
378
  },
375
379
  async materialize(workspaceDir, definitions, _backendType, builder) {
376
380
  await builder.materializeMcpConfig(workspaceDir, definitions);
@@ -380,11 +384,11 @@ var mcpServersHandler = {
380
384
  // src/builder/handlers/workflow-handler.ts
381
385
  var workflowHandler = {
382
386
  contextKey: "workflow",
383
- resolve(refs, manager) {
387
+ resolve(refs, manager2) {
384
388
  if (refs === void 0 || refs === null) return [];
385
389
  const name = typeof refs === "string" ? refs : Array.isArray(refs) && typeof refs[0] === "string" ? refs[0] : void 0;
386
390
  if (!name) return [];
387
- const resolved = manager?.resolve([name]);
391
+ const resolved = manager2?.resolve([name]);
388
392
  if (resolved && resolved.length > 0) return resolved;
389
393
  return [
390
394
  {
@@ -407,9 +411,9 @@ var workflowHandler = {
407
411
  // src/builder/handlers/plugins-handler.ts
408
412
  var pluginsHandler = {
409
413
  contextKey: "plugins",
410
- resolve(refs, manager) {
414
+ resolve(refs, manager2) {
411
415
  if (!refs || !Array.isArray(refs) || refs.length === 0) return [];
412
- return manager?.resolve(refs) ?? [];
416
+ return manager2?.resolve(refs) ?? [];
413
417
  },
414
418
  async materialize(workspaceDir, definitions, _backendType, builder) {
415
419
  await builder.materializePlugins(workspaceDir, definitions);
@@ -473,8 +477,8 @@ var WorkspaceBuilder = class {
473
477
  const refs = domainContext[handler.contextKey];
474
478
  if (refs === void 0 || refs === null) continue;
475
479
  if (Array.isArray(refs) && refs.length === 0) continue;
476
- const manager = this.getManager(handler.contextKey);
477
- const definitions = handler.resolve(refs, manager);
480
+ const manager2 = this.getManager(handler.contextKey);
481
+ const definitions = handler.resolve(refs, manager2);
478
482
  if (definitions.length > 0) {
479
483
  await handler.materialize(workspaceDir, definitions, backendType, activeBuilder);
480
484
  if (handler.contextKey === "mcpServers") {
@@ -1064,12 +1068,26 @@ var AgentBackendSchema = z2.object({
1064
1068
  type: z2.enum(["cursor", "cursor-agent", "claude-code", "custom", "pi"]),
1065
1069
  config: z2.record(z2.string(), z2.unknown()).optional()
1066
1070
  });
1071
+ var ModelApiProtocolEnum = z2.enum(["openai", "anthropic", "custom"]);
1072
+ var DEFAULT_PROTOCOL = {
1073
+ anthropic: "anthropic",
1074
+ openai: "openai",
1075
+ deepseek: "openai",
1076
+ ollama: "openai",
1077
+ azure: "openai",
1078
+ bedrock: "anthropic",
1079
+ vertex: "anthropic",
1080
+ custom: "custom"
1081
+ };
1067
1082
  var ModelProviderSchema = z2.object({
1068
- type: z2.enum(["anthropic", "openai", "openai-compatible", "custom"]),
1069
- protocol: z2.enum(["http", "websocket", "grpc"]).optional().default("http"),
1083
+ type: z2.string().min(1),
1084
+ protocol: ModelApiProtocolEnum.optional(),
1070
1085
  baseUrl: z2.string().optional(),
1071
1086
  config: z2.record(z2.string(), z2.unknown()).optional()
1072
- });
1087
+ }).transform((val) => ({
1088
+ ...val,
1089
+ protocol: val.protocol ?? DEFAULT_PROTOCOL[val.type] ?? "custom"
1090
+ }));
1073
1091
  var InitializerStepSchema = z2.object({
1074
1092
  type: z2.string().min(1),
1075
1093
  config: z2.record(z2.string(), z2.unknown()).optional()
@@ -1127,7 +1145,7 @@ var AgentTemplateSchema = z2.object({
1127
1145
  origin: ComponentOriginSchema.optional(),
1128
1146
  tags: z2.array(z2.string()).optional(),
1129
1147
  backend: AgentBackendSchema,
1130
- provider: ModelProviderSchema,
1148
+ provider: ModelProviderSchema.optional(),
1131
1149
  domainContext: DomainContextSchema,
1132
1150
  permissions: PermissionsInputSchema.optional(),
1133
1151
  initializer: InitializerSchema.optional(),
@@ -1255,6 +1273,58 @@ function isNodeError(err) {
1255
1273
  return err instanceof Error && "code" in err;
1256
1274
  }
1257
1275
 
1276
+ // src/provider/model-provider-registry.ts
1277
+ import { createLogger as createLogger11 } from "@actant/shared";
1278
+ var logger11 = createLogger11("model-provider-registry");
1279
+ var ModelProviderRegistry = class {
1280
+ descriptors = /* @__PURE__ */ new Map();
1281
+ defaultType;
1282
+ register(descriptor) {
1283
+ this.descriptors.set(descriptor.type, descriptor);
1284
+ logger11.debug({ type: descriptor.type }, "Provider registered");
1285
+ }
1286
+ get(type) {
1287
+ return this.descriptors.get(type);
1288
+ }
1289
+ getOrThrow(type) {
1290
+ const desc = this.descriptors.get(type);
1291
+ if (!desc) {
1292
+ throw new Error(
1293
+ `Provider "${type}" is not registered. Available providers: [${[...this.descriptors.keys()].join(", ")}].`
1294
+ );
1295
+ }
1296
+ return desc;
1297
+ }
1298
+ has(type) {
1299
+ return this.descriptors.has(type);
1300
+ }
1301
+ list() {
1302
+ return [...this.descriptors.values()];
1303
+ }
1304
+ setDefault(type) {
1305
+ if (!this.descriptors.has(type)) {
1306
+ throw new Error(
1307
+ `Cannot set default: provider "${type}" is not registered.`
1308
+ );
1309
+ }
1310
+ this.defaultType = type;
1311
+ logger11.info({ type }, "Default provider set");
1312
+ }
1313
+ getDefault() {
1314
+ if (!this.defaultType) return void 0;
1315
+ return this.descriptors.get(this.defaultType);
1316
+ }
1317
+ getDefaultType() {
1318
+ return this.defaultType;
1319
+ }
1320
+ /** @internal Test-only: clear all registrations. */
1321
+ _reset() {
1322
+ this.descriptors.clear();
1323
+ this.defaultType = void 0;
1324
+ }
1325
+ };
1326
+ var modelProviderRegistry = new ModelProviderRegistry();
1327
+
1258
1328
  // src/template/schema/config-validators.ts
1259
1329
  function zodToIssues(zodError) {
1260
1330
  return zodError.issues.map((i) => ({
@@ -1278,7 +1348,15 @@ function validateProviderConfig(data) {
1278
1348
  if (!result.success) {
1279
1349
  return { valid: false, errors: zodToIssues(result.error), warnings: [] };
1280
1350
  }
1281
- return { valid: true, data: result.data, errors: [], warnings: [] };
1351
+ const warnings = [];
1352
+ if (!modelProviderRegistry.has(result.data.type)) {
1353
+ warnings.push(warning(
1354
+ "provider.type",
1355
+ `Provider type "${result.data.type}" is not registered; it will be treated as a custom provider`,
1356
+ "UNKNOWN_PROVIDER_TYPE"
1357
+ ));
1358
+ }
1359
+ return { valid: true, data: result.data, errors: [], warnings };
1282
1360
  }
1283
1361
  function validatePermissionsConfig(data) {
1284
1362
  const result = PermissionsInputSchema.safeParse(data);
@@ -1362,12 +1440,21 @@ function validateTemplate(data) {
1362
1440
  "CUSTOM_BACKEND_NO_CONFIG"
1363
1441
  ));
1364
1442
  }
1365
- if (template.provider.type === "custom" && !template.provider.config) {
1366
- warnings.push(warning(
1367
- "provider.config",
1368
- "Custom provider type without config; model routing may fail",
1369
- "CUSTOM_PROVIDER_NO_CONFIG"
1370
- ));
1443
+ if (template.provider) {
1444
+ if (template.provider.type === "custom" && !template.provider.config) {
1445
+ warnings.push(warning(
1446
+ "provider.config",
1447
+ "Custom provider type without config; model routing may fail",
1448
+ "CUSTOM_PROVIDER_NO_CONFIG"
1449
+ ));
1450
+ }
1451
+ if (!modelProviderRegistry.has(template.provider.type)) {
1452
+ warnings.push(warning(
1453
+ "provider.type",
1454
+ `Provider type "${template.provider.type}" is not registered; it will be treated as a custom provider`,
1455
+ "UNKNOWN_PROVIDER_TYPE"
1456
+ ));
1457
+ }
1371
1458
  }
1372
1459
  return { valid: true, data: template, errors: [], warnings };
1373
1460
  }
@@ -1380,13 +1467,13 @@ import { TemplateNotFoundError, ConfigValidationError as ConfigValidationError3
1380
1467
  // src/domain/base-component-manager.ts
1381
1468
  import { readFile as readFile2, writeFile as writeFile4, readdir as readdir2, stat as stat4, unlink, mkdir as mkdir4 } from "fs/promises";
1382
1469
  import { join as join5, extname as extname2, resolve } from "path";
1383
- import { ComponentReferenceError, ConfigNotFoundError as ConfigNotFoundError2, ConfigValidationError as ConfigValidationError2, createLogger as createLogger11 } from "@actant/shared";
1470
+ import { ComponentReferenceError, ConfigNotFoundError as ConfigNotFoundError2, ConfigValidationError as ConfigValidationError2, createLogger as createLogger12 } from "@actant/shared";
1384
1471
  var BaseComponentManager = class {
1385
1472
  components = /* @__PURE__ */ new Map();
1386
1473
  logger;
1387
1474
  persistDir;
1388
1475
  constructor(loggerName) {
1389
- this.logger = createLogger11(loggerName);
1476
+ this.logger = createLogger12(loggerName);
1390
1477
  }
1391
1478
  setPersistDir(dir) {
1392
1479
  this.persistDir = dir;
@@ -1719,13 +1806,13 @@ var TemplateRegistry = class extends BaseComponentManager {
1719
1806
  import { watch } from "fs";
1720
1807
  import { join as join7 } from "path";
1721
1808
  import { access } from "fs/promises";
1722
- import { createLogger as createLogger12 } from "@actant/shared";
1723
- var logger11 = createLogger12("template-file-watcher");
1809
+ import { createLogger as createLogger13 } from "@actant/shared";
1810
+ var logger12 = createLogger13("template-file-watcher");
1724
1811
  var DEFAULT_DEBOUNCE_MS = 300;
1725
1812
  var TemplateFileWatcher = class {
1726
- constructor(templatesDir, registry3, options) {
1813
+ constructor(templatesDir, registry2, options) {
1727
1814
  this.templatesDir = templatesDir;
1728
- this.registry = registry3;
1815
+ this.registry = registry2;
1729
1816
  this.debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
1730
1817
  }
1731
1818
  watcher = null;
@@ -1742,12 +1829,12 @@ var TemplateFileWatcher = class {
1742
1829
  this.handleChange(filename);
1743
1830
  });
1744
1831
  this.watcher.on("error", (err) => {
1745
- logger11.error({ error: err }, "File watcher error");
1832
+ logger12.error({ error: err }, "File watcher error");
1746
1833
  });
1747
1834
  this.buildFileMap();
1748
- logger11.info({ dir: this.templatesDir }, "Template file watcher started");
1835
+ logger12.info({ dir: this.templatesDir }, "Template file watcher started");
1749
1836
  } catch (err) {
1750
- logger11.error({ error: err, dir: this.templatesDir }, "Failed to start file watcher");
1837
+ logger12.error({ error: err, dir: this.templatesDir }, "Failed to start file watcher");
1751
1838
  }
1752
1839
  }
1753
1840
  stop() {
@@ -1758,7 +1845,7 @@ var TemplateFileWatcher = class {
1758
1845
  clearTimeout(timer);
1759
1846
  }
1760
1847
  this.debounceTimers.clear();
1761
- logger11.info("Template file watcher stopped");
1848
+ logger12.info("Template file watcher stopped");
1762
1849
  }
1763
1850
  get isWatching() {
1764
1851
  return this.watcher !== null;
@@ -1777,7 +1864,7 @@ var TemplateFileWatcher = class {
1777
1864
  setTimeout(() => {
1778
1865
  this.debounceTimers.delete(filename);
1779
1866
  this.processChange(filename).catch((err) => {
1780
- logger11.error({ filename, error: err }, "Error processing template file change");
1867
+ logger12.error({ filename, error: err }, "Error processing template file change");
1781
1868
  });
1782
1869
  }, this.debounceMs)
1783
1870
  );
@@ -1795,7 +1882,7 @@ var TemplateFileWatcher = class {
1795
1882
  if (previousName && this.registry.has(previousName)) {
1796
1883
  this.registry.unregister(previousName);
1797
1884
  this.fileToName.delete(filename);
1798
- logger11.info({ templateName: previousName, filename }, "Template unregistered (file deleted)");
1885
+ logger12.info({ templateName: previousName, filename }, "Template unregistered (file deleted)");
1799
1886
  }
1800
1887
  return;
1801
1888
  }
@@ -1810,9 +1897,9 @@ var TemplateFileWatcher = class {
1810
1897
  }
1811
1898
  this.registry.register(template);
1812
1899
  this.fileToName.set(filename, template.name);
1813
- logger11.info({ templateName: template.name, filename }, previousName ? "Template reloaded" : "New template registered");
1900
+ logger12.info({ templateName: template.name, filename }, previousName ? "Template reloaded" : "New template registered");
1814
1901
  } catch (err) {
1815
- logger11.warn({ filePath, error: err }, "Failed to reload template");
1902
+ logger12.warn({ filePath, error: err }, "Failed to reload template");
1816
1903
  }
1817
1904
  }
1818
1905
  };
@@ -1826,7 +1913,7 @@ import {
1826
1913
  ConfigValidationError as ConfigValidationError4,
1827
1914
  InstanceCorruptedError as InstanceCorruptedError3,
1828
1915
  WorkspaceInitError,
1829
- createLogger as createLogger15
1916
+ createLogger as createLogger16
1830
1917
  } from "@actant/shared";
1831
1918
 
1832
1919
  // src/state/instance-meta-schema.ts
@@ -1848,7 +1935,7 @@ var LaunchModeSchema = z3.enum([
1848
1935
  ]);
1849
1936
  var ProcessOwnershipSchema = z3.enum(["managed", "external"]);
1850
1937
  var WorkspacePolicySchema = z3.enum(["persistent", "ephemeral"]);
1851
- var AgentBackendTypeSchema = z3.enum(["cursor", "cursor-agent", "claude-code", "custom", "pi"]);
1938
+ var AgentBackendTypeSchema = z3.string().min(1);
1852
1939
  var PermissionModeSchema2 = z3.enum([
1853
1940
  "default",
1854
1941
  "acceptEdits",
@@ -1873,6 +1960,13 @@ var PermissionsConfigSchema = z3.object({
1873
1960
  sandbox: SandboxConfigSchema.optional(),
1874
1961
  additionalDirectories: z3.array(z3.string()).optional()
1875
1962
  });
1963
+ var ModelApiProtocolSchema = z3.enum(["openai", "anthropic", "custom"]);
1964
+ var ModelProviderConfigSchema = z3.object({
1965
+ type: z3.string().min(1),
1966
+ protocol: ModelApiProtocolSchema.optional().default("custom"),
1967
+ baseUrl: z3.string().optional(),
1968
+ config: z3.record(z3.string(), z3.unknown()).optional()
1969
+ });
1876
1970
  var AgentInstanceMetaSchema = z3.object({
1877
1971
  id: z3.string().min(1),
1878
1972
  name: z3.string().min(1),
@@ -1880,6 +1974,7 @@ var AgentInstanceMetaSchema = z3.object({
1880
1974
  templateVersion: z3.string().regex(/^\d+\.\d+\.\d+$/),
1881
1975
  backendType: AgentBackendTypeSchema.default("cursor"),
1882
1976
  backendConfig: z3.record(z3.string(), z3.unknown()).optional(),
1977
+ providerConfig: ModelProviderConfigSchema.optional(),
1883
1978
  status: AgentStatusSchema,
1884
1979
  launchMode: LaunchModeSchema,
1885
1980
  workspacePolicy: WorkspacePolicySchema.default("persistent"),
@@ -1896,8 +1991,8 @@ import { readFile as readFile3, writeFile as writeFile5, rename, readdir as read
1896
1991
  import { join as join8, dirname } from "path";
1897
1992
  import { randomUUID as randomUUID5 } from "crypto";
1898
1993
  import { InstanceCorruptedError } from "@actant/shared";
1899
- import { createLogger as createLogger13 } from "@actant/shared";
1900
- var logger12 = createLogger13("instance-meta-io");
1994
+ import { createLogger as createLogger14 } from "@actant/shared";
1995
+ var logger13 = createLogger14("instance-meta-io");
1901
1996
  var META_FILENAME = ".actant.json";
1902
1997
  function metaFilePath(workspaceDir) {
1903
1998
  return join8(workspaceDir, META_FILENAME);
@@ -1949,12 +2044,12 @@ async function updateInstanceMeta(workspaceDir, patch) {
1949
2044
  await writeInstanceMeta(workspaceDir, updated);
1950
2045
  return updated;
1951
2046
  }
1952
- async function scanInstances(instancesBaseDir, registry3) {
2047
+ async function scanInstances(instancesBaseDir, registry2) {
1953
2048
  const valid = [];
1954
2049
  const corrupted = [];
1955
2050
  const validNames = /* @__PURE__ */ new Set();
1956
- if (registry3) {
1957
- for (const entry of registry3.list()) {
2051
+ if (registry2) {
2052
+ for (const entry of registry2.list()) {
1958
2053
  if (entry.status === "orphaned") continue;
1959
2054
  try {
1960
2055
  const st = await stat5(entry.workspacePath);
@@ -1966,7 +2061,7 @@ async function scanInstances(instancesBaseDir, registry3) {
1966
2061
  valid.push(meta);
1967
2062
  validNames.add(entry.name);
1968
2063
  } catch (err) {
1969
- logger12.warn({ name: entry.name, path: entry.workspacePath, error: err }, "Registry entry unreachable or corrupted");
2064
+ logger13.warn({ name: entry.name, path: entry.workspacePath, error: err }, "Registry entry unreachable or corrupted");
1970
2065
  corrupted.push(entry.name);
1971
2066
  }
1972
2067
  }
@@ -1991,7 +2086,7 @@ async function scanInstances(instancesBaseDir, registry3) {
1991
2086
  valid.push(meta);
1992
2087
  validNames.add(meta.name);
1993
2088
  } catch (err) {
1994
- logger12.warn({ dir: entry, error: err }, "Corrupted instance directory");
2089
+ logger13.warn({ dir: entry, error: err }, "Corrupted instance directory");
1995
2090
  corrupted.push(entry);
1996
2091
  }
1997
2092
  }
@@ -2140,13 +2235,13 @@ function isNodeError4(err) {
2140
2235
  }
2141
2236
 
2142
2237
  // src/initializer/pipeline/initialization-pipeline.ts
2143
- import { createLogger as createLogger14 } from "@actant/shared";
2144
- var logger13 = createLogger14("initialization-pipeline");
2238
+ import { createLogger as createLogger15 } from "@actant/shared";
2239
+ var logger14 = createLogger15("initialization-pipeline");
2145
2240
  var DEFAULT_STEP_TIMEOUT_MS = 6e4;
2146
2241
  var DEFAULT_TOTAL_TIMEOUT_MS = 3e5;
2147
2242
  var InitializationPipeline = class {
2148
- constructor(registry3, options) {
2149
- this.registry = registry3;
2243
+ constructor(registry2, options) {
2244
+ this.registry = registry2;
2150
2245
  this.stepTimeoutMs = options?.defaultStepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
2151
2246
  this.totalTimeoutMs = options?.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS;
2152
2247
  this.onProgress = options?.onProgress;
@@ -2175,14 +2270,14 @@ var InitializationPipeline = class {
2175
2270
  const errors = [];
2176
2271
  const executed = [];
2177
2272
  const pipelineStart = Date.now();
2178
- logger13.info({ stepsCount: steps.length }, "Starting initialization pipeline");
2273
+ logger14.info({ stepsCount: steps.length }, "Starting initialization pipeline");
2179
2274
  for (let i = 0; i < steps.length; i++) {
2180
2275
  const step = steps[i];
2181
2276
  if (!step) continue;
2182
2277
  if (Date.now() - pipelineStart > this.totalTimeoutMs) {
2183
2278
  const err = new Error(`Pipeline total timeout exceeded (${this.totalTimeoutMs}ms)`);
2184
2279
  errors.push({ stepIndex: i, stepType: step.type, error: err });
2185
- logger13.error({ stepIndex: i, stepType: step.type }, "Pipeline timeout exceeded");
2280
+ logger14.error({ stepIndex: i, stepType: step.type }, "Pipeline timeout exceeded");
2186
2281
  await this.rollback(executed, context, err);
2187
2282
  return { success: false, stepsExecuted: i, stepsTotal: steps.length, errors, outputs };
2188
2283
  }
@@ -2194,7 +2289,7 @@ var InitializationPipeline = class {
2194
2289
  return { success: false, stepsExecuted: i, stepsTotal: steps.length, errors, outputs };
2195
2290
  }
2196
2291
  this.onProgress?.(i, steps.length, step.type);
2197
- logger13.debug({ stepIndex: i, stepType: step.type }, "Executing step");
2292
+ logger14.debug({ stepIndex: i, stepType: step.type }, "Executing step");
2198
2293
  try {
2199
2294
  const result = await this.executeWithTimeout(executor, context, step.config ?? {});
2200
2295
  if (!result.success) {
@@ -2209,18 +2304,18 @@ var InitializationPipeline = class {
2209
2304
  outputs.set(`step-${i}-${step.type}`, result.output);
2210
2305
  }
2211
2306
  if (result.message) {
2212
- logger13.info({ stepIndex: i, stepType: step.type }, result.message);
2307
+ logger14.info({ stepIndex: i, stepType: step.type }, result.message);
2213
2308
  }
2214
2309
  } catch (err) {
2215
2310
  const error = err instanceof Error ? err : new Error(String(err));
2216
2311
  errors.push({ stepIndex: i, stepType: step.type, error });
2217
- logger13.error({ stepIndex: i, stepType: step.type, error }, "Step execution failed");
2312
+ logger14.error({ stepIndex: i, stepType: step.type, error }, "Step execution failed");
2218
2313
  executed.push({ index: i, executor, config: step.config ?? {} });
2219
2314
  await this.rollback(executed, context, error);
2220
2315
  return { success: false, stepsExecuted: i + 1, stepsTotal: steps.length, errors, outputs };
2221
2316
  }
2222
2317
  }
2223
- logger13.info({ stepsExecuted: steps.length, elapsedMs: Date.now() - pipelineStart }, "Pipeline completed successfully");
2318
+ logger14.info({ stepsExecuted: steps.length, elapsedMs: Date.now() - pipelineStart }, "Pipeline completed successfully");
2224
2319
  return { success: true, stepsExecuted: steps.length, stepsTotal: steps.length, errors, outputs };
2225
2320
  }
2226
2321
  async executeWithTimeout(executor, context, config) {
@@ -2242,7 +2337,7 @@ var InitializationPipeline = class {
2242
2337
  }
2243
2338
  async rollback(executed, context, triggerError) {
2244
2339
  if (executed.length === 0) return;
2245
- logger13.info({ stepsToRollback: executed.length }, "Rolling back executed steps");
2340
+ logger14.info({ stepsToRollback: executed.length }, "Rolling back executed steps");
2246
2341
  for (let i = executed.length - 1; i >= 0; i--) {
2247
2342
  const entry = executed[i];
2248
2343
  if (!entry) continue;
@@ -2250,9 +2345,9 @@ var InitializationPipeline = class {
2250
2345
  if (!executor.rollback) continue;
2251
2346
  try {
2252
2347
  await executor.rollback(context, config, triggerError);
2253
- logger13.debug({ stepIndex: index, stepType: executor.type }, "Step rolled back");
2348
+ logger14.debug({ stepIndex: index, stepType: executor.type }, "Step rolled back");
2254
2349
  } catch (rollbackErr) {
2255
- logger13.warn(
2350
+ logger14.warn(
2256
2351
  { stepIndex: index, stepType: executor.type, error: rollbackErr },
2257
2352
  "Rollback failed (best-effort)"
2258
2353
  );
@@ -2262,7 +2357,7 @@ var InitializationPipeline = class {
2262
2357
  };
2263
2358
 
2264
2359
  // src/initializer/agent-initializer.ts
2265
- var logger14 = createLogger15("agent-initializer");
2360
+ var logger15 = createLogger16("agent-initializer");
2266
2361
  var AgentInitializer = class {
2267
2362
  constructor(templateRegistry, instancesBaseDir, options) {
2268
2363
  this.templateRegistry = templateRegistry;
@@ -2304,10 +2399,10 @@ var AgentInitializer = class {
2304
2399
  );
2305
2400
  case "overwrite":
2306
2401
  await rm(workspaceDir, { recursive: true, force: true });
2307
- logger14.info({ workspaceDir }, "Existing directory removed (overwrite)");
2402
+ logger15.info({ workspaceDir }, "Existing directory removed (overwrite)");
2308
2403
  break;
2309
2404
  case "append":
2310
- logger14.info({ workspaceDir }, "Appending to existing directory");
2405
+ logger15.info({ workspaceDir }, "Appending to existing directory");
2311
2406
  break;
2312
2407
  }
2313
2408
  }
@@ -2335,7 +2430,7 @@ var AgentInitializer = class {
2335
2430
  workspaceDir,
2336
2431
  instanceMeta: { name, templateName: template.name },
2337
2432
  template,
2338
- logger: logger14,
2433
+ logger: logger15,
2339
2434
  state: /* @__PURE__ */ new Map()
2340
2435
  };
2341
2436
  const pipelineResult = await this.pipeline.run(template.initializer.steps, stepContext);
@@ -2348,6 +2443,7 @@ var AgentInitializer = class {
2348
2443
  }
2349
2444
  }
2350
2445
  const effectivePermissions = resolvePermissions(finalPermissions);
2446
+ const resolvedProvider = resolveProviderConfig(template.provider);
2351
2447
  const now = (/* @__PURE__ */ new Date()).toISOString();
2352
2448
  const launchMode = overrides?.launchMode ?? this.options?.defaultLaunchMode ?? "direct";
2353
2449
  const defaultPolicy = launchMode === "one-shot" ? "ephemeral" : "persistent";
@@ -2358,6 +2454,7 @@ var AgentInitializer = class {
2358
2454
  templateVersion: template.version,
2359
2455
  backendType: template.backend.type,
2360
2456
  backendConfig: template.backend.config ? { ...template.backend.config } : void 0,
2457
+ providerConfig: resolvedProvider,
2361
2458
  status: "created",
2362
2459
  launchMode,
2363
2460
  workspacePolicy: overrides?.workspacePolicy ?? defaultPolicy,
@@ -2372,13 +2469,13 @@ var AgentInitializer = class {
2372
2469
  const linkType = process.platform === "win32" ? "junction" : "dir";
2373
2470
  await symlink(workspaceDir, join10(this.instancesBaseDir, name), linkType);
2374
2471
  }
2375
- logger14.info({ name, templateName, workspaceDir, customWorkDir: !!customWorkDir }, "Agent instance created");
2472
+ logger15.info({ name, templateName, workspaceDir, customWorkDir: !!customWorkDir }, "Agent instance created");
2376
2473
  return meta;
2377
2474
  } catch (err) {
2378
2475
  if (shouldCleanupOnError) {
2379
2476
  await rm(workspaceDir, { recursive: true, force: true }).catch(() => {
2380
2477
  });
2381
- logger14.debug({ workspaceDir }, "Cleaned up workspace after failed creation");
2478
+ logger15.debug({ workspaceDir }, "Cleaned up workspace after failed creation");
2382
2479
  }
2383
2480
  if (err instanceof ActantError) {
2384
2481
  throw err;
@@ -2397,7 +2494,7 @@ var AgentInitializer = class {
2397
2494
  if (await dirExists(workspaceDir)) {
2398
2495
  try {
2399
2496
  const meta2 = await readInstanceMeta(workspaceDir);
2400
- logger14.debug({ name }, "Existing instance found");
2497
+ logger15.debug({ name }, "Existing instance found");
2401
2498
  return { meta: meta2, created: false };
2402
2499
  } catch (err) {
2403
2500
  if (err instanceof InstanceCorruptedError3) {
@@ -2418,7 +2515,7 @@ var AgentInitializer = class {
2418
2515
  async destroyInstance(name) {
2419
2516
  const entryPath = join10(this.instancesBaseDir, name);
2420
2517
  if (!await entryExists(entryPath)) {
2421
- logger14.warn({ name }, "Instance directory not found, nothing to destroy");
2518
+ logger15.warn({ name }, "Instance directory not found, nothing to destroy");
2422
2519
  return;
2423
2520
  }
2424
2521
  if (await isSymlink(entryPath)) {
@@ -2428,13 +2525,30 @@ var AgentInitializer = class {
2428
2525
  } catch {
2429
2526
  }
2430
2527
  await rm(entryPath, { recursive: true, force: true });
2431
- logger14.info({ name, targetDir }, "Symlinked agent instance unregistered");
2528
+ logger15.info({ name, targetDir }, "Symlinked agent instance unregistered");
2432
2529
  } else {
2433
2530
  await rm(entryPath, { recursive: true, force: true });
2434
- logger14.info({ name }, "Agent instance destroyed");
2531
+ logger15.info({ name }, "Agent instance destroyed");
2435
2532
  }
2436
2533
  }
2437
2534
  };
2535
+ function resolveProviderConfig(templateProvider) {
2536
+ if (templateProvider) {
2537
+ if (templateProvider.protocol) return templateProvider;
2538
+ const desc = modelProviderRegistry.get(templateProvider.type);
2539
+ return {
2540
+ ...templateProvider,
2541
+ protocol: desc?.protocol ?? "custom"
2542
+ };
2543
+ }
2544
+ const defaultDesc = modelProviderRegistry.getDefault();
2545
+ if (!defaultDesc) return void 0;
2546
+ return {
2547
+ type: defaultDesc.type,
2548
+ protocol: defaultDesc.protocol,
2549
+ baseUrl: defaultDesc.defaultBaseUrl
2550
+ };
2551
+ }
2438
2552
  async function dirExists(path) {
2439
2553
  try {
2440
2554
  await access2(path);
@@ -2463,8 +2577,8 @@ async function isSymlink(path) {
2463
2577
  // src/initializer/context/context-materializer.ts
2464
2578
  import { writeFile as writeFile7, mkdir as mkdir9 } from "fs/promises";
2465
2579
  import { join as join11 } from "path";
2466
- import { createLogger as createLogger16 } from "@actant/shared";
2467
- var logger15 = createLogger16("context-materializer");
2580
+ import { createLogger as createLogger17 } from "@actant/shared";
2581
+ var logger16 = createLogger17("context-materializer");
2468
2582
  var BACKEND_CONFIG_DIR = {
2469
2583
  cursor: ".cursor",
2470
2584
  "cursor-agent": ".cursor",
@@ -2477,7 +2591,7 @@ var ContextMaterializer = class {
2477
2591
  this.managers = managers;
2478
2592
  }
2479
2593
  async materialize(workspaceDir, domainContext, backendType = "cursor") {
2480
- const configDir = BACKEND_CONFIG_DIR[backendType];
2594
+ const configDir = BACKEND_CONFIG_DIR[backendType] ?? ".cursor";
2481
2595
  const tasks = [];
2482
2596
  if (domainContext.skills && domainContext.skills.length > 0) {
2483
2597
  tasks.push(this.materializeSkills(workspaceDir, domainContext.skills));
@@ -2495,7 +2609,7 @@ var ContextMaterializer = class {
2495
2609
  tasks.push(this.materializePrompts(workspaceDir, domainContext.prompts));
2496
2610
  }
2497
2611
  await Promise.all(tasks);
2498
- logger15.debug({ workspaceDir, backendType, configDir }, "Domain context materialized");
2612
+ logger16.debug({ workspaceDir, backendType, configDir }, "Domain context materialized");
2499
2613
  }
2500
2614
  async materializeSkills(workspaceDir, skillNames) {
2501
2615
  let content;
@@ -2610,16 +2724,16 @@ var InitializerStepExecutor = class {
2610
2724
  };
2611
2725
 
2612
2726
  // src/initializer/pipeline/step-registry.ts
2613
- import { createLogger as createLogger17 } from "@actant/shared";
2614
- var logger16 = createLogger17("step-registry");
2727
+ import { createLogger as createLogger18 } from "@actant/shared";
2728
+ var logger17 = createLogger18("step-registry");
2615
2729
  var StepRegistry = class {
2616
2730
  executors = /* @__PURE__ */ new Map();
2617
2731
  register(executor) {
2618
2732
  if (this.executors.has(executor.type)) {
2619
- logger16.warn({ type: executor.type }, "Overwriting existing step executor");
2733
+ logger17.warn({ type: executor.type }, "Overwriting existing step executor");
2620
2734
  }
2621
2735
  this.executors.set(executor.type, executor);
2622
- logger16.debug({ type: executor.type }, "Step executor registered");
2736
+ logger17.debug({ type: executor.type }, "Step executor registered");
2623
2737
  }
2624
2738
  get(type) {
2625
2739
  return this.executors.get(type);
@@ -2878,10 +2992,10 @@ var NpmInstallStep = class extends InitializerStepExecutor {
2878
2992
  return { valid: issues.length === 0, issues };
2879
2993
  }
2880
2994
  async execute(context, config) {
2881
- const { packageManager = "npm", cwd = ".", args = [], registry: registry3 } = config;
2995
+ const { packageManager = "npm", cwd = ".", args = [], registry: registry2 } = config;
2882
2996
  const workDir = join16(context.workspaceDir, cwd);
2883
2997
  const cmdArgs = ["install", ...args];
2884
- if (registry3) cmdArgs.push("--registry", registry3);
2998
+ if (registry2) cmdArgs.push("--registry", registry2);
2885
2999
  context.logger.debug({ packageManager, cwd: workDir, args: cmdArgs }, "Installing dependencies");
2886
3000
  const result = await runInstall(packageManager, cmdArgs, workDir);
2887
3001
  if (result.exitCode !== 0) {
@@ -2923,80 +3037,294 @@ function runInstall(pm, args, cwd) {
2923
3037
 
2924
3038
  // src/initializer/steps/index.ts
2925
3039
  function createDefaultStepRegistry() {
2926
- const registry3 = new StepRegistry();
2927
- registry3.register(new MkdirStep());
2928
- registry3.register(new ExecStep());
2929
- registry3.register(new FileCopyStep());
2930
- registry3.register(new GitCloneStep());
2931
- registry3.register(new NpmInstallStep());
2932
- return registry3;
3040
+ const registry2 = new StepRegistry();
3041
+ registry2.register(new MkdirStep());
3042
+ registry2.register(new ExecStep());
3043
+ registry2.register(new FileCopyStep());
3044
+ registry2.register(new GitCloneStep());
3045
+ registry2.register(new NpmInstallStep());
3046
+ return registry2;
2933
3047
  }
2934
3048
 
2935
3049
  // src/manager/agent-manager.ts
2936
3050
  import { join as join17 } from "path";
3051
+ import { existsSync } from "fs";
2937
3052
  import { rename as rename3, mkdir as mkdir11 } from "fs/promises";
2938
3053
  import {
2939
3054
  AgentNotFoundError,
2940
3055
  AgentAlreadyRunningError,
2941
3056
  AgentAlreadyAttachedError,
2942
3057
  AgentNotAttachedError,
2943
- createLogger as createLogger23
3058
+ AgentLaunchError,
3059
+ createLogger as createLogger24
2944
3060
  } from "@actant/shared";
2945
3061
 
3062
+ // src/domain/backend/backend-manager.ts
3063
+ import { execFile } from "child_process";
3064
+
3065
+ // src/domain/backend/backend-schema.ts
3066
+ import { z as z4 } from "zod/v4";
3067
+ var PlatformCommandSchema = z4.object({
3068
+ win32: z4.string().min(1),
3069
+ default: z4.string().min(1)
3070
+ });
3071
+ var OpenSpawnOptionsSchema = z4.object({
3072
+ stdio: z4.enum(["inherit", "ignore"]).optional(),
3073
+ detached: z4.boolean().optional(),
3074
+ windowsHide: z4.boolean().optional(),
3075
+ shell: z4.boolean().optional()
3076
+ });
3077
+ var ExistenceCheckSchema = z4.object({
3078
+ command: z4.string().min(1),
3079
+ args: z4.array(z4.string()).optional(),
3080
+ expectedExitCode: z4.number().int().optional(),
3081
+ versionPattern: z4.string().optional()
3082
+ });
3083
+ var InstallMethodSchema = z4.object({
3084
+ type: z4.enum(["npm", "brew", "winget", "choco", "url", "manual"]),
3085
+ package: z4.string().optional(),
3086
+ platforms: z4.array(z4.string()).optional(),
3087
+ label: z4.string().optional(),
3088
+ instructions: z4.string().optional()
3089
+ });
3090
+ var BackendDefinitionSchema = z4.object({
3091
+ name: z4.string().min(1),
3092
+ version: z4.string().optional(),
3093
+ description: z4.string().optional(),
3094
+ tags: z4.array(z4.string()).optional(),
3095
+ supportedModes: z4.array(z4.enum(["resolve", "open", "acp"])).min(1),
3096
+ resolveCommand: PlatformCommandSchema.optional(),
3097
+ openCommand: PlatformCommandSchema.optional(),
3098
+ acpCommand: PlatformCommandSchema.optional(),
3099
+ acpOwnsProcess: z4.boolean().optional(),
3100
+ resolvePackage: z4.string().optional(),
3101
+ openWorkspaceDir: z4.enum(["arg", "cwd"]).optional(),
3102
+ openSpawnOptions: OpenSpawnOptionsSchema.optional(),
3103
+ existenceCheck: ExistenceCheckSchema.optional(),
3104
+ install: z4.array(InstallMethodSchema).optional()
3105
+ }).passthrough();
3106
+
3107
+ // src/domain/backend/backend-manager.ts
3108
+ var BackendManager = class extends BaseComponentManager {
3109
+ componentType = "Backend";
3110
+ acpResolvers = /* @__PURE__ */ new Map();
3111
+ constructor() {
3112
+ super("backend-manager");
3113
+ }
3114
+ // ---------------------------------------------------------------------------
3115
+ // ACP resolver (behavioral extension, not serializable)
3116
+ // ---------------------------------------------------------------------------
3117
+ registerAcpResolver(backendName, resolver) {
3118
+ this.acpResolvers.set(backendName, resolver);
3119
+ }
3120
+ getAcpResolver(backendName) {
3121
+ return this.acpResolvers.get(backendName);
3122
+ }
3123
+ // ---------------------------------------------------------------------------
3124
+ // Mode queries
3125
+ // ---------------------------------------------------------------------------
3126
+ supportsMode(backendName, mode) {
3127
+ const def = this.get(backendName);
3128
+ return def != null && def.supportedModes.includes(mode);
3129
+ }
3130
+ requireMode(backendName, mode) {
3131
+ const def = this.get(backendName);
3132
+ if (!def) {
3133
+ throw new Error(
3134
+ `Backend "${backendName}" is not registered. Ensure the backend package is installed and its definition was loaded at startup.`
3135
+ );
3136
+ }
3137
+ if (!def.supportedModes.includes(mode)) {
3138
+ const supported = def.supportedModes.join(", ");
3139
+ throw new Error(
3140
+ `Backend "${backendName}" does not support "${mode}" mode. Supported modes: [${supported}]. ` + (mode === "resolve" ? `Use \`agent start\` or \`agent run\` instead.` : mode === "open" ? `This backend has no native TUI/UI to open.` : `Use \`agent resolve\` or \`agent open\` instead.`)
3141
+ );
3142
+ }
3143
+ }
3144
+ // ---------------------------------------------------------------------------
3145
+ // Platform command helper
3146
+ // ---------------------------------------------------------------------------
3147
+ getPlatformCommand(cmd) {
3148
+ return process.platform === "win32" ? cmd.win32 : cmd.default;
3149
+ }
3150
+ // ---------------------------------------------------------------------------
3151
+ // Availability check
3152
+ // ---------------------------------------------------------------------------
3153
+ /**
3154
+ * Probe whether a backend binary is available on the system.
3155
+ * Uses the `existenceCheck` field. Returns `{ available, version? }`.
3156
+ * If no `existenceCheck` is configured, returns `{ available: true }` (assume ok).
3157
+ */
3158
+ async checkAvailability(backendName) {
3159
+ const def = this.get(backendName);
3160
+ if (!def) return { available: false, error: `Backend "${backendName}" not registered` };
3161
+ if (!def.existenceCheck) return { available: true };
3162
+ const { command, args = ["--version"], expectedExitCode = 0, versionPattern } = def.existenceCheck;
3163
+ try {
3164
+ const { stdout, exitCode } = await execCommand(command, args);
3165
+ if (exitCode !== expectedExitCode) {
3166
+ return { available: false, error: `Exit code ${exitCode} (expected ${expectedExitCode})` };
3167
+ }
3168
+ if (versionPattern) {
3169
+ const match = new RegExp(versionPattern).exec(stdout);
3170
+ if (!match) {
3171
+ return { available: false, error: `Version output didn't match pattern: ${versionPattern}` };
3172
+ }
3173
+ return { available: true, version: match[0] };
3174
+ }
3175
+ const version = stdout.trim().split("\n")[0];
3176
+ return { available: true, version: version || void 0 };
3177
+ } catch (err) {
3178
+ return { available: false, error: err.message };
3179
+ }
3180
+ }
3181
+ /**
3182
+ * Get platform-appropriate install methods for a backend.
3183
+ * Filters by `platforms` field; returns all methods if no platform restriction.
3184
+ */
3185
+ getInstallMethods(backendName) {
3186
+ const def = this.get(backendName);
3187
+ if (!def?.install) return [];
3188
+ const plat = process.platform;
3189
+ return def.install.filter((m) => !m.platforms || m.platforms.includes(plat));
3190
+ }
3191
+ // ---------------------------------------------------------------------------
3192
+ // Validation
3193
+ // ---------------------------------------------------------------------------
3194
+ validate(data, _source) {
3195
+ const result = BackendDefinitionSchema.safeParse(data);
3196
+ if (!result.success) {
3197
+ return {
3198
+ valid: false,
3199
+ errors: result.error.issues.map((i) => ({
3200
+ path: i.path.map(String).join("."),
3201
+ message: i.message,
3202
+ severity: "error"
3203
+ })),
3204
+ warnings: []
3205
+ };
3206
+ }
3207
+ return { valid: true, data: result.data, errors: [], warnings: [] };
3208
+ }
3209
+ };
3210
+ function execCommand(command, args) {
3211
+ return new Promise((resolve3) => {
3212
+ execFile(command, args, { timeout: 1e4, windowsHide: true }, (err, stdout) => {
3213
+ if (err && typeof err.code === "string" && err.code === "ENOENT") {
3214
+ resolve3({ stdout: "", exitCode: -1 });
3215
+ return;
3216
+ }
3217
+ const exitCode = err && "code" in err && typeof err.code === "number" ? err.code : err ? 1 : 0;
3218
+ resolve3({ stdout: stdout ?? "", exitCode });
3219
+ });
3220
+ });
3221
+ }
3222
+
2946
3223
  // src/manager/launcher/backend-registry.ts
2947
- var registry = /* @__PURE__ */ new Map();
3224
+ var manager = new BackendManager();
3225
+ function getBackendManager() {
3226
+ return manager;
3227
+ }
2948
3228
  function registerBackend(descriptor) {
2949
- registry.set(descriptor.type, descriptor);
3229
+ const { type, acpResolver, ...rest } = descriptor;
3230
+ const definition = { ...rest, name: type };
3231
+ manager.register(definition);
3232
+ if (acpResolver) {
3233
+ manager.registerAcpResolver(type, acpResolver);
3234
+ }
3235
+ }
3236
+ function registerBackendDefinition(definition) {
3237
+ manager.register(definition);
2950
3238
  }
2951
3239
  function getBackendDescriptor(type) {
2952
- const desc = registry.get(type);
2953
- if (!desc) {
3240
+ const def = manager.get(type);
3241
+ if (!def) {
2954
3242
  throw new Error(
2955
3243
  `Backend "${type}" is not registered. Ensure the backend package is installed and registerBackend() was called at startup.`
2956
3244
  );
2957
3245
  }
2958
- return desc;
3246
+ return def;
2959
3247
  }
2960
3248
  function supportsMode(type, mode) {
2961
- const desc = registry.get(type);
2962
- return desc != null && desc.supportedModes.includes(mode);
3249
+ return manager.supportsMode(type, mode);
2963
3250
  }
2964
3251
  function requireMode(type, mode) {
2965
- const desc = getBackendDescriptor(type);
2966
- if (!desc.supportedModes.includes(mode)) {
2967
- const supported = desc.supportedModes.join(", ");
2968
- throw new Error(
2969
- `Backend "${type}" does not support "${mode}" mode. Supported modes: [${supported}]. ` + (mode === "resolve" ? `Use \`agent start\` or \`agent run\` instead.` : mode === "open" ? `This backend has no native TUI/UI to open.` : `Use \`agent resolve\` or \`agent open\` instead.`)
2970
- );
2971
- }
3252
+ manager.requireMode(type, mode);
2972
3253
  }
2973
3254
  function getPlatformCommand(cmd) {
2974
- return process.platform === "win32" ? cmd.win32 : cmd.default;
3255
+ return manager.getPlatformCommand(cmd);
3256
+ }
3257
+ function getInstallHint(type) {
3258
+ const methods = manager.getInstallMethods(type);
3259
+ const first = methods[0];
3260
+ return first?.label ?? first?.instructions;
3261
+ }
3262
+ function getAcpResolver(type) {
3263
+ return manager.getAcpResolver(type);
2975
3264
  }
2976
3265
 
2977
3266
  // src/manager/launcher/builtin-backends.ts
2978
- function registerBuiltinBackends() {
2979
- registerBackend({
2980
- type: "cursor",
3267
+ var BUILTIN_BACKENDS = [
3268
+ {
3269
+ name: "cursor",
3270
+ version: "1.0.0",
3271
+ description: "Cursor IDE",
3272
+ origin: { type: "builtin" },
2981
3273
  supportedModes: ["resolve", "open"],
2982
3274
  resolveCommand: { win32: "cursor.cmd", default: "cursor" },
2983
- openCommand: { win32: "cursor.cmd", default: "cursor" }
2984
- });
2985
- registerBackend({
2986
- type: "cursor-agent",
3275
+ openCommand: { win32: "cursor.cmd", default: "cursor" },
3276
+ openSpawnOptions: { shell: process.platform === "win32" },
3277
+ existenceCheck: { command: "cursor", args: ["--version"] },
3278
+ install: [
3279
+ { type: "url", package: "https://cursor.com", label: "Download Cursor", platforms: ["win32", "darwin", "linux"] },
3280
+ { type: "brew", package: "cursor", label: "brew install --cask cursor", platforms: ["darwin"] },
3281
+ { type: "winget", package: "Anysphere.Cursor", label: "winget install Anysphere.Cursor", platforms: ["win32"] }
3282
+ ]
3283
+ },
3284
+ {
3285
+ name: "cursor-agent",
3286
+ version: "1.0.0",
3287
+ description: "Cursor Agent (TUI)",
3288
+ origin: { type: "builtin" },
2987
3289
  supportedModes: ["resolve", "open", "acp"],
2988
- resolveCommand: { win32: "cursor.cmd", default: "cursor" },
2989
- openCommand: { win32: "cursor.cmd", default: "cursor" }
2990
- });
2991
- registerBackend({
2992
- type: "claude-code",
2993
- supportedModes: ["resolve", "acp"],
2994
- resolveCommand: { win32: "claude-agent-acp.cmd", default: "claude-agent-acp" }
2995
- });
2996
- registerBackend({
2997
- type: "custom",
3290
+ resolveCommand: { win32: "agent", default: "agent" },
3291
+ openCommand: { win32: "agent", default: "agent" },
3292
+ openWorkspaceDir: "cwd",
3293
+ openSpawnOptions: { stdio: "inherit", detached: false, windowsHide: false },
3294
+ existenceCheck: { command: "agent", args: ["--version"] },
3295
+ install: [
3296
+ { type: "manual", label: "Included with Cursor", instructions: "Install Cursor from https://cursor.com \u2014 the `agent` CLI is bundled." }
3297
+ ]
3298
+ },
3299
+ {
3300
+ name: "claude-code",
3301
+ version: "1.0.0",
3302
+ description: "Claude Code CLI",
3303
+ origin: { type: "builtin" },
3304
+ supportedModes: ["resolve", "open", "acp"],
3305
+ resolveCommand: { win32: "claude-agent-acp.cmd", default: "claude-agent-acp" },
3306
+ openCommand: { win32: "claude.exe", default: "claude" },
3307
+ openWorkspaceDir: "cwd",
3308
+ openSpawnOptions: { stdio: "inherit", detached: false, windowsHide: false },
3309
+ resolvePackage: "@zed-industries/claude-agent-acp",
3310
+ existenceCheck: { command: "claude", args: ["--version"] },
3311
+ install: [
3312
+ { type: "npm", package: "@anthropic-ai/claude-code", label: "npm install -g @anthropic-ai/claude-code" }
3313
+ ]
3314
+ },
3315
+ {
3316
+ name: "custom",
3317
+ version: "1.0.0",
3318
+ description: "Custom backend (user-provided executable)",
3319
+ origin: { type: "builtin" },
2998
3320
  supportedModes: ["resolve"]
2999
- });
3321
+ }
3322
+ ];
3323
+ function registerBuiltinBackends() {
3324
+ const mgr = getBackendManager();
3325
+ for (const def of BUILTIN_BACKENDS) {
3326
+ mgr.register(def);
3327
+ }
3000
3328
  }
3001
3329
  registerBuiltinBackends();
3002
3330
 
@@ -3032,25 +3360,36 @@ function resolveBackend(backendType, workspaceDir, backendConfig) {
3032
3360
  })();
3033
3361
  return {
3034
3362
  command,
3035
- args: buildArgs(backendType, workspaceDir, backendConfig)
3363
+ args: buildArgs(backendType, workspaceDir, backendConfig),
3364
+ resolvePackage: desc.resolvePackage
3036
3365
  };
3037
3366
  }
3367
+ var DEFAULT_OPEN_SPAWN = {
3368
+ stdio: "ignore",
3369
+ detached: true,
3370
+ windowsHide: true,
3371
+ shell: false
3372
+ };
3038
3373
  function openBackend(backendType, workspaceDir) {
3039
3374
  requireMode(backendType, "open");
3040
3375
  const desc = getBackendDescriptor(backendType);
3041
3376
  if (!desc.openCommand) {
3042
3377
  throw new Error(`Backend "${backendType}" has no openCommand configured.`);
3043
3378
  }
3379
+ const useCwd = desc.openWorkspaceDir === "cwd";
3044
3380
  return {
3045
3381
  command: getPlatformCommand(desc.openCommand),
3046
- args: [workspaceDir]
3382
+ args: useCwd ? [] : [workspaceDir],
3383
+ cwd: useCwd ? workspaceDir : void 0,
3384
+ openSpawnOptions: { ...DEFAULT_OPEN_SPAWN, ...desc.openSpawnOptions }
3047
3385
  };
3048
3386
  }
3049
3387
  function resolveAcpBackend(backendType, workspaceDir, backendConfig) {
3050
3388
  requireMode(backendType, "acp");
3051
3389
  const desc = getBackendDescriptor(backendType);
3052
- if (desc.acpResolver) {
3053
- return desc.acpResolver(workspaceDir, backendConfig);
3390
+ const resolver = getAcpResolver(backendType);
3391
+ if (resolver) {
3392
+ return resolver(workspaceDir, backendConfig);
3054
3393
  }
3055
3394
  const explicitPath = backendConfig?.executablePath;
3056
3395
  const commandSource = desc.acpCommand ?? desc.resolveCommand;
@@ -3059,12 +3398,13 @@ function resolveAcpBackend(backendType, workspaceDir, backendConfig) {
3059
3398
  })();
3060
3399
  return {
3061
3400
  command,
3062
- args: buildArgs(backendType, workspaceDir, backendConfig)
3401
+ args: buildArgs(backendType, workspaceDir, backendConfig),
3402
+ resolvePackage: desc.resolvePackage
3063
3403
  };
3064
3404
  }
3065
3405
 
3066
3406
  // src/manager/launcher/process-watcher.ts
3067
- import { createLogger as createLogger18 } from "@actant/shared";
3407
+ import { createLogger as createLogger19 } from "@actant/shared";
3068
3408
 
3069
3409
  // src/manager/launcher/process-utils.ts
3070
3410
  function isProcessAlive(pid) {
@@ -3100,7 +3440,7 @@ function isNodeError5(err) {
3100
3440
  }
3101
3441
 
3102
3442
  // src/manager/launcher/process-watcher.ts
3103
- var logger17 = createLogger18("process-watcher");
3443
+ var logger18 = createLogger19("process-watcher");
3104
3444
  var DEFAULT_POLL_INTERVAL = 5e3;
3105
3445
  var ProcessWatcher = class {
3106
3446
  constructor(onProcessExit, options) {
@@ -3113,12 +3453,12 @@ var ProcessWatcher = class {
3113
3453
  pollIntervalMs;
3114
3454
  watch(instanceName, pid) {
3115
3455
  this.watches.set(instanceName, { pid });
3116
- logger17.debug({ instanceName, pid }, "Watching process");
3456
+ logger18.debug({ instanceName, pid }, "Watching process");
3117
3457
  }
3118
3458
  unwatch(instanceName) {
3119
3459
  const removed = this.watches.delete(instanceName);
3120
3460
  if (removed) {
3121
- logger17.debug({ instanceName }, "Unwatched process");
3461
+ logger18.debug({ instanceName }, "Unwatched process");
3122
3462
  }
3123
3463
  return removed;
3124
3464
  }
@@ -3133,13 +3473,13 @@ var ProcessWatcher = class {
3133
3473
  this.timer = setInterval(() => {
3134
3474
  void this.poll();
3135
3475
  }, this.pollIntervalMs);
3136
- logger17.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3476
+ logger18.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3137
3477
  }
3138
3478
  stop() {
3139
3479
  if (this.timer) {
3140
3480
  clearInterval(this.timer);
3141
3481
  this.timer = null;
3142
- logger17.info("ProcessWatcher stopped");
3482
+ logger18.info("ProcessWatcher stopped");
3143
3483
  }
3144
3484
  }
3145
3485
  dispose() {
@@ -3161,11 +3501,11 @@ var ProcessWatcher = class {
3161
3501
  }
3162
3502
  for (const info of exited) {
3163
3503
  this.watches.delete(info.instanceName);
3164
- logger17.info(info, "Process exited \u2014 removed from watch list");
3504
+ logger18.info(info, "Process exited \u2014 removed from watch list");
3165
3505
  try {
3166
3506
  await this.onProcessExit(info);
3167
3507
  } catch (err) {
3168
- logger17.error({ ...info, error: err }, "Error in process exit handler");
3508
+ logger18.error({ ...info, error: err }, "Error in process exit handler");
3169
3509
  }
3170
3510
  }
3171
3511
  } finally {
@@ -3175,8 +3515,8 @@ var ProcessWatcher = class {
3175
3515
  };
3176
3516
 
3177
3517
  // src/manager/launch-mode-handler.ts
3178
- import { createLogger as createLogger19 } from "@actant/shared";
3179
- var logger18 = createLogger19("launch-mode-handler");
3518
+ import { createLogger as createLogger20 } from "@actant/shared";
3519
+ var logger19 = createLogger20("launch-mode-handler");
3180
3520
  var DirectModeHandler = class {
3181
3521
  mode = "direct";
3182
3522
  getProcessExitAction(_instanceName) {
@@ -3198,11 +3538,11 @@ var AcpBackgroundModeHandler = class {
3198
3538
  var AcpServiceModeHandler = class {
3199
3539
  mode = "acp-service";
3200
3540
  getProcessExitAction(instanceName) {
3201
- logger18.info({ instanceName }, "acp-service process exited \u2014 restart policy will be checked");
3541
+ logger19.info({ instanceName }, "acp-service process exited \u2014 restart policy will be checked");
3202
3542
  return { type: "restart" };
3203
3543
  }
3204
3544
  getRecoveryAction(instanceName) {
3205
- logger18.info({ instanceName }, "acp-service stale instance \u2014 will attempt recovery restart");
3545
+ logger19.info({ instanceName }, "acp-service stale instance \u2014 will attempt recovery restart");
3206
3546
  return { type: "restart" };
3207
3547
  }
3208
3548
  };
@@ -3229,8 +3569,8 @@ function getLaunchModeHandler(mode) {
3229
3569
  }
3230
3570
 
3231
3571
  // src/manager/restart-tracker.ts
3232
- import { createLogger as createLogger20 } from "@actant/shared";
3233
- var logger19 = createLogger20("restart-tracker");
3572
+ import { createLogger as createLogger21 } from "@actant/shared";
3573
+ var logger20 = createLogger21("restart-tracker");
3234
3574
  var DEFAULT_RESTART_POLICY = {
3235
3575
  maxRestarts: 5,
3236
3576
  backoffBaseMs: 1e3,
@@ -3248,12 +3588,12 @@ var RestartTracker = class {
3248
3588
  if (state.lastStartAt > 0) {
3249
3589
  const stableMs = Date.now() - state.lastStartAt;
3250
3590
  if (stableMs >= this.policy.resetAfterMs) {
3251
- logger19.info({ instanceName, stableMs, resetAfterMs: this.policy.resetAfterMs }, "Resetting restart counter \u2014 agent was stable");
3591
+ logger20.info({ instanceName, stableMs, resetAfterMs: this.policy.resetAfterMs }, "Resetting restart counter \u2014 agent was stable");
3252
3592
  state.count = 0;
3253
3593
  }
3254
3594
  }
3255
3595
  if (state.count >= this.policy.maxRestarts) {
3256
- logger19.warn({ instanceName, count: state.count, maxRestarts: this.policy.maxRestarts }, "Restart limit exceeded");
3596
+ logger20.warn({ instanceName, count: state.count, maxRestarts: this.policy.maxRestarts }, "Restart limit exceeded");
3257
3597
  return { allowed: false, delayMs: 0, attempt: state.count };
3258
3598
  }
3259
3599
  const delayMs = Math.min(
@@ -3266,7 +3606,7 @@ var RestartTracker = class {
3266
3606
  const state = this.getOrCreate(instanceName);
3267
3607
  state.count++;
3268
3608
  state.lastRestartAt = Date.now();
3269
- logger19.debug({ instanceName, count: state.count }, "Restart recorded");
3609
+ logger20.debug({ instanceName, count: state.count }, "Restart recorded");
3270
3610
  }
3271
3611
  recordStart(instanceName) {
3272
3612
  const state = this.getOrCreate(instanceName);
@@ -3293,8 +3633,8 @@ var RestartTracker = class {
3293
3633
 
3294
3634
  // src/communicator/claude-code-communicator.ts
3295
3635
  import { spawn as spawn4 } from "child_process";
3296
- import { createLogger as createLogger21 } from "@actant/shared";
3297
- var logger20 = createLogger21("claude-code-communicator");
3636
+ import { createLogger as createLogger22 } from "@actant/shared";
3637
+ var logger21 = createLogger22("claude-code-communicator");
3298
3638
  var DEFAULT_TIMEOUT_MS = 3e5;
3299
3639
  var ClaudeCodeCommunicator = class {
3300
3640
  executable;
@@ -3303,12 +3643,13 @@ var ClaudeCodeCommunicator = class {
3303
3643
  }
3304
3644
  async runPrompt(workspaceDir, prompt, options) {
3305
3645
  const args = this.buildArgs(prompt, options, "json");
3306
- logger20.debug({ workspaceDir, args }, "Running claude-code prompt");
3646
+ logger21.debug({ workspaceDir, args }, "Running claude-code prompt");
3307
3647
  return new Promise((resolve3, reject) => {
3308
3648
  const child = spawn4(this.executable, args, {
3309
3649
  cwd: workspaceDir,
3310
3650
  stdio: ["pipe", "pipe", "pipe"],
3311
- env: { ...process.env }
3651
+ env: { ...process.env },
3652
+ windowsHide: true
3312
3653
  });
3313
3654
  let stdout = "";
3314
3655
  let stderr = "";
@@ -3326,7 +3667,7 @@ var ClaudeCodeCommunicator = class {
3326
3667
  child.on("close", (code) => {
3327
3668
  clearTimeout(timer);
3328
3669
  if (code !== 0) {
3329
- logger20.warn({ code, stderr: stderr.slice(0, 500) }, "claude-code exited with error");
3670
+ logger21.warn({ code, stderr: stderr.slice(0, 500) }, "claude-code exited with error");
3330
3671
  reject(new Error(`claude-code exited with code ${code}: ${stderr.slice(0, 500)}`));
3331
3672
  return;
3332
3673
  }
@@ -3336,7 +3677,7 @@ var ClaudeCodeCommunicator = class {
3336
3677
  const sessionId = parsed["session_id"];
3337
3678
  const subtype = parsed["subtype"];
3338
3679
  if (subtype && subtype !== "success") {
3339
- logger20.warn({ subtype }, "claude-code returned non-success subtype");
3680
+ logger21.warn({ subtype }, "claude-code returned non-success subtype");
3340
3681
  }
3341
3682
  let text;
3342
3683
  if (result && result.length > 0) {
@@ -3344,11 +3685,18 @@ var ClaudeCodeCommunicator = class {
3344
3685
  } else if (subtype === "error_max_turns") {
3345
3686
  text = "[max turns reached \u2014 no final result text]";
3346
3687
  } else {
3347
- text = stdout.trim();
3688
+ const costUsd = parsed["cost_usd"];
3689
+ const duration = parsed["duration_ms"];
3690
+ const parts = ["[no result text]"];
3691
+ if (subtype) parts.push(`subtype=${subtype}`);
3692
+ if (costUsd != null) parts.push(`cost=$${costUsd.toFixed(4)}`);
3693
+ if (duration != null) parts.push(`duration=${duration}ms`);
3694
+ text = parts.join(" ");
3348
3695
  }
3349
3696
  resolve3({ text, sessionId });
3350
3697
  } catch {
3351
- resolve3({ text: stdout.trim() });
3698
+ const firstLine = stdout.slice(0, 200).split("\n")[0] ?? "";
3699
+ resolve3({ text: firstLine || "[unparseable response]" });
3352
3700
  }
3353
3701
  });
3354
3702
  child.on("error", (err) => {
@@ -3360,11 +3708,12 @@ var ClaudeCodeCommunicator = class {
3360
3708
  }
3361
3709
  async *streamPrompt(workspaceDir, prompt, options) {
3362
3710
  const args = this.buildArgs(prompt, options, "stream-json");
3363
- logger20.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3711
+ logger21.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3364
3712
  const child = spawn4(this.executable, args, {
3365
3713
  cwd: workspaceDir,
3366
3714
  stdio: ["pipe", "pipe", "pipe"],
3367
- env: { ...process.env }
3715
+ env: { ...process.env },
3716
+ windowsHide: true
3368
3717
  });
3369
3718
  const timeout = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3370
3719
  const timer = setTimeout(() => {
@@ -3457,11 +3806,11 @@ function parseStreamEvent(event) {
3457
3806
  }
3458
3807
 
3459
3808
  // src/communicator/cursor-communicator.ts
3460
- import { createLogger as createLogger22 } from "@actant/shared";
3461
- var logger21 = createLogger22("cursor-communicator");
3809
+ import { createLogger as createLogger23 } from "@actant/shared";
3810
+ var logger22 = createLogger23("cursor-communicator");
3462
3811
  var CursorCommunicator = class {
3463
3812
  async runPrompt(_workspaceDir, _prompt, _options) {
3464
- logger21.warn("Cursor backend does not yet support programmatic communication");
3813
+ logger22.warn("Cursor backend does not yet support programmatic communication");
3465
3814
  throw new Error(
3466
3815
  "Cursor backend does not support programmatic communication yet. Use claude-code backend for agent.run / agent.chat functionality."
3467
3816
  );
@@ -3475,12 +3824,12 @@ var CursorCommunicator = class {
3475
3824
  };
3476
3825
 
3477
3826
  // src/communicator/create-communicator.ts
3478
- var registry2 = /* @__PURE__ */ new Map();
3827
+ var registry = /* @__PURE__ */ new Map();
3479
3828
  function registerCommunicator(backendType, factory) {
3480
- registry2.set(backendType, factory);
3829
+ registry.set(backendType, factory);
3481
3830
  }
3482
3831
  function createCommunicator(backendType, backendConfig) {
3483
- const registered = registry2.get(backendType);
3832
+ const registered = registry.get(backendType);
3484
3833
  if (registered) return registered(backendConfig);
3485
3834
  switch (backendType) {
3486
3835
  case "claude-code":
@@ -3488,19 +3837,15 @@ function createCommunicator(backendType, backendConfig) {
3488
3837
  case "cursor":
3489
3838
  case "cursor-agent":
3490
3839
  return new CursorCommunicator();
3491
- case "custom":
3840
+ default:
3492
3841
  throw new Error(
3493
- "Custom backend communicator not yet supported. Implement AgentCommunicator for your backend."
3494
- );
3495
- case "pi":
3496
- throw new Error(
3497
- "Pi backend communicator not registered. Ensure @actant/pi is installed and initialized."
3842
+ `No communicator registered for backend "${backendType}". Register one via registerCommunicator() at startup.`
3498
3843
  );
3499
3844
  }
3500
3845
  }
3501
3846
 
3502
3847
  // src/manager/agent-manager.ts
3503
- var logger22 = createLogger23("agent-manager");
3848
+ var logger23 = createLogger24("agent-manager");
3504
3849
  var AgentManager = class {
3505
3850
  constructor(initializer, launcher, instancesBaseDir, options) {
3506
3851
  this.initializer = initializer;
@@ -3541,7 +3886,7 @@ var AgentManager = class {
3541
3886
  const dir = join17(this.instancesBaseDir, meta.name);
3542
3887
  const fixed = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3543
3888
  this.cache.set(meta.name, fixed);
3544
- logger22.info({ name: meta.name, oldStatus: meta.status, launchMode: meta.launchMode, recoveryAction: action.type }, "Stale status corrected");
3889
+ logger23.info({ name: meta.name, oldStatus: meta.status, launchMode: meta.launchMode, recoveryAction: action.type }, "Stale status corrected");
3545
3890
  if (action.type === "restart") {
3546
3891
  pendingRestarts.push(meta.name);
3547
3892
  }
@@ -3553,7 +3898,7 @@ var AgentManager = class {
3553
3898
  await this.moveToCorrupted(name);
3554
3899
  }
3555
3900
  this.watcher.start();
3556
- logger22.info({
3901
+ logger23.info({
3557
3902
  valid: valid.length,
3558
3903
  corrupted: corrupted.length,
3559
3904
  pendingRestarts: pendingRestarts.length
@@ -3561,9 +3906,9 @@ var AgentManager = class {
3561
3906
  for (const name of pendingRestarts) {
3562
3907
  try {
3563
3908
  await this.startAgent(name);
3564
- logger22.info({ name }, "Recovery restart succeeded");
3909
+ logger23.info({ name }, "Recovery restart succeeded");
3565
3910
  } catch (err) {
3566
- logger22.error({ name, error: err }, "Recovery restart failed");
3911
+ logger23.error({ name, error: err }, "Recovery restart failed");
3567
3912
  }
3568
3913
  }
3569
3914
  }
@@ -3613,14 +3958,19 @@ var AgentManager = class {
3613
3958
  pid = proc.pid;
3614
3959
  }
3615
3960
  if (this.acpManager) {
3616
- const { command, args } = resolveAcpBackend(meta.backendType, dir, meta.backendConfig);
3961
+ const acpResolved = resolveAcpBackend(meta.backendType, dir, meta.backendConfig);
3962
+ const providerEnv = buildProviderEnv(meta.providerConfig);
3617
3963
  const connResult = await this.acpManager.connect(name, {
3618
- command,
3619
- args,
3964
+ command: acpResolved.command,
3965
+ args: acpResolved.args,
3620
3966
  cwd: dir,
3621
- connectionOptions: { autoApprove: true }
3967
+ resolvePackage: acpResolved.resolvePackage,
3968
+ connectionOptions: {
3969
+ autoApprove: true,
3970
+ ...Object.keys(providerEnv).length > 0 ? { env: providerEnv } : {}
3971
+ }
3622
3972
  });
3623
- logger22.info({ name, acpOnly }, "ACP connection established");
3973
+ logger23.info({ name, acpOnly, providerType: meta.providerConfig?.type }, "ACP connection established");
3624
3974
  if (acpOnly && "pid" in connResult && typeof connResult.pid === "number") {
3625
3975
  pid = connResult.pid;
3626
3976
  this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
@@ -3632,7 +3982,7 @@ var AgentManager = class {
3632
3982
  this.watcher.watch(name, pid);
3633
3983
  }
3634
3984
  this.restartTracker.recordStart(name);
3635
- logger22.info({ name, pid, launchMode: starting.launchMode, acp: true }, "Agent started");
3985
+ logger23.info({ name, pid, launchMode: starting.launchMode, acp: true }, "Agent started");
3636
3986
  } catch (err) {
3637
3987
  if (this.acpManager?.has(name)) {
3638
3988
  await this.acpManager.disconnect(name).catch(() => {
@@ -3640,7 +3990,11 @@ var AgentManager = class {
3640
3990
  }
3641
3991
  const errored = await updateInstanceMeta(dir, { status: "error" });
3642
3992
  this.cache.set(name, errored);
3643
- throw err;
3993
+ if (err instanceof AgentLaunchError) throw err;
3994
+ const spawnMsg = err instanceof Error ? err.message : String(err);
3995
+ throw new AgentLaunchError(name, new Error(
3996
+ isSpawnNotFound(spawnMsg) ? buildSpawnNotFoundMessage(meta.backendType) : spawnMsg
3997
+ ));
3644
3998
  }
3645
3999
  }
3646
4000
  /**
@@ -3651,7 +4005,7 @@ var AgentManager = class {
3651
4005
  const meta = this.requireAgent(name);
3652
4006
  const dir = join17(this.instancesBaseDir, name);
3653
4007
  if (meta.status !== "running" && meta.status !== "starting") {
3654
- logger22.warn({ name, status: meta.status }, "Agent is not running, setting to stopped");
4008
+ logger23.warn({ name, status: meta.status }, "Agent is not running, setting to stopped");
3655
4009
  const stopped2 = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3656
4010
  this.cache.set(name, stopped2);
3657
4011
  return;
@@ -3661,7 +4015,7 @@ var AgentManager = class {
3661
4015
  this.cache.set(name, stopping);
3662
4016
  if (this.acpManager?.has(name)) {
3663
4017
  await this.acpManager.disconnect(name).catch((err) => {
3664
- logger22.warn({ name, error: err }, "Error disconnecting ACP during stop");
4018
+ logger23.warn({ name, error: err }, "Error disconnecting ACP during stop");
3665
4019
  });
3666
4020
  }
3667
4021
  const proc = this.processes.get(name);
@@ -3671,11 +4025,14 @@ var AgentManager = class {
3671
4025
  }
3672
4026
  const stopped = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3673
4027
  this.cache.set(name, stopped);
3674
- logger22.info({ name }, "Agent stopped");
4028
+ logger23.info({ name }, "Agent stopped");
3675
4029
  }
3676
4030
  /** Destroy an agent — stop it if running, then remove workspace. */
3677
4031
  async destroyAgent(name) {
3678
4032
  const meta = this.cache.get(name);
4033
+ if (!meta && !existsSync(join17(this.instancesBaseDir, name))) {
4034
+ throw new AgentNotFoundError(name);
4035
+ }
3679
4036
  if (meta && (meta.status === "running" || meta.status === "starting")) {
3680
4037
  await this.stopAgent(name);
3681
4038
  }
@@ -3684,7 +4041,7 @@ var AgentManager = class {
3684
4041
  await this.initializer.destroyInstance(name);
3685
4042
  this.cache.delete(name);
3686
4043
  this.processes.delete(name);
3687
- logger22.info({ name }, "Agent destroyed");
4044
+ logger23.info({ name }, "Agent destroyed");
3688
4045
  }
3689
4046
  /** Get agent metadata by name. */
3690
4047
  getAgent(name) {
@@ -3717,14 +4074,15 @@ var AgentManager = class {
3717
4074
  throw new AgentNotFoundError(name);
3718
4075
  }
3719
4076
  const dir = join17(this.instancesBaseDir, name);
3720
- const { command, args } = resolveBackend(meta.backendType, dir, meta.backendConfig);
4077
+ const resolved = resolveBackend(meta.backendType, dir, meta.backendConfig);
3721
4078
  return {
3722
4079
  workspaceDir: dir,
3723
- command,
3724
- args,
4080
+ command: resolved.command,
4081
+ args: resolved.args,
3725
4082
  instanceName: name,
3726
4083
  backendType: meta.backendType,
3727
- created
4084
+ created,
4085
+ resolvePackage: resolved.resolvePackage
3728
4086
  };
3729
4087
  }
3730
4088
  /**
@@ -3735,8 +4093,7 @@ var AgentManager = class {
3735
4093
  async openAgent(name) {
3736
4094
  const meta = this.requireAgent(name);
3737
4095
  const dir = join17(this.instancesBaseDir, name);
3738
- const resolved = openBackend(meta.backendType, dir);
3739
- return resolved;
4096
+ return openBackend(meta.backendType, dir);
3740
4097
  }
3741
4098
  /**
3742
4099
  * Register an externally-spawned process with the manager.
@@ -3749,6 +4106,17 @@ var AgentManager = class {
3749
4106
  if (meta.status === "running" && meta.pid != null) {
3750
4107
  throw new AgentAlreadyAttachedError(name);
3751
4108
  }
4109
+ try {
4110
+ process.kill(pid, 0);
4111
+ } catch (err) {
4112
+ const code = err.code;
4113
+ if (code === "ESRCH") {
4114
+ throw new AgentLaunchError(
4115
+ name,
4116
+ new Error(`Process with PID ${pid} does not exist`)
4117
+ );
4118
+ }
4119
+ }
3752
4120
  const dir = join17(this.instancesBaseDir, name);
3753
4121
  const mergedMetadata = attachMetadata ? { ...meta.metadata, ...attachMetadata } : meta.metadata;
3754
4122
  const updated = await updateInstanceMeta(dir, {
@@ -3760,7 +4128,7 @@ var AgentManager = class {
3760
4128
  this.cache.set(name, updated);
3761
4129
  this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
3762
4130
  this.watcher.watch(name, pid);
3763
- logger22.info({ name, pid }, "External process attached");
4131
+ logger23.info({ name, pid }, "External process attached");
3764
4132
  return updated;
3765
4133
  }
3766
4134
  /**
@@ -3783,7 +4151,7 @@ var AgentManager = class {
3783
4151
  processOwnership: "managed"
3784
4152
  });
3785
4153
  this.cache.set(name, updated);
3786
- logger22.info({ name }, "External process detached");
4154
+ logger23.info({ name }, "External process detached");
3787
4155
  let workspaceCleaned = false;
3788
4156
  if (options?.cleanup && meta.workspacePolicy === "ephemeral") {
3789
4157
  await this.destroyAgent(name);
@@ -3801,7 +4169,7 @@ var AgentManager = class {
3801
4169
  const conn = this.acpManager.getConnection(name);
3802
4170
  const sessionId = this.acpManager.getPrimarySessionId(name);
3803
4171
  if (conn && sessionId) {
3804
- logger22.debug({ name, sessionId }, "Sending prompt via ACP");
4172
+ logger23.debug({ name, sessionId }, "Sending prompt via ACP");
3805
4173
  const result = await conn.prompt(sessionId, prompt);
3806
4174
  return { text: result.text, sessionId };
3807
4175
  }
@@ -3820,7 +4188,7 @@ var AgentManager = class {
3820
4188
  const conn = this.acpManager.getConnection(name);
3821
4189
  const sessionId = this.acpManager.getPrimarySessionId(name);
3822
4190
  if (conn && sessionId) {
3823
- logger22.debug({ name, sessionId }, "Streaming prompt via ACP");
4191
+ logger23.debug({ name, sessionId }, "Streaming prompt via ACP");
3824
4192
  return this.streamFromAcp(conn, sessionId, prompt);
3825
4193
  }
3826
4194
  }
@@ -3892,7 +4260,7 @@ var AgentManager = class {
3892
4260
  }
3893
4261
  const handler = getLaunchModeHandler(meta.launchMode);
3894
4262
  const action = handler.getProcessExitAction(instanceName, meta);
3895
- logger22.warn({ instanceName, pid, launchMode: meta.launchMode, action: action.type, previousStatus: meta.status }, "Agent process exited unexpectedly");
4263
+ logger23.warn({ instanceName, pid, launchMode: meta.launchMode, action: action.type, previousStatus: meta.status }, "Agent process exited unexpectedly");
3896
4264
  if (this.acpManager?.has(instanceName)) {
3897
4265
  await this.acpManager.disconnect(instanceName).catch(() => {
3898
4266
  });
@@ -3913,28 +4281,28 @@ var AgentManager = class {
3913
4281
  if (!decision.allowed) {
3914
4282
  const errored = await updateInstanceMeta(dir, { status: "error" });
3915
4283
  this.cache.set(instanceName, errored);
3916
- logger22.error({ instanceName, attempt: decision.attempt }, "Restart limit exceeded \u2014 marking as error");
4284
+ logger23.error({ instanceName, attempt: decision.attempt }, "Restart limit exceeded \u2014 marking as error");
3917
4285
  break;
3918
4286
  }
3919
- logger22.info({ instanceName, attempt: decision.attempt, delayMs: decision.delayMs }, "Scheduling crash restart with backoff");
4287
+ logger23.info({ instanceName, attempt: decision.attempt, delayMs: decision.delayMs }, "Scheduling crash restart with backoff");
3920
4288
  if (decision.delayMs > 0) {
3921
4289
  await delay(decision.delayMs);
3922
4290
  }
3923
4291
  this.restartTracker.recordRestart(instanceName);
3924
4292
  try {
3925
4293
  await this.startAgent(instanceName);
3926
- logger22.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
4294
+ logger23.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
3927
4295
  } catch (err) {
3928
- logger22.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
4296
+ logger23.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
3929
4297
  }
3930
4298
  break;
3931
4299
  }
3932
4300
  case "destroy":
3933
4301
  try {
3934
4302
  await this.destroyAgent(instanceName);
3935
- logger22.info({ instanceName }, "One-shot agent destroyed after exit");
4303
+ logger23.info({ instanceName }, "One-shot agent destroyed after exit");
3936
4304
  } catch (err) {
3937
- logger22.error({ instanceName, error: err }, "One-shot auto-destroy failed");
4305
+ logger23.error({ instanceName, error: err }, "One-shot auto-destroy failed");
3938
4306
  }
3939
4307
  break;
3940
4308
  case "mark-stopped":
@@ -3954,12 +4322,39 @@ var AgentManager = class {
3954
4322
  const src = join17(this.instancesBaseDir, name);
3955
4323
  const dest = join17(this.corruptedDir, `${name}-${Date.now()}`);
3956
4324
  await rename3(src, dest);
3957
- logger22.warn({ name, dest }, "Corrupted instance moved");
4325
+ logger23.warn({ name, dest }, "Corrupted instance moved");
3958
4326
  } catch (err) {
3959
- logger22.error({ name, error: err }, "Failed to move corrupted instance");
4327
+ logger23.error({ name, error: err }, "Failed to move corrupted instance");
3960
4328
  }
3961
4329
  }
3962
4330
  };
4331
+ function buildProviderEnv(providerConfig) {
4332
+ const env = {};
4333
+ const defaultDesc = modelProviderRegistry.getDefault();
4334
+ const providerType = providerConfig?.type ?? defaultDesc?.type;
4335
+ if (providerType) {
4336
+ env["ACTANT_PROVIDER"] = providerType;
4337
+ }
4338
+ const descriptor = providerType ? modelProviderRegistry.get(providerType) : defaultDesc;
4339
+ const apiKey = descriptor?.apiKey ?? process.env["ACTANT_API_KEY"];
4340
+ if (apiKey) {
4341
+ env["ACTANT_API_KEY"] = apiKey;
4342
+ }
4343
+ const baseUrl = providerConfig?.baseUrl ?? descriptor?.defaultBaseUrl;
4344
+ if (baseUrl) {
4345
+ env["ACTANT_BASE_URL"] = baseUrl;
4346
+ }
4347
+ return env;
4348
+ }
4349
+ function isSpawnNotFound(msg) {
4350
+ return /ENOENT|EINVAL|is not recognized|not found/i.test(msg);
4351
+ }
4352
+ function buildSpawnNotFoundMessage(backendType) {
4353
+ const hint = getInstallHint(backendType);
4354
+ const base = `Backend "${backendType}" executable not found.`;
4355
+ return hint ? `${base}
4356
+ Install with: ${hint}` : `${base} Ensure the required CLI is installed and in your PATH.`;
4357
+ }
3963
4358
 
3964
4359
  // src/manager/launcher/mock-launcher.ts
3965
4360
  var nextPid = 1e4;
@@ -3982,14 +4377,14 @@ var MockLauncher = class {
3982
4377
 
3983
4378
  // src/manager/launcher/process-launcher.ts
3984
4379
  import { spawn as spawn5 } from "child_process";
3985
- import { AgentLaunchError, createLogger as createLogger25 } from "@actant/shared";
4380
+ import { AgentLaunchError as AgentLaunchError2, createLogger as createLogger26 } from "@actant/shared";
3986
4381
 
3987
4382
  // src/manager/launcher/process-log-writer.ts
3988
4383
  import { createWriteStream } from "fs";
3989
4384
  import { mkdir as mkdir12, rename as rename4, stat as stat7, readFile as readFile5 } from "fs/promises";
3990
4385
  import { join as join18 } from "path";
3991
- import { createLogger as createLogger24 } from "@actant/shared";
3992
- var logger23 = createLogger24("process-log-writer");
4386
+ import { createLogger as createLogger25 } from "@actant/shared";
4387
+ var logger24 = createLogger25("process-log-writer");
3993
4388
  var DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
3994
4389
  var DEFAULT_MAX_FILES = 3;
3995
4390
  var ProcessLogWriter = class {
@@ -4026,7 +4421,7 @@ var ProcessLogWriter = class {
4026
4421
  } catch {
4027
4422
  this.stderrBytes = 0;
4028
4423
  }
4029
- logger23.debug({ logsDir: this.logsDir }, "Log writer initialized");
4424
+ logger24.debug({ logsDir: this.logsDir }, "Log writer initialized");
4030
4425
  }
4031
4426
  /**
4032
4427
  * Attach to readable streams from a spawned process.
@@ -4108,7 +4503,7 @@ var ProcessLogWriter = class {
4108
4503
  this.stderrStream = newStream;
4109
4504
  this.stderrBytes = 0;
4110
4505
  }
4111
- logger23.debug({ filename }, "Log file rotated");
4506
+ logger24.debug({ filename }, "Log file rotated");
4112
4507
  }
4113
4508
  closeStream(stream) {
4114
4509
  if (!stream) return Promise.resolve();
@@ -4119,7 +4514,7 @@ var ProcessLogWriter = class {
4119
4514
  };
4120
4515
 
4121
4516
  // src/manager/launcher/process-launcher.ts
4122
- var logger24 = createLogger25("process-launcher");
4517
+ var logger25 = createLogger26("process-launcher");
4123
4518
  var DEFAULT_TERMINATE_TIMEOUT = 5e3;
4124
4519
  var DEFAULT_SPAWN_VERIFY_DELAY = 500;
4125
4520
  var ProcessLauncher = class {
@@ -4141,7 +4536,7 @@ var ProcessLauncher = class {
4141
4536
  meta.backendConfig
4142
4537
  );
4143
4538
  const useAcp = isAcpBackend(meta.backendType);
4144
- logger24.info({ name: meta.name, command, args, backendType: meta.backendType, acp: useAcp }, "Spawning backend process");
4539
+ logger25.info({ name: meta.name, command, args, backendType: meta.backendType, acp: useAcp }, "Spawning backend process");
4145
4540
  const captureNonAcpLogs = !useAcp && this.enableProcessLogs;
4146
4541
  let stdio;
4147
4542
  if (useAcp) {
@@ -4173,7 +4568,7 @@ var ProcessLauncher = class {
4173
4568
  }
4174
4569
  });
4175
4570
  if ("error" in spawnResult) {
4176
- throw new AgentLaunchError(meta.name, spawnResult.error);
4571
+ throw new AgentLaunchError2(meta.name, spawnResult.error);
4177
4572
  }
4178
4573
  const pid = spawnResult.pid;
4179
4574
  if (!useAcp) {
@@ -4184,27 +4579,27 @@ var ProcessLauncher = class {
4184
4579
  earlyExit = true;
4185
4580
  });
4186
4581
  child.on("error", (err) => {
4187
- logger24.error({ name: meta.name, pid, error: err }, "Backend process error after spawn");
4582
+ logger25.error({ name: meta.name, pid, error: err }, "Backend process error after spawn");
4188
4583
  });
4189
4584
  if (this.spawnVerifyDelayMs > 0) {
4190
4585
  await delay(this.spawnVerifyDelayMs);
4191
4586
  if (earlyExit || !isProcessAlive(pid)) {
4192
- throw new AgentLaunchError(
4587
+ throw new AgentLaunchError2(
4193
4588
  meta.name,
4194
4589
  new Error(`Process exited immediately after spawn (pid=${pid}, command=${command})`)
4195
4590
  );
4196
4591
  }
4197
4592
  }
4198
- logger24.info({ name: meta.name, pid, command, acp: useAcp }, "Backend process spawned");
4593
+ logger25.info({ name: meta.name, pid, command, acp: useAcp }, "Backend process spawned");
4199
4594
  if (captureNonAcpLogs && child.stdout && child.stderr) {
4200
4595
  const logWriter = new ProcessLogWriter(workspaceDir, this.logWriterOptions);
4201
4596
  try {
4202
4597
  await logWriter.initialize();
4203
4598
  logWriter.attach(child.stdout, child.stderr);
4204
4599
  this.logWriters.set(meta.name, logWriter);
4205
- logger24.debug({ name: meta.name }, "Process log capture enabled");
4600
+ logger25.debug({ name: meta.name }, "Process log capture enabled");
4206
4601
  } catch (err) {
4207
- logger24.warn({ name: meta.name, error: err }, "Failed to initialize log writer, continuing without log capture");
4602
+ logger25.warn({ name: meta.name, error: err }, "Failed to initialize log writer, continuing without log capture");
4208
4603
  }
4209
4604
  }
4210
4605
  const result = {
@@ -4233,27 +4628,27 @@ var ProcessLauncher = class {
4233
4628
  this.logWriters.delete(instanceName);
4234
4629
  }
4235
4630
  if (!isProcessAlive(pid)) {
4236
- logger24.info({ instanceName, pid }, "Process already exited");
4631
+ logger25.info({ instanceName, pid }, "Process already exited");
4237
4632
  return;
4238
4633
  }
4239
- logger24.info({ instanceName, pid }, "Sending SIGTERM");
4634
+ logger25.info({ instanceName, pid }, "Sending SIGTERM");
4240
4635
  sendSignal(pid, "SIGTERM");
4241
4636
  const deadline = Date.now() + this.terminateTimeoutMs;
4242
4637
  const pollInterval = 200;
4243
4638
  while (Date.now() < deadline) {
4244
4639
  await delay(pollInterval);
4245
4640
  if (!isProcessAlive(pid)) {
4246
- logger24.info({ instanceName, pid }, "Process terminated gracefully");
4641
+ logger25.info({ instanceName, pid }, "Process terminated gracefully");
4247
4642
  return;
4248
4643
  }
4249
4644
  }
4250
- logger24.warn({ instanceName, pid }, "Process did not exit after SIGTERM, sending SIGKILL");
4645
+ logger25.warn({ instanceName, pid }, "Process did not exit after SIGTERM, sending SIGKILL");
4251
4646
  sendSignal(pid, "SIGKILL");
4252
4647
  await delay(500);
4253
4648
  if (isProcessAlive(pid)) {
4254
- logger24.error({ instanceName, pid }, "Process still alive after SIGKILL");
4649
+ logger25.error({ instanceName, pid }, "Process still alive after SIGKILL");
4255
4650
  } else {
4256
- logger24.info({ instanceName, pid }, "Process killed with SIGKILL");
4651
+ logger25.info({ instanceName, pid }, "Process killed with SIGKILL");
4257
4652
  }
4258
4653
  }
4259
4654
  };
@@ -4270,12 +4665,15 @@ function createLauncher(config) {
4270
4665
  }
4271
4666
 
4272
4667
  // src/domain/skill/skill-manager.ts
4273
- import { z as z4 } from "zod/v4";
4274
- var SkillDefinitionSchema = z4.object({
4275
- name: z4.string().min(1),
4276
- description: z4.string().optional(),
4277
- content: z4.string().min(1),
4278
- tags: z4.array(z4.string()).optional()
4668
+ import { z as z5 } from "zod/v4";
4669
+ var SkillDefinitionSchema = z5.object({
4670
+ name: z5.string().min(1),
4671
+ description: z5.string().optional(),
4672
+ content: z5.string().min(1),
4673
+ tags: z5.array(z5.string()).optional(),
4674
+ license: z5.string().optional(),
4675
+ compatibility: z5.string().optional(),
4676
+ allowedTools: z5.array(z5.string()).optional()
4279
4677
  }).passthrough();
4280
4678
  var SkillManager = class extends BaseComponentManager {
4281
4679
  componentType = "Skill";
@@ -4315,12 +4713,12 @@ ${sections.join("\n\n---\n\n")}
4315
4713
  };
4316
4714
 
4317
4715
  // src/domain/prompt/prompt-manager.ts
4318
- import { z as z5 } from "zod/v4";
4319
- var PromptDefinitionSchema = z5.object({
4320
- name: z5.string().min(1),
4321
- description: z5.string().optional(),
4322
- content: z5.string().min(1),
4323
- variables: z5.array(z5.string()).optional()
4716
+ import { z as z6 } from "zod/v4";
4717
+ var PromptDefinitionSchema = z6.object({
4718
+ name: z6.string().min(1),
4719
+ description: z6.string().optional(),
4720
+ content: z6.string().min(1),
4721
+ variables: z6.array(z6.string()).optional()
4324
4722
  }).passthrough();
4325
4723
  var PromptManager = class extends BaseComponentManager {
4326
4724
  componentType = "Prompt";
@@ -4372,13 +4770,13 @@ ${sections.join("\n\n---\n\n")}
4372
4770
  };
4373
4771
 
4374
4772
  // src/domain/mcp/mcp-config-manager.ts
4375
- import { z as z6 } from "zod/v4";
4376
- var McpServerDefinitionSchema = z6.object({
4377
- name: z6.string().min(1),
4378
- description: z6.string().optional(),
4379
- command: z6.string().min(1),
4380
- args: z6.array(z6.string()).optional(),
4381
- env: z6.record(z6.string(), z6.string()).optional()
4773
+ import { z as z7 } from "zod/v4";
4774
+ var McpServerDefinitionSchema = z7.object({
4775
+ name: z7.string().min(1),
4776
+ description: z7.string().optional(),
4777
+ command: z7.string().min(1),
4778
+ args: z7.array(z7.string()).optional(),
4779
+ env: z7.record(z7.string(), z7.string()).optional()
4382
4780
  }).passthrough();
4383
4781
  var McpConfigManager = class extends BaseComponentManager {
4384
4782
  componentType = "McpServer";
@@ -4418,11 +4816,11 @@ var McpConfigManager = class extends BaseComponentManager {
4418
4816
  };
4419
4817
 
4420
4818
  // src/domain/workflow/workflow-manager.ts
4421
- import { z as z7 } from "zod/v4";
4422
- var WorkflowDefinitionSchema = z7.object({
4423
- name: z7.string().min(1),
4424
- description: z7.string().optional(),
4425
- content: z7.string().min(1)
4819
+ import { z as z8 } from "zod/v4";
4820
+ var WorkflowDefinitionSchema = z8.object({
4821
+ name: z8.string().min(1),
4822
+ description: z8.string().optional(),
4823
+ content: z8.string().min(1)
4426
4824
  }).passthrough();
4427
4825
  var WorkflowManager = class extends BaseComponentManager {
4428
4826
  componentType = "Workflow";
@@ -4451,14 +4849,14 @@ var WorkflowManager = class extends BaseComponentManager {
4451
4849
  };
4452
4850
 
4453
4851
  // src/domain/plugin/plugin-manager.ts
4454
- import { z as z8 } from "zod/v4";
4455
- var PluginDefinitionSchema = z8.object({
4456
- name: z8.string().min(1),
4457
- description: z8.string().optional(),
4458
- type: z8.enum(["npm", "file", "config"]),
4459
- source: z8.string().optional(),
4460
- config: z8.record(z8.string(), z8.unknown()).optional(),
4461
- enabled: z8.boolean().optional().default(true)
4852
+ import { z as z9 } from "zod/v4";
4853
+ var PluginDefinitionSchema = z9.object({
4854
+ name: z9.string().min(1),
4855
+ description: z9.string().optional(),
4856
+ type: z9.enum(["npm", "file", "config"]),
4857
+ source: z9.string().optional(),
4858
+ config: z9.record(z9.string(), z9.unknown()).optional(),
4859
+ enabled: z9.boolean().optional().default(true)
4462
4860
  }).passthrough();
4463
4861
  var PluginManager = class extends BaseComponentManager {
4464
4862
  componentType = "Plugin";
@@ -4503,8 +4901,8 @@ var PluginManager = class extends BaseComponentManager {
4503
4901
  };
4504
4902
 
4505
4903
  // src/permissions/permission-policy-enforcer.ts
4506
- import { createLogger as createLogger26 } from "@actant/shared";
4507
- var logger25 = createLogger26("permission-policy-enforcer");
4904
+ import { createLogger as createLogger27 } from "@actant/shared";
4905
+ var logger26 = createLogger27("permission-policy-enforcer");
4508
4906
  var KIND_TO_TOOL = {
4509
4907
  read: "Read",
4510
4908
  edit: "Edit",
@@ -4521,7 +4919,7 @@ var PermissionPolicyEnforcer = class {
4521
4919
  }
4522
4920
  updateConfig(config) {
4523
4921
  this.config = { ...config };
4524
- logger25.info("Permission policy config updated");
4922
+ logger26.info("Permission policy config updated");
4525
4923
  }
4526
4924
  getConfig() {
4527
4925
  return this.config;
@@ -4679,8 +5077,8 @@ function escapeRegex(str) {
4679
5077
  }
4680
5078
 
4681
5079
  // src/permissions/permission-audit.ts
4682
- import { createLogger as createLogger27 } from "@actant/shared";
4683
- var logger26 = createLogger27("permission-audit");
5080
+ import { createLogger as createLogger28 } from "@actant/shared";
5081
+ var logger27 = createLogger28("permission-audit");
4684
5082
  var PermissionAuditLogger = class {
4685
5083
  instanceName;
4686
5084
  constructor(instanceName) {
@@ -4693,7 +5091,7 @@ var PermissionAuditLogger = class {
4693
5091
  ...detail,
4694
5092
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4695
5093
  };
4696
- logger26.info(entry, `[audit] ${event}`);
5094
+ logger27.info(entry, `[audit] ${event}`);
4697
5095
  }
4698
5096
  logEvaluation(toolCall, decision) {
4699
5097
  this.log("permission.evaluated", { toolCall, decision });
@@ -4714,8 +5112,8 @@ var PermissionAuditLogger = class {
4714
5112
 
4715
5113
  // src/session/session-registry.ts
4716
5114
  import { randomUUID as randomUUID8 } from "crypto";
4717
- import { createLogger as createLogger28 } from "@actant/shared";
4718
- var logger27 = createLogger28("session-registry");
5115
+ import { createLogger as createLogger29 } from "@actant/shared";
5116
+ var logger28 = createLogger29("session-registry");
4719
5117
  var DEFAULT_IDLE_TTL_MS = 30 * 60 * 1e3;
4720
5118
  var TTL_CHECK_INTERVAL_MS = 60 * 1e3;
4721
5119
  var SessionRegistry = class {
@@ -4746,7 +5144,7 @@ var SessionRegistry = class {
4746
5144
  idleTtlMs: opts.idleTtlMs ?? this.defaultIdleTtlMs
4747
5145
  };
4748
5146
  this.sessions.set(session.sessionId, session);
4749
- logger27.info({ sessionId: session.sessionId, agentName: opts.agentName, clientId: opts.clientId }, "Session created");
5147
+ logger28.info({ sessionId: session.sessionId, agentName: opts.agentName, clientId: opts.clientId }, "Session created");
4750
5148
  return session;
4751
5149
  }
4752
5150
  /** Get a session by ID, or undefined if not found. */
@@ -4778,7 +5176,7 @@ var SessionRegistry = class {
4778
5176
  session.clientId = null;
4779
5177
  session.state = "idle";
4780
5178
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4781
- logger27.info({ sessionId, agentName: session.agentName }, "Session released to idle");
5179
+ logger28.info({ sessionId, agentName: session.agentName }, "Session released to idle");
4782
5180
  }
4783
5181
  /**
4784
5182
  * Resume an idle session, binding it to a (potentially new) client.
@@ -4791,7 +5189,7 @@ var SessionRegistry = class {
4791
5189
  session.clientId = clientId;
4792
5190
  session.state = "active";
4793
5191
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4794
- logger27.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
5192
+ logger28.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
4795
5193
  return true;
4796
5194
  }
4797
5195
  /** Explicitly close and remove a session. */
@@ -4799,7 +5197,7 @@ var SessionRegistry = class {
4799
5197
  const session = this.sessions.get(sessionId);
4800
5198
  if (!session) return false;
4801
5199
  this.sessions.delete(sessionId);
4802
- logger27.info({ sessionId, agentName: session.agentName }, "Session closed");
5200
+ logger28.info({ sessionId, agentName: session.agentName }, "Session closed");
4803
5201
  return true;
4804
5202
  }
4805
5203
  /** Close all sessions for a given agent. */
@@ -4812,7 +5210,7 @@ var SessionRegistry = class {
4812
5210
  }
4813
5211
  }
4814
5212
  if (count > 0) {
4815
- logger27.info({ agentName, count }, "Sessions closed for agent");
5213
+ logger28.info({ agentName, count }, "Sessions closed for agent");
4816
5214
  }
4817
5215
  return count;
4818
5216
  }
@@ -4836,7 +5234,7 @@ var SessionRegistry = class {
4836
5234
  if (now - lastActivity > session.idleTtlMs) {
4837
5235
  session.state = "expired";
4838
5236
  this.sessions.delete(id);
4839
- logger27.info({ sessionId: id, agentName: session.agentName, idleMs: now - lastActivity }, "Session expired (TTL)");
5237
+ logger28.info({ sessionId: id, agentName: session.agentName, idleMs: now - lastActivity }, "Session expired (TTL)");
4840
5238
  this.onExpireCallback?.(session);
4841
5239
  }
4842
5240
  }
@@ -4846,15 +5244,15 @@ var SessionRegistry = class {
4846
5244
  // src/source/github-source.ts
4847
5245
  import { join as join20 } from "path";
4848
5246
  import { mkdir as mkdir13, rm as rm5 } from "fs/promises";
4849
- import { execFile } from "child_process";
5247
+ import { execFile as execFile2 } from "child_process";
4850
5248
  import { promisify } from "util";
4851
- import { createLogger as createLogger30 } from "@actant/shared";
5249
+ import { createLogger as createLogger31 } from "@actant/shared";
4852
5250
 
4853
5251
  // src/source/local-source.ts
4854
5252
  import { readFile as readFile6, readdir as readdir5, stat as stat8 } from "fs/promises";
4855
5253
  import { join as join19, extname as extname3 } from "path";
4856
- import { createLogger as createLogger29 } from "@actant/shared";
4857
- var logger28 = createLogger29("local-source");
5254
+ import { createLogger as createLogger30 } from "@actant/shared";
5255
+ var logger29 = createLogger30("local-source");
4858
5256
  var LocalSource = class {
4859
5257
  type = "local";
4860
5258
  packageName;
@@ -4863,6 +5261,9 @@ var LocalSource = class {
4863
5261
  this.packageName = packageName;
4864
5262
  this.config = config;
4865
5263
  }
5264
+ getRootDir() {
5265
+ return this.config.path;
5266
+ }
4866
5267
  async fetch() {
4867
5268
  return this.loadPackage();
4868
5269
  }
@@ -4874,16 +5275,17 @@ var LocalSource = class {
4874
5275
  async loadPackage() {
4875
5276
  const rootDir = this.config.path;
4876
5277
  const manifest = await this.loadManifest(rootDir);
4877
- const [skills, prompts, mcpServers, workflows, presets, templates] = await Promise.all([
5278
+ const [skills, prompts, mcpServers, workflows, backends, presets, templates] = await Promise.all([
4878
5279
  this.loadJsonDir(rootDir, manifest.components?.skills, "skills"),
4879
5280
  this.loadJsonDir(rootDir, manifest.components?.prompts, "prompts"),
4880
5281
  this.loadJsonDir(rootDir, manifest.components?.mcp, "mcp"),
4881
5282
  this.loadJsonDir(rootDir, manifest.components?.workflows, "workflows"),
5283
+ this.loadJsonDir(rootDir, manifest.components?.backends, "backends"),
4882
5284
  this.loadPresets(rootDir, manifest.presets),
4883
5285
  this.loadJsonDir(rootDir, manifest.components?.templates, "templates")
4884
5286
  ]);
4885
- logger28.info({ packageName: this.packageName, rootDir }, "Local package loaded");
4886
- return { manifest, skills, prompts, mcpServers, workflows, presets, templates };
5287
+ logger29.info({ packageName: this.packageName, rootDir }, "Local package loaded");
5288
+ return { manifest, skills, prompts, mcpServers, workflows, backends, presets, templates };
4887
5289
  }
4888
5290
  async loadManifest(rootDir) {
4889
5291
  const manifestPath = join19(rootDir, "actant.json");
@@ -4891,7 +5293,7 @@ var LocalSource = class {
4891
5293
  const raw = await readFile6(manifestPath, "utf-8");
4892
5294
  return JSON.parse(raw);
4893
5295
  } catch {
4894
- logger28.debug({ rootDir }, "No actant.json found, scanning directories");
5296
+ logger29.debug({ rootDir }, "No actant.json found, scanning directories");
4895
5297
  return { name: this.packageName };
4896
5298
  }
4897
5299
  }
@@ -4903,7 +5305,7 @@ var LocalSource = class {
4903
5305
  const raw = await readFile6(join19(rootDir, relPath), "utf-8");
4904
5306
  items2.push(JSON.parse(raw));
4905
5307
  } catch (err) {
4906
- logger28.warn({ file: relPath, error: err }, `Failed to load from manifest, skipping`);
5308
+ logger29.warn({ file: relPath, error: err }, `Failed to load from manifest, skipping`);
4907
5309
  }
4908
5310
  }
4909
5311
  return items2;
@@ -4926,7 +5328,7 @@ var LocalSource = class {
4926
5328
  const raw = await readFile6(fullPath, "utf-8");
4927
5329
  items.push(JSON.parse(raw));
4928
5330
  } catch (err) {
4929
- logger28.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
5331
+ logger29.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
4930
5332
  }
4931
5333
  }
4932
5334
  for (const entry of entries) {
@@ -4951,7 +5353,7 @@ var LocalSource = class {
4951
5353
  }
4952
5354
  }
4953
5355
  if (subDir === "skills") {
4954
- const { parseSkillMd } = await import("./skill-md-parser-2HXC4AAW.js");
5356
+ const { parseSkillMd } = await import("./skill-md-parser-HXLTZAUU.js");
4955
5357
  for (const entry of entries) {
4956
5358
  if (extname3(entry) === ".json") continue;
4957
5359
  const subDirPath = join19(dirPath, entry);
@@ -4975,8 +5377,8 @@ var LocalSource = class {
4975
5377
  };
4976
5378
 
4977
5379
  // src/source/github-source.ts
4978
- var execFileAsync = promisify(execFile);
4979
- var logger29 = createLogger30("github-source");
5380
+ var execFileAsync = promisify(execFile2);
5381
+ var logger30 = createLogger31("github-source");
4980
5382
  var GitHubSource = class {
4981
5383
  type = "github";
4982
5384
  packageName;
@@ -4987,6 +5389,9 @@ var GitHubSource = class {
4987
5389
  this.config = config;
4988
5390
  this.cacheDir = join20(cacheDir, packageName);
4989
5391
  }
5392
+ getRootDir() {
5393
+ return this.cacheDir;
5394
+ }
4990
5395
  async fetch() {
4991
5396
  await this.clone();
4992
5397
  return this.readCached();
@@ -4995,21 +5400,21 @@ var GitHubSource = class {
4995
5400
  try {
4996
5401
  await this.pull();
4997
5402
  } catch {
4998
- logger29.info("Pull failed, re-cloning");
5403
+ logger30.info("Pull failed, re-cloning");
4999
5404
  await this.clone();
5000
5405
  }
5001
5406
  return this.readCached();
5002
5407
  }
5003
5408
  async dispose() {
5004
5409
  await rm5(this.cacheDir, { recursive: true, force: true });
5005
- logger29.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
5410
+ logger30.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
5006
5411
  }
5007
5412
  async clone() {
5008
5413
  await rm5(this.cacheDir, { recursive: true, force: true });
5009
5414
  await mkdir13(this.cacheDir, { recursive: true });
5010
5415
  const branch = this.config.branch ?? "main";
5011
5416
  const args = ["clone", "--depth", "1", "--branch", branch, this.config.url, this.cacheDir];
5012
- logger29.info({ url: this.config.url, branch }, "Cloning repository");
5417
+ logger30.info({ url: this.config.url, branch }, "Cloning repository");
5013
5418
  await execFileAsync("git", args, { timeout: 6e4 });
5014
5419
  }
5015
5420
  async pull() {
@@ -5027,7 +5432,7 @@ var GitHubSource = class {
5027
5432
  // src/source/source-manager.ts
5028
5433
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir14 } from "fs/promises";
5029
5434
  import { join as join21 } from "path";
5030
- import { createLogger as createLogger31 } from "@actant/shared";
5435
+ import { createLogger as createLogger32 } from "@actant/shared";
5031
5436
 
5032
5437
  // src/version/sync-report.ts
5033
5438
  function createEmptySyncReport() {
@@ -5052,7 +5457,7 @@ var DEFAULT_SOURCE_CONFIG = {
5052
5457
  url: "https://github.com/blackplume233/actant-hub.git",
5053
5458
  branch: "main"
5054
5459
  };
5055
- var logger30 = createLogger31("source-manager");
5460
+ var logger31 = createLogger32("source-manager");
5056
5461
  var SourceManager = class {
5057
5462
  sources = /* @__PURE__ */ new Map();
5058
5463
  presets = /* @__PURE__ */ new Map();
@@ -5080,7 +5485,7 @@ var SourceManager = class {
5080
5485
  this.sources.set(name, source);
5081
5486
  this.injectComponents(name, result);
5082
5487
  await this.persistSources();
5083
- logger30.info({ name, type: config.type }, "Source added");
5488
+ logger31.info({ name, type: config.type }, "Source added");
5084
5489
  return result;
5085
5490
  }
5086
5491
  async syncSource(name) {
@@ -5096,7 +5501,7 @@ var SourceManager = class {
5096
5501
  const newSnapshot = this.snapshotComponents(name);
5097
5502
  await this.persistSources();
5098
5503
  const report = this.buildSyncReport(oldSnapshot, newSnapshot);
5099
- logger30.info({ name, report }, "Source synced");
5504
+ logger31.info({ name, report }, "Source synced");
5100
5505
  return { fetchResult: result, report };
5101
5506
  }
5102
5507
  async syncAll() {
@@ -5109,7 +5514,7 @@ var SourceManager = class {
5109
5514
  const { report } = await this.syncSourceWithReport(name);
5110
5515
  reports.push(report);
5111
5516
  } catch (err) {
5112
- logger30.warn({ name, error: err }, "Failed to sync source, skipping");
5517
+ logger31.warn({ name, error: err }, "Failed to sync source, skipping");
5113
5518
  }
5114
5519
  }
5115
5520
  return { report: mergeSyncReports(reports) };
@@ -5122,7 +5527,7 @@ var SourceManager = class {
5122
5527
  await source.dispose();
5123
5528
  this.sources.delete(name);
5124
5529
  await this.persistSources();
5125
- logger30.info({ name }, "Source removed");
5530
+ logger31.info({ name }, "Source removed");
5126
5531
  return true;
5127
5532
  }
5128
5533
  listSources() {
@@ -5135,6 +5540,10 @@ var SourceManager = class {
5135
5540
  hasSource(name) {
5136
5541
  return this.sources.has(name);
5137
5542
  }
5543
+ /** Resolve a registered source name to its filesystem root directory. */
5544
+ getSourceRootDir(name) {
5545
+ return this.getSourceOrThrow(name).getRootDir();
5546
+ }
5138
5547
  // ---------------------------------------------------------------------------
5139
5548
  // Preset operations
5140
5549
  // ---------------------------------------------------------------------------
@@ -5206,7 +5615,7 @@ var SourceManager = class {
5206
5615
  const data = JSON.parse(raw);
5207
5616
  entries = Object.entries(data.sources ?? {}).map(([name, config]) => ({ name, config }));
5208
5617
  } catch {
5209
- logger30.debug("No sources.json found, starting with empty sources");
5618
+ logger31.debug("No sources.json found, starting with empty sources");
5210
5619
  }
5211
5620
  for (const entry of entries) {
5212
5621
  try {
@@ -5214,9 +5623,9 @@ var SourceManager = class {
5214
5623
  const result = await source.fetch();
5215
5624
  this.sources.set(entry.name, source);
5216
5625
  this.injectComponents(entry.name, result);
5217
- logger30.info({ name: entry.name }, "Source restored from config");
5626
+ logger31.info({ name: entry.name }, "Source restored from config");
5218
5627
  } catch (err) {
5219
- logger30.warn({ name: entry.name, error: err }, "Failed to restore source, skipping");
5628
+ logger31.warn({ name: entry.name, error: err }, "Failed to restore source, skipping");
5220
5629
  }
5221
5630
  }
5222
5631
  await this.ensureDefaultSource();
@@ -5230,9 +5639,9 @@ var SourceManager = class {
5230
5639
  if (this.sources.has(DEFAULT_SOURCE_NAME)) return;
5231
5640
  try {
5232
5641
  await this.addSource(DEFAULT_SOURCE_NAME, DEFAULT_SOURCE_CONFIG);
5233
- logger30.info("Default source registered: %s", DEFAULT_SOURCE_NAME);
5642
+ logger31.info("Default source registered: %s", DEFAULT_SOURCE_NAME);
5234
5643
  } catch (err) {
5235
- logger30.debug({ error: err }, "Failed to register default source (offline?), skipping");
5644
+ logger31.debug({ error: err }, "Failed to register default source (offline?), skipping");
5236
5645
  }
5237
5646
  }
5238
5647
  // ---------------------------------------------------------------------------
@@ -5262,22 +5671,40 @@ var SourceManager = class {
5262
5671
  for (const wf of result.workflows) {
5263
5672
  this.managers.workflowManager.register(ns(wf));
5264
5673
  }
5674
+ if (this.managers.backendManager) {
5675
+ for (const backend of result.backends) {
5676
+ this.managers.backendManager.register(ns(backend));
5677
+ }
5678
+ }
5265
5679
  for (const preset of result.presets) {
5266
5680
  this.presets.set(`${packageName}@${preset.name}`, preset);
5267
5681
  }
5268
5682
  if (this.managers.templateRegistry && result.templates.length > 0) {
5683
+ const nsRef = (ref) => ref.includes("@") ? ref : `${packageName}@${ref}`;
5269
5684
  for (const template of result.templates) {
5270
- const nsTemplate = { ...template, name: `${packageName}@${template.name}` };
5685
+ const dc = template.domainContext;
5686
+ const nsTemplate = {
5687
+ ...template,
5688
+ name: `${packageName}@${template.name}`,
5689
+ domainContext: {
5690
+ ...dc,
5691
+ skills: dc.skills?.map(nsRef),
5692
+ prompts: dc.prompts?.map(nsRef),
5693
+ subAgents: dc.subAgents?.map(nsRef),
5694
+ workflow: dc.workflow ? nsRef(dc.workflow) : dc.workflow
5695
+ }
5696
+ };
5271
5697
  this.managers.templateRegistry.register(nsTemplate);
5272
5698
  }
5273
5699
  }
5274
- logger30.debug(
5700
+ logger31.debug(
5275
5701
  {
5276
5702
  packageName,
5277
5703
  skills: result.skills.length,
5278
5704
  prompts: result.prompts.length,
5279
5705
  mcp: result.mcpServers.length,
5280
5706
  workflows: result.workflows.length,
5707
+ backends: result.backends.length,
5281
5708
  presets: result.presets.length,
5282
5709
  templates: result.templates.length
5283
5710
  },
@@ -5297,6 +5724,9 @@ var SourceManager = class {
5297
5724
  removeFrom(this.managers.promptManager);
5298
5725
  removeFrom(this.managers.mcpConfigManager);
5299
5726
  removeFrom(this.managers.workflowManager);
5727
+ if (this.managers.backendManager) {
5728
+ removeFrom(this.managers.backendManager);
5729
+ }
5300
5730
  if (this.managers.templateRegistry) {
5301
5731
  for (const t of this.managers.templateRegistry.list()) {
5302
5732
  if (t.name.startsWith(prefix)) {
@@ -5404,6 +5834,705 @@ function isMajorVersionChange(oldVersion, newVersion) {
5404
5834
  if (oldMajor === void 0 || newMajor === void 0) return false;
5405
5835
  return newMajor !== oldMajor;
5406
5836
  }
5837
+
5838
+ // src/source/source-validator.ts
5839
+ import { readFile as readFile8, readdir as readdir6, stat as stat9, access as access4 } from "fs/promises";
5840
+ import { join as join22, relative as relative2, extname as extname4 } from "path";
5841
+
5842
+ // src/source/source-schemas.ts
5843
+ import { z as z10 } from "zod";
5844
+ var VersionedComponentFields = {
5845
+ name: z10.string().min(1, "name is required"),
5846
+ version: z10.string().optional(),
5847
+ description: z10.string().optional(),
5848
+ $type: z10.string().optional(),
5849
+ $version: z10.number().optional(),
5850
+ origin: z10.object({
5851
+ type: z10.enum(["builtin", "source", "local"]),
5852
+ sourceName: z10.string().optional(),
5853
+ syncHash: z10.string().optional(),
5854
+ syncedAt: z10.string().optional(),
5855
+ modified: z10.boolean().optional()
5856
+ }).optional(),
5857
+ tags: z10.array(z10.string()).optional()
5858
+ };
5859
+ var PackageManifestSchema = z10.object({
5860
+ name: z10.string().min(1, "name is required"),
5861
+ version: z10.string().optional(),
5862
+ description: z10.string().optional(),
5863
+ components: z10.object({
5864
+ skills: z10.array(z10.string()).optional(),
5865
+ prompts: z10.array(z10.string()).optional(),
5866
+ mcp: z10.array(z10.string()).optional(),
5867
+ workflows: z10.array(z10.string()).optional(),
5868
+ templates: z10.array(z10.string()).optional(),
5869
+ backends: z10.array(z10.string()).optional()
5870
+ }).optional(),
5871
+ presets: z10.array(z10.string()).optional()
5872
+ });
5873
+ var SkillDefinitionSchema2 = z10.object({
5874
+ ...VersionedComponentFields,
5875
+ content: z10.string().min(1, "content is required"),
5876
+ license: z10.string().optional(),
5877
+ compatibility: z10.string().optional(),
5878
+ allowedTools: z10.array(z10.string()).optional()
5879
+ });
5880
+ var PromptDefinitionSchema2 = z10.object({
5881
+ ...VersionedComponentFields,
5882
+ content: z10.string().min(1, "content is required"),
5883
+ variables: z10.array(z10.string()).optional()
5884
+ });
5885
+ var McpServerDefinitionSchema2 = z10.object({
5886
+ ...VersionedComponentFields,
5887
+ command: z10.string().min(1, "command is required"),
5888
+ args: z10.array(z10.string()).optional(),
5889
+ env: z10.record(z10.string(), z10.string()).optional()
5890
+ });
5891
+ var WorkflowDefinitionSchema2 = z10.object({
5892
+ ...VersionedComponentFields,
5893
+ content: z10.string().min(1, "content is required")
5894
+ });
5895
+ var PlatformCommandSchema2 = z10.object({
5896
+ win32: z10.string().min(1),
5897
+ default: z10.string().min(1)
5898
+ });
5899
+ var SourceBackendDefinitionSchema = z10.object({
5900
+ ...VersionedComponentFields,
5901
+ supportedModes: z10.array(z10.enum(["resolve", "open", "acp"])).min(1),
5902
+ resolveCommand: PlatformCommandSchema2.optional(),
5903
+ openCommand: PlatformCommandSchema2.optional(),
5904
+ acpCommand: PlatformCommandSchema2.optional(),
5905
+ acpOwnsProcess: z10.boolean().optional(),
5906
+ resolvePackage: z10.string().optional(),
5907
+ openWorkspaceDir: z10.enum(["arg", "cwd"]).optional(),
5908
+ openSpawnOptions: z10.object({
5909
+ stdio: z10.enum(["inherit", "ignore"]).optional(),
5910
+ detached: z10.boolean().optional(),
5911
+ windowsHide: z10.boolean().optional(),
5912
+ shell: z10.boolean().optional()
5913
+ }).optional(),
5914
+ existenceCheck: z10.object({
5915
+ command: z10.string().min(1),
5916
+ args: z10.array(z10.string()).optional(),
5917
+ expectedExitCode: z10.number().int().optional(),
5918
+ versionPattern: z10.string().optional()
5919
+ }).optional(),
5920
+ install: z10.array(z10.object({
5921
+ type: z10.enum(["npm", "brew", "winget", "choco", "url", "manual"]),
5922
+ package: z10.string().optional(),
5923
+ platforms: z10.array(z10.string()).optional(),
5924
+ label: z10.string().optional(),
5925
+ instructions: z10.string().optional()
5926
+ })).optional()
5927
+ });
5928
+ var PresetDefinitionSchema = z10.object({
5929
+ name: z10.string().min(1, "name is required"),
5930
+ version: z10.string().optional(),
5931
+ description: z10.string().optional(),
5932
+ skills: z10.array(z10.string()).optional(),
5933
+ prompts: z10.array(z10.string()).optional(),
5934
+ mcpServers: z10.array(z10.string()).optional(),
5935
+ workflows: z10.array(z10.string()).optional(),
5936
+ templates: z10.array(z10.string()).optional()
5937
+ });
5938
+
5939
+ // src/source/source-validator.ts
5940
+ var COMPONENT_DIR_SCHEMAS = {
5941
+ skills: SkillDefinitionSchema2,
5942
+ prompts: PromptDefinitionSchema2,
5943
+ mcp: McpServerDefinitionSchema2,
5944
+ workflows: WorkflowDefinitionSchema2,
5945
+ presets: PresetDefinitionSchema
5946
+ };
5947
+ var COMPONENT_DIRS = ["skills", "prompts", "mcp", "workflows", "templates", "presets"];
5948
+ var SourceValidator = class {
5949
+ /**
5950
+ * Validate all assets in a source directory.
5951
+ * Returns a report with pass/warn/error counts and detailed issues.
5952
+ */
5953
+ async validate(rootDir, options) {
5954
+ const issues = [];
5955
+ let passCount = 0;
5956
+ const validatedFiles = /* @__PURE__ */ new Set();
5957
+ const compat = options?.compat;
5958
+ const manifest = await this.validateManifest(rootDir, issues);
5959
+ if (manifest) passCount++;
5960
+ const componentNames = /* @__PURE__ */ new Map();
5961
+ if (manifest) {
5962
+ passCount += await this.validateExplicitFiles(rootDir, manifest, issues, componentNames, validatedFiles, compat);
5963
+ }
5964
+ passCount += await this.validateComponentDirs(rootDir, issues, componentNames, validatedFiles, compat);
5965
+ await this.validatePresetReferences(rootDir, manifest, componentNames, issues);
5966
+ const errorCount = issues.filter((i) => i.severity === "error").length;
5967
+ const warnCount = issues.filter((i) => i.severity === "warning").length;
5968
+ const valid = options?.strict ? errorCount === 0 && warnCount === 0 : errorCount === 0;
5969
+ return {
5970
+ valid,
5971
+ sourceName: manifest?.name ?? "unknown",
5972
+ rootDir,
5973
+ summary: { pass: passCount, warn: warnCount, error: errorCount },
5974
+ issues
5975
+ };
5976
+ }
5977
+ // -------------------------------------------------------------------------
5978
+ // Layer 1: Manifest validation
5979
+ // -------------------------------------------------------------------------
5980
+ async validateManifest(rootDir, issues) {
5981
+ const manifestPath = join22(rootDir, "actant.json");
5982
+ let raw;
5983
+ try {
5984
+ raw = await readFile8(manifestPath, "utf-8");
5985
+ } catch {
5986
+ issues.push({
5987
+ severity: "error",
5988
+ path: "actant.json",
5989
+ message: "actant.json not found in source root",
5990
+ code: "MANIFEST_MISSING"
5991
+ });
5992
+ return null;
5993
+ }
5994
+ let data;
5995
+ try {
5996
+ data = JSON.parse(raw);
5997
+ } catch (err) {
5998
+ issues.push({
5999
+ severity: "error",
6000
+ path: "actant.json",
6001
+ message: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
6002
+ code: "MANIFEST_INVALID_JSON"
6003
+ });
6004
+ return null;
6005
+ }
6006
+ const result = PackageManifestSchema.safeParse(data);
6007
+ if (!result.success) {
6008
+ for (const issue of result.error.issues) {
6009
+ issues.push({
6010
+ severity: "error",
6011
+ path: "actant.json",
6012
+ message: `${issue.path.join(".")}: ${issue.message}`,
6013
+ code: "MANIFEST_SCHEMA"
6014
+ });
6015
+ }
6016
+ return null;
6017
+ }
6018
+ const manifest = result.data;
6019
+ await this.verifyManifestFileRefs(rootDir, manifest, issues);
6020
+ return manifest;
6021
+ }
6022
+ async verifyManifestFileRefs(rootDir, manifest, issues) {
6023
+ const allRefs = [];
6024
+ if (manifest.components) {
6025
+ for (const [type, files] of Object.entries(manifest.components)) {
6026
+ if (files) {
6027
+ for (const f of files) {
6028
+ allRefs.push({ ref: f, section: `components.${type}` });
6029
+ }
6030
+ }
6031
+ }
6032
+ }
6033
+ if (manifest.presets) {
6034
+ for (const f of manifest.presets) {
6035
+ allRefs.push({ ref: f, section: "presets" });
6036
+ }
6037
+ }
6038
+ for (const { ref, section } of allRefs) {
6039
+ const fullPath = join22(rootDir, ref);
6040
+ try {
6041
+ await access4(fullPath);
6042
+ } catch {
6043
+ issues.push({
6044
+ severity: "error",
6045
+ path: `actant.json`,
6046
+ component: ref,
6047
+ message: `File referenced in ${section} does not exist: ${ref}`,
6048
+ code: "MANIFEST_FILE_MISSING"
6049
+ });
6050
+ }
6051
+ }
6052
+ }
6053
+ // -------------------------------------------------------------------------
6054
+ // Layer 2a: Validate files explicitly listed in manifest
6055
+ // -------------------------------------------------------------------------
6056
+ async validateExplicitFiles(rootDir, manifest, issues, componentNames, validatedFiles, compat) {
6057
+ let passCount = 0;
6058
+ const components = manifest.components;
6059
+ if (!components && !manifest.presets) return 0;
6060
+ if (components) {
6061
+ for (const [type, files] of Object.entries(components)) {
6062
+ if (!files) continue;
6063
+ for (const filePath of files) {
6064
+ validatedFiles.add(filePath);
6065
+ const ok = await this.validateSingleFile(rootDir, filePath, type, issues, componentNames);
6066
+ if (ok) passCount++;
6067
+ }
6068
+ }
6069
+ }
6070
+ const presets = manifest.presets;
6071
+ if (presets) {
6072
+ for (const filePath of presets) {
6073
+ validatedFiles.add(filePath);
6074
+ const ok = await this.validateSingleFile(rootDir, filePath, "presets", issues, componentNames);
6075
+ if (ok) passCount++;
6076
+ }
6077
+ }
6078
+ void compat;
6079
+ return passCount;
6080
+ }
6081
+ // -------------------------------------------------------------------------
6082
+ // Layer 2b: Scan component directories
6083
+ // -------------------------------------------------------------------------
6084
+ async validateComponentDirs(rootDir, issues, componentNames, validatedFiles, compat) {
6085
+ let passCount = 0;
6086
+ for (const dir of COMPONENT_DIRS) {
6087
+ const dirPath = join22(rootDir, dir);
6088
+ try {
6089
+ const dirStat = await stat9(dirPath);
6090
+ if (!dirStat.isDirectory()) continue;
6091
+ } catch {
6092
+ continue;
6093
+ }
6094
+ passCount += await this.scanDirectory(rootDir, dirPath, dir, issues, componentNames, validatedFiles, compat);
6095
+ }
6096
+ return passCount;
6097
+ }
6098
+ async scanDirectory(rootDir, dirPath, componentType, issues, componentNames, validatedFiles, compat) {
6099
+ let passCount = 0;
6100
+ const entries = await readdir6(dirPath);
6101
+ for (const entry of entries) {
6102
+ const fullPath = join22(dirPath, entry);
6103
+ const relPath = relative2(rootDir, fullPath).replace(/\\/g, "/");
6104
+ const entryStat = await stat9(fullPath).catch(() => null);
6105
+ if (!entryStat) continue;
6106
+ if (entryStat.isFile() && extname4(entry) === ".json") {
6107
+ if (validatedFiles.has(relPath)) continue;
6108
+ validatedFiles.add(relPath);
6109
+ const ok = await this.validateSingleFile(rootDir, relPath, componentType, issues, componentNames);
6110
+ if (ok) passCount++;
6111
+ } else if (entryStat.isDirectory()) {
6112
+ if (componentType === "skills") {
6113
+ const skillMdPath = join22(fullPath, "SKILL.md");
6114
+ try {
6115
+ await access4(skillMdPath);
6116
+ const skillRelPath = relative2(rootDir, skillMdPath).replace(/\\/g, "/");
6117
+ if (!validatedFiles.has(skillRelPath)) {
6118
+ validatedFiles.add(skillRelPath);
6119
+ const ok = await this.validateSkillMd(rootDir, skillRelPath, issues, componentNames, entry, compat);
6120
+ if (ok) passCount++;
6121
+ }
6122
+ if (compat === "agent-skills") {
6123
+ await this.validateSkillDirConventions(rootDir, fullPath, entry, issues);
6124
+ }
6125
+ } catch {
6126
+ const manifestJsonPath = join22(fullPath, "manifest.json");
6127
+ try {
6128
+ await access4(manifestJsonPath);
6129
+ const mRelPath = relative2(rootDir, manifestJsonPath).replace(/\\/g, "/");
6130
+ if (!validatedFiles.has(mRelPath)) {
6131
+ validatedFiles.add(mRelPath);
6132
+ const ok = await this.validateSingleFile(rootDir, mRelPath, componentType, issues, componentNames);
6133
+ if (ok) passCount++;
6134
+ }
6135
+ } catch {
6136
+ }
6137
+ }
6138
+ }
6139
+ }
6140
+ }
6141
+ return passCount;
6142
+ }
6143
+ // -------------------------------------------------------------------------
6144
+ // Single file validation
6145
+ // -------------------------------------------------------------------------
6146
+ async validateSingleFile(rootDir, relPath, componentType, issues, componentNames) {
6147
+ const fullPath = join22(rootDir, relPath);
6148
+ let raw;
6149
+ try {
6150
+ raw = await readFile8(fullPath, "utf-8");
6151
+ } catch {
6152
+ issues.push({
6153
+ severity: "error",
6154
+ path: relPath,
6155
+ message: "Cannot read file",
6156
+ code: "FILE_UNREADABLE"
6157
+ });
6158
+ return false;
6159
+ }
6160
+ let data;
6161
+ try {
6162
+ data = JSON.parse(raw);
6163
+ } catch (err) {
6164
+ issues.push({
6165
+ severity: "error",
6166
+ path: relPath,
6167
+ message: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
6168
+ code: "INVALID_JSON"
6169
+ });
6170
+ return false;
6171
+ }
6172
+ if (componentType === "templates") {
6173
+ return this.validateTemplateComponent(relPath, data, issues, componentNames);
6174
+ }
6175
+ const schema = COMPONENT_DIR_SCHEMAS[componentType];
6176
+ if (!schema) return true;
6177
+ const result = schema.safeParse(data);
6178
+ if (!result.success) {
6179
+ for (const issue of result.error.issues) {
6180
+ issues.push({
6181
+ severity: "error",
6182
+ path: relPath,
6183
+ component: data?.name,
6184
+ message: `${issue.path.join(".")}: ${issue.message}`,
6185
+ code: "COMPONENT_SCHEMA"
6186
+ });
6187
+ }
6188
+ return false;
6189
+ }
6190
+ const parsed = result.data;
6191
+ this.trackComponentName(componentNames, componentType, parsed.name);
6192
+ if (!parsed.description) {
6193
+ issues.push({
6194
+ severity: "warning",
6195
+ path: relPath,
6196
+ component: parsed.name,
6197
+ message: `Missing "description" field`,
6198
+ code: "MISSING_DESCRIPTION"
6199
+ });
6200
+ }
6201
+ return true;
6202
+ }
6203
+ validateTemplateComponent(relPath, data, issues, componentNames) {
6204
+ const schemaResult = AgentTemplateSchema.safeParse(data);
6205
+ if (!schemaResult.success) {
6206
+ for (const issue of schemaResult.error.issues) {
6207
+ issues.push({
6208
+ severity: "error",
6209
+ path: relPath,
6210
+ component: data?.name,
6211
+ message: `${issue.path.join(".")}: ${issue.message}`,
6212
+ code: "TEMPLATE_SCHEMA"
6213
+ });
6214
+ }
6215
+ return false;
6216
+ }
6217
+ const semanticResult = validateTemplate(data);
6218
+ for (const w of semanticResult.warnings ?? []) {
6219
+ issues.push({
6220
+ severity: "warning",
6221
+ path: relPath,
6222
+ component: schemaResult.data.name,
6223
+ message: w.message,
6224
+ code: w.code ?? "TEMPLATE_SEMANTIC"
6225
+ });
6226
+ }
6227
+ this.trackComponentName(componentNames, "templates", schemaResult.data.name);
6228
+ return true;
6229
+ }
6230
+ // -------------------------------------------------------------------------
6231
+ // SKILL.md validation
6232
+ // -------------------------------------------------------------------------
6233
+ async validateSkillMd(rootDir, relPath, issues, componentNames, parentDirName, compat) {
6234
+ const fullPath = join22(rootDir, relPath);
6235
+ let raw;
6236
+ try {
6237
+ raw = await readFile8(fullPath, "utf-8");
6238
+ } catch {
6239
+ issues.push({
6240
+ severity: "error",
6241
+ path: relPath,
6242
+ message: "Cannot read SKILL.md file",
6243
+ code: "FILE_UNREADABLE"
6244
+ });
6245
+ return false;
6246
+ }
6247
+ const skill = parseSkillMdContent(raw);
6248
+ if (!skill) {
6249
+ issues.push({
6250
+ severity: "error",
6251
+ path: relPath,
6252
+ message: "Invalid SKILL.md: missing YAML frontmatter or required 'name' field",
6253
+ code: "SKILL_MD_INVALID"
6254
+ });
6255
+ return false;
6256
+ }
6257
+ this.trackComponentName(componentNames, "skills", skill.name);
6258
+ if (compat === "agent-skills") {
6259
+ this.validateAgentSkillsCompat(skill, relPath, issues, parentDirName, raw);
6260
+ } else {
6261
+ if (!skill.description) {
6262
+ issues.push({
6263
+ severity: "warning",
6264
+ path: relPath,
6265
+ component: skill.name,
6266
+ message: `Missing "description" in SKILL.md frontmatter`,
6267
+ code: "SKILL_MD_MISSING_DESCRIPTION"
6268
+ });
6269
+ }
6270
+ }
6271
+ if (!skill.content || skill.content.trim().length === 0) {
6272
+ issues.push({
6273
+ severity: "warning",
6274
+ path: relPath,
6275
+ component: skill.name,
6276
+ message: "SKILL.md has empty content body",
6277
+ code: "SKILL_MD_EMPTY_CONTENT"
6278
+ });
6279
+ }
6280
+ return true;
6281
+ }
6282
+ // -------------------------------------------------------------------------
6283
+ // Agent Skills (agentskills.io) compatibility checks
6284
+ // -------------------------------------------------------------------------
6285
+ validateAgentSkillsCompat(skill, relPath, issues, parentDirName, raw) {
6286
+ const name = skill.name;
6287
+ const NAME_RE = /^[a-z][a-z0-9-]*$/;
6288
+ if (name.length > 64) {
6289
+ issues.push({
6290
+ severity: "error",
6291
+ path: relPath,
6292
+ component: name,
6293
+ message: `Name exceeds 64 characters (${name.length})`,
6294
+ code: "AGENT_SKILLS_NAME_TOO_LONG"
6295
+ });
6296
+ } else if (!NAME_RE.test(name)) {
6297
+ issues.push({
6298
+ severity: "error",
6299
+ path: relPath,
6300
+ component: name,
6301
+ message: `Name must contain only lowercase letters, numbers, and hyphens, starting with a letter`,
6302
+ code: "AGENT_SKILLS_NAME_FORMAT"
6303
+ });
6304
+ } else {
6305
+ if (name.endsWith("-")) {
6306
+ issues.push({
6307
+ severity: "error",
6308
+ path: relPath,
6309
+ component: name,
6310
+ message: "Name must not end with a hyphen",
6311
+ code: "AGENT_SKILLS_NAME_TRAILING_HYPHEN"
6312
+ });
6313
+ }
6314
+ if (name.includes("--")) {
6315
+ issues.push({
6316
+ severity: "error",
6317
+ path: relPath,
6318
+ component: name,
6319
+ message: "Name must not contain consecutive hyphens",
6320
+ code: "AGENT_SKILLS_NAME_CONSECUTIVE_HYPHENS"
6321
+ });
6322
+ }
6323
+ }
6324
+ if (parentDirName && name !== parentDirName) {
6325
+ issues.push({
6326
+ severity: "error",
6327
+ path: relPath,
6328
+ component: name,
6329
+ message: `Name "${name}" does not match parent directory "${parentDirName}"`,
6330
+ code: "AGENT_SKILLS_NAME_DIR_MISMATCH"
6331
+ });
6332
+ }
6333
+ if (!skill.description) {
6334
+ issues.push({
6335
+ severity: "error",
6336
+ path: relPath,
6337
+ component: name,
6338
+ message: `Missing required "description" field (Agent Skills spec)`,
6339
+ code: "AGENT_SKILLS_DESCRIPTION_REQUIRED"
6340
+ });
6341
+ } else if (skill.description.length > 1024) {
6342
+ issues.push({
6343
+ severity: "warning",
6344
+ path: relPath,
6345
+ component: name,
6346
+ message: `Description exceeds 1024 characters (${skill.description.length})`,
6347
+ code: "AGENT_SKILLS_DESCRIPTION_TOO_LONG"
6348
+ });
6349
+ }
6350
+ if (skill.compatibility && skill.compatibility.length > 500) {
6351
+ issues.push({
6352
+ severity: "warning",
6353
+ path: relPath,
6354
+ component: name,
6355
+ message: `Compatibility field exceeds 500 characters (${skill.compatibility.length})`,
6356
+ code: "AGENT_SKILLS_COMPAT_TOO_LONG"
6357
+ });
6358
+ }
6359
+ if (raw) {
6360
+ const frontmatterEnd = raw.indexOf("---", 3);
6361
+ if (frontmatterEnd !== -1) {
6362
+ const body = raw.substring(frontmatterEnd + 3).trim();
6363
+ const lineCount = body.split("\n").length;
6364
+ if (lineCount > 500) {
6365
+ issues.push({
6366
+ severity: "warning",
6367
+ path: relPath,
6368
+ component: name,
6369
+ message: `SKILL.md body has ${lineCount} lines; Agent Skills spec recommends < 500 lines`,
6370
+ code: "AGENT_SKILLS_BODY_TOO_LONG"
6371
+ });
6372
+ }
6373
+ }
6374
+ }
6375
+ }
6376
+ // -------------------------------------------------------------------------
6377
+ // Agent Skills directory convention (scripts/, references/, assets/)
6378
+ // -------------------------------------------------------------------------
6379
+ async validateSkillDirConventions(rootDir, skillDir, skillName, issues) {
6380
+ const KNOWN_DIRS = /* @__PURE__ */ new Set(["scripts", "references", "assets"]);
6381
+ const KNOWN_FILES = /* @__PURE__ */ new Set(["SKILL.md", "LICENSE", "LICENSE.txt", "LICENSE.md"]);
6382
+ let entries;
6383
+ try {
6384
+ entries = await readdir6(skillDir);
6385
+ } catch {
6386
+ return;
6387
+ }
6388
+ for (const entry of entries) {
6389
+ const fullPath = join22(skillDir, entry);
6390
+ const relPath = relative2(rootDir, fullPath).replace(/\\/g, "/");
6391
+ const entryStat = await stat9(fullPath).catch(() => null);
6392
+ if (!entryStat) continue;
6393
+ if (entryStat.isDirectory()) {
6394
+ if (KNOWN_DIRS.has(entry)) {
6395
+ issues.push({
6396
+ severity: "info",
6397
+ path: relPath,
6398
+ component: skillName,
6399
+ message: `Agent Skills convention: ${entry}/ directory detected`,
6400
+ code: "AGENT_SKILLS_DIR_FOUND"
6401
+ });
6402
+ }
6403
+ } else if (!KNOWN_FILES.has(entry)) {
6404
+ }
6405
+ }
6406
+ }
6407
+ // -------------------------------------------------------------------------
6408
+ // Layer 3: Cross-reference validation
6409
+ // -------------------------------------------------------------------------
6410
+ async validatePresetReferences(rootDir, manifest, componentNames, issues) {
6411
+ const presetsDir = join22(rootDir, "presets");
6412
+ const presetFiles = [];
6413
+ if (manifest?.presets) {
6414
+ for (const p of manifest.presets) {
6415
+ presetFiles.push(p);
6416
+ }
6417
+ } else {
6418
+ try {
6419
+ const entries = await readdir6(presetsDir);
6420
+ for (const e of entries) {
6421
+ if (extname4(e) === ".json") {
6422
+ presetFiles.push(`presets/${e}`);
6423
+ }
6424
+ }
6425
+ } catch {
6426
+ return;
6427
+ }
6428
+ }
6429
+ for (const presetFile of presetFiles) {
6430
+ const fullPath = join22(rootDir, presetFile);
6431
+ let data;
6432
+ try {
6433
+ const raw = await readFile8(fullPath, "utf-8");
6434
+ data = JSON.parse(raw);
6435
+ } catch {
6436
+ continue;
6437
+ }
6438
+ const presetName = data.name || presetFile;
6439
+ const refMap = [
6440
+ ["skills", "skills"],
6441
+ ["prompts", "prompts"],
6442
+ ["mcpServers", "mcp"],
6443
+ ["workflows", "workflows"],
6444
+ ["templates", "templates"]
6445
+ ];
6446
+ for (const [field, compType] of refMap) {
6447
+ const refs = data[field];
6448
+ if (!refs) continue;
6449
+ const available = componentNames.get(compType) ?? /* @__PURE__ */ new Set();
6450
+ for (const ref of refs) {
6451
+ if (!available.has(ref)) {
6452
+ issues.push({
6453
+ severity: "warning",
6454
+ path: presetFile,
6455
+ component: presetName,
6456
+ message: `Preset references ${compType} "${ref}" which was not found in this source`,
6457
+ code: "PRESET_REF_MISSING"
6458
+ });
6459
+ }
6460
+ }
6461
+ }
6462
+ }
6463
+ }
6464
+ // -------------------------------------------------------------------------
6465
+ // Helpers
6466
+ // -------------------------------------------------------------------------
6467
+ trackComponentName(componentNames, type, name) {
6468
+ let set = componentNames.get(type);
6469
+ if (!set) {
6470
+ set = /* @__PURE__ */ new Set();
6471
+ componentNames.set(type, set);
6472
+ }
6473
+ set.add(name);
6474
+ }
6475
+ };
6476
+
6477
+ // src/provider/builtin-providers.ts
6478
+ var BUILTIN_PROVIDERS = [
6479
+ {
6480
+ type: "anthropic",
6481
+ displayName: "Anthropic (Claude)",
6482
+ protocol: "anthropic",
6483
+ defaultBaseUrl: "https://api.anthropic.com",
6484
+ models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"]
6485
+ },
6486
+ {
6487
+ type: "openai",
6488
+ displayName: "OpenAI",
6489
+ protocol: "openai",
6490
+ defaultBaseUrl: "https://api.openai.com/v1",
6491
+ models: ["gpt-4o", "gpt-4o-mini", "o3-mini"]
6492
+ },
6493
+ {
6494
+ type: "deepseek",
6495
+ displayName: "DeepSeek",
6496
+ protocol: "openai",
6497
+ defaultBaseUrl: "https://api.deepseek.com/v1",
6498
+ models: ["deepseek-chat", "deepseek-reasoner"]
6499
+ },
6500
+ {
6501
+ type: "ollama",
6502
+ displayName: "Ollama (Local)",
6503
+ protocol: "openai",
6504
+ defaultBaseUrl: "http://localhost:11434/v1"
6505
+ },
6506
+ {
6507
+ type: "azure",
6508
+ displayName: "Azure OpenAI",
6509
+ protocol: "openai",
6510
+ defaultBaseUrl: "https://<resource>.openai.azure.com"
6511
+ },
6512
+ {
6513
+ type: "bedrock",
6514
+ displayName: "AWS Bedrock",
6515
+ protocol: "anthropic",
6516
+ defaultBaseUrl: "https://bedrock-runtime.<region>.amazonaws.com"
6517
+ },
6518
+ {
6519
+ type: "vertex",
6520
+ displayName: "Google Vertex AI",
6521
+ protocol: "anthropic",
6522
+ defaultBaseUrl: "https://<region>-aiplatform.googleapis.com"
6523
+ },
6524
+ {
6525
+ type: "custom",
6526
+ displayName: "Custom",
6527
+ protocol: "custom",
6528
+ defaultBaseUrl: "http://localhost:8080"
6529
+ }
6530
+ ];
6531
+ function registerBuiltinProviders() {
6532
+ for (const descriptor of BUILTIN_PROVIDERS) {
6533
+ modelProviderRegistry.register(descriptor);
6534
+ }
6535
+ }
5407
6536
  export {
5408
6537
  AgentBackendSchema,
5409
6538
  AgentInitializer,
@@ -5411,6 +6540,8 @@ export {
5411
6540
  AgentManager,
5412
6541
  AgentStatusSchema,
5413
6542
  AgentTemplateSchema,
6543
+ BUILTIN_PROVIDERS,
6544
+ BackendManager,
5414
6545
  BaseComponentManager,
5415
6546
  ClaudeCodeBuilder,
5416
6547
  ClaudeCodeCommunicator,
@@ -5447,6 +6578,7 @@ export {
5447
6578
  McpServerRefSchema,
5448
6579
  MkdirStep,
5449
6580
  MockLauncher,
6581
+ ModelProviderRegistry,
5450
6582
  ModelProviderSchema,
5451
6583
  NpmInstallStep,
5452
6584
  PermissionAuditLogger,
@@ -5464,6 +6596,7 @@ export {
5464
6596
  SessionRegistry,
5465
6597
  SkillManager,
5466
6598
  SourceManager,
6599
+ SourceValidator,
5467
6600
  StepRegistry,
5468
6601
  TaskDispatcher,
5469
6602
  TaskQueue,
@@ -5476,15 +6609,20 @@ export {
5476
6609
  createDefaultStepRegistry,
5477
6610
  createLauncher,
5478
6611
  getBackendDescriptor,
6612
+ getBackendManager,
6613
+ getInstallHint,
5479
6614
  getLaunchModeHandler,
5480
6615
  globMatch,
5481
6616
  isAcpBackend,
5482
6617
  isAcpOnlyBackend,
5483
6618
  isProcessAlive,
5484
6619
  metaFilePath,
6620
+ modelProviderRegistry,
5485
6621
  openBackend,
5486
6622
  readInstanceMeta,
5487
6623
  registerBackend,
6624
+ registerBackendDefinition,
6625
+ registerBuiltinProviders,
5488
6626
  registerCommunicator,
5489
6627
  requireMode,
5490
6628
  resolveAcpBackend,