@rowan-agent/agent 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,6 +33,18 @@ function buildSkillsDescription(skills) {
33
33
  lines.push("</available_skills>");
34
34
  return lines.join("\n");
35
35
  }
36
+ function buildContextDescription(contexts) {
37
+ if (contexts.length === 0) return "";
38
+ const lines = ["<agent_context>"];
39
+ for (const context of contexts) {
40
+ lines.push(` <context name="${escapeXml(context.name)}">`);
41
+ const value = jsonToXml(context.value, 2);
42
+ if (value) lines.push(value);
43
+ lines.push(" </context>");
44
+ }
45
+ lines.push("</agent_context>");
46
+ return lines.join("\n");
47
+ }
36
48
  function formatResourceOutput(resource) {
37
49
  const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
38
50
  if (resource.baseDir) {
@@ -955,6 +967,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
955
967
  };
956
968
  const ctx = options?.context ?? noopContext;
957
969
  const phaseIn = options?.phase;
970
+ const trackCleanup = options?.trackCleanup;
958
971
  let outputPayload = phaseIn?.state?.payload;
959
972
  let nextPhase;
960
973
  let outputMessage;
@@ -962,6 +975,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
962
975
  on: (eventType, handler) => {
963
976
  assertActive();
964
977
  hooks?.on(eventType, handler);
978
+ trackCleanup?.(() => hooks?.off(eventType, handler));
965
979
  },
966
980
  off: (eventType, handler) => {
967
981
  assertActive();
@@ -978,6 +992,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
978
992
  registerProvider: (config) => {
979
993
  assertActive();
980
994
  options?.registerProvider?.(config);
995
+ trackCleanup?.(() => options?.unregisterProvider?.(config.id));
981
996
  },
982
997
  unregisterProvider: (name) => {
983
998
  assertActive();
@@ -989,7 +1004,17 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
989
1004
  formatJson
990
1005
  },
991
1006
  context: ctx,
992
- events: eventBus ?? { on: () => () => {
1007
+ events: eventBus ? {
1008
+ on: (event, listener) => {
1009
+ const unsubscribe = eventBus.on(event, listener);
1010
+ trackCleanup?.(unsubscribe);
1011
+ return unsubscribe;
1012
+ },
1013
+ off: (event) => eventBus.off(event),
1014
+ emit: (event, ...args) => eventBus.emit(event, ...args),
1015
+ has: (event) => eventBus.has(event),
1016
+ count: (event) => eventBus.count(event)
1017
+ } : { on: () => () => {
993
1018
  }, off: () => {
994
1019
  }, emit: () => {
995
1020
  }, has: () => false, count: () => 0 },
@@ -1025,9 +1050,10 @@ function assertAgentDefinition(value) {
1025
1050
  requiredString(value.content, "definition.content");
1026
1051
  optionalStringList(value.tools, "definition.tools");
1027
1052
  optionalStringList(value.skills, "definition.skills");
1028
- optionalStringList(value.phases, "definition.phases");
1029
- optionalStringList(value.extensions, "definition.extensions");
1030
- optionalString(value.entryPhase, "definition.entryPhase");
1053
+ optionalPhaseRegistrySelection(value.phases, "definition.phases");
1054
+ optionalStringList(value.context, "definition.context");
1055
+ if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
1056
+ if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
1031
1057
  if (value.model !== void 0) {
1032
1058
  if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
1033
1059
  throw new TypeError("definition.model must be a valid model reference");
@@ -1044,9 +1070,14 @@ function normalizeAgentDefinition(frontmatter, content, options = {}) {
1044
1070
  const normalizedContent = requiredString(content, "content");
1045
1071
  const tools = optionalStringList(frontmatter.tools, "tools");
1046
1072
  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");
1073
+ const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
1074
+ if (frontmatter.extensions !== void 0) {
1075
+ throw new TypeError("extensions is not supported; Extensions are Runtime-global");
1076
+ }
1077
+ const context = optionalStringList(frontmatter.context, "context");
1078
+ if (frontmatter.entryPhase !== void 0) {
1079
+ throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
1080
+ }
1050
1081
  const model = optionalModel(frontmatter.model);
1051
1082
  return {
1052
1083
  name,
@@ -1055,8 +1086,7 @@ function normalizeAgentDefinition(frontmatter, content, options = {}) {
1055
1086
  ...tools ? { tools } : {},
1056
1087
  ...skills ? { skills } : {},
1057
1088
  ...phases ? { phases } : {},
1058
- ...extensions ? { extensions } : {},
1059
- ...entryPhase ? { entryPhase } : {},
1089
+ ...context ? { context } : {},
1060
1090
  ...model ? { model } : {}
1061
1091
  };
1062
1092
  }
@@ -1089,6 +1119,22 @@ function optionalStringList(value, field) {
1089
1119
  }
1090
1120
  return names;
1091
1121
  }
1122
+ function optionalPhaseRegistrySelection(value, field) {
1123
+ if (value === void 0) return void 0;
1124
+ if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
1125
+ if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
1126
+ if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
1127
+ const entryPhaseId = value.entryPhaseId;
1128
+ if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
1129
+ throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
1130
+ }
1131
+ const phaseIds = optionalStringList(value.phaseIds, `${field}.phaseIds`);
1132
+ if (!phaseIds) throw new TypeError(`${field}.phaseIds is required`);
1133
+ return {
1134
+ entryPhaseId: entryPhaseId === null ? null : entryPhaseId.trim(),
1135
+ phaseIds
1136
+ };
1137
+ }
1092
1138
  function optionalModel(value) {
1093
1139
  const source = optionalString(value, "model");
1094
1140
  if (!source) return void 0;
@@ -1693,7 +1739,7 @@ function createTerminalOutcome(phase, output, transcript) {
1693
1739
  return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
1694
1740
  }
1695
1741
  function buildToolsWithRouting(config, availablePhases) {
1696
- const tools = [...config.context.tools];
1742
+ const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
1697
1743
  if (availablePhases.length > 0) {
1698
1744
  tools.push(createRouteTool(availablePhases));
1699
1745
  }
@@ -2158,7 +2204,7 @@ async function withRetry(fn, options = {}) {
2158
2204
  }
2159
2205
  const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2160
2206
  options.onRetry?.(attempt, error, delayMs);
2161
- await new Promise((resolve6) => setTimeout(resolve6, delayMs));
2207
+ await new Promise((resolve7) => setTimeout(resolve7, delayMs));
2162
2208
  if (options.signal?.aborted) {
2163
2209
  throw lastError;
2164
2210
  }
@@ -2663,6 +2709,9 @@ function isContinuation(value) {
2663
2709
  }
2664
2710
 
2665
2711
  // src/runtime/contracts.ts
2712
+ function isAgentConfiguration(config) {
2713
+ return !("resources" in config);
2714
+ }
2666
2715
  var METADATA_LIMIT = 64 * 1024;
2667
2716
  var IDENTITY_LIMIT = 256;
2668
2717
  function isRecord3(value) {
@@ -2715,6 +2764,39 @@ function assertAgentConfig(config) {
2715
2764
  throw new TypeError("config.resources is invalid");
2716
2765
  }
2717
2766
  for (const tool of config.resources.tools) projectToolDefinition(tool);
2767
+ const names = /* @__PURE__ */ new Set();
2768
+ for (const context of config.resources.contexts ?? []) {
2769
+ if (typeof context.name !== "string" || context.name.trim() === "") {
2770
+ throw new TypeError("Context candidate name must be non-empty");
2771
+ }
2772
+ if (names.has(context.name)) throw new TypeError(`Duplicate Context candidate "${context.name}".`);
2773
+ names.add(context.name);
2774
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2775
+ }
2776
+ }
2777
+ function assertAgentConfigRequest(config) {
2778
+ if (!isAgentConfiguration(config)) {
2779
+ assertAgentConfig(config);
2780
+ return;
2781
+ }
2782
+ if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2783
+ assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2784
+ if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
2785
+ throw new TypeError("config.definition.name must be non-empty");
2786
+ }
2787
+ const view = config.resourceView;
2788
+ if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
2789
+ throw new TypeError("config.resourceView is invalid");
2790
+ }
2791
+ for (const [kind, ids] of Object.entries(view)) {
2792
+ for (const sourceId of ids) {
2793
+ if (typeof sourceId !== "string" || sourceId.trim() === "") throw new TypeError(`config.resourceView.${kind} contains an invalid Source ID`);
2794
+ }
2795
+ }
2796
+ for (const context of config.contexts ?? []) {
2797
+ if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
2798
+ assertJsonValue(context.value, `Context candidate "${context.name}" value`);
2799
+ }
2718
2800
  }
2719
2801
  function assertToolExecutionResult(value) {
2720
2802
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -2808,21 +2890,92 @@ function validateConfigResolution(agentId, value) {
2808
2890
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2809
2891
  }
2810
2892
  function snapshotConfig(config) {
2893
+ if (isAgentConfiguration(config)) return snapshotConfiguration(config);
2811
2894
  const definition = Object.freeze({
2812
2895
  ...config.definition,
2813
2896
  ...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
2814
2897
  ...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]) } : {}
2898
+ ...config.definition.phases ? {
2899
+ phases: Object.freeze({
2900
+ entryPhaseId: config.definition.phases.entryPhaseId,
2901
+ phaseIds: Object.freeze([...config.definition.phases.phaseIds])
2902
+ })
2903
+ } : {},
2904
+ ...config.definition.context ? { context: Object.freeze([...config.definition.context]) } : {}
2817
2905
  });
2818
2906
  const resources = Object.freeze({
2819
2907
  ...config.resources,
2820
2908
  tools: Object.freeze([...config.resources.tools]),
2821
2909
  skills: Object.freeze([...config.resources.skills]),
2822
- ...config.resources.extensions ? { extensions: Object.freeze([...config.resources.extensions]) } : {}
2910
+ ...config.resources.resourceView ? {
2911
+ resourceView: Object.freeze({
2912
+ agents: Object.freeze([...config.resources.resourceView.agents]),
2913
+ tools: Object.freeze([...config.resources.resourceView.tools]),
2914
+ skills: Object.freeze([...config.resources.resourceView.skills]),
2915
+ phases: Object.freeze([...config.resources.resourceView.phases])
2916
+ })
2917
+ } : {},
2918
+ ...config.resources.resourceRefs ? { resourceRefs: Object.freeze(config.resources.resourceRefs.map((ref) => Object.freeze({ ...ref }))) } : {},
2919
+ ...config.resources.resourceRevisions ? {
2920
+ resourceRevisions: Object.freeze(Object.fromEntries(
2921
+ Object.entries(config.resources.resourceRevisions).map(([kind, revisions]) => [kind, Object.freeze([...revisions])])
2922
+ ))
2923
+ } : {},
2924
+ ...config.resources.contexts ? {
2925
+ contexts: Object.freeze(config.resources.contexts.map((context) => Object.freeze({
2926
+ name: context.name,
2927
+ value: snapshotJsonValue(context.value)
2928
+ })))
2929
+ } : {}
2823
2930
  });
2824
2931
  return Object.freeze({ ...config, definition, resources });
2825
2932
  }
2933
+ function snapshotConfiguration(config) {
2934
+ const layer = config.definition.layer;
2935
+ const definition = Object.freeze({
2936
+ name: config.definition.name,
2937
+ ...layer === void 0 ? {} : {
2938
+ layer: Object.freeze({
2939
+ ...layer,
2940
+ ...layer.tools ? { tools: Object.freeze([...layer.tools]) } : {},
2941
+ ...layer.skills ? { skills: Object.freeze([...layer.skills]) } : {},
2942
+ ...layer.phases ? {
2943
+ phases: Object.freeze({
2944
+ entryPhaseId: layer.phases.entryPhaseId,
2945
+ phaseIds: Object.freeze([...layer.phases.phaseIds])
2946
+ })
2947
+ } : {}
2948
+ })
2949
+ }
2950
+ });
2951
+ const resourceView = Object.freeze({
2952
+ agents: Object.freeze([...config.resourceView.agents]),
2953
+ tools: Object.freeze([...config.resourceView.tools]),
2954
+ skills: Object.freeze([...config.resourceView.skills]),
2955
+ phases: Object.freeze([...config.resourceView.phases])
2956
+ });
2957
+ return Object.freeze({
2958
+ ...config,
2959
+ definition,
2960
+ resourceView,
2961
+ ...config.contexts ? {
2962
+ contexts: Object.freeze(config.contexts.map((context) => Object.freeze({
2963
+ name: context.name,
2964
+ value: snapshotJsonValue(context.value)
2965
+ })))
2966
+ } : {}
2967
+ });
2968
+ }
2969
+ function snapshotJsonValue(value) {
2970
+ return deepFreeze(JSON.parse(canonicalJson(value)));
2971
+ }
2972
+ function deepFreeze(value) {
2973
+ if (value && typeof value === "object") {
2974
+ for (const child of Object.values(value)) deepFreeze(child);
2975
+ Object.freeze(value);
2976
+ }
2977
+ return value;
2978
+ }
2826
2979
  function assertConfigIdentity(identity) {
2827
2980
  if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
2828
2981
  assertUtf8ByteLimit(identity, 256, "config.identity");
@@ -2867,7 +3020,7 @@ var ConfigCommandService = class {
2867
3020
  configs;
2868
3021
  storeIncarnation;
2869
3022
  async createAgent(input) {
2870
- assertAgentConfig(input.config);
3023
+ assertAgentConfigRequest(input.config);
2871
3024
  assertIdentity(input.config.identity);
2872
3025
  const reserved = await this.store.reserveAgent({
2873
3026
  idempotencyKey: input.idempotencyKey,
@@ -2889,7 +3042,7 @@ var ConfigCommandService = class {
2889
3042
  return reserved.id;
2890
3043
  }
2891
3044
  async updateAgentConfig(input) {
2892
- assertAgentConfig(input.config);
3045
+ assertAgentConfigRequest(input.config);
2893
3046
  assertIdentity(input.config.identity);
2894
3047
  const agent = await this.findAgent(input.agentId);
2895
3048
  const operationId = this.operationId("update_agent_config", input.agentId, input.idempotencyKey);
@@ -2916,6 +3069,23 @@ var ConfigCommandService = class {
2916
3069
  const resolution = validateConfigResolution(input.agent.id, result);
2917
3070
  return resolution;
2918
3071
  }
3072
+ /** Persist one resolved Configuration Snapshot under a Run-specific token.
3073
+ * The durable Run pins this token at claim time, so later Source replacement
3074
+ * cannot mutate an active or input-waiting Run. */
3075
+ async storeSnapshot(input) {
3076
+ assertAgentConfigRequest(input.config);
3077
+ const result = await this.put({
3078
+ agentId: input.agent.id,
3079
+ agentMetadata: input.agent.metadata,
3080
+ config: input.config,
3081
+ operationId: input.operationId,
3082
+ signal: input.signal
3083
+ });
3084
+ if (result.kind === "identity_conflict") {
3085
+ throw idempotencyConflict("update_agent_config", input.operationId);
3086
+ }
3087
+ return brandConfigToken(result.token);
3088
+ }
2919
3089
  async findAgent(agentId) {
2920
3090
  const agent = (await this.store.listAgents()).find((candidate) => candidate.id === agentId);
2921
3091
  if (!agent) throw new RuntimeError("agent_not_found", { agentId });
@@ -3149,157 +3319,401 @@ function jsonText(value) {
3149
3319
  return typeof value === "string" ? value : JSON.stringify(value) ?? "null";
3150
3320
  }
3151
3321
 
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
- }
3322
+ // src/runtime/extensions.ts
3323
+ function assembleRegisteredExtensions(config, runner) {
3324
+ const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3325
+ const tools = [...config.resources.tools];
3326
+ const names = new Set(tools.map((tool) => tool.name));
3327
+ for (const tool of extensionTools) {
3328
+ if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
3329
+ names.add(tool.name);
3330
+ tools.push(tool);
3186
3331
  }
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
- }
3332
+ const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3333
+ const phases = mergePhases(config.resources.phases, extensionPhases);
3334
+ const context = resolveDefinitionContext(config, { tools, phases });
3335
+ return {
3336
+ context,
3337
+ beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3338
+ afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3339
+ beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
3340
+ beforeToolCall: async (input) => {
3341
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3342
+ const decision = await runner.emitBeforeToolCall(tool, input.args);
3343
+ return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
3344
+ },
3345
+ afterToolCall: async (input) => {
3346
+ const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
3347
+ const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3348
+ return fromLoopResult(result);
3349
+ },
3350
+ setContext: (context2) => {
3351
+ runner.currentContext = context2;
3201
3352
  }
3202
- return void 0;
3353
+ };
3354
+ }
3355
+ function resolveDefinitionContext(config, assembled = {}) {
3356
+ const tools = selectNamedResources(
3357
+ assembled.tools ?? config.resources.tools,
3358
+ config.definition.tools,
3359
+ "Tool"
3360
+ );
3361
+ const skills = selectNamedResources(
3362
+ config.resources.skills,
3363
+ config.definition.skills,
3364
+ "Skill"
3365
+ );
3366
+ const contexts = selectNamedResources(
3367
+ config.resources.contexts ?? [],
3368
+ config.definition.context,
3369
+ "Context"
3370
+ );
3371
+ const candidateRegistry = assembled.phases ?? config.resources.phases;
3372
+ const selectedPhases = selectNamedResources(
3373
+ [...candidateRegistry?.phases.values() ?? []],
3374
+ config.definition.phases?.phaseIds,
3375
+ "Phase"
3376
+ );
3377
+ const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
3378
+ const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
3379
+ const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
3380
+ if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
3381
+ console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
3203
3382
  }
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
3383
  return {
3219
- source,
3220
- baseDir: options.baseDir,
3221
- displayName
3384
+ systemPrompt: [config.definition.content, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
3385
+ tools,
3386
+ skills,
3387
+ phases: { phases, entryPhaseId }
3222
3388
  };
3223
3389
  }
3224
- function createExtensionRuntime() {
3225
- const state = {};
3226
- const assertActive = () => {
3227
- if (state.staleMessage) {
3228
- throw new Error(state.staleMessage);
3229
- }
3390
+ function mergePhases(base, extension) {
3391
+ if (!base && extension.phases.size === 0) return void 0;
3392
+ const phases = new Map(base?.phases ?? []);
3393
+ for (const [name, phase] of extension.phases) {
3394
+ if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
3395
+ phases.set(name, phase);
3396
+ }
3397
+ return {
3398
+ phases,
3399
+ entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
3230
3400
  };
3401
+ }
3402
+ function adaptExtensionTool(input) {
3403
+ const definition = input.definition;
3231
3404
  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.";
3405
+ name: definition.name,
3406
+ description: definition.description,
3407
+ parameters: definition.parameters,
3408
+ execute: async (args, _context, signal) => {
3409
+ const result = await definition.execute(args, signal);
3410
+ const content = JSON.parse(JSON.stringify(result.content));
3411
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3235
3412
  }
3236
3413
  };
3237
3414
  }
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();
3415
+ function toLoopResult(result, toolCallId, toolName) {
3245
3416
  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
- }
3417
+ toolCallId,
3418
+ toolName,
3419
+ ok: result.ok,
3420
+ content: result.content,
3421
+ ...!result.ok ? { error: result.error } : {}
3280
3422
  };
3281
3423
  }
3424
+ function fromLoopResult(result) {
3425
+ return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
3426
+ }
3282
3427
 
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,
3302
- stdout: stdout ?? "",
3428
+ // src/runtime/transient-run-events.ts
3429
+ var MAX_BUFFERED_EVENTS = 128;
3430
+ var MAX_BUFFERED_TEXT = 64 * 1024;
3431
+ function deferred() {
3432
+ let resolve7;
3433
+ const promise = new Promise((done) => {
3434
+ resolve7 = done;
3435
+ });
3436
+ return { promise, resolve: resolve7 };
3437
+ }
3438
+ var TransientRunEventSubscription = class {
3439
+ queue = [];
3440
+ wake = deferred();
3441
+ version = 0;
3442
+ stopped = false;
3443
+ push(event) {
3444
+ if (this.stopped) return;
3445
+ const previous = this.queue.at(-1);
3446
+ if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3447
+ this.queue[this.queue.length - 1] = {
3448
+ ...previous,
3449
+ text: previous.text + event.text
3450
+ };
3451
+ } else {
3452
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3453
+ this.queue.push(event);
3454
+ }
3455
+ this.trimText();
3456
+ this.notify();
3457
+ }
3458
+ shift() {
3459
+ return this.queue.shift();
3460
+ }
3461
+ clear(executionId) {
3462
+ if (executionId === void 0) this.queue.length = 0;
3463
+ else {
3464
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3465
+ if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
3466
+ }
3467
+ }
3468
+ this.notify();
3469
+ }
3470
+ clearMessage(messageId) {
3471
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3472
+ const event = this.queue[index];
3473
+ if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
3474
+ }
3475
+ this.notify();
3476
+ }
3477
+ clearTool(toolCallId) {
3478
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3479
+ const event = this.queue[index];
3480
+ if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
3481
+ }
3482
+ this.notify();
3483
+ }
3484
+ checkpoint() {
3485
+ return this.version;
3486
+ }
3487
+ changed(since) {
3488
+ if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3489
+ return this.wake.promise;
3490
+ }
3491
+ stop() {
3492
+ if (this.stopped) return;
3493
+ this.stopped = true;
3494
+ this.queue.length = 0;
3495
+ this.notify();
3496
+ }
3497
+ notify() {
3498
+ const wake = this.wake;
3499
+ this.wake = deferred();
3500
+ this.version += 1;
3501
+ wake.resolve();
3502
+ }
3503
+ trimText() {
3504
+ let buffered = this.queue.reduce(
3505
+ (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3506
+ 0
3507
+ );
3508
+ while (buffered > MAX_BUFFERED_TEXT) {
3509
+ const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3510
+ if (index < 0) return;
3511
+ const event = this.queue[index];
3512
+ const overflow = buffered - MAX_BUFFERED_TEXT;
3513
+ if (event.text.length <= overflow) {
3514
+ this.queue.splice(index, 1);
3515
+ buffered -= event.text.length;
3516
+ } else {
3517
+ this.queue[index] = {
3518
+ ...event,
3519
+ offset: event.offset + overflow,
3520
+ text: event.text.slice(overflow)
3521
+ };
3522
+ buffered -= overflow;
3523
+ }
3524
+ }
3525
+ }
3526
+ };
3527
+ var TransientRunEventHub = class {
3528
+ subscriptions = /* @__PURE__ */ new Map();
3529
+ subscribe(runId) {
3530
+ const subscription = new TransientRunEventSubscription();
3531
+ const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3532
+ current.add(subscription);
3533
+ this.subscriptions.set(runId, current);
3534
+ return subscription;
3535
+ }
3536
+ unsubscribe(runId, subscription) {
3537
+ subscription.stop();
3538
+ const current = this.subscriptions.get(runId);
3539
+ current?.delete(subscription);
3540
+ if (current?.size === 0) this.subscriptions.delete(runId);
3541
+ }
3542
+ publish(event) {
3543
+ for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3544
+ subscription.push(event);
3545
+ }
3546
+ }
3547
+ clear(runId, executionId) {
3548
+ for (const subscription of this.subscriptions.get(runId) ?? []) {
3549
+ subscription.clear(executionId);
3550
+ }
3551
+ }
3552
+ clearMessage(runId, messageId) {
3553
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3554
+ }
3555
+ clearTool(runId, toolCallId) {
3556
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
3557
+ }
3558
+ close() {
3559
+ for (const subscriptions of this.subscriptions.values()) {
3560
+ for (const subscription of subscriptions) subscription.stop();
3561
+ }
3562
+ this.subscriptions.clear();
3563
+ }
3564
+ };
3565
+
3566
+ // src/extensions/hooks.ts
3567
+ var HookError = class extends Error {
3568
+ constructor(eventType, message, cause) {
3569
+ super(message, cause === void 0 ? void 0 : { cause });
3570
+ this.eventType = eventType;
3571
+ this.name = "HookError";
3572
+ }
3573
+ eventType;
3574
+ };
3575
+ var HooksManager = class {
3576
+ handlers = /* @__PURE__ */ new Map();
3577
+ on(eventType, handler) {
3578
+ const handlers = this.handlers.get(eventType) ?? [];
3579
+ handlers.push(handler);
3580
+ this.handlers.set(eventType, handlers);
3581
+ }
3582
+ off(eventType, handler) {
3583
+ const handlers = this.handlers.get(eventType);
3584
+ if (!handlers) return;
3585
+ const index = handlers.indexOf(handler);
3586
+ if (index >= 0) handlers.splice(index, 1);
3587
+ }
3588
+ async emit(eventType, event) {
3589
+ const handlers = this.handlers.get(eventType);
3590
+ if (!handlers?.length) return;
3591
+ const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
3592
+ const failure = results.find((result) => result.status === "rejected");
3593
+ if (failure) {
3594
+ throw new HookError(
3595
+ eventType,
3596
+ `${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
3597
+ failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
3598
+ );
3599
+ }
3600
+ }
3601
+ async emitFirst(eventType, event) {
3602
+ const handlers = this.handlers.get(eventType);
3603
+ if (!handlers?.length) return void 0;
3604
+ for (const handler of handlers) {
3605
+ try {
3606
+ const result = await handler(event);
3607
+ if (result !== void 0) return result;
3608
+ } catch (error) {
3609
+ throw new HookError(
3610
+ eventType,
3611
+ `Handler failed for "${eventType}"`,
3612
+ error instanceof Error ? error : new Error(String(error))
3613
+ );
3614
+ }
3615
+ }
3616
+ return void 0;
3617
+ }
3618
+ };
3619
+
3620
+ // src/extensions/runner.ts
3621
+ import { execFile } from "child_process";
3622
+ import {
3623
+ registerModel,
3624
+ unregisterProviderModels,
3625
+ registerApiProvider
3626
+ } from "@rowan-agent/models";
3627
+
3628
+ // src/extensions/types.ts
3629
+ function createSourceInfo(extensionPath, options = {}) {
3630
+ const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
3631
+ const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
3632
+ return {
3633
+ source,
3634
+ baseDir: options.baseDir,
3635
+ displayName
3636
+ };
3637
+ }
3638
+ function createExtensionRuntime() {
3639
+ const state = {};
3640
+ const assertActive = () => {
3641
+ if (state.staleMessage) {
3642
+ throw new Error(state.staleMessage);
3643
+ }
3644
+ };
3645
+ return {
3646
+ assertActive,
3647
+ invalidate: (message) => {
3648
+ state.staleMessage ??= message ?? "This extension context is stale after runtime replacement or reload. Do not use a captured extension API after the runner has been replaced.";
3649
+ }
3650
+ };
3651
+ }
3652
+
3653
+ // src/extensions/runner.ts
3654
+ import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3655
+
3656
+ // src/extensions/event-bus.ts
3657
+ function createEventBus() {
3658
+ const listeners = /* @__PURE__ */ new Map();
3659
+ return {
3660
+ on(event, listener) {
3661
+ const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
3662
+ set.add(listener);
3663
+ listeners.set(event, set);
3664
+ return () => {
3665
+ set.delete(listener);
3666
+ if (set.size === 0) listeners.delete(event);
3667
+ };
3668
+ },
3669
+ emit(event, ...args) {
3670
+ const set = listeners.get(event);
3671
+ if (!set) return;
3672
+ for (const listener of set) {
3673
+ try {
3674
+ listener(...args);
3675
+ } catch (err) {
3676
+ console.error(`[event-bus] Listener error for "${event}":`, err);
3677
+ }
3678
+ }
3679
+ },
3680
+ off(event) {
3681
+ if (event) {
3682
+ listeners.delete(event);
3683
+ } else {
3684
+ listeners.clear();
3685
+ }
3686
+ },
3687
+ has(event) {
3688
+ const set = listeners.get(event);
3689
+ return set !== void 0 && set.size > 0;
3690
+ },
3691
+ count(event) {
3692
+ return listeners.get(event)?.size ?? 0;
3693
+ }
3694
+ };
3695
+ }
3696
+
3697
+ // src/extensions/runner.ts
3698
+ async function execCommand(command, args, cwd, options) {
3699
+ return new Promise((resolve7, reject) => {
3700
+ const child = execFile(
3701
+ command,
3702
+ args,
3703
+ {
3704
+ cwd: options?.cwd ?? cwd,
3705
+ env: options?.env ? { ...process.env, ...options.env } : void 0,
3706
+ timeout: options?.timeout,
3707
+ maxBuffer: 10 * 1024 * 1024
3708
+ },
3709
+ (error, stdout, stderr) => {
3710
+ if (error && error.killed && options?.signal?.aborted) {
3711
+ reject(new Error("Command was aborted"));
3712
+ return;
3713
+ }
3714
+ resolve7({
3715
+ exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3716
+ stdout: stdout ?? "",
3303
3717
  stderr: stderr ?? ""
3304
3718
  });
3305
3719
  }
@@ -3416,6 +3830,7 @@ var ExtensionRunner = class {
3416
3830
  */
3417
3831
  invalidate(message) {
3418
3832
  this.runtime.invalidate(message);
3833
+ for (const extension of this.extensions) extension.runtime.invalidate(message);
3419
3834
  }
3420
3835
  // ---------------------------------------------------------------------------
3421
3836
  // Direct hook API
@@ -3445,13 +3860,21 @@ var ExtensionRunner = class {
3445
3860
  */
3446
3861
  async loadExtensions(extensions) {
3447
3862
  for (const ext of extensions) {
3863
+ const extension = {
3864
+ path: ext.path,
3865
+ tools: /* @__PURE__ */ new Map(),
3866
+ phases: /* @__PURE__ */ new Set(),
3867
+ cleanup: [],
3868
+ runtime: createExtensionRuntime()
3869
+ };
3448
3870
  try {
3449
- const extension = { path: ext.path, tools: /* @__PURE__ */ new Map() };
3450
3871
  const api = this.createExtensionAPI(extension, ext.manifest);
3451
- await ext.factory(api);
3872
+ const disposer = await ext.factory(api);
3873
+ if (typeof disposer === "function") extension.disposer = disposer;
3452
3874
  this.extensions.push(extension);
3453
3875
  this._phaseCache = null;
3454
3876
  } catch (error) {
3877
+ await this.rollbackExtension(extension);
3455
3878
  const message = error instanceof Error ? error.message : String(error);
3456
3879
  this.emitError({
3457
3880
  extensionPath: ext.path,
@@ -3463,6 +3886,17 @@ var ExtensionRunner = class {
3463
3886
  }
3464
3887
  }
3465
3888
  }
3889
+ /** Dispose all active Extensions in reverse activation order. */
3890
+ async close() {
3891
+ this.abortController.abort();
3892
+ for (const extension of [...this.extensions].reverse()) {
3893
+ await this.disposeExtension(extension, "This Extension Runtime has been closed.");
3894
+ }
3895
+ this.extensions.length = 0;
3896
+ this.phases.clear();
3897
+ this._phaseCache = null;
3898
+ this.runtime.invalidate("This Extension Runtime has been closed.");
3899
+ }
3466
3900
  // ---------------------------------------------------------------------------
3467
3901
  // Tool management
3468
3902
  // ---------------------------------------------------------------------------
@@ -3660,8 +4094,9 @@ var ExtensionRunner = class {
3660
4094
  unregisterProvider: (name) => this.unregisterProvider(name),
3661
4095
  registerTool: (tool) => this.registerTool(extension, tool),
3662
4096
  context: extContext,
3663
- manifest
3664
- }, this.runtime, this.events);
4097
+ manifest,
4098
+ trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
4099
+ }, extension.runtime, this.events);
3665
4100
  }
3666
4101
  registerTool(extension, tool) {
3667
4102
  if (extension.tools.has(tool.name)) {
@@ -3718,6 +4153,7 @@ var ExtensionRunner = class {
3718
4153
  source: { extensionPath: extension.path }
3719
4154
  };
3720
4155
  this.phases.set(name, registered);
4156
+ extension.phases.add(name);
3721
4157
  this._phaseCache = null;
3722
4158
  }
3723
4159
  registerProvider(config) {
@@ -3752,260 +4188,834 @@ var ExtensionRunner = class {
3752
4188
  this._phaseCache = new Map(this.phases);
3753
4189
  return this._phaseCache;
3754
4190
  }
4191
+ async rollbackExtension(extension) {
4192
+ await this.disposeExtension(extension, "This Extension activation was rolled back.");
4193
+ }
4194
+ async disposeExtension(extension, message) {
4195
+ extension.runtime.invalidate(message);
4196
+ for (const name of extension.phases) this.phases.delete(name);
4197
+ for (const cleanup of [...extension.cleanup].reverse()) {
4198
+ await Promise.resolve().then(() => cleanup()).catch(() => void 0);
4199
+ }
4200
+ if (extension.disposer) {
4201
+ await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
4202
+ }
4203
+ this._phaseCache = null;
4204
+ }
3755
4205
  };
3756
4206
  function createExtensionRunner(options) {
3757
4207
  return new ExtensionRunner(options);
3758
4208
  }
3759
4209
 
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) };
4210
+ // src/extensions/loader.ts
4211
+ import { existsSync as existsSync2, readFileSync } from "fs";
4212
+ import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
4213
+ import { fileURLToPath } from "url";
4214
+ import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
4215
+ import { createJiti } from "jiti";
4216
+ function isExtensionFile(path) {
4217
+ const extension = extname2(path).toLowerCase();
4218
+ return extension === ".ts" || extension === ".js";
4219
+ }
4220
+ function isSyntheticPath(path) {
4221
+ return path.startsWith("<") && path.endsWith(">");
4222
+ }
4223
+ function readManifestSync(dir) {
4224
+ const manifestPath = join2(dir, "package.json");
4225
+ if (!existsSync2(manifestPath)) {
4226
+ return void 0;
3769
4227
  }
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);
4228
+ try {
4229
+ const content = readFileSync(manifestPath, "utf8");
4230
+ const pkg = JSON.parse(content);
4231
+ const rowan = pkg.rowan;
4232
+ if (!rowan) return void 0;
4233
+ return {
4234
+ entry: rowan.extensions?.[0],
4235
+ phase: rowan.phase
4236
+ };
4237
+ } catch {
4238
+ return void 0;
3780
4239
  }
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
4240
  }
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".`);
4241
+ function jitiAliases() {
4242
+ const moduleUrl = import.meta.url;
4243
+ if (!moduleUrl) {
4244
+ return {};
3826
4245
  }
3827
- return {
3828
- systemPrompt: config.definition.content,
3829
- tools,
3830
- skills,
3831
- phases: { phases, entryPhaseId }
3832
- };
4246
+ const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
4247
+ if (!existsSync2(agentSourcePath)) {
4248
+ return {};
4249
+ }
4250
+ return { "@rowan-agent/agent": agentSourcePath };
3833
4251
  }
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);
4252
+ var sharedJiti;
4253
+ function getJiti() {
4254
+ sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
4255
+ fsCache: false,
4256
+ moduleCache: false,
4257
+ tryNative: false,
4258
+ alias: jitiAliases()
4259
+ });
4260
+ return sharedJiti;
4261
+ }
4262
+ async function loadExtensionModule(extensionPath) {
4263
+ const jiti = getJiti();
4264
+ const module = await jiti.import(extensionPath, { default: true });
4265
+ return typeof module === "function" ? module : void 0;
4266
+ }
4267
+ async function readPackageManifest(path) {
4268
+ if (!existsSync2(path)) {
4269
+ return void 0;
4270
+ }
4271
+ const content = await readFile3(path, "utf8");
4272
+ return JSON.parse(content);
4273
+ }
4274
+ async function resolveExtensionEntries(dir) {
4275
+ const manifest = await readPackageManifest(join2(dir, "package.json"));
4276
+ const declared = manifest?.rowan?.extensions;
4277
+ if (declared && declared.length > 0) {
4278
+ return declared.map((entry) => resolve4(dir, entry));
4279
+ }
4280
+ for (const name of ["index.ts", "index.js"]) {
4281
+ const entry = join2(dir, name);
4282
+ if (existsSync2(entry)) {
4283
+ return [entry];
4284
+ }
4285
+ }
4286
+ return void 0;
4287
+ }
4288
+ async function discoverExtensionsInDir(dir) {
4289
+ const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
4290
+ if (error.code === "ENOENT") {
4291
+ return [];
4292
+ }
4293
+ throw error;
4294
+ });
4295
+ const paths = [];
4296
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4297
+ const entryPath = join2(dir, entry.name);
4298
+ if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4299
+ paths.push(entryPath);
4300
+ continue;
4301
+ }
4302
+ if (entry.isDirectory() || entry.isSymbolicLink()) {
4303
+ const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
4304
+ if (entry.isDirectory() || info?.isDirectory()) {
4305
+ const resolvedEntries = await resolveExtensionEntries(entryPath);
4306
+ if (resolvedEntries) {
4307
+ paths.push(...resolvedEntries);
4308
+ }
4309
+ }
4310
+ }
3840
4311
  }
