@rowan-agent/agent 0.15.5 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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." };
@@ -1247,6 +1250,56 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1247
1250
  assertActive();
1248
1251
  hooks?.off(eventType, handler);
1249
1252
  },
1253
+ config: {
1254
+ get: async (scope) => {
1255
+ assertActive();
1256
+ return await options?.config?.get(scope) ?? null;
1257
+ },
1258
+ changed: (handler) => {
1259
+ assertActive();
1260
+ options?.config?.changed(handler);
1261
+ }
1262
+ },
1263
+ state: {
1264
+ run: (runId) => {
1265
+ assertActive();
1266
+ const store = options?.state?.run(runId);
1267
+ return {
1268
+ get: async (key) => {
1269
+ assertActive();
1270
+ return store?.get(key);
1271
+ },
1272
+ set: async (key, value) => {
1273
+ assertActive();
1274
+ assertJsonValue(value);
1275
+ return store?.set(key, value);
1276
+ },
1277
+ delete: async (key) => {
1278
+ assertActive();
1279
+ return store?.delete(key);
1280
+ }
1281
+ };
1282
+ },
1283
+ agent: (agentId) => {
1284
+ assertActive();
1285
+ const store = options?.state?.agent(agentId);
1286
+ return {
1287
+ get: async (key) => {
1288
+ assertActive();
1289
+ return store?.get(key);
1290
+ },
1291
+ set: async (key, value) => {
1292
+ assertActive();
1293
+ assertJsonValue(value);
1294
+ return store?.set(key, value);
1295
+ },
1296
+ delete: async (key) => {
1297
+ assertActive();
1298
+ return store?.delete(key);
1299
+ }
1300
+ };
1301
+ }
1302
+ },
1250
1303
  tool: {
1251
1304
  register: (tool) => {
1252
1305
  assertActive();
@@ -1257,6 +1310,25 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1257
1310
  options?.unregisterTool?.(toolName);
1258
1311
  }
1259
1312
  },
