@dxos/assistant 0.13.0 → 0.14.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.
@@ -1,8 +1,9 @@
1
1
  import { t as __exportAll } from "../chunk-rolldown-runtime.mjs";
2
+ import * as Context from "effect/Context";
2
3
  import * as Effect from "effect/Effect";
3
4
  import * as Schema from "effect/Schema";
4
- import { DXN, Feed, Obj, Query, Ref, Type } from "@dxos/echo";
5
- import { assertArgument } from "@dxos/invariant";
5
+ import { DXN, Database, Feed, Obj, Query, Ref, Type } from "@dxos/echo";
6
+ import { assertArgument, invariant } from "@dxos/invariant";
6
7
  import { log } from "@dxos/log";
7
8
  import { EID as EID$1 } from "@dxos/keys";
8
9
  import { ComplexSet, isNonNullable } from "@dxos/util";
@@ -68,6 +69,7 @@ var Binder = class extends Resource {
68
69
  _registry;
69
70
  _feed;
70
71
  _runtime;
72
+ _hold;
71
73
  #bindingsQuery;
72
74
  /** {@link bindingWriteCount} at the last read of the bindings query. */
73
75
  #readWrites = 0;
@@ -79,6 +81,9 @@ var Binder = class extends Resource {
79
81
  skills: /* @__PURE__ */ new Set(),
80
82
  objects: /* @__PURE__ */ new Set()
81
83
  };
84
+ #pending;
85
+ /** Bumped by each held write, so only the latest resolution of the held bindings lands. */
86
+ #heldGeneration = 0;
82
87
  constructor(options) {
83
88
  super();
84
89
  assertArgument(options.feed, "options.feed", "Feed is required");
@@ -86,6 +91,7 @@ var Binder = class extends Resource {
86
91
  this._feed = options.feed;
87
92
  this._runtime = options.runtime;
88
93
  this._registry = options.registry ?? AtomEx.makeRegistry();
94
+ this._hold = options.hold ?? false;
89
95
  }
90
96
  /**
91
97
  * Returns the skills atom for subscription.
@@ -124,6 +130,40 @@ var Binder = class extends Resource {
124
130
  return this._registry.subscribe(this._objects, () => cb(this._registry.get(this._objects)));
125
131
  }
126
132
  async _open() {
133
+ if (this._hold) {
134
+ this.#pending = [];
135
+ return;
136
+ }
137
+ await this._follow();
138
+ }
139
+ /**
140
+ * Writes a holding binder's bindings to its feed, which the caller has stored, as one binding. Later
141
+ * writes go straight to the feed, which the binder does not follow.
142
+ */
143
+ async flush() {
144
+ const held = this.#pending;
145
+ if (!held) return;
146
+ invariant(Obj.getDatabase(this._feed), "The feed must be stored before its bindings are flushed.", {
147
+ "~LogMeta": "~LogMeta",
148
+ F: __dxlog_file,
149
+ L: 184,
150
+ S: this,
151
+ A: ["Obj.getDatabase(this._feed)", "'The feed must be stored before its bindings are flushed.'"]
152
+ });
153
+ this.#pending = void 0;
154
+ const { skills, objects } = this._reduce(held);
155
+ if (skills.size > 0 || objects.size > 0) await this._append(Obj.make(Binding, {
156
+ skills: {
157
+ added: [...skills],
158
+ removed: []
159
+ },
160
+ objects: {
161
+ added: [...objects],
162
+ removed: []
163
+ }
164
+ }));
165
+ }
166
+ async _follow() {
127
167
  const bindingsQuery = await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(Feed.query(this._feed, Query.type(Binding)));
128
168
  this.#bindingsQuery = bindingsQuery;
129
169
  this.#readWrites = bindingWriteCount(this._feed);
@@ -147,7 +187,7 @@ var Binder = class extends Resource {
147
187
  log.warn("bindings sync failed; keeping the current bindings", { error }, {
148
188
  "~LogMeta": "~LogMeta",
149
189
  F: __dxlog_file,
150
- L: 192,
190
+ L: 230,
151
191
  S: this
152
192
  });
153
193
  return;
@@ -155,7 +195,7 @@ var Binder = class extends Resource {
155
195
  log("sync", { bindingItems: results.length }, {
156
196
  "~LogMeta": "~LogMeta",
157
197
  F: __dxlog_file,
158
- L: 195,
198
+ L: 233,
159
199
  S: this
160
200
  });
161
201
  await this._updateBindings(results);
@@ -166,7 +206,7 @@ var Binder = class extends Resource {
166
206
  }, {
167
207
  "~LogMeta": "~LogMeta",
168
208
  F: __dxlog_file,
169
- L: 199,
209
+ L: 237,
170
210
  S: this
171
211
  });
172
212
  }
@@ -187,7 +227,7 @@ var Binder = class extends Resource {
187
227
  }, {
188
228
  "~LogMeta": "~LogMeta",
189
229
  F: __dxlog_file,
190
- L: 220,
230
+ L: 258,
191
231
  S: this
192
232
  });
193
233
  const currentSkills = this._registry.get(this._skills);
@@ -200,7 +240,7 @@ var Binder = class extends Resource {
200
240
  }, {
201
241
  "~LogMeta": "~LogMeta",
202
242
  F: __dxlog_file,
203
- L: 231,
243
+ L: 269,
204
244
  S: this
205
245
  });
206
246
  const reducedSkillDxns = new Set([...bindings.skills].map((ref) => ref.uri));
@@ -223,7 +263,7 @@ var Binder = class extends Resource {
223
263
  }, {
224
264
  "~LogMeta": "~LogMeta",
225
265
  F: __dxlog_file,
226
- L: 254,
266
+ L: 292,
227
267
  S: this
228
268
  });
229
269
  }
@@ -247,10 +287,10 @@ var Binder = class extends Resource {
247
287
  }, {
248
288
  "~LogMeta": "~LogMeta",
249
289
  F: __dxlog_file,
250
- L: 286,
290
+ L: 324,
251
291
  S: this
252
292
  });
253
- await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(Feed.append(this._feed, [Obj.make(Binding, {
293
+ await this._write(Obj.make(Binding, {
254
294
  skills: {
255
295
  added: addedSkills,
256
296
  removed: []
@@ -259,22 +299,21 @@ var Binder = class extends Resource {
259
299
  added: addedObjects,
260
300
  removed: []
261
301
  }
262
- })]));
263
- countBindingWrite(this._feed);
302
+ }));
264
303
  }
