@rowan-agent/agent 0.22.0 → 0.23.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
@@ -368,10 +368,12 @@ declare class ExtensionRunner {
368
368
  private readonly hostUnsubscribers;
369
369
  private readonly phases;
370
370
  private _phaseCache;
371
+ private readonly skills;
371
372
  private readonly pendingProviders;
372
373
  private bound;
373
374
  private readonly errorListeners;
374
375
  private readonly capabilityListeners;
376
+ private readonly resourceChangeListeners;
375
377
  /** Current agent context — set by the agent before each phase */
376
378
  currentContext?: AgentContext;
377
379
  constructor(options?: ExtensionRunnerOptions);
@@ -419,7 +421,7 @@ declare class ExtensionRunner {
419
421
  /** Dispose all active Extensions in reverse activation order. */
420
422
  close(): Promise<void>;
421
423
  get extensionHost(): ExtensionHost;
422
- notifyConfigChanged(extensionId: string, scope: ScopeRef): void;
424
+ notifyConfigChanged(extensionId: string, scope: ScopeRef): Promise<void>;
423
425
  getRunState(runId: string, extensionId: string, key: string): JsonValue | undefined;
424
426
  setRunState(runId: string, extensionId: string, key: string, value: JsonValue): void;
425
427
  deleteRunState(runId: string, extensionId: string, key: string): void;
@@ -437,6 +439,12 @@ declare class ExtensionRunner {
437
439
  * Returns an unsubscribe function.
438
440
  */
439
441
  onCapabilitiesChanged(listener: (capabilities: readonly ExtensionCapability[]) => void): () => void;
442
+ /**
443
+ * Subscribe to resource changes (tools, phases, skills registered, unregistered, or disposed).
444
+ * Used by RuntimeBootstrapRegistry to re-commit implicit sources after activation.
445
+ */
446
+ onResourceChange(listener: (kind: "tool" | "phase" | "skill") => Promise<void> | void): () => void;
447
+ private notifyResourceChange;
440
448
  triggerUiAction(event: {
441
449
  contributionId: string;
442
450
  actionId: string;
@@ -449,6 +457,7 @@ declare class ExtensionRunner {
449
457
  }): PhaseRegistry;
450
458
  /** Adapt an extension RegisteredPhase to the core Phase type. */
451
459
  private adaptToPhase;
460
+ getSkills(): Skill[];
452
461
  /**
453
462
  * Bind the runner — flushes pending provider registrations and
454
463
  * replaces runtime stubs with real implementations.
@@ -515,6 +524,10 @@ declare class ExtensionRunner {
515
524
  private registerPhaseDefinition;
516
525
  private unregisterPhase;
517
526
  private loadRegisteredPhase;
527
+ private registerSkill;
528
+ private registerSkillDefinition;
529
+ private unregisterSkill;
530
+ private loadRegisteredSkill;
518
531
  private registerProvider;
519
532
  private unregisterProvider;
520
533
  private flushPendingProviders;
@@ -588,8 +601,10 @@ declare class RuntimeExtensionLifetime {
588
601
  activate(extensions: readonly LoadedExtension[]): Promise<ExtensionActivationResult>;
589
602
  tools(): readonly RegisteredTool[];
590
603
  phases(): readonly Phase[];
604
+ skills(): readonly Skill[];
591
605
  get extensionRunner(): ExtensionRunner;
592
606
  get extensionHost(): ExtensionHost;
607
+ onResourceChange(listener: (kind: "tool" | "phase" | "skill") => Promise<void> | void): () => void;
593
608
  dropRunState(runId: string): void;
594
609
  close(): Promise<void>;
595
610
  }
@@ -597,7 +612,9 @@ declare class RuntimeExtensionLifetime {
597
612
  declare class RuntimeBootstrapRegistry extends ResourceRegistry {
598
613
  private readonly extensions;
599
614
  private readonly extensionSourceId;
615
+ private commitQueue;
600
616
  constructor(extensions?: RuntimeExtensionLifetime);
617
+ private recommit;
601
618
  get extensionHost(): ExtensionHost;
602
619
  /** Install Rowan-owned implicit resources before any host source is read. */
603
620
  ensureCoreResources(): Promise<void>;
@@ -1324,6 +1341,8 @@ interface SourceInfo {
1324
1341
  }
1325
1342
  /** Register a Phase by directory path or Phase object; PHASE.md and direct child Skills are loaded atomically when a path is supplied. */
1326
1343
  type PhaseRegistration = string | Phase;
1344
+ /** Register a Skill directory (containing SKILL.md) or a Skill object. Registering an existing name replaces it. */
1345
+ type SkillRegistration = string | Skill;
1327
1346
  /**
1328
1347
  * Tool definition for registering LLM-callable tools via `api.tools.register()`.
1329
1348
  *
@@ -1495,9 +1514,9 @@ interface ExtensionAPI {
1495
1514
  /** Tool capabilities — register and unregister custom tools. */
1496
1515
  tools: {
1497
1516
  /** Register a custom LLM-callable tool. */
1498
- register(tool: ToolDefinition): void;
1517
+ register(tool: ToolDefinition): Promise<void>;
1499
1518
  /** Unregister a previously registered tool by name. */
1500
- unregister(toolName: string): void;
1519
+ unregister(toolName: string): Promise<void>;
1501
1520
  };
1502
1521
  /** Capability contributions — advertise tools or features to the host. */
1503
1522
  capabilities: {
@@ -1532,6 +1551,13 @@ interface ExtensionAPI {
1532
1551
  /** Get the message set by setMessage */
1533
1552
  getMessage(): string | undefined;
1534
1553
  };
1554
+ /** Skill capabilities — register and unregister standalone skills. */
1555
+ skills: {
1556
+ /** Register a Skill directory (containing SKILL.md) or a Skill object. Registering an existing name replaces it. */
1557
+ register(skill: SkillRegistration): Promise<void>;
1558
+ /** Unregister a previously registered Skill by name. */
1559
+ unregister(skillName: string): void;
1560
+ };
1535
1561
  /** Extension manifest from package.json `rowan` field. */
1536
1562
  manifest?: ExtensionManifest;
1537
1563
  /** Utility functions. */
@@ -3331,4 +3357,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
3331
3357
 
3332
3358
  declare function createCoreTools(input?: CoreToolContext): Tool$1[];
3333
3359
 
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 };
3360
+ 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, isConfigurationSnapshot, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhases, loadSkill, loadSkills, mergeToolCall, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
package/dist/index.js CHANGED
@@ -1704,11 +1704,11 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1704
1704
  tools: {
1705
1705
  register: (tool) => {
1706
1706
  assertActive();
1707
- options?.registerTool?.(tool);
1707
+ return Promise.resolve(options?.registerTool?.(tool));
1708
1708
  },
1709
1709
  unregister: (toolName) => {
1710
1710
  assertActive();
1711
- options?.unregisterTool?.(toolName);
1711
+ return Promise.resolve(options?.unregisterTool?.(toolName));
1712
1712
  }
1713
1713
  },
1714
1714
  capabilities: {
@@ -1768,7 +1768,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1768
1768
  },
1769
1769
  unregister: (phaseName) => {
1770
1770
  assertActive();
1771
- options?.unregisterPhase?.(phaseName);
1771
+ return options?.unregisterPhase?.(phaseName);
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) => {
1789
+ assertActive();
1790
+ await options?.registerSkill?.(skill);
1791
+ },
1792
+ unregister: (skillName) => {
1793
+ assertActive();
1794
+ return options?.unregisterSkill?.(skillName);
1795
+ }
1786
1796
  }
1787
1797
  };
1788
1798
  if (options?.namespaces) {
@@ -3502,12 +3512,10 @@ var DEFAULT_RETRY_DELAY_MS = 5e3;
3502
3512
  function isRetryableError(error) {
3503
3513
  if (!(error instanceof Error)) return false;
3504
3514
  const message = error.message.toLowerCase();
3505
- if (message.includes("rate limit") || message.includes("429")) return true;
3506
- if (message.includes("overloaded") || message.includes("529")) return true;
3507
- if (message.includes("server error") || message.includes("500")) return true;
3508
- if (message.includes("bad gateway") || message.includes("502")) return true;
3509
- if (message.includes("service unavailable") || message.includes("503")) return true;
3510
- if (message.includes("gateway timeout") || message.includes("504")) return true;
3515
+ if (/(?<![\d:])(429|500|502|503|504|529)(?!\d)/.test(message)) return true;
3516
+ if (message.includes("rate limit") || message.includes("overloaded")) return true;
3517
+ if (message.includes("server error") || message.includes("bad gateway")) return true;
3518
+ if (message.includes("service unavailable") || message.includes("gateway timeout")) return true;
3511
3519
  if (message.includes("econnreset") || message.includes("econnrefused")) return true;
3512
3520
  if (message.includes("socket connection was closed") || message.includes("socket hang up")) return true;
3513
3521
  if (message.includes("fetch failed") || message.includes("etimedout")) return true;
@@ -3575,6 +3583,12 @@ async function executePhase(ctx) {
3575
3583
  const api = createExtensionAPI(void 0, {
3576
3584
  registerPhase: async () => {
3577
3585
  },
3586
+ unregisterPhase: () => {
3587
+ },
3588
+ registerSkill: async () => {
3589
+ },
3590
+ unregisterSkill: () => {
3591
+ },
3578
3592
  registerProvider: () => {
3579
3593
  },
3580
3594
  unregisterProvider: () => {
@@ -6107,6 +6121,8 @@ var ExtensionRunner = class {
6107
6121
  // Phase management
6108
6122
  phases = /* @__PURE__ */ new Map();
6109
6123
  _phaseCache = null;
6124
+ // Skill management
6125
+ skills = /* @__PURE__ */ new Map();
6110
6126
  // Provider management
6111
6127
  pendingProviders = [];
6112
6128
  bound = false;
@@ -6114,6 +6130,8 @@ var ExtensionRunner = class {
6114
6130
  errorListeners = /* @__PURE__ */ new Set();
6115
6131
  // Capability change listeners
6116
6132
  capabilityListeners = /* @__PURE__ */ new Set();
6133
+ // Resource change listeners (for Registry live sync)
6134
+ resourceChangeListeners = /* @__PURE__ */ new Set();
6117
6135
  /** Current agent context — set by the agent before each phase */
6118
6136
  currentContext;
6119
6137
  constructor(options) {
@@ -6124,7 +6142,7 @@ var ExtensionRunner = class {
6124
6142
  this.host = options?.host ?? new InMemoryExtensionHost();
6125
6143
  if (this.host.onConfigChanged) {
6126
6144
  const unsub = this.host.onConfigChanged((extensionId, scope) => {
6127
- this.notifyConfigChanged(extensionId, scope);
6145
+ void this.notifyConfigChanged(extensionId, scope);
6128
6146
  });
6129
6147
  if (typeof unsub === "function") {
6130
6148
  this.hostUnsubscribers.push(unsub);
@@ -6217,6 +6235,7 @@ var ExtensionRunner = class {
6217
6235
  path: ext.path,
6218
6236
  tools: /* @__PURE__ */ new Map(),
6219
6237
  phases: /* @__PURE__ */ new Set(),
6238
+ skills: /* @__PURE__ */ new Set(),
6220
6239
  capabilities: /* @__PURE__ */ new Map(),
6221
6240
  cleanup: [],
6222
6241
  runtime: createExtensionRuntime(),
@@ -6261,19 +6280,22 @@ var ExtensionRunner = class {
6261
6280
  }
6262
6281
  this.extensions.length = 0;
6263
6282
  this.capabilityListeners.clear();
6283
+ this.resourceChangeListeners.clear();
6264
6284
  this.phases.clear();
6265
6285
  this._phaseCache = null;
6286
+ this.skills.clear();
6287
+ this.bound = false;
6266
6288
  this.runtime.invalidate("This Extension Runtime has been closed.");
6267
6289
  }
6268
6290
  get extensionHost() {
6269
6291
  return this.host;
6270
6292
  }
6271
- notifyConfigChanged(extensionId, scope) {
6293
+ async notifyConfigChanged(extensionId, scope) {
6272
6294
  const listeners = this.configChangeListeners.get(extensionId);
6273
6295
  if (!listeners) return;
6274
6296
  for (const listener of listeners) {
6275
6297
  try {
6276
- listener(scope);
6298
+ await listener(scope);
6277
6299
  } catch (error) {
6278
6300
  this.emitError({
6279
6301
  extensionPath: "<runtime>",
@@ -6359,6 +6381,20 @@ var ExtensionRunner = class {
6359
6381
  this.capabilityListeners.add(listener);
6360
6382
  return () => this.capabilityListeners.delete(listener);
6361
6383
  }
6384
+ /**
6385
+ * Subscribe to resource changes (tools, phases, skills registered, unregistered, or disposed).
6386
+ * Used by RuntimeBootstrapRegistry to re-commit implicit sources after activation.
6387
+ */
6388
+ onResourceChange(listener) {
6389
+ this.resourceChangeListeners.add(listener);
6390
+ return () => this.resourceChangeListeners.delete(listener);
6391
+ }
6392
+ async notifyResourceChange(kind) {
6393
+ if (!this.bound) return;
6394
+ for (const listener of this.resourceChangeListeners) {
6395
+ await listener(kind);
6396
+ }
6397
+ }
6362
6398
  triggerUiAction(event) {
6363
6399
  this.events.emit("ui.action", event);
6364
6400
  }
@@ -6395,6 +6431,12 @@ var ExtensionRunner = class {
6395
6431
  };
6396
6432
  }
6397
6433
  // ---------------------------------------------------------------------------
6434
+ // Skill management
6435
+ // ---------------------------------------------------------------------------
6436
+ getSkills() {
6437
+ return [...this.skills.values()].map((s) => s.definition);
6438
+ }
6439
+ // ---------------------------------------------------------------------------
6398
6440
  // Lifecycle
6399
6441
  // ---------------------------------------------------------------------------
6400
6442
  /**
@@ -6571,6 +6613,8 @@ var ExtensionRunner = class {
6571
6613
  return createExtensionAPI(this.hooks, {
6572
6614
  registerPhase: (registration) => this.registerPhase(extension, registration),
6573
6615
  unregisterPhase: (phaseName) => this.unregisterPhase(extension, phaseName),
6616
+ registerSkill: (registration) => this.registerSkill(extension, registration),
6617
+ unregisterSkill: (skillName) => this.unregisterSkill(extension, skillName),
6574
6618
  registerProvider: (config) => this.registerProvider(config),
6575
6619
  unregisterProvider: (name) => this.unregisterProvider(name),
6576
6620
  registerTool: (tool) => this.registerTool(extension, tool),
@@ -6620,6 +6664,8 @@ var ExtensionRunner = class {
6620
6664
  }
6621
6665
  }, extension.runtime, this.events);
6622
6666
  }
6667
+ // Validation stays synchronous so a duplicate throws at the call site even
6668
+ // when the caller does not await the Registry re-commit.
6623
6669
  registerTool(extension, tool) {
6624
6670
  if (extension.tools.has(tool.name)) {
6625
6671
  throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
@@ -6640,11 +6686,11 @@ var ExtensionRunner = class {
6640
6686
  definition: tool,
6641
6687
  sourceInfo
6642
6688
  });
6689
+ return this.notifyResourceChange("tool");
6643
6690
  }
6644
6691
  unregisterTool(extension, toolName) {
6645
- if (extension.tools.has(toolName)) {
6646
- extension.tools.delete(toolName);
6647
- }
6692
+ if (!extension.tools.delete(toolName)) return Promise.resolve();
6693
+ return this.notifyResourceChange("tool");
6648
6694
  }
6649
6695
  contributeCapability(extension, contribution) {
6650
6696
  const key = `${contribution.kind}:${contribution.name}`;
@@ -6698,7 +6744,7 @@ var ExtensionRunner = class {
6698
6744
  }
6699
6745
  throw new Error(`Phase registration requires a directory path or Phase object.`);
6700
6746
  }
6701
- registerPhaseDefinition(extension, phase) {
6747
+ async registerPhaseDefinition(extension, phase) {
6702
6748
  const name = phase.name;
6703
6749
  if (this.phases.has(name)) {
6704
6750
  throw new Error(`Duplicate phase name: ${name}`);
@@ -6710,29 +6756,65 @@ var ExtensionRunner = class {
6710
6756
  this.phases.set(name, registered);
6711
6757
  extension.phases.add(name);
6712
6758
  this._phaseCache = null;
6759
+ await this.notifyResourceChange("phase");
6713
6760
  }
6714
- unregisterPhase(extension, phaseName) {
6761
+ async unregisterPhase(extension, phaseName) {
6715
6762
  const registered = this.phases.get(phaseName);
6716
6763
  if (registered && registered.source.extensionPath === extension.path) {
6717
6764
  this.phases.delete(phaseName);
6718
6765
  extension.phases.delete(phaseName);
6719
6766
  this._phaseCache = null;
6767
+ await this.notifyResourceChange("phase");
6720
6768
  }
6721
6769
  }
6722
6770
  async loadRegisteredPhase(extension, registration) {
6723
6771
  const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6724
6772
  const phase = await loadPhase(resolve5(extensionBase, registration));
6725
- const name = phase.name;
6726
- if (this.phases.has(name)) {
6727
- throw new Error(`Duplicate phase name: ${name}`);
6773
+ await this.registerPhaseDefinition(extension, phase);
6774
+ }
6775
+ async registerSkill(extension, registration) {
6776
+ if (typeof registration === "string") {
6777
+ if (registration.length === 0) {
6778
+ throw new Error(`Skill registration requires a directory path.`);
6779
+ }
6780
+ return this.loadRegisteredSkill(extension, registration);
6781
+ }
6782
+ if (typeof registration === "object" && registration !== null && "name" in registration) {
6783
+ return this.registerSkillDefinition(extension, registration);
6784
+ }
6785
+ throw new Error(`Skill registration requires a directory path or Skill object.`);
6786
+ }
6787
+ async registerSkillDefinition(extension, skill) {
6788
+ const name = skill.name;
6789
+ const existing = this.skills.get(name);
6790
+ if (existing && existing.source.extensionPath !== extension.path) {
6791
+ for (const ext of this.extensions) {
6792
+ if (ext.path === existing.source.extensionPath) {
6793
+ ext.skills.delete(name);
6794
+ break;
6795
+ }
6796
+ }
6728
6797
  }
6729
6798
  const registered = {
6730
- definition: phase,
6799
+ definition: skill,
6731
6800
  source: { extensionPath: extension.path }
6732
6801
  };
6733
- this.phases.set(name, registered);
6734
- extension.phases.add(name);
6735
- this._phaseCache = null;
6802
+ this.skills.set(name, registered);
6803
+ extension.skills.add(name);
6804
+ await this.notifyResourceChange("skill");
6805
+ }
6806
+ async unregisterSkill(extension, skillName) {
6807
+ const registered = this.skills.get(skillName);
6808
+ if (registered && registered.source.extensionPath === extension.path) {
6809
+ this.skills.delete(skillName);
6810
+ extension.skills.delete(skillName);
6811
+ await this.notifyResourceChange("skill");
6812
+ }
6813
+ }
6814
+ async loadRegisteredSkill(extension, registration) {
6815
+ const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6816
+ const skill = await loadSkill(resolve5(extensionBase, registration));
6817
+ await this.registerSkillDefinition(extension, skill);
6736
6818
  }
6737
6819
  registerProvider(config) {
6738
6820
  if (this.bound) {
@@ -6771,7 +6853,18 @@ var ExtensionRunner = class {
6771
6853
  }
6772
6854
  async disposeExtension(extension, message) {
6773
6855
  extension.runtime.invalidate(message);
6856
+ const hadTools = extension.tools.size > 0;
6857
+ const hadPhases = extension.phases.size > 0;
6858
+ const hadSkills = extension.skills.size > 0;
6859
+ extension.tools.clear();
6774
6860
  for (const name of extension.phases) this.phases.delete(name);
6861
+ extension.phases.clear();
6862
+ for (const name of extension.skills) {
6863
+ if (this.skills.get(name)?.source.extensionPath === extension.path) {
6864
+ this.skills.delete(name);
6865
+ }
6866
+ }
6867
+ extension.skills.clear();
6775
6868
  const hadCapabilities = extension.capabilities.size > 0;
6776
6869
  extension.capabilities.clear();
6777
6870
  for (const cleanup of [...extension.cleanup].reverse()) {
@@ -6789,6 +6882,11 @@ var ExtensionRunner = class {
6789
6882
  if (hadCapabilities) {
6790
6883
  this.notifyCapabilitiesChanged();
6791
6884
  }
6885
+ if (this.bound) {
6886
+ if (hadTools) await this.notifyResourceChange("tool");
6887
+ if (hadPhases) await this.notifyResourceChange("phase");
6888
+ if (hadSkills) await this.notifyResourceChange("skill");
6889
+ }
6792
6890
  }
6793
6891
  };
6794
6892
  function createExtensionRunner(options) {
@@ -7409,12 +7507,18 @@ var RuntimeExtensionLifetime = class {
7409
7507
  phases() {
7410
7508
  return this.runner.getPhases();
7411
7509
  }
7510
+ skills() {
7511
+ return this.runner.getSkills();
7512
+ }
7412
7513
  get extensionRunner() {
7413
7514
  return this.runner;
7414
7515
  }
7415
7516
  get extensionHost() {
7416
7517
  return this.runner.extensionHost;
7417
7518
  }
7519
+ onResourceChange(listener) {
7520
+ return this.runner.onResourceChange(listener);
7521
+ }
7418
7522
  dropRunState(runId) {
7419
7523
  this.runner.dropRunState(runId);
7420
7524
  }
@@ -7428,9 +7532,24 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7428
7532
  constructor(extensions = new RuntimeExtensionLifetime()) {
7429
7533
  super();
7430
7534
  this.extensions = extensions;
7535
+ this.extensions.onResourceChange((kind) => this.recommit(kind));
7431
7536
  }
7432
7537
  extensions;
7433
7538
  extensionSourceId = "rowan.extensions";
7539
+ commitQueue = Promise.resolve();
7540
+ recommit(kind) {
7541
+ const next = this.commitQueue.then(async () => {
7542
+ if (kind === "tool") {
7543
+ await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool));
7544
+ } else if (kind === "phase") {
7545
+ await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
7546
+ } else if (kind === "skill") {
7547
+ await this.replaceImplicit("skill", this.extensionSourceId, [...this.extensions.skills()]);
7548
+ }
7549
+ });
7550
+ this.commitQueue = next.catch(() => void 0);
7551
+ return next;
7552
+ }
7434
7553
  get extensionHost() {
7435
7554
  return this.extensions.extensionHost;
7436
7555
  }
@@ -7446,8 +7565,9 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7446
7565
  })();
7447
7566
  const normalized = await loaded;
7448
7567
  const result = await this.extensions.activate(normalized.extensions);
7449
- await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool));
7450
- await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
7568
+ await this.recommit("tool");
7569
+ await this.recommit("phase");
7570
+ await this.recommit("skill");
7451
7571
  return {
7452
7572
  ...result,
7453
7573
  revision: `extensions:${Date.now()}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",