@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +150 -6
  2. package/dist/index.js +906 -236
  3. 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
- ...availablePhases.map(buildPhaseEntry),
176
+ ...routablePhases.map(buildPhaseEntry),
175
177
  {
176
178
  name: "stop",
177
- description: "Explicitly finish the current user request or task when no further user input is needed"
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` means the current user request or task is complete and no further user input is needed; `stop` must be the only target.",
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
- ...availablePhases.map((p) => Type.Literal(p.name)),
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 an explicit stop.",
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 and no further user input is needed."
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 = 64e3;
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
- const remainingBytes = maxBytes - totalBytes;
453
- if (remainingBytes > 0) {
454
- const kept = value.subarray(0, remainingBytes);
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 selected = readTextLines(source, parsed.offset, parsed.limit);
520
- const selectedBytes = new TextEncoder().encode(selected);
521
- const sliced = selectedBytes.subarray(0, maxBytes);
522
- const text = new TextDecoder().decode(sliced);
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(text);
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 content = selectedBytes.byteLength > maxBytes ? `${formatted}
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 content = stdout.truncated || stderr.truncated ? `${output}
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
- activeMessageId ??= input.message.reserve("assistant", {
882
- phase: input.metadataPhase
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
- activeMessageId ??= input.message.reserve("assistant", {
896
- phase: input.metadataPhase
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.disableModelInvocation !== "boolean") {
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
- disableModelInvocation: metadata["disable-model-invocation"] === true
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
- description: "Executes concrete task operations and produces artifacts.",
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: "Execute tasks using current context.\nNo planning. No evaluation.\nOmit route when more user input is needed. Use route for immediate phase execution, or route(stop) only when the current user request or task is complete and no further user input is needed."
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 ? { ...result } : { message: "Phase completed.", route: "stop" };
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
- return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
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
- if (availablePhases.length > 0) {
2100
- tools.push(createRouteTool(availablePhases));
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({ name: phase2.name, description: phase2.description, tools: phase2.tools, skills: phase2.skills, input: phase2.input, isolated: phase2.isolated });
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, true);
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
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2523
- removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2524
- previousPhaseMsgId = void 0;
2525
- previousPhaseInputMsgId = void 0;
2526
- return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
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
- removePhaseMessage(config.context.messages, previousPhaseMsgId);
2542
- removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
2543
- previousPhaseMsgId = void 0;
2544
- previousPhaseInputMsgId = void 0;
2545
- return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
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((resolve8) => setTimeout(resolve8, delayMs));
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 ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
2870
- return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
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 tools = [...config.resources.tools];
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 tools = selectNamedResources(
3817
- assembled.tools ?? config.resources.tools,
3818
- config.definition.tools,
3819
- "Tool"
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 selectedPhases = selectNamedResources(
3832
- [...candidateRegistry?.phases.values() ?? []],
3833
- config.definition.phases?.phaseIds,
3834
- "Phase"
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
- if (!base && extension.phases.size === 0) return void 0;
3851
- const phases = new Map(base?.phases ?? []);
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 resolve8;
4061
+ let resolve9;
3892
4062
  const promise = new Promise((done) => {
3893
- resolve8 = done;
4063
+ resolve9 = done;
3894
4064
  });
3895
- return { promise, resolve: resolve8 };
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
- if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
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((resolve8, reject) => {
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
- resolve8({
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", [createDefaultPhase()]);
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 selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
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.find((run) => !this.activeAgents.has(run.agentId));
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 config = resolvedConfig;
5677
- const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
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 executionContext = projectModelContext({
5682
- context: {
5683
- ...assembly.context,
5684
- phases: normalizePhaseRegistry(assembly.context.phases)
5685
- },
5686
- messages: claim.history,
5687
- agentId: run.agentId,
5688
- runId: run.id
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 result = await executeOnce({
5696
- canonicalMessages: executionContext.messages,
5697
- context: executionContext,
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(executionContext.messages),
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, claim.history.length, true);
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 prompt = promptMessage(run, result.request.prompt, result.messages.length);
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, claim.history.length);
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
- if (["completed", "failed", "cancelled"].includes(snapshot.state)) {
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((resolve8) => setTimeout(resolve8, ms));
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
- if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
6105
- throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
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 === "stop" || name === "continue") {
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
- [DEFAULT_PHASE_ID, createDefaultPhase()],
6115
- ...registry?.phases ?? []
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 resolve8;
6600
+ let resolve9;
6134
6601
  let reject;
6135
6602
  const promise = new Promise((res, rej) => {
6136
- resolve8 = (value) => res(value);
6603
+ resolve9 = (value) => res(value);
6137
6604
  reject = rej;
6138
6605
  });
6139
- return { promise, resolve: resolve8, reject };
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 && ["running", "input_required"].includes(candidate.state))) {
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 createCoreTools(input).map((tool) => ({
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,