@rowan-agent/agent 0.8.6 → 0.9.0

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/README.md CHANGED
@@ -11,24 +11,39 @@ import {
11
11
  AgentRuntime,
12
12
  createCoreTools,
13
13
  InMemoryStore,
14
+ loadPhases,
15
+ loadSkills,
14
16
  } from "@rowan-agent/agent";
15
17
 
18
+ const skills = await loadSkills("./.rowan/skills");
19
+ const phases = await loadPhases("./.rowan/phases");
16
20
  const runtime = await AgentRuntime.init({
17
21
  store: new InMemoryStore(),
18
22
  });
19
23
 
20
24
  const agentId = await runtime.createAgent({
21
25
  identity: "example:v1", // Stable config snapshot identity, not the Agent ID
22
- model: { provider: "openai", id: "gpt-4o" },
23
- stream,
26
+ model: {
27
+ provider: "openai",
28
+ id: "gpt-4o",
29
+ protocol: "openai-responses",
30
+ baseUrl: "https://api.openai.com/v1",
31
+ apiKey: process.env.OPENAI_API_KEY!,
32
+ },
24
33
  definition: {
25
34
  name: "workspace-assistant",
26
35
  description: "Assist with the current workspace.",
27
- content: "You are helpful.",
36
+ prompt: "You are helpful.",
37
+ contexts: ["workspace"],
28
38
  },
29
39
  resources: {
30
40
  tools: createCoreTools({ root: process.cwd() }),
31
- skills: [],
41
+ skills,
42
+ contexts: [{
43
+ name: "workspace",
44
+ value: { root: process.cwd() },
45
+ }],
46
+ phases,
32
47
  },
33
48
  });
34
49
 
@@ -126,7 +141,7 @@ await runtime.loadAgents({
126
141
  values: [{
127
142
  name: "workspace-assistant",
128
143
  description: "Assist with the current workspace.",
129
- content: "You are helpful.",
144
+ prompt: "You are helpful.",
130
145
  }],
131
146
  });
