@rowan-agent/agent 0.15.5 → 0.16.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
@@ -206,6 +206,40 @@ type PhaseMessageManager = {
206
206
  discard(messageId: string): void;
207
207
  };
208
208
 
209
+ type ScopeRef = {
210
+ kind: "global";
211
+ } | {
212
+ kind: "team";
213
+ teamId: string;
214
+ } | {
215
+ kind: "project";
216
+ teamId: string;
217
+ projectId: string;
218
+ };
219
+ interface ExtensionStateStore {
220
+ get(key: string): Promise<JsonValue | undefined>;
221
+ set(key: string, value: JsonValue): Promise<void>;
222
+ delete(key: string): Promise<void>;
223
+ }
224
+ interface ExtensionHost {
225
+ getConfig(extensionId: string, scope?: ScopeRef): Promise<JsonObject | null> | JsonObject | null;
226
+ onConfigChanged?(listener: (extensionId: string, scope: ScopeRef) => void): (() => void) | void;
227
+ getAgentState(extensionId: string, agentId: string, key: string): Promise<JsonValue | undefined> | JsonValue | undefined;
228
+ setAgentState(extensionId: string, agentId: string, key: string, value: JsonValue): Promise<void> | void;
229
+ deleteAgentState(extensionId: string, agentId: string, key: string): Promise<void> | void;
230
+ }
231
+ type ExtensionToolContribution = {
232
+ kind: "tool";
233
+ name: string;
234
+ description: string;
235
+ };
236
+ type ExtensionCapabilityContribution = ExtensionToolContribution;
237
+ type ExtensionCapability = {
238
+ extensionId: string;
239
+ kind: "tool";
240
+ name: string;
241
+ description: string;
242
+ };
209
243
  interface SourceInfo {
210
244
  source: string;
211
245
  baseDir?: string;
@@ -318,6 +352,7 @@ type LoadExtensionsResult = {
318
352
  * Runner.loadExtensions() calls the factory and creates the full Extension object.
319
353
  */
320
354
  interface LoadedExtension {
355
+ id?: string;
321
356
  path: string;
322
357
  name: string;
323
358
  factory: ExtensionFactory;
@@ -325,6 +360,7 @@ interface LoadedExtension {
325
360
  }
326
361
  /** Extension manifest from package.json `rowan` field. */
327
362
  interface ExtensionManifest {
363
+ id?: string;
328
364
  entry?: string;
329
365
  name?: string;
330
366
  }
@@ -471,6 +507,20 @@ interface ExtensionAPI {
471
507
  on<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
472
508
  /** Unsubscribe from a hook event. */
473
509
  off<K extends HookEventType>(eventType: K, handler: HookHandler<K>): void;
510
+ /** Extension configuration — read own layered config or listen for changes. */
511
+ config: {
512
+ /** Get the extension's own effective configuration block. */
513
+ get(scope?: ScopeRef): Promise<JsonObject | null>;
514
+ /** Listen for configuration changes for a scope. */
515
+ changed(handler: (scope: ScopeRef) => void): void;
516
+ };
517
+ /** Extension state — scoped to run (in-memory) or agent (durable). */
518
+ state: {
519
+ /** Run-scoped in-memory state; dropped when the Run ends. */
520
+ run(runId: string): ExtensionStateStore;
521
+ /** Agent-scoped durable state; persisted with the Agent record. */
522
+ agent(agentId: string): ExtensionStateStore;
523
+ };
474
524
  /** Tool capabilities — register and unregister custom tools. */
475
525
  tool: {
476
526
  /** Register a custom LLM-callable tool. */
@@ -478,6 +528,11 @@ interface ExtensionAPI {
478
528
  /** Unregister a previously registered tool by name. */
479
529
  unregister(toolName: string): void;
480
530
  };
531
+ /** Capability contributions — advertise tools or features to the host. */
532
+ capabilities: {
533
+ /** Contribute a capability to the host. Returns a disposer to remove the contribution. */
534
+ contribute(contribution: ExtensionCapabilityContribution): () => void;
535
+ };
481
536
  /** Register a model provider. */
482
537
  registerProvider(config: _rowan_agent_models.ProviderConfig): void;
483
538
  /** Unregister a model provider. */
@@ -524,6 +579,23 @@ type ExtensionDisposer = () => void | Promise<void>;
524
579
  type ExtensionFactoryResult = void | ExtensionDisposer;
525
580
  type ExtensionFactory = (api: ExtensionAPI) => ExtensionFactoryResult | Promise<ExtensionFactoryResult>;
526
581
 
582
+ declare class InMemoryExtensionHost implements ExtensionHost {
583
+ private readonly configs;
584
+ private readonly agentState;
585
+ private readonly listeners;
586
+ constructor(options?: {
587
+ configs?: Record<string, Record<string, JsonObject>>;
588
+ agentState?: Record<string, Record<string, Record<string, JsonValue>>>;
589
+ });
590
+ setConfig(extensionId: string, config: JsonObject | null, scope?: ScopeRef): void;
591
+ getConfig(extensionId: string, scope?: ScopeRef): JsonObject | null;
592
+ onConfigChanged(listener: (extensionId: string, scope: ScopeRef) => void): () => void;
593
+ notifyConfigChanged(extensionId: string, scope?: ScopeRef): void;
594
+ getAgentState(extensionId: string, agentId: string, key: string): JsonValue | undefined;
595
+ setAgentState(extensionId: string, agentId: string, key: string, value: JsonValue): void;
596
+ deleteAgentState(extensionId: string, agentId: string, key: string): void;
597
+ }
598
+
527
599
  /**
528
600
  * Extension runner — manages extension loading and hook execution.
529
601
  *
@@ -538,6 +610,7 @@ type ExtensionFactory = (api: ExtensionAPI) => ExtensionFactoryResult | Promise<
538
610
  type ExtensionRunnerOptions = {
539
611
  entryPhaseId?: string | null;
540
612
  cwd?: string;
613
+ host?: ExtensionHost;
541
614
  };
542
615
  /**
543
616
  * Manages extensions and provides hooks for the agent loop.
@@ -573,15 +646,20 @@ declare class ExtensionRunner {
573
646
  readonly hooks: HooksManager;
574
647
  readonly runtime: ExtensionRuntime;
575
648
  readonly events: EventBus;
649
+ readonly host: ExtensionHost;
576
650
  private readonly cwd;
577
651
  private readonly abortController;
578
652
  private _idle;
579
653
  private readonly extensions;
654
+ private readonly runState;
655
+ private readonly configChangeListeners;
656
+ private readonly hostUnsubscribers;
580
657
  private readonly phases;
581
658
  private _phaseCache;
582
659
  private readonly pendingProviders;
583
660
  private bound;
584
661
  private readonly errorListeners;
662
+ private readonly capabilityListeners;
585
663
  /** Current agent context — set by the agent before each phase */
586
664
  currentContext?: AgentContext;
587
665
  constructor(options?: ExtensionRunnerOptions);
@@ -628,12 +706,25 @@ declare class ExtensionRunner {
628
706
  loadExtensions(extensions: LoadedExtension[]): Promise<void>;
629
707
  /** Dispose all active Extensions in reverse activation order. */
630
708
  close(): Promise<void>;
709
+ get extensionHost(): ExtensionHost;
710
+ notifyConfigChanged(extensionId: string, scope: ScopeRef): void;
711
+ getRunState(runId: string, extensionId: string, key: string): JsonValue | undefined;
712
+ setRunState(runId: string, extensionId: string, key: string, value: JsonValue): void;
713
+ deleteRunState(runId: string, extensionId: string, key: string): void;
714
+ dropRunState(runId: string): void;
631
715
  /** Get all registered tools from all extensions. */
632
716
  getAllRegisteredTools(): RegisteredTool[];
633
717
  /**
634
718
  * Get a tool definition by name. Returns undefined if not found.
635
719
  */
636
720
  getToolDefinition(toolName: string): RegisteredTool["definition"] | undefined;
721
+ /** Get all contributed capabilities across all extensions. */
722
+ getCapabilities(): readonly ExtensionCapability[];
723
+ /**
724
+ * Subscribe to capability changes (contributions added, removed, or extensions disposed).
725
+ * Returns an unsubscribe function.
726
+ */
727
+ onCapabilitiesChanged(listener: (capabilities: readonly ExtensionCapability[]) => void): () => void;
637
728
  getPhase(name: string): Phase | undefined;
638
729
  getPhases(): Phase[];
639
730
  createPhaseRegistry(input?: {
@@ -665,18 +756,33 @@ declare class ExtensionRunner {
665
756
  output?: PhaseOutput;
666
757
  }>;
667
758
  emitBeforePrompt(phaseId: string, input: PhaseContext): Promise<PhaseContext>;
759
+ emitRunStart(event: {
760
+ runId: string;
761
+ agentId: string;
762
+ metadata: Readonly<Record<string, unknown>>;
763
+ turn: JsonObject;
764
+ }): Promise<void>;
765
+ emitRunEnd(event: {
766
+ runId: string;
767
+ agentId: string;
768
+ outcome: unknown;
769
+ }): Promise<void>;
668
770
  emitBeforeToolCall(tool: Tool, args: unknown, context?: {
669
771
  runId?: string;
670
772
  agentId?: string;
671
773
  toolCallId?: string;
672
774
  metadata?: Readonly<Record<string, unknown>>;
673
775
  answer?: unknown;
776
+ scope?: ScopeRef;
777
+ turn?: JsonObject;
674
778
  }): Promise<BeforeToolCallResult>;
675
779
  emitAfterToolCall(tool: Tool, result: ToolResult, context?: {
676
780
  runId?: string;
677
781
  agentId?: string;
678
782
  toolCallId?: string;
679
783
  metadata?: Readonly<Record<string, unknown>>;
784
+ scope?: ScopeRef;
785
+ turn?: JsonObject;
680
786
  }): Promise<ToolResult>;
681
787
  /**
682
788
  * Create an ExtensionAPI for a specific extension.
@@ -686,6 +792,8 @@ declare class ExtensionRunner {
686
792
  private createExtensionAPI;
687
793
  private registerTool;
688
794
  private unregisterTool;
795
+ private contributeCapability;
796
+ private notifyCapabilitiesChanged;
689
797
  private registerPhase;
690
798
  private registerPhaseDefinition;
691
799
  private unregisterPhase;
@@ -724,11 +832,14 @@ declare class RuntimeExtensionLifetime {
724
832
  private closed;
725
833
  constructor(options?: {
726
834
  cwd?: string;
835
+ host?: ExtensionHost;
727
836
  });
728
837
  activate(extensions: readonly LoadedExtension[]): Promise<ExtensionActivationResult>;
729
838
  tools(): readonly RegisteredTool[];
730
839
  phases(): readonly Phase[];
731
840
  get extensionRunner(): ExtensionRunner;
841
+ get extensionHost(): ExtensionHost;
842
+ dropRunState(runId: string): void;
732
843
  close(): Promise<void>;
733
844
  }
734
845
  /** Bootstrap-only Registry facade; its Extension method disappears after init. */
@@ -736,6 +847,7 @@ declare class RuntimeBootstrapRegistry extends ResourceRegistry {
736
847
  private readonly extensions;
737
848
  private readonly extensionSourceId;
738
849
  constructor(extensions?: RuntimeExtensionLifetime);
850
+ get extensionHost(): ExtensionHost;
739
851
  /** Install Rowan-owned implicit resources before any host source is read. */
740
852
  ensureCoreResources(): Promise<void>;
741
853
  loadExtensions(input: readonly LoadedExtension[] | ExtensionLoadInput): Promise<ExtensionActivationResult>;
@@ -830,6 +942,8 @@ type ToolCallContext = Readonly<{
830
942
  agentMetadata?: Metadata;
831
943
  runMetadata?: Metadata;
832
944
  toolCallId: ToolCallId;
945
+ scope?: ScopeRef;
946
+ turn?: JsonObject;
833
947
  reportProgress(progress: JsonValue): void;
834
948
  }>;
835
949
  type ToolInvocationContext = ToolCallContext & Readonly<{
@@ -1244,6 +1358,7 @@ type AgentRuntimeOptions = Readonly<{
1244
1358
  configs?: ConfigProvider;
1245
1359
  concurrency?: number;
1246
1360
  bootstrap?: (registry: RuntimeBootstrapRegistry) => void | Promise<void>;
1361
+ host?: ExtensionHost;
1247
1362
  }>;
1248
1363
  type DurableConsumer = Readonly<{
1249
1364
  caughtUp: Promise<void>;
@@ -1268,6 +1383,7 @@ interface AgentRun {
1268
1383
  cancel(reason?: string): Promise<RunBoundary>;
1269
1384
  }
1270
1385
  interface AgentRuntime$1 {
1386
+ readonly host: ExtensionHost;
1271
1387
  loadAgents(input: LoadInput<AgentDefinition>): Promise<LoadResult>;
1272
1388
  loadSkills(input: LoadInput<Skill>): Promise<LoadResult>;
1273
1389
  loadPhases(input: LoadInput<Phase>): Promise<LoadResult>;
@@ -1335,6 +1451,8 @@ interface AgentRuntime$1 {
1335
1451
  signal: AbortSignal;
1336
1452
  }>): void | Promise<void>;
1337
1453
  }): Promise<DurableConsumer>;
1454
+ listCapabilities(): readonly ExtensionCapability[];
1455
+ onCapabilitiesChanged(listener: (capabilities: readonly ExtensionCapability[]) => void): () => void;
1338
1456
  close(): Promise<void>;
1339
1457
  }
1340
1458
  type ConsumerRegistration = Readonly<{
@@ -2039,6 +2157,8 @@ interface BeforeToolCallEvent {
2039
2157
  readonly toolCallId?: string;
2040
2158
  readonly metadata?: Readonly<Record<string, unknown>>;
2041
2159
  readonly answer?: unknown;
2160
+ readonly scope?: ScopeRef;
2161
+ readonly turn?: JsonObject;
2042
2162
  }
2043
2163
  interface AfterToolCallEvent {
2044
2164
  type: "after_tool_call";
@@ -2048,8 +2168,23 @@ interface AfterToolCallEvent {
2048
2168
  readonly agentId?: string;
2049
2169
  readonly toolCallId?: string;
2050
2170
  readonly metadata?: Readonly<Record<string, unknown>>;
2171
+ readonly scope?: ScopeRef;
2172
+ readonly turn?: JsonObject;
2173
+ }
2174
+ interface RunStartEvent {
2175
+ type: "run_start";
2176
+ runId: string;
2177
+ agentId: string;
2178
+ metadata: Readonly<Record<string, unknown>>;
2179
+ turn: JsonObject;
2180
+ }
2181
+ interface RunEndEvent {
2182
+ type: "run_end";
2183
+ runId: string;
2184
+ agentId: string;
2185
+ outcome: unknown;
2051
2186
  }
2052
- type HookEvent = BeforePhaseEvent | AfterPhaseEvent | BeforePromptEvent | BeforeToolCallEvent | AfterToolCallEvent;
2187
+ type HookEvent = BeforePhaseEvent | AfterPhaseEvent | BeforePromptEvent | BeforeToolCallEvent | AfterToolCallEvent | RunStartEvent | RunEndEvent;
2053
2188
  interface BeforePhaseResult {
2054
2189
  abort?: Outcome$1;
2055
2190
  skip?: {
@@ -2080,6 +2215,8 @@ interface HookResultMap {
2080
2215
  before_prompt: BeforePromptResult | undefined;
2081
2216
  before_tool_call: BeforeToolCallResult | undefined;
2082
2217
  after_tool_call: AfterToolCallResult | undefined;
2218
+ run_start: void;
2219
+ run_end: void;
2083
2220
  }
2084
2221
  type HookEventType = HookEvent["type"];
2085
2222
  type HookHandler<K extends HookEventType> = (event: Extract<HookEvent, {
@@ -2157,12 +2294,15 @@ declare class AgentRuntime implements AgentRuntime$1 {
2157
2294
  private readonly autoCompactionRuns;
2158
2295
  private readonly consumers;
2159
2296
  private readonly transientEvents;
2297
+ readonly host: ExtensionHost;
2160
2298
  private readonly resources;
2299
+ private readonly startedRuns;
2161
2300
  private heartbeat?;
2162
2301
  private pumping;
2163
2302
  private closed;
2164
2303
  private constructor();
2165
2304
  static init(options: AgentRuntimeOptions): Promise<AgentRuntime>;
2305
+ private handleRunEnd;
2166
2306
  loadAgents(input: LoadInput<AgentDefinition>): Promise<LoadResult>;
2167
2307
  loadSkills(input: LoadInput<Skill>): Promise<LoadResult>;
2168
2308
  loadPhases(input: LoadInput<Phase>): Promise<LoadResult>;
@@ -2235,6 +2375,8 @@ declare class AgentRuntime implements AgentRuntime$1 {
2235
2375
  signal: AbortSignal;
2236
2376
  }>): void | Promise<void>;
2237
2377
  }): Promise<DurableConsumer>;
2378
+ listCapabilities(): readonly ExtensionCapability[];
2379
+ onCapabilitiesChanged(listener: (capabilities: readonly ExtensionCapability[]) => void): () => void;
2238
2380
  close(): Promise<void>;
2239
2381
  snapshot(runId: RunId): Promise<RunSnapshot>;
2240
2382
  history(agentId: AgentId): Promise<readonly Message[]>;
@@ -2990,4 +3132,4 @@ declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): Fro
2990
3132
 
2991
3133
  declare function createCoreTools(input?: CoreToolContext): Tool$1[];
2992
3134
 
2993
- 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 ExtensionContribution, type ExtensionDisposer, type ExtensionFactory, type ExtensionFactoryResult, ExtensionLifetimeError, type ExtensionLifetimeErrorCode, type ExtensionLoadInput, type FrontmatterResult, type HistorySeed, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InteractionRecord, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, 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 OpaqueId, type 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 PhaseSettingsBadge, type PhaseSettingsContext, type PhaseSettingsControl, type PhaseSettingsDefinition, type PhaseSettingsItem, type PhaseSettingsOption, type PhaseSettingsProvider, type PhaseSettingsSection, 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 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 RunState, type RunStateChanged, type RunSummary, RuntimeBootstrapRegistry, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, RuntimeExtensionLifetime, STOP_PHASE_ID, type Skill, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool$1 as Tool, type ToolCallContext, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, isConfigurationSnapshot, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhaseSettings, loadPhases, loadSkill, loadSkills, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
3135
+ 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, InMemoryConfigProvider, InMemoryExtensionHost, InMemoryStore, type InteractionRecord, type InvocationCatalogEntry, type InvocationSource, type JsonObject, type JsonPrimitive, type JsonValue, 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 OpaqueId, type 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 PhaseSettingsBadge, type PhaseSettingsContext, type PhaseSettingsControl, type PhaseSettingsDefinition, type PhaseSettingsItem, type PhaseSettingsOption, type PhaseSettingsProvider, type PhaseSettingsSection, 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 ScopeRef, type Skill, SqliteStore, type TextContent, type ThinkingContent, type ThinkingDelta, type Tool$1 as Tool, type ToolCallContext, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolContribution, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCompactPhase, createCorePhases, createCoreTools, createDefaultPhase, createStopPhase, isConfigurationSnapshot, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhase, loadPhaseSettings, loadPhases, loadSkill, loadSkills, parseAgentDefinition, parseFrontmatter, resolveConfigurationSnapshot };
package/dist/index.js CHANGED
@@ -1247,6 +1247,56 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1247
1247
  assertActive();
1248
1248
  hooks?.off(eventType, handler);
1249
1249
  },
1250
+ config: {
1251
+ get: async (scope) => {
1252
+ assertActive();
1253
+ return await options?.config?.get(scope) ?? null;
1254
+ },
1255
+ changed: (handler) => {
1256
+ assertActive();
1257
+ options?.config?.changed(handler);
1258
+ }
1259
+ },
1260
+ state: {
1261
+ run: (runId) => {
1262
+ assertActive();
1263
+ const store = options?.state?.run(runId);
1264
+ return {
1265
+ get: async (key) => {
1266
+ assertActive();
1267
+ return store?.get(key);
1268
+ },
1269
+ set: async (key, value) => {
1270
+ assertActive();
1271
+ assertJsonValue(value);
1272
+ return store?.set(key, value);
1273
+ },
1274
+ delete: async (key) => {
1275
+ assertActive();
1276
+ return store?.delete(key);
1277
+ }
1278
+ };
1279
+ },
1280
+ agent: (agentId) => {
1281
+ assertActive();
1282
+ const store = options?.state?.agent(agentId);
1283
+ return {
1284
+ get: async (key) => {
1285
+ assertActive();
1286
+ return store?.get(key);
1287
+ },
1288
+ set: async (key, value) => {
1289
+ assertActive();
1290
+ assertJsonValue(value);
1291
+ return store?.set(key, value);
1292
+ },
1293
+ delete: async (key) => {
1294
+ assertActive();
1295
+ return store?.delete(key);
1296
+ }
1297
+ };
1298
+ }
1299
+ },
1250
1300
  tool: {
1251
1301
  register: (tool) => {
1252
1302
  assertActive();
@@ -1257,6 +1307,25 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
1257
1307
  options?.unregisterTool?.(toolName);
1258
1308
  }
1259
1309
  },
1310
+ capabilities: {
1311
+ contribute: (contribution) => {
1312
+ assertActive();
1313
+ if (!contribution || typeof contribution !== "object") {
1314
+ throw new TypeError("Capability contribution must be an object.");
1315
+ }
1316
+ if (contribution.kind !== "tool") {
1317
+ throw new TypeError(`Unsupported capability contribution kind: "${contribution.kind}".`);
1318
+ }
1319
+ if (typeof contribution.name !== "string" || contribution.name.trim() === "") {
1320
+ throw new TypeError("Capability contribution requires a non-empty name.");
1321
+ }
1322
+ if (typeof contribution.description !== "string") {
1323
+ throw new TypeError("Capability contribution requires a description string.");
1324
+ }
1325
+ return options?.contributeCapability ? options.contributeCapability(contribution) : () => {
1326
+ };
1327
+ }
1328
+ },
1260
1329
  registerProvider: (config) => {
1261
1330
  assertActive();
1262
1331
  options?.registerProvider?.(config);
@@ -4518,7 +4587,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4518
4587
  agentId: input.context.agentId,
4519
4588
  toolCallId: input.context.toolCallId,
4520
4589
  metadata: input.context.runMetadata,
4521
- answer: input.answer
4590
+ answer: input.answer,
4591
+ scope: input.context.scope,
4592
+ turn: input.context.turn
4522
4593
  });
4523
4594
  if (decision.interaction !== void 0) {
4524
4595
  return { interaction: decision.interaction };
@@ -4534,7 +4605,9 @@ function assembleRegisteredExtensions(snapshot, runner, options = {}) {
4534
4605
  runId: input.context.runId,
4535
4606
  agentId: input.context.agentId,
4536
4607
  toolCallId: input.context.toolCallId,
4537
- metadata: input.context.runMetadata
4608
+ metadata: input.context.runMetadata,
4609
+ scope: input.context.scope,
4610
+ turn: input.context.turn
4538
4611
  }
4539
4612
  );
4540
4613
  return fromLoopResult(result);
@@ -4792,6 +4865,126 @@ var HooksManager = class {
4792
4865
  }
4793
4866
  };
4794
4867
 
4868
+ // src/extensions/host.ts
4869
+ var InMemoryExtensionHost = class {
4870
+ configs = /* @__PURE__ */ new Map();
4871
+ agentState = /* @__PURE__ */ new Map();
4872
+ listeners = /* @__PURE__ */ new Set();
4873
+ constructor(options = {}) {
4874
+ if (options.configs) {
4875
+ for (const [scopeKey, extMap] of Object.entries(options.configs)) {
4876
+ const map = /* @__PURE__ */ new Map();
4877
+ for (const [extId, cfg] of Object.entries(extMap)) {
4878
+ assertJsonValue(cfg);
4879
+ map.set(extId, structuredClone(cfg));
4880
+ }
4881
+ this.configs.set(scopeKey, map);
4882
+ }
4883
+ }
4884
+ if (options.agentState) {
4885
+ for (const [agentId, extMap] of Object.entries(options.agentState)) {
4886
+ const agentMap = /* @__PURE__ */ new Map();
4887
+ for (const [extId, stateMap] of Object.entries(extMap)) {
4888
+ const map = /* @__PURE__ */ new Map();
4889
+ for (const [key, val] of Object.entries(stateMap)) {
4890
+ assertJsonValue(val);
4891
+ map.set(key, structuredClone(val));
4892
+ }
4893
+ agentMap.set(extId, map);
4894
+ }
4895
+ this.agentState.set(agentId, agentMap);
4896
+ }
4897
+ }
4898
+ }
4899
+ setConfig(extensionId, config, scope = { kind: "global" }) {
4900
+ const scopeKey = scopeToKey(scope);
4901
+ if (!this.configs.has(scopeKey)) {
4902
+ this.configs.set(scopeKey, /* @__PURE__ */ new Map());
4903
+ }
4904
+ const scopeConfigs = this.configs.get(scopeKey);
4905
+ if (config === null) {
4906
+ scopeConfigs.delete(extensionId);
4907
+ } else {
4908
+ assertJsonValue(config);
4909
+ scopeConfigs.set(extensionId, structuredClone(config));
4910
+ }
4911
+ this.notifyConfigChanged(extensionId, scope);
4912
+ }
4913
+ getConfig(extensionId, scope = { kind: "global" }) {
4914
+ const globalConfig = this.configs.get("global")?.get(extensionId);
4915
+ let merged = globalConfig ? { ...globalConfig } : null;
4916
+ if (scope.kind === "team" || scope.kind === "project") {
4917
+ const teamConfig = this.configs.get(`team:${scope.teamId}`)?.get(extensionId);
4918
+ if (teamConfig) {
4919
+ merged = { ...merged ?? {}, ...teamConfig };
4920
+ }
4921
+ }
4922
+ if (scope.kind === "project") {
4923
+ const projectConfig = this.configs.get(`project:${scope.teamId}:${scope.projectId}`)?.get(extensionId);
4924
+ if (projectConfig) {
4925
+ merged = { ...merged ?? {}, ...projectConfig };
4926
+ }
4927
+ }
4928
+ return merged ? structuredClone(merged) : null;
4929
+ }
4930
+ onConfigChanged(listener) {
4931
+ this.listeners.add(listener);
4932
+ return () => this.listeners.delete(listener);
4933
+ }
4934
+ notifyConfigChanged(extensionId, scope = { kind: "global" }) {
4935
+ for (const listener of this.listeners) {
4936
+ try {
4937
+ listener(extensionId, scope);
4938
+ } catch {
4939
+ }
4940
+ }
4941
+ }
4942
+ getAgentState(extensionId, agentId, key) {
4943
+ const val = this.agentState.get(agentId)?.get(extensionId)?.get(key);
4944
+ return val !== void 0 ? structuredClone(val) : void 0;
4945
+ }
4946
+ setAgentState(extensionId, agentId, key, value) {
4947
+ assertJsonValue(value);
4948
+ let agentMap = this.agentState.get(agentId);
4949
+ if (!agentMap) {
4950
+ agentMap = /* @__PURE__ */ new Map();
4951
+ this.agentState.set(agentId, agentMap);
4952
+ }
4953
+ let extMap = agentMap.get(extensionId);
4954
+ if (!extMap) {
4955
+ extMap = /* @__PURE__ */ new Map();
4956
+ agentMap.set(extensionId, extMap);
4957
+ }
4958
+ extMap.set(key, structuredClone(value));
4959
+ }
4960
+ deleteAgentState(extensionId, agentId, key) {
4961
+ this.agentState.get(agentId)?.get(extensionId)?.delete(key);
4962
+ }
4963
+ };
4964
+ function scopeToKey(scope) {
4965
+ switch (scope.kind) {
4966
+ case "global":
4967
+ return "global";
4968
+ case "team":
4969
+ return `team:${scope.teamId}`;
4970
+ case "project":
4971
+ return `project:${scope.teamId}:${scope.projectId}`;
4972
+ }
4973
+ }
4974
+ function resolveScopeFromMetadata(metadata) {
4975
+ if (metadata) {
4976
+ const teamId = typeof metadata.teamId === "string" ? metadata.teamId : void 0;
4977
+ const projectId = typeof metadata.projectId === "string" ? metadata.projectId : void 0;
4978
+ if (teamId && projectId) {
4979
+ return { kind: "project", teamId, projectId };
4980
+ }
4981
+ if (teamId) {
4982
+ return { kind: "team", teamId };
4983
+ }
4984
+ }
4985
+ return { kind: "global" };
4986
+ }
4987
+
4795
4988
  // src/extensions/runner.ts
4796
4989
  import { execFile } from "child_process";
4797
4990
  import { dirname as dirname5, resolve as resolve5 } from "path";
@@ -4934,11 +5127,17 @@ var ExtensionRunner = class {
4934
5127
  hooks;
4935
5128
  runtime;
4936
5129
  events;
5130
+ host;
4937
5131
  cwd;
4938
5132
  abortController = new AbortController();
4939
5133
  _idle = true;
4940
5134
  // Per-extension tracking
4941
5135
  extensions = [];
5136
+ // Run state: runId -> extensionId -> key -> value
5137
+ runState = /* @__PURE__ */ new Map();
5138
+ // Config change listeners: extensionId -> Set of handlers
5139
+ configChangeListeners = /* @__PURE__ */ new Map();
5140
+ hostUnsubscribers = [];
4942
5141
  // Phase management
4943
5142
  phases = /* @__PURE__ */ new Map();
4944
5143
  _phaseCache = null;
@@ -4947,6 +5146,8 @@ var ExtensionRunner = class {
4947
5146
  bound = false;
4948
5147
  // Error listeners
4949
5148
  errorListeners = /* @__PURE__ */ new Set();
5149
+ // Capability change listeners
5150
+ capabilityListeners = /* @__PURE__ */ new Set();
4950
5151
  /** Current agent context — set by the agent before each phase */
4951
5152
  currentContext;
4952
5153
  constructor(options) {
@@ -4954,6 +5155,15 @@ var ExtensionRunner = class {
4954
5155
  this.runtime = createExtensionRuntime();
4955
5156
  this.events = createEventBus();
4956
5157
  this.cwd = options?.cwd ?? process.cwd();
5158
+ this.host = options?.host ?? new InMemoryExtensionHost();
5159
+ if (this.host.onConfigChanged) {
5160
+ const unsub = this.host.onConfigChanged((extensionId, scope) => {
5161
+ this.notifyConfigChanged(extensionId, scope);
5162
+ });
5163
+ if (typeof unsub === "function") {
5164
+ this.hostUnsubscribers.push(unsub);
5165
+ }
5166
+ }
4957
5167
  }
4958
5168
  /** Whether the agent is currently idle (not streaming). */
4959
5169
  get isIdle() {
@@ -5034,10 +5244,13 @@ var ExtensionRunner = class {
5034
5244
  */
5035
5245
  async loadExtensions(extensions) {
5036
5246
  for (const ext of extensions) {
5247
+ const extensionId = ext.id ?? ext.manifest?.id ?? ext.name;
5037
5248
  const extension = {
5249
+ id: extensionId,
5038
5250
  path: ext.path,
5039
5251
  tools: /* @__PURE__ */ new Map(),
5040
5252
  phases: /* @__PURE__ */ new Set(),
5253
+ capabilities: /* @__PURE__ */ new Map(),
5041
5254
  cleanup: [],
5042
5255
  runtime: createExtensionRuntime()
5043
5256
  };
@@ -5047,6 +5260,9 @@ var ExtensionRunner = class {
5047
5260
  if (typeof disposer === "function") extension.disposer = disposer;
5048
5261
  this.extensions.push(extension);
5049
5262
  this._phaseCache = null;
5263
+ if (extension.capabilities.size > 0) {
5264
+ this.notifyCapabilitiesChanged();
5265
+ }
5050
5266
  } catch (error) {
5051
5267
  await this.rollbackExtension(extension);
5052
5268
  const message = error instanceof Error ? error.message : String(error);
@@ -5063,14 +5279,67 @@ var ExtensionRunner = class {
5063
5279
  /** Dispose all active Extensions in reverse activation order. */
5064
5280
  async close() {
5065
5281
  this.abortController.abort();
5282
+ for (const unsub of this.hostUnsubscribers) {
5283
+ try {
5284
+ unsub();
5285
+ } catch {
5286
+ }
5287
+ }
5288
+ this.hostUnsubscribers.length = 0;
5289
+ this.configChangeListeners.clear();
5290
+ this.runState.clear();
5066
5291
  for (const extension of [...this.extensions].reverse()) {
5067
5292
  await this.disposeExtension(extension, "This Extension Runtime has been closed.");
5068
5293
  }
5069
5294
  this.extensions.length = 0;
5295
+ this.capabilityListeners.clear();
5070
5296
  this.phases.clear();
5071
5297
  this._phaseCache = null;
5072
5298
  this.runtime.invalidate("This Extension Runtime has been closed.");
5073
5299
  }
5300
+ get extensionHost() {
5301
+ return this.host;
5302
+ }
5303
+ notifyConfigChanged(extensionId, scope) {
5304
+ const listeners = this.configChangeListeners.get(extensionId);
5305
+ if (!listeners) return;
5306
+ for (const listener of listeners) {
5307
+ try {
5308
+ listener(scope);
5309
+ } catch (error) {
5310
+ this.emitError({
5311
+ extensionPath: "<runtime>",
5312
+ event: "config_changed",
5313
+ error: error instanceof Error ? error.message : String(error),
5314
+ stack: error instanceof Error ? error.stack : void 0
5315
+ });
5316
+ }
5317
+ }
5318
+ }
5319
+ getRunState(runId, extensionId, key) {
5320
+ const val = this.runState.get(runId)?.get(extensionId)?.get(key);
5321
+ return val !== void 0 ? structuredClone(val) : void 0;
5322
+ }
5323
+ setRunState(runId, extensionId, key, value) {
5324
+ assertJsonValue(value);
5325
+ let runMap = this.runState.get(runId);
5326
+ if (!runMap) {
5327
+ runMap = /* @__PURE__ */ new Map();
5328
+ this.runState.set(runId, runMap);
5329
+ }
5330
+ let extMap = runMap.get(extensionId);
5331
+ if (!extMap) {
5332
+ extMap = /* @__PURE__ */ new Map();
5333
+ runMap.set(extensionId, extMap);
5334
+ }
5335
+ extMap.set(key, structuredClone(value));
5336
+ }
5337
+ deleteRunState(runId, extensionId, key) {
5338
+ this.runState.get(runId)?.get(extensionId)?.delete(key);
5339
+ }
5340
+ dropRunState(runId) {
5341
+ this.runState.delete(runId);
5342
+ }
5074
5343
  // ---------------------------------------------------------------------------
5075
5344
  // Tool management
5076
5345
  // ---------------------------------------------------------------------------
@@ -5097,6 +5366,32 @@ var ExtensionRunner = class {
5097
5366
  return void 0;
5098
5367
  }
5099
5368
  // ---------------------------------------------------------------------------
5369
+ // Capability management
5370
+ // ---------------------------------------------------------------------------
5371
+ /** Get all contributed capabilities across all extensions. */
5372
+ getCapabilities() {
5373
+ const result = [];
5374
+ for (const ext of this.extensions) {
5375
+ for (const contrib of ext.capabilities.values()) {
5376
+ result.push({
5377
+ extensionId: ext.id,
5378
+ kind: contrib.kind,
5379
+ name: contrib.name,
5380
+ description: contrib.description
5381
+ });
5382
+ }
5383
+ }
5384
+ return Object.freeze(result);
5385
+ }
5386
+ /**
5387
+ * Subscribe to capability changes (contributions added, removed, or extensions disposed).
5388
+ * Returns an unsubscribe function.
5389
+ */
5390
+ onCapabilitiesChanged(listener) {
5391
+ this.capabilityListeners.add(listener);
5392
+ return () => this.capabilityListeners.delete(listener);
5393
+ }
5394
+ // ---------------------------------------------------------------------------
5100
5395
  // Phase management
5101
5396
  // ---------------------------------------------------------------------------
5102
5397
  getPhase(name) {
@@ -5182,7 +5477,39 @@ var ExtensionRunner = class {
5182
5477
  });
5183
5478
  return result?.input ?? input;
5184
5479
  }
5480
+ async emitRunStart(event) {
5481
+ try {
5482
+ await this.hooks.emit("run_start", {
5483
+ type: "run_start",
5484
+ ...event
5485
+ });
5486
+ } catch (error) {
5487
+ this.emitError({
5488
+ extensionPath: "<runtime>",
5489
+ event: "run_start",
5490
+ error: error instanceof Error ? error.message : String(error),
5491
+ stack: error instanceof Error ? error.stack : void 0
5492
+ });
5493
+ }
5494
+ }
5495
+ async emitRunEnd(event) {
5496
+ try {
5497
+ await this.hooks.emit("run_end", {
5498
+ type: "run_end",
5499
+ ...event
5500
+ });
5501
+ } catch (error) {
5502
+ this.emitError({
5503
+ extensionPath: "<runtime>",
5504
+ event: "run_end",
5505
+ error: error instanceof Error ? error.message : String(error),
5506
+ stack: error instanceof Error ? error.stack : void 0
5507
+ });
5508
+ }
5509
+ }
5185
5510
  async emitBeforeToolCall(tool, args, context) {
5511
+ const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
5512
+ const turn = context?.turn ?? {};
5186
5513
  const result = await this.emitHook("before_tool_call", {
5187
5514
  type: "before_tool_call",
5188
5515
  tool,
@@ -5191,11 +5518,15 @@ var ExtensionRunner = class {
5191
5518
  ...context?.agentId !== void 0 ? { agentId: context.agentId } : {},
5192
5519
  ...context?.toolCallId !== void 0 ? { toolCallId: context.toolCallId } : {},
5193
5520
  ...context?.metadata !== void 0 ? { metadata: context.metadata } : {},
5194
- ...context?.answer !== void 0 ? { answer: context.answer } : {}
5521
+ ...context?.answer !== void 0 ? { answer: context.answer } : {},
5522
+ scope,
5523
+ turn
5195
5524
  });
5196
5525
  return result ?? { allow: true };
5197
5526
  }
5198
5527
  async emitAfterToolCall(tool, result, context) {
5528
+ const scope = context?.scope ?? resolveScopeFromMetadata(context?.metadata);
5529
+ const turn = context?.turn ?? {};
5199
5530
  const hookResult = await this.emitHook("after_tool_call", {
5200
5531
  type: "after_tool_call",
5201
5532
  tool,
@@ -5203,7 +5534,9 @@ var ExtensionRunner = class {
5203
5534
  ...context?.runId !== void 0 ? { runId: context.runId } : {},
5204
5535
  ...context?.agentId !== void 0 ? { agentId: context.agentId } : {},
5205
5536
  ...context?.toolCallId !== void 0 ? { toolCallId: context.toolCallId } : {},
5206
- ...context?.metadata !== void 0 ? { metadata: context.metadata } : {}
5537
+ ...context?.metadata !== void 0 ? { metadata: context.metadata } : {},
5538
+ scope,
5539
+ turn
5207
5540
  });
5208
5541
  return hookResult?.result ?? result;
5209
5542
  }
@@ -5271,9 +5604,43 @@ var ExtensionRunner = class {
5271
5604
  unregisterProvider: (name) => this.unregisterProvider(name),
5272
5605
  registerTool: (tool) => this.registerTool(extension, tool),
5273
5606
  unregisterTool: (toolName) => this.unregisterTool(extension, toolName),
5607
+ contributeCapability: (contribution) => this.contributeCapability(extension, contribution),
5274
5608
  context: extContext,
5275
5609
  manifest,
5276
- trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
5610
+ trackCleanup: (cleanup) => extension.cleanup.push(cleanup),
5611
+ config: {
5612
+ get: async (scope) => {
5613
+ const effectiveScope = scope ?? { kind: "global" };
5614
+ const res = await runner.host.getConfig(extension.id, effectiveScope);
5615
+ return res ?? null;
5616
+ },
5617
+ changed: (handler) => {
5618
+ let listeners = runner.configChangeListeners.get(extension.id);
5619
+ if (!listeners) {
5620
+ listeners = /* @__PURE__ */ new Set();
5621
+ runner.configChangeListeners.set(extension.id, listeners);
5622
+ }
5623
+ listeners.add(handler);
5624
+ extension.cleanup.push(() => {
5625
+ listeners?.delete(handler);
5626
+ if (listeners && listeners.size === 0) {
5627
+ runner.configChangeListeners.delete(extension.id);
5628
+ }
5629
+ });
5630
+ }
5631
+ },
5632
+ state: {
5633
+ run: (runId) => ({
5634
+ get: async (key) => runner.getRunState(runId, extension.id, key),
5635
+ set: async (key, value) => runner.setRunState(runId, extension.id, key, value),
5636
+ delete: async (key) => runner.deleteRunState(runId, extension.id, key)
5637
+ }),
5638
+ agent: (agentId) => ({
5639
+ get: async (key) => runner.host.getAgentState(extension.id, agentId, key),
5640
+ set: async (key, value) => runner.host.setAgentState(extension.id, agentId, key, value),
5641
+ delete: async (key) => runner.host.deleteAgentState(extension.id, agentId, key)
5642
+ })
5643
+ }
5277
5644
  }, extension.runtime, this.events);
5278
5645
  }
5279
5646
  registerTool(extension, tool) {
@@ -5302,6 +5669,46 @@ var ExtensionRunner = class {
5302
5669
  extension.tools.delete(toolName);
5303
5670
  }
5304
5671
  }
5672
+ contributeCapability(extension, contribution) {
5673
+ const key = `${contribution.kind}:${contribution.name}`;
5674
+ const entry = {
5675
+ kind: contribution.kind,
5676
+ name: contribution.name,
5677
+ description: contribution.description
5678
+ };
5679
+ extension.capabilities.set(key, entry);
5680
+ if (this.extensions.includes(extension)) {
5681
+ this.notifyCapabilitiesChanged();
5682
+ }
5683
+ let disposed = false;
5684
+ const dispose = () => {
5685
+ if (disposed) return;
5686
+ disposed = true;
5687
+ if (extension.capabilities.get(key) === entry) {
5688
+ extension.capabilities.delete(key);
5689
+ if (this.extensions.includes(extension)) {
5690
+ this.notifyCapabilitiesChanged();
5691
+ }
5692
+ }
5693
+ };
5694
+ extension.cleanup.push(dispose);
5695
+ return dispose;
5696
+ }
5697
+ notifyCapabilitiesChanged() {
5698
+ const capabilities = this.getCapabilities();
5699
+ for (const listener of this.capabilityListeners) {
5700
+ try {
5701
+ listener(capabilities);
5702
+ } catch (error) {
5703
+ this.emitError({
5704
+ extensionPath: "<runtime>",
5705
+ event: "capabilities_changed",
5706
+ error: error instanceof Error ? error.message : String(error),
5707
+ stack: error instanceof Error ? error.stack : void 0
5708
+ });
5709
+ }
5710
+ }
5711
+ }
5305
5712
  async registerPhase(extension, registration) {
5306
5713
  if (typeof registration === "string") {
5307
5714
  if (registration.length === 0) {
@@ -5388,6 +5795,8 @@ var ExtensionRunner = class {
5388
5795
  async disposeExtension(extension, message) {
5389
5796
  extension.runtime.invalidate(message);
5390
5797
  for (const name of extension.phases) this.phases.delete(name);
5798
+ const hadCapabilities = extension.capabilities.size > 0;
5799
+ extension.capabilities.clear();
5391
5800
  for (const cleanup of [...extension.cleanup].reverse()) {
5392
5801
  await Promise.resolve().then(() => cleanup()).catch(() => void 0);
5393
5802
  }
@@ -5395,6 +5804,9 @@ var ExtensionRunner = class {
5395
5804
  await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
5396
5805
  }
5397
5806
  this._phaseCache = null;
5807
+ if (hadCapabilities) {
5808
+ this.notifyCapabilitiesChanged();
5809
+ }
5398
5810
  }
5399
5811
  };
5400
5812
  function createExtensionRunner(options) {
@@ -5425,6 +5837,8 @@ function readManifestSync(dir) {
5425
5837
  const rowan = pkg.rowan;
5426
5838
  if (!rowan) return void 0;
5427
5839
  return {
5840
+ id: rowan.id ?? pkg.name,
5841
+ name: pkg.name,
5428
5842
  entry: rowan.extensions?.[0]
5429
5843
  };
5430
5844
  } catch {
@@ -5507,13 +5921,15 @@ async function discoverExtensionsInDir(dir) {
5507
5921
  }
5508
5922
  return paths;
5509
5923
  }
5510
- function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
5924
+ function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>", options = {}) {
5511
5925
  const resolvedCwd = resolve6(cwd);
5512
5926
  const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve6(resolvedCwd, extensionPath);
5513
- const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown";
5927
+ const name = options.name ?? (isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname6(resolvedPath)) || "unknown");
5514
5928
  const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname6(resolvedPath);
5515
5929
  const manifest = readManifestSync(manifestDir);
5930
+ const id = options.id ?? manifest?.id ?? name;
5516
5931
  return {
5932
+ id,
5517
5933
  path: resolvedPath,
5518
5934
  name,
5519
5935
  factory,
@@ -5983,6 +6399,12 @@ var RuntimeExtensionLifetime = class {
5983
6399
  get extensionRunner() {
5984
6400
  return this.runner;
5985
6401
  }
6402
+ get extensionHost() {
6403
+ return this.runner.extensionHost;
6404
+ }
6405
+ dropRunState(runId) {
6406
+ this.runner.dropRunState(runId);
6407
+ }
5986
6408
  async close() {
5987
6409
  if (this.closed) return;
5988
6410
  this.closed = true;
@@ -5996,6 +6418,9 @@ var RuntimeBootstrapRegistry = class extends ResourceRegistry {
5996
6418
  }
5997
6419
  extensions;
5998
6420
  extensionSourceId = "rowan.extensions";
6421
+ get extensionHost() {
6422
+ return this.extensions.extensionHost;
6423
+ }
5999
6424
  /** Install Rowan-owned implicit resources before any host source is read. */
6000
6425
  async ensureCoreResources() {
6001
6426
  await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
@@ -6134,11 +6559,14 @@ var AgentRuntime = class _AgentRuntime {
6134
6559
  autoCompactionRuns = /* @__PURE__ */ new Set();
6135
6560
  consumers = /* @__PURE__ */ new Map();
6136
6561
  transientEvents = new TransientRunEventHub();
6562
+ host;
6137
6563
  resources;
6564
+ startedRuns = /* @__PURE__ */ new Set();
6138
6565
  heartbeat;
6139
6566
  pumping = false;
6140
6567
  closed = false;
6141
6568
  constructor(options, owned, resources) {
6569
+ this.host = options.host;
6142
6570
  this.owned = owned;
6143
6571
  this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
6144
6572
  this.storeIncarnation = String(owned.lease.token).split(":")[0];
@@ -6148,10 +6576,12 @@ var AgentRuntime = class _AgentRuntime {
6148
6576
  static async init(options) {
6149
6577
  if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
6150
6578
  const configs = options.configs ?? new InMemoryConfigProvider();
6579
+ const host = options.host ?? new InMemoryExtensionHost();
6151
6580
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
6152
6581
  if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
6153
6582
  const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
6154
- const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
6583
+ const extensions = new RuntimeExtensionLifetime({ host });
6584
+ const runtime = new _AgentRuntime({ ...options, configs, concurrency, host }, owned, new RuntimeBootstrapRegistry(extensions));
6155
6585
  try {
6156
6586
  await runtime.resources.ensureCoreResources();
6157
6587
  await options.bootstrap?.(runtime.resources);
@@ -6164,6 +6594,20 @@ var AgentRuntime = class _AgentRuntime {
6164
6594
  throw error;
6165
6595
  }
6166
6596
  }
6597
+ async handleRunEnd(runId, agentId, outcome) {
6598
+ if (this.startedRuns.has(runId)) {
6599
+ this.startedRuns.delete(runId);
6600
+ try {
6601
+ await this.resources.extensionRunner.emitRunEnd({
6602
+ runId,
6603
+ agentId,
6604
+ outcome
6605
+ });
6606
+ } catch {
6607
+ }
6608
+ }
6609
+ this.resources.extensionRunner.dropRunState(runId);
6610
+ }
6167
6611
  async loadAgents(input) {
6168
6612
  this.assertOpen();
6169
6613
  return this.resources.loadAgents(input);
@@ -6374,6 +6818,14 @@ var AgentRuntime = class _AgentRuntime {
6374
6818
  });
6375
6819
  return { caughtUp: subscription.caughtUp.promise, done: subscription.done.promise, stop: () => controller.abort() };
6376
6820
  }
6821
+ listCapabilities() {
6822
+ this.assertOpen();
6823
+ return this.resources.extensionRunner.getCapabilities();
6824
+ }
6825
+ onCapabilitiesChanged(listener) {
6826
+ this.assertOpen();
6827
+ return this.resources.extensionRunner.onCapabilitiesChanged(listener);
6828
+ }
6377
6829
  async close() {
6378
6830
  if (this.closed) return;
6379
6831
  this.closed = true;
@@ -6417,6 +6869,11 @@ var AgentRuntime = class _AgentRuntime {
6417
6869
  const active = this.executions.get(runId);
6418
6870
  if (active) this.transientEvents.clear(runId, active.executionId);
6419
6871
  active?.controller.abort();
6872
+ if (this.startedRuns.has(runId)) {
6873
+ await this.handleRunEnd(runId, snapshot.agentId, { id: createId("out"), status: "cancelled", message: reason ?? "Agent run stopped." });
6874
+ } else {
6875
+ this.resources.extensionRunner.dropRunState(runId);
6876
+ }
6420
6877
  return boundaryFromSnapshot(await this.owned.snapshotRun(runId));
6421
6878
  }
6422
6879
  async pump() {
@@ -6528,6 +6985,19 @@ var AgentRuntime = class _AgentRuntime {
6528
6985
  const controller = new AbortController();
6529
6986
  this.executions.set(run.id, { controller, executionId });
6530
6987
  if (this.cancellationRequested.has(run.id)) controller.abort();
6988
+ if (!this.startedRuns.has(run.id)) {
6989
+ this.startedRuns.add(run.id);
6990
+ const turn = typeof run.input === "object" && run.input !== null && !Array.isArray(run.input) ? run.input : {};
6991
+ try {
6992
+ await this.resources.extensionRunner.emitRunStart({
6993
+ runId: run.id,
6994
+ agentId: run.agentId,
6995
+ metadata: run.metadata ?? {},
6996
+ turn
6997
+ });
6998
+ } catch {
6999
+ }
7000
+ }
6531
7001
  const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner, {
6532
7002
  toolArchiveDir: this.archiveDirFor(run.agentId)
6533
7003
  });
@@ -6754,6 +7224,11 @@ var AgentRuntime = class _AgentRuntime {
6754
7224
  ...output && hasVisibleAssistantText(output) ? { output } : {}
6755
7225
  });
6756
7226
  this.transientEvents.clear(run.id, claim.execution.executionId);
7227
+ await this.handleRunEnd(run.id, run.agentId, {
7228
+ id: createId("out"),
7229
+ status: "cancelled",
7230
+ message: reason
7231
+ });
6757
7232
  return;
6758
7233
  }
6759
7234
  if (result.type === "input_required") {
@@ -6789,33 +7264,53 @@ var AgentRuntime = class _AgentRuntime {
6789
7264
  const output = latestAssistant(run, result.messages, executionContext.messages.length);
6790
7265
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, outcome: durableOutcome(result.outcome), ...output ? { output } : {} });
6791
7266
  this.transientEvents.clear(run.id, claim.execution.executionId, true);
7267
+ await this.handleRunEnd(run.id, run.agentId, result.outcome);
6792
7268
  return;
6793
7269
  }
6794
7270
  const failure = { code: "execution_failed", message: result.error instanceof Error ? result.error.message : "Execution failed." };
6795
7271
  await this.owned.commitOutcome({ runId: run.id, execution: claim.execution, expectedRevision: executionRevision, failure });
6796
7272
  this.transientEvents.clear(run.id, claim.execution.executionId);
7273
+ await this.handleRunEnd(run.id, run.agentId, {
7274
+ id: createId("out"),
7275
+ status: "failed",
7276
+ message: failure.message,
7277
+ error: failure
7278
+ });
6797
7279
  } catch (error) {
6798
7280
  if (!claim && error instanceof RuntimeError && ["run_state_conflict", "runtime_ownership_lost", "run_not_found"].includes(error.code)) return;
6799
7281
  if (claim) {
6800
7282
  if (this.cancellationRequested.has(run.id)) {
7283
+ const reason = this.cancellationReasons.get(run.id) ?? "Agent run stopped.";
6801
7284
  await this.owned.cancelRun({
6802
7285
  runId: run.id,
6803
7286
  expectedRevision: executionRevision,
6804
- reason: this.cancellationReasons.get(run.id) ?? "Agent run stopped."
7287
+ reason
6805
7288
  }).catch(() => void 0);
7289
+ await this.handleRunEnd(run.id, run.agentId, {
7290
+ id: createId("out"),
7291
+ status: "cancelled",
7292
+ message: reason
7293
+ });
6806
7294
  return;
6807
7295
  }
7296
+ const failureMessage = error instanceof Error ? error.message : "Execution failed.";
6808
7297
  await this.owned.commitOutcome({
6809
7298
  runId: run.id,
6810
7299
  execution: claim.execution,
6811
7300
  expectedRevision: executionRevision,
6812
- failure: { code: "execution_failed", message: error instanceof Error ? error.message : "Execution failed." }
7301
+ failure: { code: "execution_failed", message: failureMessage }
6813
7302
  }).catch(async (commitError) => {
6814
7303
  await this.owned.cancelRun({
6815
7304
  runId: run.id,
6816
7305
  reason: commitError instanceof Error ? commitError.message : "Run failure could not be committed."
6817
7306
  }).catch(() => void 0);
6818
7307
  });
7308
+ await this.handleRunEnd(run.id, run.agentId, {
7309
+ id: createId("out"),
7310
+ status: "failed",
7311
+ message: failureMessage,
7312
+ error: { code: "execution_failed", message: failureMessage }
7313
+ });
6819
7314
  return;
6820
7315
  }
6821
7316
  throw error;
@@ -6838,6 +7333,8 @@ var AgentRuntime = class _AgentRuntime {
6838
7333
  return { result: batch.results[0], revision: batch.revision };
6839
7334
  }
6840
7335
  async executeToolBatch(input) {
7336
+ const scope = resolveScopeFromMetadata(input.run.metadata);
7337
+ const turn = typeof input.run.input === "object" && input.run.input !== null && !Array.isArray(input.run.input) ? input.run.input : {};
6841
7338
  const checkpoint = input.driver?.checkpoint();
6842
7339
  const isSuspensionResume = checkpoint && typeof checkpoint === "object" && checkpoint.kind === "tool_call_suspension";
6843
7340
  const suspension = isSuspensionResume ? checkpoint : void 0;
@@ -6864,6 +7361,8 @@ var AgentRuntime = class _AgentRuntime {
6864
7361
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
6865
7362
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
6866
7363
  toolCallId,
7364
+ scope,
7365
+ turn,
6867
7366
  interaction: createRunInteractionDriver({
6868
7367
  currentPhase: "default",
6869
7368
  attempt: 0,
@@ -6951,6 +7450,8 @@ var AgentRuntime = class _AgentRuntime {
6951
7450
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
6952
7451
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
6953
7452
  toolCallId,
7453
+ scope,
7454
+ turn,
6954
7455
  interaction: createToolInteractionDriver(input.driver, {
6955
7456
  toolCallId,
6956
7457
  toolCallIds,
@@ -7105,6 +7606,8 @@ var AgentRuntime = class _AgentRuntime {
7105
7606
  ...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
7106
7607
  ...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
7107
7608
  toolCallId,
7609
+ scope,
7610
+ turn,
7108
7611
  interaction: createToolInteractionDriver(input.driver, {
7109
7612
  toolCallId,
7110
7613
  ...suspension?.toolCallIds === void 0 ? {} : { toolCallIds: suspension.toolCallIds },
@@ -10620,6 +11123,7 @@ export {
10620
11123
  DEFAULT_PHASE_ID,
10621
11124
  ExtensionLifetimeError,
10622
11125
  InMemoryConfigProvider,
11126
+ InMemoryExtensionHost,
10623
11127
  InMemoryStore,
10624
11128
  ResourceRegistry,
10625
11129
  ResourceRegistryError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowan-agent/agent",
3
- "version": "0.15.5",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",