4312
+ return paths;
4313
+ }
4314
+ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4315
+ const resolvedCwd = resolve4(cwd);
4316
+ const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
4317
+ const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
4318
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
4319
+ const manifest = readManifestSync(manifestDir);
3841
4320
  return {
3842
- phases,
3843
- entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
4321
+ path: resolvedPath,
4322
+ name,
4323
+ factory,
4324
+ manifest
3844
4325
  };
3845
4326
  }
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 };
4327
+ async function loadExtensions(paths, cwd) {
4328
+ const extensions = [];
4329
+ const errors = [];
4330
+ const resolvedCwd = resolve4(cwd);
4331
+ for (const path of paths) {
4332
+ const resolvedPath = resolve4(resolvedCwd, path);
4333
+ try {
4334
+ const factory = await loadExtensionModule(resolvedPath);
4335
+ if (!factory) {
4336
+ errors.push({
4337
+ path: resolvedPath,
4338
+ error: `Extension does not export a valid factory function: ${path}`
4339
+ });
4340
+ continue;
4341
+ }
4342
+ extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
4343
+ } catch (error) {
4344
+ errors.push({
4345
+ path: resolvedPath,
4346
+ error: error instanceof Error ? error.message : String(error)
4347
+ });
3856
4348
  }
3857
- };
4349
+ }
4350
+ return { extensions, errors };
3858
4351
  }
