@rowan-agent/agent 0.22.1 → 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) {
@@ -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: () => {
@@ -6105,6 +6121,8 @@ var ExtensionRunner = class {
6105
6121
  // Phase management
6106
6122
  phases = /* @__PURE__ */ new Map();
6107
6123
  _phaseCache = null;
6124
+ // Skill management
6125
+ skills = /* @__PURE__ */ new Map();
6108
6126
  // Provider management
6109
6127
  pendingProviders = [];
6110
6128
  bound = false;
@@ -6112,6 +6130,8 @@ var ExtensionRunner = class {
6112
6130
  errorListeners = /* @__PURE__ */ new Set();
6113
6131
  // Capability change listeners
6114
6132
  capabilityListeners = /* @__PURE__ */ new Set();
6133
+ // Resource change listeners (for Registry live sync)
6134
+ resourceChangeListeners = /* @__PURE__ */ new Set();
6115
6135
  /** Current agent context — set by the agent before each phase */
6116
6136
  currentContext;
6117
6137
  constructor(options) {
@@ -6122,7 +6142,7 @@ var ExtensionRunner = class {
6122
6142
  this.host = options?.host ?? new InMemoryExtensionHost();
6123
6143
  if (this.host.onConfigChanged) {
6124
6144
  const unsub = this.host.onConfigChanged((extensionId, scope) => {
6125
- this.notifyConfigChanged(extensionId, scope);
6145
+ void this.notifyConfigChanged(extensionId, scope);
6126
6146
  });
6127
6147
  if (typeof unsub === "function") {
6128
6148
  this.hostUnsubscribers.push(unsub);
@@ -6215,6 +6235,7 @@ var ExtensionRunner = class {
6215
6235
  path: ext.path,
6216
6236
  tools: /* @__PURE__ */ new Map(),
6217
6237
  phases: /* @__PURE__ */ new Set(),
6238
+ skills: /* @__PURE__ */ new Set(),
6218
6239
  capabilities: /* @__PURE__ */ new Map(),
6219
6240
  cleanup: [],
6220
6241
  runtime: createExtensionRuntime(),
@@ -6259,19 +6280,22 @@ var ExtensionRunner = class {
6259
6280
  }
6260
6281
  this.extensions.length = 0;
6261
6282
  this.capabilityListeners.clear();
6283
+ this.resourceChangeListeners.clear();
6262
6284
  this.phases.clear();
6263
6285
  this._phaseCache = null;
6286
+ this.skills.clear();
6287
+ this.bound = false;
6264
6288
  this.runtime.invalidate("This Extension Runtime has been closed.");
6265
6289
  }
6266
6290
  get extensionHost() {
6267
6291
  return this.host;
6268
6292
  }
6269
- notifyConfigChanged(extensionId, scope) {
6293
+ async notifyConfigChanged(extensionId, scope) {
6270
6294
  const listeners = this.configChangeListeners.get(extensionId);
6271
6295
  if (!listeners) return;
6272
6296
  for (const listener of listeners) {
6273
6297
  try {
6274
- listener(scope);
6298
+ await listener(scope);
6275
6299
  } catch (error) {
6276
6300
  this.emitError({
6277
6301
  extensionPath: "<runtime>",
@@ -6357,6 +6381,20 @@ var ExtensionRunner = class {
6357
6381
  this.capabilityListeners.add(listener);
6358
6382
  return () => this.capabilityListeners.delete(listener);
6359
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
+ }
6360
6398
  triggerUiAction(event) {
6361
6399
  this.events.emit("ui.action", event);
6362
6400
  }
@@ -6393,6 +6431,12 @@ var ExtensionRunner = class {
6393
6431
  };
6394
6432
  }
6395
6433
  // ---------------------------------------------------------------------------
6434
+ // Skill management
6435
+ // ---------------------------------------------------------------------------
6436
+ getSkills() {
6437
+ return [...this.skills.values()].map((s) => s.definition);
6438
+ }
6439
+ // ---------------------------------------------------------------------------
6396
6440
  // Lifecycle
6397
6441
  // ---------------------------------------------------------------------------
6398
6442
  /**
@@ -6569,6 +6613,8 @@ var ExtensionRunner = class {
6569
6613
  return createExtensionAPI(this.hooks, {
6570
6614
  registerPhase: (registration) => this.registerPhase(extension, registration),
6571
6615
  unregisterPhase: (phaseName) => this.unregisterPhase(extension, phaseName),
6616
+ registerSkill: (registration) => this.registerSkill(extension, registration),
6617
+ unregisterSkill: (skillName) => this.unregisterSkill(extension, skillName),
6572
6618
  registerProvider: (config) => this.registerProvider(config),
6573
6619
  unregisterProvider: (name) => this.unregisterProvider(name),
6574
6620
  registerTool: (tool) => this.registerTool(extension, tool),
@@ -6618,6 +6664,8 @@ var ExtensionRunner = class {
6618
6664
  }
6619
6665
  }, extension.runtime, this.events);
6620
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.
6621
6669
  registerTool(extension, tool) {
6622
6670
  if (extension.tools.has(tool.name)) {
6623
6671
  throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
@@ -6638,11 +6686,11 @@ var ExtensionRunner = class {
6638
6686
  definition: tool,
6639
6687
  sourceInfo
6640
6688
  });
6689
+ return this.notifyResourceChange("tool");
6641
6690
  }
6642
6691
  unregisterTool(extension, toolName) {
6643
- if (extension.tools.has(toolName)) {
6644
- extension.tools.delete(toolName);
6645
- }
6692
+ if (!extension.tools.delete(toolName)) return Promise.resolve();
6693
+ return this.notifyResourceChange("tool");
6646
6694
  }
6647
6695
  contributeCapability(extension, contribution) {
6648
6696
  const key = `${contribution.kind}:${contribution.name}`;
@@ -6696,7 +6744,7 @@ var ExtensionRunner = class {
6696
6744
  }
6697
6745
  throw new Error(`Phase registration requires a directory path or Phase object.`);
6698
6746
  }
6699
- registerPhaseDefinition(extension, phase) {
6747
+ async registerPhaseDefinition(extension, phase) {
6700
6748
  const name = phase.name;
6701
6749
  if (this.phases.has(name)) {
6702
6750
  throw new Error(`Duplicate phase name: ${name}`);
@@ -6708,29 +6756,65 @@ var ExtensionRunner = class {
6708
6756
  this.phases.set(name, registered);
6709
6757
  extension.phases.add(name);
6710
6758
  this._phaseCache = null;
6759
+ await this.notifyResourceChange("phase");
6711
6760
  }
6712
- unregisterPhase(extension, phaseName) {
6761
+ async unregisterPhase(extension, phaseName) {
6713
6762
  const registered = this.phases.get(phaseName);
6714
6763
  if (registered && registered.source.extensionPath === extension.path) {
6715
6764
  this.phases.delete(phaseName);
6716
6765
  extension.phases.delete(phaseName);
6717
6766
  this._phaseCache = null;
6767
+ await this.notifyResourceChange("phase");
6718
6768
  }
6719
6769
  }
6720
6770
  async loadRegisteredPhase(extension, registration) {
6721
6771
  const extensionBase = extension.path.startsWith("<") ? this.cwd : dirname6(extension.path);
6722
6772
  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}`);
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
+ }
6726
6797
  }
6727
6798
  const registered = {
6728
- definition: phase,
6799
+ definition: skill,
6729
6800
  source: { extensionPath: extension.path }
6730
6801
  };
6731
- this.phases.set(name, registered);
6732
- extension.phases.add(name);
6733
- 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);
6734
6818
  }
6735
6819
  registerProvider(config) {
6736
6820
  if (this.bound) {
@@ -6769,7 +6853,18 @@ var ExtensionRunner = class {
6769
6853
  }
6770
6854
  async disposeExtension(extension, message) {
6771
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();
6772
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();
6773
6868
  const hadCapabilities = extension.capabilities.size > 0;
6774
6869
  extension.capabilities.clear();
6775
6870
  for (const cleanup of [...extension.cleanup].reverse()) {
@@ -6787,6 +6882,11 @@ var ExtensionRunner = class {
6787
6882
  if (hadCapabilities) {
6788
6883
  this.notifyCapabilitiesChanged();
6789
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
+ }
6790
6890
  }
6791
6891
  };
6792
6892
  function createExtensionRunner(options) {
@@ -7407,12 +7507,18 @@ var RuntimeExtensionLifetime = class {
7407
7507
  phases() {
7408
7508
  return this.runner.getPhases();
7409
7509
  }
7510
+ skills() {
7511
+ return this.runner.getSkills();
7512
+ }
7410
7513
  get extensionRunner() {
7411
7514
  return this.runner;
7412
7515
  }
7413
7516
  get extensionHost() {
7414
7517
  return this.runner.extensionHost;
7415
7518
  }
7519
+ onResourceChange(listener) {
7520
+ return this.runner.onResourceChange(listener);
7521
+ }
7416
7522
  dropRunState(runId) {
7417
7523
  this.runner.dropRunState(runId);
7418
7524
  }
@@ -7426,9 +7532,24 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7426
7532
  constructor(extensions = new RuntimeExtensionLifetime()) {
7427
7533
  super();
7428
7534
  this.extensions = extensions;
7535
+ this.extensions.onResourceChange((kind) => this.recommit(kind));
7429
7536
  }
7430
7537
  extensions;
7431
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
+ }
7432
7553
  get extensionHost() {
7433
7554
  return this.extensions.extensionHost;
7434
7555
  }
@@ -7444,8 +7565,9 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
7444
7565
  })();
7445
7566
  const normalized = await loaded;
7446
7567
  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()]);
7568
+ await this.recommit("tool");
7569
+ await this.recommit("phase");
7570
+ await this.recommit("skill");
7449
7571
  return {
7450
7572
  ...result,
7451
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.1",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",