@rowan-agent/agent 0.8.7 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +17 -28
  2. package/dist/index.js +241 -207
  3. package/package.json +1 -1
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) */
@@ -613,6 +590,10 @@ type AgentContext = {
613
590
  phases?: PhaseRegistry;
614
591
  };
615
592
 
593
+ /**
594
+ * `root` anchors relative paths and the Bash working directory. It is not a
595
+ * filesystem sandbox: core Tools use the permissions of the host process.
596
+ */
616
597
  type CoreToolContext = {
617
598
  root?: string;
618
599
  maxReadBytes?: number;
@@ -1176,6 +1157,7 @@ declare class ExtensionRunner {
1176
1157
  private createExtensionAPI;
1177
1158
  private registerTool;
1178
1159
  private registerPhase;
1160
+ private loadRegisteredPhase;
1179
1161
  private registerProvider;
1180
1162
  private unregisterProvider;
1181
1163
  private flushPendingProviders;
@@ -2222,8 +2204,15 @@ declare class RuntimeError<Code extends RuntimeErrorCode = RuntimeErrorCode> ext
2222
2204
  }
2223
2205
  declare function isRuntimeError(value: unknown): value is AnyRuntimeError;
2224
2206
 
2207
+ declare function loadSkill(path: string): Promise<Skill>;
2225
2208
  declare function loadSkills(targetPath: string): Promise<Skill[]>;
2226
2209
 
2210
+ /**
2211
+ * Load a single phase from a PHASE.md file or phase directory.
2212
+ *
2213
+ * If a directory is provided, the loader reads its PHASE.md.
2214
+ */
2215
+ declare function loadPhase(targetPath: string): Promise<Phase>;
2227
2216
  /**
2228
2217
  * Load all phases from the target directory.
2229
2218
  *
@@ -2269,4 +2258,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
2269
2258
 
2270
2259
  declare function createCoreTools(input?: CoreToolContext): Tool[];
2271
2260
 
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 };
2261
+ 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
@@ -1,6 +1,6 @@
1
1
  // src/harness/tools/index.ts
2
2
  import { mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
3
- import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
3
+ import { basename as basename2, dirname as dirname2, relative as relative2, resolve as resolve2 } from "path";
4
4
  import Type2 from "typebox";
5
5
  import Schema from "typebox/schema";
6
6
 
@@ -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
  }
@@ -311,9 +308,6 @@ function resolveCoreToolPath(context, path = ".") {
311
308
  const inputPath = normalizeCoreToolInputPath(path);
312
309
  const absolutePath = resolve2(root, inputPath);
313
310
  const relativePath = relative2(root, absolutePath);
314
- if (relativePath === ".." || relativePath.startsWith(`..${sep2}`) || isAbsolute2(relativePath)) {
315
- throw new Error(`Path escapes workspace root: ${path}`);
316
- }
317
311
  return {
318
312
  root,
319
313
  inputPath,
@@ -468,7 +462,7 @@ async function captureStream(stream, maxBytes) {
468
462
  function createReadTool(context) {
469
463
  return {
470
464
  name: "read",
471
- description: "Read a file in the workspace.",
465
+ description: "Read a file using the host process filesystem permissions.",
472
466
  parameters: ReadArgsSchema,
473
467
  promptSnippet: "Read file contents.",
474
468
  promptGuidelines: ["Read files before editing them."],
@@ -539,7 +533,7 @@ function createReadTool(context) {
539
533
  function createWriteTool(context) {
540
534
  return {
541
535
  name: "write",
542
- description: "Create or overwrite a file in the workspace.",
536
+ description: "Create or overwrite a file using the host process filesystem permissions.",
543
537
  parameters: WriteArgsSchema,
544
538
  promptSnippet: "Create or overwrite files.",
545
539
  promptGuidelines: ["Use edit for partial changes."],
@@ -591,7 +585,7 @@ function applyEditReplacements(current, edits) {
591
585
  function createEditTool(context) {
592
586
  return {
593
587
  name: "edit",
594
- description: "Apply exact text replacements to a workspace file.",
588
+ description: "Apply exact text replacements using the host process filesystem permissions.",
595
589
  parameters: EditArgsSchema,
596
590
  promptSnippet: "Apply exact text replacements.",
597
591
  promptGuidelines: ["Read the file first; each oldText must match exactly once."],
@@ -627,7 +621,7 @@ function createEditTool(context) {
627
621
  function createBashTool(context) {
628
622
  return {
629
623
  name: "bash",
630
- description: "Run a bash command in the workspace.",
624
+ description: "Run a bash command with the host process filesystem permissions; root is the working directory.",
631
625
  parameters: BashArgsSchema,
632
626
  promptSnippet: "Run shell commands.",
633
627
  promptGuidelines: ["Use read/write/edit for file operations."],
@@ -985,9 +979,9 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
985
979
  assertActive();
986
980
  options?.registerTool?.(tool);
987
981
  },
988
- registerPhase: (registration) => {
982
+ registerPhase: async (registration) => {
989
983
  assertActive();
990
- options?.registerPhase?.(registration);
984
+ await options?.registerPhase?.(registration);
991
985
  },
992
986
  registerProvider: (config) => {
993
987
  assertActive();
@@ -1037,9 +1031,9 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1037
1031
  }
1038
1032
 
1039
1033
  // 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";
1034
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
1035
+ import { readdir as readdir2 } from "fs/promises";
1036
+ import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
1043
1037
 
1044
1038
  // src/harness/definitions.ts
1045
1039
  import { parseModelRef } from "@rowan-agent/models";
@@ -1126,9 +1120,8 @@ function optionalStringList(value, field) {
1126
1120
  function optionalPhaseRegistrySelection(value, field) {
1127
1121
  if (value === void 0) return void 0;
1128
1122
  if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
1129
- if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
1130
1123
  if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
1131
- const entryPhaseId = value.entryPhaseId;
1124
+ const entryPhaseId = value.entryPhaseId ?? null;
1132
1125
  if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
1133
1126
  throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
1134
1127
  }
@@ -1203,19 +1196,6 @@ function validateDescription(value) {
1203
1196
  }
1204
1197
  return { description: value, warnings: [], missing: false };
1205
1198
  }
1206
- function validateSkillReferences(value) {
1207
- if (value === void 0) return [];
1208
- if (!Array.isArray(value)) return ["skills must be an array"];
1209
- const errors = [];
1210
- for (const [index, skillId] of value.entries()) {
1211
- if (typeof skillId !== "string") {
1212
- errors.push(`skills[${index}] must be a string`);
1213
- continue;
1214
- }
1215
- errors.push(...validateResourceId(skillId, `skills[${index}]`));
1216
- }
1217
- return errors;
1218
- }
1219
1199
  function validatePhaseTarget(value) {
1220
1200
  if (value === void 0) return [];
1221
1201
  if (typeof value !== "string") return ["target must be a string"];
@@ -1228,11 +1208,91 @@ function warnResourceDiagnostics(kind, filePath, diagnostics) {
1228
1208
  }
1229
1209
  }
1230
1210
 
1211
+ // src/harness/skills.ts
1212
+ import { existsSync, statSync } from "fs";
1213
+ import { readdir } from "fs/promises";
1214
+ import { dirname as dirname3, join, resolve as resolve3 } from "path";
1215
+ var SKILL_MARKER = "SKILL.md";
1216
+ function resolveSkillPath(input) {
1217
+ const resolved = resolve3(input);
1218
+ return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, SKILL_MARKER) : resolved;
1219
+ }
1220
+ async function loadSkill(path) {
1221
+ const resolved = resolveSkillPath(path);
1222
+ let loaded;
1223
+ try {
1224
+ loaded = await loadMarkdown(resolved);
1225
+ } catch (error) {
1226
+ if (!(error instanceof FrontmatterParseError)) throw error;
1227
+ warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
1228
+ throw new ResourceMetadataError("parse_failed", error.message);
1229
+ }
1230
+ const { frontmatter, body } = loaded;
1231
+ const metadata = frontmatter;
1232
+ const id = inferResourceName(resolved, SKILL_MARKER);
1233
+ const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1234
+ const name = frontmatterName || id;
1235
+ const description = validateDescription(metadata.description);
1236
+ const diagnostics = validateResourceId(id);
1237
+ if (frontmatterName && frontmatterName !== id) {
1238
+ diagnostics.push(...validateResourceName(frontmatterName, id));
1239
+ }
1240
+ diagnostics.push(...description.warnings);
1241
+ warnResourceDiagnostics("skill", resolved, diagnostics);
1242
+ if (description.missing) {
1243
+ throw new ResourceMetadataError("invalid_metadata", "description is required");
1244
+ }
1245
+ return {
1246
+ name,
1247
+ description: description.description,
1248
+ filePath: resolved,
1249
+ baseDir: dirname3(resolved),
1250
+ content: body,
1251
+ disableModelInvocation: metadata["disable-model-invocation"] === true
1252
+ };
1253
+ }
1254
+ async function loadSkills(targetPath) {
1255
+ const skillsDir = resolve3(targetPath);
1256
+ if (existsSync(skillsDir) && statSync(skillsDir).isFile()) {
1257
+ try {
1258
+ return [await loadSkill(skillsDir)];
1259
+ } catch (error) {
1260
+ if (error instanceof ResourceMetadataError) return [];
1261
+ throw error;
1262
+ }
1263
+ }
1264
+ if (existsSync(join(skillsDir, SKILL_MARKER))) {
1265
+ try {
1266
+ return [await loadSkill(skillsDir)];
1267
+ } catch (error) {
1268
+ if (error instanceof ResourceMetadataError) return [];
1269
+ throw error;
1270
+ }
1271
+ }
1272
+ const entries = await readdir(skillsDir, { withFileTypes: true });
1273
+ const skills = [];
1274
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1275
+ if (!entry.isDirectory()) continue;
1276
+ const skillFile = join(skillsDir, entry.name, SKILL_MARKER);
1277
+ if (!existsSync(skillFile)) continue;
1278
+ try {
1279
+ const skill = await loadSkill(skillFile);
1280
+ skills.push(skill);
1281
+ } catch (error) {
1282
+ if (error instanceof ResourceMetadataError) continue;
1283
+ console.warn(`Failed to load skill "${entry.name}":`, error);
1284
+ }
1285
+ }
1286
+ return skills;
1287
+ }
1288
+
1231
1289
  // src/harness/phases/loader.ts
1232
1290
  var PHASE_MARKER = "PHASE.md";
1291
+ var SKILL_MARKER2 = "SKILL.md";
1292
+ var RESOURCE_MARKERS = /* @__PURE__ */ new Set([PHASE_MARKER, SKILL_MARKER2]);
1233
1293
  function resolvePhasePath(input) {
1234
- const resolved = resolve3(input);
1235
- return existsSync(resolved) && statSync(resolved).isDirectory() ? join(resolved, PHASE_MARKER) : resolved;
1294
+ const resolved = resolve4(input);
1295
+ return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, PHASE_MARKER) : resolved;
1236
1296
  }
