@actant/core 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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";
@@ -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,8 +1806,8 @@ 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
1813
  constructor(templatesDir, registry3, options) {
@@ -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
@@ -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);
@@ -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,8 +2235,8 @@ 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 {
@@ -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",
@@ -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);
@@ -2934,13 +3048,15 @@ function createDefaultStepRegistry() {
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
 
2946
3062
  // src/manager/launcher/backend-registry.ts
@@ -2973,6 +3089,14 @@ function requireMode(type, mode) {
2973
3089
  function getPlatformCommand(cmd) {
2974
3090
  return process.platform === "win32" ? cmd.win32 : cmd.default;
2975
3091
  }
3092
+ var installHints = /* @__PURE__ */ new Map([
3093
+ ["claude-code", "npm install -g @zed-industries/claude-agent-acp"],
3094
+ ["cursor", "Install Cursor from https://cursor.com"],
3095
+ ["cursor-agent", "Install Cursor from https://cursor.com"]
3096
+ ]);
3097
+ function getInstallHint(type) {
3098
+ return installHints.get(type);
3099
+ }
2976
3100
 
2977
3101
  // src/manager/launcher/builtin-backends.ts
2978
3102
  function registerBuiltinBackends() {
@@ -2990,8 +3114,9 @@ function registerBuiltinBackends() {
2990
3114
  });
2991
3115
  registerBackend({
2992
3116
  type: "claude-code",
2993
- supportedModes: ["resolve", "acp"],
2994
- resolveCommand: { win32: "claude-agent-acp.cmd", default: "claude-agent-acp" }
3117
+ supportedModes: ["resolve", "open", "acp"],
3118
+ resolveCommand: { win32: "claude-agent-acp.cmd", default: "claude-agent-acp" },
3119
+ openCommand: { win32: "claude.cmd", default: "claude" }
2995
3120
  });
2996
3121
  registerBackend({
2997
3122
  type: "custom",
@@ -3064,7 +3189,7 @@ function resolveAcpBackend(backendType, workspaceDir, backendConfig) {
3064
3189
  }
3065
3190
 
3066
3191
  // src/manager/launcher/process-watcher.ts
3067
- import { createLogger as createLogger18 } from "@actant/shared";
3192
+ import { createLogger as createLogger19 } from "@actant/shared";
3068
3193
 
3069
3194
  // src/manager/launcher/process-utils.ts
3070
3195
  function isProcessAlive(pid) {
@@ -3100,7 +3225,7 @@ function isNodeError5(err) {
3100
3225
  }
3101
3226
 
3102
3227
  // src/manager/launcher/process-watcher.ts
3103
- var logger17 = createLogger18("process-watcher");
3228
+ var logger18 = createLogger19("process-watcher");
3104
3229
  var DEFAULT_POLL_INTERVAL = 5e3;
3105
3230
  var ProcessWatcher = class {
3106
3231
  constructor(onProcessExit, options) {
@@ -3113,12 +3238,12 @@ var ProcessWatcher = class {
3113
3238
  pollIntervalMs;
3114
3239
  watch(instanceName, pid) {
3115
3240
  this.watches.set(instanceName, { pid });
3116
- logger17.debug({ instanceName, pid }, "Watching process");
3241
+ logger18.debug({ instanceName, pid }, "Watching process");
3117
3242
  }
3118
3243
  unwatch(instanceName) {
3119
3244
  const removed = this.watches.delete(instanceName);
3120
3245
  if (removed) {
3121
- logger17.debug({ instanceName }, "Unwatched process");
3246
+ logger18.debug({ instanceName }, "Unwatched process");
3122
3247
  }
3123
3248
  return removed;
3124
3249
  }
@@ -3133,13 +3258,13 @@ var ProcessWatcher = class {
3133
3258
  this.timer = setInterval(() => {
3134
3259
  void this.poll();
3135
3260
  }, this.pollIntervalMs);
3136
- logger17.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3261
+ logger18.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3137
3262
  }
3138
3263
  stop() {
3139
3264
  if (this.timer) {
3140
3265
  clearInterval(this.timer);
3141
3266
  this.timer = null;
3142
- logger17.info("ProcessWatcher stopped");
3267
+ logger18.info("ProcessWatcher stopped");
3143
3268
  }
3144
3269
  }
3145
3270
  dispose() {
@@ -3161,11 +3286,11 @@ var ProcessWatcher = class {
3161
3286
  }
3162
3287
  for (const info of exited) {
3163
3288
  this.watches.delete(info.instanceName);
3164
- logger17.info(info, "Process exited \u2014 removed from watch list");
3289
+ logger18.info(info, "Process exited \u2014 removed from watch list");
3165
3290
  try {
3166
3291
  await this.onProcessExit(info);
3167
3292
  } catch (err) {
3168
- logger17.error({ ...info, error: err }, "Error in process exit handler");
3293
+ logger18.error({ ...info, error: err }, "Error in process exit handler");
3169
3294
  }
3170
3295
  }
3171
3296
  } finally {
@@ -3175,8 +3300,8 @@ var ProcessWatcher = class {
3175
3300
  };
3176
3301
 
3177
3302
  // src/manager/launch-mode-handler.ts
3178
- import { createLogger as createLogger19 } from "@actant/shared";
3179
- var logger18 = createLogger19("launch-mode-handler");
3303
+ import { createLogger as createLogger20 } from "@actant/shared";
3304
+ var logger19 = createLogger20("launch-mode-handler");
3180
3305
  var DirectModeHandler = class {
3181
3306
  mode = "direct";
3182
3307
  getProcessExitAction(_instanceName) {
@@ -3198,11 +3323,11 @@ var AcpBackgroundModeHandler = class {
3198
3323
  var AcpServiceModeHandler = class {
3199
3324
  mode = "acp-service";
3200
3325
  getProcessExitAction(instanceName) {
3201
- logger18.info({ instanceName }, "acp-service process exited \u2014 restart policy will be checked");
3326
+ logger19.info({ instanceName }, "acp-service process exited \u2014 restart policy will be checked");
3202
3327
  return { type: "restart" };
3203
3328
  }
3204
3329
  getRecoveryAction(instanceName) {
3205
- logger18.info({ instanceName }, "acp-service stale instance \u2014 will attempt recovery restart");
3330
+ logger19.info({ instanceName }, "acp-service stale instance \u2014 will attempt recovery restart");
3206
3331
  return { type: "restart" };
3207
3332
  }
3208
3333
  };
@@ -3229,8 +3354,8 @@ function getLaunchModeHandler(mode) {
3229
3354
  }
3230
3355
 
3231
3356
  // src/manager/restart-tracker.ts
3232
- import { createLogger as createLogger20 } from "@actant/shared";
3233
- var logger19 = createLogger20("restart-tracker");
3357
+ import { createLogger as createLogger21 } from "@actant/shared";
3358
+ var logger20 = createLogger21("restart-tracker");
3234
3359
  var DEFAULT_RESTART_POLICY = {
3235
3360
  maxRestarts: 5,
3236
3361
  backoffBaseMs: 1e3,
@@ -3248,12 +3373,12 @@ var RestartTracker = class {
3248
3373
  if (state.lastStartAt > 0) {
3249
3374
  const stableMs = Date.now() - state.lastStartAt;
3250
3375
  if (stableMs >= this.policy.resetAfterMs) {
3251
- logger19.info({ instanceName, stableMs, resetAfterMs: this.policy.resetAfterMs }, "Resetting restart counter \u2014 agent was stable");
3376
+ logger20.info({ instanceName, stableMs, resetAfterMs: this.policy.resetAfterMs }, "Resetting restart counter \u2014 agent was stable");
3252
3377
  state.count = 0;
3253
3378
  }
3254
3379
  }
3255
3380
  if (state.count >= this.policy.maxRestarts) {
3256
- logger19.warn({ instanceName, count: state.count, maxRestarts: this.policy.maxRestarts }, "Restart limit exceeded");
3381
+ logger20.warn({ instanceName, count: state.count, maxRestarts: this.policy.maxRestarts }, "Restart limit exceeded");
3257
3382
  return { allowed: false, delayMs: 0, attempt: state.count };
3258
3383
  }
3259
3384
  const delayMs = Math.min(
@@ -3266,7 +3391,7 @@ var RestartTracker = class {
3266
3391
  const state = this.getOrCreate(instanceName);
3267
3392
  state.count++;
3268
3393
  state.lastRestartAt = Date.now();
3269
- logger19.debug({ instanceName, count: state.count }, "Restart recorded");
3394
+ logger20.debug({ instanceName, count: state.count }, "Restart recorded");
3270
3395
  }
3271
3396
  recordStart(instanceName) {
3272
3397
  const state = this.getOrCreate(instanceName);
@@ -3293,8 +3418,8 @@ var RestartTracker = class {
3293
3418
 
3294
3419
  // src/communicator/claude-code-communicator.ts
3295
3420
  import { spawn as spawn4 } from "child_process";
3296
- import { createLogger as createLogger21 } from "@actant/shared";
3297
- var logger20 = createLogger21("claude-code-communicator");
3421
+ import { createLogger as createLogger22 } from "@actant/shared";
3422
+ var logger21 = createLogger22("claude-code-communicator");
3298
3423
  var DEFAULT_TIMEOUT_MS = 3e5;
3299
3424
  var ClaudeCodeCommunicator = class {
3300
3425
  executable;
@@ -3303,7 +3428,7 @@ var ClaudeCodeCommunicator = class {
3303
3428
  }
3304
3429
  async runPrompt(workspaceDir, prompt, options) {
3305
3430
  const args = this.buildArgs(prompt, options, "json");
3306
- logger20.debug({ workspaceDir, args }, "Running claude-code prompt");
3431
+ logger21.debug({ workspaceDir, args }, "Running claude-code prompt");
3307
3432
  return new Promise((resolve3, reject) => {
3308
3433
  const child = spawn4(this.executable, args, {
3309
3434
  cwd: workspaceDir,
@@ -3326,7 +3451,7 @@ var ClaudeCodeCommunicator = class {
3326
3451
  child.on("close", (code) => {
3327
3452
  clearTimeout(timer);
3328
3453
  if (code !== 0) {
3329
- logger20.warn({ code, stderr: stderr.slice(0, 500) }, "claude-code exited with error");
3454
+ logger21.warn({ code, stderr: stderr.slice(0, 500) }, "claude-code exited with error");
3330
3455
  reject(new Error(`claude-code exited with code ${code}: ${stderr.slice(0, 500)}`));
3331
3456
  return;
3332
3457
  }
@@ -3336,7 +3461,7 @@ var ClaudeCodeCommunicator = class {
3336
3461
  const sessionId = parsed["session_id"];
3337
3462
  const subtype = parsed["subtype"];
3338
3463
  if (subtype && subtype !== "success") {
3339
- logger20.warn({ subtype }, "claude-code returned non-success subtype");
3464
+ logger21.warn({ subtype }, "claude-code returned non-success subtype");
3340
3465
  }
3341
3466
  let text;
3342
3467
  if (result && result.length > 0) {
@@ -3360,7 +3485,7 @@ var ClaudeCodeCommunicator = class {
3360
3485
  }
3361
3486
  async *streamPrompt(workspaceDir, prompt, options) {
3362
3487
  const args = this.buildArgs(prompt, options, "stream-json");
3363
- logger20.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3488
+ logger21.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3364
3489
  const child = spawn4(this.executable, args, {
3365
3490
  cwd: workspaceDir,
3366
3491
  stdio: ["pipe", "pipe", "pipe"],
@@ -3457,11 +3582,11 @@ function parseStreamEvent(event) {
3457
3582
  }
3458
3583
 
3459
3584
  // src/communicator/cursor-communicator.ts
3460
- import { createLogger as createLogger22 } from "@actant/shared";
3461
- var logger21 = createLogger22("cursor-communicator");
3585
+ import { createLogger as createLogger23 } from "@actant/shared";
3586
+ var logger22 = createLogger23("cursor-communicator");
3462
3587
  var CursorCommunicator = class {
3463
3588
  async runPrompt(_workspaceDir, _prompt, _options) {
3464
- logger21.warn("Cursor backend does not yet support programmatic communication");
3589
+ logger22.warn("Cursor backend does not yet support programmatic communication");
3465
3590
  throw new Error(
3466
3591
  "Cursor backend does not support programmatic communication yet. Use claude-code backend for agent.run / agent.chat functionality."
3467
3592
  );
@@ -3500,7 +3625,7 @@ function createCommunicator(backendType, backendConfig) {
3500
3625
  }
3501
3626
 
3502
3627
  // src/manager/agent-manager.ts
3503
- var logger22 = createLogger23("agent-manager");
3628
+ var logger23 = createLogger24("agent-manager");
3504
3629
  var AgentManager = class {
3505
3630
  constructor(initializer, launcher, instancesBaseDir, options) {
3506
3631
  this.initializer = initializer;
@@ -3541,7 +3666,7 @@ var AgentManager = class {
3541
3666
  const dir = join17(this.instancesBaseDir, meta.name);
3542
3667
  const fixed = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3543
3668
  this.cache.set(meta.name, fixed);
3544
- logger22.info({ name: meta.name, oldStatus: meta.status, launchMode: meta.launchMode, recoveryAction: action.type }, "Stale status corrected");
3669
+ logger23.info({ name: meta.name, oldStatus: meta.status, launchMode: meta.launchMode, recoveryAction: action.type }, "Stale status corrected");
3545
3670
  if (action.type === "restart") {
3546
3671
  pendingRestarts.push(meta.name);
3547
3672
  }
@@ -3553,7 +3678,7 @@ var AgentManager = class {
3553
3678
  await this.moveToCorrupted(name);
3554
3679
  }
3555
3680
  this.watcher.start();
3556
- logger22.info({
3681
+ logger23.info({
3557
3682
  valid: valid.length,
3558
3683
  corrupted: corrupted.length,
3559
3684
  pendingRestarts: pendingRestarts.length
@@ -3561,9 +3686,9 @@ var AgentManager = class {
3561
3686
  for (const name of pendingRestarts) {
3562
3687
  try {
3563
3688
  await this.startAgent(name);
3564
- logger22.info({ name }, "Recovery restart succeeded");
3689
+ logger23.info({ name }, "Recovery restart succeeded");
3565
3690
  } catch (err) {
3566
- logger22.error({ name, error: err }, "Recovery restart failed");
3691
+ logger23.error({ name, error: err }, "Recovery restart failed");
3567
3692
  }
3568
3693
  }
3569
3694
  }
@@ -3614,13 +3739,17 @@ var AgentManager = class {
3614
3739
  }
3615
3740
  if (this.acpManager) {
3616
3741
  const { command, args } = resolveAcpBackend(meta.backendType, dir, meta.backendConfig);
3742
+ const providerEnv = buildProviderEnv(meta.providerConfig);
3617
3743
  const connResult = await this.acpManager.connect(name, {
3618
3744
  command,
3619
3745
  args,
3620
3746
  cwd: dir,
3621
- connectionOptions: { autoApprove: true }
3747
+ connectionOptions: {
3748
+ autoApprove: true,
3749
+ ...Object.keys(providerEnv).length > 0 ? { env: providerEnv } : {}
3750
+ }
3622
3751
  });
3623
- logger22.info({ name, acpOnly }, "ACP connection established");
3752
+ logger23.info({ name, acpOnly, providerType: meta.providerConfig?.type }, "ACP connection established");
3624
3753
  if (acpOnly && "pid" in connResult && typeof connResult.pid === "number") {
3625
3754
  pid = connResult.pid;
3626
3755
  this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
@@ -3632,7 +3761,7 @@ var AgentManager = class {
3632
3761
  this.watcher.watch(name, pid);
3633
3762
  }
3634
3763
  this.restartTracker.recordStart(name);
3635
- logger22.info({ name, pid, launchMode: starting.launchMode, acp: true }, "Agent started");
3764
+ logger23.info({ name, pid, launchMode: starting.launchMode, acp: true }, "Agent started");
3636
3765
  } catch (err) {
3637
3766
  if (this.acpManager?.has(name)) {
3638
3767
  await this.acpManager.disconnect(name).catch(() => {
@@ -3640,7 +3769,11 @@ var AgentManager = class {
3640
3769
  }
3641
3770
  const errored = await updateInstanceMeta(dir, { status: "error" });
3642
3771
  this.cache.set(name, errored);
3643
- throw err;
3772
+ if (err instanceof AgentLaunchError) throw err;
3773
+ const spawnMsg = err instanceof Error ? err.message : String(err);
3774
+ throw new AgentLaunchError(name, new Error(
3775
+ isSpawnNotFound(spawnMsg) ? buildSpawnNotFoundMessage(meta.backendType) : spawnMsg
3776
+ ));
3644
3777
  }
3645
3778
  }
3646
3779
  /**
@@ -3651,7 +3784,7 @@ var AgentManager = class {
3651
3784
  const meta = this.requireAgent(name);
3652
3785
  const dir = join17(this.instancesBaseDir, name);
3653
3786
  if (meta.status !== "running" && meta.status !== "starting") {
3654
- logger22.warn({ name, status: meta.status }, "Agent is not running, setting to stopped");
3787
+ logger23.warn({ name, status: meta.status }, "Agent is not running, setting to stopped");
3655
3788
  const stopped2 = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3656
3789
  this.cache.set(name, stopped2);
3657
3790
  return;
@@ -3661,7 +3794,7 @@ var AgentManager = class {
3661
3794
  this.cache.set(name, stopping);
3662
3795
  if (this.acpManager?.has(name)) {
3663
3796
  await this.acpManager.disconnect(name).catch((err) => {
3664
- logger22.warn({ name, error: err }, "Error disconnecting ACP during stop");
3797
+ logger23.warn({ name, error: err }, "Error disconnecting ACP during stop");
3665
3798
  });
3666
3799
  }
3667
3800
  const proc = this.processes.get(name);
@@ -3671,11 +3804,14 @@ var AgentManager = class {
3671
3804
  }
3672
3805
  const stopped = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3673
3806
  this.cache.set(name, stopped);
3674
- logger22.info({ name }, "Agent stopped");
3807
+ logger23.info({ name }, "Agent stopped");
3675
3808
  }
3676
3809
  /** Destroy an agent — stop it if running, then remove workspace. */
3677
3810
  async destroyAgent(name) {
3678
3811
  const meta = this.cache.get(name);
3812
+ if (!meta && !existsSync(join17(this.instancesBaseDir, name))) {
3813
+ throw new AgentNotFoundError(name);
3814
+ }
3679
3815
  if (meta && (meta.status === "running" || meta.status === "starting")) {
3680
3816
  await this.stopAgent(name);
3681
3817
  }
@@ -3684,7 +3820,7 @@ var AgentManager = class {
3684
3820
  await this.initializer.destroyInstance(name);
3685
3821
  this.cache.delete(name);
3686
3822
  this.processes.delete(name);
3687
- logger22.info({ name }, "Agent destroyed");
3823
+ logger23.info({ name }, "Agent destroyed");
3688
3824
  }
3689
3825
  /** Get agent metadata by name. */
3690
3826
  getAgent(name) {
@@ -3749,6 +3885,17 @@ var AgentManager = class {
3749
3885
  if (meta.status === "running" && meta.pid != null) {
3750
3886
  throw new AgentAlreadyAttachedError(name);
3751
3887
  }
3888
+ try {
3889
+ process.kill(pid, 0);
3890
+ } catch (err) {
3891
+ const code = err.code;
3892
+ if (code === "ESRCH") {
3893
+ throw new AgentLaunchError(
3894
+ name,
3895
+ new Error(`Process with PID ${pid} does not exist`)
3896
+ );
3897
+ }
3898
+ }
3752
3899
  const dir = join17(this.instancesBaseDir, name);
3753
3900
  const mergedMetadata = attachMetadata ? { ...meta.metadata, ...attachMetadata } : meta.metadata;
3754
3901
  const updated = await updateInstanceMeta(dir, {
@@ -3760,7 +3907,7 @@ var AgentManager = class {
3760
3907
  this.cache.set(name, updated);
3761
3908
  this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
3762
3909
  this.watcher.watch(name, pid);
3763
- logger22.info({ name, pid }, "External process attached");
3910
+ logger23.info({ name, pid }, "External process attached");
3764
3911
  return updated;
3765
3912
  }
3766
3913
  /**
@@ -3783,7 +3930,7 @@ var AgentManager = class {
3783
3930
  processOwnership: "managed"
3784
3931
  });
3785
3932
  this.cache.set(name, updated);
3786
- logger22.info({ name }, "External process detached");
3933
+ logger23.info({ name }, "External process detached");
3787
3934
  let workspaceCleaned = false;
3788
3935
  if (options?.cleanup && meta.workspacePolicy === "ephemeral") {
3789
3936
  await this.destroyAgent(name);
@@ -3801,7 +3948,7 @@ var AgentManager = class {
3801
3948
  const conn = this.acpManager.getConnection(name);
3802
3949
  const sessionId = this.acpManager.getPrimarySessionId(name);
3803
3950
  if (conn && sessionId) {
3804
- logger22.debug({ name, sessionId }, "Sending prompt via ACP");
3951
+ logger23.debug({ name, sessionId }, "Sending prompt via ACP");
3805
3952
  const result = await conn.prompt(sessionId, prompt);
3806
3953
  return { text: result.text, sessionId };
3807
3954
  }
@@ -3820,7 +3967,7 @@ var AgentManager = class {
3820
3967
  const conn = this.acpManager.getConnection(name);
3821
3968
  const sessionId = this.acpManager.getPrimarySessionId(name);
3822
3969
  if (conn && sessionId) {
3823
- logger22.debug({ name, sessionId }, "Streaming prompt via ACP");
3970
+ logger23.debug({ name, sessionId }, "Streaming prompt via ACP");
3824
3971
  return this.streamFromAcp(conn, sessionId, prompt);
3825
3972
  }
3826
3973
  }
@@ -3892,7 +4039,7 @@ var AgentManager = class {
3892
4039
  }
3893
4040
  const handler = getLaunchModeHandler(meta.launchMode);
3894
4041
  const action = handler.getProcessExitAction(instanceName, meta);
3895
- logger22.warn({ instanceName, pid, launchMode: meta.launchMode, action: action.type, previousStatus: meta.status }, "Agent process exited unexpectedly");
4042
+ logger23.warn({ instanceName, pid, launchMode: meta.launchMode, action: action.type, previousStatus: meta.status }, "Agent process exited unexpectedly");
3896
4043
  if (this.acpManager?.has(instanceName)) {
3897
4044
  await this.acpManager.disconnect(instanceName).catch(() => {
3898
4045
  });
@@ -3913,28 +4060,28 @@ var AgentManager = class {
3913
4060
  if (!decision.allowed) {
3914
4061
  const errored = await updateInstanceMeta(dir, { status: "error" });
3915
4062
  this.cache.set(instanceName, errored);
3916
- logger22.error({ instanceName, attempt: decision.attempt }, "Restart limit exceeded \u2014 marking as error");
4063
+ logger23.error({ instanceName, attempt: decision.attempt }, "Restart limit exceeded \u2014 marking as error");
3917
4064
  break;
3918
4065
  }
3919
- logger22.info({ instanceName, attempt: decision.attempt, delayMs: decision.delayMs }, "Scheduling crash restart with backoff");
4066
+ logger23.info({ instanceName, attempt: decision.attempt, delayMs: decision.delayMs }, "Scheduling crash restart with backoff");
3920
4067
  if (decision.delayMs > 0) {
3921
4068
  await delay(decision.delayMs);
3922
4069
  }
3923
4070
  this.restartTracker.recordRestart(instanceName);
3924
4071
  try {
3925
4072
  await this.startAgent(instanceName);
3926
- logger22.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
4073
+ logger23.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
3927
4074
  } catch (err) {
3928
- logger22.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
4075
+ logger23.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
3929
4076
  }
3930
4077
  break;
3931
4078
  }
3932
4079
  case "destroy":
3933
4080
  try {
3934
4081
  await this.destroyAgent(instanceName);
3935
- logger22.info({ instanceName }, "One-shot agent destroyed after exit");
4082
+ logger23.info({ instanceName }, "One-shot agent destroyed after exit");
3936
4083
  } catch (err) {
3937
- logger22.error({ instanceName, error: err }, "One-shot auto-destroy failed");
4084
+ logger23.error({ instanceName, error: err }, "One-shot auto-destroy failed");
3938
4085
  }
3939
4086
  break;
3940
4087
  case "mark-stopped":
@@ -3954,12 +4101,39 @@ var AgentManager = class {
3954
4101
  const src = join17(this.instancesBaseDir, name);
3955
4102
  const dest = join17(this.corruptedDir, `${name}-${Date.now()}`);
3956
4103
  await rename3(src, dest);
3957
- logger22.warn({ name, dest }, "Corrupted instance moved");
4104
+ logger23.warn({ name, dest }, "Corrupted instance moved");
3958
4105
  } catch (err) {
3959
- logger22.error({ name, error: err }, "Failed to move corrupted instance");
4106
+ logger23.error({ name, error: err }, "Failed to move corrupted instance");
3960
4107
  }
3961
4108
  }
3962
4109
  };
4110
+ function buildProviderEnv(providerConfig) {
4111
+ const env = {};
4112
+ const defaultDesc = modelProviderRegistry.getDefault();
4113
+ const providerType = providerConfig?.type ?? defaultDesc?.type;
4114
+ if (providerType) {
4115
+ env["ACTANT_PROVIDER"] = providerType;
4116
+ }
4117
+ const descriptor = providerType ? modelProviderRegistry.get(providerType) : defaultDesc;
4118
+ const apiKey = descriptor?.apiKey ?? process.env["ACTANT_API_KEY"];
4119
+ if (apiKey) {
4120
+ env["ACTANT_API_KEY"] = apiKey;
4121
+ }
4122
+ const baseUrl = providerConfig?.baseUrl ?? descriptor?.defaultBaseUrl;
4123
+ if (baseUrl) {
4124
+ env["ACTANT_BASE_URL"] = baseUrl;
4125
+ }
4126
+ return env;
4127
+ }
4128
+ function isSpawnNotFound(msg) {
4129
+ return /ENOENT|EINVAL|is not recognized|not found/i.test(msg);
4130
+ }
4131
+ function buildSpawnNotFoundMessage(backendType) {
4132
+ const hint = getInstallHint(backendType);
4133
+ const base = `Backend "${backendType}" executable not found.`;
4134
+ return hint ? `${base}
4135
+ Install with: ${hint}` : `${base} Ensure the required CLI is installed and in your PATH.`;
4136
+ }
3963
4137
 
3964
4138
  // src/manager/launcher/mock-launcher.ts
3965
4139
  var nextPid = 1e4;
@@ -3982,14 +4156,14 @@ var MockLauncher = class {
3982
4156
 
3983
4157
  // src/manager/launcher/process-launcher.ts
3984
4158
  import { spawn as spawn5 } from "child_process";
3985
- import { AgentLaunchError, createLogger as createLogger25 } from "@actant/shared";
4159
+ import { AgentLaunchError as AgentLaunchError2, createLogger as createLogger26 } from "@actant/shared";
3986
4160
 
3987
4161
  // src/manager/launcher/process-log-writer.ts
3988
4162
  import { createWriteStream } from "fs";
3989
4163
  import { mkdir as mkdir12, rename as rename4, stat as stat7, readFile as readFile5 } from "fs/promises";
3990
4164
  import { join as join18 } from "path";
3991
- import { createLogger as createLogger24 } from "@actant/shared";
3992
- var logger23 = createLogger24("process-log-writer");
4165
+ import { createLogger as createLogger25 } from "@actant/shared";
4166
+ var logger24 = createLogger25("process-log-writer");
3993
4167
  var DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
3994
4168
  var DEFAULT_MAX_FILES = 3;
3995
4169
  var ProcessLogWriter = class {
@@ -4026,7 +4200,7 @@ var ProcessLogWriter = class {
4026
4200
  } catch {
4027
4201
  this.stderrBytes = 0;
4028
4202
  }
4029
- logger23.debug({ logsDir: this.logsDir }, "Log writer initialized");
4203
+ logger24.debug({ logsDir: this.logsDir }, "Log writer initialized");
4030
4204
  }
4031
4205
  /**
4032
4206
  * Attach to readable streams from a spawned process.
@@ -4108,7 +4282,7 @@ var ProcessLogWriter = class {
4108
4282
  this.stderrStream = newStream;
4109
4283
  this.stderrBytes = 0;
4110
4284
  }
4111
- logger23.debug({ filename }, "Log file rotated");
4285
+ logger24.debug({ filename }, "Log file rotated");
4112
4286
  }
4113
4287
  closeStream(stream) {
4114
4288
  if (!stream) return Promise.resolve();
@@ -4119,7 +4293,7 @@ var ProcessLogWriter = class {
4119
4293
  };
4120
4294
 
4121
4295
  // src/manager/launcher/process-launcher.ts
4122
- var logger24 = createLogger25("process-launcher");
4296
+ var logger25 = createLogger26("process-launcher");
4123
4297
  var DEFAULT_TERMINATE_TIMEOUT = 5e3;
4124
4298
  var DEFAULT_SPAWN_VERIFY_DELAY = 500;
4125
4299
  var ProcessLauncher = class {
@@ -4141,7 +4315,7 @@ var ProcessLauncher = class {
4141
4315
  meta.backendConfig
4142
4316
  );
4143
4317
  const useAcp = isAcpBackend(meta.backendType);
4144
- logger24.info({ name: meta.name, command, args, backendType: meta.backendType, acp: useAcp }, "Spawning backend process");
4318
+ logger25.info({ name: meta.name, command, args, backendType: meta.backendType, acp: useAcp }, "Spawning backend process");
4145
4319
  const captureNonAcpLogs = !useAcp && this.enableProcessLogs;
4146
4320
  let stdio;
4147
4321
  if (useAcp) {
@@ -4173,7 +4347,7 @@ var ProcessLauncher = class {
4173
4347
  }
4174
4348
  });
4175
4349
  if ("error" in spawnResult) {
4176
- throw new AgentLaunchError(meta.name, spawnResult.error);
4350
+ throw new AgentLaunchError2(meta.name, spawnResult.error);
4177
4351
  }
4178
4352
  const pid = spawnResult.pid;
4179
4353
  if (!useAcp) {
@@ -4184,27 +4358,27 @@ var ProcessLauncher = class {
4184
4358
  earlyExit = true;
4185
4359
  });
4186
4360
  child.on("error", (err) => {
4187
- logger24.error({ name: meta.name, pid, error: err }, "Backend process error after spawn");
4361
+ logger25.error({ name: meta.name, pid, error: err }, "Backend process error after spawn");
4188
4362
  });
4189
4363
  if (this.spawnVerifyDelayMs > 0) {
4190
4364
  await delay(this.spawnVerifyDelayMs);
4191
4365
  if (earlyExit || !isProcessAlive(pid)) {
4192
- throw new AgentLaunchError(
4366
+ throw new AgentLaunchError2(
4193
4367
  meta.name,
4194
4368
  new Error(`Process exited immediately after spawn (pid=${pid}, command=${command})`)
4195
4369
  );
4196
4370
  }
4197
4371
  }
4198
- logger24.info({ name: meta.name, pid, command, acp: useAcp }, "Backend process spawned");
4372
+ logger25.info({ name: meta.name, pid, command, acp: useAcp }, "Backend process spawned");
4199
4373
  if (captureNonAcpLogs && child.stdout && child.stderr) {
4200
4374
  const logWriter = new ProcessLogWriter(workspaceDir, this.logWriterOptions);
4201
4375
  try {
4202
4376
  await logWriter.initialize();
4203
4377
  logWriter.attach(child.stdout, child.stderr);
4204
4378
  this.logWriters.set(meta.name, logWriter);
4205
- logger24.debug({ name: meta.name }, "Process log capture enabled");
4379
+ logger25.debug({ name: meta.name }, "Process log capture enabled");
4206
4380
  } catch (err) {
4207
- logger24.warn({ name: meta.name, error: err }, "Failed to initialize log writer, continuing without log capture");
4381
+ logger25.warn({ name: meta.name, error: err }, "Failed to initialize log writer, continuing without log capture");
4208
4382
  }
4209
4383
  }
4210
4384
  const result = {
@@ -4233,27 +4407,27 @@ var ProcessLauncher = class {
4233
4407
  this.logWriters.delete(instanceName);
4234
4408
  }
4235
4409
  if (!isProcessAlive(pid)) {
4236
- logger24.info({ instanceName, pid }, "Process already exited");
4410
+ logger25.info({ instanceName, pid }, "Process already exited");
4237
4411
  return;
4238
4412
  }
4239
- logger24.info({ instanceName, pid }, "Sending SIGTERM");
4413
+ logger25.info({ instanceName, pid }, "Sending SIGTERM");
4240
4414
  sendSignal(pid, "SIGTERM");
4241
4415
  const deadline = Date.now() + this.terminateTimeoutMs;
4242
4416
  const pollInterval = 200;
4243
4417
  while (Date.now() < deadline) {
4244
4418
  await delay(pollInterval);
4245
4419
  if (!isProcessAlive(pid)) {
4246
- logger24.info({ instanceName, pid }, "Process terminated gracefully");
4420
+ logger25.info({ instanceName, pid }, "Process terminated gracefully");
4247
4421
  return;
4248
4422
  }
4249
4423
  }
4250
- logger24.warn({ instanceName, pid }, "Process did not exit after SIGTERM, sending SIGKILL");
4424
+ logger25.warn({ instanceName, pid }, "Process did not exit after SIGTERM, sending SIGKILL");
4251
4425
  sendSignal(pid, "SIGKILL");
4252
4426
  await delay(500);
4253
4427
  if (isProcessAlive(pid)) {
4254
- logger24.error({ instanceName, pid }, "Process still alive after SIGKILL");
4428
+ logger25.error({ instanceName, pid }, "Process still alive after SIGKILL");
4255
4429
  } else {
4256
- logger24.info({ instanceName, pid }, "Process killed with SIGKILL");
4430
+ logger25.info({ instanceName, pid }, "Process killed with SIGKILL");
4257
4431
  }
4258
4432
  }
4259
4433
  };
@@ -4275,7 +4449,10 @@ var SkillDefinitionSchema = z4.object({
4275
4449
  name: z4.string().min(1),
4276
4450
  description: z4.string().optional(),
4277
4451
  content: z4.string().min(1),
4278
- tags: z4.array(z4.string()).optional()
4452
+ tags: z4.array(z4.string()).optional(),
4453
+ license: z4.string().optional(),
4454
+ compatibility: z4.string().optional(),
4455
+ allowedTools: z4.array(z4.string()).optional()
4279
4456
  }).passthrough();
4280
4457
  var SkillManager = class extends BaseComponentManager {
4281
4458
  componentType = "Skill";
@@ -4503,8 +4680,8 @@ var PluginManager = class extends BaseComponentManager {
4503
4680
  };
4504
4681
 
4505
4682
  // src/permissions/permission-policy-enforcer.ts
4506
- import { createLogger as createLogger26 } from "@actant/shared";
4507
- var logger25 = createLogger26("permission-policy-enforcer");
4683
+ import { createLogger as createLogger27 } from "@actant/shared";
4684
+ var logger26 = createLogger27("permission-policy-enforcer");
4508
4685
  var KIND_TO_TOOL = {
4509
4686
  read: "Read",
4510
4687
  edit: "Edit",
@@ -4521,7 +4698,7 @@ var PermissionPolicyEnforcer = class {
4521
4698
  }
4522
4699
  updateConfig(config) {
4523
4700
  this.config = { ...config };
4524
- logger25.info("Permission policy config updated");
4701
+ logger26.info("Permission policy config updated");
4525
4702
  }
4526
4703
  getConfig() {
4527
4704
  return this.config;
@@ -4679,8 +4856,8 @@ function escapeRegex(str) {
4679
4856
  }
4680
4857
 
4681
4858
  // src/permissions/permission-audit.ts
4682
- import { createLogger as createLogger27 } from "@actant/shared";
4683
- var logger26 = createLogger27("permission-audit");
4859
+ import { createLogger as createLogger28 } from "@actant/shared";
4860
+ var logger27 = createLogger28("permission-audit");
4684
4861
  var PermissionAuditLogger = class {
4685
4862
  instanceName;
4686
4863
  constructor(instanceName) {
@@ -4693,7 +4870,7 @@ var PermissionAuditLogger = class {
4693
4870
  ...detail,
4694
4871
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4695
4872
  };
4696
- logger26.info(entry, `[audit] ${event}`);
4873
+ logger27.info(entry, `[audit] ${event}`);
4697
4874
  }
4698
4875
  logEvaluation(toolCall, decision) {
4699
4876
  this.log("permission.evaluated", { toolCall, decision });
@@ -4714,8 +4891,8 @@ var PermissionAuditLogger = class {
4714
4891
 
4715
4892
  // src/session/session-registry.ts
4716
4893
  import { randomUUID as randomUUID8 } from "crypto";
4717
- import { createLogger as createLogger28 } from "@actant/shared";
4718
- var logger27 = createLogger28("session-registry");
4894
+ import { createLogger as createLogger29 } from "@actant/shared";
4895
+ var logger28 = createLogger29("session-registry");
4719
4896
  var DEFAULT_IDLE_TTL_MS = 30 * 60 * 1e3;
4720
4897
  var TTL_CHECK_INTERVAL_MS = 60 * 1e3;
4721
4898
  var SessionRegistry = class {
@@ -4746,7 +4923,7 @@ var SessionRegistry = class {
4746
4923
  idleTtlMs: opts.idleTtlMs ?? this.defaultIdleTtlMs
4747
4924
  };
4748
4925
  this.sessions.set(session.sessionId, session);
4749
- logger27.info({ sessionId: session.sessionId, agentName: opts.agentName, clientId: opts.clientId }, "Session created");
4926
+ logger28.info({ sessionId: session.sessionId, agentName: opts.agentName, clientId: opts.clientId }, "Session created");
4750
4927
  return session;
4751
4928
  }
4752
4929
  /** Get a session by ID, or undefined if not found. */
@@ -4778,7 +4955,7 @@ var SessionRegistry = class {
4778
4955
  session.clientId = null;
4779
4956
  session.state = "idle";
4780
4957
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4781
- logger27.info({ sessionId, agentName: session.agentName }, "Session released to idle");
4958
+ logger28.info({ sessionId, agentName: session.agentName }, "Session released to idle");
4782
4959
  }
4783
4960
  /**
4784
4961
  * Resume an idle session, binding it to a (potentially new) client.
@@ -4791,7 +4968,7 @@ var SessionRegistry = class {
4791
4968
  session.clientId = clientId;
4792
4969
  session.state = "active";
4793
4970
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4794
- logger27.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
4971
+ logger28.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
4795
4972
  return true;
4796
4973
  }
4797
4974
  /** Explicitly close and remove a session. */
@@ -4799,7 +4976,7 @@ var SessionRegistry = class {
4799
4976
  const session = this.sessions.get(sessionId);
4800
4977
  if (!session) return false;
4801
4978
  this.sessions.delete(sessionId);
4802
- logger27.info({ sessionId, agentName: session.agentName }, "Session closed");
4979
+ logger28.info({ sessionId, agentName: session.agentName }, "Session closed");
4803
4980
  return true;
4804
4981
  }
4805
4982
  /** Close all sessions for a given agent. */
@@ -4812,7 +4989,7 @@ var SessionRegistry = class {
4812
4989
  }
4813
4990
  }
4814
4991
  if (count > 0) {
4815
- logger27.info({ agentName, count }, "Sessions closed for agent");
4992
+ logger28.info({ agentName, count }, "Sessions closed for agent");
4816
4993
  }
4817
4994
  return count;
4818
4995
  }
@@ -4836,7 +5013,7 @@ var SessionRegistry = class {
4836
5013
  if (now - lastActivity > session.idleTtlMs) {
4837
5014
  session.state = "expired";
4838
5015
  this.sessions.delete(id);
4839
- logger27.info({ sessionId: id, agentName: session.agentName, idleMs: now - lastActivity }, "Session expired (TTL)");
5016
+ logger28.info({ sessionId: id, agentName: session.agentName, idleMs: now - lastActivity }, "Session expired (TTL)");
4840
5017
  this.onExpireCallback?.(session);
4841
5018
  }
4842
5019
  }
@@ -4848,13 +5025,13 @@ import { join as join20 } from "path";
4848
5025
  import { mkdir as mkdir13, rm as rm5 } from "fs/promises";
4849
5026
  import { execFile } from "child_process";
4850
5027
  import { promisify } from "util";
4851
- import { createLogger as createLogger30 } from "@actant/shared";
5028
+ import { createLogger as createLogger31 } from "@actant/shared";
4852
5029
 
4853
5030
  // src/source/local-source.ts
4854
5031
  import { readFile as readFile6, readdir as readdir5, stat as stat8 } from "fs/promises";
4855
5032
  import { join as join19, extname as extname3 } from "path";
4856
- import { createLogger as createLogger29 } from "@actant/shared";
4857
- var logger28 = createLogger29("local-source");
5033
+ import { createLogger as createLogger30 } from "@actant/shared";
5034
+ var logger29 = createLogger30("local-source");
4858
5035
  var LocalSource = class {
4859
5036
  type = "local";
4860
5037
  packageName;
@@ -4863,6 +5040,9 @@ var LocalSource = class {
4863
5040
  this.packageName = packageName;
4864
5041
  this.config = config;
4865
5042
  }
5043
+ getRootDir() {
5044
+ return this.config.path;
5045
+ }
4866
5046
  async fetch() {
4867
5047
  return this.loadPackage();
4868
5048
  }
@@ -4882,7 +5062,7 @@ var LocalSource = class {
4882
5062
  this.loadPresets(rootDir, manifest.presets),
4883
5063
  this.loadJsonDir(rootDir, manifest.components?.templates, "templates")
4884
5064
  ]);
4885
- logger28.info({ packageName: this.packageName, rootDir }, "Local package loaded");
5065
+ logger29.info({ packageName: this.packageName, rootDir }, "Local package loaded");
4886
5066
  return { manifest, skills, prompts, mcpServers, workflows, presets, templates };
4887
5067
  }
4888
5068
  async loadManifest(rootDir) {
@@ -4891,7 +5071,7 @@ var LocalSource = class {
4891
5071
  const raw = await readFile6(manifestPath, "utf-8");
4892
5072
  return JSON.parse(raw);
4893
5073
  } catch {
4894
- logger28.debug({ rootDir }, "No actant.json found, scanning directories");
5074
+ logger29.debug({ rootDir }, "No actant.json found, scanning directories");
4895
5075
  return { name: this.packageName };
4896
5076
  }
4897
5077
  }
@@ -4903,7 +5083,7 @@ var LocalSource = class {
4903
5083
  const raw = await readFile6(join19(rootDir, relPath), "utf-8");
4904
5084
  items2.push(JSON.parse(raw));
4905
5085
  } catch (err) {
4906
- logger28.warn({ file: relPath, error: err }, `Failed to load from manifest, skipping`);
5086
+ logger29.warn({ file: relPath, error: err }, `Failed to load from manifest, skipping`);
4907
5087
  }
4908
5088
  }
4909
5089
  return items2;
@@ -4926,7 +5106,7 @@ var LocalSource = class {
4926
5106
  const raw = await readFile6(fullPath, "utf-8");
4927
5107
  items.push(JSON.parse(raw));
4928
5108
  } catch (err) {
4929
- logger28.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
5109
+ logger29.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
4930
5110
  }
4931
5111
  }
4932
5112
  for (const entry of entries) {
@@ -4951,7 +5131,7 @@ var LocalSource = class {
4951
5131
  }
4952
5132
  }
4953
5133
  if (subDir === "skills") {
4954
- const { parseSkillMd } = await import("./skill-md-parser-2HXC4AAW.js");
5134
+ const { parseSkillMd } = await import("./skill-md-parser-HXLTZAUU.js");
4955
5135
  for (const entry of entries) {
4956
5136
  if (extname3(entry) === ".json") continue;
4957
5137
  const subDirPath = join19(dirPath, entry);
@@ -4976,7 +5156,7 @@ var LocalSource = class {
4976
5156
 
4977
5157
  // src/source/github-source.ts
4978
5158
  var execFileAsync = promisify(execFile);
4979
- var logger29 = createLogger30("github-source");
5159
+ var logger30 = createLogger31("github-source");
4980
5160
  var GitHubSource = class {
4981
5161
  type = "github";
4982
5162
  packageName;
@@ -4987,6 +5167,9 @@ var GitHubSource = class {
4987
5167
  this.config = config;
4988
5168
  this.cacheDir = join20(cacheDir, packageName);
4989
5169
  }
5170
+ getRootDir() {
5171
+ return this.cacheDir;
5172
+ }
4990
5173
  async fetch() {
4991
5174
  await this.clone();
4992
5175
  return this.readCached();
@@ -4995,21 +5178,21 @@ var GitHubSource = class {
4995
5178
  try {
4996
5179
  await this.pull();
4997
5180
  } catch {
4998
- logger29.info("Pull failed, re-cloning");
5181
+ logger30.info("Pull failed, re-cloning");
4999
5182
  await this.clone();
5000
5183
  }
5001
5184
  return this.readCached();
5002
5185
  }
5003
5186
  async dispose() {
5004
5187
  await rm5(this.cacheDir, { recursive: true, force: true });
5005
- logger29.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
5188
+ logger30.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
5006
5189
  }
5007
5190
  async clone() {
5008
5191
  await rm5(this.cacheDir, { recursive: true, force: true });
5009
5192
  await mkdir13(this.cacheDir, { recursive: true });
5010
5193
  const branch = this.config.branch ?? "main";
5011
5194
  const args = ["clone", "--depth", "1", "--branch", branch, this.config.url, this.cacheDir];
5012
- logger29.info({ url: this.config.url, branch }, "Cloning repository");
5195
+ logger30.info({ url: this.config.url, branch }, "Cloning repository");
5013
5196
  await execFileAsync("git", args, { timeout: 6e4 });
5014
5197
  }
5015
5198
  async pull() {
@@ -5027,7 +5210,7 @@ var GitHubSource = class {
5027
5210
  // src/source/source-manager.ts
5028
5211
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir14 } from "fs/promises";
5029
5212
  import { join as join21 } from "path";
5030
- import { createLogger as createLogger31 } from "@actant/shared";
5213
+ import { createLogger as createLogger32 } from "@actant/shared";
5031
5214
 
5032
5215
  // src/version/sync-report.ts
5033
5216
  function createEmptySyncReport() {
@@ -5052,7 +5235,7 @@ var DEFAULT_SOURCE_CONFIG = {
5052
5235
  url: "https://github.com/blackplume233/actant-hub.git",
5053
5236
  branch: "main"
5054
5237
  };
5055
- var logger30 = createLogger31("source-manager");
5238
+ var logger31 = createLogger32("source-manager");
5056
5239
  var SourceManager = class {
5057
5240
  sources = /* @__PURE__ */ new Map();
5058
5241
  presets = /* @__PURE__ */ new Map();
@@ -5080,7 +5263,7 @@ var SourceManager = class {
5080
5263
  this.sources.set(name, source);
5081
5264
  this.injectComponents(name, result);
5082
5265
  await this.persistSources();
5083
- logger30.info({ name, type: config.type }, "Source added");
5266
+ logger31.info({ name, type: config.type }, "Source added");
5084
5267
  return result;
5085
5268
  }
5086
5269
  async syncSource(name) {
@@ -5096,7 +5279,7 @@ var SourceManager = class {
5096
5279
  const newSnapshot = this.snapshotComponents(name);
5097
5280
  await this.persistSources();
5098
5281
  const report = this.buildSyncReport(oldSnapshot, newSnapshot);
5099
- logger30.info({ name, report }, "Source synced");
5282
+ logger31.info({ name, report }, "Source synced");
5100
5283
  return { fetchResult: result, report };
5101
5284
  }
5102
5285
  async syncAll() {
@@ -5109,7 +5292,7 @@ var SourceManager = class {
5109
5292
  const { report } = await this.syncSourceWithReport(name);
5110
5293
  reports.push(report);
5111
5294
  } catch (err) {
5112
- logger30.warn({ name, error: err }, "Failed to sync source, skipping");
5295
+ logger31.warn({ name, error: err }, "Failed to sync source, skipping");
5113
5296
  }
5114
5297
  }
5115
5298
  return { report: mergeSyncReports(reports) };
@@ -5122,7 +5305,7 @@ var SourceManager = class {
5122
5305
  await source.dispose();
5123
5306
  this.sources.delete(name);
5124
5307
  await this.persistSources();
5125
- logger30.info({ name }, "Source removed");
5308
+ logger31.info({ name }, "Source removed");
5126
5309
  return true;
5127
5310
  }
5128
5311
  listSources() {
@@ -5135,6 +5318,10 @@ var SourceManager = class {
5135
5318
  hasSource(name) {
5136
5319
  return this.sources.has(name);
5137
5320
  }
5321
+ /** Resolve a registered source name to its filesystem root directory. */
5322
+ getSourceRootDir(name) {
5323
+ return this.getSourceOrThrow(name).getRootDir();
5324
+ }
5138
5325
  // ---------------------------------------------------------------------------
5139
5326
  // Preset operations
5140
5327
  // ---------------------------------------------------------------------------
@@ -5206,7 +5393,7 @@ var SourceManager = class {
5206
5393
  const data = JSON.parse(raw);
5207
5394
  entries = Object.entries(data.sources ?? {}).map(([name, config]) => ({ name, config }));
5208
5395
  } catch {
5209
- logger30.debug("No sources.json found, starting with empty sources");
5396
+ logger31.debug("No sources.json found, starting with empty sources");
5210
5397
  }
5211
5398
  for (const entry of entries) {
5212
5399
  try {
@@ -5214,9 +5401,9 @@ var SourceManager = class {
5214
5401
  const result = await source.fetch();
5215
5402
  this.sources.set(entry.name, source);
5216
5403
  this.injectComponents(entry.name, result);
5217
- logger30.info({ name: entry.name }, "Source restored from config");
5404
+ logger31.info({ name: entry.name }, "Source restored from config");
5218
5405
  } catch (err) {
5219
- logger30.warn({ name: entry.name, error: err }, "Failed to restore source, skipping");
5406
+ logger31.warn({ name: entry.name, error: err }, "Failed to restore source, skipping");
5220
5407
  }
5221
5408
  }
5222
5409
  await this.ensureDefaultSource();
@@ -5230,9 +5417,9 @@ var SourceManager = class {
5230
5417
  if (this.sources.has(DEFAULT_SOURCE_NAME)) return;
5231
5418
  try {
5232
5419
  await this.addSource(DEFAULT_SOURCE_NAME, DEFAULT_SOURCE_CONFIG);
5233
- logger30.info("Default source registered: %s", DEFAULT_SOURCE_NAME);
5420
+ logger31.info("Default source registered: %s", DEFAULT_SOURCE_NAME);
5234
5421
  } catch (err) {
5235
- logger30.debug({ error: err }, "Failed to register default source (offline?), skipping");
5422
+ logger31.debug({ error: err }, "Failed to register default source (offline?), skipping");
5236
5423
  }
5237
5424
  }
5238
5425
  // ---------------------------------------------------------------------------
@@ -5266,12 +5453,24 @@ var SourceManager = class {
5266
5453
  this.presets.set(`${packageName}@${preset.name}`, preset);
5267
5454
  }
5268
5455
  if (this.managers.templateRegistry && result.templates.length > 0) {
5456
+ const nsRef = (ref) => ref.includes("@") ? ref : `${packageName}@${ref}`;
5269
5457
  for (const template of result.templates) {
5270
- const nsTemplate = { ...template, name: `${packageName}@${template.name}` };
5458
+ const dc = template.domainContext;
5459
+ const nsTemplate = {
5460
+ ...template,
5461
+ name: `${packageName}@${template.name}`,
5462
+ domainContext: {
5463
+ ...dc,
5464
+ skills: dc.skills?.map(nsRef),
5465
+ prompts: dc.prompts?.map(nsRef),
5466
+ subAgents: dc.subAgents?.map(nsRef),
5467
+ workflow: dc.workflow ? nsRef(dc.workflow) : dc.workflow
5468
+ }
5469
+ };
5271
5470
  this.managers.templateRegistry.register(nsTemplate);
5272
5471
  }
5273
5472
  }
5274
- logger30.debug(
5473
+ logger31.debug(
5275
5474
  {
5276
5475
  packageName,
5277
5476
  skills: result.skills.length,
@@ -5404,6 +5603,671 @@ function isMajorVersionChange(oldVersion, newVersion) {
5404
5603
  if (oldMajor === void 0 || newMajor === void 0) return false;
5405
5604
  return newMajor !== oldMajor;
5406
5605
  }
5606
+
5607
+ // src/source/source-validator.ts
5608
+ import { readFile as readFile8, readdir as readdir6, stat as stat9, access as access4 } from "fs/promises";
5609
+ import { join as join22, relative as relative2, extname as extname4 } from "path";
5610
+
5611
+ // src/source/source-schemas.ts
5612
+ import { z as z9 } from "zod";
5613
+ var VersionedComponentFields = {
5614
+ name: z9.string().min(1, "name is required"),
5615
+ version: z9.string().optional(),
5616
+ description: z9.string().optional(),
5617
+ $type: z9.string().optional(),
5618
+ $version: z9.number().optional(),
5619
+ origin: z9.object({
5620
+ type: z9.enum(["builtin", "source", "local"]),
5621
+ sourceName: z9.string().optional(),
5622
+ syncHash: z9.string().optional(),
5623
+ syncedAt: z9.string().optional(),
5624
+ modified: z9.boolean().optional()
5625
+ }).optional(),
5626
+ tags: z9.array(z9.string()).optional()
5627
+ };
5628
+ var PackageManifestSchema = z9.object({
5629
+ name: z9.string().min(1, "name is required"),
5630
+ version: z9.string().optional(),
5631
+ description: z9.string().optional(),
5632
+ components: z9.object({
5633
+ skills: z9.array(z9.string()).optional(),
5634
+ prompts: z9.array(z9.string()).optional(),
5635
+ mcp: z9.array(z9.string()).optional(),
5636
+ workflows: z9.array(z9.string()).optional(),
5637
+ templates: z9.array(z9.string()).optional()
5638
+ }).optional(),
5639
+ presets: z9.array(z9.string()).optional()
5640
+ });
5641
+ var SkillDefinitionSchema2 = z9.object({
5642
+ ...VersionedComponentFields,
5643
+ content: z9.string().min(1, "content is required"),
5644
+ license: z9.string().optional(),
5645
+ compatibility: z9.string().optional(),
5646
+ allowedTools: z9.array(z9.string()).optional()
5647
+ });
5648
+ var PromptDefinitionSchema2 = z9.object({
5649
+ ...VersionedComponentFields,
5650
+ content: z9.string().min(1, "content is required"),
5651
+ variables: z9.array(z9.string()).optional()
5652
+ });
5653
+ var McpServerDefinitionSchema2 = z9.object({
5654
+ ...VersionedComponentFields,
5655
+ command: z9.string().min(1, "command is required"),
5656
+ args: z9.array(z9.string()).optional(),
5657
+ env: z9.record(z9.string(), z9.string()).optional()
5658
+ });
5659
+ var WorkflowDefinitionSchema2 = z9.object({
5660
+ ...VersionedComponentFields,
5661
+ content: z9.string().min(1, "content is required")
5662
+ });
5663
+ var PresetDefinitionSchema = z9.object({
5664
+ name: z9.string().min(1, "name is required"),
5665
+ version: z9.string().optional(),
5666
+ description: z9.string().optional(),
5667
+ skills: z9.array(z9.string()).optional(),
5668
+ prompts: z9.array(z9.string()).optional(),
5669
+ mcpServers: z9.array(z9.string()).optional(),
5670
+ workflows: z9.array(z9.string()).optional(),
5671
+ templates: z9.array(z9.string()).optional()
5672
+ });
5673
+
5674
+ // src/source/source-validator.ts
5675
+ var COMPONENT_DIR_SCHEMAS = {
5676
+ skills: SkillDefinitionSchema2,
5677
+ prompts: PromptDefinitionSchema2,
5678
+ mcp: McpServerDefinitionSchema2,
5679
+ workflows: WorkflowDefinitionSchema2,
5680
+ presets: PresetDefinitionSchema
5681
+ };
5682
+ var COMPONENT_DIRS = ["skills", "prompts", "mcp", "workflows", "templates", "presets"];
5683
+ var SourceValidator = class {
5684
+ /**
5685
+ * Validate all assets in a source directory.
5686
+ * Returns a report with pass/warn/error counts and detailed issues.
5687
+ */
5688
+ async validate(rootDir, options) {
5689
+ const issues = [];
5690
+ let passCount = 0;
5691
+ const validatedFiles = /* @__PURE__ */ new Set();
5692
+ const compat = options?.compat;
5693
+ const manifest = await this.validateManifest(rootDir, issues);
5694
+ if (manifest) passCount++;
5695
+ const componentNames = /* @__PURE__ */ new Map();
5696
+ if (manifest) {
5697
+ passCount += await this.validateExplicitFiles(rootDir, manifest, issues, componentNames, validatedFiles, compat);
5698
+ }
5699
+ passCount += await this.validateComponentDirs(rootDir, issues, componentNames, validatedFiles, compat);
5700
+ await this.validatePresetReferences(rootDir, manifest, componentNames, issues);
5701
+ const errorCount = issues.filter((i) => i.severity === "error").length;
5702
+ const warnCount = issues.filter((i) => i.severity === "warning").length;
5703
+ const valid = options?.strict ? errorCount === 0 && warnCount === 0 : errorCount === 0;
5704
+ return {
5705
+ valid,
5706
+ sourceName: manifest?.name ?? "unknown",
5707
+ rootDir,
5708
+ summary: { pass: passCount, warn: warnCount, error: errorCount },
5709
+ issues
5710
+ };
5711
+ }
5712
+ // -------------------------------------------------------------------------
5713
+ // Layer 1: Manifest validation
5714
+ // -------------------------------------------------------------------------
5715
+ async validateManifest(rootDir, issues) {
5716
+ const manifestPath = join22(rootDir, "actant.json");
5717
+ let raw;
5718
+ try {
5719
+ raw = await readFile8(manifestPath, "utf-8");
5720
+ } catch {
5721
+ issues.push({
5722
+ severity: "error",
5723
+ path: "actant.json",
5724
+ message: "actant.json not found in source root",
5725
+ code: "MANIFEST_MISSING"
5726
+ });
5727
+ return null;
5728
+ }
5729
+ let data;
5730
+ try {
5731
+ data = JSON.parse(raw);
5732
+ } catch (err) {
5733
+ issues.push({
5734
+ severity: "error",
5735
+ path: "actant.json",
5736
+ message: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
5737
+ code: "MANIFEST_INVALID_JSON"
5738
+ });
5739
+ return null;
5740
+ }
5741
+ const result = PackageManifestSchema.safeParse(data);
5742
+ if (!result.success) {
5743
+ for (const issue of result.error.issues) {
5744
+ issues.push({
5745
+ severity: "error",
5746
+ path: "actant.json",
5747
+ message: `${issue.path.join(".")}: ${issue.message}`,
5748
+ code: "MANIFEST_SCHEMA"
5749
+ });
5750
+ }
5751
+ return null;
5752
+ }
5753
+ const manifest = result.data;
5754
+ await this.verifyManifestFileRefs(rootDir, manifest, issues);
5755
+ return manifest;
5756
+ }
5757
+ async verifyManifestFileRefs(rootDir, manifest, issues) {
5758
+ const allRefs = [];
5759
+ if (manifest.components) {
5760
+ for (const [type, files] of Object.entries(manifest.components)) {
5761
+ if (files) {
5762
+ for (const f of files) {
5763
+ allRefs.push({ ref: f, section: `components.${type}` });
5764
+ }
5765
+ }
5766
+ }
5767
+ }
5768
+ if (manifest.presets) {
5769
+ for (const f of manifest.presets) {
5770
+ allRefs.push({ ref: f, section: "presets" });
5771
+ }
5772
+ }
5773
+ for (const { ref, section } of allRefs) {
5774
+ const fullPath = join22(rootDir, ref);
5775
+ try {
5776
+ await access4(fullPath);
5777
+ } catch {
5778
+ issues.push({
5779
+ severity: "error",
5780
+ path: `actant.json`,
5781
+ component: ref,
5782
+ message: `File referenced in ${section} does not exist: ${ref}`,
5783
+ code: "MANIFEST_FILE_MISSING"
5784
+ });
5785
+ }
5786
+ }
5787
+ }
5788
+ // -------------------------------------------------------------------------
5789
+ // Layer 2a: Validate files explicitly listed in manifest
5790
+ // -------------------------------------------------------------------------
5791
+ async validateExplicitFiles(rootDir, manifest, issues, componentNames, validatedFiles, compat) {
5792
+ let passCount = 0;
5793
+ const components = manifest.components;
5794
+ if (!components && !manifest.presets) return 0;
5795
+ if (components) {
5796
+ for (const [type, files] of Object.entries(components)) {
5797
+ if (!files) continue;
5798
+ for (const filePath of files) {
5799
+ validatedFiles.add(filePath);
5800
+ const ok = await this.validateSingleFile(rootDir, filePath, type, issues, componentNames);
5801
+ if (ok) passCount++;
5802
+ }
5803
+ }
5804
+ }
5805
+ const presets = manifest.presets;
5806
+ if (presets) {
5807
+ for (const filePath of presets) {
5808
+ validatedFiles.add(filePath);
5809
+ const ok = await this.validateSingleFile(rootDir, filePath, "presets", issues, componentNames);
5810
+ if (ok) passCount++;
5811
+ }
5812
+ }
5813
+ void compat;
5814
+ return passCount;
5815
+ }
5816
+ // -------------------------------------------------------------------------
5817
+ // Layer 2b: Scan component directories
5818
+ // -------------------------------------------------------------------------
5819
+ async validateComponentDirs(rootDir, issues, componentNames, validatedFiles, compat) {
5820
+ let passCount = 0;
5821
+ for (const dir of COMPONENT_DIRS) {
5822
+ const dirPath = join22(rootDir, dir);
5823
+ try {
5824
+ const dirStat = await stat9(dirPath);
5825
+ if (!dirStat.isDirectory()) continue;
5826
+ } catch {
5827
+ continue;
5828
+ }
5829
+ passCount += await this.scanDirectory(rootDir, dirPath, dir, issues, componentNames, validatedFiles, compat);
5830
+ }
5831
+ return passCount;
5832
+ }
5833
+ async scanDirectory(rootDir, dirPath, componentType, issues, componentNames, validatedFiles, compat) {
5834
+ let passCount = 0;
5835
+ const entries = await readdir6(dirPath);
5836
+ for (const entry of entries) {
5837
+ const fullPath = join22(dirPath, entry);
5838
+ const relPath = relative2(rootDir, fullPath).replace(/\\/g, "/");
5839
+ const entryStat = await stat9(fullPath).catch(() => null);
5840
+ if (!entryStat) continue;
5841
+ if (entryStat.isFile() && extname4(entry) === ".json") {
5842
+ if (validatedFiles.has(relPath)) continue;
5843
+ validatedFiles.add(relPath);
5844
+ const ok = await this.validateSingleFile(rootDir, relPath, componentType, issues, componentNames);
5845
+ if (ok) passCount++;
5846
+ } else if (entryStat.isDirectory()) {
5847
+ if (componentType === "skills") {
5848
+ const skillMdPath = join22(fullPath, "SKILL.md");
5849
+ try {
5850
+ await access4(skillMdPath);
5851
+ const skillRelPath = relative2(rootDir, skillMdPath).replace(/\\/g, "/");
5852
+ if (!validatedFiles.has(skillRelPath)) {
5853
+ validatedFiles.add(skillRelPath);
5854
+ const ok = await this.validateSkillMd(rootDir, skillRelPath, issues, componentNames, entry, compat);
5855
+ if (ok) passCount++;
5856
+ }
5857
+ if (compat === "agent-skills") {
5858
+ await this.validateSkillDirConventions(rootDir, fullPath, entry, issues);
5859
+ }
5860
+ } catch {
5861
+ const manifestJsonPath = join22(fullPath, "manifest.json");
5862
+ try {
5863
+ await access4(manifestJsonPath);
5864
+ const mRelPath = relative2(rootDir, manifestJsonPath).replace(/\\/g, "/");
5865
+ if (!validatedFiles.has(mRelPath)) {
5866
+ validatedFiles.add(mRelPath);
5867
+ const ok = await this.validateSingleFile(rootDir, mRelPath, componentType, issues, componentNames);
5868
+ if (ok) passCount++;
5869
+ }
5870
+ } catch {
5871
+ }
5872
+ }
5873
+ }
5874
+ }
5875
+ }
5876
+ return passCount;
5877
+ }
5878
+ // -------------------------------------------------------------------------
5879
+ // Single file validation
5880
+ // -------------------------------------------------------------------------
5881
+ async validateSingleFile(rootDir, relPath, componentType, issues, componentNames) {
5882
+ const fullPath = join22(rootDir, relPath);
5883
+ let raw;
5884
+ try {
5885
+ raw = await readFile8(fullPath, "utf-8");
5886
+ } catch {
5887
+ issues.push({
5888
+ severity: "error",
5889
+ path: relPath,
5890
+ message: "Cannot read file",
5891
+ code: "FILE_UNREADABLE"
5892
+ });
5893
+ return false;
5894
+ }
5895
+ let data;
5896
+ try {
5897
+ data = JSON.parse(raw);
5898
+ } catch (err) {
5899
+ issues.push({
5900
+ severity: "error",
5901
+ path: relPath,
5902
+ message: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
5903
+ code: "INVALID_JSON"
5904
+ });
5905
+ return false;
5906
+ }
5907
+ if (componentType === "templates") {
5908
+ return this.validateTemplateComponent(relPath, data, issues, componentNames);
5909
+ }
5910
+ const schema = COMPONENT_DIR_SCHEMAS[componentType];
5911
+ if (!schema) return true;
5912
+ const result = schema.safeParse(data);
5913
+ if (!result.success) {
5914
+ for (const issue of result.error.issues) {
5915
+ issues.push({
5916
+ severity: "error",
5917
+ path: relPath,
5918
+ component: data?.name,
5919
+ message: `${issue.path.join(".")}: ${issue.message}`,
5920
+ code: "COMPONENT_SCHEMA"
5921
+ });
5922
+ }
5923
+ return false;
5924
+ }
5925
+ const parsed = result.data;
5926
+ this.trackComponentName(componentNames, componentType, parsed.name);
5927
+ if (!parsed.description) {
5928
+ issues.push({
5929
+ severity: "warning",
5930
+ path: relPath,
5931
+ component: parsed.name,
5932
+ message: `Missing "description" field`,
5933
+ code: "MISSING_DESCRIPTION"
5934
+ });
5935
+ }
5936
+ return true;
5937
+ }
5938
+ validateTemplateComponent(relPath, data, issues, componentNames) {
5939
+ const schemaResult = AgentTemplateSchema.safeParse(data);
5940
+ if (!schemaResult.success) {
5941
+ for (const issue of schemaResult.error.issues) {
5942
+ issues.push({
5943
+ severity: "error",
5944
+ path: relPath,
5945
+ component: data?.name,
5946
+ message: `${issue.path.join(".")}: ${issue.message}`,
5947
+ code: "TEMPLATE_SCHEMA"
5948
+ });
5949
+ }
5950
+ return false;
5951
+ }
5952
+ const semanticResult = validateTemplate(data);
5953
+ for (const w of semanticResult.warnings ?? []) {
5954
+ issues.push({
5955
+ severity: "warning",
5956
+ path: relPath,
5957
+ component: schemaResult.data.name,
5958
+ message: w.message,
5959
+ code: w.code ?? "TEMPLATE_SEMANTIC"
5960
+ });
5961
+ }
5962
+ this.trackComponentName(componentNames, "templates", schemaResult.data.name);
5963
+ return true;
5964
+ }
5965
+ // -------------------------------------------------------------------------
5966
+ // SKILL.md validation
5967
+ // -------------------------------------------------------------------------
5968
+ async validateSkillMd(rootDir, relPath, issues, componentNames, parentDirName, compat) {
5969
+ const fullPath = join22(rootDir, relPath);
5970
+ let raw;
5971
+ try {
5972
+ raw = await readFile8(fullPath, "utf-8");
5973
+ } catch {
5974
+ issues.push({
5975
+ severity: "error",
5976
+ path: relPath,
5977
+ message: "Cannot read SKILL.md file",
5978
+ code: "FILE_UNREADABLE"
5979
+ });
5980
+ return false;
5981
+ }
5982
+ const skill = parseSkillMdContent(raw);
5983
+ if (!skill) {
5984
+ issues.push({
5985
+ severity: "error",
5986
+ path: relPath,
5987
+ message: "Invalid SKILL.md: missing YAML frontmatter or required 'name' field",
5988
+ code: "SKILL_MD_INVALID"
5989
+ });
5990
+ return false;
5991
+ }
5992
+ this.trackComponentName(componentNames, "skills", skill.name);
5993
+ if (compat === "agent-skills") {
5994
+ this.validateAgentSkillsCompat(skill, relPath, issues, parentDirName, raw);
5995
+ } else {
5996
+ if (!skill.description) {
5997
+ issues.push({
5998
+ severity: "warning",
5999
+ path: relPath,
6000
+ component: skill.name,
6001
+ message: `Missing "description" in SKILL.md frontmatter`,
6002
+ code: "SKILL_MD_MISSING_DESCRIPTION"
6003
+ });
6004
+ }
6005
+ }
6006
+ if (!skill.content || skill.content.trim().length === 0) {
6007
+ issues.push({
6008
+ severity: "warning",
6009
+ path: relPath,
6010
+ component: skill.name,
6011
+ message: "SKILL.md has empty content body",
6012
+ code: "SKILL_MD_EMPTY_CONTENT"
6013
+ });
6014
+ }
6015
+ return true;
6016
+ }
6017
+ // -------------------------------------------------------------------------
6018
+ // Agent Skills (agentskills.io) compatibility checks
6019
+ // -------------------------------------------------------------------------
6020
+ validateAgentSkillsCompat(skill, relPath, issues, parentDirName, raw) {
6021
+ const name = skill.name;
6022
+ const NAME_RE = /^[a-z][a-z0-9-]*$/;
6023
+ if (name.length > 64) {
6024
+ issues.push({
6025
+ severity: "error",
6026
+ path: relPath,
6027
+ component: name,
6028
+ message: `Name exceeds 64 characters (${name.length})`,
6029
+ code: "AGENT_SKILLS_NAME_TOO_LONG"
6030
+ });
6031
+ } else if (!NAME_RE.test(name)) {
6032
+ issues.push({
6033
+ severity: "error",
6034
+ path: relPath,
6035
+ component: name,
6036
+ message: `Name must contain only lowercase letters, numbers, and hyphens, starting with a letter`,
6037
+ code: "AGENT_SKILLS_NAME_FORMAT"
6038
+ });
6039
+ } else {
6040
+ if (name.endsWith("-")) {
6041
+ issues.push({
6042
+ severity: "error",
6043
+ path: relPath,
6044
+ component: name,
6045
+ message: "Name must not end with a hyphen",
6046
+ code: "AGENT_SKILLS_NAME_TRAILING_HYPHEN"
6047
+ });
6048
+ }
6049
+ if (name.includes("--")) {
6050
+ issues.push({
6051
+ severity: "error",
6052
+ path: relPath,
6053
+ component: name,
6054
+ message: "Name must not contain consecutive hyphens",
6055
+ code: "AGENT_SKILLS_NAME_CONSECUTIVE_HYPHENS"
6056
+ });
6057
+ }
6058
+ }
6059
+ if (parentDirName && name !== parentDirName) {
6060
+ issues.push({
6061
+ severity: "error",
6062
+ path: relPath,
6063
+ component: name,
6064
+ message: `Name "${name}" does not match parent directory "${parentDirName}"`,
6065
+ code: "AGENT_SKILLS_NAME_DIR_MISMATCH"
6066
+ });
6067
+ }
6068
+ if (!skill.description) {
6069
+ issues.push({
6070
+ severity: "error",
6071
+ path: relPath,
6072
+ component: name,
6073
+ message: `Missing required "description" field (Agent Skills spec)`,
6074
+ code: "AGENT_SKILLS_DESCRIPTION_REQUIRED"
6075
+ });
6076
+ } else if (skill.description.length > 1024) {
6077
+ issues.push({
6078
+ severity: "warning",
6079
+ path: relPath,
6080
+ component: name,
6081
+ message: `Description exceeds 1024 characters (${skill.description.length})`,
6082
+ code: "AGENT_SKILLS_DESCRIPTION_TOO_LONG"
6083
+ });
6084
+ }
6085
+ if (skill.compatibility && skill.compatibility.length > 500) {
6086
+ issues.push({
6087
+ severity: "warning",
6088
+ path: relPath,
6089
+ component: name,
6090
+ message: `Compatibility field exceeds 500 characters (${skill.compatibility.length})`,
6091
+ code: "AGENT_SKILLS_COMPAT_TOO_LONG"
6092
+ });
6093
+ }
6094
+ if (raw) {
6095
+ const frontmatterEnd = raw.indexOf("---", 3);
6096
+ if (frontmatterEnd !== -1) {
6097
+ const body = raw.substring(frontmatterEnd + 3).trim();
6098
+ const lineCount = body.split("\n").length;
6099
+ if (lineCount > 500) {
6100
+ issues.push({
6101
+ severity: "warning",
6102
+ path: relPath,
6103
+ component: name,
6104
+ message: `SKILL.md body has ${lineCount} lines; Agent Skills spec recommends < 500 lines`,
6105
+ code: "AGENT_SKILLS_BODY_TOO_LONG"
6106
+ });
6107
+ }
6108
+ }
6109
+ }
6110
+ }
6111
+ // -------------------------------------------------------------------------
6112
+ // Agent Skills directory convention (scripts/, references/, assets/)
6113
+ // -------------------------------------------------------------------------
6114
+ async validateSkillDirConventions(rootDir, skillDir, skillName, issues) {
6115
+ const KNOWN_DIRS = /* @__PURE__ */ new Set(["scripts", "references", "assets"]);
6116
+ const KNOWN_FILES = /* @__PURE__ */ new Set(["SKILL.md", "LICENSE", "LICENSE.txt", "LICENSE.md"]);
6117
+ let entries;
6118
+ try {
6119
+ entries = await readdir6(skillDir);
6120
+ } catch {
6121
+ return;
6122
+ }
6123
+ for (const entry of entries) {
6124
+ const fullPath = join22(skillDir, entry);
6125
+ const relPath = relative2(rootDir, fullPath).replace(/\\/g, "/");
6126
+ const entryStat = await stat9(fullPath).catch(() => null);
6127
+ if (!entryStat) continue;
6128
+ if (entryStat.isDirectory()) {
6129
+ if (KNOWN_DIRS.has(entry)) {
6130
+ issues.push({
6131
+ severity: "info",
6132
+ path: relPath,
6133
+ component: skillName,
6134
+ message: `Agent Skills convention: ${entry}/ directory detected`,
6135
+ code: "AGENT_SKILLS_DIR_FOUND"
6136
+ });
6137
+ }
6138
+ } else if (!KNOWN_FILES.has(entry)) {
6139
+ }
6140
+ }
6141
+ }
6142
+ // -------------------------------------------------------------------------
6143
+ // Layer 3: Cross-reference validation
6144
+ // -------------------------------------------------------------------------
6145
+ async validatePresetReferences(rootDir, manifest, componentNames, issues) {
6146
+ const presetsDir = join22(rootDir, "presets");
6147
+ const presetFiles = [];
6148
+ if (manifest?.presets) {
6149
+ for (const p of manifest.presets) {
6150
+ presetFiles.push(p);
6151
+ }
6152
+ } else {
6153
+ try {
6154
+ const entries = await readdir6(presetsDir);
6155
+ for (const e of entries) {
6156
+ if (extname4(e) === ".json") {
6157
+ presetFiles.push(`presets/${e}`);
6158
+ }
6159
+ }
6160
+ } catch {
6161
+ return;
6162
+ }
6163
+ }
6164
+ for (const presetFile of presetFiles) {
6165
+ const fullPath = join22(rootDir, presetFile);
6166
+ let data;
6167
+ try {
6168
+ const raw = await readFile8(fullPath, "utf-8");
6169
+ data = JSON.parse(raw);
6170
+ } catch {
6171
+ continue;
6172
+ }
6173
+ const presetName = data.name || presetFile;
6174
+ const refMap = [
6175
+ ["skills", "skills"],
6176
+ ["prompts", "prompts"],
6177
+ ["mcpServers", "mcp"],
6178
+ ["workflows", "workflows"],
6179
+ ["templates", "templates"]
6180
+ ];
6181
+ for (const [field, compType] of refMap) {
6182
+ const refs = data[field];
6183
+ if (!refs) continue;
6184
+ const available = componentNames.get(compType) ?? /* @__PURE__ */ new Set();
6185
+ for (const ref of refs) {
6186
+ if (!available.has(ref)) {
6187
+ issues.push({
6188
+ severity: "warning",
6189
+ path: presetFile,
6190
+ component: presetName,
6191
+ message: `Preset references ${compType} "${ref}" which was not found in this source`,
6192
+ code: "PRESET_REF_MISSING"
6193
+ });
6194
+ }
6195
+ }
6196
+ }
6197
+ }
6198
+ }
6199
+ // -------------------------------------------------------------------------
6200
+ // Helpers
6201
+ // -------------------------------------------------------------------------
6202
+ trackComponentName(componentNames, type, name) {
6203
+ let set = componentNames.get(type);
6204
+ if (!set) {
6205
+ set = /* @__PURE__ */ new Set();
6206
+ componentNames.set(type, set);
6207
+ }
6208
+ set.add(name);
6209
+ }
6210
+ };
6211
+
6212
+ // src/provider/builtin-providers.ts
6213
+ var BUILTIN_PROVIDERS = [
6214
+ {
6215
+ type: "anthropic",
6216
+ displayName: "Anthropic (Claude)",
6217
+ protocol: "anthropic",
6218
+ defaultBaseUrl: "https://api.anthropic.com",
6219
+ models: ["claude-sonnet-4-20250514", "claude-opus-4-20250514"]
6220
+ },
6221
+ {
6222
+ type: "openai",
6223
+ displayName: "OpenAI",
6224
+ protocol: "openai",
6225
+ defaultBaseUrl: "https://api.openai.com/v1",
6226
+ models: ["gpt-4o", "gpt-4o-mini", "o3-mini"]
6227
+ },
6228
+ {
6229
+ type: "deepseek",
6230
+ displayName: "DeepSeek",
6231
+ protocol: "openai",
6232
+ defaultBaseUrl: "https://api.deepseek.com/v1",
6233
+ models: ["deepseek-chat", "deepseek-reasoner"]
6234
+ },
6235
+ {
6236
+ type: "ollama",
6237
+ displayName: "Ollama (Local)",
6238
+ protocol: "openai",
6239
+ defaultBaseUrl: "http://localhost:11434/v1"
6240
+ },
6241
+ {
6242
+ type: "azure",
6243
+ displayName: "Azure OpenAI",
6244
+ protocol: "openai",
6245
+ defaultBaseUrl: "https://<resource>.openai.azure.com"
6246
+ },
6247
+ {
6248
+ type: "bedrock",
6249
+ displayName: "AWS Bedrock",
6250
+ protocol: "anthropic",
6251
+ defaultBaseUrl: "https://bedrock-runtime.<region>.amazonaws.com"
6252
+ },
6253
+ {
6254
+ type: "vertex",
6255
+ displayName: "Google Vertex AI",
6256
+ protocol: "anthropic",
6257
+ defaultBaseUrl: "https://<region>-aiplatform.googleapis.com"
6258
+ },
6259
+ {
6260
+ type: "custom",
6261
+ displayName: "Custom",
6262
+ protocol: "custom",
6263
+ defaultBaseUrl: "http://localhost:8080"
6264
+ }
6265
+ ];
6266
+ function registerBuiltinProviders() {
6267
+ for (const descriptor of BUILTIN_PROVIDERS) {
6268
+ modelProviderRegistry.register(descriptor);
6269
+ }
6270
+ }
5407
6271
  export {
5408
6272
  AgentBackendSchema,
5409
6273
  AgentInitializer,
@@ -5411,6 +6275,7 @@ export {
5411
6275
  AgentManager,
5412
6276
  AgentStatusSchema,
5413
6277
  AgentTemplateSchema,
6278
+ BUILTIN_PROVIDERS,
5414
6279
  BaseComponentManager,
5415
6280
  ClaudeCodeBuilder,
5416
6281
  ClaudeCodeCommunicator,
@@ -5447,6 +6312,7 @@ export {
5447
6312
  McpServerRefSchema,
5448
6313
  MkdirStep,
5449
6314
  MockLauncher,
6315
+ ModelProviderRegistry,
5450
6316
  ModelProviderSchema,
5451
6317
  NpmInstallStep,
5452
6318
  PermissionAuditLogger,
@@ -5464,6 +6330,7 @@ export {
5464
6330
  SessionRegistry,
5465
6331
  SkillManager,
5466
6332
  SourceManager,
6333
+ SourceValidator,
5467
6334
  StepRegistry,
5468
6335
  TaskDispatcher,
5469
6336
  TaskQueue,
@@ -5482,9 +6349,11 @@ export {
5482
6349
  isAcpOnlyBackend,
5483
6350
  isProcessAlive,
5484
6351
  metaFilePath,
6352
+ modelProviderRegistry,
5485
6353
  openBackend,
5486
6354
  readInstanceMeta,
5487
6355
  registerBackend,
6356
+ registerBuiltinProviders,
5488
6357
  registerCommunicator,
5489
6358
  requireMode,
5490
6359
  resolveAcpBackend,