@rowan-agent/agent 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +150 -6
- package/dist/index.js +906 -236
- 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,9 @@ 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,
|
|
862
|
+
onThinkingDelta: input.onThinkingDelta
|
|
826
863
|
});
|
|
827
864
|
const response = result.doneResponse ?? {
|
|
828
865
|
content: result.text,
|
|
@@ -845,12 +882,14 @@ async function invokeModel(input) {
|
|
|
845
882
|
};
|
|
846
883
|
}
|
|
847
884
|
async function collectStreamResult(input) {
|
|
885
|
+
const persistAssistant = input.output !== "internal";
|
|
848
886
|
let activeMessageId;
|
|
849
887
|
let lastPartial;
|
|
850
888
|
let stopReason;
|
|
851
889
|
let doneResponse;
|
|
852
890
|
let streamedOutputCharacters = 0;
|
|
853
891
|
const toolCallsWithDeltas = /* @__PURE__ */ new Set();
|
|
892
|
+
const thinkingOffsets = /* @__PURE__ */ new Map();
|
|
854
893
|
for await (const event of input.events) {
|
|
855
894
|
if (event.type === "text_delta") {
|
|
856
895
|
streamedOutputCharacters += event.text.length;
|
|
@@ -867,7 +906,7 @@ async function collectStreamResult(input) {
|
|
|
867
906
|
}
|
|
868
907
|
const abortResult2 = LoopGuard.checkAbort(input.config.signal);
|
|
869
908
|
if (abortResult2.stopReason !== "none") {
|
|
870
|
-
if (activeMessageId) {
|
|
909
|
+
if (persistAssistant && activeMessageId) {
|
|
871
910
|
await input.message.end(activeMessageId);
|
|
872
911
|
activeMessageId = void 0;
|
|
873
912
|
}
|
|
@@ -878,27 +917,53 @@ async function collectStreamResult(input) {
|
|
|
878
917
|
}
|
|
879
918
|
if (event.type === "start") {
|
|
880
919
|
lastPartial = event.partial;
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
920
|
+
if (persistAssistant) {
|
|
921
|
+
activeMessageId ??= input.message.reserve("assistant", {
|
|
922
|
+
phase: input.metadataPhase
|
|
923
|
+
});
|
|
924
|
+
}
|
|
884
925
|
}
|
|
885
926
|
if (event.type === "text_delta") {
|
|
886
927
|
lastPartial = event.partial;
|
|
887
|
-
if (!activeMessageId) {
|
|
928
|
+
if (persistAssistant && !activeMessageId) {
|
|
888
929
|
activeMessageId = input.message.reserve("assistant", {
|
|
889
930
|
phase: input.metadataPhase
|
|
890
931
|
});
|
|
891
932
|
}
|
|
892
|
-
await input.message.update(activeMessageId, event.text);
|
|
933
|
+
if (persistAssistant && activeMessageId) await input.message.update(activeMessageId, event.text);
|
|
893
934
|
}
|
|
894
935
|
if (event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
936
|
+
if (persistAssistant) {
|
|
937
|
+
activeMessageId ??= input.message.reserve("assistant", {
|
|
938
|
+
phase: input.metadataPhase
|
|
939
|
+
});
|
|
940
|
+
}
|
|
898
941
|
lastPartial = event.partial;
|
|
899
942
|
}
|
|
900
943
|
if (event.type === "thinking_delta") {
|
|
944
|
+
if (persistAssistant && !activeMessageId) {
|
|
945
|
+
activeMessageId = input.message.reserve("assistant", {
|
|
946
|
+
phase: input.metadataPhase
|
|
947
|
+
});
|
|
948
|
+
}
|
|
901
949
|
lastPartial = event.partial;
|
|
950
|
+
if (persistAssistant && activeMessageId) {
|
|
951
|
+
let blockIndex = 0;
|
|
952
|
+
for (let index = event.partial.contentBlocks.length - 1; index >= 0; index -= 1) {
|
|
953
|
+
if (event.partial.contentBlocks[index]?.type === "thinking") {
|
|
954
|
+
blockIndex = index;
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const offset = thinkingOffsets.get(blockIndex) ?? 0;
|
|
959
|
+
input.onThinkingDelta?.({
|
|
960
|
+
messageId: activeMessageId,
|
|
961
|
+
blockIndex,
|
|
962
|
+
offset,
|
|
963
|
+
text: event.thinking
|
|
964
|
+
});
|
|
965
|
+
thinkingOffsets.set(blockIndex, offset + event.thinking.length);
|
|
966
|
+
}
|
|
902
967
|
}
|
|
903
968
|
if (event.type === "done") {
|
|
904
969
|
doneResponse = event.response;
|
|
@@ -906,10 +971,10 @@ async function collectStreamResult(input) {
|
|
|
906
971
|
const contentBlocks2 = lastPartial?.contentBlocks ?? [];
|
|
907
972
|
const hasContent2 = contentBlocks2.length > 0 || (event.response?.content?.length ?? 0) > 0 || (event.response?.toolCalls?.length ?? 0) > 0;
|
|
908
973
|
const shouldStoreContentParts = contentBlocks2.some((block) => block.type !== "text");
|
|
909
|
-
if (activeMessageId && hasContent2 && shouldStoreContentParts) {
|
|
974
|
+
if (persistAssistant && activeMessageId && hasContent2 && shouldStoreContentParts) {
|
|
910
975
|
input.message.replaceContent(activeMessageId, contentBlocksToMessageContent(contentBlocks2));
|
|
911
976
|
}
|
|
912
|
-
if (activeMessageId) {
|
|
977
|
+
if (persistAssistant && activeMessageId) {
|
|
913
978
|
if (hasContent2) await input.message.end(activeMessageId);
|
|
914
979
|
else input.message.discard(activeMessageId);
|
|
915
980
|
activeMessageId = void 0;
|
|
@@ -918,13 +983,13 @@ async function collectStreamResult(input) {
|
|
|
918
983
|
}
|
|
919
984
|
const abortResult = LoopGuard.checkAbort(input.config.signal);
|
|
920
985
|
if (abortResult.stopReason !== "none") {
|
|
921
|
-
if (activeMessageId) {
|
|
986
|
+
if (persistAssistant && activeMessageId) {
|
|
922
987
|
await input.message.end(activeMessageId);
|
|
923
988
|
activeMessageId = void 0;
|
|
924
989
|
}
|
|
925
990
|
return { text: abortResult.message, contentBlocks: [], toolCalls: [], stopReason: "aborted" };
|
|
926
991
|
}
|
|
927
|
-
if (activeMessageId) {
|
|
992
|
+
if (persistAssistant && activeMessageId) {
|
|
928
993
|
await input.message.end(activeMessageId);
|
|
929
994
|
}
|
|
930
995
|
const hasContent = (lastPartial?.contentBlocks?.length ?? 0) > 0 || (doneResponse?.content?.length ?? 0) > 0 || (doneResponse?.toolCalls?.length ?? 0) > 0;
|
|
@@ -1317,7 +1382,7 @@ function optionalBundledSkills(value, field) {
|
|
|
1317
1382
|
if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of Skills`);
|
|
1318
1383
|
const names = /* @__PURE__ */ new Set();
|
|
1319
1384
|
for (const [index, item] of value.entries()) {
|
|
1320
|
-
if (!isRecord(item) || typeof item.name !== "string" || item.name.trim() === "" || typeof item.description !== "string" || typeof item.filePath !== "string" || typeof item.baseDir !== "string" || typeof item.content !== "string" || typeof item.
|
|
1385
|
+
if (!isRecord(item) || typeof item.name !== "string" || item.name.trim() === "" || typeof item.description !== "string" || typeof item.filePath !== "string" || typeof item.baseDir !== "string" || typeof item.content !== "string" || item.disableAutoInvocation !== void 0 && typeof item.disableAutoInvocation !== "boolean" || item.disableImplicitInvocation !== void 0 && typeof item.disableImplicitInvocation !== "boolean") {
|
|
1321
1386
|
throw new TypeError(`${field}[${index}] is invalid`);
|
|
1322
1387
|
}
|
|
1323
1388
|
if (names.has(item.name)) throw new TypeError(`Duplicate bundled Skill "${item.name}".`);
|
|
@@ -1456,7 +1521,9 @@ async function loadSkill(path) {
|
|
|
1456
1521
|
filePath: resolved,
|
|
1457
1522
|
baseDir: dirname3(resolved),
|
|
1458
1523
|
content: body,
|
|
1459
|
-
|
|
1524
|
+
disableAutoInvocation: metadata["disable-auto-invocation"] === true || metadata["disable-model-invocation"] === true,
|
|
1525
|
+
disableImplicitInvocation: metadata["disable-implicit-invocation"] === true,
|
|
1526
|
+
...metadata["disable-model-invocation"] === true ? { disableModelInvocation: true } : {}
|
|
1460
1527
|
};
|
|
1461
1528
|
}
|
|
1462
1529
|
async function loadSkills(targetPath) {
|
|
@@ -1548,7 +1615,9 @@ async function loadPhase(targetPath) {
|
|
|
1548
1615
|
filePath: resolved,
|
|
1549
1616
|
baseDir,
|
|
1550
1617
|
content: definition.prompt,
|
|
1551
|
-
model: definition.model
|
|
1618
|
+
model: definition.model,
|
|
1619
|
+
disableAutoInvocation: metadata["disable-auto-invocation"] === true,
|
|
1620
|
+
disableImplicitInvocation: metadata["disable-implicit-invocation"] === true
|
|
1552
1621
|
};
|
|
1553
1622
|
const codePath = await discoverPhaseCode(baseDir);
|
|
1554
1623
|
if (codePath) {
|
|
@@ -1684,16 +1753,126 @@ async function loadPhaseCode(codePath) {
|
|
|
1684
1753
|
|
|
1685
1754
|
// src/harness/phases/default.ts
|
|
1686
1755
|
var DEFAULT_PHASE_ID = "default";
|
|
1756
|
+
var STOP_PHASE_ID = "stop";
|
|
1757
|
+
var COMPACT_PHASE_ID = "compact";
|
|
1687
1758
|
function createDefaultPhase() {
|
|
1688
1759
|
return {
|
|
1689
1760
|
name: DEFAULT_PHASE_ID,
|
|
1690
|
-
|
|
1761
|
+
core: true,
|
|
1762
|
+
description: "Execute the current user request using the current context.",
|
|
1763
|
+
filePath: "",
|
|
1764
|
+
baseDir: "",
|
|
1765
|
+
skills: [],
|
|
1766
|
+
content: [
|
|
1767
|
+
"Execute the current user request using the current context.",
|
|
1768
|
+
"Must not plan or evaluate unless requested; use available tools only when needed.",
|
|
1769
|
+
"If more user input is needed, omit route. If the request is complete, route(stop) as the only target."
|
|
1770
|
+
].join("\n"),
|
|
1771
|
+
disableAutoInvocation: false,
|
|
1772
|
+
disableImplicitInvocation: true
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
function createStopPhase() {
|
|
1776
|
+
return {
|
|
1777
|
+
name: STOP_PHASE_ID,
|
|
1778
|
+
core: true,
|
|
1779
|
+
description: "Provide the final user-facing conclusion for the completed task.",
|
|
1691
1780
|
filePath: "",
|
|
1692
1781
|
baseDir: "",
|
|
1693
1782
|
skills: [],
|
|
1694
|
-
content:
|
|
1783
|
+
content: [
|
|
1784
|
+
"Return only a brief normal-exit explanation in the user's language (one or two sentences).",
|
|
1785
|
+
"Must use only facts from the conversation and completed work, and state that the current run ended normally.",
|
|
1786
|
+
"If no actionable work occurred, say so briefly. Never invent Backlog, Task, Context, blockers, or next steps.",
|
|
1787
|
+
"Do not greet, ask questions, mention routing, call tools, or route to another Phase."
|
|
1788
|
+
].join("\n"),
|
|
1789
|
+
disableAutoInvocation: false,
|
|
1790
|
+
disableImplicitInvocation: false
|
|
1695
1791
|
};
|
|
1696
1792
|
}
|
|
1793
|
+
function createCompactPhase() {
|
|
1794
|
+
return {
|
|
1795
|
+
name: COMPACT_PHASE_ID,
|
|
1796
|
+
core: true,
|
|
1797
|
+
description: "Compact the complete conversation context into a durable summary.",
|
|
1798
|
+
filePath: "",
|
|
1799
|
+
baseDir: "",
|
|
1800
|
+
skills: [],
|
|
1801
|
+
content: "Summarize all historical conversation roles and tool activity for future context. Preserve durable archive references and important decisions.",
|
|
1802
|
+
disableAutoInvocation: true,
|
|
1803
|
+
disableImplicitInvocation: false,
|
|
1804
|
+
run: async (context, execution) => {
|
|
1805
|
+
await execution.reportStatus({
|
|
1806
|
+
state: "running",
|
|
1807
|
+
kind: "compacting",
|
|
1808
|
+
message: "Compacting conversation context."
|
|
1809
|
+
});
|
|
1810
|
+
const tools = context.tools.filter((tool) => tool.core && (tool.name === "read" || tool.name === "bash"));
|
|
1811
|
+
const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
|
|
1812
|
+
let result = await execution.invokeModel(working, { output: "internal" });
|
|
1813
|
+
for (let attempt = 0; attempt < 8 && result.toolCalls.length > 0; attempt += 1) {
|
|
1814
|
+
const calls = result.toolCalls.filter((call) => tools.some((tool) => tool.name === call.name));
|
|
1815
|
+
if (calls.length === 0) break;
|
|
1816
|
+
const normalizedCalls = calls.map((call) => ({
|
|
1817
|
+
...call,
|
|
1818
|
+
args: typeof call.args === "string" ? (() => {
|
|
1819
|
+
try {
|
|
1820
|
+
return JSON.parse(call.args);
|
|
1821
|
+
} catch {
|
|
1822
|
+
return call.args;
|
|
1823
|
+
}
|
|
1824
|
+
})() : call.args
|
|
1825
|
+
}));
|
|
1826
|
+
const assistantContent = result.contentBlocks.length > 0 ? contentBlocksToMessageContent(result.contentBlocks) : normalizedCalls.map((call) => ({ type: "tool_use", id: call.id, name: call.name, input: call.args }));
|
|
1827
|
+
working.messages.push(createMessage("assistant", assistantContent, {
|
|
1828
|
+
kind: "phase_tool",
|
|
1829
|
+
phase: COMPACT_PHASE_ID
|
|
1830
|
+
}));
|
|
1831
|
+
for (const call of normalizedCalls) {
|
|
1832
|
+
const tool = tools.find((candidate) => candidate.name === call.name);
|
|
1833
|
+
let toolResult2;
|
|
1834
|
+
try {
|
|
1835
|
+
toolResult2 = await tool.execute(call.args, {
|
|
1836
|
+
skills: working.skills,
|
|
1837
|
+
toolCallId: call.id
|
|
1838
|
+
});
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
toolResult2 = {
|
|
1841
|
+
toolCallId: call.id,
|
|
1842
|
+
toolName: call.name,
|
|
1843
|
+
ok: false,
|
|
1844
|
+
content: null,
|
|
1845
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
working.messages.push(createMessage("tool", [{
|
|
1849
|
+
type: "tool_result",
|
|
1850
|
+
toolUseId: call.id,
|
|
1851
|
+
content: JSON.stringify(toolResult2),
|
|
1852
|
+
isError: !toolResult2.ok
|
|
1853
|
+
}], {
|
|
1854
|
+
kind: "phase_tool",
|
|
1855
|
+
phase: COMPACT_PHASE_ID
|
|
1856
|
+
}));
|
|
1857
|
+
}
|
|
1858
|
+
result = await execution.invokeModel(working, { output: "internal" });
|
|
1859
|
+
}
|
|
1860
|
+
return {
|
|
1861
|
+
route: "stop",
|
|
1862
|
+
phase: COMPACT_PHASE_ID,
|
|
1863
|
+
payload: { kind: "context_compaction", summary: result.text },
|
|
1864
|
+
status: {
|
|
1865
|
+
state: "completed",
|
|
1866
|
+
kind: "compacted",
|
|
1867
|
+
message: "Conversation context compacted."
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function createCorePhases() {
|
|
1874
|
+
return [createDefaultPhase(), createStopPhase(), createCompactPhase()];
|
|
1875
|
+
}
|
|
1697
1876
|
|
|
1698
1877
|
// src/harness/resource-selection.ts
|
|
1699
1878
|
function selectNamedResources(candidates, names, kind) {
|
|
@@ -1745,7 +1924,7 @@ function buildSystemPrompt(options) {
|
|
|
1745
1924
|
addGuideline(g);
|
|
1746
1925
|
}
|
|
1747
1926
|
}
|
|
1748
|
-
const visibleSkills = (skills ?? []).filter((s) => !s.disableModelInvocation);
|
|
1927
|
+
const visibleSkills = (skills ?? []).filter((s) => !(s.disableAutoInvocation ?? s.disableModelInvocation));
|
|
1749
1928
|
const skillsBlock = visibleSkills.length > 0 ? buildSkillsDescription(visibleSkills) : "";
|
|
1750
1929
|
for (const g of promptGuidelines ?? []) {
|
|
1751
1930
|
addGuideline(g);
|
|
@@ -1781,6 +1960,7 @@ function serializeSkills(skills) {
|
|
|
1781
1960
|
name: skill.name,
|
|
1782
1961
|
description: skill.description,
|
|
1783
1962
|
filePath: skill.filePath,
|
|
1963
|
+
disableAutoInvocation: skill.disableAutoInvocation,
|
|
1784
1964
|
disableModelInvocation: skill.disableModelInvocation
|
|
1785
1965
|
}));
|
|
1786
1966
|
}
|
|
@@ -1903,80 +2083,9 @@ var createOutcome = {
|
|
|
1903
2083
|
}
|
|
1904
2084
|
};
|
|
1905
2085
|
|
|
1906
|
-
// src/harness/context/compaction.ts
|
|
1907
|
-
function needsCompaction(messages, options = {}) {
|
|
1908
|
-
const maxMessages = options.maxMessages ?? 50;
|
|
1909
|
-
return messages.length > maxMessages;
|
|
1910
|
-
}
|
|
1911
|
-
function buildSummary(messages) {
|
|
1912
|
-
const parts = [];
|
|
1913
|
-
let currentRole;
|
|
1914
|
-
let currentContent = [];
|
|
1915
|
-
function flush() {
|
|
1916
|
-
if (currentRole && currentContent.length > 0) {
|
|
1917
|
-
const combined = currentContent.join("\n");
|
|
1918
|
-
const truncated = combined.length > 500 ? combined.slice(0, 500) + "..." : combined;
|
|
1919
|
-
parts.push(`[${currentRole}]: ${truncated}`);
|
|
1920
|
-
}
|
|
1921
|
-
currentContent = [];
|
|
1922
|
-
}
|
|
1923
|
-
for (const msg of messages) {
|
|
1924
|
-
if (msg.role !== currentRole) {
|
|
1925
|
-
flush();
|
|
1926
|
-
currentRole = msg.role;
|
|
1927
|
-
}
|
|
1928
|
-
currentContent.push(messageContentText(msg.content));
|
|
1929
|
-
}
|
|
1930
|
-
flush();
|
|
1931
|
-
return parts.join("\n\n");
|
|
1932
|
-
}
|
|
1933
|
-
function compactMessages(messages, options = {}) {
|
|
1934
|
-
if (!needsCompaction(messages, options)) {
|
|
1935
|
-
return { compacted: false, messages };
|
|
1936
|
-
}
|
|
1937
|
-
const keepRecent = options.keepRecent ?? 10;
|
|
1938
|
-
const minCompact = options.minCompact ?? 20;
|
|
1939
|
-
const firstUserIdx = messages.findIndex(
|
|
1940
|
-
(m) => m.role === "user"
|
|
1941
|
-
);
|
|
1942
|
-
const recentStart = Math.max(messages.length - keepRecent, firstUserIdx + 1);
|
|
1943
|
-
const oldMessages = messages.slice(firstUserIdx >= 0 ? firstUserIdx + 1 : 0, recentStart);
|
|
1944
|
-
const protectedPhaseMessages = oldMessages.filter(
|
|
1945
|
-
(message) => message.metadata?.kind === "phase_prompt"
|
|
1946
|
-
);
|
|
1947
|
-
const summarizableMessages = oldMessages.filter(
|
|
1948
|
-
(message) => message.metadata?.kind !== "phase_prompt"
|
|
1949
|
-
);
|
|
1950
|
-
if (summarizableMessages.length < minCompact) {
|
|
1951
|
-
return { compacted: false, messages };
|
|
1952
|
-
}
|
|
1953
|
-
const recentMessages = messages.slice(recentStart);
|
|
1954
|
-
const summary = buildSummary(summarizableMessages);
|
|
1955
|
-
const result = [];
|
|
1956
|
-
if (firstUserIdx >= 0) {
|
|
1957
|
-
result.push(messages[firstUserIdx]);
|
|
1958
|
-
}
|
|
1959
|
-
result.push(
|
|
1960
|
-
createMessage("assistant", `[Context compaction summary]
|
|
1961
|
-
|
|
1962
|
-
${summary}`, {
|
|
1963
|
-
type: "compaction_summary",
|
|
1964
|
-
compactedCount: summarizableMessages.length
|
|
1965
|
-
})
|
|
1966
|
-
);
|
|
1967
|
-
result.push(...protectedPhaseMessages);
|
|
1968
|
-
result.push(...recentMessages);
|
|
1969
|
-
return {
|
|
1970
|
-
compacted: true,
|
|
1971
|
-
messages: result,
|
|
1972
|
-
summarizedCount: summarizableMessages.length,
|
|
1973
|
-
summary
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
|
|
1977
2086
|
// src/loop/runners.ts
|
|
1978
2087
|
function resolvePhaseOutput(result) {
|
|
1979
|
-
return result
|
|
2088
|
+
return result ?? { message: "Phase completed.", route: "stop" };
|
|
1980
2089
|
}
|
|
1981
2090
|
function removePhaseMessage(messages, msgId) {
|
|
1982
2091
|
if (!msgId) return;
|
|
@@ -2092,12 +2201,18 @@ async function completeRun(config, state, outcome) {
|
|
|
2092
2201
|
return createRunResult(config, state, outcome);
|
|
2093
2202
|
}
|
|
2094
2203
|
function createTerminalOutcome(phase, output, transcript) {
|
|
2095
|
-
|
|
2204
|
+
const outcome = phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
|
|
2205
|
+
return phase.name === "compact" ? { ...outcome, display: false } : outcome;
|
|
2206
|
+
}
|
|
2207
|
+
function shouldRunStopPhase(phase, output) {
|
|
2208
|
+
if (phase.run || phase.factory) return false;
|
|
2209
|
+
return typeof output.message !== "string" || output.message.trim().length === 0;
|
|
2096
2210
|
}
|
|
2097
2211
|
function buildToolsWithRouting(config, availablePhases) {
|
|
2098
2212
|
const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
|
|
2099
|
-
|
|
2100
|
-
|
|
2213
|
+
const autoInvocablePhases = availablePhases.filter((phase) => phase.disableAutoInvocation !== true);
|
|
2214
|
+
if (autoInvocablePhases.length > 0) {
|
|
2215
|
+
tools.push(createRouteTool(autoInvocablePhases));
|
|
2101
2216
|
}
|
|
2102
2217
|
return tools;
|
|
2103
2218
|
}
|
|
@@ -2310,7 +2425,15 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2310
2425
|
while (currentPhaseId) {
|
|
2311
2426
|
const availablePhases = [];
|
|
2312
2427
|
for (const [, phase2] of registry.phases) {
|
|
2313
|
-
availablePhases.push({
|
|
2428
|
+
availablePhases.push({
|
|
2429
|
+
name: phase2.name,
|
|
2430
|
+
description: phase2.description,
|
|
2431
|
+
tools: phase2.tools,
|
|
2432
|
+
skills: phase2.skills,
|
|
2433
|
+
input: phase2.input,
|
|
2434
|
+
isolated: phase2.isolated,
|
|
2435
|
+
disableAutoInvocation: phase2.disableAutoInvocation
|
|
2436
|
+
});
|
|
2314
2437
|
}
|
|
2315
2438
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
2316
2439
|
if (abortResult.stopReason !== "none") {
|
|
@@ -2334,13 +2457,6 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2334
2457
|
}
|
|
2335
2458
|
state.attempt++;
|
|
2336
2459
|
state.metrics.iterations++;
|
|
2337
|
-
if (needsCompaction(config.context.messages)) {
|
|
2338
|
-
const compacted = compactMessages(config.context.messages);
|
|
2339
|
-
if (compacted.compacted) {
|
|
2340
|
-
config.context.messages = compacted.messages;
|
|
2341
|
-
state.metrics.compactionCount++;
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
2460
|
const phase = registry.phases.get(currentPhaseId);
|
|
2345
2461
|
if (!phase) {
|
|
2346
2462
|
throw new Error(`Phase "${currentPhaseId}" not found`);
|
|
@@ -2465,10 +2581,20 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2465
2581
|
}
|
|
2466
2582
|
}
|
|
2467
2583
|
}
|
|
2584
|
+
if (output.status) {
|
|
2585
|
+
await execution.reportStatus(output.status);
|
|
2586
|
+
}
|
|
2587
|
+
if (phase.name === STOP_PHASE_ID) {
|
|
2588
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2589
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2590
|
+
previousPhaseMsgId = void 0;
|
|
2591
|
+
previousPhaseInputMsgId = void 0;
|
|
2592
|
+
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2593
|
+
}
|
|
2468
2594
|
if (phase.run || phase.factory) {
|
|
2469
2595
|
if (output.route === void 0 && !routeDecision) output = { ...output, route: "stop" };
|
|
2470
2596
|
} else if (!phase.target && output.route === void 0 && !routeDecision) {
|
|
2471
|
-
const input = await awaitInput(output.message
|
|
2597
|
+
const input = await awaitInput(output.message ?? `${output.phase ?? "Phase"} completed.`, true);
|
|
2472
2598
|
if (input?.type === "completed") return input.result;
|
|
2473
2599
|
if (input?.type === "continued") {
|
|
2474
2600
|
isContinuing = true;
|
|
@@ -2519,11 +2645,16 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2519
2645
|
pendingInstruction = void 0;
|
|
2520
2646
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
2521
2647
|
if (entryPhaseId === "stop") {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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 = void 0;
|
|
2657
|
+
continue;
|
|
2527
2658
|
}
|
|
2528
2659
|
currentPhaseId = entryPhaseId;
|
|
2529
2660
|
previousPayload = void 0;
|
|
@@ -2538,11 +2669,18 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
2538
2669
|
nextRoute = "stop";
|
|
2539
2670
|
}
|
|
2540
2671
|
if (nextRoute === "stop") {
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2672
|
+
if (!shouldRunStopPhase(phase, output)) {
|
|
2673
|
+
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
2674
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
2675
|
+
previousPhaseMsgId = void 0;
|
|
2676
|
+
previousPhaseInputMsgId = void 0;
|
|
2677
|
+
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
2678
|
+
}
|
|
2679
|
+
currentPhaseId = STOP_PHASE_ID;
|
|
2680
|
+
previousPayload = output.payload;
|
|
2681
|
+
previousResults = output.payload !== void 0 ? [{ name: phase.name, output: output.payload }] : [];
|
|
2682
|
+
pendingInstruction = phase.target ? void 0 : routeDecision?.instruction;
|
|
2683
|
+
continue;
|
|
2546
2684
|
}
|
|
2547
2685
|
const targetPhaseId = nextRoute;
|
|
2548
2686
|
if (!registry.phases.has(targetPhaseId)) {
|
|
@@ -2606,7 +2744,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2606
2744
|
}
|
|
2607
2745
|
const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
|
|
2608
2746
|
options.onRetry?.(attempt, error, delayMs);
|
|
2609
|
-
await new Promise((
|
|
2747
|
+
await new Promise((resolve9) => setTimeout(resolve9, delayMs));
|
|
2610
2748
|
if (options.signal?.aborted) {
|
|
2611
2749
|
throw lastError;
|
|
2612
2750
|
}
|
|
@@ -2730,7 +2868,7 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2730
2868
|
state.currentPhase = snapshot.currentPhase;
|
|
2731
2869
|
state.metrics.iterations = snapshot.turnNumber;
|
|
2732
2870
|
},
|
|
2733
|
-
async invokeModel(phaseContext) {
|
|
2871
|
+
async invokeModel(phaseContext, options = {}) {
|
|
2734
2872
|
if (config.beforePrompt) {
|
|
2735
2873
|
phaseContext = await config.beforePrompt(phase.name, phaseContext);
|
|
2736
2874
|
}
|
|
@@ -2760,7 +2898,9 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2760
2898
|
config,
|
|
2761
2899
|
message: messageManager,
|
|
2762
2900
|
request,
|
|
2763
|
-
phaseId: phase.name
|
|
2901
|
+
phaseId: phase.name,
|
|
2902
|
+
output: options.output,
|
|
2903
|
+
onThinkingDelta: config.onThinkingDelta
|
|
2764
2904
|
}),
|
|
2765
2905
|
{
|
|
2766
2906
|
signal: config.signal,
|
|
@@ -2773,6 +2913,9 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2773
2913
|
await config.onModelTranscript?.(result.transcript, { phase: phase.name, model: phase.model ?? config.model });
|
|
2774
2914
|
return result;
|
|
2775
2915
|
},
|
|
2916
|
+
async reportStatus(status) {
|
|
2917
|
+
await config.onPhaseStatus?.(phase.name, status);
|
|
2918
|
+
},
|
|
2776
2919
|
async executeTool(phaseContext, toolCall) {
|
|
2777
2920
|
return runTurn(async () => {
|
|
2778
2921
|
await toolExecutionManager.start(toolCall.id, toolCall.name, toolCall.args);
|
|
@@ -2862,12 +3005,13 @@ async function executeParallelPhase(config, state, registry, phase, payload, ins
|
|
|
2862
3005
|
if (phaseMsgId) removePhaseMessage(messages, phaseMsgId);
|
|
2863
3006
|
const decision = output.toolCalls ? extractRouteCall(output.toolCalls) : void 0;
|
|
2864
3007
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2865
|
-
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
3008
|
+
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message ?? `${phase.name} completed.` };
|
|
2866
3009
|
}
|
|
2867
3010
|
function selectPhaseTools(candidates, names, includeRouting = true) {
|
|
2868
3011
|
const routing = includeRouting ? candidates.filter(({ name }) => name === PhaseRouteTool) : [];
|
|
2869
|
-
const
|
|
2870
|
-
|
|
3012
|
+
const core = candidates.filter(({ core: core2 }) => core2);
|
|
3013
|
+
const ordinary = candidates.filter(({ name, core: core2 }) => name !== PhaseRouteTool && !core2);
|
|
3014
|
+
return [...core, ...selectNamedResources(ordinary, names, "Tool"), ...routing];
|
|
2871
3015
|
}
|
|
2872
3016
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2873
3017
|
const entries = Array.from(backgroundTasks.entries());
|
|
@@ -3020,8 +3164,10 @@ async function executeOnce(input) {
|
|
|
3020
3164
|
beforePrompt: input.beforePrompt,
|
|
3021
3165
|
onMessage: input.onMessage,
|
|
3022
3166
|
onMessageDelta: input.onMessageDelta,
|
|
3167
|
+
onThinkingDelta: input.onThinkingDelta,
|
|
3023
3168
|
onOutcome: input.onOutcome,
|
|
3024
3169
|
onModelTranscript: input.onModelTranscript,
|
|
3170
|
+
onPhaseStatus: input.onPhaseStatus,
|
|
3025
3171
|
runtime: input.runtime,
|
|
3026
3172
|
waitForInput: async (nextState, request) => {
|
|
3027
3173
|
if (!nextState || !request) throw new Error("Input boundary did not include execution state.");
|
|
@@ -3762,6 +3908,7 @@ function projectToolContent(content) {
|
|
|
3762
3908
|
function projectTool(tool, agentId, runId) {
|
|
3763
3909
|
return {
|
|
3764
3910
|
name: tool.name,
|
|
3911
|
+
...tool.core ? { core: true } : {},
|
|
3765
3912
|
description: tool.description,
|
|
3766
3913
|
parameters: tool.parameters,
|
|
3767
3914
|
execute: async (args, context, signal) => {
|
|
@@ -3780,9 +3927,17 @@ function jsonText(value) {
|
|
|
3780
3927
|
}
|
|
3781
3928
|
|
|
3782
3929
|
// src/runtime/extensions.ts
|
|
3783
|
-
function assembleRegisteredExtensions(config, runner) {
|
|
3930
|
+
function assembleRegisteredExtensions(config, runner, options = {}) {
|
|
3784
3931
|
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3785
|
-
const
|
|
3932
|
+
const coreTools = createRuntimeCoreTools({
|
|
3933
|
+
root: config.cwd,
|
|
3934
|
+
...options.toolArchiveDir ? { archiveDir: options.toolArchiveDir } : {}
|
|
3935
|
+
});
|
|
3936
|
+
const coreNames = new Set(coreTools.map((tool) => tool.name));
|
|
3937
|
+
const tools = [
|
|
3938
|
+
...config.resources.tools.filter((tool) => !coreNames.has(tool.name)),
|
|
3939
|
+
...coreTools
|
|
3940
|
+
];
|
|
3786
3941
|
const names = new Set(tools.map((tool) => tool.name));
|
|
3787
3942
|
for (const tool of extensionTools) {
|
|
3788
3943
|
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
@@ -3813,11 +3968,13 @@ function assembleRegisteredExtensions(config, runner) {
|
|
|
3813
3968
|
};
|
|
3814
3969
|
}
|
|
3815
3970
|
function resolveDefinitionContext(config, assembled = {}) {
|
|
3816
|
-
const
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3971
|
+
const candidateTools = assembled.tools ?? config.resources.tools;
|
|
3972
|
+
const coreTools = candidateTools.filter((tool) => tool.core);
|
|
3973
|
+
const authoredTools = candidateTools.filter((tool) => !tool.core);
|
|
3974
|
+
const tools = [
|
|
3975
|
+
...selectNamedResources(authoredTools, config.definition.tools, "Tool"),
|
|
3976
|
+
...coreTools
|
|
3977
|
+
];
|
|
3821
3978
|
const skills = mergeSkills(
|
|
3822
3979
|
selectNamedResources(config.resources.skills, config.definition.skills, "Skill"),
|
|
3823
3980
|
config.definition.bundledSkills
|
|
@@ -3828,11 +3985,16 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3828
3985
|
"Context"
|
|
3829
3986
|
);
|
|
3830
3987
|
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3831
|
-
const
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3988
|
+
const phaseCandidates = [...candidateRegistry?.phases.values() ?? []];
|
|
3989
|
+
const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
|
|
3990
|
+
const selectedPhases = [
|
|
3991
|
+
...phaseCandidates.filter((phase) => phase.core || coreNames.has(phase.name)),
|
|
3992
|
+
...selectNamedResources(
|
|
3993
|
+
phaseCandidates.filter((phase) => !phase.core && !coreNames.has(phase.name)),
|
|
3994
|
+
config.definition.phases?.phaseIds,
|
|
3995
|
+
"Phase"
|
|
3996
|
+
)
|
|
3997
|
+
];
|
|
3836
3998
|
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3837
3999
|
const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
|
|
3838
4000
|
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
@@ -3847,15 +4009,23 @@ function resolveDefinitionContext(config, assembled = {}) {
|
|
|
3847
4009
|
};
|
|
3848
4010
|
}
|
|
3849
4011
|
function mergePhases(base, extension) {
|
|
3850
|
-
|
|
3851
|
-
const
|
|
4012
|
+
const core = createCorePhases();
|
|
4013
|
+
const coreNames = new Set(core.map(({ name }) => name));
|
|
4014
|
+
const phases = new Map(core.map((phase) => [phase.name, phase]));
|
|
4015
|
+
for (const [name, phase] of base?.phases ?? []) {
|
|
4016
|
+
if (coreNames.has(name)) {
|
|
4017
|
+
if (!phase.core) throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
|
|
4018
|
+
continue;
|
|
4019
|
+
}
|
|
4020
|
+
phases.set(name, phase);
|
|
4021
|
+
}
|
|
3852
4022
|
for (const [name, phase] of extension.phases) {
|
|
3853
4023
|
if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
|
|
3854
4024
|
phases.set(name, phase);
|
|
3855
4025
|
}
|
|
3856
4026
|
return {
|
|
3857
4027
|
phases,
|
|
3858
|
-
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
4028
|
+
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
3859
4029
|
};
|
|
3860
4030
|
}
|
|
3861
4031
|
function adaptExtensionTool(input) {
|
|
@@ -3888,11 +4058,11 @@ function fromLoopResult(result) {
|
|
|
3888
4058
|
var MAX_BUFFERED_EVENTS = 128;
|
|
3889
4059
|
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3890
4060
|
function deferred() {
|
|
3891
|
-
let
|
|
4061
|
+
let resolve9;
|
|
3892
4062
|
const promise = new Promise((done) => {
|
|
3893
|
-
|
|
4063
|
+
resolve9 = done;
|
|
3894
4064
|
});
|
|
3895
|
-
return { promise, resolve:
|
|
4065
|
+
return { promise, resolve: resolve9 };
|
|
3896
4066
|
}
|
|
3897
4067
|
var TransientRunEventSubscription = class {
|
|
3898
4068
|
queue = [];
|
|
@@ -3907,6 +4077,11 @@ var TransientRunEventSubscription = class {
|
|
|
3907
4077
|
...previous,
|
|
3908
4078
|
text: previous.text + event.text
|
|
3909
4079
|
};
|
|
4080
|
+
} else if (previous?.kind === "thinking_delta" && event.kind === "thinking_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.blockIndex === event.blockIndex && previous.offset + previous.text.length === event.offset) {
|
|
4081
|
+
this.queue[this.queue.length - 1] = {
|
|
4082
|
+
...previous,
|
|
4083
|
+
text: previous.text + event.text
|
|
4084
|
+
};
|
|
3910
4085
|
} else {
|
|
3911
4086
|
if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
|
|
3912
4087
|
this.queue.push(event);
|
|
@@ -3917,11 +4092,14 @@ var TransientRunEventSubscription = class {
|
|
|
3917
4092
|
shift() {
|
|
3918
4093
|
return this.queue.shift();
|
|
3919
4094
|
}
|
|
3920
|
-
clear(executionId) {
|
|
4095
|
+
clear(executionId, preserveCompletedPhaseStatus = false) {
|
|
3921
4096
|
if (executionId === void 0) this.queue.length = 0;
|
|
3922
4097
|
else {
|
|
3923
4098
|
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3924
|
-
|
|
4099
|
+
const event = this.queue[index];
|
|
4100
|
+
if (event?.executionId === executionId && !(preserveCompletedPhaseStatus && event.kind === "phase_status" && event.status.state === "completed")) {
|
|
4101
|
+
this.queue.splice(index, 1);
|
|
4102
|
+
}
|
|
3925
4103
|
}
|
|
3926
4104
|
}
|
|
3927
4105
|
this.notify();
|
|
@@ -3929,7 +4107,7 @@ var TransientRunEventSubscription = class {
|
|
|
3929
4107
|
clearMessage(messageId) {
|
|
3930
4108
|
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3931
4109
|
const event = this.queue[index];
|
|
3932
|
-
if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
|
|
4110
|
+
if ((event?.kind === "message_delta" || event?.kind === "thinking_delta") && event.messageId === messageId) this.queue.splice(index, 1);
|
|
3933
4111
|
}
|
|
3934
4112
|
this.notify();
|
|
3935
4113
|
}
|
|
@@ -3961,11 +4139,11 @@ var TransientRunEventSubscription = class {
|
|
|
3961
4139
|
}
|
|
3962
4140
|
trimText() {
|
|
3963
4141
|
let buffered = this.queue.reduce(
|
|
3964
|
-
(length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
|
|
4142
|
+
(length, event) => length + (event.kind === "message_delta" || event.kind === "thinking_delta" ? event.text.length : 0),
|
|
3965
4143
|
0
|
|
3966
4144
|
);
|
|
3967
4145
|
while (buffered > MAX_BUFFERED_TEXT) {
|
|
3968
|
-
const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
|
|
4146
|
+
const index = this.queue.findIndex((event2) => event2.kind === "message_delta" || event2.kind === "thinking_delta");
|
|
3969
4147
|
if (index < 0) return;
|
|
3970
4148
|
const event = this.queue[index];
|
|
3971
4149
|
const overflow = buffered - MAX_BUFFERED_TEXT;
|
|
@@ -4003,9 +4181,9 @@ var TransientRunEventHub = class {
|
|
|
4003
4181
|
subscription.push(event);
|
|
4004
4182
|
}
|
|
4005
4183
|
}
|
|
4006
|
-
clear(runId, executionId) {
|
|
4184
|
+
clear(runId, executionId, preserveCompletedPhaseStatus = false) {
|
|
4007
4185
|
for (const subscription of this.subscriptions.get(runId) ?? []) {
|
|
4008
|
-
subscription.clear(executionId);
|
|
4186
|
+
subscription.clear(executionId, preserveCompletedPhaseStatus);
|
|
4009
4187
|
}
|
|
4010
4188
|
}
|
|
4011
4189
|
clearMessage(runId, messageId) {
|
|
@@ -4153,7 +4331,7 @@ function createEventBus() {
|
|
|
4153
4331
|
|
|
4154
4332
|
// src/extensions/runner.ts
|
|
4155
4333
|
async function execCommand(command, args, cwd, options) {
|
|
4156
|
-
return new Promise((
|
|
4334
|
+
return new Promise((resolve9, reject) => {
|
|
4157
4335
|
const child = execFile(
|
|
4158
4336
|
command,
|
|
4159
4337
|
args,
|
|
@@ -4168,7 +4346,7 @@ async function execCommand(command, args, cwd, options) {
|
|
|
4168
4346
|
reject(new Error("Command was aborted"));
|
|
4169
4347
|
return;
|
|
4170
4348
|
}
|
|
4171
|
-
|
|
4349
|
+
resolve9({
|
|
4172
4350
|
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
4173
4351
|
stdout: stdout ?? "",
|
|
4174
4352
|
stderr: stderr ?? ""
|
|
@@ -5026,6 +5204,8 @@ function snapshotResourceValue(kind, value) {
|
|
|
5026
5204
|
const phase = value;
|
|
5027
5205
|
const snapshot = {
|
|
5028
5206
|
...phase,
|
|
5207
|
+
disableAutoInvocation: phase.disableAutoInvocation ?? false,
|
|
5208
|
+
disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
|
|
5029
5209
|
...phase.tools ? { tools: Object.freeze([...phase.tools]) } : {},
|
|
5030
5210
|
skills: Object.freeze((phase.skills ?? []).map((skill) => Object.freeze({ ...skill }))),
|
|
5031
5211
|
...phase.input ? { input: Object.freeze({ ...phase.input }) } : {},
|
|
@@ -5051,7 +5231,7 @@ function capitalize(value) {
|
|
|
5051
5231
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
5052
5232
|
}
|
|
5053
5233
|
function isImplicitCoreName(kind, name) {
|
|
5054
|
-
return kind === "tool" && name === "route" || kind === "phase" && name === "default";
|
|
5234
|
+
return kind === "tool" && name === "route" || kind === "phase" && (name === "default" || name === "stop" || name === "compact");
|
|
5055
5235
|
}
|
|
5056
5236
|
async function loadDirectoryValues(kind, directory, sourceId) {
|
|
5057
5237
|
const root = resolve7(directory);
|
|
@@ -5235,7 +5415,7 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
|
|
|
5235
5415
|
/** Install Rowan-owned implicit resources before any host source is read. */
|
|
5236
5416
|
async ensureCoreResources() {
|
|
5237
5417
|
await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
|
|
5238
|
-
await this.replaceImplicit("phase", "rowan.core",
|
|
5418
|
+
await this.replaceImplicit("phase", "rowan.core", createCorePhases());
|
|
5239
5419
|
}
|
|
5240
5420
|
async loadExtensions(input) {
|
|
5241
5421
|
const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
|
|
@@ -5379,7 +5559,10 @@ function intersectPhaseSelection(parent, layer) {
|
|
|
5379
5559
|
};
|
|
5380
5560
|
}
|
|
5381
5561
|
function resolvePhases(candidates, selection) {
|
|
5382
|
-
const
|
|
5562
|
+
const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
|
|
5563
|
+
const core = candidates.filter((phase) => phase.core || coreNames.has(phase.name));
|
|
5564
|
+
const authored = candidates.filter((phase) => !coreNames.has(phase.name) && !phase.core);
|
|
5565
|
+
const selected = [...core, ...selectNamedResources(authored, selection?.phaseIds, "Phase")];
|
|
5383
5566
|
const entryPhaseId = selection?.entryPhaseId ?? null;
|
|
5384
5567
|
if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
|
|
5385
5568
|
console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
|
|
@@ -5401,6 +5584,31 @@ var DEFAULT_POLL_MS = 25;
|
|
|
5401
5584
|
var MAX_CONSUMER_IDLE_POLL_MS = 250;
|
|
5402
5585
|
var OWNER_LEASE_MS = 3e4;
|
|
5403
5586
|
var OWNER_RENEWAL_MS = 1e4;
|
|
5587
|
+
var MAX_INLINE_TOOL_RESULT_BYTES = 16 * 1024;
|
|
5588
|
+
async function spillLargeToolResult(result, archiveDir, toolName) {
|
|
5589
|
+
if (!archiveDir) return result;
|
|
5590
|
+
const serialized = typeof result.content === "string" ? result.content : JSON.stringify(result.content, null, 2);
|
|
5591
|
+
if (serialized === void 0) return result;
|
|
5592
|
+
const bytes = new TextEncoder().encode(serialized);
|
|
5593
|
+
if (bytes.byteLength <= MAX_INLINE_TOOL_RESULT_BYTES || /\nFull result:\s+\S+/.test(serialized)) {
|
|
5594
|
+
return result;
|
|
5595
|
+
}
|
|
5596
|
+
const toolDir = archiveDir;
|
|
5597
|
+
await mkdir2(toolDir, { recursive: true, mode: 448 });
|
|
5598
|
+
await chmod2(toolDir, 448).catch(() => void 0);
|
|
5599
|
+
const safeToolName = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
5600
|
+
const path = join5(toolDir, `rowan-${safeToolName}-${Date.now()}-${randomUUID()}.log`);
|
|
5601
|
+
await writeFile2(path, serialized, { encoding: "utf8", mode: 384 });
|
|
5602
|
+
await chmod2(path, 384).catch(() => void 0);
|
|
5603
|
+
const preview = new TextDecoder().decode(bytes.subarray(0, MAX_INLINE_TOOL_RESULT_BYTES));
|
|
5604
|
+
return {
|
|
5605
|
+
...result,
|
|
5606
|
+
content: `${preview}
|
|
5607
|
+
[truncated]
|
|
5608
|
+
Full result: ${path}
|
|
5609
|
+
Offset: 0`
|
|
5610
|
+
};
|
|
5611
|
+
}
|
|
5404
5612
|
var AgentRuntime = class _AgentRuntime {
|
|
5405
5613
|
concurrency;
|
|
5406
5614
|
owned;
|
|
@@ -5411,6 +5619,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5411
5619
|
executionDone = /* @__PURE__ */ new Map();
|
|
5412
5620
|
cancellationRequested = /* @__PURE__ */ new Set();
|
|
5413
5621
|
cancellationReasons = /* @__PURE__ */ new Map();
|
|
5622
|
+
autoCompactionRuns = /* @__PURE__ */ new Set();
|
|
5414
5623
|
consumers = /* @__PURE__ */ new Map();
|
|
5415
5624
|
transientEvents = new TransientRunEventHub();
|
|
5416
5625
|
resources;
|
|
@@ -5503,6 +5712,36 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5503
5712
|
this.assertOpen();
|
|
5504
5713
|
return this.owned.compact(input);
|
|
5505
5714
|
}
|
|
5715
|
+
async contextStatus(agentId, options = {}) {
|
|
5716
|
+
this.assertOpen();
|
|
5717
|
+
const agent = await this.requireAgent(agentId);
|
|
5718
|
+
const contextWindow = options.contextWindow ?? await this.resolveContextWindow(agent);
|
|
5719
|
+
return this.owned.contextStatus(agentId, contextWindow);
|
|
5720
|
+
}
|
|
5721
|
+
async compactContext(agentId, options = {}) {
|
|
5722
|
+
this.assertOpen();
|
|
5723
|
+
const agent = await this.requireAgent(agentId);
|
|
5724
|
+
if (!agent.activatedAt || !agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
|
|
5725
|
+
const active = (await this.owned.listRuns({ agentId })).filter((run2) => ["queued", "running", "input_required"].includes(run2.state));
|
|
5726
|
+
const existing = active.find((run2) => controlRunKind(run2) === "compact");
|
|
5727
|
+
if (existing) return new DurableRun(this, existing.id);
|
|
5728
|
+
const blocking = active.find((run2) => run2.state === "queued" || run2.state === "running");
|
|
5729
|
+
if (blocking) throw new RuntimeError("context_busy", { agentId, runId: blocking.id, actual: blocking.state });
|
|
5730
|
+
const metadata = {
|
|
5731
|
+
rowan: {
|
|
5732
|
+
kind: "compact",
|
|
5733
|
+
...options.instructions === void 0 ? {} : { instructions: options.instructions }
|
|
5734
|
+
}
|
|
5735
|
+
};
|
|
5736
|
+
const run = await this.owned.createRun({
|
|
5737
|
+
agentId,
|
|
5738
|
+
input: "",
|
|
5739
|
+
metadata,
|
|
5740
|
+
idempotencyKey: options.idempotencyKey ?? createId("compact")
|
|
5741
|
+
});
|
|
5742
|
+
void this.pump();
|
|
5743
|
+
return new DurableRun(this, run.id);
|
|
5744
|
+
}
|
|
5506
5745
|
async start(agentId, input, options) {
|
|
5507
5746
|
this.assertOpen();
|
|
5508
5747
|
const agent = await this.requireAgent(agentId);
|
|
@@ -5522,6 +5761,47 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5522
5761
|
this.assertOpen();
|
|
5523
5762
|
return pageRuns(await this.owned.listRuns(input), { ...input, storeIncarnation: this.storeIncarnation });
|
|
5524
5763
|
}
|
|
5764
|
+
async listInvocations(agentId, options) {
|
|
5765
|
+
this.assertOpen();
|
|
5766
|
+
if (!options || !["auto", "implicit", "external"].includes(options.source)) {
|
|
5767
|
+
throw new TypeError("options.source must be auto, implicit, or external");
|
|
5768
|
+
}
|
|
5769
|
+
const agent = await this.requireAgent(agentId);
|
|
5770
|
+
if (!agent.currentConfigToken) throw new RuntimeError("agent_not_found", { agentId });
|
|
5771
|
+
const resolution = await this.commands.resolve({ agent, token: agent.currentConfigToken });
|
|
5772
|
+
if (resolution.kind !== "available") {
|
|
5773
|
+
throw new RuntimeError("configuration_unavailable", {
|
|
5774
|
+
agentId,
|
|
5775
|
+
retryable: resolution.kind === "deferred",
|
|
5776
|
+
reason: resolution.kind === "deferred" ? "Configuration is deferred." : resolution.reason
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
5779
|
+
const config = isAgentConfiguration(resolution.config) ? this.materializeConfig(resolution.config) : resolution.config;
|
|
5780
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
|
|
5781
|
+
toolArchiveDir: this.archiveDirFor(agentId)
|
|
5782
|
+
});
|
|
5783
|
+
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);
|
|
5784
|
+
const skills = assembly.context.skills.filter((skill) => options.source === "external" || (options.source === "auto" ? !(skill.disableAutoInvocation ?? skill.disableModelInvocation) : !skill.disableImplicitInvocation));
|
|
5785
|
+
return [
|
|
5786
|
+
...phases.map((phase) => ({
|
|
5787
|
+
kind: "phase",
|
|
5788
|
+
name: phase.name,
|
|
5789
|
+
description: phase.description,
|
|
5790
|
+
...phase.core ? { core: true } : {},
|
|
5791
|
+
disableAutoInvocation: phase.disableAutoInvocation ?? false,
|
|
5792
|
+
disableImplicitInvocation: phase.disableImplicitInvocation ?? false,
|
|
5793
|
+
filePath: phase.filePath
|
|
5794
|
+
})),
|
|
5795
|
+
...skills.map((skill) => ({
|
|
5796
|
+
kind: "skill",
|
|
5797
|
+
name: skill.name,
|
|
5798
|
+
description: skill.description,
|
|
5799
|
+
disableAutoInvocation: skill.disableAutoInvocation ?? skill.disableModelInvocation ?? false,
|
|
5800
|
+
disableImplicitInvocation: skill.disableImplicitInvocation ?? false,
|
|
5801
|
+
filePath: skill.filePath
|
|
5802
|
+
}))
|
|
5803
|
+
];
|
|
5804
|
+
}
|
|
5525
5805
|
async consume(input) {
|
|
5526
5806
|
this.assertOpen();
|
|
5527
5807
|
if (input.consumerId.trim().length === 0) throw new TypeError("consumerId must be non-empty");
|
|
@@ -5601,16 +5881,19 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5601
5881
|
try {
|
|
5602
5882
|
while (!this.closed && this.executions.size < this.concurrency) {
|
|
5603
5883
|
const queued = await this.owned.listRuns({ states: ["queued"] });
|
|
5604
|
-
const next = queued.
|
|
5884
|
+
const next = queued.filter((run) => !this.activeAgents.has(run.agentId)).sort((left, right) => Number(controlRunKind(right) === "compact") - Number(controlRunKind(left) === "compact"))[0];
|
|
5605
5885
|
if (!next) return;
|
|
5606
5886
|
this.activeAgents.add(next.agentId);
|
|
5607
5887
|
const task = this.execute(next).finally(() => {
|
|
5608
5888
|
const execution = this.executions.get(next.id);
|
|
5609
|
-
if (execution) this.transientEvents.clear(next.id, execution.executionId);
|
|
5889
|
+
if (execution) this.transientEvents.clear(next.id, execution.executionId, true);
|
|
5610
5890
|
this.activeAgents.delete(next.agentId);
|
|
5611
5891
|
this.executions.delete(next.id);
|
|
5612
5892
|
this.cancellationRequested.delete(next.id);
|
|
5613
5893
|
this.cancellationReasons.delete(next.id);
|
|
5894
|
+
void this.owned.snapshotRun(next.id).then((snapshot) => {
|
|
5895
|
+
if (snapshot.state !== "queued") this.autoCompactionRuns.delete(next.id);
|
|
5896
|
+
}).catch(() => void 0);
|
|
5614
5897
|
this.executionDone.delete(next.id);
|
|
5615
5898
|
void this.pump();
|
|
5616
5899
|
});
|
|
@@ -5667,34 +5950,81 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5667
5950
|
return;
|
|
5668
5951
|
}
|
|
5669
5952
|
}
|
|
5953
|
+
const config = resolvedConfig;
|
|
5954
|
+
const controlKind = controlRunKind(run);
|
|
5955
|
+
if (!controlKind && !this.autoCompactionRuns.has(run.id)) {
|
|
5956
|
+
const contextWindow = await resolveContextWindowForConfig(config);
|
|
5957
|
+
const status = await this.owned.contextStatus(run.agentId, contextWindow);
|
|
5958
|
+
const inputTokens = estimateRunInputTokens(run.input);
|
|
5959
|
+
if (status.thresholdTokens > 0 && status.tokens + inputTokens >= status.thresholdTokens) {
|
|
5960
|
+
this.autoCompactionRuns.add(run.id);
|
|
5961
|
+
await this.owned.createRun({
|
|
5962
|
+
agentId: run.agentId,
|
|
5963
|
+
input: "",
|
|
5964
|
+
metadata: {
|
|
5965
|
+
rowan: {
|
|
5966
|
+
kind: "compact",
|
|
5967
|
+
trigger: "auto",
|
|
5968
|
+
sourceRunId: String(run.id)
|
|
5969
|
+
}
|
|
5970
|
+
},
|
|
5971
|
+
idempotencyKey: `auto-compact:${run.id}`
|
|
5972
|
+
});
|
|
5973
|
+
return;
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5670
5976
|
const executionId = createId("exec");
|
|
5671
5977
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
5672
5978
|
executionRevision = claim.run.revision;
|
|
5673
5979
|
const controller = new AbortController();
|
|
5674
5980
|
this.executions.set(run.id, { controller, executionId });
|
|
5675
5981
|
if (this.cancellationRequested.has(run.id)) controller.abort();
|
|
5676
|
-
const
|
|
5677
|
-
|
|
5982
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
|
|
5983
|
+
toolArchiveDir: this.archiveDirFor(run.agentId)
|
|
5984
|
+
});
|
|
5678
5985
|
let toolQueue = Promise.resolve();
|
|
5679
5986
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
5680
5987
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
5681
|
-
const
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5988
|
+
const phaseRegistry = normalizePhaseRegistry(assembly.context.phases);
|
|
5989
|
+
let modelMessages = await this.owned.contextMessages(run.agentId);
|
|
5990
|
+
const buildExecutionContext = (messages, phaseId) => {
|
|
5991
|
+
const executionContext2 = projectModelContext({
|
|
5992
|
+
context: {
|
|
5993
|
+
...assembly.context,
|
|
5994
|
+
phases: phaseRegistry
|
|
5995
|
+
},
|
|
5996
|
+
messages,
|
|
5997
|
+
agentId: run.agentId,
|
|
5998
|
+
runId: run.id
|
|
5999
|
+
});
|
|
6000
|
+
if (phaseId === COMPACT_PHASE_ID || controlKind === "compact") {
|
|
6001
|
+
executionContext2.phases = {
|
|
6002
|
+
...executionContext2.phases,
|
|
6003
|
+
entryPhaseId: COMPACT_PHASE_ID
|
|
6004
|
+
};
|
|
6005
|
+
const instructions = controlKind === "compact" ? compactInstructions(run) : void 0;
|
|
6006
|
+
if (instructions) {
|
|
6007
|
+
executionContext2.messages.push({
|
|
6008
|
+
id: createId("msg"),
|
|
6009
|
+
role: "user",
|
|
6010
|
+
content: `Additional compaction instructions:
|
|
6011
|
+
${instructions}`,
|
|
6012
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6013
|
+
metadata: { kind: "phase_input", phase: COMPACT_PHASE_ID }
|
|
6014
|
+
});
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
return executionContext2;
|
|
6018
|
+
};
|
|
6019
|
+
let executionContext = buildExecutionContext(modelMessages);
|
|
5690
6020
|
const executionTools = {
|
|
5691
6021
|
tools: assembly.context.tools,
|
|
5692
6022
|
beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
|
|
5693
6023
|
afterToolCall: assembly.afterToolCall ?? config.afterToolCall
|
|
5694
6024
|
};
|
|
5695
|
-
const
|
|
5696
|
-
canonicalMessages:
|
|
5697
|
-
context
|
|
6025
|
+
const executeModel = (context) => executeOnce({
|
|
6026
|
+
canonicalMessages: context.messages,
|
|
6027
|
+
context,
|
|
5698
6028
|
execution: {
|
|
5699
6029
|
agentId: run.agentId,
|
|
5700
6030
|
runId: run.id,
|
|
@@ -5703,7 +6033,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5703
6033
|
...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
|
|
5704
6034
|
},
|
|
5705
6035
|
model,
|
|
5706
|
-
thinkingLevel: thinkingLevelFromMessages(
|
|
6036
|
+
thinkingLevel: thinkingLevelFromMessages(context.messages),
|
|
5707
6037
|
stream,
|
|
5708
6038
|
maxAttempts: config.maxAttempts,
|
|
5709
6039
|
checkpoint: claim.run.checkpoint,
|
|
@@ -5712,6 +6042,18 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5712
6042
|
beforePhase: assembly.beforePhase,
|
|
5713
6043
|
afterPhase: assembly.afterPhase,
|
|
5714
6044
|
beforePrompt: assembly.beforePrompt,
|
|
6045
|
+
onPhaseStatus: (phaseId, status) => {
|
|
6046
|
+
const active = this.executions.get(run.id);
|
|
6047
|
+
if (active?.executionId !== claim.execution.executionId) return;
|
|
6048
|
+
this.transientEvents.publish({
|
|
6049
|
+
kind: "phase_status",
|
|
6050
|
+
durability: "transient",
|
|
6051
|
+
runId: run.id,
|
|
6052
|
+
executionId: claim.execution.executionId,
|
|
6053
|
+
phaseId,
|
|
6054
|
+
status
|
|
6055
|
+
});
|
|
6056
|
+
},
|
|
5715
6057
|
onMessageDelta: (event) => {
|
|
5716
6058
|
const active = this.executions.get(run.id);
|
|
5717
6059
|
if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
|
|
@@ -5725,6 +6067,20 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5725
6067
|
text: event.text
|
|
5726
6068
|
});
|
|
5727
6069
|
},
|
|
6070
|
+
onThinkingDelta: (event) => {
|
|
6071
|
+
const active = this.executions.get(run.id);
|
|
6072
|
+
if (this.closed || controller.signal.aborted || active?.executionId !== claim.execution.executionId) return;
|
|
6073
|
+
this.transientEvents.publish({
|
|
6074
|
+
kind: "thinking_delta",
|
|
6075
|
+
durability: "transient",
|
|
6076
|
+
runId: run.id,
|
|
6077
|
+
executionId: claim.execution.executionId,
|
|
6078
|
+
messageId: event.messageId,
|
|
6079
|
+
blockIndex: event.blockIndex,
|
|
6080
|
+
offset: event.offset,
|
|
6081
|
+
text: event.text
|
|
6082
|
+
});
|
|
6083
|
+
},
|
|
5728
6084
|
onContext: assembly.setContext,
|
|
5729
6085
|
runtime: {
|
|
5730
6086
|
tools: ({ toolCall }) => {
|
|
@@ -5763,8 +6119,27 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5763
6119
|
}
|
|
5764
6120
|
}
|
|
5765
6121
|
});
|
|
6122
|
+
let result = await executeModel(executionContext);
|
|
6123
|
+
if (!controlKind && result.type === "failed" && isContextOverflowError(result.error)) {
|
|
6124
|
+
const compactContext = buildExecutionContext(await this.owned.contextMessages(run.agentId), COMPACT_PHASE_ID);
|
|
6125
|
+
const compactResult = await executeModel(compactContext);
|
|
6126
|
+
const summary = compactResult.type === "completed" ? compactSummary(compactResult.outcome.payload) : void 0;
|
|
6127
|
+
if (summary) {
|
|
6128
|
+
const covered = claim.history.at(-1);
|
|
6129
|
+
await this.owned.commitContextCompaction({
|
|
6130
|
+
id: createId("compact"),
|
|
6131
|
+
agentId: run.agentId,
|
|
6132
|
+
summary,
|
|
6133
|
+
...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
|
|
6134
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6135
|
+
});
|
|
6136
|
+
modelMessages = await this.owned.contextMessages(run.agentId);
|
|
6137
|
+
executionContext = buildExecutionContext(modelMessages);
|
|
6138
|
+
result = await executeModel(executionContext);
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
5766
6141
|
if (this.cancellationRequested.has(run.id) || controller.signal.aborted) {
|
|
5767
|
-
const output = latestAssistant(run, result.messages,
|
|
6142
|
+
const output = latestAssistant(run, result.messages, modelMessages.length, true);
|
|
5768
6143
|
const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
|
|
5769
6144
|
await this.owned.cancelRun({
|
|
5770
6145
|
runId: run.id,
|
|
@@ -5776,7 +6151,12 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5776
6151
|
return;
|
|
5777
6152
|
}
|
|
5778
6153
|
if (result.type === "input_required") {
|
|
5779
|
-
const
|
|
6154
|
+
const output = latestAssistant(
|
|
6155
|
+
run,
|
|
6156
|
+
result.messages.slice(modelMessages.length),
|
|
6157
|
+
modelMessages.length
|
|
6158
|
+
);
|
|
6159
|
+
const prompt = output ?? promptMessage(run, result.request.prompt, result.messages.length);
|
|
5780
6160
|
await this.owned.commitInputRequired({
|
|
5781
6161
|
runId: run.id,
|
|
5782
6162
|
execution: claim.execution,
|
|
@@ -5792,9 +6172,24 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5792
6172
|
return;
|
|
5793
6173
|
}
|
|
5794
6174
|
if (result.type === "completed") {
|
|
5795
|
-
const output = latestAssistant(run, result.messages,
|
|
6175
|
+
const output = latestAssistant(run, result.messages.slice(modelMessages.length), modelMessages.length);
|
|
6176
|
+
if (controlKind === "compact" || isCompactionOutcome(result.outcome.payload)) {
|
|
6177
|
+
const summary = compactSummary(result.outcome.payload);
|
|
6178
|
+
if (summary) {
|
|
6179
|
+
const covered = claim.history.at(-1);
|
|
6180
|
+
const record = {
|
|
6181
|
+
id: createId("compact"),
|
|
6182
|
+
agentId: run.agentId,
|
|
6183
|
+
summary,
|
|
6184
|
+
...covered ? { coveredThrough: { messageId: covered.id, sequence: covered.sequenceWithinRun } } : {},
|
|
6185
|
+
...compactInstructions(run) ? { instructions: compactInstructions(run) } : {},
|
|
6186
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6187
|
+
};
|
|
6188
|
+
await this.owned.commitContextCompaction(record);
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
5796
6191
|
await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
|
|
5797
|
-
this.transientEvents.clear(run.id, claim.execution.executionId);
|
|
6192
|
+
this.transientEvents.clear(run.id, claim.execution.executionId, true);
|
|
5798
6193
|
return;
|
|
5799
6194
|
}
|
|
5800
6195
|
const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
|
|
@@ -5827,6 +6222,13 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5827
6222
|
if (!(error instanceof RuntimeError) || !["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) throw error;
|
|
5828
6223
|
});
|
|
5829
6224
|
}
|
|
6225
|
+
async resolveContextWindow(agent) {
|
|
6226
|
+
if (!agent.currentConfigToken) return 128e3;
|
|
6227
|
+
const resolution = await this.commands.resolve({ agent, token: agent.currentConfigToken });
|
|
6228
|
+
if (resolution.kind !== "available") return 128e3;
|
|
6229
|
+
const config = isAgentConfiguration(resolution.config) ? this.materializeConfig(resolution.config) : resolution.config;
|
|
6230
|
+
return resolveContextWindowForConfig(config);
|
|
6231
|
+
}
|
|
5830
6232
|
async executeTool(input) {
|
|
5831
6233
|
const batch = await this.executeToolBatch({ ...input, toolCalls: [input.toolCall] });
|
|
5832
6234
|
return { result: batch.results[0], revision: batch.revision };
|
|
@@ -5917,6 +6319,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5917
6319
|
assertToolExecutionResult(result);
|
|
5918
6320
|
if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
|
|
5919
6321
|
assertToolExecutionResult(result);
|
|
6322
|
+
result = await spillLargeToolResult(
|
|
6323
|
+
result,
|
|
6324
|
+
this.archiveDirFor(input.run.agentId),
|
|
6325
|
+
input.toolCall.name
|
|
6326
|
+
);
|
|
5920
6327
|
const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
|
|
5921
6328
|
return {
|
|
5922
6329
|
result: { toolCallId: providerToolCallId, toolName: input.toolCall.name, ...result },
|
|
@@ -5936,6 +6343,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5936
6343
|
if (!agent) throw new RuntimeError("agent_not_found", { agentId });
|
|
5937
6344
|
return agent;
|
|
5938
6345
|
}
|
|
6346
|
+
archiveDirFor(agentId) {
|
|
6347
|
+
const store = this.owned;
|
|
6348
|
+
const archiveDir = store.contextArchiveDir?.(agentId);
|
|
6349
|
+
return archiveDir ? join5(archiveDir, "tool-results") : void 0;
|
|
6350
|
+
}
|
|
5939
6351
|
async runConsumer(subscription) {
|
|
5940
6352
|
const registration = await this.owned.openConsumer(subscription.consumerId);
|
|
5941
6353
|
let cursor = registration.cursor;
|
|
@@ -5977,10 +6389,11 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5977
6389
|
const subscription = this.transientEvents.subscribe(runId);
|
|
5978
6390
|
try {
|
|
5979
6391
|
while (true) {
|
|
5980
|
-
if (options.signal?.aborted) throw abortError();
|
|
5981
6392
|
const observationVersion = subscription.checkpoint();
|
|
5982
6393
|
const snapshot = await this.owned.snapshotRun(runId);
|
|
5983
|
-
|
|
6394
|
+
const terminal = ["completed", "failed", "cancelled"].includes(snapshot.state);
|
|
6395
|
+
if (options.signal?.aborted && !terminal) throw abortError();
|
|
6396
|
+
if (terminal) {
|
|
5984
6397
|
const pending = await this.owned.listEvents(cursor ? { after: cursor } : {});
|
|
5985
6398
|
if (!pending.some((event) => event.runId === runId)) return;
|
|
5986
6399
|
}
|
|
@@ -5993,8 +6406,16 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
5993
6406
|
} else if (event.kind === "tool_state_changed" && ["completed", "failed", "indeterminate"].includes(event.transition.to)) {
|
|
5994
6407
|
subscription.clearTool(event.toolCall.id);
|
|
5995
6408
|
}
|
|
6409
|
+
if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) {
|
|
6410
|
+
while (true) {
|
|
6411
|
+
const transient2 = subscription.shift();
|
|
6412
|
+
if (!transient2) break;
|
|
6413
|
+
yield transient2;
|
|
6414
|
+
}
|
|
6415
|
+
yield event;
|
|
6416
|
+
return;
|
|
6417
|
+
}
|
|
5996
6418
|
yield event;
|
|
5997
|
-
if (event.kind === "run_state_changed" && ["completed", "failed", "cancelled"].includes(event.to)) return;
|
|
5998
6419
|
}
|
|
5999
6420
|
const transient = subscription.shift();
|
|
6000
6421
|
if (transient) {
|
|
@@ -6098,21 +6519,67 @@ function boundaryFromSnapshot(snapshot) {
|
|
|
6098
6519
|
}
|
|
6099
6520
|
}
|
|
6100
6521
|
function delay(ms) {
|
|
6101
|
-
return new Promise((
|
|
6522
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
6523
|
+
}
|
|
6524
|
+
function controlRunKind(run) {
|
|
6525
|
+
const rowan = run.metadata?.rowan;
|
|
6526
|
+
if (typeof rowan !== "object" || rowan === null || !("kind" in rowan)) return void 0;
|
|
6527
|
+
const kind = rowan.kind;
|
|
6528
|
+
return typeof kind === "string" ? kind : void 0;
|
|
6529
|
+
}
|
|
6530
|
+
function compactInstructions(run) {
|
|
6531
|
+
const rowan = run.metadata?.rowan;
|
|
6532
|
+
if (typeof rowan !== "object" || rowan === null || !("instructions" in rowan)) return void 0;
|
|
6533
|
+
const instructions = rowan.instructions;
|
|
6534
|
+
return typeof instructions === "string" && instructions.trim().length > 0 ? instructions : void 0;
|
|
6535
|
+
}
|
|
6536
|
+
function compactSummary(payload) {
|
|
6537
|
+
if (typeof payload !== "object" || payload === null || !("summary" in payload)) return void 0;
|
|
6538
|
+
const summary = payload.summary;
|
|
6539
|
+
return typeof summary === "string" && summary.trim().length > 0 ? summary : void 0;
|
|
6540
|
+
}
|
|
6541
|
+
function isCompactionOutcome(payload) {
|
|
6542
|
+
return typeof payload === "object" && payload !== null && "kind" in payload && payload.kind === "context_compaction";
|
|
6543
|
+
}
|
|
6544
|
+
async function resolveContextWindowForConfig(config) {
|
|
6545
|
+
if ("contextWindow" in config.model && typeof config.model.contextWindow === "number") {
|
|
6546
|
+
return config.model.contextWindow;
|
|
6547
|
+
}
|
|
6548
|
+
return 128e3;
|
|
6549
|
+
}
|
|
6550
|
+
function estimateRunInputTokens(input) {
|
|
6551
|
+
const content = typeof input === "string" ? input : input.content;
|
|
6552
|
+
const text = typeof content === "string" ? content : content.map((part) => part.type === "text" ? part.text : part.data).join("\n");
|
|
6553
|
+
return Math.ceil(text.length / 4) + 20;
|
|
6554
|
+
}
|
|
6555
|
+
function isContextOverflowError(error) {
|
|
6556
|
+
const message = error instanceof Error ? `${error.name} ${error.message}` : typeof error === "string" ? error : (() => {
|
|
6557
|
+
try {
|
|
6558
|
+
return JSON.stringify(error);
|
|
6559
|
+
} catch {
|
|
6560
|
+
return String(error);
|
|
6561
|
+
}
|
|
6562
|
+
})();
|
|
6563
|
+
return /context(?:\s|_|-)?(?:length|window|limit|overflow|exceed)|(?:maximum|max).{0,24}tokens|too many tokens|prompt.{0,24}(?:too large|exceed|limit)/i.test(message);
|
|
6102
6564
|
}
|
|
6103
6565
|
function normalizePhaseRegistry(registry) {
|
|
6104
|
-
|
|
6105
|
-
|
|
6566
|
+
const core = createCorePhases();
|
|
6567
|
+
const coreNames = new Set(core.map(({ name }) => name));
|
|
6568
|
+
for (const [name, phase] of registry?.phases ?? []) {
|
|
6569
|
+
if (coreNames.has(name) && !phase.core) {
|
|
6570
|
+
throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
|
|
6571
|
+
}
|
|
6106
6572
|
}
|
|
6107
6573
|
for (const [name] of registry?.phases ?? []) {
|
|
6108
|
-
if (name === "
|
|
6574
|
+
if (name === "continue") {
|
|
6109
6575
|
throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
|
|
6110
6576
|
}
|
|
6111
6577
|
}
|
|
6578
|
+
const authored = [...registry?.phases ?? []].filter(([name, phase]) => !coreNames.has(name) || phase.core === false);
|
|
6112
6579
|
return {
|
|
6113
6580
|
phases: new Map([
|
|
6114
|
-
[
|
|
6115
|
-
...
|
|
6581
|
+
...core.map((phase) => [phase.name, phase]),
|
|
6582
|
+
...authored
|
|
6116
6583
|
]),
|
|
6117
6584
|
entryPhaseId: registry?.entryPhaseId ?? DEFAULT_PHASE_ID
|
|
6118
6585
|
};
|
|
@@ -6130,13 +6597,13 @@ function toJsonValue(value) {
|
|
|
6130
6597
|
return value;
|
|
6131
6598
|
}
|
|
6132
6599
|
function deferred2() {
|
|
6133
|
-
let
|
|
6600
|
+
let resolve9;
|
|
6134
6601
|
let reject;
|
|
6135
6602
|
const promise = new Promise((res, rej) => {
|
|
6136
|
-
|
|
6603
|
+
resolve9 = (value) => res(value);
|
|
6137
6604
|
reject = rej;
|
|
6138
6605
|
});
|
|
6139
|
-
return { promise, resolve:
|
|
6606
|
+
return { promise, resolve: resolve9, reject };
|
|
6140
6607
|
}
|
|
6141
6608
|
|
|
6142
6609
|
// src/runtime/durable-store.ts
|
|
@@ -6158,6 +6625,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6158
6625
|
ownerEpoch = 0;
|
|
6159
6626
|
eventSequence = 0;
|
|
6160
6627
|
retentionFloor = 1;
|
|
6628
|
+
contextCompactions = /* @__PURE__ */ new Map();
|
|
6161
6629
|
constructor(options = {}) {
|
|
6162
6630
|
this.incarnation = options.incarnation ?? createId("store");
|
|
6163
6631
|
}
|
|
@@ -6176,6 +6644,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6176
6644
|
for (const [agentId, sequence] of state.nextReadySequence) store.nextReadySequence.set(agentId, sequence);
|
|
6177
6645
|
store.eventSequence = state.eventSequence;
|
|
6178
6646
|
store.retentionFloor = Math.max(1, state.retentionFloor ?? 1);
|
|
6647
|
+
for (const [agentId, record] of state.contextCompactions ?? []) store.contextCompactions.set(agentId, clone(record));
|
|
6179
6648
|
store.ownerEpoch = Math.max(
|
|
6180
6649
|
0,
|
|
6181
6650
|
...state.runs.map((run) => run.execution?.ownerEpoch ?? 0)
|
|
@@ -6197,7 +6666,8 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6197
6666
|
retentionFloor: this.retentionFloor,
|
|
6198
6667
|
nextAgentSequence: [...this.nextAgentSequence.entries()],
|
|
6199
6668
|
nextReadySequence: [...this.nextReadySequence.entries()],
|
|
6200
|
-
eventSequence: this.eventSequence
|
|
6669
|
+
eventSequence: this.eventSequence,
|
|
6670
|
+
contextCompactions: [...this.contextCompactions.entries()]
|
|
6201
6671
|
});
|
|
6202
6672
|
}
|
|
6203
6673
|
attachOwner(lease) {
|
|
@@ -6376,6 +6846,18 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6376
6846
|
actual: -1
|
|
6377
6847
|
});
|
|
6378
6848
|
}
|
|
6849
|
+
const coveredThrough = this.contextCompactions.get(agent.id)?.coveredThrough;
|
|
6850
|
+
if (coveredThrough) {
|
|
6851
|
+
const history = this.activeHistory(agent.id, Number.MAX_SAFE_INTEGER);
|
|
6852
|
+
const coveredIndex = history.findIndex((message) => message.id === coveredThrough.messageId);
|
|
6853
|
+
const originalIndex = history.findIndex((message) => message.id === original.id);
|
|
6854
|
+
if (coveredIndex >= 0 && originalIndex >= 0 && originalIndex <= coveredIndex) {
|
|
6855
|
+
throw new RuntimeError("message_history_compacted", {
|
|
6856
|
+
agentId: agent.id,
|
|
6857
|
+
messageId: original.id
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6379
6861
|
const actualRevision = original.messageRevision ?? 0;
|
|
6380
6862
|
if (actualRevision !== input.expectedMessageRevision) {
|
|
6381
6863
|
throw new RuntimeError("message_revision_conflict", {
|
|
@@ -6519,10 +7001,10 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6519
7001
|
const run = this.requireRun(input.runId);
|
|
6520
7002
|
this.assertRevision(run, input.expectedRevision);
|
|
6521
7003
|
this.assertState(run, ["queued"]);
|
|
6522
|
-
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && candidate.agentSequence < run.agentSequence && !["completed", "failed", "cancelled"].includes(candidate.state))) {
|
|
7004
|
+
if (!isControlRun(run) && [...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && candidate.agentSequence < run.agentSequence && !["completed", "failed", "cancelled"].includes(candidate.state))) {
|
|
6523
7005
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6524
7006
|
}
|
|
6525
|
-
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id &&
|
|
7007
|
+
if ([...this.runs.values()].some((candidate) => candidate.agentId === run.agentId && candidate.id !== run.id && (candidate.state === "running" || !isControlRun(run) && candidate.state === "input_required"))) {
|
|
6526
7008
|
throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["queued"], actual: run.state });
|
|
6527
7009
|
}
|
|
6528
7010
|
if (run.pinnedConfigToken && input.configToken && run.pinnedConfigToken !== input.configToken) {
|
|
@@ -6530,7 +7012,7 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
6530
7012
|
}
|
|
6531
7013
|
if (!run.pinnedConfigToken && input.configToken) run.pinnedConfigToken = input.configToken;
|
|
6532
7014
|
let message;
|
|
6533
|
-
if (!run.checkpoint && !run.initialMessageId) {
|
|
7015
|
+
if (!run.checkpoint && !run.initialMessageId && !isControlRun(run)) {
|
|
6534
7016
|
const userInput = normalizeUserInput(run.input);
|
|
6535
7017
|
message = {
|
|
6536
7018
|
id: input.messageId ?? createId("msg"),
|
|
@@ -7205,6 +7687,72 @@ var InMemoryStore = class _InMemoryStore {
|
|
|
7205
7687
|
this.requireAgent(agentId);
|
|
7206
7688
|
return this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
|
|
7207
7689
|
}
|
|
7690
|
+
contextStatus(lease, agentId, contextWindow) {
|
|
7691
|
+
this.assertOwner(lease);
|
|
7692
|
+
this.requireAgent(agentId);
|
|
7693
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
7694
|
+
throw new TypeError("contextWindow must be a positive finite number");
|
|
7695
|
+
}
|
|
7696
|
+
const messages = this.contextMessages(lease, agentId);
|
|
7697
|
+
const tokens = estimateMessageTokens(messages);
|
|
7698
|
+
const record = this.contextCompactions.get(agentId);
|
|
7699
|
+
const thresholdTokens = Math.max(0, Math.floor(contextWindow) - 16384);
|
|
7700
|
+
return {
|
|
7701
|
+
tokens,
|
|
7702
|
+
contextWindow: Math.floor(contextWindow),
|
|
7703
|
+
percent: Math.round(tokens / contextWindow * 100),
|
|
7704
|
+
thresholdTokens,
|
|
7705
|
+
estimated: true,
|
|
7706
|
+
...record?.coveredThrough ? { coveredThrough: clone(record.coveredThrough) } : {}
|
|
7707
|
+
};
|
|
7708
|
+
}
|
|
7709
|
+
contextMessages(lease, agentId, recentTokenBudget = 2e4) {
|
|
7710
|
+
this.assertOwner(lease);
|
|
7711
|
+
this.requireAgent(agentId);
|
|
7712
|
+
if (!Number.isFinite(recentTokenBudget) || recentTokenBudget <= 0) {
|
|
7713
|
+
throw new TypeError("recentTokenBudget must be a positive finite number");
|
|
7714
|
+
}
|
|
7715
|
+
const history = this.activeHistory(agentId, Number.MAX_SAFE_INTEGER);
|
|
7716
|
+
const record = this.contextCompactions.get(agentId);
|
|
7717
|
+
if (!record) return history;
|
|
7718
|
+
const coveredIndex = record.coveredThrough ? history.findIndex((message) => message.id === record.coveredThrough.messageId) : -1;
|
|
7719
|
+
const suffix = coveredIndex >= 0 ? history.slice(coveredIndex + 1) : history;
|
|
7720
|
+
const recent = [];
|
|
7721
|
+
let tokens = 0;
|
|
7722
|
+
for (let index = suffix.length - 1; index >= 0; index -= 1) {
|
|
7723
|
+
const message = suffix[index];
|
|
7724
|
+
const messageTokens = estimateMessageTokens([message]);
|
|
7725
|
+
if (recent.length > 0 && tokens + messageTokens > recentTokenBudget) break;
|
|
7726
|
+
recent.unshift(message);
|
|
7727
|
+
tokens += messageTokens;
|
|
7728
|
+
}
|
|
7729
|
+
const covered = record.coveredThrough ? history.find((message) => message.id === record.coveredThrough.messageId) : void 0;
|
|
7730
|
+
const summary = {
|
|
7731
|
+
id: `context-summary:${record.id}`,
|
|
7732
|
+
agentId,
|
|
7733
|
+
runId: covered?.runId ?? history.at(-1)?.runId ?? `context:${agentId}`,
|
|
7734
|
+
role: "assistant",
|
|
7735
|
+
content: `[Context summary]
|
|
7736
|
+
|
|
7737
|
+
${record.summary}`,
|
|
7738
|
+
sequenceWithinRun: covered?.sequenceWithinRun ?? 0,
|
|
7739
|
+
createdAt: record.createdAt,
|
|
7740
|
+
metadata: { kind: "context_summary", compactionId: record.id }
|
|
7741
|
+
};
|
|
7742
|
+
return [summary, ...recent];
|
|
7743
|
+
}
|
|
7744
|
+
commitContextCompaction(lease, record) {
|
|
7745
|
+
this.assertOwner(lease);
|
|
7746
|
+
this.requireAgent(record.agentId);
|
|
7747
|
+
if (record.id.trim().length === 0 || record.summary.trim().length === 0) {
|
|
7748
|
+
throw new TypeError("context compaction id and summary must be non-empty");
|
|
7749
|
+
}
|
|
7750
|
+
const existing = this.contextCompactions.get(record.agentId);
|
|
7751
|
+
if (existing?.id === record.id) return clone(existing);
|
|
7752
|
+
const next = clone(record);
|
|
7753
|
+
this.contextCompactions.set(record.agentId, next);
|
|
7754
|
+
return clone(next);
|
|
7755
|
+
}
|
|
7208
7756
|
activeHistory(agentId, beforeSequence) {
|
|
7209
7757
|
return clone([
|
|
7210
7758
|
...this.historySeeds.get(agentId) ?? [],
|
|
@@ -7287,6 +7835,15 @@ var MemoryOwnedStore = class {
|
|
|
7287
7835
|
async compact(input) {
|
|
7288
7836
|
return this.store.compact(this.lease, input);
|
|
7289
7837
|
}
|
|
7838
|
+
async contextStatus(agentId, contextWindow) {
|
|
7839
|
+
return this.store.contextStatus(this.lease, agentId, contextWindow);
|
|
7840
|
+
}
|
|
7841
|
+
async contextMessages(agentId, recentTokenBudget) {
|
|
7842
|
+
return this.store.contextMessages(this.lease, agentId, recentTokenBudget);
|
|
7843
|
+
}
|
|
7844
|
+
async commitContextCompaction(record) {
|
|
7845
|
+
return this.store.commitContextCompaction(this.lease, record);
|
|
7846
|
+
}
|
|
7290
7847
|
async createRun(input) {
|
|
7291
7848
|
return this.store.createRun(this.lease, input);
|
|
7292
7849
|
}
|
|
@@ -7376,6 +7933,29 @@ function userInputContent(input) {
|
|
|
7376
7933
|
function userInputMetadata(input) {
|
|
7377
7934
|
return typeof input === "string" ? void 0 : input.metadata;
|
|
7378
7935
|
}
|
|
7936
|
+
function isControlRun(run) {
|
|
7937
|
+
const rowan = run.metadata?.rowan;
|
|
7938
|
+
return typeof rowan === "object" && rowan !== null && "kind" in rowan && rowan.kind === "compact";
|
|
7939
|
+
}
|
|
7940
|
+
function estimateMessageTokens(messages) {
|
|
7941
|
+
let characters = 0;
|
|
7942
|
+
for (const message of messages) {
|
|
7943
|
+
characters += messageContentText2(message.content).length;
|
|
7944
|
+
characters += 20;
|
|
7945
|
+
}
|
|
7946
|
+
return Math.ceil(characters / 4);
|
|
7947
|
+
}
|
|
7948
|
+
function messageContentText2(content) {
|
|
7949
|
+
if (typeof content === "string") return content;
|
|
7950
|
+
return content.map((part) => {
|
|
7951
|
+
if (part.type === "text") return part.text;
|
|
7952
|
+
if (part.type === "thinking") return part.thinking;
|
|
7953
|
+
if (part.type === "tool_use") return JSON.stringify(part.input);
|
|
7954
|
+
if (part.type === "tool_result") return JSON.stringify(part.result);
|
|
7955
|
+
if (part.type === "image") return `[image:${part.mimeType}]`;
|
|
7956
|
+
return "";
|
|
7957
|
+
}).join("\n");
|
|
7958
|
+
}
|
|
7379
7959
|
function targetInput(input, metadata) {
|
|
7380
7960
|
const content = typeof input === "string" ? input : input.content;
|
|
7381
7961
|
return metadata === void 0 ? { content } : { content, metadata: clone(metadata) };
|
|
@@ -7417,6 +7997,9 @@ function assertToolValue(value, argument) {
|
|
|
7417
7997
|
|
|
7418
7998
|
// src/runtime/sqlite-durable-store.ts
|
|
7419
7999
|
import { Database } from "bun:sqlite";
|
|
8000
|
+
import { appendFileSync, chmodSync, mkdirSync, readFileSync as readFileSync2, rmSync } from "fs";
|
|
8001
|
+
import { dirname as dirname7, join as join6, resolve as resolve8 } from "path";
|
|
8002
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7420
8003
|
var SCHEMA_ID = "rowan.agent.runtime";
|
|
7421
8004
|
var SCHEMA_VERSION = "2";
|
|
7422
8005
|
var SCHEMA_VALUE = `${SCHEMA_ID}:${SCHEMA_VERSION}`;
|
|
@@ -7521,10 +8104,15 @@ var CURRENT_SCHEMA_SQL = `
|
|
|
7521
8104
|
`;
|
|
7522
8105
|
var SqliteStore = class {
|
|
7523
8106
|
database;
|
|
8107
|
+
archiveRoot;
|
|
8108
|
+
mirroredEventIds = /* @__PURE__ */ new Map();
|
|
8109
|
+
mirroredCompactionIds = /* @__PURE__ */ new Map();
|
|
8110
|
+
mirroredAgents = /* @__PURE__ */ new Set();
|
|
7524
8111
|
closed = false;
|
|
7525
8112
|
configured = false;
|
|
7526
8113
|
constructor(filename = ":memory:") {
|
|
7527
8114
|
this.database = new Database(filename, { create: true, readwrite: true, strict: true });
|
|
8115
|
+
this.archiveRoot = filename === ":memory:" ? join6(tmpdir2(), `rowan-context-archives-${crypto.randomUUID()}`) : join6(dirname7(resolve8(filename)), "context-archives");
|
|
7528
8116
|
}
|
|
7529
8117
|
async openOwner(input) {
|
|
7530
8118
|
this.assertOpen();
|
|
@@ -7576,6 +8164,10 @@ var SqliteStore = class {
|
|
|
7576
8164
|
this.closed = true;
|
|
7577
8165
|
this.database.close();
|
|
7578
8166
|
}
|
|
8167
|
+
/** Filesystem location owned by this Store for one Agent's readable archive. */
|
|
8168
|
+
contextArchiveDir(agentId) {
|
|
8169
|
+
return join6(this.archiveRoot, String(agentId));
|
|
8170
|
+
}
|
|
7579
8171
|
async reserveAgent(lease, input) {
|
|
7580
8172
|
return this.invoke(lease, (store, current) => store.reserveAgent(current, input));
|
|
7581
8173
|
}
|
|
@@ -7591,6 +8183,15 @@ var SqliteStore = class {
|
|
|
7591
8183
|
async reviseMessage(lease, input) {
|
|
7592
8184
|
return this.invoke(lease, (store, current) => store.reviseMessage(current, input));
|
|
7593
8185
|
}
|
|
8186
|
+
async contextStatus(lease, agentId, contextWindow) {
|
|
8187
|
+
return this.invoke(lease, (store, current) => store.contextStatus(current, agentId, contextWindow), false);
|
|
8188
|
+
}
|
|
8189
|
+
async contextMessages(lease, agentId, recentTokenBudget) {
|
|
8190
|
+
return this.invoke(lease, (store, current) => store.contextMessages(current, agentId, recentTokenBudget), false);
|
|
8191
|
+
}
|
|
8192
|
+
async commitContextCompaction(lease, record) {
|
|
8193
|
+
return this.invoke(lease, (store, current) => store.commitContextCompaction(current, record));
|
|
8194
|
+
}
|
|
7594
8195
|
async createRun(lease, input) {
|
|
7595
8196
|
return this.invoke(lease, (store, current) => store.createRun(current, input));
|
|
7596
8197
|
}
|
|
@@ -7838,6 +8439,64 @@ var SqliteStore = class {
|
|
|
7838
8439
|
[consumerId, Number(String(cursor).split(":").at(-1)), null, (/* @__PURE__ */ new Date()).toISOString()]
|
|
7839
8440
|
);
|
|
7840
8441
|
}
|
|
8442
|
+
this.mirrorState(state);
|
|
8443
|
+
}
|
|
8444
|
+
/** Append the semantic durable event mirror and remove deleted Agent sidecars. */
|
|
8445
|
+
mirrorState(state) {
|
|
8446
|
+
mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
|
|
8447
|
+
chmodSync(this.archiveRoot, 448);
|
|
8448
|
+
const currentAgents = new Set(state.agents.map((agent) => String(agent.id)));
|
|
8449
|
+
for (const agentId of this.mirroredAgents) {
|
|
8450
|
+
if (currentAgents.has(agentId)) continue;
|
|
8451
|
+
rmSync(join6(this.archiveRoot, agentId), { recursive: true, force: true });
|
|
8452
|
+
this.mirroredEventIds.delete(agentId);
|
|
8453
|
+
this.mirroredCompactionIds.delete(agentId);
|
|
8454
|
+
}
|
|
8455
|
+
for (const agent of state.agents) {
|
|
8456
|
+
const agentId = String(agent.id);
|
|
8457
|
+
const archiveDir = join6(this.archiveRoot, agentId);
|
|
8458
|
+
const toolDir = join6(archiveDir, "tool-results");
|
|
8459
|
+
mkdirSync(toolDir, { recursive: true, mode: 448 });
|
|
8460
|
+
chmodSync(archiveDir, 448);
|
|
8461
|
+
chmodSync(toolDir, 448);
|
|
8462
|
+
const sessionPath = join6(archiveDir, "session.jsonl");
|
|
8463
|
+
try {
|
|
8464
|
+
chmodSync(sessionPath, 384);
|
|
8465
|
+
} catch {
|
|
8466
|
+
}
|
|
8467
|
+
const known = this.mirroredEventIds.get(agentId) ?? /* @__PURE__ */ new Set();
|
|
8468
|
+
const knownCompactions = this.mirroredCompactionIds.get(agentId) ?? /* @__PURE__ */ new Set();
|
|
8469
|
+
if (known.size === 0 || knownCompactions.size === 0) {
|
|
8470
|
+
try {
|
|
8471
|
+
for (const line of readFileSync2(sessionPath, "utf8").split("\n")) {
|
|
8472
|
+
if (!line.trim()) continue;
|
|
8473
|
+
const parsed = JSON.parse(line);
|
|
8474
|
+
if (parsed.event?.id) known.add(parsed.event.id);
|
|
8475
|
+
if (parsed.record?.id) knownCompactions.add(parsed.record.id);
|
|
8476
|
+
}
|
|
8477
|
+
} catch {
|
|
8478
|
+
}
|
|
8479
|
+
}
|
|
8480
|
+
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");
|
|
8481
|
+
if (lines.length > 0) {
|
|
8482
|
+
appendFileSync(sessionPath, lines.join(""), { encoding: "utf8", mode: 384 });
|
|
8483
|
+
chmodSync(sessionPath, 384);
|
|
8484
|
+
for (const event of state.events) {
|
|
8485
|
+
if (String(event.agentId) === agentId) known.add(String(event.id));
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
8488
|
+
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");
|
|
8489
|
+
if (compactionLines.length > 0) {
|
|
8490
|
+
appendFileSync(sessionPath, compactionLines.join(""), { encoding: "utf8", mode: 384 });
|
|
8491
|
+
chmodSync(sessionPath, 384);
|
|
8492
|
+
for (const [, record] of state.contextCompactions ?? []) {
|
|
8493
|
+
if (String(record.agentId) === agentId) knownCompactions.add(record.id);
|
|
8494
|
+
}
|
|
8495
|
+
}
|
|
8496
|
+
this.mirroredEventIds.set(agentId, known);
|
|
8497
|
+
this.mirroredCompactionIds.set(agentId, knownCompactions);
|
|
8498
|
+
}
|
|
8499
|
+
this.mirroredAgents = currentAgents;
|
|
7841
8500
|
}
|
|
7842
8501
|
requireMatchingOwner(lease, requireLive) {
|
|
7843
8502
|
const row = this.readOwner();
|
|
@@ -7893,6 +8552,9 @@ var SqliteOwnedStore = class {
|
|
|
7893
8552
|
}
|
|
7894
8553
|
store;
|
|
7895
8554
|
lease;
|
|
8555
|
+
contextArchiveDir(agentId) {
|
|
8556
|
+
return this.store.contextArchiveDir(agentId);
|
|
8557
|
+
}
|
|
7896
8558
|
reserveAgent(input) {
|
|
7897
8559
|
return this.store.reserveAgent(this.lease, input);
|
|
7898
8560
|
}
|
|
@@ -7911,6 +8573,15 @@ var SqliteOwnedStore = class {
|
|
|
7911
8573
|
compact(input) {
|
|
7912
8574
|
return this.store.compact(this.lease, input);
|
|
7913
8575
|
}
|
|
8576
|
+
contextStatus(agentId, contextWindow) {
|
|
8577
|
+
return this.store.contextStatus(this.lease, agentId, contextWindow);
|
|
8578
|
+
}
|
|
8579
|
+
contextMessages(agentId, recentTokenBudget) {
|
|
8580
|
+
return Promise.resolve(this.store.contextMessages(this.lease, agentId, recentTokenBudget));
|
|
8581
|
+
}
|
|
8582
|
+
commitContextCompaction(record) {
|
|
8583
|
+
return this.store.commitContextCompaction(this.lease, record);
|
|
8584
|
+
}
|
|
7914
8585
|
createRun(input) {
|
|
7915
8586
|
return this.store.createRun(this.lease, input);
|
|
7916
8587
|
}
|
|
@@ -8016,18 +8687,12 @@ function assertOwnerInput(ownerId, leaseMs) {
|
|
|
8016
8687
|
|
|
8017
8688
|
// src/index.ts
|
|
8018
8689
|
function createCoreTools2(input = {}) {
|
|
8019
|
-
return
|
|
8020
|
-
name: tool.name,
|
|
8021
|
-
description: tool.description,
|
|
8022
|
-
parameters: tool.parameters,
|
|
8023
|
-
execute: async (args, context, signal) => {
|
|
8024
|
-
const result = await tool.execute(args, { skills: [], toolCallId: context.toolCallId }, signal);
|
|
8025
|
-
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Tool failed." };
|
|
8026
|
-
}
|
|
8027
|
-
}));
|
|
8690
|
+
return createRuntimeCoreTools(input);
|
|
8028
8691
|
}
|
|
8029
8692
|
export {
|
|
8030
8693
|
AgentRuntime,
|
|
8694
|
+
COMPACT_PHASE_ID,
|
|
8695
|
+
DEFAULT_PHASE_ID,
|
|
8031
8696
|
ExtensionLifetimeError,
|
|
8032
8697
|
InMemoryConfigProvider,
|
|
8033
8698
|
InMemoryStore,
|
|
@@ -8038,9 +8703,14 @@ export {
|
|
|
8038
8703
|
RuntimeBootstrapRegistry,
|
|
8039
8704
|
RuntimeError,
|
|
8040
8705
|
RuntimeExtensionLifetime,
|
|
8706
|
+
STOP_PHASE_ID,
|
|
8041
8707
|
SqliteStore,
|
|
8042
8708
|
brandConfigToken,
|
|
8709
|
+
createCompactPhase,
|
|
8710
|
+
createCorePhases,
|
|
8043
8711
|
createCoreTools2 as createCoreTools,
|
|
8712
|
+
createDefaultPhase,
|
|
8713
|
+
createStopPhase,
|
|
8044
8714
|
isRuntimeError,
|
|
8045
8715
|
loadExtensionsFromPath as loadExtensions,
|
|
8046
8716
|
loadPhase,
|