3859
- function toLoopResult(result, toolCallId, toolName) {
4352
+ async function loadExtensionsFromPath(path) {
4353
+ const resolvedPath = resolve4(path);
4354
+ const info = await stat2(resolvedPath);
4355
+ if (!info.isDirectory()) {
4356
+ return loadExtensions([resolvedPath], dirname4(resolvedPath));
4357
+ }
4358
+ const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
4359
+ return loadExtensions(entries, resolvedPath);
4360
+ }
4361
+
4362
+ // src/runtime/resource-registry.ts
4363
+ import { stat as stat3, readdir as readdir4 } from "fs/promises";
4364
+ import { join as join4, resolve as resolve6 } from "path";
4365
+
4366
+ // src/harness/skills.ts
4367
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
4368
+ import { readdir as readdir3 } from "fs/promises";
4369
+ import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
4370
+ var SKILL_MARKER = "SKILL.md";
4371
+ function resolveSkillPath(input) {
4372
+ const resolved = resolve5(input);
4373
+ return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
4374
+ }
4375
+ async function loadSkill(path) {
4376
+ const resolved = resolveSkillPath(path);
4377
+ let loaded;
4378
+ try {
4379
+ loaded = await loadMarkdown(resolved);
4380
+ } catch (error) {
4381
+ if (!(error instanceof FrontmatterParseError)) throw error;
4382
+ warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
4383
+ throw new ResourceMetadataError("parse_failed", error.message);
4384
+ }
4385
+ const { frontmatter, body } = loaded;
4386
+ const metadata = frontmatter;
4387
+ const id = inferResourceName(resolved, SKILL_MARKER);
4388
+ const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
4389
+ const name = frontmatterName || id;
4390
+ const description = validateDescription(metadata.description);
4391
+ const diagnostics = validateResourceId(id);
4392
+ if (frontmatterName && frontmatterName !== id) {
4393
+ diagnostics.push(...validateResourceName(frontmatterName, id));
4394
+ }
4395
+ diagnostics.push(...description.warnings);
4396
+ warnResourceDiagnostics("skill", resolved, diagnostics);
4397
+ if (description.missing) {
4398
+ throw new ResourceMetadataError("invalid_metadata", "description is required");
4399
+ }
3860
4400
  return {
3861
- toolCallId,
3862
- toolName,
3863
- ok: result.ok,
3864
- content: result.content,
3865
- ...!result.ok ? { error: result.error } : {}
4401
+ name,
4402
+ description: description.description,
4403
+ filePath: resolved,
4404
+ baseDir: dirname5(resolved),
4405
+ content: body,
4406
+ disableModelInvocation: metadata["disable-model-invocation"] === true
3866
4407
  };
3867
4408
  }
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." };
4409
+ async function loadSkills(targetPath) {
4410
+ const skillsDir = resolve5(targetPath);
4411
+ if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
4412
+ try {
4413
+ return [await loadSkill(skillsDir)];
4414
+ } catch (error) {
4415
+ if (error instanceof ResourceMetadataError) return [];
4416
+ throw error;
4417
+ }
4418
+ }
4419
+ if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
4420
+ try {
4421
+ return [await loadSkill(skillsDir)];
4422
+ } catch (error) {
4423
+ if (error instanceof ResourceMetadataError) return [];
4424
+ throw error;
4425
+ }
4426
+ }
4427
+ const entries = await readdir3(skillsDir, { withFileTypes: true });
4428
+ const skills = [];
4429
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4430
+ if (!entry.isDirectory()) continue;
4431
+ const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
4432
+ if (!existsSync3(skillFile)) continue;
4433
+ try {
4434
+ const skill = await loadSkill(skillFile);
4435
+ skills.push(skill);
4436
+ } catch (error) {
4437
+ if (error instanceof ResourceMetadataError) continue;
4438
+ console.warn(`Failed to load skill "${entry.name}":`, error);
4439
+ }
4440
+ }
4441
+ return skills;
3870
4442
  }
