@rowan-agent/agent 0.4.8 → 0.4.10

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
@@ -214,7 +214,7 @@ agent.subscribe((event: AgentEvent) => {
214
214
 
215
215
  ### Parallel Phase Events
216
216
 
217
- When multiple phases run concurrently (via multi-target `route`), each branch emits its own `turn_*`, `message_*`, and `tool_execution_*` events into the shared event stream — they are interleaved, not sequenced. Individual parallel phases do **not** emit `phase_start`/`phase_end`; those only fire for serial phases. After all branches complete, a merged `<phase_results>` message is injected and `message_start`/`message_end` fire for it.
217
+ When multiple phases run concurrently (via multi-target `route`), each branch emits its own `turn_*`, `message_*`, and `tool_execution_*` events into the shared event stream — they are interleaved, not sequenced. Individual parallel phases do **not** emit `phase_start`/`phase_end`; those only fire for serial phases. After all branches complete, their outputs are stashed and surfaced in the next iteration's phase entry message (under `<prev_phase_outputs>`); the `message_start`/`message_end` you observe for that entry message carry the merged results.
218
218
 
219
219
  ## Session
220
220
 
@@ -296,11 +296,11 @@ Per iteration:
296
296
  ┌──────────┐ ┌──────────┐
297
297
  │ lint │ │typecheck │
298
298
  └────┬─────┘ └────┬─────┘
299
- └─────────┬─────────┘
300
-
301
- <phase_results> merged
302
-
303
- route("stop")
299
+ └─────────┬─────────┘
300
+
301
+ merged into <prev_phase_outputs>
302
+
303
+ route("stop")
304
304
 
305
305
 
306
306
  ┌──────────┐
@@ -336,26 +336,25 @@ ${indent}</${tag}>`;
336
336
  return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
337
337
  }).join("\n");
338
338
  }
339
- function buildPhaseResultMessage(phases, toolUseId, instruction) {
339
+ function buildPhaseDirectiveMessage(phase, output, toolUseId) {
340
340
  const parts = [];
341
- if (instruction) {
342
- parts.push(`<instruction>${instruction}</instruction>`);
343
- }
344
- parts.push(`<phase_results>`);
345
- for (const p of phases) {
346
- parts.push(` <phase name="${p.name}">`);
347
- if (p.content) {
348
- parts.push(` <content>${p.content}</content>`);
341
+ parts.push(`<phase name="${escapeXml(phase.name)}">`);
342
+ parts.push(` <content>${phase.content}</content>`);
343
+ if (output.results && output.results.length > 0) {
344
+ parts.push(` <prev_phase_outputs>`);
345
+ if (output.instruction) {
346
+ parts.push(` <instruction>${escapeXml(output.instruction)}</instruction>`);
349
347
  }
350
- if (p.output !== void 0) {
351
- parts.push(` <output>${jsonToXml(p.output, 2)}</output>`);
352
- }
353
- if (!p.content && p.output === void 0) {
354
- parts.push(` <content>Phase "${p.name}" completed with no output.</content>`);
348
+ for (const r of output.results) {
349
+ parts.push(` <phase name="${escapeXml(r.name)}">`);
350
+ if (r.output !== void 0) {
351
+ parts.push(jsonToXml(r.output, 3));
352
+ }
353
+ parts.push(` </phase>`);
355
354
  }
356
- parts.push(` </phase>`);
355
+ parts.push(` </prev_phase_outputs>`);
357
356
  }
358
- parts.push(`</phase_results>`);
357
+ parts.push(`</phase>`);
359
358
  return [{
360
359
  type: "tool_result",
361
360
  toolUseId,
@@ -497,7 +496,7 @@ export {
497
496
  buildSkillsDescription,
498
497
  formatResourceOutput,
499
498
  detectResourceType,
500
- buildPhaseResultMessage,
499
+ buildPhaseDirectiveMessage,
501
500
  loadPhase,
502
501
  readPhaseContent,
503
502
  loadPhases,
package/dist/index.cjs CHANGED
@@ -520,26 +520,25 @@ ${indent}</${tag}>`;
520
520
  return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
521
521
  }).join("\n");
522
522
  }
