@rowan-agent/agent 0.7.5 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @rowan-agent/agent
2
2
 
3
- Durable Agent Runtime. The public API consists of `AgentRuntime`, Durable
4
- Stores, Config Providers, Run handles, and Durable Run Events. An Agent is a
5
- persistent identity, not a process-local Session object.
3
+ Durable Agent Runtime. The public interface consists of `AgentRuntime`, Durable
4
+ Stores, Config Providers, Run handles, and Run Events. An Agent is a persistent
5
+ identity, not a process-local Session object.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -31,7 +31,13 @@ const agentId = await runtime.createAgent({
31
31
  const run = await runtime.start(agentId, "Summarize the workspace.", {
32
32
  idempotencyKey: "run-example", // One Agent can have multiple independent Runs
33
33
  });
34
+ const observing = (async () => {
35
+ for await (const event of run.observe()) {
36
+ if (event.kind === "message_delta") process.stdout.write(event.text);
37
+ }
38
+ })();
34
39
  const boundary = await run.wait();
40
+ await observing;
35
41
  await runtime.close();
36
42
  ```
37
43
 
@@ -40,7 +46,7 @@ await runtime.close();
40
46
  1. `AgentRuntime.init({ store })` opens a Runtime Owner with an in-memory Config Provider by default. Pass `configs` when configuration must survive process boundaries.
41
47
  2. `createAgent()` creates a persistent Agent identity and binds a configuration snapshot.
42
48
  3. `start()` creates a queued Run; `run(runId)` returns a stateless Run handle.
43
- 4. `observe()` reads replayable `DurableRunEvent` values; `wait()` waits for a boundary.
49
+ 4. `observe()` follows display-oriented `RunEvent` values; `wait()` waits for an authoritative boundary.
44
50
  5. `respond()` continues an `input_required` Run; `cancel()` terminates an unfinished Run.
45
51
  6. `close()` seals the Owner and releases the Store.
46
52
 
@@ -69,16 +75,29 @@ Tools are supplied through `AgentConfig.context.tools` and persist through:
69
75
  When an external side effect cannot be confirmed, the Tool must become
70
76
  `indeterminate`; the Run then fails and is never automatically retried.
71
77
 
78
+ While running, a Tool may call `context.reportProgress(progress)` with a
79
+ JSON-safe value. Progress is live-only and may be dropped.
80
+
72
81
  ## Events
73
82
 
74
- `run.observe()` and `runtime.consume()` deliver only Durable Run Events:
83
+ `run.observe()` delivers `RunEvent` values for live presentation:
84
+
85
+ - transient `message_delta` and `tool_progress` events are live-only and
86
+ best-effort;
87
+ - durable `message_committed`, `run_state_changed`, and `tool_state_changed`
88
+ events are replayable;
89
+ - a durable `message_committed` event is the authoritative full content if a
90
+ transient delta was coalesced or dropped.
91
+
92
+ `runtime.consume()` delivers only `DurableRunEvent` values:
75
93
 
76
94
  - `message_committed`
77
- - `run_transitioned`
95
+ - `run_state_changed`
78
96
  - `tool_state_changed`
79
97
 
80
- Events and their corresponding Run aggregate changes commit in one Store
81
- transaction. Consumers persist their progress through cursors and checkpoints.
98
+ Durable events and their corresponding Run aggregate changes commit in one
99
+ Store transaction. Reliable consumers persist progress through cursors and
100
+ checkpoints; transient events never enter the Durable Store.
82
101
 
83
102
  ## Resources
84
103
 
package/dist/index.d.ts CHANGED
@@ -71,6 +71,7 @@ type PhaseExecution = {
71
71
  restore(snapshot: PhaseContextSnapshot): void;
72
72
  invokeModel(context: PhaseContext): Promise<ModelInvokeOutput>;
73
73
  executeTool(context: AgentContext, toolCall: ToolCall): Promise<ToolResult>;
74
+ executeTools(context: AgentContext, toolCalls: readonly ToolCall[]): Promise<readonly ToolResult[]>;
74
75
  };
75
76
 
76
77
  /**
@@ -461,6 +462,12 @@ type PhaseInvocation = {
461
462
  count: number;
462
463
  sourcePhaseId: string;
463
464
  };
465
+ /** Durable identity of the Run execution currently invoking a Phase. */
466
+ type PhaseExecutionIdentity = Readonly<{
467
+ agentId: string;
468
+ runId: string;
469
+ executionId: string;
470
+ }>;
464
471
  /** Everything a phase needs to execute */
465
472
  interface PhaseContext {
466
473
  systemPrompt: string;
@@ -473,6 +480,8 @@ interface PhaseContext {
473
480
  state: PhaseState;
474
481
  /** Identity and dispatch metadata for this phase execution */
475
482
  readonly invocation: PhaseInvocation;
483
+ /** Durable Run identity for cross-store integrations and idempotent commands. */
484
+ readonly execution: PhaseExecutionIdentity;
476
485
  /** Additional guideline bullets appended to the system prompt */
477
486
  promptGuidelines?: string[];
478
487
  /** Text to append after the system prompt */
@@ -597,6 +606,8 @@ type ThinkingContent = Readonly<{
597
606
  type ToolUseContent = Readonly<{
598
607
  type: "tool_use";
599
608
  toolCallId: ToolCallId;
609
+ /** Provider/model correlation, retained only to reconstruct the next request. */
610
+ providerToolCallId?: string;
600
611
  name: string;
601
612
  input: JsonValue;
602
613
  }>;
@@ -615,6 +626,8 @@ type DurableToolResult = Readonly<{
615
626
  type ToolResultContent = Readonly<{
616
627
  type: "tool_result";
617
628
  toolCallId: ToolCallId;
629
+ /** Provider/model correlation, retained only to reconstruct the next request. */
630
+ providerToolCallId?: string;
618
631
  result: ToolExecutionResult;
619
632
  }>;
620
633
  type UserContent = string | readonly (TextContent | ImageContent)[];
@@ -685,6 +698,7 @@ type RunningRunFailure = Extract<RunFailure, {
685
698
  type ToolCallState = "pending" | "running" | "completed" | "failed" | "indeterminate";
686
699
  type ToolCallSnapshotBase = Readonly<{
687
700
  id: ToolCallId;
701
+ providerToolCallId: string;
688
702
  agentId: AgentId;
689
703
  runId: RunId;
690
704
  executionId: ExecutionId;
@@ -733,44 +747,45 @@ type MessageCommitted = DurableEventBase & Readonly<{
733
747
  kind: "message_committed";
734
748
  message: Message;
735
749
  }>;
736
- type RunTransitioned = DurableEventBase & (Readonly<{
737
- kind: "run_transitioned";
750
+ type RunStateChanged = DurableEventBase & (Readonly<{
751
+ kind: "run_state_changed";
738
752
  from: null;
739
753
  to: "queued";
740
754
  }> | Readonly<{
741
- kind: "run_transitioned";
755
+ kind: "run_state_changed";
742
756
  from: "input_required";
743
757
  to: "queued";
744
758
  }> | Readonly<{
745
- kind: "run_transitioned";
759
+ kind: "run_state_changed";
746
760
  from: "queued";
747
761
  to: "running";
748
762
  }> | Readonly<{
749
- kind: "run_transitioned";
763
+ kind: "run_state_changed";
750
764
  from: "running";
751
765
  to: "input_required";
752
766
  request: Readonly<{
753
767
  id: InputRequestId;
768
+ phase: string;
754
769
  prompt: AssistantMessage;
755
770
  }>;
756
771
  }> | Readonly<{
757
- kind: "run_transitioned";
772
+ kind: "run_state_changed";
758
773
  from: "running";
759
774
  to: "completed";
760
775
  outcome: Outcome;
761
776
  output?: AssistantMessage;
762
777
  }> | Readonly<{
763
- kind: "run_transitioned";
778
+ kind: "run_state_changed";
764
779
  from: "queued";
765
780
  to: "failed";
766
781
  failure: QueuedRunFailure;
767
782
  }> | Readonly<{
768
- kind: "run_transitioned";
783
+ kind: "run_state_changed";
769
784
  from: "running";
770
785
  to: "failed";
771
786
  failure: RunningRunFailure;
772
787
  }> | Readonly<{
773
- kind: "run_transitioned";
788
+ kind: "run_state_changed";
774
789
  from: "queued" | "running" | "input_required";
775
790
  to: "cancelled";
776
791
  reason?: string;
@@ -830,7 +845,25 @@ type ToolStateChanged = DurableEventBase & (Readonly<{
830
845
  state: "indeterminate";
831
846
  }>;
832
847
  }>);
833
- type DurableRunEvent = MessageCommitted | RunTransitioned | ToolStateChanged;
848
+ type DurableRunEvent = MessageCommitted | RunStateChanged | ToolStateChanged;
849
+ type MessageDelta = Readonly<{
850
+ kind: "message_delta";
851
+ durability: "transient";
852
+ runId: RunId;
853
+ executionId: ExecutionId;
854
+ messageId: MessageId;
855
+ offset: number;
856
+ text: string;
857
+ }>;
858
+ type ToolProgress = Readonly<{
859
+ kind: "tool_progress";
860
+ durability: "transient";
861
+ runId: RunId;
862
+ executionId: ExecutionId;
863
+ toolCallId: ToolCallId;
864
+ progress: JsonValue;
865
+ }>;
866
+ type RunEvent = DurableRunEvent | MessageDelta | ToolProgress;
834
867
 
835
868
  type UserInput = string | Readonly<{
836
869
  content: UserContent;
@@ -840,6 +873,7 @@ type ToolInvocationContext = Readonly<{
840
873
  agentId: AgentId;
841
874
  runId: RunId;
842
875
  toolCallId: ToolCallId;
876
+ reportProgress(progress: JsonValue): void;
843
877
  }>;
844
878
  type Tool = Readonly<{
845
879
  name: string;
@@ -911,6 +945,7 @@ type ExecutionCheckpoint = Readonly<{
911
945
  }>;
912
946
  type InputRequest = Readonly<{
913
947
  id: InputRequestId;
948
+ phase: string;
914
949
  messageId: MessageId;
915
950
  createdAt: string;
916
951
  }>;
@@ -930,10 +965,20 @@ type InputRequiredCommit = Readonly<{
930
965
  prompt: AssistantMessage;
931
966
  request: InputRequest;
932
967
  }>;
968
+ type ToolCallReservation = Readonly<{
969
+ providerToolCallId: string;
970
+ name: string;
971
+ args: JsonValue;
972
+ toolCallId?: ToolCallId;
973
+ }>;
933
974
  type ToolCommit = Readonly<{
934
975
  run: RunRecord;
935
976
  toolCall: ToolCallSnapshot;
936
977
  }>;
978
+ type ToolBatchCommit = Readonly<{
979
+ run: RunRecord;
980
+ toolCalls: readonly ToolCallSnapshot[];
981
+ }>;
937
982
  type RunRecord = Readonly<{
938
983
  id: RunId;
939
984
  agentId: AgentId;
@@ -993,6 +1038,7 @@ type RunSnapshot = RunSnapshotBase & (Readonly<{
993
1038
  state: "input_required";
994
1039
  request: Readonly<{
995
1040
  id: InputRequestId;
1041
+ phase: string;
996
1042
  prompt: AssistantMessage;
997
1043
  }>;
998
1044
  }> | Readonly<{
@@ -1009,6 +1055,7 @@ type RunSnapshot = RunSnapshotBase & (Readonly<{
1009
1055
  type RunBoundary = Readonly<{
1010
1056
  type: "input_required";
1011
1057
  requestId: InputRequestId;
1058
+ phase: string;
1012
1059
  prompt: AssistantMessage;
1013
1060
  }> | Readonly<{
1014
1061
  type: "completed";
@@ -1089,6 +1136,7 @@ interface OwnedStore {
1089
1136
  execution: ExecutionToken;
1090
1137
  expectedRevision: number;
1091
1138
  requestId?: InputRequestId;
1139
+ phase: string;
1092
1140
  prompt: AssistantMessage;
1093
1141
  checkpoint: ExecutionCheckpoint;
1094
1142
  }): Promise<InputRequiredCommit>;
@@ -1115,7 +1163,15 @@ interface OwnedStore {
1115
1163
  name: string;
1116
1164
  args: JsonValue;
1117
1165
  toolCallId?: ToolCallId;
1166
+ providerToolCallId?: string;
1118
1167
  }): Promise<ToolCommit>;
1168
+ reserveToolCalls(input: {
1169
+ runId: RunId;
1170
+ execution: ExecutionToken;
1171
+ expectedRevision: number;
1172
+ requestMessageId: MessageId;
1173
+ calls: readonly ToolCallReservation[];
1174
+ }): Promise<ToolBatchCommit>;
1119
1175
  startToolCall(input: {
1120
1176
  runId: RunId;
1121
1177
  execution: ExecutionToken;
@@ -1175,7 +1231,7 @@ interface AgentRun {
1175
1231
  observe(options?: {
1176
1232
  after?: EventCursor;
1177
1233
  signal?: AbortSignal;
1178
- }): AsyncIterable<DurableRunEvent>;
1234
+ }): AsyncIterable<RunEvent>;
1179
1235
  wait(options?: {
1180
1236
  signal?: AbortSignal;
1181
1237
  }): Promise<RunBoundary>;
@@ -1230,6 +1286,7 @@ declare class AgentRuntime implements AgentRuntime$1 {
1230
1286
  private readonly activeAgents;
1231
1287
  private readonly executions;
1232
1288
  private readonly consumers;
1289
+ private readonly transientEvents;
1233
1290
  private heartbeat?;
1234
1291
  private pumping;
1235
1292
  private closed;
@@ -1275,12 +1332,14 @@ declare class AgentRuntime implements AgentRuntime$1 {
1275
1332
  private execute;
1276
1333
  private failQueued;
1277
1334
  private executeTool;
1335
+ private executeToolBatch;
1336
+ private executeReservedTool;
1278
1337
  private requireAgent;
1279
1338
  private runConsumer;
1280
1339
  observe(runId: RunId, options?: {
1281
1340
  after?: EventCursor;
1282
1341
  signal?: AbortSignal;
1283
- }): AsyncIterable<DurableRunEvent>;
1342
+ }): AsyncIterable<RunEvent>;
1284
1343
  wait(runId: RunId, options?: {
1285
1344
  signal?: AbortSignal;
1286
1345
  }): Promise<RunBoundary>;
@@ -1372,6 +1431,7 @@ declare class InMemoryStore implements DurableStore {
1372
1431
  execution: ExecutionToken;
1373
1432
  expectedRevision: number;
1374
1433
  requestId?: InputRequestId;
1434
+ phase: string;
1375
1435
  prompt: AssistantMessage;
1376
1436
  checkpoint: ExecutionCheckpoint;
1377
1437
  }): InputRequiredCommit;
@@ -1390,7 +1450,20 @@ declare class InMemoryStore implements DurableStore {
1390
1450
  name: string;
1391
1451
  args: JsonValue;
1392
1452
  toolCallId?: ToolCallId;
1453
+ providerToolCallId?: string;
1393
1454
  }): ToolCommit;
1455
+ reserveToolCalls(lease: OwnerLease, input: {
1456
+ runId: RunId;
1457
+ execution: ExecutionToken;
1458
+ expectedRevision: number;
1459
+ requestMessageId: MessageId;
1460
+ calls: readonly Readonly<{
1461
+ providerToolCallId: string;
1462
+ name: string;
1463
+ args: JsonValue;
1464
+ toolCallId?: ToolCallId;
1465
+ }>[];
1466
+ }): ToolBatchCommit;
1394
1467
  startToolCall(lease: OwnerLease, input: {
1395
1468
  runId: RunId;
1396
1469
  execution: ExecutionToken;
@@ -1505,6 +1578,7 @@ declare class SqliteStore implements DurableStore {
1505
1578
  execution: ExecutionToken;
1506
1579
  expectedRevision: number;
1507
1580
  requestId?: InputRequestId;
1581
+ phase: string;
1508
1582
  prompt: AssistantMessage;
1509
1583
  checkpoint: ExecutionCheckpoint;
1510
1584
  }): Promise<InputRequiredCommit>;
@@ -1531,7 +1605,20 @@ declare class SqliteStore implements DurableStore {
1531
1605
  name: string;
1532
1606
  args: JsonValue;
1533
1607
  toolCallId?: ToolCallId;
1608
+ providerToolCallId?: string;
1534
1609
  }): Promise<ToolCommit>;
1610
+ reserveToolCalls(lease: OwnerLease, input: {
1611
+ runId: RunId;
1612
+ execution: ExecutionToken;
1613
+ expectedRevision: number;
1614
+ requestMessageId: MessageId;
1615
+ calls: readonly Readonly<{
1616
+ providerToolCallId: string;
1617
+ name: string;
1618
+ args: JsonValue;
1619
+ toolCallId?: ToolCallId;
1620
+ }>[];
1621
+ }): Promise<ToolBatchCommit>;
1535
1622
  startToolCall(lease: OwnerLease, input: {
1536
1623
  runId: RunId;
1537
1624
  execution: ExecutionToken;
@@ -1720,4 +1807,4 @@ declare function loadExtensionsFromPath(path: string): Promise<{
1720
1807
 
1721
1808
  declare function createCoreTools(input?: CoreToolContext): Tool[];
1722
1809
 
1723
- 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 MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunSummary, type RunTransitioned, 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 ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhases, loadSkills };
1810
+ 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 };
package/dist/index.js CHANGED
@@ -832,12 +832,11 @@ async function collectStreamResult(input) {
832
832
  if (event.type === "text_delta") {
833
833
  lastPartial = event.partial;
834
834
  if (!activeMessageId) {
835
- activeMessageId = input.message.start("assistant", event.text, {
835
+ activeMessageId = input.message.reserve("assistant", {
836
836
  phase: input.metadataPhase
837
837
  });
838
- } else {
839
- await input.message.update(activeMessageId, event.text);
840
838
  }
839
+ await input.message.update(activeMessageId, event.text);
841
840
  }
842
841
  if (event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
843
842
  activeMessageId ??= input.message.reserve("assistant", {
@@ -1541,7 +1540,7 @@ function createRunResult(config, state, outcome) {
1541
1540
  }
1542
1541
  async function completeRun(config, state, outcome) {
1543
1542
  if (outcome.display) {
1544
- const messages = createMessageManager(config.context, config.onMessage);
1543
+ const messages = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1545
1544
  const messageId = messages.start("assistant", outcome.message, { outcomeId: outcome.id });
1546
1545
  await messages.end(messageId);
1547
1546
  }
@@ -1560,7 +1559,7 @@ function buildToolsWithRouting(config, availablePhases) {
1560
1559
  }
1561
1560
  return tools;
1562
1561
  }
1563
- function createMessageManager(context, onMessage) {
1562
+ function createMessageManager(context, onMessage, onMessageDelta) {
1564
1563
  const activeMessages = /* @__PURE__ */ new Map();
1565
1564
  const emitStart = (active) => {
1566
1565
  if (active.emitted) return;
@@ -1584,7 +1583,12 @@ function createMessageManager(context, onMessage) {
1584
1583
  const active = activeMessages.get(messageId);
1585
1584
  if (!active) return;
1586
1585
  emitStart(active);
1586
+ const offset = typeof active.message.content === "string" ? active.message.content.length : active.message.content.reduce(
1587
+ (length, part) => length + (part.type === "text" ? part.text.length : 0),
1588
+ 0
1589
+ );
1587
1590
  active.message.content = typeof active.message.content === "string" ? active.message.content + delta : [...active.message.content, { type: "text", text: delta }];
1591
+ onMessageDelta?.({ messageId, offset, text: delta });
1588
1592
  },
1589
1593
  replaceContent(messageId, content) {
1590
1594
  const active = activeMessages.get(messageId);
@@ -1670,8 +1674,8 @@ async function executePhaseWithModel(ctx) {
1670
1674
  if (executableToolCalls.length === 0) {
1671
1675
  return output;
1672
1676
  }
1673
- for (const toolCall of executableToolCalls) {
1674
- const result = await ctx.execution.executeTool(roundContext, toolCall);
1677
+ const results = await ctx.execution.executeTools(roundContext, executableToolCalls);
1678
+ for (const result of results) {
1675
1679
  const messageId = ctx.messageManager.start("tool", createToolResultContent(result), {
1676
1680
  phase: ctx.phase.name
1677
1681
  });
@@ -1731,7 +1735,7 @@ async function runPhaseLoop(config, state, registry) {
1731
1735
  }
1732
1736
  state.currentPhase = currentPhaseId;
1733
1737
  const allTools = buildToolsWithRouting(config, availablePhases);
1734
- const messageManager = createMessageManager(config.context, config.onMessage);
1738
+ const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
1735
1739
  const toolExecutionManager = createToolExecutionManager();
1736
1740
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
1737
1741
  const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
@@ -1745,6 +1749,7 @@ async function runPhaseLoop(config, state, registry) {
1745
1749
  mode: "serial",
1746
1750
  instanceId: currentPhaseId
1747
1751
  },
1752
+ execution: config.execution,
1748
1753
  state: {
1749
1754
  current: currentPhaseId,
1750
1755
  available: Array.from(registry.phases.keys()),
@@ -2098,6 +2103,25 @@ async function executeToolCall(input) {
2098
2103
  toolName: input.toolCall.name
2099
2104
  };
2100
2105
  }
2106
+ async function executeToolCalls(input) {
2107
+ if (input.config.runtime?.toolsBatch) {
2108
+ const results2 = await input.config.runtime.toolsBatch({
2109
+ config: input.config,
2110
+ toolCalls: input.toolCalls
2111
+ });
2112
+ if (results2.length !== input.toolCalls.length) throw new Error("Runtime returned an invalid Tool batch result");
2113
+ return results2.map((result, index) => ({
2114
+ ...result,
2115
+ toolCallId: input.toolCalls[index].id,
2116
+ toolName: input.toolCalls[index].name
2117
+ }));
2118
+ }
2119
+ const results = [];
2120
+ for (const toolCall of input.toolCalls) {
2121
+ results.push(await executeToolCall({ ...input, toolCall }));
2122
+ }
2123
+ return results;
2124
+ }
2101
2125
  function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
2102
2126
  return {
2103
2127
  snapshot() {
@@ -2175,6 +2199,30 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2175
2199
  await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
2176
2200
  return result;
2177
2201
  });
2202
+ },
2203
+ async executeTools(phaseContext, toolCalls) {
2204
+ return runTurn(async () => {
2205
+ const tools = phaseContext.tools.filter((tool) => tool.name !== PhaseRouteTool);
2206
+ for (const toolCall of toolCalls) {
2207
+ await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
2208
+ }
2209
+ const results = await executeToolCalls({
2210
+ config: {
2211
+ ...config,
2212
+ context: {
2213
+ ...config.context,
2214
+ tools,
2215
+ skills: phaseContext.skills
2216
+ }
2217
+ },
2218
+ tools,
2219
+ toolCalls
2220
+ });
2221
+ for (const result of results) {
2222
+ await toolExecutionManager.end(result.toolCallId, result.toolName, result, !result.ok);
2223
+ }
2224
+ return results;
2225
+ });
2178
2226
  }
2179
2227
  };
2180
2228
  }
@@ -2197,6 +2245,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2197
2245
  count,
2198
2246
  sourcePhaseId
2199
2247
  },
2248
+ execution: config.execution,
2200
2249
  state: {
2201
2250
  current: phase.name,
2202
2251
  available: Array.from(registry.phases.keys()),
@@ -2204,7 +2253,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
2204
2253
  payload
2205
2254
  }
2206
2255
  };
2207
- const messageManager = createMessageManager({ messages }, config.onMessage);
2256
+ const messageManager = createMessageManager({ messages }, config.onMessage, config.onMessageDelta);
2208
2257
  const phaseMsgId = injectPhaseContent(
2209
2258
  phase,
2210
2259
  { results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : [] },
@@ -2395,6 +2444,7 @@ async function executeOnce(input) {
2395
2444
  };
2396
2445
  const config = {
2397
2446
  context,
2447
+ execution: input.execution,
2398
2448
  model: input.model,
2399
2449
  stream: input.stream,
2400
2450
  maxAttempts: input.maxAttempts,
@@ -2405,6 +2455,7 @@ async function executeOnce(input) {
2405
2455
  afterPhase: input.afterPhase,
2406
2456
  beforePrompt: input.beforePrompt,
2407
2457
  onMessage: input.onMessage,
2458
+ onMessageDelta: input.onMessageDelta,
2408
2459
  onOutcome: input.onOutcome,
2409
2460
  onModelTranscript: input.onModelTranscript,
2410
2461
  runtime: input.runtime,
@@ -2911,13 +2962,13 @@ function projectAssistantContent(content) {
2911
2962
  return content.map((part) => {
2912
2963
  if (part.type === "text") return { ...part };
2913
2964
  if (part.type === "thinking") return { ...part };
2914
- return { type: "tool_use", id: part.toolCallId, name: part.name, input: part.input };
2965
+ return { type: "tool_use", id: part.providerToolCallId ?? part.toolCallId, name: part.name, input: part.input };
2915
2966
  });
2916
2967
  }
2917
2968
  function projectToolContent(content) {
2918
2969
  return content.map((part) => ({
2919
2970
  type: "tool_result",
2920
- toolUseId: part.toolCallId,
2971
+ toolUseId: part.providerToolCallId ?? part.toolCallId,
2921
2972
  content: jsonText(part.result.content),
2922
2973
  ...part.result.ok ? {} : { isError: true }
2923
2974
  }));
@@ -2931,7 +2982,8 @@ function projectTool(tool, agentId, runId) {
2931
2982
  const result = await tool.execute(args, {
2932
2983
  agentId,
2933
2984
  runId,
2934
- toolCallId: context.toolCallId
2985
+ toolCallId: context.toolCallId,
2986
+ reportProgress: () => void 0
2935
2987
  }, signal ?? new AbortController().signal);
2936
2988
  return { toolCallId: context.toolCallId, toolName: tool.name, ...result };
2937
2989
  }
@@ -3623,6 +3675,144 @@ function fromLoopResult(result) {
3623
3675
  return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
3624
3676
  }
3625
3677
 
3678
+ // src/runtime/transient-run-events.ts
3679
+ var MAX_BUFFERED_EVENTS = 128;
3680
+ var MAX_BUFFERED_TEXT = 64 * 1024;
3681
+ function deferred() {
3682
+ let resolve6;
3683
+ const promise = new Promise((done) => {
3684
+ resolve6 = done;
3685
+ });
3686
+ return { promise, resolve: resolve6 };
3687
+ }
3688
+ var TransientRunEventSubscription = class {
3689
+ queue = [];
3690
+ wake = deferred();
3691
+ version = 0;
3692
+ stopped = false;
3693
+ push(event) {
3694
+ if (this.stopped) return;
3695
+ const previous = this.queue.at(-1);
3696
+ if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
3697
+ this.queue[this.queue.length - 1] = {
3698
+ ...previous,
3699
+ text: previous.text + event.text
3700
+ };
3701
+ } else {
3702
+ if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
3703
+ this.queue.push(event);
3704
+ }
3705
+ this.trimText();
3706
+ this.notify();
3707
+ }
3708
+ shift() {
3709
+ return this.queue.shift();
3710
+ }
3711
+ clear(executionId) {
3712
+ if (executionId === void 0) this.queue.length = 0;
3713
+ else {
3714
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3715
+ if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
3716
+ }
3717
+ }
3718
+ this.notify();
3719
+ }
3720
+ clearMessage(messageId) {
3721
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3722
+ const event = this.queue[index];
3723
+ if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
3724
+ }
3725
+ this.notify();
3726
+ }
3727
+ clearTool(toolCallId) {
3728
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
3729
+ const event = this.queue[index];
3730
+ if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
3731
+ }
3732
+ this.notify();
3733
+ }
3734
+ checkpoint() {
3735
+ return this.version;
3736
+ }
3737
+ changed(since) {
3738
+ if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
3739
+ return this.wake.promise;
3740
+ }
3741
+ stop() {
3742
+ if (this.stopped) return;
3743
+ this.stopped = true;
3744
+ this.queue.length = 0;
3745
+ this.notify();
3746
+ }
3747
+ notify() {
3748
+ const wake = this.wake;
3749
+ this.wake = deferred();
3750
+ this.version += 1;
3751
+ wake.resolve();
3752
+ }
3753
+ trimText() {
3754
+ let buffered = this.queue.reduce(
3755
+ (length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
3756
+ 0
3757
+ );
3758
+ while (buffered > MAX_BUFFERED_TEXT) {
3759
+ const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
3760
+ if (index < 0) return;
3761
+ const event = this.queue[index];
3762
+ const overflow = buffered - MAX_BUFFERED_TEXT;
3763
+ if (event.text.length <= overflow) {
3764
+ this.queue.splice(index, 1);
3765
+ buffered -= event.text.length;
3766
+ } else {
3767
+ this.queue[index] = {
3768
+ ...event,
3769
+ offset: event.offset + overflow,
3770
+ text: event.text.slice(overflow)
3771
+ };
3772
+ buffered -= overflow;
3773
+ }
3774
+ }
3775
+ }
3776
+ };
3777
+ var TransientRunEventHub = class {
3778
+ subscriptions = /* @__PURE__ */ new Map();
3779
+ subscribe(runId) {
3780
+ const subscription = new TransientRunEventSubscription();
3781
+ const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
3782
+ current.add(subscription);
3783
+ this.subscriptions.set(runId, current);
3784
+ return subscription;
3785
+ }
3786
+ unsubscribe(runId, subscription) {
3787
+ subscription.stop();
3788
+ const current = this.subscriptions.get(runId);
3789
+ current?.delete(subscription);
3790
+ if (current?.size === 0) this.subscriptions.delete(runId);
3791
+ }
3792
+ publish(event) {
3793
+ for (const subscription of this.subscriptions.get(event.runId) ?? []) {
3794
+ subscription.push(event);
3795
+ }
3796
+ }
3797
+ clear(runId, executionId) {
3798
+ for (const subscription of this.subscriptions.get(runId) ?? []) {
3799
+ subscription.clear(executionId);
3800
+ }
3801
+ }
3802
+ clearMessage(runId, messageId) {
3803
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
3804
+ }
3805
+ clearTool(runId, toolCallId) {
3806
+ for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
3807
+ }
3808
+ close() {
3809
+ for (const subscriptions of this.subscriptions.values()) {
3810
+ for (const subscription of subscriptions) subscription.stop();
3811
+ }
3812
+ this.subscriptions.clear();
3813
+ }
3814
+ };
3815
+
3626
3816
  // src/runtime/durable-runtime.ts
3627
3817
  var DEFAULT_CONCURRENCY = 10;
3628
3818
  var DEFAULT_POLL_MS = 25;
@@ -3636,6 +3826,7 @@ var AgentRuntime = class _AgentRuntime {
3636
3826
  activeAgents = /* @__PURE__ */ new Set();
3637
3827
  executions = /* @__PURE__ */ new Map();
3638
3828
  consumers = /* @__PURE__ */ new Map();
3829
+ transientEvents = new TransientRunEventHub();
3639
3830
  heartbeat;
3640
3831
  pumping = false;
3641
3832
  closed = false;
@@ -3693,7 +3884,7 @@ var AgentRuntime = class _AgentRuntime {
3693
3884
  if (input.signal.aborted) throw abortError();
3694
3885
  if (this.consumers.has(input.consumerId)) throw new RuntimeError("consumer_already_active", { consumerId: input.consumerId });
3695
3886
  const controller = new AbortController();
3696
- const subscription = { consumerId: input.consumerId, controller, input, caughtUp: deferred(), done: deferred() };
3887
+ const subscription = { consumerId: input.consumerId, controller, input, caughtUp: deferred2(), done: deferred2() };
3697
3888
  this.consumers.set(input.consumerId, subscription);
3698
3889
  const onAbort = () => controller.abort();
3699
3890
  input.signal.addEventListener("abort", onAbort, { once: true });
@@ -3710,8 +3901,9 @@ var AgentRuntime = class _AgentRuntime {
3710
3901
  if (this.closed) return;
3711
3902
  this.closed = true;
3712
3903
  if (this.heartbeat) clearInterval(this.heartbeat);
3713
- for (const controller of this.executions.values()) controller.abort();
3904
+ for (const execution of this.executions.values()) execution.controller.abort();
3714
3905
  this.executions.clear();
3906
+ this.transientEvents.close();
3715
3907
  for (const subscription of this.consumers.values()) subscription.controller.abort();
3716
3908
  await this.owned.sealAndReleaseOwner();
3717
3909
  }
@@ -3732,7 +3924,9 @@ var AgentRuntime = class _AgentRuntime {
3732
3924
  this.assertOpen();
3733
3925
  const snapshot = await this.owned.snapshotRun(runId);
3734
3926
  await this.owned.cancelRun({ runId, expectedRevision: snapshot.revision, ...reason === void 0 ? {} : { reason } });
3735
- this.executions.get(runId)?.abort();
3927
+ const active = this.executions.get(runId);
3928
+ if (active) this.transientEvents.clear(runId, active.executionId);
3929
+ active?.controller.abort();
3736
3930
  return boundaryFromSnapshot(await this.owned.snapshotRun(runId));
3737
3931
  }
3738
3932
  async pump() {
@@ -3745,6 +3939,8 @@ var AgentRuntime = class _AgentRuntime {
3745
3939
  if (!next) return;
3746
3940
  this.activeAgents.add(next.agentId);
3747
3941
  const task = this.execute(next).finally(() => {
3942
+ const execution = this.executions.get(next.id);
3943
+ if (execution) this.transientEvents.clear(next.id, execution.executionId);
3748
3944
  this.activeAgents.delete(next.agentId);
3749
3945
  this.executions.delete(next.id);
3750
3946
  void this.pump();
@@ -3783,7 +3979,7 @@ var AgentRuntime = class _AgentRuntime {
3783
3979
  const executionId = createId("exec");
3784
3980
  claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
3785
3981
  const controller = new AbortController();
3786
- this.executions.set(run.id, controller);
3982
+ this.executions.set(run.id, { controller, executionId });
3787
3983
  const config = resolution.config;
3788
3984
  const assembly = await assembleExtensions(config);
3789
3985
  const executionConfig = {
@@ -3811,6 +4007,11 @@ var AgentRuntime = class _AgentRuntime {
3811
4007
  const result = await executeOnce({
3812
4008
  canonicalMessages: executionContext.messages,
3813
4009
  context: executionContext,
4010
+ execution: {
4011
+ agentId: run.agentId,
4012
+ runId: run.id,
4013
+ executionId: claim.execution.executionId
4014
+ },
3814
4015
  model,
3815
4016
  stream,
3816
4017
  maxAttempts: executionConfig.maxAttempts,
@@ -3819,20 +4020,49 @@ var AgentRuntime = class _AgentRuntime {
3819
4020
  beforePhase: assembly.beforePhase,
3820
4021
  afterPhase: assembly.afterPhase,
3821
4022
  beforePrompt: assembly.beforePrompt,
4023
+ onMessageDelta: (event) => {
4024
+ const active = this.executions.get(run.id);
4025
+ if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
4026
+ this.transientEvents.publish({
4027
+ kind: "message_delta",
4028
+ durability: "transient",
4029
+ runId: run.id,
4030
+ executionId: claim.execution.executionId,
4031
+ messageId: event.messageId,
4032
+ offset: event.offset,
4033
+ text: event.text
4034
+ });
4035
+ },
3822
4036
  onContext: assembly.setContext,
3823
4037
  runtime: {
3824
4038
  tools: ({ toolCall }) => {
3825
4039
  const task = toolQueue.then(async () => {
3826
- const execution = await this.executeTool({
4040
+ const execution = await this.executeToolBatch({
4041
+ run,
4042
+ execution: claim.execution,
4043
+ expectedRevision: executionRevision,
4044
+ config: executionConfig,
4045
+ toolCalls: [toolCall],
4046
+ signal: controller.signal
4047
+ });
4048
+ executionRevision = execution.revision;
4049
+ return execution.results[0];
4050
+ });
4051
+ toolQueue = task.then(() => void 0, () => void 0);
4052
+ return task;
4053
+ },
4054
+ toolsBatch: ({ toolCalls }) => {
4055
+ const task = toolQueue.then(async () => {
4056
+ const execution = await this.executeToolBatch({
3827
4057
  run,
3828
4058
  execution: claim.execution,
3829
4059
  expectedRevision: executionRevision,
3830
4060
  config: executionConfig,
3831
- toolCall,
4061
+ toolCalls,
3832
4062
  signal: controller.signal
3833
4063
  });
3834
4064
  executionRevision = execution.revision;
3835
- return execution.result;
4065
+ return execution.results;
3836
4066
  });
3837
4067
  toolQueue = task.then(() => void 0, () => void 0);
3838
4068
  return task;
@@ -3841,16 +4071,19 @@ var AgentRuntime = class _AgentRuntime {
3841
4071
  });
3842
4072
  if (result.type === "input_required") {
3843
4073
  const prompt = promptMessage(run, result.request.prompt, result.messages.length);
3844
- await this.owned.commitInputRequired({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, requestId: createId("input"), prompt, checkpoint: result.checkpoint });
4074
+ await this.owned.commitInputRequired({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, requestId: createId("input"), phase: result.request.phase, prompt, checkpoint: result.checkpoint });
4075
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3845
4076
  return;
3846
4077
  }
3847
4078
  if (result.type === "completed") {
3848
4079
  const output = latestAssistant(run, result.messages, claim.history.length);
3849
4080
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
4081
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3850
4082
  return;
3851
4083
  }
3852
4084
  const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
3853
4085
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, failure });
4086
+ this.transientEvents.clear(run.id, claim.execution.executionId);
3854
4087
  } catch (error) {
3855
4088
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
3856
4089
  if (claim) {
@@ -3871,29 +4104,75 @@ var AgentRuntime = class _AgentRuntime {
3871
4104
  });
3872
4105
  }
3873
4106
  async executeTool(input) {
3874
- const tool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
3875
- const providerToolCallId = input.toolCall.id;
3876
- const toolCallId = createId("tool");
3877
- const requestMessageId = createId("msg");
3878
- const reserved = await this.owned.reserveToolCall({
4107
+ const batch = await this.executeToolBatch({ ...input, toolCalls: [input.toolCall] });
4108
+ return { result: batch.results[0], revision: batch.revision };
4109
+ }
4110
+ async executeToolBatch(input) {
4111
+ const reserved = await this.owned.reserveToolCalls({
3879
4112
  runId: input.run.id,
3880
4113
  execution: input.execution,
3881
4114
  expectedRevision: input.expectedRevision,
3882
- requestMessageId,
3883
- toolCallId,
3884
- name: input.toolCall.name,
3885
- args: toJsonValue(input.toolCall.args)
4115
+ requestMessageId: createId("msg"),
4116
+ calls: input.toolCalls.map((toolCall) => ({
4117
+ providerToolCallId: toolCall.id,
4118
+ name: toolCall.name,
4119
+ args: toJsonValue(toolCall.args)
4120
+ }))
3886
4121
  });
3887
4122
  let revision = reserved.run.revision;
4123
+ const results = [];
4124
+ for (let index = 0; index < input.toolCalls.length; index += 1) {
4125
+ const toolCall = input.toolCalls[index];
4126
+ const tool = reserved.toolCalls[index];
4127
+ const execution = await this.executeReservedTool({
4128
+ ...input,
4129
+ toolCall,
4130
+ tool,
4131
+ expectedRevision: revision
4132
+ });
4133
+ revision = execution.revision;
4134
+ results.push(execution.result);
4135
+ }
4136
+ return { results, revision };
4137
+ }
4138
+ async executeReservedTool(input) {
4139
+ const durableTool = input.config.context.tools.find((candidate) => candidate.name === input.toolCall.name);
4140
+ const providerToolCallId = input.toolCall.id;
4141
+ const toolCallId = input.tool.id;
4142
+ let revision = input.expectedRevision;
3888
4143
  const protocolFailure = (error) => ({ toolCallId: providerToolCallId, toolName: input.toolCall.name, ok: false, content: null, error });
3889
- if (!tool) {
4144
+ if (!durableTool) {
3890
4145
  const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: `Tool ${input.toolCall.name} is not available.` } });
3891
4146
  return { result: protocolFailure(`Tool ${input.toolCall.name} is not available.`), revision: failed.run.revision };
3892
4147
  }
3893
- const context = { agentId: input.run.agentId, runId: input.run.id, toolCallId };
4148
+ let progressActive = false;
4149
+ const context = {
4150
+ agentId: input.run.agentId,
4151
+ runId: input.run.id,
4152
+ toolCallId,
4153
+ reportProgress: (progress) => {
4154
+ const active = this.executions.get(input.run.id);
4155
+ if (!progressActive || this.closed || input.signal.aborted || active?.executionId !== input.execution.executionId || !isJsonValue(progress)) return;
4156
+ let copy;
4157
+ try {
4158
+ if (JSON.stringify(progress).length > 64 * 1024) return;
4159
+ copy = structuredClone(progress);
4160
+ } catch {
4161
+ return;
4162
+ }
4163
+ this.transientEvents.publish({
4164
+ kind: "tool_progress",
4165
+ durability: "transient",
4166
+ runId: input.run.id,
4167
+ executionId: input.execution.executionId,
4168
+ toolCallId,
4169
+ progress: copy
4170
+ });
4171
+ }
4172
+ };
3894
4173
  try {
3895
4174
  if (input.config.beforeToolCall) {
3896
- const decision = await input.config.beforeToolCall({ tool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
4175
+ const decision = await input.config.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
3897
4176
  if (!decision.allow) {
3898
4177
  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 } });
3899
4178
  return { result: protocolFailure(decision.reason), revision: failed.run.revision };
@@ -3906,10 +4185,11 @@ var AgentRuntime = class _AgentRuntime {
3906
4185
  }
3907
4186
  const started = await this.owned.startToolCall({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId });
3908
4187
  revision = started.run.revision;
4188
+ progressActive = true;
3909
4189
  try {
3910
- let result = await tool.execute(toJsonValue(input.toolCall.args), context, input.signal);
4190
+ let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
3911
4191
  assertToolExecutionResult(result);
3912
- if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool, result, context, signal: input.signal });
4192
+ if (input.config.afterToolCall) result = await input.config.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
3913
4193
  assertToolExecutionResult(result);
3914
4194
  const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
3915
4195
  return {
@@ -3920,6 +4200,9 @@ var AgentRuntime = class _AgentRuntime {
3920
4200
  const reason = error instanceof Error ? error.message : "Tool execution outcome is indeterminate.";
3921
4201
  const indeterminate = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "indeterminate", reason, result: { ok: false, content: null, error: reason } });
3922
4202
  return { result: protocolFailure(reason), revision: indeterminate.run.revision };
4203
+ } finally {
4204
+ progressActive = false;
4205
+ this.transientEvents.clearTool(input.run.id, toolCallId);
3923
4206
  }
3924
4207
  }
3925
4208
  async requireAgent(agentId) {
@@ -3963,23 +4246,37 @@ var AgentRuntime = class _AgentRuntime {
3963
4246
  }
3964
4247
  async *observe(runId, options = {}) {
3965
4248
  let cursor = options.after;
3966
- while (true) {
3967
- if (options.signal?.aborted) throw abortError();
3968
- const snapshot = await this.owned.snapshotRun(runId);
3969
- if (["completed", "failed", "cancelled"].includes(snapshot.state)) {
3970
- const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
3971
- if (!pending.some((event) => event.runId === runId)) return;
3972
- }
3973
- const events = await this.owned.listEvents(cursor ? { after: cursor } : {});
3974
- let terminal = false;
3975
- for (const event of events) {
3976
- cursor = event.cursor;
3977
- if (event.runId !== runId) continue;
3978
- yield event;
3979
- if (event.kind === "run_transitioned" && ["completed", "failed", "cancelled"].includes(event.to)) terminal = true;
4249
+ const subscription = this.transientEvents.subscribe(runId);
4250
+ try {
4251
+ while (true) {
4252
+ if (options.signal?.aborted) throw abortError();
4253
+ const observationVersion = subscription.checkpoint();
4254
+ const snapshot = await this.owned.snapshotRun(runId);
4255
+ if (["completed", "failed", "cancelled"].includes(snapshot.state)) {
4256
+ const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
4257
+ if (!pending.some((event) => event.runId === runId)) return;
4258
+ }
4259
+ const events = await this.owned.listEvents(cursor ? { after: cursor } : {});
4260
+ for (const event of events) {
4261
+ cursor = event.cursor;
4262
+ if (event.runId !== runId) continue;
4263
+ if (event.kind === "message_committed") {
4264
+ subscription.clearMessage(event.message.id);
4265
+ } else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
4266
+ subscription.clearTool(event.toolCall.id);
4267
+ }
4268
+ yield event;
4269
+ if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) return;
4270
+ }
4271
+ const transient = subscription.shift();
4272
+ if (transient) {
4273
+ yield transient;
4274
+ continue;
4275
+ }
4276
+ await Promise.race([subscription.changed(observationVersion), delay(DEFAULT_POLL_MS)]);
3980
4277
  }
3981
- if (terminal) return;
3982
- await delay(DEFAULT_POLL_MS);
4278
+ } finally {
4279
+ this.transientEvents.unsubscribe(runId, subscription);
3983
4280
  }
3984
4281
  }
3985
4282
  async wait(runId, options = {}) {
@@ -3996,7 +4293,7 @@ var AgentRuntime = class _AgentRuntime {
3996
4293
  startHeartbeat() {
3997
4294
  this.heartbeat = setInterval(() => {
3998
4295
  void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
3999
- for (const controller of this.executions.values()) controller.abort();
4296
+ for (const execution of this.executions.values()) execution.controller.abort();
4000
4297
  });
4001
4298
  }, OWNER_RENEWAL_MS);
4002
4299
  this.heartbeat.unref?.();
@@ -4044,7 +4341,7 @@ function durableOutcome(outcome) {
4044
4341
  function boundaryFromSnapshot(snapshot) {
4045
4342
  switch (snapshot.state) {
4046
4343
  case "input_required":
4047
- return { type: "input_required", requestId: snapshot.request.id, prompt: snapshot.request.prompt };
4344
+ return { type: "input_required", requestId: snapshot.request.id, phase: snapshot.request.phase, prompt: snapshot.request.prompt };
4048
4345
  case "completed":
4049
4346
  return { type: "completed", outcome: snapshot.outcome, ...snapshot.output ? { output: snapshot.output } : {} };
4050
4347
  case "failed":
@@ -4070,7 +4367,7 @@ function toJsonValue(value) {
4070
4367
  assertJsonValue(value, "tool arguments");
4071
4368
  return value;
4072
4369
  }
4073
- function deferred() {
4370
+ function deferred2() {
4074
4371
  let resolve6;
4075
4372
  let reject;
4076
4373
  const promise = new Promise((res, rej) => {
@@ -4337,9 +4634,10 @@ var InMemoryStore = class _InMemoryStore {
4337
4634
  }
4338
4635
  commitInputRequired(lease, input) {
4339
4636
  this.assertOwner(lease);
4637
+ if (typeof input.phase !== "string" || input.phase.length === 0) throw new TypeError("phase must be non-empty");
4340
4638
  const requestId = input.requestId ?? createId("input");
4341
4639
  const operationKey = `input_required:${requestId}`;
4342
- const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.prompt.id, input.checkpoint]);
4640
+ const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.phase, input.prompt.id, input.checkpoint]);
4343
4641
  const replay = this.replayOperation(operationKey, operationPayload);
4344
4642
  if (replay) return clone(replay);
4345
4643
  const run = this.requireRun(input.runId);
@@ -4352,6 +4650,7 @@ var InMemoryStore = class _InMemoryStore {
4352
4650
  };
4353
4651
  const request = {
4354
4652
  id: requestId,
4653
+ phase: input.phase,
4355
4654
  messageId: prompt.id,
4356
4655
  createdAt: createTimestamp()
4357
4656
  };
@@ -4404,54 +4703,91 @@ var InMemoryStore = class _InMemoryStore {
4404
4703
  return result;
4405
4704
  }
4406
4705
  reserveToolCall(lease, input) {
4706
+ const providerToolCallId = input.providerToolCallId ?? (input.toolCallId ? String(input.toolCallId) : createId("provider"));
4707
+ const result = this.reserveToolCalls(lease, {
4708
+ runId: input.runId,
4709
+ execution: input.execution,
4710
+ expectedRevision: input.expectedRevision,
4711
+ requestMessageId: input.requestMessageId,
4712
+ calls: [{
4713
+ providerToolCallId,
4714
+ name: input.name,
4715
+ args: input.args,
4716
+ ...input.toolCallId === void 0 ? {} : { toolCallId: input.toolCallId }
4717
+ }]
4718
+ });
4719
+ return { run: result.run, toolCall: result.toolCalls[0] };
4720
+ }
4721
+ reserveToolCalls(lease, input) {
4407
4722
  this.assertOwner(lease);
4408
- assertToolValue(input.args, "tool.args");
4409
- const toolCallId = input.toolCallId ?? createId("tool");
4410
- const operationKey = `tool_reserve:${toolCallId}`;
4723
+ if (input.calls.length === 0) throw new TypeError("calls must be non-empty");
4724
+ const providerIds = /* @__PURE__ */ new Set();
4725
+ const durableIds = /* @__PURE__ */ new Set();
4726
+ for (const call of input.calls) {
4727
+ if (call.providerToolCallId.trim().length === 0) throw new TypeError("providerToolCallId must be non-empty");
4728
+ if (providerIds.has(call.providerToolCallId)) throw new TypeError("Tool provider IDs must be unique within one response");
4729
+ providerIds.add(call.providerToolCallId);
4730
+ assertToolValue(call.args, "tool.args");
4731
+ if (call.toolCallId !== void 0) {
4732
+ if (durableIds.has(call.toolCallId)) throw new TypeError("Tool Call IDs must be unique within one response");
4733
+ durableIds.add(call.toolCallId);
4734
+ }
4735
+ }
4736
+ const operationKey = `tool_reserve_batch:${input.requestMessageId}`;
4411
4737
  const operationPayload = canonicalJson([
4412
4738
  input.runId,
4413
4739
  input.expectedRevision,
4414
4740
  input.execution.executionId,
4415
4741
  input.requestMessageId,
4416
- input.name,
4417
- input.args
4742
+ input.calls
4418
4743
  ]);
4419
4744
  const replay = this.replayOperation(operationKey, operationPayload);
4420
4745
  if (replay) return clone(replay);
4421
4746
  const run = this.requireRun(input.runId);
4422
4747
  this.assertExecution(run, input.execution, input.expectedRevision);
4423
- const existing = this.toolCalls.get(toolCallId);
4424
- if (existing) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4748
+ if (this.messages.has(input.requestMessageId)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4425
4749
  const timestamp = createTimestamp();
4426
- const toolCall = {
4427
- id: toolCallId,
4750
+ const toolCalls = input.calls.map((call) => ({
4751
+ id: call.toolCallId ?? createId("tool"),
4752
+ providerToolCallId: call.providerToolCallId,
4428
4753
  agentId: run.agentId,
4429
4754
  runId: run.id,
4430
4755
  executionId: input.execution.executionId,
4431
4756
  requestMessageId: input.requestMessageId,
4432
- name: input.name,
4433
- args: clone(input.args),
4757
+ name: call.name,
4758
+ args: clone(call.args),
4434
4759
  state: "pending",
4435
4760
  createdAt: timestamp,
4436
4761
  updatedAt: timestamp
4437
- };
4762
+ }));
4763
+ for (const toolCall of toolCalls) {
4764
+ if (this.toolCalls.has(toolCall.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4765
+ }
4438
4766
  const requestMessage = {
4439
4767
  id: input.requestMessageId,
4440
4768
  agentId: run.agentId,
4441
4769
  runId: run.id,
4442
4770
  role: "assistant",
4443
- content: [{ type: "tool_use", toolCallId, name: input.name, input: clone(input.args) }],
4771
+ content: toolCalls.map((toolCall) => ({
4772
+ type: "tool_use",
4773
+ toolCallId: toolCall.id,
4774
+ providerToolCallId: toolCall.providerToolCallId,
4775
+ name: toolCall.name,
4776
+ input: clone(toolCall.args)
4777
+ })),
4444
4778
  sequenceWithinRun: this.nextMessageSequence(run.id),
4445
4779
  createdAt: timestamp
4446
4780
  };
4447
- if (this.messages.has(requestMessage.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
4448
- this.toolCalls.set(toolCall.id, toolCall);
4781
+ for (const toolCall of toolCalls) this.toolCalls.set(toolCall.id, toolCall);
4449
4782
  this.messages.set(requestMessage.id, requestMessage);
4450
4783
  run.revision += 1;
4451
4784
  run.updatedAt = timestamp;
4452
4785
  this.appendMessage(run, requestMessage);
4453
- this.appendToolTransition(run, { from: null, to: "pending" }, toolCall);
4454
- const result = { run: clone(run), toolCall: clone(toolCall) };
4786
+ for (const toolCall of toolCalls) this.appendToolTransition(run, { from: null, to: "pending" }, toolCall);
4787
+ const result = {
4788
+ run: clone(run),
4789
+ toolCalls: toolCalls.map((toolCall) => clone(toolCall))
4790
+ };
4455
4791
  this.writeOperationReceipt(operationKey, operationPayload, result);
4456
4792
  return result;
4457
4793
  }
@@ -4500,7 +4836,7 @@ var InMemoryStore = class _InMemoryStore {
4500
4836
  agentId: run.agentId,
4501
4837
  runId: run.id,
4502
4838
  role: "tool",
4503
- content: [{ type: "tool_result", toolCallId: toolCall.id, result: clone(input.result) }],
4839
+ content: [{ type: "tool_result", toolCallId: toolCall.id, providerToolCallId: toolCall.providerToolCallId, result: clone(input.result) }],
4504
4840
  sequenceWithinRun: this.nextMessageSequence(run.id),
4505
4841
  createdAt: createTimestamp()
4506
4842
  };
@@ -4637,7 +4973,7 @@ var InMemoryStore = class _InMemoryStore {
4637
4973
  const request = run.openInputRequest;
4638
4974
  const prompt = request && this.messages.get(request.messageId);
4639
4975
  if (!prompt || prompt.role !== "assistant") throw new Error(`Run ${run.id} has an invalid input prompt.`);
4640
- return { ...base, state: "input_required", request: { id: request.id, prompt: clone(prompt) } };
4976
+ return { ...base, state: "input_required", request: { id: request.id, phase: request.phase, prompt: clone(prompt) } };
4641
4977
  }
4642
4978
  case "completed": {
4643
4979
  const output = this.messagesForRun(run.id).filter((message) => message.role === "assistant").at(-1);
@@ -4660,6 +4996,9 @@ var InMemoryStore = class _InMemoryStore {
4660
4996
  listEvents(lease, input = {}) {
4661
4997
  this.assertOwner(lease);
4662
4998
  const after = input.after ? this.parseCursor(input.after) : 0;
4999
+ if (after > this.eventSequence) {
5000
+ throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
5001
+ }
4663
5002
  return clone(this.events.filter((event) => this.parseCursor(event.cursor) > after));
4664
5003
  }
4665
5004
  openConsumer(lease, consumerId) {
@@ -4730,7 +5069,7 @@ var InMemoryStore = class _InMemoryStore {
4730
5069
  agentId: run.agentId,
4731
5070
  runId: run.id,
4732
5071
  role: "tool",
4733
- content: [{ type: "tool_result", toolCallId: toolCall.id, result }],
5072
+ content: [{ type: "tool_result", toolCallId: toolCall.id, providerToolCallId: toolCall.providerToolCallId, result }],
4734
5073
  sequenceWithinRun: this.nextMessageSequence(run.id),
4735
5074
  createdAt: createTimestamp()
4736
5075
  };
@@ -4787,10 +5126,10 @@ var InMemoryStore = class _InMemoryStore {
4787
5126
  appendTransition(run, from, to, request, prompt, outcome, failure, reason) {
4788
5127
  const transition = {
4789
5128
  ...this.baseEvent(run),
4790
- kind: "run_transitioned",
5129
+ kind: "run_state_changed",
4791
5130
  from,
4792
5131
  to,
4793
- ...to === "input_required" && request && prompt ? { request: { id: request.id, prompt } } : {},
5132
+ ...to === "input_required" && request && prompt ? { request: { id: request.id, phase: request.phase, prompt } } : {},
4794
5133
  ...to === "completed" && outcome ? { outcome } : {},
4795
5134
  ...to === "failed" && failure ? { failure } : {},
4796
5135
  ...to === "cancelled" && reason ? { reason } : {}
@@ -4865,6 +5204,9 @@ var MemoryOwnedStore = class {
4865
5204
  async reserveToolCall(input) {
4866
5205
  return this.store.reserveToolCall(this.lease, input);
4867
5206
  }
5207
+ async reserveToolCalls(input) {
5208
+ return this.store.reserveToolCalls(this.lease, input);
5209
+ }
4868
5210
  async startToolCall(input) {
4869
5211
  return this.store.startToolCall(this.lease, input);
4870
5212
  }
@@ -5118,6 +5460,9 @@ var SqliteStore = class {
5118
5460
  async reserveToolCall(lease, input) {
5119
5461
  return this.invoke(lease, (store, current) => store.reserveToolCall(current, input));
5120
5462
  }
5463
+ async reserveToolCalls(lease, input) {
5464
+ return this.invoke(lease, (store, current) => store.reserveToolCalls(current, input));
5465
+ }
5121
5466
  async startToolCall(lease, input) {
5122
5467
  return this.invoke(lease, (store, current) => store.startToolCall(current, input));
5123
5468
  }
@@ -5367,6 +5712,9 @@ var SqliteOwnedStore = class {
5367
5712
  reserveToolCall(input) {
5368
5713
  return this.store.reserveToolCall(this.lease, input);
5369
5714
  }
5715
+ reserveToolCalls(input) {
5716
+ return this.store.reserveToolCalls(this.lease, input);
5717
+ }
5370
5718
  startToolCall(input) {
5371
5719
  return this.store.startToolCall(this.lease, input);
5372
5720
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.7.5",
3
+ "version": "0.8.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",