@rowan-agent/agent 0.9.7 → 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/README.md +6 -0
- package/dist/index.d.ts +797 -539
- package/dist/index.js +1182 -296
- 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;
|
|
@@ -1052,6 +1093,178 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
1052
1093
|
};
|
|
1053
1094
|
}
|
|
1054
1095
|
|
|
1096
|
+
// src/runtime/json.ts
|
|
1097
|
+
function isPlainObject(value) {
|
|
1098
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1099
|
+
return prototype === Object.prototype || prototype === null;
|
|
1100
|
+
}
|
|
1101
|
+
function hasOnlyDataProperties(value) {
|
|
1102
|
+
return Reflect.ownKeys(value).every((key) => {
|
|
1103
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1104
|
+
return Boolean(descriptor && "value" in descriptor);
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
function visit(value, seen) {
|
|
1108
|
+
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
1109
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1110
|
+
if (typeof value !== "object" || seen.has(value)) return false;
|
|
1111
|
+
seen.add(value);
|
|
1112
|
+
try {
|
|
1113
|
+
if (Array.isArray(value)) {
|
|
1114
|
+
if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
|
|
1115
|
+
if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
|
|
1116
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1117
|
+
if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
|
|
1118
|
+
}
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
|
|
1122
|
+
if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
|
|
1123
|
+
if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
|
|
1124
|
+
return Object.keys(value).every((key) => visit(value[key], seen));
|
|
1125
|
+
} finally {
|
|
1126
|
+
seen.delete(value);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
function isJsonValue(value) {
|
|
1130
|
+
return visit(value, /* @__PURE__ */ new Set());
|
|
1131
|
+
}
|
|
1132
|
+
function assertJsonValue(value, argument = "value") {
|
|
1133
|
+
if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
|
|
1134
|
+
}
|
|
1135
|
+
function canonicalJson(value) {
|
|
1136
|
+
assertJsonValue(value);
|
|
1137
|
+
const encode = (candidate) => {
|
|
1138
|
+
if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
|
|
1139
|
+
if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
|
|
1140
|
+
const object = candidate;
|
|
1141
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
|
|
1142
|
+
};
|
|
1143
|
+
return encode(value);
|
|
1144
|
+
}
|
|
1145
|
+
function utf8ByteLength(value) {
|
|
1146
|
+
return new TextEncoder().encode(value).byteLength;
|
|
1147
|
+
}
|
|
1148
|
+
function assertUtf8ByteLimit(value, limit, argument = "value") {
|
|
1149
|
+
if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// src/harness/phases/interactions.ts
|
|
1153
|
+
var PhaseInteractionCancelledError = class extends Error {
|
|
1154
|
+
code = "phase_interaction_cancelled";
|
|
1155
|
+
constructor() {
|
|
1156
|
+
super("Phase interaction work was cancelled.");
|
|
1157
|
+
this.name = "PhaseInteractionCancelledError";
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
var PhaseInteractionBoundary = class extends Error {
|
|
1161
|
+
constructor(state, interactions) {
|
|
1162
|
+
super("Phase interactions require host input.");
|
|
1163
|
+
this.state = state;
|
|
1164
|
+
this.interactions = interactions;
|
|
1165
|
+
this.name = "PhaseInteractionBoundary";
|
|
1166
|
+
}
|
|
1167
|
+
state;
|
|
1168
|
+
interactions;
|
|
1169
|
+
};
|
|
1170
|
+
function createPhaseInteractionDriver(state, phase, signal) {
|
|
1171
|
+
const stored = state.phaseInteractions;
|
|
1172
|
+
const requests = new Map(
|
|
1173
|
+
stored?.requests.map((request) => [request.id, { ...request }]) ?? []
|
|
1174
|
+
);
|
|
1175
|
+
const answers = new Map(Object.entries(stored?.answers ?? {}));
|
|
1176
|
+
const usedRequestIds = /* @__PURE__ */ new Set();
|
|
1177
|
+
let checkpoint = stored?.checkpoint;
|
|
1178
|
+
const snapshot = () => ({
|
|
1179
|
+
requests: [...requests.values()].map((request) => ({
|
|
1180
|
+
...request,
|
|
1181
|
+
status: answers.has(request.id) ? "answered" : request.status
|
|
1182
|
+
})),
|
|
1183
|
+
answers: Object.fromEntries(answers),
|
|
1184
|
+
...checkpoint === void 0 ? {} : { checkpoint }
|
|
1185
|
+
});
|
|
1186
|
+
const sync = () => {
|
|
1187
|
+
state.phaseInteractions = snapshot();
|
|
1188
|
+
};
|
|
1189
|
+
const assertActive = () => {
|
|
1190
|
+
if (signal?.aborted) throw new PhaseInteractionCancelledError();
|
|
1191
|
+
};
|
|
1192
|
+
const matchingStoredRequest = (input) => {
|
|
1193
|
+
const fingerprint = interactionFingerprint(input);
|
|
1194
|
+
return [...requests.values()].find((request) => !usedRequestIds.has(request.id) && interactionFingerprint(request) === fingerprint);
|
|
1195
|
+
};
|
|
1196
|
+
return {
|
|
1197
|
+
signal: signal ?? new AbortController().signal,
|
|
1198
|
+
request(input) {
|
|
1199
|
+
assertActive();
|
|
1200
|
+
if (input.id !== void 0 && input.id.trim().length === 0) {
|
|
1201
|
+
throw new TypeError("Phase interaction id must be non-empty.");
|
|
1202
|
+
}
|
|
1203
|
+
if (input.prompt.trim().length === 0) {
|
|
1204
|
+
throw new TypeError("Phase interaction prompt must be non-empty.");
|
|
1205
|
+
}
|
|
1206
|
+
if (!PHASE_INTERACTION_KINDS.includes(input.kind)) {
|
|
1207
|
+
throw new TypeError(`Unsupported Phase interaction kind: ${String(input.kind)}.`);
|
|
1208
|
+
}
|
|
1209
|
+
if (input.payload !== void 0) assertJsonValue(input.payload, "Phase interaction payload");
|
|
1210
|
+
const matched = input.id === void 0 ? matchingStoredRequest(input) : void 0;
|
|
1211
|
+
const id = input.id ?? matched?.id ?? createId("interaction");
|
|
1212
|
+
const existing = requests.get(id);
|
|
1213
|
+
if (existing) {
|
|
1214
|
+
if (interactionFingerprint(existing) !== interactionFingerprint(input)) {
|
|
1215
|
+
throw new Error(`Phase interaction ${id} was requested with different metadata.`);
|
|
1216
|
+
}
|
|
1217
|
+
usedRequestIds.add(id);
|
|
1218
|
+
return {
|
|
1219
|
+
...existing,
|
|
1220
|
+
status: answers.has(id) ? "answered" : existing.status
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
const request = {
|
|
1224
|
+
id,
|
|
1225
|
+
phase,
|
|
1226
|
+
kind: input.kind,
|
|
1227
|
+
prompt: input.prompt,
|
|
1228
|
+
...input.payload === void 0 ? {} : { payload: input.payload },
|
|
1229
|
+
createdAt: createTimestamp(),
|
|
1230
|
+
status: "pending"
|
|
1231
|
+
};
|
|
1232
|
+
requests.set(id, request);
|
|
1233
|
+
usedRequestIds.add(id);
|
|
1234
|
+
sync();
|
|
1235
|
+
return request;
|
|
1236
|
+
},
|
|
1237
|
+
pending() {
|
|
1238
|
+
return [...requests.values()].filter((request) => request.status === "pending" && !answers.has(request.id)).map((request) => ({ ...request, status: "pending" }));
|
|
1239
|
+
},
|
|
1240
|
+
answers() {
|
|
1241
|
+
return new Map(answers);
|
|
1242
|
+
},
|
|
1243
|
+
suspend(input = {}) {
|
|
1244
|
+
assertActive();
|
|
1245
|
+
const pending = this.pending();
|
|
1246
|
+
if (pending.length === 0) throw new Error("Cannot suspend without pending Phase interactions.");
|
|
1247
|
+
if (input.checkpoint !== void 0) checkpoint = input.checkpoint;
|
|
1248
|
+
state.status = "suspended";
|
|
1249
|
+
sync();
|
|
1250
|
+
throw new PhaseInteractionBoundary(state, pending);
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
var PHASE_INTERACTION_KINDS = [
|
|
1255
|
+
"user_input",
|
|
1256
|
+
"permission",
|
|
1257
|
+
"elicitation",
|
|
1258
|
+
"confirmation"
|
|
1259
|
+
];
|
|
1260
|
+
function interactionFingerprint(value) {
|
|
1261
|
+
return canonicalJson({
|
|
1262
|
+
kind: value.kind,
|
|
1263
|
+
prompt: value.prompt,
|
|
1264
|
+
payload: value.payload ?? null
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1055
1268
|
// src/harness/phases/loader.ts
|
|
1056
1269
|
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
1057
1270
|
import { readdir as readdir2 } from "fs/promises";
|
|
@@ -1145,7 +1358,7 @@ function optionalBundledSkills(value, field) {
|
|
|
1145
1358
|
if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of Skills`);
|
|
1146
1359
|
const names = /* @__PURE__ */ new Set();
|
|
1147
1360
|
for (const [index, item] of value.entries()) {
|
|
1148
|
-
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") {
|
|
1149
1362
|
throw new TypeError(`${field}[${index}] is invalid`);
|
|
1150
1363
|
}
|
|
1151
1364
|
if (names.has(item.name)) throw new TypeError(`Duplicate bundled Skill "${item.name}".`);
|
|
@@ -1284,7 +1497,9 @@ async function loadSkill(path) {
|
|
|
1284
1497
|
filePath: resolved,
|
|
1285
1498
|
baseDir: dirname3(resolved),
|
|
1286
1499
|
content: body,
|
|
1287
|
-
|
|
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 } : {}
|
|
1288
1503
|
};
|
|
1289
1504
|
}
|
|
1290
1505
|
async function loadSkills(targetPath) {
|
|
@@ -1376,7 +1591,9 @@ async function loadPhase(targetPath) {
|
|
|
1376
1591
|
filePath: resolved,
|
|
1377
1592
|
baseDir,
|
|
1378
1593
|
content: definition.prompt,
|
|
1379
|
-
model: definition.model
|
|
1594
|
+
model: definition.model,
|
|
1595
|
+
disableAutoInvocation: metadata["disable-auto-invocation"] === true,
|
|
1596
|
+
disableImplicitInvocation: metadata["disable-implicit-invocation"] === true
|
|
1380
1597
|
};
|
|
1381
1598
|
const codePath = await discoverPhaseCode(baseDir);
|
|
1382
1599
|
if (codePath) {
|
|
@@ -1512,16 +1729,126 @@ async function loadPhaseCode(codePath) {
|
|
|
1512
1729
|
|
|
1513
1730
|
// src/harness/phases/default.ts
|
|
1514
1731
|
var DEFAULT_PHASE_ID = "default";
|
|
1732
|
+
var STOP_PHASE_ID = "stop";
|
|
1733
|
+
var COMPACT_PHASE_ID = "compact";
|
|
1515
1734
|
function createDefaultPhase() {
|
|
1516
1735
|
return {
|
|
1517
1736
|
name: DEFAULT_PHASE_ID,
|
|
1518
|
-
|
|
1737
|
+
core: true,
|
|
1738
|
+
description: "Execute the current user request using the current context.",
|
|
1519
1739
|
filePath: "",
|
|
1520
1740
|
baseDir: "",
|
|
1521
1741
|
skills: [],
|
|
1522
|
-
content:
|
|
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
|
|
1523
1749
|
};
|
|
1524
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.",
|
|
1774
|
+
filePath: "",
|
|
1775
|
+
baseDir: "",
|
|
1776
|
+
skills: [],
|
|
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
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
function createCorePhases() {
|
|
1850
|
+
return [createDefaultPhase(), createStopPhase(), createCompactPhase()];
|
|
1851
|
+
}
|
|
1525
1852
|
|
|
1526
1853
|
// src/harness/resource-selection.ts
|
|
1527
1854
|
function selectNamedResources(candidates, names, kind) {
|
|
@@ -1573,7 +1900,7 @@ function buildSystemPrompt(options) {
|
|
|
1573
1900
|
addGuideline(g);
|
|
1574
1901
|
}
|
|
1575
1902
|
}
|
|
1576
|
-
const visibleSkills = (skills ?? []).filter((s) => !s.disableModelInvocation);
|
|
1903
|
+
const visibleSkills = (skills ?? []).filter((s) => !(s.disableAutoInvocation ?? s.disableModelInvocation));
|
|
1577
1904
|
const skillsBlock = visibleSkills.length > 0 ? buildSkillsDescription(visibleSkills) : "";
|
|
1578
1905
|
for (const g of promptGuidelines ?? []) {
|
|
1579
1906
|
addGuideline(g);
|
|
@@ -1609,6 +1936,7 @@ function serializeSkills(skills) {
|
|
|
1609
1936
|
name: skill.name,
|
|
1610
1937
|
description: skill.description,
|
|
1611
1938
|
filePath: skill.filePath,
|
|
1939
|
+
disableAutoInvocation: skill.disableAutoInvocation,
|
|
1612
1940
|
disableModelInvocation: skill.disableModelInvocation
|
|
1613
1941
|
}));
|
|
1614
1942
|
}
|
|
@@ -1731,80 +2059,9 @@ var createOutcome = {
|
|
|
1731
2059
|
}
|
|
1732
2060
|
};
|
|
1733
2061
|
|
|
1734
|
-
// src/harness/context/compaction.ts
|
|
1735
|
-
function needsCompaction(messages, options = {}) {
|
|
1736
|
-
const maxMessages = options.maxMessages ?? 50;
|
|
1737
|
-
return messages.length > maxMessages;
|
|
1738
|
-
}
|
|
1739
|
-
function buildSummary(messages) {
|
|
1740
|
-
const parts = [];
|
|
1741
|
-
let currentRole;
|
|
1742
|
-
let currentContent = [];
|
|
1743
|
-
function flush() {
|
|
1744
|
-
if (currentRole && currentContent.length > 0) {
|
|
1745
|
-
const combined = currentContent.join("\n");
|
|
1746
|
-
const truncated = combined.length > 500 ? combined.slice(0, 500) + "..." : combined;
|
|
1747
|
-
parts.push(`[${currentRole}]: ${truncated}`);
|
|
1748
|
-
}
|
|
1749
|
-
currentContent = [];
|
|
1750
|
-
}
|
|
1751
|
-
for (const msg of messages) {
|
|
1752
|
-
if (msg.role !== currentRole) {
|
|
1753
|
-
flush();
|
|
1754
|
-
currentRole = msg.role;
|
|
1755
|
-
}
|
|
1756
|
-
currentContent.push(messageContentText(msg.content));
|
|
1757
|
-
}
|
|
1758
|
-
flush();
|
|
1759
|
-
return parts.join("\n\n");
|
|
1760
|
-
}
|
|
1761
|
-
function compactMessages(messages, options = {}) {
|
|
1762
|
-
if (!needsCompaction(messages, options)) {
|
|
1763
|
-
return { compacted: false, messages };
|
|
1764
|
-
}
|
|
1765
|
-
const keepRecent = options.keepRecent ?? 10;
|
|
1766
|
-
const minCompact = options.minCompact ?? 20;
|
|
1767
|
-
const firstUserIdx = messages.findIndex(
|
|
1768
|
-
(m) => m.role === "user"
|
|
1769
|
-
);
|
|
1770
|
-
const recentStart = Math.max(messages.length - keepRecent, firstUserIdx + 1);
|
|
1771
|
-
const oldMessages = messages.slice(firstUserIdx >= 0 ? firstUserIdx + 1 : 0, recentStart);
|
|
1772
|
-
const protectedPhaseMessages = oldMessages.filter(
|
|
1773
|
-
(message) => message.metadata?.kind === "phase_prompt"
|
|
1774
|
-
);
|
|
1775
|
-
const summarizableMessages = oldMessages.filter(
|
|
1776
|
-
(message) => message.metadata?.kind !== "phase_prompt"
|
|
1777
|
-
);
|
|
1778
|
-
if (summarizableMessages.length < minCompact) {
|
|
1779
|
-
return { compacted: false, messages };
|
|
1780
|
-
}
|
|
1781
|
-
const recentMessages = messages.slice(recentStart);
|
|
1782
|
-
const summary = buildSummary(summarizableMessages);
|
|
1783
|
-
const result = [];
|
|
1784
|
-
if (firstUserIdx >= 0) {
|
|
1785
|
-
result.push(messages[firstUserIdx]);
|
|
1786
|
-
}
|
|
1787
|
-
result.push(
|
|
1788
|
-
createMessage("assistant", `[Context compaction summary]
|
|
1789
|
-
|
|
1790
|
-
${summary}`, {
|
|
1791
|
-
type: "compaction_summary",
|
|
1792
|
-
compactedCount: summarizableMessages.length
|
|
1793
|
-
})
|
|
1794
|
-
);
|
|
1795
|
-
result.push(...protectedPhaseMessages);
|
|
1796
|
-
result.push(...recentMessages);
|
|
1797
|
-
return {
|
|
1798
|
-
compacted: true,
|
|
1799
|
-
messages: result,
|
|
1800
|
-
summarizedCount: summarizableMessages.length,
|
|
1801
|
-
summary
|
|
1802
|
-
};
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
2062
|
// src/loop/runners.ts
|
|
1806
2063
|
function resolvePhaseOutput(result) {
|
|
1807
|
-
return result
|
|
2064
|
+
return result ?? { message: "Phase completed.", route: "stop" };
|
|
1808
2065
|
}
|
|
1809
2066
|
function removePhaseMessage(messages, msgId) {
|
|
1810
2067
|
if (!msgId) return;
|
|
@@ -1920,12 +2177,18 @@ async function completeRun(config, state, outcome) {
|
|
|
1920
2177
|
return createRunResult(config, state, outcome);
|
|
1921
2178
|
}
|
|
1922
2179
|
function createTerminalOutcome(phase, output, transcript) {
|
|
1923
|
-
|
|
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;
|
|
1924
2186
|
}
|
|
1925
2187
|
function buildToolsWithRouting(config, availablePhases) {
|
|
1926
2188
|
const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
|
|
1927
|
-
|
|
1928
|
-
|
|
2189
|
+
const autoInvocablePhases = availablePhases.filter((phase) => phase.disableAutoInvocation !== true);
|
|
2190
|
+
if (autoInvocablePhases.length > 0) {
|
|
2191
|
+
tools.push(createRouteTool(autoInvocablePhases));
|
|
1929
2192
|
}
|
|
1930
2193
|
return tools;
|
|
1931
2194
|
}
|
|
@@ -2138,7 +2401,15 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2138
2401
|
while (currentPhaseId) {
|
|
2139
2402
|
const availablePhases = [];
|
|
2140
2403
|
for (const [, phase2] of registry.phases) {
|
|
2141
|
-
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
|
+
});
|
|
2142
2413
|
}
|
|
2143
2414
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
2144
2415
|
if (abortResult.stopReason !== "none") {
|
|
@@ -2162,13 +2433,6 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2162
2433
|
}
|
|
2163
2434
|
state.attempt++;
|
|
2164
2435
|
state.metrics.iterations++;
|
|
2165
|
-
if (needsCompaction(config.context.messages)) {
|
|
2166
|
-
const compacted = compactMessages(config.context.messages);
|
|
2167
|
-
if (compacted.compacted) {
|
|
2168
|
-
config.context.messages = compacted.messages;
|
|
2169
|
-
state.metrics.compactionCount++;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
2436
|
const phase = registry.phases.get(currentPhaseId);
|
|
2173
2437
|
if (!phase) {
|
|
2174
2438
|
throw new Error(`Phase "${currentPhaseId}" not found`);
|
|
@@ -2293,10 +2557,20 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2293
2557
|
}
|
|
2294
2558
|
}
|
|
2295
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
|
+
}
|
|
2296
2570
|
if (phase.run || phase.factory) {
|
|
2297
2571
|
if (output.route === void 0 && !routeDecision) output = { ...output, route: "stop" };
|
|
2298
2572
|
} else if (!phase.target && output.route === void 0 && !routeDecision) {
|
|
2299
|
-
const input = await awaitInput(output.message
|
|
2573
|
+
const input = await awaitInput(output.message ?? `${output.phase ?? "Phase"} completed.`, true);
|
|
2300
2574
|
if (input?.type === "completed") return input.result;
|
|
2301
2575
|
if (input?.type === "continued") {
|
|
2302
2576
|
isContinuing = true;
|
|
@@ -2347,11 +2621,16 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2347
2621
|
pendingInstruction = void 0;
|
|
2348
2622
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
2349
2623
|
if (entryPhaseId === "stop") {
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
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;
|
|
2355
2634
|
}
|
|
2356
2635
|
currentPhaseId = entryPhaseId;
|
|
2357
2636
|
previousPayload = void 0;
|
|
@@ -2366,11 +2645,18 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2366
2645
|
nextRoute = "stop";
|
|
2367
2646
|
}
|
|
2368
2647
|
if (nextRoute === "stop") {
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
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;
|
|
2374
2660
|
}
|
|
2375
2661
|
const targetPhaseId = nextRoute;
|
|
2376
2662
|
if (!registry.phases.has(targetPhaseId)) {
|
|
@@ -2434,7 +2720,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2434
2720
|
}
|
|
2435
2721
|
const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
|
|
2436
2722
|
options.onRetry?.(attempt, error, delayMs);
|
|
2437
|
-
await new Promise((
|
|
2723
|
+
await new Promise((resolve9) => setTimeout(resolve9, delayMs));
|
|
2438
2724
|
if (options.signal?.aborted) {
|
|
2439
2725
|
throw lastError;
|
|
2440
2726
|
}
|
|
@@ -2539,7 +2825,9 @@ async function executeToolCalls(input) {
|
|
|
2539
2825
|
return results;
|
|
2540
2826
|
}
|
|
2541
2827
|
function createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry) {
|
|
2828
|
+
const interaction = createPhaseInteractionDriver(state, phase.name, config.signal);
|
|
2542
2829
|
return {
|
|
2830
|
+
interaction,
|
|
2543
2831
|
snapshot() {
|
|
2544
2832
|
return {
|
|
2545
2833
|
systemPrompt: config.context.systemPrompt,
|
|
@@ -2556,7 +2844,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2556
2844
|
state.currentPhase = snapshot.currentPhase;
|
|
2557
2845
|
state.metrics.iterations = snapshot.turnNumber;
|
|
2558
2846
|
},
|
|
2559
|
-
async invokeModel(phaseContext) {
|
|
2847
|
+
async invokeModel(phaseContext, options = {}) {
|
|
2560
2848
|
if (config.beforePrompt) {
|
|
2561
2849
|
phaseContext = await config.beforePrompt(phase.name, phaseContext);
|
|
2562
2850
|
}
|
|
@@ -2586,7 +2874,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2586
2874
|
config,
|
|
2587
2875
|
message: messageManager,
|
|
2588
2876
|
request,
|
|
2589
|
-
phaseId: phase.name
|
|
2877
|
+
phaseId: phase.name,
|
|
2878
|
+
output: options.output
|
|
2590
2879
|
}),
|
|
2591
2880
|
{
|
|
2592
2881
|
signal: config.signal,
|
|
@@ -2599,6 +2888,9 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2599
2888
|
await config.onModelTranscript?.(result.transcript, { phase: phase.name, model: phase.model ?? config.model });
|
|
2600
2889
|
return result;
|
|
2601
2890
|
},
|
|
2891
|
+
async reportStatus(status) {
|
|
2892
|
+
await config.onPhaseStatus?.(phase.name, status);
|
|
2893
|
+
},
|
|
2602
2894
|
async executeTool(phaseContext, toolCall) {
|
|
2603
2895
|
return runTurn(async () => {
|
|
2604
2896
|
await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
|
|
@@ -2688,12 +2980,13 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
|
|
|
2688
2980
|
if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
|
|
2689
2981
|
const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
|
|
2690
2982
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2691
|
-
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2983
|
+
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message ?? `${phase.name} completed.` };
|
|
2692
2984
|
}
|
|
2693
2985
|
function selectPhaseTools(candidates, names, includeRouting = true) {
|
|
2694
2986
|
const routing = includeRouting ? candidates.filter(({ name }) => name === PhaseRouteTool) : [];
|
|
2695
|
-
const
|
|
2696
|
-
|
|
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];
|
|
2697
2990
|
}
|
|
2698
2991
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2699
2992
|
const entries = Array.from(backgroundTasks.entries());
|
|
@@ -2709,62 +3002,6 @@ async function waitForBackgroundTasks(backgroundTasks) {
|
|
|
2709
3002
|
return successful;
|
|
2710
3003
|
}
|
|
2711
3004
|
|
|
2712
|
-
// src/runtime/json.ts
|
|
2713
|
-
function isPlainObject(value) {
|
|
2714
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2715
|
-
return prototype === Object.prototype || prototype === null;
|
|
2716
|
-
}
|
|
2717
|
-
function hasOnlyDataProperties(value) {
|
|
2718
|
-
return Reflect.ownKeys(value).every((key) => {
|
|
2719
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2720
|
-
return Boolean(descriptor && "value" in descriptor);
|
|
2721
|
-
});
|
|
2722
|
-
}
|
|
2723
|
-
function visit(value, seen) {
|
|
2724
|
-
if (value === null || typeof value === "boolean" || typeof value === "string") return true;
|
|
2725
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
2726
|
-
if (typeof value !== "object" || seen.has(value)) return false;
|
|
2727
|
-
seen.add(value);
|
|
2728
|
-
try {
|
|
2729
|
-
if (Array.isArray(value)) {
|
|
2730
|
-
if (Object.getPrototypeOf(value) !== Array.prototype || !hasOnlyDataProperties(value)) return false;
|
|
2731
|
-
if (Reflect.ownKeys(value).some((key) => key !== "length" && typeof key !== "string")) return false;
|
|
2732
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
2733
|
-
if (!Object.prototype.hasOwnProperty.call(value, index) || !visit(value[index], seen)) return false;
|
|
2734
|
-
}
|
|
2735
|
-
return true;
|
|
2736
|
-
}
|
|
2737
|
-
if (!isPlainObject(value) || !hasOnlyDataProperties(value)) return false;
|
|
2738
|
-
if (Reflect.ownKeys(value).some((key) => typeof key !== "string")) return false;
|
|
2739
|
-
if (Object.prototype.hasOwnProperty.call(value, "toJSON")) return false;
|
|
2740
|
-
return Object.keys(value).every((key) => visit(value[key], seen));
|
|
2741
|
-
} finally {
|
|
2742
|
-
seen.delete(value);
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
function isJsonValue(value) {
|
|
2746
|
-
return visit(value, /* @__PURE__ */ new Set());
|
|
2747
|
-
}
|
|
2748
|
-
function assertJsonValue(value, argument = "value") {
|
|
2749
|
-
if (!isJsonValue(value)) throw new TypeError(`${argument} must be JSON-safe`);
|
|
2750
|
-
}
|
|
2751
|
-
function canonicalJson(value) {
|
|
2752
|
-
assertJsonValue(value);
|
|
2753
|
-
const encode = (candidate) => {
|
|
2754
|
-
if (candidate === null || typeof candidate !== "object") return JSON.stringify(candidate);
|
|
2755
|
-
if (Array.isArray(candidate)) return `[${candidate.map(encode).join(",")}]`;
|
|
2756
|
-
const object = candidate;
|
|
2757
|
-
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
|
|
2758
|
-
};
|
|
2759
|
-
return encode(value);
|
|
2760
|
-
}
|
|
2761
|
-
function utf8ByteLength(value) {
|
|
2762
|
-
return new TextEncoder().encode(value).byteLength;
|
|
2763
|
-
}
|
|
2764
|
-
function assertUtf8ByteLimit(value, limit, argument = "value") {
|
|
2765
|
-
if (utf8ByteLength(value) > limit) throw new RangeError(`${argument} exceeds ${limit} UTF-8 bytes`);
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
3005
|
// src/runtime/execution.ts
|
|
2769
3006
|
var EXECUTION_CHECKPOINT_CODEC = "rowan.agent.execution";
|
|
2770
3007
|
var EXECUTION_CHECKPOINT_VERSION = 1;
|
|
@@ -2813,7 +3050,8 @@ function encodeExecutionCheckpoint(state) {
|
|
|
2813
3050
|
currentPhase: state.currentPhase,
|
|
2814
3051
|
attempt: state.attempt,
|
|
2815
3052
|
metrics,
|
|
2816
|
-
...continuation ? { continuation } : {}
|
|
3053
|
+
...continuation ? { continuation } : {},
|
|
3054
|
+
...state.phaseInteractions === void 0 ? {} : { phaseInteractions: state.phaseInteractions }
|
|
2817
3055
|
};
|
|
2818
3056
|
assertJsonValue(data, "execution checkpoint");
|
|
2819
3057
|
return {
|
|
@@ -2844,6 +3082,13 @@ function decodeExecutionCheckpoint(checkpoint) {
|
|
|
2844
3082
|
...checkpoint.data.continuation,
|
|
2845
3083
|
previousResults: checkpoint.data.continuation.previousResults.map((result) => ({ ...result }))
|
|
2846
3084
|
}
|
|
3085
|
+
} : {},
|
|
3086
|
+
...checkpoint.data.phaseInteractions ? {
|
|
3087
|
+
phaseInteractions: {
|
|
3088
|
+
requests: checkpoint.data.phaseInteractions.requests.map((request) => ({ ...request })),
|
|
3089
|
+
answers: { ...checkpoint.data.phaseInteractions.answers },
|
|
3090
|
+
...checkpoint.data.phaseInteractions.checkpoint === void 0 ? {} : { checkpoint: checkpoint.data.phaseInteractions.checkpoint }
|
|
3091
|
+
}
|
|
2847
3092
|
} : {}
|
|
2848
3093
|
};
|
|
2849
3094
|
}
|
|
@@ -2869,6 +3114,16 @@ async function executeOnce(input) {
|
|
|
2869
3114
|
status: "running",
|
|
2870
3115
|
metrics: createMetrics()
|
|
2871
3116
|
};
|
|
3117
|
+
if (input.interactionAnswers) {
|
|
3118
|
+
state.phaseInteractions = {
|
|
3119
|
+
requests: state.phaseInteractions?.requests ?? [],
|
|
3120
|
+
answers: {
|
|
3121
|
+
...state.phaseInteractions?.answers ?? {},
|
|
3122
|
+
...input.interactionAnswers
|
|
3123
|
+
},
|
|
3124
|
+
...state.phaseInteractions?.checkpoint === void 0 ? {} : { checkpoint: state.phaseInteractions.checkpoint }
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
2872
3127
|
const config = {
|
|
2873
3128
|
context,
|
|
2874
3129
|
execution: input.execution,
|
|
@@ -2886,6 +3141,7 @@ async function executeOnce(input) {
|
|
|
2886
3141
|
onMessageDelta: input.onMessageDelta,
|
|
2887
3142
|
onOutcome: input.onOutcome,
|
|
2888
3143
|
onModelTranscript: input.onModelTranscript,
|
|
3144
|
+
onPhaseStatus: input.onPhaseStatus,
|
|
2889
3145
|
runtime: input.runtime,
|
|
2890
3146
|
waitForInput: async (nextState, request) => {
|
|
2891
3147
|
if (!nextState || !request) throw new Error("Input boundary did not include execution state.");
|
|
@@ -2904,6 +3160,20 @@ async function executeOnce(input) {
|
|
|
2904
3160
|
return {
|
|
2905
3161
|
type: "input_required",
|
|
2906
3162
|
request: error.request,
|
|
3163
|
+
interactions: [],
|
|
3164
|
+
checkpoint: encodeExecutionCheckpoint(error.state),
|
|
3165
|
+
messages: snapshotMessages(context.messages)
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
if (error instanceof PhaseInteractionBoundary) {
|
|
3169
|
+
return {
|
|
3170
|
+
type: "input_required",
|
|
3171
|
+
request: {
|
|
3172
|
+
phase: error.interactions[0]?.phase ?? state.currentPhase,
|
|
3173
|
+
prompt: error.interactions.map((interaction) => interaction.prompt).join("\n"),
|
|
3174
|
+
requestedAt: error.interactions[0]?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3175
|
+
},
|
|
3176
|
+
interactions: error.interactions,
|
|
2907
3177
|
checkpoint: encodeExecutionCheckpoint(error.state),
|
|
2908
3178
|
messages: snapshotMessages(context.messages)
|
|
2909
3179
|
};
|
|
@@ -2933,8 +3203,15 @@ function isCheckpointData(value) {
|
|
|
2933
3203
|
if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
|
|
2934
3204
|
if (!isMetrics(value.metrics)) return false;
|
|
2935
3205
|
if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
|
|
3206
|
+
if (value.phaseInteractions !== void 0 && !isPhaseInteractionState(value.phaseInteractions)) return false;
|
|
2936
3207
|
return true;
|
|
2937
3208
|
}
|
|
3209
|
+
function isPhaseInteractionState(value) {
|
|
3210
|
+
if (!isRecord2(value) || !Array.isArray(value.requests) || !isRecord2(value.answers)) return false;
|
|
3211
|
+
if (!Object.values(value.answers).every((answer) => isJsonValue(answer))) return false;
|
|
3212
|
+
if (value.checkpoint !== void 0 && !isJsonValue(value.checkpoint)) return false;
|
|
3213
|
+
return value.requests.every((request) => isRecord2(request) && typeof request.id === "string" && typeof request.phase === "string" && ["user_input", "permission", "elicitation", "confirmation"].includes(request.kind) && typeof request.prompt === "string" && typeof request.createdAt === "string" && ["pending", "answered", "denied", "cancelled", "expired"].includes(request.status) && (request.payload === void 0 || isJsonValue(request.payload)));
|
|
3214
|
+
}
|
|
2938
3215
|
function isMetrics(value) {
|
|
2939
3216
|
if (!isRecord2(value)) return false;
|
|
2940
3217
|
return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
|
|
@@ -3605,6 +3882,7 @@ function projectToolContent(content) {
|
|
|
3605
3882
|
function projectTool(tool, agentId, runId) {
|
|
3606
3883
|
return {
|
|
3607
3884
|
name: tool.name,
|
|
3885
|
+
...tool.core ? { core: true } : {},
|
|
3608
3886
|
description: tool.description,
|
|
3609
3887
|
parameters: tool.parameters,
|
|
3610
3888
|
execute: async (args, context, signal) => {
|
|
@@ -3623,9 +3901,17 @@ function jsonText(value) {
|
|
|
3623
3901
|
}
|
|
3624
3902
|
|
|
3625
3903
|
// src/runtime/extensions.ts
|
|
3626
|
-
function assembleRegisteredExtensions(config, runner) {
|
|
3904
|
+
function assembleRegisteredExtensions(config, runner, options = {}) {
|
|
3627
3905
|
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3628
|
-
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
|
+
];
|
|
3629
3915
|
const names = new Set(tools.map((tool) => tool.name));
|
|
3630
3916
|
for (const tool of extensionTools) {
|
|
3631
3917
|
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
@@ -3656,11 +3942,13 @@ function assembleRegisteredExtensions(config, runner) {
|
|
|
3656
3942
|
};
|
|
3657
3943
|
}
|
|
3658
3944
|
function resolveDefinitionContext(config, assembled = {}) {
|
|
3659
|
-
const
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
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
|
+
];
|
|
3664
3952
|
const skills = mergeSkills(
|
|
3665
3953
|
selectNamedResources(config.resources.skills, config.definition.skills, "Skill"),
|
|
3666
3954
|
config.definition.bundledSkills
|
|
@@ -3671,11 +3959,16 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3671
3959
|
"Context"
|
|
3672
3960
|
);
|
|
3673
3961
|
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3674
|
-
const
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
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
|
+
];
|
|
3679
3972
|
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3680
3973
|
const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
|
|
3681
3974
|
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
@@ -3690,15 +3983,23 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3690
3983
|
};
|
|
3691
3984
|
}
|
|
3692
3985
|
function mergePhases(base, extension) {
|
|
3693
|
-
|
|
3694
|
-
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
|
+
}
|
|
3695
3996
|
for (const [name, phase] of extension.phases) {
|
|
3696
3997
|
if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
|
|
3697
3998
|
phases.set(name, phase);
|
|
3698
3999
|
}
|
|
3699
4000
|
return {
|
|
3700
4001
|
phases,
|
|
3701
|
-
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
4002
|
+
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
3702
4003
|
};
|
|
3703
4004
|
}
|
|
3704
4005
|
function adaptExtensionTool(input) {
|
|
@@ -3731,11 +4032,11 @@ function fromLoopResult(result) {
|
|
|
3731
4032
|
var MAX_BUFFERED_EVENTS = 128;
|
|
3732
4033
|
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3733
4034
|
function deferred() {
|
|
3734
|
-
let
|
|
4035
|
+
let resolve9;
|
|
3735
4036
|
const promise = new Promise((done) => {
|
|
3736
|
-
|
|
4037
|
+
resolve9 = done;
|
|
3737
4038
|
});
|
|
3738
|
-
return { promise, resolve:
|
|
4039
|
+
return { promise, resolve: resolve9 };
|
|
3739
4040
|
}
|
|
3740
4041
|
var TransientRunEventSubscription = class {
|
|
3741
4042
|
queue = [];
|
|
@@ -3760,11 +4061,14 @@ var TransientRunEventSubscription = class {
|
|
|
3760
4061
|
shift() {
|
|
3761
4062
|
return this.queue.shift();
|
|
3762
4063
|
}
|
|
3763
|
-
clear(executionId) {
|
|
4064
|
+
clear(executionId, preserveCompletedPhaseStatus = false) {
|
|
3764
4065
|
if (executionId === void 0) this.queue.length = 0;
|
|
3765
4066
|
else {
|
|
3766
4067
|
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3767
|
-
|
|
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
|
+
}
|
|
3768
4072
|
}
|
|
3769
4073
|
}
|
|
3770
4074
|
this.notify();
|
|
@@ -3846,9 +4150,9 @@ var TransientRunEventHub = class {
|
|
|
3846
4150
|
subscription.push(event);
|
|
3847
4151
|
}
|
|
3848
4152
|
}
|
|
3849
|
-
clear(runId, executionId) {
|
|
4153
|
+
clear(runId, executionId, preserveCompletedPhaseStatus = false) {
|
|
3850
4154
|
for (const subscription of this.subscriptions.get(runId) ?? []) {
|
|
3851
|
-
subscription.clear(executionId);
|
|
4155
|
+
subscription.clear(executionId, preserveCompletedPhaseStatus);
|
|
3852
4156
|
}
|
|
3853
4157
|
}
|
|
3854
4158
|
clearMessage(runId, messageId) {
|
|
@@ -3996,7 +4300,7 @@ function createEventBus() {
|
|
|
3996
4300
|
|
|
3997
4301
|
// src/extensions/runner.ts
|
|
3998
4302
|
async function execCommand(command, args, cwd, options) {
|
|
3999
|
-
return new Promise((
|
|
4303
|
+
return new Promise((resolve9, reject) => {
|
|
4000
4304
|
const child = execFile(
|
|
4001
4305
|
command,
|
|
4002
4306
|
args,
|
|
@@ -4011,7 +4315,7 @@ async function execCommand(command, args, cwd, options) {
|
|
|
4011
4315
|
reject(new Error("Command was aborted"));
|
|
4012
4316
|
return;
|
|
4013
4317
|
}
|
|
4014
|
-
|
|
4318
|
+
resolve9({
|
|
4015
4319
|
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
4016
4320
|
stdout: stdout ?? "",
|
|
4017
4321
|
stderr: stderr ?? ""
|
|
@@ -4869,6 +5173,8 @@ function snapshotResourceValue(kind, value) {
|
|
|
4869
5173
|
const phase = value;
|
|
4870
5174
|
const snapshot = {
|
|
4871
5175
|
...phase,
|
|
5176
|
+
disableAutoInvocation: phase.disableAutoInvocation ?? false,
|
|
5177
|
+
disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
|
|
4872
5178
|
...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
|
|
4873
5179
|
skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
|
|
4874
5180
|
...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
|
|
@@ -4894,7 +5200,7 @@ function capitalize(value) {
|
|
|
4894
5200
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
4895
5201
|
}
|
|
4896
5202
|
function isImplicitCoreName(kind, name) {
|
|
4897
|
-
return kind === "tool" && name === "route" || kind === "phase" && name === "default";
|
|
5203
|
+
return kind === "tool" && name === "route" || kind === "phase" && (name === "default" || name === "stop" || name === "compact");
|
|
4898
5204
|
}
|
|
4899
5205
|
async function loadDirectoryValues(kind, directory, sourceId) {
|
|
4900
5206
|
const root = resolve7(directory);
|
|
@@ -5078,7 +5384,7 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
|
|
|
5078
5384
|
/** Install Rowan-owned implicit resources before any host source is read. */
|
|
5079
5385
|
async ensureCoreResources() {
|
|
5080
5386
|
await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
|
|
5081
|
-
await this.replaceImplicit("phase", "rowan.core",
|
|
5387
|
+
await this.replaceImplicit("phase", "rowan.core", createCorePhases());
|
|
5082
5388
|
}
|
|
5083
5389
|
async loadExtensions(input) {
|
|
5084
5390
|
const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
|
|
@@ -5222,7 +5528,10 @@ function intersectPhaseSelection(parent, layer) {
|
|
|
5222
5528
|
};
|
|
5223
5529
|
}
|
|
5224
5530
|
function resolvePhases(candidates, selection) {
|
|
5225
|
-
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")];
|
|
5226
5535
|
const entryPhaseId = selection?.entryPhaseId ?? null;
|
|
5227
5536
|
if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
|
|
5228
5537
|
console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
|
|
@@ -5244,6 +5553,31 @@ var DEFAULT_POLL_MS = 25;
|
|
|
5244
5553
|
var MAX_CONSUMER_IDLE_POLL_MS = 250;
|
|
5245
5554
|
var OWNER_LEASE_MS = 3e4;
|
|
5246
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
|
+
}
|
|
5247
5581
|
var AgentRuntime = class _AgentRuntime {
|
|
5248
5582
|
concurrency;
|
|
5249
5583
|
owned;
|
|
@@ -5254,6 +5588,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5254
5588
|
executionDone = /* @__PURE__ */ new Map();
|
|
5255
5589
|
cancellationRequested = /* @__PURE__ */ new Set();
|
|
5256
5590
|
cancellationReasons = /* @__PURE__ */ new Map();
|
|
5591
|
+
autoCompactionRuns = /* @__PURE__ */ new Set();
|
|
5257
5592
|
consumers = /* @__PURE__ */ new Map();
|
|
5258
5593
|
transientEvents = new TransientRunEventHub();
|
|
5259
5594
|
resources;
|
|
@@ -5346,6 +5681,36 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5346
5681
|
this.assertOpen();
|
|
5347
5682
|
return this.owned.compact(input);
|
|
5348
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
|
+
}
|
|
5349
5714
|
async start(agentId, input, options) {
|
|
5350
5715
|
this.assertOpen();
|
|
5351
5716
|
const agent = await this.requireAgent(agentId);
|
|
@@ -5365,6 +5730,47 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5365
5730
|
this.assertOpen();
|
|
5366
5731
|
return pageRuns(await this.owned.listRuns(input), { ...input, storeIncarnation: this.storeIncarnation });
|
|
5367
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
|
+
}
|
|
5368
5774
|
async consume(input) {
|
|
5369
5775
|
this.assertOpen();
|
|
5370
5776
|
if (input.consumerId.trim().length === 0) throw new TypeError("consumerId must be non-empty");
|
|
@@ -5412,6 +5818,15 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5412
5818
|
await this.owned.answerInput({ runId, requestId: input.requestId, expectedRevision: snapshot.revision, input: input.input });
|
|
5413
5819
|
void this.pump();
|
|
5414
5820
|
}
|
|
5821
|
+
async respondInteraction(runId, input) {
|
|
5822
|
+
this.assertOpen();
|
|
5823
|
+
const snapshot = await this.owned.snapshotRun(runId);
|
|
5824
|
+
if (snapshot.state !== "input_required" || !snapshot.interactions.some((interaction) => interaction.id === input.interactionId)) {
|
|
5825
|
+
throw new RuntimeError("input_request_conflict", { runId, requestId: input.interactionId, reason: "not_found" });
|
|
5826
|
+
}
|
|
5827
|
+
await this.owned.answerInteraction({ runId, interactionId: input.interactionId, expectedRevision: snapshot.revision, input: input.input });
|
|
5828
|
+
void this.pump();
|
|
5829
|
+
}
|
|
5415
5830
|
async cancel(runId, reason) {
|
|
5416
5831
|
this.assertOpen();
|
|
5417
5832
|
const done = this.executionDone.get(runId);
|
|
@@ -5435,16 +5850,19 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5435
5850
|
try {
|
|
5436
5851
|
while (!this.closed && this.executions.size < this.concurrency) {
|
|
5437
5852
|
const queued = await this.owned.listRuns({ states: ["queued"] });
|
|
5438
|
-
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];
|
|
5439
5854
|
if (!next) return;
|
|
5440
5855
|
this.activeAgents.add(next.agentId);
|
|
5441
5856
|
const task = this.execute(next).finally(() => {
|
|
5442
5857
|
const execution = this.executions.get(next.id);
|
|
5443
|
-
if (execution) this.transientEvents.clear(next.id, execution.executionId);
|
|
5858
|
+
if (execution) this.transientEvents.clear(next.id, execution.executionId, true);
|
|
5444
5859
|
this.activeAgents.delete(next.agentId);
|
|
5445
5860
|
this.executions.delete(next.id);
|
|
5446
5861
|
this.cancellationRequested.delete(next.id);
|
|
5447
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);
|
|
5448
5866
|
this.executionDone.delete(next.id);
|
|
5449
5867
|
void this.pump();
|
|
5450
5868
|
});
|
|
@@ -5501,34 +5919,81 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5501
5919
|
return;
|
|
5502
5920
|
}
|
|
5503
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
|
+
}
|
|
5504
5945
|
const executionId = createId("exec");
|
|
5505
5946
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
5506
5947
|
executionRevision = claim.run.revision;
|
|
5507
5948
|
const controller = new AbortController();
|
|
5508
5949
|
this.executions.set(run.id, { controller, executionId });
|
|
5509
5950
|
if (this.cancellationRequested.has(run.id)) controller.abort();
|
|
5510
|
-
const
|
|
5511
|
-
|
|
5951
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
|
|
5952
|
+
toolArchiveDir: this.archiveDirFor(run.agentId)
|
|
5953
|
+
});
|
|
5512
5954
|
let toolQueue = Promise.resolve();
|
|
5513
5955
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
5514
5956
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
5515
|
-
const
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
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);
|
|
5524
5989
|
const executionTools = {
|
|
5525
5990
|
tools: assembly.context.tools,
|
|
5526
5991
|
beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
|
|
5527
5992
|
afterToolCall: assembly.afterToolCall ?? config.afterToolCall
|
|
5528
5993
|
};
|
|
5529
|
-
const
|
|
5530
|
-
canonicalMessages:
|
|
5531
|
-
context
|
|
5994
|
+
const executeModel = (context) => executeOnce({
|
|
5995
|
+
canonicalMessages: context.messages,
|
|
5996
|
+
context,
|
|
5532
5997
|
execution: {
|
|
5533
5998
|
agentId: run.agentId,
|
|
5534
5999
|
runId: run.id,
|
|
@@ -5537,14 +6002,27 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5537
6002
|
...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
|
|
5538
6003
|
},
|
|
5539
6004
|
model,
|
|
5540
|
-
thinkingLevel: thinkingLevelFromMessages(
|
|
6005
|
+
thinkingLevel: thinkingLevelFromMessages(context.messages),
|
|
5541
6006
|
stream,
|
|
5542
6007
|
maxAttempts: config.maxAttempts,
|
|
5543
6008
|
checkpoint: claim.run.checkpoint,
|
|
6009
|
+
interactionAnswers: claim.run.interactionAnswers,
|
|
5544
6010
|
signal: controller.signal,
|
|
5545
6011
|
beforePhase: assembly.beforePhase,
|
|
5546
6012
|
afterPhase: assembly.afterPhase,
|
|
5547
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
|
+
},
|
|
5548
6026
|
onMessageDelta: (event) => {
|
|
5549
6027
|
const active = this.executions.get(run.id);
|
|
5550
6028
|
if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
|
|
@@ -5596,8 +6074,27 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5596
6074
|
}
|
|
5597
6075
|
}
|
|
5598
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
|
+
}
|
|
5599
6096
|
if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
|
|
5600
|
-
const output = latestAssistant(run, result.messages,
|
|
6097
|
+
const output = latestAssistant(run, result.messages, modelMessages.length, true);
|
|
5601
6098
|
const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
|
|
5602
6099
|
await this.owned.cancelRun({
|
|
5603
6100
|
runId: run.id,
|
|
@@ -5610,14 +6107,39 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5610
6107
|
}
|
|
5611
6108
|
if (result.type === "input_required") {
|
|
5612
6109
|
const prompt = promptMessage(run, result.request.prompt, result.messages.length);
|
|
5613
|
-
await this.owned.commitInputRequired({
|
|
6110
|
+
await this.owned.commitInputRequired({
|
|
6111
|
+
runId: run.id,
|
|
6112
|
+
execution: claim.execution,
|
|
6113
|
+
expectedRevision: executionRevision,
|
|
6114
|
+
requestId: createId("input"),
|
|
6115
|
+
phase: result.request.phase,
|
|
6116
|
+
prompt,
|
|
6117
|
+
checkpoint: result.checkpoint,
|
|
6118
|
+
interactions: result.interactions,
|
|
6119
|
+
interactionAnswers: claim.run.interactionAnswers
|
|
6120
|
+
});
|
|
5614
6121
|
this.transientEvents.clear(run.id, claim.execution.executionId);
|
|
5615
6122
|
return;
|
|
5616
6123
|
}
|
|
5617
6124
|
if (result.type === "completed") {
|
|
5618
|
-
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
|
+
}
|
|
5619
6141
|
await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
|
|
5620
|
-
this.transientEvents.clear(run.id, claim.execution.executionId);
|
|
6142
|
+
this.transientEvents.clear(run.id, claim.execution.executionId, true);
|
|
5621
6143
|
return;
|
|
5622
6144
|
}
|
|
5623
6145
|
const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
|
|
@@ -5650,6 +6172,13 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5650
6172
|
if (!(error instanceof RuntimeError) || !["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) throw error;
|
|
5651
6173
|
});
|
|
5652
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
|
+
}
|
|
5653
6182
|
async executeTool(input) {
|
|
5654
6183
|
const batch = await this.executeToolBatch({ ...input, toolCalls: [input.toolCall] });
|
|
5655
6184
|
return { result: batch.results[0], revision: batch.revision };
|
|
@@ -5740,6 +6269,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5740
6269
|
assertToolExecutionResult(result);
|
|
5741
6270
|
if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
|
|
5742
6271
|
assertToolExecutionResult(result);
|
|
6272
|
+
result = await spillLargeToolResult(
|
|
6273
|
+
result,
|
|
6274
|
+
this.archiveDirFor(input.run.agentId),
|
|
6275
|
+
input.toolCall.name
|
|
6276
|
+
);
|
|
5743
6277
|
const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
|
|
5744
6278
|
return {
|
|
5745
6279
|
result: { toolCallId: providerToolCallId, toolName: input.toolCall.name, ...result },
|
|
@@ -5759,6 +6293,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5759
6293
|
if (!agent) throw new RuntimeError("agent_not_found", { agentId });
|
|
5760
6294
|
return agent;
|
|
5761
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
|
+
}
|
|
5762
6301
|
async runConsumer(subscription) {
|
|
5763
6302
|
const registration = await this.owned.openConsumer(subscription.consumerId);
|
|
5764
6303
|
let cursor = registration.cursor;
|
|
@@ -5800,10 +6339,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5800
6339
|
const subscription = this.transientEvents.subscribe(runId);
|
|
5801
6340
|
try {
|
|
5802
6341
|
while (true) {
|
|
5803
|
-
if (options.signal?.aborted) throw abortError();
|
|
5804
6342
|
const observationVersion = subscription.checkpoint();
|
|
5805
6343
|
const snapshot = await this.owned.snapshotRun(runId);
|
|
5806
|
-
|
|
6344
|
+
const terminal = ["completed", "failed", "cancelled"].includes(snapshot.state);
|
|
6345
|
+
if (options.signal?.aborted && !terminal) throw abortError();
|
|
6346
|
+
if (terminal) {
|
|
5807
6347
|
const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
|
|
5808
6348
|
if (!pending.some((event) => event.runId === runId)) return;
|
|
5809
6349
|
}
|
|
@@ -5816,8 +6356,16 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5816
6356
|
} else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
|
|
5817
6357
|
subscription.clearTool(event.toolCall.id);
|
|
5818
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
|
+
}
|
|
5819
6368
|
yield event;
|
|
5820
|
-
if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) return;
|
|
5821
6369
|
}
|
|
5822
6370
|
const transient = subscription.shift();
|
|
5823
6371
|
if (transient) {
|
|
@@ -5873,6 +6421,9 @@ var DurableRun = class {
|
|
|
5873
6421
|
respond(input) {
|
|
5874
6422
|
return this.runtime.respond(this.id, input);
|
|
5875
6423
|
}
|
|
6424
|
+
respondInteraction(input) {
|
|
6425
|
+
return this.runtime.respondInteraction(this.id, input);
|
|
6426
|
+
}
|
|
5876
6427
|
cancel(reason) {
|
|
5877
6428
|
return this.runtime.cancel(this.id, reason);
|
|
5878
6429
|
}
|
|
@@ -5899,7 +6450,14 @@ function durableOutcome(outcome) {
|
|
|
5899
6450
|
function boundaryFromSnapshot(snapshot) {
|
|
5900
6451
|
switch (snapshot.state) {
|
|
5901
6452
|
case "input_required":
|
|
5902
|
-
return {
|
|
6453
|
+
return {
|
|
6454
|
+
type: "input_required",
|
|
6455
|
+
requestId: snapshot.request.id,
|
|
6456
|
+
phase: snapshot.request.phase,
|
|
6457
|
+
prompt: snapshot.request.prompt,
|
|
6458
|
+
interactions: snapshot.interactions,
|
|
6459
|
+
answers: snapshot.answers
|
|
6460
|
+
};
|
|
5903
6461
|
case "completed":
|
|
5904
6462
|
return { type: "completed", outcome: snapshot.outcome, ...snapshot.output ? { output: snapshot.output } : {} };
|
|
5905
6463
|
case "failed":
|
|
@@ -5911,21 +6469,67 @@ function boundaryFromSnapshot(snapshot) {
|
|
|
5911
6469
|
}
|
|
5912
6470
|
}
|
|
5913
6471
|
function delay(ms) {
|
|
5914
|
-
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);
|
|
5915
6514
|
}
|
|
5916
6515
|
function normalizePhaseRegistry(registry) {
|
|
5917
|
-
|
|
5918
|
-
|
|
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
|
+
}
|
|
5919
6522
|
}
|
|
5920
6523
|
for (const [name] of registry?.phases ?? []) {
|
|
5921
|
-
if (name === "
|
|
6524
|
+
if (name === "continue") {
|
|
5922
6525
|
throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
|
|
5923
6526
|
}
|
|
5924
6527
|
}
|
|
6528
|
+
const authored = [...registry?.phases ?? []].filter(([name, phase]) => !coreNames.has(name) || phase.core === false);
|
|
5925
6529
|
return {
|
|
5926
6530
|
phases: new Map([
|
|
5927
|
-
[
|
|
5928
|
-
...
|
|
6531
|
+
...core.map((phase) => [phase.name, phase]),
|
|
6532
|
+
...authored
|
|
5929
6533
|
]),
|
|
5930
6534
|
entryPhaseId: registry?.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
5931
6535
|
};
|
|
@@ -5943,13 +6547,13 @@ function toJsonValue(value) {
|
|
|
5943
6547
|
return value;
|
|
5944
6548
|
}
|
|
5945
6549
|
function deferred2() {
|
|
5946
|
-
let
|
|
6550
|
+
let resolve9;
|
|
5947
6551
|
let reject;
|
|
5948
6552
|
const promise = new Promise((res, rej) => {
|
|
5949
|
-
|
|
6553
|
+
resolve9 = (value) => res(value);
|
|
5950
6554
|
reject = rej;
|
|
5951
6555
|
});
|
|
5952
|
-
return { promise, resolve:
|
|
6556
|
+
return { promise, resolve: resolve9, reject };
|
|
5953
6557
|
}
|
|
5954
6558
|
|
|
5955
6559
|
// src/runtime/durable-store.ts
|
|
@@ -5971,6 +6575,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
5971
6575
|
ownerEpoch = 0;
|
|
5972
6576
|
eventSequence = 0;
|
|
5973
6577
|
retentionFloor = 1;
|
|
6578
|
+
contextCompactions = /* @__PURE__ */ new Map();
|
|
5974
6579
|
constructor(options = {}) {
|
|
5975
6580
|
this.incarnation = options.incarnation ?? createId("store");
|
|
5976
6581
|
}
|
|
@@ -5989,6 +6594,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
5989
6594
|
for (const [agentId, sequence] of state.nextReadySequence) store.nextReadySequence.set(agentId, sequence);
|
|
5990
6595
|
store.eventSequence = state.eventSequence;
|
|
5991
6596
|
store.retentionFloor = Math.max(1, state.retentionFloor ?? 1);
|
|
6597
|
+
for (const [agentId, record] of state.contextCompactions ?? []) store.contextCompactions.set(agentId, clone(record));
|
|
5992
6598
|
store.ownerEpoch = Math.max(
|
|
5993
6599
|
0,
|
|
5994
6600
|
...state.runs.map((run) => run.execution?.ownerEpoch ?? 0)
|
|
@@ -6010,7 +6616,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6010
6616
|
retentionFloor: this.retentionFloor,
|
|
6011
6617
|
nextAgentSequence: [...this.nextAgentSequence.entries()],
|
|
6012
6618
|
nextReadySequence: [...this.nextReadySequence.entries()],
|
|
6013
|
-
eventSequence: this.eventSequence
|
|
6619
|
+
eventSequence: this.eventSequence,
|
|
6620
|
+
contextCompactions: [...this.contextCompactions.entries()]
|
|
6014
6621
|
});
|
|
6015
6622
|
}
|
|
6016
6623
|
attachOwner(lease) {
|
|
@@ -6189,6 +6796,18 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6189
6796
|
actual: -1
|
|
6190
6797
|
});
|
|
6191
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
|
+
}
|
|
6192
6811
|
const actualRevision = original.messageRevision ?? 0;
|
|
6193
6812
|
if (actualRevision !== input.expectedMessageRevision) {
|
|
6194
6813
|
throw new RuntimeError("message_revision_conflict", {
|
|
@@ -6332,10 +6951,10 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6332
6951
|
const run = this.requireRun(input.runId);
|
|
6333
6952
|
this.assertRevision(run, input.expectedRevision);
|
|
6334
6953
|
this.assertState(run, ["queued"]);
|
|
6335
|
-
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))) {
|
|
6336
6955
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6337
6956
|
}
|
|
6338
|
-
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"))) {
|
|
6339
6958
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6340
6959
|
}
|
|
6341
6960
|
if (run.pinnedConfigToken && input.configToken && run.pinnedConfigToken !== input.configToken) {
|
|
@@ -6343,7 +6962,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6343
6962
|
}
|
|
6344
6963
|
if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
|
|
6345
6964
|
let message;
|
|
6346
|
-
if (!run.checkpoint && !run.initialMessageId) {
|
|
6965
|
+
if (!run.checkpoint && !run.initialMessageId && !isControlRun(run)) {
|
|
6347
6966
|
const userInput = normalizeUserInput(run.input);
|
|
6348
6967
|
message = {
|
|
6349
6968
|
id: input.messageId ?? createId("msg"),
|
|
@@ -6395,7 +7014,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6395
7014
|
if (typeof input.phase !== "string" || input.phase.length === 0) throw new TypeError("phase must be non-empty");
|
|
6396
7015
|
const requestId = input.requestId ?? createId("input");
|
|
6397
7016
|
const operationKey = `input_required:${requestId}`;
|
|
6398
|
-
const
|
|
7017
|
+
const interactions = input.interactions ?? [];
|
|
7018
|
+
const operationPayload = canonicalJson([input.runId, input.expectedRevision, input.phase, input.prompt.id, input.checkpoint, interactions, input.interactionAnswers ?? null]);
|
|
6399
7019
|
const replay = this.replayOperation(operationKey, operationPayload);
|
|
6400
7020
|
if (replay) return clone(replay);
|
|
6401
7021
|
const run = this.requireRun(input.runId);
|
|
@@ -6416,12 +7036,19 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6416
7036
|
run.state = "input_required";
|
|
6417
7037
|
run.checkpoint = clone(input.checkpoint);
|
|
6418
7038
|
run.openInputRequest = request;
|
|
7039
|
+
if (interactions.length > 0) {
|
|
7040
|
+
run.openInteractions = clone(interactions);
|
|
7041
|
+
run.interactionAnswers = clone(input.interactionAnswers ?? run.interactionAnswers ?? {});
|
|
7042
|
+
} else {
|
|
7043
|
+
delete run.openInteractions;
|
|
7044
|
+
delete run.interactionAnswers;
|
|
7045
|
+
}
|
|
6419
7046
|
delete run.execution;
|
|
6420
7047
|
run.revision += 1;
|
|
6421
7048
|
run.updatedAt = createTimestamp();
|
|
6422
7049
|
this.appendMessage(run, prompt);
|
|
6423
|
-
this.appendTransition(run, "running", "input_required", request, prompt);
|
|
6424
|
-
const result = { run: clone(run), prompt: clone(prompt), request: clone(request) };
|
|
7050
|
+
this.appendTransition(run, "running", "input_required", request, prompt, void 0, void 0, void 0, interactions, run.interactionAnswers ?? {});
|
|
7051
|
+
const result = { run: clone(run), prompt: clone(prompt), request: clone(request), interactions: clone(interactions) };
|
|
6425
7052
|
this.writeOperationReceipt(operationKey, operationPayload, result);
|
|
6426
7053
|
return result;
|
|
6427
7054
|
}
|
|
@@ -6438,6 +7065,9 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6438
7065
|
if (!run.openInputRequest || run.openInputRequest.id !== input.requestId) {
|
|
6439
7066
|
throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.requestId, reason: "not_found" });
|
|
6440
7067
|
}
|
|
7068
|
+
if (run.openInteractions && run.openInteractions.length > 0) {
|
|
7069
|
+
throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.requestId, reason: "not_found" });
|
|
7070
|
+
}
|
|
6441
7071
|
const message = {
|
|
6442
7072
|
id: input.messageId ?? createId("msg"),
|
|
6443
7073
|
agentId: run.agentId,
|
|
@@ -6460,6 +7090,50 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6460
7090
|
this.writeOperationReceipt(operationKey, operationPayload, result);
|
|
6461
7091
|
return result;
|
|
6462
7092
|
}
|
|
7093
|
+
answerInteraction(lease, input) {
|
|
7094
|
+
this.assertOwner(lease);
|
|
7095
|
+
if (input.interactionId.trim().length === 0) throw new TypeError("interactionId must be non-empty");
|
|
7096
|
+
const operationKey = `interaction_answer:${input.runId}:${input.interactionId}`;
|
|
7097
|
+
const operationPayload = canonicalJson(input.input);
|
|
7098
|
+
const replay = this.replayOperation(operationKey, operationPayload);
|
|
7099
|
+
if (replay) return clone(replay);
|
|
7100
|
+
const run = this.requireRun(input.runId);
|
|
7101
|
+
this.assertRevision(run, input.expectedRevision);
|
|
7102
|
+
this.assertState(run, ["input_required"]);
|
|
7103
|
+
const openInteractions = run.openInteractions ?? [];
|
|
7104
|
+
if (!openInteractions.some((interaction) => interaction.id === input.interactionId)) {
|
|
7105
|
+
throw new RuntimeError("input_request_conflict", { runId: run.id, requestId: input.interactionId, reason: "not_found" });
|
|
7106
|
+
}
|
|
7107
|
+
const message = {
|
|
7108
|
+
id: createId("msg"),
|
|
7109
|
+
agentId: run.agentId,
|
|
7110
|
+
runId: run.id,
|
|
7111
|
+
role: "user",
|
|
7112
|
+
content: typeof input.input === "string" ? input.input : JSON.stringify(input.input),
|
|
7113
|
+
metadata: { kind: "phase_interaction", interactionId: input.interactionId },
|
|
7114
|
+
sequenceWithinRun: this.nextMessageSequence(run.id),
|
|
7115
|
+
createdAt: createTimestamp()
|
|
7116
|
+
};
|
|
7117
|
+
this.messages.set(message.id, message);
|
|
7118
|
+
run.openInteractions = openInteractions.filter((interaction) => interaction.id !== input.interactionId);
|
|
7119
|
+
run.interactionAnswers = {
|
|
7120
|
+
...run.interactionAnswers ?? {},
|
|
7121
|
+
[input.interactionId]: clone(input.input)
|
|
7122
|
+
};
|
|
7123
|
+
run.revision += 1;
|
|
7124
|
+
run.updatedAt = message.createdAt;
|
|
7125
|
+
this.appendMessage(run, message);
|
|
7126
|
+
if (run.openInteractions.length === 0) {
|
|
7127
|
+
delete run.openInteractions;
|
|
7128
|
+
delete run.openInputRequest;
|
|
7129
|
+
run.readySequence = this.nextReady(run.agentId);
|
|
7130
|
+
run.state = "queued";
|
|
7131
|
+
this.appendTransition(run, "input_required", "queued");
|
|
7132
|
+
}
|
|
7133
|
+
const result = clone(run);
|
|
7134
|
+
this.writeOperationReceipt(operationKey, operationPayload, result);
|
|
7135
|
+
return result;
|
|
7136
|
+
}
|
|
6463
7137
|
reserveToolCall(lease, input) {
|
|
6464
7138
|
const providerToolCallId = input.providerToolCallId ?? (input.toolCallId ? String(input.toolCallId) : createId("provider"));
|
|
6465
7139
|
const result = this.reserveToolCalls(lease, {
|
|
@@ -6654,6 +7328,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6654
7328
|
if (input.outcome) run.outcome = clone(input.outcome);
|
|
6655
7329
|
if (input.failure) run.failure = clone(input.failure);
|
|
6656
7330
|
delete run.execution;
|
|
7331
|
+
delete run.openInteractions;
|
|
7332
|
+
delete run.interactionAnswers;
|
|
6657
7333
|
run.revision += 1;
|
|
6658
7334
|
run.updatedAt = createTimestamp();
|
|
6659
7335
|
this.appendTransition(run, "running", nextState, void 0, output, input.outcome, input.failure);
|
|
@@ -6699,6 +7375,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6699
7375
|
delete run.cancellationReason;
|
|
6700
7376
|
delete run.execution;
|
|
6701
7377
|
delete run.openInputRequest;
|
|
7378
|
+
delete run.openInteractions;
|
|
7379
|
+
delete run.interactionAnswers;
|
|
6702
7380
|
run.revision += 1;
|
|
6703
7381
|
run.updatedAt = createTimestamp();
|
|
6704
7382
|
this.appendTransition(run, from, "failed", void 0, void 0, void 0, failure);
|
|
@@ -6710,6 +7388,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6710
7388
|
run.cancellationReason = input.reason;
|
|
6711
7389
|
delete run.execution;
|
|
6712
7390
|
delete run.openInputRequest;
|
|
7391
|
+
delete run.openInteractions;
|
|
7392
|
+
delete run.interactionAnswers;
|
|
6713
7393
|
run.revision += 1;
|
|
6714
7394
|
run.updatedAt = createTimestamp();
|
|
6715
7395
|
this.appendTransition(run, from, "cancelled", void 0, void 0, void 0, void 0, input.reason);
|
|
@@ -6741,7 +7421,13 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6741
7421
|
const request = run.openInputRequest;
|
|
6742
7422
|
const prompt = request && this.messages.get(request.messageId);
|
|
6743
7423
|
if (!prompt || prompt.role !== "assistant") throw new Error(`Run ${run.id} has an invalid input prompt.`);
|
|
6744
|
-
return {
|
|
7424
|
+
return {
|
|
7425
|
+
...base,
|
|
7426
|
+
state: "input_required",
|
|
7427
|
+
request: { id: request.id, phase: request.phase, prompt: clone(prompt) },
|
|
7428
|
+
interactions: clone(run.openInteractions ?? []),
|
|
7429
|
+
answers: clone(run.interactionAnswers ?? {})
|
|
7430
|
+
};
|
|
6745
7431
|
}
|
|
6746
7432
|
case "completed": {
|
|
6747
7433
|
const output = this.messagesForRun(run.id).filter((message) => message.role === "assistant").at(-1);
|
|
@@ -6951,6 +7637,72 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6951
7637
|
this.requireAgent(agentId);
|
|
6952
7638
|
return this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
|
|
6953
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
|
+
}
|
|
6954
7706
|
activeHistory(agentId, beforeSequence) {
|
|
6955
7707
|
return clone([
|
|
6956
7708
|
...this.historySeeds.get(agentId) ?? [],
|
|
@@ -6963,13 +7715,14 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6963
7715
|
message
|
|
6964
7716
|
}));
|
|
6965
7717
|
}
|
|
6966
|
-
appendTransition(run, from, to, request, prompt, outcome, failure, reason) {
|
|
7718
|
+
appendTransition(run, from, to, request, prompt, outcome, failure, reason, interactions, answers) {
|
|
6967
7719
|
const transition = {
|
|
6968
7720
|
...this.baseEvent(run),
|
|
6969
7721
|
kind: "run_state_changed",
|
|
6970
7722
|
from,
|
|
6971
7723
|
to,
|
|
6972
7724
|
...to === "input_required" && request && prompt ? { request: { id: request.id, phase: request.phase, prompt } } : {},
|
|
7725
|
+
...to === "input_required" ? { interactions: clone(interactions ?? []), answers: clone(answers ?? {}) } : {},
|
|
6973
7726
|
...to === "completed" && outcome ? { outcome } : {},
|
|
6974
7727
|
...to === "failed" && failure ? { failure } : {},
|
|
6975
7728
|
...to === "cancelled" && reason ? { reason } : {}
|
|
@@ -7032,6 +7785,15 @@ var MemoryOwnedStore = class {
|
|
|
7032
7785
|
async compact(input) {
|
|
7033
7786
|
return this.store.compact(this.lease, input);
|
|
7034
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
|
+
}
|
|
7035
7797
|
async createRun(input) {
|
|
7036
7798
|
return this.store.createRun(this.lease, input);
|
|
7037
7799
|
}
|
|
@@ -7047,6 +7809,9 @@ var MemoryOwnedStore = class {
|
|
|
7047
7809
|
async answerInput(input) {
|
|
7048
7810
|
return this.store.answerInput(this.lease, input);
|
|
7049
7811
|
}
|
|
7812
|
+
async answerInteraction(input) {
|
|
7813
|
+
return this.store.answerInteraction(this.lease, input);
|
|
7814
|
+
}
|
|
7050
7815
|
async commitOutcome(input) {
|
|
7051
7816
|
return this.store.commitOutcome(this.lease, input);
|
|
7052
7817
|
}
|
|
@@ -7118,6 +7883,29 @@ function userInputContent(input) {
|
|
|
7118
7883
|
function userInputMetadata(input) {
|
|
7119
7884
|
return typeof input === "string" ? void 0 : input.metadata;
|
|
7120
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
|
+
}
|
|
7121
7909
|
function targetInput(input, metadata) {
|
|
7122
7910
|
const content = typeof input === "string" ? input : input.content;
|
|
7123
7911
|
return metadata === void 0 ? { content } : { content, metadata: clone(metadata) };
|
|
@@ -7159,6 +7947,9 @@ function assertToolValue(value, argument) {
|
|
|
7159
7947
|
|
|
7160
7948
|
// src/runtime/sqlite-durable-store.ts
|
|
7161
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";
|
|
7162
7953
|
var SCHEMA_ID = "rowan.agent.runtime";
|
|
7163
7954
|
var SCHEMA_VERSION = "2";
|
|
7164
7955
|
var SCHEMA_VALUE = `${SCHEMA_ID}:${SCHEMA_VERSION}`;
|
|
@@ -7263,10 +8054,15 @@ var CURRENT_SCHEMA_SQL = `
|
|
|
7263
8054
|
`;
|
|
7264
8055
|
var SqliteStore = class {
|
|
7265
8056
|
database;
|
|
8057
|
+
archiveRoot;
|
|
8058
|
+
mirroredEventIds = /* @__PURE__ */ new Map();
|
|
8059
|
+
mirroredCompactionIds = /* @__PURE__ */ new Map();
|
|
8060
|
+
mirroredAgents = /* @__PURE__ */ new Set();
|
|
7266
8061
|
closed = false;
|
|
7267
8062
|
configured = false;
|
|
7268
8063
|
constructor(filename = ":memory:") {
|
|
7269
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");
|
|
7270
8066
|
}
|
|
7271
8067
|
async openOwner(input) {
|
|
7272
8068
|
this.assertOpen();
|
|
@@ -7318,6 +8114,10 @@ var SqliteStore = class {
|
|
|
7318
8114
|
this.closed = true;
|
|
7319
8115
|
this.database.close();
|
|
7320
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
|
+
}
|
|
7321
8121
|
async reserveAgent(lease, input) {
|
|
7322
8122
|
return this.invoke(lease, (store, current) => store.reserveAgent(current, input));
|
|
7323
8123
|
}
|
|
@@ -7333,6 +8133,15 @@ var SqliteStore = class {
|
|
|
7333
8133
|
async reviseMessage(lease, input) {
|
|
7334
8134
|
return this.invoke(lease, (store, current) => store.reviseMessage(current, input));
|
|
7335
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
|
+
}
|
|
7336
8145
|
async createRun(lease, input) {
|
|
7337
8146
|
return this.invoke(lease, (store, current) => store.createRun(current, input));
|
|
7338
8147
|
}
|
|
@@ -7348,6 +8157,9 @@ var SqliteStore = class {
|
|
|
7348
8157
|
async answerInput(lease, input) {
|
|
7349
8158
|
return this.invoke(lease, (store, current) => store.answerInput(current, input));
|
|
7350
8159
|
}
|
|
8160
|
+
async answerInteraction(lease, input) {
|
|
8161
|
+
return this.invoke(lease, (store, current) => store.answerInteraction(current, input));
|
|
8162
|
+
}
|
|
7351
8163
|
async commitOutcome(lease, input) {
|
|
7352
8164
|
return this.invoke(lease, (store, current) => store.commitOutcome(current, input));
|
|
7353
8165
|
}
|
|
@@ -7577,6 +8389,64 @@ var SqliteStore = class {
|
|
|
7577
8389
|
[consumerId, Number(String(cursor).split(":").at(-1)), null, (/* @__PURE__ */ new Date()).toISOString()]
|
|
7578
8390
|
);
|
|
7579
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;
|
|
7580
8450
|
}
|
|
7581
8451
|
requireMatchingOwner(lease, requireLive) {
|
|
7582
8452
|
const row = this.readOwner();
|
|
@@ -7632,6 +8502,9 @@ var SqliteOwnedStore = class {
|
|
|
7632
8502
|
}
|
|
7633
8503
|
store;
|
|
7634
8504
|
lease;
|
|
8505
|
+
contextArchiveDir(agentId) {
|
|
8506
|
+
return this.store.contextArchiveDir(agentId);
|
|
8507
|
+
}
|
|
7635
8508
|
reserveAgent(input) {
|
|
7636
8509
|
return this.store.reserveAgent(this.lease, input);
|
|
7637
8510
|
}
|
|
@@ -7650,6 +8523,15 @@ var SqliteOwnedStore = class {
|
|
|
7650
8523
|
compact(input) {
|
|
7651
8524
|
return this.store.compact(this.lease, input);
|
|
7652
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
|
+
}
|
|
7653
8535
|
createRun(input) {
|
|
7654
8536
|
return this.store.createRun(this.lease, input);
|
|
7655
8537
|
}
|
|
@@ -7665,6 +8547,9 @@ var SqliteOwnedStore = class {
|
|
|
7665
8547
|
answerInput(input) {
|
|
7666
8548
|
return this.store.answerInput(this.lease, input);
|
|
7667
8549
|
}
|
|
8550
|
+
answerInteraction(input) {
|
|
8551
|
+
return this.store.answerInteraction(this.lease, input);
|
|
8552
|
+
}
|
|
7668
8553
|
commitOutcome(input) {
|
|
7669
8554
|
return this.store.commitOutcome(this.lease, input);
|
|
7670
8555
|
}
|
|
@@ -7752,29 +8637,30 @@ function assertOwnerInput(ownerId, leaseMs) {
|
|
|
7752
8637
|
|
|
7753
8638
|
// src/index.ts
|
|
7754
8639
|
function createCoreTools2(input = {}) {
|
|
7755
|
-
return
|
|
7756
|
-
name: tool.name,
|
|
7757
|
-
description: tool.description,
|
|
7758
|
-
parameters: tool.parameters,
|
|
7759
|
-
execute: async (args, context, signal) => {
|
|
7760
|
-
const result = await tool.execute(args, { skills: [], toolCallId: context.toolCallId }, signal);
|
|
7761
|
-
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Tool failed." };
|
|
7762
|
-
}
|
|
7763
|
-
}));
|
|
8640
|
+
return createRuntimeCoreTools(input);
|
|
7764
8641
|
}
|
|
7765
8642
|
export {
|
|
7766
8643
|
AgentRuntime,
|
|
8644
|
+
COMPACT_PHASE_ID,
|
|
8645
|
+
DEFAULT_PHASE_ID,
|
|
7767
8646
|
ExtensionLifetimeError,
|
|
7768
8647
|
InMemoryConfigProvider,
|
|
7769
8648
|
InMemoryStore,
|
|
8649
|
+
PhaseInteractionBoundary,
|
|
8650
|
+
PhaseInteractionCancelledError,
|
|
7770
8651
|
ResourceRegistry,
|
|
7771
8652
|
ResourceRegistryError,
|
|
7772
8653
|
RuntimeBootstrapRegistry,
|
|
7773
8654
|
RuntimeError,
|
|
7774
8655
|
RuntimeExtensionLifetime,
|
|
8656
|
+
STOP_PHASE_ID,
|
|
7775
8657
|
SqliteStore,
|
|
7776
8658
|
brandConfigToken,
|
|
8659
|
+
createCompactPhase,
|
|
8660
|
+
createCorePhases,
|
|
7777
8661
|
createCoreTools2 as createCoreTools,
|
|
8662
|
+
createDefaultPhase,
|
|
8663
|
+
createStopPhase,
|
|
7778
8664
|
isRuntimeError,
|
|
7779
8665
|
loadExtensionsFromPath as loadExtensions,
|
|
7780
8666
|
loadPhase,
|