@rowan-agent/agent 0.15.1 → 0.15.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/dist/index.d.ts CHANGED
@@ -1137,6 +1137,12 @@ interface OwnedStore {
1137
1137
  phaseId: string;
1138
1138
  visit: number;
1139
1139
  }): Promise<RunRecord>;
1140
+ commitPhaseOutput(input: {
1141
+ runId: RunId;
1142
+ execution: ExecutionToken;
1143
+ expectedRevision: number;
1144
+ message: AssistantMessage;
1145
+ }): Promise<RunRecord>;
1140
1146
  commitInputRequired(input: {
1141
1147
  runId: RunId;
1142
1148
  execution: ExecutionToken;
@@ -2368,6 +2374,13 @@ declare class InMemoryStore implements DurableStore {
2368
2374
  phaseId: string;
2369
2375
  visit: number;
2370
2376
  }): RunRecord;
2377
+ /** Commit a parallel Phase's reply while its Run is still running. */
2378
+ commitPhaseOutput(lease: OwnerLease, input: {
2379
+ runId: RunId;
2380
+ execution: ExecutionToken;
2381
+ expectedRevision: number;
2382
+ message: AssistantMessage;
2383
+ }): RunRecord;
2371
2384
  commitInputRequired(lease: OwnerLease, input: {
2372
2385
  runId: RunId;
2373
2386
  execution: ExecutionToken;
@@ -2571,6 +2584,12 @@ declare class SqliteStore implements DurableStore {
2571
2584
  phaseId: string;
2572
2585
  visit: number;
2573
2586
  }): Promise<RunRecord>;
2587
+ commitPhaseOutput(lease: OwnerLease, input: {
2588
+ runId: RunId;
2589
+ execution: ExecutionToken;
2590
+ expectedRevision: number;
2591
+ message: AssistantMessage;
2592
+ }): Promise<RunRecord>;
2574
2593
  commitInputRequired(lease: OwnerLease, input: {
2575
2594
  runId: RunId;
2576
2595
  execution: ExecutionToken;
package/dist/index.js CHANGED
@@ -2512,12 +2512,22 @@ function createToolResultContent(result) {
2512
2512
  }
2513
2513
  ];
2514
2514
  }
2515
- function createRouteToolResultContent(toolCall, error) {
2515
+ function createRouteToolResultContent(toolCall, error, executionContext) {
2516
+ const result = error ? { ok: false, error } : { ok: true };
2517
+ if (!error && executionContext) {
2518
+ const { completedPhases, phaseTransitions } = executionContext;
2519
+ if (completedPhases && completedPhases.length > 0) {
2520
+ result.completed_phases = completedPhases;
2521
+ }
2522
+ if (phaseTransitions && phaseTransitions.length > 0) {
2523
+ result.route_history = phaseTransitions.map(({ from, to }) => `${from} \u2192 ${to}`);
2524
+ }
2525
+ }
2516
2526
  return [
2517
2527
  {
2518
2528
  type: "tool_result",
2519
2529
  toolUseId: toolCall.id,
2520
- content: error ? JSON.stringify({ ok: false, error }) : '{"ok": true}',
2530
+ content: JSON.stringify(result),
2521
2531
  ...error ? { isError: true } : {}
2522
2532
  }
2523
2533
  ];
@@ -2561,11 +2571,13 @@ async function executePhaseWithModel(ctx) {
2561
2571
  };
2562
2572
  for (const toolCall of collected.toolCalls) {
2563
2573
  if (toolCall.name !== PhaseRouteTool) continue;
2574
+ const hasOrdinaryTools = collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool);
2564
2575
  const messageId = ctx.messageManager.start(
2565
2576
  "tool",
2566
2577
  createRouteToolResultContent(
2567
2578
  toolCall,
2568
- collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool) ? "Route must be called separately after ordinary tools finish; this route decision was ignored." : void 0
2579
+ hasOrdinaryTools ? "Route must be called separately after ordinary tools finish; this route decision was ignored." : void 0,
2580
+ !hasOrdinaryTools ? { completedPhases: ctx.completedPhases, phaseTransitions: ctx.state.metrics.phaseTransitions } : void 0
2569
2581
  ),
2570
2582
  {
2571
2583
  phase: ctx.phase.name
@@ -2617,6 +2629,7 @@ async function runPhaseLoop(config, state, registry) {
2617
2629
  let previousPhaseInputMsgId;
2618
2630
  let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
2619
2631
  let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
2632
+ const completedPhases = [];
2620
2633
  const availablePhases = [];
2621
2634
  for (const [, phase] of registry.phases) {
2622
2635
  availablePhases.push({
@@ -2657,7 +2670,16 @@ async function runPhaseLoop(config, state, registry) {
2657
2670
  parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2658
2671
  }
2659
2672
  const successfulResults = await waitForBackgroundTasks(parallelTasks);
2660
- previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2673
+ previousResults = successfulResults.map((r) => ({
2674
+ name: r.instanceId,
2675
+ output: {
2676
+ message: r.content,
2677
+ ...r.payload !== void 0 ? { payload: r.payload } : {}
2678
+ }
2679
+ }));
2680
+ for (const r of successfulResults) {
2681
+ completedPhases.push(r.phaseId);
2682
+ }
2661
2683
  pendingInstruction = void 0;
2662
2684
  };
2663
2685
  if (!resumingSuspendedRun && state.entryPhases && state.entryPhases.length > 0) {
@@ -2842,7 +2864,7 @@ async function runPhaseLoop(config, state, registry) {
2842
2864
  previousResults = [];
2843
2865
  pendingInstruction = void 0;
2844
2866
  }
2845
- const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2867
+ const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext, completedPhases };
2846
2868
  let output = await executePhase(runtime);
2847
2869
  delete state.initialPhasePayload;
2848
2870
  delete state.entryPhases;
@@ -2882,6 +2904,9 @@ async function runPhaseLoop(config, state, registry) {
2882
2904
  if (output.status) {
2883
2905
  await execution.reportStatus(output.status);
2884
2906
  }
2907
+ if (output.route !== "continue") {
2908
+ completedPhases.push(currentPhaseId);
2909
+ }
2885
2910
  if (phase.name === STOP_PHASE_ID) {
2886
2911
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
2887
2912
  removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
@@ -3239,6 +3264,13 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
3239
3264
  }
3240
3265
  };
3241
3266
  }
3267
+ function parallelPhaseReply(produced) {
3268
+ const last = [...produced].reverse().find((message) => message.role === "assistant");
3269
+ if (!last) return void 0;
3270
+ if (typeof last.content === "string") return last.content.trim() ? last : void 0;
3271
+ if (last.content.some((part) => part.type === "tool_use")) return void 0;
3272
+ return last.content.some((part) => part.type === "text" && part.text.trim()) ? last : void 0;
3273
+ }
3242
3274
  async function executeParallelPhase(config, state, registry, phase, payload, instruction, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
3243
3275
  const messages = [...context];
3244
3276
  const effectivePayload = preparePhasePayload(phase.input, payload);
@@ -3280,7 +3312,12 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
3280
3312
  const toolExecutionManager = createToolExecutionManager();
3281
3313
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
3282
3314
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
3315
+ const phaseStart = messages.length;
3283
3316
  let output = await executePhase(runtime);
3317
+ const reply = parallelPhaseReply(messages.slice(phaseStart));
3318
+ if (reply && config.onParallelPhaseOutput) {
3319
+ await config.onParallelPhaseOutput(reply, { mode: "parallel", instanceId, groupId, index, count, sourcePhaseId });
3320
+ }
3284
3321
  if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
3285
3322
  if (config.afterPhase) {
3286
3323
  const extAfter = await config.afterPhase(phase.name, output);
@@ -3458,6 +3495,7 @@ async function executeOnce(input) {
3458
3495
  afterPhase: input.afterPhase,
3459
3496
  beforePrompt: input.beforePrompt,
3460
3497
  onMessage: input.onMessage,
3498
+ onParallelPhaseOutput: input.onParallelPhaseOutput,
3461
3499
  onMessageDelta: input.onMessageDelta,
3462
3500
  onThinkingDelta: input.onThinkingDelta,
3463
3501
  onOutcome: input.onOutcome,
@@ -4282,6 +4320,10 @@ function renderInteractionText(record) {
4282
4320
  const replyStr = record.reply ?? "";
4283
4321
  return template.replaceAll("{{prompt}}", record.prompt).replaceAll("{{answer}}", answerStr).replaceAll("{{reply}}", replyStr);
4284
4322
  }
4323
+ var PARALLEL_PHASE_METADATA_KEY = "parallelPhase";
4324
+ function isParallelPhaseOutput(message) {
4325
+ return message.role === "assistant" && message.metadata?.[PARALLEL_PHASE_METADATA_KEY] !== void 0;
4326
+ }
4285
4327
  function projectModelContext(input) {
4286
4328
  const toolInteractionTexts = /* @__PURE__ */ new Map();
4287
4329
  for (const message of input.messages) {
@@ -4297,6 +4339,7 @@ function projectModelContext(input) {
4297
4339
  }
4298
4340
  const projectedMessages = [];
4299
4341
  for (const message of input.messages) {
4342
+ if (isParallelPhaseOutput(message)) continue;
4300
4343
  if (message.role === "interaction") {
4301
4344
  if (message.toolCallId !== void 0) {
4302
4345
  continue;
@@ -6588,6 +6631,33 @@ var AgentRuntime = class _AgentRuntime {
6588
6631
  status
6589
6632
  });
6590
6633
  },
6634
+ onParallelPhaseOutput: (message, invocation) => {
6635
+ const task = toolQueue.then(async () => {
6636
+ const active = this.executions.get(run.id);
6637
+ if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
6638
+ const projected = projectAssistantMessage(message, run.agentId, run.id, 0);
6639
+ const committed = await this.owned.commitPhaseOutput({
6640
+ runId: run.id,
6641
+ execution: claim.execution,
6642
+ expectedRevision: executionRevision,
6643
+ message: {
6644
+ ...projected,
6645
+ metadata: {
6646
+ ...projected.metadata,
6647
+ [PARALLEL_PHASE_METADATA_KEY]: {
6648
+ groupId: invocation.groupId,
6649
+ instanceId: invocation.instanceId,
6650
+ index: invocation.index,
6651
+ count: invocation.count
6652
+ }
6653
+ }
6654
+ }
6655
+ });
6656
+ executionRevision = committed.revision;
6657
+ });
6658
+ toolQueue = task.then(() => void 0, () => void 0);
6659
+ return task;
6660
+ },
6591
6661
  onMessageDelta: (event) => {
6592
6662
  const active = this.executions.get(run.id);
6593
6663
  if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
@@ -6675,7 +6745,7 @@ var AgentRuntime = class _AgentRuntime {
6675
6745
  }
6676
6746
  }
6677
6747
  if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
6678
- const output = latestAssistant(run, result.messages, modelMessages.length, true);
6748
+ const output = latestAssistant(run, result.messages, executionContext.messages.length, true);
6679
6749
  const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
6680
6750
  await this.owned.cancelRun({
6681
6751
  runId: run.id,
@@ -6698,7 +6768,7 @@ var AgentRuntime = class _AgentRuntime {
6698
6768
  const output = isToolCallInteraction ? void 0 : latestAssistant(
6699
6769
  run,
6700
6770
  result.messages,
6701
- modelMessages.length
6771
+ executionContext.messages.length
6702
6772
  );
6703
6773
  const prompt = isToolCallInteraction ? void 0 : output ?? (result.interactions[0]?.prompt ? promptMessage(run, result.interactions[0].prompt, result.messages.length) : void 0);
6704
6774
  await this.owned.commitInputRequired({
@@ -6716,7 +6786,7 @@ var AgentRuntime = class _AgentRuntime {
6716
6786
  return;
6717
6787
  }
6718
6788
  if (result.type === "completed") {
6719
- const output = latestAssistant(run, result.messages, modelMessages.length);
6789
+ const output = latestAssistant(run, result.messages, executionContext.messages.length);
6720
6790
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
6721
6791
  this.transientEvents.clear(run.id, claim.execution.executionId, true);
6722
6792
  return;
@@ -8100,6 +8170,32 @@ var InMemoryStore = class _InMemoryStore {
8100
8170
  this.writeOperationReceipt(operationKey, operationPayload, result);
8101
8171
  return result;
8102
8172
  }
8173
+ /** Commit a parallel Phase's reply while its Run is still running. */
8174
+ commitPhaseOutput(lease, input) {
8175
+ this.assertOwner(lease);
8176
+ const operationKey = `phase_output:${input.message.id}`;
8177
+ const operationPayload = canonicalJson([input.runId, input.execution.executionId, input.expectedRevision, input.message]);
8178
+ const replay = this.replayOperation(operationKey, operationPayload);
8179
+ if (replay) return clone(replay);
8180
+ const run = this.requireRun(input.runId);
8181
+ this.assertExecution(run, input.execution, input.expectedRevision);
8182
+ this.assertState(run, ["running"]);
8183
+ if (this.messages.has(input.message.id)) throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
8184
+ const message = {
8185
+ ...clone(input.message),
8186
+ agentId: run.agentId,
8187
+ runId: run.id,
8188
+ role: "assistant",
8189
+ sequenceWithinRun: this.nextMessageSequence(run.id)
8190
+ };
8191
+ this.messages.set(message.id, message);
8192
+ run.revision += 1;
8193
+ run.updatedAt = createTimestamp();
8194
+ this.appendMessage(run, message);
8195
+ const result = clone(run);
8196
+ this.writeOperationReceipt(operationKey, operationPayload, result);
8197
+ return result;
8198
+ }
8103
8199
  commitInputRequired(lease, input) {
8104
8200
  this.assertOwner(lease);
8105
8201
  if (typeof input.phase !== "string" || input.phase.length === 0) throw new TypeError("phase must be non-empty");
@@ -8919,6 +9015,9 @@ var MemoryOwnedStore = class {
8919
9015
  async commitPhaseEntered(input) {
8920
9016
  return this.store.commitPhaseEntered(this.lease, input);
8921
9017
  }
9018
+ async commitPhaseOutput(input) {
9019
+ return this.store.commitPhaseOutput(this.lease, input);
9020
+ }
8922
9021
  async commitInputRequired(input) {
8923
9022
  return this.store.commitInputRequired(this.lease, input);
8924
9023
  }
@@ -9322,6 +9421,9 @@ var SqliteStore = class {
9322
9421
  async commitPhaseEntered(lease, input) {
9323
9422
  return this.invoke(lease, (store, current) => store.commitPhaseEntered(current, input));
9324
9423
  }
9424
+ async commitPhaseOutput(lease, input) {
9425
+ return this.invoke(lease, (store, current) => store.commitPhaseOutput(current, input));
9426
+ }
9325
9427
  async commitInputRequired(lease, input) {
9326
9428
  return this.invoke(lease, (store, current) => store.commitInputRequired(current, input));
9327
9429
  }
@@ -9715,6 +9817,9 @@ var SqliteOwnedStore = class {
9715
9817
  commitPhaseEntered(input) {
9716
9818
  return this.store.commitPhaseEntered(this.lease, input);
9717
9819
  }
9820
+ commitPhaseOutput(input) {
9821
+ return this.store.commitPhaseOutput(this.lease, input);
9822
+ }
9718
9823
  commitInputRequired(input) {
9719
9824
  return this.store.commitInputRequired(this.lease, input);
9720
9825
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.15.1",
3
+ "version": "0.15.4",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",