523
- function buildPhaseResultMessage(phases, toolUseId, instruction) {
523
+ function buildPhaseDirectiveMessage(phase, output, toolUseId) {
524
524
  const parts = [];
525
- if (instruction) {
526
- parts.push(`<instruction>${instruction}</instruction>`);
527
- }
528
- parts.push(`<phase_results>`);
529
- for (const p of phases) {
530
- parts.push(` <phase name="${p.name}">`);
531
- if (p.content) {
532
- parts.push(` <content>${p.content}</content>`);
533
- }
534
- if (p.output !== void 0) {
535
- parts.push(` <output>${jsonToXml(p.output, 2)}</output>`);
536
- }
537
- if (!p.content && p.output === void 0) {
538
- parts.push(` <content>Phase "${p.name}" completed with no output.</content>`);
525
+ parts.push(`<phase name="${escapeXml(phase.name)}">`);
526
+ parts.push(` <content>${phase.content}</content>`);
527
+ if (output.results && output.results.length > 0) {
528
+ parts.push(` <prev_phase_outputs>`);
529
+ if (output.instruction) {
530
+ parts.push(` <instruction>${escapeXml(output.instruction)}</instruction>`);
531
+ }
532
+ for (const r of output.results) {
533
+ parts.push(` <phase name="${escapeXml(r.name)}">`);
534
+ if (r.output !== void 0) {
535
+ parts.push(jsonToXml(r.output, 3));
536
+ }
537
+ parts.push(` </phase>`);
539
538
  }
540
- parts.push(` </phase>`);
539
+ parts.push(` </prev_phase_outputs>`);
541
540
  }
542
- parts.push(`</phase_results>`);
541
+ parts.push(`</phase>`);
543
542
  return [{
544
543
  type: "tool_result",
545
544
  toolUseId,
@@ -1856,10 +1855,14 @@ function applyFirstDecision(route, output) {
1856
1855
  if (first.reason) output.routeReason = first.reason;
1857
1856
  if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
1858
1857
  }
1859
- function injectPhaseContent(phase, payload, messageManager) {
1858
+ function injectPhaseContent(phase, output, messageManager) {
1860
1859
  try {
1861
1860
  const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
1862
- const content = buildPhaseResultMessage([{ name: phase.name, content: phaseContent, output: payload }], `phase_${phase.id}`);
1861
+ const content = buildPhaseDirectiveMessage(
1862
+ { name: phase.id, content: phaseContent },
1863
+ output,
1864
+ `phase_${phase.id}`
1865
+ );
1863
1866
  const msgId = messageManager.start("tool", content, { phase: phase.id });
1864
1867
  messageManager.end(msgId);
1865
1868
  return msgId;
@@ -2033,6 +2036,8 @@ async function runPhaseLoop(config, state, registry) {
2033
2036
  let isContinuing = false;
2034
2037
  let previousPayload = void 0;
2035
2038
  let previousPhaseMsgId = void 0;
2039
+ let previousResults = [];
2040
+ let pendingInstruction = void 0;
2036
2041
  while (currentPhaseId) {
2037
2042
  await reloadPhases(registry);
2038
2043
  if (config.phases) {
@@ -2117,7 +2122,9 @@ async function runPhaseLoop(config, state, registry) {
2117
2122
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
2118
2123
  previousPhaseMsgId = void 0;
2119
2124
  if (enteringNewPhase) {
2120
- previousPhaseMsgId = injectPhaseContent(phase, phaseContext.state.payload, messageManager);
2125
+ previousPhaseMsgId = injectPhaseContent(phase, { results: previousResults, instruction: pendingInstruction }, messageManager);
2126
+ previousResults = [];
2127
+ pendingInstruction = void 0;
2121
2128
  }
2122
2129
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2123
2130
  let output = await executePhase(runtime);
@@ -2160,21 +2167,15 @@ async function runPhaseLoop(config, state, registry) {
2160
2167
  if (!pt) continue;
2161
2168
  const instanceId = instanceIds[i];
2162
2169
  const context = pt.isolated ? [] : contextSnapshot;
2163
- const payload = target.payload !== void 0 ? normalizePayload(target.payload) : previousPayload;
2164
- const promise = executeParallelPhase(config, state, registry, pt, payload, context, availablePhases);
2170
+ const payload = target.payload !== void 0 ? normalizePayload(target.payload) : void 0;
2171
+ const promise = executeParallelPhase(config, state, registry, pt, payload, context, availablePhases, currentPhaseId);
2165
2172
  parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2166
2173
  }
2167
2174
  const successfulResults = await waitForBackgroundTasks(parallelTasks);
2168
- if (successfulResults.length > 0) {
2169
- const phaseResults = successfulResults.map((r) => ({ name: r.phaseId, content: r.content, output: r.payload }));
2170
- const content = buildPhaseResultMessage(phaseResults, `phase_results`, routeDecision.instruction);
2171
- const msgId = messageManager.start("tool", content, { phase: "parallel_group" });
2172
- await messageManager.end(msgId);
2173
- previousPhaseMsgId = msgId;
2174
- }
2175
+ previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2176
+ pendingInstruction = routeDecision.instruction;
2175
2177
  const entryPhaseId = phase.target ?? registry.entryPhaseId ?? "default";
2176
2178
  if (entryPhaseId === "stop") {
2177
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2178
2179
  return completeRun(config, state, createOutcome.default(output, config.context.messages));
2179
2180
  }
2180
2181
  currentPhaseId = entryPhaseId;
@@ -2204,6 +2205,7 @@ async function runPhaseLoop(config, state, registry) {
2204
2205
  ts: createTimestamp()
2205
2206
  });
2206
2207
  previousPayload = output.payload;
2208
+ previousResults = output.payload !== void 0 ? [{ name: phase.id, output: output.payload }] : [];
2207
2209
  currentPhaseId = targetPhaseId;
2208
2210
  }
2209
2211
  throw new Error("Phase machine exited without a stop or abort transition.");
@@ -2411,7 +2413,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
2411
2413
  }
2412
2414
  };
2413
2415
  }
2414
- async function executeParallelPhase(config, state, registry, phase, parentPayload, context, availablePhases) {
2416
+ async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, currentPhaseId) {
2415
2417
  const messages = [...context];
2416
2418
  const allTools = buildToolsWithRouting(config, availablePhases);
2417
2419
  const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
@@ -2426,19 +2428,23 @@ async function executeParallelPhase(config, state, registry, phase, parentPayloa
2426
2428
  current: phase.id,
2427
2429
  available: Array.from(registry.phases.keys()),
2428
2430
  iterations: 0,
2429
- payload: parentPayload
2431
+ payload
2430
2432
  }
2431
2433
  };
2432
2434
  const messageManager = createMessageManager({ messages }, config.emit, config.onMessage);
2433
- const phaseMsgId = phase.isolated ? void 0 : injectPhaseContent(phase, parentPayload, messageManager);
2435
+ const phaseMsgId = phase.isolated ? void 0 : injectPhaseContent(
2436
+ phase,
2437
+ { results: payload !== void 0 ? [{ name: currentPhaseId, output: payload }] : [] },
2438
+ messageManager
2439
+ );
2434
2440
  const toolExecutionManager = createToolExecutionManager(config.emit);
2435
2441
  const execution = createPhaseExecution(config, state, phaseTools, phase, messageManager, toolExecutionManager, registry);
2436
2442
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2437
2443
  const output = await executePhase(runtime);
2438
2444
  if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
2439
2445
  const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
2440
- const payload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2441
- return { phaseId: phase.id, payload, content: output.message };
2446
+ const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2447
+ return { instanceId: "", phaseId: phase.id, payload: resultPayload, content: output.message };
2442
2448
  }
2443
2449
  async function waitForBackgroundTasks(backgroundTasks) {
2444
2450
  const entries = Array.from(backgroundTasks.entries());
@@ -2447,7 +2453,7 @@ async function waitForBackgroundTasks(backgroundTasks) {
2447
2453
  for (let i = 0; i < results.length; i++) {
2448
2454
  const r = results[i];
2449
2455
  if (r.status === "fulfilled") {
2450
- successful.push(r.value);
2456
+ successful.push({ ...r.value, instanceId: entries[i][0] });
2451
2457
  }
2452
2458
  }
2453
2459
  backgroundTasks.clear();
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import {
2
- buildPhaseResultMessage,
2
+ buildPhaseDirectiveMessage,
3
3
  buildSkillsDescription,
4
4
  buildStructuredSection,
5
5
  detectResourceType,
@@ -16,7 +16,7 @@ import {
16
16
  reloadPhases,
17
17
  resolveDefaultModel,
18
18
  resolveResourcePath
19
- } from "./chunk-APSIAOUD.js";
19
+ } from "./chunk-FTIS43EE.js";
20
20
  import {
21
21
  normalizeRelativePath,
22
22
  resolveInWorkspace,
@@ -1132,10 +1132,14 @@ function applyFirstDecision(route, output) {
1132
1132
  if (first.reason) output.routeReason = first.reason;
1133
1133
  if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
1134
1134
  }
1135
- function injectPhaseContent(phase, payload, messageManager) {
1135
+ function injectPhaseContent(phase, output, messageManager) {
1136
1136
  try {
1137
1137
  const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
1138
- const content = buildPhaseResultMessage([{ name: phase.name, content: phaseContent, output: payload }], `phase_${phase.id}`);
1138
+ const content = buildPhaseDirectiveMessage(
1139
+ { name: phase.id, content: phaseContent },
1140
+ output,
1141
+ `phase_${phase.id}`
1142
+ );
1139
1143
  const msgId = messageManager.start("tool", content, { phase: phase.id });
1140
1144
  messageManager.end(msgId);
1141
1145
  return msgId;
@@ -1309,6 +1313,8 @@ async function runPhaseLoop(config, state, registry) {
1309
1313
  let isContinuing = false;
1310
1314
  let previousPayload = void 0;
1311
1315
  let previousPhaseMsgId = void 0;
1316
+ let previousResults = [];
1317
+ let pendingInstruction = void 0;
1312
1318
  while (currentPhaseId) {
1313
1319
  await reloadPhases(registry);
1314
1320
  if (config.phases) {
@@ -1393,7 +1399,9 @@ async function runPhaseLoop(config, state, registry) {
1393
1399
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
1394
1400
  previousPhaseMsgId = void 0;
1395
1401
  if (enteringNewPhase) {
1396
- previousPhaseMsgId = injectPhaseContent(phase, phaseContext.state.payload, messageManager);
1402
+ previousPhaseMsgId = injectPhaseContent(phase, { results: previousResults, instruction: pendingInstruction }, messageManager);
1403
+ previousResults = [];
1404
+ pendingInstruction = void 0;
1397
1405
  }
1398
1406
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
1399
1407
  let output = await executePhase(runtime);
@@ -1436,21 +1444,15 @@ async function runPhaseLoop(config, state, registry) {
1436
1444
  if (!pt) continue;
1437
1445
  const instanceId = instanceIds[i];
1438
1446
  const context = pt.isolated ? [] : contextSnapshot;
1439
- const payload = target.payload !== void 0 ? normalizePayload(target.payload) : previousPayload;
1440
- const promise = executeParallelPhase(config, state, registry, pt, payload, context, availablePhases);
1447
+ const payload = target.payload !== void 0 ? normalizePayload(target.payload) : void 0;
1448
+ const promise = executeParallelPhase(config, state, registry, pt, payload, context, availablePhases, currentPhaseId);
1441
1449
  parallelTasks.set(instanceId, { promise, phaseId: target.phase });
1442
1450
  }
1443
1451
  const successfulResults = await waitForBackgroundTasks(parallelTasks);
1444
- if (successfulResults.length > 0) {
1445
- const phaseResults = successfulResults.map((r) => ({ name: r.phaseId, content: r.content, output: r.payload }));
1446
- const content = buildPhaseResultMessage(phaseResults, `phase_results`, routeDecision.instruction);
1447
- const msgId = messageManager.start("tool", content, { phase: "parallel_group" });
1448
- await messageManager.end(msgId);
1449
- previousPhaseMsgId = msgId;
1450
- }
1452
+ previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
1453
+ pendingInstruction = routeDecision.instruction;
1451
1454
  const entryPhaseId = phase.target ?? registry.entryPhaseId ?? "default";
1452
1455
  if (entryPhaseId === "stop") {
1453
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
1454
1456
  return completeRun(config, state, createOutcome.default(output, config.context.messages));
1455
1457
  }
1456
1458
  currentPhaseId = entryPhaseId;
@@ -1480,6 +1482,7 @@ async function runPhaseLoop(config, state, registry) {
1480
1482
  ts: createTimestamp()
1481
1483
  });
1482
1484
  previousPayload = output.payload;
1485
+ previousResults = output.payload !== void 0 ? [{ name: phase.id, output: output.payload }] : [];
1483
1486
  currentPhaseId = targetPhaseId;
1484
1487
  }
1485
1488
  throw new Error("Phase machine exited without a stop or abort transition.");
@@ -1687,7 +1690,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
1687
1690
  }
1688
1691
  };
1689
1692
  }
1690
- async function executeParallelPhase(config, state, registry, phase, parentPayload, context, availablePhases) {
1693
+ async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, currentPhaseId) {
1691
1694
  const messages = [...context];
1692
1695
  const allTools = buildToolsWithRouting(config, availablePhases);
1693
1696
  const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
@@ -1702,19 +1705,23 @@ async function executeParallelPhase(config, state, registry, phase, parentPayloa
1702
1705
  current: phase.id,
1703
1706
  available: Array.from(registry.phases.keys()),
1704
1707
  iterations: 0,
1705
- payload: parentPayload
1708
+ payload
1706
1709
  }
1707
1710
  };
1708
1711
  const messageManager = createMessageManager({ messages }, config.emit, config.onMessage);
1709
- const phaseMsgId = phase.isolated ? void 0 : injectPhaseContent(phase, parentPayload, messageManager);
1712
+ const phaseMsgId = phase.isolated ? void 0 : injectPhaseContent(
1713
+ phase,
1714
+ { results: payload !== void 0 ? [{ name: currentPhaseId, output: payload }] : [] },
1715
+ messageManager
1716
+ );
1710
1717
  const toolExecutionManager = createToolExecutionManager(config.emit);
1711
1718
  const execution = createPhaseExecution(config, state, phaseTools, phase, messageManager, toolExecutionManager, registry);
1712
1719
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
1713
1720
  const output = await executePhase(runtime);
1714
1721
  if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
1715
1722
  const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
1716
- const payload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
1717
- return { phaseId: phase.id, payload, content: output.message };
1723
+ const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
1724
+ return { instanceId: "", phaseId: phase.id, payload: resultPayload, content: output.message };
1718
1725
  }
1719
1726
  async function waitForBackgroundTasks(backgroundTasks) {
1720
1727
  const entries = Array.from(backgroundTasks.entries());
@@ -1723,7 +1730,7 @@ async function waitForBackgroundTasks(backgroundTasks) {
1723
1730
  for (let i = 0; i < results.length; i++) {
1724
1731
  const r = results[i];
1725
1732
  if (r.status === "fulfilled") {
1726
- successful.push(r.value);
1733
+ successful.push({ ...r.value, instanceId: entries[i][0] });
1727
1734
  }
1728
1735
  }
1729
1736
  backgroundTasks.clear();
@@ -2127,7 +2134,7 @@ ${additionalInstructions}` : content;
2127
2134
  */
2128
2135
  async phase(name) {
2129
2136
  const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
2130
- const { loadPhase: loadPhase2, readPhaseContent: readPhaseContent2 } = await import("./loader-MVD5JAKM.js");
2137
+ const { loadPhase: loadPhase2, readPhaseContent: readPhaseContent2 } = await import("./loader-HCYCHPGT.js");
2131
2138
  try {
2132
2139
  const phase = await loadPhase2(name, workspace);
2133
2140
  return readPhaseContent2(phase);
@@ -3,7 +3,7 @@ import {
3
3
  loadPhases,
4
4
  readPhaseContent,
5
5
  reloadPhases
6
- } from "./chunk-APSIAOUD.js";
6
+ } from "./chunk-FTIS43EE.js";
7
7
  import "./chunk-2FIII4GP.js";
8
8
  export {
9
9
  loadPhase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "build": "tsup"
26
26
  },
27
27
  "dependencies": {
28
- "@rowan-agent/models": "workspace:*",
28
+ "@rowan-agent/models": "0.4.10",
29
29
  "jiti": "2.7.0",
30
30
  "typebox": "^1.0.0",
31
31
  "yaml": "^2.7.0"