@rowan-agent/agent 0.9.15 → 0.9.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -886,6 +886,10 @@ interface ExtensionAPI {
886
886
  getNextPhase(): string | undefined;
887
887
  /** Get the message set by setMessage */
888
888
  getMessage(): string | undefined;
889
+ /** Phase Settings contributions registered by the Phase extension. */
890
+ settings: {
891
+ register(provider: PhaseSettingsProvider): void;
892
+ };
889
893
  };
890
894
  }
891
895
  /**
@@ -976,6 +980,85 @@ interface PhaseContext {
976
980
  /** Text to append after the system prompt */
977
981
  appendSystemPrompt?: string;
978
982
  }
983
+ /** JSON-safe structural defaults declared by a Phase's `input` mapping. */
984
+ type PhaseInput = Readonly<Record<string, JsonValue>>;
985
+ /** JSON-safe option metadata exposed by a Phase to a host Settings surface. */
986
+ interface PhaseSettingsOption {
987
+ value: string;
988
+ label: string;
989
+ description?: string;
990
+ disabled?: boolean;
991
+ }
992
+ interface PhaseSettingsBadge {
993
+ label: string;
994
+ tone?: "neutral" | "success" | "review" | "danger";
995
+ }
996
+ interface PhaseSettingsItem {
997
+ id: string;
998
+ title: string;
999
+ description?: string;
1000
+ badges?: readonly PhaseSettingsBadge[];
1001
+ }
1002
+ type PhaseSettingsControl = {
1003
+ type: "boolean";
1004
+ path: string;
1005
+ label: string;
1006
+ description?: string;
1007
+ } | {
1008
+ type: "number";
1009
+ path: string;
1010
+ label: string;
1011
+ description?: string;
1012
+ min?: number;
1013
+ max?: number;
1014
+ step?: number;
1015
+ unit?: string;
1016
+ scale?: number;
1017
+ } | {
1018
+ type: "select";
1019
+ path: string;
1020
+ label: string;
1021
+ description?: string;
1022
+ options: readonly PhaseSettingsOption[];
1023
+ } | {
1024
+ type: "text";
1025
+ path: string;
1026
+ label: string;
1027
+ description?: string;
1028
+ placeholder?: string;
1029
+ format?: "string" | "string-array" | "key-value";
1030
+ multiline?: boolean;
1031
+ } | {
1032
+ type: "collection";
1033
+ path: string;
1034
+ label: string;
1035
+ description?: string;
1036
+ items: readonly PhaseSettingsItem[];
1037
+ fields: readonly PhaseSettingsControl[];
1038
+ add?: {
1039
+ label: string;
1040
+ idLabel?: string;
1041
+ fields: readonly PhaseSettingsControl[];
1042
+ };
1043
+ removeLabel?: string;
1044
+ };
1045
+ interface PhaseSettingsSection {
1046
+ id: string;
1047
+ title: string;
1048
+ description?: string;
1049
+ controls: readonly PhaseSettingsControl[];
1050
+ }
1051
+ /** Declarative, host-neutral Settings surface contributed by a Phase. */
1052
+ interface PhaseSettingsDefinition {
1053
+ description?: string;
1054
+ sections: readonly PhaseSettingsSection[];
1055
+ }
1056
+ /** Host context is intentionally opaque beyond the effective configuration. */
1057
+ interface PhaseSettingsContext {
1058
+ configuration: Readonly<Record<string, unknown>>;
1059
+ metadata?: Readonly<Record<string, unknown>>;
1060
+ }
1061
+ type PhaseSettingsProvider = (context: PhaseSettingsContext) => PhaseSettingsDefinition | Promise<PhaseSettingsDefinition>;
979
1062
  /**
980
1063
  * Loaded Phase object
981
1064
  */
