@rowan-agent/agent 0.22.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -319,6 +319,8 @@ declare class InMemoryExtensionHost implements ExtensionHost {
319
319
  * - Tool registration support
320
320
  */
321
321
 
322
+ /** The Resource Source an Extension registration lands in: Global (`[]`) is `rowan.extensions`, any other Scope `rowan.extensions:<scopeToKey(scope)>`. */
323
+ declare function extensionSourceId(scope?: ScopeRef): string;
322
324
  type ExtensionRunnerOptions = {
323
325
  entryPhaseId?: string | null;
324
326
  cwd?: string;
@@ -368,10 +370,12 @@ declare class ExtensionRunner {
368
370
  private readonly hostUnsubscribers;
369
371
  private readonly phases;
370
372
  private _phaseCache;
373
+ private readonly skills;
371
374
  private readonly pendingProviders;
372
375
  private bound;
373
376
  private readonly errorListeners;
374
377
  private readonly capabilityListeners;
378
+ private readonly resourceChangeListeners;
375
379
  /** Current agent context — set by the agent before each phase */
376
380
  currentContext?: AgentContext;
377
381
  constructor(options?: ExtensionRunnerOptions);
@@ -419,7 +423,7 @@ declare class ExtensionRunner {
419
423
  /** Dispose all active Extensions in reverse activation order. */
420
424
  close(): Promise<void>;
421
425
  get extensionHost(): ExtensionHost;
422
- notifyConfigChanged(extensionId: string, scope: ScopeRef): void;
426
+ notifyConfigChanged(extensionId: string, scope: ScopeRef): Promise<void>;
423
427
  getRunState(runId: string, extensionId: string, key: string): JsonValue | undefined;
424
428
  setRunState(runId: string, extensionId: string, key: string, value: JsonValue): void;
425
429
  deleteRunState(runId: string, extensionId: string, key: string): void;
@@ -437,6 +441,12 @@ declare class ExtensionRunner {
437
441
  * Returns an unsubscribe function.
438
442
  */
439
443
  onCapabilitiesChanged(listener: (capabilities: readonly ExtensionCapability[]) => void): () => void;
444
+ /**
445
+ * Subscribe to resource changes (tools, phases, skills registered, unregistered, or disposed).
446
+ * Used by RuntimeBootstrapRegistry to re-commit implicit sources after activation.
447
+ */
448
+ onResourceChange(listener: (kind: "tool" | "phase" | "skill") => Promise<void> | void): () => void;
449
+ private notifyResourceChange;
440
450
  triggerUiAction(event: {
441
451
  contributionId: string;
442
452
  actionId: string;
@@ -444,11 +454,14 @@ declare class ExtensionRunner {
444
454
  }): void;
445
455
  getPhase(name: string): Phase | undefined;
446
456
  getPhases(): Phase[];
457
+ getRegisteredPhases(): readonly RegisteredPhase[];
447
458
  createPhaseRegistry(input?: {
448
459
  entryPhaseId?: string | null;
449
460
  }): PhaseRegistry;
450
461
  /** Adapt an extension RegisteredPhase to the core Phase type. */
451
- private adaptToPhase;
462
+ adaptToPhase(reg: RegisteredPhase): Phase;
463
+ getSkills(): Skill[];
464
+ getRegisteredSkills(): readonly RegisteredSkill[];
452
465
  /**
453
466
  * Bind the runner — flushes pending provider registrations and
454
467
  * replaces runtime stubs with real implementations.
@@ -489,6 +502,7 @@ declare class ExtensionRunner {
489
502
  agentId?: string;
490
503
  toolCallId?: string;
491
504
  metadata?: Readonly<Record<string, unknown>>;
505
+ agentMetadata?: Readonly<Record<string, unknown>>;
492
506
  answer?: unknown;
493
507
  scope?: ScopeRef;
494
508
  turn?: JsonObject;
@@ -515,6 +529,10 @@ declare class ExtensionRunner {
515
529
  private registerPhaseDefinition;
516
530
  private unregisterPhase;
517
531
  private loadRegisteredPhase;
532
+ private registerSkill;
533
+ private registerSkillDefinition;
534
+ private unregisterSkill;
535
+ private loadRegisteredSkill;
518
536
  private registerProvider;
519
537
  private unregisterProvider;
520
538
  private flushPendingProviders;
@@ -588,16 +606,22 @@ declare class RuntimeExtensionLifetime {
588
606
  activate(extensions: readonly LoadedExtension[]): Promise<ExtensionActivationResult>;
589
607
  tools(): readonly RegisteredTool[];
590
608
  phases(): readonly Phase[];
609
+ registeredPhases(): readonly RegisteredPhase[];
610
+ skills(): readonly Skill[];
611
+ registeredSkills(): readonly RegisteredSkill[];
591
612
  get extensionRunner(): ExtensionRunner;
592
613
  get extensionHost(): ExtensionHost;
614
+ onResourceChange(listener: (kind: "tool" | "phase" | "skill") => Promise<void> | void): () => void;
593
615
  dropRunState(runId: string): void;
594
616
  close(): Promise<void>;
595
617
  }
596
618
  /** Bootstrap-only Registry facade; its Extension method disappears after init. */
597
619
  declare class RuntimeBootstrapRegistry extends ResourceRegistry {
598
620
  private readonly extensions;
599
- private readonly extensionSourceId;
621
+ private readonly knownSources;
622
+ private commitQueue;
600
623
  constructor(extensions?: RuntimeExtensionLifetime);
624
+ private recommit;
601
625
  get extensionHost(): ExtensionHost;
602
626
  /** Install Rowan-owned implicit resources before any host source is read. */
603
627
  ensureCoreResources(): Promise<void>;
@@ -1322,8 +1346,28 @@ interface SourceInfo {
1322
1346
  baseDir?: string;
1323
1347
  displayName?: string;
1324
1348
  }
1349
+ /** A loaded directory Bundle used by an extension. */
1350
+ type PhaseDefinition = Phase;
1325
1351
  /** Register a Phase by directory path or Phase object; PHASE.md and direct child Skills are loaded atomically when a path is supplied. */
1326
1352
  type PhaseRegistration = string | Phase;
1353
+ type RegisteredPhase = {
1354
+ definition: PhaseDefinition;
1355
+ source: {
1356
+ extensionPath: string;
1357
+ };
1358
+ sourceId: string;
1359
+ scope?: ScopeRef;
1360
+ };
1361
+ /** Register a Skill directory (containing SKILL.md) or a Skill object. Registering an existing name replaces it. */
1362
+ type SkillRegistration = string | Skill;
1363
+ type RegisteredSkill = {
1364
+ definition: Skill;
1365
+ source: {
1366
+ extensionPath: string;
1367
+ };
1368
+ sourceId: string;
1369
+ scope?: ScopeRef;
1370
+ };
1327
1371
  /**
1328
1372
  * Tool definition for registering LLM-callable tools via `api.tools.register()`.
1329
1373
  *
@@ -1381,6 +1425,8 @@ interface ToolDefinition {
1381
1425
  interface RegisteredTool {
1382
1426
  definition: ToolDefinition;
1383
1427
  sourceInfo: SourceInfo;
1428
+ sourceId: string;
1429
+ scope?: ScopeRef;
1384
1430
  }
1385
1431
  /**
1386
1432
  * Structured extension error with attribution.
@@ -1495,9 +1541,9 @@ interface ExtensionAPI {
1495
1541
  /** Tool capabilities — register and unregister custom tools. */
1496
1542
  tools: {
1497
1543
  /** Register a custom LLM-callable tool. */
1498
- register(tool: ToolDefinition): void;
1544
+ register(tool: ToolDefinition, scope?: ScopeRef): Promise<void>;
1499
1545
  /** Unregister a previously registered tool by name. */
1500
- unregister(toolName: string): void;
1546
+ unregister(toolName: string, scope?: ScopeRef): Promise<void>;
1501
1547
  };
1502
1548
  /** Capability contributions — advertise tools or features to the host. */
1503
1549
  capabilities: {
@@ -1514,9 +1560,9 @@ interface ExtensionAPI {
1514
1560
  /** Phase execution capabilities — provides Phase In/Out, phase identity, phase routing, and registration. */
1515
1561
  phases: {
1516
1562
  /** Register a Phase directory bundle or Phase object. */
1517
- register(phase: PhaseRegistration): Promise<void>;
1563
+ register(phase: PhaseRegistration, scope?: ScopeRef): Promise<void>;
1518
1564
  /** Unregister a previously registered phase by name. */
1519
- unregister(phaseName: string): void;
1565
+ unregister(phaseName: string, scope?: ScopeRef): Promise<void> | void;
1520
1566
  /** Phase In: get payload from previous phase */
1521
1567
  getPayload(): unknown;
1522
1568
  /** Phase Out: set payload for next phase */
@@ -1532,6 +1578,13 @@ interface ExtensionAPI {
1532
1578
  /** Get the message set by setMessage */
1533
1579
  getMessage(): string | undefined;
1534
1580
  };
1581
+ /** Skill capabilities — register and unregister standalone skills. */
1582
+ skills: {
1583
+ /** Register a Skill directory (containing SKILL.md) or a Skill object. Registering an existing name replaces it. */
1584
+ register(skill: SkillRegistration, scope?: ScopeRef): Promise<void>;
1585
+ /** Unregister a previously registered Skill by name. */
1586
+ unregister(skillName: string, scope?: ScopeRef): Promise<void> | void;
1587
+ };
1535
1588
  /** Extension manifest from package.json `rowan` field. */
1536
1589
  manifest?: ExtensionManifest;
1537
1590
  /** Utility functions. */
@@ -1774,6 +1827,8 @@ interface BeforeToolCallEvent {
1774
1827
  readonly agentId?: string;
1775
1828
  readonly toolCallId?: string;
1776
1829
  readonly metadata?: Readonly<Record<string, unknown>>;
1830
+ /** The invoking Agent's metadata, as the Tool's own call context carries it. */
1831
+ readonly agentMetadata?: Readonly<Record<string, unknown>>;
1777
1832
  readonly answer?: unknown;
1778
1833
  readonly scope?: ScopeRef;
1779
1834
  readonly turn?: JsonObject;
@@ -3331,4 +3386,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
3331
3386
 
3332
3387
  declare function createCoreTools(input?: CoreToolContext): Tool$1[];
3333
3388
 
3334
- export { type AfterToolCall, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, COMPACT_PHASE_ID, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type ContextCompactionRecord, type ContextStatus, type CoreToolContext, DEFAULT_PHASE_ID, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EntryPhaseSpec, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionCapability, type ExtensionCapabilityContribution, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, type ExtensionHost, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type ExtensionStateStore, type ExtensionToolContribution, type FrontmatterResult, type HistorySeed, type HookEvent, type HookEventType, type HookHandler, type HostApiExtension, InMemoryConfigProvider, InMemoryExtensionHost, InMemoryStore, type InteractionRecord, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, type LiveInteractionEvent, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type MessageRevised, type MessageRevisionResult, type Metadata, type ModelRetry, type OpaqueId, type Outcome$1 as Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecution, type PhaseExecutionIdentity, type PhaseInput, type PhaseInvocation, type PhaseMessageManager, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseState, type PhaseStatus, type PhaseStatusState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RetentionResult, type RunBoundary, type RunClaim, type RunEndEvent, type RunEvent, type RunFailure, type RunId, type RunInteraction, RunInteractionBoundary, RunInteractionCancelledError, type RunInteractionDriver, type RunInteractionKind, type RunInteractionRequest, type RunInteractionState, type RunInteractionStatus, type RunListCursor, type RunRecord, type RunSnapshot, type RunStartEvent, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, STOP_PHASE_ID, type SettingsBadge, type SettingsControl, type SettingsDefinition, type SettingsItem, type SettingsOption, type SettingsSection, type Skill, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool$1 as Tool, type ToolCallContext, type ToolCallDelta, type ToolCallId, type ToolCallPresentationInput, type ToolCallPresentationOutput, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, type WithdrawnUserInput, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, isConfigurationSnapshot, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhases, loadSkill, loadSkills, mergeToolCall, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
3389
+ export { type AfterToolCall, type AgentConfiguration, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, COMPACT_PHASE_ID, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type ConfigurationSnapshot, type ContextCandidate, type ContextCompactionRecord, type ContextStatus, type CoreToolContext, DEFAULT_PHASE_ID, type DefinitionLayer, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EntryPhaseSpec, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionActivationError, type ExtensionActivationResult, type ExtensionCapability, type ExtensionCapabilityContribution, type ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, type ExtensionHost, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type ExtensionStateStore, type ExtensionToolContribution, type FrontmatterResult, type HistorySeed, type HookEvent, type HookEventType, type HookHandler, type HostApiExtension, InMemoryConfigProvider, InMemoryExtensionHost, InMemoryStore, type InteractionRecord, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, type LiveInteractionEvent, type LoadExtensionsResult, type LoadInput, type LoadResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type MessageRevised, type MessageRevisionResult, type Metadata, type ModelRetry, type OpaqueId, type Outcome$1 as Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseContribution, type PhaseExecution, type PhaseExecutionIdentity, type PhaseInput, type PhaseInvocation, type PhaseMessageManager, type PhaseOutput, type PhaseRegistry, type PhaseRegistrySelection, type PhaseState, type PhaseStatus, type PhaseStatusState, type ResolvedResourceView, type ResourceDiagnostic, type ResourceKind, type ResourceRef, ResourceRegistry, ResourceRegistryError, type ResourceRegistryErrorCode, type ResourceSourceId, type ResourceView, type RetentionResult, type RunBoundary, type RunClaim, type RunEndEvent, type RunEvent, type RunFailure, type RunId, type RunInteraction, RunInteractionBoundary, RunInteractionCancelledError, type RunInteractionDriver, type RunInteractionKind, type RunInteractionRequest, type RunInteractionState, type RunInteractionStatus, type RunListCursor, type RunRecord, type RunSnapshot, type RunStartEvent, type RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, STOP_PHASE_ID, type SettingsBadge, type SettingsControl, type SettingsDefinition, type SettingsItem, type SettingsOption, type SettingsSection, type Skill, type SkillRegistration, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool$1 as Tool, type ToolCallContext, type ToolCallDelta, type ToolCallId, type ToolCallPresentationInput, type ToolCallPresentationOutput, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, type WithdrawnUserInput, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, extensionSourceId, isConfigurationSnapshot, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhases, loadSkill, loadSkills, mergeToolCall, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
package/dist/index.js CHANGED
@@ -1702,13 +1702,13 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1702
1702
  }
1703
1703
  },
1704
1704
  tools: {
1705
- register: (tool) => {
1705
+ register: (tool, scope) => {
1706
1706
  assertActive();
1707
- options?.registerTool?.(tool);
1707
+ return Promise.resolve(options?.registerTool?.(tool, scope));
1708
1708
  },
1709
- unregister: (toolName) => {
1709
+ unregister: (toolName, scope) => {
1710
1710
  assertActive();
1711
- options?.unregisterTool?.(toolName);
1711
+ return Promise.resolve(options?.unregisterTool?.(toolName, scope));
1712
1712
  }
1713
1713
  },
1714
1714
  capabilities: {
@@ -1762,13 +1762,13 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1762
1762
  }, emit: () => {
1763
1763
  }, has: () => false, count: () => 0 },
1764
1764
  phases: {
1765
- register: async (registration) => {
1765
+ register: async (registration, scope) => {
1766
1766
  assertActive();
1767
- await options?.registerPhase?.(registration);
1767
+ await options?.registerPhase?.(registration, scope);
1768
1768
  },
1769
- unregister: (phaseName) => {
1769
+ unregister: (phaseName, scope) => {
1770
1770
  assertActive();
1771
- options?.unregisterPhase?.(phaseName);
1771
+ return options?.unregisterPhase?.(phaseName, scope);
1772
1772
  },
1773
1773
  getPayload: () => outputPayload,
1774
1774
  setPayload: (p) => {
@@ -1783,6 +1783,16 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1783
1783
  },
1784
1784
  getNextPhase: () => nextPhase,
1785
1785
  getMessage: () => outputMessage
1786
+ },
1787
+ skills: {
1788
+ register: async (skill, scope) => {
1789
+ assertActive();
1790
+ await options?.registerSkill?.(skill, scope);
1791
+ },
1792
+ unregister: (skillName, scope) => {
1793
+ assertActive();
1794
+ return options?.unregisterSkill?.(skillName, scope);
1795
+ }
1786
1796
  }
1787
1797
  };
1788
1798
  if (options?.namespaces) {
@@ -3573,6 +3583,12 @@ async function executePhase(ctx) {
3573
3583
  const api = createExtensionAPI(void 0, {
3574
3584
  registerPhase: async () => {
3575
3585
  },
3586
+ unregisterPhase: () => {
3587
+ },
3588
+ registerSkill: async () => {
3589
+ },
3590
+ unregisterSkill: () => {
3591
+ },
3576
3592
  registerProvider: () => {
3577
3593
  },
3578
3594
  unregisterProvider: () => {
@@ -5171,6 +5187,7 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
5171
5187
  agentId: input.context.agentId,
5172
5188
  toolCallId: input.context.toolCallId,
5173
5189
  metadata: input.context.runMetadata,
5190
+ ...input.context.agentMetadata === void 0 ? {} : { agentMetadata: input.context.agentMetadata },
5174
5191
  answer: input.answer,
5175
5192
  scope: input.context.scope,
5176
5193
  turn: input.context.turn
@@ -6023,6 +6040,10 @@ function createEventBus() {
6023
6040
  }
6024
6041
 
6025
6042
  // src/extensions/runner.ts
6043
+ function extensionSourceId(scope) {
6044
+ if (!scope || scope.length === 0) return "rowan.extensions";
6045
+ return `rowan.extensions:${scopeToKey(scope)}`;
6046
+ }
6026
6047
  async function execCommand(command, args, cwd, options) {
6027
6048
  return new Promise((resolve9, reject) => {
6028
6049
  const child = execFile(
@@ -6105,6 +6126,8 @@ var ExtensionRunner = class {
6105
6126
  // Phase management
6106
6127
  phases = /* @__PURE__ */ new Map();
6107
6128
  _phaseCache = null;
6129
+ // Skill management
6130
+ skills = /* @__PURE__ */ new Map();
6108
6131
  // Provider management
6109
6132
  pendingProviders = [];
6110
6133
  bound = false;
@@ -6112,6 +6135,8 @@ var ExtensionRunner = class {
6112
6135
  errorListeners = /* @__PURE__ */ new Set();
6113
6136
  // Capability change listeners
6114
6137
  capabilityListeners = /* @__PURE__ */ new Set();
6138
+ // Resource change listeners (for Registry live sync)
6139
+ resourceChangeListeners = /* @__PURE__ */ new Set();
6115
6140
  /** Current agent context — set by the agent before each phase */
6116
6141
  currentContext;
6117
6142
  constructor(options) {
@@ -6122,7 +6147,7 @@ var ExtensionRunner = class {
6122
6147
  this.host = options?.host ?? new InMemoryExtensionHost();
6123
6148
  if (this.host.onConfigChanged) {
6124
6149
  const unsub = this.host.onConfigChanged((extensionId, scope) => {
6125
- this.notifyConfigChanged(extensionId, scope);
6150
+ void this.notifyConfigChanged(extensionId, scope);
6126
6151
  });
6127
6152
  if (typeof unsub === "function") {
6128
6153
  this.hostUnsubscribers.push(unsub);
@@ -6215,6 +6240,7 @@ var ExtensionRunner = class {
6215
6240
  path: ext.path,
6216
6241
  tools: /* @__PURE__ */ new Map(),
6217
6242
  phases: /* @__PURE__ */ new Set(),
6243
+ skills: /* @__PURE__ */ new Set(),
6218
6244
  capabilities: /* @__PURE__ */ new Map(),
6219
6245
  cleanup: [],
6220
6246
  runtime: createExtensionRuntime(),
@@ -6259,19 +6285,22 @@ var ExtensionRunner = class {
6259
6285
  }
6260
6286
  this.extensions.length = 0;
6261
6287
  this.capabilityListeners.clear();
6288
+ this.resourceChangeListeners.clear();
6262
6289
  this.phases.clear();
6263
6290
  this._phaseCache = null;
6291
+ this.skills.clear();
6292
+ this.bound = false;
6264
6293
  this.runtime.invalidate("This Extension Runtime has been closed.");
6265
6294
  }
6266
6295
  get extensionHost() {
6267
6296
  return this.host;
6268
6297
  }
6269
- notifyConfigChanged(extensionId, scope) {
6298
+ async notifyConfigChanged(extensionId, scope) {
6270
6299
  const listeners = this.configChangeListeners.get(extensionId);
6271
6300
  if (!listeners) return;
6272
6301
  for (const listener of listeners) {
6273
6302
  try {
6274
- listener(scope);
6303
+ await listener(scope);
6275
6304
  } catch (error) {
6276
6305
  this.emitError({
6277
6306
  extensionPath: "<runtime>",
@@ -6326,8 +6355,9 @@ var ExtensionRunner = class {
6326
6355
  */
6327
6356
  getToolDefinition(toolName) {
6328
6357
  for (const ext of this.extensions) {
6329
- const tool = ext.tools.get(toolName);
6330
- if (tool) return tool.definition;
6358
+ for (const tool of ext.tools.values()) {
6359
+ if (tool.definition.name === toolName) return tool.definition;
6360
+ }
6331
6361
  }
6332
6362
  return void 0;
6333
6363
  }
@@ -6357,6 +6387,20 @@ var ExtensionRunner = class {
6357
6387
  this.capabilityListeners.add(listener);
6358
6388
  return () => this.capabilityListeners.delete(listener);
6359
6389
  }
6390
+ /**
6391
+ * Subscribe to resource changes (tools, phases, skills registered, unregistered, or disposed).
6392
+ * Used by RuntimeBootstrapRegistry to re-commit implicit sources after activation.
6393
+ */
6394
+ onResourceChange(listener) {
6395
+ this.resourceChangeListeners.add(listener);
6396
+ return () => this.resourceChangeListeners.delete(listener);
6397
+ }
6398
+ async notifyResourceChange(kind) {
6399
+ if (!this.bound) return;
6400
+ for (const listener of this.resourceChangeListeners) {
6401
+ await listener(kind);
6402
+ }
6403
+ }
6360
6404
  triggerUiAction(event) {
6361
6405
  this.events.emit("ui.action", event);
6362
6406
  }
@@ -6364,20 +6408,24 @@ var ExtensionRunner = class {
6364
6408
  // Phase management
6365
6409
  // ---------------------------------------------------------------------------
6366
6410
  getPhase(name) {
6367
- const reg = this.getRegisteredPhase(name);
6368
- if (!reg) return void 0;
6369
- return this.adaptToPhase(reg);
6411
+ for (const reg of this.collectRegisteredPhases().values()) {
6412
+ if (reg.definition.name === name) return this.adaptToPhase(reg);
6413
+ }
6414
+ return void 0;
6370
6415
  }
6371
6416
  getPhases() {
6372
6417
  return [...this.collectRegisteredPhases().values()].map(
6373
6418
  (p) => this.adaptToPhase(p)
6374
6419
  );
6375
6420
  }
6421
+ getRegisteredPhases() {
6422
+ return [...this.collectRegisteredPhases().values()];
6423
+ }
6376
6424
  createPhaseRegistry(input = {}) {
6377
6425
  const registered = this.collectRegisteredPhases();
6378
6426
  const phases = /* @__PURE__ */ new Map();
6379
- for (const [name, reg] of registered) {
6380
- phases.set(name, this.adaptToPhase(reg));
6427
+ for (const reg of registered.values()) {
6428
+ phases.set(reg.definition.name, this.adaptToPhase(reg));
6381
6429
  }
6382
6430
  const entryPhaseId = input.entryPhaseId ?? null;
6383
6431
  return { phases, entryPhaseId };
@@ -6393,6 +6441,15 @@ var ExtensionRunner = class {
6393
6441
  };
6394
6442
  }
6395
6443
  // ---------------------------------------------------------------------------
6444
+ // Skill management
6445
+ // ---------------------------------------------------------------------------
6446
+ getSkills() {
6447
+ return [...this.skills.values()].map((s) => s.definition);
6448
+ }
6449
+ getRegisteredSkills() {
6450
+ return [...this.skills.values()];
6451
+ }
6452
+ // ---------------------------------------------------------------------------
6396
6453
  // Lifecycle
6397
6454
  // ---------------------------------------------------------------------------
6398
6455
  /**
@@ -6487,6 +6544,7 @@ var ExtensionRunner = class {
6487
6544
  ...context?.agentId !== void 0 ? { agentId: context.agentId } : {},
6488
6545
  ...context?.toolCallId !== void 0 ? { toolCallId: context.toolCallId } : {},
6489
6546
  ...context?.metadata !== void 0 ? { metadata: context.metadata } : {},
6547
+ ...context?.agentMetadata !== void 0 ? { agentMetadata: context.agentMetadata } : {},
6490
6548
  ...context?.answer !== void 0 ? { answer: context.answer } : {},
6491
6549
  scope,
6492
6550
  turn
@@ -6567,12 +6625,14 @@ var ExtensionRunner = class {
6567
6625
  }
6568
6626
  };
6569
6627
  return createExtensionAPI(this.hooks, {
6570
- registerPhase: (registration) => this.registerPhase(extension, registration),
6571
- unregisterPhase: (phaseName) => this.unregisterPhase(extension, phaseName),
6628
+ registerPhase: (registration, scope) => this.registerPhase(extension, registration, scope),
6629
+ unregisterPhase: (phaseName, scope) => this.unregisterPhase(extension, phaseName, scope),
6630
+ registerSkill: (registration, scope) => this.registerSkill(extension, registration, scope),
6631
+ unregisterSkill: (skillName, scope) => this.unregisterSkill(extension, skillName, scope),
6572
6632
  registerProvider: (config) => this.registerProvider(config),
6573
6633
  unregisterProvider: (name) => this.unregisterProvider(name),
6574
- registerTool: (tool) => this.registerTool(extension, tool),
6575
- unregisterTool: (toolName) => this.unregisterTool(extension, toolName),
6634
+ registerTool: (tool, scope) => this.registerTool(extension, tool, scope),
6635
+ unregisterTool: (toolName, scope) => this.unregisterTool(extension, toolName, scope),
6576
6636
  contributeCapability: (contribution) => this.contributeCapability(extension, contribution),
6577
6637
  context: extContext,
6578
6638
  manifest,
@@ -6618,12 +6678,16 @@ var ExtensionRunner = class {
6618
6678
  }
6619
6679
  }, extension.runtime, this.events);
6620
6680
  }
6621
- registerTool(extension, tool) {
6622
- if (extension.tools.has(tool.name)) {
6681
+ // Validation stays synchronous so a duplicate throws at the call site even
6682
+ // when the caller does not await the Registry re-commit.
6683
+ registerTool(extension, tool, scope) {
6684
+ const sourceId = extensionSourceId(scope);
6685
+ const regKey = `${sourceId}::${tool.name}`;
6686
+ if (extension.tools.has(regKey)) {
6623
6687
  throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
6624
6688
  }
6625
6689
  for (const ext of this.extensions) {
6626
- if (ext.tools.has(tool.name)) {
6690
+ if (ext.tools.has(regKey)) {
6627
6691
  const message = `Duplicate Tool name "${tool.name}" from extensions ${ext.path} and ${extension.path}.`;
6628
6692
  this.emitError({
6629
6693
  extensionPath: extension.path,
@@ -6634,15 +6698,19 @@ var ExtensionRunner = class {
6634
6698
  }
6635
6699
  }
6636
6700
  const sourceInfo = createSourceInfo(extension.path);
6637
- extension.tools.set(tool.name, {
6701
+ extension.tools.set(regKey, {
6638
6702
  definition: tool,
6639
- sourceInfo
6703
+ sourceInfo,
6704
+ sourceId,
6705
+ scope
6640
6706
  });
6707
+ return this.notifyResourceChange("tool");
6641
6708
  }
6642
- unregisterTool(extension, toolName) {
6643
- if (extension.tools.has(toolName)) {
6644
- extension.tools.delete(toolName);
6645
- }
6709
+ unregisterTool(extension, toolName, scope) {
6710
+ const sourceId = extensionSourceId(scope);
6711
+ const regKey = `${sourceId}::${toolName}`;
6712
+ if (!extension.tools.delete(regKey)) return Promise.resolve();
6713
+ return this.notifyResourceChange("tool");
6646
6714
  }
6647
6715
  contributeCapability(extension, contribution) {
6648
6716
  const key = `${contribution.kind}:${contribution.name}`;
@@ -6684,53 +6752,101 @@ var ExtensionRunner = class {
6684
6752
  }
6685
6753
  }
6686
6754
  }
6687
- async registerPhase(extension, registration) {
6755
+ async registerPhase(extension, registration, scope) {
6688
6756
  if (typeof registration === "string") {
6689
6757
  if (registration.length === 0) {
6690
6758
  throw new Error(`Phase registration requires a directory path.`);
6691
6759
  }
6692
- return this.loadRegisteredPhase(extension, registration);
6760
+ return this.loadRegisteredPhase(extension, registration, scope);
6693
6761
  }
6694
6762
  if (typeof registration === "object" && registration !== null && "name" in registration) {
6695
- return this.registerPhaseDefinition(extension, registration);
6763
+ return this.registerPhaseDefinition(extension, registration, scope);
6696
6764
  }
6697
6765
  throw new Error(`Phase registration requires a directory path or Phase object.`);
6698
6766
  }
6699
- registerPhaseDefinition(extension, phase) {
6767
+ async registerPhaseDefinition(extension, phase, scope) {
6768
+ const sourceId = extensionSourceId(scope);
6700
6769
  const name = phase.name;
6701
- if (this.phases.has(name)) {
6770
+ const regKey = `${sourceId}::${name}`;
6771
+ if (this.phases.has(regKey)) {
6702
6772
  throw new Error(`Duplicate phase name: ${name}`);
6703
6773
  }
6704
6774
  const registered = {
6705
6775
  definition: phase,
6706
- source: { extensionPath: extension.path }
6776
+ source: { extensionPath: extension.path },
6777
+ sourceId,
6778
+ scope
6707
6779
  };
6708
- this.phases.set(name, registered);
6709
- extension.phases.add(name);
6780
+ this.phases.set(regKey, registered);
6781
+ extension.phases.add(regKey);
6710
6782
  this._phaseCache = null;
6783
+ await this.notifyResourceChange("phase");
6711
6784
  }
6712
- unregisterPhase(extension, phaseName) {
6713
- const registered = this.phases.get(phaseName);
6785
+ async unregisterPhase(extension, phaseName, scope) {
6786
+ const sourceId = extensionSourceId(scope);
6787
+ const regKey = `${sourceId}::${phaseName}`;
6788
+ const registered = this.phases.get(regKey);
6714
6789
  if (registered && registered.source.extensionPath === extension.path) {
6715
- this.phases.delete(phaseName);
6716
- extension.phases.delete(phaseName);
6790
+ this.phases.delete(regKey);
6791
+ extension.phases.delete(regKey);
6717
6792
  this._phaseCache = null;
6793
+ await this.notifyResourceChange("phase");
6718
6794
  }
6719
6795
  }
6720
- async loadRegisteredPhase(extension, registration) {
6796
+ async loadRegisteredPhase(extension, registration, scope) {
6721
6797
  const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6722
6798
  const phase = await loadPhase(resolve5(extensionBase, registration));
6723
- const name = phase.name;
6724
- if (this.phases.has(name)) {
6725
- throw new Error(`Duplicate phase name: ${name}`);
6799
+ await this.registerPhaseDefinition(extension, phase, scope);
6800
+ }
6801
+ async registerSkill(extension, registration, scope) {
6802
+ if (typeof registration === "string") {
6803
+ if (registration.length === 0) {
6804
+ throw new Error(`Skill registration requires a directory path.`);
6805
+ }
6806
+ return this.loadRegisteredSkill(extension, registration, scope);
6807
+ }
6808
+ if (typeof registration === "object" && registration !== null && "name" in registration) {
6809
+ return this.registerSkillDefinition(extension, registration, scope);
6810
+ }
6811
+ throw new Error(`Skill registration requires a directory path or Skill object.`);
6812
+ }
6813
+ async registerSkillDefinition(extension, skill, scope) {
6814
+ const sourceId = extensionSourceId(scope);
6815
+ const name = skill.name;
6816
+ const regKey = `${sourceId}::${name}`;
6817
+ const existing = this.skills.get(regKey);
6818
+ if (existing && existing.source.extensionPath !== extension.path) {
6819
+ for (const ext of this.extensions) {
6820
+ if (ext.path === existing.source.extensionPath) {
6821
+ ext.skills.delete(regKey);
6822
+ break;
6823
+ }
6824
+ }
6726
6825
  }
6727
6826
  const registered = {
6728
- definition: phase,
6729
- source: { extensionPath: extension.path }
6827
+ definition: skill,
6828
+ source: { extensionPath: extension.path },
6829
+ sourceId,
6830
+ scope
6730
6831
  };
6731
- this.phases.set(name, registered);
6732
- extension.phases.add(name);
6733
- this._phaseCache = null;
6832
+ this.skills.set(regKey, registered);
6833
+ extension.skills.add(regKey);
6834
+ await this.notifyResourceChange("skill");
6835
+ }
6836
+ async unregisterSkill(extension, skillName, scope) {
6837
+ const sourceId = extensionSourceId(scope);
6838
+ const regKey = `${sourceId}::${skillName}`;
6839
+ const registered = this.skills.get(regKey);
6840
+ if (registered && registered.source.extensionPath === extension.path) {
6841
+ this.skills.delete(regKey);
6842
+ extension.skills.delete(regKey);
6843
+ await this.notifyResourceChange("skill");
6844
+ }
6845
+ }
6846
+ async loadRegisteredSkill(extension, registration, scope) {
6847
+ const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6848
+ const skill = await loadSkill(resolve5(extensionBase, registration));
6849
+ await this.registerSkillDefinition(extension, skill, scope);
6734
6850
  }
6735
6851
  registerProvider(config) {
6736
6852
  if (this.bound) {
@@ -6769,7 +6885,18 @@ var ExtensionRunner = class {
6769
6885
  }
6770
6886
  async disposeExtension(extension, message) {
6771
6887
  extension.runtime.invalidate(message);
6888
+ const hadTools = extension.tools.size > 0;
6889
+ const hadPhases = extension.phases.size > 0;
6890
+ const hadSkills = extension.skills.size > 0;
6891
+ extension.tools.clear();
6772
6892
  for (const name of extension.phases) this.phases.delete(name);
6893
+ extension.phases.clear();
6894
+ for (const name of extension.skills) {
6895
+ if (this.skills.get(name)?.source.extensionPath === extension.path) {
6896
+ this.skills.delete(name);
6897
+ }
6898
+ }
6899
+ extension.skills.clear();
6773
6900
  const hadCapabilities = extension.capabilities.size > 0;
6774
6901
  extension.capabilities.clear();
6775
6902
  for (const cleanup of [...extension.cleanup].reverse()) {
@@ -6787,6 +6914,11 @@ var ExtensionRunner = class {
6787
6914
  if (hadCapabilities) {
6788
6915
  this.notifyCapabilitiesChanged();
6789
6916
  }
6917
+ if (this.bound) {
6918
+ if (hadTools) await this.notifyResourceChange("tool");
6919
+ if (hadPhases) await this.notifyResourceChange("phase");
6920
+ if (hadSkills) await this.notifyResourceChange("skill");
6921
+ }
6790
6922
  }
6791
6923
  };
6792
6924
  function createExtensionRunner(options) {
@@ -6963,7 +7095,7 @@ var ResourceRegistryError = class extends Error {
6963
7095
  }
6964
7096
  };
6965
7097
  var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
6966
- var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
7098
+ var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
6967
7099
  var ResourceRegistry = class {
6968
7100
  sources = /* @__PURE__ */ new Map();
6969
7101
  /** Sources registered by the bootstrap boundary (currently Extensions). */
@@ -7056,15 +7188,7 @@ var ResourceRegistry = class {
7056
7188
  }
7057
7189
  withImplicit(kind, sourceIds) {
7058
7190
  const combined = [...sourceIds];
7059
- let hasCore = false;
7060
- for (const sourceId of this.implicitSources.get(kind) ?? []) {
7061
- if (sourceId === "rowan.core") {
7062
- hasCore = true;
7063
- } else if (!combined.includes(sourceId)) {
7064
- combined.push(sourceId);
7065
- }
7066
- }
7067
- if (hasCore && !combined.includes("rowan.core")) {
7191
+ if (this.hasImplicit(kind, "rowan.core") && !combined.includes("rowan.core")) {
7068
7192
  combined.push("rowan.core");
7069
7193
  }
7070
7194
  return combined;
@@ -7139,9 +7263,6 @@ var ResourceRegistry = class {
7139
7263
  if (source.sourceId === "rowan.core") {
7140
7264
  continue;
7141
7265
  }
7142
- if (source.sourceId === "rowan.extensions" && isCoreResourceName(kind, value.name)) {
7143
- continue;
7144
- }
7145
7266
  throw new ResourceRegistryError(
7146
7267
  "resource_collision",
7147
7268
  `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
@@ -7165,9 +7286,6 @@ var ResourceRegistry = class {
7165
7286
  if (source.sourceId === "rowan.core") {
7166
7287
  continue;
7167
7288
  }
7168
- if (source.sourceId === "rowan.extensions" && isCoreResourceName(kind, value.name)) {
7169
- continue;
7170
- }
7171
7289
  throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
7172
7290
  }
7173
7291
  seenNames.add(value.name);
@@ -7186,10 +7304,7 @@ var ResourceRegistry = class {
7186
7304
  seen.add(sourceId);
7187
7305
  const source = bySource?.get(sourceId);
7188
7306
  if (!source) {
7189
- throw new ResourceRegistryError(
7190
- "unknown_resource_source",
7191
- `Unknown resource source "${sourceId}" for ${kind}.`
7192
- );
7307
+ continue;
7193
7308
  }
7194
7309
  sources.push(source);
7195
7310
  }
@@ -7241,9 +7356,6 @@ function capitalize(value) {
7241
7356
  function isImplicitCoreName(kind, name) {
7242
7357
  return kind === "tool" && name === "route";
7243
7358
  }
7244
- function isCoreResourceName(kind, name) {
7245
- return kind === "tool" && CORE_TOOL_NAMES.has(name) || kind === "phase" && CORE_PHASE_NAMES.has(name);
7246
- }
7247
7359
  async function loadDirectoryValues(kind, directory, sourceId) {
7248
7360
  const root = resolve7(directory);
7249
7361
  let rootInfo;
@@ -7407,12 +7519,24 @@ var RuntimeExtensionLifetime = class {
7407
7519
  phases() {
7408
7520
  return this.runner.getPhases();
7409
7521
  }
7522
+ registeredPhases() {
7523
+ return this.runner.getRegisteredPhases();
7524
+ }
7525
+ skills() {
7526
+ return this.runner.getSkills();
7527
+ }
7528
+ registeredSkills() {
7529
+ return this.runner.getRegisteredSkills();
7530
+ }
7410
7531
  get extensionRunner() {
7411
7532
  return this.runner;
7412
7533
  }
7413
7534
  get extensionHost() {
7414
7535
  return this.runner.extensionHost;
7415
7536
  }
7537
+ onResourceChange(listener) {
7538
+ return this.runner.onResourceChange(listener);
7539
+ }
7416
7540
  dropRunState(runId) {
7417
7541
  this.runner.dropRunState(runId);
7418
7542
  }
@@ -7426,9 +7550,33 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7426
7550
  constructor(extensions = new RuntimeExtensionLifetime()) {
7427
7551
  super();
7428
7552
  this.extensions = extensions;
7553
+ this.extensions.onResourceChange((kind) => this.recommit(kind));
7429
7554
  }
7430
7555
  extensions;
7431
- extensionSourceId = "rowan.extensions";
7556
+ knownSources = /* @__PURE__ */ new Map();
7557
+ commitQueue = Promise.resolve();
7558
+ recommit(kind) {
7559
+ const next = this.commitQueue.then(async () => {
7560
+ const entries = kind === "tool" ? this.extensions.tools().map((tool) => [tool.sourceId, adaptExtensionTool(tool)]) : kind === "phase" ? this.extensions.registeredPhases().map((reg) => [reg.sourceId, this.extensions.extensionRunner.adaptToPhase(reg)]) : this.extensions.registeredSkills().map((reg) => [reg.sourceId, reg.definition]);
7561
+ const bySource = /* @__PURE__ */ new Map();
7562
+ for (const [sourceId, value] of entries) bySource.set(sourceId, [...bySource.get(sourceId) ?? [], value]);
7563
+ const known = this.knownSources.get(kind) ?? /* @__PURE__ */ new Set();
7564
+ this.knownSources.set(kind, known);
7565
+ for (const [sourceId, values] of bySource) {
7566
+ known.add(sourceId);
7567
+ if (kind === "tool") await this.loadTools({ sourceId, values });
7568
+ else if (kind === "phase") await this.loadPhases({ sourceId, values });
7569
+ else await this.loadSkills({ sourceId, values });
7570
+ }
7571
+ for (const sourceId of [...known]) {
7572
+ if (bySource.has(sourceId)) continue;
7573
+ known.delete(sourceId);
7574
+ await this.unload({ kind, sourceId });
7575
+ }
7576
+ });
7577
+ this.commitQueue = next.catch(() => void 0);
7578
+ return next;
7579
+ }
7432
7580
  get extensionHost() {
7433
7581
  return this.extensions.extensionHost;
7434
7582
  }
@@ -7444,8 +7592,9 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7444
7592
  })();
7445
7593
  const normalized = await loaded;
7446
7594
  const result = await this.extensions.activate(normalized.extensions);
7447
- await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool));
7448
- await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
7595
+ await this.recommit("tool");
7596
+ await this.recommit("phase");
7597
+ await this.recommit("skill");
7449
7598
  return {
7450
7599
  ...result,
7451
7600
  revision: `extensions:${Date.now()}`,
@@ -12984,6 +13133,7 @@ export {
12984
13133
  createCoreTools2 as createCoreTools,
12985
13134
  createDefaultPhase,
12986
13135
  createStopPhase,
13136
+ extensionSourceId,
12987
13137
  isConfigurationSnapshot,
12988
13138
  isRuntimeError,
12989
13139
  loadExtensionsFromPath as loadExtensions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",