@rowan-agent/agent 0.14.3 → 0.15.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +807 -795
  2. package/dist/index.js +137 -74
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2610,12 +2610,74 @@ async function runPhaseLoop(config, state, registry) {
2610
2610
  let isContinuing = resumingSuspendedRun ? state.continuation?.isContinuing ?? true : false;
2611
2611
  if (resumingSuspendedRun) {
2612
2612
  state.status = "running";
2613
+ delete state.entryPhases;
2613
2614
  }
2614
2615
  let previousPayload = resumingSuspendedRun ? state.continuation && Object.hasOwn(state.continuation, "previousPayload") ? state.continuation.previousPayload : state.initialPhasePayload : state.initialPhasePayload;
2615
2616
  let previousPhaseMsgId = resumingSuspendedRun ? state.continuation?.previousPhaseMessageId : void 0;
2616
2617
  let previousPhaseInputMsgId;
2617
2618
  let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
2618
2619
  let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
2620
+ const availablePhases = [];
2621
+ for (const [, phase] of registry.phases) {
2622
+ availablePhases.push({
2623
+ name: phase.name,
2624
+ description: phase.description,
2625
+ tools: phase.tools,
2626
+ skills: phase.skills,
2627
+ input: phase.input,
2628
+ isolated: phase.isolated,
2629
+ disableAutoInvocation: phase.disableAutoInvocation
2630
+ });
2631
+ }
2632
+ const dispatchParallel = async (targets, instruction, fromPhaseId) => {
2633
+ const contextSnapshot = snapshotMessages(config.context.messages);
2634
+ const parallelTasks = /* @__PURE__ */ new Map();
2635
+ const instanceIds = buildInstanceIds(targets.map((t) => t.phase));
2636
+ const groupId = createId("phase-group");
2637
+ for (let i = 0; i < targets.length; i++) {
2638
+ const target = targets[i];
2639
+ const pt = registry.phases.get(target.phase);
2640
+ if (!pt) continue;
2641
+ const instanceId = instanceIds[i];
2642
+ const promise = executeParallelPhase(
2643
+ config,
2644
+ state,
2645
+ registry,
2646
+ pt,
2647
+ target.payload !== void 0 ? normalizePayload(target.payload) : void 0,
2648
+ instruction,
2649
+ pt.isolated ? [] : contextSnapshot,
2650
+ availablePhases,
2651
+ instanceId,
2652
+ groupId,
2653
+ i,
2654
+ targets.length,
2655
+ fromPhaseId
2656
+ );
2657
+ parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2658
+ }
2659
+ const successfulResults = await waitForBackgroundTasks(parallelTasks);
2660
+ previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2661
+ pendingInstruction = void 0;
2662
+ };
2663
+ if (!resumingSuspendedRun && state.entryPhases && state.entryPhases.length > 0) {
2664
+ const entryPhases = state.entryPhases;
2665
+ delete state.entryPhases;
2666
+ for (const entry of entryPhases) {
2667
+ if (!registry.phases.has(entry.phase)) throw new Error(`Phase "${entry.phase}" not found`);
2668
+ }
2669
+ if (entryPhases.length === 1) {
2670
+ currentPhaseId = entryPhases[0].phase;
2671
+ previousPayload = entryPhases[0].payload;
2672
+ } else {
2673
+ if (LoopGuard.checkAbort(config.signal).stopReason !== "none") {
2674
+ return completeRun(config, state, createOutcome.aborted());
2675
+ }
2676
+ await dispatchParallel(entryPhases, void 0, registry.entryPhaseId);
2677
+ currentPhaseId = registry.entryPhaseId;
2678
+ previousPayload = void 0;
2679
+ }
2680
+ }
2619
2681
  const awaitInput = async (prompt, resumeAsContinuing) => {
2620
2682
  if (!config.waitForInput) return void 0;
2621
2683
  const preAbort = LoopGuard.checkAbort(config.signal);
@@ -2664,18 +2726,6 @@ async function runPhaseLoop(config, state, registry) {
2664
2726
  return { type: "continued" };
2665
2727
  };
2666
2728
  while (currentPhaseId) {
2667
- const availablePhases = [];
2668
- for (const [, phase2] of registry.phases) {
2669
- availablePhases.push({
2670
- name: phase2.name,
2671
- description: phase2.description,
2672
- tools: phase2.tools,
2673
- skills: phase2.skills,
2674
- input: phase2.input,
2675
- isolated: phase2.isolated,
2676
- disableAutoInvocation: phase2.disableAutoInvocation
2677
- });
2678
- }
2679
2729
  const abortResult = LoopGuard.checkAbort(config.signal);
2680
2730
  if (abortResult.stopReason !== "none") {
2681
2731
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
@@ -2795,6 +2845,7 @@ async function runPhaseLoop(config, state, registry) {
2795
2845
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2796
2846
  let output = await executePhase(runtime);
2797
2847
  delete state.initialPhasePayload;
2848
+ delete state.entryPhases;
2798
2849
  let modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry);
2799
2850
  let routeDecision = modelRouteDecision;
2800
2851
  if (routeDecision) applyFirstDecision(routeDecision, output);
@@ -2858,38 +2909,7 @@ async function runPhaseLoop(config, state, registry) {
2858
2909
  continue;
2859
2910
  }
2860
2911
  if (routeDecision && routeDecision.requestedCount > 1) {
2861
- const contextSnapshot = snapshotMessages(config.context.messages);
2862
- const parallelTasks = /* @__PURE__ */ new Map();
2863
- const instanceIds = buildInstanceIds(routeDecision.decision.map((t) => t.phase));
2864
- const groupId = createId("phase-group");
2865
- const count = routeDecision.decision.length;
2866
- for (let i = 0; i < routeDecision.decision.length; i++) {
2867
- const target = routeDecision.decision[i];
2868
- const pt = registry.phases.get(target.phase);
2869
- if (!pt) continue;
2870
- const instanceId = instanceIds[i];
2871
- const context = pt.isolated ? [] : contextSnapshot;
2872
- const payload = target.payload !== void 0 ? normalizePayload(target.payload) : void 0;
2873
- const promise = executeParallelPhase(
2874
- config,
2875
- state,
2876
- registry,
2877
- pt,
2878
- payload,
2879
- routeDecision.instruction,
2880
- context,
2881
- availablePhases,
2882
- instanceId,
2883
- groupId,
2884
- i,
2885
- count,
2886
- currentPhaseId
2887
- );
2888
- parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2889
- }
2890
- const successfulResults = await waitForBackgroundTasks(parallelTasks);
2891
- previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2892
- pendingInstruction = void 0;
2912
+ await dispatchParallel(routeDecision.decision, routeDecision.instruction, currentPhaseId);
2893
2913
  const entryPhaseId = phase.target ?? registry.entryPhaseId;
2894
2914
  if (entryPhaseId === "stop") {
2895
2915
  if (!shouldRunStopPhase(phase, output)) {
@@ -3260,8 +3280,15 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
3260
3280
  const toolExecutionManager = createToolExecutionManager();
3261
3281
  const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
3262
3282
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
3263
- const output = await executePhase(runtime);
3283
+ let output = await executePhase(runtime);
3264
3284
  if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
3285
+ if (config.afterPhase) {
3286
+ const extAfter = await config.afterPhase(phase.name, output);
3287
+ if (extAfter?.output) output = extAfter.output;
3288
+ }
3289
+ if (output.status) {
3290
+ await execution.reportStatus(output.status);
3291
+ }
3265
3292
  const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
3266
3293
  const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
3267
3294
  return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message ?? `${phase.name} completed.` };
@@ -3337,7 +3364,8 @@ function encodeExecutionCheckpoint(state) {
3337
3364
  metrics,
3338
3365
  ...continuation ? { continuation } : {},
3339
3366
  ...runInteractions === void 0 ? {} : { runInteractions },
3340
- ...state.initialPhasePayload === void 0 ? {} : { initialPhasePayload: state.initialPhasePayload }
3367
+ ...state.initialPhasePayload === void 0 ? {} : { initialPhasePayload: state.initialPhasePayload },
3368
+ ...state.entryPhases === void 0 ? {} : { entryPhases: state.entryPhases }
3341
3369
  };
3342
3370
  assertJsonValue(data, "execution checkpoint");
3343
3371
  return {
@@ -3365,6 +3393,7 @@ function decodeExecutionCheckpoint(checkpoint) {
3365
3393
  },
3366
3394
  status: "suspended",
3367
3395
  ...checkpoint.data.initialPhasePayload === void 0 ? {} : { initialPhasePayload: checkpoint.data.initialPhasePayload },
3396
+ ...checkpoint.data.entryPhases === void 0 ? {} : { entryPhases: checkpoint.data.entryPhases },
3368
3397
  ...checkpoint.data.continuation ? {
3369
3398
  continuation: {
3370
3399
  ...checkpoint.data.continuation,
@@ -3401,7 +3430,8 @@ async function executeOnce(input) {
3401
3430
  attempt: 0,
3402
3431
  status: "running",
3403
3432
  metrics: createMetrics(),
3404
- ...input.initialPhasePayload === void 0 ? {} : { initialPhasePayload: input.initialPhasePayload }
3433
+ ...input.initialPhasePayload === void 0 ? {} : { initialPhasePayload: input.initialPhasePayload },
3434
+ ...input.entryPhases === void 0 ? {} : { entryPhases: input.entryPhases }
3405
3435
  };
3406
3436
  if (input.interactionAnswers) {
3407
3437
  const existing = state.runInteractions ?? state.phaseInteractions;
@@ -3498,11 +3528,16 @@ function isCheckpointData(value) {
3498
3528
  if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
3499
3529
  if (!isMetrics(value.metrics)) return false;
3500
3530
  if (value.initialPhasePayload !== void 0 && !isJsonValue(value.initialPhasePayload)) return false;
3531
+ if (value.entryPhases !== void 0 && !isEntryPhases(value.entryPhases)) return false;
3501
3532
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
3502
3533
  if (value.runInteractions !== void 0 && !isRunInteractionState(value.runInteractions)) return false;
3503
3534
  if (value.phaseInteractions !== void 0 && !isRunInteractionState(value.phaseInteractions)) return false;
3504
3535
  return true;
3505
3536
  }
3537
+ function isEntryPhases(value) {
3538
+ if (!Array.isArray(value) || value.length === 0) return false;
3539
+ return value.every((item) => isRecord2(item) && typeof item.phase === "string" && item.phase.trim().length > 0 && (item.payload === void 0 || isJsonValue(item.payload)));
3540
+ }
3506
3541
  function isRunInteractionState(value) {
3507
3542
  if (!isRecord2(value) || !Array.isArray(value.requests) || !isRecord2(value.answers)) return false;
3508
3543
  if (!Object.values(value.answers).every((answer) => isJsonValue(answer))) return false;
@@ -3569,6 +3604,19 @@ function isMetadata(value) {
3569
3604
  function assertMetadata(value, argument) {
3570
3605
  if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
3571
3606
  }
3607
+ function assertEntryPhases(value, argument = "entryPhases") {
3608
+ if (!Array.isArray(value) || value.length === 0) {
3609
+ throw new TypeError(`${argument} must be a non-empty array`);
3610
+ }
3611
+ for (const item of value) {
3612
+ if (!isRecord3(item) || typeof item.phase !== "string" || item.phase.trim() === "") {
3613
+ throw new TypeError(`${argument} items must contain a non-blank phase name`);
3614
+ }
3615
+ if (item.payload !== void 0) {
3616
+ assertJsonValue(item.payload, `${argument}.payload`);
3617
+ }
3618
+ }
3619
+ }
3572
3620
  function isText(value) {
3573
3621
  return isRecord3(value) && value.type === "text" && typeof value.text === "string";
3574
3622
  }
@@ -3763,14 +3811,16 @@ function encodeIdempotencyScope(storeIncarnation, scope) {
3763
3811
  if (typeof storeIncarnation !== "string" || storeIncarnation.length === 0) throw new TypeError("storeIncarnation must be non-empty");
3764
3812
  return canonicalJson([storeIncarnation, ...scope]);
3765
3813
  }
3766
- function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken) {
3814
+ function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases) {
3767
3815
  const normalized = normalizeUserInput(input);
3768
3816
  if (metadata !== void 0) assertUtf8ByteLimit(canonicalJson(metadata), METADATA_JSON_BYTES, "run.metadata");
3769
3817
  if (phasePayload !== void 0) assertUtf8ByteLimit(canonicalJson(phasePayload), MESSAGE_CONTENT_JSON_BYTES, "run.phasePayload");
3818
+ if (entryPhases !== void 0) assertUtf8ByteLimit(canonicalJson(entryPhases), MESSAGE_CONTENT_JSON_BYTES, "run.entryPhases");
3770
3819
  return canonicalJson({
3771
3820
  input: normalized,
3772
3821
  ...metadata === void 0 ? {} : { metadata },
3773
3822
  ...phasePayload === void 0 ? {} : { phasePayload },
3823
+ ...entryPhases === void 0 ? {} : { entryPhases },
3774
3824
  ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken }
3775
3825
  });
3776
3826
  }
@@ -6165,6 +6215,12 @@ var AgentRuntime = class _AgentRuntime {
6165
6215
  }
6166
6216
  async start(agentId, input, options) {
6167
6217
  this.assertOpen();
6218
+ if (options.phasePayload !== void 0 && options.entryPhases !== void 0) {
6219
+ throw new TypeError("phasePayload and entryPhases are mutually exclusive");
6220
+ }
6221
+ if (options.entryPhases !== void 0) {
6222
+ assertEntryPhases(options.entryPhases);
6223
+ }
6168
6224
  const agent = await this.requireAgent(agentId);
6169
6225
  if (!agent.activatedAt || !agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
6170
6226
  let phasePayload;
@@ -6196,6 +6252,7 @@ var AgentRuntime = class _AgentRuntime {
6196
6252
  input,
6197
6253
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
6198
6254
  ...phasePayload === void 0 ? {} : { phasePayload },
6255
+ ...options.entryPhases === void 0 ? {} : { entryPhases: options.entryPhases },
6199
6256
  ...pinnedConfigToken === agent.currentConfigToken ? {} : { pinnedConfigToken },
6200
6257
  idempotencyKey: options.idempotencyKey
6201
6258
  });
@@ -6478,10 +6535,34 @@ var AgentRuntime = class _AgentRuntime {
6478
6535
  maxAttempts: config.maxAttempts,
6479
6536
  checkpoint: claim.run.checkpoint,
6480
6537
  ...claim.run.phasePayload === void 0 ? {} : { initialPhasePayload: claim.run.phasePayload },
6538
+ ...claim.run.entryPhases === void 0 ? {} : { entryPhases: claim.run.entryPhases },
6481
6539
  interactionAnswers: claim.run.interactionAnswers,
6482
6540
  signal: controller.signal,
6483
6541
  beforePhase: assembly.beforePhase,
6484
- afterPhase: assembly.afterPhase,
6542
+ afterPhase: async (phaseId, output) => {
6543
+ const extAfter = assembly.afterPhase ? await assembly.afterPhase(phaseId, output) : {};
6544
+ const effectiveOutput = extAfter.output ?? output;
6545
+ if (!extAfter.abort && (phaseId === COMPACT_PHASE_ID || isCompactionOutcome(effectiveOutput.payload))) {
6546
+ const active = this.executions.get(run.id);
6547
+ if (active?.executionId === claim.execution.executionId && !this.cancellationRequested.has(run.id) && !controller.signal.aborted) {
6548
+ const summary = compactSummary(effectiveOutput.payload);
6549
+ const instructions = compactInstructions2(run) ?? compactOutputInstructions(effectiveOutput.payload);
6550
+ if (summary) {
6551
+ const covered = claim.history.at(-1);
6552
+ const record = {
6553
+ id: createId("compact"),
6554
+ agentId: run.agentId,
6555
+ summary,
6556
+ ...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
6557
+ ...instructions ? { instructions } : {},
6558
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
6559
+ };
6560
+ await this.owned.commitContextCompaction(record);
6561
+ }
6562
+ }
6563
+ }
6564
+ return extAfter;
6565
+ },
6485
6566
  beforePrompt: assembly.beforePrompt,
6486
6567
  onPhaseEntered: async (phaseId) => {
6487
6568
  const active = this.executions.get(run.id);
@@ -6588,14 +6669,6 @@ var AgentRuntime = class _AgentRuntime {
6588
6669
  const compactResult = await executeModel(compactContext);
6589
6670
  const summary = compactResult.type === "completed" ? compactSummary(compactResult.outcome.payload) : void 0;
6590
6671
  if (summary) {
6591
- const covered = claim.history.at(-1);
6592
- await this.owned.commitContextCompaction({
6593
- id: createId("compact"),
6594
- agentId: run.agentId,
6595
- summary,
6596
- ...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
6597
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
6598
- });
6599
6672
  modelMessages = await this.owned.contextMessages(run.agentId);
6600
6673
  executionContext = buildExecutionContext(modelMessages);
6601
6674
  result = await executeModel(executionContext);
@@ -6644,22 +6717,6 @@ var AgentRuntime = class _AgentRuntime {
6644
6717
  }
6645
6718
  if (result.type === "completed") {
6646
6719
  const output = latestAssistant(run, result.messages, modelMessages.length);
6647
- if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
6648
- const summary = compactSummary(result.outcome.payload);
6649
- const instructions = compactInstructions2(run) ?? compactOutputInstructions(result.outcome.payload);
6650
- if (summary) {
6651
- const covered = claim.history.at(-1);
6652
- const record = {
6653
- id: createId("compact"),
6654
- agentId: run.agentId,
6655
- summary,
6656
- ...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
6657
- ...instructions ? { instructions } : {},
6658
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
6659
- };
6660
- await this.owned.commitContextCompaction(record);
6661
- }
6662
- }
6663
6720
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
6664
6721
  this.transientEvents.clear(run.id, claim.execution.executionId, true);
6665
6722
  return;
@@ -7877,10 +7934,14 @@ var InMemoryStore = class _InMemoryStore {
7877
7934
  createRun(lease, input) {
7878
7935
  this.assertOwner(lease);
7879
7936
  this.requireAgent(input.agentId);
7937
+ if (input.phasePayload !== void 0 && input.entryPhases !== void 0) {
7938
+ throw new TypeError("phasePayload and entryPhases are mutually exclusive");
7939
+ }
7880
7940
  const normalizedInput = normalizeUserInput(input.input);
7881
7941
  if (input.phasePayload !== void 0) assertJsonValue(input.phasePayload, "run.phasePayload");
7942
+ if (input.entryPhases !== void 0) assertEntryPhases(input.entryPhases, "run.entryPhases");
7882
7943
  const scope = createIdempotencyScope("start_run", input.agentId, input.idempotencyKey);
7883
- const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken);
7944
+ const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases);
7884
7945
  const replay = this.replay(scope, payload);
7885
7946
  if (replay) return clone(replay);
7886
7947
  const waitingRun = isControlMetadata(input.metadata) ? void 0 : [...this.runs.values()].find((candidate) => candidate.agentId === input.agentId && candidate.state === "input_required");
@@ -7936,6 +7997,7 @@ var InMemoryStore = class _InMemoryStore {
7936
7997
  input: clone(normalizedInput),
7937
7998
  ...input.metadata ? { metadata: clone(input.metadata) } : {},
7938
7999
  ...input.phasePayload === void 0 ? {} : { phasePayload: clone(input.phasePayload) },
8000
+ ...input.entryPhases === void 0 ? {} : { entryPhases: clone(input.entryPhases) },
7939
8001
  ...input.pinnedConfigToken === void 0 ? {} : { pinnedConfigToken: input.pinnedConfigToken },
7940
8002
  createdAt: timestamp,
7941
8003
  updatedAt: timestamp
@@ -8435,6 +8497,7 @@ var InMemoryStore = class _InMemoryStore {
8435
8497
  input: clone(run.input),
8436
8498
  ...run.metadata ? { metadata: clone(run.metadata) } : {},
8437
8499
  ...run.phasePayload === void 0 ? {} : { phasePayload: clone(run.phasePayload) },
8500
+ ...run.entryPhases === void 0 ? {} : { entryPhases: clone(run.entryPhases) },
8438
8501
  messageCount: this.messagesForRun(run.id).length,
8439
8502
  toolCallCount: [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id).length,
8440
8503
  ...run.currentPhaseId === void 0 ? {} : { currentPhaseId: run.currentPhaseId },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.14.3",
3
+ "version": "0.15.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",