265
304
  async unbind({ skills, objects }) {
266
305
  if (!skills?.length && !objects?.length) return;
267
306
  (skills ?? []).forEach((ref) => this.#bound.skills.delete(refKey(ref.uri)));
268
307
  (objects ?? []).forEach((ref) => this.#bound.objects.delete(refKey(ref.uri)));
269
- const removedSkillDxns = (skills ?? []).map((ref) => ref.uri);
270
- const removedObjectDxns = (objects ?? []).map((ref) => ref.uri);
271
- if (removedSkillDxns.length > 0) {
308
+ const removedSkillKeys = new Set((skills ?? []).map((ref) => refKey(ref.uri)));
309
+ const removedObjectKeys = new Set((objects ?? []).map((ref) => refKey(ref.uri)));
310
+ if (removedSkillKeys.size > 0) {
272
311
  const current = this._registry.get(this._skills);
273
- this._registry.set(this._skills, current.filter((obj) => !removedSkillDxns.some((uri) => Obj.getURI(obj) === uri)));
312
+ this._registry.set(this._skills, current.filter((obj) => !removedSkillKeys.has(refKey(Obj.getURI(obj)))));
274
313
  }
275
- if (removedObjectDxns.length > 0) {
314
+ if (removedObjectKeys.size > 0) {
276
315
  const current = this._registry.get(this._objects);
277
- this._registry.set(this._objects, current.filter((obj) => !removedObjectDxns.some((uri) => Obj.getURI(obj) === uri)));
316
+ this._registry.set(this._objects, current.filter((obj) => !removedObjectKeys.has(refKey(Obj.getURI(obj)))));
278
317
  }
279
318
  log("unbind", {
280
319
  skills: skills?.length,
@@ -282,10 +321,10 @@ var Binder = class extends Resource {
282
321
  }, {
283
322
  "~LogMeta": "~LogMeta",
284
323
  F: __dxlog_file,
285
- L: 330,
324
+ L: 365,
286
325
  S: this
287
326
  });
288
- await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(Feed.append(this._feed, [Obj.make(Binding, {
327
+ await this._write(Obj.make(Binding, {
289
328
  skills: {
290
329
  added: [],
291
330
  removed: skills ?? []
@@ -294,7 +333,32 @@ var Binder = class extends Resource {
294
333
  added: [],
295
334
  removed: objects ?? []
296
335
  }
297
- })]));
336
+ }));
337
+ }
338
+ async _write(binding) {
339
+ if (this.#pending) {
340
+ this.#pending.push(binding);
341
+ await this._resolveHeld(this.#pending);
342
+ return;
343
+ }
344
+ await this._append(binding);
345
+ }
346
+ /**
347
+ * Sets the atoms from the held bindings. Their refs have no feed query to hydrate them, so each is
348
+ * resolved against the database, which also spans the registry.
349
+ */
350
+ async _resolveHeld(held) {
351
+ const generation = ++this.#heldGeneration;
352
+ const { db } = Context.get(this._runtime, Database.Service);
353
+ const { skills, objects } = this._reduce(held);
354
+ const hydrate = (refs) => [...refs].map((ref) => ref.isAvailable ? ref : db.makeRef(ref.uri));
355
+ const [resolvedSkills, resolvedObjects] = await Promise.all([this._resolve(hydrate(skills), this._registry.get(this._skills)), this._resolve(hydrate(objects), this._registry.get(this._objects))]);
356
+ if (generation !== this.#heldGeneration) return;
357
+ this._registry.set(this._skills, resolvedSkills);
358
+ this._registry.set(this._objects, resolvedObjects);
359
+ }
360
+ async _append(binding) {
361
+ await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(Feed.append(this._feed, [binding]));
298
362
  countBindingWrite(this._feed);
299
363
  }
300
364
  /**
@@ -371,7 +435,7 @@ var Binder = class extends Resource {
371
435
  if (!resolved) log.warn("unresolved context binding", { uri: ref.uri }, {
372
436
  "~LogMeta": "~LogMeta",
373
437
  F: __dxlog_file,
374
- L: 456,
438
+ L: 523,
375
439
  S: this
376
440
  });
377
441
  return resolved;
@@ -1 +1 @@
1
- {"version":3,"file":"AiContext.mjs","names":[],"sources":["../../../src/session/AiContext.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as EArray from 'effect/Array';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Function from 'effect/Function';\nimport * as Order from 'effect/Order';\nimport * as Atom from 'effect/reactivity/Atom';\nimport * as AtomRegistry from 'effect/reactivity/AtomRegistry';\nimport * as Schema from 'effect/Schema';\n\nimport * as Skill from '@dxos/compute/Skill';\nimport { Resource } from '@dxos/context';\nimport { Database, DXN, Feed, Obj, Query, type QueryResult, Ref, Type } from '@dxos/echo';\nimport * as AtomEx from '@dxos/effect/AtomEx';\nimport * as RuntimeProvider from '@dxos/effect/RuntimeProvider';\nimport { assertArgument } from '@dxos/invariant';\nimport { EID, type URI } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { ComplexSet, isNonNullable } from '@dxos/util';\n\n/**\n * Thread message that binds or unbinds contextual objects to a conversation.\n */\nexport class Binding extends Type.makeObject<Binding>(DXN.make('org.dxos.type.contextBinding', '0.1.0'))(\n Schema.Struct({\n skills: Schema.Struct({\n added: Schema.Array(Ref.Ref(Skill.Skill)),\n removed: Schema.Array(Ref.Ref(Skill.Skill)),\n }),\n\n objects: Schema.Struct({\n added: Schema.Array(Ref.Ref(Obj.Unknown)),\n removed: Schema.Array(Ref.Ref(Obj.Unknown)),\n }),\n }),\n) {}\nexport type BindingProps = Partial<{\n skills: Ref.Ref<Skill.Skill>[];\n objects: Ref.Ref<Obj.Unknown>[];\n}>;\n\nexport class Bindings {\n readonly skills = new ComplexSet<Ref.Ref<Skill.Skill>>((ref) => ref.uri);\n\n // TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.\n readonly objects = new ComplexSet<Ref.Ref<Obj.Unknown>>((ref) => {\n const echoUri = EID.tryParse(ref.uri);\n return echoUri ? EID.getEntityId(echoUri) : undefined;\n });\n\n toJSON(): { skills: URI.URI[]; objects: URI.URI[] } {\n return {\n skills: EArray.fromIterable(this.skills).map((ref) => ref.uri),\n objects: EArray.fromIterable(this.objects).map((ref) => ref.uri),\n };\n }\n}\n\n/**\n * Bindings appended in this realm, per feed id (stable whether or not the feed is persisted yet).\n *\n * Lets {@link Binder.sync} skip its re-read when nothing here could have changed the bindings: the\n * re-read is a feed-scoped query, which waits for an index pass over every pending write, and it\n * exists only for read-your-writes — a binding written elsewhere arrives through the live query.\n */\nconst bindingWrites = new Map<string, number>();\n\nconst bindingWriteCount = (feed: Feed.Feed): number => bindingWrites.get(feed.id) ?? 0;\n\nconst countBindingWrite = (feed: Feed.Feed): void => {\n bindingWrites.set(feed.id, (bindingWrites.get(feed.id) ?? 0) + 1);\n};\n\nexport type BinderOptions = {\n feed: Feed.Feed;\n runtime: Context.Context<Database.Service>;\n /** @effect/atom-react Registry for reactive state management. */\n registry?: AtomRegistry.AtomRegistry;\n};\n\n/**\n * Manages bindings of skills and objects to a conversation.\n */\n// TODO(burdon): Context should manage ephemeral state of bindings until prompt is issued?\nexport class Binder extends Resource {\n private readonly _skills = Atom.make<Skill.Skill[]>([]).pipe(Atom.keepAlive);\n private readonly _objects = Atom.make<Obj.Unknown[]>([]).pipe(Atom.keepAlive);\n private readonly _registry: AtomRegistry.AtomRegistry;\n private readonly _feed: Feed.Feed;\n private readonly _runtime: Context.Context<Database.Service>;\n\n #bindingsQuery: QueryResult.QueryResult<Binding> | undefined;\n\n /** {@link bindingWriteCount} at the last read of the bindings query. */\n #readWrites = 0;\n\n /**\n * Keys of the refs the feed already binds, whether or not their targets resolve here: a registry\n * skill bound by URI never matches its resolved target's URI, so the atoms alone cannot tell it is bound.\n */\n #bound = { skills: new Set<URI.URI>(), objects: new Set<URI.URI>() };\n\n constructor(options: BinderOptions) {\n super();\n assertArgument(options.feed, 'options.feed', 'Feed is required');\n assertArgument(options.runtime, 'options.runtime', 'Feed runtime is required');\n this._feed = options.feed;\n this._runtime = options.runtime;\n this._registry = options.registry ?? AtomEx.makeRegistry();\n }\n\n /**\n * Returns the skills atom for subscription.\n */\n get skills(): Atom.Atom<Skill.Skill[]> {\n return this._skills;\n }\n\n /**\n * Returns the objects atom for subscription.\n */\n get objects(): Atom.Atom<Obj.Unknown[]> {\n return this._objects;\n }\n\n /**\n * Gets the current skills value.\n */\n getSkills(): Skill.Skill[] {\n return this._registry.get(this._skills);\n }\n\n /**\n * Gets the current objects value.\n */\n getObjects(): Obj.Unknown[] {\n return this._registry.get(this._objects);\n }\n\n /**\n * Subscribe to changes in skills.\n */\n subscribeSkills(cb: (skills: Skill.Skill[]) => void): () => void {\n return this._registry.subscribe(this._skills, () => cb(this._registry.get(this._skills)));\n }\n\n /**\n * Subscribe to changes in objects.\n */\n subscribeObjects(cb: (objects: Obj.Unknown[]) => void): () => void {\n return this._registry.subscribe(this._objects, () => cb(this._registry.get(this._objects)));\n }\n\n protected override async _open(): Promise<void> {\n const bindingsQuery = await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(\n Feed.query(this._feed, Query.type(Binding)),\n );\n this.#bindingsQuery = bindingsQuery;\n\n // Process initial state before returning.\n this.#readWrites = bindingWriteCount(this._feed);\n const initialResults = await bindingsQuery.run();\n await this._updateBindings(initialResults);\n\n // Subscribe to future changes.\n this._ctx.onDispose(\n bindingsQuery.subscribe(async () => {\n await this._updateBindings(bindingsQuery.results);\n }),\n );\n }\n\n /**\n * Re-reads bindings from the feed to pick up changes made by other processes in this realm, such\n * as a tool that bound a skill. A no-op when none has been written since the last read.\n */\n async sync(): Promise<void> {\n const writes = bindingWriteCount(this._feed);\n if (this.#bindingsQuery && writes !== this.#readWrites) {\n let results: Binding[];\n try {\n results = await this.#bindingsQuery.run();\n } catch (error) {\n // The query's live subscription already keeps the bindings current, so a re-read that fails\n // (an index query timing out under load) costs freshness, not correctness — and a caller\n // running this between agent turns would otherwise fail the whole agent process on it.\n log.warn('bindings sync failed; keeping the current bindings', { error });\n return;\n }\n log('sync', { bindingItems: results.length });\n await this._updateBindings(results);\n // Only after the update lands, so one that fails is retried by the next sync.\n this.#readWrites = writes;\n log('sync complete', {\n skills: this._registry.get(this._skills).length,\n // Read the meta key directly: `Skill.getKey` throws on a space-authored skill, which would\n // make a diagnostic log the thing that breaks the sync.\n skillKeys: this._registry.get(this._skills).map((skill) => Obj.getMeta(skill).key),\n });\n }\n }\n\n private async _updateBindings(items: Binding[]): Promise<void> {\n // Skip update if no items - preserve existing state set by bind().\n if (items.length === 0) {\n return;\n }\n\n const bindings = this._reduce(inAppendOrder(items));\n this.#bound = {\n skills: new Set([...bindings.skills].map((ref) => refKey(ref.uri))),\n objects: new Set([...bindings.objects].map((ref) => refKey(ref.uri))),\n };\n\n log('_updateBindings', {\n items: items.length,\n skillRefs: [...bindings.skills].map((ref) => ({ uri: ref.uri, available: ref.isAvailable })),\n });\n\n // Resolve references (loading them first if needed).\n const currentSkills = this._registry.get(this._skills);\n const currentObjects = this._registry.get(this._objects);\n const resolvedSkills = await this._resolve(bindings.skills, currentSkills);\n const resolvedObjects = await this._resolve(bindings.objects, currentObjects);\n\n log('_updateBindings resolved', {\n resolvedSkills: resolvedSkills.length,\n resolvedSkillKeys: resolvedSkills.map((bp) => Obj.getMeta(bp).key ?? '<missing>'),\n });\n\n // Filter current state to only items still in the reduced binding set,\n // then merge in newly resolved items. This ensures unbind events are respected.\n const reducedSkillDxns = new Set<URI.URI>([...bindings.skills].map((ref) => ref.uri));\n const reducedObjectDxns = new Set<URI.URI>([...bindings.objects].map((ref) => ref.uri));\n const filteredSkills = currentSkills.filter((obj) => {\n const uri = Obj.getURI(obj);\n return uri != null && reducedSkillDxns.has(uri);\n });\n const filteredObjects = currentObjects.filter((obj) => {\n const uri = Obj.getURI(obj);\n return uri != null && reducedObjectDxns.has(uri);\n });\n const mergedSkills = this._mergeInto(filteredSkills, resolvedSkills);\n const mergedObjects = this._mergeInto(filteredObjects, resolvedObjects);\n\n this._registry.set(this._skills, mergedSkills);\n this._registry.set(this._objects, mergedObjects);\n\n log('updated bindings', {\n skills: mergedSkills.length,\n objects: mergedObjects.length,\n });\n }\n\n protected override async _close(): Promise<void> {\n // Reset atoms to empty state.\n this._registry.set(this._skills, []);\n this._registry.set(this._objects, []);\n }\n\n async bind({ skills, objects }: BindingProps): Promise<void> {\n const currentSkills = this._registry.get(this._skills);\n const currentObjects = this._registry.get(this._objects);\n\n const { added: addedSkills, next: nextSkills } = this._processBindings(skills, currentSkills, this.#bound.skills);\n const { added: addedObjects, next: nextObjects } = this._processBindings(\n objects,\n currentObjects,\n this.#bound.objects,\n );\n if (!addedSkills.length && !addedObjects.length) {\n return;\n }\n addedSkills.forEach((ref) => this.#bound.skills.add(refKey(ref.uri)));\n addedObjects.forEach((ref) => this.#bound.objects.add(refKey(ref.uri)));\n\n // Atomic updates - subscribers notified automatically.\n this._registry.set(this._skills, nextSkills);\n this._registry.set(this._objects, nextObjects);\n\n log('bind', { skills: addedSkills.length, objects: addedObjects.length });\n await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(\n Feed.append(this._feed, [\n Obj.make(Binding, {\n skills: {\n added: addedSkills,\n removed: [],\n },\n objects: {\n added: addedObjects,\n removed: [],\n },\n }),\n ]),\n );\n countBindingWrite(this._feed);\n }\n\n async unbind({ skills, objects }: BindingProps): Promise<void> {\n if (!skills?.length && !objects?.length) {\n return;\n }\n\n (skills ?? []).forEach((ref) => this.#bound.skills.delete(refKey(ref.uri)));\n (objects ?? []).forEach((ref) => this.#bound.objects.delete(refKey(ref.uri)));\n\n // Immediately update atom state so removals are reflected before the queue round-trips.\n const removedSkillDxns = (skills ?? []).map((ref) => ref.uri);\n const removedObjectDxns = (objects ?? []).map((ref) => ref.uri);\n if (removedSkillDxns.length > 0) {\n const current = this._registry.get(this._skills);\n this._registry.set(\n this._skills,\n current.filter((obj) => !removedSkillDxns.some((uri) => Obj.getURI(obj) === uri)),\n );\n }\n if (removedObjectDxns.length > 0) {\n const current = this._registry.get(this._objects);\n this._registry.set(\n this._objects,\n current.filter((obj) => !removedObjectDxns.some((uri) => Obj.getURI(obj) === uri)),\n );\n }\n\n log('unbind', { skills: skills?.length, objects: objects?.length });\n await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(\n Feed.append(this._feed, [\n Obj.make(Binding, {\n skills: {\n added: [],\n removed: skills ?? [],\n },\n objects: {\n added: [],\n removed: objects ?? [],\n },\n }),\n ]),\n );\n countBindingWrite(this._feed);\n }\n\n /**\n * Process bindings to filter duplicates (against the resolved targets and the refs already bound) and\n * determine next state.\n */\n private _processBindings<T extends Obj.Unknown>(\n refs: Ref.Ref<T>[] | undefined,\n current: T[],\n bound: ReadonlySet<URI.URI>,\n ): { added: Ref.Ref<T>[]; next: T[] } {\n const next = [...current];\n const added: Ref.Ref<T>[] = [];\n if (!refs?.length) {\n return { added, next };\n }\n\n const seen = new Set<URI.URI>([...bound, ...current.map((obj) => refKey(Obj.getURI(obj)))]);\n for (const ref of refs) {\n const key = refKey(ref.uri);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n added.push(ref);\n\n // Only resolve target if available (has target or resolver).\n if (ref.isAvailable) {\n const target = ref.target;\n if (target) {\n next.push(target);\n }\n }\n }\n\n return { added, next };\n }\n\n /**\n * Reduce results into sets of skills and objects.\n *\n * Order-sensitive: a later removal must fold after the addition it undoes, so callers pass items\n * in append order (see {@link inAppendOrder}).\n */\n private _reduce(items: Binding[]): Bindings {\n return Function.pipe(\n items,\n EArray.reduce(new Bindings(), (context, { skills, objects }) => {\n skills.added.forEach((ref) => context.skills.add(ref));\n skills.removed.forEach((ref) => context.skills.delete(ref));\n\n objects.added.forEach((ref) => context.objects.add(ref));\n objects.removed.forEach((ref) => {\n for (const obj of context.objects) {\n if (\n obj.uri === ref.uri ||\n (EID.tryParse(obj.uri) &&\n EID.tryParse(ref.uri) &&\n EID.getEntityId(EID.tryParse(obj.uri)!) === EID.getEntityId(EID.tryParse(ref.uri)!))\n ) {\n context.objects.delete(obj);\n }\n }\n });\n\n return context;\n }),\n );\n }\n\n /**\n * Merge resolved items into the current set, adding only items not already present (by DXN).\n */\n private _mergeInto<T extends Obj.Unknown>(current: T[], resolved: T[]): T[] {\n const seen = new Set(current.map((obj) => Obj.getURI(obj)));\n const merged = [...current];\n for (const obj of resolved) {\n const uri = Obj.getURI(obj);\n if (!seen.has(uri)) {\n seen.add(uri);\n merged.push(obj);\n }\n }\n return merged;\n }\n\n /**\n * Resolve references to objects, loading them first if needed and falling back to existing objects.\n * DXN refs (e.g. `dxn:org.dxos.skill.database`) resolve via the wired-up ECHO ref resolver\n * which already spans both the space DB and the hypergraph registry.\n */\n private async _resolve<T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {\n const refArray = [...refs];\n\n // Load all refs that need loading.\n await Promise.all(refArray.map((ref) => ref.tryLoad()));\n\n return refArray\n .map((ref) => {\n let target: T | undefined;\n // Only resolve target if available (has target or resolver).\n if (ref.isAvailable) {\n target = ref.target;\n }\n\n // Fallback to existing object.\n const resolved = target ?? current.find((obj) => Obj.getURI(obj) === ref.uri);\n if (!resolved) {\n // A binding that cannot load (e.g. a registry skill the host never registered) would otherwise\n // vanish from the session without trace.\n log.warn('unresolved context binding', { uri: ref.uri });\n }\n return resolved;\n })\n .filter(isNonNullable);\n }\n}\n\n/**\n * Identity of a bound ref: an ECHO URI compares in its local form, since some carry the space and some do not.\n */\nconst refKey = (uri: URI.URI): URI.URI => {\n const echoUri = EID.tryParse(uri);\n return echoUri ? EID.toLocal(echoUri) : uri;\n};\n\n/**\n * Bindings in the order they were appended, which is the order the fold has to see them: a query\n * returns an unordered set, so an unsorted fold can apply a removal before the addition it undoes\n * and silently keep the object bound. Server-assigned position is authoritative; a locally-written\n * block has none yet and sorts last, which is correct — it is the newest.\n */\nconst inAppendOrder = (items: readonly Binding[]): Binding[] =>\n EArray.sort(items, Order.mapInput(Order.Number, Feed.getPosition));\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiContext.mjs","names":[],"sources":["../../../src/session/AiContext.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as EArray from 'effect/Array';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Function from 'effect/Function';\nimport * as Order from 'effect/Order';\nimport * as Atom from 'effect/reactivity/Atom';\nimport * as AtomRegistry from 'effect/reactivity/AtomRegistry';\nimport * as Schema from 'effect/Schema';\n\nimport * as Skill from '@dxos/compute/Skill';\nimport { Resource } from '@dxos/context';\nimport { Database, DXN, Feed, Obj, Query, type QueryResult, Ref, Type } from '@dxos/echo';\nimport * as AtomEx from '@dxos/effect/AtomEx';\nimport * as RuntimeProvider from '@dxos/effect/RuntimeProvider';\nimport { assertArgument, invariant } from '@dxos/invariant';\nimport { EID, type URI } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { ComplexSet, isNonNullable } from '@dxos/util';\n\n/**\n * Thread message that binds or unbinds contextual objects to a conversation.\n */\nexport class Binding extends Type.makeObject<Binding>(DXN.make('org.dxos.type.contextBinding', '0.1.0'))(\n Schema.Struct({\n skills: Schema.Struct({\n added: Schema.Array(Ref.Ref(Skill.Skill)),\n removed: Schema.Array(Ref.Ref(Skill.Skill)),\n }),\n\n objects: Schema.Struct({\n added: Schema.Array(Ref.Ref(Obj.Unknown)),\n removed: Schema.Array(Ref.Ref(Obj.Unknown)),\n }),\n }),\n) {}\nexport type BindingProps = Partial<{\n skills: Ref.Ref<Skill.Skill>[];\n objects: Ref.Ref<Obj.Unknown>[];\n}>;\n\nexport class Bindings {\n readonly skills = new ComplexSet<Ref.Ref<Skill.Skill>>((ref) => ref.uri);\n\n // TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.\n readonly objects = new ComplexSet<Ref.Ref<Obj.Unknown>>((ref) => {\n const echoUri = EID.tryParse(ref.uri);\n return echoUri ? EID.getEntityId(echoUri) : undefined;\n });\n\n toJSON(): { skills: URI.URI[]; objects: URI.URI[] } {\n return {\n skills: EArray.fromIterable(this.skills).map((ref) => ref.uri),\n objects: EArray.fromIterable(this.objects).map((ref) => ref.uri),\n };\n }\n}\n\n/**\n * Bindings appended in this realm, per feed id (stable whether or not the feed is persisted yet).\n *\n * Lets {@link Binder.sync} skip its re-read when nothing here could have changed the bindings: the\n * re-read is a feed-scoped query, which waits for an index pass over every pending write, and it\n * exists only for read-your-writes — a binding written elsewhere arrives through the live query.\n */\nconst bindingWrites = new Map<string, number>();\n\nconst bindingWriteCount = (feed: Feed.Feed): number => bindingWrites.get(feed.id) ?? 0;\n\nconst countBindingWrite = (feed: Feed.Feed): void => {\n bindingWrites.set(feed.id, (bindingWrites.get(feed.id) ?? 0) + 1);\n};\n\nexport type BinderOptions = {\n feed: Feed.Feed;\n runtime: Context.Context<Database.Service>;\n /** @effect/atom-react Registry for reactive state management. */\n registry?: AtomRegistry.AtomRegistry;\n /** Holds bindings in memory until {@link Binder.flush}, so the feed need not be stored yet (a draft conversation). */\n hold?: boolean;\n};\n\n/**\n * Manages bindings of skills and objects to a conversation.\n */\nexport class Binder extends Resource {\n private readonly _skills = Atom.make<Skill.Skill[]>([]).pipe(Atom.keepAlive);\n private readonly _objects = Atom.make<Obj.Unknown[]>([]).pipe(Atom.keepAlive);\n private readonly _registry: AtomRegistry.AtomRegistry;\n private readonly _feed: Feed.Feed;\n private readonly _runtime: Context.Context<Database.Service>;\n private readonly _hold: boolean;\n\n #bindingsQuery: QueryResult.QueryResult<Binding> | undefined;\n\n /** {@link bindingWriteCount} at the last read of the bindings query. */\n #readWrites = 0;\n\n /**\n * Keys of the refs the feed already binds, whether or not their targets resolve here: a registry\n * skill bound by URI never matches its resolved target's URI, so the atoms alone cannot tell it is bound.\n */\n #bound = { skills: new Set<URI.URI>(), objects: new Set<URI.URI>() };\n\n #pending?: Binding[];\n\n /** Bumped by each held write, so only the latest resolution of the held bindings lands. */\n #heldGeneration = 0;\n\n constructor(options: BinderOptions) {\n super();\n assertArgument(options.feed, 'options.feed', 'Feed is required');\n assertArgument(options.runtime, 'options.runtime', 'Feed runtime is required');\n this._feed = options.feed;\n this._runtime = options.runtime;\n this._registry = options.registry ?? AtomEx.makeRegistry();\n this._hold = options.hold ?? false;\n }\n\n /**\n * Returns the skills atom for subscription.\n */\n get skills(): Atom.Atom<Skill.Skill[]> {\n return this._skills;\n }\n\n /**\n * Returns the objects atom for subscription.\n */\n get objects(): Atom.Atom<Obj.Unknown[]> {\n return this._objects;\n }\n\n /**\n * Gets the current skills value.\n */\n getSkills(): Skill.Skill[] {\n return this._registry.get(this._skills);\n }\n\n /**\n * Gets the current objects value.\n */\n getObjects(): Obj.Unknown[] {\n return this._registry.get(this._objects);\n }\n\n /**\n * Subscribe to changes in skills.\n */\n subscribeSkills(cb: (skills: Skill.Skill[]) => void): () => void {\n return this._registry.subscribe(this._skills, () => cb(this._registry.get(this._skills)));\n }\n\n /**\n * Subscribe to changes in objects.\n */\n subscribeObjects(cb: (objects: Obj.Unknown[]) => void): () => void {\n return this._registry.subscribe(this._objects, () => cb(this._registry.get(this._objects)));\n }\n\n protected override async _open(): Promise<void> {\n if (this._hold) {\n this.#pending = [];\n return;\n }\n await this._follow();\n }\n\n /**\n * Writes a holding binder's bindings to its feed, which the caller has stored, as one binding. Later\n * writes go straight to the feed, which the binder does not follow.\n */\n async flush(): Promise<void> {\n const held = this.#pending;\n if (!held) {\n return;\n }\n invariant(Obj.getDatabase(this._feed), 'The feed must be stored before its bindings are flushed.');\n this.#pending = undefined;\n const { skills, objects } = this._reduce(held);\n if (skills.size > 0 || objects.size > 0) {\n await this._append(\n Obj.make(Binding, {\n skills: { added: [...skills], removed: [] },\n objects: { added: [...objects], removed: [] },\n }),\n );\n }\n }\n\n private async _follow(): Promise<void> {\n const bindingsQuery = await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(\n Feed.query(this._feed, Query.type(Binding)),\n );\n this.#bindingsQuery = bindingsQuery;\n\n // Process initial state before returning.\n this.#readWrites = bindingWriteCount(this._feed);\n const initialResults = await bindingsQuery.run();\n await this._updateBindings(initialResults);\n\n // Subscribe to future changes.\n this._ctx.onDispose(\n bindingsQuery.subscribe(async () => {\n await this._updateBindings(bindingsQuery.results);\n }),\n );\n }\n\n /**\n * Re-reads bindings from the feed to pick up changes made by other processes in this realm, such\n * as a tool that bound a skill. A no-op when none has been written since the last read.\n */\n async sync(): Promise<void> {\n const writes = bindingWriteCount(this._feed);\n if (this.#bindingsQuery && writes !== this.#readWrites) {\n let results: Binding[];\n try {\n results = await this.#bindingsQuery.run();\n } catch (error) {\n // The query's live subscription already keeps the bindings current, so a re-read that fails\n // (an index query timing out under load) costs freshness, not correctness — and a caller\n // running this between agent turns would otherwise fail the whole agent process on it.\n log.warn('bindings sync failed; keeping the current bindings', { error });\n return;\n }\n log('sync', { bindingItems: results.length });\n await this._updateBindings(results);\n // Only after the update lands, so one that fails is retried by the next sync.\n this.#readWrites = writes;\n log('sync complete', {\n skills: this._registry.get(this._skills).length,\n // Read the meta key directly: `Skill.getKey` throws on a space-authored skill, which would\n // make a diagnostic log the thing that breaks the sync.\n skillKeys: this._registry.get(this._skills).map((skill) => Obj.getMeta(skill).key),\n });\n }\n }\n\n private async _updateBindings(items: Binding[]): Promise<void> {\n // Skip update if no items - preserve existing state set by bind().\n if (items.length === 0) {\n return;\n }\n\n const bindings = this._reduce(inAppendOrder(items));\n this.#bound = {\n skills: new Set([...bindings.skills].map((ref) => refKey(ref.uri))),\n objects: new Set([...bindings.objects].map((ref) => refKey(ref.uri))),\n };\n\n log('_updateBindings', {\n items: items.length,\n skillRefs: [...bindings.skills].map((ref) => ({ uri: ref.uri, available: ref.isAvailable })),\n });\n\n // Resolve references (loading them first if needed).\n const currentSkills = this._registry.get(this._skills);\n const currentObjects = this._registry.get(this._objects);\n const resolvedSkills = await this._resolve(bindings.skills, currentSkills);\n const resolvedObjects = await this._resolve(bindings.objects, currentObjects);\n\n log('_updateBindings resolved', {\n resolvedSkills: resolvedSkills.length,\n resolvedSkillKeys: resolvedSkills.map((bp) => Obj.getMeta(bp).key ?? '<missing>'),\n });\n\n // Filter current state to only items still in the reduced binding set,\n // then merge in newly resolved items. This ensures unbind events are respected.\n const reducedSkillDxns = new Set<URI.URI>([...bindings.skills].map((ref) => ref.uri));\n const reducedObjectDxns = new Set<URI.URI>([...bindings.objects].map((ref) => ref.uri));\n const filteredSkills = currentSkills.filter((obj) => {\n const uri = Obj.getURI(obj);\n return uri != null && reducedSkillDxns.has(uri);\n });\n const filteredObjects = currentObjects.filter((obj) => {\n const uri = Obj.getURI(obj);\n return uri != null && reducedObjectDxns.has(uri);\n });\n const mergedSkills = this._mergeInto(filteredSkills, resolvedSkills);\n const mergedObjects = this._mergeInto(filteredObjects, resolvedObjects);\n\n this._registry.set(this._skills, mergedSkills);\n this._registry.set(this._objects, mergedObjects);\n\n log('updated bindings', {\n skills: mergedSkills.length,\n objects: mergedObjects.length,\n });\n }\n\n protected override async _close(): Promise<void> {\n // Reset atoms to empty state.\n this._registry.set(this._skills, []);\n this._registry.set(this._objects, []);\n }\n\n async bind({ skills, objects }: BindingProps): Promise<void> {\n const currentSkills = this._registry.get(this._skills);\n const currentObjects = this._registry.get(this._objects);\n\n const { added: addedSkills, next: nextSkills } = this._processBindings(skills, currentSkills, this.#bound.skills);\n const { added: addedObjects, next: nextObjects } = this._processBindings(\n objects,\n currentObjects,\n this.#bound.objects,\n );\n if (!addedSkills.length && !addedObjects.length) {\n return;\n }\n addedSkills.forEach((ref) => this.#bound.skills.add(refKey(ref.uri)));\n addedObjects.forEach((ref) => this.#bound.objects.add(refKey(ref.uri)));\n\n // Atomic updates - subscribers notified automatically.\n this._registry.set(this._skills, nextSkills);\n this._registry.set(this._objects, nextObjects);\n\n log('bind', { skills: addedSkills.length, objects: addedObjects.length });\n await this._write(\n Obj.make(Binding, {\n skills: {\n added: addedSkills,\n removed: [],\n },\n objects: {\n added: addedObjects,\n removed: [],\n },\n }),\n );\n }\n\n async unbind({ skills, objects }: BindingProps): Promise<void> {\n if (!skills?.length && !objects?.length) {\n return;\n }\n\n (skills ?? []).forEach((ref) => this.#bound.skills.delete(refKey(ref.uri)));\n (objects ?? []).forEach((ref) => this.#bound.objects.delete(refKey(ref.uri)));\n\n // Immediately update atom state so removals are reflected before the queue round-trips.\n const removedSkillKeys = new Set((skills ?? []).map((ref) => refKey(ref.uri)));\n const removedObjectKeys = new Set((objects ?? []).map((ref) => refKey(ref.uri)));\n if (removedSkillKeys.size > 0) {\n const current = this._registry.get(this._skills);\n this._registry.set(\n this._skills,\n current.filter((obj) => !removedSkillKeys.has(refKey(Obj.getURI(obj)))),\n );\n }\n if (removedObjectKeys.size > 0) {\n const current = this._registry.get(this._objects);\n this._registry.set(\n this._objects,\n current.filter((obj) => !removedObjectKeys.has(refKey(Obj.getURI(obj)))),\n );\n }\n\n log('unbind', { skills: skills?.length, objects: objects?.length });\n await this._write(\n Obj.make(Binding, {\n skills: {\n added: [],\n removed: skills ?? [],\n },\n objects: {\n added: [],\n removed: objects ?? [],\n },\n }),\n );\n }\n\n private async _write(binding: Binding): Promise<void> {\n if (this.#pending) {\n this.#pending.push(binding);\n await this._resolveHeld(this.#pending);\n return;\n }\n await this._append(binding);\n }\n\n /**\n * Sets the atoms from the held bindings. Their refs have no feed query to hydrate them, so each is\n * resolved against the database, which also spans the registry.\n */\n private async _resolveHeld(held: Binding[]): Promise<void> {\n const generation = ++this.#heldGeneration;\n const { db } = Context.get(this._runtime, Database.Service);\n const { skills, objects } = this._reduce(held);\n const hydrate = <T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>): Ref.Ref<T>[] =>\n [...refs].map((ref) => (ref.isAvailable ? ref : db.makeRef<T>(ref.uri)));\n const [resolvedSkills, resolvedObjects] = await Promise.all([\n this._resolve(hydrate(skills), this._registry.get(this._skills)),\n this._resolve(hydrate(objects), this._registry.get(this._objects)),\n ]);\n if (generation !== this.#heldGeneration) {\n return;\n }\n this._registry.set(this._skills, resolvedSkills);\n this._registry.set(this._objects, resolvedObjects);\n }\n\n private async _append(binding: Binding): Promise<void> {\n await RuntimeProvider.runPromise(Effect.succeed(this._runtime))(Feed.append(this._feed, [binding]));\n countBindingWrite(this._feed);\n }\n\n /**\n * Process bindings to filter duplicates (against the resolved targets and the refs already bound) and\n * determine next state.\n */\n private _processBindings<T extends Obj.Unknown>(\n refs: Ref.Ref<T>[] | undefined,\n current: T[],\n bound: ReadonlySet<URI.URI>,\n ): { added: Ref.Ref<T>[]; next: T[] } {\n const next = [...current];\n const added: Ref.Ref<T>[] = [];\n if (!refs?.length) {\n return { added, next };\n }\n\n const seen = new Set<URI.URI>([...bound, ...current.map((obj) => refKey(Obj.getURI(obj)))]);\n for (const ref of refs) {\n const key = refKey(ref.uri);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n added.push(ref);\n\n // Only resolve target if available (has target or resolver).\n if (ref.isAvailable) {\n const target = ref.target;\n if (target) {\n next.push(target);\n }\n }\n }\n\n return { added, next };\n }\n\n /**\n * Reduce results into sets of skills and objects.\n *\n * Order-sensitive: a later removal must fold after the addition it undoes, so callers pass items\n * in append order (see {@link inAppendOrder}).\n */\n private _reduce(items: Binding[]): Bindings {\n return Function.pipe(\n items,\n EArray.reduce(new Bindings(), (context, { skills, objects }) => {\n skills.added.forEach((ref) => context.skills.add(ref));\n skills.removed.forEach((ref) => context.skills.delete(ref));\n\n objects.added.forEach((ref) => context.objects.add(ref));\n objects.removed.forEach((ref) => {\n for (const obj of context.objects) {\n if (\n obj.uri === ref.uri ||\n (EID.tryParse(obj.uri) &&\n EID.tryParse(ref.uri) &&\n EID.getEntityId(EID.tryParse(obj.uri)!) === EID.getEntityId(EID.tryParse(ref.uri)!))\n ) {\n context.objects.delete(obj);\n }\n }\n });\n\n return context;\n }),\n );\n }\n\n /**\n * Merge resolved items into the current set, adding only items not already present (by DXN).\n */\n private _mergeInto<T extends Obj.Unknown>(current: T[], resolved: T[]): T[] {\n const seen = new Set(current.map((obj) => Obj.getURI(obj)));\n const merged = [...current];\n for (const obj of resolved) {\n const uri = Obj.getURI(obj);\n if (!seen.has(uri)) {\n seen.add(uri);\n merged.push(obj);\n }\n }\n return merged;\n }\n\n /**\n * Resolve references to objects, loading them first if needed and falling back to existing objects.\n * DXN refs (e.g. `dxn:org.dxos.skill.database`) resolve via the wired-up ECHO ref resolver\n * which already spans both the space DB and the hypergraph registry.\n */\n private async _resolve<T extends Obj.Unknown>(refs: Iterable<Ref.Ref<T>>, current: T[]): Promise<T[]> {\n const refArray = [...refs];\n\n // Load all refs that need loading.\n await Promise.all(refArray.map((ref) => ref.tryLoad()));\n\n return refArray\n .map((ref) => {\n let target: T | undefined;\n // Only resolve target if available (has target or resolver).\n if (ref.isAvailable) {\n target = ref.target;\n }\n\n // Fallback to existing object.\n const resolved = target ?? current.find((obj) => Obj.getURI(obj) === ref.uri);\n if (!resolved) {\n // A binding that cannot load (e.g. a registry skill the host never registered) would otherwise\n // vanish from the session without trace.\n log.warn('unresolved context binding', { uri: ref.uri });\n }\n return resolved;\n })\n .filter(isNonNullable);\n }\n}\n\n/**\n * Identity of a bound ref: an ECHO URI compares in its local form, since some carry the space and some do not.\n */\nconst refKey = (uri: URI.URI): URI.URI => {\n const echoUri = EID.tryParse(uri);\n return echoUri ? EID.toLocal(echoUri) : uri;\n};\n\n/**\n * Bindings in the order they were appended, which is the order the fold has to see them: a query\n * returns an unordered set, so an unsorted fold can apply a removal before the addition it undoes\n * and silently keep the object bound. Server-assigned position is authoritative; a locally-written\n * block has none yet and sorts last, which is correct — it is the newest.\n */\nconst inAppendOrder = (items: readonly Binding[]): Binding[] =>\n EArray.sort(items, Order.mapInput(Order.Number, Feed.getPosition));\n"],"mappings":"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"}
@@ -24,11 +24,11 @@ var runHooks = ({ skills, phase, invoke }) => Effect.gen(function* () {
24
24
  invariant(Obj.instanceOf(Operation.PersistentOperation, record), "Hook function must be an operation.", {
25
25
  "~LogMeta": "~LogMeta",
26
26
  F: __dxlog_file,
27
- L: 60,
27
+ L: 61,
28
28
  S: this,
29
29
  A: ["Obj.instanceOf(Operation.PersistentOperation, record)", "'Hook function must be an operation.'"]
30
30
  });
31
- yield* invoke(Operation.deserialize(record), { ...hook.input });
31
+ yield* invoke(Operation.deserialize(record), { ...hook.input }, hook);
32
32
  }).pipe(Effect.catchCause((cause) => Effect.sync(() => log.warn("skill hook failed", {
33
33
  phase,
34
34
  skill: Obj.getMeta(skill).key,
@@ -36,7 +36,7 @@ var runHooks = ({ skills, phase, invoke }) => Effect.gen(function* () {
36
36
  }, {
37
37
  "~LogMeta": "~LogMeta",
38
38
  F: __dxlog_file,
39
- L: 69,
39
+ L: 70,
40
40
  S: this
41
41
  }))));
42
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SkillHooks.mjs","names":[],"sources":["../../../src/session/SkillHooks.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Operation from '@dxos/compute/Operation';\nimport * as Skill from '@dxos/compute/Skill';\nimport { Database, Obj } from '@dxos/echo';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\n/**\n * Tag of the lifecycle point a hook fires at, matching {@link Skill.HookSpec}.\n */\nexport type HookPhase = 'begin-request' | 'end-request';\n\n/**\n * Invokes a resolved operation with the hook's templated input. Begin and end hooks fire in\n * different contexts (the agent's turn fiber vs. a child process with Tier B reachable), so the\n * invocation strategy is supplied by the caller rather than fixed here. `R` carries the invoker's\n * own requirements (e.g. `Operation.Service`) up into {@link runHooks}.\n */\nexport type HookInvoker<R> = (\n operation: Operation.Definition.Any,\n input: Record<string, unknown>,\n) => Effect.Effect<void, never, R>;\n\n/**\n * Runs every {@link Skill.Hook} on `skills` whose spec matches `phase`. Each hook resolves its\n * `function` ref to a {@link Operation.PersistentOperation}, deserializes it to a definition, and\n * invokes it with the templated `input` via `invoke`. Hooks without a function ref are skipped.\n *\n * Hook failures are isolated: a throwing hook logs and is swallowed so one skill's hook cannot\n * abort the request lifecycle (or the sibling hooks).\n */\nexport const runHooks = <R>({\n skills,\n phase,\n invoke,\n}: {\n skills: readonly Skill.Skill[];\n phase: HookPhase;\n invoke: HookInvoker<R>;\n}): Effect.Effect<void, never, Database.Service | R> =>\n Effect.gen(function* () {\n for (const skill of skills) {\n for (const hook of skill.hooks ?? []) {\n if (hook.spec._tag !== phase) {\n continue;\n }\n if (!hook.function) {\n continue;\n }\n\n yield* Effect.gen(function* () {\n const record = yield* Database.load(hook.function!);\n invariant(Obj.instanceOf(Operation.PersistentOperation, record), 'Hook function must be an operation.');\n const operation = Operation.deserialize(record);\n // Hooks carry no event payload (the spec is a bare tag), so the input template is passed\n // through verbatim — the hook's operation reads any conversation state it needs itself.\n yield* invoke(operation, { ...hook.input });\n }).pipe(\n Effect.catchCause((cause) =>\n // Meta key rather than `Skill.getKey`: a space-authored skill has none, and an error\n // report must not itself throw.\n Effect.sync(() => log.warn('skill hook failed', { phase, skill: Obj.getMeta(skill).key, cause })),\n ),\n );\n }\n }\n });\n"],"mappings":";;;;;;;;;;;;;;;;;AAsCA,IAAa,YAAe,EAC1B,QACA,OACA,aAMA,OAAO,IAAI,aAAa;CACtB,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;EACpC,IAAI,KAAK,KAAK,SAAS,OACrB;EAEF,IAAI,CAAC,KAAK,UACR;EAGF,OAAO,OAAO,IAAI,aAAa;GAC7B,MAAM,SAAS,OAAO,SAAS,KAAK,KAAK,QAAS;GAClD,UAAU,IAAI,WAAW,UAAU,qBAAqB,MAAM,GAAG,uCAAoC;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,CAAA,yDAAA,uCAAA;GAAA,CAAC;GAItG,OAAO,OAHW,UAAU,YAAY,MAG1B,GAAW,EAAE,GAAG,KAAK,MAAM,CAAC;EAC5C,CAAC,CAAC,CAAC,KACD,OAAO,YAAY,UAGjB,OAAO,WAAW,IAAI,KAAK,qBAAqB;GAAE;GAAO,OAAO,IAAI,QAAQ,KAAK,CAAC,CAAC;GAAK;EAAM,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC,CAClG,CACF;CACF;AAEJ,CAAC"}
1
+ {"version":3,"file":"SkillHooks.mjs","names":[],"sources":["../../../src/session/SkillHooks.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Operation from '@dxos/compute/Operation';\nimport * as Skill from '@dxos/compute/Skill';\nimport { Database, Obj } from '@dxos/echo';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\n/**\n * Tag of the lifecycle point a hook fires at, matching {@link Skill.HookSpec}.\n */\nexport type HookPhase = 'begin-request' | 'end-request';\n\n/**\n * Invokes a resolved operation with the hook's templated input. Begin and end hooks fire in\n * different contexts (the agent's turn fiber vs. a child process with Tier B reachable), so the\n * invocation strategy is supplied by the caller rather than fixed here. `R` carries the invoker's\n * own requirements (e.g. `Operation.Service`) up into {@link runHooks}.\n */\nexport type HookInvoker<R> = (\n operation: Operation.Definition.Any,\n input: Record<string, unknown>,\n hook: Skill.Hook,\n) => Effect.Effect<void, never, R>;\n\n/**\n * Runs every {@link Skill.Hook} on `skills` whose spec matches `phase`. Each hook resolves its\n * `function` ref to a {@link Operation.PersistentOperation}, deserializes it to a definition, and\n * invokes it with the templated `input` via `invoke`. Hooks without a function ref are skipped.\n *\n * Hook failures are isolated: a throwing hook logs and is swallowed so one skill's hook cannot\n * abort the request lifecycle (or the sibling hooks).\n */\nexport const runHooks = <R>({\n skills,\n phase,\n invoke,\n}: {\n skills: readonly Skill.Skill[];\n phase: HookPhase;\n invoke: HookInvoker<R>;\n}): Effect.Effect<void, never, Database.Service | R> =>\n Effect.gen(function* () {\n for (const skill of skills) {\n for (const hook of skill.hooks ?? []) {\n if (hook.spec._tag !== phase) {\n continue;\n }\n if (!hook.function) {\n continue;\n }\n\n yield* Effect.gen(function* () {\n const record = yield* Database.load(hook.function!);\n invariant(Obj.instanceOf(Operation.PersistentOperation, record), 'Hook function must be an operation.');\n const operation = Operation.deserialize(record);\n // Hooks carry no event payload (the spec is a bare tag), so the input template is passed\n // through verbatim — the hook's operation reads any conversation state it needs itself.\n yield* invoke(operation, { ...hook.input }, hook);\n }).pipe(\n Effect.catchCause((cause) =>\n // Meta key rather than `Skill.getKey`: a space-authored skill has none, and an error\n // report must not itself throw.\n Effect.sync(() => log.warn('skill hook failed', { phase, skill: Obj.getMeta(skill).key, cause })),\n ),\n );\n }\n }\n });\n"],"mappings":";;;;;;;;;;;;;;;;;AAuCA,IAAa,YAAe,EAC1B,QACA,OACA,aAMA,OAAO,IAAI,aAAa;CACtB,KAAK,MAAM,SAAS,QAClB,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;EACpC,IAAI,KAAK,KAAK,SAAS,OACrB;EAEF,IAAI,CAAC,KAAK,UACR;EAGF,OAAO,OAAO,IAAI,aAAa;GAC7B,MAAM,SAAS,OAAO,SAAS,KAAK,KAAK,QAAS;GAClD,UAAU,IAAI,WAAW,UAAU,qBAAqB,MAAM,GAAG,uCAAoC;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,CAAA,yDAAA,uCAAA;GAAA,CAAC;GAItG,OAAO,OAHW,UAAU,YAAY,MAG1B,GAAW,EAAE,GAAG,KAAK,MAAM,GAAG,IAAI;EAClD,CAAC,CAAC,CAAC,KACD,OAAO,YAAY,UAGjB,OAAO,WAAW,IAAI,KAAK,qBAAqB;GAAE;GAAO,OAAO,IAAI,QAAQ,KAAK,CAAC,CAAC;GAAK;EAAM,GAAA;GAAA,YAAA;GAAA,GAAA;GAAA,GAAA;GAAA,GAAA;EAAA,CAAC,CAAC,CAClG,CACF;CACF;AAEJ,CAAC"}
@@ -11,7 +11,9 @@ import * as Instructions from "@dxos/compute/Instructions";
11
11
  //#region src/types/Agent.ts
12
12
  var Agent_exports = /* @__PURE__ */ __exportAll({
13
13
  Agent: () => Agent,
14
+ chatLocation: () => chatLocation,
14
15
  getFromChatContext: () => getFromChatContext,
16
+ isAgentChat: () => isAgentChat,
15
17
  loadChat: () => loadChat,
16
18
  loadForChat: () => loadForChat,
17
19
  loadInstructions: () => loadInstructions,
@@ -90,6 +92,17 @@ var loadForChat = (chat) => Effect.sync(() => {
90
92
  return parent && Obj.instanceOf(Agent, parent) ? parent : void 0;
91
93
  });
92
94
  /**
95
+ * Where a chat's turns run: as its `remote` flag says, else on EDGE for an agent's chat, since its chats share
96
+ * the agent's one brain, which only EDGE holds. An explicit `remote: false` keeps an agent's chats local, for
97
+ * a runtime with no EDGE to reach (a story or test), where every chat shares the client's brain instead.
98
+ */
99
+ var chatLocation = (chat) => chat.remote ?? isAgentChat(chat) ? "edge" : "local";
100
+ /** Whether the chat runs as an agent (the agent parents it); synchronous, like {@link loadForChat}. */
101
+ var isAgentChat = (chat) => {
102
+ const parent = Obj.getParent(chat);
103
+ return parent !== void 0 && Obj.instanceOf(Agent, parent);
104
+ };
105
+ /**
93
106
  * Creates a fully initialized Agent with its first chat and context bindings.
94
107
  *
95
108
  * @param props - Agent properties including spec, skills, and context objects.
@@ -184,6 +197,6 @@ var getFromChatContext = Effect.gen(function* () {
184
197
  return agents[0];
185
198
  });
186
199
  //#endregion
187
- export { Agent, getFromChatContext, loadChat, loadForChat, loadInstructions, makeInitialized, resetChatHistory, Agent_exports as t };
200
+ export { Agent, chatLocation, getFromChatContext, isAgentChat, loadChat, loadForChat, loadInstructions, makeInitialized, resetChatHistory, Agent_exports as t };
188
201
 
189
202
  //# sourceMappingURL=Agent.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Agent.mjs","names":[],"sources":["../../../src/types/Agent.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport * as Instructions from '@dxos/compute/Instructions';\nimport * as Skill from '@dxos/compute/Skill';\nimport { Annotation, Database, DXN, type Error as EchoError, Feed, Filter, Obj, Ref, Type } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { IdentityDid } from '@dxos/keys';\n\nimport type * as Harness from '../session/Harness.ts';\nimport { HarnessContextError } from '../util/errors.ts';\nimport * as Chat from './Chat.ts';\n\n/**\n * An agent identity: a personality (attribution DID) plus its preset payload (instructions with\n * text, skills, objects, and commands). Owns no conversation state — a chat is linked to the agent\n * it runs as by the ECHO parent edge (the agent parents its chats), and durable work products live on a Project\n * (plugin-projects DESIGN.md, \"Agent ↔ Project convergence\").\n */\n\n/**\n * The AI session runtime (MCP SDK, Anthropic client, ~280 KB) is loaded lazily: this module holds\n * the Agent *schema*, which core plugins reference for operation definitions, so the runtime loads\n * only when an agent operation actually runs.\n */\nconst aiContextRuntime = () => import('../session/AiContext.ts');\nconst harnessRuntime = () => import('../session/Harness.ts');\n\nexport class Agent extends Type.makeObject<Agent>(DXN.make('org.dxos.type.agent', '0.2.0'))(\n Schema.Struct({\n name: Schema.optional(Schema.String),\n\n /**\n * Stable identity DID for the agent, used to attribute content it authors (e.g. suggestion\n * branches) as its own author — distinct from any human or other agent. Provisioned\n * externally, never minted by {@link makeInitialized} (see the note there); the slot a real\n * HALO identity DID takes once agents get first-class identities. Optional because nothing\n * populates it yet.\n */\n did: Schema.optional(\n // Annotated inside `optional`: the form reads the title from the value schema, not the wrapper.\n IdentityDid.annotate({\n title: 'DID',\n description: \"The agent's identity DID; attributes content the agent authors.\",\n }),\n ).annotate({\n title: 'DID',\n description: \"The agent's DID.\",\n }),\n\n /**\n * Master switch for the agent's automation (propagated onto its compiled routine triggers).\n */\n enabled: Schema.optional(Schema.Boolean).annotate({\n title: 'Enabled',\n description: 'Master switch for agent automation; propagated to all triggers on sync.',\n }),\n\n /**\n * Instructions for the agent — the preset payload (text, skills, objects, commands) a chat\n * receives when the agent is applied to it. Owned: `SetParent` cascades it with the agent.\n */\n instructions: Ref.Ref(Instructions.Instructions).pipe(\n Annotation.SetParent.set(),\n Annotation.FormInlineAnnotation.set(true),\n Schema.annotate({ title: 'Instructions' }),\n ),\n }).pipe(\n Annotation.LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--drone--regular', hue: 'amber' }),\n Annotation.UserType.set(),\n ),\n) {}\n\n/**\n * Resolves the agent's instructions and their markdown text.\n */\nexport const loadInstructions = (\n agent: Agent,\n): Effect.Effect<\n { text: string; instructions: Instructions.Instructions },\n EchoError.EntityNotFoundError,\n Database.Service\n> =>\n Effect.gen(function* () {\n const instructions = yield* Database.load(agent.instructions);\n const text = yield* Database.load(instructions.text).pipe(\n Effect.map((doc) => doc.content),\n Effect.catchTag('EntityNotFoundError', () => Effect.succeed('')),\n );\n return { text, instructions };\n });\n\n/**\n * Resolves the agent's primary chat (the agent holds no chat ref): the latest chat parented to the\n * agent. Entity ids are ULIDs, so the id order is creation order — `resetChatHistory` makes the\n * replacement chat primary simply by creating it later.\n */\nexport const loadChat = (agent: Agent): Effect.Effect<Chat.Chat | undefined, never, Database.Service> =>\n Effect.gen(function* () {\n // A child-of filter rather than a `.children()` traversal: EDGE's query planner has no hierarchy\n // traversal, so an agent hosted there could not resolve its chat.\n const chats = yield* Database.query(Filter.and(Filter.type(Chat.Chat), Filter.childOf(agent))).run;\n return (\n chats\n // Chats bridged from an external conversation (a Discord thread) carry its foreign key; they are\n // the agent's too, but never its own primary chat.\n .filter((chat) => Obj.getMeta(chat).keys.length === 0)\n .sort((left, right) => left.id.localeCompare(right.id))\n .at(-1)\n );\n }).pipe(Effect.orDie);\n\n/**\n * Resolves the agent a chat runs as, if any. Plain (agentless) chats yield `undefined`.\n * The parent is loaded with the object, so this is synchronous — no query.\n */\nexport const loadForChat = (chat: Chat.Chat): Effect.Effect<Agent | undefined> =>\n Effect.sync(() => {\n const parent = Obj.getParent(chat);\n return parent && Obj.instanceOf(Agent, parent) ? parent : undefined;\n });\n\nexport type MakeProps = Omit<Obj.MakeProps<typeof Agent>, 'instructions'> & {\n instructions: string;\n skills?: Ref.Ref<Skill.Skill>[];\n contextObjects?: Ref.Ref<Obj.Any>[];\n};\n\n/**\n * Creates a fully initialized Agent with its first chat and context bindings.\n *\n * @param props - Agent properties including spec, skills, and context objects.\n * @param skill - The skill to use for the agent context: a skill object is cloned into the space, a ref\n * (e.g. a registry URI) is bound as-is so the agent follows the compiled skill until customized.\n * @returns An Effect that yields the initialized Agent.\n */\nexport const makeInitialized = (\n props: MakeProps,\n // TODO(burdon): Reconcile with props.skills.\n skill: Skill.Skill | Ref.Ref<Skill.Skill>,\n): Effect.Effect<Agent, never, Database.Service> =>\n Effect.gen(function* () {\n const { skills: propsSkills, contextObjects, ...agentProps } = props;\n\n // Loaded before anything is persisted: a rejected chunk after the first `Database.add` would\n // leave a half-built agent graph behind with nothing to clean it up.\n const AiContextRuntime = yield* Effect.promise(aiContextRuntime);\n\n // Persist any inline (transient) skills so their refs are resolvable from feed bindings later.\n // Refs created with Ref.make(obj) carry an inline target, but when stored in ECHO and read back\n // by a new AiSession, the target is lost and must be found in the DB via tryLoad().\n // `peek` rather than `target`: a registry-URI ref has no inline target and no resolver yet.\n const persistedPropsSkills = yield* Effect.all(\n (propsSkills ?? []).map((ref) => {\n const target = ref.peek();\n return target !== undefined\n ? Database.add(target).pipe(Effect.map((persisted) => Ref.make(persisted)))\n : Effect.succeed(ref);\n }),\n );\n\n // The typed Instructions is the agent's preset payload: text plus skill set.\n const instructions = yield* Database.add(\n Instructions.make({ text: props.instructions, skills: persistedPropsSkills }),\n );\n const agent = yield* Database.add(\n Obj.make(Agent, {\n // `did` (if provided) flows through via agentProps. Not auto-minted here: `IdentityDid.random()`\n // uses uncontrolled `randomBytes`, which would make agent creation non-deterministic and break\n // memoized-LLM tests. Minting is deferred to the runtime-identity provision (see agent-identity\n // spec), where it can be deterministic or a real HALO DID.\n ...agentProps,\n instructions: Ref.make(instructions),\n enabled: props.enabled ?? true,\n }),\n );\n const feed = yield* Database.add(Feed.make());\n const runtime = yield* Effect.context<Database.Service>();\n const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({ feed, runtime }));\n const agentSkill = Ref.isRef(skill) ? skill : Ref.make(yield* Database.add(Obj.clone(skill, { deep: 'all' })));\n\n const chat = yield* Database.add(\n Chat.make({\n feed: Ref.make(feed),\n // Steered through the chat's own channel: instructions render into the system prompt.\n // Identity/attribution comes from the companion link (ref on the agent + parent edge).\n instructions: Ref.make(instructions),\n }),\n );\n Chat.linkCompanion({ chat, subject: agent });\n yield* Effect.promise(() =>\n contextBinder.bind({\n skills: [agentSkill, ...persistedPropsSkills],\n objects: [Ref.make(agent), Ref.make(chat), ...(contextObjects ?? [])],\n }),\n );\n return agent;\n }).pipe(Effect.scoped);\n\n/**\n * Resets the agent's chat history by rebuilding the chat context.\n * Preserves the existing skills and objects from the current chat context.\n *\n * @param agent - The agent whose chat history should be reset. Must have an existing chat.\n * @returns An Effect that resets the chat history.\n */\nexport const resetChatHistory = (agent: Agent): Effect.Effect<void, EchoError.EntityNotFoundError, Database.Service> =>\n Effect.gen(function* () {\n const existingChat = yield* loadChat(agent);\n if (!existingChat) {\n return yield* Effect.die(new Error('Agent must have an existing chat to reset.'));\n }\n\n const existingFeed = yield* Database.load(existingChat.feed);\n const runtime = yield* Effect.context<Database.Service>();\n const AiContextRuntime = yield* Effect.promise(aiContextRuntime);\n const existingContextBinder = yield* EffectEx.acquireReleaseResource(\n () =>\n new AiContextRuntime.Binder({\n feed: existingFeed,\n runtime,\n }),\n );\n const skills = existingContextBinder.getSkills().map(Skill.makeRef);\n const objects = existingContextBinder\n .getObjects()\n .filter((object) => !Obj.instanceOf(Chat.Chat, object))\n .map((object) => Ref.make(object));\n\n const feed = yield* Database.add(Feed.make());\n const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({ feed, runtime }));\n\n const chat = yield* Database.add(\n Chat.make({\n feed: Ref.make(feed),\n instructions: agent.instructions,\n }),\n );\n Chat.linkCompanion({ chat, subject: agent });\n yield* Effect.promise(() =>\n contextBinder.bind({\n skills,\n objects: [...objects, Ref.make(chat)],\n }),\n );\n\n // The old chat stays parented (history is retired, not deleted); the new chat is primary\n // because `loadChat` resolves the latest child by creation order.\n }).pipe(Effect.scoped);\n\nexport const getFromChatContext: Effect.Effect<\n Agent,\n HarnessContextError | Harness.NotSupportedError,\n Harness.HarnessService\n> = Effect.gen(function* () {\n const HarnessRuntime = yield* Effect.promise(harnessRuntime);\n const agents = yield* HarnessRuntime.queryContext(Filter.type(Agent));\n if (agents.length !== 1) {\n return yield* Effect.fail(new HarnessContextError({ type: 'agent', count: agents.length }));\n }\n\n const agent = agents[0];\n return agent;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"Agent.mjs","names":[],"sources":["../../../src/types/Agent.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport type * as AgentService from '@dxos/compute/AgentService';\nimport * as Instructions from '@dxos/compute/Instructions';\nimport * as Skill from '@dxos/compute/Skill';\nimport { Annotation, Database, DXN, type Error as EchoError, Feed, Filter, Obj, Ref, Type } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { IdentityDid } from '@dxos/keys';\n\nimport type * as Harness from '../session/Harness.ts';\nimport { HarnessContextError } from '../util/errors.ts';\nimport * as Chat from './Chat.ts';\n\n/**\n * An agent identity: a personality (attribution DID) plus its preset payload (instructions with\n * text, skills, objects, and commands). Owns no conversation state — a chat is linked to the agent\n * it runs as by the ECHO parent edge (the agent parents its chats), and durable work products live on a Project\n * (plugin-projects DESIGN.md, \"Agent ↔ Project convergence\").\n */\n\n/**\n * The AI session runtime (MCP SDK, Anthropic client, ~280 KB) is loaded lazily: this module holds\n * the Agent *schema*, which core plugins reference for operation definitions, so the runtime loads\n * only when an agent operation actually runs.\n */\nconst aiContextRuntime = () => import('../session/AiContext.ts');\nconst harnessRuntime = () => import('../session/Harness.ts');\n\nexport class Agent extends Type.makeObject<Agent>(DXN.make('org.dxos.type.agent', '0.2.0'))(\n Schema.Struct({\n name: Schema.optional(Schema.String),\n\n /**\n * Stable identity DID for the agent, used to attribute content it authors (e.g. suggestion\n * branches) as its own author — distinct from any human or other agent. Provisioned\n * externally, never minted by {@link makeInitialized} (see the note there); the slot a real\n * HALO identity DID takes once agents get first-class identities. Optional because nothing\n * populates it yet.\n */\n did: Schema.optional(\n // Annotated inside `optional`: the form reads the title from the value schema, not the wrapper.\n IdentityDid.annotate({\n title: 'DID',\n description: \"The agent's identity DID; attributes content the agent authors.\",\n }),\n ).annotate({\n title: 'DID',\n description: \"The agent's DID.\",\n }),\n\n /**\n * Master switch for the agent's automation (propagated onto its compiled routine triggers).\n */\n enabled: Schema.optional(Schema.Boolean).annotate({\n title: 'Enabled',\n description: 'Master switch for agent automation; propagated to all triggers on sync.',\n }),\n\n /**\n * Instructions for the agent — the preset payload (text, skills, objects, commands) a chat\n * receives when the agent is applied to it. Owned: `SetParent` cascades it with the agent.\n */\n instructions: Ref.Ref(Instructions.Instructions).pipe(\n Annotation.SetParent.set(),\n Annotation.FormInlineAnnotation.set(true),\n Schema.annotate({ title: 'Instructions' }),\n ),\n }).pipe(\n Annotation.LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--drone--regular', hue: 'amber' }),\n Annotation.UserType.set(),\n ),\n) {}\n\n/**\n * Resolves the agent's instructions and their markdown text.\n */\nexport const loadInstructions = (\n agent: Agent,\n): Effect.Effect<\n { text: string; instructions: Instructions.Instructions },\n EchoError.EntityNotFoundError,\n Database.Service\n> =>\n Effect.gen(function* () {\n const instructions = yield* Database.load(agent.instructions);\n const text = yield* Database.load(instructions.text).pipe(\n Effect.map((doc) => doc.content),\n Effect.catchTag('EntityNotFoundError', () => Effect.succeed('')),\n );\n return { text, instructions };\n });\n\n/**\n * Resolves the agent's primary chat (the agent holds no chat ref): the latest chat parented to the\n * agent. Entity ids are ULIDs, so the id order is creation order — `resetChatHistory` makes the\n * replacement chat primary simply by creating it later.\n */\nexport const loadChat = (agent: Agent): Effect.Effect<Chat.Chat | undefined, never, Database.Service> =>\n Effect.gen(function* () {\n // A child-of filter rather than a `.children()` traversal: EDGE's query planner has no hierarchy\n // traversal, so an agent hosted there could not resolve its chat.\n const chats = yield* Database.query(Filter.and(Filter.type(Chat.Chat), Filter.childOf(agent))).run;\n return (\n chats\n // Chats bridged from an external conversation (a Discord thread) carry its foreign key; they are\n // the agent's too, but never its own primary chat.\n .filter((chat) => Obj.getMeta(chat).keys.length === 0)\n .sort((left, right) => left.id.localeCompare(right.id))\n .at(-1)\n );\n }).pipe(Effect.orDie);\n\n/**\n * Resolves the agent a chat runs as, if any. Plain (agentless) chats yield `undefined`.\n * The parent is loaded with the object, so this is synchronous — no query.\n */\nexport const loadForChat = (chat: Chat.Chat): Effect.Effect<Agent | undefined> =>\n Effect.sync(() => {\n const parent = Obj.getParent(chat);\n return parent && Obj.instanceOf(Agent, parent) ? parent : undefined;\n });\n\n/**\n * Where a chat's turns run: as its `remote` flag says, else on EDGE for an agent's chat, since its chats share\n * the agent's one brain, which only EDGE holds. An explicit `remote: false` keeps an agent's chats local, for\n * a runtime with no EDGE to reach (a story or test), where every chat shares the client's brain instead.\n */\nexport const chatLocation = (chat: Chat.Chat): AgentService.AgentLocation =>\n (chat.remote ?? isAgentChat(chat)) ? 'edge' : 'local';\n\n/** Whether the chat runs as an agent (the agent parents it); synchronous, like {@link loadForChat}. */\nexport const isAgentChat = (chat: Chat.Chat): boolean => {\n const parent = Obj.getParent(chat);\n return parent !== undefined && Obj.instanceOf(Agent, parent);\n};\n\nexport type MakeProps = Omit<Obj.MakeProps<typeof Agent>, 'instructions'> & {\n instructions: string;\n skills?: Ref.Ref<Skill.Skill>[];\n contextObjects?: Ref.Ref<Obj.Any>[];\n};\n\n/**\n * Creates a fully initialized Agent with its first chat and context bindings.\n *\n * @param props - Agent properties including spec, skills, and context objects.\n * @param skill - The skill to use for the agent context: a skill object is cloned into the space, a ref\n * (e.g. a registry URI) is bound as-is so the agent follows the compiled skill until customized.\n * @returns An Effect that yields the initialized Agent.\n */\nexport const makeInitialized = (\n props: MakeProps,\n // TODO(burdon): Reconcile with props.skills.\n skill: Skill.Skill | Ref.Ref<Skill.Skill>,\n): Effect.Effect<Agent, never, Database.Service> =>\n Effect.gen(function* () {\n const { skills: propsSkills, contextObjects, ...agentProps } = props;\n\n // Loaded before anything is persisted: a rejected chunk after the first `Database.add` would\n // leave a half-built agent graph behind with nothing to clean it up.\n const AiContextRuntime = yield* Effect.promise(aiContextRuntime);\n\n // Persist any inline (transient) skills so their refs are resolvable from feed bindings later.\n // Refs created with Ref.make(obj) carry an inline target, but when stored in ECHO and read back\n // by a new AiSession, the target is lost and must be found in the DB via tryLoad().\n // `peek` rather than `target`: a registry-URI ref has no inline target and no resolver yet.\n const persistedPropsSkills = yield* Effect.all(\n (propsSkills ?? []).map((ref) => {\n const target = ref.peek();\n return target !== undefined\n ? Database.add(target).pipe(Effect.map((persisted) => Ref.make(persisted)))\n : Effect.succeed(ref);\n }),\n );\n\n // The typed Instructions is the agent's preset payload: text plus skill set.\n const instructions = yield* Database.add(\n Instructions.make({ text: props.instructions, skills: persistedPropsSkills }),\n );\n const agent = yield* Database.add(\n Obj.make(Agent, {\n // `did` (if provided) flows through via agentProps. Not auto-minted here: `IdentityDid.random()`\n // uses uncontrolled `randomBytes`, which would make agent creation non-deterministic and break\n // memoized-LLM tests. Minting is deferred to the runtime-identity provision (see agent-identity\n // spec), where it can be deterministic or a real HALO DID.\n ...agentProps,\n instructions: Ref.make(instructions),\n enabled: props.enabled ?? true,\n }),\n );\n const feed = yield* Database.add(Feed.make());\n const runtime = yield* Effect.context<Database.Service>();\n const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({ feed, runtime }));\n const agentSkill = Ref.isRef(skill) ? skill : Ref.make(yield* Database.add(Obj.clone(skill, { deep: 'all' })));\n\n const chat = yield* Database.add(\n Chat.make({\n feed: Ref.make(feed),\n // Steered through the chat's own channel: instructions render into the system prompt.\n // Identity/attribution comes from the companion link (ref on the agent + parent edge).\n instructions: Ref.make(instructions),\n }),\n );\n Chat.linkCompanion({ chat, subject: agent });\n yield* Effect.promise(() =>\n contextBinder.bind({\n skills: [agentSkill, ...persistedPropsSkills],\n objects: [Ref.make(agent), Ref.make(chat), ...(contextObjects ?? [])],\n }),\n );\n return agent;\n }).pipe(Effect.scoped);\n\n/**\n * Resets the agent's chat history by rebuilding the chat context.\n * Preserves the existing skills and objects from the current chat context.\n *\n * @param agent - The agent whose chat history should be reset. Must have an existing chat.\n * @returns An Effect that resets the chat history.\n */\nexport const resetChatHistory = (agent: Agent): Effect.Effect<void, EchoError.EntityNotFoundError, Database.Service> =>\n Effect.gen(function* () {\n const existingChat = yield* loadChat(agent);\n if (!existingChat) {\n return yield* Effect.die(new Error('Agent must have an existing chat to reset.'));\n }\n\n const existingFeed = yield* Database.load(existingChat.feed);\n const runtime = yield* Effect.context<Database.Service>();\n const AiContextRuntime = yield* Effect.promise(aiContextRuntime);\n const existingContextBinder = yield* EffectEx.acquireReleaseResource(\n () =>\n new AiContextRuntime.Binder({\n feed: existingFeed,\n runtime,\n }),\n );\n const skills = existingContextBinder.getSkills().map(Skill.makeRef);\n const objects = existingContextBinder\n .getObjects()\n .filter((object) => !Obj.instanceOf(Chat.Chat, object))\n .map((object) => Ref.make(object));\n\n const feed = yield* Database.add(Feed.make());\n const contextBinder = yield* EffectEx.acquireReleaseResource(() => new AiContextRuntime.Binder({ feed, runtime }));\n\n const chat = yield* Database.add(\n Chat.make({\n feed: Ref.make(feed),\n instructions: agent.instructions,\n }),\n );\n Chat.linkCompanion({ chat, subject: agent });\n yield* Effect.promise(() =>\n contextBinder.bind({\n skills,\n objects: [...objects, Ref.make(chat)],\n }),\n );\n\n // The old chat stays parented (history is retired, not deleted); the new chat is primary\n // because `loadChat` resolves the latest child by creation order.\n }).pipe(Effect.scoped);\n\nexport const getFromChatContext: Effect.Effect<\n Agent,\n HarnessContextError | Harness.NotSupportedError,\n Harness.HarnessService\n> = Effect.gen(function* () {\n const HarnessRuntime = yield* Effect.promise(harnessRuntime);\n const agents = yield* HarnessRuntime.queryContext(Filter.type(Agent));\n if (agents.length !== 1) {\n return yield* Effect.fail(new HarnessContextError({ type: 'agent', count: agents.length }));\n }\n\n const agent = agents[0];\n return agent;\n});\n"],"mappings":"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"}
@@ -38,6 +38,8 @@ export type BinderOptions = {
38
38
  runtime: Context.Context<Database.Service>;
39
39
  /** @effect/atom-react Registry for reactive state management. */
40
40
  registry?: AtomRegistry.AtomRegistry;
41
+ /** Holds bindings in memory until {@link Binder.flush}, so the feed need not be stored yet (a draft conversation). */
42
+ hold?: boolean;
41
43
  };
42
44
  /**
43
45
  * Manages bindings of skills and objects to a conversation.
@@ -49,6 +51,7 @@ export declare class Binder extends Resource {
49
51
  private readonly _registry;
50
52
  private readonly _feed;
51
53
  private readonly _runtime;
54
+ private readonly _hold;
52
55
  constructor(options: BinderOptions);
53
56
  /**
54
57
  * Returns the skills atom for subscription.
@@ -75,6 +78,12 @@ export declare class Binder extends Resource {
75
78
  */
76
79
  subscribeObjects(cb: (objects: Obj.Unknown[]) => void): () => void;
77
80
  protected _open(): Promise<void>;
81
+ /**
82
+ * Writes a holding binder's bindings to its feed, which the caller has stored, as one binding. Later
83
+ * writes go straight to the feed, which the binder does not follow.
84
+ */
85
+ flush(): Promise<void>;
86
+ private _follow;
78
87
  /**
79
88
  * Re-reads bindings from the feed to pick up changes made by other processes in this realm, such
80
89
  * as a tool that bound a skill. A no-op when none has been written since the last read.
@@ -84,6 +93,13 @@ export declare class Binder extends Resource {
84
93
  protected _close(): Promise<void>;
85
94
  bind({ skills, objects }: BindingProps): Promise<void>;
86
95
  unbind({ skills, objects }: BindingProps): Promise<void>;
96
+ private _write;
97
+ /**
98
+ * Sets the atoms from the held bindings. Their refs have no feed query to hydrate them, so each is
99
+ * resolved against the database, which also spans the registry.
100
+ */
101
+ private _resolveHeld;
102
+ private _append;
87
103
  /**
88
104
  * Process bindings to filter duplicates (against the resolved targets and the refs already bound) and
89
105
  * determine next state.
@@ -1 +1 @@
1
- {"version":3,"file":"AiContext.d.ts","sourceRoot":"","sources":["../../../../src/session/AiContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AiContext.d.ts","sourceRoot":"","sources":["../../../../src/session/AiContext.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ export type HookPhase = 'begin-request' | 'end-request';
12
12
  * invocation strategy is supplied by the caller rather than fixed here. `R` carries the invoker's
13
13
  * own requirements (e.g. `Operation.Service`) up into {@link runHooks}.
14
14
  */
15
- export type HookInvoker<R> = (operation: Operation.Definition.Any, input: Record<string, unknown>) => Effect.Effect<void, never, R>;
15
+ export type HookInvoker<R> = (operation: Operation.Definition.Any, input: Record<string, unknown>, hook: Skill.Hook) => Effect.Effect<void, never, R>;
16
16
  /**
17
17
  * Runs every {@link Skill.Hook} on `skills` whose spec matches `phase`. Each hook resolves its
18
18
  * `function` ref to a {@link Operation.PersistentOperation}, deserializes it to a definition, and
@@ -1 +1 @@
1
- {"version":3,"file":"SkillHooks.d.ts","sourceRoot":"","sources":["../../../../src/session/SkillHooks.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,SAAS,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAO,MAAM,YAAY,CAAC;AAI3C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,aAAa,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAC3B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,EACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,8BAIvB;IACD,MAAM,EAAE,SAAS,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CACxB,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,GAAG,CAAC,CA2B/C,CAAC"}
1
+ {"version":3,"file":"SkillHooks.d.ts","sourceRoot":"","sources":["../../../../src/session/SkillHooks.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,SAAS,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAO,MAAM,YAAY,CAAC;AAI3C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,aAAa,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAC3B,SAAS,EAAE,SAAS,CAAC,UAAU,CAAC,GAAG,EACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,KACb,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,8BAIvB;IACD,MAAM,EAAE,SAAS,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CACxB,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,GAAG,CAAC,CA2B/C,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import * as Effect from 'effect/Effect';
2
+ import type * as AgentService from '@dxos/compute/AgentService';
2
3
  import * as Instructions from '@dxos/compute/Instructions';
3
4
  import * as Skill from '@dxos/compute/Skill';
4
5
  import { Database, type Error as EchoError, Obj, Ref, Type } from '@dxos/echo';
@@ -32,6 +33,14 @@ export declare const loadChat: (agent: Agent) => Effect.Effect<Chat.Chat | undef
32
33
  * The parent is loaded with the object, so this is synchronous — no query.
33
34
  */
34
35
  export declare const loadForChat: (chat: Chat.Chat) => Effect.Effect<Agent | undefined>;
36
+ /**
37
+ * Where a chat's turns run: as its `remote` flag says, else on EDGE for an agent's chat, since its chats share
38
+ * the agent's one brain, which only EDGE holds. An explicit `remote: false` keeps an agent's chats local, for
39
+ * a runtime with no EDGE to reach (a story or test), where every chat shares the client's brain instead.
40
+ */
41
+ export declare const chatLocation: (chat: Chat.Chat) => AgentService.AgentLocation;
42
+ /** Whether the chat runs as an agent (the agent parents it); synchronous, like {@link loadForChat}. */
43
+ export declare const isAgentChat: (chat: Chat.Chat) => boolean;
35
44
  export type MakeProps = Omit<Obj.MakeProps<typeof Agent>, 'instructions'> & {
36
45
  instructions: string;
37
46
  skills?: Ref.Ref<Skill.Skill>[];
@@ -1 +1 @@
1
- {"version":3,"file":"Agent.d.ts","sourceRoot":"","sources":["../../../../src/types/Agent.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAc,QAAQ,EAAO,KAAK,KAAK,IAAI,SAAS,EAAgB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE9G,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,KAAK,KAAK,OAAO,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;;;;;;;AAiBlC,qBAAa,KAAM,SAAQ,UA4C1B;CAAG;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB,UACpB,KAAK,KACX,MAAM,CAAC,MAAM,CACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC,YAAY,CAAA;CAAE,EACzD,SAAS,CAAC,mBAAmB,EAC7B,QAAQ,CAAC,OAAO,CASd,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,QAAQ,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAa7E,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,WAAW,SAAU,IAAI,CAAC,IAAI,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,SAAS,CAIzE,CAAC;AAEL,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,CAAC,EAAE,cAAc,CAAC,GAAG;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;CACrC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,UACnB,SAAS,SAET,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KACxC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAyDvB,CAAC;AAEzB;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CA0C3F,CAAC;AAEzB,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAC5C,KAAK,EACL,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,EAC/C,OAAO,CAAC,cAAc,CAUtB,CAAC"}
1
+ {"version":3,"file":"Agent.d.ts","sourceRoot":"","sources":["../../../../src/types/Agent.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,KAAK,YAAY,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,YAAY,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAc,QAAQ,EAAO,KAAK,KAAK,IAAI,SAAS,EAAgB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE9G,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,OAAO,KAAK,KAAK,OAAO,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;;;;;;;AAiBlC,qBAAa,KAAM,SAAQ,UA4C1B;CAAG;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB,UACpB,KAAK,KACX,MAAM,CAAC,MAAM,CACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC,YAAY,CAAA;CAAE,EACzD,SAAS,CAAC,mBAAmB,EAC7B,QAAQ,CAAC,OAAO,CASd,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,QAAQ,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAa7E,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,WAAW,SAAU,IAAI,CAAC,IAAI,KAAG,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,SAAS,CAIzE,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,IAAI,CAAC,IAAI,KAAG,YAAY,CAAC,aACL,CAAC;AAExD,uGAAuG;AACvG,eAAO,MAAM,WAAW,SAAU,IAAI,CAAC,IAAI,KAAG,OAG7C,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,CAAC,EAAE,cAAc,CAAC,GAAG;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;CACrC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,UACnB,SAAS,SAET,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KACxC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAyDvB,CAAC;AAEzB;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,UAAW,KAAK,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CA0C3F,CAAC;AAEzB,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAC5C,KAAK,EACL,mBAAmB,GAAG,OAAO,CAAC,iBAAiB,EAC/C,OAAO,CAAC,cAAc,CAUtB,CAAC"}