@rowan-agent/agent 0.5.2 → 0.5.4

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
@@ -42,14 +42,52 @@ The `Agent` class is the public facade. It drives the entire execution loop —
42
42
  ```ts
43
43
  class Agent {
44
44
  constructor(options: AgentOptions);
45
- run(options?: RunOptions): Promise<AgentRunResult>;
45
+ run(options?: RunOptions): Promise<RunResult>;
46
+
47
+ // User input / conversation continuation
48
+ appendUserMessage(input: string): void;
49
+ appendMessage(message: AgentMessage): void;
50
+ appendMessages(messages: AgentMessage[]): void;
51
+ runWithUserInput(input: string, options?: RunOptions): Promise<RunResult>;
52
+ runWithMessage(message: AgentMessage, options?: RunOptions): Promise<RunResult>;
53
+ resetInitialization(): void;
54
+
55
+ // Context and transcript
56
+ getContext(): AgentContext;
57
+ setContext(context: AgentContext): void;
58
+ updateContext(updater: (context: AgentContext) => AgentContext): void;
59
+ forkContext(overrides?: Partial<AgentContext>): AgentContext;
60
+ getMessages(): AgentMessage[];
61
+ setMessages(messages: AgentMessage[]): void;
62
+ clearMessages(): void;
63
+ getTranscript(): AgentMessage[];
64
+ replaceTranscript(messages: AgentMessage[]): void;
65
+
66
+ // Config access and shortcuts
67
+ getConfig(): AgentOptions;
68
+ setConfig(config: AgentOptions): void;
69
+ updateConfig(updater: (config: AgentOptions) => AgentOptions): void;
70
+ setSessionId(sessionId: string): void;
71
+ getSessionId(): string | undefined;
72
+ setModel(model: LlmModelRef): void;
73
+ setTools(tools: Tool[]): void;
74
+ setSkills(skills: Skill[]): void;
75
+ setPhases(phases: PhaseRegistry): void;
76
+ setCwd(cwd: string): void;
77
+ setStream(stream: StreamFn): void;
78
+ getModel(): LlmModelRef;
79
+ getTools(): Tool[];
80
+ getSkills(): Skill[];
81
+ getPhases(): PhaseRegistry | undefined;
82
+ getCwd(): string | undefined;
83
+
46
84
  abort(): void;
47
85
  subscribe(listener: AgentEventListener): () => void;
48
- skill(name: string): Promise<void>;
86
+ skill(name: string, additionalInstructions?: string): string;
49
87
  phase(name: string): Promise<string>;
50
88
  waitForIdle(): Promise<void>;
51
89
  flushEvents(): Promise<void>;
52
- readonly status: AgentStatus;
90
+ readonly state: AgentStatus;
53
91
 
54
92
  // Resource loading — replaces standalone loadSkills/loadPhases/loadExtensions
55
93
  static loadSkills(targetPath: string): Promise<Skill[]>;
@@ -65,6 +103,7 @@ type AgentOptions = {
65
103
  context: AgentContext;
66
104
  model: LlmModelRef;
67
105
  stream: StreamFn;
106
+ cwd?: string;
68
107
  extensions?: LoadedExtension[];
69
108
  sessionId?: string;
70
109
  maxAttempts?: number;
@@ -78,18 +117,59 @@ type AgentOptions = {
78
117
  };
79
118
  ```
80
119
 
81
- ### AgentRunResult
120
+ ### RunResult
82
121
 
83
122
  ```ts
84
- type AgentRunResult = {
85
- status: AgentStatus;
86
- outcome?: Outcome;
87
- metrics?: LoopMetrics;
88
- messages: AgentMessage[];
123
+ type RunResult = {
89
124
  sessionId: string;
125
+ messages: AgentMessage[];
126
+ outcome: Outcome;
127
+ metrics: LoopMetrics;
90
128
  };
91
129
  ```
92
130
 
