@rowan-agent/agent 0.5.0 → 0.5.2

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
@@ -264,6 +264,8 @@ Per iteration:
264
264
  5. Transition, continue, or stop
265
265
  ```
266
266
 
267
+ **`entryPhaseId`** specifies which phase the loop enters first. When phases are loaded from `.rowan/phases/`, the first discovered phase becomes the entry. When none are configured, the Agent normalises to `"default"`. This field is an internal routing hint for the phase loop — it is **not** exposed to the LLM, since the agent does not need to know which phase is the entry point to make routing decisions.
268
+
267
269
  ### Example Phase Flow
268
270
 
269
271
  ```
@@ -377,7 +379,7 @@ interface PhaseContext {
377
379
  messages: AgentMessage[];
378
380
  tools: Tool[];
379
381
  skills: Skill[];
380
- state: PhaseState; // { current, available, iterations, payload }
382
+ state: PhaseState; // { current, available, entryPhaseId, iterations, payload }
381
383
  }
382
384
 
383
385
  type PhaseOutput = {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _rowan_agent_models from '@rowan-agent/models';
2
- import { LlmRequest, LlmResponse, LlmModelRef, LlmModelUsage, AgentMessage, Skill as Skill$1, ToolResult, Outcome, ContentBlock, ToolCall, ProviderConfig, StreamFn, AgentEventListener, Protocol, ModelCost, AgentEvent, LlmMessage } from '@rowan-agent/models';
2
+ import { LlmRequest, LlmResponse, LlmModelRef, LlmModelUsage, AgentMessage, ContentBlock, ToolCall, ToolResult, Outcome, ProviderConfig, Skill as Skill$1, StreamFn, AgentEventListener, Protocol, ModelCost, AgentEvent, LlmMessage } from '@rowan-agent/models';
3
3
  export { AgentEvent, AgentEventListener, AgentMessage, LlmModelRef, Outcome, Skill, StreamFn, ToolResult, createDispatchStream, registerBuiltInApiProviders } from '@rowan-agent/models';
4
4
  import Type from 'typebox';
5
5
 
@@ -23,87 +23,6 @@ type ModelTranscript = {
23
23
  response: LlmResponse;
24
24
  };
25
25
 
26
- type LoopMetrics = {
27
- /** Number of phase iterations executed. */
28
- iterations: number;
29
- /** Phase transition history. */
30
- phaseTransitions: Array<{
31
- from: string;
32
- to: string;
33
- ts: string;
34
- }>;
35
- /** Number of times compaction was triggered. */
36
- compactionCount: number;
37
- /** Number of retry attempts due to transient errors. */
38
- retryCount: number;
39
- /** Loop start timestamp. */
40
- startedAt: string;
41
- /** Loop start time as epoch ms (for duration calculation). */
42
- startedAtMs: number;
43
- /** Loop end timestamp (set on completion). */
44
- endedAt?: string;
45
- /** Total wall-clock duration in ms. */
46
- durationMs?: number;
47
- };
48
- type SessionState = {
49
- currentPhase: string;
50
- attempt: number;
51
- metrics: LoopMetrics;
52
- status: "idle" | "running" | "completed" | "aborted" | "failed";
53
- };
54
-
55
- type ToolContext = Pick<AgentContext, "skills"> & {
56
- toolCallId: string;
57
- };
58
- type ToolExecutionMode = "sequential" | "parallel";
59
- type Tool<TArgs = unknown> = {
60
- name: string;
61
- description: string;
62
- parameters: Type.TSchema;
63
- /** One-line snippet shown in the system prompt tool list. */
64
- promptSnippet?: string;
65
- /** Additional guidelines appended to the system prompt when this tool is active. */
66
- promptGuidelines?: string[];
67
- /** Whether this tool can run concurrently with others. Default: "parallel". */
68
- executionMode?: ToolExecutionMode;
69
- execute(args: TArgs, context: ToolContext, signal?: AbortSignal): Promise<ToolResult>;
70
- };
71
- /** Context snapshot passed into the low-level agent loop. */
72
- type AgentContext = {
73
- /** System prompt included with the request. */
74
- systemPrompt: string;
75
- /** Transcript visible to the model. */
76
- messages: AgentMessage[];
77
- /** Tools available for this run. */
78
- tools: Tool[];
79
- /** Skills available for this run. */
80
- skills: Skill$1[];
81
- /** Custom phases for this run. Agent normalizes this with the built-in default phase. */
82
- phases?: PhaseRegistry;
83
- };
84
- type BeforeToolCall = (input: {
85
- tool: Tool;
86
- args: unknown;
87
- }) => Promise<{
88
- allow: true;
89
- } | {
90
- allow: false;
91
- reason: string;
92
- }>;
93
- type AfterToolCall = (input: {
94
- tool: Tool;
95
- result: ToolResult;
96
- }) => Promise<ToolResult>;
97
- type RunResult = {
98
- sessionId: string;
99
- messages: AgentMessage[];
100
- outcome: Outcome;
101
- metrics: LoopMetrics;
102
- };
103
- type Unsubscribe = () => void;
104
- declare function createMessage(role: AgentMessage["role"], content: AgentMessage["content"], metadata?: Record<string, unknown>): AgentMessage;
105
- declare function messageContentText(content: AgentMessage["content"]): string;
106
-
107
26
  type ModelInvokeOutput = {
108
27
  text: string;
109
28
  contentBlocks: ContentBlock[];
@@ -127,89 +46,6 @@ type PhaseExecution = {
127
46
  executeTool(context: AgentContext, toolCall: ToolCall): Promise<ToolResult>;
128
47
  };
129
48
 
130
- /** Unified phase output — model decides routing via route. */
131
- type PhaseOutput = {
132
- message: string;
133
- /** Route to next phase, or "continue" to re-execute current phase, or "stop" to end */
134
- route: string;
135
- /** Phase name that produced this output */
136
- phase?: string;
137
- /** Tool calls from the model invocation (used by framework for route extraction) */
138
- toolCalls?: Array<{
139
- id: string;
140
- name: string;
141
- args: unknown;
142
- }>;
143
- /** Route reason extracted from route tool call (for hooks to inspect) */
144
- routeReason?: string;
145
- /** Structured data from this phase, passed to the next phase */
146
- payload?: unknown;
147
- };
148
- /** Phase machine state — tracks position and inter-phase data */
149
- interface PhaseState {
150
- current: string;
151
- available: string[];
152
- iterations: number;
153
- payload?: unknown;
154
- }
155
- /** Everything a phase needs to execute */
156
- interface PhaseContext {
157
- systemPrompt: string;
158
- messages: AgentMessage[];
159
- /** Phase-filtered tools */
160
- tools: Tool[];
161
- /** Phase-filtered skills */
162
- skills: Skill$1[];
163
- /** Phase machine state */
164
- state: PhaseState;
165
- /** Additional guideline bullets appended to the system prompt */
166
- promptGuidelines?: string[];
167
- /** Text to append after the system prompt */
168
- appendSystemPrompt?: string;
169
- }
170
- /**
171
- * Loaded Phase object
172
- */
173
- interface Phase {
174
- /** Unique identifier */
175
- id: string;
176
- /** Display name */
177
- name: string;
178
- /** Description */
179
- description: string;
180
- /** Restricted tools (undefined = all tools available) */
181
- tools?: string[];
182
- /** Restricted skills (undefined = all skills available) */
183
- skills?: string[];
184
- /** Forced next phase */
185
- target?: string;
186
- /** Expected input fields (key → description) */
187
- input?: Record<string, string>;
188
- /** If true, phase gets a fresh context when executed in parallel */
189
- isolated?: boolean;
190
- /** Path to PHASE.md file */
191
- filePath: string;
192
- /** Phase directory path */
193
- baseDir: string;
194
- /** PHASE.md body content */
195
- content: string;
196
- /** Model override for this phase (resolved from frontmatter) */
197
- model?: LlmModelRef;
198
- /** ExtensionAPI factory function (default export pattern) */
199
- factory?: (api: ExtensionAPI) => Promise<void>;
200
- /** Legacy run function */
201
- run?: (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
202
- }
203
- /**
204
- * Phase registry containing all loaded phases
205
- */
206
- interface PhaseRegistry {
207
- /** Map of phase id to Phase object */
208
- phases: Map<string, Phase>;
209
- /** Entry phase id (null until Agent applies its default phase) */
210
- entryPhaseId: string | null;
211
- }
212
-
213
49
  /**
214
50
  * Extension types — simplified for the new hook-based system.
215
51
  */
@@ -1307,10 +1143,202 @@ declare class ExtensionRunner {
1307
1143
  }
1308
1144
  declare function createExtensionRunner(options?: ExtensionRunnerOptions): ExtensionRunner;
1309
1145
 
1146
+ type LoopMetrics = {
1147
+ /** Number of phase iterations executed. */
1148
+ iterations: number;
1149
+ /** Phase transition history. */
1150
+ phaseTransitions: Array<{
1151
+ from: string;
1152
+ to: string;
1153
+ ts: string;
1154
+ }>;
1155
+ /** Number of times compaction was triggered. */
1156
+ compactionCount: number;
1157
+ /** Number of retry attempts due to transient errors. */
1158
+ retryCount: number;
1159
+ /** Loop start timestamp. */
1160
+ startedAt: string;
1161
+ /** Loop start time as epoch ms (for duration calculation). */
1162
+ startedAtMs: number;
1163
+ /** Loop end timestamp (set on completion). */
1164
+ endedAt?: string;
1165
+ /** Total wall-clock duration in ms. */
1166
+ durationMs?: number;
1167
+ };
1168
+ type SessionState = {
1169
+ currentPhase: string;
1170
+ attempt: number;
1171
+ metrics: LoopMetrics;
1172
+ status: "idle" | "running" | "completed" | "aborted" | "failed";
1173
+ };
1174
+
1175
+ type ToolContext = Pick<AgentContext, "skills"> & {
1176
+ toolCallId: string;
1177
+ };
1178
+ type ToolExecutionMode = "sequential" | "parallel";
1179
+ type Tool<TArgs = unknown> = {
1180
+ name: string;
1181
+ description: string;
1182
+ parameters: Type.TSchema;
1183
+ /** One-line snippet shown in the system prompt tool list. */
1184
+ promptSnippet?: string;
1185
+ /** Additional guidelines appended to the system prompt when this tool is active. */
1186
+ promptGuidelines?: string[];
1187
+ /** Whether this tool can run concurrently with others. Default: "parallel". */
1188
+ executionMode?: ToolExecutionMode;
1189
+ execute(args: TArgs, context: ToolContext, signal?: AbortSignal): Promise<ToolResult>;
1190
+ };
1191
+ /** Context snapshot passed into the low-level agent loop. */
1192
+ type AgentContext = {
1193
+ /** System prompt included with the request. */
1194
+ systemPrompt: string;
1195
+ /** Transcript visible to the model. */
1196
+ messages: AgentMessage[];
1197
+ /** Tools available for this run. */
1198
+ tools: Tool[];
1199
+ /** Skills available for this run. */
1200
+ skills: Skill$1[];
1201
+ /** Custom phases for this run. Agent normalizes this with the built-in default phase. */
1202
+ phases?: PhaseRegistry;
1203
+ };
1204
+ type BeforeToolCall = (input: {
1205
+ tool: Tool;
1206
+ args: unknown;
1207
+ }) => Promise<{
1208
+ allow: true;
1209
+ } | {
1210
+ allow: false;
1211
+ reason: string;
1212
+ }>;
1213
+ type AfterToolCall = (input: {
1214
+ tool: Tool;
1215
+ result: ToolResult;
1216
+ }) => Promise<ToolResult>;
1217
+ type RunResult = {
1218
+ sessionId: string;
1219
+ messages: AgentMessage[];
1220
+ outcome: Outcome;
1221
+ metrics: LoopMetrics;
1222
+ };
1223
+ type Unsubscribe = () => void;
1224
+ declare function createMessage(role: AgentMessage["role"], content: AgentMessage["content"], metadata?: Record<string, unknown>): AgentMessage;
1225
+ declare function messageContentText(content: AgentMessage["content"]): string;
1226
+
1227
+ /** Unified phase output — model decides routing via route. */
1228
+ type PhaseOutput = {
1229
+ message: string;
1230
+ /** Route to next phase, or "continue" to re-execute current phase, or "stop" to end */
1231
+ route: string;
1232
+ /** Phase name that produced this output */
1233
+ phase?: string;
1234
+ /** Tool calls from the model invocation (used by framework for route extraction) */
1235
+ toolCalls?: Array<{
1236
+ id: string;
1237
+ name: string;
1238
+ args: unknown;
1239
+ }>;
1240
+ /** Route reason extracted from route tool call (for hooks to inspect) */
1241
+ routeReason?: string;
1242
+ /** Structured data from this phase, passed to the next phase */
1243
+ payload?: unknown;
1244
+ };
1245
+ /** Phase machine state — tracks position and inter-phase data */
1246
+ interface PhaseState {
1247
+ current: string;
1248
+ available: string[];
1249
+ iterations: number;
1250
+ payload?: unknown;
1251
+ }
1252
+ /** Everything a phase needs to execute */
1253
+ interface PhaseContext {
1254
+ systemPrompt: string;
1255
+ messages: AgentMessage[];
1256
+ /** Phase-filtered tools */
1257
+ tools: Tool[];
1258
+ /** Phase-filtered skills */
1259
+ skills: Skill$1[];
1260
+ /** Phase machine state */
1261
+ state: PhaseState;
1262
+ /** Additional guideline bullets appended to the system prompt */
1263
+ promptGuidelines?: string[];
1264
+ /** Text to append after the system prompt */
1265
+ appendSystemPrompt?: string;
1266
+ }
1267
+ /**
1268
+ * Loaded Phase object
1269
+ */
1270
+ interface Phase {
1271
+ /** Unique identifier */
1272
+ id: string;
1273
+ /** Display name */
1274
+ name: string;
1275
+ /** Description */
1276
+ description: string;
1277
+ /** Restricted tools (undefined = all tools available) */
1278
+ tools?: string[];
1279
+ /** Restricted skills (undefined = all skills available) */
1280
+ skills?: string[];
1281
+ /** Forced next phase */
1282
+ target?: string;
1283
+ /** Expected input fields (key → description) */
1284
+ input?: Record<string, string>;
1285
+ /** If true, phase gets a fresh context when executed in parallel */
1286
+ isolated?: boolean;
1287
+ /** Path to PHASE.md file */
1288
+ filePath: string;
1289
+ /** Phase directory path */
1290
+ baseDir: string;
1291
+ /** PHASE.md body content */
1292
+ content: string;
1293
+ /** Model override for this phase (resolved from frontmatter) */
1294
+ model?: LlmModelRef;
1295
+ /** ExtensionAPI factory function (default export pattern) */
1296
+ factory?: (api: ExtensionAPI) => Promise<void>;
1297
+ /** Legacy run function */
1298
+ run?: (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
1299
+ }
1300
+ /**
1301
+ * Phase registry containing all loaded phases
1302
+ */
1303
+ interface PhaseRegistry {
1304
+ /** Map of phase id to Phase object */
1305
+ phases: Map<string, Phase>;
1306
+ /** Entry phase id (null until Agent applies its default phase) */
1307
+ entryPhaseId: string | null;
1308
+ }
1309
+
1310
+ type WorkspacePaths = {
1311
+ cwd: string;
1312
+ rowanDir: string;
1313
+ };
1314
+ type ResolveWorkspaceOptions = {
1315
+ cwd?: string;
1316
+ env?: NodeJS.ProcessEnv;
1317
+ homeDir?: string;
1318
+ rowanDir?: string;
1319
+ };
1320
+ declare function resolveWorkspacePaths(options?: ResolveWorkspaceOptions): WorkspacePaths;
1321
+ declare function resolveInWorkspace(path: string, rootOrPaths: string | Pick<WorkspacePaths, "cwd">): string;
1322
+
1323
+ type PhaseLoadTarget = string | Pick<WorkspacePaths, "rowanDir">;
1324
+ /**
1325
+ * Load a single phase from a PHASE.md file or phase directory.
1326
+ *
1327
+ * If a directory is provided, the loader reads its PHASE.md.
1328
+ */
1329
+ declare function loadPhase(targetPath: string): Promise<Phase>;
1330
+ declare function loadPhases(targetPath: PhaseLoadTarget): Promise<PhaseRegistry>;
1331
+ /**
1332
+ * Re-read all file-based phases from disk and update the registry in place.
1333
+ * Extension-registered phases (with empty filePath) are preserved as-is.
1334
+ */
1335
+ declare function reloadPhases(registry: PhaseRegistry): Promise<void>;
1336
+
1310
1337
  type AgentOptions = {
1311
1338
  context: AgentContext;
1312
1339
  model: LlmModelRef;
1313
1340
  stream: StreamFn;
1341
+ cwd?: string;
1314
1342
  extensions?: LoadedExtension[];
1315
1343
  sessionId?: string;
1316
1344
  maxAttempts?: number;
@@ -1342,8 +1370,9 @@ declare class Agent {
1342
1370
  private activeRun?;
1343
1371
  private extensionRunner?;
1344
1372
  private loadedExtensions?;
1373
+ private loadedExtensionsCwd?;
1345
1374
  static loadSkills(targetPath: string): Promise<AgentContext["skills"]>;
1346
- static loadPhases(targetPath: string): Promise<PhaseRegistry>;
1375
+ static loadPhases(targetPath: Parameters<typeof loadPhases>[0]): Promise<PhaseRegistry>;
1347
1376
  static loadExtensions(targetPath: string): Promise<LoadExtensionsResult>;
1348
1377
  constructor(options: AgentOptions);
1349
1378
  subscribe(listener: AgentEventListener): Unsubscribe;
@@ -1614,19 +1643,6 @@ type CoreToolContext = {
1614
1643
  };
1615
1644
  declare function createCoreTools(input?: CoreToolContext): Tool[];
1616
1645
 
1617
- type WorkspacePaths = {
1618
- cwd: string;
1619
- rowanDir: string;
1620
- };
1621
- type ResolveWorkspaceOptions = {
1622
- cwd?: string;
1623
- env?: NodeJS.ProcessEnv;
1624
- homeDir?: string;
1625
- rowanDir?: string;
1626
- };
1627
- declare function resolveWorkspacePaths(options?: ResolveWorkspaceOptions): WorkspacePaths;
1628
- declare function resolveInWorkspace(path: string, rootOrPaths: string | Pick<WorkspacePaths, "cwd">): string;
1629
-
1630
1646
  type ModelConfigFromFile = {
1631
1647
  id: string;
1632
1648
  name?: string;
@@ -1693,6 +1709,19 @@ declare class AgentEventStream extends EventStream<AgentEvent, AgentMessage[]> {
1693
1709
  constructor();
1694
1710
  }
1695
1711
 
1712
+ /** Error thrown when the model returns an empty response (no text, no tool calls). */
1713
+ declare class EmptyResponseError extends Error {
1714
+ readonly code = "empty_response";
1715
+ constructor(message?: string);
1716
+ }
1717
+ /** Error thrown when a routed phase needs a route tool call but the model did not emit one. */
1718
+ declare class MissingRouteToolCallError extends Error {
1719
+ readonly code = "missing_route_tool_call";
1720
+ readonly phase: string;
1721
+ readonly toolCallNames: string[];
1722
+ constructor(phase: string, toolCallNames: string[]);
1723
+ }
1724
+
1696
1725
  interface SystemPromptOptions {
1697
1726
  /** Base system prompt. */
1698
1727
  systemPrompt: string;
@@ -1746,4 +1775,4 @@ declare function buildModelRequest(input: ModelRequestInput, options?: {
1746
1775
  model?: LlmModelRef;
1747
1776
  }): LlmRequest;
1748
1777
 
1749
- export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, messageContentText, parseModelRef, registerConfigModels, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveWorkspacePaths, serializeSkills };
1778
+ export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, EmptyResponseError, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, MissingRouteToolCallError, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, loadPhase, loadPhases, messageContentText, parseModelRef, registerConfigModels, reloadPhases, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveWorkspacePaths, serializeSkills };
package/dist/index.js CHANGED
@@ -21,6 +21,19 @@ var EmptyResponseError = class extends Error {
21
21
  this.name = "EmptyResponseError";
22
22
  }
23
23
  };
24
+ var MissingRouteToolCallError = class extends Error {
25
+ code = "missing_route_tool_call";
26
+ phase;
27
+ toolCallNames;
28
+ constructor(phase, toolCallNames) {
29
+ super(
30
+ `Phase "${phase}" requires a route tool call, but the model did not emit one.`
31
+ );
32
+ this.name = "MissingRouteToolCallError";
33
+ this.phase = phase;
34
+ this.toolCallNames = toolCallNames;
35
+ }
36
+ };
24
37
  var LoopGuard = {
25
38
  /** Returns abort result if signal is aborted */
26
39
  checkAbort(signal) {
@@ -81,6 +94,9 @@ var createOutcome = {
81
94
  id: createId("out"),
82
95
  message
83
96
  };
97
+ if (output.payload !== void 0) {
98
+ outcome.payload = output.payload;
99
+ }
84
100
  if (toolResults.length > 0) {
85
101
  outcome.toolResults = toolResults;
86
102
  }
@@ -747,9 +763,12 @@ function readPhaseContent(phase) {
747
763
  baseDir: phase.baseDir
748
764
  });
749
765
  }
766
+ function resolvePhaseLoadTarget(targetPath) {
767
+ return typeof targetPath === "string" ? targetPath : join2(targetPath.rowanDir, "phases");
768
+ }
750
769
  async function loadPhases(targetPath) {
751
770
  const phases = /* @__PURE__ */ new Map();
752
- const phasesDir = resolve(targetPath);
771
+ const phasesDir = resolve(resolvePhaseLoadTarget(targetPath));
753
772
  if (existsSync2(phasesDir) && statSync(phasesDir).isFile()) {
754
773
  const phase = await loadPhase(phasesDir);
755
774
  phases.set(phase.id, phase);
@@ -799,6 +818,19 @@ async function loadPhaseCode(codePath) {
799
818
  }
800
819
  throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
801
820
  }
821
+ async function reloadPhases(registry) {
822
+ for (const [id, phase] of registry.phases) {
823
+ if (!phase.filePath) {
824
+ continue;
825
+ }
826
+ try {
827
+ const fresh = await loadPhase(phase.filePath);
828
+ registry.phases.set(id, fresh);
829
+ } catch (error) {
830
+ console.warn(`Failed to reload phase "${id}":`, error);
831
+ }
832
+ }
833
+ }
802
834
 
803
835
  // src/harness/phases/default.ts
804
836
  var DEFAULT_PHASE_ID = "default";
@@ -1956,6 +1988,14 @@ async function runPhaseLoop(config, state, registry) {
1956
1988
  output = extAfter.output;
1957
1989
  }
1958
1990
  }
1991
+ const routeToolAvailable = phaseContext.tools.some((tool) => tool.name === PhaseRouteTool);
1992
+ const routeRequired = !phase.target && !phase.run && !phase.factory && routeToolAvailable && registry.phases.size > 1;
1993
+ if (routeRequired && !routeDecision) {
1994
+ throw new MissingRouteToolCallError(
1995
+ currentPhaseId,
1996
+ output.toolCalls?.map((toolCall) => toolCall.name) ?? []
1997
+ );
1998
+ }
1959
1999
  if (output.route === "continue") {
1960
2000
  isContinuing = true;
1961
2001
  continue;
@@ -3378,6 +3418,7 @@ var Agent = class {
3378
3418
  activeRun;
3379
3419
  extensionRunner;
3380
3420
  loadedExtensions;
3421
+ loadedExtensionsCwd;
3381
3422
  static loadSkills(targetPath) {
3382
3423
  return loadSkills(targetPath);
3383
3424
  }
@@ -3388,14 +3429,14 @@ var Agent = class {
3388
3429
  return loadExtensionsFromPath(targetPath);
3389
3430
  }
3390
3431
  constructor(options) {
3391
- const context = cloneAgentContext(options.context);
3432
+ const context = prepareAgentContext(options.context);
3392
3433
  this.options = {
3393
3434
  ...options,
3394
3435
  context
3395
3436
  };
3396
3437
  this.state = {
3397
3438
  ...this.options.sessionId ? { sessionId: this.options.sessionId } : {},
3398
- context: cloneAgentContext(context),
3439
+ context: prepareAgentContext(context),
3399
3440
  model: this.options.model,
3400
3441
  tools: this.options.context.tools ?? [],
3401
3442
  isRunning: false
@@ -3529,28 +3570,30 @@ var Agent = class {
3529
3570
  this.state.isRunning = false;
3530
3571
  this.activeRun = void 0;
3531
3572
  }
3532
- async loadExtensions(extensions) {
3573
+ async loadExtensions(extensions, cwd) {
3533
3574
  if (extensions.length === 0) {
3534
3575
  this.extensionRunner?.invalidate();
3535
3576
  this.extensionRunner = void 0;
3536
3577
  this.loadedExtensions = extensions;
3578
+ this.loadedExtensionsCwd = cwd;
3537
3579
  return;
3538
3580
  }
3539
- if (this.extensionRunner && this.loadedExtensions === extensions) {
3581
+ if (this.extensionRunner && this.loadedExtensions === extensions && this.loadedExtensionsCwd === cwd) {
3540
3582
  return;
3541
3583
  }
3542
- const runner = createExtensionRunner();
3584
+ const runner = createExtensionRunner({ cwd });
3543
3585
  await runner.loadExtensions(extensions);
3544
3586
  runner.bind();
3545
3587
  this.extensionRunner?.invalidate();
3546
3588
  this.extensionRunner = runner;
3547
3589
  this.loadedExtensions = extensions;
3590
+ this.loadedExtensionsCwd = cwd;
3548
3591
  }
3549
3592
  async run(config) {
3550
- const extensions = config?.extensions ?? this.options.extensions ?? [];
3551
- await this.loadExtensions(extensions);
3552
3593
  const resolved = this.resolveRunConfig(config);
3553
- const runContext = cloneAgentContext(resolved.context, this.extensionRunner);
3594
+ const extensions = resolved.extensions ?? [];
3595
+ await this.loadExtensions(extensions, resolved.cwd);
3596
+ const runContext = prepareAgentContext(resolved.context, this.extensionRunner);
3554
3597
  const sessionId = resolved.sessionId ?? this.state.sessionId;
3555
3598
  this.options = resolved;
3556
3599
  if (sessionId) {
@@ -3601,11 +3644,11 @@ var Agent = class {
3601
3644
  onModelTranscript: resolved.onModelTranscript
3602
3645
  });
3603
3646
  const nextContext = {
3604
- ...cloneAgentContext(resolved.context),
3647
+ ...prepareAgentContext(resolved.context),
3605
3648
  messages: snapshotMessages(result.messages)
3606
3649
  };
3607
3650
  this.state.sessionId = result.sessionId;
3608
- this.state.context = cloneAgentContext(nextContext, this.extensionRunner);
3651
+ this.state.context = prepareAgentContext(nextContext, this.extensionRunner);
3609
3652
  this.state.currentResult = result;
3610
3653
  this.options = {
3611
3654
  ...resolved,
@@ -3648,8 +3691,8 @@ ${additionalInstructions}` : content;
3648
3691
  * @returns Formatted phase content string, or empty string if not found
3649
3692
  */
3650
3693
  async phase(name) {
3651
- await this.loadExtensions(this.options.extensions ?? []);
3652
- const registry = cloneAgentContext(this.options.context, this.extensionRunner).phases?.phases;
3694
+ await this.loadExtensions(this.options.extensions ?? [], this.options.cwd);
3695
+ const registry = prepareAgentContext(this.options.context, this.extensionRunner).phases?.phases;
3653
3696
  const phase = registry?.get(name) ?? [...registry?.values() ?? []].find((candidate) => candidate.name === name);
3654
3697
  return phase ? readPhaseContent(phase) : "";
3655
3698
  }
@@ -3661,19 +3704,22 @@ ${additionalInstructions}` : content;
3661
3704
  await this.flushEvents().catch(() => void 0);
3662
3705
  }
3663
3706
  resolveRunConfig(config) {
3664
- const context = cloneAgentContext(config?.context ?? this.createContextSnapshot());
3665
- return {
3707
+ const merged = {
3666
3708
  ...this.options,
3667
- ...config,
3709
+ ...config
3710
+ };
3711
+ const context = prepareAgentContext(config?.context ?? this.createContextSnapshot());
3712
+ return {
3713
+ ...merged,
3668
3714
  context,
3669
3715
  sessionId: config?.sessionId ?? this.state.sessionId ?? this.options.sessionId
3670
3716
  };
3671
3717
  }
3672
3718
  createContextSnapshot() {
3673
- return cloneAgentContext(this.options.context);
3719
+ return prepareAgentContext(this.options.context);
3674
3720
  }
3675
3721
  };
3676
- function cloneAgentContext(context, extensionRunner) {
3722
+ function prepareAgentContext(context, extensionRunner) {
3677
3723
  const phases = /* @__PURE__ */ new Map();
3678
3724
  phases.set(DEFAULT_PHASE_ID, createDefaultPhase());
3679
3725
  for (const [id, phase] of context.phases?.phases ?? []) {
@@ -4222,10 +4268,12 @@ import {
4222
4268
  export {
4223
4269
  Agent,
4224
4270
  AgentEventStream,
4271
+ EmptyResponseError,
4225
4272
  EventStream,
4226
4273
  ExtensionRunner,
4227
4274
  HooksManager,
4228
4275
  LocalJsonlSessionManager,
4276
+ MissingRouteToolCallError,
4229
4277
  appendUserTurn,
4230
4278
  buildModelRequest,
4231
4279
  buildSystemPrompt,
@@ -4246,10 +4294,13 @@ export {
4246
4294
  interpolateEnvVars,
4247
4295
  latestUserInput,
4248
4296
  loadConfigFile,
4297
+ loadPhase,
4298
+ loadPhases,
4249
4299
  messageContentText,
4250
4300
  parseModelRef,
4251
4301
  registerBuiltInApiProviders,
4252
4302
  registerConfigModels,
4303
+ reloadPhases,
4253
4304
  resetGlobalHooks,
4254
4305
  resolveDefaultModel,
4255
4306
  resolveInWorkspace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",