@rowan-agent/agent 0.8.3 → 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;
@@ -1391,7 +1405,7 @@ declare class InMemoryStore implements DurableStore {
1391
1405
  ownerId: string;
1392
1406
  leaseMs: number;
1393
1407
  }): Promise<OwnedStore>;
1394
- assertOwner(lease: OwnerLease): void;
1408
+ assertOwner(lease: OwnerLease, requireLive?: boolean): void;
1395
1409
  renewOwner(lease: OwnerLease, leaseMs: number): OwnerLease;
1396
1410
  releaseOwner(lease: OwnerLease): void;
1397
1411
  reserveAgent(lease: OwnerLease, input: {
@@ -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
@@ -767,6 +767,15 @@ var EmptyResponseError = class extends Error {
767
767
  this.name = "EmptyResponseError";
768
768
  }
769
769
  };
770
+ var ModelOutputLimitError = class extends Error {
771
+ constructor(limit) {
772
+ super(`Model output exceeded the ${limit}-character safety limit.`);
773
+ this.limit = limit;
774
+ this.name = "ModelOutputLimitError";
775
+ }
776
+ limit;
777
+ code = "model_output_limit";
778
+ };
770
779
  var LoopGuard = {
771
780
  /** Returns abort result if signal is aborted */
772
781
  checkAbort(signal) {
@@ -778,6 +787,7 @@ var LoopGuard = {
778
787
  };
779
788
 
780
789
  // src/loop/stream-collector.ts
790
+ var MAX_STREAMED_OUTPUT_CHARACTERS = 1024 * 1024;
781
791
  async function invokeModel(input) {
782
792
  const events = input.config.stream(input.request, { signal: input.config.signal });
783
793
  const result = await collectStreamResult({
@@ -811,7 +821,22 @@ async function collectStreamResult(input) {
811
821
  let lastPartial;
812
822
  let stopReason;
813
823
  let doneResponse;
824
+ let streamedOutputCharacters = 0;
825
+ const toolCallsWithDeltas = /* @__PURE__ */ new Set();
814
826
  for await (const event of input.events) {
827
+ if (event.type === "text_delta") {
828
+ streamedOutputCharacters += event.text.length;
829
+ } else if (event.type === "thinking_delta") {
830
+ streamedOutputCharacters += event.thinking.length;
831
+ } else if (event.type === "tool_call_delta") {
832
+ streamedOutputCharacters += event.arguments.length;
833
+ toolCallsWithDeltas.add(event.id);
834
+ } else if (event.type === "tool_call_end" && !toolCallsWithDeltas.has(event.id)) {
835
+ streamedOutputCharacters += event.arguments.length;
836
+ }
837
+ if (streamedOutputCharacters > MAX_STREAMED_OUTPUT_CHARACTERS) {
838
+ throw new ModelOutputLimitError(MAX_STREAMED_OUTPUT_CHARACTERS);
839
+ }
815
840
  const abortResult2 = LoopGuard.checkAbort(input.config.signal);
816
841
  if (abortResult2.stopReason !== "none") {
817
842
  if (activeMessageId) {
@@ -990,7 +1015,92 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
990
1015
  import { existsSync, statSync } from "fs";
991
1016
  import { readdir } from "fs/promises";
992
1017
  import { dirname as dirname3, join, resolve as resolve3 } from "path";
1018
+
1019
+ // src/harness/definitions.ts
993
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
+ }
994
1104
 
995
1105
  // src/harness/resource-validation.ts
996
1106
  var MAX_RESOURCE_ID_LENGTH = 64;
@@ -1088,8 +1198,16 @@ async function loadPhase(targetPath) {
1088
1198
  const { frontmatter, body } = loaded;
1089
1199
  const metadata = frontmatter;
1090
1200
  const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
1091
- const name = frontmatterName || directoryName;
1092
- 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);
1093
1211
  const diagnostics = validateResourceId(directoryName);
1094
1212
  if (frontmatterName && frontmatterName !== directoryName) {
1095
1213
  diagnostics.push(...validateResourceName(frontmatterName, directoryName));
@@ -1098,22 +1216,19 @@ async function loadPhase(targetPath) {
1098
1216
  diagnostics.push(...validateSkillReferences(metadata.skills));
1099
1217
  diagnostics.push(...validatePhaseTarget(metadata.target));
1100
1218
  warnResourceDiagnostics("phase", resolved, diagnostics);
1101
- if (description.missing) {
1102
- throw new ResourceMetadataError("invalid_metadata", "description is required");
1103
- }
1104
1219
  const baseDir = dirname3(resolved);
1105
1220
  const phase = {
1106
1221
  name,
1107
1222
  description: description.description,
1108
- tools: metadata.tools,
1109
- skills: metadata.skills,
1223
+ tools: definition.tools ? [...definition.tools] : void 0,
1224
+ skills: definition.skills ? [...definition.skills] : void 0,
1110
1225
  target: metadata.target,
1111
1226
  input: metadata.input,
1112
1227
  isolated: metadata.isolated,
1113
1228
  filePath: resolved,
1114
1229
  baseDir,
1115
- content: body,
1116
- model: parseModelRef(metadata.model)
1230
+ content: definition.content,
1231
+ model: definition.model
1117
1232
  };
1118
1233
  const codePath = await discoverPhaseCode(baseDir);
1119
1234
  if (codePath) {
@@ -1211,6 +1326,31 @@ function createDefaultPhase() {
1211
1326
  };
1212
1327
  }
1213
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
+
1214
1354
  // src/harness/context/system-prompt.ts
1215
1355
  function buildSystemPrompt(options) {
1216
1356
  const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills } = options;
@@ -1738,8 +1878,8 @@ async function runPhaseLoop(config, state, registry) {
1738
1878
  const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1739
1879
  const toolExecutionManager = createToolExecutionManager();
1740
1880
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1741
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
1742
- 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");
1743
1883
  let phaseContext = {
1744
1884
  systemPrompt: config.context.systemPrompt,
1745
1885
  messages: messageManager.visible(),
@@ -2229,8 +2369,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2229
2369
  async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2230
2370
  const messages = [...context];
2231
2371
  const allTools = buildToolsWithRouting(config, availablePhases);
2232
- const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
2233
- 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");
2234
2374
  const systemPrompt = config.context.systemPrompt;
2235
2375
  const phaseContext = {
2236
2376
  systemPrompt,
@@ -2268,6 +2408,11 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2268
2408
  const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2269
2409
  return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
2270
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
+ }
2271
2416
  async function waitForBackgroundTasks(backgroundTasks) {
2272
2417
  const entries = Array.from(backgroundTasks.entries());
2273
2418
  const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
@@ -2498,29 +2643,29 @@ function createMetrics() {
2498
2643
  startedAtMs: Date.now()
2499
2644
  };
2500
2645
  }
2501
- function isRecord(value) {
2646
+ function isRecord2(value) {
2502
2647
  return value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
2503
2648
  }
2504
2649
  function isCheckpointData(value) {
2505
- 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;
2506
2651
  if (!isMetrics(value.metrics)) return false;
2507
2652
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
2508
2653
  return true;
2509
2654
  }
2510
2655
  function isMetrics(value) {
2511
- if (!isRecord(value)) return false;
2512
- 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));
2513
2658
  }
2514
2659
  function isContinuation(value) {
2515
- 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;
2516
2661
  if (value.previousPayload !== void 0 && !isJsonValue(value.previousPayload)) return false;
2517
- 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");
2518
2663
  }
2519
2664
 
2520
2665
  // src/runtime/contracts.ts
2521
2666
  var METADATA_LIMIT = 64 * 1024;
2522
2667
  var IDENTITY_LIMIT = 256;
2523
- function isRecord2(value) {
2668
+ function isRecord3(value) {
2524
2669
  if (value === null || typeof value !== "object") return false;
2525
2670
  const prototype = Object.getPrototypeOf(value);
2526
2671
  return prototype === Object.prototype || prototype === null;
@@ -2529,7 +2674,7 @@ function hasOnlyKeys(value, allowed) {
2529
2674
  return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.includes(key));
2530
2675
  }
2531
2676
  function isMetadata(value) {
2532
- if (!isRecord2(value) || !isJsonValue(value)) return false;
2677
+ if (!isRecord3(value) || !isJsonValue(value)) return false;
2533
2678
  try {
2534
2679
  assertUtf8ByteLimit(canonicalJson(value), METADATA_LIMIT, "metadata");
2535
2680
  return true;
@@ -2541,23 +2686,23 @@ function assertMetadata(value, argument) {
2541
2686
  if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
2542
2687
  }
2543
2688
  function isText(value) {
2544
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
2689
+ return isRecord3(value) && value.type === "text" && typeof value.text === "string";
2545
2690
  }
2546
2691
  function isImage(value) {
2547
- 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";
2548
2693
  }
2549
2694
  function isUserContent(value) {
2550
2695
  return typeof value === "string" || Array.isArray(value) && value.every((part) => isText(part) || isImage(part));
2551
2696
  }
2552
2697
  function normalizeUserInput(input) {
2553
2698
  const normalized = typeof input === "string" ? { content: input } : input;
2554
- 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");
2555
2700
  if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
2556
2701
  assertJsonValue(normalized, "input");
2557
2702
  return normalized;
2558
2703
  }
2559
2704
  function isToolResult(value) {
2560
- 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;
2561
2706
  const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
2562
2707
  if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
2563
2708
  return value.ok || typeof value.error === "string";
@@ -2565,8 +2710,11 @@ function isToolResult(value) {
2565
2710
  function assertAgentConfig(config) {
2566
2711
  if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
2567
2712
  assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
2568
- if (!config.context || typeof config.context.systemPrompt !== "string") throw new TypeError("config.context is invalid");
2569
- 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);
2570
2718
  }
2571
2719
  function assertToolExecutionResult(value) {
2572
2720
  if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
@@ -2574,7 +2722,7 @@ function assertToolExecutionResult(value) {
2574
2722
  function projectToolDefinition(tool) {
2575
2723
  if (typeof tool.name !== "string" || tool.name.length === 0 || typeof tool.description !== "string") throw new TypeError("Tool definition is invalid");
2576
2724
  assertJsonValue(tool.parameters, "tool.parameters");
2577
- 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");
2578
2726
  return { name: tool.name, description: tool.description, parameters: JSON.parse(canonicalJson(tool.parameters)) };
2579
2727
  }
2580
2728
 
@@ -2641,7 +2789,7 @@ function brandConfigToken(raw) {
2641
2789
  return raw;
2642
2790
  }
2643
2791
  function validateConfigResolution(agentId, value) {
2644
- if (!isRecord3(value) || typeof value.kind !== "string") {
2792
+ if (!isRecord4(value) || typeof value.kind !== "string") {
2645
2793
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2646
2794
  }
2647
2795
  if (value.kind === "deferred") {
@@ -2654,18 +2802,26 @@ function validateConfigResolution(agentId, value) {
2654
2802
  };
2655
2803
  }
2656
2804
  if (value.kind === "unavailable" && typeof value.reason === "string") return { kind: "unavailable", reason: value.reason };
2657
- 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") {
2658
2806
  return { kind: "available", config: value.config };
2659
2807
  }
2660
2808
  throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
2661
2809
  }
2662
2810
  function snapshotConfig(config) {
2663
- const context = Object.freeze({
2664
- ...config.context,
2665
- tools: Object.freeze([...config.context.tools]),
2666
- 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]) } : {}
2817
+ });
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]) } : {}
2667
2823
  });
2668
- return Object.freeze({ ...config, context });
2824
+ return Object.freeze({ ...config, definition, resources });
2669
2825
  }
2670
2826
  function assertConfigIdentity(identity) {
2671
2827
  if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
@@ -2680,7 +2836,7 @@ function throwIfAborted(signal) {
2680
2836
  error.name = "AbortError";
2681
2837
  throw error;
2682
2838
  }
2683
- function isRecord3(value) {
2839
+ function isRecord4(value) {
2684
2840
  return value !== null && typeof value === "object";
2685
2841
  }
2686
2842
 
@@ -3603,12 +3759,19 @@ function createExtensionRunner(options) {
3603
3759
 
3604
3760
  // src/runtime/extensions.ts
3605
3761
  async function assembleExtensions(config) {
3606
- 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
+ }
3607
3770
  const runner = createExtensionRunner({ cwd: config.cwd });
3608
- await runner.loadExtensions([...config.extensions]);
3771
+ await runner.loadExtensions(selectedExtensions);
3609
3772
  runner.bind();
3610
3773
  const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
3611
- const tools = [...config.context.tools];
3774
+ const tools = [...config.resources.tools];
3612
3775
  const names = new Set(tools.map((tool) => tool.name));
3613
3776
  for (const tool of extensionTools) {
3614
3777
  if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
@@ -3616,9 +3779,10 @@ async function assembleExtensions(config) {
3616
3779
  tools.push(tool);
3617
3780
  }
3618
3781
  const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
3619
- const phases = mergePhases(config.context.phases, extensionPhases);
3782
+ const phases = mergePhases(config.resources.phases, extensionPhases);
3783
+ const context = resolveDefinitionContext(config, { tools, phases });
3620
3784
  return {
3621
- context: { ...config.context, tools, ...phases ? { phases } : {} },
3785
+ context,
3622
3786
  beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
3623
3787
  afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
3624
3788
  beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
@@ -3632,11 +3796,41 @@ async function assembleExtensions(config) {
3632
3796
  const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
3633
3797
  return fromLoopResult(result);
3634
3798
  },
3635
- setContext: (context) => {
3636
- runner.currentContext = context;
3799
+ setContext: (context2) => {
3800
+ runner.currentContext = context2;
3637
3801
  }
3638
3802
  };
3639
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
+ }
3640
3834
  function mergePhases(base, extension) {
3641
3835
  if (!base && extension.phases.size === 0) return void 0;
3642
3836
  const phases = new Map(base?.phases ?? []);
@@ -3985,29 +4179,28 @@ var AgentRuntime = class _AgentRuntime {
3985
4179
  }
3986
4180
  const executionId = createId("exec");
3987
4181
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
4182
+ executionRevision = claim.run.revision;
3988
4183
  const controller = new AbortController();
3989
4184
  this.executions.set(run.id, { controller, executionId });
3990
4185
  const config = resolution.config;
3991
4186
  const assembly = await assembleExtensions(config);
3992
- const executionConfig = {
3993
- ...config,
3994
- context: assembly.context,
3995
- ...assembly.beforeToolCall ? { beforeToolCall: assembly.beforeToolCall } : {},
3996
- ...assembly.afterToolCall ? { afterToolCall: assembly.afterToolCall } : {}
3997
- };
3998
- executionRevision = claim.run.revision;
3999
4187
  let toolQueue = Promise.resolve();
4000
4188
  const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
4001
4189
  const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
4002
4190
  const executionContext = projectModelContext({
4003
4191
  context: {
4004
- ...executionConfig.context,
4005
- phases: normalizePhaseRegistry(executionConfig.context.phases)
4192
+ ...assembly.context,
4193
+ phases: normalizePhaseRegistry(assembly.context.phases)
4006
4194
  },
4007
4195
  messages: claim.history,
4008
4196
  agentId: run.agentId,
4009
4197
  runId: run.id
4010
4198
  });
4199
+ const executionTools = {
4200
+ tools: assembly.context.tools,
4201
+ beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
4202
+ afterToolCall: assembly.afterToolCall ?? config.afterToolCall
4203
+ };
4011
4204
  const result = await executeOnce({
4012
4205
  canonicalMessages: executionContext.messages,
4013
4206
  context: executionContext,
@@ -4018,7 +4211,7 @@ var AgentRuntime = class _AgentRuntime {
4018
4211
  },
4019
4212
  model,
4020
4213
  stream,
4021
- maxAttempts: executionConfig.maxAttempts,
4214
+ maxAttempts: config.maxAttempts,
4022
4215
  checkpoint: claim.run.checkpoint,
4023
4216
  signal: controller.signal,
4024
4217
  beforePhase: assembly.beforePhase,
@@ -4045,7 +4238,7 @@ var AgentRuntime = class _AgentRuntime {
4045
4238
  run,
4046
4239
  execution: claim.execution,
4047
4240
  expectedRevision: executionRevision,
4048
- config: executionConfig,
4241
+ toolConfig: executionTools,
4049
4242
  toolCalls: [toolCall],
4050
4243
  signal: controller.signal
4051
4244
  });
@@ -4061,7 +4254,7 @@ var AgentRuntime = class _AgentRuntime {
4061
4254
  run,
4062
4255
  execution: claim.execution,
4063
4256
  expectedRevision: executionRevision,
4064
- config: executionConfig,
4257
+ toolConfig: executionTools,
4065
4258
  toolCalls,
4066
4259
  signal: controller.signal
4067
4260
  });
@@ -4140,7 +4333,7 @@ var AgentRuntime = class _AgentRuntime {
4140
4333
  return { results, revision };
4141
4334
  }
4142
4335
  async executeReservedTool(input) {
4143
- 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);
4144
4337
  const providerToolCallId = input.toolCall.id;
4145
4338
  const toolCallId = input.tool.id;
4146
4339
  let revision = input.expectedRevision;
@@ -4175,8 +4368,8 @@ var AgentRuntime = class _AgentRuntime {
4175
4368
  }
4176
4369
  };
4177
4370
  try {
4178
- if (input.config.beforeToolCall) {
4179
- 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 });
4180
4373
  if (!decision.allow) {
4181
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 } });
4182
4375
  return { result: protocolFailure(decision.reason), revision: failed.run.revision };
@@ -4193,7 +4386,7 @@ var AgentRuntime = class _AgentRuntime {
4193
4386
  try {
4194
4387
  let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
4195
4388
  assertToolExecutionResult(result);
4196
- 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 });
4197
4390
  assertToolExecutionResult(result);
4198
4391
  const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
4199
4392
  return {
@@ -4495,17 +4688,17 @@ var InMemoryStore = class _InMemoryStore {
4495
4688
  };
4496
4689
  return new MemoryOwnedStore(this, clone(this.owner));
4497
4690
  }
4498
- assertOwner(lease) {
4691
+ assertOwner(lease, requireLive = true) {
4499
4692
  const actual = this.owner;
4500
4693
  if (!actual || actual.token !== lease.token || actual.epoch !== lease.epoch || actual.ownerId !== lease.ownerId) {
4501
4694
  throw ownershipLost(lease, actual, "epoch_advanced");
4502
4695
  }
4503
- if (Date.parse(actual.expiresAt) <= Date.now()) {
4696
+ if (requireLive && Date.parse(actual.expiresAt) <= Date.now()) {
4504
4697
  throw ownershipLost(lease, actual, "expired");
4505
4698
  }
4506
4699
  }
4507
4700
  renewOwner(lease, leaseMs) {
4508
- this.assertOwner(lease);
4701
+ this.assertOwner(lease, false);
4509
4702
  assertLeaseDuration(leaseMs);
4510
4703
  this.owner.expiresAt = expiry(leaseMs);
4511
4704
  return clone(this.owner);
@@ -4600,23 +4793,25 @@ var InMemoryStore = class _InMemoryStore {
4600
4793
  if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && ["running", "input_required"].includes(candidate.state))) {
4601
4794
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
4602
4795
  }
4603
- const messageId = input.messageId ?? createId("msg");
4604
4796
  if (run.pinnedConfigToken && input.configToken && run.pinnedConfigToken !== input.configToken) {
4605
4797
  throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
4606
4798
  }
4607
4799
  if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
4608
- const userInput = normalizeUserInput(run.input);
4609
- const message = {
4610
- id: messageId,
4611
- agentId: run.agentId,
4612
- runId: run.id,
4613
- role: "user",
4614
- content: userInputContent(userInput),
4615
- ...userInputMetadata(userInput) ? { metadata: clone(userInputMetadata(userInput)) } : {},
4616
- sequenceWithinRun: this.nextMessageSequence(run.id),
4617
- createdAt: createTimestamp()
4618
- };
4619
- this.messages.set(message.id, message);
4800
+ let message;
4801
+ if (!run.checkpoint) {
4802
+ const userInput = normalizeUserInput(run.input);
4803
+ message = {
4804
+ id: input.messageId ?? createId("msg"),
4805
+ agentId: run.agentId,
4806
+ runId: run.id,
4807
+ role: "user",
4808
+ content: userInputContent(userInput),
4809
+ ...userInputMetadata(userInput) ? { metadata: clone(userInputMetadata(userInput)) } : {},
4810
+ sequenceWithinRun: this.nextMessageSequence(run.id),
4811
+ createdAt: createTimestamp()
4812
+ };
4813
+ this.messages.set(message.id, message);
4814
+ }
4620
4815
  const execution = {
4621
4816
  runId: run.id,
4622
4817
  ownerEpoch: lease.epoch,
@@ -4626,7 +4821,7 @@ var InMemoryStore = class _InMemoryStore {
4626
4821
  run.execution = execution;
4627
4822
  run.revision += 1;
4628
4823
  run.updatedAt = createTimestamp();
4629
- this.appendMessage(run, message);
4824
+ if (message) this.appendMessage(run, message);
4630
4825
  this.appendTransition(run, "queued", "running");
4631
4826
  const result = { run: clone(run), execution: clone(execution), history: this.history(run.agentId, run.agentSequence) };
4632
4827
  this.writeOperationReceipt(operationKey, operationPayload, result);
@@ -5533,7 +5728,7 @@ var SqliteStore = class {
5533
5728
  this.assertOpen();
5534
5729
  assertOwnerInput(lease.ownerId, leaseMs);
5535
5730
  return this.immediateTransaction(() => {
5536
- const row = this.requireMatchingOwner(lease, true);
5731
+ const row = this.requireMatchingOwner(lease, false);
5537
5732
  const expiresAt = Date.now() + leaseMs;
5538
5733
  this.writeOwner({ ...row, expires_at: expiresAt });
5539
5734
  return { ...lease, expiresAt: new Date(expiresAt).toISOString() };
@@ -6094,5 +6289,7 @@ export {
6094
6289
  isRuntimeError,
6095
6290
  loadExtensionsFromPath as loadExtensions,
6096
6291
  loadPhases,
6097
- loadSkills
6292
+ loadSkills,
6293
+ parseAgentDefinition,
6294
+ parseFrontmatter
6098
6295
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",