@rowan-agent/agent 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -183,10 +183,12 @@ const agentId = await runtime.createAgent({
183
183
  Each `load*()` call replaces one source atomically; use `directory` or inline
184
184
  `values`. `resourceView` controls visibility, so same-name resources can live
185
185
  in isolated sources but collide when selected together. The Runtime supplies the
186
- core `read`/`bash`/`edit`/`write`/`route` Tools itself, and its `rowan.core`
187
- source — like an Extension's `rowan.extensions` — is implicit in every view, so
188
- a Definition can select a built-in Phase without naming that source. Core names
189
- cannot be claimed by another source.
186
+ core `read`/`bash`/`edit`/`write` Tools and built-in `default`/`stop`/`compact` Phases.
187
+ Host-defined resources (including Tools and Phases registered by Extensions) replace
188
+ built-ins by name, retaining the built-in status (replaced Core Tools remain always
189
+ available without explicit Definition selection; replaced Core Phases retain their
190
+ built-in role). Reserved control names (`route` for Tools, `continue` for Phases)
191
+ cannot be claimed by host sources.
190
192
 
191
193
  Extensions are Runtime-global. Load them only during `AgentRuntime.init()` via
192
194
  `bootstrap`; after initialization they are frozen until the Runtime closes.
package/dist/index.d.ts CHANGED
@@ -269,6 +269,10 @@ interface ToolDefinition {
269
269
  description: string;
270
270
  /** Parameter schema (JSON Schema) */
271
271
  parameters: Record<string, unknown>;
272
+ /** Optional: one-line snippet shown in the system prompt tool list */
273
+ promptSnippet?: string;
274
+ /** Optional: additional guidelines appended to system prompt */
275
+ promptGuidelines?: string[];
272
276
  /** Execute the tool */
273
277
  execute: (args: unknown, context: ToolInvocationContext, signal?: AbortSignal) => Promise<ToolExecutionResult$1>;
274
278
  /** Optional: per-tool execution mode override */
@@ -955,6 +959,8 @@ type Tool$1 = Readonly<{
955
959
  core?: boolean;
956
960
  description: string;
957
961
  parameters: Type.TSchema;
962
+ promptSnippet?: string;
963
+ promptGuidelines?: readonly string[];
958
964
  execute(args: JsonValue, context: ToolInvocationContext, signal: AbortSignal): Promise<ToolExecutionResult>;
959
965
  }>;
960
966
  type ContextCandidate = Readonly<{
package/dist/index.js CHANGED
@@ -738,8 +738,8 @@ function createReadTool(context) {
738
738
  [truncated]` : formatted;
739
739
  const archivePath = await spillResult(context, "read", fullFormatted);
740
740
  content += `
741
- Full result: ${archivePath}
742
- Offset: 0`;
741
+
742
+ [Full result: ${archivePath}, offset 0]`;
743
743
  if (lineTruncated) {
744
744
  content += `
745
745
  Showing lines ${requestedOffset}-${selectedLineEnd} of ${sourceLines.length}. Use offset=${selectedLineEnd + 1} to continue.`;
@@ -896,8 +896,8 @@ function createBashTool(context) {
896
896
  [truncated]` : output;
897
897
  const archivePath = await spillResult(context, "bash", output);
898
898
  content += `
899
- Full result: ${archivePath}
900
- Offset: 0`;
899
+
900
+ [Full result: ${archivePath}, offset 0]`;
901
901
  return toolResult({
902
902
  context: toolContext,
903
903
  toolName: "bash",
@@ -925,12 +925,15 @@ function createCoreTools(input = {}) {
925
925
  }
926
926
 
927
927
  // src/runtime/core-tools.ts
928
+ var CORE_TOOL_NAMES = /* @__PURE__ */ new Set(["read", "edit", "write", "bash"]);
928
929
  function createRuntimeCoreTools(input = {}) {
929
930
  return createCoreTools(input).map((tool) => ({
930
931
  name: tool.name,
931
932
  core: true,
932
933
  description: tool.description,
933
934
  parameters: tool.parameters,
935
+ ...tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {},
936
+ ...tool.promptGuidelines ? { promptGuidelines: tool.promptGuidelines } : {},
934
937
  execute: async (args, context, signal) => {
935
938
  const result = await tool.execute(args, { skills: [], toolCallId: context.toolCallId }, signal);
936
939
  return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Tool failed." };
@@ -2010,6 +2013,7 @@ async function loadPhaseCode(codePath) {
2010
2013
  var DEFAULT_PHASE_ID = "default";
2011
2014
  var STOP_PHASE_ID = "stop";
2012
2015
  var COMPACT_PHASE_ID = "compact";
2016
+ var CORE_PHASE_NAMES = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
2013
2017
  function createDefaultPhase() {
2014
2018
  return {
2015
2019
  name: DEFAULT_PHASE_ID,
@@ -2062,7 +2066,7 @@ function createCompactPhase() {
2062
2066
  kind: "compacting",
2063
2067
  message: "Compacting conversation context."
2064
2068
  });
2065
- const tools = context.tools.filter((tool) => tool.core && (tool.name === "read" || tool.name === "bash"));
2069
+ const tools = context.tools.filter((tool) => tool.name === "read" || tool.name === "bash");
2066
2070
  const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
2067
2071
  const instructions = compactInstructions(context);
2068
2072
  if (instructions) {
@@ -3800,7 +3804,7 @@ function resolveConfigurationSnapshot(registry, input) {
3800
3804
  const selectedContexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
3801
3805
  const additionalContexts = deduplicateContexts(input.additionalContexts ?? [], selectedContexts);
3802
3806
  const definition = applyDefinitionLayer(base, layer);
3803
- const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
3807
+ const tools = resolveTools(resolved.tools, definition.tools);
3804
3808
  const skills = mergeSkills(
3805
3809
  selectNamedResources(resolved.skills, definition.skills, "Skill"),
3806
3810
  definition.bundledSkills
@@ -3873,11 +3877,20 @@ function intersectPhaseSelection(parent, layer) {
3873
3877
  phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
3874
3878
  };
3875
3879
  }
3880
+ function resolveCoreResources(candidates, selection, coreNames, kind) {
3881
+ const core = candidates.filter((item) => item.core || coreNames.has(item.name));
3882
+ const authoredSelection = selection ? selection.filter((name) => !coreNames.has(name)) : void 0;
3883
+ const authored = candidates.filter((item) => !coreNames.has(item.name) && !item.core);
3884
+ return [
3885
+ ...core.map((item) => item.core ? item : { ...item, core: true }),
3886
+ ...selectNamedResources(authored, authoredSelection, kind)
3887
+ ];
3888
+ }
3889
+ function resolveTools(candidates, selection) {
3890
+ return resolveCoreResources(candidates, selection, CORE_TOOL_NAMES, "Tool");
3891
+ }
3876
3892
  function resolvePhases(candidates, selection) {
3877
- const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
3878
- const core = candidates.filter((phase) => phase.core || coreNames.has(phase.name));
3879
- const authored = candidates.filter((phase) => !coreNames.has(phase.name) && !phase.core);
3880
- const selected = [...core, ...selectNamedResources(authored, selection?.phaseIds, "Phase")];
3893
+ const selected = resolveCoreResources(candidates, selection?.phaseIds, CORE_PHASE_NAMES, "Phase");
3881
3894
  const entryPhaseId = selection?.entryPhaseId ?? null;
3882
3895
  if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
3883
3896
  console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
@@ -4547,6 +4560,8 @@ function projectTool(tool, agentId, runId) {
4547
4560
  ...tool.core ? { core: true } : {},
4548
4561
  description: tool.description,
4549
4562
  parameters: tool.parameters,
4563
+ ...tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {},
4564
+ ...tool.promptGuidelines ? { promptGuidelines: [...tool.promptGuidelines] } : {},
4550
4565
  execute: async (args, context, signal) => {
4551
4566
  const result = await tool.execute(args, {
4552
4567
  agentId,
@@ -4569,10 +4584,12 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4569
4584
  root: snapshot.cwd,
4570
4585
  ...options.toolArchiveDir ? { archiveDir: options.toolArchiveDir } : {}
4571
4586
  });
4572
- const coreNames = new Set(coreTools.map((tool) => tool.name));
4587
+ const replacingNames = new Set(
4588
+ snapshot.resources.tools.filter((tool) => tool.core).map((tool) => tool.name)
4589
+ );
4573
4590
  const tools = [
4574
- ...snapshot.resources.tools.filter((tool) => !coreNames.has(tool.name)),
4575
- ...coreTools
4591
+ ...snapshot.resources.tools,
4592
+ ...coreTools.filter((tool) => !replacingNames.has(tool.name))
4576
4593
  ];
4577
4594
  const context = resolveDefinitionContext(snapshot, tools);
4578
4595
  return {
@@ -4620,8 +4637,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4620
4637
  function resolveDefinitionContext(snapshot, assembled) {
4621
4638
  const coreTools = assembled.filter((tool) => tool.core);
4622
4639
  const authoredTools = assembled.filter((tool) => !tool.core);
4640
+ const authoredSelection = snapshot.definition.tools ? snapshot.definition.tools.filter((name) => !assembled.some((t) => t.core && t.name === name)) : void 0;
4623
4641
  const tools = [
4624
- ...selectNamedResources(authoredTools, snapshot.definition.tools, "Tool"),
4642
+ ...selectNamedResources(authoredTools, authoredSelection, "Tool"),
4625
4643
  ...coreTools
4626
4644
  ];
4627
4645
  const skills = mergeSkills(
@@ -4633,14 +4651,12 @@ function resolveDefinitionContext(snapshot, assembled) {
4633
4651
  ...snapshot.additionalContexts
4634
4652
  ];
4635
4653
  const phaseCandidates = [...snapshot.resources.phases?.phases.values() ?? []];
4636
- const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
4654
+ const corePhases = phaseCandidates.filter((phase) => phase.core);
4655
+ const authoredPhases = phaseCandidates.filter((phase) => !phase.core);
4656
+ const authoredPhaseIds = snapshot.definition.phases?.phaseIds ? snapshot.definition.phases.phaseIds.filter((name) => !corePhases.some((p) => p.name === name)) : void 0;
4637
4657
  const selectedPhases = [
4638
- ...phaseCandidates.filter((phase) => phase.core || coreNames.has(phase.name)),
4639
- ...selectNamedResources(
4640
- phaseCandidates.filter((phase) => !phase.core && !coreNames.has(phase.name)),
4641
- snapshot.definition.phases?.phaseIds,
4642
- "Phase"
4643
- )
4658
+ ...corePhases,
4659
+ ...selectNamedResources(authoredPhases, authoredPhaseIds, "Phase")
4644
4660
  ];
4645
4661
  const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
4646
4662
  const requestedEntry = snapshot.definition.phases ? snapshot.definition.phases.entryPhaseId : snapshot.resources.phases?.entryPhaseId ?? null;
@@ -6076,8 +6092,16 @@ var ResourceRegistry = class {
6076
6092
  }
6077
6093
  withImplicit(kind, sourceIds) {
6078
6094
  const combined = [...sourceIds];
6095
+ let hasCore = false;
6079
6096
  for (const sourceId of this.implicitSources.get(kind) ?? []) {
6080
- if (!combined.includes(sourceId)) combined.push(sourceId);
6097
+ if (sourceId === "rowan.core") {
6098
+ hasCore = true;
6099
+ } else if (!combined.includes(sourceId)) {
6100
+ combined.push(sourceId);
6101
+ }
6102
+ }
6103
+ if (hasCore && !combined.includes("rowan.core")) {
6104
+ combined.push("rowan.core");
6081
6105
  }
6082
6106
  return combined;
6083
6107
  }
@@ -6108,6 +6132,12 @@ var ResourceRegistry = class {
6108
6132
  `${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
6109
6133
  );
6110
6134
  }
6135
+ if (kind === "phase" && value.name === "continue") {
6136
+ throw new ResourceRegistryError(
6137
+ "resource_collision",
6138
+ `Phase resource "continue" is reserved by Rowan routing controls.`
6139
+ );
6140
+ }
6111
6141
  if (names.has(value.name)) {
6112
6142
  throw new ResourceRegistryError(
6113
6143
  "resource_collision",
@@ -6142,6 +6172,12 @@ var ResourceRegistry = class {
6142
6172
  for (const source of this.sourcesFor(kind, sourceIds)) {
6143
6173
  for (const value of source.values) {
6144
6174
  if (names.has(value.name)) {
6175
+ if (source.sourceId === "rowan.core") {
6176
+ continue;
6177
+ }
6178
+ if (source.sourceId === "rowan.extensions" && isCoreResourceName(kind, value.name)) {
6179
+ continue;
6180
+ }
6145
6181
  throw new ResourceRegistryError(
6146
6182
  "resource_collision",
6147
6183
  `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
@@ -6161,7 +6197,15 @@ var ResourceRegistry = class {
6161
6197
  const refs = [];
6162
6198
  for (const source of this.sourcesFor(kind, sourceIds)) {
6163
6199
  for (const value of source.values) {
6164
- if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
6200
+ if (seenNames.has(value.name)) {
6201
+ if (source.sourceId === "rowan.core") {
6202
+ continue;
6203
+ }
6204
+ if (source.sourceId === "rowan.extensions" && isCoreResourceName(kind, value.name)) {
6205
+ continue;
6206
+ }
6207
+ throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
6208
+ }
6165
6209
  seenNames.add(value.name);
6166
6210
  refs.push({ kind, sourceId: source.sourceId, name: value.name });
6167
6211
  }
@@ -6231,7 +6275,10 @@ function capitalize(value) {
6231
6275
  return value.charAt(0).toUpperCase() + value.slice(1);
6232
6276
  }
6233
6277
  function isImplicitCoreName(kind, name) {
6234
- return kind === "tool" && name === "route" || kind === "phase" && (name === "default" || name === "stop" || name === "compact");
6278
+ return kind === "tool" && name === "route";
6279
+ }
6280
+ function isCoreResourceName(kind, name) {
6281
+ return kind === "tool" && CORE_TOOL_NAMES.has(name) || kind === "phase" && CORE_PHASE_NAMES.has(name);
6235
6282
  }
6236
6283
  async function loadDirectoryValues(kind, directory, sourceId) {
6237
6284
  const root = resolve7(directory);
@@ -6466,6 +6513,8 @@ function adaptExtensionTool(input) {
6466
6513
  name: definition.name,
6467
6514
  description: definition.description,
6468
6515
  parameters: definition.parameters,
6516
+ ...definition.promptSnippet ? { promptSnippet: definition.promptSnippet } : {},
6517
+ ...definition.promptGuidelines ? { promptGuidelines: definition.promptGuidelines } : {},
6469
6518
  execute: async (args, context, signal) => {
6470
6519
  const result = await definition.execute(args, context, signal);
6471
6520
  const content = JSON.parse(JSON.stringify(result.content));
@@ -6527,7 +6576,7 @@ async function spillLargeToolResult(result, archiveDir, toolName) {
6527
6576
  const serialized = typeof result.content === "string" ? result.content : JSON.stringify(result.content, null, 2);
6528
6577
  if (serialized === void 0) return result;
6529
6578
  const bytes = new TextEncoder().encode(serialized);
6530
- if (bytes.byteLength <= MAX_INLINE_TOOL_RESULT_BYTES || /\nFull result:\s+\S+/.test(serialized)) {
6579
+ if (bytes.byteLength <= MAX_INLINE_TOOL_RESULT_BYTES || /(?:\[Full result:\s+[^\]]+\]|\nFull result:\s+\S+)/.test(serialized)) {
6531
6580
  return result;
6532
6581
  }
6533
6582
  const toolDir = archiveDir;
@@ -6542,8 +6591,8 @@ async function spillLargeToolResult(result, archiveDir, toolName) {
6542
6591
  ...result,
6543
6592
  content: `${preview}
6544
6593
  [truncated]
6545
- Full result: ${path}
6546
- Offset: 0`
6594
+
6595
+ [Full result: ${path}, offset 0]`
6547
6596
  };
6548
6597
  }
6549
6598
  var AgentRuntime = class _AgentRuntime {
@@ -8059,23 +8108,29 @@ function isContextOverflowError(error) {
8059
8108
  }
8060
8109
  function normalizePhaseRegistry(registry) {
8061
8110
  const core = createCorePhases();
8062
- const coreNames = new Set(core.map(({ name }) => name));
8063
- for (const [name, phase] of registry?.phases ?? []) {
8064
- if (coreNames.has(name) && !phase.core) {
8065
- throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
8066
- }
8067
- }
8111
+ const coreNames = CORE_PHASE_NAMES;
8068
8112
  for (const [name] of registry?.phases ?? []) {
8069
8113
  if (name === "continue") {
8070
8114
  throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
8071
8115
  }
8072
8116
  }
8073
- const authored = [...registry?.phases ?? []].filter(([name, phase]) => !coreNames.has(name) || phase.core === false);
8117
+ const configuredPhases = registry?.phases ?? /* @__PURE__ */ new Map();
8118
+ const phases = /* @__PURE__ */ new Map();
8119
+ for (const corePhase of core) {
8120
+ const configured = configuredPhases.get(corePhase.name);
8121
+ if (configured) {
8122
+ phases.set(corePhase.name, configured.core ? configured : { ...configured, core: true });
8123
+ } else {
8124
+ phases.set(corePhase.name, corePhase);
8125
+ }
8126
+ }
8127
+ for (const [name, phase] of configuredPhases) {
8128
+ if (!coreNames.has(name)) {
8129
+ phases.set(name, phase);
8130
+ }
8131
+ }
8074
8132
  return {
8075
- phases: new Map([
8076
- ...core.map((phase) => [phase.name, phase]),
8077
- ...authored
8078
- ]),
8133
+ phases,
8079
8134
  entryPhaseId: registry?.entryPhaseId ?? DEFAULT_PHASE_ID
8080
8135
  };
8081
8136
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",