@@ -992,8 +1075,8 @@ interface Phase {
992
1075
  skills?: Skill[];
993
1076
  /** Forced next phase */
994
1077
  target?: string;
995
- /** Expected input fields (key → description) */
996
- input?: Record<string, string>;
1078
+ /** Structural JSON-safe input defaults */
1079
+ input?: PhaseInput;
997
1080
  /** If true, phase gets a fresh context when executed in parallel */
998
1081
  isolated?: boolean;
999
1082
  /** Path to PHASE.md file */
@@ -1008,7 +1091,7 @@ interface Phase {
1008
1091
  disableAutoInvocation?: boolean;
1009
1092
  /** Do not expose this Phase to direct user invocation. */
1010
1093
  disableImplicitInvocation?: boolean;
1011
- /** ExtensionAPI factory function (default export pattern) */
1094
+ /** ExtensionAPI factory function used for registration or programmatic execution. */
1012
1095
  factory?: (api: ExtensionAPI) => Promise<void>;
1013
1096
  /** Direct run function */
1014
1097
  run?: (context: PhaseContext, execution: PhaseExecution) => Promise<PhaseOutput | void>;
@@ -1517,6 +1600,7 @@ type InvocationCatalogEntry = Readonly<{
1517
1600
  kind: "phase" | "skill";
1518
1601
  name: string;
1519
1602
  description: string;
1603
+ input?: Phase["input"];
1520
1604
  core?: boolean;
1521
1605
  disableAutoInvocation: boolean;
1522
1606
  disableImplicitInvocation: boolean;
@@ -2645,6 +2729,12 @@ declare function loadSkills(targetPath: string): Promise<Skill[]>;
2645
2729
  * If a directory is provided, the loader reads its PHASE.md.
2646
2730
  */
2647
2731
  declare function loadPhase(targetPath: string): Promise<Phase>;
2732
+ /**
2733
+ * Collect and evaluate the Settings provider registered through the Phase's
2734
+ * ExtensionAPI namespace. Settings discovery never reads a direct module
2735
+ * export; the Phase contributes through `api.phase.settings.register()`.
2736
+ */
2737
+ declare function loadPhaseSettings(phase: Phase, context: PhaseSettingsContext): Promise<PhaseSettingsDefinition | undefined>;
2648
2738
  /**
2649
2739
  * Load all phases from the target directory.
2650
2740
  *
@@ -2698,4 +2788,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
2698
2788
 
2699
2789
  declare function createCoreTools(input?: CoreToolContext): Tool[];
2700
2790
 
2701
- export { type AfterToolCall, type AgentConfig, type AgentConfigRequest, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentResources, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, COMPACT_PHASE_ID, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type ContextCompactionRecord, type ContextStatus, type CoreToolContext, DEFAULT_PHASE_ID, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type FrontmatterResult, type HistorySeed, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type MessageRevised, type MessageRevisionResult, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecution, type PhaseExecutionIdentity, type PhaseInteraction, PhaseInteractionBoundary, PhaseInteractionCancelledError, type PhaseInteractionDriver, type PhaseInteractionKind, type PhaseInteractionState, type PhaseInteractionStatus, type PhaseInvocation, type PhaseMessageManager, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseState, type PhaseStatus, type PhaseStatusState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RetentionResult, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, STOP_PHASE_ID, type Skill, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult$1 as ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhases, loadSkill, loadSkills, materializeConfigurationSnapshot, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
2791
+ export { type AfterToolCall, type AgentConfig, type AgentConfigRequest, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentResources, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, COMPACT_PHASE_ID, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type ContextCompactionRecord, type ContextStatus, type CoreToolContext, DEFAULT_PHASE_ID, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type FrontmatterResult, type HistorySeed, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type MessageRevised, type MessageRevisionResult, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecution, type PhaseExecutionIdentity, type PhaseInput, type PhaseInteraction, PhaseInteractionBoundary, PhaseInteractionCancelledError, type PhaseInteractionDriver, type PhaseInteractionKind, type PhaseInteractionState, type PhaseInteractionStatus, type PhaseInvocation, type PhaseMessageManager, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseSettingsBadge, type PhaseSettingsContext, type PhaseSettingsControl, type PhaseSettingsDefinition, type PhaseSettingsItem, type PhaseSettingsOption, type PhaseSettingsProvider, type PhaseSettingsSection, type PhaseState, type PhaseStatus, type PhaseStatusState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RetentionResult, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, STOP_PHASE_ID, type Skill, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult$1 as ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhaseSettings, loadPhases, loadSkill, loadSkills, materializeConfigurationSnapshot, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { chmod, mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
3
3
  import { basename as basename2, dirname as dirname2, relative as relative2, resolve as resolve2 } from "path";
4
4
  import { tmpdir } from "os";
5
- import Type2 from "typebox";
6
- import Schema from "typebox/schema";
5
+ import Type3 from "typebox";
6
+ import Schema2 from "typebox/schema";
7
7
 
8
8
  // src/harness/context/resource-formatter.ts
9
9
  function escapeXml(value) {
@@ -95,6 +95,13 @@ function buildPhaseDirectiveMessage(phase, output) {
95
95
  const parts = [];
96
96
  parts.push(`<phase_content name="${escapeXml(phase.name)}">`);
97
97
  parts.push(phase.content);
98
+ if (output.payload !== void 0) {
99
+ const payload = jsonToXml(output.payload, 2);
100
+ parts.push(" <phase_input>");
101
+ if (payload) parts.push(payload);
102
+ else if (output.payload === null) parts.push(" null");
103
+ parts.push(" </phase_input>");
104
+ }
98
105
  if (output.results && output.results.length > 0 || output.instruction) {
99
106
  parts.push(` <prev_phase_outputs>`);
100
107
  if (output.instruction) {
@@ -161,15 +168,147 @@ function normalizeRelativePath(path) {
161
168
  }
162
169
 
163
170
  // src/harness/tools/route-tool.ts
171
+ import Type2 from "typebox";
172
+
173
+ // src/runtime/json.ts
174
+ function isPlainObject(value) {
175
+ const prototype = Object.getPrototypeOf(value);
176
+ return prototype === Object.prototype || prototype === null;
177
+ }
178
+ function hasOnlyDataProperties(value) {
179
+ return Reflect.ownKeys(value).every((key) => {
180
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
181
+ return Boolean(descriptor && "value" in descriptor);
182
+ });
183
+ }
184
+ function visit(value, seen) {
185
+ if (value === null || typeof value === "boolean" || typeof value === "string") return true;
186
+ if (typeof value === "number") return Number.isFinite(value);
187
+ if (typeof value !== "object" || seen.has(value)) return false;
188
+ seen.add(value);
189
+ try {
190
+ if (Array.isArray(value)) {
191
+ if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
192
+ if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
193
+ for (let index = 0; index < value.length; index += 1) {
194
+ if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
195
+ }
196
+ return true;
197
+ }
198
+ if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
199
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
200
+ if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
201
+ return Object.keys(value).every((key) => visit(value[key], seen));
202
+ } finally {
203
+ seen.delete(value);
204
+ }
205
+ }
206
+ function isJsonValue(value) {
207
+ return visit(value, /* @__PURE__ */ new Set());
208
+ }
209
+ function assertJsonValue(value, argument = "value") {
210
+ if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
211
+ }
212
+ function canonicalJson(value) {
213
+ assertJsonValue(value);
214
+ const encode = (candidate) => {
215
+ if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
216
+ if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
217
+ const object = candidate;
218
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
219
+ };
220
+ return encode(value);
221
+ }
222
+ function utf8ByteLength(value) {
223
+ return new TextEncoder().encode(value).byteLength;
224
+ }
225
+ function assertUtf8ByteLimit(value, limit, argument = "value") {
226
+ if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
227
+ }
228
+
229
+ // src/harness/phases/input.ts
164
230
  import Type from "typebox";
231
+ import Schema from "typebox/schema";
232
+ function parsePhaseInput(value) {
233
+ if (value === void 0) return void 0;
234
+ if (!isJsonValue(value) || value === null || Array.isArray(value)) {
235
+ throw new TypeError("input must be a JSON-safe object");
236
+ }
237
+ return value;
238
+ }
239
+ function phaseInputSchema(input) {
240
+ return schemaForObject(input);
241
+ }
242
+ function preparePhasePayload(input, payload) {
243
+ if (payload !== void 0 && !isJsonValue(payload)) {
244
+ throw new TypeError("Phase payload must be JSON-safe");
245
+ }
246
+ if (input === void 0 || payload === void 0) {
247
+ if (input === void 0) return payload;
248
+ return mergeDefaultObject(input, {});
249
+ }
250
+ const validator = Schema.Compile(phaseInputSchema(input));
251
+ if (!validator.Check(payload)) {
252
+ throw new TypeError("Phase payload does not match the Phase input definition");
253
+ }
254
+ return mergeDefaultObject(input, payload);
255
+ }
256
+ function schemaForValue(value) {
257
+ if (value === null) return Type.Unknown();
258
+ if (typeof value === "string") return Type.String();
259
+ if (typeof value === "boolean") return Type.Boolean();
260
+ if (typeof value === "number") return Type.Number();
261
+ if (Array.isArray(value)) {
262
+ if (value.length === 0) return Type.Array(Type.Unknown());
263
+ const schemas = value.map(schemaForValue);
264
+ const unique = new Map(schemas.map((schema) => [JSON.stringify(schema), schema]));
265
+ const item = unique.size === 1 ? [...unique.values()][0] : Type.Union([...unique.values()]);
266
+ return Type.Array(item);
267
+ }
268
+ if (!isJsonObject(value)) throw new TypeError("Phase input values must be JSON-safe");
269
+ return schemaForObject(value);
270
+ }
271
+ function schemaForObject(value) {
272
+ const keys = Object.keys(value);
273
+ if (keys.length === 0) return Type.Record(Type.String(), Type.Unknown());
274
+ const properties = Object.fromEntries(keys.map((key) => [key, schemaForValue(value[key])]));
275
+ return Type.Partial(Type.Object(properties), { additionalProperties: false });
276
+ }
277
+ function mergeDefaultObject(defaults, payload) {
278
+ const result = {};
279
+ for (const [key, value] of Object.entries(payload)) {
280
+ const defaultValue = defaults[key];
281
+ result[key] = defaultValue !== void 0 && isJsonObject(defaultValue) && isJsonObject(value) ? mergeDefaultObject(defaultValue, value) : cloneJsonValue(value);
282
+ }
283
+ for (const [key, value] of Object.entries(defaults)) {
284
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) {
285
+ result[key] = cloneJsonValue(value);
286
+ }
287
+ }
288
+ return result;
289
+ }
290
+ function cloneJsonValue(value) {
291
+ if (Array.isArray(value)) return value.map(cloneJsonValue);
292
+ if (isJsonObject(value)) {
293
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneJsonValue(item)]));
294
+ }
295
+ return value;
296
+ }
297
+ function isJsonObject(value) {
298
+ return value !== null && typeof value === "object" && !Array.isArray(value);
299
+ }
300
+
301
+ // src/harness/tools/route-tool.ts
165
302
  var PhaseRouteTool = "route";