131
+ ### Conversation Continuation
132
+
133
+ For multi-turn use, append input to the agent's current transcript and run with the updated context. `runWithUserInput()` is the main convenience API; the lower-level append methods are useful when a UI or session layer controls message creation.
134
+
135
+ ```ts
136
+ const first = await agent.runWithUserInput("summarize this repository");
137
+
138
+ agent.appendUserMessage("now focus on the CLI package");
139
+ const second = await agent.run();
140
+
141
+ await agent.runWithMessage(createMessage("user", "what changed since last turn?"));
142
+ ```
143
+
144
+ The agent uses `context.phases.entryPhaseId` only until the first successful run completes. Later turns start from the normalized `default` phase so long-lived agents do not repeat one-time entry work. Call `agent.resetInitialization()` when the next run should use `entryPhaseId` again.
145
+
146
+ Transcript helpers return snapshots, so callers can inspect or edit history without accidentally mutating the agent until they call a setter.
147
+
148
+ ```ts
149
+ const messages = agent.getMessages();
150
+ agent.replaceTranscript(messages.slice(-6));
151
+ agent.clearMessages();
152
+ ```
153
+
154
+ ### Updating Config
155
+
156
+ `AgentOptions` can be replaced wholesale with `setConfig()`, or updated through focused shortcuts for common orchestration flows.
157
+
158
+ ```ts
159
+ agent.setSessionId("ses_known");
160
+ agent.setModel({ provider: "openai", id: "gpt-4.1" });
161
+ agent.setTools(createCoreTools({ root: process.cwd() }));
162
+ agent.setSkills(await Agent.loadSkills("./.rowan/skills"));
163
+ agent.setPhases(await Agent.loadPhases("./.rowan/phases"));
164
+ agent.setCwd(process.cwd());
165
+ agent.setStream(createDispatchStream());
166
+
167
+ agent.updateContext((context) => ({
168
+ ...context,
169
+ systemPrompt: `${context.systemPrompt}\n\nPrefer concise answers.`,
170
+ }));
171
+ ```
172
+
93
173
  ## AgentContext
94
174
 
95
175
  The context snapshot that defines what the agent can see and do — the system prompt sets the role, messages form the conversation history, and tools/skills define the capability boundary.
@@ -264,7 +344,7 @@ Per iteration:
264
344
  5. Transition, continue, or stop