3871
4443
 
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
4444
+ // src/runtime/resource-registry.ts
4445
+ var ResourceRegistryError = class extends Error {
4446
+ code;
4447
+ constructor(code, message) {
4448
+ super(message);
4449
+ this.name = "ResourceRegistryError";
4450
+ this.code = code;
4451
+ }
4452
+ };
4453
+ var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4454
+ var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
4455
+ var ResourceRegistry = class {
4456
+ sources = /* @__PURE__ */ new Map();
4457
+ /** Sources registered by the bootstrap boundary (currently Extensions). */
4458
+ implicitSources = /* @__PURE__ */ new Map();
4459
+ revisionSequence = 0;
4460
+ async loadAgents(input) {
4461
+ assertSourceId(input.sourceId);
4462
+ const collected = await this.collect("agent", input);
4463
+ return this.commit("agent", input.sourceId, collected.values, collected.skipped);
4464
+ }
4465
+ async loadSkills(input) {
4466
+ assertSourceId(input.sourceId);
4467
+ const collected = await this.collect("skill", input);
4468
+ return this.commit("skill", input.sourceId, collected.values, collected.skipped);
4469
+ }
4470
+ async loadPhases(input) {
4471
+ assertSourceId(input.sourceId);
4472
+ const collected = await this.collect("phase", input);
4473
+ return this.commit("phase", input.sourceId, collected.values, collected.skipped);
4474
+ }
4475
+ async loadTools(input) {
4476
+ assertSourceId(input.sourceId);
4477
+ return this.commit("tool", input.sourceId, input.values, []);
4478
+ }
4479
+ async unload(input) {
4480
+ assertSourceId(input.sourceId);
4481
+ if (this.hasImplicit(input.kind, input.sourceId)) {
4482
+ throw new ResourceRegistryError(
4483
+ "implicit_source_protected",
4484
+ `Implicit ${input.kind} source "${input.sourceId}" is managed by Runtime bootstrap.`
4485
+ );
4486
+ }
4487
+ const bySource = this.sources.get(input.kind);
4488
+ const existing = bySource?.get(input.sourceId);
4489
+ if (!existing) {
4490
+ return {
4491
+ revision: this.nextRevision(input.kind, input.sourceId),
4492
+ registered: [],
4493
+ skipped: []
3894
4494
  };
3895
- } else {
3896
- if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3897
- this.queue.push(event);
3898
4495
  }
3899
- this.trimText();
3900
- this.notify();
4496
+ bySource.delete(input.sourceId);
4497
+ return {
4498
+ revision: this.nextRevision(input.kind, input.sourceId),
4499
+ registered: [],
4500
+ skipped: []
4501
+ };
3901
4502
  }
3902
- shift() {
3903
- return this.queue.shift();
4503
+ resolveView(view) {
4504
+ const sourceIds = {
4505
+ agents: this.withImplicit("agent", view.agents),
4506
+ tools: this.withImplicit("tool", view.tools),
4507
+ skills: this.withImplicit("skill", view.skills),
4508
+ phases: this.withImplicit("phase", view.phases)
4509
+ };
4510
+ return {
4511
+ agents: this.resolveKind("agent", sourceIds.agents),
4512
+ tools: this.resolveKind("tool", sourceIds.tools),
4513
+ skills: this.resolveKind("skill", sourceIds.skills),
4514
+ phases: this.resolveKind("phase", sourceIds.phases),
4515
+ revisions: {
4516
+ agent: this.resolveRevisions("agent", sourceIds.agents),
4517
+ tool: this.resolveRevisions("tool", sourceIds.tools),
4518
+ skill: this.resolveRevisions("skill", sourceIds.skills),
4519
+ phase: this.resolveRevisions("phase", sourceIds.phases)
4520
+ },
4521
+ refs: {
4522
+ agent: this.resolveRefs("agent", sourceIds.agents),
4523
+ tool: this.resolveRefs("tool", sourceIds.tools),
4524
+ skill: this.resolveRefs("skill", sourceIds.skills),
4525
+ phase: this.resolveRefs("phase", sourceIds.phases)
4526
+ }
4527
+ };
3904
4528
  }
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);
4529
+ /** Mark one already-loaded source as implicit for every Resource View. */
4530
+ markImplicit(kind, sourceId) {
4531
+ assertSourceId(sourceId);
4532
+ const entries = this.implicitSources.get(kind) ?? [];
4533
+ if (!entries.includes(sourceId)) entries.push(sourceId);
4534
+ this.implicitSources.set(kind, entries);
4535
+ }
4536
+ /** Bootstrap-only helper for replacing an implicit contribution source. */
4537
+ async replaceImplicit(kind, sourceId, values) {
4538
+ const result = await this.commit(kind, sourceId, values, []);
4539
+ this.markImplicit(kind, sourceId);
4540
+ return result;
4541
+ }
4542
+ hasImplicit(kind, sourceId) {
4543
+ return this.implicitSources.get(kind)?.includes(sourceId) ?? false;
4544
+ }
4545
+ withImplicit(kind, sourceIds) {
4546
+ const combined = [...sourceIds];
4547
+ for (const sourceId of this.implicitSources.get(kind) ?? []) {
4548
+ if (!combined.includes(sourceId)) combined.push(sourceId);
4549
+ }
4550
+ return combined;
4551
+ }
4552
+ async collect(kind, input) {
4553
+ const hasDirectory = input.directory !== void 0;
4554
+ const hasValues = input.values !== void 0;
4555
+ if (!hasDirectory && !hasValues) {
4556
+ throw new ResourceRegistryError(
4557
+ "resource_input_missing",
4558
+ `Resource source "${input.sourceId}" requires directory or values.`
4559
+ );
4560
+ }
4561
+ const directoryValues = hasDirectory ? await loadDirectoryValues(kind, input.directory, input.sourceId) : { values: [], skipped: [] };
4562
+ return {
4563
+ values: [...directoryValues.values, ...input.values ?? []],
4564
+ skipped: directoryValues.skipped
4565
+ };
4566
+ }
4567
+ async commit(kind, sourceId, values, skipped) {
4568
+ assertSourceId(sourceId);
4569
+ const names = /* @__PURE__ */ new Set();
4570
+ for (const value of values) {
4571
+ assertNamedResource(value, kind, sourceId);
4572
+ if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
4573
+ throw new ResourceRegistryError(
4574
+ "resource_collision",
4575
+ `${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
4576
+ );
4577
+ }
4578
+ if (names.has(value.name)) {
4579
+ throw new ResourceRegistryError(
4580
+ "resource_collision",
4581
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in source "${sourceId}".`
4582
+ );
3910
4583
  }
4584
+ names.add(value.name);
4585
+ }
4586
+ const revision = this.nextRevision(kind, sourceId);
4587
+ const record = {
4588
+ kind,
4589
+ sourceId,
4590
+ revision,
4591
+ values: Object.freeze([...values])
4592
+ };
4593
+ let bySource = this.sources.get(kind);
4594
+ if (!bySource) {
4595
+ bySource = /* @__PURE__ */ new Map();
4596
+ this.sources.set(kind, bySource);
3911
4597
  }
3912
- this.notify();
4598
+ bySource.set(sourceId, record);
4599
+ return {
4600
+ revision,
4601
+ registered: values.map((value) => ({ kind, sourceId, name: value.name })),
4602
+ skipped
4603
+ };
3913
4604
  }
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);
4605
+ resolveKind(kind, sourceIds) {
4606
+ const names = /* @__PURE__ */ new Set();
4607
+ const values = [];
4608
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4609
+ for (const value of source.values) {
4610
+ if (names.has(value.name)) {
4611
+ throw new ResourceRegistryError(
4612
+ "resource_collision",
4613
+ `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
4614
+ );
4615
+ }
4616
+ names.add(value.name);
4617
+ values.push(value);
4618
+ }
3918
4619
  }
3919
- this.notify();
4620
+ return values;
3920
4621
  }
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);
4622
+ resolveRevisions(kind, sourceIds) {
4623
+ return this.sourcesFor(kind, sourceIds).map(({ revision }) => revision);
4624
+ }
4625
+ resolveRefs(kind, sourceIds) {
4626
+ const seenNames = /* @__PURE__ */ new Set();
4627
+ const refs = [];
4628
+ for (const source of this.sourcesFor(kind, sourceIds)) {
4629
+ for (const value of source.values) {
4630
+ if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
4631
+ seenNames.add(value.name);
4632
+ refs.push({ kind, sourceId: source.sourceId, name: value.name });
4633
+ }
3925
4634
  }
3926
- this.notify();
4635
+ return refs;
4636
+ }
4637
+ sourcesFor(kind, sourceIds) {
4638
+ const bySource = this.sources.get(kind);
4639
+ const seen = /* @__PURE__ */ new Set();
4640
+ const sources = [];
4641
+ for (const sourceId of sourceIds) {
4642
+ assertSourceId(sourceId);
4643
+ if (seen.has(sourceId)) continue;
4644
+ seen.add(sourceId);
4645
+ const source = bySource?.get(sourceId);
4646
+ if (!source) {
4647
+ throw new ResourceRegistryError(
4648
+ "unknown_resource_source",
4649
+ `Unknown resource source "${sourceId}" for ${kind}.`
4650
+ );
4651
+ }
4652
+ sources.push(source);
4653
+ }
4654
+ return sources;
3927
4655
  }
3928
- checkpoint() {
3929
- return this.version;
4656
+ nextRevision(kind, sourceId) {
4657
+ this.revisionSequence += 1;
4658
+ return `${kind}:${sourceId}:${this.revisionSequence}`;
3930
4659
  }
3931
- changed(since) {
3932
- if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3933
- return this.wake.promise;
4660
+ };
4661
+ function assertSourceId(sourceId) {
4662
+ if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
4663
+ throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
3934
4664
  }
3935
- stop() {
3936
- if (this.stopped) return;
3937
- this.stopped = true;
3938
- this.queue.length = 0;
3939
- this.notify();
4665
+ }
4666
+ function assertNamedResource(value, kind, sourceId) {
4667
+ if (!value || typeof value !== "object" || typeof value.name !== "string") {
4668
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${kind} resource in source "${sourceId}".`);
3940
4669
  }
3941
- notify() {
3942
- const wake = this.wake;
3943
- this.wake = deferred();
3944
- this.version += 1;
3945
- wake.resolve();
4670
+ const name = value.name;
4671
+ if (!RESOURCE_NAME.test(name)) {
4672
+ throw new ResourceRegistryError("invalid_resource_name", `Invalid ${capitalize(kind)} resource name "${name}".`);
3946
4673
  }
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)
4674
+ }
4675
+ function capitalize(value) {
4676
+ return value.charAt(0).toUpperCase() + value.slice(1);
4677
+ }
4678
+ function isImplicitCoreName(kind, name) {
4679
+ return kind === "tool" && name === "route" || kind === "phase" && name === "default";
4680
+ }
4681
+ async function loadDirectoryValues(kind, directory, sourceId) {
4682
+ const root = resolve6(directory);
4683
+ let rootInfo;
4684
+ try {
4685
+ rootInfo = await stat3(root);
4686
+ } catch (error) {
4687
+ if (isMissingPath(error)) {
4688
+ return {
4689
+ values: [],
4690
+ skipped: [{ kind: "missing", sourceId, path: root, message: `Resource directory "${root}" does not exist.` }]
4691
+ };
4692
+ }
4693
+ return {
4694
+ values: [],
4695
+ skipped: [{
4696
+ kind: "invalid",
4697
+ sourceId,
4698
+ path: root,
4699
+ message: `Resource directory "${root}" could not be read: ${error instanceof Error ? error.message : String(error)}`
4700
+ }]
4701
+ };
4702
+ }
4703
+ const marker = markerFor(kind);
4704
+ const targets = [];
4705
+ if (rootInfo.isFile()) {
4706
+ targets.push(root);
4707
+ } else if (rootInfo.isDirectory()) {
4708
+ const markerAtRoot = join4(root, marker);
4709
+ try {
4710
+ const markerInfo = await stat3(markerAtRoot);
4711
+ if (markerInfo.isFile()) targets.push(markerAtRoot);
4712
+ } catch (error) {
4713
+ if (!isMissingPath(error)) throw error;
4714
+ let entries;
4715
+ try {
4716
+ entries = await readdir4(root, { withFileTypes: true });
4717
+ } catch (error2) {
4718
+ return {
4719
+ values: [],
4720
+ skipped: [{
4721
+ kind: "invalid",
4722
+ sourceId,
4723
+ path: root,
4724
+ message: `Resource directory "${root}" could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
4725
+ }]
3965
4726
  };
3966
- buffered -= overflow;
4727
+ }
4728
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
4729
+ if (!entry.isDirectory()) continue;
4730
+ const target = join4(root, entry.name, marker);
4731
+ try {
4732
+ const targetInfo = await stat3(target);
4733
+ if (targetInfo.isFile()) targets.push(target);
4734
+ } catch (targetError) {
4735
+ if (!isMissingPath(targetError)) throw targetError;
4736
+ }
3967
4737
  }
3968
4738
  }
3969
4739
  }
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;
4740
+ const values = [];
4741
+ const skipped = [];
4742
+ for (const target of targets) {
4743
+ try {
4744
+ const value = await loadDirectoryValue(kind, target);
4745
+ values.push(value);
4746
+ } catch (error) {
4747
+ skipped.push({
4748
+ kind: "invalid",
4749
+ sourceId,
4750
+ path: target,
4751
+ message: error instanceof Error ? error.message : String(error)
4752
+ });
4753
+ }
3979
4754
  }
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);
4755
+ return { values, skipped };
4756
+ }
4757
+ async function loadDirectoryValue(kind, target) {
4758
+ switch (kind) {
4759
+ case "agent": {
4760
+ let loaded;
4761
+ try {
4762
+ loaded = await loadMarkdown(target);
4763
+ } catch (error) {
4764
+ if (error instanceof FrontmatterParseError) {
4765
+ throw new TypeError(`frontmatter could not be parsed: ${error.message}`);
4766
+ }
4767
+ throw error;
4768
+ }
4769
+ return normalizeAgentDefinition(
4770
+ loaded.frontmatter,
4771
+ loaded.body,
4772
+ { fallbackName: inferResourceName(target, "AGENT.md") }
4773
+ );
4774
+ }
4775
+ case "skill":
4776
+ return await loadSkill(target);
4777
+ case "phase":
4778
+ return await loadPhase(target);
4779
+ case "tool":
4780
+ throw new ResourceRegistryError(
4781
+ "resource_directory_not_supported",
4782
+ "Tool sources accept inline values only."
4783
+ );
3985
4784
  }
3986
- publish(event) {
3987
- for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3988
- subscription.push(event);
4785
+ }
4786
+ function markerFor(kind) {
4787
+ switch (kind) {
4788
+ case "agent":
4789
+ return "AGENT.md";
4790
+ case "skill":
4791
+ return "SKILL.md";
4792
+ case "phase":
4793
+ return "PHASE.md";
4794
+ case "tool":
4795
+ return "";
4796
+ }
4797
+ }
4798
+ function isMissingPath(error) {
4799
+ return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
4800
+ }
4801
+
4802
+ // src/runtime/extension-lifetime.ts
4803
+ var ExtensionLifetimeError = class extends Error {
4804
+ code;
4805
+ constructor(code, message) {
4806
+ super(message);
4807
+ this.name = "ExtensionLifetimeError";
4808
+ this.code = code;
4809
+ }
4810
+ };
4811
+ var RuntimeExtensionLifetime = class {
4812
+ runner;
4813
+ frozen = false;
4814
+ closed = false;
4815
+ constructor(options = {}) {
4816
+ this.runner = createExtensionRunner(options);
4817
+ }
4818
+ async activate(extensions) {
4819
+ if (this.closed) throw new ExtensionLifetimeError("extensions_closed", "The Extension Runtime has been closed.");
4820
+ if (this.frozen) throw new ExtensionLifetimeError("extensions_frozen", "Extensions are frozen until Runtime restart.");
4821
+ const active = [];
4822
+ const errors = [];
4823
+ for (const extension of extensions) {
4824
+ try {
4825
+ await this.runner.loadExtensions([extension]);
4826
+ active.push(extension.name);
4827
+ } catch (error) {
4828
+ errors.push({
4829
+ path: extension.path,
4830
+ error: error instanceof Error ? error.message : String(error)
4831
+ });
4832
+ }
3989
4833
  }
4834
+ this.runner.bind();
4835
+ this.frozen = true;
4836
+ return { active, errors };
3990
4837
  }
3991
- clear(runId, executionId) {
3992
- for (const subscription of this.subscriptions.get(runId) ?? []) {
3993
- subscription.clear(executionId);
4838
+ tools() {
4839
+ return this.runner.getAllRegisteredTools();
4840
+ }
4841
+ phases() {
4842
+ return this.runner.getPhases();
4843
+ }
4844
+ get extensionRunner() {
4845
+ return this.runner;
4846
+ }
4847
+ async close() {
4848
+ if (this.closed) return;
4849
+ this.closed = true;
4850
+ await this.runner.close();
4851
+ }
4852
+ };
4853
+ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
4854
+ constructor(extensions = new RuntimeExtensionLifetime()) {
4855
+ super();
4856
+ this.extensions = extensions;
4857
+ }
4858
+ extensions;
4859
+ extensionSourceId = "rowan.extensions";
4860
+ /** Install Rowan-owned implicit resources before any host source is read. */
4861
+ async ensureCoreResources() {
4862
+ await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
4863
+ await this.replaceImplicit("phase", "rowan.core", [createDefaultPhase()]);
4864
+ }
4865
+ async loadExtensions(input) {
4866
+ const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
4867
+ const descriptor = input;
4868
+ return descriptor.directory ? loadExtensionsFromPath(descriptor.directory) : Promise.resolve({ extensions: [...descriptor.values ?? []], errors: [] });
4869
+ })();
4870
+ const normalized = await loaded;
4871
+ const result = await this.extensions.activate(normalized.extensions);
4872
+ await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool2));
4873
+ await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
4874
+ return {
4875
+ ...result,
4876
+ revision: `extensions:${Date.now()}`,
4877
+ registered: result.active,
4878
+ skipped: normalized.errors.map((error) => ({
4879
+ kind: "invalid",
4880
+ sourceId: "rowan.extensions",
4881
+ path: error.path,
4882
+ message: error.error
4883
+ }))
4884
+ };
4885
+ }
4886
+ get extensionRunner() {
4887
+ return this.extensions.extensionRunner;
4888
+ }
4889
+ get extensionTools() {
4890
+ return this.extensions.tools();
4891
+ }
4892
+ get extensionPhases() {
4893
+ return this.extensions.phases();
4894
+ }
4895
+ async closeExtensions() {
4896
+ await this.extensions.close();
4897
+ }
4898
+ };
4899
+ function adaptExtensionTool2(input) {
4900
+ const definition = input.definition;
4901
+ return {
4902
+ name: definition.name,
4903
+ description: definition.description,
4904
+ parameters: definition.parameters,
4905
+ execute: async (args, context, signal) => {
4906
+ const result = await definition.execute(args, signal);
4907
+ const content = JSON.parse(JSON.stringify(result.content));
4908
+ return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
3994
4909
  }
4910
+ };
4911
+ }
4912
+
4913
+ // src/runtime/configuration-snapshot.ts
4914
+ function resolveConfigurationSnapshot(registry, input) {
4915
+ const resolved = registry.resolveView(input.resourceView);
4916
+ const base = resolved.agents.find(({ name }) => name === input.definition.name);
4917
+ if (!base) {
4918
+ throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
4919
+ }
4920
+ const layer = input.definition.layer;
4921
+ const definition = applyDefinitionLayer(base, layer);
4922
+ const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
4923
+ const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
4924
+ const phases = resolvePhases(resolved.phases, definition.phases);
4925
+ const contexts = selectNamedResources(input.contexts ?? [], base.context, "Context");
4926
+ return {
4927
+ identity: input.identity,
4928
+ definition,
4929
+ resources: {
4930
+ tools,
4931
+ skills,
4932
+ ...phases ? { phases } : {},
4933
+ revisions: resolved.revisions,
4934
+ refs: {
4935
+ agent: selectedRefs(resolved.refs.agent, [base.name]),
4936
+ tool: selectedRefs(resolved.refs.tool, tools.map(({ name }) => name)),
4937
+ skill: selectedRefs(resolved.refs.skill, skills.map(({ name }) => name)),
4938
+ phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
4939
+ }
4940
+ },
4941
+ contexts,
4942
+ resourceView: input.resourceView,
4943
+ ...input.cwd === void 0 ? {} : { cwd: input.cwd },
4944
+ ...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
4945
+ ..."stream" in input && input.stream ? { model: layer?.model ?? input.model, stream: input.stream } : { model: layer?.model ?? input.model }
4946
+ };
4947
+ }
4948
+ function materializeConfigurationSnapshot(snapshot) {
4949
+ return {
4950
+ identity: snapshot.identity,
4951
+ definition: snapshot.definition,
4952
+ resources: {
4953
+ tools: snapshot.resources.tools,
4954
+ skills: snapshot.resources.skills,
4955
+ ...snapshot.resources.phases ? { phases: snapshot.resources.phases } : {},
4956
+ ...snapshot.contexts.length > 0 ? { contexts: snapshot.contexts } : {},
4957
+ resourceView: snapshot.resourceView,
4958
+ resourceRefs: [
4959
+ ...snapshot.resources.refs.agent,
4960
+ ...snapshot.resources.refs.tool,
4961
+ ...snapshot.resources.refs.skill,
4962
+ ...snapshot.resources.refs.phase
4963
+ ],
4964
+ resourceRevisions: snapshot.resources.revisions
4965
+ },
4966
+ ...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
4967
+ ...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
4968
+ ..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
4969
+ };
4970
+ }
4971
+ function applyDefinitionLayer(base, layer) {
4972
+ if (!layer) return { ...base };
4973
+ return {
4974
+ ...base,
4975
+ ...layer.description === void 0 ? {} : { description: layer.description },
4976
+ ...layer.content === void 0 ? {} : { content: layer.content },
4977
+ ...layer.model === void 0 ? {} : { model: layer.model },
4978
+ ...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
4979
+ ...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
4980
+ ...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
4981
+ };
4982
+ }
4983
+ function intersectNames(parent, layer, kind) {
4984
+ if (parent === void 0) return [...layer];
4985
+ const parentNames = new Set(parent);
4986
+ for (const name of layer) {
4987
+ if (!parentNames.has(name)) console.warn(`${kind} "${name}" is not available and will be skipped.`);
3995
4988
  }
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);
4989
+ return layer.filter((name) => parentNames.has(name));
4990
+ }
4991
+ function intersectPhaseSelection(parent, layer) {
4992
+ if (!parent) return layer;
4993
+ const parentNames = new Set(parent.phaseIds);
4994
+ for (const name of layer.phaseIds) {
4995
+ if (!parentNames.has(name)) console.warn(`Phase "${name}" is not available and will be skipped.`);
4001
4996
  }
4002
- close() {
4003
- for (const subscriptions of this.subscriptions.values()) {
4004
- for (const subscription of subscriptions) subscription.stop();
4005
- }
4006
- this.subscriptions.clear();
4997
+ return {
4998
+ entryPhaseId: layer.entryPhaseId,
4999
+ phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
5000
+ };
5001
+ }
5002
+ function resolvePhases(candidates, selection) {
5003
+ const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
5004
+ if (selected.length === 0 && selection === void 0) return void 0;
5005
+ const entryPhaseId = selection?.entryPhaseId ?? null;
5006
+ if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
5007
+ console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
5008
+ return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
4007
5009
  }
4008
- };
5010
+ return {
5011
+ phases: new Map(selected.map((phase) => [phase.name, phase])),
5012
+ entryPhaseId
5013
+ };
5014
+ }
5015
+ function selectedRefs(refs, names) {
5016
+ const selected = new Set(names);
5017
+ return refs.filter(({ name }) => selected.has(name));
5018
+ }
4009
5019
 
4010
5020
  // src/runtime/durable-runtime.ts
4011
5021
  var DEFAULT_CONCURRENCY = 10;
@@ -4022,14 +5032,16 @@ var AgentRuntime = class _AgentRuntime {
4022
5032
  executions = /* @__PURE__ */ new Map();
4023
5033
  consumers = /* @__PURE__ */ new Map();
4024
5034
  transientEvents = new TransientRunEventHub();
5035
+ resources;
4025
5036
  heartbeat;
4026
5037
  pumping = false;
4027
5038
  closed = false;
4028
- constructor(options, owned) {
5039
+ constructor(options, owned, resources) {
4029
5040
  this.owned = owned;
4030
5041
  this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
4031
5042
  this.storeIncarnation = String(owned.lease.token).split(":")[0];
4032
5043
  this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
5044
+ this.resources = resources;
4033
5045
  }
4034
5046
  static async init(options) {
4035
5047
  if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
@@ -4037,10 +5049,38 @@ var AgentRuntime = class _AgentRuntime {
4037
5049
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
4038
5050
  if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
4039
5051
  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;
5052
+ const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
5053
+ try {
5054
+ await runtime.resources.ensureCoreResources();
5055
+ await options.bootstrap?.(runtime.resources);
5056
+ runtime.startHeartbeat();
5057
+ void runtime.pump();
5058
+ return runtime;
5059
+ } catch (error) {
5060
+ await runtime.resources.closeExtensions().catch(() => void 0);
5061
+ await owned.sealAndReleaseOwner().catch(() => void 0);
5062
+ throw error;
5063
+ }
5064
+ }
5065
+ async loadAgents(input) {
5066
+ this.assertOpen();
5067
+ return this.resources.loadAgents(input);
5068
+ }
5069
+ async loadSkills(input) {
5070
+ this.assertOpen();
5071
+ return this.resources.loadSkills(input);
5072
+ }
5073
+ async loadPhases(input) {
5074
+ this.assertOpen();
5075
+ return this.resources.loadPhases(input);
5076
+ }
5077
+ async loadTools(input) {
5078
+ this.assertOpen();
5079
+ return this.resources.loadTools(input);
5080
+ }
5081
+ async unload(input) {
5082
+ this.assertOpen();
5083
+ return this.resources.unload(input);
4044
5084
  }
4045
5085
  async createAgent(config, options = {}) {
4046
5086
  this.assertOpen();
@@ -4100,6 +5140,7 @@ var AgentRuntime = class _AgentRuntime {
4100
5140
  this.executions.clear();
4101
5141
  this.transientEvents.close();
4102
5142
  for (const subscription of this.consumers.values()) subscription.controller.abort();
5143
+ await this.resources.closeExtensions();
4103
5144
  await this.owned.sealAndReleaseOwner();
4104
5145
  }
4105
5146
  async snapshot(runId) {
@@ -4157,7 +5198,7 @@ var AgentRuntime = class _AgentRuntime {
4157
5198
  let executionRevision = run.revision;
4158
5199
  try {
4159
5200
  const agent = await this.requireAgent(run.agentId);
4160
- const token = run.pinnedConfigToken ?? agent.currentConfigToken;
5201
+ let token = run.pinnedConfigToken ?? agent.currentConfigToken;
4161
5202
  if (!token) {
4162
5203
  await this.failQueued(run, "Agent has no Config Token.");
4163
5204
  return;
@@ -4177,13 +5218,28 @@ var AgentRuntime = class _AgentRuntime {
4177
5218
  await this.failQueued(run, resolution.reason);
4178
5219
  return;
4179
5220
  }
5221
+ let resolvedConfig = resolution.config;
5222
+ if (isAgentConfiguration(resolvedConfig)) {
5223
+ try {
5224
+ const snapshot = this.materializeConfig(resolvedConfig);
5225
+ token = await this.commands.storeSnapshot({
5226
+ agent,
5227
+ config: snapshot,
5228
+ operationId: `run-snapshot:${run.id}`
5229
+ });
5230
+ resolvedConfig = snapshot;
5231
+ } catch (error) {
5232
+ await this.failQueued(run, error instanceof Error ? error.message : "Configuration Snapshot failed.");
5233
+ return;
5234
+ }
5235
+ }
4180
5236
  const executionId = createId("exec");
4181
5237
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
4182
5238
  executionRevision = claim.run.revision;
4183
5239
  const controller = new AbortController();
4184
5240
  this.executions.set(run.id, { controller, executionId });
4185
- const config = resolution.config;
4186
- const assembly = await assembleExtensions(config);
5241
+ const config = resolvedConfig;
5242
+ const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
4187
5243
  let toolQueue = Promise.resolve();
4188
5244
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
4189
5245
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
@@ -4207,7 +5263,9 @@ var AgentRuntime = class _AgentRuntime {
4207
5263
  execution: {
4208
5264
  agentId: run.agentId,
4209
5265
  runId: run.id,
4210
- executionId: claim.execution.executionId
5266
+ executionId: claim.execution.executionId,
5267
+ ...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
5268
+ ...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
4211
5269
  },
4212
5270
  model,
4213
5271
  stream,
@@ -4236,6 +5294,7 @@ var AgentRuntime = class _AgentRuntime {
4236
5294
  const task = toolQueue.then(async () => {
4237
5295
  const execution = await this.executeToolBatch({
4238
5296
  run,
5297
+ agentMetadata: agent.metadata,
4239
5298
  execution: claim.execution,
4240
5299
  expectedRevision: executionRevision,
4241
5300
  toolConfig: executionTools,
@@ -4252,6 +5311,7 @@ var AgentRuntime = class _AgentRuntime {
4252
5311
  const task = toolQueue.then(async () => {
4253
5312
  const execution = await this.executeToolBatch({
4254
5313
  run,
5314
+ agentMetadata: agent.metadata,
4255
5315
  execution: claim.execution,
4256
5316
  expectedRevision: executionRevision,
4257
5317
  toolConfig: executionTools,
@@ -4346,6 +5406,8 @@ var AgentRuntime = class _AgentRuntime {
4346
5406
  const context = {
4347
5407
  agentId: input.run.agentId,
4348
5408
  runId: input.run.id,
5409
+ ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
5410
+ ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
4349
5411
  toolCallId,
4350
5412
  reportProgress: (progress) => {
4351
5413
  const active = this.executions.get(input.run.id);
@@ -4489,6 +5551,10 @@ var AgentRuntime = class _AgentRuntime {
4489
5551
  assertOpen() {
4490
5552
  if (this.closed) throw new RuntimeError("runtime_closed", null);
4491
5553
  }
5554
+ materializeConfig(config) {
5555
+ if (!isAgentConfiguration(config)) return config;
5556
+ return materializeConfigurationSnapshot(resolveConfigurationSnapshot(this.resources, config));
5557
+ }
4492
5558
  startHeartbeat() {
4493
5559
  this.heartbeat = setInterval(() => {
4494
5560
  void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
@@ -4552,10 +5618,10 @@ function boundaryFromSnapshot(snapshot) {
4552
5618
  }
4553
5619
  }
4554
5620
  function delay(ms) {
4555
- return new Promise((resolve6) => setTimeout(resolve6, ms));
5621
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
4556
5622
  }
4557
5623
  function normalizePhaseRegistry(registry) {
4558
- if (registry?.phases.has(DEFAULT_PHASE_ID)) {
5624
+ if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
4559
5625
  throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
4560
5626
  }
4561
5627
  return {
@@ -4579,13 +5645,13 @@ function toJsonValue(value) {
4579
5645
  return value;
4580
5646
  }
4581
5647
  function deferred2() {
4582
- let resolve6;
5648
+ let resolve7;
4583
5649
  let reject;
4584
5650
  const promise = new Promise((res, rej) => {
4585
- resolve6 = (value) => res(value);
5651
+ resolve7 = (value) => res(value);
4586
5652
  reject = rej;
4587
5653
  });
4588
- return { promise, resolve: resolve6, reject };
5654
+ return { promise, resolve: resolve7, reject };
4589
5655
  }
4590
5656
 
4591
5657
  // src/runtime/durable-store.ts
@@ -6036,236 +7102,6 @@ function assertOwnerInput(ownerId, leaseMs) {
6036
7102
  if (!Number.isFinite(leaseMs) || leaseMs <= 0) throw new TypeError("leaseMs must be a positive finite number");
6037
7103
  }
6038
7104
 
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
7105
  // src/index.ts
6270
7106
  function createCoreTools2(input = {}) {
6271
7107
  return createCoreTools(input).map((tool) => ({
@@ -6280,9 +7116,14 @@ function createCoreTools2(input = {}) {
6280
7116
  }
6281
7117
  export {
6282
7118
  AgentRuntime,
7119
+ ExtensionLifetimeError,
6283
7120
  InMemoryConfigProvider,
6284
7121
  InMemoryStore,
7122
+ ResourceRegistry,
7123
+ ResourceRegistryError,
7124
+ RuntimeBootstrapRegistry,
6285
7125
  RuntimeError,
7126
+ RuntimeExtensionLifetime,
6286
7127
  SqliteStore,
6287
7128
  brandConfigToken,
6288
7129
  createCoreTools2 as createCoreTools,
@@ -6290,6 +7131,8 @@ export {
6290
7131
  loadExtensionsFromPath as loadExtensions,
6291
7132
  loadPhases,
6292
7133
  loadSkills,
7134
+ materializeConfigurationSnapshot,
6293
7135
  parseAgentDefinition,
6294
- parseFrontmatter
7136
+ parseFrontmatter,
7137
+ resolveConfigurationSnapshot
6295
7138
  };