@rowan-agent/agent 0.4.7 → 0.4.9

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
@@ -60,6 +60,8 @@ type AgentOptions = {
60
60
  context: AgentContext;
61
61
  model: LlmModelRef;
62
62
  stream: StreamFn;
63
+ cwd?: string;
64
+ rowanDir?: string; // project-local Rowan directory, default: ".rowan"
63
65
  sessionId?: string;
64
66
  phases?: PhaseRegistry;
65
67
  extensions?: ExtensionRunnerRef;
@@ -212,7 +214,7 @@ agent.subscribe((event: AgentEvent) => {
212
214
 
213
215
  ### Parallel Phase Events
214
216
 
215
- 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.
216
218
 
217
219
  ## Session
218
220
 
@@ -294,11 +296,11 @@ Per iteration:
294
296
  ┌──────────┐ ┌──────────┐
295
297
  │ lint │ │typecheck │
296
298
  └────┬─────┘ └────┬─────┘
297
- └─────────┬─────────┘
298
-
299
- <phase_results> merged
300
-
301
- route("stop")
299
+ └─────────┬─────────┘
300
+
301
+ merged into <prev_phase_outputs>
302
+
303
+ route("stop")
302
304
 
303
305
 
304
306
  ┌──────────┐
@@ -460,6 +462,8 @@ export default function myPlugin(rowan: ExtensionAPI) {
460
462
 
461
463
  Multi-provider model configuration via `.rowan/config.yaml`. Supports multiple API providers, per-model settings, environment variable interpolation, and per-phase model overrides.
462
464
 
465
+ Config is loaded from the runtime Rowan directory, which defaults to `.rowan` and can be set when constructing `Agent` via `rowanDir`.
466
+
463
467
  ### Config File
464
468
 
465
469
  Place `config.yaml` in your `.rowan/` directory (alongside `phases/`, `skills/`, etc.):
@@ -632,13 +636,16 @@ const request = buildModelRequest({ systemPrompt, messages, tools });
632
636
 
633
637
  ## Workspace
634
638
 
635
- Workspace resolution — dev mode uses the project root, packaged binary uses `~/.rowan`.
639
+ Workspace resolution uses the current project for both source and binary runs. The project Rowan directory defaults to `<cwd>/.rowan`; pass `rowanDir` to resolve another project-local directory.
636
640
 
637
641
  ```ts
638
642
  import { resolveWorkspacePaths, resolveInWorkspace } from "@rowan-agent/agent";
639
643
 
640
644
  const workspace = resolveWorkspacePaths();
641
645
  // → { mode: "source" | "binary", cwd: string, rowanDir: string }
646
+
647
+ const custom = resolveWorkspacePaths({ rowanDir: ".rowan-project" });
648
+ // → custom.rowanDir is <cwd>/.rowan-project
642
649
  ```
643
650
 
644
651
  ## Loop Metrics
@@ -59,17 +59,10 @@ function detectRuntimeMode(input = {}) {
59
59
  const executable = basename(input.execPath ?? process.execPath).toLowerCase().replace(/\.exe$/, "");
60
60
  return executable === "bun" ? "source" : "binary";
61
61
  }
62
- function defaultSourceStartDir(options) {
62
+ function defaultWorkspaceStartDir(options) {
63
63
  if (options.cwd) {
64
64
  return options.cwd;
65
65
  }
66
- const entrypoint = options.entrypoint ?? process.argv[1];
67
- if (entrypoint) {
68
- const entrypointPath = resolve(process.cwd(), entrypoint);
69
- if (existsSync(entrypointPath)) {
70
- return dirname(entrypointPath);
71
- }
72
- }
73
66
  return process.cwd();
74
67
  }
75
68
  function resolveWorkspaceRoot(options = {}) {
@@ -79,11 +72,7 @@ function resolveWorkspaceRoot(options = {}) {
79
72
  if (override) {
80
73
  return resolveUserPath(override, homeDir);
81
74
  }
82
- const mode = options.mode ?? detectRuntimeMode(options);
83
- if (mode === "binary") {
84
- return homeDir;
85
- }
86
- return findSourceWorkspaceRoot(defaultSourceStartDir(options));
75
+ return findSourceWorkspaceRoot(defaultWorkspaceStartDir(options));
87
76
  }
88
77
  function resolveWorkspacePaths(options = {}) {
89
78
  const mode = options.mode ?? detectRuntimeMode(options);
@@ -91,9 +80,16 @@ function resolveWorkspacePaths(options = {}) {
91
80
  return {
92
81
  mode,
93
82
  cwd,
94
- rowanDir: join(cwd, BINARY_WORKSPACE_DIR)
83
+ rowanDir: resolveProjectRowanDir(cwd, options.rowanDir)
95
84
  };
96
85
  }
86
+ function resolveProjectRowanDir(cwd, rowanDir = BINARY_WORKSPACE_DIR) {
87
+ const inputPath = rowanDir.trim() || BINARY_WORKSPACE_DIR;
88
+ if (isAbsolute(inputPath)) {
89
+ throw new Error(`Project Rowan dir must be a relative path: ${rowanDir}`);
90
+ }
91
+ return resolveWorkspacePath({ root: cwd }, inputPath).absolutePath;
92
+ }
97
93
  function resolveInWorkspace(path, rootOrPaths) {
98
94
  if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
99
95
  return resolveUserPath(path, homedir());
@@ -139,6 +135,7 @@ export {
139
135
  detectRuntimeMode,
140
136
  resolveWorkspaceRoot,
141
137
  resolveWorkspacePaths,
138
+ resolveProjectRowanDir,
142
139
  resolveInWorkspace,
143
140
  normalizeRelativePath,
144
141
  resolveWorkspacePath
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  resolveInWorkspace,
3
3
  resolveWorkspacePaths
4
- } from "./chunk-S7DI7HIF.js";
4
+ } from "./chunk-2FIII4GP.js";
5
5
 
6
6
  // src/harness/phases/loader.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -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,
@@ -370,11 +369,18 @@ var PHASE_DIR = "phases";
370
369
  async function loadPhase(input, workspace) {
371
370
  const resolved = resolveResourcePath(input, PHASE_DIR, PHASE_MARKER, workspace);
372
371
  const { frontmatter, body } = await loadMarkdown(resolved);
372
+ const id = inferResourceName(resolved, PHASE_MARKER);
373
+ if (!frontmatter.name) {
374
+ throw new Error(`Phase "${id}" at "${resolved}" is missing required field "name" in PHASE.md frontmatter.`);
375
+ }
376
+ if (!frontmatter.description) {
377
+ throw new Error(`Phase "${id}" at "${resolved}" is missing required field "description" in PHASE.md frontmatter.`);
378
+ }
373
379
  const baseDir = dirname2(resolved);
374
380
  const phase = {
375
- id: inferResourceName(resolved, PHASE_MARKER),
376
- name: frontmatter.name ?? inferResourceName(resolved, PHASE_MARKER),
377
- description: frontmatter.description ?? "",
381
+ id,
382
+ name: frontmatter.name,
383
+ description: frontmatter.description,
378
384
  tools: frontmatter.tools,
379
385
  skills: frontmatter.skills,
380
386
  target: frontmatter.target,
@@ -409,12 +415,16 @@ async function loadPhases(workspace, paths) {
409
415
  const phases = /* @__PURE__ */ new Map();
410
416
  if (paths && paths.length > 0) {
411
417
  for (const path of paths) {
412
- const phase = await loadPhase(path, workspace);
413
- phases.set(phase.id, phase);
418
+ try {
419
+ const phase = await loadPhase(path, workspace);
420
+ phases.set(phase.id, phase);
421
+ } catch (error) {
422
+ console.warn(`Failed to load phase "${path}":`, error);
423
+ }
414
424
  }
415
425
  return { phases, entryPhaseId: null };
416
426
  }
417
- const ws = workspace ?? (await import("./path-TJ2G5L4N.js")).resolveWorkspacePaths();
427
+ const ws = workspace ?? (await import("./path-XORRK2EC.js")).resolveWorkspacePaths();
418
428
  const phasesDir = join3(ws.rowanDir, PHASE_DIR);
419
429
  if (!existsSync3(phasesDir)) {
420
430
  return { phases, entryPhaseId: null };
@@ -486,7 +496,7 @@ export {
486
496
  buildSkillsDescription,
487
497
  formatResourceOutput,
488
498
  detectResourceType,
489
- buildPhaseResultMessage,
499
+ buildPhaseDirectiveMessage,
490
500
  loadPhase,
491
501
  readPhaseContent,
492
502
  loadPhases,
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(path_exports, {
41
41
  findSourceWorkspaceRoot: () => findSourceWorkspaceRoot,
42
42
  normalizeRelativePath: () => normalizeRelativePath,
43
43
  resolveInWorkspace: () => resolveInWorkspace,
44
+ resolveProjectRowanDir: () => resolveProjectRowanDir,
44
45
  resolveWorkspacePath: () => resolveWorkspacePath,
45
46
  resolveWorkspacePaths: () => resolveWorkspacePaths,
46
47
  resolveWorkspaceRoot: () => resolveWorkspaceRoot
@@ -98,17 +99,10 @@ function detectRuntimeMode(input = {}) {
98
99
  const executable = (0, import_node_path.basename)(input.execPath ?? process.execPath).toLowerCase().replace(/\.exe$/, "");
99
100
  return executable === "bun" ? "source" : "binary";
100
101
  }
101
- function defaultSourceStartDir(options) {
102
+ function defaultWorkspaceStartDir(options) {
102
103
  if (options.cwd) {
103
104
  return options.cwd;
104
105
  }
105
- const entrypoint = options.entrypoint ?? process.argv[1];
106
- if (entrypoint) {
107
- const entrypointPath = (0, import_node_path.resolve)(process.cwd(), entrypoint);
108
- if ((0, import_node_fs.existsSync)(entrypointPath)) {
109
- return (0, import_node_path.dirname)(entrypointPath);
110
- }
111
- }
112
106
  return process.cwd();
113
107
  }
114
108
  function resolveWorkspaceRoot(options = {}) {
@@ -118,11 +112,7 @@ function resolveWorkspaceRoot(options = {}) {
118
112
  if (override) {
119
113
  return resolveUserPath(override, homeDir);
120
114
  }
121
- const mode = options.mode ?? detectRuntimeMode(options);
122
- if (mode === "binary") {
123
- return homeDir;
124
- }
125
- return findSourceWorkspaceRoot(defaultSourceStartDir(options));
115
+ return findSourceWorkspaceRoot(defaultWorkspaceStartDir(options));
126
116
  }
127
117
  function resolveWorkspacePaths(options = {}) {
128
118
  const mode = options.mode ?? detectRuntimeMode(options);
@@ -130,9 +120,16 @@ function resolveWorkspacePaths(options = {}) {
130
120
  return {
131
121
  mode,
132
122
  cwd,
133
- rowanDir: (0, import_node_path.join)(cwd, BINARY_WORKSPACE_DIR)
123
+ rowanDir: resolveProjectRowanDir(cwd, options.rowanDir)
134
124
  };
135
125
  }
126
+ function resolveProjectRowanDir(cwd, rowanDir = BINARY_WORKSPACE_DIR) {
127
+ const inputPath = rowanDir.trim() || BINARY_WORKSPACE_DIR;
128
+ if ((0, import_node_path.isAbsolute)(inputPath)) {
129
+ throw new Error(`Project Rowan dir must be a relative path: ${rowanDir}`);
130
+ }
131
+ return resolveWorkspacePath({ root: cwd }, inputPath).absolutePath;
132
+ }
136
133
  function resolveInWorkspace(path, rootOrPaths) {
137
134
  if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
138
135
  return resolveUserPath(path, (0, import_node_os.homedir)());
@@ -523,26 +520,25 @@ ${indent}</${tag}>`;
523
520
  return `${indent}<${tag}>${escapeXml(String(val))}</${tag}>`;
524
521
  }).join("\n");
525
522
  }
526
- function buildPhaseResultMessage(phases, toolUseId, instruction) {
523
+ function buildPhaseDirectiveMessage(phase, output, toolUseId) {
527
524
  const parts = [];
528
- if (instruction) {
529
- parts.push(`<instruction>${instruction}</instruction>`);
530
- }
531
- parts.push(`<phase_results>`);
532
- for (const p of phases) {
533
- parts.push(` <phase name="${p.name}">`);
534
- if (p.content) {
535
- parts.push(` <content>${p.content}</content>`);
536
- }
537
- if (p.output !== void 0) {
538
- parts.push(` <output>${jsonToXml(p.output, 2)}</output>`);
539
- }
540
- if (!p.content && p.output === void 0) {
541
- 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>`);
542
538
  }
543
- parts.push(` </phase>`);
539
+ parts.push(` </prev_phase_outputs>`);
544
540
  }
545
- parts.push(`</phase_results>`);
541
+ parts.push(`</phase>`);
546
542
  return [{
547
543
  type: "tool_result",
548
544
  toolUseId,
@@ -567,11 +563,18 @@ __export(loader_exports, {
567
563
  async function loadPhase(input, workspace) {
568
564
  const resolved = resolveResourcePath(input, PHASE_DIR, PHASE_MARKER, workspace);
569
565
  const { frontmatter, body } = await loadMarkdown(resolved);
566
+ const id = inferResourceName(resolved, PHASE_MARKER);
567
+ if (!frontmatter.name) {
568
+ throw new Error(`Phase "${id}" at "${resolved}" is missing required field "name" in PHASE.md frontmatter.`);
569
+ }
570
+ if (!frontmatter.description) {
571
+ throw new Error(`Phase "${id}" at "${resolved}" is missing required field "description" in PHASE.md frontmatter.`);
572
+ }
570
573
  const baseDir = (0, import_node_path4.dirname)(resolved);
571
574
  const phase = {
572
- id: inferResourceName(resolved, PHASE_MARKER),
573
- name: frontmatter.name ?? inferResourceName(resolved, PHASE_MARKER),
574
- description: frontmatter.description ?? "",
575
+ id,
576
+ name: frontmatter.name,
577
+ description: frontmatter.description,
575
578
  tools: frontmatter.tools,
576
579
  skills: frontmatter.skills,
577
580
  target: frontmatter.target,
@@ -606,8 +609,12 @@ async function loadPhases(workspace, paths) {
606
609
  const phases = /* @__PURE__ */ new Map();
607
610
  if (paths && paths.length > 0) {
608
611
  for (const path of paths) {
609
- const phase = await loadPhase(path, workspace);
610
- phases.set(phase.id, phase);
612
+ try {
613
+ const phase = await loadPhase(path, workspace);
614
+ phases.set(phase.id, phase);
615
+ } catch (error) {
616
+ console.warn(`Failed to load phase "${path}":`, error);
617
+ }
611
618
  }
612
619
  return { phases, entryPhaseId: null };
613
620
  }
@@ -715,6 +722,7 @@ __export(index_exports, {
715
722
  loadConfigFile: () => loadConfigFile,
716
723
  loadExtensionFromFactory: () => loadExtensionFromFactory,
717
724
  loadExtensions: () => loadExtensions,
725
+ loadPhases: () => loadPhases,
718
726
  loadSkill: () => loadSkill,
719
727
  loadSkills: () => loadSkills,
720
728
  messageContentText: () => messageContentText,
@@ -1847,10 +1855,14 @@ function applyFirstDecision(route, output) {
1847
1855
  if (first.reason) output.routeReason = first.reason;
1848
1856
  if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
1849
1857
  }
1850
- function injectPhaseContent(phase, payload, messageManager) {
1858
+ function injectPhaseContent(phase, output, messageManager) {
1851
1859
  try {
1852
1860
  const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
1853
- 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
+ );
1854
1866
  const msgId = messageManager.start("tool", content, { phase: phase.id });
1855
1867
  messageManager.end(msgId);
1856
1868
  return msgId;
@@ -2024,6 +2036,8 @@ async function runPhaseLoop(config, state, registry) {
2024
2036
  let isContinuing = false;
2025
2037
  let previousPayload = void 0;
2026
2038
  let previousPhaseMsgId = void 0;
2039
+ let previousResults = [];
2040
+ let pendingInstruction = void 0;
2027
2041
  while (currentPhaseId) {
2028
2042
  await reloadPhases(registry);
2029
2043
  if (config.phases) {
@@ -2108,7 +2122,9 @@ async function runPhaseLoop(config, state, registry) {
2108
2122
  removePhaseMessage(config.context.messages, previousPhaseMsgId);
2109
2123
  previousPhaseMsgId = void 0;
2110
2124
  if (enteringNewPhase) {
2111
- previousPhaseMsgId = injectPhaseContent(phase, phaseContext.state.payload, messageManager);
2125
+ previousPhaseMsgId = injectPhaseContent(phase, { results: previousResults, instruction: pendingInstruction }, messageManager);
2126
+ previousResults = [];
2127
+ pendingInstruction = void 0;
2112
2128
  }
2113
2129
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2114
2130
  let output = await executePhase(runtime);
@@ -2151,21 +2167,15 @@ async function runPhaseLoop(config, state, registry) {
2151
2167
  if (!pt) continue;
2152
2168
  const instanceId = instanceIds[i];
2153
2169
  const context = pt.isolated ? [] : contextSnapshot;
2154
- const payload = target.payload !== void 0 ? normalizePayload(target.payload) : previousPayload;
2155
- 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);
2156
2172
  parallelTasks.set(instanceId, { promise, phaseId: target.phase });
2157
2173
  }
2158
2174
  const successfulResults = await waitForBackgroundTasks(parallelTasks);
2159
- if (successfulResults.length > 0) {
2160
- const phaseResults = successfulResults.map((r) => ({ name: r.phaseId, content: r.content, output: r.payload }));
2161
- const content = buildPhaseResultMessage(phaseResults, `phase_results`, routeDecision.instruction);
2162
- const msgId = messageManager.start("tool", content, { phase: "parallel_group" });
2163
- await messageManager.end(msgId);
2164
- previousPhaseMsgId = msgId;
2165
- }
2175
+ previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
2176
+ pendingInstruction = routeDecision.instruction;
2166
2177
  const entryPhaseId = phase.target ?? registry.entryPhaseId ?? "default";
2167
2178
  if (entryPhaseId === "stop") {
2168
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2169
2179
  return completeRun(config, state, createOutcome.default(output, config.context.messages));
2170
2180
  }
2171
2181
  currentPhaseId = entryPhaseId;
@@ -2195,6 +2205,7 @@ async function runPhaseLoop(config, state, registry) {
2195
2205
  ts: createTimestamp()
2196
2206
  });
2197
2207
  previousPayload = output.payload;
2208
+ previousResults = output.payload !== void 0 ? [{ name: phase.id, output: output.payload }] : [];
2198
2209
  currentPhaseId = targetPhaseId;
2199
2210
  }
2200
2211
  throw new Error("Phase machine exited without a stop or abort transition.");
@@ -2402,7 +2413,7 @@ function createPhaseExecution(config, state, allTools, phase, messageManager, to
2402
2413
  }
2403
2414
  };
2404
2415
  }
2405
- async function executeParallelPhase(config, state, registry, phase, parentPayload, context, availablePhases) {
2416
+ async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, currentPhaseId) {
2406
2417
  const messages = [...context];
2407
2418
  const allTools = buildToolsWithRouting(config, availablePhases);
2408
2419
  const phaseTools = phase.tools ? allTools.filter((t) => t.name === PhaseRouteTool || phase.tools.includes(t.name)) : allTools;
@@ -2417,19 +2428,23 @@ async function executeParallelPhase(config, state, registry, phase, parentPayloa
2417
2428
  current: phase.id,
2418
2429
  available: Array.from(registry.phases.keys()),
2419
2430
  iterations: 0,
2420
- payload: parentPayload
2431
+ payload
2421
2432
  }
2422
2433
  };
2423
2434
  const messageManager = createMessageManager({ messages }, config.emit, config.onMessage);
2424
- 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
+ );
2425
2440
  const toolExecutionManager = createToolExecutionManager(config.emit);
2426
2441
  const execution = createPhaseExecution(config, state, phaseTools, phase, messageManager, toolExecutionManager, registry);
2427
2442
  const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
2428
2443
  const output = await executePhase(runtime);
2429
2444
  if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
2430
2445
  const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
2431
- const payload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
2432
- 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 };
2433
2448
  }
2434
2449
  async function waitForBackgroundTasks(backgroundTasks) {
2435
2450
  const entries = Array.from(backgroundTasks.entries());
@@ -2438,7 +2453,7 @@ async function waitForBackgroundTasks(backgroundTasks) {
2438
2453
  for (let i = 0; i < results.length; i++) {
2439
2454
  const r = results[i];
2440
2455
  if (r.status === "fulfilled") {
2441
- successful.push(r.value);
2456
+ successful.push({ ...r.value, instanceId: entries[i][0] });
2442
2457
  }
2443
2458
  }
2444
2459
  backgroundTasks.clear();
@@ -2688,7 +2703,7 @@ var Agent = class {
2688
2703
  * - Merges with CLI-provided phases and skills
2689
2704
  */
2690
2705
  async discoverResources(context) {
2691
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd });
2706
+ const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
2692
2707
  const filePhases = await loadPhases(workspace);
2693
2708
  let phases;
2694
2709
  const providedPhases = this.options.phases;
@@ -2848,7 +2863,7 @@ ${additionalInstructions}` : content;
2848
2863
  * @returns Formatted phase content string, or empty string if not found
2849
2864
  */
2850
2865
  async phase(name) {
2851
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd });
2866
+ const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
2852
2867
  const { loadPhase: loadPhase2, readPhaseContent: readPhaseContent2 } = await Promise.resolve().then(() => (init_loader2(), loader_exports));
2853
2868
  try {
2854
2869
  const phase = await loadPhase2(name, workspace);
@@ -4198,13 +4213,19 @@ var ExtensionRunner = class {
4198
4213
  if (!registration.id) {
4199
4214
  throw new Error(`Phase registration requires an "id" field.`);
4200
4215
  }
4216
+ if (!registration.name) {
4217
+ throw new Error(`Phase registration "${registration.id}" requires a "name" field.`);
4218
+ }
4219
+ if (!registration.description) {
4220
+ throw new Error(`Phase registration "${registration.id}" requires a "description" field.`);
4221
+ }
4201
4222
  if (this.phases.has(registration.id)) {
4202
4223
  throw new Error(`Duplicate phase id: ${registration.id}`);
4203
4224
  }
4204
4225
  const definition = {
4205
4226
  id: registration.id,
4206
- name: registration.name ?? registration.id,
4207
- description: registration.description ?? "",
4227
+ name: registration.name,
4228
+ description: registration.description,
4208
4229
  run: registration.run,
4209
4230
  ...registration.model ? { model: registration.model } : {}
4210
4231
  };
@@ -4288,14 +4309,19 @@ function readManifestSync(dir) {
4288
4309
  }
4289
4310
  }
4290
4311
  function jitiAliases() {
4291
- return {
4292
- "@rowan-agent/agent": (0, import_node_url.fileURLToPath)(new URL("../index.ts", import_meta2.url)),
4293
- "@rowan-agent/models": (0, import_node_url.fileURLToPath)(new URL("../../../models/src/index.ts", import_meta2.url))
4294
- };
4312
+ const moduleUrl = import_meta2.url;
4313
+ if (!moduleUrl) {
4314
+ return {};
4315
+ }
4316
+ const agentSourcePath = (0, import_node_url.fileURLToPath)(new URL("../index.ts", moduleUrl));
4317
+ if (!(0, import_node_fs6.existsSync)(agentSourcePath)) {
4318
+ return {};
4319
+ }
4320
+ return { "@rowan-agent/agent": agentSourcePath };
4295
4321
  }
4296
4322
  var sharedJiti;
4297
4323
  function getJiti() {
4298
- sharedJiti ??= (0, import_jiti.createJiti)(import_meta2.url, {
4324
+ sharedJiti ??= (0, import_jiti.createJiti)(import_meta2.url || process.cwd(), {
4299
4325
  moduleCache: false,
4300
4326
  alias: jitiAliases()
4301
4327
  });
@@ -4398,6 +4424,9 @@ async function discoverAndLoadExtensions(cwd) {
4398
4424
  return loadExtensions(paths, cwd);
4399
4425
  }
4400
4426
 
4427
+ // src/index.ts
4428
+ init_loader2();
4429
+
4401
4430
  // src/harness/context/index.ts
4402
4431
  init_resource_formatter();
4403
4432
  // Annotate the CommonJS export names for ESM import in node:
@@ -4430,6 +4459,7 @@ init_resource_formatter();
4430
4459
  loadConfigFile,
4431
4460
  loadExtensionFromFactory,
4432
4461
  loadExtensions,
4462
+ loadPhases,
4433
4463
  loadSkill,
4434
4464
  loadSkills,
4435
4465
  messageContentText,
package/dist/index.d.cts CHANGED
@@ -1381,6 +1381,7 @@ type AgentOptions = {
1381
1381
  model: LlmModelRef;
1382
1382
  stream: StreamFn;
1383
1383
  cwd?: string;
1384
+ rowanDir?: string;
1384
1385
  phases?: PhaseRegistry;
1385
1386
  extensionRunnerRef?: ExtensionRunnerRef;
1386
1387
  sessionId?: string;
@@ -1700,6 +1701,7 @@ type ResolveWorkspaceOptions = {
1700
1701
  entrypoint?: string;
1701
1702
  homeDir?: string;
1702
1703
  mode?: RuntimeMode;
1704
+ rowanDir?: string;
1703
1705
  };
1704
1706
  declare function resolveWorkspacePaths(options?: ResolveWorkspaceOptions): WorkspacePaths;
1705
1707
  declare function resolveInWorkspace(path: string, rootOrPaths: string | Pick<WorkspacePaths, "cwd">): string;
@@ -1774,6 +1776,18 @@ declare class AgentEventStream extends EventStream<AgentEvent, AgentMessage[]> {
1774
1776
  constructor();
1775
1777
  }
1776
1778
 
1779
+ /**
1780
+ * Load all phases from .rowan/phases directory.
1781
+ *
1782
+ * Scans for subdirectories containing PHASE.md files.
1783
+ * Returns PhaseRegistry with entryPhaseId:
1784
+ * - null by default (caller must explicitly set to start from a specific phase)
1785
+ * - Set to a specific phase id to start from that phase
1786
+ *
1787
+ * When entryPhaseId is null, AgentLoop starts from "none" phase.
1788
+ */
1789
+ declare function loadPhases(workspace?: WorkspacePaths, paths?: string[]): Promise<PhaseRegistry>;
1790
+
1777
1791
  interface SystemPromptOptions {
1778
1792
  /** Base system prompt. */
1779
1793
  systemPrompt: string;
@@ -1827,4 +1841,4 @@ declare function buildModelRequest(input: ModelRequestInput, options?: {
1827
1841
  model?: LlmModelRef;
1828
1842
  }): LlmRequest;
1829
1843
 
1830
- export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRunnerRef, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, discoverAndLoadExtensions, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, loadExtensionFromFactory, loadExtensions, loadSkill, loadSkills, messageContentText, parseModelRef, registerConfigModels, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveSkillPath, resolveWorkspacePaths, serializeSkills };
1844
+ export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRunnerRef, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, discoverAndLoadExtensions, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, loadExtensionFromFactory, loadExtensions, loadPhases, loadSkill, loadSkills, messageContentText, parseModelRef, registerConfigModels, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveSkillPath, resolveWorkspacePaths, serializeSkills };
package/dist/index.d.ts CHANGED
@@ -1381,6 +1381,7 @@ type AgentOptions = {
1381
1381
  model: LlmModelRef;
1382
1382
  stream: StreamFn;
1383
1383
  cwd?: string;
1384
+ rowanDir?: string;
1384
1385
  phases?: PhaseRegistry;
1385
1386
  extensionRunnerRef?: ExtensionRunnerRef;
1386
1387
  sessionId?: string;
@@ -1700,6 +1701,7 @@ type ResolveWorkspaceOptions = {
1700
1701
  entrypoint?: string;
1701
1702
  homeDir?: string;
1702
1703
  mode?: RuntimeMode;
1704
+ rowanDir?: string;
1703
1705
  };
1704
1706
  declare function resolveWorkspacePaths(options?: ResolveWorkspaceOptions): WorkspacePaths;
1705
1707
  declare function resolveInWorkspace(path: string, rootOrPaths: string | Pick<WorkspacePaths, "cwd">): string;
@@ -1774,6 +1776,18 @@ declare class AgentEventStream extends EventStream<AgentEvent, AgentMessage[]> {
1774
1776
  constructor();
1775
1777
  }
1776
1778
 
1779
+ /**
1780
+ * Load all phases from .rowan/phases directory.
1781
+ *
1782
+ * Scans for subdirectories containing PHASE.md files.
1783
+ * Returns PhaseRegistry with entryPhaseId:
1784
+ * - null by default (caller must explicitly set to start from a specific phase)
1785
+ * - Set to a specific phase id to start from that phase
1786
+ *
1787
+ * When entryPhaseId is null, AgentLoop starts from "none" phase.
1788
+ */
1789
+ declare function loadPhases(workspace?: WorkspacePaths, paths?: string[]): Promise<PhaseRegistry>;
1790
+
1777
1791
  interface SystemPromptOptions {
1778
1792
  /** Base system prompt. */
1779
1793
  systemPrompt: string;
@@ -1827,4 +1841,4 @@ declare function buildModelRequest(input: ModelRequestInput, options?: {
1827
1841
  model?: LlmModelRef;
1828
1842
  }): LlmRequest;
1829
1843
 
1830
- export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRunnerRef, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, discoverAndLoadExtensions, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, loadExtensionFromFactory, loadExtensions, loadSkill, loadSkills, messageContentText, parseModelRef, registerConfigModels, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveSkillPath, resolveWorkspacePaths, serializeSkills };
1844
+ export { type AbortEvent, type AfterPhaseEvent, type AfterPhaseHookResult, type AfterPhaseResult, type AfterToolCallEvent, type AfterToolCallResult, Agent, type AgentConfigFile, type AgentContext, type AgentEndEvent, AgentEventStream, type AgentOptions, type AgentStartEvent, type AgentStatus, type BeforePhaseEvent, type BeforePhaseHookResult, type BeforePhaseResult, type BeforePromptEvent, type BeforePromptResult, type BeforeToolCallEvent, type BeforeToolCallResult, type EventBus, EventStream, type ExecOptions, type ExecResult, type ExecutionTurn, type Extension, type ExtensionAPI, type ExtensionContext, type ExtensionError, type ExtensionErrorListener, type ExtensionFactory, type ExtensionManifest, type ExtensionPackageManifest, ExtensionRunner, type ExtensionRunnerOptions, type ExtensionRunnerRef, type ExtensionRuntime, type ExtensionUtils, HookError, type HookEvent, type HookEventType, type HookHandler, type HookResultMap, HooksManager, type LoadExtensionsResult, type LoadedExtension, LocalJsonlSessionManager, type LoopMetrics, type MessageEndEvent, type MessageStartEvent, type MessageUpdateEvent, type ModelConfigFromFile, type ModelTranscript, type Phase, type PhaseContext, type PhaseDefinition, type PhaseExecution, type PhaseOutput, type PhaseRegistration, type PhaseRegistry, type PhaseRun, type PhaseState, type ProviderConfigFromFile, type QueueUpdateEvent, type RegisteredPhase, type RegisteredTool, type RunOptions, type RunResult, type SavePointEvent, type Session, type SessionListItem, type SettledEvent, type SourceInfo, type Tool, type ToolDefinition, type ToolExecutionEndEvent, type ToolExecutionResult, type ToolExecutionStartEvent, type ToolExecutionUpdateEvent, type TurnEndEvent, type TurnStartEvent, type WorkspacePaths, appendUserTurn, buildModelRequest, buildSystemPrompt, conversationMessages, createCoreTools, createEventBus, createExtension, createExtensionAPI, createExtensionRunner, createExtensionRuntime, createId, createMessage, createSession, createSourceInfo, createTimestamp, discoverAndLoadExtensions, getGlobalHooks, interpolateEnvVars, latestUserInput, loadConfigFile, loadExtensionFromFactory, loadExtensions, loadPhases, loadSkill, loadSkills, messageContentText, parseModelRef, registerConfigModels, resetGlobalHooks, resolveDefaultModel, resolveInWorkspace, resolveSkillPath, resolveWorkspacePaths, serializeSkills };
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,12 +16,12 @@ import {
16
16
  reloadPhases,
17
17
  resolveDefaultModel,
18
18
  resolveResourcePath
19
- } from "./chunk-AFRSS3C5.js";
19
+ } from "./chunk-FTIS43EE.js";
20
20
  import {
21
21
  normalizeRelativePath,
22
22
  resolveInWorkspace,
23
23
  resolveWorkspacePaths
24
- } from "./chunk-S7DI7HIF.js";
24
+ } from "./chunk-2FIII4GP.js";
25
25
 
26
26
  // src/utils.ts
27
27
  function createId(prefix) {
@@ -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();
@@ -1966,7 +1973,7 @@ var Agent = class {
1966
1973
  * - Merges with CLI-provided phases and skills
1967
1974
  */
1968
1975
  async discoverResources(context) {
1969
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd });
1976
+ const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
1970
1977
  const filePhases = await loadPhases(workspace);
1971
1978
  let phases;
1972
1979
  const providedPhases = this.options.phases;
@@ -2126,8 +2133,8 @@ ${additionalInstructions}` : content;
2126
2133
  * @returns Formatted phase content string, or empty string if not found
2127
2134
  */
2128
2135
  async phase(name) {
2129
- const workspace = resolveWorkspacePaths({ cwd: this.options.cwd });
2130
- const { loadPhase: loadPhase2, readPhaseContent: readPhaseContent2 } = await import("./loader-UKBWDLDZ.js");
2136
+ const workspace = resolveWorkspacePaths({ cwd: this.options.cwd, rowanDir: this.options.rowanDir });
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);
@@ -3471,13 +3478,19 @@ var ExtensionRunner = class {
3471
3478
  if (!registration.id) {
3472
3479
  throw new Error(`Phase registration requires an "id" field.`);
3473
3480
  }
3481
+ if (!registration.name) {
3482
+ throw new Error(`Phase registration "${registration.id}" requires a "name" field.`);
3483
+ }
3484
+ if (!registration.description) {
3485
+ throw new Error(`Phase registration "${registration.id}" requires a "description" field.`);
3486
+ }
3474
3487
  if (this.phases.has(registration.id)) {
3475
3488
  throw new Error(`Duplicate phase id: ${registration.id}`);
3476
3489
  }
3477
3490
  const definition = {
3478
3491
  id: registration.id,
3479
- name: registration.name ?? registration.id,
3480
- description: registration.description ?? "",
3492
+ name: registration.name,
3493
+ description: registration.description,
3481
3494
  run: registration.run,
3482
3495
  ...registration.model ? { model: registration.model } : {}
3483
3496
  };
@@ -3560,14 +3573,19 @@ function readManifestSync(dir) {
3560
3573
  }
3561
3574
  }
3562
3575
  function jitiAliases() {
3563
- return {
3564
- "@rowan-agent/agent": fileURLToPath(new URL("../index.ts", import.meta.url)),
3565
- "@rowan-agent/models": fileURLToPath(new URL("../../../models/src/index.ts", import.meta.url))
3566
- };
3576
+ const moduleUrl = import.meta.url;
3577
+ if (!moduleUrl) {
3578
+ return {};
3579
+ }
3580
+ const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
3581
+ if (!existsSync2(agentSourcePath)) {
3582
+ return {};
3583
+ }
3584
+ return { "@rowan-agent/agent": agentSourcePath };
3567
3585
  }
3568
3586
  var sharedJiti;
3569
3587
  function getJiti() {
3570
- sharedJiti ??= createJiti(import.meta.url, {
3588
+ sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
3571
3589
  moduleCache: false,
3572
3590
  alias: jitiAliases()
3573
3591
  });
@@ -3698,6 +3716,7 @@ export {
3698
3716
  loadConfigFile,
3699
3717
  loadExtensionFromFactory,
3700
3718
  loadExtensions,
3719
+ loadPhases,
3701
3720
  loadSkill,
3702
3721
  loadSkills,
3703
3722
  messageContentText,
@@ -3,8 +3,8 @@ import {
3
3
  loadPhases,
4
4
  readPhaseContent,
5
5
  reloadPhases
6
- } from "./chunk-AFRSS3C5.js";
7
- import "./chunk-S7DI7HIF.js";
6
+ } from "./chunk-FTIS43EE.js";
7
+ import "./chunk-2FIII4GP.js";
8
8
  export {
9
9
  loadPhase,
10
10
  loadPhases,
@@ -7,10 +7,11 @@ import {
7
7
  findSourceWorkspaceRoot,
8
8
  normalizeRelativePath,
9
9
  resolveInWorkspace,
10
+ resolveProjectRowanDir,
10
11
  resolveWorkspacePath,
11
12
  resolveWorkspacePaths,
12
13
  resolveWorkspaceRoot
13
- } from "./chunk-S7DI7HIF.js";
14
+ } from "./chunk-2FIII4GP.js";
14
15
  export {
15
16
  BINARY_WORKSPACE_DIR,
16
17
  PACKAGED_ENV,
@@ -20,6 +21,7 @@ export {
20
21
  findSourceWorkspaceRoot,
21
22
  normalizeRelativePath,
22
23
  resolveInWorkspace,
24
+ resolveProjectRowanDir,
23
25
  resolveWorkspacePath,
24
26
  resolveWorkspacePaths,
25
27
  resolveWorkspaceRoot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",