265
345
  ```
266
346
 
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.
347
+ **`entryPhaseId`** specifies which phase the loop enters for an uninitialized Agent. When phases are loaded from `.rowan/phases/`, the first discovered phase becomes the entry. When none are configured, the Agent normalises to `"default"`. After a successful run, later turns start from `"default"` until `agent.resetInitialization()` is called. 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
348
 
269
349
  ### Example Phase Flow
270
350
 
package/dist/index.d.ts CHANGED
@@ -1357,7 +1357,8 @@ type AgentStatus = {
1357
1357
  context: AgentContext;
1358
1358
  model: LlmModelRef;
1359
1359
  tools: Tool[];
1360
- isRunning: boolean;
1360
+ initialized: boolean;
1361
+ running: boolean;
1361
1362
  currentResult?: RunResult;
1362
1363
  error?: string;
1363
1364
  };
@@ -1376,6 +1377,37 @@ declare class Agent {
1376
1377
  static loadExtensions(targetPath: string): Promise<LoadExtensionsResult>;
1377
1378
  constructor(options: AgentOptions);
1378
1379
  subscribe(listener: AgentEventListener): Unsubscribe;
1380
+ appendUserMessage(input: string): void;
1381
+ appendMessage(message: AgentMessage): void;
1382
+ appendMessages(messages: AgentMessage[]): void;
1383
+ runWithUserInput(input: string, options?: RunOptions): Promise<RunResult>;
1384
+ runWithMessage(message: AgentMessage, options?: RunOptions): Promise<RunResult>;
1385
+ resetInitialization(): void;
1386
+ getContext(): AgentContext;
1387
+ setContext(context: AgentContext): void;
1388
+ updateContext(updater: (context: AgentContext) => AgentContext): void;
1389
+ forkContext(overrides?: Partial<AgentContext>): AgentContext;
1390
+ getMessages(): AgentMessage[];
1391
+ setMessages(messages: AgentMessage[]): void;
1392
+ clearMessages(): void;
1393
+ getTranscript(): AgentMessage[];
1394
+ replaceTranscript(messages: AgentMessage[]): void;
1395
+ getConfig(): AgentOptions;
1396
+ setConfig(config: AgentOptions): void;
1397
+ updateConfig(updater: (config: AgentOptions) => AgentOptions): void;
1398
+ setSessionId(sessionId: string): void;
1399
+ getSessionId(): string | undefined;
1400
+ setModel(model: LlmModelRef): void;
1401
+ setTools(tools: Tool[]): void;
1402
+ setSkills(skills: Skill$1[]): void;
1403
+ setPhases(phases: PhaseRegistry): void;
1404
+ setCwd(cwd: string): void;
1405
+ setStream(stream: StreamFn): void;
1406
+ getModel(): LlmModelRef;
1407
+ getTools(): Tool[];
1408
+ getSkills(): Skill$1[];
1409
+ getPhases(): PhaseRegistry | undefined;
1410
+ getCwd(): string | undefined;
1379
1411
  private emitToListeners;
1380
1412
  flushEvents(): Promise<void>;
1381
1413
  private processEvents;
@@ -1434,6 +1466,7 @@ declare class Agent {
1434
1466
  waitForIdle(): Promise<void>;
1435
1467
  private resolveRunConfig;
1436
1468
  private createContextSnapshot;
1469
+ private cloneOptions;
1437
1470
  }
1438
1471
 
1439
1472
  declare function createId(prefix: string): string;
@@ -1714,13 +1747,6 @@ declare class EmptyResponseError extends Error {
1714
1747
  readonly code = "empty_response";
1715
1748
  constructor(message?: string);
1716
1749
  }
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
1750
 
1725
1751
  interface SystemPromptOptions {
1726
1752
  /** Base system prompt. */
@@ -1775,4 +1801,4 @@ declare function buildModelRequest(input: ModelRequestInput, options?: {
1775
1801
  model?: LlmModelRef;
1776
1802
  }): LlmRequest;
1777
1803
 
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 };
1804
+ 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, 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,19 +21,6 @@ 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
- };
37
24
  var LoopGuard = {
38
25
  /** Returns abort result if signal is aborted */
39
26
  checkAbort(signal) {
@@ -85,7 +72,11 @@ function extractToolResults(transcript) {
85
72
  }
86
73
  var createOutcome = {
87
74
  phaseNotFound(output) {
88
- return { id: createId("out"), message: `Phase "${output.phase ?? "Unknown"}" not found.` };
75
+ return {
76
+ id: createId("out"),
77
+ message: `Phase "${output.phase ?? "Unknown"}" not found.`,
78
+ display: true
79
+ };
89
80
  },
90
81
  default(output, transcript) {
91
82
  const toolResults = transcript ? extractToolResults(transcript) : [];
@@ -102,11 +93,17 @@ var createOutcome = {
102
93
  }
103
94
  return outcome;
104
95
  },
96
+ phase(output, transcript) {
97
+ return {
98
+ ...this.default(output, transcript),
99
+ display: true
100
+ };
101
+ },
105
102
  aborted() {
106
103
  return { id: createId("out"), message: "Agent run aborted." };
107
104
  },
108
105
  error(message) {
109
- return { id: createId("out"), message };
106
+ return { id: createId("out"), message, display: true };
110
107
  }
111
108
  };
112
109
 
@@ -196,9 +193,13 @@ async function collectStreamResult(input) {
196
193
  let stopReason;
197
194
  let doneResponse;
198
195
  for await (const event of input.events) {
199
- const abortResult = LoopGuard.checkAbort(input.config.signal);
200
- if (abortResult.stopReason !== "none") {
201
- return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
196
+ const abortResult2 = LoopGuard.checkAbort(input.config.signal);
197
+ if (abortResult2.stopReason !== "none") {
198
+ if (activeMessageId) {
199
+ await input.message.end(activeMessageId);
200
+ activeMessageId = void 0;
201
+ }
202
+ return { text: abortResult2.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
202
203
  }
203
204
  if (event.type === "model_requested") {
204
205
  input.config.emit?.({
@@ -249,6 +250,14 @@ async function collectStreamResult(input) {
249
250
  }
250
251
  }
251
252
  }
253
+ const abortResult = LoopGuard.checkAbort(input.config.signal);
254
+ if (abortResult.stopReason !== "none") {
255
+ if (activeMessageId) {
256
+ await input.message.end(activeMessageId);
257
+ activeMessageId = void 0;
258
+ }
259
+ return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
260
+ }
252
261
  if (activeMessageId) {
253
262
  await input.message.end(activeMessageId);
254
263
  }
@@ -1753,11 +1762,19 @@ function createRunResult(config, state, outcome) {
1753
1762
  };
1754
1763
  }
1755
1764
  async function completeRun(config, state, outcome) {
1765
+ if (outcome.display) {
1766
+ const messages = createMessageManager(config.context, config.emit, config.onMessage);
1767
+ const messageId = messages.start("assistant", outcome.message, { outcomeId: outcome.id });
1768
+ await messages.end(messageId);
1769
+ }
1756
1770
  state.metrics.endedAt = createTimestamp();
1757
1771
  state.metrics.durationMs = Date.now() - state.metrics.startedAtMs;
1758
1772
  await config.onOutcome?.(outcome);
1759
1773
  return createRunResult(config, state, outcome);
1760
1774
  }
1775
+ function createTerminalOutcome(phase, output, transcript) {
1776
+ return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
1777
+ }
1761
1778
  function buildToolsWithRouting(config, availablePhases) {
1762
1779
  const tools = [...config.context.tools];
1763
1780
  if (availablePhases.length > 0) {
@@ -1991,10 +2008,34 @@ async function runPhaseLoop(config, state, registry) {
1991
2008
  const routeToolAvailable = phaseContext.tools.some((tool) => tool.name === PhaseRouteTool);
1992
2009
  const routeRequired = !phase.target && !phase.run && !phase.factory && routeToolAvailable && registry.phases.size > 1;
1993
2010
  if (routeRequired && !routeDecision) {
1994
- throw new MissingRouteToolCallError(
1995
- currentPhaseId,
1996
- output.toolCalls?.map((toolCall) => toolCall.name) ?? []
1997
- );
2011
+ if (config.waitForInput) {
2012
+ const preAbort = LoopGuard.checkAbort(config.signal);
2013
+ if (preAbort.stopReason !== "none") {
2014
+ config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2015
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2016
+ previousPhaseMsgId = void 0;
2017
+ return completeRun(config, state, createOutcome.aborted());
2018
+ }
2019
+ config.emit?.({ type: "user_prompt_requested", phase: currentPhaseId, ts: createTimestamp() });
2020
+ const userMessages = await config.waitForInput();
2021
+ const abortResult2 = LoopGuard.checkAbort(config.signal);
2022
+ if (abortResult2.stopReason !== "none") {
2023
+ config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2024
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2025
+ previousPhaseMsgId = void 0;
2026
+ return completeRun(config, state, createOutcome.aborted());
2027
+ }
2028
+ for (const message of userMessages) {
2029
+ config.context.messages.push(message);
2030
+ }
2031
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2032
+ previousPhaseMsgId = void 0;
2033
+ isContinuing = true;
2034
+ continue;
2035
+ }
2036
+ config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
2037
+ removePhaseMessage(config.context.messages, previousPhaseMsgId);
2038
+ return completeRun(config, state, createOutcome.default(output, config.context.messages));
1998
2039
  }
1999
2040
  if (output.route === "continue") {
2000
2041
  isContinuing = true;
@@ -2020,7 +2061,7 @@ async function runPhaseLoop(config, state, registry) {
2020
2061
  pendingInstruction = routeDecision.instruction;
2021
2062
  const entryPhaseId = phase.target ?? registry.entryPhaseId;
2022
2063
  if (entryPhaseId === "stop") {
2023
- return completeRun(config, state, createOutcome.default(output, config.context.messages));
2064
+ return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
2024
2065
  }
2025
2066
  currentPhaseId = entryPhaseId;
2026
2067
  previousPayload = void 0;
@@ -2036,7 +2077,7 @@ async function runPhaseLoop(config, state, registry) {
2036
2077
  }
2037
2078
  if (nextRoute === "stop") {
2038
2079
  if (routeToolCalled) removePhaseMessage(config.context.messages, previousPhaseMsgId);
2039
- return completeRun(config, state, createOutcome.default(output, config.context.messages));
2080
+ return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
2040
2081
  }
2041
2082
  const targetPhaseId = nextRoute;
2042
2083
  if (!registry.phases.has(targetPhaseId)) {
@@ -3439,7 +3480,8 @@ var Agent = class {
3439
3480
  context: prepareAgentContext(context),
3440
3481
  model: this.options.model,
3441
3482
  tools: this.options.context.tools ?? [],
3442
- isRunning: false
3483
+ initialized: false,
3484
+ running: false
3443
3485
  };
3444
3486
  }
3445
3487
  subscribe(listener) {
@@ -3448,6 +3490,161 @@ var Agent = class {
3448
3490
  this.listeners.delete(listener);
3449
3491
  };
3450
3492
  }
3493
+ appendUserMessage(input) {
3494
+ this.appendMessage(createMessage("user", input));
3495
+ }
3496
+ appendMessage(message) {
3497
+ this.appendMessages([message]);
3498
+ }
3499
+ appendMessages(messages) {
3500
+ this.setMessages([...this.options.context.messages, ...snapshotMessages(messages)]);
3501
+ }
3502
+ runWithUserInput(input, options) {
3503
+ const activeRun = this.activeRun;
3504
+ if (activeRun?.resume) {
3505
+ activeRun.resume([createMessage("user", input)]);
3506
+ return activeRun.promise;
3507
+ }
3508
+ if (activeRun) {
3509
+ return Promise.reject(new Error("Agent is already running."));
3510
+ }
3511
+ this.appendUserMessage(input);
3512
+ return this.run(options);
3513
+ }
3514
+ runWithMessage(message, options) {
3515
+ const activeRun = this.activeRun;
3516
+ if (activeRun?.resume) {
3517
+ activeRun.resume([message]);
3518
+ return activeRun.promise;
3519
+ }
3520
+ if (activeRun) {
3521
+ return Promise.reject(new Error("Agent is already running."));
3522
+ }
3523
+ this.appendMessage(message);
3524
+ return this.run(options);
3525
+ }
3526
+ resetInitialization() {
3527
+ this.state.initialized = false;
3528
+ }
3529
+ getContext() {
3530
+ return prepareAgentContext(this.options.context);
3531
+ }
3532
+ setContext(context) {
3533
+ const nextContext = prepareAgentContext(context);
3534
+ this.options = {
3535
+ ...this.options,
3536
+ context: nextContext
3537
+ };
3538
+ this.state.context = prepareAgentContext(nextContext, this.extensionRunner);
3539
+ this.state.tools = this.state.context.tools ?? [];
3540
+ }
3541
+ updateContext(updater) {
3542
+ this.setContext(updater(this.getContext()));
3543
+ }
3544
+ forkContext(overrides = {}) {
3545
+ return prepareAgentContext({
3546
+ ...this.getContext(),
3547
+ ...overrides
3548
+ });
3549
+ }
3550
+ getMessages() {
3551
+ return snapshotMessages(this.options.context.messages);
3552
+ }
3553
+ setMessages(messages) {
3554
+ this.setContext({
3555
+ ...this.options.context,
3556
+ messages: snapshotMessages(messages)
3557
+ });
3558
+ }
3559
+ clearMessages() {
3560
+ this.setMessages([]);
3561
+ }
3562
+ getTranscript() {
3563
+ return this.getMessages();
3564
+ }
3565
+ replaceTranscript(messages) {
3566
+ this.setMessages(messages);
3567
+ }
3568
+ getConfig() {
3569
+ return this.cloneOptions(this.options);
3570
+ }
3571
+ setConfig(config) {
3572
+ const context = prepareAgentContext(config.context);
3573
+ this.options = {
3574
+ ...config,
3575
+ context,
3576
+ ...config.extensions ? { extensions: config.extensions.slice() } : {}
3577
+ };
3578
+ this.state.sessionId = config.sessionId;
3579
+ this.state.context = prepareAgentContext(context, this.extensionRunner);
3580
+ this.state.model = config.model;
3581
+ this.state.tools = this.state.context.tools ?? [];
3582
+ }
3583
+ updateConfig(updater) {
3584
+ this.setConfig(updater(this.getConfig()));
3585
+ }
3586
+ setSessionId(sessionId) {
3587
+ this.options = {
3588
+ ...this.options,
3589
+ sessionId
3590
+ };
3591
+ this.state.sessionId = sessionId;
3592
+ }
3593
+ getSessionId() {
3594
+ return this.state.sessionId;
3595
+ }
3596
+ setModel(model) {
3597
+ this.options = {
3598
+ ...this.options,
3599
+ model
3600
+ };
3601
+ this.state.model = model;
3602
+ }
3603
+ setTools(tools) {
3604
+ this.setContext({
3605
+ ...this.options.context,
3606
+ tools: tools.slice()
3607
+ });
3608
+ }
3609
+ setSkills(skills) {
3610
+ this.setContext({
3611
+ ...this.options.context,
3612
+ skills: skills.slice()
3613
+ });
3614
+ }
3615
+ setPhases(phases) {
3616
+ this.setContext({
3617
+ ...this.options.context,
3618
+ phases: clonePhaseRegistry(phases)
3619
+ });
3620
+ }
3621
+ setCwd(cwd) {
3622
+ this.options = {
3623
+ ...this.options,
3624
+ cwd
3625
+ };
3626
+ }
3627
+ setStream(stream) {
3628
+ this.options = {
3629
+ ...this.options,
3630
+ stream
3631
+ };
3632
+ }
3633
+ getModel() {
3634
+ return this.state.model;
3635
+ }
3636
+ getTools() {
3637
+ return this.state.context.tools.slice();
3638
+ }
3639
+ getSkills() {
3640
+ return this.state.context.skills.slice();
3641
+ }
3642
+ getPhases() {
3643
+ return this.state.context.phases ? clonePhaseRegistry(this.state.context.phases) : void 0;
3644
+ }
3645
+ getCwd() {
3646
+ return this.options.cwd;
3647
+ }
3451
3648
  emitToListeners(event) {
3452
3649
  for (const listener of this.listeners) {
3453
3650
  try {
@@ -3543,31 +3740,30 @@ var Agent = class {
3543
3740
  const message = error instanceof Error ? error.message : "Agent run failed.";
3544
3741
  this.state.error = message;
3545
3742
  }
3546
- async runWithLifecycle(executor) {
3743
+ runWithLifecycle(executor) {
3547
3744
  if (this.activeRun) {
3548
- throw new Error("Agent is already running.");
3745
+ return Promise.reject(new Error("Agent is already running."));
3549
3746
  }
3550
- let resolvePromise;
3551
3747
  const abortController = new AbortController();
3552
- const promise = new Promise((resolve7) => {
3553
- resolvePromise = resolve7;
3748
+ const waitForInput = () => new Promise((resolve7) => {
3749
+ const activeRun = this.activeRun;
3750
+ activeRun.resume = (messages) => {
3751
+ activeRun.resume = void 0;
3752
+ resolve7(messages);
3753
+ };
3554
3754
  });
3555
- this.activeRun = { promise, resolve: resolvePromise, abortController };
3556
- this.state.isRunning = true;
3557
- try {
3558
- const result = await executor(abortController.signal);
3559
- resolvePromise(result);
3560
- return result;
3561
- } catch (error) {
3755
+ const promise = Promise.resolve().then(() => executor({ signal: abortController.signal, waitForInput })).catch((error) => {
3562
3756
  this.handleRunFailure(error, abortController.signal.aborted);
3563
- resolvePromise(void 0);
3564
3757
  throw error;
3565
- } finally {
3758
+ }).finally(() => {
3566
3759
  this.finishRun();
3567
- }
3760
+ });
3761
+ this.activeRun = { promise, abortController };
3762
+ this.state.running = true;
3763
+ return promise;
3568
3764
  }
3569
3765
  finishRun() {
3570
- this.state.isRunning = false;
3766
+ this.state.running = false;
3571
3767
  this.activeRun = void 0;
3572
3768
  }
3573
3769
  async loadExtensions(extensions, cwd) {
@@ -3589,22 +3785,30 @@ var Agent = class {
3589
3785
  this.loadedExtensions = extensions;
3590
3786
  this.loadedExtensionsCwd = cwd;
3591
3787
  }
3592
- async run(config) {
3593
- const resolved = this.resolveRunConfig(config);
3594
- const extensions = resolved.extensions ?? [];
3595
- await this.loadExtensions(extensions, resolved.cwd);
3596
- const runContext = prepareAgentContext(resolved.context, this.extensionRunner);
3597
- const sessionId = resolved.sessionId ?? this.state.sessionId;
3598
- this.options = resolved;
3599
- if (sessionId) {
3600
- this.state.sessionId = sessionId;
3601
- }
3602
- this.state.context = runContext;
3603
- this.state.model = resolved.model;
3604
- this.state.tools = runContext.tools ?? [];
3605
- this.state.currentResult = void 0;
3606
- this.state.error = void 0;
3607
- return this.runWithLifecycle(async (signal) => {
3788
+ run(config) {
3789
+ return this.runWithLifecycle(async ({ signal, waitForInput }) => {
3790
+ const resolved = this.resolveRunConfig(config);
3791
+ const extensions = resolved.extensions ?? [];
3792
+ await this.loadExtensions(extensions, resolved.cwd);
3793
+ const runContext = prepareAgentContext(resolved.context, this.extensionRunner);
3794
+ const entryPhaseId = this.state.initialized ? DEFAULT_PHASE_ID : runContext.phases?.entryPhaseId ?? DEFAULT_PHASE_ID;
3795
+ const loopContext = {
3796
+ ...runContext,
3797
+ phases: {
3798
+ ...runContext.phases,
3799
+ entryPhaseId
3800
+ }
3801
+ };
3802
+ const sessionId = resolved.sessionId ?? this.state.sessionId;
3803
+ this.options = resolved;
3804
+ if (sessionId) {
3805
+ this.state.sessionId = sessionId;
3806
+ }
3807
+ this.state.context = runContext;
3808
+ this.state.model = resolved.model;
3809
+ this.state.tools = runContext.tools ?? [];
3810
+ this.state.currentResult = void 0;
3811
+ this.state.error = void 0;
3608
3812
  const emit = (event) => {
3609
3813
  this.processEvents(event);
3610
3814
  };
@@ -3627,7 +3831,7 @@ var Agent = class {
3627
3831
  return this.handleAfterToolCall(input.tool, result2);
3628
3832
  };
3629
3833
  const result = await runAgentLoop({
3630
- context: runContext,
3834
+ context: loopContext,
3631
3835
  sessionId: sessionId ?? createId("ses"),
3632
3836
  model: resolved.model,
3633
3837
  stream: resolved.stream,
@@ -3641,7 +3845,8 @@ var Agent = class {
3641
3845
  emit,
3642
3846
  onMessage: resolved.onMessage,
3643
3847
  onOutcome: resolved.onOutcome,
3644
- onModelTranscript: resolved.onModelTranscript
3848
+ onModelTranscript: resolved.onModelTranscript,
3849
+ waitForInput
3645
3850
  });
3646
3851
  const nextContext = {
3647
3852
  ...prepareAgentContext(resolved.context),
@@ -3650,6 +3855,7 @@ var Agent = class {
3650
3855
  this.state.sessionId = result.sessionId;
3651
3856
  this.state.context = prepareAgentContext(nextContext, this.extensionRunner);
3652
3857
  this.state.currentResult = result;
3858
+ this.state.initialized = true;
3653
3859
  this.options = {
3654
3860
  ...resolved,
3655
3861
  sessionId: result.sessionId,
@@ -3659,7 +3865,9 @@ var Agent = class {
3659
3865
  });
3660
3866
  }
3661
3867
  abort(reason = "Aborted by caller.") {
3662
- this.activeRun?.abortController.abort(reason);
3868
+ const activeRun = this.activeRun;
3869
+ activeRun?.resume?.([]);
3870
+ activeRun?.abortController.abort(reason);
3663
3871
  }
3664
3872
  /**
3665
3873
  * Get formatted skill content for LLM consumption.
@@ -3718,7 +3926,20 @@ ${additionalInstructions}` : content;
3718
3926
  createContextSnapshot() {
3719
3927
  return prepareAgentContext(this.options.context);
3720
3928
  }
3929
+ cloneOptions(options) {
3930
+ return {
3931
+ ...options,
3932
+ context: prepareAgentContext(options.context),
3933
+ ...options.extensions ? { extensions: options.extensions.slice() } : {}
3934
+ };
3935
+ }
3721
3936
  };
3937
+ function clonePhaseRegistry(registry) {
3938
+ return {
3939
+ phases: new Map(registry.phases),
3940
+ entryPhaseId: registry.entryPhaseId
3941
+ };
3942
+ }
3722
3943
  function prepareAgentContext(context, extensionRunner) {
3723
3944
  const phases = /* @__PURE__ */ new Map();
3724
3945
  phases.set(DEFAULT_PHASE_ID, createDefaultPhase());
@@ -4273,7 +4494,6 @@ export {
4273
4494
  ExtensionRunner,
4274
4495
  HooksManager,
4275
4496
  LocalJsonlSessionManager,
4276
- MissingRouteToolCallError,
4277
4497
  appendUserTurn,
4278
4498
  buildModelRequest,
4279
4499
  buildSystemPrompt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",