@rowan-agent/agent 0.8.4 → 0.8.5

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
@@ -21,8 +21,12 @@ const agentId = await runtime.createAgent({
21
21
  identity: "example:v1", // Stable config snapshot identity, not the Agent ID
22
22
  model: { provider: "openai", id: "gpt-4o" },
23
23
  stream,
24
- context: {
25
- systemPrompt: "You are helpful.",
24
+ definition: {
25
+ name: "workspace-assistant",
26
+ description: "Assist with the current workspace.",
27
+ content: "You are helpful.",
28
+ },
29
+ resources: {
26
30
  tools: createCoreTools({ root: process.cwd() }),
27
31
  skills: [],
28
32
  },
@@ -68,7 +72,8 @@ lifecycles, and Owner fencing.
68
72
 
69
73
  ## Tool lifecycle
70
74
 
71
- Tools are supplied through `AgentConfig.context.tools` and persist through:
75
+ Tools are supplied as `AgentConfig.resources.tools`, selected by the
76
+ Definition, and persist through:
72
77
 
73
78
  `pending → running → completed | failed | indeterminate`
74
79
 
@@ -101,5 +106,7 @@ checkpoints; transient events never enter the Durable Store.
101
106
 
102
107
  ## Resources
103
108
 
104
- `loadSkills()`, `loadPhases()`, and `loadExtensions()` load workspace resources.
105
- Pass the resulting resources through `AgentConfig.context` or `extensions`.
109
+ `loadSkills()`, `loadPhases()`, and `loadExtensions()` load Resource Candidates.
110
+ Pass them through `AgentConfig.resources`; the Definition's optional name lists
111
+ select from them after Extension assembly. Omitted lists inherit all candidates,
112
+ `[]` selects none, and missing names warn and are skipped.
package/dist/index.d.ts CHANGED
@@ -865,6 +865,20 @@ type ToolProgress = Readonly<{
865
865
  }>;
866
866
  type RunEvent = DurableRunEvent | MessageDelta | ToolProgress;
867
867
 
868
+ type AgentDefinition = Readonly<{
869
+ name: string;
870
+ description: string;
871
+ content: string;
872
+ tools?: readonly string[];
873
+ skills?: readonly string[];
874
+ phases?: readonly string[];
875
+ extensions?: readonly string[];
876
+ entryPhase?: string;
877
+ model?: ModelRef;
878
+ }>;
879
+ /** Parse one Markdown definition using the same frontmatter vocabulary as Phase resources. */
880
+ declare function parseAgentDefinition(raw: string): AgentDefinition;
881
+
868
882
  type UserInput = string | Readonly<{
869
883
  content: UserContent;
870
884
  metadata?: Metadata;
@@ -903,17 +917,17 @@ type AfterToolCall = (input: Readonly<{
903
917
  context: ToolInvocationContext;
904
918
  signal: AbortSignal;
905
919
  }>) => ToolExecutionResult | Promise<ToolExecutionResult>;
906
- type AgentDefinitionContext = Readonly<{
907
- systemPrompt: string;
920
+ type AgentResources = Readonly<{
908
921
  tools: readonly Tool[];
909
922
  skills: readonly Skill[];
910
923
  phases?: PhaseRegistry;
924
+ extensions?: readonly LoadedExtension[];
911
925
  }>;
912
926
  type AgentConfig = Readonly<{
913
927
  identity: string;
914
- context: AgentDefinitionContext;
928
+ definition: AgentDefinition;
929
+ resources: AgentResources;
915
930
  cwd?: string;
916
- extensions?: readonly LoadedExtension[];
917
931
  maxAttempts?: number;
918
932
  beforeToolCall?: BeforeToolCall;
919
933
  afterToolCall?: AfterToolCall;
@@ -1806,6 +1820,17 @@ declare function loadExtensionsFromPath(path: string): Promise<{
1806
1820
  }>;
1807
1821
  }>;
1808
1822
 
1823
+ /** Parsed frontmatter result */
1824
+ interface FrontmatterResult<T = Record<string, unknown>> {
1825
+ frontmatter: T;
1826
+ body: string;
1827
+ }
1828
+ /**
1829
+ * Parse YAML frontmatter from markdown content.
1830
+ * Parse YAML frontmatter and return the markdown body after the closing delimiter.
1831
+ */
1832
+ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): FrontmatterResult<T>;
1833
+
1809
1834
  declare function createCoreTools(input?: CoreToolContext): Tool[];
1810
1835
 
1811
- export { type AfterToolCall, type AgentConfig, type AgentDefinitionContext, type AgentId, type AgentListCursor, type AgentRecord, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type CoreToolContext, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionFactory, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, 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 PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, 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 };
1836
+ export { type AfterToolCall, type AgentConfig, 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 CoreToolContext, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionFactory, 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 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 PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, 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, parseAgentDefinition, parseFrontmatter };
package/dist/index.js CHANGED
@@ -1015,7 +1015,92 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1015
1015
  import { existsSync, statSync } from "fs";
1016
1016
  import { readdir } from "fs/promises";
1017
1017
  import { dirname as dirname3, join, resolve as resolve3 } from "path";
1018
+
1019
+ // src/harness/definitions.ts
1018
1020
  import { parseModelRef } from "@rowan-agent/models";
1021
+ function assertAgentDefinition(value) {
1022
+ if (!isRecord(value)) throw new TypeError("config.definition is invalid");
1023
+ requiredString(value.name, "definition.name");
1024
+ requiredString(value.description, "definition.description");
1025
+ requiredString(value.content, "definition.content");
1026
+ optionalStringList(value.tools, "definition.tools");
1027
+ optionalStringList(value.skills, "definition.skills");
1028
+ optionalStringList(value.phases, "definition.phases");
1029
+ optionalStringList(value.extensions, "definition.extensions");
1030
+ optionalString(value.entryPhase, "definition.entryPhase");
1031
+ if (value.model !== void 0) {
1032
+ if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
1033
+ throw new TypeError("definition.model must be a valid model reference");
1034
+ }
1035
+ }
1036
+ }
1037
+ function parseAgentDefinition(raw) {
1038
+ const { frontmatter, body } = parseFrontmatter(raw);
1039
+ return normalizeAgentDefinition(frontmatter, body);
1040
+ }
1041
+ function normalizeAgentDefinition(frontmatter, content, options = {}) {
1042
+ const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
1043
+ const description = requiredString(frontmatter.description, "description");
1044
+ const normalizedContent = requiredString(content, "content");
1045
+ const tools = optionalStringList(frontmatter.tools, "tools");
1046
+ const skills = optionalStringList(frontmatter.skills, "skills");
1047
+ const phases = optionalStringList(frontmatter.phases, "phases");
1048
+ const extensions = optionalStringList(frontmatter.extensions, "extensions");
1049
+ const entryPhase = optionalString(frontmatter.entryPhase, "entryPhase");
1050
+ const model = optionalModel(frontmatter.model);
1051
+ return {
1052
+ name,
1053
+ description,
1054
+ content: normalizedContent,
1055
+ ...tools ? { tools } : {},
1056
+ ...skills ? { skills } : {},
1057
+ ...phases ? { phases } : {},
1058
+ ...extensions ? { extensions } : {},
1059
+ ...entryPhase ? { entryPhase } : {},
1060
+ ...model ? { model } : {}
1061
+ };
1062
+ }
1063
+ function requiredString(value, field) {
1064
+ if (typeof value !== "string" || value.trim() === "") {
1065
+ throw new TypeError(`${field} is required`);
1066
+ }
1067
+ return value.trim();
1068
+ }
1069
+ function optionalString(value, field) {
1070
+ if (value === void 0) return void 0;
1071
+ if (typeof value !== "string" || value.trim() === "") {
1072
+ throw new TypeError(`${field} must be a non-empty string`);
1073
+ }
1074
+ return value.trim();
1075
+ }
1076
+ function optionalStringList(value, field) {
1077
+ if (value === void 0) return void 0;
1078
+ if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of strings`);
1079
+ const names = [];
1080
+ const seen = /* @__PURE__ */ new Set();
1081
+ for (const [index, item] of value.entries()) {
1082
+ if (typeof item !== "string" || item.trim() === "") {
1083
+ throw new TypeError(`${field}[${index}] must be a non-empty string`);
1084
+ }
1085
+ const name = item.trim();
1086
+ if (seen.has(name)) continue;
1087
+ seen.add(name);
1088
+ names.push(name);
1089
+ }
1090
+ return names;
1091
+ }
1092
+ function optionalModel(value) {
1093
+ const source = optionalString(value, "model");
1094
+ if (!source) return void 0;
1095
+ const model = parseModelRef(source);
1096
+ if (!model || model.provider.trim() === "" || model.id.trim() === "") {
1097
+ throw new TypeError("model must be a valid model reference");
1098
+ }
1099
+ return model;
1100
+ }
1101
+ function isRecord(value) {
1102
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1103
+ }
1019
1104
 
1020
1105
  // src/harness/resource-validation.ts
1021
1106
  var MAX_RESOURCE_ID_LENGTH = 64;
@@ -1113,8 +1198,16 @@ async function loadPhase(targetPath) {
1113
1198
  const { frontmatter, body } = loaded;
1114
1199
  const metadata = frontmatter;
1115
1200
  const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1116
- const name = frontmatterName || directoryName;
1117
- const description = validateDescription(metadata.description);
1201
+ let definition;
1202
+ try {
1203
+ definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
1204
+ } catch (error) {
1205
+ const message = error instanceof Error ? error.message : String(error);
1206
+ warnResourceDiagnostics("phase", resolved, [message]);
1207
+ throw new ResourceMetadataError("invalid_metadata", message);
1208
+ }
1209
+ const name = definition.name;
1210
+ const description = validateDescription(definition.description);
1118
1211
  const diagnostics = validateResourceId(directoryName);
1119
1212
  if (frontmatterName && frontmatterName !== directoryName) {
1120
1213
  diagnostics.push(...validateResourceName(frontmatterName, directoryName));
@@ -1123,22 +1216,19 @@ async function loadPhase(targetPath) {
1123
1216
  diagnostics.push(...validateSkillReferences(metadata.skills));
1124
1217
  diagnostics.push(...validatePhaseTarget(metadata.target));
1125
1218
  warnResourceDiagnostics("phase", resolved, diagnostics);
1126
- if (description.missing) {
1127
- throw new ResourceMetadataError("invalid_metadata", "description is required");
1128
- }
1129
1219
  const baseDir = dirname3(resolved);
1130
1220
  const phase = {
1131
1221
  name,
1132
1222
  description: description.description,
1133
- tools: metadata.tools,
1134
- skills: metadata.skills,
1223
+ tools: definition.tools ? [...definition.tools] : void 0,
1224
+ skills: definition.skills ? [...definition.skills] : void 0,
1135
1225
  target: metadata.target,
1136
1226
  input: metadata.input,
1137
1227
  isolated: metadata.isolated,
1138
1228
  filePath: resolved,
1139
1229
  baseDir,
1140
- content: body,
1141
- model: parseModelRef(metadata.model)
1230
+ content: definition.content,
1231
+ model: definition.model
1142
1232
  };
1143
1233
  const codePath = await discoverPhaseCode(baseDir);
1144
1234
  if (codePath) {
@@ -1236,6 +1326,31 @@ function createDefaultPhase() {
1236
1326
  };
1237
1327
  }
1238
1328
 
1329
+ // src/harness/resource-selection.ts
1330
+ function selectNamedResources(candidates, names, kind) {
1331
+ const byName = /* @__PURE__ */ new Map();
1332
+ for (const candidate of candidates) {
1333
+ if (byName.has(candidate.name)) {
1334
+ throw new TypeError(`Duplicate ${kind} candidate "${candidate.name}".`);
1335
+ }
1336
+ byName.set(candidate.name, candidate);
1337
+ }
1338
+ if (names === void 0) return [...candidates];
1339
+ const selected = [];
1340
+ const seen = /* @__PURE__ */ new Set();
1341
+ for (const name of names) {
1342
+ if (seen.has(name)) continue;
1343
+ seen.add(name);
1344
+ const candidate = byName.get(name);
1345
+ if (!candidate) {
1346
+ console.warn(`${kind} "${name}" is not available and will be skipped.`);
1347
+ continue;
1348
+ }
1349
+ selected.push(candidate);
1350
+ }
1351
+ return selected;
1352
+ }
1353
+
1239
1354
  // src/harness/context/system-prompt.ts
1240
1355
  function buildSystemPrompt(options) {
1241
1356
  const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills } = options;
@@ -1763,8 +1878,8 @@ async function runPhaseLoop(config, state, registry) {
1763
1878
  const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1764
1879
  const toolExecutionManager = createToolExecutionManager();
1765
1880
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1766
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
1767
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
1881
+ const phaseTools = selectPhaseTools(allTools, phase.tools);
1882
+ const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
1768
1883
  let phaseContext = {
1769
1884
  systemPrompt: config.context.systemPrompt,
1770
1885
  messages: messageManager.visible(),
@@ -2254,8 +2369,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2254
2369
  async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2255
2370
  const messages = [...context];
2256
2371
  const allTools = buildToolsWithRouting(config, availablePhases);
2257
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
2258
- const phaseSkills = phase.skills ? config.context.skills.filter((s) => phase.skills.includes(s.name)) : config.context.skills;
2372
+ const phaseTools = selectPhaseTools(allTools, phase.tools);
2373
+ const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
2259
2374
  const systemPrompt = config.context.systemPrompt;
2260
2375
  const phaseContext = {
2261
2376
  systemPrompt,
@@ -2293,6 +2408,11 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2293
2408
  const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2294
2409
  return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
2295
2410
  }
2411
+ function selectPhaseTools(candidates, names) {
2412
+ const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
2413
+ const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
2414
+ return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
2415
+ }
2296
2416
  async function waitForBackgroundTasks(backgroundTasks) {
2297
2417
  const entries = Array.from(backgroundTasks.entries());
2298
2418
  const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
@@ -2523,29 +2643,29 @@ function createMetrics() {
2523
2643
  startedAtMs: Date.now()
2524
2644
  };
2525
2645
  }
2526
- function isRecord(value) {
2646
+ function isRecord2(value) {
2527
2647
  return value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
2528
2648
  }
2529
2649
  function isCheckpointData(value) {
2530
- if (!isRecord(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2650
+ if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
2531
2651
  if (!isMetrics(value.metrics)) return false;
2532
2652
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
2533
2653
  return true;
2534
2654
  }
2535
2655
  function isMetrics(value) {
2536
- if (!isRecord(value)) return false;
2537
- return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
2656
+ if (!isRecord2(value)) return false;
2657
+ return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
2538
2658
  }
2539
2659
  function isContinuation(value) {
2540
- if (!isRecord(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
2660
+ if (!isRecord2(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
2541
2661
  if (value.previousPayload !== void 0 && !isJsonValue(value.previousPayload)) return false;
2542
- return value.previousResults.every((result) => isRecord(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
2662
+ return value.previousResults.every((result) => isRecord2(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
2543
2663
  }
2544
2664
 
2545
2665
  // src/runtime/contracts.ts
2546
2666
  var METADATA_LIMIT = 64 * 1024;
2547
2667
  var IDENTITY_LIMIT = 256;
2548
- function isRecord2(value) {
2668
+ function isRecord3(value) {
2549
2669
  if (value === null || typeof value !== "object") return false;
2550
2670
  const prototype = Object.getPrototypeOf(value);
2551
2671
  return prototype === Object.prototype || prototype === null;
@@ -2554,7 +2674,7 @@ function hasOnlyKeys(value, allowed) {
2554
2674
  return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.includes(key));
2555
2675
  }
2556
2676
  function isMetadata(value) {
2557
- if (!isRecord2(value) || !isJsonValue(value)) return false;
2677
+ if (!isRecord3(value) || !isJsonValue(value)) return false;
2558
2678
  try {
2559
2679
  assertUtf8ByteLimit(canonicalJson(value), METADATA_LIMIT, "metadata");
2560
2680
  return true;
@@ -2566,23 +2686,23 @@ function assertMetadata(value, argument) {
2566
2686
  if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
2567
2687
  }
2568
2688
  function isText(value) {
2569
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
2689
+ return isRecord3(value) && value.type === "text" && typeof value.text === "string";
2570
2690
  }
2571
2691
  function isImage(value) {
2572
- return isRecord2(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
2692
+ return isRecord3(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
2573
2693
  }
2574
2694
  function isUserContent(value) {
2575
2695
  return typeof value === "string" || Array.isArray(value) && value.every((part) => isText(part) || isImage(part));
2576
2696
  }
2577
2697
  function normalizeUserInput(input) {
2578
2698
  const normalized = typeof input === "string" ? { content: input } : input;
2579
- if (!isRecord2(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
2699
+ if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
2580
2700
  if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
2581
2701
  assertJsonValue(normalized, "input");
2582
2702
  return normalized;
2583
2703
  }
2584
2704
  function isToolResult(value) {
2585
- if (!isRecord2(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
2705
+ if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
2586
2706
  const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
2587
2707
  if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
2588
2708
  return value.ok || typeof value.error === "string";
@@ -2590,8 +2710,11 @@ function isToolResult(value) {
2590
2710
  function assertAgentConfig(config) {
2591
2711
  if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2592
2712
  assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2593
- if (!config.context || typeof config.context.systemPrompt !== "string") throw new TypeError("config.context is invalid");
2594
- for (const tool of config.context.tools) projectToolDefinition(tool);
2713
+ assertAgentDefinition(config.definition);
2714
+ if (!config.resources || !Array.isArray(config.resources.tools) || !Array.isArray(config.resources.skills)) {
2715
+ throw new TypeError("config.resources is invalid");
2716
+ }
2717
+ for (const tool of config.resources.tools) projectToolDefinition(tool);
2595
2718
  }
2596
2719
  function assertToolExecutionResult(value) {
2597
2720
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -2599,7 +2722,7 @@ function assertToolExecutionResult(value) {
2599
2722
  function projectToolDefinition(tool) {
2600
2723
  if (typeof tool.name !== "string" || tool.name.length === 0 || typeof tool.description !== "string") throw new TypeError("Tool definition is invalid");
2601
2724
  assertJsonValue(tool.parameters, "tool.parameters");
2602
- if (!isRecord2(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
2725
+ if (!isRecord3(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
2603
2726
  return { name: tool.name, description: tool.description, parameters: JSON.parse(canonicalJson(tool.parameters)) };
2604
2727
  }
2605
2728
 
@@ -2666,7 +2789,7 @@ function brandConfigToken(raw) {
2666
2789
  return raw;
2667
2790
  }
2668
2791
  function validateConfigResolution(agentId, value) {
2669
- if (!isRecord3(value) || typeof value.kind !== "string") {
2792
+ if (!isRecord4(value) || typeof value.kind !== "string") {
2670
2793
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2671
2794
  }
2672
2795
  if (value.kind === "deferred") {
@@ -2679,18 +2802,26 @@ function validateConfigResolution(agentId, value) {
2679
2802
  };
2680
2803
  }
2681
2804
  if (value.kind === "unavailable" && typeof value.reason === "string") return { kind: "unavailable", reason: value.reason };
2682
- if (value.kind === "available" && isRecord3(value.config) && typeof value.config.identity === "string") {
2805
+ if (value.kind === "available" && isRecord4(value.config) && typeof value.config.identity === "string") {
2683
2806
  return { kind: "available", config: value.config };
2684
2807
  }
2685
2808
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2686
2809
  }
2687
2810
  function snapshotConfig(config) {
2688
- const context = Object.freeze({
2689
- ...config.context,
2690
- tools: Object.freeze([...config.context.tools]),
2691
- skills: Object.freeze([...config.context.skills])
2811
+ const definition = Object.freeze({
2812
+ ...config.definition,
2813
+ ...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
2814
+ ...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
2815
+ ...config.definition.phases ? { phases: Object.freeze([...config.definition.phases]) } : {},
2816
+ ...config.definition.extensions ? { extensions: Object.freeze([...config.definition.extensions]) } : {}
2692
2817
  });
2693
- return Object.freeze({ ...config, context });
2818
+ const resources = Object.freeze({
2819
+ ...config.resources,
2820
+ tools: Object.freeze([...config.resources.tools]),
2821
+ skills: Object.freeze([...config.resources.skills]),
2822
+ ...config.resources.extensions ? { extensions: Object.freeze([...config.resources.extensions]) } : {}
2823
+ });
2824
+ return Object.freeze({ ...config, definition, resources });
2694
2825
  }
2695
2826
  function assertConfigIdentity(identity) {
2696
2827
  if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
@@ -2705,7 +2836,7 @@ function throwIfAborted(signal) {
2705
2836
  error.name = "AbortError";
2706
2837
  throw error;
2707
2838
  }
2708
- function isRecord3(value) {
2839
+ function isRecord4(value) {
2709
2840
  return value !== null && typeof value === "object";
2710
2841
  }
2711
2842
 
@@ -3628,12 +3759,19 @@ function createExtensionRunner(options) {
3628
3759
 
3629
3760
  // src/runtime/extensions.ts
3630
3761
  async function assembleExtensions(config) {
3631
- if (!config.extensions?.length) return { context: config.context };
3762
+ const selectedExtensions = selectNamedResources(
3763
+ config.resources.extensions ?? [],
3764
+ config.definition.extensions,
3765
+ "Extension"
3766
+ );
3767
+ if (selectedExtensions.length === 0) {
3768
+ return { context: resolveDefinitionContext(config) };
3769
+ }
3632
3770
  const runner = createExtensionRunner({ cwd: config.cwd });
3633
- await runner.loadExtensions([...config.extensions]);
3771
+ await runner.loadExtensions(selectedExtensions);
3634
3772
  runner.bind();
3635
3773
  const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3636
- const tools = [...config.context.tools];
3774
+ const tools = [...config.resources.tools];
3637
3775
  const names = new Set(tools.map((tool) => tool.name));
3638
3776
  for (const tool of extensionTools) {
3639
3777
  if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
@@ -3641,9 +3779,10 @@ async function assembleExtensions(config) {
3641
3779
  tools.push(tool);
3642
3780
  }
3643
3781
  const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3644
- const phases = mergePhases(config.context.phases, extensionPhases);
3782
+ const phases = mergePhases(config.resources.phases, extensionPhases);
3783
+ const context = resolveDefinitionContext(config, { tools, phases });
3645
3784
  return {
3646
- context: { ...config.context, tools, ...phases ? { phases } : {} },
3785
+ context,
3647
3786
  beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3648
3787
  afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3649
3788
  beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
@@ -3657,11 +3796,41 @@ async function assembleExtensions(config) {
3657
3796
  const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3658
3797
  return fromLoopResult(result);
3659
3798
  },
3660
- setContext: (context) => {
3661
- runner.currentContext = context;
3799
+ setContext: (context2) => {
3800
+ runner.currentContext = context2;
3662
3801
  }
3663
3802
  };
3664
3803
  }
3804
+ function resolveDefinitionContext(config, assembled = {}) {
3805
+ const tools = selectNamedResources(
3806
+ assembled.tools ?? config.resources.tools,
3807
+ config.definition.tools,
3808
+ "Tool"
3809
+ );
3810
+ const skills = selectNamedResources(
3811
+ config.resources.skills,
3812
+ config.definition.skills,
3813
+ "Skill"
3814
+ );
3815
+ const candidateRegistry = assembled.phases ?? config.resources.phases;
3816
+ const selectedPhases = selectNamedResources(
3817
+ [...candidateRegistry?.phases.values() ?? []],
3818
+ config.definition.phases,
3819
+ "Phase"
3820
+ );
3821
+ const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
3822
+ const requestedEntry = config.definition.entryPhase ?? candidateRegistry?.entryPhaseId ?? null;
3823
+ const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
3824
+ if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
3825
+ console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
3826
+ }
3827
+ return {
3828
+ systemPrompt: config.definition.content,
3829
+ tools,
3830
+ skills,
3831
+ phases: { phases, entryPhaseId }
3832
+ };
3833
+ }
3665
3834
  function mergePhases(base, extension) {
3666
3835
  if (!base && extension.phases.size === 0) return void 0;
3667
3836
  const phases = new Map(base?.phases ?? []);
@@ -4010,29 +4179,28 @@ var AgentRuntime = class _AgentRuntime {
4010
4179
  }
4011
4180
  const executionId = createId("exec");
4012
4181
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
4182
+ executionRevision = claim.run.revision;
4013
4183
  const controller = new AbortController();
4014
4184
  this.executions.set(run.id, { controller, executionId });
4015
4185
  const config = resolution.config;
4016
4186
  const assembly = await assembleExtensions(config);
4017
- const executionConfig = {
4018
- ...config,
4019
- context: assembly.context,
4020
- ...assembly.beforeToolCall ? { beforeToolCall: assembly.beforeToolCall } : {},
4021
- ...assembly.afterToolCall ? { afterToolCall: assembly.afterToolCall } : {}
4022
- };
4023
- executionRevision = claim.run.revision;
4024
4187
  let toolQueue = Promise.resolve();
4025
4188
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
4026
4189
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
4027
4190
  const executionContext = projectModelContext({
4028
4191
  context: {
4029
- ...executionConfig.context,
4030
- phases: normalizePhaseRegistry(executionConfig.context.phases)
4192
+ ...assembly.context,
4193
+ phases: normalizePhaseRegistry(assembly.context.phases)
4031
4194
  },
4032
4195
  messages: claim.history,
4033
4196
  agentId: run.agentId,
4034
4197
  runId: run.id
4035
4198
  });
4199
+ const executionTools = {
4200
+ tools: assembly.context.tools,
4201
+ beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
4202
+ afterToolCall: assembly.afterToolCall ?? config.afterToolCall
4203
+ };
4036
4204
  const result = await executeOnce({
4037
4205
  canonicalMessages: executionContext.messages,
4038
4206
  context: executionContext,
@@ -4043,7 +4211,7 @@ var AgentRuntime = class _AgentRuntime {
4043
4211
  },
4044
4212
  model,
4045
4213
  stream,
4046
- maxAttempts: executionConfig.maxAttempts,
4214
+ maxAttempts: config.maxAttempts,
4047
4215
  checkpoint: claim.run.checkpoint,
4048
4216
  signal: controller.signal,
4049
4217
  beforePhase: assembly.beforePhase,
@@ -4070,7 +4238,7 @@ var AgentRuntime = class _AgentRuntime {
4070
4238
  run,
4071
4239
  execution: claim.execution,
4072
4240
  expectedRevision: executionRevision,
4073
- config: executionConfig,
4241
+ toolConfig: executionTools,
4074
4242
  toolCalls: [toolCall],
4075
4243
  signal: controller.signal
4076
4244
  });
@@ -4086,7 +4254,7 @@ var AgentRuntime = class _AgentRuntime {
4086
4254
  run,
4087
4255
  execution: claim.execution,
4088
4256
  expectedRevision: executionRevision,
4089
- config: executionConfig,
4257
+ toolConfig: executionTools,
4090
4258
  toolCalls,
4091
4259
  signal: controller.signal
4092
4260
  });
@@ -4165,7 +4333,7 @@ var AgentRuntime = class _AgentRuntime {
4165
4333
  return { results, revision };
4166
4334
  }
4167
4335
  async executeReservedTool(input) {
4168
- const durableTool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
4336
+ const durableTool = input.toolConfig.tools.find((candidate) => candidate.name === input.toolCall.name);
4169
4337
  const providerToolCallId = input.toolCall.id;
4170
4338
  const toolCallId = input.tool.id;
4171
4339
  let revision = input.expectedRevision;
@@ -4200,8 +4368,8 @@ var AgentRuntime = class _AgentRuntime {
4200
4368
  }
4201
4369
  };
4202
4370
  try {
4203
- if (input.config.beforeToolCall) {
4204
- const decision = await input.config.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
4371
+ if (input.toolConfig.beforeToolCall) {
4372
+ const decision = await input.toolConfig.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
4205
4373
  if (!decision.allow) {
4206
4374
  const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: decision.reason } });
4207
4375
  return { result: protocolFailure(decision.reason), revision: failed.run.revision };
@@ -4218,7 +4386,7 @@ var AgentRuntime = class _AgentRuntime {
4218
4386
  try {
4219
4387
  let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
4220
4388
  assertToolExecutionResult(result);
4221
- if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
4389
+ if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
4222
4390
  assertToolExecutionResult(result);
4223
4391
  const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
4224
4392
  return {
@@ -6121,5 +6289,7 @@ export {
6121
6289
  isRuntimeError,
6122
6290
  loadExtensionsFromPath as loadExtensions,
6123
6291
  loadPhases,
6124
- loadSkills
6292
+ loadSkills,
6293
+ parseAgentDefinition,
6294
+ parseFrontmatter
6125
6295
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",