@rowan-agent/agent 0.15.4 → 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.
Files changed (3) hide show
  1. package/dist/index.d.ts +242 -6
  2. package/dist/index.js +1345 -145
  3. package/package.json +1 -1
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 },
@@ -7618,9 +8121,83 @@ var InMemoryStore = class _InMemoryStore {
7618
8121
  eventSequence = 0;
7619
8122
  retentionFloor = 1;
7620
8123
  contextCompactions = /* @__PURE__ */ new Map();
8124
+ recorder;
8125
+ runReceiptKeys = /* @__PURE__ */ new Map();
7621
8126
  constructor(options = {}) {
7622
8127
  this.incarnation = options.incarnation ?? createId("store");
7623
8128
  }
8129
+ setChangeRecorder(recorder) {
8130
+ this.recorder = recorder;
8131
+ }
8132
+ touchAgent(agent) {
8133
+ this.recorder?.onAgentUpsert(agent);
8134
+ }
8135
+ touchRun(run) {
8136
+ this.recorder?.onRunUpsert(run);
8137
+ }
8138
+ storeMessage(message) {
8139
+ this.messages.set(message.id, message);
8140
+ this.recorder?.onMessageInsert(message);
8141
+ }
8142
+ storeToolCall(toolCall) {
8143
+ this.toolCalls.set(toolCall.id, toolCall);
8144
+ this.recorder?.onToolCallUpsert(toolCall);
8145
+ }
8146
+ appendEvent(event) {
8147
+ this.events.push(event);
8148
+ this.recorder?.onEventAppend(event);
8149
+ }
8150
+ inferRunIdForReceipt(key, payload) {
8151
+ const parts = key.split(":");
8152
+ const prefix = parts[0];
8153
+ if (["queued_failure", "phase_entered", "input_required", "interaction_answer", "outcome", "cancel"].includes(prefix) && parts[1]) {
8154
+ return parts[1];
8155
+ }
8156
+ const match = payload.match(/^\[\s*"([^"]+)"/);
8157
+ if (match && match[1]) {
8158
+ const candidate = match[1];
8159
+ if (this.runs.has(candidate) || candidate.startsWith("run_") || candidate.startsWith("run-")) {
8160
+ return candidate;
8161
+ }
8162
+ }
8163
+ return void 0;
8164
+ }
8165
+ dropRunOperationReceipts(runId) {
8166
+ const keys = this.runReceiptKeys.get(runId);
8167
+ if (keys && keys.size > 0) {
8168
+ for (const key of keys) {
8169
+ this.operationReceipts.delete(key);
8170
+ this.recorder?.onOperationReceiptDelete(key);
8171
+ }
8172
+ this.runReceiptKeys.delete(runId);
8173
+ }
8174
+ for (const [key] of this.operationReceipts.entries()) {
8175
+ if (key.includes(`:${runId}:`) || key.endsWith(`:${runId}`) || key.startsWith(`phase_output:${runId}`) || key.startsWith(`tool_reserve_batch:${runId}`)) {
8176
+ this.operationReceipts.delete(key);
8177
+ this.recorder?.onOperationReceiptDelete(key);
8178
+ }
8179
+ }
8180
+ }
8181
+ compactTerminalReceipts() {
8182
+ let count = 0;
8183
+ for (const [key, receipt] of [...this.operationReceipts.entries()]) {
8184
+ const runId = this.inferRunIdForReceipt(key, receipt.payload);
8185
+ if (runId) {
8186
+ const run = this.runs.get(runId);
8187
+ if (!run || ["completed", "failed", "cancelled"].includes(run.state)) {
8188
+ this.operationReceipts.delete(key);
8189
+ const keys = this.runReceiptKeys.get(runId);
8190
+ if (keys) {
8191
+ keys.delete(key);
8192
+ if (keys.size === 0) this.runReceiptKeys.delete(runId);
8193
+ }
8194
+ this.recorder?.onOperationReceiptDelete(key);
8195
+ count += 1;
8196
+ }
8197
+ }
8198
+ }
8199
+ return count;
8200
+ }
7624
8201
  static fromState(state) {
7625
8202
  const store = new _InMemoryStore({ incarnation: state.incarnation });
7626
8203
  for (const agent of state.agents) store.agents.set(agent.id, clone(agent));
@@ -7638,6 +8215,7 @@ var InMemoryStore = class _InMemoryStore {
7638
8215
  clonedRun.updatedAt = createTimestamp();
7639
8216
  store.runs.set(clonedRun.id, clonedRun);
7640
8217
  store.appendTransition(clonedRun, "input_required", "cancelled", { reason: clonedRun.cancellationReason });
8218
+ store.dropRunOperationReceipts(clonedRun.id);
7641
8219
  } else {
7642
8220
  store.runs.set(clonedRun.id, clonedRun);
7643
8221
  }
@@ -7646,7 +8224,18 @@ var InMemoryStore = class _InMemoryStore {
7646
8224
  for (const toolCall of state.toolCalls ?? []) store.toolCalls.set(toolCall.id, clone(toolCall));
7647
8225
  store.events.push(...clone(state.events));
7648
8226
  for (const [key, receipt] of state.idempotency) store.idempotency.set(key, clone(receipt));
7649
- for (const [key, receipt] of state.operationReceipts) store.operationReceipts.set(key, clone(receipt));
8227
+ for (const [key, receipt] of state.operationReceipts) {
8228
+ store.operationReceipts.set(key, clone(receipt));
8229
+ const runId = store.inferRunIdForReceipt(key, receipt.payload);
8230
+ if (runId) {
8231
+ let keys = store.runReceiptKeys.get(runId);
8232
+ if (!keys) {
8233
+ keys = /* @__PURE__ */ new Set();
8234
+ store.runReceiptKeys.set(runId, keys);
8235
+ }
8236
+ keys.add(key);
8237
+ }
8238
+ }
7650
8239
  for (const [agentId, messages] of state.historySeeds ?? []) store.historySeeds.set(agentId, clone(messages));
7651
8240
  for (const [consumerId, cursor] of state.consumerCheckpoints ?? []) store.consumerCheckpoints.set(consumerId, cursor);
7652
8241
  for (const [agentId, sequence] of state.nextAgentSequence) store.nextAgentSequence.set(agentId, sequence);
@@ -7679,6 +8268,16 @@ var InMemoryStore = class _InMemoryStore {
7679
8268
  contextCompactions: [...this.contextCompactions.entries()]
7680
8269
  });
7681
8270
  }
8271
+ exportMetadata() {
8272
+ return {
8273
+ incarnation: this.incarnation,
8274
+ eventSequence: this.eventSequence,
8275
+ nextAgentSequence: [...this.nextAgentSequence.entries()],
8276
+ nextReadySequence: [...this.nextReadySequence.entries()],
8277
+ historySeeds: [...this.historySeeds.entries()],
8278
+ contextCompactions: [...this.contextCompactions.entries()]
8279
+ };
8280
+ }
7682
8281
  attachOwner(lease) {
7683
8282
  this.owner = clone(lease);
7684
8283
  this.ownerEpoch = Math.max(this.ownerEpoch, lease.epoch);
@@ -7701,6 +8300,7 @@ var InMemoryStore = class _InMemoryStore {
7701
8300
  run.revision += 1;
7702
8301
  run.updatedAt = createTimestamp();
7703
8302
  this.appendTransition(run, "running", "failed", { failure });
8303
+ this.dropRunOperationReceipts(run.id);
7704
8304
  }
7705
8305
  }
7706
8306
  async openOwner(input) {
@@ -7718,6 +8318,7 @@ var InMemoryStore = class _InMemoryStore {
7718
8318
  }
7719
8319
  if (this.owner) this.interruptOwner(this.owner.epoch);
7720
8320
  this.dropSettledClaimReceipts();
8321
+ this.compactTerminalReceipts();
7721
8322
  this.ownerEpoch += 1;
7722
8323
  this.owner = {
7723
8324
  ownerId: input.ownerId,
@@ -7764,6 +8365,7 @@ var InMemoryStore = class _InMemoryStore {
7764
8365
  updatedAt: timestamp
7765
8366
  };
7766
8367
  this.agents.set(agent.id, agent);
8368
+ this.touchAgent(agent);
7767
8369
  if (input.historySeed && input.historySeed.length > 0) this.historySeeds.set(agent.id, materializeHistorySeed(agent.id, input.historySeed));
7768
8370
  this.nextAgentSequence.set(agent.id, 0);
7769
8371
  this.nextReadySequence.set(agent.id, 0);
@@ -7777,6 +8379,7 @@ var InMemoryStore = class _InMemoryStore {
7777
8379
  if (configToken !== void 0) agent.currentConfigToken = configToken;
7778
8380
  if (configIdentity !== void 0) agent.currentConfigIdentity = configIdentity;
7779
8381
  agent.updatedAt = createTimestamp();
8382
+ this.touchAgent(agent);
7780
8383
  return clone(agent);
7781
8384
  }
7782
8385
  updateAgentConfigToken(lease, input) {
@@ -7789,6 +8392,7 @@ var InMemoryStore = class _InMemoryStore {
7789
8392
  agent.currentConfigToken = input.token;
7790
8393
  if (input.configIdentity !== void 0) agent.currentConfigIdentity = input.configIdentity;
7791
8394
  agent.updatedAt = createTimestamp();
8395
+ this.touchAgent(agent);
7792
8396
  this.writeReceipt(scope, payload, agent);
7793
8397
  return clone(agent);
7794
8398
  }
@@ -7810,24 +8414,48 @@ var InMemoryStore = class _InMemoryStore {
7810
8414
  }
7811
8415
  const runIds = new Set(runs.map((run) => run.id));
7812
8416
  this.agents.delete(agent.id);
8417
+ this.recorder?.onAgentDelete(agent.id);
7813
8418
  this.nextAgentSequence.delete(agent.id);
7814
8419
  this.nextReadySequence.delete(agent.id);
7815
8420
  this.historySeeds.delete(agent.id);
7816
- for (const run of runs) this.runs.delete(run.id);
8421
+ for (const run of runs) {
8422
+ this.runs.delete(run.id);
8423
+ this.runReceiptKeys.delete(run.id);
8424
+ this.recorder?.onRunDelete(run.id);
8425
+ }
7817
8426
  for (const [messageId, message] of this.messages) {
7818
- if (runIds.has(message.runId)) this.messages.delete(messageId);
8427
+ if (runIds.has(message.runId)) {
8428
+ this.messages.delete(messageId);
8429
+ this.recorder?.onMessageDelete(messageId);
8430
+ }
7819
8431
  }
7820
8432
  for (const [toolCallId, toolCall] of this.toolCalls) {
7821
- if (runIds.has(toolCall.runId)) this.toolCalls.delete(toolCallId);
8433
+ if (runIds.has(toolCall.runId)) {
8434
+ this.toolCalls.delete(toolCallId);
8435
+ this.recorder?.onToolCallDelete(toolCallId);
8436
+ }
7822
8437
  }
8438
+ const deletedEventIds = [];
7823
8439
  for (let index = this.events.length - 1; index >= 0; index -= 1) {
7824
- if (this.events[index].agentId === agent.id) this.events.splice(index, 1);
8440
+ if (this.events[index].agentId === agent.id) {
8441
+ deletedEventIds.push(this.events[index].id);
8442
+ this.events.splice(index, 1);
8443
+ }
8444
+ }
8445
+ if (deletedEventIds.length > 0) {
8446
+ this.recorder?.onEventsTruncate(deletedEventIds);
7825
8447
  }
7826
8448
  for (const [key] of this.idempotency) {
7827
- if (key.includes(String(agent.id)) || [...runIds].some((runId) => key.includes(String(runId)))) this.idempotency.delete(key);
8449
+ if (key.includes(String(agent.id)) || [...runIds].some((runId) => key.includes(String(runId)))) {
8450
+ this.idempotency.delete(key);
8451
+ this.recorder?.onIdempotencyDelete(key);
8452
+ }
7828
8453
  }
7829
8454
  for (const [key] of this.operationReceipts) {
7830
- if (key.includes(String(agent.id)) || [...runIds].some((runId) => key.includes(String(runId)))) this.operationReceipts.delete(key);
8455
+ if (key.includes(String(agent.id)) || [...runIds].some((runId) => key.includes(String(runId)))) {
8456
+ this.operationReceipts.delete(key);
8457
+ this.recorder?.onOperationReceiptDelete(key);
8458
+ }
7831
8459
  }
7832
8460
  }
7833
8461
  reviseMessage(lease, input) {
@@ -7935,14 +8563,19 @@ var InMemoryStore = class _InMemoryStore {
7935
8563
  run.revision += 1;
7936
8564
  run.updatedAt = timestamp;
7937
8565
  this.appendTransition(run, previousState, "cancelled", { reason: run.cancellationReason });
8566
+ this.dropRunOperationReceipts(run.id);
7938
8567
  } else {
7939
8568
  run.revision += 1;
7940
8569
  run.updatedAt = timestamp;
8570
+ this.touchRun(run);
7941
8571
  }
7942
8572
  }
7943
8573
  for (const [messageId, message] of this.messages) {
7944
8574
  if (!affectedRunSet.has(message.runId)) continue;
7945
- if (message.runId !== original.runId || message.sequenceWithinRun > original.sequenceWithinRun) this.messages.delete(messageId);
8575
+ if (message.runId !== original.runId || message.sequenceWithinRun > original.sequenceWithinRun) {
8576
+ this.messages.delete(messageId);
8577
+ this.recorder?.onMessageDelete(messageId);
8578
+ }
7946
8579
  }
7947
8580
  if (isSeedMessage) {
7948
8581
  const targetIndex = seed.findIndex(({ id }) => id === original.id);
@@ -7956,10 +8589,11 @@ var InMemoryStore = class _InMemoryStore {
7956
8589
  sequenceWithinRun: 0,
7957
8590
  createdAt: timestamp
7958
8591
  };
7959
- this.messages.set(revisedMessage.id, revisedMessage);
8592
+ this.storeMessage(revisedMessage);
7960
8593
  this.runs.set(replacementRun.id, replacementRun);
8594
+ this.touchRun(replacementRun);
7961
8595
  this.appendTransition(replacementRun, null, "queued");
7962
- this.events.push({
8596
+ this.appendEvent({
7963
8597
  ...this.baseEvent(replacementRun),
7964
8598
  kind: "message_revised",
7965
8599
  message: revisedMessage,
@@ -7975,7 +8609,7 @@ var InMemoryStore = class _InMemoryStore {
7975
8609
  affectedToolCallIds: affectedTools.map((toolCall) => toolCall.id),
7976
8610
  ...effectDigest === void 0 ? {} : { effectDigest }
7977
8611
  };
7978
- this.writeOperationReceipt(operationKey, operationPayload, result);
8612
+ this.writeOperationReceipt(operationKey, operationPayload, result, replacementRun.id);
7979
8613
  return clone(result);
7980
8614
  }
7981
8615
  commitInteractionRecord(run, interaction, status, answer, reply) {
@@ -7997,7 +8631,7 @@ var InMemoryStore = class _InMemoryStore {
7997
8631
  sequenceWithinRun: this.nextMessageSequence(run.id),
7998
8632
  createdAt: now
7999
8633
  };
8000
- this.messages.set(record.id, record);
8634
+ this.storeMessage(record);
8001
8635
  this.appendMessage(run, record);
8002
8636
  return record;
8003
8637
  }
@@ -8046,12 +8680,13 @@ var InMemoryStore = class _InMemoryStore {
8046
8680
  sequenceWithinRun: this.nextMessageSequence(waitingRun.id),
8047
8681
  createdAt: now
8048
8682
  };
8049
- this.messages.set(message.id, message);
8683
+ this.storeMessage(message);
8050
8684
  this.appendMessage(waitingRun, message);
8051
8685
  waitingRun.readySequence = this.nextReady(waitingRun.agentId);
8052
8686
  waitingRun.state = "queued";
8053
8687
  waitingRun.revision += 1;
8054
8688
  waitingRun.updatedAt = now;
8689
+ this.touchRun(waitingRun);
8055
8690
  this.appendTransition(waitingRun, "input_required", "queued");
8056
8691
  this.writeReceipt(scope, payload, waitingRun);
8057
8692
  return clone(waitingRun);
@@ -8073,6 +8708,7 @@ var InMemoryStore = class _InMemoryStore {
8073
8708
  updatedAt: timestamp
8074
8709
  };
8075
8710
  this.runs.set(run.id, run);
8711
+ this.touchRun(run);
8076
8712
  this.appendTransition(run, null, "queued");
8077
8713
  this.writeReceipt(scope, payload, run);
8078
8714
  return clone(run);
@@ -8115,7 +8751,7 @@ var InMemoryStore = class _InMemoryStore {
8115
8751
  sequenceWithinRun: this.nextMessageSequence(run.id),
8116
8752
  createdAt: createTimestamp()
8117
8753
  };
8118
- this.messages.set(message.id, message);
8754
+ this.storeMessage(message);
8119
8755
  committedMessages.push(message);
8120
8756
  }
8121
8757
  const execution = {
@@ -8127,10 +8763,11 @@ var InMemoryStore = class _InMemoryStore {
8127
8763
  run.execution = execution;
8128
8764
  run.revision += 1;
8129
8765
  run.updatedAt = createTimestamp();
8766
+ this.touchRun(run);
8130
8767
  for (const message of committedMessages) this.appendMessage(run, message);
8131
8768
  this.appendTransition(run, "queued", "running");
8132
8769
  const result = { run: clone(run), execution: clone(execution), history: this.activeHistory(run.agentId, run.agentSequence) };
8133
- this.writeOperationReceipt(operationKey, operationPayload, result);
8770
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8134
8771
  return result;
8135
8772
  }
8136
8773
  failQueuedRun(lease, input) {
@@ -8146,9 +8783,11 @@ var InMemoryStore = class _InMemoryStore {
8146
8783
  run.failure = clone(input.failure);
8147
8784
  run.revision += 1;
8148
8785
  run.updatedAt = createTimestamp();
8786
+ this.touchRun(run);
8149
8787
  this.appendTransition(run, "queued", "failed", { failure: input.failure });
8788
+ this.dropRunOperationReceipts(run.id);
8150
8789
  const result = clone(run);
8151
- this.writeOperationReceipt(operationKey, operationPayload, result);
8790
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8152
8791
  return result;
8153
8792
  }
8154
8793
  commitPhaseEntered(lease, input) {
@@ -8164,10 +8803,11 @@ var InMemoryStore = class _InMemoryStore {
8164
8803
  run.currentPhaseId = input.phaseId;
8165
8804
  run.revision += 1;
8166
8805
  run.updatedAt = createTimestamp();
8806
+ this.touchRun(run);
8167
8807
  const visit2 = this.events.filter((event) => event.runId === run.id && event.kind === "phase_entered").length + 1;
8168
- this.events.push(this.baseEvent(run, { kind: "phase_entered", executionId: input.execution.executionId, phaseId: input.phaseId, visit: visit2 }));
8808
+ this.appendEvent(this.baseEvent(run, { kind: "phase_entered", executionId: input.execution.executionId, phaseId: input.phaseId, visit: visit2 }));
8169
8809
  const result = clone(run);
8170
- this.writeOperationReceipt(operationKey, operationPayload, result);
8810
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8171
8811
  return result;
8172
8812
  }
8173
8813
  /** Commit a parallel Phase's reply while its Run is still running. */
@@ -8188,12 +8828,13 @@ var InMemoryStore = class _InMemoryStore {
8188
8828
  role: "assistant",
8189
8829
  sequenceWithinRun: this.nextMessageSequence(run.id)
8190
8830
  };
8191
- this.messages.set(message.id, message);
8831
+ this.storeMessage(message);
8192
8832
  run.revision += 1;
8193
8833
  run.updatedAt = createTimestamp();
8834
+ this.touchRun(run);
8194
8835
  this.appendMessage(run, message);
8195
8836
  const result = clone(run);
8196
- this.writeOperationReceipt(operationKey, operationPayload, result);
8837
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8197
8838
  return result;
8198
8839
  }
8199
8840
  commitInputRequired(lease, input) {
@@ -8215,7 +8856,7 @@ var InMemoryStore = class _InMemoryStore {
8215
8856
  ...clone(input.prompt),
8216
8857
  sequenceWithinRun: this.nextMessageSequence(run.id)
8217
8858
  };
8218
- this.messages.set(prompt.id, prompt);
8859
+ this.storeMessage(prompt);
8219
8860
  }
8220
8861
  run.state = "input_required";
8221
8862
  run.checkpoint = clone(input.checkpoint);
@@ -8230,12 +8871,13 @@ var InMemoryStore = class _InMemoryStore {
8230
8871
  delete run.execution;
8231
8872
  run.revision += 1;
8232
8873
  run.updatedAt = createTimestamp();
8874
+ this.touchRun(run);
8233
8875
  if (prompt) {
8234
8876
  this.appendMessage(run, prompt);
8235
8877
  }
8236
8878
  this.appendTransition(run, "running", "input_required", { prompt, interactions, answers: run.interactionAnswers ?? {} });
8237
8879
  const result = clone(run);
8238
- this.writeOperationReceipt(operationKey, operationPayload, result);
8880
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8239
8881
  return result;
8240
8882
  }
8241
8883
  answerInteraction(lease, input) {
@@ -8283,8 +8925,9 @@ var InMemoryStore = class _InMemoryStore {
8283
8925
  run.state = "queued";
8284
8926
  this.appendTransition(run, "input_required", "queued");
8285
8927
  }
8928
+ this.touchRun(run);
8286
8929
  const result = clone(run);
8287
- this.writeOperationReceipt(operationKey, operationPayload, result);
8930
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8288
8931
  return result;
8289
8932
  }
8290
8933
  reserveToolCall(lease, input) {
@@ -8358,17 +9001,18 @@ var InMemoryStore = class _InMemoryStore {
8358
9001
  sequenceWithinRun: this.nextMessageSequence(run.id),
8359
9002
  createdAt: timestamp
8360
9003
  };
8361
- for (const toolCall of toolCalls) this.toolCalls.set(toolCall.id, toolCall);
8362
- this.messages.set(requestMessage.id, requestMessage);
9004
+ for (const toolCall of toolCalls) this.storeToolCall(toolCall);
9005
+ this.storeMessage(requestMessage);
8363
9006
  run.revision += 1;
8364
9007
  run.updatedAt = timestamp;
9008
+ this.touchRun(run);
8365
9009
  this.appendMessage(run, requestMessage);
8366
9010
  for (const toolCall of toolCalls) this.appendToolTransition(run, { from: null, to: "pending" }, toolCall);
8367
9011
  const result = {
8368
9012
  run: clone(run),
8369
9013
  toolCalls: toolCalls.map((toolCall) => clone(toolCall))
8370
9014
  };
8371
- this.writeOperationReceipt(operationKey, operationPayload, result);
9015
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8372
9016
  return result;
8373
9017
  }
8374
9018
  startToolCall(lease, input) {
@@ -8383,13 +9027,14 @@ var InMemoryStore = class _InMemoryStore {
8383
9027
  if (toolCall.state !== "pending") throw new RuntimeError("run_state_conflict", { runId: run.id, expected: ["running"], actual: run.state });
8384
9028
  toolCall.state = "running";
8385
9029
  toolCall.executionId = input.execution.executionId;
8386
- toolCall.executionId = input.execution.executionId;
8387
9030
  toolCall.updatedAt = createTimestamp();
9031
+ this.storeToolCall(toolCall);
8388
9032
  run.revision += 1;
8389
9033
  run.updatedAt = toolCall.updatedAt;
9034
+ this.touchRun(run);
8390
9035
  this.appendToolTransition(run, { from: "pending", to: "running" }, toolCall);
8391
9036
  const result = { run: clone(run), toolCall: clone(toolCall) };
8392
- this.writeOperationReceipt(operationKey, operationPayload, result);
9037
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8393
9038
  return result;
8394
9039
  }
8395
9040
  suspendToolCall(lease, input) {
@@ -8406,11 +9051,13 @@ var InMemoryStore = class _InMemoryStore {
8406
9051
  }
8407
9052
  toolCall.state = "pending";
8408
9053
  toolCall.updatedAt = createTimestamp();
9054
+ this.storeToolCall(toolCall);
8409
9055
  run.revision += 1;
8410
9056
  run.updatedAt = toolCall.updatedAt;
9057
+ this.touchRun(run);
8411
9058
  this.appendToolTransition(run, { from: "running", to: "pending" }, toolCall);
8412
9059
  const result = { run: clone(run), toolCall: clone(toolCall) };
8413
- this.writeOperationReceipt(operationKey, operationPayload, result);
9060
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8414
9061
  return result;
8415
9062
  }
8416
9063
  commitToolResult(lease, input) {
@@ -8442,15 +9089,17 @@ var InMemoryStore = class _InMemoryStore {
8442
9089
  sequenceWithinRun: this.nextMessageSequence(run.id),
8443
9090
  createdAt: createTimestamp()
8444
9091
  };
8445
- this.messages.set(message.id, message);
9092
+ this.storeMessage(message);
8446
9093
  toolCall.state = input.state;
8447
9094
  toolCall.result = durableResult;
8448
9095
  toolCall.resultMessageId = resultMessageId;
8449
9096
  if (input.state === "indeterminate") toolCall.reason = input.reason;
8450
9097
  else delete toolCall.reason;
8451
9098
  toolCall.updatedAt = message.createdAt;
9099
+ this.storeToolCall(toolCall);
8452
9100
  run.revision += 1;
8453
9101
  run.updatedAt = message.createdAt;
9102
+ this.touchRun(run);
8454
9103
  this.appendToolTransition(run, { from, to: input.state }, toolCall);
8455
9104
  this.appendMessage(run, message);
8456
9105
  if (input.state === "indeterminate") {
@@ -8471,10 +9120,12 @@ var InMemoryStore = class _InMemoryStore {
8471
9120
  delete run.execution;
8472
9121
  run.revision += 1;
8473
9122
  run.updatedAt = createTimestamp();
9123
+ this.touchRun(run);
8474
9124
  this.appendTransition(run, "running", "failed", { failure });
9125
+ this.dropRunOperationReceipts(run.id);
8475
9126
  }
8476
9127
  const result = { run: clone(run), toolCall: clone(toolCall) };
8477
- this.writeOperationReceipt(operationKey, operationPayload, result);
9128
+ this.writeOperationReceipt(operationKey, operationPayload, result, run.id);
8478
9129
  return result;
8479
9130
  }
8480
9131
  commitOutcome(lease, input) {
@@ -8492,7 +9143,7 @@ var InMemoryStore = class _InMemoryStore {
8492
9143
  if (nextState === "failed" && !input.failure) throw new TypeError("failed Run requires a failure");
8493
9144
  const output = input.output ? { ...clone(input.output), sequenceWithinRun: this.nextMessageSequence(run.id) } : void 0;
8494
9145
  if (output) {
8495
- this.messages.set(output.id, output);
9146
+ this.storeMessage(output);
8496
9147
  this.appendMessage(run, output);
8497
9148
  }
8498
9149
  run.state = nextState;
@@ -8504,9 +9155,10 @@ var InMemoryStore = class _InMemoryStore {
8504
9155
  delete run.interactionAnswers;
8505
9156
  run.revision += 1;
8506
9157
  run.updatedAt = createTimestamp();
9158
+ this.touchRun(run);
8507
9159
  this.appendTransition(run, "running", nextState, { outcome: input.outcome, failure: input.failure, output });
9160
+ this.dropRunOperationReceipts(run.id);
8508
9161
  const result = clone(run);
8509
- this.writeOperationReceipt(operationKey, operationPayload, result);
8510
9162
  return result;
8511
9163
  }
8512
9164
  cancelRun(lease, input) {
@@ -8526,7 +9178,6 @@ var InMemoryStore = class _InMemoryStore {
8526
9178
  }
8527
9179
  if (["completed", "failed", "cancelled"].includes(run.state)) {
8528
9180
  const result2 = clone(run);
8529
- if (input.expectedRevision !== void 0) this.writeOperationReceipt(operationKey, operationPayload, result2);
8530
9181
  return result2;
8531
9182
  }
8532
9183
  const from = run.state;
@@ -8541,7 +9192,7 @@ var InMemoryStore = class _InMemoryStore {
8541
9192
  for (const toolCall of activeToolCalls) this.interruptToolCall(run, toolCall, input.reason ?? "The Run was cancelled.");
8542
9193
  if (input.output && (typeof input.output.content === "string" ? input.output.content.length > 0 : input.output.content.length > 0)) {
8543
9194
  const output = { ...clone(input.output), sequenceWithinRun: this.nextMessageSequence(run.id) };
8544
- this.messages.set(output.id, output);
9195
+ this.storeMessage(output);
8545
9196
  this.appendMessage(run, output);
8546
9197
  }
8547
9198
  if (run.openInteractions && run.openInteractions.length > 0) {
@@ -8564,9 +9215,10 @@ var InMemoryStore = class _InMemoryStore {
8564
9215
  delete run.interactionAnswers;
8565
9216
  run.revision += 1;
8566
9217
  run.updatedAt = createTimestamp();
9218
+ this.touchRun(run);
8567
9219
  this.appendTransition(run, from, "failed", { failure });
9220
+ this.dropRunOperationReceipts(run.id);
8568
9221
  const result2 = clone(run);
8569
- if (input.expectedRevision !== void 0) this.writeOperationReceipt(operationKey, operationPayload, result2);
8570
9222
  return result2;
8571
9223
  }
8572
9224
  run.state = "cancelled";
@@ -8577,9 +9229,10 @@ var InMemoryStore = class _InMemoryStore {
8577
9229
  delete run.interactionAnswers;
8578
9230
  run.revision += 1;
8579
9231
  run.updatedAt = createTimestamp();
9232
+ this.touchRun(run);
8580
9233
  this.appendTransition(run, from, "cancelled", { reason: input.reason });
9234
+ this.dropRunOperationReceipts(run.id);
8581
9235
  const result = clone(run);
8582
- if (input.expectedRevision !== void 0) this.writeOperationReceipt(operationKey, operationPayload, result);
8583
9236
  return result;
8584
9237
  }
8585
9238
  snapshotRun(lease, runId) {
@@ -8660,6 +9313,7 @@ var InMemoryStore = class _InMemoryStore {
8660
9313
  const previous = this.consumerCheckpoints.get(input.consumerId);
8661
9314
  if (previous !== void 0 && next <= this.parseCursor(previous)) return;
8662
9315
  this.consumerCheckpoints.set(input.consumerId, input.cursor);
9316
+ this.recorder?.onConsumerCheckpointUpsert(input.consumerId, input.cursor);
8663
9317
  }
8664
9318
  compact(lease, input = {}) {
8665
9319
  this.assertOwner(lease);
@@ -8693,11 +9347,15 @@ var InMemoryStore = class _InMemoryStore {
8693
9347
  }
8694
9348
  const deletedEvents = deleteThrough >= this.retentionFloor ? this.events.filter((event) => this.parseCursor(event.cursor) <= deleteThrough) : [];
8695
9349
  if (deleteThrough >= this.retentionFloor) {
9350
+ const deletedEventIds = this.events.filter((event) => this.parseCursor(event.cursor) <= deleteThrough).map((event) => event.id);
8696
9351
  this.events.splice(0, this.events.length, ...this.events.filter((event) => this.parseCursor(event.cursor) > deleteThrough));
8697
9352
  this.retentionFloor = deleteThrough + 1;
9353
+ this.recorder?.onRetentionFloorChange(this.retentionFloor);
9354
+ this.recorder?.onEventsTruncate(deletedEventIds);
8698
9355
  for (const [consumerId, cursor] of this.consumerCheckpoints) {
8699
9356
  if (this.parseCursor(cursor) < this.retentionFloor - 1) {
8700
9357
  this.consumerCheckpoints.delete(consumerId);
9358
+ this.recorder?.onConsumerCheckpointDelete(consumerId);
8701
9359
  }
8702
9360
  }
8703
9361
  }
@@ -8706,11 +9364,21 @@ var InMemoryStore = class _InMemoryStore {
8706
9364
  const deletedRunIds = deletableRuns.map((run) => run.id);
8707
9365
  const deletedRunSet = new Set(deletedRunIds);
8708
9366
  const deletedTools = [...this.toolCalls.values()].filter((tool) => deletedRunSet.has(tool.runId));
8709
- for (const runId of deletedRunIds) this.runs.delete(runId);
9367
+ for (const runId of deletedRunIds) {
9368
+ this.runs.delete(runId);
9369
+ this.runReceiptKeys.delete(runId);
9370
+ this.recorder?.onRunDelete(runId);
9371
+ }
8710
9372
  for (const [messageId, message] of this.messages) {
8711
- if (deletedRunSet.has(message.runId)) this.messages.delete(messageId);
9373
+ if (deletedRunSet.has(message.runId)) {
9374
+ this.messages.delete(messageId);
9375
+ this.recorder?.onMessageDelete(messageId);
9376
+ }
9377
+ }
9378
+ for (const tool of deletedTools) {
9379
+ this.toolCalls.delete(tool.id);
9380
+ this.recorder?.onToolCallDelete(tool.id);
8712
9381
  }
8713
- for (const tool of deletedTools) this.toolCalls.delete(tool.id);
8714
9382
  return {
8715
9383
  deletedRunIds,
8716
9384
  deletedToolCallIds: deletedTools.map((tool) => tool.id),
@@ -8732,7 +9400,10 @@ var InMemoryStore = class _InMemoryStore {
8732
9400
  return receipt.result;
8733
9401
  }
8734
9402
  writeReceipt(scope, payload, result) {
8735
- this.idempotency.set(encodeIdempotencyScope(this.incarnation, scope), { payload, result: clone(result) });
9403
+ const key = encodeIdempotencyScope(this.incarnation, scope);
9404
+ const receipt = { payload, result: clone(result) };
9405
+ this.idempotency.set(key, receipt);
9406
+ this.recorder?.onIdempotencyUpsert(key, receipt);
8736
9407
  }
8737
9408
  replayOperation(key, payload) {
8738
9409
  const receipt = this.operationReceipts.get(key);
@@ -8740,8 +9411,19 @@ var InMemoryStore = class _InMemoryStore {
8740
9411
  if (receipt.payload !== payload) throw new Error(`Idempotency payload conflict for ${key}.`);
8741
9412
  return receipt.result;
8742
9413
  }
8743
- writeOperationReceipt(key, payload, result) {
8744
- this.operationReceipts.set(key, { payload, result: clone(result) });
9414
+ writeOperationReceipt(key, payload, result, runId) {
9415
+ const receipt = { payload, result: clone(result) };
9416
+ this.operationReceipts.set(key, receipt);
9417
+ const resolvedRunId = runId ?? this.inferRunIdForReceipt(key, payload);
9418
+ if (resolvedRunId) {
9419
+ let keys = this.runReceiptKeys.get(resolvedRunId);
9420
+ if (!keys) {
9421
+ keys = /* @__PURE__ */ new Set();
9422
+ this.runReceiptKeys.set(resolvedRunId, keys);
9423
+ }
9424
+ keys.add(key);
9425
+ }
9426
+ this.recorder?.onOperationReceiptUpsert(key, receipt);
8745
9427
  }
8746
9428
  /**
8747
9429
  * A Claim receipt exists for idempotent replay of one live Execution Attempt
@@ -8749,7 +9431,11 @@ var InMemoryStore = class _InMemoryStore {
8749
9431
  * one history snapshot per Agent instead of one per Attempt.
8750
9432
  */
8751
9433
  dropClaimReceipt(execution) {
8752
- if (execution) this.operationReceipts.delete(`claim:${execution.executionId}`);
9434
+ if (execution) {
9435
+ const key = `claim:${execution.executionId}`;
9436
+ this.operationReceipts.delete(key);
9437
+ this.recorder?.onOperationReceiptDelete(key);
9438
+ }
8753
9439
  }
8754
9440
  /**
8755
9441
  * A new owner fences the Execution Attempts of the previous one, so no Claim
@@ -8765,6 +9451,7 @@ var InMemoryStore = class _InMemoryStore {
8765
9451
  for (const key of [...this.operationReceipts.keys()]) {
8766
9452
  if (key.startsWith("claim:") && !liveExecutions.has(key.slice("claim:".length))) {
8767
9453
  this.operationReceipts.delete(key);
9454
+ this.recorder?.onOperationReceiptDelete(key);
8768
9455
  }
8769
9456
  }
8770
9457
  }
@@ -8803,14 +9490,16 @@ var InMemoryStore = class _InMemoryStore {
8803
9490
  };
8804
9491
  const from = toolCall.state;
8805
9492
  const to = from === "running" ? "indeterminate" : "failed";
8806
- this.messages.set(message.id, message);
9493
+ this.storeMessage(message);
8807
9494
  toolCall.state = to;
8808
9495
  toolCall.result = durableResult;
8809
9496
  toolCall.resultMessageId = message.id;
8810
9497
  if (to === "indeterminate") toolCall.reason = reason;
8811
9498
  toolCall.updatedAt = message.createdAt;
9499
+ this.storeToolCall(toolCall);
8812
9500
  run.revision += 1;
8813
9501
  run.updatedAt = message.createdAt;
9502
+ this.touchRun(run);
8814
9503
  this.appendToolTransition(run, { from, to }, toolCall);
8815
9504
  this.appendMessage(run, message);
8816
9505
  }
@@ -8911,6 +9600,7 @@ ${record.summary}`,
8911
9600
  if (existing?.id === record.id) return clone(existing);
8912
9601
  const next = clone(record);
8913
9602
  this.contextCompactions.set(record.agentId, next);
9603
+ this.recorder?.onContextCompactionUpsert(record.agentId, next);
8914
9604
  return clone(next);
8915
9605
  }
8916
9606
  activeHistory(agentId, beforeSequence) {
@@ -8920,7 +9610,7 @@ ${record.summary}`,
8920
9610
  ].map((message) => ({ ...message, messageRevision: message.messageRevision ?? 0 })));
8921
9611
  }
8922
9612
  appendMessage(run, message) {
8923
- this.events.push(this.baseEvent(run, {
9613
+ this.appendEvent(this.baseEvent(run, {
8924
9614
  kind: "message_committed",
8925
9615
  message
8926
9616
  }));
@@ -8936,13 +9626,14 @@ ${record.summary}`,
8936
9626
  ...to === "failed" && options?.failure ? { failure: options.failure } : {},
8937
9627
  ...to === "cancelled" && options?.reason ? { reason: options.reason } : {}
8938
9628
  };
8939
- this.events.push(transition);
9629
+ this.touchRun(run);
9630
+ this.appendEvent(transition);
8940
9631
  }
8941
9632
  appendToolTransition(run, transition, toolCall) {
8942
- this.events.push(this.baseEvent(run, {
9633
+ this.appendEvent(this.baseEvent(run, {
8943
9634
  kind: "tool_state_changed",
8944
9635
  transition,
8945
- toolCall
9636
+ toolCall: clone(toolCall)
8946
9637
  }));
8947
9638
  }
8948
9639
  baseEvent(run, extra) {
@@ -9318,6 +10009,94 @@ var CURRENT_SCHEMA_SQL = `
9318
10009
  CREATE INDEX runs_active_idx ON runs (agent_id, state, agent_sequence);
9319
10010
  CREATE INDEX run_events_sequence_idx ON run_events (sequence);
9320
10011
  `;
10012
+ var ChangeTracker = class {
10013
+ upsertedAgents = /* @__PURE__ */ new Map();
10014
+ deletedAgentIds = /* @__PURE__ */ new Set();
10015
+ upsertedRuns = /* @__PURE__ */ new Map();
10016
+ deletedRunIds = /* @__PURE__ */ new Set();
10017
+ insertedMessages = /* @__PURE__ */ new Map();
10018
+ deletedMessageIds = /* @__PURE__ */ new Set();
10019
+ upsertedToolCalls = /* @__PURE__ */ new Map();
10020
+ deletedToolCallIds = /* @__PURE__ */ new Set();
10021
+ appendedEvents = [];
10022
+ truncatedEventIds = /* @__PURE__ */ new Set();
10023
+ upsertedIdempotency = /* @__PURE__ */ new Map();
10024
+ deletedIdempotencyScopes = /* @__PURE__ */ new Set();
10025
+ upsertedOperationReceipts = /* @__PURE__ */ new Map();
10026
+ deletedOperationReceiptKeys = /* @__PURE__ */ new Set();
10027
+ upsertedConsumerCheckpoints = /* @__PURE__ */ new Map();
10028
+ deletedConsumerCheckpoints = /* @__PURE__ */ new Set();
10029
+ upsertedContextCompactions = /* @__PURE__ */ new Map();
10030
+ retentionFloorChanged;
10031
+ onAgentUpsert(agent) {
10032
+ this.deletedAgentIds.delete(agent.id);
10033
+ this.upsertedAgents.set(agent.id, agent);
10034
+ }
10035
+ onAgentDelete(agentId) {
10036
+ this.upsertedAgents.delete(agentId);
10037
+ this.deletedAgentIds.add(agentId);
10038
+ }
10039
+ onRunUpsert(run) {
10040
+ this.deletedRunIds.delete(run.id);
10041
+ this.upsertedRuns.set(run.id, run);
10042
+ }
10043
+ onRunDelete(runId) {
10044
+ this.upsertedRuns.delete(runId);
10045
+ this.deletedRunIds.add(runId);
10046
+ }
10047
+ onMessageInsert(message) {
10048
+ this.deletedMessageIds.delete(message.id);
10049
+ this.insertedMessages.set(message.id, message);
10050
+ }
10051
+ onMessageDelete(messageId) {
10052
+ this.insertedMessages.delete(messageId);
10053
+ this.deletedMessageIds.add(messageId);
10054
+ }
10055
+ onToolCallUpsert(toolCall) {
10056
+ this.deletedToolCallIds.delete(toolCall.id);
10057
+ this.upsertedToolCalls.set(toolCall.id, toolCall);
10058
+ }
10059
+ onToolCallDelete(toolCallId) {
10060
+ this.upsertedToolCalls.delete(toolCallId);
10061
+ this.deletedToolCallIds.add(toolCallId);
10062
+ }
10063
+ onEventAppend(event) {
10064
+ this.appendedEvents.push(event);
10065
+ }
10066
+ onEventsTruncate(deletedEventIds) {
10067
+ for (const id of deletedEventIds) this.truncatedEventIds.add(id);
10068
+ }
10069
+ onIdempotencyUpsert(scope, receipt) {
10070
+ this.deletedIdempotencyScopes.delete(scope);
10071
+ this.upsertedIdempotency.set(scope, receipt);
10072
+ }
10073
+ onIdempotencyDelete(scope) {
10074
+ this.upsertedIdempotency.delete(scope);
10075
+ this.deletedIdempotencyScopes.add(scope);
10076
+ }
10077
+ onOperationReceiptUpsert(key, receipt) {
10078
+ this.deletedOperationReceiptKeys.delete(key);
10079
+ this.upsertedOperationReceipts.set(key, receipt);
10080
+ }
10081
+ onOperationReceiptDelete(key) {
10082
+ this.upsertedOperationReceipts.delete(key);
10083
+ this.deletedOperationReceiptKeys.add(key);
10084
+ }
10085
+ onConsumerCheckpointUpsert(consumerId, cursor) {
10086
+ this.deletedConsumerCheckpoints.delete(consumerId);
10087
+ this.upsertedConsumerCheckpoints.set(consumerId, cursor);
10088
+ }
10089
+ onConsumerCheckpointDelete(consumerId) {
10090
+ this.upsertedConsumerCheckpoints.delete(consumerId);
10091
+ this.deletedConsumerCheckpoints.add(consumerId);
10092
+ }
10093
+ onContextCompactionUpsert(agentId, record) {
10094
+ this.upsertedContextCompactions.set(agentId, record);
10095
+ }
10096
+ onRetentionFloorChange(floor) {
10097
+ this.retentionFloorChanged = floor;
10098
+ }
10099
+ };
9321
10100
  var SqliteStore = class {
9322
10101
  database;
9323
10102
  archiveRoot;
@@ -9326,6 +10105,25 @@ var SqliteStore = class {
9326
10105
  mirroredAgents = /* @__PURE__ */ new Set();
9327
10106
  closed = false;
9328
10107
  configured = false;
10108
+ cachedMemory;
10109
+ cachedOwner;
10110
+ isLegacyState = false;
10111
+ stmtUpsertAgent;
10112
+ stmtDeleteAgent;
10113
+ stmtUpsertRun;
10114
+ stmtDeleteRun;
10115
+ stmtInsertMessage;
10116
+ stmtDeleteMessage;
10117
+ stmtUpsertToolCall;
10118
+ stmtDeleteToolCall;
10119
+ stmtInsertEvent;
10120
+ stmtDeleteEvent;
10121
+ stmtUpsertIdempotency;
10122
+ stmtDeleteIdempotency;
10123
+ stmtUpsertCheckpoint;
10124
+ stmtDeleteCheckpoint;
10125
+ stmtUpdateMeta;
10126
+ stmtUpdateState;
9329
10127
  constructor(filename = ":memory:") {
9330
10128
  this.database = new Database(filename, { create: true, readwrite: true, strict: true });
9331
10129
  this.archiveRoot = filename === ":memory:" ? join6(tmpdir2(), `rowan-context-archives-${crypto.randomUUID()}`) : join6(dirname7(resolve8(filename)), "context-archives");
@@ -9346,7 +10144,26 @@ var SqliteStore = class {
9346
10144
  }
9347
10145
  const replay = ownerLease(row);
9348
10146
  this.writeOwner({ ...row, expires_at: now + input.leaseMs });
9349
- return { ...replay, expiresAt: new Date(now + input.leaseMs).toISOString() };
10147
+ const newLease2 = { ...replay, expiresAt: new Date(now + input.leaseMs).toISOString() };
10148
+ if (!this.cachedMemory || this.cachedOwner?.epoch !== replay.epoch) {
10149
+ const state2 = this.readState();
10150
+ const memory2 = InMemoryStore.fromState(state2);
10151
+ memory2.attachOwner(newLease2);
10152
+ const tracker2 = new ChangeTracker();
10153
+ memory2.setChangeRecorder(tracker2);
10154
+ try {
10155
+ memory2.dropSettledClaimReceipts();
10156
+ memory2.compactTerminalReceipts();
10157
+ } finally {
10158
+ memory2.setChangeRecorder(void 0);
10159
+ }
10160
+ this.ensureTableBackedStorage(memory2, tracker2, this.isLegacyState);
10161
+ this.cachedMemory = memory2;
10162
+ } else {
10163
+ this.cachedMemory.attachOwner(newLease2);
10164
+ }
10165
+ this.cachedOwner = newLease2;
10166
+ return newLease2;
9350
10167
  }
9351
10168
  const state = this.readState();
9352
10169
  const memory = InMemoryStore.fromState(state);
@@ -9365,22 +10182,42 @@ var SqliteStore = class {
9365
10182
  released_epoch: row.released_epoch
9366
10183
  };
9367
10184
  this.writeOwner(next);
9368
- memory.dropSettledClaimReceipts();
9369
- this.persistState(memory.exportState());
9370
- return {
10185
+ const newLease = {
9371
10186
  ownerId: input.ownerId,
9372
10187
  token,
9373
10188
  epoch,
9374
10189
  expiresAt: new Date(now + input.leaseMs).toISOString()
9375
10190
  };
10191
+ memory.attachOwner(newLease);
10192
+ const tracker = new ChangeTracker();
10193
+ memory.setChangeRecorder(tracker);
10194
+ try {
10195
+ memory.dropSettledClaimReceipts();
10196
+ memory.compactTerminalReceipts();
10197
+ } finally {
10198
+ memory.setChangeRecorder(void 0);
10199
+ }
10200
+ this.ensureTableBackedStorage(memory, tracker, this.isLegacyState);
10201
+ this.cachedMemory = memory;
10202
+ this.cachedOwner = newLease;
10203
+ return newLease;
9376
10204
  });
9377
10205
  return new SqliteOwnedStore(this, lease);
9378
10206
  }
9379
10207
  close() {
9380
10208
  if (this.closed) return;
9381
10209
  this.closed = true;
10210
+ this.cachedMemory = void 0;
10211
+ this.cachedOwner = void 0;
9382
10212
  this.database.close();
9383
10213
  }
10214
+ exportState() {
10215
+ if (this.cachedMemory) {
10216
+ return this.cachedMemory.exportState();
10217
+ }
10218
+ const state = this.readState();
10219
+ return InMemoryStore.fromState(state).exportState();
10220
+ }
9384
10221
  /** Filesystem location owned by this Store for one Agent's readable archive. */
9385
10222
  contextArchiveDir(agentId) {
9386
10223
  return join6(this.archiveRoot, String(agentId));
@@ -9465,31 +10302,7 @@ var SqliteStore = class {
9465
10302
  }
9466
10303
  async listEvents(lease, input = {}) {
9467
10304
  this.assertOpen();
9468
- return this.readTransaction(() => {
9469
- this.requireMatchingOwner(lease, true);
9470
- const retentionRow = this.database.query(
9471
- "SELECT value FROM runtime_meta WHERE key = ?"
9472
- ).get(RETENTION_FLOOR_KEY);
9473
- const retentionFloor = Math.max(1, Number(retentionRow?.value ?? "1"));
9474
- const after = input.after ? parseEventCursor(lease, input.after) : retentionFloor - 1;
9475
- if (input.after && after < retentionFloor - 1) {
9476
- throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "expired" });
9477
- }
9478
- if (input.after) {
9479
- const latest = this.database.query(
9480
- "SELECT max(sequence) AS sequence FROM run_events"
9481
- ).get();
9482
- const waterline = latest.sequence ?? 0;
9483
- if (after > waterline) {
9484
- throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "beyond_waterline" });
9485
- }
9486
- if (after === waterline) return [];
9487
- }
9488
- const rows = this.database.query(
9489
- "SELECT payload_json FROM run_events ORDER BY sequence"
9490
- ).all();
9491
- return rows.map((row) => JSON.parse(row.payload_json)).filter((event) => parseEventCursor(lease, event.cursor) > after);
9492
- });
10305
+ return this.read(lease, (memory) => memory.listEvents(lease, input));
9493
10306
  }
9494
10307
  async openConsumer(lease, consumerId) {
9495
10308
  return this.invoke(lease, (store, current) => store.openConsumer(current, consumerId), false);
@@ -9513,7 +10326,12 @@ var SqliteStore = class {
9513
10326
  const row = this.requireMatchingOwner(lease, false);
9514
10327
  const expiresAt = Date.now() + leaseMs;
9515
10328
  this.writeOwner({ ...row, expires_at: expiresAt });
9516
- return { ...lease, expiresAt: new Date(expiresAt).toISOString() };
10329
+ const nextLease = { ...lease, expiresAt: new Date(expiresAt).toISOString() };
10330
+ if (this.cachedOwner && this.cachedOwner.token === lease.token) {
10331
+ this.cachedOwner = nextLease;
10332
+ this.cachedMemory?.attachOwner(nextLease);
10333
+ }
10334
+ return nextLease;
9517
10335
  });
9518
10336
  }
9519
10337
  async sealAndReleaseOwner(lease) {
@@ -9524,27 +10342,75 @@ var SqliteStore = class {
9524
10342
  if (row.owner_id !== lease.ownerId || row.owner_token !== lease.token || row.epoch !== lease.epoch) {
9525
10343
  throw ownershipLost2(lease, row);
9526
10344
  }
9527
- const memory = InMemoryStore.fromState(this.readState());
9528
- memory.attachOwner(lease);
9529
- memory.interruptOwner(lease.epoch, "The Runtime owner was sealed.");
9530
- this.persistState(memory.exportState());
10345
+ if (!this.cachedMemory || this.cachedOwner?.epoch !== lease.epoch) {
10346
+ this.cachedMemory = InMemoryStore.fromState(this.readState());
10347
+ this.cachedOwner = lease;
10348
+ }
10349
+ const tracker = new ChangeTracker();
10350
+ this.cachedMemory.setChangeRecorder(tracker);
10351
+ try {
10352
+ this.cachedMemory.attachOwner(lease);
10353
+ this.cachedMemory.interruptOwner(lease.epoch, "The Runtime owner was sealed.");
10354
+ } finally {
10355
+ this.cachedMemory.setChangeRecorder(void 0);
10356
+ }
10357
+ this.persistChanges(tracker, this.cachedMemory);
10358
+ this.mirrorChanges(tracker);
9531
10359
  this.writeOwner({ ...row, owner_id: null, owner_token: null, expires_at: null, released_epoch: lease.epoch });
10360
+ this.cachedMemory = void 0;
10361
+ this.cachedOwner = void 0;
9532
10362
  });
9533
10363
  }
9534
- invoke(lease, operation, persist = true) {
10364
+ read(lease, operation) {
10365
+ this.assertOpen();
10366
+ this.requireMatchingOwner(lease, true);
10367
+ if (!this.cachedMemory || this.cachedOwner?.epoch !== lease.epoch) {
10368
+ this.cachedMemory = InMemoryStore.fromState(this.readState());
10369
+ this.cachedMemory.attachOwner(lease);
10370
+ this.cachedOwner = lease;
10371
+ } else {
10372
+ this.cachedMemory.attachOwner(lease);
10373
+ }
10374
+ return operation(this.cachedMemory);
10375
+ }
10376
+ write(lease, operation) {
9535
10377
  this.assertOpen();
9536
10378
  return this.immediateTransaction(() => {
9537
10379
  this.requireMatchingOwner(lease, true);
9538
- const memory = InMemoryStore.fromState(this.readState());
9539
- memory.attachOwner(lease);
9540
- const result = operation(memory, lease);
9541
- if (persist) {
9542
- this.requireMatchingOwner(lease, true);
9543
- this.persistState(memory.exportState());
10380
+ if (!this.cachedMemory || this.cachedOwner?.epoch !== lease.epoch) {
10381
+ this.cachedMemory = InMemoryStore.fromState(this.readState());
10382
+ this.cachedMemory.attachOwner(lease);
10383
+ this.cachedOwner = lease;
10384
+ } else {
10385
+ this.cachedMemory.attachOwner(lease);
10386
+ }
10387
+ const tracker = new ChangeTracker();
10388
+ this.cachedMemory.setChangeRecorder(tracker);
10389
+ let result;
10390
+ try {
10391
+ result = operation(this.cachedMemory);
10392
+ } catch (error) {
10393
+ this.cachedMemory.setChangeRecorder(void 0);
10394
+ this.cachedMemory = void 0;
10395
+ throw error;
10396
+ }
10397
+ this.cachedMemory.setChangeRecorder(void 0);
10398
+ try {
10399
+ this.persistChanges(tracker, this.cachedMemory);
10400
+ this.mirrorChanges(tracker);
10401
+ } catch (error) {
10402
+ this.cachedMemory = void 0;
10403
+ throw error;
9544
10404
  }
9545
10405
  return result;
9546
10406
  });
9547
10407
  }
10408
+ invoke(lease, operation, persist = true) {
10409
+ if (persist) {
10410
+ return this.write(lease, (memory) => operation(memory, lease));
10411
+ }
10412
+ return this.read(lease, (memory) => operation(memory, lease));
10413
+ }
9548
10414
  ensureSchema() {
9549
10415
  const catalog = this.catalog();
9550
10416
  if (catalog.length > 0 && !this.isCurrentSchema(catalog)) {
@@ -9561,7 +10427,7 @@ var SqliteStore = class {
9561
10427
  this.database.run("INSERT INTO runtime_meta (key, value) VALUES (?, ?)", [RETENTION_FLOOR_KEY, "1"]);
9562
10428
  const state = new InMemoryStore();
9563
10429
  this.database.run("INSERT INTO runtime_owner (singleton, epoch) VALUES (?, ?)", [STATE_ROW, 0]);
9564
- this.database.run("INSERT INTO runtime_state (singleton, state_json) VALUES (?, ?)", [STATE_ROW, JSON.stringify(state.exportState())]);
10430
+ this.database.run("INSERT INTO runtime_state (singleton, state_json) VALUES (?, ?)", [STATE_ROW, JSON.stringify(state.exportMetadata())]);
9565
10431
  });
9566
10432
  return;
9567
10433
  }
@@ -9600,13 +10466,267 @@ var SqliteStore = class {
9600
10466
  readState() {
9601
10467
  const row = this.database.query("SELECT state_json FROM runtime_state WHERE singleton = ?").get(STATE_ROW);
9602
10468
  if (!row) throw new RuntimeError("store_unavailable", { operation: "read_state", retryable: false, reason: "runtime_state row is missing" });
9603
- return JSON.parse(row.state_json);
10469
+ const parsed = JSON.parse(row.state_json);
10470
+ if (parsed.agents && Array.isArray(parsed.agents)) {
10471
+ this.isLegacyState = true;
10472
+ return parsed;
10473
+ }
10474
+ this.isLegacyState = false;
10475
+ return this.loadTableBackedState(parsed);
10476
+ }
10477
+ loadTableBackedState(metadata) {
10478
+ const agentRows = this.database.query(
10479
+ "SELECT id, metadata_json, config_token, config_identity, created_at, activated_at, updated_at FROM agents ORDER BY created_at"
10480
+ ).all();
10481
+ const agents = agentRows.map((row) => ({
10482
+ id: row.id,
10483
+ ...row.metadata_json ? { metadata: JSON.parse(row.metadata_json) } : {},
10484
+ ...row.config_token ? { currentConfigToken: row.config_token } : {},
10485
+ ...row.config_identity ? { currentConfigIdentity: row.config_identity } : {},
10486
+ createdAt: row.created_at,
10487
+ ...row.activated_at ? { activatedAt: row.activated_at } : {},
10488
+ updatedAt: row.updated_at
10489
+ }));
10490
+ const runRows = this.database.query("SELECT payload_json FROM runs").all();
10491
+ const runs = runRows.map((r) => JSON.parse(r.payload_json));
10492
+ const messageRows = this.database.query("SELECT payload_json FROM messages ORDER BY run_id, sequence_within_run").all();
10493
+ const messages = messageRows.map((m) => JSON.parse(m.payload_json));
10494
+ const toolRows = this.database.query("SELECT payload_json FROM tool_calls").all();
10495
+ const toolCalls = toolRows.map((t) => JSON.parse(t.payload_json));
10496
+ const eventRows = this.database.query("SELECT payload_json FROM run_events ORDER BY sequence").all();
10497
+ const events = eventRows.map((e) => JSON.parse(e.payload_json));
10498
+ const idempotencyRows = this.database.query("SELECT scope, payload_json, result_json FROM idempotency").all();
10499
+ const idempotency = [];
10500
+ const operationReceipts = [];
10501
+ for (const row of idempotencyRows) {
10502
+ const receipt = { payload: row.payload_json, result: JSON.parse(row.result_json) };
10503
+ if (row.scope.startsWith("operation:")) {
10504
+ operationReceipts.push([row.scope.slice("operation:".length), receipt]);
10505
+ } else {
10506
+ idempotency.push([row.scope, receipt]);
10507
+ }
10508
+ }
10509
+ const cpRows = this.database.query("SELECT consumer_id, sequence FROM consumer_checkpoints").all();
10510
+ const consumerCheckpoints = cpRows.map((r) => [r.consumer_id, `${metadata.incarnation}:${r.sequence}`]);
10511
+ const retentionRow = this.database.query("SELECT value FROM runtime_meta WHERE key = ?").get(RETENTION_FLOOR_KEY);
10512
+ const retentionFloor = Math.max(1, Number(retentionRow?.value ?? metadata.retentionFloor ?? "1"));
10513
+ return {
10514
+ incarnation: metadata.incarnation,
10515
+ agents,
10516
+ runs,
10517
+ messages,
10518
+ toolCalls,
10519
+ events,
10520
+ idempotency,
10521
+ operationReceipts,
10522
+ historySeeds: metadata.historySeeds ?? [],
10523
+ consumerCheckpoints,
10524
+ retentionFloor,
10525
+ nextAgentSequence: metadata.nextAgentSequence ?? [],
10526
+ nextReadySequence: metadata.nextReadySequence ?? [],
10527
+ eventSequence: metadata.eventSequence ?? 0,
10528
+ contextCompactions: metadata.contextCompactions ?? []
10529
+ };
10530
+ }
10531
+ ensureTableBackedStorage(memory, tracker, isLegacy) {
10532
+ if (isLegacy) {
10533
+ this.writeFullStateToTables(memory);
10534
+ this.isLegacyState = false;
10535
+ } else {
10536
+ this.persistChanges(tracker, memory);
10537
+ this.mirrorChanges(tracker);
10538
+ }
10539
+ }
10540
+ getStmtUpsertAgent() {
10541
+ return this.stmtUpsertAgent ??= this.database.prepare(
10542
+ `INSERT INTO agents (id, metadata_json, config_token, config_identity, created_at, activated_at, updated_at)
10543
+ VALUES (?, ?, ?, ?, ?, ?, ?)
10544
+ ON CONFLICT(id) DO UPDATE SET
10545
+ metadata_json = excluded.metadata_json,
10546
+ config_token = excluded.config_token,
10547
+ config_identity = excluded.config_identity,
10548
+ activated_at = excluded.activated_at,
10549
+ updated_at = excluded.updated_at`
10550
+ );
10551
+ }
10552
+ getStmtDeleteAgent() {
10553
+ return this.stmtDeleteAgent ??= this.database.prepare("DELETE FROM agents WHERE id = ?");
10554
+ }
10555
+ getStmtUpsertRun() {
10556
+ return this.stmtUpsertRun ??= this.database.prepare(
10557
+ `INSERT INTO runs (id, agent_id, agent_sequence, state, revision, payload_json, created_at, updated_at)
10558
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
10559
+ ON CONFLICT(id) DO UPDATE SET
10560
+ state = excluded.state,
10561
+ revision = excluded.revision,
10562
+ payload_json = excluded.payload_json,
10563
+ updated_at = excluded.updated_at`
10564
+ );
10565
+ }
10566
+ getStmtDeleteRun() {
10567
+ return this.stmtDeleteRun ??= this.database.prepare("DELETE FROM runs WHERE id = ?");
10568
+ }
10569
+ getStmtInsertMessage() {
10570
+ return this.stmtInsertMessage ??= this.database.prepare(
10571
+ `INSERT INTO messages (id, run_id, sequence_within_run, payload_json, created_at)
10572
+ VALUES (?, ?, ?, ?, ?)
10573
+ ON CONFLICT(id) DO UPDATE SET payload_json = excluded.payload_json`
10574
+ );
10575
+ }
10576
+ getStmtDeleteMessage() {
10577
+ return this.stmtDeleteMessage ??= this.database.prepare("DELETE FROM messages WHERE id = ?");
9604
10578
  }
9605
- writeState(state) {
9606
- this.database.run("UPDATE runtime_state SET state_json = ? WHERE singleton = ?", [JSON.stringify(state), STATE_ROW]);
10579
+ getStmtUpsertToolCall() {
10580
+ return this.stmtUpsertToolCall ??= this.database.prepare(
10581
+ `INSERT INTO tool_calls (id, run_id, execution_id, state, payload_json)
10582
+ VALUES (?, ?, ?, ?, ?)
10583
+ ON CONFLICT(id) DO UPDATE SET state = excluded.state, payload_json = excluded.payload_json`
10584
+ );
9607
10585
  }
9608
- persistState(state) {
9609
- this.writeState(state);
10586
+ getStmtDeleteToolCall() {
10587
+ return this.stmtDeleteToolCall ??= this.database.prepare("DELETE FROM tool_calls WHERE id = ?");
10588
+ }
10589
+ getStmtInsertEvent() {
10590
+ return this.stmtInsertEvent ??= this.database.prepare(
10591
+ "INSERT INTO run_events (id, run_id, payload_json, created_at) VALUES (?, ?, ?, ?)"
10592
+ );
10593
+ }
10594
+ getStmtDeleteEvent() {
10595
+ return this.stmtDeleteEvent ??= this.database.prepare("DELETE FROM run_events WHERE id = ?");
10596
+ }
10597
+ getStmtUpsertIdempotency() {
10598
+ return this.stmtUpsertIdempotency ??= this.database.prepare(
10599
+ `INSERT INTO idempotency (scope, payload_json, result_json)
10600
+ VALUES (?, ?, ?)
10601
+ ON CONFLICT(scope) DO UPDATE SET payload_json = excluded.payload_json, result_json = excluded.result_json`
10602
+ );
10603
+ }
10604
+ getStmtDeleteIdempotency() {
10605
+ return this.stmtDeleteIdempotency ??= this.database.prepare("DELETE FROM idempotency WHERE scope = ?");
10606
+ }
10607
+ getStmtUpsertCheckpoint() {
10608
+ return this.stmtUpsertCheckpoint ??= this.database.prepare(
10609
+ `INSERT INTO consumer_checkpoints (consumer_id, sequence, event_id, updated_at)
10610
+ VALUES (?, ?, ?, ?)
10611
+ ON CONFLICT(consumer_id) DO UPDATE SET sequence = excluded.sequence, updated_at = excluded.updated_at`
10612
+ );
10613
+ }
10614
+ getStmtDeleteCheckpoint() {
10615
+ return this.stmtDeleteCheckpoint ??= this.database.prepare("DELETE FROM consumer_checkpoints WHERE consumer_id = ?");
10616
+ }
10617
+ getStmtUpdateMeta() {
10618
+ return this.stmtUpdateMeta ??= this.database.prepare(
10619
+ "INSERT INTO runtime_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
10620
+ );
10621
+ }
10622
+ getStmtUpdateState() {
10623
+ return this.stmtUpdateState ??= this.database.prepare(
10624
+ "UPDATE runtime_state SET state_json = ? WHERE singleton = ?"
10625
+ );
10626
+ }
10627
+ persistChanges(tracker, memory) {
10628
+ for (const agent of tracker.upsertedAgents.values()) {
10629
+ this.getStmtUpsertAgent().run(
10630
+ agent.id,
10631
+ agent.metadata === void 0 ? null : JSON.stringify(agent.metadata),
10632
+ agent.currentConfigToken ?? null,
10633
+ agent.currentConfigIdentity ?? null,
10634
+ agent.createdAt,
10635
+ agent.activatedAt ?? null,
10636
+ agent.updatedAt
10637
+ );
10638
+ }
10639
+ for (const agentId of tracker.deletedAgentIds) {
10640
+ this.getStmtDeleteAgent().run(agentId);
10641
+ }
10642
+ for (const run of tracker.upsertedRuns.values()) {
10643
+ this.getStmtUpsertRun().run(
10644
+ run.id,
10645
+ run.agentId,
10646
+ run.agentSequence,
10647
+ run.state,
10648
+ run.revision,
10649
+ JSON.stringify(run),
10650
+ run.createdAt,
10651
+ run.updatedAt
10652
+ );
10653
+ }
10654
+ for (const runId of tracker.deletedRunIds) {
10655
+ this.getStmtDeleteRun().run(runId);
10656
+ }
10657
+ for (const message of tracker.insertedMessages.values()) {
10658
+ this.getStmtInsertMessage().run(
10659
+ message.id,
10660
+ message.runId,
10661
+ message.sequenceWithinRun,
10662
+ JSON.stringify(message),
10663
+ message.createdAt
10664
+ );
10665
+ }
10666
+ for (const messageId of tracker.deletedMessageIds) {
10667
+ this.getStmtDeleteMessage().run(messageId);
10668
+ }
10669
+ for (const toolCall of tracker.upsertedToolCalls.values()) {
10670
+ this.getStmtUpsertToolCall().run(
10671
+ toolCall.id,
10672
+ toolCall.runId,
10673
+ toolCall.executionId,
10674
+ toolCall.state,
10675
+ JSON.stringify(toolCall)
10676
+ );
10677
+ }
10678
+ for (const toolCallId of tracker.deletedToolCallIds) {
10679
+ this.getStmtDeleteToolCall().run(toolCallId);
10680
+ }
10681
+ for (const event of tracker.appendedEvents) {
10682
+ this.getStmtInsertEvent().run(
10683
+ event.id,
10684
+ event.runId,
10685
+ JSON.stringify(event),
10686
+ event.createdAt
10687
+ );
10688
+ }
10689
+ for (const eventId of tracker.truncatedEventIds) {
10690
+ this.getStmtDeleteEvent().run(eventId);
10691
+ }
10692
+ for (const [scope, receipt] of tracker.upsertedIdempotency) {
10693
+ this.getStmtUpsertIdempotency().run(
10694
+ scope,
10695
+ receipt.payload,
10696
+ JSON.stringify(receipt.result)
10697
+ );
10698
+ }
10699
+ for (const scope of tracker.deletedIdempotencyScopes) {
10700
+ this.getStmtDeleteIdempotency().run(scope);
10701
+ }
10702
+ for (const [key, receipt] of tracker.upsertedOperationReceipts) {
10703
+ this.getStmtUpsertIdempotency().run(
10704
+ `operation:${key}`,
10705
+ receipt.payload,
10706
+ JSON.stringify(receipt.result)
10707
+ );
10708
+ }
10709
+ for (const key of tracker.deletedOperationReceiptKeys) {
10710
+ this.getStmtDeleteIdempotency().run(`operation:${key}`);
10711
+ }
10712
+ for (const [consumerId, cursor] of tracker.upsertedConsumerCheckpoints) {
10713
+ this.getStmtUpsertCheckpoint().run(
10714
+ consumerId,
10715
+ Number(String(cursor).split(":").at(-1)),
10716
+ null,
10717
+ (/* @__PURE__ */ new Date()).toISOString()
10718
+ );
10719
+ }
10720
+ for (const consumerId of tracker.deletedConsumerCheckpoints) {
10721
+ this.getStmtDeleteCheckpoint().run(consumerId);
10722
+ }
10723
+ if (tracker.retentionFloorChanged !== void 0) {
10724
+ this.getStmtUpdateMeta().run(RETENTION_FLOOR_KEY, String(tracker.retentionFloorChanged));
10725
+ }
10726
+ this.getStmtUpdateState().run(JSON.stringify(memory.exportMetadata()), STATE_ROW);
10727
+ }
10728
+ writeFullStateToTables(memory) {
10729
+ const state = memory.exportState();
9610
10730
  this.database.run(
9611
10731
  "INSERT INTO runtime_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
9612
10732
  [RETENTION_FLOOR_KEY, String(Math.max(1, state.retentionFloor ?? 1))]
@@ -9615,57 +10735,149 @@ var SqliteStore = class {
9615
10735
  this.database.run(`DELETE FROM ${table}`);
9616
10736
  }
9617
10737
  for (const agent of state.agents) {
9618
- this.database.run(
9619
- "INSERT INTO agents (id, metadata_json, config_token, config_identity, created_at, activated_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
9620
- [agent.id, agent.metadata === void 0 ? null : JSON.stringify(agent.metadata), agent.currentConfigToken ?? null, agent.currentConfigIdentity ?? null, agent.createdAt, agent.activatedAt ?? null, agent.updatedAt]
10738
+ this.getStmtUpsertAgent().run(
10739
+ agent.id,
10740
+ agent.metadata === void 0 ? null : JSON.stringify(agent.metadata),
10741
+ agent.currentConfigToken ?? null,
10742
+ agent.currentConfigIdentity ?? null,
10743
+ agent.createdAt,
10744
+ agent.activatedAt ?? null,
10745
+ agent.updatedAt
9621
10746
  );
9622
10747
  }
9623
10748
  for (const run of state.runs) {
9624
- this.database.run(
9625
- "INSERT INTO runs (id, agent_id, agent_sequence, state, revision, payload_json, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
9626
- [run.id, run.agentId, run.agentSequence, run.state, run.revision, JSON.stringify(run), run.createdAt, run.updatedAt]
10749
+ this.getStmtUpsertRun().run(
10750
+ run.id,
10751
+ run.agentId,
10752
+ run.agentSequence,
10753
+ run.state,
10754
+ run.revision,
10755
+ JSON.stringify(run),
10756
+ run.createdAt,
10757
+ run.updatedAt
9627
10758
  );
9628
10759
  }
9629
10760
  for (const message of state.messages) {
9630
- this.database.run(
9631
- "INSERT INTO messages (id, run_id, sequence_within_run, payload_json, created_at) VALUES (?, ?, ?, ?, ?)",
9632
- [message.id, message.runId, message.sequenceWithinRun, JSON.stringify(message), message.createdAt]
10761
+ this.getStmtInsertMessage().run(
10762
+ message.id,
10763
+ message.runId,
10764
+ message.sequenceWithinRun,
10765
+ JSON.stringify(message),
10766
+ message.createdAt
9633
10767
  );
9634
10768
  }
9635
10769
  for (const toolCall of state.toolCalls) {
9636
- this.database.run(
9637
- "INSERT INTO tool_calls (id, run_id, execution_id, state, payload_json) VALUES (?, ?, ?, ?, ?)",
9638
- [toolCall.id, toolCall.runId, toolCall.executionId, toolCall.state, JSON.stringify(toolCall)]
10770
+ this.getStmtUpsertToolCall().run(
10771
+ toolCall.id,
10772
+ toolCall.runId,
10773
+ toolCall.executionId,
10774
+ toolCall.state,
10775
+ JSON.stringify(toolCall)
9639
10776
  );
9640
10777
  }
9641
10778
  for (const event of state.events) {
9642
- this.database.run(
9643
- "INSERT INTO run_events (id, run_id, payload_json, created_at) VALUES (?, ?, ?, ?)",
9644
- [event.id, event.runId, JSON.stringify(event), event.createdAt]
10779
+ this.getStmtInsertEvent().run(
10780
+ event.id,
10781
+ event.runId,
10782
+ JSON.stringify(event),
10783
+ event.createdAt
9645
10784
  );
9646
10785
  }
9647
10786
  for (const [scope, receipt] of state.idempotency) {
9648
- this.database.run(
9649
- "INSERT INTO idempotency (scope, payload_json, result_json) VALUES (?, ?, ?)",
9650
- [scope, receipt.payload, JSON.stringify(receipt.result)]
10787
+ this.getStmtUpsertIdempotency().run(
10788
+ scope,
10789
+ receipt.payload,
10790
+ JSON.stringify(receipt.result)
9651
10791
  );
9652
10792
  }
9653
10793
  for (const [scope, receipt] of state.operationReceipts) {
9654
- this.database.run(
9655
- "INSERT INTO idempotency (scope, payload_json, result_json) VALUES (?, ?, ?)",
9656
- [`operation:${scope}`, receipt.payload, JSON.stringify(receipt.result)]
10794
+ this.getStmtUpsertIdempotency().run(
10795
+ `operation:${scope}`,
10796
+ receipt.payload,
10797
+ JSON.stringify(receipt.result)
9657
10798
  );
9658
10799
  }
9659
10800
  for (const [consumerId, cursor] of state.consumerCheckpoints ?? []) {
9660
- this.database.run(
9661
- "INSERT INTO consumer_checkpoints (consumer_id, sequence, event_id, updated_at) VALUES (?, ?, ?, ?)",
9662
- [consumerId, Number(String(cursor).split(":").at(-1)), null, (/* @__PURE__ */ new Date()).toISOString()]
10801
+ this.getStmtUpsertCheckpoint().run(
10802
+ consumerId,
10803
+ Number(String(cursor).split(":").at(-1)),
10804
+ null,
10805
+ (/* @__PURE__ */ new Date()).toISOString()
9663
10806
  );
9664
10807
  }
9665
- this.mirrorState(state);
10808
+ this.getStmtUpdateState().run(JSON.stringify(memory.exportMetadata()), STATE_ROW);
10809
+ this.mirrorInitialState(state);
10810
+ }
10811
+ mirrorChanges(tracker) {
10812
+ for (const agentId of tracker.deletedAgentIds) {
10813
+ const idStr = String(agentId);
10814
+ rmSync(join6(this.archiveRoot, idStr), { recursive: true, force: true });
10815
+ this.mirroredEventIds.delete(idStr);
10816
+ this.mirroredCompactionIds.delete(idStr);
10817
+ this.mirroredAgents.delete(idStr);
10818
+ }
10819
+ for (const agent of tracker.upsertedAgents.values()) {
10820
+ const idStr = String(agent.id);
10821
+ this.mirroredAgents.add(idStr);
10822
+ const archiveDir = join6(this.archiveRoot, idStr);
10823
+ const toolDir = join6(archiveDir, "tool-results");
10824
+ mkdirSync(toolDir, { recursive: true, mode: 448 });
10825
+ chmodSync(archiveDir, 448);
10826
+ chmodSync(toolDir, 448);
10827
+ }
10828
+ if (tracker.appendedEvents.length > 0) {
10829
+ mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
10830
+ chmodSync(this.archiveRoot, 448);
10831
+ const eventsByAgent = /* @__PURE__ */ new Map();
10832
+ for (const event of tracker.appendedEvents) {
10833
+ const agentId = String(event.agentId);
10834
+ let list = eventsByAgent.get(agentId);
10835
+ if (!list) {
10836
+ list = [];
10837
+ eventsByAgent.set(agentId, list);
10838
+ }
10839
+ list.push(event);
10840
+ }
10841
+ for (const [agentId, events] of eventsByAgent) {
10842
+ const archiveDir = join6(this.archiveRoot, agentId);
10843
+ const toolDir = join6(archiveDir, "tool-results");
10844
+ mkdirSync(toolDir, { recursive: true, mode: 448 });
10845
+ chmodSync(archiveDir, 448);
10846
+ chmodSync(toolDir, 448);
10847
+ const sessionPath = join6(archiveDir, "session.jsonl");
10848
+ try {
10849
+ chmodSync(sessionPath, 384);
10850
+ } catch {
10851
+ }
10852
+ const known = this.mirroredEventIds.get(agentId) ?? /* @__PURE__ */ new Set();
10853
+ const newEvents = events.filter((e) => !known.has(String(e.id)));
10854
+ if (newEvents.length > 0) {
10855
+ const lines = newEvents.map((e) => JSON.stringify({ schemaVersion: 1, kind: "rowan_event", event: e }) + "\n").join("");
10856
+ appendFileSync(sessionPath, lines, { encoding: "utf8", mode: 384 });
10857
+ chmodSync(sessionPath, 384);
10858
+ for (const e of newEvents) known.add(String(e.id));
10859
+ this.mirroredEventIds.set(agentId, known);
10860
+ }
10861
+ }
10862
+ }
10863
+ if (tracker.upsertedContextCompactions.size > 0) {
10864
+ for (const [agentId, record] of tracker.upsertedContextCompactions) {
10865
+ const idStr = String(agentId);
10866
+ const archiveDir = join6(this.archiveRoot, idStr);
10867
+ const sessionPath = join6(archiveDir, "session.jsonl");
10868
+ const knownCompactions = this.mirroredCompactionIds.get(idStr) ?? /* @__PURE__ */ new Set();
10869
+ if (!knownCompactions.has(record.id)) {
10870
+ const line = JSON.stringify({ schemaVersion: 1, kind: "context_compacted", record }) + "\n";
10871
+ appendFileSync(sessionPath, line, { encoding: "utf8", mode: 384 });
10872
+ chmodSync(sessionPath, 384);
10873
+ knownCompactions.add(record.id);
10874
+ this.mirroredCompactionIds.set(idStr, knownCompactions);
10875
+ }
10876
+ }
10877
+ }
9666
10878
  }
9667
10879
  /** Append the semantic durable event mirror and remove deleted Agent sidecars. */
9668
- mirrorState(state) {
10880
+ mirrorInitialState(state) {
9669
10881
  mkdirSync(this.archiveRoot, { recursive: true, mode: 448 });
9670
10882
  chmodSync(this.archiveRoot, 448);
9671
10883
  const currentAgents = new Set(state.agents.map((agent) => String(agent.id)));
@@ -9885,19 +11097,6 @@ function ownerLease(row) {
9885
11097
  expiresAt: new Date(row.expires_at).toISOString()
9886
11098
  };
9887
11099
  }
9888
- function parseEventCursor(lease, cursor) {
9889
- const value = String(cursor);
9890
- const separator = value.lastIndexOf(":");
9891
- const incarnation = separator < 0 ? "" : value.slice(0, separator);
9892
- const sequence = separator < 0 ? "" : value.slice(separator + 1);
9893
- const token = String(lease.token);
9894
- const ownerSuffix = `:${lease.epoch}:${lease.ownerId}`;
9895
- const expectedIncarnation = token.endsWith(ownerSuffix) ? token.slice(0, -ownerSuffix.length) : "";
9896
- if (incarnation !== expectedIncarnation || !/^\d+$/.test(sequence)) {
9897
- throw new RuntimeError("invalid_cursor", { cursorType: "event", reason: "wrong_store" });
9898
- }
9899
- return Number(sequence);
9900
- }
9901
11100
  function ownershipLost2(lease, row) {
9902
11101
  return new RuntimeError("runtime_ownership_lost", {
9903
11102
  reason: row.epoch > lease.epoch ? "epoch_advanced" : row.owner_id ? "released" : "released",
@@ -9924,6 +11123,7 @@ export {
9924
11123
  DEFAULT_PHASE_ID,
9925
11124
  ExtensionLifetimeError,
9926
11125
  InMemoryConfigProvider,
11126
+ InMemoryExtensionHost,
9927
11127
  InMemoryStore,
9928
11128
  ResourceRegistry,
9929
11129
  ResourceRegistryError,