@rowan-agent/agent 0.9.4 → 0.9.5
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 +3 -3
- package/dist/index.js +183 -80
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -455,11 +455,11 @@ type ExtensionDisposer = () => void | Promise<void>;
|
|
|
455
455
|
type ExtensionFactoryResult = void | ExtensionDisposer;
|
|
456
456
|
type ExtensionFactory = (api: ExtensionAPI) => ExtensionFactoryResult | Promise<ExtensionFactoryResult>;
|
|
457
457
|
|
|
458
|
-
/** Unified phase output — model
|
|
458
|
+
/** Unified phase output — model-driven routing is optional; programmatic phases may normalize omission to stop. */
|
|
459
459
|
type PhaseOutput = {
|
|
460
460
|
message: string;
|
|
461
|
-
/** Route to next phase, or "continue" to re-execute current phase, or "stop" to end */
|
|
462
|
-
route
|
|
461
|
+
/** Route to next phase, or "continue" to re-execute current phase, or "stop" to end. Undefined means no model route. */
|
|
462
|
+
route?: string;
|
|
463
463
|
/** Phase name that produced this output */
|
|
464
464
|
phase?: string;
|
|
465
465
|
/** Tool calls from the model invocation (used by framework for route extraction) */
|
package/dist/index.js
CHANGED
|
@@ -91,12 +91,12 @@ function buildPhaseDirectiveMessage(phase, output) {
|
|
|
91
91
|
const parts = [];
|
|
92
92
|
parts.push(`<phase_content name="${escapeXml(phase.name)}">`);
|
|
93
93
|
parts.push(phase.content);
|
|
94
|
-
if (output.results && output.results.length > 0) {
|
|
94
|
+
if (output.results && output.results.length > 0 || output.instruction) {
|
|
95
95
|
parts.push(` <prev_phase_outputs>`);
|
|
96
96
|
if (output.instruction) {
|
|
97
97
|
parts.push(` <instruction>${escapeXml(output.instruction)}</instruction>`);
|
|
98
98
|
}
|
|
99
|
-
for (const r of output.results) {
|
|
99
|
+
for (const r of output.results ?? []) {
|
|
100
100
|
parts.push(` <phase name="${escapeXml(r.name)}">`);
|
|
101
101
|
if (r.output !== void 0) {
|
|
102
102
|
parts.push(jsonToXml(r.output, 3));
|
|
@@ -172,18 +172,24 @@ function buildPhaseEntry(p) {
|
|
|
172
172
|
function buildRouteDescription(availablePhases) {
|
|
173
173
|
const phasesBlock = buildStructuredSection("phase", [
|
|
174
174
|
...availablePhases.map(buildPhaseEntry),
|
|
175
|
-
{
|
|
175
|
+
{
|
|
176
|
+
name: "stop",
|
|
177
|
+
description: "Explicitly finish the current user request or task when no further user input is needed"
|
|
178
|
+
}
|
|
176
179
|
]);
|
|
177
180
|
return [
|
|
178
|
-
"Route
|
|
181
|
+
"Route is optional. Omit it when the current reply should wait for more user input and remain in the current phase.",
|
|
179
182
|
"",
|
|
180
183
|
"Rules:",
|
|
181
|
-
"-
|
|
184
|
+
"- Call route only to continue execution immediately in one or more phases, or to explicitly stop.",
|
|
185
|
+
"- `decision` lists phase executions; a single target equal to the current phase starts another iteration of that phase.",
|
|
186
|
+
"- `stop` means the current user request or task is complete and no further user input is needed; `stop` must be the only target.",
|
|
182
187
|
"- Each target may include `phase`, `reason`, `payload`.",
|
|
183
188
|
"- A phase may appear multiple times as independent execution instances.",
|
|
184
189
|
"- `payload` MUST match the phase's `payload_schema`",
|
|
185
190
|
"- `instruction` is optional shared guidance for all phases.",
|
|
186
191
|
"- Executions are independent and concurrent; order is irrelevant.",
|
|
192
|
+
"- Do not call route in the same response as an ordinary tool; finish ordinary tools first.",
|
|
187
193
|
"",
|
|
188
194
|
"<available_phases>",
|
|
189
195
|
phasesBlock,
|
|
@@ -202,12 +208,13 @@ function createRouteTool(availablePhases) {
|
|
|
202
208
|
return {
|
|
203
209
|
name: PhaseRouteTool,
|
|
204
210
|
description: buildRouteDescription(availablePhases),
|
|
205
|
-
promptSnippet: "Route to
|
|
211
|
+
promptSnippet: "Route is optional: omit it to remain in the current phase and wait for user input; use it for immediate phase execution or an explicit stop.",
|
|
206
212
|
promptGuidelines: [
|
|
207
|
-
"
|
|
213
|
+
"Do not call route together with ordinary tools.",
|
|
214
|
+
"Use route(stop) only when the current user request or task is complete and no further user input is needed."
|
|
208
215
|
],
|
|
209
216
|
parameters: Type.Object({
|
|
210
|
-
decision: Type.Array(DecisionTarget, { description: "Phase executions to start" }),
|
|
217
|
+
decision: Type.Array(DecisionTarget, { description: "Phase executions to start", minItems: 1 }),
|
|
211
218
|
instruction: Type.Optional(Type.String({ description: "Overall instruction, passed as context" }))
|
|
212
219
|
}),
|
|
213
220
|
// No-op: this tool is intercepted by phases, never executed via tool execution
|
|
@@ -220,29 +227,35 @@ function createRouteTool(availablePhases) {
|
|
|
220
227
|
};
|
|
221
228
|
}
|
|
222
229
|
function extractRouteCall(toolCalls) {
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
230
|
+
const routeCalls = toolCalls.filter((t) => t.name === PhaseRouteTool);
|
|
231
|
+
if (routeCalls.length !== 1) return void 0;
|
|
232
|
+
const routeCall = routeCalls[0];
|
|
225
233
|
let args;
|
|
226
234
|
if (typeof routeCall.args === "string") {
|
|
227
235
|
try {
|
|
228
|
-
|
|
236
|
+
const parsed = JSON.parse(routeCall.args);
|
|
237
|
+
args = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
229
238
|
} catch {
|
|
230
|
-
return
|
|
239
|
+
return { decision: [{ phase: "" }] };
|
|
231
240
|
}
|
|
232
241
|
} else {
|
|
233
|
-
args = routeCall.args;
|
|
242
|
+
args = routeCall.args !== null && typeof routeCall.args === "object" ? routeCall.args : {};
|
|
234
243
|
}
|
|
235
244
|
const decisionRaw = args.decision;
|
|
236
245
|
let decision = [];
|
|
237
246
|
if (Array.isArray(decisionRaw)) {
|
|
238
247
|
decision = decisionRaw.map((d) => {
|
|
239
|
-
const obj = d;
|
|
248
|
+
const obj = d !== null && typeof d === "object" ? d : {};
|
|
240
249
|
return {
|
|
241
|
-
|
|
250
|
+
// Empty phase is an invalid target sentinel. Never coerce malformed
|
|
251
|
+
// input to stop: only an explicit, valid stop target may terminate.
|
|
252
|
+
phase: typeof obj.phase === "string" ? obj.phase : "",
|
|
242
253
|
reason: typeof obj?.reason === "string" ? obj.reason : void 0,
|
|
243
254
|
payload: obj?.payload
|
|
244
255
|
};
|
|
245
256
|
});
|
|
257
|
+
} else if (decisionRaw !== void 0) {
|
|
258
|
+
decision = [{ phase: "" }];
|
|
246
259
|
}
|
|
247
260
|
return {
|
|
248
261
|
decision,
|
|
@@ -765,6 +778,15 @@ function contentBlocksToMessageContent(blocks) {
|
|
|
765
778
|
});
|
|
766
779
|
}
|
|
767
780
|
|
|
781
|
+
// src/loop/types.ts
|
|
782
|
+
var DEFAULT_MAX_ATTEMPTS = 16;
|
|
783
|
+
function validateMaxAttempts(value) {
|
|
784
|
+
if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) {
|
|
785
|
+
throw new TypeError("maxAttempts must be a positive integer.");
|
|
786
|
+
}
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
|
|
768
790
|
// src/loop/errors.ts
|
|
769
791
|
var EmptyResponseError = class extends Error {
|
|
770
792
|
code = "empty_response";
|
|
@@ -882,7 +904,7 @@ async function collectStreamResult(input) {
|
|
|
882
904
|
doneResponse = event.response;
|
|
883
905
|
stopReason = event.response?.stopReason;
|
|
884
906
|
const contentBlocks2 = lastPartial?.contentBlocks ?? [];
|
|
885
|
-
const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0;
|
|
907
|
+
const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0 || (event.response?.toolCalls?.length ?? 0) > 0;
|
|
886
908
|
const shouldStoreContentParts = contentBlocks2.some((block) => block.type !== "text");
|
|
887
909
|
if (activeMessageId && hasContent2 && shouldStoreContentParts) {
|
|
888
910
|
input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
|
|
@@ -905,7 +927,7 @@ async function collectStreamResult(input) {
|
|
|
905
927
|
if (activeMessageId) {
|
|
906
928
|
await input.message.end(activeMessageId);
|
|
907
929
|
}
|
|
908
|
-
const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0;
|
|
930
|
+
const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0 || (doneResponse?.toolCalls?.length ?? 0) > 0;
|
|
909
931
|
if (!hasContent && stopReason !== "error" && stopReason !== "aborted") {
|
|
910
932
|
throw new EmptyResponseError();
|
|
911
933
|
}
|
|
@@ -1497,7 +1519,7 @@ function createDefaultPhase() {
|
|
|
1497
1519
|
filePath: "",
|
|
1498
1520
|
baseDir: "",
|
|
1499
1521
|
skills: [],
|
|
1500
|
-
content: "Execute tasks using current context.\nNo planning. No evaluation.\
|
|
1522
|
+
content: "Execute tasks using current context.\nNo planning. No evaluation.\nOmit route when more user input is needed. Use route for immediate phase execution, or route(stop) only when the current user request or task is complete and no further user input is needed."
|
|
1501
1523
|
};
|
|
1502
1524
|
}
|
|
1503
1525
|
|
|
@@ -1821,6 +1843,31 @@ function applyFirstDecision(route, output) {
|
|
|
1821
1843
|
if (first.reason) output.routeReason = first.reason;
|
|
1822
1844
|
if (first.payload !== void 0) output.payload = normalizePayload(first.payload);
|
|
1823
1845
|
}
|
|
1846
|
+
function resolveRouteDecision(route, registry) {
|
|
1847
|
+
if (!route || route.decision.length === 0) return void 0;
|
|
1848
|
+
const requestedCount = route.decision.length;
|
|
1849
|
+
const hasStop = route.decision.some(({ phase }) => phase === "stop");
|
|
1850
|
+
if (hasStop) {
|
|
1851
|
+
if (requestedCount !== 1) return void 0;
|
|
1852
|
+
return { ...route, requestedCount };
|
|
1853
|
+
}
|
|
1854
|
+
const decision = route.decision.filter(({ phase }) => registry.phases.has(phase));
|
|
1855
|
+
if (decision.length === 0) return void 0;
|
|
1856
|
+
return { ...route, decision, requestedCount };
|
|
1857
|
+
}
|
|
1858
|
+
function resolveModelRouteDecision(toolCalls, registry) {
|
|
1859
|
+
return resolveRouteDecision(toolCalls ? extractRouteCall(toolCalls) : void 0, registry);
|
|
1860
|
+
}
|
|
1861
|
+
function resolveHookRouteDecision(output, registry) {
|
|
1862
|
+
if (output.route === void 0 || output.route === "continue") return void 0;
|
|
1863
|
+
return resolveRouteDecision({
|
|
1864
|
+
decision: [{
|
|
1865
|
+
phase: output.route,
|
|
1866
|
+
...output.routeReason ? { reason: output.routeReason } : {},
|
|
1867
|
+
...output.payload === void 0 ? {} : { payload: output.payload }
|
|
1868
|
+
}]
|
|
1869
|
+
}, registry);
|
|
1870
|
+
}
|
|
1824
1871
|
function injectPhaseContent(phase, output, messages, mirror) {
|
|
1825
1872
|
try {
|
|
1826
1873
|
const phaseContent = phase.filePath ? readPhaseContent(phase) : phase.content ?? phase.description ?? "";
|
|
@@ -1952,12 +1999,13 @@ function createToolResultContent(result) {
|
|
|
1952
1999
|
}
|
|
1953
2000
|
];
|
|
1954
2001
|
}
|
|
1955
|
-
function createRouteToolResultContent(toolCall) {
|
|
2002
|
+
function createRouteToolResultContent(toolCall, error) {
|
|
1956
2003
|
return [
|
|
1957
2004
|
{
|
|
1958
2005
|
type: "tool_result",
|
|
1959
2006
|
toolUseId: toolCall.id,
|
|
1960
|
-
content: '{"ok": true}'
|
|
2007
|
+
content: error ? JSON.stringify({ ok: false, error }) : '{"ok": true}',
|
|
2008
|
+
...error ? { isError: true } : {}
|
|
1961
2009
|
}
|
|
1962
2010
|
];
|
|
1963
2011
|
}
|
|
@@ -1967,7 +2015,6 @@ async function executePhaseWithModel(ctx) {
|
|
|
1967
2015
|
);
|
|
1968
2016
|
let output = {
|
|
1969
2017
|
message: "",
|
|
1970
|
-
route: "stop",
|
|
1971
2018
|
phase: ctx.phase.name,
|
|
1972
2019
|
toolCalls: []
|
|
1973
2020
|
};
|
|
@@ -1979,15 +2026,24 @@ async function executePhaseWithModel(ctx) {
|
|
|
1979
2026
|
const collected = await ctx.execution.invokeModel(roundContext);
|
|
1980
2027
|
output = {
|
|
1981
2028
|
message: collected.text,
|
|
1982
|
-
route: "stop",
|
|
1983
2029
|
phase: ctx.phase.name,
|
|
1984
|
-
|
|
2030
|
+
// A route decision sharing a model response with an ordinary Tool is
|
|
2031
|
+
// rejected for this round. Keep ordinary calls visible so they can run,
|
|
2032
|
+
// but do not let the route escape the error/re-decision boundary.
|
|
2033
|
+
toolCalls: collected.toolCalls.some((candidate) => candidate.name === PhaseRouteTool) && collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool) ? collected.toolCalls.filter((candidate) => candidate.name !== PhaseRouteTool) : collected.toolCalls
|
|
1985
2034
|
};
|
|
1986
2035
|
for (const toolCall of collected.toolCalls) {
|
|
1987
2036
|
if (toolCall.name !== PhaseRouteTool) continue;
|
|
1988
|
-
const messageId = ctx.messageManager.start(
|
|
1989
|
-
|
|
1990
|
-
|
|
2037
|
+
const messageId = ctx.messageManager.start(
|
|
2038
|
+
"tool",
|
|
2039
|
+
createRouteToolResultContent(
|
|
2040
|
+
toolCall,
|
|
2041
|
+
collected.toolCalls.some((candidate) => candidate.name !== PhaseRouteTool) ? "Route must be called separately after ordinary tools finish; this route decision was ignored." : void 0
|
|
2042
|
+
),
|
|
2043
|
+
{
|
|
2044
|
+
phase: ctx.phase.name
|
|
2045
|
+
}
|
|
2046
|
+
);
|
|
1991
2047
|
await ctx.messageManager.end(messageId);
|
|
1992
2048
|
}
|
|
1993
2049
|
const executableToolCalls = collected.toolCalls.filter(
|
|
@@ -2019,6 +2075,7 @@ async function startPhaseLoop(config, state) {
|
|
|
2019
2075
|
return runPhaseLoop(config, state, registry);
|
|
2020
2076
|
}
|
|
2021
2077
|
async function runPhaseLoop(config, state, registry) {
|
|
2078
|
+
const maxAttempts = validateMaxAttempts(config.maxAttempts) ?? DEFAULT_MAX_ATTEMPTS;
|
|
2022
2079
|
const resumingSuspendedRun = state.status === "suspended" && Boolean(state.currentPhase);
|
|
2023
2080
|
let currentPhaseId = resumingSuspendedRun ? state.currentPhase : registry.entryPhaseId;
|
|
2024
2081
|
let isContinuing = resumingSuspendedRun ? state.continuation?.isContinuing ?? true : false;
|
|
@@ -2030,6 +2087,53 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2030
2087
|
let previousPhaseInputMsgId;
|
|
2031
2088
|
let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
|
|
2032
2089
|
let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
|
|
2090
|
+
const awaitInput = async (prompt, resumeAsContinuing) => {
|
|
2091
|
+
if (!config.waitForInput) return void 0;
|
|
2092
|
+
const preAbort = LoopGuard.checkAbort(config.signal);
|
|
2093
|
+
if (preAbort.stopReason !== "none") {
|
|
2094
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2095
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2096
|
+
previousPhaseMsgId = void 0;
|
|
2097
|
+
previousPhaseInputMsgId = void 0;
|
|
2098
|
+
return {
|
|
2099
|
+
type: "completed",
|
|
2100
|
+
result: await completeRun(config, state, createOutcome.aborted())
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
const requestedAt = createTimestamp();
|
|
2104
|
+
const inputRequest = {
|
|
2105
|
+
phase: currentPhaseId,
|
|
2106
|
+
prompt,
|
|
2107
|
+
requestedAt
|
|
2108
|
+
};
|
|
2109
|
+
state.currentPhase = currentPhaseId;
|
|
2110
|
+
state.status = "suspended";
|
|
2111
|
+
state.attempt = 0;
|
|
2112
|
+
state.continuation = {
|
|
2113
|
+
isContinuing: resumeAsContinuing,
|
|
2114
|
+
previousPayload,
|
|
2115
|
+
previousResults: previousResults.map((result) => ({ ...result })),
|
|
2116
|
+
pendingInstruction,
|
|
2117
|
+
previousPhaseMessageId: previousPhaseMsgId
|
|
2118
|
+
};
|
|
2119
|
+
const userMessages = await config.waitForInput(state, inputRequest);
|
|
2120
|
+
state.status = "running";
|
|
2121
|
+
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
2122
|
+
if (abortResult.stopReason !== "none") {
|
|
2123
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2124
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2125
|
+
previousPhaseMsgId = void 0;
|
|
2126
|
+
previousPhaseInputMsgId = void 0;
|
|
2127
|
+
return {
|
|
2128
|
+
type: "completed",
|
|
2129
|
+
result: await completeRun(config, state, createOutcome.aborted())
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
for (const message of userMessages) {
|
|
2133
|
+
config.context.messages.push(message);
|
|
2134
|
+
}
|
|
2135
|
+
return { type: "continued" };
|
|
2136
|
+
};
|
|
2033
2137
|
while (currentPhaseId) {
|
|
2034
2138
|
const availablePhases = [];
|
|
2035
2139
|
for (const [, phase2] of registry.phases) {
|
|
@@ -2043,6 +2147,19 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2043
2147
|
previousPhaseInputMsgId = void 0;
|
|
2044
2148
|
return completeRun(config, state, createOutcome.aborted());
|
|
2045
2149
|
}
|
|
2150
|
+
if (state.attempt >= maxAttempts) {
|
|
2151
|
+
const input = await awaitInput(
|
|
2152
|
+
`The autonomous Phase execution limit (${maxAttempts}) was reached. Provide input to continue.`,
|
|
2153
|
+
false
|
|
2154
|
+
);
|
|
2155
|
+
if (input?.type === "completed") return input.result;
|
|
2156
|
+
if (input?.type === "continued") {
|
|
2157
|
+
isContinuing = false;
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
throw new Error("Phase execution reached maxAttempts but waitForInput is unavailable.");
|
|
2161
|
+
}
|
|
2162
|
+
state.attempt++;
|
|
2046
2163
|
state.metrics.iterations++;
|
|
2047
2164
|
if (needsCompaction(config.context.messages)) {
|
|
2048
2165
|
const compacted = compactMessages(config.context.messages);
|
|
@@ -2143,13 +2260,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2143
2260
|
}
|
|
2144
2261
|
const runtime = { phase, config, state, execution, messageManager, registry, context: phaseContext };
|
|
2145
2262
|
let output = await executePhase(runtime);
|
|
2146
|
-
let
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
if (routeDecision) {
|
|
2150
|
-
applyFirstDecision(routeDecision, output);
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2263
|
+
let modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry);
|
|
2264
|
+
let routeDecision = modelRouteDecision;
|
|
2265
|
+
if (routeDecision) applyFirstDecision(routeDecision, output);
|
|
2153
2266
|
if (config.afterPhase) {
|
|
2154
2267
|
const extAfter = await config.afterPhase(currentPhaseId, output);
|
|
2155
2268
|
if (extAfter.abort) {
|
|
@@ -2161,52 +2274,30 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2161
2274
|
}
|
|
2162
2275
|
if (extAfter.retry && (phase.run || phase.factory)) {
|
|
2163
2276
|
output = await executePhase(runtime);
|
|
2164
|
-
|
|
2277
|
+
modelRouteDecision = resolveModelRouteDecision(output.toolCalls, registry);
|
|
2278
|
+
routeDecision = modelRouteDecision;
|
|
2165
2279
|
if (routeDecision) applyFirstDecision(routeDecision, output);
|
|
2166
2280
|
}
|
|
2167
2281
|
if (extAfter.output) {
|
|
2282
|
+
const hookHasRoute = extAfter.output.route !== void 0;
|
|
2168
2283
|
output = extAfter.output;
|
|
2284
|
+
if (hookHasRoute) {
|
|
2285
|
+
routeDecision = resolveHookRouteDecision(output, registry);
|
|
2286
|
+
} else if (phase.run || phase.factory) {
|
|
2287
|
+
output = { ...output, route: "stop" };
|
|
2288
|
+
routeDecision = void 0;
|
|
2289
|
+
} else {
|
|
2290
|
+
routeDecision = modelRouteDecision;
|
|
2291
|
+
if (routeDecision) applyFirstDecision(routeDecision, output);
|
|
2292
|
+
}
|
|
2169
2293
|
}
|
|
2170
2294
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
if (
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2178
|
-
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2179
|
-
previousPhaseMsgId = void 0;
|
|
2180
|
-
previousPhaseInputMsgId = void 0;
|
|
2181
|
-
return completeRun(config, state, createOutcome.aborted());
|
|
2182
|
-
}
|
|
2183
|
-
const requestedAt = createTimestamp();
|
|
2184
|
-
const inputRequest = {
|
|
2185
|
-
phase: currentPhaseId,
|
|
2186
|
-
prompt: output.message,
|
|
2187
|
-
requestedAt
|
|
2188
|
-
};
|
|
2189
|
-
state.status = "suspended";
|
|
2190
|
-
state.continuation = {
|
|
2191
|
-
isContinuing,
|
|
2192
|
-
previousPayload,
|
|
2193
|
-
previousResults: previousResults.map((result) => ({ ...result })),
|
|
2194
|
-
pendingInstruction,
|
|
2195
|
-
previousPhaseMessageId: previousPhaseMsgId
|
|
2196
|
-
};
|
|
2197
|
-
const userMessages = await config.waitForInput(state, inputRequest);
|
|
2198
|
-
state.status = "running";
|
|
2199
|
-
const abortResult2 = LoopGuard.checkAbort(config.signal);
|
|
2200
|
-
if (abortResult2.stopReason !== "none") {
|
|
2201
|
-
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2202
|
-
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2203
|
-
previousPhaseMsgId = void 0;
|
|
2204
|
-
previousPhaseInputMsgId = void 0;
|
|
2205
|
-
return completeRun(config, state, createOutcome.aborted());
|
|
2206
|
-
}
|
|
2207
|
-
for (const message of userMessages) {
|
|
2208
|
-
config.context.messages.push(message);
|
|
2209
|
-
}
|
|
2295
|
+
if (phase.run || phase.factory) {
|
|
2296
|
+
if (output.route === void 0 && !routeDecision) output = { ...output, route: "stop" };
|
|
2297
|
+
} else if (!phase.target && output.route === void 0 && !routeDecision) {
|
|
2298
|
+
const input = await awaitInput(output.message, true);
|
|
2299
|
+
if (input?.type === "completed") return input.result;
|
|
2300
|
+
if (input?.type === "continued") {
|
|
2210
2301
|
isContinuing = true;
|
|
2211
2302
|
continue;
|
|
2212
2303
|
}
|
|
@@ -2220,7 +2311,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2220
2311
|
isContinuing = true;
|
|
2221
2312
|
continue;
|
|
2222
2313
|
}
|
|
2223
|
-
if (routeDecision && routeDecision.
|
|
2314
|
+
if (routeDecision && routeDecision.requestedCount > 1) {
|
|
2224
2315
|
const contextSnapshot = snapshotMessages(config.context.messages);
|
|
2225
2316
|
const parallelTasks = /* @__PURE__ */ new Map();
|
|
2226
2317
|
const instanceIds = buildInstanceIds(routeDecision.decision.map((t) => t.phase));
|
|
@@ -2239,6 +2330,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2239
2330
|
registry,
|
|
2240
2331
|
pt,
|
|
2241
2332
|
payload,
|
|
2333
|
+
routeDecision.instruction,
|
|
2242
2334
|
context,
|
|
2243
2335
|
availablePhases,
|
|
2244
2336
|
instanceId,
|
|
@@ -2251,7 +2343,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2251
2343
|
}
|
|
2252
2344
|
const successfulResults = await waitForBackgroundTasks(parallelTasks);
|
|
2253
2345
|
previousResults = successfulResults.map((r) => ({ name: r.instanceId, output: r.payload }));
|
|
2254
|
-
pendingInstruction =
|
|
2346
|
+
pendingInstruction = void 0;
|
|
2255
2347
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
2256
2348
|
if (entryPhaseId === "stop") {
|
|
2257
2349
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
@@ -2294,6 +2386,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2294
2386
|
});
|
|
2295
2387
|
previousPayload = output.payload;
|
|
2296
2388
|
previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
|
|
2389
|
+
pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
|
|
2297
2390
|
currentPhaseId = targetPhaseId;
|
|
2298
2391
|
}
|
|
2299
2392
|
throw new Error("Phase machine exited without a stop or abort transition.");
|
|
@@ -2548,10 +2641,10 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2548
2641
|
}
|
|
2549
2642
|
};
|
|
2550
2643
|
}
|
|
2551
|
-
async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
|
|
2644
|
+
async function executeParallelPhase(config, state, registry, phase, payload, instruction, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
|
|
2552
2645
|
const messages = [...context];
|
|
2553
2646
|
const allTools = buildToolsWithRouting(config, availablePhases);
|
|
2554
|
-
const phaseTools = selectPhaseTools(allTools, phase.tools);
|
|
2647
|
+
const phaseTools = selectPhaseTools(allTools, phase.tools, false);
|
|
2555
2648
|
const phaseSkills = mergeSkills(config.context.skills, phase.skills);
|
|
2556
2649
|
const systemPrompt = config.context.systemPrompt;
|
|
2557
2650
|
const phaseContext = {
|
|
@@ -2578,7 +2671,10 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2578
2671
|
const messageManager = createMessageManager({ messages }, config.onMessage, config.onMessageDelta);
|
|
2579
2672
|
const phaseMsgId = injectPhaseContent(
|
|
2580
2673
|
phase,
|
|
2581
|
-
{
|
|
2674
|
+
{
|
|
2675
|
+
instruction,
|
|
2676
|
+
results: payload !== void 0 ? [{ name: sourcePhaseId, output: payload }] : []
|
|
2677
|
+
},
|
|
2582
2678
|
messages
|
|
2583
2679
|
);
|
|
2584
2680
|
const toolExecutionManager = createToolExecutionManager();
|
|
@@ -2590,8 +2686,8 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2590
2686
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2591
2687
|
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2592
2688
|
}
|
|
2593
|
-
function selectPhaseTools(candidates, names) {
|
|
2594
|
-
const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
|
|
2689
|
+
function selectPhaseTools(candidates, names, includeRouting = true) {
|
|
2690
|
+
const routing = includeRouting ? candidates.filter(({ name }) => name === PhaseRouteTool) : [];
|
|
2595
2691
|
const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
|
|
2596
2692
|
return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
|
|
2597
2693
|
}
|
|
@@ -3040,6 +3136,7 @@ function validateConfigResolution(agentId, value) {
|
|
|
3040
3136
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
3041
3137
|
}
|
|
3042
3138
|
function snapshotConfig(config) {
|
|
3139
|
+
validateMaxAttempts(config.maxAttempts);
|
|
3043
3140
|
if (isAgentConfiguration(config)) return snapshotConfiguration(config);
|
|
3044
3141
|
const definition = Object.freeze({
|
|
3045
3142
|
...config.definition,
|
|
@@ -5000,6 +5097,7 @@ function adaptExtensionTool2(input) {
|
|
|
5000
5097
|
|
|
5001
5098
|
// src/runtime/configuration-snapshot.ts
|
|
5002
5099
|
function resolveConfigurationSnapshot(registry, input) {
|
|
5100
|
+
validateMaxAttempts(input.maxAttempts);
|
|
5003
5101
|
const resolved = registry.resolveView(input.resourceView);
|
|
5004
5102
|
const base = resolved.agents.find(({ name }) => name === input.definition.name);
|
|
5005
5103
|
if (!base) {
|
|
@@ -5785,6 +5883,11 @@ function normalizePhaseRegistry(registry) {
|
|
|
5785
5883
|
if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
|
|
5786
5884
|
throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
|
|
5787
5885
|
}
|
|
5886
|
+
for (const [name] of registry?.phases ?? []) {
|
|
5887
|
+
if (name === "stop" || name === "continue") {
|
|
5888
|
+
throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5788
5891
|
return {
|
|
5789
5892
|
phases: new Map([
|
|
5790
5893
|
[DEFAULT_PHASE_ID, createDefaultPhase()],
|