@rowan-agent/agent 0.9.8 → 0.9.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/dist/index.d.ts +140 -6
- package/dist/index.js +852 -232
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/harness/tools/index.ts
|
|
2
|
-
import { mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
|
|
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
|
+
import { tmpdir } from "os";
|
|
4
5
|
import Type2 from "typebox";
|
|
5
6
|
import Schema from "typebox/schema";
|
|
6
7
|
|
|
@@ -20,7 +21,7 @@ function buildStructuredSection(tag, items) {
|
|
|
20
21
|
return lines.join("\n");
|
|
21
22
|
}
|
|
22
23
|
function buildSkillsDescription(skills) {
|
|
23
|
-
const visibleSkills = skills.filter((skill) => !skill.disableModelInvocation);
|
|
24
|
+
const visibleSkills = skills.filter((skill) => !(skill.disableAutoInvocation ?? skill.disableModelInvocation));
|
|
24
25
|
if (visibleSkills.length === 0) return "";
|
|
25
26
|
const lines = ["<available_skills>"];
|
|
26
27
|
for (const skill of visibleSkills) {
|
|
@@ -170,11 +171,12 @@ function buildPhaseEntry(p) {
|
|
|
170
171
|
return entry;
|
|
171
172
|
}
|
|
172
173
|
function buildRouteDescription(availablePhases) {
|
|
174
|
+
const routablePhases = availablePhases.filter(({ name }) => name !== "stop");
|
|
173
175
|
const phasesBlock = buildStructuredSection("phase", [
|
|
174
|
-
...
|
|
176
|
+
...routablePhases.map(buildPhaseEntry),
|
|
175
177
|
{
|
|
176
178
|
name: "stop",
|
|
177
|
-
description: "
|
|
179
|
+
description: "Complete the task; when the current reply has no final user-facing conclusion, the built-in Stop Phase provides one"
|
|
178
180
|
}
|
|
179
181
|
]);
|
|
180
182
|
return [
|
|
@@ -183,7 +185,7 @@ function buildRouteDescription(availablePhases) {
|
|
|
183
185
|
"Rules:",
|
|
184
186
|
"- Call route only to continue execution immediately in one or more phases, or to explicitly stop.",
|
|
185
187
|
"- `decision` lists phase executions; a single target equal to the current phase starts another iteration of that phase.",
|
|
186
|
-
"- `stop`
|
|
188
|
+
"- `stop` completes the task; when the current reply has no final user-facing conclusion, the built-in Stop Phase provides one. Use it only when the task is complete and no further user input is needed, and make it the only target.",
|
|
187
189
|
"- Each target may include `phase`, `reason`, `payload`.",
|
|
188
190
|
"- A phase may appear multiple times as independent execution instances.",
|
|
189
191
|
"- `payload` MUST match the phase's `payload_schema`",
|
|
@@ -197,9 +199,10 @@ function buildRouteDescription(availablePhases) {
|
|
|
197
199
|
].join("\n");
|
|
198
200
|
}
|
|
199
201
|
function createRouteTool(availablePhases) {
|
|
202
|
+
const routablePhases = availablePhases.filter(({ name }) => name !== "stop");
|
|
200
203
|
const DecisionTarget = Type.Object({
|
|
201
204
|
phase: Type.Union([
|
|
202
|
-
...
|
|
205
|
+
...routablePhases.map((p) => Type.Literal(p.name)),
|
|
203
206
|
Type.Literal("stop")
|
|
204
207
|
]),
|
|
205
208
|
reason: Type.Optional(Type.String({ description: "Brief reason for this decision" })),
|
|
@@ -208,10 +211,10 @@ function createRouteTool(availablePhases) {
|
|
|
208
211
|
return {
|
|
209
212
|
name: PhaseRouteTool,
|
|
210
213
|
description: buildRouteDescription(availablePhases),
|
|
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
|
|
214
|
+
promptSnippet: "Route is optional: omit it to remain in the current phase and wait for user input; use it for immediate phase execution, or use stop to complete the task and obtain a final user-facing conclusion when needed.",
|
|
212
215
|
promptGuidelines: [
|
|
213
216
|
"Do not call route together with ordinary tools.",
|
|
214
|
-
"Use route(stop) only when the current user request or task is complete
|
|
217
|
+
"Use route(stop) only when the current user request or task is complete; the Stop Phase will provide the final user-facing conclusion."
|
|
215
218
|
],
|
|
216
219
|
parameters: Type.Object({
|
|
217
220
|
decision: Type.Array(DecisionTarget, { description: "Phase executions to start", minItems: 1 }),
|
|
@@ -264,7 +267,8 @@ function extractRouteCall(toolCalls) {
|
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
// src/harness/tools/index.ts
|
|
267
|
-
var DEFAULT_MAX_READ_BYTES =
|
|
270
|
+
var DEFAULT_MAX_READ_BYTES = 50 * 1024;
|
|
271
|
+
var DEFAULT_MAX_READ_LINES = 2e3;
|
|
268
272
|
var DEFAULT_BASH_TIMEOUT_MS = 3e4;
|
|
269
273
|
var DEFAULT_MAX_BASH_OUTPUT_BYTES = 64e3;
|
|
270
274
|
var ReadArgsSchema = Type2.Object({
|
|
@@ -311,7 +315,9 @@ function normalizeCoreToolInputPath(path = ".") {
|
|
|
311
315
|
function createCoreToolContext(input = {}) {
|
|
312
316
|
return {
|
|
313
317
|
root: resolve2(input.root ?? process.cwd()),
|
|
318
|
+
archiveDir: resolve2(input.archiveDir ?? `${tmpdir()}/rowan-tool-results`),
|
|
314
319
|
maxReadBytes: input.maxReadBytes ?? DEFAULT_MAX_READ_BYTES,
|
|
320
|
+
maxReadLines: input.maxReadLines ?? DEFAULT_MAX_READ_LINES,
|
|
315
321
|
bashTimeoutMs: input.bashTimeoutMs ?? DEFAULT_BASH_TIMEOUT_MS,
|
|
316
322
|
maxBashOutputBytes: input.maxBashOutputBytes ?? DEFAULT_MAX_BASH_OUTPUT_BYTES
|
|
317
323
|
};
|
|
@@ -449,17 +455,9 @@ async function captureStream(stream, maxBytes) {
|
|
|
449
455
|
if (!value) {
|
|
450
456
|
continue;
|
|
451
457
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
chunks.push(kept);
|
|
456
|
-
totalBytes += kept.byteLength;
|
|
457
|
-
}
|
|
458
|
-
if (value.byteLength > remainingBytes || totalBytes >= maxBytes) {
|
|
459
|
-
truncated = true;
|
|
460
|
-
await reader.cancel().catch(() => void 0);
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
458
|
+
chunks.push(value);
|
|
459
|
+
totalBytes += value.byteLength;
|
|
460
|
+
if (totalBytes > maxBytes) truncated = true;
|
|
463
461
|
}
|
|
464
462
|
const bytes = new Uint8Array(totalBytes);
|
|
465
463
|
let offset = 0;
|
|
@@ -472,6 +470,19 @@ async function captureStream(stream, maxBytes) {
|
|
|
472
470
|
truncated
|
|
473
471
|
};
|
|
474
472
|
}
|
|
473
|
+
async function spillResult(context, toolName, content) {
|
|
474
|
+
await mkdir(context.archiveDir, { recursive: true, mode: 448 });
|
|
475
|
+
await chmod(context.archiveDir, 448).catch(() => void 0);
|
|
476
|
+
const path = `${context.archiveDir}/rowan-${toolName}-${Date.now()}-${crypto.randomUUID()}.log`;
|
|
477
|
+
await writeFile(path, content, { encoding: "utf8", mode: 384 });
|
|
478
|
+
await chmod(path, 384).catch(() => void 0);
|
|
479
|
+
return path;
|
|
480
|
+
}
|
|
481
|
+
function boundedPreview(text, maxBytes) {
|
|
482
|
+
const bytes = new TextEncoder().encode(text);
|
|
483
|
+
if (bytes.byteLength <= maxBytes) return { text, truncated: false };
|
|
484
|
+
return { text: new TextDecoder().decode(bytes.subarray(0, maxBytes)), truncated: true };
|
|
485
|
+
}
|
|
475
486
|
function createReadTool(context) {
|
|
476
487
|
return {
|
|
477
488
|
name: "read",
|
|
@@ -516,15 +527,19 @@ function createReadTool(context) {
|
|
|
516
527
|
}
|
|
517
528
|
const bytes = await readFile2(resolved.absolutePath);
|
|
518
529
|
const source = new TextDecoder().decode(bytes);
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const
|
|
530
|
+
const sourceLines = source.split(/\r\n|\n|\r/);
|
|
531
|
+
const requestedOffset = parsed.offset ?? 1;
|
|
532
|
+
const requestedLimit = parsed.limit ?? context.maxReadLines;
|
|
533
|
+
const selected = readTextLines(source, requestedOffset, requestedLimit);
|
|
534
|
+
const preview = boundedPreview(selected, maxBytes);
|
|
535
|
+
const text = preview.text;
|
|
536
|
+
const shownLines = text.split(/\r\n|\n|\r/).length;
|
|
537
|
+
const selectedLineEnd = Math.min(sourceLines.length, requestedOffset + shownLines - 1);
|
|
523
538
|
const resourceType = detectResourceType(resolved.absolutePath);
|
|
524
539
|
let name;
|
|
525
540
|
let baseDir;
|
|
526
541
|
if (resourceType === "skill" || resourceType === "phase") {
|
|
527
|
-
const { frontmatter } = parseFrontmatter(
|
|
542
|
+
const { frontmatter } = parseFrontmatter(source);
|
|
528
543
|
const marker = resourceType === "skill" ? "SKILL.md" : "PHASE.md";
|
|
529
544
|
name = frontmatter.name ?? inferResourceName(resolved.absolutePath, marker);
|
|
530
545
|
baseDir = dirname2(resolved.absolutePath);
|
|
@@ -532,8 +547,19 @@ function createReadTool(context) {
|
|
|
532
547
|
name = inferResourceName(resolved.absolutePath, basename2(resolved.absolutePath));
|
|
533
548
|
}
|
|
534
549
|
const formatted = resourceType === "skill" || resourceType === "phase" ? formatResourceOutput({ type: resourceType, name, location: resolved.absolutePath, content: text, baseDir }) : text;
|
|
535
|
-
const
|
|
550
|
+
const fullFormatted = resourceType === "skill" || resourceType === "phase" ? formatResourceOutput({ type: resourceType, name, location: resolved.absolutePath, content: source, baseDir }) : source;
|
|
551
|
+
const lineTruncated = parsed.limit === void 0 && requestedOffset + requestedLimit - 1 < sourceLines.length;
|
|
552
|
+
const truncated = preview.truncated || lineTruncated;
|
|
553
|
+
let content = truncated ? `${formatted}
|
|
536
554
|
[truncated]` : formatted;
|
|
555
|
+
const archivePath = await spillResult(context, "read", fullFormatted);
|
|
556
|
+
content += `
|
|
557
|
+
Full result: ${archivePath}
|
|
558
|
+
Offset: 0`;
|
|
559
|
+
if (lineTruncated) {
|
|
560
|
+
content += `
|
|
561
|
+
Showing lines ${requestedOffset}-${selectedLineEnd} of ${sourceLines.length}. Use offset=${selectedLineEnd + 1} to continue.`;
|
|
562
|
+
}
|
|
537
563
|
return toolResult({
|
|
538
564
|
context: toolContext,
|
|
539
565
|
toolName: "read",
|
|
@@ -681,8 +707,13 @@ function createBashTool(context) {
|
|
|
681
707
|
]);
|
|
682
708
|
const ok = exitCode === 0 && !timedOut && !aborted;
|
|
683
709
|
const output = [stdout.text, stderr.text].filter(Boolean).join(stdout.text && stderr.text ? "\n" : "");
|
|
684
|
-
const
|
|
710
|
+
const preview = boundedPreview(output, maxOutputBytes);
|
|
711
|
+
let content = preview.truncated ? `${preview.text}
|
|
685
712
|
[truncated]` : output;
|
|
713
|
+
const archivePath = await spillResult(context, "bash", output);
|
|
714
|
+
content += `
|
|
715
|
+
Full result: ${archivePath}
|
|
716
|
+
Offset: 0`;
|
|
686
717
|
return toolResult({
|
|
687
718
|
context: toolContext,
|
|
688
719
|
toolName: "bash",
|
|
@@ -709,7 +740,24 @@ function createCoreTools(input = {}) {
|
|
|
709
740
|
];
|
|
710
741
|
}
|
|
711
742
|
|
|
743
|
+
// src/runtime/core-tools.ts
|
|
744
|
+
function createRuntimeCoreTools(input = {}) {
|
|
745
|
+
return createCoreTools(input).map((tool) => ({
|
|
746
|
+
name: tool.name,
|
|
747
|
+
core: true,
|
|
748
|
+
description: tool.description,
|
|
749
|
+
parameters: tool.parameters,
|
|
750
|
+
execute: async (args, context, signal) => {
|
|
751
|
+
const result = await tool.execute(args, { skills: [], toolCallId: context.toolCallId }, signal);
|
|
752
|
+
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Tool failed." };
|
|
753
|
+
}
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
|
|
712
757
|
// src/runtime/durable-runtime.ts
|
|
758
|
+
import { mkdir as mkdir2, chmod as chmod2, writeFile as writeFile2 } from "fs/promises";
|
|
759
|
+
import { randomUUID } from "crypto";
|
|
760
|
+
import { join as join5 } from "path";
|
|
713
761
|
import { createModelStream } from "@rowan-agent/models";
|
|
714
762
|
|
|
715
763
|
// src/utils.ts
|
|
@@ -748,19 +796,6 @@ function createMessage(role, content, metadata) {
|
|
|
748
796
|
...metadata ? { metadata } : {}
|
|
749
797
|
};
|
|
750
798
|
}
|
|
751
|
-
function messageContentText(content) {
|
|
752
|
-
if (typeof content === "string") {
|
|
753
|
-
return content;
|
|
754
|
-
}
|
|
755
|
-
return content.map((part) => {
|
|
756
|
-
if (part.type === "text") return part.text;
|
|
757
|
-
if (part.type === "thinking") return part.thinking;
|
|
758
|
-
if (part.type === "tool_result") return part.content;
|
|
759
|
-
if (part.type === "tool_use") return JSON.stringify(part.input);
|
|
760
|
-
if (part.type === "image") return `[image:${part.mimeType}]`;
|
|
761
|
-
return "";
|
|
762
|
-
}).filter(Boolean).join("\n");
|
|
763
|
-
}
|
|
764
799
|
function contentBlocksToMessageContent(blocks) {
|
|
765
800
|
return blocks.map((block) => {
|
|
766
801
|
if (block.type === "text") {
|
|
@@ -822,7 +857,8 @@ async function invokeModel(input) {
|
|
|
822
857
|
config: input.config,
|
|
823
858
|
message: input.message,
|
|
824
859
|
events,
|
|
825
|
-
metadataPhase: input.phaseId
|
|
860
|
+
metadataPhase: input.phaseId,
|
|
861
|
+
output: input.output
|
|
826
862
|
});
|
|
827
863
|
const response = result.doneResponse ?? {
|
|
828
864
|
content: result.text,
|
|
@@ -845,6 +881,7 @@ async function invokeModel(input) {
|
|
|
845
881
|
};
|
|
846
882
|
}
|
|
847
883
|
async function collectStreamResult(input) {
|
|
884
|
+
const persistAssistant = input.output !== "internal";
|
|
848
885
|
let activeMessageId;
|
|
849
886
|
let lastPartial;
|
|
850
887
|
let stopReason;
|
|
@@ -867,7 +904,7 @@ async function collectStreamResult(input) {
|
|
|
867
904
|
}
|
|
868
905
|
const abortResult2 = LoopGuard.checkAbort(input.config.signal);
|
|
869
906
|
if (abortResult2.stopReason !== "none") {
|
|
870
|
-
if (activeMessageId) {
|
|
907
|
+
if (persistAssistant && activeMessageId) {
|
|
871
908
|
await input.message.end(activeMessageId);
|
|
872
909
|
activeMessageId = void 0;
|
|
873
910
|
}
|
|
@@ -878,23 +915,27 @@ async function collectStreamResult(input) {
|
|
|
878
915
|
}
|
|
879
916
|
if (event.type === "start") {
|
|
880
917
|
lastPartial = event.partial;
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
918
|
+
if (persistAssistant) {
|
|
919
|
+
activeMessageId ??= input.message.reserve("assistant", {
|
|
920
|
+
phase: input.metadataPhase
|
|
921
|
+
});
|
|
922
|
+
}
|
|
884
923
|
}
|
|
885
924
|
if (event.type === "text_delta") {
|
|
886
925
|
lastPartial = event.partial;
|
|
887
|
-
if (!activeMessageId) {
|
|
926
|
+
if (persistAssistant && !activeMessageId) {
|
|
888
927
|
activeMessageId = input.message.reserve("assistant", {
|
|
889
928
|
phase: input.metadataPhase
|
|
890
929
|
});
|
|
891
930
|
}
|
|
892
|
-
await input.message.update(activeMessageId, event.text);
|
|
931
|
+
if (persistAssistant && activeMessageId) await input.message.update(activeMessageId, event.text);
|
|
893
932
|
}
|
|
894
933
|
if (event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
934
|
+
if (persistAssistant) {
|
|
935
|
+
activeMessageId ??= input.message.reserve("assistant", {
|
|
936
|
+
phase: input.metadataPhase
|
|
937
|
+
});
|
|
938
|
+
}
|
|
898
939
|
lastPartial = event.partial;
|
|
899
940
|
}
|
|
900
941
|
if (event.type === "thinking_delta") {
|
|
@@ -906,10 +947,10 @@ async function collectStreamResult(input) {
|
|
|
906
947
|
const contentBlocks2 = lastPartial?.contentBlocks ?? [];
|
|
907
948
|
const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0 || (event.response?.toolCalls?.length ?? 0) > 0;
|
|
908
949
|
const shouldStoreContentParts = contentBlocks2.some((block) => block.type !== "text");
|
|
909
|
-
if (activeMessageId && hasContent2 && shouldStoreContentParts) {
|
|
950
|
+
if (persistAssistant && activeMessageId && hasContent2 && shouldStoreContentParts) {
|
|
910
951
|
input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
|
|
911
952
|
}
|
|
912
|
-
if (activeMessageId) {
|
|
953
|
+
if (persistAssistant && activeMessageId) {
|
|
913
954
|
if (hasContent2) await input.message.end(activeMessageId);
|
|
914
955
|
else input.message.discard(activeMessageId);
|
|
915
956
|
activeMessageId = void 0;
|
|
@@ -918,13 +959,13 @@ async function collectStreamResult(input) {
|
|
|
918
959
|
}
|
|
919
960
|
const abortResult = LoopGuard.checkAbort(input.config.signal);
|
|
920
961
|
if (abortResult.stopReason !== "none") {
|
|
921
|
-
if (activeMessageId) {
|
|
962
|
+
if (persistAssistant && activeMessageId) {
|
|
922
963
|
await input.message.end(activeMessageId);
|
|
923
964
|
activeMessageId = void 0;
|
|
924
965
|
}
|
|
925
966
|
return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
|
|
926
967
|
}
|
|
927
|
-
if (activeMessageId) {
|
|
968
|
+
if (persistAssistant && activeMessageId) {
|
|
928
969
|
await input.message.end(activeMessageId);
|
|
929
970
|
}
|
|
930
971
|
const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0 || (doneResponse?.toolCalls?.length ?? 0) > 0;
|
|
@@ -1317,7 +1358,7 @@ function optionalBundledSkills(value, field) {
|
|
|
1317
1358
|
if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of Skills`);
|
|
1318
1359
|
const names = /* @__PURE__ */ new Set();
|
|
1319
1360
|
for (const [index, item] of value.entries()) {
|
|
1320
|
-
if (!isRecord(item) || typeof item.name !== "string" || item.name.trim() === "" || typeof item.description !== "string" || typeof item.filePath !== "string" || typeof item.baseDir !== "string" || typeof item.content !== "string" || typeof item.
|
|
1361
|
+
if (!isRecord(item) || typeof item.name !== "string" || item.name.trim() === "" || typeof item.description !== "string" || typeof item.filePath !== "string" || typeof item.baseDir !== "string" || typeof item.content !== "string" || item.disableAutoInvocation !== void 0 && typeof item.disableAutoInvocation !== "boolean" || item.disableImplicitInvocation !== void 0 && typeof item.disableImplicitInvocation !== "boolean") {
|
|
1321
1362
|
throw new TypeError(`${field}[${index}] is invalid`);
|
|
1322
1363
|
}
|
|
1323
1364
|
if (names.has(item.name)) throw new TypeError(`Duplicate bundled Skill "${item.name}".`);
|
|
@@ -1456,7 +1497,9 @@ async function loadSkill(path) {
|
|
|
1456
1497
|
filePath: resolved,
|
|
1457
1498
|
baseDir: dirname3(resolved),
|
|
1458
1499
|
content: body,
|
|
1459
|
-
|
|
1500
|
+
disableAutoInvocation: metadata["disable-auto-invocation"] === true || metadata["disable-model-invocation"] === true,
|
|
1501
|
+
disableImplicitInvocation: metadata["disable-implicit-invocation"] === true,
|
|
1502
|
+
...metadata["disable-model-invocation"] === true ? { disableModelInvocation: true } : {}
|
|
1460
1503
|
};
|
|
1461
1504
|
}
|
|
1462
1505
|
async function loadSkills(targetPath) {
|
|
@@ -1548,7 +1591,9 @@ async function loadPhase(targetPath) {
|
|
|
1548
1591
|
filePath: resolved,
|
|
1549
1592
|
baseDir,
|
|
1550
1593
|
content: definition.prompt,
|
|
1551
|
-
model: definition.model
|
|
1594
|
+
model: definition.model,
|
|
1595
|
+
disableAutoInvocation: metadata["disable-auto-invocation"] === true,
|
|
1596
|
+
disableImplicitInvocation: metadata["disable-implicit-invocation"] === true
|
|
1552
1597
|
};
|
|
1553
1598
|
const codePath = await discoverPhaseCode(baseDir);
|
|
1554
1599
|
if (codePath) {
|
|
@@ -1684,16 +1729,126 @@ async function loadPhaseCode(codePath) {
|
|
|
1684
1729
|
|
|
1685
1730
|
// src/harness/phases/default.ts
|
|
1686
1731
|
var DEFAULT_PHASE_ID = "default";
|
|
1732
|
+
var STOP_PHASE_ID = "stop";
|
|
1733
|
+
var COMPACT_PHASE_ID = "compact";
|
|
1687
1734
|
function createDefaultPhase() {
|
|
1688
1735
|
return {
|
|
1689
1736
|
name: DEFAULT_PHASE_ID,
|
|
1690
|
-
|
|
1737
|
+
core: true,
|
|
1738
|
+
description: "Execute the current user request using the current context.",
|
|
1739
|
+
filePath: "",
|
|
1740
|
+
baseDir: "",
|
|
1741
|
+
skills: [],
|
|
1742
|
+
content: [
|
|
1743
|
+
"Execute the current user request using the current context.",
|
|
1744
|
+
"Must not plan or evaluate unless requested; use available tools only when needed.",
|
|
1745
|
+
"If more user input is needed, omit route. If the request is complete, route(stop) as the only target."
|
|
1746
|
+
].join("\n"),
|
|
1747
|
+
disableAutoInvocation: false,
|
|
1748
|
+
disableImplicitInvocation: true
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
function createStopPhase() {
|
|
1752
|
+
return {
|
|
1753
|
+
name: STOP_PHASE_ID,
|
|
1754
|
+
core: true,
|
|
1755
|
+
description: "Provide the final user-facing conclusion for the completed task.",
|
|
1756
|
+
filePath: "",
|
|
1757
|
+
baseDir: "",
|
|
1758
|
+
skills: [],
|
|
1759
|
+
content: [
|
|
1760
|
+
"Return only a brief normal-exit explanation in the user's language (one or two sentences).",
|
|
1761
|
+
"Must use only facts from the conversation and completed work, and state that the current run ended normally.",
|
|
1762
|
+
"If no actionable work occurred, say so briefly. Never invent Backlog, Task, Context, blockers, or next steps.",
|
|
1763
|
+
"Do not greet, ask questions, mention routing, call tools, or route to another Phase."
|
|
1764
|
+
].join("\n"),
|
|
1765
|
+
disableAutoInvocation: false,
|
|
1766
|
+
disableImplicitInvocation: false
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
function createCompactPhase() {
|
|
1770
|
+
return {
|
|
1771
|
+
name: COMPACT_PHASE_ID,
|
|
1772
|
+
core: true,
|
|
1773
|
+
description: "Compact the complete conversation context into a durable summary.",
|
|
1691
1774
|
filePath: "",
|
|
1692
1775
|
baseDir: "",
|
|
1693
1776
|
skills: [],
|
|
1694
|
-
content: "
|
|
1777
|
+
content: "Summarize all historical conversation roles and tool activity for future context. Preserve durable archive references and important decisions.",
|
|
1778
|
+
disableAutoInvocation: true,
|
|
1779
|
+
disableImplicitInvocation: false,
|
|
1780
|
+
run: async (context, execution) => {
|
|
1781
|
+
await execution.reportStatus({
|
|
1782
|
+
state: "running",
|
|
1783
|
+
kind: "compacting",
|
|
1784
|
+
message: "Compacting conversation context."
|
|
1785
|
+
});
|
|
1786
|
+
const tools = context.tools.filter((tool) => tool.core && (tool.name === "read" || tool.name === "bash"));
|
|
1787
|
+
const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
|
|
1788
|
+
let result = await execution.invokeModel(working, { output: "internal" });
|
|
1789
|
+
for (let attempt = 0; attempt < 8 && result.toolCalls.length > 0; attempt += 1) {
|
|
1790
|
+
const calls = result.toolCalls.filter((call) => tools.some((tool) => tool.name === call.name));
|
|
1791
|
+
if (calls.length === 0) break;
|
|
1792
|
+
const normalizedCalls = calls.map((call) => ({
|
|
1793
|
+
...call,
|
|
1794
|
+
args: typeof call.args === "string" ? (() => {
|
|
1795
|
+
try {
|
|
1796
|
+
return JSON.parse(call.args);
|
|
1797
|
+
} catch {
|
|
1798
|
+
return call.args;
|
|
1799
|
+
}
|
|
1800
|
+
})() : call.args
|
|
1801
|
+
}));
|
|
1802
|
+
const assistantContent = result.contentBlocks.length > 0 ? contentBlocksToMessageContent(result.contentBlocks) : normalizedCalls.map((call) => ({ type: "tool_use", id: call.id, name: call.name, input: call.args }));
|
|
1803
|
+
working.messages.push(createMessage("assistant", assistantContent, {
|
|
1804
|
+
kind: "phase_tool",
|
|
1805
|
+
phase: COMPACT_PHASE_ID
|
|
1806
|
+
}));
|
|
1807
|
+
for (const call of normalizedCalls) {
|
|
1808
|
+
const tool = tools.find((candidate) => candidate.name === call.name);
|
|
1809
|
+
let toolResult2;
|
|
1810
|
+
try {
|
|
1811
|
+
toolResult2 = await tool.execute(call.args, {
|
|
1812
|
+
skills: working.skills,
|
|
1813
|
+
toolCallId: call.id
|
|
1814
|
+
});
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
toolResult2 = {
|
|
1817
|
+
toolCallId: call.id,
|
|
1818
|
+
toolName: call.name,
|
|
1819
|
+
ok: false,
|
|
1820
|
+
content: null,
|
|
1821
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
working.messages.push(createMessage("tool", [{
|
|
1825
|
+
type: "tool_result",
|
|
1826
|
+
toolUseId: call.id,
|
|
1827
|
+
content: JSON.stringify(toolResult2),
|
|
1828
|
+
isError: !toolResult2.ok
|
|
1829
|
+
}], {
|
|
1830
|
+
kind: "phase_tool",
|
|
1831
|
+
phase: COMPACT_PHASE_ID
|
|
1832
|
+
}));
|
|
1833
|
+
}
|
|
1834
|
+
result = await execution.invokeModel(working, { output: "internal" });
|
|
1835
|
+
}
|
|
1836
|
+
return {
|
|
1837
|
+
route: "stop",
|
|
1838
|
+
phase: COMPACT_PHASE_ID,
|
|
1839
|
+
payload: { kind: "context_compaction", summary: result.text },
|
|
1840
|
+
status: {
|
|
1841
|
+
state: "completed",
|
|
1842
|
+
kind: "compacted",
|
|
1843
|
+
message: "Conversation context compacted."
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1695
1847
|
};
|
|
1696
1848
|
}
|
|
1849
|
+
function createCorePhases() {
|
|
1850
|
+
return [createDefaultPhase(), createStopPhase(), createCompactPhase()];
|
|
1851
|
+
}
|
|
1697
1852
|
|
|
1698
1853
|
// src/harness/resource-selection.ts
|
|
1699
1854
|
function selectNamedResources(candidates, names, kind) {
|
|
@@ -1745,7 +1900,7 @@ function buildSystemPrompt(options) {
|
|
|
1745
1900
|
addGuideline(g);
|
|
1746
1901
|
}
|
|
1747
1902
|
}
|
|
1748
|
-
const visibleSkills = (skills ?? []).filter((s) => !s.disableModelInvocation);
|
|
1903
|
+
const visibleSkills = (skills ?? []).filter((s) => !(s.disableAutoInvocation ?? s.disableModelInvocation));
|
|
1749
1904
|
const skillsBlock = visibleSkills.length > 0 ? buildSkillsDescription(visibleSkills) : "";
|
|
1750
1905
|
for (const g of promptGuidelines ?? []) {
|
|
1751
1906
|
addGuideline(g);
|
|
@@ -1781,6 +1936,7 @@ function serializeSkills(skills) {
|
|
|
1781
1936
|
name: skill.name,
|
|
1782
1937
|
description: skill.description,
|
|
1783
1938
|
filePath: skill.filePath,
|
|
1939
|
+
disableAutoInvocation: skill.disableAutoInvocation,
|
|
1784
1940
|
disableModelInvocation: skill.disableModelInvocation
|
|
1785
1941
|
}));
|
|
1786
1942
|
}
|
|
@@ -1903,80 +2059,9 @@ var createOutcome = {
|
|
|
1903
2059
|
}
|
|
1904
2060
|
};
|
|
1905
2061
|
|
|
1906
|
-
// src/harness/context/compaction.ts
|
|
1907
|
-
function needsCompaction(messages, options = {}) {
|
|
1908
|
-
const maxMessages = options.maxMessages ?? 50;
|
|
1909
|
-
return messages.length > maxMessages;
|
|
1910
|
-
}
|
|
1911
|
-
function buildSummary(messages) {
|
|
1912
|
-
const parts = [];
|
|
1913
|
-
let currentRole;
|
|
1914
|
-
let currentContent = [];
|
|
1915
|
-
function flush() {
|
|
1916
|
-
if (currentRole && currentContent.length > 0) {
|
|
1917
|
-
const combined = currentContent.join("\n");
|
|
1918
|
-
const truncated = combined.length > 500 ? combined.slice(0, 500) + "..." : combined;
|
|
1919
|
-
parts.push(`[${currentRole}]: ${truncated}`);
|
|
1920
|
-
}
|
|
1921
|
-
currentContent = [];
|
|
1922
|
-
}
|
|
1923
|
-
for (const msg of messages) {
|
|
1924
|
-
if (msg.role !== currentRole) {
|
|
1925
|
-
flush();
|
|
1926
|
-
currentRole = msg.role;
|
|
1927
|
-
}
|
|
1928
|
-
currentContent.push(messageContentText(msg.content));
|
|
1929
|
-
}
|
|
1930
|
-
flush();
|
|
1931
|
-
return parts.join("\n\n");
|
|
1932
|
-
}
|
|
1933
|
-
function compactMessages(messages, options = {}) {
|
|
1934
|
-
if (!needsCompaction(messages, options)) {
|
|
1935
|
-
return { compacted: false, messages };
|
|
1936
|
-
}
|
|
1937
|
-
const keepRecent = options.keepRecent ?? 10;
|
|
1938
|
-
const minCompact = options.minCompact ?? 20;
|
|
1939
|
-
const firstUserIdx = messages.findIndex(
|
|
1940
|
-
(m) => m.role === "user"
|
|
1941
|
-
);
|
|
1942
|
-
const recentStart = Math.max(messages.length - keepRecent, firstUserIdx + 1);
|
|
1943
|
-
const oldMessages = messages.slice(firstUserIdx >= 0 ? firstUserIdx + 1 : 0, recentStart);
|
|
1944
|
-
const protectedPhaseMessages = oldMessages.filter(
|
|
1945
|
-
(message) => message.metadata?.kind === "phase_prompt"
|
|
1946
|
-
);
|
|
1947
|
-
const summarizableMessages = oldMessages.filter(
|
|
1948
|
-
(message) => message.metadata?.kind !== "phase_prompt"
|
|
1949
|
-
);
|
|
1950
|
-
if (summarizableMessages.length < minCompact) {
|
|
1951
|
-
return { compacted: false, messages };
|
|
1952
|
-
}
|
|
1953
|
-
const recentMessages = messages.slice(recentStart);
|
|
1954
|
-
const summary = buildSummary(summarizableMessages);
|
|
1955
|
-
const result = [];
|
|
1956
|
-
if (firstUserIdx >= 0) {
|
|
1957
|
-
result.push(messages[firstUserIdx]);
|
|
1958
|
-
}
|
|
1959
|
-
result.push(
|
|
1960
|
-
createMessage("assistant", `[Context compaction summary]
|
|
1961
|
-
|
|
1962
|
-
${summary}`, {
|
|
1963
|
-
type: "compaction_summary",
|
|
1964
|
-
compactedCount: summarizableMessages.length
|
|
1965
|
-
})
|
|
1966
|
-
);
|
|
1967
|
-
result.push(...protectedPhaseMessages);
|
|
1968
|
-
result.push(...recentMessages);
|
|
1969
|
-
return {
|
|
1970
|
-
compacted: true,
|
|
1971
|
-
messages: result,
|
|
1972
|
-
summarizedCount: summarizableMessages.length,
|
|
1973
|
-
summary
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
2062
|
// src/loop/runners.ts
|
|
1978
2063
|
function resolvePhaseOutput(result) {
|
|
1979
|
-
return result
|
|
2064
|
+
return result ?? { message: "Phase completed.", route: "stop" };
|
|
1980
2065
|
}
|
|
1981
2066
|
function removePhaseMessage(messages, msgId) {
|
|
1982
2067
|
if (!msgId) return;
|
|
@@ -2092,12 +2177,18 @@ async function completeRun(config, state, outcome) {
|
|
|
2092
2177
|
return createRunResult(config, state, outcome);
|
|
2093
2178
|
}
|
|
2094
2179
|
function createTerminalOutcome(phase, output, transcript) {
|
|
2095
|
-
|
|
2180
|
+
const outcome = phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
|
|
2181
|
+
return phase.name === "compact" ? { ...outcome, display: false } : outcome;
|
|
2182
|
+
}
|
|
2183
|
+
function shouldRunStopPhase(phase, output) {
|
|
2184
|
+
if (phase.run || phase.factory) return false;
|
|
2185
|
+
return typeof output.message !== "string" || output.message.trim().length === 0;
|
|
2096
2186
|
}
|
|
2097
2187
|
function buildToolsWithRouting(config, availablePhases) {
|
|
2098
2188
|
const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
|
|
2099
|
-
|
|
2100
|
-
|
|
2189
|
+
const autoInvocablePhases = availablePhases.filter((phase) => phase.disableAutoInvocation !== true);
|
|
2190
|
+
if (autoInvocablePhases.length > 0) {
|
|
2191
|
+
tools.push(createRouteTool(autoInvocablePhases));
|
|
2101
2192
|
}
|
|
2102
2193
|
return tools;
|
|
2103
2194
|
}
|
|
@@ -2310,7 +2401,15 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2310
2401
|
while (currentPhaseId) {
|
|
2311
2402
|
const availablePhases = [];
|
|
2312
2403
|
for (const [, phase2] of registry.phases) {
|
|
2313
|
-
availablePhases.push({
|
|
2404
|
+
availablePhases.push({
|
|
2405
|
+
name: phase2.name,
|
|
2406
|
+
description: phase2.description,
|
|
2407
|
+
tools: phase2.tools,
|
|
2408
|
+
skills: phase2.skills,
|
|
2409
|
+
input: phase2.input,
|
|
2410
|
+
isolated: phase2.isolated,
|
|
2411
|
+
disableAutoInvocation: phase2.disableAutoInvocation
|
|
2412
|
+
});
|
|
2314
2413
|
}
|
|
2315
2414
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
2316
2415
|
if (abortResult.stopReason !== "none") {
|
|
@@ -2334,13 +2433,6 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2334
2433
|
}
|
|
2335
2434
|
state.attempt++;
|
|
2336
2435
|
state.metrics.iterations++;
|
|
2337
|
-
if (needsCompaction(config.context.messages)) {
|
|
2338
|
-
const compacted = compactMessages(config.context.messages);
|
|
2339
|
-
if (compacted.compacted) {
|
|
2340
|
-
config.context.messages = compacted.messages;
|
|
2341
|
-
state.metrics.compactionCount++;
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
2436
|
const phase = registry.phases.get(currentPhaseId);
|
|
2345
2437
|
if (!phase) {
|
|
2346
2438
|
throw new Error(`Phase "${currentPhaseId}" not found`);
|
|
@@ -2465,10 +2557,20 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2465
2557
|
}
|
|
2466
2558
|
}
|
|
2467
2559
|
}
|
|
2560
|
+
if (output.status) {
|
|
2561
|
+
await execution.reportStatus(output.status);
|
|
2562
|
+
}
|
|
2563
|
+
if (phase.name === STOP_PHASE_ID) {
|
|
2564
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2565
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2566
|
+
previousPhaseMsgId = void 0;
|
|
2567
|
+
previousPhaseInputMsgId = void 0;
|
|
2568
|
+
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2569
|
+
}
|
|
2468
2570
|
if (phase.run || phase.factory) {
|
|
2469
2571
|
if (output.route === void 0 && !routeDecision) output = { ...output, route: "stop" };
|
|
2470
2572
|
} else if (!phase.target && output.route === void 0 && !routeDecision) {
|
|
2471
|
-
const input = await awaitInput(output.message
|
|
2573
|
+
const input = await awaitInput(output.message ?? `${output.phase ?? "Phase"} completed.`, true);
|
|
2472
2574
|
if (input?.type === "completed") return input.result;
|
|
2473
2575
|
if (input?.type === "continued") {
|
|
2474
2576
|
isContinuing = true;
|
|
@@ -2519,11 +2621,16 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2519
2621
|
pendingInstruction = void 0;
|
|
2520
2622
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
2521
2623
|
if (entryPhaseId === "stop") {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2624
|
+
if (!shouldRunStopPhase(phase, output)) {
|
|
2625
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2626
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2627
|
+
previousPhaseMsgId = void 0;
|
|
2628
|
+
previousPhaseInputMsgId = void 0;
|
|
2629
|
+
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2630
|
+
}
|
|
2631
|
+
currentPhaseId = STOP_PHASE_ID;
|
|
2632
|
+
previousPayload = void 0;
|
|
2633
|
+
continue;
|
|
2527
2634
|
}
|
|
2528
2635
|
currentPhaseId = entryPhaseId;
|
|
2529
2636
|
previousPayload = void 0;
|
|
@@ -2538,11 +2645,18 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2538
2645
|
nextRoute = "stop";
|
|
2539
2646
|
}
|
|
2540
2647
|
if (nextRoute === "stop") {
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2648
|
+
if (!shouldRunStopPhase(phase, output)) {
|
|
2649
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2650
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2651
|
+
previousPhaseMsgId = void 0;
|
|
2652
|
+
previousPhaseInputMsgId = void 0;
|
|
2653
|
+
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2654
|
+
}
|
|
2655
|
+
currentPhaseId = STOP_PHASE_ID;
|
|
2656
|
+
previousPayload = output.payload;
|
|
2657
|
+
previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
|
|
2658
|
+
pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
|
|
2659
|
+
continue;
|
|
2546
2660
|
}
|
|
2547
2661
|
const targetPhaseId = nextRoute;
|
|
2548
2662
|
if (!registry.phases.has(targetPhaseId)) {
|
|
@@ -2606,7 +2720,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2606
2720
|
}
|
|
2607
2721
|
const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
|
|
2608
2722
|
options.onRetry?.(attempt, error, delayMs);
|
|
2609
|
-
await new Promise((
|
|
2723
|
+
await new Promise((resolve9) => setTimeout(resolve9, delayMs));
|
|
2610
2724
|
if (options.signal?.aborted) {
|
|
2611
2725
|
throw lastError;
|
|
2612
2726
|
}
|
|
@@ -2730,7 +2844,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2730
2844
|
state.currentPhase = snapshot.currentPhase;
|
|
2731
2845
|
state.metrics.iterations = snapshot.turnNumber;
|
|
2732
2846
|
},
|
|
2733
|
-
async invokeModel(phaseContext) {
|
|
2847
|
+
async invokeModel(phaseContext, options = {}) {
|
|
2734
2848
|
if (config.beforePrompt) {
|
|
2735
2849
|
phaseContext = await config.beforePrompt(phase.name, phaseContext);
|
|
2736
2850
|
}
|
|
@@ -2760,7 +2874,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2760
2874
|
config,
|
|
2761
2875
|
message: messageManager,
|
|
2762
2876
|
request,
|
|
2763
|
-
phaseId: phase.name
|
|
2877
|
+
phaseId: phase.name,
|
|
2878
|
+
output: options.output
|
|
2764
2879
|
}),
|
|
2765
2880
|
{
|
|
2766
2881
|
signal: config.signal,
|
|
@@ -2773,6 +2888,9 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2773
2888
|
await config.onModelTranscript?.(result.transcript, { phase: phase.name, model: phase.model ?? config.model });
|
|
2774
2889
|
return result;
|
|
2775
2890
|
},
|
|
2891
|
+
async reportStatus(status) {
|
|
2892
|
+
await config.onPhaseStatus?.(phase.name, status);
|
|
2893
|
+
},
|
|
2776
2894
|
async executeTool(phaseContext, toolCall) {
|
|
2777
2895
|
return runTurn(async () => {
|
|
2778
2896
|
await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
|
|
@@ -2862,12 +2980,13 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
|
|
|
2862
2980
|
if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
|
|
2863
2981
|
const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
|
|
2864
2982
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2865
|
-
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2983
|
+
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message ?? `${phase.name} completed.` };
|
|
2866
2984
|
}
|
|
2867
2985
|
function selectPhaseTools(candidates, names, includeRouting = true) {
|
|
2868
2986
|
const routing = includeRouting ? candidates.filter(({ name }) => name === PhaseRouteTool) : [];
|
|
2869
|
-
const
|
|
2870
|
-
|
|
2987
|
+
const core = candidates.filter(({ core: core2 }) => core2);
|
|
2988
|
+
const ordinary = candidates.filter(({ name, core: core2 }) => name !== PhaseRouteTool && !core2);
|
|
2989
|
+
return [...core, ...selectNamedResources(ordinary, names, "Tool"), ...routing];
|
|
2871
2990
|
}
|
|
2872
2991
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2873
2992
|
const entries = Array.from(backgroundTasks.entries());
|
|
@@ -3022,6 +3141,7 @@ async function executeOnce(input) {
|
|
|
3022
3141
|
onMessageDelta: input.onMessageDelta,
|
|
3023
3142
|
onOutcome: input.onOutcome,
|
|
3024
3143
|
onModelTranscript: input.onModelTranscript,
|
|
3144
|
+
onPhaseStatus: input.onPhaseStatus,
|
|
3025
3145
|
runtime: input.runtime,
|
|
3026
3146
|
waitForInput: async (nextState, request) => {
|
|
3027
3147
|
if (!nextState || !request) throw new Error("Input boundary did not include execution state.");
|
|
@@ -3762,6 +3882,7 @@ function projectToolContent(content) {
|
|
|
3762
3882
|
function projectTool(tool, agentId, runId) {
|
|
3763
3883
|
return {
|
|
3764
3884
|
name: tool.name,
|
|
3885
|
+
...tool.core ? { core: true } : {},
|
|
3765
3886
|
description: tool.description,
|
|
3766
3887
|
parameters: tool.parameters,
|
|
3767
3888
|
execute: async (args, context, signal) => {
|
|
@@ -3780,9 +3901,17 @@ function jsonText(value) {
|
|
|
3780
3901
|
}
|
|
3781
3902
|
|
|
3782
3903
|
// src/runtime/extensions.ts
|
|
3783
|
-
function assembleRegisteredExtensions(config, runner) {
|
|
3904
|
+
function assembleRegisteredExtensions(config, runner, options = {}) {
|
|
3784
3905
|
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3785
|
-
const
|
|
3906
|
+
const coreTools = createRuntimeCoreTools({
|
|
3907
|
+
root: config.cwd,
|
|
3908
|
+
...options.toolArchiveDir ? { archiveDir: options.toolArchiveDir } : {}
|
|
3909
|
+
});
|
|
3910
|
+
const coreNames = new Set(coreTools.map((tool) => tool.name));
|
|
3911
|
+
const tools = [
|
|
3912
|
+
...config.resources.tools.filter((tool) => !coreNames.has(tool.name)),
|
|
3913
|
+
...coreTools
|
|
3914
|
+
];
|
|
3786
3915
|
const names = new Set(tools.map((tool) => tool.name));
|
|
3787
3916
|
for (const tool of extensionTools) {
|
|
3788
3917
|
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
@@ -3813,11 +3942,13 @@ function assembleRegisteredExtensions(config, runner) {
|
|
|
3813
3942
|
};
|
|
3814
3943
|
}
|
|
3815
3944
|
function resolveDefinitionContext(config, assembled = {}) {
|
|
3816
|
-
const
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3945
|
+
const candidateTools = assembled.tools ?? config.resources.tools;
|
|
3946
|
+
const coreTools = candidateTools.filter((tool) => tool.core);
|
|
3947
|
+
const authoredTools = candidateTools.filter((tool) => !tool.core);
|
|
3948
|
+
const tools = [
|
|
3949
|
+
...selectNamedResources(authoredTools, config.definition.tools, "Tool"),
|
|
3950
|
+
...coreTools
|
|
3951
|
+
];
|
|
3821
3952
|
const skills = mergeSkills(
|
|
3822
3953
|
selectNamedResources(config.resources.skills, config.definition.skills, "Skill"),
|
|
3823
3954
|
config.definition.bundledSkills
|
|
@@ -3828,11 +3959,16 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3828
3959
|
"Context"
|
|
3829
3960
|
);
|
|
3830
3961
|
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3831
|
-
const
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3962
|
+
const phaseCandidates = [...candidateRegistry?.phases.values() ?? []];
|
|
3963
|
+
const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
|
|
3964
|
+
const selectedPhases = [
|
|
3965
|
+
...phaseCandidates.filter((phase) => phase.core || coreNames.has(phase.name)),
|
|
3966
|
+
...selectNamedResources(
|
|
3967
|
+
phaseCandidates.filter((phase) => !phase.core && !coreNames.has(phase.name)),
|
|
3968
|
+
config.definition.phases?.phaseIds,
|
|
3969
|
+
"Phase"
|
|
3970
|
+
)
|
|
3971
|
+
];
|
|
3836
3972
|
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3837
3973
|
const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
|
|
3838
3974
|
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
@@ -3847,15 +3983,23 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3847
3983
|
};
|
|
3848
3984
|
}
|
|
3849
3985
|
function mergePhases(base, extension) {
|
|
3850
|
-
|
|
3851
|
-
const
|
|
3986
|
+
const core = createCorePhases();
|
|
3987
|
+
const coreNames = new Set(core.map(({ name }) => name));
|
|
3988
|
+
const phases = new Map(core.map((phase) => [phase.name, phase]));
|
|
3989
|
+
for (const [name, phase] of base?.phases ?? []) {
|
|
3990
|
+
if (coreNames.has(name)) {
|
|
3991
|
+
if (!phase.core) throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
|
|
3992
|
+
continue;
|
|
3993
|
+
}
|
|
3994
|
+
phases.set(name, phase);
|
|
3995
|
+
}
|
|
3852
3996
|
for (const [name, phase] of extension.phases) {
|
|
3853
3997
|
if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
|
|
3854
3998
|
phases.set(name, phase);
|
|
3855
3999
|
}
|
|
3856
4000
|
return {
|
|
3857
4001
|
phases,
|
|
3858
|
-
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
4002
|
+
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
3859
4003
|
};
|
|
3860
4004
|
}
|
|
3861
4005
|
function adaptExtensionTool(input) {
|
|
@@ -3888,11 +4032,11 @@ function fromLoopResult(result) {
|
|
|
3888
4032
|
var MAX_BUFFERED_EVENTS = 128;
|
|
3889
4033
|
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3890
4034
|
function deferred() {
|
|
3891
|
-
let
|
|
4035
|
+
let resolve9;
|
|
3892
4036
|
const promise = new Promise((done) => {
|
|
3893
|
-
|
|
4037
|
+
resolve9 = done;
|
|
3894
4038
|
});
|
|
3895
|
-
return { promise, resolve:
|
|
4039
|
+
return { promise, resolve: resolve9 };
|
|
3896
4040
|
}
|
|
3897
4041
|
var TransientRunEventSubscription = class {
|
|
3898
4042
|
queue = [];
|
|
@@ -3917,11 +4061,14 @@ var TransientRunEventSubscription = class {
|
|
|
3917
4061
|
shift() {
|
|
3918
4062
|
return this.queue.shift();
|
|
3919
4063
|
}
|
|
3920
|
-
clear(executionId) {
|
|
4064
|
+
clear(executionId, preserveCompletedPhaseStatus = false) {
|
|
3921
4065
|
if (executionId === void 0) this.queue.length = 0;
|
|
3922
4066
|
else {
|
|
3923
4067
|
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3924
|
-
|
|
4068
|
+
const event = this.queue[index];
|
|
4069
|
+
if (event?.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
|
|
4070
|
+
this.queue.splice(index, 1);
|
|
4071
|
+
}
|
|
3925
4072
|
}
|
|
3926
4073
|
}
|
|
3927
4074
|
this.notify();
|
|
@@ -4003,9 +4150,9 @@ var TransientRunEventHub = class {
|
|
|
4003
4150
|
subscription.push(event);
|
|
4004
4151
|
}
|
|
4005
4152
|
}
|
|
4006
|
-
clear(runId, executionId) {
|
|
4153
|
+
clear(runId, executionId, preserveCompletedPhaseStatus = false) {
|
|
4007
4154
|
for (const subscription of this.subscriptions.get(runId) ?? []) {
|
|
4008
|
-
subscription.clear(executionId);
|
|
4155
|
+
subscription.clear(executionId, preserveCompletedPhaseStatus);
|
|
4009
4156
|
}
|
|
4010
4157
|
}
|
|
4011
4158
|
clearMessage(runId, messageId) {
|
|
@@ -4153,7 +4300,7 @@ function createEventBus() {
|
|
|
4153
4300
|
|
|
4154
4301
|
// src/extensions/runner.ts
|
|
4155
4302
|
async function execCommand(command, args, cwd, options) {
|
|
4156
|
-
return new Promise((
|
|
4303
|
+
return new Promise((resolve9, reject) => {
|
|
4157
4304
|
const child = execFile(
|
|
4158
4305
|
command,
|
|
4159
4306
|
args,
|
|
@@ -4168,7 +4315,7 @@ async function execCommand(command, args, cwd, options) {
|
|
|
4168
4315
|
reject(new Error("Command was aborted"));
|
|
4169
4316
|
return;
|
|
4170
4317
|
}
|
|
4171
|
-
|
|
4318
|
+
resolve9({
|
|
4172
4319
|
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
4173
4320
|
stdout: stdout ?? "",
|
|
4174
4321
|
stderr: stderr ?? ""
|
|
@@ -5026,6 +5173,8 @@ function snapshotResourceValue(kind, value) {
|
|
|
5026
5173
|
const phase = value;
|
|
5027
5174
|
const snapshot = {
|
|
5028
5175
|
...phase,
|
|
5176
|
+
disableAutoInvocation: phase.disableAutoInvocation ?? false,
|
|
5177
|
+
disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
|
|
5029
5178
|
...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
|
|
5030
5179
|
skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
|
|
5031
5180
|
...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
|
|
@@ -5051,7 +5200,7 @@ function capitalize(value) {
|
|
|
5051
5200
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
5052
5201
|
}
|
|
5053
5202
|
function isImplicitCoreName(kind, name) {
|
|
5054
|
-
return kind === "tool" && name === "route" || kind === "phase" && name === "default";
|
|
5203
|
+
return kind === "tool" && name === "route" || kind === "phase" && (name === "default" || name === "stop" || name === "compact");
|
|
5055
5204
|
}
|
|
5056
5205
|
async function loadDirectoryValues(kind, directory, sourceId) {
|
|
5057
5206
|
const root = resolve7(directory);
|
|
@@ -5235,7 +5384,7 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
|
|
|
5235
5384
|
/** Install Rowan-owned implicit resources before any host source is read. */
|
|
5236
5385
|
async ensureCoreResources() {
|
|
5237
5386
|
await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
|
|
5238
|
-
await this.replaceImplicit("phase", "rowan.core",
|
|
5387
|
+
await this.replaceImplicit("phase", "rowan.core", createCorePhases());
|
|
5239
5388
|
}
|
|
5240
5389
|
async loadExtensions(input) {
|
|
5241
5390
|
const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
|
|
@@ -5379,7 +5528,10 @@ function intersectPhaseSelection(parent, layer) {
|
|
|
5379
5528
|
};
|
|
5380
5529
|
}
|
|
5381
5530
|
function resolvePhases(candidates, selection) {
|
|
5382
|
-
const
|
|
5531
|
+
const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
|
|
5532
|
+
const core = candidates.filter((phase) => phase.core || coreNames.has(phase.name));
|
|
5533
|
+
const authored = candidates.filter((phase) => !coreNames.has(phase.name) && !phase.core);
|
|
5534
|
+
const selected = [...core, ...selectNamedResources(authored, selection?.phaseIds, "Phase")];
|
|
5383
5535
|
const entryPhaseId = selection?.entryPhaseId ?? null;
|
|
5384
5536
|
if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
|
|
5385
5537
|
console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
|
|
@@ -5401,6 +5553,31 @@ var DEFAULT_POLL_MS = 25;
|
|
|
5401
5553
|
var MAX_CONSUMER_IDLE_POLL_MS = 250;
|
|
5402
5554
|
var OWNER_LEASE_MS = 3e4;
|
|
5403
5555
|
var OWNER_RENEWAL_MS = 1e4;
|
|
5556
|
+
var MAX_INLINE_TOOL_RESULT_BYTES = 16 * 1024;
|
|
5557
|
+
async function spillLargeToolResult(result, archiveDir, toolName) {
|
|
5558
|
+
if (!archiveDir) return result;
|
|
5559
|
+
const serialized = typeof result.content === "string" ? result.content : JSON.stringify(result.content, null, 2);
|
|
5560
|
+
if (serialized === void 0) return result;
|
|
5561
|
+
const bytes = new TextEncoder().encode(serialized);
|
|
5562
|
+
if (bytes.byteLength <= MAX_INLINE_TOOL_RESULT_BYTES || /\nFull result:\s+\S+/.test(serialized)) {
|
|
5563
|
+
return result;
|
|
5564
|
+
}
|
|
5565
|
+
const toolDir = archiveDir;
|
|
5566
|
+
await mkdir2(toolDir, { recursive: true, mode: 448 });
|
|
5567
|
+
await chmod2(toolDir, 448).catch(() => void 0);
|
|
5568
|
+
const safeToolName = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
5569
|
+
const path = join5(toolDir, `rowan-${safeToolName}-${Date.now()}-${randomUUID()}.log`);
|
|
5570
|
+
await writeFile2(path, serialized, { encoding: "utf8", mode: 384 });
|
|
5571
|
+
await chmod2(path, 384).catch(() => void 0);
|
|
5572
|
+
const preview = new TextDecoder().decode(bytes.subarray(0, MAX_INLINE_TOOL_RESULT_BYTES));
|
|
5573
|
+
return {
|
|
5574
|
+
...result,
|
|
5575
|
+
content: `${preview}
|
|
5576
|
+
[truncated]
|
|
5577
|
+
Full result: ${path}
|
|
5578
|
+
Offset: 0`
|
|
5579
|
+
};
|
|
5580
|
+
}
|
|
5404
5581
|
var AgentRuntime = class _AgentRuntime {
|
|
5405
5582
|
concurrency;
|
|
5406
5583
|
owned;
|
|
@@ -5411,6 +5588,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5411
5588
|
executionDone = /* @__PURE__ */ new Map();
|
|
5412
5589
|
cancellationRequested = /* @__PURE__ */ new Set();
|
|
5413
5590
|
cancellationReasons = /* @__PURE__ */ new Map();
|
|
5591
|
+
autoCompactionRuns = /* @__PURE__ */ new Set();
|
|
5414
5592
|
consumers = /* @__PURE__ */ new Map();
|
|
5415
5593
|
transientEvents = new TransientRunEventHub();
|
|
5416
5594
|
resources;
|
|
@@ -5503,6 +5681,36 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5503
5681
|
this.assertOpen();
|
|
5504
5682
|
return this.owned.compact(input);
|
|
5505
5683
|
}
|
|
5684
|
+
async contextStatus(agentId, options = {}) {
|
|
5685
|
+
this.assertOpen();
|
|
5686
|
+
const agent = await this.requireAgent(agentId);
|
|
5687
|
+
const contextWindow = options.contextWindow ?? await this.resolveContextWindow(agent);
|
|
5688
|
+
return this.owned.contextStatus(agentId, contextWindow);
|
|
5689
|
+
}
|
|
5690
|
+
async compactContext(agentId, options = {}) {
|
|
5691
|
+
this.assertOpen();
|
|
5692
|
+
const agent = await this.requireAgent(agentId);
|
|
5693
|
+
if (!agent.activatedAt || !agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
|
|
5694
|
+
const active = (await this.owned.listRuns({ agentId })).filter((run2) => ["queued", "running", "input_required"].includes(run2.state));
|
|
5695
|
+
const existing = active.find((run2) => controlRunKind(run2) === "compact");
|
|
5696
|
+
if (existing) return new DurableRun(this, existing.id);
|
|
5697
|
+
const blocking = active.find((run2) => run2.state === "queued" || run2.state === "running");
|
|
5698
|
+
if (blocking) throw new RuntimeError("context_busy", { agentId, runId: blocking.id, actual: blocking.state });
|
|
5699
|
+
const metadata = {
|
|
5700
|
+
rowan: {
|
|
5701
|
+
kind: "compact",
|
|
5702
|
+
...options.instructions === void 0 ? {} : { instructions: options.instructions }
|
|
5703
|
+
}
|
|
5704
|
+
};
|
|
5705
|
+
const run = await this.owned.createRun({
|
|
5706
|
+
agentId,
|
|
5707
|
+
input: "",
|
|
5708
|
+
metadata,
|
|
5709
|
+
idempotencyKey: options.idempotencyKey ?? createId("compact")
|
|
5710
|
+
});
|
|
5711
|
+
void this.pump();
|
|
5712
|
+
return new DurableRun(this, run.id);
|
|
5713
|
+
}
|
|
5506
5714
|
async start(agentId, input, options) {
|
|
5507
5715
|
this.assertOpen();
|
|
5508
5716
|
const agent = await this.requireAgent(agentId);
|
|
@@ -5522,6 +5730,47 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5522
5730
|
this.assertOpen();
|
|
5523
5731
|
return pageRuns(await this.owned.listRuns(input), { ...input, storeIncarnation: this.storeIncarnation });
|
|
5524
5732
|
}
|
|
5733
|
+
async listInvocations(agentId, options) {
|
|
5734
|
+
this.assertOpen();
|
|
5735
|
+
if (!options || !["auto", "implicit", "external"].includes(options.source)) {
|
|
5736
|
+
throw new TypeError("options.source must be auto, implicit, or external");
|
|
5737
|
+
}
|
|
5738
|
+
const agent = await this.requireAgent(agentId);
|
|
5739
|
+
if (!agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
|
|
5740
|
+
const resolution = await this.commands.resolve({ agent, token: agent.currentConfigToken });
|
|
5741
|
+
if (resolution.kind !== "available") {
|
|
5742
|
+
throw new RuntimeError("configuration_unavailable", {
|
|
5743
|
+
agentId,
|
|
5744
|
+
retryable: resolution.kind === "deferred",
|
|
5745
|
+
reason: resolution.kind === "deferred" ? "Configuration is deferred." : resolution.reason
|
|
5746
|
+
});
|
|
5747
|
+
}
|
|
5748
|
+
const config = isAgentConfiguration(resolution.config) ? this.materializeConfig(resolution.config) : resolution.config;
|
|
5749
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
|
|
5750
|
+
toolArchiveDir: this.archiveDirFor(agentId)
|
|
5751
|
+
});
|
|
5752
|
+
const phases = [...assembly.context.phases?.phases.values() ?? []].filter((phase) => options.source === "external" || (options.source === "auto" ? !phase.disableAutoInvocation : !phase.disableImplicitInvocation)).filter((phase) => options.source !== "implicit" || phase.name !== DEFAULT_PHASE_ID);
|
|
5753
|
+
const skills = assembly.context.skills.filter((skill) => options.source === "external" || (options.source === "auto" ? !(skill.disableAutoInvocation ?? skill.disableModelInvocation) : !skill.disableImplicitInvocation));
|
|
5754
|
+
return [
|
|
5755
|
+
...phases.map((phase) => ({
|
|
5756
|
+
kind: "phase",
|
|
5757
|
+
name: phase.name,
|
|
5758
|
+
description: phase.description,
|
|
5759
|
+
...phase.core ? { core: true } : {},
|
|
5760
|
+
disableAutoInvocation: phase.disableAutoInvocation ?? false,
|
|
5761
|
+
disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
|
|
5762
|
+
filePath: phase.filePath
|
|
5763
|
+
})),
|
|
5764
|
+
...skills.map((skill) => ({
|
|
5765
|
+
kind: "skill",
|
|
5766
|
+
name: skill.name,
|
|
5767
|
+
description: skill.description,
|
|
5768
|
+
disableAutoInvocation: skill.disableAutoInvocation ?? skill.disableModelInvocation ?? false,
|
|
5769
|
+
disableImplicitInvocation: skill.disableImplicitInvocation ?? false,
|
|
5770
|
+
filePath: skill.filePath
|
|
5771
|
+
}))
|
|
5772
|
+
];
|
|
5773
|
+
}
|
|
5525
5774
|
async consume(input) {
|
|
5526
5775
|
this.assertOpen();
|
|
5527
5776
|
if (input.consumerId.trim().length === 0) throw new TypeError("consumerId must be non-empty");
|
|
@@ -5601,16 +5850,19 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5601
5850
|
try {
|
|
5602
5851
|
while (!this.closed && this.executions.size < this.concurrency) {
|
|
5603
5852
|
const queued = await this.owned.listRuns({ states: ["queued"] });
|
|
5604
|
-
const next = queued.
|
|
5853
|
+
const next = queued.filter((run) => !this.activeAgents.has(run.agentId)).sort((left, right) => Number(controlRunKind(right) === "compact") - Number(controlRunKind(left) === "compact"))[0];
|
|
5605
5854
|
if (!next) return;
|
|
5606
5855
|
this.activeAgents.add(next.agentId);
|
|
5607
5856
|
const task = this.execute(next).finally(() => {
|
|
5608
5857
|
const execution = this.executions.get(next.id);
|
|
5609
|
-
if (execution) this.transientEvents.clear(next.id, execution.executionId);
|
|
5858
|
+
if (execution) this.transientEvents.clear(next.id, execution.executionId, true);
|
|
5610
5859
|
this.activeAgents.delete(next.agentId);
|
|
5611
5860
|
this.executions.delete(next.id);
|
|
5612
5861
|
this.cancellationRequested.delete(next.id);
|
|
5613
5862
|
this.cancellationReasons.delete(next.id);
|
|
5863
|
+
void this.owned.snapshotRun(next.id).then((snapshot) => {
|
|
5864
|
+
if (snapshot.state !== "queued") this.autoCompactionRuns.delete(next.id);
|
|
5865
|
+
}).catch(() => void 0);
|
|
5614
5866
|
this.executionDone.delete(next.id);
|
|
5615
5867
|
void this.pump();
|
|
5616
5868
|
});
|
|
@@ -5667,34 +5919,81 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5667
5919
|
return;
|
|
5668
5920
|
}
|
|
5669
5921
|
}
|
|
5922
|
+
const config = resolvedConfig;
|
|
5923
|
+
const controlKind = controlRunKind(run);
|
|
5924
|
+
if (!controlKind && !this.autoCompactionRuns.has(run.id)) {
|
|
5925
|
+
const contextWindow = await resolveContextWindowForConfig(config);
|
|
5926
|
+
const status = await this.owned.contextStatus(run.agentId, contextWindow);
|
|
5927
|
+
const inputTokens = estimateRunInputTokens(run.input);
|
|
5928
|
+
if (status.thresholdTokens > 0 && status.tokens + inputTokens >= status.thresholdTokens) {
|
|
5929
|
+
this.autoCompactionRuns.add(run.id);
|
|
5930
|
+
await this.owned.createRun({
|
|
5931
|
+
agentId: run.agentId,
|
|
5932
|
+
input: "",
|
|
5933
|
+
metadata: {
|
|
5934
|
+
rowan: {
|
|
5935
|
+
kind: "compact",
|
|
5936
|
+
trigger: "auto",
|
|
5937
|
+
sourceRunId: String(run.id)
|
|
5938
|
+
}
|
|
5939
|
+
},
|
|
5940
|
+
idempotencyKey: `auto-compact:${run.id}`
|
|
5941
|
+
});
|
|
5942
|
+
return;
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5670
5945
|
const executionId = createId("exec");
|
|
5671
5946
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
5672
5947
|
executionRevision = claim.run.revision;
|
|
5673
5948
|
const controller = new AbortController();
|
|
5674
5949
|
this.executions.set(run.id, { controller, executionId });
|
|
5675
5950
|
if (this.cancellationRequested.has(run.id)) controller.abort();
|
|
5676
|
-
const
|
|
5677
|
-
|
|
5951
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
|
|
5952
|
+
toolArchiveDir: this.archiveDirFor(run.agentId)
|
|
5953
|
+
});
|
|
5678
5954
|
let toolQueue = Promise.resolve();
|
|
5679
5955
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
5680
5956
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5957
|
+
const phaseRegistry = normalizePhaseRegistry(assembly.context.phases);
|
|
5958
|
+
let modelMessages = await this.owned.contextMessages(run.agentId);
|
|
5959
|
+
const buildExecutionContext = (messages, phaseId) => {
|
|
5960
|
+
const executionContext2 = projectModelContext({
|
|
5961
|
+
context: {
|
|
5962
|
+
...assembly.context,
|
|
5963
|
+
phases: phaseRegistry
|
|
5964
|
+
},
|
|
5965
|
+
messages,
|
|
5966
|
+
agentId: run.agentId,
|
|
5967
|
+
runId: run.id
|
|
5968
|
+
});
|
|
5969
|
+
if (phaseId === COMPACT_PHASE_ID || controlKind === "compact") {
|
|
5970
|
+
executionContext2.phases = {
|
|
5971
|
+
...executionContext2.phases,
|
|
5972
|
+
entryPhaseId: COMPACT_PHASE_ID
|
|
5973
|
+
};
|
|
5974
|
+
const instructions = controlKind === "compact" ? compactInstructions(run) : void 0;
|
|
5975
|
+
if (instructions) {
|
|
5976
|
+
executionContext2.messages.push({
|
|
5977
|
+
id: createId("msg"),
|
|
5978
|
+
role: "user",
|
|
5979
|
+
content: `Additional compaction instructions:
|
|
5980
|
+
${instructions}`,
|
|
5981
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5982
|
+
metadata: { kind: "phase_input", phase: COMPACT_PHASE_ID }
|
|
5983
|
+
});
|
|
5984
|
+
}
|
|
5985
|
+
}
|
|
5986
|
+
return executionContext2;
|
|
5987
|
+
};
|
|
5988
|
+
let executionContext = buildExecutionContext(modelMessages);
|
|
5690
5989
|
const executionTools = {
|
|
5691
5990
|
tools: assembly.context.tools,
|
|
5692
5991
|
beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
|
|
5693
5992
|
afterToolCall: assembly.afterToolCall ?? config.afterToolCall
|
|
5694
5993
|
};
|
|
5695
|
-
const
|
|
5696
|
-
canonicalMessages:
|
|
5697
|
-
context
|
|
5994
|
+
const executeModel = (context) => executeOnce({
|
|
5995
|
+
canonicalMessages: context.messages,
|
|
5996
|
+
context,
|
|
5698
5997
|
execution: {
|
|
5699
5998
|
agentId: run.agentId,
|
|
5700
5999
|
runId: run.id,
|
|
@@ -5703,7 +6002,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5703
6002
|
...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
|
|
5704
6003
|
},
|
|
5705
6004
|
model,
|
|
5706
|
-
thinkingLevel: thinkingLevelFromMessages(
|
|
6005
|
+
thinkingLevel: thinkingLevelFromMessages(context.messages),
|
|
5707
6006
|
stream,
|
|
5708
6007
|
maxAttempts: config.maxAttempts,
|
|
5709
6008
|
checkpoint: claim.run.checkpoint,
|
|
@@ -5712,6 +6011,18 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5712
6011
|
beforePhase: assembly.beforePhase,
|
|
5713
6012
|
afterPhase: assembly.afterPhase,
|
|
5714
6013
|
beforePrompt: assembly.beforePrompt,
|
|
6014
|
+
onPhaseStatus: (phaseId, status) => {
|
|
6015
|
+
const active = this.executions.get(run.id);
|
|
6016
|
+
if (active?.executionId !== claim.execution.executionId) return;
|
|
6017
|
+
this.transientEvents.publish({
|
|
6018
|
+
kind: "phase_status",
|
|
6019
|
+
durability: "transient",
|
|
6020
|
+
runId: run.id,
|
|
6021
|
+
executionId: claim.execution.executionId,
|
|
6022
|
+
phaseId,
|
|
6023
|
+
status
|
|
6024
|
+
});
|
|
6025
|
+
},
|
|
5715
6026
|
onMessageDelta: (event) => {
|
|
5716
6027
|
const active = this.executions.get(run.id);
|
|
5717
6028
|
if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
|
|
@@ -5763,8 +6074,27 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5763
6074
|
}
|
|
5764
6075
|
}
|
|
5765
6076
|
});
|
|
6077
|
+
let result = await executeModel(executionContext);
|
|
6078
|
+
if (!controlKind && result.type === "failed" && isContextOverflowError(result.error)) {
|
|
6079
|
+
const compactContext = buildExecutionContext(await this.owned.contextMessages(run.agentId), COMPACT_PHASE_ID);
|
|
6080
|
+
const compactResult = await executeModel(compactContext);
|
|
6081
|
+
const summary = compactResult.type === "completed" ? compactSummary(compactResult.outcome.payload) : void 0;
|
|
6082
|
+
if (summary) {
|
|
6083
|
+
const covered = claim.history.at(-1);
|
|
6084
|
+
await this.owned.commitContextCompaction({
|
|
6085
|
+
id: createId("compact"),
|
|
6086
|
+
agentId: run.agentId,
|
|
6087
|
+
summary,
|
|
6088
|
+
...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
|
|
6089
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6090
|
+
});
|
|
6091
|
+
modelMessages = await this.owned.contextMessages(run.agentId);
|
|
6092
|
+
executionContext = buildExecutionContext(modelMessages);
|
|
6093
|
+
result = await executeModel(executionContext);
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
5766
6096
|
if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
|
|
5767
|
-
const output = latestAssistant(run, result.messages,
|
|
6097
|
+
const output = latestAssistant(run, result.messages, modelMessages.length, true);
|
|
5768
6098
|
const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
|
|
5769
6099
|
await this.owned.cancelRun({
|
|
5770
6100
|
runId: run.id,
|
|
@@ -5792,9 +6122,24 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5792
6122
|
return;
|
|
5793
6123
|
}
|
|
5794
6124
|
if (result.type === "completed") {
|
|
5795
|
-
const output = latestAssistant(run, result.messages,
|
|
6125
|
+
const output = latestAssistant(run, result.messages.slice(modelMessages.length), modelMessages.length);
|
|
6126
|
+
if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
|
|
6127
|
+
const summary = compactSummary(result.outcome.payload);
|
|
6128
|
+
if (summary) {
|
|
6129
|
+
const covered = claim.history.at(-1);
|
|
6130
|
+
const record = {
|
|
6131
|
+
id: createId("compact"),
|
|
6132
|
+
agentId: run.agentId,
|
|
6133
|
+
summary,
|
|
6134
|
+
...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
|
|
6135
|
+
...compactInstructions(run) ? { instructions: compactInstructions(run) } : {},
|
|
6136
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6137
|
+
};
|
|
6138
|
+
await this.owned.commitContextCompaction(record);
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
5796
6141
|
await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
|
|
5797
|
-
this.transientEvents.clear(run.id, claim.execution.executionId);
|
|
6142
|
+
this.transientEvents.clear(run.id, claim.execution.executionId, true);
|
|
5798
6143
|
return;
|
|
5799
6144
|
}
|
|
5800
6145
|
const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
|
|
@@ -5827,6 +6172,13 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5827
6172
|
if (!(error instanceof RuntimeError) || !["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) throw error;
|
|
5828
6173
|
});
|
|
5829
6174
|
}
|
|
6175
|
+
async resolveContextWindow(agent) {
|
|
6176
|
+
if (!agent.currentConfigToken) return 128e3;
|
|
6177
|
+
const resolution = await this.commands.resolve({ agent, token: agent.currentConfigToken });
|
|
6178
|
+
if (resolution.kind !== "available") return 128e3;
|
|
6179
|
+
const config = isAgentConfiguration(resolution.config) ? this.materializeConfig(resolution.config) : resolution.config;
|
|
6180
|
+
return resolveContextWindowForConfig(config);
|
|
6181
|
+
}
|
|
5830
6182
|
async executeTool(input) {
|
|
5831
6183
|
const batch = await this.executeToolBatch({ ...input, toolCalls: [input.toolCall] });
|
|
5832
6184
|
return { result: batch.results[0], revision: batch.revision };
|
|
@@ -5917,6 +6269,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5917
6269
|
assertToolExecutionResult(result);
|
|
5918
6270
|
if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
|
|
5919
6271
|
assertToolExecutionResult(result);
|
|
6272
|
+
result = await spillLargeToolResult(
|
|
6273
|
+
result,
|
|
6274
|
+
this.archiveDirFor(input.run.agentId),
|
|
6275
|
+
input.toolCall.name
|
|
6276
|
+
);
|
|
5920
6277
|
const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
|
|
5921
6278
|
return {
|
|
5922
6279
|
result: { toolCallId: providerToolCallId, toolName: input.toolCall.name, ...result },
|
|
@@ -5936,6 +6293,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5936
6293
|
if (!agent) throw new RuntimeError("agent_not_found", { agentId });
|
|
5937
6294
|
return agent;
|
|
5938
6295
|
}
|
|
6296
|
+
archiveDirFor(agentId) {
|
|
6297
|
+
const store = this.owned;
|
|
6298
|
+
const archiveDir = store.contextArchiveDir?.(agentId);
|
|
6299
|
+
return archiveDir ? join5(archiveDir, "tool-results") : void 0;
|
|
6300
|
+
}
|
|
5939
6301
|
async runConsumer(subscription) {
|
|
5940
6302
|
const registration = await this.owned.openConsumer(subscription.consumerId);
|
|
5941
6303
|
let cursor = registration.cursor;
|
|
@@ -5977,10 +6339,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5977
6339
|
const subscription = this.transientEvents.subscribe(runId);
|
|
5978
6340
|
try {
|
|
5979
6341
|
while (true) {
|
|
5980
|
-
if (options.signal?.aborted) throw abortError();
|
|
5981
6342
|
const observationVersion = subscription.checkpoint();
|
|
5982
6343
|
const snapshot = await this.owned.snapshotRun(runId);
|
|
5983
|
-
|
|
6344
|
+
const terminal = ["completed", "failed", "cancelled"].includes(snapshot.state);
|
|
6345
|
+
if (options.signal?.aborted && !terminal) throw abortError();
|
|
6346
|
+
if (terminal) {
|
|
5984
6347
|
const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
|
|
5985
6348
|
if (!pending.some((event) => event.runId === runId)) return;
|
|
5986
6349
|
}
|
|
@@ -5993,8 +6356,16 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5993
6356
|
} else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
|
|
5994
6357
|
subscription.clearTool(event.toolCall.id);
|
|
5995
6358
|
}
|
|
6359
|
+
if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) {
|
|
6360
|
+
while (true) {
|
|
6361
|
+
const transient2 = subscription.shift();
|
|
6362
|
+
if (!transient2) break;
|
|
6363
|
+
yield transient2;
|
|
6364
|
+
}
|
|
6365
|
+
yield event;
|
|
6366
|
+
return;
|
|
6367
|
+
}
|
|
5996
6368
|
yield event;
|
|
5997
|
-
if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) return;
|
|
5998
6369
|
}
|
|
5999
6370
|
const transient = subscription.shift();
|
|
6000
6371
|
if (transient) {
|
|
@@ -6098,21 +6469,67 @@ function boundaryFromSnapshot(snapshot) {
|
|
|
6098
6469
|
}
|
|
6099
6470
|
}
|
|
6100
6471
|
function delay(ms) {
|
|
6101
|
-
return new Promise((
|
|
6472
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
6473
|
+
}
|
|
6474
|
+
function controlRunKind(run) {
|
|
6475
|
+
const rowan = run.metadata?.rowan;
|
|
6476
|
+
if (typeof rowan !== "object" || rowan === null || !("kind" in rowan)) return void 0;
|
|
6477
|
+
const kind = rowan.kind;
|
|
6478
|
+
return typeof kind === "string" ? kind : void 0;
|
|
6479
|
+
}
|
|
6480
|
+
function compactInstructions(run) {
|
|
6481
|
+
const rowan = run.metadata?.rowan;
|
|
6482
|
+
if (typeof rowan !== "object" || rowan === null || !("instructions" in rowan)) return void 0;
|
|
6483
|
+
const instructions = rowan.instructions;
|
|
6484
|
+
return typeof instructions === "string" && instructions.trim().length > 0 ? instructions : void 0;
|
|
6485
|
+
}
|
|
6486
|
+
function compactSummary(payload) {
|
|
6487
|
+
if (typeof payload !== "object" || payload === null || !("summary" in payload)) return void 0;
|
|
6488
|
+
const summary = payload.summary;
|
|
6489
|
+
return typeof summary === "string" && summary.trim().length > 0 ? summary : void 0;
|
|
6490
|
+
}
|
|
6491
|
+
function isCompactionOutcome(payload) {
|
|
6492
|
+
return typeof payload === "object" && payload !== null && "kind" in payload && payload.kind === "context_compaction";
|
|
6493
|
+
}
|
|
6494
|
+
async function resolveContextWindowForConfig(config) {
|
|
6495
|
+
if ("contextWindow" in config.model && typeof config.model.contextWindow === "number") {
|
|
6496
|
+
return config.model.contextWindow;
|
|
6497
|
+
}
|
|
6498
|
+
return 128e3;
|
|
6499
|
+
}
|
|
6500
|
+
function estimateRunInputTokens(input) {
|
|
6501
|
+
const content = typeof input === "string" ? input : input.content;
|
|
6502
|
+
const text = typeof content === "string" ? content : content.map((part) => part.type === "text" ? part.text : part.data).join("\n");
|
|
6503
|
+
return Math.ceil(text.length / 4) + 20;
|
|
6504
|
+
}
|
|
6505
|
+
function isContextOverflowError(error) {
|
|
6506
|
+
const message = error instanceof Error ? `${error.name} ${error.message}` : typeof error === "string" ? error : (() => {
|
|
6507
|
+
try {
|
|
6508
|
+
return JSON.stringify(error);
|
|
6509
|
+
} catch {
|
|
6510
|
+
return String(error);
|
|
6511
|
+
}
|
|
6512
|
+
})();
|
|
6513
|
+
return /context(?:\s|_|-)?(?:length|window|limit|overflow|exceed)|(?:maximum|max).{0,24}tokens|too many tokens|prompt.{0,24}(?:too large|exceed|limit)/i.test(message);
|
|
6102
6514
|
}
|
|
6103
6515
|
function normalizePhaseRegistry(registry) {
|
|
6104
|
-
|
|
6105
|
-
|
|
6516
|
+
const core = createCorePhases();
|
|
6517
|
+
const coreNames = new Set(core.map(({ name }) => name));
|
|
6518
|
+
for (const [name, phase] of registry?.phases ?? []) {
|
|
6519
|
+
if (coreNames.has(name) && !phase.core) {
|
|
6520
|
+
throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
|
|
6521
|
+
}
|
|
6106
6522
|
}
|
|
6107
6523
|
for (const [name] of registry?.phases ?? []) {
|
|
6108
|
-
if (name === "
|
|
6524
|
+
if (name === "continue") {
|
|
6109
6525
|
throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
|
|
6110
6526
|
}
|
|
6111
6527
|
}
|
|
6528
|
+
const authored = [...registry?.phases ?? []].filter(([name, phase]) => !coreNames.has(name) || phase.core === false);
|
|
6112
6529
|
return {
|
|
6113
6530
|
phases: new Map([
|
|
6114
|
-
[
|
|
6115
|
-
...
|
|
6531
|
+
...core.map((phase) => [phase.name, phase]),
|
|
6532
|
+
...authored
|
|
6116
6533
|
]),
|
|
6117
6534
|
entryPhaseId: registry?.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
6118
6535
|
};
|
|
@@ -6130,13 +6547,13 @@ function toJsonValue(value) {
|
|
|
6130
6547
|
return value;
|
|
6131
6548
|
}
|
|
6132
6549
|
function deferred2() {
|
|
6133
|
-
let
|
|
6550
|
+
let resolve9;
|
|
6134
6551
|
let reject;
|
|
6135
6552
|
const promise = new Promise((res, rej) => {
|
|
6136
|
-
|
|
6553
|
+
resolve9 = (value) => res(value);
|
|
6137
6554
|
reject = rej;
|
|
6138
6555
|
});
|
|
6139
|
-
return { promise, resolve:
|
|
6556
|
+
return { promise, resolve: resolve9, reject };
|
|
6140
6557
|
}
|
|
6141
6558
|
|
|
6142
6559
|
// src/runtime/durable-store.ts
|
|
@@ -6158,6 +6575,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6158
6575
|
ownerEpoch = 0;
|
|
6159
6576
|
eventSequence = 0;
|
|
6160
6577
|
retentionFloor = 1;
|
|
6578
|
+
contextCompactions = /* @__PURE__ */ new Map();
|
|
6161
6579
|
constructor(options = {}) {
|
|
6162
6580
|
this.incarnation = options.incarnation ?? createId("store");
|
|
6163
6581
|
}
|
|
@@ -6176,6 +6594,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6176
6594
|
for (const [agentId, sequence] of state.nextReadySequence) store.nextReadySequence.set(agentId, sequence);
|
|
6177
6595
|
store.eventSequence = state.eventSequence;
|
|
6178
6596
|
store.retentionFloor = Math.max(1, state.retentionFloor ?? 1);
|
|
6597
|
+
for (const [agentId, record] of state.contextCompactions ?? []) store.contextCompactions.set(agentId, clone(record));
|
|
6179
6598
|
store.ownerEpoch = Math.max(
|
|
6180
6599
|
0,
|
|
6181
6600
|
...state.runs.map((run) => run.execution?.ownerEpoch ?? 0)
|
|
@@ -6197,7 +6616,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6197
6616
|
retentionFloor: this.retentionFloor,
|
|
6198
6617
|
nextAgentSequence: [...this.nextAgentSequence.entries()],
|
|
6199
6618
|
nextReadySequence: [...this.nextReadySequence.entries()],
|
|
6200
|
-
eventSequence: this.eventSequence
|
|
6619
|
+
eventSequence: this.eventSequence,
|
|
6620
|
+
contextCompactions: [...this.contextCompactions.entries()]
|
|
6201
6621
|
});
|
|
6202
6622
|
}
|
|
6203
6623
|
attachOwner(lease) {
|
|
@@ -6376,6 +6796,18 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6376
6796
|
actual: -1
|
|
6377
6797
|
});
|
|
6378
6798
|
}
|
|
6799
|
+
const coveredThrough = this.contextCompactions.get(agent.id)?.coveredThrough;
|
|
6800
|
+
if (coveredThrough) {
|
|
6801
|
+
const history = this.activeHistory(agent.id, Number.MAX_SAFE_INTEGER);
|
|
6802
|
+
const coveredIndex = history.findIndex((message) => message.id === coveredThrough.messageId);
|
|
6803
|
+
const originalIndex = history.findIndex((message) => message.id === original.id);
|
|
6804
|
+
if (coveredIndex >= 0 && originalIndex >= 0 && originalIndex <= coveredIndex) {
|
|
6805
|
+
throw new RuntimeError("message_history_compacted", {
|
|
6806
|
+
agentId: agent.id,
|
|
6807
|
+
messageId: original.id
|
|
6808
|
+
});
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6379
6811
|
const actualRevision = original.messageRevision ?? 0;
|
|
6380
6812
|
if (actualRevision !== input.expectedMessageRevision) {
|
|
6381
6813
|
throw new RuntimeError("message_revision_conflict", {
|
|
@@ -6519,10 +6951,10 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6519
6951
|
const run = this.requireRun(input.runId);
|
|
6520
6952
|
this.assertRevision(run, input.expectedRevision);
|
|
6521
6953
|
this.assertState(run, ["queued"]);
|
|
6522
|
-
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && candidate.agentSequence < run.agentSequence && !["completed", "failed", "cancelled"].includes(candidate.state))) {
|
|
6954
|
+
if (!isControlRun(run) && [...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && candidate.agentSequence < run.agentSequence && !["completed", "failed", "cancelled"].includes(candidate.state))) {
|
|
6523
6955
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6524
6956
|
}
|
|
6525
|
-
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id &&
|
|
6957
|
+
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && (candidate.state === "running" || !isControlRun(run) && candidate.state === "input_required"))) {
|
|
6526
6958
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6527
6959
|
}
|
|
6528
6960
|
if (run.pinnedConfigToken && input.configToken && run.pinnedConfigToken !== input.configToken) {
|
|
@@ -6530,7 +6962,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6530
6962
|
}
|
|
6531
6963
|
if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
|
|
6532
6964
|
let message;
|
|
6533
|
-
if (!run.checkpoint && !run.initialMessageId) {
|
|
6965
|
+
if (!run.checkpoint && !run.initialMessageId && !isControlRun(run)) {
|
|
6534
6966
|
const userInput = normalizeUserInput(run.input);
|
|
6535
6967
|
message = {
|
|
6536
6968
|
id: input.messageId ?? createId("msg"),
|
|
@@ -7205,6 +7637,72 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
7205
7637
|
this.requireAgent(agentId);
|
|
7206
7638
|
return this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
|
|
7207
7639
|
}
|
|
7640
|
+
contextStatus(lease, agentId, contextWindow) {
|
|
7641
|
+
this.assertOwner(lease);
|
|
7642
|
+
this.requireAgent(agentId);
|
|
7643
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
7644
|
+
throw new TypeError("contextWindow must be a positive finite number");
|
|
7645
|
+
}
|
|
7646
|
+
const messages = this.contextMessages(lease, agentId);
|
|
7647
|
+
const tokens = estimateMessageTokens(messages);
|
|
7648
|
+
const record = this.contextCompactions.get(agentId);
|
|
7649
|
+
const thresholdTokens = Math.max(0, Math.floor(contextWindow) - 16384);
|
|
7650
|
+
return {
|
|
7651
|
+
tokens,
|
|
7652
|
+
contextWindow: Math.floor(contextWindow),
|
|
7653
|
+
percent: Math.round(tokens / contextWindow * 100),
|
|
7654
|
+
thresholdTokens,
|
|
7655
|
+
estimated: true,
|
|
7656
|
+
...record?.coveredThrough ? { coveredThrough: clone(record.coveredThrough) } : {}
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
contextMessages(lease, agentId, recentTokenBudget = 2e4) {
|
|
7660
|
+
this.assertOwner(lease);
|
|
7661
|
+
this.requireAgent(agentId);
|
|
7662
|
+
if (!Number.isFinite(recentTokenBudget) || recentTokenBudget <= 0) {
|
|
7663
|
+
throw new TypeError("recentTokenBudget must be a positive finite number");
|
|
7664
|
+
}
|
|
7665
|
+
const history = this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
|
|
7666
|
+
const record = this.contextCompactions.get(agentId);
|
|
7667
|
+
if (!record) return history;
|
|
7668
|
+
const coveredIndex = record.coveredThrough ? history.findIndex((message) => message.id === record.coveredThrough.messageId) : -1;
|
|
7669
|
+
const suffix = coveredIndex >= 0 ? history.slice(coveredIndex + 1) : history;
|
|
7670
|
+
const recent = [];
|
|
7671
|
+
let tokens = 0;
|
|
7672
|
+
for (let index = suffix.length - 1; index >= 0; index -= 1) {
|
|
7673
|
+
const message = suffix[index];
|
|
7674
|
+
const messageTokens = estimateMessageTokens([message]);
|
|
7675
|
+
if (recent.length > 0 && tokens + messageTokens > recentTokenBudget) break;
|
|
7676
|
+
recent.unshift(message);
|
|
7677
|
+
tokens += messageTokens;
|
|
7678
|
+
}
|
|
7679
|
+
const covered = record.coveredThrough ? history.find((message) => message.id === record.coveredThrough.messageId) : void 0;
|
|
7680
|
+
const summary = {
|
|
7681
|
+
id: `context-summary:${record.id}`,
|
|
7682
|
+
agentId,
|
|
7683
|
+
runId: covered?.runId ?? history.at(-1)?.runId ?? `context:${agentId}`,
|
|
7684
|
+
role: "assistant",
|
|
7685
|
+
content: `[Context summary]
|
|
7686
|
+
|
|
7687
|
+
${record.summary}`,
|
|
7688
|
+
sequenceWithinRun: covered?.sequenceWithinRun ?? 0,
|
|
7689
|
+
createdAt: record.createdAt,
|
|
7690
|
+
metadata: { kind: "context_summary", compactionId: record.id }
|
|
7691
|
+
};
|
|
7692
|
+
return [summary, ...recent];
|
|
7693
|
+
}
|
|
7694
|
+
commitContextCompaction(lease, record) {
|
|
7695
|
+
this.assertOwner(lease);
|
|
7696
|
+
this.requireAgent(record.agentId);
|
|
7697
|
+
if (record.id.trim().length === 0 || record.summary.trim().length === 0) {
|
|
7698
|
+
throw new TypeError("context compaction id and summary must be non-empty");
|
|
7699
|
+
}
|
|
7700
|
+
const existing = this.contextCompactions.get(record.agentId);
|
|
7701
|
+
if (existing?.id === record.id) return clone(existing);
|
|
7702
|
+
const next = clone(record);
|
|
7703
|
+
this.contextCompactions.set(record.agentId, next);
|
|
7704
|
+
return clone(next);
|
|
7705
|
+
}
|
|
7208
7706
|
activeHistory(agentId, beforeSequence) {
|
|
7209
7707
|
return clone([
|
|
7210
7708
|
...this.historySeeds.get(agentId) ?? [],
|
|
@@ -7287,6 +7785,15 @@ var MemoryOwnedStore = class {
|
|
|
7287
7785
|
async compact(input) {
|
|
7288
7786
|
return this.store.compact(this.lease, input);
|
|
7289
7787
|
}
|
|
7788
|
+
async contextStatus(agentId, contextWindow) {
|
|
7789
|
+
return this.store.contextStatus(this.lease, agentId, contextWindow);
|
|
7790
|
+
}
|
|
7791
|
+
async contextMessages(agentId, recentTokenBudget) {
|
|
7792
|
+
return this.store.contextMessages(this.lease, agentId, recentTokenBudget);
|
|
7793
|
+
}
|
|
7794
|
+
async commitContextCompaction(record) {
|
|
7795
|
+
return this.store.commitContextCompaction(this.lease, record);
|
|
7796
|
+
}
|
|
7290
7797
|
async createRun(input) {
|
|
7291
7798
|
return this.store.createRun(this.lease, input);
|
|
7292
7799
|
}
|
|
@@ -7376,6 +7883,29 @@ function userInputContent(input) {
|
|
|
7376
7883
|
function userInputMetadata(input) {
|
|
7377
7884
|
return typeof input === "string" ? void 0 : input.metadata;
|
|
7378
7885
|
}
|
|
7886
|
+
function isControlRun(run) {
|
|
7887
|
+
const rowan = run.metadata?.rowan;
|
|
7888
|
+
return typeof rowan === "object" && rowan !== null && "kind" in rowan && rowan.kind === "compact";
|
|
7889
|
+
}
|
|
7890
|
+
function estimateMessageTokens(messages) {
|
|
7891
|
+
let characters = 0;
|
|
7892
|
+
for (const message of messages) {
|
|
7893
|
+
characters += messageContentText2(message.content).length;
|
|
7894
|
+
characters += 20;
|
|
7895
|
+
}
|
|
7896
|
+
return Math.ceil(characters / 4);
|
|
7897
|
+
}
|
|
7898
|
+
function messageContentText2(content) {
|
|
7899
|
+
if (typeof content === "string") return content;
|
|
7900
|
+
return content.map((part) => {
|
|
7901
|
+
if (part.type === "text") return part.text;
|
|
7902
|
+
if (part.type === "thinking") return part.thinking;
|
|
7903
|
+
if (part.type === "tool_use") return JSON.stringify(part.input);
|
|
7904
|
+
if (part.type === "tool_result") return JSON.stringify(part.result);
|
|
7905
|
+
if (part.type === "image") return `[image:${part.mimeType}]`;
|
|
7906
|
+
return "";
|
|
7907
|
+
}).join("\n");
|
|
7908
|
+
}
|
|
7379
7909
|
function targetInput(input, metadata) {
|
|
7380
7910
|
const content = typeof input === "string" ? input : input.content;
|
|
7381
7911
|
return metadata === void 0 ? { content } : { content, metadata: clone(metadata) };
|
|
@@ -7417,6 +7947,9 @@ function assertToolValue(value, argument) {
|
|
|
7417
7947
|
|
|
7418
7948
|
// src/runtime/sqlite-durable-store.ts
|
|
7419
7949
|
import { Database } from "bun:sqlite";
|
|
7950
|
+
import { appendFileSync, chmodSync, mkdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
7951
|
+
import { dirname as dirname7, join as join6, resolve as resolve8 } from "path";
|
|
7952
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7420
7953
|
var SCHEMA_ID = "rowan.agent.runtime";
|
|
7421
7954
|
var SCHEMA_VERSION = "2";
|
|
7422
7955
|
var SCHEMA_VALUE = `${SCHEMA_ID}:${SCHEMA_VERSION}`;
|
|
@@ -7521,10 +8054,15 @@ var CURRENT_SCHEMA_SQL = `
|
|
|
7521
8054
|
`;
|
|
7522
8055
|
var SqliteStore = class {
|
|
7523
8056
|
database;
|
|
8057
|
+
archiveRoot;
|
|
8058
|
+
mirroredEventIds = /* @__PURE__ */ new Map();
|
|
8059
|
+
mirroredCompactionIds = /* @__PURE__ */ new Map();
|
|
8060
|
+
mirroredAgents = /* @__PURE__ */ new Set();
|
|
7524
8061
|
closed = false;
|
|
7525
8062
|
configured = false;
|
|
7526
8063
|
constructor(filename = ":memory:") {
|
|
7527
8064
|
this.database = new Database(filename, { create: true, readwrite: true, strict: true });
|
|
8065
|
+
this.archiveRoot = filename === ":memory:" ? join6(tmpdir2(), `rowan-context-archives-${crypto.randomUUID()}`) : join6(dirname7(resolve8(filename)), "context-archives");
|
|
7528
8066
|
}
|
|
7529
8067
|
async openOwner(input) {
|
|
7530
8068
|
this.assertOpen();
|
|
@@ -7576,6 +8114,10 @@ var SqliteStore = class {
|
|
|
7576
8114
|
this.closed = true;
|
|
7577
8115
|
this.database.close();
|
|
7578
8116
|
}
|
|
8117
|
+
/** Filesystem location owned by this Store for one Agent's readable archive. */
|
|
8118
|
+
contextArchiveDir(agentId) {
|
|
8119
|
+
return join6(this.archiveRoot, String(agentId));
|
|
8120
|
+
}
|
|
7579
8121
|
async reserveAgent(lease, input) {
|
|
7580
8122
|
return this.invoke(lease, (store, current) => store.reserveAgent(current, input));
|
|
7581
8123
|
}
|
|
@@ -7591,6 +8133,15 @@ var SqliteStore = class {
|
|
|
7591
8133
|
async reviseMessage(lease, input) {
|
|
7592
8134
|
return this.invoke(lease, (store, current) => store.reviseMessage(current, input));
|
|
7593
8135
|
}
|
|
8136
|
+
async contextStatus(lease, agentId, contextWindow) {
|
|
8137
|
+
return this.invoke(lease, (store, current) => store.contextStatus(current, agentId, contextWindow), false);
|
|
8138
|
+
}
|
|
8139
|
+
async contextMessages(lease, agentId, recentTokenBudget) {
|
|
8140
|
+
return this.invoke(lease, (store, current) => store.contextMessages(current, agentId, recentTokenBudget), false);
|
|
8141
|
+
}
|
|
8142
|
+
async commitContextCompaction(lease, record) {
|
|
8143
|
+
return this.invoke(lease, (store, current) => store.commitContextCompaction(current, record));
|
|
8144
|
+
}
|
|
7594
8145
|
async createRun(lease, input) {
|
|
7595
8146
|
return this.invoke(lease, (store, current) => store.createRun(current, input));
|
|
7596
8147
|
}
|
|
@@ -7838,6 +8389,64 @@ var SqliteStore = class {
|
|
|
7838
8389
|
[consumerId, Number(String(cursor).split(":").at(-1)), null, (/* @__PURE__ */ new Date()).toISOString()]
|
|
7839
8390
|
);
|
|
7840
8391
|
}
|
|
8392
|
+
this.mirrorState(state);
|
|
8393
|
+
}
|
|
8394
|
+
/** Append the semantic durable event mirror and remove deleted Agent sidecars. */
|
|
8395
|
+
mirrorState(state) {
|
|
8396
|
+
mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
|
|
8397
|
+
chmodSync(this.archiveRoot, 448);
|
|
8398
|
+
const currentAgents = new Set(state.agents.map((agent) => String(agent.id)));
|
|
8399
|
+
for (const agentId of this.mirroredAgents) {
|
|
8400
|
+
if (currentAgents.has(agentId)) continue;
|
|
8401
|
+
rmSync(join6(this.archiveRoot, agentId), { recursive: true, force: true });
|
|
8402
|
+
this.mirroredEventIds.delete(agentId);
|
|
8403
|
+
this.mirroredCompactionIds.delete(agentId);
|
|
8404
|
+
}
|
|
8405
|
+
for (const agent of state.agents) {
|
|
8406
|
+
const agentId = String(agent.id);
|
|
8407
|
+
const archiveDir = join6(this.archiveRoot, agentId);
|
|
8408
|
+
const toolDir = join6(archiveDir, "tool-results");
|
|
8409
|
+
mkdirSync(toolDir, { recursive: true, mode: 448 });
|
|
8410
|
+
chmodSync(archiveDir, 448);
|
|
8411
|
+
chmodSync(toolDir, 448);
|
|
8412
|
+
const sessionPath = join6(archiveDir, "session.jsonl");
|
|
8413
|
+
try {
|
|
8414
|
+
chmodSync(sessionPath, 384);
|
|
8415
|
+
} catch {
|
|
8416
|
+
}
|
|
8417
|
+
const known = this.mirroredEventIds.get(agentId) ?? /* @__PURE__ */ new Set();
|
|
8418
|
+
const knownCompactions = this.mirroredCompactionIds.get(agentId) ?? /* @__PURE__ */ new Set();
|
|
8419
|
+
if (known.size === 0 || knownCompactions.size === 0) {
|
|
8420
|
+
try {
|
|
8421
|
+
for (const line of readFileSync2(sessionPath, "utf8").split("\n")) {
|
|
8422
|
+
if (!line.trim()) continue;
|
|
8423
|
+
const parsed = JSON.parse(line);
|
|
8424
|
+
if (parsed.event?.id) known.add(parsed.event.id);
|
|
8425
|
+
if (parsed.record?.id) knownCompactions.add(parsed.record.id);
|
|
8426
|
+
}
|
|
8427
|
+
} catch {
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
const lines = state.events.filter((event) => String(event.agentId) === agentId && !known.has(String(event.id))).map((event) => JSON.stringify({ schemaVersion: 1, kind: "rowan_event", event }) + "\n");
|
|
8431
|
+
if (lines.length > 0) {
|
|
8432
|
+
appendFileSync(sessionPath, lines.join(""), { encoding: "utf8", mode: 384 });
|
|
8433
|
+
chmodSync(sessionPath, 384);
|
|
8434
|
+
for (const event of state.events) {
|
|
8435
|
+
if (String(event.agentId) === agentId) known.add(String(event.id));
|
|
8436
|
+
}
|
|
8437
|
+
}
|
|
8438
|
+
const compactionLines = (state.contextCompactions ?? []).filter(([candidateId, record]) => String(candidateId) === agentId && !knownCompactions.has(record.id)).map(([, record]) => JSON.stringify({ schemaVersion: 1, kind: "context_compacted", record }) + "\n");
|
|
8439
|
+
if (compactionLines.length > 0) {
|
|
8440
|
+
appendFileSync(sessionPath, compactionLines.join(""), { encoding: "utf8", mode: 384 });
|
|
8441
|
+
chmodSync(sessionPath, 384);
|
|
8442
|
+
for (const [, record] of state.contextCompactions ?? []) {
|
|
8443
|
+
if (String(record.agentId) === agentId) knownCompactions.add(record.id);
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
this.mirroredEventIds.set(agentId, known);
|
|
8447
|
+
this.mirroredCompactionIds.set(agentId, knownCompactions);
|
|
8448
|
+
}
|
|
8449
|
+
this.mirroredAgents = currentAgents;
|
|
7841
8450
|
}
|
|
7842
8451
|
requireMatchingOwner(lease, requireLive) {
|
|
7843
8452
|
const row = this.readOwner();
|
|
@@ -7893,6 +8502,9 @@ var SqliteOwnedStore = class {
|
|
|
7893
8502
|
}
|
|
7894
8503
|
store;
|
|
7895
8504
|
lease;
|
|
8505
|
+
contextArchiveDir(agentId) {
|
|
8506
|
+
return this.store.contextArchiveDir(agentId);
|
|
8507
|
+
}
|
|
7896
8508
|
reserveAgent(input) {
|
|
7897
8509
|
return this.store.reserveAgent(this.lease, input);
|
|
7898
8510
|
}
|
|
@@ -7911,6 +8523,15 @@ var SqliteOwnedStore = class {
|
|
|
7911
8523
|
compact(input) {
|
|
7912
8524
|
return this.store.compact(this.lease, input);
|
|
7913
8525
|
}
|
|
8526
|
+
contextStatus(agentId, contextWindow) {
|
|
8527
|
+
return this.store.contextStatus(this.lease, agentId, contextWindow);
|
|
8528
|
+
}
|
|
8529
|
+
contextMessages(agentId, recentTokenBudget) {
|
|
8530
|
+
return Promise.resolve(this.store.contextMessages(this.lease, agentId, recentTokenBudget));
|
|
8531
|
+
}
|
|
8532
|
+
commitContextCompaction(record) {
|
|
8533
|
+
return this.store.commitContextCompaction(this.lease, record);
|
|
8534
|
+
}
|
|
7914
8535
|
createRun(input) {
|
|
7915
8536
|
return this.store.createRun(this.lease, input);
|
|
7916
8537
|
}
|
|
@@ -8016,18 +8637,12 @@ function assertOwnerInput(ownerId, leaseMs) {
|
|
|
8016
8637
|
|
|
8017
8638
|
// src/index.ts
|
|
8018
8639
|
function createCoreTools2(input = {}) {
|
|
8019
|
-
return
|
|
8020
|
-
name: tool.name,
|
|
8021
|
-
description: tool.description,
|
|
8022
|
-
parameters: tool.parameters,
|
|
8023
|
-
execute: async (args, context, signal) => {
|
|
8024
|
-
const result = await tool.execute(args, { skills: [], toolCallId: context.toolCallId }, signal);
|
|
8025
|
-
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Tool failed." };
|
|
8026
|
-
}
|
|
8027
|
-
}));
|
|
8640
|
+
return createRuntimeCoreTools(input);
|
|
8028
8641
|
}
|
|
8029
8642
|
export {
|
|
8030
8643
|
AgentRuntime,
|
|
8644
|
+
COMPACT_PHASE_ID,
|
|
8645
|
+
DEFAULT_PHASE_ID,
|
|
8031
8646
|
ExtensionLifetimeError,
|
|
8032
8647
|
InMemoryConfigProvider,
|
|
8033
8648
|
InMemoryStore,
|
|
@@ -8038,9 +8653,14 @@ export {
|
|
|
8038
8653
|
RuntimeBootstrapRegistry,
|
|
8039
8654
|
RuntimeError,
|
|
8040
8655
|
RuntimeExtensionLifetime,
|
|
8656
|
+
STOP_PHASE_ID,
|
|
8041
8657
|
SqliteStore,
|
|
8042
8658
|
brandConfigToken,
|
|
8659
|
+
createCompactPhase,
|
|
8660
|
+
createCorePhases,
|
|
8043
8661
|
createCoreTools2 as createCoreTools,
|
|
8662
|
+
createDefaultPhase,
|
|
8663
|
+
createStopPhase,
|
|
8044
8664
|
isRuntimeError,
|
|
8045
8665
|
loadExtensionsFromPath as loadExtensions,
|
|
8046
8666
|
loadPhase,
|