132
147
  await runtime.loadTools({
@@ -143,8 +158,13 @@ const agentId = await runtime.createAgent({
143
158
  skills: [],
144
159
  phases: [],
145
160
  },
146
- model: { provider: "openai", id: "gpt-4o" },
147
- stream,
161
+ model: {
162
+ provider: "openai",
163
+ id: "gpt-4o",
164
+ protocol: "openai-responses",
165
+ baseUrl: "https://api.openai.com/v1",
166
+ apiKey: process.env.OPENAI_API_KEY!,
167
+ },
148
168
  });
149
169
  ```
150
170
 
@@ -156,4 +176,53 @@ Tool and `default` Phase are always available and cannot be overridden.
156
176
  Extensions are Runtime-global. Load them only during `AgentRuntime.init()` via
157
177
  `bootstrap`; after initialization they are frozen until the Runtime closes.
158
178
  Definition name lists narrow the selected Tools, Skills, and Phases: omission
159
- inherits all candidates, `[]` selects none, and missing names are skipped.
179
+ inherits all candidates, `[]` selects none, and missing names are skipped. The
180
+ same rule applies to `definition.contexts`.
181
+
182
+ ### Resources and Definition
183
+
184
+ `resources` and `definition` have different jobs:
185
+
186
+ - `resources` supplies the concrete candidates available to one Agent. Its
187
+ Tools contain executable `execute()` functions; Skills and Phases contain
188
+ their loaded content; Contexts contain JSON-safe values.
189
+ - `definition` declares which candidates this Agent uses. `tools`, `skills`,
190
+ `contexts`, and `phases` are name-based selectors; they cannot create a
191
+ resource that is absent from `resources`.
192
+
193
+ For a single-process embedding, provide concrete resources directly and omit
194
+ the selectors when the Agent should use everything:
195
+
196
+ ```ts
197
+ const agentId = await runtime.createAgent({
198
+ identity: "workspace:v1",
199
+ definition: {
200
+ name: "workspace-assistant",
201
+ description: "Assist with the current workspace.",
202
+ prompt: "You are helpful.",
203
+ },
204
+ resources: { tools, skills, contexts, phases },
205
+ model,
206
+ });
207
+ ```
208
+
209
+ Use selectors when several Agents share a candidate pool:
210
+
211
+ ```ts
212
+ definition: {
213
+ name: "read-only-assistant",
214
+ description: "Inspect the workspace without changing it.",
215
+ prompt: "You are helpful.",
216
+ tools: ["read"],
217
+ contexts: ["workspace"],
218
+ phases: { entryPhaseId: "review", phaseIds: ["review"] },
219
+ }
220
+ ```
221
+
222
+ For process-boundary persistence, use the Resource Registry form shown above:
223
+ `resourceView` stores stable source IDs instead of executable resource
224
+ closures, and the Runtime resolves those sources into an immutable
225
+ Configuration Snapshot. This lets a restarted Runtime resolve the same
226
+ resource revisions while keeping each Run pinned to the snapshot that created
227
+ it. Direct `resources` are simpler for embedding; `resourceView` is the
228
+ declarative form for shared, reloadable, and restart-resolvable resources.
package/dist/index.d.ts CHANGED
@@ -83,23 +83,6 @@ interface SourceInfo {
83
83
  baseDir?: string;
84
84
  displayName?: string;
85
85
  }
86
- /** Phase run function type for extensions */
87
- type PhaseRun = (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
88
- /** Phase definition shape used by extensions */
89
- type PhaseDefinition = {
90
- name: string;
91
- description: string;
92
- run?: PhaseRun;
93
- tools?: string[];
94
- skills?: string[];
95
- target?: string;
96
- input?: Record<string, string>;
97
- model?: string;
98
- };
99
- type PhaseRegistration = Omit<PhaseDefinition, 'run'> & {
100
- /** Optional execution override — takes over model invocation */
101
- run?: PhaseRun;
102
- };
103
86
  /**
104
87
  * Tool definition for registering LLM-callable tools via `api.registerTool()`.
105
88
  *
@@ -214,12 +197,6 @@ interface LoadedExtension {
214
197
  interface ExtensionManifest {
215
198
  entry?: string;
216
199
  name?: string;
217
- phase?: {
218
- name?: string;
219
- description?: string;
220
- tools?: string[];
221
- skills?: string[];
222
- };
223
200
  }
224
201
 
225
202
  /**
@@ -438,8 +415,8 @@ interface ExtensionAPI {
438
415
  off<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
439
416
  /** Register a custom LLM-callable tool. */
440
417
  registerTool(tool: ToolDefinition): void;
441
- /** Register a custom phase. */
442
- registerPhase(registration: PhaseRegistration): void;
418
+ /** Register a Phase directory bundle. */
419
+ registerPhase(path: string): Promise<void>;
443
420
  /** Register a model provider. */
444
421
  registerProvider(config: _rowan_agent_models.ProviderConfig): void;
445
422
  /** Unregister a model provider. */
@@ -552,8 +529,8 @@ interface Phase {
552
529
  description: string;
553
530
  /** Restricted tools (undefined = all tools available) */
554
531
  tools?: string[];
555
- /** Restricted skills (undefined = all skills available) */
556
- skills?: string[];
532
+ /** Skills bundled directly inside this phase directory. Inline phases may omit this (equivalent to []). */
533
+ skills?: Skill[];
557
534
  /** Forced next phase */
558
535
  target?: string;
559
536
  /** Expected input fields (key → description) */
@@ -627,11 +604,11 @@ type PhaseRegistrySelection = Readonly<{
627
604
  type AgentDefinition = Readonly<{
628
605
  name: string;
629
606
  description: string;
630
- content: string;
607
+ prompt: string;
631
608
  tools?: readonly string[];
632
609
  skills?: readonly string[];
633
610
  phases?: PhaseRegistrySelection;
634
- context?: readonly string[];
611
+ contexts?: readonly string[];
635
612
  model?: ModelRef;
636
613
  }>;
637
614
  /** Parse one Markdown definition using the same frontmatter vocabulary as Phase resources. */
@@ -1176,6 +1153,7 @@ declare class ExtensionRunner {
1176
1153
  private createExtensionAPI;
1177
1154
  private registerTool;
1178
1155
  private registerPhase;
1156
+ private loadRegisteredPhase;
1179
1157
  private registerProvider;
1180
1158
  private unregisterProvider;
1181
1159
  private flushPendingProviders;
@@ -1233,7 +1211,7 @@ declare class RuntimeBootstrapRegistry extends ResourceRegistry {
1233
1211
 
1234
1212
  type DefinitionLayer = Readonly<{
1235
1213
  description?: string;
1236
- content?: string;
1214
+ prompt?: string;
1237
1215
  model?: ModelRef;
1238
1216
  tools?: readonly string[];
1239
1217
  skills?: readonly string[];
@@ -2222,8 +2200,15 @@ declare class RuntimeError<Code extends RuntimeErrorCode = RuntimeErrorCode> ext
2222
2200
  }
2223
2201
  declare function isRuntimeError(value: unknown): value is AnyRuntimeError;
2224
2202
 
2203
+ declare function loadSkill(path: string): Promise<Skill>;
2225
2204
  declare function loadSkills(targetPath: string): Promise<Skill[]>;
2226
2205
 
2206
+ /**
2207
+ * Load a single phase from a PHASE.md file or phase directory.
2208
+ *
2209
+ * If a directory is provided, the loader reads its PHASE.md.
2210
+ */
2211
+ declare function loadPhase(targetPath: string): Promise<Phase>;
2227
2212
  /**
2228
2213
  * Load all phases from the target directory.
2229
2214
  *
@@ -2269,4 +2254,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
2269
2254
 
2270
2255
  declare function createCoreTools(input?: CoreToolContext): Tool[];
2271
2256
 
2272
- export { type AfterToolCall, type AgentConfig, type AgentConfigRequest, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentResources, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type CoreToolContext, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type FrontmatterResult, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhases, loadSkills, materializeConfigurationSnapshot, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
2257
+ export { type AfterToolCall, type AgentConfig, type AgentConfigRequest, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentResources, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type CoreToolContext, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type FrontmatterResult, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhases, loadSkill, loadSkills, materializeConfigurationSnapshot, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
package/dist/index.js CHANGED
@@ -164,9 +164,6 @@ function buildPhaseEntry(p) {
164
164
  if (p.tools && p.tools.length > 0) {
165
165
  entry.available_tools = p.tools.join(", ");
166
166
  }
167
- if (p.skills && p.skills.length > 0) {
168
- entry.available_skills = p.skills.join(", ");
169
- }
170
167
  if (p.input && Object.keys(p.input).length > 0) {
171
168
  entry.required_input = Object.entries(p.input).map(([key, desc]) => `- ${key}: ${desc}`).join("\n");
172
169
  }
@@ -985,9 +982,9 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
985
982
  assertActive();
986
983
  options?.registerTool?.(tool);
987
984
  },
988
- registerPhase: (registration) => {
985
+ registerPhase: async (registration) => {
989
986
  assertActive();
990
- options?.registerPhase?.(registration);
987
+ await options?.registerPhase?.(registration);
991
988
  },
992
989
  registerProvider: (config) => {
993
990
  assertActive();
@@ -1037,21 +1034,23 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1037
1034
  }
1038
1035
 
1039
1036
  // src/harness/phases/loader.ts
1040
- import { existsSync, statSync } from "fs";
1041
- import { readdir } from "fs/promises";
1042
- import { dirname as dirname3, join, resolve as resolve3 } from "path";
1037
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
1038
+ import { readdir as readdir2 } from "fs/promises";
1039
+ import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
1043
1040
 
1044
1041
  // src/harness/definitions.ts
1045
1042
  import { parseModelRef } from "@rowan-agent/models";
1046
1043
  function assertAgentDefinition(value) {
1047
1044
  if (!isRecord(value)) throw new TypeError("config.definition is invalid");
1045
+ if ("content" in value) throw new TypeError("definition.content is not supported; use definition.prompt");
1046
+ if ("context" in value) throw new TypeError("definition.context is not supported; use definition.contexts");
1048
1047
  requiredString(value.name, "definition.name");
1049
1048
  requiredString(value.description, "definition.description");
1050
- requiredString(value.content, "definition.content");
1049
+ requiredString(value.prompt, "definition.prompt");
1051
1050
  optionalStringList(value.tools, "definition.tools");
1052
1051
  optionalStringList(value.skills, "definition.skills");
1053
1052
  optionalPhaseRegistrySelection(value.phases, "definition.phases");
1054
- optionalStringList(value.context, "definition.context");
1053
+ optionalStringList(value.contexts, "definition.contexts");
1055
1054
  if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
1056
1055
  if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
1057
1056
  if (value.model !== void 0) {
@@ -1064,17 +1063,19 @@ function parseAgentDefinition(raw) {
1064
1063
  const { frontmatter, body } = parseFrontmatter(raw);
1065
1064
  return normalizeAgentDefinition(frontmatter, body);
1066
1065
  }
1067
- function normalizeAgentDefinition(frontmatter, content, options = {}) {
1066
+ function normalizeAgentDefinition(frontmatter, prompt, options = {}) {
1067
+ if (frontmatter.content !== void 0) throw new TypeError("content is not supported; use prompt and the document body");
1068
+ if (frontmatter.context !== void 0) throw new TypeError("context is not supported; use contexts");
1068
1069
  const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
1069
1070
  const description = requiredString(frontmatter.description, "description");
1070
- const normalizedContent = requiredString(content, "content");
1071
+ const normalizedPrompt = requiredString(prompt, "prompt");
1071
1072
  const tools = optionalStringList(frontmatter.tools, "tools");
1072
1073
  const skills = optionalStringList(frontmatter.skills, "skills");
1073
1074
  const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
1074
1075
  if (frontmatter.extensions !== void 0) {
1075
1076
  throw new TypeError("extensions is not supported; Extensions are Runtime-global");
1076
1077
  }
1077
- const context = optionalStringList(frontmatter.context, "context");
1078
+ const contexts = optionalStringList(frontmatter.contexts, "contexts");
1078
1079
  if (frontmatter.entryPhase !== void 0) {
1079
1080
  throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
1080
1081
  }
@@ -1082,11 +1083,11 @@ function normalizeAgentDefinition(frontmatter, content, options = {}) {
1082
1083
  return {
1083
1084
  name,
1084
1085
  description,
1085
- content: normalizedContent,
1086
+ prompt: normalizedPrompt,
1086
1087
  ...tools ? { tools } : {},
1087
1088
  ...skills ? { skills } : {},
1088
1089
  ...phases ? { phases } : {},
1089
- ...context ? { context } : {},
1090
+ ...contexts ? { contexts } : {},
1090
1091
  ...model ? { model } : {}
1091
1092
  };
1092
1093
  }
@@ -1122,9 +1123,8 @@ function optionalStringList(value, field) {
1122
1123
  function optionalPhaseRegistrySelection(value, field) {
1123
1124
  if (value === void 0) return void 0;
1124
1125
  if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
1125
- if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
1126
1126
  if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
1127
- const entryPhaseId = value.entryPhaseId;
1127
+ const entryPhaseId = value.entryPhaseId ?? null;
1128
1128
  if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
1129
1129
  throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
1130
1130
  }
@@ -1199,19 +1199,6 @@ function validateDescription(value) {
1199
1199
  }
1200
1200
  return { description: value, warnings: [], missing: false };
1201
1201
  }
1202
- function validateSkillReferences(value) {
1203
- if (value === void 0) return [];
1204
- if (!Array.isArray(value)) return ["skills must be an array"];
1205
- const errors = [];
1206
- for (const [index, skillId] of value.entries()) {
1207
- if (typeof skillId !== "string") {
1208
- errors.push(`skills[${index}] must be a string`);
1209
- continue;
1210
- }
1211
- errors.push(...validateResourceId(skillId, `skills[${index}]`));
1212
- }
1213
- return errors;
1214
- }
1215
1202
  function validatePhaseTarget(value) {
1216
1203
  if (value === void 0) return [];
1217
1204
  if (typeof value !== "string") return ["target must be a string"];
@@ -1224,11 +1211,91 @@ function warnResourceDiagnostics(kind, filePath, diagnostics) {
1224
1211
  }
1225
1212
  }
1226
1213
 
1214
+ // src/harness/skills.ts
1215
+ import { existsSync, statSync } from "fs";
1216
+ import { readdir } from "fs/promises";
1217
+ import { dirname as dirname3, join, resolve as resolve3 } from "path";
1218
+ var SKILL_MARKER = "SKILL.md";
1219
+ function resolveSkillPath(input) {
1220
+ const resolved = resolve3(input);
1221
+ return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, SKILL_MARKER) : resolved;
1222
+ }
1223
+ async function loadSkill(path) {
1224
+ const resolved = resolveSkillPath(path);
1225
+ let loaded;
1226
+ try {
1227
+ loaded = await loadMarkdown(resolved);
1228
+ } catch (error) {
1229
+ if (!(error instanceof FrontmatterParseError)) throw error;
1230
+ warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
1231
+ throw new ResourceMetadataError("parse_failed", error.message);
1232
+ }
1233
+ const { frontmatter, body } = loaded;
1234
+ const metadata = frontmatter;
1235
+ const id = inferResourceName(resolved, SKILL_MARKER);
1236
+ const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1237
+ const name = frontmatterName || id;
1238
+ const description = validateDescription(metadata.description);
1239
+ const diagnostics = validateResourceId(id);
1240
+ if (frontmatterName && frontmatterName !== id) {
1241
+ diagnostics.push(...validateResourceName(frontmatterName, id));
1242
+ }
1243
+ diagnostics.push(...description.warnings);
1244
+ warnResourceDiagnostics("skill", resolved, diagnostics);
1245
+ if (description.missing) {
1246
+ throw new ResourceMetadataError("invalid_metadata", "description is required");
1247
+ }
1248
+ return {
1249
+ name,
1250
+ description: description.description,
1251
+ filePath: resolved,
1252
+ baseDir: dirname3(resolved),
1253
+ content: body,
1254
+ disableModelInvocation: metadata["disable-model-invocation"] === true
1255
+ };
1256
+ }
1257
+ async function loadSkills(targetPath) {
1258
+ const skillsDir = resolve3(targetPath);
1259
+ if (existsSync(skillsDir) && statSync(skillsDir).isFile()) {
1260
+ try {
1261
+ return [await loadSkill(skillsDir)];
1262
+ } catch (error) {
1263
+ if (error instanceof ResourceMetadataError) return [];
1264
+ throw error;
1265
+ }
1266
+ }
1267
+ if (existsSync(join(skillsDir, SKILL_MARKER))) {
1268
+ try {
1269
+ return [await loadSkill(skillsDir)];
1270
+ } catch (error) {
1271
+ if (error instanceof ResourceMetadataError) return [];
1272
+ throw error;
1273
+ }
1274
+ }
1275
+ const entries = await readdir(skillsDir, { withFileTypes: true });
1276
+ const skills = [];
1277
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1278
+ if (!entry.isDirectory()) continue;
1279
+ const skillFile = join(skillsDir, entry.name, SKILL_MARKER);
1280
+ if (!existsSync(skillFile)) continue;
1281
+ try {
1282
+ const skill = await loadSkill(skillFile);
1283
+ skills.push(skill);
1284
+ } catch (error) {
1285
+ if (error instanceof ResourceMetadataError) continue;
1286
+ console.warn(`Failed to load skill "${entry.name}":`, error);
1287
+ }
1288
+ }
1289
+ return skills;
1290
+ }
1291
+
1227
1292
  // src/harness/phases/loader.ts
1228
1293
  var PHASE_MARKER = "PHASE.md";
1294
+ var SKILL_MARKER2 = "SKILL.md";
1295
+ var RESOURCE_MARKERS = /* @__PURE__ */ new Set([PHASE_MARKER, SKILL_MARKER2]);
1229
1296
  function resolvePhasePath(input) {
1230
- const resolved = resolve3(input);
1231
- return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, PHASE_MARKER) : resolved;
1297
+ const resolved = resolve4(input);
1298
+ return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, PHASE_MARKER) : resolved;
1232
1299
  }
1233
1300
  async function loadPhase(targetPath) {
1234
1301
  const resolved = resolvePhasePath(targetPath);
@@ -1246,7 +1313,9 @@ async function loadPhase(targetPath) {
1246
1313
  const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1247
1314
  let definition;
1248
1315
  try {
1249
- definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
1316
+ const phaseMetadata = { ...metadata };
1317
+ delete phaseMetadata.skills;
1318
+ definition = normalizeAgentDefinition(phaseMetadata, body, { fallbackName: directoryName });
1250
1319
  } catch (error) {
1251
1320
  const message = error instanceof Error ? error.message : String(error);
1252
1321
  warnResourceDiagnostics("phase", resolved, [message]);
@@ -1259,21 +1328,21 @@ async function loadPhase(targetPath) {
1259
1328
  diagnostics.push(...validateResourceName(frontmatterName, directoryName));
1260
1329
  }
1261
1330
  diagnostics.push(...description.warnings);
1262
- diagnostics.push(...validateSkillReferences(metadata.skills));
1263
1331
  diagnostics.push(...validatePhaseTarget(metadata.target));
1264
1332
  warnResourceDiagnostics("phase", resolved, diagnostics);
1265
- const baseDir = dirname3(resolved);
1333
+ const baseDir = dirname4(resolved);
1334
+ const skills = await loadPhaseSkills(baseDir);
1266
1335
  const phase = {
1267
1336
  name,
1268
1337
  description: description.description,
1269
1338
  tools: definition.tools ? [...definition.tools] : void 0,
1270
- skills: definition.skills ? [...definition.skills] : void 0,
1339
+ skills,
1271
1340
  target: metadata.target,
1272
1341
  input: metadata.input,
1273
1342
  isolated: metadata.isolated,
1274
1343
  filePath: resolved,
1275
1344
  baseDir,
1276
- content: definition.content,
1345
+ content: definition.prompt,
1277
1346
  model: definition.model
1278
1347
  };
1279
1348
  const codePath = await discoverPhaseCode(baseDir);
@@ -1287,6 +1356,54 @@ async function loadPhase(targetPath) {
1287
1356
  }
1288
1357
  return phase;
1289
1358
  }
1359
+ async function loadPhaseSkills(baseDir) {
1360
+ const entries = await readdir2(baseDir, { withFileTypes: true });
1361
+ const skills = [];
1362
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1363
+ const entryPath = join2(baseDir, entry.name);
1364
+ if (!entry.isDirectory()) {
1365
+ if (RESOURCE_MARKERS.has(entry.name) && entry.name !== PHASE_MARKER) {
1366
+ throw invalidPhaseBundle(`resource marker ${entry.name} is not valid at the phase root`);
1367
+ }
1368
+ continue;
1369
+ }
1370
+ const markers = await findResourceMarkers(entryPath);
1371
+ const directSkillPath = join2(entryPath, SKILL_MARKER2);
1372
+ const hasDirectSkill = markers.includes(directSkillPath);
1373
+ if (!hasDirectSkill) {
1374
+ if (markers.length > 0) {
1375
+ throw invalidPhaseBundle(`resource marker ${markers[0]} is not a direct child Skill`);
1376
+ }
1377
+ continue;
1378
+ }
1379
+ const nestedMarkers = markers.filter((marker) => marker !== directSkillPath);
1380
+ if (nestedMarkers.length > 0) {
1381
+ throw invalidPhaseBundle(`resource marker ${nestedMarkers[0]} is nested below a Skill bundle`);
1382
+ }
1383
+ const skill = await loadSkill(directSkillPath);
1384
+ if (skills.some((candidate) => candidate.name === skill.name)) {
1385
+ throw invalidPhaseBundle(`duplicate bundled Skill name "${skill.name}"`);
1386
+ }
1387
+ skills.push(skill);
1388
+ }
1389
+ return skills;
1390
+ }
1391
+ async function findResourceMarkers(directory) {
1392
+ const entries = await readdir2(directory, { withFileTypes: true });
1393
+ const markers = [];
1394
+ for (const entry of entries) {
1395
+ const entryPath = join2(directory, entry.name);
1396
+ if (entry.isDirectory()) {
1397
+ markers.push(...await findResourceMarkers(entryPath));
1398
+ } else if (RESOURCE_MARKERS.has(entry.name)) {
1399
+ markers.push(entryPath);
1400
+ }
1401
+ }
1402
+ return markers;
1403
+ }
1404
+ function invalidPhaseBundle(message) {
1405
+ return new ResourceMetadataError("invalid_metadata", message);
1406
+ }
1290
1407
  function readPhaseContent(phase) {
1291
1408
  return formatResourceOutput({
1292
1409
  type: "phase",
@@ -1298,8 +1415,8 @@ function readPhaseContent(phase) {
1298
1415
  }
1299
1416
  async function loadPhases(targetPath) {
1300
1417
  const phases = /* @__PURE__ */ new Map();
1301
- const phasesDir = resolve3(targetPath);
1302
- if (existsSync(phasesDir) && statSync(phasesDir).isFile()) {
1418
+ const phasesDir = resolve4(targetPath);
1419
+ if (existsSync2(phasesDir) && statSync2(phasesDir).isFile()) {
1303
1420
  try {
1304
1421
  const phase = await loadPhase(phasesDir);
1305
1422
  phases.set(phase.name, phase);
@@ -1308,7 +1425,7 @@ async function loadPhases(targetPath) {
1308
1425
  }
1309
1426
  return { phases, entryPhaseId: null };
1310
1427
  }
1311
- if (existsSync(join(phasesDir, PHASE_MARKER))) {
1428
+ if (existsSync2(join2(phasesDir, PHASE_MARKER))) {
1312
1429
  try {
1313
1430
  const phase = await loadPhase(phasesDir);
1314
1431
  phases.set(phase.name, phase);
@@ -1317,11 +1434,11 @@ async function loadPhases(targetPath) {
1317
1434
  }
1318
1435
  return { phases, entryPhaseId: null };
1319
1436
  }
1320
- const entries = await readdir(phasesDir, { withFileTypes: true });
1437
+ const entries = await readdir2(phasesDir, { withFileTypes: true });
1321
1438
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1322
1439
  if (!entry.isDirectory()) continue;
1323
- const phaseFile = join(phasesDir, entry.name, PHASE_MARKER);
1324
- if (!existsSync(phaseFile)) continue;
1440
+ const phaseFile = join2(phasesDir, entry.name, PHASE_MARKER);
1441
+ if (!existsSync2(phaseFile)) continue;
1325
1442
  try {
1326
1443
  const phase = await loadPhase(phaseFile);
1327
1444
  phases.set(phase.name, phase);
@@ -1335,8 +1452,8 @@ async function loadPhases(targetPath) {
1335
1452
  async function discoverPhaseCode(baseDir) {
1336
1453
  const candidates = ["index.ts", "index.js"];
1337
1454
  for (const candidate of candidates) {
1338
- const fullPath = join(baseDir, candidate);
1339
- if (existsSync(fullPath)) {
1455
+ const fullPath = join2(baseDir, candidate);
1456
+ if (existsSync2(fullPath)) {
1340
1457
  return fullPath;
1341
1458
  }
1342
1459
  }
@@ -1368,6 +1485,7 @@ function createDefaultPhase() {
1368
1485
  description: "Executes concrete task operations and produces artifacts.",
1369
1486
  filePath: "",
1370
1487
  baseDir: "",
1488
+ skills: [],
1371
1489
  content: "Execute tasks using current context.\nNo planning. No evaluation.\nRoute to next phase or stop when done."
1372
1490
  };
1373
1491
  }
@@ -1925,7 +2043,7 @@ async function runPhaseLoop(config, state, registry) {
1925
2043
  const toolExecutionManager = createToolExecutionManager();
1926
2044
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1927
2045
  const phaseTools = selectPhaseTools(allTools, phase.tools);
1928
- const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2046
+ const phaseSkills = phase.name === DEFAULT_PHASE_ID ? config.context.skills : phase.skills ?? [];
1929
2047
  let phaseContext = {
1930
2048
  systemPrompt: config.context.systemPrompt,
1931
2049
  messages: messageManager.visible(),
@@ -2204,7 +2322,7 @@ async function withRetry(fn, options = {}) {
2204
2322
  }
2205
2323
  const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2206
2324
  options.onRetry?.(attempt, error, delayMs);
2207
- await new Promise((resolve7) => setTimeout(resolve7, delayMs));
2325
+ await new Promise((resolve8) => setTimeout(resolve8, delayMs));
2208
2326
  if (options.signal?.aborted) {
2209
2327
  throw lastError;
2210
2328
  }
@@ -2216,7 +2334,7 @@ async function executePhase(ctx) {
2216
2334
  const { phase, config, execution, registry, context } = ctx;
2217
2335
  if (phase.factory) {
2218
2336
  const api = createExtensionAPI(void 0, {
2219
- registerPhase: () => {
2337
+ registerPhase: async () => {
2220
2338
  },
2221
2339
  registerProvider: () => {
2222
2340
  },
@@ -2238,7 +2356,7 @@ async function executePhase(ctx) {
2238
2356
  config.context.messages.push(createMessage(role, content));
2239
2357
  },
2240
2358
  getAvailableTools: () => config.context.tools.map((t) => ({ name: t.name, description: t.description })),
2241
- getAvailableSkills: () => config.context.skills.map((s) => ({ name: s.name, description: s.description })),
2359
+ getAvailableSkills: () => context.skills.map((s) => ({ name: s.name, description: s.description })),
2242
2360
  getPhaseContent: (id) => registry.phases.get(id)?.content ?? "",
2243
2361
  getAvailablePhases: () => Array.from(registry.phases.keys())
2244
2362
  },
@@ -2416,7 +2534,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2416
2534
  const messages = [...context];
2417
2535
  const allTools = buildToolsWithRouting(config, availablePhases);
2418
2536
  const phaseTools = selectPhaseTools(allTools, phase.tools);
2419
- const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2537
+ const phaseSkills = phase.name === DEFAULT_PHASE_ID ? config.context.skills : phase.skills ?? [];
2420
2538
  const systemPrompt = config.context.systemPrompt;
2421
2539
  const phaseContext = {
2422
2540
  systemPrompt,
@@ -2784,6 +2902,9 @@ function assertAgentConfigRequest(config) {
2784
2902
  if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
2785
2903
  throw new TypeError("config.definition.name must be non-empty");
2786
2904
  }
2905
+ if (config.definition.layer && "content" in config.definition.layer) {
2906
+ throw new TypeError("definition.layer.content is not supported; use definition.layer.prompt");
2907
+ }
2787
2908
  const view = config.resourceView;
2788
2909
  if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
2789
2910
  throw new TypeError("config.resourceView is invalid");
@@ -2901,12 +3022,13 @@ function snapshotConfig(config) {
2901
3022
  phaseIds: Object.freeze([...config.definition.phases.phaseIds])
2902
3023
  })
2903
3024
  } : {},
2904
- ...config.definition.context ? { context: Object.freeze([...config.definition.context]) } : {}
3025
+ ...config.definition.contexts ? { contexts: Object.freeze([...config.definition.contexts]) } : {}
2905
3026
  });
2906
3027
  const resources = Object.freeze({
2907
3028
  ...config.resources,
2908
3029
  tools: Object.freeze([...config.resources.tools]),
2909
3030
  skills: Object.freeze([...config.resources.skills]),
3031
+ ...config.resources.phases ? { phases: snapshotPhaseRegistry(config.resources.phases) } : {},
2910
3032
  ...config.resources.resourceView ? {
2911
3033
  resourceView: Object.freeze({
2912
3034
  agents: Object.freeze([...config.resources.resourceView.agents]),
@@ -2930,6 +3052,20 @@ function snapshotConfig(config) {
2930
3052
  });
2931
3053
  return Object.freeze({ ...config, definition, resources });
2932
3054
  }
3055
+ function snapshotPhaseRegistry(registry) {
3056
+ const phases = /* @__PURE__ */ new Map();
3057
+ for (const [name, phase] of registry.phases) phases.set(name, snapshotPhase(phase));
3058
+ return { phases, entryPhaseId: registry.entryPhaseId };
3059
+ }
3060
+ function snapshotPhase(phase) {
3061
+ return Object.freeze({
3062
+ ...phase,
3063
+ ...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
3064
+ skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
3065
+ ...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
3066
+ ...phase.model ? { model: Object.freeze({ ...phase.model }) } : {}
3067
+ });
3068
+ }
2933
3069
  function snapshotConfiguration(config) {
2934
3070
  const layer = config.definition.layer;
2935
3071
  const definition = Object.freeze({
@@ -3365,7 +3501,7 @@ function resolveDefinitionContext(config, assembled = {}) {
3365
3501
  );
3366
3502
  const contexts = selectNamedResources(
3367
3503
  config.resources.contexts ?? [],
3368
- config.definition.context,
3504
+ config.definition.contexts,
3369
3505
  "Context"
3370
3506
  );
3371
3507
  const candidateRegistry = assembled.phases ?? config.resources.phases;
@@ -3381,7 +3517,7 @@ function resolveDefinitionContext(config, assembled = {}) {
3381
3517
  console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
3382
3518
  }
3383
3519
  return {
3384
- systemPrompt: [config.definition.content, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
3520
+ systemPrompt: [config.definition.prompt, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
3385
3521
  tools,
3386
3522
  skills,
3387
3523
  phases: { phases, entryPhaseId }
@@ -3429,11 +3565,11 @@ function fromLoopResult(result) {
3429
3565
  var MAX_BUFFERED_EVENTS = 128;
3430
3566
  var MAX_BUFFERED_TEXT = 64 * 1024;
3431
3567
  function deferred() {
3432
- let resolve7;
3568
+ let resolve8;
3433
3569
  const promise = new Promise((done) => {
3434
- resolve7 = done;
3570
+ resolve8 = done;
3435
3571
  });
3436
- return { promise, resolve: resolve7 };
3572
+ return { promise, resolve: resolve8 };
3437
3573
  }
3438
3574
  var TransientRunEventSubscription = class {
3439
3575
  queue = [];
@@ -3619,6 +3755,7 @@ var HooksManager = class {
3619
3755
 
3620
3756
  // src/extensions/runner.ts
3621
3757
  import { execFile } from "child_process";
3758
+ import { dirname as dirname5, resolve as resolve5 } from "path";
3622
3759
  import {
3623
3760
  registerModel,
3624
3761
  unregisterProviderModels,
@@ -3650,9 +3787,6 @@ function createExtensionRuntime() {
3650
3787
  };
3651
3788
  }
3652
3789
 
3653
- // src/extensions/runner.ts
3654
- import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3655
-
3656
3790
  // src/extensions/event-bus.ts
3657
3791
  function createEventBus() {
3658
3792
  const listeners = /* @__PURE__ */ new Map();
@@ -3696,7 +3830,7 @@ function createEventBus() {
3696
3830
 
3697
3831
  // src/extensions/runner.ts
3698
3832
  async function execCommand(command, args, cwd, options) {
3699
- return new Promise((resolve7, reject) => {
3833
+ return new Promise((resolve8, reject) => {
3700
3834
  const child = execFile(
3701
3835
  command,
3702
3836
  args,
@@ -3711,7 +3845,7 @@ async function execCommand(command, args, cwd, options) {
3711
3845
  reject(new Error("Command was aborted"));
3712
3846
  return;
3713
3847
  }
3714
- resolve7({
3848
+ resolve8({
3715
3849
  exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3716
3850
  stdout: stdout ?? "",
3717
3851
  stderr: stderr ?? ""
@@ -3948,17 +4082,10 @@ var ExtensionRunner = class {
3948
4082
  adaptToPhase(reg) {
3949
4083
  const def = reg.definition;
3950
4084
  return {
3951
- name: def.name,
3952
- description: def.description,
3953
- tools: def.tools,
3954
- skills: def.skills,
3955
- target: def.target,
3956
- input: def.input,
3957
- run: def.run,
3958
- filePath: "",
3959
- baseDir: "",
3960
- content: "",
3961
- ...def.model ? { model: parseModelRef2(def.model) } : {}
4085
+ ...def,
4086
+ ...def.tools ? { tools: [...def.tools] } : {},
4087
+ skills: def.skills ? [...def.skills] : [],
4088
+ ...def.input ? { input: { ...def.input } } : {}
3962
4089
  };
3963
4090
  }
3964
4091
  // ---------------------------------------------------------------------------
@@ -4120,36 +4247,20 @@ var ExtensionRunner = class {
4120
4247
  });
4121
4248
  }
4122
4249
  registerPhase(extension, registration) {
4123
- if (typeof registration.name !== "string" || registration.name.length === 0) {
4124
- throw new Error(`Phase registration requires a "name" field.`);
4125
- }
4126
- const name = registration.name;
4127
- const description = validateDescription(registration.description);
4128
- const errors = validateResourceId(name, "name");
4129
- if (description.missing) errors.push(...description.warnings);
4130
- errors.push(...validateSkillReferences(registration.skills));
4131
- errors.push(...validatePhaseTarget(registration.target));
4132
- if (errors.length > 0) {
4133
- throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
4134
- }
4135
- if (description.warnings.length > 0) {
4136
- warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
4250
+ if (typeof registration !== "string" || registration.length === 0) {
4251
+ throw new Error(`Phase registration requires a directory path.`);
4137
4252
  }
4253
+ return this.loadRegisteredPhase(extension, registration);
4254
+ }
4255
+ async loadRegisteredPhase(extension, registration) {
4256
+ const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname5(extension.path);
4257
+ const phase = await loadPhase(resolve5(extensionBase, registration));
4258
+ const name = phase.name;
4138
4259
  if (this.phases.has(name)) {
4139
4260
  throw new Error(`Duplicate phase name: ${name}`);
4140
4261
  }
4141
- const definition = {
4142
- name,
4143
- description: description.description,
4144
- run: registration.run,
4145
- ...registration.tools ? { tools: registration.tools.slice() } : {},
4146
- ...registration.skills ? { skills: registration.skills.slice() } : {},
4147
- ...registration.target ? { target: registration.target } : {},
4148
- ...registration.input ? { input: { ...registration.input } } : {},
4149
- ...registration.model ? { model: registration.model } : {}
4150
- };
4151
4262
  const registered = {
4152
- definition,
4263
+ definition: phase,
4153
4264
  source: { extensionPath: extension.path }
4154
4265
  };
4155
4266
  this.phases.set(name, registered);
@@ -4208,10 +4319,10 @@ function createExtensionRunner(options) {
4208
4319
  }
4209
4320
 
4210
4321
  // 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";
4322
+ import { existsSync as existsSync3, readFileSync } from "fs";
4323
+ import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
4213
4324
  import { fileURLToPath } from "url";
4214
- import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
4325
+ import { basename as basename3, dirname as dirname6, extname as extname2, join as join3, resolve as resolve6 } from "path";
4215
4326
  import { createJiti } from "jiti";
4216
4327
  function isExtensionFile(path) {
4217
4328
  const extension = extname2(path).toLowerCase();
@@ -4221,8 +4332,8 @@ function isSyntheticPath(path) {
4221
4332
  return path.startsWith("<") && path.endsWith(">");
4222
4333
  }
4223
4334
  function readManifestSync(dir) {
4224
- const manifestPath = join2(dir, "package.json");
4225
- if (!existsSync2(manifestPath)) {
4335
+ const manifestPath = join3(dir, "package.json");
4336
+ if (!existsSync3(manifestPath)) {
4226
4337
  return void 0;
4227
4338
  }
4228
4339
  try {
@@ -4231,8 +4342,7 @@ function readManifestSync(dir) {
4231
4342
  const rowan = pkg.rowan;
4232
4343
  if (!rowan) return void 0;
4233
4344
  return {
4234
- entry: rowan.extensions?.[0],
4235
- phase: rowan.phase
4345
+ entry: rowan.extensions?.[0]
4236
4346
  };
4237
4347
  } catch {
4238
4348
  return void 0;
@@ -4244,7 +4354,7 @@ function jitiAliases() {
4244
4354
  return {};
4245
4355
  }
4246
4356
  const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
4247
- if (!existsSync2(agentSourcePath)) {
4357
+ if (!existsSync3(agentSourcePath)) {
4248
4358
  return {};
4249
4359
  }
4250
4360
  return { "@rowan-agent/agent": agentSourcePath };
@@ -4265,28 +4375,28 @@ async function loadExtensionModule(extensionPath) {
4265
4375
  return typeof module === "function" ? module : void 0;
4266
4376
  }
4267
4377
  async function readPackageManifest(path) {
4268
- if (!existsSync2(path)) {
4378
+ if (!existsSync3(path)) {
4269
4379
  return void 0;
4270
4380
  }
4271
4381
  const content = await readFile3(path, "utf8");
4272
4382
  return JSON.parse(content);
4273
4383
  }
4274
4384
  async function resolveExtensionEntries(dir) {
4275
- const manifest = await readPackageManifest(join2(dir, "package.json"));
4385
+ const manifest = await readPackageManifest(join3(dir, "package.json"));
4276
4386
  const declared = manifest?.rowan?.extensions;
4277
4387
  if (declared && declared.length > 0) {
4278
- return declared.map((entry) => resolve4(dir, entry));
4388
+ return declared.map((entry) => resolve6(dir, entry));
4279
4389
  }
4280
4390
  for (const name of ["index.ts", "index.js"]) {
4281
- const entry = join2(dir, name);
4282
- if (existsSync2(entry)) {
4391
+ const entry = join3(dir, name);
4392
+ if (existsSync3(entry)) {
4283
4393
  return [entry];
4284
4394
  }
4285
4395
  }
4286
4396
  return void 0;
4287
4397
  }
4288
4398
  async function discoverExtensionsInDir(dir) {
4289
- const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
4399
+ const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
4290
4400
  if (error.code === "ENOENT") {
4291
4401
  return [];
4292
4402
  }
@@ -4294,7 +4404,7 @@ async function discoverExtensionsInDir(dir) {
4294
4404
  });
4295
4405
  const paths = [];
4296
4406
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4297
- const entryPath = join2(dir, entry.name);
4407
+ const entryPath = join3(dir, entry.name);
4298
4408
  if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4299
4409
  paths.push(entryPath);
4300
4410
  continue;
@@ -4312,10 +4422,10 @@ async function discoverExtensionsInDir(dir) {
4312
4422
  return paths;
4313
4423
  }
4314
4424
  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);
4425
+ const resolvedCwd = resolve6(cwd);
4426
+ const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve6(resolvedCwd, extensionPath);
4427
+ const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown";
4428
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname6(resolvedPath);
4319
4429
  const manifest = readManifestSync(manifestDir);
4320
4430
  return {
4321
4431
  path: resolvedPath,
@@ -4327,9 +4437,9 @@ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4327
4437
  async function loadExtensions(paths, cwd) {
4328
4438
  const extensions = [];
4329
4439
  const errors = [];
4330
- const resolvedCwd = resolve4(cwd);
4440
+ const resolvedCwd = resolve6(cwd);
4331
4441
  for (const path of paths) {
4332
- const resolvedPath = resolve4(resolvedCwd, path);
4442
+ const resolvedPath = resolve6(resolvedCwd, path);
4333
4443
  try {
4334
4444
  const factory = await loadExtensionModule(resolvedPath);
4335
4445
  if (!factory) {
@@ -4350,10 +4460,10 @@ async function loadExtensions(paths, cwd) {
4350
4460
  return { extensions, errors };
4351
4461
  }
4352
4462
  async function loadExtensionsFromPath(path) {
4353
- const resolvedPath = resolve4(path);
4463
+ const resolvedPath = resolve6(path);
4354
4464
  const info = await stat2(resolvedPath);
4355
4465
  if (!info.isDirectory()) {
4356
- return loadExtensions([resolvedPath], dirname4(resolvedPath));
4466
+ return loadExtensions([resolvedPath], dirname6(resolvedPath));
4357
4467
  }
4358
4468
  const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
4359
4469
  return loadExtensions(entries, resolvedPath);
@@ -4361,87 +4471,7 @@ async function loadExtensionsFromPath(path) {
4361
4471
 
4362
4472
  // src/runtime/resource-registry.ts
4363
4473
  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
- }
4400
- return {
4401
- name,
4402
- description: description.description,
4403
- filePath: resolved,
4404
- baseDir: dirname5(resolved),
4405
- content: body,
4406
- disableModelInvocation: metadata["disable-model-invocation"] === true
4407
- };
4408
- }
4409
- async function loadSkills(targetPath) {
4410
- const skillsDir = resolve5(targetPath);
4411
- if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
4412
- try {
4413
- return [await loadSkill(skillsDir)];
4414
- } catch (error) {
4415
- if (error instanceof ResourceMetadataError) return [];
4416
- throw error;
4417
- }
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;
4442
- }
4443
-
4444
- // src/runtime/resource-registry.ts
4474
+ import { join as join4, resolve as resolve7 } from "path";
4445
4475
  var ResourceRegistryError = class extends Error {
4446
4476
  code;
4447
4477
  constructor(code, message) {
@@ -4568,6 +4598,7 @@ var ResourceRegistry = class {
4568
4598
  assertSourceId(sourceId);
4569
4599
  const names = /* @__PURE__ */ new Set();
4570
4600
  for (const value of values) {
4601
+ if (kind === "agent") assertAgentDefinition(value);
4571
4602
  assertNamedResource(value, kind, sourceId);
4572
4603
  if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
4573
4604
  throw new ResourceRegistryError(
@@ -4584,11 +4615,12 @@ var ResourceRegistry = class {
4584
4615
  names.add(value.name);
4585
4616
  }
4586
4617
  const revision = this.nextRevision(kind, sourceId);
4618
+ const snapshottedValues = values.map((value) => snapshotResourceValue(kind, value));
4587
4619
  const record = {
4588
4620
  kind,
4589
4621
  sourceId,
4590
4622
  revision,
4591
- values: Object.freeze([...values])
4623
+ values: Object.freeze(snapshottedValues)
4592
4624
  };
4593
4625
  let bySource = this.sources.get(kind);
4594
4626
  if (!bySource) {
@@ -4658,6 +4690,18 @@ var ResourceRegistry = class {
4658
4690
  return `${kind}:${sourceId}:${this.revisionSequence}`;
4659
4691
  }
4660
4692
  };
4693
+ function snapshotResourceValue(kind, value) {
4694
+ if (kind !== "phase") return value;
4695
+ const phase = value;
4696
+ const snapshot = {
4697
+ ...phase,
4698
+ ...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
4699
+ skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
4700
+ ...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
4701
+ ...phase.model ? { model: Object.freeze({ ...phase.model }) } : {}
4702
+ };
4703
+ return Object.freeze(snapshot);
4704
+ }
4661
4705
  function assertSourceId(sourceId) {
4662
4706
  if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
4663
4707
  throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
@@ -4679,7 +4723,7 @@ function isImplicitCoreName(kind, name) {
4679
4723
  return kind === "tool" && name === "route" || kind === "phase" && name === "default";
4680
4724
  }
4681
4725
  async function loadDirectoryValues(kind, directory, sourceId) {
4682
- const root = resolve6(directory);
4726
+ const root = resolve7(directory);
4683
4727
  let rootInfo;
4684
4728
  try {
4685
4729
  rootInfo = await stat3(root);
@@ -4922,7 +4966,7 @@ function resolveConfigurationSnapshot(registry, input) {
4922
4966
  const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
4923
4967
  const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
4924
4968
  const phases = resolvePhases(resolved.phases, definition.phases);
4925
- const contexts = selectNamedResources(input.contexts ?? [], base.context, "Context");
4969
+ const contexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
4926
4970
  return {
4927
4971
  identity: input.identity,
4928
4972
  definition,
@@ -4973,7 +5017,7 @@ function applyDefinitionLayer(base, layer) {
4973
5017
  return {
4974
5018
  ...base,
4975
5019
  ...layer.description === void 0 ? {} : { description: layer.description },
4976
- ...layer.content === void 0 ? {} : { content: layer.content },
5020
+ ...layer.prompt === void 0 ? {} : { prompt: layer.prompt },
4977
5021
  ...layer.model === void 0 ? {} : { model: layer.model },
4978
5022
  ...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
4979
5023
  ...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
@@ -5001,9 +5045,8 @@ function intersectPhaseSelection(parent, layer) {
5001
5045
  }
5002
5046
  function resolvePhases(candidates, selection) {
5003
5047
  const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
5004
- if (selected.length === 0 && selection === void 0) return void 0;
5005
5048
  const entryPhaseId = selection?.entryPhaseId ?? null;
5006
- if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
5049
+ if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
5007
5050
  console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
5008
5051
  return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
5009
5052
  }
@@ -5618,7 +5661,7 @@ function boundaryFromSnapshot(snapshot) {
5618
5661
  }
5619
5662
  }
5620
5663
  function delay(ms) {
5621
- return new Promise((resolve7) => setTimeout(resolve7, ms));
5664
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
5622
5665
  }
5623
5666
  function normalizePhaseRegistry(registry) {
5624
5667
  if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
@@ -5645,13 +5688,13 @@ function toJsonValue(value) {
5645
5688
  return value;
5646
5689
  }
5647
5690
  function deferred2() {
5648
- let resolve7;
5691
+ let resolve8;
5649
5692
  let reject;
5650
5693
  const promise = new Promise((res, rej) => {
5651
- resolve7 = (value) => res(value);
5694
+ resolve8 = (value) => res(value);
5652
5695
  reject = rej;
5653
5696
  });
5654
- return { promise, resolve: resolve7, reject };
5697
+ return { promise, resolve: resolve8, reject };
5655
5698
  }
5656
5699
 
5657
5700
  // src/runtime/durable-store.ts
@@ -7129,7 +7172,9 @@ export {
7129
7172
  createCoreTools2 as createCoreTools,
7130
7173
  isRuntimeError,
7131
7174
  loadExtensionsFromPath as loadExtensions,
7175
+ loadPhase,
7132
7176
  loadPhases,
7177
+ loadSkill,
7133
7178
  loadSkills,
7134
7179
  materializeConfigurationSnapshot,
7135
7180
  parseAgentDefinition,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",