@rowan-agent/agent 0.8.4 → 0.8.6

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
@@ -33,6 +33,18 @@ function buildSkillsDescription(skills) {
33
33
  lines.push("</available_skills>");
34
34
  return lines.join("\n");
35
35
  }
36
+ function buildContextDescription(contexts) {
37
+ if (contexts.length === 0) return "";
38
+ const lines = ["<agent_context>"];
39
+ for (const context of contexts) {
40
+ lines.push(` <context name="${escapeXml(context.name)}">`);
41
+ const value = jsonToXml(context.value, 2);
42
+ if (value) lines.push(value);
43
+ lines.push(" </context>");
44
+ }
45
+ lines.push("</agent_context>");
46
+ return lines.join("\n");
47
+ }
36
48
  function formatResourceOutput(resource) {
37
49
  const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
38
50
  if (resource.baseDir) {
@@ -955,6 +967,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
955
967
  };
956
968
  const ctx = options?.context ?? noopContext;
957
969
  const phaseIn = options?.phase;
970
+ const trackCleanup = options?.trackCleanup;
958
971
  let outputPayload = phaseIn?.state?.payload;
959
972
  let nextPhase;
960
973
  let outputMessage;
@@ -962,6 +975,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
962
975
  on: (eventType, handler) => {
963
976
  assertActive();
964
977
  hooks?.on(eventType, handler);
978
+ trackCleanup?.(() => hooks?.off(eventType, handler));
965
979
  },
966
980
  off: (eventType, handler) => {
967
981
  assertActive();
@@ -978,6 +992,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
978
992
  registerProvider: (config) => {
979
993
  assertActive();
980
994
  options?.registerProvider?.(config);
995
+ trackCleanup?.(() => options?.unregisterProvider?.(config.id));
981
996
  },
982
997
  unregisterProvider: (name) => {
983
998
  assertActive();
@@ -989,7 +1004,17 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
989
1004
  formatJson
990
1005
  },
991
1006
  context: ctx,
992
- events: eventBus ?? { on: () => () => {
1007
+ events: eventBus ? {
1008
+ on: (event, listener) => {
1009
+ const unsubscribe = eventBus.on(event, listener);
1010
+ trackCleanup?.(unsubscribe);
1011
+ return unsubscribe;
1012
+ },
1013
+ off: (event) => eventBus.off(event),
1014
+ emit: (event, ...args) => eventBus.emit(event, ...args),
1015
+ has: (event) => eventBus.has(event),
1016
+ count: (event) => eventBus.count(event)
1017
+ } : { on: () => () => {
993
1018
  }, off: () => {
994
1019
  }, emit: () => {
995
1020
  }, has: () => false, count: () => 0 },
@@ -1015,7 +1040,113 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1015
1040
  import { existsSync, statSync } from "fs";
1016
1041
  import { readdir } from "fs/promises";
1017
1042
  import { dirname as dirname3, join, resolve as resolve3 } from "path";
1043
+
1044
+ // src/harness/definitions.ts
1018
1045
  import { parseModelRef } from "@rowan-agent/models";
1046
+ function assertAgentDefinition(value) {
1047
+ if (!isRecord(value)) throw new TypeError("config.definition is invalid");
1048
+ requiredString(value.name, "definition.name");
1049
+ requiredString(value.description, "definition.description");
1050
+ requiredString(value.content, "definition.content");
1051
+ optionalStringList(value.tools, "definition.tools");
1052
+ optionalStringList(value.skills, "definition.skills");
1053
+ optionalPhaseRegistrySelection(value.phases, "definition.phases");
1054
+ optionalStringList(value.context, "definition.context");
1055
+ if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
1056
+ if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
1057
+ if (value.model !== void 0) {
1058
+ if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
1059
+ throw new TypeError("definition.model must be a valid model reference");
1060
+ }
1061
+ }
1062
+ }
1063
+ function parseAgentDefinition(raw) {
1064
+ const { frontmatter, body } = parseFrontmatter(raw);
1065
+ return normalizeAgentDefinition(frontmatter, body);
1066
+ }
1067
+ function normalizeAgentDefinition(frontmatter, content, options = {}) {
1068
+ const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
1069
+ const description = requiredString(frontmatter.description, "description");
1070
+ const normalizedContent = requiredString(content, "content");
1071
+ const tools = optionalStringList(frontmatter.tools, "tools");
1072
+ const skills = optionalStringList(frontmatter.skills, "skills");
1073
+ const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
1074
+ if (frontmatter.extensions !== void 0) {
1075
+ throw new TypeError("extensions is not supported; Extensions are Runtime-global");
1076
+ }
1077
+ const context = optionalStringList(frontmatter.context, "context");
1078
+ if (frontmatter.entryPhase !== void 0) {
1079
+ throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
1080
+ }
1081
+ const model = optionalModel(frontmatter.model);
1082
+ return {
1083
+ name,
1084
+ description,
1085
+ content: normalizedContent,
1086
+ ...tools ? { tools } : {},
1087
+ ...skills ? { skills } : {},
1088
+ ...phases ? { phases } : {},
1089
+ ...context ? { context } : {},
1090
+ ...model ? { model } : {}
1091
+ };
1092
+ }
1093
+ function requiredString(value, field) {
1094
+ if (typeof value !== "string" || value.trim() === "") {
1095
+ throw new TypeError(`${field} is required`);
1096
+ }
1097
+ return value.trim();
1098
+ }
1099
+ function optionalString(value, field) {
1100
+ if (value === void 0) return void 0;
1101
+ if (typeof value !== "string" || value.trim() === "") {
1102
+ throw new TypeError(`${field} must be a non-empty string`);
1103
+ }
1104
+ return value.trim();
1105
+ }
1106
+ function optionalStringList(value, field) {
1107
+ if (value === void 0) return void 0;
1108
+ if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of strings`);
1109
+ const names = [];
1110
+ const seen = /* @__PURE__ */ new Set();
1111
+ for (const [index, item] of value.entries()) {
1112
+ if (typeof item !== "string" || item.trim() === "") {
1113
+ throw new TypeError(`${field}[${index}] must be a non-empty string`);
1114
+ }
1115
+ const name = item.trim();
1116
+ if (seen.has(name)) continue;
1117
+ seen.add(name);
1118
+ names.push(name);
1119
+ }
1120
+ return names;
1121
+ }
1122
+ function optionalPhaseRegistrySelection(value, field) {
1123
+ if (value === void 0) return void 0;
1124
+ if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
1125
+ if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
1126
+ if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
1127
+ const entryPhaseId = value.entryPhaseId;
1128
+ if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
1129
+ throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
1130
+ }
1131
+ const phaseIds = optionalStringList(value.phaseIds, `${field}.phaseIds`);
1132
+ if (!phaseIds) throw new TypeError(`${field}.phaseIds is required`);
1133
+ return {
1134
+ entryPhaseId: entryPhaseId === null ? null : entryPhaseId.trim(),
1135
+ phaseIds
1136
+ };
1137
+ }
1138
+ function optionalModel(value) {
1139
+ const source = optionalString(value, "model");
1140
+ if (!source) return void 0;
1141
+ const model = parseModelRef(source);
1142
+ if (!model || model.provider.trim() === "" || model.id.trim() === "") {
1143
+ throw new TypeError("model must be a valid model reference");
1144
+ }
1145
+ return model;
1146
+ }
1147
+ function isRecord(value) {
1148
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1149
+ }
1019
1150
 
1020
1151
  // src/harness/resource-validation.ts
1021
1152
  var MAX_RESOURCE_ID_LENGTH = 64;
@@ -1113,8 +1244,16 @@ async function loadPhase(targetPath) {
1113
1244
  const { frontmatter, body } = loaded;
1114
1245
  const metadata = frontmatter;
1115
1246
  const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1116
- const name = frontmatterName || directoryName;
1117
- const description = validateDescription(metadata.description);
1247
+ let definition;
1248
+ try {
1249
+ definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
1250
+ } catch (error) {
1251
+ const message = error instanceof Error ? error.message : String(error);
1252
+ warnResourceDiagnostics("phase", resolved, [message]);
1253
+ throw new ResourceMetadataError("invalid_metadata", message);
1254
+ }
1255
+ const name = definition.name;
1256
+ const description = validateDescription(definition.description);
1118
1257
  const diagnostics = validateResourceId(directoryName);
1119
1258
  if (frontmatterName && frontmatterName !== directoryName) {
1120
1259
  diagnostics.push(...validateResourceName(frontmatterName, directoryName));
@@ -1123,22 +1262,19 @@ async function loadPhase(targetPath) {
1123
1262
  diagnostics.push(...validateSkillReferences(metadata.skills));
1124
1263
  diagnostics.push(...validatePhaseTarget(metadata.target));
1125
1264
  warnResourceDiagnostics("phase", resolved, diagnostics);
1126
- if (description.missing) {
1127
- throw new ResourceMetadataError("invalid_metadata", "description is required");
1128
- }
1129
1265
  const baseDir = dirname3(resolved);
1130
1266
  const phase = {
1131
1267
  name,
1132
1268
  description: description.description,
1133
- tools: metadata.tools,
1134
- skills: metadata.skills,
1269
+ tools: definition.tools ? [...definition.tools] : void 0,
1270
+ skills: definition.skills ? [...definition.skills] : void 0,
1135
1271
  target: metadata.target,
1136
1272
  input: metadata.input,
1137
1273
  isolated: metadata.isolated,
1138
1274
  filePath: resolved,
1139
1275
  baseDir,
1140
- content: body,
1141
- model: parseModelRef(metadata.model)
1276
+ content: definition.content,
1277
+ model: definition.model
1142
1278
  };
1143
1279
  const codePath = await discoverPhaseCode(baseDir);
1144
1280
  if (codePath) {
@@ -1236,6 +1372,31 @@ function createDefaultPhase() {
1236
1372
  };
1237
1373
  }
1238
1374
 
1375
+ // src/harness/resource-selection.ts
1376
+ function selectNamedResources(candidates, names, kind) {
1377
+ const byName = /* @__PURE__ */ new Map();
1378
+ for (const candidate of candidates) {
1379
+ if (byName.has(candidate.name)) {
1380
+ throw new TypeError(`Duplicate ${kind} candidate "${candidate.name}".`);
1381
+ }
1382
+ byName.set(candidate.name, candidate);
1383
+ }
1384
+ if (names === void 0) return [...candidates];
1385
+ const selected = [];
1386
+ const seen = /* @__PURE__ */ new Set();
1387
+ for (const name of names) {
1388
+ if (seen.has(name)) continue;
1389
+ seen.add(name);
1390
+ const candidate = byName.get(name);
1391
+ if (!candidate) {
1392
+ console.warn(`${kind} "${name}" is not available and will be skipped.`);
1393
+ continue;
1394
+ }
1395
+ selected.push(candidate);
1396
+ }
1397
+ return selected;
1398
+ }
1399
+
1239
1400
  // src/harness/context/system-prompt.ts
1240
1401
  function buildSystemPrompt(options) {
1241
1402
  const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills } = options;
@@ -1578,7 +1739,7 @@ function createTerminalOutcome(phase, output, transcript) {
1578
1739
  return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
1579
1740
  }
1580
1741
  function buildToolsWithRouting(config, availablePhases) {
1581
- const tools = [...config.context.tools];
1742
+ const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
1582
1743
  if (availablePhases.length > 0) {
1583
1744
  tools.push(createRouteTool(availablePhases));
1584
1745
  }
@@ -1763,8 +1924,8 @@ async function runPhaseLoop(config, state, registry) {
1763
1924
  const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1764
1925
  const toolExecutionManager = createToolExecutionManager();
1765
1926
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1766
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
1767
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
1927
+ const phaseTools = selectPhaseTools(allTools, phase.tools);
1928
+ const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
1768
1929
  let phaseContext = {
1769
1930
  systemPrompt: config.context.systemPrompt,
1770
1931
  messages: messageManager.visible(),
@@ -2043,7 +2204,7 @@ async function withRetry(fn, options = {}) {
2043
2204
  }
2044
2205
  const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2045
2206
  options.onRetry?.(attempt, error, delayMs);
2046
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
2207
+ await new Promise((resolve7) => setTimeout(resolve7, delayMs));
2047
2208
  if (options.signal?.aborted) {
2048
2209
  throw lastError;
2049
2210
  }
@@ -2254,8 +2415,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2254
2415
  async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2255
2416
  const messages = [...context];
2256
2417
  const allTools = buildToolsWithRouting(config, availablePhases);
2257
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
2258
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
2418
+ const phaseTools = selectPhaseTools(allTools, phase.tools);
2419
+ const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2259
2420
  const systemPrompt = config.context.systemPrompt;
2260
2421
  const phaseContext = {
2261
2422
  systemPrompt,
@@ -2293,6 +2454,11 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2293
2454
  const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2294
2455
  return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
2295
2456
  }
2457
+ function selectPhaseTools(candidates, names) {
2458
+ const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
2459
+ const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
2460
+ return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
2461
+ }
2296
2462
  async function waitForBackgroundTasks(backgroundTasks) {
2297
2463
  const entries = Array.from(backgroundTasks.entries());
2298
2464
  const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
@@ -2523,29 +2689,32 @@ function createMetrics() {
2523
2689
  startedAtMs: Date.now()
2524
2690
  };
2525
2691
  }
2526
- function isRecord(value) {
2692
+ function isRecord2(value) {
2527
2693
  return value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
2528
2694
  }
2529
2695
  function isCheckpointData(value) {
2530
- if (!isRecord(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2696
+ if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2531
2697
  if (!isMetrics(value.metrics)) return false;
2532
2698
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
2533
2699
  return true;
2534
2700
  }
2535
2701
  function isMetrics(value) {
2536
- if (!isRecord(value)) return false;
2537
- return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
2702
+ if (!isRecord2(value)) return false;
2703
+ return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
2538
2704
  }
2539
2705
  function isContinuation(value) {
2540
- if (!isRecord(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
2706
+ if (!isRecord2(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
2541
2707
  if (value.previousPayload !== void 0 && !isJsonValue(value.previousPayload)) return false;
2542
- return value.previousResults.every((result) => isRecord(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
2708
+ return value.previousResults.every((result) => isRecord2(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
2543
2709
  }
2544
2710
 
2545
2711
  // src/runtime/contracts.ts
2712
+ function isAgentConfiguration(config) {
2713
+ return !("resources" in config);
2714
+ }
2546
2715
  var METADATA_LIMIT = 64 * 1024;
2547
2716
  var IDENTITY_LIMIT = 256;
2548
- function isRecord2(value) {
2717
+ function isRecord3(value) {
2549
2718
  if (value === null || typeof value !== "object") return false;
2550
2719
  const prototype = Object.getPrototypeOf(value);
2551
2720
  return prototype === Object.prototype || prototype === null;
@@ -2554,7 +2723,7 @@ function hasOnlyKeys(value, allowed) {
2554
2723
  return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.includes(key));
2555
2724
  }
2556
2725
  function isMetadata(value) {
2557
- if (!isRecord2(value) || !isJsonValue(value)) return false;
2726
+ if (!isRecord3(value) || !isJsonValue(value)) return false;
2558
2727
  try {
2559
2728
  assertUtf8ByteLimit(canonicalJson(value), METADATA_LIMIT, "metadata");
2560
2729
  return true;
@@ -2566,23 +2735,23 @@ function assertMetadata(value, argument) {
2566
2735
  if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
2567
2736
  }
2568
2737
  function isText(value) {
2569
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
2738
+ return isRecord3(value) && value.type === "text" && typeof value.text === "string";
2570
2739
  }
2571
2740
  function isImage(value) {
2572
- return isRecord2(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
2741
+ return isRecord3(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
2573
2742
  }
2574
2743
  function isUserContent(value) {
2575
2744
  return typeof value === "string" || Array.isArray(value) && value.every((part) => isText(part) || isImage(part));
2576
2745
  }
2577
2746
  function normalizeUserInput(input) {
2578
2747
  const normalized = typeof input === "string" ? { content: input } : input;
2579
- if (!isRecord2(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
2748
+ if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
2580
2749
  if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
2581
2750
  assertJsonValue(normalized, "input");
2582
2751
  return normalized;
2583
2752
  }
2584
2753
  function isToolResult(value) {
2585
- if (!isRecord2(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
2754
+ if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
2586
2755
  const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
2587
2756
  if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
2588
2757
  return value.ok || typeof value.error === "string";
@@ -2590,8 +2759,44 @@ function isToolResult(value) {
2590
2759
  function assertAgentConfig(config) {
2591
2760
  if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2592
2761
  assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2593
- if (!config.context || typeof config.context.systemPrompt !== "string") throw new TypeError("config.context is invalid");
2594
- for (const tool of config.context.tools) projectToolDefinition(tool);
2762
+ assertAgentDefinition(config.definition);
2763
+ if (!config.resources || !Array.isArray(config.resources.tools) || !Array.isArray(config.resources.skills)) {
2764
+ throw new TypeError("config.resources is invalid");
2765
+ }
2766
+ for (const tool of config.resources.tools) projectToolDefinition(tool);
2767
+ const names = /* @__PURE__ */ new Set();
2768
+ for (const context of config.resources.contexts ?? []) {
2769
+ if (typeof context.name !== "string" || context.name.trim() === "") {
2770
+ throw new TypeError("Context candidate name must be non-empty");
2771
+ }
2772
+ if (names.has(context.name)) throw new TypeError(`Duplicate Context candidate "${context.name}".`);
2773
+ names.add(context.name);
2774
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2775
+ }
2776
+ }
2777
+ function assertAgentConfigRequest(config) {
2778
+ if (!isAgentConfiguration(config)) {
2779
+ assertAgentConfig(config);
2780
+ return;
2781
+ }
2782
+ if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2783
+ assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2784
+ if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
2785
+ throw new TypeError("config.definition.name must be non-empty");
2786
+ }
2787
+ const view = config.resourceView;
2788
+ if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
2789
+ throw new TypeError("config.resourceView is invalid");
2790
+ }
2791
+ for (const [kind, ids] of Object.entries(view)) {
2792
+ for (const sourceId of ids) {
2793
+ if (typeof sourceId !== "string" || sourceId.trim() === "") throw new TypeError(`config.resourceView.${kind} contains an invalid Source ID`);
2794
+ }
2795
+ }
2796
+ for (const context of config.contexts ?? []) {
2797
+ if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
2798
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2799
+ }
2595
2800
  }
2596
2801
  function assertToolExecutionResult(value) {
2597
2802
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -2599,7 +2804,7 @@ function assertToolExecutionResult(value) {
2599
2804
  function projectToolDefinition(tool) {
2600
2805
  if (typeof tool.name !== "string" || tool.name.length === 0 || typeof tool.description !== "string") throw new TypeError("Tool definition is invalid");
2601
2806
  assertJsonValue(tool.parameters, "tool.parameters");
2602
- if (!isRecord2(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
2807
+ if (!isRecord3(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
2603
2808
  return { name: tool.name, description: tool.description, parameters: JSON.parse(canonicalJson(tool.parameters)) };
2604
2809
  }
2605
2810
 
@@ -2666,7 +2871,7 @@ function brandConfigToken(raw) {
2666
2871
  return raw;
2667
2872
  }
2668
2873
  function validateConfigResolution(agentId, value) {
2669
- if (!isRecord3(value) || typeof value.kind !== "string") {
2874
+ if (!isRecord4(value) || typeof value.kind !== "string") {
2670
2875
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2671
2876
  }
2672
2877
  if (value.kind === "deferred") {
@@ -2679,18 +2884,97 @@ function validateConfigResolution(agentId, value) {
2679
2884
  };
2680
2885
  }
2681
2886
  if (value.kind === "unavailable" && typeof value.reason === "string") return { kind: "unavailable", reason: value.reason };
2682
- if (value.kind === "available" && isRecord3(value.config) && typeof value.config.identity === "string") {
2887
+ if (value.kind === "available" && isRecord4(value.config) && typeof value.config.identity === "string") {
2683
2888
  return { kind: "available", config: value.config };
2684
2889
  }
2685
2890
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2686
2891
  }
2687
2892
  function snapshotConfig(config) {
2688
- const context = Object.freeze({
2689
- ...config.context,
2690
- tools: Object.freeze([...config.context.tools]),
2691
- skills: Object.freeze([...config.context.skills])
2893
+ if (isAgentConfiguration(config)) return snapshotConfiguration(config);
2894
+ const definition = Object.freeze({
2895
+ ...config.definition,
2896
+ ...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
2897
+ ...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
2898
+ ...config.definition.phases ? {
2899
+ phases: Object.freeze({
2900
+ entryPhaseId: config.definition.phases.entryPhaseId,
2901
+ phaseIds: Object.freeze([...config.definition.phases.phaseIds])
2902
+ })
2903
+ } : {},
2904
+ ...config.definition.context ? { context: Object.freeze([...config.definition.context]) } : {}
2905
+ });
2906
+ const resources = Object.freeze({
2907
+ ...config.resources,
2908
+ tools: Object.freeze([...config.resources.tools]),
2909
+ skills: Object.freeze([...config.resources.skills]),
2910
+ ...config.resources.resourceView ? {
2911
+ resourceView: Object.freeze({
2912
+ agents: Object.freeze([...config.resources.resourceView.agents]),
2913
+ tools: Object.freeze([...config.resources.resourceView.tools]),
2914
+ skills: Object.freeze([...config.resources.resourceView.skills]),
2915
+ phases: Object.freeze([...config.resources.resourceView.phases])
2916
+ })
2917
+ } : {},
2918
+ ...config.resources.resourceRefs ? { resourceRefs: Object.freeze(config.resources.resourceRefs.map((ref) => Object.freeze({ ...ref }))) } : {},
2919
+ ...config.resources.resourceRevisions ? {
2920
+ resourceRevisions: Object.freeze(Object.fromEntries(
2921
+ Object.entries(config.resources.resourceRevisions).map(([kind, revisions]) => [kind, Object.freeze([...revisions])])
2922
+ ))
2923
+ } : {},
2924
+ ...config.resources.contexts ? {
2925
+ contexts: Object.freeze(config.resources.contexts.map((context) => Object.freeze({
2926
+ name: context.name,
2927
+ value: snapshotJsonValue(context.value)
2928
+ })))
2929
+ } : {}
2930
+ });
2931
+ return Object.freeze({ ...config, definition, resources });
2932
+ }
2933
+ function snapshotConfiguration(config) {
2934
+ const layer = config.definition.layer;
2935
+ const definition = Object.freeze({
2936
+ name: config.definition.name,
2937
+ ...layer === void 0 ? {} : {
2938
+ layer: Object.freeze({
2939
+ ...layer,
2940
+ ...layer.tools ? { tools: Object.freeze([...layer.tools]) } : {},
2941
+ ...layer.skills ? { skills: Object.freeze([...layer.skills]) } : {},
2942
+ ...layer.phases ? {
2943
+ phases: Object.freeze({
2944
+ entryPhaseId: layer.phases.entryPhaseId,
2945
+ phaseIds: Object.freeze([...layer.phases.phaseIds])
2946
+ })
2947
+ } : {}
2948
+ })
2949
+ }
2950
+ });
2951
+ const resourceView = Object.freeze({
2952
+ agents: Object.freeze([...config.resourceView.agents]),
2953
+ tools: Object.freeze([...config.resourceView.tools]),
2954
+ skills: Object.freeze([...config.resourceView.skills]),
2955
+ phases: Object.freeze([...config.resourceView.phases])
2956
+ });
2957
+ return Object.freeze({
2958
+ ...config,
2959
+ definition,
2960
+ resourceView,
2961
+ ...config.contexts ? {
2962
+ contexts: Object.freeze(config.contexts.map((context) => Object.freeze({
2963
+ name: context.name,
2964
+ value: snapshotJsonValue(context.value)
2965
+ })))
2966
+ } : {}
2692
2967
  });
2693
- return Object.freeze({ ...config, context });
2968
+ }
2969
+ function snapshotJsonValue(value) {
2970
+ return deepFreeze(JSON.parse(canonicalJson(value)));
2971
+ }
2972
+ function deepFreeze(value) {
2973
+ if (value && typeof value === "object") {
2974
+ for (const child of Object.values(value)) deepFreeze(child);
2975
+ Object.freeze(value);
2976
+ }
2977
+ return value;
2694
2978
  }
2695
2979
  function assertConfigIdentity(identity) {
2696
2980
  if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
@@ -2705,7 +2989,7 @@ function throwIfAborted(signal) {
2705
2989
  error.name = "AbortError";
2706
2990
  throw error;
2707
2991
  }
2708
- function isRecord3(value) {
2992
+ function isRecord4(value) {
2709
2993
  return value !== null && typeof value === "object";
2710
2994
  }
2711
2995
 
@@ -2736,7 +3020,7 @@ var ConfigCommandService = class {
2736
3020
  configs;
2737
3021
  storeIncarnation;
2738
3022
  async createAgent(input) {
2739
- assertAgentConfig(input.config);
3023
+ assertAgentConfigRequest(input.config);
2740
3024
  assertIdentity(input.config.identity);
2741
3025
  const reserved = await this.store.reserveAgent({
2742
3026
  idempotencyKey: input.idempotencyKey,
@@ -2758,7 +3042,7 @@ var ConfigCommandService = class {
2758
3042
  return reserved.id;
2759
3043
  }
2760
3044
  async updateAgentConfig(input) {
2761
- assertAgentConfig(input.config);
3045
+ assertAgentConfigRequest(input.config);
2762
3046
  assertIdentity(input.config.identity);
2763
3047
  const agent = await this.findAgent(input.agentId);
2764
3048
  const operationId = this.operationId("update_agent_config", input.agentId, input.idempotencyKey);
@@ -2785,6 +3069,23 @@ var ConfigCommandService = class {
2785
3069
  const resolution = validateConfigResolution(input.agent.id, result);
2786
3070
  return resolution;
2787
3071
  }
3072
+ /** Persist one resolved Configuration Snapshot under a Run-specific token.
3073
+ * The durable Run pins this token at claim time, so later Source replacement
3074
+ * cannot mutate an active or input-waiting Run. */
3075
+ async storeSnapshot(input) {
3076
+ assertAgentConfigRequest(input.config);
3077
+ const result = await this.put({
3078
+ agentId: input.agent.id,
3079
+ agentMetadata: input.agent.metadata,
3080
+ config: input.config,
3081
+ operationId: input.operationId,
3082
+ signal: input.signal
3083
+ });
3084
+ if (result.kind === "identity_conflict") {
3085
+ throw idempotencyConflict("update_agent_config", input.operationId);
3086
+ }
3087
+ return brandConfigToken(result.token);
3088
+ }
2788
3089
  async findAgent(agentId) {
2789
3090
  const agent = (await this.store.listAgents()).find((candidate) => candidate.id === agentId);
2790
3091
  if (!agent) throw new RuntimeError("agent_not_found", { agentId });
@@ -3018,326 +3319,590 @@ function jsonText(value) {
3018
3319
  return typeof value === "string" ? value : JSON.stringify(value) ?? "null";
3019
3320
  }
3020
3321
 
3021
- // src/extensions/hooks.ts
3022
- var HookError = class extends Error {
3023
- constructor(eventType, message, cause) {
3024
- super(message, cause === void 0 ? void 0 : { cause });
3025
- this.eventType = eventType;
3026
- this.name = "HookError";
3027
- }
3028
- eventType;
3029
- };
3030
- var HooksManager = class {
3031
- handlers = /* @__PURE__ */ new Map();
3032
- on(eventType, handler) {
3033
- const handlers = this.handlers.get(eventType) ?? [];
3034
- handlers.push(handler);
3035
- this.handlers.set(eventType, handlers);
3036
- }
3037
- off(eventType, handler) {
3038
- const handlers = this.handlers.get(eventType);
3039
- if (!handlers) return;
3040
- const index = handlers.indexOf(handler);
3041
- if (index >= 0) handlers.splice(index, 1);
3042
- }
3043
- async emit(eventType, event) {
3044
- const handlers = this.handlers.get(eventType);
3045
- if (!handlers?.length) return;
3046
- const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
3047
- const failure = results.find((result) => result.status === "rejected");
3048
- if (failure) {
3049
- throw new HookError(
3050
- eventType,
3051
- `${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
3052
- failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
3053
- );
3054
- }
3322
+ // src/runtime/extensions.ts
3323
+ function assembleRegisteredExtensions(config, runner) {
3324
+ const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3325
+ const tools = [...config.resources.tools];
3326
+ const names = new Set(tools.map((tool) => tool.name));
3327
+ for (const tool of extensionTools) {
3328
+ if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
3329
+ names.add(tool.name);
3330
+ tools.push(tool);
3055
3331
  }
3056
- async emitFirst(eventType, event) {
3057
- const handlers = this.handlers.get(eventType);
3058
- if (!handlers?.length) return void 0;
3059
- for (const handler of handlers) {
3060
- try {
3061
- const result = await handler(event);
3062
- if (result !== void 0) return result;
3063
- } catch (error) {
3064
- throw new HookError(
3065
- eventType,
3066
- `Handler failed for "${eventType}"`,
3067
- error instanceof Error ? error : new Error(String(error))
3068
- );
3069
- }
3332
+ const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3333
+ const phases = mergePhases(config.resources.phases, extensionPhases);
3334
+ const context = resolveDefinitionContext(config, { tools, phases });
3335
+ return {
3336
+ context,
3337
+ beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3338
+ afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3339
+ beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
3340
+ beforeToolCall: async (input) => {
3341
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3342
+ const decision = await runner.emitBeforeToolCall(tool, input.args);
3343
+ return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
3344
+ },
3345
+ afterToolCall: async (input) => {
3346
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3347
+ const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3348
+ return fromLoopResult(result);
3349
+ },
3350
+ setContext: (context2) => {
3351
+ runner.currentContext = context2;
3070
3352
  }
3071
- return void 0;
3353
+ };
3354
+ }
3355
+ function resolveDefinitionContext(config, assembled = {}) {
3356
+ const tools = selectNamedResources(
3357
+ assembled.tools ?? config.resources.tools,
3358
+ config.definition.tools,
3359
+ "Tool"
3360
+ );
3361
+ const skills = selectNamedResources(
3362
+ config.resources.skills,
3363
+ config.definition.skills,
3364
+ "Skill"
3365
+ );
3366
+ const contexts = selectNamedResources(
3367
+ config.resources.contexts ?? [],
3368
+ config.definition.context,
3369
+ "Context"
3370
+ );
3371
+ const candidateRegistry = assembled.phases ?? config.resources.phases;
3372
+ const selectedPhases = selectNamedResources(
3373
+ [...candidateRegistry?.phases.values() ?? []],
3374
+ config.definition.phases?.phaseIds,
3375
+ "Phase"
3376
+ );
3377
+ const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
3378
+ const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
3379
+ const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
3380
+ if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
3381
+ console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
3072
3382
  }
3073
- };
3074
-
3075
- // src/extensions/runner.ts
3076
- import { execFile } from "child_process";
3077
- import {
3078
- registerModel,
3079
- unregisterProviderModels,
3080
- registerApiProvider
3081
- } from "@rowan-agent/models";
3082
-
3083
- // src/extensions/types.ts
3084
- function createSourceInfo(extensionPath, options = {}) {
3085
- const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3086
- const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3087
3383
  return {
3088
- source,
3089
- baseDir: options.baseDir,
3090
- displayName
3384
+ systemPrompt: [config.definition.content, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
3385
+ tools,
3386
+ skills,
3387
+ phases: { phases, entryPhaseId }
3091
3388
  };
3092
3389
  }
3093
- function createExtensionRuntime() {
3094
- const state = {};
3095
- const assertActive = () => {
3096
- if (state.staleMessage) {
3097
- throw new Error(state.staleMessage);
3098
- }
3390
+ function mergePhases(base, extension) {
3391
+ if (!base && extension.phases.size === 0) return void 0;
3392
+ const phases = new Map(base?.phases ?? []);
3393
+ for (const [name, phase] of extension.phases) {
3394
+ if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
3395
+ phases.set(name, phase);
3396
+ }
3397
+ return {
3398
+ phases,
3399
+ entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
3099
3400
  };
3401
+ }
3402
+ function adaptExtensionTool(input) {
3403
+ const definition = input.definition;
3100
3404
  return {
3101
- assertActive,
3102
- invalidate: (message) => {
3103
- state.staleMessage ??= message ?? "This extension context is stale after runtime replacement or reload. Do not use a captured extension API after the runner has been replaced.";
3405
+ name: definition.name,
3406
+ description: definition.description,
3407
+ parameters: definition.parameters,
3408
+ execute: async (args, _context, signal) => {
3409
+ const result = await definition.execute(args, signal);
3410
+ const content = JSON.parse(JSON.stringify(result.content));
3411
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3104
3412
  }
3105
3413
  };
3106
3414
  }
3107
-
3108
- // src/extensions/runner.ts
3109
- import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3110
-
3111
- // src/extensions/event-bus.ts
3112
- function createEventBus() {
3113
- const listeners = /* @__PURE__ */ new Map();
3415
+ function toLoopResult(result, toolCallId, toolName) {
3114
3416
  return {
3115
- on(event, listener) {
3116
- const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3117
- set.add(listener);
3118
- listeners.set(event, set);
3119
- return () => {
3120
- set.delete(listener);
3121
- if (set.size === 0) listeners.delete(event);
3122
- };
3123
- },
3124
- emit(event, ...args) {
3125
- const set = listeners.get(event);
3126
- if (!set) return;
3127
- for (const listener of set) {
3128
- try {
3129
- listener(...args);
3130
- } catch (err) {
3131
- console.error(`[event-bus] Listener error for "${event}":`, err);
3132
- }
3133
- }
3134
- },
3135
- off(event) {
3136
- if (event) {
3137
- listeners.delete(event);
3138
- } else {
3139
- listeners.clear();
3140
- }
3141
- },
3142
- has(event) {
3143
- const set = listeners.get(event);
3144
- return set !== void 0 && set.size > 0;
3145
- },
3146
- count(event) {
3147
- return listeners.get(event)?.size ?? 0;
3148
- }
3417
+ toolCallId,
3418
+ toolName,
3419
+ ok: result.ok,
3420
+ content: result.content,
3421
+ ...!result.ok ? { error: result.error } : {}
3149
3422
  };
3150
3423
  }
3424
+ function fromLoopResult(result) {
3425
+ return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
3426
+ }
3151
3427
 
3152
- // src/extensions/runner.ts
3153
- async function execCommand(command, args, cwd, options) {
3154
- return new Promise((resolve6, reject) => {
3155
- const child = execFile(
3156
- command,
3157
- args,
3158
- {
3159
- cwd: options?.cwd ?? cwd,
3160
- env: options?.env ? { ...process.env, ...options.env } : void 0,
3161
- timeout: options?.timeout,
3162
- maxBuffer: 10 * 1024 * 1024
3163
- },
3164
- (error, stdout, stderr) => {
3165
- if (error && error.killed && options?.signal?.aborted) {
3166
- reject(new Error("Command was aborted"));
3167
- return;
3168
- }
3169
- resolve6({
3170
- exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3171
- stdout: stdout ?? "",
3172
- stderr: stderr ?? ""
3173
- });
3174
- }
3175
- );
3176
- if (options?.signal) {
3177
- options.signal.addEventListener(
3178
- "abort",
3179
- () => {
3180
- child.kill("SIGTERM");
3181
- },
3182
- { once: true }
3183
- );
3184
- }
3428
+ // src/runtime/transient-run-events.ts
3429
+ var MAX_BUFFERED_EVENTS = 128;
3430
+ var MAX_BUFFERED_TEXT = 64 * 1024;
3431
+ function deferred() {
3432
+ let resolve7;
3433
+ const promise = new Promise((done) => {
3434
+ resolve7 = done;
3185
3435
  });
3436
+ return { promise, resolve: resolve7 };
3186
3437
  }
3187
- function applyProviderRegistration(config) {
3188
- if (config.streamSimple) {
3189
- registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
3438
+ var TransientRunEventSubscription = class {
3439
+ queue = [];
3440
+ wake = deferred();
3441
+ version = 0;
3442
+ stopped = false;
3443
+ push(event) {
3444
+ if (this.stopped) return;
3445
+ const previous = this.queue.at(-1);
3446
+ if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3447
+ this.queue[this.queue.length - 1] = {
3448
+ ...previous,
3449
+ text: previous.text + event.text
3450
+ };
3451
+ } else {
3452
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3453
+ this.queue.push(event);
3454
+ }
3455
+ this.trimText();
3456
+ this.notify();
3190
3457
  }
3191
- for (const modelConfig of config.models) {
3192
- registerModel({
3193
- id: modelConfig.id,
3194
- name: modelConfig.name,
3195
- protocol: config.protocol,
3196
- provider: config.id,
3197
- baseUrl: config.baseUrl,
3198
- apiKey: config.apiKey,
3199
- reasoning: modelConfig.reasoning,
3200
- input: modelConfig.input,
3201
- cost: modelConfig.cost,
3202
- contextWindow: modelConfig.contextWindow,
3203
- maxTokens: modelConfig.maxTokens,
3204
- ...config.headers ? { headers: config.headers } : {},
3205
- ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
3206
- ...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
3207
- ...config.retryDelayMs !== void 0 ? { retryDelayMs: config.retryDelayMs } : {}
3208
- });
3458
+ shift() {
3459
+ return this.queue.shift();
3209
3460
  }
3210
- }
3211
- function applyProviderUnregistration(name) {
3212
- unregisterProviderModels(name);
3213
- }
3214
- var ExtensionRunner = class {
3215
- hooks;
3216
- runtime;
3217
- events;
3218
- cwd;
3219
- abortController = new AbortController();
3220
- _idle = true;
3221
- // Per-extension tracking
3222
- extensions = [];
3223
- // Phase management
3224
- phases = /* @__PURE__ */ new Map();
3225
- _phaseCache = null;
3226
- // Provider management
3227
- pendingProviders = [];
3228
- bound = false;
3229
- // Error listeners
3230
- errorListeners = /* @__PURE__ */ new Set();
3231
- /** Current agent context — set by the agent before each phase */
3232
- currentContext;
3233
- constructor(options) {
3234
- this.hooks = new HooksManager();
3235
- this.runtime = createExtensionRuntime();
3236
- this.events = createEventBus();
3237
- this.cwd = options?.cwd ?? process.cwd();
3461
+ clear(executionId) {
3462
+ if (executionId === void 0) this.queue.length = 0;
3463
+ else {
3464
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3465
+ if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
3466
+ }
3467
+ }
3468
+ this.notify();
3238
3469
  }
3239
- /** Whether the agent is currently idle (not streaming). */
3240
- get isIdle() {
3241
- return this._idle;
3470
+ clearMessage(messageId) {
3471
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3472
+ const event = this.queue[index];
3473
+ if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
3474
+ }
3475
+ this.notify();
3242
3476
  }
3243
- /** Set idle state — called by the agent loop. */
3244
- setIdle(idle) {
3245
- this._idle = idle;
3477
+ clearTool(toolCallId) {
3478
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3479
+ const event = this.queue[index];
3480
+ if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
3481
+ }
3482
+ this.notify();
3246
3483
  }
3247
- /** Abort signal for the current runner instance. */
3248
- get signal() {
3249
- return this.abortController.signal;
3484
+ checkpoint() {
3485
+ return this.version;
3250
3486
  }
3251
- /** Abort the current runner operation. */
3252
- abort() {
3253
- this.abortController.abort();
3487
+ changed(since) {
3488
+ if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3489
+ return this.wake.promise;
3254
3490
  }
3255
- // ---------------------------------------------------------------------------
3256
- // Error handling
3257
- // ---------------------------------------------------------------------------
3258
- /**
3259
- * Register an error listener.
3260
- * Returns an unsubscribe function.
3261
- */
3262
- onError(listener) {
3263
- this.errorListeners.add(listener);
3264
- return () => this.errorListeners.delete(listener);
3491
+ stop() {
3492
+ if (this.stopped) return;
3493
+ this.stopped = true;
3494
+ this.queue.length = 0;
3495
+ this.notify();
3265
3496
  }
3266
- /**
3267
- * Emit a structured extension error to all listeners.
3268
- */
3269
- emitError(error) {
3270
- for (const listener of this.errorListeners) {
3271
- try {
3272
- listener(error);
3273
- } catch (err) {
3274
- console.error("[extension-runner] Error listener failed:", err);
3497
+ notify() {
3498
+ const wake = this.wake;
3499
+ this.wake = deferred();
3500
+ this.version += 1;
3501
+ wake.resolve();
3502
+ }
3503
+ trimText() {
3504
+ let buffered = this.queue.reduce(
3505
+ (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3506
+ 0
3507
+ );
3508
+ while (buffered > MAX_BUFFERED_TEXT) {
3509
+ const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3510
+ if (index < 0) return;
3511
+ const event = this.queue[index];
3512
+ const overflow = buffered - MAX_BUFFERED_TEXT;
3513
+ if (event.text.length <= overflow) {
3514
+ this.queue.splice(index, 1);
3515
+ buffered -= event.text.length;
3516
+ } else {
3517
+ this.queue[index] = {
3518
+ ...event,
3519
+ offset: event.offset + overflow,
3520
+ text: event.text.slice(overflow)
3521
+ };
3522
+ buffered -= overflow;
3275
3523
  }
3276
3524
  }
3277
3525
  }
3278
- // ---------------------------------------------------------------------------
3279
- // Lifecycle: invalidate / assertActive
3280
- // ---------------------------------------------------------------------------
3281
- /**
3282
- * Mark all extension contexts as stale.
3283
- * After calling this, any captured ExtensionAPI or ExtensionContext will throw
3284
- * on use. Used during runtime replacement or reload.
3285
- */
3286
- invalidate(message) {
3287
- this.runtime.invalidate(message);
3526
+ };
3527
+ var TransientRunEventHub = class {
3528
+ subscriptions = /* @__PURE__ */ new Map();
3529
+ subscribe(runId) {
3530
+ const subscription = new TransientRunEventSubscription();
3531
+ const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3532
+ current.add(subscription);
3533
+ this.subscriptions.set(runId, current);
3534
+ return subscription;
3288
3535
  }
3289
- // ---------------------------------------------------------------------------
3290
- // Direct hook API
3291
- // ---------------------------------------------------------------------------
3292
- /**
3293
- * Subscribe to a specific hook event type.
3294
- * Returns an unsubscribe function.
3295
- *
3296
- * @example
3297
- * ```ts
3298
- * const unsub = runner.on("before_tool_call", (event) => {
3299
- * return { allow: false, reason: "Blocked" };
3300
- * });
3301
- * unsub(); // Cancel subscription
3302
- * ```
3303
- */
3304
- on(type, handler) {
3305
- this.hooks.on(type, handler);
3306
- return () => this.hooks.off(type, handler);
3536
+ unsubscribe(runId, subscription) {
3537
+ subscription.stop();
3538
+ const current = this.subscriptions.get(runId);
3539
+ current?.delete(subscription);
3540
+ if (current?.size === 0) this.subscriptions.delete(runId);
3307
3541
  }
3308
- // ---------------------------------------------------------------------------
3309
- // Extension loading
3310
- // ---------------------------------------------------------------------------
3311
- /**
3312
- * Load and initialize extensions.
3313
- * Creates Extension tracking objects and calls each factory with an ExtensionAPI.
3314
- */
3315
- async loadExtensions(extensions) {
3316
- for (const ext of extensions) {
3317
- try {
3318
- const extension = { path: ext.path, tools: /* @__PURE__ */ new Map() };
3319
- const api = this.createExtensionAPI(extension, ext.manifest);
3320
- await ext.factory(api);
3321
- this.extensions.push(extension);
3322
- this._phaseCache = null;
3323
- } catch (error) {
3324
- const message = error instanceof Error ? error.message : String(error);
3325
- this.emitError({
3326
- extensionPath: ext.path,
3327
- event: "load",
3328
- error: message,
3329
- stack: error instanceof Error ? error.stack : void 0
3330
- });
3331
- throw error;
3332
- }
3542
+ publish(event) {
3543
+ for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3544
+ subscription.push(event);
3333
3545
  }
3334
3546
  }
3335
- // ---------------------------------------------------------------------------
3336
- // Tool management
3337
- // ---------------------------------------------------------------------------
3338
- /** Get all registered tools from all extensions. */
3339
- getAllRegisteredTools() {
3340
- const toolsByName = /* @__PURE__ */ new Map();
3547
+ clear(runId, executionId) {
3548
+ for (const subscription of this.subscriptions.get(runId) ?? []) {
3549
+ subscription.clear(executionId);
3550
+ }
3551
+ }
3552
+ clearMessage(runId, messageId) {
3553
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3554
+ }
3555
+ clearTool(runId, toolCallId) {
3556
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
3557
+ }
3558
+ close() {
3559
+ for (const subscriptions of this.subscriptions.values()) {
3560
+ for (const subscription of subscriptions) subscription.stop();
3561
+ }
3562
+ this.subscriptions.clear();
3563
+ }
3564
+ };
3565
+
3566
+ // src/extensions/hooks.ts
3567
+ var HookError = class extends Error {
3568
+ constructor(eventType, message, cause) {
3569
+ super(message, cause === void 0 ? void 0 : { cause });
3570
+ this.eventType = eventType;
3571
+ this.name = "HookError";
3572
+ }
3573
+ eventType;
3574
+ };
3575
+ var HooksManager = class {
3576
+ handlers = /* @__PURE__ */ new Map();
3577
+ on(eventType, handler) {
3578
+ const handlers = this.handlers.get(eventType) ?? [];
3579
+ handlers.push(handler);
3580
+ this.handlers.set(eventType, handlers);
3581
+ }
3582
+ off(eventType, handler) {
3583
+ const handlers = this.handlers.get(eventType);
3584
+ if (!handlers) return;
3585
+ const index = handlers.indexOf(handler);
3586
+ if (index >= 0) handlers.splice(index, 1);
3587
+ }
3588
+ async emit(eventType, event) {
3589
+ const handlers = this.handlers.get(eventType);
3590
+ if (!handlers?.length) return;
3591
+ const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
3592
+ const failure = results.find((result) => result.status === "rejected");
3593
+ if (failure) {
3594
+ throw new HookError(
3595
+ eventType,
3596
+ `${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
3597
+ failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
3598
+ );
3599
+ }
3600
+ }
3601
+ async emitFirst(eventType, event) {
3602
+ const handlers = this.handlers.get(eventType);
3603
+ if (!handlers?.length) return void 0;
3604
+ for (const handler of handlers) {
3605
+ try {
3606
+ const result = await handler(event);
3607
+ if (result !== void 0) return result;
3608
+ } catch (error) {
3609
+ throw new HookError(
3610
+ eventType,
3611
+ `Handler failed for "${eventType}"`,
3612
+ error instanceof Error ? error : new Error(String(error))
3613
+ );
3614
+ }
3615
+ }
3616
+ return void 0;
3617
+ }
3618
+ };
3619
+
3620
+ // src/extensions/runner.ts
3621
+ import { execFile } from "child_process";
3622
+ import {
3623
+ registerModel,
3624
+ unregisterProviderModels,
3625
+ registerApiProvider
3626
+ } from "@rowan-agent/models";
3627
+
3628
+ // src/extensions/types.ts
3629
+ function createSourceInfo(extensionPath, options = {}) {
3630
+ const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3631
+ const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3632
+ return {
3633
+ source,
3634
+ baseDir: options.baseDir,
3635
+ displayName
3636
+ };
3637
+ }
3638
+ function createExtensionRuntime() {
3639
+ const state = {};
3640
+ const assertActive = () => {
3641
+ if (state.staleMessage) {
3642
+ throw new Error(state.staleMessage);
3643
+ }
3644
+ };
3645
+ return {
3646
+ assertActive,
3647
+ invalidate: (message) => {
3648
+ state.staleMessage ??= message ?? "This extension context is stale after runtime replacement or reload. Do not use a captured extension API after the runner has been replaced.";
3649
+ }
3650
+ };
3651
+ }
3652
+
3653
+ // src/extensions/runner.ts
3654
+ import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3655
+
3656
+ // src/extensions/event-bus.ts
3657
+ function createEventBus() {
3658
+ const listeners = /* @__PURE__ */ new Map();
3659
+ return {
3660
+ on(event, listener) {
3661
+ const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3662
+ set.add(listener);
3663
+ listeners.set(event, set);
3664
+ return () => {
3665
+ set.delete(listener);
3666
+ if (set.size === 0) listeners.delete(event);
3667
+ };
3668
+ },
3669
+ emit(event, ...args) {
3670
+ const set = listeners.get(event);
3671
+ if (!set) return;
3672
+ for (const listener of set) {
3673
+ try {
3674
+ listener(...args);
3675
+ } catch (err) {
3676
+ console.error(`[event-bus] Listener error for "${event}":`, err);
3677
+ }
3678
+ }
3679
+ },
3680
+ off(event) {
3681
+ if (event) {
3682
+ listeners.delete(event);
3683
+ } else {
3684
+ listeners.clear();
3685
+ }
3686
+ },
3687
+ has(event) {
3688
+ const set = listeners.get(event);
3689
+ return set !== void 0 && set.size > 0;
3690
+ },
3691
+ count(event) {
3692
+ return listeners.get(event)?.size ?? 0;
3693
+ }
3694
+ };
3695
+ }
3696
+
3697
+ // src/extensions/runner.ts
3698
+ async function execCommand(command, args, cwd, options) {
3699
+ return new Promise((resolve7, reject) => {
3700
+ const child = execFile(
3701
+ command,
3702
+ args,
3703
+ {
3704
+ cwd: options?.cwd ?? cwd,
3705
+ env: options?.env ? { ...process.env, ...options.env } : void 0,
3706
+ timeout: options?.timeout,
3707
+ maxBuffer: 10 * 1024 * 1024
3708
+ },
3709
+ (error, stdout, stderr) => {
3710
+ if (error && error.killed && options?.signal?.aborted) {
3711
+ reject(new Error("Command was aborted"));
3712
+ return;
3713
+ }
3714
+ resolve7({
3715
+ exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3716
+ stdout: stdout ?? "",
3717
+ stderr: stderr ?? ""
3718
+ });
3719
+ }
3720
+ );
3721
+ if (options?.signal) {
3722
+ options.signal.addEventListener(
3723
+ "abort",
3724
+ () => {
3725
+ child.kill("SIGTERM");
3726
+ },
3727
+ { once: true }
3728
+ );
3729
+ }
3730
+ });
3731
+ }
3732
+ function applyProviderRegistration(config) {
3733
+ if (config.streamSimple) {
3734
+ registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
3735
+ }
3736
+ for (const modelConfig of config.models) {
3737
+ registerModel({
3738
+ id: modelConfig.id,
3739
+ name: modelConfig.name,
3740
+ protocol: config.protocol,
3741
+ provider: config.id,
3742
+ baseUrl: config.baseUrl,
3743
+ apiKey: config.apiKey,
3744
+ reasoning: modelConfig.reasoning,
3745
+ input: modelConfig.input,
3746
+ cost: modelConfig.cost,
3747
+ contextWindow: modelConfig.contextWindow,
3748
+ maxTokens: modelConfig.maxTokens,
3749
+ ...config.headers ? { headers: config.headers } : {},
3750
+ ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
3751
+ ...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
3752
+ ...config.retryDelayMs !== void 0 ? { retryDelayMs: config.retryDelayMs } : {}
3753
+ });
3754
+ }
3755
+ }
3756
+ function applyProviderUnregistration(name) {
3757
+ unregisterProviderModels(name);
3758
+ }
3759
+ var ExtensionRunner = class {
3760
+ hooks;
3761
+ runtime;
3762
+ events;
3763
+ cwd;
3764
+ abortController = new AbortController();
3765
+ _idle = true;
3766
+ // Per-extension tracking
3767
+ extensions = [];
3768
+ // Phase management
3769
+ phases = /* @__PURE__ */ new Map();
3770
+ _phaseCache = null;
3771
+ // Provider management
3772
+ pendingProviders = [];
3773
+ bound = false;
3774
+ // Error listeners
3775
+ errorListeners = /* @__PURE__ */ new Set();
3776
+ /** Current agent context — set by the agent before each phase */
3777
+ currentContext;
3778
+ constructor(options) {
3779
+ this.hooks = new HooksManager();
3780
+ this.runtime = createExtensionRuntime();
3781
+ this.events = createEventBus();
3782
+ this.cwd = options?.cwd ?? process.cwd();
3783
+ }
3784
+ /** Whether the agent is currently idle (not streaming). */
3785
+ get isIdle() {
3786
+ return this._idle;
3787
+ }
3788
+ /** Set idle state — called by the agent loop. */
3789
+ setIdle(idle) {
3790
+ this._idle = idle;
3791
+ }
3792
+ /** Abort signal for the current runner instance. */
3793
+ get signal() {
3794
+ return this.abortController.signal;
3795
+ }
3796
+ /** Abort the current runner operation. */
3797
+ abort() {
3798
+ this.abortController.abort();
3799
+ }
3800
+ // ---------------------------------------------------------------------------
3801
+ // Error handling
3802
+ // ---------------------------------------------------------------------------
3803
+ /**
3804
+ * Register an error listener.
3805
+ * Returns an unsubscribe function.
3806
+ */
3807
+ onError(listener) {
3808
+ this.errorListeners.add(listener);
3809
+ return () => this.errorListeners.delete(listener);
3810
+ }
3811
+ /**
3812
+ * Emit a structured extension error to all listeners.
3813
+ */
3814
+ emitError(error) {
3815
+ for (const listener of this.errorListeners) {
3816
+ try {
3817
+ listener(error);
3818
+ } catch (err) {
3819
+ console.error("[extension-runner] Error listener failed:", err);
3820
+ }
3821
+ }
3822
+ }
3823
+ // ---------------------------------------------------------------------------
3824
+ // Lifecycle: invalidate / assertActive
3825
+ // ---------------------------------------------------------------------------
3826
+ /**
3827
+ * Mark all extension contexts as stale.
3828
+ * After calling this, any captured ExtensionAPI or ExtensionContext will throw
3829
+ * on use. Used during runtime replacement or reload.
3830
+ */
3831
+ invalidate(message) {
3832
+ this.runtime.invalidate(message);
3833
+ for (const extension of this.extensions) extension.runtime.invalidate(message);
3834
+ }
3835
+ // ---------------------------------------------------------------------------
3836
+ // Direct hook API
3837
+ // ---------------------------------------------------------------------------
3838
+ /**
3839
+ * Subscribe to a specific hook event type.
3840
+ * Returns an unsubscribe function.
3841
+ *
3842
+ * @example
3843
+ * ```ts
3844
+ * const unsub = runner.on("before_tool_call", (event) => {
3845
+ * return { allow: false, reason: "Blocked" };
3846
+ * });
3847
+ * unsub(); // Cancel subscription
3848
+ * ```
3849
+ */
3850
+ on(type, handler) {
3851
+ this.hooks.on(type, handler);
3852
+ return () => this.hooks.off(type, handler);
3853
+ }
3854
+ // ---------------------------------------------------------------------------
3855
+ // Extension loading
3856
+ // ---------------------------------------------------------------------------
3857
+ /**
3858
+ * Load and initialize extensions.
3859
+ * Creates Extension tracking objects and calls each factory with an ExtensionAPI.
3860
+ */
3861
+ async loadExtensions(extensions) {
3862
+ for (const ext of extensions) {
3863
+ const extension = {
3864
+ path: ext.path,
3865
+ tools: /* @__PURE__ */ new Map(),
3866
+ phases: /* @__PURE__ */ new Set(),
3867
+ cleanup: [],
3868
+ runtime: createExtensionRuntime()
3869
+ };
3870
+ try {
3871
+ const api = this.createExtensionAPI(extension, ext.manifest);
3872
+ const disposer = await ext.factory(api);
3873
+ if (typeof disposer === "function") extension.disposer = disposer;
3874
+ this.extensions.push(extension);
3875
+ this._phaseCache = null;
3876
+ } catch (error) {
3877
+ await this.rollbackExtension(extension);
3878
+ const message = error instanceof Error ? error.message : String(error);
3879
+ this.emitError({
3880
+ extensionPath: ext.path,
3881
+ event: "load",
3882
+ error: message,
3883
+ stack: error instanceof Error ? error.stack : void 0
3884
+ });
3885
+ throw error;
3886
+ }
3887
+ }
3888
+ }
3889
+ /** Dispose all active Extensions in reverse activation order. */
3890
+ async close() {
3891
+ this.abortController.abort();
3892
+ for (const extension of [...this.extensions].reverse()) {
3893
+ await this.disposeExtension(extension, "This Extension Runtime has been closed.");
3894
+ }
3895
+ this.extensions.length = 0;
3896
+ this.phases.clear();
3897
+ this._phaseCache = null;
3898
+ this.runtime.invalidate("This Extension Runtime has been closed.");
3899
+ }
3900
+ // ---------------------------------------------------------------------------
3901
+ // Tool management
3902
+ // ---------------------------------------------------------------------------
3903
+ /** Get all registered tools from all extensions. */
3904
+ getAllRegisteredTools() {
3905
+ const toolsByName = /* @__PURE__ */ new Map();
3341
3906
  for (const ext of this.extensions) {
3342
3907
  for (const tool of ext.tools.values()) {
3343
3908
  if (!toolsByName.has(tool.definition.name)) {
@@ -3345,498 +3910,1112 @@ var ExtensionRunner = class {
3345
3910
  }
3346
3911
  }
3347
3912
  }
3348
- return Array.from(toolsByName.values());
3913
+ return Array.from(toolsByName.values());
3914
+ }
3915
+ /**
3916
+ * Get a tool definition by name. Returns undefined if not found.
3917
+ */
3918
+ getToolDefinition(toolName) {
3919
+ for (const ext of this.extensions) {
3920
+ const tool = ext.tools.get(toolName);
3921
+ if (tool) return tool.definition;
3922
+ }
3923
+ return void 0;
3924
+ }
3925
+ // ---------------------------------------------------------------------------
3926
+ // Phase management
3927
+ // ---------------------------------------------------------------------------
3928
+ getPhase(name) {
3929
+ const reg = this.getRegisteredPhase(name);
3930
+ if (!reg) return void 0;
3931
+ return this.adaptToPhase(reg);
3932
+ }
3933
+ getPhases() {
3934
+ return [...this.collectRegisteredPhases().values()].map(
3935
+ (p) => this.adaptToPhase(p)
3936
+ );
3937
+ }
3938
+ createPhaseRegistry(input = {}) {
3939
+ const registered = this.collectRegisteredPhases();
3940
+ const phases = /* @__PURE__ */ new Map();
3941
+ for (const [name, reg] of registered) {
3942
+ phases.set(name, this.adaptToPhase(reg));
3943
+ }
3944
+ const entryPhaseId = input.entryPhaseId ?? null;
3945
+ return { phases, entryPhaseId };
3946
+ }
3947
+ /** Adapt an extension RegisteredPhase to the core Phase type. */
3948
+ adaptToPhase(reg) {
3949
+ const def = reg.definition;
3950
+ return {
3951
+ name: def.name,
3952
+ description: def.description,
3953
+ tools: def.tools,
3954
+ skills: def.skills,
3955
+ target: def.target,
3956
+ input: def.input,
3957
+ run: def.run,
3958
+ filePath: "",
3959
+ baseDir: "",
3960
+ content: "",
3961
+ ...def.model ? { model: parseModelRef2(def.model) } : {}
3962
+ };
3963
+ }
3964
+ // ---------------------------------------------------------------------------
3965
+ // Lifecycle
3966
+ // ---------------------------------------------------------------------------
3967
+ /**
3968
+ * Bind the runner — flushes pending provider registrations and
3969
+ * replaces runtime stubs with real implementations.
3970
+ */
3971
+ bind() {
3972
+ if (this.bound) return;
3973
+ this.bound = true;
3974
+ this.flushPendingProviders();
3975
+ }
3976
+ // ---------------------------------------------------------------------------
3977
+ // Unified hook emission
3978
+ // ---------------------------------------------------------------------------
3979
+ /**
3980
+ * Generic emit — fire-and-forget for any event type.
3981
+ */
3982
+ async emit(eventType, event) {
3983
+ await this.hooks.emit(eventType, event);
3984
+ }
3985
+ /**
3986
+ * Unified hook emission — returns the first non-undefined result.
3987
+ */
3988
+ async emitHook(type, event) {
3989
+ return this.hooks.emitFirst(type, event);
3990
+ }
3991
+ // ---------------------------------------------------------------------------
3992
+ // Phase hooks (with inline processing)
3993
+ // ---------------------------------------------------------------------------
3994
+ async emitBeforePhase(phaseId, input) {
3995
+ const result = await this.emitHook("before_phase", {
3996
+ type: "before_phase",
3997
+ phaseId,
3998
+ input
3999
+ });
4000
+ return result ?? {};
4001
+ }
4002
+ async emitAfterPhase(phaseId, output) {
4003
+ const result = await this.emitHook("after_phase", {
4004
+ type: "after_phase",
4005
+ phaseId,
4006
+ output
4007
+ });
4008
+ return result ?? {};
4009
+ }
4010
+ async emitBeforePrompt(phaseId, input) {
4011
+ const result = await this.emitHook("before_prompt", {
4012
+ type: "before_prompt",
4013
+ phaseId,
4014
+ input
4015
+ });
4016
+ return result?.input ?? input;
4017
+ }
4018
+ async emitBeforeToolCall(tool, args) {
4019
+ const result = await this.emitHook("before_tool_call", {
4020
+ type: "before_tool_call",
4021
+ tool,
4022
+ args
4023
+ });
4024
+ return result ?? { allow: true };
4025
+ }
4026
+ async emitAfterToolCall(tool, result) {
4027
+ const hookResult = await this.emitHook("after_tool_call", {
4028
+ type: "after_tool_call",
4029
+ tool,
4030
+ result
4031
+ });
4032
+ return hookResult?.result ?? result;
4033
+ }
4034
+ // ---------------------------------------------------------------------------
4035
+ // Internal helpers
4036
+ // ---------------------------------------------------------------------------
4037
+ /**
4038
+ * Create an ExtensionAPI for a specific extension.
4039
+ * Registration methods write to the extension tracking object.
4040
+ * Action methods delegate to the shared runtime.
4041
+ */
4042
+ createExtensionAPI(extension, manifest) {
4043
+ const runner = this;
4044
+ const extContext = {
4045
+ get cwd() {
4046
+ return runner.cwd;
4047
+ },
4048
+ get signal() {
4049
+ return runner.abortController.signal;
4050
+ },
4051
+ isIdle() {
4052
+ return runner._idle;
4053
+ },
4054
+ abort() {
4055
+ runner.abortController.abort();
4056
+ },
4057
+ exec(command, args, options) {
4058
+ return execCommand(command, args, runner.cwd, options);
4059
+ },
4060
+ manifest,
4061
+ getSystemPrompt() {
4062
+ return runner.currentContext?.systemPrompt ?? "";
4063
+ },
4064
+ setSystemPrompt(prompt) {
4065
+ if (runner.currentContext) runner.currentContext.systemPrompt = prompt;
4066
+ },
4067
+ getMessages() {
4068
+ return runner.currentContext?.messages ?? [];
4069
+ },
4070
+ addMessage(role, content) {
4071
+ runner.currentContext?.messages.push({ role, content });
4072
+ },
4073
+ getAvailableTools() {
4074
+ return (runner.currentContext?.tools ?? []).map((t) => ({ name: t.name, description: t.description }));
4075
+ },
4076
+ getAvailableSkills() {
4077
+ return (runner.currentContext?.skills ?? []).map((s) => ({ name: s.name, description: s.description }));
4078
+ },
4079
+ getSkillContent(skillName) {
4080
+ const skill = runner.currentContext?.skills.find((s) => s.name === skillName);
4081
+ return skill?.content ?? "";
4082
+ },
4083
+ getAvailablePhases() {
4084
+ return [...runner.currentContext?.phases?.phases.keys() ?? []];
4085
+ },
4086
+ getPhaseContent(phaseId) {
4087
+ const phase = runner.currentContext?.phases?.phases.get(phaseId);
4088
+ return phase?.content || phase?.description || "";
4089
+ }
4090
+ };
4091
+ return createExtensionAPI(this.hooks, {
4092
+ registerPhase: (registration) => this.registerPhase(extension, registration),
4093
+ registerProvider: (config) => this.registerProvider(config),
4094
+ unregisterProvider: (name) => this.unregisterProvider(name),
4095
+ registerTool: (tool) => this.registerTool(extension, tool),
4096
+ context: extContext,
4097
+ manifest,
4098
+ trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
4099
+ }, extension.runtime, this.events);
4100
+ }
4101
+ registerTool(extension, tool) {
4102
+ if (extension.tools.has(tool.name)) {
4103
+ throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
4104
+ }
4105
+ for (const ext of this.extensions) {
4106
+ if (ext.tools.has(tool.name)) {
4107
+ const message = `Duplicate Tool name "${tool.name}" from extensions ${ext.path} and ${extension.path}.`;
4108
+ this.emitError({
4109
+ extensionPath: extension.path,
4110
+ event: "register_tool",
4111
+ error: message
4112
+ });
4113
+ throw new Error(message);
4114
+ }
4115
+ }
4116
+ const sourceInfo = createSourceInfo(extension.path);
4117
+ extension.tools.set(tool.name, {
4118
+ definition: tool,
4119
+ sourceInfo
4120
+ });
3349
4121
  }
3350
- /**
3351
- * Get a tool definition by name. Returns undefined if not found.
3352
- */
3353
- getToolDefinition(toolName) {
3354
- for (const ext of this.extensions) {
3355
- const tool = ext.tools.get(toolName);
3356
- if (tool) return tool.definition;
4122
+ registerPhase(extension, registration) {
4123
+ if (typeof registration.name !== "string" || registration.name.length === 0) {
4124
+ throw new Error(`Phase registration requires a "name" field.`);
3357
4125
  }
3358
- return void 0;
4126
+ const name = registration.name;
4127
+ const description = validateDescription(registration.description);
4128
+ const errors = validateResourceId(name, "name");
4129
+ if (description.missing) errors.push(...description.warnings);
4130
+ errors.push(...validateSkillReferences(registration.skills));
4131
+ errors.push(...validatePhaseTarget(registration.target));
4132
+ if (errors.length > 0) {
4133
+ throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
4134
+ }
4135
+ if (description.warnings.length > 0) {
4136
+ warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
4137
+ }
4138
+ if (this.phases.has(name)) {
4139
+ throw new Error(`Duplicate phase name: ${name}`);
4140
+ }
4141
+ const definition = {
4142
+ name,
4143
+ description: description.description,
4144
+ run: registration.run,
4145
+ ...registration.tools ? { tools: registration.tools.slice() } : {},
4146
+ ...registration.skills ? { skills: registration.skills.slice() } : {},
4147
+ ...registration.target ? { target: registration.target } : {},
4148
+ ...registration.input ? { input: { ...registration.input } } : {},
4149
+ ...registration.model ? { model: registration.model } : {}
4150
+ };
4151
+ const registered = {
4152
+ definition,
4153
+ source: { extensionPath: extension.path }
4154
+ };
4155
+ this.phases.set(name, registered);
4156
+ extension.phases.add(name);
4157
+ this._phaseCache = null;
3359
4158
  }
3360
- // ---------------------------------------------------------------------------
3361
- // Phase management
3362
- // ---------------------------------------------------------------------------
3363
- getPhase(name) {
3364
- const reg = this.getRegisteredPhase(name);
3365
- if (!reg) return void 0;
3366
- return this.adaptToPhase(reg);
4159
+ registerProvider(config) {
4160
+ if (this.bound) {
4161
+ applyProviderRegistration(config);
4162
+ } else {
4163
+ this.pendingProviders.push({ kind: "register", config });
4164
+ }
3367
4165
  }
3368
- getPhases() {
3369
- return [...this.collectRegisteredPhases().values()].map(
3370
- (p) => this.adaptToPhase(p)
3371
- );
4166
+ unregisterProvider(name) {
4167
+ if (this.bound) {
4168
+ applyProviderUnregistration(name);
4169
+ } else {
4170
+ this.pendingProviders.push({ kind: "unregister", name });
4171
+ }
3372
4172
  }
3373
- createPhaseRegistry(input = {}) {
3374
- const registered = this.collectRegisteredPhases();
3375
- const phases = /* @__PURE__ */ new Map();
3376
- for (const [name, reg] of registered) {
3377
- phases.set(name, this.adaptToPhase(reg));
4173
+ flushPendingProviders() {
4174
+ for (const action of this.pendingProviders) {
4175
+ if (action.kind === "register") {
4176
+ applyProviderRegistration(action.config);
4177
+ } else {
4178
+ applyProviderUnregistration(action.name);
4179
+ }
3378
4180
  }
3379
- const entryPhaseId = input.entryPhaseId ?? null;
3380
- return { phases, entryPhaseId };
4181
+ this.pendingProviders.length = 0;
3381
4182
  }
3382
- /** Adapt an extension RegisteredPhase to the core Phase type. */
3383
- adaptToPhase(reg) {
3384
- const def = reg.definition;
4183
+ getRegisteredPhase(name) {
4184
+ return this.collectRegisteredPhases().get(name);
4185
+ }
4186
+ collectRegisteredPhases() {
4187
+ if (this._phaseCache) return this._phaseCache;
4188
+ this._phaseCache = new Map(this.phases);
4189
+ return this._phaseCache;
4190
+ }
4191
+ async rollbackExtension(extension) {
4192
+ await this.disposeExtension(extension, "This Extension activation was rolled back.");
4193
+ }
4194
+ async disposeExtension(extension, message) {
4195
+ extension.runtime.invalidate(message);
4196
+ for (const name of extension.phases) this.phases.delete(name);
4197
+ for (const cleanup of [...extension.cleanup].reverse()) {
4198
+ await Promise.resolve().then(() => cleanup()).catch(() => void 0);
4199
+ }
4200
+ if (extension.disposer) {
4201
+ await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
4202
+ }
4203
+ this._phaseCache = null;
4204
+ }
4205
+ };
4206
+ function createExtensionRunner(options) {
4207
+ return new ExtensionRunner(options);
4208
+ }
4209
+
4210
+ // src/extensions/loader.ts
4211
+ import { existsSync as existsSync2, readFileSync } from "fs";
4212
+ import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
4213
+ import { fileURLToPath } from "url";
4214
+ import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
4215
+ import { createJiti } from "jiti";
4216
+ function isExtensionFile(path) {
4217
+ const extension = extname2(path).toLowerCase();
4218
+ return extension === ".ts" || extension === ".js";
4219
+ }
4220
+ function isSyntheticPath(path) {
4221
+ return path.startsWith("<") && path.endsWith(">");
4222
+ }
4223
+ function readManifestSync(dir) {
4224
+ const manifestPath = join2(dir, "package.json");
4225
+ if (!existsSync2(manifestPath)) {
4226
+ return void 0;
4227
+ }
4228
+ try {
4229
+ const content = readFileSync(manifestPath, "utf8");
4230
+ const pkg = JSON.parse(content);
4231
+ const rowan = pkg.rowan;
4232
+ if (!rowan) return void 0;
3385
4233
  return {
3386
- name: def.name,
3387
- description: def.description,
3388
- tools: def.tools,
3389
- skills: def.skills,
3390
- target: def.target,
3391
- input: def.input,
3392
- run: def.run,
3393
- filePath: "",
3394
- baseDir: "",
3395
- content: "",
3396
- ...def.model ? { model: parseModelRef2(def.model) } : {}
4234
+ entry: rowan.extensions?.[0],
4235
+ phase: rowan.phase
3397
4236
  };
4237
+ } catch {
4238
+ return void 0;
3398
4239
  }
3399
- // ---------------------------------------------------------------------------
3400
- // Lifecycle
3401
- // ---------------------------------------------------------------------------
3402
- /**
3403
- * Bind the runner — flushes pending provider registrations and
3404
- * replaces runtime stubs with real implementations.
3405
- */
3406
- bind() {
3407
- if (this.bound) return;
3408
- this.bound = true;
3409
- this.flushPendingProviders();
4240
+ }
4241
+ function jitiAliases() {
4242
+ const moduleUrl = import.meta.url;
4243
+ if (!moduleUrl) {
4244
+ return {};
4245
+ }
4246
+ const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
4247
+ if (!existsSync2(agentSourcePath)) {
4248
+ return {};
4249
+ }
4250
+ return { "@rowan-agent/agent": agentSourcePath };
4251
+ }
4252
+ var sharedJiti;
4253
+ function getJiti() {
4254
+ sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
4255
+ fsCache: false,
4256
+ moduleCache: false,
4257
+ tryNative: false,
4258
+ alias: jitiAliases()
4259
+ });
4260
+ return sharedJiti;
4261
+ }
4262
+ async function loadExtensionModule(extensionPath) {
4263
+ const jiti = getJiti();
4264
+ const module = await jiti.import(extensionPath, { default: true });
4265
+ return typeof module === "function" ? module : void 0;
4266
+ }
4267
+ async function readPackageManifest(path) {
4268
+ if (!existsSync2(path)) {
4269
+ return void 0;
4270
+ }
4271
+ const content = await readFile3(path, "utf8");
4272
+ return JSON.parse(content);
4273
+ }
4274
+ async function resolveExtensionEntries(dir) {
4275
+ const manifest = await readPackageManifest(join2(dir, "package.json"));
4276
+ const declared = manifest?.rowan?.extensions;
4277
+ if (declared && declared.length > 0) {
4278
+ return declared.map((entry) => resolve4(dir, entry));
4279
+ }
4280
+ for (const name of ["index.ts", "index.js"]) {
4281
+ const entry = join2(dir, name);
4282
+ if (existsSync2(entry)) {
4283
+ return [entry];
4284
+ }
4285
+ }
4286
+ return void 0;
4287
+ }
4288
+ async function discoverExtensionsInDir(dir) {
4289
+ const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
4290
+ if (error.code === "ENOENT") {
4291
+ return [];
4292
+ }
4293
+ throw error;
4294
+ });
4295
+ const paths = [];
4296
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4297
+ const entryPath = join2(dir, entry.name);
4298
+ if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4299
+ paths.push(entryPath);
4300
+ continue;
4301
+ }
4302
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
4303
+ const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
4304
+ if (entry.isDirectory() || info?.isDirectory()) {
4305
+ const resolvedEntries = await resolveExtensionEntries(entryPath);
4306
+ if (resolvedEntries) {
4307
+ paths.push(...resolvedEntries);
4308
+ }
4309
+ }
4310
+ }
4311
+ }
4312
+ return paths;
4313
+ }
4314
+ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4315
+ const resolvedCwd = resolve4(cwd);
4316
+ const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
4317
+ const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
4318
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
4319
+ const manifest = readManifestSync(manifestDir);
4320
+ return {
4321
+ path: resolvedPath,
4322
+ name,
4323
+ factory,
4324
+ manifest
4325
+ };
4326
+ }
4327
+ async function loadExtensions(paths, cwd) {
4328
+ const extensions = [];
4329
+ const errors = [];
4330
+ const resolvedCwd = resolve4(cwd);
4331
+ for (const path of paths) {
4332
+ const resolvedPath = resolve4(resolvedCwd, path);
4333
+ try {
4334
+ const factory = await loadExtensionModule(resolvedPath);
4335
+ if (!factory) {
4336
+ errors.push({
4337
+ path: resolvedPath,
4338
+ error: `Extension does not export a valid factory function: ${path}`
4339
+ });
4340
+ continue;
4341
+ }
4342
+ extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
4343
+ } catch (error) {
4344
+ errors.push({
4345
+ path: resolvedPath,
4346
+ error: error instanceof Error ? error.message : String(error)
4347
+ });
4348
+ }
4349
+ }
4350
+ return { extensions, errors };
4351
+ }
4352
+ async function loadExtensionsFromPath(path) {
4353
+ const resolvedPath = resolve4(path);
4354
+ const info = await stat2(resolvedPath);
4355
+ if (!info.isDirectory()) {
4356
+ return loadExtensions([resolvedPath], dirname4(resolvedPath));
3410
4357
  }
3411
- // ---------------------------------------------------------------------------
3412
- // Unified hook emission
3413
- // ---------------------------------------------------------------------------
3414
- /**
3415
- * Generic emit — fire-and-forget for any event type.
3416
- */
3417
- async emit(eventType, event) {
3418
- await this.hooks.emit(eventType, event);
4358
+ const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
4359
+ return loadExtensions(entries, resolvedPath);
4360
+ }
4361
+
4362
+ // src/runtime/resource-registry.ts
4363
+ import { stat as stat3, readdir as readdir4 } from "fs/promises";
4364
+ import { join as join4, resolve as resolve6 } from "path";
4365
+
4366
+ // src/harness/skills.ts
4367
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
4368
+ import { readdir as readdir3 } from "fs/promises";
4369
+ import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
4370
+ var SKILL_MARKER = "SKILL.md";
4371
+ function resolveSkillPath(input) {
4372
+ const resolved = resolve5(input);
4373
+ return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
4374
+ }
4375
+ async function loadSkill(path) {
4376
+ const resolved = resolveSkillPath(path);
4377
+ let loaded;
4378
+ try {
4379
+ loaded = await loadMarkdown(resolved);
4380
+ } catch (error) {
4381
+ if (!(error instanceof FrontmatterParseError)) throw error;
4382
+ warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
4383
+ throw new ResourceMetadataError("parse_failed", error.message);
3419
4384
  }
3420
- /**
3421
- * Unified hook emission — returns the first non-undefined result.
3422
- */
3423
- async emitHook(type, event) {
3424
- return this.hooks.emitFirst(type, event);
4385
+ const { frontmatter, body } = loaded;
4386
+ const metadata = frontmatter;
4387
+ const id = inferResourceName(resolved, SKILL_MARKER);
4388
+ const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
4389
+ const name = frontmatterName || id;
4390
+ const description = validateDescription(metadata.description);
4391
+ const diagnostics = validateResourceId(id);
4392
+ if (frontmatterName && frontmatterName !== id) {
4393
+ diagnostics.push(...validateResourceName(frontmatterName, id));
3425
4394
  }
3426
- // ---------------------------------------------------------------------------
3427
- // Phase hooks (with inline processing)
3428
- // ---------------------------------------------------------------------------
3429
- async emitBeforePhase(phaseId, input) {
3430
- const result = await this.emitHook("before_phase", {
3431
- type: "before_phase",
3432
- phaseId,
3433
- input
3434
- });
3435
- return result ?? {};
4395
+ diagnostics.push(...description.warnings);
4396
+ warnResourceDiagnostics("skill", resolved, diagnostics);
4397
+ if (description.missing) {
4398
+ throw new ResourceMetadataError("invalid_metadata", "description is required");
3436
4399
  }
3437
- async emitAfterPhase(phaseId, output) {
3438
- const result = await this.emitHook("after_phase", {
3439
- type: "after_phase",
3440
- phaseId,
3441
- output
3442
- });
3443
- return result ?? {};
4400
+ return {
4401
+ name,
4402
+ description: description.description,
4403
+ filePath: resolved,
4404
+ baseDir: dirname5(resolved),
4405
+ content: body,
4406
+ disableModelInvocation: metadata["disable-model-invocation"] === true
4407
+ };
4408
+ }
4409
+ async function loadSkills(targetPath) {
4410
+ const skillsDir = resolve5(targetPath);
4411
+ if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
4412
+ try {
4413
+ return [await loadSkill(skillsDir)];
4414
+ } catch (error) {
4415
+ if (error instanceof ResourceMetadataError) return [];
4416
+ throw error;
4417
+ }
3444
4418
  }
3445
- async emitBeforePrompt(phaseId, input) {
3446
- const result = await this.emitHook("before_prompt", {
3447
- type: "before_prompt",
3448
- phaseId,
3449
- input
3450
- });
3451
- return result?.input ?? input;
4419
+ if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
4420
+ try {
4421
+ return [await loadSkill(skillsDir)];
4422
+ } catch (error) {
4423
+ if (error instanceof ResourceMetadataError) return [];
4424
+ throw error;
4425
+ }
3452
4426
  }
3453
- async emitBeforeToolCall(tool, args) {
3454
- const result = await this.emitHook("before_tool_call", {
3455
- type: "before_tool_call",
3456
- tool,
3457
- args
3458
- });
3459
- return result ?? { allow: true };
4427
+ const entries = await readdir3(skillsDir, { withFileTypes: true });
4428
+ const skills = [];
4429
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4430
+ if (!entry.isDirectory()) continue;
4431
+ const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
4432
+ if (!existsSync3(skillFile)) continue;
4433
+ try {
4434
+ const skill = await loadSkill(skillFile);
4435
+ skills.push(skill);
4436
+ } catch (error) {
4437
+ if (error instanceof ResourceMetadataError) continue;
4438
+ console.warn(`Failed to load skill "${entry.name}":`, error);
4439
+ }
3460
4440
  }
3461
- async emitAfterToolCall(tool, result) {
3462
- const hookResult = await this.emitHook("after_tool_call", {
3463
- type: "after_tool_call",
3464
- tool,
3465
- result
3466
- });
3467
- return hookResult?.result ?? result;
4441
+ return skills;
4442
+ }
4443
+
4444
+ // src/runtime/resource-registry.ts
4445
+ var ResourceRegistryError = class extends Error {
4446
+ code;
4447
+ constructor(code, message) {
4448
+ super(message);
4449
+ this.name = "ResourceRegistryError";
4450
+ this.code = code;
3468
4451
  }
3469
- // ---------------------------------------------------------------------------
3470
- // Internal helpers
3471
- // ---------------------------------------------------------------------------
3472
- /**
3473
- * Create an ExtensionAPI for a specific extension.
3474
- * Registration methods write to the extension tracking object.
3475
- * Action methods delegate to the shared runtime.
3476
- */
3477
- createExtensionAPI(extension, manifest) {
3478
- const runner = this;
3479
- const extContext = {
3480
- get cwd() {
3481
- return runner.cwd;
3482
- },
3483
- get signal() {
3484
- return runner.abortController.signal;
3485
- },
3486
- isIdle() {
3487
- return runner._idle;
3488
- },
3489
- abort() {
3490
- runner.abortController.abort();
3491
- },
3492
- exec(command, args, options) {
3493
- return execCommand(command, args, runner.cwd, options);
3494
- },
3495
- manifest,
3496
- getSystemPrompt() {
3497
- return runner.currentContext?.systemPrompt ?? "";
3498
- },
3499
- setSystemPrompt(prompt) {
3500
- if (runner.currentContext) runner.currentContext.systemPrompt = prompt;
3501
- },
3502
- getMessages() {
3503
- return runner.currentContext?.messages ?? [];
3504
- },
3505
- addMessage(role, content) {
3506
- runner.currentContext?.messages.push({ role, content });
3507
- },
3508
- getAvailableTools() {
3509
- return (runner.currentContext?.tools ?? []).map((t) => ({ name: t.name, description: t.description }));
3510
- },
3511
- getAvailableSkills() {
3512
- return (runner.currentContext?.skills ?? []).map((s) => ({ name: s.name, description: s.description }));
3513
- },
3514
- getSkillContent(skillName) {
3515
- const skill = runner.currentContext?.skills.find((s) => s.name === skillName);
3516
- return skill?.content ?? "";
3517
- },
3518
- getAvailablePhases() {
3519
- return [...runner.currentContext?.phases?.phases.keys() ?? []];
4452
+ };
4453
+ var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4454
+ var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4455
+ var ResourceRegistry = class {
4456
+ sources = /* @__PURE__ */ new Map();
4457
+ /** Sources registered by the bootstrap boundary (currently Extensions). */
4458
+ implicitSources = /* @__PURE__ */ new Map();
4459
+ revisionSequence = 0;
4460
+ async loadAgents(input) {
4461
+ assertSourceId(input.sourceId);
4462
+ const collected = await this.collect("agent", input);
4463
+ return this.commit("agent", input.sourceId, collected.values, collected.skipped);
4464
+ }
4465
+ async loadSkills(input) {
4466
+ assertSourceId(input.sourceId);
4467
+ const collected = await this.collect("skill", input);
4468
+ return this.commit("skill", input.sourceId, collected.values, collected.skipped);
4469
+ }
4470
+ async loadPhases(input) {
4471
+ assertSourceId(input.sourceId);
4472
+ const collected = await this.collect("phase", input);
4473
+ return this.commit("phase", input.sourceId, collected.values, collected.skipped);
4474
+ }
4475
+ async loadTools(input) {
4476
+ assertSourceId(input.sourceId);
4477
+ return this.commit("tool", input.sourceId, input.values, []);
4478
+ }
4479
+ async unload(input) {
4480
+ assertSourceId(input.sourceId);
4481
+ if (this.hasImplicit(input.kind, input.sourceId)) {
4482
+ throw new ResourceRegistryError(
4483
+ "implicit_source_protected",
4484
+ `Implicit ${input.kind} source "${input.sourceId}" is managed by Runtime bootstrap.`
4485
+ );
4486
+ }
4487
+ const bySource = this.sources.get(input.kind);
4488
+ const existing = bySource?.get(input.sourceId);
4489
+ if (!existing) {
4490
+ return {
4491
+ revision: this.nextRevision(input.kind, input.sourceId),
4492
+ registered: [],
4493
+ skipped: []
4494
+ };
4495
+ }
4496
+ bySource.delete(input.sourceId);
4497
+ return {
4498
+ revision: this.nextRevision(input.kind, input.sourceId),
4499
+ registered: [],
4500
+ skipped: []
4501
+ };
4502
+ }
4503
+ resolveView(view) {
4504
+ const sourceIds = {
4505
+ agents: this.withImplicit("agent", view.agents),
4506
+ tools: this.withImplicit("tool", view.tools),
4507
+ skills: this.withImplicit("skill", view.skills),
4508
+ phases: this.withImplicit("phase", view.phases)
4509
+ };
4510
+ return {
4511
+ agents: this.resolveKind("agent", sourceIds.agents),
4512
+ tools: this.resolveKind("tool", sourceIds.tools),
4513
+ skills: this.resolveKind("skill", sourceIds.skills),
4514
+ phases: this.resolveKind("phase", sourceIds.phases),
4515
+ revisions: {
4516
+ agent: this.resolveRevisions("agent", sourceIds.agents),
4517
+ tool: this.resolveRevisions("tool", sourceIds.tools),
4518
+ skill: this.resolveRevisions("skill", sourceIds.skills),
4519
+ phase: this.resolveRevisions("phase", sourceIds.phases)
3520
4520
  },
3521
- getPhaseContent(phaseId) {
3522
- const phase = runner.currentContext?.phases?.phases.get(phaseId);
3523
- return phase?.content || phase?.description || "";
4521
+ refs: {
4522
+ agent: this.resolveRefs("agent", sourceIds.agents),
4523
+ tool: this.resolveRefs("tool", sourceIds.tools),
4524
+ skill: this.resolveRefs("skill", sourceIds.skills),
4525
+ phase: this.resolveRefs("phase", sourceIds.phases)
3524
4526
  }
3525
4527
  };
3526
- return createExtensionAPI(this.hooks, {
3527
- registerPhase: (registration) => this.registerPhase(extension, registration),
3528
- registerProvider: (config) => this.registerProvider(config),
3529
- unregisterProvider: (name) => this.unregisterProvider(name),
3530
- registerTool: (tool) => this.registerTool(extension, tool),
3531
- context: extContext,
3532
- manifest
3533
- }, this.runtime, this.events);
3534
4528
  }
3535
- registerTool(extension, tool) {
3536
- if (extension.tools.has(tool.name)) {
3537
- throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
3538
- }
3539
- for (const ext of this.extensions) {
3540
- if (ext.tools.has(tool.name)) {
3541
- const message = `Duplicate Tool name "${tool.name}" from extensions ${ext.path} and ${extension.path}.`;
3542
- this.emitError({
3543
- extensionPath: extension.path,
3544
- event: "register_tool",
3545
- error: message
3546
- });
3547
- throw new Error(message);
3548
- }
3549
- }
3550
- const sourceInfo = createSourceInfo(extension.path);
3551
- extension.tools.set(tool.name, {
3552
- definition: tool,
3553
- sourceInfo
3554
- });
4529
+ /** Mark one already-loaded source as implicit for every Resource View. */
4530
+ markImplicit(kind, sourceId) {
4531
+ assertSourceId(sourceId);
4532
+ const entries = this.implicitSources.get(kind) ?? [];
4533
+ if (!entries.includes(sourceId)) entries.push(sourceId);
4534
+ this.implicitSources.set(kind, entries);
4535
+ }
4536
+ /** Bootstrap-only helper for replacing an implicit contribution source. */
4537
+ async replaceImplicit(kind, sourceId, values) {
4538
+ const result = await this.commit(kind, sourceId, values, []);
4539
+ this.markImplicit(kind, sourceId);
4540
+ return result;
3555
4541
  }
3556
- registerPhase(extension, registration) {
3557
- if (typeof registration.name !== "string" || registration.name.length === 0) {
3558
- throw new Error(`Phase registration requires a "name" field.`);
3559
- }
3560
- const name = registration.name;
3561
- const description = validateDescription(registration.description);
3562
- const errors = validateResourceId(name, "name");
3563
- if (description.missing) errors.push(...description.warnings);
3564
- errors.push(...validateSkillReferences(registration.skills));
3565
- errors.push(...validatePhaseTarget(registration.target));
3566
- if (errors.length > 0) {
3567
- throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
3568
- }
3569
- if (description.warnings.length > 0) {
3570
- warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
4542
+ hasImplicit(kind, sourceId) {
4543
+ return this.implicitSources.get(kind)?.includes(sourceId) ?? false;
4544
+ }
4545
+ withImplicit(kind, sourceIds) {
4546
+ const combined = [...sourceIds];
4547
+ for (const sourceId of this.implicitSources.get(kind) ?? []) {
4548
+ if (!combined.includes(sourceId)) combined.push(sourceId);
3571
4549
  }
3572
- if (this.phases.has(name)) {
3573
- throw new Error(`Duplicate phase name: ${name}`);
4550
+ return combined;
4551
+ }
4552
+ async collect(kind, input) {
4553
+ const hasDirectory = input.directory !== void 0;
4554
+ const hasValues = input.values !== void 0;
4555
+ if (!hasDirectory && !hasValues) {
4556
+ throw new ResourceRegistryError(
4557
+ "resource_input_missing",
4558
+ `Resource source "${input.sourceId}" requires directory or values.`
4559
+ );
3574
4560
  }
3575
- const definition = {
3576
- name,
3577
- description: description.description,
3578
- run: registration.run,
3579
- ...registration.tools ? { tools: registration.tools.slice() } : {},
3580
- ...registration.skills ? { skills: registration.skills.slice() } : {},
3581
- ...registration.target ? { target: registration.target } : {},
3582
- ...registration.input ? { input: { ...registration.input } } : {},
3583
- ...registration.model ? { model: registration.model } : {}
3584
- };
3585
- const registered = {
3586
- definition,
3587
- source: { extensionPath: extension.path }
4561
+ const directoryValues = hasDirectory ? await loadDirectoryValues(kind, input.directory, input.sourceId) : { values: [], skipped: [] };
4562
+ return {
4563
+ values: [...directoryValues.values, ...input.values ?? []],
4564
+ skipped: directoryValues.skipped
3588
4565
  };
3589
- this.phases.set(name, registered);
3590
- this._phaseCache = null;
3591
4566
  }
3592
- registerProvider(config) {
3593
- if (this.bound) {
3594
- applyProviderRegistration(config);
3595
- } else {
3596
- this.pendingProviders.push({ kind: "register", config });
4567
+ async commit(kind, sourceId, values, skipped) {
4568
+ assertSourceId(sourceId);
4569
+ const names = /* @__PURE__ */ new Set();
4570
+ for (const value of values) {
4571
+ assertNamedResource(value, kind, sourceId);
4572
+ if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
4573
+ throw new ResourceRegistryError(
4574
+ "resource_collision",
4575
+ `${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
4576
+ );
4577
+ }
4578
+ if (names.has(value.name)) {
4579
+ throw new ResourceRegistryError(
4580
+ "resource_collision",
4581
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in source "${sourceId}".`
4582
+ );
4583
+ }
4584
+ names.add(value.name);
4585
+ }
4586
+ const revision = this.nextRevision(kind, sourceId);
4587
+ const record = {
4588
+ kind,
4589
+ sourceId,
4590
+ revision,
4591
+ values: Object.freeze([...values])
4592
+ };
4593
+ let bySource = this.sources.get(kind);
4594
+ if (!bySource) {
4595
+ bySource = /* @__PURE__ */ new Map();
4596
+ this.sources.set(kind, bySource);
3597
4597
  }
4598
+ bySource.set(sourceId, record);
4599
+ return {
4600
+ revision,
4601
+ registered: values.map((value) => ({ kind, sourceId, name: value.name })),
4602
+ skipped
4603
+ };
3598
4604
  }
3599
- unregisterProvider(name) {
3600
- if (this.bound) {
3601
- applyProviderUnregistration(name);
3602
- } else {
3603
- this.pendingProviders.push({ kind: "unregister", name });
4605
+ resolveKind(kind, sourceIds) {
4606
+ const names = /* @__PURE__ */ new Set();
4607
+ const values = [];
4608
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4609
+ for (const value of source.values) {
4610
+ if (names.has(value.name)) {
4611
+ throw new ResourceRegistryError(
4612
+ "resource_collision",
4613
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
4614
+ );
4615
+ }
4616
+ names.add(value.name);
4617
+ values.push(value);
4618
+ }
3604
4619
  }
4620
+ return values;
3605
4621
  }
3606
- flushPendingProviders() {
3607
- for (const action of this.pendingProviders) {
3608
- if (action.kind === "register") {
3609
- applyProviderRegistration(action.config);
3610
- } else {
3611
- applyProviderUnregistration(action.name);
4622
+ resolveRevisions(kind, sourceIds) {
4623
+ return this.sourcesFor(kind, sourceIds).map(({ revision }) => revision);
4624
+ }
4625
+ resolveRefs(kind, sourceIds) {
4626
+ const seenNames = /* @__PURE__ */ new Set();
4627
+ const refs = [];
4628
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4629
+ for (const value of source.values) {
4630
+ if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
4631
+ seenNames.add(value.name);
4632
+ refs.push({ kind, sourceId: source.sourceId, name: value.name });
3612
4633
  }
3613
4634
  }
3614
- this.pendingProviders.length = 0;
3615
- }
3616
- getRegisteredPhase(name) {
3617
- return this.collectRegisteredPhases().get(name);
4635
+ return refs;
4636
+ }
4637
+ sourcesFor(kind, sourceIds) {
4638
+ const bySource = this.sources.get(kind);
4639
+ const seen = /* @__PURE__ */ new Set();
4640
+ const sources = [];
4641
+ for (const sourceId of sourceIds) {
4642
+ assertSourceId(sourceId);
4643
+ if (seen.has(sourceId)) continue;
4644
+ seen.add(sourceId);
4645
+ const source = bySource?.get(sourceId);
4646
+ if (!source) {
4647
+ throw new ResourceRegistryError(
4648
+ "unknown_resource_source",
4649
+ `Unknown resource source "${sourceId}" for ${kind}.`
4650
+ );
4651
+ }
4652
+ sources.push(source);
4653
+ }
4654
+ return sources;
3618
4655
  }
3619
- collectRegisteredPhases() {
3620
- if (this._phaseCache) return this._phaseCache;
3621
- this._phaseCache = new Map(this.phases);
3622
- return this._phaseCache;
4656
+ nextRevision(kind, sourceId) {
4657
+ this.revisionSequence += 1;
4658
+ return `${kind}:${sourceId}:${this.revisionSequence}`;
3623
4659
  }
3624
4660
  };
3625
- function createExtensionRunner(options) {
3626
- return new ExtensionRunner(options);
3627
- }
3628
-
3629
- // src/runtime/extensions.ts
3630
- async function assembleExtensions(config) {
3631
- if (!config.extensions?.length) return { context: config.context };
3632
- const runner = createExtensionRunner({ cwd: config.cwd });
3633
- await runner.loadExtensions([...config.extensions]);
3634
- runner.bind();
3635
- const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3636
- const tools = [...config.context.tools];
3637
- const names = new Set(tools.map((tool) => tool.name));
3638
- for (const tool of extensionTools) {
3639
- if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
3640
- names.add(tool.name);
3641
- tools.push(tool);
4661
+ function assertSourceId(sourceId) {
4662
+ if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
4663
+ throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
3642
4664
  }
3643
- const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3644
- const phases = mergePhases(config.context.phases, extensionPhases);
3645
- return {
3646
- context: { ...config.context, tools, ...phases ? { phases } : {} },
3647
- beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3648
- afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3649
- beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
3650
- beforeToolCall: async (input) => {
3651
- const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3652
- const decision = await runner.emitBeforeToolCall(tool, input.args);
3653
- return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
3654
- },
3655
- afterToolCall: async (input) => {
3656
- const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3657
- const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3658
- return fromLoopResult(result);
3659
- },
3660
- setContext: (context) => {
3661
- runner.currentContext = context;
3662
- }
3663
- };
3664
4665
  }
3665
- function mergePhases(base, extension) {
3666
- if (!base && extension.phases.size === 0) return void 0;
3667
- const phases = new Map(base?.phases ?? []);
3668
- for (const [name, phase] of extension.phases) {
3669
- if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
3670
- phases.set(name, phase);
4666
+ function assertNamedResource(value, kind, sourceId) {
4667
+ if (!value || typeof value !== "object" || typeof value.name !== "string") {
4668
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${kind} resource in source "${sourceId}".`);
4669
+ }
4670
+ const name = value.name;
4671
+ if (!RESOURCE_NAME.test(name)) {
4672
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${capitalize(kind)} resource name "${name}".`);
3671
4673
  }
3672
- return {
3673
- phases,
3674
- entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
3675
- };
3676
- }
3677
- function adaptExtensionTool(input) {
3678
- const definition = input.definition;
3679
- return {
3680
- name: definition.name,
3681
- description: definition.description,
3682
- parameters: definition.parameters,
3683
- execute: async (args, _context, signal) => {
3684
- const result = await definition.execute(args, signal);
3685
- const content = JSON.parse(JSON.stringify(result.content));
3686
- return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3687
- }
3688
- };
3689
- }
3690
- function toLoopResult(result, toolCallId, toolName) {
3691
- return {
3692
- toolCallId,
3693
- toolName,
3694
- ok: result.ok,
3695
- content: result.content,
3696
- ...!result.ok ? { error: result.error } : {}
3697
- };
3698
4674
  }
3699
- function fromLoopResult(result) {
3700
- return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
4675
+ function capitalize(value) {
4676
+ return value.charAt(0).toUpperCase() + value.slice(1);
3701
4677
  }
3702
-
3703
- // src/runtime/transient-run-events.ts
3704
- var MAX_BUFFERED_EVENTS = 128;
3705
- var MAX_BUFFERED_TEXT = 64 * 1024;
3706
- function deferred() {
3707
- let resolve6;
3708
- const promise = new Promise((done) => {
3709
- resolve6 = done;
3710
- });
3711
- return { promise, resolve: resolve6 };
4678
+ function isImplicitCoreName(kind, name) {
4679
+ return kind === "tool" && name === "route" || kind === "phase" && name === "default";
3712
4680
  }
3713
- var TransientRunEventSubscription = class {
3714
- queue = [];
3715
- wake = deferred();
3716
- version = 0;
3717
- stopped = false;
3718
- push(event) {
3719
- if (this.stopped) return;
3720
- const previous = this.queue.at(-1);
3721
- if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3722
- this.queue[this.queue.length - 1] = {
3723
- ...previous,
3724
- text: previous.text + event.text
4681
+ async function loadDirectoryValues(kind, directory, sourceId) {
4682
+ const root = resolve6(directory);
4683
+ let rootInfo;
4684
+ try {
4685
+ rootInfo = await stat3(root);
4686
+ } catch (error) {
4687
+ if (isMissingPath(error)) {
4688
+ return {
4689
+ values: [],
4690
+ skipped: [{ kind: "missing", sourceId, path: root, message: `Resource directory "${root}" does not exist.` }]
3725
4691
  };
3726
- } else {
3727
- if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3728
- this.queue.push(event);
3729
4692
  }
3730
- this.trimText();
3731
- this.notify();
3732
- }
3733
- shift() {
3734
- return this.queue.shift();
4693
+ return {
4694
+ values: [],
4695
+ skipped: [{
4696
+ kind: "invalid",
4697
+ sourceId,
4698
+ path: root,
4699
+ message: `Resource directory "${root}" could not be read: ${error instanceof Error ? error.message : String(error)}`
4700
+ }]
4701
+ };
3735
4702
  }
3736
- clear(executionId) {
3737
- if (executionId === void 0) this.queue.length = 0;
3738
- else {
3739
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3740
- if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
4703
+ const marker = markerFor(kind);
4704
+ const targets = [];
4705
+ if (rootInfo.isFile()) {
4706
+ targets.push(root);
4707
+ } else if (rootInfo.isDirectory()) {
4708
+ const markerAtRoot = join4(root, marker);
4709
+ try {
4710
+ const markerInfo = await stat3(markerAtRoot);
4711
+ if (markerInfo.isFile()) targets.push(markerAtRoot);
4712
+ } catch (error) {
4713
+ if (!isMissingPath(error)) throw error;
4714
+ let entries;
4715
+ try {
4716
+ entries = await readdir4(root, { withFileTypes: true });
4717
+ } catch (error2) {
4718
+ return {
4719
+ values: [],
4720
+ skipped: [{
4721
+ kind: "invalid",
4722
+ sourceId,
4723
+ path: root,
4724
+ message: `Resource directory "${root}" could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
4725
+ }]
4726
+ };
4727
+ }
4728
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
4729
+ if (!entry.isDirectory()) continue;
4730
+ const target = join4(root, entry.name, marker);
4731
+ try {
4732
+ const targetInfo = await stat3(target);
4733
+ if (targetInfo.isFile()) targets.push(target);
4734
+ } catch (targetError) {
4735
+ if (!isMissingPath(targetError)) throw targetError;
4736
+ }
3741
4737
  }
3742
4738
  }
3743
- this.notify();
3744
4739
  }
3745
- clearMessage(messageId) {
3746
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3747
- const event = this.queue[index];
3748
- if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
4740
+ const values = [];
4741
+ const skipped = [];
4742
+ for (const target of targets) {
4743
+ try {
4744
+ const value = await loadDirectoryValue(kind, target);
4745
+ values.push(value);
4746
+ } catch (error) {
4747
+ skipped.push({
4748
+ kind: "invalid",
4749
+ sourceId,
4750
+ path: target,
4751
+ message: error instanceof Error ? error.message : String(error)
4752
+ });
3749
4753
  }
3750
- this.notify();
3751
4754
  }
3752
- clearTool(toolCallId) {
3753
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3754
- const event = this.queue[index];
3755
- if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
4755
+ return { values, skipped };
4756
+ }
4757
+ async function loadDirectoryValue(kind, target) {
4758
+ switch (kind) {
4759
+ case "agent": {
4760
+ let loaded;
4761
+ try {
4762
+ loaded = await loadMarkdown(target);
4763
+ } catch (error) {
4764
+ if (error instanceof FrontmatterParseError) {
4765
+ throw new TypeError(`frontmatter could not be parsed: ${error.message}`);
4766
+ }
4767
+ throw error;
4768
+ }
4769
+ return normalizeAgentDefinition(
4770
+ loaded.frontmatter,
4771
+ loaded.body,
4772
+ { fallbackName: inferResourceName(target, "AGENT.md") }
4773
+ );
3756
4774
  }
3757
- this.notify();
3758
- }
3759
- checkpoint() {
3760
- return this.version;
3761
- }
3762
- changed(since) {
3763
- if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3764
- return this.wake.promise;
4775
+ case "skill":
4776
+ return await loadSkill(target);
4777
+ case "phase":
4778
+ return await loadPhase(target);
4779
+ case "tool":
4780
+ throw new ResourceRegistryError(
4781
+ "resource_directory_not_supported",
4782
+ "Tool sources accept inline values only."
4783
+ );
3765
4784
  }
3766
- stop() {
3767
- if (this.stopped) return;
3768
- this.stopped = true;
3769
- this.queue.length = 0;
3770
- this.notify();
4785
+ }
4786
+ function markerFor(kind) {
4787
+ switch (kind) {
4788
+ case "agent":
4789
+ return "AGENT.md";
4790
+ case "skill":
4791
+ return "SKILL.md";
4792
+ case "phase":
4793
+ return "PHASE.md";
4794
+ case "tool":
4795
+ return "";
3771
4796
  }
3772
- notify() {
3773
- const wake = this.wake;
3774
- this.wake = deferred();
3775
- this.version += 1;
3776
- wake.resolve();
4797
+ }
4798
+ function isMissingPath(error) {
4799
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
4800
+ }
4801
+
4802
+ // src/runtime/extension-lifetime.ts
4803
+ var ExtensionLifetimeError = class extends Error {
4804
+ code;
4805
+ constructor(code, message) {
4806
+ super(message);
4807
+ this.name = "ExtensionLifetimeError";
4808
+ this.code = code;
3777
4809
  }
3778
- trimText() {
3779
- let buffered = this.queue.reduce(
3780
- (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3781
- 0
3782
- );
3783
- while (buffered > MAX_BUFFERED_TEXT) {
3784
- const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3785
- if (index < 0) return;
3786
- const event = this.queue[index];
3787
- const overflow = buffered - MAX_BUFFERED_TEXT;
3788
- if (event.text.length <= overflow) {
3789
- this.queue.splice(index, 1);
3790
- buffered -= event.text.length;
3791
- } else {
3792
- this.queue[index] = {
3793
- ...event,
3794
- offset: event.offset + overflow,
3795
- text: event.text.slice(overflow)
3796
- };
3797
- buffered -= overflow;
4810
+ };
4811
+ var RuntimeExtensionLifetime = class {
4812
+ runner;
4813
+ frozen = false;
4814
+ closed = false;
4815
+ constructor(options = {}) {
4816
+ this.runner = createExtensionRunner(options);
4817
+ }
4818
+ async activate(extensions) {
4819
+ if (this.closed) throw new ExtensionLifetimeError("extensions_closed", "The Extension Runtime has been closed.");
4820
+ if (this.frozen) throw new ExtensionLifetimeError("extensions_frozen", "Extensions are frozen until Runtime restart.");
4821
+ const active = [];
4822
+ const errors = [];
4823
+ for (const extension of extensions) {
4824
+ try {
4825
+ await this.runner.loadExtensions([extension]);
4826
+ active.push(extension.name);
4827
+ } catch (error) {
4828
+ errors.push({
4829
+ path: extension.path,
4830
+ error: error instanceof Error ? error.message : String(error)
4831
+ });
3798
4832
  }
3799
4833
  }
4834
+ this.runner.bind();
4835
+ this.frozen = true;
4836
+ return { active, errors };
4837
+ }
4838
+ tools() {
4839
+ return this.runner.getAllRegisteredTools();
4840
+ }
4841
+ phases() {
4842
+ return this.runner.getPhases();
4843
+ }
4844
+ get extensionRunner() {
4845
+ return this.runner;
4846
+ }
4847
+ async close() {
4848
+ if (this.closed) return;
4849
+ this.closed = true;
4850
+ await this.runner.close();
3800
4851
  }
3801
4852
  };
3802
- var TransientRunEventHub = class {
3803
- subscriptions = /* @__PURE__ */ new Map();
3804
- subscribe(runId) {
3805
- const subscription = new TransientRunEventSubscription();
3806
- const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3807
- current.add(subscription);
3808
- this.subscriptions.set(runId, current);
3809
- return subscription;
4853
+ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
4854
+ constructor(extensions = new RuntimeExtensionLifetime()) {
4855
+ super();
4856
+ this.extensions = extensions;
4857
+ }
4858
+ extensions;
4859
+ extensionSourceId = "rowan.extensions";
4860
+ /** Install Rowan-owned implicit resources before any host source is read. */
4861
+ async ensureCoreResources() {
4862
+ await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
4863
+ await this.replaceImplicit("phase", "rowan.core", [createDefaultPhase()]);
4864
+ }
4865
+ async loadExtensions(input) {
4866
+ const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
4867
+ const descriptor = input;
4868
+ return descriptor.directory ? loadExtensionsFromPath(descriptor.directory) : Promise.resolve({ extensions: [...descriptor.values ?? []], errors: [] });
4869
+ })();
4870
+ const normalized = await loaded;
4871
+ const result = await this.extensions.activate(normalized.extensions);
4872
+ await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool2));
4873
+ await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
4874
+ return {
4875
+ ...result,
4876
+ revision: `extensions:${Date.now()}`,
4877
+ registered: result.active,
4878
+ skipped: normalized.errors.map((error) => ({
4879
+ kind: "invalid",
4880
+ sourceId: "rowan.extensions",
4881
+ path: error.path,
4882
+ message: error.error
4883
+ }))
4884
+ };
3810
4885
  }
3811
- unsubscribe(runId, subscription) {
3812
- subscription.stop();
3813
- const current = this.subscriptions.get(runId);
3814
- current?.delete(subscription);
3815
- if (current?.size === 0) this.subscriptions.delete(runId);
4886
+ get extensionRunner() {
4887
+ return this.extensions.extensionRunner;
3816
4888
  }
3817
- publish(event) {
3818
- for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3819
- subscription.push(event);
3820
- }
4889
+ get extensionTools() {
4890
+ return this.extensions.tools();
3821
4891
  }
3822
- clear(runId, executionId) {
3823
- for (const subscription of this.subscriptions.get(runId) ?? []) {
3824
- subscription.clear(executionId);
4892
+ get extensionPhases() {
4893
+ return this.extensions.phases();
4894
+ }
4895
+ async closeExtensions() {
4896
+ await this.extensions.close();
4897
+ }
4898
+ };
4899
+ function adaptExtensionTool2(input) {
4900
+ const definition = input.definition;
4901
+ return {
4902
+ name: definition.name,
4903
+ description: definition.description,
4904
+ parameters: definition.parameters,
4905
+ execute: async (args, context, signal) => {
4906
+ const result = await definition.execute(args, signal);
4907
+ const content = JSON.parse(JSON.stringify(result.content));
4908
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3825
4909
  }
4910
+ };
4911
+ }
4912
+
4913
+ // src/runtime/configuration-snapshot.ts
4914
+ function resolveConfigurationSnapshot(registry, input) {
4915
+ const resolved = registry.resolveView(input.resourceView);
4916
+ const base = resolved.agents.find(({ name }) => name === input.definition.name);
4917
+ if (!base) {
4918
+ throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
4919
+ }
4920
+ const layer = input.definition.layer;
4921
+ const definition = applyDefinitionLayer(base, layer);
4922
+ const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
4923
+ const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
4924
+ const phases = resolvePhases(resolved.phases, definition.phases);
4925
+ const contexts = selectNamedResources(input.contexts ?? [], base.context, "Context");
4926
+ return {
4927
+ identity: input.identity,
4928
+ definition,
4929
+ resources: {
4930
+ tools,
4931
+ skills,
4932
+ ...phases ? { phases } : {},
4933
+ revisions: resolved.revisions,
4934
+ refs: {
4935
+ agent: selectedRefs(resolved.refs.agent, [base.name]),
4936
+ tool: selectedRefs(resolved.refs.tool, tools.map(({ name }) => name)),
4937
+ skill: selectedRefs(resolved.refs.skill, skills.map(({ name }) => name)),
4938
+ phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
4939
+ }
4940
+ },
4941
+ contexts,
4942
+ resourceView: input.resourceView,
4943
+ ...input.cwd === void 0 ? {} : { cwd: input.cwd },
4944
+ ...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
4945
+ ..."stream" in input && input.stream ? { model: layer?.model ?? input.model, stream: input.stream } : { model: layer?.model ?? input.model }
4946
+ };
4947
+ }
4948
+ function materializeConfigurationSnapshot(snapshot) {
4949
+ return {
4950
+ identity: snapshot.identity,
4951
+ definition: snapshot.definition,
4952
+ resources: {
4953
+ tools: snapshot.resources.tools,
4954
+ skills: snapshot.resources.skills,
4955
+ ...snapshot.resources.phases ? { phases: snapshot.resources.phases } : {},
4956
+ ...snapshot.contexts.length > 0 ? { contexts: snapshot.contexts } : {},
4957
+ resourceView: snapshot.resourceView,
4958
+ resourceRefs: [
4959
+ ...snapshot.resources.refs.agent,
4960
+ ...snapshot.resources.refs.tool,
4961
+ ...snapshot.resources.refs.skill,
4962
+ ...snapshot.resources.refs.phase
4963
+ ],
4964
+ resourceRevisions: snapshot.resources.revisions
4965
+ },
4966
+ ...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
4967
+ ...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
4968
+ ..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
4969
+ };
4970
+ }
4971
+ function applyDefinitionLayer(base, layer) {
4972
+ if (!layer) return { ...base };
4973
+ return {
4974
+ ...base,
4975
+ ...layer.description === void 0 ? {} : { description: layer.description },
4976
+ ...layer.content === void 0 ? {} : { content: layer.content },
4977
+ ...layer.model === void 0 ? {} : { model: layer.model },
4978
+ ...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
4979
+ ...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
4980
+ ...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
4981
+ };
4982
+ }
4983
+ function intersectNames(parent, layer, kind) {
4984
+ if (parent === void 0) return [...layer];
4985
+ const parentNames = new Set(parent);
4986
+ for (const name of layer) {
4987
+ if (!parentNames.has(name)) console.warn(`${kind} "${name}" is not available and will be skipped.`);
3826
4988
  }
3827
- clearMessage(runId, messageId) {
3828
- for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3829
- }
3830
- clearTool(runId, toolCallId) {
3831
- for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
4989
+ return layer.filter((name) => parentNames.has(name));
4990
+ }
4991
+ function intersectPhaseSelection(parent, layer) {
4992
+ if (!parent) return layer;
4993
+ const parentNames = new Set(parent.phaseIds);
4994
+ for (const name of layer.phaseIds) {
4995
+ if (!parentNames.has(name)) console.warn(`Phase "${name}" is not available and will be skipped.`);
3832
4996
  }
3833
- close() {
3834
- for (const subscriptions of this.subscriptions.values()) {
3835
- for (const subscription of subscriptions) subscription.stop();
3836
- }
3837
- this.subscriptions.clear();
4997
+ return {
4998
+ entryPhaseId: layer.entryPhaseId,
4999
+ phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
5000
+ };
5001
+ }
5002
+ function resolvePhases(candidates, selection) {
5003
+ const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
5004
+ if (selected.length === 0 && selection === void 0) return void 0;
5005
+ const entryPhaseId = selection?.entryPhaseId ?? null;
5006
+ if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
5007
+ console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
5008
+ return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
3838
5009
  }
3839
- };
5010
+ return {
5011
+ phases: new Map(selected.map((phase) => [phase.name, phase])),
5012
+ entryPhaseId
5013
+ };
5014
+ }
5015
+ function selectedRefs(refs, names) {
5016
+ const selected = new Set(names);
5017
+ return refs.filter(({ name }) => selected.has(name));
5018
+ }
3840
5019
 
3841
5020
  // src/runtime/durable-runtime.ts
3842
5021
  var DEFAULT_CONCURRENCY = 10;
@@ -3853,14 +5032,16 @@ var AgentRuntime = class _AgentRuntime {
3853
5032
  executions = /* @__PURE__ */ new Map();
3854
5033
  consumers = /* @__PURE__ */ new Map();
3855
5034
  transientEvents = new TransientRunEventHub();
5035
+ resources;
3856
5036
  heartbeat;
3857
5037
  pumping = false;
3858
5038
  closed = false;
3859
- constructor(options, owned) {
5039
+ constructor(options, owned, resources) {
3860
5040
  this.owned = owned;
3861
5041
  this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
3862
5042
  this.storeIncarnation = String(owned.lease.token).split(":")[0];
3863
5043
  this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
5044
+ this.resources = resources;
3864
5045
  }
3865
5046
  static async init(options) {
3866
5047
  if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
@@ -3868,10 +5049,38 @@ var AgentRuntime = class _AgentRuntime {
3868
5049
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
3869
5050
  if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
3870
5051
  const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
3871
- const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned);
3872
- runtime.startHeartbeat();
3873
- void runtime.pump();
3874
- return runtime;
5052
+ const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
5053
+ try {
5054
+ await runtime.resources.ensureCoreResources();
5055
+ await options.bootstrap?.(runtime.resources);
5056
+ runtime.startHeartbeat();
5057
+ void runtime.pump();
5058
+ return runtime;
5059
+ } catch (error) {
5060
+ await runtime.resources.closeExtensions().catch(() => void 0);
5061
+ await owned.sealAndReleaseOwner().catch(() => void 0);
5062
+ throw error;
5063
+ }
5064
+ }
5065
+ async loadAgents(input) {
5066
+ this.assertOpen();
5067
+ return this.resources.loadAgents(input);
5068
+ }
5069
+ async loadSkills(input) {
5070
+ this.assertOpen();
5071
+ return this.resources.loadSkills(input);
5072
+ }
5073
+ async loadPhases(input) {
5074
+ this.assertOpen();
5075
+ return this.resources.loadPhases(input);
5076
+ }
5077
+ async loadTools(input) {
5078
+ this.assertOpen();
5079
+ return this.resources.loadTools(input);
5080
+ }
5081
+ async unload(input) {
5082
+ this.assertOpen();
5083
+ return this.resources.unload(input);
3875
5084
  }
3876
5085
  async createAgent(config, options = {}) {
3877
5086
  this.assertOpen();
@@ -3931,6 +5140,7 @@ var AgentRuntime = class _AgentRuntime {
3931
5140
  this.executions.clear();
3932
5141
  this.transientEvents.close();
3933
5142
  for (const subscription of this.consumers.values()) subscription.controller.abort();
5143
+ await this.resources.closeExtensions();
3934
5144
  await this.owned.sealAndReleaseOwner();
3935
5145
  }
3936
5146
  async snapshot(runId) {
@@ -3988,7 +5198,7 @@ var AgentRuntime = class _AgentRuntime {
3988
5198
  let executionRevision = run.revision;
3989
5199
  try {
3990
5200
  const agent = await this.requireAgent(run.agentId);
3991
- const token = run.pinnedConfigToken ?? agent.currentConfigToken;
5201
+ let token = run.pinnedConfigToken ?? agent.currentConfigToken;
3992
5202
  if (!token) {
3993
5203
  await this.failQueued(run, "Agent has no Config Token.");
3994
5204
  return;
@@ -4008,42 +5218,58 @@ var AgentRuntime = class _AgentRuntime {
4008
5218
  await this.failQueued(run, resolution.reason);
4009
5219
  return;
4010
5220
  }
5221
+ let resolvedConfig = resolution.config;
5222
+ if (isAgentConfiguration(resolvedConfig)) {
5223
+ try {
5224
+ const snapshot = this.materializeConfig(resolvedConfig);
5225
+ token = await this.commands.storeSnapshot({
5226
+ agent,
5227
+ config: snapshot,
5228
+ operationId: `run-snapshot:${run.id}`
5229
+ });
5230
+ resolvedConfig = snapshot;
5231
+ } catch (error) {
5232
+ await this.failQueued(run, error instanceof Error ? error.message : "Configuration Snapshot failed.");
5233
+ return;
5234
+ }
5235
+ }
4011
5236
  const executionId = createId("exec");
4012
5237
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
5238
+ executionRevision = claim.run.revision;
4013
5239
  const controller = new AbortController();
4014
5240
  this.executions.set(run.id, { controller, executionId });
4015
- const config = resolution.config;
4016
- const assembly = await assembleExtensions(config);
4017
- const executionConfig = {
4018
- ...config,
4019
- context: assembly.context,
4020
- ...assembly.beforeToolCall ? { beforeToolCall: assembly.beforeToolCall } : {},
4021
- ...assembly.afterToolCall ? { afterToolCall: assembly.afterToolCall } : {}
4022
- };
4023
- executionRevision = claim.run.revision;
5241
+ const config = resolvedConfig;
5242
+ const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
4024
5243
  let toolQueue = Promise.resolve();
4025
5244
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
4026
5245
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
4027
5246
  const executionContext = projectModelContext({
4028
5247
  context: {
4029
- ...executionConfig.context,
4030
- phases: normalizePhaseRegistry(executionConfig.context.phases)
5248
+ ...assembly.context,
5249
+ phases: normalizePhaseRegistry(assembly.context.phases)
4031
5250
  },
4032
5251
  messages: claim.history,
4033
5252
  agentId: run.agentId,
4034
5253
  runId: run.id
4035
5254
  });
5255
+ const executionTools = {
5256
+ tools: assembly.context.tools,
5257
+ beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
5258
+ afterToolCall: assembly.afterToolCall ?? config.afterToolCall
5259
+ };
4036
5260
  const result = await executeOnce({
4037
5261
  canonicalMessages: executionContext.messages,
4038
5262
  context: executionContext,
4039
5263
  execution: {
4040
5264
  agentId: run.agentId,
4041
5265
  runId: run.id,
4042
- executionId: claim.execution.executionId
5266
+ executionId: claim.execution.executionId,
5267
+ ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
5268
+ ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
4043
5269
  },
4044
5270
  model,
4045
5271
  stream,
4046
- maxAttempts: executionConfig.maxAttempts,
5272
+ maxAttempts: config.maxAttempts,
4047
5273
  checkpoint: claim.run.checkpoint,
4048
5274
  signal: controller.signal,
4049
5275
  beforePhase: assembly.beforePhase,
@@ -4068,9 +5294,10 @@ var AgentRuntime = class _AgentRuntime {
4068
5294
  const task = toolQueue.then(async () => {
4069
5295
  const execution = await this.executeToolBatch({
4070
5296
  run,
5297
+ agentMetadata: agent.metadata,
4071
5298
  execution: claim.execution,
4072
5299
  expectedRevision: executionRevision,
4073
- config: executionConfig,
5300
+ toolConfig: executionTools,
4074
5301
  toolCalls: [toolCall],
4075
5302
  signal: controller.signal
4076
5303
  });
@@ -4084,9 +5311,10 @@ var AgentRuntime = class _AgentRuntime {
4084
5311
  const task = toolQueue.then(async () => {
4085
5312
  const execution = await this.executeToolBatch({
4086
5313
  run,
5314
+ agentMetadata: agent.metadata,
4087
5315
  execution: claim.execution,
4088
5316
  expectedRevision: executionRevision,
4089
- config: executionConfig,
5317
+ toolConfig: executionTools,
4090
5318
  toolCalls,
4091
5319
  signal: controller.signal
4092
5320
  });
@@ -4165,7 +5393,7 @@ var AgentRuntime = class _AgentRuntime {
4165
5393
  return { results, revision };
4166
5394
  }
4167
5395
  async executeReservedTool(input) {
4168
- const durableTool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
5396
+ const durableTool = input.toolConfig.tools.find((candidate) => candidate.name === input.toolCall.name);
4169
5397
  const providerToolCallId = input.toolCall.id;
4170
5398
  const toolCallId = input.tool.id;
4171
5399
  let revision = input.expectedRevision;
@@ -4178,6 +5406,8 @@ var AgentRuntime = class _AgentRuntime {
4178
5406
  const context = {
4179
5407
  agentId: input.run.agentId,
4180
5408
  runId: input.run.id,
5409
+ ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
5410
+ ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
4181
5411
  toolCallId,
4182
5412
  reportProgress: (progress) => {
4183
5413
  const active = this.executions.get(input.run.id);
@@ -4200,8 +5430,8 @@ var AgentRuntime = class _AgentRuntime {
4200
5430
  }
4201
5431
  };
4202
5432
  try {
4203
- if (input.config.beforeToolCall) {
4204
- const decision = await input.config.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
5433
+ if (input.toolConfig.beforeToolCall) {
5434
+ const decision = await input.toolConfig.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
4205
5435
  if (!decision.allow) {
4206
5436
  const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: decision.reason } });
4207
5437
  return { result: protocolFailure(decision.reason), revision: failed.run.revision };
@@ -4218,7 +5448,7 @@ var AgentRuntime = class _AgentRuntime {
4218
5448
  try {
4219
5449
  let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
4220
5450
  assertToolExecutionResult(result);
4221
- if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
5451
+ if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
4222
5452
  assertToolExecutionResult(result);
4223
5453
  const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
4224
5454
  return {
@@ -4321,6 +5551,10 @@ var AgentRuntime = class _AgentRuntime {
4321
5551
  assertOpen() {
4322
5552
  if (this.closed) throw new RuntimeError("runtime_closed", null);
4323
5553
  }
5554
+ materializeConfig(config) {
5555
+ if (!isAgentConfiguration(config)) return config;
5556
+ return materializeConfigurationSnapshot(resolveConfigurationSnapshot(this.resources, config));
5557
+ }
4324
5558
  startHeartbeat() {
4325
5559
  this.heartbeat = setInterval(() => {
4326
5560
  void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
@@ -4384,10 +5618,10 @@ function boundaryFromSnapshot(snapshot) {
4384
5618
  }
4385
5619
  }
4386
5620
  function delay(ms) {
4387
- return new Promise((resolve6) => setTimeout(resolve6, ms));
5621
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
4388
5622
  }
4389
5623
  function normalizePhaseRegistry(registry) {
4390
- if (registry?.phases.has(DEFAULT_PHASE_ID)) {
5624
+ if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
4391
5625
  throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
4392
5626
  }
4393
5627
  return {
@@ -4411,13 +5645,13 @@ function toJsonValue(value) {
4411
5645
  return value;
4412
5646
  }
4413
5647
  function deferred2() {
4414
- let resolve6;
5648
+ let resolve7;
4415
5649
  let reject;
4416
5650
  const promise = new Promise((res, rej) => {
4417
- resolve6 = (value) => res(value);
5651
+ resolve7 = (value) => res(value);
4418
5652
  reject = rej;
4419
5653
  });
4420
- return { promise, resolve: resolve6, reject };
5654
+ return { promise, resolve: resolve7, reject };
4421
5655
  }
4422
5656
 
4423
5657
  // src/runtime/durable-store.ts
@@ -5868,236 +7102,6 @@ function assertOwnerInput(ownerId, leaseMs) {
5868
7102
  if (!Number.isFinite(leaseMs) || leaseMs <= 0) throw new TypeError("leaseMs must be a positive finite number");
5869
7103
  }
5870
7104
 
5871
- // src/harness/skills.ts
5872
- import { existsSync as existsSync2, statSync as statSync2 } from "fs";
5873
- import { readdir as readdir2 } from "fs/promises";
5874
- import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
5875
- var SKILL_MARKER = "SKILL.md";
5876
- function resolveSkillPath(input) {
5877
- const resolved = resolve4(input);
5878
- return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
5879
- }
5880
- async function loadSkill(path) {
5881
- const resolved = resolveSkillPath(path);
5882
- let loaded;
5883
- try {
5884
- loaded = await loadMarkdown(resolved);
5885
- } catch (error) {
5886
- if (!(error instanceof FrontmatterParseError)) throw error;
5887
- warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
5888
- throw new ResourceMetadataError("parse_failed", error.message);
5889
- }
5890
- const { frontmatter, body } = loaded;
5891
- const metadata = frontmatter;
5892
- const id = inferResourceName(resolved, SKILL_MARKER);
5893
- const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
5894
- const name = frontmatterName || id;
5895
- const description = validateDescription(metadata.description);
5896
- const diagnostics = validateResourceId(id);
5897
- if (frontmatterName && frontmatterName !== id) {
5898
- diagnostics.push(...validateResourceName(frontmatterName, id));
5899
- }
5900
- diagnostics.push(...description.warnings);
5901
- warnResourceDiagnostics("skill", resolved, diagnostics);
5902
- if (description.missing) {
5903
- throw new ResourceMetadataError("invalid_metadata", "description is required");
5904
- }
5905
- return {
5906
- name,
5907
- description: description.description,
5908
- filePath: resolved,
5909
- baseDir: dirname4(resolved),
5910
- content: body,
5911
- disableModelInvocation: metadata["disable-model-invocation"] === true
5912
- };
5913
- }
5914
- async function loadSkills(targetPath) {
5915
- const skillsDir = resolve4(targetPath);
5916
- if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
5917
- try {
5918
- return [await loadSkill(skillsDir)];
5919
- } catch (error) {
5920
- if (error instanceof ResourceMetadataError) return [];
5921
- throw error;
5922
- }
5923
- }
5924
- if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
5925
- try {
5926
- return [await loadSkill(skillsDir)];
5927
- } catch (error) {
5928
- if (error instanceof ResourceMetadataError) return [];
5929
- throw error;
5930
- }
5931
- }
5932
- const entries = await readdir2(skillsDir, { withFileTypes: true });
5933
- const skills = [];
5934
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
5935
- if (!entry.isDirectory()) continue;
5936
- const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
5937
- if (!existsSync2(skillFile)) continue;
5938
- try {
5939
- const skill = await loadSkill(skillFile);
5940
- skills.push(skill);
5941
- } catch (error) {
5942
- if (error instanceof ResourceMetadataError) continue;
5943
- console.warn(`Failed to load skill "${entry.name}":`, error);
5944
- }
5945
- }
5946
- return skills;
5947
- }
5948
-
5949
- // src/extensions/loader.ts
5950
- import { existsSync as existsSync3, readFileSync } from "fs";
5951
- import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
5952
- import { fileURLToPath } from "url";
5953
- import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
5954
- import { createJiti } from "jiti";
5955
- function isExtensionFile(path) {
5956
- const extension = extname2(path).toLowerCase();
5957
- return extension === ".ts" || extension === ".js";
5958
- }
5959
- function isSyntheticPath(path) {
5960
- return path.startsWith("<") && path.endsWith(">");
5961
- }
5962
- function readManifestSync(dir) {
5963
- const manifestPath = join3(dir, "package.json");
5964
- if (!existsSync3(manifestPath)) {
5965
- return void 0;
5966
- }
5967
- try {
5968
- const content = readFileSync(manifestPath, "utf8");
5969
- const pkg = JSON.parse(content);
5970
- const rowan = pkg.rowan;
5971
- if (!rowan) return void 0;
5972
- return {
5973
- entry: rowan.extensions?.[0],
5974
- phase: rowan.phase
5975
- };
5976
- } catch {
5977
- return void 0;
5978
- }
5979
- }
5980
- function jitiAliases() {
5981
- const moduleUrl = import.meta.url;
5982
- if (!moduleUrl) {
5983
- return {};
5984
- }
5985
- const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
5986
- if (!existsSync3(agentSourcePath)) {
5987
- return {};
5988
- }
5989
- return { "@rowan-agent/agent": agentSourcePath };
5990
- }
5991
- var sharedJiti;
5992
- function getJiti() {
5993
- sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
5994
- fsCache: false,
5995
- moduleCache: false,
5996
- tryNative: false,
5997
- alias: jitiAliases()
5998
- });
5999
- return sharedJiti;
6000
- }
6001
- async function loadExtensionModule(extensionPath) {
6002
- const jiti = getJiti();
6003
- const module = await jiti.import(extensionPath, { default: true });
6004
- return typeof module === "function" ? module : void 0;
6005
- }
6006
- async function readPackageManifest(path) {
6007
- if (!existsSync3(path)) {
6008
- return void 0;
6009
- }
6010
- const content = await readFile3(path, "utf8");
6011
- return JSON.parse(content);
6012
- }
6013
- async function resolveExtensionEntries(dir) {
6014
- const manifest = await readPackageManifest(join3(dir, "package.json"));
6015
- const declared = manifest?.rowan?.extensions;
6016
- if (declared && declared.length > 0) {
6017
- return declared.map((entry) => resolve5(dir, entry));
6018
- }
6019
- for (const name of ["index.ts", "index.js"]) {
6020
- const entry = join3(dir, name);
6021
- if (existsSync3(entry)) {
6022
- return [entry];
6023
- }
6024
- }
6025
- return void 0;
6026
- }
6027
- async function discoverExtensionsInDir(dir) {
6028
- const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
6029
- if (error.code === "ENOENT") {
6030
- return [];
6031
- }
6032
- throw error;
6033
- });
6034
- const paths = [];
6035
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
6036
- const entryPath = join3(dir, entry.name);
6037
- if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
6038
- paths.push(entryPath);
6039
- continue;
6040
- }
6041
- if (entry.isDirectory() || entry.isSymbolicLink()) {
6042
- const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
6043
- if (entry.isDirectory() || info?.isDirectory()) {
6044
- const resolvedEntries = await resolveExtensionEntries(entryPath);
6045
- if (resolvedEntries) {
6046
- paths.push(...resolvedEntries);
6047
- }
6048
- }
6049
- }
6050
- }
6051
- return paths;
6052
- }
6053
- function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
6054
- const resolvedCwd = resolve5(cwd);
6055
- const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
6056
- const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
6057
- const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
6058
- const manifest = readManifestSync(manifestDir);
6059
- return {
6060
- path: resolvedPath,
6061
- name,
6062
- factory,
6063
- manifest
6064
- };
6065
- }
6066
- async function loadExtensions(paths, cwd) {
6067
- const extensions = [];
6068
- const errors = [];
6069
- const resolvedCwd = resolve5(cwd);
6070
- for (const path of paths) {
6071
- const resolvedPath = resolve5(resolvedCwd, path);
6072
- try {
6073
- const factory = await loadExtensionModule(resolvedPath);
6074
- if (!factory) {
6075
- errors.push({
6076
- path: resolvedPath,
6077
- error: `Extension does not export a valid factory function: ${path}`
6078
- });
6079
- continue;
6080
- }
6081
- extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
6082
- } catch (error) {
6083
- errors.push({
6084
- path: resolvedPath,
6085
- error: error instanceof Error ? error.message : String(error)
6086
- });
6087
- }
6088
- }
6089
- return { extensions, errors };
6090
- }
6091
- async function loadExtensionsFromPath(path) {
6092
- const resolvedPath = resolve5(path);
6093
- const info = await stat2(resolvedPath);
6094
- if (!info.isDirectory()) {
6095
- return loadExtensions([resolvedPath], dirname5(resolvedPath));
6096
- }
6097
- const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
6098
- return loadExtensions(entries, resolvedPath);
6099
- }
6100
-
6101
7105
  // src/index.ts
6102
7106
  function createCoreTools2(input = {}) {
6103
7107
  return createCoreTools(input).map((tool) => ({
@@ -6112,14 +7116,23 @@ function createCoreTools2(input = {}) {
6112
7116
  }
6113
7117
  export {
6114
7118
  AgentRuntime,
7119
+ ExtensionLifetimeError,
6115
7120
  InMemoryConfigProvider,
6116
7121
  InMemoryStore,
7122
+ ResourceRegistry,
7123
+ ResourceRegistryError,
7124
+ RuntimeBootstrapRegistry,
6117
7125
  RuntimeError,
7126
+ RuntimeExtensionLifetime,
6118
7127
  SqliteStore,
6119
7128
  brandConfigToken,
6120
7129
  createCoreTools2 as createCoreTools,
6121
7130
  isRuntimeError,
6122
7131
  loadExtensionsFromPath as loadExtensions,
6123
7132
  loadPhases,
6124
- loadSkills
7133
+ loadSkills,
7134
+ materializeConfigurationSnapshot,
7135
+ parseAgentDefinition,
7136
+ parseFrontmatter,
7137
+ resolveConfigurationSnapshot
6125
7138
  };