@rowan-agent/agent 0.9.8 → 0.9.9

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