@rowan-agent/agent 0.8.5 → 0.8.7

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 },
@@ -1020,14 +1045,17 @@ import { dirname as dirname3, join, resolve as resolve3 } from "path";
1020
1045
  import { parseModelRef } from "@rowan-agent/models";
1021
1046
  function assertAgentDefinition(value) {
1022
1047
  if (!isRecord(value)) throw new TypeError("config.definition is invalid");
1048
+ if ("content" in value) throw new TypeError("definition.content is not supported; use definition.prompt");
1049
+ if ("context" in value) throw new TypeError("definition.context is not supported; use definition.contexts");
1023
1050
  requiredString(value.name, "definition.name");
1024
1051
  requiredString(value.description, "definition.description");
1025
- requiredString(value.content, "definition.content");
1052
+ requiredString(value.prompt, "definition.prompt");
1026
1053
  optionalStringList(value.tools, "definition.tools");
1027
1054
  optionalStringList(value.skills, "definition.skills");
1028
- optionalStringList(value.phases, "definition.phases");
1029
- optionalStringList(value.extensions, "definition.extensions");
1030
- optionalString(value.entryPhase, "definition.entryPhase");
1055
+ optionalPhaseRegistrySelection(value.phases, "definition.phases");
1056
+ optionalStringList(value.contexts, "definition.contexts");
1057
+ if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
1058
+ if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
1031
1059
  if (value.model !== void 0) {
1032
1060
  if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
1033
1061
  throw new TypeError("definition.model must be a valid model reference");
@@ -1038,25 +1066,31 @@ function parseAgentDefinition(raw) {
1038
1066
  const { frontmatter, body } = parseFrontmatter(raw);
1039
1067
  return normalizeAgentDefinition(frontmatter, body);
1040
1068
  }
1041
- function normalizeAgentDefinition(frontmatter, content, options = {}) {
1069
+ function normalizeAgentDefinition(frontmatter, prompt, options = {}) {
1070
+ if (frontmatter.content !== void 0) throw new TypeError("content is not supported; use prompt and the document body");
1071
+ if (frontmatter.context !== void 0) throw new TypeError("context is not supported; use contexts");
1042
1072
  const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
1043
1073
  const description = requiredString(frontmatter.description, "description");
1044
- const normalizedContent = requiredString(content, "content");
1074
+ const normalizedPrompt = requiredString(prompt, "prompt");
1045
1075
  const tools = optionalStringList(frontmatter.tools, "tools");
1046
1076
  const skills = optionalStringList(frontmatter.skills, "skills");
1047
- const phases = optionalStringList(frontmatter.phases, "phases");
1048
- const extensions = optionalStringList(frontmatter.extensions, "extensions");
1049
- const entryPhase = optionalString(frontmatter.entryPhase, "entryPhase");
1077
+ const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
1078
+ if (frontmatter.extensions !== void 0) {
1079
+ throw new TypeError("extensions is not supported; Extensions are Runtime-global");
1080
+ }
1081
+ const contexts = optionalStringList(frontmatter.contexts, "contexts");
1082
+ if (frontmatter.entryPhase !== void 0) {
1083
+ throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
1084
+ }
1050
1085
  const model = optionalModel(frontmatter.model);
1051
1086
  return {
1052
1087
  name,
1053
1088
  description,
1054
- content: normalizedContent,
1089
+ prompt: normalizedPrompt,
1055
1090
  ...tools ? { tools } : {},
1056
1091
  ...skills ? { skills } : {},
1057
1092
  ...phases ? { phases } : {},
1058
- ...extensions ? { extensions } : {},
1059
- ...entryPhase ? { entryPhase } : {},
1093
+ ...contexts ? { contexts } : {},
1060
1094
  ...model ? { model } : {}
1061
1095
  };
1062
1096
  }
@@ -1089,6 +1123,22 @@ function optionalStringList(value, field) {
1089
1123
  }
1090
1124
  return names;
1091
1125
  }
1126
+ function optionalPhaseRegistrySelection(value, field) {
1127
+ if (value === void 0) return void 0;
1128
+ if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
1129
+ if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
1130
+ if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
1131
+ const entryPhaseId = value.entryPhaseId;
1132
+ if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
1133
+ throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
1134
+ }
1135
+ const phaseIds = optionalStringList(value.phaseIds, `${field}.phaseIds`);
1136
+ if (!phaseIds) throw new TypeError(`${field}.phaseIds is required`);
1137
+ return {
1138
+ entryPhaseId: entryPhaseId === null ? null : entryPhaseId.trim(),
1139
+ phaseIds
1140
+ };
1141
+ }
1092
1142
  function optionalModel(value) {
1093
1143
  const source = optionalString(value, "model");
1094
1144
  if (!source) return void 0;
@@ -1227,7 +1277,7 @@ async function loadPhase(targetPath) {
1227
1277
  isolated: metadata.isolated,
1228
1278
  filePath: resolved,
1229
1279
  baseDir,
1230
- content: definition.content,
1280
+ content: definition.prompt,
1231
1281
  model: definition.model
1232
1282
  };
1233
1283
  const codePath = await discoverPhaseCode(baseDir);
@@ -1693,7 +1743,7 @@ function createTerminalOutcome(phase, output, transcript) {
1693
1743
  return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
1694
1744
  }
1695
1745
  function buildToolsWithRouting(config, availablePhases) {
1696
- const tools = [...config.context.tools];
1746
+ const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
1697
1747
  if (availablePhases.length > 0) {
1698
1748
  tools.push(createRouteTool(availablePhases));
1699
1749
  }
@@ -2158,7 +2208,7 @@ async function withRetry(fn, options = {}) {
2158
2208
  }
2159
2209
  const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2160
2210
  options.onRetry?.(attempt, error, delayMs);
2161
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
2211
+ await new Promise((resolve7) => setTimeout(resolve7, delayMs));
2162
2212
  if (options.signal?.aborted) {
2163
2213
  throw lastError;
2164
2214
  }
@@ -2663,6 +2713,9 @@ function isContinuation(value) {
2663
2713
  }
2664
2714
 
2665
2715
  // src/runtime/contracts.ts
2716
+ function isAgentConfiguration(config) {
2717
+ return !("resources" in config);
2718
+ }
2666
2719
  var METADATA_LIMIT = 64 * 1024;
2667
2720
  var IDENTITY_LIMIT = 256;
2668
2721
  function isRecord3(value) {
@@ -2715,6 +2768,42 @@ function assertAgentConfig(config) {
2715
2768
  throw new TypeError("config.resources is invalid");
2716
2769
  }
2717
2770
  for (const tool of config.resources.tools) projectToolDefinition(tool);
2771
+ const names = /* @__PURE__ */ new Set();
2772
+ for (const context of config.resources.contexts ?? []) {
2773
+ if (typeof context.name !== "string" || context.name.trim() === "") {
2774
+ throw new TypeError("Context candidate name must be non-empty");
2775
+ }
2776
+ if (names.has(context.name)) throw new TypeError(`Duplicate Context candidate "${context.name}".`);
2777
+ names.add(context.name);
2778
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2779
+ }
2780
+ }
2781
+ function assertAgentConfigRequest(config) {
2782
+ if (!isAgentConfiguration(config)) {
2783
+ assertAgentConfig(config);
2784
+ return;
2785
+ }
2786
+ if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2787
+ assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2788
+ if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
2789
+ throw new TypeError("config.definition.name must be non-empty");
2790
+ }
2791
+ if (config.definition.layer && "content" in config.definition.layer) {
2792
+ throw new TypeError("definition.layer.content is not supported; use definition.layer.prompt");
2793
+ }
2794
+ const view = config.resourceView;
2795
+ if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
2796
+ throw new TypeError("config.resourceView is invalid");
2797
+ }
2798
+ for (const [kind, ids] of Object.entries(view)) {
2799
+ for (const sourceId of ids) {
2800
+ if (typeof sourceId !== "string" || sourceId.trim() === "") throw new TypeError(`config.resourceView.${kind} contains an invalid Source ID`);
2801
+ }
2802
+ }
2803
+ for (const context of config.contexts ?? []) {
2804
+ if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
2805
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2806
+ }
2718
2807
  }
2719
2808
  function assertToolExecutionResult(value) {
2720
2809
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -2808,21 +2897,92 @@ function validateConfigResolution(agentId, value) {
2808
2897
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2809
2898
  }
2810
2899
  function snapshotConfig(config) {
2900
+ if (isAgentConfiguration(config)) return snapshotConfiguration(config);
2811
2901
  const definition = Object.freeze({
2812
2902
  ...config.definition,
2813
2903
  ...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
2814
2904
  ...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
2815
- ...config.definition.phases ? { phases: Object.freeze([...config.definition.phases]) } : {},
2816
- ...config.definition.extensions ? { extensions: Object.freeze([...config.definition.extensions]) } : {}
2905
+ ...config.definition.phases ? {
2906
+ phases: Object.freeze({
2907
+ entryPhaseId: config.definition.phases.entryPhaseId,
2908
+ phaseIds: Object.freeze([...config.definition.phases.phaseIds])
2909
+ })
2910
+ } : {},
2911
+ ...config.definition.contexts ? { contexts: Object.freeze([...config.definition.contexts]) } : {}
2817
2912
  });
2818
2913
  const resources = Object.freeze({
2819
2914
  ...config.resources,
2820
2915
  tools: Object.freeze([...config.resources.tools]),
2821
2916
  skills: Object.freeze([...config.resources.skills]),
2822
- ...config.resources.extensions ? { extensions: Object.freeze([...config.resources.extensions]) } : {}
2917
+ ...config.resources.resourceView ? {
2918
+ resourceView: Object.freeze({
2919
+ agents: Object.freeze([...config.resources.resourceView.agents]),
2920
+ tools: Object.freeze([...config.resources.resourceView.tools]),
2921
+ skills: Object.freeze([...config.resources.resourceView.skills]),
2922
+ phases: Object.freeze([...config.resources.resourceView.phases])
2923
+ })
2924
+ } : {},
2925
+ ...config.resources.resourceRefs ? { resourceRefs: Object.freeze(config.resources.resourceRefs.map((ref) => Object.freeze({ ...ref }))) } : {},
2926
+ ...config.resources.resourceRevisions ? {
2927
+ resourceRevisions: Object.freeze(Object.fromEntries(
2928
+ Object.entries(config.resources.resourceRevisions).map(([kind, revisions]) => [kind, Object.freeze([...revisions])])
2929
+ ))
2930
+ } : {},
2931
+ ...config.resources.contexts ? {
2932
+ contexts: Object.freeze(config.resources.contexts.map((context) => Object.freeze({
2933
+ name: context.name,
2934
+ value: snapshotJsonValue(context.value)
2935
+ })))
2936
+ } : {}
2823
2937
  });
2824
2938
  return Object.freeze({ ...config, definition, resources });
2825
2939
  }
2940
+ function snapshotConfiguration(config) {
2941
+ const layer = config.definition.layer;
2942
+ const definition = Object.freeze({
2943
+ name: config.definition.name,
2944
+ ...layer === void 0 ? {} : {
2945
+ layer: Object.freeze({
2946
+ ...layer,
2947
+ ...layer.tools ? { tools: Object.freeze([...layer.tools]) } : {},
2948
+ ...layer.skills ? { skills: Object.freeze([...layer.skills]) } : {},
2949
+ ...layer.phases ? {
2950
+ phases: Object.freeze({
2951
+ entryPhaseId: layer.phases.entryPhaseId,
2952
+ phaseIds: Object.freeze([...layer.phases.phaseIds])
2953
+ })
2954
+ } : {}
2955
+ })
2956
+ }
2957
+ });
2958
+ const resourceView = Object.freeze({
2959
+ agents: Object.freeze([...config.resourceView.agents]),
2960
+ tools: Object.freeze([...config.resourceView.tools]),
2961
+ skills: Object.freeze([...config.resourceView.skills]),
2962
+ phases: Object.freeze([...config.resourceView.phases])
2963
+ });
2964
+ return Object.freeze({
2965
+ ...config,
2966
+ definition,
2967
+ resourceView,
2968
+ ...config.contexts ? {
2969
+ contexts: Object.freeze(config.contexts.map((context) => Object.freeze({
2970
+ name: context.name,
2971
+ value: snapshotJsonValue(context.value)
2972
+ })))
2973
+ } : {}
2974
+ });
2975
+ }
2976
+ function snapshotJsonValue(value) {
2977
+ return deepFreeze(JSON.parse(canonicalJson(value)));
2978
+ }
2979
+ function deepFreeze(value) {
2980
+ if (value && typeof value === "object") {
2981
+ for (const child of Object.values(value)) deepFreeze(child);
2982
+ Object.freeze(value);
2983
+ }
2984
+ return value;
2985
+ }
2826
2986
  function assertConfigIdentity(identity) {
2827
2987
  if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
2828
2988
  assertUtf8ByteLimit(identity, 256, "config.identity");
@@ -2867,7 +3027,7 @@ var ConfigCommandService = class {
2867
3027
  configs;
2868
3028
  storeIncarnation;
2869
3029
  async createAgent(input) {
2870
- assertAgentConfig(input.config);
3030
+ assertAgentConfigRequest(input.config);
2871
3031
  assertIdentity(input.config.identity);
2872
3032
  const reserved = await this.store.reserveAgent({
2873
3033
  idempotencyKey: input.idempotencyKey,
@@ -2889,7 +3049,7 @@ var ConfigCommandService = class {
2889
3049
  return reserved.id;
2890
3050
  }
2891
3051
  async updateAgentConfig(input) {
2892
- assertAgentConfig(input.config);
3052
+ assertAgentConfigRequest(input.config);
2893
3053
  assertIdentity(input.config.identity);
2894
3054
  const agent = await this.findAgent(input.agentId);
2895
3055
  const operationId = this.operationId("update_agent_config", input.agentId, input.idempotencyKey);
@@ -2916,6 +3076,23 @@ var ConfigCommandService = class {
2916
3076
  const resolution = validateConfigResolution(input.agent.id, result);
2917
3077
  return resolution;
2918
3078
  }
3079
+ /** Persist one resolved Configuration Snapshot under a Run-specific token.
3080
+ * The durable Run pins this token at claim time, so later Source replacement
3081
+ * cannot mutate an active or input-waiting Run. */
3082
+ async storeSnapshot(input) {
3083
+ assertAgentConfigRequest(input.config);
3084
+ const result = await this.put({
3085
+ agentId: input.agent.id,
3086
+ agentMetadata: input.agent.metadata,
3087
+ config: input.config,
3088
+ operationId: input.operationId,
3089
+ signal: input.signal
3090
+ });
3091
+ if (result.kind === "identity_conflict") {
3092
+ throw idempotencyConflict("update_agent_config", input.operationId);
3093
+ }
3094
+ return brandConfigToken(result.token);
3095
+ }
2919
3096
  async findAgent(agentId) {
2920
3097
  const agent = (await this.store.listAgents()).find((candidate) => candidate.id === agentId);
2921
3098
  if (!agent) throw new RuntimeError("agent_not_found", { agentId });
@@ -3149,156 +3326,400 @@ function jsonText(value) {
3149
3326
  return typeof value === "string" ? value : JSON.stringify(value) ?? "null";
3150
3327
  }
3151
3328
 
3152
- // src/extensions/hooks.ts
3153
- var HookError = class extends Error {
3154
- constructor(eventType, message, cause) {
3155
- super(message, cause === void 0 ? void 0 : { cause });
3156
- this.eventType = eventType;
3157
- this.name = "HookError";
3158
- }
3159
- eventType;
3160
- };
3161
- var HooksManager = class {
3162
- handlers = /* @__PURE__ */ new Map();
3163
- on(eventType, handler) {
3164
- const handlers = this.handlers.get(eventType) ?? [];
3165
- handlers.push(handler);
3166
- this.handlers.set(eventType, handlers);
3167
- }
3168
- off(eventType, handler) {
3169
- const handlers = this.handlers.get(eventType);
3170
- if (!handlers) return;
3171
- const index = handlers.indexOf(handler);
3172
- if (index >= 0) handlers.splice(index, 1);
3173
- }
3174
- async emit(eventType, event) {
3175
- const handlers = this.handlers.get(eventType);
3176
- if (!handlers?.length) return;
3177
- const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
3178
- const failure = results.find((result) => result.status === "rejected");
3179
- if (failure) {
3180
- throw new HookError(
3181
- eventType,
3182
- `${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
3183
- failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
3184
- );
3185
- }
3329
+ // src/runtime/extensions.ts
3330
+ function assembleRegisteredExtensions(config, runner) {
3331
+ const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3332
+ const tools = [...config.resources.tools];
3333
+ const names = new Set(tools.map((tool) => tool.name));
3334
+ for (const tool of extensionTools) {
3335
+ if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
3336
+ names.add(tool.name);
3337
+ tools.push(tool);
3186
3338
  }
3187
- async emitFirst(eventType, event) {
3188
- const handlers = this.handlers.get(eventType);
3189
- if (!handlers?.length) return void 0;
3190
- for (const handler of handlers) {
3191
- try {
3192
- const result = await handler(event);
3193
- if (result !== void 0) return result;
3194
- } catch (error) {
3195
- throw new HookError(
3196
- eventType,
3197
- `Handler failed for "${eventType}"`,
3198
- error instanceof Error ? error : new Error(String(error))
3199
- );
3200
- }
3339
+ const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3340
+ const phases = mergePhases(config.resources.phases, extensionPhases);
3341
+ const context = resolveDefinitionContext(config, { tools, phases });
3342
+ return {
3343
+ context,
3344
+ beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3345
+ afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3346
+ beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
3347
+ beforeToolCall: async (input) => {
3348
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3349
+ const decision = await runner.emitBeforeToolCall(tool, input.args);
3350
+ return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
3351
+ },
3352
+ afterToolCall: async (input) => {
3353
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3354
+ const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3355
+ return fromLoopResult(result);
3356
+ },
3357
+ setContext: (context2) => {
3358
+ runner.currentContext = context2;
3201
3359
  }
3202
- return void 0;
3360
+ };
3361
+ }
3362
+ function resolveDefinitionContext(config, assembled = {}) {
3363
+ const tools = selectNamedResources(
3364
+ assembled.tools ?? config.resources.tools,
3365
+ config.definition.tools,
3366
+ "Tool"
3367
+ );
3368
+ const skills = selectNamedResources(
3369
+ config.resources.skills,
3370
+ config.definition.skills,
3371
+ "Skill"
3372
+ );
3373
+ const contexts = selectNamedResources(
3374
+ config.resources.contexts ?? [],
3375
+ config.definition.contexts,
3376
+ "Context"
3377
+ );
3378
+ const candidateRegistry = assembled.phases ?? config.resources.phases;
3379
+ const selectedPhases = selectNamedResources(
3380
+ [...candidateRegistry?.phases.values() ?? []],
3381
+ config.definition.phases?.phaseIds,
3382
+ "Phase"
3383
+ );
3384
+ const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
3385
+ const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
3386
+ const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
3387
+ if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
3388
+ console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
3203
3389
  }
3204
- };
3205
-
3206
- // src/extensions/runner.ts
3207
- import { execFile } from "child_process";
3208
- import {
3209
- registerModel,
3210
- unregisterProviderModels,
3211
- registerApiProvider
3212
- } from "@rowan-agent/models";
3213
-
3214
- // src/extensions/types.ts
3215
- function createSourceInfo(extensionPath, options = {}) {
3216
- const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3217
- const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3218
3390
  return {
3219
- source,
3220
- baseDir: options.baseDir,
3221
- displayName
3391
+ systemPrompt: [config.definition.prompt, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
3392
+ tools,
3393
+ skills,
3394
+ phases: { phases, entryPhaseId }
3222
3395
  };
3223
3396
  }
3224
- function createExtensionRuntime() {
3225
- const state = {};
3226
- const assertActive = () => {
3227
- if (state.staleMessage) {
3228
- throw new Error(state.staleMessage);
3229
- }
3397
+ function mergePhases(base, extension) {
3398
+ if (!base && extension.phases.size === 0) return void 0;
3399
+ const phases = new Map(base?.phases ?? []);
3400
+ for (const [name, phase] of extension.phases) {
3401
+ if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
3402
+ phases.set(name, phase);
3403
+ }
3404
+ return {
3405
+ phases,
3406
+ entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
3230
3407
  };
3408
+ }
3409
+ function adaptExtensionTool(input) {
3410
+ const definition = input.definition;
3231
3411
  return {
3232
- assertActive,
3233
- invalidate: (message) => {
3234
- 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.";
3412
+ name: definition.name,
3413
+ description: definition.description,
3414
+ parameters: definition.parameters,
3415
+ execute: async (args, _context, signal) => {
3416
+ const result = await definition.execute(args, signal);
3417
+ const content = JSON.parse(JSON.stringify(result.content));
3418
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3235
3419
  }
3236
3420
  };
3237
3421
  }
3238
-
3239
- // src/extensions/runner.ts
3240
- import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3241
-
3242
- // src/extensions/event-bus.ts
3243
- function createEventBus() {
3244
- const listeners = /* @__PURE__ */ new Map();
3422
+ function toLoopResult(result, toolCallId, toolName) {
3245
3423
  return {
3246
- on(event, listener) {
3247
- const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3248
- set.add(listener);
3249
- listeners.set(event, set);
3250
- return () => {
3251
- set.delete(listener);
3252
- if (set.size === 0) listeners.delete(event);
3253
- };
3254
- },
3255
- emit(event, ...args) {
3256
- const set = listeners.get(event);
3257
- if (!set) return;
3258
- for (const listener of set) {
3259
- try {
3260
- listener(...args);
3261
- } catch (err) {
3262
- console.error(`[event-bus] Listener error for "${event}":`, err);
3263
- }
3264
- }
3265
- },
3266
- off(event) {
3267
- if (event) {
3268
- listeners.delete(event);
3269
- } else {
3270
- listeners.clear();
3271
- }
3272
- },
3273
- has(event) {
3274
- const set = listeners.get(event);
3275
- return set !== void 0 && set.size > 0;
3276
- },
3277
- count(event) {
3278
- return listeners.get(event)?.size ?? 0;
3279
- }
3424
+ toolCallId,
3425
+ toolName,
3426
+ ok: result.ok,
3427
+ content: result.content,
3428
+ ...!result.ok ? { error: result.error } : {}
3280
3429
  };
3281
3430
  }
3431
+ function fromLoopResult(result) {
3432
+ return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
3433
+ }
3282
3434
 
3283
- // src/extensions/runner.ts
3284
- async function execCommand(command, args, cwd, options) {
3285
- return new Promise((resolve6, reject) => {
3286
- const child = execFile(
3287
- command,
3288
- args,
3289
- {
3290
- cwd: options?.cwd ?? cwd,
3291
- env: options?.env ? { ...process.env, ...options.env } : void 0,
3292
- timeout: options?.timeout,
3293
- maxBuffer: 10 * 1024 * 1024
3294
- },
3295
- (error, stdout, stderr) => {
3296
- if (error && error.killed && options?.signal?.aborted) {
3297
- reject(new Error("Command was aborted"));
3298
- return;
3299
- }
3300
- resolve6({
3301
- exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3435
+ // src/runtime/transient-run-events.ts
3436
+ var MAX_BUFFERED_EVENTS = 128;
3437
+ var MAX_BUFFERED_TEXT = 64 * 1024;
3438
+ function deferred() {
3439
+ let resolve7;
3440
+ const promise = new Promise((done) => {
3441
+ resolve7 = done;
3442
+ });
3443
+ return { promise, resolve: resolve7 };
3444
+ }
3445
+ var TransientRunEventSubscription = class {
3446
+ queue = [];
3447
+ wake = deferred();
3448
+ version = 0;
3449
+ stopped = false;
3450
+ push(event) {
3451
+ if (this.stopped) return;
3452
+ const previous = this.queue.at(-1);
3453
+ if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3454
+ this.queue[this.queue.length - 1] = {
3455
+ ...previous,
3456
+ text: previous.text + event.text
3457
+ };
3458
+ } else {
3459
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3460
+ this.queue.push(event);
3461
+ }
3462
+ this.trimText();
3463
+ this.notify();
3464
+ }
3465
+ shift() {
3466
+ return this.queue.shift();
3467
+ }
3468
+ clear(executionId) {
3469
+ if (executionId === void 0) this.queue.length = 0;
3470
+ else {
3471
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3472
+ if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
3473
+ }
3474
+ }
3475
+ this.notify();
3476
+ }
3477
+ clearMessage(messageId) {
3478
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3479
+ const event = this.queue[index];
3480
+ if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
3481
+ }
3482
+ this.notify();
3483
+ }
3484
+ clearTool(toolCallId) {
3485
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3486
+ const event = this.queue[index];
3487
+ if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
3488
+ }
3489
+ this.notify();
3490
+ }
3491
+ checkpoint() {
3492
+ return this.version;
3493
+ }
3494
+ changed(since) {
3495
+ if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3496
+ return this.wake.promise;
3497
+ }
3498
+ stop() {
3499
+ if (this.stopped) return;
3500
+ this.stopped = true;
3501
+ this.queue.length = 0;
3502
+ this.notify();
3503
+ }
3504
+ notify() {
3505
+ const wake = this.wake;
3506
+ this.wake = deferred();
3507
+ this.version += 1;
3508
+ wake.resolve();
3509
+ }
3510
+ trimText() {
3511
+ let buffered = this.queue.reduce(
3512
+ (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3513
+ 0
3514
+ );
3515
+ while (buffered > MAX_BUFFERED_TEXT) {
3516
+ const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3517
+ if (index < 0) return;
3518
+ const event = this.queue[index];
3519
+ const overflow = buffered - MAX_BUFFERED_TEXT;
3520
+ if (event.text.length <= overflow) {
3521
+ this.queue.splice(index, 1);
3522
+ buffered -= event.text.length;
3523
+ } else {
3524
+ this.queue[index] = {
3525
+ ...event,
3526
+ offset: event.offset + overflow,
3527
+ text: event.text.slice(overflow)
3528
+ };
3529
+ buffered -= overflow;
3530
+ }
3531
+ }
3532
+ }
3533
+ };
3534
+ var TransientRunEventHub = class {
3535
+ subscriptions = /* @__PURE__ */ new Map();
3536
+ subscribe(runId) {
3537
+ const subscription = new TransientRunEventSubscription();
3538
+ const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3539
+ current.add(subscription);
3540
+ this.subscriptions.set(runId, current);
3541
+ return subscription;
3542
+ }
3543
+ unsubscribe(runId, subscription) {
3544
+ subscription.stop();
3545
+ const current = this.subscriptions.get(runId);
3546
+ current?.delete(subscription);
3547
+ if (current?.size === 0) this.subscriptions.delete(runId);
3548
+ }
3549
+ publish(event) {
3550
+ for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3551
+ subscription.push(event);
3552
+ }
3553
+ }
3554
+ clear(runId, executionId) {
3555
+ for (const subscription of this.subscriptions.get(runId) ?? []) {
3556
+ subscription.clear(executionId);
3557
+ }
3558
+ }
3559
+ clearMessage(runId, messageId) {
3560
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3561
+ }
3562
+ clearTool(runId, toolCallId) {
3563
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
3564
+ }
3565
+ close() {
3566
+ for (const subscriptions of this.subscriptions.values()) {
3567
+ for (const subscription of subscriptions) subscription.stop();
3568
+ }
3569
+ this.subscriptions.clear();
3570
+ }
3571
+ };
3572
+
3573
+ // src/extensions/hooks.ts
3574
+ var HookError = class extends Error {
3575
+ constructor(eventType, message, cause) {
3576
+ super(message, cause === void 0 ? void 0 : { cause });
3577
+ this.eventType = eventType;
3578
+ this.name = "HookError";
3579
+ }
3580
+ eventType;
3581
+ };
3582
+ var HooksManager = class {
3583
+ handlers = /* @__PURE__ */ new Map();
3584
+ on(eventType, handler) {
3585
+ const handlers = this.handlers.get(eventType) ?? [];
3586
+ handlers.push(handler);
3587
+ this.handlers.set(eventType, handlers);
3588
+ }
3589
+ off(eventType, handler) {
3590
+ const handlers = this.handlers.get(eventType);
3591
+ if (!handlers) return;
3592
+ const index = handlers.indexOf(handler);
3593
+ if (index >= 0) handlers.splice(index, 1);
3594
+ }
3595
+ async emit(eventType, event) {
3596
+ const handlers = this.handlers.get(eventType);
3597
+ if (!handlers?.length) return;
3598
+ const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
3599
+ const failure = results.find((result) => result.status === "rejected");
3600
+ if (failure) {
3601
+ throw new HookError(
3602
+ eventType,
3603
+ `${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
3604
+ failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
3605
+ );
3606
+ }
3607
+ }
3608
+ async emitFirst(eventType, event) {
3609
+ const handlers = this.handlers.get(eventType);
3610
+ if (!handlers?.length) return void 0;
3611
+ for (const handler of handlers) {
3612
+ try {
3613
+ const result = await handler(event);
3614
+ if (result !== void 0) return result;
3615
+ } catch (error) {
3616
+ throw new HookError(
3617
+ eventType,
3618
+ `Handler failed for "${eventType}"`,
3619
+ error instanceof Error ? error : new Error(String(error))
3620
+ );
3621
+ }
3622
+ }
3623
+ return void 0;
3624
+ }
3625
+ };
3626
+
3627
+ // src/extensions/runner.ts
3628
+ import { execFile } from "child_process";
3629
+ import {
3630
+ registerModel,
3631
+ unregisterProviderModels,
3632
+ registerApiProvider
3633
+ } from "@rowan-agent/models";
3634
+
3635
+ // src/extensions/types.ts
3636
+ function createSourceInfo(extensionPath, options = {}) {
3637
+ const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3638
+ const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3639
+ return {
3640
+ source,
3641
+ baseDir: options.baseDir,
3642
+ displayName
3643
+ };
3644
+ }
3645
+ function createExtensionRuntime() {
3646
+ const state = {};
3647
+ const assertActive = () => {
3648
+ if (state.staleMessage) {
3649
+ throw new Error(state.staleMessage);
3650
+ }
3651
+ };
3652
+ return {
3653
+ assertActive,
3654
+ invalidate: (message) => {
3655
+ 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.";
3656
+ }
3657
+ };
3658
+ }
3659
+
3660
+ // src/extensions/runner.ts
3661
+ import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3662
+
3663
+ // src/extensions/event-bus.ts
3664
+ function createEventBus() {
3665
+ const listeners = /* @__PURE__ */ new Map();
3666
+ return {
3667
+ on(event, listener) {
3668
+ const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3669
+ set.add(listener);
3670
+ listeners.set(event, set);
3671
+ return () => {
3672
+ set.delete(listener);
3673
+ if (set.size === 0) listeners.delete(event);
3674
+ };
3675
+ },
3676
+ emit(event, ...args) {
3677
+ const set = listeners.get(event);
3678
+ if (!set) return;
3679
+ for (const listener of set) {
3680
+ try {
3681
+ listener(...args);
3682
+ } catch (err) {
3683
+ console.error(`[event-bus] Listener error for "${event}":`, err);
3684
+ }
3685
+ }
3686
+ },
3687
+ off(event) {
3688
+ if (event) {
3689
+ listeners.delete(event);
3690
+ } else {
3691
+ listeners.clear();
3692
+ }
3693
+ },
3694
+ has(event) {
3695
+ const set = listeners.get(event);
3696
+ return set !== void 0 && set.size > 0;
3697
+ },
3698
+ count(event) {
3699
+ return listeners.get(event)?.size ?? 0;
3700
+ }
3701
+ };
3702
+ }
3703
+
3704
+ // src/extensions/runner.ts
3705
+ async function execCommand(command, args, cwd, options) {
3706
+ return new Promise((resolve7, reject) => {
3707
+ const child = execFile(
3708
+ command,
3709
+ args,
3710
+ {
3711
+ cwd: options?.cwd ?? cwd,
3712
+ env: options?.env ? { ...process.env, ...options.env } : void 0,
3713
+ timeout: options?.timeout,
3714
+ maxBuffer: 10 * 1024 * 1024
3715
+ },
3716
+ (error, stdout, stderr) => {
3717
+ if (error && error.killed && options?.signal?.aborted) {
3718
+ reject(new Error("Command was aborted"));
3719
+ return;
3720
+ }
3721
+ resolve7({
3722
+ exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3302
3723
  stdout: stdout ?? "",
3303
3724
  stderr: stderr ?? ""
3304
3725
  });
@@ -3416,6 +3837,7 @@ var ExtensionRunner = class {
3416
3837
  */
3417
3838
  invalidate(message) {
3418
3839
  this.runtime.invalidate(message);
3840
+ for (const extension of this.extensions) extension.runtime.invalidate(message);
3419
3841
  }
3420
3842
  // ---------------------------------------------------------------------------
3421
3843
  // Direct hook API
@@ -3445,13 +3867,21 @@ var ExtensionRunner = class {
3445
3867
  */
3446
3868
  async loadExtensions(extensions) {
3447
3869
  for (const ext of extensions) {
3870
+ const extension = {
3871
+ path: ext.path,
3872
+ tools: /* @__PURE__ */ new Map(),
3873
+ phases: /* @__PURE__ */ new Set(),
3874
+ cleanup: [],
3875
+ runtime: createExtensionRuntime()
3876
+ };
3448
3877
  try {
3449
- const extension = { path: ext.path, tools: /* @__PURE__ */ new Map() };
3450
3878
  const api = this.createExtensionAPI(extension, ext.manifest);
3451
- await ext.factory(api);
3879
+ const disposer = await ext.factory(api);
3880
+ if (typeof disposer === "function") extension.disposer = disposer;
3452
3881
  this.extensions.push(extension);
3453
3882
  this._phaseCache = null;
3454
3883
  } catch (error) {
3884
+ await this.rollbackExtension(extension);
3455
3885
  const message = error instanceof Error ? error.message : String(error);
3456
3886
  this.emitError({
3457
3887
  extensionPath: ext.path,
@@ -3463,6 +3893,17 @@ var ExtensionRunner = class {
3463
3893
  }
3464
3894
  }
3465
3895
  }
3896
+ /** Dispose all active Extensions in reverse activation order. */
3897
+ async close() {
3898
+ this.abortController.abort();
3899
+ for (const extension of [...this.extensions].reverse()) {
3900
+ await this.disposeExtension(extension, "This Extension Runtime has been closed.");
3901
+ }
3902
+ this.extensions.length = 0;
3903
+ this.phases.clear();
3904
+ this._phaseCache = null;
3905
+ this.runtime.invalidate("This Extension Runtime has been closed.");
3906
+ }
3466
3907
  // ---------------------------------------------------------------------------
3467
3908
  // Tool management
3468
3909
  // ---------------------------------------------------------------------------
@@ -3660,8 +4101,9 @@ var ExtensionRunner = class {
3660
4101
  unregisterProvider: (name) => this.unregisterProvider(name),
3661
4102
  registerTool: (tool) => this.registerTool(extension, tool),
3662
4103
  context: extContext,
3663
- manifest
3664
- }, this.runtime, this.events);
4104
+ manifest,
4105
+ trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
4106
+ }, extension.runtime, this.events);
3665
4107
  }
3666
4108
  registerTool(extension, tool) {
3667
4109
  if (extension.tools.has(tool.name)) {
@@ -3718,6 +4160,7 @@ var ExtensionRunner = class {
3718
4160
  source: { extensionPath: extension.path }
3719
4161
  };
3720
4162
  this.phases.set(name, registered);
4163
+ extension.phases.add(name);
3721
4164
  this._phaseCache = null;
3722
4165
  }
3723
4166
  registerProvider(config) {
@@ -3752,260 +4195,835 @@ var ExtensionRunner = class {
3752
4195
  this._phaseCache = new Map(this.phases);
3753
4196
  return this._phaseCache;
3754
4197
  }
4198
+ async rollbackExtension(extension) {
4199
+ await this.disposeExtension(extension, "This Extension activation was rolled back.");
4200
+ }
4201
+ async disposeExtension(extension, message) {
4202
+ extension.runtime.invalidate(message);
4203
+ for (const name of extension.phases) this.phases.delete(name);
4204
+ for (const cleanup of [...extension.cleanup].reverse()) {
4205
+ await Promise.resolve().then(() => cleanup()).catch(() => void 0);
4206
+ }
4207
+ if (extension.disposer) {
4208
+ await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
4209
+ }
4210
+ this._phaseCache = null;
4211
+ }
3755
4212
  };
3756
4213
  function createExtensionRunner(options) {
3757
4214
  return new ExtensionRunner(options);
3758
4215
  }
3759
4216
 
3760
- // src/runtime/extensions.ts
3761
- async function assembleExtensions(config) {
3762
- const selectedExtensions = selectNamedResources(
3763
- config.resources.extensions ?? [],
3764
- config.definition.extensions,
3765
- "Extension"
3766
- );
3767
- if (selectedExtensions.length === 0) {
3768
- return { context: resolveDefinitionContext(config) };
4217
+ // src/extensions/loader.ts
4218
+ import { existsSync as existsSync2, readFileSync } from "fs";
4219
+ import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
4220
+ import { fileURLToPath } from "url";
4221
+ import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
4222
+ import { createJiti } from "jiti";
4223
+ function isExtensionFile(path) {
4224
+ const extension = extname2(path).toLowerCase();
4225
+ return extension === ".ts" || extension === ".js";
4226
+ }
4227
+ function isSyntheticPath(path) {
4228
+ return path.startsWith("<") && path.endsWith(">");
4229
+ }
4230
+ function readManifestSync(dir) {
4231
+ const manifestPath = join2(dir, "package.json");
4232
+ if (!existsSync2(manifestPath)) {
4233
+ return void 0;
3769
4234
  }
3770
- const runner = createExtensionRunner({ cwd: config.cwd });
3771
- await runner.loadExtensions(selectedExtensions);
3772
- runner.bind();
3773
- const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3774
- const tools = [...config.resources.tools];
3775
- const names = new Set(tools.map((tool) => tool.name));
3776
- for (const tool of extensionTools) {
3777
- if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
3778
- names.add(tool.name);
3779
- tools.push(tool);
4235
+ try {
4236
+ const content = readFileSync(manifestPath, "utf8");
4237
+ const pkg = JSON.parse(content);
4238
+ const rowan = pkg.rowan;
4239
+ if (!rowan) return void 0;
4240
+ return {
4241
+ entry: rowan.extensions?.[0],
4242
+ phase: rowan.phase
4243
+ };
4244
+ } catch {
4245
+ return void 0;
3780
4246
  }
3781
- const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3782
- const phases = mergePhases(config.resources.phases, extensionPhases);
3783
- const context = resolveDefinitionContext(config, { tools, phases });
3784
- return {
3785
- context,
3786
- beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3787
- afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3788
- beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
3789
- beforeToolCall: async (input) => {
3790
- const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3791
- const decision = await runner.emitBeforeToolCall(tool, input.args);
3792
- return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
3793
- },
3794
- afterToolCall: async (input) => {
3795
- const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3796
- const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3797
- return fromLoopResult(result);
3798
- },
3799
- setContext: (context2) => {
3800
- runner.currentContext = context2;
3801
- }
3802
- };
3803
4247
  }
3804
- function resolveDefinitionContext(config, assembled = {}) {
3805
- const tools = selectNamedResources(
3806
- assembled.tools ?? config.resources.tools,
3807
- config.definition.tools,
3808
- "Tool"
3809
- );
3810
- const skills = selectNamedResources(
3811
- config.resources.skills,
3812
- config.definition.skills,
3813
- "Skill"
3814
- );
3815
- const candidateRegistry = assembled.phases ?? config.resources.phases;
3816
- const selectedPhases = selectNamedResources(
3817
- [...candidateRegistry?.phases.values() ?? []],
3818
- config.definition.phases,
3819
- "Phase"
3820
- );
3821
- const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
3822
- const requestedEntry = config.definition.entryPhase ?? candidateRegistry?.entryPhaseId ?? null;
3823
- const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
3824
- if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
3825
- console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
4248
+ function jitiAliases() {
4249
+ const moduleUrl = import.meta.url;
4250
+ if (!moduleUrl) {
4251
+ return {};
3826
4252
  }
3827
- return {
3828
- systemPrompt: config.definition.content,
3829
- tools,
3830
- skills,
3831
- phases: { phases, entryPhaseId }
3832
- };
4253
+ const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
4254
+ if (!existsSync2(agentSourcePath)) {
4255
+ return {};
4256
+ }
4257
+ return { "@rowan-agent/agent": agentSourcePath };
3833
4258
  }
3834
- function mergePhases(base, extension) {
3835
- if (!base && extension.phases.size === 0) return void 0;
3836
- const phases = new Map(base?.phases ?? []);
3837
- for (const [name, phase] of extension.phases) {
3838
- if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
3839
- phases.set(name, phase);
4259
+ var sharedJiti;
4260
+ function getJiti() {
4261
+ sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
4262
+ fsCache: false,
4263
+ moduleCache: false,
4264
+ tryNative: false,
4265
+ alias: jitiAliases()
4266
+ });
4267
+ return sharedJiti;
4268
+ }
4269
+ async function loadExtensionModule(extensionPath) {
4270
+ const jiti = getJiti();
4271
+ const module = await jiti.import(extensionPath, { default: true });
4272
+ return typeof module === "function" ? module : void 0;
4273
+ }
4274
+ async function readPackageManifest(path) {
4275
+ if (!existsSync2(path)) {
4276
+ return void 0;
4277
+ }
4278
+ const content = await readFile3(path, "utf8");
4279
+ return JSON.parse(content);
4280
+ }
4281
+ async function resolveExtensionEntries(dir) {
4282
+ const manifest = await readPackageManifest(join2(dir, "package.json"));
4283
+ const declared = manifest?.rowan?.extensions;
4284
+ if (declared && declared.length > 0) {
4285
+ return declared.map((entry) => resolve4(dir, entry));
4286
+ }
4287
+ for (const name of ["index.ts", "index.js"]) {
4288
+ const entry = join2(dir, name);
4289
+ if (existsSync2(entry)) {
4290
+ return [entry];
4291
+ }
4292
+ }
4293
+ return void 0;
4294
+ }
4295
+ async function discoverExtensionsInDir(dir) {
4296
+ const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
4297
+ if (error.code === "ENOENT") {
4298
+ return [];
4299
+ }
4300
+ throw error;
4301
+ });
4302
+ const paths = [];
4303
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4304
+ const entryPath = join2(dir, entry.name);
4305
+ if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4306
+ paths.push(entryPath);
4307
+ continue;
4308
+ }
4309
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
4310
+ const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
4311
+ if (entry.isDirectory() || info?.isDirectory()) {
4312
+ const resolvedEntries = await resolveExtensionEntries(entryPath);
4313
+ if (resolvedEntries) {
4314
+ paths.push(...resolvedEntries);
4315
+ }
4316
+ }
4317
+ }
3840
4318
  }
4319
+ return paths;
4320
+ }
4321
+ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4322
+ const resolvedCwd = resolve4(cwd);
4323
+ const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
4324
+ const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
4325
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
4326
+ const manifest = readManifestSync(manifestDir);
3841
4327
  return {
3842
- phases,
3843
- entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
4328
+ path: resolvedPath,
4329
+ name,
4330
+ factory,
4331
+ manifest
3844
4332
  };
3845
4333
  }
3846
- function adaptExtensionTool(input) {
3847
- const definition = input.definition;
3848
- return {
3849
- name: definition.name,
3850
- description: definition.description,
3851
- parameters: definition.parameters,
3852
- execute: async (args, _context, signal) => {
3853
- const result = await definition.execute(args, signal);
3854
- const content = JSON.parse(JSON.stringify(result.content));
3855
- return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
4334
+ async function loadExtensions(paths, cwd) {
4335
+ const extensions = [];
4336
+ const errors = [];
4337
+ const resolvedCwd = resolve4(cwd);
4338
+ for (const path of paths) {
4339
+ const resolvedPath = resolve4(resolvedCwd, path);
4340
+ try {
4341
+ const factory = await loadExtensionModule(resolvedPath);
4342
+ if (!factory) {
4343
+ errors.push({
4344
+ path: resolvedPath,
4345
+ error: `Extension does not export a valid factory function: ${path}`
4346
+ });
4347
+ continue;
4348
+ }
4349
+ extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
4350
+ } catch (error) {
4351
+ errors.push({
4352
+ path: resolvedPath,
4353
+ error: error instanceof Error ? error.message : String(error)
4354
+ });
3856
4355
  }
3857
- };
4356
+ }
4357
+ return { extensions, errors };
3858
4358
  }
3859
- function toLoopResult(result, toolCallId, toolName) {
4359
+ async function loadExtensionsFromPath(path) {
4360
+ const resolvedPath = resolve4(path);
4361
+ const info = await stat2(resolvedPath);
4362
+ if (!info.isDirectory()) {
4363
+ return loadExtensions([resolvedPath], dirname4(resolvedPath));
4364
+ }
4365
+ const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
4366
+ return loadExtensions(entries, resolvedPath);
4367
+ }
4368
+
4369
+ // src/runtime/resource-registry.ts
4370
+ import { stat as stat3, readdir as readdir4 } from "fs/promises";
4371
+ import { join as join4, resolve as resolve6 } from "path";
4372
+
4373
+ // src/harness/skills.ts
4374
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
4375
+ import { readdir as readdir3 } from "fs/promises";
4376
+ import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
4377
+ var SKILL_MARKER = "SKILL.md";
4378
+ function resolveSkillPath(input) {
4379
+ const resolved = resolve5(input);
4380
+ return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
4381
+ }
4382
+ async function loadSkill(path) {
4383
+ const resolved = resolveSkillPath(path);
4384
+ let loaded;
4385
+ try {
4386
+ loaded = await loadMarkdown(resolved);
4387
+ } catch (error) {
4388
+ if (!(error instanceof FrontmatterParseError)) throw error;
4389
+ warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
4390
+ throw new ResourceMetadataError("parse_failed", error.message);
4391
+ }
4392
+ const { frontmatter, body } = loaded;
4393
+ const metadata = frontmatter;
4394
+ const id = inferResourceName(resolved, SKILL_MARKER);
4395
+ const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
4396
+ const name = frontmatterName || id;
4397
+ const description = validateDescription(metadata.description);
4398
+ const diagnostics = validateResourceId(id);
4399
+ if (frontmatterName && frontmatterName !== id) {
4400
+ diagnostics.push(...validateResourceName(frontmatterName, id));
4401
+ }
4402
+ diagnostics.push(...description.warnings);
4403
+ warnResourceDiagnostics("skill", resolved, diagnostics);
4404
+ if (description.missing) {
4405
+ throw new ResourceMetadataError("invalid_metadata", "description is required");
4406
+ }
3860
4407
  return {
3861
- toolCallId,
3862
- toolName,
3863
- ok: result.ok,
3864
- content: result.content,
3865
- ...!result.ok ? { error: result.error } : {}
4408
+ name,
4409
+ description: description.description,
4410
+ filePath: resolved,
4411
+ baseDir: dirname5(resolved),
4412
+ content: body,
4413
+ disableModelInvocation: metadata["disable-model-invocation"] === true
3866
4414
  };
3867
4415
  }
3868
- function fromLoopResult(result) {
3869
- return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
4416
+ async function loadSkills(targetPath) {
4417
+ const skillsDir = resolve5(targetPath);
4418
+ if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
4419
+ try {
4420
+ return [await loadSkill(skillsDir)];
4421
+ } catch (error) {
4422
+ if (error instanceof ResourceMetadataError) return [];
4423
+ throw error;
4424
+ }
4425
+ }
4426
+ if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
4427
+ try {
4428
+ return [await loadSkill(skillsDir)];
4429
+ } catch (error) {
4430
+ if (error instanceof ResourceMetadataError) return [];
4431
+ throw error;
4432
+ }
4433
+ }
4434
+ const entries = await readdir3(skillsDir, { withFileTypes: true });
4435
+ const skills = [];
4436
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4437
+ if (!entry.isDirectory()) continue;
4438
+ const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
4439
+ if (!existsSync3(skillFile)) continue;
4440
+ try {
4441
+ const skill = await loadSkill(skillFile);
4442
+ skills.push(skill);
4443
+ } catch (error) {
4444
+ if (error instanceof ResourceMetadataError) continue;
4445
+ console.warn(`Failed to load skill "${entry.name}":`, error);
4446
+ }
4447
+ }
4448
+ return skills;
3870
4449
  }
3871
4450
 
3872
- // src/runtime/transient-run-events.ts
3873
- var MAX_BUFFERED_EVENTS = 128;
3874
- var MAX_BUFFERED_TEXT = 64 * 1024;
3875
- function deferred() {
3876
- let resolve6;
3877
- const promise = new Promise((done) => {
3878
- resolve6 = done;
3879
- });
3880
- return { promise, resolve: resolve6 };
3881
- }
3882
- var TransientRunEventSubscription = class {
3883
- queue = [];
3884
- wake = deferred();
3885
- version = 0;
3886
- stopped = false;
3887
- push(event) {
3888
- if (this.stopped) return;
3889
- const previous = this.queue.at(-1);
3890
- if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3891
- this.queue[this.queue.length - 1] = {
3892
- ...previous,
3893
- text: previous.text + event.text
4451
+ // src/runtime/resource-registry.ts
4452
+ var ResourceRegistryError = class extends Error {
4453
+ code;
4454
+ constructor(code, message) {
4455
+ super(message);
4456
+ this.name = "ResourceRegistryError";
4457
+ this.code = code;
4458
+ }
4459
+ };
4460
+ var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4461
+ var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4462
+ var ResourceRegistry = class {
4463
+ sources = /* @__PURE__ */ new Map();
4464
+ /** Sources registered by the bootstrap boundary (currently Extensions). */
4465
+ implicitSources = /* @__PURE__ */ new Map();
4466
+ revisionSequence = 0;
4467
+ async loadAgents(input) {
4468
+ assertSourceId(input.sourceId);
4469
+ const collected = await this.collect("agent", input);
4470
+ return this.commit("agent", input.sourceId, collected.values, collected.skipped);
4471
+ }
4472
+ async loadSkills(input) {
4473
+ assertSourceId(input.sourceId);
4474
+ const collected = await this.collect("skill", input);
4475
+ return this.commit("skill", input.sourceId, collected.values, collected.skipped);
4476
+ }
4477
+ async loadPhases(input) {
4478
+ assertSourceId(input.sourceId);
4479
+ const collected = await this.collect("phase", input);
4480
+ return this.commit("phase", input.sourceId, collected.values, collected.skipped);
4481
+ }
4482
+ async loadTools(input) {
4483
+ assertSourceId(input.sourceId);
4484
+ return this.commit("tool", input.sourceId, input.values, []);
4485
+ }
4486
+ async unload(input) {
4487
+ assertSourceId(input.sourceId);
4488
+ if (this.hasImplicit(input.kind, input.sourceId)) {
4489
+ throw new ResourceRegistryError(
4490
+ "implicit_source_protected",
4491
+ `Implicit ${input.kind} source "${input.sourceId}" is managed by Runtime bootstrap.`
4492
+ );
4493
+ }
4494
+ const bySource = this.sources.get(input.kind);
4495
+ const existing = bySource?.get(input.sourceId);
4496
+ if (!existing) {
4497
+ return {
4498
+ revision: this.nextRevision(input.kind, input.sourceId),
4499
+ registered: [],
4500
+ skipped: []
3894
4501
  };
3895
- } else {
3896
- if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3897
- this.queue.push(event);
3898
4502
  }
3899
- this.trimText();
3900
- this.notify();
4503
+ bySource.delete(input.sourceId);
4504
+ return {
4505
+ revision: this.nextRevision(input.kind, input.sourceId),
4506
+ registered: [],
4507
+ skipped: []
4508
+ };
3901
4509
  }
3902
- shift() {
3903
- return this.queue.shift();
4510
+ resolveView(view) {
4511
+ const sourceIds = {
4512
+ agents: this.withImplicit("agent", view.agents),
4513
+ tools: this.withImplicit("tool", view.tools),
4514
+ skills: this.withImplicit("skill", view.skills),
4515
+ phases: this.withImplicit("phase", view.phases)
4516
+ };
4517
+ return {
4518
+ agents: this.resolveKind("agent", sourceIds.agents),
4519
+ tools: this.resolveKind("tool", sourceIds.tools),
4520
+ skills: this.resolveKind("skill", sourceIds.skills),
4521
+ phases: this.resolveKind("phase", sourceIds.phases),
4522
+ revisions: {
4523
+ agent: this.resolveRevisions("agent", sourceIds.agents),
4524
+ tool: this.resolveRevisions("tool", sourceIds.tools),
4525
+ skill: this.resolveRevisions("skill", sourceIds.skills),
4526
+ phase: this.resolveRevisions("phase", sourceIds.phases)
4527
+ },
4528
+ refs: {
4529
+ agent: this.resolveRefs("agent", sourceIds.agents),
4530
+ tool: this.resolveRefs("tool", sourceIds.tools),
4531
+ skill: this.resolveRefs("skill", sourceIds.skills),
4532
+ phase: this.resolveRefs("phase", sourceIds.phases)
4533
+ }
4534
+ };
3904
4535
  }
3905
- clear(executionId) {
3906
- if (executionId === void 0) this.queue.length = 0;
3907
- else {
3908
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3909
- if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
4536
+ /** Mark one already-loaded source as implicit for every Resource View. */
4537
+ markImplicit(kind, sourceId) {
4538
+ assertSourceId(sourceId);
4539
+ const entries = this.implicitSources.get(kind) ?? [];
4540
+ if (!entries.includes(sourceId)) entries.push(sourceId);
4541
+ this.implicitSources.set(kind, entries);
4542
+ }
4543
+ /** Bootstrap-only helper for replacing an implicit contribution source. */
4544
+ async replaceImplicit(kind, sourceId, values) {
4545
+ const result = await this.commit(kind, sourceId, values, []);
4546
+ this.markImplicit(kind, sourceId);
4547
+ return result;
4548
+ }
4549
+ hasImplicit(kind, sourceId) {
4550
+ return this.implicitSources.get(kind)?.includes(sourceId) ?? false;
4551
+ }
4552
+ withImplicit(kind, sourceIds) {
4553
+ const combined = [...sourceIds];
4554
+ for (const sourceId of this.implicitSources.get(kind) ?? []) {
4555
+ if (!combined.includes(sourceId)) combined.push(sourceId);
4556
+ }
4557
+ return combined;
4558
+ }
4559
+ async collect(kind, input) {
4560
+ const hasDirectory = input.directory !== void 0;
4561
+ const hasValues = input.values !== void 0;
4562
+ if (!hasDirectory && !hasValues) {
4563
+ throw new ResourceRegistryError(
4564
+ "resource_input_missing",
4565
+ `Resource source "${input.sourceId}" requires directory or values.`
4566
+ );
4567
+ }
4568
+ const directoryValues = hasDirectory ? await loadDirectoryValues(kind, input.directory, input.sourceId) : { values: [], skipped: [] };
4569
+ return {
4570
+ values: [...directoryValues.values, ...input.values ?? []],
4571
+ skipped: directoryValues.skipped
4572
+ };
4573
+ }
4574
+ async commit(kind, sourceId, values, skipped) {
4575
+ assertSourceId(sourceId);
4576
+ const names = /* @__PURE__ */ new Set();
4577
+ for (const value of values) {
4578
+ if (kind === "agent") assertAgentDefinition(value);
4579
+ assertNamedResource(value, kind, sourceId);
4580
+ if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
4581
+ throw new ResourceRegistryError(
4582
+ "resource_collision",
4583
+ `${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
4584
+ );
4585
+ }
4586
+ if (names.has(value.name)) {
4587
+ throw new ResourceRegistryError(
4588
+ "resource_collision",
4589
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in source "${sourceId}".`
4590
+ );
3910
4591
  }
4592
+ names.add(value.name);
4593
+ }
4594
+ const revision = this.nextRevision(kind, sourceId);
4595
+ const record = {
4596
+ kind,
4597
+ sourceId,
4598
+ revision,
4599
+ values: Object.freeze([...values])
4600
+ };
4601
+ let bySource = this.sources.get(kind);
4602
+ if (!bySource) {
4603
+ bySource = /* @__PURE__ */ new Map();
4604
+ this.sources.set(kind, bySource);
3911
4605
  }
3912
- this.notify();
4606
+ bySource.set(sourceId, record);
4607
+ return {
4608
+ revision,
4609
+ registered: values.map((value) => ({ kind, sourceId, name: value.name })),
4610
+ skipped
4611
+ };
3913
4612
  }
3914
- clearMessage(messageId) {
3915
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3916
- const event = this.queue[index];
3917
- if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
4613
+ resolveKind(kind, sourceIds) {
4614
+ const names = /* @__PURE__ */ new Set();
4615
+ const values = [];
4616
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4617
+ for (const value of source.values) {
4618
+ if (names.has(value.name)) {
4619
+ throw new ResourceRegistryError(
4620
+ "resource_collision",
4621
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
4622
+ );
4623
+ }
4624
+ names.add(value.name);
4625
+ values.push(value);
4626
+ }
3918
4627
  }
3919
- this.notify();
4628
+ return values;
3920
4629
  }
3921
- clearTool(toolCallId) {
3922
- for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3923
- const event = this.queue[index];
3924
- if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
4630
+ resolveRevisions(kind, sourceIds) {
4631
+ return this.sourcesFor(kind, sourceIds).map(({ revision }) => revision);
4632
+ }
4633
+ resolveRefs(kind, sourceIds) {
4634
+ const seenNames = /* @__PURE__ */ new Set();
4635
+ const refs = [];
4636
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4637
+ for (const value of source.values) {
4638
+ if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
4639
+ seenNames.add(value.name);
4640
+ refs.push({ kind, sourceId: source.sourceId, name: value.name });
4641
+ }
3925
4642
  }
3926
- this.notify();
4643
+ return refs;
4644
+ }
4645
+ sourcesFor(kind, sourceIds) {
4646
+ const bySource = this.sources.get(kind);
4647
+ const seen = /* @__PURE__ */ new Set();
4648
+ const sources = [];
4649
+ for (const sourceId of sourceIds) {
4650
+ assertSourceId(sourceId);
4651
+ if (seen.has(sourceId)) continue;
4652
+ seen.add(sourceId);
4653
+ const source = bySource?.get(sourceId);
4654
+ if (!source) {
4655
+ throw new ResourceRegistryError(
4656
+ "unknown_resource_source",
4657
+ `Unknown resource source "${sourceId}" for ${kind}.`
4658
+ );
4659
+ }
4660
+ sources.push(source);
4661
+ }
4662
+ return sources;
3927
4663
  }
3928
- checkpoint() {
3929
- return this.version;
4664
+ nextRevision(kind, sourceId) {
4665
+ this.revisionSequence += 1;
4666
+ return `${kind}:${sourceId}:${this.revisionSequence}`;
3930
4667
  }
3931
- changed(since) {
3932
- if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3933
- return this.wake.promise;
4668
+ };
4669
+ function assertSourceId(sourceId) {
4670
+ if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
4671
+ throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
3934
4672
  }
3935
- stop() {
3936
- if (this.stopped) return;
3937
- this.stopped = true;
3938
- this.queue.length = 0;
3939
- this.notify();
4673
+ }
4674
+ function assertNamedResource(value, kind, sourceId) {
4675
+ if (!value || typeof value !== "object" || typeof value.name !== "string") {
4676
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${kind} resource in source "${sourceId}".`);
3940
4677
  }
3941
- notify() {
3942
- const wake = this.wake;
3943
- this.wake = deferred();
3944
- this.version += 1;
3945
- wake.resolve();
4678
+ const name = value.name;
4679
+ if (!RESOURCE_NAME.test(name)) {
4680
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${capitalize(kind)} resource name "${name}".`);
3946
4681
  }
3947
- trimText() {
3948
- let buffered = this.queue.reduce(
3949
- (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3950
- 0
3951
- );
3952
- while (buffered > MAX_BUFFERED_TEXT) {
3953
- const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3954
- if (index < 0) return;
3955
- const event = this.queue[index];
3956
- const overflow = buffered - MAX_BUFFERED_TEXT;
3957
- if (event.text.length <= overflow) {
3958
- this.queue.splice(index, 1);
3959
- buffered -= event.text.length;
3960
- } else {
3961
- this.queue[index] = {
3962
- ...event,
3963
- offset: event.offset + overflow,
3964
- text: event.text.slice(overflow)
4682
+ }
4683
+ function capitalize(value) {
4684
+ return value.charAt(0).toUpperCase() + value.slice(1);
4685
+ }
4686
+ function isImplicitCoreName(kind, name) {
4687
+ return kind === "tool" && name === "route" || kind === "phase" && name === "default";
4688
+ }
4689
+ async function loadDirectoryValues(kind, directory, sourceId) {
4690
+ const root = resolve6(directory);
4691
+ let rootInfo;
4692
+ try {
4693
+ rootInfo = await stat3(root);
4694
+ } catch (error) {
4695
+ if (isMissingPath(error)) {
4696
+ return {
4697
+ values: [],
4698
+ skipped: [{ kind: "missing", sourceId, path: root, message: `Resource directory "${root}" does not exist.` }]
4699
+ };
4700
+ }
4701
+ return {
4702
+ values: [],
4703
+ skipped: [{
4704
+ kind: "invalid",
4705
+ sourceId,
4706
+ path: root,
4707
+ message: `Resource directory "${root}" could not be read: ${error instanceof Error ? error.message : String(error)}`
4708
+ }]
4709
+ };
4710
+ }
4711
+ const marker = markerFor(kind);
4712
+ const targets = [];
4713
+ if (rootInfo.isFile()) {
4714
+ targets.push(root);
4715
+ } else if (rootInfo.isDirectory()) {
4716
+ const markerAtRoot = join4(root, marker);
4717
+ try {
4718
+ const markerInfo = await stat3(markerAtRoot);
4719
+ if (markerInfo.isFile()) targets.push(markerAtRoot);
4720
+ } catch (error) {
4721
+ if (!isMissingPath(error)) throw error;
4722
+ let entries;
4723
+ try {
4724
+ entries = await readdir4(root, { withFileTypes: true });
4725
+ } catch (error2) {
4726
+ return {
4727
+ values: [],
4728
+ skipped: [{
4729
+ kind: "invalid",
4730
+ sourceId,
4731
+ path: root,
4732
+ message: `Resource directory "${root}" could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
4733
+ }]
3965
4734
  };
3966
- buffered -= overflow;
4735
+ }
4736
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
4737
+ if (!entry.isDirectory()) continue;
4738
+ const target = join4(root, entry.name, marker);
4739
+ try {
4740
+ const targetInfo = await stat3(target);
4741
+ if (targetInfo.isFile()) targets.push(target);
4742
+ } catch (targetError) {
4743
+ if (!isMissingPath(targetError)) throw targetError;
4744
+ }
3967
4745
  }
3968
4746
  }
3969
4747
  }
3970
- };
3971
- var TransientRunEventHub = class {
3972
- subscriptions = /* @__PURE__ */ new Map();
3973
- subscribe(runId) {
3974
- const subscription = new TransientRunEventSubscription();
3975
- const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3976
- current.add(subscription);
3977
- this.subscriptions.set(runId, current);
3978
- return subscription;
4748
+ const values = [];
4749
+ const skipped = [];
4750
+ for (const target of targets) {
4751
+ try {
4752
+ const value = await loadDirectoryValue(kind, target);
4753
+ values.push(value);
4754
+ } catch (error) {
4755
+ skipped.push({
4756
+ kind: "invalid",
4757
+ sourceId,
4758
+ path: target,
4759
+ message: error instanceof Error ? error.message : String(error)
4760
+ });
4761
+ }
3979
4762
  }
3980
- unsubscribe(runId, subscription) {
3981
- subscription.stop();
3982
- const current = this.subscriptions.get(runId);
3983
- current?.delete(subscription);
3984
- if (current?.size === 0) this.subscriptions.delete(runId);
4763
+ return { values, skipped };
4764
+ }
4765
+ async function loadDirectoryValue(kind, target) {
4766
+ switch (kind) {
4767
+ case "agent": {
4768
+ let loaded;
4769
+ try {
4770
+ loaded = await loadMarkdown(target);
4771
+ } catch (error) {
4772
+ if (error instanceof FrontmatterParseError) {
4773
+ throw new TypeError(`frontmatter could not be parsed: ${error.message}`);
4774
+ }
4775
+ throw error;
4776
+ }
4777
+ return normalizeAgentDefinition(
4778
+ loaded.frontmatter,
4779
+ loaded.body,
4780
+ { fallbackName: inferResourceName(target, "AGENT.md") }
4781
+ );
4782
+ }
4783
+ case "skill":
4784
+ return await loadSkill(target);
4785
+ case "phase":
4786
+ return await loadPhase(target);
4787
+ case "tool":
4788
+ throw new ResourceRegistryError(
4789
+ "resource_directory_not_supported",
4790
+ "Tool sources accept inline values only."
4791
+ );
3985
4792
  }
3986
- publish(event) {
3987
- for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3988
- subscription.push(event);
4793
+ }
4794
+ function markerFor(kind) {
4795
+ switch (kind) {
4796
+ case "agent":
4797
+ return "AGENT.md";
4798
+ case "skill":
4799
+ return "SKILL.md";
4800
+ case "phase":
4801
+ return "PHASE.md";
4802
+ case "tool":
4803
+ return "";
4804
+ }
4805
+ }
4806
+ function isMissingPath(error) {
4807
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
4808
+ }
4809
+
4810
+ // src/runtime/extension-lifetime.ts
4811
+ var ExtensionLifetimeError = class extends Error {
4812
+ code;
4813
+ constructor(code, message) {
4814
+ super(message);
4815
+ this.name = "ExtensionLifetimeError";
4816
+ this.code = code;
4817
+ }
4818
+ };
4819
+ var RuntimeExtensionLifetime = class {
4820
+ runner;
4821
+ frozen = false;
4822
+ closed = false;
4823
+ constructor(options = {}) {
4824
+ this.runner = createExtensionRunner(options);
4825
+ }
4826
+ async activate(extensions) {
4827
+ if (this.closed) throw new ExtensionLifetimeError("extensions_closed", "The Extension Runtime has been closed.");
4828
+ if (this.frozen) throw new ExtensionLifetimeError("extensions_frozen", "Extensions are frozen until Runtime restart.");
4829
+ const active = [];
4830
+ const errors = [];
4831
+ for (const extension of extensions) {
4832
+ try {
4833
+ await this.runner.loadExtensions([extension]);
4834
+ active.push(extension.name);
4835
+ } catch (error) {
4836
+ errors.push({
4837
+ path: extension.path,
4838
+ error: error instanceof Error ? error.message : String(error)
4839
+ });
4840
+ }
3989
4841
  }
4842
+ this.runner.bind();
4843
+ this.frozen = true;
4844
+ return { active, errors };
3990
4845
  }
3991
- clear(runId, executionId) {
3992
- for (const subscription of this.subscriptions.get(runId) ?? []) {
3993
- subscription.clear(executionId);
4846
+ tools() {
4847
+ return this.runner.getAllRegisteredTools();
4848
+ }
4849
+ phases() {
4850
+ return this.runner.getPhases();
4851
+ }
4852
+ get extensionRunner() {
4853
+ return this.runner;
4854
+ }
4855
+ async close() {
4856
+ if (this.closed) return;
4857
+ this.closed = true;
4858
+ await this.runner.close();
4859
+ }
4860
+ };
4861
+ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
4862
+ constructor(extensions = new RuntimeExtensionLifetime()) {
4863
+ super();
4864
+ this.extensions = extensions;
4865
+ }
4866
+ extensions;
4867
+ extensionSourceId = "rowan.extensions";
4868
+ /** Install Rowan-owned implicit resources before any host source is read. */
4869
+ async ensureCoreResources() {
4870
+ await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
4871
+ await this.replaceImplicit("phase", "rowan.core", [createDefaultPhase()]);
4872
+ }
4873
+ async loadExtensions(input) {
4874
+ const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
4875
+ const descriptor = input;
4876
+ return descriptor.directory ? loadExtensionsFromPath(descriptor.directory) : Promise.resolve({ extensions: [...descriptor.values ?? []], errors: [] });
4877
+ })();
4878
+ const normalized = await loaded;
4879
+ const result = await this.extensions.activate(normalized.extensions);
4880
+ await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool2));
4881
+ await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
4882
+ return {
4883
+ ...result,
4884
+ revision: `extensions:${Date.now()}`,
4885
+ registered: result.active,
4886
+ skipped: normalized.errors.map((error) => ({
4887
+ kind: "invalid",
4888
+ sourceId: "rowan.extensions",
4889
+ path: error.path,
4890
+ message: error.error
4891
+ }))
4892
+ };
4893
+ }
4894
+ get extensionRunner() {
4895
+ return this.extensions.extensionRunner;
4896
+ }
4897
+ get extensionTools() {
4898
+ return this.extensions.tools();
4899
+ }
4900
+ get extensionPhases() {
4901
+ return this.extensions.phases();
4902
+ }
4903
+ async closeExtensions() {
4904
+ await this.extensions.close();
4905
+ }
4906
+ };
4907
+ function adaptExtensionTool2(input) {
4908
+ const definition = input.definition;
4909
+ return {
4910
+ name: definition.name,
4911
+ description: definition.description,
4912
+ parameters: definition.parameters,
4913
+ execute: async (args, context, signal) => {
4914
+ const result = await definition.execute(args, signal);
4915
+ const content = JSON.parse(JSON.stringify(result.content));
4916
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3994
4917
  }
4918
+ };
4919
+ }
4920
+
4921
+ // src/runtime/configuration-snapshot.ts
4922
+ function resolveConfigurationSnapshot(registry, input) {
4923
+ const resolved = registry.resolveView(input.resourceView);
4924
+ const base = resolved.agents.find(({ name }) => name === input.definition.name);
4925
+ if (!base) {
4926
+ throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
4927
+ }
4928
+ const layer = input.definition.layer;
4929
+ const definition = applyDefinitionLayer(base, layer);
4930
+ const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
4931
+ const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
4932
+ const phases = resolvePhases(resolved.phases, definition.phases);
4933
+ const contexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
4934
+ return {
4935
+ identity: input.identity,
4936
+ definition,
4937
+ resources: {
4938
+ tools,
4939
+ skills,
4940
+ ...phases ? { phases } : {},
4941
+ revisions: resolved.revisions,
4942
+ refs: {
4943
+ agent: selectedRefs(resolved.refs.agent, [base.name]),
4944
+ tool: selectedRefs(resolved.refs.tool, tools.map(({ name }) => name)),
4945
+ skill: selectedRefs(resolved.refs.skill, skills.map(({ name }) => name)),
4946
+ phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
4947
+ }
4948
+ },
4949
+ contexts,
4950
+ resourceView: input.resourceView,
4951
+ ...input.cwd === void 0 ? {} : { cwd: input.cwd },
4952
+ ...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
4953
+ ..."stream" in input && input.stream ? { model: layer?.model ?? input.model, stream: input.stream } : { model: layer?.model ?? input.model }
4954
+ };
4955
+ }
4956
+ function materializeConfigurationSnapshot(snapshot) {
4957
+ return {
4958
+ identity: snapshot.identity,
4959
+ definition: snapshot.definition,
4960
+ resources: {
4961
+ tools: snapshot.resources.tools,
4962
+ skills: snapshot.resources.skills,
4963
+ ...snapshot.resources.phases ? { phases: snapshot.resources.phases } : {},
4964
+ ...snapshot.contexts.length > 0 ? { contexts: snapshot.contexts } : {},
4965
+ resourceView: snapshot.resourceView,
4966
+ resourceRefs: [
4967
+ ...snapshot.resources.refs.agent,
4968
+ ...snapshot.resources.refs.tool,
4969
+ ...snapshot.resources.refs.skill,
4970
+ ...snapshot.resources.refs.phase
4971
+ ],
4972
+ resourceRevisions: snapshot.resources.revisions
4973
+ },
4974
+ ...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
4975
+ ...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
4976
+ ..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
4977
+ };
4978
+ }
4979
+ function applyDefinitionLayer(base, layer) {
4980
+ if (!layer) return { ...base };
4981
+ return {
4982
+ ...base,
4983
+ ...layer.description === void 0 ? {} : { description: layer.description },
4984
+ ...layer.prompt === void 0 ? {} : { prompt: layer.prompt },
4985
+ ...layer.model === void 0 ? {} : { model: layer.model },
4986
+ ...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
4987
+ ...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
4988
+ ...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
4989
+ };
4990
+ }
4991
+ function intersectNames(parent, layer, kind) {
4992
+ if (parent === void 0) return [...layer];
4993
+ const parentNames = new Set(parent);
4994
+ for (const name of layer) {
4995
+ if (!parentNames.has(name)) console.warn(`${kind} "${name}" is not available and will be skipped.`);
3995
4996
  }
3996
- clearMessage(runId, messageId) {
3997
- for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3998
- }
3999
- clearTool(runId, toolCallId) {
4000
- for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
4997
+ return layer.filter((name) => parentNames.has(name));
4998
+ }
4999
+ function intersectPhaseSelection(parent, layer) {
5000
+ if (!parent) return layer;
5001
+ const parentNames = new Set(parent.phaseIds);
5002
+ for (const name of layer.phaseIds) {
5003
+ if (!parentNames.has(name)) console.warn(`Phase "${name}" is not available and will be skipped.`);
4001
5004
  }
4002
- close() {
4003
- for (const subscriptions of this.subscriptions.values()) {
4004
- for (const subscription of subscriptions) subscription.stop();
4005
- }
4006
- this.subscriptions.clear();
5005
+ return {
5006
+ entryPhaseId: layer.entryPhaseId,
5007
+ phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
5008
+ };
5009
+ }
5010
+ function resolvePhases(candidates, selection) {
5011
+ const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
5012
+ if (selected.length === 0 && selection === void 0) return void 0;
5013
+ const entryPhaseId = selection?.entryPhaseId ?? null;
5014
+ if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
5015
+ console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
5016
+ return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
4007
5017
  }
4008
- };
5018
+ return {
5019
+ phases: new Map(selected.map((phase) => [phase.name, phase])),
5020
+ entryPhaseId
5021
+ };
5022
+ }
5023
+ function selectedRefs(refs, names) {
5024
+ const selected = new Set(names);
5025
+ return refs.filter(({ name }) => selected.has(name));
5026
+ }
4009
5027
 
4010
5028
  // src/runtime/durable-runtime.ts
4011
5029
  var DEFAULT_CONCURRENCY = 10;
@@ -4022,14 +5040,16 @@ var AgentRuntime = class _AgentRuntime {
4022
5040
  executions = /* @__PURE__ */ new Map();
4023
5041
  consumers = /* @__PURE__ */ new Map();
4024
5042
  transientEvents = new TransientRunEventHub();
5043
+ resources;
4025
5044
  heartbeat;
4026
5045
  pumping = false;
4027
5046
  closed = false;
4028
- constructor(options, owned) {
5047
+ constructor(options, owned, resources) {
4029
5048
  this.owned = owned;
4030
5049
  this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
4031
5050
  this.storeIncarnation = String(owned.lease.token).split(":")[0];
4032
5051
  this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
5052
+ this.resources = resources;
4033
5053
  }
4034
5054
  static async init(options) {
4035
5055
  if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
@@ -4037,10 +5057,38 @@ var AgentRuntime = class _AgentRuntime {
4037
5057
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
4038
5058
  if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
4039
5059
  const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
4040
- const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned);
4041
- runtime.startHeartbeat();
4042
- void runtime.pump();
4043
- return runtime;
5060
+ const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
5061
+ try {
5062
+ await runtime.resources.ensureCoreResources();
5063
+ await options.bootstrap?.(runtime.resources);
5064
+ runtime.startHeartbeat();
5065
+ void runtime.pump();
5066
+ return runtime;
5067
+ } catch (error) {
5068
+ await runtime.resources.closeExtensions().catch(() => void 0);
5069
+ await owned.sealAndReleaseOwner().catch(() => void 0);
5070
+ throw error;
5071
+ }
5072
+ }
5073
+ async loadAgents(input) {
5074
+ this.assertOpen();
5075
+ return this.resources.loadAgents(input);
5076
+ }
5077
+ async loadSkills(input) {
5078
+ this.assertOpen();
5079
+ return this.resources.loadSkills(input);
5080
+ }
5081
+ async loadPhases(input) {
5082
+ this.assertOpen();
5083
+ return this.resources.loadPhases(input);
5084
+ }
5085
+ async loadTools(input) {
5086
+ this.assertOpen();
5087
+ return this.resources.loadTools(input);
5088
+ }
5089
+ async unload(input) {
5090
+ this.assertOpen();
5091
+ return this.resources.unload(input);
4044
5092
  }
4045
5093
  async createAgent(config, options = {}) {
4046
5094
  this.assertOpen();
@@ -4100,6 +5148,7 @@ var AgentRuntime = class _AgentRuntime {
4100
5148
  this.executions.clear();
4101
5149
  this.transientEvents.close();
4102
5150
  for (const subscription of this.consumers.values()) subscription.controller.abort();
5151
+ await this.resources.closeExtensions();
4103
5152
  await this.owned.sealAndReleaseOwner();
4104
5153
  }
4105
5154
  async snapshot(runId) {
@@ -4157,7 +5206,7 @@ var AgentRuntime = class _AgentRuntime {
4157
5206
  let executionRevision = run.revision;
4158
5207
  try {
4159
5208
  const agent = await this.requireAgent(run.agentId);
4160
- const token = run.pinnedConfigToken ?? agent.currentConfigToken;
5209
+ let token = run.pinnedConfigToken ?? agent.currentConfigToken;
4161
5210
  if (!token) {
4162
5211
  await this.failQueued(run, "Agent has no Config Token.");
4163
5212
  return;
@@ -4177,13 +5226,28 @@ var AgentRuntime = class _AgentRuntime {
4177
5226
  await this.failQueued(run, resolution.reason);
4178
5227
  return;
4179
5228
  }
5229
+ let resolvedConfig = resolution.config;
5230
+ if (isAgentConfiguration(resolvedConfig)) {
5231
+ try {
5232
+ const snapshot = this.materializeConfig(resolvedConfig);
5233
+ token = await this.commands.storeSnapshot({
5234
+ agent,
5235
+ config: snapshot,
5236
+ operationId: `run-snapshot:${run.id}`
5237
+ });
5238
+ resolvedConfig = snapshot;
5239
+ } catch (error) {
5240
+ await this.failQueued(run, error instanceof Error ? error.message : "Configuration Snapshot failed.");
5241
+ return;
5242
+ }
5243
+ }
4180
5244
  const executionId = createId("exec");
4181
5245
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
4182
5246
  executionRevision = claim.run.revision;
4183
5247
  const controller = new AbortController();
4184
5248
  this.executions.set(run.id, { controller, executionId });
4185
- const config = resolution.config;
4186
- const assembly = await assembleExtensions(config);
5249
+ const config = resolvedConfig;
5250
+ const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
4187
5251
  let toolQueue = Promise.resolve();
4188
5252
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
4189
5253
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
@@ -4207,7 +5271,9 @@ var AgentRuntime = class _AgentRuntime {
4207
5271
  execution: {
4208
5272
  agentId: run.agentId,
4209
5273
  runId: run.id,
4210
- executionId: claim.execution.executionId
5274
+ executionId: claim.execution.executionId,
5275
+ ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
5276
+ ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
4211
5277
  },
4212
5278
  model,
4213
5279
  stream,
@@ -4236,6 +5302,7 @@ var AgentRuntime = class _AgentRuntime {
4236
5302
  const task = toolQueue.then(async () => {
4237
5303
  const execution = await this.executeToolBatch({
4238
5304
  run,
5305
+ agentMetadata: agent.metadata,
4239
5306
  execution: claim.execution,
4240
5307
  expectedRevision: executionRevision,
4241
5308
  toolConfig: executionTools,
@@ -4252,6 +5319,7 @@ var AgentRuntime = class _AgentRuntime {
4252
5319
  const task = toolQueue.then(async () => {
4253
5320
  const execution = await this.executeToolBatch({
4254
5321
  run,
5322
+ agentMetadata: agent.metadata,
4255
5323
  execution: claim.execution,
4256
5324
  expectedRevision: executionRevision,
4257
5325
  toolConfig: executionTools,
@@ -4346,6 +5414,8 @@ var AgentRuntime = class _AgentRuntime {
4346
5414
  const context = {
4347
5415
  agentId: input.run.agentId,
4348
5416
  runId: input.run.id,
5417
+ ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
5418
+ ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
4349
5419
  toolCallId,
4350
5420
  reportProgress: (progress) => {
4351
5421
  const active = this.executions.get(input.run.id);
@@ -4489,6 +5559,10 @@ var AgentRuntime = class _AgentRuntime {
4489
5559
  assertOpen() {
4490
5560
  if (this.closed) throw new RuntimeError("runtime_closed", null);
4491
5561
  }
5562
+ materializeConfig(config) {
5563
+ if (!isAgentConfiguration(config)) return config;
5564
+ return materializeConfigurationSnapshot(resolveConfigurationSnapshot(this.resources, config));
5565
+ }
4492
5566
  startHeartbeat() {
4493
5567
  this.heartbeat = setInterval(() => {
4494
5568
  void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
@@ -4552,10 +5626,10 @@ function boundaryFromSnapshot(snapshot) {
4552
5626
  }
4553
5627
  }
4554
5628
  function delay(ms) {
4555
- return new Promise((resolve6) => setTimeout(resolve6, ms));
5629
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
4556
5630
  }
4557
5631
  function normalizePhaseRegistry(registry) {
4558
- if (registry?.phases.has(DEFAULT_PHASE_ID)) {
5632
+ if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
4559
5633
  throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
4560
5634
  }
4561
5635
  return {
@@ -4579,13 +5653,13 @@ function toJsonValue(value) {
4579
5653
  return value;
4580
5654
  }
4581
5655
  function deferred2() {
4582
- let resolve6;
5656
+ let resolve7;
4583
5657
  let reject;
4584
5658
  const promise = new Promise((res, rej) => {
4585
- resolve6 = (value) => res(value);
5659
+ resolve7 = (value) => res(value);
4586
5660
  reject = rej;
4587
5661
  });
4588
- return { promise, resolve: resolve6, reject };
5662
+ return { promise, resolve: resolve7, reject };
4589
5663
  }
4590
5664
 
4591
5665
  // src/runtime/durable-store.ts
@@ -6036,236 +7110,6 @@ function assertOwnerInput(ownerId, leaseMs) {
6036
7110
  if (!Number.isFinite(leaseMs) || leaseMs <= 0) throw new TypeError("leaseMs must be a positive finite number");
6037
7111
  }
6038
7112
 
6039
- // src/harness/skills.ts
6040
- import { existsSync as existsSync2, statSync as statSync2 } from "fs";
6041
- import { readdir as readdir2 } from "fs/promises";
6042
- import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
6043
- var SKILL_MARKER = "SKILL.md";
6044
- function resolveSkillPath(input) {
6045
- const resolved = resolve4(input);
6046
- return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
6047
- }
6048
- async function loadSkill(path) {
6049
- const resolved = resolveSkillPath(path);
6050
- let loaded;
6051
- try {
6052
- loaded = await loadMarkdown(resolved);
6053
- } catch (error) {
6054
- if (!(error instanceof FrontmatterParseError)) throw error;
6055
- warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
6056
- throw new ResourceMetadataError("parse_failed", error.message);
6057
- }
6058
- const { frontmatter, body } = loaded;
6059
- const metadata = frontmatter;
6060
- const id = inferResourceName(resolved, SKILL_MARKER);
6061
- const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
6062
- const name = frontmatterName || id;
6063
- const description = validateDescription(metadata.description);
6064
- const diagnostics = validateResourceId(id);
6065
- if (frontmatterName && frontmatterName !== id) {
6066
- diagnostics.push(...validateResourceName(frontmatterName, id));
6067
- }
6068
- diagnostics.push(...description.warnings);
6069
- warnResourceDiagnostics("skill", resolved, diagnostics);
6070
- if (description.missing) {
6071
- throw new ResourceMetadataError("invalid_metadata", "description is required");
6072
- }
6073
- return {
6074
- name,
6075
- description: description.description,
6076
- filePath: resolved,
6077
- baseDir: dirname4(resolved),
6078
- content: body,
6079
- disableModelInvocation: metadata["disable-model-invocation"] === true
6080
- };
6081
- }
6082
- async function loadSkills(targetPath) {
6083
- const skillsDir = resolve4(targetPath);
6084
- if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
6085
- try {
6086
- return [await loadSkill(skillsDir)];
6087
- } catch (error) {
6088
- if (error instanceof ResourceMetadataError) return [];
6089
- throw error;
6090
- }
6091
- }
6092
- if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
6093
- try {
6094
- return [await loadSkill(skillsDir)];
6095
- } catch (error) {
6096
- if (error instanceof ResourceMetadataError) return [];
6097
- throw error;
6098
- }
6099
- }
6100
- const entries = await readdir2(skillsDir, { withFileTypes: true });
6101
- const skills = [];
6102
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
6103
- if (!entry.isDirectory()) continue;
6104
- const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
6105
- if (!existsSync2(skillFile)) continue;
6106
- try {
6107
- const skill = await loadSkill(skillFile);
6108
- skills.push(skill);
6109
- } catch (error) {
6110
- if (error instanceof ResourceMetadataError) continue;
6111
- console.warn(`Failed to load skill "${entry.name}":`, error);
6112
- }
6113
- }
6114
- return skills;
6115
- }
6116
-
6117
- // src/extensions/loader.ts
6118
- import { existsSync as existsSync3, readFileSync } from "fs";
6119
- import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
6120
- import { fileURLToPath } from "url";
6121
- import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
6122
- import { createJiti } from "jiti";
6123
- function isExtensionFile(path) {
6124
- const extension = extname2(path).toLowerCase();
6125
- return extension === ".ts" || extension === ".js";
6126
- }
6127
- function isSyntheticPath(path) {
6128
- return path.startsWith("<") && path.endsWith(">");
6129
- }
6130
- function readManifestSync(dir) {
6131
- const manifestPath = join3(dir, "package.json");
6132
- if (!existsSync3(manifestPath)) {
6133
- return void 0;
6134
- }
6135
- try {
6136
- const content = readFileSync(manifestPath, "utf8");
6137
- const pkg = JSON.parse(content);
6138
- const rowan = pkg.rowan;
6139
- if (!rowan) return void 0;
6140
- return {
6141
- entry: rowan.extensions?.[0],
6142
- phase: rowan.phase
6143
- };
6144
- } catch {
6145
- return void 0;
6146
- }
6147
- }
6148
- function jitiAliases() {
6149
- const moduleUrl = import.meta.url;
6150
- if (!moduleUrl) {
6151
- return {};
6152
- }
6153
- const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
6154
- if (!existsSync3(agentSourcePath)) {
6155
- return {};
6156
- }
6157
- return { "@rowan-agent/agent": agentSourcePath };
6158
- }
6159
- var sharedJiti;
6160
- function getJiti() {
6161
- sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
6162
- fsCache: false,
6163
- moduleCache: false,
6164
- tryNative: false,
6165
- alias: jitiAliases()
6166
- });
6167
- return sharedJiti;
6168
- }
6169
- async function loadExtensionModule(extensionPath) {
6170
- const jiti = getJiti();
6171
- const module = await jiti.import(extensionPath, { default: true });
6172
- return typeof module === "function" ? module : void 0;
6173
- }
6174
- async function readPackageManifest(path) {
6175
- if (!existsSync3(path)) {
6176
- return void 0;
6177
- }
6178
- const content = await readFile3(path, "utf8");
6179
- return JSON.parse(content);
6180
- }
6181
- async function resolveExtensionEntries(dir) {
6182
- const manifest = await readPackageManifest(join3(dir, "package.json"));
6183
- const declared = manifest?.rowan?.extensions;
6184
- if (declared && declared.length > 0) {
6185
- return declared.map((entry) => resolve5(dir, entry));
6186
- }
6187
- for (const name of ["index.ts", "index.js"]) {
6188
- const entry = join3(dir, name);
6189
- if (existsSync3(entry)) {
6190
- return [entry];
6191
- }
6192
- }
6193
- return void 0;
6194
- }
6195
- async function discoverExtensionsInDir(dir) {
6196
- const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
6197
- if (error.code === "ENOENT") {
6198
- return [];
6199
- }
6200
- throw error;
6201
- });
6202
- const paths = [];
6203
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
6204
- const entryPath = join3(dir, entry.name);
6205
- if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
6206
- paths.push(entryPath);
6207
- continue;
6208
- }
6209
- if (entry.isDirectory() || entry.isSymbolicLink()) {
6210
- const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
6211
- if (entry.isDirectory() || info?.isDirectory()) {
6212
- const resolvedEntries = await resolveExtensionEntries(entryPath);
6213
- if (resolvedEntries) {
6214
- paths.push(...resolvedEntries);
6215
- }
6216
- }
6217
- }
6218
- }
6219
- return paths;
6220
- }
6221
- function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
6222
- const resolvedCwd = resolve5(cwd);
6223
- const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
6224
- const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
6225
- const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
6226
- const manifest = readManifestSync(manifestDir);
6227
- return {
6228
- path: resolvedPath,
6229
- name,
6230
- factory,
6231
- manifest
6232
- };
6233
- }
6234
- async function loadExtensions(paths, cwd) {
6235
- const extensions = [];
6236
- const errors = [];
6237
- const resolvedCwd = resolve5(cwd);
6238
- for (const path of paths) {
6239
- const resolvedPath = resolve5(resolvedCwd, path);
6240
- try {
6241
- const factory = await loadExtensionModule(resolvedPath);
6242
- if (!factory) {
6243
- errors.push({
6244
- path: resolvedPath,
6245
- error: `Extension does not export a valid factory function: ${path}`
6246
- });
6247
- continue;
6248
- }
6249
- extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
6250
- } catch (error) {
6251
- errors.push({
6252
- path: resolvedPath,
6253
- error: error instanceof Error ? error.message : String(error)
6254
- });
6255
- }
6256
- }
6257
- return { extensions, errors };
6258
- }
6259
- async function loadExtensionsFromPath(path) {
6260
- const resolvedPath = resolve5(path);
6261
- const info = await stat2(resolvedPath);
6262
- if (!info.isDirectory()) {
6263
- return loadExtensions([resolvedPath], dirname5(resolvedPath));
6264
- }
6265
- const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
6266
- return loadExtensions(entries, resolvedPath);
6267
- }
6268
-
6269
7113
  // src/index.ts
6270
7114
  function createCoreTools2(input = {}) {
6271
7115
  return createCoreTools(input).map((tool) => ({
@@ -6280,9 +7124,14 @@ function createCoreTools2(input = {}) {
6280
7124
  }
6281
7125
  export {
6282
7126
  AgentRuntime,
7127
+ ExtensionLifetimeError,
6283
7128
  InMemoryConfigProvider,
6284
7129
  InMemoryStore,
7130
+ ResourceRegistry,
7131
+ ResourceRegistryError,
7132
+ RuntimeBootstrapRegistry,
6285
7133
  RuntimeError,
7134
+ RuntimeExtensionLifetime,
6286
7135
  SqliteStore,
6287
7136
  brandConfigToken,
6288
7137
  createCoreTools2 as createCoreTools,
@@ -6290,6 +7139,8 @@ export {
6290
7139
  loadExtensionsFromPath as loadExtensions,
6291
7140
  loadPhases,
6292
7141
  loadSkills,
7142
+ materializeConfigurationSnapshot,
6293
7143
  parseAgentDefinition,
6294
- parseFrontmatter
7144
+ parseFrontmatter,
7145
+ resolveConfigurationSnapshot
6295
7146
  };