@actant/core 0.1.2 → 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";
@@ -1061,13 +1065,29 @@ var DomainContextSchema = z2.object({
1061
1065
  extensions: z2.record(z2.string(), z2.array(z2.unknown())).optional()
1062
1066
  });
1063
1067
  var AgentBackendSchema = z2.object({
1064
- type: z2.enum(["cursor", "claude-code", "custom"]),
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", "custom"]),
1083
+ type: z2.string().min(1),
1084
+ protocol: ModelApiProtocolEnum.optional(),
1085
+ baseUrl: z2.string().optional(),
1069
1086
  config: z2.record(z2.string(), z2.unknown()).optional()
1070
- });
1087
+ }).transform((val) => ({
1088
+ ...val,
1089
+ protocol: val.protocol ?? DEFAULT_PROTOCOL[val.type] ?? "custom"
1090
+ }));
1071
1091
  var InitializerStepSchema = z2.object({
1072
1092
  type: z2.string().min(1),
1073
1093
  config: z2.record(z2.string(), z2.unknown()).optional()
@@ -1125,7 +1145,7 @@ var AgentTemplateSchema = z2.object({
1125
1145
  origin: ComponentOriginSchema.optional(),
1126
1146
  tags: z2.array(z2.string()).optional(),
1127
1147
  backend: AgentBackendSchema,
1128
- provider: ModelProviderSchema,
1148
+ provider: ModelProviderSchema.optional(),
1129
1149
  domainContext: DomainContextSchema,
1130
1150
  permissions: PermissionsInputSchema.optional(),
1131
1151
  initializer: InitializerSchema.optional(),
@@ -1253,6 +1273,58 @@ function isNodeError(err) {
1253
1273
  return err instanceof Error && "code" in err;
1254
1274
  }
1255
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
+
1256
1328
  // src/template/schema/config-validators.ts
1257
1329
  function zodToIssues(zodError) {
1258
1330
  return zodError.issues.map((i) => ({
@@ -1276,7 +1348,15 @@ function validateProviderConfig(data) {
1276
1348
  if (!result.success) {
1277
1349
  return { valid: false, errors: zodToIssues(result.error), warnings: [] };
1278
1350
  }
1279
- 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 };
1280
1360
  }
1281
1361
  function validatePermissionsConfig(data) {
1282
1362
  const result = PermissionsInputSchema.safeParse(data);
@@ -1360,12 +1440,21 @@ function validateTemplate(data) {
1360
1440
  "CUSTOM_BACKEND_NO_CONFIG"
1361
1441
  ));
1362
1442
  }
1363
- if (template.provider.type === "custom" && !template.provider.config) {
1364
- warnings.push(warning(
1365
- "provider.config",
1366
- "Custom provider type without config; model routing may fail",
1367
- "CUSTOM_PROVIDER_NO_CONFIG"
1368
- ));
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
+ }
1369
1458
  }
1370
1459
  return { valid: true, data: template, errors: [], warnings };
1371
1460
  }
@@ -1378,13 +1467,13 @@ import { TemplateNotFoundError, ConfigValidationError as ConfigValidationError3
1378
1467
  // src/domain/base-component-manager.ts
1379
1468
  import { readFile as readFile2, writeFile as writeFile4, readdir as readdir2, stat as stat4, unlink, mkdir as mkdir4 } from "fs/promises";
1380
1469
  import { join as join5, extname as extname2, resolve } from "path";
1381
- 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";
1382
1471
  var BaseComponentManager = class {
1383
1472
  components = /* @__PURE__ */ new Map();
1384
1473
  logger;
1385
1474
  persistDir;
1386
1475
  constructor(loggerName) {
1387
- this.logger = createLogger11(loggerName);
1476
+ this.logger = createLogger12(loggerName);
1388
1477
  }
1389
1478
  setPersistDir(dir) {
1390
1479
  this.persistDir = dir;
@@ -1717,13 +1806,13 @@ var TemplateRegistry = class extends BaseComponentManager {
1717
1806
  import { watch } from "fs";
1718
1807
  import { join as join7 } from "path";
1719
1808
  import { access } from "fs/promises";
1720
- import { createLogger as createLogger12 } from "@actant/shared";
1721
- var logger11 = createLogger12("template-file-watcher");
1809
+ import { createLogger as createLogger13 } from "@actant/shared";
1810
+ var logger12 = createLogger13("template-file-watcher");
1722
1811
  var DEFAULT_DEBOUNCE_MS = 300;
1723
1812
  var TemplateFileWatcher = class {
1724
- constructor(templatesDir, registry, options) {
1813
+ constructor(templatesDir, registry3, options) {
1725
1814
  this.templatesDir = templatesDir;
1726
- this.registry = registry;
1815
+ this.registry = registry3;
1727
1816
  this.debounceMs = options?.debounceMs ?? DEFAULT_DEBOUNCE_MS;
1728
1817
  }
1729
1818
  watcher = null;
@@ -1740,12 +1829,12 @@ var TemplateFileWatcher = class {
1740
1829
  this.handleChange(filename);
1741
1830
  });
1742
1831
  this.watcher.on("error", (err) => {
1743
- logger11.error({ error: err }, "File watcher error");
1832
+ logger12.error({ error: err }, "File watcher error");
1744
1833
  });
1745
1834
  this.buildFileMap();
1746
- logger11.info({ dir: this.templatesDir }, "Template file watcher started");
1835
+ logger12.info({ dir: this.templatesDir }, "Template file watcher started");
1747
1836
  } catch (err) {
1748
- 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");
1749
1838
  }
1750
1839
  }
1751
1840
  stop() {
@@ -1756,7 +1845,7 @@ var TemplateFileWatcher = class {
1756
1845
  clearTimeout(timer);
1757
1846
  }
1758
1847
  this.debounceTimers.clear();
1759
- logger11.info("Template file watcher stopped");
1848
+ logger12.info("Template file watcher stopped");
1760
1849
  }
1761
1850
  get isWatching() {
1762
1851
  return this.watcher !== null;
@@ -1775,7 +1864,7 @@ var TemplateFileWatcher = class {
1775
1864
  setTimeout(() => {
1776
1865
  this.debounceTimers.delete(filename);
1777
1866
  this.processChange(filename).catch((err) => {
1778
- logger11.error({ filename, error: err }, "Error processing template file change");
1867
+ logger12.error({ filename, error: err }, "Error processing template file change");
1779
1868
  });
1780
1869
  }, this.debounceMs)
1781
1870
  );
@@ -1793,7 +1882,7 @@ var TemplateFileWatcher = class {
1793
1882
  if (previousName && this.registry.has(previousName)) {
1794
1883
  this.registry.unregister(previousName);
1795
1884
  this.fileToName.delete(filename);
1796
- logger11.info({ templateName: previousName, filename }, "Template unregistered (file deleted)");
1885
+ logger12.info({ templateName: previousName, filename }, "Template unregistered (file deleted)");
1797
1886
  }
1798
1887
  return;
1799
1888
  }
@@ -1808,9 +1897,9 @@ var TemplateFileWatcher = class {
1808
1897
  }
1809
1898
  this.registry.register(template);
1810
1899
  this.fileToName.set(filename, template.name);
1811
- 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");
1812
1901
  } catch (err) {
1813
- logger11.warn({ filePath, error: err }, "Failed to reload template");
1902
+ logger12.warn({ filePath, error: err }, "Failed to reload template");
1814
1903
  }
1815
1904
  }
1816
1905
  };
@@ -1824,7 +1913,7 @@ import {
1824
1913
  ConfigValidationError as ConfigValidationError4,
1825
1914
  InstanceCorruptedError as InstanceCorruptedError3,
1826
1915
  WorkspaceInitError,
1827
- createLogger as createLogger15
1916
+ createLogger as createLogger16
1828
1917
  } from "@actant/shared";
1829
1918
 
1830
1919
  // src/state/instance-meta-schema.ts
@@ -1846,7 +1935,7 @@ var LaunchModeSchema = z3.enum([
1846
1935
  ]);
1847
1936
  var ProcessOwnershipSchema = z3.enum(["managed", "external"]);
1848
1937
  var WorkspacePolicySchema = z3.enum(["persistent", "ephemeral"]);
1849
- var AgentBackendTypeSchema = z3.enum(["cursor", "claude-code", "custom"]);
1938
+ var AgentBackendTypeSchema = z3.enum(["cursor", "cursor-agent", "claude-code", "custom", "pi"]);
1850
1939
  var PermissionModeSchema2 = z3.enum([
1851
1940
  "default",
1852
1941
  "acceptEdits",
@@ -1871,6 +1960,13 @@ var PermissionsConfigSchema = z3.object({
1871
1960
  sandbox: SandboxConfigSchema.optional(),
1872
1961
  additionalDirectories: z3.array(z3.string()).optional()
1873
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
+ });
1874
1970
  var AgentInstanceMetaSchema = z3.object({
1875
1971
  id: z3.string().min(1),
1876
1972
  name: z3.string().min(1),
@@ -1878,6 +1974,7 @@ var AgentInstanceMetaSchema = z3.object({
1878
1974
  templateVersion: z3.string().regex(/^\d+\.\d+\.\d+$/),
1879
1975
  backendType: AgentBackendTypeSchema.default("cursor"),
1880
1976
  backendConfig: z3.record(z3.string(), z3.unknown()).optional(),
1977
+ providerConfig: ModelProviderConfigSchema.optional(),
1881
1978
  status: AgentStatusSchema,
1882
1979
  launchMode: LaunchModeSchema,
1883
1980
  workspacePolicy: WorkspacePolicySchema.default("persistent"),
@@ -1894,8 +1991,8 @@ import { readFile as readFile3, writeFile as writeFile5, rename, readdir as read
1894
1991
  import { join as join8, dirname } from "path";
1895
1992
  import { randomUUID as randomUUID5 } from "crypto";
1896
1993
  import { InstanceCorruptedError } from "@actant/shared";
1897
- import { createLogger as createLogger13 } from "@actant/shared";
1898
- var logger12 = createLogger13("instance-meta-io");
1994
+ import { createLogger as createLogger14 } from "@actant/shared";
1995
+ var logger13 = createLogger14("instance-meta-io");
1899
1996
  var META_FILENAME = ".actant.json";
1900
1997
  function metaFilePath(workspaceDir) {
1901
1998
  return join8(workspaceDir, META_FILENAME);
@@ -1947,12 +2044,12 @@ async function updateInstanceMeta(workspaceDir, patch) {
1947
2044
  await writeInstanceMeta(workspaceDir, updated);
1948
2045
  return updated;
1949
2046
  }
1950
- async function scanInstances(instancesBaseDir, registry) {
2047
+ async function scanInstances(instancesBaseDir, registry3) {
1951
2048
  const valid = [];
1952
2049
  const corrupted = [];
1953
2050
  const validNames = /* @__PURE__ */ new Set();
1954
- if (registry) {
1955
- for (const entry of registry.list()) {
2051
+ if (registry3) {
2052
+ for (const entry of registry3.list()) {
1956
2053
  if (entry.status === "orphaned") continue;
1957
2054
  try {
1958
2055
  const st = await stat5(entry.workspacePath);
@@ -1964,7 +2061,7 @@ async function scanInstances(instancesBaseDir, registry) {
1964
2061
  valid.push(meta);
1965
2062
  validNames.add(entry.name);
1966
2063
  } catch (err) {
1967
- 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");
1968
2065
  corrupted.push(entry.name);
1969
2066
  }
1970
2067
  }
@@ -1989,7 +2086,7 @@ async function scanInstances(instancesBaseDir, registry) {
1989
2086
  valid.push(meta);
1990
2087
  validNames.add(meta.name);
1991
2088
  } catch (err) {
1992
- logger12.warn({ dir: entry, error: err }, "Corrupted instance directory");
2089
+ logger13.warn({ dir: entry, error: err }, "Corrupted instance directory");
1993
2090
  corrupted.push(entry);
1994
2091
  }
1995
2092
  }
@@ -2138,13 +2235,13 @@ function isNodeError4(err) {
2138
2235
  }
2139
2236
 
2140
2237
  // src/initializer/pipeline/initialization-pipeline.ts
2141
- import { createLogger as createLogger14 } from "@actant/shared";
2142
- var logger13 = createLogger14("initialization-pipeline");
2238
+ import { createLogger as createLogger15 } from "@actant/shared";
2239
+ var logger14 = createLogger15("initialization-pipeline");
2143
2240
  var DEFAULT_STEP_TIMEOUT_MS = 6e4;
2144
2241
  var DEFAULT_TOTAL_TIMEOUT_MS = 3e5;
2145
2242
  var InitializationPipeline = class {
2146
- constructor(registry, options) {
2147
- this.registry = registry;
2243
+ constructor(registry3, options) {
2244
+ this.registry = registry3;
2148
2245
  this.stepTimeoutMs = options?.defaultStepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
2149
2246
  this.totalTimeoutMs = options?.totalTimeoutMs ?? DEFAULT_TOTAL_TIMEOUT_MS;
2150
2247
  this.onProgress = options?.onProgress;
@@ -2173,14 +2270,14 @@ var InitializationPipeline = class {
2173
2270
  const errors = [];
2174
2271
  const executed = [];
2175
2272
  const pipelineStart = Date.now();
2176
- logger13.info({ stepsCount: steps.length }, "Starting initialization pipeline");
2273
+ logger14.info({ stepsCount: steps.length }, "Starting initialization pipeline");
2177
2274
  for (let i = 0; i < steps.length; i++) {
2178
2275
  const step = steps[i];
2179
2276
  if (!step) continue;
2180
2277
  if (Date.now() - pipelineStart > this.totalTimeoutMs) {
2181
2278
  const err = new Error(`Pipeline total timeout exceeded (${this.totalTimeoutMs}ms)`);
2182
2279
  errors.push({ stepIndex: i, stepType: step.type, error: err });
2183
- logger13.error({ stepIndex: i, stepType: step.type }, "Pipeline timeout exceeded");
2280
+ logger14.error({ stepIndex: i, stepType: step.type }, "Pipeline timeout exceeded");
2184
2281
  await this.rollback(executed, context, err);
2185
2282
  return { success: false, stepsExecuted: i, stepsTotal: steps.length, errors, outputs };
2186
2283
  }
@@ -2192,7 +2289,7 @@ var InitializationPipeline = class {
2192
2289
  return { success: false, stepsExecuted: i, stepsTotal: steps.length, errors, outputs };
2193
2290
  }
2194
2291
  this.onProgress?.(i, steps.length, step.type);
2195
- logger13.debug({ stepIndex: i, stepType: step.type }, "Executing step");
2292
+ logger14.debug({ stepIndex: i, stepType: step.type }, "Executing step");
2196
2293
  try {
2197
2294
  const result = await this.executeWithTimeout(executor, context, step.config ?? {});
2198
2295
  if (!result.success) {
@@ -2207,18 +2304,18 @@ var InitializationPipeline = class {
2207
2304
  outputs.set(`step-${i}-${step.type}`, result.output);
2208
2305
  }
2209
2306
  if (result.message) {
2210
- logger13.info({ stepIndex: i, stepType: step.type }, result.message);
2307
+ logger14.info({ stepIndex: i, stepType: step.type }, result.message);
2211
2308
  }
2212
2309
  } catch (err) {
2213
2310
  const error = err instanceof Error ? err : new Error(String(err));
2214
2311
  errors.push({ stepIndex: i, stepType: step.type, error });
2215
- logger13.error({ stepIndex: i, stepType: step.type, error }, "Step execution failed");
2312
+ logger14.error({ stepIndex: i, stepType: step.type, error }, "Step execution failed");
2216
2313
  executed.push({ index: i, executor, config: step.config ?? {} });
2217
2314
  await this.rollback(executed, context, error);
2218
2315
  return { success: false, stepsExecuted: i + 1, stepsTotal: steps.length, errors, outputs };
2219
2316
  }
2220
2317
  }
2221
- 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");
2222
2319
  return { success: true, stepsExecuted: steps.length, stepsTotal: steps.length, errors, outputs };
2223
2320
  }
2224
2321
  async executeWithTimeout(executor, context, config) {
@@ -2240,7 +2337,7 @@ var InitializationPipeline = class {
2240
2337
  }
2241
2338
  async rollback(executed, context, triggerError) {
2242
2339
  if (executed.length === 0) return;
2243
- logger13.info({ stepsToRollback: executed.length }, "Rolling back executed steps");
2340
+ logger14.info({ stepsToRollback: executed.length }, "Rolling back executed steps");
2244
2341
  for (let i = executed.length - 1; i >= 0; i--) {
2245
2342
  const entry = executed[i];
2246
2343
  if (!entry) continue;
@@ -2248,9 +2345,9 @@ var InitializationPipeline = class {
2248
2345
  if (!executor.rollback) continue;
2249
2346
  try {
2250
2347
  await executor.rollback(context, config, triggerError);
2251
- logger13.debug({ stepIndex: index, stepType: executor.type }, "Step rolled back");
2348
+ logger14.debug({ stepIndex: index, stepType: executor.type }, "Step rolled back");
2252
2349
  } catch (rollbackErr) {
2253
- logger13.warn(
2350
+ logger14.warn(
2254
2351
  { stepIndex: index, stepType: executor.type, error: rollbackErr },
2255
2352
  "Rollback failed (best-effort)"
2256
2353
  );
@@ -2260,7 +2357,7 @@ var InitializationPipeline = class {
2260
2357
  };
2261
2358
 
2262
2359
  // src/initializer/agent-initializer.ts
2263
- var logger14 = createLogger15("agent-initializer");
2360
+ var logger15 = createLogger16("agent-initializer");
2264
2361
  var AgentInitializer = class {
2265
2362
  constructor(templateRegistry, instancesBaseDir, options) {
2266
2363
  this.templateRegistry = templateRegistry;
@@ -2273,6 +2370,9 @@ var AgentInitializer = class {
2273
2370
  }
2274
2371
  builder;
2275
2372
  pipeline;
2373
+ get workspaceBuilder() {
2374
+ return this.builder;
2375
+ }
2276
2376
  /**
2277
2377
  * Create a new Agent Instance.
2278
2378
  * 1. Resolve template from registry
@@ -2299,10 +2399,10 @@ var AgentInitializer = class {
2299
2399
  );
2300
2400
  case "overwrite":
2301
2401
  await rm(workspaceDir, { recursive: true, force: true });
2302
- logger14.info({ workspaceDir }, "Existing directory removed (overwrite)");
2402
+ logger15.info({ workspaceDir }, "Existing directory removed (overwrite)");
2303
2403
  break;
2304
2404
  case "append":
2305
- logger14.info({ workspaceDir }, "Appending to existing directory");
2405
+ logger15.info({ workspaceDir }, "Appending to existing directory");
2306
2406
  break;
2307
2407
  }
2308
2408
  }
@@ -2330,7 +2430,7 @@ var AgentInitializer = class {
2330
2430
  workspaceDir,
2331
2431
  instanceMeta: { name, templateName: template.name },
2332
2432
  template,
2333
- logger: logger14,
2433
+ logger: logger15,
2334
2434
  state: /* @__PURE__ */ new Map()
2335
2435
  };
2336
2436
  const pipelineResult = await this.pipeline.run(template.initializer.steps, stepContext);
@@ -2343,6 +2443,7 @@ var AgentInitializer = class {
2343
2443
  }
2344
2444
  }
2345
2445
  const effectivePermissions = resolvePermissions(finalPermissions);
2446
+ const resolvedProvider = resolveProviderConfig(template.provider);
2346
2447
  const now = (/* @__PURE__ */ new Date()).toISOString();
2347
2448
  const launchMode = overrides?.launchMode ?? this.options?.defaultLaunchMode ?? "direct";
2348
2449
  const defaultPolicy = launchMode === "one-shot" ? "ephemeral" : "persistent";
@@ -2353,6 +2454,7 @@ var AgentInitializer = class {
2353
2454
  templateVersion: template.version,
2354
2455
  backendType: template.backend.type,
2355
2456
  backendConfig: template.backend.config ? { ...template.backend.config } : void 0,
2457
+ providerConfig: resolvedProvider,
2356
2458
  status: "created",
2357
2459
  launchMode,
2358
2460
  workspacePolicy: overrides?.workspacePolicy ?? defaultPolicy,
@@ -2367,13 +2469,13 @@ var AgentInitializer = class {
2367
2469
  const linkType = process.platform === "win32" ? "junction" : "dir";
2368
2470
  await symlink(workspaceDir, join10(this.instancesBaseDir, name), linkType);
2369
2471
  }
2370
- logger14.info({ name, templateName, workspaceDir, customWorkDir: !!customWorkDir }, "Agent instance created");
2472
+ logger15.info({ name, templateName, workspaceDir, customWorkDir: !!customWorkDir }, "Agent instance created");
2371
2473
  return meta;
2372
2474
  } catch (err) {
2373
2475
  if (shouldCleanupOnError) {
2374
2476
  await rm(workspaceDir, { recursive: true, force: true }).catch(() => {
2375
2477
  });
2376
- logger14.debug({ workspaceDir }, "Cleaned up workspace after failed creation");
2478
+ logger15.debug({ workspaceDir }, "Cleaned up workspace after failed creation");
2377
2479
  }
2378
2480
  if (err instanceof ActantError) {
2379
2481
  throw err;
@@ -2392,7 +2494,7 @@ var AgentInitializer = class {
2392
2494
  if (await dirExists(workspaceDir)) {
2393
2495
  try {
2394
2496
  const meta2 = await readInstanceMeta(workspaceDir);
2395
- logger14.debug({ name }, "Existing instance found");
2497
+ logger15.debug({ name }, "Existing instance found");
2396
2498
  return { meta: meta2, created: false };
2397
2499
  } catch (err) {
2398
2500
  if (err instanceof InstanceCorruptedError3) {
@@ -2413,7 +2515,7 @@ var AgentInitializer = class {
2413
2515
  async destroyInstance(name) {
2414
2516
  const entryPath = join10(this.instancesBaseDir, name);
2415
2517
  if (!await entryExists(entryPath)) {
2416
- logger14.warn({ name }, "Instance directory not found, nothing to destroy");
2518
+ logger15.warn({ name }, "Instance directory not found, nothing to destroy");
2417
2519
  return;
2418
2520
  }
2419
2521
  if (await isSymlink(entryPath)) {
@@ -2423,13 +2525,30 @@ var AgentInitializer = class {
2423
2525
  } catch {
2424
2526
  }
2425
2527
  await rm(entryPath, { recursive: true, force: true });
2426
- logger14.info({ name, targetDir }, "Symlinked agent instance unregistered");
2528
+ logger15.info({ name, targetDir }, "Symlinked agent instance unregistered");
2427
2529
  } else {
2428
2530
  await rm(entryPath, { recursive: true, force: true });
2429
- logger14.info({ name }, "Agent instance destroyed");
2531
+ logger15.info({ name }, "Agent instance destroyed");
2430
2532
  }
2431
2533
  }
2432
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
+ }
2433
2552
  async function dirExists(path) {
2434
2553
  try {
2435
2554
  await access2(path);
@@ -2458,11 +2577,13 @@ async function isSymlink(path) {
2458
2577
  // src/initializer/context/context-materializer.ts
2459
2578
  import { writeFile as writeFile7, mkdir as mkdir9 } from "fs/promises";
2460
2579
  import { join as join11 } from "path";
2461
- import { createLogger as createLogger16 } from "@actant/shared";
2462
- var logger15 = createLogger16("context-materializer");
2580
+ import { createLogger as createLogger17 } from "@actant/shared";
2581
+ var logger16 = createLogger17("context-materializer");
2463
2582
  var BACKEND_CONFIG_DIR = {
2464
2583
  cursor: ".cursor",
2584
+ "cursor-agent": ".cursor",
2465
2585
  "claude-code": ".claude",
2586
+ pi: ".pi",
2466
2587
  custom: ".cursor"
2467
2588
  };
2468
2589
  var ContextMaterializer = class {
@@ -2488,7 +2609,7 @@ var ContextMaterializer = class {
2488
2609
  tasks.push(this.materializePrompts(workspaceDir, domainContext.prompts));
2489
2610
  }
2490
2611
  await Promise.all(tasks);
2491
- logger15.debug({ workspaceDir, backendType, configDir }, "Domain context materialized");
2612
+ logger16.debug({ workspaceDir, backendType, configDir }, "Domain context materialized");
2492
2613
  }
2493
2614
  async materializeSkills(workspaceDir, skillNames) {
2494
2615
  let content;
@@ -2603,16 +2724,16 @@ var InitializerStepExecutor = class {
2603
2724
  };
2604
2725
 
2605
2726
  // src/initializer/pipeline/step-registry.ts
2606
- import { createLogger as createLogger17 } from "@actant/shared";
2607
- var logger16 = createLogger17("step-registry");
2727
+ import { createLogger as createLogger18 } from "@actant/shared";
2728
+ var logger17 = createLogger18("step-registry");
2608
2729
  var StepRegistry = class {
2609
2730
  executors = /* @__PURE__ */ new Map();
2610
2731
  register(executor) {
2611
2732
  if (this.executors.has(executor.type)) {
2612
- logger16.warn({ type: executor.type }, "Overwriting existing step executor");
2733
+ logger17.warn({ type: executor.type }, "Overwriting existing step executor");
2613
2734
  }
2614
2735
  this.executors.set(executor.type, executor);
2615
- logger16.debug({ type: executor.type }, "Step executor registered");
2736
+ logger17.debug({ type: executor.type }, "Step executor registered");
2616
2737
  }
2617
2738
  get(type) {
2618
2739
  return this.executors.get(type);
@@ -2871,10 +2992,10 @@ var NpmInstallStep = class extends InitializerStepExecutor {
2871
2992
  return { valid: issues.length === 0, issues };
2872
2993
  }
2873
2994
  async execute(context, config) {
2874
- const { packageManager = "npm", cwd = ".", args = [], registry } = config;
2995
+ const { packageManager = "npm", cwd = ".", args = [], registry: registry3 } = config;
2875
2996
  const workDir = join16(context.workspaceDir, cwd);
2876
2997
  const cmdArgs = ["install", ...args];
2877
- if (registry) cmdArgs.push("--registry", registry);
2998
+ if (registry3) cmdArgs.push("--registry", registry3);
2878
2999
  context.logger.debug({ packageManager, cwd: workDir, args: cmdArgs }, "Installing dependencies");
2879
3000
  const result = await runInstall(packageManager, cmdArgs, workDir);
2880
3001
  if (result.exitCode !== 0) {
@@ -2916,56 +3037,151 @@ function runInstall(pm, args, cwd) {
2916
3037
 
2917
3038
  // src/initializer/steps/index.ts
2918
3039
  function createDefaultStepRegistry() {
2919
- const registry = new StepRegistry();
2920
- registry.register(new MkdirStep());
2921
- registry.register(new ExecStep());
2922
- registry.register(new FileCopyStep());
2923
- registry.register(new GitCloneStep());
2924
- registry.register(new NpmInstallStep());
2925
- return registry;
3040
+ const registry3 = new StepRegistry();
3041
+ registry3.register(new MkdirStep());
3042
+ registry3.register(new ExecStep());
3043
+ registry3.register(new FileCopyStep());
3044
+ registry3.register(new GitCloneStep());
3045
+ registry3.register(new NpmInstallStep());
3046
+ return registry3;
2926
3047
  }
2927
3048
 
2928
3049
  // src/manager/agent-manager.ts
2929
3050
  import { join as join17 } from "path";
3051
+ import { existsSync } from "fs";
2930
3052
  import { rename as rename3, mkdir as mkdir11 } from "fs/promises";
2931
3053
  import {
2932
3054
  AgentNotFoundError,
2933
3055
  AgentAlreadyRunningError,
2934
3056
  AgentAlreadyAttachedError,
2935
3057
  AgentNotAttachedError,
2936
- createLogger as createLogger23
3058
+ AgentLaunchError,
3059
+ createLogger as createLogger24
2937
3060
  } from "@actant/shared";
2938
3061
 
2939
- // src/manager/launcher/backend-resolver.ts
2940
- var IS_WINDOWS = process.platform === "win32";
2941
- var DEFAULT_COMMANDS = {
2942
- cursor: () => IS_WINDOWS ? "cursor.cmd" : "cursor",
2943
- "claude-code": () => IS_WINDOWS ? "claude-agent-acp.cmd" : "claude-agent-acp",
2944
- custom: () => {
2945
- throw new Error("Custom backend requires explicit executablePath in backend config");
3062
+ // src/manager/launcher/backend-registry.ts
3063
+ var registry = /* @__PURE__ */ new Map();
3064
+ function registerBackend(descriptor) {
3065
+ registry.set(descriptor.type, descriptor);
3066
+ }
3067
+ function getBackendDescriptor(type) {
3068
+ const desc = registry.get(type);
3069
+ if (!desc) {
3070
+ throw new Error(
3071
+ `Backend "${type}" is not registered. Ensure the backend package is installed and registerBackend() was called at startup.`
3072
+ );
2946
3073
  }
2947
- };
3074
+ return desc;
3075
+ }
3076
+ function supportsMode(type, mode) {
3077
+ const desc = registry.get(type);
3078
+ return desc != null && desc.supportedModes.includes(mode);
3079
+ }
3080
+ function requireMode(type, mode) {
3081
+ const desc = getBackendDescriptor(type);
3082
+ if (!desc.supportedModes.includes(mode)) {
3083
+ const supported = desc.supportedModes.join(", ");
3084
+ throw new Error(
3085
+ `Backend "${type}" does not support "${mode}" mode. Supported modes: [${supported}]. ` + (mode === "resolve" ? `Use \`agent start\` or \`agent run\` instead.` : mode === "open" ? `This backend has no native TUI/UI to open.` : `Use \`agent resolve\` or \`agent open\` instead.`)
3086
+ );
3087
+ }
3088
+ }
3089
+ function getPlatformCommand(cmd) {
3090
+ return process.platform === "win32" ? cmd.win32 : cmd.default;
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
+ }
3100
+
3101
+ // src/manager/launcher/builtin-backends.ts
3102
+ function registerBuiltinBackends() {
3103
+ registerBackend({
3104
+ type: "cursor",
3105
+ supportedModes: ["resolve", "open"],
3106
+ resolveCommand: { win32: "cursor.cmd", default: "cursor" },
3107
+ openCommand: { win32: "cursor.cmd", default: "cursor" }
3108
+ });
3109
+ registerBackend({
3110
+ type: "cursor-agent",
3111
+ supportedModes: ["resolve", "open", "acp"],
3112
+ resolveCommand: { win32: "cursor.cmd", default: "cursor" },
3113
+ openCommand: { win32: "cursor.cmd", default: "cursor" }
3114
+ });
3115
+ registerBackend({
3116
+ type: "claude-code",
3117
+ supportedModes: ["resolve", "open", "acp"],
3118
+ resolveCommand: { win32: "claude-agent-acp.cmd", default: "claude-agent-acp" },
3119
+ openCommand: { win32: "claude.cmd", default: "claude" }
3120
+ });
3121
+ registerBackend({
3122
+ type: "custom",
3123
+ supportedModes: ["resolve"]
3124
+ });
3125
+ }
3126
+ registerBuiltinBackends();
3127
+
3128
+ // src/manager/launcher/backend-resolver.ts
2948
3129
  function isAcpBackend(backendType) {
2949
- return backendType === "claude-code";
3130
+ return supportsMode(backendType, "acp");
3131
+ }
3132
+ function isAcpOnlyBackend(backendType) {
3133
+ const desc = getBackendDescriptor(backendType);
3134
+ return supportsMode(backendType, "acp") && desc.acpOwnsProcess === true;
2950
3135
  }
2951
3136
  function buildArgs(backendType, workspaceDir, backendConfig) {
2952
- switch (backendType) {
2953
- case "cursor":
2954
- return [workspaceDir];
2955
- case "claude-code":
2956
- return [];
2957
- case "custom": {
2958
- const configArgs = backendConfig?.args;
2959
- if (Array.isArray(configArgs)) {
2960
- return configArgs.map(String);
2961
- }
2962
- return [workspaceDir];
2963
- }
3137
+ const desc = getBackendDescriptor(backendType);
3138
+ if (backendType === "custom") {
3139
+ const configArgs = backendConfig?.args;
3140
+ if (Array.isArray(configArgs)) return configArgs.map(String);
3141
+ return [workspaceDir];
3142
+ }
3143
+ if (desc.supportedModes.includes("open") && !desc.supportedModes.includes("acp")) {
3144
+ return [workspaceDir];
2964
3145
  }
3146
+ if (desc.supportedModes.includes("acp")) {
3147
+ return [];
3148
+ }
3149
+ return [workspaceDir];
2965
3150
  }
2966
3151
  function resolveBackend(backendType, workspaceDir, backendConfig) {
3152
+ requireMode(backendType, "resolve");
3153
+ const desc = getBackendDescriptor(backendType);
3154
+ const explicitPath = backendConfig?.executablePath;
3155
+ const command = typeof explicitPath === "string" && explicitPath.length > 0 ? explicitPath : desc.resolveCommand ? getPlatformCommand(desc.resolveCommand) : (() => {
3156
+ throw new Error(`Backend "${backendType}" has no resolveCommand configured.`);
3157
+ })();
3158
+ return {
3159
+ command,
3160
+ args: buildArgs(backendType, workspaceDir, backendConfig)
3161
+ };
3162
+ }
3163
+ function openBackend(backendType, workspaceDir) {
3164
+ requireMode(backendType, "open");
3165
+ const desc = getBackendDescriptor(backendType);
3166
+ if (!desc.openCommand) {
3167
+ throw new Error(`Backend "${backendType}" has no openCommand configured.`);
3168
+ }
3169
+ return {
3170
+ command: getPlatformCommand(desc.openCommand),
3171
+ args: [workspaceDir]
3172
+ };
3173
+ }
3174
+ function resolveAcpBackend(backendType, workspaceDir, backendConfig) {
3175
+ requireMode(backendType, "acp");
3176
+ const desc = getBackendDescriptor(backendType);
3177
+ if (desc.acpResolver) {
3178
+ return desc.acpResolver(workspaceDir, backendConfig);
3179
+ }
2967
3180
  const explicitPath = backendConfig?.executablePath;
2968
- const command = typeof explicitPath === "string" && explicitPath.length > 0 ? explicitPath : DEFAULT_COMMANDS[backendType]();
3181
+ const commandSource = desc.acpCommand ?? desc.resolveCommand;
3182
+ const command = typeof explicitPath === "string" && explicitPath.length > 0 ? explicitPath : commandSource ? getPlatformCommand(commandSource) : (() => {
3183
+ throw new Error(`Backend "${backendType}" has no command configured for ACP spawn.`);
3184
+ })();
2969
3185
  return {
2970
3186
  command,
2971
3187
  args: buildArgs(backendType, workspaceDir, backendConfig)
@@ -2973,7 +3189,7 @@ function resolveBackend(backendType, workspaceDir, backendConfig) {
2973
3189
  }
2974
3190
 
2975
3191
  // src/manager/launcher/process-watcher.ts
2976
- import { createLogger as createLogger18 } from "@actant/shared";
3192
+ import { createLogger as createLogger19 } from "@actant/shared";
2977
3193
 
2978
3194
  // src/manager/launcher/process-utils.ts
2979
3195
  function isProcessAlive(pid) {
@@ -3009,7 +3225,7 @@ function isNodeError5(err) {
3009
3225
  }
3010
3226
 
3011
3227
  // src/manager/launcher/process-watcher.ts
3012
- var logger17 = createLogger18("process-watcher");
3228
+ var logger18 = createLogger19("process-watcher");
3013
3229
  var DEFAULT_POLL_INTERVAL = 5e3;
3014
3230
  var ProcessWatcher = class {
3015
3231
  constructor(onProcessExit, options) {
@@ -3022,12 +3238,12 @@ var ProcessWatcher = class {
3022
3238
  pollIntervalMs;
3023
3239
  watch(instanceName, pid) {
3024
3240
  this.watches.set(instanceName, { pid });
3025
- logger17.debug({ instanceName, pid }, "Watching process");
3241
+ logger18.debug({ instanceName, pid }, "Watching process");
3026
3242
  }
3027
3243
  unwatch(instanceName) {
3028
3244
  const removed = this.watches.delete(instanceName);
3029
3245
  if (removed) {
3030
- logger17.debug({ instanceName }, "Unwatched process");
3246
+ logger18.debug({ instanceName }, "Unwatched process");
3031
3247
  }
3032
3248
  return removed;
3033
3249
  }
@@ -3042,13 +3258,13 @@ var ProcessWatcher = class {
3042
3258
  this.timer = setInterval(() => {
3043
3259
  void this.poll();
3044
3260
  }, this.pollIntervalMs);
3045
- logger17.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3261
+ logger18.info({ pollIntervalMs: this.pollIntervalMs }, "ProcessWatcher started");
3046
3262
  }
3047
3263
  stop() {
3048
3264
  if (this.timer) {
3049
3265
  clearInterval(this.timer);
3050
3266
  this.timer = null;
3051
- logger17.info("ProcessWatcher stopped");
3267
+ logger18.info("ProcessWatcher stopped");
3052
3268
  }
3053
3269
  }
3054
3270
  dispose() {
@@ -3070,11 +3286,11 @@ var ProcessWatcher = class {
3070
3286
  }
3071
3287
  for (const info of exited) {
3072
3288
  this.watches.delete(info.instanceName);
3073
- logger17.info(info, "Process exited \u2014 removed from watch list");
3289
+ logger18.info(info, "Process exited \u2014 removed from watch list");
3074
3290
  try {
3075
3291
  await this.onProcessExit(info);
3076
3292
  } catch (err) {
3077
- logger17.error({ ...info, error: err }, "Error in process exit handler");
3293
+ logger18.error({ ...info, error: err }, "Error in process exit handler");
3078
3294
  }
3079
3295
  }
3080
3296
  } finally {
@@ -3084,8 +3300,8 @@ var ProcessWatcher = class {
3084
3300
  };
3085
3301
 
3086
3302
  // src/manager/launch-mode-handler.ts
3087
- import { createLogger as createLogger19 } from "@actant/shared";
3088
- var logger18 = createLogger19("launch-mode-handler");
3303
+ import { createLogger as createLogger20 } from "@actant/shared";
3304
+ var logger19 = createLogger20("launch-mode-handler");
3089
3305
  var DirectModeHandler = class {
3090
3306
  mode = "direct";
3091
3307
  getProcessExitAction(_instanceName) {
@@ -3107,11 +3323,11 @@ var AcpBackgroundModeHandler = class {
3107
3323
  var AcpServiceModeHandler = class {
3108
3324
  mode = "acp-service";
3109
3325
  getProcessExitAction(instanceName) {
3110
- 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");
3111
3327
  return { type: "restart" };
3112
3328
  }
3113
3329
  getRecoveryAction(instanceName) {
3114
- 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");
3115
3331
  return { type: "restart" };
3116
3332
  }
3117
3333
  };
@@ -3138,8 +3354,8 @@ function getLaunchModeHandler(mode) {
3138
3354
  }
3139
3355
 
3140
3356
  // src/manager/restart-tracker.ts
3141
- import { createLogger as createLogger20 } from "@actant/shared";
3142
- var logger19 = createLogger20("restart-tracker");
3357
+ import { createLogger as createLogger21 } from "@actant/shared";
3358
+ var logger20 = createLogger21("restart-tracker");
3143
3359
  var DEFAULT_RESTART_POLICY = {
3144
3360
  maxRestarts: 5,
3145
3361
  backoffBaseMs: 1e3,
@@ -3157,12 +3373,12 @@ var RestartTracker = class {
3157
3373
  if (state.lastStartAt > 0) {
3158
3374
  const stableMs = Date.now() - state.lastStartAt;
3159
3375
  if (stableMs >= this.policy.resetAfterMs) {
3160
- 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");
3161
3377
  state.count = 0;
3162
3378
  }
3163
3379
  }
3164
3380
  if (state.count >= this.policy.maxRestarts) {
3165
- 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");
3166
3382
  return { allowed: false, delayMs: 0, attempt: state.count };
3167
3383
  }
3168
3384
  const delayMs = Math.min(
@@ -3175,7 +3391,7 @@ var RestartTracker = class {
3175
3391
  const state = this.getOrCreate(instanceName);
3176
3392
  state.count++;
3177
3393
  state.lastRestartAt = Date.now();
3178
- logger19.debug({ instanceName, count: state.count }, "Restart recorded");
3394
+ logger20.debug({ instanceName, count: state.count }, "Restart recorded");
3179
3395
  }
3180
3396
  recordStart(instanceName) {
3181
3397
  const state = this.getOrCreate(instanceName);
@@ -3202,8 +3418,8 @@ var RestartTracker = class {
3202
3418
 
3203
3419
  // src/communicator/claude-code-communicator.ts
3204
3420
  import { spawn as spawn4 } from "child_process";
3205
- import { createLogger as createLogger21 } from "@actant/shared";
3206
- var logger20 = createLogger21("claude-code-communicator");
3421
+ import { createLogger as createLogger22 } from "@actant/shared";
3422
+ var logger21 = createLogger22("claude-code-communicator");
3207
3423
  var DEFAULT_TIMEOUT_MS = 3e5;
3208
3424
  var ClaudeCodeCommunicator = class {
3209
3425
  executable;
@@ -3212,7 +3428,7 @@ var ClaudeCodeCommunicator = class {
3212
3428
  }
3213
3429
  async runPrompt(workspaceDir, prompt, options) {
3214
3430
  const args = this.buildArgs(prompt, options, "json");
3215
- logger20.debug({ workspaceDir, args }, "Running claude-code prompt");
3431
+ logger21.debug({ workspaceDir, args }, "Running claude-code prompt");
3216
3432
  return new Promise((resolve3, reject) => {
3217
3433
  const child = spawn4(this.executable, args, {
3218
3434
  cwd: workspaceDir,
@@ -3235,7 +3451,7 @@ var ClaudeCodeCommunicator = class {
3235
3451
  child.on("close", (code) => {
3236
3452
  clearTimeout(timer);
3237
3453
  if (code !== 0) {
3238
- 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");
3239
3455
  reject(new Error(`claude-code exited with code ${code}: ${stderr.slice(0, 500)}`));
3240
3456
  return;
3241
3457
  }
@@ -3245,7 +3461,7 @@ var ClaudeCodeCommunicator = class {
3245
3461
  const sessionId = parsed["session_id"];
3246
3462
  const subtype = parsed["subtype"];
3247
3463
  if (subtype && subtype !== "success") {
3248
- logger20.warn({ subtype }, "claude-code returned non-success subtype");
3464
+ logger21.warn({ subtype }, "claude-code returned non-success subtype");
3249
3465
  }
3250
3466
  let text;
3251
3467
  if (result && result.length > 0) {
@@ -3269,7 +3485,7 @@ var ClaudeCodeCommunicator = class {
3269
3485
  }
3270
3486
  async *streamPrompt(workspaceDir, prompt, options) {
3271
3487
  const args = this.buildArgs(prompt, options, "stream-json");
3272
- logger20.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3488
+ logger21.debug({ workspaceDir, args }, "Streaming claude-code prompt");
3273
3489
  const child = spawn4(this.executable, args, {
3274
3490
  cwd: workspaceDir,
3275
3491
  stdio: ["pipe", "pipe", "pipe"],
@@ -3366,11 +3582,11 @@ function parseStreamEvent(event) {
3366
3582
  }
3367
3583
 
3368
3584
  // src/communicator/cursor-communicator.ts
3369
- import { createLogger as createLogger22 } from "@actant/shared";
3370
- var logger21 = createLogger22("cursor-communicator");
3585
+ import { createLogger as createLogger23 } from "@actant/shared";
3586
+ var logger22 = createLogger23("cursor-communicator");
3371
3587
  var CursorCommunicator = class {
3372
3588
  async runPrompt(_workspaceDir, _prompt, _options) {
3373
- logger21.warn("Cursor backend does not yet support programmatic communication");
3589
+ logger22.warn("Cursor backend does not yet support programmatic communication");
3374
3590
  throw new Error(
3375
3591
  "Cursor backend does not support programmatic communication yet. Use claude-code backend for agent.run / agent.chat functionality."
3376
3592
  );
@@ -3384,21 +3600,32 @@ var CursorCommunicator = class {
3384
3600
  };
3385
3601
 
3386
3602
  // src/communicator/create-communicator.ts
3387
- function createCommunicator(backendType) {
3603
+ var registry2 = /* @__PURE__ */ new Map();
3604
+ function registerCommunicator(backendType, factory) {
3605
+ registry2.set(backendType, factory);
3606
+ }
3607
+ function createCommunicator(backendType, backendConfig) {
3608
+ const registered = registry2.get(backendType);
3609
+ if (registered) return registered(backendConfig);
3388
3610
  switch (backendType) {
3389
3611
  case "claude-code":
3390
3612
  return new ClaudeCodeCommunicator();
3391
3613
  case "cursor":
3614
+ case "cursor-agent":
3392
3615
  return new CursorCommunicator();
3393
3616
  case "custom":
3394
3617
  throw new Error(
3395
3618
  "Custom backend communicator not yet supported. Implement AgentCommunicator for your backend."
3396
3619
  );
3620
+ case "pi":
3621
+ throw new Error(
3622
+ "Pi backend communicator not registered. Ensure @actant/pi is installed and initialized."
3623
+ );
3397
3624
  }
3398
3625
  }
3399
3626
 
3400
3627
  // src/manager/agent-manager.ts
3401
- var logger22 = createLogger23("agent-manager");
3628
+ var logger23 = createLogger24("agent-manager");
3402
3629
  var AgentManager = class {
3403
3630
  constructor(initializer, launcher, instancesBaseDir, options) {
3404
3631
  this.initializer = initializer;
@@ -3439,7 +3666,7 @@ var AgentManager = class {
3439
3666
  const dir = join17(this.instancesBaseDir, meta.name);
3440
3667
  const fixed = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3441
3668
  this.cache.set(meta.name, fixed);
3442
- 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");
3443
3670
  if (action.type === "restart") {
3444
3671
  pendingRestarts.push(meta.name);
3445
3672
  }
@@ -3451,7 +3678,7 @@ var AgentManager = class {
3451
3678
  await this.moveToCorrupted(name);
3452
3679
  }
3453
3680
  this.watcher.start();
3454
- logger22.info({
3681
+ logger23.info({
3455
3682
  valid: valid.length,
3456
3683
  corrupted: corrupted.length,
3457
3684
  pendingRestarts: pendingRestarts.length
@@ -3459,9 +3686,9 @@ var AgentManager = class {
3459
3686
  for (const name of pendingRestarts) {
3460
3687
  try {
3461
3688
  await this.startAgent(name);
3462
- logger22.info({ name }, "Recovery restart succeeded");
3689
+ logger23.info({ name }, "Recovery restart succeeded");
3463
3690
  } catch (err) {
3464
- logger22.error({ name, error: err }, "Recovery restart failed");
3691
+ logger23.error({ name, error: err }, "Recovery restart failed");
3465
3692
  }
3466
3693
  }
3467
3694
  }
@@ -3486,13 +3713,16 @@ var AgentManager = class {
3486
3713
  return { meta, created };
3487
3714
  }
3488
3715
  /**
3489
- * Start an agent — launch the backend process.
3490
- * For ACP backends, also establishes ACP connection (initialize + session/new).
3716
+ * Start an agent — launch the backend process via ACP.
3717
+ * Requires the backend to support "acp" mode.
3718
+ * For acpOwnsProcess backends, ProcessLauncher is skipped.
3491
3719
  * @throws {AgentNotFoundError} if agent is not in cache
3492
3720
  * @throws {AgentAlreadyRunningError} if agent is already running
3721
+ * @throws {Error} if backend does not support "acp" mode
3493
3722
  */
3494
3723
  async startAgent(name) {
3495
3724
  const meta = this.requireAgent(name);
3725
+ requireMode(meta.backendType, "acp");
3496
3726
  if (meta.status === "running" || meta.status === "starting") {
3497
3727
  throw new AgentAlreadyRunningError(name);
3498
3728
  }
@@ -3500,23 +3730,38 @@ var AgentManager = class {
3500
3730
  const starting = await updateInstanceMeta(dir, { status: "starting" });
3501
3731
  this.cache.set(name, starting);
3502
3732
  try {
3503
- const proc = await this.launcher.launch(dir, starting);
3504
- this.processes.set(name, proc);
3505
- if (isAcpBackend(meta.backendType) && this.acpManager) {
3506
- const { command, args } = resolveBackend(meta.backendType, dir, meta.backendConfig);
3507
- await this.acpManager.connect(name, {
3733
+ const acpOnly = isAcpOnlyBackend(meta.backendType);
3734
+ let pid;
3735
+ if (!acpOnly) {
3736
+ const proc = await this.launcher.launch(dir, starting);
3737
+ this.processes.set(name, proc);
3738
+ pid = proc.pid;
3739
+ }
3740
+ if (this.acpManager) {
3741
+ const { command, args } = resolveAcpBackend(meta.backendType, dir, meta.backendConfig);
3742
+ const providerEnv = buildProviderEnv(meta.providerConfig);
3743
+ const connResult = await this.acpManager.connect(name, {
3508
3744
  command,
3509
3745
  args,
3510
3746
  cwd: dir,
3511
- connectionOptions: { autoApprove: true }
3747
+ connectionOptions: {
3748
+ autoApprove: true,
3749
+ ...Object.keys(providerEnv).length > 0 ? { env: providerEnv } : {}
3750
+ }
3512
3751
  });
3513
- logger22.info({ name }, "ACP connection established");
3752
+ logger23.info({ name, acpOnly, providerType: meta.providerConfig?.type }, "ACP connection established");
3753
+ if (acpOnly && "pid" in connResult && typeof connResult.pid === "number") {
3754
+ pid = connResult.pid;
3755
+ this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
3756
+ }
3514
3757
  }
3515
- const running = await updateInstanceMeta(dir, { status: "running", pid: proc.pid });
3758
+ const running = await updateInstanceMeta(dir, { status: "running", pid });
3516
3759
  this.cache.set(name, running);
3517
- this.watcher.watch(name, proc.pid);
3760
+ if (pid) {
3761
+ this.watcher.watch(name, pid);
3762
+ }
3518
3763
  this.restartTracker.recordStart(name);
3519
- logger22.info({ name, pid: proc.pid, launchMode: starting.launchMode, acp: isAcpBackend(meta.backendType) }, "Agent started");
3764
+ logger23.info({ name, pid, launchMode: starting.launchMode, acp: true }, "Agent started");
3520
3765
  } catch (err) {
3521
3766
  if (this.acpManager?.has(name)) {
3522
3767
  await this.acpManager.disconnect(name).catch(() => {
@@ -3524,7 +3769,11 @@ var AgentManager = class {
3524
3769
  }
3525
3770
  const errored = await updateInstanceMeta(dir, { status: "error" });
3526
3771
  this.cache.set(name, errored);
3527
- 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
+ ));
3528
3777
  }
3529
3778
  }
3530
3779
  /**
@@ -3535,7 +3784,7 @@ var AgentManager = class {
3535
3784
  const meta = this.requireAgent(name);
3536
3785
  const dir = join17(this.instancesBaseDir, name);
3537
3786
  if (meta.status !== "running" && meta.status !== "starting") {
3538
- 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");
3539
3788
  const stopped2 = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3540
3789
  this.cache.set(name, stopped2);
3541
3790
  return;
@@ -3545,7 +3794,7 @@ var AgentManager = class {
3545
3794
  this.cache.set(name, stopping);
3546
3795
  if (this.acpManager?.has(name)) {
3547
3796
  await this.acpManager.disconnect(name).catch((err) => {
3548
- logger22.warn({ name, error: err }, "Error disconnecting ACP during stop");
3797
+ logger23.warn({ name, error: err }, "Error disconnecting ACP during stop");
3549
3798
  });
3550
3799
  }
3551
3800
  const proc = this.processes.get(name);
@@ -3555,11 +3804,14 @@ var AgentManager = class {
3555
3804
  }
3556
3805
  const stopped = await updateInstanceMeta(dir, { status: "stopped", pid: void 0 });
3557
3806
  this.cache.set(name, stopped);
3558
- logger22.info({ name }, "Agent stopped");
3807
+ logger23.info({ name }, "Agent stopped");
3559
3808
  }
3560
3809
  /** Destroy an agent — stop it if running, then remove workspace. */
3561
3810
  async destroyAgent(name) {
3562
3811
  const meta = this.cache.get(name);
3812
+ if (!meta && !existsSync(join17(this.instancesBaseDir, name))) {
3813
+ throw new AgentNotFoundError(name);
3814
+ }
3563
3815
  if (meta && (meta.status === "running" || meta.status === "starting")) {
3564
3816
  await this.stopAgent(name);
3565
3817
  }
@@ -3568,7 +3820,7 @@ var AgentManager = class {
3568
3820
  await this.initializer.destroyInstance(name);
3569
3821
  this.cache.delete(name);
3570
3822
  this.processes.delete(name);
3571
- logger22.info({ name }, "Agent destroyed");
3823
+ logger23.info({ name }, "Agent destroyed");
3572
3824
  }
3573
3825
  /** Get agent metadata by name. */
3574
3826
  getAgent(name) {
@@ -3611,6 +3863,17 @@ var AgentManager = class {
3611
3863
  created
3612
3864
  };
3613
3865
  }
3866
+ /**
3867
+ * Open an agent's native TUI/UI (e.g. `cursor <dir>`).
3868
+ * Requires the backend to support "open" mode.
3869
+ * @throws if backend does not support "open" mode
3870
+ */
3871
+ async openAgent(name) {
3872
+ const meta = this.requireAgent(name);
3873
+ const dir = join17(this.instancesBaseDir, name);
3874
+ const resolved = openBackend(meta.backendType, dir);
3875
+ return resolved;
3876
+ }
3614
3877
  /**
3615
3878
  * Register an externally-spawned process with the manager.
3616
3879
  * Sets processOwnership to "external" and registers ProcessWatcher monitoring.
@@ -3622,6 +3885,17 @@ var AgentManager = class {
3622
3885
  if (meta.status === "running" && meta.pid != null) {
3623
3886
  throw new AgentAlreadyAttachedError(name);
3624
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
+ }
3625
3899
  const dir = join17(this.instancesBaseDir, name);
3626
3900
  const mergedMetadata = attachMetadata ? { ...meta.metadata, ...attachMetadata } : meta.metadata;
3627
3901
  const updated = await updateInstanceMeta(dir, {
@@ -3633,7 +3907,7 @@ var AgentManager = class {
3633
3907
  this.cache.set(name, updated);
3634
3908
  this.processes.set(name, { pid, workspaceDir: dir, instanceName: name });
3635
3909
  this.watcher.watch(name, pid);
3636
- logger22.info({ name, pid }, "External process attached");
3910
+ logger23.info({ name, pid }, "External process attached");
3637
3911
  return updated;
3638
3912
  }
3639
3913
  /**
@@ -3656,7 +3930,7 @@ var AgentManager = class {
3656
3930
  processOwnership: "managed"
3657
3931
  });
3658
3932
  this.cache.set(name, updated);
3659
- logger22.info({ name }, "External process detached");
3933
+ logger23.info({ name }, "External process detached");
3660
3934
  let workspaceCleaned = false;
3661
3935
  if (options?.cleanup && meta.workspacePolicy === "ephemeral") {
3662
3936
  await this.destroyAgent(name);
@@ -3674,7 +3948,7 @@ var AgentManager = class {
3674
3948
  const conn = this.acpManager.getConnection(name);
3675
3949
  const sessionId = this.acpManager.getPrimarySessionId(name);
3676
3950
  if (conn && sessionId) {
3677
- logger22.debug({ name, sessionId }, "Sending prompt via ACP");
3951
+ logger23.debug({ name, sessionId }, "Sending prompt via ACP");
3678
3952
  const result = await conn.prompt(sessionId, prompt);
3679
3953
  return { text: result.text, sessionId };
3680
3954
  }
@@ -3685,14 +3959,46 @@ var AgentManager = class {
3685
3959
  }
3686
3960
  /**
3687
3961
  * Send a prompt to an agent and stream the response.
3688
- * Uses ACP connection if available, otherwise falls back to CLI pipe mode.
3962
+ * Uses ACP connection if available, otherwise falls back to communicator.
3689
3963
  */
3690
3964
  streamPrompt(name, prompt, options) {
3691
3965
  const meta = this.requireAgent(name);
3966
+ if (this.acpManager?.has(name)) {
3967
+ const conn = this.acpManager.getConnection(name);
3968
+ const sessionId = this.acpManager.getPrimarySessionId(name);
3969
+ if (conn && sessionId) {
3970
+ logger23.debug({ name, sessionId }, "Streaming prompt via ACP");
3971
+ return this.streamFromAcp(conn, sessionId, prompt);
3972
+ }
3973
+ }
3692
3974
  const dir = join17(this.instancesBaseDir, name);
3693
3975
  const communicator = createCommunicator(meta.backendType);
3694
3976
  return communicator.streamPrompt(dir, prompt, options);
3695
3977
  }
3978
+ async *streamFromAcp(conn, sessionId, prompt) {
3979
+ try {
3980
+ for await (const event of conn.streamPrompt(sessionId, prompt)) {
3981
+ const record = event;
3982
+ const type = record["type"];
3983
+ if (type === "text" || type === "assistant") {
3984
+ const content = record["content"] ?? record["message"] ?? "";
3985
+ yield { type: "text", content };
3986
+ } else if (type === "tool_use") {
3987
+ const toolName = record["name"];
3988
+ yield { type: "tool_use", content: toolName ? `[Tool: ${toolName}]` : "" };
3989
+ } else if (type === "result") {
3990
+ yield { type: "result", content: record["result"] ?? "" };
3991
+ } else if (type === "error") {
3992
+ const errMsg = record["error"]?.["message"];
3993
+ yield { type: "error", content: errMsg ?? "Unknown error" };
3994
+ } else if (typeof record["content"] === "string") {
3995
+ yield { type: "text", content: record["content"] };
3996
+ }
3997
+ }
3998
+ } catch (err) {
3999
+ yield { type: "error", content: err instanceof Error ? err.message : String(err) };
4000
+ }
4001
+ }
3696
4002
  /**
3697
4003
  * Send a message to a running agent via its ACP session.
3698
4004
  * Unlike runPrompt, this requires the agent to be started with ACP.
@@ -3733,7 +4039,7 @@ var AgentManager = class {
3733
4039
  }
3734
4040
  const handler = getLaunchModeHandler(meta.launchMode);
3735
4041
  const action = handler.getProcessExitAction(instanceName, meta);
3736
- 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");
3737
4043
  if (this.acpManager?.has(instanceName)) {
3738
4044
  await this.acpManager.disconnect(instanceName).catch(() => {
3739
4045
  });
@@ -3754,28 +4060,28 @@ var AgentManager = class {
3754
4060
  if (!decision.allowed) {
3755
4061
  const errored = await updateInstanceMeta(dir, { status: "error" });
3756
4062
  this.cache.set(instanceName, errored);
3757
- 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");
3758
4064
  break;
3759
4065
  }
3760
- 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");
3761
4067
  if (decision.delayMs > 0) {
3762
4068
  await delay(decision.delayMs);
3763
4069
  }
3764
4070
  this.restartTracker.recordRestart(instanceName);
3765
4071
  try {
3766
4072
  await this.startAgent(instanceName);
3767
- logger22.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
4073
+ logger23.info({ instanceName, attempt: decision.attempt }, "Crash restart succeeded");
3768
4074
  } catch (err) {
3769
- logger22.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
4075
+ logger23.error({ instanceName, attempt: decision.attempt, error: err }, "Crash restart failed");
3770
4076
  }
3771
4077
  break;
3772
4078
  }
3773
4079
  case "destroy":
3774
4080
  try {
3775
4081
  await this.destroyAgent(instanceName);
3776
- logger22.info({ instanceName }, "One-shot agent destroyed after exit");
4082
+ logger23.info({ instanceName }, "One-shot agent destroyed after exit");
3777
4083
  } catch (err) {
3778
- logger22.error({ instanceName, error: err }, "One-shot auto-destroy failed");
4084
+ logger23.error({ instanceName, error: err }, "One-shot auto-destroy failed");
3779
4085
  }
3780
4086
  break;
3781
4087
  case "mark-stopped":
@@ -3795,12 +4101,39 @@ var AgentManager = class {
3795
4101
  const src = join17(this.instancesBaseDir, name);
3796
4102
  const dest = join17(this.corruptedDir, `${name}-${Date.now()}`);
3797
4103
  await rename3(src, dest);
3798
- logger22.warn({ name, dest }, "Corrupted instance moved");
4104
+ logger23.warn({ name, dest }, "Corrupted instance moved");
3799
4105
  } catch (err) {
3800
- logger22.error({ name, error: err }, "Failed to move corrupted instance");
4106
+ logger23.error({ name, error: err }, "Failed to move corrupted instance");
3801
4107
  }
3802
4108
  }
3803
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
+ }
3804
4137
 
3805
4138
  // src/manager/launcher/mock-launcher.ts
3806
4139
  var nextPid = 1e4;
@@ -3823,14 +4156,14 @@ var MockLauncher = class {
3823
4156
 
3824
4157
  // src/manager/launcher/process-launcher.ts
3825
4158
  import { spawn as spawn5 } from "child_process";
3826
- import { AgentLaunchError, createLogger as createLogger25 } from "@actant/shared";
4159
+ import { AgentLaunchError as AgentLaunchError2, createLogger as createLogger26 } from "@actant/shared";
3827
4160
 
3828
4161
  // src/manager/launcher/process-log-writer.ts
3829
4162
  import { createWriteStream } from "fs";
3830
4163
  import { mkdir as mkdir12, rename as rename4, stat as stat7, readFile as readFile5 } from "fs/promises";
3831
4164
  import { join as join18 } from "path";
3832
- import { createLogger as createLogger24 } from "@actant/shared";
3833
- var logger23 = createLogger24("process-log-writer");
4165
+ import { createLogger as createLogger25 } from "@actant/shared";
4166
+ var logger24 = createLogger25("process-log-writer");
3834
4167
  var DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
3835
4168
  var DEFAULT_MAX_FILES = 3;
3836
4169
  var ProcessLogWriter = class {
@@ -3867,7 +4200,7 @@ var ProcessLogWriter = class {
3867
4200
  } catch {
3868
4201
  this.stderrBytes = 0;
3869
4202
  }
3870
- logger23.debug({ logsDir: this.logsDir }, "Log writer initialized");
4203
+ logger24.debug({ logsDir: this.logsDir }, "Log writer initialized");
3871
4204
  }
3872
4205
  /**
3873
4206
  * Attach to readable streams from a spawned process.
@@ -3949,7 +4282,7 @@ var ProcessLogWriter = class {
3949
4282
  this.stderrStream = newStream;
3950
4283
  this.stderrBytes = 0;
3951
4284
  }
3952
- logger23.debug({ filename }, "Log file rotated");
4285
+ logger24.debug({ filename }, "Log file rotated");
3953
4286
  }
3954
4287
  closeStream(stream) {
3955
4288
  if (!stream) return Promise.resolve();
@@ -3960,7 +4293,7 @@ var ProcessLogWriter = class {
3960
4293
  };
3961
4294
 
3962
4295
  // src/manager/launcher/process-launcher.ts
3963
- var logger24 = createLogger25("process-launcher");
4296
+ var logger25 = createLogger26("process-launcher");
3964
4297
  var DEFAULT_TERMINATE_TIMEOUT = 5e3;
3965
4298
  var DEFAULT_SPAWN_VERIFY_DELAY = 500;
3966
4299
  var ProcessLauncher = class {
@@ -3982,7 +4315,7 @@ var ProcessLauncher = class {
3982
4315
  meta.backendConfig
3983
4316
  );
3984
4317
  const useAcp = isAcpBackend(meta.backendType);
3985
- 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");
3986
4319
  const captureNonAcpLogs = !useAcp && this.enableProcessLogs;
3987
4320
  let stdio;
3988
4321
  if (useAcp) {
@@ -4014,7 +4347,7 @@ var ProcessLauncher = class {
4014
4347
  }
4015
4348
  });
4016
4349
  if ("error" in spawnResult) {
4017
- throw new AgentLaunchError(meta.name, spawnResult.error);
4350
+ throw new AgentLaunchError2(meta.name, spawnResult.error);
4018
4351
  }
4019
4352
  const pid = spawnResult.pid;
4020
4353
  if (!useAcp) {
@@ -4025,27 +4358,27 @@ var ProcessLauncher = class {
4025
4358
  earlyExit = true;
4026
4359
  });
4027
4360
  child.on("error", (err) => {
4028
- 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");
4029
4362
  });
4030
4363
  if (this.spawnVerifyDelayMs > 0) {
4031
4364
  await delay(this.spawnVerifyDelayMs);
4032
4365
  if (earlyExit || !isProcessAlive(pid)) {
4033
- throw new AgentLaunchError(
4366
+ throw new AgentLaunchError2(
4034
4367
  meta.name,
4035
4368
  new Error(`Process exited immediately after spawn (pid=${pid}, command=${command})`)
4036
4369
  );
4037
4370
  }
4038
4371
  }
4039
- 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");
4040
4373
  if (captureNonAcpLogs && child.stdout && child.stderr) {
4041
4374
  const logWriter = new ProcessLogWriter(workspaceDir, this.logWriterOptions);
4042
4375
  try {
4043
4376
  await logWriter.initialize();
4044
4377
  logWriter.attach(child.stdout, child.stderr);
4045
4378
  this.logWriters.set(meta.name, logWriter);
4046
- logger24.debug({ name: meta.name }, "Process log capture enabled");
4379
+ logger25.debug({ name: meta.name }, "Process log capture enabled");
4047
4380
  } catch (err) {
4048
- 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");
4049
4382
  }
4050
4383
  }
4051
4384
  const result = {
@@ -4074,27 +4407,27 @@ var ProcessLauncher = class {
4074
4407
  this.logWriters.delete(instanceName);
4075
4408
  }
4076
4409
  if (!isProcessAlive(pid)) {
4077
- logger24.info({ instanceName, pid }, "Process already exited");
4410
+ logger25.info({ instanceName, pid }, "Process already exited");
4078
4411
  return;
4079
4412
  }
4080
- logger24.info({ instanceName, pid }, "Sending SIGTERM");
4413
+ logger25.info({ instanceName, pid }, "Sending SIGTERM");
4081
4414
  sendSignal(pid, "SIGTERM");
4082
4415
  const deadline = Date.now() + this.terminateTimeoutMs;
4083
4416
  const pollInterval = 200;
4084
4417
  while (Date.now() < deadline) {
4085
4418
  await delay(pollInterval);
4086
4419
  if (!isProcessAlive(pid)) {
4087
- logger24.info({ instanceName, pid }, "Process terminated gracefully");
4420
+ logger25.info({ instanceName, pid }, "Process terminated gracefully");
4088
4421
  return;
4089
4422
  }
4090
4423
  }
4091
- 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");
4092
4425
  sendSignal(pid, "SIGKILL");
4093
4426
  await delay(500);
4094
4427
  if (isProcessAlive(pid)) {
4095
- logger24.error({ instanceName, pid }, "Process still alive after SIGKILL");
4428
+ logger25.error({ instanceName, pid }, "Process still alive after SIGKILL");
4096
4429
  } else {
4097
- logger24.info({ instanceName, pid }, "Process killed with SIGKILL");
4430
+ logger25.info({ instanceName, pid }, "Process killed with SIGKILL");
4098
4431
  }
4099
4432
  }
4100
4433
  };
@@ -4116,7 +4449,10 @@ var SkillDefinitionSchema = z4.object({
4116
4449
  name: z4.string().min(1),
4117
4450
  description: z4.string().optional(),
4118
4451
  content: z4.string().min(1),
4119
- 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()
4120
4456
  }).passthrough();
4121
4457
  var SkillManager = class extends BaseComponentManager {
4122
4458
  componentType = "Skill";
@@ -4344,8 +4680,8 @@ var PluginManager = class extends BaseComponentManager {
4344
4680
  };
4345
4681
 
4346
4682
  // src/permissions/permission-policy-enforcer.ts
4347
- import { createLogger as createLogger26 } from "@actant/shared";
4348
- var logger25 = createLogger26("permission-policy-enforcer");
4683
+ import { createLogger as createLogger27 } from "@actant/shared";
4684
+ var logger26 = createLogger27("permission-policy-enforcer");
4349
4685
  var KIND_TO_TOOL = {
4350
4686
  read: "Read",
4351
4687
  edit: "Edit",
@@ -4362,7 +4698,7 @@ var PermissionPolicyEnforcer = class {
4362
4698
  }
4363
4699
  updateConfig(config) {
4364
4700
  this.config = { ...config };
4365
- logger25.info("Permission policy config updated");
4701
+ logger26.info("Permission policy config updated");
4366
4702
  }
4367
4703
  getConfig() {
4368
4704
  return this.config;
@@ -4520,8 +4856,8 @@ function escapeRegex(str) {
4520
4856
  }
4521
4857
 
4522
4858
  // src/permissions/permission-audit.ts
4523
- import { createLogger as createLogger27 } from "@actant/shared";
4524
- var logger26 = createLogger27("permission-audit");
4859
+ import { createLogger as createLogger28 } from "@actant/shared";
4860
+ var logger27 = createLogger28("permission-audit");
4525
4861
  var PermissionAuditLogger = class {
4526
4862
  instanceName;
4527
4863
  constructor(instanceName) {
@@ -4534,7 +4870,7 @@ var PermissionAuditLogger = class {
4534
4870
  ...detail,
4535
4871
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4536
4872
  };
4537
- logger26.info(entry, `[audit] ${event}`);
4873
+ logger27.info(entry, `[audit] ${event}`);
4538
4874
  }
4539
4875
  logEvaluation(toolCall, decision) {
4540
4876
  this.log("permission.evaluated", { toolCall, decision });
@@ -4555,8 +4891,8 @@ var PermissionAuditLogger = class {
4555
4891
 
4556
4892
  // src/session/session-registry.ts
4557
4893
  import { randomUUID as randomUUID8 } from "crypto";
4558
- import { createLogger as createLogger28 } from "@actant/shared";
4559
- var logger27 = createLogger28("session-registry");
4894
+ import { createLogger as createLogger29 } from "@actant/shared";
4895
+ var logger28 = createLogger29("session-registry");
4560
4896
  var DEFAULT_IDLE_TTL_MS = 30 * 60 * 1e3;
4561
4897
  var TTL_CHECK_INTERVAL_MS = 60 * 1e3;
4562
4898
  var SessionRegistry = class {
@@ -4587,7 +4923,7 @@ var SessionRegistry = class {
4587
4923
  idleTtlMs: opts.idleTtlMs ?? this.defaultIdleTtlMs
4588
4924
  };
4589
4925
  this.sessions.set(session.sessionId, session);
4590
- 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");
4591
4927
  return session;
4592
4928
  }
4593
4929
  /** Get a session by ID, or undefined if not found. */
@@ -4619,7 +4955,7 @@ var SessionRegistry = class {
4619
4955
  session.clientId = null;
4620
4956
  session.state = "idle";
4621
4957
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4622
- logger27.info({ sessionId, agentName: session.agentName }, "Session released to idle");
4958
+ logger28.info({ sessionId, agentName: session.agentName }, "Session released to idle");
4623
4959
  }
4624
4960
  /**
4625
4961
  * Resume an idle session, binding it to a (potentially new) client.
@@ -4632,7 +4968,7 @@ var SessionRegistry = class {
4632
4968
  session.clientId = clientId;
4633
4969
  session.state = "active";
4634
4970
  session.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
4635
- logger27.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
4971
+ logger28.info({ sessionId, clientId, agentName: session.agentName }, "Session resumed");
4636
4972
  return true;
4637
4973
  }
4638
4974
  /** Explicitly close and remove a session. */
@@ -4640,7 +4976,7 @@ var SessionRegistry = class {
4640
4976
  const session = this.sessions.get(sessionId);
4641
4977
  if (!session) return false;
4642
4978
  this.sessions.delete(sessionId);
4643
- logger27.info({ sessionId, agentName: session.agentName }, "Session closed");
4979
+ logger28.info({ sessionId, agentName: session.agentName }, "Session closed");
4644
4980
  return true;
4645
4981
  }
4646
4982
  /** Close all sessions for a given agent. */
@@ -4653,7 +4989,7 @@ var SessionRegistry = class {
4653
4989
  }
4654
4990
  }
4655
4991
  if (count > 0) {
4656
- logger27.info({ agentName, count }, "Sessions closed for agent");
4992
+ logger28.info({ agentName, count }, "Sessions closed for agent");
4657
4993
  }
4658
4994
  return count;
4659
4995
  }
@@ -4677,7 +5013,7 @@ var SessionRegistry = class {
4677
5013
  if (now - lastActivity > session.idleTtlMs) {
4678
5014
  session.state = "expired";
4679
5015
  this.sessions.delete(id);
4680
- 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)");
4681
5017
  this.onExpireCallback?.(session);
4682
5018
  }
4683
5019
  }
@@ -4689,13 +5025,13 @@ import { join as join20 } from "path";
4689
5025
  import { mkdir as mkdir13, rm as rm5 } from "fs/promises";
4690
5026
  import { execFile } from "child_process";
4691
5027
  import { promisify } from "util";
4692
- import { createLogger as createLogger30 } from "@actant/shared";
5028
+ import { createLogger as createLogger31 } from "@actant/shared";
4693
5029
 
4694
5030
  // src/source/local-source.ts
4695
5031
  import { readFile as readFile6, readdir as readdir5, stat as stat8 } from "fs/promises";
4696
5032
  import { join as join19, extname as extname3 } from "path";
4697
- import { createLogger as createLogger29 } from "@actant/shared";
4698
- var logger28 = createLogger29("local-source");
5033
+ import { createLogger as createLogger30 } from "@actant/shared";
5034
+ var logger29 = createLogger30("local-source");
4699
5035
  var LocalSource = class {
4700
5036
  type = "local";
4701
5037
  packageName;
@@ -4704,6 +5040,9 @@ var LocalSource = class {
4704
5040
  this.packageName = packageName;
4705
5041
  this.config = config;
4706
5042
  }
5043
+ getRootDir() {
5044
+ return this.config.path;
5045
+ }
4707
5046
  async fetch() {
4708
5047
  return this.loadPackage();
4709
5048
  }
@@ -4723,7 +5062,7 @@ var LocalSource = class {
4723
5062
  this.loadPresets(rootDir, manifest.presets),
4724
5063
  this.loadJsonDir(rootDir, manifest.components?.templates, "templates")
4725
5064
  ]);
4726
- logger28.info({ packageName: this.packageName, rootDir }, "Local package loaded");
5065
+ logger29.info({ packageName: this.packageName, rootDir }, "Local package loaded");
4727
5066
  return { manifest, skills, prompts, mcpServers, workflows, presets, templates };
4728
5067
  }
4729
5068
  async loadManifest(rootDir) {
@@ -4732,7 +5071,7 @@ var LocalSource = class {
4732
5071
  const raw = await readFile6(manifestPath, "utf-8");
4733
5072
  return JSON.parse(raw);
4734
5073
  } catch {
4735
- logger28.debug({ rootDir }, "No actant.json found, scanning directories");
5074
+ logger29.debug({ rootDir }, "No actant.json found, scanning directories");
4736
5075
  return { name: this.packageName };
4737
5076
  }
4738
5077
  }
@@ -4744,7 +5083,7 @@ var LocalSource = class {
4744
5083
  const raw = await readFile6(join19(rootDir, relPath), "utf-8");
4745
5084
  items2.push(JSON.parse(raw));
4746
5085
  } catch (err) {
4747
- 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`);
4748
5087
  }
4749
5088
  }
4750
5089
  return items2;
@@ -4767,7 +5106,7 @@ var LocalSource = class {
4767
5106
  const raw = await readFile6(fullPath, "utf-8");
4768
5107
  items.push(JSON.parse(raw));
4769
5108
  } catch (err) {
4770
- logger28.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
5109
+ logger29.warn({ file, error: err }, `Failed to load ${subDir} file, skipping`);
4771
5110
  }
4772
5111
  }
4773
5112
  for (const entry of entries) {
@@ -4792,7 +5131,7 @@ var LocalSource = class {
4792
5131
  }
4793
5132
  }
4794
5133
  if (subDir === "skills") {
4795
- const { parseSkillMd } = await import("./skill-md-parser-2HXC4AAW.js");
5134
+ const { parseSkillMd } = await import("./skill-md-parser-HXLTZAUU.js");
4796
5135
  for (const entry of entries) {
4797
5136
  if (extname3(entry) === ".json") continue;
4798
5137
  const subDirPath = join19(dirPath, entry);
@@ -4817,7 +5156,7 @@ var LocalSource = class {
4817
5156
 
4818
5157
  // src/source/github-source.ts
4819
5158
  var execFileAsync = promisify(execFile);
4820
- var logger29 = createLogger30("github-source");
5159
+ var logger30 = createLogger31("github-source");
4821
5160
  var GitHubSource = class {
4822
5161
  type = "github";
4823
5162
  packageName;
@@ -4828,6 +5167,9 @@ var GitHubSource = class {
4828
5167
  this.config = config;
4829
5168
  this.cacheDir = join20(cacheDir, packageName);
4830
5169
  }
5170
+ getRootDir() {
5171
+ return this.cacheDir;
5172
+ }
4831
5173
  async fetch() {
4832
5174
  await this.clone();
4833
5175
  return this.readCached();
@@ -4836,21 +5178,21 @@ var GitHubSource = class {
4836
5178
  try {
4837
5179
  await this.pull();
4838
5180
  } catch {
4839
- logger29.info("Pull failed, re-cloning");
5181
+ logger30.info("Pull failed, re-cloning");
4840
5182
  await this.clone();
4841
5183
  }
4842
5184
  return this.readCached();
4843
5185
  }
4844
5186
  async dispose() {
4845
5187
  await rm5(this.cacheDir, { recursive: true, force: true });
4846
- logger29.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
5188
+ logger30.debug({ cacheDir: this.cacheDir }, "Cache cleaned");
4847
5189
  }
4848
5190
  async clone() {
4849
5191
  await rm5(this.cacheDir, { recursive: true, force: true });
4850
5192
  await mkdir13(this.cacheDir, { recursive: true });
4851
5193
  const branch = this.config.branch ?? "main";
4852
5194
  const args = ["clone", "--depth", "1", "--branch", branch, this.config.url, this.cacheDir];
4853
- logger29.info({ url: this.config.url, branch }, "Cloning repository");
5195
+ logger30.info({ url: this.config.url, branch }, "Cloning repository");
4854
5196
  await execFileAsync("git", args, { timeout: 6e4 });
4855
5197
  }
4856
5198
  async pull() {
@@ -4868,7 +5210,7 @@ var GitHubSource = class {
4868
5210
  // src/source/source-manager.ts
4869
5211
  import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir14 } from "fs/promises";
4870
5212
  import { join as join21 } from "path";
4871
- import { createLogger as createLogger31 } from "@actant/shared";
5213
+ import { createLogger as createLogger32 } from "@actant/shared";
4872
5214
 
4873
5215
  // src/version/sync-report.ts
4874
5216
  function createEmptySyncReport() {
@@ -4893,7 +5235,7 @@ var DEFAULT_SOURCE_CONFIG = {
4893
5235
  url: "https://github.com/blackplume233/actant-hub.git",
4894
5236
  branch: "main"
4895
5237
  };
4896
- var logger30 = createLogger31("source-manager");
5238
+ var logger31 = createLogger32("source-manager");
4897
5239
  var SourceManager = class {
4898
5240
  sources = /* @__PURE__ */ new Map();
4899
5241
  presets = /* @__PURE__ */ new Map();
@@ -4901,11 +5243,13 @@ var SourceManager = class {
4901
5243
  homeDir;
4902
5244
  sourcesFilePath;
4903
5245
  cacheDir;
4904
- constructor(homeDir, managers) {
5246
+ skipDefaultSource;
5247
+ constructor(homeDir, managers, options) {
4905
5248
  this.homeDir = homeDir;
4906
5249
  this.managers = managers;
4907
5250
  this.sourcesFilePath = join21(homeDir, "sources.json");
4908
5251
  this.cacheDir = join21(homeDir, "sources-cache");
5252
+ this.skipDefaultSource = options?.skipDefaultSource ?? false;
4909
5253
  }
4910
5254
  // ---------------------------------------------------------------------------
4911
5255
  // Source CRUD
@@ -4919,7 +5263,7 @@ var SourceManager = class {
4919
5263
  this.sources.set(name, source);
4920
5264
  this.injectComponents(name, result);
4921
5265
  await this.persistSources();
4922
- logger30.info({ name, type: config.type }, "Source added");
5266
+ logger31.info({ name, type: config.type }, "Source added");
4923
5267
  return result;
4924
5268
  }
4925
5269
  async syncSource(name) {
@@ -4935,7 +5279,7 @@ var SourceManager = class {
4935
5279
  const newSnapshot = this.snapshotComponents(name);
4936
5280
  await this.persistSources();
4937
5281
  const report = this.buildSyncReport(oldSnapshot, newSnapshot);
4938
- logger30.info({ name, report }, "Source synced");
5282
+ logger31.info({ name, report }, "Source synced");
4939
5283
  return { fetchResult: result, report };
4940
5284
  }
4941
5285
  async syncAll() {
@@ -4948,7 +5292,7 @@ var SourceManager = class {
4948
5292
  const { report } = await this.syncSourceWithReport(name);
4949
5293
  reports.push(report);
4950
5294
  } catch (err) {
4951
- logger30.warn({ name, error: err }, "Failed to sync source, skipping");
5295
+ logger31.warn({ name, error: err }, "Failed to sync source, skipping");
4952
5296
  }
4953
5297
  }
4954
5298
  return { report: mergeSyncReports(reports) };
@@ -4961,7 +5305,7 @@ var SourceManager = class {
4961
5305
  await source.dispose();
4962
5306
  this.sources.delete(name);
4963
5307
  await this.persistSources();
4964
- logger30.info({ name }, "Source removed");
5308
+ logger31.info({ name }, "Source removed");
4965
5309
  return true;
4966
5310
  }
4967
5311
  listSources() {
@@ -4974,6 +5318,10 @@ var SourceManager = class {
4974
5318
  hasSource(name) {
4975
5319
  return this.sources.has(name);
4976
5320
  }
5321
+ /** Resolve a registered source name to its filesystem root directory. */
5322
+ getSourceRootDir(name) {
5323
+ return this.getSourceOrThrow(name).getRootDir();
5324
+ }
4977
5325
  // ---------------------------------------------------------------------------
4978
5326
  // Preset operations
4979
5327
  // ---------------------------------------------------------------------------
@@ -5039,14 +5387,13 @@ var SourceManager = class {
5039
5387
  // Initialization (load persisted sources on startup)
5040
5388
  // ---------------------------------------------------------------------------
5041
5389
  async initialize() {
5042
- let entries;
5390
+ let entries = [];
5043
5391
  try {
5044
5392
  const raw = await readFile7(this.sourcesFilePath, "utf-8");
5045
5393
  const data = JSON.parse(raw);
5046
5394
  entries = Object.entries(data.sources ?? {}).map(([name, config]) => ({ name, config }));
5047
5395
  } catch {
5048
- logger30.debug("No sources.json found, starting with empty sources");
5049
- return;
5396
+ logger31.debug("No sources.json found, starting with empty sources");
5050
5397
  }
5051
5398
  for (const entry of entries) {
5052
5399
  try {
@@ -5054,11 +5401,26 @@ var SourceManager = class {
5054
5401
  const result = await source.fetch();
5055
5402
  this.sources.set(entry.name, source);
5056
5403
  this.injectComponents(entry.name, result);
5057
- logger30.info({ name: entry.name }, "Source restored from config");
5404
+ logger31.info({ name: entry.name }, "Source restored from config");
5058
5405
  } catch (err) {
5059
- 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");
5060
5407
  }
5061
5408
  }
5409
+ await this.ensureDefaultSource();
5410
+ }
5411
+ /**
5412
+ * Registers the official actant-hub as the default source if not already present.
5413
+ * Fails silently when offline or the repo is unreachable.
5414
+ */
5415
+ async ensureDefaultSource() {
5416
+ if (this.skipDefaultSource) return;
5417
+ if (this.sources.has(DEFAULT_SOURCE_NAME)) return;
5418
+ try {
5419
+ await this.addSource(DEFAULT_SOURCE_NAME, DEFAULT_SOURCE_CONFIG);
5420
+ logger31.info("Default source registered: %s", DEFAULT_SOURCE_NAME);
5421
+ } catch (err) {
5422
+ logger31.debug({ error: err }, "Failed to register default source (offline?), skipping");
5423
+ }
5062
5424
  }
5063
5425
  // ---------------------------------------------------------------------------
5064
5426
  // Internals
@@ -5091,12 +5453,24 @@ var SourceManager = class {
5091
5453
  this.presets.set(`${packageName}@${preset.name}`, preset);
5092
5454
  }
5093
5455
  if (this.managers.templateRegistry && result.templates.length > 0) {
5456
+ const nsRef = (ref) => ref.includes("@") ? ref : `${packageName}@${ref}`;
5094
5457
  for (const template of result.templates) {
5095
- 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
+ };
5096
5470
  this.managers.templateRegistry.register(nsTemplate);
5097
5471
  }
5098
5472
  }
5099
- logger30.debug(
5473
+ logger31.debug(
5100
5474
  {
5101
5475
  packageName,
5102
5476
  skills: result.skills.length,
@@ -5229,6 +5603,671 @@ function isMajorVersionChange(oldVersion, newVersion) {
5229
5603
  if (oldMajor === void 0 || newMajor === void 0) return false;
5230
5604
  return newMajor !== oldMajor;
5231
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
+ }
5232
6271
  export {
5233
6272
  AgentBackendSchema,
5234
6273
  AgentInitializer,
@@ -5236,6 +6275,7 @@ export {
5236
6275
  AgentManager,
5237
6276
  AgentStatusSchema,
5238
6277
  AgentTemplateSchema,
6278
+ BUILTIN_PROVIDERS,
5239
6279
  BaseComponentManager,
5240
6280
  ClaudeCodeBuilder,
5241
6281
  ClaudeCodeCommunicator,
@@ -5272,6 +6312,7 @@ export {
5272
6312
  McpServerRefSchema,
5273
6313
  MkdirStep,
5274
6314
  MockLauncher,
6315
+ ModelProviderRegistry,
5275
6316
  ModelProviderSchema,
5276
6317
  NpmInstallStep,
5277
6318
  PermissionAuditLogger,
@@ -5289,6 +6330,7 @@ export {
5289
6330
  SessionRegistry,
5290
6331
  SkillManager,
5291
6332
  SourceManager,
6333
+ SourceValidator,
5292
6334
  StepRegistry,
5293
6335
  TaskDispatcher,
5294
6336
  TaskQueue,
@@ -5300,16 +6342,26 @@ export {
5300
6342
  createCommunicator,
5301
6343
  createDefaultStepRegistry,
5302
6344
  createLauncher,
6345
+ getBackendDescriptor,
5303
6346
  getLaunchModeHandler,
5304
6347
  globMatch,
5305
6348
  isAcpBackend,
6349
+ isAcpOnlyBackend,
5306
6350
  isProcessAlive,
5307
6351
  metaFilePath,
6352
+ modelProviderRegistry,
6353
+ openBackend,
5308
6354
  readInstanceMeta,
6355
+ registerBackend,
6356
+ registerBuiltinProviders,
6357
+ registerCommunicator,
6358
+ requireMode,
6359
+ resolveAcpBackend,
5309
6360
  resolveBackend,
5310
6361
  resolvePermissions,
5311
6362
  resolvePermissionsWithMcp,
5312
6363
  scanInstances,
6364
+ supportsMode,
5313
6365
  toAgentTemplate,
5314
6366
  updateInstanceMeta,
5315
6367
  validateBackendConfig,