166
303
  function buildPhaseEntry(p) {
167
304
  const entry = { name: p.name, description: p.description };
168
305
  if (p.tools && p.tools.length > 0) {
169
306
  entry.available_tools = p.tools.join(", ");
170
307
  }
171
- if (p.input && Object.keys(p.input).length > 0) {
172
- entry.required_input = Object.entries(p.input).map(([key, desc]) => `- ${key}: ${desc}`).join("\n");
308
+ if (p.input !== void 0) {
309
+ entry.payload_schema = JSON.stringify(phaseInputSchema(p.input));
310
+ } else {
311
+ entry.payload_schema = "any JSON-safe value";
173
312
  }
174
313
  return entry;
175
314
  }
@@ -203,14 +342,20 @@ function buildRouteDescription(availablePhases) {
203
342
  }
204
343
  function createRouteTool(availablePhases) {
205
344
  const routablePhases = availablePhases.filter(({ name }) => name !== "stop");
206
- const DecisionTarget = Type.Object({
207
- phase: Type.Union([
208
- ...routablePhases.map((p) => Type.Literal(p.name)),
209
- Type.Literal("stop")
210
- ]),
211
- reason: Type.Optional(Type.String({ description: "Brief reason for this decision" })),
212
- payload: Type.Optional(Type.Unknown({ description: "Structured input for the target phase" }))
213
- });
345
+ const decisionTargets = [
346
+ ...routablePhases.map((phase) => Type2.Object({
347
+ phase: Type2.Literal(phase.name),
348
+ reason: Type2.Optional(Type2.String({ description: "Brief reason for this decision" })),
349
+ payload: Type2.Optional(
350
+ phase.input === void 0 ? Type2.Unknown({ description: "Any JSON-safe value for the target phase" }) : phaseInputSchema(phase.input)
351
+ )
352
+ })),
353
+ Type2.Object({
354
+ phase: Type2.Literal("stop"),
355
+ reason: Type2.Optional(Type2.String({ description: "Brief reason for this decision" })),
356
+ payload: Type2.Optional(Type2.Unknown({ description: "Optional stop payload" }))
357
+ })
358
+ ];
214
359
  return {
215
360
  name: PhaseRouteTool,
216
361
  description: buildRouteDescription(availablePhases),
@@ -219,9 +364,9 @@ function createRouteTool(availablePhases) {
219
364
  "Do not call route together with ordinary tools.",
220
365
  "Use route(stop) only when the current user request or task is complete; the Stop Phase will provide the final user-facing conclusion."
221
366
  ],
222
- parameters: Type.Object({
223
- decision: Type.Array(DecisionTarget, { description: "Phase executions to start", minItems: 1 }),
224
- instruction: Type.Optional(Type.String({ description: "Overall instruction, passed as context" }))
367
+ parameters: Type2.Object({
368
+ decision: Type2.Array(Type2.Union(decisionTargets), { description: "Phase executions to start", minItems: 1 }),
369
+ instruction: Type2.Optional(Type2.String({ description: "Overall instruction, passed as context" }))
225
370
  }),
226
371
  // No-op: this tool is intercepted by phases, never executed via tool execution
227
372
  execute: async (_, context) => ({
@@ -274,37 +419,37 @@ var DEFAULT_MAX_READ_BYTES = 50 * 1024;
274
419
  var DEFAULT_MAX_READ_LINES = 2e3;
275
420
  var DEFAULT_BASH_TIMEOUT_MS = 3e4;
276
421
  var DEFAULT_MAX_BASH_OUTPUT_BYTES = 64e3;
277
- var ReadArgsSchema = Type2.Object({
278
- path: Type2.String({ description: "Path to the file to read." }),
279
- offset: Type2.Optional(Type2.Number({ description: "1-based line to start from." })),
280
- limit: Type2.Optional(Type2.Number({ description: "Maximum number of lines." }))
422
+ var ReadArgsSchema = Type3.Object({
423
+ path: Type3.String({ description: "Path to the file to read." }),
424
+ offset: Type3.Optional(Type3.Number({ description: "1-based line to start from." })),
425
+ limit: Type3.Optional(Type3.Number({ description: "Maximum number of lines." }))
281
426
  });
282
- var ReadArgsValidator = Schema.Compile(ReadArgsSchema);
283
- var WriteArgsSchema = Type2.Object({
284
- path: Type2.String({ description: "Path to the file to write." }),
285
- content: Type2.String({ description: "Complete file contents." })
427
+ var ReadArgsValidator = Schema2.Compile(ReadArgsSchema);
428
+ var WriteArgsSchema = Type3.Object({
429
+ path: Type3.String({ description: "Path to the file to write." }),
430
+ content: Type3.String({ description: "Complete file contents." })
286
431
  });
287
- var WriteArgsValidator = Schema.Compile(WriteArgsSchema);
288
- var EditArgsSchema = Type2.Object({
289
- path: Type2.String({ description: "Path to the file to edit." }),
290
- edits: Type2.Array(Type2.Object({
291
- oldText: Type2.String({ description: "Exact unique text to replace." }),
292
- newText: Type2.String({ description: "Replacement text." })
432
+ var WriteArgsValidator = Schema2.Compile(WriteArgsSchema);
433
+ var EditArgsSchema = Type3.Object({
434
+ path: Type3.String({ description: "Path to the file to edit." }),
435
+ edits: Type3.Array(Type3.Object({
436
+ oldText: Type3.String({ description: "Exact unique text to replace." }),
437
+ newText: Type3.String({ description: "Replacement text." })
293
438
  }), { description: "One or more non-overlapping replacements." })
294
439
  });
295
- var EditArgsValidator = Schema.Compile(EditArgsSchema);
296
- var BashArgsSchema = Type2.Object({
297
- command: Type2.String({ description: "Bash command to execute." }),
298
- timeout: Type2.Optional(Type2.Number({ description: "Timeout in seconds." }))
440
+ var EditArgsValidator = Schema2.Compile(EditArgsSchema);
441
+ var BashArgsSchema = Type3.Object({
442
+ command: Type3.String({ description: "Bash command to execute." }),
443
+ timeout: Type3.Optional(Type3.Number({ description: "Timeout in seconds." }))
299
444
  });
300
- var BashArgsValidator = Schema.Compile(BashArgsSchema);
445
+ var BashArgsValidator = Schema2.Compile(BashArgsSchema);
301
446
  var validatorCache = /* @__PURE__ */ new WeakMap();
302
447
  function validatorFor(schema) {
303
448
  const cached = validatorCache.get(schema);
304
449
  if (cached) {
305
450
  return cached;
306
451
  }
307
- const validator = Schema.Compile(schema);
452
+ const validator = Schema2.Compile(schema);
308
453
  validatorCache.set(schema, validator);
309
454
  return validator;
310
455
  }
@@ -1055,6 +1200,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1055
1200
  let outputPayload = phaseIn?.state?.payload;
1056
1201
  let nextPhase;
1057
1202
  let outputMessage;
1203
+ let settingsProvider;
1058
1204
  return {
1059
1205
  on: (eventType, handler) => {
1060
1206
  assertActive();
@@ -1115,65 +1261,22 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1115
1261
  nextPhase = id;
1116
1262
  },
1117
1263
  getNextPhase: () => nextPhase,
1118
- getMessage: () => outputMessage
1119
- }
1120
- };
1121
- }
1122
-
1123
- // src/runtime/json.ts
1124
- function isPlainObject(value) {
1125
- const prototype = Object.getPrototypeOf(value);
1126
- return prototype === Object.prototype || prototype === null;
1127
- }
1128
- function hasOnlyDataProperties(value) {
1129
- return Reflect.ownKeys(value).every((key) => {
1130
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
1131
- return Boolean(descriptor && "value" in descriptor);
1132
- });
1133
- }
1134
- function visit(value, seen) {
1135
- if (value === null || typeof value === "boolean" || typeof value === "string") return true;
1136
- if (typeof value === "number") return Number.isFinite(value);
1137
- if (typeof value !== "object" || seen.has(value)) return false;
1138
- seen.add(value);
1139
- try {
1140
- if (Array.isArray(value)) {
1141
- if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
1142
- if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
1143
- for (let index = 0; index < value.length; index += 1) {
1144
- if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
1264
+ getMessage: () => outputMessage,
1265
+ settings: {
1266
+ register: (provider) => {
1267
+ assertActive();
1268
+ if (typeof provider !== "function") {
1269
+ throw new Error("Phase Settings registration requires a provider function.");
1270
+ }
1271
+ if (settingsProvider) {
1272
+ throw new Error("A Phase may register only one Settings provider.");
1273
+ }
1274
+ settingsProvider = provider;
1275
+ options?.registerSettings?.(provider);
1276
+ }
1145
1277
  }
1146
- return true;
1147
1278
  }
1148
- if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
1149
- if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
1150
- if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
1151
- return Object.keys(value).every((key) => visit(value[key], seen));
1152
- } finally {
1153
- seen.delete(value);
1154
- }
1155
- }
1156
- function isJsonValue(value) {
1157
- return visit(value, /* @__PURE__ */ new Set());
1158
- }
1159
- function assertJsonValue(value, argument = "value") {
1160
- if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
1161
- }
1162
- function canonicalJson(value) {
1163
- assertJsonValue(value);
1164
- const encode = (candidate) => {
1165
- if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
1166
- if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
1167
- const object = candidate;
1168
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
1169
1279
  };
1170
- return encode(value);
1171
- }
1172
- function utf8ByteLength(value) {
1173
- return new TextEncoder().encode(value).byteLength;
1174
- }
1175
- function assertUtf8ByteLimit(value, limit, argument = "value") {
1176
- if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
1177
1280
  }
1178
1281
 
1179
1282
  // src/harness/phases/interactions.ts
@@ -1607,13 +1710,21 @@ async function loadPhase(targetPath) {
1607
1710
  warnResourceDiagnostics("phase", resolved, diagnostics);
1608
1711
  const baseDir = dirname4(resolved);
1609
1712
  const skills = await loadPhaseSkills(baseDir);
1713
+ let input;
1714
+ try {
1715
+ input = parsePhaseInput(metadata.input);
1716
+ } catch (error) {
1717
+ const message = error instanceof Error ? error.message : String(error);
1718
+ warnResourceDiagnostics("phase", resolved, [message]);
1719
+ throw new ResourceMetadataError("invalid_metadata", message);
1720
+ }
1610
1721
  const phase = {
1611
1722
  name,
1612
1723
  description: description.description,
1613
1724
  tools: definition.tools ? [...definition.tools] : void 0,
1614
1725
  skills,
1615
1726
  target: metadata.target,
1616
- input: metadata.input,
1727
+ ...input === void 0 ? {} : { input },
1617
1728
  isolated: metadata.isolated,
1618
1729
  filePath: resolved,
1619
1730
  baseDir,
@@ -1627,12 +1738,24 @@ async function loadPhase(targetPath) {
1627
1738
  const code = await loadPhaseCode(codePath);
1628
1739
  if (code.factory) {
1629
1740
  phase.factory = code.factory;
1630
- } else if (code.run) {
1741
+ }
1742
+ if (code.run) {
1631
1743
  phase.run = code.run;
1632
1744
  }
1633
1745
  }
1634
1746
  return phase;
1635
1747
  }
1748
+ async function loadPhaseSettings(phase, context) {
1749
+ if (!phase.factory) return void 0;
1750
+ let provider;
1751
+ const api = createExtensionAPI(void 0, {
1752
+ registerSettings: (candidate) => {
1753
+ provider = candidate;
1754
+ }
1755
+ });
1756
+ await phase.factory(api);
1757
+ return provider ? provider(context) : void 0;
1758
+ }
1636
1759
  async function loadPhaseSkills(baseDir) {
1637
1760
  const entries = await readdir2(baseDir, { withFileTypes: true });
1638
1761
  const skills = [];
@@ -1743,13 +1866,15 @@ async function loadPhaseCode(codePath) {
1743
1866
  moduleCache: false,
1744
1867
  tryNative: false
1745
1868
  });
1746
- const mod = await jiti.import(codePath, { default: true });
1747
- const fn = typeof mod === "function" ? mod : mod?.default;
1748
- if (typeof fn === "function") {
1749
- return { factory: fn };
1750
- }
1751
- if (typeof mod?.run === "function") {
1752
- return { run: mod.run };
1869
+ const mod = await jiti.import(codePath);
1870
+ const namespace = mod && typeof mod === "object" ? mod : {};
1871
+ const fn = typeof mod === "function" ? mod : namespace.default;
1872
+ const run = namespace.run;
1873
+ if (typeof fn === "function" || typeof run === "function") {
1874
+ return {
1875
+ ...typeof fn === "function" ? { factory: fn } : {},
1876
+ ...typeof run === "function" ? { run } : {}
1877
+ };
1753
1878
  }
1754
1879
  throw new Error(`Phase code at "${codePath}" must export a default function or a run() function.`);
1755
1880
  }
@@ -2163,9 +2288,21 @@ function resolveRouteDecision(route, registry) {
2163
2288
  if (requestedCount !== 1) return void 0;
2164
2289
  return { ...route, requestedCount };
2165
2290
  }
2166
- const decision = route.decision.filter(({ phase }) => registry.phases.has(phase));
2291
+ const decision = route.decision.flatMap((candidate) => {
2292
+ const target = registry.phases.get(candidate.phase);
2293
+ if (!target) return [];
2294
+ try {
2295
+ const payload = preparePhasePayload(target.input, normalizePayload(candidate.payload));
2296
+ return [{
2297
+ ...candidate,
2298
+ ...payload === void 0 ? {} : { payload }
2299
+ }];
2300
+ } catch {
2301
+ return [];
2302
+ }
2303
+ });
2167
2304
  if (decision.length === 0) return void 0;
2168
- return { ...route, decision, requestedCount };
2305
+ return { ...route, decision, requestedCount: decision.length };
2169
2306
  }
2170
2307
  function resolveModelRouteDecision(toolCalls, registry) {
2171
2308
  return resolveRouteDecision(toolCalls ? extractRouteCall(toolCalls) : void 0, registry);
@@ -2400,7 +2537,8 @@ async function runPhaseLoop(config, state, registry) {
2400
2537
  if (resumingSuspendedRun) {
2401
2538
  state.status = "running";
2402
2539
  }
2403
- let previousPayload = resumingSuspendedRun ? state.continuation?.previousPayload : void 0;
2540
+ const initialPayload = !resumingSuspendedRun ? config.execution.runMetadata?.phasePayload : void 0;
2541
+ let previousPayload = resumingSuspendedRun ? state.continuation?.previousPayload : initialPayload;
2404
2542
  let previousPhaseMsgId = resumingSuspendedRun ? state.continuation?.previousPhaseMessageId : void 0;
2405
2543
  let previousPhaseInputMsgId;
2406
2544
  let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
@@ -2491,6 +2629,9 @@ async function runPhaseLoop(config, state, registry) {
2491
2629
  if (!phase) {
2492
2630
  throw new Error(`Phase "${currentPhaseId}" not found`);
2493
2631
  }
2632
+ if (previousPayload !== void 0 || phase.input !== void 0) {
2633
+ previousPayload = preparePhasePayload(phase.input, previousPayload);
2634
+ }
2494
2635
  state.currentPhase = currentPhaseId;
2495
2636
  const allTools = buildToolsWithRouting(config, availablePhases);
2496
2637
  const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
@@ -2556,7 +2697,7 @@ async function runPhaseLoop(config, state, registry) {
2556
2697
  const lastMessageBeforePhase = config.context.messages.at(-1);
2557
2698
  previousPhaseMsgId = injectPhaseContent(
2558
2699
  phase,
2559
- { results: previousResults, instruction: pendingInstruction },
2700
+ { results: previousResults, instruction: pendingInstruction, payload: previousPayload },
2560
2701
  config.context.messages,
2561
2702
  phaseContext.messages
2562
2703
  );
@@ -2602,6 +2743,7 @@ async function runPhaseLoop(config, state, registry) {
2602
2743
  output = extAfter.output;
2603
2744
  if (hookHasRoute) {
2604
2745
  routeDecision = resolveHookRouteDecision(output, registry);
2746
+ if (routeDecision) applyFirstDecision(routeDecision, output);
2605
2747
  } else if (phase.run || phase.factory) {
2606
2748
  output = { ...output, route: "stop" };
2607
2749
  routeDecision = void 0;
@@ -2708,7 +2850,7 @@ async function runPhaseLoop(config, state, registry) {
2708
2850
  }
2709
2851
  currentPhaseId = STOP_PHASE_ID;
2710
2852
  previousPayload = output.payload;
2711
- previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
2853
+ previousResults = [];
2712
2854
  pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
2713
2855
  continue;
2714
2856
  }
@@ -2725,8 +2867,10 @@ async function runPhaseLoop(config, state, registry) {
2725
2867
  to: targetPhaseId,
2726
2868
  ts: createTimestamp()
2727
2869
  });
2728
- previousPayload = output.payload;
2729
- previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
2870
+ const targetPhase = registry.phases.get(targetPhaseId);
2871
+ const targetPayload = !phase.target && routeDecision ? routeDecision.decision[0]?.payload : output.payload;
2872
+ previousPayload = preparePhasePayload(targetPhase?.input, targetPayload);
2873
+ previousResults = [];
2730
2874
  pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
2731
2875
  currentPhaseId = targetPhaseId;
2732
2876
  }
@@ -2784,6 +2928,14 @@ async function withRetry(fn, options = {}) {
2784
2928
  }
2785
2929
  async function executePhase(ctx) {
2786
2930
  const { phase, config, execution, registry, context } = ctx;
2931
+ if (phase.run) {
2932
+ const output = resolvePhaseOutput(await phase.run(context, execution));
2933
+ output.phase = phase.name;
2934
+ if (output.message === "Phase completed.") {
2935
+ output.message = `${phase.name} phase completed.`;
2936
+ }
2937
+ return output;
2938
+ }
2787
2939
  if (phase.factory) {
2788
2940
  const api = createExtensionAPI(void 0, {
2789
2941
  registerPhase: async () => {
@@ -2822,14 +2974,6 @@ async function executePhase(ctx) {
2822
2974
  payload: api.phase.getPayload()
2823
2975
  };
2824
2976
  }
2825
- if (phase.run) {
2826
- const output = resolvePhaseOutput(await phase.run(context, execution));
2827
- output.phase = phase.name;
2828
- if (output.message === "Phase completed.") {
2829
- output.message = `${phase.name} phase completed.`;
2830
- }
2831
- return output;
2832
- }
2833
2977
  return executePhaseWithModel(ctx);
2834
2978
  }
2835
2979
  async function executeToolCall(input) {
@@ -2995,6 +3139,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
2995
3139
  }
2996
3140
  async function executeParallelPhase(config, state, registry, phase, payload, instruction, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
2997
3141
  const messages = [...context];
3142
+ const effectivePayload = preparePhasePayload(phase.input, payload);
2998
3143
  const allTools = buildToolsWithRouting(config, availablePhases);
2999
3144
  const phaseTools = selectPhaseTools(allTools, phase.tools, false);
3000
3145
  const phaseSkills = mergeSkills(config.context.skills, phase.skills);
@@ -3017,7 +3162,7 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
3017
3162
  current: phase.name,
3018
3163
  available: Array.from(registry.phases.keys()),
3019
3164
  iterations: 0,
3020
- payload
3165
+ payload: effectivePayload
3021
3166
  }
3022
3167
  };
3023
3168
  const messageManager = createMessageManager({ messages }, config.onMessage, config.onMessageDelta);
@@ -3025,7 +3170,8 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
3025
3170
  phase,
3026
3171
  {
3027
3172
  instruction,
3028
- results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : []
3173
+ payload: effectivePayload,
3174
+ results: []
3029
3175
  },
3030
3176
  messages
3031
3177
  );
@@ -5863,6 +6009,7 @@ var AgentRuntime = class _AgentRuntime {
5863
6009
  kind: "phase",
5864
6010
  name: phase.name,
5865
6011
  description: phase.description,
6012
+ ...phase.input === void 0 ? {} : { input: phase.input },
5866
6013
  ...phase.core ? { core: true } : {},
5867
6014
  disableAutoInvocation: phase.disableAutoInvocation ?? false,
5868
6015
  disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
@@ -8825,6 +8972,7 @@ export {
8825
8972
  isRuntimeError,
8826
8973
  loadExtensionsFromPath as loadExtensions,
8827
8974
  loadPhase,
8975
+ loadPhaseSettings,
8828
8976
  loadPhases,
8829
8977
  loadSkill,
8830
8978
  loadSkills,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",