@rowan-agent/agent 0.14.2 → 0.15.0

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 +112 -53
  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)) {
@@ -3337,7 +3357,8 @@ function encodeExecutionCheckpoint(state) {
3337
3357
  metrics,
3338
3358
  ...continuation ? { continuation } : {},
3339
3359
  ...runInteractions === void 0 ? {} : { runInteractions },
3340
- ...state.initialPhasePayload === void 0 ? {} : { initialPhasePayload: state.initialPhasePayload }
3360
+ ...state.initialPhasePayload === void 0 ? {} : { initialPhasePayload: state.initialPhasePayload },
3361
+ ...state.entryPhases === void 0 ? {} : { entryPhases: state.entryPhases }
3341
3362
  };
3342
3363
  assertJsonValue(data, "execution checkpoint");
3343
3364
  return {
@@ -3365,6 +3386,7 @@ function decodeExecutionCheckpoint(checkpoint) {
3365
3386
  },
3366
3387
  status: "suspended",
3367
3388
  ...checkpoint.data.initialPhasePayload === void 0 ? {} : { initialPhasePayload: checkpoint.data.initialPhasePayload },
3389
+ ...checkpoint.data.entryPhases === void 0 ? {} : { entryPhases: checkpoint.data.entryPhases },
3368
3390
  ...checkpoint.data.continuation ? {
3369
3391
  continuation: {
3370
3392
  ...checkpoint.data.continuation,
@@ -3401,7 +3423,8 @@ async function executeOnce(input) {
3401
3423
  attempt: 0,
3402
3424
  status: "running",
3403
3425
  metrics: createMetrics(),
3404
- ...input.initialPhasePayload === void 0 ? {} : { initialPhasePayload: input.initialPhasePayload }
3426
+ ...input.initialPhasePayload === void 0 ? {} : { initialPhasePayload: input.initialPhasePayload },
3427
+ ...input.entryPhases === void 0 ? {} : { entryPhases: input.entryPhases }
3405
3428
  };
3406
3429
  if (input.interactionAnswers) {
3407
3430
  const existing = state.runInteractions ?? state.phaseInteractions;
@@ -3498,11 +3521,16 @@ function isCheckpointData(value) {
3498
3521
  if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
3499
3522
  if (!isMetrics(value.metrics)) return false;
3500
3523
  if (value.initialPhasePayload !== void 0 && !isJsonValue(value.initialPhasePayload)) return false;
3524
+ if (value.entryPhases !== void 0 && !isEntryPhases(value.entryPhases)) return false;
3501
3525
  if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
3502
3526
  if (value.runInteractions !== void 0 && !isRunInteractionState(value.runInteractions)) return false;
3503
3527
  if (value.phaseInteractions !== void 0 && !isRunInteractionState(value.phaseInteractions)) return false;
3504
3528
  return true;
3505
3529
  }
3530
+ function isEntryPhases(value) {
3531
+ if (!Array.isArray(value) || value.length === 0) return false;
3532
+ return value.every((item) => isRecord2(item) && typeof item.phase === "string" && item.phase.trim().length > 0 && (item.payload === void 0 || isJsonValue(item.payload)));
3533
+ }
3506
3534
  function isRunInteractionState(value) {
3507
3535
  if (!isRecord2(value) || !Array.isArray(value.requests) || !isRecord2(value.answers)) return false;
3508
3536
  if (!Object.values(value.answers).every((answer) => isJsonValue(answer))) return false;
@@ -3569,6 +3597,19 @@ function isMetadata(value) {
3569
3597
  function assertMetadata(value, argument) {
3570
3598
  if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
3571
3599
  }
3600
+ function assertEntryPhases(value, argument = "entryPhases") {
3601
+ if (!Array.isArray(value) || value.length === 0) {
3602
+ throw new TypeError(`${argument} must be a non-empty array`);
3603
+ }
3604
+ for (const item of value) {
3605
+ if (!isRecord3(item) || typeof item.phase !== "string" || item.phase.trim() === "") {
3606
+ throw new TypeError(`${argument} items must contain a non-blank phase name`);
3607
+ }
3608
+ if (item.payload !== void 0) {
3609
+ assertJsonValue(item.payload, `${argument}.payload`);
3610
+ }
3611
+ }
3612
+ }
3572
3613
  function isText(value) {
3573
3614
  return isRecord3(value) && value.type === "text" && typeof value.text === "string";
3574
3615
  }
@@ -3763,14 +3804,16 @@ function encodeIdempotencyScope(storeIncarnation, scope) {
3763
3804
  if (typeof storeIncarnation !== "string" || storeIncarnation.length === 0) throw new TypeError("storeIncarnation must be non-empty");
3764
3805
  return canonicalJson([storeIncarnation, ...scope]);
3765
3806
  }
3766
- function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken) {
3807
+ function canonicalStartRunRequest(input, metadata, phasePayload, pinnedConfigToken, entryPhases) {
3767
3808
  const normalized = normalizeUserInput(input);
3768
3809
  if (metadata !== void 0) assertUtf8ByteLimit(canonicalJson(metadata), METADATA_JSON_BYTES, "run.metadata");
3769
3810
  if (phasePayload !== void 0) assertUtf8ByteLimit(canonicalJson(phasePayload), MESSAGE_CONTENT_JSON_BYTES, "run.phasePayload");
3811
+ if (entryPhases !== void 0) assertUtf8ByteLimit(canonicalJson(entryPhases), MESSAGE_CONTENT_JSON_BYTES, "run.entryPhases");
3770
3812
  return canonicalJson({
3771
3813
  input: normalized,
3772
3814
  ...metadata === void 0 ? {} : { metadata },
3773
3815
  ...phasePayload === void 0 ? {} : { phasePayload },
3816
+ ...entryPhases === void 0 ? {} : { entryPhases },
3774
3817
  ...pinnedConfigToken === void 0 ? {} : { pinnedConfigToken }
3775
3818
  });
3776
3819
  }
@@ -6165,6 +6208,12 @@ var AgentRuntime = class _AgentRuntime {
6165
6208
  }
6166
6209
  async start(agentId, input, options) {
6167
6210
  this.assertOpen();
6211
+ if (options.phasePayload !== void 0 && options.entryPhases !== void 0) {
6212
+ throw new TypeError("phasePayload and entryPhases are mutually exclusive");
6213
+ }
6214
+ if (options.entryPhases !== void 0) {
6215
+ assertEntryPhases(options.entryPhases);
6216
+ }
6168
6217
  const agent = await this.requireAgent(agentId);
6169
6218
  if (!agent.activatedAt || !agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
6170
6219
  let phasePayload;
@@ -6196,6 +6245,7 @@ var AgentRuntime = class _AgentRuntime {
6196
6245
  input,
6197
6246
  ...options.metadata === void 0 ? {} : { metadata: options.metadata },
6198
6247
  ...phasePayload === void 0 ? {} : { phasePayload },
6248
+ ...options.entryPhases === void 0 ? {} : { entryPhases: options.entryPhases },
6199
6249
  ...pinnedConfigToken === agent.currentConfigToken ? {} : { pinnedConfigToken },
6200
6250
  idempotencyKey: options.idempotencyKey
6201
6251
  });
@@ -6478,6 +6528,7 @@ var AgentRuntime = class _AgentRuntime {
6478
6528
  maxAttempts: config.maxAttempts,
6479
6529
  checkpoint: claim.run.checkpoint,
6480
6530
  ...claim.run.phasePayload === void 0 ? {} : { initialPhasePayload: claim.run.phasePayload },
6531
+ ...claim.run.entryPhases === void 0 ? {} : { entryPhases: claim.run.entryPhases },
6481
6532
  interactionAnswers: claim.run.interactionAnswers,
6482
6533
  signal: controller.signal,
6483
6534
  beforePhase: assembly.beforePhase,
@@ -6624,7 +6675,7 @@ var AgentRuntime = class _AgentRuntime {
6624
6675
  const isToolCallInteraction = result.interactions?.some((i) => i.toolCallId !== void 0);
6625
6676
  const output = isToolCallInteraction ? void 0 : latestAssistant(
6626
6677
  run,
6627
- result.messages.slice(modelMessages.length),
6678
+ result.messages,
6628
6679
  modelMessages.length
6629
6680
  );
6630
6681
  const prompt = isToolCallInteraction ? void 0 : output ?? (result.interactions[0]?.prompt ? promptMessage(run, result.interactions[0].prompt, result.messages.length) : void 0);
@@ -6643,7 +6694,7 @@ var AgentRuntime = class _AgentRuntime {
6643
6694
  return;
6644
6695
  }
6645
6696
  if (result.type === "completed") {
6646
- const output = latestAssistant(run, result.messages.slice(modelMessages.length), modelMessages.length);
6697
+ const output = latestAssistant(run, result.messages, modelMessages.length);
6647
6698
  if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
6648
6699
  const summary = compactSummary(result.outcome.payload);
6649
6700
  const instructions = compactInstructions2(run) ?? compactOutputInstructions(result.outcome.payload);
@@ -7344,10 +7395,10 @@ var DurableRun = class {
7344
7395
  function promptMessage(run, prompt, sequence) {
7345
7396
  return { id: createId("msg"), agentId: run.agentId, runId: run.id, role: "assistant", content: prompt, sequenceWithinRun: sequence, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
7346
7397
  }
7347
- function latestAssistant(run, messages, sequence, interrupted = false) {
7348
- const message = [...messages].reverse().find((candidate) => candidate.role === "assistant");
7398
+ function latestAssistant(run, messages, historyLength, interrupted = false) {
7399
+ const message = [...messages.slice(historyLength)].reverse().find((candidate) => candidate.role === "assistant");
7349
7400
  if (!message) return void 0;
7350
- return projectAssistantMessage(message, run.agentId, run.id, sequence, { interrupted });
7401
+ return projectAssistantMessage(message, run.agentId, run.id, historyLength, { interrupted });
7351
7402
  }
7352
7403
  function hasVisibleAssistantText(message) {
7353
7404
  return typeof message.content === "string" ? message.content.length > 0 : message.content.some((part) => part.type === "text" && part.text.length > 0);
@@ -7877,10 +7928,14 @@ var InMemoryStore = class _InMemoryStore {
7877
7928
  createRun(lease, input) {
7878
7929
  this.assertOwner(lease);
7879
7930
  this.requireAgent(input.agentId);
7931
+ if (input.phasePayload !== void 0 && input.entryPhases !== void 0) {
7932
+ throw new TypeError("phasePayload and entryPhases are mutually exclusive");
7933
+ }
7880
7934
  const normalizedInput = normalizeUserInput(input.input);
7881
7935
  if (input.phasePayload !== void 0) assertJsonValue(input.phasePayload, "run.phasePayload");
7936
+ if (input.entryPhases !== void 0) assertEntryPhases(input.entryPhases, "run.entryPhases");
7882
7937
  const scope = createIdempotencyScope("start_run", input.agentId, input.idempotencyKey);
7883
- const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken);
7938
+ const payload = canonicalStartRunRequest(normalizedInput, input.metadata, input.phasePayload, input.pinnedConfigToken, input.entryPhases);
7884
7939
  const replay = this.replay(scope, payload);
7885
7940
  if (replay) return clone(replay);
7886
7941
  const waitingRun = isControlMetadata(input.metadata) ? void 0 : [...this.runs.values()].find((candidate) => candidate.agentId === input.agentId && candidate.state === "input_required");
@@ -7936,6 +7991,7 @@ var InMemoryStore = class _InMemoryStore {
7936
7991
  input: clone(normalizedInput),
7937
7992
  ...input.metadata ? { metadata: clone(input.metadata) } : {},
7938
7993
  ...input.phasePayload === void 0 ? {} : { phasePayload: clone(input.phasePayload) },
7994
+ ...input.entryPhases === void 0 ? {} : { entryPhases: clone(input.entryPhases) },
7939
7995
  ...input.pinnedConfigToken === void 0 ? {} : { pinnedConfigToken: input.pinnedConfigToken },
7940
7996
  createdAt: timestamp,
7941
7997
  updatedAt: timestamp
@@ -8360,6 +8416,8 @@ var InMemoryStore = class _InMemoryStore {
8360
8416
  const run = this.requireRun(input.runId);
8361
8417
  if (input.expectedRevision !== void 0) this.assertRevision(run, input.expectedRevision);
8362
8418
  if (input.output !== void 0) {
8419
+ const existing = this.messages.get(input.output.id);
8420
+ if (existing && existing.runId !== run.id) throw new TypeError("cancelled output message id belongs to another Run");
8363
8421
  if (!isAssistantMessage(input.output) || input.output.agentId !== run.agentId || input.output.runId !== run.id || input.output.interrupted !== true) {
8364
8422
  throw new TypeError("cancelled output must be an interrupted AssistantMessage from this Run");
8365
8423
  }
@@ -8433,6 +8491,7 @@ var InMemoryStore = class _InMemoryStore {
8433
8491
  input: clone(run.input),
8434
8492
  ...run.metadata ? { metadata: clone(run.metadata) } : {},
8435
8493
  ...run.phasePayload === void 0 ? {} : { phasePayload: clone(run.phasePayload) },
8494
+ ...run.entryPhases === void 0 ? {} : { entryPhases: clone(run.entryPhases) },
8436
8495
  messageCount: this.messagesForRun(run.id).length,
8437
8496
  toolCallCount: [...this.toolCalls.values()].filter((toolCall) => toolCall.runId === run.id).length,
8438
8497
  ...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.2",
3
+ "version": "0.15.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",