1313
+ capabilities: {
1314
+ contribute: (contribution) => {
1315
+ assertActive();
1316
+ if (!contribution || typeof contribution !== "object") {
1317
+ throw new TypeError("Capability contribution must be an object.");
1318
+ }
1319
+ if (contribution.kind !== "tool") {
1320
+ throw new TypeError(`Unsupported capability contribution kind: "${contribution.kind}".`);
1321
+ }
1322
+ if (typeof contribution.name !== "string" || contribution.name.trim() === "") {
1323
+ throw new TypeError("Capability contribution requires a non-empty name.");
1324
+ }
1325
+ if (typeof contribution.description !== "string") {
1326
+ throw new TypeError("Capability contribution requires a description string.");
1327
+ }
1328
+ return options?.contributeCapability ? options.contributeCapability(contribution) : () => {
1329
+ };
1330
+ }
1331
+ },
1260
1332
  registerProvider: (config) => {
1261
1333
  assertActive();
1262
1334
  options?.registerProvider?.(config);
@@ -1941,6 +2013,7 @@ async function loadPhaseCode(codePath) {
1941
2013
  var DEFAULT_PHASE_ID = "default";
1942
2014
  var STOP_PHASE_ID = "stop";
1943
2015
  var COMPACT_PHASE_ID = "compact";
2016
+ var CORE_PHASE_NAMES = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
1944
2017
  function createDefaultPhase() {
1945
2018
  return {
1946
2019
  name: DEFAULT_PHASE_ID,
@@ -1993,7 +2066,7 @@ function createCompactPhase() {
1993
2066
  kind: "compacting",
1994
2067
  message: "Compacting conversation context."
1995
2068
  });
1996
- 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");
1997
2070
  const working = { ...context, tools, skills: [...context.skills], messages: [...context.messages] };
1998
2071
  const instructions = compactInstructions(context);
1999
2072
  if (instructions) {
@@ -3731,7 +3804,7 @@ function resolveConfigurationSnapshot(registry, input) {
3731
3804
  const selectedContexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
3732
3805
  const additionalContexts = deduplicateContexts(input.additionalContexts ?? [], selectedContexts);
3733
3806
  const definition = applyDefinitionLayer(base, layer);
3734
- const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
3807
+ const tools = resolveTools(resolved.tools, definition.tools);
3735
3808
  const skills = mergeSkills(
3736
3809
  selectNamedResources(resolved.skills, definition.skills, "Skill"),
3737
3810
  definition.bundledSkills
@@ -3804,11 +3877,20 @@ function intersectPhaseSelection(parent, layer) {
3804
3877
  phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
3805
3878
  };
3806
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
+ }
3807
3892
  function resolvePhases(candidates, selection) {
3808
- const coreNames = /* @__PURE__ */ new Set([DEFAULT_PHASE_ID, STOP_PHASE_ID, COMPACT_PHASE_ID]);
3809
- const core = candidates.filter((phase) => phase.core || coreNames.has(phase.name));
3810
- const authored = candidates.filter((phase) => !coreNames.has(phase.name) && !phase.core);
3811
- const selected = [...core, ...selectNamedResources(authored, selection?.phaseIds, "Phase")];
3893
+ const selected = resolveCoreResources(candidates, selection?.phaseIds, CORE_PHASE_NAMES, "Phase");
3812
3894
  const entryPhaseId = selection?.entryPhaseId ?? null;
3813
3895
  if (entryPhaseId !== null && entryPhaseId !== DEFAULT_PHASE_ID && !selected.some(({ name }) => name === entryPhaseId)) {
3814
3896
  console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
@@ -4478,6 +4560,8 @@ function projectTool(tool, agentId, runId) {
4478
4560
  ...tool.core ? { core: true } : {},
4479
4561
  description: tool.description,
4480
4562
  parameters: tool.parameters,
4563
+ ...tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {},
4564
+ ...tool.promptGuidelines ? { promptGuidelines: [...tool.promptGuidelines] } : {},
4481
4565
  execute: async (args, context, signal) => {
4482
4566
  const result = await tool.execute(args, {
4483
4567
  agentId,
@@ -4500,10 +4584,12 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4500
4584
  root: snapshot.cwd,
4501
4585
  ...options.toolArchiveDir ? { archiveDir: options.toolArchiveDir } : {}
4502
4586
  });
4503
- 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
+ );
4504
4590
  const tools = [
4505
- ...snapshot.resources.tools.filter((tool) => !coreNames.has(tool.name)),
4506
- ...coreTools
4591
+ ...snapshot.resources.tools,
4592
+ ...coreTools.filter((tool) => !replacingNames.has(tool.name))
4507
4593
  ];
4508
4594
  const context = resolveDefinitionContext(snapshot, tools);
4509
4595
  return {
@@ -4518,7 +4604,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4518
4604
  agentId: input.context.agentId,
4519
4605
  toolCallId: input.context.toolCallId,
4520
4606
  metadata: input.context.runMetadata,
4521
- answer: input.answer
4607
+ answer: input.answer,
4608
+ scope: input.context.scope,
4609
+ turn: input.context.turn
4522
4610
  });
4523
4611
  if (decision.interaction !== void 0) {
4524
4612
  return { interaction: decision.interaction };
@@ -4534,7 +4622,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4534
4622
  runId: input.context.runId,
4535
4623
  agentId: input.context.agentId,
4536
4624
  toolCallId: input.context.toolCallId,
4537
- metadata: input.context.runMetadata
4625
+ metadata: input.context.runMetadata,
4626
+ scope: input.context.scope,
4627
+ turn: input.context.turn
4538
4628
  }
4539
4629
  );
4540
4630
  return fromLoopResult(result);
@@ -4547,8 +4637,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4547
4637
  function resolveDefinitionContext(snapshot, assembled) {
4548
4638
  const coreTools = assembled.filter((tool) => tool.core);
4549
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;
4550
4641
  const tools = [
4551
- ...selectNamedResources(authoredTools, snapshot.definition.tools, "Tool"),
4642
+ ...selectNamedResources(authoredTools, authoredSelection, "Tool"),
4552
4643
  ...coreTools
4553
4644
  ];
4554
4645
  const skills = mergeSkills(
@@ -4560,14 +4651,12 @@ function resolveDefinitionContext(snapshot, assembled) {
4560
4651
  ...snapshot.additionalContexts
4561
4652
  ];
4562
4653
  const phaseCandidates = [...snapshot.resources.phases?.phases.values() ?? []];
4563
- 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;
4564
4657
  const selectedPhases = [
4565
- ...phaseCandidates.filter((phase) => phase.core || coreNames.has(phase.name)),
4566
- ...selectNamedResources(
4567
- phaseCandidates.filter((phase) => !phase.core && !coreNames.has(phase.name)),
4568
- snapshot.definition.phases?.phaseIds,
4569
- "Phase"
4570
- )
4658
+ ...corePhases,
4659
+ ...selectNamedResources(authoredPhases, authoredPhaseIds, "Phase")
4571
4660
  ];
4572
4661
  const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
4573
4662
  const requestedEntry = snapshot.definition.phases ? snapshot.definition.phases.entryPhaseId : snapshot.resources.phases?.entryPhaseId ?? null;
@@ -4792,6 +4881,126 @@ var HooksManager = class {
4792
4881
  }
4793
4882
  };
4794
4883
 
4884
+ // src/extensions/host.ts
4885
+ var InMemoryExtensionHost = class {
4886
+ configs = /* @__PURE__ */ new Map();
4887
+ agentState = /* @__PURE__ */ new Map();
4888
+ listeners = /* @__PURE__ */ new Set();
4889
+ constructor(options = {}) {
4890
+ if (options.configs) {
4891
+ for (const [scopeKey, extMap] of Object.entries(options.configs)) {
4892
+ const map = /* @__PURE__ */ new Map();
4893
+ for (const [extId, cfg] of Object.entries(extMap)) {
4894
+ assertJsonValue(cfg);
4895
+ map.set(extId, structuredClone(cfg));
4896
+ }
4897
+ this.configs.set(scopeKey, map);
4898
+ }
4899
+ }
4900
+ if (options.agentState) {
4901
+ for (const [agentId, extMap] of Object.entries(options.agentState)) {
4902
+ const agentMap = /* @__PURE__ */ new Map();
4903
+ for (const [extId, stateMap] of Object.entries(extMap)) {
4904
+ const map = /* @__PURE__ */ new Map();
4905
+ for (const [key, val] of Object.entries(stateMap)) {
4906
+ assertJsonValue(val);
4907
+ map.set(key, structuredClone(val));
4908
+ }
4909
+ agentMap.set(extId, map);
4910
+ }
4911
+ this.agentState.set(agentId, agentMap);
4912
+ }
4913
+ }
4914
+ }
4915
+ setConfig(extensionId, config, scope = { kind: "global" }) {
4916
+ const scopeKey = scopeToKey(scope);
4917
+ if (!this.configs.has(scopeKey)) {
4918
+ this.configs.set(scopeKey, /* @__PURE__ */ new Map());
4919
+ }
4920
+ const scopeConfigs = this.configs.get(scopeKey);
4921
+ if (config === null) {
4922
+ scopeConfigs.delete(extensionId);
4923
+ } else {
4924
+ assertJsonValue(config);
4925
+ scopeConfigs.set(extensionId, structuredClone(config));
4926
+ }
4927
+ this.notifyConfigChanged(extensionId, scope);
4928
+ }
4929
+ getConfig(extensionId, scope = { kind: "global" }) {
4930
+ const globalConfig = this.configs.get("global")?.get(extensionId);
4931
+ let merged = globalConfig ? { ...globalConfig } : null;
4932
+ if (scope.kind === "team" || scope.kind === "project") {
4933
+ const teamConfig = this.configs.get(`team:${scope.teamId}`)?.get(extensionId);
4934
+ if (teamConfig) {
4935
+ merged = { ...merged ?? {}, ...teamConfig };
4936
+ }
4937
+ }
4938
+ if (scope.kind === "project") {
4939
+ const projectConfig = this.configs.get(`project:${scope.teamId}:${scope.projectId}`)?.get(extensionId);
4940
+ if (projectConfig) {
4941
+ merged = { ...merged ?? {}, ...projectConfig };
4942
+ }
4943
+ }
4944
+ return merged ? structuredClone(merged) : null;
4945
+ }
4946
+ onConfigChanged(listener) {
4947
+ this.listeners.add(listener);
4948
+ return () => this.listeners.delete(listener);
4949
+ }
4950
+ notifyConfigChanged(extensionId, scope = { kind: "global" }) {
4951
+ for (const listener of this.listeners) {
4952
+ try {
4953
+ listener(extensionId, scope);
4954
+ } catch {
4955
+ }
4956
+ }
4957
+ }
4958
+ getAgentState(extensionId, agentId, key) {
4959
+ const val = this.agentState.get(agentId)?.get(extensionId)?.get(key);
4960
+ return val !== void 0 ? structuredClone(val) : void 0;
4961
+ }
4962
+ setAgentState(extensionId, agentId, key, value) {
4963
+ assertJsonValue(value);
4964
+ let agentMap = this.agentState.get(agentId);
4965
+ if (!agentMap) {
4966
+ agentMap = /* @__PURE__ */ new Map();
4967
+ this.agentState.set(agentId, agentMap);
4968
+ }
4969
+ let extMap = agentMap.get(extensionId);
4970
+ if (!extMap) {
4971
+ extMap = /* @__PURE__ */ new Map();
4972
+ agentMap.set(extensionId, extMap);
4973
+ }
4974
+ extMap.set(key, structuredClone(value));
4975
+ }
4976
+ deleteAgentState(extensionId, agentId, key) {
4977
+ this.agentState.get(agentId)?.get(extensionId)?.delete(key);
4978
+ }
4979
+ };
4980
+ function scopeToKey(scope) {
4981
+ switch (scope.kind) {
4982
+ case "global":
4983
+ return "global";
4984
+ case "team":
4985
+ return `team:${scope.teamId}`;
4986
+ case "project":
4987
+ return `project:${scope.teamId}:${scope.projectId}`;
4988
+ }
4989
+ }
4990
+ function resolveScopeFromMetadata(metadata) {
4991
+ if (metadata) {
4992
+ const teamId = typeof metadata.teamId === "string" ? metadata.teamId : void 0;
4993
+ const projectId = typeof metadata.projectId === "string" ? metadata.projectId : void 0;
4994
+ if (teamId && projectId) {
4995
+ return { kind: "project", teamId, projectId };
4996
+ }
4997
+ if (teamId) {
4998
+ return { kind: "team", teamId };
4999
+ }
5000
+ }
5001
+ return { kind: "global" };
5002
+ }
5003
+
4795
5004
  // src/extensions/runner.ts
4796
5005
  import { execFile } from "child_process";
4797
5006
  import { dirname as dirname5, resolve as resolve5 } from "path";
@@ -4934,11 +5143,17 @@ var ExtensionRunner = class {
4934
5143
  hooks;
4935
5144
  runtime;
4936
5145
  events;
5146
+ host;
4937
5147
  cwd;
4938
5148
  abortController = new AbortController();
4939
5149
  _idle = true;
4940
5150
  // Per-extension tracking
4941
5151
  extensions = [];
5152
+ // Run state: runId -> extensionId -> key -> value
5153
+ runState = /* @__PURE__ */ new Map();
5154
+ // Config change listeners: extensionId -> Set of handlers
5155
+ configChangeListeners = /* @__PURE__ */ new Map();
5156
+ hostUnsubscribers = [];
4942
5157
  // Phase management
4943
5158
  phases = /* @__PURE__ */ new Map();
4944
5159
  _phaseCache = null;
@@ -4947,6 +5162,8 @@ var ExtensionRunner = class {
4947
5162
  bound = false;
4948
5163
  // Error listeners
4949
5164
  errorListeners = /* @__PURE__ */ new Set();
5165
+ // Capability change listeners
5166
+ capabilityListeners = /* @__PURE__ */ new Set();
4950
5167
  /** Current agent context — set by the agent before each phase */
4951
5168
  currentContext;
4952
5169
  constructor(options) {
@@ -4954,6 +5171,15 @@ var ExtensionRunner = class {
4954
5171
  this.runtime = createExtensionRuntime();
4955
5172
  this.events = createEventBus();
4956
5173
  this.cwd = options?.cwd ?? process.cwd();
5174
+ this.host = options?.host ?? new InMemoryExtensionHost();
5175
+ if (this.host.onConfigChanged) {
5176
+ const unsub = this.host.onConfigChanged((extensionId, scope) => {
5177
+ this.notifyConfigChanged(extensionId, scope);
5178
+ });
5179
+ if (typeof unsub === "function") {
5180
+ this.hostUnsubscribers.push(unsub);
5181
+ }
5182
+ }
4957
5183
  }
4958
5184
  /** Whether the agent is currently idle (not streaming). */
4959
5185
  get isIdle() {
@@ -5034,10 +5260,13 @@ var ExtensionRunner = class {
5034
5260
  */
5035
5261
  async loadExtensions(extensions) {
5036
5262
  for (const ext of extensions) {
5263
+ const extensionId = ext.id ?? ext.manifest?.id ?? ext.name;
5037
5264
  const extension = {
5265
+ id: extensionId,
5038
5266
  path: ext.path,
5039
5267
  tools: /* @__PURE__ */ new Map(),
5040
5268
  phases: /* @__PURE__ */ new Set(),
5269
+ capabilities: /* @__PURE__ */ new Map(),
5041
5270
  cleanup: [],
5042
5271
  runtime: createExtensionRuntime()
5043
5272
  };
@@ -5047,6 +5276,9 @@ var ExtensionRunner = class {
5047
5276
  if (typeof disposer === "function") extension.disposer = disposer;
5048
5277
  this.extensions.push(extension);
5049
5278
  this._phaseCache = null;
5279
+ if (extension.capabilities.size > 0) {
5280
+ this.notifyCapabilitiesChanged();
5281
+ }
5050
5282
  } catch (error) {
5051
5283
  await this.rollbackExtension(extension);
5052
5284
  const message = error instanceof Error ? error.message : String(error);
@@ -5063,14 +5295,67 @@ var ExtensionRunner = class {
5063
5295
  /** Dispose all active Extensions in reverse activation order. */
5064
5296
  async close() {
5065
5297
  this.abortController.abort();
5298
+ for (const unsub of this.hostUnsubscribers) {
5299
+ try {
5300
+ unsub();
5301
+ } catch {
5302
+ }
5303
+ }
5304
+ this.hostUnsubscribers.length = 0;
5305
+ this.configChangeListeners.clear();
5306
+ this.runState.clear();
5066
5307
  for (const extension of [...this.extensions].reverse()) {
5067
5308
  await this.disposeExtension(extension, "This Extension Runtime has been closed.");
5068
5309
  }
5069
5310
  this.extensions.length = 0;
5311
+ this.capabilityListeners.clear();
5070
5312
  this.phases.clear();
5071
5313
  this._phaseCache = null;
5072
5314
  this.runtime.invalidate("This Extension Runtime has been closed.");
5073
5315
  }
5316
+ get extensionHost() {
5317
+ return this.host;
5318
+ }
5319
+ notifyConfigChanged(extensionId, scope) {
5320
+ const listeners = this.configChangeListeners.get(extensionId);
5321
+ if (!listeners) return;
5322
+ for (const listener of listeners) {
5323
+ try {
5324
+ listener(scope);
5325
+ } catch (error) {
5326
+ this.emitError({
5327
+ extensionPath: "<runtime>",
5328
+ event: "config_changed",
5329
+ error: error instanceof Error ? error.message : String(error),
5330
+ stack: error instanceof Error ? error.stack : void 0
5331
+ });
5332
+ }
5333
+ }
5334
+ }
5335
+ getRunState(runId, extensionId, key) {
5336
+ const val = this.runState.get(runId)?.get(extensionId)?.get(key);
5337
+ return val !== void 0 ? structuredClone(val) : void 0;
5338
+ }
5339
+ setRunState(runId, extensionId, key, value) {
5340
+ assertJsonValue(value);
5341
+ let runMap = this.runState.get(runId);
5342
+ if (!runMap) {
5343
+ runMap = /* @__PURE__ */ new Map();
5344
+ this.runState.set(runId, runMap);
5345
+ }
5346
+ let extMap = runMap.get(extensionId);
5347
+ if (!extMap) {
5348
+ extMap = /* @__PURE__ */ new Map();
5349
+ runMap.set(extensionId, extMap);
5350
+ }
5351
+ extMap.set(key, structuredClone(value));
5352
+ }
5353
+ deleteRunState(runId, extensionId, key) {
5354
+ this.runState.get(runId)?.get(extensionId)?.delete(key);
5355
+ }
5356
+ dropRunState(runId) {
5357
+ this.runState.delete(runId);
5358
+ }
5074
5359
  // ---------------------------------------------------------------------------
5075
5360
  // Tool management
5076
5361
  // ---------------------------------------------------------------------------
@@ -5097,6 +5382,32 @@ var ExtensionRunner = class {
5097
5382
  return void 0;
5098
5383
  }
5099
5384
  // ---------------------------------------------------------------------------
5385
+ // Capability management
5386
+ // ---------------------------------------------------------------------------
5387
+ /** Get all contributed capabilities across all extensions. */
5388
+ getCapabilities() {
5389
+ const result = [];
5390
+ for (const ext of this.extensions) {
5391
+ for (const contrib of ext.capabilities.values()) {
5392
+ result.push({
5393
+ extensionId: ext.id,
5394
+ kind: contrib.kind,
5395
+ name: contrib.name,
5396
+ description: contrib.description
5397
+ });
5398
+ }
5399
+ }
5400
+ return Object.freeze(result);
5401
+ }
5402
+ /**
5403
+ * Subscribe to capability changes (contributions added, removed, or extensions disposed).
5404
+ * Returns an unsubscribe function.
5405
+ */
5406
+ onCapabilitiesChanged(listener) {
5407
+ this.capabilityListeners.add(listener);
5408
+ return () => this.capabilityListeners.delete(listener);
5409
+ }
5410
+ // ---------------------------------------------------------------------------
5100
5411
  // Phase management
5101
5412
  // ---------------------------------------------------------------------------
5102
5413
  getPhase(name) {
@@ -5182,7 +5493,39 @@ var ExtensionRunner = class {
5182
5493
  });
5183
5494
  return result?.input ?? input;
5184
5495
  }
5496
+ async emitRunStart(event) {
5497
+ try {
5498
+ await this.hooks.emit("run_start", {
5499
+ type: "run_start",
5500
+ ...event
5501
+ });
5502
+ } catch (error) {
5503
+ this.emitError({
5504
+ extensionPath: "<runtime>",
5505
+ event: "run_start",
5506
+ error: error instanceof Error ? error.message : String(error),
5507
+ stack: error instanceof Error ? error.stack : void 0
5508
+ });
5509
+ }
5510
+ }
5511
+ async emitRunEnd(event) {
5512
+ try {
5513
+ await this.hooks.emit("run_end", {
5514
+ type: "run_end",
5515
+ ...event
5516
+ });
5517
+ } catch (error) {
5518
+ this.emitError({
5519
+ extensionPath: "<runtime>",
5520
+ event: "run_end",
5521
+ error: error instanceof Error ? error.message : String(error),
5522
+ stack: error instanceof Error ? error.stack : void 0
5523
+ });
5524
+ }
5525
+ }
5185
5526
  async emitBeforeToolCall(tool, args, context) {
5527
+ const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
5528
+ const turn = context?.turn ?? {};
5186
5529
  const result = await this.emitHook("before_tool_call", {
5187
5530
  type: "before_tool_call",
5188
5531
  tool,
@@ -5191,11 +5534,15 @@ var ExtensionRunner = class {
5191
5534
  ...context?.agentId !== void 0 ? { agentId: context.agentId } : {},
5192
5535
  ...context?.toolCallId !== void 0 ? { toolCallId: context.toolCallId } : {},
5193
5536
  ...context?.metadata !== void 0 ? { metadata: context.metadata } : {},
5194
- ...context?.answer !== void 0 ? { answer: context.answer } : {}
5537
+ ...context?.answer !== void 0 ? { answer: context.answer } : {},
5538
+ scope,
5539
+ turn
5195
5540
  });
5196
5541
  return result ?? { allow: true };
5197
5542
  }
5198
5543
  async emitAfterToolCall(tool, result, context) {
5544
+ const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
5545
+ const turn = context?.turn ?? {};
5199
5546
  const hookResult = await this.emitHook("after_tool_call", {
5200
5547
  type: "after_tool_call",
5201
5548
  tool,
@@ -5203,7 +5550,9 @@ var ExtensionRunner = class {
5203
5550
  ...context?.runId !== void 0 ? { runId: context.runId } : {},
5204
5551
  ...context?.agentId !== void 0 ? { agentId: context.agentId } : {},
5205
5552
  ...context?.toolCallId !== void 0 ? { toolCallId: context.toolCallId } : {},
5206
- ...context?.metadata !== void 0 ? { metadata: context.metadata } : {}
5553
+ ...context?.metadata !== void 0 ? { metadata: context.metadata } : {},
5554
+ scope,
5555
+ turn
5207
5556
  });
5208
5557
  return hookResult?.result ?? result;
5209
5558
  }
@@ -5271,9 +5620,43 @@ var ExtensionRunner = class {
5271
5620
  unregisterProvider: (name) => this.unregisterProvider(name),
5272
5621
  registerTool: (tool) => this.registerTool(extension, tool),
5273
5622
  unregisterTool: (toolName) => this.unregisterTool(extension, toolName),
5623
+ contributeCapability: (contribution) => this.contributeCapability(extension, contribution),
5274
5624
  context: extContext,
5275
5625
  manifest,
5276
- trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
5626
+ trackCleanup: (cleanup) => extension.cleanup.push(cleanup),
5627
+ config: {
5628
+ get: async (scope) => {
5629
+ const effectiveScope = scope ?? { kind: "global" };
5630
+ const res = await runner.host.getConfig(extension.id, effectiveScope);
5631
+ return res ?? null;
5632
+ },
5633
+ changed: (handler) => {
5634
+ let listeners = runner.configChangeListeners.get(extension.id);
5635
+ if (!listeners) {
5636
+ listeners = /* @__PURE__ */ new Set();
5637
+ runner.configChangeListeners.set(extension.id, listeners);
5638
+ }
5639
+ listeners.add(handler);
5640
+ extension.cleanup.push(() => {
5641
+ listeners?.delete(handler);
5642
+ if (listeners && listeners.size === 0) {
5643
+ runner.configChangeListeners.delete(extension.id);
5644
+ }
5645
+ });
5646
+ }
5647
+ },
5648
+ state: {
5649
+ run: (runId) => ({
5650
+ get: async (key) => runner.getRunState(runId, extension.id, key),
5651
+ set: async (key, value) => runner.setRunState(runId, extension.id, key, value),
5652
+ delete: async (key) => runner.deleteRunState(runId, extension.id, key)
5653
+ }),
5654
+ agent: (agentId) => ({
5655
+ get: async (key) => runner.host.getAgentState(extension.id, agentId, key),
5656
+ set: async (key, value) => runner.host.setAgentState(extension.id, agentId, key, value),
5657
+ delete: async (key) => runner.host.deleteAgentState(extension.id, agentId, key)
5658
+ })
5659
+ }
5277
5660
  }, extension.runtime, this.events);
5278
5661
  }
5279
5662
  registerTool(extension, tool) {
@@ -5302,6 +5685,46 @@ var ExtensionRunner = class {
5302
5685
  extension.tools.delete(toolName);
5303
5686
  }
5304
5687
  }
5688
+ contributeCapability(extension, contribution) {
5689
+ const key = `${contribution.kind}:${contribution.name}`;
5690
+ const entry = {
5691
+ kind: contribution.kind,
5692
+ name: contribution.name,
5693
+ description: contribution.description
5694
+ };
5695
+ extension.capabilities.set(key, entry);
5696
+ if (this.extensions.includes(extension)) {
5697
+ this.notifyCapabilitiesChanged();
5698
+ }
5699
+ let disposed = false;
5700
+ const dispose = () => {
5701
+ if (disposed) return;
5702
+ disposed = true;
5703
+ if (extension.capabilities.get(key) === entry) {
5704
+ extension.capabilities.delete(key);
5705
+ if (this.extensions.includes(extension)) {
5706
+ this.notifyCapabilitiesChanged();
5707
+ }
5708
+ }
5709
+ };
5710
+ extension.cleanup.push(dispose);
5711
+ return dispose;
5712
+ }
5713
+ notifyCapabilitiesChanged() {
5714
+ const capabilities = this.getCapabilities();
5715
+ for (const listener of this.capabilityListeners) {
5716
+ try {
5717
+ listener(capabilities);
5718
+ } catch (error) {
5719
+ this.emitError({
5720
+ extensionPath: "<runtime>",
5721
+ event: "capabilities_changed",
5722
+ error: error instanceof Error ? error.message : String(error),
5723
+ stack: error instanceof Error ? error.stack : void 0
5724
+ });
5725
+ }
5726
+ }
5727
+ }
5305
5728
  async registerPhase(extension, registration) {
5306
5729
  if (typeof registration === "string") {
5307
5730
  if (registration.length === 0) {
@@ -5388,6 +5811,8 @@ var ExtensionRunner = class {
5388
5811
  async disposeExtension(extension, message) {
5389
5812
  extension.runtime.invalidate(message);
5390
5813
  for (const name of extension.phases) this.phases.delete(name);
5814
+ const hadCapabilities = extension.capabilities.size > 0;
5815
+ extension.capabilities.clear();
5391
5816
  for (const cleanup of [...extension.cleanup].reverse()) {
5392
5817
  await Promise.resolve().then(() => cleanup()).catch(() => void 0);
5393
5818
  }
@@ -5395,6 +5820,9 @@ var ExtensionRunner = class {
5395
5820
  await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
5396
5821
  }
5397
5822
  this._phaseCache = null;
5823
+ if (hadCapabilities) {
5824
+ this.notifyCapabilitiesChanged();
5825
+ }
5398
5826
  }
5399
5827
  };
5400
5828
  function createExtensionRunner(options) {
@@ -5425,6 +5853,8 @@ function readManifestSync(dir) {
5425
5853
  const rowan = pkg.rowan;
5426
5854
  if (!rowan) return void 0;
5427
5855
  return {
5856
+ id: rowan.id ?? pkg.name,
5857
+ name: pkg.name,
5428
5858
  entry: rowan.extensions?.[0]
5429
5859
  };
5430
5860
  } catch {
@@ -5507,13 +5937,15 @@ async function discoverExtensionsInDir(dir) {
5507
5937
  }
5508
5938
  return paths;
5509
5939
  }
5510
- function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
5940
+ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>", options = {}) {
5511
5941
  const resolvedCwd = resolve6(cwd);
5512
5942
  const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve6(resolvedCwd, extensionPath);
5513
- const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown";
5943
+ const name = options.name ?? (isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown");
5514
5944
  const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname6(resolvedPath);
5515
5945
  const manifest = readManifestSync(manifestDir);
5946
+ const id = options.id ?? manifest?.id ?? name;
5516
5947
  return {
5948
+ id,
5517
5949
  path: resolvedPath,
5518
5950
  name,
5519
5951
  factory,
@@ -5660,8 +6092,16 @@ var ResourceRegistry = class {
5660
6092
  }
5661
6093
  withImplicit(kind, sourceIds) {
5662
6094
  const combined = [...sourceIds];
6095
+ let hasCore = false;
5663
6096
  for (const sourceId of this.implicitSources.get(kind) ?? []) {
5664
- 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");
5665
6105
  }
5666
6106
  return combined;
5667
6107
  }
@@ -5692,6 +6132,12 @@ var ResourceRegistry = class {
5692
6132
  `${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
5693
6133
  );
5694
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
+ }
5695
6141
  if (names.has(value.name)) {
5696
6142
  throw new ResourceRegistryError(
5697
6143
  "resource_collision",
@@ -5726,6 +6172,12 @@ var ResourceRegistry = class {
5726
6172
  for (const source of this.sourcesFor(kind, sourceIds)) {
5727
6173
  for (const value of source.values) {
5728
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
+ }
5729
6181
  throw new ResourceRegistryError(
5730
6182
  "resource_collision",
5731
6183
  `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
@@ -5745,7 +6197,15 @@ var ResourceRegistry = class {
5745
6197
  const refs = [];
5746
6198
  for (const source of this.sourcesFor(kind, sourceIds)) {
5747
6199
  for (const value of source.values) {
5748
- 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
+ }
5749
6209
  seenNames.add(value.name);
5750
6210
  refs.push({ kind, sourceId: source.sourceId, name: value.name });
5751
6211
  }
@@ -5815,7 +6275,10 @@ function capitalize(value) {
5815
6275
  return value.charAt(0).toUpperCase() + value.slice(1);
5816
6276
  }
5817
6277
  function isImplicitCoreName(kind, name) {
5818
- 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);
5819
6282
  }
5820
6283
  async function loadDirectoryValues(kind, directory, sourceId) {
5821
6284
  const root = resolve7(directory);
@@ -5983,6 +6446,12 @@ var RuntimeExtensionLifetime = class {
5983
6446
  get extensionRunner() {
5984
6447
  return this.runner;
5985
6448
  }
6449
+ get extensionHost() {
6450
+ return this.runner.extensionHost;
6451
+ }
6452
+ dropRunState(runId) {
6453
+ this.runner.dropRunState(runId);
6454
+ }
5986
6455
  async close() {
5987
6456
  if (this.closed) return;
5988
6457
  this.closed = true;
@@ -5996,6 +6465,9 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
5996
6465
  }
5997
6466
  extensions;
5998
6467
  extensionSourceId = "rowan.extensions";
6468
+ get extensionHost() {
6469
+ return this.extensions.extensionHost;
6470
+ }
5999
6471
  /** Install Rowan-owned implicit resources before any host source is read. */
6000
6472
  async ensureCoreResources() {
6001
6473
  await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
@@ -6041,6 +6513,8 @@ function adaptExtensionTool(input) {
6041
6513
  name: definition.name,
6042
6514
  description: definition.description,
6043
6515
  parameters: definition.parameters,
6516
+ ...definition.promptSnippet ? { promptSnippet: definition.promptSnippet } : {},
6517
+ ...definition.promptGuidelines ? { promptGuidelines: definition.promptGuidelines } : {},
6044
6518
  execute: async (args, context, signal) => {
6045
6519
  const result = await definition.execute(args, context, signal);
6046
6520
  const content = JSON.parse(JSON.stringify(result.content));
@@ -6134,11 +6608,14 @@ var AgentRuntime = class _AgentRuntime {
6134
6608
  autoCompactionRuns = /* @__PURE__ */ new Set();
6135
6609
  consumers = /* @__PURE__ */ new Map();
6136
6610
  transientEvents = new TransientRunEventHub();
6611
+ host;
6137
6612
  resources;
6613
+ startedRuns = /* @__PURE__ */ new Set();
6138
6614
  heartbeat;
6139
6615
  pumping = false;
6140
6616
  closed = false;
6141
6617
  constructor(options, owned, resources) {
6618
+ this.host = options.host;
6142
6619
  this.owned = owned;
6143
6620
  this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
6144
6621
  this.storeIncarnation = String(owned.lease.token).split(":")[0];
@@ -6148,10 +6625,12 @@ var AgentRuntime = class _AgentRuntime {
6148
6625
  static async init(options) {
6149
6626
  if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
6150
6627
  const configs = options.configs ?? new InMemoryConfigProvider();
6628
+ const host = options.host ?? new InMemoryExtensionHost();
6151
6629
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
6152
6630
  if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
6153
6631
  const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
6154
- const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
6632
+ const extensions = new RuntimeExtensionLifetime({ host });
6633
+ const runtime = new _AgentRuntime({ ...options, configs, concurrency, host }, owned, new RuntimeBootstrapRegistry(extensions));
6155
6634
  try {
6156
6635
  await runtime.resources.ensureCoreResources();
6157
6636
  await options.bootstrap?.(runtime.resources);
@@ -6164,6 +6643,20 @@ var AgentRuntime = class _AgentRuntime {
6164
6643
  throw error;
6165
6644
  }
6166
6645
  }
6646
+ async handleRunEnd(runId, agentId, outcome) {
6647
+ if (this.startedRuns.has(runId)) {
6648
+ this.startedRuns.delete(runId);
6649
+ try {
6650
+ await this.resources.extensionRunner.emitRunEnd({
6651
+ runId,
6652
+ agentId,
6653
+ outcome
6654
+ });
6655
+ } catch {
6656
+ }
6657
+ }
6658
+ this.resources.extensionRunner.dropRunState(runId);
6659
+ }
6167
6660
  async loadAgents(input) {
6168
6661
  this.assertOpen();
6169
6662
  return this.resources.loadAgents(input);
@@ -6374,6 +6867,14 @@ var AgentRuntime = class _AgentRuntime {
6374
6867
  });
6375
6868
  return { caughtUp: subscription.caughtUp.promise, done: subscription.done.promise, stop: () => controller.abort() };
6376
6869
  }
6870
+ listCapabilities() {
6871
+ this.assertOpen();
6872
+ return this.resources.extensionRunner.getCapabilities();
6873
+ }
6874
+ onCapabilitiesChanged(listener) {
6875
+ this.assertOpen();
6876
+ return this.resources.extensionRunner.onCapabilitiesChanged(listener);
6877
+ }
6377
6878
  async close() {
6378
6879
  if (this.closed) return;
6379
6880
  this.closed = true;
@@ -6417,6 +6918,11 @@ var AgentRuntime = class _AgentRuntime {
6417
6918
  const active = this.executions.get(runId);
6418
6919
  if (active) this.transientEvents.clear(runId, active.executionId);
6419
6920
  active?.controller.abort();
6921
+ if (this.startedRuns.has(runId)) {
6922
+ await this.handleRunEnd(runId, snapshot.agentId, { id: createId("out"), status: "cancelled", message: reason ?? "Agent run stopped." });
6923
+ } else {
6924
+ this.resources.extensionRunner.dropRunState(runId);
6925
+ }
6420
6926
  return boundaryFromSnapshot(await this.owned.snapshotRun(runId));
6421
6927
  }
6422
6928
  async pump() {
@@ -6528,6 +7034,19 @@ var AgentRuntime = class _AgentRuntime {
6528
7034
  const controller = new AbortController();
6529
7035
  this.executions.set(run.id, { controller, executionId });
6530
7036
  if (this.cancellationRequested.has(run.id)) controller.abort();
7037
+ if (!this.startedRuns.has(run.id)) {
7038
+ this.startedRuns.add(run.id);
7039
+ const turn = typeof run.input === "object" && run.input !== null && !Array.isArray(run.input) ? run.input : {};
7040
+ try {
7041
+ await this.resources.extensionRunner.emitRunStart({
7042
+ runId: run.id,
7043
+ agentId: run.agentId,
7044
+ metadata: run.metadata ?? {},
7045
+ turn
7046
+ });
7047
+ } catch {
7048
+ }
7049
+ }
6531
7050
  const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
6532
7051
  toolArchiveDir: this.archiveDirFor(run.agentId)
6533
7052
  });
@@ -6754,6 +7273,11 @@ var AgentRuntime = class _AgentRuntime {
6754
7273
  ...output && hasVisibleAssistantText(output) ? { output } : {}
6755
7274
  });
6756
7275
  this.transientEvents.clear(run.id, claim.execution.executionId);
7276
+ await this.handleRunEnd(run.id, run.agentId, {
7277
+ id: createId("out"),
7278
+ status: "cancelled",
7279
+ message: reason
7280
+ });
6757
7281
  return;
6758
7282
  }
6759
7283
  if (result.type === "input_required") {
@@ -6789,33 +7313,53 @@ var AgentRuntime = class _AgentRuntime {
6789
7313
  const output = latestAssistant(run, result.messages, executionContext.messages.length);
6790
7314
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
6791
7315
  this.transientEvents.clear(run.id, claim.execution.executionId, true);
7316
+ await this.handleRunEnd(run.id, run.agentId, result.outcome);
6792
7317
  return;
6793
7318
  }
6794
7319
  const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
6795
7320
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, failure });
6796
7321
  this.transientEvents.clear(run.id, claim.execution.executionId);
7322
+ await this.handleRunEnd(run.id, run.agentId, {
7323
+ id: createId("out"),
7324
+ status: "failed",
7325
+ message: failure.message,
7326
+ error: failure
7327
+ });
6797
7328
  } catch (error) {
6798
7329
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
6799
7330
  if (claim) {
6800
7331
  if (this.cancellationRequested.has(run.id)) {
7332
+ const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
6801
7333
  await this.owned.cancelRun({
6802
7334
  runId: run.id,
6803
7335
  expectedRevision: executionRevision,
6804
- reason: this.cancellationReasons.get(run.id) ?? "Agent run stopped."
7336
+ reason
6805
7337
  }).catch(() => void 0);
7338
+ await this.handleRunEnd(run.id, run.agentId, {
7339
+ id: createId("out"),
7340
+ status: "cancelled",
7341
+ message: reason
7342
+ });
6806
7343
  return;
6807
7344
  }
7345
+ const failureMessage = error instanceof Error ? error.message : "Execution failed.";
6808
7346
  await this.owned.commitOutcome({
6809
7347
  runId: run.id,
6810
7348
  execution: claim.execution,
6811
7349
  expectedRevision: executionRevision,
6812
- failure: { code: "execution_failed", message: error instanceof Error ? error.message : "Execution failed." }
7350
+ failure: { code: "execution_failed", message: failureMessage }
6813
7351
  }).catch(async (commitError) => {
6814
7352
  await this.owned.cancelRun({
6815
7353
  runId: run.id,
6816
7354
  reason: commitError instanceof Error ? commitError.message : "Run failure could not be committed."
6817
7355
  }).catch(() => void 0);
6818
7356
  });
7357
+ await this.handleRunEnd(run.id, run.agentId, {
7358
+ id: createId("out"),
7359
+ status: "failed",
7360
+ message: failureMessage,
7361
+ error: { code: "execution_failed", message: failureMessage }
7362
+ });
6819
7363
  return;
6820
7364
  }
6821
7365
  throw error;
@@ -6838,6 +7382,8 @@ var AgentRuntime = class _AgentRuntime {
6838
7382
  return { result: batch.results[0], revision: batch.revision };
6839
7383
  }
6840
7384
  async executeToolBatch(input) {
7385
+ const scope = resolveScopeFromMetadata(input.run.metadata);
7386
+ const turn = typeof input.run.input === "object" && input.run.input !== null && !Array.isArray(input.run.input) ? input.run.input : {};
6841
7387
  const checkpoint = input.driver?.checkpoint();
6842
7388
  const isSuspensionResume = checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension";
6843
7389
  const suspension = isSuspensionResume ? checkpoint : void 0;
@@ -6864,6 +7410,8 @@ var AgentRuntime = class _AgentRuntime {
6864
7410
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
6865
7411
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
6866
7412
  toolCallId,
7413
+ scope,
7414
+ turn,
6867
7415
  interaction: createRunInteractionDriver({
6868
7416
  currentPhase: "default",
6869
7417
  attempt: 0,
@@ -6951,6 +7499,8 @@ var AgentRuntime = class _AgentRuntime {
6951
7499
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
6952
7500
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
6953
7501
  toolCallId,
7502
+ scope,
7503
+ turn,
6954
7504
  interaction: createToolInteractionDriver(input.driver, {
6955
7505
  toolCallId,
6956
7506
  toolCallIds,
@@ -7105,6 +7655,8 @@ var AgentRuntime = class _AgentRuntime {
7105
7655
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
7106
7656
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
7107
7657
  toolCallId,
7658
+ scope,
7659
+ turn,
7108
7660
  interaction: createToolInteractionDriver(input.driver, {
7109
7661
  toolCallId,
7110
7662
  ...suspension?.toolCallIds === void 0 ? {} : { toolCallIds: suspension.toolCallIds },
@@ -7556,23 +8108,29 @@ function isContextOverflowError(error) {
7556
8108
  }
7557
8109
  function normalizePhaseRegistry(registry) {
7558
8110
  const core = createCorePhases();
7559
- const coreNames = new Set(core.map(({ name }) => name));
7560
- for (const [name, phase] of registry?.phases ?? []) {
7561
- if (coreNames.has(name) && !phase.core) {
7562
- throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${name}".`);
7563
- }
7564
- }
8111
+ const coreNames = CORE_PHASE_NAMES;
7565
8112
  for (const [name] of registry?.phases ?? []) {
7566
8113
  if (name === "continue") {
7567
8114
  throw new TypeError(`Configured Phase name "${name}" is reserved by Rowan routing controls.`);
7568
8115
  }
7569
8116
  }
7570
- 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
+ }
7571
8132
  return {
7572
- phases: new Map([
7573
- ...core.map((phase) => [phase.name, phase]),
7574
- ...authored
7575
- ]),
8133
+ phases,
7576
8134
  entryPhaseId: registry?.entryPhaseId ?? DEFAULT_PHASE_ID
7577
8135
  };
7578
8136
  }
@@ -10620,6 +11178,7 @@ export {
10620
11178
  DEFAULT_PHASE_ID,
10621
11179
  ExtensionLifetimeError,
10622
11180
  InMemoryConfigProvider,
11181
+ InMemoryExtensionHost,
10623
11182
  InMemoryStore,
10624
11183
  ResourceRegistry,
10625
11184
  ResourceRegistryError,