1237
1297
  async function loadPhase(targetPath) {
1238
1298
  const resolved = resolvePhasePath(targetPath);
@@ -1250,7 +1310,9 @@ async function loadPhase(targetPath) {
1250
1310
  const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1251
1311
  let definition;
1252
1312
  try {
1253
- definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
1313
+ const phaseMetadata = { ...metadata };
1314
+ delete phaseMetadata.skills;
1315
+ definition = normalizeAgentDefinition(phaseMetadata, body, { fallbackName: directoryName });
1254
1316
  } catch (error) {
1255
1317
  const message = error instanceof Error ? error.message : String(error);
1256
1318
  warnResourceDiagnostics("phase", resolved, [message]);
@@ -1263,15 +1325,15 @@ async function loadPhase(targetPath) {
1263
1325
  diagnostics.push(...validateResourceName(frontmatterName, directoryName));
1264
1326
  }
1265
1327
  diagnostics.push(...description.warnings);
1266
- diagnostics.push(...validateSkillReferences(metadata.skills));
1267
1328
  diagnostics.push(...validatePhaseTarget(metadata.target));
1268
1329
  warnResourceDiagnostics("phase", resolved, diagnostics);
1269
- const baseDir = dirname3(resolved);
1330
+ const baseDir = dirname4(resolved);
1331
+ const skills = await loadPhaseSkills(baseDir);
1270
1332
  const phase = {
1271
1333
  name,
1272
1334
  description: description.description,
1273
1335
  tools: definition.tools ? [...definition.tools] : void 0,
1274
- skills: definition.skills ? [...definition.skills] : void 0,
1336
+ skills,
1275
1337
  target: metadata.target,
1276
1338
  input: metadata.input,
1277
1339
  isolated: metadata.isolated,
@@ -1291,6 +1353,54 @@ async function loadPhase(targetPath) {
1291
1353
  }
1292
1354
  return phase;
1293
1355
  }
1356
+ async function loadPhaseSkills(baseDir) {
1357
+ const entries = await readdir2(baseDir, { withFileTypes: true });
1358
+ const skills = [];
1359
+ for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1360
+ const entryPath = join2(baseDir, entry.name);
1361
+ if (!entry.isDirectory()) {
1362
+ if (RESOURCE_MARKERS.has(entry.name) && entry.name !== PHASE_MARKER) {
1363
+ throw invalidPhaseBundle(`resource marker ${entry.name} is not valid at the phase root`);
1364
+ }
1365
+ continue;
1366
+ }
1367
+ const markers = await findResourceMarkers(entryPath);
1368
+ const directSkillPath = join2(entryPath, SKILL_MARKER2);
1369
+ const hasDirectSkill = markers.includes(directSkillPath);
1370
+ if (!hasDirectSkill) {
1371
+ if (markers.length > 0) {
1372
+ throw invalidPhaseBundle(`resource marker ${markers[0]} is not a direct child Skill`);
1373
+ }
1374
+ continue;
1375
+ }
1376
+ const nestedMarkers = markers.filter((marker) => marker !== directSkillPath);
1377
+ if (nestedMarkers.length > 0) {
1378
+ throw invalidPhaseBundle(`resource marker ${nestedMarkers[0]} is nested below a Skill bundle`);
1379
+ }
1380
+ const skill = await loadSkill(directSkillPath);
1381
+ if (skills.some((candidate) => candidate.name === skill.name)) {
1382
+ throw invalidPhaseBundle(`duplicate bundled Skill name "${skill.name}"`);
1383
+ }
1384
+ skills.push(skill);
1385
+ }
1386
+ return skills;
1387
+ }
1388
+ async function findResourceMarkers(directory) {
1389
+ const entries = await readdir2(directory, { withFileTypes: true });
1390
+ const markers = [];
1391
+ for (const entry of entries) {
1392
+ const entryPath = join2(directory, entry.name);
1393
+ if (entry.isDirectory()) {
1394
+ markers.push(...await findResourceMarkers(entryPath));
1395
+ } else if (RESOURCE_MARKERS.has(entry.name)) {
1396
+ markers.push(entryPath);
1397
+ }
1398
+ }
1399
+ return markers;
1400
+ }
1401
+ function invalidPhaseBundle(message) {
1402
+ return new ResourceMetadataError("invalid_metadata", message);
1403
+ }
1294
1404
  function readPhaseContent(phase) {
1295
1405
  return formatResourceOutput({
1296
1406
  type: "phase",
@@ -1302,8 +1412,8 @@ function readPhaseContent(phase) {
1302
1412
  }
1303
1413
  async function loadPhases(targetPath) {
1304
1414
  const phases = /* @__PURE__ */ new Map();
1305
- const phasesDir = resolve3(targetPath);
1306
- if (existsSync(phasesDir) && statSync(phasesDir).isFile()) {
1415
+ const phasesDir = resolve4(targetPath);
1416
+ if (existsSync2(phasesDir) && statSync2(phasesDir).isFile()) {
1307
1417
  try {
1308
1418
  const phase = await loadPhase(phasesDir);
1309
1419
  phases.set(phase.name, phase);
@@ -1312,7 +1422,7 @@ async function loadPhases(targetPath) {
1312
1422
  }
1313
1423
  return { phases, entryPhaseId: null };
1314
1424
  }
1315
- if (existsSync(join(phasesDir, PHASE_MARKER))) {
1425
+ if (existsSync2(join2(phasesDir, PHASE_MARKER))) {
1316
1426
  try {
1317
1427
  const phase = await loadPhase(phasesDir);
1318
1428
  phases.set(phase.name, phase);
@@ -1321,11 +1431,11 @@ async function loadPhases(targetPath) {
1321
1431
  }
1322
1432
  return { phases, entryPhaseId: null };
1323
1433
  }
1324
- const entries = await readdir(phasesDir, { withFileTypes: true });
1434
+ const entries = await readdir2(phasesDir, { withFileTypes: true });
1325
1435
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
1326
1436
  if (!entry.isDirectory()) continue;
1327
- const phaseFile = join(phasesDir, entry.name, PHASE_MARKER);
1328
- if (!existsSync(phaseFile)) continue;
1437
+ const phaseFile = join2(phasesDir, entry.name, PHASE_MARKER);
1438
+ if (!existsSync2(phaseFile)) continue;
1329
1439
  try {
1330
1440
  const phase = await loadPhase(phaseFile);
1331
1441
  phases.set(phase.name, phase);
@@ -1339,8 +1449,8 @@ async function loadPhases(targetPath) {
1339
1449
  async function discoverPhaseCode(baseDir) {
1340
1450
  const candidates = ["index.ts", "index.js"];
1341
1451
  for (const candidate of candidates) {
1342
- const fullPath = join(baseDir, candidate);
1343
- if (existsSync(fullPath)) {
1452
+ const fullPath = join2(baseDir, candidate);
1453
+ if (existsSync2(fullPath)) {
1344
1454
  return fullPath;
1345
1455
  }
1346
1456
  }
@@ -1372,6 +1482,7 @@ function createDefaultPhase() {
1372
1482
  description: "Executes concrete task operations and produces artifacts.",
1373
1483
  filePath: "",
1374
1484
  baseDir: "",
1485
+ skills: [],
1375
1486
  content: "Execute tasks using current context.\nNo planning. No evaluation.\nRoute to next phase or stop when done."
1376
1487
  };
1377
1488
  }
@@ -1426,7 +1537,7 @@ function buildSystemPrompt(options) {
1426
1537
  }
1427
1538
  const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
1428
1539
  const skillsSection = skillsBlock ? `The following skills provide specialized instructions for specific tasks.
1429
- Read the full skill file when the task matches its description.
1540
+ Use the read tool to load a skill's file when the task matches its description.
1430
1541
  When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md) and use that absolute path in tool commands.
1431
1542
 
1432
1543
  ${skillsBlock}
@@ -1929,7 +2040,7 @@ async function runPhaseLoop(config, state, registry) {
1929
2040
  const toolExecutionManager = createToolExecutionManager();
1930
2041
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1931
2042
  const phaseTools = selectPhaseTools(allTools, phase.tools);
1932
- const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2043
+ const phaseSkills = phase.name === DEFAULT_PHASE_ID ? config.context.skills : phase.skills ?? [];
1933
2044
  let phaseContext = {
1934
2045
  systemPrompt: config.context.systemPrompt,
1935
2046
  messages: messageManager.visible(),
@@ -2208,7 +2319,7 @@ async function withRetry(fn, options = {}) {
2208
2319
  }
2209
2320
  const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
2210
2321
  options.onRetry?.(attempt, error, delayMs);
2211
- await new Promise((resolve7) => setTimeout(resolve7, delayMs));
2322
+ await new Promise((resolve8) => setTimeout(resolve8, delayMs));
2212
2323
  if (options.signal?.aborted) {
2213
2324
  throw lastError;
2214
2325
  }
@@ -2220,7 +2331,7 @@ async function executePhase(ctx) {
2220
2331
  const { phase, config, execution, registry, context } = ctx;
2221
2332
  if (phase.factory) {
2222
2333
  const api = createExtensionAPI(void 0, {
2223
- registerPhase: () => {
2334
+ registerPhase: async () => {
2224
2335
  },
2225
2336
  registerProvider: () => {
2226
2337
  },
@@ -2242,7 +2353,7 @@ async function executePhase(ctx) {
2242
2353
  config.context.messages.push(createMessage(role, content));
2243
2354
  },
2244
2355
  getAvailableTools: () => config.context.tools.map((t) => ({ name: t.name, description: t.description })),
2245
- getAvailableSkills: () => config.context.skills.map((s) => ({ name: s.name, description: s.description })),
2356
+ getAvailableSkills: () => context.skills.map((s) => ({ name: s.name, description: s.description })),
2246
2357
  getPhaseContent: (id) => registry.phases.get(id)?.content ?? "",
2247
2358
  getAvailablePhases: () => Array.from(registry.phases.keys())
2248
2359
  },
@@ -2420,7 +2531,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2420
2531
  const messages = [...context];
2421
2532
  const allTools = buildToolsWithRouting(config, availablePhases);
2422
2533
  const phaseTools = selectPhaseTools(allTools, phase.tools);
2423
- const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2534
+ const phaseSkills = phase.name === DEFAULT_PHASE_ID ? config.context.skills : phase.skills ?? [];
2424
2535
  const systemPrompt = config.context.systemPrompt;
2425
2536
  const phaseContext = {
2426
2537
  systemPrompt,
@@ -2914,6 +3025,7 @@ function snapshotConfig(config) {
2914
3025
  ...config.resources,
2915
3026
  tools: Object.freeze([...config.resources.tools]),
2916
3027
  skills: Object.freeze([...config.resources.skills]),
3028
+ ...config.resources.phases ? { phases: snapshotPhaseRegistry(config.resources.phases) } : {},
2917
3029
  ...config.resources.resourceView ? {
2918
3030
  resourceView: Object.freeze({
2919
3031
  agents: Object.freeze([...config.resources.resourceView.agents]),
@@ -2937,6 +3049,20 @@ function snapshotConfig(config) {
2937
3049
  });
2938
3050
  return Object.freeze({ ...config, definition, resources });
2939
3051
  }
3052
+ function snapshotPhaseRegistry(registry) {
3053
+ const phases = /* @__PURE__ */ new Map();
3054
+ for (const [name, phase] of registry.phases) phases.set(name, snapshotPhase(phase));
3055
+ return { phases, entryPhaseId: registry.entryPhaseId };
3056
+ }
3057
+ function snapshotPhase(phase) {
3058
+ return Object.freeze({
3059
+ ...phase,
3060
+ ...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
3061
+ skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
3062
+ ...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
3063
+ ...phase.model ? { model: Object.freeze({ ...phase.model }) } : {}
3064
+ });
3065
+ }
2940
3066
  function snapshotConfiguration(config) {
2941
3067
  const layer = config.definition.layer;
2942
3068
  const definition = Object.freeze({
@@ -3436,11 +3562,11 @@ function fromLoopResult(result) {
3436
3562
  var MAX_BUFFERED_EVENTS = 128;
3437
3563
  var MAX_BUFFERED_TEXT = 64 * 1024;
3438
3564
  function deferred() {
3439
- let resolve7;
3565
+ let resolve8;
3440
3566
  const promise = new Promise((done) => {
3441
- resolve7 = done;
3567
+ resolve8 = done;
3442
3568
  });
3443
- return { promise, resolve: resolve7 };
3569
+ return { promise, resolve: resolve8 };
3444
3570
  }
3445
3571
  var TransientRunEventSubscription = class {
3446
3572
  queue = [];
@@ -3626,6 +3752,7 @@ var HooksManager = class {
3626
3752
 
3627
3753
  // src/extensions/runner.ts
3628
3754
  import { execFile } from "child_process";
3755
+ import { dirname as dirname5, resolve as resolve5 } from "path";
3629
3756
  import {
3630
3757
  registerModel,
3631
3758
  unregisterProviderModels,
@@ -3657,9 +3784,6 @@ function createExtensionRuntime() {
3657
3784
  };
3658
3785
  }
3659
3786
 
3660
- // src/extensions/runner.ts
3661
- import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
3662
-
3663
3787
  // src/extensions/event-bus.ts
3664
3788
  function createEventBus() {
3665
3789
  const listeners = /* @__PURE__ */ new Map();
@@ -3703,7 +3827,7 @@ function createEventBus() {
3703
3827
 
3704
3828
  // src/extensions/runner.ts
3705
3829
  async function execCommand(command, args, cwd, options) {
3706
- return new Promise((resolve7, reject) => {
3830
+ return new Promise((resolve8, reject) => {
3707
3831
  const child = execFile(
3708
3832
  command,
3709
3833
  args,
@@ -3718,7 +3842,7 @@ async function execCommand(command, args, cwd, options) {
3718
3842
  reject(new Error("Command was aborted"));
3719
3843
  return;
3720
3844
  }
3721
- resolve7({
3845
+ resolve8({
3722
3846
  exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
3723
3847
  stdout: stdout ?? "",
3724
3848
  stderr: stderr ?? ""
@@ -3955,17 +4079,10 @@ var ExtensionRunner = class {
3955
4079
  adaptToPhase(reg) {
3956
4080
  const def = reg.definition;
3957
4081
  return {
3958
- name: def.name,
3959
- description: def.description,
3960
- tools: def.tools,
3961
- skills: def.skills,
3962
- target: def.target,
3963
- input: def.input,
3964
- run: def.run,
3965
- filePath: "",
3966
- baseDir: "",
3967
- content: "",
3968
- ...def.model ? { model: parseModelRef2(def.model) } : {}
4082
+ ...def,
4083
+ ...def.tools ? { tools: [...def.tools] } : {},
4084
+ skills: def.skills ? [...def.skills] : [],
4085
+ ...def.input ? { input: { ...def.input } } : {}
3969
4086
  };
3970
4087
  }
3971
4088
  // ---------------------------------------------------------------------------
@@ -4127,36 +4244,20 @@ var ExtensionRunner = class {
4127
4244
  });
4128
4245
  }
4129
4246
  registerPhase(extension, registration) {
4130
- if (typeof registration.name !== "string" || registration.name.length === 0) {
4131
- throw new Error(`Phase registration requires a "name" field.`);
4132
- }
4133
- const name = registration.name;
4134
- const description = validateDescription(registration.description);
4135
- const errors = validateResourceId(name, "name");
4136
- if (description.missing) errors.push(...description.warnings);
4137
- errors.push(...validateSkillReferences(registration.skills));
4138
- errors.push(...validatePhaseTarget(registration.target));
4139
- if (errors.length > 0) {
4140
- throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
4141
- }
4142
- if (description.warnings.length > 0) {
4143
- warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
4247
+ if (typeof registration !== "string" || registration.length === 0) {
4248
+ throw new Error(`Phase registration requires a directory path.`);
4144
4249
  }
4250
+ return this.loadRegisteredPhase(extension, registration);
4251
+ }
4252
+ async loadRegisteredPhase(extension, registration) {
4253
+ const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname5(extension.path);
4254
+ const phase = await loadPhase(resolve5(extensionBase, registration));
4255
+ const name = phase.name;
4145
4256
  if (this.phases.has(name)) {
4146
4257
  throw new Error(`Duplicate phase name: ${name}`);
4147
4258
  }
4148
- const definition = {
4149
- name,
4150
- description: description.description,
4151
- run: registration.run,
4152
- ...registration.tools ? { tools: registration.tools.slice() } : {},
4153
- ...registration.skills ? { skills: registration.skills.slice() } : {},
4154
- ...registration.target ? { target: registration.target } : {},
4155
- ...registration.input ? { input: { ...registration.input } } : {},
4156
- ...registration.model ? { model: registration.model } : {}
4157
- };
4158
4259
  const registered = {
4159
- definition,
4260
+ definition: phase,
4160
4261
  source: { extensionPath: extension.path }
4161
4262
  };
4162
4263
  this.phases.set(name, registered);
@@ -4215,10 +4316,10 @@ function createExtensionRunner(options) {
4215
4316
  }
4216
4317
 
4217
4318
  // src/extensions/loader.ts
4218
- import { existsSync as existsSync2, readFileSync } from "fs";
4219
- import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
4319
+ import { existsSync as existsSync3, readFileSync } from "fs";
4320
+ import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
4220
4321
  import { fileURLToPath } from "url";
4221
- import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
4322
+ import { basename as basename3, dirname as dirname6, extname as extname2, join as join3, resolve as resolve6 } from "path";
4222
4323
  import { createJiti } from "jiti";
4223
4324
  function isExtensionFile(path) {
4224
4325
  const extension = extname2(path).toLowerCase();
@@ -4228,8 +4329,8 @@ function isSyntheticPath(path) {
4228
4329
  return path.startsWith("<") && path.endsWith(">");
4229
4330
  }
4230
4331
  function readManifestSync(dir) {
4231
- const manifestPath = join2(dir, "package.json");
4232
- if (!existsSync2(manifestPath)) {
4332
+ const manifestPath = join3(dir, "package.json");
4333
+ if (!existsSync3(manifestPath)) {
4233
4334
  return void 0;
4234
4335
  }
4235
4336
  try {
@@ -4238,8 +4339,7 @@ function readManifestSync(dir) {
4238
4339
  const rowan = pkg.rowan;
4239
4340
  if (!rowan) return void 0;
4240
4341
  return {
4241
- entry: rowan.extensions?.[0],
4242
- phase: rowan.phase
4342
+ entry: rowan.extensions?.[0]
4243
4343
  };
4244
4344
  } catch {
4245
4345
  return void 0;
@@ -4251,7 +4351,7 @@ function jitiAliases() {
4251
4351
  return {};
4252
4352
  }
4253
4353
  const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
4254
- if (!existsSync2(agentSourcePath)) {
4354
+ if (!existsSync3(agentSourcePath)) {
4255
4355
  return {};
4256
4356
  }
4257
4357
  return { "@rowan-agent/agent": agentSourcePath };
@@ -4272,28 +4372,28 @@ async function loadExtensionModule(extensionPath) {
4272
4372
  return typeof module === "function" ? module : void 0;
4273
4373
  }
4274
4374
  async function readPackageManifest(path) {
4275
- if (!existsSync2(path)) {
4375
+ if (!existsSync3(path)) {
4276
4376
  return void 0;
4277
4377
  }
4278
4378
  const content = await readFile3(path, "utf8");
4279
4379
  return JSON.parse(content);
4280
4380
  }
4281
4381
  async function resolveExtensionEntries(dir) {
4282
- const manifest = await readPackageManifest(join2(dir, "package.json"));
4382
+ const manifest = await readPackageManifest(join3(dir, "package.json"));
4283
4383
  const declared = manifest?.rowan?.extensions;
4284
4384
  if (declared && declared.length > 0) {
4285
- return declared.map((entry) => resolve4(dir, entry));
4385
+ return declared.map((entry) => resolve6(dir, entry));
4286
4386
  }
4287
4387
  for (const name of ["index.ts", "index.js"]) {
4288
- const entry = join2(dir, name);
4289
- if (existsSync2(entry)) {
4388
+ const entry = join3(dir, name);
4389
+ if (existsSync3(entry)) {
4290
4390
  return [entry];
4291
4391
  }
4292
4392
  }
4293
4393
  return void 0;
4294
4394
  }
4295
4395
  async function discoverExtensionsInDir(dir) {
4296
- const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
4396
+ const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
4297
4397
  if (error.code === "ENOENT") {
4298
4398
  return [];
4299
4399
  }
@@ -4301,7 +4401,7 @@ async function discoverExtensionsInDir(dir) {
4301
4401
  });
4302
4402
  const paths = [];
4303
4403
  for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4304
- const entryPath = join2(dir, entry.name);
4404
+ const entryPath = join3(dir, entry.name);
4305
4405
  if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
4306
4406
  paths.push(entryPath);
4307
4407
  continue;
@@ -4319,10 +4419,10 @@ async function discoverExtensionsInDir(dir) {
4319
4419
  return paths;
4320
4420
  }
4321
4421
  function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4322
- const resolvedCwd = resolve4(cwd);
4323
- const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
4324
- const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
4325
- const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
4422
+ const resolvedCwd = resolve6(cwd);
4423
+ const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve6(resolvedCwd, extensionPath);
4424
+ const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown";
4425
+ const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname6(resolvedPath);
4326
4426
  const manifest = readManifestSync(manifestDir);
4327
4427
  return {
4328
4428
  path: resolvedPath,
@@ -4334,9 +4434,9 @@ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
4334
4434
  async function loadExtensions(paths, cwd) {
4335
4435
  const extensions = [];
4336
4436
  const errors = [];
4337
- const resolvedCwd = resolve4(cwd);
4437
+ const resolvedCwd = resolve6(cwd);
4338
4438
  for (const path of paths) {
4339
- const resolvedPath = resolve4(resolvedCwd, path);
4439
+ const resolvedPath = resolve6(resolvedCwd, path);
4340
4440
  try {
4341
4441
  const factory = await loadExtensionModule(resolvedPath);
4342
4442
  if (!factory) {
@@ -4357,10 +4457,10 @@ async function loadExtensions(paths, cwd) {
4357
4457
  return { extensions, errors };
4358
4458
  }
4359
4459
  async function loadExtensionsFromPath(path) {
4360
- const resolvedPath = resolve4(path);
4460
+ const resolvedPath = resolve6(path);
4361
4461
  const info = await stat2(resolvedPath);
4362
4462
  if (!info.isDirectory()) {
4363
- return loadExtensions([resolvedPath], dirname4(resolvedPath));
4463
+ return loadExtensions([resolvedPath], dirname6(resolvedPath));
4364
4464
  }
4365
4465
  const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
4366
4466
  return loadExtensions(entries, resolvedPath);
@@ -4368,87 +4468,7 @@ async function loadExtensionsFromPath(path) {
4368
4468
 
4369
4469
  // src/runtime/resource-registry.ts
4370
4470
  import { stat as stat3, readdir as readdir4 } from "fs/promises";
4371
- import { join as join4, resolve as resolve6 } from "path";
4372
-
4373
- // src/harness/skills.ts
4374
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
4375
- import { readdir as readdir3 } from "fs/promises";
4376
- import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
4377
- var SKILL_MARKER = "SKILL.md";
4378
- function resolveSkillPath(input) {
4379
- const resolved = resolve5(input);
4380
- return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
4381
- }
4382
- async function loadSkill(path) {
4383
- const resolved = resolveSkillPath(path);
4384
- let loaded;
4385
- try {
4386
- loaded = await loadMarkdown(resolved);
4387
- } catch (error) {
4388
- if (!(error instanceof FrontmatterParseError)) throw error;
4389
- warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
4390
- throw new ResourceMetadataError("parse_failed", error.message);
4391
- }
4392
- const { frontmatter, body } = loaded;
4393
- const metadata = frontmatter;
4394
- const id = inferResourceName(resolved, SKILL_MARKER);
4395
- const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
4396
- const name = frontmatterName || id;
4397
- const description = validateDescription(metadata.description);
4398
- const diagnostics = validateResourceId(id);
4399
- if (frontmatterName && frontmatterName !== id) {
4400
- diagnostics.push(...validateResourceName(frontmatterName, id));
4401
- }
4402
- diagnostics.push(...description.warnings);
4403
- warnResourceDiagnostics("skill", resolved, diagnostics);
4404
- if (description.missing) {
4405
- throw new ResourceMetadataError("invalid_metadata", "description is required");
4406
- }
4407
- return {
4408
- name,
4409
- description: description.description,
4410
- filePath: resolved,
4411
- baseDir: dirname5(resolved),
4412
- content: body,
4413
- disableModelInvocation: metadata["disable-model-invocation"] === true
4414
- };
4415
- }
4416
- async function loadSkills(targetPath) {
4417
- const skillsDir = resolve5(targetPath);
4418
- if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
4419
- try {
4420
- return [await loadSkill(skillsDir)];
4421
- } catch (error) {
4422
- if (error instanceof ResourceMetadataError) return [];
4423
- throw error;
4424
- }
4425
- }
4426
- if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
4427
- try {
4428
- return [await loadSkill(skillsDir)];
4429
- } catch (error) {
4430
- if (error instanceof ResourceMetadataError) return [];
4431
- throw error;
4432
- }
4433
- }
4434
- const entries = await readdir3(skillsDir, { withFileTypes: true });
4435
- const skills = [];
4436
- for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
4437
- if (!entry.isDirectory()) continue;
4438
- const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
4439
- if (!existsSync3(skillFile)) continue;
4440
- try {
4441
- const skill = await loadSkill(skillFile);
4442
- skills.push(skill);
4443
- } catch (error) {
4444
- if (error instanceof ResourceMetadataError) continue;
4445
- console.warn(`Failed to load skill "${entry.name}":`, error);
4446
- }
4447
- }
4448
- return skills;
4449
- }
4450
-
4451
- // src/runtime/resource-registry.ts
4471
+ import { join as join4, resolve as resolve7 } from "path";
4452
4472
  var ResourceRegistryError = class extends Error {
4453
4473
  code;
4454
4474
  constructor(code, message) {
@@ -4592,11 +4612,12 @@ var ResourceRegistry = class {
4592
4612
  names.add(value.name);
4593
4613
  }
4594
4614
  const revision = this.nextRevision(kind, sourceId);
4615
+ const snapshottedValues = values.map((value) => snapshotResourceValue(kind, value));
4595
4616
  const record = {
4596
4617
  kind,
4597
4618
  sourceId,
4598
4619
  revision,
4599
- values: Object.freeze([...values])
4620
+ values: Object.freeze(snapshottedValues)
4600
4621
  };
4601
4622
  let bySource = this.sources.get(kind);
4602
4623
  if (!bySource) {
@@ -4666,6 +4687,18 @@ var ResourceRegistry = class {
4666
4687
  return `${kind}:${sourceId}:${this.revisionSequence}`;
4667
4688
  }
4668
4689
  };
4690
+ function snapshotResourceValue(kind, value) {
4691
+ if (kind !== "phase") return value;
4692
+ const phase = value;
4693
+ const snapshot = {
4694
+ ...phase,
4695
+ ...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
4696
+ skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
4697
+ ...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
4698
+ ...phase.model ? { model: Object.freeze({ ...phase.model }) } : {}
4699
+ };
4700
+ return Object.freeze(snapshot);
4701
+ }
4669
4702
  function assertSourceId(sourceId) {
4670
4703
  if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
4671
4704
  throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
@@ -4687,7 +4720,7 @@ function isImplicitCoreName(kind, name) {
4687
4720
  return kind === "tool" && name === "route" || kind === "phase" && name === "default";
4688
4721
  }
4689
4722
  async function loadDirectoryValues(kind, directory, sourceId) {
4690
- const root = resolve6(directory);
4723
+ const root = resolve7(directory);
4691
4724
  let rootInfo;
4692
4725
  try {
4693
4726
  rootInfo = await stat3(root);
@@ -5009,9 +5042,8 @@ function intersectPhaseSelection(parent, layer) {
5009
5042
  }
5010
5043
  function resolvePhases(candidates, selection) {
5011
5044
  const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
5012
- if (selected.length === 0 && selection === void 0) return void 0;
5013
5045
  const entryPhaseId = selection?.entryPhaseId ?? null;
5014
- if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
5046
+ if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
5015
5047
  console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
5016
5048
  return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
5017
5049
  }
@@ -5626,7 +5658,7 @@ function boundaryFromSnapshot(snapshot) {
5626
5658
  }
5627
5659
  }
5628
5660
  function delay(ms) {
5629
- return new Promise((resolve7) => setTimeout(resolve7, ms));
5661
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
5630
5662
  }
5631
5663
  function normalizePhaseRegistry(registry) {
5632
5664
  if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
@@ -5653,13 +5685,13 @@ function toJsonValue(value) {
5653
5685
  return value;
5654
5686
  }
5655
5687
  function deferred2() {
5656
- let resolve7;
5688
+ let resolve8;
5657
5689
  let reject;
5658
5690
  const promise = new Promise((res, rej) => {
5659
- resolve7 = (value) => res(value);
5691
+ resolve8 = (value) => res(value);
5660
5692
  reject = rej;
5661
5693
  });
5662
- return { promise, resolve: resolve7, reject };
5694
+ return { promise, resolve: resolve8, reject };
5663
5695
  }
5664
5696
 
5665
5697
  // src/runtime/durable-store.ts
@@ -7137,7 +7169,9 @@ export {
7137
7169
  createCoreTools2 as createCoreTools,
7138
7170
  isRuntimeError,
7139
7171
  loadExtensionsFromPath as loadExtensions,
7172
+ loadPhase,
7140
7173
  loadPhases,
7174
+ loadSkill,
7141
7175
  loadSkills,
7142
7176
  materializeConfigurationSnapshot,
7143
7177
  parseAgentDefinition,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.8.7",
3
+ "version": "0.9.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",